Repository: labuladong/fucking-algorithm Branch: english Commit: a3a28d05b60e Files: 71 Total size: 2.6 MB Directory structure: gitextract_jc1mrgvh/ ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── 01-algo-website-bug.md │ │ ├── 02-algo-visualize-bug.md │ │ ├── 03-chrome-extension-bug.md │ │ ├── 04-vscode-extension-bug.md │ │ ├── 05-jetbrain-plugin-bug.md │ │ └── 06-suggestion.md │ └── PULL_REQUEST_TEMPLATE.md ├── README.md ├── algorithmic-thinking/ │ ├── README.md │ ├── backtracking.md │ ├── bfs-framework.md │ ├── binary-search.md │ ├── bit-manipulation.md │ ├── difference-array.md │ ├── matrix-traversal.md │ ├── pancake-sorting.md │ ├── prefix-sum.md │ ├── probability-problems.md │ ├── set-partition.md │ ├── sliding-window.md │ ├── string-multiplication.md │ ├── two-pointers.md │ └── union-find.md ├── data-structures/ │ ├── README.md │ ├── binary-tree-practice1.md │ ├── binary-tree-practice2.md │ ├── binary-tree-summary.md │ ├── bst-part1.md │ ├── bst-part2.md │ ├── calculator.md │ ├── dijkstra.md │ ├── monotonic-queue.md │ ├── monotonic-stack.md │ ├── queue-stack.md │ ├── reverse-linked-list.md │ └── topological-sort.md ├── dynamic-programming/ │ ├── README.md │ ├── dp-framework.md │ ├── edit-distance.md │ ├── egg-drop.md │ ├── game-theory.md │ ├── house-robber.md │ ├── interval-scheduling.md │ ├── knapsack.md │ ├── longest-common-subsequence.md │ ├── magic-tower.md │ ├── optimal-substructure.md │ ├── regular-expression.md │ ├── state-compression.md │ ├── stock-problems.md │ ├── subsequence-problems.md │ └── word-break.md ├── interview/ │ ├── README.md │ ├── binary-search-in-action.md │ ├── celebrity-problem.md │ ├── count-primes.md │ ├── island-problems.md │ ├── lru-cache.md │ ├── meeting-rooms.md │ ├── missing-duplicate-element.md │ ├── palindrome-linked-list.md │ ├── random-weight.md │ ├── subset-permutation-combination.md │ └── trapping-rain-water.md ├── multi-language-solutions/ │ ├── contribution-guide.md │ └── solution_code.md └── technical/ ├── cryptography.md ├── linux-process.md ├── linux-shell.md ├── problem-solving-tips.md └── session-and-cookie.md ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/ISSUE_TEMPLATE/01-algo-website-bug.md ================================================ --- name: Website bug about: Report bug on website `labuladong.online` title: '' labels: algo-websie-bug assignees: labuladong --- **Version:** What's the extension version are you using? **Describe the bug** A clear and concise description of what the bug is. **Screenshots** If applicable, add screenshots to help explain your problem. **Platform** Mobile phone or PC? What kind of web browser? (chrome/edge/...) ================================================ FILE: .github/ISSUE_TEMPLATE/02-algo-visualize-bug.md ================================================ --- name: Algo-visualize bug about: Report bug for algo-visualize tool in website/plugins title: '' labels: algo-visualize-bug assignees: labuladong --- ================================================ FILE: .github/ISSUE_TEMPLATE/03-chrome-extension-bug.md ================================================ --- name: Chrome extension bug about: Report bug on Chrome extension title: '' labels: algo-website, chrome-extension-bug assignees: labuladong --- **Describe the bug** A clear and concise description of what the bug is. **Screenshots** If applicable, add screenshots to help explain your problem. **Platform** What kind of web browser are you using? (chrome/edge/...) ================================================ FILE: .github/ISSUE_TEMPLATE/04-vscode-extension-bug.md ================================================ --- name: VSCode extension bug about: Report bug on vscode extension title: '' labels: vscode-extension-bug assignees: labuladong --- **Version:** What's the extension version are you using? **Describe the bug** A clear and concise description of what the bug is. **Screenshots** If applicable, add screenshots to help explain your problem. ================================================ FILE: .github/ISSUE_TEMPLATE/05-jetbrain-plugin-bug.md ================================================ --- name: JetBrain plugin bug about: Report bug on JetBrain plugin title: '' labels: jb-plugin-bug assignees: labuladong --- **Version:** What's the plugin version are you using? **Describe the bug** A clear and concise description of what the bug is. **Screenshots** If applicable, add screenshots to help explain your problem. ================================================ FILE: .github/ISSUE_TEMPLATE/06-suggestion.md ================================================ --- name: Suggestion about: Suggest an idea/improvement for this project title: '' labels: feature-request assignees: labuladong --- Do you have any suggestions? Is there anything that you feel inconvenient to use? ================================================ FILE: .github/PULL_REQUEST_TEMPLATE.md ================================================ ================================================ FILE: README.md ================================================ [![Star History Chart](https://api.star-history.com/svg?repos=labuladong/fucking-algorithm&type=Date)](https://star-history.com/#labuladong/fucking-algorithm&Date) # labuladong Algo Notes This repository contains 60+ original articles based on LeetCode problems, covering all problem types and techniques. The goal is to help you **think algorithmically** — not just memorize solutions. When it comes to LeetCode, what matters is not the answer itself, but the **thought process** behind it. A repository full of raw code without explanation isn't very useful. The real value lies in understanding the frameworks and patterns that let you solve new problems on your own. Most people grind LeetCode to land a job, not to compete in programming contests. So the focus here is on **clarity and practical understanding** — building reusable mental frameworks that make algorithm problems approachable and solvable. ## Before You Start **1. Give this repo a star** if you find it helpful — it keeps me motivated to write more. **2. I recommend studying on my website, where each article links to the corresponding LeetCode problems so you can read and practice side by side. The site covers 500+ problems with step-by-step guidance:** https://labuladong.online/en/algo/ ![](pictures/website_en.png) ## Table of Contents * [Introduction](https://labuladong.online/en/algo/home/) * [Study Plans for Beginners and Quick Mastery](https://labuladong.online/en/algo/menu/plan/) * [Fast-Track Learning Plan](https://labuladong.online/en/algo/intro/quick-learning-plan/) * [Complete Learning Plan](https://labuladong.online/en/algo/intro/beginner-learning-plan/) * [How to Learn Algorithms Efficiently](https://labuladong.online/en/algo/intro/how-to-learn-algorithms/) * [How to Practice](https://labuladong.online/en/algo/intro/how-to-practice/) * [Tools and Algorithm Visualization](https://labuladong.online/en/algo/menu/tools/) * [AI Assistant for Questions](https://labuladong.online/en/algo/intro/ai-assistant/) * [Algorithm Visualization Introduction](https://labuladong.online/en/algo/intro/visualize/) * [Algorithm Game Introduction](https://labuladong.online/en/algo/intro/game/) * [Chrome Extension for LeetCode](https://labuladong.online/en/algo/intro/chrome/) * [vscode/cursor Plugin for LeetCode](https://labuladong.online/en/algo/intro/vscode/) * [JetBrains Plugin for LeetCode](https://labuladong.online/en/algo/intro/jetbrains/) * [Subscribe to Pro](https://labuladong.online/en/algo/intro/site-vip/) * [Programming Language Basics](https://labuladong.online/en/algo/menu/) * [Chapter Introduction](https://labuladong.online/en/algo/intro/programming-language-basic/) * [C++ Basics](https://labuladong.online/en/algo/programming-language-basic/cpp/) * [Java Basics](https://labuladong.online/en/algo/programming-language-basic/java/) * [Golang Basics](https://labuladong.online/en/algo/programming-language-basic/golang/) * [Python Basics](https://labuladong.online/en/algo/programming-language-basic/python/) * [JavaScript Basics](https://labuladong.online/en/algo/intro/js/) * [LeetCode Guide](https://labuladong.online/en/algo/intro/leetcode/) * [Let's Have Fun with LeetCode](https://labuladong.online/en/algo/programming-language-basic/lc-practice/) * [ACM Mode Code Template](https://labuladong.online/en/algo/intro/acm-mode/) * [Getting Started: Data Structures and Sorting](https://labuladong.online/en/algo/menu/quick-start/) * [Chapter Introduction](https://labuladong.online/en/algo/intro/data-structure-basic/) * [Basic Time Complexity](https://labuladong.online/en/algo/intro/complexity-basic/) * [Implement Dynamic Arrays](https://labuladong.online/en/algo/menu/dynamic-array/) * [Array (Sequential Storage)](https://labuladong.online/en/algo/data-structure-basic/array-basic/) * [Dynamic Array Code Implementation](https://labuladong.online/en/algo/data-structure-basic/array-implement/) * [Implement Single/Double Linked List](https://labuladong.online/en/algo/menu/linked-list/) * [Linked List (Chain Storage)](https://labuladong.online/en/algo/data-structure-basic/linkedlist-basic/) * [Linked List Code Implementation](https://labuladong.online/en/algo/data-structure-basic/linkedlist-implement/) * [Implement Snake Game](https://labuladong.online/en/algo/game/snake/) * [Array and LinkedList Variations](https://labuladong.online/en/algo/menu/arr-linked/) * [Circular Array Technique and Implementation](https://labuladong.online/en/algo/data-structure-basic/cycle-array/) * [Skip List Basics](https://labuladong.online/en/algo/data-structure-basic/skip-list-basic/) * [BitMap Principles and Implementation](https://labuladong.online/en/algo/data-structure-basic/bitmap/) * [Implement Queue and Stack](https://labuladong.online/en/algo/menu/queue-stack/) * [Queue/Stack Basic](https://labuladong.online/en/algo/data-structure-basic/queue-stack-basic/) * [Implement Queue/Stack with Linked List](https://labuladong.online/en/algo/data-structure-basic/linked-queue-stack/) * [Implement Queue/Stack with Array](https://labuladong.online/en/algo/data-structure-basic/array-queue-stack/) * [Deque Implementation](https://labuladong.online/en/algo/data-structure-basic/deque-implement/) * [Implement HashMap](https://labuladong.online/en/algo/menu/hash-table/) * [Basic Concept of HashMap](https://labuladong.online/en/algo/data-structure-basic/hashmap-basic/) * [Implement HashMap with Separate Chaining](https://labuladong.online/en/algo/data-structure-basic/hashtable-chaining/) * [Key Points to Implement Linear Probing](https://labuladong.online/en/algo/data-structure-basic/linear-probing-key-point/) * [Two Implementations of Linear Probing](https://labuladong.online/en/algo/data-structure-basic/linear-probing-code/) * [Hash Set Basic and Implementation](https://labuladong.online/en/algo/data-structure-basic/hash-set/) * [Hash Table Variations](https://labuladong.online/en/algo/menu/hash-table-variation/) * [Use Linked List to Enhance Hash Table (LinkedHashMap)](https://labuladong.online/en/algo/data-structure-basic/hashtable-with-linked-list/) * [Use Array to Enhance Hash Table (ArrayHashMap)](https://labuladong.online/en/algo/data-structure-basic/hashtable-with-array/) * [Bloom Filter Implementation](https://labuladong.online/en/algo/data-structure-basic/bloom-filter/) * [Binary Tree Structure and Traversal](https://labuladong.online/en/algo/menu/binary-tree/) * [Binary Tree Basic and Common Types](https://labuladong.online/en/algo/data-structure-basic/binary-tree-basic/) * [Binary Tree Recursive/Level Traversal](https://labuladong.online/en/algo/data-structure-basic/binary-tree-traverse-basic/) * [Use cases of DFS and BFS](https://labuladong.online/en/algo/data-structure-basic/use-case-of-dfs-bfs/) * [N-ary Tree Recursive/Level Traversal](https://labuladong.online/en/algo/data-structure-basic/n-ary-tree-traverse-basic/) * [Binary Tree Variations](https://labuladong.online/en/algo/menu/binary-tree/) * [TreeMap Structure and Visualization](https://labuladong.online/en/algo/data-structure-basic/tree-map-basic/) * [Red-Black Trees Basics and Visualization](https://labuladong.online/en/algo/data-structure-basic/rbtree-basic/) * [Trie, Digital Tree, Prefix Tree Basics and Visualization](https://labuladong.online/en/algo/data-structure-basic/trie-map-basic/) * [Basic Concept of Binary Heap](https://labuladong.online/en/algo/data-structure-basic/binary-heap-basic/) * [Binary Heap/Priority Queue Code Implementation](https://labuladong.online/en/algo/data-structure-basic/binary-heap-implement/) * [Segment Tree Basics and Visualization](https://labuladong.online/en/algo/data-structure-basic/segment-tree-basic/) * [Data Compression and Huffman Tree](https://labuladong.online/en/algo/data-structure-basic/huffman-tree/) * [Updating](https://labuladong.online/en/algo/intro/updating/) * [Graph Structure and Algorithm Overview](https://labuladong.online/en/algo/menu/graph-theory/) * [Basic Terminology in Graph Theory](https://labuladong.online/en/algo/data-structure-basic/graph-terminology/) * [Graph Structure Code Implementation](https://labuladong.online/en/algo/data-structure-basic/graph-basic/) * [Graph Structure DFS/BFS Traversal](https://labuladong.online/en/algo/data-structure-basic/graph-traverse-basic/) * [Eulerian Graph and One-Stroke Game](https://labuladong.online/en/algo/data-structure-basic/eulerian-graph/) * [Graph Shortest Path Algorithms Overview](https://labuladong.online/en/algo/data-structure-basic/graph-shortest-path/) * [Minimum Spanning Tree Algorithms Overview](https://labuladong.online/en/algo/data-structure-basic/graph-minimum-spanning-tree/) * [Basic Concept of Union Find Algorithm](https://labuladong.online/en/algo/data-structure-basic/union-find-basic/) * [Updating](https://labuladong.online/en/algo/intro/updating/) * [Implement and Visualize 10 Sorting Algorithms](https://labuladong.online/en/algo/menu/sorting/) * [Chapter Introduction](https://labuladong.online/en/algo/intro/sorting/) * [Key Metrics of Sorting Algorithms](https://labuladong.online/en/algo/data-structure-basic/sort-basic/) * [Explore Selection Sort in Depth](https://labuladong.online/en/algo/data-structure-basic/select-sort/) * [Bubble Sort with Stability](https://labuladong.online/en/algo/data-structure-basic/bubble-sort/) * [Insertion Sort with Reverse Thinking](https://labuladong.online/en/algo/data-structure-basic/insertion-sort/) * [Shell Sort - Better than O(N^2)](https://labuladong.online/en/algo/data-structure-basic/shell-sort/) * [Quick Sort and Binary Tree Preorder](https://labuladong.online/en/algo/data-structure-basic/quick-sort/) * [Merge Sort and Binary Tree Postorder](https://labuladong.online/en/algo/data-structure-basic/merge-sort/) * [Heap Sort and Binary Heap](https://labuladong.online/en/algo/data-structure-basic/heap-sort/) * [Counting Sort: A New Pespective on Sorting](https://labuladong.online/en/algo/data-structure-basic/counting-sort/) * [Bucket Sort](https://labuladong.online/en/algo/data-structure-basic/bucket-sort/) * [Radix Sort](https://labuladong.online/en/algo/data-structure-basic/radix-sort/) * [Updating](https://labuladong.online/en/algo/intro/updating/) * [Chapter 0. Classic Problem Solving Templates](https://labuladong.online/en/algo/menu/core/) * [Chapter Introduction](https://labuladong.online/en/algo/intro/core-intro/) * [How to Think About Data Structure and Algorithm](https://labuladong.online/en/algo/essential-technique/algorithm-summary/) * [Two Pointer Techniques for Linked List Problems](https://labuladong.online/en/algo/essential-technique/linked-list-skills-summary/) * [Two Pointer Techniques for Array Problems](https://labuladong.online/en/algo/essential-technique/array-two-pointers-summary/) * [Sliding Window Algorithm Code Template](https://labuladong.online/en/algo/essential-technique/sliding-window-framework/) * [Thinking Recursion Algorithms from Binary Tree Perspective](https://labuladong.online/en/algo/essential-technique/binary-tree-summary/) * [One Perspective + Two Thinking Patterns to Master Recursion](https://labuladong.online/en/algo/essential-technique/understand-recursion/) * [Dynamic Programming Common Patterns and Code Template](https://labuladong.online/en/algo/essential-technique/dynamic-programming-framework/) * [Backtracking Algorithm Common Patterns and Code Template](https://labuladong.online/en/algo/essential-technique/backtrack-framework/) * [BFS Algorithm Common Patterns and Code Template](https://labuladong.online/en/algo/essential-technique/bfs-framework/) * [Backtracking Algorithm to Solve All Permutation/Combination/Subset Problems](https://labuladong.online/en/algo/essential-technique/permutation-combination-subset-all-in-one/) * [Greedy Algorithms Principles and Techniques](https://labuladong.online/en/algo/essential-technique/greedy/) * [Divide and Conquer Principles and Techniques](https://labuladong.online/en/algo/essential-technique/divide-and-conquer/) * [Time and Space Complexity Analysis Practical Guide](https://labuladong.online/en/algo/essential-technique/complexity-analysis/) * [Chapter 1. Data Structure Algorithms](https://labuladong.online/en/algo/menu/ds/) * [Linked List Algorithm](https://labuladong.online/en/algo/menu/linked-list/) * [Two Pointer Techniques for Linked List Problems](https://labuladong.online/en/algo/essential-technique/linked-list-skills-summary/) * [Exercise: Two Pointer Techniques for Linked List](https://labuladong.online/en/algo/problem-set/linkedlist-two-pointers/) * [Tricks to Reverse a Linked List Recursively](https://labuladong.online/en/algo/data-structure/reverse-linked-list-recursion/) * [How to Determine a Palindrome Linked List](https://labuladong.online/en/algo/data-structure/palindrome-linked-list/) * [Array Algorithm](https://labuladong.online/en/algo/menu/array/) * [Two Pointer Techniques for Array Problems](https://labuladong.online/en/algo/essential-technique/array-two-pointers-summary/) * [Match Three Game](https://labuladong.online/en/algo/game/match-three/) * [Tricks to Traverse a 2D Array](https://labuladong.online/en/algo/practice-in-action/2d-array-traversal-summary/) * [Exercise: Two Pointer Techniques for Array](https://labuladong.online/en/algo/problem-set/array-two-pointers/) * [Game of Life](https://labuladong.online/en/algo/game/life-game/) * [One Trick to Solve All N-Sum Problems](https://labuladong.online/en/algo/practice-in-action/nsum/) * [Prefix Sum Array Technique](https://labuladong.online/en/algo/data-structure/prefix-sum/) * [Exercise: Prefix Sum Techniques](https://labuladong.online/en/algo/problem-set/perfix-sum/) * [Difference Array Technique](https://labuladong.online/en/algo/data-structure/diff-array/) * [Sliding Window Algorithm Code Template](https://labuladong.online/en/algo/essential-technique/sliding-window-framework/) * [Exercise: Sliding Window In Action](https://labuladong.online/en/algo/problem-set/sliding-window/) * [Sliding Window: Rabin Karp Algorithm](https://labuladong.online/en/algo/practice-in-action/rabinkarp/) * [Binary Search Algorithm Code Template](https://labuladong.online/en/algo/essential-technique/binary-search-framework/) * [Binary Search Follow-up](https://labuladong.online/en/algo/essential-technique/binary-search-left-open/) * [Binary Search in Action](https://labuladong.online/en/algo/frequency-interview/binary-search-in-action/) * [Exercise: Binary Search Algorithm](https://labuladong.online/en/algo/problem-set/binary-search/) * [Weighted Random Selection Algorithm](https://labuladong.online/en/algo/frequency-interview/random-pick-with-weight/) * [Advantage Shuffle Algorithm](https://labuladong.online/en/algo/practice-in-action/advantage-shuffle/) * [Stack/Queue Algorithm](https://labuladong.online/en/algo/menu/queue-stack/) * [Implement Stack with Queue, Implement Queue with Stack](https://labuladong.online/en/algo/data-structure/stack-queue/) * [Exercise: Stack Problems on LeetCode](https://labuladong.online/en/algo/problem-set/stack/) * [Exercise: Bracket Problems on LeetCode](https://labuladong.online/en/algo/problem-set/parentheses/) * [Exercise: Queue Problems on LeetCode](https://labuladong.online/en/algo/problem-set/queue/) * [Monotonic Stack Code Template](https://labuladong.online/en/algo/data-structure/monotonic-stack/) * [Exercise: Monotonic Stack Problems on LeetCode](https://labuladong.online/en/algo/problem-set/monotonic-stack/) * [Monotonic Queue to Solve Sliding Window Problems](https://labuladong.online/en/algo/data-structure/monotonic-queue/) * [Exercise: Monotonic Queue Implementation and Leetcode Problems](https://labuladong.online/en/algo/problem-set/monotonic-queue/) * [Binary Tree Algorithm](https://labuladong.online/en/algo/menu/binary-tree/) * [Thinking Recursion Algorithms from Binary Tree Perspective](https://labuladong.online/en/algo/essential-technique/binary-tree-summary/) * [Binary Tree in Action (Traversal)](https://labuladong.online/en/algo/data-structure/binary-tree-part1/) * [Binary Tree in Action (Construction)](https://labuladong.online/en/algo/data-structure/binary-tree-part2/) * [Binary Tree in Action (Post-order)](https://labuladong.online/en/algo/data-structure/binary-tree-part3/) * [Binary Tree in Action (Serialization)](https://labuladong.online/en/algo/data-structure/serialize-and-deserialize-binary-tree/) * [Binary Search Tree in Action (In-order)](https://labuladong.online/en/algo/data-structure/bst-part1/) * [Binary Search Tree in Action (Basic Operations)](https://labuladong.online/en/algo/data-structure/bst-part2/) * [Binary Search Tree in Action (Construction)](https://labuladong.online/en/algo/data-structure/bst-part3/) * [Binary Search Tree in Action (Post-order)](https://labuladong.online/en/algo/data-structure/bst-part4/) * [Master Binary Tree Problems](https://labuladong.online/en/algo/menu/100-bt/) * [Chapter Introduction](https://labuladong.online/en/algo/intro/binary-tree-practice/) * [Exercise: Binary Tree Traversal I](https://labuladong.online/en/algo/problem-set/binary-tree-traverse-i/) * [Exercise: Binary Tree Traversal II](https://labuladong.online/en/algo/problem-set/binary-tree-traverse-ii/) * [Exercise: Binary Tree Traversal III](https://labuladong.online/en/algo/problem-set/binary-tree-traverse-iii/) * [Exercise: Binary Tree Divide and Conquer I](https://labuladong.online/en/algo/problem-set/binary-tree-divide-i/) * [Exercise: Binary Tree Divide and Conquer II](https://labuladong.online/en/algo/problem-set/binary-tree-divide-ii/) * [Exercise: Binary Tree Combine Two Views](https://labuladong.online/en/algo/problem-set/binary-tree-combine-two-view/) * [Exercise: Binary Tree Post-order I](https://labuladong.online/en/algo/problem-set/binary-tree-post-order-i/) * [Exercise: Binary Tree Post-order II](https://labuladong.online/en/algo/problem-set/binary-tree-post-order-ii/) * [Exercise: Binary Tree Post-order III](https://labuladong.online/en/algo/problem-set/binary-tree-post-order-iii/) * [Exercise: Binary Tree Level I](https://labuladong.online/en/algo/problem-set/binary-tree-level-i/) * [Exercise: Binary Tree Level II](https://labuladong.online/en/algo/problem-set/binary-tree-level-ii/) * [Exercise: Binary Search Tree I](https://labuladong.online/en/algo/problem-set/bst1/) * [Exercise: Binary Search Tree II](https://labuladong.online/en/algo/problem-set/bst2/) * [Binary Tree Follow-up](https://labuladong.online/en/algo/menu/more-bt/) * [Lowest Common Ancestor All in One](https://labuladong.online/en/algo/practice-in-action/lowest-common-ancestor-summary/) * [Trick: How to Count Nodes in a Complete Binary Tree](https://labuladong.online/en/algo/data-structure/count-complete-tree-nodes/) * [Trick: Lazy Expansion of a Multiway Tree](https://labuladong.online/en/algo/data-structure/flatten-nested-list-iterator/) * [Follow-up: Merge Sort Implementation and Applications](https://labuladong.online/en/algo/practice-in-action/merge-sort/) * [Follow-up: Quick Sort Implementation and Applications](https://labuladong.online/en/algo/practice-in-action/quick-sort/) * [Trick: Traverse Binary Tree with Stack](https://labuladong.online/en/algo/data-structure/iterative-traversal-binary-tree/) * [Design Data Structures](https://labuladong.online/en/algo/menu/design/) * [Implementing LRU Cache like Building a Lego](https://labuladong.online/en/algo/data-structure/lru-cache/) * [Implementing LFU Cache like Building a Lego](https://labuladong.online/en/algo/frequency-interview/lfu/) * [How to Deleting Array Element in O(1) Time](https://labuladong.online/en/algo/data-structure/random-set/) * [Exercise: Hash Table Problems on LeetCode](https://labuladong.online/en/algo/problem-set/hash-table/) * [Exercise: Priority Queue Problems on LeetCode](https://labuladong.online/en/algo/problem-set/binary-heap/) * [Implementing TreeMap/TreeSet](https://labuladong.online/en/algo/data-structure-basic/tree-map-implement/) * [Basic Segment Tree Implementation](https://labuladong.online/en/algo/data-structure/segment-tree-implement/) * [Dynamic Segment Tree Implementation](https://labuladong.online/en/algo/data-structure/segment-tree-dynamic/) * [Lazy Update Segment Tree Implementation](https://labuladong.online/en/algo/data-structure/segment-tree-lazy-update/) * [Exercise: Segment Tree Problems](https://labuladong.online/en/algo/problem-set/segment-tree/) * [Implementing Trie Tree](https://labuladong.online/en/algo/data-structure/trie-implement/) * [Exercise: Trie Problems on LeetCode](https://labuladong.online/en/algo/problem-set/trie/) * [Designing an Exam Room Algorithm](https://labuladong.online/en/algo/frequency-interview/exam-room/) * [Exercise: Classic Design Problems on LeetCode](https://labuladong.online/en/algo/problem-set/ds-design/) * [Implement Huffman Coding Compression](https://labuladong.online/en/algo/data-structure/huffman-tree-implementation/) * [Implement Consistent Hashing Algorithm](https://labuladong.online/en/algo/data-structure/consistent-hashing/) * [How to Implement a Calculator](https://labuladong.online/en/algo/data-structure/implement-calculator/) * [Implementing Median Algorithm with Two Binary Heaps](https://labuladong.online/en/algo/practice-in-action/find-median-from-data-stream/) * [Removing Duplicates from an Array (Hard Version)](https://labuladong.online/en/algo/frequency-interview/remove-duplicate-letters/) * [Graph Algorithm](https://labuladong.online/en/algo/menu/graph/) * [How to Determine a Bipartite Graph](https://labuladong.online/en/algo/data-structure/bipartite-graph/) * [Hierholzer Algorithm to Find Eulerian Path](https://labuladong.online/en/algo/data-structure/eulerian-graph-hierholzer/) * [Exercise: Eulerian Path](https://labuladong.online/en/algo/problem-set/eulerian-path/) * [Cycle Detection Algorithm](https://labuladong.online/en/algo/data-structure/cycle-detection/) * [Topological Sort Algorithm](https://labuladong.online/en/algo/data-structure/topological-sort/) * [Union-Find Algorithm](https://labuladong.online/en/algo/data-structure/union-find/) * [Exercise: Union-Find Problems on LeetCode](https://labuladong.online/en/algo/problem-set/union-find/) * [Dijkstra Algorithm](https://labuladong.online/en/algo/data-structure/dijkstra/) * [Dijkstra Algorithm with Restrictions](https://labuladong.online/en/algo/data-structure/dijkstra-follow-up/) * [Exercise: Dijkstra Problems](https://labuladong.online/en/algo/problem-set/dijkstra/) * [A* Algorithm](https://labuladong.online/en/algo/data-structure/a-star/) * [Kruskal Minimum Spanning Tree Algorithm](https://labuladong.online/en/algo/data-structure/kruskal/) * [Prim Minimum Spanning Tree Algorithm](https://labuladong.online/en/algo/data-structure/prim/) * [Chapter 2. Brute Force Search](https://labuladong.online/en/algo/menu/braute-force-search/) * [DFS and Backtracking Algorithm](https://labuladong.online/en/algo/menu/dfs/) * [Backtracking Algorithm Common Patterns and Code Template](https://labuladong.online/en/algo/essential-technique/backtrack-framework/) * [Backtracking in Action: Sudoku and N-Queens](https://labuladong.online/en/algo/practice-in-action/sudoku-nqueue/) * [Implement Sudoku Cheat](https://labuladong.online/en/algo/game/sudoku/) * [Backtracking Algorithm to Solve All Permutation/Combination/Subset Problems](https://labuladong.online/en/algo/essential-technique/permutation-combination-subset-all-in-one/) * [Some Questions About Backtracking and DFS Algorithms](https://labuladong.online/en/algo/essential-technique/backtrack-vs-dfs/) * [Solve All Island Problems with DFS](https://labuladong.online/en/algo/frequency-interview/island-dfs-summary/) * [Minesweeper Game II](https://labuladong.online/en/algo/game/minesweeper-ii/) * [Ball and Box: Two Perspectives of Backtracking Enumeration](https://labuladong.online/en/algo/practice-in-action/two-views-of-backtrack/) * [Backtracking Algorithm Practice: Generating Valid Parentheses](https://labuladong.online/en/algo/practice-in-action/generate-parentheses/) * [Backtracking Algorithm Practice: Partitioning k Subsets](https://labuladong.online/en/algo/practice-in-action/partition-to-k-equal-sum-subsets/) * [Exercise: Backtracking Problems on LeetCode I](https://labuladong.online/en/algo/problem-set/backtrack-i/) * [Exercise: Backtracking Problems on LeetCode II](https://labuladong.online/en/algo/problem-set/backtrack-ii/) * [Exercise: Backtracking Problems on LeetCode III](https://labuladong.online/en/algo/problem-set/backtrack-iii/) * [BFS Algorithm](https://labuladong.online/en/algo/menu/bfs/) * [BFS Algorithm Common Patterns and Code Template](https://labuladong.online/en/algo/essential-technique/bfs-framework/) * [Solve Maze Game](https://labuladong.online/en/algo/game/maze/) * [Huarong Road Game](https://labuladong.online/en/algo/game/huarong-road/) * [Connect Two Game](https://labuladong.online/en/algo/game/connect-two/) * [Exercise: BFS Problems on LeetCode I](https://labuladong.online/en/algo/problem-set/bfs/) * [Exercise: BFS Problems on LeetCode II](https://labuladong.online/en/algo/problem-set/bfs-ii/) * [Chapter 3. Dynamic Programming Algorithms](https://labuladong.online/en/algo/menu/dp/) * [Basic DP Techniques](https://labuladong.online/en/algo/menu/dp-basic/) * [Dynamic Programming Common Patterns and Code Template](https://labuladong.online/en/algo/essential-technique/dynamic-programming-framework/) * [How to Design Transition Equations](https://labuladong.online/en/algo/dynamic-programming/longest-increasing-subsequence/) * [How to Determine the Base Case and Initial Values for Memoization?](https://labuladong.online/en/algo/dynamic-programming/memo-fundamental/) * [Two Perspectives of Dynamic Programming Enumeration](https://labuladong.online/en/algo/dynamic-programming/two-views-of-dp/) * [How to Convert Backtracking to Dynamic Programming](https://labuladong.online/en/algo/dynamic-programming/word-break/) * [Optimize Space Complexity for Dynamic Programming](https://labuladong.online/en/algo/dynamic-programming/space-optimization/) * [Clarifying Some Questions About Dynamic Programming](https://labuladong.online/en/algo/dynamic-programming/faq-summary/) * [Subsequence Problems](https://labuladong.online/en/algo/menu/subsequence/) * [Classic DP: Edit Distance](https://labuladong.online/en/algo/dynamic-programming/edit-distance/) * [DP Design: Maximum Subarray](https://labuladong.online/en/algo/dynamic-programming/maximum-subarray/) * [Classic DP: Longest Common Subsequence](https://labuladong.online/en/algo/dynamic-programming/longest-common-subsequence/) * [Subsequence Problem Patterns for DP](https://labuladong.online/en/algo/dynamic-programming/subsequence-problem/) * [Knapsack Problems](https://labuladong.online/en/algo/menu/knapsack/) * [Classic DP: 0-1 Knapsack Problem](https://labuladong.online/en/algo/dynamic-programming/knapsack1/) * [Classic DP: Subset Knapsack Problem](https://labuladong.online/en/algo/dynamic-programming/knapsack2/) * [Classic DP: Unbounded Knapsack Problem](https://labuladong.online/en/algo/dynamic-programming/knapsack3/) * [A Variant of the Knapsack Problem: Target Sum](https://labuladong.online/en/algo/dynamic-programming/target-sum/) * [Dynamic Programming Game](https://labuladong.online/en/algo/menu/dp-game/) * [Classic DP: Minimum Path Sum](https://labuladong.online/en/algo/dynamic-programming/minimum-path-sum/) * [Play Dungeon Game with DP](https://labuladong.online/en/algo/dynamic-programming/magic-tower/) * [Play Freedom Trail with DP](https://labuladong.online/en/algo/dynamic-programming/freedom-trail/) * [Save Money on Your Trip: Weighted Shortest Path](https://labuladong.online/en/algo/dynamic-programming/cheap-travel/) * [Multi-source shortest path: Floyd algorithm](https://labuladong.online/en/algo/data-structure/floyd/) * [Classic DP: Regular Expression Matching](https://labuladong.online/en/algo/dynamic-programming/regular-expression-matching/) * [Classic DP: Egg Drop](https://labuladong.online/en/algo/dynamic-programming/egg-drop/) * [Classic DP: Burst Balloons](https://labuladong.online/en/algo/dynamic-programming/burst-balloons/) * [Classic DP: Game Theory](https://labuladong.online/en/algo/dynamic-programming/game-theory/) * [One Method to Solve All House Robber Problems on LeetCode](https://labuladong.online/en/algo/dynamic-programming/house-robber/) * [One Method to Solve all Stock Problems on LeetCode](https://labuladong.online/en/algo/dynamic-programming/stock-problem-summary/) * [Dynamic Programming ProblemSet](https://labuladong.online/en/algo/menu/dp-basic/) * [Exercise: Rob House Pattern](https://labuladong.online/en/algo/problem-set/rob-house/) * [Exercise: Knapsack Problems](https://labuladong.online/en/algo/problem-set/knapsack/) * [Exercise: Dynamic Programming Problems I](https://labuladong.online/en/algo/problem-set/dynamic-programming-i/) * [Exercise: Dynamic Programming Problems II](https://labuladong.online/en/algo/problem-set/dynamic-programming-ii/) * [Greedy](https://labuladong.online/en/algo/menu/greedy/) * [Greedy Algorithms Principles and Techniques](https://labuladong.online/en/algo/essential-technique/greedy/) * [Two Approaches for Gas Station Problem](https://labuladong.online/en/algo/frequency-interview/gas-station-greedy/) * [Greedy Algorithm for Interval Scheduling Problem](https://labuladong.online/en/algo/frequency-interview/interval-scheduling/) * [Scan Line Technique: Scheduling Meeting Rooms](https://labuladong.online/en/algo/frequency-interview/scan-line-technique/) * [Cut Video with a Greedy Algorithm](https://labuladong.online/en/algo/frequency-interview/cut-video/) * [Chapter 4. Other Common Techniques](https://labuladong.online/en/algo/menu/other/) * [Mathematical Techniques](https://labuladong.online/en/algo/menu/math/) * [LeetCode Problems with One Line Solution](https://labuladong.online/en/algo/frequency-interview/one-line-solutions/) * [Common Bit Manipulation Techniques](https://labuladong.online/en/algo/frequency-interview/bitwise-operation/) * [Essential Math Techniques](https://labuladong.online/en/algo/essential-technique/math-techniques-summary/) * [Minesweeper Game I](https://labuladong.online/en/algo/game/minesweeper/) * [Random Algorithms in Games](https://labuladong.online/en/algo/frequency-interview/random-algorithm/) * [Two Classic Factorial Problems on LeetCode](https://labuladong.online/en/algo/frequency-interview/factorial-problems/) * [How to Efficiently Count Prime Numbers](https://labuladong.online/en/algo/frequency-interview/print-prime-number/) * [How to Find Missing and Duplicate Elements](https://labuladong.online/en/algo/frequency-interview/mismatch-set/) * [Interesting Probability Problems](https://labuladong.online/en/algo/frequency-interview/probability-problem/) * [Exercise: Math Tricks](https://labuladong.online/en/algo/problem-set/math-tricks/) * [Classic Interview Problems](https://labuladong.online/en/algo/menu/interview/) * [How to Efficiently Solve the Trapping Rain Water Problem](https://labuladong.online/en/algo/frequency-interview/trapping-rain-water/) * [One Article to Solve All Ugly Number Problems on LeetCode](https://labuladong.online/en/algo/frequency-interview/ugly-number-summary/) * [One Method to Solve Three Interval Problems on LeetCode](https://labuladong.online/en/algo/practice-in-action/interval-problem-summary/) * [Split Array into Consecutive Subsequences](https://labuladong.online/en/algo/practice-in-action/split-array-into-consecutive-subsequences/) * [Pancake Sorting Algorithm](https://labuladong.online/en/algo/frequency-interview/pancake-sorting/) * [String Multiplication Calculation](https://labuladong.online/en/algo/practice-in-action/multiply-strings/) * [How to Determine if a Rectangle is Perfect](https://labuladong.online/en/algo/frequency-interview/perfect-rectangle/) * [More Topics](https://labuladong.online/en/algo/menu/appendix/) * [Computer Science](https://labuladong.online/en/algo/menu/computer-basics/) * [Frontend Development Introduction for AI Era](https://labuladong.online/en/algo/computer-science/frontend-introduction/) * [Introduction to Modern Encryption](https://labuladong.online/en/algo/computer-science/encryption-intro/) * [Understand Session and Cookie](https://labuladong.online/en/algo/other-skills/session-and-cookie/) * [Understanding JSON Web Token (JWT)](https://labuladong.online/en/algo/computer-science/how-jwt-works/) * [Authentication vs. Authorization](https://labuladong.online/en/algo/computer-science/authentication-vs-authorization/) * [Understanding OAuth 2.0 Authorization Framework](https://labuladong.online/en/algo/computer-science/oauth2-explained/) * [OAuth 2.0 and OIDC Authentication](https://labuladong.online/en/algo/computer-science/oidc/) * [OAuth 2.0 and PKCE](https://labuladong.online/en/algo/computer-science/pkce/) * [Understanding Single Sign-On (SSO)](https://labuladong.online/en/algo/computer-science/sso/) * [Certificate and CA](https://labuladong.online/en/algo/computer-science/certificate-and-ca/) * [TLS Key Exchange](https://labuladong.online/en/algo/computer-science/tls-key-exchange/) * [Mutual TLS Authentication](https://labuladong.online/en/algo/computer-science/mtls/) * [Introduction to Linux File System](https://labuladong.online/en/algo/other-skills/linux-file-system/) * [Linux Processes, Threads and File Descriptors](https://labuladong.online/en/algo/other-skills/linux-process/) * [Pitfalls of Linux Pipeline](https://labuladong.online/en/algo/other-skills/linux-pipeline/) * [Linux Shell Tips](https://labuladong.online/en/algo/other-skills/linux-shell/) * [LSM Tree in Storage System](https://labuladong.online/en/algo/other-skills/lsm-tree/) * [Updating](https://labuladong.online/en/algo/intro/updating/) * [Design Pattern](https://labuladong.online/en/algo/menu/design-pattern/) * [Design Pattern: Singleton](https://labuladong.online/en/algo/design-pattern/singleton/) * [Design Pattern: Factory Method](https://labuladong.online/en/algo/design-pattern/factory-method/) * [Design Pattern: Abstract Factory](https://labuladong.online/en/algo/design-pattern/abstract-factory/) * [Design Pattern: Builder](https://labuladong.online/en/algo/design-pattern/builder/) * [Design Pattern: Prototype](https://labuladong.online/en/algo/design-pattern/prototype/) * [Design Pattern: Adapter](https://labuladong.online/en/algo/design-pattern/adapter/) * [Design Pattern: Composite](https://labuladong.online/en/algo/design-pattern/composite/) * [Design Pattern: Decorator](https://labuladong.online/en/algo/design-pattern/decorator/) * [Design Pattern: Bridge](https://labuladong.online/en/algo/design-pattern/bridge/) * [Design Pattern: Observer](https://labuladong.online/en/algo/design-pattern/observer/) * [Design Pattern: Strategy](https://labuladong.online/en/algo/design-pattern/strategy/) * [Updating](https://labuladong.online/en/algo/intro/updating/) ================================================ FILE: algorithmic-thinking/README.md ================================================ # 算法思维系列 本章包含一些常用的算法技巧,比如前缀和、回溯思想、位操作、双指针、如何正确书写二分查找等等。 欢迎关注我的公众号 labuladong,查看全部文章: ![labuladong二维码](../pictures/table_qr2.jpg) ![labuladong二维码](../pictures/qrcode.jpg) ================================================ FILE: algorithmic-thinking/backtracking.md ================================================ ::: info Prerequisites Before reading this article, you should first learn: - [Binary Tree Basics](https://labuladong.online/en/algo/data-structure-basic/binary-tree-basic/) - [Binary Tree Traversal Framework](https://labuladong.online/en/algo/data-structure-basic/binary-tree-traverse-basic/) - [N-ary Tree Structure and Traversal Framework](https://labuladong.online/en/algo/data-structure-basic/n-ary-tree-traverse-basic/) ::: This article answers several questions: What is backtracking? What techniques help with backtracking problems? How should you study backtracking? Is there a pattern to backtracking code? Backtracking and DFS are essentially the same algorithm. I explain the subtle differences in [FAQ on DFS and Backtracking](https://labuladong.online/en/algo/essential-technique/backtrack-vs-dfs/). This article focuses on backtracking and won't dive into that distinction. **At a high level, solving a backtracking problem is really just traversing a decision tree. Each leaf node holds a valid answer. Traverse the entire tree, collect all the answers at the leaf nodes, and you've got every valid solution.** Standing at any node on the backtracking tree, you only need to think about 3 things: 1. **Path**: the choices you've already made. 2. **Choice list**: the choices you can currently make. 3. **Termination condition**: the point where you've reached the bottom of the decision tree and can't make any more choices. Don't worry if these don't fully click yet—we'll use the classic "permutations" problem to make everything concrete. Just keep them in mind for now. On the code side, here's the backtracking framework: ```python result = [] def backtrack(path, choice_list): if termination_condition_met: result.add(path) return for choice in choice_list: make_choice backtrack(path, choice_list) undo_choice ``` **The core idea is the recursion inside the for loop: "make a choice" before the recursive call, and "undo the choice" after it.** Dead simple. But what does "make a choice" and "undo a choice" really mean? What's the underlying principle behind this framework? Let's use the "permutations" problem to clear things up! ## Permutations Problem Breakdown LeetCode problem 46, "[Permutations](https://leetcode.cn/problems/permutations/)," gives you an array `nums` and asks you to return all possible permutations of those numbers. ::: info Note **The permutations problem we're discussing here doesn't involve duplicate numbers. I cover the extension with duplicates in [Backtracking: 9 Types of Permutation/Combination/Subset Problems](https://labuladong.online/en/algo/essential-technique/permutation-combination-subset-all-in-one/).** Also, some of you may have seen permutation code that uses `swap` to exchange elements, which is different from my approach here. These are two different enumeration strategies for backtracking, and I'll explain both in [Ball-in-Box Model: Two Perspectives on Backtracking Enumeration](https://labuladong.online/en/algo/practice-in-action/two-views-of-backtrack/). It's not the right time to introduce that approach yet—just follow along with my method for now. ::: You probably did permutation and combination problems back in high school math. You know that `n` distinct numbers have `n!` total permutations. But how did you actually enumerate them back then? Say you're given `[1,2,3]`. You wouldn't just randomly guess permutations—you'd do something like this: Fix the first position as 1, then the second can be 2, which forces the third to be 3. Then change the second to 3, forcing the third to be 2. Then change the first position to 2, and enumerate the remaining two positions... That's backtracking! You already knew how to do it intuitively in high school. Some of you might even draw out the backtracking tree like this: ![](../pictures/backtracking/1.jpg) Just traverse this tree from the root and record the numbers along each path—those are all the permutations. **Let's call this the "decision tree" of the backtracking algorithm.** **Why "decision tree"? Because at every node, you're making a decision.** For example, say you're standing at the red node below: ![](../pictures/backtracking/2.jpg) You're choosing right now—you can go down the branch for 1 or the branch for 3. Why only 1 and 3? Because the branch for 2 is behind you; you already made that choice, and permutations don't allow reusing numbers. **Now we can clarify those terms from earlier: `[2]` is the "path"—the record of choices you've already made. `[1,3]` is the "choice list"—the choices currently available to you. The "termination condition" is when you reach a leaf node at the bottom of the tree, which here means the choice list is empty.** Once you understand these terms, you can think of "path" and "choice list" as attributes of each node in the decision tree. The diagram below shows the attributes for a few blue nodes: ![](../pictures/backtracking/3.jpg) **The `backtrack` function we define is essentially a pointer walking through this tree, maintaining each node's attributes correctly. Whenever it reaches a leaf node, the "path" at that point is a complete permutation.** Going one step further: how do you traverse a tree? That shouldn't be too hard. Recall what we discussed in [A Framework for Learning Data Structures](https://labuladong.online/en/algo/essential-technique/algorithm-summary/)—all kinds of search problems are really tree traversal problems. The N-ary tree traversal framework looks like this: ```java void traverse(TreeNode root) { for (TreeNode child : root.childern) { // operations needed at the preorder position traverse(child); // operations needed at the postorder position } } ``` ::: info Info Sharp readers might wonder: shouldn't the pre-order and post-order positions in the N-ary tree DFS traversal framework be outside the for loop, not inside it? Why did they move inside the for loop in backtracking? Good catch. The pre-order and post-order positions in DFS should indeed be outside the for loop. However, backtracking is slightly different from standard DFS. I'll explain this in detail in [FAQ on Backtracking/DFS](https://labuladong.online/en/algo/essential-technique/backtrack-vs-dfs/). For now, you can set this question aside. ::: As for pre-order and post-order traversal, they're just two useful points in time. Let me draw a picture to make it clear: ![](../pictures/backtracking/4.jpg) **Pre-order code executes at the moment just before entering a node; post-order code executes at the moment just after leaving a node.** Remember what we said: "path" and "choices" are attributes of each node, and the function needs to handle these attributes correctly as it walks the tree. That means we need to take action at these two special time points: ![](../pictures/backtracking/5.jpg) Now, does this core backtracking framework make sense? ```python for choice in choice_list: # make a choice remove choice from choice_list path.add(choice) backtrack(path, choice_list) # undo the choice path.remove(choice) add choice back to choice_list ``` **Just make a choice before the recursion and undo it after the recursion**, and you'll correctly maintain each node's choice list and path. Now let's look at the actual permutations code: ```java class Solution { List> res = new LinkedList<>(); // Main function, input a set of unique numbers, return their permutations List> permute(int[] nums) { // Record "path" LinkedList track = new LinkedList<>(); // Elements in the "path" will be marked as true to avoid reuse boolean[] used = new boolean[nums.length]; backtrack(nums, track, used); return res; } // Path: recorded in track // Selection list: elements in nums that are not in track (used[i] is false) // Termination condition: all elements in nums appear in track void backtrack(int[] nums, LinkedList track, boolean[] used) { // Trigger termination condition if (track.size() == nums.length) { res.add(new LinkedList(track)); return; } for (int i = 0; i < nums.length; i++) { // Exclude invalid choices if (used[i]) { /** ![](../pictures/backtracking/6.jpg) */ // nums[i] is already in track, skip continue; } // Make a choice track.add(nums[i]); used[i] = true; // Enter the next level of the decision tree backtrack(nums, track, used); // Cancel the choice track.removeLast(); used[i] = false; } } } ``` We made a small tweak here: instead of explicitly tracking a "choice list," we use a `used` array to exclude elements already in `track`, effectively deriving the current choice list: ![](../pictures/backtracking/6.jpg) And that's how we've used the permutations problem to explain the underlying mechanics of backtracking. Of course, this isn't the most efficient way to solve permutations—you may have seen solutions that don't even use a `used` array and instead swap elements directly. That approach is a bit harder to understand, so I'll cover it in [Ball-in-Box Model: Two Perspectives on Backtracking Enumeration](https://labuladong.online/en/algo/practice-in-action/two-views-of-backtrack/). One thing to be clear about: no matter how you optimize, it still fits the backtracking framework, and the time complexity can't go below O(N!). Enumerating the entire decision tree is unavoidable—you ultimately need to produce all N! permutations. **This is a key characteristic of backtracking: unlike dynamic programming where overlapping subproblems allow optimization, backtracking is pure brute-force enumeration, so the complexity is generally high.** ## Final Summary Backtracking is essentially an N-ary tree traversal problem. The key is to perform operations at the pre-order and post-order traversal positions. Here's the framework: ```python def backtrack(...): for choice in choice_list: make_choice backtrack(...) undo_choice ``` **When writing the `backtrack` function, you need to maintain the "path" (choices made so far) and the current "choice list." When the "termination condition" is triggered, add the "path" to your result set.** Here's something interesting to think about: doesn't backtracking look a lot like dynamic programming? We've emphasized many times in the dynamic programming series that DP requires three things: "state," "choices," and "base case." Don't those map directly to "path," "choice list," and "termination condition"? Both dynamic programming and backtracking abstract the problem into a tree structure, but the two algorithms take completely different approaches. You'll see the deeper distinctions and connections between them in [Binary Tree Essentials (Overview)](https://labuladong.online/en/algo/essential-technique/binary-tree-summary/). ================================================ FILE: algorithmic-thinking/bfs-framework.md ================================================ ::: info Prerequisites Before reading this article, you need to learn: - [Binary Tree Recursive/Level Order Traversal](https://labuladong.online/en/algo/data-structure-basic/binary-tree-traverse-basic/) - [N-ary Tree Recursive/Level Order Traversal](https://labuladong.online/en/algo/data-structure-basic/n-ary-tree-traverse-basic/) - [DFS/BFS Traversal of Graphs](https://labuladong.online/en/algo/data-structure-basic/graph-traverse-basic/) ::: I have said many times, algorithms like DFS, backtracking, and BFS are actually about turning a problem into a tree structure and then traversing that tree with brute-force search. So, the code for these brute-force algorithms is really just tree traversal code. Let’s sort out the logic here: The core of DFS and backtracking is to recursively traverse an "exhaustive tree" (an N-ary tree). And recursive traversal of an N-ary tree comes from recursive traversal of a binary tree. That’s why I say DFS and backtracking are really recursive traversal of binary trees. The core of BFS is traversing a graph. As you will see soon, the BFS framework is just like the [DFS/BFS traversal of graphs](https://labuladong.online/en/algo/data-structure-basic/graph-traverse-basic/). Graph traversal is basically N-ary tree traversal, but with a `visited` array to avoid infinite loops. And N-ary tree traversal comes from binary tree traversal. So I say, at its core, BFS is really level order traversal of a binary tree. Why is BFS often used to solve shortest path problems? In [When to Use DFS and BFS](https://labuladong.online/en/algo/data-structure-basic/use-case-of-dfs-bfs/), I explained this in detail with the example of binary tree minimum depth. In fact, all shortest path problems are similar to the minimum depth problem of a binary tree (finding the closest leaf node to the root). Recursive traversal must visit all nodes to find the target, but level order traversal can find the answer without visiting every node. That’s why level order traversal is good for shortest path problems. Is this clear enough now? **So before reading this article, make sure you have learned [Binary Tree Recursive/Level Order Traversal](https://labuladong.online/en/algo/data-structure-basic/binary-tree-traverse-basic/), [N-ary Tree Recursive/Level Order Traversal](https://labuladong.online/en/algo/data-structure-basic/n-ary-tree-traverse-basic/), and [DFS/BFS Traversal of Graphs](https://labuladong.online/en/algo/data-structure-basic/graph-traverse-basic/). Once you understand how to traverse these basic data structures, other algorithms will be much easier to learn.** The main point of this article is to teach you how to turn real algorithm problems into abstract models, then use the BFS framework to solve them. In real coding interviews, you won’t be directly asked to traverse a tree or graph. Instead, you’ll get a real-world problem, and you have to turn it into a standard graph or tree, then use BFS to find the answer. For example, you are given a maze game and asked to find the minimum number of steps to reach the exit. If the maze has teleporters that instantly move you to another spot, what is the minimum number of steps then? Or, given two words, you can change one into another by replacing, deleting, or inserting one character each time. What is the smallest number of operations needed? Or, in a tile-matching game, two tiles can only be matched if the shortest line connecting them has at most two turns. When you click two tiles, how does the game check how many turns the line between them has? At first, these problems don’t seem related to trees or graphs. But with a bit of abstraction, they are really just tree or graph traversal problems. They just look simple and boring. Let’s use a few examples to show how to use the BFS framework, so you won’t find these problems hard anymore. ## Algorithm Framework The BFS framework is actually the same as the [DFS/BFS Traversal of Graphs](https://labuladong.online/en/algo/data-structure-basic/graph-traverse-basic/) article. There are three ways to write BFS. For real BFS problems, the first way is simple but not often used because it’s limited. The second way is the most common—most medium-level BFS problems can be solved this way. The third way is a bit more complex but more flexible. For harder problems, you may need the third way. In the next chapter, [BFS Problems](https://labuladong.online/en/algo/problem-set/bfs/), you’ll see some hard questions that use the third way. You can try them yourself later. The examples in this article are all medium difficulty, so the solutions are based on the second way: ```java // BFS traversal of the graph starting from s, and record the steps // When the target node is reached, return the number of steps int bfs(int s, int target) { boolean[] visited = new boolean[graph.size()]; Queue q = new LinkedList<>(); q.offer(s); visited[s] = true; // record the number of steps taken from s to the current node 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); // determine if the target point is reached if (cur == target) { return step; } // add the neighbors to the queue to search around for (int to : neighborsOf(cur)) { if (!visited[to]) { q.offer(to); visited[to] = true; } } } step++; } // If we reach here, it means the target node was not found in the graph return -1; } ``` The code framework above is almost copied from [DFS/BFS Traversal of Graphs](https://labuladong.online/en/algo/data-structure-basic/graph-traverse-basic/). It just adds a `target` parameter, so when you reach the target for the first time, you stop and return the number of steps. Next, let’s use a few examples to see how to use this framework. ## Sliding Puzzle LeetCode Problem 773 "[Sliding Puzzle](https://leetcode.com/problems/sliding-puzzle/)" is a problem that can be solved using the BFS framework. The problem is described as follows: You are given a 2x3 sliding puzzle, represented as a 2x3 array `board`. The board contains numbers 0 to 5. The number 0 represents the empty slot. You can move the numbers. When `board` becomes `[[1,2,3],[4,5,0]]`, you win the game. Write an algorithm to find the minimum number of moves to win the game. If it is impossible to win, return -1. For example, if the input is `board = [[4,1,2],[5,0,3]]`, the algorithm should return 5: ![](../pictures/sliding_puzzle/5.jpeg) If the input is `board = [[1,2,3],[5,4,0]]`, the algorithm should return -1 because there is no way to win from this situation. This puzzle is quite interesting. I played similar games when I was young, such as the "Huarong Dao": ![](../pictures/sliding_puzzle/2.jpeg) You need to move the blocks and try to move Cao Cao from the starting position to the exit at the bottom. "Huarong Dao" is harder than this problem because the blocks have different sizes, while in this problem, each block has the same size. Back to this problem, how do we change it into a tree or graph structure so we can use the BFS algorithm? The initial state of the board is the starting point: ``` [[2,4,1], [5,0,3]] ``` Our goal is to turn the board into this: ``` [[1,2,3], [4,5,0]] ``` This is the target. Now, this problem becomes a graph problem. The question is actually asking for the shortest path from the start to the target. Who are the neighbors of the start? You can swap the 0 with the numbers above, below, left, or right. These are the neighbors of the start (since the board is 2x3, the actual neighbors are less than four if on the edge): ![](../pictures/sliding_puzzle/3.jpeg) In the same way, each neighbor has its own neighbors. This makes a graph. So, we can use BFS from the start. The first time we reach the target, the number of steps is the answer. Here is the pseudocode: ```java int bfs(int[][] board, int[][] target) { Queue q = new LinkedList<>(); HashSet visited = new HashSet<>(); // add the start point to the queue q.offer(board); visited.add(board); int step = 0; while (!q.isEmpty()) { int sz = q.size(); for (int i = 0; i < sz; i++) { int[][] cur = q.poll(); // determine if the end point is reached if (cur == target) { return step; } // add the neighbors of the current node to the queue for (int[][] neighbor : getNeighbors(cur)) { if (!visited.contains(neighbor)) { q.offer(neighbor); visited.add(neighbor); } } } step++; } return -1; } List getNeighbors(int[][] board) { // swap the number 0 with the numbers above, below, left, and right, to get 4 neighbors } ``` For this problem, the graph we build could have cycles, so we need a `visited` array to record the nodes we have already visited, to avoid falling into an infinite loop. For example, if we start from the node `[[2,4,1],[5,0,3]]`, moving 0 to the right gives us a new node `[[2,4,1],[5,3,0]]`. But from this new node, 0 can also move to the left to return to `[[2,4,1],[5,0,3]]`. This creates a cycle. So we need a `visited` hash set to keep track of the nodes we have visited and avoid infinite loops caused by cycles. There is another point: in this problem, `board` is a 2D array. In our article [Basics of Hash Table/Set](https://labuladong.online/en/algo/data-structure-basic/hashmap-basic/), we mentioned that a 2D array is a mutable structure and cannot be directly stored in a hash set. So we need to use a small trick: convert the 2D array into an immutable type before storing it in the hash set. A common way is to serialize the 2D array into a string, so we can save it in the hash set. **The tricky part is: since the 2D array has “up, down, left, right” movements, how can we swap 0 with its neighbors after converting the board to a 1D string?** In this problem, the input board is always size 2 x 3, so we can write out this mapping by hand: ```java // Record the adjacent indices of one-dimensional strings int[][] neighbor = new int[][]{ {1, 3}, {0, 4, 2}, {1, 5}, {0, 4}, {3, 1, 5}, {4, 2} }; ``` **This mapping means: in the 1D string, the neighbor indexes of index `i` in the 2D board are `neighbor[i]`**. For example, we know that the neighbors of `neighbor[4]` are `neighbor[3], neighbor[1], neighbor[5]`: ![](../pictures/sliding_puzzle/4.jpeg) :::: details What if it is an `m x n` 2D array? For an `m x n` 2D array, it is not realistic to write the 1D mapping by hand. We need code to generate the neighbor index mapping. Looking at the image above, you can see: if an element `e` in the 2D array has index `i` in the 1D array, then its left and right neighbor indexes are `i - 1` and `i + 1`, and its up and down neighbors are `i - n` and `i + n`, where `n` is the number of columns. So for an `m x n` 2D array, we can write a function to generate its `neighbor` index mapping: ```java int[][] generateNeighborMapping(int m, int n) { int[][] neighbor = new int[m * n][]; for (int i = 0; i < m * n; i++) { List neighbors = new ArrayList<>(); // if it is not the first column, it has a left neighbor if (i % n != 0) neighbors.add(i - 1); // if it is not the last column, it has a right neighbor if (i % n != n - 1) neighbors.add(i + 1); // if it is not the first row, it has an upper neighbor if (i - n >= 0) neighbors.add(i - n); // if it is not the last row, it has a lower neighbor if (i + n < m * n) neighbors.add(i + n); // Java language feature, convert List type to int[] array neighbor[i] = neighbors.stream().mapToInt(Integer::intValue).toArray(); } return neighbor; } ``` :::: With this mapping, no matter where the 0 is, we can find its neighbors by these indexes and swap them. Here is the complete code: ```java class Solution { public int slidingPuzzle(int[][] board) { int m = 2, n = 3; StringBuilder sb = new StringBuilder(); String target = "123450"; // convert the 2x3 array into a string as the starting point for bfs for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { sb.append(board[i][j]); } } String start = sb.toString(); // record the adjacent indices of the 1d string int[][] neighbor = new int[][]{ {1, 3}, {0, 4, 2}, {1, 5}, {0, 4}, {3, 1, 5}, {4, 2} }; // ****** Start of BFS algorithm framework ****** Queue q = new LinkedList<>(); HashSet visited = new HashSet<>(); // start bfs search from the starting point q.offer(start); visited.add(start); int step = 0; while (!q.isEmpty()) { int sz = q.size(); for (int i = 0; i < sz; i++) { String cur = q.poll(); // check if the target state is reached if (target.equals(cur)) { return step; } // find the index of the number 0 int idx = 0; for (; cur.charAt(idx) != '0'; idx++) ; // swap the number 0 with adjacent numbers for (int adj : neighbor[idx]) { String new_board = swap(cur.toCharArray(), adj, idx); // prevent revisiting the same state if (!visited.contains(new_board)) { q.offer(new_board); visited.add(new_board); } } } step++; } // ****** End of BFS algorithm framework ****** return -1; } private String swap(char[] chars, int i, int j) { char temp = chars[i]; chars[i] = chars[j]; chars[j] = temp; return new String(chars); } } ``` This problem is solved. You can see that writing BFS algorithm is always the same pattern. The hard part is turning the problem into a BFS brute-force model and finding a good way to turn a multi-dimensional array into a string, so that we can use a hash set to record visited nodes. Now, let’s look at another real problem. ## Minimum Turns to Unlock the Lock Let's look at LeetCode problem 752 "[Open the Lock](https://leetcode.com/problems/open-the-lock/)". It's an interesting problem: **LeetCode 752. Open the Lock** You have a lock in front of you with 4 circular wheels. Each wheel has 10 slots: `'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'`. The wheels can rotate freely and wrap around: for example we can turn `'9'` to be `'0'`, or `'0'` to be `'9'`. Each move consists of turning one wheel one slot. The lock initially starts at `'0000'`, a string representing the state of the 4 wheels. You are given a list of `deadends` dead ends, meaning if the lock displays any of these codes, the wheels of the lock will stop turning and you will be unable to open it. Given a `target` representing the value of the wheels that will unlock the lock, return the minimum total number of turns required to open the lock, or -1 if it is impossible. Example 1:** ``` **Input:** deadends = ["0201","0101","0102","1212","2002"], target = "0202" **Output:** 6 **Explanation:** A sequence of valid moves would be "0000" -> "1000" -> "1100" -> "1200" -> "1201" -> "1202" -> "0202". Note that a sequence like "0000" -> "0001" -> "0002" -> "0102" -> "0202" would be invalid, because the wheels of the lock become stuck after the display becomes the dead end "0102". ``` Example 2:** ``` **Input:** deadends = ["8888"], target = "0009" **Output:** 1 **Explanation:** We can turn the last wheel in reverse to move from "0000" -> "0009". ``` Example 3:** ``` **Input:** deadends = ["8887","8889","8878","8898","8788","8988","7888","9888"], target = "8888" **Output:** -1 **Explanation:** We cannot reach the target without getting stuck. ``` **Constraints:** - `1 <= deadends.length <= 500` - `deadends[i].length == 4` - `target.length == 4` - target **will not be** in the list `deadends`. - `target` and `deadends[i]` consist of digits only. Here is the function signature: ```java class Solution { public int openLock(String[] deadends, String target) { // ... } } ``` The problem describes a password lock that we often see in daily life. If there are no restrictions, it's easy to count the minimum number of turns. For example, if you want to reach `"1234"`, you just turn each digit. The minimum turns will be `1 + 2 + 3 + 4 = 10`. But the difficulty is that you cannot go through any `deadends` while turning the lock. How do you handle `deadends` to make the total number of turns as few as possible? Don't worry about the details or try to consider every specific case. Remember, the heart of algorithms is brute-force. We can simply try every possible turn from `"0000"`. If we try all possible combinations, won't we definitely find the minimum turns? **First, ignore all restrictions like `deadends` and `target`. Think about this: if you need to write an algorithm to try all possible combinations, how would you do it?** Start from `"0000"`. How many possibilities are there if you turn the lock once? There are 4 positions, and each can turn up or down. That is, you can get `"1000", "9000", "0100", "0900"...`, a total of 8 combinations. Then, for each of these 8 combinations, you can turn again and get 8 more combinations for each, and so on... Can you see the recursion tree? It is an 8-way tree, and each node has 8 children. This pseudocode below describes this idea, using level-order traversal of an 8-way tree: ```java // increment s[j] by one String plusOne(String s, int j) { char[] ch = s.toCharArray(); if (ch[j] == '9') ch[j] = '0'; else ch[j] += 1; return new String(ch); } // decrement s[j] by one String minusOne(String s, int j) { char[] ch = s.toCharArray(); if (ch[j] == '0') ch[j] = '9'; else ch[j] -= 1; return new String(ch); } // BFS framework to find the minimum number of moves void BFS(String target) { Queue q = new LinkedList<>(); q.offer("0000"); int step = 0; while (!q.isEmpty()) { int sz = q.size(); // spread all nodes in the current queue to their neighbors for (int i = 0; i < sz; i++) { String cur = q.poll(); // determine if the end point is reached if (cur.equals(target)) { return step; } // a password can derive 8 neighboring passwords for (String neighbor : getNeighbors(cur)) { q.offer(neighbor); } } // increase the step count here step++; } } // increment or decrement each digit of s by one, return 8 neighboring passwords List getNeighbors(String s) { List neighbors = new ArrayList<>(); for (int i = 0; i < 4; i++) { neighbors.add(plusOne(s, i)); neighbors.add(minusOne(s, i)); } return neighbors; } ``` This code can already try all possible combinations, but there are still some problems to solve. 1. There will be repeated paths. For example, you can go from `"0000"` to `"1000"`, but when you take `"1000"` from the queue, you can turn it back to `"0000"`. This will create an infinite loop. This is easy to fix. Actually, it is a cycle in the graph. We can use a `visited` set to record all passwords we have already tried. If you see the same password again, just don't add it to the queue. 2. We haven't handled `deadends`. We should avoid these "dead passwords". This can also be fixed easily. Use a `deadends` set to record these passwords. Whenever you meet one, do not add it to the queue. Or even simpler, just add all `deadends` to the `visited` set at the beginning. This works too. Here is the complete code: ```java class Solution { public int openLock(String[] deadends, String target) { // record the deadends that need to be skipped Set deads = new HashSet<>(); for (String s : deadends) deads.add(s); if (deads.contains("0000")) return -1; // record the passwords that have been exhausted to prevent backtracking Set visited = new HashSet<>(); Queue q = new LinkedList<>(); // start breadth-first search from the starting point int step = 0; q.offer("0000"); visited.add("0000"); while (!q.isEmpty()) { int sz = q.size(); // spread all nodes in the current queue to their surroundings for (int i = 0; i < sz; i++) { String cur = q.poll(); // determine if the end is reached if (cur.equals(target)) return step; // add the valid adjacent nodes of a node to the queue for (String neighbor : getNeighbors(cur)) { if (!visited.contains(neighbor) && !deads.contains(neighbor)) { q.offer(neighbor); visited.add(neighbor); } } } // increment the step count here step++; } // if the target password is not found after exhaustion, then it is not found return -1; } // turn s[j] up once String plusOne(String s, int j) { char[] ch = s.toCharArray(); if (ch[j] == '9') ch[j] = '0'; else ch[j] += 1; return new String(ch); } // turn s[i] down once String minusOne(String s, int j) { char[] ch = s.toCharArray(); if (ch[j] == '0') ch[j] = '9'; else ch[j] -= 1; return new String(ch); } // increment or decrement each digit of s by one, return 8 neighboring passwords List getNeighbors(String s) { List neighbors = new ArrayList<>(); for (int i = 0; i < 4; i++) { neighbors.add(plusOne(s, i)); neighbors.add(minusOne(s, i)); } return neighbors; } } ``` ## Bidirectional BFS Optimization Now let's talk about an optimization for BFS called **Bidirectional BFS**. This method can make BFS faster. Think of this technique as extra reading. In most interviews and tests, normal BFS is enough. Only consider bidirectional BFS when your solution is too slow or if the interviewer asks for more optimization. Bidirectional BFS is an advanced version of standard BFS: **In normal BFS, you start from the starting point and search outwards until you find the target. In bidirectional BFS, you start searching from both the start and the end at the same time. When the two searches meet, you stop.** Why is this faster? It's like person A is looking for person B. In normal BFS, A goes to find B while B stays in place. In bidirectional BFS, both A and B walk toward each other. Of course, they will meet faster this way. ![](../pictures/bfs/1.jpeg) ![](../pictures/bfs/2.jpeg) In the picture above, if the target is at the bottom of the tree, normal BFS will search the whole tree before finding the target. But bidirectional BFS only searches half the tree before the two searches meet, so it is faster. From the Big O notation, both methods are $O(N)$ in the worst case, since both might search all nodes. But in real practice, bidirectional BFS is often much faster. ::: info Limitations of Bidirectional BFS **You must know where the target is to use bidirectional BFS.** For BFS, you always know the start point. But sometimes you do not know the target node at the beginning. For example, in the lock problem or the sliding puzzle problem above, the target is given, so you can use bidirectional BFS. But in the [Binary Tree DFS/BFS traversal](https://labuladong.online/en/algo/data-structure-basic/binary-tree-traverse-basic/), the start is the root, but the target is the nearest leaf, which you do not know at the start. So you can't use bidirectional BFS there. ::: Let's use the lock problem as an example to see how to upgrade BFS to bidirectional BFS. Here is the code: ```java class Solution { public int openLock(String[] deadends, String target) { Set deads = new HashSet<>(); for (String s : deadends) deads.add(s); // base case if (deads.contains("0000")) return -1; if (target.equals("0000")) return 0; // Use a set instead of a queue to quickly determine if an element exists Set q1 = new HashSet<>(); Set q2 = new HashSet<>(); Set visited = new HashSet<>(); int step = 0; q1.add("0000"); visited.add("0000"); q2.add(target); visited.add(target); while (!q1.isEmpty() && !q2.isEmpty()) { /** ![](../pictures/bfs/2.jpeg) */ // Increment the step count here step++; // The hash set cannot be modified during traversal, so use newQ1 to store the neighbor nodes Set newQ1 = new HashSet<>(); // Get the neighbors of all nodes in q1 for (String cur : q1) { // Add an unvisited neighboring node of a node to the set for (String neighbor : getNeighbors(cur)) { // Determine if the end point is reached if (q2.contains(neighbor)) { return step; } if (!visited.contains(neighbor) && !deads.contains(neighbor)) { newQ1.add(neighbor); visited.add(neighbor); } } } // newQ1 stores the neighbors of q1 q1 = newQ1; // Because each BFS spreads q1, the set with fewer elements is used as q1 if (q1.size() > q2.size()) { Set temp = q1; q1 = q2; q2 = temp; } } return -1; } // Turn s[j] up once String plusOne(String s, int j) { char[] ch = s.toCharArray(); if (ch[j] == '9') ch[j] = '0'; else ch[j] += 1; return new String(ch); } // Turn s[i] down once String minusOne(String s, int j) { char[] ch = s.toCharArray(); if (ch[j] == '0') ch[j] = '9'; else ch[j] -= 1; return new String(ch); } List getNeighbors(String s) { List neighbors = new ArrayList<>(); for (int i = 0; i < 4; i++) { neighbors.add(plusOne(s, i)); neighbors.add(minusOne(s, i)); } return neighbors; } } ``` Bidirectional BFS still follows the BFS framework, but there are a few differences: 1. Instead of using a queue, use a [hash set](https://labuladong.online/en/algo/data-structure-basic/hash-set/) to store elements. This makes it easier and faster to check if two sets have any common elements. 2. The place to return `step` is changed. In bidirectional BFS, you do not just check if you reached the end. Instead, you check if the two sets have common elements as soon as you find neighbors. 3. Another tip is to always make `q1` the smaller set each time you search. This helps reduce the search size. In BFS, the more elements in your queue (or set), the more neighbors you will add in the next round. In bidirectional BFS, if you always expand the smaller set, the total number of nodes searched grows more slowly and makes the algorithm faster. Again, **no matter if you use normal BFS or bidirectional BFS, and no matter if you optimize or not, the Big O time complexity is the same**. Bidirectional BFS is just an advanced trick to speed up code in practice. It's not required. The most important thing is to remember the general BFS framework and practice using it. Later, there is a [BFS Exercise Section](https://labuladong.online/en/algo/problem-set/bfs/). Try to use the tips from this article to solve those problems. ================================================ FILE: algorithmic-thinking/binary-search.md ================================================ ::: info Prerequisite Before reading this article, you should first learn: - [Array Basics](https://labuladong.online/en/algo/data-structure-basic/array-basic/) ::: First, a small joke: One day, A-Dong borrowed `N` books from the library. When he was leaving, the alarm went off. The security guard stopped him to check which book was not registered. A-Dong was about to scan each book through the gate one by one to find the problem book, but the guard looked at him with disdain: "You don't even know binary search?" So the guard split the books into two piles. He let the first pile pass the gate. The alarm sounded, which meant the problem book was in this pile. Then he split this pile into two piles again and let the first pile pass the gate. The alarm sounded again, and he kept splitting into two piles... After `logN` checks, the guard finally found the book that triggered the alarm. He looked proud and mocking. Then A-Dong walked away with all the remaining books. From then on, the library lost `N - 1` books. Binary search is not simple. Knuth (the one who invented the KMP algorithm) said about binary search: **the idea is simple, the devil is in the details**. Many people like to talk about the integer overflow bug, but the real traps in binary search are not that small detail. The real questions are: should you add one or subtract one from `mid`? Should the while condition be `<=` or `<`? If you do not really understand these details, writing binary search will feel like magic. You will rely on luck to avoid bugs, and only the writer knows how unreliable it is. This article will explore the three most common binary search cases: find a number, find the left boundary, and find the right boundary. These three cases use one unified framework: the while condition is always `<=`, and the boundary updates always use `mid ± 1`, which is easy to remember. ## Binary Search Framework There are two main styles of binary search: - both ends closed, `[left, right]` - left closed, right open, `[left, right)` **This article uses the “both ends closed” style** because it is easier to remember and unify. The left-closed-right-open style is explained in another article: [Binary Search: Left-Closed-Right-Open Style](https://labuladong.online/en/algo/essential-technique/binary-search-left-open/). No matter which style you use, the code follows this general framework: ```java int binarySearch(int[] nums, int target) { int left = 0, right = ...; while(...) { int mid = left + (right - left) / 2; if (nums[mid] == target) { ... } else if (nums[mid] < target) { left = ... } else if (nums[mid] > target) { right = ... } } return ...; } ``` The parts marked with `...` are where details can go wrong. When you read any binary search code, first look at these places. Later we will see how they change in different cases. One trick to write correct binary search is: do not use plain `else`. Instead, write all branches as `else if`, so every case is clear. After you fully understand all the details, you can simplify the code yourself. **Another point: when computing `mid`, you must avoid overflow.** In the code, `left + (right - left) / 2` is the same as `(left + right) / 2` in result, but it prevents overflow when `left` and `right` are very large. ## The Idea of Search Interval To really understand binary search, you must understand the idea of the “search interval” — what `left` and `right` mean. For example, if we set `right = nums.length - 1`, the index of the last element, then the search interval is a closed interval `[left, right]`. This is exactly the range we search each time. If we set `right = nums.length`, which is last index + 1, then the search interval is left-closed-right-open `[left, right)`. **The essence of binary search is to use a `while` loop to move `left` and `right`, shrinking the search interval again and again, until we lock onto the index of the target value.** Different open/closed choices give different code. For a correct binary search, we must make sure: - When the search interval is empty, the search must stop. Otherwise, the algorithm will run forever. - During the search, we must not skip any element. Otherwise, the answer can be wrong. Keep these two rules in mind. Next, we will use the **both-ends-closed** style to handle three cases: find a number, find the left boundary, and find the right boundary. ## Find a Number This is the simplest case: search for a number. If it exists, return its index; otherwise, return -1. ```java class Solution { // standard binary search framework, search for the index of the target element, return -1 if it does not exist public int search(int[] nums, int target) { int left = 0; // note int right = nums.length - 1; while(left <= right) { int mid = left + (right - left) / 2; if(nums[mid] == target) { return mid; } else if (nums[mid] < target) { // note left = mid + 1; } else if (nums[mid] > target) { // note right = mid - 1; } } return -1; } } ``` This code solves LeetCode 704 “[Binary Search](https://leetcode.com/problems/binary-search/)”. Let’s look at the details. ### Why is the while condition `<=` and not `<`? For a closed search interval, `<=` will not miss elements. The loop `while (left <= right)` stops when `left == right + 1`. The search interval is then `[right + 1, right]`. There is no index that is both `>= right + 1` and `<= right`, so the interval is empty. Stopping here is correct. If we use `while (left < right)`, the loop stops when `left == right`. The interval is `[left, left]`, which still has **one element**, but the while loop has stopped. That element is skipped. If the target is that element, the algorithm will wrongly think the target does not exist. This shows why binary search is hard to write perfectly: bugs are not always visible. Most test cases might pass, and only some special cases will fail. You must really understand the search interval to write fully correct code. ### Why `left = mid + 1` and `right = mid - 1`? Because `mid` has already been checked, so we must remove it from the search interval. Our interval is closed: `[left, right]`. When we find that index `mid` is not the target, the next search interval must be either `[left, mid - 1]` or `[mid + 1, right]`. ### What limitation does this algorithm have? For example, in a sorted array `nums = [1,2,2,2,3]`, with `target = 2`, this algorithm returns index 2. That is fine. But what if we want the **left boundary** (index 1), or the **right boundary** (index 3)? This algorithm cannot do that. You might say: once we find one `target`, we can linearly scan left or right. That works, but then we lose the logarithmic time advantage of binary search. The next algorithms will handle these two “boundary search” cases, which are very common in real problems. ## Find the Left Boundary First, look at the code for finding the left boundary: ```java int left_bound(int[] nums, int target) { int left = 0, right = nums.length - 1; // the search range is [left, right] while (left <= right) { int mid = left + (right - left) / 2; if (nums[mid] < target) { // the search range becomes [mid+1, right] left = mid + 1; } else if (nums[mid] > target) { // the search range becomes [left, mid-1] right = mid - 1; } else if (nums[mid] == target) { // shrink the right boundary right = mid - 1; } } return left; } ``` ### Why does this algorithm find the left boundary? The key is how we handle `nums[mid] == target`: ```java if (nums[mid] == target) { // shrink the right boundary right = mid - 1; } ``` We do not return as soon as we find `target`. Instead, we shrink the right boundary with `right = mid - 1` and keep searching in `[left, mid - 1]`. This keeps moving the interval to the left, so we can lock onto the leftmost position of `target`. ### What if `target` does not exist? If `target` does not exist, `left_bound` returns the index of the **smallest element that is greater than `target`**. You do not need to memorize this. Just see an example: `nums = [2,3,5,7], target = 4`. The return value is 2, because the element 5 is the smallest element greater than 4. If you want to return -1 when `target` does not exist, you can check `nums[left]` at the end: ```java // if index is out of bounds, target definitely does not exist if (left < 0 || left >= nums.length) { return -1; } // if nums[left] is not target, then target does not exist if (nums[left] != target) { return -1; } // nums[left] == target, so left is the target index return left; ``` ## Find the Right Boundary Now look at the code for finding the right boundary: ```java 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) { // here change to shrink the left boundary left = mid + 1; } } // finally change to return right return right; } ``` ### Why does this algorithm find the right boundary? Again, the key is how we handle `nums[mid] == target`: ```java if (nums[mid] == target) { // shrink the left boundary left = mid + 1; } ``` We do not return as soon as we find `target`. Instead, we shrink the left boundary with `left = mid + 1` and keep searching in `[mid + 1, right]`. This keeps moving the interval to the right, so we can lock onto the rightmost position of `target`. ### What if `target` does not exist? If `target` does not exist, `right_bound` returns the index of the **largest element that is less than `target`**. For example, `nums = [2,3,5,7], target = 4`. The return value is 1, because the element 3 is the largest element smaller than 4. If you want to return -1 when `target` does not exist, you can check `nums[right]` at the end: ```java // if index is out of bounds, target does not exist if (right < 0 || right >= nums.length) { return -1; } // if nums[right] is not target, then target does not exist if (nums[right] != target) { return -1; } // nums[right] == target, so right is the target index return right; ``` ## Unified Template All three cases use the same closed search interval `[left, right]`. The only difference is what we do when `nums[mid] == target`: ```java 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 directly return mid; } } // return directly 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) { // do not return, lock the left boundary right = mid - 1; } } // determine if target exists in nums if (left < 0 || left >= nums.length) { return -1; } // check if nums[left] is the 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) { // do not return, lock the right boundary left = mid + 1; } } // since the while loop ends with right == left - 1 and we are now looking for the right boundary // it's easier to remember to use right instead of left - 1 if (right < 0 || right >= nums.length) { return -1; } return nums[right] == target ? right : -1; } ``` From this article, you learned: 1. When you read or write binary search code, avoid plain `else`. Use only `if` and `else if` so that all cases are clear. 2. Always think about the “search interval” and the stopping condition of the while loop. Stop when the search interval is empty. 3. Use the same closed-interval style for all three cases, and only change the `nums[mid] == target` branch and the return logic. In the end, this binary search framework is the “technique” level. On the “idea” level, **the core of binary search thinking is: use the known information to shrink (halve) the search space as much as possible**, so brute-force search becomes much faster. If you understand this article, you can write correct binary search code. The closed-interval style here is my recommended style: one unified framework for all three cases, easy to remember. You may also see another style on the internet: left-closed-right-open `[left, right)`, using `while (left < right)` and `right = mid`. That style is also common. If you want to deeply understand it, see [Binary Search: Left-Closed-Right-Open Style](https://labuladong.online/en/algo/essential-technique/binary-search-left-open/). In real problems you are rarely asked to just “write binary search code”. I will further explain how to use binary search thinking to improve brute-force solutions in [Applications of Binary Search](https://labuladong.online/en/algo/frequency-interview/binary-search-in-action/) and [More Binary Search Exercises](https://labuladong.online/en/algo/problem-set/binary-search/). ================================================ FILE: algorithmic-thinking/bit-manipulation.md ================================================ Bit manipulation has many tricks. There is a website called “Bit Twiddling Hacks” that collects almost all bit tricks: http://graphics.stanford.edu/~seander/bithacks.html But most of these tricks are too hard to read. I think you can use that site like a dictionary, no need to study every line. What we really need to learn are the bit tricks that are interesting and useful. So in this article, we will start from the basics, explain some simple ideas of bit operations, then summarize some common bit tricks used in algorithm problems and real-world development. ## Basics of Bit Operations ### Binary representation In a computer, all data is finally stored in binary form. Binary has only two digits: 0 and 1. Each digit is called a bit. For example, the decimal number 13 is `1101` in binary: ``` 1101 = 1×2³ + 1×2² + 0×2¹ + 1×2⁰ = 8 + 4 + 0 + 1 = 13 ``` In Java and some other languages, we can use the `0b` prefix to write binary numbers: ```java int a = 0b1101; // same as decimal 13 int b = 0b1010; // same as decimal 10 ``` ### Shift operations **Left shift `<<`**: move the bits to the left, and fill 0s on the right. Shifting left by n bits is the same as multiplying by 2^n. ```java int a = 5; // binary: 0b0101 int b = a << 1; // binary: 0b1010, decimal: 10 int c = a << 2; // binary: 0b10100, decimal: 20 // Left shift n bits is like multiply by 2^n // 5 << 1 = 5 * 2¹ = 10 // 5 << 2 = 5 * 2² = 20 ``` **Right shift `>>`**: move the bits to the right, and fill the left side with the sign bit. Shifting right by n bits is like dividing by 2^n and taking the floor. ```java int a = 20; // binary: 0b10100 int b = a >> 1; // binary: 0b1010, decimal: 10 int c = a >> 2; // binary: 0b101, decimal: 5 // Right shift n bits is like divide by 2^n // 20 >> 1 = 20 / 2¹ = 10 // 20 >> 2 = 20 / 2² = 5 ``` ### AND operation **AND `&`**: the result bit is 1 only when both bits are 1, otherwise it is 0. ```java int a = 12; // binary: 0b1100 int b = 10; // binary: 0b1010 int c = a & b; // binary: 0b1000, decimal: 8 // bit by bit: // 1100 // 1010 // ---- // 1000 ``` **Common uses**: - Check odd or even: `n & 1`, result 1 means odd, 0 means even - Get a specific bit: `n & (1 << k)` can get the k-th bit of n ```java int n = 13; // binary: 0b1101 boolean isOdd = (n & 1) == 1; // true, 13 is odd // get bit 2 (count from right, starting at 0) int bit2 = (n & (1 << 2)) != 0 ? 1 : 0; // result is 1 ``` ### OR operation **OR `|`**: the result bit is 1 if at least one of the bits is 1. ```java int a = 12; // binary: 0b1100 int b = 10; // binary: 0b1010 int c = a | b; // binary: 0b1110, decimal: 14 // bit by bit: // 1100 // 1010 // ---- // 1110 ``` **Common uses**: - Set a specific bit to 1: `n | (1 << k)` sets the k-th bit of n to 1 ```java int n = 12; // binary: 0b1100 n = n | (1 << 0); // set bit 0 to 1, result: 0b1101, decimal: 13 ``` ### XOR operation **XOR `^`**: the result bit is 1 only when the two bits are different; if they are the same, the result is 0. ```java int a = 12; // binary: 0b1100 int b = 10; // binary: 0b1010 int c = a ^ b; // binary: 0b0110, decimal: 6 // bit by bit: // 1100 // 1010 // ---- // 0110 ``` **Important properties of XOR**: 1. `a ^ a = 0`: any number XOR itself is 0 2. `a ^ 0 = a`: any number XOR 0 is itself 3. XOR is commutative and associative: `a ^ b ^ c = a ^ (b ^ c) = (a ^ b) ^ c` ```java int a = 5; int result1 = a ^ a; // result is 0 int result2 = a ^ 0; // result is 5 // commutative and associative int x = 3, y = 5, z = 7; int r1 = x ^ y ^ z; // result is 1 int r2 = x ^ (y ^ z); // result is 1 int r3 = (x ^ y) ^ z; // result is 1 ``` **Common uses**: - Flip a specific bit: `n ^ (1 << k)` flips the k-th bit of n (0 -> 1, 1 -> 0) - Swap two numbers (will be explained later) ```java int n = 12; // binary: 0b1100 n = n ^ (1 << 0); // flip bit 0, result: 0b1101, decimal: 13 n = n ^ (1 << 0); // flip bit 0 again, result: 0b1100, decimal: 12 ``` Once we understand these basic bit operations, we can move on to some interesting bit tricks. ## Some Interesting Bit Operations ```java // 1. Use OR `|` with space to convert English letters to lowercase ('a' | ' ') = 'a' ('A' | ' ') = 'a' // 2. Use AND `&` with underscore to convert English letters to uppercase ('b' & '_') = 'B' ('B' & '_') = 'B' // 3. Use XOR `^` with space to flip letter case ('d' ^ ' ') = 'D' ('D' ^ ' ') = 'd' // These work because of ASCII codes. // Characters are actually numbers, and the codes for space and underscore // can flip case through bit operations. // If you are interested, you can check the ASCII table and do the math yourself. // 4. Swap two numbers without a temp variable int a = 1, b = 2; a ^= b; b ^= a; a ^= b; // now a = 2, b = 1 // 5. Add one int n = 1; n = -~n; // now n = 2 // 6. Subtract one int n = 2; n = ~-n; // now n = 1 // 7. Check if two numbers have different signs int x = -1, y = 2; boolean f = ((x ^ y) < 0); // true int x = 3, y = 2; boolean f = ((x ^ y) < 0); // false ``` The first 6 tricks are not very useful in real code. But the 7th trick is quite practical. It uses the sign bit in **two’s complement**. In integer encoding, the highest bit is the sign bit. For negative numbers, the sign bit is 1. For non-negative numbers, the sign bit is 0. With XOR, you can know whether two numbers have different signs. If you do not use bit operations, you must use if-else to check the sign, which is more trouble. You may think of using the product to check the sign, but that can easily cause integer overflow and give wrong results. ## Modulo with Power of 2 For modulus (remainder), we usually use the `%` operator. But in some code (for example, in the source code of HashMap), you may see `&` instead. This is an optimization. **When the modulus `m` is a power of 2, `x % m` is the same as `x & (m - 1)`.** The bit operation `&` is much faster than `%`, so this trick is very useful where performance matters. A common use is in a circular array. The normal way is to use `%` so that the index loops in `[0, arr.length - 1]`: ```java int[] arr = {1,2,3,4}; int index = 0; while (true) { // looping in a circular array print(arr[index % arr.length]); index++; } // output: 1,2,3,4,1,2,3,4,1,2,3,4... ``` If the array length is a power of 2, we can use `&` to speed up the modulus: ```java int[] arr = {1,2,3,4}; int index = 0; while (true) { // loop in the circular array print(arr[index & (arr.length - 1)]); index++; } // output: 1,2,3,4,1,2,3,4,1,2,3,4... ``` ::: important Important This trick only works when the array length is a power of 2, like 2, 4, 8, 16, 32, and so on. There are clever bit hacks that can round a number up to a power of 2. You can check https://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2 ::: Simply put, `& (arr.length - 1)` can replace `% arr.length` and gives better performance. Now another question: we keep doing `index++`, and this gives us a circular effect. But if we keep doing `index--`, can we still get a circular array? If you use `%` for modulus, once `index` becomes negative, the result of `%` can also be negative, and you must handle this case specially. But with `&`, `index` will not become negative and everything still works: ```java int[] arr = {1,2,3,4}; int index = 0; while (true) { // looping in a circular array print(arr[index & (arr.length - 1)]); index--; } // output: 1,4,3,2,1,4,3,2,1,4,3,2,1... ``` You may not use this trick often in your own code, but you will see it a lot when reading other code bases. Just keep it in mind so you are not confused when you see it. ## The Usage of `n & (n-1)` The operation **`n & (n-1)` is common in algorithms, and its effect is to remove the last 1 in the binary form of `n`**. Look at the picture and it becomes clear: ![](../pictures/bit-op/1.png) The key idea: `n - 1` will remove the last 1 in `n`, and turn all the bits after it into 1. Then `n & (n - 1)` will clear only that last 1 to 0. ### Hamming Weight This is LeetCode 191: “[Number of 1 Bits](https://leetcode.com/problems/number-of-1-bits/)”: **LeetCode 191. Number of 1 Bits** Write a function that takes the binary representation of a positive integer and returns the number of set bits it has (also known as the [Hamming weight](http://en.wikipedia.org/wiki/Hamming_weight)). Example 1:** **Input:** n = 11 **Output:** 3 **Explanation:** The input binary string **1011** has a total of three set bits. Example 2:** **Input:** n = 128 **Output:** 1 **Explanation:** The input binary string **10000000** has a total of one set bit. Example 3:** **Input:** n = 2147483645 **Output:** 30 **Explanation:** The input binary string **1111111111111111111111111111101** has a total of thirty set bits. **Constraints:** - `1 <= n <= 2^(31) - 1` **Follow up:** If this function is called many times, how would you optimize it? You need to return how many 1s are in the binary form of `n`. Since `n & (n - 1)` can remove the last 1, we can keep doing this in a loop and count, until `n` becomes 0. ```java public class Solution { // you need to treat n as an unsigned value public int hammingWeight(int n) { int res = 0; while (n != 0) { n = n & (n - 1); res++; } return res; } } ``` ### Check If a Number Is a Power of 2 This is LeetCode 231: “[Power of Two](https://leetcode.com/problems/power-of-two/)”. If a number is a power of 2, its binary form has exactly one 1: ```java 2^0 = 1 = 0b0001 2^1 = 2 = 0b0010 2^2 = 4 = 0b0100 ``` Using the trick `n & (n - 1)` makes it easy (note operator precedence, you cannot remove the parentheses): ```java class Solution { public boolean isPowerOfTwo(int n) { if (n <= 0) return false; return (n & (n - 1)) == 0; } } ``` ## The Usage of `a ^ a = 0` We must remember these properties of XOR: A number XOR itself is 0, that is `a ^ a = 0`. A number XOR 0 is the number itself, that is `a ^ 0 = a`. ### Find the Element That Appears Only Once This is LeetCode 136: “[Single Number](https://leetcode.com/problems/single-number/)”: **LeetCode 136. Single Number** Given a **non-empty** array of integers `nums`, every element appears *twice* except for one. Find that single one. You must implement a solution with a linear runtime complexity and use only constant extra space. Example 1:** ``` **Input:** nums = [2,2,1] **Output:** 1 ``` Example 2:** ``` **Input:** nums = [4,1,2,1,2] **Output:** 4 ``` Example 3:** ``` **Input:** nums = [1] **Output:** 1 ``` **Constraints:** - `1 <= nums.length <= 3 * 10^(4)` - `-3 * 10^(4) <= nums[i] <= 3 * 10^(4)` - Each element in the array appears twice except for one element which appears only once. For this problem, we just XOR all numbers together. Pairs of equal numbers will become 0. The single number XOR 0 is still itself, so the final XOR result is the element that appears only once: ```java class Solution { public int singleNumber(int[] nums) { int res = 0; for (int n : nums) { res ^= n; } return res; } } ``` ### Find the Missing Number This is LeetCode Problem 268: [Missing Number](https://leetcode.com/problems/missing-number/): **LeetCode 268. Missing Number** Given an array `nums` containing `n` distinct numbers in the range `[0, n]`, return *the only number in the range that is missing from the array.* Example 1:** ``` **Input:** nums = [3,0,1] **Output:** 2 **Explanation:** n = 3 since there are 3 numbers, so all numbers are in the range [0,3]. 2 is the missing number in the range since it does not appear in nums. ``` Example 2:** ``` **Input:** nums = [0,1] **Output:** 2 **Explanation:** n = 2 since there are 2 numbers, so all numbers are in the range [0,2]. 2 is the missing number in the range since it does not appear in nums. ``` Example 3:** ``` **Input:** nums = [9,6,4,2,3,5,7,0,1] **Output:** 8 **Explanation:** n = 9 since there are 9 numbers, so all numbers are in the range [0,9]. 8 is the missing number in the range since it does not appear in nums. ``` **Constraints:** - `n == nums.length` - `1 <= n <= 10^(4)` - `0 <= nums[i] <= n` - All the numbers of `nums` are **unique**. **Follow up:** Could you implement a solution using only `O(1)` extra space complexity and `O(n)` runtime complexity? You are given an array of length `n`. The valid indices are `[0, n)`, but now you want to put `n + 1` numbers `[0, n]` into it. So one number must be missing. Please find this missing number. This problem is not hard. We can easily think of sorting the array first, then scan it once to find the missing number. Or we can use a data structure: put all numbers from the array into a HashSet, then iterate through the numbers in `[0, n]` and check which one is not in the HashSet. The sorting solution has time complexity O(NlogN). The HashSet solution has time complexity O(N), but it also needs O(N) extra space to store the HashSet. There is a very simple solution for this problem: the sum formula of an arithmetic sequence. We can understand the problem like this: we have an arithmetic sequence `0, 1, 2, ..., n`, and one number is missing. We need to find it. Then the answer is: `sum(0, 1, ..., n) - sum(nums)`. ```java int missingNumber(int[] nums) { int n = nums.length; // Although the data range given by the problem is not large, we should use long type to prevent integer overflow for the sake of rigor // Sum formula: (first term + last term) * number of terms / 2 long expect = (0 + n) * (n + 1) / 2; long sum = 0; for (int x : nums) { sum += x; } return (int)(expect - sum); } ``` But the main topic of this article is bit operations. So we will see how to use bit tricks to solve this problem. Recall the properties of XOR: - A number XOR itself is 0. - A number XOR 0 is still the number itself. XOR also has commutative and associative laws. That is: ```java 2 ^ 3 ^ 2 = 3 ^ (2 ^ 2) = 3 ^ 0 = 3 ``` We can use these properties to find the missing number. For example, `nums = [0, 3, 1, 4]`: ![](../pictures/missing-elem/1.jpg) To make it easier to understand, we first imagine extending the index by one, then match each element with the index that has the same value: ![](../pictures/missing-elem/2.jpg) After doing this, except for the missing number, every index and element form a pair. If we can find the lonely index 2, we find the missing number. How do we find this lonely number? **Just XOR all the indices and all the elements together. All paired numbers will cancel out to 0, and only the lonely one will remain.** That is our answer: ```java class Solution { public int missingNumber(int[] nums) { int n = nums.length; int res = 0; // first XOR with the new added index res ^= n; // XOR with other elements and indices for (int i = 0; i < n; i++) res ^= i ^ nums[i]; return res; } } ``` ![](../pictures/missing-elem/3.jpg) Because XOR is commutative and associative, we can always cancel all pairs and keep only the missing number. Up to here, we have covered most common bit operations. These tricks are easy once you get them, and you do not need to memorize them. Just keep a rough idea in mind, and that is enough. ================================================ FILE: algorithmic-thinking/difference-array.md ================================================ ::: info Prerequisite Knowledge Before reading this article, you should first learn: - [Array Basics](https://labuladong.online/en/algo/data-structure-basic/array-basic/) - [Prefix Sum Technique](https://labuladong.online/en/algo/data-structure/prefix-sum/) ::: The [Prefix Sum Technique](https://labuladong.online/en/algo/data-structure/prefix-sum/) is mainly used when the original array does not change, and you need to quickly find the sum of any interval. The key code is below: ```java class PrefixSum { // prefix sum array private int[] preSum; // input an array to construct the prefix sum public PrefixSum(int[] nums) { // preSum[0] = 0, convenient for calculating cumulative sum preSum = new int[nums.length + 1]; // calculate the cumulative sum of nums for (int i = 1; i < preSum.length; i++) { preSum[i] = preSum[i - 1] + nums[i - 1]; } } // query the cumulative sum of the closed interval [left, right] public int sumRange(int left, int right) { return preSum[right + 1] - preSum[left]; } } ``` ![](../pictures/difference/1.jpeg) `preSum[i]` means the sum of all elements from `nums[0]` to `nums[i-1]`. If you want to find the sum from `nums[i]` to `nums[j]`, just calculate `preSum[j+1] - preSum[i]`. You don't need to loop through the whole interval. In this article, we talk about another technique similar to prefix sum: the **Difference Array**. The main use of the difference array is when you need to frequently increase or decrease the values in a range of the original array. For example, suppose you have an array `nums`, and you need to: - add 1 to all elements from `nums[2]` to `nums[6]` - subtract 3 from all elements from `nums[3]` to `nums[9]` - add 2 to all elements from `nums[0]` to `nums[4]` - and so on... After many such operations, what is the final value of the `nums` array? The usual way is simple. If you want to add `val` to all elements from `nums[i]` to `nums[j]`, just use a for loop and add `val` to each element. But this takes $O(N)$ time for each operation. If there are many operations, this will be slow. Here is where the difference array helps. Just like the prefix sum uses a `preSum` array, we can build a `diff` array for `nums`. **`diff[i]` is the difference between `nums[i]` and `nums[i-1]`**: ```java int[] diff = new int[nums.length]; // construct the difference array diff[0] = nums[0]; for (int i = 1; i < nums.length; i++) { diff[i] = nums[i] - nums[i - 1]; } ``` ![](../pictures/difference/2.jpeg) Using this `diff` array, you can get back the original `nums` array. The code is like this: ```java int[] res = new int[diff.length]; // construct the result array based on the difference array res[0] = diff[0]; for (int i = 1; i < diff.length; i++) { res[i] = res[i - 1] + diff[i]; } ``` **With the difference array `diff`, you can quickly increase or decrease a range of elements.** If you want to add 3 to all elements from `nums[i]` to `nums[j]`, just do `diff[i] += 3` and `diff[j+1] -= 3`: ![](../pictures/difference/3.jpeg) **The idea is simple. When you set `diff[i] += 3`, it means you add 3 to all elements from `nums[i]` and after. Then `diff[j+1] -= 3` means you subtract 3 from all elements from `nums[j+1]` and after. In total, only the elements from `nums[i]` to `nums[j]` get increased by 3.** You only need O(1) time to change the `diff` array, which is like updating an entire interval in the original `nums` array. Do all your changes to `diff`, then build the final `nums` from `diff`. Now, let's make the difference array into a class with `increment` and `result` methods: ```java // Difference Array Utility Class class Difference { // difference array private int[] diff; // input an initial array, range operations will be performed on this array public Difference(int[] nums) { diff = new int[nums.length]; // construct the difference array based on the initial array diff[0] = nums[0]; for (int i = 1; i < nums.length; i++) { diff[i] = nums[i] - nums[i - 1]; } } // increment the closed interval [i, j] by val (can be negative) public void increment(int i, int j, int val) { diff[i] += val; if (j + 1 < diff.length) { diff[j + 1] -= val; } } // return the result array public int[] result() { int[] res = new int[diff.length]; // construct the result array based on the difference array res[0] = diff[0]; for (int i = 1; i < diff.length; i++) { res[i] = res[i - 1] + diff[i]; } return res; } } ``` Notice the `if` statement in the `increment` method: ```java void increment(int i, int j, int val) { diff[i] += val; if (j + 1 < diff.length) { diff[j + 1] -= val; } } ``` When `j+1 >= diff.length`, it means you are changing all elements from `nums[i]` to the end, so you don't need to subtract `val` from `diff` anymore. You can open the panel below. Click the line diff[i] = nums[i] - nums[i - 1] several times to see how the `diff` array is built. Then click df.increment a few times to see how the `diff` array changes: ## Practical Examples LeetCode problem 370 "[Range Addition](https://leetcode.com/problems/range-addition/)" directly tests the difference array technique. You're given an array `nums` of length `n` initialized to all zeros, asked to perform increment/decrement operations on ranges, and finally return the resulting `nums` array. Just copy over the `Difference` class we implemented and you're done: ```java class Solution { public int[] getModifiedArray(int length, int[][] updates) { // nums initialized to all 0s int[] nums = new int[length]; // construct difference method Difference df = new Difference(nums); for (int[] update : updates) { int i = update[0]; int j = update[1]; int val = update[2]; df.increment(i, j, val); } return df.result(); } class Difference { // difference array private int[] diff; public Difference(int[] nums) { diff = new int[nums.length]; // construct difference array diff[0] = nums[0]; for (int i = 1; i < nums.length; i++) { diff[i] = nums[i] - nums[i - 1]; } } // increment closed interval [i, j] by val (can be negative) 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]; // construct result array based on difference array res[0] = diff[0]; for (int i = 1; i < diff.length; i++) { res[i] = res[i - 1] + diff[i]; } return res; } } } ``` Of course, in real problems you'll need to recognize and abstract the pattern—they won't be this obvious about using difference arrays. Let's look at LeetCode problem 1109 "[Corporate Flight Bookings](https://leetcode.com/problems/corporate-flight-bookings/)": **LeetCode 1109. Corporate Flight Bookings** There are `n` flights that are labeled from `1` to `n`. You are given an array of flight bookings `bookings`, where `bookings[i] = [firsti, lasti, seatsi]` represents a booking for flights `firsti` through `lasti` (**inclusive**) with `seatsi` seats reserved for **each flight** in the range. Return *an array *`answer`* of length *`n`*, where *`answer[i]`* is the total number of seats reserved for flight *`i`. Example 1:** ``` **Input:** bookings = [[1,2,10],[2,3,20],[2,5,25]], n = 5 **Output:** [10,55,45,25,25] **Explanation:** Flight labels: 1 2 3 4 5 Booking 1 reserved: 10 10 Booking 2 reserved: 20 20 Booking 3 reserved: 25 25 25 25 Total seats: 10 55 45 25 25 Hence, answer = [10,55,45,25,25] ``` Example 2:** ``` **Input:** bookings = [[1,2,10],[2,2,15]], n = 2 **Output:** [10,25] **Explanation:** Flight labels: 1 2 Booking 1 reserved: 10 10 Booking 2 reserved: 15 Total seats: 10 25 Hence, answer = [10,25] ``` **Constraints:** - `1 <= n <= 2 * 10^(4)` - `1 <= bookings.length <= 2 * 10^(4)` - `bookings[i].length == 3` - `1 <= firsti <= lasti <= n` - `1 <= seatsi <= 10^(4)` The function signature is: ```java int[] corpFlightBookings(int[][] bookings, int n) ``` This problem wraps things in confusing language, but it's really just a difference array problem. Let me translate it for you: You're given an array `nums` of length `n` where all elements are 0. You're also given `bookings`, which contains several triplets `(i, j, k)`. Each triplet means you need to add `k` to all elements in the closed interval `[i-1, j-1]` of `nums`. Return the final `nums` array. ::: note Note Since the problem counts `n` starting from 1, but array indices start from 0, the triplet `(i, j, k)` corresponds to the array interval `[i-1, j-1]`. ::: Now it's clear—this is a standard difference array problem! We can directly reuse the class we wrote: ```java class Solution { public int[] corpFlightBookings(int[][] bookings, int n) { // initialize nums as all 0 int[] nums = new int[n]; // construct the difference array Difference df = new Difference(nums); for (int[] booking : bookings) { // note that converting to array index needs to subtract one int i = booking[0] - 1; int j = booking[1] - 1; int val = booking[2]; // increment the range nums[i..j] by val df.increment(i, j, val); } // return the final result array return df.result(); } class Difference { // difference array private int[] diff; public Difference(int[] nums) { diff = new int[nums.length]; // construct the difference array diff[0] = nums[0]; for (int i = 1; i < nums.length; i++) { diff[i] = nums[i] - nums[i - 1]; } } // increment the closed interval [i, j] by val (can be negative) 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]; // construct the result array based on the difference array res[0] = diff[0]; for (int i = 1; i < diff.length; i++) { res[i] = res[i - 1] + diff[i]; } return res; } } } ``` Problem solved. Here's another similar problem—LeetCode problem 1094 "[Car Pooling](https://leetcode.com/problems/car-pooling/)": **LeetCode 1094. Car Pooling** There is a car with `capacity` empty seats. The vehicle only drives east (i.e., it cannot turn around and drive west). You are given the integer `capacity` and an array `trips` where `trips[i] = [numPassengersi, fromi, toi]` indicates that the `i^(th)` trip has `numPassengersi` passengers and the locations to pick them up and drop them off are `fromi` and `toi` respectively. The locations are given as the number of kilometers due east from the car's initial location. Return `true`* if it is possible to pick up and drop off all passengers for all the given trips, or *`false`* otherwise*. Example 1:** ``` **Input:** trips = [[2,1,5],[3,3,7]], capacity = 4 **Output:** false ``` Example 2:** ``` **Input:** trips = [[2,1,5],[3,3,7]], capacity = 5 **Output:** true ``` **Constraints:** - `1 <= trips.length <= 1000` - `trips[i].length == 3` - `1 <= numPassengersi <= 100` - `0 <= fromi < toi <= 1000` - `1 <= capacity <= 10^(5)` The function signature is: ```java boolean carPooling(int[][] trips, int capacity); ``` For example, given: ``` trips = [[2,1,5],[3,3,7]], capacity = 4 ``` This trip can't be completed in one go because `trips[1]` can only pick up 2 more passengers at most, otherwise the car would be overloaded. You've probably already connected this to difference arrays: **each `trips[i]` represents a range operation—passengers boarding and alighting correspond to incrementing and decrementing ranges. If every element in the result array is less than `capacity`, all passengers can be transported without overloading.** But what should the length of the difference array (number of stops) be? The problem doesn't tell us directly, but gives us the data range: ```java 0 <= trips[i][1] < trips[i][2] <= 1000 ``` Stop numbers range from 0 to at most 1000, meaning there are at most 1001 stops. So we can set our difference array length to 1001, which covers all possible stop numbers: ```java class Solution { public boolean carPooling(int[][] trips, int capacity) { // at most there are 1000 stops int[] nums = new int[1001]; // construct the difference array method Difference df = new Difference(nums); for (int[] trip : trips) { // number of passengers int val = trip[0]; // passengers get on at stop trip[1] int i = trip[1]; // passengers get off at stop trip[2], // meaning the interval passengers are on the car is [trip[1], trip[2] - 1] int j = trip[2] - 1; // perform interval operation df.increment(i, j, val); } int[] res = df.result(); // the car should never exceed its capacity for (int i = 0; i < res.length; i++) { if (capacity < res[i]) { return false; } } return true; } // difference array utility class class Difference { // difference array private int[] diff; // input an initial array, interval operations will be performed on this array public Difference(int[] nums) { diff = new int[nums.length]; // construct the difference array based on the initial array diff[0] = nums[0]; for (int i = 1; i < nums.length; i++) { diff[i] = nums[i] - nums[i - 1]; } } // increment the closed interval [i, j] by val (can be negative) public void increment(int i, int j, int val) { diff[i] += val; if (j + 1 < diff.length) { diff[j + 1] -= val; } } // return the result array public int[] result() { int[] res = new int[diff.length]; // construct the result array based on the difference array res[0] = diff[0]; for (int i = 1; i < diff.length; i++) { res[i] = res[i - 1] + diff[i]; } return res; } } } ``` And that solves this problem too. Both difference arrays and prefix sum arrays are common and clever techniques. They suit different scenarios, and once you understand them, they're straightforward—but tricky if you don't. ## Further Reading First question: to use the difference array technique, you need to create a `diff` array with the same length as your interval. What if you have a huge interval like `[0, 10^9]`? Do you really need to create an array of length `10^9` just to start doing range operations? Second question: prefix sums enable fast range queries, while difference arrays enable fast range updates. Can we combine them to get both fast range updates AND fast range queries? These are common questions when dealing with range problems. The ultimate answer is the [Segment Tree](https://labuladong.online/en/algo/data-structure-basic/segment-tree-basic/) data structure, which can perform both range updates and range queries on intervals of any length in $O(\log N)$ time. ================================================ FILE: algorithmic-thinking/matrix-traversal.md ================================================ ::: info Prerequisites Before reading this article, you should learn: - [Array Basics](https://labuladong.online/en/algo/data-structure-basic/array-basic/) ::: Some readers say that after reading many articles on this site, they learned “framework thinking” and can solve most problems that follow a fixed pattern. But framework thinking is not magic. Some special tricks are like this: if you know them, they are easy; if you don’t, they feel very hard. You can only learn them by doing more problems and summarizing. In this article, I will share some smart operations on 2D arrays. Just remember the idea. Next time you see a similar problem, you won’t get stuck. ## Rotate a Matrix Clockwise / Counterclockwise Rotating a 2D array is a common interview test question. LeetCode 48, “[Rotate Image](https://leetcode.com/problems/rotate-image/)”, is a classic one: **LeetCode 48. Rotate Image** You are given an `n x n` 2D `matrix` representing an image, rotate the image by **90** degrees (clockwise). You have to rotate the image [**in-place**](https://en.wikipedia.org/wiki/In-place_algorithm), which means you have to modify the input 2D matrix directly. **DO NOT** allocate another 2D matrix and do the rotation. Example 1:** ![](https://assets.leetcode.com/uploads/2020/08/28/mat1.jpg) ``` **Input:** matrix = [[1,2,3],[4,5,6],[7,8,9]] **Output:** [[7,4,1],[8,5,2],[9,6,3]] ``` Example 2:** ![](https://assets.leetcode.com/uploads/2020/08/28/mat2.jpg) ``` **Input:** matrix = [[5,1,9,11],[2,4,8,10],[13,3,6,7],[15,14,12,16]] **Output:** [[15,13,2,5],[14,3,4,1],[12,6,8,9],[16,7,10,11]] ``` **Constraints:** - `n == matrix.length == matrix[i].length` - `1 <= n <= 20` - `-1000 <= matrix[i][j] <= 1000` The problem is easy to understand: rotate a 2D matrix 90 degrees clockwise. The hard part is: **you must modify it in-place**. The function signature is: ```java void rotate(int[][] matrix) ``` How do we rotate a 2D matrix in-place? If you think a bit, it feels complicated. You may think you need to rotate it “layer by layer”: ![](../pictures/2d-array/1.png) **But for this problem, you need a different idea.** Before we talk about the smart solution, let’s warm up with another problem that Google once asked: You are given a string `s` with words and spaces. Write an algorithm to reverse the order of words **in-place**. For example: ```shell s = "hello world labuladong" ``` You need to make it: ```shell s = "labuladong world hello" ``` A common way is: split by spaces, reverse the word list, then join. But that uses extra space, so it is not “in-place”. **The correct trick is: first reverse the whole string `s`:** ```shell s = "gnodalubal dlrow olleh" ``` **Then reverse each word by itself:** ```shell s = "labuladong world hello" ``` Now you reversed the word order in-place. LeetCode 151, “[Reverse Words in a String](https://leetcode.com/problems/reverse-words-in-a-string/)”, is a similar problem. This trick can be used in other problems too. For example, LeetCode 61, “[Rotate List](https://leetcode.com/problems/rotate-list/)”: given a singly linked list, rotate it so every node moves right by `k` positions. Example: `1 -> 2 -> 3 -> 4 -> 5`, `k = 2`. The result is `4 -> 5 -> 1 -> 2 -> 3`. For this problem, don’t move nodes one by one. Let me translate it: it really means “move the last `k` nodes to the head”, right? Still not clear? Moving the last `k` nodes to the head means you split the list into the first `n - k` nodes and the last `k` nodes, then reverse them in-place, right? This is the same idea as reversing words in-place. You first reverse the whole list, then reverse the first `n - k` nodes and the last `k` nodes. There are some details. For example, `k` can be larger than the list length. So you should compute the length `n` first, then do `k = k % n`. Then `k` will be valid and the result will be correct. If you have time, try this problem yourself. It is not hard, so I won’t show the code here. Why did I talk about these two problems? **The point is: our “natural” idea is not always the best for a computer; and the computer’s clean idea is not always natural for us.** That may be the fun part of algorithms. Back to rotating an `n x n` matrix clockwise. The common idea is to find a mapping from old coordinates to new coordinates. But we can try a jump in thinking: do reverse or mirror operations. That can give a new way. **First, mirror the `n x n` matrix `matrix` across the main diagonal (top-left to bottom-right):** ![](../pictures/2d-array/2.jpeg) **Then reverse each row:** ![](../pictures/2d-array/3.jpeg) **You will get the matrix rotated 90 degrees clockwise:** ![](../pictures/2d-array/4.jpeg) Turn this idea into code and you can solve the problem: ```java class Solution { public void rotate(int[][] matrix) { int n = matrix.length; // first, reverse the 2D matrix along the diagonal for (int i = 0; i < n; i++) { for (int j = i; j < n; j++) { // swap(matrix[i][j], matrix[j][i]); int temp = matrix[i][j]; matrix[i][j] = matrix[j][i]; matrix[j][i] = temp; } } // then, reverse each row of the 2D matrix for (int[] row : matrix) { reverse(row); } } // reverse a 1D array void reverse(int[] arr) { int i = 0, j = arr.length - 1; while (j > i) { // swap(arr[i], arr[j]); int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; i++; j--; } } } ``` You can open the panel below. Click the line let temp = matrix[i][j] many times to see the diagonal mirror step. Then click reverse(row) many times to see each row reversed and get the final answer: Some readers may ask: if I never did this problem before, how could I think of this? Yes, if you never saw this type of problem, it’s hard to think of. But now you have seen it. If you know it, it’s easy; if you don’t, it’s hard. You will probably never forget it. **Now let’s extend it: how do we rotate the matrix 90 degrees counterclockwise?** The idea is similar: mirror the matrix across the other diagonal, then reverse each row. That gives the counterclockwise result: ![](../pictures/2d-array/5.jpeg) The code is: ```java class Solution { // rotate the two-dimensional matrix 90 degrees counterclockwise in place public void rotate2(int[][] matrix) { int n = matrix.length; // mirror the two-dimensional matrix along the diagonal from bottom left to top right for (int i = 0; i < n; i++) { for (int j = 0; j < n - i; j++) { // swap(matrix[i][j], matrix[n-j-1][n-i-1]) int temp = matrix[i][j]; matrix[i][j] = matrix[n - j - 1][n - i - 1]; matrix[n - j - 1][n - i - 1] = temp; } } // then reverse each row of the two-dimensional matrix for (int[] row : matrix) { reverse(row); } } void reverse(int[] arr) { // see above } } ``` Now the rotation problem is solved. ## Spiral Traversal of a Matrix Next, let’s look at LeetCode 54, “[Spiral Matrix](https://leetcode.com/problems/spiral-matrix/)”, and see a common way to traverse a 2D matrix: **LeetCode 54. Spiral Matrix** Given an `m x n` `matrix`, return *all elements of the* `matrix` *in spiral order*. Example 1:** ![](https://assets.leetcode.com/uploads/2020/11/13/spiral1.jpg) ``` **Input:** matrix = [[1,2,3],[4,5,6],[7,8,9]] **Output:** [1,2,3,6,9,8,7,4,5] ``` Example 2:** ![](https://assets.leetcode.com/uploads/2020/11/13/spiral.jpg) ``` **Input:** matrix = [[1,2,3,4],[5,6,7,8],[9,10,11,12]] **Output:** [1,2,3,4,8,12,11,10,9,5,6,7] ``` **Constraints:** - `m == matrix.length` - `n == matrix[i].length` - `1 <= m, n <= 10` - `-100 <= matrix[i][j] <= 100` ```java // The function signature is as follows List spiralOrder(int[][] matrix) ``` **The key idea is to traverse in the order: right, down, left, up, and use four variables to mark the borders of the unvisited area:** ![](../pictures/2d-array/6.png) As we traverse in a spiral, the borders shrink, until we finish the whole matrix: ![](../pictures/2d-array/7.png) With this idea, writing the code is easy: ```java class Solution { public List spiralOrder(int[][] matrix) { int m = matrix.length, n = matrix[0].length; int upper_bound = 0, lower_bound = m - 1; int left_bound = 0, right_bound = n - 1; List res = new LinkedList<>(); // if res.size() == m * n, the entire array has been traversed while (res.size() < m * n) { if (upper_bound <= lower_bound) { // traverse from left to right at the top for (int j = left_bound; j <= right_bound; j++) { res.add(matrix[upper_bound][j]); } // move the upper boundary down upper_bound++; } if (left_bound <= right_bound) { // traverse from top to bottom on the right for (int i = upper_bound; i <= lower_bound; i++) { res.add(matrix[i][right_bound]); } // move the right boundary left right_bound--; } if (upper_bound <= lower_bound) { // traverse from right to left at the bottom for (int j = right_bound; j >= left_bound; j--) { res.add(matrix[lower_bound][j]); } // move the lower boundary up lower_bound--; } if (left_bound <= right_bound) { // traverse from bottom to top on the left for (int i = lower_bound; i >= upper_bound; i--) { res.add(matrix[i][left_bound]); } // move the left boundary right left_bound++; } } return res; } } ``` You can open the panel below. Click the line while (res.length < m * n) many times to see the spiral traversal from outside to inside: LeetCode 59, “[Spiral Matrix II](https://leetcode.com/problems/spiral-matrix-ii/)”, is very similar, but reversed: it asks you to generate a matrix in spiral order: **LeetCode 59. Spiral Matrix II** Given a positive integer `n`, generate an `n x n` `matrix` filled with elements from `1` to `n^(2)` in spiral order. Example 1:** ![](https://assets.leetcode.com/uploads/2020/11/13/spiraln.jpg) ``` **Input:** n = 3 **Output:** [[1,2,3],[8,9,4],[7,6,5]] ``` Example 2:** ``` **Input:** n = 1 **Output:** [[1]] ``` **Constraints:** - `1 <= n <= 20` ```java // The function signature is as follows int[][] generateMatrix(int n) ``` With the setup above, you can finish it with small code changes: ```java class Solution { public int[][] generateMatrix(int n) { int[][] matrix = new int[n][n]; int upper_bound = 0, lower_bound = n - 1; int left_bound = 0, right_bound = n - 1; // the number to be filled into the matrix int num = 1; while (num <= n * n) { if (upper_bound <= lower_bound) { // traverse from left to right at the top for (int j = left_bound; j <= right_bound; j++) { matrix[upper_bound][j] = num++; } // move the upper bound down upper_bound++; } if (left_bound <= right_bound) { // traverse from top to bottom on the right for (int i = upper_bound; i <= lower_bound; i++) { matrix[i][right_bound] = num++; } // move the right bound left right_bound--; } if (upper_bound <= lower_bound) { // traverse from right to left at the bottom for (int j = right_bound; j >= left_bound; j--) { matrix[lower_bound][j] = num++; } // move the lower bound up lower_bound--; } if (left_bound <= right_bound) { // traverse from bottom to top on the left for (int i = lower_bound; i >= upper_bound; i--) { matrix[i][left_bound] = num++; } // move the left bound right left_bound++; } } return matrix; } } ``` You can open the panel below. Click the line while (num <= n * n) many times to see the spiral matrix being generated: Now both spiral matrix problems are solved. These are some useful 2D array traversal tricks. For other array tricks, see: [Prefix Sum Array](https://labuladong.online/en/algo/data-structure/prefix-sum/), [Difference Array](https://labuladong.online/en/algo/data-structure/diff-array/), [Two-Pointer Tricks for Arrays](https://labuladong.online/en/algo/essential-technique/array-two-pointers-summary/). For linked list tricks, see: [Six Key Tricks for Singly Linked Lists](https://labuladong.online/en/algo/essential-technique/linked-list-skills-summary/). ================================================ FILE: algorithmic-thinking/pancake-sorting.md ================================================ LeetCode Problem 969 ["Pancake Sorting"](https://leetcode.com/problems/pancake-sorting/) is an interesting real-world problem: Imagine you have `n` pancakes of different sizes on a plate. Using a spatula, how can you flip the pancakes a few times to sort them by size (smallest on top, largest on bottom)? ![](../pictures/pancakeSort/1.jpg) Think of flipping a stack of pancakes with a spatula. There is a rule: each time, you can only flip the top few pancakes. ![](../pictures/pancakeSort/2.png) Our task is: **How can we use an algorithm to find a sequence of flips to make the pancake stack sorted?** First, we need to model the problem. We use an array to represent the pancake stack: **LeetCode 969. Pancake Sorting** Given an array of integers `arr`, sort the array by performing a series of **pancake flips**. In one pancake flip we do the following steps: - Choose an integer `k` where `1 <= k <= arr.length`. - Reverse the sub-array `arr[0...k-1]` (**0-indexed**). For example, if `arr = [3,2,1,4]` and we performed a pancake flip choosing `k = 3`, we reverse the sub-array `[3,2,1]`, so `arr = [1,2,3,4]` after the pancake flip at `k = 3`. Return *an array of the *`k`*-values corresponding to a sequence of pancake flips that sort *`arr`. Any valid answer that sorts the array within `10 * arr.length` flips will be judged as correct. Example 1:** ``` **Input:** arr = [3,2,4,1] **Output:** [4,2,4,3] **Explanation: ** We perform 4 pancake flips, with k values 4, 2, 4, and 3. Starting state: arr = [3, 2, 4, 1] After 1st flip (k = 4): arr = [1, 4, 2, 3] After 2nd flip (k = 2): arr = [4, 1, 2, 3] After 3rd flip (k = 4): arr = [3, 2, 1, 4] After 4th flip (k = 3): arr = [1, 2, 3, 4], which is sorted. ``` Example 2:** ``` **Input:** arr = [1,2,3] **Output:** [] **Explanation: **The input is already sorted, so there is no need to flip anything. Note that other answers, such as [3, 3], would also be accepted. ``` **Constraints:** - `1 <= arr.length <= 100` - `1 <= arr[i] <= arr.length` - All integers in `arr` are unique (i.e. `arr` is a permutation of the integers from `1` to `arr.length`). How do we solve this problem? Like in the article [Recursively Reverse Part of a Linked List](https://labuladong.online/en/algo/data-structure/reverse-linked-list-recursion/), we need to use recursion. ### 1. Idea Analysis Why does this problem have the property of recursion? For example, we can make a function like this: ```java // cakes is a pile of pancakes, the function will sort the first n pancakes void sort(int[] cakes, int n); ``` If we find the largest pancake among the first `n` pancakes, and flip it to the bottom: ![](../pictures/pancakeSort/3.jpg) Now the problem size is smaller, so we can recursively call `pancakeSort(A, n-1)`: ![](../pictures/pancakeSort/4.jpg) Next, for these `n - 1` pancakes, how to sort them? Again, find the largest, put it at the bottom, and call `pancakeSort(A, n-1-1)`... You see, this is recursion. To summarize: 1. Find the largest pancake among the `n` pancakes. 2. Move this largest pancake to the bottom. 3. Recursively call `pancakeSort(A, n - 1)`. Base case: When `n == 1`, there is only one pancake, so no flips are needed. Now, one last question: **How do we move a certain pancake to the bottom?** It's simple. For example, if the 3rd pancake is the largest and you want to move it to the bottom (the `n`th position): 1. Use the spatula to flip the top 3 pancakes. Now the largest is at the top. 2. Flip the top `n` pancakes. Now the largest is at the bottom. With these two steps, you can write the solution. The problem also asks us to return the flip operation sequence. We just need to record each flip. ### 2. Code Implementation Just write the above idea in code. Note: array index starts from 0, but the answer should use 1-based indices. ```java class Solution { // record the sequence of flip operations LinkedList res = new LinkedList<>(); public List pancakeSort(int[] cakes) { sort(cakes, cakes.length); return res; } void sort(int[] cakes, int n) { // base case if (n == 1) return; // find the index of the largest pancake int maxCake = 0; int maxCakeIndex = 0; for (int i = 0; i < n; i++) if (cakes[i] > maxCake) { maxCakeIndex = i; maxCake = cakes[i]; } // first flip, move the largest pancake to the top reverse(cakes, 0, maxCakeIndex); res.add(maxCakeIndex + 1); // second flip, move the largest pancake to the bottom reverse(cakes, 0, n - 1); res.add(n); /** ![](../pictures/pancakeSort/3.jpg) */ // recursive call sort(cakes, n - 1); /** ![](../pictures/pancakeSort/4.jpg) */ } void reverse(int[] arr, int i, int j) { while (i < j) { int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; i++; j--; } } } ``` With the explanation above, the code should be clear. The time complexity is easy to calculate. There are `n` recursive calls, and each call has a for loop of O(n), so total time complexity is O(n^2). **Finally, let's think about a question:** In this solution, the length of the flip sequence is `2(n - 1)`, because each recursion does 2 flips, and there are `n` layers of recursion. But the base case does not flip, so the total number is `2(n - 1)`. Clearly, this is not the optimal (shortest) sequence. For example, with pancakes `[3,2,4,1]`, our algorithm gives flips `[3,4,2,3,1,2]`. But the shortest flip sequence is `[2,3,4]`: ``` Start: [3,2,4,1] Flip top 2: [2,3,4,1] Flip top 3: [4,3,2,1] Flip top 4: [1,2,3,4] ``` If you are asked to find the **shortest** flip sequence to sort the pancakes, how would you solve it? What is the core idea to find the optimal solution? What algorithm should you use? Feel free to share your thoughts. ================================================ FILE: algorithmic-thinking/prefix-sum.md ================================================ ::: info Prerequisite Before reading this article, you should first learn: - [Array Basics](https://labuladong.online/en/algo/data-structure-basic/array-basic/) ::: The prefix sum technique is used to quickly and repeatedly calculate the sum of elements in a range of indices. ## Prefix Sum in a 1D Array Let’s look at an example: LeetCode 303 “[Range Sum Query - Immutable](https://leetcode.com/problems/range-sum-query-immutable/)”. You need to calculate the sum of elements in a subarray. This is a standard prefix sum problem: **LeetCode 303. Range Sum Query - Immutable** Given an integer array `nums`, handle multiple queries of the following type: - Calculate the **sum** of the elements of `nums` between indices `left` and `right` **inclusive** where `left <= right`. Implement the `NumArray` class: - `NumArray(int[] nums)` Initializes the object with the integer array `nums`. - `int sumRange(int left, int right)` Returns the **sum** of the elements of `nums` between indices `left` and `right` **inclusive** (i.e. `nums[left] + nums[left + 1] + ... + nums[right]`). Example 1:** ``` **Input** ["NumArray", "sumRange", "sumRange", "sumRange"] [[[-2, 0, 3, -5, 2, -1]], [0, 2], [2, 5], [0, 5]] **Output** [null, 1, -1, -3] **Explanation** NumArray numArray = new NumArray([-2, 0, 3, -5, 2, -1]); numArray.sumRange(0, 2); // return (-2) + 0 + 3 = 1 numArray.sumRange(2, 5); // return 3 + (-5) + 2 + (-1) = -1 numArray.sumRange(0, 5); // return (-2) + 0 + 3 + (-5) + 2 + (-1) = -3 ``` **Constraints:** - `1 <= nums.length <= 10^(4)` - `-10^(5) <= nums[i] <= 10^(5)` - `0 <= left <= right < nums.length` - At most `10^(4)` calls will be made to `sumRange`. ```java // The problem requires you to implement such a class class NumArray { public NumArray(int[] nums) {} // Query the cumulative sum of the closed interval [left, right] public int sumRange(int left, int right) {} } ``` The `sumRange` function needs to return the sum of elements in a given index range. Without prefix sum, people may write code like this: ```java class NumArray { private int[] nums; public NumArray(int[] nums) { this.nums = nums; } public int sumRange(int left, int right) { // use a for loop to compute the sum int sum = 0; for (int i = left; i <= right; i++) { sum += nums[i]; } return sum; } } ``` This solution runs a for loop every time `sumRange` is called. The time complexity is $O(N)$. Since `sumRange` may be called very often, this is not efficient. The correct way is to use prefix sum to optimize it, so the time complexity of `sumRange` becomes $O(1)$: ```java class NumArray { // prefix sum array private int[] preSum; // input an array to construct the prefix sum public NumArray(int[] nums) { // preSum[0] = 0, to facilitate the calculation of accumulated sums preSum = new int[nums.length + 1]; // calculate the accumulated sums of nums for (int i = 1; i < preSum.length; i++) { preSum[i] = preSum[i - 1] + nums[i - 1]; } } // query the sum of the closed interval [left, right] public int sumRange(int left, int right) { return preSum[right + 1] - preSum[left]; } } ``` The key idea is to create a new array `preSum`, where `preSum[i]` stores the sum of `nums[0..i-1]`. See the picture, where $10 = 3 + 5 + 2$: ![](../pictures/difference/1.jpeg) With this `preSum` array, if we want the sum of the elements in the index range `[1, 4]`, we can compute `preSum[5] - preSum[1]`. In this way, `sumRange` only needs one subtraction, no loop, so the worst-case time complexity is constant $O(1)$. Open the visualization below and click the line preSum[i] = preSum[i - 1] + nums[i - 1] to see how the `preSum` array is built. Click the line console.log multiple times to see calls to `sumRange`: This technique also appears in real life. For example, your class has several students, each with a final exam score (full score is 100). You need to design an API: given a score range, return how many students have scores in this range. You can first use counting sort to count how many students got each score, then use prefix sum to build the score-range query API: ```java // scores of all students int[] scores = new int[]{...}; // full score is 100 int[] count = new int[100 + 1]; // count how many students got each score for (int score : scores) { count[score]++; } // build the prefix sum array for (int i = 1; i < count.length; i++) { count[i] = count[i] + count[i-1]; } // use the prefix sum array `count` to do range queries // query how many students scored in [80, 90] int result = count[90] - count[79]; ``` Next, we will see how the prefix sum idea works in 2D arrays. ## Prefix Sum in 2D Matrix This is LeetCode 304: [Range Sum Query 2D – Immutable](https://leetcode.com/problems/range-sum-query-2d-immutable/). It is very similar to the previous problem. The previous one asked you to compute the sum of a subarray. This one asks you to compute the sum of a submatrix in a 2D matrix: **LeetCode 304. Range Sum Query 2D - Immutable** Given a 2D matrix `matrix`, handle multiple queries of the following type: - Calculate the **sum** of the elements of `matrix` inside the rectangle defined by its **upper left corner** `(row1, col1)` and **lower right corner** `(row2, col2)`. Implement the `NumMatrix` class: - `NumMatrix(int[][] matrix)` Initializes the object with the integer matrix `matrix`. - `int sumRegion(int row1, int col1, int row2, int col2)` Returns the **sum** of the elements of `matrix` inside the rectangle defined by its **upper left corner** `(row1, col1)` and **lower right corner** `(row2, col2)`. You must design an algorithm where `sumRegion` works on `O(1)` time complexity. Example 1:** ![](https://assets.leetcode.com/uploads/2021/03/14/sum-grid.jpg) ``` **Input** ["NumMatrix", "sumRegion", "sumRegion", "sumRegion"] [[[[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]]], [2, 1, 4, 3], [1, 1, 2, 2], [1, 2, 2, 4]] **Output** [null, 8, 11, 12] **Explanation** NumMatrix numMatrix = new NumMatrix([[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]]); numMatrix.sumRegion(2, 1, 4, 3); // return 8 (i.e sum of the red rectangle) numMatrix.sumRegion(1, 1, 2, 2); // return 11 (i.e sum of the green rectangle) numMatrix.sumRegion(1, 2, 2, 4); // return 12 (i.e sum of the blue rectangle) ``` **Constraints:** - `m == matrix.length` - `n == matrix[i].length` - `1 <= m, n <= 200` - `-10^(4) <= matrix[i][j] <= 10^(4)` - `0 <= row1 <= row2 < m` - `0 <= col1 <= col2 < n` - At most `10^(4)` calls will be made to `sumRegion`. Of course, you can use nested for loops to scan the matrix. But then the time complexity of the `sumRegion` function will be high, and your algorithm will look naive. Notice that the sum of any submatrix can be turned into a combination of sums of several larger rectangles: ![](../pictures/presum/5.jpeg) These four large rectangles share a key feature: their top-left corner is always the origin `(0, 0)`. So the better idea for this problem is very similar to prefix sum in a 1D array. We can keep a 2D `preSum` array. `preSum[i][j]` records the sum of all elements in the matrix with top-left at the origin and bottom-right at `(i, j)`. Then we can use a few additions and subtractions to get the sum of any submatrix: ```java class NumMatrix { // preSum[i][j] records the sum of elements in the matrix [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; // construct the prefix sum matrix preSum = new int[m + 1][n + 1]; for (int i = 1; i <= m; i++) { for (int j = 1; j <= n; j++) { // calculate the sum of elements for each matrix [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]; } } } // calculate the sum of elements in the submatrix [x1, y1, x2, y2] public int sumRegion(int x1, int y1, int x2, int y2) { // the sum of the target matrix is obtained by operations on four adjacent matrices return preSum[x2+1][y2+1] - preSum[x1][y2+1] - preSum[x2+1][y1] + preSum[x1][y1]; } } ``` In this way, the time complexity of the `sumRegion` function is optimized to $O(1)$ with the prefix sum trick. This is a classic “space-for-time” method. You can open the visualization below. Click the line preSum[i][j] = ... many times to see how the preSum array is computed. Click the console.log line many times to see how the sumRegion function is called: This is all for the prefix sum technique. You can say this skill is easy once you get it, but hard if you do not. In real problems, you should train your flexibility in thinking, so you can quickly see that a problem is a prefix sum problem. ## Further Expansion The prefix sum technique explained in this article uses a precomputed `preSum` array to quickly calculate the sum of elements within a given index range. However, it is not limited to summation; it can also be used for quickly computing products and other scenarios. Moreover, prefix sum arrays are often combined with other data structures or algorithmic techniques. I will explain these combinations in [High-Frequency Prefix Sum Exercises](https://labuladong.online/en/algo/problem-set/perfix-sum/) along with relevant exercises. However, the prefix sum technique has several limitations. **First Limitation: The prerequisite for using the prefix sum technique is that the original array `nums` does not change.** If an element in the original array changes, the values in the `preSum` array after that element become invalid, requiring $O(n)$ time to recalculate the `preSum` array, which is similar to the brute-force method. **Second Limitation: The prefix sum technique is only applicable in scenarios with inverse operations.** For example, in summation scenarios, if you know $x + 6 = 10$, you can deduce $x = 10 - 6 = 4$. Similarly, in product scenarios, if you know $x * 6 = 12$, you can deduce $x = 12 / 6 = 2$. This is known as having an inverse operation, which allows the use of the prefix sum technique. However, some scenarios do not have inverse operations. For example, in maximum value scenarios, if you know $max(x, 8) = 8$, you cannot deduce the value of $x$. To address both issues simultaneously, more advanced data structures are required. The most general solution is the [Segment Tree](https://labuladong.online/en/algo/data-structure-basic/segment-tree-basic/), which will be explained in detail in the data structure design chapter. ================================================ FILE: algorithmic-thinking/probability-problems.md ================================================ In the previous article [Discussing Random Algorithms in Games](https://labuladong.online/en/algo/frequency-interview/random-algorithm/), we talked about the Monte Carlo method for verifying probability algorithms. Today, let's dive into some lighter content: a few interesting problems related to probability. There are two simplest principles for calculating probability: **Principle One:** To calculate probability, you must have a reference frame called the "sample space," which includes all possible outcomes of a random event. The probability of event A occurring = Number of sample points in A / Total number of sample points in the sample space. **Principle Two:** When calculating probability, it's crucial to understand that probability is a continuous whole. You cannot split continuous probability, which is known as conditional probability. We learned these two principles in high school, but we still easily make mistakes. Interestingly, the process of making these mistakes often follows a similar pattern: First, we overlook Principle Two and incorrectly calculate the sample space. Then, using Principle One, we arrive at the wrong answer. Next, let's explore a few simple yet deceptive problems: the Boy or Girl problem, the Birthday Paradox, and the Monty Hall problem. Of course, the Monty Hall problem is probably the most familiar to everyone, so we'll delve into some interesting thoughts about it. ### I. The Boy-Girl Problem Imagine a family with two children. You are told that one of the children is a boy. What is the probability that the other child is also a boy? Many people, including myself, might instinctively answer: 1/2, because the other child could either be a boy or a girl, with equal probability. However, the correct answer is actually 1/3. Why is the initial thought wrong? It's because the sample space was not correctly calculated, leading to a mistake in the principle of probability calculation. With two children, the sample space consists of 4 possibilities: older brother and younger sister, older brother and younger brother, older sister and younger sister, and older sister and younger brother. Knowing that one child is a boy eliminates the older sister and younger sister scenario, reducing the sample space to 3. Only one of these scenarios involves both children being boys (older brother and younger brother), so the probability is 1/3. Why does the calculation of the sample space often go wrong? It's because we overlook conditional probability, confusing the following two questions: 1. If a family has only one child, what is the probability that the child is a boy? 2. If a family has two children, and one of them is a boy, what is the probability that the other child is also a boy? According to the second principle, probability problems are continuous, and these two questions should not be confused. The second question requires the use of conditional probability, which is the probability of one child being a boy given that the other child is a boy. Applying the formula for conditional probability makes this calculation straightforward. Through this problem, readers should understand the relationship between the two principles of probability calculation. The most misleading aspect is the neglect of conditional probability. To avoid being misled, the simplest method is to enumerate all possible outcomes. Finally, I've encountered a rather odd objection to this problem: What if the two children are twins and there is no age difference between them? I must admit, there seems to be a hint of logic in this! However, we use age difference to represent the independence of the two children. Even if the children are of the same gender, there are still two distinct possibilities. So, let's not use twins as a counterargument. ### II. Birthday Paradox The birthday paradox arises from the question: How many people need to be in a room for there to be at least a 50% chance that two of them share the same birthday? The answer is 23 people, meaning that if there are 23 people in a room, there's a 50% chance that two of them will have the same birthday. This conclusion seems counterintuitive, hence it is called a paradox. Intuitively, you might think you need at least 183 people to reach a 50% probability, since there are 365 days in a year. However, this isn't the case, and the disbelief stems from two common misconceptions: **The first misconception is misunderstanding the meaning of "exist."** Readers might assume that if there's a 50% chance of a shared birthday among 23 people, it implies: Suppose there are 22 people in a room, and I join them, there's a 50% chance that one of them shares my birthday. But how can that be possible? It's not. This thinking is self-centered, whereas the problem's probability describes the group as a whole. "Exist" means any two of the 23 people, involving combinations, largely unrelated to you. If you want to calculate the probability of someone having the same birthday as you, you can do it like this: 1 - P(22 people have different birthdays from mine) = 1 - (364/365)^22 = 0.06 Doesn't this result seem more reasonable? The birthday paradox doesn't focus on a single individual but rather on the group, including all possible combinations, making the total probability much larger. **The second misconception is thinking that probability changes linearly.** Readers might think that if 23 people give a 50% probability of a shared birthday, then 46 people should give a 100% probability. This is incorrect. Like a game with a 50% win rate, playing twice doesn't guarantee a 100% win rate. Clearly, playing twice gives a 75% win rate: `P(winning in two tries) = P(winning the first time) + P(not winning the first time but winning the second time) = 1/2 + 1/2*1/2 = 75%` The same logic applies to the birthday paradox. Probability isn't simply additive; it involves a continuous process. Thus, the conclusion isn't unreasonable. Why is it that with just 23 people, the probability of sharing a birthday exceeds 50%? First, calculate the probability that all 23 have unique (non-repeating) birthdays. With 1 person, the probability is `365/365`; with 2 people, it's `365/365 × 364/365`, and so on. For 23 people, the probability is: ![](../pictures/probability/p.png) This calculation yields approximately 0.493, so the probability of at least two people sharing a birthday is 0.507, roughly 50%. In fact, following this method, when the number of people reaches 70, the probability of a shared birthday rises to 99.9%, almost certain. Thus, probabilistically, it's not surprising to find people with the same birthday in a small group of a few dozen people. ### 3. The Monty Hall Problem This classic game involves a participant facing three doors. Behind two doors are goats, and behind one door is a car. The participant picks a door, and whatever is behind it becomes theirs (the car is obviously more valuable). The host decides to assist: after the participant chooses a door, the host opens one of the remaining doors to reveal a goat (the host knows what is behind each door). The participant is then given a chance to switch doors. Should they switch or stick with their original choice? To avoid confusion for those encountering this problem for the first time, let's describe it in more detail: You are the participant in the game. There are doors 1, 2, and 3. Suppose you randomly choose door 1. The host then opens door 3 and shows you a goat. Should you stick with your initial choice of door 1, or switch to door 2? ![](../pictures/probability/sanmen.png) The answer is to switch doors. If you switch, the probability of winning the car is 2/3, whereas if you don't switch, it is 1/3. This counterintuitive result might suggest that the probability should be 1/2 since there are two remaining doors, one with a goat and one with a car. However, this is not the case. Like the boy-girl paradox mentioned earlier, the simplest and safest method is to enumerate all possible outcomes: ![](../pictures/probability/tree.png) It is clear that the probability of winning by switching doors is 2/3, while not switching gives a probability of 1/3. There is an even simpler explanation for this problem: the host's action effectively "condenses" the probability. Initially, the chance of choosing the car is 1/3, leaving a probability of 2/3 for the car being behind one of the other two doors. When the host reveals a goat behind one door, the 2/3 probability is transferred to the remaining door. Thus, would you hold onto the 1/3 probability door, or switch to the "condensed" 2/3 probability door? To make it more intuitive, suppose you have 1 door selected and 2 remaining, and 98 additional doors with goats are added, making 100 doors in total. If asked whether to switch, you would likely refuse, as the probability seems diluted, suggesting the original door is most likely to contain the car. Now, consider starting with 100 doors. You choose one, and the host eliminates 98 goat doors from the remaining 99. Should you switch? Absolutely, because your selected door has a 1% chance, while the other has 99%. Alternatively, not switching is like choosing 1 door, while switching is like choosing 99 doors, making the outcome obvious. Some readers might have thought about this concept. Consider this: If Xiao Ming, unaware of previous events, barges in to help you decide, knowing only that there are two doors, one with a car and one with a goat, what is his probability of choosing the car? For Xiao Ming, the probability is 1/2, and this misinterpretation is the root cause of many errors in solving the Monty Hall problem. Similar to the birthday paradox, people often calculate based on their own perspective, which leads to mistakes. Imagine there are two boxes: Box 1 contains 4 black balls and 2 red balls, while Box 2 contains 2 black balls and 4 red balls. Choosing a box at random, and picking a ball, what is the probability of drawing a red ball? For Xiao Ming, who is uninformed, he randomly picks a box and a ball, leading to a probability of: 1/2 × 2/6 + 1/2 × 4/6 = 1/2 For you, who is informed and knows the higher probability of drawing from Box 2, the probability is: 0 × 2/6 + 1 × 4/6 = 2/3 ================================================ FILE: algorithmic-thinking/set-partition.md ================================================ ::: info Prerequisite Knowledge Before reading this article, you should first learn: - [N-ary Tree Structure and Traversal Framework](https://labuladong.online/en/algo/data-structure-basic/n-ary-tree-traverse-basic/) - [Binary Tree Algorithms (Overview)](https://labuladong.online/en/algo/essential-technique/binary-tree-summary/) - [Backtracking Algorithm Framework](https://labuladong.online/en/algo/essential-technique/backtrack-framework/) - [Ball-and-Box Model: Two Brute-Force Views of Backtracking](https://labuladong.online/en/algo/practice-in-action/two-views-of-backtrack/) ::: I said before that backtracking is the most useful algorithm in written tests. When you have no idea, just use backtracking to do a brute-force search. Even if you cannot pass all test cases, you can still pass some of them. The basic skill of backtracking is not hard: you brute-force a decision tree, and for each step, you "make a choice" before recursion and "undo the choice" after recursion. **However, even for brute-force search, some ideas are better than others.** In this article, we look at a classic backtracking problem: LeetCode 698, “[Partition to K Equal Sum Subsets](https://leetcode.com/problems/partition-to-k-equal-sum-subsets/)”. This problem can help you understand the backtracking mindset more deeply and write backtracking functions more easily. The problem is very simple: You are given an array `nums` and a positive integer `k`. Please check whether `nums` can be divided into `k` subsets such that the sum of elements in each subset is the same. The function signature is: ```java boolean canPartitionKSubsets(int[] nums, int k); ``` ::: info Thinking Question Earlier, in [Subset Partition as a Knapsack Problem](https://labuladong.online/en/algo/dynamic-programming/knapsack2/), we solved a subset partition problem. But that problem only asked us to split the set into two equal subsets, and we could turn it into a knapsack problem and solve it with dynamic programming. Why can partitioning into two equal subsets be transformed into a knapsack problem and solved with dynamic programming, but partitioning into `k` equal subsets cannot be transformed this way and must be solved by brute-force backtracking? Please think about this yourself first. ::: ::: details Answer to the Thinking Question Why can partitioning into two equal subsets be transformed into a knapsack problem? In the setting of [Subset Partition as a Knapsack Problem](https://labuladong.online/en/algo/dynamic-programming/knapsack2/), we have one knapsack and several items. Each item has **two choices**: "put it into the knapsack" or "do not put it into the knapsack". When we split the original set `S` into two equal subsets `S_1` and `S_2`, each element in `S` also has **two choices**: "put it into `S_1`" or "do not put it into `S_1` (put it into `S_2`)". The brute-force idea here is actually the same as in the knapsack problem. But if you want to split `S` into `k` equal subsets, then each element in `S` has **`k` choices**. This is essentially different from the standard knapsack setting, so you cannot directly use the knapsack DP approach. You have to use backtracking and brute-force search. ::: ## Problem Idea Now back to this problem. We need to partition the array into subsets. Subset problems are different from permutation and combination problems, but we can still use the “balls and boxes model” to think about it from two different views. We want to split an array `nums` with `n` numbers into `k` subsets with the same sum. You can imagine putting `n` numbers into `k` “buckets”, and at the end, the sum of numbers in each of the `k` buckets must be the same. In the earlier article [Understanding Backtracking with the Balls and Boxes Model](https://labuladong.online/en/algo/practice-in-action/two-views-of-backtrack/), we said the key of backtracking is: You must know how to “make choices”, then you can use recursion to do brute-force search. If we imitate the way we derive the permutation formula, and assign `n` numbers into `k` buckets, we can also have two views: **First view: from the `n` numbers’ perspective, each number must choose one of the `k` buckets to go into.** ![](../pictures/set-split/5.jpeg) **Second view: from the `k` buckets’ perspective, for each bucket, you go through all `n` numbers in `nums`, and decide whether to put the current number into this bucket.** ![](../pictures/set-split/6.jpeg) You may ask, what is the difference between these two views? Just like in the permutation and subset problems, using different views to do brute-force search gives the same result, but the code logic is different. The concrete implementation is different, and the time and space complexity may also be different. We want to choose the solution with lower complexity. ## Number-based view Everyone knows how to iterate through the `nums` array with a for loop: ```java for (int index = 0; index < nums.length; index++) { print(nums[index]); } ``` Can you traverse the array with recursion? It is also simple: ```java void traverse(int[] nums, int index) { if (index == nums.length) { return; } print(nums[index]); traverse(nums, index + 1); } ``` If you call `traverse(nums, 0)`, it has the same effect as the for loop. Now back to this problem. From the numbers’ view, each number chooses one of `k` buckets. The for-loop version looks like this: ```java // k buckets (collections), record the sum of numbers in each bucket int[] bucket = new int[k]; // enumerate each number in nums for (int index = 0; index < nums.length; index++) { // enumerate each bucket for (int i = 0; i < k; i++) { // decide whether nums[index] should go into the i-th bucket // ... } } ``` If we change it to a recursive version, the logic becomes: ```java // k buckets (collections), record the sum of numbers in each bucket int[] bucket = new int[k]; // enumerate each number in nums void backtrack(int[] nums, int index) { // base case if (index == nums.length) { return; } // enumerate each bucket for (int i = 0; i < bucket.length; i++) { // choose to put it into the i-th bucket bucket[i] += nums[index]; // recursively enumerate the next number's choice backtrack(nums, index + 1); // undo the choice bucket[i] -= nums[index]; } } ``` The above code is only the brute-force structure, it cannot solve the problem yet. But we can fix it with some small changes: ```java class Solution { public boolean canPartitionKSubsets(int[] nums, int k) { // exclude some base cases if (k > nums.length) return false; int sum = 0; for (int v : nums) sum += v; if (sum % k != 0) return false; // k buckets (sets), record the sum of numbers in each bucket int[] bucket = new int[k]; // the theoretical sum of numbers in each bucket int target = sum / k; // try all possibilities, to see if nums can be divided into k subsets with sum of target return backtrack(nums, 0, bucket, target); } // recursively try each number in nums boolean backtrack( int[] nums, int index, int[] bucket, int target) { if (index == nums.length) { // check if the sum of numbers in all buckets is target for (int i = 0; i < bucket.length; i++) { if (bucket[i] != target) { return false; } } // successfully divide nums into k subsets return true; } // try all possible buckets for nums[index] for (int i = 0; i < bucket.length; i++) { // pruning, the bucket is full if (bucket[i] + nums[index] > target) { continue; } // put nums[index] into bucket[i] bucket[i] += nums[index]; /** ![](../pictures/set-split/5.jpeg) */ // recursively try the next number's choices if (backtrack(nums, index + 1, bucket, target)) { return true; } // undo the choice bucket[i] -= nums[index]; } // nums[index] cannot be put into any bucket return false; } } ``` With the earlier explanation, this code should be easy to understand. In fact, we can still do one more optimization. Look at the recursive part of the `backtrack` function: ```java for (int i = 0; i < bucket.length; i++) { // pruning if (bucket[i] + nums[index] > target) { continue; } if (backtrack(nums, index + 1, bucket, target)) { return true; } } ``` **If we can make more cases hit that pruning `if` branch, we can reduce the number of recursive calls, and thus reduce the time complexity to some extent.** How can we make more cases hit this `if` branch? Note that our `index` parameter increases from 0, which means we go through the `nums` array from 0 in the recursion. If we sort the `nums` array in advance and put larger numbers in front, then larger numbers will be put into the `bucket` first. After that, for the remaining numbers, `bucket[i] + nums[index]` will be larger, so the pruning `if` condition is easier to trigger. So we can add some code to the previous solution: ```java boolean canPartitionKSubsets(int[] nums, int k) { // other code remains unchanged // ... // sort the nums array in descending order Arrays.sort(nums); // reverse the array to get the descending order array for (i = 0, j = nums.length - 1; i < j; i++, j--) { int temp = nums[i]; nums[i] = nums[j]; nums[j] = temp; } // ***************** return backtrack(nums, 0, bucket, target); } ``` This solution is correct, but it is still slow and cannot pass all test cases. Next we will look at the solution from the other view. ## The Bucket Perspective At the beginning of the article, we said: **from the bucket perspective, we use brute-force. For each bucket, we scan all numbers in `nums` and decide whether to put the current number into this bucket. After one bucket is full, we continue to fill the next bucket, until all buckets are full**. We can express this idea with the following code: ```java // fill all the buckets until while (k > 0) { // record the sum of numbers in the current bucket int bucket = 0; for (int i = 0; i < nums.length; i++) { // decide whether to put nums[i] into the current bucket if (canAdd(bucket, num[i])) { bucket += nums[i]; } if (bucket == target) { // filled one bucket, move on to the next one k--; break; } } } ``` We can also rewrite this `while` loop as a recursive function. It is a bit more complex than before. First, we write a `backtrack` function: ```java boolean backtrack(int k, int bucket, int[] nums, int start, boolean[] used, int target); ``` Do not be scared by so many parameters. I will explain them one by one. If you fully understood the earlier article [Understanding Backtracking with the Ball-and-Box Model](https://labuladong.online/en/algo/practice-in-action/two-views-of-backtrack/), you can also write such a backtracking function easily. The parameters of this `backtrack` function can be understood like this: Now bucket `k` is deciding whether it should take the element `nums[start]`. The current sum of numbers already in bucket `k` is `bucket`. `used` marks whether an element has already been put into some bucket. `target` is the required sum for each bucket. With this function definition, we can call `backtrack` like this: ```java class Solution { public boolean canPartitionKSubsets(int[] nums, int k) { // exclude some base cases if (k > nums.length) return false; int sum = 0; for (int v : nums) sum += v; if (sum % k != 0) return false; boolean[] used = new boolean[nums.length]; int target = sum / k; // the k-th bucket is initially empty, start making choices from nums[0] return backtrack(k, 0, nums, 0, used, target); } } ``` Before we implement the logic of `backtrack`, repeat the bucket perspective again: 1. We must scan all numbers in `nums` and decide which numbers should be put into the current bucket. 2. If the current bucket is full (the sum in this bucket reaches `target`), we move on to the next bucket and repeat step 1. The code below implements this logic: ```java class Solution { public boolean canPartitionKSubsets(int[] nums, int k) { // see the above text } boolean backtrack(int k, int bucket, int[] nums, int start, boolean[] used, int target) { // base case if (k == 0) { // all buckets are full, and nums must all be used up // because target == sum / k return true; } if (bucket == target) { /** ![](../pictures/set-split/6.jpeg) */ // the current bucket is full, recursively enumerate the choices for the next bucket // let the next bucket start selecting numbers from nums[0] return backtrack(k - 1, 0 ,nums, 0, used, target); } // start from start to look back for valid nums[i] to put into the current bucket for (int i = start; i < nums.length; i++) { // pruning if (used[i]) { // nums[i] has already been placed in another bucket continue; } if (nums[i] + bucket > target) { // the current bucket cannot hold nums[i] continue; } // make a choice, put nums[i] into the current bucket used[i] = true; bucket += nums[i]; // recursively enumerate whether the next number should be placed in the current bucket if (backtrack(k, bucket, nums, i + 1, used, target)) { return true; } // cancel the choice used[i] = false; bucket -= nums[i]; } // having exhausted all numbers, none can fill the current bucket return false; } } ``` **This code can produce the correct answer, but it is very slow. We should think about how to optimize it.** First, in this solution each bucket is actually identical, but our backtracking algorithm treats them as different. This causes repeated work. What does this mean? Our backtracking algorithm is, in the end, brute-forcing all possible combinations, and checking whether we can form `k` buckets (subsets) each with sum `target`. For example, in the case below, `target = 5`. The algorithm may first fill the first bucket with `1, 4`: ![](../pictures/set-split/1.jpeg) Now the first bucket is full, so we fill the second bucket. The algorithm may put `2, 3` in it: ![](../pictures/set-split/2.jpeg) Then it continues with the later elements, using brute-force to try to build more buckets with sum 5. But if, in the end, we cannot form `k` subsets with sum `target`, what will the algorithm do? Backtracking will go back to the first bucket and try again. Now it knows `{1, 4}` in the first bucket does not work, so it will try `{2, 3}` in the first bucket: ![](../pictures/set-split/3.jpeg) Now the first bucket is full again. Then it starts to fill the second bucket with `{1, 4}`: ![](../pictures/set-split/4.jpeg) Here you should see the problem. This situation is actually the same as the previous one. That means we already know there is no solution. We do not need to brute-force again. But our algorithm will still keep searching, because it thinks: “The first and second buckets hold different elements now, so these are two different cases.” How can we make the algorithm smarter, so it can recognize this and avoid redundant work? Notice that the `used` array must be the same in these two situations. So we can treat the `used` array as the “state” of the backtracking process. **So we can use a `memo` map. When we fill one bucket, we record the current `used` state. If the same `used` state appears again later, we know we have already tried this situation, so we can stop and prune the search.** You may ask: `used` is a boolean array, how can we use it as a key? This is easy. We can convert the array to a string and then use the string as the key in a hash map. Look at the code. We only need to slightly change the `backtrack` function: ```java class Solution { // Memoization, storing the state of the used array HashMap memo = new HashMap<>(); public boolean canPartitionKSubsets(int[] nums, int k) { // See above } boolean backtrack(int k, int bucket, int[] nums, int start, boolean[] used, int target) { // base case if (k == 0) { return true; } // Convert the state of used into a string like [true, false, ...] // for easy storage in the HashMap String state = Arrays.toString(used); if (bucket == target) { // The current bucket is filled, recursively enumerate the choices for the next bucket boolean res = backtrack(k - 1, 0, nums, 0, used, target); // Store the current state and result in the memoization memo.put(state, res); return res; } if (memo.containsKey(state)) { // If the current state has been calculated before, return directly without further recursion return memo.get(state); } // Other logic remains unchanged... } } ``` If we submit this version, we will still find it slow. This time, the problem is not the algorithm logic, but the implementation. **Because in each recursion we convert the `used` array to a string, which is costly for the language runtime. So we can optimize further.** Notice the constraint: `nums.length <= 16`. That means `used` will never have more than 16 elements. So we can use a bit-mask trick: use one integer variable `used` instead of a boolean array. More precisely, we can use the `i`-th bit of integer `used` (`(used >> i) & 1`) to represent `used[i]` being true or false. With this, we both save space and can use the integer `used` directly as the key in a HashMap, without converting arrays to strings. Here is the final solution: ```java class Solution { public boolean canPartitionKSubsets(int[] nums, int k) { // exclude some basic cases if (k > nums.length) return false; int sum = 0; for (int v : nums) sum += v; if (sum % k != 0) return false; // use bit manipulation technique int used = 0; int target = sum / k; // the k-th bucket initially has nothing, start choosing from nums[0] return backtrack(k, 0, nums, 0, used, target); } HashMap memo = new HashMap<>(); boolean backtrack(int k, int bucket, int[] nums, int start, int used, int target) { // base case if (k == 0) { // all buckets are filled, and all nums must have been used return true; } if (bucket == target) { // current bucket is filled, recursively enumerate the choices for the next bucket // let the next bucket start choosing from nums[0] boolean res = backtrack(k - 1, 0, nums, 0, used, target); // cache the result memo.put(used, res); return res; } if (memo.containsKey(used)) { // avoid redundant calculations return memo.get(used); } for (int i = start; i < nums.length; i++) { // pruning // check if the i-th bit is 1 if (((used >> i) & 1) == 1) { // nums[i] has already been used in another bucket continue; } if (nums[i] + bucket > target) { continue; } // make a choice // set the i-th bit to 1 used |= 1 << i; bucket += nums[i]; // recursively enumerate whether the next number goes into the current bucket if (backtrack(k, bucket, nums, i + 1, used, target)) { return true; } // undo the choice // use XOR operation to reset the i-th bit to 0 used ^= 1 << i; bucket -= nums[i]; } return false; } } ``` Now the second idea for this problem is done. ## Final Summary Both ideas in this article can produce the correct answer. But even with sorting optimization, the first solution is clearly slower than the second one. Why? Let’s analyze the time complexity. Assume `n` is the number of elements in `nums`. For the first solution (the number perspective): for each of the `n` numbers, we have `k` choices (which bucket to put it in). So the number of combinations is `k^n`, and the time complexity is $O(k^n)$. For the second solution (the bucket perspective): each bucket scans `n` numbers, and for each number we have two choices: “put in” or “not put in”. So there are `2^n` combinations per bucket. We have `k` buckets, so the time complexity is $O(k * 2^n)$. **Of course, these are rough upper bounds for the worst case. In practice the complexity is much better thanks to all our pruning. But from the upper bounds we can already see that the first idea is much slower.** So, who says backtracking has no technique? Backtracking is brute-force, but brute-force can be smart or stupid. The key is: from which “perspective” do you brute-force? In simple words, we should try to “do more small steps” instead of “one huge step”. That is, it is better to have more choices with small branching (multiplicative growth), instead of having a huge branching factor (exponential growth). Doing `n` times of “k choices once” giving $O(k^n)$ is often worse than doing `n` times of “2 choices” repeated `k` times giving $O(k * 2^n)$. In this problem, we tried brute-force from two perspectives. The code looks long, but the core logic is similar. After reading this article, you should have a deeper understanding of backtracking. ================================================ FILE: algorithmic-thinking/sliding-window.md ================================================ In the previous article [Double Pointer Techniques](https://labuladong.online/en/algo/essential-technique/array-two-pointers-summary/), we talked about some simple double pointer tricks for arrays. In this article, we will discuss a slightly more complex technique: the sliding window. The sliding window can be seen as a fast and slow double pointer. One pointer moves fast, the other slow, and the part between them is the window. **The sliding window algorithm is mainly used to solve subarray problems, such as finding the longest or shortest subarray that meets certain conditions.** You can open the visualization panel below. Click the line while (right < s.length) multiple times to see how the window moves step by step: This article will give you a template that helps you write correct solutions easily. Each problem also has a visualization panel to help you better understand how the window slides. ## Sliding Window Framework Overview If you use brute-force, you need nested for-loops to go through all subarrays. The time complexity is $O(N^2)$: ```java for (int i = 0; i < nums.length; i++) { for (int j = i; j < nums.length; j++) { // nums[i, j] is a subarray } } ``` The sliding window idea is simple: keep a window, move it step by step, and update the answer. The general logic is: ```java // The range [left, right) is the window int left = 0, right = 0; while (right < nums.size()) { // Expand the window window.addLast(nums[right]); right++; while (window needs shrink) { // Shrink the window window.removeFirst(nums[left]); left++; } } ``` If you use this sliding window framework, the time complexity is $O(N)$, which is much faster than the brute-force solution. ::: info Why is it $O(N)$? Some of you may ask, "Isn't there a nested while loop? Why is the complexity $O(N)$?" Simply put, the pointers `left` and `right` never move backward. They only move forward. So, each element in the string/array is added to the window once, and removed once. No element is added or removed more than once, so the time complexity is proportional to the length of the string/array. But in the nested for-loop brute-force solution, `j` moves backward, so some elements are counted many times. That's why its time complexity is $O(N^2)$. If you want to learn more about time and space complexity, check my article [Guide to Analyzing Algorithm Complexity](https://labuladong.online/en/algo/essential-technique/complexity-analysis/). ::: ::: info Can sliding window really list all subarrays in $O(N)$ time? Actually, this is a mistake. **Sliding window cannot list all substrings.** If you want to go through all substrings, you have to use the nested for-loops. But for some problems, you don't need to check all substrings to find the answer. In these cases, the sliding window is a good template to make the solution faster and avoid extra calculations. That's why in [The Essence of Algorithms](https://labuladong.online/en/algo/essential-technique/algorithm-summary/), I put sliding window in the category of "smart enumeration". ::: What really confuses people are the details. For example, how to add new elements to the window, when to shrink the window, and when to update the result. Even if you know these, the code may still have bugs, and it's not easy to debug. **So today I will give you a sliding window code template. I will even add print debug statements for you. Next time you see a related problem, just recall this template and change three places. This will help you avoid bugs.** Since most examples in this article are substring problems, and a string is just an array, the input will be a string. When you solve problems, adapt as needed: ```java // Pseudocode framework of sliding window algorithm void slidingWindow(String s) { // Use an appropriate data structure to record the data in the window, which can vary according to the specific scenario // For example, if I want to record the frequency of elements in the window, I would use a map // If I want to record the sum of elements in the window, I could just use an int Object window = ... int left = 0, right = 0; while (right < s.length()) { // c is the character that will be added to the window char c = s[right]; window.add(c) // Expand the window right++; // Perform a series of updates to the data within the window ... // *** Position of debug output *** // Note that in the final solution code, do not use print // Because IO operations are time-consuming and may cause timeouts printf("window: [%d, %d) ", left, right); // *********************** // Determine whether the left side of the window needs to shrink while (left < right && window needs shrink) { // d is the character that will be removed from the window char d = s[left]; window.remove(d) // Shrink the window left++; // Perform a series of updates to the data within the window ... } } } ``` **There are two places in the template marked with `...`. These are where you update the data for the window. In a real problem, just fill in your logic here. These two places are for expanding and shrinking the window, and you will see that their logic is almost the same, just opposite.** With this template, if you get a substring or subarray problem, just answer these three questions: 1. When should you move `right` to expand the window? What data should you update when adding a character to the window? 2. When should you stop expanding and start moving `left` to shrink the window? What data should you update when removing a character from the window? 3. When should you update the result? If you can answer these questions, you can use the sliding window technique for the problem. Next, we'll use this template to solve four LeetCode problems. For the first problem, I will explain the ideas in detail. For the others, just use the template and solve them quickly. ## 1. Minimum Window Substring Let's look at LeetCode problem 76: ["Minimum Window Substring"](https://leetcode.com/problems/minimum-window-substring/) (Hard): **LeetCode 76. Minimum Window Substring** Given two strings `s` and `t` of lengths `m` and `n` respectively, return *the **minimum window*** ***substring**** of *`s`* such that every character in *`t`* (**including duplicates**) is included in the window*. If there is no such substring, return *the empty string *`""`. The testcases will be generated such that the answer is **unique**. Example 1:** ``` **Input:** s = "ADOBECODEBANC", t = "ABC" **Output:** "BANC" **Explanation:** The minimum window substring "BANC" includes 'A', 'B', and 'C' from string t. ``` Example 2:** ``` **Input:** s = "a", t = "a" **Output:** "a" **Explanation:** The entire string s is the minimum window. ``` Example 3:** ``` **Input:** s = "a", t = "aa" **Output:** "" **Explanation:** Both 'a's from t must be included in the window. Since the largest window of s only has one 'a', return empty string. ``` **Constraints:** - `m == s.length` - `n == t.length` - `1 <= m, n <= 10^(5)` - `s` and `t` consist of uppercase and lowercase English letters. **Follow up:** Could you find an algorithm that runs in `O(m + n)` time? You need to find a substring in `S` (source) that contains all the letters from `T` (target), and this substring must be the shortest one among all possible substrings. If we use a brute-force solution, the code looks like this: ```java for (int i = 0; i < s.length(); i++) for (int j = i + 1; j < s.length(); j++) if s[i:j] contains all letters in t: update answer ``` This idea is simple, but the time complexity is definitely more than $O(N^2)$, which is not good. **The sliding window algorithm works like this:** 1. We use two pointers, left and right, to define a window in string `S`. Initialize `left = right = 0`. The range `[left, right)` is a **left-closed, right-open** window. ::: tip Why use "left-closed, right-open" interval? **In theory, you can use any kind of interval, but using left-closed, right-open is the most convenient.** When you set `left = right = 0`, the interval `[0, 0)` has no elements. But if you move `right` one step, `[0, 1)` now contains element `0`. If you use both ends open, moving `right` to `(0, 1)` still has no element. If you use both ends closed, `[0, 0]` already contains one element at the start. These two cases will make boundary handling harder. ::: 2. First, keep moving the `right` pointer to expand the window `[left, right)`, until the window contains all the characters in `T`. 3. Then, stop moving `right`, and start moving `left` to shrink the window `[left, right)`, until the window no longer contains all characters in `T`. Each time you move `left`, update the answer. 4. Repeat steps 2 and 3 until `right` reaches the end of string `S`. This idea is not hard. **Step 2 is to find a "valid solution"; step 3 is to optimize this "valid solution" to find the optimal answer, which is the shortest substring.** The left and right pointers move back and forth, making the window grow and shrink, just like a caterpillar moving along—this is why it's called a "sliding window." Let's use some pictures for understanding. `needs` and `window` are like counters. `needs` records the count of each character in `T`, and `window` records the count of each character in the current window. Initial state: ![](../pictures/slidingwindow/1.png) Move `right` until the window `[left, right)` contains all characters in `T`: ![](../pictures/slidingwindow/2.png) Now move `left` to shrink the window `[left, right)`: ![](../pictures/slidingwindow/3.png) Stop moving `left` when the window no longer meets the requirement: ![](../pictures/slidingwindow/4.png) Then repeat: move `right`, then move `left`... until `right` reaches the end of `S`. The algorithm ends. If you understand this process, congratulations, you have mastered the sliding window algorithm. **Now let's look at the sliding window code template:** First, initialize two hash maps, `window` and `need`, to record the characters in the window and the characters you need: ```java // Count of characters in window HashMap window = new HashMap<>(); // Count of required characters HashMap need = new HashMap<>(); for (int i = 0; i < t.length(); i++) { char c = t.charAt(i); need.put(c, need.getOrDefault(c, 0) + 1); } ``` Then, use `left` and `right` as the two ends of the window. Remember, `[left, right)` is left-closed, right-open, so at the start the window is empty: ```java int left = 0, right = 0; int valid = 0; while (right < s.length()) { // c is the character going into the window char c = s.charAt(right); // Move right to expand window right++; // Update data inside the window ... } ``` **The `valid` variable counts how many characters in the window meet the `need` requirement.** If `valid` equals `need.size()`, the window is valid and covers all characters in `T`. **Now, using this template, just think about these questions:** 1. When should you move `right` to expand the window? When you add a character to the window, what should you update? 2. When should you stop expanding and start moving `left` to shrink the window? When you remove a character, what should you update? 3. Should you update the result when expanding or shrinking the window? If a character enters the window, increase its count in `window`. If a character leaves, decrease its count. When `valid` meets the requirement, start shrinking the window. Update the answer when shrinking. Here is the complete code: ```java 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 left = 0, right = 0; /** ![](../pictures/slidingwindow/1.png) */ int valid = 0; // record the start index and length of the minimum coverage substring int start = 0, len = Integer.MAX_VALUE; while (right < s.length()) { // c is the character that will be added to the window char c = s.charAt(right); // expand the window right++; // perform a series of updates to the data inside the window if (need.containsKey(c)) { window.put(c, window.getOrDefault(c, 0) + 1); if (window.get(c).equals(need.get(c))) valid++; } // determine whether the left side of the window needs to be contracted while (valid == need.size()) { /** ![](../pictures/slidingwindow/2.png) */ // update the minimum coverage substring here if (right - left < len) { start = left; len = right - left; } // d is the character that will be removed from the window char d = s.charAt(left); // shrink the window left++; // perform a series of updates to the data inside the window if (need.containsKey(d)) { if (window.get(d).equals(need.get(d))) valid--; window.put(d, window.get(d) - 1); } } /** ![](../pictures/slidingwindow/4.png) */ } // return the minimum coverage substring return len == Integer.MAX_VALUE ? "" : s.substring(start, start + len); } } ``` You can open the panel below and click the line while (right < s.length) several times to see how the sliding window `[left, right)` moves: ::: warning Note for Java users Be careful when comparing Java wrapper classes like `Integer` and `String`. You should use the `equals` method instead of `==`, or you may get errors. For example, don't write `window.get(d) == need.get(d)`, but use `window.get(d).equals(need.get(d))`. This applies to the following problems as well. ::: In the code above, when a character's count in `window` meets the need in `need`, update `valid` to show that one more character meets the requirement. Notice that the updates for adding and removing characters are symmetric. When `valid == need.size()`, all characters in `T` are included, and you have a valid window. Now you should start shrinking the window to try to find the smallest one. When moving `left` to shrink the window, every window is a valid answer, so update the result during the shrinking phase to find the shortest one. Now you should fully understand this template. The sliding window algorithm is not hard, just some details to be careful with. **Whenever you see a sliding window problem, use this template and your code will be bug-free and easy to write.** Let's use this template to quickly solve a few more problems. Once you see the description, you will know what to do. ## 2. Permutation in String This is LeetCode Problem 567 "[Permutation in String](https://leetcode.com/problems/permutation-in-string/)", medium level: **LeetCode 567. Permutation in String** Given two strings `s1` and `s2`, return `true`* if *`s2`* contains a permutation of *`s1`*, or *`false`* otherwise*. In other words, return `true` if one of `s1`'s permutations is the substring of `s2`. Example 1:** ``` **Input:** s1 = "ab", s2 = "eidbaooo" **Output:** true **Explanation:** s2 contains one permutation of s1 ("ba"). ``` Example 2:** ``` **Input:** s1 = "ab", s2 = "eidboaoo" **Output:** false ``` **Constraints:** - `1 <= s1.length, s2.length <= 10^(4)` - `s1` and `s2` consist of lowercase English letters. Note that the input `s1` can contain duplicate characters, so this problem is not easy. This is a typical sliding window problem. Basically, you are given a string `S` and a string `T`. You need to check if there is a substring in `S` with the same length as `T`, and that substring contains all characters in `T`. First, copy and paste the sliding window template code. Then, just make a few changes to answer the questions mentioned earlier, and you can solve this problem: ```java class Solution { // determine if there is a permutation of t in s 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++; // update the data inside the window if (need.containsKey(c)) { window.put(c, window.getOrDefault(c, 0) + 1); if (window.get(c).equals(need.get(c))) valid++; } // check if the left side of the window needs to shrink while (right - left >= t.length()) { // check here if a valid substring is found if (valid == need.size()) return true; char d = s.charAt(left); left++; // update the data inside the window if (need.containsKey(d)) { if (window.get(d).equals(need.get(d))) valid--; window.put(d, window.get(d) - 1); } } } // no valid substring found return false; } } ``` You can open the visual panel below and click the line while (right < s.length) multiple times to see how the fixed-length window slides: The code for this problem is almost the same as the code for the minimum window substring. You only need to change a few things: 1. In this problem, you move `left` to shrink the window when the window size is greater than `t.length()`. Since we are looking for a permutation, the lengths must be the same. 2. When you find `valid == need.size()`, it means the window is a valid permutation, so return `true` right away. How to move the window is the same as the minimum window substring problem. ::: note Small Optimization In this problem, the window `[left, right)` is always a fixed size, which is `t.length()`. Each time the window moves, only one character leaves the window. So you can change the inner while loop to an if statement, and it will work the same. ::: ## 3. Find All Anagrams in a String This is LeetCode Problem 438 "[Find All Anagrams in a String](https://leetcode.com/problems/find-all-anagrams-in-a-string/)", medium level: **LeetCode 438. Find All Anagrams in a String** Given two strings `s` and `p`, return *an array of all the start indices of *`p`*'s anagrams in *`s`. You may return the answer in **any order**. An **Anagram** is a word or phrase formed by rearranging the letters of a different word or phrase, typically using all the original letters exactly once. Example 1:** ``` **Input:** s = "cbaebabacd", p = "abc" **Output:** [0,6] **Explanation:** The substring with start index = 0 is "cba", which is an anagram of "abc". The substring with start index = 6 is "bac", which is an anagram of "abc". ``` Example 2:** ``` **Input:** s = "abab", p = "ab" **Output:** [0,1,2] **Explanation:** The substring with start index = 0 is "ab", which is an anagram of "ab". The substring with start index = 1 is "ba", which is an anagram of "ab". The substring with start index = 2 is "ab", which is an anagram of "ab". ``` **Constraints:** - `1 <= s.length, p.length <= 3 * 10^(4)` - `s` and `p` consist of lowercase English letters. An anagram is just a permutation. The problem gives it a fancy name, but it is the same. You are given a string `S` and a string `T`. Find all the starting indexes of `T`'s permutations in `S`. Just copy the sliding window framework, answer the three key questions, and you can solve this problem: ```java 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; // record the result List res = new ArrayList<>(); while (right < s.length()) { char c = s.charAt(right); right++; // update various data within the window if (need.containsKey(c)) { window.put(c, window.getOrDefault(c, 0) + 1); if (window.get(c).equals(need.get(c))) { valid++; } } // check if the left side of the window needs to shrink while (right - left >= t.length()) { // when the window meets the condition, add the starting index to res if (valid == need.size()) { res.add(left); } char d = s.charAt(left); left++; // update various data within the window if (need.containsKey(d)) { if (window.get(d).equals(need.get(d))) { valid--; } window.put(d, window.get(d) - 1); } } } return res; } } ``` This is almost the same as the previous problem. When you find a valid anagram (permutation), just save the starting index in `res`. You can open the visual panel below and click the line while (right < s.length) multiple times to see how the fixed-length window slides: ## 4. Longest Substring Without Repeating Characters This is LeetCode Problem 3 "[Longest Substring Without Repeating Characters](https://leetcode.com/problems/longest-substring-without-repeating-characters/)", medium level: **LeetCode 3. Longest Substring Without Repeating Characters** Given a string `s`, find the length of the **longest** **substring** without repeating characters. Example 1:** ``` **Input:** s = "abcabcbb" **Output:** 3 **Explanation:** The answer is "abc", with the length of 3. ``` Example 2:** ``` **Input:** s = "bbbbb" **Output:** 1 **Explanation:** The answer is "b", with the length of 1. ``` Example 3:** ``` **Input:** s = "pwwkew" **Output:** 3 **Explanation:** The answer is "wke", with the length of 3. Notice that the answer must be a substring, "pwke" is a subsequence and not a substring. ``` **Constraints:** - `0 <= s.length <= 5 * 10^(4)` - `s` consists of English letters, digits, symbols and spaces. This problem is a bit different. You cannot use the same template directly, but it is actually simpler. Just make a small change: ```java class Solution { public int lengthOfLongestSubstring(String s) { Map window = new HashMap<>(); int left = 0, right = 0; // record the result int res = 0; while (right < s.length()) { char c = s.charAt(right); right++; // perform a series of updates within the window window.put(c, window.getOrDefault(c, 0) + 1); // determine whether the left side of the window needs to shrink while (window.get(c) > 1) { char d = s.charAt(left); left++; // perform a series of updates within the window window.put(d, window.get(d) - 1); } // update the result here res = Math.max(res, right - left); } return res; } } ``` You can open the visual panel below and click the line while (right < s.length) multiple times to see how the window updates the answer: This problem is simpler. You do not need `need` and `valid`. You only need to update the character count in the `window`. If `window[c]` is greater than 1, it means there are repeated characters in the window. Then you need to move `left` to shrink the window. One thing to pay attention to is: when should you update the result `res`? We want the longest substring without repeating characters. At which moment does the window have no repeated characters? This is different from before. You should update `res` after shrinking the window. The while loop for shrinking only runs when there are repeated characters. After shrinking, the window must have no repeated characters. That is all for the sliding window algorithm template. I hope you can understand the logic, remember the template, and apply it. To review, when you face a problem about subarrays or substrings, if you can answer these three questions, you can use the sliding window algorithm: 1. When should you expand the window? 2. When should you shrink the window? 3. When should you update the answer? In [Sliding Window Exercise Collection](https://labuladong.online/en/algo/problem-set/sliding-window/), I list more classic problems using this way of thinking, to help you understand and remember the algorithm. After that, you will not be afraid of substring or subarray problems. ================================================ FILE: algorithmic-thinking/string-multiplication.md ================================================ For small numbers, you can use the operators provided by your programming language to do calculations. But if the numbers are very large, the data type might overflow. One solution is to input the numbers as strings, then simulate the multiplication process we learned in elementary school, and use strings to represent the result. Let's look at LeetCode problem 43, "[Multiply Strings](https://leetcode.com/problems/multiply-strings/)": **LeetCode 43. Multiply Strings** Given two non-negative integers `num1` and `num2` represented as strings, return the product of `num1` and `num2`, also represented as a string. **Note:** You must not use any built-in BigInteger library or convert the inputs to integer directly. Example 1:** ``` **Input:** num1 = "2", num2 = "3" **Output:** "6" ``` Example 2:** ``` **Input:** num1 = "123", num2 = "456" **Output:** "56088" ``` **Constraints:** - `1 <= num1.length, num2.length <= 200` - `num1` and `num2` consist of digits only. - Both `num1` and `num2` do not contain any leading zero, except the number `0` itself. Note that `num1` and `num2` can be very long, so you can't just convert them to integers and multiply. The only way is to simulate the manual multiplication process. For example, if you want to calculate `123 × 45` by hand, you would do it like this: ![](../pictures/string-multiply/1.jpg) First, calculate `123 × 5`, then `123 × 4`, and finally add them with the correct offset. This is very easy for elementary school students. But can you **turn this calculation process into a set of algorithm steps** that a computer can follow? In this simple process, there is multiplication with carry, addition with offset, and addition with carry. There are also some small problems. For example, multiplying two two-digit numbers may get a three- or four-digit result. How can you handle this in a standard way? This is the magic of algorithms. If you don't think like a computer, even simple problems can't be automated. First, even this manual process is a bit "advanced." We can break it down even more. The process of `123 × 5` and `123 × 4` can be split into smaller steps, and then add them at the end: ![](../pictures/string-multiply/2.jpg) Right now, `123` is not a large number. But if it is very big, you can't directly calculate the product. We can use an array to collect the result as we add up the products: ![](../pictures/string-multiply/3.jpg) The whole process works like this: **use two pointers `i` and `j` to walk through `num1` and `num2`, calculate the products, and add the products to the correct position in `res`**, as shown in this GIF: ![](../pictures/string-multiply/4.gif) Now there is a key question: how do you add the product to the correct position in `res`? Or, how do you find the right index in `res` using `i` and `j`? If you look closely, you will see that **the product of `num1[i]` and `num2[j]` should go to `res[i+j]` and `res[i+j+1]`**. ![](../pictures/string-multiply/6.jpg) Once you understand this, you can write code to simulate this process: ```java class Solution { public String multiply(String num1, String num2) { int m = num1.length(), n = num2.length(); // the result can be at most m + n digits int[] res = new int[m + n]; // start multiplying from the least significant digit for (int i = m - 1; i >= 0; i--) { for (int j = n - 1; j >= 0; j--) { int mul = (num1.charAt(i) - '0') * (num2.charAt(j) - '0'); // the product is at the corresponding index in res int p1 = i + j, p2 = i + j + 1; // add to res int sum = mul + res[p2]; res[p2] = sum % 10; res[p1] += sum / 10; } } // the leading zeros that might exist in the result (unused digits) int i = 0; while (i < res.length && res[i] == 0) i++; // convert the calculation result to a string StringBuilder str = new StringBuilder(); for (; i < res.length; i++) str.append(res[i]); return str.length() == 0 ? "0" : str.toString(); } } ``` Now, the string multiplication algorithm is done. **To sum up**, the ways we usually think about calculation are actually hard for computers. The arithmetic processes we are used to are not complicated, but it's not easy to turn them into code. Algorithms need to simplify the process. Here, we get the result by adding as we calculate. There is a saying: "Don't fall into fixed ways of thinking. Don't be too mechanical. Be creative." But I think being mechanical is not always bad. It can make things faster and reduce mistakes. Algorithms are a set of mechanical thinking steps. Only by being mechanical can computers help us solve hard problems! Maybe algorithm is just a way to **find fixed ways of thinking**. I hope this article helps you. ================================================ FILE: algorithmic-thinking/two-pointers.md ================================================ ::: info Prerequisites Before reading this article, you should first learn: - [Array Basics](https://labuladong.online/en/algo/data-structure-basic/array-basic/) - [Six Patterns for Linked List Problems](https://labuladong.online/en/algo/essential-technique/linked-list-skills-summary/) ::: When working with array and linked list problems, two-pointer techniques come up all the time. These techniques fall into two main categories: **left-right pointers** and **fast-slow pointers**. Left-right pointers move toward or away from each other. Fast-slow pointers move in the same direction, but one moves faster than the other. For singly linked lists, most techniques involve fast-slow pointers. [Six Patterns for Linked List Problems](https://labuladong.online/en/algo/essential-technique/linked-list-skills-summary/) covers all of them—things like cycle detection and finding the `K`th node from the end. These problems are solved using a `fast` pointer and a `slow` pointer working together. Arrays don't have actual pointers, but we can treat indices as pointers. This lets us apply two-pointer techniques to arrays as well. **This article focuses on two-pointer algorithms for arrays.** ## Fast-Slow Pointer Techniques ### In-Place Modification **A common use of fast-slow pointers in array problems is in-place modification.** Take LeetCode problem 26, "[Remove Duplicates from Sorted Array](https://leetcode.cn/problems/remove-duplicates-from-sorted-array/)," which asks you to deduplicate a sorted array: **LeetCode 26. Remove Duplicates from Sorted Array** Given an integer array `nums` sorted in **non-decreasing order**, remove the duplicates [**in-place**](https://en.wikipedia.org/wiki/In-place_algorithm) such that each unique element appears only **once**. The **relative order** of the elements should be kept the **same**. Then return *the number of unique elements in *`nums`. Consider the number of unique elements of `nums` to be `k`, to get accepted, you need to do the following things: - Change the array `nums` such that the first `k` elements of `nums` contain the unique elements in the order they were present in `nums` initially. The remaining elements of `nums` are not important as well as the size of `nums`. - Return `k`. **Custom Judge:** The judge will test your solution with the following code: ``` int[] nums = [...]; // Input array int[] expectedNums = [...]; // The expected answer with correct length int k = removeDuplicates(nums); // Calls your implementation assert k == expectedNums.length; for (int i = 0; i < k; i++) { assert nums[i] == expectedNums[i]; } ``` If all assertions pass, then your solution will be **accepted**. Example 1:** ``` **Input:** nums = [1,1,2] **Output:** 2, nums = [1,2,_] **Explanation:** Your function should return k = 2, with the first two elements of nums being 1 and 2 respectively. It does not matter what you leave beyond the returned k (hence they are underscores). ``` Example 2:** ``` **Input:** nums = [0,0,1,1,1,2,2,3,3,4] **Output:** 5, nums = [0,1,2,3,4,_,_,_,_,_] **Explanation:** Your function should return k = 5, with the first five elements of nums being 0, 1, 2, 3, and 4 respectively. It does not matter what you leave beyond the returned k (hence they are underscores). ``` **Constraints:** - `1 <= nums.length <= 3 * 10^(4)` - `-100 <= nums[i] <= 100` - `nums` is sorted in **non-decreasing** order. Here's the function signature: ```java int removeDuplicates(int[] nums); ``` Let me quickly explain what "in-place" means: If we weren't required to do this in-place, we could just create a new `int[]` array, put the deduplicated elements in it, and return it. But the problem requires in-place deletion—no new arrays allowed. You can only work with the original array and return a length. With that length and the original array, you can identify which elements remain after deduplication. Since the array is sorted, duplicate elements are always adjacent, so finding them is easy. But if you delete each duplicate immediately when you find it, the data shifting involved would push the time complexity to $O(N^2)$. The efficient solution uses fast-slow pointers: The `slow` pointer trails behind while the `fast` pointer scouts ahead. When `fast` finds a non-duplicate element, it assigns that value to `slow`, and `slow` moves forward one step. This guarantees that `nums[0..slow]` contains only unique elements. Once `fast` finishes traversing the entire array, `nums[0..slow]` is the deduplicated result. Here's the code: ```java class Solution { public int removeDuplicates(int[] nums) { if (nums.length == 0) { return 0; } int slow = 0, fast = 0; while (fast < nums.length) { if (nums[fast] != nums[slow]) { slow++; // maintain nums[0..slow] without duplicates nums[slow] = nums[fast]; } fast++; } // array length is index + 1 return slow + 1; } } ``` Open the visualization panel below and click the line while (fast < nums.length) multiple times to see how the two pointers maintain unique elements in `nums[0..slow]`: Let's extend this a bit. Look at LeetCode problem 83, "[Remove Duplicates from Sorted List](https://leetcode.cn/problems/remove-duplicates-from-sorted-list/)." How would you deduplicate a sorted singly linked list? It's essentially the same as array deduplication—the only difference is replacing array assignment with pointer manipulation. Compare it with the previous code: ```java class Solution { public ListNode deleteDuplicates(ListNode head) { if (head == null) return null; ListNode slow = head, fast = head; while (fast != null) { if (fast.val != slow.val) { // nums[slow] = nums[fast]; slow.next = fast; // slow++; slow = slow.next; } // fast++ fast = fast.next; } // disconnect the link to the subsequent duplicate elements slow.next = null; return head; } } ``` Check out the visualization panel below to see the algorithm in action: ::: note Note You might be wondering: the duplicate nodes aren't actually deleted from the linked list—they're just left hanging there. Is that okay? This comes down to language-specific behavior. Languages like Java and Python have garbage collection that automatically finds and reclaims memory from these "dangling" nodes. Languages like C++ don't have automatic garbage collection, so you'd need to manually free the memory for these nodes. That said, when it comes to developing algorithmic thinking, just understanding this fast-slow pointer technique is what matters. ::: **Beyond deduplicating sorted arrays/linked lists, you might also need to "remove" certain elements from an array in-place.** Take LeetCode problem 27, "[Remove Element](https://leetcode.cn/problems/remove-element/)": **LeetCode 27. Remove Element** Given an integer array `nums` and an integer `val`, remove all occurrences of `val` in `nums` [**in-place**](https://en.wikipedia.org/wiki/In-place_algorithm). The order of the elements may be changed. Then return *the number of elements in *`nums`* which are not equal to *`val`. Consider the number of elements in `nums` which are not equal to `val` be `k`, to get accepted, you need to do the following things: - Change the array `nums` such that the first `k` elements of `nums` contain the elements which are not equal to `val`. The remaining elements of `nums` are not important as well as the size of `nums`. - Return `k`. **Custom Judge:** The judge will test your solution with the following code: ``` int[] nums = [...]; // Input array int val = ...; // Value to remove int[] expectedNums = [...]; // The expected answer with correct length. // It is sorted with no values equaling val. int k = removeElement(nums, val); // Calls your implementation assert k == expectedNums.length; sort(nums, 0, k); // Sort the first k elements of nums for (int i = 0; i < actualLength; i++) { assert nums[i] == expectedNums[i]; } ``` If all assertions pass, then your solution will be **accepted**. Example 1:** ``` **Input:** nums = [3,2,2,3], val = 3 **Output:** 2, nums = [2,2,_,_] **Explanation:** Your function should return k = 2, with the first two elements of nums being 2. It does not matter what you leave beyond the returned k (hence they are underscores). ``` Example 2:** ``` **Input:** nums = [0,1,2,2,3,0,4,2], val = 2 **Output:** 5, nums = [0,1,4,0,3,_,_,_] **Explanation:** Your function should return k = 5, with the first five elements of nums containing 0, 0, 1, 3, and 4. Note that the five elements can be returned in any order. It does not matter what you leave beyond the returned k (hence they are underscores). ``` **Constraints:** - `0 <= nums.length <= 100` - `0 <= nums[i] <= 50` - `0 <= val <= 100` ```java // The function signature is as follows int removeElement(int[] nums, int val); ``` The problem asks you to remove all elements equal to `val` from `nums` in-place. Once again, we use fast-slow pointers: If `fast` encounters an element equal to `val`, it skips over it. Otherwise, it assigns the value to `slow` and moves `slow` forward one step. The approach is exactly the same as the array deduplication problem. Here's the code: ```java class Solution { public int removeElement(int[] nums, int val) { int fast = 0, slow = 0; while (fast < nums.length) { if (nums[fast] != val) { nums[slow] = nums[fast]; slow++; } fast++; } return slow; } } ``` Open the visualization panel below and click the line while (fast < nums.length) multiple times to see how the two pointers maintain `nums[0..slow]` without the target element: Notice a subtle difference from the sorted array deduplication solution: here we assign to `nums[slow]` first, then increment `slow++`. This ensures `nums[0..slow-1]` contains no elements equal to `val`, so the final result length is `slow`. With this `removeElement` function implemented, let's look at LeetCode problem 283, "[Move Zeroes](https://leetcode.cn/problems/move-zeroes/)": Given an array `nums`, **modify it in-place** to move all zeros to the end. Here's the function signature: ```java void moveZeroes(int[] nums); ``` For example, given `nums = [0,1,4,0,2]`, your algorithm returns nothing but modifies `nums` in-place to `[1,4,2,0,0]`. Given what we've covered so far, can you already see the solution? A slight modification to the `removeElement` function above solves this problem, or you can just reuse `removeElement` directly. Moving all zeros to the end is essentially removing all zeros from `nums`, then setting the remaining positions to 0: ```java class Solution { public void moveZeroes(int[] nums) { // remove all 0s from nums // return the length of the array after removing 0s int p = removeElement(nums, 0); // set all elements after p to 0 for (; p < nums.length; p++) { nums[p] = 0; } } // two-pointer technique, reusing the solution from [27. Remove Element]. int removeElement(int[] nums, int val) { int fast = 0, slow = 0; while (fast < nums.length) { if (nums[fast] != val) { nums[slow] = nums[fast]; slow++; } fast++; } return slow; } } ``` Open the visualization panel below and click the line while (fast < nums.length) multiple times to watch the fast and slow pointers move. Then click the line nums[p] = 0; multiple times to see the remaining positions set to 0: That wraps up these in-place array modification problems. ### Sliding Window Another major category of fast-slow pointer problems in arrays is the "sliding window algorithm." I've provided a code framework for sliding window in [The Core Framework for Sliding Window Algorithms](https://labuladong.online/en/algo/essential-technique/sliding-window-framework/): ```java // Pseudocode for the sliding window algorithm framework int left = 0, right = 0; while (right < nums.size()) { // Expand the window window.addLast(nums[right]); right++; while (window needs shrink) { // Shrink the window window.removeFirst(nums[left]); left++; } } ``` I won't repeat the specific problems here—instead, let me just highlight the fast-slow pointer nature of the sliding window algorithm: The `left` pointer stays behind while the `right` pointer moves ahead. The portion between these two pointers forms the "window," and the algorithm solves problems by expanding and shrinking this window. ## II. Common Left-Right Pointer Algorithms ### Binary Search I've covered the details of binary search code in [The Binary Search Framework Explained](https://labuladong.online/en/algo/essential-technique/binary-search-framework/). Here, I'll just show the simplest version to highlight its two-pointer nature: ```java int binarySearch(int[] nums, int target) { // two pointers, one on the left and one on the right, move towards each other int left = 0, right = nums.length - 1; while(left <= right) { int mid = (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 -1; } ``` ### `n`-Sum Problems Let's look at LeetCode problem 167, "[Two Sum II](https://leetcode.cn/problems/two-sum-ii-input-array-is-sorted/)": **LeetCode 167. Two Sum II - Input Array Is Sorted** Given a **1-indexed** array of integers `numbers` that is already ***sorted in non-decreasing order***, find two numbers such that they add up to a specific `target` number. Let these two numbers be `numbers[index1]` and `numbers[index2]` where `1 <= index1 < index2 <= numbers.length`. Return* the indices of the two numbers, *`index1`* and *`index2`*, **added by one** as an integer array *`[index1, index2]`* of length 2.* The tests are generated such that there is **exactly one solution**. You **may not** use the same element twice. Your solution must use only constant extra space. Example 1:** ``` **Input:** numbers = [2,7,11,15], target = 9 **Output:** [1,2] **Explanation:** The sum of 2 and 7 is 9. Therefore, index1 = 1, index2 = 2. We return [1, 2]. ``` Example 2:** ``` **Input:** numbers = [2,3,4], target = 6 **Output:** [1,3] **Explanation:** The sum of 2 and 4 is 6. Therefore index1 = 1, index2 = 3. We return [1, 3]. ``` Example 3:** ``` **Input:** numbers = [-1,0], target = -1 **Output:** [1,2] **Explanation:** The sum of -1 and 0 is -1. Therefore index1 = 1, index2 = 2. We return [1, 2]. ``` **Constraints:** - `2 <= numbers.length <= 3 * 10^(4)` - `-1000 <= numbers[i] <= 1000` - `numbers` is sorted in **non-decreasing order**. - `-1000 <= target <= 1000` - The tests are generated such that there is **exactly one solution**. Whenever you see a sorted array, think two pointers. The approach here is similar to binary search—by adjusting `left` and `right`, you can control the value of `sum`: ```java class Solution { public int[] twoSum(int[] numbers, int target) { // one left and one right pointers moving towards each other int left = 0, right = numbers.length - 1; while (left < right) { int sum = numbers[left] + numbers[right]; if (sum == target) { // the index required by the problem starts from 1 return new int[]{left + 1, right + 1}; } else if (sum < target) { // make the sum a little bigger left++; } else if (sum > target) { // make the sum a little smaller right--; } } return new int[]{-1, -1}; } } ``` In another article, [One Function to Solve All nSum Problems](https://labuladong.online/en/algo/practice-in-action/nsum/), I use a similar left-right pointer technique to provide a general approach for `nSum` problems. At its core, it's all about the two-pointer technique. ### Reversing an Array Most programming languages provide a `reverse` function, but the underlying principle is quite simple. LeetCode problem 344, "[Reverse String](https://leetcode.cn/problems/reverse-string/)," asks you to do something similar—reverse a `char[]` character array. Let's look at the code: ```java void reverseString(char[] s) { // two pointers, one on the left and one on the right, moving towards each other int left = 0, right = s.length - 1; while (left < right) { // swap s[left] and s[right] char temp = s[left]; s[left] = s[right]; s[right] = temp; left++; right--; } } ``` For more advanced problems involving array reversal, check out [Creative Ways to Traverse 2D Arrays](https://labuladong.online/en/algo/practice-in-action/2d-array-traversal-summary/). ### Palindrome Check A palindrome is a string that reads the same forwards and backwards. For example, `aba` and `abba` are both palindromes because they're symmetric—reverse them and you get the same thing. On the other hand, `abac` is not a palindrome. By now, you can probably sense that palindrome problems are closely tied to left-right pointers. If you need to check whether a string is a palindrome, you might write something like this: ```java boolean isPalindrome(String s) { // two pointers, one on the left and one on the right, move towards each other int left = 0, right = s.length() - 1; while (left < right) { if (s.charAt(left) != s.charAt(right)) { return false; } left++; right--; } return true; } ``` Now let's step it up a notch. Given a string, can you use the two-pointer technique to find the longest palindrome within it? That's LeetCode problem 5, "[Longest Palindromic Substring](https://leetcode.cn/problems/longest-palindromic-substring/)": **LeetCode 5. Longest Palindromic Substring** Given a string `s`, return *the longest* *palindromic* *substring* in `s`. Example 1:** ``` **Input:** s = "babad" **Output:** "bab" **Explanation:** "aba" is also a valid answer. ``` Example 2:** ``` **Input:** s = "cbbd" **Output:** "bb" ``` **Constraints:** - `1 <= s.length <= 1000` - `s` consist of only digits and English letters. The function signature looks like this: ```java String longestPalindrome(String s); ``` The tricky part about finding palindromes is that they can have either odd or even length. The key to solving this is **using two pointers that expand outward from the center**. If a palindrome has odd length, it has one center character. If it has even length, you can think of it as having two center characters. So let's first implement a helper function: ```java // Find the longest palindrome centered with s[l] and s[r] in s String palindrome(String s, int l, int r) { // prevent index out of bounds while (l >= 0 && r < s.length() && s.charAt(l) == s.charAt(r)) { // two pointers, expand to both sides l--; r++; } // now s[l+1..r-1] is the longest palindrome string return s.substring(l + 1, r); } ``` This way, if you pass in the same value for `l` and `r`, you're looking for odd-length palindromes. If you pass in adjacent values for `l, r`, you're looking for even-length palindromes. With that in place, here's the general approach for finding the longest palindrome: ```python for 0 <= i < len(s): find the palindrome centered at s[i] find the palindrome centered at s[i] and s[i+1] update the answer ``` Translated into code, this solves the longest palindromic substring problem: ```java class Solution { public String longestPalindrome(String s) { String res = ""; for (int i = 0; i < s.length(); i++) { // the longest palindromic substring centered at s[i] String s1 = palindrome(s, i, i); // the longest palindromic substring centered at s[i] and s[i+1] String s2 = palindrome(s, i, i + 1); // res = longest(res, s1, s2) res = res.length() > s1.length() ? res : s1; res = res.length() > s2.length() ? res : s2; } return res; } String palindrome(String s, int l, int r) { // prevent index out of bounds while (l >= 0 && r < s.length() && s.charAt(l) == s.charAt(r)) { // expand to both sides l--; r++; } // now [l+1, r-1] is the longest palindrome string return s.substring(l + 1, r); } } ``` Click the visualization panel below and repeatedly click on the line while (l >= 0 && r < s.length && s[l] === s[r]) to watch the `l, r` pointers expand outward from the center: You'll notice that the left-right pointers in the longest palindromic substring problem work differently from the previous examples. Before, our left and right pointers moved inward from both ends toward the middle. For palindrome problems, the pointers expand outward from the center. That said, this pattern really only comes up with palindrome-type problems, so I still classify it under left-right pointers. That wraps up all the two-pointer techniques for arrays. For more extensions and variations on these techniques, check out [More Classic Two-Pointer Problems for Arrays](https://labuladong.online/en/algo/problem-set/array-two-pointers/). ================================================ FILE: algorithmic-thinking/union-find.md ================================================ ::: info Prerequisite Knowledge Before reading this article, you should first study: - [Basics and Traversal of N-ary Trees](https://labuladong.online/en/algo/data-structure-basic/n-ary-tree-traverse-basic/) - [Basics and General Implementation of Graphs](https://labuladong.online/en/algo/data-structure-basic/graph-basic/) ::: The Union-Find algorithm is designed for the problem of "dynamic connectivity". It is tested very often, and you must master it. First, let’s talk about what dynamic connectivity in a graph means. ## 1. Dynamic Connectivity Simply put, dynamic connectivity can be seen as connecting nodes in a graph. For example, in the graph below, there are 10 nodes. They are not connected to each other, and are labeled from 0 to 9: ![](../pictures/unionfind/1.jpg) Our Union-Find algorithm mainly needs to implement these two APIs: ```java class UF { // connect p and q public void union(int p, int q); // determine if p and q are connected public boolean connected(int p, int q); // return the number of connected components in the graph public int count(); } ``` The "connected" relation here is an equivalence relation. It has these three properties: 1. Reflexive: node `p` is connected to itself `p`. 2. Symmetric: if node `p` is connected to `q`, then `q` is also connected to `p`. 3. Transitive: if node `p` is connected to `q`, and `q` is connected to `r`, then `p` is connected to `r`. For example, in the graph above, any two different nodes from 0 to 9 are not connected. Calling `connected` on any pair returns false, and there are 10 connected components. If we call `union(0, 1)`, then 0 and 1 become connected, and the number of connected components drops to 9. Then we call `union(1, 2)`. Now 0, 1, 2 are all connected. Calling `connected(0, 2)` will return true, and the number of connected components becomes 8. ![](../pictures/unionfind/2.jpg) Checking this kind of equivalence relation is very useful, like when a compiler checks different references to the same variable, or when social networks compute friend circles, and so on. Now you should have a basic idea of what dynamic connectivity is. The key of the Union-Find algorithm is the efficiency of the `union` and `connected` functions. So, what model should we use to represent the connectivity of this graph? What data structure should we use to implement the code? ## 2. Basic Idea Just now I separated the “model” from the concrete “data structure”, and there is a reason for that. We use a forest (several trees) to represent dynamic connectivity in a graph, and we use an array to implement this forest. How do we use a forest to represent connectivity? We set a pointer in each node to point to its parent. If the node is a root, the pointer points to itself. For example, for the graph with 10 nodes we showed before, at the beginning, no nodes are connected, it looks like this: ![](../pictures/unionfind/3.jpg) ```java class UF { // record the number of connected components private int count; // the parent node of node x is parent[x] private int[] parent; // constructor, n is the total number of nodes in the graph public UF(int n) { // initially, all nodes are not connected this.count = n; // the parent pointer initially points to itself parent = new int[n]; for (int i = 0; i < n; i++) parent[i] = i; } // other functions } ``` **If two nodes are connected, we connect the root of one node to the root of the other node (either one is fine)**: ![](../pictures/unionfind/4.jpg) ```java class UF { // For the sake of brevity, the previous given code is omitted... public void union(int p, int q) { int rootP = find(p); int rootQ = find(q); if (rootP == rootQ) return; // merge two trees into one parent[rootP] = rootQ; // parent[rootQ] = rootP works as well // two components become one count--; } // return the root node of a node x private int find(int x) { // the root node has parent[x] == x while (parent[x] != x) x = parent[x]; return x; } // return the number of connected components currently public int count() { return count; } } ``` **In this way, if nodes `p` and `q` are connected, they must have the same root**: ![](../pictures/unionfind/5.jpg) ```java class UF { // For brevity, the previous given code part is omitted... public boolean connected(int p, int q) { int rootP = find(p); int rootQ = find(q); return rootP == rootQ; } } ``` So the basic Union-Find algorithm is done. It is quite amazing that we can use an array to simulate a forest, and solve this complex problem in such a clever way. What is the time complexity of this algorithm? We see that the main APIs `connected` and `union` are both dominated by the complexity of the `find` function, so their complexity is the same as `find`. The main job of `find` is to move from a node upward to the root of its tree. So its time complexity is the height of the tree. We may be used to thinking that tree height is `logN`, but that is not always true. Height `logN` only appears in balanced binary trees. A general tree can be very unbalanced, almost like a linked list. In the worst case, the height can be `N`. ![](../pictures/unionfind/6.jpg) So for the above solution, the time complexity of `find`, `union`, and `connected` is all $O(N)$. This is not good. Graph problems like social networks usually have huge data sizes. The operations `union` and `connected` are called very frequently, and it is unacceptable if each call needs linear time. **The key question is: how can we avoid unbalanced trees?** We only need a small trick. ## 3. Balance Optimization We need to know in what case the tree becomes unbalanced. The key is the `union` process: ```java class UF { // To save space, the previous code is omitted... public void union(int p, int q) { int rootP = find(p); int rootQ = find(q); if (rootP == rootQ) return; // merge two trees into one parent[rootP] = rootQ; // parent[rootQ] = rootP also works count--; } } ``` At first, we simply connect the tree of `p` under the root of the tree of `q`. This can create a “big head, small body” unbalanced shape, like this: ![](../pictures/unionfind/7.jpg) If this keeps happening, the tree will become more and more unbalanced. **What we really want is to attach the smaller tree under the larger tree. This will keep the tree more balanced and avoid the big-head-small-body shape.** To do this, we use an extra `size` array, which records how many nodes each tree has. We can think of this as the “weight” of the tree: ```java class UF { private int count; private int[] parent; // add an array to record the "weight" of the tree private int[] size; public UF(int n) { this.count = n; parent = new int[n]; // initially each tree has only one node // the weight should be initialized to 1 size = new int[n]; for (int i = 0; i < n; i++) { parent[i] = i; size[i] = 1; } } // other functions } ``` For example, `size[3] = 5` means the tree whose root is node `3` has `5` nodes in total. With this, we can change the `union` method: ```java class UF { private int count; private int[] parent; private int[] size; public UF(int n) { this.count = n; parent = new int[n]; size = new int[n]; for (int i = 0; i < n; i++) { parent[i] = i; size[i] = 1; } } // Connect node p and node q public void union(int p, int q) { int rootP = find(p); int rootQ = find(q); if (rootP == rootQ) return; // Attach the smaller tree under the larger tree for balance if (size[rootP] > size[rootQ]) { parent[rootQ] = rootP; size[rootP] += size[rootQ]; } else { parent[rootP] = rootQ; size[rootQ] += size[rootP]; } count--; } public boolean connected(int p, int q) { int rootP = find(p); int rootQ = find(q); return rootP == rootQ; } private int find(int x) { while (parent[x] != x) x = parent[x]; return x; } public int count() { return count; } // Return the total number of nodes in the connected component where node x is located public int size(int x) { int root = find(x); return size[root]; } } ``` By comparing the weights of the two trees, we can keep the trees relatively balanced. The height of the tree will be about `logN`. This greatly improves performance. Also, because we record the weight of every tree, we can add a `size(x)` method to quickly get the number of nodes in the connected component that contains `x`. Now the time complexity of `find`, `union`, `connected`, and `size` all drops to $O(logN)$. Even if the data size reaches hundreds of millions, the time used is still very small. ## 4. Path Compression This optimization is simple to write, but the idea is very clever. **We do not care about the exact shape of each tree, we only care about the root.** Because no matter how the tree looks, the root of every node in the tree is the same. So can we further reduce the height of each tree, and keep the tree height as a constant? ![](../pictures/unionfind/8.jpg) In this way, the parent of every node is the root of the tree. The `find` function can find the root of a node in O(1) time. As a result, the time complexity of both `connected` and `union` also becomes O(1). To do this, we mainly change the logic of the `find` function. It is very simple, but you may see two different ways to write it. The first way is to add one line in `find`: ```java class UF { // For brevity, the previous code is omitted... private int find(int x) { while (parent[x] != x) { // this line of code performs path compression parent[x] = parent[parent[x]]; x = parent[x]; } return x; } } ``` This operation looks strange. But if you look at the GIF (this tree is extreme for clarity), you will understand: ![](../pictures/unionfind/9.gif) In words, in every while loop, some child nodes move up. So each time you call `find` and walk toward the root, you also shorten the tree height. The second way to do path compression is: ```java class UF { // In order to save space, the previous given code part is omitted... // The second find method with path compression public int find(int x) { if (parent[x] != x) { parent[x] = find(parent[x]); } return parent[x]; } } ``` I used to think this recursive version and the first iterative version did the same thing. But I was careless. A reader pointed out that this recursive version does path compression more efficiently than the first one. This recursive process is a bit hard to understand. You can draw the recursion by hand. I translated what this function does into an iterative form, to help you understand its path compression idea: ```java // This iterative code is for your understanding of what the recursive code does public int find(int x) { // First, find the root node int root = x; while (parent[root] != root) { root = parent[root]; } // Then connect all nodes from x to the root node directly below the root node int old_parent = parent[x]; while (x != root) { parent[x] = root; x = old_parent; old_parent = parent[old_parent]; } return root; } ``` The effect of this path compression is: ![](../pictures/unionfind/10.jpeg) Compared with the first kind of path compression, this method is clearly more thorough. It flattens a whole branch in one go, no surprise here. Even if in some extreme case we get a tall tree, one path compression call can greatly reduce its height. From the view of [amortized analysis](https://labuladong.online/en/algo/essential-technique/complexity-analysis/), the average time complexity of all operations is still O(1). So for efficiency, I suggest you use this version of path compression. **Also, if you use path compression, then the `size` array for balance is not needed.** So the Union-Find algorithm you see in most cases will look like this: ```java class UF { // the number of connected components private int count; // store the parent of each node private int[] parent; // n is the number of nodes in the graph public UF(int n) { this.count = n; parent = new int[n]; for (int i = 0; i < n; i++) { parent[i] = i; } } // connect node p and node q public void union(int p, int q) { int rootP = find(p); int rootQ = find(q); if (rootP == rootQ) return; parent[rootQ] = rootP; // merge two connected components into one count--; } // determine if node p and node q are connected 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]; } // return the number of connected components in the graph public int count() { return count; } } ``` We can analyze the complexity of Union-Find like this: the constructor that builds the data structure needs O(N) time and space. `union` (connect two nodes), `connected` (check if two nodes are connected), and `count` (count the number of connected components) all take O(1) time. But for some problems, even if we use path compression, we may still need the `size` array. It can help us count the number of nodes in each connected component. For example, you need to implement a method `size(x)` that returns the number of nodes in the connected component that contains node `x`. Then you must use the `size` array. Otherwise you would need to traverse the whole tree: ```java class UF { // the number of connected components private int count; // store the parent of each node private int[] parent; // record the "weight" (number of nodes) of each tree private int[] size; // n is the number of nodes in the graph public UF(int n) { this.count = n; parent = new int[n]; size = new int[n]; for (int i = 0; i < n; i++) { parent[i] = i; size[i] = 1; } } // connect node p and node q public void union(int p, int q) { int rootP = find(p); int rootQ = find(q); if (rootP == rootQ) return; // attach the smaller tree to the larger one, more balanced if (size[rootP] > size[rootQ]) { parent[rootQ] = rootP; size[rootP] += size[rootQ]; } else { parent[rootP] = rootQ; size[rootQ] += size[rootP]; } // merge two connected components into one count--; } // determine if node p and node q are connected public boolean connected(int p, int q) { int rootP = find(p); int rootQ = find(q); return rootP == rootQ; } // using path compression public int find(int x) { if (parent[x] != x) { parent[x] = find(parent[x]); } return parent[x]; } // return the number of connected components in the graph public int count() { return count; } // return the total number of nodes in the connected component containing node x public int size(int x) { int root = find(x); return size[root]; } } ``` Note that in our `size(x)` method, we first get the root of `x`. The root node stores the size of the whole component. That is the number of nodes in this connected component. Now you should have mastered the core logic of the Union-Find algorithm. Let’s summarize our optimization steps: 1. Use the `parent` array to record the parent of each node, which is like a pointer to its parent. So the `parent` array actually stores a forest (several multi-way trees). 2. Use the `size` array to record the weight of each tree. The goal is to keep the tree balanced after `union`, so that each API has O(logN) time complexity, and does not degrade to a linked list which would slow down operations. 3. Do path compression in the `find` function to keep the height of any tree as a constant, so each API has O(1) time complexity. After using path compression, you can skip the `size` array balance optimization. ::: tip Tip In most written tests, you are allowed to use your own IDE to code. So you can write this `UF` class in your favorite language in advance, and just copy it when needed. Its code is a bit long, no need to write it from scratch on the spot. ::: ================================================ FILE: data-structures/README.md ================================================ # 数据结构系列 这一章主要是一些特殊的数据结构设计,比如单调栈解决 Next Greater Number,单调队列解决滑动窗口问题;还有常用数据结构的操作,比如链表、树、二叉堆。 欢迎关注我的公众号 labuladong,查看全部文章: ![labuladong二维码](../pictures/qrcode.jpg) ================================================ FILE: data-structures/binary-tree-practice1.md ================================================ ::: info Prerequisites Before reading this article, you should first study: - [Basics of Binary Tree Structure](https://labuladong.online/en/algo/data-structure-basic/binary-tree-basic/) - [Binary Tree DFS/BFS Traversal](https://labuladong.online/en/algo/data-structure-basic/binary-tree-traverse-basic/) - [Binary Tree Essentials (Guiding Principles)](https://labuladong.online/en/algo/essential-technique/binary-tree-summary/) ::: This article continues from [Binary Tree Essentials (Guiding Principles)](https://labuladong.online/en/algo/essential-technique/binary-tree-summary/). Let’s first review the key points summarized in the previous article: ::: note Note There are two main thinking patterns for solving binary tree problems: **1. Can you get the answer by traversing the binary tree once?** If yes, use a `traverse` function with external variables—this is called the "traversal" approach. **2. Can you define a recursive function that derives the answer to the original problem using answers from its subproblems (subtrees)?** If yes, write out this recursive function and make full use of its return value—this is called the "problem decomposition" approach. No matter which approach you use, you should always think: **If you isolate a single tree node, what does it need to do? At what point (preorder/inorder/postorder) should it do it?** You don’t need to worry about other nodes, as the recursive function will perform the same operation on all of them. ::: This article uses a few simple examples to help you practice these key principles, and to understand the differences and connections between the "traversal" and "problem decomposition" approaches. ## Problem 1: Invert Binary Tree Let's start with an easy problem. Look at LeetCode 226: [Invert Binary Tree](https://leetcode.com/problems/invert-binary-tree/). The input is the root node `root` of a binary tree. You need to flip the tree so that it becomes its mirror image. For example, the input tree is: ```yaml BinaryTree root: value: 4 left: value: 2 left: value: 1 right: value: 3 right: value: 7 left: value: 6 right: value: 9 ``` After the algorithm inverts the tree, it should look like this: ```yaml BinaryTree root: value: 4 left: value: 7 left: value: 9 right: value: 6 right: value: 2 left: value: 3 right: value: 1 ``` It is easy to see that if you swap the left and right children of every node in the tree, the whole tree will be inverted. Now, let's recall our general approach for binary tree problems: **1. Can we solve this problem with a traversal approach?** Yes. We can write a `traverse` function to visit every node, and at each node, swap its left and right children. What should we do at each node? Just swap its left and right children. When should we do this? We can do it in pre-order, in-order, or post-order. Any of them work. So, the code can be written like this: ```java class Solution { // main function public TreeNode invertTree(TreeNode root) { // traverse the binary tree, swap the child nodes of each node traverse(root); return root; } // binary tree traversal function void traverse(TreeNode root) { if (root == null) { return; } // *** pre-order position *** // what each node needs to do is to swap its left and right children TreeNode tmp = root.left; root.left = root.right; root.right = tmp; // traversal framework, go traverse the nodes of the left and right subtrees traverse(root.left); traverse(root.right); } } ``` You can move the swap code from pre-order to post-order and it will still work. But moving it to in-order requires some changes. This should be easy to see, so I won't explain more. So, we have solved the problem using traversal. But let's also think about another way. **2. Can we solve this problem with a divide-and-conquer approach?** Let's define what the `invertTree` function does: ```java // Definition: Invert the binary tree rooted at 'root', return the root node of the inverted tree TreeNode invertTree(TreeNode root); ``` Now, for a given node `x`, what can we do with `invertTree(x)`? We can call `invertTree(x.left)` to invert the left subtree, and `invertTree(x.right)` to invert the right subtree. Then, we swap the left and right children of `x`. This completes the inversion of the subtree rooted at `x`. Here is the code: ```java class Solution { // Definition: Invert the binary tree rooted at 'root', return the root node of the inverted tree public TreeNode invertTree(TreeNode root) { if (root == null) { return null; } // Utilize the function definition to invert left and right subtrees first TreeNode left = invertTree(root.left); TreeNode right = invertTree(root.right); // Then swap the left and right child nodes root.left = right; root.right = left; // Consistent with the definition logic: the binary tree rooted at 'root' has been inverted, return root return root; } } ``` In this divide-and-conquer way, the key is to give the recursive function a clear definition, and then write the code based on this definition. If your logic is self-consistent, your algorithm should be correct. That's all for this problem. Both traversal and divide-and-conquer work. Let's look at the next problem. ## Problem 2: Populating Next Right Pointers in Each Node This is LeetCode problem 116: [Populating Next Right Pointers in Each Node](https://leetcode.com/problems/populating-next-right-pointers-in-each-node/). Let's look at the problem: **LeetCode 116. Populating Next Right Pointers in Each Node** You are given a **perfect binary tree** where all leaves are on the same level, and every parent has two children. The binary tree has the following definition: ``` struct Node { int val; Node *left; Node *right; Node *next; } ``` Populate each next pointer to point to its next right node. If there is no next right node, the next pointer should be set to `NULL`. Initially, all next pointers are set to `NULL`. Example 1:** ![](https://assets.leetcode.com/uploads/2019/02/14/116_sample.png) ``` **Input:** root = [1,2,3,4,5,6,7] **Output:** [1,#,2,3,#,4,5,6,7,#] **Explanation: **Given the above perfect binary tree (Figure A), your function should populate each next pointer to point to its next right node, just like in Figure B. The serialized output is in level order as connected by the next pointers, with '#' signifying the end of each level. ``` Example 2:** ``` **Input:** root = [] **Output:** [] ``` **Constraints:** - The number of nodes in the tree is in the range `[0, 2^(12) - 1]`. - `-1000 <= Node.val <= 1000` **Follow-up:** - You may only use constant extra space. - The recursive approach is fine. You may assume implicit stack space does not count as extra space for this problem. ```java // Function signature Node connect(Node root); ``` The goal is to connect all nodes at the same level in a binary tree using the `next` pointer: ![](../pictures/binary-tree-i/1.png) The problem also says the input is a "perfect binary tree," which means the tree is shaped like an equilateral triangle. Except for the rightmost node at each level, every node has a neighbor to its right, and the `next` pointer should point to that neighbor. The rightmost node's `next` pointer should point to `null`. How do we solve this problem? Let's recall the general ideas for solving binary tree problems: **1. Can we solve this problem using a "traversal" approach?** Of course, we can. Each node just needs to set its `next` pointer to the node on its right. You might want to write code similar to the previous problem, like this: ```java // Binary tree traversal function void traverse(Node root) { if (root == null || root.left == null) { return; } // Point the next pointer of the left child to the right child root.left.next = root.right; traverse(root.left); traverse(root.right); } ``` But this code has a big problem. It only connects two nodes with the same parent. Look at this picture again: ![](../pictures/binary-tree-i/1.png) Node 5 and node 6 don't have the same parent. According to this code, they won't be connected, which is not what we want. So where is the problem? **The traditional `traverse` function visits every node in the binary tree, but now we want to visit the "gap" between two neighboring nodes.** So, let's try to see the tree differently. Think of each box in the picture as a node: ![](../pictures/binary-tree-i/3.png) **Now, the binary tree becomes like a ternary tree. Each node in the ternary tree represents two neighboring nodes from the binary tree.** We just need to write a `traverse` function for this ternary tree. Each "ternary tree node" connects its two binary tree nodes: ```java class Solution { // Main function public Node connect(Node root) { if (root == null) return null; // Traverse the "ternary tree" and connect adjacent nodes traverse(root.left, root.right); return root; } // Ternary tree traversal framework void traverse(Node node1, Node node2) { if (node1 == null || node2 == null) { return; } // *** Pre-order position *** // Connect the two input nodes node1.next = node2; // Connect two child nodes with the same parent traverse(node1.left, node1.right); traverse(node2.left, node2.right); // Connect two child nodes across parent nodes traverse(node1.right, node2.left); } } ``` With this, the `traverse` function will visit all the "gaps" between neighboring binary tree nodes and connect them correctly. This solves the problem perfectly. **2. Can we solve this problem by "breaking it into subproblems"?** Hmm, there doesn't seem to be a good way to do this. So, we can't use the "decompose subproblems" idea for this problem. ## Problem 3: Flatten Binary Tree to Linked List This is LeetCode problem 114 "[Flatten Binary Tree to Linked List](https://leetcode.com/problems/flatten-binary-tree-to-linked-list/)". Let's look at the problem: **LeetCode 114. Flatten Binary Tree to Linked List** Given the `root` of a binary tree, flatten the tree into a "linked list": - The "linked list" should use the same `TreeNode` class where the `right` child pointer points to the next node in the list and the `left` child pointer is always `null`. - The "linked list" should be in the same order as a [**pre-order**** traversal**](https://en.wikipedia.org/wiki/Tree_traversal#Pre-order,_NLR) of the binary tree. Example 1:** ![](https://assets.leetcode.com/uploads/2021/01/14/flaten.jpg) ``` **Input:** root = [1,2,5,3,4,null,6] **Output:** [1,null,2,null,3,null,4,null,5,null,6] ``` Example 2:** ``` **Input:** root = [] **Output:** [] ``` Example 3:** ``` **Input:** root = [0] **Output:** [0] ``` **Constraints:** - The number of nodes in the tree is in the range `[0, 2000]`. - `-100 <= Node.val <= 100` **Follow up:** Can you flatten the tree in-place (with `O(1)` extra space)? ```java // The function signature is as follows void flatten(TreeNode root); ``` **1. Can we solve this using the "traversal" approach?** At first glance, it seems possible: perform a preorder traversal of the entire tree, building a "linked list" as you traverse: ```java // Virtual head node, dummy.right is the result TreeNode dummy = new TreeNode(-1); // Pointer used to build the linked list TreeNode p = dummy; void traverse(TreeNode root) { if (root == null) { return; } // Pre-order position p.right = new TreeNode(root.val); p = p.right; traverse(root.left); traverse(root.right); } ``` But notice the signature of the `flatten` function—its return type is `void`. This means the problem wants us to flatten the binary tree into a linked list in place. This makes it impossible to solve the problem with simple binary tree traversal. **2. Can we solve this using the "decompose the problem" approach?** Let's try to define the `flatten` function: ```java // Definition: Input node root, then the binary tree rooted at root will be flattened into a linked list void flatten(TreeNode root); ``` With this function definition, how do we flatten a tree into a linked list as required? For a node `x`, you can follow this process: 1. First use `flatten(x.left)` and `flatten(x.right)` to flatten `x`'s left and right subtrees. 2. Set the entire left subtree as the right subtree, then attach the original right subtree to the end of the current right subtree. ![](../pictures/binary-tree-i/2.jpeg) This way, the entire binary tree rooted at `x` is flattened, which is exactly what the `flatten(x)` definition requires. Here's the code implementation: ```java class Solution { // Definition: flatten the tree with root as the root node into a linked list public void flatten(TreeNode root) { // base case if (root == null) return; // Utilize the definition to flatten the left and right subtrees flatten(root.left); flatten(root.right); // *** Post-order traversal position *** // 1. Left and right subtrees have been flattened into linked lists TreeNode left = root.left; TreeNode right = root.right; // 2. Make the left subtree the new right subtree root.left = null; root.right = left; // 3. Attach the original right subtree to the end of the current right subtree TreeNode p = root; while (p.right != null) { p = p.right; } p.right = right; } } ``` See, this is the magic of recursion. Can you explain exactly how the `flatten` function flattens the left and right subtrees? It's not easy to articulate, but as long as you know the definition of `flatten` and use that definition to let each node do what it's supposed to do, the `flatten` function will work according to its definition. With that, this problem is solved. The recursive approach in our previous article [Reverse Nodes in k-Group](https://labuladong.online/en/algo/data-structure/reverse-linked-list-recursion/) shares some similarities with this problem. Finally, let's revisit the binary tree problem-solving framework to bring things full circle. There are two approaches for solving binary tree problems: **1. Can you get the answer by traversing the binary tree once?** If yes, use a `traverse` function with external variables. This is the "traversal" approach. **2. Can you define a recursive function that derives the answer to the original problem from the answers of subproblems (subtrees)?** If yes, write out the definition of this recursive function and fully utilize its return value. This is the "decompose the problem" approach. Regardless of which approach you use, you need to think about: **If you isolate a single binary tree node, what does it need to do? When (pre/in/post-order position) does it need to do it?** Don't worry about the other nodes—the recursive function will execute the same operation on all nodes. I hope you can internalize this and apply it to all binary tree problems. That's all for this article. For more classic binary tree exercises and recursion training, see [Recursion Practice](https://labuladong.online/en/algo/intro/binary-tree-practice/) in the binary tree chapter. ================================================ FILE: data-structures/binary-tree-practice2.md ================================================ ::: info Prerequisite Knowledge Before reading this article, you should first learn: - [Basics of Binary Tree Structure](https://labuladong.online/en/algo/data-structure-basic/binary-tree-basic/) - [DFS/BFS Traversal of Binary Trees](https://labuladong.online/en/algo/data-structure-basic/binary-tree-traverse-basic/) - [Binary Tree Key Ideas (Overview)](https://labuladong.online/en/algo/essential-technique/binary-tree-summary/) ::: This article follows [Binary Tree Key Ideas (Overview)](https://labuladong.online/en/algo/essential-technique/binary-tree-summary/). Let's review the main points for solving binary tree problems: ::: note Note There are two main ways to solve binary tree problems: **1. Can you get the answer by traversing the binary tree once?** If yes, use a `traverse` function with external variables. This is called the "traversal" approach. **2. Can you define a recursive function that uses the answers of subproblems (subtrees) to get the answer of the original problem?** If yes, write this recursive function and use its return value. This is the "divide and conquer" approach. No matter which way you use, you should think about: **If you take out a single tree node, what should it do? When should it do it (preorder, inorder, postorder)?** You don't need to worry about other nodes. The recursive function will handle all nodes for you. ::: The first article [Binary Tree Key Ideas (Thinking)](https://labuladong.online/en/algo/data-structure/binary-tree-part1/) talked about the "traversal" and "divide and conquer" approaches. This article explains construction problems for binary trees. **Most binary tree construction problems use the "divide and conquer" idea: build the whole tree = root node + build left subtree + build right subtree.** Let's look at some problems. ## Construct Maximum Binary Tree Let's start with an easy one. This is LeetCode 654 "[Maximum Binary Tree](https://leetcode.com/problems/maximum-binary-tree/)". Here is the problem: **LeetCode 654. Maximum Binary Tree** You are given an integer array `nums` with no duplicates. A **maximum binary tree** can be built recursively from `nums` using the following algorithm: - Create a root node whose value is the maximum value in `nums`. - Recursively build the left subtree on the **subarray prefix** to the **left** of the maximum value. - Recursively build the right subtree on the **subarray suffix** to the **right** of the maximum value. Return *the **maximum binary tree** built from *`nums`. Example 1:** ![](https://assets.leetcode.com/uploads/2020/12/24/tree1.jpg) ``` **Input:** nums = [3,2,1,6,0,5] **Output:** [6,3,5,null,2,0,null,null,1] **Explanation:** The recursive calls are as follow: - The largest value in [3,2,1,6,0,5] is 6. Left prefix is [3,2,1] and right suffix is [0,5]. - The largest value in [3,2,1] is 3. Left prefix is [] and right suffix is [2,1]. - Empty array, so no child. - The largest value in [2,1] is 2. Left prefix is [] and right suffix is [1]. - Empty array, so no child. - Only one element, so child is a node with value 1. - The largest value in [0,5] is 5. Left prefix is [0] and right suffix is []. - Only one element, so child is a node with value 0. - Empty array, so no child. ``` Example 2:** ![](https://assets.leetcode.com/uploads/2020/12/24/tree2.jpg) ``` **Input:** nums = [3,2,1] **Output:** [3,null,2,null,1] ``` **Constraints:** - `1 <= nums.length <= 1000` - `0 <= nums[i] <= 1000` - All integers in `nums` are **unique**. ```java // function signature as follows TreeNode constructMaximumBinaryTree(int[] nums); ``` Each binary tree node can be seen as the root of a subtree. For the root, we first need to build the root itself, then build its left and right subtrees. So, we scan the array to find the maximum value `maxVal`. This becomes the root node `root`. Then, we recursively build the left subtree with the subarray to the left of `maxVal`, and the right subtree with the subarray to the right. For example, if the input is `[3,2,1,6,0,5]`, the root does this: ```java TreeNode constructMaximumBinaryTree([3,2,1,6,0,5]) { // find the maximum value in the array TreeNode root = new TreeNode(6); // recursively construct the left and right subtrees root.left = constructMaximumBinaryTree([3,2,1]); root.right = constructMaximumBinaryTree([0,5]); return root; } // the maximum value in the current nums is the root node, and then recursively call the left and right arrays to construct the left and right subtrees // in more detail, the pseudocode is as follows TreeNode constructMaximumBinaryTree(int[] nums) { if (nums is empty) return null; // find the maximum value in the array int maxVal = Integer.MIN_VALUE; int index = 0; for (int i = 0; i < nums.length; i++) { if (nums[i] > maxVal) { maxVal = nums[i]; index = i; } } TreeNode root = new TreeNode(maxVal); // recursively construct the left and right subtrees root.left = constructMaximumBinaryTree(nums[0..index-1]); root.right = constructMaximumBinaryTree(nums[index+1..nums.length-1]); return root; } ``` **The maximum value in `nums` is the root. Then, recursively build the left and right subtrees using the left and right parts of the array.** With this idea, let's write a helper function `build` to control the indices: ```java class Solution { public TreeNode constructMaximumBinaryTree(int[] nums) { return build(nums, 0, nums.length - 1); } // Definition: Construct a tree from nums[lo..hi] that meets the conditions, return the root node TreeNode build(int[] nums, int lo, int hi) { // base case if (lo > hi) { return null; } // Find the maximum value in the array and its corresponding index int index = -1, maxVal = Integer.MIN_VALUE; for (int i = lo; i <= hi; i++) { if (maxVal < nums[i]) { index = i; maxVal = nums[i]; } } // First construct the root node TreeNode root = new TreeNode(maxVal); // Recursively construct the left and right subtrees root.left = build(nums, lo, index - 1); root.right = build(nums, index + 1, hi); return root; } } ``` That's it for this problem. It's pretty simple. Now let's look at two harder problems. ## Build a Binary Tree from Preorder and Inorder Traversal LeetCode problem 105 “[Construct Binary Tree from Preorder and Inorder Traversal](https://leetcode.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal/)” is a classic question often asked in interviews and written tests: **LeetCode 105. Construct Binary Tree from Preorder and Inorder Traversal** Given two integer arrays `preorder` and `inorder` where `preorder` is the preorder traversal of a binary tree and `inorder` is the inorder traversal of the same tree, construct and return *the binary tree*. Example 1:** ![](https://assets.leetcode.com/uploads/2021/02/19/tree.jpg) ``` **Input:** preorder = [3,9,20,15,7], inorder = [9,3,15,20,7] **Output:** [3,9,20,null,null,15,7] ``` Example 2:** ``` **Input:** preorder = [-1], inorder = [-1] **Output:** [-1] ``` **Constraints:** - `1 <= preorder.length <= 3000` - `inorder.length == preorder.length` - `-3000 <= preorder[i], inorder[i] <= 3000` - `preorder` and `inorder` consist of **unique** values. - Each value of `inorder` also appears in `preorder`. - `preorder` is **guaranteed** to be the preorder traversal of the tree. - `inorder` is **guaranteed** to be the inorder traversal of the tree. ```java // The function signature is as follows TreeNode buildTree(int[] preorder, int[] inorder); ``` Let’s get straight to the main idea. First, think about what the root node should do. **Like the previous question, we need to find the value of the root node, build the root, and then use recursion to build the left and right subtrees.** First, let’s review the characteristics of preorder and inorder traversal results. ```java void traverse(TreeNode root) { // preorder traversal preorder.add(root.val); traverse(root.left); traverse(root.right); } void traverse(TreeNode root) { traverse(root.left); // inorder traversal inorder.add(root.val); traverse(root.right); } ``` In the article [Binary Tree Frameworks](https://labuladong.online/en/algo/data-structure/flatten-nested-list-iterator/), we discussed how different traversal orders affect the arrangement of elements in the `preorder` and `inorder` arrays: ![](../pictures/binary-tree-ii/1.jpeg) It’s easy to find the root node; the first value in preorder, `preorder[0]`, is the root. The key is how to use the root value to split the `preorder` and `inorder` arrays and build the left and right subtrees. In other words, what should be put in the `?` parts of the following code: ```java TreeNode buildTree(int[] preorder, int[] inorder) { // According to the function definition, construct the binary tree using preorder and inorder arrays return build(preorder, 0, preorder.length - 1, inorder, 0, inorder.length - 1); } // Definition of the build function: // If the preorder traversal array is preorder[preStart..preEnd], // and the inorder traversal array is inorder[inStart..inEnd], // construct the binary tree and return its root node TreeNode build(int[] preorder, int preStart, int preEnd, int[] inorder, int inStart, int inEnd) { // The value of the root node corresponds to the first element of the preorder array int rootVal = preorder[preStart]; // The index of rootVal in the inorder array int index = 0; for (int i = inStart; i <= inEnd; i++) { if (inorder[i] == rootVal) { index = i; break; } } TreeNode root = new TreeNode(rootVal); // Recursively construct the left and right subtrees root.left = build(preorder, ?, ?, inorder, ?, ?); root.right = build(preorder, ?, ?, inorder, ?, ?); return root; } ``` In the code, the variables `rootVal` and `index` correspond to this situation: ![](../pictures/binary-tree-ii/2.jpeg) Some readers may notice that using a for loop to find `index` is not very efficient. It can be improved. Since the values in the tree are unique, we can use a HashMap to map values to their indexes. Then we can quickly find the `index` for `rootVal`: ```java // store the value-to-index mapping in inorder HashMap valToIndex = new HashMap<>(); public TreeNode buildTree(int[] preorder, int[] inorder) { for (int i = 0; i < inorder.length; i++) { valToIndex.put(inorder[i], i); } return build(preorder, 0, preorder.length - 1, inorder, 0, inorder.length - 1); } TreeNode build(int[] preorder, int preStart, int preEnd, int[] inorder, int inStart, int inEnd) { int rootVal = preorder[preStart]; // avoid using a for loop to find rootVal int index = valToIndex.get(rootVal); // ... } ``` Now, let’s look at the diagram and fill in the blanks. What should be filled in for these question marks: ```java root.left = build(preorder, ?, ?, inorder, ?, ?); root.right = build(preorder, ?, ?, inorder, ?, ?); ``` The start and end indexes for the left and right subtrees in the `inorder` array are easy to figure out: ![](../pictures/binary-tree-ii/3.jpeg) ```java root.left = build(preorder, ?, ?, inorder, inStart, index - 1); root.right = build(preorder, ?, ?, inorder, index + 1, inEnd); ``` What about the `preorder` array? How do we find the start and end indexes for the left and right subtrees? We can figure this out by counting the number of nodes in the left subtree. Let’s call this number `leftSize`. Here is how the indexes look in the `preorder` array: ![](../pictures/binary-tree-ii/4.jpeg) Looking at the diagram, we can write the indexes for the `preorder` array: ```java int leftSize = index - inStart; root.left = build(preorder, preStart + 1, preStart + leftSize, inorder, inStart, index - 1); root.right = build(preorder, preStart + leftSize + 1, preEnd, inorder, index + 1, inEnd); ``` Now, the whole algorithm is complete. We just need to add the base case to finish the solution: ```java class Solution { // store the mapping of values to indices in inorder HashMap valToIndex = new HashMap<>(); public TreeNode buildTree(int[] preorder, int[] inorder) { for (int i = 0; i < inorder.length; i++) { valToIndex.put(inorder[i], i); } return build(preorder, 0, preorder.length - 1, inorder, 0, inorder.length - 1); } // Definition: the preorder array is preorder[preStart..preEnd] // the inorder array is inorder[inStart..inEnd] // construct this binary tree and return the root node of the binary tree TreeNode build(int[] preorder, int preStart, int preEnd, int[] inorder, int inStart, int inEnd) { if (preStart > preEnd) { return null; } // the value of the root node corresponds to the first element of the preorder array int rootVal = preorder[preStart]; // the index of rootVal in the inorder array int index = valToIndex.get(rootVal); int leftSize = index - inStart; // first, construct the current root node TreeNode root = new TreeNode(rootVal); /** ![](../pictures/binary-tree-ii/4.jpeg) */ // recursively construct the left and right subtrees root.left = build(preorder, preStart + 1, preStart + leftSize, inorder, inStart, index - 1); root.right = build(preorder, preStart + leftSize + 1, preEnd, inorder, index + 1, inEnd); return root; } } ``` The main function only needs to call the `buildTree` function. The solution may look long and the function has many parameters, but all these parameters do is control the range in the arrays. Drawing a diagram makes everything clear. ## Build a Binary Tree from Postorder and Inorder Traversal This problem is similar to the previous one. This time, we use the **postorder** and **inorder** traversal arrays to build a binary tree. This is LeetCode Problem 106: [Construct Binary Tree from Inorder and Postorder Traversal](https://leetcode.com/problems/construct-binary-tree-from-inorder-and-postorder-traversal/): **LeetCode 106. Construct Binary Tree from Inorder and Postorder Traversal** Given two integer arrays `inorder` and `postorder` where `inorder` is the inorder traversal of a binary tree and `postorder` is the postorder traversal of the same tree, construct and return *the binary tree*. Example 1:** ![](https://assets.leetcode.com/uploads/2021/02/19/tree.jpg) ``` **Input:** inorder = [9,3,15,20,7], postorder = [9,15,7,20,3] **Output:** [3,9,20,null,null,15,7] ``` Example 2:** ``` **Input:** inorder = [-1], postorder = [-1] **Output:** [-1] ``` **Constraints:** - `1 <= inorder.length <= 3000` - `postorder.length == inorder.length` - `-3000 <= inorder[i], postorder[i] <= 3000` - `inorder` and `postorder` consist of **unique** values. - Each value of `postorder` also appears in `inorder`. - `inorder` is **guaranteed** to be the inorder traversal of the tree. - `postorder` is **guaranteed** to be the postorder traversal of the tree. ```java // The function signature is as follows TreeNode buildTree(int[] inorder, int[] postorder); ``` Let's look at the features of postorder and inorder traversals: ```java void traverse(TreeNode root) { traverse(root.left); traverse(root.right); // postorder traversal postorder.add(root.val); } void traverse(TreeNode root) { traverse(root.left); // inorder traversal inorder.add(root.val); traverse(root.right); } ``` Because of the difference in traversal orders, the elements in the `postorder` and `inorder` arrays have the following layout: ![](../pictures/binary-tree-ii/5.jpeg) The key difference from the previous problem is that postorder is the opposite of preorder, so the root node is the last element in the `postorder` array. The overall algorithm structure is very similar to the previous problem. We still write a helper function called `build`: ```java class Solution { // Store the mapping from values to indices in the inorder array HashMap valToIndex = new HashMap<>(); public TreeNode buildTree(int[] inorder, int[] postorder) { for (int i = 0; i < inorder.length; i++) { valToIndex.put(inorder[i], i); } return build(inorder, 0, inorder.length - 1, postorder, 0, postorder.length - 1); } // Definition of the build function: // The postorder traversal array is postorder[postStart..postEnd], // The inorder traversal array is inorder[inStart..inEnd], // Construct the binary tree and return its root node TreeNode build(int[] inorder, int inStart, int inEnd, int[] postorder, int postStart, int postEnd) { // The value of the root node is the last element of the postorder traversal array int rootVal = postorder[postEnd]; // The index of rootVal in the inorder traversal array int index = valToIndex.get(rootVal); TreeNode root = new TreeNode(rootVal); // Recursively construct the left and right subtrees root.left = build(inorder, ?, ?, postorder, ?, ?); root.right = build(inorder, ?, ?, postorder, ?, ?); return root; } } ``` Now, the status of `postorder` and `inorder` is as follows: ![](../pictures/binary-tree-ii/6.jpeg) We can fill in the missing indices as shown above: ```java int leftSize = index - inStart; root.left = build(inorder, inStart, index - 1, postorder, postStart, postStart + leftSize - 1); root.right = build(inorder, index + 1, inEnd, postorder, postStart + leftSize, postEnd - 1); ``` Here is the complete solution code: ```java class Solution { // store the mapping from value to index in inorder HashMap valToIndex = new HashMap<>(); public TreeNode buildTree(int[] inorder, int[] postorder) { for (int i = 0; i < inorder.length; i++) { valToIndex.put(inorder[i], i); } return build(inorder, 0, inorder.length - 1, postorder, 0, postorder.length - 1); } // Definition: the inorder array is inorder[inStart..inEnd], // the postorder array is postorder[postStart..postEnd], // the build function constructs this binary tree and returns the root node of the binary tree TreeNode build(int[] inorder, int inStart, int inEnd, int[] postorder, int postStart, int postEnd) { if (inStart > inEnd) { return null; } // the value of the root node is the last element in the postorder array int rootVal = postorder[postEnd]; // the index of rootVal in the inorder array int index = valToIndex.get(rootVal); // the number of nodes in the left subtree int leftSize = index - inStart; TreeNode root = new TreeNode(rootVal); /** ![](../pictures/binary-tree-ii/6.jpeg) */ // recursively construct the left and right subtrees root.left = build(inorder, inStart, index - 1, postorder, postStart, postStart + leftSize - 1); root.right = build(inorder, index + 1, inEnd, postorder, postStart + leftSize, postEnd - 1); return root; } } ``` With the foundation from the previous problem, this one is easy to solve. The only difference is that `rootVal` is now the last element, and we need to adjust the function parameters a bit. As long as you understand the features of a binary tree, this problem is not hard to write. ## Constructing a Binary Tree from Preorder and Postorder Traversal This is LeetCode Problem 889: [Construct Binary Tree from Preorder and Postorder Traversal](https://leetcode.com/problems/construct-binary-tree-from-preorder-and-postorder-traversal/). You are given the preorder and postorder traversal of a binary tree. Your task is to restore the structure of the binary tree. The function signature is: ```java TreeNode constructFromPrePost(int[] preorder, int[] postorder); ``` This problem is different from the previous two: **Given preorder and inorder, or postorder and inorder traversal, you can restore the unique original binary tree. But with preorder and postorder, you cannot restore a unique tree.** The problem also says, if there are multiple possible answers, you can return any of them. Why is that? As we said before, the usual way to build a binary tree is simple: find the root node, then find and build the left and right subtrees recursively. In the previous two problems, you could use preorder or postorder to find the root, then use inorder to know the left and right subtrees (the problem says all values are unique). In this problem, you can find the root, but you cannot know exactly which nodes belong to the left or right subtrees. For example, given this input: ``` preorder = [1,2,3], postorder = [3,2,1] ``` Both of the following trees fit the condition, but their structures are different: ![](../pictures/binary-tree-ii/7.png) However, the logic to restore the tree from preorder and postorder is not much different from the previous problems. You still use the indexes of left and right subtrees to build the tree: **1. First, take the first element of preorder or the last element of postorder as the root.** **2. Then, take the second element of preorder as the root of the left subtree.** **3. In postorder, find the index of the left subtree's root, so you know the boundary for the left subtree, and then for the right subtree. Build the left and right subtrees recursively.** ![](../pictures/binary-tree-ii/8.jpeg) For details, see the code. ```java class Solution { // store the mapping of values to indices in postorder HashMap valToIndex = new HashMap<>(); public TreeNode constructFromPrePost(int[] preorder, int[] postorder) { for (int i = 0; i < postorder.length; i++) { valToIndex.put(postorder[i], i); } return build(preorder, 0, preorder.length - 1, postorder, 0, postorder.length - 1); } // definition: construct a binary tree based on preorder[prestart..preend] and postorder[poststart..postend] // and return the root node. TreeNode build(int[] preorder, int preStart, int preEnd, int[] postorder, int postStart, int postEnd) { if (preStart > preEnd) { return null; } if (preStart == preEnd) { return new TreeNode(preorder[preStart]); } // the value of the root node corresponds to the first element in the preorder array int rootVal = preorder[preStart]; // the value of root.left is the second element in the preorder array // the key to constructing the binary tree from preorder and postorder traversals is determining the left subtree's root node // and the element ranges of the left and right subtrees in preorder and postorder int leftRootVal = preorder[preStart + 1]; // the index of leftrootval in the postorder array int index = valToIndex.get(leftRootVal); // the number of elements in the left subtree int leftSize = index - postStart + 1; // first, construct the current root node TreeNode root = new TreeNode(rootVal); /** ![](../pictures/binary-tree-ii/8.jpeg) */ // recursively construct the left and right subtrees // derive the index boundaries of the left and right subtrees based on the left subtree's root node index and the number of elements root.left = build(preorder, preStart + 1, preStart + leftSize, postorder, postStart, index); root.right = build(preorder, preStart + leftSize + 1, preEnd, postorder, index + 1, postEnd - 1); return root; } } ``` The code is very similar to the previous two problems. While reading the code, you can think about why the answer is not unique with preorder and postorder. The key part is this line: ```java int leftRootVal = preorder[preStart + 1]; ``` We assume the second element in preorder is the root of the left subtree. But actually, the left subtree could be empty, then this element is the root of the right subtree. Since we can't know for sure, the answer is not unique. Now, we have solved the problem of restoring a binary tree from preorder and postorder traversal. To sum up, **constructing a binary tree usually uses the "divide the problem" idea: build the whole tree = root node + build left subtree + build right subtree**. First find the root, then use the root's value to find the left and right subtree elements, then build them recursively. Do you understand the trick now? ================================================ FILE: data-structures/binary-tree-summary.md ================================================ ::: info Prerequisites Before reading this article, you should first learn: - [Binary Tree Structure Basics](https://labuladong.online/en/algo/data-structure-basic/binary-tree-basic/) - [Binary Tree DFS/BFS Traversal](https://labuladong.online/en/algo/data-structure-basic/binary-tree-traverse-basic/) ::: ::: important How to Read This Article This article abstracts and summarizes many algorithms, so it contains numerous links to other articles. **If you're reading this for the first time and encounter unfamiliar algorithms or concepts, skip them. Just get a general impression of the theories summarized here**. As you learn the algorithm techniques in later chapters, you'll gradually understand the essence of this article. When you revisit it later, you'll gain deeper insights. ::: All articles on this site follow the framework proposed in [Framework Thinking for Learning Data Structures and Algorithms](https://labuladong.online/en/algo/essential-technique/algorithm-summary/), which emphasizes the importance of binary tree problems. That's why this article is placed in the essential reading section of the first chapter. After years of solving problems, I've distilled the core principles of binary tree algorithms here. The terminology might not be textbook-perfect, and no textbook would include these experience-based insights, but no binary tree problem on any coding platform can escape the framework outlined in this article. If you find a problem that doesn't fit this framework, please let me know in the comments. Let me summarize upfront. Binary tree problem-solving falls into two mental models: **1. Can you get the answer by traversing the binary tree once?** If yes, use a `traverse` function with external variables. This is the "traversal" mindset. **2. Can you define a recursive function that derives the answer to the original problem from the answers of subproblems (subtrees)?** If yes, write out this recursive function's definition and fully utilize its return value. This is the "decompose the problem" mindset. Regardless of which mindset you use, you need to think: **If you isolate a single binary tree node, what does it need to do? When should it do this (preorder/inorder/postorder position)?** Don't worry about the other nodes—the recursive function will execute the same operation on all nodes for you. I'll use problems as examples in this article, but they're all very simple ones. Don't worry about not understanding them. I'll help you extract the common patterns from the simplest problems, elevate the thinking embedded in binary trees, and apply it to [Dynamic Programming](https://labuladong.online/en/algo/essential-technique/dynamic-programming-framework/), [Backtracking](https://labuladong.online/en/algo/essential-technique/backtrack-framework/), [Divide and Conquer](https://labuladong.online/en/algo/essential-technique/divide-and-conquer/), and [Graph Algorithms](https://labuladong.online/en/algo/data-structure-basic/graph-basic/). This is why I keep emphasizing framework thinking. I hope after you learn these advanced algorithms, you'll come back to reread this article—you'll understand it much more deeply. First, let me once again stress the importance of binary trees as a data structure and its related algorithms. ## The Importance of Binary Trees For example, consider two classic sorting algorithms: [Quick Sort](https://labuladong.online/en/algo/practice-in-action/quick-sort/) and [Merge Sort](https://labuladong.online/en/algo/practice-in-action/merge-sort/). What's your understanding of them? **If you tell me that quick sort is essentially a preorder traversal of a binary tree, and merge sort is essentially a postorder traversal, then I know you're an algorithm expert**. Why can quick sort and merge sort be related to binary trees? Let's briefly analyze their algorithmic ideas and code frameworks: The logic of quick sort is: to sort `nums[lo..hi]`, first find a partition point `p`. By swapping elements, make all elements in `nums[lo..p-1]` less than or equal to `nums[p]`, and all elements in `nums[p+1..hi]` greater than `nums[p]`. Then recursively find new partition points in `nums[lo..p-1]` and `nums[p+1..hi]`. Eventually, the entire array is sorted. The code framework for quick sort: ```java void sort(int[] nums, int lo, int hi) { if (lo >= hi) { return; } // ****** pre-order position ****** // partition the nums[lo..hi], put nums[p] in the right position // such that nums[lo..p-1] <= nums[p] < nums[p+1..hi] int p = partition(nums, lo, hi); // recursively partition the left and right subarrays sort(nums, lo, p - 1); sort(nums, p + 1, hi); } ``` First construct the partition point, then go to the left and right subarrays to construct partition points. Isn't this just a preorder traversal of a binary tree? Now let's talk about merge sort's logic. To sort `nums[lo..hi]`, first sort `nums[lo..mid]`, then sort `nums[mid+1..hi]`, and finally merge these two sorted subarrays. The entire array is then sorted. The code framework for merge sort: ```java // definition: sort nums[lo..hi] void sort(int[] nums, int lo, int hi) { if (lo == hi) { return; } int mid = (lo + hi) / 2; // use the definition to sort nums[lo..mid] sort(nums, lo, mid); // use the definition to sort nums[mid+1..hi] sort(nums, mid + 1, hi); // ****** Post-order position ****** // at this point, the two subarrays are already sorted // merge the two sorted arrays to make nums[lo..hi] sorted merge(nums, lo, mid, hi); } ``` First sort the left and right subarrays, then merge them (similar to merging sorted linked lists). Isn't this the postorder traversal framework of a binary tree? Also, this is the legendary divide and conquer algorithm—nothing more than this. If you can see through these sorting algorithms at a glance, do you still need to memorize them? No. You can derive them effortlessly from the binary tree traversal framework. The point of all this is: binary tree algorithmic thinking has wide applications. You could even say that anything involving recursion can be abstracted as a binary tree problem. Next, let's start from preorder, inorder, and postorder traversal of binary trees, so you can deeply understand the elegance of this data structure. ## Understand Preorder, Inorder, and Postorder Traversal Let me give you a few questions. Think about them for 30 seconds: 1. What do you think preorder, inorder, and postorder traversals of a binary tree are? Are they just three lists in different orders? 2. Can you explain what is special about postorder traversal? 3. Why does an n-ary tree (a tree with more than two children per node) not have an inorder traversal? If you cannot answer these, it means you only know the textbook understanding of these traversals. Don't worry, I will use simple comparisons to explain how I see these traversals. First, let's review the binary tree recursive traversal framework mentioned in [Binary Tree DFS/BFS Traversal](https://labuladong.online/en/algo/data-structure-basic/binary-tree-traverse-basic/): ```java // Binary tree traversal framework void traverse(TreeNode root) { if (root == null) { return; } // pre-order position traverse(root.left); // in-order position traverse(root.right); // post-order position } ``` Ignore the terms preorder, inorder, and postorder for now. Look at the `traverse` function. What is it really doing? It is simply a function that visits every node in the binary tree. This is just like traversing an array or a linked list. ```java // iterate through the array void traverse(int[] arr) { for (int i = 0; i < arr.length; i++) { } } // recursively traverse the array void traverse(int[] arr, int i) { if (i == arr.length) { return; } // pre-order position traverse(arr, i + 1); // post-order position } // iterate through the singly linked list void traverse(ListNode head) { for (ListNode p = head; p != null; p = p.next) { } } // recursively traverse the singly linked list void traverse(ListNode head) { if (head == null) { return; } // pre-order position traverse(head.next); // post-order position } ``` You can traverse a singly linked list or an array by iteration or recursion. **A binary tree is just a type of linked structure**, but it cannot be easily rewritten as a for loop. So we usually use recursion to traverse a binary tree. You may also have noticed that recursive traversals can have a "preorder position" and a "postorder position". Preorder is before the recursion, postorder is after the recursion. **Preorder position is when you just enter a node. Postorder position is when you are about to leave a node.** If you put code in different positions, it will run at different times: ![](../pictures/binary-tree-summary/1.jpeg) For example, if you want to **print the values of a singly linked list in reverse order**, how would you do it? There are many ways, but if you understand recursion well, you can use the postorder position: ```java // recursively traverse a singly linked list and print the elements in reverse order void traverse(ListNode head) { if (head == null) { return; } traverse(head.next); // post-order position print(head.val); } ``` With the picture above, you can see why this code prints the linked list in reverse order. It uses the call stack from recursion to print from the end to the start. The same idea applies to binary trees, but there is also an "inorder position". Textbooks often ask you to list the results of preorder, inorder, and postorder traversals. So, many people think these are just three lists in different orders. But I want to say, **preorder, inorder, and postorder are actually three special moments when you process each node during tree traversal**, not just three lists. - Preorder position: code runs when you just enter a binary tree node. - Postorder position: code runs when you are about to leave a binary tree node. - Inorder position: code runs when you have finished visiting the left subtree and are about to visit the right subtree of a node. Notice that I always say "preorder/inorder/postorder position" instead of "traversal". This is to show the difference: you can put code at the preorder position to add elements to a list, and you will get the preorder traversal result. But you can also write more complex code to do other things. Here is a picture of the three positions on a binary tree: ![](../pictures/binary-tree-summary/2.jpeg) **You can see that every node has its own unique preorder, inorder, and postorder position.** So, preorder/inorder/postorder are three special times to process each node during traversal. Now you can also understand why an n-ary tree does not have an inorder position. In a binary tree, each node switches from left to right subtree only once, so there is a unique inorder position. But in an n-ary tree, a node can have many children and switch between them many times, so there is no unique inorder position. All of this is to help you build the right understanding of binary trees. You will see: **All binary tree problems can be solved by writing smart logic at preorder, inorder, or postorder positions. You only need to think about what to do with each node. The traversal framework and recursion will handle the rest and apply your code to all nodes.** You can also see in [Graph Algorithm Basics](https://labuladong.online/en/algo/data-structure-basic/graph-basic/) that the binary tree traversal framework can be extended to graphs, and many classic graph algorithms are built on traversal. But that's a topic for another article. ## Two Approaches to Problem Solving As mentioned in [My Algorithm Learning Insights](https://labuladong.online/en/algo/essential-technique/algorithm-summary/): **Recursive solutions for binary tree problems fall into two categories: the first is to traverse the tree once to get the answer, and the second is to calculate the answer by breaking down the problem. These two approaches correspond to [Backtracking Algorithm Framework](https://labuladong.online/en/algo/essential-technique/backtrack-framework/) and [Dynamic Programming Framework](https://labuladong.online/en/algo/essential-technique/dynamic-programming-framework/) respectively.** ::: tip Tip Let me explain my function naming convention: When solving binary tree problems with the traversal approach, the function signature is typically `void traverse(...)` with no return value, relying on external variables to compute the result. When using the problem decomposition approach, the function name depends on its specific purpose and usually has a return value that represents the result of subproblems. Correspondingly, you'll notice that in [Backtracking Algorithm Framework](https://labuladong.online/en/algo/essential-technique/backtrack-framework/), the function signature is typically `void backtrack(...)` without a return value, while in [Dynamic Programming Framework](https://labuladong.online/en/algo/essential-technique/dynamic-programming-framework/), the function signature is a `dp` function with a return value. This reveals the deep connections between these two approaches and binary trees. While there's no strict requirement for function naming, I recommend you follow this style. It better highlights the function's purpose and the problem-solving mindset, making it easier for you to understand and apply. ::: I previously used the maximum depth of a binary tree as an example, focusing on comparing these two approaches with dynamic programming and backtracking algorithms. This article focuses on analyzing how these two approaches solve binary tree problems. LeetCode problem 104 "[Maximum Depth of Binary Tree](https://leetcode.com/problems/maximum-depth-of-binary-tree/)" is about maximum depth. The maximum depth is the number of nodes along the longest path from the root node to the farthest leaf node. For example, given this binary tree, the algorithm should return 3: ![](../pictures/binary-tree-summary/tree.jpg) How would you approach this problem? Simply traverse the tree once, use an external variable to record the depth of each node, and take the maximum value to get the maximum depth. **This is the traversal approach to computing the answer.** Here's the solution code: ```java // Traversal Idea class Solution { // Record the depth of the traversed node int depth = 0; // Record the maximum depth int res = 0; public int maxDepth(TreeNode root) { traverse(root); return res; } // Traverse the binary tree void traverse(TreeNode root) { if (root == null) { return; } // Pre-order traversal position (entering a node) increases depth depth++; // Record the maximum depth during traversal if (root.left == null && root.right == null) { res = Math.max(res, depth); } traverse(root.left); traverse(root.right); // Post-order traversal position (leaving a node) decreases depth depth--; } } ``` This solution should be straightforward, but why do we need to increment `depth` at the preorder position and decrement it at the postorder position? As mentioned earlier, the preorder position is when you enter a node, and the postorder position is when you leave a node. The `depth` variable tracks the current node's depth in the recursion. Think of `traverse` as a pointer walking through the binary tree, so naturally you need to maintain it this way. As for updating `res`, you can place it at preorder, inorder, or postorder positions, as long as it happens after entering the node and before leaving it (i.e., after `depth` increments and before it decrements). Of course, you can easily see that the maximum depth of a binary tree can be derived from the maximum depths of its subtrees. **This is the problem decomposition approach to computing the answer.** Here's the solution code: ```java // Divide and Conquer Idea class Solution { // Definition: Given a node, return the maximum depth of the binary tree rooted at that node public int maxDepth(TreeNode root) { if (root == null) { return 0; } // Use the definition to calculate the maximum depth of the left and right subtrees int leftMax = maxDepth(root.left); int rightMax = maxDepth(root.right); // Derive the maximum depth of the original binary tree based on the maximum depth of the left and right subtrees // The maximum depth of the entire tree is the maximum of the left and right subtree depths, // plus one for the root node itself return 1 + Math.max(leftMax, rightMax); } } ``` This solution isn't hard to understand once you clarify the recursive function's definition, but why is the main logic concentrated at the postorder position? Because the key to this approach is that you can indeed derive the tree's depth from the maximum depths of its subtrees. So naturally, you first use the recursive function definition to calculate the maximum depths of the left and right subtrees, then derive the original tree's maximum depth. The main logic naturally goes at the postorder position. If you understand the two approaches to the maximum depth problem, **let's revisit the most basic binary tree traversals: preorder, inorder, and postorder**. For example, LeetCode problem 144 "[Binary Tree Preorder Traversal](https://leetcode.com/problems/binary-tree-preorder-traversal/)" asks you to compute the preorder traversal result. The familiar solution uses the "traversal" approach, which I think needs little explanation: ```java // Calculate the preorder traversal result using the traversal idea class Solution { // store the result of the preorder traversal List res = new LinkedList<>(); public List preorderTraversal(TreeNode root) { traverse(root); return res; } // binary tree traversal function void traverse(TreeNode root) { if (root == null) { return; } // preorder position res.add(root.val); traverse(root.left); traverse(root.right); } } ``` But can you use the "problem decomposition" approach to compute the preorder traversal result? In other words, without using helper functions like `traverse` or any external variables, can you solve it purely with the given `preorderTraverse` function recursively? We know that preorder traversal has this characteristic: the root node's value comes first, followed by the left subtree's preorder traversal result, and finally the right subtree's preorder traversal result: ![](../pictures/binary-tree-summary/3.jpeg) So this can be decomposed, right? **A binary tree's preorder traversal result = root node + left subtree's preorder traversal result + right subtree's preorder traversal result**. Therefore, you can implement the preorder traversal algorithm like this: ```java class Solution { // Definition: Given the root node of a binary tree, return the preorder traversal result of this tree List preorderTraversal(TreeNode root) { List res = new LinkedList<>(); if (root == null) { return res; } // The result of preorder traversal, root.val is first res.add(root.val); // Using the function definition, followed by the preorder traversal result of the left subtree res.addAll(preorderTraversal(root.left)); // Using the function definition, finally followed by the preorder traversal result of the right subtree res.addAll(preorderTraversal(root.right)); /** ![](../pictures/binary-tree-summary/3.jpeg) */ return res; } } ``` Inorder and postorder traversals are similar—just place `add(root.val)` at the corresponding inorder and postorder positions. This solution is short and elegant, but why isn't it common? One reason is that **the complexity of this algorithm is hard to control** and depends heavily on language features. In Java, whether using ArrayList or LinkedList, the `addAll` method has O(N) complexity, so the overall worst-case time complexity reaches O(N^2), unless you implement your own O(1) `addAll` method. This is achievable with an underlying linked list implementation, since multiple linked lists can be connected with simple pointer operations. Of course, the main reason is that textbooks never teach it this way... The above examples are simple, but many binary tree problems can be approached and solved with both mindsets. This requires you to practice and think more on your own—don't just settle for one familiar solution approach. In summary, here's the general thinking process when encountering a binary tree problem: **1. Can you get the answer by traversing the tree once?** If so, use a `traverse` function with external variables to implement it. **2. Can you define a recursive function that derives the original problem's answer from subproblem (subtree) answers?** If so, write out this recursive function's definition and fully utilize its return value. **3. Regardless of which approach you use, you need to understand what each node in the binary tree needs to do and when (preorder, inorder, or postorder) to do it**. The [Binary Tree Recursion Practice](https://labuladong.online/en/algo/intro/binary-tree-practice/) on this site lists over 100 binary tree problems, using the two approaches above to guide you step by step through practice, helping you fully master recursive thinking and more easily understand advanced algorithms. ## The Special Properties of Postorder Position Before discussing postorder position, let's briefly talk about preorder and inorder. Preorder position itself doesn't actually have any special properties. The reason you might notice that many problems seem to write code in preorder position is actually because we habitually write code that isn't sensitive to preorder, inorder, or postorder positions in the preorder position. Inorder position is mainly used in BST scenarios. You can completely think of BST inorder traversal as traversing a sorted array. ::: important Key Point **Observe carefully: the capabilities of code in preorder, inorder, and postorder positions increase progressively.** Code in preorder position can only get data passed from the parent node through function parameters. Code in inorder position can not only get parameter data, but also get data passed back from the left subtree through the function's return value. Code in postorder position is the most powerful. It can not only get parameter data, but also simultaneously get data passed back from both left and right subtrees through function return values. Therefore, in certain situations, moving code to postorder position is most efficient; some things can only be done by code in postorder position. ::: Let's look at some concrete examples to feel the difference in their capabilities. Now I'll give you a binary tree and ask you two simple questions: 1. If you consider the root node as level 1, how do you print the level number each node is at? 2. How do you print how many nodes each node's left and right subtrees have? For the first question, you can write code like this: ```java // Binary tree traversal function void traverse(TreeNode root, int level) { if (root == null) { return; } // Preorder position printf("Node %s is at level %d", root.val, level); traverse(root.left, level + 1); traverse(root.right, level + 1); } // Call it like this traverse(root, 1); ``` For the second question, you can write code like this: ```java // Definition: given a binary tree, return the total number of nodes in this tree int count(TreeNode root) { if (root == null) { return 0; } int leftCount = count(root.left); int rightCount = count(root.right); // Postorder position printf("Node %s has %d nodes in left subtree and %d nodes in right subtree", root, leftCount, rightCount); return leftCount + rightCount + 1; } ``` ::: info The fundamental difference between these two problems is What level a node is at can be recorded on the fly as you traverse from the root node, and can be passed down through the recursive function's parameters. But how many nodes are in the entire subtree rooted at a node - you must finish traversing the subtree before you can count it clearly, then get the answer through the recursive function's return value. Combined with these two simple problems, you can appreciate the characteristics of postorder position. Only postorder position can get subtree information through return values. **In other words, once you discover that a problem is related to subtrees, you'll likely need to set a reasonable definition and return value for your function, and write code in postorder position.** ::: Next, let's look at how postorder position plays a role in actual problems. Let's briefly discuss LeetCode problem 543 "[Diameter of Binary Tree](https://leetcode.com/problems/diameter-of-binary-tree/)", which asks you to calculate the longest diameter length of a binary tree. The so-called "diameter" length of a binary tree is the path length between any two nodes. The longest "diameter" doesn't necessarily pass through the root node. For example, in the binary tree below: ![](../pictures/binary-tree-summary/tree1.png) Its longest diameter is 3, namely the "diameter" formed by paths like `[4,2,1,3]`, `[4,2,1,9]`, or `[5,2,1,3]`. The key to solving this problem is: **the "diameter" length of each binary tree is the sum of the maximum depths of a node's left and right subtrees**. Now if you want me to find the longest "diameter" in the entire tree, the straightforward approach is to traverse every node in the entire tree, then calculate each node's "diameter" through the maximum depth of each node's left and right subtrees, and finally find the maximum of all "diameters". We just implemented the maximum depth algorithm, so the above approach can be written as the following code: ```java class Solution { // record the length of the maximum diameter int maxDiameter = 0; public int diameterOfBinaryTree(TreeNode root) { // calculate the diameter for each node and find the maximum diameter traverse(root); return maxDiameter; } // traverse the binary tree void traverse(TreeNode root) { if (root == null) { return; } // calculate the diameter for each node int leftMax = maxDepth(root.left); int rightMax = maxDepth(root.right); int myDiameter = leftMax + rightMax; // update the global maximum diameter maxDiameter = Math.max(maxDiameter, myDiameter); traverse(root.left); traverse(root.right); } // calculate the maximum depth of the binary tree int maxDepth(TreeNode root) { if (root == null) { return 0; } int leftMax = maxDepth(root.left); int rightMax = maxDepth(root.right); return 1 + Math.max(leftMax, rightMax); } } ``` This solution is correct, but the running time is very long. The reason is also obvious: when `traverse` visits each node, it also calls the recursive function `maxDepth`, and `maxDepth` has to traverse all nodes of the subtree, so the worst-case time complexity is O(N^2). This is exactly the situation we just discussed. **Preorder position cannot get subtree information, so each node can only call the `maxDepth` function to calculate the subtree's depth**. So how do we optimize? We should put the logic for calculating "diameter" in postorder position. To be precise, it should be placed in the postorder position of `maxDepth`, because the postorder position of `maxDepth` knows the maximum depths of the left and right subtrees. So, slightly changing the code logic gives us a better solution: ```java class Solution { // record the length of the maximum diameter int maxDiameter = 0; public int diameterOfBinaryTree(TreeNode root) { maxDepth(root); return maxDiameter; } int maxDepth(TreeNode root) { if (root == null) { return 0; } int leftMax = maxDepth(root.left); int rightMax = maxDepth(root.right); // post-order position, calculate the maximum diameter by the way int myDiameter = leftMax + rightMax; maxDiameter = Math.max(maxDiameter, myDiameter); return 1 + Math.max(leftMax, rightMax); } } ``` Now the time complexity is only O(N) from the `maxDepth` function. At this point, let me echo what was said earlier: when you encounter subtree problems, the first thing to think about is setting a return value for your function, then working on the postorder position. ::: info Info Exercise: Please think about whether problems that use postorder position employ a "traversal" approach or a "decomposition" approach? ::: ::: details Click to view answer Problems that utilize postorder position generally use a "decomposition" approach. Because the current node receives and uses information returned from subtrees, this means you've decomposed the original problem into the current node + the subproblems of the left and right subtrees. ::: Conversely, if you write a solution that involves recursion within recursion like the initial approach, you should probably reflect on whether it can be optimized through postorder traversal. For more exercises utilizing postorder position, see [Hands-on Guide to Binary Tree Problems (Postorder Edition)](https://labuladong.online/en/algo/data-structure/binary-tree-part3/), [Hands-on Guide to Binary Search Tree Problems (Postorder Edition)](https://labuladong.online/en/algo/data-structure/bst-part4/), and [Solving Problems Using Postorder Position](https://labuladong.online/en/algo/problem-set/binary-tree-post-order-i/). ## Understanding DP/Backtracking/DFS Through the Lens of Trees Earlier I said that dynamic programming and backtracking are just two different manifestations of binary tree thinking. If you've read this far, you probably agree. But some sharp readers keep asking: your way of thinking really opened my eyes, but it seems like you've never covered DFS? I actually used DFS in [Solving All Island Problems in One Go](https://labuladong.online/en/algo/frequency-interview/island-dfs-summary/), but I never devoted a standalone article to it. **That's because DFS and backtracking are very similar—they only differ in one subtle detail**. What's the difference exactly? It boils down to whether "making a choice" and "undoing a choice" happen inside or outside the for loop. DFS puts them outside; backtracking puts them inside. Why the difference? Again, it all comes back to binary trees. In this section, I'll use one sentence each to explain the connections and differences among backtracking, DFS, and dynamic programming—and how they relate to binary tree algorithms: ::: important Key Takeaway DP, DFS, and backtracking can all be viewed as extensions of binary tree problems. The difference is what they focus on: - Dynamic programming uses a decomposition (divide-and-conquer) approach, focusing on entire "subtrees." - Backtracking uses a traversal approach, focusing on the "edges" between nodes. - DFS uses a traversal approach, focusing on individual "nodes." ::: How to understand this? Three examples will make it crystal clear. ### Example 1: The Decomposition Mindset **First example**: given a binary tree, write a `count` function using the decomposition approach to count the total number of nodes. The code is straightforward—we've already seen it: ```java // Definition: input a binary tree, return the total number of nodes in this binary tree int count(TreeNode root) { if (root == null) { return 0; } // The current node cares about the total number of nodes in each of its subtrees // Because the result of the subproblems can be used to derive the result of the original problem int leftCount = count(root.left); int rightCount = count(root.right); // In the postorder position, the total number of nodes in the left and right subtrees plus the current node itself is the total number of nodes in the entire tree return leftCount + rightCount + 1; } ``` **See? This is the decomposition mindset of dynamic programming. It always focuses on structurally identical subproblems, which correspond to "subtrees" in a binary tree**. Now look at a concrete DP problem. For instance, the Fibonacci example from [Dynamic Programming Framework Explained](https://labuladong.online/en/algo/essential-technique/dynamic-programming-framework/)—our focus is on the return values of each subtree: ```java // f(n) calculates the nth Fibonacci number int fib(int n) { // base case if (n == 0 || n == 1){ return n; } return fib(n - 1) + fib(n - 2); } ``` ![](../pictures/dynamic-programming/2.jpg) ### Example 2: The Backtracking Mindset **Second example**: given a binary tree, write a `traverse` function using the traversal approach to print out the traversal process. Check out the code: ```java void traverse(TreeNode root) { if (root == null) return; printf("enter node %s from node %s", root.left, root); traverse(root.left); printf("leave node %s, return to node %s", root.left, root); printf("enter node %s from node %s", root.right, root); traverse(root.right); printf("leave node %s, return to node %s", root.right, root); } ``` Not hard to understand, right? Now let's level up from binary tree to N-ary tree—the code looks similar: ```java // N-ary tree node class Node { int val; Node[] children; } void traverse(Node root) { if (root == null) return; for (Node child : root.children) { printf("enter node %s from node %s", child, root); traverse(child); printf("leave node %s, return to node %s", child, root); } } ``` This N-ary tree traversal framework naturally extends into the backtracking framework from [Backtracking Algorithm Framework Explained](https://labuladong.online/en/algo/essential-technique/backtrack-framework/): ```java // backtracking framework void backtrack(...) { // base case if (...) return; for (int i = 0; i < ...; i++) { // make a choice ... // enter the next level of the decision tree backtrack(...); // undo the choice ... } } ``` **See? This is the traversal mindset of backtracking. It always focuses on the process of moving between nodes, which corresponds to "edges" in a binary tree**. Now look at a concrete backtracking problem. For instance, the permutation problem from [Backtracking: 9 Variations of Permutations and Combinations](https://labuladong.online/en/algo/essential-technique/permutation-combination-subset-all-in-one/)—our focus is on individual tree edges: ```java // core backtracking code void backtrack(int[] nums) { // backtracking framework for (int i = 0; i < nums.length; i++) { // make a choice used[i] = true; track.addLast(nums[i]); // enter the next level of the backtracking tree backtrack(nums); // undo the choice track.removeLast(); used[i] = false; } } ``` ![](../pictures/permutation/2.jpeg) ### Example 3: The DFS Mindset **Third example**: given a binary tree, write a `traverse` function that increments every node's value by one. Simple enough: ```java void traverse(TreeNode root) { if (root == null) return; // increment the value of each traversed node by one root.val++; traverse(root.left); traverse(root.right); } ``` **See? This is the traversal mindset of DFS. It always focuses on individual nodes, which corresponds to processing each "node" in a binary tree**. Now look at a concrete DFS problem. For instance, the first few problems from [Solving All Island Problems in One Go](https://labuladong.online/en/algo/frequency-interview/island-dfs-summary/)—our focus is on each cell (node) in the `grid` array. We need to process every visited cell, which is why I say we're using DFS to solve these problems: ```java // core DFS logic void dfs(int[][] grid, int i, int j) { int m = grid.length, n = grid[0].length; if (i < 0 || j < 0 || i >= m || j >= n) { return; } if (grid[i][j] == 0) { return; } // mark each visited cell as 0 grid[i][j] = 0; dfs(grid, i + 1, j); dfs(grid, i, j + 1); dfs(grid, i - 1, j); dfs(grid, i, j - 1); } ``` ![](../pictures/island/5.jpg) Take a moment to digest these three examples. See the pattern? Dynamic programming focuses on entire "subtrees," backtracking focuses on "edges" between nodes, and DFS focuses on individual "nodes." With this foundation, it's easy to understand why "making a choice" and "undoing a choice" are placed differently in backtracking vs. DFS code. Look at these two snippets: ```java // DFS algorithm puts the logic of "making a choice" and "undoing a choice" outside the for loop void dfs(Node root) { if (root == null) return; // make a choice print("enter node %s", root); for (Node child : root.children) { dfs(child); } // undo a choice print("leave node %s", root); } // Backtracking algorithm puts the logic of "making a choice" and "undoing a choice" inside the for loop void backtrack(Node root) { if (root == null) return; for (Node child : root.children) { // make a choice print("I'm on the branch from %s to %s", root, child); backtrack(child); // undo a choice print("I'll leave the branch from %s to %s", child, root); } } ``` See the difference? Backtracking must put "make a choice" and "undo the choice" inside the for loop—otherwise, how would you capture both endpoints of an "edge"? ## Level-Order Traversal Binary tree problems mainly serve to build your recursive thinking. Level-order traversal, on the other hand, is iterative and relatively simple. Let's quickly go over the framework: ```java // Input the root node of a binary tree, perform level-order traversal on the binary tree void levelTraverse(TreeNode root) { if (root == null) return; Queue q = new LinkedList<>(); q.offer(root); int depth = 1; // Traverse each level of the binary tree from top to bottom while (!q.isEmpty()) { int sz = q.size(); // Traverse each node of the level from left to right for (int i = 0; i < sz; i++) { TreeNode cur = q.poll(); // put the nodes of the next level into the queue if (cur.left != null) { q.offer(cur.left); } if (cur.right != null) { q.offer(cur.right); } } depth++; } } ``` The while loop handles top-to-bottom traversal, while the for loop handles left-to-right traversal: ![](../pictures/dijkstra/1.jpeg) The [BFS Algorithm Framework](https://labuladong.online/en/algo/essential-technique/bfs-framework/) is a direct extension of binary tree level-order traversal, commonly used for **shortest path** problems in unweighted graphs. You can also modify this framework flexibly. When the problem doesn't require tracking levels (steps), you can drop the for loop. For example, [Dijkstra's Algorithm](https://labuladong.online/en/algo/data-structure/dijkstra/) explores how to extend BFS for shortest path problems in weighted graphs. Worth mentioning: some binary tree problems that obviously call for level-order traversal can also be solved with recursive traversal. The techniques involved are trickier and really test your mastery of preorder, inorder, and postorder positions. Alright, this article is long enough. We've thoroughly covered all the key patterns in binary tree problems by building everything around preorder, inorder, and postorder positions. How much of this you can actually apply comes down to practice and hands-on problem solving. Finally, the [Binary Tree Recursion Practice](https://labuladong.online/en/algo/intro/binary-tree-practice/) section will walk you through applying the techniques from this article step by step. ## Answering Questions from the Comments Let me talk a bit more about level order traversal (and the [BFS algorithm framework](https://labuladong.online/en/algo/essential-technique/bfs-framework/) that comes from it). If you know enough about binary trees, you might think of many ways to get the level order result using recursion, like the example below: ```java class Solution { List> res = new ArrayList<>(); public List> levelTraverse(TreeNode root) { if (root == null) { return res; } // root is considered as level 0 traverse(root, 0); return res; } void traverse(TreeNode root, int depth) { if (root == null) { return; } // pre-order position, check if nodes of depth level are already stored if (res.size() <= depth) { // first time entering depth level res.add(new LinkedList<>()); } // pre-order position, add root node's value at depth level res.get(depth).add(root.val); traverse(root.left, depth + 1); traverse(root.right, depth + 1); } } ``` This method does give you the level order result, but in essence, it is still a preorder traversal of the binary tree, or in other words, it uses DFS thinking, not true level order or BFS thinking. This is because the solution depends on preorder traversal's top-down, left-to-right order to get the right answer. **To put it simply, this solution is more like a "column order traversal" from left to right, not a "level order traversal" from top to bottom.** So, for problems like finding the minimum distance, this solution works the same as DFS, and does not have the performance advantage of BFS. Some readers also shared another recursive way to do level order traversal: ```java class Solution { List> res = new LinkedList<>(); public List> levelTraverse(TreeNode root) { if (root == null) { return res; } List nodes = new LinkedList<>(); nodes.add(root); traverse(nodes); return res; } void traverse(List curLevelNodes) { // base case if (curLevelNodes.isEmpty()) { return; } // pre-order position, calculate the values of the current level and the node list of the next level List nodeValues = new LinkedList<>(); List nextLevelNodes = new LinkedList<>(); for (TreeNode node : curLevelNodes) { nodeValues.add(node.val); if (node.left != null) { nextLevelNodes.add(node.left); } if (node.right != null) { nextLevelNodes.add(node.right); } } // add results in pre-order position to get top-down level order traversal res.add(nodeValues); traverse(nextLevelNodes); // add results in post-order position to get bottom-up level order traversal // res.add(nodeValues); } } ``` The `traverse` function here is a bit like a recursive function for traversing a singly linked list. It treats each level of the binary tree as a node in a linked list. Compared to the previous recursive solution, this one does a top-down "level order traversal." It is closer to the core idea of BFS. You can use this as a recursive way to implement BFS and expand your thinking. ================================================ FILE: data-structures/bst-part1.md ================================================ ::: info Prerequisites Before reading this article, you need to study: - [Binary Tree Basics](https://labuladong.online/en/algo/data-structure-basic/binary-tree-basic/) - [DFS/BFS Traversal of Binary Trees](https://labuladong.online/en/algo/data-structure-basic/binary-tree-traverse-basic/) ::: In previous articles, we covered binary trees step by step. We wrote [Thinking](https://labuladong.online/en/algo/data-structure/binary-tree-part1/), [Construction](https://labuladong.online/en/algo/data-structure/binary-tree-part2/), [Postorder](https://labuladong.online/en/algo/data-structure/binary-tree-part3/), and [Serialization](https://labuladong.online/en/algo/data-structure/serialize-and-deserialize-binary-tree/) articles. Today, we start a series on Binary Search Tree (BST). I will guide you through solving BST problems step by step. First, you should be familiar with the properties of BST (see [Binary Tree Basics](https://labuladong.online/en/algo/data-structure-basic/binary-tree-basic/)): 1. For any node `node` in a BST, all nodes in the left subtree are smaller than `node`, and all nodes in the right subtree are larger than `node`. 2. For any node `node`, its left and right subtrees are also BSTs. Binary search trees are not very complex, but they are very important in data structures. Data structures like AVL tree and Red-Black tree are based on BST and have self-balancing properties. These provide logN time for insertion, deletion, search, and update. B+ trees, segment trees, and other structures are also designed with BST ideas. **From the perspective of solving algorithm problems, besides its definition, BST has another key property: the result of its inorder traversal is sorted in ascending order.** This means, if you have a BST, the following code can print all node values in ascending order: ```java void traverse(TreeNode root) { if (root == null) return; traverse(root.left); // inorder traversal code position print(root.val); traverse(root.right); } ``` Now, based on this property, let's solve two algorithm problems. ## Find the Kth Smallest Element This is LeetCode Problem 230: [Kth Smallest Element in a BST](https://leetcode.com/problems/kth-smallest-element-in-a-bst/). Let's look at the problem: **LeetCode 230. Kth Smallest Element in a BST** Given the `root` of a binary search tree, and an integer `k`, return *the* `k^(th)` *smallest value (**1-indexed**) of all the values of the nodes in the tree*. Example 1:** ![](https://assets.leetcode.com/uploads/2021/01/28/kthtree1.jpg) ``` **Input:** root = [3,1,4,null,2], k = 1 **Output:** 1 ``` Example 2:** ![](https://assets.leetcode.com/uploads/2021/01/28/kthtree2.jpg) ``` **Input:** root = [5,3,6,2,4,null,null,1], k = 3 **Output:** 3 ``` **Constraints:** - The number of nodes in the tree is `n`. - `1 <= k <= n <= 10^(4)` - `0 <= Node.val <= 10^(4)` **Follow up:** If the BST is modified often (i.e., we can do insert and delete operations) and you need to find the kth smallest frequently, how would you optimize? This is a common problem. An easy idea is to sort the elements in ascending order and then find the k-th element. The in-order traversal of a BST gives you the elements in order, so finding the k-th smallest is not hard. Following this idea, you can write the code like this: ```java class Solution { public int kthSmallest(TreeNode root, int k) { // use the in-order traversal property of BST traverse(root, k); return res; } // record the result int res = 0; // record the rank of the current element int rank = 0; void traverse(TreeNode root, int k) { if (root == null) { return; } traverse(root.left, k); // in-order code position rank++; if (k == rank) { // found the k-th smallest element res = root.val; return; } traverse(root.right, k); } } ``` That's it for this problem. But let me say more. This solution is not the most efficient. It only works well for this problem. In the previous article [Efficiently Find the Median of a Data Stream](https://labuladong.online/en/algo/practice-in-action/find-median-from-data-stream/), we talked about this problem: ::: info Info If you need to implement a method `select(int k)` in a binary search tree to get the element by rank, how would you design it? ::: If you use the in-order traversal method we just mentioned, each time you want to find the k-th smallest element, you must traverse the whole tree. The worst-case time complexity is $O(N)$, where `N` is the number of nodes in the BST. But the BST is powerful. For example, in balanced BSTs like Red-Black Trees, insert, delete, search, and update are all $O(\log N)$ time. But finding the k-th smallest element this way is $O(N)$, which is not efficient. So, to find the k-th smallest element, the best algorithm should also have logarithmic time complexity. But this depends on how much information each node in the BST stores. Why are BST operations so fast? For example, to search for a value, BST can find an element in logarithmic time because of its property: the left subtree is smaller, the right subtree is bigger. Each node can compare its value to the target and decide to go left or right. This avoids searching the whole tree and keeps complexity low. Back to our problem: to find the k-th smallest element, or the element with rank k, we also want logarithmic time complexity. The key is: every node should know its own rank. For example, if you want to find the element with rank k, and the current node knows its own rank is m, you can compare `m` and `k`: 1. If `m == k`, you found the k-th element. Return the current node. 2. If `k < m`, the k-th element is in the left subtree. Go search the left subtree for the k-th element. 3. If `k > m`, the k-th element is in the right subtree. Search the right subtree for the `(k - m - 1)`-th element. With this, the time complexity becomes $O(\log N)$. So, how can each node know its rank? This is where we need to keep extra information in each node. **Each node should record the number of nodes in its subtree (including itself).** So, the `TreeNode` should look like this: ```java class TreeNode { int val; // total number of nodes in the tree rooted at this node int size; TreeNode left; TreeNode right; } ``` With the `size` field and the BST property (left is smaller, right is bigger), each node can use `node.left` to calculate its rank. This makes the algorithm logarithmic time. Of course, the `size` field must be updated correctly when nodes are added or removed. LeetCode's `TreeNode` does not have this field, so for this problem, you can only use the in-order traversal method. But the idea above is a common BST optimization. It's good to understand it. ## Convert BST to Greater Tree LeetCode problems 538 and 1038 are actually the same question. You can solve them together. Here is the problem: **LeetCode 538. Convert BST to Greater Tree** Given the `root` of a Binary Search Tree (BST), convert it to a Greater Tree such that every key of the original BST is changed to the original key plus the sum of all keys greater than the original key in BST. As a reminder, a *binary search tree* is a tree that satisfies these constraints: - The left subtree of a node contains only nodes with keys **less than** the node's key. - The right subtree of a node contains only nodes with keys **greater than** the node's key. - Both the left and right subtrees must also be binary search trees. Example 1:** ![](https://assets.leetcode.com/uploads/2019/05/02/tree.png) ``` **Input:** root = [4,1,6,0,2,5,7,null,null,null,3,null,null,null,8] **Output:** [30,36,21,36,35,26,15,null,null,null,33,null,null,null,8] ``` Example 2:** ``` **Input:** root = [0,null,1] **Output:** [1,null,1] ``` **Constraints:** - The number of nodes in the tree is in the range `[0, 10^(4)]`. - `-10^(4) <= Node.val <= 10^(4)` - All the values in the tree are **unique**. - `root` is guaranteed to be a valid binary search tree. **Note:** This question is the same as 1038: [https://leetcode.com/problems/binary-search-tree-to-greater-sum-tree/](https://leetcode.com/problems/binary-search-tree-to-greater-sum-tree/) The problem is easy to understand. For example, look at node 5 in the picture. To convert to a greater tree, you need to add all nodes greater than 5, which are 6, 7, and 8, plus 5 itself. So, the value of this node in the greater tree should be 5+6+7+8=26. We need to convert a BST to a greater tree. The function signature is as follows: ```java TreeNode convertBST(TreeNode root) ``` According to the usual way of solving binary tree problems, we should think about what to do at each node. But for this problem, it is hard to come up with a good idea right away. Each node in a BST has smaller values on the left and bigger values on the right. This seems useful. Since we want the sum of all values greater than or equal to the current node, can't we just calculate the sum of the right subtree for every node? Actually, this is not enough. The right subtree does contain bigger values, but the parent node might also have a bigger value. We cannot be sure because we do not have a pointer to the parent node. So, the usual binary tree approach does not work here. **Let's try another way and use the property of BST's in-order traversal**. Earlier, we said that an in-order traversal of a BST prints the node values in increasing order. What if we want to print the values in decreasing order? It is simple. We just change the recursion order: visit the right subtree first, then the left subtree: ```java void traverse(TreeNode root) { if (root == null) return; // First, recursively traverse the right subtree traverse(root.right); // Inorder traversal code position print(root.val); // Then, recursively traverse the left subtree traverse(root.left); } ``` **This code can print the BST node values in decreasing order. If we keep an external variable `sum` and set each BST node's value to `sum`, we can convert the BST to a greater tree.** Let's look at the code: ```java class Solution { public TreeNode convertBST(TreeNode root) { traverse(root); return root; } // record the cumulative sum int sum = 0; void traverse(TreeNode root) { if (root == null) { return; } traverse(root.right); // maintain the cumulative sum sum += root.val; // convert bst to cumulative tree root.val = sum; traverse(root.left); } } ``` That solves the problem. The key is still the in-order traversal of the BST. We just changed the order of recursion to visit the right subtree first, so we visit nodes from biggest to smallest, which matches the requirement for the greater tree. To sum up, for BST problems, you should either use the BST property (left is smaller, right is bigger) to make your algorithm faster, or use in-order traversal to meet the problem's needs. That's the main idea. That's all for this article. For more classic binary tree problems and practice with recursion, please see the [Exercise section](https://labuladong.online/en/algo/problem-set/bst1/) in the binary tree chapter. ================================================ FILE: data-structures/bst-part2.md ================================================ ::: info Prerequisite Before reading this article, you should first learn: - [Basics of Binary Tree Structure](https://labuladong.online/en/algo/data-structure-basic/binary-tree-basic/) - [DFS/BFS Traversal of Binary Trees](https://labuladong.online/en/algo/data-structure-basic/binary-tree-traverse-basic/) ::: In the previous article [Binary Search Tree Basics (Features)](https://labuladong.online/en/algo/data-structure/bst-part1/), we introduced the basic features of BST and used the "in-order traversal is sorted" property to solve some problems. In this article, we'll implement the basic operations of BST: checking if a BST is valid, inserting, deleting, and searching. Among these, "deleting" and "checking validity" are a bit more complex. The basic operations of BST mainly rely on the "left smaller, right bigger" property. This allows us to do binary search-like operations in the tree, making it very efficient to find an element. For example, the following is a valid binary search tree: ![](../pictures/bst/0.png) For BST problems, you will often see code logic like this: ```java void BST(TreeNode root, int target) { if (root.val == target) // found the target, do something if (root.val < target) BST(root.right, target); if (root.val > target) BST(root.left, target); } ``` This code framework is similar to the usual binary tree traversal, just using the BST "left smaller, right bigger" feature. Next, let's see how the basic operations on BST are implemented. ## 1. Check if a BST is Valid LeetCode Problem 98 "[Validate Binary Search Tree](https://leetcode.com/problems/validate-binary-search-tree/)" asks you to check if a given BST is valid: **LeetCode 98. Validate Binary Search Tree** Given the `root` of a binary tree, *determine if it is a valid binary search tree (BST)*. A **valid BST** is defined as follows: - The left subtree of a node contains only nodes with keys **less than** the node's key. - The right subtree of a node contains only nodes with keys **greater than** the node's key. - Both the left and right subtrees must also be binary search trees. Example 1:** ![](https://assets.leetcode.com/uploads/2020/12/01/tree1.jpg) ``` **Input:** root = [2,1,3] **Output:** true ``` Example 2:** ![](https://assets.leetcode.com/uploads/2020/12/01/tree2.jpg) ``` **Input:** root = [5,1,4,null,null,3,6] **Output:** false **Explanation:** The root node's value is 5 but its right child's value is 4. ``` **Constraints:** - The number of nodes in the tree is in the range `[1, 10^(4)]`. - `-2^(31) <= Node.val <= 2^(31) - 1` Be careful, there is a trap here. According to the BST property, every node should be compared with its left and right children to check if it is valid. It seems you might write code like this: ```java boolean isValidBST(TreeNode root) { if (root == null) return true; // the left side of the root should be smaller if (root.left != null && root.left.val >= root.val) return false; // the right side of the root should be larger if (root.right != null && root.right.val <= root.val) return false; return isValidBST(root.left) && isValidBST(root.right); } ``` But this algorithm is wrong. For BST, every node must be less than **all** nodes in its right subtree. The following tree is not a valid BST because there is a node `8` in the left subtree of node `7`, but our code would say it is valid: ```yaml BinaryTree root: value: 7 color: orange left: value: 4 left: value: 1 right: value: 8 color: orange right: value: 9 right: value: 10 ``` **The reason for the mistake is that, for each node `root`, the code only checks its left and right child nodes. But, by the definition of BST, the whole left subtree of `root` must be less than `root.val`, and the whole right subtree must be greater than `root.val`.** The problem is, for a node `root`, it can only directly check its children. How do we pass this constraint down to all nodes of the left and right subtree? Here is the correct code: ```java class Solution { public boolean isValidBST(TreeNode root) { return isValidBST(root, null, null); } // nodes in the subtree rooted at root must satisfy max.val > root.val > min.val boolean isValidBST(TreeNode root, TreeNode min, TreeNode max) { // base case if (root == null) return true; // if root.val does not satisfy the constraints of max and min, it is not a valid BST if (min != null && root.val <= min.val) return false; if (max != null && root.val >= max.val) return false; // the maximum value in the left subtree is root.val, and the minimum value in the right subtree is root.val return isValidBST(root.left, min, root) && isValidBST(root.right, root, max); } } ``` We use a helper function and add extra parameters to pass down these constraints to all child nodes. This is also a useful trick in binary tree algorithms. ## Search for an Element in a BST LeetCode problem 700, "[Search in a Binary Search Tree](https://leetcode.com/problems/search-in-a-binary-search-tree/)," asks you to find a node with value `target` in a BST. The function signature is as follows: ```java TreeNode searchBST(TreeNode root, int target); ``` If you are searching in a normal binary tree, you can write the code like this: ```java TreeNode searchBST(TreeNode root, int target) { if (root == null) return null; if (root.val == target) return root; // if the current node is not found, recursively search the left and right subtrees TreeNode left = searchBST(root.left, target); TreeNode right = searchBST(root.right, target); return left != null ? left : right; } ``` This code is correct, but it checks all nodes, which is brute-force and works for any binary tree. But how can we use the special property of BST, where the left side is smaller and the right side is larger? It is simple. You do not need to search both sides. You can use a binary search idea: compare `target` with `root.val`. This way, you can ignore one side. Let's change the code using this idea: ```java // define: search for the node with value target in the BST rooted at root, return the node TreeNode searchBST(TreeNode root, int target) { if (root == null) { return null; } // search in the left subtree if (root.val > target) { return searchBST(root.left, target); } // search in the right subtree if (root.val < target) { return searchBST(root.right, target); } // the current node is the target value return root; } ``` ## Insert a Number into a BST When working with data structures, you usually travel through (find) and visit (change) the nodes. For this problem, to insert a number, you first find the right position, then insert it. BSTs usually do not have duplicate values, so you do not need to insert a value that is already in the BST. **The code below assumes you will not insert a value that already exists in the BST.** In the last problem, we summarized the way to travel through a BST, which is the "find" part. Now, just add the "change" part. **If you need to change the tree, it is like building a binary tree; the function should return `TreeNode`, and you need to use the result from the recursive call.** LeetCode problem 701, "[Insert into a Binary Search Tree](https://leetcode.com/problems/insert-into-a-binary-search-tree/)," is about this: **LeetCode 701. Insert into a Binary Search Tree** You are given the `root` node of a binary search tree (BST) and a `value` to insert into the tree. Return *the root node of the BST after the insertion*. It is **guaranteed** that the new value does not exist in the original BST. **Notice** that there may exist multiple valid ways for the insertion, as long as the tree remains a BST after insertion. You can return **any of them**. Example 1:** ![](https://assets.leetcode.com/uploads/2020/10/05/insertbst.jpg) ``` **Input:** root = [4,2,7,1,3], val = 5 **Output:** [4,2,7,1,3,5] **Explanation:** Another accepted tree is: ![](https://assets.leetcode.com/uploads/2020/10/05/bst.jpg) ``` Example 2:** ``` **Input:** root = [40,20,60,10,30,50,70], val = 25 **Output:** [40,20,60,10,30,50,70,null,null,25] ``` Example 3:** ``` **Input:** root = [4,2,7,1,3,null,null,null,null,null,null], val = 5 **Output:** [4,2,7,1,3,5] ``` **Constraints:** - The number of nodes in the tree will be in the range `[0, 10^(4)]`. - `-10^(8) <= Node.val <= 10^(8)` - All the values `Node.val` are **unique**. - `-10^(8) <= val <= 10^(8)` - It's **guaranteed** that `val` does not exist in the original BST. Let's look at the solution code. You can use the comments and visual panel to help you understand: ```java class Solution { public TreeNode insertIntoBST(TreeNode root, int val) { // find the empty spot to insert the new node if (root == null) return new TreeNode(val); // if (root.val == val) // generally, in a bst, we don't insert an element that already exists if (root.val < val) root.right = insertIntoBST(root.right, val); if (root.val > val) root.left = insertIntoBST(root.left, val); return root; } } ``` ## 3. Delete a Node in a BST LeetCode Problem 450: [Delete Node in a BST](https://leetcode.com/problems/delete-node-in-a-bst/) asks you to delete a node with value `key` from a BST: **LeetCode 450. Delete Node in a BST** Given a root node reference of a BST and a key, delete the node with the given key in the BST. Return *the **root node reference** (possibly updated) of the BST*. Basically, the deletion can be divided into two stages: - Search for a node to remove. - If the node is found, delete the node. Example 1:** ![](https://assets.leetcode.com/uploads/2020/09/04/del_node_1.jpg) ``` **Input:** root = [5,3,6,2,4,null,7], key = 3 **Output:** [5,4,6,2,null,null,7] **Explanation:** Given key to delete is 3. So we find the node with value 3 and delete it. One valid answer is [5,4,6,2,null,null,7], shown in the above BST. Please notice that another valid answer is [5,2,6,null,4,null,7] and it's also accepted. ![](https://assets.leetcode.com/uploads/2020/09/04/del_node_supp.jpg) ``` Example 2:** ``` **Input:** root = [5,3,6,2,4,null,7], key = 0 **Output:** [5,3,6,2,4,null,7] **Explanation:** The tree does not contain a node with value = 0. ``` Example 3:** ``` **Input:** root = [], key = 0 **Output:** [] ``` **Constraints:** - The number of nodes in the tree is in the range `[0, 10^(4)]`. - `-10^(5) <= Node.val <= 10^(5)` - Each node has a **unique** value. - `root` is a valid binary search tree. - `-10^(5) <= key <= 10^(5)` **Follow up:** Could you solve it with time complexity `O(height of tree)`? This problem is a bit tricky. Like insertion, you need to "find" first, then "change." Let's write the basic structure first: ```java TreeNode deleteNode(TreeNode root, int key) { if (root.val == key) { // found it, proceed to delete } else if (root.val > key) { // go to the left subtree root.left = deleteNode(root.left, key); } else if (root.val < key) { // go to the right subtree root.right = deleteNode(root.right, key); } return root; } ``` After finding the target node, let's say it is node `A`, how do we delete it? This is the main challenge, because you can't break the properties of the BST. There are three possible cases, shown with pictures. **Case 1**: `A` is a leaf node (both children are null). You can simply delete it. ![](../pictures/bst/bst_deletion_case_1.png) ```java if (root.left == null && root.right == null) return null; ``` **Case 2**: `A` has only one non-empty child. Let this child take `A`'s place. ![](../pictures/bst/bst_deletion_case_2.png) ```java // After excluding case 1 if (root.left == null) return root.right; if (root.right == null) return root.left; ``` **Case 3**: `A` has two children. This is more complex. To keep the BST property, you must find either the largest node in the left subtree or the smallest node in the right subtree to replace `A`. We will explain the second way. ![](../pictures/bst/bst_deletion_case_3.png) ```java if (root.left != null && root.right != null) { // Find the smallest node in the right subtree TreeNode minNode = getMin(root.right); // Replace root with minNode root.val = minNode.val; // Delete minNode root.right = deleteNode(root.right, minNode.val); } ``` After explaining all three cases, fill them into the framework and simplify the code: ```java class Solution { public TreeNode deleteNode(TreeNode root, int key) { if (root == null) return null; if (root.val == key) { // these two if statements correctly handle cases 1 and 2 if (root.left == null) return root.right; if (root.right == null) return root.left; // handle case 3 // get the smallest node in the right subtree TreeNode minNode = getMin(root.right); // delete the smallest node in the right subtree root.right = deleteNode(root.right, minNode.val); // replace the root node with the smallest node from the right subtree minNode.left = root.left; minNode.right = root.right; root = minNode; } else if (root.val > key) { root.left = deleteNode(root.left, key); } else if (root.val < key) { root.right = deleteNode(root.right, key); } return root; } TreeNode getMin(TreeNode node) { // the leftmost node in a BST is the smallest while (node.left != null) node = node.left; return node; } } ``` Now, the delete operation is finished. Note: In case 3, the code above replaces the `root` node with `minNode` by swapping their values, which is a bit simpler: ```java // Handle case 3 // Get the smallest node in the right subtree TreeNode minNode = getMin(root.right); // Delete the smallest node in the right subtree root.right = deleteNode(root.right, minNode.val); // Replace root node with the smallest node in the right subtree minNode.left = root.left; minNode.right = root.right; root = minNode; ``` Some readers may wonder why we need to swap the nodes with pointer operations. Why not just change the `val` field? It looks easier: ```java // Handle case 3 // Get the smallest node in the right subtree TreeNode minNode = getMin(root.right); // Delete the smallest node in the right subtree root.right = deleteNode(root.right, minNode.val); // Replace root node with the smallest node in the right subtree root.val = minNode.val; ``` For this algorithm problem, it is fine. But in real applications, we do not swap nodes by just changing the internal value. The data inside a BST node could be very complex, and the BST structure should not care about the actual data. So, we prefer using pointers to swap nodes. Let's summarize a few key points from this article: 1. If the current node will affect its children, you can pass information by adding parameters to helper functions. 2. Master how to insert, delete, search, and update nodes in a BST. 3. When changing a data structure with recursion, always receive the return value and return the updated node. That's all for this article. For more classic binary tree problems and recursion practice, see the [Recursion Practice for Binary Search Trees](https://labuladong.online/en/algo/problem-set/bst1/) in the binary tree chapter. ================================================ FILE: data-structures/calculator.md ================================================ ::: info Prerequisites Before reading this article, you need to learn: - [Principles of Queue/Stack](https://labuladong.online/en/algo/data-structure-basic/queue-stack-basic/) ::: The calculator we want to build will have these features: 1. You input a string. It can have `+ - * /`, numbers, parentheses, and spaces. Your algorithm returns the result. 2. The calculation follows the normal math rules. Parentheses have the highest priority. Multiply and divide come before add and subtract. 3. Division is integer division. No matter positive or negative, round toward zero (5/2=2, -5/2=-2). 4. You can assume the input is always valid. There will not be integer overflow, and no division by zero. For example, if you input this string, the algorithm will return 9: ```java 3 * (2 - 6 / (3 - 7)) = 3 * (2 - 6 / (-4)) = 3 * (2 - (-1)) = 9 ``` As you can see, this is already very close to a real calculator. Although we all have used calculators before, if you think about how to implement its algorithm, it is not that easy: 1. To handle parentheses, you must calculate the innermost ones first, then simplify step by step. Even when doing it by hand, it's easy to make mistakes. Writing it as an algorithm is even harder. 2. You must handle multiplication and division before addition and subtraction. This is not so hard for kids to learn, but it is tricky for computers. 3. You need to deal with spaces. For better readability, we often put spaces between numbers and operators. But during calculation, these spaces should be ignored. I remember many data structure textbooks use calculators as an example when explaining stacks. But honestly, the explanation is not good. Many future computer scientists may have been discouraged by such a simple data structure. So in this article, let's talk about how to build a fully functional calculator. **The key is to break down the problem step by step, solve each part one by one.** With a few simple algorithm rules, you can handle very complex calculations. I believe this way of thinking can help you solve many hard problems. Let's start by breaking down the problem, beginning with the simplest one. ## 1. String to Integer Yes, this is a simple problem. First, tell me how to convert a string that represents a **positive integer** into an int type? ```java String s = "458"; int n = 0; for (int i = 0; i < s.length(); i++) { char c = s.charAt(i); n = 10 * n + (c - '0'); } // n is now equal to 458 ``` This is very simple and a common routine. But even for such a simple problem, there is a pitfall: **You cannot remove the parentheses in `(c - '0')`, or it may cause integer overflow**. Because the variable `c` is an ASCII code. Without parentheses, addition may happen before subtraction. If `s` is close to INT_MAX, this can cause overflow. So, use parentheses to make sure subtraction happens first. ## 2. Handling Addition and Subtraction Now, let's move on. **If the input formula only contains addition and subtraction, and there are no spaces**, how can you calculate the result? Let's use the string `1-12+3` as an example: 1. Add a default `+` sign before the first number, so it becomes `+1-12+3`. 2. Combine each operator and number into a pair. For example: `+1`, `-12`, `+3`. Convert them to numbers, then put them into a stack. 3. Add up all the numbers in the stack. The sum is the result of the formula. Let's look at the code and a picture to make it clear: ```java int calculate(String s) { Stack stk = new Stack<>(); // record the numbers in the expression int num = 0; // record the sign before num, initialize as + char sign = '+'; for (int i = 0; i < s.length(); i++) { char c = s.charAt(i); // if it's a digit, continuously read it into num if (Character.isDigit(c)) { num = 10 * num + (c - '0'); } // if it's not a digit, it means we encountered the next sign or the end of the expression // then the previous number and sign should be pushed into the stack if (c == '+' || c == '-' || i == s.length() - 1) { switch (sign) { case '+': stk.push(num); break; case '-': stk.push(-num); break; } // update the sign to the current one, reset num to zero sign = c; num = 0; } } // sum up all the results in the stack to get the answer int res = 0; while (!stk.isEmpty()) { res += stk.pop(); } return res; } ``` Maybe the part with the `switch` statement is a bit hard to understand. `i` scans from left to right, and `sign` and `num` follow it. When `s[i]` meets an operator, here's what happens: ![](../pictures/calculator/1.jpg) Now, use `sign` to decide if the number is positive or negative, put it into the stack, then update `sign` and reset `num` to get ready for the next pair. Also, not only when you meet a new operator should you push to the stack. When `i` reaches the end of the string (`i == s.size() - 1`), you should also put the last number into the stack for the final calculation. ![](../pictures/calculator/2.jpg) That's it. This is the algorithm for compact addition and subtraction strings. Make sure you understand this part. The next steps just change this base framework a bit. ## 3. Handling Multiplication and Division The idea is almost the same as just handling addition and subtraction. Let's use the string `2-3*4+5` as an example. The core idea is still to break the string into pairs of symbols and numbers. For example, this can be split into `+2`, `-3`, `*4`, `+5`. Before, we didn't handle multiplication and division. That's easy. **Everything else stays the same**. Just add the cases for multiplication and division in the `switch` part: ```java int calculate(String s) { Stack stk = new Stack<>(); // record the numbers in the formula int num = 0; // record the sign before num, initialized as + char sign = '+'; for (int i = 0; i < s.length(); i++) { char c = s.charAt(i); if (Character.isDigit(c)) { num = 10 * num + (c - '0'); } if (c == '+' || c == '-' || c == '/' || c == '*' || i == s.length() - 1) { int pre; switch (sign) { case '+': stk.push(num); break; case '-': stk.push(-num); break; // just take out the previous number and perform corresponding operation case '*': pre = stk.pop(); stk.push(pre * num); break; case '/': pre = stk.pop(); stk.push(pre / num); break; } // update the sign to the current sign, reset the number to zero sign = c; num = 0; } } // sum up all the results in the stack to get the answer int res = 0; while (!stk.isEmpty()) { res += stk.pop(); } return res; } ``` ![](../pictures/calculator/3.jpg) **Multiplication and division have higher priority than addition and subtraction. This means you combine them with the top number in the stack, while addition and subtraction just push their number into the stack.** Now let's think about spaces in the string. In our code, there is no need to handle spaces specially. Look at the `if` conditions: if the character `c` is a space, we just skip it. Now, our algorithm can calculate addition, subtraction, multiplication, and division correctly, and automatically ignores spaces. The last step is to make the algorithm handle parentheses correctly. ## 4. Handling Parentheses Handling parentheses in expressions looks hard, but it is not as difficult as it seems. Let's first make a small change to our previous code: ```java int calculate(String s) { return _calculate(s, 0, s.length() - 1); } // Definition: return the calculation result of the expression within s[start..end] int _calculate(String s, int start, int end) { // need to convert the string into a double-ended queue for easy operation Stack stk = new Stack<>(); // record the numbers in the formula int num = 0; // record the sign before num, initialized as + char sign = '+'; for (int i = start; i <= end; i++) { char c = s.charAt(i); if (Character.isDigit(c)) { num = 10 * num + (c - '0'); } if (c == '+' || c == '-' || c == '/' || c == '*' || i == s.length() - 1) { int pre; switch (sign) { case '+': stk.push(num); break; case '-': stk.push(-num); break; // just take out the previous number for the corresponding operation case '*': pre = stk.pop(); stk.push(pre * num); break; case '/': pre = stk.pop(); stk.push(pre / num); break; } // update the sign to the current sign, and reset the number to zero sign = c; num = 0; } } // sum all results in the stack to get the answer int res = 0; while (!stk.isEmpty()) { res += stk.pop(); } return res; } ``` Here we define a new function `_calculate`, which takes three arguments: the string `s`, and the left and right boundaries of the string, `start` and `end`. In this way, we can calculate the value of any sub-expression in `s`. So, why is handling parentheses not that hard? **Because parentheses have a recursive property.** Let's use the string `3*(4-5/2)-6` as an example: ```java calculate(3 * (4 - 5/2) - 6) = 3 * calculate(4 - 5/2) - 6 = 3 * 2 - 6 = 0 ``` As you can see, no matter how many nested parentheses there are, we can always use the `_calculate` function to call itself recursively, and get the result inside the parentheses. **In other words, we can just treat the expression inside the parentheses as a single number.** Now, the question is, when I see a left parenthesis `(`, how do I know where the matching right parenthesis `)` is? Here, we need a stack. We can scan `s` first, and record the position of each left parenthesis's matching right parenthesis. Let's look at the code. Based on the `_calculate` function above, we add some extra logic: ```java class Solution { public int calculate(String s) { // key is the index of the left parenthesis, value is the corresponding index of the right parenthesis Map rightIndex = new HashMap<>(); // use stack structure to find the corresponding parentheses Stack stack = new Stack<>(); for (int i = 0; i < s.length(); i++) { if (s.charAt(i) == '(') { stack.push(i); } else if (s.charAt(i) == ')') { rightIndex.put(stack.pop(), i); } } return _calculate(s, 0, s.length() - 1, rightIndex); } // Definition: return the result of the expression within s[start..end] private int _calculate(String s, int start, int end, Map rightIndex) { // need to convert the string to a deque for easy operation Stack stk = new Stack<>(); // record the number in the formula int num = 0; // record the sign before num, initialized to + char sign = '+'; for (int i = start; i <= end; i++) { char c = s.charAt(i); if (Character.isDigit(c)) { num = 10 * num + (c - '0'); } if (c == '(') { // recursively calculate the expression inside the parentheses num = _calculate(s, i + 1, rightIndex.get(i) - 1, rightIndex); i = rightIndex.get(i); } if (c == '+' || c == '-' || c == '*' || c == '/' || i == end) { int pre; switch (sign) { case '+': stk.push(num); break; case '-': stk.push(-num); break; // just take out the previous number and perform the corresponding operation case '*': pre = stk.pop(); stk.push(pre * num); break; case '/': pre = stk.pop(); stk.push(pre / num); break; } // update the sign to the current sign, reset the number to zero sign = c; num = 0; } } // sum all results in the stack to get the answer int res = 0; while (!stk.isEmpty()) { res += stk.pop(); } return res; } } ``` ![](../pictures/calculator/4.jpg) ![](../pictures/calculator/5.jpg) ![](../pictures/calculator/6.jpg) See, with just a few more lines of code, we can handle parentheses. This is the power of recursion. Now, our calculator can handle all the required functions. By breaking down the problem step by step, it doesn't look so hard after all. ## 5. Summary Through building a calculator, this article mainly shows a way to solve complex problems. We started from converting a string to a number, then handled expressions with only plus and minus, then added multiplication and division, then handled spaces, and finally handled parentheses. For difficult problems, solutions are usually not done all at once, but step by step, getting better each time. If you see the original problem and don't know how to do it, that's normal. The key is to break down the problem and solve smaller parts first. After understanding the calculator algorithm, **you can save this code as a general calculator**. In other algorithm problems, if you need to calculate the value of an expression, you can use this code directly. You don't need to write it from scratch. ================================================ FILE: data-structures/dijkstra.md ================================================ ::: info Prerequisite Knowledge Before reading this article, you should first learn: - [Graph Basics and General Implementation](https://labuladong.online/en/algo/data-structure-basic/graph-basic/) - [Overview of Shortest Path Algorithms in Graphs](https://labuladong.online/en/algo/data-structure-basic/graph-shortest-path/) - [DFS/BFS Traversal in Graph Structures](https://labuladong.online/en/algo/data-structure-basic/graph-traverse-basic/) ::: ::: important Summary in One Sentence Dijkstra's algorithm is used to find the shortest path from one node to all others in a graph. **It is basically the standard BFS algorithm plus a greedy idea.** If the graph has negative weight edges, the greedy idea will not work, so **Dijkstra's algorithm can only handle graphs without negative weight edges**. There are only two differences between Dijkstra's algorithm and standard BFS: 1. Standard BFS uses a regular queue. Dijkstra uses a priority queue, so nodes closer to the start are processed first (this is the greedy idea). 2. Standard BFS uses a `visited` array to record visited nodes to avoid getting stuck in a loop. Dijkstra uses a `distTo` array which also avoids loops and saves the shortest path from the start to every node. ::: In [Overview of Shortest Path Algorithms in Graphs](https://labuladong.online/en/algo/data-structure-basic/graph-shortest-path/), we gave a brief introduction to shortest path problems, the difference between single-source and multi-source shortest paths, the problem with negative weight edges, and some classic shortest path algorithms including Dijkstra. In this article, we will cover: 1. How to build Dijkstra's algorithm based on the standard [Graph BFS Traversal Algorithm](https://labuladong.online/en/algo/data-structure-basic/graph-traverse-basic/). 2. Detailed explanation of how Dijkstra's algorithm works and why it is correct. 3. The shortest path between two points is a special case of the single-source shortest path. We can slightly change Dijkstra's algorithm to make it faster. After you understand the logic and code, the next article will show how to change standard Dijkstra code to solve harder shortest path problems with more conditions. We will use the graph interfaces defined in [Graph Basics and General Implementation](https://labuladong.online/en/algo/data-structure-basic/graph-basic/). Please read that part first to know how graphs are stored and defined. Let's start. ## Dijkstra Algorithm Code Let's start with the code. We only need to make a few changes to the standard BFS algorithm to get Dijkstra, so it's simple. We will first see what changes are needed, then explain why they work. In [Graph BFS Traversal Algorithm](https://labuladong.online/en/algo/data-structure-basic/graph-traverse-basic/), we showed three ways to write BFS. The third one is best for Dijkstra, because we need to track the shortest path from the start to each visited node. Here is the standard graph BFS traversal algorithm: ```java // BFS traversal of a graph, starting from node s and recording the number of steps (the number of edges from the start node s to the current node) // Each node maintains its own State class to record the number of steps from s class State { // Current node ID int node; // Number of steps from the start node s to the current node int step; public State(int node, int step) { this.node = node; this.step = step; } } void bfs(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 cur = state.node; int step = state.step; System.out.println("visit " + cur + " with step " + step); for (Edge e : graph.neighbors(cur)) { if (visited[e.to]) { // [!code highlight:5] continue; } q.offer(new State(e.to, step + 1)); visited[e.to] = true; } } } ``` In this algorithm, we use `State.step` to record the least number of steps (edges) from the start to the current node, and use a `visited` array so each node is visited only once (enters and leaves the queue only once), to avoid loops. In a weighted graph, the shortest path problem wants to find the smallest total weight from the start to each node. Because edges can have different weights, the least step count used above does not help and cannot give the path with the smallest total weight. Here is the code for Dijkstra's algorithm. The differences are highlighted below: ```java class State { // Current node ID int node; // Minimum path weight from the start node s to the current node int distFromStart; public State(int node, int distFromStart) { this.node = node; this.distFromStart = distFromStart; } } // Input the weighted graph (without negative weight edges) graph and the starting node src // Return the minimum path weight from the starting node src to other nodes int[] dijkstra(Graph graph, int src) { // Record the minimum path weight from the starting node src to other nodes // distTo[i] represents the minimum path weight from the starting node src to node i int[] distTo = new int[graph.size()]; // Initialize all values to positive infinity, representing not calculated Arrays.fill(distTo, Integer.MAX_VALUE); // Priority queue, nodes with smaller distFromStart are in front Queue pq = new PriorityQueue<>((a, b) -> { return a.distFromStart - b.distFromStart; }); // Start BFS from the starting node src 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) { // [!code highlight:5] // In Dijkstra's algorithm, the queue may contain duplicate nodes state // So it is necessary to check when the element leaves the queue to remove the worse duplicate nodes continue; } for (Edge e : graph.neighbors(curNode)) { int nextNode = e.to; int nextDistFromStart = curDistFromStart + e.weight; if (distTo[nextNode] <= nextDistFromStart) { continue; } // [!code highlight:6] // Add nextNode node to the priority queue pq.offer(new State(nextNode, nextDistFromStart)); // Record the minimum path weight from the starting node to the nextNode node distTo[nextNode] = nextDistFromStart; } } return distTo; } ``` ## Key Differences Between Dijkstra and BFS The logic of Dijkstra’s algorithm is almost the same as the standard BFS algorithm. The main changes are: 1. Add a `distFromStart` field to the `State` class. This records the total path weight from the starting node to the current node. Use a [priority queue](https://labuladong.online/en/algo/data-structure-basic/binary-heap-basic/) instead of a normal queue, so the node with the smallest `distFromStart` is processed first. 2. Use a `distTo` array instead of a `visited` array. In standard BFS, a node enters the queue only if `visited[node] == false`. In Dijkstra’s algorithm, a node state enters the queue only if it can make `distTo[node]` smaller. 3. When removing an element from the queue, prune if `distTo[curNode] < curDistFromStart`. **First, let’s talk about why we use a priority queue.** It is mainly to make the algorithm faster. The speed of the algorithm depends on the total number of nodes that enter the queue—the fewer nodes, the faster the search. With Dijkstra, only nodes with `state.distFromStart` less than `distTo[state.node]` are allowed into the queue: ```java if (distTo[nextNode] <= nextDistFromStart) { continue; } // Only allow nodes with nextDistFromStart < distTo[nextNode] into the queue pq.offer(new State(nextNode, nextDistFromStart)); distTo[nextNode] = nextDistFromStart; ``` Using a priority queue means nodes with the smallest `state.distFromStart` are removed first, which usually have the smallest `distTo[state.node]`, helping avoid many non-optimal node states and making the search more efficient. **Second, why do we use the `distTo` array instead of the `visited` array?** In standard BFS, the path weight is equal to the number of edges because every edge has a weight of 1. So, the first time you reach a node, you have found the shortest path. But in a weighted graph, the path weight and the number of edges may be different. A node reached early may have a high path weight, and a node reached later may have a lower path weight. So, we need a `distTo` array to record the shortest known path weight to each node. If we find a shorter path, we update the value of `distTo[node]`. **Third, the most important difference: why do we prune when popping nodes from the queue?** ```java // When removing a node from the queue, prune int curNode = state.node; int curDistFromStart = state.distFromStart; if (distTo[curNode] < curDistFromStart) { continue; } ``` The main reason is that Dijkstra’s queue can contain multiple states for the same node. Here’s an example. Suppose node `1` is the start. On the first step, add `State(4, 2)` and `State(3, 7)` to the priority queue (for paths `1->4` and `1->3`). Update `distTo[4] = 2` and `distTo[3] = 7`: ![](../pictures/dijkstra/d1.jpg) Then `State(4, 2)` is removed, finding the shortest path `1->4`. The priority queue still has `State(3, 7)`. Next, add the neighbors of node `4` to the queue. If there is an edge `4->3`, as shown below: ![](../pictures/dijkstra/d5.jpg) Now, `State(3, 3)` will go into the queue and update `distTo[3] = 3`: Notice there are now two ways to reach node `3`—paths `1->4->3` and `1->3`, represented as `State(3, 3)` and `State(3, 7)`. `State(3, 3)` comes out first, giving the shortest path `1->4->3` and pushes its neighbors into the queue. When `State(3, 7)` is removed, since `distTo[3] < 7`, we skip it and stop searching that path. **So we must prune when removing nodes from the queue. Otherwise, the algorithm might search the same node many times and get stuck.** This example also gives us two key observations: - **When a node enters the queue, even though `distTo[node]` is updated, this value may not be the final shortest path. There could be a shorter path later.** - **When a node comes out of the queue, `state.distFromStart` may not be the shortest path, but `distTo[state.node]` is guaranteed to be the shortest path.** ## Complexity Analysis Suppose the input graph has $V$ nodes and $E$ edges. Let's use the [time and space complexity analysis method](https://labuladong.online/en/algo/essential-technique/complexity-analysis/) to analyze the time and space complexity of Dijkstra's algorithm. The space complexity of Dijkstra's algorithm depends on the number of states stored in the `distTo` array and the priority queue `pq`. To analyze the time complexity, you need to understand that although the code seems to have nested for/while loops, the real focus should be on the enqueue and dequeue operations of `pq`. The time complexity depends on how many states are stored in `pq` and how many operations you do. So, the key question is: how many node states can be stored in `pq` at most? We discussed before that there may be duplicate node states in the queue, so it's not just $O(V)$ states. If you think carefully about the example above, node `3` appears twice in the queue. This is because its [in-degree](https://labuladong.online/en/algo/data-structure-basic/graph-basic/) is 2, which means there are 2 edges pointing to node `3`: ![](../pictures/dijkstra/d5.jpg) In other words, the number of elements in `pq` is not proportional to the number of nodes $V$, but to the number of edges $E$. So, in the worst case, `pq` may hold $O(E)$ states. In summary, `pq` can store up to $O(E)$ node states. Each enqueue and dequeue operation takes $O(\log{E})$ time. So, the **overall time complexity is $O(E\log{E})$, and the space complexity is $O(V + E)$**. ::: note Note If you check other sources, you may see that Dijkstra's algorithm has a time complexity of $O(E\log{V})$ and space complexity of $O(V)$. That's because, ideally, `pq` only holds up to $V$ nodes, and there are $O(E)$ operations on `pq`. So, the overall time complexity is $O(E\log{V})$ and space complexity is $O(V)$. However, there are many ways to implement Dijkstra's algorithm. Different programming languages or different data structure APIs may change the complexity a bit. For example, the Dijkstra algorithm in this article uses a PriorityQueue which is implemented by a binary heap. It only provides enqueue and dequeue APIs, but does not support updating elements in the queue directly. So duplicate node states can appear, and up to $E$ elements may be in the queue. The above complexity analysis is for your reference. ::: ## Optimization for Point-to-Point Shortest Path The Dijkstra algorithm code above computes the shortest path from the start node `src` to all other nodes. In some problems, you only need to find the shortest path from `src` to a specific node `dst`. The code above can still solve this problem, but with a small change, the algorithm can be a bit faster. As mentioned above, when a node State is dequeued from `pq`, `distTo[state.node]` is its final shortest path length. So, you can add a check when dequeuing: if `state.node == dst`, then exit early: ```java // Calculate shortest path weight sum from src to dst int dijkstra(Graph graph, int src, int dst) { while (!pq.isEmpty()) { State state = pq.poll(); int curNode = state.node; int curDistFromStart = state.distFromStart; if (distTo[curNode] < curDistFromStart) { continue; } // Check when dequeuing; return when reaching destination if (curNode == dst) { return curDistFromStart; } ... } return -1; } ``` This optimization can improve the real running speed of the algorithm, but from a theoretical point of view, it doesn't change the complexity. The time complexity is still $O(E\log{E})$, and the space complexity is still $O(V + E)$. ## Summary The Dijkstra algorithm in this article has time complexity $O(E\log{E})$ and space complexity $O(V + E)$. The core idea of Dijkstra is BFS + greedy. With the help of a priority queue, the first node dequeued has the smallest distance from the start node. The greedy idea works because: when you add an edge to the path, the total weight should increase. If the graph has any negative weight edges, this condition fails and the greedy idea no longer works. So Dijkstra's algorithm also does not work for graphs with negative edges. In the next article, [Dijkstra Algorithm Extensions](https://labuladong.online/en/algo/data-structure/dijkstra-follow-up/), I will introduce Dijkstra's algorithm with more constraints. In the [Dijkstra Practice Problems](https://labuladong.online/en/algo/problem-set/dijkstra/), you will practice using Dijkstra to solve some interesting problems. ================================================ FILE: data-structures/monotonic-queue.md ================================================ ::: info Prerequisite Knowledge Before reading this article, you should first learn: - [Basic Array](https://labuladong.online/en/algo/data-structure-basic/array-basic/) - [Basic Linked List](https://labuladong.online/en/algo/data-structure-basic/linkedlist-basic/) - [Basic Queue/Stack](https://labuladong.online/en/algo/data-structure-basic/queue-stack-basic/) ::: In a previous article [Solving Three Algorithm Problems Using Monotonic Stack](https://labuladong.online/en/algo/data-structure/monotonic-stack/), we introduced the special data structure called monotonic stack. Here, we will discuss a similar data structure called "monotonic queue." You might not have heard of this data structure before. Essentially, it is just a "queue" that uses a clever method to ensure that the elements in the queue are all monotonically increasing (or decreasing). Why invent a structure like the "monotonic queue"? It is mainly to solve the following scenario: **Given an array `window` with a known extreme value `A`, if you add a number `B` to `window`, you can immediately calculate the new extreme value by comparing `A` and `B`. However, if you remove a number from `window`, you cannot directly obtain the extreme value. If the removed number happens to be `A`, you need to traverse all elements in `window` to find the new extreme value.** This scenario is common, but it seems that a monotonic queue is not necessary. For example, a [priority queue (binary heap)](https://labuladong.online/en/algo/data-structure-basic/binary-heap-basic/) is specifically designed for dynamically finding extreme values. By creating a max (min) heap, you can quickly get the maximum (minimum) value, right? For simply maintaining the extreme values, a priority queue is professional, with the head of the queue being the extreme value. However, a priority queue cannot satisfy the standard "first-in, first-out" time sequence of a queue structure. This is because a priority queue uses a binary heap to dynamically sort elements, and the order of dequeue is determined by the element size, having no relation to the order of enqueue. Thus, a new queue structure is needed that can both maintain the "first-in, first-out" time sequence of queue elements and correctly maintain the extreme values of all elements in the queue, which is the "monotonic queue" structure. The "monotonic queue" is mainly used to assist in solving problems related to sliding windows. In the previous article [Core Framework of Sliding Window](https://labuladong.online/en/algo/essential-technique/sliding-window-framework/), the sliding window algorithm is explained as a part of the double pointer technique. However, some slightly more complex sliding window problems cannot be solved with just two pointers and require more advanced data structures. For instance, in the previous article [Core Framework of Sliding Window](https://labuladong.online/en/algo/essential-technique/sliding-window-framework/), for each problem discussed, whenever the window is expanded (`right++`) or contracted (`left++`), you can decide whether to update the answer based solely on the elements entering and leaving the window. But in the example at the beginning of this article about determining the extreme value in a window, you cannot update the extreme value of the window based solely on the element leaving the window, unless you traverse all elements again, which increases the time complexity, something we do not want to see. Let's look at LeetCode Problem 239 [Sliding Window Maximum](https://leetcode.com/problems/sliding-window-maximum/), which is a standard sliding window problem: Given an array `nums` and a positive integer `k`, there is a window of size `k` that slides from left to right across `nums`. Please output the maximum value of `k` elements in the window each time. The function signature is as follows: ```java int[] maxSlidingWindow(int[] nums, int k); ``` For example, here is a sample problem provided by LeetCode: ``` Input: nums = [1,3,-1,-3,5,3,6,7], k = 3 Output: [3,3,5,5,6,7] Explanation: Position of the sliding window Maximum value --------------- ----- [1 3 -1] -3 5 3 6 7 3 1 [3 -1 -3] 5 3 6 7 3 1 3 [-1 -3 5] 3 6 7 5 1 3 -1 [-3 5 3] 6 7 5 1 3 -1 -3 [5 3 6] 7 6 1 3 -1 -3 5 [3 6 7] 7 ``` Next, we will use a monotonic queue structure to calculate the maximum value in each sliding window in $O(1)$ time, allowing the entire algorithm to be completed in linear time. ### 1. Building the Solution Framework Before introducing the API of the "monotonic queue" data structure, let's compare the standard API of a [regular queue](https://labuladong.online/en/algo/data-structure-basic/queue-stack-basic/) with the API implemented by the monotonic queue: ```java // API for a regular queue class Queue { // enqueue operation, add element n to the end of the queue void push(int n); // dequeue operation, remove the front element of the queue void pop(); } // API for a monotonic queue class MonotonicQueue { // add element n to the end of the queue void push(int n); // return the maximum value currently in the queue int max(); // if the front element is n, remove it void pop(int n); } ``` Of course, the implementation of these APIs for a monotonic queue is different from a regular Queue, but for now, let's assume these operations have a time complexity of O(1) and first set up the solution framework for this "sliding window" problem: ```java 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) { // first fill the window with the first k - 1 elements window.push(nums[i]); } else { // the window starts sliding forward // move in the new element window.push(nums[i]); // record the maximum element in the current window to the result res.add(window.max()); // remove the last element window.pop(nums[i - k + 1]); } } // convert the List to an int[] array as the return value int[] arr = new int[res.size()]; for (int i = 0; i < res.size(); i++) { arr[i] = res.get(i); } return arr; } ``` ![](../pictures/monotonic-queue/1.png) This idea is quite simple, right? Now let's start with the main part, the implementation of the monotonic queue. ### 2. Implementing the Monotonic Queue Data Structure By observing the sliding window process, we can see that implementing a "monotonic queue" requires a data structure that supports insertion and deletion at both the head and tail. Clearly, a [doubly linked list](https://labuladong.online/en/algo/data-structure-basic/linkedlist-basic/) meets this requirement. The core idea of the "monotonic queue" is similar to the "monotonic stack." The `push` method still adds elements at the queue's tail, but it removes elements in front that are smaller than itself: ```java class MonotonicQueue { // doubly linked list, supports quick insertion and deletion at both head and tail // maintain elements in a monotonically increasing order from tail to head private LinkedList maxq = new LinkedList<>(); // add an element n at the tail, maintaining the monotonic property of maxq public void push(int n) { // remove all elements smaller than itself from the front while (!maxq.isEmpty() && maxq.getLast() < n) { maxq.pollLast(); } maxq.addLast(n); } } ``` Imagine that the size of a number represents a person's weight. A heavier weight will flatten the lighter ones in front of it until it encounters a weight of greater magnitude. ![](../pictures/monotonic-queue/3.png) If each element is processed in this manner when added, the elements in the monotonic queue will maintain a **monotonically decreasing** order. This makes our `max` method straightforward to implement: simply return the front element of the queue. The `pop` method also operates on the front of the queue. If the front element is the element `n` to be removed, then delete it: ```java class MonotonicQueue { // to save space, the previous code section is omitted... public int max() { // the element at the front of the queue is definitely the largest return maxq.getFirst(); } public void pop(int n) { if (n == maxq.getFirst()) { maxq.pollFirst(); } } } ``` The reason the `pop` method checks if `n == maxq.getFirst()` is that the front element `n` we want to remove might have been "flattened" during the `push` process and may no longer exist. In this case, we don't need to remove it: ![](../pictures/monotonic-queue/2.png) With this, the design of the monotonic queue is complete. Let's look at the full solution code: ```java class Solution { // implementation of the monotonic queue class MonotonicQueue { LinkedList q = new LinkedList<>(); public void push(int n) { // remove all elements smaller than n while (!q.isEmpty() && q.getLast() < n) { /** ![](../pictures/monotonic-queue/3.png) */ q.pollLast(); } // then add n to the end q.addLast(n); } public int max() { return q.getFirst(); } public void pop(int n) { if (n == q.getFirst()) { q.pollFirst(); } } } // implementation of the solution function 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) { // first fill the window with the first k - 1 elements window.push(nums[i]); } else { /** ![](../pictures/monotonic-queue/1.png) */ // slide the window forward and add the new number window.push(nums[i]); // record the maximum value of the current window res.add(window.max()); // remove the old number window.pop(nums[i - k + 1]); } } // need to convert to int[] array before returning int[] arr = new int[res.size()]; for (int i = 0; i < res.size(); i++) { arr[i] = res.get(i); } return arr; } } ``` Do not overlook some detailed issues. When implementing `MonotonicQueue`, we used Java's `LinkedList` because the linked list structure supports fast insertion and deletion of elements at both the head and tail. Meanwhile, the `res` in the solution code uses the `ArrayList` structure, as elements will be accessed by index later, making the array structure more suitable. Pay attention to these details when implementing in other languages. Regarding the time complexity of the monotonic queue API, readers might be puzzled: the `push` operation contains a while loop, so the worst-case time complexity should be $O(N)$, and with an additional for loop, the algorithm's time complexity should be $O(N^2)$, right? Here, we apply amortized analysis as mentioned in the [Guide to Algorithm Time and Space Complexity Analysis](https://labuladong.online/en/algo/essential-technique/complexity-analysis/): Looking at the `push` operation alone, the worst-case time complexity is indeed $O(N)$, but the average time complexity is $O(1)$. Typically, we use average complexity rather than the worst-case complexity to measure API interfaces, so the overall time complexity of this algorithm is $O(N)$, not $O(N^2)$. Alternatively, analyzing from an overall perspective: the algorithm essentially involves adding and removing each element in `nums` from the `window` **at most once**. It is impossible to repeatedly add and remove the same element, so the overall time complexity is $O(N)$. The space complexity is easy to analyze, which is the size of the window $O(k)$. ### Further Exploration Finally, I pose a few questions for you to consider: 1. The `MonotonicQueue` class provided in this article only implements the `max` method. Can you add a `min` method to return the minimum value of all elements in the queue in $O(1)$ time? 2. The `pop` method of the `MonotonicQueue` class provided in this article still requires a parameter, which is not so elegant and goes against the standard queue API. Please fix this defect. 3. Implement the `size` method of the `MonotonicQueue` class to return the number of elements in the monotonic queue (note that each `push` operation might remove elements from the underlying `q` list, so the number of elements in `q` is not the number of elements in the monotonic queue). In other words, can you implement a general-purpose monotonic queue: ```java // General implementation of a monotonic queue, which can efficiently maintain maximum and minimum values class MonotonicQueue> { // Standard queue API to add an element to the back of the queue public void push(E elem); // Standard queue API to pop an element from the front of the queue, following FIFO order public E pop(); // Standard queue API to return the number of elements in the queue public int size(); // Monotonic queue specific API to compute the maximum value in the queue in O(1) time public E max(); // Monotonic queue specific API to compute the minimum value in the queue in O(1) time public E min(); } ``` I will provide a general implementation of the monotonic queue and classic exercises in [General Implementation and Applications of Monotonic Queue](https://labuladong.online/en/algo/problem-set/monotonic-queue/). For more data structure design problems, see [Classic Exercises on Data Structure Design](https://labuladong.online/en/algo/problem-set/ds-design/). ================================================ FILE: data-structures/monotonic-stack.md ================================================ ::: info Prerequisites Before reading this article, you should first learn: - [Array Basics](https://labuladong.online/en/algo/data-structure-basic/array-basic/) - [Linked List Basics](https://labuladong.online/en/algo/data-structure-basic/linkedlist-basic/) - [Queue/Stack Basics](https://labuladong.online/en/algo/data-structure-basic/queue-stack-basic/) ::: A stack is a very simple data structure. It follows a first-in-last-out order, which fits some problems well, such as the function call stack. A monotonic stack is just a stack, but with some clever logic. After each new element is pushed, the elements in the stack stay ordered (monotonically increasing or decreasing). Sounds a bit like a heap? No. A monotonic stack is not used as widely. It is used for a specific type of problem, such as “next greater element”, “previous smaller element”, and so on. This article explains the monotonic stack template to solve “next greater element” problems, and also talks about how to handle “circular arrays”. Other variants and classic problems will be in the next article: [Monotonic Stack Variants and Classic Problems](https://labuladong.online/en/algo/problem-set/monotonic-stack/). ## Monotonic Stack Template Here is a problem: given an array `nums`, return a result array of the same length. For each index, store the next greater element. If there is no greater element, store -1. The function signature is: ```java int[] calculateGreaterElement(int[] nums); ``` For example, if the input is `nums = [2,1,2,4,3]`, you should return `[4,2,4,-1,-1]`. The first 2 has a next greater element 4; after 1, the next greater element is 2; the second 2 also has 4 as its next greater element; 4 has no greater element after it, so we put -1; 3 also has no greater element after it, so we put -1. The brute-force solution is easy: for each element, scan to the right and find the first greater element. But the time complexity is $O(n^2)$. We can think about the problem like this: imagine the array elements as people standing in a line, and the value is the person’s height. They stand in a row facing you. How do you find the next greater element of the element “2”? Easy: if you can see “2”, then the first person you see behind “2” is its next greater element. Any person shorter than “2” will be blocked by “2”, so the first one who shows up must be taller and is the answer. ![](../pictures/monotonic-stack/1.jpeg) This picture is easy to understand. With this in mind, look at the code: ```java int[] calculateGreaterElement(int[] nums) { int n = nums.length; // array to store the answers int[] res = new int[n]; Stack s = new Stack<>(); // push elements into the stack from the end for (int i = n - 1; i >= 0; i--) { // compare the heights while (!s.isEmpty() && s.peek() <= nums[i]) { // shorter ones step aside, they're blocked anyway... s.pop(); } // the greater element behind nums[i] res[i] = s.isEmpty() ? -1 : s.peek(); s.push(nums[i]); } return res; } ``` This is the standard monotonic stack template. The for loop scans from right to left, because we use a stack. Pushing from right to left is like popping from left to right. The while loop removes elements between two “taller” elements, because those in the middle are useless. If a taller element stands in front of them, they can never be the next greater element for any future element. The time complexity is not very obvious. Seeing a for loop with a nested while loop, you might think it is $O(n^2)$, but it is actually $O(n)$. To analyze it, look at the whole process: there are `n` elements. Each element is pushed onto the stack once, and at most popped once. There is no extra work. So the total work is proportional to `n`, which is $O(n)$. ## Variants The code for a monotonic stack is quite simple. Now let’s look at some concrete problems. ### 496. Next Greater Element I First, an easy variant: LeetCode 496, “[Next Greater Element I](https://leetcode.com/problems/next-greater-element-i/)”: **LeetCode 496. Next Greater Element I** The **next greater element** of some element `x` in an array is the **first greater** element that is **to the right** of `x` in the same array. You are given two **distinct 0-indexed** integer arrays `nums1` and `nums2`, where `nums1` is a subset of `nums2`. For each `0 <= i < nums1.length`, find the index `j` such that `nums1[i] == nums2[j]` and determine the **next greater element** of `nums2[j]` in `nums2`. If there is no next greater element, then the answer for this query is `-1`. Return *an array *`ans`* of length *`nums1.length`* such that *`ans[i]`* is the **next greater element** as described above.* Example 1:** ``` **Input:** nums1 = [4,1,2], nums2 = [1,3,4,2] **Output:** [-1,3,-1] **Explanation:** The next greater element for each value of nums1 is as follows: - 4 is underlined in nums2 = [1,3,4,2]. There is no next greater element, so the answer is -1. - 1 is underlined in nums2 = [1,3,4,2]. The next greater element is 3. - 2 is underlined in nums2 = [1,3,4,2]. There is no next greater element, so the answer is -1. ``` Example 2:** ``` **Input:** nums1 = [2,4], nums2 = [1,2,3,4] **Output:** [3,-1] **Explanation:** The next greater element for each value of nums1 is as follows: - 2 is underlined in nums2 = [1,2,3,4]. The next greater element is 3. - 4 is underlined in nums2 = [1,2,3,4]. There is no next greater element, so the answer is -1. ``` **Constraints:** - `1 <= nums1.length <= nums2.length <= 1000` - `0 <= nums1[i], nums2[i] <= 10^(4)` - All integers in `nums1` and `nums2` are **unique**. - All the integers of `nums1` also appear in `nums2`. **Follow up:** Could you find an `O(nums1.length + nums2.length)` solution? You are given two arrays `nums1` and `nums2`. For each element in `nums1`, find its next greater element in `nums2`. The function signature is: ```java int[] nextGreaterElement(int[] nums1, int[] nums2); ``` We can solve it by slightly changing our previous code. Since `nums1` is a subset of `nums2`, we can first compute the next greater element for every element in `nums2`, and store it in a map. Then, for each element in `nums1`, we just look it up in the map: ```java class Solution { public int[] nextGreaterElement(int[] nums1, int[] nums2) { // record the next greater element for each element in nums2 int[] greater = nextGreaterElement(nums2); // convert to a map: element x -> next greater element of x HashMap greaterMap = new HashMap<>(); for (int i = 0; i < nums2.length; i++) { greaterMap.put(nums2[i], greater[i]); } // nums1 is a subset of nums2, so we can get the result based on greaterMap int[] res = new int[nums1.length]; for (int i = 0; i < nums1.length; i++) { res[i] = greaterMap.get(nums1[i]); } return res; } // calculate the next greater element for each element in nums int[] nextGreaterElement(int[] nums) { int n = nums.length; // array to store the answers int[] res = new int[n]; Stack s = new Stack<>(); // push elements onto the stack in reverse order for (int i = n - 1; i >= 0; i--) { // determine the height (size) of elements while (!s.isEmpty() && s.peek() <= nums[i]) { // remove the shorter elements as they are blocked anyway s.pop(); } // the next greater element after nums[i] res[i] = s.isEmpty() ? -1 : s.peek(); s.push(nums[i]); } return res; } } ``` ### 739. Daily Temperatures Now look at LeetCode 739, “[Daily Temperatures](https://leetcode.com/problems/daily-temperatures/)”: You are given an array `temperatures` that records the temperature of each day. Return an array of the same length that, for each day, tells you how many days you have to wait until a warmer temperature. If there is no future day with a warmer temperature, put 0. The function signature is: ```java int[] dailyTemperatures(int[] temperatures); ``` For example, if `temperatures = [73,74,75,71,69,76]`, the answer is `[1,1,3,2,1,0]`. For day 1 with 73°F, on day 2 it is 74°F, which is warmer. So for day 1, you need to wait 1 day; and so on. This is also a “next greater element” problem. But now we do not return the value of the next greater element, we return the distance between the current index and the index of the next greater element. We use the same idea, apply the monotonic stack template, and make small changes: ```java class Solution { public int[] dailyTemperatures(int[] temperatures) { int n = temperatures.length; int[] res = new int[n]; // Store element indices here, not the elements themselves Stack s = new Stack<>(); // Monotonic stack template for (int i = n - 1; i >= 0; i--) { while (!s.isEmpty() && temperatures[s.peek()] <= temperatures[i]) { s.pop(); } // Get the distance between indices res[i] = s.isEmpty() ? 0 : (s.peek() - i); // Push the index onto the stack, not the element s.push(i); } return res; } } ``` We are done with monotonic stacks. Next we will talk about another key point: how to handle “circular arrays”. ## How to Handle Circular Arrays We still want to find the next greater element. But now the array is circular. How do we handle it? LeetCode 503 “[Next Greater Element II](https://leetcode.com/problems/next-greater-element-ii/)” is this problem: given a *circular array*, compute the next greater element for every element. For example, input `[2,1,2,4,3]`, you should return `[4,2,4,-1,4]`. Because with the circular property, **the last element 3 can loop around and find 4 as its next greater element**. If you have read the [Circular Array Techniques](https://labuladong.online/en/algo/data-structure-basic/cycle-array/) in the basics section, you should be familiar with this idea: we usually use the `%` operator (modulo) to simulate the circular behavior: ```java int[] arr = {1,2,3,4,5}; int n = arr.length, index = 0; while (true) { // Rotate in a circular array print(arr[index % n]); index++; } ``` We still need to use the monotonic stack template to solve this problem. The hard part is: for input `[2,1,2,4,3]`, how can the last element 3 find 4 as its next greater element? **A common trick for this kind of problem is to double the length of the array**: ![](../pictures/monotonic-stack/2.jpeg) This way, element 3 can find element 4 as its next greater element, and other elements can also be computed correctly. With this idea, the simplest way is to really build a new array of double length, then apply the template. But **we do not need to build a new array. We can use circular array tricks to *simulate* the effect of doubling the array**. Let’s go straight to the code: ```java class Solution { public int[] nextGreaterElements(int[] nums) { int n = nums.length; int[] res = new int[n]; Stack s = new Stack<>(); // Double the array length to simulate a circular array for (int i = 2 * n - 1; i >= 0; i--) { // Index i needs to be modulo, the rest is the same as the template while (!s.isEmpty() && s.peek() <= nums[i % n]) { s.pop(); } res[i % n] = s.isEmpty() ? -1 : s.peek(); s.push(nums[i % n]); } return res; } } ``` This nicely solves the circular array problem, with time complexity $O(N)$. To end, think about some questions. The monotonic stack template we used is the `nextGreaterElement` function, which finds the next greater element for each position. But what if the problem asks for the *previous* greater element, or the previous greater or equal element? How should you change the template? Also, in real problems, you are rarely asked to directly compute the next (or previous) greater (or smaller) element. How can you turn a problem into one that can be solved using a monotonic stack? I will compare several variants of the monotonic stack and give classic practice problems in [Monotonic Stack Variants and Exercises](https://labuladong.online/en/algo/problem-set/monotonic-stack/). For more data structure design problems, see [Classic Data Structure Design Exercises](https://labuladong.online/en/algo/problem-set/ds-design/). ================================================ FILE: data-structures/queue-stack.md ================================================ ::: info Prerequisites Before reading this article, you should first learn: - [Array Basics](https://labuladong.online/en/algo/data-structure-basic/array-basic/) - [Linked List Basics](https://labuladong.online/en/algo/data-structure-basic/linkedlist-basic/) - [Queue and Stack Basics](https://labuladong.online/en/algo/data-structure-basic/queue-stack-basic/) ::: A queue is a first-in-first-out (FIFO) data structure. A stack is a last-in-first-out (LIFO) data structure. Here is a simple illustration: ![](../pictures/stack-queue/1.jpg) Both of these data structures are usually implemented using arrays or linked lists. Their APIs make them different. For more details, see [Principles and Implementation of Queue/Stack](https://labuladong.online/en/algo/data-structure-basic/queue-stack-basic/). Today, let's see how to use a stack to build a queue, and how to use a queue to build a stack. ### 1. Implementing a Queue Using Stacks LeetCode Problem 232 "[Implement Queue using Stacks](https://leetcode.com/problems/implement-queue-using-stacks/)" asks us to implement the following API: ```java class MyQueue { // add element to the end of the queue public void push(int x); // remove the element at the front of the queue and return it public int pop(); // return the front element public int peek(); // check if the queue is empty public boolean empty(); } ``` We can use two stacks, `s1` and `s2`, to build a queue. (The diagram below shows how the stacks are arranged for better understanding.) ![](../pictures/stack-queue/2.jpg) When you call `push` to add an element to the queue, just push the element onto `s1`. For example, if you `push` three elements 1, 2, and 3, the stacks look like this: ![](../pictures/stack-queue/3.jpg) Now, what if you want to use `peek` to see the front element of the queue? The front of the queue should be 1, but in `s1`, the number 1 is at the bottom. This is where `s2` comes in. When `s2` is empty, you can pop all elements from `s1` and push them into `s2`. **Now the elements in `s2` are in the correct queue order (first in, first out).** ![](../pictures/stack-queue/4.jpg) When `s2` has elements, you can just use `pop` on `s2` to remove the oldest element. This is how you get the `pop` operation of a queue. Here is the complete code: ```java class MyQueue { private Stack s1, s2; public MyQueue() { s1 = new Stack<>(); s2 = new Stack<>(); } // Push element x to the back of the queue. public void push(int x) { s1.push(x); } // Removes the element from in front of queue and returns that element. public int pop() { // call peek first to ensure s2 is not empty peek(); return s2.pop(); } // Get the front element. public int peek() { if (s2.isEmpty()) // push elements from s1 to s2 while (!s1.isEmpty()) s2.push(s1.pop()); return s2.peek(); } // Returns whether the queue is empty. public boolean empty() { return s1.isEmpty() && s2.isEmpty(); } } ``` With this method, we use two stacks to make a queue. The key idea is to let the two stacks work together. Now, what is the time complexity of these operations? The `peek` operation is interesting. When you call `peek`, it may trigger a `while` loop, making the time complexity O(N). But in most cases, the `while` loop is not triggered, so the time complexity is O(1). The `pop` operation also calls `peek`, so its time complexity is the same as `peek`. In this situation, we can say the **worst-case time complexity** is O(N), because the `while` loop **might** need to move all elements from `s1` to `s2`. But the **amortized time complexity** is O(1). Each element will be moved at most once, so on average, every `peek` operation takes O(1) time per element. For more on analyzing time complexity, see [Practical Analysis of Time and Space Complexity](https://labuladong.online/en/algo/essential-technique/complexity-analysis/). ### 2. Implementing a Stack Using a Queue If using two stacks to make a queue is clever, then using a queue to make a stack is more simple and direct. You only need one queue as the base data structure. LeetCode Problem 225 “[Implement Stack using Queues](https://leetcode.com/problems/implement-stack-using-queues/)” asks us to build these APIs: ```java class MyStack { // add element to the top of the stack public void push(int x); // remove the top element of the stack and return it public int pop(); // return the top element of the stack public int top(); // check if the stack is empty public boolean empty(); } ``` Let's start with the `push` API. Just add the element to the queue and record the last element of the queue. The last element is like the top of the stack. If you want to use `top` to see the top element, you can return it directly: ```java class MyStack { Queue q = new LinkedList<>(); int top_elem = 0; // add element to the top of the stack public void push(int x) { // x is the tail of the queue, which is the top of the stack q.offer(x); top_elem = x; } // return the top element of the stack public int top() { return top_elem; } public boolean empty() { return q.isEmpty(); } } ``` Our base data structure is a queue, which is first in, first out. Each time you `pop`, you can only take from the front. But a stack is last in, first out, which means the `pop` API needs to take from the end: ![](../pictures/stack-queue/5.jpg) The solution is simple. Take all the elements from the front of the queue and add them to the end, except the last one. This way, the original last element moves to the front, so you can take it out: ![](../pictures/stack-queue/6.jpg) ```java class MyStack { // To save space, the code above is omitted... // Delete the top element of the stack and return it public int pop() { int size = q.size(); while (size > 1) { q.offer(q.poll()); size--; } // The previous last element of the queue is now at the front return q.poll(); } } ``` There is one small problem. The last element of the queue is moved to the front and removed, but the `top_elem` variable is not updated. We need to make a small change: ```java class MyStack { // To save space, the code given above is omitted... // remove the top element and return it public int pop() { int size = q.size(); // leave the last 2 elements while (size > 2) { q.offer(q.poll()); size--; } // record the new last element top_elem = q.peek(); q.offer(q.poll()); // remove the previous last element return q.poll(); } } ``` Now it works. Here is the complete code: ```java class MyStack { Queue q = new LinkedList<>(); int top_elem = 0; // Push element x onto stack public void push(int x) { // x is the tail of the queue, which is the top of the stack q.offer(x); top_elem = x; } // Removes the element on top of the stack and returns that element public int top() { return top_elem; } // Removes the element on top of the stack public int pop() { int size = q.size(); // leave the last 2 elements in the queue while (size > 2) { q.offer(q.poll()); size--; } // record the new tail element top_elem = q.peek(); q.offer(q.poll()); // remove the previous tail element return q.poll(); } // Returns whether the stack is empty public boolean empty() { return q.isEmpty(); } } ``` It is clear that when you use a queue to make a stack, the `pop` operation takes O(N) time, and the other operations are O(1). In my opinion, using a queue to make a stack is not very interesting, but using two stacks to make a queue is a good thing to learn. ![](../pictures/stack-queue/4.jpg) After moving elements from stack `s1` to `s2`, the elements in `s2` become first in, first out, just like a queue. This is a bit like “two negatives make a positive” and is not easy to think of at first. ================================================ FILE: data-structures/reverse-linked-list.md ================================================ Reversing a singly linked list with iteration is not hard, but the recursive solution is a bit tricky. If we add more difficulty and only reverse part of a linked list, can you solve it with both iteration and recursion? Going further, if you need to reverse the list in groups of k, how would you handle that? This article will go from easy to hard and solve these linked list problems step by step. I will use both recursive and iterative methods, and use visual panels to help you understand. This will strengthen your recursive thinking and your skill in operating linked list pointers. ## Reverse the entire singly linked list On LeetCode, the common structure of a singly linked list is like this: ```java class ListNode { int val; ListNode next; ListNode(int x) { val = x; } } ``` Reversing a singly linked list is a basic algorithm problem. LeetCode 206 “[Reverse Linked List](https://leetcode.com/problems/reverse-linked-list/)” is exactly this problem: **LeetCode 206. Reverse Linked List** Given the `head` of a singly linked list, reverse the list, and return *the reversed list*. Example 1:** ![](https://assets.leetcode.com/uploads/2021/02/19/rev1ex1.jpg) ``` **Input:** head = [1,2,3,4,5] **Output:** [5,4,3,2,1] ``` Example 2:** ![](https://assets.leetcode.com/uploads/2021/02/19/rev1ex2.jpg) ``` **Input:** head = [1,2] **Output:** [2,1] ``` Example 3:** ``` **Input:** head = [] **Output:** [] ``` **Constraints:** - The number of nodes in the list is the range `[0, 5000]`. - `-5000 <= Node.val <= 5000` **Follow up:** A linked list can be reversed either iteratively or recursively. Could you implement both? Now let’s try several methods to solve this problem. ### Iterative solution The standard way to solve this problem is the iterative solution. We operate several pointers to reverse the direction of each node’s `next` pointer. There is no big difficulty, the key is to handle pointer details correctly. Here is the code. With the comments and the visual panel, it should be easy to understand: ```java class Solution { // Reverse the linked list starting from head public ListNode reverseList(ListNode head) { if (head == null || head.next == null) { return head; } // Due to the structure of a singly linked list, at least three pointers are needed to complete the iterative reversal // cur is the current node being traversed, pre is the predecessor node of cur, nxt is the successor node of cur ListNode pre, cur, nxt; pre = null; cur = head; nxt = head.next; while (cur != null) { // Reverse each node cur.next = pre; // Update pointer positions pre = cur; cur = nxt; if (nxt != null) { nxt = nxt.next; } } // Return the head node after reversal return pre; } } ``` You can open the visual panel below, and click the line cur.next = pre many times. Then you can clearly see the reversing process of the singly linked list: ::: tip Small tips for pointer operations The logic of the code above is not complicated, and there is more than one correct way to write it. But when working with pointers, there are some very basic and simple tips that can make your thinking clearer: 1. Once you see an operation like `nxt.next`, you should immediately think: first check whether `nxt` is null, otherwise you may get a null pointer exception. 2. Pay attention to the loop end condition. You must know where each pointer is when the loop ends. Then you can return the correct answer. If you feel it is complicated, draw the simplest case and run the algorithm by hand. For example, in this problem you can draw a list with only two nodes `1->2`. Then you can figure out exactly where each pointer is when the loop finishes. ::: ### Recursive Solution The iterative solution above is a bit tedious because of pointer operations, but the idea is still clear. Now, what if you are asked to reverse a singly linked list using recursion? Do you have any ideas? For beginners, it might be hard to think of a recursive way. That is normal. If you learn the way of thinking in binary tree algorithms later, you may be able to come up with this algorithm yourself. A binary tree is actually an extension of a singly linked list—it's like a "binary linked list". So the recursive thinking for binary trees also works for linked lists. **The key to reversing a linked list recursively is that this problem has a subproblem structure.** For example, suppose you have a singly linked list `1->2->3->4` with `1` as the head. If you ignore the head node `1` and just look at the sublist `2->3->4`, it is still a singly linked list, right? So, your `reverseList` function should be able to reverse any linked list given as input. Can you use this function to reverse the sublist `2->3->4` first, and then think about how to attach `1` to the end of the reversed list `4->3->2`? This way, you will finish reversing the whole list. ```java reverseList(1->2->3->4) = reverseList(2->3->4) -> 1 ``` **This is the idea of "breaking down the problem". Using the definition of the recursive function, we break the original problem into smaller, similar subproblems, and then combine their results to solve the original problem.** There will be special chapters and exercises about this idea later, so we won’t go into more detail here. Let's look at the code for recursively reversing a singly linked list: ```java class Solution { // Definition: Input the head node of a singly linked list, reverse the list, and return the new head node public ListNode reverseList(ListNode head) { if (head == null || head.next == null) { return head; } ListNode last = reverseList(head.next); /** ![](../pictures/reverse-linked-list/3.jpg) */ head.next.next = head; /** ![](../pictures/reverse-linked-list/4.jpg) */ head.next = null; /** ![](../pictures/reverse-linked-list/5.jpg) */ return last; } } ``` This algorithm often shows the beauty and cleverness of recursion. Next, let's explain this code in detail. We will also provide a visual panel so you can explore the recursion yourself. For recursive algorithms that "break down the problem", the most important thing is to be clear about the definition of the recursive function. Specifically, our `reverseList` function is defined as: **Given a node `head`, reverse the list starting from `head` and return the new head of the reversed list.** Once you understand the function definition, let's look at the problem. For example, we want to reverse this list: ![](../pictures/reverse-linked-list/1.jpg) When you call `reverseList(head)`, recursion happens here: ```java ListNode last = reverseList(head.next); ``` Don’t get lost in recursion (your brain can only hold so many stacks!). Instead, use the function definition to understand what this code does: ![](../pictures/reverse-linked-list/2.jpg) After `reverseList(head.next)` finishes, the whole list becomes: ![](../pictures/reverse-linked-list/3.jpg) And according to the function definition, the `reverseList` function returns the new head of the reversed list, which we store in the variable `last`. Now, look at the next line: ```java head.next.next = head; ``` ![](../pictures/reverse-linked-list/4.jpg) Next: ```java head.next = null; return last; ``` ![](../pictures/reverse-linked-list/5.jpg) Amazing! Now the whole linked list is reversed. Recursive code is simple and elegant, but there are two things to pay attention to: 1. The recursive function needs a base case, which is this line: ```java if (head == null || head.next == null) { return head; } ``` This means if the list is empty or only has one node, the reversed result is itself, so just return it. 2. After the list is reversed recursively, the new head is `last`, and the old `head` becomes the last node. Don't forget to set its `next` to null: ```java head.next = null; ``` This way, the whole linked list is reversed. Isn’t it amazing? Here is a visual process of recursive reversal: ::: note Do not get lost in recursion details The visual panel can show all the steps of the recursion, but I do not suggest beginners focus too much on the details. First, use the way of thinking explained above to understand recursion, then use the visual panel to deepen your understanding. ::: ::: note Recursion is less efficient than iteration for linked lists It is worth mentioning that recursion is not very efficient for linked lists. The recursive and iterative solutions both have time complexity O(N), but the iterative one uses O(1) space, while the recursive one needs stack space, so its space complexity is O(N). So, recursion is good for practicing thinking, but for efficiency, iteration is better. ::: ## Reverse the First N Nodes of a Linked List Now let’s implement a function like this: ```java // Reverse the first n nodes of the linked list (n <= length of the list) ListNode reverseN(ListNode head, int n) ``` For example, for the linked list below, if you run `reverseN(head, 3)`: ![](../pictures/reverse-linked-list/6.jpg) ### Iterative Solution The iterative solution is easy to write. You can modify the `reverseList` function we wrote before: ```java 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--; } // At this point, cur is the (n+1)th node, and head is the tail node after reversal head.next = cur; /** ![](../pictures/reverse-linked-list/6.jpg) */ // At this point, pre is the head node after reversal return pre; } ``` ### Recursive Solution The recursive idea is similar to reversing the whole linked list. You just need a small change: ```java // Successor node ListNode successor = null; // Reverse n nodes starting from head, return the new head node ListNode reverseN(ListNode head, int n) { if (n == 1) { // Record the (n+1)th node successor = head.next; return head; } // Starting from head.next, need to reverse the first n-1 nodes ListNode last = reverseN(head.next, n - 1); head.next.next = head; // Connect the reversed head node with the nodes after it head.next = successor; return last; /** ![](../pictures/reverse-linked-list/7.jpg) */ } ``` Main differences: 1. The base case becomes `n == 1`. Reversing one element means it stays the same. **You also need to record the successor node**, which is the `n + 1` node. 2. Before, we set `head.next` to null because after reversing the whole list, the original `head` becomes the last node. Now, after recursion, `head` may not be the last node. So you need to record the successor (`n + 1` node) and connect `head` to it after reversing. ![](../pictures/reverse-linked-list/7.jpg) ## Reverse a Part of a Linked List We can go further. Given a range of indices, reverse that part in the linked list and keep other parts unchanged. LeetCode 92 "[Reverse Linked List II](https://leetcode.com/problems/reverse-linked-list-ii/)" is this problem: **LeetCode 92. Reverse Linked List II** Given the `head` of a singly linked list and two integers `left` and `right` where `left <= right`, reverse the nodes of the list from position `left` to position `right`, and return *the reversed list*. Example 1:** ![](https://assets.leetcode.com/uploads/2021/02/19/rev2ex2.jpg) ``` **Input:** head = [1,2,3,4,5], left = 2, right = 4 **Output:** [1,4,3,2,5] ``` Example 2:** ``` **Input:** head = [5], left = 1, right = 1 **Output:** [5] ``` **Constraints:** - The number of nodes in the list is `n`. - `1 <= n <= 500` - `-500 <= Node.val <= 500` - `1 <= left <= right <= n` **Follow up:** Could you do it in one pass? The function gets index range `[m, n]` (1-based). You only reverse the elements in this range. The function signature: ```java ListNode reverseBetween(ListNode head, int m, int n) ``` ### Iterative Solution The iterative idea is simple. First, find the `m - 1` node, then use the `reverseN` function we wrote before: ```java class Solution { public ListNode reverseBetween(ListNode head, int m, int n) { if (m == 1) { return reverseN(head, n); } // find the predecessor of the m-th node ListNode pre = head; for (int i = 1; i < m - 1; i++) { pre = pre.next; } // start reversing from the m-th node 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--; } // at this point, cur is the (n+1)-th node, head is the tail node after reversal head.next = cur; /** ![](../pictures/reverse-linked-list/6.jpg) */ // at this point, pre is the head node after reversal return pre; } } ``` ### Recursive Solution For the recursive solution, also find the `m - 1` node and use the `reverseN` function. The key is: how to find the `m - 1` node with recursion? If we treat `head` as index 1, we want to reverse from the `m`th node. If we treat `head.next` as index 1, then we should reverse from the `m - 1`th node. For `head.next.next`, it’s from the `m - 2`th node, and so on... This is using recursion to move forward. The code can be written like this: ```java class Solution { public ListNode reverseBetween(ListNode head, int m, int n) { // base case if (m == 1) { return reverseN(head, n); } // advance to the starting point of reversal to trigger the base case head.next = reverseBetween(head.next, m - 1, n - 1); return head; } // successor node ListNode successor = null; // reverse n nodes starting from head, return the new head node ListNode reverseN(ListNode head, int n) { if (n == 1) { // record the (n+1)th node successor = head.next; return head; } ListNode last = reverseN(head.next, n - 1); head.next.next = head; head.next = successor; return last; } } ``` ## Reverse Nodes in k-Group This problem often appears in interviews, and its difficulty on LeetCode is Hard. Let's look at the question: **LeetCode 25. Reverse Nodes in k-Group** Given the `head` of a linked list, reverse the nodes of the list `k` at a time, and return *the modified list*. `k` is a positive integer and is less than or equal to the length of the linked list. If the number of nodes is not a multiple of `k` then left-out nodes, in the end, should remain as it is. You may not alter the values in the list's nodes, only nodes themselves may be changed. Example 1:** ![](https://assets.leetcode.com/uploads/2020/10/03/reverse_ex1.jpg) ``` **Input:** head = [1,2,3,4,5], k = 2 **Output:** [2,1,4,3,5] ``` Example 2:** ![](https://assets.leetcode.com/uploads/2020/10/03/reverse_ex2.jpg) ``` **Input:** head = [1,2,3,4,5], k = 3 **Output:** [3,2,1,4,5] ``` **Constraints:** - The number of nodes in the list is `n`. - `1 <= k <= n <= 5000` - `0 <= Node.val <= 1000` **Follow-up:** Can you solve the problem in `O(1)` extra memory space? With the previous explanations, is this problem really that hard? Actually, if you use the idea of "breaking down the problem" and reuse the `reverseN` function from before, it becomes much easier. ### Idea If you think carefully, you will find that **this problem has a recursive nature**. For example, let's call `reverseKGroup(head, 2)` on a linked list. This means we reverse the list in groups of 2: ![](../pictures/kgroup/1.jpg) If we manage to reverse the first 2 nodes, what about the rest of the nodes? The rest is also a linked list, but smaller in size. This is a smaller subproblem with the same structure. We can move the `head` pointer to the start of the next part of the list, and then call `reverseKGroup(head, 2)` recursively: ![](../pictures/kgroup/2.jpg) Once we find the recursive structure, the general algorithm is clear: **1. First, reverse the first `k` nodes starting from `head`.** Here, we can reuse the `reverseN` function we implemented before. ![](../pictures/kgroup/3.jpg) **2. Use the (`k+1`)th node as `head`, and make a recursive call to `reverseKGroup`.** ![](../pictures/kgroup/4.jpg) **3. Connect the results from the above two steps.** ![](../pictures/kgroup/5.jpg) ### Code With the step-by-step explanation above, the code can be written directly. Here I use the iterative version of the `reverseN` function. You can also use the recursive one if you like: ```java class Solution { public ListNode reverseKGroup(ListNode head, int k) { if (head == null) return null; // interval [a, b) contains k elements to be reversed ListNode a, b; a = b = head; for (int i = 0; i < k; i++) { // if there are less than k elements, no need to reverse if (b == null) return head; b = b.next; } // reverse the first k elements ListNode newHead = reverseN(a, k); // at this point, b points to the head node of the next group to be reversed // recursively reverse the subsequent linked list and connect them a.next = reverseKGroup(b, k); /** ![](../pictures/kgroup/6.jpg) */ return newHead; } // function to reverse the first N nodes implemented above 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--; } head.next = cur; /** ![](../pictures/reverse-linked-list/6.jpg) */ return pre; } } ``` Very quickly, the problem is solved. ## Summary The idea of recursion is a bit harder to understand than iteration. The trick is: do not get lost in recursion, but use clear definitions to write your algorithm. When you face a difficult problem, try to break it into smaller parts. Modify simple solutions to solve harder problems. ================================================ FILE: data-structures/topological-sort.md ================================================ ::: info Prerequisites Before reading this article, you need to learn: - [DFS/BFS Traversal of Graph Structures](https://labuladong.online/en/algo/data-structure-basic/graph-traverse-basic/) - [Cycle Detection in Directed Graphs](https://labuladong.online/en/algo/data-structure/cycle-detection/) ::: ::: important One-Sentence Summary The reverse postorder of a graph's DFS traversal, or the BFS traversal order using an in-degree array, gives you the topological sort result. ::: Topological sort is another classic algorithm in graph theory. Before performing a topological sort, you must first ensure the graph has no cycles. For cycle detection, refer to [Cycle Detection in Directed Graphs](https://labuladong.online/en/algo/data-structure/cycle-detection/). **This article uses specific algorithm problems to implement topological sort using both DFS and BFS approaches**. The BFS solution is somewhat cleaner in terms of code, but the DFS solution helps you better understand the essence of recursive data structure traversal. So in this article, I'll cover the DFS approach first, then the BFS approach. ## Topological Sort (DFS Version) Take a look at LeetCode problem 210, "[Course Schedule II](https://leetcode.cn/problems/course-schedule-ii/)": **LeetCode 210. Course Schedule II** There are a total of `numCourses` courses you have to take, labeled from `0` to `numCourses - 1`. You are given an array `prerequisites` where `prerequisites[i] = [ai, bi]` indicates that you **must** take course `bi` first if you want to take course `ai`. - For example, the pair `[0, 1]`, indicates that to take course `0` you have to first take course `1`. Return *the ordering of courses you should take to finish all courses*. If there are many valid answers, return **any** of them. If it is impossible to finish all courses, return **an empty array**. Example 1:** ``` **Input:** numCourses = 2, prerequisites = [[1,0]] **Output:** [0,1] **Explanation:** There are a total of 2 courses to take. To take course 1 you should have finished course 0. So the correct course order is [0,1]. ``` Example 2:** ``` **Input:** numCourses = 4, prerequisites = [[1,0],[2,0],[3,1],[3,2]] **Output:** [0,2,1,3] **Explanation:** There are a total of 4 courses to take. To take course 3 you should have finished both courses 1 and 2. Both courses 1 and 2 should be taken after you finished course 0. So one correct course order is [0,1,2,3]. Another correct ordering is [0,2,1,3]. ``` Example 3:** ``` **Input:** numCourses = 1, prerequisites = [] **Output:** [0] ``` **Constraints:** - `1 <= numCourses <= 2000` - `0 <= prerequisites.length <= numCourses * (numCourses - 1)` - `prerequisites[i].length == 2` - `0 <= ai, bi < numCourses` - `ai != bi` - All the pairs `[ai, bi]` are **distinct**. This problem is an advanced version of the one in [Cycle Detection](https://labuladong.online/en/algo/data-structure/cycle-detection/). Instead of just determining whether you can complete all courses, it asks you to return a valid course order that ensures all prerequisites are completed before starting each course. The function signature is: ```java int[] findOrder(int numCourses, int[][] prerequisites); ``` Let me first explain the term Topological Sorting. The definitions you find online tend to be very mathematical, so let me just use this diagram from Baidu Baike to give you an intuitive feel: ![](../pictures/topological-sort/top.jpg) **Intuitively, topological sorting means "flattening" a graph such that all arrows point in the same direction** — for example, all arrows point to the right in the diagram above. Clearly, if a directed graph contains a cycle, topological sorting is impossible because you can't make all arrows point in the same direction. Conversely, if a graph is a DAG (Directed Acyclic Graph), topological sorting is always possible. But what does this problem have to do with topological sorting? **It's not hard to see: if you treat courses as nodes and dependencies between courses as directed edges, the topological sort of this graph gives you a valid course order**. First, let's check whether the input course dependencies contain a cycle. If there's a cycle, topological sorting is impossible, so we can reuse the logic from [Cycle Detection](https://labuladong.online/en/algo/data-structure/cycle-detection/): ```java public int[] findOrder(int numCourses, int[][] prerequisites) { if (!canFinish(numCourses, prerequisites)) { // not possible to finish all courses return new int[]{}; } // ... } ``` Now here's the key question: how do you actually perform topological sorting? Do you need some fancy technique? **It's actually very simple: reverse the postorder traversal result of the graph, and that's your topological sort**. ::: note Is Reversal Necessary? Some readers have mentioned that the topological sort algorithms they've seen online use the postorder traversal result directly without reversal. Why is that? You can indeed find such solutions. The reason is that they define edges differently when building the graph. In my graph, the arrow direction represents "depended upon" — for example, node `1` points to `2`, meaning node `1` is depended upon by node `2`, i.e., you must finish `1` before doing `2`. This feels more intuitive. If you reverse this and define directed edges as "depends on," then all edges in the graph are flipped, and you don't need to reverse the postorder result. Specifically, just change `graph[from].add(to);` to `graph[to].add(from);` in my solution, and no reversal is needed. ::: Let's look at the solution code directly. It builds on the cycle detection code by adding logic to record the postorder traversal result: ```java class Solution { // record the postorder traversal result List postorder = new ArrayList<>(); // record if a cycle exists boolean hasCycle = false; boolean[] visited, onPath; // main function public int[] findOrder(int numCourses, int[][] prerequisites) { List[] graph = buildGraph(numCourses, prerequisites); visited = new boolean[numCourses]; onPath = new boolean[numCourses]; // traverse the graph for (int i = 0; i < numCourses; i++) { traverse(graph, i); } // cannot perform topological sort if there is a cycle if (hasCycle) { return new int[]{}; } // reverse postorder traversal result is the topological sort result Collections.reverse(postorder); int[] res = new int[numCourses]; for (int i = 0; i < numCourses; i++) { res[i] = postorder.get(i); } return res; } // graph traversal function void traverse(List[] graph, int s) { if (onPath[s]) { // found a cycle hasCycle = true; } if (visited[s] || hasCycle) { return; } // pre-order traversal position onPath[s] = true; visited[s] = true; for (int t : graph[s]) { traverse(graph, t); } // post-order traversal position postorder.add(s); onPath[s] = false; } // build graph function List[] buildGraph(int numCourses, int[][] prerequisites) { // code as seen earlier } } ``` Nodes with `visited` set to true are shown in green, and nodes with `onPath` set to true are shown in orange. You can open the visualization panel and click if (onPath[s]) multiple times to watch the DFS traversal of the graph. The code may look long, but the logic should be clear. As long as the graph has no cycle, we call `traverse` to perform DFS on the graph, record the postorder traversal result, and then reverse it to get the final answer. **So why is the reverse of the postorder traversal the topological sort?** Instead of a mathematical proof, let me explain with an intuitive example using binary trees. Here's the binary tree traversal framework we've discussed many times: ```java void traverse(TreeNode root) { // pre-order traversal position traverse(root.left) // in-order traversal position traverse(root.right) // post-order traversal position } ``` When does postorder traversal execute? Only after both the left and right subtrees have been fully traversed. In other words, the child nodes are added to the result list before the root node. **This property of postorder traversal is crucial. Topological sort is based on postorder traversal because a task can only begin after all the tasks it depends on have been completed**. Think of a binary tree as a directed graph where edges point from parent nodes to child nodes, like this: ![](../pictures/topological-sort/2.jpeg) In the standard postorder traversal result, the root node appears last. Simply reverse the traversal result, and you get the topological sort: ![](../pictures/topological-sort/3.jpeg) I know some readers will ask: what's the relationship between the reversed postorder result and the preorder traversal result? For binary trees, they might look related, but in reality they have no relationship whatsoever. Do not assume that reversing the postorder result gives you the preorder result. The key difference was already explained in [Binary Tree Thinking (Master Plan)](https://labuladong.online/en/algo/essential-technique/binary-tree-summary/): postorder code executes only after both subtrees are fully traversed. Only postorder traversal can capture the "dependency" relationship — no other traversal order can do this. ## Topological Sort (BFS Version) Make sure you understand the BFS algorithm from [Cycle Detection](https://labuladong.online/en/algo/data-structure/cycle-detection/) that uses an `indegree` array to determine whether a directed graph contains a cycle. **If you can understand the BFS version of the cycle detection algorithm, the BFS version of topological sort is easy to grasp, because the node traversal order is the topological sort result**. For example, in the cycle detection example, the value in each node below represents its enqueue order: ![](../pictures/topological-sort/13.jpeg) Clearly, this order is a valid topological sort result. So we just need to slightly modify the BFS cycle detection algorithm to record the node traversal order, and we get the topological sort result: ```java class Solution { public int[] findOrder(int numCourses, int[][] prerequisites) { // Build the graph, same as in cycle detection algorithm List[] graph = buildGraph(numCourses, prerequisites); // Calculate in-degrees, same as in cycle detection algorithm int[] indegree = new int[numCourses]; for (int[] edge : prerequisites) { int from = edge[1], to = edge[0]; indegree[to]++; } // Initialize the queue with nodes having in-degree of 0, same as in cycle detection algorithm Queue q = new LinkedList<>(); for (int i = 0; i < numCourses; i++) { if (indegree[i] == 0) { q.offer(i); /** ![](../pictures/topological-sort/6.jpeg) */ } } // Record the topological sort result int[] res = new int[numCourses]; // Record the order of traversed nodes (index) int count = 0; // Start executing BFS algorithm while (!q.isEmpty()) { int cur = q.poll(); // The order of nodes being dequeued is the topological sort result res[count] = cur; count++; for (int next : graph[cur]) { /** ![](../pictures/topological-sort/7.jpeg) */ indegree[next]--; if (indegree[next] == 0) { q.offer(next); } } } if (count != numCourses) { // A cycle exists, topological sort is not possible return new int[]{}; } return res; } // Function to build the graph List[] buildGraph(int n, int[][] edges) { // See above } } ``` In principle, [graph traversal](https://labuladong.online/en/algo/data-structure-basic/graph-basic/) requires a `visited` array to prevent revisiting nodes. Here, the BFS algorithm effectively uses the `indegree` array to serve the same purpose as a `visited` array — only nodes with an in-degree of 0 can enter the queue, which prevents infinite loops. ================================================ FILE: dynamic-programming/README.md ================================================ # 动态规划系列 我们公众号最火的就是动态规划系列的文章,也许是动态规划问题有难度而且有意思,也许因为它是面试常考题型。不管你之前是否害怕动态规划系列的问题,相信这一章的内容足以帮助你消除对动态规划算法的恐惧。 具体来说,动态规划的一般流程就是三步:**暴力的递归解法 -> 带备忘录的递归解法 -> 迭代的动态规划解法**。 就思考流程来说,就分为一下几步:**找到状态和选择 -> 明确 dp 数组/函数的定义 -> 寻找状态之间的关系**。 这就是思维模式的框架,**本章都会按照以上的模式来解决问题,辅助读者养成这种模式思维**,有了方向遇到问题就不会抓瞎,足以解决一般的动态规划问题。 欢迎关注我的公众号 labuladong,查看全部文章: ![labuladong二维码](../pictures/qrcode.jpg) ================================================ FILE: dynamic-programming/dp-framework.md ================================================ ::: info Prerequisite Knowledge Before reading this article, you should first learn: - [Binary Tree Traversal Framework](https://labuladong.online/en/algo/data-structure-basic/binary-tree-traverse-basic/) - [N-ary Tree Structure and Traversal Framework](https://labuladong.online/en/algo/data-structure-basic/n-ary-tree-traverse-basic/) ::: Dynamic Programming (DP) problems can be challenging for many readers, but they are also among the most interesting and skillful types of problems. This site dedicates an entire chapter to this algorithm, which shows the importance of dynamic programming. This article will address several questions: - What is dynamic programming? - What are the techniques to solve dynamic programming problems? - How should you learn dynamic programming? After solving many problems, you will notice that there are only a few main algorithm strategies. In the following chapters about dynamic programming, we will use the same problem-solving framework discussed here. If you understand the framework, tackling DP problems will become much easier. That is why this article is placed at the beginning, aiming to serve as a guide for solving dynamic programming problems. Now, let’s get to the main content. First, **the common form of a dynamic programming problem is to find an optimal value**. Dynamic programming is actually an optimization technique from operations research, but it is widely used in computer science. For example, you may be asked to find the longest increasing subsequence, the minimum edit distance, and so on. Since we are looking for an optimal value, what is the core issue? **The core of solving a dynamic programming problem is enumeration (brute-force search)**. To find the optimal value, you have to enumerate all possible answers and then select the optimal one. Is dynamic programming really that simple—just brute-force enumeration? But the DP problems I’ve seen are so difficult! Although the main idea of dynamic programming is brute-force search for the optimal value, the problems can be very diverse. Enumerating all possible solutions is not easy; you need to be good at recursive thinking. Only by writing the **correct "state transition equation"** can you enumerate correctly. In addition, you need to determine whether the problem **has "optimal substructure"**, which means whether the optimal solution to the problem can be constructed from the optimal solutions to its subproblems. Moreover, dynamic programming problems **have "overlapping subproblems"**. If you use brute-force enumeration, the efficiency will be very low. Therefore, you need to use a "memoization" technique or a "DP table" to optimize the process and avoid unnecessary calculations. The three key elements of dynamic programming are: overlapping subproblems, optimal substructure, and state transition equations. We will explain these in detail with examples. However, in practical algorithm problems, writing the state transition equation is usually the hardest part. Many people struggle with this, so I will share a thinking framework to help you develop state transition equations: **Clarify the "state" -> Clarify the "choices" -> Define the meaning of the `dp` array/function.** By following this framework, your final code will look like this: ```python # Top-down recursive dynamic programming def dp(state1, state2, ...): for choice in all possible choices: # The state changes after making the choice result = find_optimal(result, dp(state1, state2, ...)) return result # Bottom-up iterative dynamic programming # Initialize base case dp[0][0][...] = base case # Perform state transitions for state1 in all possible values of state1: for state2 in all possible values of state2: for ... dp[state1][state2][...] = find_optimal(choice1, choice2, ...) ``` Next, we will use the Fibonacci problem and the coin change problem to explain the basic principles of dynamic programming. The first example will help you understand what overlapping subproblems are (although Fibonacci does not optimize for an optimal value, so strictly speaking, it is not a DP problem), and the second will focus on how to construct state transition equations. ## 1. Fibonacci Sequence LeetCode Problem 509 "[Fibonacci Number](https://leetcode.com/problems/fibonacci-number/)" is about this problem. Please don't be discouraged by the simplicity of this example. **Only simple examples allow you to focus fully on the underlying ideas and techniques of the algorithm, without getting distracted by tricky details.** If you want more challenging examples, there will be plenty in the upcoming dynamic programming series. ### Brute-force Recursion The mathematical definition of the Fibonacci sequence is recursive. The code implementation is as follows: ```java // f(n) calculates the nth Fibonacci number int fib(int n) { // base case if (n == 0 || n == 1){ return n; } return fib(n - 1) + fib(n - 2); } ``` ::: info According to the LeetCode problem description, the base cases are `f(0) = 0` and `f(1) = 1`. In some other definitions, the base cases are `f(1) = 1` and `f(2) = 1`. In fact, these are equivalent. ::: When teaching recursion, school teachers often use this as an example. We also know that although this code is simple and easy to understand, it is very inefficient. Why is it inefficient? Suppose `n = 20`. Let's draw the recursion tree: ![](../pictures/dynamic-programming/1.jpg) How do we understand this recursion tree? To compute the original problem `f(20)`, we first need to compute the subproblems `f(19)` and `f(18)`. To compute `f(19)`, we need to compute `f(18)` and `f(17)`, and so on. When we reach `f(1)` or `f(2)`, we already know the results, so we can return immediately, and the recursion tree does not grow further downward. Using an algorithm visualization tool can help you understand this process better. The recursion tree for `f(20)` is too large, so let's look at the recursion process for `f(5)`. Please open the visualization panel below and click the line if (n == 0 || n == 1). You will see the recursion tree grow downward, and when it reaches a leaf node (base case), it returns the result layer by layer: **How do we calculate the time complexity of a recursive algorithm? Multiply the number of subproblems by the time needed to solve each subproblem.** First, count the number of subproblems, which is the total number of nodes in the recursion tree. The height of this recursion tree is $n$, so the total number of nodes in a binary tree is $2^n$. Next, calculate the time needed to solve a subproblem. In this algorithm, there are no loops, only one addition operation `f(n - 1) + f(n - 2)`, which takes $O(1)$ time. Therefore, the total time complexity is the product of the two, which is $O(2^n)$—exponential time, which can quickly become unmanageable. Looking at the recursion tree, it is clear why the algorithm is inefficient: there is a lot of repeated computation. For example, `f(18)` is calculated twice, and as you can see, the subtree rooted at `f(18)` is quite large. Recomputing it wastes a lot of time. And `f(18)` is not the only node that gets recalculated, so the algorithm is very inefficient. ![](../pictures/dynamic-programming/1.jpg) This demonstrates the first property of dynamic programming problems: **overlapping subproblems**. Next, we will look for ways to solve this issue. ### Recursive Solution with Memoization Since the main cause of inefficiency is repeated calculations, we can create a "memoization" table. Each time we solve a subproblem, we save the answer in the memoization table. When we encounter a subproblem, we first check the table. If the answer has already been computed, we simply return it instead of recalculating. For the Fibonacci problem, we need a memoization table to record the value of the subproblem `f(x)`, where `x` is a non-negative integer. We usually use a one-dimensional array `memo` as the memoization table, where `memo[x]` stores the answer for subproblem `f(x)`. Of course, you can also use a hash table for storage. The idea is the same. ```java int fib(int n) { // initialize the memo array to all -1 // because the Fibonacci number is a non-negative integer, so initialize it with -1 to indicate that it has not been calculated // because the index of the array starts at 0, so we need n + 1 spaces // so we can record `f(0) ~ f(n)` in memo int[] memo = new int[n + 1]; Arrays.fill(memo, -1); return dp(memo, n); } // perform recursion with memoization int dp(int[] memo, int n) { // base case if (n == 0 || n == 1) { return n; } // already calculated, no need to calculate again if (memo[n] != -1) { return memo[n]; } // before returning the result, store it in the memo memo[n] = dp(memo, n - 1) + dp(memo, n - 2); return memo[n]; } ``` Now, let's draw the recursion tree to see what memoization actually does. ![](../pictures/dynamic-programming/2.jpg) In fact, recursive algorithms with memoization prune away the redundant branches of the recursion tree, turning it into a recursion graph without redundancy. This greatly reduces the number of subproblems (nodes in the graph), and each subproblem is calculated only once: ![](../pictures/dynamic-programming/3.jpg) **How do you calculate the time complexity of a recursive algorithm? Multiply the number of subproblems by the time needed to solve each subproblem.** The number of subproblems is the total number of nodes in the graph. Since this algorithm avoids redundant calculations, the subproblems are simply `f(0)`, `f(1)`, `f(2)`, ..., `f(20)`. The number of subproblems is proportional to the input size `n = 20`, so there are $O(n)$ subproblems. The time to solve each subproblem is $O(1)$, as there are no loops inside. Therefore, the overall time complexity of this algorithm is $O(n)$, which is much more efficient than the exponential time complexity of brute-force solutions. Here, you can also use the algorithm visualization panel to see the effect of pruning. The recursion tree of `fib(20)` is too large, so let's look at the recursion process of `fib(5)`. Please open this visualization panel and click the line if (n == 0 || n == 1) multiple times to observe the growth of the recursion tree. Notice that when encountering duplicate nodes, the recursion tree stops growing downward and directly returns the result. This is the effect of pruning. ### Top-Down vs Bottom-Up If you have mastered the content above, you already know how to solve dynamic programming problems: start with a brute-force solution, then use a "memoization" technique to prune and eliminate overlapping subproblems. Dynamic programming is just that simple. However, some readers may ask: why do many dynamic programming solutions I see only use several for loops, without any recursion or memoization? What is going on here? In fact, there are two main approaches to dynamic programming: The first is the recursive solution with memoization, also known as the "top-down" approach, which we showed above—a recursive function with a `memo` array. The second is the iterative solution with a DP table, also called the "bottom-up" approach. This is what you described: using for loops to fill out a `dp` array. **These two approaches are essentially the same and can be converted into each other. The `dp` array in the iterative solution is the same as the `memo` array in the recursive solution.** Why is it called "top-down"? For example, in the recursive solution above, if you click if (n == 0 || n == 1) multiple times, you can see the recursion tree growing from top to bottom. It starts from the original problem `f(5)` and breaks it down into smaller problems until it reaches the base cases `f(0)` and `f(1)`, then returns the answers layer by layer. This is called "top-down". What is "bottom-up"? It is the opposite. We start from the simplest, smallest subproblems with known results—`f(0)` and `f(1)` (the base cases)—and work our way up to find `f(2)`, `f(3)`, ..., until we reach `f(5)`. This is "bottom-up". **In fact, "top-down" and "bottom-up" are essentially the same, just from different perspectives.** For example, if we slightly modify the recursive solution with memoization above, and move the base case handling (`n == 0 || n == 1`) from the recursive function `dp` into the `memo` array, it should be fine. Let's look again at the calculation process of `fib(5)`. You can click on memo[n] = dp(memo, n - 1) + dp(memo, n - 2) multiple times. **Pay attention to the changes in the recursion tree and the `memo` array**: You can see that the process of passing results up the recursion tree is just the same as calculating the `memo` array from the base cases to the right. This is the bottom-up approach, and it is very intuitive. By now, you might have noticed that the entire computation is just calculating the values in `memo` from left to right. Why bother using recursion, making things more complex? Isn't a for loop enough? ### Iterative DP Array Solution With this insight, we no longer need to use recursion. We simply create an array (the DP table) and use a for loop to calculate values from the base cases from left to right. ```java int fib(int n) { if (n == 0 || n == 1) { return n; } // dp table int[] dp = new int[n + 1]; // base case dp[0] = 0; dp[1] = 1; // state transition for (int i = 2; i <= n; i++) { dp[i] = dp[i - 1] + dp[i - 2]; } return dp[n]; } ``` The diagram makes this approach easy to understand, and you can see that this DP table is similar to the "pruned" result from before, just calculated in the opposite direction: ![](../pictures/dynamic-programming/4.jpg) In fact, the "memo" array in the recursive solution with memoization ends up being the same as the `dp` array in this solution. If you compare the visualizations of the two algorithms, you can clearly see their connection. So, the top-down and bottom-up approaches are essentially the same, and in most cases, their efficiency is about the same. ### Further Exploration Here, let's introduce the concept of the "state transition equation," which is simply the mathematical way to describe the structure of a problem: $$ f(n) = egin{cases} 0, & n = 0 \ 1, & n = 1 \ f(n-1) + f(n-2), & n > 1 nd{cases} $$ Why is it called a "state transition equation"? The name just sounds fancy. The function parameter `f(n)` keeps changing, so you can think of the parameter `n` as a "state." This state `n` is derived (by addition) from states `n - 1` and `n - 2`. This process is called a state transition, and that's all there is to it. You will notice that all the operations in the solutions above, such as `return f(n - 1) + f(n - 2)`, `dp[i] = dp[i - 1] + dp[i - 2]`, and the initialization of a memoization array or DP table, are different forms of this same equation. This shows how important it is to write out the "state transition equation." It is the core of solving the problem, and it is easy to see that the equation itself represents the brute-force solution. **Never look down on the brute-force solution. The hardest part of dynamic programming is writing out this brute-force solution, which is the state transition equation.** Once you have the brute-force solution, the only way to optimize it is to use memoization or a DP table. There are no more mysteries after that. Now, let's discuss a detail for optimization. Careful readers may notice that according to the state transition equation for the Fibonacci sequence, the current state `n` only depends on the previous two states, `n-1` and `n-2`. Therefore, you do not need a long DP table to store all the states. You just need to find a way to store the previous two states. So, you can further optimize and reduce the space complexity to $O(1)$. This is the most common algorithm for computing Fibonacci numbers: ```java int fib(int n) { if (n == 0 || n == 1) { // base case return n; } // represent dp[i - 1] and dp[i - 2] respectively 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; // rolling update dp_i_2 = dp_i_1; dp_i_1 = dp_i; } return dp_i_1; } ``` This is usually the final step of optimization for dynamic programming problems. If you find that each state transition only needs part of the DP table, you can try to shrink the size of the DP table and record only the necessary data, thereby lowering the space complexity. In the example above, the size of the DP table is reduced from `n` to 2, which reduces the space complexity by an order of magnitude. I will explain more about this technique for reducing space complexity in the article [Reducing the Dimension of Dynamic Programming](https://labuladong.online/en/algo/dynamic-programming/space-optimization/). Generally, it is used to compress a two-dimensional DP table into one dimension, reducing the space complexity from $O(n^2)$ to $O(n)$. Some might ask: What about another important property of dynamic programming, the "optimal substructure"? Why isn't it discussed here? It will be covered below. Strictly speaking, the Fibonacci sequence is not a real dynamic programming problem because it does not involve finding an optimal value. The example above is mainly to show how to eliminate overlapping subproblems and demonstrate the process of step-by-step refinement of the optimal solution. Next, let's look at the second example: the coin change problem. ## 2. Coin Change Problem This is LeetCode Problem 322: [Coin Change](https://leetcode.com/problems/coin-change/): You are given `k` types of coins, with denominations `c1, c2 ... ck`. Each type of coin has an unlimited supply. Given a total amount `amount`, find the **minimum** number of coins needed to make up that amount. If it is not possible, return -1. The function signature is as follows: ```java // `coins` contains the denominations of available coins, and `amount` is the target amount int coinChange(int[] coins, int amount); ``` For example, if `k = 3`, the denominations are 1, 2, and 5, and the total amount `amount = 11`. The minimum number of coins required is 3, that is, 11 = 5 + 5 + 1. How should a computer solve this problem? Clearly, we can enumerate all possible combinations of coins and find the one that uses the fewest coins. ### Brute-force Recursion First, this is a dynamic programming problem because it has an "optimal substructure." **For the optimal substructure to hold, the subproblems must be independent of each other.** What does it mean to be independent? Instead of a mathematical proof, let's look at an intuitive example. Suppose you are taking an exam, and the scores of each subject are independent. Your main goal is to achieve the highest total score. Your subproblems are to get the highest score in Chinese, the highest in Math, and so on. To get the highest score in each subject, you need to get the highest possible score in each section, like multiple choice and fill-in-the-blank. In the end, if you score full marks in every subject, you achieve the highest total score. This gives the correct result: the highest total score is the sum of the highest scores in each subject. This works because the subproblems are independent and do not interfere with each other, so the problem has an optimal substructure. However, if there is a new condition: your Chinese and Math scores affect each other and cannot both be full marks; if your Math score is high, your Chinese score goes down, and vice versa. In this case, clearly, you cannot achieve the full total score, and the previous approach will give the wrong result. This is because the subproblems are not independent—Chinese and Math scores affect each other, so the optimal substructure is broken. Back to the coin change problem, why does it have an optimal substructure? Suppose you have coins with denominations `1, 2, 5`, and you want to find the minimum number of coins needed for `amount = 11` (the main problem). If you already know the minimum number of coins needed for `amount = 10, 9, 6` (the subproblems), you just add one more coin (of value `1, 2, or 5`) to those subproblems and take the minimum result. Because you have an unlimited supply of each coin, the subproblems are independent of each other. ::: tip Tip For more examples about the optimal substructure property, see [Dynamic Programming Q&A](https://labuladong.online/en/algo/dynamic-programming/faq-summary/) later in this article. ::: Now that we know this is a dynamic programming problem, how do we write the correct state transition equation? **1. Determine the "state", which refers to the variables that change in the original problem and its subproblems.** Since the number of coins is unlimited and the coin denominations are provided by the problem, only the target amount keeps moving closer to the base case. So the only "state" is the target amount `amount`. **2. Determine the "choices", which are the actions that cause the "state" to change.** Why does the target amount change? Because you are choosing coins. Each time you pick a coin, you reduce the target amount by the value of that coin. Therefore, the denominations of all coins are your "choices". **3. Define the `dp` function/array.** Here we are using the top-down approach, so we will have a recursive `dp` function. Usually, the parameters of the function are the variables that change during the state transition, i.e., the "state" mentioned above. The return value of the function is the quantity the problem requires us to compute. For this problem, the only state is the "target amount", and the problem requires us to calculate the minimum number of coins needed to make up the target amount. **So, we can define the `dp` function like this: `dp(n)` means, given a target amount `n`, return the fewest number of coins needed to make up amount `n`.** According to this definition, our final answer is the return value of `dp(amount)`. Once you understand these key points, you can write the pseudocode for the solution: ```java // Pseudocode framework int coinChange(int[] coins, int amount) { // The final result required by the problem is dp(amount) return dp(coins, amount); } // Definition: To make up the amount n, at least dp(coins, n) coins are needed int dp(int[] coins, int n) { // Make a choice, choose the result that requires the fewest coins for (int coin : coins) { res = min(res, 1 + dp(coins, n - coin)); } return res; } ``` Based on the pseudocode, we add the base cases to get the final solution. Obviously, when the target amount is 0, the minimum number of coins needed is 0. When the target amount is less than 0, there is no solution, so return -1: ```java class Solution { public int coinChange(int[] coins, int amount) { // the final result required by the problem is dp(amount) return dp(coins, amount); } // definition: to make up the `amount`, at least dp(coins, amount) coins are needed int dp(int[] coins, int amount) { // base case if (amount == 0) return 0; if (amount < 0) return -1; int res = Integer.MAX_VALUE; for (int coin : coins) { // calculate the result of the subproblem int subProblem = dp(coins, amount - coin); // skip if the subproblem has no solution if (subProblem == -1) continue; // choose the optimal solution from the subproblem, then add one res = Math.min(res, subProblem + 1); } return res == Integer.MAX_VALUE ? -1 : res; } } ``` ::: info Info Here, the signature of `coinChange` and the `dp` function are exactly the same, so in theory you don't need to write a separate `dp` function. However, for clarity in later explanations, we still implement the main logic in a separate `dp` function. Additionally, I often see readers ask: why do we add 1 to the result of the subproblem (`subProblem + 1`), instead of adding the coin's denomination or something else? Here is a general reminder: the key to dynamic programming problems is the definition of the `dp` function/array. What does your function's return value represent? Go back and make sure you understand this, and you will know why we add 1 to the result of the subproblem. ::: At this point, the state transition equation is actually complete. The above algorithm is a brute-force solution, and its mathematical form is the state transition equation: $$ dp(n) = egin{cases} 0, & n = 0 \ -1, & n < 0 \ \min\{dp(n - ext{coin}) + 1 | ext{coin} \in ext{coins}\}, & n > 0 nd{cases} $$ So, the problem is basically solved. The only thing left is to eliminate overlapping subproblems. For example, when `amount = 11` and `coins = {1,2,5}`, you can draw out the recursion tree to see: ![](../pictures/dynamic-programming/5.jpg) **Time complexity analysis of the recursive algorithm: total number of subproblems × time required to solve each subproblem.** The total number of subproblems is the number of nodes in the recursion tree. The algorithm will perform pruning, and the timing of pruning depends on the specific coin denominations given in the problem. So, you can imagine that the tree does not grow regularly, and it is difficult to compute the exact number of nodes. In this case, we usually estimate the upper bound of the time complexity in the worst case. Assume the target amount is `n`, and there are `k` types of coins. In the worst case, the height of the recursion tree is `n` (all coins are of denomination 1). Assuming this is a full k-ary tree, the total number of nodes is on the order of `k^n`. Next, consider the complexity for each subproblem. Since each recursion contains a for loop, the complexity is $O(k)$. Multiply them to get a total time complexity of $O(k^n)$, which is exponential. ### Recursive Solution with Memoization Similar to the previous Fibonacci example, with slight modification, we can use memoization to eliminate redundant subproblems: ```java class Solution { int[] memo; public int coinChange(int[] coins, int amount) { memo = new int[amount + 1]; // Initialize the memo with a special value that won't be picked, representing it has not been calculated Arrays.fill(memo, -666); return dp(coins, amount); } int dp(int[] coins, int amount) { if (amount == 0) return 0; if (amount < 0) return -1; // Check the memo to prevent repeated calculations if (memo[amount] != -666) return memo[amount]; int res = Integer.MAX_VALUE; for (int coin : coins) { // Calculate the result of the subproblem int subProblem = dp(coins, amount - coin); /** ![](../pictures/dynamic-programming/5.jpg) */ // Skip if the subproblem has no solution if (subProblem == -1) continue; // Choose the optimal solution in the subproblem, then add one res = Math.min(res, subProblem + 1); } // Store the calculation result in the memo memo[amount] = (res == Integer.MAX_VALUE) ? -1 : res; return memo[amount]; } } ``` Without showing the diagram, it is clear that memoization greatly reduces the number of subproblems and completely removes redundancy. Therefore, the total number of subproblems will not exceed the amount `n`, making the number of subproblems $O(n)$. The time to process each subproblem remains $O(k)$, so the total time complexity is $O(kn)$. ### Iterative Solution Using `dp` Array Of course, we can also use a bottom-up approach with a dp table to eliminate overlapping subproblems. The concepts of "state", "choice", and base case remain the same as before. The definition of the `dp` array is similar to the previous `dp` function; both use the target amount as a variable. The `dp` function uses function arguments, while the `dp` array uses array indices: **Definition of the `dp` array: When the target amount is `i`, `dp[i]` represents the minimum number of coins needed to make up the amount.** Based on the dynamic programming framework provided at the beginning of this article, we can write the following solution: ```java class Solution { public int coinChange(int[] coins, int amount) { int[] dp = new int[amount + 1]; // The array size is amount + 1, and the initial value is also amount + 1 Arrays.fill(dp, amount + 1); // base case dp[0] = 0; // The outer for loop traverses all possible values of all states for (int i = 0; i < dp.length; i++) { // The inner for loop finds the minimum value among all choices for (int coin : coins) { // The subproblem has no solution, skip if (i - coin < 0) { continue; } dp[i] = Math.min(dp[i], 1 + dp[i - coin]); /** ![](../pictures/dynamic-programming/6.jpg) */ } } return (dp[amount] == amount + 1) ? -1 : dp[amount]; } } ``` ::: info Info Why do we initialize all values in the `dp` array to `amount + 1`? Because the maximum number of coins needed to make up `amount` is at most `amount` (using only coins of value 1). So initializing to `amount + 1` is equivalent to initializing to positive infinity, which makes it easier to take the minimum value later. Why not use the maximum value of the integer type, `Integer.MAX_VALUE`? Because later we have `dp[i - coin] + 1`, which could cause integer overflow. ::: ![](../pictures/dynamic-programming/6.jpg) ## 3. Final Summary The first Fibonacci sequence problem explained how to optimize the recursive tree using either "memoization" or a "DP table." It also clarified that these two methods are essentially the same, differing only in their top-down and bottom-up approaches. The second coin change problem demonstrated how to systematically determine the "state transition equation." Once you write a brute-force recursive solution based on the state transition equation, the remaining task is to optimize the recursive tree and eliminate overlapping subproblems. If you don't know much about dynamic programming but still made it to this point, you deserve applause. I believe you have already mastered the design techniques of this algorithm. **When solving problems, computers have no special tricks. The only solution is brute-force—trying all possibilities.** Algorithm design is simply thinking about "how to brute-force," and then pursuing "how to brute-force smartly." Listing the state transition equation is solving the problem of "how to brute-force." The reason it's considered difficult is, first, that many brute-force solutions require recursion, and second, that some problems have complex solution spaces, making it hard to exhaust all possibilities. Memoization and DP tables are ways to "brute-force smartly." The idea of trading space for time is the key to lowering time complexity. Besides this, what other tricks can we really play? We will have a dedicated chapter on dynamic programming problems later. If you have any questions, feel free to come back and reread this article. I hope readers will focus more on "states" and "choices" when reading each problem and solution, so you can develop your own understanding of this framework and use it fluently. ================================================ FILE: dynamic-programming/edit-distance.md ================================================ ::: info Prerequisites Before reading this article, you should first learn: - [Binary Tree Algorithms (Overview)](https://labuladong.online/en/algo/essential-technique/binary-tree-summary/) - [Core Framework of Dynamic Programming](https://labuladong.online/en/algo/essential-technique/dynamic-programming-framework/) ::: A few days ago, I saw an interview question from Tencent. Most of the algorithm section was about dynamic programming, and the last question was to write a function to calculate the edit distance. Today, I will write a dedicated article to discuss this problem. LeetCode Problem 72 "[Edit Distance](https://leetcode.com/problems/edit-distance/)" is exactly about this topic. Let's look at the problem first: **LeetCode 72. Edit Distance** Given two strings `word1` and `word2`, return *the minimum number of operations required to convert `word1` to `word2`*. You have the following three operations permitted on a word: - Insert a character - Delete a character - Replace a character Example 1:** ``` **Input:** word1 = "horse", word2 = "ros" **Output:** 3 **Explanation:** horse -> rorse (replace 'h' with 'r') rorse -> rose (remove 'r') rose -> ros (remove 'e') ``` Example 2:** ``` **Input:** word1 = "intention", word2 = "execution" **Output:** 5 **Explanation:** intention -> inention (remove 't') inention -> enention (replace 'i' with 'e') enention -> exention (replace 'n' with 'x') exention -> exection (replace 'n' with 'c') exection -> execution (insert 'u') ``` **Constraints:** - `0 <= word1.length, word2.length <= 500` - `word1` and `word2` consist of lowercase English letters. ```java // The function signature is as follows int minDistance(String s1, String s2) ``` For readers who have not encountered dynamic programming problems before, this question can be quite challenging. Does it feel like you don't know where to begin? However, this problem is actually very practical. I have used this algorithm in real life. In the past, I wrote a public article and accidentally misplaced a section of content. I decided to fix this part to make the logic clear. But the platform only allows you to edit up to 20 characters, supporting only insert, delete, and replace operations (exactly like the edit distance problem). So, I used the algorithm to find the optimal solution and completed the modification in just 16 steps. Another more advanced application is in DNA sequences, which are composed of A, G, C, T and can be viewed as strings. Edit distance can measure the similarity between two DNA sequences. The smaller the edit distance, the more similar the two DNA strands are. It's possible that the owners of those DNAs are ancient relatives. Now, let's get back to the main topic and explain in detail how to calculate the edit distance. I believe this article will be helpful to you. ## I. Approach The edit distance problem gives you two strings `s1` and `s2`. Using only three operations, you need to transform `s1` into `s2` with the minimum number of operations. Note that whether you transform `s1` into `s2` or vice versa, the result is the same. So throughout this article, we'll use transforming `s1` into `s2` as our example. ::: tip Pro Tip When solving dynamic programming problems involving two strings, you typically use two pointers `i` and `j` pointing to either the beginning or end of both strings, then work out the state transition equation. For example, let `i` and `j` point to the end of both strings, and define `dp[i], dp[j]` as the edit distance between substrings `s1[0..i]` and `s2[0..j]`. As `i` and `j` move forward step by step, the problem size (substring length) gradually decreases. Of course, you could also have `i` and `j` start at the beginning and move backward—there's no fundamental difference. You just need to adjust the definition of your `dp` function/array accordingly. ::: Let's say the two strings are `"rad"` and `"apple"`. Have pointers `i` and `j` point to the end of `s1` and `s2` respectively. To transform `s1` into `s2`, the algorithm proceeds like this: ![](../pictures/editDistance/edit.gif) ![](../pictures/editDistance/1.jpg) Remember this GIF—it shows how to compute the edit distance. The key is knowing how to make the right operation at each step, which we'll explain shortly. From the GIF above, you'll notice there aren't just three operations—there's actually a fourth: do nothing (skip). For example, in this situation: ![](../pictures/editDistance/2.jpg) Since these two characters are already the same, to minimize edit distance, you obviously shouldn't perform any operation on them. Just move both `i` and `j` forward. There's another easy case to handle: when `j` finishes traversing `s2` but `i` hasn't finished `s1`, you can only use delete operations to shorten `s1` to match `s2`. Like this: ![](../pictures/editDistance/3.jpg) Similarly, if `i` finishes `s1` while `j` still has characters left in `s2`, you can only use insert operations to add all remaining characters from `s2` into `s1`. As you'll see, these two situations are the algorithm's **base cases**. Now let's dive into how to convert this thinking into code. ## 2. Detailed Code Explanation Let's first review the previous approach: The base case occurs when `i` traverses through `s1` or `j` through `s2`, at which point you can directly return the remaining length of the other string. For each pair of characters `s1[i]` and `s2[j]`, there are four possible operations: ```python if s1[i] == s2[j]: Do nothing (skip) Move both i and j forward else: Choose one of three: Insert Delete Replace ``` With this framework, the problem is essentially solved. Readers might ask, how exactly should we choose among the "three options"? It's simple: try all of them, and choose the one that results in the minimum edit distance. Here, recursion is necessary. Let's first look at the brute-force solution code: ```java class Solution { public int minDistance(String s1, String s2) { int m = s1.length(), n = s2.length(); // i, j are initialized to point to the last index return dp(s1, m - 1, s2, n - 1); } // definition: return the minimum edit distance between s1[0..i] and s2[0..j] int dp(String s1, int i, String s2, int j) { // base case if (i == -1) return j + 1; if (j == -1) return i + 1; if (s1.charAt(i) == s2.charAt(j)) { // do nothing return dp(s1, i - 1, s2, j - 1); } return min( // insert dp(s1, i, s2, j - 1) + 1, // delete dp(s1, i - 1, s2, j) + 1, // replace dp(s1, i - 1, s2, j - 1) + 1 ); } int min(int a, int b, int c) { return Math.min(a, Math.min(b, c)); } } ``` Now, let's explain this recursive code in detail. The base case should be self-explanatory, so let's focus on the recursive part. It's often said that recursive code is highly interpretable, and there is a reason for that. As long as you understand the function's definition, you can clearly understand the algorithm's logic. Here, the `dp` function is defined as follows: ```java // Definition: return the minimum edit distance between s1[0..i] and s2[0..j] int dp(String s1, int i, String s2, int j) ``` **Remember this definition**, then let's look at this code: ```python if s1[i] == s2[j]: # Do nothing return dp(s1, i - 1, s2, j - 1) # Explanation: # They are already equal, no operation needed # The minimum edit distance between s1[0..i] and s2[0..j] equals # the minimum edit distance between s1[0..i-1] and s2[0..j-1] # In other words, dp(i, j) equals dp(i-1, j-1) ``` If `s1[i] != s2[j]`, three operations need to be considered recursively, requiring some thought: ```python # Insert dp(s1, i, s2, j - 1) + 1, # Explanation: # Insert a character identical to s2[j] after s1[i] # This matches s2[j], move j forward, continue comparing with i # Don't forget to add one to the operation count ``` ![](../pictures/editDistance/insert.gif) ```python # Delete dp(s1, i - 1, s2, j) + 1, # Explanation: # Delete the character s[i] # The minimum edit distance between s1[0..i-1] and s2[0..j] equals # Move i forward, continue comparing with j # Add one to the operation count ``` ![](../pictures/editDistance/delete.gif) ```python # Replace dp(s1, i - 1, s2, j - 1) + 1 # Explanation: # Replace s1[i] with s2[j], making them match # Move both i and j forward, continue comparison # Add one to the operation count ``` ![](../pictures/editDistance/replace.gif) Now, you should fully understand this concise code. A minor issue is that this solution is a brute-force method, with overlapping subproblems that require dynamic programming techniques for optimization. **How to identify overlapping subproblems at a glance?** I have discussed this in [Dynamic Programming Q&A](https://labuladong.online/en/algo/dynamic-programming/faq-summary/). To briefly mention, it is necessary to abstract the recursive framework of this algorithm: ```java int dp(i, j) { dp(i - 1, j - 1); // #1 dp(i, j - 1); // #2 dp(i - 1, j); // #3 } ``` For the subproblem `dp(i-1, j-1)`, how can it be derived from the original problem `dp(i, j)`? There is more than one path, such as `dp(i, j) -> #1` and `dp(i, j) -> #2 -> #3`. Once a duplicate path is found, it indicates a significant number of duplicate paths, meaning overlapping subproblems exist. ## 3. Dynamic Programming Optimization For overlapping subproblems, as covered in detail in [Dynamic Programming Explained](https://labuladong.online/en/algo/essential-technique/dynamic-programming-framework/), optimization methods boil down to either adding memoization to the recursive solution, or implementing the dynamic programming process iteratively with a DP table. Let's cover each approach. ### Memoization Solution Since we already have the brute-force recursive solution, adding memoization is straightforward. Just modify the original code slightly: ```java class Solution { // memoization int[][] memo; public int minDistance(String s1, String s2) { int m = s1.length(), n = s2.length(); // initialize the memoization with a special value, indicating it has not been calculated memo = new int[m][n]; for (int[] row : memo) { Arrays.fill(row, -1); } return dp(s1, m - 1, s2, n - 1); } int dp(String s1, int i, String s2, int j) { if (i == -1) return j + 1; if (j == -1) return i + 1; // check the memoization to avoid overlapping subproblems if (memo[i][j] != -1) { return memo[i][j]; } // state transition, store the result in memoization if (s1.charAt(i) == s2.charAt(j)) { memo[i][j] = dp(s1, i - 1, s2, j - 1); } else { memo[i][j] = min( dp(s1, i, s2, j - 1) + 1, dp(s1, i - 1, s2, j) + 1, dp(s1, i - 1, s2, j - 1) + 1 ); } return memo[i][j]; } int min(int a, int b, int c) { return Math.min(a, Math.min(b, c)); } } ``` ### DP Table Solution Let's focus on the DP table approach. We need to define a `dp` array and execute the state transition equation on it. First, clarify what the `dp` array represents. Since this problem has two states (indices `i` and `j`), the `dp` array is two-dimensional, looking something like this: ![](../pictures/editDistance/dp.jpg) The state transition is the same as the recursive solution. `dp[..][0]` and `dp[0][..]` correspond to the base case. The meaning of `dp[i][j]` is similar to our earlier `dp` function definition: ```java int dp(String s1, int i, String s2, int j) // returns the minimum edit distance between s1[0..i] and s2[0..j] dp[i+1][j+1] // stores the minimum edit distance between s1[0..i] and s2[0..j] ``` The base case for the `dp` function is when `i, j` equals -1, but array indices must be at least 0, so the `dp` array is offset by one. Since the `dp` array has the same meaning as the recursive `dp` function, you can directly apply the same logic to write the code. **The only difference is that the recursive solution works top-down (starting from the original problem and breaking it down to the base case), while the DP table works bottom-up (starting from the base case and building up to the original problem)**: ```java class Solution { public int minDistance(String s1, String s2) { int m = s1.length(), n = s2.length(); int[][] dp = new int[m + 1][n + 1]; // base case for (int i = 1; i <= m; i++) dp[i][0] = i; for (int j = 1; j <= n; j++) dp[0][j] = j; // bottom-up calculation for (int i = 1; i <= m; i++) for (int j = 1; j <= n; j++) if (s1.charAt(i - 1) == s2.charAt(j - 1)) dp[i][j] = dp[i - 1][j - 1]; else dp[i][j] = min( dp[i - 1][j] + 1, /** ![](../pictures/editDistance/delete.gif) */ dp[i][j - 1] + 1, /** ![](../pictures/editDistance/insert.gif) */ dp[i - 1][j - 1] + 1 /** ![](../pictures/editDistance/replace.gif) */ ); // stores the minimum edit distance between s1 and s2 return dp[m][n]; } int min(int a, int b, int c) { return Math.min(a, Math.min(b, c)); } } ``` ## IV. Further Exploration Generally, when dealing with dynamic programming problems involving two strings, the approach outlined in this article is used to establish a DP table. Why? Because it's easier to identify the state transitions, for example, the DP table for edit distance: ![](../pictures/editDistance/4.jpg) There's another detail: since each `dp[i][j]` is only related to the three nearby states, the space complexity can be reduced to $O(min(M, N))$ (where M and N are the lengths of the two strings). This is not difficult, but it greatly reduces interpretability, so readers can try optimizing it themselves. You might also ask, **this only finds the minimum edit distance, but what are the specific operations?** In the example of modifying a WeChat public account article you gave, just knowing the minimum edit distance is not enough; you also need to know the specific modifications. This is actually quite simple. With slight modifications to the code, additional information can be added to the dp array: ```java // int[][] dp; Node[][] dp; class Node { int val; int choice; // 0 represents doing nothing // 1 represents insertion // 2 represents deletion // 3 represents replacement } ``` The `val` attribute represents the previous value of the dp array, and the `choice` attribute represents the operation. When making the optimal choice, record the operation at the same time, and then backtrack from the result to get the specific operations. Our final result is `dp[m][n]`, where `val` stores the minimum edit distance and `choice` stores the last operation, for example, an insertion operation, allowing you to move left one step: ![](../pictures/editDistance/5.jpg) By repeating this process, you can step back to the starting point `dp[0][0]`, forming a path. Following the operations on this path for editing provides the optimal solution. ![](../pictures/editDistance/6.jpg) At everyone's request, I have written this approach as well, and you can try running it yourself: ```java int minDistance(String s1, String s2) { int m = s1.length(), n = s2.length(); Node[][] dp = new Node[m + 1][n + 1]; // base case for (int i = 0; i <= m; i++) { // transforming s1 to s2 only requires deleting a character dp[i][0] = new Node(i, 2); } for (int j = 1; j <= n; j++) { // transforming s1 to s2 only requires inserting a character dp[0][j] = new Node(j, 1); } // state transition equation for (int i = 1; i <= m; i++) { for (int j = 1; j <= n; j++) { if (s1.charAt(i-1) == s2.charAt(j-1)){ // if the two characters are the same, nothing needs to be done Node node = dp[i - 1][j - 1]; dp[i][j] = new Node(node.val, 0); } else { // otherwise, record the operation with the minimum cost dp[i][j] = minNode( dp[i - 1][j], dp[i][j - 1], dp[i-1][j-1] ); // and increment the edit distance by one dp[i][j].val++; } } } // deduce the specific operation process based on the dp table and print it printResult(dp, s1, s2); return dp[m][n].val; } // calculate the operation with the minimum cost among delete, insert, replace Node minNode(Node a, Node b, Node c) { Node res = new Node(a.val, 2); if (res.val > b.val) { res.val = b.val; res.choice = 1; } if (res.val > c.val) { res.val = c.val; res.choice = 3; } return res; } // deduce the result and print the specific operations void printResult(Node[][] dp, String s1, String s2) { int rows = dp.length; int cols = dp[0].length; int i = rows - 1, j = cols - 1; System.out.println("Change s1=" + s1 + " to s2=" + s2 + ": "); while (i != 0 && j != 0) { char c1 = s1.charAt(i - 1); char c2 = s2.charAt(j - 1); int choice = dp[i][j].choice; System.out.print("s1[" + (i - 1) + "]:"); switch (choice) { case 0: // skip, then both pointers move forward System.out.println("skip '" + c1 + "'"); i--; j--; break; case 1: // insert s2[j] into s1[i], then the s2 pointer moves forward System.out.println("insert '" + c2 + "'"); j--; break; case 2: // delete s1[i], then the s1 pointer moves forward System.out.println("delete '" + c1 + "'"); i--; break; case 3: // replace s1[i] with s2[j], then both pointers move forward System.out.println( "replace '" + c1 + "'" + " with '" + c2 + "'"); i--; j--; break; } } // if s1 is not finished, the remaining characters need to be deleted while (i > 0) { System.out.print("s1[" + (i - 1) + "]:"); System.out.println("delete '" + s1.charAt(i - 1) + "'"); i--; } // if s2 is not finished, the remaining characters need to be inserted into s1 while (j > 0) { System.out.print("s1[0]:"); System.out.println("insert '" + s2.charAt(j - 1) + "'"); j--; } } ``` ================================================ FILE: dynamic-programming/egg-drop.md ================================================ ::: info Prerequisites Before reading this article, you should first learn: - [Dynamic Programming Core Framework](https://labuladong.online/en/algo/essential-technique/dynamic-programming-framework/) ::: This article discusses a classic algorithm problem: given several floors and several eggs, you need to determine the minimum number of attempts required to find the highest floor from which an egg can be dropped without breaking. This problem is frequently asked in interviews at major Chinese companies as well as Google and Facebook. However, they often change the context to throwing cups or bowls instead of eggs to avoid wastage. We'll get to the specific problem shortly. This problem has numerous solution techniques, including several different dynamic programming approaches with varying efficiencies, and finally, an extremely efficient mathematical solution. Consistent with the style of this book, we avoid overly complex techniques that are not broadly applicable, as they are not worth the effort to learn. Let's now use the general dynamic programming approach we've emphasized to analyze this problem. ## 1. Understanding the Problem This is LeetCode problem 887: [Super Egg Drop](https://leetcode.com/problems/super-egg-drop/). I will explain the problem: You have a building with floors numbered from 1 to `N`. You are given `K` eggs (`K` is at least 1). There is a floor `F` (where `0 <= F <= N`). If you drop an egg from floor `F`, it will **not break**. If you drop an egg from any floor higher than `F`, it will break. If you drop an egg from any floor lower than `F`, it will not break. If the egg does not break, you can pick it up and use it again. Now, the question is: In the **worst case**, what is the **minimum** number of tries needed to find floor `F`? In other words, you need to find the highest floor `F` where the egg does not break. But what does "minimum number of tries in the worst case" mean? Let’s look at some examples. First, let’s ignore the limit on the number of eggs. If there are 7 floors, how do you find the floor where the egg just breaks? The most basic way is linear search: drop the egg from the 1st floor. If it does not break, try the 2nd floor. If it does not break, try the 3rd floor, and so on. With this method, the **worst case** is when you reach the 7th floor and the egg still does not break (`F = 7`). So you need to drop the egg 7 times. Now you should understand what "worst case" means. **The egg only breaks when you have tried all possible floors**. If the egg breaks on the 1st floor, you are just lucky; that is not the worst case. Now, what does "minimum number of tries" mean? Still ignoring the egg limit, if there are 7 floors, we can use a better strategy. The best idea is to use binary search. First, drop the egg from the middle floor: `(1 + 7) / 2 = 4`. If the egg breaks, then `F` is less than 4. Try the middle of 1 to 3. If the egg does not break, then `F` is at least 4. Try the middle of 5 to 7. With this strategy, the **worst case** is either when `F = 7` and the egg never breaks, or when `F = 0` and the egg always breaks. But in both worst cases, you only need to try `log7` rounded up, which is 3. This is fewer than 7, so this is the **minimum** number of tries. Actually, if there is no limit on the number of eggs, binary search is clearly the best strategy. But in this problem, you **have a limit on the number of eggs `K`, so you cannot always use binary search**. For example, if you have only 1 egg and 7 floors, can you use binary search? If you drop the egg from the 4th floor and it does not break, you can try higher floors. But if it breaks, you have no eggs left and cannot test any more floors, so you cannot find the exact floor `F`. In this case, you can only use linear search, try floors one by one from the bottom. In the worst case, you need 7 tries, so the answer is 7. Some readers may think: Binary search eliminates floors the fastest. What if you use binary search first, and only switch to linear search when there is 1 egg left? Is this the best way? Unfortunately, no. For example, if there are 100 floors and 2 eggs, you drop the first egg at floor 50. If it breaks, you must do linear search from floor 1 to 49. In the worst case, you need 50 tries. If you don’t do "binary search", but do "divide by 5" or "divide by 10", you can reduce the number of tries. For example, drop the first egg every 10 floors. When it breaks, use the second egg to check the floors one by one. In this way, you need at most 20 tries. The best answer is actually 14. There are many possible good strategies, but there is no simple rule. After all this explanation, the goal is to make sure you understand the problem. This problem is really complex, even for humans. So how can we solve it with an algorithm? ## 2. Idea Analysis For dynamic programming problems, we can use the framework we mentioned before: figure out the "state" and "choice", then try all possibilities. The "state" here is simple. It is the number of eggs `K` you have now and the number of floors `N` you need to test. As you throw eggs, the number of eggs may go down, and the range of floors will get smaller. That is how the state changes. The "choice" is which floor you choose to throw the egg from. In the linear scan method, you try each floor one by one; in the binary search idea, you pick the middle floor each time. Different choices will change the state. Now that we know the "state" and the "choice", the basic idea of dynamic programming is clear: use a 2D `dp` array or a `dp` function with two parameters to represent the state. Add a for loop to try all choices and update the answer with the best choice: ```java // Definition: With K eggs and N floors, // return the minimum number of throws needed in this state int dp(int K, int N) { int res; for (int i = 1; i <= N; i++) { res = Math.min(res, throw egg from the i-th floor); } return res; } ``` This pseudocode does not show the recursion and state transition yet, but the main structure is done. When you throw the egg from the i-th floor, there are two possible results: the egg breaks, or it doesn't break. Now the state transitions: If the egg breaks, you have one less egg (`K-1`), and you only need to check the floors below (from `1` to `i-1`), which is `i-1` floors. If the egg does not break, the number of eggs stays the same, but you only need to check the floors above (from `i+1` to `N`), which is `N-i` floors. ![](../pictures/drop-egg/1.jpg) ::: note Note Some readers may ask: If the egg does not break on the i-th floor, should we include the i-th floor in the next search? The answer is no, because we already checked it. At the start, we said `F` can be 0, and after the recursion, the i-th floor is actually like the 0-th floor, so it is already included. ::: Because we want the minimum number of throws in the worst case, we need to consider the bigger result of the two situations (egg breaks or not): ```java int dp(int K, int N): for 1 <= i <= N: // Minimum throws in the worst case res = min(res, max( // Egg breaks dp(K - 1, i - 1), // Egg does not break dp(K, N - i), ) + 1 // Add one because we threw an egg from the i-th floor ) return res ``` The recursive base cases are easy. If there are 0 floors (`N == 0`), you need 0 throws. If you have only 1 egg (`K == 1`), you must check every floor one by one: ```java int dp(int K, int N) { // base case if (K == 1) return N; if (N == 0) return 0; // ... } ``` That solves the problem! Just add a memo table to remove repeated subproblems: ```java class Solution { // memoization int[][] memo; public int superEggDrop(int K, int N) { // m will not exceed N times (linear scan) memo = new int[K + 1][N + 1]; for (int[] row : memo) { Arrays.fill(row, -666); } return dp(K, N); } // definition: holding K eggs, facing N floors, the minimum number of egg drops is dp(K, N) int dp(int K, int N) { // base case if (K == 1) return N; if (N == 0) return 0; // check memo to avoid redundant calculations if (memo[K][N] != -666) { return memo[K][N]; } // state transition equation int res = Integer.MAX_VALUE; for (int i = 1; i <= N; i++) { // try on all floors, take the minimum number of egg drops res = Math.min( res, // take the worst case whether it breaks or not Math.max(dp(K, N - i), dp(K - 1, i - 1)) + 1 ); } // store the result in memo memo[K][N] = res; return res; } } ``` What is the time complexity of this algorithm? For dynamic programming, the time complexity is the number of subproblems times the cost of the function. The cost of the function itself is O(N), since there is a for loop in the `dp` function. The number of subproblems is the number of different states, which is O(KN). So the total time complexity is O(K*N^2), and the space complexity is O(KN). This problem is complicated, but the code is simple. That is the power of dynamic programming: try all possibilities, use a memo table to speed up, and get the answer. Some readers may not understand why we use a for loop to go through all floors `[1..N]`. You may think this is like linear scan, but it is not. We are just making a "choice" at each step. For example, if you have 2 eggs and 10 floors, which floor will you throw the egg from this time? We do not know, so we try all 10 floors. The recursion will handle the next steps and pick the best choice for us. There are better solutions to this problem. For example, you can use binary search in the for loop to make it O(K*N*logN). With more advanced dynamic programming, you can bring it down to O(KN). With math, you can reach O(K*logN) time and O(1) space. The binary search solution might confuse you. It has nothing to do with the binary search idea we discussed before. Here, binary search works because the function is monotonic, so we can quickly find the best value. Next, let's see how to optimize this. ## 3. Binary Search Optimization The key to optimizing binary search is the monotonicity of the state transition equation. Let's first briefly review the original dynamic programming idea: 1. Use brute-force to try dropping eggs on every floor `1 <= i <= N`. Each time, choose the floor that gives the **fewest** attempts. 2. Each time you drop an egg, there are two possibilities: the egg breaks or it does not break. 3. If the egg breaks, then `F` is below floor `i`. If it does not break, then `F` is above floor `i`. 4. Whether the egg breaks or not, we care about the **worst case**. So, we take the higher number of attempts between the two situations. The key state transition code is this: ```java // Current state: K eggs, N floors // Returns the best result for this state int dp(int K, int N): for 1 <= i <= N: // Fewest number of attempts in the worst case res = min(res, max( // Egg breaks dp(K - 1, i - 1), // Egg does not break dp(K, N - i), ) + 1 // Dropped an egg at floor i, so add one ) return res ``` This for loop is the code implementation of the following state transition formula: ![](../pictures/drop-egg/formula1.png) If you can understand this state transition formula, it will be easy to understand how binary search can be used to optimize it. According to the definition of the `dp(K, N)` array (with `K` eggs and `N` floors, the minimum number of attempts needed), **when `K` is fixed, this function increases as `N` increases**. No matter how smart your strategy is, if the number of floors increases, the number of attempts must increase. Now, look at the two functions `dp(K - 1, i - 1)` and `dp(K, N - i)`, where `i` goes from 1 to `N`. If we fix `K` and `N`, **treat these two functions as functions of `i`**: the first one increases as `i` increases, and the second one decreases as `i` increases: ![](../pictures/drop-egg/2.jpg) We want the minimum of the maximum value of these two functions. In other words, we are looking for the lowest point where the two curves cross, which is shown in red in the picture above. In the previous article [Binary Search in Real World](https://labuladong.online/en/algo/frequency-interview/binary-search-in-action/), we discussed that binary search is widely used. As long as you can find a function with monotonicity, you can often use binary search to optimize a linear search. Looking at the curves of these two `dp` functions, what we want is the lowest point, which looks like this: ```java for (int i = 1; i <= N; i++) { if (dp(K - 1, i - 1) == dp(K, N - i)) return dp(K, N - i); } ``` If you are familiar with binary search, you may notice that this is just finding a "valley" point. We can use binary search to quickly find this point. Here is the improved code. The linear search in the `dp` function is changed to binary search, making it much faster: ```java class Solution { // memoization int[][] memo; public int superEggDrop(int K, int N) { // m will not exceed N times (linear scan) memo = new int[K + 1][N + 1]; for (int[] row : memo) { Arrays.fill(row, -666); } return dp(K, N); } // definition: holding K eggs, facing N floors, the minimum number of egg drops is dp(K, N) int dp(int K, int N) { // base case if (K == 1) return N; if (N == 0) return 0; // check memo to avoid redundant calculations if (memo[K][N] != -666) { return memo[K][N]; } // for (int i = 1; i <= N; i++) { // res = Math.min( // res, // Math.max(dp(K, N - i), dp(K - 1, i - 1)) + 1 // ); // } // use binary search instead of linear search int res = Integer.MAX_VALUE; int lo = 1, hi = N; while (lo <= hi) { int mid = lo + (hi - lo) / 2; // two cases: the egg breaks at the mid floor or it does not break int broken = dp(K - 1, mid - 1); int not_broken = dp(K, N - mid); // res = min(max(broken, not_broken) + 1) if (broken > not_broken) { hi = mid - 1; res = Math.min(res, broken + 1); } else { lo = mid + 1; res = Math.min(res, not_broken + 1); } } memo[K][N] = res; return res; } } ``` What is the time complexity of this algorithm? **The time complexity of a dynamic programming algorithm is the number of subproblems times the complexity of the function itself.** The complexity of the function itself ignores the recursive part. Here, there is a binary search inside the `dp` function, so the function's complexity is O(logN). The number of subproblems is the total number of different states, which is O(KN). So, the total time complexity of the algorithm is O(KNlogN), and the space complexity is O(KN). This is more efficient than the previous O(KN^2) algorithm. ## 4. Redefining State Transition Finding the state transition for dynamic programming is not always clear. Different ways to define the state can lead to very different solutions and complexity. This is a good example. Let’s review our previous definition of the `dp` array: ```java int dp(int k, int n) // Current state: k eggs, facing n floors // Return the minimum number of throws needed in this state ``` Or using an array: ```java dp[k][n] = m // Current state: k eggs, facing n floors // The minimum number of throws needed in this state is m ``` With this definition, if you know the number of eggs and the number of floors, you can know the minimum number of throws needed. The final answer is `dp(K, N)`. In this way, you must try all possible ways to throw the eggs. Even if you use binary search to optimize, you are just pruning the search space, but the overall idea is still brute-force. Now, let's change the definition of the `dp` array a bit. **If you know the number of eggs and the maximum number of throws allowed, you can know the highest floor you can test for `F`.** This is what it means: ```java dp[k][m] = n // You have k eggs, and can throw them m times // In the worst case, you can test up to n floors // For example, dp[1][7] = 7 means: // You have 1 egg, and you are allowed 7 throws; // In this state, you can test up to 7 floors, // so you can find the floor F where the egg just doesn’t break // (You check one floor at a time) ``` This is like a "reverse" version of our original idea. Let's not worry about how to write the state transition for now. First, think about what answer we want under this definition. What we want to find is the minimum number of throws `m`. But now, `m` is part of the state, not the result. We can handle it like this: ```java int superEggDrop(int K, int N) { int m = 0; while (dp[K][m] < N) { m++; // state transition... } return m; } ``` The problem asks: **Given `K` eggs and `N` floors, what is the minimum number of throws needed in the worst case?** The `while` loop ends when `dp[K][m] == N`, which means: **Given `K` eggs and `m` throws, you can test up to `N` floors in the worst case.** Look at these two statements, they are exactly the same! So organizing the code this way is correct. The key is how to find the state transition equation. Let's start from our original idea. Before, we used a picture to help understand the state transition: ![](../pictures/drop-egg/1.jpg) This picture only shows one floor `i`. The original method needs to scan all floors linearly or with binary search to find min and max values. But with this new `dp` definition, you don’t need that anymore, because of these two facts: **1. No matter which floor you throw the egg from, the egg either breaks or does not break. If it breaks, you check the floors below. If it does not break, you check the floors above.** **2. The total number of floors = floors above + floors below + 1 (the current floor).** With this, we can write the state transition as: ```python dp[k][m] = dp[k][m - 1] + dp[k - 1][m - 1] + 1 ``` **`dp[k][m - 1]` is the number of floors above,** because the number of eggs `k` does not change (egg didn’t break), but the throws decrease by one. **`dp[k - 1][m - 1]` is the number of floors below,** because you have one less egg (egg broke), and the throws decrease by one. ::: note Note Why do we subtract one from `m`? The definition says `m` is the maximum number of throws allowed, not the number of throws used. ::: ![](../pictures/drop-egg/3.jpg) Now the whole idea is ready. Just put the state transition into the code framework: ```java class Solution { public int superEggDrop(int K, int N) { // m will not exceed N times (linear scan) int[][] dp = new int[K + 1][N + 1]; // base case: // dp[0][..] = 0 // dp[..][0] = 0 // Java initializes arrays to 0 by default int m = 0; while (dp[K][m] < N) { m++; for (int k = 1; k <= K; k++) dp[k][m] = dp[k][m - 1] + dp[k - 1][m - 1] + 1; } return m; } } ``` If this code is still hard to understand, it’s actually the same as this: ```java for (int m = 1; dp[K][m] < N; m++) for (int k = 1; k <= K; k++) dp[k][m] = dp[k][m - 1] + dp[k - 1][m - 1] + 1; ``` This code is easier to follow. We are not looking for a value in the `dp` array, but for an index `m` that meets the condition, so we use a `while` loop to find it. What is the time complexity of this algorithm? It's clearly O(KN), two nested loops. Also, note that `dp[k][m]` only depends on the left and upper-left states. You can use the space optimization trick from [Dynamic Programming Space Compression](https://labuladong.online/en/algo/dynamic-programming/space-optimization/) to make it a one-dimensional `dp` array. We won’t write that here. ## 5. Further Optimization We can optimize further, but I will just briefly mention the idea here. Based on the previous idea, **note that the function `dp(m, k)` increases as `m` increases, because with the same number of eggs `k`, more attempts allow you to test more floors**. Here, you can use binary search to quickly find when `dp[K][m] == N`. This will reduce the time complexity to O(KlogN). But I think writing an O(K\*N\*logN) binary search algorithm is enough. The later optimizations are just for expanding your thinking, and you just need to know about them. One thing is clear: you can replace the linear scan of `m` with binary search. The main code framework is to change the brute-force `while` loop over `m` to binary search: ```java // change linear search to binary search // for (int m = 1; dp[K][m] < N; m++) int lo = 1, hi = N; while (lo < hi) { int mid = (lo + hi) / 2; if (... < N) { lo = ...; } else { hi = ...; } for (int k = 1; k <= K; k++) { // state transition equation } } ``` To sum up, the first binary search optimization uses the monotonicity of the `dp` function to quickly search for the answer. The second optimization changes the state transition equation to simplify the process, but the logic is harder to come up with. You can also use some math methods and binary search to optimize the second solution, but it is not really necessary to learn them. ================================================ FILE: dynamic-programming/game-theory.md ================================================ ::: info Prerequisite Knowledge Before reading this article, you should first learn: - [Core Framework of Dynamic Programming](https://labuladong.online/en/algo/essential-technique/dynamic-programming-framework/) ::: In the previous article [A Few Brain Teasers](https://labuladong.online/en/algo/frequency-interview/one-line-solutions/), an interesting "Stone Game" was discussed, where, due to the constraints of the problem, the first player is guaranteed to win. However, brain teasers are ultimately just brain teasers; real algorithm problems cannot be solved through trickery. Therefore, this article uses the Stone Game to discuss how to solve problems of the type "assuming both players are smart, who will win in the end" using dynamic programming algorithms. The approach to game theory problems is generally similar. The explanation below references [this YouTube video](https://www.youtube.com/watch?v=WxpIHvsu1RI), where the core idea is to use tuples to store the results of both players' games on the basis of 2D dynamic programming. Once you master this technique, when someone asks you about two pirates dividing gems or two people taking coins, you can simply say: "I’m too lazy to think, I'll just write an algorithm to calculate it." We modify LeetCode problem 877, "[Stone Game](https://leetcode.com/problems/stone-game/)", to be more general: You and your friend have a row of stone piles represented by an array `piles`, where `piles[i]` represents the number of stones in the `i-th` pile. You take turns picking stones, one pile at a time, but can only take the leftmost or rightmost pile. Whoever has more stones after all are taken wins. The number of piles and the total number of stones can be any positive integer, thus breaking the first-player-win scenario. For example, with three piles `piles = [1, 100, 3]`, the first player, whether taking 1 or 3, will allow the second player to take the decisive 100, resulting in a win for the second player. **Assuming both players are very smart**, write a `stoneGame` function that returns the difference between the final scores (total stones) of the first and second players. For example, in the case above, the first player can get 4 points, and the second player can get 100 points, so your algorithm should return -96: ```java int stoneGame(int[] nums); ``` This generalization becomes a rather challenging dynamic programming problem. LeetCode problem 486, "[Predict the Winner](https://leetcode.com/problems/predict-the-winner/)", is a similar problem: **LeetCode 486. Predict the Winner** You are given an integer array `nums`. Two players are playing a game with this array: player 1 and player 2. Player 1 and player 2 take turns, with player 1 starting first. Both players start the game with a score of `0`. At each turn, the player takes one of the numbers from either end of the array (i.e., `nums[0]` or `nums[nums.length - 1]`) which reduces the size of the array by `1`. The player adds the chosen number to their score. The game ends when there are no more elements in the array. Return `true` if Player 1 can win the game. If the scores of both players are equal, then player 1 is still the winner, and you should also return `true`. You may assume that both players are playing optimally. Example 1:** ``` **Input:** nums = [1,5,2] **Output:** false **Explanation:** Initially, player 1 can choose between 1 and 2. If he chooses 2 (or 1), then player 2 can choose from 1 (or 2) and 5. If player 2 chooses 5, then player 1 will be left with 1 (or 2). So, final score of player 1 is 1 + 2 = 3, and player 2 is 5. Hence, player 1 will never be the winner and you need to return false. ``` Example 2:** ``` **Input:** nums = [1,5,233,7] **Output:** true **Explanation:** Player 1 first chooses 1. Then player 2 has to choose between 5 and 7. No matter which number player 2 choose, player 1 can choose 233. Finally, player 1 has more score (234) than player 2 (12), so you need to return True representing player1 can win. ``` **Constraints:** - `1 <= nums.length <= 20` - `0 <= nums[i] <= 10^(7)` The function signature is as follows: ```java boolean predictTheWinner(int[] nums); ``` If you have a `stoneGame` function that calculates the score difference between the first and second players, the solution to this problem is straightforward: ```java boolean predictTheWinner(int[] nums) { // If the first player's score is greater than or equal to the second player's, they can win return stoneGame(nums) >= 0; } ``` How should the `stoneGame` function be written? The challenge in game theory problems lies in the fact that two players take turns making choices, and both are extremely clever. How can we represent this process in programming? It's not difficult; just follow the routine emphasized multiple times in the [Core Framework of Dynamic Programming](https://labuladong.online/en/algo/essential-technique/dynamic-programming-framework/). First, clarify the meaning of the `dp` array, and then as long as you identify the "state" and "choice," everything will fall into place. ## 1. Define the Meaning of the `dp` Array Defining the meaning of the `dp` array is quite technical. The same problem can have multiple definitions, leading to different state transition equations. However, as long as the logic is correct, the same answer can be obtained in the end. I recommend not pursuing overly intricate solution ideas. It's better to be stable and adopt a solution that is most interpretable and easiest to generalize. This article provides a general design framework for game theory problems. Before introducing the meaning of the `dp` array, let's take a look at the final form of the `dp` array: ![](../pictures/stone-game/1.png) In the following explanation, assume that a tuple is a class containing `first` and `second` attributes, and for brevity, these two attributes are abbreviated as `fir` and `sec`. For example, with the data in the above image, we say `dp[1][3].fir = 11`, `dp[0][1].sec = 2`. Let's answer some questions readers might have: How to represent the tuple stored in this two-dimensional dp table in programming? How to optimize since half of this dp table is unused? It's simple: don't worry about it for now. First, understand the problem-solving approach, and discuss optimization later. **Explanation of the Meaning of the `dp` Array:** `dp[i][j].fir = x` means that for the stone piles `piles[i...j]`, the highest score the first player can achieve is `x`. `dp[i][j].sec = y` means that for the stone piles `piles[i...j]`, the highest score the second player can achieve is `y`. To understand with an example, assume `piles = [2, 8, 3, 5]`, with indexing starting from 0: `dp[0][1].fir = 8` means that facing stone piles `[2, 8]`, the first player can obtain at most 8 points; `dp[1][3].sec = 5` means that facing stone piles `[8, 3, 5]`, the second player can obtain at most 5 points. The answer we seek is the difference in final scores between the first and second players, according to this definition it is `dp[0][n-1].fir - dp[0][n-1].sec`, which is the difference between the optimal scores of the first and second players when facing the entire `piles`. ## 2. State Transition Equation Writing the state transition equation is straightforward. First, identify all the "states" and the "choices" available in each state, then choose optimally. According to the definition of the `dp` array, **there are evidently three states: starting index `i`, ending index `j`, and whose turn it is.** ```python dp[i][j][fir or sec] where: 0 <= i < piles.length i <= j < piles.length ``` For each state in this problem, there are **two choices: choose the leftmost pile of stones, or choose the rightmost pile of stones.** We can enumerate all states as follows: ```python n = piles.length for 0 <= i < n: for j <= i < n: for who in {fir, sec}: dp[i][j][who] = max(left, right) ``` The pseudocode above outlines a general framework for dynamic programming. The challenge in this problem is that both players are smart and alternate in making choices, meaning the first player's choice affects the second player. How do we express this? Based on our definition of the `dp` array, we can easily address this challenge and **write the state transition equation**: ```python dp[i][j].fir = max(piles[i] + dp[i+1][j].sec, piles[j] + dp[i][j-1].sec) dp[i][j].fir = max( choosing the leftmost pile , choosing the rightmost pile ) # Explanation: As the first player, when facing piles[i...j], I have two choices: # Either I choose the leftmost pile piles[i], changing the situation to piles[i+1...j], # then it's the opponent's turn, and I become the second player. My optimal score as the second player is dp[i+1][j].sec # Or I choose the rightmost pile piles[j], changing the situation to piles[i...j-1], # then it's the opponent's turn, and I become the second player. My optimal score as the second player is dp[i][j-1].sec if the first player chooses left: dp[i][j].sec = dp[i+1][j].fir if the first player chooses right: dp[i][j].sec = dp[i][j-1].fir # Explanation: As the second player, I have to wait for the first player to choose, with two scenarios: # If the first player chooses the leftmost pile, leaving me with piles[i+1...j], # now it's my turn, and I become the first player. My optimal score is dp[i+1][j].fir # If the first player chooses the rightmost pile, leaving me with piles[i...j-1], # now it's my turn, and I become the first player. My optimal score is dp[i][j-1].fir ``` Based on the definition of the dp array, we can also identify the **base case**, which is the simplest scenario: ```python dp[i][j].fir = piles[i] dp[i][j].sec = 0 where 0 <= i == j < n # Explanation: i and j being equal means there's only one pile of stones piles[i] in front. # Obviously, the first player's score is piles[i] # The second player has no stones to take, so the score is 0 ``` ![](../pictures/stone-game/2.png) One thing to note here is that we find the base case is diagonal, and when calculating `dp[i][j]`, we need `dp[i+1][j]` and `dp[i][j-1]`: ![](../pictures/stone-game/3.png) According to the principle of determining the traversal direction of the `dp` array from the previous article [Dynamic Programming Q&A](https://labuladong.online/en/algo/dynamic-programming/faq-summary/), the algorithm should traverse the `dp` array backwards: ```java for (int i = n - 2; i >= 0; i--) { for (int j = i + 1; j < n; j++) { dp[i][j] = ... } } ``` ![](../pictures/stone-game/4.png) ## 3. Code Implementation How to implement the `fir` and `sec` tuples? You can use Python's built-in tuple type, or use C++'s `pair` container, or employ a three-dimensional array `dp[n][n][2]`, where the last dimension acts as a tuple. Alternatively, we can write a custom `Pair` class: ```java class Pair { int fir, sec; Pair(int fir, int sec) { this.fir = fir; this.sec = sec; } } ``` Then, directly translate our state transition equation into code, remembering to iterate the array in reverse: ```java // Return the score difference between the first and second player at the end of the game int stoneGame(int[] piles) { int n = piles.length; // Initialize the dp array Pair[][] dp = new Pair[n][n]; for (int i = 0; i < n; i++) for (int j = i; j < n; j++) dp[i][j] = new Pair(0, 0); // Fill in the base case for (int i = 0; i < n; i++) { dp[i][i].fir = piles[i]; dp[i][i].sec = 0; } // Traverse the array in reverse order for (int i = n - 2; i >= 0; i--) { for (int j = i + 1; j < n; j++) { // The first player chooses the score from the leftmost or rightmost int left = piles[i] + dp[i+1][j].sec; int right = piles[j] + dp[i][j-1].sec; // Apply the state transition equation // The first player will definitely choose the larger result, and the second player's choice will change accordingly if (left > right) { dp[i][j].fir = left; dp[i][j].sec = dp[i+1][j].fir; } else { dp[i][j].fir = right; dp[i][j].sec = dp[i][j-1].fir; } } } Pair res = dp[0][n-1]; return res.fir - res.sec; } ``` Dynamic programming solutions can be quite confusing without the guidance of state transition equations. However, with the detailed explanation provided earlier, readers should clearly understand the essence of this code segment. Moreover, note that calculating `dp[i][j]` only depends on elements to its left and below, indicating room for optimization by transforming it into a one-dimensional `dp`. Imagine flattening the two-dimensional plane, projecting it onto one dimension. However, one-dimensional `dp` is complex and less interpretable, so you need not spend time understanding it. ## 4. Conclusion This article presents a dynamic programming solution for solving game problems. The premise of game problems generally involves two intelligent individuals. The typical method for describing such games in programming is using a two-dimensional `dp` array, where tuples in the array represent each participant's optimal decisions. The reason for such design is that after the first player makes a choice, they become the second player, and after the second player completes their choice, they become the first player. **This role reversal allows us to reuse previous results, a hallmark of dynamic programming**. Readers at this point should understand the algorithmic approach to solving game problems. When learning algorithms, focus on the template framework rather than seemingly sophisticated ideas, and don't expect to write an optimal solution immediately. Don't hesitate to use more space, avoid premature optimization, and don't fear multidimensional arrays. The `dp` array is for storing information to avoid redundant calculations. Use it freely until you are satisfied. ================================================ FILE: dynamic-programming/house-robber.md ================================================ ::: info Prerequisites Before reading this article, you should first learn: - [Binary Tree Algorithms (Overview)](https://labuladong.online/en/algo/essential-technique/binary-tree-summary/) - [Core Framework of Dynamic Programming](https://labuladong.online/en/algo/essential-technique/dynamic-programming-framework/) ::: Today, let's talk about the "House Robber" series of problems. This is a classic and skillful dynamic programming problem. The House Robber series has three problems, and the difficulty increases step by step. The first is a standard dynamic programming problem. The second adds a circular array condition. The third combines bottom-up and top-down dynamic programming with binary trees, which is very inspiring. Let's start with the first problem. ## House Robber I LeetCode 198 "[House Robber](https://leetcode.com/problems/house-robber/)" is described as follows: There is a row of houses. Each house has some cash, given as a non-negative integer array `nums`, where `nums[i]` is the amount in the i-th house. You are a professional thief. You want to steal as much cash as possible, but **you cannot steal from two adjacent houses**. Otherwise, the alarm will go off. Write an algorithm to calculate the maximum amount of cash you can steal without triggering the alarm. The function signature is: ```java int rob(int[] nums); ``` For example, if the input is `nums = [2,1,7,9,3,1]`, the algorithm should return 12. The thief can steal from `nums[0]`, `nums[3]`, and `nums[5]`, getting 2 + 9 + 1 = 12, which is the best choice. The problem is easy to understand, and it is clearly a dynamic programming problem. As summarized in [Dynamic Programming Details](https://labuladong.online/en/algo/essential-technique/dynamic-programming-framework/), **to solve a dynamic programming problem, you just need to find the "state" and the "choices".** Imagine you are a professional robber. You walk from left to right along a row of houses. At each house, you have two choices: rob it or skip it. If you rob this house, you cannot rob the next house. You must start making choices again from the house after the next. If you skip this house, you can go to the next house and keep making choices. When you have passed the last house, there is nothing left to rob, so the money you can get is 0 (this is the base case). This logic is simple. It shows the "state" and the "choices": the index of the house in front of you is the state, and robbing or skipping is your choice. ![](../pictures/robber/1.jpg) Each time, pick the bigger result between the two choices. In the end, you get the maximum amount of money you can rob: ```java class Solution { // main function public int rob(int[] nums) { return dp(nums, 0); } // definition: return the maximum value that can be robbed from nums[start..] private int dp(int[] nums, int start) { if (start >= nums.length) { return 0; } int res = Math.max( // do not rob, go to the next house dp(nums, start + 1), // rob, go to the house after the next nums[start] + dp(nums, start + 2) ); return res; } } ``` After understanding the state transitions, you can see there are overlapping subproblems for the same `start` position. For example: ![](../pictures/robber/2.jpg) There are many ways for the robber to reach this position. If you enter recursion every time, you waste time. So, there are overlapping subproblems, and you can use memoization to optimize it: ```java class Solution { private int[] memo; // main function public int rob(int[] nums) { // initialize the memoization array memo = new int[nums.length]; Arrays.fill(memo, -1); // the robber starts robbing from the 0th house return dp(nums, 0); } // definition: return the maximum amount that can be robbed from dp[start..] private int dp(int[] nums, int start) { if (start >= nums.length) { return 0; } // avoid repeated calculation if (memo[start] != -1) return memo[start]; int res = Math.max( dp(nums, start + 1), dp(nums, start + 2) + nums[start] ); // record in the memoization array memo[start] = res; return res; } } ``` This is the top-down dynamic programming solution. We can also make some changes to write a **bottom-up** solution: ```java class Solution { public int rob(int[] nums) { int n = nums.length; // dp[i] = x means: // starting from the i-th house, the maximum amount of money that can be robbed is x // base case: dp[n] = 0 int[] dp = new int[n + 2]; for (int i = n - 1; i >= 0; i--) { dp[i] = Math.max(dp[i + 1], nums[i] + dp[i + 2]); } return dp[0]; } } ``` We can see that the state transition only depends on the last two states `dp[i]`. So, we can further optimize and reduce the space complexity to O(1): ```java class Solution { public int rob(int[] nums) { int n = nums.length; // record dp[i+1] and dp[i+2] int dp_i_1 = 0, dp_i_2 = 0; // record dp[i] int dp_i = 0; for (int i = n - 1; i >= 0; i--) { dp_i = Math.max(dp_i_1, nums[i] + dp_i_2); dp_i_2 = dp_i_1; dp_i_1 = dp_i; } return dp_i; } } ``` This process is explained in detail in our [Dynamic Programming Detailed Explanation](https://labuladong.online/en/algo/essential-technique/dynamic-programming-framework/). I believe you can master it easily. What is interesting is the follow-up to this problem, which needs some clever changes based on our current thinking. ## House Robber II LeetCode Problem 213 "[House Robber II](https://leetcode.com/problems/house-robber-ii/)" is almost the same as the previous problem. The thief still cannot rob two adjacent houses, and the input is still an array. But now, the houses are arranged in a circle, not in a straight line. This means the first and last houses are also neighbors, so they cannot both be robbed. For example, if the input array is `nums=[2,3,2]`, the answer should be 3, not 4, because you cannot rob both the first and last house. This new rule is not hard to handle. In our previous article [Monotonic Stack Problems](https://labuladong.online/en/algo/data-structure/monotonic-stack/), we talked about how to deal with circular arrays. So how do we solve this here? Since the first and last houses cannot be robbed together, there are only three cases: 1. Rob neither the first nor the last house; 2. Rob the first house, but not the last; 3. Rob the last house, but not the first. ![](../pictures/robber/3.jpg) It's simple. Just find the maximum result among these three cases. Actually, we only need to compare case 2 and case 3. **These two cases give us more choices than case 1. Since all house values are non-negative, more choices mean the result will not be worse.** So we only need to slightly change the previous solution: ```java class Solution { public int rob(int[] nums) { int n = nums.length; if (n == 1) return nums[0]; return Math.max(robRange(nums, 0, n - 2), robRange(nums, 1, n - 1)); } // Definition: return the maximum value that can be robbed in the closed interval [start, end] int robRange(int[] nums, int start, int end) { int n = nums.length; int dp_i_1 = 0, dp_i_2 = 0; int dp_i = 0; for (int i = end; i >= start; i--) { dp_i = Math.max(dp_i_1, nums[i] + dp_i_2); dp_i_2 = dp_i_1; dp_i_1 = dp_i; } return dp_i; } } ``` Now, the second problem is solved. ## House Robber III LeetCode Problem 337 ["House Robber III"](https://leetcode.com/problems/house-robber-iii/) gives a new twist to the problem. The robber now faces houses arranged as a binary tree, not in a row or a circle. The houses are the nodes of the tree. Two connected houses cannot be robbed at the same time. This really is a smart thief! The function signature is as follows: ```java int rob(TreeNode root); ``` For example, if the input is the following binary tree: ```yaml BinaryTree root: value: 3 left: value: 2 right: value: 3 right: value: 3 right: value: 1 ``` The algorithm should return 7, because robbing the first and third levels gives the highest amount: 3 + 3 + 1 = 7. If the input is this binary tree: ```yaml BinaryTree root: value: 3 left: value: 4 left: value: 1 right: value: 3 right: value: 5 right: value: 1 ``` The algorithm should return 9, because robbing the second level gets the most money: 4 + 5 = 9. The main idea is still the same: at each node, we choose to rob it or not, and pick the better option. We can write the code directly based on this idea: ```java class Solution { Map memo = new HashMap<>(); public int rob(TreeNode root) { if (root == null) return 0; // use memoization to eliminate overlapping subproblems if (memo.containsKey(root)) return memo.get(root); // rob this house, then go to the next next house int do_it = root.val + (root.left == null ? 0 : rob(root.left.left) + rob(root.left.right)) + (root.right == null ? 0 : rob(root.right.left) + rob(root.right.right)); // do not rob this house, then go to the next house int not_do = rob(root.left) + rob(root.right); int res = Math.max(do_it, not_do); memo.put(root, res); return res; } } ``` Let's look at the time complexity. Although the recursion looks like a four-way tree, with memoization, each node is only visited once. So the time complexity is $O(N)$, where $N$ is the number of nodes in the tree. The space complexity is also $O(N)$ because of the memoization. If you are confused about time or space complexity, you can read [A Practical Guide to Time and Space Complexity](https://labuladong.online/en/algo/essential-technique/complexity-analysis/). But there is an even better solution. For example, one reader commented with this solution: ```java class Solution { int rob(TreeNode root) { int[] res = dp(root); return Math.max(res[0], res[1]); } // return an array of size 2, arr // arr[0] represents the maximum amount of money obtained without robbing root // arr[1] represents the maximum amount of money obtained by robbing root int[] dp(TreeNode root) { if (root == null) return new int[]{0, 0}; int[] left = dp(root.left); int[] right = dp(root.right); // if we rob, the next house cannot be robbed int rob = root.val + left[0] + right[0]; // if we do not rob, the next house can either be robbed or not, depending on the profit int not_rob = Math.max(left[0], left[1]) + Math.max(right[0], right[1]); return new int[]{not_rob, rob}; } } ``` The time complexity is still $O(N)$, but the space complexity is only the stack space for the recursion, which is the height of the tree $O(H)$. No extra space for memoization is needed. This solution is a bit different. It changes the definition of the recursive function and adjusts the logic, but still gets the right answer with cleaner code. This is a good example of using post-order traversal, which we discussed in [Binary Tree Thinking (Overview)](https://labuladong.online/en/algo/essential-technique/binary-tree-summary/). In fact, this solution is much faster in practice, even though the time complexity is the same. This is because it does not use extra memoization, so it has less overhead and is more efficient. ================================================ FILE: dynamic-programming/interval-scheduling.md ================================================ What is a greedy algorithm? You can think of a greedy algorithm as a special case of dynamic programming. Compared to dynamic programming, a greedy algorithm needs more conditions to work (the greedy choice property), but if it works, it is faster. For example, suppose a problem needs exponential time with a brute-force solution. If you can use dynamic programming to remove overlapping subproblems, the time can drop to polynomial. If the problem also satisfies the greedy choice property, you can further reduce the time complexity to linear. What is the greedy choice property? Simply put: at each step, if you make the best local choice, the final result is also the global best. Note that this is a special property, and only some problems have it. For example, you have 100 RMB bills in front of you, and you can only pick 10. How do you get the largest total amount? Clearly, each time you should pick the bill with the largest value among the remaining ones. In the end, your choice will be optimal. However, most problems do not have the greedy choice property. For example, in the card game Dou Dizhu, if your opponent plays a pair of 3s, a greedy strategy would be to play the smallest pair that can beat it. But in real situations, it depends on the opponent’s cards and strategy; you might even play the strongest combination “rocket” (the two jokers). In such cases, you cannot use a greedy algorithm and instead need a more complex brute-force algorithm to find the optimal solution. ## 1. Problem Overview Now back to the main topic. This article solves a classic greedy algorithm problem, Interval Scheduling, which is LeetCode Problem 435 “[Non-overlapping Intervals](https://leetcode.com/problems/non-overlapping-intervals/)”: You are given many closed intervals of the form `[start, end]`. Please design an algorithm to **find the maximum number of intervals that are mutually non-overlapping**. ```java int intervalSchedule(int[][] intvs); ``` For example, `intvs = [[1,3], [2,4], [3,6]]`. Among these intervals, the maximum number of non-overlapping intervals is 2, that is `[[1,3], [3,6]]`. Your algorithm should return 2. Note that intervals that only touch at the boundary are not considered overlapping. This problem has many real-world uses. For example, you have several events today. Each event can be represented as an interval `[start, end]` for its start time and end time. **What is the maximum number of events you can attend today?** Clearly, you cannot attend two events at the same time. So this problem is to find the largest subset of these time intervals that do not overlap. ## 2. Greedy Solution There are many greedy ideas for this problem that look good but do not give the correct answer. For example: Maybe we can always choose the interval with the earliest start time? But some intervals may start early but are very long, causing us to miss some short intervals. Or maybe choose the shortest interval each time? Or the interval with the fewest conflicts? All of these ideas have counterexamples and are not correct. The correct solution is actually simple and has three steps: 1. From the set of intervals `intvs`, choose one interval `x` which ends the earliest (the smallest `end` value). 2. Remove all intervals that overlap with `x` from `intvs`. 3. Repeat step 1 and 2 until `intvs` is empty. All the selected `x` intervals form the largest set of non-overlapping intervals. To implement this idea, we can sort all intervals by their `end` value in ascending order. After sorting, both step 1 and step 2 are easy to do, as shown in the GIF below: ![](../pictures/interval/1.gif) Now let's implement the algorithm. For step 1, since we already sorted by `end`, it is easy to choose `x`. The key point is how to remove all intervals that overlap with `x` and select the next `x` for the next round. **Because we sorted the intervals**, all intervals that overlap with `x` must have a `start` value less than `x`'s `end`. If an interval does not overlap with `x`, its `start` must be greater than or equal to `x`'s `end`: ![](../pictures/interval/2.jpg) Here is the code: ```java class Solution { public int intervalSchedule(int[][] intvs) { if (intvs.length == 0) return 0; // sort by end in ascending order Arrays.sort(intvs, (a, b) -> Integer.compare(a[1], b[1])); // at least one interval does not intersect int count = 1; // after sorting, the first interval is x int x_end = intvs[0][1]; for (int[] interval : intvs) { int start = interval[0]; if (start >= x_end) { // found the next selected interval count++; x_end = interval[1]; } } return count; } } ``` ## 3. Application Examples Let's look at some examples of how to use the interval scheduling algorithm. First is LeetCode 435 "[Non-overlapping Intervals](https://leetcode.com/problems/non-overlapping-intervals/)": You are given a set of intervals. Find out the minimum number of intervals you need to remove so that the rest do not overlap. The function signature is: ```java int eraseOverlapIntervals(int[][] intvs); ``` You can assume that the end point of each interval is always greater than the start point. If two intervals only touch at the boundary, they are not considered overlapping. For example, if the input is `intvs = [[1,2],[2,3],[3,4],[1,3]]`, the algorithm should return 1. This is because if you remove `[1,3]`, the remaining intervals do not overlap. We already know how to find the maximum number of non-overlapping intervals. The rest are the intervals we need to remove. ```java class Solution { public int eraseOverlapIntervals(int[][] intvs) { int n = intvs.length; return n - intervalSchedule(intvs); } private int intervalSchedule(int[][] intvs) { // see above } } ``` Next, let's talk about LeetCode 452 "[Minimum Number of Arrows to Burst Balloons](https://leetcode.com/problems/minimum-number-of-arrows-to-burst-balloons/)". Here is the problem: Imagine there are many round balloons on a 2D plane. If you look at their shadow on the x-axis, you get some intervals. You are given these intervals. As you move along the x-axis, you can shoot an arrow straight up. What is the minimum number of arrows needed to burst all the balloons? The function signature is: ```java int findMinArrowShots(int[][] intvs); ``` For example, if the input is `[[10,16],[2,8],[1,6],[7,12]]`, the algorithm should return 2. You can shoot one arrow at x = 6, which bursts `[2,8]` and `[1,6]`. Then shoot another arrow at x = 10, 11, or 12 to burst `[10,16]` and `[7,12]`. If you think about it, this problem is exactly the same as the interval scheduling problem! If there are at most `n` non-overlapping intervals, you need at least `n` arrows to burst all balloons: ![](../pictures/interval/3.jpg) There is one small difference: In the `intervalSchedule` algorithm, intervals that only touch at the boundary do not overlap. But in this problem, if an arrow touches the edge of a balloon, the balloon will burst. So, intervals that touch at the boundary are considered overlapping: ![](../pictures/interval/4.jpg) So, by making a small change to our previous algorithm, we can solve this problem: ```java class Solution { public int findMinArrowShots(int[][] intvs) { if (intvs.length == 0) return 0; Arrays.sort(intvs, (a, b) -> Integer.compare(a[1], b[1])); int count = 1; int x_end = intvs[0][1]; for (int[] interval : intvs) { int start = interval[0]; // just change >= to > if (start > x_end) { count++; x_end = interval[1]; } } return count; } } ``` ================================================ FILE: dynamic-programming/knapsack.md ================================================ ::: info Prerequisite Knowledge Before reading this article, you should first study: - [Core Framework of Dynamic Programming](https://labuladong.online/en/algo/essential-technique/dynamic-programming-framework/) ::: Many people frequently ask about the knapsack problem. Actually, it's not difficult. Using the dynamic programming approach, it's just about states and choices—nothing special. Today, let's discuss the knapsack problem, focusing on the most common 0-1 knapsack problem. Problem description: You have a knapsack with a capacity of `W`, and `N` items. Each item has a weight and a value. The weight of the ith item is `wt[i]`, and its value is `val[i]`. You can put items into the knapsack, but each item can only be used once. What is the maximum total value you can carry in the knapsack without exceeding its capacity? ![](../pictures/knapsack/1.png) Here's a simple example. Input: ```py N = 3, W = 4 wt = [2, 1, 3] val = [4, 2, 3] ``` The algorithm returns 6. You can put the first two items into the knapsack, with a total weight of 3, which is less than `W`, and the maximum value you can get is 6. That's the whole problem—a classic dynamic programming problem. The items cannot be divided; you either put an item in the knapsack or you don't. You can't split an item. This is where the name 0-1 knapsack comes from. There is no clever sorting or shortcut to solve this problem. You have to try all possible combinations. Following the pattern from our [Dynamic Programming Detailed Explanation](https://labuladong.online/en/algo/essential-technique/dynamic-programming-framework/), just follow the process step by step. ## Standard DP Pattern In almost every dynamic programming article, we repeat this pattern. All DP problems in past articles follow this pattern. **Step 1: Make clear two things: "state" and "choice".** First, the state. How do we describe the situation of the problem? If we are given some items and a capacity limit of a bag, we get a knapsack problem. **So we have two states: "capacity of the knapsack" and "items we can choose".** Next, the choice. This is also easy. For each item, what can you do? **The choice is "put it into the knapsack" or "do not put it into the knapsack".** Once you understand the states and choices, the DP problem is almost solved. For a bottom-up solution, the general code pattern is: ```python for 状态1 in 状态1的所有取值: for 状态2 in 状态2的所有取值: for ... dp[状态1][状态2][...] = 择优(选择1,选择2...) ``` **Step 2: Define the `dp` array.** Look at the states we found. There are two. So we need a 2D `dp` array. ::: important Definition of `dp` in the knapsack problem `dp[i][w]` is defined as: for the first `i` items, if the current knapsack capacity is `w`, then the maximum value we can get is `dp[i][w]`. For example, if `dp[3][5] = 6`, it means: from the first 3 items, when the knapsack capacity is 5, the maximum value we can put into the bag is 6. ::: Why define it like this? Because this helps us find the state transition. You can remember this as a pattern for knapsack-like problems. When you see similar problems, you can try this definition. With this definition, the final answer we want is `dp[N][W]`. The base cases are `dp[0][..] = 0` and `dp[..][0] = 0`, because if there are no items or no capacity, the max value is 0. Now refine the framework: ```python 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] ``` **Step 3: Use the "choices" to write the state transition.** We now ask: how do we write - "put item `i` into the knapsack" - "do not put item `i` into the knapsack" in code? We go back to the definition of `dp` and see how these choices change the state. Recall the definition: `dp[i][w]` means: for the first `i` items (indexed from 1), when the capacity is `w`, the maximum value is `dp[i][w]`. **If you do not put item `i` into the knapsack**, then clearly `dp[i][w]` should just be `dp[i-1][w]`. You just keep the previous result. **If you do put item `i` into the knapsack**, then `dp[i][w]` should be `val[i-1] + dp[i-1][w - wt[i-1]]`. Array indices start from 0, but our `i` starts from 1. So `val[i-1]` and `wt[i-1]` are the value and weight of item `i`. If you choose to put item `i` into the bag: - you get its value `val[i-1]` - with the remaining capacity `w - wt[i-1]` - you can only choose from the first `i-1` items So the best you can do is `dp[i-1][w - wt[i-1]]`. Add them together: `val[i-1] + dp[i-1][w - wt[i-1]]`. These are the two choices. Now we have the state transition equation and can refine the code: ```python for i in [1..N]: for w in [1..W]: dp[i][w] = max( dp[i-1][w], dp[i-1][w - wt[i-1]] + val[i-1] ) return dp[N][W] ``` **Step 4: Translate the pseudocode into real code and handle edge cases.** Here is Java code that implements the idea and handles the case where `w - wt[i-1]` might be negative (which would cause an index error): ```java int knapsack(int W, int[] wt, int[] val) { int N = wt.length; // base case has been initialized int[][] dp = new int[N + 1][W + 1]; for (int i = 1; i <= N; i++) { for (int w = 1; w <= W; w++) { if (w - wt[i - 1] < 0) { // in this case, we can only choose not to put it in the backpack dp[i][w] = dp[i - 1][w]; } else { // choose the better option between putting it in the backpack or not dp[i][w] = Math.max( dp[i - 1][w - wt[i-1]] + val[i-1], dp[i - 1][w] ); } } } return dp[N][W]; } ``` ## Space Optimization We can see that `dp[i][..]` only depends on `dp[i-1][..]`. So we can do [space compression for DP](https://labuladong.online/en/algo/dynamic-programming/space-optimization/) to reduce space. More concretely, we do not need a 2D array with `N` rows. We only need a 2D array with 2 rows: `dp[2][W+1]`. When we compute row `i`, we only use row `(i-1) % 2`. ```java // Rolling array optimization int[][] dp = new int[2][W+1]; for (int i = 1; i <= N; i++) { int curr = i % 2; int prev = (i + 1) % 2; for (int w = 1; w <= W; w++) { if (w - wt[i-1] < 0) { // In this case, we can only choose not to put it into the bag dp[curr][w] = dp[prev][w]; } else { // Put it in or not, take the better one dp[curr][w] = Math.max( dp[prev][w], dp[prev][w - wt[i-1]] + val[i-1] ); } } } return dp[N % 2][W]; ``` More often, we compress it further into a 1D array, but then you must be careful with the loop order. If you are interested, you can click the link to learn more. Now the knapsack problem is solved. Compared to other DP problems, this one is quite simple, because the state transition is very natural. Once you make the `dp` definition clear, the transition almost follows directly. ================================================ FILE: dynamic-programming/longest-common-subsequence.md ================================================ ::: info Prerequisite Knowledge Before reading this article, you need to learn: - [Dynamic Programming Core Framework](https://labuladong.online/en/algo/essential-technique/dynamic-programming-framework/) ::: I'm not sure how everyone feels about solving algorithm problems, but I've concluded that the technique is to break down a large problem into a small point, study how to solve the problem at this small point, and then expand it to the whole problem through recursion/iteration. For example, in our previous article [Binary Tree Series Part 3](https://labuladong.online/en/algo/data-structure/binary-tree-part3/), when solving binary tree problems, we break down the entire problem to a specific node, imagine standing at that node, figure out what needs to be done, and then apply the binary tree recursion framework. The same applies to dynamic programming problems, especially those related to subsequences. **This article starts with the "Longest Common Subsequence Problem," summarizing three subsequence problems**. By carefully discussing this type of problem, you can grasp this way of thinking. ## Longest Common Subsequence Calculating the Longest Common Subsequence (LCS) is a classic dynamic programming problem, as seen in LeetCode Problem 1143 "[Longest Common Subsequence](https://leetcode.com/problems/longest-common-subsequence/)": Given two input strings `s1` and `s2`, find their longest common subsequence and return its length. The function signature is as follows: ```java int longestCommonSubsequence(String s1, String s2); ``` For instance, if `s1 = "zabcde", s2 = "acez"`, their longest common subsequence is `lcs = "ace"`, with a length of 3, so the algorithm returns 3. If you haven't solved this problem before, a simple brute-force algorithm would be to enumerate all subsequences of `s1` and `s2`, check for common ones, and then find the longest among them. Obviously, this approach has a very high complexity, as enumerating all subsequences is exponential, making it impractical. The correct approach is not to consider the entire strings, but to focus on each character of `s1` and `s2`. As summarized in the previous article [Subsequence Problem Template](https://labuladong.online/en/algo/dynamic-programming/subsequence-problem/): **For problems involving finding subsequences of two strings, using two pointers `i` and `j` to move through the strings usually indicates a dynamic programming approach**. The problem of finding the longest common subsequence can also follow this pattern. We can start by writing a `dp` function: ```java // Definition: Calculate the length of the longest common subsequence of s1[i..] and s2[j..] int dp(String s1, int i, String s2, int j) ``` According to the definition of this `dp` function, the answer we seek is `dp(s1, 0, s2, 0)`, with the base case being when `i == len(s1)` or `j == len(s2)`, since at this point, `s1[i..]` or `s2[j..]` is equivalent to an empty string, and the length of the longest common subsequence is obviously 0: ```java int longestCommonSubsequence(String s1, String s2) { return dp(s1, 0, s2, 0); } // Definition: Calculate the length of the longest common subsequence of s1[i..] and s2[j..] int dp(String s1, int i, String s2, int j) { // base case if (i == s1.length() || j == s2.length()) { return 0; } // ... } ``` **Next, we should focus not on the entire strings `s1` and `s2`, but on each individual character, considering what each character should do**. ![](../pictures/LCS/1.jpeg) We look at `s1[i]` and `s2[j]`, **if `s1[i] == s2[j]`, it indicates that this character is definitely in the `lcs`**: ![](../pictures/LCS/2.jpeg) This way, a character in the `lcs` is found. Based on the definition of the `dp` function, we can refine the code: ```java // Definition: Calculate the length of the longest common subsequence of s1[i..] and s2[j..] int dp(String s1, int i, String s2, int j) { if (s1.charAt(i) == s2.charAt(j)) { // s1[i] and s2[j] must be in the lcs, // plus the lcs length of s1[i+1..] and s2[j+1..], which is the answer return 1 + dp(s1, i + 1, s2, j + 1); } else { // ... } } ``` Earlier, we discussed the case of `s1[i] == s2[j]`, but what should we do if `s1[i] != s2[j]`? **`s1[i] != s2[j]` means at least one character from `s1[i]` or `s2[j]` is not in the `lcs`:** ![](../pictures/LCS/3.jpeg) As shown above, there are three possible situations. How do we determine which one it is? In fact, we don't know, so we calculate the answers for all three situations and take the largest result because the problem requires us to find the length of the "longest" common subsequence. How do we calculate the answers for these three situations? Recall the definition of our `dp` function, which is specifically designed for this purpose! The code can be further refined: ```java // Definition: Calculate the length of the longest common subsequence of s1[i..] and s2[j..] int dp(String s1, int i, String s2, int j) { if (s1.charAt(i) == s2.charAt(j)) { return 1 + dp(s1, i + 1, s2, j + 1); } else { // At least one character from s1[i] and s2[j] is not in the lcs, // Enumerate the results of the three cases and take the maximum result return max( /** ![](../pictures/LCS/3.jpeg) */ // Case 1: s1[i] is not in the lcs dp(s1, i + 1, s2, j), // Case 2: s2[j] is not in the lcs dp(s1, i, s2, j + 1), // Case 3: neither is in the lcs dp(s1, i + 1, s2, j + 1) ); } } ``` This is already very close to our final answer. **There is a small optimization, the situation where "neither `s1[i]` nor `s2[j]` is in the lcs" can actually be ignored.** Since we are looking for the maximum value, the length of `lcs` calculated in situation three with `s1[i+1..]` and `s2[j+1..]` is definitely less than or equal to the `lcs` length in situation two with `s1[i..]` and `s2[j+1..]`, because `s1[i+1..]` is shorter than `s1[i..]`, and therefore the `lcs` calculated from it cannot be longer. Similarly, the result of situation three is definitely less than or equal to situation one. **In short, situation three is covered by situation one and situation two,** so we can directly ignore situation three, and the complete code is as follows: ```java class Solution { // memoization to eliminate overlapping subproblems int[][] memo; // main function public int longestCommonSubsequence(String s1, String s2) { int m = s1.length(), n = s2.length(); // memoization value of -1 represents not yet calculated memo = new int[m][n]; for (int[] row : memo) Arrays.fill(row, -1); // calculate the lcs length of s1[0..] and s2[0..] return dp(s1, 0, s2, 0); } // definition: calculate the longest common subsequence length of s1[i..] and s2[j..] int dp(String s1, int i, String s2, int j) { // base case if (i == s1.length() || j == s2.length()) { return 0; } // if calculated before, return the answer from the memoization directly if (memo[i][j] != -1) { return memo[i][j]; } // make a choice based on the situation of s1[i] and s2[j] if (s1.charAt(i) == s2.charAt(j)) { // s1[i] and s2[j] must be in the lcs memo[i][j] = 1 + dp(s1, i + 1, s2, j + 1); } else { // at least one of s1[i] and s2[j] is not in the lcs memo[i][j] = Math.max( dp(s1, i + 1, s2, j), dp(s1, i, s2, j + 1) ); } return memo[i][j]; } } ``` This approach follows our previous popular article [Dynamic Programming Framework](https://labuladong.online/en/algo/essential-technique/dynamic-programming-framework/) and should be easy to understand. As for why we add a `memo` for memoization, we've written about it many times before. For new readers, here's a brief repetition: first, abstract the recursive framework of our core `dp` function: ```java int dp(int i, int j) { dp(i + 1, j + 1); // #1 dp(i, j + 1); // #2 dp(i + 1, j); // #3 } ``` You see, assuming I want to transition from `dp(i, j)` to `dp(i+1, j+1)`, there is more than one way to do so. You can go directly via `#1`, or through `#2 -> #3`, or through `#3 -> #2`. This is the overlapping subproblem. If we do not use `memo` for memoization to eliminate subproblems, then `dp(i+1, j+1)` will be calculated multiple times, which is unnecessary. At this point, the longest common subsequence problem is completely solved, using a top-down dynamic programming approach with memoization. We can also use a bottom-up iterative dynamic programming approach. The key is how to define the `dp` array, and here is the bottom-up solution: ```java class Solution { public int longestCommonSubsequence(String s1, String s2) { int m = s1.length(), n = s2.length(); // definition: the length of lcs of s1[0..i-1] and s2[0..j-1] is dp[i][j] int[][] dp = new int[m + 1][n + 1]; // goal: the length of lcs of s1[0..m-1] and s2[0..n-1] is dp[m][n] // base case: dp[0][..] = dp[..][0] = 0 for (int i = 1; i <= m; i++) { for (int j = 1; j <= n; j++) { // now i and j start from 1, so we need to subtract 1 if (s1.charAt(i - 1) == s2.charAt(j - 1)) { // s1[i-1] and s2[j-1] must be in the LCS dp[i][j] = 1 + dp[i - 1][j - 1]; } else { // at least one of s1[i-1] and s2[j-1] is not in the lcs dp[i][j] = Math.max(dp[i][j - 1], dp[i - 1][j]); } } } return dp[m][n]; } } ``` In the bottom-up solution, the way the `dp` array is defined differs slightly from our recursive solution, and there is an index offset because array indices start at 0. However, the thought process is entirely the same as our recursive solution. If you understand the recursive solution, this solution should not be difficult to understand. Additionally, the bottom-up solution can be optimized using the [Dynamic Programming Space Compression Technique](https://labuladong.online/en/algo/dynamic-programming/space-optimization/) we discussed earlier, reducing the space complexity to O(N). Due to space limitations, we won't elaborate here. Now, let's look at two problems similar to the longest common subsequence. ## Delete Operation for Strings This is LeetCode Problem 583, "[Delete Operation for Two Strings](https://leetcode.com/problems/delete-operation-for-two-strings/)". Let's look at the problem: Given two words `s1` and `s2`, return the minimum number of steps required to make `s1` and `s2` the same. In each step, you can delete one character from either string. The function signature is as follows: ```java int minDistance(String s1, String s2); ``` For example, given `s1 = "sea"` and `s2 = "eat"`, the algorithm returns 2. In the first step, transform `"sea"` to `"ea"`, and in the second step, transform `"eat"` to `"ea"`. The problem asks us to calculate the minimum number of deletions needed to make the two strings identical. We can think about what these two strings will look like after deletions. The result of deletions is their longest common subsequence! Therefore, to calculate the number of deletions, we can derive it from the length of the longest common subsequence: ```java class Solution { public int minDistance(String s1, String s2) { int m = s1.length(), n = s2.length(); // reuse the previous function to calculate the length of lcs int lcs = longestCommonSubsequence(s1, s2); return m - lcs + n - lcs; } // calculate the length of the longest common subsequence int longestCommonSubsequence(String s1, String s2) { int m = s1.length(), n = s2.length(); // define: the length of lcs for s1[0..i-1] and s2[0..j-1] as dp[i][j] int[][] dp = new int[m + 1][n + 1]; for (int i = 1; i <= m; i++) { for (int j = 1; j <= n; j++) { // now i and j start from 1, so we need to subtract one if (s1.charAt(i - 1) == s2.charAt(j - 1)) { // s1[i-1] and s2[j-1] are definitely in the lcs dp[i][j] = 1 + dp[i - 1][j - 1]; } else { // at least one of s1[i-1] and s2[j-1] is not in the lcs dp[i][j] = Math.max(dp[i][j - 1], dp[i - 1][j]); } } } return dp[m][n]; } } ``` And that's how we solve this problem! ## Minimum ASCII Delete Sum This is LeetCode problem 712, ["Minimum ASCII Delete Sum for Two Strings"](https://leetcode.com/problems/minimum-ascii-delete-sum-for-two-strings/). The problem is similar to the previous one, except that the previous problem aimed to minimize the number of deletions, while this one aims to minimize the sum of the ASCII values of the deleted characters. The function signature is as follows: ```java int minimumDeleteSum(String s1, String s2) ``` For example, given `s1 = "sea", s2 = "eat"`, the algorithm returns 231. This is because deleting `"s"` from `"sea"` and `"t"` from `"eat"` makes the two strings equal, with the minimum sum of the ASCII values of the deleted characters, i.e., `s(115) + t(116) = 231`. **This problem cannot directly reuse the function for calculating the longest common subsequence, but by slightly modifying the base case and state transition part, you can directly write the solution code**: ```java class Solution { // memoization int memo[][]; // main function public int minimumDeleteSum(String s1, String s2) { int m = s1.length(), n = s2.length(); // memo value of -1 indicates uncalculated memo = new int[m][n]; for (int[] row : memo) Arrays.fill(row, -1); return dp(s1, 0, s2, 0); } // definition: delete s1[i..] and s2[j..] to make them the same string, // the minimum ascii sum is dp(s1, i, s2, j). int dp(String s1, int i, String s2, int j) { int res = 0; // base case if (i == s1.length()) { // if s1 is exhausted, delete the remaining characters of s2 for (; j < s2.length(); j++) res += s2.charAt(j); return res; } if (j == s2.length()) { // if s2 is exhausted, delete the remaining characters of s1 for (; i < s1.length(); i++) res += s1.charAt(i); return res; } if (memo[i][j] != -1) { return memo[i][j]; } if (s1.charAt(i) == s2.charAt(j)) { // if s1[i] and s2[j] are in the longest common subsequence (lcs), no deletion needed memo[i][j] = dp(s1, i + 1, s2, j + 1); } else { // at least one of s1[i] and s2[j] is not in the lcs, delete one memo[i][j] = Math.min( s1.charAt(i) + dp(s1, i + 1, s2, j), s2.charAt(j) + dp(s1, i, s2, j + 1) ); } return memo[i][j]; } } ``` The base case has some differences. When calculating the `lcs` length, if one string is empty, the `lcs` length is necessarily 0. However, in this problem, if one string is empty, all characters of the other string must be deleted, so you need to calculate the sum of the ASCII values of all characters in the other string. Regarding state transition, when `s1[i]` and `s2[j]` are the same, no deletion is needed; when they are different, deletion is needed. Therefore, the `dp` function can be used to calculate both cases and derive the optimal result. The rest is similar, so it won't be elaborated. Thus, the three subsequence problems are solved. The key is to break down the problem to the character level and determine whether each pair of characters should be included in the result subsequence, thereby avoiding brute-force enumeration of all subsequences. This is a common approach to finding subsequences in two strings. It is recommended to understand it well and practice more~ ================================================ FILE: dynamic-programming/magic-tower.md ================================================ ::: info Prerequisite Knowledge Before reading this article, you need to learn: - [Core Framework of Dynamic Programming](https://labuladong.online/en/algo/essential-technique/dynamic-programming-framework/) ::: "The Magic Tower" is a classic dungeon crawler game where you lose health when encountering monsters, gain health by consuming health potions, collect keys, and advance through levels to ultimately rescue the beautiful princess. This game is still available on mobile devices today: ![](../pictures/dungeons/0.png) Many people probably have fond childhood memories of this game. I remember playing it alone on a gaming device, surrounded by two or three friends giving directions, which made the gaming experience quite frustrating for the player but extremely enjoyable for the onlookers 😂. LeetCode Problem 174, "[Dungeon Game](https://leetcode.com/problems/dungeon-game/)" is a similar challenge: **LeetCode 174. Dungeon Game** The demons had captured the princess and imprisoned her in **the bottom-right corner** of a `dungeon`. The `dungeon` consists of `m x n` rooms laid out in a 2D grid. Our valiant knight was initially positioned in **the top-left room** and must fight his way through `dungeon` to rescue the princess. The knight has an initial health point represented by a positive integer. If at any point his health point drops to `0` or below, he dies immediately. Some of the rooms are guarded by demons (represented by negative integers), so the knight loses health upon entering these rooms; other rooms are either empty (represented as 0) or contain magic orbs that increase the knight's health (represented by positive integers). To reach the princess as quickly as possible, the knight decides to move only **rightward** or **downward** in each step. Return *the knight's minimum initial health so that he can rescue the princess*. **Note** that any room can contain threats or power-ups, even the first room the knight enters and the bottom-right room where the princess is imprisoned. Example 1:** ![](https://assets.leetcode.com/uploads/2021/03/13/dungeon-grid-1.jpg) ``` **Input:** dungeon = [[-2,-3,3],[-5,-10,1],[10,30,-5]] **Output:** 7 **Explanation:** The initial health of the knight must be at least 7 if he follows the optimal path: RIGHT-> RIGHT -> DOWN -> DOWN. ``` Example 2:** ``` **Input:** dungeon = [[0]] **Output:** 1 ``` **Constraints:** - `m == dungeon.length` - `n == dungeon[i].length` - `1 <= m, n <= 200` - `-1000 <= dungeon[i][j] <= 1000` **In simple terms, it asks how much initial health is needed for a knight to move from the top-left corner to the bottom-right corner, ensuring the health is always greater than 0**. The function signature is as follows: ```java int calculateMinimumHP(int[][] grid); ``` The previous article [Minimum Path Sum](https://labuladong.online/en/algo/dynamic-programming/minimum-path-sum/) discussed a similar problem, asking for the minimum path sum from the top-left corner to the bottom-right corner. When solving algorithm problems, we should always try to draw inferences from one instance to another. It feels like today's problem is somewhat related to the minimum path sum, right? Minimizing the knight's initial health means maximizing the health potions along the knight's path. Isn't it equivalent to finding the "maximum path sum"? Can we directly apply the thought process of calculating the "minimum path sum"? However, after some thought, this inference does not hold; collecting the most health potions does not necessarily result in the minimum initial health. For example, in the following case, if you want to collect the most potions for the "maximum path sum," you should follow the path shown by the arrows in the image below, requiring an initial health of 11: ![](../pictures/dungeons/2.png) But it's easy to see that the correct path is shown by the arrows in the image below, requiring only an initial health of 1: ![](../pictures/dungeons/3.png) **So, the key is not in collecting the most potions but in losing the least health**. For such optimization problems, dynamic programming techniques must be used, and the `dp` array/function definition must be designed appropriately. Referring to the previous article [Minimum Path Sum Problem](https://labuladong.online/en/algo/dynamic-programming/minimum-path-sum/), the `dp` function signature will likely be as follows: ```java int dp(int[][] grid, int i, int j); ``` However, the definition of the `dp` function in this problem is quite interesting. Logically, the `dp` function should be defined as: **The minimum health required to reach `grid[i][j]` from the top-left corner (`grid[0][0]`) is `dp(grid, i, j)`**. With this definition, the base case occurs when both `i` and `j` equal 0, and we can write the code as follows: ```java int calculateMinimumHP(int[][] grid) { int m = grid.length; int n = grid[0].length; // We want to calculate the minimum health required from the top-left corner to the bottom-right corner return dp(grid, m - 1, n - 1); } int dp(int[][] grid, int i, int j) { // base case if (i == 0 && j == 0) { // Ensure the knight survives the landing return grid[i][j] > 0 ? 1 : -grid[i][j] + 1; } ... } ``` For simplicity, we will abbreviate `dp(grid, i, j)` as `dp(i, j)` from now on, and you should understand the context. Next, we need to find the state transition. Do you remember how to derive the state transition equation? Can we correctly perform state transitions with this definition of the `dp` function? We want `dp(i, j)` to be derived from `dp(i-1, j)` and `dp(i, j-1)`, so that we can progressively approach the base case and ensure correct state transitions. Specifically, the "minimum health required to reach `A`" should be deduced from the "minimum health required to reach `B`" and the "minimum health required to reach `C`": ![](../pictures/dungeons/4.png) **But the problem is, can we derive it? In fact, we cannot.** According to the definition of the `dp` function, you only know the "minimum health required to reach `B` from the top left corner," but you do not know the "health level when reaching `B`." The "health level when reaching `B`" is a necessary reference for state transitions. Let me give you an example: ![](../pictures/dungeons/5.png) What do you think is the optimal path for the knight to rescue the princess in this scenario? Clearly, it's to follow the blue line to `B` and then to `A`, right? This way, the initial health required is only 1. If you follow the yellow arrow path, first going to `C` and then to `A`, the initial health required would be at least 6. Why is this the case? The minimum initial health to reach both `B` and `C` is 1. Why is the path from `B` to `A` preferred over `C` to `A`? Because when the knight reaches `B`, the health level is 11, but when reaching `C`, it remains 1. If the knight insists on going from `C` to `A`, the initial health must be increased to 6; but if from `B` to `A`, an initial health of 1 is sufficient because health potions picked up along the way provide enough health to withstand the damage from monsters above `A`. This should be clear now. Reviewing our definition of the `dp` function, in the above scenario, the algorithm only knows that `dp(1, 2) = dp(2, 1) = 1`, which is the same. How can it make the right decision and compute `dp(2, 2)`? **Therefore, our previous definition of the `dp` array was incorrect; it lacked sufficient information for the algorithm to make correct state transitions.** The correct approach requires reverse thinking, still using the following `dp` function: ```java int dp(int[][] grid, int i, int j); ``` But we need to change the definition of the `dp` function: **The minimum health needed to go from `grid[i][j]` to the bottom-right corner is `dp(grid, i, j)`.** We can write the code like this: ```java int calculateMinimumHP(int[][] grid) { // We want to calculate the minimum health value required from the top-left corner to the bottom-right corner return dp(grid, 0, 0); } int dp(int[][] grid, int i, int j) { int m = grid.length; int n = grid[0].length; // base case if (i == m - 1 && j == n - 1) { return grid[i][j] >= 0 ? 1 : -grid[i][j] + 1; } ... } ``` Based on this new definition and the base case, we want to find `dp(0, 0)`. So we should use `dp(i, j+1)` and `dp(i+1, j)` to figure out `dp(i, j)`, step by step, to get closer to the base case and do the state transition correctly. More specifically, "the minimum health to reach the bottom-right from point `A`" should be decided by "the minimum health from `B` to the bottom-right" and "the minimum health from `C` to the bottom-right": ![](../pictures/dungeons/6.png) Can we figure it out? Yes. For example, if `dp(0, 1) = 5` and `dp(1, 0) = 4`, it's better to go from `A` to `C`, because 4 is less than 5. So how do we figure out the value for `dp(0, 0)`? Suppose the value at `A` is 1. Since we know that the next step is to `C`, and `dp(1, 0) = 4` means we need at least 4 health to reach `grid[1][0]`, then at point `A`, the knight must have 4 - 1 = 3 health to continue. If the value at `A` is 10, which means the knight goes up by 10 health right away, then 4 - 10 = -6, which is negative. But health should never be less than 1, otherwise the knight will die, so in this case, the required health is 1. So we get the state transition formula: ```java int res = min( dp(i + 1, j), dp(i, j + 1) ) - grid[i][j]; dp(i, j) = res <= 0 ? 1 : res; ``` With this core logic and a memoization table to avoid overlapping subproblems, we can write the final code directly: ```java class Solution { public int calculateMinimumHP(int[][] grid) { int m = grid.length; int n = grid[0].length; // initialize the memo array with -1 memo = new int[m][n]; for (int[] row : memo) { Arrays.fill(row, -1); } return dp(grid, 0, 0); } // memo array to eliminate overlapping subproblems int[][] memo; // definition: the minimum initial health required to reach the bottom-right corner from (i, j) int dp(int[][] grid, int i, int j) { int m = grid.length; int n = grid[0].length; // base case if (i == m - 1 && j == n - 1) { return grid[i][j] >= 0 ? 1 : -grid[i][j] + 1; } if (i == m || j == n) { return Integer.MAX_VALUE; } // avoid redundant calculations if (memo[i][j] != -1) { return memo[i][j]; } // state transition logic int res = Math.min( dp(grid, i, j + 1), dp(grid, i + 1, j) ) - grid[i][j]; // knight's health must be at least 1 memo[i][j] = res <= 0 ? 1 : res; return memo[i][j]; } } ``` This is the top-down dynamic programming solution with memoization. You can check the previous article [Dynamic Programming Pattern Explained](https://labuladong.online/en/algo/essential-technique/dynamic-programming-framework/) to see how to write the iterative version with a `dp` array. We can define the `dp` array like this: **The minimum health needed from `grid[i][j]` to the bottom-right is `dp[i][j]`.** The base case is `dp[m-1][n-1] = 1`, that is, standing at the goal, the knight needs at least 1 health. From each cell `grid[i][j]`, you can go either right or down, so the state transition formula is: ```java dp[i][j] = Math.min(dp[i + 1][j], dp[i][j + 1]) - grid[i][j]; ``` The value of `dp[i][j]` depends on the cell below (`dp[i+1][j]`) and to the right (`dp[i][j+1]`). So we first need to fill in the last row `dp[n-1][..]` and last column `dp[..][m-1]`, and then fill the rest from bottom to top and right to left: ```java class Solution { public int calculateMinimumHP(int[][] grid) { int m = grid.length; int n = grid[0].length; final int INF = Integer.MAX_VALUE / 2; // dp[i][j] is the minimum initial health to reach bottom-right from (i, j) int[][] dp = new int[m][n]; // base case, bottom-right needs at least 1 health dp[m - 1][n - 1] = Math.max(1, 1 - grid[m - 1][n - 1]); // last column for (int i = m - 2; i >= 0; i--) { int need = dp[i + 1][n - 1] - grid[i][n - 1]; dp[i][n - 1] = need <= 0 ? 1 : need; } // last row for (int j = n - 2; j >= 0; j--) { int need = dp[m - 1][j + 1] - grid[m - 1][j]; dp[m - 1][j] = need <= 0 ? 1 : need; } // fill the rest bottom-up, right-to-left for (int i = m - 2; i >= 0; i--) { for (int j = n - 2; j >= 0; j--) { int down = dp[i + 1][j]; int right = dp[i][j + 1]; int need = Math.min(down, right) - grid[i][j]; dp[i][j] = need <= 0 ? 1 : need; } } return dp[0][0]; } } ``` This completes the dynamic programming solution for this problem. The hardest part is to define the `dp` function. Once you do that, you can find the correct state transition and get the right answer. ================================================ FILE: dynamic-programming/optimal-substructure.md ================================================ ::: info Prerequisites Before reading this article, you need to learn: - [Dynamic Programming Core Framework](https://labuladong.online/en/algo/essential-technique/dynamic-programming-framework/) ::: This article is a full Q&A after [Dynamic Programming Core Framework](https://labuladong.online/en/algo/essential-technique/dynamic-programming-framework/). It will explain these questions: 1. What is “optimal substructure”, and how is it related to dynamic programming? 2. How to tell whether a problem is a dynamic programming problem, meaning how to see if there are overlapping subproblems. 3. Why we often set the `dp` array size to `n + 1` instead of `n`. 4. Why there are many ways to traverse the `dp` array: forward, backward, or even diagonally. ## 1. Optimal Substructure Explained “Optimal substructure” is a property of some problems. It is not only for dynamic programming. Many problems have optimal substructure, but most of them do not have overlapping subproblems, so we do not call them dynamic programming problems. Here is an easy example: suppose your school has 10 classes. You already know the highest exam score in each class. Now I ask you to find the highest score in the whole school. Can you do it? Of course. You do not need to scan every student again. You just take the max among these 10 highest scores. This problem **has optimal substructure**: from the best result of subproblems, you can get the best result of a bigger problem. The best score of **each class** is a subproblem. After you know all subproblem answers, you can get the answer for the bigger problem: the best score of the **whole school**. Even this simple problem has optimal substructure. But it clearly has no overlapping subproblems, so dynamic programming is not needed. Now another example: suppose your school has 10 classes, and you know the maximum score gap in each class (highest score minus lowest score in that class). Now I ask you to find the maximum score gap in the whole school. You can compute it, but you cannot get it from those 10 class gaps. Because the max gap in the whole school may be between the top score in class 3 and the lowest score in class 6. This problem **does not have optimal substructure**. You cannot use the best value of each class to get the best value for the whole school. As said in [Dynamic Programming Core Framework](https://labuladong.online/en/algo/essential-technique/dynamic-programming-framework/), to have optimal substructure, subproblems must be independent. Here the max gap can happen across two classes, so subproblems are not independent. So the problem does not have optimal substructure. **So what if optimal substructure fails? The strategy is: change the problem.** For the max gap problem, since we cannot use the known class gaps, we may only write brute-force code: ```java int result = 0; for (Student a : school) { for (Student b : school) { if (a is b) continue; result = max(result, |a.score - b.score|); } } return result; ``` Changing the problem means turning it into an equal problem: the maximum score gap is the same as (highest score - lowest score). So we just need the highest and lowest score. That is exactly the first problem, and it has optimal substructure. Now we use optimal substructure to get the extremes, and then solve the max gap. This is much faster. This example is very simple. But think about dynamic programming problems: we are often finding some kind of max or min. It is the same idea, just with overlapping subproblems. The earlier article [Egg Dropping Problem](https://labuladong.online/en/algo/dynamic-programming/egg-drop/) shows how to change a problem. Different optimal substructure may lead to different solutions and different speed. Another common and simple example: find the maximum value in a binary tree (for simplicity, assume all node values are non-negative): ```java int maxVal(TreeNode root) { if (root == null) return -1; int left = maxVal(root.left); int right = maxVal(root.right); return max(root.val, left, right); } ``` This also has optimal substructure. The max value of the tree rooted at `root` can be derived from the max values of the left and right subtrees (subproblems). This is easy to understand if you remember the school/class examples. Still, this is not a dynamic programming problem. The goal is to show: optimal substructure is not special to dynamic programming. Most “find max/min” problems have it. But the other way is important: as a necessary condition for dynamic programming, **optimal substructure usually means you are asked to find a max/min**. So when you see an annoying max/min problem, you should think about dynamic programming. This is a common pattern. Dynamic programming is pushing forward from the simplest base case. You can think of it like a chain reaction: small answers build bigger answers. Only problems with optimal substructure can work like this. Finding optimal substructure is basically proving the state transition is correct. If your transition follows optimal substructure, you can write a brute-force recursive solution. After you have the brute-force solution, you can check if there are overlapping subproblems. If yes, optimize; if no, it is fine. This is also a common pattern. I will not list “real” dynamic programming examples here. You can read older articles to see how transitions follow optimal substructure. Let’s move on to other confusing dynamic programming behaviors. ## 2. How to See Overlapping Subproblems Quickly Many readers say: After reading [Dynamic Programming Core Framework](https://labuladong.online/en/algo/essential-technique/dynamic-programming-framework/), I know how to optimize a dynamic programming solution step by step; After reading [Dynamic Programming Design: Mathematical Induction](https://labuladong.online/en/algo/dynamic-programming/longest-increasing-subsequence/), I know how to write a brute-force solution (the state transition). **But even if I have a brute-force solution, it is hard to tell whether it has overlapping subproblems**, so I cannot know if I should use memoization to speed it up. I have mentioned this in several dynamic programming articles. Here I will summarize it. **First, the simplest way is to draw the recursion tree and check if there are repeated nodes.** For example, the Fibonacci recursion tree in [Dynamic Programming Core Framework](https://labuladong.online/en/algo/essential-technique/dynamic-programming-framework/): ![](../pictures/dynamic-programming/1.jpg) This tree clearly has repeated nodes, so we can use memoization to avoid repeated work. But Fibonacci is too simple. In real problems, dynamic programming can be more complex, like 2D or 3D DP. You can still draw the recursion tree, but it becomes messy. For example, in [Minimum Path Sum](https://labuladong.online/en/algo/dynamic-programming/minimum-path-sum/), we have this brute-force solution: ```java int dp(int[][] grid, int i, int j) { if (i == 0 && j == 0) { return grid[0][0]; } if (i < 0 || j < 0) { return Integer.MAX_VALUE; } return Math.min( dp(grid, i - 1, j), dp(grid, i, j - 1) ) + grid[i][j]; } ``` Even if you never read the earlier article, you can see from the code that the parameters `i, j` keep changing during recursion. So the “state” is the value `(i, j)`. Can you tell whether there are overlapping subproblems? If the input is `i = 8, j = 7`, the recursion tree for this 2D state looks like this, and we can see overlap: ![](../pictures/optimal/2.jpeg) **But if you think a bit more, you will see you do not need to draw the tree. You can tell from the recursion structure.** The trick is: remove code details, and keep only the recursion framework: ```java int dp(int[][] grid, int i, int j) { dp(grid, i - 1, j), // #1 dp(grid, i, j - 1) // #2 } ``` We can see `i, j` keep decreasing. Now a question: if we want to move from state `(i, j)` to `(i-1, j-1)`, how many paths are there? There are clearly two paths: `(i, j) -> #1 -> #2` or `(i, j) -> #2 -> #1`. More than one path means `(i-1, j-1)` will be computed many times, so overlapping subproblems must exist. One more slightly more complex example: the brute-force code for [Regular Expression Matching](https://labuladong.online/en/algo/dynamic-programming/regular-expression-matching/): ```java boolean dp(String s, int i, String p, int j) { int m = s.length(), n = p.length(); // base case if (j == n) { return i == m; } if (i == m) { if ((n - j) % 2 == 1) { return false; } for (; j + 1 < n; j += 2) { if (p.charAt(j + 1) != '*') { return false; } } return true; } boolean res = false; if (s.charAt(i) == p.charAt(j) || p.charAt(j) == '.') { if (j < n - 1 && p.charAt(j + 1) == '*') { res = dp(s, i, p, j + 2) || dp(s, i + 1, p, j); } else { res = dp(s, i + 1, p, j + 1); } } else { if (j < n - 1 && p.charAt(j + 1) == '*') { res = dp(s, i, p, j + 2); } else { res = false; } } return res; } ``` The code looks complex. Drawing a tree is painful. But we can ignore all details and branches, and only keep the recursion framework: ```java boolean dp(String s, int i, String p, int j) { dp(s, i, p, j + 2); // #1 dp(s, i + 1, p, j); // #2 dp(s, i + 1, p, j + 1); // #3 } ``` Same as the previous problem, the “state” is still `(i, j)`. Now another question: if we want to move from `(i, j)` to `(i+2, j+2)`, how many paths are there? Clearly, there are at least two paths: `(i, j) -> #1 -> #2 -> #2` and `(i, j) -> #3 -> #3`. This means there are a huge number of overlapping subproblems. So without drawing anything, we already know this solution needs memoization to speed it up. ## 3. Choosing the size of the dp array For example, in the earlier article [Edit Distance](https://labuladong.online/en/algo/dynamic-programming/edit-distance/), I first showed a top-down recursive solution, with a `dp` function like this: ```java class Solution { public int minDistance(String s1, String s2) { int m = s1.length(), n = s2.length(); // According to the definition of the dp function, calculate the minimum edit distance between s1 and s2 return dp(s1, m - 1, s2, n - 1); } // Definition: the minimum edit distance between s1[0..i] and s2[0..j] is dp(s1, i, s2, j) int dp(String s1, int i, String s2, int j) { // Handle base case if (i == -1) { return j + 1; } if (j == -1) { return i + 1; } // Perform state transition if (s1.charAt(i) == s2.charAt(j)) { return dp(s1, i - 1, s2, j - 1); } else { return min( dp(s1, i, s2, j - 1) + 1, dp(s1, i - 1, s2, j) + 1, dp(s1, i - 1, s2, j - 1) + 1 ); } } int min(int a, int b, int c) { return Math.min(a, Math.min(b, c)); } } ``` Then I changed it into a bottom-up iterative solution: ```java class Solution { public int minDistance(String s1, String s2) { int m = s1.length(), n = s2.length(); // Definition: the minimum edit distance between s1[0..i] and s2[0..j] is dp[i+1][j+1] int[][] dp = new int[m + 1][n + 1]; // Initialize base case for (int i = 1; i <= m; i++) dp[i][0] = i; for (int j = 1; j <= n; j++) dp[0][j] = j; // Solve from bottom to top for (int i = 1; i <= m; i++) { for (int j = 1; j <= n; j++) { // Perform state transition if (s1.charAt(i-1) == s2.charAt(j-1)) { dp[i][j] = dp[i - 1][j - 1]; } else { dp[i][j] = min( dp[i - 1][j] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + 1 ); } } } // According to the definition of the dp array, store the minimum edit distance between s1 and s2 return dp[m][n]; } } ``` These two solutions use the same idea. But some readers asked: why does the iterative solution create the `dp` array as `int[m+1][n+1]`? Why is the edit distance of `s1[0..i]` and `s2[0..j]` stored in `dp[i+1][j+1]` with an index shift? Can we copy the definition of the `dp` function, create `dp` as `int[m][n]`, and store the answer for `s1[0..i]` and `s2[0..j]` in `dp[i][j]`? **In theory, you can define it in any way, as long as you handle the base case correctly.** Look at the definition of the `dp` function: `dp(s1, i, s2, j)` computes the edit distance of `s1[0..i]` and `s2[0..j]`. When `i` or `j` is `-1`, it means an empty string, which is the base case. So the function handles these special cases at the beginning. Now look at the `dp` array. You can define `dp[i][j]` as the edit distance of `s1[0..i]` and `s2[0..j]`. But then how do you handle the base case? Array indexes cannot be `-1`. So we create the `dp` array as `int[m+1][n+1]` and shift all indexes by 1. We keep index 0 for the base case (empty string). Then we define `dp[i+1][j+1]` to store the edit distance of `s1[0..i]` and `s2[0..j]`. ## 4. The traversal direction of the dp array When solving dynamic programming problems, many people feel unsure about the traversal order of the `dp` array. Using a 2D `dp` array as an example, sometimes we traverse forward: ```java int[][] dp = new int[m][n]; for (int i = 0; i < m; i++) for (int j = 0; j < n; j++) // compute dp[i][j] ``` Sometimes we traverse backward: ```java for (int i = m - 1; i >= 0; i--) for (int j = n - 1; j >= 0; j--) // compute dp[i][j] ``` Sometimes we traverse diagonally: ```java // traverse the array diagonally for (int l = 2; l <= n; l++) { for (int i = 0; i <= n - l; i++) { int j = l + i - 1; // compute dp[i][j] } } ``` Even more confusing, sometimes both forward and backward traversal work. For example, in some parts of [Stock Problems](https://labuladong.online/en/algo/dynamic-programming/stock-problem-summary/), both directions are fine. If you look closely, you only need to remember two rules: **1. During traversal, the states you need must already be computed.** **2. When traversal ends, the cell that stores the final answer must have been computed.** Now let’s explain these two rules. Take the classic problem [Edit Distance](https://labuladong.online/en/algo/dynamic-programming/edit-distance/). From the definition of `dp`, the base cases are `dp[..][0]` and `dp[0][..]`, and the final answer is `dp[m][n]`. From the transition, `dp[i][j]` depends on `dp[i-1][j]`, `dp[i][j-1]`, and `dp[i-1][j-1]`, like this: ![](../pictures/optimal/1.jpg) So how should you traverse the `dp` array? Based on the two rules, you should traverse forward: ```java for (int i = 1; i < m; i++) for (int j = 1; j < n; j++) // use dp[i-1][j], dp[i][j - 1], dp[i-1][j-1] // to compute dp[i][j] ``` Because in each step, the left, upper, and upper-left cells are base cases or have already been computed. And when the loops end, you reach the answer `dp[m][n]`. Here is another example: the palindromic subsequence problem. See [Subsequence Problem Template](https://labuladong.online/en/algo/dynamic-programming/subsequence-problem/). From the definition of `dp`, the base case is on the diagonal in the middle. `dp[i][j]` depends on `dp[i+1][j]`, `dp[i][j-1]`, and `dp[i+1][j-1]`. The final answer is `dp[0][n-1]`, like this: ![](../pictures/lps/4.jpg) In this case, based on the two rules, there are two correct traversal orders: ![](../pictures/lps/5.jpg) You can traverse diagonally from top-left to bottom-right, or traverse from bottom to top and left to right. This way, when you compute `dp[i][j]`, the left, lower, and lower-left cells have already been computed, so you get the correct result. Now you should understand these two rules. Just look at where the base case is and where the final result is stored. Make sure every value you use during traversal is already computed. Sometimes there are multiple correct ways, and you can choose the one you like. ================================================ FILE: dynamic-programming/regular-expression.md ================================================ ::: info Prerequisite Knowledge Before reading this article, you need to study: - [Core Framework of Dynamic Programming](https://labuladong.online/en/algo/essential-technique/dynamic-programming-framework/) - [Classic Dynamic Programming: Edit Distance](https://labuladong.online/en/algo/dynamic-programming/edit-distance/) ::: Regular expressions are a very powerful tool. In this article, we will look at the basic idea behind regular expressions. LeetCode problem 10: [Regular Expression Matching](https://leetcode.com/problems/regular-expression-matching/) asks us to implement a simple regular matching algorithm, including the "." wildcard and the "*" wildcard. These two wildcards are used most often. The dot "." can match any single character. The star "*" means the character before it can repeat any number of times (including 0 times). For example, the pattern `".a*b"` can match the text `"zaaab"` or `"cb"`. The pattern `"a..b"` matches `"amnb"`. The pattern `".*"` can match any text. The problem gives us two strings `s` and `p`. `s` is the text, and `p` is the pattern. You need to check if the pattern `p` can match the text `s`. You can assume the pattern only contains lowercase letters and the two wildcards above. The pattern will always be valid; there won't be cases like `*a` or `b**`. The function signature is as follows: ```java boolean isMatch(string s, string p); ``` What is the hard part of this regular expression we need to implement? The dot wildcard is easy. Any character in `s` can match a `.` in the pattern. The tricky part is the star wildcard. When you see "*", the character before it can be used any number of times: repeated once, many times, or not at all. What should we do? The answer is simple: try all possible cases. If any case matches, then `p` matches `s`. Whenever we need to try all cases for two strings, we should think about using dynamic programming. ## 1. Idea Analysis Let’s think about how `s` and `p` match. We use two pointers `i` and `j` to move along `s` and `p`. If both pointers reach the end, it means the match is successful. Otherwise, the match fails. **If we ignore the "*" wildcard, when matching characters `s[i]` and `p[j]`, all we need to do is check if they match:** ```java boolean isMatch(String s, String p) { int i = 0, j = 0; while (i < s.length() && j < p.length()) { // the '.' wildcard is a versatile match if (s.charAt(i) == p.charAt(j) || p.charAt(j) == '.') { // match, continue to match s[i+1..] and p[j+1..] i++; j++; } else { // not a match return false; } } return i == j; } ``` Now, if we add the "*" wildcard, things get a bit more complex, but we just need to consider each case separately. **When `p[j + 1]` is "*", let’s look at the cases:** 1. If `s[i] == p[j]`, there are two possibilities: 1.1 `p[j]` might match multiple characters. For example, `s = "aaa", p = "a*"`, then `p[0]` matches all three `"a"`. 1.2 `p[j]` might match 0 characters. For example, `s = "aa", p = "a*aa"`. Since the rest of the pattern can match `s`, `p[0]` can match 0 times. 2. If `s[i] != p[j]`, there is only one case: `p[j]` can only match 0 times, and we see if the next part can match `s[i]`. For example, `s = "aa", p = "b*aa"`, `p[0]` can only match 0 times. So, we can update the code for the "*" wildcard as follows: ```java if (s.charAt(i) == p.charAt(j) || p.charAt(j) == '.') { // match if (j < p.length() - 1 && p.charAt(j + 1) == '*') { // there is a * wildcard, which can match 0 or more times } else { // no * wildcard, match exactly once i++; j++; } } else { // do not match if (j < p.length() - 1 && p.charAt(j + 1) == '*') { // there is a * wildcard, can only match 0 times } else { // no * wildcard, the match cannot proceed return false; } } ``` The idea is clear. But when we see the "*" wildcard, should we match 0 times or more? How many times? This is a "choice" problem. We need to try all possible choices to get the answer. The core of dynamic programming is "state" and "choice". **The "state" is the position of the two pointers `i` and `j`. The "choice" is how many times `p[j]` matches a character.** ## 2. Dynamic Programming Solution Based on the "state", we can define a `dp` function: ```java boolean dp(String s, int i, String p, int j); ``` The definition of the `dp` function is as follows: **If `dp(s, i, p, j) = true`, it means `s[i..]` can match `p[j..]`. If `dp(s, i, p, j) = false`, it means `s[i..]` cannot match `p[j..]`.** Based on this definition, the answer we want is the result of `dp` when `i = 0` and `j = 0`. So we use the `dp` function like this: ```java boolean isMatch(String s, String p) { // pointers i and j start moving from index 0 return dp(s, 0, p, 0); } ``` We can write the main logic of the `dp` function based on the previous code: ```java // core logic of the dp function (pseudocode) // definition: the function returns whether s[i..] can match p[j..] boolean dp(String s, int i, String p, int j) { if (s.charAt(i) == p.charAt(j) || p.charAt(j) == '.') { // match if (j < p.length() - 1 && p.charAt(j + 1) == '*') { // 1.1 wildcard matches 0 times or multiple times return dp(s, i, p, j + 2) || dp(s, i + 1, p, j); } else { // 1.2 regular match 1 time return dp(s, i + 1, p, j + 1); } } else { // no match if (j < p.length() - 1 && p.charAt(j + 1) == '*') { // 2.1 wildcard matches 0 times return dp(s, i, p, j + 2); } else { // 2.2 cannot continue matching return false; } } } ``` **According to the definition of the `dp` function**, these cases are easy to explain: **1.1 Wildcard matches 0 or more times** Add 2 to `j` and keep `i` the same. This means we skip `p[j]` and the wildcard after it, which is the case where the wildcard matches 0 times. Even if `s[i] == p[j]`, this can still happen, as shown below: ![](../pictures/regex/5.jpeg) Add 1 to `i` and keep `j` the same. This means `p[j]` matches `s[i]`, but `p[j]` can match more, which is the case where the wildcard matches multiple times: ![](../pictures/regex/2.jpeg) If either case leads to a match, we return true, so we use "or" to combine both. **1.2 Normal matching once** This is the case without a `*` wildcard. If `s[i] == p[j]`, just add 1 to both `i` and `j`: ![](../pictures/regex/3.jpeg) **2.1 Wildcard matches 0 times** Similar to case 1.1, add 2 to `j` and keep `i` the same: ![](../pictures/regex/1.jpeg) **2.2 If there is no `*` wildcard and cannot match, it means match failed** ![](../pictures/regex/4.jpeg) Looking at the pictures makes it easy to understand. Now, let's think about the base cases of the `dp` function: **One base case is when `j == p.length()`**. According to the definition of the `dp` function, it means the pattern string `p` has been matched. We need to see if the text string `s` has also been matched. If `s` is also matched, then it's a success: ```java if (j == p.length()) { return i == s.length(); } ``` **Another base case is when `i == s.length()`**. According to the `dp` definition, this means the text string `s` has been completely matched. Can we just check if `p` is also matched? ```java if (i == s.length()) { // Is this enough? return j == p.length(); } ``` **This is not correct. At this time, we cannot just check if `j` is equal to `p.length()`. As long as `p[j..]` can match an empty string, it counts as a successful match.** For example, for `s = "a", p = "ab*c*"`, when `i` reaches the end of `s`, `j` hasn't reached the end of `p`, but `p` can still match `s`. So we can write code like this: ```java int m = s.length(), n = p.length(); if (i == s.length()) { // if it can match an empty string, characters and * must appear in pairs if ((n - j) % 2 == 1) { return false; } // check if it is in the form of x*y*z* for (; j + 1 < p.length(); j += 2) { if (p.charAt(j + 1) != '*') { return false; } } return true; } ``` With this idea, we can write the complete code: ```java class Solution { // memoization private int[][] memo; public boolean isMatch(String s, String p) { int m = s.length(), n = p.length(); memo = new int[m][n]; for (int[] row : memo) { Arrays.fill(row, -1); } // pointers i and j start moving from index 0 return dp(s, 0, p, 0); } // calculate whether p[j..] matches s[i..] private boolean dp(String s, int i, String p, int j) { int m = s.length(), n = p.length(); // base case if (j == n) { return i == m; } if (i == m) { if ((n - j) % 2 == 1) { return false; } for (; j + 1 < n; j += 2) { if (p.charAt(j + 1) != '*') { return false; } } return true; } // check memo to avoid redundant calculations if (memo[i][j] != -1) { return memo[i][j] == 1; } boolean res = false; if (s.charAt(i) == p.charAt(j) || p.charAt(j) == '.') { if (j < n - 1 && p.charAt(j + 1) == '*') { res = dp(s, i, p, j + 2) || dp(s, i + 1, p, j); } else { res = dp(s, i + 1, p, j + 1); } } else { if (j < n - 1 && p.charAt(j + 1) == '*') { res = dp(s, i, p, j + 2); } else { res = false; } } // store the current result in the memo memo[i][j] = res ? 1 : 0; return res; } } ``` The code uses a hash table called `memo` to remove overlapping subproblems. How to spot overlapping subproblems? You can check [Dynamic Programming Q&A](https://labuladong.online/en/algo/dynamic-programming/faq-summary/) for techniques. Let's look at the recursive framework of the regex algorithm: ```java boolean dp(String s, int i, String p, int j) { dp(s, i, p, j + 2); // 1 dp(s, i + 1, p, j); // 2 dp(s, i + 1, p, j + 1); // 3 dp(s, i, p, j + 2); // 4 } ``` If you want to get from `dp(s, i, p, j)` to `dp(s, i+2, p, j+2)`, there are at least two paths: `1 -> 2 -> 2` and `3 -> 3`. This means the state `(i+2, j+2)` will be calculated more than once. This shows there are overlapping subproblems, so we need a memo to remove them and make the code more efficient. The time complexity of dynamic programming is "number of states" * "time per recursion". In this problem, the number of states is the combinations of `i` and `j`, which is `M * N` (`M` is the length of `s`, `N` is the length of `p`). The `dp` function has no loop (except for the base case, which doesn't happen often), so each recursion takes constant time. Multiply them, and the total time complexity is $O(MN)$. The space complexity is just the size of the memo, which is $O(MN)$. ================================================ FILE: dynamic-programming/state-compression.md ================================================ ::: info Prerequisites Before reading this article, you should first study: - [Dynamic Programming Core Framework](https://labuladong.online/en/algo/essential-technique/dynamic-programming-framework/) ::: The main forms of dynamic programming algorithms are recursive solutions with memoization and iterative solutions using state transition. These two approaches are essentially the same and have similar efficiency. This article will introduce one advantage of the iterative approach in dynamic programming: the ability to optimize the space usage of the `dp` array (commonly known as the rolling array technique), reducing space complexity. Simply put, in some cases, if the state transition equation only depends on adjacent states, there is no need to maintain the entire `dp` array. You only need to keep the necessary states, which can reduce space complexity. In my opinion, mastering the space optimization technique is not mandatory, for the following reasons: 1. In most coding tests, space constraints are not very strict. Usually, you can pass all test cases even without this optimization. 2. Using space optimization can make the code less readable, making it harder to understand and debug. Therefore, this article is for those who are interested in further study. Feel free to learn and understand it in depth. ## When can we use space compression We usually use space compression on 2D `dp` problems. **If to compute `dp[i][j]` you only need states that are “next to” `dp[i][j]`, then you can compress the 2D `dp` array into 1D**. This reduces space from O(N^2) to O(N). What does “states next to `dp[i][j]`” mean? For example, in the previous article [Longest Palindromic Subsequence](https://labuladong.online/en/algo/dynamic-programming/subsequence-problem/), the final code is: ```java class Solution { public int longestPalindromeSubseq(String s) { int n = s.length(); // initialize the dp array to all zeros int[][] dp = new int[n][n]; // base case for (int i = 0; i < n; i++) { dp[i][i] = 1; } // traverse in reverse to ensure correct state transitions for (int i = n - 1; i >= 0; i--) { for (int j = i + 1; j < n; j++) { // state transition equation 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]); } } } // length of the longest palindromic subsequence in the entire s return dp[0][n - 1]; } } ``` ::: tip Tip In this article, we won’t talk about how to derive the state transition formula. We only talk about how to compress space for 2D DP. The skill is general, so even if you did not read the previous article or do not understand the code, it will not stop you from learning space compression. ::: Look at how we update `dp[i][j]`. It only depends on three states: `dp[i+1][j-1], dp[i][j-1], dp[i+1][j]`: ![](../pictures/space-optimal/1.jpeg) These are the states “next to” `dp[i][j]`. Since we only need these three neighbors to compute `dp[i][j]`, we actually do not need the whole big 2D `dp` table, right? **The core idea of space compression is to “project” the 2D array into a 1D array**: ![](../pictures/space-optimal/2.jpeg) The word “project” is just to help you imagine it: we want one 1D array to play the role of the original 2D array. This idea is simple, but there is a clear problem. In the picture, `dp[i][j-1]` and `dp[i+1][j-1]` are in the same column, but a 1D array can only hold one value at that position. If we project them into 1D, one will overwrite the other. Then how can we still compute `dp[i][j]`? This is the hard part of space compression. Let’s solve it using the “Longest Palindromic Subsequence” example. Its main transition logic is this code: ```java for (int i = n - 2; i >= 0; i--) { 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]); } } } ``` ## Rolling array optimization First, we do a simple **rolling array optimization**. Since `dp[i][j]` only depends on `dp[i+1][..]` and `dp[i][..]`, we do not need the whole `N * N` `dp` array. We only need two rows. We can use an array `dp[2][n]`, and use modulo `% 2` to switch between these two rows: ```java // 滚动数组优化 int[][] dp = new int[2][n]; dp[0][0] = 1; for (int i = n - 2; i >= 0; i--) { int curr = i % 2; int next = (i + 1) % 2; // need to handle base case when using rolling array dp[curr][i] = 1; for (int j = i + 1; j < n; j++) { if (s.charAt(i) == s.charAt(j)) { dp[curr][j] = dp[next][j - 1] + 2; } else { dp[curr][j] = Math.max(dp[next][j], dp[curr][j - 1]); } } } return dp[0][n - 1]; ``` **In practice, this level of optimization is already enough. The `dp` array now has only two rows, so the space complexity is O(N)**, which is almost the same as a 1D array. Next, we will go further and fully compress `dp` into a 1D array. ## One-Dimensional Array Optimization Think about the picture above. The “projection” turns many rows into one row. So when we compress a 2D `dp` array into 1D, we usually remove the first dimension `i`, and keep only the `j` dimension. **The 1D `dp` array after compression is just the row `dp[i][..]` from the original 2D `dp` array**. First, let’s change the code above. Just blindly remove the `i` dimension and make `dp` a 1D array: ```java for (int i = n - 2; i >= 0; i--) { for (int j = i + 1; j < n; j++) { // What does each number in the 1D dp array mean here? if (s.charAt(i) == s.charAt(j)) { dp[j] = dp[j - 1] + 2; } else { dp[j] = Math.max(dp[j], dp[j - 1]); } } } ``` In this code, the 1D `dp` array can only express one row `dp[i][..]` of the 2D `dp` array. But when we do state transition, we need the values `dp[i+1][j-1], dp[i][j-1], dp[i+1][j]`. So we must think about two questions: 1. Before we assign a new value to `dp[j]`, which cell in the 2D `dp` array does `dp[j]` correspond to? 2. Which cell in the 2D `dp` array does `dp[j-1]` correspond to? **For question 1: before `dp[j]` gets a new value, it is the value from the previous iteration of the outer loop. That is the value at `dp[i+1][j]` in the 2D `dp` array**. **For question 2: `dp[j-1]` is from the previous iteration of the inner loop. That is the value at `dp[i][j-1]` in the 2D `dp` array**. So most of the problem is solved. The only remaining state we can’t get directly from the 1D array is `dp[i+1][j-1]`: ```java for (int i = n - 2; i >= 0; i--) { for (int j = i + 1; j < n; j++) { if (s.charAt(i) == s.charAt(j)) { // dp[i][j] = dp[i+1][j-1] + 2; dp[j] = ?? + 2; } else { // dp[i][j] = max(dp[i+1][j], dp[i][j-1]); dp[j] = Math.max(dp[j], dp[j - 1]); } } } ``` We iterate `i` and `j` from left to right, and from bottom to top. So we can see that when we update the 1D `dp` array, `dp[i+1][j-1]` will be overwritten by `dp[i][j-1]`. The figure below marks the visiting order of these four positions: ![](../pictures/space-optimal/3.jpeg) **So if we want `dp[i+1][j-1]`, we must store it in a temp variable `temp` before it is overwritten, and keep this value until we compute `dp[i][j]`**. To do this, based on the figure, we can write code like this: ```java for (int i = n - 2; i >= 0; i--) { // variable to store dp[i+1][j-1] int pre = 0; for (int j = i + 1; j < n; j++) { int temp = dp[j]; if (s.charAt(i) == s.charAt(j)) { // dp[i][j] = dp[i+1][j-1] + 2; dp[j] = pre + 2; } else { dp[j] = Math.max(dp[j], dp[j - 1]); } // in the next round, pre becomes dp[i+1][j-1] pre = temp; } } ``` Don’t underestimate this piece of code. This is the most clever part of 1D `dp`. If you get it, it feels easy; if you don’t, it feels impossible. To make it clear, let’s use concrete values to break down the logic: Assume `i = 5, j = 7` and `s[5] == s[7]`. Then we enter this logic: ```java for (int i = 5; i--) { for (int j = 7; j++) { if (s[5] == s[7]) { // dp[5][7] = dp[i+1][j-1] + 2; dp[7] = pre + 2; } } } ``` I ask you, what is this `pre` variable? It is the `temp` value from the previous iteration of the inner for loop. Now, let me ask you, what was the `temp` value from the previous iteration of the **inner** for loop? It is `dp[j-1]`, which is `dp[6]`. However, note that this is the `dp[6]` from the **previous iteration** of the **outer** for loop, not the current `dp[6]`. This should be understood in terms of the index of a two-dimensional array. Your current `dp[6]` is `dp[i][6] = dp[5][6]` in the two-dimensional `dp` array, while the `temp` is `dp[i+1][6] = dp[6][6]` in the two-dimensional `dp` array. In other words, the `pre` variable is `dp[i+1][j-1] = dp[6][6]`, which is the result we are looking for. Now we have successfully reduced the dimensionality of the state transition equation, tackling the toughest part, but we must still handle the base case: ```java // initialize all elements of the dp array to 0 int[][] dp = new int[n][n]; // base case for (int i = 0; i < n; i++) { dp[i][i] = 1; } ``` How do we reduce the base case to one dimension? It’s simple. Remember that space compression is projection. Let's project the base case onto one dimension: ![](../pictures/space-optimal/4.jpeg) All the base cases in the two-dimensional `dp` array fall into the one-dimensional `dp` array without conflict or overlap, so we can write the code like this: ```java // base case: initialize the one-dimensional dp array to all 1s int[] dp = new int[n]; Arrays.fill(dp, 1); ``` At this point, we have reduced both the base case and the state transition equation, effectively writing a complete code: ```java class Solution { public int longestPalindromeSubseq(String s) { int n = s.length(); // base case: initialize all elements of the one-dimensional dp array to 1 int[] dp = new int[n]; Arrays.fill(dp, 1); for (int i = n - 2; i >= 0; i--) { int pre = 0; for (int j = i + 1; j < n; j++) { int temp = dp[j]; // state transition equation if (s.charAt(i) == s.charAt(j)) dp[j] = pre + 2; else dp[j] = Math.max(dp[j], dp[j - 1]); pre = temp; } } return dp[n - 1]; } } ``` This concludes the article. However, the space compression technique, while impressive, is based on conventional dynamic programming thinking. As you can see, using space compression techniques to reduce the dimensionality of a two-dimensional `dp` array makes the solution code much less readable. If someone looks only at this solution, it can be quite confusing. Algorithm optimization is such a process: start by writing a highly readable brute-force recursive algorithm, then try to use dynamic programming techniques to optimize overlapping subproblems, and finally attempt to use space compression techniques to optimize space complexity. This means you should at least be proficient in using the framework discussed in our previous article [Detailed Explanation of Dynamic Programming Framework](https://labuladong.online/en/algo/essential-technique/dynamic-programming-framework/) to find the state transition equation and write a correct dynamic programming solution. Only then can you analyze the situation of state transitions and consider whether space compression techniques can be applied for optimization. I hope readers can progress steadily and gradually. For such extreme optimizations, it's okay not to pursue them. After all, having a good grasp of the basics will make you confident in any situation! ================================================ FILE: dynamic-programming/stock-problems.md ================================================ ::: info Prerequisites Before reading this article, you need to learn: - [Dynamic Programming Core Framework](https://labuladong.online/en/algo/essential-technique/dynamic-programming-framework/) ::: Many readers complain that the stock series problems on LeetCode have too many different solutions. If you encounter these problems in an interview, you probably won't think of those clever approaches. What should you do? **So this article won't cover those overly clever ideas. Instead, we'll take a solid, step-by-step approach, using just one universal method to solve all problems - adapting to any situation with a consistent strategy**. This article references the approach from this [highly upvoted solution](https://leetcode.com/problems/best-time-to-buy-and-sell-stock-with-transaction-fee/discuss/108870/Most-consistent-ways-of-dealing-with-the-series-of-stock-problems), using state machine techniques to solve these problems, all of which can pass submission. Don't let this fancy term intimidate you - it's just a literary expression. In reality, it's just a DP table, and you'll understand it at a glance. Let's randomly pick a problem and look at someone else's solution: ```java int maxProfit(int[] prices) { if(prices.empty()) return 0; int s1 = -prices[0], s2 = INT_MIN, s3 = INT_MIN, s4 = INT_MIN; for(int i = 1; i < prices.size(); ++i) { s1 = max(s1, -prices[i]); s2 = max(s2, s1 + prices[i]); s3 = max(s3, s2 - prices[i]); s4 = max(s4, s3 + prices[i]); } return max(0, s4); } ``` Can you understand it? Can you solve it now? Impossible - you can't understand it, and that's normal. Even if you barely understand it, you still won't be able to solve the next problem. Why can others write such strange yet efficient solutions? Because there's a framework for this type of problem, but they won't tell you. Once they tell you, you'll learn it in five minutes, and this algorithm problem will no longer be mysterious - it becomes easy to crack. This article will tell you this framework, then guide you through solving each problem quickly. We'll use state machine techniques to solve these problems, all of which can pass submission. Don't let this fancy term intimidate you - it's just a literary expression. In reality, it's just a DP table, and you'll understand it at a glance. These 6 problems share common characteristics. We only need to focus on LeetCode problem 188 "[Best Time to Buy and Sell Stock IV](https://leetcode.com/problems/best-time-to-buy-and-sell-stock-iv/)" because this is the most generalized form. The other problems are simplifications of this form. Let's look at the problem: **LeetCode 188. Best Time to Buy and Sell Stock IV** You are given an integer array `prices` where `prices[i]` is the price of a given stock on the `i^(th)` day, and an integer `k`. Find the maximum profit you can achieve. You may complete at most `k` transactions: i.e. you may buy at most `k` times and sell at most `k` times. **Note:** You may not engage in multiple transactions simultaneously (i.e., you must sell the stock before you buy again). Example 1:** ``` **Input:** k = 2, prices = [2,4,1] **Output:** 2 **Explanation:** Buy on day 1 (price = 2) and sell on day 2 (price = 4), profit = 4-2 = 2. ``` Example 2:** ``` **Input:** k = 2, prices = [3,2,6,5,0,3] **Output:** 7 **Explanation:** Buy on day 2 (price = 2) and sell on day 3 (price = 6), profit = 6-2 = 4. Then buy on day 5 (price = 0) and sell on day 6 (price = 3), profit = 3-0 = 3. ``` **Constraints:** - `1 <= k <= 100` - `1 <= prices.length <= 1000` - `0 <= prices[i] <= 1000` The first problem allows only one transaction, equivalent to `k = 1`; the second problem allows unlimited transactions, equivalent to `k = +infinity`; the third problem allows only 2 transactions, equivalent to `k = 2`; the remaining two problems also allow unlimited transactions, but add extra conditions like "cooldown period" and "transaction fee" - they're actually variants of the second problem and are easy to handle. Now let's get down to business and start solving. ## Brute-Force Framework First, let's think about the same question: how do we brute-force all possibilities? As mentioned in [Dynamic Programming Core Pattern](https://labuladong.online/en/algo/essential-technique/dynamic-programming-framework/), dynamic programming is essentially about brute-forcing all "states" and then picking the optimal solution from all "choices". For this problem, let's look at each day and see how many possible "states" there are, then find the "choices" for each "state". We need to enumerate all "states", and the purpose is to update states based on the corresponding "choices". This sounds abstract, but just remember the two words "state" and "choice", and it will become clear once we work through an example. ```python for state1 in all_values_of_state1: for state2 in all_values_of_state2: for ... dp[state1][state2][...] = best(choice1, choice2...) ``` For this problem, **there are three "choices" each day**: buy, sell, or do nothing. We use `buy`, `sell`, `rest` to represent these three choices. But the issue is, we can't freely choose any of these three options every day. `sell` must come after `buy`, and `buy` must come after `sell`. Also, `rest` should be split into two states: one is `rest` after `buy` (holding stock), and the other is `rest` after `sell` (not holding stock). Don't forget we also have a limit on the number of transactions `k`, which means `buy` can only happen when `k > 0`. ::: note Note Note that I will frequently use the word "transaction" in this article. **We define one buy and one sell as one "transaction"**. ::: Seems complex, right? Don't worry, our goal right now is just to brute-force. No matter how many states you have, we'll list them all out. **This problem has three "states"**: the first is the day number, the second is the maximum number of transactions allowed, and the third is the current holding status (the `rest` state mentioned earlier; we can use 1 for holding and 0 for not holding). We can use a 3D array to store all combinations of these states: ```python dp[i][k][0 or 1] 0 <= i <= n - 1, 1 <= k <= K n is the number of days, K is the maximum number of transactions, 0 and 1 represent whether holding stock. This problem has n × K × 2 states in total. Enumerate all of them and we're done. for 0 <= i < n: for 1 <= k <= K: for s in {0, 1}: dp[i][k][s] = max(buy, sell, rest) ``` We can describe each state in plain language. For example, `dp[3][2][1]` means: today is day 3, I'm currently holding stock, and I've made at most 2 transactions so far. Another example, `dp[2][3][0]` means: today is day 2, I'm not holding any stock, and I've made at most 3 transactions so far. Easy to understand, right? The final answer we want is `dp[n - 1][K][0]`, which means on the last day, with at most `K` transactions allowed, what's the maximum profit. You might ask why not `dp[n - 1][K][1]`? Because `dp[n - 1][K][1]` means you're still holding stock on the last day, while `dp[n - 1][K][0]` means you've sold all your stock by the last day. Obviously, the latter gives more profit than the former. Remember how to interpret "states". Whenever something feels confusing, translate it into plain language and it becomes easier to understand. ## State Transition Framework Now that we've completed the brute-force enumeration of "states", let's think about what "choices" each state has and how to update the "state". Looking only at the "holding state", we can draw a state transition diagram: ![](../pictures/stock/1.png) This diagram clearly shows how each state (0 and 1) transitions. Based on this diagram, let's write the state transition equations: ```python dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + prices[i]) max( choose rest today, choose sell today ) ``` Explanation: Today I don't hold any stock. There are two possibilities, and I want the maximum profit from these two: 1. I didn't hold stock yesterday, and the maximum transaction limit up to yesterday was `k`. Then I choose `rest` today, so I still don't hold stock today, and the maximum transaction limit remains `k`. 2. I held stock yesterday, and the maximum transaction limit up to yesterday was `k`. But today I `sell`, so I no longer hold stock today, and the maximum transaction limit remains `k`. ```python dp[i][k][1] = max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i]) max( choose rest today, choose buy today ) ``` Explanation: Today I'm holding stock with a maximum transaction limit of `k`. For yesterday, there are two possibilities, and I want the maximum profit from these two: 1. I was holding stock yesterday, and the maximum transaction limit up to yesterday was `k`. Then I choose `rest` today, so I'm still holding stock today, and the maximum transaction limit remains `k`. 2. I wasn't holding stock yesterday, and the maximum transaction limit up to yesterday was `k - 1`. But today I choose `buy`, so now I'm holding stock today, and the maximum transaction limit is `k`. ::: note Note Here's an important reminder: **always keep the definition of "state" in mind**. State `k` is not defined as "the number of completed transactions", but rather "the upper limit on maximum transactions". If you decide to make a transaction today and want to ensure the maximum transaction limit up to today is `k`, then yesterday's maximum transaction limit must be `k - 1`. For a concrete example, suppose you need at least $100 in your bank account today, and you know you can earn $10 today, then you need to make sure your bank account had at least $90 yesterday. ::: This explanation should be clear. If you `buy`, you subtract `prices[i]` from the profit; if you `sell`, you add `prices[i]` to the profit. Today's maximum profit is the larger of these two possible choices. Note the constraint on `k`: when choosing `buy`, it's like starting a new transaction, so for yesterday, the transaction limit `k` should decrease by 1. ::: note Note Here's a correction: I used to think that decreasing `k` by 1 when `sell` and decreasing `k` by 1 when `buy` were equivalent. But a careful reader raised a question, and after deeper thought, I realized the former is indeed wrong. Since a transaction starts with `buy`, if the `buy` choice doesn't change the transaction count `k`, you'll get errors where the transaction count exceeds the limit. ::: Now we've completed the most difficult part of dynamic programming: the state transition equations. **If you understand everything above, you can solve all these problems—just apply this framework**. But there's one more thing: defining the base case, the simplest situations. ```python dp[-1][...][0] = 0 Explanation: Since i starts from 0, i = -1 means we haven't started yet, so the profit is naturally 0. dp[-1][...][1] = -infinity Explanation: Before we start, it's impossible to hold any stock. Since our algorithm seeks a maximum value, we set the initial value to a minimum to facilitate finding the maximum. dp[...][0][0] = 0 Explanation: Since k starts from 1, k = 0 means no transactions are allowed, so the profit is naturally 0. dp[...][0][1] = -infinity Explanation: When no transactions are allowed, it's impossible to hold any stock. Since our algorithm seeks a maximum value, we set the initial value to a minimum to facilitate finding the maximum. ``` Let's summarize the state transition equations above: ```python base case: dp[-1][...][0] = dp[...][0][0] = 0 dp[-1][...][1] = dp[...][0][1] = -infinity state transition equations: dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + prices[i]) dp[i][k][1] = max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i]) ``` You might ask: how do we represent an array index of -1 in code, and how do we represent negative infinity? These are implementation details with many solutions. **The most common technique is "index offset"**: we expand the `dp` array size from `n` to `n + 1`, letting `dp[0]` represent the base case (the original `dp[-1]`), and `dp[i]` represent the state of the original day `i - 1`. This way we avoid handling boundary cases in the loop. Now the complete framework is ready. Let's get specific. ## Solving the Problems ### 121. Best Time to Buy and Sell Stock **For the first problem, let's look at LeetCode problem 121 "[Best Time to Buy and Sell Stock](https://leetcode.com/problems/best-time-to-buy-and-sell-stock/)", which is equivalent to the case where `k = 1`**: **LeetCode 121. Best Time to Buy and Sell Stock** You are given an array `prices` where `prices[i]` is the price of a given stock on the `i^(th)` day. You want to maximize your profit by choosing a **single day** to buy one stock and choosing a **different day in the future** to sell that stock. Return *the maximum profit you can achieve from this transaction*. If you cannot achieve any profit, return `0`. Example 1:** ``` **Input:** prices = [7,1,5,3,6,4] **Output:** 5 **Explanation:** Buy on day 2 (price = 1) and sell on day 5 (price = 6), profit = 6-1 = 5. Note that buying on day 2 and selling on day 1 is not allowed because you must buy before you sell. ``` Example 2:** ``` **Input:** prices = [7,6,4,3,1] **Output:** 0 **Explanation:** In this case, no transactions are done and the max profit = 0. ``` **Constraints:** - `1 <= prices.length <= 10^(5)` - `0 <= prices[i] <= 10^(4)` We can directly apply the state transition equation and simplify it based on the base case: ```python dp[i][1][0] = max(dp[i-1][1][0], dp[i-1][1][1] + prices[i]) dp[i][1][1] = max(dp[i-1][1][1], dp[i-1][0][0] - prices[i]) = max(dp[i-1][1][1], -prices[i]) Explanation: From the base case where k = 0, we have dp[i-1][0][0] = 0. Now we notice that k is always 1 and never changes, meaning k no longer affects the state transition. We can simplify further by removing all k: dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i]) dp[i][1] = max(dp[i-1][1], -prices[i]) ``` Here's the direct code implementation: ```java int n = prices.length; int[][] dp = new int[n][2]; for (int i = 0; i < n; 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], -prices[i]); } return dp[n - 1][0]; ``` Obviously, when `i = 0`, `dp[i-1]` is an invalid index because we haven't handled the base case. The correct approach is to use the "index offset" technique, where `dp[0]` represents the base case before we start, and `dp[i]` represents the state on day `i - 1`: ```java int n = prices.length; // dp[0] represents base case, dp[i] represents day i-1 int[][] dp = new int[n + 1][2]; // base case: dp[0][0] = 0, dp[0][1] = -infinity dp[0][0] = 0; dp[0][1] = Integer.MIN_VALUE; for (int i = 1; i <= n; i++) { // prices[i-1] is the stock price on day i-1 dp[i][0] = Math.max(dp[i-1][0], dp[i-1][1] + prices[i-1]); dp[i][1] = Math.max(dp[i-1][1], -prices[i-1]); } return dp[n][0]; ``` This way, the base case is initialized once outside the loop, and the loop body doesn't need any conditional checks, making the code cleaner. Notice in the state transition equation that each new state only depends on one adjacent state. So we can use the space compression technique from [Space Compression in Dynamic Programming](https://labuladong.online/en/algo/dynamic-programming/space-optimization/). Instead of using the entire `dp` array, we only need a variable to store the adjacent state, reducing space complexity to O(1): ```java // Original version int maxProfit_k_1(int[] prices) { int n = prices.length; // dp[0] represents base case, dp[i] represents day i-1 int[][] dp = new int[n + 1][2]; dp[0][0] = 0; dp[0][1] = Integer.MIN_VALUE; for (int i = 1; i <= n; i++) { dp[i][0] = Math.max(dp[i-1][0], dp[i-1][1] + prices[i-1]); dp[i][1] = Math.max(dp[i-1][1], -prices[i-1]); } return dp[n][0]; } // Space complexity optimized version int maxProfit_k_1(int[] prices) { int n = prices.length; // base case: dp[-1][0] = 0, dp[-1][1] = -infinity int dp_i_0 = 0, dp_i_1 = Integer.MIN_VALUE; for (int i = 0; i < n; i++) { // dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i]) dp_i_0 = Math.max(dp_i_0, dp_i_1 + prices[i]); // dp[i][1] = max(dp[i-1][1], -prices[i]) dp_i_1 = Math.max(dp_i_1, -prices[i]); } return dp_i_0; } ``` Both approaches yield the same result, but the space-compressed version is much more concise. In the following problems, you can compare how to optimize away the `dp` array space. ### 122. Best Time to Buy and Sell Stock II **The second problem, let's look at LeetCode problem 122 "[Best Time to Buy and Sell Stock II](https://leetcode.com/problems/best-time-to-buy-and-sell-stock-ii/)", which is the case where `k` is positive infinity**: **LeetCode 122. Best Time to Buy and Sell Stock II** You are given an integer array `prices` where `prices[i]` is the price of a given stock on the `i^(th)` day. On each day, you may decide to buy and/or sell the stock. You can only hold **at most one** share of the stock at any time. However, you can buy it then immediately sell it on the **same day**. Find and return *the **maximum** profit you can achieve*. Example 1:** ``` **Input:** prices = [7,1,5,3,6,4] **Output:** 7 **Explanation:** Buy on day 2 (price = 1) and sell on day 3 (price = 5), profit = 5-1 = 4. Then buy on day 4 (price = 3) and sell on day 5 (price = 6), profit = 6-3 = 3. Total profit is 4 + 3 = 7. ``` Example 2:** ``` **Input:** prices = [1,2,3,4,5] **Output:** 4 **Explanation:** Buy on day 1 (price = 1) and sell on day 5 (price = 5), profit = 5-1 = 4. Total profit is 4. ``` Example 3:** ``` **Input:** prices = [7,6,4,3,1] **Output:** 0 **Explanation:** There is no way to make a positive profit, so we never buy the stock to achieve the maximum profit of 0. ``` **Constraints:** - `1 <= prices.length <= 3 * 10^(4)` - `0 <= prices[i] <= 10^(4)` The problem specifically emphasizes that you can sell on the same day, but I think this condition is redundant. If you buy and sell on the same day, the profit is obviously 0, which is the same as not making any transaction, right? The key characteristic of this problem is that there's no limit on the total number of transactions `k`, which is equivalent to `k` being positive infinity. If `k` is positive infinity, then we can consider `k` and `k - 1` to be the same. We can rewrite the framework like this: ```python dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + prices[i]) dp[i][k][1] = max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i]) = max(dp[i-1][k][1], dp[i-1][k][0] - prices[i]) We find that k in the array no longer changes, which means we don't need to track the state k anymore: dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i]) dp[i][1] = max(dp[i-1][1], dp[i-1][0] - prices[i]) ``` Translating directly into code: ```java // Original version int maxProfit_k_inf(int[] prices) { int n = prices.length; int[][] dp = new int[n + 1][2]; dp[0][0] = 0; dp[0][1] = Integer.MIN_VALUE; for (int i = 1; i <= n; i++) { dp[i][0] = Math.max(dp[i-1][0], dp[i-1][1] + prices[i-1]); dp[i][1] = Math.max(dp[i-1][1], dp[i-1][0] - prices[i-1]); } return dp[n][0]; } // Space complexity optimized version int maxProfit_k_inf(int[] prices) { int n = prices.length; int dp_i_0 = 0, dp_i_1 = Integer.MIN_VALUE; for (int i = 0; i < n; i++) { int temp = dp_i_0; dp_i_0 = Math.max(dp_i_0, dp_i_1 + prices[i]); dp_i_1 = Math.max(dp_i_1, temp - prices[i]); } return dp_i_0; } ``` ### 309. Best Time to Buy and Sell Stock with Cooldown **The third problem, LeetCode problem 309 "[Best Time to Buy and Sell Stock with Cooldown](https://leetcode.com/problems/best-time-to-buy-and-sell-stock-with-cooldown/)", where `k` is positive infinity but with a cooldown period**: **LeetCode 309. Best Time to Buy and Sell Stock with Cooldown** You are given an array `prices` where `prices[i]` is the price of a given stock on the `i^(th)` day. Find the maximum profit you can achieve. You may complete as many transactions as you like (i.e., buy one and sell one share of the stock multiple times) with the following restrictions: - After you sell your stock, you cannot buy stock on the next day (i.e., cooldown one day). **Note:** You may not engage in multiple transactions simultaneously (i.e., you must sell the stock before you buy again). Example 1:** ``` **Input:** prices = [1,2,3,0,2] **Output:** 3 **Explanation:** transactions = [buy, sell, cooldown, buy, sell] ``` Example 2:** ``` **Input:** prices = [1] **Output:** 0 ``` **Constraints:** - `1 <= prices.length <= 5000` - `0 <= prices[i] <= 1000` Similar to the previous problem, but after each `sell` you must wait one day before trading again. We just need to incorporate this feature into the state transition equation from the previous problem: ```python dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i]) dp[i][1] = max(dp[i-1][1], dp[i-2][0] - prices[i]) Explanation: When choosing to buy on day i, we transition from state i-2, not i-1. ``` Since the state transition requires `dp[i-2][0]`, we need to offset by 2 positions, letting both `dp[0]` and `dp[1]` represent the base case, and `dp[i]` represent the state of day `i - 2`: ```java // Original version int maxProfit_with_cool(int[] prices) { int n = prices.length; // dp[0], dp[1] represent base case, dp[i] represents day i-2 int[][] dp = new int[n + 2][2]; dp[0][0] = 0; dp[0][1] = Integer.MIN_VALUE; dp[1][0] = 0; dp[1][1] = Integer.MIN_VALUE; for (int i = 2; i <= n + 1; i++) { // prices[i-2] is the stock price on day i-2 dp[i][0] = Math.max(dp[i-1][0], dp[i-1][1] + prices[i-2]); dp[i][1] = Math.max(dp[i-1][1], dp[i-2][0] - prices[i-2]); } return dp[n + 1][0]; } // Space complexity optimized version int maxProfit_with_cool(int[] prices) { int n = prices.length; int dp_i_0 = 0, dp_i_1 = Integer.MIN_VALUE; // represents dp[i-2][0] int dp_pre_0 = 0; for (int i = 0; i < n; i++) { int temp = dp_i_0; dp_i_0 = Math.max(dp_i_0, dp_i_1 + prices[i]); dp_i_1 = Math.max(dp_i_1, dp_pre_0 - prices[i]); dp_pre_0 = temp; } return dp_i_0; } ``` ### 714. Best Time to Buy and Sell Stock with Transaction Fee **The fourth problem, LeetCode problem 714 "[Best Time to Buy and Sell Stock with Transaction Fee](https://leetcode.com/problems/best-time-to-buy-and-sell-stock-with-transaction-fee/)", is the case where `k` is positive infinity with transaction fees**: **LeetCode 714. Best Time to Buy and Sell Stock with Transaction Fee** You are given an array `prices` where `prices[i]` is the price of a given stock on the `i^(th)` day, and an integer `fee` representing a transaction fee. Find the maximum profit you can achieve. You may complete as many transactions as you like, but you need to pay the transaction fee for each transaction. **Note:** - You may not engage in multiple transactions simultaneously (i.e., you must sell the stock before you buy again). - The transaction fee is only charged once for each stock purchase and sale. Example 1:** ``` **Input:** prices = [1,3,2,8,4,9], fee = 2 **Output:** 8 **Explanation:** The maximum profit can be achieved by: - Buying at prices[0] = 1 - Selling at prices[3] = 8 - Buying at prices[4] = 4 - Selling at prices[5] = 9 The total profit is ((8 - 1) - 2) + ((9 - 4) - 2) = 8. ``` Example 2:** ``` **Input:** prices = [1,3,7,5,10,3], fee = 3 **Output:** 6 ``` **Constraints:** - `1 <= prices.length <= 5 * 10^(4)` - `1 <= prices[i] < 5 * 10^(4)` - `0 <= fee < 5 * 10^(4)` Each transaction requires paying a fee, so we just need to subtract the fee from the profit. The modified equations are: ```python dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i]) dp[i][1] = max(dp[i-1][1], dp[i-1][0] - prices[i] - fee) Explanation: This is equivalent to increasing the buying price of the stock. Subtracting in the first equation works too - it's equivalent to decreasing the selling price. ``` ::: note Note If you directly subtract `fee` in the first equation, some test cases will fail. The error is caused by integer overflow, not a logic problem. One solution is to change all `int` types in the code to `long` types to avoid integer overflow. ::: Translating directly to code, note that when the state transition equation changes, the base case must also change accordingly: ```java // Original version int maxProfit_with_fee(int[] prices, int fee) { int n = prices.length; int[][] dp = new int[n + 1][2]; dp[0][0] = 0; dp[0][1] = Integer.MIN_VALUE; for (int i = 1; i <= n; i++) { dp[i][0] = Math.max(dp[i-1][0], dp[i-1][1] + prices[i-1]); dp[i][1] = Math.max(dp[i-1][1], dp[i-1][0] - prices[i-1] - fee); } return dp[n][0]; } // Space complexity optimized version int maxProfit_with_fee(int[] prices, int fee) { int n = prices.length; int dp_i_0 = 0, dp_i_1 = Integer.MIN_VALUE; for (int i = 0; i < n; i++) { int temp = dp_i_0; dp_i_0 = Math.max(dp_i_0, dp_i_1 + prices[i]); dp_i_1 = Math.max(dp_i_1, temp - prices[i] - fee); } return dp_i_0; } ``` ### 123. Best Time to Buy and Sell Stock III **The fifth problem, LeetCode problem 123 "[Best Time to Buy and Sell Stock III](https://leetcode.com/problems/best-time-to-buy-and-sell-stock-iii/)", is the case where `k = 2`**: **LeetCode 123. Best Time to Buy and Sell Stock III** You are given an array `prices` where `prices[i]` is the price of a given stock on the `i^(th)` day. Find the maximum profit you can achieve. You may complete **at most two transactions**. **Note:** You may not engage in multiple transactions simultaneously (i.e., you must sell the stock before you buy again). Example 1:** ``` **Input:** prices = [3,3,5,0,0,3,1,4] **Output:** 6 **Explanation:** Buy on day 4 (price = 0) and sell on day 6 (price = 3), profit = 3-0 = 3. Then buy on day 7 (price = 1) and sell on day 8 (price = 4), profit = 4-1 = 3. ``` Example 2:** ``` **Input:** prices = [1,2,3,4,5] **Output:** 4 **Explanation:** Buy on day 1 (price = 1) and sell on day 5 (price = 5), profit = 5-1 = 4. Note that you cannot buy on day 1, buy on day 2 and sell them later, as you are engaging multiple transactions at the same time. You must sell before buying again. ``` Example 3:** ``` **Input:** prices = [7,6,4,3,1] **Output:** 0 **Explanation:** In this case, no transaction is done, i.e. max profit = 0. ``` **Constraints:** - `1 <= prices.length <= 10^(5)` - `0 <= prices[i] <= 10^(5)` The case `k = 2` is slightly different from the previous problems, because those cases didn't really depend on `k`: either `k` was infinity and the state transition had nothing to do with `k`; or `k = 1`, which was close to the base case `k = 0`, and ultimately had no impact. In this problem where `k = 2` and the upcoming case where `k` is any positive integer, the handling of `k` becomes important. Let's write the code and analyze the reasons as we go. ```python The original state transition equation, with no simplification possible dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + prices[i]) dp[i][k][1] = max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i]) ``` Following the previous code, we might naturally write code like this (incorrect): ```java int k = 2; int[][][] dp = new int[n][k + 1][2]; for (int i = 0; i < n; i++) { if (i - 1 == -1) { // handle the base case dp[i][k][0] = 0; dp[i][k][1] = -prices[i]; continue; } 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[n - 1][k][0]; ``` Why is this wrong? Didn't I write it according to the state transition equation? Remember the "brute-force framework" we summarized earlier? It says we must enumerate all states. Actually, our previous solutions were enumerating all states, but in those problems `k` was simplified away. For example, in the first problem, the code framework when `k = 1`: ```java int n = prices.length; int[][] dp = new int[n][2]; for (int i = 0; i < n; 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], -prices[i]); } return dp[n - 1][0]; ``` But when `k = 2`, since the effect of `k` is not eliminated, we must enumerate `k`: ```java // Original version int maxProfit_k_2(int[] prices) { int max_k = 2, n = prices.length; int[][][] dp = new int[n + 1][max_k + 1][2]; // base case for (int k = 0; k <= max_k; k++) { dp[0][k][0] = 0; dp[0][k][1] = Integer.MIN_VALUE; } for (int i = 1; i <= n; i++) { for (int k = max_k; k >= 1; k--) { dp[i][k][0] = Math.max(dp[i-1][k][0], dp[i-1][k][1] + prices[i-1]); dp[i][k][1] = Math.max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i-1]); } } // enumerated n × max_k × 2 states, correct return dp[n][max_k][0]; } // Space complexity optimized version int maxProfit_k_2(int[] prices) { // base case int dp_i10 = 0, dp_i11 = Integer.MIN_VALUE; int dp_i20 = 0, dp_i21 = Integer.MIN_VALUE; for (int price : prices) { dp_i20 = Math.max(dp_i20, dp_i21 + price); dp_i21 = Math.max(dp_i21, dp_i10 - price); dp_i10 = Math.max(dp_i10, dp_i11 + price); dp_i11 = Math.max(dp_i11, -price); } return dp_i20; } ``` ::: note Note **Some readers might be confused here: the base case for `k` is 0, so logically shouldn't we enumerate state `k` starting from `k = 1, k++`? And if you actually traverse `k` from small to large like this, submitting it will also be accepted**. ::: This question is valid, because in our previous article [Dynamic Programming Q&A](https://labuladong.online/en/algo/dynamic-programming/faq-summary/), we introduced how to determine the traversal order of the `dp` array, which is mainly based on the base case, starting from the base case and gradually approaching the result. But why can I also get accepted by traversing `k` from large to small? Notice that `dp[i][k][..]` doesn't depend on `dp[i][k - 1][..]`, but depends on `dp[i - 1][k - 1][..]`, and `dp[i - 1][..][..]` has already been computed. So whether you use `k = max_k, k--` or `k = 1, k++`, you can get the correct answer. Then why do I use the `k = max_k, k--` approach? Because it aligns with the semantics: When you buy stocks, what's the initial "state"? It should start from day 0, with no trades made yet, so the maximum transaction limit `k` should be `max_k`. As the "state" progresses, you make trades, so the transaction limit `k` should decrease. Thinking this way, the `k = max_k, k--` approach is more realistic. Of course, since `k` has a small range here, we can also skip the for loop and directly list out all cases for k = 1 and 2. The space-optimized version of the above code does exactly this. With the state transition equation and clearly named variables as guidance, you should easily understand it. Actually, we could be mysterious and replace those four variables with `a, b, c, d`. Then when others see your code, they'll be amazed and look at you with great respect. ### 188. Best Time to Buy and Sell Stock IV The sixth problem is LeetCode problem 188 "[Best Time to Buy and Sell Stock IV](https://leetcode.com/problems/best-time-to-buy-and-sell-stock-iv/)", where `k` can be any number given by the problem: **LeetCode 188. Best Time to Buy and Sell Stock IV** You are given an integer array `prices` where `prices[i]` is the price of a given stock on the `i^(th)` day, and an integer `k`. Find the maximum profit you can achieve. You may complete at most `k` transactions: i.e. you may buy at most `k` times and sell at most `k` times. **Note:** You may not engage in multiple transactions simultaneously (i.e., you must sell the stock before you buy again). Example 1:** ``` **Input:** k = 2, prices = [2,4,1] **Output:** 2 **Explanation:** Buy on day 1 (price = 2) and sell on day 2 (price = 4), profit = 4-2 = 2. ``` Example 2:** ``` **Input:** k = 2, prices = [3,2,6,5,0,3] **Output:** 7 **Explanation:** Buy on day 2 (price = 2) and sell on day 3 (price = 6), profit = 6-2 = 4. Then buy on day 5 (price = 0) and sell on day 6 (price = 3), profit = 3-0 = 3. ``` **Constraints:** - `1 <= k <= 100` - `1 <= prices.length <= 1000` - `0 <= prices[i] <= 1000` With the foundation from the previous problem where `k = 2`, this problem should be no different from the first solution of that problem. You just need to replace `k = 2` with the input `k` from the problem. But if you try it, you'll get a memory limit exceeded error. This is because the input `k` can be very large, making the `dp` array too big. So let's think about it: what's the maximum meaningful value for the number of transactions `k`? One transaction consists of buying and selling, which requires at least two days. So the effective limit `k` should not exceed `n/2`. If it does, the constraint has no effect, which is equivalent to the case where `k` has no limit - and we've already solved that case before. So we can directly reuse our previous code: ```java int maxProfit_k_any(int max_k, int[] prices) { int n = prices.length; if (n <= 0) { return 0; } if (max_k > n / 2) { // reuse maxProfit_k_inf function return maxProfit_k_inf(prices); } int[][][] dp = new int[n + 1][max_k + 1][2]; // base case for (int k = 0; k <= max_k; k++) { dp[0][k][0] = 0; dp[0][k][1] = Integer.MIN_VALUE; } for (int i = 1; i <= n; i++) { for (int k = max_k; k >= 1; k--) { dp[i][k][0] = Math.max(dp[i-1][k][0], dp[i-1][k][1] + prices[i-1]); dp[i][k][1] = Math.max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i-1]); } } return dp[n][max_k][0]; } ``` With this, all 6 problems are solved using a single state transition equation. ## All Methods Return to One If you've made it this far, give yourself a round of applause. Understanding such complex dynamic programming problems for the first time must have consumed quite a few brain cells. But it's worth it—the stock trading series is among the more difficult dynamic programming problems. If you can figure out all these problems, what else could possibly stand in your way? **Now you've passed 80 of the 81 trials. Let me challenge you one more time—please implement the following function**: ```java int maxProfit_all_in_one(int max_k, int[] prices, int cooldown, int fee); ``` Given a stock price array `prices`, you can make at most `max_k` transactions. Each transaction costs an additional `fee` as transaction fee, and after each transaction you must wait `cooldown` days before making the next transaction. Calculate and return the maximum profit you can achieve. Scary, right? If you gave someone this problem directly, they'd probably pass out on the spot. But having worked through it step by step, you should easily see that this problem is just a combination of the cases we've discussed before. So we just need to mix together the code we implemented earlier, **adding the `cooldown` and `fee` constraints to both the base case and the state transition equation**. Since state transition needs `dp[i-cooldown-1]`, we need to offset by `cooldown + 1` positions: ```java // Consider the limit on the number of transactions, cooldown period, and transaction fees simultaneously int maxProfit_all_in_one(int max_k, int[] prices, int cooldown, int fee) { int n = prices.length; if (n <= 0) { return 0; } if (max_k > n / 2) { // The case where the number of transactions k is unlimited return maxProfit_k_inf(prices, cooldown, fee); } // offset is cooldown + 1, ensuring access to dp[i - cooldown - 1] int offset = cooldown + 1; int[][][] dp = new int[n + offset][max_k + 1][2]; // base case: first offset rows are all base cases for (int i = 0; i < offset; i++) { for (int k = 0; k <= max_k; k++) { dp[i][k][0] = 0; dp[i][k][1] = Integer.MIN_VALUE; } } for (int i = offset; i < n + offset; i++) { for (int k = max_k; k >= 1; k--) { // prices[i - offset] is the stock price on day i-offset dp[i][k][0] = Math.max(dp[i-1][k][0], dp[i-1][k][1] + prices[i - offset]); // Consider both cooldown and fee simultaneously dp[i][k][1] = Math.max(dp[i-1][k][1], dp[i - cooldown - 1][k-1][0] - prices[i - offset] - fee); } } return dp[n + offset - 1][max_k][0]; } // k is unlimited, including transaction fees and cooldown period int maxProfit_k_inf(int[] prices, int cooldown, int fee) { int n = prices.length; int offset = cooldown + 1; int[][] dp = new int[n + offset][2]; // base case: first offset rows are all base cases for (int i = 0; i < offset; i++) { dp[i][0] = 0; dp[i][1] = Integer.MIN_VALUE; } for (int i = offset; i < n + offset; i++) { dp[i][0] = Math.max(dp[i-1][0], dp[i-1][1] + prices[i - offset]); // Consider both cooldown and fee simultaneously dp[i][1] = Math.max(dp[i-1][1], dp[i - cooldown - 1][0] - prices[i - offset] - fee); } return dp[n + offset - 1][0]; } ``` You can use this `maxProfit_all_in_one` function to solve all 6 problems we discussed earlier. Since we can't optimize the `dp` array, the execution efficiency isn't optimal, but the correctness is guaranteed. Let me wrap up. This article showed you how to solve complex problems through state transition, crushing 6 stock trading problems with a single state transition equation. Looking back now, it's not that scary, right? The key is to enumerate all possible "states", then think about how to brute-force update these "states". We typically use a multi-dimensional `dp` array to store these states, starting from the base case and moving forward until we reach the final state—which is the answer we want. Thinking about this process, do you now understand what "dynamic programming" really means? For the stock trading problem specifically, we identified three states and used a three-dimensional array. It's still just brute-force + update, but we can make it sound fancy—this is called "3D DP". Sounds impressive, doesn't it? ================================================ FILE: dynamic-programming/subsequence-problems.md ================================================ ::: info Prerequisites Before reading this article, you need to learn: - [Dynamic Programming Core Framework](https://labuladong.online/en/algo/essential-technique/dynamic-programming-framework/) ::: Subsequence problems are common, and they are not easy. First, subsequences are harder than substrings and subarrays. A subsequence is not continuous, but a substring/subarray is continuous. Even if you try brute-force, you may still not know how to do it, not to mention using it to solve algorithm problems. Also, subsequence problems often involve two strings, like [Longest Common Subsequence](https://labuladong.online/en/algo/dynamic-programming/longest-common-subsequence/). If you don’t have enough experience, it is hard to come up with the solution. So this article will show the common patterns for subsequence problems. There are only two templates. If you think in these two ways, you are very likely to get it right. In most cases, these problems ask for the **longest subsequence**. The shortest subsequence is just one character, so it is not interesting. Once a problem involves subsequences and max/min, it almost always tests **dynamic programming**, and the time complexity is usually **O(n^2)**. The reason is simple: how many subsequences can a string have? At least exponential. Without dynamic programming, what else can you do? Since we use dynamic programming, we need to define a `dp` array and find the state transition. The two templates are two ways to define the `dp` array. Different problems may need different `dp` definitions. ## 1. Two templates **1) Template 1: a 1D `dp` array**: ```java int n = array.length; int[] dp = new int[n]; for (int i = 1; i < n; i++) { for (int j = 0; j < i; j++) { dp[i] = best(dp[i], dp[j] + ...) } } ``` For example, [Longest Increasing Subsequence](https://labuladong.online/en/algo/dynamic-programming/longest-increasing-subsequence/) and [Maximum Subarray Sum](https://labuladong.online/en/algo/dynamic-programming/maximum-subarray/) use this idea. In this idea, the definition of `dp` is: **In the subarray `arr[0..i]`, the length of the subsequence that ends with `arr[i]` is `dp[i]`.** Why does LIS need this? Because it matches induction, so we can find the state transition. I won’t expand it here. **2) Template 2: a 2D `dp` array**: ```java int n = arr.length; int[][] dp = new dp[n][n]; for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { if (arr[i] == arr[j]) dp[i][j] = dp[i][j] + ... else // calculate min or max dp[i][j] = min(...) } } ``` This idea is used more often, especially when the subsequence involves two strings/arrays, like [Longest Common Subsequence](https://labuladong.online/en/algo/dynamic-programming/longest-common-subsequence/) and [Edit Distance](https://labuladong.online/en/algo/dynamic-programming/edit-distance/). It can also be used for one string/array, like the palindrome subsequence problem in this article. **2.1 Two strings/arrays**: the `dp` definition is: **In subarray `arr1[0..i]` and subarray `arr2[0..j]`, the subsequence length we want is `dp[i][j]`.** **2.2 One string/array**: the `dp` definition is: **In subarray `array[i..j]`, the subsequence length we want is `dp[i][j]`.** Next, we use the longest palindromic subsequence problem to explain how to use dynamic programming in the second case. ## 2. Longest Palindromic Subsequence We previously solved [Longest Palindromic Substring](https://labuladong.online/en/algo/essential-technique/array-two-pointers-summary/). Now let’s level up: LeetCode 516, “[Longest Palindromic Subsequence](https://leetcode.com/problems/longest-palindromic-subsequence/)”. We need the length of the longest palindromic subsequence. Given a string `s`, find the length of the longest palindromic subsequence in `s`. The function signature is: ```java int longestPalindromeSubseq(String s); ``` Example: `s = "aecda"`, the answer is 3, because the longest palindromic subsequence is `"aca"`, length 3. We define the `dp` array like this: **in substring `s[i..j]`, the length of the longest palindromic subsequence is `dp[i][j]`**. Remember this definition, or you won’t understand the algorithm. Why define `dp` this way? To find the state transition, we need induction: how to use known results to get unknown results. With this definition, induction is natural, and the state transition is easy to see. More specifically, to compute `dp[i][j]`, assume you already know `dp[i+1][j-1]` (the answer for `s[i+1..j-1]`). Can you compute `dp[i][j]` (the answer for `s[i..j]`)? ![](../pictures/lps/1.jpg) Yes. It depends on whether `s[i]` equals `s[j]`: **If they are equal**, then they must be part of the longest palindromic subsequence, so: ![](../pictures/lps/2.jpg) **If they are not equal**, then they **cannot both** be in the longest palindromic subsequence. We try removing one side, and take the larger one: ![](../pictures/lps/3.jpg) In code: ```java if (s[i] == s[j]) // both must be in the longest palindromic subsequence dp[i][j] = dp[i + 1][j - 1] + 2; else // compare s[i+1..j] and s[i..j-1] dp[i][j] = max(dp[i + 1][j], dp[i][j - 1]); ``` So we get the transition. By the `dp` definition, the answer is `dp[0][n - 1]`, which is the longest palindromic subsequence length of the whole string `s`. ## 3. Code implementation First, the base case: if there is only one character, the longest palindromic subsequence length is 1, so `dp[i][j] = 1` when `i == j`. Since `i <= j`, for `i > j`, there is no substring, so we should set it to 0. Now look at the transition: to compute `dp[i][j]`, we need `dp[i+1][j-1]`, `dp[i+1][j]`, and `dp[i][j-1]`. After filling the base case, the table looks like this: ![](../pictures/lps/4.jpg) **To make sure these needed cells are already computed, we must traverse diagonally or traverse backward**: ![](../pictures/lps/5.jpg) ::: tip Tip For more about the traversal order of the `dp` array, see [Dynamic Programming Q&A](https://labuladong.online/en/algo/dynamic-programming/faq-summary/). ::: I choose backward traversal. The code is: ```java class Solution { public int longestPalindromeSubseq(String s) { int n = s.length(); // initialize the dp array to all zeros int[][] dp = new int[n][n]; // base case for (int i = 0; i < n; i++) { dp[i][i] = 1; } // traverse in reverse to ensure correct state transitions for (int i = n - 1; i >= 0; i--) { for (int j = i + 1; j < n; j++) { // state transition equation 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]); } } } // length of the longest palindromic subsequence in the entire s return dp[0][n - 1]; } } ``` Now the longest palindromic subsequence problem is solved. ## 4. Extension Palindrome problems are not used everywhere, but after you can solve longest palindromic subsequence, you can also solve similar problems. For example, LeetCode 1312, “[Minimum Insertion Steps to Make a String Palindrome](https://leetcode.com/problems/minimum-insertion-steps-to-make-a-string-palindrome/)”: Given a string `s`, you can insert any character at any position. To make `s` a palindrome, find the minimum number of insertions. The function signature is: ```java int minInsertions(String s); ``` Example: `s = "abcea"`, the answer is 2, because you can insert 2 characters to get `"abeceba"` or `"aebcbea"`. If `s = "aba"`, the answer is 0, because `s` is already a palindrome. This is also a subsequence problem on a single string, so we can use a 2D `dp` array. Define `dp[i][j]` like this: **For substring `s[i..j]`, the minimum insertions needed to make it a palindrome is `dp[i][j]`.** By this definition, the base case is `dp[i][i] = 0`, because one character is already a palindrome. Then use induction. Assume you already know `dp[i+1][j-1]`. How do you compute `dp[i][j]`? ![](../pictures/palindrome-insert/1.jpeg) It is very similar to the longest palindromic subsequence transition: ```java if (s[i] == s[j]) { // no insertion needed dp[i][j] = dp[i + 1][j - 1]; } else { // make s[i+1..j] or s[i..j-1] a palindrome, pick the smaller one // then insert one more char to match s[i] or s[j] dp[i][j] = min(dp[i + 1][j], dp[i][j - 1]) + 1; } ``` Finally, we still traverse the `dp` table backward and write the code: ```java class Solution { public int minInsertions(String s) { int n = s.length(); // dp[i][j] represents the minimum number of insertions needed to make the substring s[i..j] a palindrome // initialize all elements of the dp array to 0 int[][] dp = new int[n][n]; // traverse in reverse order to ensure correct state transitions for (int i = n - 1; i >= 0; i--) { for (int j = i + 1; j < n; j++) { // state transition equation if (s.charAt(i) == s.charAt(j)) { dp[i][j] = dp[i + 1][j - 1]; } else { dp[i][j] = Math.min(dp[i + 1][j], dp[i][j - 1]) + 1; } } } // the minimum number of insertions for the entire string s return dp[0][n - 1]; } } ``` Now this problem is also solved with the subsequence template. Since it is so similar to the longest palindromic subsequence problem, can we reuse that solution directly? Yes. We can even avoid writing the transition if you think about it: **First compute the longest palindromic subsequence of `s`. Then all characters not in it are exactly the characters we need to insert.** So we can reuse the previous `longestPalindromeSubseq` function: ```java class Solution { // calculate the minimum number of insertions to make s a palindrome public int minInsertions(String s) { return s.length() - longestPalindromeSubseq(s); } // calculate the length of the longest palindromic subsequence in s public int longestPalindromeSubseq(String s) { int n = s.length(); // initialize the dp array to all zeros int[][] dp = new int[n][n]; // base case for (int i = 0; i < n; i++) { dp[i][i] = 1; } // traverse in reverse to ensure correct state transitions for (int i = n - 1; i >= 0; i--) { for (int j = i + 1; j < n; j++) { // state transition equation 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]); } } } // length of the longest palindromic subsequence in the entire s return dp[0][n - 1]; } } ``` That’s all for subsequence algorithms. Hope it helps. ================================================ FILE: dynamic-programming/word-break.md ================================================ ::: info Prerequisites Before reading this article, you need to study: - [Binary Tree Algorithms Series (Overview)](https://labuladong.online/en/algo/essential-technique/binary-tree-summary/) - [Dynamic Programming Core Framework](https://labuladong.online/en/algo/essential-technique/dynamic-programming-framework/) ::: In the earlier article [Step-by-Step Guide to Binary Trees (Overview)](https://labuladong.online/en/algo/essential-technique/binary-tree-summary/), we divided recursive enumeration into two approaches: "Traversal" and "Decomposing Problems". The "Traversal" approach can be extended to [Backtracking Algorithms](https://labuladong.online/en/algo/essential-technique/backtrack-framework/), and the "Decomposing Problems" approach can be extended to [Dynamic Programming Algorithms](https://labuladong.online/en/algo/essential-technique/dynamic-programming-framework/). This shift in thinking is not limited to binary tree-related algorithms. In this article, we will step outside the realm of binary tree problems to see how to abstract problems into a tree structure in actual algorithm questions, and then optimize step-by-step through "Traversal" and "Decomposing Problems" approaches, smoothly transitioning from backtracking algorithms to dynamic programming algorithms. As a quick aside, the previous article [Detailed Explanation of the Dynamic Programming Core Framework](https://labuladong.online/en/algo/essential-technique/dynamic-programming-framework/) stated that **standard dynamic programming problems always aim to find the optimal solution**. This is because dynamic programming problems have a property called "optimal substructure", meaning that the optimal solution to the overall problem can be derived from the optimal solutions of its subproblems. However, in common parlance, even if a problem does not seek the optimal solution, as long as it uses a memoization technique to eliminate overlapping subproblems, we often call it a dynamic programming algorithm. Strictly speaking, this does not fit the definition of a dynamic programming problem. It might be more accurate to call such solutions "DFS algorithms with memoization". But we don't need to be too hung up on terminology. Since everyone is comfortable with the term, we can call it dynamic programming. The two problems discussed in this article do not seek the optimal solution, but we will still refer to their solutions as dynamic programming solutions. This explanation is to prevent confusion for those who prefer precision. Without further ado, let's dive into the problems. ## Word Break I Let's first look at Leetcode problem 139, "[Word Break](https://leetcode.com/problems/word-break/)": **LeetCode 139. Word Break** Given a string `s` and a dictionary of strings `wordDict`, return `true` if `s` can be segmented into a space-separated sequence of one or more dictionary words. **Note** that the same word in the dictionary may be reused multiple times in the segmentation. Example 1:** ``` **Input:** s = "leetcode", wordDict = ["leet","code"] **Output:** true **Explanation:** Return true because "leetcode" can be segmented as "leet code". ``` Example 2:** ``` **Input:** s = "applepenapple", wordDict = ["apple","pen"] **Output:** true **Explanation:** Return true because "applepenapple" can be segmented as "apple pen apple". Note that you are allowed to reuse a dictionary word. ``` Example 3:** ``` **Input:** s = "catsandog", wordDict = ["cats","dog","sand","and","cat"] **Output:** false ``` **Constraints:** - `1 <= s.length <= 300` - `1 <= wordDict.length <= 1000` - `1 <= wordDict[i].length <= 20` - `s` and `wordDict[i]` consist of only lowercase English letters. - All the strings of `wordDict` are **unique**. The function signature is as follows: ```java boolean wordBreak(String s, List wordDict); ``` This is a very frequently asked interview question. Let's think about how to solve it using "traversal" and "problem decomposition" strategies. ### Traversal Strategy (Backtracking Solution) **First, let's discuss the "traversal" strategy, specifically using backtracking to solve this problem.** The most classic application of backtracking algorithms is in permutation and combination problems. It is not hard to see that this problem can be transformed into a permutation problem: Now, given a word list `wordDict` with no duplicate words and a string `s`, determine whether you can select and arrange some words (words can be selected repeatedly) from `wordDict` to form the string `s`. This is the last variant discussed in the previous article [Nine Variants of Permutation and Combination Problems Solved by Backtracking Algorithms](https://labuladong.online/en/algo/essential-technique/permutation-combination-subset-all-in-one/): the permutation problem with distinct elements that can be selected repeatedly. In the previous article, I wrote a `permuteRepeat` function, the code is as follows: ```java class Solution { List> res = new LinkedList<>(); LinkedList track = new LinkedList<>(); public List> permuteRepeat(int[] nums) { backtrack(nums); return res; } // core function of backtracking algorithm void backtrack(int[] nums) { // base case, reaching the leaf node if (track.size() == nums.length) { // collect the values at the leaf node res.add(new LinkedList(track)); return; } // standard framework of backtracking algorithm for (int i = 0; i < nums.length; i++) { // make a choice track.add(nums[i]); // enter the next level of backtracking tree backtrack(nums); // undo the choice track.removeLast(); } } } ``` When you input `nums = [1,2,3]` to this function, the output is 3^3 = 27 possible combinations: ```java [ [1,1,1],[1,1,2],[1,1,3],[1,2,1],[1,2,2],[1,2,3],[1,3,1],[1,3,2],[1,3,3], [2,1,1],[2,1,2],[2,1,3],[2,2,1],[2,2,2],[2,2,3],[2,3,1],[2,3,2],[2,3,3], [3,1,1],[3,1,2],[3,1,3],[3,2,1],[3,2,2],[3,2,3],[3,3,1],[3,3,2],[3,3,3] ] ``` This code is actually traversing a full N-ary tree of height `N + 1` (`N` is the length of `nums`). Each path from the root to a leaf is a permutation result: ![](../pictures/word-break/1.jpeg) Similarly, this problem is quite the same. Suppose `wordDict = ["a", "aa", "ab"], s = "aaab"`. If you want to use the words in `wordDict` to form `s`, it is like facing an M-ary tree, where `M` is the number of words in `wordDict`. **At each node of the backtracking tree, you need to check which word matches the prefix of `s[i..]`, and decide which branch to take:** ![](../pictures/word-break/2.jpeg) As mentioned before in [Backtracking Algorithm Framework](https://labuladong.online/en/algo/essential-technique/backtrack-framework/), you can think of the `backtrack` function as a pointer walking through the backtracking tree. It keeps track of the variable `i` at each node, so you can traverse the whole tree and find all combinations that match `s`. Here is the backtracking solution code: ```java class Solution { List wordDict; // record whether a valid answer is found boolean found = false; // record the path of the backtracking algorithm LinkedList track = new LinkedList<>(); // main function public boolean wordBreak(String s, List wordDict) { this.wordDict = wordDict; // execute the backtracking algorithm to enumerate all possible combinations backtrack(s, 0); return found; } // backtracking algorithm framework void backtrack(String s, int i) { // base case if (found) { // if an answer has been found, do not continue recursive search return; } if (i == s.length()) { // the entire s has been matched, a valid answer is found found = true; return; } // backtracking algorithm framework for (String word : wordDict) { // check which word can match the prefix of s[i..] int len = word.length(); if (i + len <= s.length() && s.substring(i, i + len).equals(word)) { // found a word that matches s[i..i+len) // make a choice track.addLast(word); // enter the next level of the backtracking tree, continue matching s[i+len..] backtrack(s, i + len); // undo the choice track.removeLast(); } } } } ``` This code follows the backtracking framework strictly. It should be easy to understand. But this code cannot pass all test cases. Let's analyze its time complexity using the method from [Time Complexity Guide](https://labuladong.online/en/algo/essential-technique/complexity-analysis/). A rough way to estimate the time complexity of a recursive function is: number of recursive calls (number of nodes in the recursion tree) x complexity of the function itself. In this problem, each node in the recursion tree is one way to cut `s`. In the worst case, how many ways to cut `s`? A string `s` of length `N` has `N - 1` "gaps" to cut. Each gap can be "cut" or "not cut", so there are up to $O(2^N)$ ways, meaning up to $O(2^N)$ nodes in the recursion tree. Of course, in practice, things are better because there is pruning logic. But in terms of worst-case complexity, the number of nodes is exponential. How about the complexity of the `backtrack` function itself? The main cost is looping over `wordDict` to find a word that matches the prefix of `s[i..]`: ```java // Traverse all words in wordDict for (String word : wordDict) { // Check which word can match the prefix of s[i..] int len = word.length(); if (i + len <= s.length() && s.substring(i, i + len).equals(word)) { // Found a word that matches s[i..i+len) // ... } } ``` Let `M` be the length of `wordDict`, and `N` the length of `s`. The worst-case time complexity here is $O(MN)$ (for loop is $O(M)$, Java's `substring` is $O(N)$). So in total, the time complexity is $O(2^N * MN)$. Here is a small optimization: you can also enumerate prefixes of `s[i..]` and check if `wordDict` contains them: ```java // Note that we need to convert it into a HashSet to improve the efficiency of the contains method HashSet wordDict = new HashSet<>(wordDict); // Traverse all prefixes of s[i..] for (int len = 1; i + len <= s.length(); len++) { // Check if there is a word in wordDict that can match the prefix of s[i..] String prefix = s.substring(i, i + len); if (wordDict.contains(prefix)) { // Found a word that matches s[i..i+len) // ... } } ``` This code gives the same result as before, but its time complexity is $O(N^2)$, which is different from the previous code. Which is better? It depends on the problem's data range. Here, `1 <= s.length <= 300, 1 <= wordDict.length <= 1000`, so $O(N^2)$ is smaller. This code should run a bit faster. You can try it yourself; I won't write it out here. But even if you optimize this part, the total time complexity is still exponential, $O(2^N * N^2)$, so this approach cannot pass all test cases. Where is the problem? For example, input `wordDict = ["a", "aa", "b"], s = "aaab"`. Notice that the backtracking algorithm will have repeated cases: ![](../pictures/word-break/3.jpeg) The red-marked parts are different cuts but end up with the same `"aab"`. So the subtrees below these two nodes repeat, which means redundant computation. This is why the complexity is exponential. ### Optimizing with Postorder Position For any algorithm, the way to remove redundant calculations is to use a memo. Backtracking can also use a memo, which we call "pruning". This means we cut off the redundant subtrees. For example, when facing the substring `"aab"`, we want the memo to tell us if `"aab"` can be split successfully. If we already tried and found it cannot be split, we can skip it and do not need to explore its subtrees, which improves efficiency. If it can be split, we do not need to care about the memo, because `found == true` is a base case, and the whole recursion will stop. As explained in [General Rules for Binary Tree/Recursion Algorithms](https://labuladong.online/en/algo/essential-technique/binary-tree-summary/), if we want the memo to work this way, we need to update the memo at the postorder position, because `"aab"` is actually a subtree. **You need to record in the memo whether the current subtree can be split successfully after visiting all its children.** The backtracking function `backtrack` is used for traversal, and does not return a value, so there is no information passed back from the subtree. But for this problem, we have a way, because we have an external variable `found`. This variable can tell us if the subtree can be split successfully: **If `found` is false, it means we have not found a successful split, which also means the current subtree cannot be split.** Now we can write this result in the memo, so next time we can skip redundant brute-force searching. The actual code only needs a small change to add the memo: ```java class Solution { // memoization, store substrings (subtrees) that cannot be split, to avoid redundant calculations HashSet memo = new HashSet<>(); // ... void backtrack(String s, int i) { if (found) { return; } if (i == s.length()) { found = true; return; } // added pruning logic, check if the substring (subtree) has been calculated String suffix = s.substring(i); if (memo.contains(suffix)) { // the current substring (subtree) cannot be split, no need to continue recursion return; } for (String word : wordDict) { // ... } // post-order position, record substrings (subtrees) that cannot be split in the memo if (!found) { memo.add(suffix); } } } ``` ### Thinking of Problem Decomposition (Dynamic Programming) The last solution used the backtracking algorithm because the problem is simple. We used a `found` variable to update the memoization at the postorder position. But for more complex problems, like Word Break II, this method will not work. To update the memoization with the answer of a subtree, it is better to use the return value of the recursive function. This is the "problem decomposition" way of thinking. Before, we tried to solve this problem from a permutation and combination perspective. Now, let's see if we can break the original problem into smaller, similar subproblems, and use their answers to solve the original problem. For the input string `s`, if we can find a word from `wordDict` that matches the prefix `s[0..k]`, then as long as we can form `s[k+1..]`, we can form the whole string `s`. In other words, we have broken the big problem `wordBreak(s[0..])` into a smaller subproblem `wordBreak(s[k+1..])`, and then use the answer of the subproblem to get the answer of the original problem. With this idea, we can define a `dp` function as follows: ```java // Definition: return whether s[i..] can be composed int dp(String s, int i); // Calculate whether the entire s can be composed, call dp(s, 0) ``` With this function definition, we can translate the above logic into pseudocode: ```java List wordDict; // Definition: return whether s[i..] can be formed int dp(String s, int i) { // base case, s[i..] is an empty string if (i == s.length()) { return true; } // traverse wordDict to see which words are prefixes of s[i..] for (Strnig word : wordDict) { // word is a prefix of s[i..] if (s.substring(i).startsWith(word)) { int len = word.length(); // as long as s[i+len..] can be formed, s[i..] can be formed if (dp(s, i + len) == true) { return true; } } } // all words have been tried, cannot form the entire s return false; } ``` Just like in the backtracking solution, the for loop in the `dp` function can also be optimized: ```java // Note that we use a hash set to quickly determine if an element exists HashSet wordDict; // Definition: return whether s[i..] can be formed int dp(String s, int i) { // base case, s[i..] is an empty string if (i == s.length()) { return true; } // Traverse all prefixes of s[i..] to see which prefixes exist in wordDict for (int len = 1; i + len <= s.length(); len++) { // s[i..len) exists in wordDict if (wordDict.contains(s.substring(i, i + len))) { // As long as s[i+len..] can be formed, s[i..] can be formed if (dp(s, i + len) == true) { return true; } } } // All words have been tried, and the entire s cannot be formed return false; } ``` For this `dp` function, the position of the pointer `i` is the "state". So we can use a memoization table to avoid recalculating the same subproblems, making the solution faster. Here is the final code: ```java class Solution { // Use a hash set to facilitate quick existence checks HashSet wordDict; // Memoization, -1 represents uncomputed, 0 represents cannot form, 1 represents can form int[] memo; // Main function public boolean wordBreak(String s, List wordDict) { // Convert to a hash set for quick element existence checks this.wordDict = new HashSet<>(wordDict); // Initialize memoization array to -1 this.memo = new int[s.length()]; Arrays.fill(memo, -1); return dp(s, 0); } // Definition: whether s[i..] can be formed boolean dp(String s, int i) { // base case if (i == s.length()) { return true; } // Prevent redundant calculations if (memo[i] != -1) { return memo[i] == 0 ? false : true; } // Traverse all prefixes of s[i..] for (int len = 1; i + len <= s.length(); len++) { // Check which prefixes exist in wordDict String prefix = s.substring(i, i + len); if (wordDict.contains(prefix)) { // Found a word that matches s[i..i+len) // As long as s[i+len..] can be formed, s[i..] can be formed boolean subProblem = dp(s, i + len); if (subProblem == true) { memo[i] = 1; return true; } } } // s[i..] cannot be formed memo[i] = 0; return false; } } ``` ::: tip More Optimization Notice when we compute the `prefix`, we use the substring function provided by the language, which has time complexity $O(N)$. The start index is always `i`, and the end index increases with `j`. We can manually maintain the `prefix` substring to avoid calling the substring function, which will make it a bit faster. You can try this small optimization yourself. ::: This solution can pass all test cases. According to the [Algorithm Time and Space Complexity Guide](https://labuladong.online/en/algo/essential-technique/complexity-analysis/), let's calculate its time complexity: With memoization, duplicate nodes in the recursion tree are removed. The number of function calls drops from exponential to $O(N)$ (number of states). Each function call has complexity $O(N^2)$, so the total time complexity is $O(N^3)$, which is much better than backtracking. ## Word Break II With the last problem as a foundation, LeetCode 140 "[Word Break II](https://leetcode.com/problems/word-break-ii/)" becomes much easier. Let's look at the problem: **LeetCode 140. Word Break II** Given a string `s` and a dictionary of strings `wordDict`, add spaces in `s` to construct a sentence where each word is a valid dictionary word. Return all such possible sentences in **any order**. **Note** that the same word in the dictionary may be reused multiple times in the segmentation. Example 1:** ``` **Input:** s = "catsanddog", wordDict = ["cat","cats","and","sand","dog"] **Output:** ["cats and dog","cat sand dog"] ``` Example 2:** ``` **Input:** s = "pineapplepenapple", wordDict = ["apple","pen","applepen","pine","pineapple"] **Output:** ["pine apple pen apple","pineapple pen apple","pine applepen apple"] **Explanation:** Note that you are allowed to reuse a dictionary word. ``` Example 3:** ``` **Input:** s = "catsandog", wordDict = ["cats","dog","sand","and","cat"] **Output:** [] ``` **Constraints:** - `1 <= s.length <= 20` - `1 <= wordDict.length <= 1000` - `1 <= wordDict[i].length <= 10` - `s` and `wordDict[i]` consist of only lowercase English letters. - All the strings of `wordDict` are **unique**. - Input is generated in a way that the length of the answer doesn't exceed 10^(5). Compared to the last problem, this question not only asks if `s` can be broken up, but also how to break it up. In fact, you just need to change the previous solution a little to solve this problem. ### Traversal Approach (Backtracking) In the last problem, the backtracking algorithm used a `found` variable. Once it found a way to break up the string, it stopped searching. For this problem, we should not stop early. Instead, we need to collect all possible ways to break up the string as answers: ```java class Solution { // record the results List res = new LinkedList<>(); // record the path of the backtracking algorithm LinkedList track = new LinkedList<>(); List wordDict; // main function public List wordBreak(String s, List wordDict) { this.wordDict = wordDict; // execute the backtracking algorithm to enumerate all possible combinations backtrack(s, 0); return res; } // backtracking algorithm framework void backtrack(String s, int i) { // base case if (i == s.length()) { // find a valid combination that spells out the entire s, convert to string res.add(String.join(" ", track)); return; } // backtracking algorithm framework for (String word : wordDict) { // see which word can match the prefix of s[i..] int len = word.length(); if (i + len <= s.length() && s.substring(i, i + len).equals(word)) { // find a word that matches s[i..i+len) // make a choice track.addLast(word); // enter the next level of the backtracking tree, continue matching s[i+len..] backtrack(s, i + len); // undo the choice track.removeLast(); } } } } ``` The time complexity of this solution is similar to the previous one: $O(2^N * MN)$. But since the data size for this problem is small, it can still pass all test cases. ### Can We Optimize with Postorder Position? Like before, this solution still has room for optimization: ![](../pictures/word-break/3.jpeg) For repeated subtrees, there will still be unnecessary repeated calculations. We can use a memo to cache the split results of substring `"aab"` to avoid this. However, it is hard to add memoization in a backtracking algorithm, because the `track` variable only keeps track of the path from the root to the current node, and does not record information about subtrees. So, to remove overlapping subproblems, we usually use a "divide problem" approach and update the memo with the function's return value. ### Divide Problem Approach (Dynamic Programming) This problem can also be solved by dividing the problem. We just need to slightly change the `dp` function from the previous problem: ```java class Solution { HashSet wordDict; // memoization List[] memo; public List wordBreak(String s, List wordDict) { this.wordDict = new HashSet<>(wordDict); memo = new List[s.length()]; return dp(s, 0); } // definition: returns all possible constructions of s[i..] using wordDict List dp(String s, int i) { List res = new LinkedList<>(); if (i == s.length()) { res.add(""); return res; } // prevent redundant calculations if (memo[i] != null) { return memo[i]; } // traverse all prefixes of s[i..] for (int len = 1; i + len <= s.length(); len++) { // check which prefixes exist in wordDict String prefix = s.substring(i, i + len); if (wordDict.contains(prefix)) { // found a word matching s[i..i+len) List subProblem = dp(s, i + len); // all combinations of s[i+len..] plus prefix // are all combinations of s[i] for (String sub : subProblem) { if (sub.isEmpty()) { // prevent extra spaces res.add(prefix); } else { res.add(prefix + " " + sub); } } } } // store in memo memo[i] = res; return res; } } ``` This solution also uses a memo to remove overlapping subproblems, so the number of recursive calls for the `dp` function is reduced to $O(N)$. But the complexity of the `dp` function itself increases, because `subProblem` is a list of subsets and its length is exponential. Also, string concatenation is not very efficient, and memo needs to store all subproblem results. So, from a Big O point of view, the time complexity of this algorithm is not lower than the backtracking algorithm. It is still exponential. But this solution does remove overlapping subproblems, so it is a bit better than backtracking. In summary, when we solve permutation and combination problems, we usually use backtracking to "traverse" the tree, not the divide problem approach. That's because saving the results of subproblems takes a lot of time and space. Unless there are many overlapping subproblems, it's not worth it. That’s all for this article. I hope you now have a better understanding of the backtracking approach and the divide problem approach. ================================================ FILE: interview/README.md ================================================ # 高频面试系列 8 说了,本章都是高频面试题,配合前面的动态规划系列,祝各位马到成功! 欢迎关注我的公众号 labuladong,查看全部文章: ![labuladong二维码](../pictures/qrcode.jpg) ================================================ FILE: interview/binary-search-in-action.md ================================================ ::: info Required Knowledge Before reading this article, you need to learn: - [Binary Search Framework in Detail](https://labuladong.online/en/algo/essential-technique/binary-search-framework/) ::: In [Binary Search Framework in Detail](https://labuladong.online/en/algo/essential-technique/binary-search-framework/), we thoroughly examined the details of binary search, exploring three scenarios: "searching for an element", "searching for the left boundary", and "searching for the right boundary", teaching you how to write correct, bug-free binary search algorithms. **However, the binary search framework summarized in that article is limited to the basic scenario of "searching for a specified element in a sorted array". Real algorithm problems aren't this straightforward—you might not even recognize that a problem can use binary search**. So this article summarizes a framework for applying binary search algorithms, helping you think through and analyze binary search problems systematically, step by step, to arrive at the solution. ## Original Binary Search Code The prototype of binary search is searching for an element `target` in a **sorted array** and returning the corresponding index. If the element doesn't exist, you can return some special value. These details can be implemented with minor adjustments to the algorithm. There's another important issue: if the **sorted array** contains multiple `target` elements, these elements will definitely be adjacent. This involves whether the algorithm should return the index of the leftmost `target` element or the rightmost one—the so-called "searching for the left boundary" and "searching for the right boundary". This can also be implemented through minor code adjustments. **We discussed these issues in detail in the previous article [Binary Search Core Framework](https://labuladong.online/en/algo/essential-technique/binary-search-framework/). Readers who are unclear on this should review that article**. If you already understand the basic binary search algorithm, you can continue reading. **In real algorithm problems, the "search left boundary" and "search right boundary" scenarios are commonly used**, while rarely will you be asked to simply "search for an element". This is because algorithm problems generally ask you to find optimal values, like finding the "minimum speed" for eating bananas or the "minimum capacity" for a ship. The process of finding optimal values is necessarily a process of searching for a boundary, so we'll analyze these two boundary search binary algorithms in detail. ::: note Note Note that I'm using the left-closed, right-open notation for binary search. If you prefer both ends closed, you can modify the code accordingly. ::: The specific code implementation for the "search left boundary" binary search algorithm is as follows: ```java // search for the left boundary int left_bound(int[] nums, int target) { if (nums.length == 0) return -1; int left = 0, right = nums.length; while (left < right) { int mid = left + (right - left) / 2; if (nums[mid] == target) { // when target is found, shrink the right boundary right = mid; } else if (nums[mid] < target) { left = mid + 1; } else if (nums[mid] > target) { right = mid; } } return left; } ``` Suppose the input array is `nums = [1,2,3,3,3,5,7]` and you want to search for `target = 3`. The algorithm will return index 2. If we draw a diagram, it looks like this: ![](../pictures/binary-search-in-action/1.jpeg) The specific code implementation for the "search right boundary" binary search algorithm is as follows: ```java // search for the right boundary int right_bound(int[] nums, int target) { if (nums.length == 0) return -1; int left = 0, right = nums.length; while (left < right) { int mid = left + (right - left) / 2; if (nums[mid] == target) { // when target is found, shrink the left boundary left = mid + 1; } else if (nums[mid] < target) { left = mid + 1; } else if (nums[mid] > target) { right = mid; } } return left - 1; } ``` With the same input, the algorithm will return index 4. If we draw a diagram, it looks like this: ![](../pictures/binary-search-in-action/2.jpeg) Good, the above content is all review. I expect readers at this point should understand it all. Remember the diagrams above—any problem that can be abstracted into these diagrams can be solved using binary search. ## Generalizing Binary Search Problems What problems can apply binary search algorithm techniques? **First, you need to abstract from the problem an independent variable `x`, a function `f(x)` about `x`, and a target value `target`**. Additionally, `x, f(x), target` must satisfy the following conditions: **1. `f(x)` must be a monotonic function on `x` (either monotonically increasing or decreasing)**. **2. The problem asks you to calculate the value of `x` that satisfies the constraint `f(x) == target`**. These rules sound a bit abstract, so let's use a concrete example: You're given a sorted array `nums` in ascending order and a target element `target`. Calculate the index position of `target` in the array. If there are multiple target elements, return the smallest index. This is the "search left boundary" basic problem type. We've written the solution code before, but what are `x, f(x), target` here? We can think of the array element indices as the independent variable `x`, and define the function relationship `f(x)` like this: ```java // The function f(x) is a monotonically increasing function of the independent variable x // The input parameter nums will not change, so it can be ignored and is not considered an independent variable int f(int x, int[] nums) { return nums[x]; } ``` This function `f` is actually just accessing the array `nums`. Since the problem gives us the array `nums` in ascending order, the function `f(x)` is monotonically increasing on `x`. Finally, what does the problem ask us to find? Doesn't it ask us to calculate the leftmost index of element `target`? Isn't that equivalent to asking "what is the minimum value of `x` that satisfies `f(x) == target`"? Draw a diagram like this: ![](../pictures/binary-search-in-action/3.jpeg) **If you encounter an algorithm problem and can abstract it into this diagram, you can apply binary search to it**. The algorithm code is as follows: ```java // Function f is a monotonically increasing function of the independent variable x int f(int x, int[] nums) { return nums[x]; } int left_bound(int[] nums, int target) { if (nums.length == 0) return -1; int left = 0, right = nums.length; while (left < right) { int mid = left + (right - left) / 2; if (f(mid, nums) == target) { // When target is found, shrink the right boundary right = mid; } else if (f(mid, nums) < target) { left = mid + 1; } else if (f(mid, nums) > target) { right = mid; } } return left; } ``` This code is actually redundant—it's the conventional binary search code with a slight modification, wrapping direct access to `nums[mid]` in a function `f`. However, this abstracts the framework of binary search thinking in specific algorithm problems. ## A Framework for Applying Binary Search When you want to use binary search to solve a specific algorithm problem, start by thinking through this code framework: ```java // function f is a monotonous function of the independent variable x int f(int x) { // ... } // main function, find the extremum of x under the constraint of f(x) == target int solution(int[] nums, int target) { if (nums.length == 0) return -1; // ask yourself: what is the minimum value of the independent variable x? int left = ...; // ask yourself: what is the maximum value of the independent variable x? int right = ... + 1; while (left < right) { int mid = left + (right - left) / 2; if (f(mid) == target) { // ask yourself: is the problem asking for the left boundary or the right boundary? // ... } else if (f(mid) < target) { // ask yourself: how to make f(x) larger? // ... } else if (f(mid) > target) { // ask yourself: how to make f(x) smaller? // ... } } return left; } ``` Here's how to break it down step by step: **1. Figure out what `x, f(x), target` represent, then write the code for function `f`.** **2. Determine the range of `x` as your search interval, and initialize `left` and `right` accordingly.** **3. Based on what the problem asks for, decide whether you need left-boundary or right-boundary binary search, then write your solution.** Let's walk through this process with a few examples. ## Example 1: Koko Eating Bananas This is LeetCode problem 875, "[Koko Eating Bananas](https://leetcode.cn/problems/koko-eating-bananas/)": **LeetCode 875. Koko Eating Bananas** Koko loves to eat bananas. There are `n` piles of bananas, the `i^(th)` pile has `piles[i]` bananas. The guards have gone and will come back in `h` hours. Koko can decide her bananas-per-hour eating speed of `k`. Each hour, she chooses some pile of bananas and eats `k` bananas from that pile. If the pile has less than `k` bananas, she eats all of them instead and will not eat any more bananas during this hour. Koko likes to eat slowly but still wants to finish eating all the bananas before the guards return. Return *the minimum integer* `k` *such that she can eat all the bananas within* `h` *hours*. Example 1:** ``` **Input:** piles = [3,6,7,11], h = 8 **Output:** 4 ``` Example 2:** ``` **Input:** piles = [30,11,23,4,20], h = 5 **Output:** 30 ``` Example 3:** ``` **Input:** piles = [30,11,23,4,20], h = 6 **Output:** 23 ``` **Constraints:** - `1 <= piles.length <= 10^(4)` - `piles.length <= h <= 10^(9)` - `1 <= piles[i] <= 10^(9)` Koko can eat at most one pile per hour. If she can't finish a pile, she'll continue eating it in the next hour. If she finishes early, she'll just wait until the next hour to start another pile. She wants to finish all the bananas before the guard returns. We need to find the **minimum eating speed `K`**. Here's the function signature: ```java int minEatingSpeed(int[] piles, int H); ``` So how do we apply the framework we just outlined to write a binary search solution? Just follow the steps: **1. Figure out what `x, f(x), target` represent, then write the code for function `f`.** What's the independent variable `x`? Think back to those function graphs—binary search is essentially searching over the independent variable. Whatever the problem asks you to find, that's your `x`. Here, Koko's eating speed is our `x`. What's the monotonic relationship `f(x)` with respect to `x`? The faster Koko eats, the less time she needs to finish all the piles. Speed and time have a monotonic relationship. So we can define `f(x)` like this: If Koko eats at a speed of `x` bananas/hour, she needs `f(x)` hours to finish all the bananas. Here's the implementation: ```java // Definition: When the speed is x, it takes f(x) hours to eat all the bananas // f(x) monotonically decreases as x increases long f(int[] piles, int x) { long hours = 0; for (int i = 0; i < piles.length; i++) { hours += piles[i] / x; if (piles[i] % x > 0) { hours++; } } return hours; } ``` ::: info Info Why does `f(x)` return a `long`? Look at the data constraints and the logic of `f`. Elements in `piles` can be as large as 10^9, and there can be up to 10^4 elements. When `x` is 1, the `hours` variable could reach 10^13, which exceeds the maximum value of an `int` (roughly 2×10^9). Using `long` prevents potential integer overflow. ::: The `target` is straightforward—the time limit `H` is naturally the `target`, serving as the maximum constraint on `f(x)`'s return value. **2. Determine the range of `x` as your search interval, and initialize `left` and `right` accordingly.** What's Koko's minimum eating speed? What's the maximum? The minimum speed is obviously 1. The maximum is the largest element in `piles`, since she can eat at most one pile per hour—having a bigger appetite won't help. You have two options here: either loop through `piles` to find the maximum value, or check the problem's constraints for the range of elements in `piles` and initialize `right` to a value just outside that range. I'll go with the second approach. The problem states `1 <= piles[i] <= 10^9`, so we can set our search boundaries like this: ```java public int minEatingSpeed(int[] piles, int H) { int left = 1; // Note that I choose to write the binary search with the left closed and right open, right is an open interval, so add one int right = 1000000000 + 1; // ... } ``` Since binary search has logarithmic complexity, even if `right` is a huge number, the algorithm remains efficient. **3. Based on what the problem asks for, decide whether you need left-boundary or right-boundary binary search, then write your solution.** Now we know: `x` is the eating speed, `f(x)` is monotonically decreasing, and `target` is the time limit `H`. The problem asks for the minimum speed, meaning we want `x` to be as small as possible: ![](../pictures/binary-search-in-action/4.jpeg) This calls for left-boundary binary search. But watch out—`f(x)` is monotonically decreasing, so don't blindly apply the template. Think it through with the diagram above: ```java class Solution { public int minEatingSpeed(int[] piles, int H) { int left = 1; int right = 1000000000 + 1; while (left < right) { int mid = left + (right - left) / 2; if (f(piles, mid) == H) { // searching for the left boundary, thus we need to shrink the right boundary right = mid; } else if (f(piles, mid) < H) { // need to make the return value of f(x) larger right = mid; } else if (f(piles, mid) > H) { // need to make the return value of f(x) smaller left = mid + 1; } } return left; } } ``` ::: tip Tip I'm using the left-closed, right-open binary search style here. If you prefer the both-ends-closed style, just modify how you initialize `right` and update it: ```java // both-ends-closed binary search style int minEatingSpeed(int[] piles, int H) { int left = 1; // right is a closed interval, so use the actual max value int right = 1000000000; // right is a closed interval, so use <= while (left <= right) { int mid = left + (right - left) / 2; if (f(piles, mid) <= H) { // right is a closed interval, so use mid - 1 right = mid - 1; } else { left = mid + 1; } } return left; } ``` For a deep dive into the details of this algorithm, check out [Binary Search Explained](https://labuladong.online/en/algo/essential-technique/binary-search-framework/). I won't go into it here. ::: And that solves the problem. The extra if-branches in our framework are mainly there to help you understand what's happening. Once you've got a working solution, I'd recommend merging redundant branches to improve runtime efficiency: ```java class Solution { public int minEatingSpeed(int[] piles, int H) { int left = 1; int right = 1000000000 + 1; while (left < right) { int mid = left + (right - left) / 2; if (f(piles, mid) <= H) { right = mid; } else { left = mid + 1; } } return left; } // definition: when the speed is x, it takes f(x) hours to eat all the bananas // f(x) is monotonically decreasing as x increases long f(int[] piles, int x) { long hours = 0; for (int i = 0; i < piles.length; i++) { hours += piles[i] / x; if (piles[i] % x > 0) { hours++; } } return hours; } } ``` ## Problem 2: Shipping Packages Let's look at LeetCode problem 1011, [Capacity To Ship Packages Within D Days](https://leetcode.cn/problems/capacity-to-ship-packages-within-d-days/): **LeetCode 1011. Capacity To Ship Packages Within D Days** A conveyor belt has packages that must be shipped from one port to another within `days` days. The `i^(th)` package on the conveyor belt has a weight of `weights[i]`. Each day, we load the ship with packages on the conveyor belt (in the order given by `weights`). We may not load more weight than the maximum weight capacity of the ship. Return the least weight capacity of the ship that will result in all the packages on the conveyor belt being shipped within `days` days. Example 1:** ``` **Input:** weights = [1,2,3,4,5,6,7,8,9,10], days = 5 **Output:** 15 **Explanation:** A ship capacity of 15 is the minimum to ship all the packages in 5 days like this: 1st day: 1, 2, 3, 4, 5 2nd day: 6, 7 3rd day: 8 4th day: 9 5th day: 10 Note that the cargo must be shipped in the order given, so using a ship of capacity 14 and splitting the packages into parts like (2, 3, 4, 5), (1, 6, 7), (8), (9), (10) is not allowed. ``` Example 2:** ``` **Input:** weights = [3,2,2,4,1,4], days = 3 **Output:** 6 **Explanation:** A ship capacity of 6 is the minimum to ship all the packages in 3 days like this: 1st day: 3, 2 2nd day: 2, 4 3rd day: 1, 4 ``` Example 3:** ``` **Input:** weights = [1,2,3,1,1], days = 4 **Output:** 3 **Explanation:** 1st day: 1 2nd day: 2 3rd day: 3 4th day: 1, 1 ``` **Constraints:** - `1 <= days <= weights.length <= 5 * 10^(4)` - `1 <= weights[i] <= 500` You need to ship all packages in order within `D` days, and packages can't be split. What's the minimum capacity the ship needs? Here's the function signature: ```java int shipWithinDays(int[] weights, int days); ``` Same as before—just follow the process: **1. Figure out what `x, f(x), target` represent, then implement the function `f`**. The question asks about ship capacity, so that's our variable `x`. Shipping days and capacity are inversely related—higher capacity means fewer days. So we'll make `f(x)` calculate how many days we need given capacity `x`. This makes `f(x)` monotonically decreasing. Here's how to implement `f(x)`: ```java // Definition: when the carrying capacity is x, it takes f(x) days to transport all the goods // f(x) decreases monotonically with the increase of x int f(int[] weights, int x) { int days = 0; for (int i = 0; i < weights.length; ) { // load as much cargo as possible int cap = x; while (i < weights.length) { if (cap < weights[i]) break; else cap -= weights[i]; i++; } days++; } return days; } ``` For this problem, `target` is clearly the number of shipping days `D`. We need to find the minimum capacity where `f(x) == D`. **2. Determine the range of `x` to use as the binary search interval, and initialize `left` and `right`**. What's the minimum capacity? What's the maximum? The minimum capacity should be the heaviest single package in `weights`—you've got to be able to carry at least one package per trip. The maximum capacity would be the sum of all packages—just carry everything in one trip. That gives us our search interval `[left, right)`: ```java int shipWithinDays(int[] weights, int days) { int left = 0; // note that right is an open interval, so add one more int right = 1; for (int w : weights) { left = Math.max(left, w); right += w; } // ... } ``` **3. Based on what the problem asks for, decide whether to use left-boundary or right-boundary binary search, then write the solution**. Now we know: `x` is the ship's capacity, `f(x)` is a monotonically decreasing function, and `target` is the day limit `D`. The problem wants the minimum capacity, which means we want the smallest possible `x`: ![](../pictures/binary-search-in-action/5.jpeg) This is a classic left-boundary binary search. Using the diagram above, we can write the code: ```java public int shipWithinDays(int[] weights, int days) { int left = 0; // note that right is an open interval, so add one more int right = 1; for (int w : weights) { left = Math.max(left, w); right += w; } while (left < right) { int mid = left + (right - left) / 2; if (f(weights, mid) == days) { // searching for the left boundary, we need to shrink the right boundary right = mid; } else if (f(weights, mid) < days) { // need to make the return value of f(x) larger right = mid; } else if (f(weights, mid) > days) { // need to make the return value of f(x) smaller left = mid + 1; } } return left; } ``` That's the solution! We can clean up the redundant if branches to make it faster. Here's the final code: ```java class Solution { public int shipWithinDays(int[] weights, int days) { int left = 0; int right = 1; for (int w : weights) { left = Math.max(left, w); right += w; } while (left < right) { int mid = left + (right - left) / 2; if (f(weights, mid) <= days) { right = mid; } else { left = mid + 1; } } return left; } // definition: when the carrying capacity is x, it takes f(x) days to transport all the goods // f(x) decreases monotonically as x increases int f(int[] weights, int x) { int days = 0; for (int i = 0; i < weights.length; ) { // load as many goods as possible int cap = x; while (i < weights.length) { if (cap < weights[i]) break; else cap -= weights[i]; i++; } days++; } return days; } } ``` ## Problem 3: Split Array Let's try LeetCode problem 410, [Split Array Largest Sum](https://leetcode.cn/problems/split-array-largest-sum/), marked as Hard: **LeetCode 410. Split Array Largest Sum** Given an integer array `nums` and an integer `k`, split `nums` into `k` non-empty subarrays such that the largest sum of any subarray is **minimized**. Return *the minimized largest sum of the split*. A **subarray** is a contiguous part of the array. Example 1:** ``` **Input:** nums = [7,2,5,10,8], k = 2 **Output:** 18 **Explanation:** There are four ways to split nums into two subarrays. The best way is to split it into [7,2,5] and [10,8], where the largest sum among the two subarrays is only 18. ``` Example 2:** ``` **Input:** nums = [1,2,3,4,5], k = 2 **Output:** 9 **Explanation:** There are four ways to split nums into two subarrays. The best way is to split it into [1,2,3] and [4,5], where the largest sum among the two subarrays is only 9. ``` **Constraints:** - `1 <= nums.length <= 1000` - `0 <= nums[i] <= 10^(6)` - `1 <= k <= min(50, nums.length)` Here's the function signature: ```java int splitArray(int[] nums, int m); ``` This problem is pretty confusing with all the max and min talk. Take a close look at the examples to understand what it's really asking. Here's the gist: you're given an array `nums` and a number `m`, and you need to split `nums` into `m` subarrays. There are multiple ways to split it. Each way creates `m` subarrays, and in each split, one of those subarrays will have the largest sum, right? You want to find the split where that largest subarray sum is as small as possible compared to all other splits. Your algorithm should return that minimized largest subarray sum. Yikes, this sounds super hard and confusing. How do we even apply our binary search pattern here? **Here's the thing: this problem is actually identical to the shipping problem we just solved. Don't believe me? Let me rephrase it**: You have one cargo ship. You have several packages, each weighing `nums[i]`. You need to ship all of them within `m` days. What's the minimum capacity your ship needs? That's exactly LeetCode 1011, [Capacity To Ship Packages Within D Days](https://leetcode.cn/problems/capacity-to-ship-packages-within-d-days/), which we just solved! Each day's cargo is one subarray of `nums`. Shipping everything in `m` days means splitting `nums` into `m` subarrays. Minimizing the ship's capacity means minimizing the largest subarray sum. So you can literally copy-paste the shipping problem's solution: ```java class Solution { public int splitArray(int[] nums, int m) { return shipWithinDays(nums, m); } int shipWithinDays(int[] weights, int days) { // see above } int f(int[] weights, int x) { // see above } } ``` That wraps it up! To summarize: if you spot a monotonic relationship in a problem, try using binary search. Once you understand the monotonicity and figure out which type of binary search to use, you can sketch it out and write the final code. ================================================ FILE: interview/celebrity-problem.md ================================================ ::: info Prerequisite Before reading this article, you should first learn: - [Graph Basics and Common Implementations](https://labuladong.online/en/algo/data-structure-basic/graph-basic/) ::: Today, let's talk about the classic "celebrity problem": You are given the social relationships of `n` people (you know whether any two people know each other), and you need to find the "celebrity" among them. A "celebrity" must meet two conditions: 1. Everyone else knows the celebrity. 2. The celebrity knows no one else. This is a graph algorithm problem. Social relationships can be modeled as a graph. If each person is a node, and "knows" is a directed edge from one node to another, then the celebrity is a special node in this graph: ![](../pictures/celebrity/1.jpeg) **This node has no outgoing edges to others, but every other node has an edge pointing to it.** In technical terms, the celebrity node has out-degree 0 and in-degree `n - 1`. How do we represent the social relationships of these `n` people? In the previous article [Graph Algorithms Basics](https://labuladong.online/en/algo/data-structure-basic/graph-basic/), we said that there are two main ways to store a graph: adjacency list and adjacency matrix. Adjacency lists save space; adjacency matrices make it fast to check if two nodes are connected. For the celebrity problem, we often need to check if two people know each other (i.e., if two nodes are connected), so we can use an adjacency matrix to represent the relationships. So, the algorithm problem can be described like this: You are given an `n x n` matrix `graph` representing a graph with `n` nodes. Each person is a node, numbered from `0` to `n - 1`. If `graph[i][j] == 1`, person `i` knows person `j`. If `graph[i][j] == 0`, person `i` does not know person `j`. Given this graph, can you find out if there is a "celebrity" among the `n` people? If there is, return the celebrity's number. If not, return -1. The function signature is as follows: ```java int findCelebrity(int[][] graph); ``` For example, suppose the input matrix looks like this: ![](../pictures/celebrity/2.jpeg) Then the algorithm should return 2. LeetCode Problem 277 "[Find the Celebrity](https://leetcode.com/problems/find-the-celebrity/)" is this classic problem. But instead of giving you the adjacency matrix directly, it only tells you the total number of people `n`, and provides an API `knows` to check if one person knows another: ```java // can be called directly, returns whether i knows j boolean knows(int i, int j); // please implement: return the number of the "celebrity" int findCelebrity(int n) { // todo } ``` Basically, the `knows` API is just a way to access the adjacency matrix. To keep things simple, let's follow the LeetCode way to discuss this classic problem. ## Brute-force Solution We can easily write a simple brute-force algorithm: ```java class Solution extends Relation { public int findCelebrity(int n) { for (int cand = 0; cand < n; cand++) { int other; for (other = 0; other < n; other++) { if (cand == other) continue; // ensure that everyone else knows cand, and cand does not know anyone else // otherwise, cand cannot be the celebrity if (knows(cand, other) || !knows(other, cand)) { break; } } if (other == n) { // found the celebrity return cand; } } // no one fits the characteristics of a celebrity return -1; } } ``` `cand` stands for candidate. The brute-force idea is to check every person as a candidate and see if they meet the "celebrity" conditions. As mentioned above, the `knows` function just accesses the adjacency matrix, and each call takes O(1) time. So the brute-force solution has a worst-case time complexity of O(N^2). Is there a smarter way to optimize this? Actually, yes. Think about it: where do we spend the most time? For every candidate `cand`, we use an inner for loop to check if `cand` really is a celebrity. This inner loop seems silly. While checking if someone "is a celebrity" needs a loop, checking if someone "is not a celebrity" can be done more quickly. **Since the definition of "celebrity" guarantees there can be at most one, we can use elimination: first rule out people who are obviously not celebrities. This avoids nested loops and reduces the time complexity.** ## Optimized Solution Let me repeat the definition of a "celebrity": 1. Everyone else knows the celebrity. 2. The celebrity does not know anyone else. This is interesting because it means there can be at most one celebrity in the group. It is easy to understand. If there are two celebrities at the same time, the two rules would be in conflict. **In other words, if you look at the relationship between any two candidates, you can always rule out one person as not being the celebrity.** We cannot tell if the other person is the celebrity by just looking at this pair, but that's not important. The key is we can always remove one who cannot be the celebrity, so we reduce our search. This is the main idea behind the optimization and can be hard to understand. Let's see why checking any two candidates lets us rule out one. Think about it. What are the possible relationships between two people? There are only four cases: you know me but I don’t know you, I know you but you don’t know me, we both know each other, or we both don’t know each other. If we think of people as nodes, a red arrow means “does not know,” and a green arrow means “knows.” The relationships look like these four cases: ![](../pictures/celebrity/3.jpeg) Let’s say the two people are called `cand` and `other`. Let’s look at each case and see who we can rule out. Case 1: `cand` knows `other`, so `cand` can’t be the celebrity. The celebrity should not know anyone. Case 2: `other` knows `cand`, so `other` can’t be the celebrity. Case 3: They both know each other. Neither is the celebrity, so we can rule out either one. Case 4: Neither knows the other. Again, neither is the celebrity, so we can rule out either one. The celebrity should be known by everyone. So, by checking the relationship between any two candidates, we can always rule out at least one. We can use code like this to decide: ```java if (knows(cand, other) || !knows(other, cand)) { // cand cannot be a celebrity } else { // other cannot be a celebrity } ``` If you understand this, the optimized solution is easy. **We keep picking two candidates, compare them, and remove one, until there is only one left. Then we use a for loop to check if this person is really the celebrity.** Here is the full code for this idea: ```java class Solution extends Relation { public int findCelebrity(int n) { if (n == 1) return 0; // put all candidates into the queue LinkedList q = new LinkedList<>(); for (int i = 0; i < n; i++) { q.addLast(i); } // keep eliminating until only one candidate is left while (q.size() >= 2) { // each time take out two candidates and eliminate one int cand = q.removeFirst(); int other = q.removeFirst(); if (knows(cand, other) || !knows(other, cand)) { // cand cannot be the celebrity, eliminate and let other rejoin the queue q.addFirst(other); } else { // other cannot be the celebrity, eliminate and let cand rejoin the queue q.addFirst(cand); } } // now there is only one candidate left, determine if he is really the celebrity int cand = q.removeFirst(); for (int other = 0; other < n; other++) { if (other == cand) { continue; } // ensure that everyone else knows cand, and cand does not know anyone else if (!knows(other, cand) || knows(cand, other)) { return -1; } } // cand is the celebrity return cand; } } ``` This algorithm avoids nested for loops. The time complexity is O(N). But we use a queue to store candidates, so space complexity is O(N). ::: note Note `LinkedList` is just used as a container for the candidates. Each time, we pick two people to compare and remove one. It does not matter which two you pick, or the order they are added to the queue. We use `addFirst` for convenience, but you can also use `addLast`. The result is the same. ::: Can we do better and also reduce the space complexity? ## Final Solution If you understand the optimized solution above, you can solve the problem without extra space. Here is the code: ```java class Solution extends Relation { public int findCelebrity(int n) { int cand = 0; for (int other = 1; other < n; other++) { if (!knows(other, cand) || knows(cand, other)) { // cand cannot be a celebrity, eliminate // assume other is a celebrity cand = other; } else { // other cannot be a celebrity, eliminate // do nothing, continue to assume cand is a celebrity } } // now cand is the last one remaining after elimination, but it's not guaranteed to be a celebrity for (int other = 0; other < n; other++) { if (cand == other) continue; // need to ensure everyone else knows cand, and cand does not know any other person if (!knows(other, cand) || knows(cand, other)) { return -1; } } return cand; } } ``` Before, we used a `LinkedList` as a queue to store the candidates. In this improved solution, we use the change between `other` and `cand` to simulate the queue process, so we don’t need extra storage. Now, our solution for the celebrity problem has time complexity O(N) and space complexity O(1). This is the best solution. ================================================ FILE: interview/count-primes.md ================================================ The definition of a prime number looks very simple: if a number can only be divided by 1 and itself, then this number is a prime. But even though the definition is simple, not many people can write efficient algorithms related to primes. For example, LeetCode problem 204 “[Count Primes](https://leetcode.com/problems/count-primes/)” asks you to write this function: ```java // return the number of prime numbers in the interval [2, n) int countPrimes(int n) // for example, countPrimes(10) returns 4 // because 2, 3, 5, 7 are prime numbers ``` How would you write this function? Most people will probably write it like this: ```java int countPrimes(int n) { int count = 0; for (int i = 2; i < n; i++) if (isPrime(i)) count++; return count; } // determine if the integer n is a prime boolean isPrime(int n) { for (int i = 2; i < n; i++) if (n % i == 0) // has other divisors return false; return true; } ``` With this code, the time complexity is O(n^2), which is too slow. The main issues are: **using the `isPrime` helper function is not efficient enough, and even if you use it, this version still has a lot of repeated computation**. First, let’s see **how to correctly check if a number is prime**. You only need to change the for-loop condition in the `isPrime` code: ```java boolean isPrime(int n) { for (int i = 2; i * i <= n; i++) ... } ``` In other words, `i` does not need to go up to `n`. It only needs to go up to `sqrt(n)`. This is a basic fact in number theory: if a number $n$ is not prime, then it must have a factor less than or equal to $\sqrt{n}$. Take `n = 12` as an example: ```java 12 = 2 × 6 12 = 3 × 4 12 = sqrt(12) × sqrt(12) 12 = 4 × 3 12 = 6 × 2 ``` You can see that the last two products are just the first two reversed, and the turning point is at `sqrt(n)`. So, if you do not find any factor in the range `[2, sqrt(n)]`, you can say `n` is a prime. That also means there will be no factor in the range `[sqrt(n), n]`. Now the time complexity of the `isPrime` function is $O(\sqrt{N})$. **But actually, we do not need this function to implement `countPrimes`**. I only want you to understand the idea of `sqrt(n)` here, because we will use it again later. ## Efficiently Implement `countPrimes` The next method is called the "Sieve of Eratosthenes". It was invented by an ancient Greek scholar named Eratosthenes. We saw his name in middle school textbooks, because he was the first person to correctly compute the Earth's circumference using shadows. He is known as the "father of geography". Back to the topic, the key idea of the sieve is the opposite of the normal method above: Start from 2. We know 2 is a prime number. Then 2 × 2 = 4, 3 × 2 = 6, 4 × 2 = 8... all cannot be prime. Next we see that 3 is also prime. Then 3 × 2 = 6, 3 × 3 = 9, 3 × 4 = 12... also cannot be prime. This GIF from Wikipedia shows the process clearly: ![](../pictures/prime/1.gif) By now, you may already understand the logic of this elimination method. Here is our first version of the code: ```java class Solution { public int countPrimes(int n) { boolean[] isPrime = new boolean[n]; // initialize the array to true Arrays.fill(isPrime, true); for (int i = 2; i < n; i++) { if (isPrime[i]) { // multiples of i cannot be prime for (int j = 2 * i; j < n; j += i) { isPrime[j] = false; } } } int count = 0; for (int i = 2; i < n; i++) { if (isPrime[i]) count++; } return count; } } ``` If you understand the code above, you already know the main idea. But there are still two small optimizations. First, think about the `isPrime` function at the beginning of this article. Because of factor symmetry, the for loop only needs to check `[2, sqrt(n)]`. Here it is similar: the outer for loop only needs to go up to `sqrt(n)`: ```java for (int i = 2; i * i < n; i++) if (isPrime[i]) ... ``` Besides that, it is easy to miss that the inner for loop can also be optimized. Before, we wrote: ```java for (int j = 2 * i; j < n; j += i) isPrime[j] = false; ``` This marks all multiples of `i` as `false`, but there is still extra work. For example, when `n = 25` and `i = 5`, the algorithm will mark 5 × 2 = 10, 5 × 3 = 15, and so on. But these numbers were already marked by `i = 2` and `i = 3` as 2 × 5 and 3 × 5. We can improve it a bit and let `j` start from `i * i` instead of `2 * i`: ```java for (int j = i * i; j < n; j += i) isPrime[j] = false; ``` Now the prime counting algorithm is efficient. Here is the final complete code: ```java class Solution { public int countPrimes(int n) { boolean[] isPrime = new boolean[n]; Arrays.fill(isPrime, true); for (int i = 2; i * i < n; i++) if (isPrime[i]) for (int j = i * i; j < n; j += i) isPrime[j] = false; int count = 0; for (int i = 2; i < n; i++) if (isPrime[i]) count++; return count; } } ``` The time complexity of this algorithm is not easy to compute. Clearly, the time depends on the two nested for loops. The number of operations is about: ```java n/2 + n/3 + n/5 + n/7 + ... = n × (1/2 + 1/3 + 1/5 + 1/7...) ``` The part in brackets is the sum of the reciprocals of primes. The final result is $O(N * \log\log N)$. If you are interested, you can look up the proof of this time complexity. This is all for the prime counting algorithm. As you can see, even a simple problem can have many details to polish. ================================================ FILE: interview/island-problems.md ================================================ ::: info Prerequisite Knowledge Before reading this article, you should first learn: - [Binary Tree Algorithms (Overview)](https://labuladong.online/en/algo/essential-technique/binary-tree-summary/) - [Backtracking Algorithm Core Framework](https://labuladong.online/en/algo/essential-technique/backtrack-framework/) - [Common Questions About Backtracking/DFS](https://labuladong.online/en/algo/essential-technique/backtrack-vs-dfs/) ::: The island problems are classic and very common in interviews. The basic versions are not hard, but there are many interesting variations, like counting sub‑islands, or counting islands with different shapes. This article will cover them all. **The core of island problems is using DFS/BFS to traverse a 2D grid.** In this article, we focus on how to use DFS to quickly solve island problems. The key idea for BFS is exactly the same; you just change DFS to BFS. How do we use DFS in a 2D grid? If you treat each cell in the grid as a node, then its up, down, left, and right neighbors are its adjacent nodes. In this way, the whole grid can be seen as a mesh‑like “graph”. Based on the ideas from [A Framework for Learning Data Structures and Algorithms](https://labuladong.online/en/algo/essential-technique/algorithm-summary/), we can rewrite the binary tree traversal template into a DFS template for a 2D grid: ```java // Binary tree traversal framework void traverse(TreeNode root) { traverse(root.left); traverse(root.right); } // Two-dimensional matrix traversal framework void dfs(int[][] grid, int i, int j, boolean[][] visited) { int m = grid.length, n = grid[0].length; if (i < 0 || j < 0 || i >= m || j >= n) { // out of index bounds return; } if (visited[i][j]) { // already visited (i, j) return; } // enter the current node (i, j) visited[i][j] = true; // enter adjacent nodes (quadtree) // up dfs(grid, i - 1, j, visited); // down dfs(grid, i + 1, j, visited); // left dfs(grid, i, j - 1, visited); // right dfs(grid, i, j + 1, visited); } ``` Because a 2D grid is essentially a “graph”, we need a `visited` boolean array during traversal to avoid going back to the same cell again. If you understand the code above, then all island problems become easy. Here is a common trick when working with 2D arrays. You will sometimes see a “direction array” to handle moving up, down, left, and right. This is very similar to the code in [Union-Find Algorithm Explained](https://labuladong.online/en/algo/data-structure/union-find/): ```java // Direction array, representing up, down, left, right respectively int[][] dirs = new int[][]{{-1,0}, {1,0}, {0,-1}, {0,1}}; void dfs(int[][] grid, int i, int j, boolean[][] visited) { int m = grid.length, n = grid[0].length; if (i < 0 || j < 0 || i >= m || j >= n) { // out of index bounds return; } if (visited[i][j]) { // already visited (i, j) return; } // enter node (i, j) visited[i][j] = true; // recursively visit the nodes above, below, left and right for (int[] d : dirs) { int next_i = i + d[0]; int next_j = j + d[1]; dfs(grid, next_i, next_j, visited); } // leave node (i, j) } ``` This style just uses a for loop to handle the four directions. You can choose whichever style you prefer. Next, we will solve problems using this framework together with the visual panel. ## Number of Islands This is LeetCode 200: [Number of Islands](https://leetcode.com/problems/number-of-islands/). This is the simplest and most classic island problem. The input is a 2D array `grid` that contains only `0` and `1`. `0` means water, `1` means land. Assume the whole grid is surrounded by water. Connected `1`s form an island. You need to write an algorithm to count how many islands are in `grid`. The function signature is: ```java int numIslands(char[][] grid); ``` For example, if the input `grid` is as below, there are 4 islands, so the algorithm should return 4: ![](../pictures/island/1.jpg) The idea is simple. The key is how to find and mark islands. Here we use DFS. Let’s look at the code: ```java class Solution { // main function, calculate the number of islands int numIslands(char[][] grid) { int res = 0; int m = grid.length, n = grid[0].length; // traverse the grid for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { if (grid[i][j] == '1') { // each time an island is found, increment the island count res++; // then use DFS to flood the island dfs(grid, i, j); } } } return res; } // starting from (i, j), turn all adjacent land into water void dfs(char[][] grid, int i, int j) { int m = grid.length, n = grid[0].length; if (i < 0 || j < 0 || i >= m || j >= n) { // out of index bounds return; } if (grid[i][j] == '0') { // already water return; } // turn (i, j) into water grid[i][j] = '0'; // flood the land above, below, left, and right dfs(grid, i + 1, j); dfs(grid, i, j + 1); dfs(grid, i - 1, j); dfs(grid, i, j - 1); } } ``` **Why do we use DFS to “flood” an island every time we find one? The main reason is to avoid using a `visited` array.** The `dfs` function stops when it reaches a `0`. So as long as we turn every visited cell into `0`, we will not go back to it again. ::: tip Tip This kind of DFS is also called the FloodFill algorithm. Now you can see why this name fits well. ::: That is all for the most basic island problem. Next, we look at some variations. ## Number of Closed Islands In the previous problem, we assume the whole grid is surrounded by water, so land on the border also counts as islands. LeetCode 1254: [Number of Closed Islands](https://leetcode.com/problems/number-of-closed-islands/) is different from the previous one in two ways: 1. `0` means land, `1` means water. 2. You need to count the number of “closed islands”. A “closed island” means a group of `0`s that are completely surrounded by `1`s in four directions. So **land that touches the border is not a closed island**. The function signature is: ```java int closedIsland(int[][] grid) ``` For example, for the following grid: ![](../pictures/island/2.png) The algorithm should return 2. Only the gray `0`s are land that is surrounded by water in all four directions, so they are “closed islands”. **How do we find “closed islands”? It is simple: if we remove all islands that touch the border, the remaining islands are exactly the closed islands.** With this idea, we can go straight to the code. Note that in this problem `0` is land and `1` is water: ```java class Solution { // main function: calculate the number of closed islands public int closedIsland(int[][] grid) { int m = grid.length, n = grid[0].length; for (int j = 0; j < n; j++) { // flood the island on the top edge dfs(grid, 0, j); // flood the island on the bottom edge dfs(grid, m - 1, j); } for (int i = 0; i < m; i++) { // flood the island on the left edge dfs(grid, i, 0); // flood the island on the right edge dfs(grid, i, n - 1); } // traverse the grid, the remaining islands are all closed islands int res = 0; 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; } // starting from (i, j), turn all adjacent land into water void dfs(int[][] grid, int i, int j) { int m = grid.length, n = grid[0].length; if (i < 0 || j < 0 || i >= m || j >= n) { return; } if (grid[i][j] == 1) { // it is already water return; } // turn (i, j) into water grid[i][j] = 1; // flood the land above, below, left and right dfs(grid, i + 1, j); dfs(grid, i, j + 1); dfs(grid, i - 1, j); dfs(grid, i, j - 1); } } ``` We just flood all land connected to the border first. Then, any island we count after that must be a closed island. ::: tip Tip Besides DFS/BFS, you can also use the Union Find algorithm for island problems. In the article [Union Find In Practice](https://labuladong.online/en/algo/data-structure/union-find/), we used Union Find to solve a similar problem. ::: With a small change, this solution also works for LeetCode 1020: [Number of Enclaves](https://leetcode.com/problems/number-of-enclaves/). That problem does not ask for the number of closed islands, but for the total area of all closed islands. The idea is the same: first flood all land connected to the border, then count the remaining land cells. Very simple. Note that in LeetCode 1020, `1` is land and `0` is water. Due to space, we skip the full code and move on to the next island problem. ## Max Area of Island This is LeetCode 695: [Max Area of Island](https://leetcode.com/problems/max-area-of-island/). Here `0` is water and `1` is land. This time you are not asked to count islands, but to find the maximum area of an island. The function signature is: ```java int maxAreaOfIsland(int[][] grid) ``` For example, for the grid below: ![](../pictures/island/3.jpg) The orange island has the largest area. The algorithm should return 6. **The main idea is the same as before. But while the `dfs` function is flooding an island, it should also record the area of this island.** We can let `dfs` return the number of land cells it floods each time. Let’s look at the code: ```java class Solution { public int maxAreaOfIsland(int[][] grid) { // record the maximum area of the island 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) { // flood the island and update the maximum island area res = Math.max(res, dfs(grid, i, j)); } } } return res; } // flood the land adjacent to (i, j) and return the flooded land area int dfs(int[][] grid, int i, int j) { int m = grid.length, n = grid[0].length; if (i < 0 || j < 0 || i >= m || j >= n) { // out of index boundary return 0; } if (grid[i][j] == 0) { // already sea water return 0; } // turn (i, j) into sea water grid[i][j] = 0; return dfs(grid, i + 1, j) + dfs(grid, i, j + 1) + dfs(grid, i - 1, j) + dfs(grid, i, j - 1) + 1; } } ``` The solution is almost the same as before, so we will not repeat more here. The next two island problems are more tricky; we will focus on them. ## Number of Sub-Islands If the previous problems are standard template problems, then LeetCode 1905 “[Count Sub Islands](https://leetcode.com/problems/count-sub-islands/)” needs a bit more thinking: **LeetCode 1905. Count Sub Islands** You are given two `m x n` binary matrices `grid1` and `grid2` containing only `0`'s (representing water) and `1`'s (representing land). An **island** is a group of `1`'s connected **4-directionally** (horizontal or vertical). Any cells outside of the grid are considered water cells. An island in `grid2` is considered a **sub-island **if there is an island in `grid1` that contains **all** the cells that make up **this** island in `grid2`. Return the ***number** of islands in *`grid2` *that are considered **sub-islands***. Example 1:** ![](https://assets.leetcode.com/uploads/2021/06/10/test1.png) ``` **Input:** grid1 = [[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]], grid2 = [[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]] **Output:** 3 **Explanation: **In the picture above, the grid on the left is grid1 and the grid on the right is grid2. The 1s colored red in grid2 are those considered to be part of a sub-island. There are three sub-islands. ``` Example 2:** ![](https://assets.leetcode.com/uploads/2021/06/03/testcasex2.png) ``` **Input:** grid1 = [[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]], grid2 = [[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]] **Output:** 2 **Explanation: **In the picture above, the grid on the left is grid1 and the grid on the right is grid2. The 1s colored red in grid2 are those considered to be part of a sub-island. There are two sub-islands. ``` **Constraints:** - `m == grid1.length == grid2.length` - `n == grid1[i].length == grid2[i].length` - `1 <= m, n <= 500` - `grid1[i][j]` and `grid2[i][j]` are either `0` or `1`. **The key is: how to quickly check whether an island is a sub-island**? We can use [Union Find](https://labuladong.online/en/algo/data-structure/union-find/) to solve it, but this article focuses on DFS, so we will not talk about Union Find here. When is an island `B` in `grid2` a sub-island of some island `A` in `grid1`? When every land cell in island `B` is also land in island `A` at the same position, then `B` is a sub-island of `A`. **In other words, if there is any land in island `B` whose corresponding cell in island `A` is water, then `B` is not a sub-island of `A`**. So we just need to traverse all islands in `grid2`, remove those islands that cannot be sub-islands, and the remaining ones are sub-islands. Following this idea, we can write the code directly: ```java class Solution { 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) { // this island is definitely not a sub-island, flood it dfs(grid2, i, j); } } } // now the remaining islands in grid2 are all sub-islands, calculate the number of islands int res = 0; 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; } // starting from (i, j), turn all adjacent land into water void dfs(int[][] grid, int i, int j) { int m = grid.length, n = grid[0].length; if (i < 0 || j < 0 || i >= m || j >= n) { return; } if (grid[i][j] == 0) { return; } grid[i][j] = 0; dfs(grid, i + 1, j); dfs(grid, i, j + 1); dfs(grid, i - 1, j); dfs(grid, i, j - 1); } } ``` This problem is similar to counting the number of “closed islands”. In that problem, we remove islands that touch the border. In this problem, we remove islands that cannot be sub-islands. ## Number of Distinct Islands This is the last island problem in this article. As the final problem, it is of course the most interesting one. LeetCode 694 “[Number of Distinct Islands](https://leetcode.com/problems/number-of-distinct-islands/)” still gives you a 2D grid. `0` means water, `1` means land. This time you need to count the number of **distinct** islands. The function signature is: ```java int numDistinctIslands(int[][] grid) ``` For example, suppose the input grid is: ![](../pictures/island/5.jpg) There are four islands, but the island in the bottom-left and the island in the top-right have the same shape. So there are only three distinct islands, and the algorithm should return 3. We clearly need to convert each island in the grid to some form, like a string, then use a data structure like a HashSet to remove duplicates and get the number of distinct islands. To convert an island to a string is basically serialization. Serialization is basically a traversal. In a previous article, “[Serialize and Deserialize Binary Trees](https://labuladong.online/en/algo/data-structure/serialize-and-deserialize-binary-tree/)”, we talked about converting between trees and strings. It is similar here. **First, for islands with the same shape, if we start DFS from the same relative starting point, the traversal order of the `dfs` function will be the same**. Because the traversal order is hard-coded in your recursive function and does not change dynamically: ```java void dfs(int[][] grid, int i, int j) { // recursion order: // up dfs(grid, i - 1, j); // down dfs(grid, i + 1, j); // left dfs(grid, i, j - 1); // right dfs(grid, i, j + 1); } ``` So in some sense, the traversal order can describe the shape of an island. For example, look at these two islands: ![](../pictures/island/6.png) Assume their DFS traversal order is: ``` down, right, up, backtrack up, backtrack right, backtrack down ``` If we use `1, 2, 3, 4` for up, down, left, right, and `-1, -2, -3, -4` for backtracking up, down, left, right, then we can write the traversal as: ``` 2, 4, 1, -1, -4, -2 ``` **This sequence is like the serialized result of the island. As long as we generate this sequence for each island during DFS and compare them, we can count how many distinct islands there are**. ::: info Do we have to record “backtrack” moves? Some careful readers may ask: why must we record backtrack moves to uniquely represent the traversal order? It seems okay to skip them? No, in fact we must record them. For example, “down, right, backtrack right, backtrack down” and “down, backtrack down, right, backtrack right” are clearly two different traversal orders. But if we do not record backtracks, both become “down, right”. They look the same, which is wrong. ::: So we need to slightly change the `dfs` function and add parameters to record the traversal order: ```java void dfs(int[][] grid, int i, int j, StringBuilder sb, int dir) { int m = grid.length, n = grid[0].length; if (i < 0 || j < 0 || i >= m || j >= n || grid[i][j] == 0) { return; } // preorder traversal position: entering (i, j) grid[i][j] = 0; sb.append(dir).append(','); // up dfs(grid, i - 1, j, sb, 1); // down dfs(grid, i + 1, j, sb, 2); // left dfs(grid, i, j - 1, sb, 3); // right dfs(grid, i, j + 1, sb, 4); // postorder traversal position: leaving (i, j) sb.append(-dir).append(','); } ``` ::: note Note Look carefully at this code. It makes a choice before recursion and undoes the choice after recursion. Does it look like the [backtracking framework](https://labuladong.online/en/algo/essential-technique/backtrack-framework/)? It actually is a backtracking algorithm, because it focuses on the “branches” (the traversal path of the island), not the “nodes” (each cell of the island). You can rewrite this function into the standard backtracking form. ::: `dir` records the direction. After `dfs` finishes, `sb` stores the whole traversal sequence. With this `dfs` function, we can now write the final solution: ```java class Solution { public int numDistinctIslands(int[][] grid) { int m = grid.length, n = grid[0].length; // record the serialized results of all islands HashSet islands = new HashSet<>(); for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { if (grid[i][j] == 1) { // flood this island and store the serialized result of the island StringBuilder sb = new StringBuilder(); // the initial direction can be written arbitrarily, it does not affect correctness dfs(grid, i, j, sb, 666); islands.add(sb.toString()); /** ![](../pictures/island/6.png) */ } } } // the number of distinct islands return islands.size(); } private void dfs(int[][] grid, int i, int j, StringBuilder sb, int dir) { // see above } } ``` That solves this problem. As for why the initial `dir` parameter in the first `dfs` call can be anything, it is because this `dfs` function is actually a backtracking algorithm. It focuses on the “branches” instead of the “nodes”. The difference is explained in detail in “[Graph Basics](https://labuladong.online/en/algo/data-structure-basic/graph-basic/)”, so we will not repeat it here. These are all the ideas for the island problems in this series. Many people may be able to solve the earlier problems, but the last two are more clever. I hope this article is helpful to you. ================================================ FILE: interview/lru-cache.md ================================================ ::: info Prerequisites Before reading this article, you should first learn: - [Linked List Basics](https://labuladong.online/en/algo/data-structure-basic/linkedlist-basic/) - [Hash Table Basics](https://labuladong.online/en/algo/data-structure-basic/hashmap-basic/) ::: The LRU algorithm is a cache eviction strategy. Its principle is not difficult, but writing a bug-free algorithm in an interview requires skill, involving multiple layers of abstraction and breakdown of data structures. This article will guide you to write elegant code. The key data structure used in the LRU algorithm is the hash-linked list, `LinkedHashMap`. The [Hands-on Guide to Implementing Hash Linked List](https://labuladong.online/en/algo/data-structure-basic/hashtable-with-linked-list/) in the data structure basics section explains the principles and code implementation of hash-linked lists. If you haven't read it, it's okay; this article will explain the core principles of hash-linked lists again to facilitate the implementation of the LRU algorithm. Computer cache capacity is limited. If the cache is full, some content needs to be deleted to make space for new content. But the question is, what content should be deleted? We want to remove the cache that is not useful and keep the useful data in the cache for future use. So, what kind of data do we consider "useful"? The LRU cache eviction algorithm is a common strategy. LRU stands for Least Recently Used, meaning that we consider recently used data to be "useful" and data that hasn't been used for a long time to be useless. When the memory is full, we delete the data that hasn't been used for the longest time. For example, Android phones allow apps to run in the background. If I open "Settings," "Phone Manager," and "Calendar" one after another, the order in the background is like this: ![](../pictures/lru/1.jpg) But if I then access the "Settings" interface, "Settings" will be moved to the front, like this: ![](../pictures/lru/2.jpg) Assume my phone only allows three apps to run simultaneously, and it's already full. If I open a new app "Clock," I must close one app to free up space for "Clock." Which one should be closed? According to the LRU strategy, the bottom "Phone Manager" is closed because it is the least recently used, and the new app is placed at the top: ![](../pictures/lru/3.jpg) Now you should understand the LRU (Least Recently Used) strategy. Of course, there are other cache eviction strategies, such as evicting based on access frequency (LFU strategy) rather than access order. Each has its application scenarios. This article explains the LRU algorithm strategy, and I will explain the LFU algorithm in [LFU Algorithm Details](https://labuladong.online/en/algo/frequency-interview/lfu/). ## 1. LRU Algorithm Description LeetCode Problem 146 "[LRU Cache](https://leetcode.com/problems/lru-cache/)" requires you to design a data structure: First, it should accept a `capacity` parameter as the maximum cache capacity, then implement two APIs: a `put(key, val)` method to store key-value pairs and a `get(key)` method to retrieve the `val` corresponding to `key`. If `key` does not exist, it returns -1. Note that the `get` and `put` methods must have a time complexity of $O(1)$. Let's look at a specific example to see how the LRU algorithm works: ```java // the cache capacity is 2 LRUCache cache = new LRUCache(2); // you can understand the cache as a queue // assume the left side is the head of the queue and the right side is the tail // the most recently used is at the head of the queue, and the least recently used is at the tail // parentheses represent the key-value pair (key, val) cache.put(1, 1); // cache = [(1, 1)] cache.put(2, 2); // cache = [(2, 2), (1, 1)] // return 1 cache.get(1); // cache = [(1, 1), (2, 2)] // explanation: because key 1 was recently accessed, it is moved to the head of the queue // return the value corresponding to key 1, which is 1 cache.put(3, 3); // cache = [(3, 3), (1, 1)] // explanation: the cache is full, need to delete content to make space // prefer to delete the least recently used data, which is the data at the tail // then insert the new data at the head of the queue // return -1 (not found) cache.get(2); // cache = [(3, 3), (1, 1)] // explanation: there is no data with key 2 in the cache cache.put(1, 4); // cache = [(1, 4), (3, 3)] // explanation: key 1 already exists, overwrite the original value 1 with 4 // don't forget to also move the key-value pair to the head of the queue ``` ## II. LRU Algorithm Design Analyzing the above operations, to ensure that the time complexity of the `put` and `get` methods is O(1), we can summarize the necessary conditions for the `cache` data structure: 1. Clearly, the elements in the `cache` must have a sequence to distinguish between recently used and long-unused data. When the capacity is full, the least recently used element should be removed to make space. 2. We need to quickly find whether a certain `key` exists in the `cache` and obtain the corresponding `val`. 3. Each time a `key` in the `cache` is accessed, this element needs to be made the most recently used, which means the `cache` must support quick insertion and deletion of elements at any position. So, what data structure meets these conditions? A hash table allows fast lookup, but the data is unordered. A linked list is ordered and allows fast insertion and deletion, but lookup is slow. Therefore, combining them forms a new data structure: a hash-linked list, `LinkedHashMap`. The core data structure of the LRU cache algorithm is this hash-linked list, a combination of a doubly linked list and a hash table. It looks like this: ![](../pictures/lru/4.jpg) Using this structure, let's analyze the three conditions one by one: 1. If we always add elements to the end of the list, then obviously, the closer to the end an element is, the more recently it's been used. The closer to the head an element is, the less recently it's been used. 2. For a specific `key`, we can quickly locate the node in the linked list via the hash table and obtain the corresponding `val`. 3. A linked list naturally supports quick insertion and deletion at any position by adjusting pointers. However, a traditional linked list cannot quickly access an element at a specific index. Here, with the help of a hash table, we can quickly map a `key` to any linked list node for insertion and deletion. **Readers might ask why a doubly linked list is needed instead of a singly linked list? Also, since the `key` is already stored in the hash table, why does the linked list need to store both `key` and `val`? Wouldn't storing just `val` suffice?** These questions can only be answered through implementation. The rationale behind this design will become clear once we personally implement the LRU algorithm, so let's start coding! ## Three: Code Implementation Many programming languages have built-in linked hashmaps or library functions with LRU-like functionality. But to help you understand the nuts and bolts of the algorithm, let's build an LRU cache from scratch first, then implement it again using Java's built-in `LinkedHashMap`. First, let's define the node class for our [doubly linked list](https://labuladong.online/en/algo/data-structure-basic/linkedlist-basic/). For simplicity, both `key` and `val` are int types: ```java class Node { public int key, val; public Node next, prev; public Node(int k, int v) { this.key = k; this.val = v; } } ``` Next, we'll use our `Node` type to build a doubly linked list with the essential APIs that our LRU algorithm needs: ```java class DoubleList { // virtual head and tail nodes private Node head, tail; // number of elements in the linked list private int size; public DoubleList() { // initialize the data of the doubly linked list head = new Node(0, 0); tail = new Node(0, 0); head.next = tail; tail.prev = head; size = 0; } // add node x to the end of the list, time O(1) public void addLast(Node x) { x.prev = tail.prev; x.next = tail; tail.prev.next = x; tail.prev = x; size++; } // remove node x from the list (x is guaranteed to exist) // since it's a doubly linked list and the target Node is given, time O(1) public void remove(Node x) { x.prev.next = x.next; x.next.prev = x.prev; size--; } // remove the first node from the list and return it, time O(1) public Node removeFirst() { if (head.next == tail) return null; Node first = head.next; remove(first); return first; } // return the length of the list, time O(1) public int size() { return size; } } ``` If you're not comfortable with linked list operations, check out [Hands-On Guide to Implementing a Doubly Linked List](https://labuladong.online/en/algo/data-structure-basic/linkedlist-basic/). Now we can answer the earlier question: "Why do we need a doubly linked list?" It's because we need to perform deletions. Deleting a node requires not only a pointer to the node itself, but also access to its predecessor's pointer. Only a doubly linked list lets you find the predecessor directly, keeping the operation at O(1) time complexity. ::: important Important Note that our doubly linked list API only inserts at the tail. This means data near the tail was recently used, while data near the head is the least recently used. ::: With the doubly linked list in place, all we need to do is combine it with a hash map in our LRU algorithm. Let's start with the code skeleton: ```java class LRUCache { // key -> Node(key, val) private HashMap map; // Node(k1, v1) <-> Node(k2, v2)... private DoubleList cache; // maximum capacity private int cap; public LRUCache(int capacity) { this.cap = capacity; map = new HashMap<>(); cache = new DoubleList(); } } ``` Don't rush into implementing the `get` and `put` methods just yet. Since we're maintaining both a doubly linked list `cache` and a hash map `map` simultaneously, it's easy to miss an operation—for example, when deleting a `key`, you might remove the corresponding `Node` from `cache` but forget to also remove the `key` from `map`. **The best way to avoid this kind of bug is to provide an abstraction layer of APIs on top of these two data structures.** The idea is to keep the main `get` and `put` methods from directly manipulating the details of `map` and `cache`. Let's implement a few helper functions first: ```java class LRUCache { // To save space, the previous code part is omitted... // promote a key to the most recently used private void makeRecently(int key) { Node x = map.get(key); // first remove this node from the linked list cache.remove(x); // reinsert it at the end of the queue cache.addLast(x); } // add the most recently used element private void addRecently(int key, int val) { Node x = new Node(key, val); // the tail of the linked list is the most recently used element cache.addLast(x); // don't forget to add the key mapping in the map map.put(key, x); } // delete a certain key private void deleteKey(int key) { Node x = map.get(key); // remove from the linked list cache.remove(x); // delete from the map map.remove(key); } // remove the least recently used element private void removeLeastRecently() { // the first element in the linked list is the least recently used Node deletedNode = cache.removeFirst(); // also, don't forget to remove its key from the map int deletedKey = deletedNode.key; map.remove(deletedKey); } } ``` This also answers the earlier question: "Why store both key and val in the linked list node instead of just val?" Look at the `removeLeastRecently` function—we need to get `deletedKey` from `deletedNode`. Here's the thing: when the cache is full, we don't just delete the last `Node`—we also need to remove the corresponding `key` from `map`. And the only way to get that `key` is from the `Node` itself. If the `Node` only stored `val`, we'd have no way to know which `key` to delete from `map`, and that would cause bugs. These helper methods are simple wrappers that let us avoid directly touching the `cache` linked list and `map` hash table. Now let's implement the `get` method for the LRU algorithm: ```java class LRUCache { // To save space, the previous code is omitted... public int get(int key) { if (!map.containsKey(key)) { return -1; } // promote the data to the most recently used makeRecently(key); return map.get(key).val; } } ``` The `put` method is a bit more involved. Let's draw a diagram to clarify its logic: ![](../pictures/lru/put.jpg) With that clear picture, we can easily write the code for `put`: ```java class LRUCache { // To save space, the previous given code part is omitted... public void put(int key, int val) { if (map.containsKey(key)) { // delete the old data deleteKey(key); // the newly inserted data is the most recently used data addRecently(key, val); return; } if (cap == cache.size()) { // remove the least recently used element removeLeastRecently(); } // add as the most recently used element addRecently(key, val); } } ``` At this point, you should have a solid grasp of both the theory and implementation of the LRU algorithm. Here's the complete implementation: ```java // doubly linked list node class Node { public int key, val; public Node next, prev; public Node(int k, int v) { this.key = k; this.val = v; } } // doubly linked list class DoubleList { // virtual head and tail nodes private Node head, tail; // number of elements in the linked list private int size; public DoubleList() { // initialize the data of the doubly linked list head = new Node(0, 0); tail = new Node(0, 0); head.next = tail; tail.prev = head; size = 0; } // add node x to the end of the list, time O(1) public void addLast(Node x) { x.prev = tail.prev; x.next = tail; tail.prev.next = x; tail.prev = x; size++; } // remove node x from the list (x is guaranteed to exist) // since it's a doubly linked list and the target Node is given, time O(1) public void remove(Node x) { x.prev.next = x.next; x.next.prev = x.prev; size--; } // remove the first node from the list and return it, time O(1) public Node removeFirst() { if (head.next == tail) return null; Node first = head.next; remove(first); return first; } // return the length of the list, time O(1) public int size() { return size; } } class LRUCache { // key -> Node(key, val) private HashMap map; // Node(k1, v1) <-> Node(k2, v2)... private DoubleList cache; // maximum capacity private int cap; public LRUCache(int capacity) { this.cap = capacity; map = new HashMap<>(); cache = new DoubleList(); } public int get(int key) { if (!map.containsKey(key)) { return -1; } // promote this data to the most recently used makeRecently(key); return map.get(key).val; } public void put(int key, int val) { if (map.containsKey(key)) { // remove the old data deleteKey(key); // the newly inserted data is the most recently used addRecently(key, val); return; } if (cap == cache.size()) { // remove the least recently used element removeLeastRecently(); } // add as the most recently used element addRecently(key, val); } private void makeRecently(int key) { Node x = map.get(key); // first remove this node from the list cache.remove(x); // reinsert it at the end of the list cache.addLast(x); } private void addRecently(int key, int val) { Node x = new Node(key, val); // the end of the list is the most recently used element cache.addLast(x); // don't forget to add the key mapping in the map map.put(key, x); } private void deleteKey(int key) { Node x = map.get(key); // remove from the list cache.remove(x); // remove from the map map.remove(key); } private void removeLeastRecently() { // the first element at the head of the list is the least recently used Node deletedNode = cache.removeFirst(); // also don't forget to remove its key from the map int deletedKey = deletedNode.key; map.remove(deletedKey); } } ``` You can also use Java's built-in `LinkedHashMap` or the `MyLinkedHashMap` from [Hands-On Guide to Implementing a Linked HashMap](https://labuladong.online/en/algo/data-structure-basic/hashtable-with-linked-list/) to implement LRU. The logic is exactly the same: ```java class LRUCache { int cap; LinkedHashMap cache = new LinkedHashMap<>(); public LRUCache(int capacity) { this.cap = capacity; } public int get(int key) { if (!cache.containsKey(key)) { return -1; } // mark key as recently used makeRecently(key); return cache.get(key); } public void put(int key, int val) { if (cache.containsKey(key)) { // update the value for key cache.put(key, val); // mark key as recently used makeRecently(key); return; } if (cache.size() >= this.cap) { // the head of the list is the least recently used key int oldestKey = cache.keySet().iterator().next(); cache.remove(oldestKey); } // add the new key at the tail of the list cache.put(key, val); } private void makeRecently(int key) { int val = cache.get(key); // remove key and re-insert at the tail cache.remove(key); cache.put(key, val); } } ``` And there you have it—nothing mysterious about the LRU algorithm anymore. For more data structure design problems, check out [Classic Data Structure Design Exercises](https://labuladong.online/en/algo/problem-set/ds-design/). ================================================ FILE: interview/meeting-rooms.md ================================================ In a previous interview, I was asked a very classic and practical algorithm problem: the meeting room scheduling problem. Similar problems on LeetCode are behind a paywall — you need a premium subscription to access them. But for such a classic algorithm problem, it's still important to understand the approach. Let me first describe the problem. LeetCode Problem 253, "[Meeting Rooms II](https://leetcode.cn/problems/meeting-rooms-ii/)": Given an array of intervals `[begin, end]` representing the start and end times of meetings, calculate the minimum number of meeting rooms required. The function signature is as follows: ```java // Return the number of meeting rooms to be reserved int minMeetingRooms(int[][] meetings); ``` For example, given `meetings = [[0,30],[5,10],[15,20]]`, the algorithm should return 2, because the last two meetings conflict with the first one, so you need at least two meeting rooms to hold all meetings. If meetings overlap in time, you need extra rooms. Finding the minimum number of rooms is essentially finding the maximum number of meetings happening simultaneously at any given moment. In other words, **if you think of each meeting's time range as a line segment, the problem is asking you to find the maximum number of overlapping intervals**. That's it. We've previously learned interval-related algorithms. If you recall the [difference array technique](https://labuladong.online/en/algo/data-structure/diff-array/), you should immediately think of using it to solve this problem. This problem is essentially saying: given an array initially filled with 0s and several intervals, increment every element within each interval by 1, then find the maximum value in the entire array. This is a classic use case for the difference array, right? You can directly apply the `Difference` class from the previous article to solve it. However, the difference array technique has one issue: you must construct that initial all-zero array. Since we use array indices to represent time, the array length depends on the maximum time value. For example, with `meetings = [[0,30],[5,10],[15,20]]`, you'd need an array of length 30. But if the input is `meetings = [[0,30],[5,10],[10^8,10^9]]`, you'd need an array of length 10^9, which is clearly problematic. That said, this problem constrains time values to at most 10^6, which isn't too large, so the difference array approach should pass. But in this article, I'll teach you another technique for handling intervals that doesn't require constructing such a large array and still solves the problem elegantly. ## Extending the Problem We've written many articles about interval scheduling. Let me take this opportunity to organize the different scenarios for you: **Scenario 1**: You have only one meeting room and several meetings. How do you schedule as many meetings as possible into this room? Sort the meetings (intervals) by end time (right endpoint), then process them. See [Greedy Algorithm for Time Management](https://labuladong.online/en/algo/frequency-interview/interval-scheduling/) for details. **Scenario 2**: You're given several short video clips and one long video clip. Pick as few short clips as possible to cover the entire long clip. Sort the video clips (intervals) by start time (left endpoint), then process them. See [A Greedy Algorithm from Video Editing](https://labuladong.online/en/algo/frequency-interview/cut-video/) for details. **Scenario 3**: Given several intervals where some shorter ones are completely covered by others, delete the covered intervals. Sort the intervals by left endpoint, then you can identify and remove the fully covered ones. See [Remove Covered Intervals](https://labuladong.online/en/algo/practice-in-action/interval-problem-summary/) for details. **Scenario 4**: Given several intervals, merge all overlapping intervals. Sort the intervals by left endpoint to easily find overlapping ones. See [Merge Overlapping Intervals](https://labuladong.online/en/algo/practice-in-action/interval-problem-summary/) for details. **Scenario 5**: Two departments have booked the same meeting room for various time slots. Find the conflicting time slots. This gives you two lists of intervals and asks you to find their intersection. Sort the intervals by left endpoint. See [Interval Intersection Problem](https://labuladong.online/en/algo/practice-in-action/interval-problem-summary/) for details. **Scenario 6**: You have only one meeting room and several meetings. How do you schedule meetings to minimize the room's idle time? This one requires some creative thinking. It's essentially a variation of the 0-1 knapsack problem: Think of the meeting room as a knapsack and each meeting as an item whose value is the meeting duration. How do you select items (meetings) to maximize the knapsack's value (total usage time)? Of course, the constraint here isn't a maximum weight but that items (meetings) cannot conflict with each other. Sort the meetings by end time, then apply the approach from [0-1 Knapsack Problem Explained](https://labuladong.online/en/algo/dynamic-programming/knapsack1/) along with a TreeMap to solve it. LeetCode Problem 1235, "[Maximum Profit in Job Scheduling](https://leetcode.cn/problems/maximum-profit-in-job-scheduling/)", is a similar problem. I've provided a detailed solution in my plugin's hints. You can install my [Chrome plugin](https://labuladong.online/en/algo/intro/chrome/) to check it out — I won't go into detail here. **Scenario 7** is what this article focuses on: given several meetings, minimize the number of meeting rooms needed. Alright, with all these examples laid out, let's see how to solve today's problem. ## Problem Analysis Let me restate the essence of the problem: **Given several time intervals, find the maximum number of intervals that overlap at any single moment.** The key insight is: for any given moment, can you tell how many meetings are happening? If you can, then just traverse all moments, find the maximum, and that's the number of meeting rooms you need. Is there a data structure or algorithm that, given several intervals, tells you how many intervals overlap at each position? Long-time readers should immediately think of a technique we've discussed before: the [difference array technique](https://labuladong.online/en/algo/data-structure/diff-array/). Imagine the timeline as an array initialized to 0. Each time interval `[i, j]` corresponds to a subarray. Since a meeting occupies that interval, increment every element in that subarray by one. Then you know exactly how many meetings are happening at each moment, right? Traverse the entire array to find the maximum, and you know the minimum number of meeting rooms needed. For example, if `meetings = [[0,30],[5,10],[15,20]]`, increment the array at index ranges `[0,30]`, `[5,10]`, and `[15,20]` by one each, then traverse the array and find the maximum value. Remember, the difference array technique can increment or decrement an entire interval in O(1) time, making it suitable for this problem. However, this solution isn't the most efficient, so I won't implement the difference array approach here. Refer to the [difference array technique](https://labuladong.online/en/algo/data-structure/diff-array/) for the principles — interested readers can try implementing it on their own. **Building on the difference array idea, we can derive a more efficient and elegant solution.** First, project the meeting time intervals onto a timeline: ![](../pictures/arrange-room/1.jpeg) Red dots represent each meeting's start time, and green dots represent each meeting's end time. Now imagine a line with a counter sweeping from left to right along the timeline. Each time it hits a red dot, increment `count` by one. Each time it hits a green dot, decrement `count` by one: ![](../pictures/arrange-room/2.jpeg) **This way, the number of meetings happening simultaneously at any moment equals the counter value `count`, and the maximum value of `count` is the number of meeting rooms needed.** Readers familiar with the difference array technique will immediately recognize that this sweep line is essentially the traversal process of a difference array. That's why we say this solution is derived from the difference array technique. ## Code Implementation So how do you implement this sweep process in code? First, projecting the intervals means sorting the start points and end points separately: ![](../pictures/arrange-room/3.jpeg) ```java int minMeetingRooms(int[][] meetings) { int n = meetings.length; int[] begin = new int[n]; int[] end = new int[n]; // extract the start and end points separately for(int i = 0; i < n; i++) { begin[i] = meetings[i][0]; end[i] = meetings[i][1]; } // after sorting, they are the red points in the graph Arrays.sort(begin); // after sorting, they are the green points in the graph Arrays.sort(end); // ... } ``` Then it's straightforward. The sweep line moves from left to right — increment the counter at red dots, decrement at green dots. The maximum value of `count` is the answer: ```java class Solution { public int minMeetingRooms(int[][] meetings) { int n = meetings.length; int[] begin = new int[n]; int[] end = new int[n]; for(int i = 0; i < n; i++) { begin[i] = meetings[i][0]; end[i] = meetings[i][1]; } Arrays.sort(begin); Arrays.sort(end); // counter during the scanning process int count = 0; // two-pointer technique int res = 0, i = 0, j = 0; while (i < n && j < n) { /** ![](../pictures/arrange-room/2.jpeg) */ if (begin[i] < end[j]) { // scan to a red point count++; i++; } else { // scan to a green point count--; j++; } // record the maximum value during the scanning process res = Math.max(res, count); } return res; } } ``` This uses the [two-pointer technique](https://labuladong.online/en/algo/essential-technique/array-two-pointers-summary/), simulating the sweep line's movement based on the relative positions of `i` and `j`. And that's it for this problem. Of course, this problem has variations — for example, given several meetings, determine whether `k` meeting rooms are sufficient. You can easily solve that by adapting the solution from this article. ================================================ FILE: interview/missing-duplicate-element.md ================================================ Today, let's discuss a seemingly simple yet ingenious problem: finding the missing and duplicate elements. A similar problem was covered in a previous article [Common Bit Manipulations](https://labuladong.online/en/algo/frequency-interview/bitwise-operation/), but the techniques used in this case are different. This is LeetCode problem 645 "[Set Mismatch](https://leetcode.com/problems/set-mismatch/)", and I'll describe the problem: Given an array `nums` of length `N`, originally containing the `N` elements from `[1..N]` in no particular order. However, some errors have occurred: one element in `nums` appears twice, leading to another element missing. Write an algorithm to find the values of the duplicate and missing elements in `nums`. ```java // Return two numbers, which are {dup, missing} int[] findErrorNums(int[] nums); ``` For example, given the input: `nums = [1,2,2,4]`, the algorithm should return `[2,3]`. This problem can be easily solved by first traversing the array and using a hash table to record the frequency of each number, then traversing `[1..N]` to find which element appears twice and which is missing. However, this conventional solution requires a hash table, resulting in O(N) space complexity. Given the problem's conditions, one might think of using a more elegant method to solve it. Traversing the array in O(N) time complexity is unavoidable, but we can try to reduce the space complexity. Is it possible to find the duplicate and missing elements with an O(1) space complexity? ## Thought Process Analysis The characteristic of this problem is that each element corresponds to a certain index in the array. Let's modify the problem temporarily: **Let the elements in `nums` be `[0..N-1]`, so each element corresponds to an array index, which makes it easier to understand**. If there are no duplicate or missing elements in `nums`, each element corresponds to a unique index value, right? The current issue is that one element is duplicated, causing another element to be missing. What phenomenon does this create? **It results in two elements corresponding to the same index, and one index having no corresponding element**. So, if I can find the index with duplicate correspondence, wouldn't that identify the duplicate element? And finding the index with no corresponding element identifies the missing element, right? How can we determine, without extra space, how many elements correspond to a certain index? This is the clever part of the problem: **By turning the element corresponding to each index into a negative number, we indicate that this index has been corresponded to once**, as shown in the GIF below: ![](../pictures/dupmissing/1.gif) If a duplicate element `4` appears, the intuitive result is that the element corresponding to index `4` is already negative: ![](../pictures/dupmissing/2.jpg) For the missing element `3`, the intuitive result is that the element corresponding to index `3` is positive: ![](../pictures/dupmissing/3.jpg) This phenomenon can be translated into code: ```java int[] findErrorNums(int[] nums) { int n = nums.length; int dup = -1; for (int i = 0; i < n; i++) { int index = Math.abs(nums[i]); // if nums[index] is less than 0, it indicates a duplicate visit if (nums[index] < 0) dup = Math.abs(nums[i]); else nums[index] *= -1; } int missing = -1; for (int i = 0; i < n; i++) // if nums[i] is greater than 0, it indicates it was not visited if (nums[i] > 0) missing = i; return new int[]{dup, missing}; } ``` This issue is essentially resolved. Don't forget that for convenience, we assumed elements were `[0..N-1]`, but the problem requires `[1..N]`. So, we can obtain the original answer by simply modifying two parts: ```java class Solution { public int[] findErrorNums(int[] nums) { int n = nums.length; int dup = -1; for (int i = 0; i < n; i++) { // the current elements start from 1 int index = Math.abs(nums[i]) - 1; if (nums[index] < 0) dup = Math.abs(nums[i]); else nums[index] *= -1; } int missing = -1; for (int i = 0; i < n; i++) if (nums[i] > 0) // convert index to element missing = i + 1; return new int[]{dup, missing}; } } ``` Actually, starting elements from 1 is logical and necessary, because if elements start from 0, the opposite of 0 is still itself. So, if the number 0 is repeated or missing, the algorithm cannot determine if 0 has been visited. Our previous assumption was just to simplify the problem and make it more understandable. ## Final Summary For such array problems, **the key point is that elements and indices appear in pairs, and the common methods are sorting, XOR, and mapping**. The mapping approach is what we analyzed earlier, mapping each index to an element, using positive and negative signs to record whether an element has been mapped. The sorting method is also easy to understand. For this problem, if the elements are sorted in ascending order, and an index does not match the corresponding element, the repeated or missing elements can be found. XOR operation is commonly used as well, due to its properties `a ^ a = 0, a ^ 0 = a`. By XORing both indices and elements, paired indices and elements can be eliminated, leaving the repeated or missing elements. You can refer to the previous article [Common Bit Operations](https://labuladong.online/en/algo/frequency-interview/bitwise-operation/) for more on this method. ================================================ FILE: interview/palindrome-linked-list.md ================================================ ::: info Prerequisites Before reading this article, you need to learn: - [Linked List Basics](https://labuladong.online/en/algo/data-structure-basic/linkedlist-basic/) - [Two-Pointer Skills for Linked Lists](https://labuladong.online/en/algo/essential-technique/linked-list-skills-summary/) - [Two-Pointer Skills for Arrays (Summary)](https://labuladong.online/en/algo/essential-technique/array-two-pointers-summary/) ::: The previous article [Two-Pointer Skills for Arrays (Summary)](https://labuladong.online/en/algo/essential-technique/array-two-pointers-summary/) talked about problems on palindromes. Let’s quickly review. The key idea to **find** a palindrome string is to expand from the center to both sides: ```java // Find the longest palindrome in s with s[left] and s[right] as the center String palindrome(String s, int left, int right) { // Prevent index out of bounds while (left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)) { // Two pointers, expand to both sides left--; right++; } // Return the longest palindrome with s[left] and s[right] as the center return s.substring(left + 1, right); } ``` A palindrome can have odd or even length. For odd length, there is one center. For even length, there are two centers. So the function above needs two inputs: `l` and `r`. But to **check** whether a string is a palindrome is much easier. You don’t need to care about odd/even length. You just use the [two-pointer technique](https://labuladong.online/en/algo/essential-technique/array-two-pointers-summary/) and move from both ends to the middle: ```java boolean isPalindrome(String s) { // two pointers, one on the left and one on the right, move towards each other int left = 0, right = s.length() - 1; while (left < right) { if (s.charAt(left) != s.charAt(right)) { return false; } left++; right--; } return true; } ``` This code is easy to understand. **A palindrome is symmetric, so reading it forward and backward is the same. This is the key for palindrome problems.** Now let’s extend this simplest case to solve: how to check whether a **singly linked list** is a palindrome. ## 1. Check a palindrome singly linked list Look at LeetCode 234: [Palindrome Linked List](https://leetcode.com/problems/palindrome-linked-list/): **LeetCode 234. Palindrome Linked List** Given the `head` of a singly linked list, return `true`* if it is a **palindrome** or *`false`* otherwise*. Example 1:** ![](https://assets.leetcode.com/uploads/2021/03/03/pal1linked-list.jpg) ``` **Input:** head = [1,2,2,1] **Output:** true ``` Example 2:** ![](https://assets.leetcode.com/uploads/2021/03/03/pal2linked-list.jpg) ``` **Input:** head = [1,2] **Output:** false ``` **Constraints:** - The number of nodes in the list is in the range `[1, 10^(5)]`. - `0 <= Node.val <= 9` **Follow up:** Could you do it in `O(n)` time and `O(1)` space? The function signature is: ```java boolean isPalindrome(ListNode head); ``` The key problem is: a singly linked list cannot be traversed backward, so you cannot directly use the two-pointer method. The simplest way is: reverse the original list into a new list, then compare the two lists. For how to reverse a list, see [Reverse Part of a Linked List Using Recursion](https://labuladong.online/en/algo/data-structure/reverse-linked-list-recursion/). I said in [Framework Thinking for Learning Data Structures](https://labuladong.online/en/algo/essential-technique/algorithm-summary/): a linked list has a recursive structure, and a tree is just derived from it. So **a linked list also has “preorder” and “postorder” traversal. With the idea of postorder traversal in a binary tree, you can traverse a list in reverse order without explicitly reversing it**: ```java // Binary tree traversal framework void traverse(TreeNode root) { // Pre-order traversal code traverse(root.left); // In-order traversal code traverse(root.right); // Post-order traversal code } // Recursively traverse a single linked list void traverse(ListNode head) { // Pre-order traversal code traverse(head.next); // Post-order traversal code } ``` What does this mean? If you want to print `val` in normal order, write code in the preorder position. If you want reverse order, write code in the postorder position: ```java // Print the element values in a singly linked list in reverse order void traverse(ListNode head) { if (head == null) return; traverse(head.next); // Post-order traversal code print(head.val); } ``` Now we can modify it a bit, and mimic the two-pointer method to check palindrome: ```java class Solution { // pointer moving from left to right ListNode left; // pointer moving from right to left ListNode right; // record if the linked list is a palindrome boolean res = true; boolean isPalindrome(ListNode head) { left = head; traverse(head); return res; } void traverse(ListNode right) { if (right == null) { return; } // use recursion to reach the end of the linked list traverse(right.next); // in post-order traversal, the right pointer points to the end of the linked list // so we can compare it with the left pointer to check if it's a palindrome linked list if (left.val != right.val) { res = false; } left = left.next; } } ``` What is the core idea here? **You are basically pushing nodes into a stack and popping them out, so the order becomes reversed**. We just use the recursion call stack. You can open the panel below. Click the line if (right === null) many times. You will see pointer `right` reach the tail using the recursion stack. Then click the line left = left.next; many times. You will see `left` move forward and `right` move backward. They move toward each other and finish the palindrome check. Of course, whether you build a reversed list or use postorder traversal, the time and space complexity are both O(N). Now let’s think: can we solve it without extra space? ## 2. Improve space complexity A better idea is: **1) Use fast and slow pointers (from [Two-Pointer Skills for Linked Lists](https://labuladong.online/en/algo/essential-technique/linked-list-skills-summary/)) to find the middle of the list**: ```java ListNode slow, fast; slow = fast = head; while (fast != null && fast.next != null) { slow = slow.next; fast = fast.next.next; } // the slow pointer now points to the middle of the linked list ``` ![](../pictures/palindrome-list/1.jpg) **2) If `fast` is not `null`, the list length is odd, and `slow` should move one more step**: ```java if (fast != null) slow = slow.next; ``` ![](../pictures/palindrome-list/2.jpg) **3) Reverse the second half starting from `slow`. Then compare to check palindrome**: ```java ListNode left = head; ListNode right = reverse(slow); while (right != null) { if (left.val != right.val) return false; left = left.next; right = right.next; } return true; ``` ![](../pictures/palindrome-list/3.jpg) Now combine these 3 parts, and you can solve the problem efficiently. The `reverse` function can be found in [Reverse a Singly Linked List](https://labuladong.online/en/algo/data-structure/reverse-linked-list-recursion/): ```java class Solution { public boolean isPalindrome(ListNode head) { ListNode slow, fast; slow = fast = head; while (fast != null && fast.next != null) { slow = slow.next; fast = fast.next.next; } if (fast != null) slow = slow.next; ListNode left = head; ListNode right = reverse(slow); while (right != null) { if (left.val != right.val) return false; left = left.next; right = right.next; } return true; } 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; } } ``` You can open the panel below. Click the line while (right != null) many times. You will see `left` and `right` move toward each other and finish the palindrome check. Overall time complexity is O(N), space complexity is O(1), which is optimal. Some readers may ask: this method is fast, but it changes the input list. Can we avoid this? Yes. The key is to get the positions of pointers `p` and `q`: ![](../pictures/palindrome-list/4.jpg) Then, before `return`, add this code to restore the list: ```java p.next = reverse(q); ``` Due to space, I won’t write the full code here. You can try it yourself. ## 3. Summary To find a palindrome string, expand from the center to both sides. To check a palindrome string, shrink from both ends to the center. For a singly linked list, you cannot traverse backward. You can build a new reversed list, use postorder traversal of the list, or use a stack to process it in reverse order. For the palindrome linked list problem, because of the palindrome property, you don’t need to reverse the whole list. You only reverse part of it, and reduce space complexity to O(1). ================================================ FILE: interview/random-weight.md ================================================ ::: info Prerequisites Before reading this article, you should be familiar with: - [Prefix Sum Techniques](https://labuladong.online/en/algo/data-structure/prefix-sum/) - [Binary Search Explained](https://labuladong.online/en/algo/essential-technique/binary-search-framework/) ::: I got inspired to write this article while playing League of Legends mobile. A friend of mine was complaining about getting terrible teammates in ranked matches. I told him I thought my ranked teammates were pretty solid—they didn't seem that bad to me. He gave me this knowing look and said: "Usually, when players with high MMR can't find teammates at their skill level, they get matched with... weaker players." Wait, what? I thought about it for a second. Something didn't add up. Was he saying my MMR was low, or was he calling me the weak player? I immediately challenged him to duo queue with me to prove I wasn't the weak link—he was. I'll let you guess how that went. After that game, I came here to write this article because it got me thinking about how matchmaking systems work. ![](../pictures/random-weight/images.png) **I don't know if "hidden MMR" is actually a thing. Matchmaking is the backbone of any competitive game, so it's probably way more complex than a few simple metrics.** But if we simplify this whole hidden MMR concept, it becomes an interesting algorithmic problem: how does the system randomly match players with different probabilities? Or to put it more simply: how do you make weighted random selections? Don't think it's trivial. Sure, if you have an array of length `n` and need to randomly pick an element with equal probability, that's easy—just generate a random index in `[0, n-1]` and each element has a `1/n` chance of being selected. But what if each element has a different weight, where the weight determines the probability of selecting that element? How would you write an algorithm to randomly pick elements based on those weights? LeetCode problem 528, "[Random Pick with Weight](https://leetcode.cn/problems/random-pick-with-weight/)," is exactly this problem: **LeetCode 528. Random Pick with Weight** You are given a **0-indexed** array of positive integers `w` where `w[i]` describes the **weight** of the `i^(th)` index. You need to implement the function `pickIndex()`, which **randomly** picks an index in the range `[0, w.length - 1]` (**inclusive**) and returns it. The **probability** of picking an index `i` is `w[i] / sum(w)`. - For example, if `w = [1, 3]`, the probability of picking index `0` is `1 / (1 + 3) = 0.25` (i.e., `25%`), and the probability of picking index `1` is `3 / (1 + 3) = 0.75` (i.e., `75%`). Example 1:** ``` **Input** ["Solution","pickIndex"] [[[1]],[]] **Output** [null,0] **Explanation** Solution solution = new Solution([1]); solution.pickIndex(); // return 0. The only option is to return 0 since there is only one element in w. ``` Example 2:** ``` **Input** ["Solution","pickIndex","pickIndex","pickIndex","pickIndex","pickIndex"] [[[1,3]],[],[],[],[],[]] **Output** [null,1,1,1,1,0] **Explanation** Solution solution = new Solution([1, 3]); solution.pickIndex(); // return 1. It is returning the second element (index = 1) that has a probability of 3/4. solution.pickIndex(); // return 1 solution.pickIndex(); // return 1 solution.pickIndex(); // return 1 solution.pickIndex(); // return 0. It is returning the first element (index = 0) that has a probability of 1/4. Since this is a randomization problem, multiple answers are allowed. All of the following outputs can be considered correct: [null,1,1,1,1,0] [null,1,1,1,1,1] [null,1,1,1,0,0] [null,1,1,1,0,1] [null,1,0,1,0,0] ...... and so on. ``` **Constraints:** - `1 <= w.length <= 10^(4)` - `1 <= w[i] <= 10^(5)` - `pickIndex` will be called at most `10^(4)` times. Let's think through this problem and solve weighted random selection. ## Approach First, let's recap some previous articles on randomized algorithms: In [Designing a Data Structure for Random Deletion](https://labuladong.online/en/algo/data-structure/random-set/), we focused on data structure design—moving elements to the end of the array before deleting them to avoid shifting data. In [Randomized Algorithms in Games](https://labuladong.online/en/algo/frequency-interview/random-algorithm/), we covered the classic reservoir sampling algorithm, which uses simple math to select elements with equal probability from an infinite sequence. **But neither of those articles solve the problem we're facing here. Instead, it's the combination of [Prefix Sum Techniques](https://labuladong.online/en/algo/data-structure/prefix-sum/) and [Binary Search Explained](https://labuladong.online/en/algo/essential-technique/binary-search-framework/) that unlocks weighted random selection.** How do random algorithms relate to prefix sums and binary search? Let me explain. Say the input weight array is `w = [1,3,2,1]`. We want the selection probability to match the weights. Here's a way to visualize it—imagine drawing a colored line segment based on these weights: ![](../pictures/random-weight/1.jpeg) If we randomly drop a stone onto this line segment, and we pick the index corresponding to whichever color the stone lands on, doesn't that mean each index gets selected with probability proportional to its weight? **Now take a closer look at this colored line segment. What does it look like? It's just a [prefix sum array](https://labuladong.online/en/algo/data-structure/prefix-sum/)!** ![](../pictures/random-weight/2.jpeg) Next question: how do we simulate dropping a stone onto the line segment? With a random number, of course. For the prefix sum array `preSum` above, the range is `[1, 7]`. If we generate a random number `target = 5` in that range, it's like randomly dropping a stone on the line segment: ![](../pictures/random-weight/3.jpeg) But there's another issue: `preSum` doesn't actually contain the element 5. We need to find the smallest element greater than or equal to 5, which is 6, at index 3 of the `preSum` array: ![](../pictures/random-weight/4.jpeg) **How do we quickly find the smallest element in an array that's greater than or equal to our target? That's exactly what [binary search](https://labuladong.online/en/algo/essential-technique/binary-search-framework/) is for.** That's the core idea. Here's the breakdown: 1. Build a prefix sum array `preSum` from the weight array `w`. 2. Generate a random number within the range of `preSum`, then use binary search to find the index of the smallest element greater than or equal to this random number. 3. Subtract one from this index (because the prefix sum array has an index offset), and you get the index in the weight array—your final answer: ![](../pictures/random-weight/5.jpeg) ## Solution Code The approach itself is pretty straightforward, but the implementation is where things get tricky. Problems involving open/closed intervals, index offsets, and binary search require extremely precise attention to detail. Otherwise, you'll run into all sorts of hard-to-debug issues. Let's dig into the details, continuing with our previous example: ![](../pictures/random-weight/3.jpeg) Take the `preSum` array. What range should the random number `target` be in? The closed interval `[0, 7]` or the half-open interval `[0, 7)`? Neither. It should be in the closed interval `[1, 7]`, **because the 0 in the prefix sum array is essentially just a placeholder**. Think about it: ![](../pictures/random-weight/6.jpeg) So you should write the code like this: ```java int n = preSum.length; // the range of target is closed interval [1, preSum[n - 1]] int target = rand.nextInt(preSum[n - 1]) + 1; ``` Next, when searching for the index of the smallest element in `preSum` that's greater than or equal to `target`, which variant of binary search should you use? Left boundary search or right boundary search? You should use left boundary binary search: ```java // Binary search for the left boundary int left_bound(int[] nums, int target) { if (nums.length == 0) return -1; int left = 0, right = nums.length; while (left < right) { int mid = left + (right - left) / 2; if (nums[mid] == target) { right = mid; } else if (nums[mid] < target) { left = mid + 1; } else if (nums[mid] > target) { right = mid; } } return left; } ``` In [Binary Search Explained](https://labuladong.online/en/algo/essential-technique/binary-search-framework/), I focused on cases where the target element appears multiple times in the array, but didn't go into detail about when the target doesn't exist. Let me clarify that here. **When the target element `target` doesn't exist in array `nums`, the return value from left boundary binary search can be interpreted in these ways:** 1. It's the index of the smallest element in `nums` that's greater than or equal to `target`. 2. It's the index position where `target` should be inserted into `nums`. 3. It's the count of elements in `nums` that are less than `target`. For example, if you search for `target = 4` in the sorted array `nums = [2,3,5,7]`, left boundary binary search returns 2. Try plugging that into each of the interpretations above—they all work. All three interpretations are equivalent, so you can use whichever fits your problem best. Obviously, we need the first interpretation here. Putting it all together, here's the final solution: ```java class Solution { // prefix sum array private int[] preSum; private Random rand = new Random(); public Solution(int[] w) { int n = w.length; // construct the prefix sum array, shifting one position for preSum[0] preSum = new int[n + 1]; preSum[0] = 0; // preSum[i] = sum(w[0..i-1]) for (int i = 1; i <= n; i++) { preSum[i] = preSum[i - 1] + w[i - 1]; } } public int pickIndex() { int n = preSum.length; // Java's nextInt(n) method generates a random integer in [0, n) // adding one makes it randomly select a number in the closed interval [1, preSum[n - 1]] int target = rand.nextInt(preSum[n - 1]) + 1; // get the index of target in the prefix sum array preSum // don't forget the prefix sum array preSum and the original array w have an index offset by one position return left_bound(preSum, target) - 1; } // binary search for the left boundary private int left_bound(int[] nums, int target) { int left = 0, right = nums.length; while (left < right) { int mid = left + (right - left) / 2; if (nums[mid] == target) { right = mid; } else if (nums[mid] < target) { left = mid + 1; } else if (nums[mid] > target) { right = mid; } } return left; } } ``` With all the groundwork we've laid, you should fully understand this code. That solves the weighted random selection problem. I often get comments from readers joking that they just "cloud grind" problems by reading my articles—they understand everything without actually coding it themselves. But here's the thing: many problems are easy to understand conceptually, but once you dig deeper, there are tons of subtle pitfalls. This problem is a perfect example. So I still recommend practicing hands-on and reflecting on what you learn. ================================================ FILE: interview/subset-permutation-combination.md ================================================ ::: info Prerequisite Before reading this article, you need to learn: - [Binary Tree Algorithms (Overview)](https://labuladong.online/en/algo/essential-technique/binary-tree-summary/) - [Backtracking Algorithm Core Framework](https://labuladong.online/en/algo/essential-technique/backtrack-framework/) ::: You learned permutations, combinations, and subsets in high school math. But it is still hard to write algorithms to solve them. This article will teach you the key ideas to solve these problems with code. After you learn this, you can easily handle different variants later. For permutation, combination, or subset problems, the task is always: pick some elements from an array `nums` under certain rules. There are three basic forms: **Form 1: Elements are unique and cannot be reused. That is, all elements in `nums` are different, and each element can be used at most once. This is the basic form.** For example, for combinations, if `nums = [2,3,6,7]`, the combinations whose sum is 7 should only be `[7]`. **Form 2: Elements may repeat but still cannot be reused. That is, `nums` may contain duplicates, but each element can be used at most once.** For example, for combinations, if `nums = [2,5,2,1,2]`, the combinations whose sum is 7 should be `[2,2,2,1]` and `[5,2]`. **Form 3: Elements are unique and can be reused. That is, all elements in `nums` are different, and each element can be used many times.** For example, for combinations, if `nums = [2,3,6,7]`, the combinations whose sum is 7 should be `[2,2,3]` and `[7]`. You may think there is a fourth form: elements may repeat and can be reused. But if an element can be reused, why keep duplicates in `nums`? After removing duplicates, this case becomes the same as Form 3. So we do not treat it as a separate case. The examples above use combination problems, but permutation, combination, and subset problems can all appear in these three basic forms. So in total, there are 9 variants. On top of that, the problem can add more conditions. For example, “find combinations whose sum is `target` and have exactly `k` elements.” From here you can create many more variants. No wonder these topics show up so often in coding interviews. **But no matter how the form changes, the essence is to brute-force all solutions. These solutions form a tree structure. So if you use the backtracking framework correctly, you can solve all these problems by slightly changing the same code framework.** More concretely, you should first read and understand [Backtracking Algorithm Core Framework](https://labuladong.online/en/algo/essential-technique/backtrack-framework/). Then remember the backtracking trees for the subset problem and the permutation problem below. With these two trees, you can solve all permutation/combination/subset problems: ![](../pictures/permutation/1.jpeg) ![](../pictures/permutation/2.jpeg) Why can these two tree structures solve all related problems? **First, combination problems and subset problems are actually equivalent. We will explain this later. As for the three forms we talked about, they just mean cutting off or adding some branches on these two trees.** Next, we will start brute-forcing. We will go through all 9 forms of permutation/combination/subset problems and learn how to solve them with backtracking. ::: info Tip Some of you may have seen other solutions for permutations/subsets/combinations before. The code you saw may be different from what I show in this article. That is because backtracking has two ways to view the brute-force process. I will explain them step by step in [Ball-and-Box Model: Two Views of Backtracking](https://labuladong.online/en/algo/practice-in-action/two-views-of-backtrack/). It is not the right time to explain those other solutions now. Just follow the approach in this article. ::: ## Subsets (no duplicates, no reuse) LeetCode 78 “[Subsets](https://leetcode.com/problems/subsets/)” is this problem: You are given an array `nums` with no duplicate elements. Each element can be used at most once. Return all subsets of `nums`. Function signature: ```java List> subsets(int[] nums) ``` For example, if the input is `nums = [1,2,3]`, the algorithm should return: ```java [ [],[1],[2],[3],[1,2],[1,3],[2,3],[1,2,3] ] ``` First, ignore the code. Think about high-school math: how do we list all subsets by hand? First, generate the subset with 0 elements, the empty set `[]`. To make it simple, call it `S_0`. Then, based on `S_0`, generate all subsets with 1 element. Call this `S_1`: ![](../pictures/permutation/3.jpeg) Next, from `S_1`, we can derive `S_2`, the subsets with 2 elements: ![](../pictures/permutation/4.jpeg) Why do we only add `3` to `[2]`, but not add `1` in front? Because in a set, order does not matter. In `[1,2,3]`, after `2` there is only `3`. If you add `1` before `2`, you get `[2,1]`, which is the same as `[1,2]` that we already have. **In other words, we avoid duplicate subsets by keeping the relative order of elements unchanged**. Then, from `S_2` we can get `S_3`. In fact, `S_3` has only one subset `[1,2,3]`, which comes from `[1,2]`. The whole process forms a tree: ![](../pictures/permutation/5.jpeg) Notice this property of the tree: **If we treat the root as level 0, and define a node’s “value” as all the elements on the path from the root to that node, then all nodes on level `n` represent all subsets of size `n`**. For example, all subsets of size 2 are the values of the nodes on this level: ![](../pictures/permutation/6.jpeg) ::: info **As shown above, in the rest of this article, “node value” always means the elements on the path from the root to that node. The root is level 0**. ::: Now, if we want all subsets, we just need to traverse this tree and collect the value of every node. Here is the code: ```java class Solution { List> res = new LinkedList<>(); // record the recursive path of backtracking algorithm LinkedList track = new LinkedList<>(); // main function public List> subsets(int[] nums) { backtrack(nums, 0); return res; } // the core function of backtracking algorithm, traversing the backtracking tree of subset problems void backtrack(int[] nums, int start) { // the pre-order position, the value of each node is a subset res.add(new LinkedList<>(track)); // the standard framework of backtracking algorithm for (int i = start; i < nums.length; i++) { // make a choice track.addLast(nums[i]); // control the traversal of branches through the start parameter to avoid generating duplicate subsets backtrack(nums, i + 1); // undo the choice track.removeLast(); } } } ``` If you have read the earlier article “[Backtracking Algorithm Core Framework](https://labuladong.online/en/algo/essential-technique/backtrack-framework/)”, this code should be easy to understand. We use the parameter `start` to control how the branches grow so there are no duplicate subsets. We use `track` to record the path from the root to the current node. At each node (in pre-order), we collect the path into the result. After the backtracking tree is fully traversed, we have all subsets: ![](../pictures/permutation/5.jpeg) At the start of `backtrack`, it looks like there is no base case. Will it recurse forever? No. When `start == nums.length`, the leaf node’s value is already added to `res`, but the for loop does not run, so the recursion ends. ## Combinations (no duplicate elements, no reuse) If you can generate all unique subsets, then with small changes, you can also generate all unique combinations. For example, you are asked to take 2 elements from `nums = [1,2,3]` and form all combinations. How would you do it? Think a bit: all combinations of size 2 are just all subsets of size 2. **So combinations and subsets are the same: a combination of size `k` is a subset of size `k`.** For example, LeetCode 77 “[Combinations](https://leetcode.com/problems/combinations/)”: Given two integers `n` and `k`, return all possible combinations of `k` numbers chosen from the range `[1, n]`. The function signature is: ```java List> combine(int n, int k) ``` For example, the return value of `combine(3, 2)` should be: ```java [ [1,2],[1,3],[2,3] ] ``` This is a standard combination problem, but we can translate it into a subset problem: **You are given an array `nums = [1,2..,n]` and a positive integer `k`. Generate all subsets of size `k`.** Still use `nums = [1,2,3]` as an example. Earlier, when we asked for all subsets, we collected the values of all nodes. **Now you only need to collect the nodes at level 2 (root is level 0). Those are all combinations of size 2.** ![](../pictures/permutation/6.jpeg) In code, you only need to change the base case a bit, so the algorithm only collects the values of nodes at level `k`: ```java class Solution { List> res = new LinkedList<>(); // record the recursive path of backtracking algorithm LinkedList track = new LinkedList<>(); // main function public List> combine(int n, int k) { backtrack(1, n, k); return res; } void backtrack(int start, int n, int k) { // base case if (k == track.size()) { // reached the k-th level, collect the current node's value res.add(new LinkedList<>(track)); return; } // standard framework of backtracking algorithm for (int i = start; i <= n; i++) { // make a choice track.addLast(i); // control the traversal of branches through the start parameter to avoid duplicate subsets backtrack(i + 1, n, k); // undo the choice track.removeLast(); } } } ``` In this way, the standard combination problem is also solved. ## Permutations (no duplicates, no reuse) We already talked about permutation problems in the article [Backtracking Algorithm Core Framework](https://labuladong.online/en/algo/essential-technique/backtrack-framework/), so here we will just do a quick review. LeetCode 46 “[Permutations](https://leetcode.com/problems/permutations/)” is the standard permutation problem: Given an array `nums` that **has no duplicate numbers**, return all possible **permutations**. Function signature: ```java List> permute(int[] nums) ``` For example, if the input is `nums = [1,2,3]`, the return value should be: ```java [ [1,2,3],[1,3,2], [2,1,3],[2,3,1], [3,1,2],[3,2,1] ] ``` For the combination/subset problems we just talked about, we used a `start` variable to make sure that after `nums[start]`, we only use elements in `nums[start+1..]`. By fixing the relative order of elements, we avoid duplicate subsets. **But for permutations, the goal is to try all possible positions of each element. After `nums[i]`, elements on the left of `nums[i]` can also appear. So the previous method does not work. We need an extra `used` array to mark which elements are still available.** A standard permutation problem can be seen as the following multi-way tree: ![](../pictures/permutation/7.jpeg) We use the `used` array to mark the elements already in the current path to avoid choosing them again. Then we collect the values at all the leaf nodes. These are all the permutations: ```java class Solution { List> res = new LinkedList<>(); // record the recursive path of backtracking algorithm LinkedList track = new LinkedList<>(); // elements in track will be marked as true boolean[] used; // main function, input a set of non-repeating numbers, return all permutations public List> permute(int[] nums) { used = new boolean[nums.length]; backtrack(nums); return res; } // core function of backtracking algorithm void backtrack(int[] nums) { // base case, reach the leaf node if (track.size() == nums.length) { // collect the value at the leaf node res.add(new LinkedList(track)); return; } // standard framework of backtracking algorithm for (int i = 0; i < nums.length; i++) { // elements already in track, cannot be selected repeatedly if (used[i]) { continue; } // make a choice used[i] = true; track.addLast(nums[i]); // enter the next level of backtracking tree backtrack(nums); // cancel the choice track.removeLast(); used[i] = false; } } } ``` Now the permutation problem is solved. But what if the problem does not ask for all permutations, and only asks for permutations of length `k`? How should we do that? It is also simple. Just change the base case of the `backtrack` function and only collect nodes at level `k`: ```java // core function of backtracking algorithm void backtrack(int[] nums, int k) { // base case, reach the k-th level, collect the value of the node if (track.size() == k) { // the value of the k-th level node is a permutation of size k res.add(new LinkedList(track)); return; } // standard framework of backtracking algorithm for (int i = 0; i < nums.length; i++) { // ... backtrack(nums, k); // ... } } ``` ## Subsets / Combinations (elements can repeat, cannot reuse) In the standard subset problem we just talked about, the input array `nums` has no duplicate elements. What if `nums` contains duplicates? LeetCode 90: [Subsets II](https://leetcode.com/problems/subsets-ii/) is this kind of problem: You are given an integer array `nums`, which may contain duplicates. Return all possible subsets of this array. Function signature: ```java List> subsetsWithDup(int[] nums) ``` For example, if `nums = [1,2,2]`, you should output: ```java [ [],[1],[2],[1,2],[2,2],[1,2,2] ] ``` Strictly speaking, a “set” should not contain duplicate elements. But since the problem is defined like this, we ignore this detail and just focus on how to solve it. Still use `nums = [1,2,2]` as an example. To tell the two `2`s apart, we write `nums = [1,2,2']`. If we draw the subset tree as before, it is clear that two adjacent branches with the same value will create duplicates: ![](../pictures/permutation/8.jpeg) ```txt [ [], [1],[2],[2'], [1,2],[1,2'],[2,2'], [1,2,2'] ] ``` You can see `[2]` and `[1,2]` appear as duplicates, so we need pruning. If from one node there are multiple adjacent branches with the same value, we only traverse the first branch and prune the rest: ![](../pictures/permutation/9.jpeg) **In code, we first sort `nums` to put equal elements together. If we find `nums[i] == nums[i-1]`, we skip this element**: ```java class Solution { List> res = new LinkedList<>(); LinkedList track = new LinkedList<>(); public List> subsetsWithDup(int[] nums) { // sort first, let the same elements be together Arrays.sort(nums); backtrack(nums, 0); return res; } void backtrack(int[] nums, int start) { // preorder position, the value of each node is a subset res.add(new LinkedList<>(track)); for (int i = start; i < nums.length; i++) { // pruning logic, only traverse the first branch of adjacent branches with the same value if (i > start && nums[i] == nums[i - 1]) { continue; } track.addLast(nums[i]); backtrack(nums, i + 1); track.removeLast(); } } } ``` This code is almost the same as the code for the standard subset problem. We only added sorting and pruning. Why does this pruning work? If you compare it with the tree drawing above, it should be clear. Now the subset problem with duplicates is solved. **We said combination problems and subset problems are equivalent**, so let’s look directly at a combination problem: LeetCode 40: [Combination Sum II](https://leetcode.com/problems/combination-sum-ii/): You are given `candidates` and a target sum `target`. Find all combinations in `candidates` where the numbers sum to `target`. `candidates` may contain duplicates, and each number can be used at most once. We call it a combination problem, but if you change the wording, it becomes a subset problem: find all subsets of `candidates` whose sum is `target`. How do we solve it? Compare with the subset solution. We just add one extra variable `trackSum` to record the sum of elements in the current path, then change the base case a bit, and we can solve this problem: ```java class Solution { List> res = new LinkedList<>(); // record the backtracking path LinkedList track = new LinkedList<>(); // record the sum of elements in track int trackSum = 0; public List> combinationSum2(int[] candidates, int target) { if (candidates.length == 0) { return res; } // sort first, let the same elements be together Arrays.sort(candidates); backtrack(candidates, 0, target); return res; } // main function of backtracking algorithm void backtrack(int[] nums, int start, int target) { // base case, reach the target sum, find a qualified combination if (trackSum == target) { res.add(new LinkedList<>(track)); return; } // base case, exceed the target sum, end directly if (trackSum > target) { return; } // standard framework of backtracking algorithm for (int i = start; i < nums.length; i++) { // pruning logic, only traverse the first branch with the same value if (i > start && nums[i] == nums[i - 1]) { continue; } // make a choice track.add(nums[i]); trackSum += nums[i]; // recursively traverse the next level of backtracking tree backtrack(nums, i + 1, target); // cancel the choice track.removeLast(); trackSum -= nums[i]; } } } ``` ## Permutations (elements can repeat, no reuse within one permutation) When the input of a permutation problem has duplicate elements, it is a bit more complex than subset/combination problems. Look at LeetCode 47: [Permutations II](https://leetcode.com/problems/permutations-ii/): You are given an array `nums` that may contain duplicates. Write an algorithm to return all possible unique permutations. The function signature is: ```java List> permuteUnique(int[] nums) ``` For example, if `nums = [1,2,2]`, the function should return: ```java [ [1,2,2],[2,1,2],[2,2,1] ] ``` Here is the solution code: ```java class Solution { List> res = new LinkedList<>(); LinkedList track = new LinkedList<>(); boolean[] used; public List> permuteUnique(int[] nums) { // sort first, let the same elements be together Arrays.sort(nums); used = new boolean[nums.length]; backtrack(nums); return res; } void backtrack(int[] nums) { if (track.size() == nums.length) { res.add(new LinkedList(track)); return; } for (int i = 0; i < nums.length; i++) { if (used[i]) { continue; } // newly added pruning logic, fix the relative positions of the same elements in the permutation if (i > 0 && nums[i] == nums[i - 1] && !used[i - 1]) { continue; } track.add(nums[i]); used[i] = true; backtrack(nums); track.removeLast(); used[i] = false; } } } ``` Compare this code with the standard permutation solution you saw earlier. There are only two differences: 1. We sort `nums`. 2. We add one extra pruning condition. Like the subset/combination problems with duplicates, this is also used to avoid duplicate results. But note: the pruning logic for permutations is a bit different from that for subsets/combinations. We add this condition: `!used[i - 1]`. This part is a bit tricky. To make it easier to understand, we still use subscripts to tell equal elements apart. Assume the input is `nums = [1,2,2']`. The standard permutation algorithm will produce: ``` [ [1,2,2'],[1,2',2], [2,1,2'],[2,2',1], [2',1,2],[2',2,1] ] ``` Clearly, this result has duplicates. For example, `[1,2,2']` and `[1,2',2]` should be treated as the same permutation, but they are counted as two. So the key point is: how do we design a pruning rule to remove such duplicates? **The answer: keep the relative order of equal elements the same in every permutation.** For example, in `nums = [1,2,2']`, we always keep `2` before `2'` in any permutation. Then, among the 6 permutations above, only these 3 meet the rule: ``` [ [1,2,2'],[2,1,2'],[2,2',1] ] ``` These 3 are the correct result. Further, if `nums = [1,2,2',2'']`, as long as we fix the order of the repeated `2`s, for example `2 -> 2' -> 2''`, we will get all unique permutations. If you think about it, the idea is simple: **The standard permutation algorithm creates duplicates because it treats permutations that only differ by swapping equal elements as different, while they are actually the same. If we fix the order of equal elements, we avoid this.** Now look at the pruning code: ```java // New pruning rule: fix the relative order of equal elements if (i > 0 && nums[i] == nums[i - 1] && !used[i - 1]) { // If the previous equal element has not been used, skip this one continue; } // choose nums[i] ``` **When there are duplicates, for example `nums = [1,2,2',2'']`, `2'` can only be chosen after `2` is already used; `2''` can only be chosen after `2'` is used. This fixes the relative order of equal elements in every permutation.** Now, what if you change `!used[i - 1]` to `used[i - 1]`? The code still passes all tests, but it is slower. Why? This version is still correct because it just enforces the reverse order `2'' -> 2' -> 2`. That also removes duplicates. But why is it slower? Because it prunes fewer branches. For example, for input `nums = [2,2',2'']`, the backtracking tree looks like this: ![](../pictures/permutation/12.jpeg) If we mark visited paths of `backtrack` in green, and pruned branches in red, then with `!used[i - 1]` the tree looks like this: ![](../pictures/permutation/13.jpeg) And with `used[i - 1]` it looks like this: ![](../pictures/permutation/14.jpeg) You can see that `!used[i - 1]` prunes more branches, so it does less useless work and is more efficient. The `used[i - 1]` version still avoids duplicates, but it leaves more useless branches, so it is slower. You can click the "Edit" button in the visualization panel to change the code and compare the two pruning methods: There is also another pruning idea mentioned by some readers: ```java void backtrack(int[] nums, LinkedList track) { if (track.size() == nums.length) { res.add(new LinkedList(track)); return; } // record the value of the previous branch element // the problem statement says -10 <= nums[i] <= 10, so initialize to a special value int prevNum = -666; for (int i = 0; i < nums.length; i++) { // exclude illegal choices if (used[i]) { continue; } if (nums[i] == prevNum) { continue; } track.add(nums[i]); used[i] = true; // record the value on this branch prevNum = nums[i]; backtrack(nums, track); track.removeLast(); used[i] = false; } } ``` This idea is also correct. Imagine a node has several outgoing edges (choices) with the same value: ![](../pictures/permutation/11.jpeg) If we do nothing, the subtrees under these equal edges will be exactly the same, so we will get duplicate permutations. Because the array is sorted, equal elements are next to each other. So we can use a `prevNum` variable to record the value of the previous edge. If the next edge has the same value, we skip it. This avoids exploring identical subtrees and thus avoids duplicate permutations. Now the permutation problem with duplicate elements is also solved. ## Subsets / Combinations (no duplicate elements, can reuse) Now we come to the last type: the input array has no duplicate elements, but each element can be used unlimited times. Look at LeetCode 39: [Combination Sum](https://leetcode.com/problems/combination-sum/): You are given an integer array `candidates` without duplicates and a target integer `target`. Find all combinations of `candidates` where the chosen numbers sum to `target`. You may use each number in `candidates` an unlimited number of times. The function signature: ```java List> combinationSum(int[] candidates, int target) ``` For example, if `candidates = [1,2,3], target = 3`, the algorithm should return: ``` [ [1,1,1], [1,2], [3] ] ``` This problem is described as a combination problem, but it is actually also a subset problem: which subsets of `candidates` sum to `target`? To solve this type of problem, we still look at the backtracking tree. First, think about this question: **in the standard subset / combination problem, how do we make sure each element is not reused**? The answer is in the `start` parameter of the `backtrack` function: ```java // Backtracking algorithm framework without repetition void backtrack(int[] nums, int start) { for (int i = start; i < nums.length; i++) { // ... // Recursively traverse the next level of the backtracking tree, pay attention to the parameters backtrack(nums, i + 1); // ... } } ``` Here `i` starts from `start`, so the next level of recursion will start from `start + 1`. This makes sure that the element `nums[start]` will not be used again: ![](../pictures/permutation/1.jpeg) Now, if we want to allow each element to be reused, we just change `i + 1` to `i`: ```java // Backtracking algorithm framework with reusable combinations void backtrack(int[] nums, int start) { for (int i = start; i < nums.length; i++) { // ... // Recursively traverse the next level of backtracking tree, note the parameters backtrack(nums, i); // ... } } ``` This is like adding one more branch to the old backtracking tree. When we traverse this tree, one element can be used any number of times: ![](../pictures/permutation/10.jpeg) Of course, this tree would grow forever if we are not careful, so our recursive function must have a good base case to stop the algorithm. When the path sum is greater than `target`, we should stop going deeper. Here is the solution code: ```java class Solution { List> res = new LinkedList<>(); // record the backtracking path LinkedList track = new LinkedList<>(); // record the sum of the path in track int trackSum = 0; public List> combinationSum(int[] candidates, int target) { if (candidates.length == 0) { return res; } backtrack(candidates, 0, target); return res; } // main function of backtracking algorithm void backtrack(int[] nums, int start, int target) { // base case, find the target sum, record the result if (trackSum == target) { res.add(new LinkedList<>(track)); return; } // base case, exceed the target sum, stop traversing downwards if (trackSum > target) { return; } // standard framework of backtracking algorithm for (int i = start; i < nums.length; i++) { // choose nums[i] trackSum += nums[i]; track.add(nums[i]); // recursively traverse the next level of backtracking tree backtrack(nums, i, target); // the same element can be used repeatedly, note the parameter // undo the choice of nums[i] trackSum -= nums[i]; track.removeLast(); } } } ``` ## Permutations (no duplicates, elements can be reused) There is no LeetCode problem that directly tests this case, so let’s think about it ourselves. If `nums` has no duplicate elements, and each element can be chosen many times, what permutations do we have? For example, if `nums = [1,2,3]`, then all permutations under this rule are `3^3 = 27` in total: ```java [ [1,1,1],[1,1,2],[1,1,3],[1,2,1],[1,2,2],[1,2,3],[1,3,1],[1,3,2],[1,3,3], [2,1,1],[2,1,2],[2,1,3],[2,2,1],[2,2,2],[2,2,3],[2,3,1],[2,3,2],[2,3,3], [3,1,1],[3,1,2],[3,1,3],[3,2,1],[3,2,2],[3,2,3],[3,3,1],[3,3,2],[3,3,3] ] ``` The standard permutation algorithm uses a `used` array to prune, so that we do not pick the same element twice in one permutation. If we allow reusing elements, we just remove all pruning logic related to `used`. Then this problem becomes easy. Code is as follows: ```java class Solution { List> res = new LinkedList<>(); LinkedList track = new LinkedList<>(); public List> permuteRepeat(int[] nums) { backtrack(nums); return res; } // core function of backtracking algorithm void backtrack(int[] nums) { // base case, reaching the leaf node if (track.size() == nums.length) { // collect the values at the leaf node res.add(new LinkedList(track)); return; } // standard framework of backtracking algorithm for (int i = 0; i < nums.length; i++) { // make a choice track.add(nums[i]); // enter the next level of backtracking tree backtrack(nums); // undo the choice track.removeLast(); } } } ``` Now we have finished all nine variants of permutation/combination/subset problems. ## Final Summary Let’s review how the three forms of permutation/combination/subset problems differ in code. Since subset and combination problems are basically the same (only the base case is a bit different), we put them together. **Form 1: No duplicates, cannot reuse.** All elements in `nums` are unique, and each element can be used at most once. The core `backtrack` code is: ```java // Backtracking algorithm framework for combination/subset problems void backtrack(int[] nums, int start) { // Standard backtracking algorithm framework for (int i = start; i < nums.length; i++) { // Make a choice track.addLast(nums[i]); // Note the parameter backtrack(nums, i + 1); // Undo the choice track.removeLast(); } } // Backtracking algorithm framework for permutation problems void backtrack(int[] nums) { for (int i = 0; i < nums.length; i++) { // Pruning logic if (used[i]) { continue; } // Make a choice used[i] = true; track.addLast(nums[i]); backtrack(nums); // Undo the choice track.removeLast(); used[i] = false; } } ``` **Form 2: Duplicates allowed, cannot reuse.** `nums` may contain duplicate elements, and each element can be used at most once. The key is sorting and pruning. The core `backtrack` code is: ```java Arrays.sort(nums); // backtracking algorithm framework for combination/subset problems void backtrack(int[] nums, int start) { // standard backtracking algorithm framework for (int i = start; i < nums.length; i++) { // pruning logic, skip the adjacent branches with the same value if (i > start && nums[i] == nums[i - 1]) { continue; } // make a choice track.addLast(nums[i]); // note the parameter backtrack(nums, i + 1); // undo the choice track.removeLast(); } } Arrays.sort(nums); // backtracking algorithm framework for permutation problems void backtrack(int[] nums) { for (int i = 0; i < nums.length; i++) { // pruning logic if (used[i]) { continue; } // pruning logic, fix the relative position of the same elements in the permutation if (i > 0 && nums[i] == nums[i - 1] && !used[i - 1]) { continue; } // make a choice used[i] = true; track.addLast(nums[i]); backtrack(nums); // undo the choice track.removeLast(); used[i] = false; } } ``` **Form 3: No duplicates, can reuse.** All elements in `nums` are unique, and each element can be used many times. We just remove the de-duplication logic. The core `backtrack` code is: ```java // Backtracking algorithm framework for combination/subset problems void backtrack(int[] nums, int start) { // Standard backtracking algorithm framework for (int i = start; i < nums.length; i++) { // Make a choice track.addLast(nums[i]); // Note the parameter backtrack(nums, i); // Undo the choice track.removeLast(); } } // Backtracking algorithm framework for permutation problems void backtrack(int[] nums) { for (int i = 0; i < nums.length; i++) { // Make a choice track.addLast(nums[i]); backtrack(nums); // Undo the choice track.removeLast(); } } ``` As long as you think from the view of the recursion tree, these problems look complex but are actually simple. We just change the base case and a little logic. This is why I stress the importance of tree problems in [Framework Thinking for Learning Algorithms and Data Structures](https://labuladong.online/en/algo/essential-technique/algorithm-summary/) and [Binary Tree Tutorial (Overview)](https://labuladong.online/en/algo/essential-technique/binary-tree-summary/). If you have read this far, you really deserve a round of applause. I believe that when you meet all kinds of strange algorithm problems in the future, you will see through their nature at a glance and solve them with the same stable framework. Also, because of the length of this article, I did not analyze the time and space complexity of these algorithms. You can try to analyze their complexity yourself using the methods I introduced in [A Practical Guide to Time and Space Complexity](https://labuladong.online/en/algo/essential-technique/complexity-analysis/). ================================================ FILE: interview/trapping-rain-water.md ================================================ ::: info Prerequisite Before reading this article, you need to learn: - [Two Pointers Techniques for Arrays](https://labuladong.online/en/algo/essential-technique/array-two-pointers-summary/) ::: LeetCode Problem 42 "[Trapping Rain Water](https://leetcode.com/problems/trapping-rain-water/)" is interesting and often appears in interviews. This article will explain how to optimize the solution step by step. Here is the problem: **LeetCode 42. Trapping Rain Water** Given `n` non-negative integers representing an elevation map where the width of each bar is `1`, compute how much water it can trap after raining. Example 1:** ![](https://assets.leetcode.com/uploads/2018/10/22/rainwatertrap.png) ``` **Input:** height = [0,1,0,2,1,0,1,3,2,1,2,1] **Output:** 6 **Explanation:** The above elevation map (black section) is represented by array [0,1,0,2,1,0,1,3,2,1,2,1]. In this case, 6 units of rain water (blue section) are being trapped. ``` Example 2:** ``` **Input:** height = [4,2,0,3,2,5] **Output:** 9 ``` **Constraints:** - `n == height.length` - `1 <= n <= 2 * 10^(4)` - `0 <= height[i] <= 10^(5)` You are given an array representing a bar chart. The question asks how much water can be trapped by the bars after raining. ```java int trap(int[] height); ``` Next, I will introduce three methods from simple to advanced: brute-force solution -> memoization solution -> two pointers solution. We will solve this problem in O(N) time and O(1) space. ## 1. Core Idea ::: tip Quick Tip When solving algorithm problems, if you have no idea how to start, try to simplify the problem. Think about a small part first and write the simplest brute-force solution. You might find a key point and then optimize step by step to reach the best solution. ::: For this problem, let's not think about the whole bar chart first. Just think about how much water can be trapped at position `i`? ![](../pictures/rain-water/0.jpg) At position `i`, we can trap 2 units of water. This is because `height[i]` is 0, and the highest possible water level here is 2. So, 2 - 0 = 2. Why can position `i` hold up to 2 units of water? It depends on the tallest bar to the left and the tallest bar to the right of position `i`. Let's call these heights `l_max` and `r_max`. **The highest water level at position `i` is `min(l_max, r_max)`.** So, for position `i`, the amount of water trapped is: ```python water[i] = min( # Highest bar on the left max(height[0..i]), # Highest bar on the right max(height[i..end]) ) - height[i] ``` ![](../pictures/rain-water/1.jpg) ![](../pictures/rain-water/2.jpg) This is the main idea of this problem. We can write a simple brute-force algorithm: ```java class Solution { public int trap(int[] height) { int n = height.length; int res = 0; for (int i = 1; i < n - 1; i++) { int l_max = 0, r_max = 0; // find the tallest pillar on the right for (int j = i; j < n; j++) r_max = Math.max(r_max, height[j]); // find the tallest pillar on the left for (int j = i; j >= 0; j--) l_max = Math.max(l_max, height[j]); // if itself is the tallest, // l_max == r_max == height[i] res += Math.min(l_max, r_max) - height[i]; } return res; } } ``` This solution is very straightforward. The time complexity is O(N^2), and space complexity is O(1). But calculating `r_max` and `l_max` this way is not smart, because we have to loop every time. Can we make this better? ## 2. Memoization Optimization In the brute-force solution, at each position `i`, we need to calculate `r_max` and `l_max`. Instead of calculating them every time, we can calculate the results in advance and store them. This will reduce the time complexity. **We can use two arrays, `r_max` and `l_max`, as memoization. `l_max[i]` means the highest bar on the left of position `i`, and `r_max[i]` means the highest bar on the right of position `i`.** We calculate these two arrays first, so we don't repeat work: ```java class Solution { public int trap(int[] height) { if (height.length == 0) { return 0; } int n = height.length; int res = 0; // array serves as a memo int[] l_max = new int[n]; int[] r_max = new int[n]; // initialize base case l_max[0] = height[0]; r_max[n - 1] = height[n - 1]; // calculate l_max from left to right for (int i = 1; i < n; i++) l_max[i] = Math.max(height[i], l_max[i - 1]); // calculate r_max from right to left for (int i = n - 2; i >= 0; i--) r_max[i] = Math.max(height[i], r_max[i + 1]); // calculate the answer for (int i = 1; i < n - 1; i++) res += Math.min(l_max[i], r_max[i]) - height[i]; /** ![](../pictures/rain-water/1.jpg) */ return res; } } ``` This optimization is similar to the brute-force method, but it avoids repeated calculation. The time complexity becomes O(N), which is the best possible. But the space complexity is O(N). Next, let's look at a clever solution that reduces space complexity to O(1). ## 3. Two Pointer Solution ::: note My Advice This solution is for expanding your thinking. You don't have to focus too much on finding the best solution. For most people, in real interviews or tests, it is enough to use simple and clear methods like the one above. Although it uses a bit more space, most online judges will still accept it. Unless you cannot pass all test cases, and you have finished other questions with time left, you can then spend time improving the above solution. ::: The idea of this solution is the same, but the implementation is clever. This time, we do not use extra arrays to pre-calculate the values. Instead, we use two pointers to calculate while moving, which saves space. First, take a look at this piece of code: ```java int trap(int[] height) { int left = 0, right = height.length - 1; int l_max = 0, r_max = 0; while (left < right) { l_max = Math.max(l_max, height[left]); r_max = Math.max(r_max, height[right]); // At this point, what do l_max and r_max represent respectively? left++; right--; } } ``` For this code, what do `l_max` and `r_max` mean? It is easy to understand. **`l_max` means the highest bar from `height[0..left]`, and `r_max` means the highest bar from `height[right..end]`.** With this in mind, let's look at the solution: ```java class Solution { public int trap(int[] height) { int left = 0, right = height.length - 1; int l_max = 0, r_max = 0; int res = 0; while (left < right) { l_max = Math.max(l_max, height[left]); r_max = Math.max(r_max, height[right]); // res += min(l_max, r_max) - height[i] if (l_max < r_max) { res += l_max - height[left]; /** ![](../pictures/rain-water/5.jpg) */ left++; } else { res += r_max - height[right]; right--; } } return res; } } ``` You see, the core idea is exactly the same as before. But careful readers might notice a small difference: In the previous memoization solution, `l_max[i]` and `r_max[i]` mean the highest bar from `height[0..i]` and `height[i..end]`. ```java res += Math.min(l_max[i], r_max[i]) - height[i]; ``` ![](../pictures/rain-water/3.jpg) But in the two pointer solution, `l_max` and `r_max` mean the highest bars from `height[0..left]` and `height[right..end]`. For example, look at this code: ```java if (l_max < r_max) { res += l_max - height[left]; left++; } ``` ![](../pictures/rain-water/4.jpg) Here, `l_max` is the highest bar to the left of the `left` pointer. But `r_max` is not always the highest bar to the right of `left`. Can this still give the right answer? The key is, we only care about `min(l_max, r_max)`. **In the picture above, we already know `l_max < r_max`. It does not matter if `r_max` is the largest on the right. What matters is the water at `height[i]` only depends on the lower value, which is `l_max`.** ![](../pictures/rain-water/5.jpg) In this way, the trapping rain water problem is solved. ## Extension: Container With Most Water Now let's look at a problem very similar to the "Trapping Rain Water" problem. It's LeetCode problem 11: ["Container With Most Water"](https://leetcode.com/problems/container-with-most-water/): **LeetCode 11. Container With Most Water** You are given an integer array `height` of length `n`. There are `n` vertical lines drawn such that the two endpoints of the `i^(th)` line are `(i, 0)` and `(i, height[i])`. Find two lines that together with the x-axis form a container, such that the container contains the most water. Return *the maximum amount of water a container can store*. **Notice** that you may not slant the container. Example 1:** ![](https://s3-lc-upload.s3.amazonaws.com/uploads/2018/07/17/question_11.jpg) ``` **Input:** height = [1,8,6,2,5,4,8,3,7] **Output:** 49 **Explanation:** The above vertical lines are represented by array [1,8,6,2,5,4,8,3,7]. In this case, the max area of water (blue section) the container can contain is 49. ``` Example 2:** ``` **Input:** height = [1,1] **Output:** 1 ``` **Constraints:** - `n == height.length` - `2 <= n <= 10^(5)` - `0 <= height[i] <= 10^(4)` ```java // The function signature is as follows int maxArea(int[] height); ``` This problem is similar to the rain water problem, and you can use the same method. In fact, it is even more simple. The difference is: **In the rain water problem, each x-coordinate has a bar with width. In this problem, each x-coordinate has only a vertical line, no width.** Earlier, we talked a lot about `l_max` and `r_max`. They are used to figure out how much water `height[i]` can hold. But in this problem, since `height[i]` has no width, it becomes much easier. For example, in the rain water problem, if you know the heights of `height[left]` and `height[right]`, can you calculate how much water is between `left` and `right`? You can't, because you don't know how much water each bar in between can hold. You need the `l_max` and `r_max` for each bar to get the answer. But in this problem, if you know the heights of `height[left]` and `height[right]`, can you work out the water held between `left` and `right`? Yes, because in this problem the lines have no width. The water between `left` and `right` is: ```python min(height[left], height[right]) * (right - left) ``` Just like in the rain water problem, the height is decided by the smaller of `height[left]` and `height[right]`. The way to solve this problem is still the two-pointer technique: **Use two pointers, `left` and `right`, starting from both ends and moving towards the center. For each pair `[left, right]`, calculate the area, and keep the biggest area as the answer.** Let's look at the code: ```java class Solution { public int maxArea(int[] height) { int left = 0, right = height.length - 1; int res = 0; while (left < right) { // the area of the rectangle between [left, right] int cur_area = Math.min(height[left], height[right]) * (right - left); res = Math.max(res, cur_area); // two-pointer technique, move the shorter side if (height[left] < height[right]) { left++; } else { right--; } } return res; } } ``` The code is almost the same as the rain water problem. But you may wonder, why do we move the lower side in this if statement: ```java // Two-pointer technique, move the lower side if (height[left] < height[right]) { left++; } else { right--; } ``` **It is easy to understand, because the height of the rectangle is decided by the smaller of `height[left]` and `height[right]`.** If you move the lower side, that side may get higher, and the rectangle height may get bigger, so the area might also get bigger. If you move the higher side, the height will never get bigger, so the area cannot get bigger. That's it. This problem is solved. ================================================ FILE: multi-language-solutions/contribution-guide.md ================================================ # 修正 labuladong 刷题插件中的错误 ## 背景 为了帮助大家更好地学习算法,我之前写了很多算法教程,并开发了一系列刷题插件,统称为《labuladong 的刷题全家桶》,详情见 [这里](https://labuladong.github.io/article/fname.html?fname=全家桶简介)。 在我的教程和插件中的解法主要使用的是 Java 语言,原因是 Java 这门语言中规中矩,就算之前没有接触过,也能比较容易看懂逻辑。不过现在这不是 chatGPT 横空出世了嘛,我就借助 chatGPT 把我的解法改写成多种语言,希望对不同技术背景的小伙伴更加友好。 chatGPT 的改写效果还是非常不错的,不过难免还是存在一些错误,所以我希望能够和大家一起来修正这些错误。 ## 如何反馈错误 如果你发现某些解法代码不能通过力扣的所有测试用例(一般都是 chatGPT 改写的解法代码会出现这种情况,我的解法代码都是通过测试才发布的),可以 [点这里](https://github.com/labuladong/fucking-algorithm/issues/new?assignees=&labels=code+bug&template=bug_report.yml&title=%5Bbug%5D%5B%7B%E8%BF%99%E9%87%8C%E6%9B%BF%E6%8D%A2%E4%B8%BA%E5%87%BA%E9%94%99%E7%9A%84%E7%BC%96%E7%A8%8B%E8%AF%AD%E8%A8%80%7D%5D+%7B%E8%BF%99%E9%87%8C%E6%9B%BF%E6%8D%A2%E4%B8%BA%E5%87%BA%E9%94%99%E7%9A%84%E5%8A%9B%E6%89%A3%E9%A2%98%E7%9B%AE%E6%A0%87%E8%AF%86%E7%AC%A6%7D+) 按照模板提交 issue,我和其他小伙伴会提交 PR 修复这些错误。 ## 如何修正错误 首先,感谢你愿意为我的插件提供的解法代码纠错,你向本仓库提交 PR 修复错误后,你将成为本仓库的 contributor,出现在仓库首页的贡献者列表中。本仓库已经获得了 115k star,你的贡献将会被许多人看到。 修复代码很简单,所有多语言解法代码都存储在 [多语言解法代码/solution_code.md](https://github.com/labuladong/fucking-algorithm/blob/master/%E5%A4%9A%E8%AF%AD%E8%A8%80%E8%A7%A3%E6%B3%95%E4%BB%A3%E7%A0%81/solution_code.md) 中,你只要修改这个文件就行了。其内容的组织形如如下: https://leetcode.cn/problems/xxx 的多语言解法👇 ```cpp class Solution { public: int xxx() { // ... } }; ``` ```java class Solution { public int xxx() { // ... } } ``` ```python class Solution: def xxx(self): # ... ``` ```javascript var xxx = function() { // ... } ``` ```go func xxx() { // ... } ``` https://leetcode.cn/problems/xxx 的多语言解法👆 比如你想修改 [https://leetcode-cn.com/problems/longest-palindromic-substring/](https://leetcode-cn.com/problems/longest-palindromic-substring/) 的 JavaScript 解法,你可以在 [多语言解法代码/solution_code.md](https://github.com/labuladong/fucking-algorithm/blob/master/%E5%A4%9A%E8%AF%AD%E8%A8%80%E8%A7%A3%E6%B3%95%E4%BB%A3%E7%A0%81/solution_code.md) 中搜索 `longest-palindromic-substring` 关键词,即可找到这道题的多语言解法,然后修改 JavaScript 对应的解法代码,提交 PR 即可。 我的插件会自动拉取这个文件的最新内容,所以你的 PR 被合进 master 分支后,插件中的内容修改也会生效。 ## 提交 PR 的要求 1、你的 PR 必须是针对 [多语言解法代码/solution_code.md](https://github.com/labuladong/fucking-algorithm/blob/master/%E5%A4%9A%E8%AF%AD%E8%A8%80%E8%A7%A3%E6%B3%95%E4%BB%A3%E7%A0%81/solution_code.md) 文件中代码部分的修改,不要修改其他文件和其他内容。 2、把我的解法翻译成多语言的目的是帮助不同背景的小伙伴理解算法思维,所以你修改的代码可以不是效率最优的,但应该尽可能和我的解法思路保持一致,且包含我的解法中的完整注释。 3、你的 PR 描述中需要包含代码通过所有测试用例截图。PR 标题的格式为 `[fix][{lang}] {slug}`,其中 `{lang}` 需要替换为你修复的解法语言,比如 `[fix][cpp]`,`{slug}` 需要替换为你修复的题目的标识符(题目 URL 的最后一部分),比如 [https://leetcode.cn/problems/search-a-2d-matrix/](https://leetcode.cn/problems/search-a-2d-matrix/) 这道题的标识符就是 `search-a-2d-matrix`。 **你可以查看这个 PR 作为案例**:https://github.com/labuladong/fucking-algorithm/pull/1112 ================================================ FILE: multi-language-solutions/solution_code.md ================================================ https://leetcode.cn/problems/01-matrix 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: vector> updateMatrix(vector>& mat) { int m = mat.size(), n = mat[0].size(); // 记录答案的结果数组 vector> res(m, vector(n, -1)); // 初始化队列,把那些值为 0 的坐标放到队列里 queue> q; for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { if (mat[i][j] == 0) { q.push({i, j}); res[i][j] = 0; } } } // 执行 BFS 算法框架,从值为 0 的坐标开始向四周扩散 vector> dirs{{0, 1}, {0, -1}, {1, 0}, {-1, 0}}; while (!q.empty()) { auto cur = q.front(); q.pop(); int x = cur.first, y = cur.second; // 向四周扩散 for (auto& dir : dirs) { int nextX = x + dir[0]; int nextY = y + dir[1]; // 确保相邻的这个坐标没有越界且之前未被计算过 if (nextX >= 0 && nextX < m && nextY >= 0 && nextY < n && res[nextX][nextY] == -1) { q.push({nextX, nextY}); // 从 mat[x][y] 走到 mat[nextX][nextY] 需要一步 res[nextX][nextY] = res[x][y] + 1; } } } return res; } }; ``` ```go // by chatGPT (go) func updateMatrix(mat [][]int) [][]int { m, n := len(mat), len(mat[0]) // 记录答案的结果数组 res := make([][]int, m) for i := range res { res[i] = make([]int, n) for j := range res[i] { res[i][j] = -1 } } // 初始化队列,把那些值为 0 的坐标放到队列里 q := make([][2]int, 0) for i := 0; i < m; i++ { for j := 0; j < n; j++ { if mat[i][j] == 0 { q = append(q, [2]int{i, j}) res[i][j] = 0 } } } // 执行 BFS 算法框架,从值为 0 的坐标开始向四周扩散 dirs := [][]int{{0, 1}, {0, -1}, {1, 0}, {-1, 0}} for len(q) > 0 { cur := q[0] q = q[1:] x, y := cur[0], cur[1] // 向四周扩散 for _, dir := range dirs { nextX, nextY := x+dir[0], y+dir[1] // 确保相邻的这个坐标没有越界且之前未被计算过 if nextX >= 0 && nextX < m && nextY >= 0 && nextY < n && res[nextX][nextY] == -1 { q = append(q, [2]int{nextX, nextY}) // 从 mat[x][y] 走到 mat[nextX][nextY] 需要一步 res[nextX][nextY] = res[x][y] + 1 } } } return res } ``` ```java // by labuladong (java) class Solution { public int[][] updateMatrix(int[][] mat) { int m = mat.length, n = mat[0].length; // 记录答案的结果数组 int[][] res = new int[m][n]; // 初始化全部填充特殊值 -1,代表未计算, // 待会可以用来判断坐标是否已经计算过,避免重复遍历 for (int[] row : res) { Arrays.fill(row, -1); } Queue q = new LinkedList<>(); // 初始化队列,把那些值为 0 的坐标放到队列里 for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { if (mat[i][j] == 0) { q.offer(new int[]{i, j}); res[i][j] = 0; } } } // 执行 BFS 算法框架,从值为 0 的坐标开始向四周扩散 int[][] dirs = new int[][]{{0, 1}, {0, -1}, {1, 0}, {-1, 0}}; while (!q.isEmpty()) { int[] cur = q.poll(); int x = cur[0], y = cur[1]; // 向四周扩散 for (int i = 0; i < 4; i++) { int nextX = x + dirs[i][0]; int nextY = y + dirs[i][1]; // 确保相邻的这个坐标没有越界且之前未被计算过 if (nextX >= 0 && nextX < m && nextY >= 0 && nextY < n && res[nextX][nextY] == -1) { q.offer(new int[]{nextX, nextY}); // 从 mat[x][y] 走到 mat[nextX][nextY] 需要一步 res[nextX][nextY] = res[x][y] + 1; } } } return res; } } ``` ```javascript // by chatGPT (javascript) function updateMatrix(mat) { const m = mat.length, n = mat[0].length; // 记录答案的结果数组 const res = new Array(m).fill().map(() => new Array(n).fill(-1)); const q = []; // 初始化队列,把那些值为 0 的坐标放到队列里 for (let i = 0; i < m; i++) { for (let j = 0; j < n; j++) { if (mat[i][j] === 0) { q.push([i, j]); res[i][j] = 0; } } } // 执行 BFS 算法框架,从值为 0 的坐标开始向四周扩散 const dirs = [[0, 1], [0, -1], [1, 0], [-1, 0]]; while (q.length) { const cur = q.shift(); const x = cur[0], y = cur[1]; // 向四周扩散 for (let i = 0; i < 4; i++) { const nextX = x + dirs[i][0]; const nextY = y + dirs[i][1]; // 确保相邻的这个坐标没有越界且之前未被计算过 if (nextX >= 0 && nextX < m && nextY >= 0 && nextY < n && res[nextX][nextY] === -1) { q.push([nextX, nextY]); // 从 mat[x][y] 走到 mat[nextX][nextY] 需要一步 res[nextX][nextY] = res[x][y] + 1; } } } return res; } ``` ```python # by chatGPT (python) from collections import deque class Solution: def updateMatrix(self, mat: List[List[int]]) -> List[List[int]]: m, n = len(mat), len(mat[0]) # 记录答案的结果数组 res = [[-1] * n for _ in range(m)] q = deque() # 初始化队列,把那些值为 0 的坐标放到队列里 for i in range(m): for j in range(n): if mat[i][j] == 0: q.append((i, j)) res[i][j] = 0 # 执行 BFS 算法框架,从值为 0 的坐标开始向四周扩散 dirs = [[0, 1], [0, -1], [1, 0], [-1, 0]] while q: x, y = q.popleft() # 向四周扩散 for dx, dy in dirs: nextX, nextY = x + dx, y + dy # 确保相邻的这个坐标没有越界且之前未被计算过 if 0 <= nextX < m and 0 <= nextY < n and res[nextX][nextY] == -1: q.append((nextX, nextY)) # 从 mat[x][y] 走到 mat[nextX][nextY] 需要一步 res[nextX][nextY] = res[x][y] + 1 return res ``` https://leetcode.cn/problems/01-matrix 的多语言解法👆 https://leetcode.cn/problems/0i0mDW 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int memo[201][201]; int minPathSum(vector>& grid) { int m = grid.size(); int n = grid[0].size(); // 构造备忘录,初始值全部设为 -1 memset(memo, -1, sizeof(memo)); return dp(grid, m - 1, n - 1); } int dp(vector>& grid, int i, int j) { // base case if (i == 0 && j == 0) { return grid[0][0]; } if (i < 0 || j < 0) { return INT_MAX; } // 避免重复计算 if (memo[i][j] != -1) { return memo[i][j]; } // 将计算结果记入备忘录 memo[i][j] = min( dp(grid, i - 1, j), dp(grid, i, j - 1) ) + grid[i][j]; return memo[i][j]; } }; ``` ```go // by chatGPT (go) func minPathSum(grid [][]int) int { m := len(grid) n := len(grid[0]) // 构造备忘录,初始值全部设为 -1 memo := make([][]int, m) for i := 0; i < m; i++ { memo[i] = make([]int, n) for j := 0; j < n; j++ { memo[i][j] = -1 } } return dp(grid, m - 1, n - 1, memo) } func dp(grid [][]int, i int, j int, memo [][]int) int { // base case if i == 0 && j == 0 { return grid[0][0] } if i < 0 || j < 0 { return math.MaxInt32 } // 避免重复计算 if memo[i][j] != -1 { return memo[i][j] } // 将计算结果记入备忘录 left := dp(grid, i - 1, j, memo) up := dp(grid, i, j - 1, memo) curr := grid[i][j] + min(left, up) memo[i][j] = curr return curr } func min(a, b int) int { if a < b { return a } return b } ``` ```java // by labuladong (java) class Solution { int[][] memo; public int minPathSum(int[][] grid) { int m = grid.length; int n = grid[0].length; // 构造备忘录,初始值全部设为 -1 memo = new int[m][n]; for (int[] row : memo) Arrays.fill(row, -1); return dp(grid, m - 1, n - 1); } int dp(int[][] grid, int i, int j) { // base case if (i == 0 && j == 0) { return grid[0][0]; } if (i < 0 || j < 0) { return Integer.MAX_VALUE; } // 避免重复计算 if (memo[i][j] != -1) { return memo[i][j]; } // 将计算结果记入备忘录 memo[i][j] = Math.min( dp(grid, i - 1, j), dp(grid, i, j - 1) ) + grid[i][j]; return memo[i][j]; } } ``` ```javascript // by chatGPT (javascript) var minPathSum = function(grid) { var m = grid.length; var n = grid[0].length; // 构造备忘录,初始值全部设为 -1 var memo = new Array(m); for (var i = 0; i < memo.length; i++) { memo[i] = new Array(n); memo[i].fill(-1); } return dp(grid, m - 1, n - 1, memo); }; function dp(grid, i, j, memo) { // base case if (i == 0 && j == 0) { return grid[0][0]; } if (i < 0 || j < 0) { return Number.MAX_VALUE; } // 避免重复计算 if (memo[i][j] != -1) { return memo[i][j]; } // 将计算结果记入备忘录 memo[i][j] = Math.min( dp(grid, i - 1, j, memo), dp(grid, i, j - 1, memo) ) + grid[i][j]; return memo[i][j]; } ``` ```python # by chatGPT (python) class Solution: def __init__(self): self.memo = None def minPathSum(self, grid: List[List[int]]) -> int: m = len(grid) n = len(grid[0]) # 构造备忘录,初始值全部设为 -1 self.memo = [[-1 for _ in range(n)] for _ in range(m)] return self.dp(grid, m - 1, n - 1) def dp(self, grid: List[List[int]], i: int, j: int) -> int: # base case if i == 0 and j == 0: return grid[0][0] if i < 0 or j < 0: return float('inf') # 避免重复计算 if self.memo[i][j] != -1: return self.memo[i][j] # 将计算结果记入备忘录 self.memo[i][j] = min( self.dp(grid, i - 1, j), self.dp(grid, i, j - 1) ) + grid[i][j] return self.memo[i][j] ``` https://leetcode.cn/problems/0i0mDW 的多语言解法👆 https://leetcode.cn/problems/1fGaJU 的多语言解法👇 ```cpp // by labuladong (cpp) class Solution { public: vector> threeSum(vector& nums) { sort(nums.begin(), nums.end()); // n 为 3,从 nums[0] 开始计算和为 0 的三元组 return nSumTarget(nums, 3, 0, 0); } /* 注意:调用这个函数之前一定要先给 nums 排序 */ // n 填写想求的是几数之和,start 从哪个索引开始计算(一般填 0),target 填想凑出的目标和 vector> nSumTarget( vector& nums, int n, int start, int target) { int sz = nums.size(); vector> res; // 至少是 2Sum,且数组大小不应该小于 n if (n < 2 || sz < n) return res; // 2Sum 是 base case if (n == 2) { // 双指针那一套操作 int lo = start, hi = sz - 1; while (lo < hi) { int sum = nums[lo] + nums[hi]; int left = nums[lo], right = nums[hi]; if (sum < target) { while (lo < hi && nums[lo] == left) lo++; } else if (sum > target) { while (lo < hi && nums[hi] == right) hi--; } else { res.push_back({left, right}); while (lo < hi && nums[lo] == left) lo++; while (lo < hi && nums[hi] == right) hi--; } } } else { // n > 2 时,递归计算 (n-1)Sum 的结果 for (int i = start; i < sz; i++) { vector> sub = nSumTarget(nums, n - 1, i + 1, target - nums[i]); for (vector& arr : sub) { // (n-1)Sum 加上 nums[i] 就是 nSum arr.push_back(nums[i]); res.push_back(arr); } while (i < sz - 1 && nums[i] == nums[i + 1]) i++; } } return res; } }; ``` ```go // by chatGPT (go) func threeSum(nums []int) [][]int { sort.Ints(nums) // n 为 3,从 nums[0] 开始计算和为 0 的三元组 return nSumTarget(nums, 3, 0, 0) } /* 注意:调用这个函数之前一定要先给 nums 排序 */ // n 填写想求的是几数之和,start 从哪个索引开始计算(一般填 0),target 填想凑出的目标和 func nSumTarget(nums []int, n int, start int, target int) [][]int { sz := len(nums) var res [][]int // 至少是 2Sum,且数组大小不应该小于 n if n < 2 || sz < n { return res } // 2Sum 是 base case if n == 2 { // 双指针那一套操作 lo, hi := start, sz-1 for lo < hi { sum := nums[lo] + nums[hi] left, right := nums[lo], nums[hi] if sum < target { for lo < hi && nums[lo] == left { lo++ } } else if sum > target { for lo < hi && nums[hi] == right { hi-- } } else { res = append(res, []int{left, right}) for lo < hi && nums[lo] == left { lo++ } for lo < hi && nums[hi] == right { hi-- } } } } else { // n > 2 时,递归计算 (n-1)Sum 的结果 for i := start; i < sz; i++ { sub := nSumTarget(nums, n-1, i+1, target-nums[i]) for _, arr := range sub { // (n-1)Sum 加上 nums[i] 就是 nSum arr = append(arr, nums[i]) res = append(res, arr) } for i < sz-1 && nums[i] == nums[i+1] { i++ } } } return res } ``` ```java // by chatGPT (java) class Solution { public List> threeSum(int[] nums) { Arrays.sort(nums); // n 为 3,从 nums[0] 开始计算和为 0 的三元组 return nSumTarget(nums, 3, 0, 0); } /* 注意:调用这个函数之前一定要先给 nums 排序 */ // n 填写想求的是几数之和,start 从哪个索引开始计算(一般填 0),target 填想凑出的目标和 public List> nSumTarget( int[] nums, int n, int start, int target) { int sz = nums.length; List> res = new ArrayList<>(); // 至少是 2Sum,且数组大小不应该小于 n if (n < 2 || sz < n) return res; // 2Sum 是 base case if (n == 2) { // 双指针那一套操作 int lo = start, hi = sz - 1; while (lo < hi) { int sum = nums[lo] + nums[hi]; int left = nums[lo], right = nums[hi]; if (sum < target) { while (lo < hi && nums[lo] == left) lo++; } else if (sum > target) { while (lo < hi && nums[hi] == right) hi--; } else { List triplet = new ArrayList<>(); triplet.add(left); triplet.add(right); res.add(triplet); while (lo < hi && nums[lo] == left) lo++; while (lo < hi && nums[hi] == right) hi--; } } } else { // n > 2 时,递归计算 (n-1)Sum 的结果 for (int i = start; i < sz; i++) { List> sub = nSumTarget(nums, n - 1, i + 1, target - nums[i]); for (List arr : sub) { // (n-1)Sum 加上 nums[i] 就是 nSum arr.add(nums[i]); res.add(arr); } while (i < sz - 1 && nums[i] == nums[i + 1]) i++; } } return res; } } ``` ```javascript // by chatGPT (javascript) var threeSum = function(nums) { nums.sort((a, b) => a - b); // n 为 3,从 nums[0] 开始计算和为 0 的三元组 return nSumTarget(nums, 3, 0, 0); }; /* 注意:调用这个函数之前一定要先给 nums 排序 */ // n 填写想求的是几数之和,start 从哪个索引开始计算(一般填 0),target 填想凑出的目标和 var nSumTarget = function(nums, n, start, target) { var sz = nums.length; var res = []; // 至少是 2Sum,且数组大小不应该小于 n if (n < 2 || sz < n) return res; // 2Sum 是 base case if (n === 2) { // 双指针那一套操作 var lo = start, hi = sz - 1; while (lo < hi) { var sum = nums[lo] + nums[hi]; var left = nums[lo], right = nums[hi]; if (sum < target) { while (lo < hi && nums[lo] === left) lo++; } else if (sum > target) { while (lo < hi && nums[hi] === right) hi--; } else { res.push([left, right]); while (lo < hi && nums[lo] === left) lo++; while (lo < hi && nums[hi] === right) hi--; } } } else { // n > 2 时,递归计算 (n-1)Sum 的结果 for (var i = start; i < sz; i++) { var sub = nSumTarget(nums, n - 1, i + 1, target - nums[i]); for (var j = 0; j < sub.length; j++) { // (n-1)Sum 加上 nums[i] 就是 nSum sub[j].push(nums[i]); res.push(sub[j]); } while (i < sz - 1 && nums[i] === nums[i + 1]) i++; } } return res; }; ``` ```python # by chatGPT (python) class Solution: def threeSum(self, nums: List[int]) -> List[List[int]]: nums.sort() # n 为 3,从 nums[0] 开始计算和为 0 的三元组 return self.nSumTarget(nums, 3, 0, 0) # 注意:调用这个函数之前一定要先给 nums 排序 # n 填写想求的是几数之和,start 从哪个索引开始计算(一般填 0),target 填想凑出的目标和 def nSumTarget(self, nums: List[int], n: int, start: int, target: int) -> List[List[int]]: sz = len(nums) res = [] # 至少是 2Sum,且数组大小不应该小于 n if n < 2 or sz < n: return res # 2Sum 是 base case if n == 2: # 双指针那一套操作 lo, hi = start, sz - 1 while lo < hi: _sum = nums[lo] + nums[hi] left, right = nums[lo], nums[hi] if _sum < target: while lo < hi and nums[lo] == left: lo += 1 elif _sum > target: while lo < hi and nums[hi] == right: hi -= 1 else: res.append([left, right]) while lo < hi and nums[lo] == left: lo += 1 while lo < hi and nums[hi] == right: hi -= 1 else: # n > 2 时,递归计算 (n-1)Sum 的结果 for i in range(start, sz): if i > start and nums[i] == nums[i - 1]: continue sub = self.nSumTarget(nums, n - 1, i + 1, target - nums[i]) for arr in sub: # (n-1)Sum 加上 nums[i] 就是 nSum arr.append(nums[i]) res.append(arr) return res ``` https://leetcode.cn/problems/1fGaJU 的多语言解法👆 https://leetcode.cn/problems/2AoeFn 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: // 备忘录 int memo[100][100]; int uniquePaths(int m, int n) { memset(memo, 0, sizeof(memo)); return dp(m - 1, n - 1); } // 定义:从 (0, 0) 到 (x, y) 有 dp(x, y) 条路径 int dp(int x, int y) { // base case if (x == 0 && y == 0) { return 1; } if (x < 0 || y < 0) { return 0; } // 避免冗余计算 if (memo[x][y] > 0) { return memo[x][y]; } // 状态转移方程: // 到达 (x, y) 的路径数等于到达 (x - 1, y) 和 (x, y - 1) 路径数之和 memo[x][y] = dp(x - 1, y) + dp(x, y - 1); return memo[x][y]; } }; ``` ```go // by chatGPT (go) func uniquePaths(m int, n int) int { // 备忘录 memo := make([][]int, m) for i := range memo { memo[i] = make([]int, n) } return dp(m-1, n-1, memo) } // 定义:从 (0, 0) 到 (x, y) 有 dp(x, y) 条路径 func dp(x int, y int, memo [][]int) int { // base case if x == 0 && y == 0 { return 1 } if x < 0 || y < 0 { return 0 } // 避免冗余计算 if memo[x][y] > 0 { return memo[x][y] } // 状态转移方程: // 到达 (x, y) 的路径数等于到达 (x - 1, y) 和 (x, y - 1) 路径数之和 memo[x][y] = dp(x-1, y, memo) + dp(x, y-1, memo) return memo[x][y] } ``` ```java // by labuladong (java) class Solution { // 备忘录 int[][] memo; public int uniquePaths(int m, int n) { memo = new int[m][n]; return dp(m - 1, n - 1); } // 定义:从 (0, 0) 到 (x, y) 有 dp(x, y) 条路径 int dp(int x, int y) { // base case if (x == 0 && y == 0) { return 1; } if (x < 0 || y < 0) { return 0; } // 避免冗余计算 if (memo[x][y] > 0) { return memo[x][y]; } // 状态转移方程: // 到达 (x, y) 的路径数等于到达 (x - 1, y) 和 (x, y - 1) 路径数之和 memo[x][y] = dp(x - 1, y) + dp(x, y - 1); return memo[x][y]; } } ``` ```javascript // by chatGPT (javascript) var uniquePaths = function(m, n) { // 备忘录 let memo = new Array(m).fill().map(() => new Array(n).fill(0)); // 定义:从 (0, 0) 到 (x, y) 有 dp(x, y) 条路径 var dp = function(x, y) { // base case if (x === 0 && y === 0) { return 1; } if (x < 0 || y < 0) { return 0; } // 避免冗余计算 if (memo[x][y] > 0) { return memo[x][y]; } // 状态转移方程: // 到达 (x, y) 的路径数等于到达 (x - 1, y) 和 (x, y - 1) 路径数之和 memo[x][y] = dp(x - 1, y) + dp(x, y - 1); return memo[x][y]; }; return dp(m - 1, n - 1); }; ``` ```python # by chatGPT (python) class Solution: def __init__(self): self.memo = None def uniquePaths(self, m: int, n: int) -> int: self.memo = [[0] * n for _ in range(m)] return self.dp(m-1, n-1) def dp(self, x: int, y: int) -> int: # base case if x == 0 and y == 0: return 1 if x < 0 or y < 0: return 0 # 避免冗余计算 if self.memo[x][y] > 0: return self.memo[x][y] # 状态转移方程: # 到达 (x, y) 的路径数等于到达 (x - 1, y) 和 (x, y - 1) 路径数之和 self.memo[x][y] = self.dp(x - 1, y) + self.dp(x, y - 1) return self.memo[x][y] ``` https://leetcode.cn/problems/2AoeFn 的多语言解法👆 https://leetcode.cn/problems/3sum 的多语言解法👇 ```cpp // by labuladong (cpp) class Solution { public: vector> threeSum(vector& nums) { sort(nums.begin(), nums.end()); // n 为 3,从 nums[0] 开始计算和为 0 的三元组 return nSumTarget(nums, 3, 0, 0); } /* 注意:调用这个函数之前一定要先给 nums 排序 */ // n 填写想求的是几数之和,start 从哪个索引开始计算(一般填 0),target 填想凑出的目标和 vector> nSumTarget( vector& nums, int n, int start, int target) { int sz = nums.size(); vector> res; // 至少是 2Sum,且数组大小不应该小于 n if (n < 2 || sz < n) return res; // 2Sum 是 base case if (n == 2) { // 双指针那一套操作 int lo = start, hi = sz - 1; while (lo < hi) { int sum = nums[lo] + nums[hi]; int left = nums[lo], right = nums[hi]; if (sum < target) { while (lo < hi && nums[lo] == left) lo++; } else if (sum > target) { while (lo < hi && nums[hi] == right) hi--; } else { res.push_back({left, right}); while (lo < hi && nums[lo] == left) lo++; while (lo < hi && nums[hi] == right) hi--; } } } else { // n > 2 时,递归计算 (n-1)Sum 的结果 for (int i = start; i < sz; i++) { vector> sub = nSumTarget(nums, n - 1, i + 1, target - nums[i]); for (vector& arr : sub) { // (n-1)Sum 加上 nums[i] 就是 nSum arr.push_back(nums[i]); res.push_back(arr); } while (i < sz - 1 && nums[i] == nums[i + 1]) i++; } } return res; } }; ``` ```go // by chatGPT (go) func threeSum(nums []int) [][]int { sort.Ints(nums) // n 为 3,从 nums[0] 开始计算和为 0 的三元组 return nSumTarget(nums, 3, 0, 0) } /* 注意:调用这个函数之前一定要先给 nums 排序 */ // n 填写想求的是几数之和,start 从哪个索引开始计算(一般填 0),target 填想凑出的目标和 func nSumTarget(nums []int, n int, start int, target int) [][]int { sz := len(nums) var res [][]int // 至少是 2Sum,且数组大小不应该小于 n if n < 2 || sz < n { return res } // 2Sum 是 base case if n == 2 { // 双指针那一套操作 lo, hi := start, sz-1 for lo < hi { sum := nums[lo] + nums[hi] left, right := nums[lo], nums[hi] if sum < target { for lo < hi && nums[lo] == left { lo++ } } else if sum > target { for lo < hi && nums[hi] == right { hi-- } } else { res = append(res, []int{left, right}) for lo < hi && nums[lo] == left { lo++ } for lo < hi && nums[hi] == right { hi-- } } } } else { // n > 2 时,递归计算 (n-1)Sum 的结果 for i := start; i < sz; i++ { sub := nSumTarget(nums, n-1, i+1, target-nums[i]) for _, arr := range sub { // (n-1)Sum 加上 nums[i] 就是 nSum arr = append(arr, nums[i]) res = append(res, arr) } for i < sz-1 && nums[i] == nums[i+1] { i++ } } } return res } ``` ```java // by chatGPT (java) class Solution { public List> threeSum(int[] nums) { Arrays.sort(nums); // n 为 3,从 nums[0] 开始计算和为 0 的三元组 return nSumTarget(nums, 3, 0, 0); } /* 注意:调用这个函数之前一定要先给 nums 排序 */ // n 填写想求的是几数之和,start 从哪个索引开始计算(一般填 0),target 填想凑出的目标和 public List> nSumTarget( int[] nums, int n, int start, int target) { int sz = nums.length; List> res = new ArrayList<>(); // 至少是 2Sum,且数组大小不应该小于 n if (n < 2 || sz < n) return res; // 2Sum 是 base case if (n == 2) { // 双指针那一套操作 int lo = start, hi = sz - 1; while (lo < hi) { int sum = nums[lo] + nums[hi]; int left = nums[lo], right = nums[hi]; if (sum < target) { while (lo < hi && nums[lo] == left) lo++; } else if (sum > target) { while (lo < hi && nums[hi] == right) hi--; } else { res.add(new ArrayList<>(Arrays.asList(left, right))); while (lo < hi && nums[lo] == left) lo++; while (lo < hi && nums[hi] == right) hi--; } } } else { // n > 2 时,递归计算 (n-1)Sum 的结果 for (int i = start; i < sz; i++) { List> sub = nSumTarget(nums, n - 1, i + 1, target - nums[i]); for (List arr : sub) { // (n-1)Sum 加上 nums[i] 就是 nSum arr.add(nums[i]); res.add(arr); } while (i < sz - 1 && nums[i] == nums[i + 1]) i++; } } return res; } } ``` ```javascript // by chatGPT (javascript) var threeSum = function(nums) { nums.sort((a, b) => a - b); // n 为 3,从 nums[0] 开始计算和为 0 的三元组 return nSumTarget(nums, 3, 0, 0); } /* 注意:调用这个函数之前一定要先给 nums 排序 */ // n 填写想求的是几数之和,start 从哪个索引开始计算(一般填 0),target 填想凑出的目标和 var nSumTarget = function(nums, n, start, target) { var sz = nums.length; var res = []; // 至少是 2Sum,且数组大小不应该小于 n if (n < 2 || sz < n) return res; // 2Sum 是 base case if (n == 2) { // 双指针那一套操作 var lo = start, hi = sz - 1; while (lo < hi) { var sum = nums[lo] + nums[hi]; var left = nums[lo], right = nums[hi]; if (sum < target) { while (lo < hi && nums[lo] == left) lo++; } else if (sum > target) { while (lo < hi && nums[hi] == right) hi--; } else { res.push([left, right]); while (lo < hi && nums[lo] == left) lo++; while (lo < hi && nums[hi] == right) hi--; } } } else { // n > 2 时,递归计算 (n-1)Sum 的结果 for (var i = start; i < sz; i++) { var sub = nSumTarget(nums, n - 1, i + 1, target - nums[i]); for (var j = 0; j < sub.length; j++) { // (n-1)Sum 加上 nums[i] 就是 nSum sub[j].push(nums[i]); res.push(sub[j]); } while (i < sz - 1 && nums[i] == nums[i + 1]) i++; } } return res; }; ``` ```python # by chatGPT (python) class Solution: def threeSum(self, nums: List[int]) -> List[List[int]]: nums.sort() # n 为 3,从 nums[0] 开始计算和为 0 的三元组 return self.nSumTarget(nums, 3, 0, 0) # 注意:调用这个函数之前一定要先给 nums 排序 # n 填写想求的是几数之和,start 从哪个索引开始计算(一般填 0),target 填想凑出的目标和 def nSumTarget(self, nums: List[int], n: int, start: int, target: int) -> List[List[int]]: sz = len(nums) res = [] # 至少是 2Sum,且数组大小不应该小于 n if n < 2 or sz < n: return res # 2Sum 是 base case if n == 2: # 双指针那一套操作 lo, hi = start, sz - 1 while lo < hi: s = nums[lo] + nums[hi] left, right = nums[lo], nums[hi] if s < target: while lo < hi and nums[lo] == left: lo += 1 elif s > target: while lo < hi and nums[hi] == right: hi -= 1 else: res.append([left, right]) while lo < hi and nums[lo] == left: lo += 1 while lo < hi and nums[hi] == right: hi -= 1 else: # n > 2 时,递归计算 (n-1)Sum 的结果 for i in range(start, sz): sub = self.nSumTarget(nums, n - 1, i + 1, target - nums[i]) for arr in sub: # (n-1)Sum 加上 nums[i] 就是 nSum arr.append(nums[i]) res.append(arr) while i < sz - 1 and nums[i] == nums[i + 1]: i += 1 return res ``` https://leetcode.cn/problems/3sum 的多语言解法👆 https://leetcode.cn/problems/3sum-closest 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int threeSumClosest(vector& nums, int target) { if (nums.size() < 3) { return 0; } // 别忘了要先排序数组 sort(nums.begin(), nums.end()); // 记录三数之和与目标值的偏差 int delta = INT_MAX; for (int i = 0; i < nums.size() - 2; i++) { // 固定 nums[i] 为三数之和中的第一个数, // 然后对 nums[i+1..] 搜索接近 target - nums[i] 的两数之和 int sum = nums[i] + twoSumClosest(nums, i + 1, target - nums[i]); if (abs(delta) > abs(target - sum)) { delta = target - sum; } } return target - delta; } // 在 nums[start..] 搜索最接近 target 的两数之和 int twoSumClosest(vector& nums, int start, int target) { int lo = start, hi = nums.size() - 1; // 记录两数之和与目标值的偏差 int delta = INT_MAX; while (lo < hi) { int sum = nums[lo] + nums[hi]; if (abs(delta) > abs(target - sum)) { delta = target - sum; } if (sum < target) { lo++; } else { hi--; } } return target - delta; } }; ``` ```go // by chatGPT (go) func threeSumClosest(nums []int, target int) int { if len(nums) < 3 { return 0 } // 先排序数组 sort.Ints(nums) // 记录三数之和与目标值的偏差 delta := math.MaxInt32 for i := 0; i < len(nums)-2; i++ { // 固定 nums[i] 为三数之和中的第一个数, // 然后对 nums[i+1..] 搜索接近 target - nums[i] 的两数之和 sum := nums[i] + twoSumClosest(nums, i+1, target-nums[i]) if int(math.Abs(float64(delta))) > int(math.Abs(float64(target-sum))) { delta = target - sum } } return target - delta } // 在 nums[start..] 搜索最接近 target 的两数之和 func twoSumClosest(nums []int, start int, target int) int { lo, hi := start, len(nums)-1 // 记录两数之和与目标值的偏差 delta := math.MaxInt32 for lo < hi { sum := nums[lo] + nums[hi] if int(math.Abs(float64(delta))) > int(math.Abs(float64(target-sum))) { delta = target - sum } if sum < target { lo++ } else { hi-- } } return target - delta } ``` ```java // by labuladong (java) class Solution { public int threeSumClosest(int[] nums, int target) { if (nums.length < 3) { return 0; } // 别忘了要先排序数组 Arrays.sort(nums); // 记录三数之和与目标值的偏差 int delta = Integer.MAX_VALUE; for (int i = 0; i < nums.length - 2; i++) { // 固定 nums[i] 为三数之和中的第一个数, // 然后对 nums[i+1..] 搜索接近 target - nums[i] 的两数之和 int sum = nums[i] + twoSumClosest(nums, i + 1, target - nums[i]); if (Math.abs(delta) > Math.abs(target - sum)) { delta = target - sum; } } return target - delta; } // 在 nums[start..] 搜索最接近 target 的两数之和 int twoSumClosest(int[] nums, int start, int target) { int lo = start, hi = nums.length - 1; // 记录两数之和与目标值的偏差 int delta = Integer.MAX_VALUE; while (lo < hi) { int sum = nums[lo] + nums[hi]; if (Math.abs(delta) > Math.abs(target - sum)) { delta = target - sum; } if (sum < target) { lo++; } else { hi--; } } return target - delta; } } ``` ```javascript // by chatGPT (javascript) var threeSumClosest = function(nums, target) { if (nums.length < 3) { return 0; } // 别忘了要先排序数组 nums.sort(function(a, b) { return a - b; }); // 记录三数之和与目标值的偏差 var delta = Number.MAX_SAFE_INTEGER; for (var i = 0; i < nums.length - 2; i++) { // 固定 nums[i] 为三数之和中的第一个数, // 然后对 nums[i+1..] 搜索接近 target - nums[i] 的两数之和 var sum = nums[i] + twoSumClosest(nums, i + 1, target - nums[i]); if (Math.abs(delta) > Math.abs(target - sum)) { delta = target - sum; } } return target - delta; }; // 在 nums[start..] 搜索最接近 target 的两数之和 var twoSumClosest = function(nums, start, target) { var lo = start, hi = nums.length - 1; // 记录两数之和与目标值的偏差 var delta = Number.MAX_SAFE_INTEGER; while (lo < hi) { var sum = nums[lo] + nums[hi]; if (Math.abs(delta) > Math.abs(target - sum)) { delta = target - sum; } if (sum < target) { lo++; } else { hi--; } } return target - delta; }; ``` ```python # by chatGPT (python) class Solution: def threeSumClosest(self, nums: List[int], target: int) -> int: if len(nums) < 3: return 0 # 别忘了要先排序数组 nums.sort() # 记录三数之和与目标值的偏差 delta = float('inf') for i in range(len(nums) - 2): # 固定 nums[i] 为三数之和中的第一个数, # 然后对 nums[i+1..] 搜索接近 target - nums[i] 的两数之和 sum_ = nums[i] + self.twoSumClosest(nums, i + 1, target - nums[i]) if abs(delta) > abs(target - sum_): delta = target - sum_ return target - delta # 在 nums[start..] 搜索最接近 target 的两数之和 def twoSumClosest(self, nums: List[int], start: int, target: int) -> int: lo, hi = start, len(nums) - 1 # 记录两数之和与目标值的偏差 delta = float('inf') while lo < hi: sum_ = nums[lo] + nums[hi] if abs(delta) > abs(target - sum_): delta = target - sum_ if sum_ < target: lo += 1 else: hi -= 1 return target - delta ``` https://leetcode.cn/problems/3sum-closest 的多语言解法👆 https://leetcode.cn/problems/3sum-smaller 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int threeSumSmaller(vector& nums, int target) { if (nums.size() < 3) { return 0; } // 别忘了要先排序数组 sort(nums.begin(), nums.end()); int res = 0; for (int i = 0; i < nums.size() - 2; i++) { // 固定 nums[i] 为三数之和中的第一个数, // 然后对 nums[i+1..] 搜索小于 target - nums[i] 的两数之和个数 res += twoSumSmaller(nums, i + 1, target - nums[i]); } return res; } // 在 nums[start..] 搜索小于 target 的两数之和个数 int twoSumSmaller(vector& nums, int start, int target) { int lo = start, hi = nums.size() - 1; int count = 0; while (lo < hi) { if (nums[lo] + nums[hi] < target) { // nums[lo] 和 nums[lo+1..hi] // 中的任一元素之和都小于 target count += hi - lo; lo++; } else { hi--; } } return count; } }; ``` ```go // by chatGPT (go) func threeSumSmaller(nums []int, target int) int { if len(nums) < 3 { return 0 } // 别忘了要先排序数组 sort.Ints(nums) res := 0 for i := 0; i < len(nums) - 2; i++ { // 固定 nums[i] 为三数之和中的第一个数, // 然后对 nums[i+1..] 搜索小于 target - nums[i] 的两数之和个数 res += twoSumSmaller(nums, i + 1, target - nums[i]) } return res } // 在 nums[start..] 搜索小于 target 的两数之和个数 func twoSumSmaller(nums []int, start int, target int) int { lo, hi := start, len(nums) - 1 count := 0 for lo < hi { if nums[lo] + nums[hi] < target { // nums[lo] 和 nums[lo+1..hi] // 中的任一元素之和都小于 target count += hi - lo lo++ } else { hi-- } } return count } ``` ```java // by labuladong (java) class Solution { public int threeSumSmaller(int[] nums, int target) { if (nums.length < 3) { return 0; } // 别忘了要先排序数组 Arrays.sort(nums); int res = 0; for (int i = 0; i < nums.length - 2; i++) { // 固定 nums[i] 为三数之和中的第一个数, // 然后对 nums[i+1..] 搜索小于 target - nums[i] 的两数之和个数 res += twoSumSmaller(nums, i + 1, target - nums[i]); } return res; } // 在 nums[start..] 搜索小于 target 的两数之和个数 int twoSumSmaller(int[] nums, int start, int target) { int lo = start, hi = nums.length - 1; int count = 0; while (lo < hi) { if (nums[lo] + nums[hi] < target) { // nums[lo] 和 nums[lo+1..hi] // 中的任一元素之和都小于 target count += hi - lo; lo++; } else { hi--; } } return count; } } ``` ```javascript // by chatGPT (javascript) var threeSumSmaller = function(nums, target) { // 如果数组长度小于3,返回0 if(nums.length < 3) { return 0; } // 将数组升序排序 nums.sort(function(a,b) {return a - b;}); // 定义变量res,初始化为0 var res = 0; // 枚举第一个元素 for (var i = 0; i < nums.length - 2; i++) { // 对第一个元素之后的元素搜索两数之和小于target-nums[i]的个数 res += twoSumSmaller(nums, i + 1, target - nums[i]); } // 返回答案 return res; // 搜索nums[start..]内两数之和小于target的个数 function twoSumSmaller(nums, start, target) { var lo = start, hi = nums.length - 1; var count = 0; while (lo < hi) { // 如果nums[lo] + nums[hi] < target,这时nums[lo]和nums[lo + 1..hi]中任意一个数与nums[hi]相加都会小于target if (nums[lo] + nums[hi] < target) { // 计算加入nums[lo]时小于target的两数之和的个数,并将lo移动一位 count += hi - lo; lo++; } else { // 如果nums[lo] + nums[hi] >= target,则将hi往前一位 hi--; } } // 返回小于target的两数之和的个数 return count; } }; ``` ```python # by chatGPT (python) class Solution: def threeSumSmaller(self, nums: List[int], target: int) -> int: if len(nums) < 3: return 0 nums.sort() res = 0 for i in range(len(nums) - 2): # 固定 nums[i] 为三数之和中的第一个数, # 然后对 nums[i+1..] 搜索小于 target - nums[i] 的两数之和个数 res += self.twoSumSmaller(nums, i + 1, target - nums[i]) return res def twoSumSmaller(self, nums: List[int], start: int, target: int) -> int: lo = start hi = len(nums) - 1 count = 0 while lo < hi: if nums[lo] + nums[hi] < target: # nums[lo] 和 nums[lo+1..hi] # 中的任一元素之和都小于 target count += hi - lo lo += 1 else: hi -= 1 return count ``` https://leetcode.cn/problems/3sum-smaller 的多语言解法👆 https://leetcode.cn/problems/3u1WK4 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) { // p1 指向 A 链表头结点,p2 指向 B 链表头结点 ListNode *p1 = headA, *p2 = headB; while (p1 != p2) { // p1 走一步,如果走到 A 链表末尾,转到 B 链表 if (p1 == nullptr) p1 = headB; else p1 = p1->next; // p2 走一步,如果走到 B 链表末尾,转到 A 链表 if (p2 == nullptr) p2 = headA; else p2 = p2->next; } return p1; } }; ``` ```go // by chatGPT (go) func getIntersectionNode(headA, headB *ListNode) *ListNode { // p1 指向 A 链表头结点,p2 指向 B 链表头结点 p1, p2 := headA, headB for p1 != p2 { // p1 走一步,如果走到 A 链表末尾,转到 B 链表 if p1 == nil { p1 = headB } else { p1 = p1.Next } // p2 走一步,如果走到 B 链表末尾,转到 A 链表 if p2 == nil { p2 = headA } else { p2 = p2.Next } } return p1 } ``` ```java // by labuladong (java) public class Solution { public ListNode getIntersectionNode(ListNode headA, ListNode headB) { // p1 指向 A 链表头结点,p2 指向 B 链表头结点 ListNode p1 = headA, p2 = headB; while (p1 != p2) { // p1 走一步,如果走到 A 链表末尾,转到 B 链表 if (p1 == null) p1 = headB; else p1 = p1.next; // p2 走一步,如果走到 B 链表末尾,转到 A 链表 if (p2 == null) p2 = headA; else p2 = p2.next; } return p1; } } ``` ```javascript // by chatGPT (javascript) var getIntersectionNode = function(headA, headB) { // p1 指向 A 链表头结点,p2 指向 B 链表头结点 let p1 = headA, p2 = headB; while (p1 !== p2) { // p1 走一步,如果走到 A 链表末尾,转到 B 链表 if (p1 === null) p1 = headB; else p1 = p1.next; // p2 走一步,如果走到 B 链表末尾,转到 A 链表 if (p2 === null) p2 = headA; else p2 = p2.next; } return p1; }; ``` ```python # by chatGPT (python) class Solution: def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode: # p1 指向 A 链表头结点,p2 指向 B 链表头结点 p1, p2 = headA, headB while p1 != p2: # p1 走一步,如果走到 A 链表末尾,转到 B 链表 if p1 is None: p1 = headB else: p1 = p1.next # p2 走一步,如果走到 B 链表末尾,转到 A 链表 if p2 is None: p2 = headA else: p2 = p2.next return p1 ``` https://leetcode.cn/problems/3u1WK4 的多语言解法👆 https://leetcode.cn/problems/4sum 的多语言解法👇 ```cpp // by labuladong (cpp) class Solution { public: vector> fourSum(vector& nums, int target) { sort(nums.begin(), nums.end()); // n 为 4,从 nums[0] 开始计算和为 target 的四元组 return nSumTarget(nums, 4, 0, target); } /* 注意:调用这个函数之前一定要先给 nums 排序 */ // n 填写想求的是几数之和,start 从哪个索引开始计算(一般填 0),target 填想凑出的目标和 vector> nSumTarget( vector& nums, int n, int start, int target) { int sz = nums.size(); vector> res; // 至少是 2Sum,且数组大小不应该小于 n if (n < 2 || sz < n) return res; // 2Sum 是 base case if (n == 2) { // 双指针那一套操作 int lo = start, hi = sz - 1; while (lo < hi) { int sum = nums[lo] + nums[hi]; int left = nums[lo], right = nums[hi]; if (sum < target) { while (lo < hi && nums[lo] == left) lo++; } else if (sum > target) { while (lo < hi && nums[hi] == right) hi--; } else { res.push_back({left, right}); while (lo < hi && nums[lo] == left) lo++; while (lo < hi && nums[hi] == right) hi--; } } } else { // n > 2 时,递归计算 (n-1)Sum 的结果 for (int i = start; i < sz; i++) { vector> sub = nSumTarget(nums, n - 1, i + 1, target - nums[i]); for (vector& arr : sub) { // (n-1)Sum 加上 nums[i] 就是 nSum arr.push_back(nums[i]); res.push_back(arr); } while (i < sz - 1 && nums[i] == nums[i + 1]) i++; } } return res; } }; ``` ```go // by chatGPT (go) func fourSum(nums []int, target int) [][]int { sort.Ints(nums) // n 为 4,从 nums[0] 开始计算和为 target 的四元组 return nSumTarget(nums, 4, 0, target) } /* 注意:调用这个函数之前一定要先给 nums 排序 */ // n 填写想求的是几数之和,start 从哪个索引开始计算(一般填 0),target 填想凑出的目标和 func nSumTarget(nums []int, n, start, target int) [][]int { sz := len(nums) res := [][]int{} // 至少是 2Sum,且数组大小不应该小于 n if n < 2 || sz < n { return res } // 2Sum 是 base case if n == 2 { // 双指针那一套操作 lo, hi := start, sz-1 for lo < hi { sum := nums[lo] + nums[hi] left, right := nums[lo], nums[hi] if sum < target { for lo < hi && nums[lo] == left { lo++ } } else if sum > target { for lo < hi && nums[hi] == right { hi-- } } else { res = append(res, []int{left, right}) for lo < hi && nums[lo] == left { lo++ } for lo < hi && nums[hi] == right { hi-- } } } } else { // n > 2 时,递归计算 (n-1)Sum 的结果 for i := start; i < sz; i++ { sub := nSumTarget(nums, n-1, i+1, target-nums[i]) for _, arr := range sub { // (n-1)Sum 加上 nums[i] 就是 nSum arr = append(arr, nums[i]) res = append(res, arr) } for i < sz-1 && nums[i] == nums[i+1] { i++ } } } return res } ``` ```java // by chatGPT (java) class Solution { public List> fourSum(int[] nums, int target) { Arrays.sort(nums); // n 为 4,从 nums[0] 开始计算和为 target 的四元组 return nSumTarget(nums, 4, 0, target); } /* 注意:调用这个函数之前一定要先给 nums 排序 */ // n 填写想求的是几数之和,start 从哪个索引开始计算(一般填 0),target 填想凑出的目标和 private List> nSumTarget(int[] nums, int n, int start, int target) { int sz = nums.length; List> res = new ArrayList<>(); // 至少是 2Sum,且数组大小不应该小于 n if (n < 2 || sz < n) return res; // 2Sum 是 base case if (n == 2) { // 双指针那一套操作 int lo = start, hi = sz - 1; while (lo < hi) { int sum = nums[lo] + nums[hi]; int left = nums[lo], right = nums[hi]; if (sum < target) { while (lo < hi && nums[lo] == left) lo++; } else if (sum > target) { while (lo < hi && nums[hi] == right) hi--; } else { res.add(new ArrayList<>(Arrays.asList(left, right))); while (lo < hi && nums[lo] == left) lo++; while (lo < hi && nums[hi] == right) hi--; } } } else { // n > 2 时,递归计算 (n-1)Sum 的结果 for (int i = start; i < sz; i++) { List> sub = nSumTarget(nums, n - 1, i + 1, target - nums[i]); for (List arr : sub) { // (n-1)Sum 加上 nums[i] 就是 nSum arr.add(nums[i]); res.add(arr); } while (i < sz - 1 && nums[i] == nums[i + 1]) i++; } } return res; } } ``` ```javascript // by chatGPT (javascript) var fourSum = function(nums, target) { nums.sort((a, b) => a - b); // n 为 4,从 nums[0] 开始计算和为 target 的四元组 return nSumTarget(nums, 4, 0, target); }; /* 注意:调用这个函数之前一定要先给 nums 排序 */ // n 填写想求的是几数之和,start 从哪个索引开始计算(一般填 0),target 填想凑出的目标和 var nSumTarget = function(nums, n, start, target) { var sz = nums.length; var res = []; // 至少是 2Sum,且数组大小不应该小于 n if (n < 2 || sz < n) return res; // 2Sum 是 base case if (n === 2) { // 双指针那一套操作 var lo = start, hi = sz - 1; while (lo < hi) { var sum = nums[lo] + nums[hi]; var left = nums[lo], right = nums[hi]; if (sum < target) { while (lo < hi && nums[lo] === left) lo++; } else if (sum > target) { while (lo < hi && nums[hi] === right) hi--; } else { res.push([left, right]); while (lo < hi && nums[lo] === left) lo++; while (lo < hi && nums[hi] === right) hi--; } } } else { // n > 2 时,递归计算 (n-1)Sum 的结果 for (var i = start; i < sz; i++) { var sub = nSumTarget(nums, n - 1, i + 1, target - nums[i]); for (var arr of sub) { // (n-1)Sum 加上 nums[i] 就是 nSum arr.push(nums[i]); res.push(arr); } while (i < sz - 1 && nums[i] === nums[i + 1]) i++; } } return res; }; ``` ```python # by chatGPT (python) class Solution: def fourSum(self, nums: List[int], target: int) -> List[List[int]]: nums.sort() # n 为 4,从 nums[0] 开始计算和为 target 的四元组 return self.nSumTarget(nums, 4, 0, target) # 注意:调用这个函数之前一定要先给 nums 排序 # n 填写想求的是几数之和,start 从哪个索引开始计算(一般填 0),target 填想凑出的目标和 def nSumTarget(self, nums: List[int], n: int, start: int, target: int) -> List[List[int]]: sz = len(nums) res = [] # 至少是 2Sum,且数组大小不应该小于 n if n < 2 or sz < n: return res # 2Sum 是 base case if n == 2: # 双指针那一套操作 lo, hi = start, sz - 1 while lo < hi: s = nums[lo] + nums[hi] left, right = nums[lo], nums[hi] if s < target: while lo < hi and nums[lo] == left: lo += 1 elif s > target: while lo < hi and nums[hi] == right: hi -= 1 else: res.append([left, right]) while lo < hi and nums[lo] == left: lo += 1 while lo < hi and nums[hi] == right: hi -= 1 else: # n > 2 时,递归计算 (n-1)Sum 的结果 for i in range(start, sz): sub = self.nSumTarget(nums, n - 1, i + 1, target - nums[i]) for arr in sub: # (n-1)Sum 加上 nums[i] 就是 nSum arr.append(nums[i]) res.append(arr) while i < sz - 1 and nums[i] == nums[i + 1]: i += 1 return res ``` https://leetcode.cn/problems/4sum 的多语言解法👆 https://leetcode.cn/problems/8Zf90G 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int evalRPN(vector& tokens) { stack stk; for (const auto& token : tokens) { if (string("+-*/").find(token) != string::npos) { // 是个运算符,从栈顶拿出两个数字进行运算,运算结果入栈 int a = stk.top(); stk.pop(); int b = stk.top(); stk.pop(); switch (token[0]) { case '+': stk.push(b + a); break; case '*': stk.push(b * a); break; // 对于减法和除法,顺序别搞反了,第二个数是被除(减)数 case '-': stk.push(b - a); break; case '/': stk.push(b / a); break; } } else { // 是个数字,直接入栈即可 stk.push(stoi(token)); } } // 最后栈中剩下一个数字,即是计算结果 return stk.top(); } }; ``` ```go // by chatGPT (go) func evalRPN(tokens []string) int { stk := make([]int, 0) for _, token := range tokens { if strings.Contains("+-*/", token) { // 是个运算符,从栈顶拿出两个数字进行运算,运算结果入栈 a, b := stk[len(stk)-1], stk[len(stk)-2] stk = stk[:len(stk)-2] switch token { case "+": stk = append(stk, a+b) case "*": stk = append(stk, a*b) // 对于减法和除法,顺序别搞反了,第二个数是被除(减)数 case "-": stk = append(stk, b-a) case "/": stk = append(stk, b/a) } } else { // 是个数字,直接入栈即可 num, _ := strconv.Atoi(token) stk = append(stk, num) } } // 最后栈中剩下一个数字,即是计算结果 return stk[0] } ``` ```java // by labuladong (java) class Solution { public int evalRPN(String[] tokens) { Stack stk = new Stack<>(); for (String token : tokens) { if ("+-*/".contains(token)) { // 是个运算符,从栈顶拿出两个数字进行运算,运算结果入栈 int a = stk.pop(), b = stk.pop(); switch (token) { case "+": stk.push(a + b); break; case "*": stk.push(a * b); break; // 对于减法和除法,顺序别搞反了,第二个数是被除(减)数 case "-": stk.push(b - a); break; case "/": stk.push(b / a); break; } } else { // 是个数字,直接入栈即可 stk.push(Integer.parseInt(token)); } } // 最后栈中剩下一个数字,即是计算结果 return stk.pop(); } } ``` ```javascript // by chatGPT (javascript) var evalRPN = function(tokens) { let stk = []; for (let i = 0; i < tokens.length; i++) { let token = tokens[i]; if ("+-*/".includes(token)) { // 是个运算符,从栈顶拿出两个数字进行运算,运算结果入栈 let a = stk.pop(), b = stk.pop(); switch (token) { case "+": stk.push(a + b); break; case "*": stk.push(a * b); break; // 对于减法和除法,顺序别搞反了,第二个数是被除(减)数 case "-": stk.push(b - a); break; case "/": stk.push(parseInt(b / a)); break; } } else { // 是个数字,直接入栈即可 stk.push(parseInt(token)); } } // 最后栈中剩下一个数字,即是计算结果 return stk.pop(); }; ``` ```python # by chatGPT (python) class Solution: def evalRPN(self, tokens: List[str]) -> int: stk = [] for token in tokens: if token in "+-*/": # 是个运算符,从栈顶拿出两个数字进行运算,运算结果入栈 a = stk.pop() b = stk.pop() if token == "+": stk.append(a + b) elif token == "-": stk.append(b - a) elif token == "*": stk.append(a * b) else: # token == "/" stk.append(int(b / a)) else: # 是个数字,直接入栈即可 stk.append(int(token)) # 最后栈中剩下一个数字,即是计算结果 return stk.pop() ``` https://leetcode.cn/problems/8Zf90G 的多语言解法👆 https://leetcode.cn/problems/B1IidL 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int peakIndexInMountainArray(vector& nums) { // 取两端都闭的二分搜索 int left = 0, right = nums.size() - 1; // 因为题目必然有解,所以设置 left == right 为结束条件 while (left < right) { int mid = left + (right - left) / 2; if (nums[mid] > nums[mid + 1]) { // mid 本身就是峰值或其左侧有一个峰值 right = mid; } else { // mid 右侧有一个峰值 left = mid + 1; } } return left; } }; ``` ```go // by chatGPT (go) func peakIndexInMountainArray(nums []int) int { // 取两端都闭的二分搜索 left, right := 0, len(nums)-1 // 因为题目必然有解,所以设置 left == right 为结束条件 for left < right { mid := left + (right-left)/2 if nums[mid] > nums[mid+1] { // mid 本身就是峰值或其左侧有一个峰值 right = mid } else { // mid 右侧有一个峰值 left = mid + 1 } } return left } ``` ```java // by labuladong (java) class Solution { public int peakIndexInMountainArray(int[] nums) { // 取两端都闭的二分搜索 int left = 0, right = nums.length - 1; // 因为题目必然有解,所以设置 left == right 为结束条件 while (left < right) { int mid = left + (right - left) / 2; if (nums[mid] > nums[mid + 1]) { // mid 本身就是峰值或其左侧有一个峰值 right = mid; } else { // mid 右侧有一个峰值 left = mid + 1; } } return left; } } ``` ```javascript // by chatGPT (javascript) var peakIndexInMountainArray = function(nums) { // 取两端都闭的二分搜索 let left = 0, right = nums.length - 1; // 因为题目必然有解,所以设置 left == right 为结束条件 while (left < right) { let mid = left + Math.floor((right - left) / 2); if (nums[mid] > nums[mid + 1]) { // mid 本身就是峰值或其左侧有一个峰值 right = mid; } else { // mid 右侧有一个峰值 left = mid + 1; } } return left; }; ``` ```python # by chatGPT (python) class Solution: def peakIndexInMountainArray(self, nums: List[int]) -> int: # 取两端都闭的二分搜索 left, right = 0, len(nums) - 1 # 因为题目必然有解,所以设置 left == right 为结束条件 while left < right: mid = left + (right - left) // 2 if nums[mid] > nums[mid + 1]: # mid 本身就是峰值或其左侧有一个峰值 right = mid else: # mid 右侧有一个峰值 left = mid + 1 return left ``` https://leetcode.cn/problems/B1IidL 的多语言解法👆 https://leetcode.cn/problems/FortPu 的多语言解法👇 ```cpp // by labuladong (cpp) class RandomizedSet { public: // 存储元素的值 vector nums; // 记录每个元素对应在 nums 中的索引 unordered_map valToIndex; bool insert(int val) { // 若 val 已存在,不用再插入 if (valToIndex.count(val)) { return false; } // 若 val 不存在,插入到 nums 尾部, // 并记录 val 对应的索引值 valToIndex[val] = nums.size(); nums.push_back(val); return true; } bool remove(int val) { // 若 val 不存在,不用再删除 if (!valToIndex.count(val)) { return false; } // 先拿到 val 的索引 int index = valToIndex[val]; // 将最后一个元素对应的索引修改为 index valToIndex[nums.back()] = index; // 交换 val 和最后一个元素 swap(nums[index], nums.back()); // 在数组中删除元素 val nums.pop_back(); // 删除元素 val 对应的索引 valToIndex.erase(val); return true; } int getRandom() { // 随机获取 nums 中的一个元素 return nums[rand() % nums.size()]; } }; ``` ```go // by chatGPT (go) // 定义结构体 type RandomizedSet struct { // 存储元素的值 nums []int // 记录每个元素对应在 nums 中的索引 valToIndex map[int]int } // 插入操作 func (r *RandomizedSet) insert(val int) bool { // 若 val 已存在,不用再插入 if _, ok := r.valToIndex[val]; ok { return false } // 若 val 不存在,插入到 nums 尾部, // 并记录 val 对应的索引值 r.valToIndex[val] = len(r.nums) r.nums = append(r.nums, val) return true } // 删除操作 func (r *RandomizedSet) remove(val int) bool { // 若 val 不存在,不用再删除 if _, ok := r.valToIndex[val]; !ok { return false } // 先拿到 val 的索引 index := r.valToIndex[val] // 将最后一个元素对应的索引修改为 index r.valToIndex[r.nums[len(r.nums)-1]] = index // 交换 val 和最后一个元素 r.nums[index], r.nums[len(r.nums)-1] = r.nums[len(r.nums)-1], r.nums[index] // 在数组中删除元素 val r.nums = r.nums[:len(r.nums)-1] // 删除元素 val 对应的索引 delete(r.valToIndex, val) return true } // 随机获取元素 func (r *RandomizedSet) getRandom() int { // 随机获取 nums 中的一个元素 return r.nums[rand.Intn(len(r.nums))] } ``` ```java // by chatGPT (java) class RandomizedSet { // 存储元素的值 List nums; // 记录每个元素对应在 nums 中的索引 Map valToIndex; public boolean insert(int val) { // 若 val 已存在,不用再插入 if (valToIndex.containsKey(val)) { return false; } // 若 val 不存在,插入到 nums 尾部, // 并记录 val 对应的索引值 valToIndex.put(val, nums.size()); nums.add(val); return true; } public boolean remove(int val) { // 若 val 不存在,不用再删除 if (!valToIndex.containsKey(val)) { return false; } // 先拿到 val 的索引 int index = valToIndex.get(val); // 将最后一个元素对应的索引修改为 index valToIndex.put(nums.get(nums.size()-1), index); // 交换 val 和最后一个元素 Collections.swap(nums, index, nums.size()-1); // 在数组中删除元素 val nums.remove(nums.size()-1); // 删除元素 val 对应的索引 valToIndex.remove(val); return true; } public int getRandom() { // 随机获取 nums 中的一个元素 return nums.get((int)(Math.random() * nums.size())); } } ``` ```javascript // by chatGPT (javascript) /** * Initialize your data structure here. */ var RandomizedSet = function() { // 存储元素的值 this.nums = []; // 记录每个元素对应在 nums 中的索引 this.valToIndex = new Map(); }; /** * Inserts a value to the set. Returns true if the set did not already contain the specified element. * @param {number} val * @return {boolean} */ RandomizedSet.prototype.insert = function(val) { // 若 val 已存在,不用再插入 if (this.valToIndex.has(val)) { return false; } // 若 val 不存在,插入到 nums 尾部, // 并记录 val 对应的索引值 this.valToIndex.set(val, this.nums.length); this.nums.push(val); return true; }; /** * Removes a value from the set. Returns true if the set contained the specified element. * @param {number} val * @return {boolean} */ RandomizedSet.prototype.remove = function(val) { // 若 val 不存在,不用再删除 if (!this.valToIndex.has(val)) { return false; } // 先拿到 val 的索引 const index = this.valToIndex.get(val); // 将最后一个元素对应的索引修改为 index this.valToIndex.set(this.nums[this.nums.length - 1], index); // 交换 val 和最后一个元素 [this.nums[index], this.nums[this.nums.length - 1]] = [this.nums[this.nums.length - 1], this.nums[index]]; // 在数组中删除元素 val this.nums.pop(); // 删除元素 val 对应的索引 this.valToIndex.delete(val); return true; }; /** * Get a random element from the set. * @return {number} */ RandomizedSet.prototype.getRandom = function() { // 随机获取 nums 中的一个元素 return this.nums[Math.floor(Math.random() * this.nums.length)]; }; ``` ```python # by chatGPT (python) class RandomizedSet: def __init__(self): # 存储元素的值 self.nums = [] # 记录每个元素对应在 nums 中的索引 self.valToIndex = {} def insert(self, val: int) -> bool: # 若 val 已存在,不用再插入 if val in self.valToIndex: return False # 若 val 不存在,插入到 nums 尾部, # 并记录 val 对应的索引值 self.valToIndex[val] = len(self.nums) self.nums.append(val) return True def remove(self, val: int) -> bool: # 若 val 不存在,不用再删除 if val not in self.valToIndex: return False # 先拿到 val 的索引 index = self.valToIndex[val] # 将最后一个元素对应的索引修改为 index self.valToIndex[self.nums[-1]] = index # 交换 val 和最后一个元素 self.nums[index], self.nums[-1] = self.nums[-1], self.nums[index] # 在数组中删除元素 val self.nums.pop() # 删除元素 val 对应的索引 self.valToIndex.pop(val) return True def getRandom(self) -> int: # 随机获取 nums 中的一个元素 return self.nums[random.randint(0, len(self.nums) - 1)] ``` https://leetcode.cn/problems/FortPu 的多语言解法👆 https://leetcode.cn/problems/Gu0c2T 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { private: // 备忘录 vector memo; // dp 函数 int dp(vector& nums, int start) { if (start >= nums.size()) { return 0; } // 避免重复计算 if (memo[start] != -1) { return memo[start]; } int res = max(dp(nums, start + 1), nums[start] + dp(nums, start + 2)); // 记入备忘录 memo[start] = res; return res; } public: // 主函数 int rob(vector& nums) { // 初始化备忘录 memo.resize(nums.size(), -1); // 强盗从第 0 间房子开始抢劫 return dp(nums, 0); } }; ``` ```go // by chatGPT (go) import ( "fmt" ) func rob(nums []int) int { // 初始化备忘录 memo := make([]int, len(nums)) for i := 0; i < len(memo); i++ { memo[i] = -1 } // 强盗从第 0 间房子开始抢劫 return dp(nums, 0, memo) } // 返回 dp[start..] 能抢到的最大值 func dp(nums []int, start int, memo []int) int { if start >= len(nums) { return 0 } // 避免重复计算 if memo[start] != -1 { return memo[start] } res := max(dp(nums, start+1, memo), nums[start]+dp(nums, start+2, memo)) // 记入备忘录 memo[start] = res return res } func max(x, y int) int { if x > y { return x } return y } ``` ```java // by labuladong (java) class Solution { // 备忘录 private int[] memo; // 主函数 public int rob(int[] nums) { // 初始化备忘录 memo = new int[nums.length]; Arrays.fill(memo, -1); // 强盗从第 0 间房子开始抢劫 return dp(nums, 0); } // 返回 dp[start..] 能抢到的最大值 private int dp(int[] nums, int start) { if (start >= nums.length) { return 0; } // 避免重复计算 if (memo[start] != -1) return memo[start]; int res = Math.max(dp(nums, start + 1), nums[start] + dp(nums, start + 2)); // 记入备忘录 memo[start] = res; return res; } } ``` ```javascript // by chatGPT (javascript) var rob = function(nums) { // 备忘录 const memo = new Array(nums.length).fill(-1); // 返回 dp[start..] 能抢到的最大值 const dp = (start) => { if (start >= nums.length) { return 0; } // 避免重复计算 if (memo[start] != -1) return memo[start]; const res = Math.max(dp(start + 1), nums[start] + dp(start + 2)); // 记入备忘录 memo[start] = res; return res; } // 强盗从第 0 间房子开始抢劫 return dp(0); }; ``` ```python # by chatGPT (python) class Solution: def __init__(self): # 备忘录 self.memo = None # 主函数 def rob(self, nums: List[int]) -> int: # 初始化备忘录 self.memo = [-1] * len(nums) # 强盗从第 0 间房子开始抢劫 return self.dp(nums, 0) # 返回 dp[start..] 能抢到的最大值 def dp(self, nums: List[int], start: int) -> int: if start >= len(nums): return 0 # 避免重复计算 if self.memo[start] != -1: return self.memo[start] res = max(self.dp(nums, start + 1), nums[start] + self.dp(nums, start + 2)) # 记入备忘录 self.memo[start] = res return res ``` https://leetcode.cn/problems/Gu0c2T 的多语言解法👆 https://leetcode.cn/problems/IDBivT 的多语言解法👇 ```cpp // by labuladong (cpp) class Solution { public: vector generateParenthesis(int n) { if (n == 0) return {}; // 记录所有合法的括号组合 vector res; // 回溯过程中的路径 string track; // 可用的左括号和右括号数量初始化为 n backtrack(n, n, track, res); return res; } // 可用的左括号数量为 left 个,可用的右括号数量为 rgiht 个 void backtrack(int left, int right, string& track, vector& res) { // 若左括号剩下的多,说明不合法 if (right < left) return; // 数量小于 0 肯定是不合法的 if (left < 0 || right < 0) return; // 当所有括号都恰好用完时,得到一个合法的括号组合 if (left == 0 && right == 0) { res.push_back(track); return; } // 尝试放一个左括号 track.push_back('('); // 选择 backtrack(left - 1, right, track, res); track.pop_back(); // 撤消选择 // 尝试放一个右括号 track.push_back(')'); // 选择 backtrack(left, right - 1, track, res); track.pop_back(); // 撤消选择 } } ``` ```go // by chatGPT (go) func generateParenthesis(n int) []string { if n == 0 { return []string{} } // 记录所有合法的括号组合 var res []string // 回溯过程中的路径 var track string // 可用的左括号和右括号数量初始化为 n backtrack(n, n, &track, &res) return res } // 可用的左括号数量为 left 个,可用的右括号数量为 right 个 func backtrack(left, right int, track *string, res *[]string) { // 若左括号剩下的多,说明不合法 if right < left { return } // 数量小于 0 肯定是不合法的 if left < 0 || right < 0 { return } // 当所有括号都恰好用完时,得到一个合法的括号组合 if left == 0 && right == 0 { *res = append(*res, *track) return } // 尝试放一个左括号 *track += "(" // 选择 backtrack(left-1, right, track, res) *track = (*track)[:len(*track)-1] // 撤消选择 // 尝试放一个右括号 *track += ")" // 选择 backtrack(left, right-1, track, res) *track = (*track)[:len(*track)-1] // 撤消选择 } ``` ```java // by chatGPT (java) class Solution { public List generateParenthesis(int n) { if (n == 0) return new ArrayList<>(); // 记录所有合法的括号组合 List res = new ArrayList<>(); // 回溯过程中的路径 StringBuilder track = new StringBuilder(); // 可用的左括号和右括号数量初始化为 n backtrack(n, n, track, res); return res; } // 可用的左括号数量为 left 个,可用的右括号数量为 right 个 void backtrack(int left, int right, StringBuilder track, List res) { // 若左括号剩下的多,说明不合法 if (right < left) return; // 数量小于 0 肯定是不合法的 if (left < 0 || right < 0) return; // 当所有括号都恰好用完时,得到一个合法的括号组合 if (left == 0 && right == 0) { res.add(track.toString()); return; } // 尝试放一个左括号 track.append('('); // 选择 backtrack(left - 1, right, track, res); track.deleteCharAt(track.length() - 1); // 撤消选择 // 尝试放一个右括号 track.append(')'); // 选择 backtrack(left, right - 1, track, res); track.deleteCharAt(track.length() - 1); // 撤消选择 } } ``` ```javascript // by chatGPT (javascript) var generateParenthesis = function(n) { if (n === 0) return []; // 记录所有合法的括号组合 var res = []; // 回溯过程中的路径 var track = ""; // 可用的左括号和右括号数量初始化为 n backtrack(n, n, track, res); return res; }; // 可用的左括号数量为 left 个,可用的右括号数量为 rgiht 个 var backtrack = function(left, right, track, res) { // 若左括号剩下的多,说明不合法 if (right < left) return; // 数量小于 0 肯定是不合法的 if (left < 0 || right < 0) return; // 当所有括号都恰好用完时,得到一个合法的括号组合 if (left === 0 && right === 0) { res.push(track); return; } // 尝试放一个左括号 track += '('; // 选择 backtrack(left - 1, right, track, res); track = track.substring(0, track.length - 1); // 撤消选择 // 尝试放一个右括号 track += ')'; // 选择 backtrack(left, right - 1, track, res); track = track.substring(0, track.length - 1); // 撤消选择 }; ``` ```python # by chatGPT (python) class Solution: def generateParenthesis(self, n: int) -> List[str]: if n == 0: return [] # 记录所有合法的括号组合 res = [] # 回溯过程中的路径 track = "" # 可用的左括号和右括号数量初始化为 n self.backtrack(n, n, track, res) return res # 可用的左括号数量为 left 个,可用的右括号数量为 rgiht 个 def backtrack(self, left: int, right: int, track: str, res: List[str]) -> None: # 若右括号剩下的多,说明不合法 if right < left: return # 数量小于 0 肯定是不合法的 if left < 0 or right < 0: return # 当所有括号都恰好用完时,得到一个合法的括号组合 if left == 0 and right == 0: res.append(track) return # 尝试放一个左括号 track += '(' # 选择 self.backtrack(left - 1, right, track, res) track = track[:-1] # 撤消选择 # 尝试放一个右括号 track += ')' # 选择 self.backtrack(left, right - 1, track, res) track = track[:-1] # 撤消选择 ``` https://leetcode.cn/problems/IDBivT 的多语言解法👆 https://leetcode.cn/problems/M1oyTv 的多语言解法👇 ```cpp // by labuladong (cpp) class Solution { public: string minWindow(string s, string t) { unordered_map need, window; for (char c : t) need[c]++; int left = 0, right = 0; int valid = 0; // 记录最小覆盖子串的起始索引及长度 int start = 0, len = INT_MAX; /** ![](../pictures/slidingwindow/1.png) */ while (right < s.size()) { // c 是将移入窗口的字符 char c = s[right]; // 右移窗口 right++; // 进行窗口内数据的一系列更新 if (need.count(c)) { window[c]++; if (window[c] == need[c]) valid++; } // 判断左侧窗口是否要收缩 while (valid == need.size()) { /** ![](../pictures/slidingwindow/2.png) */ // 在这里更新最小覆盖子串 if (right - left < len) { start = left; len = right - left; } // d 是将移出窗口的字符 char d = s[left]; // 左移窗口 left++; // 进行窗口内数据的一系列更新 if (need.count(d)) { if (window[d] == need[d]) valid--; window[d]--; } } /** ![](../pictures/slidingwindow/4.png) */ } // 返回最小覆盖子串 return len == INT_MAX ? "" : s.substr(start, len); } }; ``` ```go // by chatGPT (go) func minWindow(s string, t string) string { need := make(map[byte]int) window := make(map[byte]int) for i := 0; i < len(t); i++ { need[t[i]]++ } left, right, valid := 0, 0, 0 // 记录最小覆盖子串的起始索引及长度 start, len := 0, math.MaxInt32 /** ![](../pictures/slidingwindow/1.png) */ for right < len(s) { // c 是将移入窗口的字符 c := s[right] // 右移窗口 right++ // 进行窗口内数据的一系列更新 if _, ok := need[c]; ok { window[c]++ if window[c] == need[c] { valid++ } } // 判断左侧窗口是否要收缩 for valid == len(need) { /** ![](../pictures/slidingwindow/2.png) */ // 在这里更新最小覆盖子串 if right-left < len { start = left len = right - left } // d 是将移出窗口的字符 d := s[left] // 左移窗口 left++ // 进行窗口内数据的一系列更新 if _, ok := need[d]; ok { if window[d] == need[d] { valid-- } window[d]-- } } /** ![](../pictures/slidingwindow/4.png) */ } // 返回最小覆盖子串 if len == math.MaxInt32 { return "" } return s[start : start+len] } ``` ```java // by chatGPT (java) class Solution { public String minWindow(String s, String t) { HashMap need = new HashMap<>(); HashMap window = new HashMap<>(); for (char c : t.toCharArray()) { need.put(c, need.getOrDefault(c, 0) + 1); } int left = 0, right = 0; int valid = 0; // 记录最小覆盖子串的起始索引及长度 int start = 0, len = Integer.MAX_VALUE; /** ![](../pictures/slidingwindow/1.png) */ 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()) { /** ![](../pictures/slidingwindow/2.png) */ // 在这里更新最小覆盖子串 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); } } /** ![](../pictures/slidingwindow/4.png) */ } // 返回最小覆盖子串 return len == Integer.MAX_VALUE ? "" : s.substring(start, start + len); } } ``` ```javascript // by chatGPT (javascript) var minWindow = function(s, t) { var need = new Map(); var window = new Map(); for (var c of t) { need.set(c, need.has(c) ? need.get(c) + 1 : 1); } var left = 0, right = 0; var valid = 0; // 记录最小覆盖子串的起始索引及长度 var start = 0, len = Number.MAX_SAFE_INTEGER; /** ![](../pictures/slidingwindow/1.png) */ while (right < s.length) { // c 是将移入窗口的字符 var c = s[right]; // 右移窗口 right++; // 进行窗口内数据的一系列更新 if (need.has(c)) { window.set(c, window.has(c) ? window.get(c) + 1 : 1); if (window.get(c) === need.get(c)) valid++; } // 判断左侧窗口是否要收缩 while (valid === need.size) { /** ![](../pictures/slidingwindow/2.png) */ // 在这里更新最小覆盖子串 if (right - left < len) { start = left; len = right - left; } // d 是将移出窗口的字符 var d = s[left]; // 左移窗口 left++; // 进行窗口内数据的一系列更新 if (need.has(d)) { if (window.get(d) === need.get(d)) valid--; window.set(d, window.get(d) - 1); } } /** ![](../pictures/slidingwindow/4.png) */ } // 返回最小覆盖子串 return len === Number.MAX_SAFE_INTEGER ? "" : s.substring(start, start + len); }; ``` ```python # by chatGPT (python) class Solution: def minWindow(self, s: str, t: str) -> str: from collections import defaultdict need, window = defaultdict(int), defaultdict(int) for c in t: need[c] += 1 left, right = 0, 0 valid = 0 # 记录最小覆盖子串的起始索引及长度 start, length = 0, float('inf') # ![](../pictures/slidingwindow/1.png) while right < len(s): # c 是将移入窗口的字符 c = s[right] # 右移窗口 right += 1 # 进行窗口内数据的一系列更新 if c in need: window[c] += 1 if window[c] == need[c]: valid += 1 # 判断左侧窗口是否要收缩 while valid == len(need): # ![](../pictures/slidingwindow/2.png) # 在这里更新最小覆盖子串 if right - left < length: start = left length = right - left # d 是将移出窗口的字符 d = s[left] # 左移窗口 left += 1 # 进行窗口内数据的一系列更新 if d in need: if window[d] == need[d]: valid -= 1 window[d] -= 1 # ![](../pictures/slidingwindow/4.png) # 返回最小覆盖子串 return '' if length == float('inf') else s[start:start+length] ``` https://leetcode.cn/problems/M1oyTv 的多语言解法👆 https://leetcode.cn/problems/MPnaiL 的多语言解法👇 ```cpp // by labuladong (cpp) class Solution { public: // 判断 s 中是否存在 t 的排列 bool checkInclusion(string t, string s) { unordered_map need, window; for (char c : t) need[c]++; int left = 0, right = 0; int valid = 0; while (right < s.size()) { char c = s[right]; right++; // 进行窗口内数据的一系列更新 if (need.count(c)) { window[c]++; if (window[c] == need[c]) valid++; } // 判断左侧窗口是否要收缩 while (right - left >= t.size()) { // 在这里判断是否找到了合法的子串 if (valid == need.size()) return true; char d = s[left]; left++; // 进行窗口内数据的一系列更新 if (need.count(d)) { if (window[d] == need[d]) valid--; window[d]--; } } } // 未找到符合条件的子串 return false; } }; ``` ```go // by chatGPT (go) func checkInclusion(t string, s string) bool { need := make(map[byte]int) window := make(map[byte]int) for i := range t { need[t[i]]++ } left, right := 0, 0 valid := 0 for right < len(s) { c := s[right] right++ // 进行窗口内数据的一系列更新 if _, ok := need[c]; ok { window[c]++ if window[c] == need[c] { valid++ } } // 判断左侧窗口是否要收缩 for right-left >= len(t) { // 在这里判断是否找到了合法的子串 if valid == len(need) { return true } d := s[left] left++ // 进行窗口内数据的一系列更新 if _, ok := need[d]; ok { if window[d] == need[d] { valid-- } window[d]-- } } } // 未找到符合条件的子串 return false } ``` ```java // by chatGPT (java) 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; } } ``` ```javascript // by chatGPT (javascript) var checkInclusion = function(t, s) { const need = new Map(); const window = new Map(); for (let c of t) { need.set(c, (need.get(c) || 0) + 1); } let left = 0, right = 0; let valid = 0; while (right < s.length) { const c = s[right]; right++; // 进行窗口内数据的一系列更新 if (need.has(c)) { window.set(c, (window.get(c) || 0) + 1); if (window.get(c) === need.get(c)) { valid++; } } // 判断左侧窗口是否要收缩 while (right - left >= t.length) { // 在这里判断是否找到了合法的子串 if (valid === need.size) { return true; } const d = s[left]; left++; // 进行窗口内数据的一系列更新 if (need.has(d)) { if (window.get(d) === need.get(d)) { valid--; } window.set(d, (window.get(d) || 0) - 1); } } } // 未找到符合条件的子串 return false; }; ``` ```python # by chatGPT (python) class Solution: def checkInclusion(self, t: str, s: str) -> bool: need, window = {}, {} for c in t: need[c] = need.get(c, 0) + 1 left, right = 0, 0 valid = 0 while right < len(s): c = s[right] right += 1 # 进行窗口内数据的一系列更新 if c in need: window[c] = window.get(c, 0) + 1 if window[c] == need[c]: valid += 1 # 判断左侧窗口是否要收缩 while right - left >= len(t): # 在这里判断是否找到了合法的子串 if valid == len(need): return True d = s[left] left += 1 # 进行窗口内数据的一系列更新 if d in need: if window[d] == need[d]: valid -= 1 window[d] -= 1 # 未找到符合条件的子串 return False ``` https://leetcode.cn/problems/MPnaiL 的多语言解法👆 https://leetcode.cn/problems/N6YdxV 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int searchInsert(vector& nums, int target) { return left_bound(nums, target); } // 搜索左侧边界的二分算法 int left_bound(vector& nums, int target) { if (nums.size() == 0) return -1; int left = 0; int right = nums.size(); // 注意 while (left < right) { // 注意 int mid = left + (right - left) / 2; if (nums[mid] == target) { right = mid; } else if (nums[mid] < target) { left = mid + 1; } else if (nums[mid] > target) { right = mid; // 注意 } } return left; } }; ``` ```go // by chatGPT (go) func searchInsert(nums []int, target int) int { return leftBound(nums, target) } //搜索左侧边界的二分算法 func leftBound(nums []int, target int) int { if len(nums) == 0 { return -1 } left, right := 0, len(nums) for left < right { mid := left + (right - left)/2 if nums[mid] == target { right = mid } else if nums[mid] < target { left = mid + 1 } else if nums[mid] > target { right = mid } } return left } ``` ```java // by labuladong (java) class Solution { public int searchInsert(int[] nums, int target) { return left_bound(nums, target); } // 搜索左侧边界的二分算法 int left_bound(int[] nums, int target) { if (nums.length == 0) return -1; int left = 0; int right = nums.length; // 注意 while (left < right) { // 注意 int mid = left + (right - left) / 2; if (nums[mid] == target) { right = mid; } else if (nums[mid] < target) { left = mid + 1; } else if (nums[mid] > target) { right = mid; // 注意 } } return left; } } ``` ```javascript // by chatGPT (javascript) var Solution = function() {}; Solution.prototype.searchInsert = function(nums, target) { return this.left_bound(nums, target); }; // 搜索左侧边界的二分算法 Solution.prototype.left_bound = function(nums, target) { if (nums.length == 0) return -1; var left = 0; var right = nums.length; // 注意 while (left < right) { // 注意 var mid = left + Math.floor((right - left) / 2); if (nums[mid] == target) { right = mid; } else if (nums[mid] < target) { left = mid + 1; } else if (nums[mid] > target) { right = mid; // 注意 } } return left; }; ``` ```python # by chatGPT (python) class Solution: def searchInsert(self, nums: List[int], target: int) -> int: return self.left_bound(nums, target) # 搜索左侧边界的二分算法 def left_bound(self, nums: List[int], target: int) -> int: if len(nums) == 0: return -1 left, right = 0, len(nums) # 注意 while left < right: # 注意 mid = left + (right - left) // 2 if nums[mid] == target: right = mid elif nums[mid] < target: left = mid + 1 elif nums[mid] > target: right = mid # 注意 return left ``` https://leetcode.cn/problems/N6YdxV 的多语言解法👆 https://leetcode.cn/problems/NUPfPr 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: bool canPartition(vector& nums) { int sum = 0; for (int num : nums) sum += num; // 和为奇数时,不可能划分成两个和相等的集合 if (sum % 2 != 0) return false; int n = nums.size(); sum = sum / 2; vector> dp(n + 1, vector(sum + 1, false)); // base case for (int i = 0; i <= n; i++) dp[i][0] = true; for (int i = 1; i <= n; i++) { for (int j = 1; j <= sum; j++) { if (j - nums[i - 1] < 0) { // 背包容量不足,不能装入第 i 个物品 dp[i][j] = dp[i - 1][j]; } else { // 装入或不装入背包 dp[i][j] = dp[i - 1][j] || dp[i - 1][j - nums[i - 1]]; } } } return dp[n][sum]; } }; ``` ```go // by chatGPT (go) func canPartition(nums []int) bool { sum := 0 for _, num := range nums { sum += num } // 和为奇数时,不可能划分成两个和相等的集合 if sum%2 != 0 { return false } n := len(nums) sum = sum / 2 dp := make([][]bool, n+1) for i := 0; i <= n; i++ { dp[i] = make([]bool, sum+1) // base case dp[i][0] = true } for i := 1; i <= n; i++ { for j := 1; j <= sum; j++ { if j-nums[i-1] < 0 { // 背包容量不足,不能装入第 i 个物品 dp[i][j] = dp[i-1][j] } else { // 装入或不装入背包 dp[i][j] = dp[i-1][j] || dp[i-1][j-nums[i-1]] } } } return dp[n][sum] } ``` ```java // by labuladong (java) class Solution { public boolean canPartition(int[] nums) { int sum = 0; for (int num : nums) sum += num; // 和为奇数时,不可能划分成两个和相等的集合 if (sum % 2 != 0) return false; int n = nums.length; sum = sum / 2; boolean[][] dp = new boolean[n + 1][sum + 1]; // base case for (int i = 0; i <= n; i++) dp[i][0] = true; for (int i = 1; i <= n; i++) { for (int j = 1; j <= sum; j++) { if (j - nums[i - 1] < 0) { // 背包容量不足,不能装入第 i 个物品 dp[i][j] = dp[i - 1][j]; } else { // 装入或不装入背包 dp[i][j] = dp[i - 1][j] || dp[i - 1][j - nums[i - 1]]; } } } return dp[n][sum]; } } ``` ```javascript // by chatGPT (javascript) var canPartition = function(nums) { let sum = 0; for (let num of nums) { sum += num; } // 和为奇数时,不可能划分成两个和相等的集合 if (sum % 2 !== 0) { return false; } let n = nums.length; sum = sum / 2; let dp = new Array(n + 1).fill(false).map(() => new Array(sum + 1).fill(false)); // base case for (let i = 0; i <= n; i++) { dp[i][0] = true; } for (let i = 1; i <= n; i++) { for (let j = 1; j <= sum; j++) { if (j - nums[i - 1] < 0) { // 背包容量不足,不能装入第 i 个物品 dp[i][j] = dp[i - 1][j]; } else { // 装入或不装入背包 dp[i][j] = dp[i - 1][j] || dp[i - 1][j - nums[i - 1]]; } } } return dp[n][sum]; }; ``` ```python # by chatGPT (python) class Solution: def canPartition(self, nums: List[int]) -> bool: sum = 0 for num in nums: sum += num # 和为奇数时,不可能划分成两个和相等的集合 if sum % 2 != 0: return False n = len(nums) sum = sum // 2 dp = [[False] * (sum + 1) for _ in range(n + 1)] # base case for i in range(n + 1): dp[i][0] = True for i in range(1, n + 1): for j in range(1, sum + 1): if j - nums[i - 1] < 0: # 背包容量不足,不能装入第 i 个物品 dp[i][j] = dp[i - 1][j] else: # 装入或不装入背包 dp[i][j] = dp[i - 1][j] or dp[i - 1][j - nums[i - 1]] return dp[n][sum] ``` https://leetcode.cn/problems/NUPfPr 的多语言解法👆 https://leetcode.cn/problems/O4NDxx 的多语言解法👇 ```cpp // by chatGPT (cpp) class NumMatrix { private: // preSum[i][j] 记录矩阵 [0, 0, i, j] 的元素和 vector> preSum; public: NumMatrix(vector>& matrix) { int m = matrix.size(), n = matrix[0].size(); if (m == 0 || n == 0) return; // 构造前缀和矩阵 preSum = vector>(m + 1, vector(n + 1, 0)); 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] 的元素和 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]; } }; ``` ```go // by chatGPT (go) type NumMatrix struct { // preSum[i][j] 记录矩阵 [0, 0, i, j] 的元素和 preSum [][]int } func Constructor(matrix [][]int) NumMatrix { m, n := len(matrix), len(matrix[0]) if m == 0 || n == 0 { return NumMatrix{} } // 构造前缀和矩阵 preSum := make([][]int, m+1) for i := range preSum { preSum[i] = make([]int, n+1) } for i := 1; i <= m; i++ { for 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] } } return NumMatrix{preSum} } // 计算子矩阵 [x1, y1, x2, y2] 的元素和 func (this *NumMatrix) SumRegion(x1 int, y1 int, x2 int, y2 int) int { // 目标矩阵之和由四个相邻矩阵运算获得 return this.preSum[x2+1][y2+1] - this.preSum[x1][y2+1] - this.preSum[x2+1][y1] + this.preSum[x1][y1] } ``` ```java // by labuladong (java) 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]; } } ``` ```javascript // by chatGPT (javascript) var NumMatrix = function(matrix) { // preSum[i][j] 记录矩阵 [0, 0, i, j] 的元素和 var preSum; var m = matrix.length, n = matrix[0].length; if (m == 0 || n == 0) return; // 构造前缀和矩阵 preSum = new Array(m + 1); for (var i = 0; i <= m; i++) { preSum[i] = new Array(n + 1).fill(0); } for (var i = 1; i <= m; i++) { for (var 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] 的元素和 this.sumRegion = function(x1, y1, x2, y2) { // 目标矩阵之和由四个相邻矩阵运算获得 return preSum[x2+1][y2+1] - preSum[x1][y2+1] - preSum[x2+1][y1] + preSum[x1][y1]; } }; ``` ```python # by chatGPT (python) class NumMatrix: def __init__(self, matrix: List[List[int]]): m, n = len(matrix), len(matrix[0]) if m == 0 or n == 0: return # 构造前缀和矩阵 self.preSum = [[0] * (n + 1) for _ in range(m + 1)] for i in range(1, m + 1): for j in range(1, n + 1): # 计算每个矩阵 [0, 0, i, j] 的元素和 self.preSum[i][j] = self.preSum[i - 1][j] + self.preSum[i][j - 1] + matrix[i - 1][j - 1] - self.preSum[i - 1][j - 1] # 计算子矩阵 [x1, y1, x2, y2] 的元素和 def sumRegion(self, x1: int, y1: int, x2: int, y2: int) -> int: # 目标矩阵之和由四个相邻矩阵运算获得 return self.preSum[x2 + 1][y2 + 1] - self.preSum[x1][y2 + 1] - self.preSum[x2 + 1][y1] + self.preSum[x1][y1] ``` https://leetcode.cn/problems/O4NDxx 的多语言解法👆 https://leetcode.cn/problems/OrIXps 的多语言解法👇 ```cpp // by chatGPT (cpp) class LRUCache { int cap; unordered_map cache; list keys; public: LRUCache(int capacity) { this->cap = capacity; } int get(int key) { auto it = cache.find(key); if (it == cache.end()) { return -1; } // 将 key 变为最近使用 makeRecently(key); return it->second; } void put(int key, int val) { auto it = cache.find(key); if (it != cache.end()) { // 修改 key 的值 it->second = val; // 将 key 变为最近使用 makeRecently(key); return; } if (cache.size() >= this->cap) { // 链表头部就是最久未使用的 key int oldestKey = keys.front(); keys.pop_front(); cache.erase(oldestKey); } // 将新的 key 添加链表尾部 keys.push_back(key); cache[key] = val; } private: void makeRecently(int key) { int val = cache[key]; // 删除 key,重新插入到队尾 keys.remove(key); keys.push_back(key); cache[key] = val; } }; ``` ```go // by chatGPT (go) type LRUCache struct { cap int cache map[int]int } // Constructor 创建一个 LRU Cache 实例 func Constructor(capacity int) LRUCache { return LRUCache{ cap: capacity, cache: make(map[int]int), } } // Get 获取一个 key 的值 func (this *LRUCache) Get(key int) int { if val, ok := this.cache[key]; ok { this.makeRecently(key) return val } return -1 } // Put 插入一个 key/value func (this *LRUCache) Put(key int, value int) { if _, ok := this.cache[key]; ok { this.cache[key] = value this.makeRecently(key) return } if len(this.cache) >= this.cap { this.removeLeastRecently() } this.cache[key] = value } // makeRecently 将一个元素标记为最近使用的 func (this *LRUCache) makeRecently(key int) { val := this.cache[key] delete(this.cache, key) this.cache[key] = val } // removeLeastRecently 移除最近未使用的元素 func (this *LRUCache) removeLeastRecently() { for k := range this.cache { delete(this.cache, k) break } } ``` ```java // by labuladong (java) class LRUCache { int cap; LinkedHashMap cache = new LinkedHashMap<>(); public LRUCache(int capacity) { this.cap = capacity; } public int get(int key) { if (!cache.containsKey(key)) { return -1; } // 将 key 变为最近使用 makeRecently(key); return cache.get(key); } public void put(int key, int val) { if (cache.containsKey(key)) { // 修改 key 的值 cache.put(key, val); // 将 key 变为最近使用 makeRecently(key); return; } if (cache.size() >= this.cap) { // 链表头部就是最久未使用的 key int oldestKey = cache.keySet().iterator().next(); cache.remove(oldestKey); } // 将新的 key 添加链表尾部 cache.put(key, val); } private void makeRecently(int key) { int val = cache.get(key); // 删除 key,重新插入到队尾 cache.remove(key); cache.put(key, val); } } ``` ```javascript // by chatGPT (javascript) var LRUCache = function(capacity) { this.cap = capacity; this.cache = new Map(); }; LRUCache.prototype.get = function(key) { if (!this.cache.has(key)) { return -1; } // 将 key 变为最近使用 this.makeRecently(key); return this.cache.get(key); }; LRUCache.prototype.put = function(key, val) { if (this.cache.has(key)) { // 修改 key 的值 this.cache.set(key, val); // 将 key 变为最近使用 this.makeRecently(key); return; } if (this.cache.size >= this.cap) { // 链表头部就是最久未使用的 key const oldestKey = this.cache.keys().next().value; this.cache.delete(oldestKey); } // 将新的 key 添加链表尾部 this.cache.set(key, val); }; LRUCache.prototype.makeRecently = function(key) { const val = this.cache.get(key); // 删除 key,重新插入到队尾 this.cache.delete(key); this.cache.set(key, val); }; ``` ```python # by chatGPT (python) class LRUCache: def __init__(self, capacity: int): self.cap = capacity self.cache = {} def get(self, key: int) -> int: if key not in self.cache: return -1 # 将 key 变为最近使用 self.makeRecently(key) return self.cache[key] def put(self, key: int, val: int) -> None: if key in self.cache: # 修改 key 的值 self.cache[key] = val # 将 key 变为最近使用 self.makeRecently(key) return if len(self.cache) >= self.cap: # 链表头部就是最久未使用的 key oldest_key = next(iter(self.cache)) self.cache.pop(oldest_key) # 将新的 key 添加链表尾部 self.cache[key] = val def makeRecently(self, key: int) -> None: val = self.cache[key] # 删除 key,重新插入到队尾 del self.cache[key] self.cache[key] = val ``` https://leetcode.cn/problems/OrIXps 的多语言解法👆 https://leetcode.cn/problems/PzWKhm 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int rob(vector& nums) { int n = nums.size(); if (n == 1) return nums[0]; vector memo1(n, -1); vector memo2(n, -1); // 两次调用使用两个不同的备忘录 return max( dp(nums, 0, n - 2, memo1), dp(nums, 1, n - 1, memo2) ); } // 定义:计算闭区间 [start,end] 的最优结果 int dp(vector& nums, int start, int end, vector& memo) { if (start > end) { return 0; } if (memo[start] != -1) { return memo[start]; } // 状态转移方程 int res = max( dp(nums, start + 2, end, memo) + nums[start], dp(nums, start + 1, end, memo) ); memo[start] = res; return res; } }; ``` ```go // by chatGPT (go) import "fmt" func rob(nums []int) int { n := len(nums) if n == 1 { return nums[0] } memo1 := make([]int, n) memo2 := make([]int, n) for i := range memo1 { memo1[i] = -1 memo2[i] = -1 } // 两次调用使用两个不同的备忘录 return max(dp(nums, 0, n - 2, memo1), dp(nums, 1, n - 1, memo2)) } // 定义:计算闭区间 [start,end] 的最优结果 func dp(nums []int, start, end int, memo []int) int { if start > end { return 0 } if memo[start] != -1 { return memo[start] } // 状态转移方程 res := max(dp(nums, start + 2, end, memo) + nums[start], dp(nums, start + 1, end, memo)) memo[start] = res return res } func max(x, y int) int { if x > y { return x } return y } ``` ```java // by labuladong (java) class Solution { public int rob(int[] nums) { int n = nums.length; if (n == 1) return nums[0]; int[] memo1 = new int[n]; int[] memo2 = new int[n]; Arrays.fill(memo1, -1); Arrays.fill(memo2, -1); // 两次调用使用两个不同的备忘录 return Math.max( dp(nums, 0, n - 2, memo1), dp(nums, 1, n - 1, memo2) ); } // 定义:计算闭区间 [start,end] 的最优结果 int dp(int[] nums, int start, int end, int[] memo) { if (start > end) { return 0; } if (memo[start] != -1) { return memo[start]; } // 状态转移方程 int res = Math.max( dp(nums, start + 2, end, memo) + nums[start], dp(nums, start + 1, end, memo) ); memo[start] = res; return res; } } ``` ```javascript // by chatGPT (javascript) var rob = function(nums) { const n = nums.length; if (n === 1) return nums[0]; const memo1 = new Array(n).fill(-1); const memo2 = new Array(n).fill(-1); // 两次调用使用两个不同的备忘录 return Math.max( dp(nums, 0, n - 2, memo1), dp(nums, 1, n - 1, memo2) ); }; // 定义:计算闭区间 [start,end] 的最优结果 function dp(nums, start, end, memo) { if (start > end) { return 0; } if (memo[start] !== -1) { return memo[start]; } // 状态转移方程 const res = Math.max( dp(nums, start + 2, end, memo) + nums[start], dp(nums, start + 1, end, memo) ); memo[start] = res; return res; } ``` ```python # by chatGPT (python) class Solution: def rob(self, nums: List[int]) -> int: n = len(nums) if n == 1: return nums[0] memo1 = [-1]*n memo2 = [-1]*n # 两次调用使用两个不同的备忘录 return max( self.dp(nums, 0, n-2, memo1), self.dp(nums, 1, n-1, memo2) ) # 定义:计算闭区间 [start,end] 的最优结果 def dp(self, nums: List[int], start: int, end: int, memo: List[int]) -> int: if start > end: return 0 if memo[start] != -1: return memo[start] # 状态转移方程 res = max( self.dp(nums, start+2, end, memo) + nums[start], self.dp(nums, start+1, end, memo) ) memo[start] = res return res ``` https://leetcode.cn/problems/PzWKhm 的多语言解法👆 https://leetcode.cn/problems/QA2IGt 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: vector findOrder(int numCourses, vector>& prerequisites) { // 建图,和环检测算法相同 vector> graph(numCourses); for (auto& edge : prerequisites) { int from = edge[1], to = edge[0]; graph[from].push_back(to); } // 计算入度,和环检测算法相同 vector indegree(numCourses); for (auto& edge : prerequisites) { int from = edge[1], to = edge[0]; indegree[to]++; } // 根据入度初始化队列中的节点,和环检测算法相同 queue q; for (int i = 0; i < numCourses; i++) { if (indegree[i] == 0) { q.push(i); } } // 记录拓扑排序结果 vector res(numCourses); // 记录遍历节点的顺序(索引) int count = 0; // 开始执行 BFS 算法 while (!q.empty()) { int cur = q.front(); q.pop(); // 弹出节点的顺序即为拓扑排序结果 res[count] = cur; count++; for (int next : graph[cur]) { indegree[next]--; if (indegree[next] == 0) { q.push(next); } } } if (count != numCourses) { // 存在环,拓扑排序不存在 return {}; } return res; } }; ``` ```go // by chatGPT (go) // 主函数 func findOrder(numCourses int, prerequisites [][]int) []int { // 建图,和环检测算法相同 graph := buildGraph(numCourses, prerequisites) // 计算入度,和环检测算法相同 indegree := make([]int, numCourses) for _, edge := range prerequisites { _, to := edge[1], edge[0] indegree[to]++ } // 根据入度初始化队列中的节点,和环检测算法相同 q := make([]int, 0) for i := 0; i < numCourses; i++ { if indegree[i] == 0 { q = append(q, i) } } // 记录拓扑排序结果 res := make([]int, numCourses) // 记录遍历节点的顺序(索引) count := 0 // 开始执行 BFS 算法 for len(q) > 0 { cur := q[0] q = q[1:] // 弹出节点的顺序即为拓扑排序结果 res[count] = cur count++ for _, next := range graph[cur] { indegree[next]-- if indegree[next] == 0 { q = append(q, next) } } } if count != numCourses { // 存在环,拓扑排序不存在 return []int{} } return res } // 建图函数 func buildGraph(numCourses int, prerequisites [][]int) [] []int { // 图中共有 numCourses 个节点 graph := make([][]int, numCourses) for i := 0; i < numCourses; i++ { graph[i] = make([]int, 0) } for _, edge := range prerequisites { from, to := edge[1], edge[0] // 修完课程 from 才能修课程 to // 在图中添加一条从 from 指向 to 的有向边 graph[from] = append(graph[from], to) } return graph } ``` ```java // by labuladong (java) class Solution { // 主函数 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); /** ![](../pictures/拓扑排序/6.jpeg) */ } } // 记录拓扑排序结果 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]) { /** ![](../pictures/拓扑排序/7.jpeg) */ indegree[next]--; if (indegree[next] == 0) { q.offer(next); } } } if (count != numCourses) { // 存在环,拓扑排序不存在 return new int[]{}; } return res; } // 建图函数 List[] buildGraph(int numCourses, int[][] prerequisites) { // 图中共有 numCourses 个节点 List[] graph = new LinkedList[numCourses]; for (int i = 0; i < numCourses; i++) { graph[i] = new LinkedList<>(); } for (int[] edge : prerequisites) { int from = edge[1], to = edge[0]; // 修完课程 from 才能修课程 to // 在图中添加一条从 from 指向 to 的有向边 graph[from].add(to); } return graph; } } ``` ```javascript // by chatGPT (javascript) var findOrder = function(numCourses, prerequisites) { // 建图,和环检测算法相同 const graph = buildGraph(numCourses, prerequisites); // 计算入度,和环检测算法相同 const indegree = new Array(numCourses).fill(0); for (const [from, to] of prerequisites) { indegree[to]++; } // 根据入度初始化队列中的节点,和环检测算法相同 const q = []; for (let i = 0; i < numCourses; i++) { if (indegree[i] === 0) { q.push(i); /** ![](../pictures/拓扑排序/6.jpeg) */ } } // 记录拓扑排序结果 const res = new Array(numCourses); // 记录遍历节点的顺序(索引) let count = 0; // 开始执行 BFS 算法 while (q.length > 0) { const cur = q.shift(); // 弹出节点的顺序即为拓扑排序结果 res[count] = cur; count++; for (const next of graph[cur]) { /** ![](../pictures/拓扑排序/7.jpeg) */ indegree[next]--; if (indegree[next] === 0) { q.push(next); } } } if (count !== numCourses) { // 存在环,拓扑排序不存在 return []; } return res; }; // 建图函数 function buildGraph(numCourses, prerequisites) { // 图中共有 numCourses 个节点 const graph = new Array(numCourses).map(() => []); for (const [from, to] of prerequisites) { // 修完课程 from 才能修课程 to // 在图中添加一条从 from 指向 to 的有向边 graph[from].push(to); } return graph; } ``` ```python # by chatGPT (python) class Solution: def findOrder(self, numCourses: int, prerequisites: List[List[int]]) -> List[int]: # 建图,和环检测算法相同 graph = self.buildGraph(numCourses, prerequisites) # 计算入度,和环检测算法相同 indegree = [0] * numCourses for edge in prerequisites: from_, to = edge[1], edge[0] indegree[to] += 1 # 根据入度初始化队列中的节点,和环检测算法相同 q = collections.deque() for i in range(numCourses): if indegree[i] == 0: q.append(i) # 记录拓扑排序结果 res = [0] * numCourses # 记录遍历节点的顺序(索引) count = 0 # 开始执行 BFS 算法 while q: cur = q.popleft() # 弹出节点的顺序即为拓扑排序结果 res[count] = cur count += 1 for next_ in graph[cur]: indegree[next_] -= 1 if indegree[next_] == 0: q.append(next_) if count != numCourses: # 存在环,拓扑排序不存在 return [] return res # 建图函数 def buildGraph(self, numCourses: int, prerequisites: List[List[int]]) -> List[List[int]]: # 图中共有 numCourses 个节点 graph = [[] for _ in range(numCourses)] for edge in prerequisites: from_, to = edge[1], edge[0] # 修完课程 from 才能修课程 to # 在图中添加一条从 from 指向 to 的有向边 graph[from_].append(to) return graph ``` https://leetcode.cn/problems/QA2IGt 的多语言解法👆 https://leetcode.cn/problems/SLwz0R 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: // 主函数 ListNode* removeNthFromEnd(ListNode* head, int n) { // 虚拟头结点 ListNode* dummy = new ListNode(-1); dummy->next = head; // 删除倒数第 n 个,要先找倒数第 n + 1 个节点 ListNode* x = findFromEnd(dummy, n + 1); // 删掉倒数第 n 个节点 x->next = x->next->next; return dummy->next; } // 返回链表的倒数第 k 个节点 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 != nullptr) { p2 = p2->next; p1 = p1->next; } // p2 现在指向第 n - k 个节点 return p2; } }; ``` ```go // by chatGPT (go) // removeNthFromEnd 主函数 func removeNthFromEnd(head *ListNode, n int) *ListNode { // 虚拟头结点 dummy := &ListNode{-1, head} // 删除倒数第 n 个,要先找倒数第 n + 1 个节点 x := findFromEnd(dummy, n + 1) // 删掉倒数第 n 个节点 x.Next = x.Next.Next return dummy.Next } // findFromEnd 返回链表的倒数第 k 个节点 func findFromEnd(head *ListNode, k int) *ListNode { p1 := head // p1 先走 k 步 for i := 0; i < k; i++ { p1 = p1.Next } p2 := head // p1 和 p2 同时走 n - k 步 for p1 != nil { p2 = p2.Next p1 = p1.Next } // p2 现在指向第 n - k 个节点 return p2 } ``` ```java // by labuladong (java) class Solution { // 主函数 public ListNode removeNthFromEnd(ListNode head, int n) { // 虚拟头结点 ListNode dummy = new ListNode(-1); dummy.next = head; // 删除倒数第 n 个,要先找倒数第 n + 1 个节点 ListNode x = findFromEnd(dummy, n + 1); // 删掉倒数第 n 个节点 x.next = x.next.next; return dummy.next; } // 返回链表的倒数第 k 个节点 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 个节点 return p2; } } ``` ```javascript // by chatGPT (javascript) var removeNthFromEnd = function(head, n) { // 虚拟头结点 var dummy = new ListNode(-1); dummy.next = head; // 删除倒数第 n 个,要先找倒数第 n + 1 个节点 var x = findFromEnd(dummy, n + 1); // 删掉倒数第 n 个节点 x.next = x.next.next; return dummy.next; }; // 返回链表的倒数第 k 个节点 var findFromEnd = function(head, k) { var p1 = head; // p1 先走 k 步 for (var i = 0; i < k; i++) { p1 = p1.next; } var p2 = head; // p1 和 p2 同时走 n - k 步 while (p1 != null) { p2 = p2.next; p1 = p1.next; } // p2 现在指向第 n - k 个节点 return p2; }; ``` ```python # by chatGPT (python) class Solution: def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode: # 虚拟头结点 dummy = ListNode(-1) dummy.next = head # 删除倒数第 n 个,要先找倒数第 n + 1 个节点 x = self.findFromEnd(dummy, n + 1) # 删掉倒数第 n 个节点 x.next = x.next.next return dummy.next # 返回链表的倒数第 k 个节点 def findFromEnd(self, head: ListNode, k: int) -> ListNode: p1 = head # p1 先走 k 步 for i in range(k): p1 = p1.next p2 = head # p1 和 p2 同时走 n - k 步 while p1: p2 = p2.next p1 = p1.next # p2 现在指向第 n - k 个节点 return p2 ``` https://leetcode.cn/problems/SLwz0R 的多语言解法👆 https://leetcode.cn/problems/SsGoHC 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: vector> merge(vector>& intervals) { vector> res; // 按区间的 start 升序排列 sort(intervals.begin(), intervals.end(), [](vector& a, vector& b) { return a[0] < b[0]; }); res.push_back(intervals[0]); for (int i = 1; i < intervals.size(); i++) { vector& curr = intervals[i]; // res 中最后一个元素的引用 vector& last = res.back(); if (curr[0] <= last[1]) { last[1] = max(last[1], curr[1]); } else { // 处理下一个待合并区间 res.push_back(curr); } } return res; } }; ``` ```go // by chatGPT (go) func merge(intervals [][]int) [][]int { res := [][]int{} // 按区间的 start 升序排列 sort.Slice(intervals, func(i, j int) bool { return intervals[i][0] < intervals[j][0] }) res = append(res, intervals[0]) for i := 1; i < len(intervals); i++ { curr := intervals[i] // res 中最后一个元素的引用 last := res[len(res)-1] if curr[0] <= last[1] { last[1] = max(last[1], curr[1]) } else { // 处理下一个待合并区间 res = append(res, curr) } } return res } func max(a, b int) int { if a > b { return a } return b } ``` ```java // by labuladong (java) class Solution { public int[][] merge(int[][] intervals) { LinkedList res = new LinkedList<>(); // 按区间的 start 升序排列 Arrays.sort(intervals, (a, b) -> { return a[0] - b[0]; }); res.add(intervals[0]); for (int i = 1; i < intervals.length; i++) { int[] curr = intervals[i]; // res 中最后一个元素的引用 int[] last = res.getLast(); if (curr[0] <= last[1]) { last[1] = Math.max(last[1], curr[1]); } else { // 处理下一个待合并区间 res.add(curr); } } return res.toArray(new int[0][0]); } } ``` ```javascript // by chatGPT (javascript) var merge = function(intervals) { let res = []; // 按区间的 start 升序排列 intervals.sort((a, b) => { return a[0] - b[0]; }); res.push(intervals[0]); for (let i = 1; i < intervals.length; i++) { let curr = intervals[i]; // res 中最后一个元素的引用 let last = res[res.length - 1]; if (curr[0] <= last[1]) { last[1] = Math.max(last[1], curr[1]); } else { // 处理下一个待合并区间 res.push(curr); } } return res; }; ``` ```python # by chatGPT (python) class Solution: def merge(self, intervals: List[List[int]]) -> List[List[int]]: res = [] # 按区间的 start 升序排列 intervals.sort(key=lambda x: x[0]) res.append(intervals[0]) for curr in intervals[1:]: # res 中最后一个元素的引用 last = res[-1] if curr[0] <= last[1]: last[1] = max(last[1], curr[1]) else: # 处理下一个待合并区间 res.append(curr) return res ``` https://leetcode.cn/problems/SsGoHC 的多语言解法👆 https://leetcode.cn/problems/TVdhkn 的多语言解法👇 ```cpp // by labuladong (cpp) class Solution { public: vector> res; vector> subsets(vector& nums) { // 记录走过的路径 vector track; backtrack(nums, 0, track); return res; } void backtrack(vector& nums, int start, vector& track) { res.push_back(track); for (int i = start; i < nums.size(); i++) { // 做选择 track.push_back(nums[i]); // 回溯 backtrack(nums, i + 1, track); // 撤销选择 track.pop_back(); } } }; ``` ```go // by chatGPT (go) // LeetCode Solution for Subsets func subsets(nums []int) [][]int { var res [][]int backtrack := func(nums []int, start int, track []int) { tmp := make([]int, len(track)) copy(tmp, track) res = append(res, tmp) for i := start; i < len(nums); i++ { // 做选择 track = append(track, nums[i]) // 回溯 backtrack(nums, i+1, track) // 撤销选择 track = track[:len(track)-1] } } backtrack(nums, 0, []int{}) return res } ``` ```java // by chatGPT (java) class Solution { List> res = new ArrayList<>(); public List> subsets(int[] nums) { List track = new ArrayList<>(); backtrack(nums, 0, track); return res; } private void backtrack(int[] nums, int start, List track) { res.add(new ArrayList<>(track)); for (int i = start; i < nums.length; i++) { // 做选择 track.add(nums[i]); // 回溯 backtrack(nums, i + 1, track); // 撤销选择 track.remove(track.size() - 1); } } } ``` ```javascript // by chatGPT (javascript) var subsets = function(nums) { // 记录走过的路径 var res = []; var backtrack = function(nums, start, track) { res.push(track.slice()); // 添加路径的拷贝 for (var i = start; i < nums.length; i++) { // 做选择 track.push(nums[i]); // 回溯 backtrack(nums, i + 1, track); // 撤销选择 track.pop(); } } var track = []; backtrack(nums, 0, track); return res; }; ``` ```python # by chatGPT (python) class Solution: def __init__(self): self.res = [] def subsets(self, nums: List[int]) -> List[List[int]]: # 记录走过的路径 track = [] self.backtrack(nums, 0, track) return self.res def backtrack(self, nums: List[int], start: int, track: List[int]) -> None: self.res.append(track[:]) for i in range(start, len(nums)): # 做选择 track.append(nums[i]) # 回溯 self.backtrack(nums, i + 1, track) # 撤销选择 track.pop() ``` https://leetcode.cn/problems/TVdhkn 的多语言解法👆 https://leetcode.cn/problems/UHnkqh 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: ListNode* reverseList(ListNode* head) { if (head == nullptr || head->next == nullptr) { return head; } ListNode* last = reverseList(head->next); /** ![](../pictures/反转链表/3.jpg) */ head->next->next = head; /** ![](../pictures/反转链表/4.jpg) */ head->next = nullptr; /** ![](../pictures/反转链表/5.jpg) */ return last; } }; ``` ```go // by chatGPT (go) func removeNthFromEnd(head *ListNode, n int) *ListNode { // 哑节点,方便处理删除头节点的情况 dummy := &ListNode{Val: 0, Next: head} // 快指针先走 n 步 fast := head for i := 0; i < n; i++ { fast = fast.Next } // 慢指针和快指针一起走,直到快指针到达链表尾部 slow := dummy for fast != nil { slow = slow.Next fast = fast.Next } // 删除倒数第 n 个节点 slow.Next = slow.Next.Next // 返回头节点 return dummy.Next } ``` ```java // by labuladong (java) class Solution { public ListNode reverseList(ListNode head) { if (head == null || head.next == null) { return head; } ListNode last = reverseList(head.next); /** ![](../pictures/反转链表/3.jpg) */ head.next.next = head; /** ![](../pictures/反转链表/4.jpg) */ head.next = null; /** ![](../pictures/反转链表/5.jpg) */ return last; } } ``` ```javascript // by chatGPT (javascript) /** * 给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。 * * 示例 1: * * 输入: "abcabcbb" * 输出: 3 * 解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。 * * 示例 2: * * 输入: "bbbbb" * 输出: 1 * 解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。 * * 示例 3: * * 输入: "pwwkew" * 输出: 3 * 解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。 * 请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。 */ var lengthOfLongestSubstring = function(s) { let n = s.length, ans = 0; let index = new Map(); // 存储字符上一次出现的位置 // i为左指针,j为右指针 for (let j = 0, i = 0; j < n; j++) { if (index.has(s[j])) { // 如果有重复的字符,移动左指针到上一个同样字符的后一个位置 i = Math.max(index.get(s[j]) + 1, i); } ans = Math.max(ans, j - i + 1); // 更新结果 index.set(s[j], j); // 更新字符的位置 } return ans; }; ``` ```python # by chatGPT (python) # 请保留所有中文注释! class Solution: def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode: # 新建一个虚拟节点,避免特判头节点的情况 dummy = ListNode(0, head) # 快指针先走n步 fast = head for i in range(n): fast = fast.next # 慢指针开始走,与快指针保持n步的距离 slow = dummy while fast is not None: fast = fast.next slow = slow.next # 此时slow指向的是倒数第n+1个节点,删除下一个节点即可 slow.next = slow.next.next return dummy.next ``` https://leetcode.cn/problems/UHnkqh 的多语言解法👆 https://leetcode.cn/problems/VabMRr 的多语言解法👇 ```cpp // by labuladong (cpp) class Solution { public: vector findAnagrams(string s, string t) { unordered_map need, window; for (char c : t) need[c]++; int left = 0, right = 0; int valid = 0; vector res; // 记录结果 while (right < s.size()) { char c = s[right]; right++; // 进行窗口内数据的一系列更新 if (need.count(c)) { window[c]++; if (window[c] == need[c]) valid++; } // 判断左侧窗口是否要收缩 while (right - left >= t.size()) { // 当窗口符合条件时,把起始索引加入 res if (valid == need.size()) res.push_back(left); char d = s[left]; left++; // 进行窗口内数据的一系列更新 if (need.count(d)) { if (window[d] == need[d]) valid--; window[d]--; } } } return res; } }; ``` ```go // by chatGPT (go) func findAnagrams(s string, t string) []int { need, window := make(map[byte]int), make(map[byte]int) for i := range t { need[t[i]]++ } left, right := 0, 0 valid := 0 var res []int for right < len(s) { c := s[right] right++ // 进行窗口内数据的一系列更新 update := func(c byte) { if _, ok := need[c]; ok { window[c]++ if window[c] == need[c] { valid++ } } } update(c) // 判断左侧窗口是否要收缩 for right - left >= len(t) { // 当窗口符合条件时,把起始索引加入 res if valid == len(need) { res = append(res, left) } d := s[left] left++ // 进行窗口内数据的一系列更新 update := func(d byte) { if _, ok := need[d]; ok { if window[d] == need[d] { valid-- } window[d]-- } } update(d) } } return res } ``` ```java // by chatGPT (java) 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, 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; } } ``` ```javascript // by chatGPT (javascript) var findAnagrams = function(s, t) { var need = {}, window = {}; for (var i = 0; i < t.length; i++) { var c = t[i]; need[c] = (need[c] || 0) + 1; } var left = 0, right = 0; var valid = 0; var res = []; while (right < s.length) { var c = s[right]; right++; // 进行窗口内数据的一系列更新 if (need.hasOwnProperty(c)) { window[c] = (window[c] || 0) + 1; if (window[c] === need[c]) valid++; } // 判断左侧窗口是否要收缩 while (right - left >= t.length) { // 当窗口符合条件时,把起始索引加入 res if (valid === Object.keys(need).length) res.push(left); var d = s[left]; left++; // 进行窗口内数据的一系列更新 if (need.hasOwnProperty(d)) { if (window[d] === need[d]) valid--; window[d]--; } } } return res; }; ``` ```python # by chatGPT (python) class Solution: def findAnagrams(self, s: str, t: str) -> List[int]: need, window = {}, {} for c in t: need[c] = need.get(c, 0) + 1 # 统计目标字符串中字符出现次数 left, right = 0, 0 valid = 0 res = [] while right < len(s): c = s[right] # 移入窗口的字符 right += 1 # 进行窗口内数据的更新 if c in need: window[c] = window.get(c, 0) + 1 if window[c] == need[c]: valid += 1 # 判断左侧窗口是否要收缩 while right - left >= len(t): # 当窗口符合条件时,把起始索引加入 res if valid == len(need): res.append(left) d = s[left] # 移出窗口的字符 left += 1 # 进行窗口内数据的更新 if d in need: if window[d] == need[d]: valid -= 1 window[d] -= 1 return res ``` https://leetcode.cn/problems/VabMRr 的多语言解法👆 https://leetcode.cn/problems/VvJkup 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: vector> res; /* 主函数,输入一组不重复的数字,返回它们的全排列 */ vector> permute(vector& nums) { // 记录「路径」 vector track; // 「路径」中的元素会被标记为 true,避免重复使用 vector used(nums.size(), false); backtrack(nums, track, used); return res; } // 路径:记录在 track 中 // 选择列表:nums 中不存在于 track 的那些元素(used[i] 为 false) // 结束条件:nums 中的元素全都在 track 中出现 void backtrack(vector& nums, vector& track, vector& used) { // 触发结束条件 if (track.size() == nums.size()) { res.push_back(track); return; } for (int i = 0; i < nums.size(); i++) { // 排除不合法的选择 if (used[i]) { /** ![](../pictures/backtracking/6.jpg) */ // nums[i] 已经在 track 中,跳过 continue; } // 做选择 track.push_back(nums[i]); used[i] = true; // 进入下一层决策树 backtrack(nums, track, used); // 取消选择 track.pop_back(); used[i] = false; } } }; ``` ```go // by chatGPT (go) func permute(nums []int) [][]int { res := [][]int{} /* backtrack 函数会用到的参数 */ var backtrack func(nums, track []int, used []bool) backtrack = func(nums, track []int, used []bool) { // 「取消选择」的过程是撤销上一次的选择,所以不需要结束条件 if len(track) == len(nums) { temp := make([]int, len(track)) copy(temp, track) res = append(res, temp) return } for i := 0; i < len(nums); i++ { // 排除不合法的选择 if used[i] { continue } // 做选择 track = append(track, nums[i]) used[i] = true // 进入下一层决策树 backtrack(nums, track, used) // 「取消选择」,进行回溯 track = track[:len(track)-1] used[i] = false } } backtrack(nums, []int{}, make([]bool, len(nums))) return res } ``` ```java // by labuladong (java) class Solution { List> res = new LinkedList<>(); /* 主函数,输入一组不重复的数字,返回它们的全排列 */ List> permute(int[] nums) { // 记录「路径」 LinkedList track = new LinkedList<>(); // 「路径」中的元素会被标记为 true,避免重复使用 boolean[] used = new boolean[nums.length]; backtrack(nums, track, used); return res; } // 路径:记录在 track 中 // 选择列表:nums 中不存在于 track 的那些元素(used[i] 为 false) // 结束条件:nums 中的元素全都在 track 中出现 void backtrack(int[] nums, LinkedList track, boolean[] used) { // 触发结束条件 if (track.size() == nums.length) { res.add(new LinkedList(track)); return; } for (int i = 0; i < nums.length; i++) { // 排除不合法的选择 if (used[i]) { /** ![](../pictures/backtracking/6.jpg) */ // nums[i] 已经在 track 中,跳过 continue; } // 做选择 track.add(nums[i]); used[i] = true; // 进入下一层决策树 backtrack(nums, track, used); // 取消选择 track.removeLast(); used[i] = false; } } } ``` ```javascript // by chatGPT (javascript) var permute = function(nums) { const res = []; const backtrack = (track, used) => { if (track.length === nums.length) { res.push([...track]); return; } for (let i = 0; i < nums.length; i++) { if (used[i]) { // nums[i] 已经在 track 中,跳过 continue; } track.push(nums[i]); used[i] = true; backtrack(track, used); track.pop(); used[i] = false; } }; backtrack([], []); return res; }; ``` ```python # by chatGPT (python) class Solution: def __init__(self): self.res = [] def permute(self, nums: List[int]) -> List[List[int]]: track = [] used = [False] * len(nums) self.backtrack(nums, track, used) return self.res def backtrack(self, nums: List[int], track: List[int], used: List[bool]) -> None: if len(track) == len(nums): self.res.append(track.copy()) return for i in range(len(nums)): if used[i]: # nums[i] 已经在 track 中,跳过 continue track.append(nums[i]) used[i] = True self.backtrack(nums, track, used) track.pop() used[i] = False ``` https://leetcode.cn/problems/VvJkup 的多语言解法👆 https://leetcode.cn/problems/WhsWhI 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int longestConsecutive(vector& nums) { // 转化成哈希集合,方便快速查找是否存在某个元素 unordered_set set; for (int num : nums) { set.insert(num); } int res = 0; for (int num : set) { if (set.count(num - 1)) { // num 不是连续子序列的第一个,跳过 continue; } // num 是连续子序列的第一个,开始向上计算连续子序列的长度 int curNum = num; int curLen = 1; while (set.count(curNum + 1)) { curNum += 1; curLen += 1; } // 更新最长连续序列的长度 res = max(res, curLen); } return res; } }; ``` ```go // by chatGPT (go) func longestConsecutive(nums []int) int { // 转化成哈希集合,方便快速查找是否存在某个元素 set := make(map[int]bool) for _, num := range nums { set[num] = true } res := 0 for num := range set { if set[num-1] { // num 不是连续子序列的第一个,跳过 continue } // num 是连续子序列的第一个,开始向上计算连续子序列的长度 curNum := num curLen := 1 for set[curNum+1] { curNum += 1 curLen += 1 } // 更新最长连续序列的长度 res = max(res, curLen) } return res } func max(a, b int) int { if a > b { return a } return b } ``` ```java // by labuladong (java) class Solution { public int longestConsecutive(int[] nums) { // 转化成哈希集合,方便快速查找是否存在某个元素 HashSet set = new HashSet(); for (int num : nums) { set.add(num); } int res = 0; for (int num : set) { if (set.contains(num - 1)) { // num 不是连续子序列的第一个,跳过 continue; } // num 是连续子序列的第一个,开始向上计算连续子序列的长度 int curNum = num; int curLen = 1; while (set.contains(curNum + 1)) { curNum += 1; curLen += 1; } // 更新最长连续序列的长度 res = Math.max(res, curLen); } return res; } } ``` ```javascript // by chatGPT (javascript) var longestConsecutive = function(nums) { // 转化成哈希集合,方便快速查找是否存在某个元素 const set = new Set(nums); let res = 0; for (const num of set) { if (set.has(num - 1)) { // num 不是连续子序列的第一个,跳过 continue; } // num 是连续子序列的第一个,开始向上计算连续子序列的长度 let curNum = num; let curLen = 1; while (set.has(curNum + 1)) { curNum += 1; curLen += 1; } // 更新最长连续序列的长度 res = Math.max(res, curLen); } return res; }; ``` ```python # by chatGPT (python) class Solution: def longestConsecutive(self, nums: List[int]) -> int: # 转化成哈希集合,方便快速查找是否存在某个元素 num_set = set(nums) res = 0 for num in num_set: if num - 1 in num_set: # num 不是连续子序列的第一个,跳过 continue # num 是连续子序列的第一个,开始向上计算连续子序列的长度 cur_num = num cur_len = 1 while cur_num + 1 in num_set: cur_num += 1 cur_len += 1 # 更新最长连续序列的长度 res = max(res, cur_len) return res ``` https://leetcode.cn/problems/WhsWhI 的多语言解法👆 https://leetcode.cn/problems/XltzEq 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: bool isPalindrome(string s) { // 先把所有字符转化成小写,并过滤掉空格和标点这类字符 string filtered; for (int i = 0; i < s.length(); i++) { char c = s[i]; if (isalnum(c)) { // 使用isalnum函数判断是否为字母或数字 filtered += tolower(c); // 使用tolower函数将字符转换为小写 } } // 然后对剩下的这些目标字符执行双指针算法,判断回文串 s = filtered; // 一左一右两个指针相向而行 int left = 0, right = s.length() - 1; while (left < right) { if (s[left] != s[right]) { return false; } left++; right--; } return true; } }; ``` ```go // by chatGPT (go) func isPalindrome(s string) bool { // 先把所有字符转化成小写,并过滤掉空格和标点这类字符 sb := strings.Builder{} for _, c := range s { if unicode.IsLetter(c) || unicode.IsDigit(c) { sb.WriteRune(unicode.ToLower(c)) } } // 然后对剩下的这些目标字符执行双指针算法,判断回文串 s = sb.String() // 一左一右两个指针相向而行 left, right := 0, len(s)-1 for left < right { if s[left] != s[right] { return false } left++ right-- } return true } ``` ```java // by labuladong (java) class Solution { public boolean isPalindrome(String s) { // 先把所有字符转化成小写,并过滤掉空格和标点这类字符 StringBuilder sb = new StringBuilder(); for (int i = 0; i < s.length(); i++) { char c = s.charAt(i); if (Character.isLetterOrDigit(c)) { sb.append(Character.toLowerCase(c)); } } // 然后对剩下的这些目标字符执行双指针算法,判断回文串 s = sb.toString(); // 一左一右两个指针相向而行 int left = 0, right = s.length() - 1; while (left < right) { if (s.charAt(left) != s.charAt(right)) { return false; } left++; right--; } return true; } } ``` ```javascript // by chatGPT (javascript) var isPalindrome = function(s) { // 先把所有字符转化成小写,并过滤掉空格和标点这类字符 let sb = ''; for (let i = 0; i < s.length; i++) { let c = s.charAt(i); if (/[0-9a-zA-Z]/.test(c)) { sb += c.toLowerCase(); } } // 然后对剩下的这些目标字符执行双指针算法,判断回文串 s = sb; // 一左一右两个指针相向而行 let left = 0, right = s.length - 1; while (left < right) { if (s.charAt(left) !== s.charAt(right)) { return false; } left++; right--; } return true; } ``` ```python # by chatGPT (python) class Solution: def isPalindrome(self, s: str) -> bool: # 先把所有字符转化成小写,并过滤掉空格和标点这类字符 sb = [] for c in s: if c.isalnum(): sb.append(c.lower()) # 然后对剩下的这些目标字符执行双指针算法,判断回文串 s = ''.join(sb) # 一左一右两个指针相向而行 left, right = 0, len(s) - 1 while left < right: if s[left] != s[right]: return False left += 1 right -= 1 return True ``` https://leetcode.cn/problems/XltzEq 的多语言解法👆 https://leetcode.cn/problems/YaVDxD 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int findTargetSumWays(vector& nums, int target) { if (nums.empty()) return 0; return dp(nums, 0, target); } // 备忘录 unordered_map memo; int dp(vector& nums, int i, int remain) { // base case if (i == nums.size()) { if (remain == 0) return 1; return 0; } // 把它俩转成字符串才能作为哈希表的键 string key = to_string(i) + "," + to_string(remain); // 避免重复计算 if (memo.count(key)) { return memo[key]; } // 还是穷举 int result = dp(nums, i + 1, remain - nums[i]) + dp(nums, i + 1, remain + nums[i]); // 记入备忘录 memo[key] = result; return result; } }; ``` ```go // by chatGPT (go) func findTargetSumWays(nums []int, target int) int { if len(nums) == 0 { return 0 } //初始化备忘录 memo := make(map[string]int) return dp(nums, 0, target, memo) } func dp(nums []int, i int, remain int, memo map[string]int) int { //基本情况 if i == len(nums) { if remain == 0 { return 1 } return 0 } key := fmt.Sprintf("%d,%d", i, remain) //避免重复计算 if _, ok := memo[key]; ok { return memo[key] } //穷举 result := dp(nums, i+1, remain-nums[i], memo) + dp(nums, i+1, remain+nums[i], memo) //记录结果到备忘录 memo[key] = result return result } ``` ```java // by labuladong (java) class Solution { public int findTargetSumWays(int[] nums, int target) { if (nums.length == 0) return 0; return dp(nums, 0, target); } // 备忘录 HashMap memo = new HashMap<>(); int dp(int[] nums, int i, int remain) { // base case if (i == nums.length) { if (remain == 0) return 1; return 0; } // 把它俩转成字符串才能作为哈希表的键 String key = i + "," + remain; // 避免重复计算 if (memo.containsKey(key)) { return memo.get(key); } // 还是穷举 int result = dp(nums, i + 1, remain - nums[i]) + dp(nums, i + 1, remain + nums[i]); // 记入备忘录 memo.put(key, result); return result; } } ``` ```javascript // by chatGPT (javascript) var findTargetSumWays = function(nums, target) { if (nums.length === 0) return 0; // 备忘录 let memo = new Map(); function dp(nums, i, remain) { // base case if (i === nums.length) { if (remain === 0) return 1; return 0; } // 把它俩转成字符串才能作为哈希表的键 let key = i + "," + remain; // 避免重复计算 if (memo.has(key)) { return memo.get(key); } // 还是穷举 let result = dp(nums, i + 1, remain - nums[i]) + dp(nums, i + 1, remain + nums[i]); // 记入备忘录 memo.set(key, result); return result; } return dp(nums, 0, target); }; ``` ```python # by chatGPT (python) class Solution: def __init__(self): self.memo = {} def findTargetSumWays(self, nums: List[int], target: int) -> int: if len(nums) == 0: return 0 return self.dp(nums, 0, target) def dp(self, nums: List[int], i: int, remain: int) -> int: # base case if i == len(nums): if remain == 0: return 1 return 0 # 把它俩转成字符串才能作为哈希表的键 key = str(i) + "," + str(remain) # 避免重复计算 if key in self.memo: return self.memo[key] # 还是穷举 result = self.dp(nums, i + 1, remain - nums[i]) + self.dp(nums, i + 1, remain + nums[i]) # 记入备忘录 self.memo[key] = result return result ``` https://leetcode.cn/problems/YaVDxD 的多语言解法👆 https://leetcode.cn/problems/Ygoe9J 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { vector> res; list track; public: vector> combinationSum(vector& candidates, int target) { if (candidates.empty()) { return res; } backtrack(candidates, 0, target, 0); return res; } // 回溯算法主函数 void backtrack(vector& candidates, int start, int target, int sum) { if (sum == target) { // 找到目标和 res.push_back(vector(track.begin(), track.end())); return; } if (sum > target) { // 超过目标和,直接结束 return; } // 回溯算法框架 for (int i = start; i < candidates.size(); i++) { // 选择 candidates[i] track.push_back(candidates[i]); sum += candidates[i]; // 递归遍历下一层回溯树 backtrack(candidates, i, target, sum); // 撤销选择 candidates[i] sum -= candidates[i]; track.pop_back(); } } }; ``` ```go // by chatGPT (go) func combinationSum(candidates []int, target int) [][]int { res := [][]int{} if len(candidates) == 0 { return res } backtrack(candidates, []int{}, target, 0, &res) return res } // 回溯算法框架 func backtrack(candidates []int, path []int, target, sum int, res *[][]int) { if sum == target { // 找到目标和 *res = append(*res, append([]int{}, path...)) return } if sum > target { // 超过目标和,直接结束 return } for i := 0; i < len(candidates); i++ { // 选择 candidates[i] path = append(path, candidates[i]) sum += candidates[i] // 递归遍历下一层回溯树 backtrack(candidates[i:], path, target, sum, res) // 撤销选择 candidates[i] sum -= candidates[i] path = path[:len(path)-1] } } ``` ```java // by labuladong (java) class Solution { List> res = new LinkedList<>(); public List> combinationSum(int[] candidates, int target) { if (candidates.length == 0) { return res; } backtrack(candidates, 0, target, 0); return res; } // 记录回溯的路径 LinkedList track = new LinkedList<>(); // 回溯算法主函数 void backtrack(int[] candidates, int start, int target, int sum) { if (sum == target) { // 找到目标和 res.add(new LinkedList<>(track)); return; } if (sum > target) { // 超过目标和,直接结束 return; } // 回溯算法框架 for (int i = start; i < candidates.length; i++) { // 选择 candidates[i] track.add(candidates[i]); sum += candidates[i]; // 递归遍历下一层回溯树 backtrack(candidates, i, target, sum); // 撤销选择 candidates[i] sum -= candidates[i]; track.removeLast(); } } } ``` ```javascript // by chatGPT (javascript) var combinationSum = function(candidates, target) { var res = []; if (candidates.length === 0) { return res; } // 记录回溯的路径 var track = []; // 回溯算法主函数 var backtrack = function(candidates, start, target, sum) { if (sum === target) { // 找到目标和 res.push([...track]); return; } if (sum > target) { // 超过目标和,直接结束 return; } // 回溯算法框架 for (var i = start; i < candidates.length; i++) { // 选择 candidates[i] track.push(candidates[i]); sum += candidates[i]; // 递归遍历下一层回溯树 backtrack(candidates, i, target, sum); // 撤销选择 candidates[i] sum -= candidates[i]; track.pop(); } }; backtrack(candidates, 0, target, 0); return res; }; ``` ```python # by chatGPT (python) class Solution: def __init__(self): self.res = [] self.track = [] def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]: if not candidates: return self.res self.backtrack(candidates, 0, target, 0) return self.res # 记录回溯的路径 def backtrack(self, candidates, start, target, summation): if summation == target: # 找到目标和 self.res.append(self.track[:]) return if summation > target: # 超过目标和,直接结束 return # 回溯算法框架 for i in range(start, len(candidates)): # 选择 candidates[i] self.track.append(candidates[i]) summation += candidates[i] # 递归遍历下一层回溯树 self.backtrack(candidates, i, target, summation) # 撤销选择 candidates[i] summation -= candidates[i] self.track.pop() ``` https://leetcode.cn/problems/Ygoe9J 的多语言解法👆 https://leetcode.cn/problems/ZL6zAn 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int maxAreaOfIsland(vector>& grid) { // 记录岛屿的最大面积 int res = 0; int m = grid.size(), n = grid[0].size(); for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { if (grid[i][j] == 1) { // 淹没岛屿,并更新最大岛屿面积 res = max(res, dfs(grid, i, j)); } } } return res; } // 淹没与 (i, j) 相邻的陆地,并返回淹没的陆地面积 int dfs(vector>& grid, int i, int j) { int m = grid.size(), n = grid[0].size(); if (i < 0 || j < 0 || i >= m || j >= n) { // 超出索引边界 return 0; } if (grid[i][j] == 0) { // 已经是海水了 return 0; } // 将 (i, j) 变成海水 grid[i][j] = 0; return dfs(grid, i + 1, j) + dfs(grid, i, j + 1) + dfs(grid, i - 1, j) + dfs(grid, i, j - 1) + 1; } }; ``` ```go // by chatGPT (go) // 记录岛屿的最大面积 func maxAreaOfIsland(grid [][]int) int { res := 0 m, n := len(grid), len(grid[0]) for i := 0; i < m; i++ { for j := 0; j < n; j++ { if grid[i][j] == 1 { // 淹没岛屿,并更新最大岛屿面积 res = max(res, dfs(grid, i, j)) } } } return res } // 淹没与 (i, j) 相邻的陆地,并返回淹没的陆地面积 func dfs(grid [][]int, i, j int) int { m, n := len(grid), len(grid[0]) if i < 0 || j < 0 || i >= m || j >= n { // 超出索引边界 return 0 } if grid[i][j] == 0 { // 已经是海水了 return 0 } // 将 (i, j) 变成海水 grid[i][j] = 0 return dfs(grid, i+1, j) + dfs(grid, i, j+1) + dfs(grid, i-1, j) + dfs(grid, i, j-1) + 1 } func max(a, b int) int { if a > b { return a } return b } ``` ```java // by labuladong (java) class Solution { public int maxAreaOfIsland(int[][] grid) { // 记录岛屿的最大面积 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 = Math.max(res, dfs(grid, i, j)); } } } return res; } // 淹没与 (i, j) 相邻的陆地,并返回淹没的陆地面积 int dfs(int[][] grid, int i, int j) { int m = grid.length, n = grid[0].length; if (i < 0 || j < 0 || i >= m || j >= n) { // 超出索引边界 return 0; } if (grid[i][j] == 0) { // 已经是海水了 return 0; } // 将 (i, j) 变成海水 grid[i][j] = 0; return dfs(grid, i + 1, j) + dfs(grid, i, j + 1) + dfs(grid, i - 1, j) + dfs(grid, i, j - 1) + 1; } } ``` ```javascript // by chatGPT (javascript) var maxAreaOfIsland = function(grid) { let res = 0; let m = grid.length, n = grid[0].length; function dfs(grid, i, j) { if (i < 0 || j < 0 || i >= m || j >= n) { // 超出索引边界 return 0; } if (grid[i][j] == 0) { // 已经是海水了 return 0; } // 将 (i, j) 变成海水 grid[i][j] = 0; return dfs(grid, i + 1, j) + dfs(grid, i, j + 1) + dfs(grid, i - 1, j) + dfs(grid, i, j - 1) + 1; } for (let i = 0; i < m; i++) { for (let j = 0; j < n; j++) { if (grid[i][j] == 1) { res = Math.max(res, dfs(grid, i, j)); } } } return res; }; ``` ```python # by chatGPT (python) class Solution: def maxAreaOfIsland(self, grid: List[List[int]]) -> int: # 记录岛屿的最大面积 res = 0 m, n = len(grid), len(grid[0]) for i in range(m): for j in range(n): if grid[i][j] == 1: # 淹没岛屿,并更新最大岛屿面积 res = max(res, self.dfs(grid, i, j)) return res # 淹没与 (i, j) 相邻的陆地,并返回淹没的陆地面积 def dfs(self, grid: List[List[int]], i: int, j: int) -> int: m, n = len(grid), len(grid[0]) if i < 0 or j < 0 or i >= m or j >= n: # 超出索引边界 return 0 if grid[i][j] == 0: # 已经是海水了 return 0 # 将 (i, j) 变成海水 grid[i][j] = 0 return (self.dfs(grid, i + 1, j) + self.dfs(grid, i, j + 1) + self.dfs(grid, i - 1, j) + self.dfs(grid, i, j - 1) + 1) ``` https://leetcode.cn/problems/ZL6zAn 的多语言解法👆 https://leetcode.cn/problems/aMhZSa 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: bool isPalindrome(ListNode* head) { ListNode *slow, *fast; slow = fast = head; while (fast != nullptr && fast->next != nullptr) { slow = slow->next; fast = fast->next->next; } if (fast != nullptr) slow = slow->next; ListNode *left = head; ListNode *right = reverse(slow); while (right != nullptr) { if (left->val != right->val) return false; left = left->next; right = right->next; } return true; } ListNode* reverse(ListNode* head) { ListNode *pre = nullptr, *cur = head; while (cur != nullptr) { ListNode *next = cur->next; cur->next = pre; pre = cur; cur = next; } return pre; } }; ``` ```go // by chatGPT (go) func isPalindrome(head *ListNode) bool { slow, fast := head, head for fast != nil && fast.Next != nil { slow = slow.Next fast = fast.Next.Next } if fast != nil { slow = slow.Next } left, right := head, reverse(slow) for right != nil { if left.Val != right.Val { return false } left = left.Next right = right.Next } return true } func reverse(head *ListNode) *ListNode { var pre *ListNode cur := head for cur != nil { next := cur.Next cur.Next = pre pre = cur cur = next } return pre } ``` ```java // by labuladong (java) class Solution { public boolean isPalindrome(ListNode head) { ListNode slow, fast; slow = fast = head; while (fast != null && fast.next != null) { slow = slow.next; fast = fast.next.next; } if (fast != null) slow = slow.next; ListNode left = head; ListNode right = reverse(slow); while (right != null) { if (left.val != right.val) return false; left = left.next; right = right.next; } return true; } 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; } } ``` ```javascript // by chatGPT (javascript) var isPalindrome = function(head) { let slow, fast; slow = fast = head; while (fast != null && fast.next != null) { slow = slow.next; fast = fast.next.next; } if (fast != null) slow = slow.next; let left = head; let right = reverse(slow); while (right != null) { if (left.val != right.val) return false; left = left.next; right = right.next; } return true; }; var reverse = function(head) { let pre = null, cur = head; while (cur != null) { let next = cur.next; cur.next = pre; pre = cur; cur = next; } return pre; }; ``` ```python # by chatGPT (python) class Solution: def isPalindrome(self, head: ListNode) -> bool: slow, fast = head, head while fast and fast.next: slow = slow.next fast = fast.next.next if fast: slow = slow.next left = head right = self.reverse(slow) while right: if left.val != right.val: return False left = left.next right = right.next return True def reverse(self, head: ListNode) -> ListNode: pre, cur = None, head while cur: nxt = cur.next cur.next = pre pre = cur cur = nxt return pre ``` https://leetcode.cn/problems/aMhZSa 的多语言解法👆 https://leetcode.cn/problems/add-binary 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: string addBinary(string a, string b) { // 先把输入的这两个二进制串反转,低位放在前面,方便处理进位 reverse(a.begin(), a.end()); reverse(b.begin(), b.end()); // 存储结果 string res = ""; int m = a.size(), n = b.size(); // carry 记录进位 int carry = 0; int i = 0; // 开始类似 [2. 两数相加](#2) 的加法模拟逻辑 // 只是这里运算的是二进制字符串 while (i < max(m, n) || carry > 0) { int val = carry; val += i < m ? (a[i] - '0') : 0; val += i < n ? (b[i] - '0') : 0; res.push_back(val % 2 + '0'); carry = val / 2; i++; } reverse(res.begin(), res.end()); return res; } }; ``` ```go // by chatGPT (go) func addBinary(a string, b string) string { // 先把输入的这两个二进制串反转,低位放在前面,方便处理进位 reverse := func(str string) string { reversed := []rune(str) for i, j := 0, len(reversed)-1; i < j; i, j = i+1, j-1 { reversed[i], reversed[j] = reversed[j], reversed[i] } return string(reversed) } a = reverse(a) b = reverse(b) // 存储结果 var sb strings.Builder m, n := len(a), len(b) // carry 记录进位 carry := 0 i := 0 // 开始类似 [2. 两数相加](#2) 的加法模拟逻辑 // 只是这里运算的是二进制字符串 for i < max(m, n) || carry > 0 { val := carry if i < m { val += int(a[i] - '0') } if i < n { val += int(b[i] - '0') } sb.WriteString(strconv.Itoa(val % 2)) carry = val / 2 i++ } return reverse(sb.String()) } func max(a, b int) int { if a > b { return a } return b } ``` ```java // by labuladong (java) class Solution { public String addBinary(String a, String b) { // 先把输入的这两个二进制串反转,低位放在前面,方便处理进位 a = new StringBuilder(a).reverse().toString(); b = new StringBuilder(b).reverse().toString(); // 存储结果 StringBuilder sb = new StringBuilder(); int m = a.length(), n = b.length(); // carry 记录进位 int carry = 0; int i = 0; // 开始类似 [2. 两数相加](#2) 的加法模拟逻辑 // 只是这里运算的是二进制字符串 while (i < Math.max(m, n) || carry > 0) { int val = carry; val += i < m ? (a.charAt(i) - '0') : 0; val += i < n ? (b.charAt(i) - '0') : 0; sb.append(val % 2); carry = val / 2; i++; } return sb.reverse().toString(); } } ``` ```javascript // by chatGPT (javascript) var addBinary = function(a, b) { // 先把输入的这两个二进制串反转,低位放在前面,方便处理进位 a = a.split("").reverse().join(""); b = b.split("").reverse().join(""); // 存储结果 var sb = ""; var m = a.length, n = b.length; // carry 记录进位 var carry = 0; var i = 0; // 开始类似 [2. 两数相加](#2) 的加法模拟逻辑 // 只是这里运算的是二进制字符串 while (i < Math.max(m, n) || carry > 0) { var val = carry; val += i < m ? parseInt(a.charAt(i)) : 0; val += i < n ? parseInt(b.charAt(i)) : 0; sb += (val % 2); carry = Math.floor(val / 2); i++; } return sb.split("").reverse().join(""); }; ``` ```python # by chatGPT (python) class Solution: def addBinary(self, a: str, b: str) -> str: # 先把输入的这两个二进制串反转,低位放在前面,方便处理进位 a = a[::-1] b = b[::-1] # 存储结果 sb = [] m, n = len(a), len(b) # carry 记录进位 carry = 0 i = 0 # 开始类似 [2. 两数相加](#2) 的加法模拟逻辑 # 只是这里运算的是二进制字符串 while i < max(m, n) or carry > 0: val = carry val += int(a[i]) if i < m else 0 val += int(b[i]) if i < n else 0 sb.append(str(val % 2)) carry = val // 2 i += 1 return "".join(sb[::-1]) ``` https://leetcode.cn/problems/add-binary 的多语言解法👆 https://leetcode.cn/problems/add-two-numbers 的多语言解法👇 ```cpp // by chatGPT (cpp) 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 != nullptr || p2 != nullptr || carry > 0) { // 先加上上次的进位 int val = carry; if (p1 != nullptr) { val += p1->val; p1 = p1->next; } if (p2 != nullptr) { val += p2->val; p2 = p2->next; } // 处理进位情况 carry = val / 10; val = val % 10; // 构建新节点 p->next = new ListNode(val); p = p->next; } // 返回结果链表的头结点(去除虚拟头结点) return dummy->next; } }; ``` ```go // by chatGPT (go) func addTwoNumbers(l1 *ListNode, l2 *ListNode) *ListNode { // 在两条链表上的指针 p1, p2 := l1, l2 // 虚拟头结点(构建新链表时的常用技巧) dummy := &ListNode{-1, nil} // 指针 p 负责构建新链表 p := dummy // 记录进位 carry := 0 // 开始执行加法,两条链表走完且没有进位时才能结束循环 for p1 != nil || p2 != nil || carry > 0 { // 先加上上次的进位 val := carry if p1 != nil { val += p1.Val p1 = p1.Next } if p2 != nil { val += p2.Val p2 = p2.Next } // 处理进位情况 carry = val / 10 val = val % 10 // 构建新节点 p.Next = &ListNode{val, nil} p = p.Next } // 返回结果链表的头结点(去除虚拟头结点) return dummy.Next } ``` ```java // by labuladong (java) 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 = carry; if (p1 != null) { val += p1.val; p1 = p1.next; } if (p2 != null) { val += p2.val; p2 = p2.next; } // 处理进位情况 carry = val / 10; val = val % 10; // 构建新节点 p.next = new ListNode(val); p = p.next; } // 返回结果链表的头结点(去除虚拟头结点) return dummy.next; } } ``` ```javascript // by chatGPT (javascript) var addTwoNumbers = function(l1, l2) { // 在两条链表上的指针 let p1 = l1, p2 = l2; // 虚拟头结点(构建新链表时的常用技巧) let dummy = new ListNode(-1); // 指针 p 负责构建新链表 let p = dummy; // 记录进位 let carry = 0; // 开始执行加法,两条链表走完且没有进位时才能结束循环 while (p1 !== null || p2 !== null || carry > 0) { // 先加上上次的进位 let val = carry; if (p1 !== null) { val += p1.val; p1 = p1.next; } if (p2 !== null) { val += p2.val; p2 = p2.next; } // 处理进位情况 carry = Math.floor(val / 10); val = val % 10; // 构建新节点 p.next = new ListNode(val); p = p.next; } // 返回结果链表的头结点(去除虚拟头结点) return dummy.next; }; ``` ```python # by chatGPT (python) class Solution: def addTwoNumbers(self, l1: ListNode, l2: ListNode) -> ListNode: # 在两条链表上的指针 p1, p2 = l1, l2 # 虚拟头结点(构建新链表时的常用技巧) dummy = ListNode(-1) # 指针 p 负责构建新链表 p = dummy # 记录进位 carry = 0 # 开始执行加法,两条链表走完且没有进位时才能结束循环 while p1 or p2 or carry: # 先加上上次的进位 val = carry if p1: val += p1.val p1 = p1.next if p2: val += p2.val p2 = p2.next # 处理进位情况 carry, val = divmod(val, 10) # 构建新节点 p.next = ListNode(val) p = p.next # 返回结果链表的头结点(去除虚拟头结点) return dummy.next ``` https://leetcode.cn/problems/add-two-numbers 的多语言解法👆 https://leetcode.cn/problems/add-two-numbers-ii 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) { // 把链表元素转入栈中 stack stk1, stk2; while (l1 != nullptr) { stk1.push(l1->val); l1 = l1->next; } while (l2 != nullptr) { stk2.push(l2->val); l2 = l2->next; } // 接下来基本上是复用我在第 2 题的代码逻辑 // 注意新节点要直接插入到 dummy 后面 // 虚拟头结点(构建新链表时的常用技巧) ListNode* dummy = new ListNode(-1); // 记录进位 int carry = 0; // 开始执行加法,两条链表走完且没有进位时才能结束循环 while (!stk1.empty() || !stk2.empty() || carry > 0) { // 先加上上次的进位 int val = carry; if (!stk1.empty()) { val += stk1.top(); stk1.pop(); } if (!stk2.empty()) { val += stk2.top(); stk2.pop(); } // 处理进位情况 carry = val / 10; val = val % 10; // 构建新节点,直接接在 dummy 后面 ListNode* newNode = new ListNode(val); newNode->next = dummy->next; dummy->next = newNode; } // 返回结果链表的头结点(去除虚拟头结点) ListNode* result = dummy->next; delete dummy; return result; } }; ``` ```go // by chatGPT (go) func addTwoNumbers(l1 *ListNode, l2 *ListNode) *ListNode { // 把链表元素转入栈中 stk1 := []int{} for l1 != nil { stk1 = append(stk1, l1.Val) l1 = l1.Next } stk2 := []int{} for l2 != nil { stk2 = append(stk2, l2.Val) l2 = l2.Next } // 接下来基本上是复用我在第 2 题的代码逻辑 // 注意新节点要直接插入到 dummy 后面 // 虚拟头结点(构建新链表时的常用技巧) dummy := &ListNode{-1, nil} // 记录进位 carry := 0 // 开始执行加法,两条链表走完且没有进位时才能结束循环 for len(stk1) > 0 || len(stk2) > 0 || carry > 0 { // 先加上上次的进位 val := carry if len(stk1) > 0 { val += stk1[len(stk1)-1] stk1 = stk1[:len(stk1)-1] } if len(stk2) > 0 { val += stk2[len(stk2)-1] stk2 = stk2[:len(stk2)-1] } // 处理进位情况 carry = val / 10 val = val % 10 // 构建新节点,直接接在 dummy 后面 newNode := &ListNode{val, dummy.Next} dummy.Next = newNode } // 返回结果链表的头结点(去除虚拟头结点) return dummy.Next } ``` ```java // by labuladong (java) class Solution { public ListNode addTwoNumbers(ListNode l1, ListNode l2) { // 把链表元素转入栈中 Stack stk1 = new Stack<>(); while (l1 != null) { stk1.push(l1.val); l1 = l1.next; } Stack stk2 = new Stack<>(); while (l2 != null) { stk2.push(l2.val); l2 = l2.next; } // 接下来基本上是复用我在第 2 题的代码逻辑 // 注意新节点要直接插入到 dummy 后面 // 虚拟头结点(构建新链表时的常用技巧) ListNode dummy = new ListNode(-1); // 记录进位 int carry = 0; // 开始执行加法,两条链表走完且没有进位时才能结束循环 while (!stk1.isEmpty() || !stk2.isEmpty() || carry > 0) { // 先加上上次的进位 int val = carry; if (!stk1.isEmpty()) { val += stk1.pop(); } if (!stk2.isEmpty()) { val += stk2.pop(); } // 处理进位情况 carry = val / 10; val = val % 10; // 构建新节点,直接接在 dummy 后面 ListNode newNode = new ListNode(val); newNode.next = dummy.next; dummy.next = newNode; } // 返回结果链表的头结点(去除虚拟头结点) return dummy.next; } } ``` ```javascript // by chatGPT (javascript) var addTwoNumbers = function(l1, l2) { // 把链表元素转入栈中 const stk1 = []; while (l1 !== null) { stk1.push(l1.val); l1 = l1.next; } const stk2 = []; while (l2 !== null) { stk2.push(l2.val); l2 = l2.next; } // 接下来基本上是复用我在第 2 题的代码逻辑 // 注意新节点要直接插入到 dummy 后面 // 虚拟头结点(构建新链表时的常用技巧) const dummy = new ListNode(-1); // 记录进位 let carry = 0; // 开始执行加法,两条链表走完且没有进位时才能结束循环 while (stk1.length || stk2.length || carry > 0) { // 先加上上次的进位 let val = carry; if (stk1.length) { val += stk1.pop(); } if (stk2.length) { val += stk2.pop(); } // 处理进位情况 carry = Math.floor(val / 10); val = val % 10; // 构建新节点,直接接在 dummy 后面 const newNode = new ListNode(val); newNode.next = dummy.next; dummy.next = newNode; } // 返回结果链表的头结点(去除虚拟头结点) return dummy.next; }; ``` ```python # by chatGPT (python) class Solution: def addTwoNumbers(self, l1: ListNode, l2: ListNode) -> ListNode: # 把链表元素转入栈中 stk1 = [] while l1: stk1.append(l1.val) l1 = l1.next stk2 = [] while l2: stk2.append(l2.val) l2 = l2.next # 接下来基本上是复用我在第 2 题的代码逻辑 # 注意新节点要直接插入到 dummy 后面 # 虚拟头结点(构建新链表时的常用技巧) dummy = ListNode(-1) # 记录进位 carry = 0 # 开始执行加法,两条链表走完且没有进位时才能结束循环 while stk1 or stk2 or carry > 0: # 先加上上次的进位 val = carry if stk1: val += stk1.pop() if stk2: val += stk2.pop() # 处理进位情况 carry = val // 10 val = val % 10 # 构建新节点,直接接在 dummy 后面 newNode = ListNode(val) newNode.next = dummy.next dummy.next = newNode # 返回结果链表的头结点(去除虚拟头结点) return dummy.next ``` https://leetcode.cn/problems/add-two-numbers-ii 的多语言解法👆 https://leetcode.cn/problems/additive-number 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: bool isAdditiveNumber(string num) { // 穷举前两个数字 int n = num.size(); for (int i = 1; i <= n; i++) { for (int j = i + 1; j <= n; j++) { string first = num.substr(0, i); string second = num.substr(i, j-i); if (isValid(num, first, second)) { return true; } } } return false; } private: // 定义:num 前两个数字分别是 first 和 second,判断 num 是否满足累加数的性质 bool isValid(string num, string first, string second) { if ((first[0] == '0' && first.size() > 1) || (second[0] == '0' && second.size() > 1)) { // 0 开头的数字,只能是 0 本身 return false; } string sumStr = strAdd(first, second); string next = num.substr(first.size() + second.size()); if (next.find(sumStr) != 0) { // 不满足累加数的性质 return false; } if (next == sumStr) { // 已经匹配完整个字符串 return true; } // 根据递归函数的定义,继续匹配后面的三个数字,我这里用递归的方式去比较,因为容易写 // 你也可以改用迭代写法,一样的 return isValid(num.substr(first.size()), second, sumStr); } // 模拟加法竖式运算,具体可以看下这道题 // https://leetcode-cn.com/problems/add-strings/ string strAdd(string a, string b) { int n = a.size(), m = b.size(); int i = n - 1, j = m - 1, add = 0; string builder; while (i >= 0 || j >= 0 || add != 0) { int x = i >= 0 ? a[i] - '0' : 0; int y = j >= 0 ? b[j] - '0' : 0; int result = x + y + add; builder.push_back(result % 10 + '0'); add = result / 10; i--; j--; } reverse(builder.begin(), builder.end()); return builder; } }; ``` ```go // by chatGPT (go) import ( "strconv" "strings" ) // isAdditiveNumber - 穷举前两个数字 func isAdditiveNumber(num string) bool { n := len(num) for i := 1; i <= n; i++ { for j := i + 1; j <= n; j++ { first := num[:i] second := num[i:j] if isValid(num, first, second) { return true } } } return false } // isValid - 定义:num 前两个数字分别是 first 和 second,判断 num 是否满足累加数的性质 func isValid(num string, first string, second string) bool { if (strings.HasPrefix(first, "0") && len(first) > 1) || (strings.HasPrefix(second, "0") && len(second) > 1) { // 0 开头的数字,只能是 0 本身 return false } sumStr := strAdd(first, second) next := num[len(first)+len(second):] if !strings.HasPrefix(next, sumStr) { // 不满足累加数的性质 return false } if next == sumStr { // 已经匹配完整个字符串 return true } // 根据递归函数的定义,继续匹配后面的三个数字,我这里用递归的方式去比较,因为容易写 // 你也可以改用迭代写法,一样的 return isValid(num[len(first):], second, sumStr) } // strAdd - 模拟加法竖式运算,具体可以看下这道题 // https://leetcode.cn/problems/add-strings/ func strAdd(a, b string) string { i, j, carry := len(a)-1, len(b)-1, 0 ans := "" for i >= 0 || j >= 0 { x, _ := strconv.Atoi(string(a[i])) y, _ := strconv.Atoi(string(b[j])) sum := x + y + carry tmp := sum % 10 carry = sum / 10 ans = strconv.Itoa(tmp) + ans i-- j-- } if carry > 0 { ans = "1" + ans } return ans } ``` ```java // by labuladong (java) class Solution { public boolean isAdditiveNumber(String num) { // 穷举前两个数字 int n = num.length(); for (int i = 1; i <= n; i++) { // 先穷举第一个数字 String first = num.substring(0, i); for (int j = i + 1; j <= n; j++) { // 再穷举第二个数字 String second = num.substring(i, j); if (isValid(num, first, second)) { return true; } } } return false; } // 定义:num 前两个数字分别是 first 和 second,判断 num 是否满足累加数的性质 boolean isValid(String num, String first, String second) { if (first.startsWith("0") && first.length() > 1 || second.startsWith("0") && second.length() > 1) { // 0 开头的数字,只能是 0 本身 return false; } String sumStr = strAdd(first, second); String next = num.substring(first.length() + second.length()); if (!next.startsWith(sumStr)) { // 不满足累加数的性质 return false; } if (next.equals(sumStr)) { // 已经匹配完整个字符串 return true; } // 根据递归函数的定义,继续匹配后面的三个数字,我这里用递归的方式去比较,因为容易写 // 你也可以改用迭代写法,一样的 return isValid(num.substring(first.length()), second, sumStr); } // 模拟加法竖式运算,具体可以看下这道题 // https://leetcode.cn/problems/add-strings/ String strAdd(String a, String b) { int n = a.length(), m = b.length(); int i = n - 1, j = m - 1, add = 0; StringBuilder builder = new StringBuilder(); while (i >= 0 || j >= 0 || add != 0) { int x = i >= 0 ? a.charAt(i) - '0' : 0; int y = j >= 0 ? b.charAt(j) - '0' : 0; int result = x + y + add; builder.append(result % 10); add = result / 10; i--; j--; } return builder.reverse().toString(); } } ``` ```javascript // by chatGPT (javascript) var isAdditiveNumber = function(num) { // 穷举前两个数字 let n = num.length; for (let i = 1; i <= n; i++) { for (let j = i + 1; j <= n; j++) { let first = num.substring(0, i); let second = num.substring(i, j); if (isValid(num, first, second)) { return true; } } } return false; } var isValid = function(num, first, second) { if ((first.startsWith("0") && first.length > 1) || (second.startsWith("0") && second.length > 1)) { // 0 开头的数字,只能是 0 本身 return false; } let sumStr = strAdd(first, second); let next = num.substring(first.length + second.length); if (!next.startsWith(sumStr)) { // 不满足累加数的性质 return false; } if (next === sumStr) { // 已经匹配完整个字符串 return true; } // 根据递归函数的定义,继续匹配后面的三个数字,我这里用递归的方式去比较,因为容易写 // 你也可以改用迭代写法,一样的 return isValid(num.substring(first.length), second, sumStr); } var strAdd = function(a, b) { let n = a.length, m = b.length; let i = n - 1, j = m - 1, add = 0; let builder = []; while (i >= 0 || j >= 0 || add != 0) { let x = i >= 0 ? a.charAt(i) - '0' : 0; let y = j >= 0 ? b.charAt(j) - '0' : 0; let result = x + y + add; builder.push(result % 10); add = Math.floor(result / 10); i--; j--; } return builder.reverse().join(''); } ``` ```python # by chatGPT (python) class Solution: def isAdditiveNumber(self, num): # 穷举前两个数字 n = len(num) for i in range(1, n + 1): for j in range(i + 1, n + 1): first = num[0 : i] second = num[i : j] if self.isValid(num, first, second): return True return False def isValid(self, num, first, second): # 定义:num 前两个数字分别是 first 和 second,判断 num 是否满足累加数的性质 if (first.startswith("0") and len(first) > 1) or (second.startswith("0") and len(second) > 1): # 0 开头的数字,只能是 0 本身 return False sumStr = self.strAdd(first, second) next = num[len(first) + len(second):] if not next.startswith(sumStr): # 不满足累加数的性质 return False if next == sumStr: # 已经匹配完整个字符串 return True # 根据递归函数的定义,继续匹配后面的三个数字,我这里用递归的方式去比较,因为容易写 # 你也可以改用迭代写法,一样的 return self.isValid(num[len(first):], second, sumStr) def strAdd(self, a, b): # 模拟加法竖式运算,具体可以看下这道题 # https://leetcode.cn/problems/add-strings/ n, m = len(a), len(b) i, j, add = n - 1, m - 1, 0 builder = [] while i >= 0 or j >= 0 or add != 0: x = int(a[i]) if i >= 0 else 0 y = int(b[j]) if j >= 0 else 0 result = x + y + add builder.append(result % 10) add = result // 10 i -= 1 j -= 1 return ''.join(str(i) for i in builder[::-1]) ``` https://leetcode.cn/problems/additive-number 的多语言解法👆 https://leetcode.cn/problems/advantage-shuffle 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: vector advantageCount(vector& nums1, vector& nums2) { int n = nums1.size(); // 给 nums2 降序排序 priority_queue> maxpq; for (int i = 0; i < n; i++) { maxpq.push({i, nums2[i]}); } // 给 nums1 升序排序 sort(nums1.begin(), nums1.end()); // nums1[left] 是最小值,nums1[right] 是最大值 int left = 0, right = n - 1; vector res(n); while (!maxpq.empty()) { auto [i, maxval] = maxpq.top(); maxpq.pop(); // maxval 是 nums2 中的最大值,i 是对应索引 if (maxval < nums1[right]) { // 如果 nums1[right] 能胜过 maxval,那就自己上 res[i] = nums1[right]; right--; } else { // 否则用最小值混一下,养精蓄锐 res[i] = nums1[left]; left++; } } return res; } }; ``` ```go // by chatGPT (go) func advantageCount(nums1 []int, nums2 []int) []int { n := len(nums1) // 给 nums2 降序排序 maxpq := make(PriorityQueue, 0) heap.Init(&maxpq) for i := 0; i < n; i++ { heap.Push(&maxpq, []int{i, nums2[i]}) } // 给 nums1 升序排序 sort.Ints(nums1) // nums1[left] 是最小值,nums1[right] 是最大值 left, right := 0, n-1 res := make([]int, n) for maxpq.Len() > 0 { pair := heap.Pop(&maxpq).([]int) // maxval 是 nums2 中的最大值,i 是对应索引 i, maxval := pair[0], pair[1] if maxval < nums1[right] { // 如果 nums1[right] 能胜过 maxval,那就自己上 res[i] = nums1[right] right-- } else { // 否则用最小值混一下,养精蓄锐 res[i] = nums1[left] left++ } } return res } // 定义一个优先队列类型 PriorityQueue,用于按照指定比较函数排序 type PriorityQueue [][]int func (pq PriorityQueue) Len() int { return len(pq) } func (pq PriorityQueue) Less(i, j int) bool { return pq[i][1] > pq[j][1] } func (pq PriorityQueue) Swap(i, j int) { pq[i], pq[j] = pq[j], pq[i] } func (pq *PriorityQueue) Push(x interface{}) { item := x.([]int) *pq = append(*pq, item) } func (pq *PriorityQueue) Pop() interface{} { old := *pq n := len(old) item := old[n-1] *pq = old[0 : n-1] return item } ``` ```java // by labuladong (java) class Solution { public int[] advantageCount(int[] nums1, int[] nums2) { int n = nums1.length; // 给 nums2 降序排序 PriorityQueue maxpq = new PriorityQueue<>( (int[] pair1, int[] pair2) -> { return pair2[1] - pair1[1]; } ); for (int i = 0; i < n; i++) { maxpq.offer(new int[]{i, nums2[i]}); } // 给 nums1 升序排序 Arrays.sort(nums1); // nums1[left] 是最小值,nums1[right] 是最大值 int left = 0, right = n - 1; int[] res = new int[n]; while (!maxpq.isEmpty()) { int[] pair = maxpq.poll(); // maxval 是 nums2 中的最大值,i 是对应索引 int i = pair[0], maxval = pair[1]; if (maxval < nums1[right]) { // 如果 nums1[right] 能胜过 maxval,那就自己上 res[i] = nums1[right]; right--; } else { // 否则用最小值混一下,养精蓄锐 res[i] = nums1[left]; left++; } } return res; } } ``` ```javascript // by chatGPT (javascript) var advantageCount = function(nums1, nums2) { var n = nums1.length; //给 nums2 降序排序 var maxpq = new PriorityQueue((pair1, pair2) => pair2[1] - pair1[1]); for (var i = 0; i < n; i++) { maxpq.offer([i, nums2[i]]); } //给 nums1 升序排序 nums1.sort((a, b) => a - b); // nums1[left] 是最小值,nums1[right] 是最大值 var left = 0, right = n - 1; var res = new Array(n); while (!maxpq.isEmpty()) { var pair = maxpq.poll(); // maxval 是 nums2 中的最大值,i 是对应索引 var i = pair[0], maxval = pair[1]; if (maxval < nums1[right]) { // 如果 nums1[right] 能胜过 maxval,那就自己上 res[i] = nums1[right]; right--; } else { // 否则用最小值混一下,养精蓄锐 res[i] = nums1[left]; left++; } } return res; } ``` ```python # by chatGPT (python) class Solution: def advantageCount(self, nums1: List[int], nums2: List[int]) -> List[int]: n = len(nums1) # 给 nums2 降序排序 maxpq = [] for i in range(n): maxpq.append([i, nums2[i]]) maxpq.sort(key=lambda x: -x[1]) # 给 nums1 升序排序 nums1.sort() # nums1[left] 是最小值,nums1[right] 是最大值 left, right = 0, n - 1 res = [0] * n while maxpq: pair = maxpq.pop(0) # maxval 是 nums2 中的最大值,i 是对应索引 i, maxval = pair[0], pair[1] if maxval < nums1[right]: # 如果 nums1[right] 能胜过 maxval,那就自己上 res[i] = nums1[right] right -= 1 else: # 否则用最小值混一下,养精蓄锐 res[i] = nums1[left] left += 1 return res ``` https://leetcode.cn/problems/advantage-shuffle 的多语言解法👆 https://leetcode.cn/problems/all-paths-from-source-to-target 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { // 记录所有路径 vector> res; public: vector> allPathsSourceTarget(vector>& graph) { deque path; traverse(graph, 0, path); return res; } /* 图的遍历框架 */ void traverse(vector>& graph, int s, deque& path) { // 添加节点 s 到路径 path.push_back(s); int n = graph.size(); if (s == n - 1) { // 到达终点 res.push_back(vector(path.begin(), path.end())); path.pop_back(); return; } // 递归每个相邻节点 for (int v : graph[s]) { traverse(graph, v, path); } // 从路径移出节点 s path.pop_back(); } }; ``` ```go // by chatGPT (go) func allPathsSourceTarget(graph [][]int) [][]int { res := [][]int{} path := []int{} var traverse func(graph [][]int, s int, path []int) traverse = func(graph [][]int, s int, path []int) { // 添加节点 s 到路径 path = append(path, s) n := len(graph) if s == n - 1 { // 到达终点 tmp := make([]int, len(path)) copy(tmp, path) res = append(res, tmp) path = path[:len(path) - 1] return } // 递归每个相邻节点 for _, v := range graph[s] { traverse(graph, v, path) } // 从路径移出节点 s path = path[:len(path) - 1] } traverse(graph, 0, path) return res } ``` ```java // by labuladong (java) class Solution { // 记录所有路径 List> res = new LinkedList<>(); public List> allPathsSourceTarget(int[][] graph) { LinkedList path = new LinkedList<>(); traverse(graph, 0, path); return res; } /* 图的遍历框架 */ void traverse(int[][] graph, int s, LinkedList path) { // 添加节点 s 到路径 path.addLast(s); int n = graph.length; if (s == n - 1) { // 到达终点 res.add(new LinkedList<>(path)); path.removeLast(); return; } // 递归每个相邻节点 for (int v : graph[s]) { traverse(graph, v, path); } // 从路径移出节点 s path.removeLast(); } } ``` ```javascript // by chatGPT (javascript) var allPathsSourceTarget = function(graph) { // 记录所有路径 var res = []; var traverse = function(graph, s, path) { // 添加节点 s 到路径 path.push(s); var n = graph.length; if (s === n - 1) { // 到达终点 res.push(path.slice()); path.pop(); return; } // 递归每个相邻节点 for (var i = 0; i < graph[s].length; i++) { traverse(graph, graph[s][i], path); } // 从路径移出节点 s path.pop(); }; var path = []; traverse(graph, 0, path); return res; }; ``` ```python # by chatGPT (python) from typing import List class Solution: def __init__(self): # 记录所有路径 self.res = [] def allPathsSourceTarget(self, graph: List[List[int]]) -> List[List[int]]: path = [] self.traverse(graph, 0, path) return self.res """ 图的遍历框架 """ def traverse(self, graph: List[List[int]], s: int, path: List[int]) -> None: # 添加节点 s 到路径 path.append(s) n = len(graph) if s == n - 1: # 到达终点 self.res.append(path[:]) path.pop() return # 递归每个相邻节点 for v in graph[s]: self.traverse(graph, v, path) # 从路径移出节点 s path.pop() ``` https://leetcode.cn/problems/all-paths-from-source-to-target 的多语言解法👆 https://leetcode.cn/problems/bP4bmD 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { // 记录所有路径 vector> res; public: vector> allPathsSourceTarget(vector>& graph) { deque path; traverse(graph, 0, path); return res; } /* 图的遍历框架 */ void traverse(vector>& graph, int s, deque& path) { // 添加节点 s 到路径 path.push_back(s); int n = graph.size(); if (s == n - 1) { // 到达终点 res.push_back(vector(path.begin(), path.end())); path.pop_back(); return; } // 递归每个相邻节点 for (int v : graph[s]) { traverse(graph, v, path); } // 从路径移出节点 s path.pop_back(); } }; ``` ```go // by chatGPT (go) func allPathsSourceTarget(graph [][]int) [][]int { // 记录所有路径 res := [][]int{} var traverse func(graph [][]int, s int, path []int) traverse = func(graph [][]int, s int, path []int) { // 添加节点 s 到路径 path = append(path, s) n := len(graph) if s == n-1 { // 到达终点 tmp := make([]int, len(path)) copy(tmp, path) res = append(res, tmp) path = path[:len(path)-1] return } // 递归每个相邻节点 for _, v := range graph[s] { traverse(graph, v, path) } // 从路径移出节点 s path = path[:len(path)-1] } path := make([]int, 0) traverse(graph, 0, path) return res } ``` ```java // by labuladong (java) class Solution { // 记录所有路径 List> res = new LinkedList<>(); public List> allPathsSourceTarget(int[][] graph) { LinkedList path = new LinkedList<>(); traverse(graph, 0, path); return res; } /* 图的遍历框架 */ void traverse(int[][] graph, int s, LinkedList path) { // 添加节点 s 到路径 path.addLast(s); int n = graph.length; if (s == n - 1) { // 到达终点 res.add(new LinkedList<>(path)); path.removeLast(); return; } // 递归每个相邻节点 for (int v : graph[s]) { traverse(graph, v, path); } // 从路径移出节点 s path.removeLast(); } } ``` ```javascript // by chatGPT (javascript) var allPathsSourceTarget = function(graph) { // 记录所有路径 let res = []; let traverse = function(graph, s, path) { // 添加节点 s 到路径 path.push(s); let n = graph.length; if (s == n - 1) { // 到达终点 res.push([...path]); path.pop(); return; } // 递归每个相邻节点 for (let v of graph[s]) { traverse(graph, v, path); } // 从路径移出节点 s path.pop(); }; let path = []; traverse(graph, 0, path); return res; }; ``` ```python # by chatGPT (python) class Solution: # 记录所有路径 res = [] def allPathsSourceTarget(self, graph: List[List[int]]) -> List[List[int]]: path = [] self.traverse(graph, 0, path) return self.res # 图的遍历框架 def traverse(self, graph: List[List[int]], s: int, path: List[int]) -> None: # 添加节点 s 到路径 path.append(s) n = len(graph) if s == n - 1: # 到达终点 self.res.append(path[:]) path.pop() return # 递归每个相邻节点 for v in graph[s]: self.traverse(graph, v, path) # 从路径移出节点 s path.pop() ``` https://leetcode.cn/problems/bP4bmD 的多语言解法👆 https://leetcode.cn/problems/ba-shu-zi-fan-yi-cheng-zi-fu-chuan-lcof 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int translateNum(int num) { string s = to_string(num); int n = s.length(); if (n < 1) { return 0; } // 定义:dp[i] 表示 s[0..i-1] 的解码方式数量 vector dp(n + 1); // base case: s 为空或者 s 只有一个字符的情况 dp[0] = 1; dp[1] = 1; // 注意 dp 数组和 s 之间的索引偏移一位 for (int i = 2; i <= n; i++) { char c = s[i - 1], d = s[i - 2]; if ('0' <= c && c <= '9') { // 1. s[i] 本身可以作为一个字母 dp[i] += dp[i - 1]; } if (d == '1' || d == '2' && c <= '5') { // 2. s[i] 和 s[i - 1] 结合起来表示一个字母 dp[i] += dp[i - 2]; } } return dp[n]; } }; ``` ```go // by chatGPT (go) func translateNum(num int) int { s := strconv.Itoa(num) n := len(s) if n < 1 { return 0 } // 定义:dp[i] 表示 s[0..i-1] 的解码方式数量 dp := make([]int, n+1) // base case: s 为空或者 s 只有一个字符的情况 dp[0] = 1 dp[1] = 1 // 注意 dp 数组和 s 之间的索引偏移一位 for i := 2; i <= n; i++ { c, d := s[i-1], s[i-2] if '0' <= c && c <= '9' { // 1. s[i] 本身可以作为一个字母 dp[i] += dp[i-1] } if (d == '1' || (d == '2' && c <= '5')) { // 2. s[i] 和 s[i - 1] 结合起来表示一个字母 dp[i] += dp[i-2] } } return dp[n] } ``` ```java // by labuladong (java) class Solution { public int translateNum(int num) { String s = num + ""; int n = s.length(); if (n < 1) { return 0; } // 定义:dp[i] 表示 s[0..i-1] 的解码方式数量 int[] dp = new int[n + 1]; // base case: s 为空或者 s 只有一个字符的情况 dp[0] = 1; dp[1] = 1; // 注意 dp 数组和 s 之间的索引偏移一位 for (int i = 2; i <= n; i++) { char c = s.charAt(i - 1), d = s.charAt(i - 2); if ('0' <= c && c <= '9') { // 1. s[i] 本身可以作为一个字母 dp[i] += dp[i - 1]; } if (d == '1' || d == '2' && c <= '5') { // 2. s[i] 和 s[i - 1] 结合起来表示一个字母 dp[i] += dp[i - 2]; } } return dp[n]; } } ``` ```javascript // by chatGPT (javascript) var translateNum = function(num) { var s = num.toString(); var n = s.length; if (n < 1) { return 0; } // 定义:dp[i] 表示 s[0..i-1] 的解码方式数量 var dp = new Array(n + 1).fill(0); // base case: s 为空或者 s 只有一个字符的情况 dp[0] = 1; dp[1] = 1; // 注意 dp 数组和 s 之间的索引偏移一位 for (var i = 2; i <= n; i++) { var c = s.charAt(i - 1), d = s.charAt(i - 2); if ('0' <= c && c <= '9') { // 1. s[i] 本身可以作为一个字母 dp[i] += dp[i - 1]; } if (d == '1' || d == '2' && c <= '5') { // 2. s[i] 和 s[i - 1] 结合起来表示一个字母 dp[i] += dp[i - 2]; } } return dp[n]; }; ``` ```python # by chatGPT (python) class Solution: def translateNum(self, num: int) -> int: s = str(num) n = len(s) if n < 1: return 0 # 定义:dp[i] 表示 s[0..i-1] 的解码方式数量 dp = [0] * (n + 1) # base case: s 为空或者 s 只有一个字符的情况 dp[0] = 1 dp[1] = 1 # 注意 dp 数组和 s 之间的索引偏移一位 for i in range(2, n + 1): c = s[i - 1] d = s[i - 2] if '0' <= c <= '9': # 1. s[i] 本身可以作为一个字母 dp[i] += dp[i - 1] if d == '1' or (d == '2' and c <= '5'): # 2. s[i] 和 s[i - 1] 结合起来表示一个字母 dp[i] += dp[i - 2] return dp[n] ``` https://leetcode.cn/problems/ba-shu-zi-fan-yi-cheng-zi-fu-chuan-lcof 的多语言解法👆 https://leetcode.cn/problems/ba-shu-zu-pai-cheng-zui-xiao-de-shu-lcof 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: string minNumber(vector& nums) { int n = nums.size(); vector strs(n); for (int i = 0; i < n; i++) { strs[i] = to_string(nums[i]); } sort(strs.begin(), strs.end(), [](const string& s1, const string& s2) { // 看看那种拼接方式得到的数字更小,排前面 // 不用转成 int 类型,因为字符串的比较算法和正整数的比较算法是一样的 // 而且拼接字符串比较长,会导致 int 类型溢出 return (s1 + s2) < (s2 + s1); }); return accumulate(strs.begin(), strs.end(), string("")); } }; ``` ```go // by chatGPT (go) func minNumber(nums []int) string { n := len(nums) strs := make([]string, n) for i := 0; i < n; i++ { strs[i] = strconv.Itoa(nums[i]) } sort.Slice(strs, func(i, j int) bool { // 看看那种拼接方式得到的数字更小,排前面 // 不用转成 int 类型,因为字符串的比较算法和正整数的比较算法是一样的 // 而且拼接字符串比较长,会导致 int 类型溢出 return strs[i]+strs[j] < strs[j]+strs[i] }) return strings.Join(strs, "") } ``` ```java // by labuladong (java) class Solution { public String minNumber(int[] nums) { int n = nums.length; String[] strs = new String[n]; for (int i = 0; i < n; i++) { strs[i] = Integer.toString(nums[i]); } Arrays.sort(strs, (s1, s2) -> { // 看看那种拼接方式得到的数字更小,排前面 // 不用转成 int 类型,因为字符串的比较算法和正整数的比较算法是一样的 // 而且拼接字符串比较长,会导致 int 类型溢出 return (s1 + s2).compareTo(s2 + s1); }); return String.join("", strs); } } ``` ```javascript // by chatGPT (javascript) var minNumber = function(nums) { const n = nums.length; const strs = new Array(n); for (let i = 0; i < n; i++) { strs[i] = nums[i].toString(); } strs.sort((s1, s2) => { // 看看那种拼接方式得到的数字更小,排前面 // 不用转成 int 类型,因为字符串的比较算法和正整数的比较算法是一样的 // 而且拼接字符串比较长,会导致 int 类型溢出 return (s1 + s2).localeCompare(s2 + s1); }); return strs.join(""); } ``` ```python # by chatGPT (python) class Solution: def minNumber(self, nums: List[int]) -> str: n = len(nums) strs = [str(num) for num in nums] def compare(s1: str, s2: str) -> int: # 看看那种拼接方式得到的数字更小,排前面 # 不用转成 int 类型,因为字符串的比较算法和正整数的比较算法是一样的 # 而且拼接字符串比较长,会导致 int 类型溢出 return -1 if s1 + s2 < s2 + s1 else 1 strs.sort(key=functools.cmp_to_key(compare)) return ''.join(strs) ``` https://leetcode.cn/problems/ba-shu-zu-pai-cheng-zui-xiao-de-shu-lcof 的多语言解法👆 https://leetcode.cn/problems/ba-zi-fu-chuan-zhuan-huan-cheng-zheng-shu-lcof 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int strToInt(string str) { int n = str.length(); int i = 0; // 记录正负号 int sign = 1; // 用 long 避免 int 溢出 long res = 0; // 跳过前导空格 while (i < n && str[i] == ' ') { i++; } if (i == n) { return 0; } // 记录符号位 if (str[i] == '-') { sign = -1; i++; } else if (str[i] == '+') { i++; } if (i == n) { return 0; } // 统计数字位 while (i < n && '0' <= str[i] && str[i] <= '9') { res = res * 10 + str[i] - '0'; if (res > INT_MAX) { break; } i++; } // 如果溢出,强转成 int 就会和真实值不同 if ((int) res != res) { return sign == 1 ? INT_MAX : INT_MIN; } return (int) res * sign; } }; ``` ```go // by chatGPT (go) func myAtoi(str string) int { n := len(str) i := 0 // 记录正负号 sign := 1 // 用 long 避免 int 溢出 var res int64 = 0 // 跳过前导空格 for i < n && str[i] == ' ' { i++ } if i == n { return 0 } // 记录符号位 if str[i] == '-' { sign = -1 i++ } else if str[i] == '+' { i++ } if i == n { return 0 } // 统计数字位 for i < n && '0' <= str[i] && str[i] <= '9' { res = res * 10 + int64(str[i]-'0') if res > math.MaxInt32 { break } i++ } // 如果溢出,强转成 int 就会和真实值不同 if int(res) != res { if sign == 1 { return math.MaxInt32 } else { return math.MinInt32 } } return int(res) * sign } ``` ```java // by labuladong (java) class Solution { public int myAtoi(String str) { int n = str.length(); int i = 0; // 记录正负号 int sign = 1; // 用 long 避免 int 溢出 long res = 0; // 跳过前导空格 while (i < n && str.charAt(i) == ' ') { i++; } if (i == n) { return 0; } // 记录符号位 if (str.charAt(i) == '-') { sign = -1; i++; } else if (str.charAt(i) == '+') { i++; } if (i == n) { return 0; } // 统计数字位 while (i < n && '0' <= str.charAt(i) && str.charAt(i) <= '9') { res = res * 10 + str.charAt(i) - '0'; if (res > Integer.MAX_VALUE) { break; } i++; } // 如果溢出,强转成 int 就会和真实值不同 if ((int) res != res) { return sign == 1 ? Integer.MAX_VALUE : Integer.MIN_VALUE; } return (int) res * sign; } } ``` ```javascript // by chatGPT (javascript) var myAtoi = function(str) { let n = str.length; let i = 0; // 记录正负号 let sign = 1; // 用 BigInt 避免 int 溢出 let res = BigInt(0); // 跳过前导空格 while (i < n && str[i] === ' ') { i++; } if (i === n) { return 0; } // 记录符号位 if (str[i] === '-') { sign = -1; i++; } else if (str[i] === '+') { i++; } if (i === n) { return 0; } // 统计数字位 while (i < n && '0' <= str[i] && str[i] <= '9') { res = res * BigInt(10) + BigInt(str[i].charCodeAt() - '0'.charCodeAt()); if (res > BigInt(Number.MAX_VALUE)) { break; } i++; } // 如果溢出,强转成 int 就会和真实值不同 if (res > BigInt(Number.MAX_VALUE)) { return sign === 1 ? Number.MAX_VALUE : Number.MIN_VALUE; } return Number(res) * sign; }; ``` ```python # by chatGPT (python) class Solution: def myAtoi(self, s: str) -> int: n = len(s) i = 0 # 记录正负号 sign = 1 # 用 long 避免 int 溢出 res = 0 # 跳过前导空格 while i < n and s[i] == ' ': i += 1 if i == n: return 0 # 记录符号位 if s[i] == '-': sign = -1 i += 1 elif s[i] == '+': i += 1 if i == n: return 0 # 统计数字位 while i < n and '0' <= s[i] <= '9': res = res * 10 + ord(s[i]) - ord('0') if res > pow(2, 31) - 1: break i += 1 # 如果溢出,强转成 int 就会和真实值不同 if res != int(res): return sign == 1 and pow(2, 31) - 1 or -pow(2, 31) return int(res) * sign ``` https://leetcode.cn/problems/ba-zi-fu-chuan-zhuan-huan-cheng-zheng-shu-lcof 的多语言解法👆 https://leetcode.cn/problems/bao-han-minhan-shu-de-zhan-lcof 的多语言解法👇 ```cpp // by chatGPT (cpp) // 原始思路 class MinStack1 { // 记录栈中的所有元素 stack stk; // 阶段性记录栈中的最小元素 stack minStk; public: void push(int val) { stk.push(val); // 维护 minStk 栈顶为全栈最小元素 if (minStk.empty() || val <= minStk.top()) { // 新插入的这个元素就是全栈最小的 minStk.push(val); } else { // 插入的这个元素比较大 minStk.push(minStk.top()); } } void pop() { stk.pop(); minStk.pop(); } int top() { return stk.top(); } int getMin() { // minStk 栈顶为全栈最小元素 return minStk.top(); } }; // 优化版 class MinStack { // 记录栈中的所有元素 stack stk; // 阶段性记录栈中的最小元素 stack minStk; public: void push(int val) { stk.push(val); // 维护 minStk 栈顶为全栈最小元素 if (minStk.empty() || val <= minStk.top()) { // 新插入的这个元素就是全栈最小的 minStk.push(val); } } void pop() { // 注意 Java 的语言特性,比较 Integer 相等要用 equals 方法 if (stk.top() == minStk.top()) { // 弹出的元素是全栈最小的 minStk.pop(); } stk.pop(); } int top() { return stk.top(); } int getMin() { // minStk 栈顶为全栈最小元素 return minStk.top(); } }; ``` ```go // by chatGPT (go) type MinStack1 struct { stk []int minStk []int } func (this *MinStack1) Push(val int) { this.stk = append(this.stk, val) // 维护 minStk 栈顶为全栈最小元素 if len(this.minStk) == 0 || val <= this.minStk[len(this.minStk)-1] { // 新插入的这个元素就是全栈最小的 this.minStk = append(this.minStk, val) } else { // 插入的这个元素比较大 this.minStk = append(this.minStk, this.minStk[len(this.minStk)-1]) } } func (this *MinStack1) Pop() { this.stk = this.stk[:len(this.stk)-1] this.minStk = this.minStk[:len(this.minStk)-1] } func (this *MinStack1) Top() int { return this.stk[len(this.stk)-1] } func (this *MinStack1) GetMin() int { // minStk 栈顶为全栈最小元素 return this.minStk[len(this.minStk)-1] } type MinStack struct { stk []int minStk []int } func (this *MinStack) Push(val int) { this.stk = append(this.stk, val) // 维护 minStk 栈顶为全栈最小元素 if len(this.minStk) == 0 || val <= this.minStk[len(this.minStk)-1] { // 新插入的这个元素就是全栈最小的 this.minStk = append(this.minStk, val) } } func (this *MinStack) Pop() { // 注意 go 的语言特性,比较 int 相等可以直接使用 == if this.stk[len(this.stk)-1] == this.minStk[len(this.minStk)-1] { // 弹出的元素是全栈最小的 this.minStk = this.minStk[:len(this.minStk)-1] } this.stk = this.stk[:len(this.stk)-1] } func (this *MinStack) Top() int { return this.stk[len(this.stk)-1] } func (this *MinStack) GetMin() int { // minStk 栈顶为全栈最小元素 return this.minStk[len(this.minStk)-1] } ``` ```java // by labuladong (java) // 原始思路 class MinStack1 { // 记录栈中的所有元素 Stack stk = new Stack<>(); // 阶段性记录栈中的最小元素 Stack minStk = new Stack<>(); public void push(int val) { stk.push(val); // 维护 minStk 栈顶为全栈最小元素 if (minStk.isEmpty() || val <= minStk.peek()) { // 新插入的这个元素就是全栈最小的 minStk.push(val); } else { // 插入的这个元素比较大 minStk.push(minStk.peek()); } } public void pop() { stk.pop(); minStk.pop(); } public int top() { return stk.peek(); } public int getMin() { // minStk 栈顶为全栈最小元素 return minStk.peek(); } } // 优化版 class MinStack { // 记录栈中的所有元素 Stack stk = new Stack<>(); // 阶段性记录栈中的最小元素 Stack minStk = new Stack<>(); public void push(int val) { stk.push(val); // 维护 minStk 栈顶为全栈最小元素 if (minStk.isEmpty() || val <= minStk.peek()) { // 新插入的这个元素就是全栈最小的 minStk.push(val); } } public void pop() { // 注意 Java 的语言特性,比较 Integer 相等要用 equals 方法 if (stk.peek().equals(minStk.peek())) { // 弹出的元素是全栈最小的 minStk.pop(); } stk.pop(); } public int top() { return stk.peek(); } public int getMin() { // minStk 栈顶为全栈最小元素 return minStk.peek(); } } ``` ```javascript // by chatGPT (javascript) /** * @constructor */ var MinStack1 = function() { // 记录栈中的所有元素 this.stk = []; // 阶段性记录栈中的最小元素 this.minStk = []; }; /** * @param {number} val * @return {void} */ MinStack1.prototype.push = function(val) { this.stk.push(val); // 维护 minStk 栈顶为全栈最小元素 if (this.minStk.length === 0 || val <= this.minStk[this.minStk.length - 1]) { // 新插入的这个元素就是全栈最小的 this.minStk.push(val); } else { // 插入的这个元素比较大 this.minStk.push(this.minStk[this.minStk.length - 1]); } }; /** * @return {void} */ MinStack1.prototype.pop = function() { this.stk.pop(); this.minStk.pop(); }; /** * @return {number} */ MinStack1.prototype.top = function() { return this.stk[this.stk.length - 1]; }; /** * @return {number} */ MinStack1.prototype.getMin = function() { // minStk 栈顶为全栈最小元素 return this.minStk[this.minStk.length - 1]; }; /** * @constructor */ var MinStack = function() { // 记录栈中的所有元素 this.stk = []; // 阶段性记录栈中的最小元素 this.minStk = []; }; /** * @param {number} val * @return {void} */ MinStack.prototype.push = function(val) { this.stk.push(val); // 维护 minStk 栈顶为全栈最小元素 if (this.minStk.length === 0 || val <= this.minStk[this.minStk.length - 1]) { // 新插入的这个元素就是全栈最小的 this.minStk.push(val); } }; /** * @return {void} */ MinStack.prototype.pop = function() { // 注意 JavaScript 的语言特性,比较 Number 可以直接使用 === if (this.stk[this.stk.length - 1] === this.minStk[this.minStk.length - 1]) { // 弹出的元素是全栈最小的 this.minStk.pop(); } this.stk.pop(); }; /** * @return {number} */ MinStack.prototype.top = function() { return this.stk[this.stk.length - 1]; }; /** * @return {number} */ MinStack.prototype.getMin = function() { // minStk 栈顶为全栈最小元素 return this.minStk[this.minStk.length - 1]; }; ``` ```python # by chatGPT (python) # 原始思路 class MinStack1: def __init__(self): # 记录栈中的所有元素 self.stk = [] # 阶段性记录栈中的最小元素 self.minStk = [] def push(self, val: int) -> None: self.stk.append(val) # 维护 minStk 栈顶为全栈最小元素 if not self.minStk or val <= self.minStk[-1]: # 新插入的这个元素就是全栈最小的 self.minStk.append(val) else: # 插入的这个元素比较大 self.minStk.append(self.minStk[-1]) def pop(self) -> None: self.stk.pop() self.minStk.pop() def top(self) -> int: return self.stk[-1] def getMin(self) -> int: # minStk 栈顶为全栈最小元素 return self.minStk[-1] # 优化版 class MinStack: def __init__(self): # 记录栈中的所有元素 self.stk = [] # 阶段性记录栈中的最小元素 self.minStk = [] def push(self, val: int) -> None: self.stk.append(val) # 维护 minStk 栈顶为全栈最小元素 if not self.minStk or val <= self.minStk[-1]: # 新插入的这个元素就是全栈最小的 self.minStk.append(val) def pop(self) -> None: # 注意 Python 的语言特性,比较 Integer 相等不需要调用 equals 方法 if self.stk[-1] == self.minStk[-1]: # 弹出的元素是全栈最小的 self.minStk.pop() self.stk.pop() def top(self) -> int: return self.stk[-1] def getMin(self) -> int: # minStk 栈顶为全栈最小元素 return self.minStk[-1] ``` https://leetcode.cn/problems/bao-han-minhan-shu-de-zhan-lcof 的多语言解法👆 https://leetcode.cn/problems/best-time-to-buy-and-sell-stock 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int maxProfit(vector& prices) { int n = prices.size(); vector> dp(n, vector(2)); for (int i = 0; i < n; i++) { if (i - 1 == -1) { // base case dp[i][0] = 0; dp[i][1] = -prices[i]; continue; } dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] + prices[i]); dp[i][1] = max(dp[i - 1][1], -prices[i]); } return dp[n - 1][0]; } }; ``` ```go // by chatGPT (go) func maxProfit(prices []int) int { n := len(prices) dp := make([][]int, n) for i := 0; i < n; i++ { dp[i] = make([]int, 2) if i - 1 == -1 { // base case dp[i][0] = 0 dp[i][1] = -prices[i] continue } dp[i][0] = max(dp[i-1][0], dp[i-1][1]+prices[i]) dp[i][1] = max(dp[i-1][1], -prices[i]) } return dp[n-1][0] } func max(a, b int) int { if a > b { return a } return b } ``` ```java // by labuladong (java) class Solution { public int maxProfit(int[] prices) { int n = prices.length; int[][] dp = new int[n][2]; for (int i = 0; i < n; i++) { if (i - 1 == -1) { // base case dp[i][0] = 0; dp[i][1] = -prices[i]; continue; } dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i]); dp[i][1] = Math.max(dp[i - 1][1], -prices[i]); } return dp[n - 1][0]; } } ``` ```javascript // by chatGPT (javascript) /** * @param {number[]} prices * @return {number} */ var maxProfit = function(prices) { const n = prices.length; const dp = new Array(n).map(() => new Array(2)); for (let i = 0; i < n; i++) { if (i - 1 === -1) { // base case dp[i][0] = 0; dp[i][1] = -prices[i]; continue; } dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i]); dp[i][1] = Math.max(dp[i - 1][1], -prices[i]); } return dp[n - 1][0]; }; ``` ```python # by chatGPT (python) class Solution: def maxProfit(self, prices: List[int]) -> int: n = len(prices) dp = [[0] * 2 for _ in range(n)] for i in range(n): if i - 1 == -1: # base case dp[i][0] = 0 dp[i][1] = -prices[i] continue dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] + prices[i]) dp[i][1] = max(dp[i - 1][1], -prices[i]) return dp[n - 1][0] ``` https://leetcode.cn/problems/best-time-to-buy-and-sell-stock 的多语言解法👆 https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-ii 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int maxProfit(vector& prices) { int n = prices.size(); vector> dp(n, vector(2)); for (int i = 0; i < n; i++) { if (i - 1 == -1) { // base case dp[i][0] = 0; dp[i][1] = -prices[i]; continue; } dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] + prices[i]); dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] - prices[i]); } return dp[n - 1][0]; } }; ``` ```go // by chatGPT (go) func maxProfit(prices []int) int { n := len(prices) dp := make([][]int, n) for i := 0; i < n; i++ { dp[i] = make([]int, 2) if i-1 == -1 { // base case dp[i][0] = 0 dp[i][1] = -prices[i] continue } dp[i][0] = max(dp[i-1][0], dp[i-1][1]+prices[i]) dp[i][1] = max(dp[i-1][1], dp[i-1][0]-prices[i]) } return dp[n-1][0] } func max(a, b int) int { if a > b { return a } return b } ``` ```java // by labuladong (java) class Solution { public int maxProfit(int[] prices) { int n = prices.length; int[][] dp = new int[n][2]; for (int i = 0; i < n; i++) { if (i - 1 == -1) { // base case dp[i][0] = 0; dp[i][1] = -prices[i]; continue; } 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]); } return dp[n - 1][0]; } } ``` ```javascript // by chatGPT (javascript) var maxProfit = function(prices) { const n = prices.length; const dp = new Array(n).fill(0).map(() => new Array(2).fill(0)); for (let i = 0; i < n; i++) { if (i - 1 === -1) { // base case dp[i][0] = 0; dp[i][1] = -prices[i]; continue; } 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]); } return dp[n - 1][0]; }; ``` ```python # by chatGPT (python) class Solution: def maxProfit(self, prices: List[int]) -> int: n = len(prices) dp = [[0] * 2 for _ in range(n)] for i in range(n): if i - 1 == -1: dp[i][0] = 0 dp[i][1] = -prices[i] continue dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] + prices[i]) dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] - prices[i]) return dp[n - 1][0] ``` https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-ii 的多语言解法👆 https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-iii 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int maxProfit(vector& prices) { int max_k = 2, n = prices.size(); int dp[n][max_k + 1][2]; for (int i = 0; i < n; i++) { for (int k = max_k; k >= 1; k--) { if (i - 1 == -1) { // 处理 base case dp[i][k][0] = 0; dp[i][k][1] = -prices[i]; continue; } dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + prices[i]); dp[i][k][1] = max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i]); } } // 穷举了 n × max_k × 2 个状态,正确。 return dp[n - 1][max_k][0]; } }; ``` ```go // by chatGPT (go) func maxProfit(prices []int) int { max_k, n := 2, len(prices) dp := make([][][]int, n) for i := range dp { dp[i] = make([][]int, max_k+1) for j := range dp[i] { dp[i][j] = make([]int, 2) } } for i := 0; i < n; i++ { for k := max_k; k >= 1; k-- { if i-1 == -1 { // 处理 base case dp[i][k][0] = 0 dp[i][k][1] = -prices[i] continue } dp[i][k][0] = func() int { if dp[i-1][k][0] > dp[i-1][k][1]+prices[i] { return dp[i-1][k][0] } return dp[i-1][k][1] + prices[i] }() dp[i][k][1] = func() int { if dp[i-1][k][1] > dp[i-1][k-1][0]-prices[i] { return dp[i-1][k][1] } return dp[i-1][k-1][0] - prices[i] }() } } // 穷举了 n × max_k × 2 个状态,正确。 return dp[n-1][max_k][0] } ``` ```java // by labuladong (java) class Solution { public int maxProfit(int[] prices) { int max_k = 2, n = prices.length; int[][][] dp = new int[n][max_k + 1][2]; for (int i = 0; i < n; i++) { for (int k = max_k; k >= 1; k--) { if (i - 1 == -1) { // 处理 base case dp[i][k][0] = 0; dp[i][k][1] = -prices[i]; continue; } 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]); } } // 穷举了 n × max_k × 2 个状态,正确。 return dp[n - 1][max_k][0]; } } ``` ```javascript // by chatGPT (javascript) var maxProfit = function(prices) { let max_k = 2, n = prices.length; let dp = Array(n).fill().map(() => Array(max_k + 1).fill().map(() => Array(2).fill(0))); for (let i = 0; i < n; i++) { for (let k = max_k; k >= 1; k--) { if (i - 1 == -1) { // 处理 base case dp[i][k][0] = 0; dp[i][k][1] = -prices[i]; continue; } 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]); } } // 穷举了 n × max_k × 2 个状态,正确。 return dp[n - 1][max_k][0]; }; ``` ```python # by chatGPT (python) class Solution: def maxProfit(self, prices: List[int]) -> int: max_k = 2 n = len(prices) dp = [[[0 for _ in range(2)] for _ in range(max_k + 1)] for _ in range(n)] for i in range(n): for k in range(max_k, 0, -1): if i - 1 == -1: # 处理 base case dp[i][k][0] = 0 dp[i][k][1] = -prices[i] continue dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + prices[i]) dp[i][k][1] = max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i]) # 穷举了 n × max_k × 2 个状态,正确。 return dp[n - 1][max_k][0] ``` https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-iii 的多语言解法👆 https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-iv 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int maxProfit(int max_k, vector& prices) { int n = prices.size(); if (n <= 0) { return 0; } if (max_k > n / 2) { // 交易次数 k 没有限制的情况 return maxProfit_k_inf(prices); } // base case: // dp[-1][...][0] = dp[...][0][0] = 0 // dp[-1][...][1] = dp[...][0][1] = -infinity vector>> dp(n, vector>(max_k + 1, vector(2))); // k = 0 时的 base case for (int i = 0; i < n; i++) { dp[i][0][1] = INT_MIN; dp[i][0][0] = 0; } for (int i = 0; i < n; i++) { for (int k = max_k; k >= 1; k--) { if (i - 1 == -1) { // 处理 i = -1 时的 base case dp[i][k][0] = 0; dp[i][k][1] = -prices[i]; continue; } // 状态转移方程 dp[i][k][0] = max(dp[i - 1][k][0], dp[i - 1][k][1] + prices[i]); dp[i][k][1] = max(dp[i - 1][k][1], dp[i - 1][k - 1][0] - prices[i]); } } return dp[n - 1][max_k][0]; } private: // 第 122 题,k 无限的解法 int maxProfit_k_inf(vector& prices) { int n = prices.size(); vector> dp(n, vector(2)); for (int i = 0; i < n; i++) { if (i - 1 == -1) { // base case dp[i][0] = 0; dp[i][1] = -prices[i]; continue; } dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] + prices[i]); dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] - prices[i]); } return dp[n - 1][0]; } }; ``` ```go // by chatGPT (go) func maxProfit(max_k int, prices []int) int { n := len(prices) if n <= 0 { return 0 } if max_k > n/2 { // 交易次数 k 没有限制的情况 return maxProfitKInf(prices) } // base case: // dp[-1][...][0] = dp[...][0][0] = 0 // dp[-1][...][1] = dp[...][0][1] = -infinity dp := make([][][]int, n) for i := 0; i < n; i++ { dp[i] = make([][]int, max_k+1) for k := 0; k <= max_k; k++ { dp[i][k] = make([]int, 2) } } // k = 0 时的 base case for i := 0; i < n; i++ { dp[i][0][1] = -1 << 31 dp[i][0][0] = 0 } for i := 0; i < n; i++ { for k := max_k; k >= 1; k-- { if i-1 == -1 { // 处理 i = -1 时的 base case dp[i][k][0] = 0 dp[i][k][1] = -prices[i] continue } // 状态转移方程 dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1]+prices[i]) dp[i][k][1] = max(dp[i-1][k][1], dp[i-1][k-1][0]-prices[i]) } } return dp[n-1][max_k][0] } // 第 122 题,k 无限的解法 func maxProfitKInf(prices []int) int { n := len(prices) dp := make([][]int, n) for i := 0; i < n; i++ { dp[i] = make([]int, 2) } for i := 0; i < n; i++ { if i-1 == -1 { // base case dp[i][0] = 0 dp[i][1] = -prices[i] continue } dp[i][0] = max(dp[i-1][0], dp[i-1][1]+prices[i]) dp[i][1] = max(dp[i-1][1], dp[i-1][0]-prices[i]) } return dp[n-1][0] } func max(a, b int) int { if a > b { return a } return b } ``` ```java // by labuladong (java) class Solution { public int maxProfit(int max_k, int[] prices) { int n = prices.length; if (n <= 0) { return 0; } if (max_k > n / 2) { // 交易次数 k 没有限制的情况 return maxProfit_k_inf(prices); } // base case: // dp[-1][...][0] = dp[...][0][0] = 0 // dp[-1][...][1] = dp[...][0][1] = -infinity int[][][] dp = new int[n][max_k + 1][2]; // k = 0 时的 base case for (int i = 0; i < n; i++) { dp[i][0][1] = Integer.MIN_VALUE; dp[i][0][0] = 0; } for (int i = 0; i < n; i++) for (int k = max_k; k >= 1; k--) { if (i - 1 == -1) { // 处理 i = -1 时的 base case dp[i][k][0] = 0; dp[i][k][1] = -prices[i]; continue; } // 状态转移方程 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[n - 1][max_k][0]; } // 第 122 题,k 无限的解法 private int maxProfit_k_inf(int[] prices) { int n = prices.length; int[][] dp = new int[n][2]; for (int i = 0; i < n; i++) { if (i - 1 == -1) { // base case dp[i][0] = 0; dp[i][1] = -prices[i]; continue; } 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]); } return dp[n - 1][0]; } } ``` ```javascript // by chatGPT (javascript) var maxProfit = function(max_k, prices) { var n = prices.length; if (n <= 0) { return 0; } if (max_k > Math.floor(n / 2)) { // 交易次数 k 没有限制的情况 return maxProfit_k_inf(prices); } // base case: // dp[-1][...][0] = dp[...][0][0] = 0 // dp[-1][...][1] = dp[...][0][1] = -infinity var dp = new Array(n).fill(0).map(()=>new Array(max_k + 1).fill(0).map(()=>new Array(2).fill(0))); // k = 0 时的 base case for (var i = 0; i < n; i++) { dp[i][0][1] = Number.NEGATIVE_INFINITY; dp[i][0][0] = 0; } for (var i = 0; i < n; i++) for (var k = max_k; k >= 1; k--) { if (i - 1 == -1) { // 处理 i = -1 时的 base case dp[i][k][0] = 0; dp[i][k][1] = -prices[i]; continue; } // 状态转移方程 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[n - 1][max_k][0]; } // 第 122 题,k 无限的解法 var maxProfit_k_inf = function(prices) { var n = prices.length; var dp = new Array(n).fill(0).map(()=>new Array(2).fill(0)); for (var i = 0; i < n; i++) { if (i - 1 == -1) { // base case dp[i][0] = 0; dp[i][1] = -prices[i]; continue; } 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]); } return dp[n - 1][0]; } ``` ```python # by chatGPT (python) class Solution: def maxProfit(self, max_k: int, prices: List[int]) -> int: n = len(prices) if n <= 0: return 0 if max_k > n // 2: # 交易次数 k 没有限制的情况 return self.maxProfit_k_inf(prices) # base case: # dp[-1][...][0] = dp[...][0][0] = 0 # dp[-1][...][1] = dp[...][0][1] = -infinity dp = [[[0] * 2 for _ in range(max_k + 1)] for _ in range(n)] # k = 0 时的 base case for i in range(n): dp[i][0][1] = float('-inf') dp[i][0][0] = 0 for i in range(n): for k in range(max_k, 0, -1): if i - 1 == -1: # 处理 i = -1 时的 base case dp[i][k][0] = 0 dp[i][k][1] = -prices[i] continue # 状态转移方程 dp[i][k][0] = max(dp[i - 1][k][0], dp[i - 1][k][1] + prices[i]) dp[i][k][1] = max(dp[i - 1][k][1], dp[i - 1][k - 1][0] - prices[i]) return dp[n - 1][max_k][0] # 第 122 题,k 无限的解法 def maxProfit_k_inf(self, prices: List[int]) -> int: n = len(prices) dp = [[0] * 2 for _ in range(n)] for i in range(n): if i - 1 == -1: # base case dp[i][0] = 0 dp[i][1] = -prices[i] continue dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] + prices[i]) dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] - prices[i]) return dp[n - 1][0] ``` https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-iv 的多语言解法👆 https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-with-cooldown 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int maxProfit(vector& prices) { int n = prices.size(); vector> dp(n, vector(2)); for (int i = 0; i < n; i++) { if (i - 1 == -1) { // base case 1 dp[i][0] = 0; dp[i][1] = -prices[i]; continue; } if (i - 2 == -1) { // base case 2 dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] + prices[i]); // i - 2 小于 0 时根据状态转移方程推出对应 base case dp[i][1] = max(dp[i - 1][1], -prices[i]); // dp[i][1] // = max(dp[i-1][1], dp[-1][0] - prices[i]) // = max(dp[i-1][1], 0 - prices[i]) // = max(dp[i-1][1], -prices[i]) continue; } dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] + prices[i]); dp[i][1] = max(dp[i - 1][1], dp[i - 2][0] - prices[i]); } return dp[n - 1][0]; } }; ``` ```go // by chatGPT (go) func maxProfit(prices []int) int { n := len(prices) dp := make([][2]int, n) for i := 0; i < n; i++ { if i-1 == -1 { // base case 1 dp[i][0] = 0 dp[i][1] = -prices[i] continue } if i-2 == -1 { // base case 2 dp[i][0] = max(dp[i-1][0], dp[i-1][1]+prices[i]) // i - 2 小于 0 时根据状态转移方程推出对应 base case dp[i][1] = max(dp[i-1][1], -prices[i]) // dp[i][1] // = max(dp[i-1][1], dp[-1][0] - prices[i]) // = max(dp[i-1][1], 0 - prices[i]) // = max(dp[i-1][1], -prices[i]) continue } dp[i][0] = max(dp[i-1][0], dp[i-1][1]+prices[i]) dp[i][1] = max(dp[i-1][1], dp[i-2][0]-prices[i]) } return dp[n-1][0] } func max(x, y int) int { if x > y { return x } return y } ``` ```java // by labuladong (java) class Solution { public int maxProfit(int[] prices) { int n = prices.length; int[][] dp = new int[n][2]; for (int i = 0; i < n; i++) { if (i - 1 == -1) { // base case 1 dp[i][0] = 0; dp[i][1] = -prices[i]; continue; } if (i - 2 == -1) { // base case 2 dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i]); // i - 2 小于 0 时根据状态转移方程推出对应 base case dp[i][1] = Math.max(dp[i - 1][1], -prices[i]); // dp[i][1] // = max(dp[i-1][1], dp[-1][0] - prices[i]) // = max(dp[i-1][1], 0 - prices[i]) // = max(dp[i-1][1], -prices[i]) continue; } 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 - 2][0] - prices[i]); } return dp[n - 1][0]; } } ``` ```javascript // by chatGPT (javascript) /** * @param {number[]} prices * @return {number} */ var maxProfit = function(prices) { let n = prices.length; let dp = Array.from(Array(n), () => new Array(2).fill(0)); for (let i = 0; i < n; i++) { if (i - 1 == -1) { // base case 1 dp[i][0] = 0; dp[i][1] = -prices[i]; continue; } if (i - 2 == -1) { // base case 2 dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i]); // i - 2 小于 0 时根据状态转移方程推出对应 base case dp[i][1] = Math.max(dp[i - 1][1], -prices[i]); // dp[i][1] // = max(dp[i-1][1], dp[-1][0] - prices[i]) // = max(dp[i-1][1], 0 - prices[i]) // = max(dp[i-1][1], -prices[i]) continue; } 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 - 2][0] - prices[i]); } return dp[n - 1][0]; }; ``` ```python # by chatGPT (python) class Solution: def maxProfit(self, prices: List[int]) -> int: n = len(prices) dp = [[0]*2 for _ in range(n)] for i in range(n): if i - 1 == -1: # base case 1 dp[i][0] = 0 dp[i][1] = -prices[i] continue if i - 2 == -1: # base case 2 dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] + prices[i]) # i - 2 小于 0 时根据状态转移方程推出对应 base case dp[i][1] = max(dp[i - 1][1], -prices[i]) # dp[i][1] # = max(dp[i-1][1], dp[-1][0] - prices[i]) # = max(dp[i-1][1], 0 - prices[i]) # = max(dp[i-1][1], -prices[i]) continue dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] + prices[i]) dp[i][1] = max(dp[i - 1][1], dp[i - 2][0] - prices[i]) return dp[n - 1][0] ``` https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-with-cooldown 的多语言解法👆 https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-with-transaction-fee 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int maxProfit(vector& prices, int fee) { int n = prices.size(); vector> dp(n, vector(2)); for (int i = 0; i < n; i++) { if (i - 1 == -1) { // base case dp[i][0] = 0; dp[i][1] = -prices[i] - fee; // dp[i][1] // = max(dp[i - 1][1], dp[i - 1][0] - prices[i] - fee) // = max(dp[-1][1], dp[-1][0] - prices[i] - fee) // = max(-inf, 0 - prices[i] - fee) // = -prices[i] - fee continue; } dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] + prices[i]); dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] - prices[i] - fee); } return dp[n - 1][0]; } }; ``` ```go // by chatGPT (go) func maxProfit(prices []int, fee int) int { n := len(prices) dp := make([][2]int, n) for i := 0; i < n; i++ { if i-1 == -1 { // base case dp[i][0] = 0 dp[i][1] = -prices[i] - fee // dp[i][1] // = max(dp[i-1][1], dp[i-1][0]-prices[i]-fee) // = max(dp[-1][1], dp[-1][0]-prices[i]-fee) // = max(-inf, 0-prices[i]-fee) // = -prices[i]-fee continue } dp[i][0] = max(dp[i-1][0], dp[i-1][1]+prices[i]) dp[i][1] = max(dp[i-1][1], dp[i-1][0]-prices[i]-fee) } return dp[n-1][0] } func max(a, b int) int { if a > b { return a } return b } ``` ```java // by labuladong (java) class Solution { public int maxProfit(int[] prices, int fee) { int n = prices.length; int[][] dp = new int[n][2]; for (int i = 0; i < n; i++) { if (i - 1 == -1) { // base case dp[i][0] = 0; dp[i][1] = -prices[i] - fee; // dp[i][1] // = max(dp[i - 1][1], dp[i - 1][0] - prices[i] - fee) // = max(dp[-1][1], dp[-1][0] - prices[i] - fee) // = max(-inf, 0 - prices[i] - fee) // = -prices[i] - fee continue; } 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] - fee); } return dp[n - 1][0]; } } ``` ```javascript // by chatGPT (javascript) var maxProfit = function(prices, fee) { const n = prices.length; const dp = new Array(n).map(() => new Array(2).fill(0)); for (let i = 0; i < n; i++) { if (i - 1 === -1) { // base case dp[i][0] = 0; dp[i][1] = -prices[i] - fee; // dp[i][1] // = max(dp[i - 1][1], dp[i - 1][0] - prices[i] - fee) // = max(dp[-1][1], dp[-1][0] - prices[i] - fee) // = max(-inf, 0 - prices[i] - fee) // = -prices[i] - fee continue; } 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] - fee); } return dp[n - 1][0]; }; ``` ```python # by chatGPT (python) class Solution: def maxProfit(self, prices: List[int], fee: int) -> int: n = len(prices) dp = [[0]*2 for _ in range(n)] for i in range(n): if i - 1 == -1: # base case dp[i][0] = 0 dp[i][1] = -prices[i] - fee # dp[i][1] # = max(dp[i - 1][1], dp[i - 1][0] - prices[i] - fee) # = max(dp[-1][1], dp[-1][0] - prices[i] - fee) # = max(-inf, 0 - prices[i] - fee) # = -prices[i] - fee continue dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] + prices[i]) dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] - prices[i] - fee) return dp[n - 1][0] ``` https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-with-transaction-fee 的多语言解法👆 https://leetcode.cn/problems/binary-search 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int search(vector& nums, int target) { int left = 0; int right = nums.size() - 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 -1; } }; ``` ```go // by chatGPT (go) func search(nums []int, target int) int { left := 0 right := len(nums) - 1 // 注意 for left <= right { 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 -1 } ``` ```java // by labuladong (java) class Solution { public int search(int[] nums, int target) { int left = 0; int 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 -1; } } ``` ```javascript // by chatGPT (javascript) var search = function(nums, target) { var left = 0; var right = nums.length - 1; // 注意 while (left <= right) { var mid = left + Math.floor((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 -1; }; ``` ```python # by chatGPT (python) class Solution: def search(self, nums: List[int], target: int) -> int: left = 0 right = len(nums) - 1 # 注意 while left <= right: mid = left + (right - left) // 2 if nums[mid] == target: return mid elif nums[mid] < target: left = mid + 1 # 注意 elif nums[mid] > target: right = mid - 1 # 注意 return -1 ``` https://leetcode.cn/problems/binary-search 的多语言解法👆 https://leetcode.cn/problems/binary-search-tree-to-greater-sum-tree 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: TreeNode* bstToGst(TreeNode* root) { traverse(root); return root; } // 记录累加和 int sum = 0; void traverse(TreeNode* root) { if (root == nullptr) { return; } traverse(root->right); // 维护累加和 sum += root->val; // 将 BST 转化成累加树 root->val = sum; traverse(root->left); } }; ``` ```go // by chatGPT (go) /** * Definition for a binary tree node. * type TreeNode struct { * Val int * Left *TreeNode * Right *TreeNode * } */ func bstToGst(root *TreeNode) *TreeNode { var sum int // 定义中序遍历函数,内部维护sum变量 var traverse func(node *TreeNode) traverse = func(node *TreeNode) { if node == nil { return } traverse(node.Right) // 维护累加和 sum += node.Val // 将BST转化为累加树 node.Val = sum traverse(node.Left) } traverse(root) return root } ``` ```java // by labuladong (java) class Solution { public TreeNode bstToGst(TreeNode root) { traverse(root); return root; } // 记录累加和 int sum = 0; void traverse(TreeNode root) { if (root == null) { return; } traverse(root.right); // 维护累加和 sum += root.val; // 将 BST 转化成累加树 root.val = sum; traverse(root.left); } } ``` ```javascript // by chatGPT (javascript) /** * @param {TreeNode} root * @return {TreeNode} */ var bstToGst = function(root) { let sum = 0; const traverse = function(root) { if (root == null) { return; } traverse(root.right); // 维护累加和 sum += root.val; // 将 BST 转化成累加树 root.val = sum; traverse(root.left); } traverse(root); return root; }; ``` ```python # by chatGPT (python) class Solution: def bstToGst(self, root: TreeNode) -> TreeNode: # 记录累加和 sum = 0 # 中序遍历BST,递归地对BST节点的值进行累加和操作 def traverse(root: TreeNode) -> None: nonlocal sum if not root: return traverse(root.right) # 维护累加和 sum += root.val # 将 BST 转化成累加树 root.val = sum traverse(root.left) traverse(root) return root ``` https://leetcode.cn/problems/binary-search-tree-to-greater-sum-tree 的多语言解法👆 https://leetcode.cn/problems/binary-tree-inorder-traversal 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: /* 动态规划思路 */ // 定义:输入一个节点,返回以该节点为根的二叉树的中序遍历结果 vector inorderTraversal(TreeNode* root) { vector res; if (root == NULL) { return res; } vector left = inorderTraversal(root->left); res.insert(res.end(), left.begin(), left.end()); res.push_back(root->val); vector right = inorderTraversal(root->right); res.insert(res.end(), right.begin(), right.end()); return res; } /* 回溯算法思路 */ vector res; // 返回前序遍历结果 vector inorderTraversal2(TreeNode* root) { traverse(root); return res; } // 二叉树遍历函数 void traverse(TreeNode* root) { if (root == NULL) { return; } traverse(root->left); // 中序遍历位置 res.push_back(root->val); traverse(root->right); } }; ``` ```go // by chatGPT (go) /** * Definition for a binary tree node. * type TreeNode struct { * Val int * Left *TreeNode * Right *TreeNode * } */ // 动态规划思路 // 定义:输入一个节点,返回以该节点为根的二叉树的中序遍历结果 func inorderTraversal(root *TreeNode) []int { res := []int{} if root == nil { return res } res = append(res, inorderTraversal(root.Left)...) res = append(res, root.Val) res = append(res, inorderTraversal(root.Right)...) return res } // 回溯算法思路 func inorderTraversal2(root *TreeNode) []int { res := []int{} // 二叉树遍历函数 var traverse func(node *TreeNode) traverse = func(node *TreeNode) { if node == nil { return } traverse(node.Left) // 中序遍历位置 res = append(res, node.Val) traverse(node.Right) } traverse(root) return res } ``` ```java // by labuladong (java) class Solution { /* 动态规划思路 */ // 定义:输入一个节点,返回以该节点为根的二叉树的中序遍历结果 public List inorderTraversal(TreeNode root) { LinkedList res = new LinkedList<>(); if (root == null) { return res; } res.addAll(inorderTraversal(root.left)); res.add(root.val); res.addAll(inorderTraversal(root.right)); return res; } /* 回溯算法思路 */ LinkedList res = new LinkedList<>(); // 返回前序遍历结果 public List inorderTraversal2(TreeNode root) { traverse(root); return res; } // 二叉树遍历函数 void traverse(TreeNode root) { if (root == null) { return; } traverse(root.left); // 中序遍历位置 res.add(root.val); traverse(root.right); } } ``` ```javascript // by chatGPT (javascript) var inorderTraversal = function(root) { let res = new LinkedList(); if (root === null) { return res; } res.addAll(inorderTraversal(root.left)); res.add(root.val); res.addAll(inorderTraversal(root.right)); return res; }; var inorderTraversal2 = function(root) { let res = new LinkedList(); if (root === null) { return res; } traverse(root); return res; }; const traverse = function(root, res) { if (root === null) { return; } traverse(root.left, res); // 中序遍历位置 res.add(root.val); traverse(root.right, res); } ``` ```python # by chatGPT (python) class Solution: def inorderTraversal(self, root: TreeNode) -> List[int]: """动态规划思路 定义:输入一个节点,返回以该节点为根的二叉树的中序遍历结果 """ res = [] if not root: return res res += self.inorderTraversal(root.left) res.append(root.val) res += self.inorderTraversal(root.right) return res def inorderTraversal2(self, root: TreeNode) -> List[int]: """回溯算法思路""" self.res = [] self.traverse(root) return self.res def traverse(self, root: TreeNode): """二叉树遍历函数""" if not root: return self.traverse(root.left) # 中序遍历位置 self.res.append(root.val) self.traverse(root.right) ``` https://leetcode.cn/problems/binary-tree-inorder-traversal 的多语言解法👆 https://leetcode.cn/problems/binary-tree-level-order-traversal 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: vector> levelOrder(TreeNode* root) { vector> res; if (root == nullptr) { return res; } queue q; q.push(root); // while 循环控制从上向下一层层遍历 while (!q.empty()) { int sz = q.size(); // 记录这一层的节点值 vector level; // for 循环控制每一层从左向右遍历 for (int i = 0; i < sz; i++) { TreeNode* cur = q.front(); q.pop(); level.push_back(cur->val); if (cur->left != nullptr) q.push(cur->left); if (cur->right != nullptr) q.push(cur->right); } res.push_back(level); } return res; } }; ``` ```go // by chatGPT (go) func levelOrder(root *TreeNode) [][]int { res := [][]int{} if root == nil { return res } q := []*TreeNode{root} // while 循环控制从上向下一层层遍历 for len(q) > 0 { sz := len(q) // 记录这一层的节点值 level := []int{} // for 循环控制每一层从左向右遍历 for i := 0; i < sz; i++ { cur := q[0] q = q[1:] level = append(level, cur.Val) if cur.Left != nil { q = append(q, cur.Left) } if cur.Right != nil { q = append(q, cur.Right) } } res = append(res, level) } return res } ``` ```java // by labuladong (java) class Solution { public List> levelOrder(TreeNode root) { List> res = new LinkedList<>(); if (root == null) { return res; } Queue q = new LinkedList<>(); q.offer(root); // while 循环控制从上向下一层层遍历 while (!q.isEmpty()) { int sz = q.size(); // 记录这一层的节点值 List level = new LinkedList<>(); // for 循环控制每一层从左向右遍历 for (int i = 0; i < sz; i++) { TreeNode cur = q.poll(); level.add(cur.val); if (cur.left != null) q.offer(cur.left); if (cur.right != null) q.offer(cur.right); } res.add(level); } return res; } } ``` ```javascript // by chatGPT (javascript) var levelOrder = function(root) { let res = []; if (root == null) { return res; } let q = []; q.push(root); // while 循环控制从上向下一层层遍历 while (q.length > 0) { let sz = q.length; // 记录这一层的节点值 let level = []; // for 循环控制每一层从左向右遍历 for (let i = 0; i < sz; i++) { let cur = q.shift(); level.push(cur.val); if (cur.left != null) q.push(cur.left); if (cur.right != null) q.push(cur.right); } res.push(level); } return res; }; ``` ```python # by chatGPT (python) class Solution: def levelOrder(self, root: TreeNode) -> List[List[int]]: res = [] if not root: return res q = [] q.append(root) # while 循环控制从上向下一层层遍历 while q: sz = len(q) # 记录这一层的节点值 level = [] # for 循环控制每一层从左向右遍历 for i in range(sz): cur = q.pop(0) level.append(cur.val) if cur.left: q.append(cur.left) if cur.right: q.append(cur.right) res.append(level) return res ``` https://leetcode.cn/problems/binary-tree-level-order-traversal 的多语言解法👆 https://leetcode.cn/problems/binary-tree-level-order-traversal-ii 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: vector> levelOrderBottom(TreeNode* root) { vector> res; if (root == NULL) { return res; } queue q; q.push(root); // while 循环控制从上向下一层层遍历 while (!q.empty()) { int sz = q.size(); // 记录这一层的节点值 vector level; // for 循环控制每一层从左向右遍历 for (int i = 0; i < sz; i++) { TreeNode* cur = q.front(); q.pop(); level.push_back(cur->val); if (cur->left != NULL) q.push(cur->left); if (cur->right != NULL) q.push(cur->right); } // 把每一层添加到头部,就是自底向上的层序遍历。 res.insert(res.begin(), level); } return res; } }; ``` ```go // by chatGPT (go) func levelOrderBottom(root *TreeNode) [][]int { res := make([][]int, 0) if root == nil { return res } q := make([]*TreeNode, 0) q = append(q, root) // while 循环控制从上向下一层层遍历 for len(q) > 0 { sz := len(q) // 记录这一层的节点值 level := make([]int, 0) // for 循环控制每一层从左向右遍历 for i := 0; i < sz; i++ { cur := q[0] q = q[1:] level = append(level, cur.Val) if cur.Left != nil { q = append(q, cur.Left) } if cur.Right != nil { q = append(q, cur.Right) } } // 把每一层添加到头部,就是自底向上的层序遍历。 res = append([][]int{level}, res...) } return res } ``` ```java // by labuladong (java) class Solution { public List> levelOrderBottom(TreeNode root) { LinkedList> res = new LinkedList<>(); if (root == null) { return res; } Queue q = new LinkedList<>(); q.offer(root); // while 循环控制从上向下一层层遍历 while (!q.isEmpty()) { int sz = q.size(); // 记录这一层的节点值 List level = new LinkedList<>(); // for 循环控制每一层从左向右遍历 for (int i = 0; i < sz; i++) { TreeNode cur = q.poll(); level.add(cur.val); if (cur.left != null) q.offer(cur.left); if (cur.right != null) q.offer(cur.right); } // 把每一层添加到头部,就是自底向上的层序遍历。 res.addFirst(level); } return res; } } ``` ```javascript // by chatGPT (javascript) var levelOrderBottom = function(root) { let res = []; if (root == null) { return res; } let q = []; q.push(root); // while 循环控制从上向下一层层遍历 while (q.length > 0) { let sz = q.length; // 记录这一层的节点值 let level = []; // for 循环控制每一层从左向右遍历 for (let i = 0; i < sz; i++) { let cur = q.shift(); level.push(cur.val); if (cur.left != null) q.push(cur.left); if (cur.right != null) q.push(cur.right); } // 把每一层添加到头部,就是自底向上的层序遍历。 res.unshift(level); } return res; }; ``` ```python # by chatGPT (python) class Solution: def levelOrderBottom(self, root: TreeNode) -> List[List[int]]: res = [] if not root: return res q = collections.deque([root]) # while 循环控制从上向下一层层遍历 while q: sz = len(q) # 记录这一层的节点值 level = [] # for 循环控制每一层从左向右遍历 for i in range(sz): cur = q.popleft() level.append(cur.val) if cur.left: q.append(cur.left) if cur.right: q.append(cur.right) # 把每一层添加到头部,就是自底向上的层序遍历。 res.insert(0, level) return res ``` https://leetcode.cn/problems/binary-tree-level-order-traversal-ii 的多语言解法👆 https://leetcode.cn/problems/binary-tree-postorder-traversal 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: /* 动态规划思路 */ // 定义:输入一个节点,返回以该节点为根的二叉树的后序遍历结果 vector postorderTraversal(TreeNode* root) { vector res; if (root == nullptr) { return res; } // 后序遍历结果特点:先是左子树,接着是右子树,最后是根节点的值 auto left = postorderTraversal(root->left); auto right = postorderTraversal(root->right); res.insert(res.end(), left.begin(), left.end()); res.insert(res.end(), right.begin(), right.end()); res.push_back(root->val); return res; } /* 回溯算法思路 */ vector res; // 返回后序遍历结果 vector postorderTraversal2(TreeNode* root) { traverse(root); return res; } // 二叉树遍历函数 void traverse(TreeNode* root) { if (root == nullptr) { return; } traverse(root->left); traverse(root->right); // 后序遍历位置 res.push_back(root->val); } }; ``` ```go // by chatGPT (go) // 动态规划思路 // 定义:输入一个节点,返回以该节点为根的二叉树的后序遍历结果 func postorderTraversal(root *TreeNode) []int { res := []int{} if root == nil { return res } // 后序遍历结果特点:先是左子树,接着是右子树,最后是根节点的值 res = append(res, postorderTraversal(root.Left)...) res = append(res, postorderTraversal(root.Right)...) res = append(res, root.Val) return res } // 回溯算法思路 func postorderTraversal2(root *TreeNode) []int { res := []int{} traverse(root, &res) return res } // 二叉树遍历函数 func traverse(root *TreeNode, res *[]int) { if root == nil { return } traverse(root.Left, res) traverse(root.Right, res) // 后序遍历位置 *res = append(*res, root.Val) } ``` ```java // by labuladong (java) class Solution { /* 动态规划思路 */ // 定义:输入一个节点,返回以该节点为根的二叉树的后序遍历结果 public List postorderTraversal(TreeNode root) { LinkedList res = new LinkedList<>(); if (root == null) { return res; } // 后序遍历结果特点:先是左子树,接着是右子树,最后是根节点的值 res.addAll(postorderTraversal(root.left)); res.addAll(postorderTraversal(root.right)); res.add(root.val); return res; } /* 回溯算法思路 */ LinkedList res = new LinkedList<>(); // 返回后序遍历结果 public List postorderTraversal2(TreeNode root) { traverse(root); return res; } // 二叉树遍历函数 void traverse(TreeNode root) { if (root == null) { return; } traverse(root.left); traverse(root.right); // 后序遍历位置 res.add(root.val); } } ``` ```javascript // by chatGPT (javascript) /** * @param {TreeNode} root * @return {number[]} */ var postorderTraversal = function(root) { const res = []; /* 动态规划思路 */ // 定义:输入一个节点,返回以该节点为根的二叉树的后序遍历结果 if (root == null) { return res; } // 后序遍历结果特点:先是左子树,接着是右子树,最后是根节点的值 res.push(...postorderTraversal(root.left)); res.push(...postorderTraversal(root.right)); res.push(root.val); /* 回溯算法思路 */ /*LinkedList res = new LinkedList<>();*/ // 返回后序遍历结果 // public List postorderTraversal2(TreeNode root) { // traverse(root); // return res; // } // 二叉树遍历函数 // void traverse(TreeNode root) { // if (root == null) { // return; // } // traverse(root.left); // traverse(root.right); // // 后序遍历位置 // res.add(root.val); // } return res; } ``` ```python # by chatGPT (python) class Solution: def postorderTraversal(self, root: TreeNode) -> List[int]: res = [] if not root: return res # 后序遍历结果特点:先是左子树,接着是右子树,最后是根节点的值 res += self.postorderTraversal(root.left) res += self.postorderTraversal(root.right) res.append(root.val) return res res = [] def postorderTraversal2(self, root: TreeNode) -> List[int]: self.traverse(root) return self.res def traverse(self, root: TreeNode): if not root: return self.traverse(root.left) self.traverse(root.right) # 后序遍历位置 self.res.append(root.val) ``` https://leetcode.cn/problems/binary-tree-postorder-traversal 的多语言解法👆 https://leetcode.cn/problems/binary-tree-preorder-traversal 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { private: /* 动态规划思路 */ // 定义:输入一个节点,返回以该节点为根的二叉树的前序遍历结果 vector preorderTraversal(TreeNode* root) { vector res; if (!root) { return res; } // 前序遍历结果特点:第一个是根节点的值,接着是左子树,最后是右子树 res.push_back(root->val); vector left = preorderTraversal(root->left); res.insert(res.end(), left.begin(), left.end()); vector right = preorderTraversal(root->right); res.insert(res.end(), right.begin(), right.end()); return res; } /* 回溯算法思路 */ vector res; // 二叉树遍历函数 void traverse(TreeNode* root) { if (!root) { return; } // 前序遍历位置 res.push_back(root->val); traverse(root->left); traverse(root->right); } public: // 返回前序遍历结果 vector preorderTraversal2(TreeNode* root) { traverse(root); return res; } }; ``` ```go // by chatGPT (go) /** * Definition for a binary tree node. * type TreeNode struct { * Val int * Left *TreeNode * Right *TreeNode * } */ func preorderTraversal(root *TreeNode) []int { var res []int if root == nil { return res } // 动态规划思路 // 前序遍历结果特点:第一个是根节点的值,接着是左子树,最后是右子树 res = append(res, root.Val) res = append(res, preorderTraversal(root.Left)...) res = append(res, preorderTraversal(root.Right)...) // 回溯算法思路 // traverse(root, &res) return res } // 回溯算法 // func traverse(root *TreeNode, res *[]int) { // if root == nil { // return // } // *res = append(*res, root.Val) // traverse(root.Left, res) // traverse(root.Right, res) // } ``` ```java // by labuladong (java) class Solution { /* 动态规划思路 */ // 定义:输入一个节点,返回以该节点为根的二叉树的前序遍历结果 public List preorderTraversal(TreeNode root) { LinkedList res = new LinkedList<>(); if (root == null) { return res; } // 前序遍历结果特点:第一个是根节点的值,接着是左子树,最后是右子树 res.add(root.val); res.addAll(preorderTraversal(root.left)); res.addAll(preorderTraversal(root.right)); return res; } /* 回溯算法思路 */ LinkedList res = new LinkedList<>(); // 返回前序遍历结果 public List preorderTraversal2(TreeNode root) { traverse(root); return res; } // 二叉树遍历函数 void traverse(TreeNode root) { if (root == null) { return; } // 前序遍历位置 res.add(root.val); traverse(root.left); traverse(root.right); } } ``` ```javascript // by chatGPT (javascript) var Solution = function() { /* 动态规划思路 */ // 定义:输入一个节点,返回以该节点为根的二叉树的前序遍历结果 this.preorderTraversal = function(root) { let res = []; if (root == null) { return res; } // 前序遍历结果特点:第一个是根节点的值,接着是左子树,最后是右子树 res.push(root.val); res = res.concat(this.preorderTraversal(root.left)); res = res.concat(this.preorderTraversal(root.right)); return res; }; /* 回溯算法思路 */ let res = []; // 返回前序遍历结果 this.preorderTraversal2 = function(root) { traverse(root); return res; }; // 二叉树遍历函数 function traverse(root) { if (root == null) { return; } // 前序遍历位置 res.push(root.val); traverse(root.left); traverse(root.right); } }; ``` ```python # by chatGPT (python) # Definition for a binary tree node. # class TreeNode: # def __init__(self, val=0, left=None, right=None): # self.val = val # self.left = left # self.right = right class Solution: # 动态规划思路 # 定义:输入一个节点,返回以该节点为根的二叉树的前序遍历结果 def preorderTraversal(self, root: TreeNode) -> List[int]: res = [] if not root: return res # 前序遍历结果特点:第一个是根节点的值,接着是左子树,最后是右子树 res.append(root.val) res.extend(self.preorderTraversal(root.left)) res.extend(self.preorderTraversal(root.right)) return res # 回溯算法思路 def __init__(self): self.res = [] # 返回前序遍历结果 def preorderTraversal2(self, root: TreeNode) -> List[int]: self.traverse(root) return self.res # 二叉树遍历函数 def traverse(self, root: TreeNode) -> None: if not root: return # 前序遍历位置 self.res.append(root.val) self.traverse(root.left) self.traverse(root.right) ``` https://leetcode.cn/problems/binary-tree-preorder-traversal 的多语言解法👆 https://leetcode.cn/problems/binary-tree-zigzag-level-order-traversal 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: vector> zigzagLevelOrder(TreeNode* root) { vector> res; if (root == nullptr) { return res; } queue q; q.push(root); // 为 true 时向右,false 时向左 bool flag = true; // while 循环控制从上向下一层层遍历 while (!q.empty()) { int sz = q.size(); // 记录这一层的节点值 list level; // for 循环控制每一层从左向右遍历 for (int i = 0; i < sz; i++) { TreeNode* cur = q.front(); q.pop(); // 实现 z 字形遍历 if (flag) { level.push_back(cur->val); } else { level.push_front(cur->val); } if (cur->left != nullptr) q.push(cur->left); if (cur->right != nullptr) q.push(cur->right); } // 切换方向 flag = !flag; res.emplace_back(level.begin(), level.end()); } return res; } }; ``` ```go // by chatGPT (go) func zigzagLevelOrder(root *TreeNode) [][]int { res := make([][]int, 0) if root == nil { return res } q := make([]*TreeNode, 0) q = append(q, root) // 为 true 时向右,false 时向左 flag := true // while 循环控制从上向下一层层遍历 for len(q) > 0 { sz := len(q) // 记录这一层的节点值 level := make([]int, 0, sz) // for 循环控制每一层从左向右遍历 for i := 0; i < sz; i++ { cur := q[0] q = q[1:] // 实现 z 字形遍历 if flag { level = append(level, cur.Val) } else { level = append([]int{cur.Val}, level...) } if cur.Left != nil { q = append(q, cur.Left) } if cur.Right != nil { q = append(q, cur.Right) } } // 切换方向 flag = !flag res = append(res, level) } return res } ``` ```java // by labuladong (java) class Solution { public List> zigzagLevelOrder(TreeNode root) { List> res = new LinkedList<>(); if (root == null) { return res; } Queue q = new LinkedList<>(); q.offer(root); // 为 true 时向右,false 时向左 boolean flag = true; // while 循环控制从上向下一层层遍历 while (!q.isEmpty()) { int sz = q.size(); // 记录这一层的节点值 LinkedList level = new LinkedList<>(); // for 循环控制每一层从左向右遍历 for (int i = 0; i < sz; i++) { TreeNode cur = q.poll(); // 实现 z 字形遍历 if (flag) { level.addLast(cur.val); } else { level.addFirst(cur.val); } if (cur.left != null) q.offer(cur.left); if (cur.right != null) q.offer(cur.right); } // 切换方向 flag = !flag; res.add(level); } return res; } } ``` ```javascript // by chatGPT (javascript) var zigzagLevelOrder = function(root) { let res = []; if (root == null) { return res; } let q = []; q.push(root); // 为 true 时向右,false 时向左 let flag = true; // while 循环控制从上向下一层层遍历 while (q.length > 0) { let sz = q.length; // 记录这一层的节点值 let level = []; // for 循环控制每一层从左向右遍历 for (let i = 0; i < sz; i++) { let cur = q.shift(); // 实现 z 字形遍历 if (flag) { level.push(cur.val); } else { level.unshift(cur.val); } if (cur.left != null) q.push(cur.left); if (cur.right != null) q.push(cur.right); } // 切换方向 flag = !flag; res.push(level); } return res; }; ``` ```python # by chatGPT (python) class Solution: def zigzagLevelOrder(self, root: TreeNode) -> List[List[int]]: res = [] if not root: return res q = collections.deque() q.append(root) # 为 True 时向右,False 时向左 flag = True # while 循环控制从上向下一层层遍历 while q: sz = len(q) # 记录这一层的节点值 level = collections.deque() # for 循环控制每一层从左向右遍历 for i in range(sz): cur = q.popleft() # 实现 z 字形遍历 if flag: level.append(cur.val) else: level.appendleft(cur.val) if cur.left: q.append(cur.left) if cur.right: q.append(cur.right) # 切换方向 flag = not flag res.append(list(level)) return res ``` https://leetcode.cn/problems/binary-tree-zigzag-level-order-traversal 的多语言解法👆 https://leetcode.cn/problems/bu-ke-pai-zhong-de-shun-zi-lcof 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: bool isStraight(vector& nums) { unordered_set numSet; int maxVal = INT_MIN, minVal = INT_MAX; for (int num : nums) { if (num == 0) { // 小王不用管 continue; } if (numSet.count(num)) { // 出现重复,不可能是顺子 return false; } numSet.insert(num); maxVal = max(maxVal, num); minVal = min(minVal, num); } // 考虑到大小王,只要差小于 4 就一定可以凑出顺子 return maxVal - minVal <= 4; } }; ``` ```go // by chatGPT (go) func isStraight(nums []int) bool { numSet := make(map[int]bool) max, min := math.MinInt32, math.MaxInt32 for _, num := range nums { if num == 0 { // 小王不用管 continue } if numSet[num] { // 出现重复,不可能是顺子 return false } numSet[num] = true max = int(math.Max(float64(max), float64(num))) min = int(math.Min(float64(min), float64(num))) } // 考虑到大小王,只要差小于 4 就一定可以凑出顺子 return max - min <= 4 } ``` ```java // by labuladong (java) class Solution { public boolean isStraight(int[] nums) { Set numSet = new HashSet<>(); int max = Integer.MIN_VALUE, min = Integer.MAX_VALUE; for (int num : nums) { if (num == 0) { // 小王不用管 continue; } if (numSet.contains(num)) { // 出现重复,不可能是顺子 return false; } numSet.add(num); max = Math.max(max, num); min = Math.min(min, num); } // 考虑到大小王,只要差小于 4 就一定可以凑出顺子 return max - min <= 4; } } ``` ```javascript // by chatGPT (javascript) var isStraight = function(nums) { let numSet = new Set(); let max = -Infinity, min = Infinity; for (let num of nums) { if (num === 0) { // 小王不用管 continue; } if (numSet.has(num)) { // 出现重复,不可能是顺子 return false; } numSet.add(num); max = Math.max(max, num); min = Math.min(min, num); } // 考虑到大小王,只要差小于 4 就一定可以凑出顺子 return max - min <= 4; }; ``` ```python # by chatGPT (python) class Solution: def isStraight(self, nums: List[int]) -> bool: numSet = set() maximum, minimum = float('-inf'), float('inf') for num in nums: if num == 0: # 小王不用管 continue if num in numSet: # 出现重复,不可能是顺子 return False numSet.add(num) maximum = max(maximum, num) minimum = min(minimum, num) # 考虑到大小王,只要差小于 4 就一定可以凑出顺子 return maximum - minimum <= 4 ``` https://leetcode.cn/problems/bu-ke-pai-zhong-de-shun-zi-lcof 的多语言解法👆 https://leetcode.cn/problems/bu-yong-jia-jian-cheng-chu-zuo-jia-fa-lcof 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int add(int a, int b) { if (a == 0 || b == 0) { return a == 0 ? b : a; } // 设 a = 1001 // 设 b = 0101 // 求和 1100 int sum = a ^ b; // 进位 0001 << 1 = 0010 int carry = (a & b) << 1; // add(1100, 0010) return add(sum, carry); } }; ``` ```go // by chatGPT (go) func add(a int, b int) int { if a == 0 || b == 0 { if a == 0 { return b } else { return a } } // 设 a = 1001 // 设 b = 0101 // 求和 1100 sum := a ^ b // 进位 0001 << 1 = 0010 carry := (a & b) << 1 // add(1100, 0010) return add(sum, carry) } ``` ```java // by labuladong (java) class Solution { public int add(int a, int b) { if(a == 0 || b == 0) { return a == 0 ? b : a; } // 设 a = 1001 // 设 b = 0101 // 求和 1100 int sum = a ^ b; // 进位 0001 << 1 = 0010 int carry = (a & b) << 1; // add(1100, 0010) return add(sum, carry); } } ``` ```javascript // by chatGPT (javascript) var add = function(a, b) { if (a == 0 || b == 0) { return a == 0 ? b : a; } // 设 a = 1001 // 设 b = 0101 // 求和 1100 let sum = a ^ b; // 进位 0001 << 1 = 0010 let carry = (a & b) << 1; // add(1100, 0010) return add(sum, carry); }; ``` ```python # by chatGPT (python) class Solution: def add(self, a: int, b: int) -> int: if a == 0 or b == 0: return b if a == 0 else a # 设 a = 1001 # 设 b = 0101 # 求和 1100 sum = a ^ b # 进位 0001 << 1 = 0010 carry = (a & b) << 1 # add(1100, 0010) return self.add(sum, carry) ``` https://leetcode.cn/problems/bu-yong-jia-jian-cheng-chu-zuo-jia-fa-lcof 的多语言解法👆 https://leetcode.cn/problems/bulb-switcher 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int bulbSwitch(int n) { return (int) sqrt(n); } }; ``` ```go // by chatGPT (go) func bulbSwitch(n int) int { return int(math.Sqrt(float64(n))) } ``` ```java // by labuladong (java) class Solution { public int bulbSwitch(int n) { return (int)Math.sqrt(n); } } ``` ```javascript // by chatGPT (javascript) /** * @param {number} n * @return {number} */ var bulbSwitch = function(n) { return Math.floor(Math.sqrt(n)); }; ``` ```python # by chatGPT (python) class Solution: def bulbSwitch(self, n: int) -> int: return int(n ** 0.5) ``` https://leetcode.cn/problems/bulb-switcher 的多语言解法👆 https://leetcode.cn/problems/burst-balloons 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int maxCoins(vector& nums) { int n = nums.size(); // 添加两侧的虚拟气球 vector points(n + 2); points[0] = points[n + 1] = 1; for (int i = 1; i <= n; i++) { points[i] = nums[i - 1]; } // base case 已经都被初始化为 0 vector> dp(n + 2, vector(n + 2, 0)); // 开始状态转移 // i 应该从下往上 for (int i = n; i >= 0; i--) { // j 应该从左往右 for (int j = i + 1; j < n + 2; j++) { // 最后戳破的气球是哪个? for (int k = i + 1; k < j; k++) { // 择优做选择 dp[i][j] = max( dp[i][j], dp[i][k] + dp[k][j] + points[i] * points[j] * points[k] ); } } } return dp[0][n + 1]; } }; ``` ```go // by chatGPT (go) func maxCoins(nums []int) int { n := len(nums) // 添加两侧的虚拟气球 points := make([]int, n+2) points[0] = points[n+1] = 1 for i := 1; i <= n; i++ { points[i] = nums[i-1] } // base case 已经都被初始化为 0 dp := make([][]int, n+2) for i := 0; i <= n+1; i++ { dp[i] = make([]int, n+2) } // 开始状态转移 // i 应该从下往上 for i := n; i >= 0; i-- { // j 应该从左往右 for j := i + 1; j < n+2; j++ { // 最后戳破的气球是哪个? for k := i + 1; k < j; k++ { // 择优做选择 dp[i][j] = max(dp[i][j], dp[i][k]+dp[k][j]+points[i]*points[j]*points[k]) } } } return dp[0][n+1] } func max(a, b int) int { if a > b { return a } return b } ``` ```java // by labuladong (java) class Solution { public int maxCoins(int[] nums) { int n = nums.length; // 添加两侧的虚拟气球 int[] points = new int[n + 2]; points[0] = points[n + 1] = 1; for (int i = 1; i <= n; i++) { points[i] = nums[i - 1]; } // base case 已经都被初始化为 0 int[][] dp = new int[n + 2][n + 2]; // 开始状态转移 // i 应该从下往上 for (int i = n; i >= 0; i--) { // j 应该从左往右 for (int j = i + 1; j < n + 2; j++) { // 最后戳破的气球是哪个? for (int k = i + 1; k < j; k++) { // 择优做选择 dp[i][j] = Math.max( dp[i][j], dp[i][k] + dp[k][j] + points[i] * points[j] * points[k] ); } } } return dp[0][n + 1]; } } ``` ```javascript // by chatGPT (javascript) var maxCoins = function(nums) { let n = nums.length; // 添加两侧的虚拟气球 let points = new Array(n + 2).fill(0); points[0] = points[n + 1] = 1; for (let i = 1; i <= n; i++) { points[i] = nums[i - 1]; } // base case 已经都被初始化为 0 let dp = new Array(n + 2).fill().map(() => new Array(n + 2).fill(0)); // 开始状态转移 // i 应该从下往上 for (let i = n; i >= 0; i--) { // j 应该从左往右 for (let j = i + 1; j < n + 2; j++) { // 最后戳破的气球是哪个? for (let k = i + 1; k < j; k++) { // 择优做选择 dp[i][j] = Math.max( dp[i][j], dp[i][k] + dp[k][j] + points[i] * points[j] * points[k] ); } } } return dp[0][n + 1]; }; ``` ```python # by chatGPT (python) class Solution: def maxCoins(self, nums: List[int]) -> int: n = len(nums) # 添加两侧的虚拟气球 points = [0] * (n + 2) points[0], points[n + 1] = 1, 1 for i in range(1, n + 1): points[i] = nums[i - 1] # base case 已经都被初始化为 0 dp = [[0] * (n + 2) for _ in range(n + 2)] # 开始状态转移 # i 应该从下往上 for i in range(n, -1, -1): # j 应该从左往右 for j in range(i + 1, n + 2): # 最后戳破的气球是哪个? for k in range(i + 1, j): # 择优做选择 dp[i][j] = max( dp[i][j], dp[i][k] + dp[k][j] + points[i] * points[j] * points[k] ) return dp[0][n + 1] ``` https://leetcode.cn/problems/burst-balloons 的多语言解法👆 https://leetcode.cn/problems/c32eOV 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: ListNode *detectCycle(ListNode *head) { ListNode *fast, *slow; fast = slow = head; while (fast != nullptr && fast->next != nullptr) { fast = fast->next->next; slow = slow->next; if (fast == slow) break; /** ![](../pictures/双指针/3.jpeg) */ } // 上面的代码类似 hasCycle 函数 if (fast == nullptr || fast->next == nullptr) { // fast 遇到空指针说明没有环 return nullptr; } // 重新指向头结点 slow = head; /** ![](../pictures/双指针/2.jpeg) */ // 快慢指针同步前进,相交点就是环起点 while (slow != fast) { fast = fast->next; slow = slow->next; } return slow; } }; ``` ```go // by chatGPT (go) func detectCycle(head *ListNode) *ListNode { fast, slow := head, head for fast != nil && fast.Next != nil { fast = fast.Next.Next slow = slow.Next if fast == slow { break /** ![](../pictures/双指针/3.jpeg) */ } } // 上面的代码类似 hasCycle 函数 if fast == nil || fast.Next == nil { // fast 遇到空指针说明没有环 return nil } // 重新指向头结点 slow = head /** ![](../pictures/双指针/2.jpeg) */ // 快慢指针同步前进,相交点就是环起点 for slow != fast { fast = fast.Next slow = slow.Next } return slow } ``` ```java // by labuladong (java) public 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; /** ![](../pictures/双指针/3.jpeg) */ } // 上面的代码类似 hasCycle 函数 if (fast == null || fast.next == null) { // fast 遇到空指针说明没有环 return null; } // 重新指向头结点 slow = head; /** ![](../pictures/双指针/2.jpeg) */ // 快慢指针同步前进,相交点就是环起点 while (slow != fast) { fast = fast.next; slow = slow.next; } return slow; } } ``` ```javascript // by chatGPT (javascript) var detectCycle = function(head) { let fast, slow; fast = slow = head; while (fast !== null && fast.next !== null) { fast = fast.next.next; slow = slow.next; if (fast === slow) break; /** ![](../pictures/双指针/3.jpeg) */ } // 上面的代码类似 hasCycle 函数 if (fast === null || fast.next === null) { // fast 遇到空指针说明没有环 return null; } // 重新指向头结点 slow = head; /** ![](../pictures/双指针/2.jpeg) */ // 快慢指针同步前进,相交点就是环起点 while (slow !== fast) { fast = fast.next; slow = slow.next; } return slow; }; ``` ```python # by chatGPT (python) class Solution: def detectCycle(self, head: ListNode) -> ListNode: fast, slow = head, head while fast and fast.next: fast = fast.next.next slow = slow.next if fast == slow: break """ ![](../pictures/双指针/3.jpeg) """ # 上面的代码类似 hasCycle 函数 if not fast or not fast.next: # fast 遇到空指针说明没有环 return None # 重新指向头结点 slow = head """ ![](../pictures/双指针/2.jpeg) """ # 快慢指针同步前进,相交点就是环起点 while slow != fast: slow = slow.next fast = fast.next return slow ``` https://leetcode.cn/problems/c32eOV 的多语言解法👆 https://leetcode.cn/problems/capacity-to-ship-packages-within-d-days 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int shipWithinDays(vector& weights, int days) { int left = 0; int right = 1; for (int w : weights) { left = max(left, w); right += w; } while (left < right) { int mid = left + (right - left) / 2; if (f(weights, mid) <= days) { right = mid; } else { left = mid + 1; } } return left; } // 定义:当运载能力为 x 时,需要 f(x) 天运完所有货物 // f(x) 随着 x 的增加单调递减 int f(vector& weights, int x) { int days = 0; for (int i = 0; i < weights.size(); ) { // 尽可能多装货物 int cap = x; while (i < weights.size()) { if (cap < weights[i]) break; else cap -= weights[i]; i++; } days++; } return days; } }; ``` ```go // by chatGPT (go) func shipWithinDays(weights []int, days int) int { left := 0 right := 1 for _, w := range weights { left = max(left, w) right += w } for left < right { mid := left + (right-left)/2 if f(weights, mid) <= days { right = mid } else { left = mid + 1 } } return left } // 定义:当运载能力为 x 时,需要 f(x) 天运完所有货物 // f(x) 随着 x 的增加单调递减 func f(weights []int, x int) int { days := 0 for i := 0; i < len(weights); { // 尽可能多装货物 cap := x for i < len(weights) { if cap < weights[i] { break } else { cap -= weights[i] i++ } } days++ } return days } func max(a, b int) int { if a > b { return a } return b } ``` ```java // by labuladong (java) class Solution { public int shipWithinDays(int[] weights, int days) { int left = 0; int right = 1; for (int w : weights) { left = Math.max(left, w); right += w; } while (left < right) { int mid = left + (right - left) / 2; if (f(weights, mid) <= days) { right = mid; } else { 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; } } ``` ```javascript // by chatGPT (javascript) var shipWithinDays = function(weights, days) { let left = 0; let right = 1; for (let w of weights) { left = Math.max(left, w); right += w; } while (left < right) { let mid = left + Math.floor((right - left) / 2); if (f(weights, mid) <= days) { right = mid; } else { left = mid + 1; } } return left; }; // 定义:当运载能力为 x 时,需要 f(x) 天运完所有货物 // f(x) 随着 x 的增加单调递减 function f(weights, x) { let days = 0; for (let i = 0; i < weights.length; ) { // 尽可能多装货物 let cap = x; while (i < weights.length) { if (cap < weights[i]) break; else cap -= weights[i]; i++; } days++; } return days; } ``` ```python # by chatGPT (python) class Solution: def shipWithinDays(self, weights: List[int], days: int) -> int: left = max(weights) right = sum(weights) while left < right: mid = (left + right) // 2 if self.f(weights, mid) <= days: right = mid else: left = mid + 1 return left # 定义:当运载能力为 x 时,需要 f(x) 天运完所有货物 # f(x) 随着 x 的增加单调递减 def f(self, weights, x): days = 0 i = 0 while i < len(weights): # 尽可能多装货物 cap = x while i < len(weights): if cap < weights[i]: break else: cap -= weights[i] i += 1 days += 1 return days ``` https://leetcode.cn/problems/capacity-to-ship-packages-within-d-days 的多语言解法👆 https://leetcode.cn/problems/car-pooling 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: bool carPooling(vector>& trips, int capacity) { // 最多有 1000 个车站 vector nums(1001, 0); // 构造差分解法 Difference df(nums); for (const auto& 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); } vector res = df.result(); // 客车自始至终都不应该超载 for (int i = 0; i < res.size(); i++) { if (capacity < res[i]) { return false; } } return true; } // 差分数组工具类 class Difference { // 差分数组 vector diff; /* 输入一个初始数组,区间操作将在这个数组上进行 */ public: Difference(vector& nums) { diff.resize(nums.size()); // 根据初始数组构造差分数组 diff[0] = nums[0]; for (int i = 1; i < nums.size(); i++) { diff[i] = nums[i] - nums[i - 1]; } } /* 给闭区间 [i, j] 增加 val(可以是负数)*/ void increment(int i, int j, int val) { diff[i] += val; if (j + 1 < diff.size()) { diff[j + 1] -= val; } } /* 返回结果数组 */ vector result() { vector res(diff.size()); // 根据差分数组构造结果数组 res[0] = diff[0]; for (int i = 1; i < diff.size(); i++) { res[i] = res[i - 1] + diff[i]; } return res; } }; }; ``` ```go // by chatGPT (go) func carPooling(trips [][]int, capacity int) bool { // 最多有 1000 个车站 nums := make([]int, 1001) // 构造差分解法 df := Difference{nums} for _, trip := range trips { // 乘客数量 val := trip[0] // 第 trip[1] 站乘客上车 i := trip[1] // 第 trip[2] 站乘客已经下车, // 即乘客在车上的区间是 [trip[1], trip[2] - 1] j := trip[2] - 1 // 进行区间操作 df.increment(i, j, val) } res := df.result() // 客车自始至终都不应该超载 for _, val := range res { if capacity < val { return false } } return true } // 差分数组工具类 type Difference struct { // 差分数组 diff []int } /* 输入一个初始数组,区间操作将在这个数组上进行 */ func (df *Difference) Difference(nums []int) { df.diff = make([]int, len(nums)) // 根据初始数组构造差分数组 df.diff[0] = nums[0] for i := 1; i < len(nums); i++ { df.diff[i] = nums[i] - nums[i-1] } } /* 给闭区间 [i, j] 增加 val(可以是负数)*/ func (df *Difference) increment(i int, j int, val int) { df.diff[i] += val if j+1 < len(df.diff) { df.diff[j+1] -= val } } /* 返回结果数组 */ func (df *Difference) result() []int { res := make([]int, len(df.diff)) // 根据差分数组构造结果数组 res[0] = df.diff[0] for i := 1; i < len(df.diff); i++ { res[i] = res[i-1] + df.diff[i] } return res } ``` ```java // by labuladong (java) 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; } } } ``` ```javascript // by chatGPT (javascript) var carPooling = function(trips, capacity) { // 最多有 1000 个车站 const nums = new Array(1001).fill(0); // 构造差分解法 const df = new Difference(nums); for (const trip of trips) { // 乘客数量 const val = trip[0]; // 第 trip[1] 站乘客上车 const i = trip[1]; // 第 trip[2] 站乘客已经下车, // 即乘客在车上的区间是 [trip[1], trip[2] - 1] const j = trip[2] - 1; // 进行区间操作 df.increment(i, j, val); } const res = df.result(); // 客车自始至终都不应该超载 for (let i = 0; i < res.length; i++) { if (capacity < res[i]) { return false; } } return true; // 差分数组工具类 function Difference(nums) { // 差分数组 let diff = new Array(nums.length).fill(0); /* 输入一个初始数组,区间操作将在这个数组上进行 */ this.increment = function(i, j, val) { diff[i] += val; if (j + 1 < diff.length) { diff[j + 1] -= val; } } /* 返回结果数组 */ this.result = function() { const res = new Array(diff.length).fill(0); // 根据差分数组构造结果数组 res[0] = diff[0]; for (let i = 1; i < diff.length; i++) { res[i] = res[i - 1] + diff[i]; } return res; } // 根据初始数组构造差分数组 diff[0] = nums[0]; for (let i = 1; i < nums.length; i++) { diff[i] = nums[i] - nums[i - 1]; } } }; ``` ```python # by chatGPT (python) class Solution: def carPooling(self, trips: List[List[int]], capacity: int) -> bool: # 最多有 1000 个车站 nums = [0] * 1001 # 构造差分解法 df = self.Difference(nums) for trip in trips: # 乘客数量 val = trip[0] # 第 trip[1] 站乘客上车 i = trip[1] # 第 trip[2] 站乘客已经下车, # 即乘客在车上的区间是 [trip[1], trip[2] - 1] j = trip[2] - 1 # 进行区间操作 df.increment(i, j, val) res = df.result() # 客车自始至终都不应该超载 for i in range(len(res)): if capacity < res[i]: return False return True # 差分数组工具类 class Difference: # 差分数组 diff = [] """输入一个初始数组,区间操作将在这个数组上进行""" def __init__(self, nums): assert len(nums) > 0 self.diff = [0] * len(nums) # 根据初始数组构造差分数组 self.diff[0] = nums[0] for i in range(1, len(nums)): self.diff[i] = nums[i] - nums[i - 1] """给闭区间 [i, j] 增加 val(可以是负数)""" def increment(self, i, j, val): self.diff[i] += val if j + 1 < len(self.diff): self.diff[j + 1] -= val """返回结果数组""" def result(self): res = [0] * len(self.diff) # 根据差分数组构造结果数组 res[0] = self.diff[0] for i in range(1, len(self.diff)): res[i] = res[i - 1] + self.diff[i] return res ``` https://leetcode.cn/problems/car-pooling 的多语言解法👆 https://leetcode.cn/problems/cheapest-flights-within-k-stops 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { unordered_map>> indegree; int src, dst; // 备忘录 vector> memo; public: int findCheapestPrice(int n, vector>& flights, int src, int dst, int K) { // 将中转站个数转化成边的条数 K++; this->src = src; this->dst = dst; // 初始化备忘录,全部填一个特殊值 memo = vector>(n, vector(K + 1, -888)); for (auto &f : flights) { int from = f[0]; int to = f[1]; int price = f[2]; // 记录谁指向该节点,以及之间的权重 indegree[to].push_back(vector{from, price}); } return dp(dst, K); } // 定义:从 src 出发,k 步之内到达 s 的最短路径权重 int dp(int s, int k) { // base case if (s == src) { return 0; } if (k == 0) { return -1; } // 查备忘录,防止冗余计算 if (memo[s][k] != -888) { return memo[s][k]; } // 初始化为最大值,方便等会取最小值 int res = INT_MAX; if (indegree.count(s)) { // 当 s 有入度节点时,分解为子问题 auto &v = indegree[s]; for (auto &p : v) { int from = p[0]; int price = p[1]; // 从 src 到达相邻的入度节点所需的最短路径权重 int subProblem = dp(from, k - 1); /** ![](../pictures/旅行最短路径/4.jpeg) */ // 跳过无解的情况 if (subProblem != -1) { res = min(res, subProblem + price); } } } // 存入备忘录 memo[s][k] = res == INT_MAX ? -1 : res; return memo[s][k]; } }; ``` ```go // by chatGPT (go) func findCheapestPrice(n int, flights [][]int, src int, dst int, K int) int { // 将中转站个数转化成边的条数 K++ // 初始化备忘录,全部填一个特殊值 memo := make([][]int, n) for i := range memo { memo[i] = make([]int, K+1) for j := range memo[i] { memo[i][j] = -888 } } indegree := make(map[int][][]int) for _, f := range flights { from, to, price := f[0], f[1], f[2] // 记录谁指向该节点,以及之间的权重 indegree[to] = append(indegree[to], []int{from, price}) } return dp(dst, K, src, memo, indegree) } // 定义:从 src 出发,k 步之内到达 s 的最短路径权重 func dp(s int, k int, src int, memo [][]int, indegree map[int][][]int) int { // base case if s == src { return 0 } if k == 0 { return -1 } // 查备忘录,防止冗余计算 if memo[s][k] != -888 { return memo[s][k] } // 初始化为最大值,方便等会取最小值 res := math.MaxInt32 if v, ok := indegree[s]; ok { // 当 s 有入度节点时,分解为子问题 for _, edge := range v { from, price := edge[0], edge[1] // 从 src 到达相邻的入度节点所需的最短路径权重 subProblem := dp(from, k-1, src, memo, indegree) // 跳过无解的情况 if subProblem != -1 { res = min(res, subProblem+price) } } } // 存入备忘录 if res == math.MaxInt32 { memo[s][k] = -1 } else { memo[s][k] = res } return memo[s][k] } func min(x, y int) int { if x < y { return x } return y } ``` ```java // by labuladong (java) class Solution { HashMap> indegree; int src, dst; // 备忘录 int[][] memo; public int findCheapestPrice(int n, int[][] flights, int src, int dst, int K) { // 将中转站个数转化成边的条数 K++; this.src = src; this.dst = dst; // 初始化备忘录,全部填一个特殊值 memo = new int[n][K + 1]; for (int[] row : memo) { Arrays.fill(row, -888); } indegree = new HashMap<>(); for (int[] f : flights) { int from = f[0]; int to = f[1]; int price = f[2]; // 记录谁指向该节点,以及之间的权重 indegree.putIfAbsent(to, new LinkedList<>()); indegree.get(to).add(new int[]{from, price}); } return dp(dst, K); } // 定义:从 src 出发,k 步之内到达 s 的最短路径权重 int dp(int s, int k) { // base case if (s == src) { return 0; } if (k == 0) { return -1; } // 查备忘录,防止冗余计算 if (memo[s][k] != -888) { return memo[s][k]; } // 初始化为最大值,方便等会取最小值 int res = Integer.MAX_VALUE; if (indegree.containsKey(s)) { // 当 s 有入度节点时,分解为子问题 for (int[] v : indegree.get(s)) { int from = v[0]; int price = v[1]; // 从 src 到达相邻的入度节点所需的最短路径权重 int subProblem = dp(from, k - 1); /** ![](../pictures/旅行最短路径/4.jpeg) */ // 跳过无解的情况 if (subProblem != -1) { res = Math.min(res, subProblem + price); } } } // 存入备忘录 memo[s][k] = res == Integer.MAX_VALUE ? -1 : res; return memo[s][k]; } } ``` ```javascript // by chatGPT (javascript) var findCheapestPrice = function(n, flights, src, dst, K) { // 将中转站个数转化成边的条数 K++; // 定义备忘录 const memo = Array.from({ length: n }, () => Array(K + 1).fill(-888)); // 初始化起点和终点 const [source, target] = [src, dst]; // 构建入度字典 const indegree = new Map(); for (const [from, to, price] of flights) { indegree.set(to, (indegree.get(to) || []).concat([[from, price]])); } function dp(s, k) { // base case if (s === source) { return 0; } if (k === 0) { return -1; } // 查备忘录,防止冗余计算 if (memo[s][k] !== -888) { return memo[s][k]; } // 初始化为最大值,方便等会取最小值 let res = Infinity; if (indegree.has(s)) { // 当 s 有入度节点时,分解为子问题 for (const [from, price] of indegree.get(s)) { // 从 src 到达相邻的入度节点所需的最短路径权重 const subProblem = dp(from, k - 1); /** ![](../pictures/旅行最短路径/4.jpeg) */ // 跳过无解的情况 if (subProblem !== -1) { res = Math.min(res, subProblem + price); } } } // 存入备忘录 memo[s][k] = res === Infinity ? -1 : res; return memo[s][k]; } return dp(target, K); }; ``` ```python # by chatGPT (python) class Solution: def findCheapestPrice(self, n: int, flights: List[List[int]], src: int, dst: int, K: int) -> int: # 将中转站个数转化成边的条数 K += 1 # 初始化备忘录,全部填一个特殊值 memo = [[-888] * (K + 1) for _ in range(n)] # 定义入度字典 indegree = {} for f in flights: from_, to, price = f # 记录谁指向该节点,以及之间的权重 if to not in indegree: indegree[to] = [] indegree[to].append([from_, price]) # 定义:从 src 出发,k 步之内到达 s 的最短路径权重 def dp(s: int, k: int) -> int: # base case if s == src: return 0 if k == 0: return -1 # 查备忘录,防止冗余计算 if memo[s][k] != -888: return memo[s][k] # 初始化为最大值,方便等会取最小值 res = float('inf') if s in indegree: # 当 s 有入度节点时,分解为子问题 for v in indegree[s]: from_, price = v # 从 src 到达相邻的入度节点所需的最短路径权重 sub_problem = dp(from_, k - 1) # 跳过无解的情况 if sub_problem != -1: res = min(res, sub_problem + price) # 存入备忘录 memo[s][k] = -1 if res == float('inf') else res return memo[s][k] return dp(dst, K) ``` https://leetcode.cn/problems/cheapest-flights-within-k-stops 的多语言解法👆 https://leetcode.cn/problems/chou-shu-lcof 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int nthUglyNumber(int n) { // 可以理解为三个指向有序链表头结点的指针 int p2 = 1, p3 = 1, p5 = 1; // 可以理解为三个有序链表的头节点的值 int product2 = 1, product3 = 1, product5 = 1; // 可以理解为最终合并的有序链表(结果链表) int ugly[n + 1]; // 可以理解为结果链表上的指针 int p = 1; // 开始合并三个有序链表 while (p <= n) { // 取三个链表的最小结点 int minVal = min({product2, product3, product5}); // 接到结果链表上 ugly[p] = minVal; p++; // 前进对应有序链表上的指针 if (minVal == product2) { product2 = 2 * ugly[p2]; p2++; } if (minVal == product3) { product3 = 3 * ugly[p3]; p3++; } if (minVal == product5) { product5 = 5 * ugly[p5]; p5++; } } // 返回第 n 个丑数 return ugly[n]; } }; ``` ```go // by chatGPT (go) func nthUglyNumber(n int) int { // 可以理解为三个指向有序链表头结点的指针 p2, p3, p5 := 1, 1, 1 // 可以理解为三个有序链表的头节点的值 product2, product3, product5 := 1, 1, 1 // 可以理解为最终合并的有序链表(结果链表) ugly := make([]int, n+1) // 可以理解为结果链表上的指针 p := 1 // 开始合并三个有序链表 for p <= n { // 取三个链表的最小结点 min := min(min(product2, product3), product5) // 接到结果链表上 ugly[p] = min p++ // 前进对应有序链表上的指针 if min == product2 { product2 = 2 * ugly[p2] p2++ } if min == product3 { product3 = 3 * ugly[p3] p3++ } if min == product5 { product5 = 5 * ugly[p5] p5++ } } // 返回第 n 个丑数 return ugly[n] } func min(x, y int) int { if x < y { return x } return y } ``` ```java // by labuladong (java) class Solution { public int nthUglyNumber(int n) { // 可以理解为三个指向有序链表头结点的指针 int p2 = 1, p3 = 1, p5 = 1; // 可以理解为三个有序链表的头节点的值 int product2 = 1, product3 = 1, product5 = 1; // 可以理解为最终合并的有序链表(结果链表) int[] ugly = new int[n + 1]; // 可以理解为结果链表上的指针 int p = 1; // 开始合并三个有序链表 while (p <= n) { // 取三个链表的最小结点 int min = Math.min(Math.min(product2, product3), product5); // 接到结果链表上 ugly[p] = min; p++; // 前进对应有序链表上的指针 if (min == product2) { product2 = 2 * ugly[p2]; p2++; } if (min == product3) { product3 = 3 * ugly[p3]; p3++; } if (min == product5) { product5 = 5 * ugly[p5]; p5++; } } // 返回第 n 个丑数 return ugly[n]; } } ``` ```javascript // by chatGPT (javascript) var nthUglyNumber = function(n) { // 可以理解为三个指向有序链表头结点的指针 let p2 = 1, p3 = 1, p5 = 1; // 可以理解为三个有序链表的头节点的值 let product2 = 1, product3 = 1, product5 = 1; // 可以理解为最终合并的有序链表(结果链表) let ugly = new Array(n+1); // 可以理解为结果链表上的指针 let p = 1; // 开始合并三个有序链表 while (p <= n) { // 取三个链表的最小结点 let min = Math.min(Math.min(product2, product3), product5); // 接到结果链表上 ugly[p] = min; p++; // 前进对应有序链表上的指针 if (min === product2) { product2 = 2 * ugly[p2]; p2++; } if (min === product3) { product3 = 3 * ugly[p3]; p3++; } if (min === product5) { product5 = 5 * ugly[p5]; p5++; } } // 返回第 n 个丑数 return ugly[n]; }; ``` ```python # by chatGPT (python) class Solution: def nthUglyNumber(self, n: int) -> int: # 可以理解为三个指向有序链表头结点的指针 p2, p3, p5 = 1, 1, 1 # 可以理解为三个有序链表的头节点的值 product2, product3, product5 = 1, 1, 1 # 可以理解为最终合并的有序链表(结果链表) ugly = [0] * (n+1) # 可以理解为结果链表上的指针 p = 1 # 开始合并三个有序链表 while p <= n: # 取三个链表的最小结点 min_val = min(product2, product3, product5) # 接到结果链表上 ugly[p] = min_val p += 1 # 前进对应有序链表上的指针 if min_val == product2: product2 = 2 * ugly[p2] p2 += 1 if min_val == product3: product3 = 3 * ugly[p3] p3 += 1 if min_val == product5: product5 = 5 * ugly[p5] p5 += 1 # 返回第 n 个丑数 return ugly[n] ``` https://leetcode.cn/problems/chou-shu-lcof 的多语言解法👆 https://leetcode.cn/problems/climbing-stairs 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: // 备忘录 vector memo; int climbStairs(int n) { memo.resize(n + 1, 0); return dp(n); } // 定义:爬到第 n 级台阶的方法个数为 dp(n) int dp(int n) { // base case if (n <= 2) { return n; } if (memo[n] > 0) { return memo[n]; } // 状态转移方程: // 爬到第 n 级台阶的方法个数等于爬到 n - 1 的方法个数和爬到 n - 2 的方法个数之和。 memo[n] = dp(n - 1) + dp(n - 2); return memo[n]; } }; ``` ```go // by chatGPT (go) func climbStairs(n int) int { memo := make([]int, n+1) var dp func(int) int dp = func(n int) int { // base case if n <= 2 { return n } if memo[n] > 0 { return memo[n] } // 状态转移方程: // 爬到第 n 级台阶的方法个数等于爬到 n - 1 的方法个数和爬到 n - 2 的方法个数之和。 memo[n] = dp(n-1) + dp(n-2) return memo[n] } return dp(n) } ``` ```java // by labuladong (java) class Solution { // 备忘录 int[] memo; public int climbStairs(int n) { memo = new int[n + 1]; return dp(n); } // 定义:爬到第 n 级台阶的方法个数为 dp(n) int dp(int n) { // base case if (n <= 2) { return n; } if (memo[n] > 0) { return memo[n]; } // 状态转移方程: // 爬到第 n 级台阶的方法个数等于爬到 n - 1 的方法个数和爬到 n - 2 的方法个数之和。 memo[n] = dp(n - 1) + dp(n - 2); return memo[n]; } } ``` ```javascript // by chatGPT (javascript) var climbStairs = function(n) { // 备忘录 const memo = new Array(n + 1).fill(0); return dp(n); // 定义:爬到第 n 级台阶的方法个数为 dp(n) function dp(n) { // base case if (n <= 2) { return n; } if (memo[n] > 0) { return memo[n]; } // 状态转移方程: // 爬到第 n 级台阶的方法个数等于爬到 n - 1 的方法个数和爬到 n - 2 的方法个数之和。 memo[n] = dp(n - 1) + dp(n - 2); return memo[n]; } }; ``` ```python # by chatGPT (python) class Solution: # 备忘录 memo = [] def climbStairs(self, n: int) -> int: self.memo = [0] * (n + 1) return self.dp(n) # 定义:爬到第 n 级台阶的方法个数为 dp(n) def dp(self, n: int) -> int: # base case if n <= 2: return n if self.memo[n] > 0: return self.memo[n] # 状态转移方程: # 爬到第 n 级台阶的方法个数等于爬到 n - 1 的方法个数和爬到 n - 2 的方法个数之和。 self.memo[n] = self.dp(n - 1) + self.dp(n - 2) return self.memo[n] ``` https://leetcode.cn/problems/climbing-stairs 的多语言解法👆 https://leetcode.cn/problems/coin-change 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { vector memo; public: int coinChange(vector& coins, int amount) { memo.resize(amount + 1); // dp 数组全都初始化为特殊值 fill(memo.begin(), memo.end(), -666); return dp(coins, amount); } int dp(vector& coins, int amount) { if (amount == 0) return 0; if (amount < 0) return -1; // 查备忘录,防止重复计算 if (memo[amount] != -666) return memo[amount]; int res = INT_MAX; for (int coin : coins) { // 计算子问题的结果 int subProblem = dp(coins, amount - coin); /** ![](../pictures/动态规划详解进阶/5.jpg) */ // 子问题无解则跳过 if (subProblem == -1) continue; // 在子问题中选择最优解,然后加一 res = min(res, subProblem + 1); } // 把计算结果存入备忘录 memo[amount] = (res == INT_MAX) ? -1 : res; return memo[amount]; } }; ``` ```go // by chatGPT (go) func coinChange(coins []int, amount int) int { memo := make([]int, amount+1) for i := 0; i <= amount; i++ { memo[i] = -666 } return dp(coins, amount, memo) } func dp(coins []int, amount int, memo []int) int { if amount == 0 { return 0 } if amount < 0 { return -1 } // 查备忘录,防止重复计算 if memo[amount] != -666 { return memo[amount] } res := math.MaxInt32 for _, coin := range coins { // 计算子问题的结果 subProblem := dp(coins, amount-coin, memo) // 子问题无解则跳过 if subProblem == -1 { continue } // 在子问题中选择最优解,然后加一 res = min(res, subProblem+1) } // 把计算结果存入备忘录 if res == math.MaxInt32 { memo[amount] = -1 } else { memo[amount] = res } return memo[amount] } func min(a, b int) int { if a < b { return a } return b } ``` ```java // by labuladong (java) class Solution { int[] memo; public int coinChange(int[] coins, int amount) { memo = new int[amount + 1]; // dp 数组全都初始化为特殊值 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); /** ![](../pictures/动态规划详解进阶/5.jpg) */ // 子问题无解则跳过 if (subProblem == -1) continue; // 在子问题中选择最优解,然后加一 res = Math.min(res, subProblem + 1); } // 把计算结果存入备忘录 memo[amount] = (res == Integer.MAX_VALUE) ? -1 : res; return memo[amount]; } } ``` ```javascript // by chatGPT (javascript) var coinChange = function(coins, amount) { let memo = new Array(amount + 1).fill(-666); return dp(coins, amount); function dp(coins, amount) { if (amount == 0) return 0; if (amount < 0) return -1; // 查备忘录,防止重复计算 if (memo[amount] != -666) return memo[amount]; let res = Number.MAX_SAFE_INTEGER; for (let coin of coins) { // 计算子问题的结果 let subProblem = dp(coins, amount - coin); // 子问题无解则跳过 if (subProblem == -1) continue; // 在子问题中选择最优解,然后加一 res = Math.min(res, subProblem + 1); } // 把计算结果存入备忘录 memo[amount] = (res == Number.MAX_SAFE_INTEGER) ? -1 : res; return memo[amount]; } }; ``` ```python # by chatGPT (python) class Solution: def __init__(self): self.memo = [] def coinChange(self, coins: List[int], amount: int) -> int: self.memo = [-666] * (amount + 1) return self.dp(coins, amount) def dp(self, coins: List[int], amount: int) -> int: if amount == 0: return 0 if amount < 0: return -1 # 查备忘录,防止重复计算 if self.memo[amount] != -666: return self.memo[amount] res = float("inf") for coin in coins: # 计算子问题的结果 sub_problem = self.dp(coins, amount - coin) # 子问题无解则跳过 if sub_problem == -1: continue # 在子问题中选择最优解,然后加一 res = min(res, sub_problem + 1) # 把计算结果存入备忘录 self.memo[amount] = -1 if res == float("inf") else res return self.memo[amount] ``` https://leetcode.cn/problems/coin-change 的多语言解法👆 https://leetcode.cn/problems/coin-change-ii 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int change(int amount, vector& coins) { int n = coins.size(); vector> dp(n + 1, vector(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]; } }; ``` ```go // by chatGPT (go) func change(amount int, coins []int) int { n := len(coins) dp := make([][]int, n+1) for i := range dp { dp[i] = make([]int, amount+1) dp[i][0] = 1 } for i := 1; i <= n; i++ { for 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] } ``` ```java // by labuladong (java) 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]; } } ``` ```javascript // by chatGPT (javascript) var change = function(amount, coins) { let n = coins.length; let dp = Array.from(Array(n + 1), () => Array(amount + 1).fill(0)); // base case for (let i = 0; i <= n; i++) { dp[i][0] = 1; } for (let i = 1; i <= n; i++) { for (let 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]; }; ``` ```python # by chatGPT (python) class Solution: def change(self, amount: int, coins: List[int]) -> int: n = len(coins) dp = [[0]*(amount + 1) for i in range(n+1)] # base case for i in range(n+1): dp[i][0] = 1 for i in range(1, n+1): for j in range(1, amount+1): 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] ``` https://leetcode.cn/problems/coin-change-ii 的多语言解法👆 https://leetcode.cn/problems/combination-sum 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: vector> res; vector> combinationSum(vector& candidates, int target) { if (candidates.size() == 0) { return res; } backtrack(candidates, 0, target, 0); return res; } // 记录回溯的路径 vector track; // 回溯算法主函数 void backtrack(vector& candidates, int start, int target, int sum) { if (sum == target) { // 找到目标和 res.push_back(track); return; } if (sum > target) { // 超过目标和,直接结束 return; } // 回溯算法框架 for (int i = start; i < candidates.size(); i++) { // 选择 candidates[i] track.push_back(candidates[i]); sum += candidates[i]; // 递归遍历下一层回溯树 backtrack(candidates, i, target, sum); // 撤销选择 candidates[i] sum -= candidates[i]; track.pop_back(); } } }; ``` ```go // by chatGPT (go) func combinationSum(candidates []int, target int) [][]int { var res [][]int // 定义回溯函数 var backtrack func(start int, target int, sum int, track []int) backtrack = func(start int, target int, sum int, track []int) { // 如果当前 sum 等于 target,说明已找到符合要求的组合 if sum == target { // 由于 track 数组为引用类型,因此需要重新生成一个数组对象来保存到结果集 res = append(res, append([]int{}, track...)) return } // 如果当前 sum 已经大于 target,回溯结束 if sum > target { return } // 从指定位置开始遍历候选数组 for i := start; i < len(candidates); i++ { // 将候选数组当前元素加入路径 track = append(track, candidates[i]) sum += candidates[i] // 继续遍历下一个元素 backtrack(i, target, sum, track) // 回溯:将当前元素从路径中移除 sum -= candidates[i] track = track[:len(track)-1] } } backtrack(0, target, 0, []int{}) return res } ``` ```java // by labuladong (java) class Solution { List> res = new LinkedList<>(); public List> combinationSum(int[] candidates, int target) { if (candidates.length == 0) { return res; } backtrack(candidates, 0, target, 0); return res; } // 记录回溯的路径 LinkedList track = new LinkedList<>(); // 回溯算法主函数 void backtrack(int[] candidates, int start, int target, int sum) { if (sum == target) { // 找到目标和 res.add(new LinkedList<>(track)); return; } if (sum > target) { // 超过目标和,直接结束 return; } // 回溯算法框架 for (int i = start; i < candidates.length; i++) { // 选择 candidates[i] track.add(candidates[i]); sum += candidates[i]; // 递归遍历下一层回溯树 backtrack(candidates, i, target, sum); // 撤销选择 candidates[i] sum -= candidates[i]; track.removeLast(); } } } ``` ```javascript // by chatGPT (javascript) var combinationSum = function(candidates, target) { let res = []; let track = []; backtrack(candidates, 0, target, 0, track); return res; function backtrack(candidates, start, target, sum, track) { if (sum === target) { // 找到目标和 res.push([...track]); return; } if (sum > target) { // 超过目标和,直接结束 return; } // 回溯算法框架 for (let i = start; i < candidates.length; i++) { // 选择 candidates[i] track.push(candidates[i]); sum += candidates[i]; // 递归遍历下一层回溯树 backtrack(candidates, i, target, sum, track); // 撤销选择 candidates[i] sum -= candidates[i]; track.pop(); } } }; ``` ```python # by chatGPT (python) class Solution: def __init__(self): self.res = [] def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]: if not candidates: return self.res self.backtrack(candidates, 0, target, 0) return self.res # 记录回溯的路径 track = [] # 回溯算法主函数 def backtrack(self, candidates, start, target, sum): if sum == target: # 找到目标和 self.res.append(self.track[:]) return if sum > target: # 超过目标和,直接结束 return # 回溯算法框架 for i in range(start, len(candidates)): # 选择 candidates[i] self.track.append(candidates[i]) sum += candidates[i] # 递归遍历下一层回溯树 self.backtrack(candidates, i, target, sum) # 撤销选择 candidates[i] sum -= candidates[i] self.track.pop() ``` https://leetcode.cn/problems/combination-sum 的多语言解法👆 https://leetcode.cn/problems/combinations 的多语言解法👇 ```cpp // by labuladong (cpp) class Solution { public: vector>res; vector> combine(int n, int k) { if (k <= 0 || n <= 0) return res; vector track; backtrack(n, k, 1, track); return res; } void backtrack(int n, int k, int start, vector& track) { // 到达树的底部 if (k == track.size()) { res.push_back(track); return; } // 注意 i 从 start 开始递增 for (int i = start; i <= n; i++) { // 做选择 track.push_back(i); backtrack(n, k, i + 1, track); // 撤销选择 track.pop_back(); } } }; ``` ```go // by chatGPT (go) func combine(n int, k int) [][]int { res := [][]int{} if k <= 0 || n <= 0 { return res } track := []int{} backtrack := func(n, k, start int, track []int) {} backtrack = func(n, k, start int, track []int) { // 到达树的底部 if k == len(track) { ans := make([]int, k) copy(ans, track) res = append(res, ans) return } // 注意 i 从 start 开始递增 for i := start; i <= n; i++ { // 做选择 track = append(track, i) backtrack(n, k, i+1, track) // 撤销选择 track = track[:len(track)-1] } } backtrack(n, k, 1, track) return res } ``` ```java // by chatGPT (java) class Solution { List> res = new LinkedList<>(); public List> combine(int n, int k) { if (k <= 0 || n <= 0) { return res; } List track = new LinkedList<>(); backtrack(n, k, 1, track); return res; } void backtrack(int n, int k, int start, List track) { // 到达树的底部 if (k == track.size()) { res.add(new LinkedList<>(track)); return; } // 注意 i 从 start 开始递增 for (int i = start; i <= n; i++) { // 做选择 track.add(i); backtrack(n, k, i + 1, track); // 撤销选择 track.remove(track.size() - 1); } } } ``` ```javascript // by chatGPT (javascript) var combine = function(n, k) { let res = []; if (k <= 0 || n <= 0) return res; let track = []; backtrack(n, k, 1, track, res); return res; }; var backtrack = function(n, k, start, track, res) { // 到达树的底部 if (k === track.length) { res.push([...track]); return; } // 注意 i 从 start 开始递增 for (let i = start; i <= n; i++) { // 做选择 track.push(i); backtrack(n, k, i + 1, track, res); // 撤销选择 track.pop(); } }; // Example usage console.log(combine(4, 2)); // [[1,2],[1,3],[1,4],[2,3],[2,4],[3,4]] ``` ```python # by chatGPT (python) class Solution: def __init__(self): self.res = [] def combine(self, n: int, k: int) -> List[List[int]]: if k <= 0 or n <= 0: return self.res track = [] self.backtrack(n, k, 1, track) return self.res def backtrack(self, n: int, k: int, start: int, track: List[int]) -> None: # 到达树的底部 if k == len(track): self.res.append(track[:]) return # 注意 i 从 start 开始递增 for i in range(start, n + 1): # 做选择 track.append(i) self.backtrack(n, k, i + 1, track) # 撤销选择 track.pop() ``` https://leetcode.cn/problems/combinations 的多语言解法👆 https://leetcode.cn/problems/cong-shang-dao-xia-da-yin-er-cha-shu-ii-lcof 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: vector> levelOrder(TreeNode* root) { vector> res; if (root == nullptr) { return res; } queue q; q.push(root); // while 循环控制从上向下一层层遍历 while (!q.empty()) { int sz = q.size(); // 记录这一层的节点值 vector level; // for 循环控制每一层从左向右遍历 for (int i = 0; i < sz; i++) { TreeNode* cur = q.front(); q.pop(); level.push_back(cur->val); if (cur->left != nullptr) q.push(cur->left); if (cur->right != nullptr) q.push(cur->right); } res.push_back(level); } return res; } }; ``` ```go // by chatGPT (go) func levelOrder(root *TreeNode) [][]int { res := [][]int{} if root == nil { return res } q := []*TreeNode{root} // while 循环控制从上向下一层层遍历 for len(q) != 0 { sz := len(q) // 记录这一层的节点值 level := []int{} // for 循环控制每一层从左向右遍历 for i := 0; i < sz; i++ { cur := q[0] q = q[1:] level = append(level, cur.Val) if cur.Left != nil { q = append(q, cur.Left) } if cur.Right != nil { q = append(q, cur.Right) } } res = append(res, level) } return res } ``` ```java // by labuladong (java) class Solution { public List> levelOrder(TreeNode root) { List> res = new LinkedList<>(); if (root == null) { return res; } Queue q = new LinkedList<>(); q.offer(root); // while 循环控制从上向下一层层遍历 while (!q.isEmpty()) { int sz = q.size(); // 记录这一层的节点值 List level = new LinkedList<>(); // for 循环控制每一层从左向右遍历 for (int i = 0; i < sz; i++) { TreeNode cur = q.poll(); level.add(cur.val); if (cur.left != null) q.offer(cur.left); if (cur.right != null) q.offer(cur.right); } res.add(level); } return res; } } ``` ```javascript // by chatGPT (javascript) var levelOrder = function(root) { let res = []; if (root == null) { return res; } let q = []; q.push(root); // while 循环控制从上向下一层层遍历 while (q.length > 0) { let sz = q.length; // 记录这一层的节点值 let level = []; // for 循环控制每一层从左向右遍历 for (let i = 0; i < sz; i++) { let cur = q.shift(); level.push(cur.val); if (cur.left != null) q.push(cur.left); if (cur.right != null) q.push(cur.right); } res.push(level); } return res; }; ``` ```python # by chatGPT (python) class Solution: def levelOrder(self, root: TreeNode) -> List[List[int]]: res = [] if not root: return res q = collections.deque() q.append(root) # while 循环控制从上向下一层层遍历 while q: sz = len(q) # 记录这一层的节点值 level = [] # for 循环控制每一层从左向右遍历 for i in range(sz): cur = q.popleft() level.append(cur.val) if cur.left: q.append(cur.left) if cur.right: q.append(cur.right) res.append(level) return res ``` https://leetcode.cn/problems/cong-shang-dao-xia-da-yin-er-cha-shu-ii-lcof 的多语言解法👆 https://leetcode.cn/problems/cong-shang-dao-xia-da-yin-er-cha-shu-iii-lcof 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: vector> zigzagLevelOrder(TreeNode* root) { vector> res; if (root == nullptr) { return res; } queue q; q.push(root); // 为 true 时向右,false 时向左 bool flag = true; // while 循环控制从上向下一层层遍历 while (!q.empty()) { int sz = q.size(); // 记录这一层的节点值 deque level; // for 循环控制每一层从左向右遍历 for (int i = 0; i < sz; i++) { TreeNode* cur = q.front(); q.pop(); // 实现 z 字形遍历 if (flag) { level.push_back(cur->val); } else { level.push_front(cur->val); } if (cur->left != nullptr) q.push(cur->left); if (cur->right != nullptr) q.push(cur->right); } // 切换方向 flag = !flag; res.emplace_back(vector(level.begin(), level.end())); } return res; } }; ``` ```go // by chatGPT (go) func zigzagLevelOrder(root *TreeNode) [][]int { res := [][]int{} if root == nil { return res } q := []*TreeNode{root} // 为 true 时向右,false 时向左 flag := true // while 循环控制从上向下一层层遍历 for len(q) > 0 { sz := len(q) // 记录这一层的节点值 level := []int{} // for 循环控制每一层从左向右遍历 for i := 0; i < sz; i++ { cur := q[0] q = q[1:] // 实现 z 字形遍历 if flag { level = append(level, cur.Val) } else { level = append([]int{cur.Val}, level...) } if cur.Left != nil { q = append(q, cur.Left) } if cur.Right != nil { q = append(q, cur.Right) } } // 切换方向 flag = !flag res = append(res, level) } return res } ``` ```java // by labuladong (java) class Solution { public List> zigzagLevelOrder(TreeNode root) { List> res = new LinkedList<>(); if (root == null) { return res; } Queue q = new LinkedList<>(); q.offer(root); // 为 true 时向右,false 时向左 boolean flag = true; // while 循环控制从上向下一层层遍历 while (!q.isEmpty()) { int sz = q.size(); // 记录这一层的节点值 LinkedList level = new LinkedList<>(); // for 循环控制每一层从左向右遍历 for (int i = 0; i < sz; i++) { TreeNode cur = q.poll(); // 实现 z 字形遍历 if (flag) { level.addLast(cur.val); } else { level.addFirst(cur.val); } if (cur.left != null) q.offer(cur.left); if (cur.right != null) q.offer(cur.right); } // 切换方向 flag = !flag; res.add(level); } return res; } } ``` ```javascript // by chatGPT (javascript) var zigzagLevelOrder = function(root) { let res = []; if (root === null) { return res; } let q = []; q.push(root); // 为 true 时向右,false 时向左 let flag = true; // while 循环控制从上向下一层层遍历 while (q.length !== 0) { let sz = q.length; // 记录这一层的节点值 let level = []; // for 循环控制每一层从左向右遍历 for (let i = 0; i < sz; i++) { let cur = q.shift(); // 实现 z 字形遍历 if (flag) { level.push(cur.val); } else { level.unshift(cur.val); } if (cur.left !== null) q.push(cur.left); if (cur.right !== null) q.push(cur.right); } // 切换方向 flag = !flag; res.push(level); } return res; }; ``` ```python # by chatGPT (python) class Solution: def zigzagLevelOrder(self, root: TreeNode) -> List[List[int]]: res = [] if root is None: return res q = deque([root]) # 为 True 时向右,False 时向左 flag = True # while 循环控制从上向下一层层遍历 while q: sz = len(q) # 记录这一层的节点值 level = deque() # for 循环控制每一层从左向右遍历 for i in range(sz): cur = q.popleft() # 实现 z 字形遍历 if flag: level.append(cur.val) else: level.appendleft(cur.val) if cur.left: q.append(cur.left) if cur.right: q.append(cur.right) # 切换方向 flag = not flag res.append(list(level)) return res ``` https://leetcode.cn/problems/cong-shang-dao-xia-da-yin-er-cha-shu-iii-lcof 的多语言解法👆 https://leetcode.cn/problems/cong-shang-dao-xia-da-yin-er-cha-shu-lcof 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: vector> levelOrder(TreeNode* root) { vector> res; if (root == nullptr) { return res; } queue q; q.push(root); // while 循环控制从上向下一层层遍历 while (!q.empty()) { int sz = q.size(); // 记录这一层的节点值 vector level; // for 循环控制每一层从左向右遍历 for (int i = 0; i < sz; i++) { TreeNode* cur = q.front(); q.pop(); level.push_back(cur->val); if (cur->left != nullptr) q.push(cur->left); if (cur->right != nullptr) q.push(cur->right); } res.push_back(level); } return res; } }; ``` ```go // by chatGPT (go) func levelOrder(root *TreeNode) [][]int { res := [][]int{} if root == nil { return res } q := []*TreeNode{} q = append(q, root) // while 循环控制从上向下一层层遍历 for len(q) > 0 { sz := len(q) // 记录这一层的节点值 level := []int{} // for 循环控制每一层从左向右遍历 for i := 0; i < sz; i++ { cur := q[0] q = q[1:] level = append(level, cur.Val) if cur.Left != nil { q = append(q, cur.Left) } if cur.Right != nil { q = append(q, cur.Right) } } res = append(res, level) } return res } ``` ```java // by labuladong (java) class Solution { public List> levelOrder(TreeNode root) { List> res = new LinkedList<>(); if (root == null) { return res; } Queue q = new LinkedList<>(); q.offer(root); // while 循环控制从上向下一层层遍历 while (!q.isEmpty()) { int sz = q.size(); // 记录这一层的节点值 List level = new LinkedList<>(); // for 循环控制每一层从左向右遍历 for (int i = 0; i < sz; i++) { TreeNode cur = q.poll(); level.add(cur.val); if (cur.left != null) q.offer(cur.left); if (cur.right != null) q.offer(cur.right); } res.add(level); } return res; } } ``` ```javascript // by chatGPT (javascript) var levelOrder = function(root) { let res = []; if (!root) { return res; } let q = []; q.push(root); // while 循环控制从上向下一层层遍历 while (q.length > 0) { let sz = q.length; // 记录这一层的节点值 let level = []; // for 循环控制每一层从左向右遍历 for (let i = 0; i < sz; i++) { let cur = q.shift(); level.push(cur.val); if (cur.left !== null) { q.push(cur.left); } if (cur.right !== null) { q.push(cur.right); } } res.push(level); } return res; }; ``` ```python # by chatGPT (python) class Solution: def levelOrder(self, root: TreeNode) -> List[List[int]]: res = [] if root is None: return res q = collections.deque() q.append(root) # while 循环控制从上向下一层层遍历 while q: sz = len(q) # 记录这一层的节点值 level = [] # for 循环控制每一层从左向右遍历 for i in range(sz): cur = q.popleft() level.append(cur.val) if cur.left: q.append(cur.left) if cur.right: q.append(cur.right) res.append(level) return res ``` https://leetcode.cn/problems/cong-shang-dao-xia-da-yin-er-cha-shu-lcof 的多语言解法👆 https://leetcode.cn/problems/cong-wei-dao-tou-da-yin-lian-biao-lcof 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: vector reversePrint(ListNode* head) { traverse(head); return res; } // 记录链表长度 int len = 0; // 结果数组 vector res; // 结果数组中的指针 int p = 0; // 递归遍历单链表 void traverse(ListNode* head) { if (head == nullptr) { // 到达链表尾部,此时知道了链表的总长度 // 创建结果数组 res.resize(len); return; } len++; traverse(head->next); // 后序位置,可以倒序操作链表 res[p] = head->val; p++; } // 用「分解问题」的思路写递归解法 // 因为 C++ 的 vector 可以支持 push_back 操作,所以我们不需要改变返回值类型 vector reversePrint2(ListNode* head) { // base case if (head == nullptr) { return {}; } // 把子链表翻转的结果算出来,示例 [3,2] vector subProblem = reversePrint2(head->next); // 把 head 的值接到子链表的翻转结果的尾部,示例 [3,2,1] subProblem.push_back(head->val); return subProblem; } }; ``` ```go // by chatGPT (go) /* 用「遍历」的思路写递归解法 */ func reversePrint(head *ListNode) []int { var res []int var len int // 递归遍历单链表 var traverse func(*ListNode) traverse = func(head *ListNode) { if head == nil { // 到达链表尾部,此时知道了链表的总长度 // 创建结果数组 res = make([]int, len) return } len++ traverse(head.Next) // 后序位置,可以倒序操作链表 res[len-p-1] = head.Val p++ } traverse(head) return res } /* 用「分解问题」的思路写递归解法 因为 Go 不支持泛型,所以我们把返回值修改成 []int 定义:输入一个单链表,返回该链表翻转的值,示例 1->2->3 */ func reversePrint2(head *ListNode) []int { // base case if head == nil { return []int{} } // 把子链表翻转的结果算出来,示例 [3,2] subProblem := reversePrint2(head.Next) // 把 head 的值接到子链表的翻转结果的尾部,示例 [3,2,1] return append(subProblem, head.Val) } ``` ```java // by labuladong (java) // 用「遍历」的思路写递归解法 class Solution { public int[] reversePrint(ListNode head) { traverse(head); return res; } // 记录链表长度 int len = 0; // 结果数组 int[] res; // 结果数组中的指针 int p = 0; // 递归遍历单链表 void traverse(ListNode head) { if (head == null) { // 到达链表尾部,此时知道了链表的总长度 // 创建结果数组 res = new int[len]; return; } len++; traverse(head.next); // 后序位置,可以倒序操作链表 res[p] = head.val; p++; } // 用「分解问题」的思路写递归解法 // 因为 Java 的 int[] 数组不支持 add 相关的操作,所以我们把返回值修改成 List // 定义:输入一个单链表,返回该链表翻转的值,示例 1->2->3 List reversePrint2(ListNode head) { // base case if (head == null) { return new LinkedList<>(); } // 把子链表翻转的结果算出来,示例 [3,2] List subProblem = reversePrint2(head.next); // 把 head 的值接到子链表的翻转结果的尾部,示例 [3,2,1] subProblem.add(head.val); return subProblem; } } ``` ```javascript // by chatGPT (javascript) /** * 用「遍历」的思路写递归解法 */ var reversePrint = function(head) { let len = 0; // 记录链表长度 let res = []; // 结果数组 let p = 0; // 结果数组中的指针 // 递归遍历单链表 const traverse = (head) => { if (!head) { // 到达链表尾部,此时知道了链表的总长度 // 创建结果数组 res = new Array(len); return; } len++; traverse(head.next); // 后序位置,可以倒序操作链表 res[p] = head.val; p++; }; traverse(head); return res; }; /** * 用「分解问题」的思路写递归解法 * 因为 Java 的 int[] 数组不支持 add 相关的操作,所以我们把返回值修改成 List * 定义:输入一个单链表,返回该链表翻转的值,示例 1->2->3 */ var reversePrint2 = function(head) { // base case if (!head) { return new LinkedList(); } // 把子链表翻转的结果算出来,示例 [3,2] let subProblem = reversePrint2(head.next); // 把 head 的值接到子链表的翻转结果的尾部,示例 [3,2,1] subProblem.push(head.val); return subProblem; }; ``` ```python # by chatGPT (python) class Solution: def reversePrint(self, head: ListNode) -> List[int]: # 用「遍历」的思路写递归解法 def traverse(head): nonlocal len_, res, p if not head: res = [0] * len_ return len_ += 1 traverse(head.next) res[p] = head.val p += 1 len_ = 0 res = [] p = 0 traverse(head) return res def reversePrint2(self, head: ListNode) -> List[int]: # 用「分解问题」的思路写递归解法 def sub_problem(head): if not head: return [] sub_res = sub_problem(head.next) sub_res.append(head.val) return sub_res return sub_problem(head) ``` https://leetcode.cn/problems/cong-wei-dao-tou-da-yin-lian-biao-lcof 的多语言解法👆 https://leetcode.cn/problems/connecting-cities-with-minimum-cost 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int minimumCost(int n, vector>& connections) { // 城市编号为 1...n,所以初始化大小为 n + 1 UF uf(n + 1); // 对所有边按照权重从小到大排序 sort(connections.begin(), connections.end(), [](vector& a, vector& b){ return a[2] < b[2]; }); // 记录最小生成树的权重之和 int mst = 0; for (auto& edge : connections) { int u = edge[0]; int v = edge[1]; int weight = edge[2]; // 若这条边会产生环,则不能加入 mst if (uf.connected(u, v)) { continue; } // 若这条边不会产生环,则属于最小生成树 mst += weight; uf.union_(u, v); } // 保证所有节点都被连通 // 按理说 uf.count() == 1 说明所有节点被连通 // 但因为节点 0 没有被使用,所以 0 会额外占用一个连通分量 return uf.count() == 2 ? mst : -1; } private: class UF { public: // 连通分量个数 int count; // 存储一棵树 vector parent; // 记录树的「重量」 vector size; // n 为图中节点的个数 UF(int n) : count(n), parent(n), size(n, 1) { for (int i = 0; i < n; i++) { parent[i] = i; } } // 将节点 p 和节点 q 连通 void union_(int p, int q) { int rootP = find(p); int rootQ = find(q); if (rootP == rootQ) return; // 小树接到大树下面,较平衡 if (size[rootP] > size[rootQ]) { parent[rootQ] = rootP; size[rootP] += size[rootQ]; } else { parent[rootP] = rootQ; size[rootQ] += size[rootP]; } // 两个连通分量合并成一个连通分量 count--; } // 判断节点 p 和节点 q 是否连通 bool connected(int p, int q) { int rootP = find(p); int rootQ = find(q); return rootP == rootQ; } // 返回节点 x 的连通分量根节点 int find(int x) { while (parent[x] != x) { // 进行路径压缩 parent[x] = parent[parent[x]]; x = parent[x]; } return x; } }; }; ``` ```go // by chatGPT (go) func minimumCost(n int, connections [][]int) int { // 城市编号为 1...n,所以初始化大小为 n + 1 uf := NewUF(n + 1) // 对所有边按照权重从小到大排序 sort.Slice(connections, func(i, j int) bool { return connections[i][2] < connections[j][2] }) // 记录最小生成树的权重之和 mst := 0 for _, edge := range connections { u := edge[0] v := edge[1] weight := edge[2] // 若这条边会产生环,则不能加入 mst if uf.connected(u, v) { continue } // 若这条边不会产生环,则属于最小生成树 mst += weight uf.union(u, v) } // 保证所有节点都被连通 // 按理说 uf.count() == 1 说明所有节点被连通 // 但因为节点 0 没有被使用,所以 0 会额外占用一个连通分量 if uf.count() == 2 { return mst } return -1 } type UF struct { // 连通分量个数 count int // 存储一棵树 parent []int // 记录树的「重量」 size []int } // n 为图中节点的个数 func NewUF(n int) *UF { uf := &UF{} uf.count = n uf.parent = make([]int, n) uf.size = make([]int, n) for i := 0; i < n; i++ { uf.parent[i] = i uf.size[i] = 1 } return uf } // 将节点 p 和节点 q 连通 func (uf *UF) union(p, q int) { rootP := uf.find(p) rootQ := uf.find(q) if rootP == rootQ { return } // 小树接到大树下面,较平衡 if uf.size[rootP] > uf.size[rootQ] { uf.parent[rootQ] = rootP uf.size[rootP] += uf.size[rootQ] } else { uf.parent[rootP] = rootQ uf.size[rootQ] += uf.size[rootP] } // 两个连通分量合并成一个连通分量 uf.count-- } // 判断节点 p 和节点 q 是否连通 func (uf *UF) connected(p, q int) bool { rootP := uf.find(p) rootQ := uf.find(q) return rootP == rootQ } // 返回节点 x 的连通分量根节点 func (uf *UF) find(x int) int { for uf.parent[x] != x { // 进行路径压缩 uf.parent[x] = uf.parent[uf.parent[x]] x = uf.parent[x] } return x } // 返回图中的连通分量个数 func (uf *UF) count() int { return uf.count } ``` ```java // by labuladong (java) class Solution { public int minimumCost(int n, int[][] connections) { // 城市编号为 1...n,所以初始化大小为 n + 1 UF uf = new UF(n + 1); // 对所有边按照权重从小到大排序 Arrays.sort(connections, (a, b) -> (a[2] - b[2])); // 记录最小生成树的权重之和 int mst = 0; for (int[] edge : connections) { int u = edge[0]; int v = edge[1]; int weight = edge[2]; // 若这条边会产生环,则不能加入 mst if (uf.connected(u, v)) { continue; } // 若这条边不会产生环,则属于最小生成树 mst += weight; uf.union(u, v); } // 保证所有节点都被连通 // 按理说 uf.count() == 1 说明所有节点被连通 // 但因为节点 0 没有被使用,所以 0 会额外占用一个连通分量 return uf.count() == 2 ? mst : -1; } class UF { // 连通分量个数 private int count; // 存储一棵树 private int[] parent; // 记录树的「重量」 private int[] size; // n 为图中节点的个数 public UF(int n) { this.count = n; parent = new int[n]; size = new int[n]; for (int i = 0; i < n; i++) { parent[i] = i; size[i] = 1; } } // 将节点 p 和节点 q 连通 public void union(int p, int q) { int rootP = find(p); int rootQ = find(q); if (rootP == rootQ) return; // 小树接到大树下面,较平衡 if (size[rootP] > size[rootQ]) { parent[rootQ] = rootP; size[rootP] += size[rootQ]; } else { parent[rootP] = rootQ; size[rootQ] += size[rootP]; } // 两个连通分量合并成一个连通分量 count--; } // 判断节点 p 和节点 q 是否连通 public boolean connected(int p, int q) { int rootP = find(p); int rootQ = find(q); return rootP == rootQ; } // 返回节点 x 的连通分量根节点 private int find(int x) { while (parent[x] != x) { // 进行路径压缩 parent[x] = parent[parent[x]]; x = parent[x]; } return x; } // 返回图中的连通分量个数 public int count() { return count; } } } ``` ```javascript // by chatGPT (javascript) /** * @param {number} n * @param {number[][]} connections * @return {number} */ var minimumCost = function(n, connections) { // 城市编号为 1...n,所以初始化大小为 n + 1 const uf = new UF(n + 1); // 对所有边按照权重从小到大排序 connections.sort((a, b) => a[2] - b[2]); // 记录最小生成树的权重之和 let mst = 0; for (const edge of connections) { const u = edge[0]; const v = edge[1]; const weight = edge[2]; // 若这条边会产生环,则不能加入 mst if (uf.connected(u, v)) { continue; } // 若这条边不会产生环,则属于最小生成树 mst += weight; uf.union(u, v); } // 保证所有节点都被连通 // 按理说 uf.count() == 1 说明所有节点被连通 // 但因为节点 0 没有被使用,所以 0 会额外占用一个连通分量 return uf.count() == 2 ? mst : -1; }; class UF { // 连通分量个数 #count; // 存储一棵树 #parent; // 记录树的「重量」 #size; // n 为图中节点的个数 constructor(n) { this.#count = n; this.#parent = new Array(n); this.#size = new Array(n); for (let i = 0; i < n; i++) { this.#parent[i] = i; this.#size[i] = 1; } } // 将节点 p 和节点 q 连通 union(p, q) { const rootP = this.find(p); const rootQ = this.find(q); if (rootP === rootQ) return; // 小树接到大树下面,较平衡 if (this.#size[rootP] > this.#size[rootQ]) { this.#parent[rootQ] = rootP; this.#size[rootP] += this.#size[rootQ]; } else { this.#parent[rootP] = rootQ; this.#size[rootQ] += this.#size[rootP]; } // 两个连通分量合并成一个连通分量 this.#count--; } // 判断节点 p 和节点 q 是否连通 connected(p, q) { const rootP = this.find(p); const rootQ = this.find(q); return rootP === rootQ; } // 返回节点 x 的连通分量根节点 find(x) { while (this.#parent[x] !== x) { // 进行路径压缩 this.#parent[x] = this.#parent[this.#parent[x]]; x = this.#parent[x]; } return x; } // 返回图中的连通分量个数 count() { return this.#count; } } ``` ```python # by chatGPT (python) class Solution: def minimumCost(self, n: int, connections: List[List[int]]) -> int: # 城市编号为 1...n,所以初始化大小为 n + 1 uf = UF(n + 1) # 对所有边按照权重从小到大排序 connections.sort(key=lambda x: x[2]) # 记录最小生成树的权重之和 mst = 0 for edge in connections: u, v, weight = edge # 若这条边会产生环,则不能加入 mst if uf.connected(u, v): continue # 若这条边不会产生环,则属于最小生成树 mst += weight uf.union(u, v) # 保证所有节点都被连通 # 按理说 uf.count() == 1 说明所有节点被连通 # 但因为节点 0 没有被使用,所以 0 会额外占用一个连通分量 return mst if uf.count() == 2 else -1 class UF: # 连通分量个数 def __init__(self, n: int): self.count = n # 存储一棵树 self.parent = list(range(n)) # 记录树的「重量」 self.size = [1] * n # 将节点 p 和节点 q 连通 def union(self, p: int, q: int): rootP = self.find(p) rootQ = self.find(q) if rootP == rootQ: return # 小树接到大树下面,较平衡 if self.size[rootP] > self.size[rootQ]: self.parent[rootQ] = rootP self.size[rootP] += self.size[rootQ] else: self.parent[rootP] = rootQ self.size[rootQ] += self.size[rootP] # 两个连通分量合并成一个连通分量 self.count -= 1 # 判断节点 p 和节点 q 是否连通 def connected(self, p: int, q: int) -> bool: rootP = self.find(p) rootQ = self.find(q) return rootP == rootQ # 返回节点 x 的连通分量根节点 def find(self, x: int) -> int: while self.parent[x] != x: # 进行路径压缩 self.parent[x] = self.parent[self.parent[x]] x = self.parent[x] return x # 返回图中的连通分量个数 def count(self) -> int: return self.count ``` https://leetcode.cn/problems/connecting-cities-with-minimum-cost 的多语言解法👆 https://leetcode.cn/problems/construct-binary-tree-from-inorder-and-postorder-traversal 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { private: // 存储 inorder 中值到索引的映射 unordered_map valToIndex; public: TreeNode* buildTree(vector& inorder, vector& postorder) { for (int i = 0; i < inorder.size(); i++) { valToIndex[inorder[i]] = i; } return build(inorder, 0, inorder.size() - 1, postorder, 0, postorder.size() - 1); } /* 定义: 中序遍历数组为 inorder[inStart..inEnd], 后序遍历数组为 postorder[postStart..postEnd], 构造这个二叉树并返回该二叉树的根节点 */ TreeNode* build(vector& inorder, int inStart, int inEnd, vector& postorder, int postStart, int postEnd) { if (inStart > inEnd) { return nullptr; } // root 节点对应的值就是后序遍历数组的最后一个元素 int rootVal = postorder[postEnd]; // rootVal 在中序遍历数组中的索引 int index = valToIndex[rootVal]; // 左子树的节点个数 int leftSize = index - inStart; TreeNode* root = new TreeNode(rootVal); /** ![](../pictures/二叉树系列2/6.jpeg) */ // 递归构造左右子树 root->left = build(inorder, inStart, index - 1, postorder, postStart, postStart + leftSize - 1); root->right = build(inorder, index + 1, inEnd, postorder, postStart + leftSize, postEnd - 1); return root; } }; ``` ```go // by chatGPT (go) func buildTree(inorder []int, postorder []int) *TreeNode { // 存储 inorder 中值到索引的映射 valToIndex := make(map[int]int) for i, val := range inorder { valToIndex[val] = i } return build(inorder, 0, len(inorder)-1, postorder, 0, len(postorder)-1, valToIndex) } /* 定义: 中序遍历数组为 inorder[inStart..inEnd], 后序遍历数组为 postorder[postStart..postEnd], 构造这个二叉树并返回该二叉树的根节点 */ func build(inorder []int, inStart int, inEnd int, postorder []int, postStart int, postEnd int, valToIndex map[int]int) *TreeNode { if inStart > inEnd { return nil } // root 节点对应的值就是后序遍历数组的最后一个元素 rootVal := postorder[postEnd] // rootVal 在中序遍历数组中的索引 index := valToIndex[rootVal] // 左子树的节点个数 leftSize := index - inStart root := &TreeNode{Val: rootVal} // 递归构造左右子树 root.Left = build(inorder, inStart, index-1, postorder, postStart, postStart+leftSize-1, valToIndex) root.Right = build(inorder, index+1, inEnd, postorder, postStart+leftSize, postEnd-1, valToIndex) return root } ``` ```java // by labuladong (java) class Solution { // 存储 inorder 中值到索引的映射 HashMap valToIndex = new HashMap<>(); public TreeNode buildTree(int[] inorder, int[] postorder) { for (int i = 0; i < inorder.length; i++) { valToIndex.put(inorder[i], i); } return build(inorder, 0, inorder.length - 1, postorder, 0, postorder.length - 1); } /* 定义: 中序遍历数组为 inorder[inStart..inEnd], 后序遍历数组为 postorder[postStart..postEnd], 构造这个二叉树并返回该二叉树的根节点 */ TreeNode build(int[] inorder, int inStart, int inEnd, int[] postorder, int postStart, int postEnd) { if (inStart > inEnd) { return null; } // root 节点对应的值就是后序遍历数组的最后一个元素 int rootVal = postorder[postEnd]; // rootVal 在中序遍历数组中的索引 int index = valToIndex.get(rootVal); // 左子树的节点个数 int leftSize = index - inStart; TreeNode root = new TreeNode(rootVal); /** ![](../pictures/二叉树系列2/6.jpeg) */ // 递归构造左右子树 root.left = build(inorder, inStart, index - 1, postorder, postStart, postStart + leftSize - 1); root.right = build(inorder, index + 1, inEnd, postorder, postStart + leftSize, postEnd - 1); return root; } } ``` ```javascript // by chatGPT (javascript) /** * @param {number[]} inorder * @param {number[]} postorder * @return {TreeNode} */ var buildTree = function(inorder, postorder) { // 存储 inorder 中值到索引的映射 const valToIndex = new Map(); for (let i = 0; i < inorder.length; i++) { valToIndex.set(inorder[i], i); } return build(inorder, 0, inorder.length - 1, postorder, 0, postorder.length - 1, valToIndex); }; /** * 定义: * 中序遍历数组为 inorder[inStart..inEnd], * 后序遍历数组为 postorder[postStart..postEnd], * 构造这个二叉树并返回该二叉树的根节点 */ function build(inorder, inStart, inEnd, postorder, postStart, postEnd, valToIndex) { if (inStart > inEnd) { return null; } // root 节点对应的值就是后序遍历数组的最后一个元素 const rootVal = postorder[postEnd]; // rootVal 在中序遍历数组中的索引 const index = valToIndex.get(rootVal); // 左子树的节点个数 const leftSize = index - inStart; const root = new TreeNode(rootVal); /** ![](../pictures/二叉树系列2/6.jpeg) */ // 递归构造左右子树 root.left = build(inorder, inStart, index - 1, postorder, postStart, postStart + leftSize - 1, valToIndex); root.right = build(inorder, index + 1, inEnd, postorder, postStart + leftSize, postEnd - 1, valToIndex); return root; } ``` ```python # by chatGPT (python) class Solution: def __init__(self): # 存储 inorder 中值到索引的映射 self.valToIndex = {} def buildTree(self, inorder: List[int], postorder: List[int]) -> TreeNode: for i in range(len(inorder)): self.valToIndex[inorder[i]] = i return self.build(inorder, 0, len(inorder) - 1, postorder, 0, len(postorder) - 1) ''' 定义: 中序遍历数组为 inorder[inStart..inEnd], 后序遍历数组为 postorder[postStart..postEnd], 构造这个二叉树并返回该二叉树的根节点 ''' def build(self, inorder: List[int], inStart: int, inEnd: int, postorder: List[int], postStart: int, postEnd: int) -> TreeNode: if inStart > inEnd: return None # root 节点对应的值就是后序遍历数组的最后一个元素 rootVal = postorder[postEnd] # rootVal 在中序遍历数组中的索引 index = self.valToIndex[rootVal] # 左子树的节点个数 leftSize = index - inStart root = TreeNode(rootVal) ''' ![](../pictures/二叉树系列2/6.jpeg) ''' # 递归构造左右子树 root.left = self.build(inorder, inStart, index - 1, postorder, postStart, postStart + leftSize - 1) root.right = self.build(inorder, index + 1, inEnd, postorder, postStart + leftSize, postEnd - 1) return root ``` https://leetcode.cn/problems/construct-binary-tree-from-inorder-and-postorder-traversal 的多语言解法👆 https://leetcode.cn/problems/construct-binary-tree-from-preorder-and-inorder-traversal 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { private: // 存储 inorder 中值到索引的映射 unordered_map valToIndex; public: TreeNode* buildTree(vector& preorder, vector& inorder) { for (int i = 0; i < inorder.size(); i++) { valToIndex[inorder[i]] = i; } return build(preorder, 0, preorder.size() - 1, inorder, 0, inorder.size() - 1); } /* 定义:前序遍历数组为 preorder[preStart..preEnd], 中序遍历数组为 inorder[inStart..inEnd], 构造这个二叉树并返回该二叉树的根节点 */ TreeNode* build(vector& preorder, int preStart, int preEnd, vector& inorder, int inStart, int inEnd) { if (preStart > preEnd) { return nullptr; } // root 节点对应的值就是前序遍历数组的第一个元素 int rootVal = preorder[preStart]; // rootVal 在中序遍历数组中的索引 int index = valToIndex[rootVal]; int leftSize = index - inStart; // 先构造出当前根节点 TreeNode* root = new TreeNode(rootVal); // 递归构造左右子树 root->left = build(preorder, preStart + 1, preStart + leftSize, inorder, inStart, index - 1); root->right = build(preorder, preStart + leftSize + 1, preEnd, inorder, index + 1, inEnd); return root; } }; ``` ```go // by chatGPT (go) func buildTree(preorder []int, inorder []int) *TreeNode { // 存储 inorder 中值到索引的映射 valToIndex := make(map[int]int) for i, v := range inorder { valToIndex[v] = i } return build(preorder, 0, len(preorder)-1, inorder, 0, len(inorder)-1, valToIndex) } /* 定义:前序遍历数组为 preorder[preStart..preEnd], 中序遍历数组为 inorder[inStart..inEnd], 构造这个二叉树并返回该二叉树的根节点 */ func build(preorder []int, preStart int, preEnd int, inorder []int, inStart int, inEnd int, valToIndex map[int]int) *TreeNode { if preStart > preEnd { return nil } // root 节点对应的值就是前序遍历数组的第一个元素 rootVal := preorder[preStart] // rootVal 在中序遍历数组中的索引 index := valToIndex[rootVal] leftSize := index - inStart // 先构造出当前根节点 root := &TreeNode{Val: rootVal} // 递归构造左右子树 root.Left = build(preorder, preStart+1, preStart+leftSize, inorder, inStart, index-1, valToIndex) root.Right = build(preorder, preStart+leftSize+1, preEnd, inorder, index+1, inEnd, valToIndex) return root } ``` ```java // by labuladong (java) class Solution { // 存储 inorder 中值到索引的映射 HashMap valToIndex = new HashMap<>(); public TreeNode buildTree(int[] preorder, int[] inorder) { for (int i = 0; i < inorder.length; i++) { valToIndex.put(inorder[i], i); } return build(preorder, 0, preorder.length - 1, inorder, 0, inorder.length - 1); } /* 定义:前序遍历数组为 preorder[preStart..preEnd], 中序遍历数组为 inorder[inStart..inEnd], 构造这个二叉树并返回该二叉树的根节点 */ TreeNode build(int[] preorder, int preStart, int preEnd, int[] inorder, int inStart, int inEnd) { if (preStart > preEnd) { return null; } // root 节点对应的值就是前序遍历数组的第一个元素 int rootVal = preorder[preStart]; // rootVal 在中序遍历数组中的索引 int index = valToIndex.get(rootVal); int leftSize = index - inStart; // 先构造出当前根节点 TreeNode root = new TreeNode(rootVal); /** ![](../pictures/二叉树系列2/4.jpeg) */ // 递归构造左右子树 root.left = build(preorder, preStart + 1, preStart + leftSize, inorder, inStart, index - 1); root.right = build(preorder, preStart + leftSize + 1, preEnd, inorder, index + 1, inEnd); return root; } } ``` ```javascript // by chatGPT (javascript) var buildTree = function(preorder, inorder) { // 存储 inorder 中值到索引的映射 var valToIndex = new Map(); for (var i = 0; i < inorder.length; i++) { valToIndex.set(inorder[i], i); } return build(preorder, 0, preorder.length - 1, inorder, 0, inorder.length - 1); }; /* 定义:前序遍历数组为 preorder[preStart..preEnd], 中序遍历数组为 inorder[inStart..inEnd], 构造这个二叉树并返回该二叉树的根节点 */ var build = function(preorder, preStart, preEnd, inorder, inStart, inEnd) { if (preStart > preEnd) { return null; } // root 节点对应的值就是前序遍历数组的第一个元素 var rootVal = preorder[preStart]; // rootVal 在中序遍历数组中的索引 var index = valToIndex.get(rootVal); var leftSize = index - inStart; // 先构造出当前根节点 var root = new TreeNode(rootVal); // 递归构造左右子树 root.left = build(preorder, preStart + 1, preStart + leftSize, inorder, inStart, index - 1); root.right = build(preorder, preStart + leftSize + 1, preEnd, inorder, index + 1, inEnd); return root; }; ``` ```python # by chatGPT (python) class Solution: def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode: # 存储 inorder 中值到索引的映射 valToIndex = {} for i in range(len(inorder)): valToIndex[inorder[i]] = i return self.build(preorder, 0, len(preorder) - 1, inorder, 0, len(inorder) - 1, valToIndex) def build(self, preorder, preStart, preEnd, inorder, inStart, inEnd, valToIndex): if preStart > preEnd: return None # root 节点对应的值就是前序遍历数组的第一个元素 rootVal = preorder[preStart] # rootVal 在中序遍历数组中的索引 index = valToIndex[rootVal] leftSize = index - inStart # 先构造出当前根节点 root = TreeNode(rootVal) # 递归构造左右子树 root.left = self.build(preorder, preStart + 1, preStart + leftSize, inorder, inStart, index - 1, valToIndex) root.right = self.build(preorder, preStart + leftSize + 1, preEnd, inorder, index + 1, inEnd, valToIndex) return root ``` https://leetcode.cn/problems/construct-binary-tree-from-preorder-and-inorder-traversal 的多语言解法👆 https://leetcode.cn/problems/construct-binary-tree-from-preorder-and-postorder-traversal 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { // 存储 postorder 中值到索引的映射 unordered_map valToIndex; public: TreeNode* constructFromPrePost(vector& preorder, vector& postorder) { for (int i = 0; i < postorder.size(); i++) { valToIndex[postorder[i]] = i; } return build(preorder, 0, preorder.size() - 1, postorder, 0, postorder.size() - 1); } // 定义:根据 preorder[preStart..preEnd] 和 postorder[postStart..postEnd] // 构建二叉树,并返回根节点。 TreeNode* build(vector& preorder, int preStart, int preEnd, vector& postorder, int postStart, int postEnd) { if (preStart > preEnd) { return nullptr; } if (preStart == preEnd) { return new TreeNode(preorder[preStart]); } // root 节点对应的值就是前序遍历数组的第一个元素 int rootVal = preorder[preStart]; // root.left 的值是前序遍历第二个元素 // 通过前序和后序遍历构造二叉树的关键在于通过左子树的根节点 // 确定 preorder 和 postorder 中左右子树的元素区间 int leftRootVal = preorder[preStart + 1]; // leftRootVal 在后序遍历数组中的索引 int index = valToIndex[leftRootVal]; // 左子树的元素个数 int leftSize = index - postStart + 1; // 先构造出当前根节点 TreeNode* root = new TreeNode(rootVal); // 递归构造左右子树 // 根据左子树的根节点索引和元素个数推导左右子树的索引边界 root->left = build(preorder, preStart + 1, preStart + leftSize, postorder, postStart, index); root->right = build(preorder, preStart + leftSize + 1, preEnd, postorder, index + 1, postEnd - 1); return root; } }; ``` ```go // by chatGPT (go) // Definition for a binary tree node. // type TreeNode struct { // Val int // Left *TreeNode // Right *TreeNode // } func constructFromPrePost(preorder []int, postorder []int) *TreeNode { // 存储 postorder 中值到索引的映射 valToIndex := make(map[int]int) for i, v := range postorder { valToIndex[v] = i } return build(preorder, 0, len(preorder)-1, postorder, 0, len(postorder)-1, valToIndex) } // 根据 preorder[preStart..preEnd] 和 postorder[postStart..postEnd] 构建二叉树,并返回根节点。 func build(preorder []int, preStart int, preEnd int, postorder []int, postStart int, postEnd int, valToIndex map[int]int) *TreeNode { if preStart > preEnd { return nil } if preStart == preEnd { return &TreeNode{Val: preorder[preStart]} } // root 节点对应的值就是前序遍历数组的第一个元素 rootVal := preorder[preStart] // root.left 的值是前序遍历第二个元素 // 通过前序和后序遍历构造二叉树的关键在于通过左子树的根节点 // 确定 preorder 和 postorder 中左右子树的元素区间 leftRootVal := preorder[preStart+1] // leftRootVal 在后序遍历数组中的索引 index := valToIndex[leftRootVal] // 左子树的元素个数 leftSize := index - postStart + 1 // 先构造出当前根节点 root := &TreeNode{Val: rootVal} // 递归构造左右子树 // 根据左子树的根节点索引和元素个数推导左右子树的索引边界 root.Left = build(preorder, preStart+1, preStart+leftSize, postorder, postStart, index, valToIndex) root.Right = build(preorder, preStart+leftSize+1, preEnd, postorder, index+1, postEnd-1, valToIndex) return root } ``` ```java // by labuladong (java) class Solution { // 存储 postorder 中值到索引的映射 HashMap valToIndex = new HashMap<>(); public TreeNode constructFromPrePost(int[] preorder, int[] postorder) { for (int i = 0; i < postorder.length; i++) { valToIndex.put(postorder[i], i); } return build(preorder, 0, preorder.length - 1, postorder, 0, postorder.length - 1); } // 定义:根据 preorder[preStart..preEnd] 和 postorder[postStart..postEnd] // 构建二叉树,并返回根节点。 TreeNode build(int[] preorder, int preStart, int preEnd, int[] postorder, int postStart, int postEnd) { if (preStart > preEnd) { return null; } if (preStart == preEnd) { return new TreeNode(preorder[preStart]); } // root 节点对应的值就是前序遍历数组的第一个元素 int rootVal = preorder[preStart]; // root.left 的值是前序遍历第二个元素 // 通过前序和后序遍历构造二叉树的关键在于通过左子树的根节点 // 确定 preorder 和 postorder 中左右子树的元素区间 int leftRootVal = preorder[preStart + 1]; // leftRootVal 在后序遍历数组中的索引 int index = valToIndex.get(leftRootVal); // 左子树的元素个数 int leftSize = index - postStart + 1; // 先构造出当前根节点 TreeNode root = new TreeNode(rootVal); /** ![](../pictures/二叉树系列2/8.jpeg) */ // 递归构造左右子树 // 根据左子树的根节点索引和元素个数推导左右子树的索引边界 root.left = build(preorder, preStart + 1, preStart + leftSize, postorder, postStart, index); root.right = build(preorder, preStart + leftSize + 1, preEnd, postorder, index + 1, postEnd - 1); return root; } } ``` ```javascript // by chatGPT (javascript) /** * @param {number[]} preorder * @param {number[]} postorder * @return {TreeNode} */ var constructFromPrePost = function(preorder, postorder) { // 存储 postorder 中值到索引的映射 const valToIndex = new Map(); for (let i = 0; i < postorder.length; i++) { valToIndex.set(postorder[i], i); } return build(preorder, 0, preorder.length - 1, postorder, 0, postorder.length - 1, valToIndex); }; /** * @param {number[]} preorder * @param {number} preStart * @param {number} preEnd * @param {number[]} postorder * @param {number} postStart * @param {number} postEnd * @param {Map} valToIndex * @return {TreeNode} */ function build(preorder, preStart, preEnd, postorder, postStart, postEnd, valToIndex) { if (preStart > preEnd) { return null; } if (preStart === preEnd) { return new TreeNode(preorder[preStart]); } // root 节点对应的值就是前序遍历数组的第一个元素 const rootVal = preorder[preStart]; // root.left 的值是前序遍历第二个元素 // 通过前序和后序遍历构造二叉树的关键在于通过左子树的根节点 // 确定 preorder 和 postorder 中左右子树的元素区间 const leftRootVal = preorder[preStart + 1]; // leftRootVal 在后序遍历数组中的索引 const index = valToIndex.get(leftRootVal); // 左子树的元素个数 const leftSize = index - postStart + 1; // 先构造出当前根节点 const root = new TreeNode(rootVal); // 递归构造左右子树 // 根据左子树的根节点索引和元素个数推导左右子树的索引边界 root.left = build(preorder, preStart + 1, preStart + leftSize, postorder, postStart, index, valToIndex); root.right = build(preorder, preStart + leftSize + 1, preEnd, postorder, index + 1, postEnd - 1, valToIndex); return root; } ``` ```python # by chatGPT (python) class Solution: # 存储 postorder 中值到索引的映射 valToIndex = {} def constructFromPrePost(self, preorder: List[int], postorder: List[int]) -> TreeNode: for i in range(len(postorder)): self.valToIndex[postorder[i]] = i return self.build(preorder, 0, len(preorder) - 1, postorder, 0, len(postorder) - 1) # 定义:根据 preorder[preStart..preEnd] 和 postorder[postStart..postEnd] # 构建二叉树,并返回根节点。 def build(self, preorder, preStart, preEnd, postorder, postStart, postEnd): if preStart > preEnd: return None if preStart == preEnd: return TreeNode(preorder[preStart]) # root 节点对应的值就是前序遍历数组的第一个元素 rootVal = preorder[preStart] # root.left 的值是前序遍历第二个元素 # 通过前序和后序遍历构造二叉树的关键在于通过左子树的根节点 # 确定 preorder 和 postorder 中左右子树的元素区间 leftRootVal = preorder[preStart + 1] # leftRootVal 在后序遍历数组中的索引 index = self.valToIndex[leftRootVal] # 左子树的元素个数 leftSize = index - postStart + 1 # 先构造出当前根节点 root = TreeNode(rootVal) # 递归构造左右子树 # 根据左子树的根节点索引和元素个数推导左右子树的索引边界 root.left = self.build(preorder, preStart + 1, preStart + leftSize, postorder, postStart, index) root.right = self.build(preorder, preStart + leftSize + 1, preEnd, postorder, index + 1, postEnd - 1) return root ``` https://leetcode.cn/problems/construct-binary-tree-from-preorder-and-postorder-traversal 的多语言解法👆 https://leetcode.cn/problems/container-with-most-water 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int maxArea(vector& height) { int left = 0, right = height.size() - 1; int res = 0; while (left < right) { // [left, right] 之间的矩形面积 int cur_area = min(height[left], height[right]) * (right - left); res = max(res, cur_area); // 双指针技巧,移动较低的一边 if (height[left] < height[right]) { left++; } else { right--; } } return res; } }; ``` ```go // by chatGPT (go) func maxArea(height []int) int { left, right := 0, len(height)-1 res := 0 for left < right { // [left, right] 之间的矩形面积 curArea := func() int { if height[left] < height[right] { return height[left] * (right - left) } return height[right] * (right - left) }() res = func() int { if curArea > res { return curArea } return res }() // 双指针技巧,移动较低的一边 if height[left] < height[right] { left++ } else { right-- } } return res } ``` ```java // by labuladong (java) class Solution { public int maxArea(int[] height) { int left = 0, right = height.length - 1; int res = 0; while (left < right) { // [left, right] 之间的矩形面积 int cur_area = Math.min(height[left], height[right]) * (right - left); res = Math.max(res, cur_area); // 双指针技巧,移动较低的一边 if (height[left] < height[right]) { left++; } else { right--; } } return res; } } ``` ```javascript // by chatGPT (javascript) /** * @param {number[]} height * @return {number} */ var maxArea = function(height) { let left = 0, right = height.length - 1; let res = 0; while (left < right) { // [left, right] 之间的矩形面积 const cur_area = Math.min(height[left], height[right]) * (right - left); res = Math.max(res, cur_area); // 双指针技巧,移动较低的一边 if (height[left] < height[right]) { left++; } else { right--; } } return res; }; ``` ```python # by chatGPT (python) class Solution: def maxArea(self, height: List[int]) -> int: left, right = 0, len(height)-1 res = 0 while left < right: # [left, right] 之间的矩形面积 cur_area = min(height[left], height[right]) * (right - left) res = max(res, cur_area) # 双指针技巧,移动较低的一边 if height[left] < height[right]: left += 1 else: right -= 1 return res ``` https://leetcode.cn/problems/container-with-most-water 的多语言解法👆 https://leetcode.cn/problems/convert-bst-to-greater-tree 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: TreeNode* convertBST(TreeNode* root) { traverse(root); return root; } // 记录累加和 int sum = 0; void traverse(TreeNode* root) { if (root == nullptr) { return; } traverse(root->right); // 维护累加和 sum += root->val; // 将 BST 转化成累加树 root->val = sum; traverse(root->left); } }; ``` ```go // by chatGPT (go) func convertBST(root *TreeNode) *TreeNode { // 记录累加和 var sum int var traverse func(node *TreeNode) traverse = func(node *TreeNode) { if node == nil { return } traverse(node.Right) // 维护累加和 sum += node.Val // 将 BST 转化成累加树 node.Val = sum traverse(node.Left) } traverse(root) return root } ``` ```java // by labuladong (java) class Solution { public TreeNode convertBST(TreeNode root) { traverse(root); return root; } // 记录累加和 int sum = 0; void traverse(TreeNode root) { if (root == null) { return; } traverse(root.right); // 维护累加和 sum += root.val; // 将 BST 转化成累加树 root.val = sum; traverse(root.left); } } ``` ```javascript // by chatGPT (javascript) /** * @param {TreeNode} root * @return {TreeNode} */ var convertBST = function(root) { // 记录累加和 let sum = 0; // 中序遍历节点 const traverse = function(node) { if (!node) { return; } traverse(node.right); // 先遍历右子树 sum += node.val; // 维护累加和 node.val = sum; // 将 BST 节点的值更新为累加和 traverse(node.left); // 遍历左子树 } traverse(root); return root; }; ``` ```python # by chatGPT (python) class Solution: def convertBST(self, root: TreeNode) -> TreeNode: self.sum = 0 self.traverse(root) return root def traverse(self, root: TreeNode) -> None: if not root: return self.traverse(root.right) # 维护累加和 self.sum += root.val # 将 BST 转化成累加树 root.val = self.sum self.traverse(root.left) ``` https://leetcode.cn/problems/convert-bst-to-greater-tree 的多语言解法👆 https://leetcode.cn/problems/copy-list-with-random-pointer 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: Node* copyRandomList(Node* head) { unordered_map originToClone; // 第一次遍历,先把所有节点克隆出来 for (Node* p = head; p != nullptr; p = p->next) { if (originToClone.count(p) == 0) { originToClone[p] = new Node(p->val); } } // 第二次遍历,把克隆节点的结构连接好 for (Node* p = head; p != nullptr; p = p->next) { if (p->next != nullptr) { originToClone[p]->next = originToClone[p->next]; } if (p->random != nullptr) { originToClone[p]->random = originToClone[p->random]; } } // 返回克隆之后的头结点 return originToClone[head]; } }; // 用递归的方式进行遍历 class Solution2 { public: Node* copyRandomList(Node* head) { traverse(head); return originToClone[head]; } private: // 记录 DFS 遍历过的节点,防止走回头路 unordered_set visited; // 记录原节点到克隆节点的映射 unordered_map originToClone; // DFS 图遍历框架 void traverse(Node* node) { if (node == nullptr) { return; } if (visited.count(node) > 0) { return; } // 前序位置,标记为已访问 visited.insert(node); // 前序位置,克隆节点 if (originToClone.count(node) == 0) { originToClone[node] = new Node(node->val); } Node* cloneNode = originToClone[node]; // 递归遍历邻居节点,并构建克隆图 // 递归之后,邻居节点一定存在 originToClone 中 traverse(node->next); cloneNode->next = originToClone[node->next]; traverse(node->random); cloneNode->random = originToClone[node->random]; } }; ``` ```go // by chatGPT (go) func copyRandomList(head *Node) *Node { originToClone := make(map[*Node]*Node) // 第一次遍历,先把所有节点克隆出来 for p := head; p != nil; p = p.Next { if _, ok := originToClone[p]; !ok { originToClone[p] = &Node{ Val: p.Val, } } } // 第二次遍历,把克隆节点的结构连接好 for p := head; p != nil; p = p.Next { if p.Next != nil { originToClone[p].Next = originToClone[p.Next] } if p.Random != nil { originToClone[p].Random = originToClone[p.Random] } } // 返回克隆之后的头结点 return originToClone[head] } // 用递归的方式进行遍历 func copyRandomList2(head *Node) *Node { visited := make(map[*Node]bool) originToClone := make(map[*Node]*Node) traverse(head, visited, originToClone) return originToClone[head] } // DFS 图遍历框架 func traverse(node *Node, visited map[*Node]bool, originToClone map[*Node]*Node) { if node == nil { return } if visited[node] { return } // 前序位置,标记为已访问 visited[node] = true // 前序位置,克隆节点 if _, ok := originToClone[node]; !ok { originToClone[node] = &Node{ Val: node.Val, } } cloneNode := originToClone[node] // 递归遍历邻居节点,并构建克隆图 // 递归之后,邻居节点一定存在 originToClone 中 traverse(node.Next, visited, originToClone) cloneNode.Next = originToClone[node.Next] traverse(node.Random, visited, originToClone) cloneNode.Random = originToClone[node.Random] } ``` ```java // by labuladong (java) class Solution { public Node copyRandomList(Node head) { HashMap originToClone = new HashMap<>(); // 第一次遍历,先把所有节点克隆出来 for (Node p = head; p != null; p = p.next) { if (!originToClone.containsKey(p)) { originToClone.put(p, new Node(p.val)); } } // 第二次遍历,把克隆节点的结构连接好 for (Node p = head; p != null; p = p.next) { if (p.next != null) { originToClone.get(p).next = originToClone.get(p.next); } if (p.random != null) { originToClone.get(p).random = originToClone.get(p.random); } } // 返回克隆之后的头结点 return originToClone.get(head); } } // 用递归的方式进行遍历 class Solution2 { public Node copyRandomList(Node head) { traverse(head); return originToClone.get(head); } // 记录 DFS 遍历过的节点,防止走回头路 HashSet visited = new HashSet<>(); // 记录原节点到克隆节点的映射 HashMap originToClone = new HashMap<>(); // DFS 图遍历框架 void traverse(Node node) { if (node == null) { return; } if (visited.contains(node)) { return; } // 前序位置,标记为已访问 visited.add(node); // 前序位置,克隆节点 if (!originToClone.containsKey(node)) { originToClone.put(node, new Node(node.val)); } Node cloneNode = originToClone.get(node); // 递归遍历邻居节点,并构建克隆图 // 递归之后,邻居节点一定存在 originToClone 中 traverse(node.next); cloneNode.next = originToClone.get(node.next); traverse(node.random); cloneNode.random = originToClone.get(node.random); } } ``` ```javascript // by chatGPT (javascript) var copyRandomList = function(head) { const originToClone = new Map(); // 第一次遍历,先把所有节点克隆出来 for (let p = head; p !== null; p = p.next) { if (!originToClone.has(p)) { originToClone.set(p, new Node(p.val)); } } // 第二次遍历,把克隆节点的结构连接好 for (let p = head; p !== null; p = p.next) { if (p.next !== null) { originToClone.get(p).next = originToClone.get(p.next); } if (p.random !== null) { originToClone.get(p).random = originToClone.get(p.random); } } // 返回克隆之后的头结点 return originToClone.get(head); }; // 用递归的方式进行遍历 var copyRandomList2 = function(head) { const visited = new Set(); const originToClone = new Map(); const traverse = (node) => { if (node === null) { return; } if (visited.has(node)) { return; } // 前序位置,标记为已访问 visited.add(node); // 前序位置,克隆节点 if (!originToClone.has(node)) { originToClone.set(node, new Node(node.val)); } const cloneNode = originToClone.get(node); // 递归遍历邻居节点,并构建克隆图 // 递归之后,邻居节点一定存在 originToClone 中 traverse(node.next); cloneNode.next = originToClone.get(node.next); traverse(node.random); cloneNode.random = originToClone.get(node.random); }; traverse(head); return originToClone.get(head); }; ``` ```python # by chatGPT (python) class Solution: def copyRandomList(self, head: 'Node') -> 'Node': originToClone = {} # 第一次遍历,先把所有节点克隆出来 p = head while p: if p not in originToClone: originToClone[p] = Node(p.val) p = p.next # 第二次遍历,把克隆节点的结构连接好 p = head while p: if p.next: originToClone[p].next = originToClone[p.next] if p.random: originToClone[p].random = originToClone[p.random] p = p.next # 返回克隆之后的头结点 return originToClone.get(head) # 用递归的方式进行遍历 class Solution2: def copyRandomList(self, head: 'Node') -> 'Node': self.visited = set() self.originToClone = {} self.traverse(head) return self.originToClone.get(head) # DFS 图遍历框架 def traverse(self, node): if not node: return if node in self.visited: return # 前序位置,标记为已访问 self.visited.add(node) # 前序位置,克隆节点 if node not in self.originToClone: self.originToClone[node] = Node(node.val) cloneNode = self.originToClone[node] # 递归遍历邻居节点,并构建克隆图 # 递归之后,邻居节点一定存在 originToClone 中 self.traverse(node.next) cloneNode.next = self.originToClone.get(node.next) self.traverse(node.random) cloneNode.random = self.originToClone.get(node.random) ``` https://leetcode.cn/problems/copy-list-with-random-pointer 的多语言解法👆 https://leetcode.cn/problems/corporate-flight-bookings 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: vector corpFlightBookings(vector>& bookings, int n) { // nums 初始化为全 0 vector nums(n); // 构造差分解法 Difference df(nums); for (auto& booking : bookings) { // 注意转成数组索引要减一哦 int i = booking[0] - 1; int j = booking[1] - 1; int val = booking[2]; // 对区间 nums[i..j] 增加 val df.increment(i, j, val); } // 返回最终的结果数组 return df.result(); } private: class Difference { // 差分数组 vector diff; public: Difference(vector& nums) { assert(nums.size() > 0); diff = vector(nums.size()); // 构造差分数组 diff[0] = nums[0]; for (int i = 1; i < nums.size(); i++) { diff[i] = nums[i] - nums[i - 1]; } } /* 给闭区间 [i, j] 增加 val(可以是负数)*/ void increment(int i, int j, int val) { diff[i] += val; if (j + 1 < diff.size()) { diff[j + 1] -= val; } } vector result() { vector res(diff.size()); // 根据差分数组构造结果数组 res[0] = diff[0]; for (int i = 1; i < diff.size(); i++) { res[i] = res[i - 1] + diff[i]; } return res; } }; }; ``` ```go // by chatGPT (go) func corpFlightBookings(bookings [][]int, n int) []int { // nums 初始化为全 0 nums := make([]int, n) // 构造差分解法 df := Difference{nums} for _, booking := range bookings { // 注意转成数组索引要减一哦 i := booking[0] - 1 j := booking[1] - 1 val := booking[2] // 对区间 nums[i..j] 增加 val df.increment(i, j, val) } // 返回最终的结果数组 return df.result() } type Difference struct { // 差分数组 diff []int } func (df *Difference) increment(i, j, val int) { df.diff[i] += val if j+1 < len(df.diff) { df.diff[j+1] -= val } } func (df *Difference) result() []int { res := make([]int, len(df.diff)) // 根据差分数组构造结果数组 res[0] = df.diff[0] for i := 1; i < len(df.diff); i++ { res[i] = res[i-1] + df.diff[i] } return res } ``` ```java // by labuladong (java) class Solution { public int[] corpFlightBookings(int[][] bookings, int n) { // nums 初始化为全 0 int[] nums = new int[n]; // 构造差分解法 Difference df = new Difference(nums); for (int[] booking : bookings) { // 注意转成数组索引要减一哦 int i = booking[0] - 1; int j = booking[1] - 1; int val = booking[2]; // 对区间 nums[i..j] 增加 val df.increment(i, j, val); } // 返回最终的结果数组 return df.result(); } 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; } } } ``` ```javascript // by chatGPT (javascript) var corpFlightBookings = function(bookings, n) { // nums 初始化为全 0 let nums = new Array(n).fill(0); // 构造差分解法 let df = new Difference(nums); for (let booking of bookings) { // 注意转成数组索引要减一哦 let i = booking[0] - 1; let j = booking[1] - 1; let val = booking[2]; // 对区间 nums[i..j] 增加 val df.increment(i, j, val); } // 返回最终的结果数组 return df.result(); } class Difference { // 差分数组 constructor(nums) { if (nums.length <= 0) { throw new Error('nums array should not be empty'); } this.diff = new Array(nums.length).fill(0); // 构造差分数组 this.diff[0] = nums[0]; for (let i = 1; i < nums.length; i++) { this.diff[i] = nums[i] - nums[i - 1]; } } /* 给闭区间 [i, j] 增加 val(可以是负数)*/ increment(i, j, val) { this.diff[i] += val; if (j + 1 < this.diff.length) { this.diff[j + 1] -= val; } } result() { let res = new Array(this.diff.length).fill(0); // 根据差分数组构造结果数组 res[0] = this.diff[0]; for (let i = 1; i < this.diff.length; i++) { res[i] = res[i - 1] + this.diff[i]; } return res; } } ``` ```python # by chatGPT (python) class Solution: def corpFlightBookings(self, bookings: List[List[int]], n: int) -> List[int]: # nums 初始化为全 0 nums = [0] * n # 构造差分解法 df = Difference(nums) for booking in bookings: # 注意转成数组索引要减一哦 i, j, val = booking[0] - 1, booking[1] - 1, booking[2] # 对区间 nums[i..j] 增加 val df.increment(i, j, val) # 返回最终的结果数组 return df.result() class Difference: # 差分数组 def __init__(self, nums: List[int]): assert len(nums) > 0 self.diff = [0] * len(nums) # 构造差分数组 self.diff[0] = nums[0] for i in range(1, len(nums)): self.diff[i] = nums[i] - nums[i - 1] # 给闭区间 [i, j] 增加 val(可以是负数) def increment(self, i: int, j: int, val: int) -> None: self.diff[i] += val if j + 1 < len(self.diff): self.diff[j + 1] -= val def result(self) -> List[int]: res = [0] * len(self.diff) # 根据差分数组构造结果数组 res[0] = self.diff[0] for i in range(1, len(self.diff)): res[i] = res[i - 1] + self.diff[i] return res ``` https://leetcode.cn/problems/corporate-flight-bookings 的多语言解法👆 https://leetcode.cn/problems/count-complete-tree-nodes 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int countNodes(TreeNode* root) { TreeNode* l = root; TreeNode* r = root; // 记录左、右子树的高度 int hl = 0, hr = 0; while (l != nullptr) { l = l->left; hl++; } while (r != nullptr) { r = r->right; hr++; } // 如果左右子树的高度相同,则是一棵满二叉树 if (hl == hr) { return pow(2, hl) - 1; } // 如果左右高度不同,则按照普通二叉树的逻辑计算 return 1 + countNodes(root->left) + countNodes(root->right); } }; ``` ```go // by chatGPT (go) func countNodes(root *TreeNode) int { // 记录左、右子树的高度 hl, hr := 0, 0 l, r := root, root for l != nil { l = l.Left hl++ } for r != nil { r = r.Right hr++ } // 如果左右子树的高度相同,则是一棵满二叉树 if hl == hr { return int(math.Pow(2, float64(hl))) - 1 } // 如果左右高度不同,则按照普通二叉树的逻辑计算 return 1 + countNodes(root.Left) + countNodes(root.Right) } ``` ```java // by labuladong (java) class Solution { public int countNodes(TreeNode root) { TreeNode l = root, r = root; // 记录左、右子树的高度 int hl = 0, hr = 0; while (l != null) { l = l.left; hl++; } while (r != null) { r = r.right; hr++; } // 如果左右子树的高度相同,则是一棵满二叉树 if (hl == hr) { return (int) Math.pow(2, hl) - 1; } // 如果左右高度不同,则按照普通二叉树的逻辑计算 return 1 + countNodes(root.left) + countNodes(root.right); } } ``` ```javascript // by chatGPT (javascript) var countNodes = function(root) { let l = root, r = root; // 记录左、右子树的高度 let hl = 0, hr = 0; while (l !== null) { l = l.left; hl++; } while (r !== null) { r = r.right; hr++; } // 如果左右子树的高度相同,则是一棵满二叉树 if (hl === hr) { return Math.pow(2, hl) - 1; } // 如果左右高度不同,则按照普通二叉树的逻辑计算 return 1 + countNodes(root.left) + countNodes(root.right); }; ``` ```python # by chatGPT (python) class Solution: def countNodes(self, root: Optional[TreeNode]) -> int: l, r = root, root hl, hr = 0, 0 # 记录左、右子树的高度 while l: l = l.left hl += 1 while r: r = r.right hr += 1 # 如果左右子树的高度相同,则是一棵满二叉树 if hl == hr: return 2 ** hl - 1 # 如果左右高度不同,则按照普通二叉树的逻辑计算 return 1 + self.countNodes(root.left) + self.countNodes(root.right) ``` https://leetcode.cn/problems/count-complete-tree-nodes 的多语言解法👆 https://leetcode.cn/problems/count-numbers-with-unique-digits 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int countNumbersWithUniqueDigits(int n) { if (n == 0) return 1; if (n == 1) return 10; int res = 9; // 计算 n 位数的阶乘,共有几种不同数字的排列组合 for (int i = 1; i < n; i++) { res *= (10 - i); } // 最后要加上 n - 1 位数的排列组合 return res + countNumbersWithUniqueDigits(n - 1); } }; ``` ```go // by chatGPT (go) // Solution function func countNumbersWithUniqueDigits(n int) int { if n == 0 { return 1 } if n == 1 { return 10 } res := 9 // 计算 n 位数的阶乘,共有几种不同数字的排列组合 for i := 1; i < n; i++ { res *= (10 - i) } // 最后要加上 n - 1 位数的排列组合 return res + countNumbersWithUniqueDigits(n - 1) } ``` ```java // by labuladong (java) class Solution { public int countNumbersWithUniqueDigits(int n) { if (n == 0) return 1; if (n == 1) return 10; int res = 9; // 计算 n 位数的阶乘,共有几种不同数字的排列组合 for (int i = 1; i < n; i++) { res *= (10 - i); } // 最后要加上 n - 1 位数的排列组合 return res + countNumbersWithUniqueDigits(n - 1); } } ``` ```javascript // by chatGPT (javascript) var countNumbersWithUniqueDigits = function(n) { if (n == 0) return 1; if (n == 1) return 10; var res = 9; // 计算 n 位数的阶乘,共有几种不同数字的排列组合 for (var i = 1; i < n; i++) { res *= (10 - i); } // 最后要加上 n - 1 位数的排列组合 return res + countNumbersWithUniqueDigits(n - 1); }; ``` ```python # by chatGPT (python) class Solution: def countNumbersWithUniqueDigits(self, n: int) -> int: if n == 0: return 1 if n == 1: return 10 res = 9 # 计算 n 位数的阶乘,共有几种不同数字的排列组合 for i in range(1, n): res *= (10 - i) # 最后要加上 n - 1 位数的排列组合 return res + self.countNumbersWithUniqueDigits(n - 1) ``` https://leetcode.cn/problems/count-numbers-with-unique-digits 的多语言解法👆 https://leetcode.cn/problems/count-of-range-sum 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { private: int lower, upper; int count = 0; vector temp; public: int countRangeSum(vector& nums, int lower, int upper) { this->lower = lower; this->upper = upper; vector preSum(nums.size() + 1); for (int i = 0; i < nums.size(); i++) { preSum[i + 1] = nums[i] + preSum[i]; } sort(preSum, 0, preSum.size()- 1); return count; } // 归并排序 void sort(vector& nums, int lo, int hi) { if (lo == hi) { // 单个元素不用排序 return; } int mid = lo + (hi - lo) / 2; // 先对左半部分数组 nums[lo..mid] 排序 sort(nums, lo, mid); // 再对右半部分数组 nums[mid+1..hi] 排序 sort(nums, mid + 1, hi); // 将两部分有序数组合并成一个有序数组 merge(nums, lo, mid, hi); } // 归并操作 void merge(vector& nums, int lo, int mid, int hi) { temp.assign(mid - lo + 1 + hi - mid, 0); int i = lo, j = mid + 1, k = 0, start = mid + 1, end = mid + 1; while (i <= mid) { while (start <= hi && nums[start] - nums[i] < lower) { start++; } while (end <= hi && nums[end] - nums[i] <= upper) { end++; } count += end - start; while (j <= hi && nums[j] < nums[i]) { temp[k++] = nums[j++]; } temp[k++] = nums[i++]; } while (j <= hi) { temp[k++] = nums[j++]; } for (int p = 0; p < k; p++) { nums[lo + p] = temp[p]; } } }; ``` ```go // by chatGPT (go) // 解法:归并排序 // 时间复杂度:O(nlogn) // 空间复杂度:O(n) func countRangeSum(nums []int, lower int, upper int) int { // 前缀和数组 presum := make([]int64, len(nums)+1) for i := 0; i < len(nums); i++ { presum[i+1] = int64(nums[i]) + presum[i] } // 归并排序 temp := make([]int64, len(nums)+1) return mergeSort(presum, temp, 0, len(presum)-1, lower, upper) } // 将 nums[lo..hi] 分别排序,然后合并成一个有序数组 func mergeSort(nums, temp []int64, lo, hi, lower, upper int) int { if lo == hi { return 0 } mid := lo + (hi-lo)/2 count := mergeSort(nums, temp, lo, mid, lower, upper) + mergeSort(nums, temp, mid+1, hi, lower, upper) // 统计左闭右开区间 [start, end) 中的元素落在 [lower, upper] 中 start, end := mid+1, mid+1 for i := lo; i <= mid; i++ { for start <= hi && nums[start]-nums[i] < int64(lower) { start++ } for end <= hi && nums[end]-nums[i] <= int64(upper) { end++ } count += end - start } // 合并有序数组 copy(temp[lo:hi+1], nums[lo:hi+1]) i, j := lo, mid+1 for k := lo; k <= hi; k++ { if i == mid+1 { nums[k] = temp[j] j++ } else if j == hi+1 || temp[i] <= temp[j] { nums[k] = temp[i] i++ } else { nums[k] = temp[j] j++ } } return count } ``` ```java // by labuladong (java) class Solution { int lower, upper; public int countRangeSum(int[] nums, int lower, int upper) { this.lower = lower; this.upper = upper; long[] preSum = new long[nums.length + 1]; for (int i = 0; i < nums.length; i++) { preSum[i + 1] = (long) nums[i] + preSum[i]; } sort(preSum); return count; } // 用于辅助合并有序数组 private long[] temp; private int count = 0; public void sort(long[] nums) { // 先给辅助数组开辟内存空间 temp = new long[nums.length]; // 排序整个数组(原地修改) sort(nums, 0, nums.length - 1); } // 定义:将子数组 nums[lo..hi] 进行排序 private void sort(long[] nums, int lo, int hi) { if (lo == hi) { // 单个元素不用排序 return; } // 这样写是为了防止溢出,效果等同于 (hi + lo) / 2 int mid = lo + (hi - lo) / 2; // 先对左半部分数组 nums[lo..mid] 排序 sort(nums, lo, mid); // 再对右半部分数组 nums[mid+1..hi] 排序 sort(nums, mid + 1, hi); // 将两部分有序数组合并成一个有序数组 merge(nums, lo, mid, hi); } // 将 nums[lo..mid] 和 nums[mid+1..hi] 这两个有序数组合并成一个有序数组 private void merge(long[] nums, int lo, int mid, int hi) { // 先把 nums[lo..hi] 复制到辅助数组中 // 以便合并后的结果能够直接存入 nums for (int i = lo; i <= hi; i++) { temp[i] = nums[i]; } // 这段代码会超时 // for (int i = lo; i <= mid; i++) { // // 在区间 [mid + 1, hi] 中寻找 lower <= delta <= upper 的元素 // for (int k = mid + 1; k <= hi; k++) { // long delta = nums[k] - nums[i]; // if (delta <= upper && delta >= lower) { // count++; // } // } // } // 进行效率优化 // 维护左闭右开区间 [start, end) 中的元素落在 [lower, upper] 中 int start = mid + 1, end = mid + 1; for (int i = lo; i <= mid; i++) { while (start <= hi && nums[start] - nums[i] < lower) { start++; } while (end <= hi && nums[end] - nums[i] <= upper) { end++; } count += end - start; } // 数组双指针技巧,合并两个有序数组 int i = lo, j = mid + 1; for (int p = lo; p <= hi; p++) { if (i == mid + 1) { // 左半边数组已全部被合并 nums[p] = temp[j++]; } else if (j == hi + 1) { // 右半边数组已全部被合并 nums[p] = temp[i++]; } else if (temp[i] > temp[j]) { nums[p] = temp[j++]; } else { nums[p] = temp[i++]; } } } } ``` ```javascript // by chatGPT (javascript) var countRangeSum = function(nums, lower, upper) { let count = 0; const n = nums.length; const preSum = new Array(n + 1).fill(0); for (let i = 0; i < n; i++) { preSum[i + 1] = nums[i] + preSum[i]; } mergeSort(preSum, 0, n); return count; function mergeSort(nums, left, right) { if (left >= right) { return; } const mid = left + Math.floor((right - left) / 2); mergeSort(nums, left, mid); mergeSort(nums, mid + 1, right); let i = left; let j = mid + 1; let k = 0; let t = 0; const temp = new Array(right - left + 1); while (i <= mid && j <= right) { if (nums[i] <= nums[j]) { // 统计区间和 count while (t <= right && nums[t] - nums[i] < lower) { t++; } while (k <= right && nums[k] - nums[i] <= upper) { k++; } count += k - t; temp[i - left] = nums[i++]; } else { temp[i - left] = nums[j++]; } } while (i <= mid) { temp[i - left] = nums[i++]; } while (j <= right) { temp[i - left] = nums[j++]; } for (let p = left; p <= right; p++) { nums[p] = temp[p - left]; } } }; ``` ```python # by chatGPT (python) class Solution: def __init__(self): self.lower, self.upper = 0, 0 def countRangeSum(self, nums: List[int], lower: int, upper: int) -> int: # 定义全局变量 self.lower, self.upper = lower, upper # 前缀和数组 preSum = [0] * (len(nums) + 1) for i in range(len(nums)): preSum[i + 1] = nums[i] + preSum[i] # 排序 self.sort(preSum) return self.count def sort(self, nums): # 定义辅助合并数组 self.temp = [0] * len(nums) # 定义计数器 self.count = 0 # 调用递归排序方法 self._sort(nums, 0, len(nums) - 1) def _sort(self, nums, lo, hi): # 排序子数组nums[lo..hi] if lo == hi: return mid = lo + (hi - lo) // 2 self._sort(nums, lo, mid) self._sort(nums, mid + 1, hi) self._merge(nums, lo, mid, hi) def _merge(self, nums, lo, mid, hi): # 合并两个有序子数组 nums[lo..mid] 和 nums[mid+1..hi] # 先将 nums[lo..hi] 复制到临时数组 temp 中 for i in range(lo, hi + 1): self.temp[i] = nums[i] # 左右两部分计数器 start 和 end start, end = mid + 1, mid + 1 for i in range(lo, mid + 1): while start <= hi and nums[start] - nums[i] < self.lower: start += 1 while end <= hi and nums[end] - nums[i] <= self.upper: end += 1 # 更新计数器 self.count += end - start # 数组合并,双指针技巧 i, j = lo, mid + 1 for p in range(lo, hi + 1): if i > mid: nums[p] = self.temp[j] j += 1 elif j > hi: nums[p] = self.temp[i] i += 1 elif self.temp[i] < self.temp[j]: nums[p] = self.temp[i] i += 1 else: nums[p] = self.temp[j] j += 1 ``` https://leetcode.cn/problems/count-of-range-sum 的多语言解法👆 https://leetcode.cn/problems/count-primes 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int countPrimes(int n) { vector isPrime(n, true); // vector instead of array for (int i = 2; i * i < n; i++) if (isPrime[i]) for (int j = i * i; j < n; j += i) isPrime[j] = false; int count = 0; for (int i = 2; i < n; i++) if (isPrime[i]) count++; return count; } }; ``` ```go // by chatGPT (go) func countPrimes(n int) int { // create boolean array with default value true isPrime := make([]bool, n) for i := range isPrime { isPrime[i] = true } // loop through all numbers up to the square root of n for i := 2; i*i < n; i++ { if isPrime[i] { // loop through multiples of i and mark them as not prime for j := i * i; j < n; j += i { isPrime[j] = false } } } // count number of primes count := 0 for i := 2; i < n; i++ { if isPrime[i] { count++ } } return count } ``` ```java // by labuladong (java) class Solution { public int countPrimes(int n) { boolean[] isPrime = new boolean[n]; Arrays.fill(isPrime, true); for (int i = 2; i * i < n; i++) if (isPrime[i]) for (int j = i * i; j < n; j += i) isPrime[j] = false; int count = 0; for (int i = 2; i < n; i++) if (isPrime[i]) count++; return count; } } ``` ```javascript // by chatGPT (javascript) /** * @param {number} n * @return {number} */ var countPrimes = function(n) { const isPrime = new Array(n).fill(true); for (let i = 2; i * i < n; i++) { if (isPrime[i]) { for (let j = i * i; j < n; j += i) { isPrime[j] = false; } } } let count = 0; for (let i = 2; i < n; i++) { if (isPrime[i]) count++; } return count; }; ``` ```python # by chatGPT (python) class Solution: def countPrimes(self, n: int) -> int: isPrime = [True]*n for i in range(2, int(n**0.5)+1): if isPrime[i]: for j in range(i*i, n, i): isPrime[j] = False count = 0 for i in range(2, n): if isPrime[i]: count += 1 return count ``` https://leetcode.cn/problems/count-primes 的多语言解法👆 https://leetcode.cn/problems/count-sub-islands 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int countSubIslands(vector>& grid1, vector>& grid2) { int m = grid1.size(), n = grid1[0].size(); 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); } } } // 现在 grid2 中剩下的岛屿都是子岛,计算岛屿数量 int res = 0; 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; } // 从 (i, j) 开始,将与之相邻的陆地都变成海水 void dfs(vector>& grid, int i, int j) { int m = grid.size(), n = grid[0].size(); if (i < 0 || j < 0 || i >= m || j >= n) { return; } if (grid[i][j] == 0) { return; } grid[i][j] = 0; dfs(grid, i + 1, j); dfs(grid, i, j + 1); dfs(grid, i - 1, j); dfs(grid, i, j - 1); } }; ``` ```go // by chatGPT (go) /** * Definition for a binary tree node. * type TreeNode struct { * Val int * Left *TreeNode * Right *TreeNode * } */ func countSubIslands(grid1 [][]int, grid2 [][]int) int { m, n := len(grid1), len(grid1[0]) for i := 0; i < m; i++ { for j := 0; j < n; j++ { if grid1[i][j] == 0 && grid2[i][j] == 1 { // 这个岛屿肯定不是子岛,淹掉 dfs(grid2, i, j) } } } // 现在 grid2 中剩下的岛屿都是子岛,计算岛屿数量 res := 0 for i := 0; i < m; i++ { for j := 0; j < n; j++ { if grid2[i][j] == 1 { res++ dfs(grid2, i, j) } } } return res } // 从 (i, j) 开始,将与之相邻的陆地都变成海水 func dfs(grid [][]int, i, j int) { m, n := len(grid), len(grid[0]) if i < 0 || j < 0 || i >= m || j >= n { return } if grid[i][j] == 0 { return } grid[i][j] = 0 dfs(grid, i+1, j) dfs(grid, i, j+1) dfs(grid, i-1, j) dfs(grid, i, j-1) } ``` ```java // by labuladong (java) class Solution { 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); } } } // 现在 grid2 中剩下的岛屿都是子岛,计算岛屿数量 int res = 0; 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; } // 从 (i, j) 开始,将与之相邻的陆地都变成海水 void dfs(int[][] grid, int i, int j) { int m = grid.length, n = grid[0].length; if (i < 0 || j < 0 || i >= m || j >= n) { return; } if (grid[i][j] == 0) { return; } grid[i][j] = 0; dfs(grid, i + 1, j); dfs(grid, i, j + 1); dfs(grid, i - 1, j); dfs(grid, i, j - 1); } } ``` ```javascript // by chatGPT (javascript) /** * @param {number[][]} grid1 * @param {number[][]} grid2 * @return {number} */ var countSubIslands = function(grid1, grid2) { const m = grid1.length, n = grid1[0].length; // 淹没非子岛屿 for (let i = 0; i < m; i++) { for (let j = 0; j < n; j++) { if (grid1[i][j] == 0 && grid2[i][j] == 1) { dfs(grid2, i, j); } } } // 计算子岛屿数量 let res = 0; for (let i = 0; i < m; i++) { for (let j = 0; j < n; j++) { if (grid2[i][j] == 1) { res++; dfs(grid2, i, j); } } } return res; }; // 深度优先遍历 function dfs(grid, i, j) { const m = grid.length, n = grid[0].length; if (i < 0 || j < 0 || i >= m || j >= n) { return; } if (grid[i][j] == 0) { return; } grid[i][j] = 0; dfs(grid, i + 1, j); dfs(grid, i, j + 1); dfs(grid, i - 1, j); dfs(grid, i, j - 1); } ``` ```python # by chatGPT (python) class Solution: def countSubIslands(self, grid1: List[List[int]], grid2: List[List[int]]) -> int: m, n = len(grid1), len(grid1[0]) # 遍历 grid2,将非子岛的岛屿淹掉 for i in range(m): for j in range(n): if grid1[i][j] == 0 and grid2[i][j] == 1: self.dfs(grid2, i, j) # 现在 grid2 中剩下的岛屿都是子岛,计算岛屿数量 res = 0 for i in range(m): for j in range(n): if grid2[i][j] == 1: res += 1 self.dfs(grid2, i, j) return res # 从 (i, j) 开始,将与之相邻的陆地都变成海水 def dfs(self, grid: List[List[int]], i: int, j: int) -> None: m, n = len(grid), len(grid[0]) if i < 0 or j < 0 or i >= m or j >= n: return if grid[i][j] == 0: return grid[i][j] = 0 self.dfs(grid, i + 1, j) self.dfs(grid, i, j + 1) self.dfs(grid, i - 1, j) self.dfs(grid, i, j - 1) ``` https://leetcode.cn/problems/count-sub-islands 的多语言解法👆 https://leetcode.cn/problems/couples-holding-hands 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int minSwapsCouples(vector& row) { int n = row.size(); UF uf(n); for (int i = 0; i < n; i += 2) { // 将两人的 couple_id 进行连接 uf.Union(row[i] / 2, row[i + 1] / 2); } // 和连通分量的差即为需要交换的次数 return n - uf.Count(); } }; // 并查集算法模板 class UF { private: // 记录连通分量个数 int count; // 存储若干棵树 vector parent; public: UF(int n) { this->count = n; parent = vector(n); for (int i = 0; i < n; i++) { parent[i] = i; } } /* 将 p 和 q 连通 */ void Union(int p, int q) { int rootP = Find(p); int rootQ = Find(q); if (rootP == rootQ) return; parent[rootQ] = rootP; count--; } /* 判断 p 和 q 是否互相连通 */ bool Connected(int p, int q) { int rootP = Find(p); int rootQ = Find(q); // 处于同一棵树上的节点,相互连通 return rootP == rootQ; } /* 返回节点 x 的根节点 */ int Find(int x) { while (parent[x] != x) { // 进行路径压缩 parent[x] = parent[parent[x]]; x = parent[x]; } return x; } int Count() { return count; } }; ``` ```go // by chatGPT (go) func minSwapsCouples(row []int) int { n := len(row) uf := NewUF(n) for i := 0; i < n; i += 2 { // 将两人的 couple_id 进行连接 uf.Union(row[i]/2, row[i+1]/2) } // 和连通分量的差即为需要交换的次数 return n - uf.Count() } type UF struct { // 记录连通分量个数 count int // 存储若干棵树 parent []int } func NewUF(n int) *UF { uf := &UF{} uf.count = n uf.parent = make([]int, n) for i := 0; i < n; i++ { uf.parent[i] = i } return uf } /* 将 p 和 q 连通 */ func (uf *UF) Union(p, q int) { rootP := uf.find(p) rootQ := uf.find(q) if rootP == rootQ { return } uf.parent[rootQ] = rootP uf.count-- } /* 判断 p 和 q 是否互相连通 */ func (uf *UF) Connected(p, q int) bool { rootP := uf.find(p) rootQ := uf.find(q) // 处于同一棵树上的节点,相互连通 return rootP == rootQ } /* 返回节点 x 的根节点 */ func (uf *UF) find(x int) int { for uf.parent[x] != x { // 进行路径压缩 uf.parent[x] = uf.parent[uf.parent[x]] x = uf.parent[x] } return x } func (uf *UF) Count() int { return uf.count } ``` ```java // by labuladong (java) class Solution { public int minSwapsCouples(int[] row) { int n = row.length; UF uf = new UF(n); for (int i = 0; i < n; i += 2) { // 将两人的 couple_id 进行连接 uf.union(row[i] / 2, row[i + 1] / 2); } // 和连通分量的差即为需要交换的次数 return n - uf.count(); } } // 并查集算法模板 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; } } /* 将 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; } /* 返回节点 x 的根节点 */ private int find(int x) { while (parent[x] != x) { // 进行路径压缩 parent[x] = parent[parent[x]]; x = parent[x]; } return x; } public int count() { return count; } } ``` ```javascript // by chatGPT (javascript) /** * @param {number[]} row * @return {number} */ var minSwapsCouples = function(row) { const n = row.length; const uf = new UF(n); for (let i = 0; i < n; i += 2) { // 将两人的 couple_id 进行连接 uf.union(Math.floor(row[i] / 2), Math.floor(row[i + 1] / 2)); } // 和连通分量的差即为需要交换的次数 return n - uf.count(); }; // 并查集算法模板 class UF { // 记录连通分量个数 #count; // 存储若干棵树 #parent; constructor(n) { this.#count = n; this.#parent = new Array(n).fill().map((_, i) => i); } /* 将 p 和 q 连通 */ union(p, q) { const rootP = this.find(p); const rootQ = this.find(q); if (rootP === rootQ) { return; } this.#parent[rootQ] = rootP; this.#count--; } /* 判断 p 和 q 是否互相连通 */ connected(p, q) { const rootP = this.find(p); const rootQ = this.find(q); // 处于同一棵树上的节点,相互连通 return rootP === rootQ; } /* 返回节点 x 的根节点 */ find(x) { while (this.#parent[x] !== x) { // 进行路径压缩 this.#parent[x] = this.#parent[this.#parent[x]]; x = this.#parent[x]; } return x; } count() { return this.#count; } } ``` ```python # by chatGPT (python) class Solution: def minSwapsCouples(self, row: List[int]) -> int: n = len(row) uf = UF(n) for i in range(0, n, 2): # 将两人的 couple_id 进行连接 uf.union(row[i] // 2, row[i + 1] // 2) # 和连通分量的差即为需要交换的次数 return n - uf.count() class UF: def __init__(self, n: int): self.count = n self.parent = [i for i in range(n)] def union(self, p: int, q: int) -> None: rootP = self.find(p) rootQ = self.find(q) if rootP == rootQ: return self.parent[rootQ] = rootP self.count -= 1 def connected(self, p: int, q: int) -> bool: rootP = self.find(p) rootQ = self.find(q) # 处于同一棵树上的节点,相互连通 return rootP == rootQ def find(self, x: int) -> int: while self.parent[x] != x: # 进行路径压缩 self.parent[x] = self.parent[self.parent[x]] x = self.parent[x] return x def count(self) -> int: return self.count ``` https://leetcode.cn/problems/couples-holding-hands 的多语言解法👆 https://leetcode.cn/problems/course-schedule 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { // 记录一次 traverse 递归经过的节点 vector onPath; // 记录遍历过的节点,防止走回头路 vector visited; // 记录图中是否有环 bool hasCycle = false; public: bool canFinish(int numCourses, vector>& prerequisites) { vector> graph = buildGraph(numCourses, prerequisites); visited.resize(numCourses, false); onPath.resize(numCourses, false); for (int i = 0; i < numCourses; i++) { // 遍历图中的所有节点 traverse(graph, i); } // 只要没有循环依赖可以完成所有课程 return !hasCycle; } void traverse(vector>& graph, int s) { if (onPath[s]) { // 出现环 hasCycle = true; /** ![](../pictures/拓扑排序/4.jpeg) */ } if (visited[s] || hasCycle) { // 如果已经找到了环,也不用再遍历了 return; } // 前序遍历代码位置 visited[s] = true; onPath[s] = true; for (int t : graph[s]) { traverse(graph, t); } // 后序遍历代码位置 onPath[s] = false; } vector> buildGraph(int numCourses, vector>& prerequisites) { // 图中共有 numCourses 个节点 vector> graph(numCourses); for (auto edge : prerequisites) { int from = edge[1]; int to = edge[0]; // 修完课程 from 才能修课程 to // 在图中添加一条从 from 指向 to 的有向边 graph[from].push_back(to); } return graph; } }; ``` ```go // by chatGPT (go) func canFinish(numCourses int, prerequisites [][]int) bool { // 记录一次 traverse 递归经过的节点 onPath := make([]bool, numCourses) // 记录遍历过的节点,防止走回头路 visited := make([]bool, numCourses) // 记录图中是否有环 hasCycle := false graph := buildGraph(numCourses, prerequisites) for i := 0; i < numCourses; i++ { // 遍历图中的所有节点 traverse(graph, i, &hasCycle, visited, onPath) } // 只要没有循环依赖可以完成所有课程 return !hasCycle } func traverse(graph []LinkedList, s int, hasCycle *bool, visited, onPath []bool) { if onPath[s] { // 出现环 *hasCycle = true /** ![](../pictures/拓扑排序/4.jpeg) */ } if visited[s] || *hasCycle { // 如果已经找到了环,也不用再遍历了 return } // 前序遍历代码位置 visited[s] = true onPath[s] = true for _, t := range graph[s].list { traverse(graph, t, hasCycle, visited, onPath) } // 后序遍历代码位置 onPath[s] = false } type LinkedList struct { list []int } func buildGraph(numCourses int, prerequisites [][]int) []LinkedList { // 图中共有 numCourses 个节点 graph := make([]LinkedList, numCourses) for i := 0; i < numCourses; i++ { graph[i] = LinkedList{list: []int{}} } for _, edge := range prerequisites { from := edge[1] to := edge[0] // 修完课程 from 才能修课程 to // 在图中添加一条从 from 指向 to 的有向边 graph[from].list = append(graph[from].list, to) } return graph } ``` ```java // by labuladong (java) class Solution { // 记录一次 traverse 递归经过的节点 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++) { // 遍历图中的所有节点 traverse(graph, i); } // 只要没有循环依赖可以完成所有课程 return !hasCycle; } void traverse(List[] graph, int s) { if (onPath[s]) { // 出现环 hasCycle = true; /** ![](../pictures/拓扑排序/4.jpeg) */ } if (visited[s] || hasCycle) { // 如果已经找到了环,也不用再遍历了 return; } // 前序遍历代码位置 visited[s] = true; onPath[s] = true; for (int t : graph[s]) { traverse(graph, t); } // 后序遍历代码位置 onPath[s] = false; } List[] buildGraph(int numCourses, int[][] prerequisites) { // 图中共有 numCourses 个节点 List[] graph = new LinkedList[numCourses]; for (int i = 0; i < numCourses; i++) { graph[i] = new LinkedList<>(); } for (int[] edge : prerequisites) { int from = edge[1]; int to = edge[0]; // 修完课程 from 才能修课程 to // 在图中添加一条从 from 指向 to 的有向边 graph[from].add(to); } return graph; } } ``` ```javascript // by chatGPT (javascript) var canFinish = function(numCourses, prerequisites) { // 记录一次 traverse 递归经过的节点 const onPath = new Array(numCourses).fill(false); // 记录遍历过的节点,防止走回头路 const visited = new Array(numCourses).fill(false); // 记录图中是否有环 let hasCycle = false; const graph = buildGraph(numCourses, prerequisites); for (let i = 0; i < numCourses; i++) { // 遍历图中的所有节点 traverse(graph, i); } // 只要没有循环依赖可以完成所有课程 return !hasCycle; function traverse(graph, s) { if (onPath[s]) { // 出现环 hasCycle = true; /** ![](../pictures/拓扑排序/4.jpeg) */ return; } if (visited[s] || hasCycle) { // 如果已经找到了环,也不用再遍历了 return; } // 前序遍历代码位置 visited[s] = true; onPath[s] = true; for (let t of graph[s]) { traverse(graph, t); } // 后序遍历代码位置 onPath[s] = false; } function buildGraph(numCourses, prerequisites) { // 图中共有 numCourses 个节点 const graph = new Array(numCourses).map(() => []); for (let edge of prerequisites) { const from = edge[1]; const to = edge[0]; // 修完课程 from 才能修课程 to // 在图中添加一条从 from 指向 to 的有向边 graph[from].push(to); } return graph; } }; ``` ```python # by chatGPT (python) class Solution: def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool: # 记录一次 traverse 递归经过的节点 onPath = [False] * numCourses # 记录遍历过的节点,防止走回头路 visited = [False] * numCourses # 记录图中是否有环 hasCycle = [False] graph = self.buildGraph(numCourses, prerequisites) def traverse(s): if onPath[s]: # 出现环 hasCycle[0] = True """ ![](../pictures/拓扑排序/4.jpeg) """ if visited[s] or hasCycle[0]: # 如果已经找到了环,也不用再遍历了 return # 前序遍历代码位置 visited[s] = True onPath[s] = True for t in graph[s]: traverse(t) # 后序遍历代码位置 onPath[s] = False for i in range(numCourses): # 遍历图中的所有节点 traverse(i) # 只要没有循环依赖可以完成所有课程 return not hasCycle[0] def buildGraph(self, numCourses, prerequisites): # 图中共有 numCourses 个节点 graph = [[] for _ in range(numCourses)] for from_, to in prerequisites: # 修完课程 from_ 才能修课程 to # 在图中添加一条从 from_ 指向 to 的有向边 graph[from_].append(to) return graph ``` https://leetcode.cn/problems/course-schedule 的多语言解法👆 https://leetcode.cn/problems/course-schedule-ii 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: vector findOrder(int numCourses, vector>& prerequisites) { // 建图,和环检测算法相同 vector> graph(numCourses); // 计算入度,和环检测算法相同 vector indegree(numCourses, 0); for (auto& edge : prerequisites) { int from = edge[1], to = edge[0]; graph[from].push_back(to); indegree[to]++; } // 根据入度初始化队列中的节点,和环检测算法相同 queue q; for (int i = 0; i < numCourses; i++) { if (indegree[i] == 0) { q.push(i); } } // 记录拓扑排序结果 vector res; // 开始执行 BFS 算法 while (!q.empty()) { int cur = q.front(); q.pop(); // 弹出节点的顺序即为拓扑排序结果 res.push_back(cur); for (int next : graph[cur]) { indegree[next]--; if (indegree[next] == 0) { q.push(next); } } } if (res.size() != numCourses) { // 存在环,拓扑排序不存在 return {}; } return res; } }; ``` ```go // by chatGPT (go) func findOrder(numCourses int, prerequisites [][]int) []int { // 建图,和环检测算法相同 graph := buildGraph(numCourses, prerequisites) // 计算入度,和环检测算法相同 indegree := make([]int, numCourses) for _, edge := range prerequisites { _, to := edge[1], edge[0] from, to := edge[1], edge[0] _, to := edge[1], edge[0] from, to := edge[1], edge[0] _, to := edge[1], edge[0] from, to := edge[1], edge[0] _, to := edge[1], edge[0] from, to := edge[1], edge[0] _, to := edge[1], edge[0] from, to := edge[1], edge[0] _, to := edge[1], edge[0] from, to := edge[1], edge[0] _, to := edge[1], edge[0] from, to := edge[1], edge[0] _, to := edge[1], edge[0] from, to := edge[1], edge[0] _, to := edge[1], edge[0] from, to := edge[1], edge[0] _, to := edge[1], edge[0] indegree[to]++ } // 根据入度初始化队列中的节点,和环检测算法相同 q := make([]int, 0) for i := 0; i < numCourses; i++ { if indegree[i] == 0 { q = append(q, i) } } // 记录拓扑排序结果 res := make([]int, numCourses) // 记录遍历节点的顺序(索引) count := 0 // 开始执行 BFS 算法 for len(q) > 0 { cur := q[0] q = q[1:] // 弹出节点的顺序即为拓扑排序结果 res[count] = cur count++ for _, next := range graph[cur] { indegree[next]-- if indegree[next] == 0 { q = append(q, next) } } } if count != numCourses { // 存在环,拓扑排序不存在 return []int{} } return res } // 建图函数 func buildGraph(numCourses int, prerequisites [][]int) []([]int) { // 图中共有 numCourses 个节点 graph := make([]([]int), numCourses) for i := 0; i < numCourses; i++ { graph[i] = make([]int, 0) } for _, edge := range prerequisites { from, to := edge[1], edge[0] // 修完课程 from 才能修课程 to // 在图中添加一条从 from 指向 to 的有向边 graph[from] = append(graph[from], to) } return graph } ``` ```java // by labuladong (java) class Solution { // 主函数 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); /** ![](../pictures/拓扑排序/6.jpeg) */ } } // 记录拓扑排序结果 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]) { /** ![](../pictures/拓扑排序/7.jpeg) */ indegree[next]--; if (indegree[next] == 0) { q.offer(next); } } } if (count != numCourses) { // 存在环,拓扑排序不存在 return new int[]{}; } return res; } // 建图函数 List[] buildGraph(int numCourses, int[][] prerequisites) { // 图中共有 numCourses 个节点 List[] graph = new LinkedList[numCourses]; for (int i = 0; i < numCourses; i++) { graph[i] = new LinkedList<>(); } for (int[] edge : prerequisites) { int from = edge[1], to = edge[0]; // 修完课程 from 才能修课程 to // 在图中添加一条从 from 指向 to 的有向边 graph[from].add(to); } return graph; } } ``` ```javascript // by chatGPT (javascript) var findOrder = function(numCourses, prerequisites) { // 建图,和环检测算法相同 const graph = buildGraph(numCourses, prerequisites); // 计算入度,和环检测算法相同 const indegree = new Array(numCourses).fill(0); for (const [from, to] of prerequisites) { indegree[to]++; } // 根据入度初始化队列中的节点,和环检测算法相同 const q = []; for (let i = 0; i < numCourses; i++) { if (indegree[i] === 0) { q.push(i); /** ![](../pictures/拓扑排序/6.jpeg) */ } } // 记录拓扑排序结果 const res = new Array(numCourses); // 记录遍历节点的顺序(索引) let count = 0; // 开始执行 BFS 算法 while (q.length > 0) { const cur = q.shift(); // 弹出节点的顺序即为拓扑排序结果 res[count] = cur; count++; for (const next of graph[cur]) { /** ![](../pictures/拓扑排序/7.jpeg) */ indegree[next]--; if (indegree[next] === 0) { q.push(next); } } } if (count !== numCourses) { // 存在环,拓扑排序不存在 return []; } return res; } // 建图函数 function buildGraph(numCourses, prerequisites) { // 图中共有 numCourses 个节点 const graph = new Array(numCourses).fill().map(() => []); for (const [from, to] of prerequisites) { // 修完课程 from 才能修课程 to // 在图中添加一条从 from 指向 to 的有向边 graph[from].push(to); } return graph; } ``` ```python # by chatGPT (python) class Solution: def findOrder(self, numCourses: int, prerequisites: List[List[int]]) -> List[int]: # 建图,和环检测算法相同 graph = self.buildGraph(numCourses, prerequisites) # 计算入度,和环检测算法相同 indegree = [0] * numCourses for edge in prerequisites: from_course, to_course = edge[1], edge[0] indegree[to_course] += 1 # 根据入度初始化队列中的节点,和环检测算法相同 q = [] for i in range(numCourses): if indegree[i] == 0: q.append(i) # 记录拓扑排序结果 res = [] # 记录遍历节点的顺序(索引) count = 0 # 开始执行 BFS 算法 while q: cur = q.pop(0) # 弹出节点的顺序即为拓扑排序结果 res.append(cur) count += 1 for next_course in graph[cur]: indegree[next_course] -= 1 if indegree[next_course] == 0: q.append(next_course) if count != numCourses: # 存在环,拓扑排序不存在 return [] return res # 建图函数 def buildGraph(self, numCourses: int, prerequisites: List[List[int]]) -> List[List[int]]: # 图中共有 numCourses 个节点 graph = [[] for _ in range(numCourses)] for edge in prerequisites: from_course, to_course = edge[1], edge[0] # 修完课程 from 才能修课程 to # 在图中添加一条从 from 指向 to 的有向边 graph[from_course].append(to_course) return graph ``` https://leetcode.cn/problems/course-schedule-ii 的多语言解法👆 https://leetcode.cn/problems/da-yin-cong-1dao-zui-da-de-nwei-shu-lcof 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: vector printNumbers(int n) { // 先生成 n 位 9 int max = 0; for (int i = 0; i < n; i++) { max = 10 * max + 9; } // 填入结果数组 vector res(max); for (int i = 1; i <= max; i++) { res[i - 1] = i; } return res; } }; ``` ```go // by chatGPT (go) func printNumbers(n int) []int { // 先生成 n 位 9 max := 0 for i := 0; i < n; i++ { max = 10 * max + 9 } // 填入结果数组 res := make([]int, max) for i := 1; i <= max; i++ { res[i-1] = i } return res } ``` ```java // by labuladong (java) class Solution { public int[] printNumbers(int n) { // 先生成 n 位 9 int max = 0; for (int i = 0; i < n; i++) { max = 10 * max + 9; } // 填入结果数组 int[] res = new int[max]; for (int i = 1; i <= max; i++) { res[i - 1] = i; } return res; } } ``` ```javascript // by chatGPT (javascript) /** * @param {number} n * @return {number[]} */ var printNumbers = function(n) { // 先生成 n 位 9 let max = 0; for (let i = 0; i < n; i++) { max = 10 * max + 9; } // 填入结果数组 let res = new Array(max); for (let i = 1; i <= max; i++) { res[i - 1] = i; } return res; }; ``` ```python # by chatGPT (python) class Solution: def printNumbers(self, n: int) -> List[int]: # 先生成 n 位 9 max_num = 0 for i in range(n): max_num = max_num * 10 + 9 # 填入结果数组 res = [] for i in range(1, max_num + 1): res.append(i) return res ``` https://leetcode.cn/problems/da-yin-cong-1dao-zui-da-de-nwei-shu-lcof 的多语言解法👆 https://leetcode.cn/problems/delete-node-in-a-bst 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: TreeNode* deleteNode(TreeNode* root, int key) { if (root == nullptr) { return nullptr; } if (root->val == key) { // 这两个 if 把情况 1 和 2 都正确处理了 if (root->left == nullptr) { return root->right; } if (root->right == nullptr) { return root->left; } // 处理情况 3 // 获得右子树最小的节点 TreeNode* minNode = getMin(root->right); // 删除右子树最小的节点 root->right = deleteNode(root->right, minNode->val); // 用右子树最小的节点替换 root 节点 minNode->left = root->left; minNode->right = root->right; root = minNode; } else if (root->val > key) { root->left = deleteNode(root->left, key); } else if (root->val < key) { root->right = deleteNode(root->right, key); } return root; } TreeNode* getMin(TreeNode* node) { // BST 最左边的就是最小的 while (node->left != nullptr) { node = node->left; } return node; } }; ``` ```go // by chatGPT (go) func deleteNode(root *TreeNode, key int) *TreeNode { if root == nil { return nil } if root.Val == key { // 这两个 if 把情况 1 和 2 都正确处理了 if root.Left == nil { return root.Right } if root.Right == nil { return root.Left } // 处理情况 3 // 获得右子树最小的节点 minNode := getMin(root.Right) // 删除右子树最小的节点 root.Right = deleteNode(root.Right, minNode.Val) // 用右子树最小的节点替换 root 节点 minNode.Left = root.Left minNode.Right = root.Right root = minNode } else if root.Val > key { root.Left = deleteNode(root.Left, key) } else if root.Val < key { root.Right = deleteNode(root.Right, key) } return root } func getMin(node *TreeNode) *TreeNode { // BST 最左边的就是最小的 for node.Left != nil { node = node.Left } return node } ``` ```java // by labuladong (java) class Solution { public TreeNode deleteNode(TreeNode root, int key) { if (root == null) return null; if (root.val == key) { // 这两个 if 把情况 1 和 2 都正确处理了 if (root.left == null) return root.right; if (root.right == null) return root.left; // 处理情况 3 // 获得右子树最小的节点 TreeNode minNode = getMin(root.right); // 删除右子树最小的节点 root.right = deleteNode(root.right, minNode.val); // 用右子树最小的节点替换 root 节点 minNode.left = root.left; minNode.right = root.right; root = minNode; } else if (root.val > key) { root.left = deleteNode(root.left, key); } else if (root.val < key) { root.right = deleteNode(root.right, key); } return root; } TreeNode getMin(TreeNode node) { // BST 最左边的就是最小的 while (node.left != null) node = node.left; return node; } } ``` ```javascript // by chatGPT (javascript) /** * @param {TreeNode} root * @param {number} key * @return {TreeNode} */ var deleteNode = function(root, key) { if (root == null) return null; if (root.val == key) { // 这两个 if 把情况 1 和 2 都正确处理了 if (root.left == null) return root.right; if (root.right == null) return root.left; // 处理情况 3 // 获得右子树最小的节点 let minNode = getMin(root.right); // 删除右子树最小的节点 root.right = deleteNode(root.right, minNode.val); // 用右子树最小的节点替换 root 节点 minNode.left = root.left; minNode.right = root.right; root = minNode; } else if (root.val > key) { root.left = deleteNode(root.left, key); } else if (root.val < key) { root.right = deleteNode(root.right, key); } return root; }; var getMin = function(node) { // BST 最左边的就是最小的 while (node.left != null) node = node.left; return node; }; ``` ```python # by chatGPT (python) class Solution: def deleteNode(self, root: TreeNode, key: int) -> TreeNode: if not root: return None if root.val == key: # 这两个 if 把情况 1 和 2 都正确处理了 if not root.left: return root.right if not root.right: return root.left # 处理情况 3 # 获得右子树最小的节点 min_node = self.getMin(root.right) # 删除右子树最小的节点 root.right = self.deleteNode(root.right, min_node.val) # 用右子树最小的节点替换 root 节点 min_node.left = root.left min_node.right = root.right root = min_node elif root.val > key: root.left = self.deleteNode(root.left, key) elif root.val < key: root.right = self.deleteNode(root.right, key) return root def getMin(self, node: TreeNode) -> TreeNode: # BST 最左边的就是最小的 while node.left: node = node.left return node ``` https://leetcode.cn/problems/delete-node-in-a-bst 的多语言解法👆 https://leetcode.cn/problems/delete-operation-for-two-strings 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int minDistance(string s1, string s2) { int m = s1.length(), n = s2.length(); // 复用前文计算 lcs 长度的函数 int lcs = longestCommonSubsequence(s1, s2); return m - lcs + n - lcs; } // 计算最长公共子序列的长度 int longestCommonSubsequence(string s1, string s2) { int m = s1.length(), n = s2.length(); // 定义:s1[0..i-1] 和 s2[0..j-1] 的 lcs 长度为 dp[i][j] vector> dp(m + 1, vector(n + 1, 0)); for (int i = 1; i <= m; i++) { for (int j = 1; j <= n; j++) { // 现在 i 和 j 从 1 开始,所以要减一 if (s1[i - 1] == s2[j - 1]) { // s1[i-1] 和 s2[j-1] 必然在 lcs 中 dp[i][j] = 1 + dp[i - 1][j - 1]; } else { // s1[i-1] 和 s2[j-1] 至少有一个不在 lcs 中 dp[i][j] = max(dp[i][j - 1], dp[i - 1][j]); } } } return dp[m][n]; } }; ``` ```go // by chatGPT (go) func minDistance(s1 string, s2 string) int { m, n := len(s1), len(s2) // 复用前文计算 lcs 长度的函数 lcs := longestCommonSubsequence(s1, s2) return m - lcs + n - lcs } // 计算最长公共子序列的长度 func longestCommonSubsequence(s1 string, s2 string) int { m, n := len(s1), len(s2) // 定义:s1[0..i-1] 和 s2[0..j-1] 的 lcs 长度为 dp[i][j] dp := make([][]int, m+1) for i := 0; i <= m; i++ { dp[i] = make([]int, n+1) } for i := 1; i <= m; i++ { for j := 1; j <= n; j++ { // 现在 i 和 j 从 1 开始,所以要减一 if s1[i-1] == s2[j-1] { // s1[i-1] 和 s2[j-1] 必然在 lcs 中 dp[i][j] = 1 + dp[i-1][j-1] } else { // s1[i-1] 和 s2[j-1] 至少有一个不在 lcs 中 dp[i][j] = max(dp[i][j-1], dp[i-1][j]) } } } return dp[m][n] } func max(a, b int) int { if a > b { return a } return b } ``` ```java // by labuladong (java) class Solution { public int minDistance(String s1, String s2) { int m = s1.length(), n = s2.length(); // 复用前文计算 lcs 长度的函数 int lcs = longestCommonSubsequence(s1, s2); return m - lcs + n - lcs; } // 计算最长公共子序列的长度 int longestCommonSubsequence(String s1, String s2) { int m = s1.length(), n = s2.length(); // 定义:s1[0..i-1] 和 s2[0..j-1] 的 lcs 长度为 dp[i][j] int[][] dp = new int[m + 1][n + 1]; for (int i = 1; i <= m; i++) { for (int j = 1; j <= n; j++) { // 现在 i 和 j 从 1 开始,所以要减一 if (s1.charAt(i - 1) == s2.charAt(j - 1)) { // s1[i-1] 和 s2[j-1] 必然在 lcs 中 dp[i][j] = 1 + dp[i - 1][j - 1]; } else { // s1[i-1] 和 s2[j-1] 至少有一个不在 lcs 中 dp[i][j] = Math.max(dp[i][j - 1], dp[i - 1][j]); } } } return dp[m][n]; } } ``` ```javascript // by chatGPT (javascript) var minDistance = function(s1, s2) { const m = s1.length, n = s2.length; // 复用前文计算 lcs 长度的函数 const lcs = longestCommonSubsequence(s1, s2); return m - lcs + n - lcs; }; // 计算最长公共子序列的长度 function longestCommonSubsequence(s1, s2) { const m = s1.length, n = s2.length; // 定义:s1[0..i-1] 和 s2[0..j-1] 的 lcs 长度为 dp[i][j] const dp = Array.from(Array(m + 1), () => new Array(n + 1).fill(0)); for (let i = 1; i <= m; i++) { for (let j = 1; j <= n; j++) { // 现在 i 和 j 从 1 开始,所以要减一 if (s1.charAt(i - 1) == s2.charAt(j - 1)) { // s1[i-1] 和 s2[j-1] 必然在 lcs 中 dp[i][j] = 1 + dp[i - 1][j - 1]; } else { // s1[i-1] 和 s2[j-1] 至少有一个不在 lcs 中 dp[i][j] = Math.max(dp[i][j - 1], dp[i - 1][j]); } } } return dp[m][n]; }; ``` ```python # by chatGPT (python) class Solution: def minDistance(self, s1: str, s2: str) -> int: m, n = len(s1), len(s2) # 复用前文计算 lcs 长度的函数 lcs = self.longestCommonSubsequence(s1, s2) return m - lcs + n - lcs # 计算最长公共子序列的长度 def longestCommonSubsequence(self, s1: str, s2: str) -> int: m, n = len(s1), len(s2) # 定义:s1[0..i-1] 和 s2[0..j-1] 的 lcs 长度为 dp[i][j] dp = [[0] * (n + 1) for _ in range(m + 1)] for i in range(1, m + 1): for j in range(1, n + 1): # 现在 i 和 j 从 1 开始,所以要减一 if s1[i - 1] == s2[j - 1]: # s1[i-1] 和 s2[j-1] 必然在 lcs 中 dp[i][j] = 1 + dp[i - 1][j - 1] else: # s1[i-1] 和 s2[j-1] 至少有一个不在 lcs 中 dp[i][j] = max(dp[i][j - 1], dp[i - 1][j]) return dp[m][n] ``` https://leetcode.cn/problems/delete-operation-for-two-strings 的多语言解法👆 https://leetcode.cn/problems/design-twitter 的多语言解法👇 ```cpp // by chatGPT (cpp) class Twitter { private: // Tweet 类 class Tweet { private: int id; // 时间戳用于对信息流按照时间排序 int timestamp; // 指向下一条 tweet,类似单链表结构 Tweet *next; public: Tweet(int id) { this->id = id; // 新建一条 tweet 时记录并更新时间戳 this->timestamp = globalTime++; } int getId() { return id; } int getTimestamp() { return timestamp; } Tweet* getNext() { return next; } void setNext(Tweet *next) { this->next = next; } }; // 用户类 class User { private: // 记录该用户的 id 以及发布的 tweet int id; Tweet *tweetHead; // 记录该用户的关注者 unordered_set followedUserSet; public: User(int id) { this->id = id; this->tweetHead = nullptr; this->followedUserSet = unordered_set(); } int getId() { return id; } Tweet* getTweetHead() { return tweetHead; } unordered_set getFollowedUserSet() { return followedUserSet; } bool equals(User* other) { return this->id == other->id; } // 关注其他人 void follow(User *other) { followedUserSet.insert(other); } // 取关其他人 void unfollow(User *other) { followedUserSet.erase(other); } // 发布一条 tweet void post(Tweet *tweet) { // 把新发布的 tweet 作为链表头节点 tweet->setNext(tweetHead); tweetHead = tweet; } }; // 全局时间戳 int globalTime = 0; // 记录用户 ID 到用户示例的映射 unordered_map idToUser; public: void postTweet(int userId, int tweetId) { // 如果这个用户还不存在,新建用户 if (idToUser.find(userId) == idToUser.end()) { idToUser[userId] = new User(userId); } User* user = idToUser[userId]; user->post(new Tweet(tweetId)); } vector getNewsFeed(int userId) { vector res = vector(); if (idToUser.find(userId) == idToUser.end()) { return res; } // 获取该用户关注的用户列表 User* user = idToUser[userId]; unordered_set followedUserSet = user->getFollowedUserSet(); // 每个用户的 tweet 是一条按时间排序的链表 // 现在执行合并多条有序链表的逻辑,找出时间线中的最近 10 条动态 auto cmp = [](Tweet* a, Tweet* b) -> bool { // 按照每条 tweet 的发布时间降序排序(最近发布的排在事件流前面) return b->getTimestamp() < a->getTimestamp(); }; priority_queue, decltype(cmp)> pq(cmp); // 该用户自己的 tweet 也在时间线内 if (user->getTweetHead() != nullptr) { pq.push(user->getTweetHead()); } for (User* other : followedUserSet) { if (other->getTweetHead() != nullptr) { pq.push(other->getTweetHead()); } } // 合并多条有序链表 int count = 0; while (!pq.empty() && count < 10) { Tweet* tweet = pq.top(); pq.pop(); res.push_back(tweet->getId()); if (tweet->getNext() != nullptr) { pq.push(tweet->getNext()); } count++; } return res; } void follow(int followerId, int followeeId) { // 如果用户还不存在,则新建用户 if (idToUser.find(followerId) == idToUser.end()) { idToUser[followerId] = new User(followerId); } if (idToUser.find(followeeId) == idToUser.end()) { idToUser[followeeId] = new User(followeeId); } User* follower = idToUser[followerId]; User* followee = idToUser[followeeId]; // 关注者关注被关注者 follower->follow(followee); } void unfollow(int followerId, int followeeId) { if (idToUser.find(followerId) == idToUser.end() || idToUser.find(followeeId) == idToUser.end()) { return; } User* follower = idToUser[followerId]; User* followee = idToUser[followeeId]; // 关注者取关被关注者 follower->unfollow(followee); } }; ``` ```go // by chatGPT (go) type Twitter struct { // 全局时间戳 globalTime int // 记录用户 ID 到用户示例的映射 idToUser map[int]*User } // Tweet 类 type Tweet struct { id int // 时间戳用于对信息流按照时间排序 timestamp int // 指向下一条 tweet,类似单链表结构 next *Tweet } func NewTweet(id int) *Tweet { return &Tweet{ id: id, timestamp: globalTime, } } // User 类 type User struct { // 记录该用户的 id 以及发布的 tweet id int tweetHead *Tweet // 记录该用户的关注者 followedUsers map[int]*User } func NewUser(id int) *User { return &User{ id: id, followedUsers: make(map[int]*User), } } func (u *User) Post(tweet *Tweet) { // 把新发布的 tweet 作为链表头节点 tweet.next = u.tweetHead u.tweetHead = tweet } func (u *User) Follow(other *User) { u.followedUsers[other.id] = other } func (u *User) Unfollow(other *User) { delete(u.followedUsers, other.id) } func (u *User) Eq(other *User) bool { return u.id == other.id } func (t *Twitter) PostTweet(userId int, tweetId int) { // 如果这个用户还不存在,新建用户 if _, ok := t.idToUser[userId]; !ok { t.idToUser[userId] = NewUser(userId) } user := t.idToUser[userId] user.Post(NewTweet(tweetId)) } func (t *Twitter) GetNewsFeed(userId int) []int { res := make([]int, 0) if _, ok := t.idToUser[userId]; !ok { return res } // 获取该用户关注的用户列表 user := t.idToUser[userId] followedUserSet := user.followedUsers // 每个用户的 tweet 是一条按时间排序的链表 // 现在执行合并多条有序链表的逻辑,找出时间线中的最近 10 条动态 pq := make(PriorityQueue, 0) // 按照每条 tweet 的发布时间降序排序(最近发布的排在事件流前面) heap.Init(&pq) // 该用户自己的 tweet 也在时间线内 if user.tweetHead != nil { heap.Push(&pq, user.tweetHead) } for _, other := range followedUserSet { if other.tweetHead != nil { heap.Push(&pq, other.tweetHead) } } // 合并多条有序链表 count := 0 for pq.Len() > 0 && count < 10 { tweet := heap.Pop(&pq).(*Tweet) res = append(res, tweet.id) if tweet.next != nil { heap.Push(&pq, tweet.next) } count++ } return res } func (t *Twitter) Follow(followerId int, followeeId int) { // 如果用户还不存在,则新建用户 if _, ok := t.idToUser[followerId]; !ok { t.idToUser[followerId] = NewUser(followerId) } if _, ok := t.idToUser[followeeId]; !ok { t.idToUser[followeeId] = NewUser(followeeId) } follower := t.idToUser[followerId] followee := t.idToUser[followeeId] // 关注者关注被关注者 follower.Follow(followee) } func (t *Twitter) Unfollow(followerId int, followeeId int) { if _, ok := t.idToUser[followerId]; !ok || _, ok := t.idToUser[followeeId]; !ok { return } follower := t.idToUser[followerId] followee := t.idToUser[followeeId] // 关注者取关被关注者 follower.Unfollow(followee) } type PriorityQueue []*Tweet func (pq PriorityQueue) Len() int { return len(pq) } func (pq PriorityQueue) Less(i, j int) bool { return pq[i].timestamp > pq[j].timestamp } func (pq PriorityQueue) Swap(i, j int) { pq[i], pq[j] = pq[j], pq[i] } func (pq *PriorityQueue) Pop() interface{} { n := len(*pq) item := (*pq)[n-1] *pq = (*pq)[:n-1] return item } func (pq *PriorityQueue) Push(item interface{}) { *pq = append(*pq, item.(*Tweet)) } ``` ```java // by labuladong (java) class Twitter { // 全局时间戳 int globalTime = 0; // 记录用户 ID 到用户示例的映射 HashMap idToUser = new HashMap<>(); // Tweet 类 class Tweet { private int id; // 时间戳用于对信息流按照时间排序 private int timestamp; // 指向下一条 tweet,类似单链表结构 private Tweet next; public Tweet(int id) { this.id = id; // 新建一条 tweet 时记录并更新时间戳 this.timestamp = globalTime++; } public int getId() { return id; } public int getTimestamp() { return timestamp; } public Tweet getNext() { return next; } public void setNext(Tweet next) { this.next = next; } } // 用户类 class User { // 记录该用户的 id 以及发布的 tweet private int id; private Tweet tweetHead; // 记录该用户的关注者 private HashSet followedUserSet; public User(int id) { this.id = id; this.tweetHead = null; this.followedUserSet = new HashSet<>(); } public int getId() { return id; } public Tweet getTweetHead() { return tweetHead; } public HashSet getFollowedUserSet() { return followedUserSet; } public boolean equals(User other) { return this.id == other.id; } // 关注其他人 public void follow(User other) { followedUserSet.add(other); } // 取关其他人 public void unfollow(User other) { followedUserSet.remove(other); } // 发布一条 tweet public void post(Tweet tweet) { // 把新发布的 tweet 作为链表头节点 tweet.setNext(tweetHead); tweetHead = tweet; } } public void postTweet(int userId, int tweetId) { // 如果这个用户还不存在,新建用户 if (!idToUser.containsKey(userId)) { idToUser.put(userId, new User(userId)); } User user = idToUser.get(userId); user.post(new Tweet(tweetId)); } public List getNewsFeed(int userId) { List res = new LinkedList<>(); if (!idToUser.containsKey(userId)) { return res; } // 获取该用户关注的用户列表 User user = idToUser.get(userId); Set followedUserSet = user.getFollowedUserSet(); // 每个用户的 tweet 是一条按时间排序的链表 // 现在执行合并多条有序链表的逻辑,找出时间线中的最近 10 条动态 PriorityQueue pq = new PriorityQueue<>((a, b) -> { // 按照每条 tweet 的发布时间降序排序(最近发布的排在事件流前面) return b.timestamp - a.timestamp; }); // 该用户自己的 tweet 也在时间线内 if (user.getTweetHead() != null) { pq.offer(user.getTweetHead()); } for (User other : followedUserSet) { if (other.getTweetHead() != null) { pq.offer(other.tweetHead); } } // 合并多条有序链表 int count = 0; while (!pq.isEmpty() && count < 10) { Tweet tweet = pq.poll(); res.add(tweet.getId()); if (tweet.getNext() != null) { pq.offer(tweet.getNext()); } count++; } return res; } public void follow(int followerId, int followeeId) { // 如果用户还不存在,则新建用户 if (!idToUser.containsKey(followerId)) { idToUser.put(followerId, new User(followerId)); } if (!idToUser.containsKey(followeeId)) { idToUser.put(followeeId, new User(followeeId)); } User follower = idToUser.get(followerId); User followee = idToUser.get(followeeId); // 关注者关注被关注者 follower.follow(followee); } public void unfollow(int followerId, int followeeId) { if (!idToUser.containsKey(followerId) || !idToUser.containsKey(followeeId)) { return; } User follower = idToUser.get(followerId); User followee = idToUser.get(followeeId); // 关注者取关被关注者 follower.unfollow(followee); } } ``` ```javascript // by chatGPT (javascript) var Twitter = function() { // 全局时间戳 let globalTime = 0; // 记录用户 ID 到用户示例的映射 const idToUser = new Map(); // Tweet 类 function Tweet(id) { this.id = id; // 时间戳用于对信息流按照时间排序 this.timestamp = globalTime++; // 指向下一条 tweet,类似单链表结构 this.next = null; } // 用户类 function User(id) { // 记录该用户的 id 以及发布的 tweet this.id = id; this.tweetHead = null; // 记录该用户的关注者 this.followedUserSet = new Set(); } User.prototype.equals = function(other) { return this.id === other.id; } // 关注其他人 User.prototype.follow = function(other) { this.followedUserSet.add(other); } // 取关其他人 User.prototype.unfollow = function(other) { this.followedUserSet.delete(other); } // 发布一条 tweet User.prototype.post = function(tweet) { // 把新发布的 tweet 作为链表头节点 tweet.next = this.tweetHead; this.tweetHead = tweet; } this.postTweet = function(userId, tweetId) { // 如果这个用户还不存在,新建用户 if (!idToUser.has(userId)) { idToUser.set(userId, new User(userId)); } const user = idToUser.get(userId); user.post(new Tweet(tweetId)); }; this.getNewsFeed = function(userId) { const res = []; if (!idToUser.has(userId)) { return res; } // 获取该用户关注的用户列表 const user = idToUser.get(userId); const followedUserSet = user.followedUserSet; // 每个用户的 tweet 是一条按时间排序的链表 // 现在执行合并多条有序链表的逻辑,找出时间线中的最近 10 条动态 const pq = new PriorityQueue((a, b) => { // 按照每条 tweet 的发布时间降序排序(最近发布的排在事件流前面) return b.timestamp - a.timestamp; }); // 该用户自己的 tweet 也在时间线内 if (user.tweetHead !== null) { pq.offer(user.tweetHead); } for (const other of followedUserSet) { if (other.tweetHead !== null) { pq.offer(other.tweetHead); } } // 合并多条有序链表 let count = 0; while (!pq.isEmpty() && count < 10) { const tweet = pq.poll(); res.push(tweet.id); if (tweet.next !== null) { pq.offer(tweet.next); } count++; } return res; }; this.follow = function(followerId, followeeId) { // 如果用户还不存在,则新建用户 if (!idToUser.has(followerId)) { idToUser.set(followerId, new User(followerId)); } if (!idToUser.has(followeeId)) { idToUser.set(followeeId, new User(followeeId)); } const follower = idToUser.get(followerId); const followee = idToUser.get(followeeId); // 关注者关注被关注者 follower.follow(followee); }; this.unfollow = function(followerId, followeeId) { if (!idToUser.has(followerId) || !idToUser.has(followeeId)) { return; } const follower = idToUser.get(followerId); const followee = idToUser.get(followeeId); // 关注者取关被关注者 follower.unfollow(followee); }; }; // 优先队列实现 class PriorityQueue { constructor(comparator) { this.heap = []; this.comparator = comparator; } /** * 上浮操作 * @param {number} index - 上浮节点的下标 */ swim(index) { let currentIndex = index; while (currentIndex > 0) { const parentIndex = Math.floor((currentIndex - 1) / 2); if (this.comparator(this.heap[currentIndex], this.heap[parentIndex]) >= 0) { break; } [this.heap[currentIndex], this.heap[parentIndex]] = [this.heap[parentIndex], this.heap[currentIndex]]; currentIndex = parentIndex; } } /** * 下沉操作 * @param {number} index - 下沉节点的下标 */ sink(index) { let currentIndex = index; while (currentIndex * 2 + 1 < this.heap.length) { const leftIndex = currentIndex * 2 + 1; const rightIndex = currentIndex * 2 + 2 < this.heap.length ? currentIndex * 2 + 2 : leftIndex; const smallerIndex = this.comparator(this.heap[leftIndex], this.heap[rightIndex]) <= 0 ? leftIndex : rightIndex; if (this.comparator(this.heap[currentIndex], this.heap[smallerIndex]) <= 0) { break; } [this.heap[currentIndex], this.heap[smallerIndex]] = [this.heap[smallerIndex], this.heap[currentIndex]]; currentIndex = smallerIndex; } } /** * 插入元素 * @param {*} value - 插入的值 */ offer(value) { this.heap.push(value); this.swim(this.heap.length - 1); } /** * 弹出堆顶元素 * @return {*} 堆顶元素 */ poll() { if (this.size() === 0) { return null; } if (this.size() === 1) { return this.heap.pop(); } const top = this.heap[0]; this.heap[0] = this.heap.pop(); this.sink(0); return top; } /** * 获取堆大小 * @return {number} 堆大小 */ size() { return this.heap.length; } } ``` ```python # by chatGPT (python) class Twitter: def __init__(self): # 全局时间戳 self.globalTime = 0 # 记录用户 ID 到用户示例的映射 self.idToUser = {} # Tweet 类 class Tweet: def __init__(self, id: int): self.id = id # 时间戳用于对信息流按照时间排序 self.timestamp = Twitter.globalTime Twitter.globalTime += 1 # 指向下一条 tweet,类似单链表结构 self.next = None def get_id(self) -> int: return self.id def get_timestamp(self) -> int: return self.timestamp def get_next(self): return self.next def set_next(self, next_tweet): self.next = next_tweet # 用户类 class User: def __init__(self, id: int): # 记录该用户的 id 以及发布的 tweet self.id = id self.tweet_head = None # 记录该用户的关注者 self.followed_user_set = set() def get_id(self): return self.id def get_tweet_head(self) -> Tweet: return self.tweet_head def get_followed_user_set(self): return self.followed_user_set def __eq__(self, other): return self.id == other.id # 关注其他人 def follow(self, other): self.followed_user_set.add(other) # 取关其他人 def unfollow(self, other): self.followed_user_set.discard(other) # 发布一条 tweet def post(self, tweet: Tweet): # 把新发布的 tweet 作为链表头节点 tweet.set_next(self.tweet_head) self.tweet_head = tweet def postTweet(self, userId: int, tweetId: int) -> None: # 如果这个用户还不存在,新建用户 if userId not in self.idToUser: self.idToUser[userId] = Twitter.User(userId) user = self.idToUser[userId] user.post(Twitter.Tweet(tweetId)) def getNewsFeed(self, userId: int) -> List[int]: res = [] if userId not in self.idToUser: return res # 获取该用户关注的用户列表 user = self.idToUser[userId] followed_user_set = user.get_followed_user_set() # 每个用户的 tweet 是一条按时间排序的链表 # 现在执行合并多条有序链表的逻辑,找出时间线中的最近 10 条动态 pq = [] # 该用户自己的 tweet 也在时间线内 if user.get_tweet_head(): heappush(pq, (-user.tweet_head.timestamp, user.get_tweet_head())) # 按照每条 tweet 的发布时间降序排序(最近发布的排在事件流前面) for other in followed_user_set: if other.get_tweet_head(): heappush(pq, (-other.tweet_head.timestamp, other.get_tweet_head())) # 合并多条有序链表 count = 0 while pq and count < 10: _, tweet = heappop(pq) res.append(tweet.get_id()) if tweet.get_next(): heappush(pq, (-tweet.get_next().timestamp, tweet.get_next())) count += 1 return res def follow(self, followerId: int, followeeId: int) -> None: # 如果用户还不存在,则新建用户 if followerId not in self.idToUser: self.idToUser[followerId] = Twitter.User(followerId) if followeeId not in self.idToUser: self.idToUser[followeeId] = Twitter.User(followeeId) follower = self.idToUser[followerId] followee = self.idToUser[followeeId] # 关注者关注被关注者 follower.follow(followee) def unfollow(self, followerId: int, followeeId: int) -> None: if followerId not in self.idToUser or followeeId not in self.idToUser: return follower = self.idToUser[followerId] followee = self.idToUser[followeeId] # 关注者取关被关注者 follower.unfollow(followee) ``` https://leetcode.cn/problems/design-twitter 的多语言解法👆 https://leetcode.cn/problems/di-yi-ge-zhi-chu-xian-yi-ci-de-zi-fu-lcof 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: char firstUniqChar(string s) { int count[26] = {0}; for (char c : s) { // 将字符转化成数字 count[c - 'a']++; } for (int i = 0; i < s.length(); i++) { char c = s[i]; if (count[c - 'a'] == 1) { // 第一个出现一次的字符 return c; } } return ' '; } }; ``` ```go // by chatGPT (go) func firstUniqChar(s string) byte { count := make([]int, 26) for _, c := range s { // 将字符转化成数字 count[c-'a']++ } for _, c := range s { if count[c-'a'] == 1 { // 第一个出现一次的字符 return byte(c) } } return ' ' } ``` ```java // by labuladong (java) class Solution { public char firstUniqChar(String s) { int[] count = new int[26]; for (char c : s.toCharArray()) { // 将字符转化成数字 count[c - 'a']++; } for (int i = 0; i < s.length(); i++) { char c = s.charAt(i); if (count[c - 'a'] == 1) { // 第一个出现一次的字符 return c; } } return ' '; } } ``` ```javascript // by chatGPT (javascript) var firstUniqChar = function(s) { var count = new Array(26).fill(0); for (var i = 0; i < s.length; i++) { // 将字符转化成数字 count[s.charCodeAt(i) - 97]++; } for (var i = 0; i < s.length; i++) { var c = s.charAt(i); if (count[c.charCodeAt(0) - 97] == 1) { // 第一个出现一次的字符 return c; } } return ' '; }; ``` ```python # by chatGPT (python) class Solution: def firstUniqChar(self, s: str) -> str: count = [0] * 26 for c in s: # 将字符转化成数字 count[ord(c) - ord('a')] += 1 for c in s: if count[ord(c) - ord('a')] == 1: # 第一个出现一次的字符 return c return ' ' ``` https://leetcode.cn/problems/di-yi-ge-zhi-chu-xian-yi-ci-de-zi-fu-lcof 的多语言解法👆 https://leetcode.cn/problems/diameter-of-binary-tree 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { int maxDiameter = 0; public: int diameterOfBinaryTree(TreeNode* root) { maxDepth(root); return maxDiameter; } int maxDepth(TreeNode* root) { if (root == nullptr) { return 0; } int leftMax = maxDepth(root->left); int rightMax = maxDepth(root->right); // 后序遍历位置顺便计算最大直径 maxDiameter = max(maxDiameter, leftMax + rightMax); return 1 + max(leftMax, rightMax); } }; // 这是一种简单粗暴,但是效率不高的解法 class BadSolution { public: int diameterOfBinaryTree(TreeNode* root) { if (root == nullptr) { return 0; } // 计算出左右子树的最大高度 int leftMax = maxDepth(root->left); int rightMax = maxDepth(root->right); // root 这个节点的直径 int res = leftMax + rightMax; // 递归遍历 root->left 和 root->right 两个子树 return max(res, max(diameterOfBinaryTree(root->left), diameterOfBinaryTree(root->right))); } int maxDepth(TreeNode* root) { if (root == nullptr) { return 0; } int leftMax = maxDepth(root->left); int rightMax = maxDepth(root->right); return 1 + max(leftMax, rightMax); } }; ``` ```go // by mario_huang (go) var maxDiameter int func diameterOfBinaryTree(root *TreeNode) int { // 记录最大直径的长度 maxDiameter = 0 maxDepth(root) return maxDiameter } func maxDepth(root *TreeNode) int { if root == nil { return 0 } leftMax := maxDepth(root.Left) rightMax := maxDepth(root.Right) // 后序位置,顺便计算最大直径 myDiameter := leftMax + rightMax maxDiameter = max(maxDiameter, myDiameter) return max(leftMax, rightMax) + 1 } // 这是一种简单粗暴,但是效率不高的解法 func diameterOfBinaryTree(root *TreeNode) int { if root == nil { return 0 } // 计算出左右子树的最大高度 maxDepth := func(root *TreeNode) int { if root == nil { return 0 } leftMax := maxDepth(root.Left) rightMax := maxDepth(root.Right) return 1 + max(leftMax, rightMax) } leftMax := maxDepth(root.Left) rightMax := maxDepth(root.Right) // root 这个节点的直径 res := leftMax + rightMax // 递归遍历 root.Left 和 root.Right 两个子树 return max(res, max(diameterOfBinaryTree(root.Left), diameterOfBinaryTree(root.Right))) } func max(a, b int) int { if a > b { return a } return b } ``` ```java // by labuladong (java) class Solution { int maxDiameter = 0; public int diameterOfBinaryTree(TreeNode root) { maxDepth(root); return maxDiameter; } int maxDepth(TreeNode root) { if (root == null) { return 0; } int leftMax = maxDepth(root.left); int rightMax = maxDepth(root.right); // 后序遍历位置顺便计算最大直径 maxDiameter = Math.max(maxDiameter, leftMax + rightMax); return 1 + Math.max(leftMax, rightMax); } } // 这是一种简单粗暴,但是效率不高的解法 class BadSolution { public int diameterOfBinaryTree(TreeNode root) { if (root == null) { return 0; } // 计算出左右子树的最大高度 int leftMax = maxDepth(root.left); int rightMax = maxDepth(root.right); // root 这个节点的直径 int res = leftMax + rightMax; // 递归遍历 root.left 和 root.right 两个子树 return Math.max(res, Math.max(diameterOfBinaryTree(root.left), diameterOfBinaryTree(root.right))); } int maxDepth(TreeNode root) { if (root == null) { return 0; } int leftMax = maxDepth(root.left); int rightMax = maxDepth(root.right); return 1 + Math.max(leftMax, rightMax); } } ``` ```javascript // by chatGPT (javascript) var diameterOfBinaryTree = function(root) { let maxDiameter = 0; const maxDepth = (root) => { if (root == null) { return 0; } let leftMax = maxDepth(root.left); let rightMax = maxDepth(root.right); // 后序遍历位置顺便计算最大直径 maxDiameter = Math.max(maxDiameter, leftMax + rightMax); return 1 + Math.max(leftMax, rightMax); }; maxDepth(root); return maxDiameter; } // 这是一种简单粗暴,但是效率不高的解法 var BadSolution = function() {}; BadSolution.prototype.diameterOfBinaryTree = function(root) { if (root == null) { return 0; } // 计算出左右子树的最大高度 let leftMax = this.maxDepth(root.left); let rightMax = this.maxDepth(root.right); // root 这个节点的直径 let res = leftMax + rightMax; // 递归遍历 root.left 和 root.right 两个子树 return Math.max(res, Math.max(this.diameterOfBinaryTree(root.left), this.diameterOfBinaryTree(root.right))); }; BadSolution.prototype.maxDepth = function(root) { if (root == null) { return 0; } let leftMax = this.maxDepth(root.left); let rightMax = this.maxDepth(root.right); return 1 + Math.max(leftMax, rightMax); }; ``` ```python # by chatGPT (python) class Solution: def __init__(self): self.maxDiameter = 0 def diameterOfBinaryTree(self, root: TreeNode) -> int: self.maxDepth(root) return self.maxDiameter def maxDepth(self, root: TreeNode) -> int: if not root: return 0 leftMax = self.maxDepth(root.left) rightMax = self.maxDepth(root.right) # 后序遍历位置顺便计算最大直径 self.maxDiameter = max(self.maxDiameter, leftMax + rightMax) return 1 + max(leftMax, rightMax) # 这是一种简单粗暴,但是效率不高的解法 class BadSolution: def diameterOfBinaryTree(self, root: TreeNode) -> int: if not root: return 0 # 计算出左右子树的最大高度 leftMax = self.maxDepth(root.left) rightMax = self.maxDepth(root.right) # root 这个节点的直径 res = leftMax + rightMax # 递归遍历 root.left 和 root.right 两个子树 return max(res, max(self.diameterOfBinaryTree(root.left), self.diameterOfBinaryTree(root.right))) def maxDepth(self, root: TreeNode) -> int: if not root: return 0 leftMax = self.maxDepth(root.left) rightMax = self.maxDepth(root.right) return 1 + max(leftMax, rightMax) ``` https://leetcode.cn/problems/diameter-of-binary-tree 的多语言解法👆 https://leetcode.cn/problems/diao-zheng-shu-zu-shun-xu-shi-qi-shu-wei-yu-ou-shu-qian-mian-lcof 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: vector exchange(vector& nums) { // 维护 nums[0..slow) 都是奇数 int fast = 0, slow = 0; while (fast < nums.size()) { if (nums[fast] % 2 == 1) { // fast 遇到奇数,把 nums[fast] 换到 nums[slow] int temp = nums[slow]; nums[slow] = nums[fast]; nums[fast] = temp; slow++; } fast++; } return nums; } }; ``` ```go // by chatGPT (go) func exchange(nums []int) []int { // 维护 nums[0..slow) 都是奇数 fast, slow := 0, 0 for fast < len(nums) { if nums[fast]%2 == 1 { // fast 遇到奇数,把 nums[fast] 换到 nums[slow] temp := nums[slow] nums[slow] = nums[fast] nums[fast] = temp slow++ } fast++ } return nums } ``` ```java // by labuladong (java) class Solution { public int[] exchange(int[] nums) { // 维护 nums[0..slow) 都是奇数 int fast = 0, slow = 0; while (fast < nums.length) { if (nums[fast] % 2 == 1) { // fast 遇到奇数,把 nums[fast] 换到 nums[slow] int temp = nums[slow]; nums[slow] = nums[fast]; nums[fast] = temp; slow++; } fast++; } return nums; } } ``` ```javascript // by chatGPT (javascript) var exchange = function(nums) { // 维护 nums[0..slow) 都是奇数 let fast = 0, slow = 0; while (fast < nums.length) { if (nums[fast] % 2 === 1) { // fast 遇到奇数,把 nums[fast] 换到 nums[slow] let temp = nums[slow]; nums[slow] = nums[fast]; nums[fast] = temp; slow++; } fast++; } return nums; }; ``` ```python # by chatGPT (python) class Solution: def exchange(self, nums: List[int]) -> List[int]: # 维护 nums[0..slow) 都是奇数 fast, slow = 0, 0 while fast < len(nums): if nums[fast] % 2 == 1: # fast 遇到奇数,把 nums[fast] 换到 nums[slow] nums[slow], nums[fast] = nums[fast], nums[slow] slow += 1 fast += 1 return nums ``` https://leetcode.cn/problems/diao-zheng-shu-zu-shun-xu-shi-qi-shu-wei-yu-ou-shu-qian-mian-lcof 的多语言解法👆 https://leetcode.cn/problems/different-ways-to-add-parentheses 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { private: unordered_map> memo; public: vector diffWaysToCompute(string input) { // 避免重复计算 if (memo.count(input)) { return memo[input]; } vector res; for (int i = 0; i < input.length(); i++) { char c = input[i]; // 扫描算式 input 中的运算符 if (c == '-' || c == '*' || c == '+') { /******分******/ // 以运算符为中心,分割成两个字符串,分别递归计算 vector left = diffWaysToCompute(input.substr(0, i)); vector right = diffWaysToCompute(input.substr(i + 1)); /******治******/ // 通过子问题的结果,合成原问题的结果 for (int a : left) for (int b : right) if (c == '+') res.push_back(a + b); else if (c == '-') res.push_back(a - b); else if (c == '*') res.push_back(a * b); } } // base case // 如果 res 为空,说明算式是一个数字,没有运算符 if (res.empty()) { res.push_back(stoi(input)); } // 将结果添加进备忘录 memo[input] = res; return res; } }; ``` ```go // by chatGPT (go) func diffWaysToCompute(input string) []int { memo := make(map[string][]int) return compute(input, memo) } func compute(input string, memo map[string][]int) []int { // 避免重复计算 if val, ok := memo[input]; ok { return val } res := make([]int, 0) for i := 0; i < len(input); i++ { c := input[i] // 扫描算式 input 中的运算符 if c == '-' || c == '*' || c == '+' { /******分******/ // 以运算符为中心,分割成两个字符串,分别递归计算 left := compute(input[0:i], memo) right := compute(input[i+1:], memo) /******治******/ // 通过子问题的结果,合成原问题的结果 for _, a := range left { for _, b := range right { if c == '+' { res = append(res, a+b) } else if c == '-' { res = append(res, a-b) } else if c == '*' { res = append(res, a*b) } } } } } // base case // 如果 res 为空,说明算式是一个数字,没有运算符 if len(res) == 0 { n, _ := strconv.Atoi(input) res = append(res, n) } // 将结果添加进备忘录 memo[input] = res return res } ``` ```java // by labuladong (java) class Solution { HashMap> memo = new HashMap<>(); public List diffWaysToCompute(String input) { // 避免重复计算 if (memo.containsKey(input)) { return memo.get(input); } List res = new LinkedList<>(); for (int i = 0; i < input.length(); i++) { char c = input.charAt(i); // 扫描算式 input 中的运算符 if (c == '-' || c == '*' || c == '+') { /******分******/ // 以运算符为中心,分割成两个字符串,分别递归计算 List left = diffWaysToCompute(input.substring(0, i)); List right = diffWaysToCompute(input.substring(i + 1)); /******治******/ // 通过子问题的结果,合成原问题的结果 for (int a : left) for (int b : right) if (c == '+') res.add(a + b); else if (c == '-') res.add(a - b); else if (c == '*') res.add(a * b); } } // base case // 如果 res 为空,说明算式是一个数字,没有运算符 if (res.isEmpty()) { res.add(Integer.parseInt(input)); } // 将结果添加进备忘录 memo.put(input, res); return res; } } ``` ```javascript // by chatGPT (javascript) var diffWaysToCompute = function(input) { let memo = new Map(); const compute = (input) => { if (memo.has(input)) { return memo.get(input); } let res = []; for (let i = 0; i < input.length; i++) { let c = input.charAt(i); // 扫描算式 input 中的运算符 if (c == '-' || c == '*' || c == '+') { /******分******/ // 以运算符为中心,分割成两个字符串,分别递归计算 let left = compute(input.substring(0, i)); let right = compute(input.substring(i + 1)); /******治******/ // 通过子问题的结果,合成原问题的结果 for (let a of left) { for (let b of right) { if (c == '+') { res.push(a + b); } else if (c == '-') { res.push(a - b); } else if (c == '*') { res.push(a * b); } } } } } // base case // 如果 res 为空,说明算式是一个数字,没有运算符 if (res.length == 0) { res.push(parseInt(input)); } // 将结果添加进备忘录 memo.set(input, res); return res; }; return compute(input); }; ``` ```python # by chatGPT (python) class Solution: def __init__(self): self.memo = {} def diffWaysToCompute(self, input: str) -> List[int]: # 避免重复计算 if input in self.memo: return self.memo[input] res = [] for i in range(len(input)): c = input[i] # 扫描算式 input 中的运算符 if c in ['-', '*', '+']: # 以运算符为中心,分割成两个字符串,分别递归计算 left = self.diffWaysToCompute(input[:i]) right = self.diffWaysToCompute(input[i+1:]) # 通过子问题的结果,合成原问题的结果 for a in left: for b in right: if c == '+': res.append(a + b) elif c == '-': res.append(a - b) elif c == '*': res.append(a * b) # base case # 如果 res 为空,说明算式是一个数字,没有运算符 if not res: res.append(int(input)) # 将结果添加进备忘录 self.memo[input] = res return res ``` https://leetcode.cn/problems/different-ways-to-add-parentheses 的多语言解法👆 https://leetcode.cn/problems/dungeon-game 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int calculateMinimumHP(vector>& grid) { int m = grid.size(); int n = grid[0].size(); // 备忘录中都初始化为 -1 memo = vector>(m, vector(n, -1)); return dp(grid, 0, 0); } // 备忘录,消除重叠子问题 vector> memo; /* 定义:从 (i, j) 到达右下角,需要的初始血量至少是多少 */ int dp(vector>& grid, int i, int j) { int m = grid.size(); int n = grid[0].size(); // base case if (i == m - 1 && j == n - 1) { return grid[i][j] >= 0 ? 1 : -grid[i][j] + 1; } if (i == m || j == n) { return INT_MAX; } // 避免重复计算 if (memo[i][j] != -1) { return memo[i][j]; } // 状态转移逻辑 int res = min( dp(grid, i, j + 1), dp(grid, i + 1, j) ) - grid[i][j]; // 骑士的生命值至少为 1 memo[i][j] = res <= 0 ? 1 : res; return memo[i][j]; } }; ``` ```go // by chatGPT (go) import "math" func calculateMinimumHP(grid [][]int) int { m := len(grid) n := len(grid[0]) // 备忘录中都初始化为 -1 memo := make([][]int, m) for i := range memo { memo[i] = make([]int, n) for j := range memo[i] { memo[i][j] = -1 } } return dp(grid, 0, 0, memo) } /* 定义:从 (i, j) 到达右下角,需要的初始血量至少是多少 */ func dp(grid [][]int, i, j int, memo [][]int) int { m := len(grid) n := len(grid[0]) // base case if i == m-1 && j == n-1 { if grid[i][j] >= 0 { return 1 } else { return -grid[i][j] + 1 } } if i == m || j == n { return math.MaxInt32 } // 避免重复计算 if memo[i][j] != -1 { return memo[i][j] } // 状态转移逻辑 res := int(math.Min( float64(dp(grid, i, j+1, memo)), float64(dp(grid, i+1, j, memo)), )) - grid[i][j] // 骑士的生命值至少为 1 if res <= 0 { memo[i][j] = 1 } else { memo[i][j] = res } return memo[i][j] } ``` ```java // by labuladong (java) class Solution { public int calculateMinimumHP(int[][] grid) { int m = grid.length; int n = grid[0].length; // 备忘录中都初始化为 -1 memo = new int[m][n]; for (int[] row : memo) { Arrays.fill(row, -1); } return dp(grid, 0, 0); } // 备忘录,消除重叠子问题 int[][] memo; /* 定义:从 (i, j) 到达右下角,需要的初始血量至少是多少 */ int dp(int[][] grid, int i, int j) { int m = grid.length; int n = grid[0].length; // base case if (i == m - 1 && j == n - 1) { return grid[i][j] >= 0 ? 1 : -grid[i][j] + 1; } if (i == m || j == n) { return Integer.MAX_VALUE; } // 避免重复计算 if (memo[i][j] != -1) { return memo[i][j]; } // 状态转移逻辑 int res = Math.min( dp(grid, i, j + 1), dp(grid, i + 1, j) ) - grid[i][j]; // 骑士的生命值至少为 1 memo[i][j] = res <= 0 ? 1 : res; return memo[i][j]; } } ``` ```javascript // by chatGPT (javascript) var calculateMinimumHP = function(grid) { const m = grid.length; const n = grid[0].length; // 备忘录中都初始化为 -1 const memo = new Array(m).fill().map(() => new Array(n).fill(-1)); function dp(i, j) { // base case if (i === m - 1 && j === n - 1) { return grid[i][j] >= 0 ? 1 : -grid[i][j] + 1; } if (i === m || j === n) { return Number.MAX_VALUE; } // 避免重复计算 if (memo[i][j] !== -1) { return memo[i][j]; } // 状态转移逻辑 const res = Math.min(dp(i, j + 1), dp(i + 1, j)) - grid[i][j]; // 骑士的生命值至少为 1 memo[i][j] = res <= 0 ? 1 : res; return memo[i][j]; } return dp(0, 0); }; ``` ```python # by chatGPT (python) class Solution: def calculateMinimumHP(self, grid: List[List[int]]) -> int: m, n = len(grid), len(grid[0]) # 备忘录中都初始化为 -1 memo = [[-1 for _ in range(n)] for _ in range(m)] return self.dp(grid, 0, 0, memo) def dp(self, grid: List[List[int]], i: int, j: int, memo: List[List[int]]) -> int: m, n = len(grid), len(grid[0]) # base case if i == m - 1 and j == n - 1: return 1 if grid[i][j] >= 0 else -grid[i][j] + 1 if i == m or j == n: return float('inf') # 避免重复计算 if memo[i][j] != -1: return memo[i][j] # 状态转移逻辑 res = min( self.dp(grid, i, j + 1, memo), self.dp(grid, i + 1, j, memo) ) - grid[i][j] # 骑士的生命值至少为 1 memo[i][j] = 1 if res <= 0 else res return memo[i][j] ``` https://leetcode.cn/problems/dungeon-game 的多语言解法👆 https://leetcode.cn/problems/edit-distance 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int minDistance(string word1, string word2) { int m = word1.length(), n = word2.length(); vector> dp(m + 1, vector(n + 1)); // base case for (int i = 1; i <= m; i++) dp[i][0] = i; for (int j = 1; j <= n; j++) dp[0][j] = j; // 自底向上求解 for (int i = 1; i <= m; i++) { for (int j = 1; j <= n; j++) { if (word1[i - 1] == word2[j - 1]) dp[i][j] = dp[i - 1][j - 1]; else dp[i][j] = min( dp[i - 1][j] + 1, /** ![](../pictures/editDistance/delete.gif) */ dp[i][j - 1] + 1, /** ![](../pictures/editDistance/insert.gif) */ dp[i - 1][j - 1] + 1 /** ![](../pictures/editDistance/replace.gif) */ ); } } // 储存着整个 s1 和 s2 的最小编辑距离 return dp[m][n]; } int min(int a, int b, int c) { return std::min(a, std::min(b, c)); } }; ``` ```go // by chatGPT (go) func minDistance(s1 string, s2 string) int { m, n := len(s1), len(s2) dp := make([][]int, m+1) for i := range dp { dp[i] = make([]int, n+1) } // base case for i := 1; i <= m; i++ { dp[i][0] = i } for j := 1; j <= n; j++ { dp[0][j] = j } // 自底向上求解 for i := 1; i <= m; i++ { for j := 1; j <= n; j++ { if s1[i-1] == s2[j-1] { dp[i][j] = dp[i-1][j-1] } else { dp[i][j] = min( dp[i-1][j]+1, /** ![](../pictures/editDistance/delete.gif) */ dp[i][j-1]+1, /** ![](../pictures/editDistance/insert.gif) */ dp[i-1][j-1]+1, /** ![](../pictures/editDistance/replace.gif) */ ) } } } // 储存着整个 s1 和 s2 的最小编辑距离 return dp[m][n] } func min(a, b, c int) int { if a < b { if a < c { return a } return c } else if b < c { return b } return c } ``` ```java // by labuladong (java) class Solution { public int minDistance(String s1, String s2) { int m = s1.length(), n = s2.length(); int[][] dp = new int[m + 1][n + 1]; // base case for (int i = 1; i <= m; i++) dp[i][0] = i; for (int j = 1; j <= n; j++) dp[0][j] = j; // 自底向上求解 for (int i = 1; i <= m; i++) for (int j = 1; j <= n; j++) if (s1.charAt(i - 1) == s2.charAt(j - 1)) dp[i][j] = dp[i - 1][j - 1]; else dp[i][j] = min( dp[i - 1][j] + 1, /** ![](../pictures/editDistance/delete.gif) */ dp[i][j - 1] + 1, /** ![](../pictures/editDistance/insert.gif) */ dp[i - 1][j - 1] + 1 /** ![](../pictures/editDistance/replace.gif) */ ); // 储存着整个 s1 和 s2 的最小编辑距离 return dp[m][n]; } int min(int a, int b, int c) { return Math.min(a, Math.min(b, c)); } } ``` ```javascript // by chatGPT (javascript) var minDistance = function(s1, s2) { let m = s1.length, n = s2.length; let dp = new Array(m + 1).fill().map(() => new Array(n + 1).fill(0)); // base case for (let i = 1; i <= m; i++) dp[i][0] = i; for (let j = 1; j <= n; j++) dp[0][j] = j; // 自底向上求解 for (let i = 1; i <= m; i++) for (let j = 1; j <= n; j++) if (s1.charAt(i - 1) == s2.charAt(j - 1)) dp[i][j] = dp[i - 1][j - 1]; else dp[i][j] = Math.min( dp[i - 1][j] + 1, /** ![](../pictures/editDistance/delete.gif) */ dp[i][j - 1] + 1, /** ![](../pictures/editDistance/insert.gif) */ dp[i - 1][j - 1] + 1 /** ![](../pictures/editDistance/replace.gif) */ ); // 储存着整个 s1 和 s2 的最小编辑距离 return dp[m][n]; } function min(a, b, c) { return Math.min(a, Math.min(b, c)); } ``` ```python # by chatGPT (python) class Solution: def minDistance(self, word1: str, word2: str) -> int: m, n = len(word1), len(word2) dp = [[0] * (n+1) for _ in range(m+1)] # base case for i in range(1, m+1): dp[i][0] = i for j in range(1, n+1): dp[0][j] = j # 自底向上求解 for i in range(1, m+1): for j in range(1, n+1): if word1[i-1] == word2[j-1]: dp[i][j] = dp[i-1][j-1] else: dp[i][j] = min( dp[i-1][j] + 1, # insert dp[i][j-1] + 1, # delete dp[i-1][j-1] + 1 # replace ) # 储存着整个 word1 和 word2 的最小编辑距离 return dp[m][n] ``` https://leetcode.cn/problems/edit-distance 的多语言解法👆 https://leetcode.cn/problems/er-cha-shu-de-jing-xiang-lcof 的多语言解法👇 ```cpp // by chatGPT (cpp) // 「遍历」的思路 class Solution { public: // 二叉树遍历函数 void traverse(TreeNode* root) { if (root == nullptr) { return; } /**** 前序位置 ****/ // 每一个节点需要做的事就是交换它的左右子节点 TreeNode* tmp = root->left; root->left = root->right; root->right = tmp; // 遍历框架,去遍历左右子树的节点 traverse(root->left); traverse(root->right); } // 主函数 TreeNode* invertTree(TreeNode* root) { // 遍历二叉树,交换每个节点的子节点 traverse(root); return root; } }; // 「分解问题」的思路 class Solution2 { public: // 定义:将以 root 为根的这棵二叉树翻转,返回翻转后的二叉树的根节点 TreeNode* invertTree(TreeNode* root) { if (root == nullptr) { return nullptr; } // 利用函数定义,先翻转左右子树 TreeNode* left = invertTree(root->left); TreeNode* right = invertTree(root->right); // 然后交换左右子节点 root->left = right; root->right = left; // 和定义逻辑自恰:以 root 为根的这棵二叉树已经被翻转,返回 root return root; } }; ``` ```go // by chatGPT (go) /** * Definition for a binary tree node. * type TreeNode struct { * Val int * Left *TreeNode * Right *TreeNode * } */ //「遍历」的思路 func invertTree(root *TreeNode) *TreeNode { // 遍历二叉树,交换每个节点的子节点 traverse(root) return root } // 二叉树遍历函数 func traverse(root *TreeNode) { if root == nil { return } //每一个节点需要做的事就是交换它的左右子节点 tmp := root.Left root.Left = root.Right root.Right = tmp // 遍历框架,去遍历左右子树的节点 traverse(root.Left) traverse(root.Right) } //「分解问题」的思路 func invertTree(root *TreeNode) *TreeNode { if root == nil { return nil } // 利用函数定义,先翻转左右子树 left := invertTree(root.Left) right := invertTree(root.Right) // 然后交换左右子节点 root.Left = right root.Right = left // 和定义逻辑自恰:以 root 为根的这棵二叉树已经被翻转,返回 root return root } ``` ```java // by labuladong (java) // 「遍历」的思路 class Solution { // 主函数 public TreeNode invertTree(TreeNode root) { // 遍历二叉树,交换每个节点的子节点 traverse(root); return root; } // 二叉树遍历函数 void traverse(TreeNode root) { if (root == null) { return; } /**** 前序位置 ****/ // 每一个节点需要做的事就是交换它的左右子节点 TreeNode tmp = root.left; root.left = root.right; root.right = tmp; // 遍历框架,去遍历左右子树的节点 traverse(root.left); traverse(root.right); } } // 「分解问题」的思路 class Solution2 { // 定义:将以 root 为根的这棵二叉树翻转,返回翻转后的二叉树的根节点 TreeNode invertTree(TreeNode root) { if (root == null) { return null; } // 利用函数定义,先翻转左右子树 TreeNode left = invertTree(root.left); TreeNode right = invertTree(root.right); // 然后交换左右子节点 root.left = right; root.right = left; // 和定义逻辑自恰:以 root 为根的这棵二叉树已经被翻转,返回 root return root; } } ``` ```javascript // by chatGPT (javascript) // 「遍历」的思路 var Solution = function() {}; // 主函数 Solution.prototype.invertTree = function(root) { // 遍历二叉树,交换每个节点的子节点 this.traverse(root); return root; }; // 二叉树遍历函数 Solution.prototype.traverse = function(root) { if (root == null) { return; } /**** 前序位置 ****/ // 每一个节点需要做的事就是交换它的左右子节点 var tmp = root.left; root.left = root.right; root.right = tmp; // 遍历框架,去遍历左右子树的节点 this.traverse(root.left); this.traverse(root.right); }; // 「分解问题」的思路 var Solution2 = function() {}; // 定义:将以 root 为根的这棵二叉树翻转,返回翻转后的二叉树的根节点 Solution2.prototype.invertTree = function(root) { if (root == null) { return null; } // 利用函数定义,先翻转左右子树 var left = this.invertTree(root.left); var right = this.invertTree(root.right); // 然后交换左右子节点 root.left = right; root.right = left; // 和定义逻辑自恰:以 root 为根的这棵二叉树已经被翻转,返回 root return root; }; ``` ```python # by chatGPT (python) # 「遍历」的思路 class Solution: # 主函数 def invertTree(self, root: TreeNode) -> TreeNode: # 遍历二叉树,交换每个节点的子节点 self.traverse(root) return root # 二叉树遍历函数 def traverse(self, root: TreeNode) -> None: if root is None: return # 前序位置 # 每一个节点需要做的事就是交换它的左右子节点 tmp = root.left root.left = root.right root.right = tmp # 遍历框架,去遍历左右子树的节点 self.traverse(root.left) self.traverse(root.right) # 「分解问题」的思路 class Solution2: # 定义:将以 root 为根的这棵二叉树翻转,返回翻转后的二叉树的根节点 def invertTree(self, root: TreeNode) -> TreeNode: if root is None: return None # 利用函数定义,先翻转左右子树 left = self.invertTree(root.left) right = self.invertTree(root.right) # 然后交换左右子节点 root.left = right root.right = left # 和定义逻辑自恰:以 root 为根的这棵二叉树已经被翻转,返回 root return root ``` https://leetcode.cn/problems/er-cha-shu-de-jing-xiang-lcof 的多语言解法👆 https://leetcode.cn/problems/er-cha-shu-de-shen-du-lcof 的多语言解法👇 ```cpp // by chatGPT (cpp) /***** 解法一,回溯算法思路 *****/ class Solution { int depth = 0; int res = 0; public: int maxDepth(TreeNode* root) { traverse(root); return res; } // 遍历二叉树 void traverse(TreeNode* root) { if (root == nullptr) { return; } // 前序遍历位置 depth++; // 遍历的过程中记录最大深度 res = std::max(res, depth); traverse(root->left); traverse(root->right); // 后序遍历位置 depth--; } }; /***** 解法二,动态规划思路 *****/ class Solution2 { public: // 定义:输入一个节点,返回以该节点为根的二叉树的最大深度 int maxDepth(TreeNode* root) { if (root == nullptr) { return 0; } int leftMax = maxDepth(root->left); int rightMax = maxDepth(root->right); // 根据左右子树的最大深度推出原二叉树的最大深度 return 1 + std::max(leftMax, rightMax); } }; ``` ```go // by chatGPT (go) /** * Definition for a binary tree node. * type TreeNode struct { * Val int * Left *TreeNode * Right *TreeNode * } */ // 解法一,回溯算法思路 func maxDepth(root *TreeNode) int { depth := 0 res := 0 var traverse func(root *TreeNode) traverse = func(root *TreeNode) { if root == nil { return } depth++ res = Max(res, depth) traverse(root.Left) traverse(root.Right) depth-- } traverse(root) return res } // 解法二,动态规划思路 func maxDepth2(root *TreeNode) int { if root == nil { return 0 } leftMax := maxDepth(root.Left) rightMax := maxDepth(root.Right) return 1 + Max(leftMax, rightMax) } func Max(a, b int) int { if a > b { return a } return b } ``` ```java // by labuladong (java) /***** 解法一,回溯算法思路 *****/ class Solution { int depth = 0; int res = 0; public int maxDepth(TreeNode root) { traverse(root); return res; } // 遍历二叉树 void traverse(TreeNode root) { if (root == null) { return; } // 前序遍历位置 depth++; // 遍历的过程中记录最大深度 res = Math.max(res, depth); traverse(root.left); traverse(root.right); // 后序遍历位置 depth--; } } /***** 解法二,动态规划思路 *****/ class Solution2 { // 定义:输入一个节点,返回以该节点为根的二叉树的最大深度 public int maxDepth(TreeNode root) { if (root == null) { return 0; } int leftMax = maxDepth(root.left); int rightMax = maxDepth(root.right); // 根据左右子树的最大深度推出原二叉树的最大深度 return 1 + Math.max(leftMax, rightMax); } } ``` ```javascript // by chatGPT (javascript) // 解法一,回溯算法思路 var maxDepth = function(root) { let depth = 0; let res = 0; // 遍历二叉树 function traverse(root) { if (root === null) { return; } // 前序遍历位置 depth++; // 遍历的过程中记录最大深度 res = Math.max(res, depth); traverse(root.left); traverse(root.right); // 后序遍历位置 depth--; } traverse(root); return res; }; // 解法二,动态规划思路 var maxDepth = function(root) { // 定义:输入一个节点,返回以该节点为根的二叉树的最大深度 if (root === null) { return 0; } let leftMax = maxDepth(root.left); let rightMax = maxDepth(root.right); // 根据左右子树的最大深度推出原二叉树的最大深度 return 1 + Math.max(leftMax, rightMax); }; ``` ```python # by chatGPT (python) # 解法一,回溯算法思路 class Solution: def maxDepth(self, root: TreeNode) -> int: depth, res = 0, 0 self.traverse(root, depth, res) return res # 遍历二叉树 def traverse(self, root: TreeNode, depth: int, res: int) -> None: if not root: return # 前序遍历位置 depth += 1 # 遍历的过程中记录最大深度 res = max(res, depth) self.traverse(root.left, depth, res) self.traverse(root.right, depth, res) # 后序遍历位置 depth -= 1 # 解法二,动态规划思路 class Solution2: # 定义:输入一个节点,返回以该节点为根的二叉树的最大深度 def maxDepth(self, root: TreeNode) -> int: if not root: return 0 leftMax = self.maxDepth(root.left) rightMax = self.maxDepth(root.right) # 根据左右子树的最大深度推出原二叉树的最大深度 return 1 + max(leftMax, rightMax) ``` https://leetcode.cn/problems/er-cha-shu-de-shen-du-lcof 的多语言解法👆 https://leetcode.cn/problems/er-cha-shu-de-zui-jin-gong-gong-zu-xian-lcof 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) { // base case if (root == nullptr) return nullptr; if (root == p || root == q) return root; TreeNode* left = lowestCommonAncestor(root->left, p, q); TreeNode* right = lowestCommonAncestor(root->right, p, q); // 情况 1 if (left != nullptr && right != nullptr) { return root; } // 情况 2 if (left == nullptr && right == nullptr) { return nullptr; } // 情况 3 return left == nullptr ? right : left; } }; ``` ```go // by chatGPT (go) func lowestCommonAncestor(root, p, q *TreeNode) *TreeNode { // base case if root == nil { return nil } if root == p || root == q { return root } left := lowestCommonAncestor(root.Left, p, q) right := lowestCommonAncestor(root.Right, p, q) // 情况 1 if left != nil && right != nil { return root } // 情况 2 if left == nil && right == nil { return nil } // 情况 3 if left == nil { return right } else { return left } } ``` ```java // by labuladong (java) class Solution { public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { // base case 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); // 情况 1 if (left != null && right != null) { return root; } // 情况 2 if (left == null && right == null) { return null; } // 情况 3 return left == null ? right : left; } } ``` ```javascript // by chatGPT (javascript) /** * @param {TreeNode} root * @param {TreeNode} p * @param {TreeNode} q * @return {TreeNode} */ var lowestCommonAncestor = function(root, p, q) { // base case if (root == null) return null; if (root == p || root == q) return root; var left = lowestCommonAncestor(root.left, p, q); var right = lowestCommonAncestor(root.right, p, q); // 情况 1 if (left != null && right != null) { return root; } // 情况 2 if (left == null && right == null) { return null; } // 情况 3 return left == null ? right : left; }; ``` ```python # by chatGPT (python) class Solution: def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode': # base case if not root: return None if root == p or root == q: return root left = self.lowestCommonAncestor(root.left, p, q) right = self.lowestCommonAncestor(root.right, p, q) # 情况 1 if left and right: return root # 情况 2 if not left and not right: return None # 情况 3 return right if not left else left ``` https://leetcode.cn/problems/er-cha-shu-de-zui-jin-gong-gong-zu-xian-lcof 的多语言解法👆 https://leetcode.cn/problems/er-cha-sou-suo-shu-de-di-kda-jie-dian-lcof 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int kthLargest(TreeNode* root, int k) { // 利用 BST 的中序遍历特性 traverse(root, k); return res; } // 记录结果 int res = 0; // 记录当前元素的排名 int rank = 0; void traverse(TreeNode* root, int k) { if (root == nullptr) { return; } traverse(root->right, k); /* 中序遍历代码位置 */ rank++; if (k == rank) { // 找到第 k 大的元素 res = root->val; return; } /*****************/ traverse(root->left, k); } }; ``` ```go // by chatGPT (go) func kthLargest(root *TreeNode, k int) int { // 利用 BST 的中序遍历特性 traverse := func(node *TreeNode, k int) {} res, rank := 0, 0 traverse = func(node *TreeNode, k int) { if node == nil { return } traverse(node.Right, k) /* 中序遍历代码位置 */ rank++ if k == rank { // 找到第 k 大的元素 res = node.Val return } /*****************/ traverse(node.Left, k) } traverse(root, k) return res } ``` ```java // by labuladong (java) class Solution { public int kthLargest(TreeNode root, int k) { // 利用 BST 的中序遍历特性 traverse(root, k); return res; } // 记录结果 int res = 0; // 记录当前元素的排名 int rank = 0; void traverse(TreeNode root, int k) { if (root == null) { return; } traverse(root.right, k); /* 中序遍历代码位置 */ rank++; if (k == rank) { // 找到第 k 大的元素 res = root.val; return; } /*****************/ traverse(root.left, k); } } ``` ```javascript // by chatGPT (javascript) var kthLargest = function(root, k) { let res = 0, rank = 0; const traverse = function(root, k) { if (root === null) { return; } traverse(root.right, k); /* 中序遍历代码位置 */ rank++; if (k === rank) { // 找到第 k 大的元素 res = root.val; return; } /*****************/ traverse(root.left, k); } traverse(root, k); return res; }; ``` ```python # by chatGPT (python) class Solution: def kthLargest(self, root: TreeNode, k: int) -> int: # 利用 BST 的中序遍历特性 self.rank = 0 self.traverse(root, k) return self.res # 记录结果 res = 0 # 记录当前元素的排名 rank = 0 def traverse(self, root: TreeNode, k: int) -> None: if not root: return self.traverse(root.right, k) """ 中序遍历代码位置 """ self.rank += 1 if self.rank == k: # 找到第 k 大的元素 self.res = root.val return /*****************/ self.traverse(root.left, k) ``` https://leetcode.cn/problems/er-cha-sou-suo-shu-de-di-kda-jie-dian-lcof 的多语言解法👆 https://leetcode.cn/problems/er-cha-sou-suo-shu-de-hou-xu-bian-li-xu-lie-lcof 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: bool verifyPostorder(vector& postorder) { return check(postorder, 0, postorder.size() - 1); } // 定义:检查 postorder[i..j] 是否是一个合法的 BST bool check(vector& postorder, int i, int j) { if (i >= j) { return true; } // 根节点的值是后序遍历结果的最后一个元素 int root = postorder[j]; // postorder[i..left) 是左子树,应该都小于 root int left = i; while (left < j && postorder[left] < root) { left++; } // postorder[left..j) 是右子树,应该都大于 root int right = left; while (right < j && postorder[right] > root) { right++; } if (right != j) { return false; } // 递归检查左子树 [i..left) 和右子树 [left..j) 也符合 BST 的性质 return check(postorder, i, left - 1) && check(postorder, left, j - 1); } }; ``` ```go // by chatGPT (go) func verifyPostorder(postorder []int) bool { return check(postorder, 0, len(postorder) - 1) } // 定义:检查 postorder[i..j] 是否是一个合法的 BST func check(postorder []int, i int, j int) bool { if i >= j { return true } // 根节点的值是后序遍历结果的最后一个元素 root := postorder[j] /** ![](../pictures/二叉树系列2/5.jpeg) */ // postorder[i..left) 是左子树,应该都小于 root left := i for left < j && postorder[left] < root { left++ } // postorder[left..j) 是右子树,应该都大于 root right := left for right < j && postorder[right] > root { right++ } if right != j { return false } // 递归检查左子树 [i..left) 和右子树 [left..j) 也符合 BST 的性质 return check(postorder, i, left - 1) && check(postorder, left, j - 1) } ``` ```java // by labuladong (java) class Solution { public boolean verifyPostorder(int[] postorder) { return check(postorder, 0, postorder.length - 1); } // 定义:检查 postorder[i..j] 是否是一个合法的 BST boolean check(int[] postorder, int i, int j) { if (i >= j) { return true; } // 根节点的值是后序遍历结果的最后一个元素 int root = postorder[j]; /** ![](../pictures/二叉树系列2/5.jpeg) */ // postorder[i..left) 是左子树,应该都小于 root int left = i; while (left < j && postorder[left] < root) { left++; } // postorder[left..j) 是右子树,应该都大于 root int right = left; while (right < j && postorder[right] > root) { right++; } if (right != j) { return false; } // 递归检查左子树 [i..left) 和右子树 [left..j) 也符合 BST 的性质 return check(postorder, i, left - 1) && check(postorder, left, j - 1); } } ``` ```javascript // by chatGPT (javascript) var verifyPostorder = function(postorder) { return check(postorder, 0, postorder.length - 1); }; // 定义:检查 postorder[i..j] 是否是一个合法的 BST function check(postorder, i, j) { if (i >= j) { return true; } // 根节点的值是后序遍历结果的最后一个元素 let root = postorder[j]; // postorder[i..left) 是左子树,应该都小于 root let left = i; while (left < j && postorder[left] < root) { left++; } // postorder[left..j) 是右子树,应该都大于 root let right = left; while (right < j && postorder[right] > root) { right++; } if (right != j) { return false; } // 递归检查左子树 [i..left) 和右子树 [left..j) 也符合 BST 的性质 return check(postorder, i, left - 1) && check(postorder, left, j - 1); } ``` ```python # by chatGPT (python) class Solution: def verifyPostorder(self, postorder: List[int]) -> bool: return self.check(postorder, 0, len(postorder)-1) def check(self, postorder: List[int], i: int, j: int) -> bool: if i >= j: return True # 根节点的值是后序遍历结果的最后一个元素 root = postorder[j] # postorder[i..left) 是左子树,应该都小于 root left = i while left < j and postorder[left] < root: left += 1 # postorder[left..j) 是右子树,应该都大于 root right = left while right < j and postorder[right] > root: right += 1 if right != j: return False # 递归检查左子树 [i..left) 和右子树 [left..j) 也符合 BST 的性质 return self.check(postorder, i, left-1) and self.check(postorder, left, j-1) ``` https://leetcode.cn/problems/er-cha-sou-suo-shu-de-hou-xu-bian-li-xu-lie-lcof 的多语言解法👆 https://leetcode.cn/problems/er-jin-zhi-zhong-1de-ge-shu-lcof 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int hammingWeight(uint32_t n) { int res = 0; while (n != 0) { n = n & (n - 1); res++; } return res; } }; ``` ```go // by chatGPT (go) func hammingWeight(num uint32) int { res := 0 for num != 0 { num &= (num - 1) res++ } return res } ``` ```java // by labuladong (java) public class Solution { // you need to treat n as an unsigned value public int hammingWeight(int n) { int res = 0; while (n != 0) { n = n & (n - 1); res++; } return res; } } ``` ```javascript // by chatGPT (javascript) /** * @param {number} n - a positive integer * @return {number} - the number of bits set to 1 in the binary representation of n */ var hammingWeight = function(n) { let res = 0; while (n !== 0) { n = n & (n - 1); res++; } return res; }; ``` ```python # by chatGPT (python) class Solution: def hammingWeight(self, n: int) -> int: """ :type n: int :rtype: int """ res = 0 while n != 0: n &= n - 1 res += 1 return res ``` https://leetcode.cn/problems/er-jin-zhi-zhong-1de-ge-shu-lcof 的多语言解法👆 https://leetcode.cn/problems/er-wei-shu-zu-zhong-de-cha-zhao-lcof 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: bool searchMatrix(vector>& matrix, int target) { int m = matrix.size(), n = matrix[0].size(); // 初始化在右上角 int i = 0, j = n - 1; while (i < m && j >= 0) { if (matrix[i][j] == target) { return true; } if (matrix[i][j] < target) { // 需要大一点,往下移动 i++; } else { // 需要小一点,往左移动 j--; } } // while 循环中没有找到,则 target 不存在 return false; } }; ``` ```go // by chatGPT (go) func searchMatrix(matrix [][]int, target int) bool { m, n := len(matrix), len(matrix[0]) // 初始化在右上角 i, j := 0, n-1 for i < m && j >= 0 { if matrix[i][j] == target { return true } if matrix[i][j] < target { // 需要大一点,往下移动 i++ } else { // 需要小一点,往左移动 j-- } } // while 循环中没有找到,则 target 不存在 return false } ``` ```java // by labuladong (java) class Solution { public boolean searchMatrix(int[][] matrix, int target) { int m = matrix.length, n = matrix[0].length; // 初始化在右上角 int i = 0, j = n - 1; while (i < m && j >= 0) { if (matrix[i][j] == target) { return true; } if (matrix[i][j] < target) { // 需要大一点,往下移动 i++; } else { // 需要小一点,往左移动 j--; } } // while 循环中没有找到,则 target 不存在 return false; } } ``` ```javascript // by chatGPT (javascript) var searchMatrix = function(matrix, target) { const m = matrix.length; const n = matrix[0].length; // 初始化在右上角 let i = 0; let j = n - 1; while (i < m && j >= 0) { if (matrix[i][j] === target) { return true; } if (matrix[i][j] < target) { // 需要大一点,往下移动 i++; } else { // 需要小一点,往左移动 j--; } } // while 循环中没有找到,则 target 不存在 return false; }; ``` ```python # by chatGPT (python) class Solution: def searchMatrix(self, matrix: List[List[int]], target: int) -> bool: m, n = len(matrix), len(matrix[0]) # 初始化在右上角 i, j = 0, n - 1 while i < m and j >= 0: if matrix[i][j] == target: return True if matrix[i][j] < target: # 需要大一点,往下移动 i += 1 else: # 需要小一点,往左移动 j -= 1 # while 循环中没有找到,则 target 不存在 return False ``` https://leetcode.cn/problems/er-wei-shu-zu-zhong-de-cha-zhao-lcof 的多语言解法👆 https://leetcode.cn/problems/evaluate-reverse-polish-notation 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int evalRPN(vector& tokens) { stack stk; for (const string& token : tokens) { if (string("+-*/").find(token) != string::npos) { // 是个运算符,从栈顶拿出两个数字进行运算,运算结果入栈 int a = stk.top(); stk.pop(); int b = stk.top(); stk.pop(); switch (token[0]) { case '+': stk.push(a + b); break; case '*': stk.push(a * b); break; // 对于减法和除法,顺序别搞反了,第二个数是被除(减)数 case '-': stk.push(b - a); break; case '/': stk.push(b / a); break; } } else { // 是个数字,直接入栈即可 stk.push(stoi(token)); } } // 最后栈中剩下一个数字,即是计算结果 return stk.top(); } }; ``` ```go // by chatGPT (go) func evalRPN(tokens []string) int { stk := make([]int, 0) for _, token := range tokens { if strings.Contains("+-*/", token) { // 是个运算符,从栈顶拿出两个数字进行运算,运算结果入栈 a, b := stk[len(stk)-1], stk[len(stk)-2] stk = stk[:len(stk)-2] switch token { case "+": stk = append(stk, b+a) case "*": stk = append(stk, b*a) // 对于减法和除法,顺序别搞反了,第二个数是被除(减)数 case "-": stk = append(stk, b-a) case "/": stk = append(stk, b/a) } } else { // 是个数字,直接入栈即可 num, _ := strconv.Atoi(token) stk = append(stk, num) } } // 最后栈中剩下一个数字,即是计算结果 return stk[0] } ``` ```java // by labuladong (java) class Solution { public int evalRPN(String[] tokens) { Stack stk = new Stack<>(); for (String token : tokens) { if ("+-*/".contains(token)) { // 是个运算符,从栈顶拿出两个数字进行运算,运算结果入栈 int a = stk.pop(), b = stk.pop(); switch (token) { case "+": stk.push(a + b); break; case "*": stk.push(a * b); break; // 对于减法和除法,顺序别搞反了,第二个数是被除(减)数 case "-": stk.push(b - a); break; case "/": stk.push(b / a); break; } } else { // 是个数字,直接入栈即可 stk.push(Integer.parseInt(token)); } } // 最后栈中剩下一个数字,即是计算结果 return stk.pop(); } } ``` ```javascript // by chatGPT (javascript) /** * @param {string[]} tokens * @return {number} */ var evalRPN = function(tokens) { const stk = []; for (let i = 0; i < tokens.length; i++) { const token = tokens[i]; if ("+-*/".indexOf(token) !== -1) { // 是个运算符,从栈顶拿出两个数字进行运算,运算结果入栈 const a = stk.pop(), b = stk.pop(); switch (token) { case "+": stk.push(a + b); break; case "*": stk.push(a * b); break; // 对于减法和除法,顺序别搞反了,第二个数是被除(减)数 case "-": stk.push(b - a); break; case "/": stk.push(b / a | 0); // 取整 break; } } else { // 是个数字,直接入栈即可 stk.push(parseInt(token)); } } // 最后栈中剩下一个数字,即是计算结果 return stk.pop(); }; ``` ```python # by chatGPT (python) class Solution: def evalRPN(self, tokens: List[str]) -> int: stk = [] for token in tokens: if token in "+-*/": # 是个运算符,从栈顶拿出两个数字进行运算,运算结果入栈 a, b = stk.pop(), stk.pop() if token == "+": stk.append(a + b) elif token == "*": stk.append(a * b) # 对于减法和除法,顺序别搞反了,第二个数是被除(减)数 elif token == "-": stk.append(b - a) else: stk.append(int(b / a)) else: # 是个数字,直接入栈即可 stk.append(int(token)) # 最后栈中剩下一个数字,即是计算结果 return stk.pop() ``` https://leetcode.cn/problems/evaluate-reverse-polish-notation 的多语言解法👆 https://leetcode.cn/problems/exam-room 的多语言解法👇 ```cpp // by chatGPT (cpp) class ExamRoom { private: // 将端点 p 映射到以 p 为左端点的线段 unordered_map> startMap; // 将端点 p 映射到以 p 为右端点的线段 unordered_map> endMap; // 根据线段长度从小到大存放所有线段 set> pq; int N; public: ExamRoom(int N) { this->N = N; startMap.clear(); endMap.clear(); pq.clear(); pq.insert({-1, N}); } int seat() { // 从有序集合拿出最长的线段 auto longest = prev(pq.end()); int x = longest->at(0); int y = longest->at(1); int seat; if (x == -1) { // 情况一 seat = 0; } else if (y == N) { // 情况二 seat = N - 1; } else { // 情况三 seat = (y - x) / 2 + x; } // 将最长的线段分成两段 auto left = vector{x, seat}; auto right = vector{seat, y}; removeInterval(*longest); addInterval(left); addInterval(right); return seat; } void leave(int p) { // 将 p 左右的线段找出来 auto right = startMap[p]; auto left = endMap[p]; // 合并两个线段成为一个线段 auto merged = vector{left[0], right[1]}; removeInterval(left); removeInterval(right); addInterval(merged); } /* 增加一个线段 */ void addInterval(vector intv) { pq.insert(intv); startMap[intv[0]] = intv; endMap[intv[1]] = intv; } /* 去除一个线段 */ void removeInterval(vector intv) { pq.erase(intv); startMap.erase(intv[0]); endMap.erase(intv[1]); } /* 计算一个线段的长度 */ int distance(vector intv) { int x = intv[0]; int y = intv[1]; if (x == -1) return y; if (y == N) return N - 1 - x; // 中点和端点之间的长度 return (y - x) / 2; } }; ``` ```go // by chatGPT (go) type ExamRoom struct { // 将端点 p 映射到以 p 为左端点的线段 startMap map[int][]int // 将端点 p 映射到以 p 为右端点的线段 endMap map[int][]int // 根据线段长度从小到大存放所有线段 pq TreeSet N int } func Constructor(N int) ExamRoom { s := make(map[int][]int) e := make(map[int][]int) pq := newSTreeSet(func(a, b []int) int { distA := distance(a) distB := distance(b) if distA == distB { return b[0] - a[0] } return distA - distB }) addInterval := func(intv []int) { pq.add(intv) s[intv[0]] = intv e[intv[1]] = intv } removeInterval := func(intv []int) { pq.remove(intv) delete(s, intv[0]) delete(e, intv[1]) } addInterval([]int{-1, N}) return ExamRoom{startMap: s, endMap: e, pq: pq, N: N} } func (this *ExamRoom) Seat() int { longest := this.pq.last() x, y := longest[0], longest[1] var seat int if x == -1 { // 情况一 seat = 0 } else if y == this.N { // 情况二 seat = this.N - 1 } else { // 情况三 seat = (y - x) / 2 + x } left := []int{x, seat} right := []int{seat, y} removeInterval := func() { this.pq.remove(longest) delete(this.startMap, longest[0]) delete(this.endMap, longest[1]) } removeInterval() addInterval(left) addInterval(right) return seat } func (this *ExamRoom) Leave(p int) { right, left := this.startMap[p], this.endMap[p] merged := []int{left[0], right[1]} removeInterval := func(intv []int) { this.pq.remove(intv) delete(this.startMap, intv[0]) delete(this.endMap, intv[1]) } removeInterval(left) removeInterval(right) addInterval(merged) } /* 计算一个线段的长度 */ func distance(intv []int) int { x, y := intv[0], intv[1] if x == -1 { return y } if y == N { return N - 1 - x } return (y - x) / 2 } type lambdaHeap struct { less func(i, j []int) bool pq [][]int } func (h lambdaHeap) Len() int { return len(h.pq) } func (h lambdaHeap) Less(i, j int) bool { return h.less(h.pq[i], h.pq[j]) } func (h lambdaHeap) Swap(i, j int) { h.pq[i], h.pq[j] = h.pq[j], h.pq[i] } func (h *lambdaHeap) Push(x interface{}) { h.pq = append(h.pq, x.([]int)) } func (h *lambdaHeap) Pop() interface{} { n := len(h.pq) x := h.pq[n-1] h.pq = h.pq[:n-1] return x } type TreeSet struct { less func(i, j []int) int heap lambdaHeap } func newSTreeSet(f func(i, j []int) int) TreeSet { h := lambdaHeap{less: func(i, j []int) bool { return f(i, j) < 0 }} return TreeSet{less: f, heap: h} } func (t TreeSet) resize(n int) { pq := lambdaHeap{less: t.heap.less, pq: make([][]int, n)} copy(pq.pq, t.heap.pq) t.heap = pq } func (t *TreeSet) add(x []int) { t.heap.Push(x) if len(t.heap.pq) > (cap(t.heap.pq) >> 1) { t.resize(len(t.heap.pq) << 1) } for i, j := len(t.heap.pq)-1, (len(t.heap.pq)-2)>>1; i > 0 && t.heap.Less(i, j); i, j = j, (j-1)>>1 { t.heap.Swap(i, j) } } func (t *TreeSet) find(x []int) int { l, r := 0, len(t.heap.pq)-1 less := t.less(x, t.heap.pq[0]) if less < 0 { return -1 } else if less == 0 { return 0 } for l < r { mid := (l + r) >> 1 if t.less(x, t.heap.pq[mid]) < 0 { r = mid } else { l = mid + 1 } } return l } func (t *TreeSet) remove(x []int) { i := t.find(x) if i == -1 { return } t.heap.Swap(i, len(t.heap.pq)-1) t.heap.pq = t.heap.pq[:len(t.heap.pq)-1] for i < len(t.heap.pq) { left, right := (i<<1)|1, (i+1)<<1 if left >= len(t.heap.pq) { break } k := left if right < len(t.heap.pq) && t.heap.Less(right, left) { k = right } if !t.heap.Less(k, i) { break } t.heap.Swap(i, k) i = k } } ``` ```java // by labuladong (java) class ExamRoom { // 将端点 p 映射到以 p 为左端点的线段 private Map startMap; // 将端点 p 映射到以 p 为右端点的线段 private Map endMap; // 根据线段长度从小到大存放所有线段 private TreeSet pq; private int N; public ExamRoom(int N) { this.N = N; startMap = new HashMap<>(); endMap = new HashMap<>(); pq = new TreeSet<>((a, b) -> { int distA = distance(a); int distB = distance(b); // 如果长度相同,就比较索引 if (distA == distB) return b[0] - a[0]; return distA - distB; }); // 在有序集合中先放一个虚拟线段 addInterval(new int[]{-1, N}); } public int seat() { // 从有序集合拿出最长的线段 int[] longest = pq.last(); int x = longest[0]; int y = longest[1]; int seat; if (x == -1) { // 情况一 seat = 0; } else if (y == N) { // 情况二 seat = N - 1; } else { // 情况三 seat = (y - x) / 2 + x; } // 将最长的线段分成两段 int[] left = new int[]{x, seat}; int[] right = new int[]{seat, y}; removeInterval(longest); addInterval(left); addInterval(right); return seat; } public void leave(int p) { // 将 p 左右的线段找出来 int[] right = startMap.get(p); int[] left = endMap.get(p); // 合并两个线段成为一个线段 int[] merged = new int[]{left[0], right[1]}; removeInterval(left); removeInterval(right); addInterval(merged); } /* 增加一个线段 */ private void addInterval(int[] intv) { pq.add(intv); startMap.put(intv[0], intv); endMap.put(intv[1], intv); } /* 去除一个线段 */ private void removeInterval(int[] intv) { pq.remove(intv); startMap.remove(intv[0]); endMap.remove(intv[1]); } /* 计算一个线段的长度 */ private int distance(int[] intv) { int x = intv[0]; int y = intv[1]; if (x == -1) return y; if (y == N) return N - 1 - x; // 中点和端点之间的长度 return (y - x) / 2; } } ``` ```javascript // by chatGPT (javascript) /** * @param {number} N */ var ExamRoom = function(N) { // 将端点 p 映射到以 p 为左端点的线段 this.startMap = new Map(); // 将端点 p 映射到以 p 为右端点的线段 this.endMap = new Map(); // 根据线段长度从小到大存放所有线段 this.pq = new TreeMap((a, b) => { let distA = this.distance(a); let distB = this.distance(b); // 如果长度相同,就比较索引 if (distA === distB) { return b[0] - a[0]; } return distA - distB; }); this.N = N; // 在有序集合中先放一个虚拟线段 this.addInterval([-1, N]); }; /** * @return {number} */ ExamRoom.prototype.seat = function() { // 从有序集合拿出最长的线段 let longest = this.pq.last(); let x = longest[0]; let y = longest[1]; let seat; if (x === -1) { // 情况一 seat = 0; } else if (y === this.N) { // 情况二 seat = this.N - 1; } else { // 情况三 seat = Math.floor((y - x) / 2) + x; } // 将最长的线段分成两段 let left = [x, seat]; let right = [seat, y]; this.removeInterval(longest); this.addInterval(left); this.addInterval(right); return seat; }; /** * @param {number} p * @return {void} */ ExamRoom.prototype.leave = function(p) { // 将 p 左右的线段找出来 let right = this.startMap.get(p); let left = this.endMap.get(p); // 合并两个线段成为一个线段 let merged = [left[0], right[1]]; this.removeInterval(left); this.removeInterval(right); this.addInterval(merged); }; /** * @param {number[]} intv * @return {void} */ ExamRoom.prototype.addInterval = function(intv) { this.pq.add(intv); this.startMap.set(intv[0], intv); this.endMap.set(intv[1], intv); }; /** * @param {number[]} intv * @return {void} */ ExamRoom.prototype.removeInterval = function(intv) { this.pq.remove(intv); this.startMap.delete(intv[0]); this.endMap.delete(intv[1]); }; /** * @param {number[]} intv * @return {number} */ ExamRoom.prototype.distance = function(intv) { let x = intv[0]; let y = intv[1]; if (x === -1) { return y; } if (y === this.N) { return this.N - 1 - x; } // 中点和端点之间的长度 return Math.floor((y - x) / 2); }; ``` ```python # by chatGPT (python) class ExamRoom: def __init__(self, N: int): self.N = N self.startMap = {} self.endMap = {} self.pq = [(self.distance([-1, self.N]), -1, self.N)] # 一个元组,第一个元素为长度,后两个为坐标 # 所有线段已按照长度排序,第一个元素为最小长度 # 表示最开始的虚拟线段 def seat(self) -> int: # 从优先队列拿出长度最大的线段 dist, x, y = heapq.heappop(self.pq) if x == -1: # 若最大线段为虚拟线段,直接返回 seat = 0 elif y == self.N: seat = self.N - 1 else: seat = (y - x) // 2 + x # 计算座位位置 # 把新线段分成两个加入优先队列 heapq.heappush(self.pq, (self.distance([x, seat]), x, seat)) heapq.heappush(self.pq, (self.distance([seat, y]), seat, y)) # 更新两个映射 self.startMap[seat] = [x, seat] self.endMap[seat] = [seat, y] return seat def leave(self, p: int) -> None: # 从两个映射找到左右线段,并将它们删除 left, right = self.startMap[p], self.endMap[p] del self.startMap[p], self.endMap[p] # 从优先队列中删除两个线段 self.pq.remove((self.distance(left), left[0], left[1])) self.pq.remove((self.distance(right), right[0], right[1])) # 合并左右线段成为一个新的线段 merged = [left[0], right[1]] heapq.heappush(self.pq, (self.distance(merged), merged[0], merged[1])) def distance(self, interval: List[int]) -> int: x, y = interval if x == -1: return y if y == self.N: return self.N - 1 - x return abs(y - x) // 2 ``` https://leetcode.cn/problems/exam-room 的多语言解法👆 https://leetcode.cn/problems/factorial-trailing-zeroes 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int trailingZeroes(int n) { int res = 0; long long divisor = 5; while (divisor <= n) { res += n / divisor; divisor *= 5; } return res; } }; ``` ```go // by chatGPT (go) func trailingZeroes(n int) int { res := 0 divisor := int64(5) for divisor <= int64(n) { res += n / int(divisor) divisor *= 5 } return res } ``` ```java // by labuladong (java) class Solution { public int trailingZeroes(int n) { int res = 0; long divisor = 5; while (divisor <= n) { res += n / divisor; divisor *= 5; } return res; } } ``` ```javascript // by chatGPT (javascript) /** * @param {number} n * @return {number} */ var trailingZeroes = function(n) { let res = 0; let divisor = 5; while (divisor <= n) { res += Math.floor(n / divisor); divisor *= 5; } return res; }; ``` ```python # by chatGPT (python) class Solution: def trailingZeroes(self, n: int) -> int: res = 0 divisor = 5 while divisor <= n: res += n // divisor divisor *= 5 return res ``` https://leetcode.cn/problems/factorial-trailing-zeroes 的多语言解法👆 https://leetcode.cn/problems/fan-zhuan-dan-ci-shun-xu-lcof 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: string reverseWords(string s) { // 先清洗一下数据,把多于的空格都删掉 string sb; for (int i = 0; i < s.length(); i++) { char c = s[i]; if (c != ' ') { // 单词中的字母/数字 sb += c; } else if (!sb.empty() && sb[sb.length() - 1] != ' ') { // 单词之间保留一个空格 sb += ' '; } } if (sb.empty()) { return ""; } // 末尾如果有空格,清除之 if (sb[sb.length() - 1] == ' ') { sb.erase(sb.length() - 1, 1); } // 清洗之后的字符串 char* chars = new char[sb.length() + 1]; strcpy(chars, sb.c_str()); int n = strlen(chars); // 进行单词的翻转,先整体翻转 reverse(chars, 0, n - 1); // 再把每个单词翻转 for (int i = 0; i < n; ) { for (int j = i; j < n; j++) { if (j + 1 == n || chars[j + 1] == ' ') { // chars[i..j] 是一个单词,翻转之 reverse(chars, i, j); // 把 i 置为下一个单词的首字母 i = j + 2; break; } } } // 最后得到题目想要的结果 string res(chars); delete[] chars; return res; } // 翻转 arr[i..j] void reverse(char* arr, int i, int j) { while (i < j) { char temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; i++; j--; } } }; ``` ```go // by chatGPT (go) func reverseWords(s string) string { var sb strings.Builder // 先清洗一下数据,把多余的空格都删掉 for i := 0; i < len(s); i++ { c := s[i] if c != ' ' { // 单词中的字母/数字 sb.WriteByte(c) } else if sb.Len() > 0 && sb.String()[sb.Len()-1] != ' ' { // 单词之间保留一个空格 sb.WriteByte(' ') } } // 末尾如果有空格,清除之 if sb.Len() > 0 && sb.String()[sb.Len()-1] == ' ' { sb.Truncate(sb.Len() - 1) } // 清洗之后的字符串 chars := []byte(sb.String()) n := len(chars) // 进行单词的翻转,先整体翻转 reverse := func(arr []byte, i int, j int) { for i < j { arr[i], arr[j] = arr[j], arr[i] i++ j-- } } reverse(chars, 0, n-1) // 再把每个单词翻转 for i := 0; i < n; { for j := i; j < n; j++ { if j+1 == n || chars[j+1] == ' ' { // chars[i..j] 是一个单词,翻转之 reverse(chars, i, j) // 把 i 置为下一个单词的首字母 i = j + 2 break } } } // 最后得到题目想要的结果 return string(chars) } ``` ```java // by labuladong (java) class Solution { public String reverseWords(String s) { StringBuilder sb = new StringBuilder(); // 先清洗一下数据,把多于的空格都删掉 for (int i = 0; i < s.length(); i++) { char c = s.charAt(i); if (c != ' ') { // 单词中的字母/数字 sb.append(c); } else if (!sb.isEmpty() && sb.charAt(sb.length() - 1) != ' ') { // 单词之间保留一个空格 sb.append(' '); } } if (sb.isEmpty()) { return ""; } // 末尾如果有空格,清除之 if (sb.charAt(sb.length() - 1) == ' ') { sb.deleteCharAt(sb.length() - 1); } // 清洗之后的字符串 char[] chars = sb.toString().toCharArray(); int n = chars.length; // 进行单词的翻转,先整体翻转 reverse(chars, 0, n - 1); // 再把每个单词翻转 for (int i = 0; i < n; ) { for (int j = i; j < n; j++) { if (j + 1 == n || chars[j + 1] == ' ') { // chars[i..j] 是一个单词,翻转之 reverse(chars, i, j); // 把 i 置为下一个单词的首字母 i = j + 2; break; } } } // 最后得到题目想要的结果 return new String(chars); } // 翻转 arr[i..j] void reverse(char[] arr, int i, int j) { while (i < j) { char temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; i++; j--; } } } ``` ```javascript // by chatGPT (javascript) /** * @param {string} s * @return {string} */ var reverseWords = function(s) { let sb = ""; // 先清洗一下数据,把多于的空格都删掉 for (let i = 0; i < s.length; i++) { let c = s.charAt(i); if (c !== ' ') { // 单词中的字母/数字 sb += c; } else if (sb.length > 0 && sb.charAt(sb.length - 1) !== ' ') { // 单词之间保留一个空格 sb += ' '; } } // 末尾如果有空格,清除之 if (sb.charAt(sb.length - 1) === ' ') { sb = sb.slice(0, sb.length - 1); } // 清洗之后的字符串 let chars = sb.split(''); let n = chars.length; // 进行单词的翻转,先整体翻转 reverse(chars, 0, n - 1); // 再把每个单词翻转 for (let i = 0; i < n; ) { for (let j = i; j < n; j++) { if (j + 1 === n || chars[j + 1] === ' ') { // chars[i..j] 是一个单词,翻转之 reverse(chars, i, j); // 把 i 置为下一个单词的首字母 i = j + 2; break; } } } // 最后得到题目想要的结果 return chars.join(''); }; // 翻转 arr[i..j] function reverse(arr, i, j) { while (i < j) { let temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; i++; j--; } } ``` ```python # by chatGPT (python) class Solution: def reverseWords(self, s: str) -> str: # 先清洗一下数据,把多于的空格都删掉 sb = [] for c in s: if c != ' ': # 单词中的字母/数字 sb.append(c) elif sb and sb[-1] != ' ': # 单词之间保留一个空格 sb.append(' ') if not sb: return "" # 末尾如果有空格,清除之 if sb[-1] == ' ': sb.pop() # 清洗之后的字符串 chars = ''.join(sb) chars = list(chars) n = len(chars) # 进行单词的翻转,先整体翻转 self.reverse(chars, 0, n - 1) # 再把每个单词翻转 i = 0 while i < n: j = i while j < n: if j + 1 == n or chars[j + 1] == ' ': # chars[i..j] 是一个单词,翻转之 self.reverse(chars, i, j) # 把 i 置为下一个单词的首字母 i = j + 2 break j += 1 # 最后得到题目想要的结果 return ''.join(chars) # 翻转 arr[i..j] def reverse(self, arr, i, j): while i < j: arr[i], arr[j] = arr[j], arr[i] i += 1 j -= 1 ``` https://leetcode.cn/problems/fan-zhuan-dan-ci-shun-xu-lcof 的多语言解法👆 https://leetcode.cn/problems/fan-zhuan-lian-biao-lcof 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: ListNode* reverseList(ListNode* head) { // 如果链表为空或只有一个节点,则直接返回该链表 if (head == nullptr || head->next == nullptr) { return head; } ListNode* last = nullptr; // 开始迭代翻转节点 while (head != nullptr) { // 先保存当前节点的后继节点 ListNode* next = head->next; // 将当前节点的指针指向前一个节点 head->next = last; // 更新前一个节点为当前节点 last = head; // 更新当前节点为后继节点 head = next; } // 翻转链表完成,返回头节点 return last; } }; ``` ```go // by chatGPT (go) /** * Definition for singly-linked list. * type ListNode struct { * Val int * Next *ListNode * } */ func reverseList(head *ListNode) *ListNode { // 如果链表为空或者链表只有一个节点,直接返回head if head == nil || head.Next == nil { return head } // 递归处理下一个节点 last := reverseList(head.Next) // 对于当前节点: // 原来指向下一个节点的指针反转指向上一个节点 head.Next.Next = head // 原来指向下一个节点的指针指向nil head.Next = nil // 返回链表的最后一个节点,即反转后链表的头节点 return last } ``` ```java // by labuladong (java) class Solution { public ListNode reverseList(ListNode head) { if (head == null || head.next == null) { return head; } ListNode last = reverseList(head.next); /** ![](../pictures/反转链表/3.jpg) */ head.next.next = head; /** ![](../pictures/反转链表/4.jpg) */ head.next = null; /** ![](../pictures/反转链表/5.jpg) */ return last; } } ``` ```javascript // by chatGPT (javascript) /** * @param {ListNode} head * @return {ListNode} */ var reverseList = function(head) { if (head === null || head.next === null) { return head; } var last = reverseList(head.next); /** ![](../pictures/反转链表/3.jpg) */ head.next.next = head; /** ![](../pictures/反转链表/4.jpg) */ head.next = null; /** ![](../pictures/反转链表/5.jpg) */ return last; }; ``` ```python # by chatGPT (python) class Solution: def reverseList(self, head: ListNode) -> ListNode: # 如果链表为空或链表只有一个元素,则直接返回head if head == None or head.next==None: return head # 递归反转链表后面的部分 last = self.reverseList(head.next) """ 反转之前的链表 head --> 3 --> 4 --> 5 --> null last --> 5 --> null """ # 将链表的下一个节点的后继指针指向当前节点 head.next.next = head """ 反转之后的链表 last --> 5 --> 4 --> 3 --> null ↑ head --> 4 ---┘ """ # 将当前节点的后继指针指向null head.next = None """ 反转之后的链表 last --> 5 --> 4 --> 3 --> null head --> null """ # 返回反转后的链表 return last ``` https://leetcode.cn/problems/fan-zhuan-lian-biao-lcof 的多语言解法👆 https://leetcode.cn/problems/fei-bo-na-qi-shu-lie-lcof 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int fib(int n) { if (n == 0 || n == 1) { // base case return n; } int Q = 1000000007; // 分别代表 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 % Q + dp_i_2 % Q) % Q; dp_i_2 = dp_i_1; dp_i_1 = dp_i; } return dp_i_1; } }; ``` ```go // by chatGPT (go) func fib(n int) int { if n == 0 || n == 1 { // base case return n } Q := 1000000007 // 分别代表 dp[i - 1] 和 dp[i - 2] dp_i_1, dp_i_2 := 1, 0 for i := 2; i <= n; i++ { // dp[i] = dp[i - 1] + dp[i - 2]; dp_i := (dp_i_1 % Q + dp_i_2 % Q) % Q dp_i_2 = dp_i_1 dp_i_1 = dp_i } return dp_i_1 } ``` ```java // by labuladong (java) class Solution { public int fib(int n) { if (n == 0 || n == 1) { // base case return n; } int Q = 1000000007; // 分别代表 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 % Q + dp_i_2 % Q) % Q; dp_i_2 = dp_i_1; dp_i_1 = dp_i; } return dp_i_1; } } ``` ```javascript // by chatGPT (javascript) /** * @param {number} n * @return {number} */ var fib = function(n) { if (n === 0 || n === 1) { // base case return n; } const Q = 1000000007; // 分别代表 dp[i - 1] 和 dp[i - 2] let dp_i_1 = 1, dp_i_2 = 0; for (let i = 2; i <= n; i++) { // dp[i] = dp[i - 1] + dp[i - 2]; const dp_i = (dp_i_1 % Q + dp_i_2 % Q) % Q; dp_i_2 = dp_i_1; dp_i_1 = dp_i; } return dp_i_1; }; ``` ```python # by chatGPT (python) class Solution: def fib(self, n: int) -> int: if n == 0 or n == 1: # base case return n Q = 1000000007 # 分别代表 dp[i - 1] 和 dp[i - 2] dp_i_1, dp_i_2 = 1, 0 for i in range(2, n + 1): # dp[i] = dp[i - 1] + dp[i - 2]; dp_i = (dp_i_1 % Q + dp_i_2 % Q) % Q dp_i_2 = dp_i_1 dp_i_1 = dp_i return dp_i_1 ``` https://leetcode.cn/problems/fei-bo-na-qi-shu-lie-lcof 的多语言解法👆 https://leetcode.cn/problems/fibonacci-number 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int fib(int n) { if (n == 0 || n == 1) { // base case 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; } }; ``` ```go // by chatGPT (go) func fib(n int) int { if n == 0 || n == 1 { // base case return n } // 分别代表 dp[i - 1] 和 dp[i - 2] dp_i_1, dp_i_2 := 1, 0 for i := 2; i <= n; i++ { // dp[i] = dp[i - 1] + dp[i - 2]; dp_i := dp_i_1 + dp_i_2 dp_i_2 = dp_i_1 dp_i_1 = dp_i } return dp_i_1 } ``` ```java // by labuladong (java) class Solution { public int fib(int n) { if (n == 0 || n == 1) { // base case 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; } } ``` ```javascript // by chatGPT (javascript) /** * @param {number} n * @return {number} */ var fib = function(n) { if (n === 0 || n === 1) { // base case return n; } // 分别代表 dp[i - 1] 和 dp[i - 2] let dp_i_1 = 1, dp_i_2 = 0; for (let i = 2; i <= n; i++) { // dp[i] = dp[i - 1] + dp[i - 2]; const dp_i = dp_i_1 + dp_i_2; dp_i_2 = dp_i_1; dp_i_1 = dp_i; } return dp_i_1; }; ``` ```python # by chatGPT (python) class Solution: def fib(self, n: int) -> int: if n == 0 or n == 1: # base case return n # 分别代表 dp[i - 1] 和 dp[i - 2] dp_i_1, dp_i_2 = 1, 0 for i in range(2, n+1): # dp[i] = dp[i - 1] + dp[i - 2]; dp_i = dp_i_1 + dp_i_2 dp_i_2 = dp_i_1 dp_i_1 = dp_i return dp_i_1 ``` https://leetcode.cn/problems/fibonacci-number 的多语言解法👆 https://leetcode.cn/problems/find-all-anagrams-in-a-string 的多语言解法👇 ```cpp // by labuladong (cpp) class Solution { public: vector findAnagrams(string s, string t) { unordered_map need, window; for (char c : t) need[c]++; int left = 0, right = 0; int valid = 0; vector res; // 记录结果 while (right < s.size()) { char c = s[right]; right++; // 进行窗口内数据的一系列更新 if (need.count(c)) { window[c]++; if (window[c] == need[c]) valid++; } // 判断左侧窗口是否要收缩 while (right - left >= t.size()) { // 当窗口符合条件时,把起始索引加入 res if (valid == need.size()) res.push_back(left); char d = s[left]; left++; // 进行窗口内数据的一系列更新 if (need.count(d)) { if (window[d] == need[d]) valid--; window[d]--; } } } return res; } }; ``` ```go // by chatGPT (go) func findAnagrams(s string, t string) []int { need := make(map[byte]int) window := make(map[byte]int) for i := 0; i < len(t); i++ { need[t[i]]++ } left, right := 0, 0 valid := 0 var res []int for right < len(s) { c := s[right] right++ // 进行窗口内数据的一系列更新 if _, ok := need[c]; ok { window[c]++ if window[c] == need[c] { valid++ } } // 判断左侧窗口是否要收缩 for right - left >= len(t) { // 当窗口符合条件时,把起始索引加入 res if valid == len(need) { res = append(res, left) } d := s[left] left++ // 进行窗口内数据的一系列更新 if _, ok := need[d]; ok { if window[d] == need[d] { valid-- } window[d]-- } } } return res } ``` ```java // by chatGPT (java) 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; } } ``` ```javascript // by chatGPT (javascript) /** * @param {string} s * @param {string} t * @return {number[]} */ var findAnagrams = function(s, t) { const need = new Map(); const window = new Map(); for (const c of t) { need.set(c, (need.get(c) || 0) + 1); } let left = 0, right = 0; let valid = 0; const res = []; while (right < s.length) { const c = s[right]; right++; // 进行窗口内数据的一系列更新 if (need.has(c)) { window.set(c, (window.get(c) || 0) + 1); if (window.get(c) === need.get(c)) { valid++; } } // 判断左侧窗口是否要收缩 while (right - left >= t.length) { // 当窗口符合条件时,把起始索引加入 res if (valid === need.size) { res.push(left); } const d = s[left]; left++; // 进行窗口内数据的一系列更新 if (need.has(d)) { if (window.get(d) === need.get(d)) { valid--; } window.set(d, window.get(d) - 1); } } } return res; }; ``` ```python # by chatGPT (python) class Solution: def findAnagrams(self, s: str, t: str) -> List[int]: need = {} window = {} for c in t: if c in need: need[c] += 1 else: need[c] = 1 left = 0 right = 0 valid = 0 res = [] while right < len(s): c = s[right] right += 1 # 进行窗口内数据的一系列更新 if c in need: if c in window: window[c] += 1 else: window[c] = 1 if window[c] == need[c]: valid += 1 # 判断左侧窗口是否要收缩 while right - left >= len(t): # 当窗口符合条件时,把起始索引加入 res if valid == len(need): res.append(left) d = s[left] left += 1 # 进行窗口内数据的一系列更新 if d in need: if window[d] == need[d]: valid -= 1 window[d] -= 1 return res ``` https://leetcode.cn/problems/find-all-anagrams-in-a-string 的多语言解法👆 https://leetcode.cn/problems/find-all-duplicates-in-an-array 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: vector findDuplicates(vector& nums) { int n = nums.size(); vector res; // 用数组模拟哈希集合 vector seen(n + 1); for (int num : nums) { if (seen[num] == 0) { // 添加到哈希集合 seen[num] = 1; } else { // 找到重复元素 res.push_back(num); } } return res; } }; class Solution2 { public: vector findDuplicates(vector& nums) { vector res; for (int num : nums) { // 注意索引,元素大小从 1 开始,有一位索引偏移 if (nums[abs(num) - 1] < 0) { // 之前已经把对应索引的元素变成负数了, // 这说明 num 重复出现了两次 res.push_back(abs(num)); } else { // 把索引 num - 1 置为负数 nums[abs(num) - 1] *= -1; } } return res; } }; ``` ```go // by chatGPT (go) func findDuplicates(nums []int) []int { n := len(nums) res := make([]int, 0) // 用数组模拟哈希集合 seen := make([]int, n+1) for _, num := range nums { if seen[num] == 0 { // 添加到哈希集合 seen[num] = 1 } else { // 找到重复元素 res = append(res, num) } } return res } func findDuplicates2(nums []int) []int { res := make([]int, 0) for _, num := range nums { // 注意索引,元素大小从 1 开始,有一位索引偏移 if nums[abs(num)-1] < 0 { // 之前已经把对应索引的元素变成负数了, // 这说明 num 重复出现了两次 res = append(res, abs(num)) } else { // 把索引 num - 1 置为负数 nums[abs(num)-1] *= -1 } } return res } func abs(a int) int { if a < 0 { return -a } return a } ``` ```java // by labuladong (java) class Solution { public List findDuplicates(int[] nums) { int n = nums.length; List res = new LinkedList<>(); // 用数组模拟哈希集合 int[] seen = new int[n + 1]; for (int num : nums) { if (seen[num] == 0) { // 添加到哈希集合 seen[num] = 1; } else { // 找到重复元素 res.add(num); } } return res; } } class Solution2 { public List findDuplicates(int[] nums) { List res = new LinkedList<>(); for (int num : nums) { // 注意索引,nums 中元素大小从 1 开始, // 而索引是从 0 开始的,所以有一位索引偏移 if (nums[Math.abs(num) - 1] < 0) { // 之前已经把对应索引的元素变成负数了, // 这说明 num 重复出现了两次 res.add(Math.abs(num)); } else { // 把索引 num - 1 置为负数 nums[Math.abs(num) - 1] *= -1; } } return res; } } ``` ```javascript // by chatGPT (javascript) var findDuplicates = function(nums) { const n = nums.length; const res = []; // 用数组模拟哈希集合 const seen = new Array(n + 1).fill(0); for (let i = 0; i < n; i++) { const num = nums[i]; if (seen[num] === 0) { // 添加到哈希集合 seen[num] = 1; } else { // 找到重复元素 res.push(num); } } return res; }; var findDuplicates2 = function(nums) { const res = []; for (let i = 0; i < nums.length; i++) { const num = Math.abs(nums[i]); // 注意索引,元素大小从 1 开始,有一位索引偏移 if (nums[num - 1] < 0) { // 之前已经把对应索引的元素变成负数了, // 这说明 num 重复出现了两次 res.push(num); } else { // 把索引 num - 1 置为负数 nums[num - 1] *= -1; } } return res; }; ``` ```python # by chatGPT (python) class Solution: def findDuplicates(self, nums: List[int]) -> List[int]: n = len(nums) res = [] # 用数组模拟哈希集合 seen = [0] * (n + 1) for num in nums: if seen[num] == 0: # 添加到哈希集合 seen[num] = 1 else: # 找到重复元素 res.append(num) return res class Solution2: def findDuplicates(self, nums: List[int]) -> List[int]: res = [] for num in nums: # 注意索引,元素大小从 1 开始,有一位索引偏移 if nums[abs(num) - 1] < 0: # 之前已经把对应索引的元素变成负数了, # 这说明 num 重复出现了两次 res.append(abs(num)) else: # 把索引 num - 1 置为负数 nums[abs(num) - 1] *= -1 return res ``` https://leetcode.cn/problems/find-all-duplicates-in-an-array 的多语言解法👆 https://leetcode.cn/problems/find-all-numbers-disappeared-in-an-array 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: vector findDisappearedNumbers(vector& nums) { int n = nums.size(); vector count(n + 1, 0); for (int num : nums) { count[num]++; } vector res; for (int num = 1; num <= n; num++) { if (count[num] == 0) { res.push_back(num); } } return res; } }; class Solution2 { public: vector findDisappearedNumbers(vector& nums) { for (int num : nums) { // 注意索引,元素大小从 1 开始,有一位索引偏移 if (nums[abs(num) - 1] < 0) { // 之前已经把对应索引的元素变成负数了, // 这说明 num 重复出现了两次,但我们不用做,让索引继续保持负数 } else { // 把索引 num - 1 置为负数 nums[abs(num) - 1] *= -1; } } vector res; for (int i = 0; i < nums.size(); i++) { if (nums[i] > 0) { // 说明没有元素和这个索引对应,即找到一个缺失元素 res.push_back(i + 1); } } return res; } }; ``` ```go // by chatGPT (go) func findDisappearedNumbers(nums []int) []int { n := len(nums) count := make([]int, n+1) for _, num := range nums { count[num]++ } res := make([]int, 0) for num := 1; num <= n; num++ { if count[num] == 0 { res = append(res, num) } } return res } func findDisappearedNumbers2(nums []int) []int { for _, num := range nums { // 注意索引,元素大小从 1 开始,有一位索引偏移 if nums[abs(num)-1] < 0 { // 之前已经把对应索引的元素变成负数了, // 这说明 num 重复出现了两次,但我们不用做,让索引继续保持负数 } else { // 把索引 num - 1 置为负数 nums[abs(num)-1] *= -1 } } res := make([]int, 0) for i, num := range nums { if num > 0 { // 说明没有元素和这个索引对应,即找到一个缺失元素 res = append(res, i+1) } } return res } func abs(x int) int { if x < 0 { return -x } return x } ``` ```java // by labuladong (java) class Solution { public List findDisappearedNumbers(int[] nums) { int n = nums.length; int[] count = new int[n + 1]; for (int num : nums) { count[num]++; } List res = new LinkedList<>(); for (int num = 1; num <= n; num++) { if (count[num] == 0) { res.add(num); } } return res; } } class Solution2 { public List findDisappearedNumbers(int[] nums) { for (int num : nums) { // 注意索引,nums 中元素大小从 1 开始, // 而索引是从 0 开始的,所以有一位索引偏移 if (nums[Math.abs(num) - 1] < 0) { // 之前已经把对应索引的元素变成负数了, // 这说明 num 重复出现了两次,但我们什么都不用做,让索引继续保持负数 } else { // 把索引 num - 1 置为负数 nums[Math.abs(num) - 1] *= -1; } } List res = new LinkedList<>(); for (int i = 0; i < nums.length; i++) { if (nums[i] > 0) { // 说明没有元素和这个索引对应,即找到一个缺失元素 res.add(i + 1); } } return res; } } ``` ```javascript // by chatGPT (javascript) var findDisappearedNumbers = function(nums) { const n = nums.length; const count = new Array(n+1).fill(0); for (let i = 0; i < n; i++) { count[nums[i]]++; } const res = []; for (let i = 1; i <= n; i++) { if (count[i] === 0) { res.push(i); } } return res; } var findDisappearedNumbers2 = function(nums) { for (let i = 0; i < nums.length; i++) { const num = Math.abs(nums[i]); // 注意索引,元素大小从 1 开始,有一位索引偏移 if (nums[num - 1] > 0) { // 把索引 num - 1 置为负数 nums[num - 1] *= -1; } } const res = []; for (let i = 0; i < nums.length; i++) { if (nums[i] > 0) { // 说明没有元素和这个索引对应,即找到一个缺失元素 res.push(i + 1); } } return res; } ``` ```python # by chatGPT (python) class Solution: def findDisappearedNumbers(self, nums: List[int]) -> List[int]: n = len(nums) count = [0] * (n + 1) for num in nums: count[num] += 1 res = [] for num in range(1, n+1): if count[num] == 0: res.append(num) return res class Solution2: def findDisappearedNumbers(self, nums: List[int]) -> List[int]: for num in nums: # 注意索引,元素大小从 1 开始,有一位索引偏移 if nums[abs(num) - 1] < 0: # 之前已经把对应索引的元素变成负数了, # 这说明 num 重复出现了两次,但我们不用做,让索引继续保持负数 pass else: # 把索引 num - 1 置为负数 nums[abs(num) - 1] *= -1 res = [] for i in range(len(nums)): if nums[i] > 0: # 说明没有元素和这个索引对应,即找到一个缺失元素 res.append(i + 1) return res ``` https://leetcode.cn/problems/find-all-numbers-disappeared-in-an-array 的多语言解法👆 https://leetcode.cn/problems/find-distance-in-a-binary-tree 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int findDistance(TreeNode* root, int p, int q) { lca(root, p, q); return res; } private: bool found = false; int res = 0; // 定义:当子树中不包含 p 或 q 时,返回 0; // 当子树中仅包含 p 或 q 中的一个时,返回 root 到 p 或 q 的距离; // 当子树中同时包含 p 和 q 时,返回一个无意义的值(答案会被存在外部变量 res 中) int lca(TreeNode* root, int p, int q) { if (found) { // found 为 true 时答案已经被记录在全局 res 中 // 递归函数的返回值已不需要了,此时返回什么值都无所谓 return 123; } if (!root) { return 0; } int left = lca(root->left, p, q); int right = lca(root->right, p, q); if (left == 0 && right == 0) { // root 的左右子树都不包含 p 或 q if (root->val == p || root->val == q) { return 1; } return 0; } if (left != 0 && right != 0 && !found) { // 当前节点 root 就是 p, q 的最近公共祖先节点 // 答案已经算出来了,更新全局变量 res = left + right; // 这个递归函数的返回值已经不重要了,让它终止递归 found = true; return 456; } // 此时 left 和 right 有一个为 0,即只找到了一个节点 // branch 就是到该节点的距离 int branch = left == 0 ? right : left; if (root->val == p || root->val == q) { res = branch; } return branch + 1; } }; ``` ```go // by chatGPT (go) /** * Definition for a binary tree node. * type TreeNode struct { * Val int * Left *TreeNode * Right *TreeNode * } */ func findDistance(root *TreeNode, p int, q int) int { var found bool = false var res int = 0 // 定义:当子树中不包含 p 或 q 时,返回 0; // 当子树中仅包含 p 或 q 中的一个时,返回 root 到 p 或 q 的距离; // 当子树中同时包含 p 和 q 时,返回一个无意义的值(答案会被存在外部变量 res 中) var lca func(root *TreeNode, p, q int) int lca = func(root *TreeNode, p, q int) int { if found { // found 为 true 时答案已经被记录在全局 res 中 // 递归函数的返回值已不需要了,此时返回什么值都无所谓 return 123 } if root == nil { return 0 } left := lca(root.Left, p, q) right := lca(root.Right, p, q) if left == 0 && right == 0 { // root 的左右子树都不包含 p 或 q if root.Val == p || root.Val == q { return 1 } return 0 } if left != 0 && right != 0 && !found { // 当前节点 root 就是 p, q 的最近公共祖先节点 // 答案已经算出来了,更新全局变量 res = left + right // 这个递归函数的返回值已经不重要了,让它终止递归 found = true return 456 } // 此时 left 和 right 有一个为 0,即只找到了一个节点 // branch 就是到该节点的距离 branch := 0 if left == 0 { branch = right } else { branch = left } if root.Val == p || root.Val == q { res = branch } return branch + 1 } lca(root, p, q) return res } ``` ```java // by labuladong (java) class Solution { public int findDistance(TreeNode root, int p, int q) { lca(root, p, q); return res; } boolean found = false; int res = 0; // 定义:当子树中不包含 p 或 q 时,返回 0; // 当子树中仅包含 p 或 q 中的一个时,返回 root 到 p 或 q 的距离; // 当子树中同时包含 p 和 q 时,返回一个无意义的值(答案会被存在外部变量 res 中) int lca(TreeNode root, int p, int q) { if (found) { // found 为 true 时答案已经被记录在全局 res 中 // 递归函数的返回值已不需要了,此时返回什么值都无所谓 return 123; } if (root == null) { return 0; } int left = lca(root.left, p, q); int right = lca(root.right, p, q); if (left == 0 && right == 0) { // root 的左右子树都不包含 p 或 q if (root.val == p || root.val == q) { return 1; } return 0; } if (left != 0 && right != 0 && !found) { // 当前节点 root 就是 p, q 的最近公共祖先节点 // 答案已经算出来了,更新全局变量 res = left + right; // 这个递归函数的返回值已经不重要了,让它终止递归 found = true; return 456; } // 此时 left 和 right 有一个为 0,即只找到了一个节点 // branch 就是到该节点的距离 int branch = left == 0 ? right : left; if (root.val == p || root.val == q) { res = branch; } return branch + 1; } } ``` ```javascript // by chatGPT (javascript) var findDistance = function(root, p, q) { var found = false; var res = 0; // 定义:当子树中不包含 p 或 q 时,返回 0; // 当子树中仅包含 p 或 q 中的一个时,返回 root 到 p 或 q 的距离; // 当子树中同时包含 p 和 q 时,返回一个无意义的值(答案会被存在外部变量 res 中) var lca = function(root, p, q) { if (found) { // found 为 true 时答案已经被记录在全局 res 中 // 递归函数的返回值已不需要了,此时返回什么值都无所谓 return 123; } if (root == null) { return 0; } var left = lca(root.left, p, q); var right = lca(root.right, p, q); if (left == 0 && right == 0) { // root 的左右子树都不包含 p 或 q if (root.val == p || root.val == q) { return 1; } return 0; } if (left != 0 && right != 0 && !found) { // 当前节点 root 就是 p, q 的最近公共祖先节点 // 答案已经算出来了,更新全局变量 res = left + right; // 这个递归函数的返回值已经不重要了,让它终止递归 found = true; return 456; } // 此时 left 和 right 有一个为 0,即只找到了一个节点 // branch 就是到该节点的距离 var branch = left == 0 ? right : left; if (root.val == p || root.val == q) { res = branch; } return branch + 1; }; lca(root, p, q); return res; } ``` ```python # by chatGPT (python) class Solution: def findDistance(self, root: TreeNode, p: int, q: int) -> int: self.found = False self.res = 0 self.lca(root, p, q) return self.res # 定义:当子树中不包含 p 或 q 时,返回 0; # 当子树中仅包含 p 或 q 中的一个时,返回 root 到 p 或 q 的距离; # 当子树中同时包含 p 和 q 时,返回一个无意义的值(答案会被存在外部变量 res 中) def lca(self, root: TreeNode, p: int, q: int) -> int: if self.found: # found 为 true 时答案已经被记录在全局 res 中 # 递归函数的返回值已不需要了,此时返回什么值都无所谓 return 123 if root is None: return 0 left = self.lca(root.left, p, q) right = self.lca(root.right, p, q) if left == 0 and right == 0: # root 的左右子树都不包含 p 或 q if root.val == p or root.val == q: return 1 return 0 if left != 0 and right != 0 and not self.found: # 当前节点 root 就是 p, q 的最近公共祖先节点 # 答案已经算出来了,更新全局变量 self.res = left + right # 这个递归函数的返回值已经不重要了,让它终止递归 self.found = True return 456 # 此时 left 和 right 有一个为 0,即只找到了一个节点 # branch 就是到该节点的距离 branch = right if left == 0 else left if root.val == p or root.val == q: self.res = branch return branch + 1 ``` https://leetcode.cn/problems/find-distance-in-a-binary-tree 的多语言解法👆 https://leetcode.cn/problems/find-duplicate-subtrees 的多语言解法👇 ```cpp // by chatGPT (cpp) #include #include using namespace std; class Solution { // 记录所有子树以及出现的次数 unordered_map memo; // 记录重复的子树根节点 vector res; public: /* 主函数 */ vector findDuplicateSubtrees(TreeNode* root) { traverse(root); return res; } string traverse(TreeNode* root) { if (root == nullptr) { return "#"; } string left = traverse(root->left); string right = traverse(root->right); string subTree = left + "," + right + "," + to_string(root->val); int freq = memo[subTree]; // 多次重复也只会被加入结果集一次 if (freq == 1) { res.push_back(root); } // 给子树对应的出现次数加一 memo[subTree] = freq + 1; return subTree; } }; ``` ```go // by chatGPT (go) import ( "strconv" ) // TreeNode Definition for a binary tree node. // findDuplicateSubtrees 记录所有子树以及出现的次数 func findDuplicateSubtrees(root *TreeNode) []*TreeNode { memo := make(map[string]int) res := []*TreeNode{} traverse(root, memo, &res) return res } // traverse 深度优先遍历 func traverse(root *TreeNode, memo map[string]int, res *[]*TreeNode) string { if root == nil { return "#" } left := traverse(root.Left, memo, res) right := traverse(root.Right, memo, res) subTree := left + "," + right + "," + strconv.Itoa(root.Val) freq := memo[subTree] // 多次重复也只会被加入结果集一次 if freq == 1 { *res = append(*res, root) } // 给子树对应的出现次数加一 memo[subTree]++ return subTree } ``` ```java // by labuladong (java) class Solution { // 记录所有子树以及出现的次数 HashMap memo = new HashMap<>(); // 记录重复的子树根节点 LinkedList res = new LinkedList<>(); /* 主函数 */ public List findDuplicateSubtrees(TreeNode root) { traverse(root); return res; } String traverse(TreeNode root) { if (root == null) { return "#"; } String left = traverse(root.left); String right = traverse(root.right); String subTree = left + "," + right + "," + root.val; int freq = memo.getOrDefault(subTree, 0); // 多次重复也只会被加入结果集一次 if (freq == 1) { res.add(root); } // 给子树对应的出现次数加一 memo.put(subTree, freq + 1); return subTree; } } ``` ```javascript // by chatGPT (javascript) var findDuplicateSubtrees = function(root) { // 记录所有子树以及出现的次数 const memo = new Map(); // 记录重复的子树根节点 const res = []; /* 主函数 */ const traverse = function(root) { if (root == null) { return "#"; } const left = traverse(root.left); const right = traverse(root.right); const subTree = left + "," + right + "," + root.val; const freq = memo.get(subTree) || 0; // 多次重复也只会被加入结果集一次 if (freq === 1) { res.push(root); } // 给子树对应的出现次数加一 memo.set(subTree, freq + 1); return subTree; }; traverse(root); return res; }; ``` ```python # by chatGPT (python) class Solution: def findDuplicateSubtrees(self, root: TreeNode) -> List[TreeNode]: memo = {} res = [] def traverse(root): if not root: return "#" left = traverse(root.left) right = traverse(root.right) subTree = left + "," + right + "," + str(root.val) freq = memo.get(subTree, 0) # 多次重复也只会被加入结果集一次 if freq == 1: res.append(root) # 给子树对应的出现次数加一 memo[subTree] = freq + 1 return subTree traverse(root) return res ``` https://leetcode.cn/problems/find-duplicate-subtrees 的多语言解法👆 https://leetcode.cn/problems/find-first-and-last-position-of-element-in-sorted-array 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: vector searchRange(vector& nums, int target) { int left = left_bound(nums, target); int right = right_bound(nums, target); return {left, right}; } int left_bound(vector& nums, int target) { int left = 0, right = nums.size() - 1; // 搜索区间为 [left, right] while (left <= right) { int mid = left + (right - left) / 2; if (nums[mid] < target) { // 搜索区间变为 [mid+1, right] left = mid + 1; } else if (nums[mid] > target) { // 搜索区间变为 [left, mid-1] right = mid - 1; } else if (nums[mid] == target) { // 收缩右侧边界 right = mid - 1; } } // 检查出界情况 if (left >= nums.size() || nums[left] != target) { /* ![](../pictures/二分查找/2.jpg) */ return -1; } return left; } int right_bound(vector& nums, int target) { int left = 0, right = nums.size() - 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; } } // 这里改为检查 right 越界的情况,见下图 if (right < 0 || nums[right] != target) { /* ![](../pictures/二分查找/4.jpg) */ return -1; } return right; } }; ``` ```go // by chatGPT (go) func searchRange(nums []int, target int) []int { return []int{leftBound(nums, target), rightBound(nums, target)} } func leftBound(nums []int, target int) int { left, right := 0, len(nums)-1 // 搜索区间为 [left, right] for left <= right { mid := left + (right-left)/2 if nums[mid] < target { // 搜索区间变为 [mid+1, right] left = mid + 1 } else if nums[mid] > target { // 搜索区间变为 [left, mid-1] right = mid - 1 } else if nums[mid] == target { // 收缩右侧边界 right = mid - 1 } } // 检查出界情况 if left >= len(nums) || nums[left] != target { /** ![](../pictures/二分查找/2.jpg) */ return -1 } return left } func rightBound(nums []int, target int) int { left, right := 0, len(nums)-1 for left <= right { 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 } } // 这里改为检查 right 越界的情况,见下图 if right < 0 || nums[right] != target { /** ![](../pictures/二分查找/4.jpg) */ return -1 } return right } ``` ```java // by labuladong (java) class Solution { public int[] searchRange(int[] nums, int target) { return new int[]{left_bound(nums, target), right_bound(nums, target)}; } int left_bound(int[] nums, int target) { int left = 0, right = nums.length - 1; // 搜索区间为 [left, right] while (left <= right) { int mid = left + (right - left) / 2; if (nums[mid] < target) { // 搜索区间变为 [mid+1, right] left = mid + 1; } else if (nums[mid] > target) { // 搜索区间变为 [left, mid-1] right = mid - 1; } else if (nums[mid] == target) { // 收缩右侧边界 right = mid - 1; } } // 检查出界情况 if (left >= nums.length || nums[left] != target) { /** ![](../pictures/二分查找/2.jpg) */ return -1; } return left; } 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; } } // 这里改为检查 right 越界的情况,见下图 if (right < 0 || nums[right] != target) { /** ![](../pictures/二分查找/4.jpg) */ return -1; } return right; } } ``` ```javascript // by chatGPT (javascript) var searchRange = function(nums, target) { return [left_bound(nums, target), right_bound(nums, target)]; }; var left_bound = function(nums, target) { let left = 0, right = nums.length - 1; // 搜索区间为 [left, right] while (left <= right) { let mid = left + Math.floor((right - left) / 2); if (nums[mid] < target) { // 搜索区间变为 [mid+1, right] left = mid + 1; } else if (nums[mid] > target) { // 搜索区间变为 [left, mid-1] right = mid - 1; } else if (nums[mid] === target) { // 收缩右侧边界 right = mid - 1; } } // 检查出界情况 if (left >= nums.length || nums[left] !== target) { /** ![](../pictures/二分查找/2.jpg) */ return -1; } return left; }; var right_bound = function(nums, target) { let left = 0,right = nums.length - 1; while (left <= right) { let mid = left + Math.floor((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; } } // 这里改为检查 right 越界的情况,见下图 if (right < 0 || nums[right] !== target) { /** ![](../pictures/二分查找/4.jpg) */ return -1; } return right; }; ``` ```python # by chatGPT (python) class Solution: def searchRange(self, nums: List[int], target: int) -> List[int]: return [self.left_bound(nums, target), self.right_bound(nums, target)] def left_bound(self, nums: List[int], target: int) -> int: left, right = 0, len(nums) - 1 # 搜索区间为 [left, right] while left <= right: mid = left + (right - left) // 2 if nums[mid] < target: # 搜索区间变为 [mid+1, right] left = mid + 1 elif nums[mid] > target: # 搜索区间变为 [left, mid-1] right = mid - 1 elif nums[mid] == target: # 收缩右侧边界 right = mid - 1 # 检查出界情况 if left >= len(nums) or nums[left] != target: """ ![](../pictures/二分查找/2.jpg) """f return -1 return left def right_bound(self, nums: List[int], target: int) -> int: left, right = 0, len(nums) - 1 while left <= right: mid = left + (right - left) // 2 if nums[mid] < target: left = mid + 1 elif nums[mid] > target: right = mid - 1 elif nums[mid] == target: # 这里改成收缩左侧边界即可 left = mid + 1 # 这里改为检查 right 越界的情况,见下图 if right < 0 or nums[right] != target: ''' ![](../pictures/二分查找/4.jpg) ''' return -1 return right ``` https://leetcode.cn/problems/find-first-and-last-position-of-element-in-sorted-array 的多语言解法👆 https://leetcode.cn/problems/find-k-closest-elements 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: vector findClosestElements(vector& arr, int k, int x) { // 二分搜索找到 x 的位置 int p = left_bound(arr, x); // 两端都开的区间 (left, right) int left = p - 1, right = p; vector res; // 扩展区间,直到区间内包含 k 个元素 while (right - left - 1 < k) { if (left == -1) { res.push_back(arr[right]); right++; } else if (right == arr.size()) { res.insert(res.begin(), arr[left]); left--; } else if (x - arr[left] > arr[right] - x) { res.push_back(arr[right]); right++; } else { res.insert(res.begin(), arr[left]); left--; } } return res; } // 搜索左侧边界的二分搜索 int left_bound(vector& nums, int target) { int left = 0; int right = nums.size(); while (left < right) { int mid = left + (right - left) / 2; if (nums[mid] == target) { right = mid; } else if (nums[mid] < target) { left = mid + 1; } else if (nums[mid] > target) { right = mid; } } return left; } }; ``` ```go // by chatGPT (go) func findClosestElements(arr []int, k int, x int) []int { // 二分搜索找到 x 的位置 p := left_bound(arr, x) // 两端都开的区间 (left, right) left, right := p-1, p res := make([]int, 0) // 扩展区间,直到区间内包含 k 个元素 for right-left-1 < k { if left == -1 { res = append(res, arr[right]) right++ } else if right == len(arr) { res = append([]int{arr[left]}, res...) left-- } else if x-arr[left] > arr[right]-x { res = append(res, arr[right]) right++ } else { res = append([]int{arr[left]}, res...) left-- } } return res } // 搜索左侧边界的二分搜索 func left_bound(nums []int, target int) int { left := 0 right := len(nums) for left < right { mid := left + (right-left)/2 if nums[mid] == target { right = mid } else if nums[mid] < target { left = mid + 1 } else if nums[mid] > target { right = mid } } return left } ``` ```java // by labuladong (java) class Solution { public List findClosestElements(int[] arr, int k, int x) { // 二分搜索找到 x 的位置 int p = left_bound(arr, x); // 两端都开的区间 (left, right) int left = p - 1, right = p; LinkedList res = new LinkedList<>(); // 扩展区间,直到区间内包含 k 个元素 while (right - left - 1 < k) { if (left == -1) { res.addLast(arr[right]); right++; } else if (right == arr.length) { res.addFirst(arr[left]); left--; } else if (x - arr[left] > arr[right] - x) { res.addLast(arr[right]); right++; } else { res.addFirst(arr[left]); left--; } } return res; } // 搜索左侧边界的二分搜索 int left_bound(int[] nums, int target) { int left = 0; int right = nums.length; while (left < right) { int mid = left + (right - left) / 2; if (nums[mid] == target) { right = mid; } else if (nums[mid] < target) { left = mid + 1; } else if (nums[mid] > target) { right = mid; } } return left; } } ``` ```javascript // by chatGPT (javascript) var findClosestElements = function(arr, k, x) { // 二分搜索找到 x 的位置 let p = left_bound(arr, x); // 两端都开的区间 (left, right) let left = p - 1, right = p; let res = []; // 扩展区间,直到区间内包含 k 个元素 while (right - left - 1 < k) { if (left == -1) { res.push(arr[right]); right++; } else if (right == arr.length) { res.unshift(arr[left]); left--; } else if (x - arr[left] > arr[right] - x) { res.push(arr[right]); right++; } else { res.unshift(arr[left]); left--; } } return res; }; // 搜索左侧边界的二分搜索 var left_bound = function(nums, target) { let left = 0; let right = nums.length; while (left < right) { let mid = left + Math.floor((right - left) / 2); if (nums[mid] == target) { right = mid; } else if (nums[mid] < target) { left = mid + 1; } else if (nums[mid] > target) { right = mid; } } return left; }; ``` ```python # by chatGPT (python) class Solution: def findClosestElements(self, arr: List[int], k: int, x: int) -> List[int]: # 二分搜索找到 x 的位置 p = self.left_bound(arr, x) # 两端都开的区间 (left, right) left, right = p - 1, p res = [] # 扩展区间,直到区间内包含 k 个元素 while right - left - 1 < k: if left == -1: res.append(arr[right]) right += 1 elif right == len(arr): res.insert(0, arr[left]) left -= 1 elif x - arr[left] > arr[right] - x: res.append(arr[right]) right += 1 else: res.insert(0, arr[left]) left -= 1 return res # 搜索左侧边界的二分搜索 def left_bound(self, nums: List[int], target: int) -> int: left, right = 0, len(nums) while left < right: mid = left + (right - left) // 2 if nums[mid] == target: right = mid elif nums[mid] < target: left = mid + 1 elif nums[mid] > target: right = mid return left ``` https://leetcode.cn/problems/find-k-closest-elements 的多语言解法👆 https://leetcode.cn/problems/find-k-pairs-with-smallest-sums 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: vector> kSmallestPairs(vector& nums1, vector& nums2, int k) { // 存储三元组 (num1[i], nums2[i], i) // i 记录 nums2 元素的索引位置,用于生成下一个节点 priority_queue, vector>, greater>> pq([](const vector& a, const vector& b) { // 按照数对的元素和升序排序 return a[0] + a[1] < b[0] + b[1]; }); // 按照 23 题的逻辑初始化优先级队列 for (int i = 0; i < nums1.size(); i++) { pq.push({nums1[i], nums2[0], 0}); } vector> res; // 执行合并多个有序链表的逻辑 while (!pq.empty() && k > 0) { auto cur = pq.top(); pq.pop(); k--; // 链表中的下一个节点加入优先级队列 int next_index = cur[2] + 1; if (next_index < nums2.size()) { pq.push({cur[0], nums2[next_index], next_index}); } vector pair = {cur[0], cur[1]}; res.push_back(pair); } return res; } }; ``` ```go // by chatGPT (go) func kSmallestPairs(nums1 []int, nums2 []int, k int) [][]int { // 存储三元组 (num1[i], nums2[i], i) // i 记录 nums2 元素的索引位置,用于生成下一个节点 pq := make(PriorityQueue, 0) heap.Init(&pq) // 按照 23 题的逻辑初始化优先级队列 for i := 0; i < len(nums1); i++ { pq.Push([]int{nums1[i], nums2[0], 0}) } res := make([][]int, 0) // 执行合并多个有序链表的逻辑 for pq.Len() > 0 && k > 0 { cur := pq.Pop().([]int) k-- // 链表中的下一个节点加入优先级队列 next_index := cur[2] + 1 if next_index < len(nums2) { pq.Push([]int{cur[0], nums2[next_index], next_index}) } pair := []int{cur[0], cur[1]} res = append(res, pair) } return res } type PriorityQueue [][]int func (pq PriorityQueue) Len() int { return len(pq) } func (pq PriorityQueue) Less(i, j int) bool { return pq[i][0]+pq[i][1] < pq[j][0]+pq[j][1] } func (pq PriorityQueue) Swap(i, j int) { pq[i], pq[j] = pq[j], pq[i] } func (pq *PriorityQueue) Push(x interface{}) { *pq = append(*pq, x.([]int)) } func (pq *PriorityQueue) Pop() interface{} { old := *pq n := len(old) item := old[n-1] *pq = old[0 : n-1] return item } ``` ```java // by labuladong (java) class Solution { public List> kSmallestPairs(int[] nums1, int[] nums2, int k) { // 存储三元组 (num1[i], nums2[i], i) // i 记录 nums2 元素的索引位置,用于生成下一个节点 PriorityQueue pq = new PriorityQueue<>((a, b) -> { // 按照数对的元素和升序排序 return (a[0] + a[1]) - (b[0] + b[1]); }); // 按照 23 题的逻辑初始化优先级队列 for (int i = 0; i < nums1.length; i++) { pq.offer(new int[]{nums1[i], nums2[0], 0}); } List> res = new ArrayList<>(); // 执行合并多个有序链表的逻辑 while (!pq.isEmpty() && k > 0) { int[] cur = pq.poll(); k--; // 链表中的下一个节点加入优先级队列 int next_index = cur[2] + 1; if (next_index < nums2.length) { pq.add(new int[]{cur[0], nums2[next_index], next_index}); } List pair = new ArrayList<>(); pair.add(cur[0]); pair.add(cur[1]); res.add(pair); } return res; } } ``` ```javascript // by chatGPT (javascript) var kSmallestPairs = function(nums1, nums2, k) { // 存储三元组 (num1[i], nums2[i], i) // i 记录 nums2 元素的索引位置,用于生成下一个节点 const pq = new PriorityQueue((a, b) => { // 按照数对的元素和升序排序 return (a[0] + a[1]) - (b[0] + b[1]); }); // 按照 23 题的逻辑初始化优先级队列 for (let i = 0; i < nums1.length; i++) { pq.offer([nums1[i], nums2[0], 0]); } const res = []; // 执行合并多个有序链表的逻辑 while (!pq.isEmpty() && k > 0) { const cur = pq.poll(); k--; // 链表中的下一个节点加入优先级队列 const next_index = cur[2] + 1; if (next_index < nums2.length) { pq.add([cur[0], nums2[next_index], next_index]); } const pair = [cur[0], cur[1]]; res.push(pair); } return res; }; // 优先级队列的实现,用于存储三元组 class PriorityQueue { constructor(comparator) { this.heap = []; this.comparator = comparator; } isEmpty() { return this.heap.length === 0; } size() { return this.heap.length; } peek() { return this.heap[0]; } offer(value) { this.heap.push(value); this.bubbleUp(this.heap.length - 1); } poll() { const result = this.heap[0]; const end = this.heap.pop(); if (this.heap.length > 0) { this.heap[0] = end; this.bubbleDown(0); } return result; } bubbleUp(index) { while (index > 0) { const parentIndex = (index - 1) >> 1; if (this.comparator(this.heap[index], this.heap[parentIndex]) < 0) { const tmp = this.heap[index]; this.heap[index] = this.heap[parentIndex]; this.heap[parentIndex] = tmp; index = parentIndex; } else { break; } } } bubbleDown(index) { const lastIndex = this.heap.length - 1; while (STATUS_PASSED_TEST) { const leftIndex = index * 2 + 1; const rightIndex = index * 2 + 2; let findIndex = index; if (leftIndex <= lastIndex && this.comparator(this.heap[leftIndex], this.heap[findIndex]) < 0) { findIndex = leftIndex; } if (rightIndex <= lastIndex && this.comparator(this.heap[rightIndex], this.heap[findIndex]) < 0) { findIndex = rightIndex; } if (index !== findIndex) { const tmp = this.heap[index]; this.heap[index] = this.heap[findIndex]; this.heap[findIndex] = tmp; index = findIndex; } else { break; } } } } ``` ```python # by chatGPT (python) import heapq class Solution: def kSmallestPairs(self, nums1: List[int], nums2: List[int], k: int) -> List[List[int]]: # 存储三元组 (num1[i], nums2[i], i) # i 记录 nums2 元素的索引位置,用于生成下一个节点 pq = [] for i in range(len(nums1)): heapq.heappush(pq, [nums1[i], nums2[0], 0]) res = [] # 执行合并多个有序链表的逻辑 while pq and k > 0: cur = heapq.heappop(pq) k -= 1 # 链表中的下一个节点加入优先级队列 next_index = cur[2] + 1 if next_index < len(nums2): heapq.heappush(pq, [cur[0], nums2[next_index], next_index]) pair = [cur[0], cur[1]] res.append(pair) return res ``` https://leetcode.cn/problems/find-k-pairs-with-smallest-sums 的多语言解法👆 https://leetcode.cn/problems/find-median-from-data-stream 的多语言解法👇 ```cpp // by chatGPT (cpp) class MedianFinder { private: priority_queue large; // 小顶堆 priority_queue, greater> small; // 大顶堆 public: /** initialize your data structure here. */ MedianFinder() { } void addNum(int num) { if (small.size() >= large.size()) { small.push(num); large.push(small.top()); small.pop(); } else { large.push(num); small.push(large.top()); large.pop(); } } double findMedian() { /** ![](../pictures/中位数/2.jpeg) */ // 如果元素不一样多,多的那个堆的堆顶元素就是中位数 if (large.size() < small.size()) { return small.top(); } else if (large.size() > small.size()) { return large.top(); } // 如果元素一样多,两个堆堆顶元素的平均数是中位数 return (large.top() + small.top()) / 2.0; /** ![](../pictures/中位数/1.jpeg) */ } }; ``` ```go // by chatGPT (go) import "container/heap" type MedianFinder struct { large *PriorityQueue small *PriorityQueue } // 小顶堆 type PriorityQueue []int func (pq PriorityQueue) Len() int { return len(pq) } func (pq PriorityQueue) Less(i, j int) bool { return pq[i] < pq[j] } func (pq PriorityQueue) Swap(i, j int) { pq[i], pq[j] = pq[j], pq[i] } func (pq *PriorityQueue) Push(x interface{}) { *pq = append(*pq, x.(int)) } func (pq *PriorityQueue) Pop() interface{} { old := *pq n := len(old) x := old[n-1] *pq = old[:n-1] return x } // 大顶堆 type ReversePriorityQueue []int func (rpq ReversePriorityQueue) Len() int { return len(rpq) } func (rpq ReversePriorityQueue) Less(i, j int) bool { return rpq[i] > rpq[j] } func (rpq ReversePriorityQueue) Swap(i, j int) { rpq[i], rpq[j] = rpq[j], rpq[i] } func (rpq *ReversePriorityQueue) Push(x interface{}) { *rpq = append(*rpq, x.(int)) } func (rpq *ReversePriorityQueue) Pop() interface{} { old := *rpq n := len(old) x := old[n-1] *rpq = old[:n-1] return x } func Constructor() MedianFinder { // 小顶堆 large := &PriorityQueue{} // 大顶堆 small := &ReversePriorityQueue{} return MedianFinder{ large: large, small: small, } } func (this *MedianFinder) FindMedian() float64 { // 如果元素不一样多,多的那个堆的堆顶元素就是中位数 if this.large.Len() < this.small.Len() { return float64(this.small.Top()) } else if this.large.Len() > this.small.Len() { return float64(this.large.Top()) } // 如果元素一样多,两个堆堆顶元素的平均数是中位数 return (float64(this.large.Top()) + float64(this.small.Top())) / 2.0 } func (this *MedianFinder) AddNum(num int) { if this.small.Len() >= this.large.Len() { this.small.Push(num) heap.Push(this.large, this.small.Pop()) } else { this.large.Push(num) heap.Push(this.small, this.large.Pop()) } } // 返回堆顶元素 func (pq *PriorityQueue) Top() int { return (*pq)[0] } // 返回堆顶元素 func (rpq *ReversePriorityQueue) Top() int { return (*rpq)[0] } ``` ```java // by labuladong (java) class MedianFinder { private PriorityQueue large; private PriorityQueue small; public MedianFinder() { // 小顶堆 large = new PriorityQueue<>(); // 大顶堆 small = new PriorityQueue<>((a, b) -> { return b - a; }); } public double findMedian() { /** ![](../pictures/中位数/2.jpeg) */ // 如果元素不一样多,多的那个堆的堆顶元素就是中位数 if (large.size() < small.size()) { return small.peek(); } else if (large.size() > small.size()) { return large.peek(); } // 如果元素一样多,两个堆堆顶元素的平均数是中位数 return (large.peek() + small.peek()) / 2.0; /** ![](../pictures/中位数/1.jpeg) */ } public void addNum(int num) { if (small.size() >= large.size()) { small.offer(num); large.offer(small.poll()); } else { large.offer(num); small.offer(large.poll()); } } } ``` ```javascript // by chatGPT (javascript) var MedianFinder = function() { // 小顶堆 var large = new PriorityQueue(); // 大顶堆 var small = new PriorityQueue(function(a, b) { return b - a; }); this.findMedian = function() { // 如果元素不一样多,多的那个堆的堆顶元素就是中位数 if (large.size() < small.size()) { return small.peek(); } else if (large.size() > small.size()) { return large.peek(); } // 如果元素一样多,两个堆堆顶元素的平均数是中位数 return (large.peek() + small.peek()) / 2.0; }; this.addNum = function(num) { if (small.size() >= large.size()) { small.offer(num); large.offer(small.poll()); } else { large.offer(num); small.offer(large.poll()); } }; }; ``` ```python # by chatGPT (python) class MedianFinder: def __init__(self): # 小顶堆 self.large = [] # 大顶堆 self.small = [] def findMedian(self) -> float: # 如果元素不一样多,多的那个堆的堆顶元素就是中位数 if len(self.large) < len(self.small): return float(self.small[0]) elif len(self.large) > len(self.small): return float(self.large[0]) # 如果元素一样多,两个堆堆顶元素的平均数是中位数 return (self.large[0] + self.small[0]) / 2.0 def addNum(self, num: int) -> None: if len(self.small) >= len(self.large): self.small.append(num) self.large.append(-heapq.heappushpop(self.small, -self.large[0])) else: self.large.append(num) self.small.append(-heapq.heappushpop(self.large, -self.small[0])) ``` https://leetcode.cn/problems/find-median-from-data-stream 的多语言解法👆 https://leetcode.cn/problems/find-mode-in-binary-search-tree 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: vector mode; TreeNode* prev = nullptr; // 当前元素的重复次数 int curCount = 0; // 全局的最长相同序列长度 int maxCount = 0; vector findMode(TreeNode* root) { // 执行中序遍历 traverse(root); vector res(mode.size()); for (int i = 0; i < res.size(); i++) { res[i] = mode[i]; } return res; } void traverse(TreeNode* root) { if (root == nullptr) { return; } traverse(root->left); // 中序遍历位置 if (prev == nullptr) { // 初始化 curCount = 1; maxCount = 1; mode.push_back(root->val); } else { if (root->val == prev->val) { // root.val 重复的情况 curCount++; if (curCount == maxCount) { // root.val 是众数 mode.push_back(root->val); } else if (curCount > maxCount) { // 更新众数 mode.clear(); maxCount = curCount; mode.push_back(root->val); } } if (root->val != prev->val) { // root.val 不重复的情况 curCount = 1; if (curCount == maxCount) { mode.push_back(root->val); } } } // 别忘了更新 prev prev = root; traverse(root->right); } }; ``` ```go // by chatGPT (go) func findMode(root *TreeNode) []int { var mode []int var prev *TreeNode var curCount, maxCount int traverse := func(node *TreeNode) { if node == nil { return } traverse(node.Left) // 中序遍历位置 if prev == nil { // 初始化 curCount = 1 maxCount = 1 mode = append(mode, node.Val) } else { if node.Val == prev.Val { // node.Val 重复的情况 curCount++ if curCount == maxCount { // node.Val 是众数 mode = append(mode, node.Val) } else if curCount > maxCount { // 更新众数 mode = make([]int, 0) maxCount = curCount mode = append(mode, node.Val) } } if node.Val != prev.Val { // node.Val 不重复的情况 curCount = 1 if curCount == maxCount { mode = append(mode, node.Val) } } } // 别忘了更新 prev prev = node traverse(node.Right) } traverse(root) return mode } ``` ```java // by labuladong (java) class Solution { ArrayList mode = new ArrayList<>(); TreeNode prev = null; // 当前元素的重复次数 int curCount = 0; // 全局的最长相同序列长度 int maxCount = 0; public int[] findMode(TreeNode root) { // 执行中序遍历 traverse(root); int[] res = new int[mode.size()]; for (int i = 0; i < res.length; i++) { res[i] = mode.get(i); } return res; } void traverse(TreeNode root) { if (root == null) { return; } traverse(root.left); // 中序遍历位置 if (prev == null) { // 初始化 curCount = 1; maxCount = 1; mode.add(root.val); } else { if (root.val == prev.val) { // root.val 重复的情况 curCount++; if (curCount == maxCount) { // root.val 是众数 mode.add(root.val); } else if (curCount > maxCount) { // 更新众数 mode.clear(); maxCount = curCount; mode.add(root.val); } } if (root.val != prev.val) { // root.val 不重复的情况 curCount = 1; if (curCount == maxCount) { mode.add(root.val); } } } // 别忘了更新 prev prev = root; traverse(root.right); } } ``` ```javascript // by chatGPT (javascript) var findMode = function(root) { let mode = []; let prev = null; // 当前元素的重复次数 let curCount = 0; // 全局的最长相同序列长度 let maxCount = 0; function traverse(root) { if (root === null) { return; } traverse(root.left); // 中序遍历位置 if (prev === null) { // 初始化 curCount = 1; maxCount = 1; mode.push(root.val); } else { if (root.val === prev.val) { // root.val 重复的情况 curCount++; if (curCount === maxCount) { // root.val 是众数 mode.push(root.val); } else if (curCount > maxCount) { // 更新众数 mode = [root.val]; maxCount = curCount; } } if (root.val !== prev.val) { // root.val 不重复的情况 curCount = 1; if (curCount === maxCount) { mode.push(root.val); } } } // 别忘了更新 prev prev = root; traverse(root.right); } traverse(root); return mode; }; ``` ```python # by chatGPT (python) class Solution: def __init__(self): self.mode = [] self.prev = None # 当前元素的重复次数 self.curCount = 0 # 全局的最长相同序列长度 self.maxCount = 0 def findMode(self, root: TreeNode) -> List[int]: # 执行中序遍历 self.traverse(root) return self.mode def traverse(self, root: TreeNode) -> None: if not root: return self.traverse(root.left) # 中序遍历位置 if not self.prev: # 初始化 self.curCount = 1 self.maxCount = 1 self.mode.append(root.val) else: if root.val == self.prev.val: # root.val 重复的情况 self.curCount += 1 if self.curCount == self.maxCount: # root.val 是众数 self.mode.append(root.val) elif self.curCount > self.maxCount: # 更新众数 self.mode.clear() self.maxCount = self.curCount self.mode.append(root.val) if root.val != self.prev.val: # root.val 不重复的情况 self.curCount = 1 if self.curCount == self.maxCount: self.mode.append(root.val) # 别忘了更新 prev self.prev = root self.traverse(root.right) ``` https://leetcode.cn/problems/find-mode-in-binary-search-tree 的多语言解法👆 https://leetcode.cn/problems/first-unique-character-in-a-string 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int firstUniqChar(string s) { int count[26] = {0}; for (char c : s) { // 将字符转化成数字 count[c - 'a']++; } for (int i = 0; i < s.length(); i++) { char c = s[i]; if (count[c - 'a'] == 1) { // 第一个出现一次的字符 return i; } } return -1; } }; ``` ```go // by chatGPT (go) func firstUniqChar(s string) int { // 创建一个大小为26的整数数组count count := make([]int, 26) // 遍历字符串s的每一个字符 for _, c := range s { // 将字符转化成数字 count[c-'a']++ } // 遍历字符串s的每一个字符 for i, c := range s { // 找到第一个出现一次的字符 if count[c-'a'] == 1 { return i } } // 没有出现一次的字符 return -1 } ``` ```java // by labuladong (java) class Solution { public int firstUniqChar(String s) { int[] count = new int[26]; for (char c : s.toCharArray()) { // 将字符转化成数字 count[c - 'a']++; } for (int i = 0; i < s.length(); i++) { char c = s.charAt(i); if (count[c - 'a'] == 1) { // 第一个出现一次的字符 return i; } } return -1; } } ``` ```javascript // by chatGPT (javascript) var firstUniqChar = function(s) { let count = new Array(26).fill(0); for (let i = 0; i < s.length; i++) { // 将字符转化成数字 count[s.charCodeAt(i) - 97]++; } for (let i = 0; i < s.length; i++) { if (count[s.charCodeAt(i) - 97] === 1) { // 第一个出现一次的字符 return i; } } return -1; } ``` ```python # by chatGPT (python) class Solution: def firstUniqChar(self, s: str) -> int: count = [0]*26 for c in s: # 将字符转化成数字 count[ord(c) - ord('a')] += 1 for i in range(len(s)): c = s[i] if count[ord(c) - ord('a')] == 1: # 第一个出现一次的字符 return i return -1 ``` https://leetcode.cn/problems/first-unique-character-in-a-string 的多语言解法👆 https://leetcode.cn/problems/flatten-binary-tree-to-linked-list 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: // 定义:将以 root 为根的树拉平为链表 void flatten(TreeNode* root) { // base case if (root == nullptr) return; // 先递归拉平左右子树 flatten(root->left); flatten(root->right); /****后序遍历位置****/ // 1、左右子树已经被拉平成一条链表 TreeNode* left = root->left; TreeNode* right = root->right; // 2、将左子树作为右子树 root->left = nullptr; root->right = left; // 3、将原先的右子树接到当前右子树的末端 TreeNode* p = root; while (p->right != nullptr) { p = p->right; } p->right = right; /** ![](../pictures/二叉树系列/2.jpeg) */ } }; ``` ```go // by chatGPT (go) // 定义一个 TreeNode 结构体 // 定义解题函数 func flatten(root *TreeNode) { // base case if root == nil { return } // 先递归拉平左右子树 flatten(root.Left) flatten(root.Right) /****后序遍历位置****/ // 1、左右子树已经被拉平成一条链表 left := root.Left right := root.Right // 2、将左子树作为右子树 root.Left = nil root.Right = left // 3、将原先的右子树接到当前右子树的末端 p := root for p.Right != nil { p = p.Right } p.Right = right } ``` ```java // by labuladong (java) class Solution { // 定义:将以 root 为根的树拉平为链表 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; /** ![](../pictures/二叉树系列/2.jpeg) */ } } ``` ```javascript // by chatGPT (javascript) /** * @param {TreeNode} root * @return {void} Do not return anything, modify root in-place instead. */ var flatten = function(root) { // base case if (root == null) return; // 先递归拉平左右子树 flatten(root.left); flatten(root.right); /****后序遍历位置****/ // 1、左右子树已经被拉平成一条链表 var left = root.left; var right = root.right; // 2、将左子树作为右子树 root.left = null; root.right = left; // 3、将原先的右子树接到当前右子树的末端 var p = root; while (p.right != null) { p = p.right; } p.right = right; }; ``` ```python # by chatGPT (python) class Solution: def flatten(self, root: TreeNode) -> None: """ Do not return anything, modify root in-place instead. """ # base case if not root: return # 先递归拉平左右子树 self.flatten(root.left) self.flatten(root.right) # 后序遍历位置 # 1、左右子树已经被拉平成一条链表 left = root.left right = root.right # 2、将左子树作为右子树 root.left = None root.right = left # 3、将原先的右子树接到当前右子树的末端 p = root while p.right: p = p.right p.right = right ``` https://leetcode.cn/problems/flatten-binary-tree-to-linked-list 的多语言解法👆 https://leetcode.cn/problems/flatten-nested-list-iterator 的多语言解法👇 ```cpp // by chatGPT (cpp) class NestedIterator { private: std::deque list; public: NestedIterator(std::vector &nestedList) { // 不直接用 nestedList 的引用,是因为不能确定它的底层实现 // 必须保证是 deque,否则下面的 push_front 会很低效 list = std::deque(nestedList.begin(), nestedList.end()); } int next() { // hasNext 方法保证了第一个元素一定是整数类型 int val = list.front().getInteger(); list.pop_front(); return val; } bool hasNext() { // 循环拆分列表元素,直到列表第一个元素是整数类型 while (!list.empty() && !list.front().isInteger()) { // 当列表开头第一个元素是列表类型时,进入循环 std::vector first = list.front().getList(); // 将第一个列表打平并按顺序添加到开头 list.pop_front(); for (int i = first.size() - 1; i >= 0; i--) { list.push_front(first[i]); } } return !list.empty(); } }; ``` ```go // by chatGPT (go) type NestedIterator struct { list []NestedInteger } func Constructor(nestedList []*NestedInteger) *NestedIterator { list := make([]NestedInteger, 0) for _, nestedInt := range nestedList { list = append(list, *nestedInt) } return &NestedIterator{list} } func (this *NestedIterator) Next() int { // hasNext 方法保证了第一个元素一定是整数类型 res := this.list[0].GetInteger() this.list = this.list[1:] return res } func (this *NestedIterator) HasNext() bool { // 循环拆分列表元素,直到列表第一个元素是整数类型 for len(this.list) > 0 && !this.list[0].IsInteger() { // 当列表开头第一个元素是列表类型时,进入循环 first := this.list[0].GetList() this.list = this.list[1:] // 将第一个列表打平并按顺序添加到开头 for i := len(first) - 1; i >= 0; i-- { this.list = append([]NestedInteger{first[i]}, this.list...) } } return len(this.list) > 0 } ``` ```java // by labuladong (java) public class NestedIterator implements Iterator { private LinkedList list; public NestedIterator(List nestedList) { // 不直接用 nestedList 的引用,是因为不能确定它的底层实现 // 必须保证是 LinkedList,否则下面的 addFirst 会很低效 list = new LinkedList<>(nestedList); } public Integer next() { // hasNext 方法保证了第一个元素一定是整数类型 return list.remove(0).getInteger(); } public boolean hasNext() { // 循环拆分列表元素,直到列表第一个元素是整数类型 while (!list.isEmpty() && !list.get(0).isInteger()) { // 当列表开头第一个元素是列表类型时,进入循环 List first = list.remove(0).getList(); // 将第一个列表打平并按顺序添加到开头 for (int i = first.size() - 1; i >= 0; i--) { list.addFirst(first.get(i)); } } return !list.isEmpty(); } } ``` ```javascript // by chatGPT (javascript) /** * @constructor * @param {Array} nestedList */ var NestedIterator = function(nestedList) { // 不直接用 nestedList 的引用,是因为不能确定它的底层实现 // 必须保证是 LinkedList,否则下面的 addFirst 会很低效 this.list = new LinkedList(nestedList); }; /** * @this NestedIterator * @returns {number} */ NestedIterator.prototype.next = function() { // hasNext 方法保证了第一个元素一定是整数类型 return this.list.remove(0).getInteger(); }; /** * @this NestedIterator * @returns {boolean} */ NestedIterator.prototype.hasNext = function() { // 循环拆分列表元素,直到列表第一个元素是整数类型 while (!this.list.isEmpty() && !this.list.get(0).isInteger()) { // 当列表开头第一个元素是列表类型时,进入循环 const first = this.list.remove(0).getList(); // 将第一个列表打平并按顺序添加到开头 for (let i = first.length - 1; i >= 0; i--) { this.list.addFirst(first[i]); } } return !this.list.isEmpty(); }; ``` ```python # by chatGPT (python) class NestedIterator: def __init__(self, nestedList: [NestedInteger]): # 不直接用 nestedList 的引用,是因为不能确定它的底层实现 # 必须保证是 LinkedList,否则下面的 addFirst 会很低效 self.list = deque(nestedList) def next(self) -> int: # hasNext 方法保证了第一个元素一定是整数类型 return self.list.popleft().getInteger() def hasNext(self) -> bool: # 循环拆分列表元素,直到列表第一个元素是整数类型 while self.list and not self.list[0].isInteger(): # 当列表开头第一个元素是列表类型时,进入循环 first = self.list.popleft().getList() # 将第一个列表打平并按顺序添加到开头 for i in range(len(first)-1, -1, -1): self.list.appendleft(first[i]) return bool(self.list) ``` https://leetcode.cn/problems/flatten-nested-list-iterator 的多语言解法👆 https://leetcode.cn/problems/flip-game 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: vector generatePossibleNextMoves(string currentState) { vector res; for (int i = 1; i < currentState.size(); i++) { if (currentState[i] == '+' && currentState[i - 1] == '+') { // 做选择 currentState[i] = '-'; currentState[i - 1] = '-'; res.push_back(currentState); // 撤销选择 currentState[i] = '+'; currentState[i - 1] = '+'; } } return res; } }; ``` ```go // by chatGPT (go) func generatePossibleNextMoves(currentState string) []string { res := []string{} arr := []rune(currentState) for i := 1; i < len(arr); i++ { if arr[i] == '+' && arr[i-1] == '+' { // 做选择 arr[i] = '-' arr[i-1] = '-' res = append(res, string(arr)) // 撤销选择 arr[i] = '+' arr[i-1] = '+' } } return res } ``` ```java // by labuladong (java) class Solution { public List generatePossibleNextMoves(String currentState) { List res = new ArrayList<>(); char[] arr = currentState.toCharArray(); for (int i = 1; i < arr.length; i++) { if (arr[i] == '+' && arr[i - 1] == '+') { // 做选择 arr[i] = '-'; arr[i - 1] = '-'; res.add(new String(arr)); // 撤销选择 arr[i] = '+'; arr[i - 1] = '+'; } } return res; } } ``` ```javascript // by chatGPT (javascript) var generatePossibleNextMoves = function(currentState) { var res = []; var arr = Array.from(currentState); for (var i = 1; i < arr.length; i++) { if (arr[i] === '+' && arr[i - 1] === '+') { // 做选择 arr[i] = '-'; arr[i - 1] = '-'; res.push(arr.join('')); // 撤销选择 arr[i] = '+'; arr[i - 1] = '+'; } } return res; } ``` ```python # by chatGPT (python) class Solution(object): def generatePossibleNextMoves(self, currentState): """ :type currentState: str :rtype: List[str] """ res = [] for i in range(1, len(currentState)): if currentState[i] == '+' and currentState[i - 1] == '+': # 做选择 res.append(currentState[:i - 1] + '--' + currentState[i + 1:]) # 撤销选择 is not needed in python due to its immutability of string return res ``` https://leetcode.cn/problems/flip-game 的多语言解法👆 https://leetcode.cn/problems/flip-game-ii 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: // 直接把 293 的代码 copy 过来,生成所有可能的下一步 vector generatePossibleNextMoves(string currentState) { vector res; for (int i = 1; i < currentState.size(); i++) { if (currentState[i] == '+' && currentState[i - 1] == '+') { // 做选择 currentState[i] = '-'; currentState[i - 1] = '-'; res.push_back(currentState); // 撤销选择 currentState[i] = '+'; currentState[i - 1] = '+'; } } return res; } // 备忘录 unordered_map memo; bool canWin(string currentState) { // 记入备忘录 memo.clear(); return dp(currentState); } // 定义:输入字符串 s,返回在此局面下先手是否可能赢 bool dp(string s) { if (memo.find(s) != memo.end()) { // 之前遇到过这种局面,直接返回结果 return memo[s]; } vector nextStates = generatePossibleNextMoves(s); bool res = false; for (string nextState : nextStates) { // 只要有一个是 false,说明当前玩家赢了 bool win = dp(nextState); // 后序位置,做判断 if (!win) { res = true; break; } } // 记入备忘录 memo[s] = res; return res; } }; ``` ```go // by chatGPT (go) import "strings" // 直接把 293 的代码 copy 过来,生成所有可能的下一步 func generatePossibleNextMoves(currentState string) []string { res := make([]string, 0) arr := []rune(currentState) for i := 1; i < len(arr); i++ { if arr[i] == '+' && arr[i-1] == '+' { // making a choice // 做选择 arr[i] = '-' arr[i-1] = '-' res = append(res, string(arr)) // undo choice // 撤销选择 arr[i] = '+' arr[i-1] = '+' } } return res } func canWin(currentState string) bool { // Definition: Enter string s, return whether the first hand can win in this face. // 定义:输入字符串 s,返回在此局面下先手是否可能赢 var dp func(string, map[string]bool) bool dp = func(s string, memo map[string]bool) bool { if _, ok := memo[s]; ok { // have encountered this position before, return the result directly // 之前遇到过这种局面,直接返回结果 return memo[s] } res := false nextStates := generatePossibleNextMoves(s) for _, nextState := range nextStates { // As long as one is false, it means the current player won // 只要有一个是 false,说明当前玩家赢了 win := dp(nextState, memo) // subsequent position, make a judgment // 后序位置,做判断 if !win { res = true break } } // record in the memo // 记入备忘录 memo[s] = res return res } // memo is a map that acts as the memoization for dp. // 备忘录 memo := make(map[string]bool) return dp(currentState, memo) } ``` ```java // by labuladong (java) class Solution { // 直接把 293 的代码 copy 过来,生成所有可能的下一步 List generatePossibleNextMoves(String currentState) { List res = new ArrayList<>(); char[] arr = currentState.toCharArray(); for (int i = 1; i < arr.length; i++) { if (arr[i] == '+' && arr[i - 1] == '+') { // 做选择 arr[i] = '-'; arr[i - 1] = '-'; res.add(new String(arr)); // 撤销选择 arr[i] = '+'; arr[i - 1] = '+'; } } return res; } // 备忘录 Map memo = new HashMap<>(); public boolean canWin(String currentState) { // 记入备忘录 memo.clear(); return dp(currentState); } // 定义:输入字符串 s,返回在此局面下先手是否可能赢 boolean dp(String s) { if (memo.containsKey(s)) { // 之前遇到过这种局面,直接返回结果 return memo.get(s); } boolean res = false; List nextStates = generatePossibleNextMoves(s); for (String nextState : nextStates) { // 只要有一个是 false,说明当前玩家赢了 boolean win = dp(nextState); // 后序位置,做判断 if (!win) { res = true; break; } } // 记入备忘录 memo.put(s, res); return res; } } ``` ```python # by chatGPT (python) class Solution: # 直接把 293 的代码 copy 过来,生成所有可能的下一步 def generatePossibleNextMoves(self, currentState): res = [] arr = list(currentState) for i in range(1, len(arr)): if arr[i] == '+' and arr[i - 1] == '+': # 做选择 arr[i] = '-' arr[i - 1] = '-' res.append("".join(arr)) # 撤销选择 arr[i] = '+' arr[i - 1] = '+' return res # 备忘录 memo = {} def canWin(self, currentState): # 记入备忘录 self.memo.clear() return self.dp(currentState) # 定义:输入字符串 s,返回在此局面下先手是否可能赢 def dp(self, s): if s in self.memo: # 之前遇到过这种局面,直接返回结果 return self.memo[s] res = False nextStates = self.generatePossibleNextMoves(s) for nextState in nextStates: # 只要有一个是 false,说明当前玩家赢了 win = self.dp(nextState) # 后序位置,做判断 if not win: res = True break # 记入备忘录 self.memo[s] = res return res ``` https://leetcode.cn/problems/flip-game-ii 的多语言解法👆 https://leetcode.cn/problems/freedom-trail 的多语言解法👇 ```cpp // by labuladong (cpp) class Solution { public: // 字符 -> 索引列表 unordered_map> charToIndex; // 备忘录 vector> memo; /* 主函数 */ int findRotateSteps(string ring, string key) { int m = ring.size(); int n = key.size(); // 备忘录全部初始化为 0 memo.resize(m, vector(n, 0)); // 记录圆环上字符到索引的映射 for (int i = 0; i < ring.size(); i++) { charToIndex[ring[i]].push_back(i); } // 圆盘指针最初指向 12 点钟方向, // 从第一个字符开始输入 key return dp(ring, 0, key, 0); } // 计算圆盘指针在 ring[i],输入 key[j..] 的最少操作数 int dp(string& ring, int i, string& key, int j) { // base case 完成输入 if (j == key.size()) return 0; // 查找备忘录,避免重叠子问题 if (memo[i][j] != 0) return memo[i][j]; int n = ring.size(); // 做选择 int res = INT_MAX; // ring 上可能有多个字符 key[j] for (int k : charToIndex[key[j]]) { // 拨动指针的次数 int delta = abs(k - i); // 选择顺时针还是逆时针 delta = min(delta, n - delta); // 将指针拨到 ring[k],继续输入 key[j+1..] int subProblem = dp(ring, k, key, j + 1); // 选择「整体」操作次数最少的 // 加一是因为按动按钮也是一次操作 res = min(res, 1 + delta + subProblem); } // 将结果存入备忘录 memo[i][j] = res; return res; } }; ``` ```go // by chatGPT (go) // 计算两个数相减的绝对值 func abs(a, b int) int { if a > b { return b - a + 2*b } else { return a - b + 2*b } } // 返回两个数的最小值 func min(a, b int) int { if a < b { return a } else { return b } } /* 主函数 */ func findRotateSteps(ring string, key string) int { m := len(ring) n := len(key) // 备忘录全部初始化为 0 memo := make([][]int, m) for i := range memo { memo[i] = make([]int, n) } // 记录圆环上字符到索引的映射 charToIndex := make(map[byte][]int) for i := range ring { charToIndex[ring[i]] = append(charToIndex[ring[i]], i) } // 圆盘指针最初指向 12 点钟方向, // 从第一个字符开始输入 key return dp(ring, 0, key, 0, charToIndex, memo) } // 计算圆盘指针在 ring[i],输入 key[j..] 的最少操作数 func dp(ring string, i int, key string, j int, charToIndex map[byte][]int, memo [][]int) int { // base case 完成输入 if j == len(key) { return 0 } // 查找备忘录,避免重叠子问题 if memo[i][j] != 0 { return memo[i][j] } n := len(ring) // 做选择 res := math.MaxInt32 // ring 上可能有多个字符 key[j] for _, k := range charToIndex[key[j]] { // 拨动指针的次数 delta := abs(k-i, n) // 选择顺时针还是逆时针 delta = min(delta, n-delta) // 将指针拨到 ring[k],继续输入 key[j+1..] subProblem := dp(ring, k, key, j+1, charToIndex, memo) // 选择「整体」操作次数最少的 // 加一是因为按动按钮也是一次操作 res = min(res, 1+delta+subProblem) } // 将结果存入备忘录 memo[i][j] = res return res } ``` ```java // by chatGPT (java) class Solution { // 字符 -> 索引列表 Map> charToIndex = new HashMap<>(); // 备忘录 int[][] memo; /* 主函数 */ public int findRotateSteps(String ring, String key) { int m = ring.length(); int n = key.length(); // 备忘录全部初始化为 0 memo = new int[m][n]; for (int[] row : memo) { Arrays.fill(row, 0); } // 记录圆环上字符到索引的映射 for (int i = 0; i < ring.length(); i++) { char c = ring.charAt(i); List list = charToIndex.getOrDefault(c, new ArrayList<>()); list.add(i); charToIndex.put(c, list); } // 圆盘指针最初指向 12 点钟方向, // 从第一个字符开始输入 key return dp(ring, 0, key, 0); } // 计算圆盘指针在 ring[i],输入 key[j..] 的最少操作数 private int dp(String ring, int i, String key, int j) { // base case 完成输入 if (j == key.length()) return 0; // 查找备忘录,避免重叠子问题 if (memo[i][j] != 0) return memo[i][j]; int n = ring.length(); // 做选择 int res = Integer.MAX_VALUE; char c = key.charAt(j); // ring 上可能有多个字符 key[j] for (int k : charToIndex.getOrDefault(c, new ArrayList<>())) { // 拨动指针的次数 int delta = Math.abs(k - i); // 选择顺时针还是逆时针 delta = Math.min(delta, n - delta); // 将指针拨到 ring[k],继续输入 key[j+1..] int subProblem = dp(ring, k, key, j + 1); // 选择「整体」操作次数最少的 // 加一是因为按动按钮也是一次操作 res = Math.min(res, 1 + delta + subProblem); } // 将结果存入备忘录 memo[i][j] = res; return res; } } ``` ```javascript // by chatGPT (javascript) var findRotateSteps = function(ring, key) { // 字符 -> 索引列表 const charToIndex = {}; // 备忘录 const memo = []; /* 记录圆环上字符到索引的映射 */ for (let i = 0; i < ring.length; i++) { const char = ring.charAt(i); if (charToIndex[char] === undefined) { charToIndex[char] = []; } charToIndex[char].push(i); } /* 计算圆盘指针在 ring[i],输入 key[j..] 的最少操作数 */ function dp(i, j) { // base case 完成输入 if (j === key.length) { return 0; } // 查找备忘录,避免重叠子问题 if (memo[i] !== undefined && memo[i][j] !== undefined) { return memo[i][j]; } const n = ring.length; // 做选择 let res = Infinity; // ring 上可能有多个字符 key[j] for (let k of charToIndex[key.charAt(j)]) { // 拨动指针的次数 let delta = Math.abs(k - i); // 选择顺时针还是逆时针 delta = Math.min(delta, n - delta); // 将指针拨到 ring[k],继续输入 key[j+1..] const subProblem = dp(k, j + 1); // 选择「整体」操作次数最少的 // 加一是因为按动按钮也是一次操作 res = Math.min(res, 1 + delta + subProblem); } // 将结果存入备忘录 if (memo[i] === undefined) { memo[i] = []; } memo[i][j] = res; return res; } return dp(0, 0); }; ``` ```python # by chatGPT (python) class Solution: def __init__(self): # 字符 -> 索引列表 self.charToIndex = {} # 备忘录 self.memo = [] # 主函数 def findRotateSteps(self, ring: str, key: str) -> int: m = len(ring) n = len(key) # 备忘录全部初始化为 0 self.memo = [[0] * n for _ in range(m)] # 记录圆环上字符到索引的映射 for i in range(m): char = ring[i] if char in self.charToIndex: self.charToIndex[char].append(i) else: self.charToIndex[char] = [i] # 圆盘指针最初指向 12 点钟方向, # 从第一个字符开始输入 key return self.dp(ring, 0, key, 0) # 计算圆盘指针在 ring[i],输入 key[j..] 的最少操作数 def dp(self, ring: str, i: int, key: str, j: int) -> int: # base case 完成输入 if j == len(key): return 0 # 查找备忘录,避免重叠子问题 if self.memo[i][j] != 0: return self.memo[i][j] n = len(ring) # 做选择 res = float('inf') # ring 上可能有多个字符 key[j] for k in self.charToIndex[key[j]]: # 拨动指针的次数 delta = abs(k - i) # 选择顺时针还是逆时针 delta = min(delta, n - delta) # 将指针拨到 ring[k],继续输入 key[j+1..] subProblem = self.dp(ring, k, key, j + 1) # 选择「整体」操作次数最少的 # 加一是因为按动按钮也是一次操作 res = min(res, 1 + delta + subProblem) # 将结果存入备忘录 self.memo[i][j] = res return res ``` https://leetcode.cn/problems/freedom-trail 的多语言解法👆 https://leetcode.cn/problems/fu-za-lian-biao-de-fu-zhi-lcof 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: Node* copyRandomList(Node* head) { unordered_map originToClone; // 第一次遍历,先把所有节点克隆出来 for (Node* p = head; p != nullptr; p = p->next) { if (originToClone.find(p) == originToClone.end()) { originToClone[p] = new Node(p->val); } } // 第二次遍历,把克隆节点的结构连接好 for (Node* p = head; p != nullptr; p = p->next) { if (p->next != nullptr) { originToClone[p]->next = originToClone[p->next]; } if (p->random != nullptr) { originToClone[p]->random = originToClone[p->random]; } } // 返回克隆之后的头结点 return originToClone[head]; } }; // 用递归的方式进行遍历 class Solution2 { public: Node* copyRandomList(Node* head) { traverse(head); return originToClone[head]; } private: // 记录 DFS 遍历过的节点,防止走回头路 unordered_set visited; // 记录原节点到克隆节点的映射 unordered_map originToClone; // DFS 图遍历框架 void traverse(Node* node) { if (node == nullptr) { return; } if (visited.find(node) != visited.end()) { return; } // 前序位置,标记为已访问 visited.insert(node); // 前序位置,克隆节点 if (originToClone.find(node) == originToClone.end()) { originToClone[node] = new Node(node->val); } Node* cloneNode = originToClone[node]; // 递归遍历邻居节点,并构建克隆图 // 递归之后,邻居节点一定存在 originToClone 中 traverse(node->next); cloneNode->next = originToClone[node->next]; traverse(node->random); cloneNode->random = originToClone[node->random]; } }; ``` ```go // by chatGPT (go) /** * Definition for a Node. * type Node struct { * Val int * Next *Node * Random *Node * } */ func copyRandomList(head *Node) *Node { originToClone := make(map[*Node]*Node) // 第一次遍历,先把所有节点克隆出来 for p := head; p != nil; p = p.Next { if _, ok := originToClone[p]; !ok { originToClone[p] = &Node{Val:p.Val} } } // 第二次遍历,把克隆节点的结构连接好 for p := head; p != nil; p = p.Next { if p.Next != nil { originToClone[p].Next = originToClone[p.Next] } if p.Random != nil { originToClone[p].Random = originToClone[p.Random] } } // 返回克隆之后的头结点 return originToClone[head] } // 用递归的方式进行遍历 func copyRandomList2(head *Node) *Node { if head == nil { return nil } visited := make(map[*Node]bool) originToClone := make(map[*Node]*Node) traverse(head, visited, originToClone) return originToClone[head] } // DFS 图遍历框架 func traverse(node *Node, visited map[*Node]bool, originToClone map[*Node]*Node) { if visited[node] { return } // 前序位置,标记为已访问 visited[node] = true // 前序位置,克隆节点 if _, ok := originToClone[node]; !ok { originToClone[node] = &Node{Val:node.Val} } cloneNode := originToClone[node] // 递归遍历邻居节点,并构建克隆图 // 递归之后,邻居节点一定存在 originToClone 中 if node.Next != nil { traverse(node.Next, visited, originToClone) cloneNode.Next = originToClone[node.Next] } if node.Random != nil { traverse(node.Random, visited, originToClone) cloneNode.Random = originToClone[node.Random] } } ``` ```java // by labuladong (java) class Solution { public Node copyRandomList(Node head) { HashMap originToClone = new HashMap<>(); // 第一次遍历,先把所有节点克隆出来 for (Node p = head; p != null; p = p.next) { if (!originToClone.containsKey(p)) { originToClone.put(p, new Node(p.val)); } } // 第二次遍历,把克隆节点的结构连接好 for (Node p = head; p != null; p = p.next) { if (p.next != null) { originToClone.get(p).next = originToClone.get(p.next); } if (p.random != null) { originToClone.get(p).random = originToClone.get(p.random); } } // 返回克隆之后的头结点 return originToClone.get(head); } } // 用递归的方式进行遍历 class Solution2 { public Node copyRandomList(Node head) { traverse(head); return originToClone.get(head); } // 记录 DFS 遍历过的节点,防止走回头路 HashSet visited = new HashSet<>(); // 记录原节点到克隆节点的映射 HashMap originToClone = new HashMap<>(); // DFS 图遍历框架 void traverse(Node node) { if (node == null) { return; } if (visited.contains(node)) { return; } // 前序位置,标记为已访问 visited.add(node); // 前序位置,克隆节点 if (!originToClone.containsKey(node)) { originToClone.put(node, new Node(node.val)); } Node cloneNode = originToClone.get(node); // 递归遍历邻居节点,并构建克隆图 // 递归之后,邻居节点一定存在 originToClone 中 traverse(node.next); cloneNode.next = originToClone.get(node.next); traverse(node.random); cloneNode.random = originToClone.get(node.random); } } ``` ```javascript // by chatGPT (javascript) var copyRandomList = function(head) { const originToClone = new Map(); // 第一次遍历,先把所有节点克隆出来 for (let p = head; p != null; p = p.next) { if (!originToClone.has(p)) { originToClone.set(p, new Node(p.val)); } } // 第二次遍历,把克隆节点的结构连接好 for (let p = head; p != null; p = p.next) { if (p.next != null) { originToClone.get(p).next = originToClone.get(p.next); } if (p.random != null) { originToClone.get(p).random = originToClone.get(p.random); } } // 返回克隆之后的头结点 return originToClone.get(head); }; // 用递归的方式进行遍历 var copyRandomList2 = function(head) { const visited = new Set(); const originToClone = new Map(); // DFS 图遍历框架 const traverse = (node) => { if (node == null) { return; } if (visited.has(node)) { return; } // 前序位置,标记为已访问 visited.add(node); // 前序位置,克隆节点 if (!originToClone.has(node)) { originToClone.set(node, new Node(node.val)); } const cloneNode = originToClone.get(node); // 递归遍历邻居节点,并构建克隆图 // 递归之后,邻居节点一定存在 originToClone 中 traverse(node.next); cloneNode.next = originToClone.get(node.next); traverse(node.random); cloneNode.random = originToClone.get(node.random); }; traverse(head); return originToClone.get(head); }; ``` ```python # by chatGPT (python) class Solution: def copyRandomList(self, head: 'Node') -> 'Node': originToClone = {} # 第一次遍历,先把所有节点克隆出来 for p in [head]: while p: if p not in originToClone: originToClone[p] = Node(p.val) p = p.next # 第二次遍历,把克隆节点的结构连接好 for p in [head]: while p: if p.next: originToClone[p].next = originToClone[p.next] if p.random: originToClone[p].random = originToClone[p.random] p = p.next # 返回克隆之后的头结点 return originToClone.get(head) class Solution2: def copyRandomList(self, head: 'Node') -> 'Node': self.visited = set() self.originToClone = {} self.traverse(head) return self.originToClone.get(head) # DFS 图遍历框架 def traverse(self, node): if not node: return if node in self.visited: return # 前序位置,标记为已访问 self.visited.add(node) # 前序位置,克隆节点 if node not in self.originToClone: self.originToClone[node] = Node(node.val) cloneNode = self.originToClone[node] # 递归遍历邻居节点,并构建克隆图 # 递归之后,邻居节点一定存在 originToClone 中 self.traverse(node.next) cloneNode.next = self.originToClone.get(node.next) self.traverse(node.random) cloneNode.random = self.originToClone.get(node.random) ``` https://leetcode.cn/problems/fu-za-lian-biao-de-fu-zhi-lcof 的多语言解法👆 https://leetcode.cn/problems/g5c51o 的多语言解法👇 ```cpp // by chatGPT (cpp) // 用优先级队列解决这道题 class Solution { public: vector topKFrequent(vector& nums, int k) { // nums 中的元素 -> 该元素出现的频率 unordered_map valToFreq; for (int v : nums) { valToFreq[v]++; } priority_queue, vector>, greater>> pq; // 队列按照键值对中的值(元素出现频率)从小到大排序 for (auto entry : valToFreq) { pq.push(make_pair(entry.second, entry.first)); if (pq.size() > k) { // 弹出最小元素,维护队列内是 k 个频率最大的元素 pq.pop(); } } vector res(k); for (int i = 0; i < k; i++) { // res 数组中存储前 k 个最大元素 res[i] = pq.top().second; pq.pop(); } return res; } }; // 用计数排序的方法解决这道题 class Solution2 { public: vector topKFrequent(vector& nums, int k) { // nums 中的元素 -> 该元素出现的频率 unordered_map valToFreq; for (int v : nums) { valToFreq[v]++; } // 频率 -> 这个频率有哪些元素 vector> freqToVals(nums.size() + 1); for (auto entry : valToFreq) { int val = entry.first; int freq = entry.second; freqToVals[freq].push_back(val); } vector res; // freqToVals 从后往前存储着出现最多的元素 for (int i = freqToVals.size() - 1; i >= 0; i--) { if (freqToVals[i].size() == 0) continue; for (int j = 0; j < freqToVals[i].size(); j++) { // 将出现次数最多的 k 个元素装入 res res.push_back(freqToVals[i][j]); if (res.size() == k) { return res; } } } return res; } }; ``` ```go // by chatGPT (go) // 用优先级队列解决这道题 func topKFrequent(nums []int, k int) []int { // nums 中的元素 -> 该元素出现的频率 valToFreq := make(map[int]int) for _, v := range nums { valToFreq[v] = valToFreq[v] + 1 } // 个性化的 lambda lessFn := func(a, b interface{}) bool { a.(*MapEntry).Value.(int) return a.(*MapEntry).Value.(int) < b.(*MapEntry).Value.(int) } pq := priorityqueue.NewPriorityQueue(lessFn) for key, val := range valToFreq { pq.Insert(&MapEntry{Key: key, Value: val}) if pq.Len() > k { pq.Pop() } } res := make([]int, k) for i := k - 1; i >= 0; i-- { // res 数组中存储前 k 个最大元素 res[i] = pq.Pop().(*MapEntry).Key.(int) } return res } // MapEntry 提供给优先级队列使用的数据结构 type MapEntry struct { Key interface{} Value interface{} } // 用计数排序的方法解决这道题 func topKFrequent2(nums []int, k int) []int { // nums 中的元素 -> 该元素出现的频率 valToFreq := make(map[int]int) for _, v := range nums { valToFreq[v] = valToFreq[v] + 1 } // 频率 -> 这个频率有哪些元素 freqToVals := make([][]int, len(nums)+1) for key, val := range valToFreq { if freqToVals[val] == nil { freqToVals[val] = make([]int, 0) } freqToVals[val] = append(freqToVals[val], key) } res := make([]int, k) p := 0 // freqToVals 从后往前存储着出现最多的元素 for i := len(freqToVals) - 1; i > 0; i-- { valList := freqToVals[i] if valList == nil { continue } for _, val := range valList { // 将出现次数最多的 k 个元素装入 res res[p] = val p++ if p == k { return res } } } return nil } ``` ```java // by labuladong (java) // 用优先级队列解决这道题 class Solution { public int[] topKFrequent(int[] nums, int k) { // nums 中的元素 -> 该元素出现的频率 HashMap valToFreq = new HashMap<>(); for (int v : nums) { valToFreq.put(v, valToFreq.getOrDefault(v, 0) + 1); } PriorityQueue> pq = new PriorityQueue<>((entry1, entry2) -> { // 队列按照键值对中的值(元素出现频率)从小到大排序 return entry1.getValue().compareTo(entry2.getValue()); }); for (Map.Entry entry : valToFreq.entrySet()) { pq.offer(entry); if (pq.size() > k) { // 弹出最小元素,维护队列内是 k 个频率最大的元素 pq.poll(); } } int[] res = new int[k]; for (int i = k - 1; i >= 0; i--) { // res 数组中存储前 k 个最大元素 res[i] = pq.poll().getKey(); } return res; } } // 用计数排序的方法解决这道题 class Solution2 { public int[] topKFrequent(int[] nums, int k) { // nums 中的元素 -> 该元素出现的频率 HashMap valToFreq = new HashMap<>(); for (int v : nums) { valToFreq.put(v, valToFreq.getOrDefault(v, 0) + 1); } // 频率 -> 这个频率有哪些元素 ArrayList[] freqToVals = new ArrayList[nums.length + 1]; for (int val : valToFreq.keySet()) { int freq = valToFreq.get(val); if (freqToVals[freq] == null) { freqToVals[freq] = new ArrayList<>(); } freqToVals[freq].add(val); } int[] res = new int[k]; int p = 0; // freqToVals 从后往前存储着出现最多的元素 for (int i = freqToVals.length - 1; i > 0; i--) { ArrayList valList = freqToVals[i]; if (valList == null) continue; for (int j = 0; j < valList.size(); j++) { // 将出现次数最多的 k 个元素装入 res res[p] = valList.get(j); p++; if (p == k) { return res; } } } return null; } } ``` ```javascript // by chatGPT (javascript) /** * @param {number[]} nums * @param {number} k * @return {number[]} */ var topKFrequent = function(nums, k) { // nums 中的元素 -> 该元素出现的频率 const valToFreq = new Map(); for (const v of nums) { valToFreq.set(v, (valToFreq.get(v) || 0) + 1); } const compare = (entry1, entry2) => entry1[1] - entry2[1]; // 队列按照键值对中的值(元素出现频率)从小到大排序 const pq = new PriorityQueue(compare); for (const entry of valToFreq.entries()) { pq.push(entry); if (pq.size() > k) { // 弹出最小元素,维护队列内是 k 个频率最大的元素 pq.pop(); } } const res = new Array(k); for (let i = k - 1; i >= 0; i--) { // res 数组中存储前 k 个最大元素 res[i] = pq.pop()[0]; } return res; }; /** * @param {number[]} nums * @param {number} k * @return {number[]} */ var topKFrequent2 = function(nums, k) { // nums 中的元素 -> 该元素出现的频率 const valToFreq = new Map(); for (const v of nums) { valToFreq.set(v, (valToFreq.get(v) || 0) + 1); } // 频率 -> 这个频率有哪些元素 const maxFreq = nums.length; const freqToVals = new Array(maxFreq + 1).map(() => new Array()); for (const [val, freq] of valToFreq.entries()) { freqToVals[freq].push(val); } const res = new Array(k); let p = 0; // freqToVals 从后往前存储着出现最多的元素 for (let freq = maxFreq; freq > 0; freq--) { const valList = freqToVals[freq]; for (const val of valList) { // 将出现次数最多的 k 个元素装入 res res[p] = val; p++; if (p === k) { return res; } } } return null; } ``` ```python # by chatGPT (python) class Solution: def topKFrequent(self, nums: List[int], k: int) -> List[int]: # nums 中的元素 -> 该元素出现的频率 valToFreq = {} for v in nums: valToFreq[v] = valToFreq.get(v, 0) + 1 # 二叉堆按照键值对中的值(元素出现频率的负值)从小到大排列 # 从二叉堆中pop出来的就是频率最大的键 (频率越大,负值越小) pq = [(-freq, val) for val, freq in valToFreq.items()] heapq.heapify(pq) # 将前 k 个最大元素装入 res res = [] for i in range(k): res.append(heapq.heappop(pq)[1]) return res class Solution2: def topKFrequent(self, nums: List[int], k: int) -> List[int]: # nums 中的元素 -> 该元素出现的频率 valToFreq = {} for v in nums: valToFreq[v] = valToFreq.get(v, 0) + 1 # 频率 -> 这个频率有哪些元素 freqToVals = [[] for _ in range(len(nums) + 1)] for val, freq in valToFreq.items(): freqToVals[freq].append(val) # freqToVals 从后往前存储着出现最多的元素 res = [] for i in range(len(nums), 0, -1): if freqToVals[i]: for val in freqToVals[i]: res.append(val) if len(res) == k: return res return res ``` https://leetcode.cn/problems/g5c51o 的多语言解法👆 https://leetcode.cn/problems/gaM7Ch 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: vector memo; int coinChange(vector& coins, int amount) { memo.resize(amount + 1, -666); // dp 数组全都初始化为特殊值 return dp(coins, amount); } int dp(vector& coins, int amount) { if (amount == 0) return 0; if (amount < 0) return -1; // 查备忘录,防止重复计算 if (memo[amount] != -666) return memo[amount]; int res = INT_MAX; for (int coin : coins) { // 计算子问题的结果 int subProblem = dp(coins, amount - coin); /** ![](../pictures/动态规划详解进阶/5.jpg) */ // 子问题无解则跳过 if (subProblem == -1) continue; // 在子问题中选择最优解,然后加一 res = min(res, subProblem + 1); } // 把计算结果存入备忘录 memo[amount] = (res == INT_MAX) ? -1 : res; return memo[amount]; } }; ``` ```go // by chatGPT (go) func coinChange(coins []int, amount int) int { memo := make([]int, amount+1) // dp 数组全都初始化为特殊值 for i := 0; i < len(memo); i++ { memo[i] = -666 } return dp(coins, amount, memo) } func dp(coins []int, amount int, memo []int) int { if amount == 0 { return 0 } if amount < 0 { return -1 } // 查备忘录,防止重复计算 if memo[amount] != -666 { return memo[amount] } res := math.MaxInt32 for _, coin := range coins { // 计算子问题的结果 subProblem := dp(coins, amount-coin, memo) /** ![](../pictures/动态规划详解进阶/5.jpg) */ // 子问题无解则跳过 if subProblem == -1 { continue } // 在子问题中选择最优解,然后加一 res = min(res, subProblem+1) } // 把计算结果存入备忘录 if res == math.MaxInt32 { memo[amount] = -1 } else { memo[amount] = res } return memo[amount] } func min(x, y int) int { if x < y { return x } return y } ``` ```java // by labuladong (java) class Solution { int[] memo; public int coinChange(int[] coins, int amount) { memo = new int[amount + 1]; // dp 数组全都初始化为特殊值 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); /** ![](../pictures/动态规划详解进阶/5.jpg) */ // 子问题无解则跳过 if (subProblem == -1) continue; // 在子问题中选择最优解,然后加一 res = Math.min(res, subProblem + 1); } // 把计算结果存入备忘录 memo[amount] = (res == Integer.MAX_VALUE) ? -1 : res; return memo[amount]; } } ``` ```javascript // by chatGPT (javascript) var coinChange = function(coins, amount) { let memo = new Array(amount + 1).fill(-666); return dp(coins, amount); function dp(coins, amount) { if (amount == 0) return 0; if (amount < 0) return -1; // 查备忘录,防止重复计算 if (memo[amount] != -666) return memo[amount]; let res = Number.MAX_VALUE; for (let coin of coins) { // 计算子问题的结果 let subProblem = dp(coins, amount - coin); /** ![](../pictures/动态规划详解进阶/5.jpg) */ // 子问题无解则跳过 if (subProblem == -1) continue; // 在子问题中选择最优解,然后加一 res = Math.min(res, subProblem + 1); } // 把计算结果存入备忘录 memo[amount] = (res == Number.MAX_VALUE) ? -1 : res; return memo[amount]; } }; ``` ```python # by chatGPT (python) class Solution: def coinChange(self, coins: List[int], amount: int) -> int: memo = [-666] * (amount + 1) # dp 数组全都初始化为特殊值 return self.dp(coins, amount, memo) def dp(self, coins: List[int], amount: int, memo: List[int]) -> int: if amount == 0: return 0 if amount < 0: return -1 # 查备忘录,防止重复计算 if memo[amount] != -666: return memo[amount] res = float('inf') for coin in coins: # 计算子问题的结果 sub_problem = self.dp(coins, amount - coin, memo) # 子问题无解则跳过 if sub_problem == -1: continue # 在子问题中选择最优解,然后加一 res = min(res, sub_problem + 1) # 把计算结果存入备忘录 memo[amount] = -1 if res == float('inf') else res return memo[amount] ``` https://leetcode.cn/problems/gaM7Ch 的多语言解法👆 https://leetcode.cn/problems/gas-station 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int canCompleteCircuit(vector& gas, vector& cost) { int n = gas.size(); // 相当于图像中的坐标点和最低点 int sum = 0, minSum = 0; int start = 0; for (int i = 0; i < n; i++) { sum += gas[i] - cost[i]; if (sum < minSum) { // 经过第 i 个站点后,使 sum 到达新低 // 所以站点 i + 1 就是最低点(起点) start = i + 1; minSum = sum; } } if (sum < 0) { // 总油量小于总的消耗,无解 return -1; } // 环形数组特性 return start == n ? 0 : start; } }; ``` ```go // by chatGPT (go) func canCompleteCircuit(gas []int, cost []int) int { n := len(gas) // 相当于图像中的坐标点和最低点 sum, minSum := 0, 0 start := 0 for i := 0; i < n; i++ { sum += gas[i] - cost[i] if sum < minSum { // 经过第 i 个站点后,使 sum 到达新低 // 所以站点 i + 1 就是最低点(起点) start = i + 1 minSum = sum } } if sum < 0 { // 总油量小于总的消耗,无解 return -1 } // 环形数组特性 if start == n { return 0 } return start } ``` ```java // by labuladong (java) class Solution { public int canCompleteCircuit(int[] gas, int[] cost) { int n = gas.length; // 相当于图像中的坐标点和最低点 int sum = 0, minSum = 0; int start = 0; for (int i = 0; i < n; i++) { sum += gas[i] - cost[i]; if (sum < minSum) { // 经过第 i 个站点后,使 sum 到达新低 // 所以站点 i + 1 就是最低点(起点) start = i + 1; minSum = sum; } } if (sum < 0) { // 总油量小于总的消耗,无解 return -1; } // 环形数组特性 return start == n ? 0 : start; } } ``` ```javascript // by chatGPT (javascript) var canCompleteCircuit = function(gas, cost) { const n = gas.length; // 相当于图像中的坐标点和最低点 let sum = 0, minSum = 0; let start = 0; for (let i = 0; i < n; i++) { sum += gas[i] - cost[i]; if (sum < minSum) { // 经过第 i 个站点后,使 sum 到达新低 // 所以站点 i + 1 就是最低点(起点) start = i + 1; minSum = sum; } } if (sum < 0) { // 总油量小于总的消耗,无解 return -1; } // 环形数组特性 return start == n ? 0 : start; }; ``` ```python # by chatGPT (python) class Solution: def canCompleteCircuit(self, gas: List[int], cost: List[int]) -> int: n = len(gas) # 相当于图像中的坐标点和最低点 sum_, minSum, start = 0, 0, 0 for i in range(n): sum_ += gas[i] - cost[i] if sum_ < minSum: # 经过第 i 个站点后,使 sum 到达新低 # 所以站点 i + 1 就是最低点(起点) start = i + 1 minSum = sum_ if sum_ < 0: # 总油量小于总的消耗,无解 return -1 # 环形数组特性 return 0 if start == n else start ``` https://leetcode.cn/problems/gas-station 的多语言解法👆 https://leetcode.cn/problems/generate-parentheses 的多语言解法👇 ```cpp // by labuladong (cpp) class Solution { public: vector generateParenthesis(int n) { if (n == 0) return {}; // 记录所有合法的括号组合 vector res; // 回溯过程中的路径 string track; // 可用的左括号和右括号数量初始化为 n backtrack(n, n, track, res); return res; } // 可用的左括号数量为 left 个,可用的右括号数量为 rgiht 个 void backtrack(int left, int right, string& track, vector& res) { // 若左括号剩下的多,说明不合法 if (right < left) return; // 数量小于 0 肯定是不合法的 if (left < 0 || right < 0) return; // 当所有括号都恰好用完时,得到一个合法的括号组合 if (left == 0 && right == 0) { res.push_back(track); return; } // 尝试放一个左括号 track.push_back('('); // 选择 backtrack(left - 1, right, track, res); track.pop_back(); // 撤消选择 // 尝试放一个右括号 track.push_back(')'); // 选择 backtrack(left, right - 1, track, res); track.pop_back(); // 撤消选择 } } ``` ```go // by chatGPT (go) func generateParenthesis(n int) []string { if n == 0 { return []string{} } // 记录所有合法的括号组合 res := []string{} // 回溯过程中的路径 var track string // 可用的左括号和右括号数量初始化为 n backtrack(n, n, &track, &res) return res } // 可用的左括号数量为 left 个,可用的右括号数量为 right 个 func backtrack(left int, right int, track *string, res *[]string) { // 若左括号剩下的多,说明不合法 if right < left { return } // 数量小于 0 肯定是不合法的 if left < 0 || right < 0 { return } // 当所有括号都恰好用完时,得到一个合法的括号组合 if left == 0 && right == 0 { *res = append(*res, *track) return } // 尝试放一个左括号 *track += "(" // 选择 backtrack(left-1, right, track, res) *track = (*track)[:len(*track)-1] // 撤消选择 // 尝试放一个右括号 *track += ")" // 选择 backtrack(left, right-1, track, res) *track = (*track)[:len(*track)-1] // 撤消选择 } ``` ```java // by chatGPT (java) class Solution { public List generateParenthesis(int n) { if (n == 0) return new ArrayList<>(); // 记录所有合法的括号组合 List res = new ArrayList<>(); // 回溯过程中的路径 StringBuilder track = new StringBuilder(); // 可用的左括号和右括号数量初始化为 n backtrack(n, n, track, res); return res; } // 可用的左括号数量为 left 个,可用的右括号数量为 right 个 void backtrack(int left, int right, StringBuilder track, List res) { // 若左括号剩下的多,说明不合法 if (right < left) return; // 数量小于 0 肯定是不合法的 if (left < 0 || right < 0) return; // 当所有括号都恰好用完时,得到一个合法的括号组合 if (left == 0 && right == 0) { res.add(track.toString()); return; } // 尝试放一个左括号 track.append('('); // 选择 backtrack(left - 1, right, track, res); track.deleteCharAt(track.length() - 1); // 撤消选择 // 尝试放一个右括号 track.append(')'); // 选择 backtrack(left, right - 1, track, res); track.deleteCharAt(track.length() - 1); // 撤消选择 } } ``` ```javascript // by chatGPT (javascript) var generateParenthesis = function(n) { if (n === 0) return []; // 记录所有合法的括号组合 var res = []; // 回溯过程中的路径 var track = ""; // 可用的左括号和右括号数量初始化为 n backtrack(n, n, track, res); return res; }; // 可用的左括号数量为 left 个,可用的右括号数量为 right 个 function backtrack(left, right, track, res) { // 若左括号剩下的多,说明不合法 if (right < left) return; // 数量小于 0 肯定是不合法的 if (left < 0 || right < 0) return; // 当所有括号都恰好用完时,得到一个合法的括号组合 if (left === 0 && right === 0) { res.push(track); return; } // 尝试放一个左括号 track += '('; // 选择 backtrack(left - 1, right, track, res); track = track.slice(0, -1); // 撤消选择 // 尝试放一个右括号 track += ')'; // 选择 backtrack(left, right - 1, track, res); track = track.slice(0, -1); // 撤消选择 } ``` ```python # by chatGPT (python) class Solution: def generateParenthesis(self, n: int) -> List[str]: if n == 0: return [] # 记录所有合法的括号组合 res = [] # 回溯过程中的路径 track = '' # 可用的左括号和右括号数量初始化为 n self.backtrack(n, n, track, res) return res # 可用的左括号数量为 left 个,可用的右括号数量为 right 个 def backtrack(self, left, right, track, res): # 若左括号剩下的多,说明不合法 if right < left: return # 数量小于 0 肯定是不合法的 if left < 0 or right < 0: return # 当所有括号都恰好用完时,得到一个合法的括号组合 if left == 0 and right == 0: res.append(track) return # 尝试放一个左括号 track += '(' # 选择 self.backtrack(left - 1, right, track, res) track = track[:-1] # 撤消选择 # 尝试放一个右括号 track += ')' # 选择 self.backtrack(left, right - 1, track, res) track = track[:-1] # 撤消选择 ``` https://leetcode.cn/problems/generate-parentheses 的多语言解法👆 https://leetcode.cn/problems/gou-jian-cheng-ji-shu-zu-lcof 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: vector constructArr(vector& nums) { int n = nums.size(); if (n == 0) { return vector(); } // 从左到右的前缀积,prefix[i] 是 nums[0..i] 的元素积 vector prefix(n); prefix[0] = nums[0]; for (int i = 1; i < n; i++) { prefix[i] = prefix[i - 1] * nums[i]; } // 从右到左的前缀积,suffix[i] 是 nums[i..n-1] 的元素积 vector suffix(n); suffix[n - 1] = nums[n - 1]; for (int i = n - 2; i >= 0; i--) { suffix[i] = suffix[i + 1] * nums[i]; } // 结果数组 vector res(n); res[0] = suffix[1]; res[n - 1] = prefix[n - 2]; for (int i = 1; i < n - 1; i++) { // 除了 nums[i] 自己的元素积就是 nums[i] 左侧和右侧所有元素之积 res[i] = prefix[i - 1] * suffix[i + 1]; } return res; } }; ``` ```go // by chatGPT (go) func constructArr(nums []int) []int { n := len(nums) if n == 0 { return []int{} } // 从左到右的前缀积,prefix[i] 是 nums[0..i] 的元素积 prefix := make([]int, n) prefix[0] = nums[0] for i := 1; i < n; i++ { prefix[i] = prefix[i-1] * nums[i] } // 从右到左的前缀积,suffix[i] 是 nums[i..n-1] 的元素积 suffix := make([]int, n) suffix[n-1] = nums[n-1] for i := n - 2; i >= 0; i-- { suffix[i] = suffix[i+1] * nums[i] } // 结果数组 res := make([]int, n) res[0] = suffix[1] res[n-1] = prefix[n-2] for i := 1; i < n-1; i++ { // 除了 nums[i] 自己的元素积就是 nums[i] 左侧和右侧所有元素之积 res[i] = prefix[i-1] * suffix[i+1] } return res } ``` ```java // by labuladong (java) class Solution { public int[] constructArr(int[] nums) { int n = nums.length; if (n == 0) { return new int[0]; } // 从左到右的前缀积,prefix[i] 是 nums[0..i] 的元素积 int[] prefix = new int[n]; prefix[0] = nums[0]; for (int i = 1; i < nums.length; i++) { prefix[i] = prefix[i - 1] * nums[i]; } // 从右到左的前缀积,suffix[i] 是 nums[i..n-1] 的元素积 int[] suffix = new int[n]; suffix[n - 1] = nums[n - 1]; for (int i = n - 2; i >= 0; i--) { suffix[i] = suffix[i + 1] * nums[i]; } // 结果数组 int[] res = new int[n]; res[0] = suffix[1]; res[n - 1] = prefix[n - 2]; for (int i = 1; i < n - 1; i++) { // 除了 nums[i] 自己的元素积就是 nums[i] 左侧和右侧所有元素之积 res[i] = prefix[i - 1] * suffix[i + 1]; } return res; } } ``` ```javascript // by chatGPT (javascript) var constructArr = function(nums) { var n = nums.length; if (n == 0) { return new Array(0); } // 从左到右的前缀积,prefix[i] 是 nums[0..i] 的元素积 var prefix = new Array(n); prefix[0] = nums[0]; for (var i = 1; i < nums.length; i++) { prefix[i] = prefix[i - 1] * nums[i]; } // 从右到左的前缀积,suffix[i] 是 nums[i..n-1] 的元素积 var suffix = new Array(n); suffix[n - 1] = nums[n - 1]; for (var i = n - 2; i >= 0; i--) { suffix[i] = suffix[i + 1] * nums[i]; } // 结果数组 var res = new Array(n); res[0] = suffix[1]; res[n - 1] = prefix[n - 2]; for (var i = 1; i < n - 1; i++) { // 除了 nums[i] 自己的元素积就是 nums[i] 左侧和右侧所有元素之积 res[i] = prefix[i - 1] * suffix[i + 1]; } return res; }; ``` ```python # by chatGPT (python) class Solution: def constructArr(self, nums: List[int]) -> List[int]: n = len(nums) if n == 0: return [] # 从左到右的前缀积,prefix[i] 是 nums[0..i] 的元素积 prefix = [0] * n prefix[0] = nums[0] for i in range(1, n): prefix[i] = prefix[i - 1] * nums[i] # 从右到左的前缀积,suffix[i] 是 nums[i..n-1] 的元素积 suffix = [0] * n suffix[n - 1] = nums[n - 1] for i in range(n - 2, -1, -1): suffix[i] = suffix[i + 1] * nums[i] # 结果数组 res = [0] * n res[0] = suffix[1] res[n - 1] = prefix[n - 2] for i in range(1, n - 1): # 除了 nums[i] 自己的元素积就是 nums[i] 左侧和右侧所有元素之积 res[i] = prefix[i - 1] * suffix[i + 1] return res ``` https://leetcode.cn/problems/gou-jian-cheng-ji-shu-zu-lcof 的多语言解法👆 https://leetcode.cn/problems/graph-valid-tree 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: bool validTree(int n, vector>& edges) { // 初始化 0...n-1 共 n 个节点 UF uf(n); // 遍历所有边,将组成边的两个节点进行连接 for (auto edge : edges) { int u = edge[0]; int v = edge[1]; // 若两个节点已经在同一连通分量中,会产生环 if (uf.connected(u, v)) { /** ![](../pictures/kruskal/4.png) */ return false; } // 这条边不会产生环,可以是树的一部分 uf.union(u, v); /** ![](../pictures/kruskal/5.png) */ } // 要保证最后只形成了一棵树,即只有一个连通分量 return uf.count() == 1; } private: class UF { // 连通分量个数 int count; // 存储一棵树 vector parent; // 记录树的「重量」 vector size; public: // n 为图中节点的个数 UF(int n) { count = n; parent.resize(n); size.resize(n); for (int i = 0; i < n; i++) { parent[i] = i; size[i] = 1; } } // 将节点 p 和节点 q 连通 void union(int p, int q) { int rootP = find(p); int rootQ = find(q); if (rootP == rootQ) return; // 小树接到大树下面,较平衡 if (size[rootP] > size[rootQ]) { parent[rootQ] = rootP; size[rootP] += size[rootQ]; } else { parent[rootP] = rootQ; size[rootQ] += size[rootP]; } // 两个连通分量合并成一个连通分量 count--; } // 判断节点 p 和节点 q 是否连通 bool connected(int p, int q) { int rootP = find(p); int rootQ = find(q); return rootP == rootQ; } // 返回节点 x 的连通分量根节点 int find(int x) { while (parent[x] != x) { // 进行路径压缩 parent[x] = parent[parent[x]]; x = parent[x]; } return x; } // 返回图中的连通分量个数 int count() { return count; } }; }; ``` ```go // by chatGPT (go) type UF struct { // 连通分量个数 count int parent []int size []int } func newUF(n int) *UF { uf := new(UF) uf.count = n uf.parent = make([]int, n) uf.size = make([]int, n) for i := 0; i < n; i++ { uf.parent[i] = i uf.size[i] = 1 } return uf } func (uf *UF) Union(p, q int) { rootP := uf.Find(p) rootQ := uf.Find(q) if rootP == rootQ { return } // 小树接到大树下面,较平衡 if uf.size[rootP] > uf.size[rootQ] { uf.parent[rootQ] = rootP uf.size[rootP] += uf.size[rootQ] } else { uf.parent[rootP] = rootQ uf.size[rootQ] += uf.size[rootP] } // 两个连通分量合并成一个连通分量 uf.count-- } func (uf *UF) Connected(p, q int) bool { rootP := uf.Find(p) rootQ := uf.Find(q) return rootP == rootQ } func (uf *UF) Find(x int) int { for uf.parent[x] != x { // 进行路径压缩 uf.parent[x] = uf.parent[uf.parent[x]] x = uf.parent[x] } return x } func (uf *UF) Count() int { return uf.count } func validTree(n int, edges [][]int) bool { // 初始化 0...n-1 共 n 个节点 uf := newUF(n) // 遍历所有边,将组成边的两个节点进行连接 for _, edge := range edges { u, v := edge[0], edge[1] // 若两个节点已经在同一连通分量中,会产生环 if uf.Connected(u, v) { /* ![](../pictures/kruskal/4.png) */ return false } // 这条边不会产生环,可以是树的一部分 uf.Union(u, v) /* ![](../pictures/kruskal/5.png) */ } // 要保证最后只形成了一棵树,即只有一个连通分量 return uf.Count() == 1 } ``` ```java // by labuladong (java) class Solution { public boolean validTree(int n, int[][] edges) { // 初始化 0...n-1 共 n 个节点 UF uf = new UF(n); // 遍历所有边,将组成边的两个节点进行连接 for (int[] edge : edges) { int u = edge[0]; int v = edge[1]; // 若两个节点已经在同一连通分量中,会产生环 if (uf.connected(u, v)) { /** ![](../pictures/kruskal/4.png) */ return false; } // 这条边不会产生环,可以是树的一部分 uf.union(u, v); /** ![](../pictures/kruskal/5.png) */ } // 要保证最后只形成了一棵树,即只有一个连通分量 return uf.count() == 1; } class UF { // 连通分量个数 private int count; // 存储一棵树 private int[] parent; // 记录树的「重量」 private int[] size; // n 为图中节点的个数 public UF(int n) { this.count = n; parent = new int[n]; size = new int[n]; for (int i = 0; i < n; i++) { parent[i] = i; size[i] = 1; } } // 将节点 p 和节点 q 连通 public void union(int p, int q) { int rootP = find(p); int rootQ = find(q); if (rootP == rootQ) return; // 小树接到大树下面,较平衡 if (size[rootP] > size[rootQ]) { parent[rootQ] = rootP; size[rootP] += size[rootQ]; } else { parent[rootP] = rootQ; size[rootQ] += size[rootP]; } // 两个连通分量合并成一个连通分量 count--; } // 判断节点 p 和节点 q 是否连通 public boolean connected(int p, int q) { int rootP = find(p); int rootQ = find(q); return rootP == rootQ; } // 返回节点 x 的连通分量根节点 private int find(int x) { while (parent[x] != x) { // 进行路径压缩 parent[x] = parent[parent[x]]; x = parent[x]; } return x; } // 返回图中的连通分量个数 public int count() { return count; } } } ``` ```javascript // by chatGPT (javascript) /** * @param {number} n * @param {number[][]} edges * @return {boolean} */ var validTree = function(n, edges) { // 初始化 0...n-1 共 n 个节点 let uf = new UF(n); // 遍历所有边,将组成边的两个节点进行连接 for (let edge of edges) { let u = edge[0]; let v = edge[1]; // 若两个节点已经在同一连通分量中,会产生环 if (uf.connected(u, v)) { return false; } // 这条边不会产生环,可以是树的一部分 uf.union(u, v); } // 要保证最后只形成了一棵树,即只有一个连通分量 return uf.count() == 1; }; class UF { constructor(n) { this.count = n; this.parent = new Array(n); this.size = new Array(n); for (let i = 0; i < n; i++) { this.parent[i] = i; this.size[i] = 1; } } // 将节点 p 和节点 q 连通 union(p, q) { let rootP = this.find(p); let rootQ = this.find(q); if (rootP === rootQ) { return; } // 小树接到大树下面,较平衡 if (this.size[rootP] > this.size[rootQ]) { this.parent[rootQ] = rootP; this.size[rootP] += this.size[rootQ]; } else { this.parent[rootP] = rootQ; this.size[rootQ] += this.size[rootP]; } // 两个连通分量合并成一个连通分量 this.count--; } // 判断节点 p 和节点 q 是否连通 connected(p, q) { let rootP = this.find(p); let rootQ = this.find(q); return rootP === rootQ; } // 返回节点 x 的连通分量根节点 find(x) { while (this.parent[x] !== x) { // 进行路径压缩 this.parent[x] = this.parent[this.parent[x]]; x = this.parent[x]; } return x; } // 返回图中的连通分量个数 count() { return this.count; } } ``` ```python # by chatGPT (python) class Solution: def validTree(self, n: int, edges: List[List[int]]) -> bool: # 初始化 0...n-1 共 n 个节点 uf = UF(n) # 遍历所有边,将组成边的两个节点进行连接 for edge in edges: u, v = edge[0], edge[1] # 若两个节点已经在同一连通分量中,会产生环 if uf.connected(u, v): ''' ![](../pictures/kruskal/4.png) ''' return False # 这条边不会产生环,可以是树的一部分 uf.union(u, v) ''' ![](../pictures/kruskal/5.png) ''' # 要保证最后只形成了一棵树,即只有一个连通分量 return uf.count() == 1 class UF: # 连通分量个数 def count(self)->int: return self.__count # n 为图中节点的个数 def __init__(self, n: int): self.__count = n self.__parent = list(range(n)) self.__size = [1] * n # 将节点 p 和节点 q 连通 def union(self, p: int, q: int): rootP = self.find(p) rootQ = self.find(q) if rootP == rootQ: return # 小树接到大树下面,较平衡 if self.__size[rootP] > self.__size[rootQ]: self.__parent[rootQ] = rootP self.__size[rootP] += self.__size[rootQ] else: self.__parent[rootP] = rootQ self.__size[rootQ] += self.__size[rootP] # 两个连通分量合并成一个连通分量 self.__count -= 1 # 判断节点 p 和节点 q 是否连通 def connected(self, p: int, q: int) -> bool: rootP = self.find(p) rootQ = self.find(q) return rootP == rootQ # 返回节点 x 的连通分量根节点 def find(self, x: int) -> int: while self.__parent[x] != x: # 进行路径压缩 self.__parent[x] = self.__parent[self.__parent[x]] x = self.__parent[x] return x ``` https://leetcode.cn/problems/graph-valid-tree 的多语言解法👆 https://leetcode.cn/problems/group-anagrams 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: vector> groupAnagrams(vector& strs) { // 编码到分组的映射 unordered_map> codeToGroup; for (string s : strs) { // 对字符串进行编码 string code = encode(s); // 把编码相同的字符串放在一起 codeToGroup[code].push_back(s); } // 获取结果 vector> res; for (auto group : codeToGroup) { res.push_back(group.second); } return res; } // 利用每个字符的出现次数进行编码 string encode(string s) { vector count(26, 0); for (char c : s) { int delta = c - 'a'; count[delta]++; } string code(count.begin(), count.end()); return code; } }; ``` ```go // by chatGPT (go) func groupAnagrams(strs []string) [][]string { // 编码到分组的映射 codeToGroup := make(map[string][]string) for _, s := range strs { // 对字符串进行编码 code := encode(s) // 把编码相同的字符串放在一起 codeToGroup[code] = append(codeToGroup[code], s) } // 获取结果 res := make([][]string, 0, len(codeToGroup)) for _, group := range codeToGroup { res = append(res, group) } return res } // 利用每个字符的出现次数进行编码 func encode(s string) string { count := make([]byte, 26) for i := 0; i < len(s); i++ { delta := s[i] - 'a' count[delta]++ } return string(count) } ``` ```java // by labuladong (java) class Solution { public List> groupAnagrams(String[] strs) { // 编码到分组的映射 HashMap> codeToGroup = new HashMap<>(); for (String s : strs) { // 对字符串进行编码 String code = encode(s); // 把编码相同的字符串放在一起 codeToGroup.putIfAbsent(code, new LinkedList<>()); codeToGroup.get(code).add(s); } // 获取结果 List> res = new LinkedList<>(); for (List group : codeToGroup.values()) { res.add(group); } return res; } // 利用每个字符的出现次数进行编码 String encode(String s) { char[] count = new char[26]; for (char c : s.toCharArray()) { int delta = c - 'a'; count[delta]++; } return new String(count); } } ``` ```javascript // by chatGPT (javascript) var groupAnagrams = function(strs) { // 编码到分组的映射 let codeToGroup = new Map(); for (let s of strs) { // 对字符串进行编码 let code = encode(s); // 把编码相同的字符串放在一起 if (!codeToGroup.has(code)) { codeToGroup.set(code, []); } codeToGroup.get(code).push(s); } // 获取结果 let res = []; for (let group of codeToGroup.values()) { res.push(group); } return res; }; // 利用每个字符的出现次数进行编码 function encode(s) { let count = new Array(26).fill(0); for (let c of s) { let delta = c.charCodeAt() - 'a'.charCodeAt(); count[delta]++; } return count.toString(); } ``` ```python # by chatGPT (python) class Solution: def groupAnagrams(self, strs: List[str]) -> List[List[str]]: # 编码到分组的映射 codeToGroup = {} for s in strs: # 对字符串进行编码 code = self.encode(s) # 把编码相同的字符串放在一起 if code not in codeToGroup: codeToGroup[code] = [] codeToGroup[code].append(s) # 获取结果 res = [] for group in codeToGroup.values(): res.append(group) return res # 利用每个字符的出现次数进行编码 def encode(self, s: str) -> str: count = [0] * 26 for c in s: delta = ord(c) - ord('a') count[delta] += 1 return str(count) ``` https://leetcode.cn/problems/group-anagrams 的多语言解法👆 https://leetcode.cn/problems/gu-piao-de-zui-da-li-run-lcof 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int maxProfit(vector& prices) { int n = prices.size(); vector> dp(n, vector(2)); for (int i = 0; i < n; i++) { if (i - 1 == -1) { // base case dp[i][0] = 0; dp[i][1] = -prices[i]; continue; } dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] + prices[i]); dp[i][1] = max(dp[i - 1][1], -prices[i]); } return dp[n - 1][0]; } }; ``` ```go // by chatGPT (go) func maxProfit(prices []int) int { n := len(prices) dp := make([][]int, n) for i := range dp { dp[i] = make([]int, 2) } for i := 0; i < n; i++ { if i-1 == -1 { dp[i][0] = 0 dp[i][1] = -prices[i] continue } dp[i][0] = max(dp[i-1][0], dp[i-1][1]+prices[i]) dp[i][1] = max(dp[i-1][1], -prices[i]) } return dp[n-1][0] } func max(a, b int) int { if a > b { return a } return b } ``` ```java // by labuladong (java) class Solution { public int maxProfit(int[] prices) { int n = prices.length; int[][] dp = new int[n][2]; for (int i = 0; i < n; i++) { if (i - 1 == -1) { // base case dp[i][0] = 0; dp[i][1] = -prices[i]; continue; } dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i]); dp[i][1] = Math.max(dp[i - 1][1], -prices[i]); } return dp[n - 1][0]; } } ``` ```javascript // by chatGPT (javascript) var maxProfit = function(prices) { const n = prices.length; const dp = Array.from(Array(n), () => new Array(2).fill(0)); for (let i = 0; i < n; i++) { if (i - 1 === -1) { // base case dp[i][0] = 0; dp[i][1] = -prices[i]; continue; } dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i]); dp[i][1] = Math.max(dp[i - 1][1], -prices[i]); } return dp[n - 1][0]; }; ``` ```python # by chatGPT (python) class Solution: def maxProfit(self, prices: List[int]) -> int: n = len(prices) dp = [[0]*2 for _ in range(n)] for i in range(n): if i - 1 == -1: dp[i][0] = 0 dp[i][1] = -prices[i] continue dp[i][0] = max(dp[i-1][0], dp[i-1][1]+prices[i]) dp[i][1] = max(dp[i-1][1], -prices[i]) return dp[n-1][0] ``` https://leetcode.cn/problems/gu-piao-de-zui-da-li-run-lcof 的多语言解法👆 https://leetcode.cn/problems/h54YBf 的多语言解法👇 ```cpp // by chatGPT (cpp) class Codec { string SEP = ","; string NULL = "#"; public: /* 主函数,将二叉树序列化为字符串 */ string serialize(TreeNode* root) { stringstream ss; serialize(root, ss); return ss.str(); } /* 辅助函数,将二叉树存入 stringstream */ void serialize(TreeNode* root, stringstream& ss) { if (root == nullptr) { ss << NULL << SEP; return; } /******前序遍历位置******/ ss << root->val << SEP; /***********************/ serialize(root->left, ss); serialize(root->right, ss); } /* 主函数,将字符串反序列化为二叉树结构 */ TreeNode* deserialize(string data) { // 将字符串转化成列表 vector nodes; stringstream ss(data); string s; while (getline(ss, s, SEP[0])) { nodes.push_back(s); } return deserialize(nodes); } /* 辅助函数,通过 nodes 列表构造二叉树 */ TreeNode* deserialize(vector& nodes) { if (nodes.empty()) return nullptr; /******前序遍历位置******/ // 列表最左侧就是根节点 string first = nodes[0]; nodes.erase(nodes.begin()); if (first == NULL) return nullptr; TreeNode* root = new TreeNode(stoi(first)); /***********************/ root->left = deserialize(nodes); root->right = deserialize(nodes); return root; } }; ``` ```go // by chatGPT (go) type Codec struct { SEP string NULL string } func Constructor() Codec { return Codec{",", "#"} } /* 主函数,将二叉树序列化为字符串 */ func (this *Codec) serialize(root *TreeNode) string { var sb strings.Builder this.serializeHelper(root, &sb) return sb.String() } /* 辅助函数,将二叉树存入 StringBuilder */ func (this *Codec) serializeHelper(root *TreeNode, sb *strings.Builder) { if root == nil { sb.WriteString(this.NULL) sb.WriteString(this.SEP) return } /******前序遍历位置******/ sb.WriteString(strconv.Itoa(root.Val)) sb.WriteString(this.SEP) /***********************/ this.serializeHelper(root.Left, sb) this.serializeHelper(root.Right, sb) } /* 主函数,将字符串反序列化为二叉树结构 */ func (this *Codec) deserialize(data string) *TreeNode { // 将字符串转化成列表 nodes := strings.Split(data, this.SEP) return this.deserializeHelper(&nodes) } /* 辅助函数,通过 nodes 列表构造二叉树 */ func (this *Codec) deserializeHelper(nodes *[]string) *TreeNode { if len(*nodes) == 0 { return nil } /******前序遍历位置******/ // 列表最左侧就是根节点 first := (*nodes)[0] *nodes = (*nodes)[1:] if first == this.NULL { return nil } val, _ := strconv.Atoi(first) root := &TreeNode{val, nil, nil} /***********************/ root.Left = this.deserializeHelper(nodes) root.Right = this.deserializeHelper(nodes) return root } ``` ```java // by labuladong (java) public class Codec { String SEP = ","; String 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; } } ``` ```javascript // by chatGPT (javascript) var Codec = function() { this.SEP = ","; this.NULL = "#"; }; /* 主函数,将二叉树序列化为字符串 */ Codec.prototype.serialize = function(root) { let sb = []; this._serialize(root, sb); return sb.join(''); }; /* 辅助函数,将二叉树存入 StringBuilder */ Codec.prototype._serialize = function(root, sb) { if (!root) { sb.push(this.NULL, this.SEP); return; } /******前序遍历位置******/ sb.push(root.val, this.SEP); /***********************/ this._serialize(root.left, sb); this._serialize(root.right, sb); }; /* 主函数,将字符串反序列化为二叉树结构 */ Codec.prototype.deserialize = function(data) { // 将字符串转化成列表 let nodes = data.split(this.SEP); return this._deserialize(nodes); }; /* 辅助函数,通过 nodes 列表构造二叉树 */ Codec.prototype._deserialize = function(nodes) { if (!nodes.length) return null; /******前序遍历位置******/ // 列表最左侧就是根节点 let first = nodes.shift(); if (first === this.NULL) return null; let root = new TreeNode(parseInt(first)); /***********************/ root.left = this._deserialize(nodes); root.right = this._deserialize(nodes); return root; }; ``` ```python # by chatGPT (python) class Codec: SEP = "," NULL = "#" def serialize(self, root: TreeNode) -> str: """ Encodes a tree to a single string. :param root: TreeNode :return: str """ sb = [] self._serialize(root, sb) return self.SEP.join(sb) def _serialize(self, root: TreeNode, sb: List[str]) -> None: """ Helper function to serialize the tree. :param root: TreeNode :param sb: List[str] """ if root is None: sb.append(self.NULL) return # 前序遍历位置 sb.append(str(root.val)) # 递归遍历左子树 self._serialize(root.left, sb) # 递归遍历右子树 self._serialize(root.right, sb) def deserialize(self, data: str) -> TreeNode: """ Decodes your encoded data to tree. :param data: str :return: TreeNode """ nodes = data.split(self.SEP) return self._deserialize(nodes) def _deserialize(self, nodes: List[str]) -> TreeNode: """ Helper function to deserialize the tree. :param nodes: List[str] :return: TreeNode """ if not nodes: return None # 前序遍历位置 val = nodes.pop(0) if val == self.NULL: return None # 创建当前节点 root = TreeNode(int(val)) # 递归构建左子树 root.left = self._deserialize(nodes) # 递归构建右子树 root.right = self._deserialize(nodes) return root ``` https://leetcode.cn/problems/h54YBf 的多语言解法👆 https://leetcode.cn/problems/he-bing-liang-ge-pai-xu-de-lian-biao-lcof 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) { // 虚拟头结点 ListNode* dummy = new ListNode(-1); ListNode* p = dummy; ListNode* p1 = l1; ListNode* p2 = l2; while (p1 != nullptr && p2 != nullptr) { /** ![](../pictures/链表技巧/1.gif) */ // 比较 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 != nullptr) { p->next = p1; } if (p2 != nullptr) { p->next = p2; } return dummy->next; } }; ``` ```go // by chatGPT (go) func mergeTwoLists(l1 *ListNode, l2 *ListNode) *ListNode { // 虚拟头结点 dummy := &ListNode{-1, nil} p := dummy p1, p2 := l1, l2 for p1 != nil && p2 != nil { /** ![](../pictures/链表技巧/1.gif) */ // 比较 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 != nil { p.Next = p1 } if p2 != nil { p.Next = p2 } return dummy.Next } ``` ```java // by labuladong (java) 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) { /** ![](../pictures/链表技巧/1.gif) */ // 比较 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; } } ``` ```javascript // by chatGPT (javascript) var mergeTwoLists = function(l1, l2) { // 虚拟头结点 let dummy = new ListNode(-1), p = dummy; let p1 = l1, p2 = l2; while (p1 != null && p2 != null) { /** ![](../pictures/链表技巧/1.gif) */ // 比较 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; }; ``` ```python # by chatGPT (python) class Solution: def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode: # 虚拟头结点 dummy = ListNode(-1) p = dummy p1, p2 = l1, l2 while p1 and p2: # 比较 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: p.next = p1 if p2: p.next = p2 return dummy.next ``` https://leetcode.cn/problems/he-bing-liang-ge-pai-xu-de-lian-biao-lcof 的多语言解法👆 https://leetcode.cn/problems/he-wei-sde-liang-ge-shu-zi-lcof 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: vector twoSum(vector& nums, int target) { // 左右双指针 int left = 0, right = nums.size() - 1; while (left < right) { int sum = nums[left] + nums[right]; if (sum < target) { // 让和大一点 left++; } else if (sum > target) { // 让和小一点 right--; } else { // 找到两个数 return {nums[left], nums[right]}; } } return {}; } }; ``` ```go // by chatGPT (go) func twoSum(nums []int, target int) []int { // 左右双指针 left, right := 0, len(nums)-1 for left < right { sum := nums[left] + nums[right] if sum < target { // 让和大一点 left++ } else if sum > target { // 让和小一点 right-- } else { // 找到两个数 return []int{nums[left], nums[right]} } } return nil } ``` ```java // by labuladong (java) class Solution { public int[] twoSum(int[] nums, int target) { // 左右双指针 int left = 0, right = nums.length - 1; while (left < right) { int sum = nums[left] + nums[right]; if (sum < target) { // 让和大一点 left++; } else if (sum > target) { // 让和小一点 right--; } else { // 找到两个数 return new int[]{nums[left], nums[right]}; } } return null; } } ``` ```javascript // by chatGPT (javascript) var twoSum = function(nums, target) { // 左右双指针 let left = 0, right = nums.length - 1; while (left < right) { let sum = nums[left] + nums[right]; if (sum < target) { // 让和大一点 left++; } else if (sum > target) { // 让和小一点 right--; } else { // 找到两个数 return [nums[left], nums[right]]; } } return null; }; ``` ```python # by chatGPT (python) class Solution: def twoSum(self, nums: List[int], target: int) -> List[int]: # 左右双指针 left, right = 0, len(nums) - 1 while left < right: sum = nums[left] + nums[right] if sum < target: # 让和大一点 left += 1 elif sum > target: # 让和小一点 right -= 1 else: # 找到两个数 return [nums[left], nums[right]] return None ``` https://leetcode.cn/problems/he-wei-sde-liang-ge-shu-zi-lcof 的多语言解法👆 https://leetcode.cn/problems/house-robber 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { private: // 备忘录 vector memo; public: // 主函数 int rob(vector& nums) { // 初始化备忘录 memo = vector(nums.size(), -1); // 强盗从第 0 间房子开始抢劫 return dp(nums, 0); } // 返回 dp[start..] 能抢到的最大值 int dp(vector& nums, int start) { if (start >= nums.size()) { return 0; } // 避免重复计算 if (memo[start] != -1) { return memo[start]; } int res = max(dp(nums, start + 1), nums[start] + dp(nums, start + 2)); // 记入备忘录 memo[start] = res; return res; } }; ``` ```go // by chatGPT (go) import "math" // 主函数 func rob(nums []int) int { // 备忘录数组 memo := make([]int, len(nums)) // 填充备忘录数组为 -1 for i := range memo { memo[i] = -1 } // 强盗从第 0 间房子开始抢劫 return dp(nums, 0, memo) } // 返回 dp[start..] 能抢到的最大值 func dp(nums []int, start int, memo []int) int { if start >= len(nums) { return 0 } // 避免重复计算 if memo[start] != -1 { return memo[start] } res := int(math.Max(float64(dp(nums, start+1, memo)), float64(nums[start]+dp(nums, start+2, memo)))) // 记入备忘录 memo[start] = res return res } ``` ```java // by labuladong (java) class Solution { // 备忘录 private int[] memo; // 主函数 public int rob(int[] nums) { // 初始化备忘录 memo = new int[nums.length]; Arrays.fill(memo, -1); // 强盗从第 0 间房子开始抢劫 return dp(nums, 0); } // 返回 dp[start..] 能抢到的最大值 private int dp(int[] nums, int start) { if (start >= nums.length) { return 0; } // 避免重复计算 if (memo[start] != -1) return memo[start]; int res = Math.max(dp(nums, start + 1), nums[start] + dp(nums, start + 2)); // 记入备忘录 memo[start] = res; return res; } } ``` ```javascript // by chatGPT (javascript) var rob = function(nums) { // 备忘录 let memo = new Array(nums.length).fill(-1); // 强盗从第 0 间房子开始抢劫 return dp(nums, 0, memo); }; // 返回 dp[start..] 能抢到的最大值 function dp(nums, start, memo) { if (start >= nums.length) { return 0; } // 避免重复计算 if (memo[start] != -1) return memo[start]; let res = Math.max(dp(nums, start + 1, memo), nums[start] + dp(nums, start + 2, memo)); // 记入备忘录 memo[start] = res; return res; } ``` ```python # by chatGPT (python) class Solution: def __init__(self): self.memo = [] def rob(self, nums: List[int]) -> int: self.memo = [-1] * len(nums) # 强盗从第 0 间房子开始抢劫 return self.dp(nums, 0) # 返回 dp[start..] 能抢到的最大值 def dp(self, nums: List[int], start: int) -> int: if start >= len(nums): return 0 # 避免重复计算 if self.memo[start] != -1: return self.memo[start] res = max(self.dp(nums, start + 1), nums[start] + self.dp(nums, start + 2)) # 记入备忘录 self.memo[start] = res return res ``` https://leetcode.cn/problems/house-robber 的多语言解法👆 https://leetcode.cn/problems/house-robber-ii 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int rob(vector& nums) { int n = nums.size(); if (n == 1) return nums[0]; vector memo1(n, -1); vector memo2(n, -1); // 两次调用使用两个不同的备忘录 return max( dp(nums, 0, n - 2, memo1), dp(nums, 1, n - 1, memo2) ); } // 定义:计算闭区间 [start,end] 的最优结果 int dp(vector& nums, int start, int end, vector& memo) { if (start > end) { return 0; } if (memo[start] != -1) { return memo[start]; } // 状态转移方程 int res = max( dp(nums, start + 2, end, memo) + nums[start], dp(nums, start + 1, end, memo) ); memo[start] = res; return res; } }; ``` ```go // by chatGPT (go) func rob(nums []int) int { n := len(nums) if n == 1 { return nums[0] } memo1 := make([]int, n) memo2 := make([]int, n) for i := 0; i < n; i++ { memo1[i] = -1 memo2[i] = -1 } // 两次调用使用两个不同的备忘录 return max( func() int { res := dp(nums, 0, n-2, memo1) return res }(), func() int { res := dp(nums, 1, n-1, memo2) return res }(), ) } // 定义:计算闭区间 [start,end] 的最优结果 func dp(nums []int, start, end int, memo []int) int { if start > end { return 0 } if memo[start] != -1 { return memo[start] } // 状态转移方程 res := max( func() int { res := dp(nums, start+2, end, memo) + nums[start] return res }(), func() int { res := dp(nums, start+1, end, memo) return res }(), ) memo[start] = res return res } func max(a, b int) int { if a > b { return a } return b } ``` ```java // by labuladong (java) class Solution { public int rob(int[] nums) { int n = nums.length; if (n == 1) return nums[0]; int[] memo1 = new int[n]; int[] memo2 = new int[n]; Arrays.fill(memo1, -1); Arrays.fill(memo2, -1); // 两次调用使用两个不同的备忘录 return Math.max( dp(nums, 0, n - 2, memo1), dp(nums, 1, n - 1, memo2) ); } // 定义:计算闭区间 [start,end] 的最优结果 int dp(int[] nums, int start, int end, int[] memo) { if (start > end) { return 0; } if (memo[start] != -1) { return memo[start]; } // 状态转移方程 int res = Math.max( dp(nums, start + 2, end, memo) + nums[start], dp(nums, start + 1, end, memo) ); memo[start] = res; return res; } } ``` ```javascript // by chatGPT (javascript) var rob = function(nums) { const n = nums.length; if (n == 1) return nums[0]; const memo1 = new Array(n).fill(-1); const memo2 = new Array(n).fill(-1); // 两次调用使用两个不同的备忘录 return Math.max( dp(nums, 0, n - 2, memo1), dp(nums, 1, n - 1, memo2) ); }; // 定义:计算闭区间 [start,end] 的最优结果 const dp = function(nums, start, end, memo) { if (start > end) { return 0; } if (memo[start] != -1) { return memo[start]; } // 状态转移方程 const res = Math.max( dp(nums, start + 2, end, memo) + nums[start], dp(nums, start + 1, end, memo) ); memo[start] = res; return res; }; ``` ```python # by chatGPT (python) class Solution: def rob(self, nums: List[int]) -> int: n = len(nums) if n == 1: return nums[0] memo1 = [-1] * n memo2 = [-1] * n # 两次调用使用两个不同的备忘录 return max( self.dp(nums, 0, n - 2, memo1), self.dp(nums, 1, n - 1, memo2) ) # 定义:计算闭区间 [start,end] 的最优结果 def dp(self, nums: List[int], start: int, end: int, memo: List[int]) -> int: if start > end: return 0 if memo[start] != -1: return memo[start] # 状态转移方程 res = max( self.dp(nums, start + 2, end, memo) + nums[start], self.dp(nums, start + 1, end, memo) ) memo[start] = res return res ``` https://leetcode.cn/problems/house-robber-ii 的多语言解法👆 https://leetcode.cn/problems/house-robber-iii 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: unordered_map memo; int rob(TreeNode* root) { if (root == nullptr) return 0; // 利用备忘录消除重叠子问题 if (memo.count(root)) return memo[root]; // 抢,然后去下下家 int do_it = root->val + (root->left == nullptr ? 0 : rob(root->left->left) + rob(root->left->right)) + (root->right == nullptr ? 0 : rob(root->right->left) + rob(root->right->right)); // 不抢,然后去下家 int not_do = rob(root->left) + rob(root->right); int res = max(do_it, not_do); memo[root] = res; return res; } }; ``` ```go // by chatGPT (go) // Definition for a binary tree node. // type TreeNode struct { // Val int // Left *TreeNode // Right *TreeNode // } func rob(root *TreeNode) int { memo := make(map[*TreeNode]int) var robSub func(*TreeNode) int robSub = func(node *TreeNode) int { if node == nil { return 0 } if _, ok := memo[node]; ok { return memo[node] } //考虑偷取该节点 doIt := node.Val if node.Left != nil { doIt += robSub(node.Left.Left) + robSub(node.Left.Right) } if node.Right != nil { doIt += robSub(node.Right.Left) + robSub(node.Right.Right) } //不偷取该节点 notDo := robSub(node.Left) + robSub(node.Right) res := max(doIt, notDo) memo[node] = res return res } return robSub(root) } func max(x, y int) int { if x > y { return x } return y } ``` ```java // by labuladong (java) class Solution { Map memo = new HashMap<>(); public int rob(TreeNode root) { if (root == null) return 0; // 利用备忘录消除重叠子问题 if (memo.containsKey(root)) return memo.get(root); // 抢,然后去下下家 int do_it = root.val + (root.left == null ? 0 : rob(root.left.left) + rob(root.left.right)) + (root.right == null ? 0 : rob(root.right.left) + rob(root.right.right)); // 不抢,然后去下家 int not_do = rob(root.left) + rob(root.right); int res = Math.max(do_it, not_do); memo.put(root, res); return res; } } ``` ```javascript // by chatGPT (javascript) var rob = function(root) { const memo = new Map(); if (root === null) { return 0; } // 利用备忘录消除重叠子问题 if (memo.has(root)) { return memo.get(root); } // 抢,然后去下下家 let do_it = root.val + ((root.left === null) ? 0 : rob(root.left.left) + rob(root.left.right)) + ((root.right === null) ? 0 : rob(root.right.left) + rob(root.right.right)); // 不抢,然后去下家 let not_do = rob(root.left) + rob(root.right); let res = Math.max(do_it, not_do); memo.set(root, res); return res; }; ``` ```python # by chatGPT (python) class Solution: def __init__(self): self.memo = {} def rob(self, root: TreeNode) -> int: if not root: return 0 # 利用备忘录消除重叠子问题 if root in self.memo: return self.memo[root] # 抢,然后去下下家 do_it = root.val if root.left: do_it += self.rob(root.left.left) + self.rob(root.left.right) if root.right: do_it += self.rob(root.right.left) + self.rob(root.right.right) # 不抢,然后去下家 not_do = self.rob(root.left) + self.rob(root.right) res = max(do_it, not_do) self.memo[root] = res return res ``` https://leetcode.cn/problems/house-robber-iii 的多语言解法👆 https://leetcode.cn/problems/hua-dong-chuang-kou-de-zui-da-zhi-lcof 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { /* 单调队列的实现 */ class MonotonicQueue { deque q; public: void push(int n) { // 将小于 n 的元素全部删除 while (!q.empty() && q.back() < n) { /** ![](../pictures/单调队列/3.png) */ q.pop_back(); } // 然后将 n 加入尾部 q.push_back(n); } int max() { return q.front(); } void pop(int n) { if (n == q.front()) { q.pop_front(); } } }; public: /* 解题函数的实现 */ vector maxSlidingWindow(vector& nums, int k) { MonotonicQueue window; vector res; for (int i = 0; i < nums.size(); i++) { if (i < k - 1) { //先填满窗口的前 k - 1 window.push(nums[i]); } else { /** ![](../pictures/单调队列/1.png) */ // 窗口向前滑动,加入新数字 window.push(nums[i]); // 记录当前窗口的最大值 res.push_back(window.max()); // 移出旧数字 window.pop(nums[i - k + 1]); } } return res; } }; ``` ```go // by chatGPT (go) type MonotonicQueue struct { q []int } func (mq *MonotonicQueue) push(n int) { // 将小于 n 的元素全部删除 for len(mq.q) > 0 && mq.q[len(mq.q)-1] < n { mq.q = mq.q[:len(mq.q)-1] } // 然后将 n 加入尾部 mq.q = append(mq.q, n) } func (mq *MonotonicQueue) max() int { return mq.q[0] } func (mq *MonotonicQueue) pop(n int) { if n == mq.q[0] { mq.q = mq.q[1:] } } func maxSlidingWindow(nums []int, k int) []int { window := &MonotonicQueue{} res := []int{} for i := 0; i < len(nums); i++ { if i < k-1 { // 先填满窗口的前 k - 1 window.push(nums[i]) } else { // 窗口向前滑动,加入新数字 window.push(nums[i]) // 记录当前窗口的最大值 res = append(res, window.max()) // 移出旧数字 window.pop(nums[i-k+1]) } } return res } ``` ```java // by labuladong (java) class Solution { /* 单调队列的实现 */ class MonotonicQueue { LinkedList q = new LinkedList<>(); public void push(int n) { // 将小于 n 的元素全部删除 while (!q.isEmpty() && q.getLast() < n) { /** ![](../pictures/单调队列/3.png) */ q.pollLast(); } // 然后将 n 加入尾部 q.addLast(n); } public int max() { return q.getFirst(); } public void pop(int n) { if (n == q.getFirst()) { q.pollFirst(); } } } /* 解题函数的实现 */ 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) { //先填满窗口的前 k - 1 window.push(nums[i]); } else { /** ![](../pictures/单调队列/1.png) */ // 窗口向前滑动,加入新数字 window.push(nums[i]); // 记录当前窗口的最大值 res.add(window.max()); // 移出旧数字 window.pop(nums[i - k + 1]); } } // 需要转成 int[] 数组再返回 int[] arr = new int[res.size()]; for (int i = 0; i < res.size(); i++) { arr[i] = res.get(i); } return arr; } } ``` ```javascript // by chatGPT (javascript) var Solution = function() { /* 单调队列的实现 */ class MonotonicQueue { constructor() { this.q = []; } push(n) { // 将小于 n 的元素全部删除 while (this.q.length > 0 && this.q[this.q.length - 1] < n) { /** ![](../pictures/单调队列/3.png) */ this.q.pop(); } // 然后将 n 加入尾部 this.q.push(n); } max() { return this.q[0]; } pop(n) { if (n === this.q[0]) { this.q.shift(); } } } /* 解题函数的实现 */ this.maxSlidingWindow = function(nums, k) { const window = new MonotonicQueue(); const res = []; for (let i = 0; i < nums.length; i++) { if (i < k - 1) { // 先填满窗口的前 k - 1 window.push(nums[i]); } else { /** ![](../pictures/单调队列/1.png) */ // 窗口向前滑动,加入新数字 window.push(nums[i]); // 记录当前窗口的最大值 res.push(window.max()); // 移出旧数字 window.pop(nums[i - k + 1]); } } // 需要转成 int[] 数组再返回 return res; } } ``` ```python # by chatGPT (python) class Solution: # 单调队列的实现 class MonotonicQueue: def __init__(self): self.q = [] def push(self, n: int) -> None: # 将小于 n 的元素全部删除 while self.q and self.q[-1] < n: ''' ![](../pictures/单调队列/3.png) ''' self.q.pop() # 然后将 n 加入尾部 self.q.append(n) def max(self) -> int: return self.q[0] def pop(self, n: int) -> None: if n == self.q[0]: self.q.pop(0) # 解题函数的实现 def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]: window = self.MonotonicQueue() res = [] for i in range(len(nums)): if i < k - 1: # 先填满窗口的前 k - 1 window.push(nums[i]) else: ''' ![](../pictures/单调队列/1.png) ''' # 窗口向前滑动,加入新数字 window.push(nums[i]) # 记录当前窗口的最大值 res.append(window.max()) # 移出旧数字 window.pop(nums[i - k + 1]) # 需要转成 int[] 数组再返回 return res ``` https://leetcode.cn/problems/hua-dong-chuang-kou-de-zui-da-zhi-lcof 的多语言解法👆 https://leetcode.cn/problems/implement-queue-using-stacks 的多语言解法👇 ```cpp // by chatGPT (cpp) class MyQueue { private: stack s1, s2; public: MyQueue() {} /** * 添加元素到队尾 */ void push(int x) { s1.push(x); } /** * 删除队头的元素并返回 */ int pop() { // 先调用 peek 保证 s2 非空 peek(); int element = s2.top(); s2.pop(); return element; } /** * 返回队头元素 */ int peek() { if (s2.empty()) // 把 s1 元素压入 s2 while (!s1.empty()) { s2.push(s1.top()); s1.pop(); } return s2.top(); } /** * 判断队列是否为空 */ bool empty() { return s1.empty() && s2.empty(); } }; ``` ```go // by chatGPT (go) type MyQueue struct { s1, s2 []int } func Constructor() MyQueue { return MyQueue{} } /** * 添加元素到队尾 */ func (q *MyQueue) Push(x int) { q.s1 = append(q.s1, x) } /** * 删除队头的元素并返回 */ func (q *MyQueue) Pop() int { // 先调用 Peek 保证 s2 非空 q.Peek() val := q.s2[len(q.s2)-1] q.s2 = q.s2[:len(q.s2)-1] return val } /** * 返回队头元素 */ func (q *MyQueue) Peek() int { if len(q.s2) == 0 { // 把 s1 元素压入 s2 for len(q.s1) != 0 { q.s2 = append(q.s2, q.s1[len(q.s1)-1]) q.s1 = q.s1[:len(q.s1)-1] } } return q.s2[len(q.s2)-1] } /** * 判断队列是否为空 */ func (q *MyQueue) Empty() bool { return len(q.s1) == 0 && len(q.s2) == 0 } ``` ```java // by labuladong (java) class MyQueue { private Stack s1, s2; public MyQueue() { s1 = new Stack<>(); s2 = new Stack<>(); } /** * 添加元素到队尾 */ public void push(int x) { s1.push(x); } /** * 删除队头的元素并返回 */ public int pop() { // 先调用 peek 保证 s2 非空 peek(); return s2.pop(); } /** * 返回队头元素 */ public int peek() { if (s2.isEmpty()) // 把 s1 元素压入 s2 while (!s1.isEmpty()) s2.push(s1.pop()); return s2.peek(); } /** * 判断队列是否为空 */ public boolean empty() { return s1.isEmpty() && s2.isEmpty(); } } ``` ```javascript // by chatGPT (javascript) var MyQueue = function() { this.s1 = []; this.s2 = []; }; /** * 添加元素到队尾 */ MyQueue.prototype.push = function(x) { this.s1.push(x); }; /** * 删除队头的元素并返回 */ MyQueue.prototype.pop = function() { // 先调用 peek 保证 s2 非空 this.peek(); return this.s2.pop(); }; /** * 返回队头元素 */ MyQueue.prototype.peek = function() { if (this.s2.length === 0) // 把 s1 元素压入 s2 while (this.s1.length > 0) this.s2.push(this.s1.pop()); return this.s2[this.s2.length - 1]; }; /** * 判断队列是否为空 */ MyQueue.prototype.empty = function() { return this.s1.length === 0 && this.s2.length === 0; }; ``` ```python # by chatGPT (python) class MyQueue: def __init__(self): self.s1 = [] self.s2 = [] def push(self, x: int) -> None: self.s1.append(x) def pop(self) -> int: # 先调用 peek 保证 s2 非空 self.peek() return self.s2.pop() def peek(self) -> int: if not self.s2: # 把 s1 元素压入 s2 while self.s1: self.s2.append(self.s1.pop()) return self.s2[-1] def empty(self) -> bool: return not self.s1 and not self.s2 ``` https://leetcode.cn/problems/implement-queue-using-stacks 的多语言解法👆 https://leetcode.cn/problems/implement-stack-using-queues 的多语言解法👇 ```cpp // by chatGPT (cpp) class MyStack { queue q; int top_elem = 0; public: /** * 添加元素到栈顶 */ void push(int x) { // x 是队列的队尾,是栈的栈顶 q.push(x); top_elem = x; } /** * 返回栈顶元素 */ int top() { return top_elem; } /** * 删除栈顶的元素并返回 */ int pop() { int size = q.size(); // 留下队尾 2 个元素 while (size > 2) { q.push(q.front()); q.pop(); size--; } // 记录新的队尾元素 top_elem = q.front(); q.push(q.front()); q.pop(); // 删除之前的队尾元素 int tmp=q.front(); q.pop(); return tmp; } /** * 判断栈是否为空 */ bool empty() { return q.empty(); } }; ``` ```go // by mario-huang (go) package ImplementStackusingQueues type MyStack struct { q []int topElem int } func Constructor() MyStack { return MyStack{q: []int{}, topElem: 0} } /** * 添加元素到栈顶 */ func (this *MyStack) Push(x int) { // x 是队列的队尾,是栈的栈顶 this.q = append(this.q, x) this.topElem = x } /** * 删除栈顶的元素并返回 */ func (this *MyStack) Pop() int { size := len(this.q) // 留下队尾 2 个元素 for size > 2 { this.q = append(this.q, this.q[0]) this.q = this.q[1:] size-- } // 记录新的队尾元素 this.topElem = this.q[0] this.q = append(this.q, this.q[0]) this.q = this.q[1:] // 删除之前的队尾元素 val := this.q[0] this.q = this.q[1:] return val } /** * 返回栈顶元素 */ func (this *MyStack) Top() int { return this.topElem } /** * 判断栈是否为空 */ func (this *MyStack) Empty() bool { return len(this.q) == 0 } ``` ```java // by labuladong (java) class MyStack { Queue q = new LinkedList<>(); int top_elem = 0; /** * 添加元素到栈顶 */ public void push(int x) { // x 是队列的队尾,是栈的栈顶 q.offer(x); top_elem = x; } /** * 返回栈顶元素 */ public int top() { return top_elem; } /** * 删除栈顶的元素并返回 */ public int pop() { int size = q.size(); // 留下队尾 2 个元素 while (size > 2) { q.offer(q.poll()); size--; } // 记录新的队尾元素 top_elem = q.peek(); q.offer(q.poll()); // 删除之前的队尾元素 return q.poll(); } /** * 判断栈是否为空 */ public boolean empty() { return q.isEmpty(); } } ``` ```javascript // by chatGPT (javascript) var MyStack = function() { this.q = []; this.top_elem = 0; }; /** * 添加元素到栈顶 */ MyStack.prototype.push = function(x) { // x 是队列的队尾,是栈的栈顶 this.q.push(x); this.top_elem = x; }; /** * 返回栈顶元素 */ MyStack.prototype.top = function() { return this.top_elem; }; /** * 删除栈顶的元素并返回 */ MyStack.prototype.pop = function() { var size = this.q.length; // 留下队尾 2 个元素 while (size > 2) { this.q.push(this.q.shift()); size--; } // 记录新的队尾元素 this.top_elem = this.q[0]; this.q.push(this.q.shift()); // 删除之前的队尾元素 return this.q.shift(); }; /** * 判断栈是否为空 */ MyStack.prototype.empty = function() { return this.q.length === 0; }; ``` ```python # by chatGPT (python) from queue import Queue class MyStack: def __init__(self): self.q = Queue() self.top_elem = 0 def push(self, x: int) -> None: """ 添加元素到栈顶 """ # x 是队列的队尾,是栈的栈顶 self.q.put(x) self.top_elem = x def pop(self) -> int: """ 删除栈顶的元素并返回 """ size = self.q.qsize() # 留下队尾 2 个元素 while size > 2: self.q.put(self.q.get()) size -= 1 # 记录新的队尾元素 self.top_elem = self.q.queue[0] self.q.put(self.q.get()) # 删除之前的队尾元素 return self.q.get() def top(self) -> int: """ 返回栈顶元素 """ return self.top_elem def empty(self) -> bool: """ 判断栈是否为空 """ return self.q.empty() ``` https://leetcode.cn/problems/implement-stack-using-queues 的多语言解法👆 https://leetcode.cn/problems/insert-delete-getrandom-o1 的多语言解法👇 ```cpp // by labuladong (cpp) class RandomizedSet { public: // 存储元素的值 vector nums; // 记录每个元素对应在 nums 中的索引 unordered_map valToIndex; bool insert(int val) { // 若 val 已存在,不用再插入 if (valToIndex.count(val)) { return false; } // 若 val 不存在,插入到 nums 尾部, // 并记录 val 对应的索引值 valToIndex[val] = nums.size(); nums.push_back(val); return true; } bool remove(int val) { // 若 val 不存在,不用再删除 if (!valToIndex.count(val)) { return false; } // 先拿到 val 的索引 int index = valToIndex[val]; // 将最后一个元素对应的索引修改为 index valToIndex[nums.back()] = index; // 交换 val 和最后一个元素 swap(nums[index], nums.back()); // 在数组中删除元素 val nums.pop_back(); // 删除元素 val 对应的索引 valToIndex.erase(val); return true; } int getRandom() { // 随机获取 nums 中的一个元素 return nums[rand() % nums.size()]; } }; ``` ```go // by chatGPT (go) type RandomizedSet struct { // 存储元素的值 nums []int // 记录每个元素对应在 nums 中的索引 valToIndex map[int]int } func Constructor() RandomizedSet { return RandomizedSet{ nums: []int{}, valToIndex: make(map[int]int), } } func (this *RandomizedSet) Insert(val int) bool { // 若 val 已存在,不用再插入 if _, ok := this.valToIndex[val]; ok { return false } // 若 val 不存在,插入到 nums 尾部, // 并记录 val 对应的索引值 this.valToIndex[val] = len(this.nums) this.nums = append(this.nums, val) return true } func (this *RandomizedSet) Remove(val int) bool { // 若 val 不存在,不用再删除 if _, ok := this.valToIndex[val]; !ok { return false } // 先拿到 val 的索引 index := this.valToIndex[val] // 将最后一个元素对应的索引修改为 index this.valToIndex[this.nums[len(this.nums)-1]] = index // 交换 val 和最后一个元素 this.nums[index], this.nums[len(this.nums)-1] = this.nums[len(this.nums)-1], this.nums[index] // 在数组中删除元素 val this.nums = this.nums[:len(this.nums)-1] // 删除元素 val 对应的索引 delete(this.valToIndex, val) return true } func (this *RandomizedSet) GetRandom() int { // 随机获取 nums 中的一个元素 return this.nums[rand.Intn(len(this.nums))] } ``` ```java // by chatGPT (java) class RandomizedSet { // 存储元素的值 List nums; // 记录每个元素对应在 nums 中的索引 Map valToIndex; public RandomizedSet() { nums = new ArrayList<>(); valToIndex = new HashMap<>(); } public boolean insert(int val) { // 若 val 已存在,不用再插入 if (valToIndex.containsKey(val)) { return false; } // 若 val 不存在,插入到 nums 尾部, // 并记录 val 对应的索引值 valToIndex.put(val, nums.size()); nums.add(val); return true; } public boolean remove(int val) { // 若 val 不存在,不用再删除 if (!valToIndex.containsKey(val)) { return false; } // 先拿到 val 的索引 int index = valToIndex.get(val); // 将最后一个元素对应的索引修改为 index valToIndex.put(nums.get(nums.size() - 1), index); // 交换 val 和最后一个元素 Collections.swap(nums, index, nums.size() - 1); // 在数组中删除元素 val nums.remove(nums.size() - 1); // 删除元素 val 对应的索引 valToIndex.remove(val); return true; } public int getRandom() { // 随机获取 nums 中的一个元素 return nums.get((int)(Math.random() * nums.size())); } } ``` ```javascript // by chatGPT (javascript) var RandomizedSet = function() { // 存储元素的值 this.nums = []; // 记录每个元素对应在 nums 中的索引 this.valToIndex = {}; }; RandomizedSet.prototype.insert = function(val) { // 若 val 已存在,不用再插入 if (this.valToIndex[val] !== undefined) { return false; } // 若 val 不存在,插入到 nums 尾部, // 并记录 val 对应的索引值 this.valToIndex[val] = this.nums.length; this.nums.push(val); return true; }; RandomizedSet.prototype.remove = function(val) { // 若 val 不存在,不用再删除 if (this.valToIndex[val] === undefined) { return false; } // 先拿到 val 的索引 var index = this.valToIndex[val]; // 将最后一个元素对应的索引修改为 index this.valToIndex[this.nums[this.nums.length - 1]] = index; // 交换 val 和最后一个元素 var temp = this.nums[index]; this.nums[index] = this.nums[this.nums.length - 1]; this.nums[this.nums.length - 1] = temp; // 在数组中删除元素 val this.nums.pop(); // 删除元素 val 对应的索引 delete this.valToIndex[val]; return true; }; RandomizedSet.prototype.getRandom = function() { // 随机获取 nums 中的一个元素 return this.nums[Math.floor(Math.random() * this.nums.length)]; }; ``` ```python # by chatGPT (python) class RandomizedSet: def __init__(self): """ Initialize your data structure here. """ self.nums = [] # 存储元素的值 self.valToIndex = dict() # 记录每个元素对应在 nums 中的索引 def insert(self, val: int) -> bool: """ Inserts a value to the set. Returns true if the set did not already contain the specified element. """ # 若 val 已存在,不用再插入 if val in self.valToIndex: return False # 若 val 不存在,插入到 nums 尾部, # 并记录 val 对应的索引值 self.valToIndex[val] = len(self.nums) self.nums.append(val) return True def remove(self, val: int) -> bool: """ Removes a value from the set. Returns true if the set contained the specified element. """ # 若 val 不存在,不用再删除 if val not in self.valToIndex: return False # 先拿到 val 的索引 index = self.valToIndex[val] # 将最后一个元素对应的索引修改为 index self.valToIndex[self.nums[-1]] = index # 交换 val 和最后一个元素 self.nums[index], self.nums[-1] = self.nums[-1], self.nums[index] # 在数组中删除元素 val self.nums.pop() # 删除元素 val 对应的索引 del self.valToIndex[val] return True def getRandom(self) -> int: """ Get a random element from the set. """ # 随机获取 nums 中的一个元素 return self.nums[random.randint(0, len(self.nums) - 1)] ``` https://leetcode.cn/problems/insert-delete-getrandom-o1 的多语言解法👆 https://leetcode.cn/problems/insert-into-a-binary-search-tree 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: TreeNode* insertIntoBST(TreeNode* root, int val) { // 找到空位置插入新节点 if (root == nullptr) return new TreeNode(val); // if (root->val == val) // BST 中一般不会插入已存在元素 if (root->val < val) root->right = insertIntoBST(root->right, val); if (root->val > val) root->left = insertIntoBST(root->left, val); return root; } }; ``` ```go // by chatGPT (go) func insertIntoBST(root *TreeNode, val int) *TreeNode { // 找到空位置插入新节点 if root == nil { return &TreeNode{Val: val} } // if (root.val == val) // BST 中一般不会插入已存在元素 if root.Val < val { root.Right = insertIntoBST(root.Right, val) } if root.Val > val { root.Left = insertIntoBST(root.Left, val) } return root } ``` ```java // by labuladong (java) class Solution { public TreeNode insertIntoBST(TreeNode root, int val) { // 找到空位置插入新节点 if (root == null) return new TreeNode(val); // if (root.val == val) // BST 中一般不会插入已存在元素 if (root.val < val) root.right = insertIntoBST(root.right, val); if (root.val > val) root.left = insertIntoBST(root.left, val); return root; } } ``` ```javascript // by chatGPT (javascript) var insertIntoBST = function(root, val) { // 找到空位置插入新节点 if (root == null) return new TreeNode(val); // if (root.val == val) // BST 中一般不会插入已存在元素 if (root.val < val) root.right = insertIntoBST(root.right, val); if (root.val > val) root.left = insertIntoBST(root.left, val); return root; }; ``` ```python # by chatGPT (python) class Solution: def insertIntoBST(self, root: TreeNode, val: int) -> TreeNode: # 找到空位置插入新节点 if root is None: return TreeNode(val) # if (root.val == val) # BST 中一般不会插入已存在元素 if root.val < val: root.right = self.insertIntoBST(root.right, val) if root.val > val: root.left = self.insertIntoBST(root.left, val) return root ``` https://leetcode.cn/problems/insert-into-a-binary-search-tree 的多语言解法👆 https://leetcode.cn/problems/intersection-of-two-linked-lists 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) { // p1 指向 A 链表头结点,p2 指向 B 链表头结点 ListNode *p1 = headA, *p2 = headB; while (p1 != p2) { // p1 走一步,如果走到 A 链表末尾,转到 B 链表 if (p1 == nullptr) p1 = headB; else p1 = p1->next; // p2 走一步,如果走到 B 链表末尾,转到 A 链表 if (p2 == nullptr) p2 = headA; else p2 = p2->next; } return p1; } }; ``` ```go // by chatGPT (go) func getIntersectionNode(headA, headB *ListNode) *ListNode { // p1 指向 A 链表头结点,p2 指向 B 链表头结点 p1, p2 := headA, headB for p1 != p2 { // p1 走一步,如果走到 A 链表末尾,转到 B 链表 if p1 == nil { p1 = headB } else { p1 = p1.Next } // p2 走一步,如果走到 B 链表末尾,转到 A 链表 if p2 == nil { p2 = headA } else { p2 = p2.Next } } return p1 } ``` ```java // by labuladong (java) public class Solution { public ListNode getIntersectionNode(ListNode headA, ListNode headB) { // p1 指向 A 链表头结点,p2 指向 B 链表头结点 ListNode p1 = headA, p2 = headB; while (p1 != p2) { // p1 走一步,如果走到 A 链表末尾,转到 B 链表 if (p1 == null) p1 = headB; else p1 = p1.next; // p2 走一步,如果走到 B 链表末尾,转到 A 链表 if (p2 == null) p2 = headA; else p2 = p2.next; } return p1; } } ``` ```javascript // by chatGPT (javascript) var getIntersectionNode = function(headA, headB) { // p1 指向 A 链表头结点,p2 指向 B 链表头结点 let p1 = headA, p2 = headB; while (p1 !== p2) { // p1 走一步,如果走到 A 链表末尾,转到 B 链表 if (p1 === null) p1 = headB; else p1 = p1.next; // p2 走一步,如果走到 B 链表末尾,转到 A 链表 if (p2 === null) p2 = headA; else p2 = p2.next; } return p1; }; ``` ```python # by chatGPT (python) class Solution: def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode: # p1 指向 A 链表头结点,p2 指向 B 链表头结点 p1, p2 = headA, headB while p1 != p2: # p1 走一步,如果走到 A 链表末尾,转到 B 链表 if p1 is None: p1 = headB else: p1 = p1.next # p2 走一步,如果走到 B 链表末尾,转到 A 链表 if p2 is None: p2 = headA else: p2 = p2.next return p1 ``` https://leetcode.cn/problems/intersection-of-two-linked-lists 的多语言解法👆 https://leetcode.cn/problems/interval-list-intersections 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: vector> intervalIntersection(vector>& A, vector>& B) { vector> res; int i = 0, j = 0; while (i < A.size() && j < B.size()) { int a1 = A[i][0], a2 = A[i][1]; int b1 = B[j][0], b2 = B[j][1]; if (b2 >= a1 && a2 >= b1) { res.push_back({ max(a1, b1), min(a2, b2) }); } if (b2 < a2) { j++; } else { i++; } } return res; } }; ``` ```go // by chatGPT (go) func intervalIntersection(A [][]int, B [][]int) [][]int { res := [][]int{} i, j := 0, 0 for i < len(A) && j < len(B) { a1, a2 := A[i][0], A[i][1] b1, b2 := B[j][0], B[j][1] if b2 >= a1 && a2 >= b1 { res = append(res, []int{ max(a1, b1), min(a2, b2), }) } if b2 < a2 { j++ } else { i++ } } return res } func max(x, y int) int { if x > y { return x } return y } func min(x, y int) int { if x < y { return x } return y } ``` ```java // by labuladong (java) class Solution { public int[][] intervalIntersection(int[][] A, int[][] B) { List res = new LinkedList<>(); int i = 0, j = 0; while (i < A.length && j < B.length) { int a1 = A[i][0], a2 = A[i][1]; int b1 = B[j][0], b2 = B[j][1]; if (b2 >= a1 && a2 >= b1) { res.add(new int[]{ Math.max(a1, b1), Math.min(a2, b2) }); } if (b2 < a2) { j++; } else { i++; } } return res.toArray(new int[0][0]); } } ``` ```javascript // by chatGPT (javascript) var intervalIntersection = function(A, B) { const res = []; let i = 0, j = 0; while (i < A.length && j < B.length) { const a1 = A[i][0], a2 = A[i][1]; const b1 = B[j][0], b2 = B[j][1]; if (b2 >= a1 && a2 >= b1) { res.push([ Math.max(a1, b1), Math.min(a2, b2) ]); } if (b2 < a2) { j++; } else { i++; } } return res; }; ``` ```python # by chatGPT (python) class Solution: def intervalIntersection(self, A: List[List[int]], B: List[List[int]]) -> List[List[int]]: res = [] i, j = 0, 0 while i < len(A) and j < len(B): a1, a2 = A[i][0], A[i][1] b1, b2 = B[j][0], B[j][1] if b2 >= a1 and a2 >= b1: res.append([max(a1, b1), min(a2, b2)]) if b2 < a2: j += 1 else: i += 1 return res ``` https://leetcode.cn/problems/interval-list-intersections 的多语言解法👆 https://leetcode.cn/problems/invert-binary-tree 的多语言解法👇 ```cpp // by chatGPT (cpp) // 「遍历」的思路 class Solution { public: // 主函数 TreeNode* invertTree(TreeNode* root) { // 遍历二叉树,交换每个节点的子节点 traverse(root); return root; } // 二叉树遍历函数 void traverse(TreeNode* root) { if (root == nullptr) { return; } /**** 前序位置 ****/ // 每一个节点需要做的事就是交换它的左右子节点 TreeNode* tmp = root->left; root->left = root->right; root->right = tmp; // 遍历框架,去遍历左右子树的节点 traverse(root->left); traverse(root->right); } }; // 「分解问题」的思路 class Solution2 { public: // 定义:将以 root 为根的这棵二叉树翻转,返回翻转后的二叉树的根节点 TreeNode* invertTree(TreeNode* root) { if (root == nullptr) { return nullptr; } // 利用函数定义,先翻转左右子树 TreeNode* left = invertTree(root->left); TreeNode* right = invertTree(root->right); // 然后交换左右子节点 root->left = right; root->right = left; // 和定义逻辑自恰:以 root 为根的这棵二叉树已经被翻转,返回 root return root; } }; ``` ```go // by chatGPT (go) /** * Definition for a binary tree node. * type TreeNode struct { * Val int * Left *TreeNode * Right *TreeNode * } */ //「遍历」的思路 func invertTree(root *TreeNode) *TreeNode { //遍历二叉树,交换每个节点的子节点 traverse(root) return root } //二叉树遍历函数 func traverse(root *TreeNode) { if root == nil { return } // 每一个节点需要做的事就是交换它的左右子节点 tmp := root.Left root.Left = root.Right root.Right = tmp // 遍历框架,去遍历左右子树的节点 traverse(root.Left) traverse(root.Right) } // 「分解问题」的思路 func invertTree2(root *TreeNode) *TreeNode { if root == nil { return nil } // 利用函数定义,先翻转左右子树 left := invertTree2(root.Left) right := invertTree2(root.Right) // 然后交换左右子节点 root.Left = right root.Right = left // 和定义逻辑自恰:以 root 为根的这棵二叉树已经被翻转,返回 root return root } ``` ```java // by labuladong (java) // 「遍历」的思路 class Solution { // 主函数 public TreeNode invertTree(TreeNode root) { // 遍历二叉树,交换每个节点的子节点 traverse(root); return root; } // 二叉树遍历函数 void traverse(TreeNode root) { if (root == null) { return; } /**** 前序位置 ****/ // 每一个节点需要做的事就是交换它的左右子节点 TreeNode tmp = root.left; root.left = root.right; root.right = tmp; // 遍历框架,去遍历左右子树的节点 traverse(root.left); traverse(root.right); } } // 「分解问题」的思路 class Solution2 { // 定义:将以 root 为根的这棵二叉树翻转,返回翻转后的二叉树的根节点 TreeNode invertTree(TreeNode root) { if (root == null) { return null; } // 利用函数定义,先翻转左右子树 TreeNode left = invertTree(root.left); TreeNode right = invertTree(root.right); // 然后交换左右子节点 root.left = right; root.right = left; // 和定义逻辑自恰:以 root 为根的这棵二叉树已经被翻转,返回 root return root; } } ``` ```javascript // by chatGPT (javascript) /** * 「遍历」的思路 */ var invertTree = function(root) { /** * 二叉树遍历函数 */ var traverse = function(root) { if (root === null) { return; } /**** 前序位置 ****/ // 每一个节点需要做的事就是交换它的左右子节点 var tmp = root.left; root.left = root.right; root.right = tmp; // 遍历框架,去遍历左右子树的节点 traverse(root.left); traverse(root.right); } // 遍历二叉树,交换每个节点的子节点 traverse(root); return root; }; /** * 「分解问题」的思路 */ var invertTree2 = function(root) { /** * 定义:将以 root 为根的这棵二叉树翻转,返回翻转后的二叉树的根节点 */ var invert = function(root) { if (root === null) { return null; } // 利用函数定义,先翻转左右子树 var left = invert(root.left); var right = invert(root.right); // 然后交换左右子节点 root.left = right; root.right = left; // 和定义逻辑自恰:以 root 为根的这棵二叉树已经被翻转,返回 root return root; } // 返回翻转后的二叉树的根节点 return invert(root); }; ``` ```python # by chatGPT (python) # 「遍历」的思路 class Solution: # 主函数 def invertTree(self, root: TreeNode) -> TreeNode: # 遍历二叉树,交换每个节点的子节点 self.traverse(root) return root # 二叉树遍历函数 def traverse(self, root: TreeNode) -> None: if not root: return # 前序位置 # 每一个节点需要做的事就是交换它的左右子节点 tmp = root.left root.left = root.right root.right = tmp # 遍历框架,去遍历左右子树的节点 self.traverse(root.left) self.traverse(root.right) # 「分解问题」的思路 class Solution2: # 定义:将以 root 为根的这棵二叉树翻转,返回翻转后的二叉树的根节点 def invertTree(self, root: TreeNode) -> TreeNode: if not root: return None # 利用函数定义,先翻转左右子树 left = self.invertTree(root.left) right = self.invertTree(root.right) # 然后交换左右子节点 root.left = right root.right = left # 和定义逻辑自恰:以 root 为根的这棵二叉树已经被翻转,返回 root return root ``` https://leetcode.cn/problems/invert-binary-tree 的多语言解法👆 https://leetcode.cn/problems/is-graph-bipartite 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: // 记录图是否符合二分图性质 bool ok = true; // 记录图中节点的颜色,false 和 true 代表两种不同颜色 vector color; // 记录图中节点是否被访问过 vector visited; // 主函数,输入邻接表,判断是否是二分图 bool isBipartite(vector>& graph) { int n = graph.size(); color.resize(n); visited.resize(n); // 因为图不一定是联通的,可能存在多个子图 // 所以要把每个节点都作为起点进行一次遍历 // 如果发现任何一个子图不是二分图,整幅图都不算二分图 for (int v = 0; v < n; v++) { if (!visited[v]) { traverse(graph, v); } } return ok; } // DFS 遍历框架 void traverse(vector>& 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 traverse(graph, w); } else { // 相邻节点 w 已经被访问过 // 根据 v 和 w 的颜色判断是否是二分图 if (color[w] == color[v]) { // 若相同,则此图不是二分图 ok = false; } } } } }; ``` ```go // by chatGPT (go) func isBipartite(graph [][]int) bool { n := len(graph) // 记录图是否符合二分图性质 ok := true // 记录图中节点的颜色,false 和 true 代表两种不同颜色 color := make([]bool, n) // 记录图中节点是否被访问过 visited := make([]bool, n) traverse := func(v int) { // 如果已经确定不是二分图了,就不用浪费时间再递归遍历了 if !ok { return } visited[v] = true for _, w := range graph[v] { if !visited[w] { // 相邻节点 w 没有被访问过 // 那么应该给节点 w 涂上和节点 v 不同的颜色 color[w] = !color[v] // 继续遍历 w traverse(w) } else { // 相邻节点 w 已经被访问过 // 根据 v 和 w 的颜色判断是否是二分图 if color[w] == color[v] { // 若相同,则此图不是二分图 ok = false } } } } // 因为图不一定是联通的,可能存在多个子图 // 所以要把每个节点都作为起点进行一次遍历 // 如果发现任何一个子图不是二分图,整幅图都不算二分图 for v := 0; v < n; v++ { if !visited[v] { traverse(v) } } return ok } ``` ```java // by labuladong (java) 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]) { traverse(graph, v); } } return ok; } // DFS 遍历框架 private void traverse(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 traverse(graph, w); } else { // 相邻节点 w 已经被访问过 // 根据 v 和 w 的颜色判断是否是二分图 if (color[w] == color[v]) { // 若相同,则此图不是二分图 ok = false; } } } } } ``` ```javascript // by chatGPT (javascript) /** * @param {number[][]} graph 邻接表 * @return {boolean} 是否是二分图 */ var isBipartite = function(graph) { // 记录图是否符合二分图性质 let ok = true; // 记录图中节点的颜色,false 和 true 代表两种不同颜色 const color = new Array(graph.length).fill(false); // 记录图中节点是否被访问过 const visited = new Array(graph.length).fill(false); /** * DFS 遍历框架 * @param {number} v 节点 */ const traverse = function(v) { // 如果已经确定不是二分图了,就不用浪费时间再递归遍历了 if (!ok) { return; } visited[v] = true; for (const w of graph[v]) { if (!visited[w]) { // 相邻节点 w 没有被访问过 // 那么应该给节点 w 涂上和节点 v 不同的颜色 color[w] = !color[v]; // 继续遍历 w traverse(w); } else { // 相邻节点 w 已经被访问过 // 根据 v 和 w 的颜色判断是否是二分图 if (color[w] === color[v]) { // 若相同,则此图不是二分图 ok = false; } } } } // 因为图不一定是联通的,可能存在多个子图 // 所以要把每个节点都作为起点进行一次遍历 // 如果发现任何一个子图不是二分图,整幅图都不算二分图 for (let v = 0; v < graph.length; v++) { if (!visited[v]) { traverse(v); } } return ok; }; ``` ```python # by chatGPT (python) class Solution: def __init__(self): # 记录图是否符合二分图性质 self.ok = True # 记录图中节点的颜色,false 和 true 代表两种不同颜色 self.color = None # 记录图中节点是否被访问过 self.visited = None # 主函数,输入邻接表,判断是否是二分图 def isBipartite(self, graph: List[List[int]]) -> bool: n = len(graph) self.color = [False] * n self.visited = [False] * n # 因为图不一定是联通的,可能存在多个子图 # 所以要把每个节点都作为起点进行一次遍历 # 如果发现任何一个子图不是二分图,整幅图都不算二分图 for v in range(n): if not self.visited[v]: self.traverse(graph, v) return self.ok # DFS 遍历框架 def traverse(self, graph: List[List[int]], v: int) -> None: # 如果已经确定不是二分图了,就不用浪费时间再递归遍历了 if not self.ok: return self.visited[v] = True for w in graph[v]: if not self.visited[w]: # 相邻节点 w 没有被访问过 # 那么应该给节点 w 涂上和节点 v 不同的颜色 self.color[w] = not self.color[v] # 继续遍历 w self.traverse(graph, w) else: # 相邻节点 w 已经被访问过 # 根据 v 和 w 的颜色判断是否是二分图 if self.color[w] == self.color[v]: # 若相同,则此图不是二分图 self.ok = False ``` https://leetcode.cn/problems/is-graph-bipartite 的多语言解法👆 https://leetcode.cn/problems/is-subsequence 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: bool isSubsequence(string s, string t) { int i = 0, j = 0; while (i < s.length() && j < t.length()) { if (s[i] == t[j]) { i++; } j++; } return i == s.length(); } }; ``` ```go // by chatGPT (go) func isSubsequence(s string, t string) bool { i, j := 0, 0 for i < len(s) && j < len(t) { if s[i] == t[j] { i++ } j++ } return i == len(s) } ``` ```java // by labuladong (java) class Solution { public boolean isSubsequence(String s, String t) { int i = 0, j = 0; while (i < s.length() && j < t.length()) { if (s.charAt(i) == t.charAt(j)) { i++; } j++; } return i == s.length(); } } ``` ```javascript // by chatGPT (javascript) /** * @param {string} s * @param {string} t * @return {boolean} */ var isSubsequence = function(s, t) { let i = 0, j = 0; while (i < s.length && j < t.length) { if (s.charAt(i) === t.charAt(j)) { i++; } j++; } return i === s.length; }; ``` ```python # by chatGPT (python) class Solution: def isSubsequence(self, s: str, t: str) -> bool: i, j = 0, 0 while i < len(s) and j < len(t): if s[i] == t[j]: i += 1 j += 1 return i == len(s) ``` https://leetcode.cn/problems/is-subsequence 的多语言解法👆 https://leetcode.cn/problems/jBjn9C 的多语言解法👇 ```cpp // by chatGPT (cpp) class KthLargest { private: int k; // 默认是小顶堆 priority_queue, greater> pq; public: KthLargest(int k, vector& nums) { // 将 nums 装入小顶堆,保留下前 k 大的元素 for (int e : nums) { pq.push(e); if (pq.size() > k) { pq.pop(); } } this->k = k; } int add(int val) { // 维护小顶堆只保留前 k 大的元素 pq.push(val); if (pq.size() > k) { pq.pop(); } // 堆顶就是第 k 大元素(即倒数第 k 小的元素) return pq.top(); } }; ``` ```go // by chatGPT (go) import ( "container/heap" ) // KthLargest 是一个数据结构,它维护了一个保留前 k 大元素的小顶堆 type KthLargest struct { k int pq PriorityQueue } // Constructor 是 KthLargest 的构造函数 func Constructor(k int, nums []int) KthLargest { pq := make(PriorityQueue, 0, k) for _, e := range nums { pq.offer(e) if pq.Len() > k { pq.poll() } } return KthLargest{ k: k, pq: pq, } } // add 方法将一个元素添加到小顶堆中,并返回第 k 大元素 func (kl *KthLargest) add(val int) int { kl.pq.offer(val) if kl.pq.Len() > kl.k { kl.pq.poll() } // 堆顶就是第 k 大元素(即倒数第 k 小的元素) return kl.pq.peek() } // PriorityQueue 是一个带有 Peek 方法的小顶堆 type PriorityQueue []int // Len 返回小顶堆中的元素数量 func (pq PriorityQueue) Len() int { return len(pq) } // Less 定义了小顶堆的排序规则 func (pq PriorityQueue) Less(i, j int) bool { return pq[i] < pq[j] } // Swap 交换小顶堆中的两个元素的位置 func (pq PriorityQueue) Swap(i, j int) { pq[i], pq[j] = pq[j], pq[i] } // Push 向小顶堆中添加一个元素 func (pq *PriorityQueue) Push(x interface{}) { item := x.(int) *pq = append(*pq, item) } // Pop 从小顶堆中弹出最小的元素 func (pq *PriorityQueue) Pop() interface{} { old := *pq n := len(old) item := old[n-1] *pq = old[:n-1] return item } // Peek 返回小顶堆中的最小元素 func (pq PriorityQueue) Peek() int { return pq[0] } ``` ```java // by labuladong (java) class KthLargest { private int k; // 默认是小顶堆 private PriorityQueue pq = new PriorityQueue<>(); public KthLargest(int k, int[] nums) { // 将 nums 装入小顶堆,保留下前 k 大的元素 for (int e : nums) { pq.offer(e); if (pq.size() > k) { pq.poll(); } } this.k = k; } public int add(int val) { // 维护小顶堆只保留前 k 大的元素 pq.offer(val); if (pq.size() > k) { pq.poll(); } // 堆顶就是第 k 大元素(即倒数第 k 小的元素) return pq.peek(); } } ``` ```javascript // by chatGPT (javascript) var KthLargest = function(k, nums) { this.k = k; // 默认是小顶堆 this.pq = new PriorityQueue(); // 将 nums 装入小顶堆,保留下前 k 大的元素 for (let e of nums) { this.pq.offer(e); if (this.pq.size() > k) { this.pq.poll(); } } }; KthLargest.prototype.add = function(val) { // 维护小顶堆只保留前 k 大的元素 this.pq.offer(val); if (this.pq.size() > this.k) { this.pq.poll(); } // 堆顶就是第 k 大元素(即倒数第 k 小的元素) return this.pq.peek(); }; // PriorityQueue implementation var PriorityQueue = function() { this.data = []; }; PriorityQueue.prototype.offer = function(val) { this.data.push(val); this.bubbleUp(this.data.length - 1); }; PriorityQueue.prototype.poll = function() { const last = this.data.pop(); const result = this.data[0]; if (this.data.length > 0) { this.data[0] = last; this.bubbleDown(0); } return result; }; PriorityQueue.prototype.peek = function() { return this.data[0]; }; PriorityQueue.prototype.size = function() { return this.data.length; }; PriorityQueue.prototype.bubbleUp = function(pos) { while (pos > 0) { const parent = Math.floor((pos - 1) / 2); if (this.data[parent] > this.data[pos]) { this.swap(parent, pos); pos = parent; } else { break; } } }; PriorityQueue.prototype.bubbleDown = function(pos) { while (pos * 2 + 1 < this.data.length) { let minChild = pos * 2 + 1; if (pos * 2 + 2 < this.data.length && this.data[pos * 2 + 2] < this.data[minChild]) { minChild = pos * 2 + 2; } if (this.data[minChild] < this.data[pos]) { this.swap(minChild, pos); pos = minChild; } else { break; } } }; PriorityQueue.prototype.swap = function(i, j) { const temp = this.data[i]; this.data[i] = this.data[j]; this.data[j] = temp; }; ``` ```python # by chatGPT (python) from queue import PriorityQueue class KthLargest: def __init__(self, k: int, nums: List[int]): self.k = k # 默认是小顶堆 self.pq = PriorityQueue() # 将 nums 装入小顶堆,保留下前 k 大的元素 for e in nums: self.pq.put(e) if self.pq.qsize() > k: self.pq.get() def add(self, val: int) -> int: # 维护小顶堆只保留前 k 大的元素 self.pq.put(val) if self.pq.qsize() > self.k: self.pq.get() # 堆顶就是第 k 大元素(即倒数第 k 小的元素) return self.pq.queue[0] ``` https://leetcode.cn/problems/jBjn9C 的多语言解法👆 https://leetcode.cn/problems/ji-qi-ren-de-yun-dong-fan-wei-lcof 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int movingCount(int m, int n, int k) { vector> visited(m, vector(n, false)); // 初始化 visited 矩阵为 false dfs(m, n, k, 0, 0, visited); return res; } // 记录合法坐标数 int res = 0; void dfs(int m, int n, int k, int i, int j, vector>& visited) { if (i < 0 || j < 0 || i >= m || j >= n) { // 超出索引边界 return; } if (i / 10 + i % 10 + j / 10 + j % 10 > k) { // 坐标和超出 k 的限制 return; } if (visited[i][j]) { // 之前已经访问过当前坐标 return; } // 走到一个合法坐标 res++; visited[i][j] = true; // DFS 遍历上下左右 dfs(m, n, k, i + 1, j, visited); dfs(m, n, k, i, j + 1, visited); dfs(m, n, k, i - 1, j, visited); dfs(m, n, k, i, j - 1, visited); } }; ``` ```go // by chatGPT (go) func movingCount(m int, n int, k int) int { visited := make([][]bool, m) for i := 0; i < m; i++ { visited[i] = make([]bool, n) } res := 0 dfs(m, n, k, 0, 0, visited, &res) return res } func dfs(m, n, k, i, j int, visited [][]bool, res *int) { if i < 0 || j < 0 || i >= m || j >= n { return // 超出索引边界 } if i/10+i%10+j/10+j%10 > k { return // 坐标和超出 k 的限制 } if visited[i][j] { return // 之前已经访问过当前坐标 } *res++ visited[i][j] = true dfs(m, n, k, i+1, j, visited, res) dfs(m, n, k, i, j+1, visited, res) dfs(m, n, k, i-1, j, visited, res) dfs(m, n, k, i, j-1, visited, res) } ``` ```java // by labuladong (java) class Solution { public int movingCount(int m, int n, int k) { boolean[][] visited = new boolean[m][n]; dfs(m, n, k, 0, 0, visited); return res; } // 记录合法坐标数 int res = 0; public void dfs(int m, int n, int k, int i, int j, boolean[][] visited) { if (i < 0 || j < 0 || i >= m || j >= n) { // 超出索引边界 return; } if (i / 10 + i % 10 + j / 10 + j % 10 > k) { // 坐标和超出 k 的限制 return; } if (visited[i][j]) { // 之前已经访问过当前坐标 return; } // 走到一个合法坐标 res++; visited[i][j] = true; // DFS 遍历上下左右 dfs(m, n, k, i + 1, j, visited); dfs(m, n, k, i, j + 1, visited); dfs(m, n, k, i - 1, j, visited); dfs(m, n, k, i, j - 1, visited); } } ``` ```javascript // by chatGPT (javascript) var movingCount = function(m, n, k) { let visited = new Array(m).fill(false).map(() => new Array(n).fill(false)); let res = 0; function dfs(m, n, k, i, j, visited) { if (i < 0 || j < 0 || i >= m || j >= n) { // 超出索引边界 return; } if (Math.floor(i / 10) + i % 10 + Math.floor(j / 10) + j % 10 > k) { // 坐标和超出 k 的限制 return; } if (visited[i][j]) { // 之前已经访问过当前坐标 return; } // 走到一个合法坐标 res++; visited[i][j] = true; // DFS 遍历上下左右 dfs(m, n, k, i + 1, j, visited); dfs(m, n, k, i, j + 1, visited); dfs(m, n, k, i - 1, j, visited); dfs(m, n, k, i, j - 1, visited); } dfs(m, n, k, 0, 0, visited); return res; }; ``` ```python # by chatGPT (python) class Solution: def movingCount(self, m: int, n: int, k: int) -> int: def dfs(i: int, j: int, visited: List[List[bool]]): nonlocal res if i < 0 or j < 0 or i >= m or j >= n: # 超出索引边界 return if i // 10 + i % 10 + j // 10 + j % 10 > k: # 坐标和超出 k 的限制 return if visited[i][j]: # 之前已经访问过当前坐标 return # 走到一个合法坐标 res += 1 visited[i][j] = True # DFS 遍历上下左右 dfs(i + 1, j, visited) dfs(i, j + 1, visited) dfs(i - 1, j, visited) dfs(i, j - 1, visited) # 记录合法坐标数 res = 0 visited = [[False] * n for _ in range(m)] dfs(0, 0, visited) return res ``` https://leetcode.cn/problems/ji-qi-ren-de-yun-dong-fan-wei-lcof 的多语言解法👆 https://leetcode.cn/problems/jump-game 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: bool canJump(vector& nums) { int n = nums.size(); int farthest = 0; for (int i = 0; i < n - 1; i++) { // 不断计算能跳到的最远距离 farthest = max(farthest, i + nums[i]); // 可能碰到了 0,卡住跳不动了 if (farthest <= i) { return false; } } return farthest >= n - 1; } }; ``` ```go // by chatGPT (go) func canJump(nums []int) bool { n := len(nums) farthest := 0 for i := 0; i < n-1; i++ { // 不断计算能跳到的最远距离 farthest = max(farthest, i+nums[i]) // 可能碰到了 0,卡住跳不动了 if farthest <= i { return false } } return farthest >= n-1 } func max(x, y int) int { if x > y { return x } return y } ``` ```java // by labuladong (java) class Solution { public boolean canJump(int[] nums) { int n = nums.length; int farthest = 0; for (int i = 0; i < n - 1; i++) { // 不断计算能跳到的最远距离 farthest = Math.max(farthest, i + nums[i]); // 可能碰到了 0,卡住跳不动了 if (farthest <= i) { return false; } } return farthest >= n - 1; } } ``` ```javascript // by chatGPT (javascript) var canJump = function(nums) { let n = nums.length; let farthest = 0; for (let i = 0; i < n - 1; i++) { // 不断计算能跳到的最远距离 farthest = Math.max(farthest, i + nums[i]); // 可能碰到了 0,卡住跳不动了 if (farthest <= i) { return false; } } return farthest >= n - 1; }; ``` ```python # by chatGPT (python) class Solution: def canJump(self, nums: List[int]) -> bool: n = len(nums) farthest = 0 for i in range(n - 1): # 不断计算能跳到的最远距离 farthest = max(farthest, i + nums[i]) # 可能碰到了 0,卡住跳不动了 if farthest <= i: return False return farthest >= n - 1 ``` https://leetcode.cn/problems/jump-game 的多语言解法👆 https://leetcode.cn/problems/jump-game-ii 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int jump(vector& nums) { int n = nums.size(); int end = 0, farthest = 0; int jumps = 0; for (int i = 0; i < n - 1; i++) { farthest = max(nums[i] + i, farthest); if (end == i) { jumps++; end = farthest; } } return jumps; } }; ``` ```go // by chatGPT (go) func jump(nums []int) int { n := len(nums) end, farthest, jumps := 0, 0, 0 for i := 0; i < n-1; i++ { farthest = max(nums[i]+i, farthest) if end == i { jumps++ end = farthest } } return jumps } func max(x, y int) int { if x > y { return x } return y } ``` ```java // by labuladong (java) class Solution { public int jump(int[] nums) { int n = nums.length; int end = 0, farthest = 0; int jumps = 0; for (int i = 0; i < n - 1; i++) { farthest = Math.max(nums[i] + i, farthest); if (end == i) { jumps++; end = farthest; } } return jumps; } } ``` ```javascript // by chatGPT (javascript) /** * @param {number[]} nums * @return {number} */ var jump = function(nums) { const n = nums.length; let end = 0, farthest = 0, jumps = 0; for (let i = 0; i < n - 1; i++) { farthest = Math.max(nums[i] + i, farthest); if (end === i) { jumps++; end = farthest; } } return jumps; }; ``` ```python # by chatGPT (python) class Solution: def jump(self, nums: List[int]) -> int: n = len(nums) end, farthest, jumps = 0, 0, 0 for i in range(n - 1): farthest = max(nums[i] + i, farthest) if end == i: jumps += 1 end = farthest return jumps ``` https://leetcode.cn/problems/jump-game-ii 的多语言解法👆 https://leetcode.cn/problems/kill-process 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: vector killProcess(vector& pid, vector& ppid, int kill) { // 构建多叉树,key 为父节点,value 为所有子节点的列表 unordered_map> tree; for (int i = 0; i < pid.size(); i++) { int child = pid[i]; int parent = ppid[i]; tree[parent].push_back(child); } vector res; // 我这里用 BFS 算法遍历子树,删除以 kill 为根的所有子节点 queue q; q.push(kill); while (!q.empty()) { int cur = q.front(); q.pop(); res.push_back(cur); if (tree.count(cur)) { // 所有子节点入队 for (int child : tree[cur]) { q.push(child); } } } return res; } }; ``` ```go // by chatGPT (go) func killProcess(pid []int, ppid []int, kill int) []int { // 构建多叉树,key 为父节点,value 为所有子节点的列表 tree := make(map[int][]int) for i := 0; i < len(pid); i++ { child := pid[i] parent := ppid[i] _, ok := tree[parent] if !ok { tree[parent] = make([]int, 0) } tree[parent] = append(tree[parent], child) } res := make([]int, 0) // 我这里用 BFS 算法遍历子树,删除以 kill 为根的所有子节点 q := make([]int, 0) q = append(q, kill) for len(q) > 0 { cur := q[0] q = q[1:] res = append(res, cur) if children, ok := tree[cur]; ok { // 所有子节点入队 for _, child := range children { q = append(q, child) } } } return res } ``` ```java // by labuladong (java) class Solution { public List killProcess(List pid, List ppid, int kill) { // 构建多叉树,key 为父节点,value 为所有子节点的列表 HashMap> tree = new HashMap<>(); for (int i = 0; i < pid.size(); i++) { int child = pid.get(i); int parent = ppid.get(i); tree.putIfAbsent(parent, new ArrayList<>()); tree.get(parent).add(child); } List res = new LinkedList<>(); // 我这里用 BFS 算法遍历子树,删除以 kill 为根的所有子节点 Queue q = new LinkedList<>(); q.offer(kill); while (!q.isEmpty()) { int cur = q.poll(); res.add(cur); if (tree.containsKey(cur)) { // 所有子节点入队 q.addAll(tree.get(cur)); } } return res; } } ``` ```javascript // by chatGPT (javascript) var killProcess = function(pid, ppid, kill) { // 构建多叉树,key 为父节点,value 为所有子节点的列表 const tree = new Map(); for (let i = 0; i < pid.length; i++) { const child = pid[i]; const parent = ppid[i]; if (!tree.has(parent)) { tree.set(parent, []); } tree.get(parent).push(child); } const res = []; // 我这里用 BFS 算法遍历子树,删除以 kill 为根的所有子节点 const q = []; q.push(kill); while (q.length) { const cur = q.shift(); res.push(cur); if (tree.has(cur)) { // 所有子节点入队 q.push(...tree.get(cur)); } } return res; }; ``` ```python # by chatGPT (python) class Solution: def killProcess(self, pid: List[int], ppid: List[int], kill: int) -> List[int]: # 构建多叉树,key 为父节点,value 为所有子节点的列表 tree = {} for i in range(len(pid)): child = pid[i] parent = ppid[i] if parent not in tree: tree[parent] = [] tree[parent].append(child) res = [] # 我这里用 BFS 算法遍历子树,删除以 kill 为根的所有子节点 q = [] q.append(kill) while len(q) > 0: cur = q.pop(0) res.append(cur) if cur in tree: # 所有子节点入队 q.extend(tree[cur]) return res ``` https://leetcode.cn/problems/kill-process 的多语言解法👆 https://leetcode.cn/problems/koko-eating-bananas 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int minEatingSpeed(vector& piles, int H) { int left = 1; int right = 1000000000 + 1; while (left < right) { int mid = left + (right - left) / 2; if (f(piles, mid) <= H) { right = mid; } else { left = mid + 1; } } return left; } // 定义:速度为 x 时,需要 f(x) 小时吃完所有香蕉 // f(x) 随着 x 的增加单调递减 int f(vector& piles, int x) { int hours = 0; for (int i = 0; i < piles.size(); i++) { hours += piles[i] / x; if (piles[i] % x > 0) { hours++; } } return hours; } }; ``` ```go // by chatGPT (go) func minEatingSpeed(piles []int, H int) int { left, right := 1, 1000000000+1 for left < right { mid := left + (right-left)/2 if f(piles, mid) <= H { right = mid } else { left = mid + 1 } } return left } // 定义:速度为 x 时,需要 f(x) 小时吃完所有香蕉 // f(x) 随着 x 的增加单调递减 func f(piles []int, x int) int { hours := 0 for i := 0; i < len(piles); i++ { hours += piles[i] / x if piles[i]%x > 0 { hours++ } } return hours } ``` ```java // by labuladong (java) class Solution { public int minEatingSpeed(int[] piles, int H) { int left = 1; int right = 1000000000 + 1; while (left < right) { int mid = left + (right - left) / 2; if (f(piles, mid) <= H) { right = mid; } else { left = mid + 1; } } return left; } // 定义:速度为 x 时,需要 f(x) 小时吃完所有香蕉 // f(x) 随着 x 的增加单调递减 int f(int[] piles, int x) { int hours = 0; for (int i = 0; i < piles.length; i++) { hours += piles[i] / x; if (piles[i] % x > 0) { hours++; } } return hours; } } ``` ```javascript // by chatGPT (javascript) var minEatingSpeed = function(piles, H) { let left = 1; let right = 1000000000 + 1; while (left < right) { let mid = left + Math.floor((right - left) / 2); if (f(piles, mid) <= H) { right = mid; } else { left = mid + 1; } } return left; }; // 定义:速度为 x 时,需要 f(x) 小时吃完所有香蕉 // f(x) 随着 x 的增加单调递减 var f = function(piles, x) { let hours = 0; for (let i = 0; i < piles.length; i++) { hours += Math.floor(piles[i] / x); if (piles[i] % x > 0) { hours++; } } return hours; }; ``` ```python # by chatGPT (python) class Solution: def minEatingSpeed(self, piles: List[int], H: int) -> int: left = 1 right = 1000000000 + 1 while left < right: mid = left + (right - left) // 2 if self.f(piles, mid) <= H: right = mid else: left = mid + 1 return left # 定义:速度为 x 时,需要 f(x) 小时吃完所有香蕉 # f(x) 随着 x 的增加单调递减 def f(self, piles: List[int], x: int) -> int: hours = 0 for i in range(len(piles)): hours += piles[i] // x if piles[i] % x > 0: hours += 1 return hours ``` https://leetcode.cn/problems/koko-eating-bananas 的多语言解法👆 https://leetcode.cn/problems/kth-largest-element-in-a-stream 的多语言解法👇 ```cpp // by chatGPT (cpp) class KthLargest { private: int k; // 默认是小顶堆 priority_queue, greater> pq; public: KthLargest(int k, vector& nums) { // 将 nums 装入小顶堆,保留下前 k 大的元素 for (int e : nums) { pq.push(e); if (pq.size() > k) { pq.pop(); } } this->k = k; } int add(int val) { // 维护小顶堆只保留前 k 大的元素 pq.push(val); if (pq.size() > k) { pq.pop(); } // 堆顶就是第 k 大元素(即倒数第 k 小的元素) return pq.top(); } }; ``` ```go // by chatGPT (go) type KthLargest struct { k int pq *PriorityQueue } func Constructor(k int, nums []int) KthLargest { // 初始化优先队列 pq := PriorityQueue{} heap.Init(&pq) // 将 nums 装入小顶堆,保留下前 k 大的元素 for _, e := range nums { heap.Push(&pq, e) if pq.Len() > k { heap.Pop(&pq) } } return KthLargest{k: k, pq: &pq} } func (this *KthLargest) Add(val int) int { // 维护小顶堆只保留前 k 大的元素 heap.Push(this.pq, val) if this.pq.Len() > this.k { heap.Pop(this.pq) } // 堆顶就是第 k 大元素(即倒数第 k 小的元素) return this.pq.Peek() } type PriorityQueue []int func (pq PriorityQueue) Len() int { return len(pq) } func (pq PriorityQueue) Less(i, j int) bool { return pq[i] < pq[j] } func (pq PriorityQueue) Swap(i, j int) { pq[i], pq[j] = pq[j], pq[i] } func (pq *PriorityQueue) Push(x interface{}) { *pq = append(*pq, x.(int)) } func (pq *PriorityQueue) Pop() interface{} { n := len(*pq) x := (*pq)[n-1] *pq = (*pq)[:n-1] return x } func (pq *PriorityQueue) Peek() int { return (*pq)[0] } ``` ```java // by labuladong (java) class KthLargest { private int k; // 默认是小顶堆 private PriorityQueue pq = new PriorityQueue<>(); public KthLargest(int k, int[] nums) { // 将 nums 装入小顶堆,保留下前 k 大的元素 for (int e : nums) { pq.offer(e); if (pq.size() > k) { pq.poll(); } } this.k = k; } public int add(int val) { // 维护小顶堆只保留前 k 大的元素 pq.offer(val); if (pq.size() > k) { pq.poll(); } // 堆顶就是第 k 大元素(即倒数第 k 小的元素) return pq.peek(); } } ``` ```javascript // by chatGPT (javascript) var KthLargest = function(k, nums) { this.k = k; // 默认是小顶堆 this.pq = new PriorityQueue(); // 将 nums 装入小顶堆,保留下前 k 大的元素 for (var i = 0; i < nums.length; i++) { this.pq.offer(nums[i]); if (this.pq.size() > k) { this.pq.poll(); } } }; KthLargest.prototype.add = function(val) { // 维护小顶堆只保留前 k 大的元素 this.pq.offer(val); if (this.pq.size() > this.k) { this.pq.poll(); } // 堆顶就是第 k 大元素(即倒数第 k 小的元素) return this.pq.peek(); }; // 实现一个小顶堆,自行实现添加和弹出方法 class PriorityQueue { constructor() { this.data = []; } offer(val) { this.data.push(val); this.heapifyUp(); } poll() { const first = this.data[0]; const last = this.data.pop(); if (this.data.length > 0) { this.data[0] = last; this.heapifyDown(); } return first; } size() { return this.data.length; } heapifyUp() { let i = this.data.length - 1; while (i > 0) { const parent = (i - 1) >> 1; if (this.data[parent] > this.data[i]) { this.swap(parent, i); i = parent; } else { break; } } } heapifyDown() { let i = 0; const size = this.data.length; while (i < size) { let left = i * 2 + 1; let right = i * 2 + 2; if (left >= size) { break; } let j = left; if (right < size && this.data[right] < this.data[left]) { j = right; } if (this.data[i] > this.data[j]) { this.swap(i, j); i = j; } else { break; } } } swap(i, j) { [this.data[i], this.data[j]] = [this.data[j], this.data[i]]; } } ``` ```python # by chatGPT (python) import queue class KthLargest: def __init__(self, k: int, nums: List[int]): self.k = k self.pq = queue.PriorityQueue() # 将 nums 装入小顶堆,保留下前 k 大的元素 for e in nums: self.pq.put(e) if self.pq.qsize() > k: self.pq.get() def add(self, val: int) -> int: # 维护小顶堆只保留前 k 大的元素 self.pq.put(val) if self.pq.qsize() > self.k: self.pq.get() # 堆顶就是第 k 大元素(即倒数第 k 小的元素) return self.pq.queue[0] ``` https://leetcode.cn/problems/kth-largest-element-in-a-stream 的多语言解法👆 https://leetcode.cn/problems/kth-largest-element-in-an-array 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int findKthLargest(vector& nums, int k) { // 小顶堆,堆顶是最小元素 priority_queue, greater> pq; for (int e : nums) { // 每个元素都要过一遍二叉堆 pq.push(e); // 堆中元素多于 k 个时,删除堆顶元素 if (pq.size() > k) { pq.pop(); } } // pq 中剩下的是 nums 中 k 个最大元素, // 堆顶是最小的那个,即第 k 个最大元素 return pq.top(); } }; ``` ```go // by chatGPT (go) func findKthLargest(nums []int, k int) int { // 小顶堆,堆顶是最小元素 pq := priorityQueue{} for _, e := range nums { // 每个元素都要过一遍二叉堆 pq.offer(e) // 堆中元素多于 k 个时,删除堆顶元素 if pq.size() > k { pq.poll() } } // pq 中剩下的是 nums 中 k 个最大元素, // 堆顶是最小的那个,即第 k 个最大元素 return pq.peek() } type priorityQueue []int func (pq *priorityQueue) Len() int { return len(*pq) } func (pq *priorityQueue) Less(i, j int) bool { return (*pq)[i] < (*pq)[j] } func (pq *priorityQueue) Swap(i, j int) { (*pq)[i], (*pq)[j] = (*pq)[j], (*pq)[i] } func (pq *priorityQueue) Push(x interface{}) { *pq = append(*pq, x.(int)) } func (pq *priorityQueue) Pop() interface{} { old := *pq n := len(old) x := old[n-1] *pq = old[0 : n-1] return x } func (pq *priorityQueue) offer(e int) { heap.Push(pq, e) } func (pq *priorityQueue) poll() int { return heap.Pop(pq).(int) } func (pq *priorityQueue) peek() int { return (*pq)[0] } ``` ```java // by labuladong (java) class Solution { public int findKthLargest(int[] nums, int k) { // 小顶堆,堆顶是最小元素 PriorityQueue pq = new PriorityQueue<>(); for (int e : nums) { // 每个元素都要过一遍二叉堆 pq.offer(e); // 堆中元素多于 k 个时,删除堆顶元素 if (pq.size() > k) { pq.poll(); } } // pq 中剩下的是 nums 中 k 个最大元素, // 堆顶是最小的那个,即第 k 个最大元素 return pq.peek(); } } ``` ```javascript // by chatGPT (javascript) var findKthLargest = function(nums, k) { // 小顶堆,堆顶是最小元素 let pq = new PriorityQueue(); for (let e of nums) { // 每个元素都要过一遍二叉堆 pq.offer(e); // 堆中元素多于 k 个时,删除堆顶元素 if (pq.size() > k) { pq.poll(); } } // pq 中剩下的是 nums 中 k 个最大元素, // 堆顶是最小的那个,即第 k 个最大元素 return pq.peek(); }; // JavaScript 的 PriorityQueue 实现可能不太一样,这里只是一个简单实现作为例子 class PriorityQueue { constructor() { this.data = []; } offer(val) { this.data.push(val); this.bubbleUp(this.size() - 1); } poll() { if (this.size() === 0) { return undefined; } const result = this.data[0]; const last = this.data.pop(); if (this.size() !== 0) { this.data[0] = last; this.bubbleDown(0); } return result; } peek() { return this.size() === 0 ? undefined : this.data[0]; } size() { return this.data.length; } bubbleUp(index) { while (index > 0) { const parentIndex = Math.floor((index - 1) / 2); if (this.data[index] >= this.data[parentIndex]) { break; } this.swap(index, parentIndex); index = parentIndex; } } bubbleDown(index) { while (STATUS_PASSED_TEST) { const leftChildIndex = index * 2 + 1; const rightChildIndex = index * 2 + 2; let smallestChildIndex = index; if (leftChildIndex < this.size() && this.data[leftChildIndex] < this.data[smallestChildIndex]) { smallestChildIndex = leftChildIndex; } if (rightChildIndex < this.size() && this.data[rightChildIndex] < this.data[smallestChildIndex]) { smallestChildIndex = rightChildIndex; } if (smallestChildIndex === index) { break; } this.swap(index, smallestChildIndex); index = smallestChildIndex; } } swap(i, j) { [this.data[i], this.data[j]] = [this.data[j], this.data[i]]; } } ``` ```python # by chatGPT (python) import heapq class Solution: def findKthLargest(self, nums: List[int], k: int) -> int: # 小顶堆,堆顶是最小元素 pq = [] for e in nums: # 每个元素都要过一遍二叉堆 heapq.heappush(pq, e) # 堆中元素多于 k 个时,删除堆顶元素 if len(pq) > k: heapq.heappop(pq) # pq 中剩下的是 nums 中 k 个最大元素, # 堆顶是最小的那个,即第 k 个最大元素 return pq[0] ``` https://leetcode.cn/problems/kth-largest-element-in-an-array 的多语言解法👆 https://leetcode.cn/problems/kth-smallest-element-in-a-bst 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int kthSmallest(TreeNode* root, int k) { // 利用 BST 的中序遍历特性 traverse(root, k); return res; } private: // 记录结果 int res = 0; // 记录当前元素的排名 int rank = 0; void traverse(TreeNode* root, int k) { if (root == nullptr) { return; } traverse(root->left, k); /* 中序遍历代码位置 */ rank++; if (k == rank) { // 找到第 k 小的元素 res = root->val; return; } /*****************/ traverse(root->right, k); } }; ``` ```go // by chatGPT (go) /** * Definition for a binary tree node. * type TreeNode struct { * Val int * Left *TreeNode * Right *TreeNode * } */ // kthSmallest returns the kth smallest element in the BST rooted with root. func kthSmallest(root *TreeNode, k int) int { // 记录结果 var res int // 记录当前元素的排名 var rank int // traverse recursively traverses the BST rooted with root in-order // and finds the rank-th smallest element in the BST. // It updates rank and res accordingly. var traverse func(root *TreeNode) traverse = func(root *TreeNode) { if root == nil { return } traverse(root.Left) /* 中序遍历代码位置 */ rank++ if k == rank { // 找到第 k 小的元素 res = root.Val return } /*****************/ traverse(root.Right) } traverse(root) return res } ``` ```java // by labuladong (java) class Solution { public int kthSmallest(TreeNode root, int k) { // 利用 BST 的中序遍历特性 traverse(root, k); return res; } // 记录结果 int res = 0; // 记录当前元素的排名 int rank = 0; void traverse(TreeNode root, int k) { if (root == null) { return; } traverse(root.left, k); /* 中序遍历代码位置 */ rank++; if (k == rank) { // 找到第 k 小的元素 res = root.val; return; } /*****************/ traverse(root.right, k); } } ``` ```javascript // by chatGPT (javascript) var kthSmallest = function(root, k) { // 记录结果 let res = 0; // 记录当前元素的排名 let rank = 0; function traverse(root, k) { if (root == null) { return; } traverse(root.left, k); /* 中序遍历代码位置 */ rank++; if (k == rank) { // 找到第 k 小的元素 res = root.val; return; } /*****************/ traverse(root.right, k); } // 利用 BST 的中序遍历特性 traverse(root, k); return res; }; ``` ```python # by chatGPT (python) class Solution: def kthSmallest(self, root: TreeNode, k: int) -> int: # 利用 BST 的中序遍历特性 self.res = 0 self.rank = 0 self.traverse(root, k) return self.res def traverse(self, root: TreeNode, k: int) -> None: if root is None: return self.traverse(root.left, k) # 中序遍历代码位置 self.rank += 1 if k == self.rank: # 找到第 k 小的元素 self.res = root.val return self.traverse(root.right, k) ``` https://leetcode.cn/problems/kth-smallest-element-in-a-bst 的多语言解法👆 https://leetcode.cn/problems/kth-smallest-element-in-a-sorted-matrix 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int kthSmallest(vector>& matrix, int k) { // 存储二元组 (matrix[i][j], i, j) // i, j 记录当前元素的索引位置,用于生成下一个节点 priority_queue, vector>, greater>> pq; // 初始化优先级队列,把每一行的第一个元素装进去 for (int i = 0; i < matrix.size(); i++) { pq.push({matrix[i][0], i, 0}); } int res = -1; // 执行合并多个有序链表的逻辑,找到第 k 小的元素 while (!pq.empty() && k > 0) { auto cur = pq.top(); pq.pop(); res = cur[0]; k--; // 链表中的下一个节点加入优先级队列 int i = cur[1], j = cur[2]; if (j + 1 < matrix[i].size()) { pq.push({matrix[i][j + 1], i, j + 1}); } } return res; } }; ``` ```go // by chatGPT (go) import ( "container/heap" ) func kthSmallest(matrix [][]int, k int) int { // 自定义一个最小堆类型 pq := IntHeap{} // 初始化堆,把每一行的第一个元素装进去 for i := 0; i < len(matrix); i++ { pq = append(pq, Item{value: matrix[i][0], row: i, col: 0}) } heap.Init(&pq) var res int // 执行合并多个有序链表的逻辑,找到第 k 小的元素 for k > 0 && pq.Len() > 0 { cur := heap.Pop(&pq).(Item) res = cur.value k-- // 链表中的下一个节点加入堆 row, col := cur.row, cur.col+1 if col < len(matrix[row]) { heap.Push(&pq, Item{value: matrix[row][col], row: row, col: col}) } } return res } // 定义一个 Item 类型,表示堆中的元素 type Item struct { value int // 当前元素的值 row int // 当前元素所在的行 col int // 当前元素所在的列 } // 定义一个最小堆类型 IntHeap // 实现 heap.Interface 接口的方法 type IntHeap []Item func (t IntHeap) Len() int { return len(t) } func (t IntHeap) Less(i, j int) bool { return t[i].value < t[j].value } func (t IntHeap) Swap(i, j int) { t[i], t[j] = t[j], t[i] } func (t *IntHeap) Push(x interface{}) { *t = append(*t, x.(Item)) } func (t *IntHeap) Pop() interface{} { n := len(*t) x := (*t)[n-1] *t = (*t)[:n-1] return x } ``` ```java // by labuladong (java) 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; // 执行合并多个有序链表的逻辑,找到第 k 小的元素 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; } } ``` ```javascript // by chatGPT (javascript) var kthSmallest = function(matrix, k) { // 存储二元组 (matrix[i][j], i, j) // i, j 记录当前元素的索引位置,用于生成下一个节点 const pq = new PriorityQueue((a, b) => { // 按照元素大小升序排序 return a[0] - b[0]; }); // 初始化优先级队列,把每一行的第一个元素装进去 for (let i = 0; i < matrix.length; i++) { pq.offer([matrix[i][0], i, 0]); } let res = -1; // 执行合并多个有序链表的逻辑,找到第 k 小的元素 while (!pq.isEmpty() && k > 0) { const cur = pq.poll(); res = cur[0]; k--; // 链表中的下一个节点加入优先级队列 const i = cur[1], j = cur[2]; if (j + 1 < matrix[i].length) { pq.offer([matrix[i][j + 1], i, j + 1]); } } return res; }; class PriorityQueue { constructor(comparator) { this.heap = []; this.comparator = comparator; } isEmpty() { return this.heap.length === 0; } offer(val) { this.heap.push(val); this.bubbleUp(this.heap.length - 1); } poll() { if (this.isEmpty()) { return null; } const val = this.heap[0]; const lastVal = this.heap.pop(); if (this.heap.length > 0) { this.heap[0] = lastVal; this.sinkDown(0); } return val; } bubbleUp(pos) { while (pos > 0) { const parentPos = (pos - 1) >>> 1; if (this.comparator(this.heap[pos], this.heap[parentPos]) < 0) { [this.heap[pos], this.heap[parentPos]] = [this.heap[parentPos], this.heap[pos]]; pos = parentPos; } else { break; } } } sinkDown(pos) { const lastPos = this.heap.length - 1; while (STATUS_PASSED_TEST) { const leftChildPos = pos * 2 + 1; const rightChildPos = pos * 2 + 2; let minPos = pos; if (leftChildPos <= lastPos && this.comparator(this.heap[leftChildPos], this.heap[minPos]) < 0) { minPos = leftChildPos; } if (rightChildPos <= lastPos && this.comparator(this.heap[rightChildPos], this.heap[minPos]) < 0) { minPos = rightChildPos; } if (minPos !== pos) { [this.heap[pos], this.heap[minPos]] = [this.heap[minPos], this.heap[pos]]; pos = minPos; } else { break; } } } }; ``` ```python # by chatGPT (python) import heapq class Solution: def kthSmallest(self, matrix: List[List[int]], k: int) -> int: # 存储二元组 (matrix[i][j], i, j) # i, j 记录当前元素的索引位置,用于生成下一个节点 pq = [] # 初始化优先级队列,把每一行的第一个元素装进去 for i in range(len(matrix)): heapq.heappush(pq, [matrix[i][0], i, 0]) res = -1 # 执行合并多个有序链表的逻辑,找到第 k 小的元素 while len(pq) > 0 and k > 0: cur = heapq.heappop(pq) res = cur[0] k -= 1 # 链表中的下一个节点加入优先级队列 i, j = cur[1], cur[2] if j + 1 < len(matrix[i]): heapq.heappush(pq, [matrix[i][j + 1], i, j + 1]) return res ``` https://leetcode.cn/problems/kth-smallest-element-in-a-sorted-matrix 的多语言解法👆 https://leetcode.cn/problems/lMSNwu 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) { // 把链表元素转入栈中 stack stk1, stk2; while (l1 != nullptr) { stk1.push(l1->val); l1 = l1->next; } while (l2 != nullptr) { stk2.push(l2->val); l2 = l2->next; } // 接下来基本上是复用我在第 2 题的代码逻辑 // 注意新节点要直接插入到 dummy 后面 // 虚拟头结点(构建新链表时的常用技巧) ListNode* dummy = new ListNode(-1); // 记录进位 int carry = 0; // 开始执行加法,两条链表走完且没有进位时才能结束循环 while (!stk1.empty() || !stk2.empty() || carry > 0) { // 先加上上次的进位 int val = carry; if (!stk1.empty()) { val += stk1.top(); stk1.pop(); } if (!stk2.empty()) { val += stk2.top(); stk2.pop(); } // 处理进位情况 carry = val / 10; val = val % 10; // 构建新节点,直接接在 dummy 后面 ListNode* newNode = new ListNode(val); newNode->next = dummy->next; dummy->next = newNode; } // 返回结果链表的头结点(去除虚拟头结点) ListNode* retNode = dummy->next; delete dummy; return retNode; } }; ``` ```go // by chatGPT (go) func addTwoNumbers(l1 *ListNode, l2 *ListNode) *ListNode { // 把链表元素转入栈中 stk1 := new(Stack) for l1 != nil { stk1.Push(l1.Val) l1 = l1.Next } stk2 := new(Stack) for l2 != nil { stk2.Push(l2.Val) l2 = l2.Next } // 接下来基本上是复用我在第 2 题的代码逻辑 // 注意新节点要直接插入到 dummy 后面 // 虚拟头结点(构建新链表时的常用技巧) dummy := &ListNode{Val: -1} // 记录进位 carry := 0 // 开始执行加法,两条链表走完且没有进位时才能结束循环 for !stk1.IsEmpty() || !stk2.IsEmpty() || carry > 0 { // 先加上上次的进位 val := carry if !stk1.IsEmpty() { val += stk1.Pop().(int) } if !stk2.IsEmpty() { val += stk2.Pop().(int) } // 处理进位情况 carry = val / 10 val = val % 10 // 构建新节点,直接接在 dummy 后面 newNode := &ListNode{Val: val} newNode.Next = dummy.Next dummy.Next = newNode } // 返回结果链表的头结点(去除虚拟头结点) return dummy.Next } // Stack 定义 type Stack struct { values []interface{} } func (s *Stack) Push(val interface{}) { s.values = append(s.values, val) } func (s *Stack) Pop() interface{} { if s.IsEmpty() { return nil } lastIndex := len(s.values) - 1 val := s.values[lastIndex] s.values = s.values[:lastIndex] return val } func (s *Stack) IsEmpty() bool { return len(s.values) == 0 } ``` ```java // by labuladong (java) class Solution { public ListNode addTwoNumbers(ListNode l1, ListNode l2) { // 把链表元素转入栈中 Stack stk1 = new Stack<>(); while (l1 != null) { stk1.push(l1.val); l1 = l1.next; } Stack stk2 = new Stack<>(); while (l2 != null) { stk2.push(l2.val); l2 = l2.next; } // 接下来基本上是复用我在第 2 题的代码逻辑 // 注意新节点要直接插入到 dummy 后面 // 虚拟头结点(构建新链表时的常用技巧) ListNode dummy = new ListNode(-1); // 记录进位 int carry = 0; // 开始执行加法,两条链表走完且没有进位时才能结束循环 while (!stk1.isEmpty() || !stk2.isEmpty() || carry > 0) { // 先加上上次的进位 int val = carry; if (!stk1.isEmpty()) { val += stk1.pop(); } if (!stk2.isEmpty()) { val += stk2.pop(); } // 处理进位情况 carry = val / 10; val = val % 10; // 构建新节点,直接接在 dummy 后面 ListNode newNode = new ListNode(val); newNode.next = dummy.next; dummy.next = newNode; } // 返回结果链表的头结点(去除虚拟头结点) return dummy.next; } } ``` ```javascript // by chatGPT (javascript) var addTwoNumbers = function(l1, l2) { // 把链表元素转入栈中 const stk1 = []; while (l1 !== null) { stk1.push(l1.val); l1 = l1.next; } const stk2 = []; while (l2 !== null) { stk2.push(l2.val); l2 = l2.next; } // 接下来基本上是复用我在第 2 题的代码逻辑 // 注意新节点要直接插入到 dummy 后面 // 虚拟头结点(构建新链表时的常用技巧) const dummy = new ListNode(-1); // 记录进位 let carry = 0; // 开始执行加法,两条链表走完且没有进位时才能结束循环 while (stk1.length !== 0 || stk2.length !== 0 || carry > 0) { // 先加上上次的进位 let val = carry; if (stk1.length !== 0) { val += stk1.pop(); } if (stk2.length !== 0) { val += stk2.pop(); } // 处理进位情况 carry = Math.floor(val / 10); val = val % 10; // 构建新节点,直接接在 dummy 后面 const newNode = new ListNode(val); newNode.next = dummy.next; dummy.next = newNode; } // 返回结果链表的头结点(去除虚拟头结点) return dummy.next; }; ``` ```python # by chatGPT (python) class Solution: def addTwoNumbers(self, l1: ListNode, l2: ListNode) -> ListNode: # 把链表元素转入栈中 stk1 = [] while l1: stk1.append(l1.val) l1 = l1.next stk2 = [] while l2: stk2.append(l2.val) l2 = l2.next # 接下来基本上是复用我在第 2 题的代码逻辑 # 注意新节点要直接插入到 dummy 后面 # 虚拟头结点(构建新链表时的常用技巧) dummy = ListNode(-1) # 记录进位 carry = 0 # 开始执行加法,两条链表走完且没有进位时才能结束循环 while stk1 or stk2 or carry > 0: # 先加上上次的进位 val = carry if stk1: val += stk1.pop() if stk2: val += stk2.pop() # 处理进位情况 carry = val // 10 val = val % 10 # 构建新节点,直接接在 dummy 后面 newNode = ListNode(val) newNode.next = dummy.next dummy.next = newNode # 返回结果链表的头结点(去除虚拟头结点) return dummy.next ``` https://leetcode.cn/problems/lMSNwu 的多语言解法👆 https://leetcode.cn/problems/letter-case-permutation 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: string track; vector res; vector letterCasePermutation(string s) { backtrack(s, 0); return res; } void backtrack(string s, int index) { if (index == s.size()) { res.push_back(track); return; } if ('0' <= s[index] && s[index] <= '9') { // s[index] 是数字 // 做选择 track.push_back(s[index]); backtrack(s, index + 1); // 撤销选择 track.pop_back(); } else { // s[index] 是字母 // 小写字母,做选择 track.push_back(tolower(s[index])); backtrack(s, index + 1); // 撤销选择 track.pop_back(); // 大写字母,做选择 track.push_back(toupper(s[index])); backtrack(s, index + 1); // 撤销选择 track.pop_back(); } } }; ``` ```go // by chatGPT (go) func letterCasePermutation(s string) []string { var track []byte var res []string var backtrack func(s string, index int) backtrack = func(s string, index int) { if index == len(s) { res = append(res, string(track)) return } if '0' <= s[index] && s[index] <= '9' { // s[index] 是数字 // 做选择 track = append(track, s[index]) backtrack(s, index + 1) // 撤销选择 track = track[:len(track)-1] } else { // s[index] 是字母 // 小写字母,做选择 track = append(track, byte(unicode.ToLower(rune(s[index])))) backtrack(s, index + 1) // 撤销选择 track = track[:len(track)-1] // 大写字母,做选择 track = append(track, byte(unicode.ToUpper(rune(s[index])))) backtrack(s, index + 1) // 撤销选择 track = track[:len(track)-1] } } backtrack(s, 0) return res } ``` ```java // by labuladong (java) class Solution { public List letterCasePermutation(String s) { backtrack(s, 0); return res; } StringBuilder track = new StringBuilder(); List res = new LinkedList<>(); void backtrack(String s, int index) { if (index == s.length()) { res.add(track.toString()); return; } if ('0' <= s.charAt(index) && s.charAt(index) <= '9') { // s[index] 是数字 // 做选择 track.append(s.charAt(index)); backtrack(s, index + 1); // 撤销选择 track.deleteCharAt(track.length() - 1); } else { // s[index] 是字母 // 小写字母,做选择 track.append(Character.toLowerCase(s.charAt(index))); backtrack(s, index + 1); // 撤销选择 track.deleteCharAt(track.length() - 1); // 大写字母,做选择 track.append(Character.toUpperCase(s.charAt(index))); backtrack(s, index + 1); // 撤销选择 track.deleteCharAt(track.length() - 1); } } } ``` ```javascript // by chatGPT (javascript) var Solution = function() { this.track = []; this.res = []; this.letterCasePermutation = function(s) { this.backtrack(s, 0); return this.res; } this.backtrack = function(s, index) { if (index == s.length) { this.res.push(this.track.join('')); return; } if ('0' <= s.charAt(index) && s.charAt(index) <= '9') { // s[index] 是数字 // 做选择 this.track.push(s.charAt(index)); this.backtrack(s, index + 1); // 撤销选择 this.track.pop(); } else { // s[index] 是字母 // 小写字母,做选择 this.track.push(s.charAt(index).toLowerCase()); this.backtrack(s, index + 1); // 撤销选择 this.track.pop(); // 大写字母,做选择 this.track.push(s.charAt(index).toUpperCase()); this.backtrack(s, index + 1); // 撤销选择 this.track.pop(); } } } ``` ```python # by chatGPT (python) class Solution: def __init__(self): self.track = "" self.res = [] def letterCasePermutation(self, s: str) -> List[str]: self.backtrack(s, 0) return self.res def backtrack(self, s, index): if index == len(s): self.res.append(self.track) return if '0' <= s[index] <= '9': # s[index] 是数字 # 做选择 self.track += s[index] self.backtrack(s, index + 1) # 撤销选择 self.track = self.track[:-1] else: # s[index] 是字母 # 小写字母,做选择 self.track += s[index].lower() self.backtrack(s, index + 1) # 撤销选择 self.track = self.track[:-1] # 大写字母,做选择 self.track += s[index].upper() self.backtrack(s, index + 1) # 撤销选择 self.track = self.track[:-1] ``` https://leetcode.cn/problems/letter-case-permutation 的多语言解法👆 https://leetcode.cn/problems/letter-combinations-of-a-phone-number 的多语言解法👇 ```java // by labuladong (java) class Solution { // 每个数字到字母的映射 String[] mapping = new String[] { "", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz" }; List res = new LinkedList<>(); StringBuilder sb = new StringBuilder(); public List letterCombinations(String digits) { if (digits.isEmpty()) { return res; } // 从 digits[0] 开始进行回溯 backtrack(digits, 0); return res; } // 回溯算法主函数 void backtrack(String digits, int start) { if (sb.length() == digits.length()) { // 到达回溯树底部 res.add(sb.toString()); return; } // 回溯算法框架 int digit = digits.charAt(start) - '0'; for (char c : mapping[digit].toCharArray()) { // 做选择 sb.append(c); // 递归下一层回溯树 backtrack(digits, start + 1); // 撤销选择 sb.deleteCharAt(sb.length() - 1); } } } ``` https://leetcode.cn/problems/letter-combinations-of-a-phone-number 的多语言解法👆 https://leetcode.cn/problems/letter-tile-possibilities 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int res = 0; vector used; int numTilePossibilities(string s) { vector nums(s.begin(), s.end()); // 先排序,让相同的元素靠在一起 sort(nums.begin(), nums.end()); used = vector(nums.size(), false); backtrack(nums); return res - 1; } void backtrack(vector& nums) { res++; for (int i = 0; i < nums.size(); i++) { if (used[i]) { continue; } // 新添加的剪枝逻辑,固定相同的元素在排列中的相对位置 if (i > 0 && nums[i] == nums[i - 1] && !used[i - 1]) { continue; } used[i] = true; backtrack(nums); used[i] = false; } } }; ``` ```go // by chatGPT (go) import ( "sort" "strings" ) func numTilePossibilities(s string) int { nums := strings.Split(s, "") // 先排序,让相同的元素靠在一起 sort.Strings(nums) used := make([]bool, len(nums)) res := 0 var backtrack func(nums []string) backtrack = func(nums []string) { res++ for i := 0; i < len(nums); i++ { if used[i] { continue } // 新添加的剪枝逻辑,固定相同的元素在排列中的相对位置 if i > 0 && nums[i] == nums[i-1] && !used[i-1] { continue } used[i] = true backtrack(nums) used[i] = false } } backtrack(nums) return res - 1 } ``` ```java // by labuladong (java) class Solution { int res = 0; boolean[] used; public int numTilePossibilities(String s) { char[] nums = s.toCharArray(); // 先排序,让相同的元素靠在一起 Arrays.sort(nums); used = new boolean[nums.length]; backtrack(nums); return res - 1; } void backtrack(char[] nums) { res++; for (int i = 0; i < nums.length; i++) { if (used[i]) { continue; } // 新添加的剪枝逻辑,固定相同的元素在排列中的相对位置 if (i > 0 && nums[i] == nums[i - 1] && !used[i - 1]) { continue; } used[i] = true; backtrack(nums); used[i] = false; } } } ``` ```javascript // by chatGPT (javascript) var numTilePossibilities = function(s) { var res = 0; var nums = s.split('').sort(); var used = new Array(nums.length).fill(false); function backtrack(nums) { res++; for (var i = 0; i < nums.length; i++) { if (used[i]) { continue; } // 新添加的剪枝逻辑,固定相同的元素在排列中的相对位置 if (i > 0 && nums[i] == nums[i - 1] && !used[i - 1]) { continue; } // 做选择 used[i] = true; backtrack(nums); // 撤销选择 used[i] = false; } } backtrack(nums); return res - 1; }; ``` ```python # by chatGPT (python) class Solution: def __init__(self): self.res = 0 self.used = [] def numTilePossibilities(self, s: str) -> int: nums = list(s) # 先排序,让相同的元素靠在一起 nums.sort() self.used = [False for _ in range(len(nums))] self.backtrack(nums) return self.res - 1 def backtrack(self, nums: list) -> None: self.res += 1 for i in range(len(nums)): if self.used[i]: continue # 新添加的剪枝逻辑,固定相同的元素在排列中的相对位置 if i > 0 and nums[i] == nums[i - 1] and not self.used[i - 1]: continue self.used[i] = True self.backtrack(nums) self.used[i] = False ``` https://leetcode.cn/problems/letter-tile-possibilities 的多语言解法👆 https://leetcode.cn/problems/lfu-cache 的多语言解法👇 ```cpp // by chatGPT (cpp) class LFUCache { // key 到 val 的映射,我们后文称为 KV 表 unordered_map keyToVal; // key 到 freq 的映射,我们后文称为 KF 表 unordered_map keyToFreq; // freq 到 key 列表的映射,我们后文称为 FK 表 unordered_map> freqToKeys; // 记录最小的频次 int minFreq; // 记录 LFU 缓存的最大容量 int cap; public: LFUCache(int capacity) { keyToVal.clear(); keyToFreq.clear(); freqToKeys.clear(); this->cap = capacity; this->minFreq = 0; } int get(int key) { if (!keyToVal.count(key)) { return -1; } // 增加 key 对应的 freq increaseFreq(key); return keyToVal[key]; } void put(int key, int val) { if (this->cap <= 0) return; /* 若 key 已存在,修改对应的 val 即可 */ if (keyToVal.count(key)) { keyToVal[key] = val; // key 对应的 freq 加一 increaseFreq(key); return; } /* key 不存在,需要插入 */ /* 容量已满的话需要淘汰一个 freq 最小的 key */ if (this->cap <= keyToVal.size()) { removeMinFreqKey(); } /* 插入 key 和 val,对应的 freq 为 1 */ // 插入 KV 表 keyToVal[key] = val; // 插入 KF 表 keyToFreq[key] = 1; // 插入 FK 表 freqToKeys[1].insert(key); // 插入新 key 后最小的 freq 肯定是 1 this->minFreq = 1; } private: void increaseFreq(int key) { int freq = keyToFreq[key]; /* 更新 KF 表 */ keyToFreq[key] = freq + 1; /* 更新 FK 表 */ // 将 key 从 freq 对应的列表中删除 freqToKeys[freq].erase(key); // 将 key 加入 freq + 1 对应的列表中 freqToKeys[freq + 1].insert(key); // 如果 freq 对应的列表空了,移除这个 freq if (freqToKeys[freq].empty()) { freqToKeys.erase(freq); // 如果这个 freq 恰好是 minFreq,更新 minFreq if (freq == this->minFreq) { this->minFreq++; } } } void removeMinFreqKey() { // freq 最小的 key 列表 unordered_set keyList = freqToKeys[this->minFreq]; // 其中最先被插入的那个 key 就是该被淘汰的 key int deletedKey = *keyList.begin(); /* 更新 FK 表 */ keyList.erase(deletedKey); if (keyList.empty()) { freqToKeys.erase(this->minFreq); // 问:这里需要更新 minFreq 的值吗? } /* 更新 KV 表 */ keyToVal.erase(deletedKey); /* 更新 KF 表 */ keyToFreq.erase(deletedKey); } }; ``` ```go // by chatGPT (go) type LFUCache struct { // key 到 val 的映射,我们后文称为 KV 表 keyToVal map[int]int // key 到 freq 的映射,我们后文称为 KF 表 keyToFreq map[int]int // freq 到 key 列表的映射,我们后文称为 FK 表 freqToKeys map[int]*linkedHashSet // 记录最小的频次 minFreq int // 记录 LFU 缓存的最大容量 cap int } func Constructor(capacity int) LFUCache { return LFUCache{ keyToVal: make(map[int]int), keyToFreq: make(map[int]int), freqToKeys: make(map[int]*linkedHashSet), cap: capacity, minFreq: 0, } } func (this *LFUCache) Get(key int) int { if _, ok := this.keyToVal[key]; !ok { return -1 } // 增加 key 对应的 freq this.increaseFreq(key) return this.keyToVal[key] } func (this *LFUCache) Put(key int, val int) { if this.cap <= 0 { return } /* 若 key 已存在,修改对应的 val 即可 */ if _, ok := this.keyToVal[key]; ok { this.keyToVal[key] = val // key 对应的 freq 加一 this.increaseFreq(key) return } /* key 不存在,需要插入 */ /* 容量已满的话需要淘汰一个 freq 最小的 key */ if this.cap <= len(this.keyToVal) { this.removeMinFreqKey() } /* 插入 key 和 val,对应的 freq 为 1 */ // 插入 KV 表 this.keyToVal[key] = val // 插入 KF 表 this.keyToFreq[key] = 1 // 插入 FK 表 this.freqToKeys[1].add(key) // 插入新 key 后最小的 freq 肯定是 1 this.minFreq = 1 } func (this *LFUCache) increaseFreq(key int) { freq := this.keyToFreq[key] /* 更新 KF 表 */ this.keyToFreq[key] = freq + 1 /* 更新 FK 表 */ // 将 key 从 freq 对应的列表中删除 this.freqToKeys[freq].remove(key) // 将 key 加入 freq + 1 对应的列表中 if this.freqToKeys[freq+1] == nil { this.freqToKeys[freq+1] = newLinkedHashSet() } this.freqToKeys[freq+1].add(key) // 如果 freq 对应的列表空了,移除这个 freq if this.freqToKeys[freq].size() == 0 { delete(this.freqToKeys, freq) // 如果这个 freq 恰好是 minFreq,更新 minFreq if freq == this.minFreq { this.minFreq++ } } } func (this *LFUCache) removeMinFreqKey() { // freq 最小的 key 列表 keyList := this.freqToKeys[this.minFreq] // 其中最先被插入的那个 key 就是该被淘汰的 key deletedKey := keyList.iterator().next() /* 更新 FK 表 */ keyList.remove(deletedKey) if keyList.size() == 0 { delete(this.freqToKeys, this.minFreq) // 问:这里需要更新 minFreq 的值吗? } /* 更新 KV 表 */ delete(this.keyToVal, deletedKey) /* 更新 KF 表 */ delete(this.keyToFreq, deletedKey) } // 封装一个链表 type linkedHashSet struct { m map[int]*node head *node tail *node } func newLinkedHashSet() *linkedHashSet { head := &node{} tail := &node{} head.next = tail tail.prev = head return &linkedHashSet{ m: make(map[int]*node), head: head, tail: tail, } } func (this *linkedHashSet) size() int { return len(this.m) } func (this *linkedHashSet) add(key int) { if _, ok := this.m[key]; ok { return } n := &node{key: key} last := this.tail.prev last.next = n n.prev = last n.next = this.tail this.tail.prev = n this.m[key] = n } func (this *linkedHashSet) remove(key int) { if n, ok := this.m[key]; ok { n.prev.next = n.next n.next.prev = n.prev delete(this.m, key) } } func (this *linkedHashSet) iterator() *keyIterator { return &keyIterator{this.head.next} } type node struct { key int prev *node next *node } type keyIterator struct { n *node } func (this *keyIterator) hasNext() bool { return this.n.next != nil } func (this *keyIterator) next() int { this.n = this.n.next return this.n.key } ``` ```java // by labuladong (java) class LFUCache { // key 到 val 的映射,我们后文称为 KV 表 HashMap keyToVal; // key 到 freq 的映射,我们后文称为 KF 表 HashMap keyToFreq; // freq 到 key 列表的映射,我们后文称为 FK 表 HashMap> freqToKeys; // 记录最小的频次 int minFreq; // 记录 LFU 缓存的最大容量 int cap; public LFUCache(int capacity) { keyToVal = new HashMap<>(); keyToFreq = new HashMap<>(); freqToKeys = new HashMap<>(); this.cap = capacity; this.minFreq = 0; } public int get(int key) { if (!keyToVal.containsKey(key)) { return -1; } // 增加 key 对应的 freq increaseFreq(key); return keyToVal.get(key); } public void put(int key, int val) { if (this.cap <= 0) return; /* 若 key 已存在,修改对应的 val 即可 */ if (keyToVal.containsKey(key)) { keyToVal.put(key, val); // key 对应的 freq 加一 increaseFreq(key); return; } /* key 不存在,需要插入 */ /* 容量已满的话需要淘汰一个 freq 最小的 key */ if (this.cap <= keyToVal.size()) { removeMinFreqKey(); } /* 插入 key 和 val,对应的 freq 为 1 */ // 插入 KV 表 keyToVal.put(key, val); // 插入 KF 表 keyToFreq.put(key, 1); // 插入 FK 表 freqToKeys.putIfAbsent(1, new LinkedHashSet<>()); freqToKeys.get(1).add(key); // 插入新 key 后最小的 freq 肯定是 1 this.minFreq = 1; } private void increaseFreq(int key) { int freq = keyToFreq.get(key); /* 更新 KF 表 */ keyToFreq.put(key, freq + 1); /* 更新 FK 表 */ // 将 key 从 freq 对应的列表中删除 freqToKeys.get(freq).remove(key); // 将 key 加入 freq + 1 对应的列表中 freqToKeys.putIfAbsent(freq + 1, new LinkedHashSet<>()); freqToKeys.get(freq + 1).add(key); // 如果 freq 对应的列表空了,移除这个 freq if (freqToKeys.get(freq).isEmpty()) { freqToKeys.remove(freq); // 如果这个 freq 恰好是 minFreq,更新 minFreq if (freq == this.minFreq) { this.minFreq++; } } } private void removeMinFreqKey() { // freq 最小的 key 列表 LinkedHashSet keyList = freqToKeys.get(this.minFreq); // 其中最先被插入的那个 key 就是该被淘汰的 key int deletedKey = keyList.iterator().next(); /* 更新 FK 表 */ keyList.remove(deletedKey); if (keyList.isEmpty()) { freqToKeys.remove(this.minFreq); // 问:这里需要更新 minFreq 的值吗? } /* 更新 KV 表 */ keyToVal.remove(deletedKey); /* 更新 KF 表 */ keyToFreq.remove(deletedKey); } } ``` ```javascript // by chatGPT (javascript) /** * @param {number} capacity */ var LFUCache = function(capacity) { // key 到 val 的映射,我们后文称为 KV 表 this.keyToVal = new Map(); // key 到 freq 的映射,我们后文称为 KF 表 this.keyToFreq = new Map(); // freq 到 key 列表的映射,我们后文称为 FK 表 this.freqToKeys = new Map(); // 记录最小的频次 this.minFreq = 0; // 记录 LFU 缓存的最大容量 this.cap = capacity; }; /** * @param {number} key * @return {number} */ LFUCache.prototype.get = function(key) { if (!this.keyToVal.has(key)) { return -1; } // 增加 key 对应的 freq this.increaseFreq(key); return this.keyToVal.get(key); }; /** * @param {number} key * @param {number} val * @return {void} */ LFUCache.prototype.put = function(key, val) { if (this.cap <= 0) return; /* 若 key 已存在,修改对应的 val 即可 */ if (this.keyToVal.has(key)) { this.keyToVal.set(key, val); // key 对应的 freq 加一 this.increaseFreq(key); return; } /* key 不存在,需要插入 */ /* 容量已满的话需要淘汰一个 freq 最小的 key */ if (this.cap <= this.keyToVal.size) { this.removeMinFreqKey(); } /* 插入 key 和 val,对应的 freq 为 1 */ // 插入 KV 表 this.keyToVal.set(key, val); // 插入 KF 表 this.keyToFreq.set(key, 1); // 插入 FK 表 if (!this.freqToKeys.has(1)) { this.freqToKeys.set(1, new Set()); } this.freqToKeys.get(1).add(key); // 插入新 key 后最小的 freq 肯定是 1 this.minFreq = 1; }; /** * @param {number} key * @return {void} */ LFUCache.prototype.increaseFreq = function(key) { let freq = this.keyToFreq.get(key); /* 更新 KF 表 */ this.keyToFreq.set(key, freq + 1); /* 更新 FK 表 */ // 将 key 从 freq 对应的列表中删除 this.freqToKeys.get(freq).delete(key); // 将 key 加入 freq + 1 对应的列表中 if (!this.freqToKeys.has(freq + 1)) { this.freqToKeys.set(freq + 1, new Set()); } this.freqToKeys.get(freq + 1).add(key); // 如果 freq 对应的列表空了,移除这个 freq if (this.freqToKeys.get(freq).size === 0) { this.freqToKeys.delete(freq); // 如果这个 freq 恰好是 minFreq,更新 minFreq if (freq === this.minFreq) { this.minFreq++; } } } /** * @return {void} */ LFUCache.prototype.removeMinFreqKey = function() { // freq 最小的 key 列表 let keyList = this.freqToKeys.get(this.minFreq); // 其中最先被插入的那个 key 就是该被淘汰的 key let deletedKey = keyList.values().next().value; /* 更新 FK 表 */ keyList.delete(deletedKey); if (keyList.size === 0) { this.freqToKeys.delete(this.minFreq); // 问:这里需要更新 minFreq 的值吗? } /* 更新 KV 表 */ this.keyToVal.delete(deletedKey); /* 更新 KF 表 */ this.keyToFreq.delete(deletedKey); }; ``` ```python # by chatGPT (python) class LFUCache: def __init__(self, capacity: int): # key 到 val 的映射,我们后文称为 KV 表 self.keyToVal = {} # key 到 freq 的映射,我们后文称为 KF 表 self.keyToFreq = {} # freq 到 key 列表的映射,我们后文称为 FK 表 self.freqToKeys = {} # 记录最小的频次 self.minFreq = 0 # 记录 LFU 缓存的最大容量 self.cap = capacity def get(self, key: int) -> int: if key not in self.keyToVal: return -1 # 增加 key 对应的 freq self.__increaseFreq(key) return self.keyToVal[key] def put(self, key: int, val: int) -> None: if self.cap <= 0: return # 若 key 已存在,修改对应的 val 即可 if key in self.keyToVal: self.keyToVal[key] = val # key 对应的 freq 加一 self.__increaseFreq(key) return # key 不存在,需要插入 # 容量已满的话需要淘汰一个 freq 最小的 key if self.cap <= len(self.keyToVal): self.__removeMinFreqKey() # 插入 key 和 val,对应的 freq 为 1 # 插入 KV 表 self.keyToVal[key] = val # 插入 KF 表 self.keyToFreq[key] = 1 # 插入 FK 表 self.freqToKeys.setdefault(1, set()) self.freqToKeys[1].add(key) # 插入新 key 后最小的 freq 肯定是 1 self.minFreq = 1 def __increaseFreq(self, key: int): freq = self.keyToFreq[key] # 更新 KF 表 self.keyToFreq[key] = freq + 1 # 更新 FK 表 # 将 key 从 freq 对应的列表中删除 self.freqToKeys[freq].remove(key) # 将 key 加入 freq + 1 对应的列表中 self.freqToKeys.setdefault(freq + 1, set()) self.freqToKeys[freq + 1].add(key) # 如果 freq 对应的列表空了,移除这个 freq if not self.freqToKeys[freq]: del self.freqToKeys[freq] # 如果这个 freq 恰好是 minFreq,更新 minFreq if freq == self.minFreq: self.minFreq += 1 def __removeMinFreqKey(self): # freq 最小的 key 列表 keyList = self.freqToKeys[self.minFreq] # 其中最先被插入的那个 key 就是该被淘汰的 key deletedKey = keyList.pop() # 更新 FK 表 if not keyList: del self.freqToKeys[self.minFreq] # 更新 KV 表 del self.keyToVal[deletedKey] # 更新 KF 表 del self.keyToFreq[deletedKey] ``` https://leetcode.cn/problems/lfu-cache 的多语言解法👆 https://leetcode.cn/problems/li-wu-de-zui-da-jie-zhi-lcof 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int maxValue(vector>& grid) { int m = grid.size(); int n = grid[0].size(); vector> dp(m, vector(n, 0)); /**** base case ****/ 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] = max( dp[i - 1][j], dp[i][j - 1] ) + grid[i][j]; } } return dp[m - 1][n - 1]; } }; ``` ```go // by chatGPT (go) func maxValue(grid [][]int) int { m := len(grid) n := len(grid[0]) dp := make([][]int, m) for i := range dp { dp[i] = make([]int, n) } /**** base case ****/ dp[0][0] = grid[0][0] for i := 1; i < m; i++ { dp[i][0] = dp[i-1][0] + grid[i][0] } for j := 1; j < n; j++ { dp[0][j] = dp[0][j-1] + grid[0][j] } // 状态转移 for i := 1; i < m; i++ { for j := 1; j < n; j++ { dp[i][j] = max( dp[i-1][j], dp[i][j-1], ) + grid[i][j] } } return dp[m-1][n-1] } func max(nums ...int) int { res := nums[0] for _, num := range nums[1:] { if num > res { res = num } } return res } ``` ```java // by labuladong (java) class Solution { public int maxValue(int[][] grid) { int m = grid.length; int n = grid[0].length; int[][] dp = new int[m][n]; /**** base case ****/ 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.max( dp[i - 1][j], dp[i][j - 1] ) + grid[i][j]; } } return dp[m - 1][n - 1]; } } ``` ```javascript // by chatGPT (javascript) var maxValue = function(grid) { let m = grid.length; let n = grid[0].length; let dp = new Array(m).fill().map(() => new Array(n).fill(0)); /**** base case ****/ dp[0][0] = grid[0][0]; for (let i = 1; i < m; i++) dp[i][0] = dp[i - 1][0] + grid[i][0]; for (let j = 1; j < n; j++) dp[0][j] = dp[0][j - 1] + grid[0][j]; // 状态转移 for (let i = 1; i < m; i++) { for (let j = 1; j < n; j++) { dp[i][j] = Math.max( dp[i - 1][j], dp[i][j - 1] ) + grid[i][j]; } } return dp[m - 1][n - 1]; }; ``` ```python # by chatGPT (python) class Solution: def maxValue(self, grid: List[List[int]]) -> int: m = len(grid) n = len(grid[0]) dp = [[0] * n for _ in range(m)] # base case dp[0][0] = grid[0][0] for i in range(1, m): dp[i][0] = dp[i - 1][0] + grid[i][0] for j in range(1, n): dp[0][j] = dp[0][j - 1] + grid[0][j] # 状态转移 for i in range(1, m): for j in range(1, n): dp[i][j] = max( dp[i - 1][j], dp[i][j - 1] ) + grid[i][j] return dp[m - 1][n - 1] ``` https://leetcode.cn/problems/li-wu-de-zui-da-jie-zhi-lcof 的多语言解法👆 https://leetcode.cn/problems/lian-xu-zi-shu-zu-de-zui-da-he-lcof 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int maxSubArray(vector& nums) { int n = nums.size(); if (n == 0) return 0; vector dp(n); // base case // 第一个元素前面没有子数组 dp[0] = nums[0]; // 状态转移方程 for (int i = 1; i < n; i++) { dp[i] = max(nums[i], nums[i] + dp[i - 1]); } // 得到 nums 的最大子数组 int res = INT_MIN; for (int i = 0; i < n; i++) { res = max(res, dp[i]); } return res; } }; ``` ```go // by chatGPT (go) func maxSubArray(nums []int) int { n := len(nums) if n == 0 { return 0 } dp := make([]int, n) // base case dp[0] = nums[0] // 状态转移方程 for i := 1; i < n; i++ { dp[i] = func() int { if nums[i] > nums[i]+dp[i-1] { return nums[i] } return nums[i] + dp[i-1] }() } // 得到 nums 的最大子数组 res := math.MinInt32 for i := 0; i < n; i++ { res = func() int { if res > dp[i] { return res } return dp[i] }() } return res } ``` ```java // by labuladong (java) 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]; // 状态转移方程 for (int i = 1; i < n; i++) { dp[i] = Math.max(nums[i], nums[i] + dp[i - 1]); } // 得到 nums 的最大子数组 int res = Integer.MIN_VALUE; for (int i = 0; i < n; i++) { res = Math.max(res, dp[i]); } return res; } } ``` ```javascript // by chatGPT (javascript) var maxSubArray = function(nums) { const n = nums.length; if (n == 0) return 0; const dp = new Array(n).fill(0); // base case // 第一个元素前面没有子数组 dp[0] = nums[0]; // 状态转移方程 for (let i = 1; i < n; i++) { dp[i] = Math.max(nums[i], nums[i] + dp[i - 1]); } // 得到 nums 的最大子数组 let res = Number.MIN_SAFE_INTEGER; for (let i = 0; i < n; i++) { res = Math.max(res, dp[i]); } return res; }; ``` ```python # by chatGPT (python) class Solution: def maxSubArray(self, nums: List[int]) -> int: n = len(nums) if n == 0: return 0 dp = [0] * n # base case # 第一个元素前面没有子数组 dp[0] = nums[0] # 状态转移方程 for i in range(1, n): dp[i] = max(nums[i], nums[i] + dp[i - 1]) # 得到 nums 的最大子数组 res = float('-inf') for i in range(n): res = max(res, dp[i]) return res ``` https://leetcode.cn/problems/lian-xu-zi-shu-zu-de-zui-da-he-lcof 的多语言解法👆 https://leetcode.cn/problems/liang-ge-lian-biao-de-di-yi-ge-gong-gong-jie-dian-lcof 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: ListNode* getIntersectionNode(ListNode* headA, ListNode* headB) { // p1 指向 A 链表头结点,p2 指向 B 链表头结点 ListNode* p1 = headA; ListNode* p2 = headB; while (p1 != p2) { // p1 走一步,如果走到 A 链表末尾,转到 B 链表 if (p1 == nullptr) { p1 = headB; } else { p1 = p1->next; } // p2 走一步,如果走到 B 链表末尾,转到 A 链表 if (p2 == nullptr) { p2 = headA; } else { p2 = p2->next; } } return p1; } }; ``` ```go // by chatGPT (go) func getIntersectionNode(headA, headB *ListNode) *ListNode { // p1 指向 A 链表头结点,p2 指向 B 链表头结点 p1, p2 := headA, headB for p1 != p2 { // p1 走一步,如果走到 A 链表末尾,转到 B 链表 if p1 == nil { p1 = headB } else { p1 = p1.Next } // p2 走一步,如果走到 B 链表末尾,转到 A 链表 if p2 == nil { p2 = headA } else { p2 = p2.Next } } return p1 } ``` ```java // by labuladong (java) public class Solution { public ListNode getIntersectionNode(ListNode headA, ListNode headB) { // p1 指向 A 链表头结点,p2 指向 B 链表头结点 ListNode p1 = headA, p2 = headB; while (p1 != p2) { // p1 走一步,如果走到 A 链表末尾,转到 B 链表 if (p1 == null) p1 = headB; else p1 = p1.next; // p2 走一步,如果走到 B 链表末尾,转到 A 链表 if (p2 == null) p2 = headA; else p2 = p2.next; } return p1; } } ``` ```javascript // by chatGPT (javascript) var getIntersectionNode = function(headA, headB) { // p1 指向 A 链表头结点,p2 指向 B 链表头结点 let p1 = headA, p2 = headB; while (p1 != p2) { // p1 走一步,如果走到 A 链表末尾,转到 B 链表 if (p1 == null) { p1 = headB; } else { p1 = p1.next; } // p2 走一步,如果走到 B 链表末尾,转到 A 链表 if (p2 == null) { p2 = headA; } else { p2 = p2.next; } } return p1; }; ``` ```python # by chatGPT (python) class Solution: def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode: # p1 指向 A 链表头结点,p2 指向 B 链表头结点 p1, p2 = headA, headB while p1 != p2: # p1 走一步,如果走到 A 链表末尾,转到 B 链表 if p1 == None: p1 = headB else: p1 = p1.next # p2 走一步,如果走到 B 链表末尾,转到 A 链表 if p2 == None: p2 = headA else: p2 = p2.next return p1 ``` https://leetcode.cn/problems/liang-ge-lian-biao-de-di-yi-ge-gong-gong-jie-dian-lcof 的多语言解法👆 https://leetcode.cn/problems/linked-list-cycle 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: bool hasCycle(ListNode *head) { // 快慢指针初始化指向 head ListNode *slow = head, *fast = head; // 快指针走到末尾时停止 while (fast != nullptr && fast->next != nullptr) { // 慢指针走一步,快指针走两步 slow = slow->next; fast = fast->next->next; // 快慢指针相遇,说明含有环 if (slow == fast) { return true; } } // 不包含环 return false; } }; ``` ```go // by chatGPT (go) func hasCycle(head *ListNode) bool { // 快慢指针初始化指向 head slow, fast := head, head // 快指针走到末尾时停止 for fast != nil && fast.Next != nil { // 慢指针走一步,快指针走两步 slow = slow.Next fast = fast.Next.Next // 快慢指针相遇,说明含有环 if slow == fast { return true } } // 不包含环 return false } ``` ```java // by labuladong (java) public 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; } } ``` ```javascript // by chatGPT (javascript) /** * @param {ListNode} head * @return {boolean} */ var hasCycle = function(head) { // 快慢指针初始化指向 head let slow = head, fast = head; // 快指针走到末尾时停止 while (fast !== null && fast.next !== null) { // 慢指针走一步,快指针走两步 slow = slow.next; fast = fast.next.next; // 快慢指针相遇,说明含有环 if (slow === fast) { return true; } } // 不包含环 return false; }; ``` ```python # by chatGPT (python) class Solution: def hasCycle(self, head: ListNode) -> bool: # 快慢指针初始化指向 head slow, fast = head, head # 快指针走到末尾时停止 while fast and fast.next: # 慢指针走一步,快指针走两步 slow = slow.next fast = fast.next.next # 快慢指针相遇,说明含有环 if slow == fast: return True # 不包含环 return False ``` https://leetcode.cn/problems/linked-list-cycle 的多语言解法👆 https://leetcode.cn/problems/linked-list-cycle-ii 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: ListNode *detectCycle(ListNode *head) { ListNode *fast, *slow; fast = slow = head; while (fast != nullptr && fast->next != nullptr) { fast = fast->next->next; slow = slow->next; if (fast == slow) break; /** ![](../pictures/双指针/3.jpeg) */ } // 上面的代码类似 hasCycle 函数 if (fast == nullptr || fast->next == nullptr) { // fast 遇到空指针说明没有环 return nullptr; } // 重新指向头结点 slow = head; /** ![](../pictures/双指针/2.jpeg) */ // 快慢指针同步前进,相交点就是环起点 while (slow != fast) { fast = fast->next; slow = slow->next; } return slow; } }; ``` ```go // by chatGPT (go) func detectCycle(head *ListNode) *ListNode { fast, slow := head, head for fast != nil && fast.Next != nil { fast = fast.Next.Next slow = slow.Next if fast == slow { break /** ![](../pictures/双指针/3.jpeg) */ } } if fast == nil || fast.Next == nil { return nil } slow = head /** ![](../pictures/双指针/2.jpeg) */ for slow != fast { slow = slow.Next fast = fast.Next } return slow } ``` ```java // by labuladong (java) public 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; /** ![](../pictures/双指针/3.jpeg) */ } // 上面的代码类似 hasCycle 函数 if (fast == null || fast.next == null) { // fast 遇到空指针说明没有环 return null; } // 重新指向头结点 slow = head; /** ![](../pictures/双指针/2.jpeg) */ // 快慢指针同步前进,相交点就是环起点 while (slow != fast) { fast = fast.next; slow = slow.next; } return slow; } } ``` ```javascript // by chatGPT (javascript) /** * @param {ListNode} head * @return {ListNode} */ var detectCycle = function(head) { let fast = head, slow = head; while (fast !== null && fast.next !== null) { fast = fast.next.next; slow = slow.next; if (fast === slow) { break; /** ![](../pictures/双指针/3.jpeg) */ } } // 上面的代码类似 hasCycle 函数 if (fast === null || fast.next === null) { // fast 遇到空指针说明没有环 return null; } // 重新指向头结点 slow = head; /** ![](../pictures/双指针/2.jpeg) */ // 快慢指针同步前进,相交点就是环起点 while (slow !== fast) { fast = fast.next; slow = slow.next; } return slow; }; ``` ```python # by chatGPT (python) class Solution: def detectCycle(self, head: ListNode) -> ListNode: fast = slow = head while fast and fast.next: fast = fast.next.next slow = slow.next if fast == slow: break """ ![](../pictures/双指针/3.jpeg) """ if not fast or not fast.next: return None slow = head """ ![](../pictures/双指针/2.jpeg) """ while slow != fast: slow = slow.next fast = fast.next return slow ``` https://leetcode.cn/problems/linked-list-cycle-ii 的多语言解法👆 https://leetcode.cn/problems/linked-list-random-node 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { private: ListNode* head; std::random_device rd; public: Solution(ListNode* head) { this->head = head; } /* 返回链表中一个随机节点的值 */ int getRandom() { int i = 0, res = 0; ListNode* p = head; // while 循环遍历链表 while (p != nullptr) { i++; // 生成一个 [0, i) 之间的整数 // 这个整数等于 0 的概率就是 1/i if (0 == rd() % i) { res = p->val; } p = p->next; } return res; } }; ``` ```go // by chatGPT (go) type Solution struct { head *ListNode r *rand.Rand } /** @param head The linked list's head. Note that the head is guaranteed to be not null, so it contains at least one node. */ func Constructor(head *ListNode) Solution { return Solution{ head: head, r: rand.New(rand.NewSource(time.Now().UnixNano())), } } /** Returns a random node's value. */ func (this *Solution) GetRandom() int { i := 0 res := 0 p := this.head // while 循环遍历链表 for p != nil { i++ // 生成一个 [0, i) 之间的整数 // 这个整数等于 0 的概率就是 1/i if 0 == this.r.Intn(i) { res = p.Val } p = p.Next } return res } ``` ```java // by labuladong (java) class Solution { ListNode head; Random r = new Random(); public Solution(ListNode head) { this.head = head; } /* 返回链表中一个随机节点的值 */ int getRandom() { int i = 0, res = 0; ListNode p = head; // while 循环遍历链表 while (p != null) { i++; // 生成一个 [0, i) 之间的整数 // 这个整数等于 0 的概率就是 1/i if (0 == r.nextInt(i)) { res = p.val; } p = p.next; } return res; } } ``` ```javascript // by chatGPT (javascript) /** * @param {ListNode} head * @return {number} */ var Solution = function(head) { this.head = head; this.r = new Random(); }; /** * Returns a random node's value. * @return {number} */ Solution.prototype.getRandom = function() { let i = 0; let res = 0; let p = this.head; // while 循环遍历链表 while (p !== null) { i++; // 生成一个 [0, i) 之间的整数 // 这个整数等于 0 的概率就是 1/i if (0 === this.r.nextInt(i)) { res = p.val; } p = p.next; } return res; }; ``` ```python # by chatGPT (python) class Solution: def __init__(self, head: ListNode): self.head = head self.r = random.Random() def getRandom(self) -> int: i, res = 0, 0 p = self.head # while 循环遍历链表 while p: i += 1 # 生成一个 [0, i) 之间的整数 # 这个整数等于 0 的概率就是 1/i if 0 == self.r.randint(0, i-1): res = p.val p = p.next return res ``` https://leetcode.cn/problems/linked-list-random-node 的多语言解法👆 https://leetcode.cn/problems/longest-common-prefix 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: string longestCommonPrefix(vector& strs) { int m = strs.size(); int n = strs[0].length(); for (int col = 0; col < n; col++) { for (int row = 1; row < m; row++) { string thisStr = strs[row], prevStr = strs[row - 1]; // 判断每个字符串的 col 索引是否都相同 if (col >= thisStr.length() || col >= prevStr.length() || thisStr.at(col) != prevStr.at(col)) { // 发现不匹配的字符,只有 strs[row][0..col-1] 是公共前缀 return strs[row].substr(0, col); } } } return strs[0]; } }; ``` ```go // by chatGPT (go) func longestCommonPrefix(strs []string) string { m := len(strs) // 以第一行的列数为基准 n := len(strs[0]) for col := 0; col < n; col++ { for row := 1; row < m; row++ { thisStr, prevStr := strs[row], strs[row-1] // 判断每个字符串的 col 索引是否都相同 if col >= len(thisStr) || col >= len(prevStr) || thisStr[col] != prevStr[col] { // 发现不匹配的字符,只有 strs[row][0..col-1] 是公共前缀 return strs[row][:col] } } } return strs[0] } ``` ```java // by labuladong (java) 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 thisStr = strs[row], prevStr = strs[row - 1]; // 判断每个字符串的 col 索引是否都相同 if (col >= thisStr.length() || col >= prevStr.length() || thisStr.charAt(col) != prevStr.charAt(col)) { // 发现不匹配的字符,只有 strs[row][0..col-1] 是公共前缀 return strs[row].substring(0, col); } } } return strs[0]; } } ``` ```javascript // by chatGPT (javascript) var longestCommonPrefix = function(strs) { let m = strs.length; // 以第一行的列数为基准 let n = strs[0].length; for (let col = 0; col < n; col++) { for (let row = 1; row < m; row++) { let thisStr = strs[row], prevStr = strs[row - 1]; // 判断每个字符串的 col 索引是否都相同 if (col >= thisStr.length || col >= prevStr.length || thisStr.charAt(col) != prevStr.charAt(col)) { // 发现不匹配的字符,只有 strs[row][0..col-1] 是公共前缀 return strs[row].substring(0, col); } } } return strs[0]; }; ``` ```python # by chatGPT (python) class Solution: def longestCommonPrefix(self, strs: List[str]) -> str: m = len(strs) # 以第一行的列数为基准 n = len(strs[0]) for col in range(n): for row in range(1, m): thisStr, prevStr = strs[row], strs[row - 1] # 判断每个字符串的 col 索引是否都相同 if col >= len(thisStr) or col >= len(prevStr) or thisStr[col] != prevStr[col]: # 发现不匹配的字符,只有 strs[row][0..col-1] 是公共前缀 return strs[row][:col] return strs[0] ``` https://leetcode.cn/problems/longest-common-prefix 的多语言解法👆 https://leetcode.cn/problems/longest-common-subsequence 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int longestCommonSubsequence(string s1, string s2) { int m = s1.length(), n = s2.length(); // 定义:s1[0..i-1] 和 s2[0..j-1] 的 lcs 长度为 dp[i][j] int dp[m + 1][n + 1]; // 目标:s1[0..m-1] 和 s2[0..n-1] 的 lcs 长度,即 dp[m][n] // base case: dp[0][..] = dp[..][0] = 0 memset(dp, 0, sizeof(dp)); for (int i = 1; i <= m; i++) { for (int j = 1; j <= n; j++) { // 现在 i 和 j 从 1 开始,所以要减一 if (s1[i - 1] == s2[j - 1]) { // s1[i-1] 和 s2[j-1] 必然在 lcs 中 dp[i][j] = 1 + dp[i - 1][j - 1]; } else { // s1[i-1] 和 s2[j-1] 至少有一个不在 lcs 中 dp[i][j] = max(dp[i][j - 1], dp[i - 1][j]); } } } return dp[m][n]; } }; ``` ```go // by chatGPT (go) func longestCommonSubsequence(s1 string, s2 string) int { m, n := len(s1), len(s2) //定义:s1[0..i-1] 和 s2[0..j-1] 的 lcs 长度为 dp[i][j] dp := make([][]int, m+1) for i := range dp { dp[i] = make([]int, n+1) } //目标:s1[0..m-1] 和 s2[0..n-1] 的 lcs 长度,即 dp[m][n] //base case: dp[0][..] = dp[..][0] = 0 for i := 1; i <= m; i++ { for j := 1; j <= n; j++ { //现在 i 和 j 从 1 开始,所以要减一 if s1[i-1] == s2[j-1] { //s1[i-1] 和 s2[j-1] 必然在 lcs 中 dp[i][j] = 1 + dp[i-1][j-1] } else { //s1[i-1] 和 s2[j-1] 至少有一个不在 lcs 中 dp[i][j] = max(dp[i][j-1], dp[i-1][j]) } } } return dp[m][n] } func max(a, b int) int { if a > b { return a } return b } ``` ```java // by labuladong (java) class Solution { public int longestCommonSubsequence(String s1, String s2) { int m = s1.length(), n = s2.length(); // 定义:s1[0..i-1] 和 s2[0..j-1] 的 lcs 长度为 dp[i][j] int[][] dp = new int[m + 1][n + 1]; // 目标:s1[0..m-1] 和 s2[0..n-1] 的 lcs 长度,即 dp[m][n] // base case: dp[0][..] = dp[..][0] = 0 for (int i = 1; i <= m; i++) { for (int j = 1; j <= n; j++) { // 现在 i 和 j 从 1 开始,所以要减一 if (s1.charAt(i - 1) == s2.charAt(j - 1)) { // s1[i-1] 和 s2[j-1] 必然在 lcs 中 dp[i][j] = 1 + dp[i - 1][j - 1]; } else { // s1[i-1] 和 s2[j-1] 至少有一个不在 lcs 中 dp[i][j] = Math.max(dp[i][j - 1], dp[i - 1][j]); } } } return dp[m][n]; } } ``` ```javascript // by chatGPT (javascript) var longestCommonSubsequence = function(s1, s2) { const m = s1.length, n = s2.length; // 定义:s1[0..i-1] 和 s2[0..j-1] 的 lcs 长度为 dp[i][j] const dp = new Array(m + 1).fill(0).map(() => new Array(n + 1).fill(0)); // 目标:s1[0..m-1] 和 s2[0..n-1] 的 lcs 长度,即 dp[m][n] // base case: dp[0][..] = dp[..][0] = 0 for (let i = 1; i <= m; i++) { for (let j = 1; j <= n; j++) { // 现在 i 和 j 从 1 开始,所以要减一 if (s1.charAt(i - 1) === s2.charAt(j - 1)) { // s1[i-1] 和 s2[j-1] 必然在 lcs 中 dp[i][j] = 1 + dp[i - 1][j - 1]; } else { // s1[i-1] 和 s2[j-1] 至少有一个不在 lcs 中 dp[i][j] = Math.max(dp[i][j - 1], dp[i - 1][j]); } } } return dp[m][n]; }; ``` ```python # by chatGPT (python) class Solution: def longestCommonSubsequence(self, s1: str, s2: str) -> int: m, n = len(s1), len(s2) # 定义:s1[0..i-1] 和 s2[0..j-1] 的 lcs 长度为 dp[i][j] dp = [[0] * (n + 1) for _ in range(m + 1)] # 目标:s1[0..m-1] 和 s2[0..n-1] 的 lcs 长度,即 dp[m][n] # base case: dp[0][..] = dp[..][0] = 0 for i in range(1, m + 1): for j in range(1, n + 1): # 现在 i 和 j 从 1 开始,所以要减一 if s1[i - 1] == s2[j - 1]: # s1[i-1] 和 s2[j-1] 必然在 lcs 中 dp[i][j] = 1 + dp[i - 1][j - 1] else: # s1[i-1] 和 s2[j-1] 至少有一个不在 lcs 中 dp[i][j] = max(dp[i][j - 1], dp[i - 1][j]) return dp[m][n] ``` https://leetcode.cn/problems/longest-common-subsequence 的多语言解法👆 https://leetcode.cn/problems/longest-consecutive-sequence 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int longestConsecutive(vector& nums) { // 转化成哈希集合,方便快速查找是否存在某个元素 unordered_set set; for (int num : nums) { set.insert(num); } int res = 0; for (int num : set) { if (set.count(num - 1)) { // num 不是连续子序列的第一个,跳过 continue; } // num 是连续子序列的第一个,开始向上计算连续子序列的长度 int curNum = num; int curLen = 1; while (set.count(curNum + 1)) { curNum += 1; curLen += 1; } // 更新最长连续序列的长度 res = max(res, curLen); } return res; } }; ``` ```go // by chatGPT (go) func longestConsecutive(nums []int) int { // 转化成哈希集合,方便快速查找是否存在某个元素 set := make(map[int]bool) for _, num := range nums { set[num] = true } res := 0 for num := range set { if set[num-1] { // num 不是连续子序列的第一个,跳过 continue } // num 是连续子序列的第一个,开始向上计算连续子序列的长度 curNum := num curLen := 1 for set[curNum+1] { curNum += 1 curLen += 1 } // 更新最长连续序列的长度 res = max(res, curLen) } return res } func max(a, b int) int { if a > b { return a } return b } ``` ```java // by labuladong (java) class Solution { public int longestConsecutive(int[] nums) { // 转化成哈希集合,方便快速查找是否存在某个元素 HashSet set = new HashSet(); for (int num : nums) { set.add(num); } int res = 0; for (int num : set) { if (set.contains(num - 1)) { // num 不是连续子序列的第一个,跳过 continue; } // num 是连续子序列的第一个,开始向上计算连续子序列的长度 int curNum = num; int curLen = 1; while (set.contains(curNum + 1)) { curNum += 1; curLen += 1; } // 更新最长连续序列的长度 res = Math.max(res, curLen); } return res; } } ``` ```javascript // by chatGPT (javascript) var longestConsecutive = function(nums) { // 转化成哈希集合,方便快速查找是否存在某个元素 let set = new Set(nums); let res = 0; for (let num of set) { if (set.has(num - 1)) { // num 不是连续子序列的第一个,跳过 continue; } // num 是连续子序列的第一个,开始向上计算连续子序列的长度 let curNum = num; let curLen = 1; while (set.has(curNum + 1)) { curNum += 1; curLen += 1; } // 更新最长连续序列的长度 res = Math.max(res, curLen); } return res; }; ``` ```python # by chatGPT (python) class Solution: def longestConsecutive(self, nums: List[int]) -> int: # 转化成哈希集合,方便快速查找是否存在某个元素 set_num = set(nums) res = 0 for num in set_num: if num - 1 in set_num: # num 不是连续子序列的第一个,跳过 continue # num 是连续子序列的第一个,开始向上计算连续子序列的长度 cur_num = num cur_len = 1 while cur_num + 1 in set_num: cur_num += 1 cur_len += 1 # 更新最长连续序列的长度 res = max(res, cur_len) return res ``` https://leetcode.cn/problems/longest-consecutive-sequence 的多语言解法👆 https://leetcode.cn/problems/longest-increasing-subsequence 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int lengthOfLIS(vector& nums) { // dp[i] 表示以 nums[i] 这个数结尾的最长递增子序列的长度 vector dp(nums.size(), 1); /** ![](../pictures/最长递增子序列/5.jpeg) */ // base case:dp 数组全都初始化为 1 for (int i = 0; i < nums.size(); i++) { for (int j = 0; j < i; j++) { if (nums[i] > nums[j]) dp[i] = max(dp[i], dp[j] + 1); } } int res = 0; for (int i = 0; i < dp.size(); i++) { res = max(res, dp[i]); } return res; } }; ``` ```go // by chatGPT (go) func lengthOfLIS(nums []int) int { // dp[i] 表示以 nums[i] 这个数结尾的最长递增子序列的长度 dp := make([]int, len(nums)) // base case:dp 数组全都初始化为 1 for i := 0; i < len(dp); i++ { dp[i] = 1 } for i := 0; i < len(nums); i++ { for j := 0; j < i; j++ { if nums[i] > nums[j] { dp[i] = max(dp[i], dp[j]+1) } } } res := 0 for i := 0; i < len(dp); i++ { res = max(res, dp[i]) } return res } func max(a, b int) int { if a > b { return a } return b } ``` ```java // by labuladong (java) class Solution { public int lengthOfLIS(int[] nums) { // dp[i] 表示以 nums[i] 这个数结尾的最长递增子序列的长度 int[] dp = new int[nums.length]; /** ![](../pictures/最长递增子序列/5.jpeg) */ // base case:dp 数组全都初始化为 1 Arrays.fill(dp, 1); for (int i = 0; i < nums.length; i++) { for (int j = 0; j < i; j++) { if (nums[i] > nums[j]) dp[i] = Math.max(dp[i], dp[j] + 1); } } int res = 0; for (int i = 0; i < dp.length; i++) { res = Math.max(res, dp[i]); } return res; } } ``` ```javascript // by chatGPT (javascript) var lengthOfLIS = function(nums) { // dp[i] 表示以 nums[i] 这个数结尾的最长递增子序列的长度 let dp = new Array(nums.length).fill(1); /** ![](../pictures/最长递增子序列/5.jpeg) */ // base case:dp 数组全都初始化为 1 for (let i = 0; i < nums.length; i++) { for (let j = 0; j < i; j++) { if (nums[i] > nums[j]) dp[i] = Math.max(dp[i], dp[j] + 1); } } let res = 0; for (let i = 0; i < dp.length; i++) { res = Math.max(res, dp[i]); } return res; }; ``` ```python # by chatGPT (python) class Solution: def lengthOfLIS(self, nums: List[int]) -> int: # dp[i] 表示以 nums[i] 这个数结尾的最长递增子序列的长度 dp = [1] * len(nums) for i in range(len(nums)): for j in range(i): if nums[i] > nums[j]: dp[i] = max(dp[i], dp[j] + 1) return max(dp) ``` https://leetcode.cn/problems/longest-increasing-subsequence 的多语言解法👆 https://leetcode.cn/problems/longest-palindromic-subsequence 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int longestPalindromeSubseq(string s) { int n = s.length(); // dp 数组全部初始化为 0 vector> dp(n, vector(n, 0)); // base case for (int i = 0; i < n; i++) { dp[i][i] = 1; } // 反着遍历保证正确的状态转移 for (int i = n - 1; i >= 0; i--) { for (int j = i + 1; j < n; j++) { // 状态转移方程 if (s[i] == s[j]) { dp[i][j] = dp[i + 1][j - 1] + 2; } else { dp[i][j] = max(dp[i + 1][j], dp[i][j - 1]); } } } // 整个 s 的最长回文子串长度 return dp[0][n - 1]; } }; ``` ```go // by chatGPT (go) func longestPalindromeSubseq(s string) int { n := len(s) // dp 数组全部初始化为 0 dp := make([][]int, n) for i := range dp { dp[i] = make([]int, n) } // base case for i := 0; i < n; i++ { dp[i][i] = 1 } // 反着遍历保证正确的状态转移 for i := n - 1; i >= 0; i-- { for j := i + 1; j < n; j++ { // 状态转移方程 if s[i] == s[j] { dp[i][j] = dp[i+1][j-1] + 2 } else { dp[i][j] = max(dp[i+1][j], dp[i][j-1]) } } } // 整个 s 的最长回文子串长度 return dp[0][n-1] } func max(x, y int) int { if x > y { return x } return y } ``` ```java // by labuladong (java) class Solution { public int longestPalindromeSubseq(String s) { int n = s.length(); // dp 数组全部初始化为 0 int[][] dp = new int[n][n]; // base case for (int i = 0; i < n; i++) { dp[i][i] = 1; } // 反着遍历保证正确的状态转移 for (int i = n - 1; i >= 0; i--) { 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]); } } } // 整个 s 的最长回文子串长度 return dp[0][n - 1]; } } ``` ```javascript // by chatGPT (javascript) var longestPalindromeSubseq = function(s) { const n = s.length; // dp 数组全部初始化为 0 const dp = new Array(n).fill(0).map(() => new Array(n).fill(0)); // base case for (let i = 0; i < n; i++) { dp[i][i] = 1; } // 反着遍历保证正确的状态转移 for (let i = n - 1; i >= 0; i--) { for (let 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]); } } } // 整个 s 的最长回文子串长度 return dp[0][n - 1]; }; ``` ```python # by chatGPT (python) class Solution: def longestPalindromeSubseq(self, s: str) -> int: n = len(s) # dp 数组全部初始化为 0 dp = [[0 for _ in range(n)] for _ in range(n)] # base case for i in range(n): dp[i][i] = 1 # 反着遍历保证正确的状态转移 for i in range(n - 1, -1, -1): for j in range(i + 1, n): # 状态转移方程 if s[i] == s[j]: dp[i][j] = dp[i + 1][j - 1] + 2 else: dp[i][j] = max(dp[i + 1][j], dp[i][j - 1]) # 整个 s 的最长回文子串长度 return dp[0][n - 1] ``` https://leetcode.cn/problems/longest-palindromic-subsequence 的多语言解法👆 https://leetcode.cn/problems/longest-palindromic-substring 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: string longestPalindrome(string s) { string res = ""; for (int i = 0; i < s.length(); i++) { // 以 s[i] 为中心的最长回文子串 string s1 = palindrome(s, i, i); // 以 s[i] 和 s[i+1] 为中心的最长回文子串 string s2 = palindrome(s, i, i + 1); // res = longest(res, s1, s2) res = res.length() > s1.length() ? res : s1; res = res.length() > s2.length() ? res : s2; } return res; } string palindrome(string s, int l, int r) { // 防止索引越界 while (l >= 0 && r < s.length() && s[l] == s[r]) { // 向两边展开 l--; r++; } // 返回以 s[l] 和 s[r] 为中心的最长回文串 return s.substr(l + 1, r - l - 1); } }; ``` ```go // by chatGPT (go) func longestPalindrome(s string) string { res := "" for i := 0; i < len(s); i++ { // 以 s[i] 为中心的最长回文子串 s1 := palindrome(s, i, i) // 以 s[i] 和 s[i+1] 为中心的最长回文子串 s2 := palindrome(s, i, i+1) // res = longest(res, s1, s2) if len(s1) > len(res) { res = s1 } if len(s2) > len(res) { res = s2 } } return res } func palindrome(s string, l, r int) string { // 防止索引越界 for l >= 0 && r < len(s) && s[l] == s[r] { // 向两边展开 l-- r++ } // 返回以 s[l] 和 s[r] 为中心的最长回文串 return s[l+1 : r] } ``` ```java // by labuladong (java) class Solution { public String longestPalindrome(String s) { String res = ""; for (int i = 0; i < s.length(); i++) { // 以 s[i] 为中心的最长回文子串 String s1 = palindrome(s, i, i); // 以 s[i] 和 s[i+1] 为中心的最长回文子串 String s2 = palindrome(s, i, i + 1); // res = longest(res, s1, s2) res = res.length() > s1.length() ? res : s1; res = res.length() > s2.length() ? res : s2; } return res; } String palindrome(String s, int l, int r) { // 防止索引越界 while (l >= 0 && r < s.length() && s.charAt(l) == s.charAt(r)) { // 向两边展开 l--; r++; } // 返回以 s[l] 和 s[r] 为中心的最长回文串 return s.substring(l + 1, r); } } ``` ```javascript // by chatGPT (javascript) var longestPalindrome = function(s) { let res = ""; for (let i = 0; i < s.length; i++) { // 以 s[i] 为中心的最长回文子串 let s1 = palindrome(s, i, i); // 以 s[i] 和 s[i+1] 为中心的最长回文子串 let s2 = palindrome(s, i, i + 1); // res = longest(res, s1, s2) res = res.length > s1.length ? res : s1; res = res.length > s2.length ? res : s2; } return res; function palindrome(s, l, r) { // 防止索引越界 while (l >= 0 && r < s.length && s.charAt(l) == s.charAt(r)) { // 向两边展开 l--; r++; } // 返回以 s[l] 和 s[r] 为中心的最长回文串 return s.substring(l + 1, r); } }; ``` ```python # by chatGPT (python) class Solution: def longestPalindrome(self, s: str) -> str: res = "" for i in range(len(s)): # 以 s[i] 为中心的最长回文子串 s1 = self.palindrome(s, i, i) # 以 s[i] 和 s[i+1] 为中心的最长回文子串 s2 = self.palindrome(s, i, i + 1) # res = longest(res, s1, s2) res = res if len(res) > len(s1) else s1 res = res if len(res) > len(s2) else s2 return res def palindrome(self, s: str, l: int, r: int) -> str: # 防止索引越界 while l >= 0 and r < len(s) and s[l] == s[r]: # 向两边展开 l -= 1 r += 1 # 返回以 s[l] 和 s[r] 为中心的最长回文串 return s[l+1:r] ``` https://leetcode.cn/problems/longest-palindromic-substring 的多语言解法👆 https://leetcode.cn/problems/longest-substring-without-repeating-characters 的多语言解法👇 ```cpp // by labuladong (cpp) class Solution { public: int lengthOfLongestSubstring(string s) { unordered_map window; int left = 0, right = 0; int res = 0; // 记录结果 while (right < s.size()) { char c = s[right]; right++; // 进行窗口内数据的一系列更新 window[c]++; // 判断左侧窗口是否要收缩 while (window[c] > 1) { char d = s[left]; left++; // 进行窗口内数据的一系列更新 window[d]--; } // 在这里更新答案 res = max(res, right - left); } return res; } }; ``` ```go // by chatGPT (go) func lengthOfLongestSubstring(s string) int { window := make(map[byte]int) left, right := 0, 0 res := 0 // 记录结果 for right < len(s) { c := s[right] right++ // 进行窗口内数据的一系列更新 window[c]++ // 判断左侧窗口是否要收缩 for window[c] > 1 { d := s[left] left++ // 进行窗口内数据的一系列更新 window[d]-- } // 在这里更新答案 res = max(res, right - left) } return res } func max(a, b int) int { if a > b { return a } return b } ``` ```java // by chatGPT (java) 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; } } ``` ```javascript // by chatGPT (javascript) var lengthOfLongestSubstring = function(s) { let window = new Map(); let left = 0, right = 0, res = 0; while (right < s.length) { let c = s[right]; right++; // 进行窗口内数据的一系列更新 window.set(c, (window.get(c) || 0) + 1); // 判断左侧窗口是否要收缩 while (window.get(c) > 1) { let d = s[left]; left++; // 进行窗口内数据的一系列更新 window.set(d, window.get(d) - 1); } // 在这里更新答案 res = Math.max(res, right - left); } return res; }; ``` ```python # by chatGPT (python) class Solution: def lengthOfLongestSubstring(self, s: str) -> int: window = {} left = right = 0 res = 0 # 记录结果 while right < len(s): c = s[right] right += 1 # 进行窗口内数据的一系列更新 window[c] = window.get(c, 0) + 1 # 判断左侧窗口是否要收缩 while window[c] > 1: d = s[left] left += 1 # 进行窗口内数据的一系列更新 window[d] -= 1 # 在这里更新答案 res = max(res, right - left) return res ``` https://leetcode.cn/problems/longest-substring-without-repeating-characters 的多语言解法👆 https://leetcode.cn/problems/longest-valid-parentheses 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int longestValidParentheses(string s) { stack stk; // dp[i] 的定义:记录以 s[i-1] 结尾的最长合法括号子串长度 vector dp(s.length() + 1, 0); for (int i = 0; i < s.length(); i++) { if (s[i] == '(') { // 遇到左括号,记录索引 stk.push(i); // 左括号不可能是合法括号子串的结尾 dp[i + 1] = 0; } else { // 遇到右括号 if (!stk.empty()) { // 配对的左括号对应索引 int leftIndex = stk.top(); stk.pop(); // 以这个右括号结尾的最长子串长度 int len = 1 + i - leftIndex + dp[leftIndex]; dp[i + 1] = len; } else { // 没有配对的左括号 dp[i + 1] = 0; } } } // 计算最长子串的长度 int res = 0; for (int i = 0; i < dp.size(); i++) { res = max(res, dp[i]); } return res; } }; ``` ```go // by chatGPT (go) func longestValidParentheses(s string) int { stk := make([]int, 0) // dp[i] 的定义:记录以 s[i-1] 结尾的最长合法括号子串长度 dp := make([]int, len(s)+1) for i := 0; i < len(s); i++ { if s[i] == '(' { // 遇到左括号,记录索引 stk = append(stk, i) // 左括号不可能是合法括号子串的结尾 dp[i+1] = 0 } else { // 遇到右括号 if len(stk) != 0 { // 配对的左括号对应索引 leftIndex := stk[len(stk)-1] stk = stk[:len(stk)-1] // 以这个右括号结尾的最长子串长度 len := 1 + i - leftIndex + dp[leftIndex] dp[i+1] = len } else { // 没有配对的左括号 dp[i+1] = 0 } } } // 计算最长子串的长度 res := 0 for i := 0; i < len(dp); i++ { res = max(res, dp[i]) } return res } func max(a, b int) int { if a > b { return a } return b } ``` ```java // by labuladong (java) class Solution { public int longestValidParentheses(String s) { Stack stk = new Stack<>(); // dp[i] 的定义:记录以 s[i-1] 结尾的最长合法括号子串长度 int[] dp = new int[s.length() + 1]; for (int i = 0; i < s.length(); i++) { if (s.charAt(i) == '(') { // 遇到左括号,记录索引 stk.push(i); // 左括号不可能是合法括号子串的结尾 dp[i + 1] = 0; } else { // 遇到右括号 if (!stk.isEmpty()) { // 配对的左括号对应索引 int leftIndex = stk.pop(); // 以这个右括号结尾的最长子串长度 int len = 1 + i - leftIndex + dp[leftIndex]; dp[i + 1] = len; } else { // 没有配对的左括号 dp[i + 1] = 0; } } } // 计算最长子串的长度 int res = 0; for (int i = 0; i < dp.length; i++) { res = Math.max(res, dp[i]); } return res; } } ``` ```javascript // by chatGPT (javascript) var longestValidParentheses = function(s) { let stk = []; // dp[i] 的定义:记录以 s[i-1] 结尾的最长合法括号子串长度 let dp = new Array(s.length + 1).fill(0); for (let i = 0; i < s.length; i++) { if (s.charAt(i) === '(') { // 遇到左括号,记录索引 stk.push(i); // 左括号不可能是合法括号子串的结尾 dp[i + 1] = 0; } else { // 遇到右括号 if (stk.length > 0) { // 配对的左括号对应索引 let leftIndex = stk.pop(); // 以这个右括号结尾的最长子串长度 let len = 1 + i - leftIndex + dp[leftIndex]; dp[i + 1] = len; } else { // 没有配对的左括号 dp[i + 1] = 0; } } } // 计算最长子串的长度 let res = 0; for (let i = 0; i < dp.length; i++) { res = Math.max(res, dp[i]); } return res; }; ``` ```python # by chatGPT (python) class Solution: def longestValidParentheses(self, s: str) -> int: stk = [] # dp[i] 的定义:记录以 s[i-1] 结尾的最长合法括号子串长度 dp = [0] * (len(s) + 1) for i in range(len(s)): if s[i] == '(': # 遇到左括号,记录索引 stk.append(i) # 左括号不可能是合法括号子串的结尾 dp[i + 1] = 0 else: # 遇到右括号 if stk: # 配对的左括号对应索引 leftIndex = stk.pop() # 以这个右括号结尾的最长子串长度 length = 1 + i - leftIndex + dp[leftIndex] dp[i + 1] = length else: # 没有配对的左括号 dp[i + 1] = 0 # 计算最长子串的长度 res = 0 for i in range(len(dp)): res = max(res, dp[i]) return res ``` https://leetcode.cn/problems/longest-valid-parentheses 的多语言解法👆 https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-tree 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) { // base case if (root == nullptr) return nullptr; if (root == p || root == q) return root; TreeNode* left = lowestCommonAncestor(root->left, p, q); TreeNode* right = lowestCommonAncestor(root->right, p, q); // 情况 1 if (left != nullptr && right != nullptr) { return root; } // 情况 2 if (left == nullptr && right == nullptr) { return nullptr; } // 情况 3 return left == nullptr ? right : left; } }; ``` ```go // by chatGPT (go) func lowestCommonAncestor(root, p, q *TreeNode) *TreeNode { // base case if root == nil { return nil } if root == p || root == q { return root } left := lowestCommonAncestor(root.Left, p, q) right := lowestCommonAncestor(root.Right, p, q) // 情况 1 if left != nil && right != nil { return root } // 情况 2 if left == nil && right == nil { return nil } // 情况 3 if left == nil { return right } return left } ``` ```java // by labuladong (java) class Solution { public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { // base case 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); // 情况 1 if (left != null && right != null) { return root; } // 情况 2 if (left == null && right == null) { return null; } // 情况 3 return left == null ? right : left; } } ``` ```javascript // by chatGPT (javascript) var lowestCommonAncestor = function(root, p, q) { // base case if (root === null) return null; if (root === p || root === q) return root; var left = lowestCommonAncestor(root.left, p, q); var right = lowestCommonAncestor(root.right, p, q); // 情况 1 if (left !== null && right !== null) { return root; } // 情况 2 if (left === null && right === null) { return null; } // 情况 3 return left === null ? right : left; }; ``` ```python # by chatGPT (python) class Solution: def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode': # base case if not root: return None if root == p or root == q: return root left = self.lowestCommonAncestor(root.left, p, q) right = self.lowestCommonAncestor(root.right, p, q) # 情况 1 if left and right: return root # 情况 2 if not left and not right: return None # 情况 3 return right if not left else left ``` https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-tree 的多语言解法👆 https://leetcode.cn/problems/lru-cache 的多语言解法👇 ```cpp // by chatGPT (cpp) class LRUCache { private: int cap; unordered_map cache; public: LRUCache(int capacity) { this->cap = capacity; } int get(int key) { if (cache.find(key) == cache.end()) { return -1; } // 将 key 变为最近使用 makeRecently(key); return cache[key]; } void put(int key, int val) { if (cache.find(key) != cache.end()) { // 修改 key 的值 cache[key] = val; // 将 key 变为最近使用 makeRecently(key); return; } if (cache.size() >= this->cap) { // 链表头部就是最久未使用的 key int oldestKey = cache.begin()->first; cache.erase(oldestKey); } // 将新的 key 添加链表尾部 cache[key] = val; } private: void makeRecently(int key) { int val = cache[key]; // 删除 key,重新插入到队尾 cache.erase(key); cache[key] = val; } }; ``` ```go // by chatGPT (go) type LRUCache struct { cap int cache map[int]int list *list.List } func Constructor(capacity int) LRUCache { return LRUCache{ cap: capacity, cache: make(map[int]int), list: list.New(), } } func (this *LRUCache) Get(key int) int { if val, ok := this.cache[key]; ok { // 将 key 移动到链表尾部表示最近访问 this.makeRecently(key) return val } return -1 } func (this *LRUCache) Put(key int, value int) { if _, ok := this.cache[key]; ok { // 修改 key 的值,将 key 移动到链表尾部表示最近访问 this.cache[key] = value this.makeRecently(key) return } if len(this.cache) >= this.cap { // 链表头部就是最久未使用的 key this.removeOldest() } // 将新的 key-value 添加链表尾部 this.cache[key] = value this.list.PushBack(key) } func (this *LRUCache) makeRecently(key int) { // 将 key 移动到链表尾部表示最近访问 for e := this.list.Front(); e != nil; e = e.Next() { if e.Value.(int) == key { this.list.MoveToBack(e) break } } } func (this *LRUCache) removeOldest() { // 删除链表头部表示最久未使用的 key e := this.list.Front() delete(this.cache, e.Value.(int)) this.list.Remove(e) } ``` ```java // by labuladong (java) class LRUCache { int cap; LinkedHashMap cache = new LinkedHashMap<>(); public LRUCache(int capacity) { this.cap = capacity; } public int get(int key) { if (!cache.containsKey(key)) { return -1; } // 将 key 变为最近使用 makeRecently(key); return cache.get(key); } public void put(int key, int val) { if (cache.containsKey(key)) { // 修改 key 的值 cache.put(key, val); // 将 key 变为最近使用 makeRecently(key); return; } if (cache.size() >= this.cap) { // 链表头部就是最久未使用的 key int oldestKey = cache.keySet().iterator().next(); cache.remove(oldestKey); } // 将新的 key 添加链表尾部 cache.put(key, val); } private void makeRecently(int key) { int val = cache.get(key); // 删除 key,重新插入到队尾 cache.remove(key); cache.put(key, val); } } ``` ```javascript // by chatGPT (javascript) /** * @param {number} capacity */ var LRUCache = function(capacity) { this.cap = capacity; this.cache = new Map(); }; /** * @param {number} key * @return {number} */ LRUCache.prototype.get = function(key) { if (!this.cache.has(key)) { return -1; } // 将 key 变为最近使用 this.makeRecently(key); return this.cache.get(key); }; /** * @param {number} key * @param {number} value * @return {void} */ LRUCache.prototype.put = function(key, value) { if (this.cache.has(key)) { // 修改 key 的值 this.cache.set(key, value); // 将 key 变为最近使用 this.makeRecently(key); return; } if (this.cache.size >= this.cap) { // 链表头部就是最久未使用的 key const oldestKey = this.cache.keys().next().value; this.cache.delete(oldestKey); } // 将新的 key 添加链表尾部 this.cache.set(key, value); }; /** * @param {number} key * @return {void} */ LRUCache.prototype.makeRecently = function(key) { const val = this.cache.get(key); // 删除 key,重新插入到队尾 this.cache.delete(key); this.cache.set(key, val); }; ``` ```python # by chatGPT (python) class LRUCache: def __init__(self, capacity: int): self.cap = capacity self.cache = collections.OrderedDict() def get(self, key: int) -> int: if key not in self.cache: return -1 # 将 key 变为最近使用 self.cache.move_to_end(key) return self.cache[key] def put(self, key: int, value: int) -> None: if key in self.cache: # 修改 key 的值 self.cache[key] = value # 将 key 变为最近使用 self.cache.move_to_end(key) return if len(self.cache) >= self.cap: # 链表头部就是最久未使用的 key self.cache.popitem(last=False) # 将新的 key 添加链表尾部 self.cache[key] = value ``` https://leetcode.cn/problems/lru-cache 的多语言解法👆 https://leetcode.cn/problems/majority-element 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int majorityElement(vector& nums) { // 我们想寻找的那个众数 int target = 0; // 计数器(类比带电粒子例子中的带电性) int count = 0; for (int i = 0; i < nums.size(); i++) { if (count == 0) { // 当计数器为 0 时,假设 nums[i] 就是众数 target = nums[i]; // 众数出现了一次 count = 1; } else if (nums[i] == target) { // 如果遇到的是目标众数,计数器累加 count++; } else { // 如果遇到的不是目标众数,计数器递减 count--; } } // 回想带电粒子的例子 // 此时的 count 必然大于 0,此时的 target 必然就是目标众数 return target; } }; ``` ```go // by chatGPT (go) func majorityElement(nums []int) int { // 我们想寻找的那个众数 target := 0 // 计数器(类比带电粒子例子中的带电性) count := 0 for i := 0; i < len(nums); i++ { if count == 0 { // 当计数器为 0 时,假设 nums[i] 就是众数 target = nums[i] // 众数出现了一次 count = 1 } else if nums[i] == target { // 如果遇到的是目标众数,计数器累加 count++ } else { // 如果遇到的不是目标众数,计数器递减 count-- } } // 回想带电粒子的例子 // 此时的 count 必然大于 0,此时的 target 必然就是目标众数 return target } ``` ```java // by labuladong (java) class Solution { public int majorityElement(int[] nums) { // 我们想寻找的那个众数 int target = 0; // 计数器(类比带电粒子例子中的带电性) int count = 0; for (int i = 0; i < nums.length; i++) { if (count == 0) { // 当计数器为 0 时,假设 nums[i] 就是众数 target = nums[i]; // 众数出现了一次 count = 1; } else if (nums[i] == target) { // 如果遇到的是目标众数,计数器累加 count++; } else { // 如果遇到的不是目标众数,计数器递减 count--; } } // 回想带电粒子的例子 // 此时的 count 必然大于 0,此时的 target 必然就是目标众数 return target; } } ``` ```javascript // by chatGPT (javascript) var majorityElement = function(nums) { // 我们想寻找的那个众数 let target = 0; // 计数器(类比带电粒子例子中的带电性) let count = 0; for (let i = 0; i < nums.length; i++) { if (count === 0) { // 当计数器为 0 时,假设 nums[i] 就是众数 target = nums[i]; // 众数出现了一次 count = 1; } else if (nums[i] === target) { // 如果遇到的是目标众数,计数器累加 count++; } else { // 如果遇到的不是目标众数,计数器递减 count--; } } // 回想带电粒子的例子 // 此时的 count 必然大于 0,此时的 target 必然就是目标众数 return target; }; ``` ```python # by chatGPT (python) class Solution: def majorityElement(self, nums: List[int]) -> int: # 我们想寻找的那个众数 target = 0 # 计数器(类比带电粒子例子中的带电性) count = 0 for i in range(len(nums)): if count == 0: # 当计数器为 0 时,假设 nums[i] 就是众数 target = nums[i] # 众数出现了一次 count = 1 elif nums[i] == target: # 如果遇到的是目标众数,计数器累加 count += 1 else: # 如果遇到的不是目标众数,计数器递减 count -= 1 # 回想带电粒子的例子 # 此时的 count 必然大于 0,此时的 target 必然就是目标众数 return target ``` https://leetcode.cn/problems/majority-element 的多语言解法👆 https://leetcode.cn/problems/matrix-block-sum 的多语言解法👇 ```cpp // by chatGPT (cpp) class NumMatrix { private: vector> preSum; public: NumMatrix(vector>& matrix) { int m = matrix.size(), n = matrix[0].size(); if (m == 0 || n == 0) return; // 构造前缀和矩阵 preSum = vector>(m + 1, vector(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] 的元素和 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]; } }; class Solution { public: vector> matrixBlockSum(vector>& mat, int k) { int m = mat.size(), n = mat[0].size(); NumMatrix numMatrix(mat); vector> res(m, vector(n)); for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { // 左上角的坐标 int x1 = max(i - k, 0); int y1 = max(j - k, 0); // 右下角坐标 int x2 = min(i + k, m - 1); int y2 = min(j + k, n - 1); res[i][j] = numMatrix.sumRegion(x1, y1, x2, y2); } } return res; } }; ``` ```go // by chatGPT (go) type NumMatrix struct { // 定义:preSum[i][j] 记录 matrix 中子矩阵 [0, 0, i-1, j-1] 的元素和 preSum [][]int } func Constructor(matrix [][]int) NumMatrix { m, n := len(matrix), len(matrix[0]) // 构造前缀和矩阵 preSum := make([][]int, m+1) for i := range preSum { preSum[i] = make([]int, n+1) } for i := 1; i <= m; i++ { for 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] } } return NumMatrix{preSum: preSum} } // 计算子矩阵 [x1, y1, x2, y2] 的元素和 func (n *NumMatrix) SumRegion(x1 int, y1 int, x2 int, y2 int) int { // 目标矩阵之和由四个相邻矩阵运算获得 return n.preSum[x2+1][y2+1] - n.preSum[x1][y2+1] - n.preSum[x2+1][y1] + n.preSum[x1][y1] } func matrixBlockSum(mat [][]int, k int) [][]int { m, n := len(mat), len(mat[0]) numMatrix := Constructor(mat) res := make([][]int, m) for i := range res { res[i] = make([]int, n) } for i := 0; i < m; i++ { for j := 0; j < n; j++ { // 左上角的坐标 x1 := max(i-k, 0) y1 := max(j-k, 0) // 右下角坐标 x2 := min(i+k, m-1) y2 := min(j+k, n-1) res[i][j] = numMatrix.SumRegion(x1, y1, x2, y2) } } return res } func max(a, b int) int { if a > b { return a } return b } func min(a, b int) int { if a < b { return a } return b } ``` ```java // by labuladong (java) class Solution { public int[][] matrixBlockSum(int[][] mat, int k) { int m = mat.length, n = mat[0].length; NumMatrix numMatrix = new NumMatrix(mat); int[][] res = new int[m][n]; for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { // 左上角的坐标 int x1 = Math.max(i - k, 0); int y1 = Math.max(j - k, 0); // 右下角坐标 int x2 = Math.min(i + k, m - 1); int y2 = Math.min(j + k, n - 1); res[i][j] = numMatrix.sumRegion(x1, y1, x2, y2); } } return res; } } class NumMatrix { // 定义:preSum[i][j] 记录 matrix 中子矩阵 [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]; } } ``` ```javascript // by chatGPT (javascript) /** * @param {number[][]} mat * @param {number} k * @return {number[][]} */ var matrixBlockSum = function(mat, k) { const m = mat.length, n = mat[0].length; const numMatrix = new NumMatrix(mat); const res = new Array(m); for (let i = 0; i < m; i++) { res[i] = new Array(n); for (let j = 0; j < n; j++) { // 左上角的坐标 const x1 = Math.max(i - k, 0); const y1 = Math.max(j - k, 0); // 右下角坐标 const x2 = Math.min(i + k, m - 1); const y2 = Math.min(j + k, n - 1); res[i][j] = numMatrix.sumRegion(x1, y1, x2, y2); } } return res; }; class NumMatrix { /** * @param {number[][]} matrix */ constructor(matrix) { const m = matrix.length, n = matrix[0].length; if (m === 0 || n === 0) return; // 构造前缀和矩阵 this.preSum = new Array(m + 1); for (let i = 0; i <= m; i++) { this.preSum[i] = new Array(n + 1).fill(0); } for (let i = 1; i <= m; i++) { for (let j = 1; j <= n; j++) { // 计算每个矩阵 [0, 0, i, j] 的元素和 this.preSum[i][j] = this.preSum[i - 1][j] + this.preSum[i][j - 1] + matrix[i - 1][j - 1] - this.preSum[i - 1][j - 1]; } } } /** * @param {number} x1 * @param {number} y1 * @param {number} x2 * @param {number} y2 * @return {number} */ sumRegion(x1, y1, x2, y2) { // 目标矩阵之和由四个相邻矩阵运算获得 return this.preSum[x2 + 1][y2 + 1] - this.preSum[x1][y2 + 1] - this.preSum[x2 + 1][y1] + this.preSum[x1][y1]; } } ``` ```python # by chatGPT (python) class Solution: def matrixBlockSum(self, mat: List[List[int]], k: int) -> List[List[int]]: m, n = len(mat), len(mat[0]) numMatrix = NumMatrix(mat) res = [[0]*n for _ in range(m)] for i in range(m): for j in range(n): # 左上角的坐标 x1, y1 = max(i - k, 0), max(j - k, 0) # 右下角坐标 x2, y2 = min(i + k, m - 1), min(j + k, n - 1) res[i][j] = numMatrix.sumRegion(x1, y1, x2, y2) return res class NumMatrix: def __init__(self, matrix: List[List[int]]): m, n = len(matrix), len(matrix[0]) if m == 0 or n == 0: return # 构造前缀和矩阵 self.preSum = [[0]*(n+1) for _ in range(m+1)] for i in range(1, m+1): for j in range(1, n+1): # 计算每个矩阵 [0, 0, i, j] 的元素和 self.preSum[i][j] = self.preSum[i-1][j] + self.preSum[i][j-1] + matrix[i-1][j-1] - self.preSum[i-1][j-1] # 计算子矩阵 [x1, y1, x2, y2] 的元素和 def sumRegion(self, x1: int, y1: int, x2: int, y2: int) -> int: # 目标矩阵之和由四个相邻矩阵运算获得 return self.preSum[x2+1][y2+1] - self.preSum[x1][y2+1] - self.preSum[x2+1][y1] + self.preSum[x1][y1] ``` https://leetcode.cn/problems/matrix-block-sum 的多语言解法👆 https://leetcode.cn/problems/max-area-of-island 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int maxAreaOfIsland(vector>& grid) { // 记录岛屿的最大面积 int res = 0; int m = grid.size(), n = grid[0].size(); for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { if (grid[i][j] == 1) { // 淹没岛屿,并更新最大岛屿面积 res = max(res, dfs(grid, i, j)); } } } return res; } // 淹没与 (i, j) 相邻的陆地,并返回淹没的陆地面积 int dfs(vector>& grid, int i, int j) { int m = grid.size(), n = grid[0].size(); if (i < 0 || j < 0 || i >= m || j >= n) { // 超出索引边界 return 0; } if (grid[i][j] == 0) { // 已经是海水了 return 0; } // 将 (i, j) 变成海水 grid[i][j] = 0; return dfs(grid, i + 1, j) + dfs(grid, i, j + 1) + dfs(grid, i - 1, j) + dfs(grid, i, j - 1) + 1; } }; ``` ```go // by chatGPT (go) func maxAreaOfIsland(grid [][]int) int { // 记录岛屿的最大面积 res := 0 m, n := len(grid), len(grid[0]) for i := 0; i < m; i++ { for j := 0; j < n; j++ { if grid[i][j] == 1 { // 淹没岛屿,并更新最大岛屿面积 res = Max(res, dfs(grid, i, j)) } } } return res } // 淹没与 (i, j) 相邻的陆地,并返回淹没的陆地面积 func dfs(grid [][]int, i int, j int) int { m, n := len(grid), len(grid[0]) if i < 0 || j < 0 || i >= m || j >= n { // 超出索引边界 return 0 } if grid[i][j] == 0 { // 已经是海水了 return 0 } // 将 (i, j) 变成海水 grid[i][j] = 0 return dfs(grid, i+1, j) + dfs(grid, i, j+1) + dfs(grid, i-1, j) + dfs(grid, i, j-1) + 1 } // Max 获取两个数的最大值 func Max(x int, y int) int { if x > y { return x } return y } ``` ```java // by labuladong (java) class Solution { public int maxAreaOfIsland(int[][] grid) { // 记录岛屿的最大面积 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 = Math.max(res, dfs(grid, i, j)); } } } return res; } // 淹没与 (i, j) 相邻的陆地,并返回淹没的陆地面积 int dfs(int[][] grid, int i, int j) { int m = grid.length, n = grid[0].length; if (i < 0 || j < 0 || i >= m || j >= n) { // 超出索引边界 return 0; } if (grid[i][j] == 0) { // 已经是海水了 return 0; } // 将 (i, j) 变成海水 grid[i][j] = 0; return dfs(grid, i + 1, j) + dfs(grid, i, j + 1) + dfs(grid, i - 1, j) + dfs(grid, i, j - 1) + 1; } } ``` ```javascript // by chatGPT (javascript) var maxAreaOfIsland = function(grid) { // 记录岛屿的最大面积 let res = 0; const m = grid.length, n = grid[0].length; // 淹没与 (i, j) 相邻的陆地,并返回淹没的陆地面积 const dfs = (grid, i, j) => { const m = grid.length, n = grid[0].length; if (i < 0 || j < 0 || i >= m || j >= n) { // 超出索引边界 return 0; } if (grid[i][j] === 0) { // 已经是海水了 return 0; } // 将 (i, j) 变成海水 grid[i][j] = 0; return dfs(grid, i + 1, j) + dfs(grid, i, j + 1) + dfs(grid, i - 1, j) + dfs(grid, i, j - 1) + 1; }; for (let i = 0; i < m; i++) { for (let j = 0; j < n; j++) { if (grid[i][j] === 1) { // 淹没岛屿,并更新最大岛屿面积 res = Math.max(res, dfs(grid, i, j)); } } } return res; }; ``` ```python # by chatGPT (python) class Solution: def maxAreaOfIsland(self, grid: List[List[int]]) -> int: # 记录岛屿的最大面积 res = 0 m, n = len(grid), len(grid[0]) for i in range(m): for j in range(n): if grid[i][j] == 1: # 淹没岛屿,并更新最大岛屿面积 res = max(res, self.dfs(grid, i, j)) return res # 淹没与 (i, j) 相邻的陆地,并返回淹没的陆地面积 def dfs(self, grid: List[List[int]], i: int, j: int) -> int: m, n = len(grid), len(grid[0]) if i < 0 or j < 0 or i >= m or j >= n: # 超出索引边界 return 0 if grid[i][j] == 0: # 已经是海水了 return 0 # 将 (i, j) 变成海水 grid[i][j] = 0 return self.dfs(grid, i + 1, j) \ + self.dfs(grid, i - 1, j) \ + self.dfs(grid, i, j + 1) \ + self.dfs(grid, i, j - 1) \ + 1 ``` https://leetcode.cn/problems/max-area-of-island 的多语言解法👆 https://leetcode.cn/problems/maximum-binary-tree 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: /* 主函数 */ TreeNode* constructMaximumBinaryTree(vector& nums) { return build(nums, 0, nums.size() - 1); } /* 定义:将 nums[lo..hi] 构造成符合条件的树,返回根节点 */ TreeNode* build(vector& nums, int lo, int hi) { /* base case */ if (lo > hi) { return nullptr; } /* 找到数组中的最大值和对应的索引 */ int index = -1, maxVal = INT_MIN; for (int i = lo; i <= hi; i++) { if (maxVal < nums[i]) { index = i; maxVal = nums[i]; } } TreeNode* root = new TreeNode(maxVal); /* 递归调用构造左右子树 */ root->left = build(nums, lo, index - 1); root->right = build(nums, index + 1, hi); return root; } }; ``` ```go // by chatGPT (go) func constructMaximumBinaryTree(nums []int) *TreeNode { return build(nums, 0, len(nums)-1) } func build(nums []int, lo int, hi int) *TreeNode { // base case if lo > hi { return nil } // 找到数组中的最大值和对应的索引 index, maxVal := -1, math.MinInt32 for i := lo; i <= hi; i++ { if maxVal < nums[i] { index = i maxVal = nums[i] } } root := &TreeNode{Val: maxVal} // 递归调用构造左右子树 root.Left = build(nums, lo, index-1) root.Right = build(nums, index+1, hi) return root } ``` ```java // by labuladong (java) class Solution { /* 主函数 */ public TreeNode constructMaximumBinaryTree(int[] nums) { return build(nums, 0, nums.length - 1); } /* 定义:将 nums[lo..hi] 构造成符合条件的树,返回根节点 */ TreeNode build(int[] nums, int lo, int hi) { // base case if (lo > hi) { return null; } // 找到数组中的最大值和对应的索引 int index = -1, maxVal = Integer.MIN_VALUE; for (int i = lo; i <= hi; i++) { if (maxVal < nums[i]) { index = i; maxVal = nums[i]; } } TreeNode root = new TreeNode(maxVal); // 递归调用构造左右子树 root.left = build(nums, lo, index - 1); root.right = build(nums, index + 1, hi); return root; } } ``` ```javascript // by chatGPT (javascript) /** * @param {number[]} nums * @return {TreeNode} */ var constructMaximumBinaryTree = function(nums) { return build(nums, 0, nums.length - 1); } /** * Define: 将 nums[lo..hi] 构造成符合条件的树,返回根节点 */ var build = function(nums, lo, hi) { // base case if (lo > hi) { return null; } // 找到数组中的最大值和对应的索引 var index = -1, maxVal = -Infinity; for (var i = lo; i <= hi; i++) { if (maxVal < nums[i]) { index = i; maxVal = nums[i]; } } var root = new TreeNode(maxVal); // 递归调用构造左右子树 root.left = build(nums, lo, index - 1); root.right = build(nums, index + 1, hi); return root; } ``` ```python # by chatGPT (python) # Definition for a binary tree node. # class TreeNode: # def __init__(self, val=0, left=None, right=None): # self.val = val # self.left = left # self.right = right class Solution: def constructMaximumBinaryTree(self, nums: List[int]) -> TreeNode: """ :type nums: List[int] :rtype: TreeNode """ def build(nums, lo, hi): # base case if lo > hi: return None # 找到数组中的最大值和对应的索引 index, maxVal = lo, nums[lo] for i in range(lo, hi+1): if nums[i] > maxVal: index, maxVal = i, nums[i] # 创建根节点 root = TreeNode(maxVal) # 递归构建左右子树 root.left = build(nums, lo, index - 1) root.right = build(nums, index + 1, hi) return root # 调用 build 函数 return build(nums, 0, len(nums)-1) ``` https://leetcode.cn/problems/maximum-binary-tree 的多语言解法👆 https://leetcode.cn/problems/maximum-depth-of-binary-tree 的多语言解法👇 ```cpp // by chatGPT (cpp) /***** 解法一,回溯算法思路 *****/ class Solution { public: int depth = 0; int res = 0; int maxDepth(TreeNode* root) { traverse(root); return res; } // 遍历二叉树 void traverse(TreeNode* root) { if (root == nullptr) { return; } // 前序遍历位置 depth++; // 遍历的过程中记录最大深度 res = max(res, depth); traverse(root->left); traverse(root->right); // 后序遍历位置 depth--; } }; /***** 解法二,动态规划思路 *****/ class Solution2 { public: // 定义:输入一个节点,返回以该节点为根的二叉树的最大深度 int maxDepth(TreeNode* root) { if (root == nullptr) { return 0; } int leftMax = maxDepth(root->left); int rightMax = maxDepth(root->right); // 根据左右子树的最大深度推出原二叉树的最大深度 return 1 + max(leftMax, rightMax); } }; ``` ```go // by chatGPT (go) /***** 解法一,回溯算法思路 *****/ func maxDepth(root *TreeNode) int { var depth int var res int traverse(root, &depth, &res) return res } // 遍历二叉树 func traverse(root *TreeNode, depth *int, res *int) { if root == nil { return } // 前序遍历位置 *depth++ // 遍历的过程中记录最大深度 *res = max(*res,*depth) traverse(root.Left, depth, res) traverse(root.Right, depth, res) // 后序遍历位置 *depth-- } /***** 解法二,动态规划思路 *****/ func maxDepth2(root *TreeNode) int { if root == nil { return 0 } leftMax := maxDepth2(root.Left) rightMax := maxDepth2(root.Right) // 根据左右子树的最大深度推出原二叉树的最大深度 return 1 + max(leftMax, rightMax) } func max(x, y int) int { if x > y { return x } return y } ``` ```java // by labuladong (java) /***** 解法一,回溯算法思路 *****/ class Solution { int depth = 0; int res = 0; public int maxDepth(TreeNode root) { traverse(root); return res; } // 遍历二叉树 void traverse(TreeNode root) { if (root == null) { return; } // 前序遍历位置 depth++; // 遍历的过程中记录最大深度 res = Math.max(res, depth); traverse(root.left); traverse(root.right); // 后序遍历位置 depth--; } } /***** 解法二,动态规划思路 *****/ class Solution2 { // 定义:输入一个节点,返回以该节点为根的二叉树的最大深度 public int maxDepth(TreeNode root) { if (root == null) { return 0; } int leftMax = maxDepth(root.left); int rightMax = maxDepth(root.right); // 根据左右子树的最大深度推出原二叉树的最大深度 return 1 + Math.max(leftMax, rightMax); } } ``` ```javascript // by chatGPT (javascript) /** * 解法一,回溯算法思路 */ function maxDepth(root) { let depth = 0; let res = 0; // 遍历二叉树 function traverse(root) { if (root === null) { return; } // 前序遍历位置 depth++; // 遍历的过程中记录最大深度 res = Math.max(res, depth); traverse(root.left); traverse(root.right); // 后序遍历位置 depth--; } traverse(root); return res; } /** * 解法二,动态规划思路 */ // 定义:输入一个节点,返回以该节点为根的二叉树的最大深度 function maxDepth2(root) { if (root === null) { return 0; } const leftMax = maxDepth2(root.left); const rightMax = maxDepth2(root.right); // 根据左右子树的最大深度推出原二叉树的最大深度 return 1 + Math.max(leftMax, rightMax); } ``` ```python # by chatGPT (python) # 解法一,回溯算法思路 class Solution: def maxDepth(self, root: TreeNode) -> int: self.res = 0 self.traverse(root, 0) return self.res # 遍历二叉树 def traverse(self, root: TreeNode, depth: int) -> None: if not root: return # 前序遍历位置 depth += 1 # 遍历的过程中记录最大深度 self.res = max(self.res, depth) self.traverse(root.left, depth) self.traverse(root.right, depth) # 后序遍历位置 depth -= 1 # 解法二,动态规划思路 class Solution: # 定义:输入一个节点,返回以该节点为根的二叉树的最大深度 def maxDepth(self, root: TreeNode) -> int: if not root: return 0 leftMax = self.maxDepth(root.left) rightMax = self.maxDepth(root.right) # 根据左右子树的最大深度推出原二叉树的最大深度 return 1 + max(leftMax, rightMax) ``` https://leetcode.cn/problems/maximum-depth-of-binary-tree 的多语言解法👆 https://leetcode.cn/problems/maximum-depth-of-n-ary-tree 的多语言解法👇 ```cpp // by chatGPT (cpp) // 分解问题的思路 class Solution { public: int maxDepth(Node* root) { if (root == nullptr) { return 0; } int subTreeMaxDepth = 0; for (Node* child : root->children) { subTreeMaxDepth = max(subTreeMaxDepth, maxDepth(child)); } return 1 + subTreeMaxDepth; } }; // 遍历的思路 class Solution2 { public: int maxDepth(Node* root) { traverse(root); return res; } private: // 记录递归遍历到的深度 int depth = 0; // 记录最大的深度 int res = 0; void traverse(Node* root) { if (root == nullptr) { return; } // 前序遍历位置 depth++; res = max(res, depth); for (Node* child : root->children) { traverse(child); } // 后序遍历位置 depth--; } }; ``` ```go // by chatGPT (go) // 分解问题的思路 func maxDepth(root *Node) int { if root == nil { return 0 } subTreeMaxDepth := 0 for _, child := range root.Children { subTreeMaxDepth = max(subTreeMaxDepth, maxDepth(child)) } return 1 + subTreeMaxDepth } // 遍历的思路 func maxDepth(root *Node) int { // 记录递归遍历到的深度 depth := 0 // 记录最大的深度 res := 0 traverse(root, &depth, &res) return res } func traverse(root *Node, depth *int, res *int) { if root == nil { return } // 前序遍历位置 (*depth)++ *res = max(*res, *depth) for _, child := range root.Children { traverse(child, depth, res) } // 后序遍历位置 (*depth)-- } func max(a, b int) int { if a > b { return a } return b } ``` ```java // by labuladong (java) // 分解问题的思路 class Solution { public int maxDepth(Node root) { if (root == null) { return 0; } int subTreeMaxDepth = 0; for (Node child : root.children) { subTreeMaxDepth = Math.max(subTreeMaxDepth, maxDepth(child)); } return 1 + subTreeMaxDepth; } } // 遍历的思路 class Solution2 { public int maxDepth(Node root) { traverse(root); return res; } // 记录递归遍历到的深度 int depth = 0; // 记录最大的深度 int res = 0; void traverse(Node root) { if (root == null) { return; } // 前序遍历位置 depth++; res = Math.max(res, depth); for (Node child : root.children) { traverse(child); } // 后序遍历位置 depth--; } } ``` ```javascript // by chatGPT (javascript) /** * 分解问题的思路 */ var maxDepth = function(root) { // 若根结点为空,则直接返回 0 if (!root) { return 0; } // 设定子树最大深度 let subTreeMaxDepth = 0; // 遍历每个子结点 for (let i = 0; i < root.children.length; i++) { subTreeMaxDepth = Math.max(subTreeMaxDepth, maxDepth(root.children[i])); } // 当前结点深度为最大子结点深度+1 return 1 + subTreeMaxDepth; }; /** * 遍历的思路 */ var maxDepth2 = function(root) { // 记录递归遍历到的深度 let depth = 0; // 记录最大的深度 let res = 0; /** * 递归遍历 */ function traverse(root) { if (!root) { return; } // 前序遍历位置 depth++; res = Math.max(res, depth); // 遍历每个子节点 for (let i = 0; i < root.children.length; i++) { traverse(root.children[i]); } // 后序遍历位置 depth--; } // 递归遍历获取最大深度 traverse(root); return res; } ``` ```python # by chatGPT (python) # 分解问题的思路 class Solution: def maxDepth(self, root: 'Node') -> int: if not root: return 0 subTreeMaxDepth = 0 for child in root.children: subTreeMaxDepth = max(subTreeMaxDepth, self.maxDepth(child)) return 1 + subTreeMaxDepth # 遍历的思路 class Solution2: def maxDepth(self, root: 'Node') -> int: self.traverse(root) return self.res # 记录递归遍历到的深度 def __init__(self): self.depth = 0 # 记录最大的深度 self.res = 0 def traverse(self, root): if not root: return # 前序遍历位置 self.depth += 1 self.res = max(self.res, self.depth) for child in root.children: self.traverse(child) # 后序遍历位置 self.depth -= 1 ``` https://leetcode.cn/problems/maximum-depth-of-n-ary-tree 的多语言解法👆 https://leetcode.cn/problems/maximum-frequency-stack 的多语言解法👇 ```cpp // by chatGPT (cpp) class FreqStack { // 记录 FreqStack 中元素的最大频率 int maxFreq = 0; // 记录 FreqStack 中每个 val 对应的出现频率,后文就称为 VF 表 unordered_map valToFreq; // 记录频率 freq 对应的 val 列表,后文就称为 FV 表 unordered_map> freqToVals; public: void push(int val) { // 修改 VF 表:val 对应的 freq 加一 int freq = valToFreq[val] + 1; valToFreq[val] = freq; // 修改 FV 表:在 freq 对应的列表加上 val freqToVals[freq].push(val); // 更新 maxFreq maxFreq = max(maxFreq, freq); } int pop() { // 修改 FV 表:pop 出一个 maxFreq 对应的元素 v stack &vals = freqToVals[maxFreq]; int v = vals.top(); vals.pop(); // 修改 VF 表:v 对应的 freq 减一 int freq = --valToFreq[v]; // 更新 maxFreq if (vals.empty()) { // 如果 maxFreq 对应的元素空了 maxFreq--; } return v; } }; ``` ```go // by chatGPT (go) type FreqStack struct { // 记录 FreqStack 中元素的最大频率 maxFreq int // 记录 FreqStack 中每个 val 对应的出现频率,后文就称为 VF 表 valToFreq map[int]int // 记录频率 freq 对应的 val 列表,后文就称为 FV 表 freqToVals map[int]*stack.Stack } func Constructor() FreqStack { return FreqStack{0, make(map[int]int), make(map[int]*stack.Stack)} } func (this *FreqStack) Push(val int) { // 修改 VF 表:val 对应的 freq 加一 freq := this.valToFreq[val] + 1 this.valToFreq[val] = freq // 修改 FV 表:在 freq 对应的列表加上 val if this.freqToVals[freq] == nil { this.freqToVals[freq] = stack.New() } this.freqToVals[freq].Push(val) // 更新 maxFreq if freq > this.maxFreq { this.maxFreq = freq } } func (this *FreqStack) Pop() int { // 修改 FV 表:pop 出一个 maxFreq 对应的元素 v vals := this.freqToVals[this.maxFreq] v := vals.Pop().(int) // 修改 VF 表:v 对应的 freq 减一 freq := this.valToFreq[v] - 1 this.valToFreq[v] = freq // 更新 maxFreq if vals.Len() == 0 { // 如果 maxFreq 对应的元素空了 this.maxFreq-- } return v } ``` ```java // by labuladong (java) class FreqStack { // 记录 FreqStack 中元素的最大频率 int maxFreq = 0; // 记录 FreqStack 中每个 val 对应的出现频率,后文就称为 VF 表 HashMap valToFreq = new HashMap<>(); // 记录频率 freq 对应的 val 列表,后文就称为 FV 表 HashMap> freqToVals = new HashMap<>(); public void push(int val) { // 修改 VF 表:val 对应的 freq 加一 int freq = valToFreq.getOrDefault(val, 0) + 1; valToFreq.put(val, freq); // 修改 FV 表:在 freq 对应的列表加上 val freqToVals.putIfAbsent(freq, new Stack<>()); freqToVals.get(freq).push(val); // 更新 maxFreq maxFreq = Math.max(maxFreq, freq); } public int pop() { // 修改 FV 表:pop 出一个 maxFreq 对应的元素 v Stack vals = freqToVals.get(maxFreq); int v = vals.pop(); // 修改 VF 表:v 对应的 freq 减一 int freq = valToFreq.get(v) - 1; valToFreq.put(v, freq); // 更新 maxFreq if (vals.isEmpty()) { // 如果 maxFreq 对应的元素空了 maxFreq--; } return v; } } ``` ```javascript // by chatGPT (javascript) /** * @param {number} val * @return {void} */ var FreqStack = function() { // 记录 FreqStack 中元素的最大频率 this.maxFreq = 0; // 记录 FreqStack 中每个 val 对应的出现频率,后文就称为 VF 表 this.valToFreq = new Map(); // 记录频率 freq 对应的 val 列表,后文就称为 FV 表 this.freqToVals = new Map(); }; /** * @param {number} val * @return {void} */ FreqStack.prototype.push = function(val) { // 修改 VF 表:val 对应的 freq 加一 const freq = this.valToFreq.get(val) + 1 || 1; this.valToFreq.set(val, freq); // 修改 FV 表:在 freq 对应的列表加上 val this.freqToVals.set(freq, this.freqToVals.get(freq) || []); this.freqToVals.get(freq).push(val); // 更新 maxFreq this.maxFreq = Math.max(this.maxFreq, freq); }; /** * @return {number} */ FreqStack.prototype.pop = function() { // 修改 FV 表:pop 出一个 maxFreq 对应的元素 v const vals = this.freqToVals.get(this.maxFreq); const v = vals.pop(); // 修改 VF 表:v 对应的 freq 减一 const freq = this.valToFreq.get(v) - 1; this.valToFreq.set(v, freq); // 更新 maxFreq if (vals.length === 0) { // 如果 maxFreq 对应的元素空了 this.maxFreq--; } return v; }; ``` ```python # by chatGPT (python) class FreqStack: def __init__(self): # 记录 FreqStack 中元素的最大频率 self.maxFreq = 0 # 记录 FreqStack 中每个 val 对应的出现频率,后文就称为 VF 表 self.valToFreq = {} # 记录频率 freq 对应的 val 列表,后文就称为 FV 表 self.freqToVals = {} def push(self, val: int) -> None: # 修改 VF 表:val 对应的 freq 加一 freq = self.valToFreq.get(val, 0) + 1 self.valToFreq[val] = freq # 修改 FV 表:在 freq 对应的列表加上 val self.freqToVals.setdefault(freq, []) self.freqToVals[freq].append(val) # 更新 maxFreq self.maxFreq = max(self.maxFreq, freq) def pop(self) -> int: # 修改 FV 表:pop 出一个 maxFreq 对应的元素 v vals = self.freqToVals[self.maxFreq] v = vals.pop() # 修改 VF 表:v 对应的 freq 减一 freq = self.valToFreq[v] - 1 self.valToFreq[v] = freq # 更新 maxFreq if not vals: # 如果 maxFreq 对应的元素空了 self.maxFreq -= 1 return v ``` https://leetcode.cn/problems/maximum-frequency-stack 的多语言解法👆 https://leetcode.cn/problems/maximum-size-subarray-sum-equals-k 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int maxSubArrayLen(vector& nums, int k) { int n = nums.size(); // preSum 中的值 -> 对应的最小索引 // 比如 preSum = [2,4,1,3,4],preSumToIndex[4] = 1 unordered_map preSumToIndex; int maxLen = 0; // 前缀和数组 int preSum = 0; // base case,这样索引相减的时候可以算出正确的子数组长度 preSumToIndex[0] = -1; for (int i = 0; i < n; i++) { // 计算前缀和,维护 preSum = sum(nums[0..i]) preSum += nums[i]; // 确保 preSumToIndex 中记录的索引是第一次出现的位置 preSumToIndex.emplace(preSum, i); int need = preSum - k; if (preSumToIndex.contains(need)) { int j = preSumToIndex[need]; // nums[j + 1..i] 是和为 k 的子数组 maxLen = max(maxLen, i - j); } } return maxLen; } }; ``` ```go // by chatGPT (go) // // MaxSubArrayLen returns the maximum length of a contiguous subarray with sum equal to k. func MaxSubArrayLen(nums []int, k int) int { n := len(nums) // preSum 中的值 -> 对应的最小索引 // 比如 preSum = [2,4,1,3,4],preSumToIndex[4] = 1 preSumToIndex := make(map[int]int, n) maxLen := 0 // 前缀和数组(在这道题中可以优化为一个变量) preSum := 0 // base case,这样索引相减的时候可以算出正确的子数组长度 preSumToIndex[0] = -1 for i := 0; i < n; i++ { // 计算前缀和,维护 preSum = sum(nums[0..i]) preSum += nums[i] // 确保 preSumToIndex 中记录的索引是第一次出现的位置 if _, ok := preSumToIndex[preSum]; !ok { preSumToIndex[preSum] = i } need := preSum - k if j, ok := preSumToIndex[need]; ok { // nums[j..i] 是和为 k 的子数组 maxLen = max(maxLen, i-j) } } return maxLen } func max(x, y int) int { if x > y { return x } return y } ``` ```java // by labuladong (java) class Solution { public static int maxSubArrayLen(int[] nums, int k) { int n = nums.length; // preSum 中的值 -> 对应的最小索引 // 比如 preSum = [2,4,1,3,4],preSumToIndex[4] = 1 HashMap preSumToIndex = new HashMap<>(); int maxLen = 0; // 前缀和数组(在这道题中可以优化为一个变量) int preSum = 0; // base case,这样索引相减的时候可以算出正确的子数组长度 preSumToIndex.put(0, -1); for (int i = 0; i < n; i++) { // 计算前缀和,维护 preSum = sum(nums[0..i]) preSum += nums[i]; // 确保 preSumToIndex 中记录的索引是第一次出现的位置 preSumToIndex.putIfAbsent(preSum, i); int need = preSum[i] - k; if (preSumToIndex.containsKey(need)) { int j = preSumToIndex.get(need); // nums[j..i] 是和为 k 的子数组 maxLen = Math.max(maxLen, i - j); } } return maxLen; } } ``` ```javascript // by chatGPT (javascript) /** * @param {number[]} nums * @param {number} k * @return {number} */ var maxSubArrayLen = function(nums, k) { let n = nums.length; // preSum 中的值 -> 对应的最小索引 // 比如 preSum = [2,4,1,3,4],preSumToIndex[4] = 1 let preSumToIndex = new Map(); let maxLen = 0; // 前缀和数组(在这道题中可以优化为一个变量) let preSum = 0; // base case,这样索引相减的时候可以算出正确的子数组长度 preSumToIndex.set(0, -1); for (let i = 0; i < n; i++) { // 计算前缀和,维护 preSum = sum(nums[0..i]) preSum += nums[i]; // 确保 preSumToIndex 中记录的索引是第一次出现的位置 if (!preSumToIndex.has(preSum)) { preSumToIndex.set(preSum, i); } let need = preSum - k; if (preSumToIndex.has(need)) { let j = preSumToIndex.get(need); // nums[j..i] 是和为 k 的子数组 maxLen = Math.max(maxLen, i - j); } } return maxLen; }; ``` ```python # by chatGPT (python) class Solution: def maxSubArrayLen(self, nums: List[int], k: int) -> int: n = len(nums) # preSum 中的值 -> 对应的最小索引 # 比如 preSum = [2,4,1,3,4],preSumToIndex[4] = 1 preSumToIndex = {} maxLen = 0 # 前缀和数组(在这道题中可以优化为一个变量) preSum = 0 # base case,这样索引相减的时候可以算出正确的子数组长度 preSumToIndex[0] = -1 for i in range(n): # 计算前缀和,维护 preSum = sum(nums[0..i]) preSum += nums[i] # 确保 preSumToIndex 中记录的索引是第一次出现的位置 preSumToIndex.setdefault(preSum, i) need = preSum - k if need in preSumToIndex: j = preSumToIndex[need] # nums[j..i] 是和为 k 的子数组 maxLen = max(maxLen, i - j) return maxLen ``` https://leetcode.cn/problems/maximum-size-subarray-sum-equals-k 的多语言解法👆 https://leetcode.cn/problems/maximum-subarray 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int maxSubArray(vector& nums) { int n = nums.size(); if (n == 0) return 0; vector dp(n); // base case // 第一个元素前面没有子数组 dp[0] = nums[0]; // 状态转移方程 for (int i = 1; i < n; i++) { dp[i] = max(nums[i], nums[i] + dp[i - 1]); } // 得到 nums 的最大子数组 int res = INT_MIN; for (int i = 0; i < n; i++) { res = max(res, dp[i]); } return res; } }; ``` ```go // by chatGPT (go) func maxSubArray(nums []int) int { n := len(nums) if n == 0 { return 0 } dp := make([]int, n) // base case // 第一个元素前面没有子数组 dp[0] = nums[0] // 状态转移方程 for i := 1; i < n; i++ { dp[i] = max(nums[i], nums[i] + dp[i - 1]) } // 得到 nums 的最大子数组 res := math.MinInt32 for i := 0; i < n; i++ { res = max(res, dp[i]) } return res } func max(a, b int) int { if a > b { return a } return b } ``` ```java // by labuladong (java) 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]; // 状态转移方程 for (int i = 1; i < n; i++) { dp[i] = Math.max(nums[i], nums[i] + dp[i - 1]); } // 得到 nums 的最大子数组 int res = Integer.MIN_VALUE; for (int i = 0; i < n; i++) { res = Math.max(res, dp[i]); } return res; } } ``` ```javascript // by chatGPT (javascript) /** * @param {number[]} nums * @return {number} */ var maxSubArray = function(nums) { const n = nums.length; if (n === 0) return 0; const dp = new Array(n); // base case // 第一个元素前面没有子数组 dp[0] = nums[0]; // 状态转移方程 for (let i = 1; i < n; i++) { dp[i] = Math.max(nums[i], nums[i] + dp[i - 1]); } // 得到 nums 的最大子数组 let res = -Infinity; for (let i = 0; i < n; i++) { res = Math.max(res, dp[i]); } return res; }; ``` ```python # by chatGPT (python) class Solution: def maxSubArray(self, nums: List[int]) -> int: n = len(nums) if n == 0: return 0 dp = [0] * n # base case # 第一个元素前面没有子数组 dp[0] = nums[0] # 状态转移方程 for i in range(1, n): dp[i] = max(nums[i], nums[i] + dp[i - 1]) # 得到 nums 的最大子数组 res = float('-inf') for i in range(n): res = max(res, dp[i]) return res ``` https://leetcode.cn/problems/maximum-subarray 的多语言解法👆 https://leetcode.cn/problems/maximum-sum-bst-in-binary-tree 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { int maxSum = 0; public: /* 主函数 */ int maxSumBST(TreeNode* root) { traverse(root); return maxSum; } vector findMaxMinSum(TreeNode* root) { // base case if (root == nullptr) { return { 1, INT_MAX, INT_MIN, 0 }; } // 递归计算左右子树 vector left = findMaxMinSum(root->left); vector right = findMaxMinSum(root->right); /*******后序遍历位置*******/ vector res(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] = min(left[1], root->val); // 计算以 root 为根的这棵 BST 的最大值 res[2] = max(right[2], root->val); // 计算以 root 为根的这棵 BST 所有节点之和 res[3] = left[3] + right[3] + root->val; // 更新全局变量 maxSum = max(maxSum, res[3]); } else { // 以 root 为根的二叉树不是 BST res[0] = 0; // 其他的值都没必要计算了,因为用不到 } /**************************/ return res; } void traverse(TreeNode* root) { if(root == nullptr) { return; } vector res = findMaxMinSum(root); traverse(root->left); traverse(root->right); } }; ``` ```go // by chatGPT (go) /** * Definition for a binary tree node. * type TreeNode struct { * Val int * Left *TreeNode * Right *TreeNode * } */ /* 主函数 */ func maxSumBST(root *TreeNode) int { // 全局变量,记录 BST 最大节点之和 maxSum := 0 traverse(root, &maxSum) return maxSum } func findMaxMinSum(root *TreeNode, maxSum *int) []int { // base case if root == nil { return []int{1, math.MaxInt64, math.MinInt64, 0} } // 递归计算左右子树 left := findMaxMinSum(root.Left, maxSum) right := findMaxMinSum(root.Right, maxSum) /*******后序遍历位置*******/ res := make([]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] = int(math.Min(float64(left[1]), float64(root.Val))) // 计算以 root 为根的这棵 BST 的最大值 res[2] = int(math.Max(float64(right[2]), float64(root.Val))) // 计算以 root 为根的这棵 BST 所有节点之和 res[3] = left[3] + right[3] + root.Val // 更新全局变量 *maxSum = int(math.Max(float64(*maxSum), float64(res[3]))) } else { // 以 root 为根的二叉树不是 BST res[0] = 0 // 其他的值都没必要计算了,因为用不到 } /**************************/ return res } func traverse(root *TreeNode, maxSum *int) { if root == nil { return } findMaxMinSum(root, maxSum) traverse(root.Left, maxSum) traverse(root.Right, maxSum) } ``` ```java // by labuladong (java) class Solution { // 全局变量,记录 BST 最大节点之和 int maxSum = 0; /* 主函数 */ public int maxSumBST(TreeNode root) { traverse(root); return maxSum; } 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); /*******后序遍历位置*******/ 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; } } ``` ```javascript // by chatGPT (javascript) var maxSumBST = function(root) { let maxSum = 0; const findMaxMinSum = node => { if (node === null) { return [1, Infinity, -Infinity, 0]; } let left = findMaxMinSum(node.left); let right = findMaxMinSum(node.right); /*******后序遍历位置*******/ let res = new Array(4); if (left[0] === 1 && right[0] === 1 && node.val > left[2] && node.val < right[1]) { res[0] = 1; res[1] = Math.min(left[1], node.val); res[2] = Math.max(right[2], node.val); res[3] = left[3] + right[3] + node.val; maxSum = Math.max(maxSum, res[3]); } else { res[0] = 0; } /**************************/ return res; } findMaxMinSum(root); return maxSum; }; ``` ```python # by chatGPT (python) class Solution: def __init__(self): # 全局变量,记录 BST 最大节点之和 self.maxSum = 0 def maxSumBST(self, root: TreeNode) -> int: self.traverse(root) return self.maxSum def findMaxMinSum(self, root: TreeNode) -> List[int]: # base case if not root: return [1, float('inf'), float('-inf'), 0] # 递归计算左右子树 left = self.findMaxMinSum(root.left) right = self.findMaxMinSum(root.right) /*******后序遍历位置*******/ res = [0] * 4 # 这个 if 在判断以 root 为根的二叉树是不是 BST if left[0] == 1 and right[0] == 1 and root.val > left[2] and root.val < right[1]: # 以 root 为根的二叉树是 BST res[0] = 1 # 计算以 root 为根的这棵 BST 的最小值 res[1] = min(left[1], root.val) # 计算以 root 为根的这棵 BST 的最大值 res[2] = max(right[2], root.val) # 计算以 root 为根的这棵 BST 所有节点之和 res[3] = left[3] + right[3] + root.val # 更新全局变量 self.maxSum = max(self.maxSum, res[3]) else: # 以 root 为根的二叉树不是 BST res[0] = 0 # 其他的值都没必要计算了,因为用不到 /**************************/ return res ``` https://leetcode.cn/problems/maximum-sum-bst-in-binary-tree 的多语言解法👆 https://leetcode.cn/problems/merge-intervals 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: vector> merge(vector>& intervals) { vector> res; // 按区间的 start 升序排列 sort(intervals.begin(), intervals.end(), [](auto& a, auto& b){ return a[0] < b[0]; }); res.push_back(intervals[0]); for (int i = 1; i < intervals.size(); i++) { auto& curr = intervals[i]; // res 中最后一个元素的引用 auto& last = res.back(); if (curr[0] <= last[1]) { last[1] = max(last[1], curr[1]); } else { // 处理下一个待合并区间 res.push_back(curr); } } return res; } }; ``` ```go // by chatGPT (go) func merge(intervals [][]int) [][]int { res := [][]int{} // 按区间的 start 升序排列 sort.Slice(intervals, func(i, j int) bool { return intervals[i][0] < intervals[j][0] }) res = append(res, intervals[0]) for i := 1; i < len(intervals); i++ { curr := intervals[i] // res 中最后一个元素的引用 last := res[len(res) - 1] if curr[0] <= last[1] { last[1] = max(last[1], curr[1]) } else { // 处理下一个待合并区间 res = append(res, curr) } } return res } func max(x, y int) int { if x > y { return x } return y } ``` ```java // by labuladong (java) class Solution { public int[][] merge(int[][] intervals) { LinkedList res = new LinkedList<>(); // 按区间的 start 升序排列 Arrays.sort(intervals, (a, b) -> { return a[0] - b[0]; }); res.add(intervals[0]); for (int i = 1; i < intervals.length; i++) { int[] curr = intervals[i]; // res 中最后一个元素的引用 int[] last = res.getLast(); if (curr[0] <= last[1]) { last[1] = Math.max(last[1], curr[1]); } else { // 处理下一个待合并区间 res.add(curr); } } return res.toArray(new int[0][0]); } } ``` ```javascript // by chatGPT (javascript) /** * @param {number[][]} intervals * @return {number[][]} */ var merge = function(intervals) { const res = []; // 按区间的 start 升序排列 intervals.sort((a, b) => { return a[0] - b[0]; }); res.push(intervals[0]); for (let i = 1; i < intervals.length; i++) { const curr = intervals[i]; // res 中最后一个元素的引用 const last = res[res.length - 1]; if (curr[0] <= last[1]) { last[1] = Math.max(last[1], curr[1]); } else { // 处理下一个待合并区间 res.push(curr); } } return res; }; ``` ```python # by chatGPT (python) class Solution: def merge(self, intervals: List[List[int]]) -> List[List[int]]: res = [] # 按区间的 start 升序排列 intervals.sort(key=lambda a: a[0]) res.append(intervals[0]) for curr in intervals[1:]: # res 中最后一个元素的引用 last = res[-1] if curr[0] <= last[1]: last[1] = max(last[1], curr[1]) else: # 处理下一个待合并区间 res.append(curr) return res ``` https://leetcode.cn/problems/merge-intervals 的多语言解法👆 https://leetcode.cn/problems/merge-k-sorted-lists 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: ListNode* mergeKLists(vector& lists) { int size = lists.size(); // 虚拟头结点 ListNode* dummy = new ListNode(-1); ListNode* p = dummy; // 优先级队列,最小堆 priority_queue, compare > pq; // 将 k 个链表的头结点加入最小堆 for (auto head : lists) { if (head) pq.push(head); } while (!pq.empty()) { // 获取最小节点,接到结果链表中 auto node = pq.top(); pq.pop(); p->next = node; if (node->next) { pq.push(node->next); } // p 指针不断前进 p = p->next; } return dummy->next; } private: // 自定义比较函数 struct compare { bool operator() (ListNode* a, ListNode* b) { return a->val > b->val; } }; }; ``` ```go // by chatGPT (go) //Definition for singly-linked list. func mergeKLists(lists []*ListNode) *ListNode { if len(lists) == 0 { return nil } // 虚拟头节点 dummy := &ListNode{Val: -1} p := dummy // 优先队列,最小堆, 用golang的heap pq := make(Queue, len(lists)) for i, head := range lists { if head != nil { pq[i] = head } } heap.Init(&pq) for pq.Len() != 0 { // 获取最小节点,接到结果链表中 node := heap.Pop(&pq).(*ListNode) p.Next = node if node.Next != nil { heap.Push(&pq, node.Next) } // p 指针不断前进 p = p.Next } return dummy.Next } // golang的堆排序Queue type Queue []*ListNode func (q Queue) Len() int { return len(q) } func (q Queue) Less(i, j int) bool { return q[i].Val < q[j].Val } func (q Queue) Swap(i, j int) { q[i], q[j] = q[j], q[i] } func (q *Queue) Push(x interface{}) { *q = append(*q, x.(*ListNode)) } func (q *Queue) Pop() interface{} { old := *q n := len(old) x := old[n-1] *q = old[:n-1] return x } ``` ```java // by labuladong (java) class Solution { public ListNode mergeKLists(ListNode[] lists) { if (lists.length == 0) return null; // 虚拟头结点 ListNode dummy = new ListNode(-1); ListNode p = dummy; // 优先级队列,最小堆 PriorityQueue pq = new PriorityQueue<>( lists.length, (a, b)->(a.val - b.val)); // 将 k 个链表的头结点加入最小堆 for (ListNode head : lists) { if (head != null) pq.add(head); } while (!pq.isEmpty()) { // 获取最小节点,接到结果链表中 ListNode node = pq.poll(); p.next = node; if (node.next != null) { pq.add(node.next); } // p 指针不断前进 p = p.next; } return dummy.next; } } ``` ```javascript // by chatGPT (javascript) /** * @param {ListNode[]} lists * @return {ListNode} */ var mergeKLists = function(lists) { if (lists.length == 0) return null; // 虚拟头结点 let dummy = new ListNode(-1); let p = dummy; // 优先级队列,最小堆 let pq = new PriorityQueue({ compare: (a, b) => (a.val - b.val) }); // 将 k 个链表的头结点加入最小堆 for (let head of lists) { if (head != null) pq.enqueue(head); } while (!pq.isEmpty()) { // 获取最小节点,接到结果链表中 let node = pq.dequeue(); p.next = node; if (node.next != null) { pq.enqueue(node.next); } // p 指针不断前进 p = p.next; } return dummy.next; }; ``` ```python # by chatGPT (python) class Solution: def mergeKLists(self, lists: List[ListNode]) -> ListNode: if not lists: return None # 虚拟头结点 dummy = ListNode(-1) p = dummy # 优先级队列,最小堆 pq = [] for head in lists: if head: heapq.heappush(pq, (head.val, id(head), head)) # 将 k 个链表的头结点加入最小堆 while pq: # 获取最小节点,接到结果链表中 node = heapq.heappop(pq)[2] p.next = node if node.next: heapq.heappush(pq, (node.next.val, id(node.next), node.next)) # p 指针不断前进 p = p.next return dummy.next ``` https://leetcode.cn/problems/merge-k-sorted-lists 的多语言解法👆 https://leetcode.cn/problems/merge-sorted-array 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: void merge(vector& nums1, int m, vector& nums2, int n) { // 两个指针分别初始化在两个数组的最后一个元素(类似拉链两端的锯齿) int i = m - 1, j = n - 1; // 生成排序的结果(类似拉链的拉锁) int p = nums1.size() - 1; // 从后向前生成结果数组,类似合并两个有序链表的逻辑 while (i >= 0 && j >= 0) { if (nums1[i] > nums2[j]) { nums1[p] = nums1[i]; i--; } else { nums1[p] = nums2[j]; j--; } p--; } // 可能其中一个数组的指针走到尽头了,而另一个还没走完 // 因为我们本身就是在往 nums1 中放元素,所以只需考虑 nums2 是否剩元素即可 while (j >= 0) { nums1[p] = nums2[j]; j--; p--; } } }; ``` ```go // by chatGPT (go) func merge(nums1 []int, m int, nums2 []int, n int) { i, j, p := m-1, n-1, len(nums1)-1 // 初始化指针 for i >= 0 && j >= 0 { // 两个数组都未遍历完时进行比较 if nums1[i] > nums2[j] { // 挑选大的元素放入 nums1 的末位 nums1[p] = nums1[i] i-- } else { nums1[p] = nums2[j] j-- } p-- // 从后往前生成结果 } for j >= 0 { // nums2 剩余元素放入 nums1 nums1[p] = nums2[j] j-- p-- } } ``` ```java // by labuladong (java) 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--; } // 可能其中一个数组的指针走到尽头了,而另一个还没走完 // 因为我们本身就是在往 nums1 中放元素,所以只需考虑 nums2 是否剩元素即可 while (j >= 0) { nums1[p] = nums2[j]; j--; p--; } } } ``` ```javascript // by chatGPT (javascript) var merge = function(nums1, m, nums2, n) { // 两个指针分别初始化在两个数组的最后一个元素(类似拉链两端的锯齿) var i = m - 1, j = n - 1; // 生成排序的结果(类似拉链的拉锁) var 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--; } // 可能其中一个数组的指针走到尽头了,而另一个还没走完 // 因为我们本身就是在往 nums1 中放元素,所以只需考虑 nums2 是否剩元素即可 while (j >= 0) { nums1[p] = nums2[j]; j--; p--; } }; ``` ```python # by chatGPT (python) class Solution: def merge(self, nums1: List[int], m: int, nums2: List[int], n: int) -> None: """ Do not return anything, modify nums1 in-place instead. """ # 两个指针分别初始化在两个数组的最后一个元素(类似拉链两端的锯齿) i, j = m - 1, n - 1 # 生成排序的结果(类似拉链的拉锁) p = len(nums1) - 1 # 从后向前生成结果数组,类似合并两个有序链表的逻辑 while i >= 0 and j >= 0: if nums1[i] > nums2[j]: nums1[p] = nums1[i] i -= 1 else: nums1[p] = nums2[j] j -= 1 p -= 1 # 可能其中一个数组的指针走到尽头了,而另一个还没走完 # 因为我们本身就是在往 nums1 中放元素,所以只需考虑 nums2 是否剩元素即可 while j >= 0: nums1[p] = nums2[j] j -= 1 p -= 1 ``` https://leetcode.cn/problems/merge-sorted-array 的多语言解法👆 https://leetcode.cn/problems/merge-two-sorted-lists 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) { // 虚拟头结点 ListNode* dummy = new ListNode(-1), *p = dummy; ListNode* p1 = l1, *p2 = l2; while (p1 != nullptr && p2 != nullptr) { /** ![](../pictures/链表技巧/1.gif) */ // 比较 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 != nullptr) { p->next = p1; } if (p2 != nullptr) { p->next = p2; } return dummy->next; } }; ``` ```go // by chatGPT (go) /** * Definition for singly-linked list. * type ListNode struct { * Val int * Next *ListNode * } */ func mergeTwoLists(l1 *ListNode, l2 *ListNode) *ListNode { // 虚拟头结点 dummy := &ListNode{-1, nil} p := dummy p1 := l1 p2 := l2 for p1 != nil && p2 != nil { /** ![](../pictures/链表技巧/1.gif) */ // 比较 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 != nil { p.Next = p1 } if p2 != nil { p.Next = p2 } return dummy.Next } ``` ```java // by labuladong (java) 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) { /** ![](../pictures/链表技巧/1.gif) */ // 比较 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; } } ``` ```javascript // by chatGPT (javascript) /** * @param {ListNode} l1 * @param {ListNode} l2 * @return {ListNode} */ var mergeTwoLists = function(l1, l2) { // 虚拟头结点 var dummy = new ListNode(-1), p = dummy; var p1 = l1, p2 = l2; while (p1 !== null && p2 !== null) { /** ![](../pictures/链表技巧/1.gif) */ // 比较 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; }; ``` ```python # by chatGPT (python) class Solution: def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode: # 虚拟头结点 dummy = ListNode(-1) p = dummy p1 = l1 p2 = l2 while p1 and p2: """ ![](../pictures/链表技巧/1.gif) """ # 比较 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: p.next = p1 if p2: p.next = p2 return dummy.next ``` https://leetcode.cn/problems/merge-two-sorted-lists 的多语言解法👆 https://leetcode.cn/problems/middle-of-the-linked-list 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: ListNode* middleNode(ListNode* head) { // 快慢指针初始化指向 head ListNode* slow = head; ListNode* fast = head; // 快指针走到末尾时停止 while (fast != nullptr && fast->next != nullptr) { // 慢指针走一步,快指针走两步 slow = slow->next; fast = fast->next->next; } // 慢指针指向中点 return slow; } }; ``` ```go // by chatGPT (go) func middleNode(head *ListNode) *ListNode { // 快慢指针初始化指向 head slow, fast := head, head // 快指针走到末尾时停止 for fast != nil && fast.Next != nil { // 慢指针走一步,快指针走两步 slow = slow.Next fast = fast.Next.Next } // 慢指针指向中点 return slow } ``` ```java // by labuladong (java) class Solution { public ListNode middleNode(ListNode head) { // 快慢指针初始化指向 head ListNode slow = head, fast = head; // 快指针走到末尾时停止 while (fast != null && fast.next != null) { // 慢指针走一步,快指针走两步 slow = slow.next; fast = fast.next.next; } // 慢指针指向中点 return slow; } } ``` ```javascript // by chatGPT (javascript) var middleNode = function(head) { // 快慢指针初始化指向 head let slow = head, fast = head; // 快指针走到末尾时停止 while (fast != null && fast.next != null) { // 慢指针走一步,快指针走两步 slow = slow.next; fast = fast.next.next; } // 慢指针指向中点 return slow; }; ``` ```python # by chatGPT (python) class Solution: def middleNode(self, head: ListNode) -> ListNode: # 快慢指针初始化指向 head slow = fast = head # 快指针走到末尾时停止 while fast and fast.next: # 慢指针走一步,快指针走两步 slow = slow.next fast = fast.next.next # 慢指针指向中点 return slow ``` https://leetcode.cn/problems/middle-of-the-linked-list 的多语言解法👆 https://leetcode.cn/problems/min-cost-to-connect-all-points 的多语言解法👇 ```cpp // by chatGPT (cpp) class UF { public: // 连通分量个数 int count; // 存储一棵树 vector parent; // 记录树的「重量」 vector size; // n 为图中节点的个数 UF(int n) { this->count = n; parent.resize(n); size.resize(n); for (int i = 0; i < n; i++) { parent[i] = i; size[i] = 1; } } // 将节点 p 和节点 q 连通 void unionn(int p, int q) { int rootP = find(p); int rootQ = find(q); if (rootP == rootQ) return; // 小树接到大树下面,较平衡 if (size[rootP] > size[rootQ]) { parent[rootQ] = rootP; size[rootP] += size[rootQ]; } else { parent[rootP] = rootQ; size[rootQ] += size[rootP]; } // 两个连通分量合并成一个连通分量 count--; } // 判断节点 p 和节点 q 是否连通 bool connected(int p, int q) { int rootP = find(p); int rootQ = find(q); return rootP == rootQ; } // 返回节点 x 的连通分量根节点 int find(int x) { while (parent[x] != x) { // 进行路径压缩 parent[x] = parent[parent[x]]; x = parent[x]; } return x; } // 返回图中的连通分量个数 int getCount() { return this->count; } }; class Solution { public: int minCostConnectPoints(vector>& points) { int n = points.size(); // 生成所有边及权重 vector> edges; for (int i = 0; i < n; i++) { for (int j = i + 1; j < n; j++) { int xi = points[i][0], yi = points[i][1]; int xj = points[j][0], yj = points[j][1]; // 用坐标点在 points 中的索引表示坐标点 edges.push_back({i, j, abs(xi - xj) + abs(yi - yj)}); } } // 将边按照权重从小到大排序 sort(edges.begin(), edges.end(), [](auto& a, auto& b){ return a[2] < b[2]; }); // 执行 Kruskal 算法 int mst = 0; UF uf(n); for (auto& edge : edges) { int u = edge[0]; int v = edge[1]; int weight = edge[2]; // 若这条边会产生环,则不能加入 mst if (uf.connected(u, v)) { continue; } // 若这条边不会产生环,则属于最小生成树 mst += weight; uf.unionn(u, v); } return mst; } }; ``` ```go // by chatGPT (go) func minCostConnectPoints(points [][]int) int { n := len(points) // 生成所有边及权重 edges := make([][]int, 0) for i := 0; i < n; i++ { for j := i + 1; j < n; j++ { xi, yi := points[i][0], points[i][1] xj, yj := points[j][0], points[j][1] // 用坐标点在 points 中的索引表示坐标点 edges = append(edges, []int{i, j, abs(xi - xj) + abs(yi - yj)}) } } // 将边按照权重从小到大排序 sort.Slice(edges, func(i, j int) bool { return edges[i][2] < edges[j][2] }) // 执行 Kruskal 算法 mst := 0 uf := NewUF(n) for _, edge := range edges { u, v, weight := edge[0], edge[1], edge[2] // 若这条边会产生环,则不能加入 mst if uf.connected(u, v) { continue } // 若这条边不会产生环,则属于最小生成树 mst += weight uf.union(u, v) } return mst } func abs(x int) int { if x < 0 { return -x } return x } type UF struct { // 连通分量个数 count int // 存储一棵树 parent []int // 记录树的「重量」 size []int } // NewUF returns a new UF with n nodes func NewUF(n int) *UF { uf := &UF{} uf.count = n uf.parent = make([]int, n) uf.size = make([]int, n) for i := 0; i < n; i++ { uf.parent[i] = i uf.size[i] = 1 } return uf } func (uf *UF) union(p, q int) { rootP := uf.find(p) rootQ := uf.find(q) if rootP == rootQ { return } // 小树接到大树下面,较平衡 if uf.size[rootP] > uf.size[rootQ] { uf.parent[rootQ] = rootP uf.size[rootP] += uf.size[rootQ] } else { uf.parent[rootP] = rootQ uf.size[rootQ] += uf.size[rootP] } // 两个连通分量合并成一个连通分量 uf.count-- } func (uf *UF) connected(p, q int) bool { rootP := uf.find(p) rootQ := uf.find(q) return rootP == rootQ } func (uf *UF) find(x int) int { for uf.parent[x] != x { // 进行路径压缩 uf.parent[x], x = uf.parent[uf.parent[x]], uf.parent[uf.parent[x]] } return x } func (uf *UF) Count() int { return uf.count } ``` ```java // by labuladong (java) class Solution { public int minCostConnectPoints(int[][] points) { int n = points.length; // 生成所有边及权重 List edges = new ArrayList<>(); for (int i = 0; i < n; i++) { for (int j = i + 1; j < n; j++) { int xi = points[i][0], yi = points[i][1]; int xj = points[j][0], yj = points[j][1]; // 用坐标点在 points 中的索引表示坐标点 edges.add(new int[]{ i, j, Math.abs(xi - xj) + Math.abs(yi - yj) }); } } // 将边按照权重从小到大排序 Collections.sort(edges, (a, b) -> { return a[2] - b[2]; }); // 执行 Kruskal 算法 int mst = 0; UF uf = new UF(n); for (int[] edge : edges) { int u = edge[0]; int v = edge[1]; int weight = edge[2]; // 若这条边会产生环,则不能加入 mst if (uf.connected(u, v)) { continue; } // 若这条边不会产生环,则属于最小生成树 mst += weight; uf.union(u, v); } return mst; } class UF { // 连通分量个数 private int count; // 存储一棵树 private int[] parent; // 记录树的「重量」 private int[] size; // n 为图中节点的个数 public UF(int n) { this.count = n; parent = new int[n]; size = new int[n]; for (int i = 0; i < n; i++) { parent[i] = i; size[i] = 1; } } // 将节点 p 和节点 q 连通 public void union(int p, int q) { int rootP = find(p); int rootQ = find(q); if (rootP == rootQ) return; // 小树接到大树下面,较平衡 if (size[rootP] > size[rootQ]) { parent[rootQ] = rootP; size[rootP] += size[rootQ]; } else { parent[rootP] = rootQ; size[rootQ] += size[rootP]; } // 两个连通分量合并成一个连通分量 count--; } // 判断节点 p 和节点 q 是否连通 public boolean connected(int p, int q) { int rootP = find(p); int rootQ = find(q); return rootP == rootQ; } // 返回节点 x 的连通分量根节点 private int find(int x) { while (parent[x] != x) { // 进行路径压缩 parent[x] = parent[parent[x]]; x = parent[x]; } return x; } // 返回图中的连通分量个数 public int count() { return count; } } } ``` ```javascript // by chatGPT (javascript) var minCostConnectPoints = function(points) { const n = points.length; // 生成所有边及权重 let edges = []; for (let i = 0; i < n; i++) { for (let j = i + 1; j < n; j++) { const xi = points[i][0], yi = points[i][1]; const xj = points[j][0], yj = points[j][1]; // 用坐标点在 points 中的索引表示坐标点 edges.push([i, j, Math.abs(xi - xj) + Math.abs(yi - yj)]); } } // 将边按照权重从小到大排序 edges.sort((a, b) => { return a[2] - b[2]; }); // 执行 Kruskal 算法 let mst = 0; const uf = new UF(n); for (let i = 0; i < edges.length; i++) { const u = edges[i][0]; const v = edges[i][1]; const weight = edges[i][2]; // 若这条边会产生环,则不能加入 mst if (uf.connected(u, v)) { continue; } // 若这条边不会产生环,则属于最小生成树 mst += weight; uf.union(u, v); } return mst; }; class UF { // 连通分量个数 count = 0; // 存储一棵树 parent = []; // 记录树的「重量」 size = []; // n 为图中节点的个数 constructor(n) { this.count = n; this.parent = new Array(n).fill(0).map((_, index) => index); this.size = new Array(n).fill(1); } // 将节点 p 和节点 q 连通 union(p, q) { let rootP = this.find(p); let rootQ = this.find(q); if (rootP == rootQ) return; // 小树接到大树下面,较平衡 if (this.size[rootP] > this.size[rootQ]) { this.parent[rootQ] = rootP; this.size[rootP] += this.size[rootQ]; } else { this.parent[rootP] = rootQ; this.size[rootQ] += this.size[rootP]; } // 两个连通分量合并成一个连通分量 this.count--; } // 判断节点 p 和节点 q 是否连通 connected(p, q) { let rootP = this.find(p); let rootQ = this.find(q); return rootP == rootQ; } // 返回节点 x 的连通分量根节点 find(x) { while (this.parent[x] != x) { // 进行路径压缩 this.parent[x] = this.parent[this.parent[x]]; x = this.parent[x]; } return x; } // 返回图中的连通分量个数 count() { return this.count; } } ``` ```python # by chatGPT (python) class Solution: def minCostConnectPoints(self, points: List[List[int]]) -> int: n = len(points) # 生成所有边及权重 edges = [] for i in range(n): for j in range(i + 1, n): xi, yi = points[i][0], points[i][1] xj, yj = points[j][0], points[j][1] # 用坐标点在 points 中的索引表示坐标点 edges.append([i, j, abs(xi - xj) + abs(yi - yj)]) # 将边按照权重从小到大排序 edges.sort(key=lambda x: x[2]) # 执行 Kruskal 算法 mst = 0 uf = UF(n) for edge in edges: u = edge[0] v = edge[1] weight = edge[2] # 若这条边会产生环,则不能加入 mst if uf.connected(u, v): continue # 若这条边不会产生环,则属于最小生成树 mst += weight uf.union(u, v) return mst class UF: # 连通分量个数 count = 0 # 存储一棵树 parent = [] # 记录树的「重量」 size = [] def __init__(self, n: int): # n 为图中节点的个数 self.count = n self.parent = [i for i in range(n)] self.size = [1] * n # 将节点 p 和节点 q 连通 def union(self, p: int, q: int) -> None: root_p = self.find(p) root_q = self.find(q) if root_p == root_q: return # 小树接到大树下面,较平衡 if self.size[root_p] > self.size[root_q]: self.parent[root_q] = root_p self.size[root_p] += self.size[root_q] else: self.parent[root_p] = root_q self.size[root_q] += self.size[root_p] # 两个连通分量合并成一个连通分量 self.count -= 1 # 判断节点 p 和节点 q 是否连通 def connected(self, p: int, q: int) -> bool: root_p = self.find(p) root_q = self.find(q) return root_p == root_q # 返回节点 x 的连通分量根节点 def find(self, x: int) -> int: while self.parent[x] != x: # 进行路径压缩 self.parent[x] = self.parent[self.parent[x]] x = self.parent[x] return x # 返回图中的连通分量个数 def count(self) -> int: return self.count ``` https://leetcode.cn/problems/min-cost-to-connect-all-points 的多语言解法👆 https://leetcode.cn/problems/min-stack 的多语言解法👇 ```cpp // by chatGPT (cpp) class MinStack { // 记录栈中的所有元素 stack stk; // 阶段性记录栈中的最小元素 stack minStk; public: void push(int val) { stk.push(val); // 维护 minStk 栈顶为全栈最小元素 if (minStk.empty() || val <= minStk.top()) { // 新插入的这个元素就是全栈最小的 minStk.push(val); } } void pop() { // 弹出的元素是全栈最小的 if (stk.top() == minStk.top()) { minStk.pop(); } stk.pop(); } int top() { return stk.top(); } int getMin() { // minStk 栈顶为全栈最小元素 return minStk.top(); } }; ``` ```go // by chatGPT (go) // 原始思路 type MinStack1 struct { // 记录栈中的所有元素 stk []int // 阶段性记录栈中的最小元素 minStk []int } /** initialize your data structure here. */ func Constructor1() MinStack1 { return MinStack1{} } func (this *MinStack1) Push(val int) { this.stk = append(this.stk, val) // 维护 minStk 栈顶为全栈最小元素 if len(this.minStk) == 0 || val <= this.minStk[len(this.minStk)-1] { // 新插入的这个元素就是全栈最小的 this.minStk = append(this.minStk, val) } else { // 插入的这个元素比较大 this.minStk = append(this.minStk, this.minStk[len(this.minStk)-1]) } } func (this *MinStack1) Pop() { this.stk = this.stk[:len(this.stk)-1] this.minStk = this.minStk[:len(this.minStk)-1] } func (this *MinStack1) Top() int { return this.stk[len(this.stk)-1] } func (this *MinStack1) GetMin() int { // minStk 栈顶为全栈最小元素 return this.minStk[len(this.minStk)-1] } // 优化版 type MinStack struct { // 记录栈中的所有元素 stk []int // 阶段性记录栈中的最小元素 minStk []int } /** initialize your data structure here. */ func Constructor() MinStack { return MinStack{} } func (this *MinStack) Push(val int) { this.stk = append(this.stk, val) // 维护 minStk 栈顶为全栈最小元素 if len(this.minStk) == 0 || val <= this.minStk[len(this.minStk)-1] { // 新插入的这个元素就是全栈最小的 this.minStk = append(this.minStk, val) } } func (this *MinStack) Pop() { // 注意 Go 语言的语言特性,比较 int 相等直接用 == if this.stk[len(this.stk)-1] == this.minStk[len(this.minStk)-1] { // 弹出的元素是全栈最小的 this.minStk = this.minStk[:len(this.minStk)-1] } this.stk = this.stk[:len(this.stk)-1] } func (this *MinStack) Top() int { return this.stk[len(this.stk)-1] } func (this *MinStack) GetMin() int { // minStk 栈顶为全栈最小元素 return this.minStk[len(this.minStk)-1] } ``` ```java // by labuladong (java) // 原始思路 class MinStack1 { // 记录栈中的所有元素 Stack stk = new Stack<>(); // 阶段性记录栈中的最小元素 Stack minStk = new Stack<>(); public void push(int val) { stk.push(val); // 维护 minStk 栈顶为全栈最小元素 if (minStk.isEmpty() || val <= minStk.peek()) { // 新插入的这个元素就是全栈最小的 minStk.push(val); } else { // 插入的这个元素比较大 minStk.push(minStk.peek()); } } public void pop() { stk.pop(); minStk.pop(); } public int top() { return stk.peek(); } public int getMin() { // minStk 栈顶为全栈最小元素 return minStk.peek(); } } // 优化版 class MinStack { // 记录栈中的所有元素 Stack stk = new Stack<>(); // 阶段性记录栈中的最小元素 Stack minStk = new Stack<>(); public void push(int val) { stk.push(val); // 维护 minStk 栈顶为全栈最小元素 if (minStk.isEmpty() || val <= minStk.peek()) { // 新插入的这个元素就是全栈最小的 minStk.push(val); } } public void pop() { // 注意 Java 的语言特性,比较 Integer 相等要用 equals 方法 if (stk.peek().equals(minStk.peek())) { // 弹出的元素是全栈最小的 minStk.pop(); } stk.pop(); } public int top() { return stk.peek(); } public int getMin() { // minStk 栈顶为全栈最小元素 return minStk.peek(); } } ``` ```javascript // by chatGPT (javascript) /** * initialize your data structure here. */ var MinStack = function() { // 记录栈中的所有元素 this.stk = []; // 阶段性记录栈中的最小元素 this.minStk = []; }; /** * @param {number} val * @return {void} */ MinStack.prototype.push = function(val) { this.stk.push(val); // 维护 minStk 栈顶为全栈最小元素 if (this.minStk.length == 0 || val <= this.minStk[this.minStk.length - 1]) { // 新插入的这个元素就是全栈最小的 this.minStk.push(val); } }; /** * @return {void} */ MinStack.prototype.pop = function() { // 注意 JavaScript 的语言特性,比较 Number 相等不需要用 equals 方法 if (this.stk[this.stk.length - 1] == this.minStk[this.minStk.length - 1]) { // 弹出的元素是全栈最小的 this.minStk.pop(); } this.stk.pop(); }; /** * @return {number} */ MinStack.prototype.top = function() { return this.stk[this.stk.length - 1]; }; /** * @return {number} */ MinStack.prototype.getMin = function() { // minStk 栈顶为全栈最小元素 return this.minStk[this.minStk.length - 1]; }; ``` ```python # by chatGPT (python) class MinStack1: # 记录栈中的所有元素 def __init__(self): self.stk = [] # 阶段性记录栈中的最小元素 self.minStk = [] def push(self, val): self.stk.append(val) # 维护 minStk 栈顶为全栈最小元素 if not self.minStk or val <= self.minStk[-1]: # 新插入的这个元素就是全栈最小的 self.minStk.append(val) else: # 插入的这个元素比较大 self.minStk.append(self.minStk[-1]) def pop(self): self.stk.pop() self.minStk.pop() def top(self): return self.stk[-1] def getMin(self): # minStk 栈顶为全栈最小元素 return self.minStk[-1] # 优化版 class MinStack: # 记录栈中的所有元素 def __init__(self): self.stk = [] # 阶段性记录栈中的最小元素 self.minStk = [] def push(self, val): self.stk.append(val) # 维护 minStk 栈顶为全栈最小元素 if not self.minStk or val <= self.minStk[-1]: # 新插入的这个元素就是全栈最小的 self.minStk.append(val) def pop(self): # 注意 Python 语言相等比较可以使用 "==" 操作符 if self.stk[-1] == self.minStk[-1]: # 弹出的元素是全栈最小的 self.minStk.pop() self.stk.pop() def top(self): return self.stk[-1] def getMin(self): # minStk 栈顶为全栈最小元素 return self.minStk[-1] ``` https://leetcode.cn/problems/min-stack 的多语言解法👆 https://leetcode.cn/problems/minimum-absolute-difference-in-bst 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int getMinimumDifference(TreeNode* root) { traverse(root); return res; } TreeNode* prev = nullptr; int res = INT_MAX; // 遍历函数 void traverse(TreeNode* root) { if (root == nullptr) { return; } traverse(root->left); // 中序遍历位置 if (prev != nullptr) { res = min(res, root->val - prev->val); } prev = root; traverse(root->right); } }; ``` ```go // by chatGPT (go) /** * Definition for a binary tree node. * type TreeNode struct { * Val int * Left *TreeNode * Right *TreeNode * } */ func getMinimumDifference(root *TreeNode) int { prev := (*TreeNode)(nil) res := math.MaxInt32 var traverse func(root *TreeNode) traverse = func(root *TreeNode) { if root == nil { return } traverse(root.Left) // 中序遍历位置 if prev != nil { res = min(res, root.Val - prev.Val) } prev = root traverse(root.Right) } traverse(root) return res } func min(x, y int) int { if x < y { return x } return y } ``` ```java // by labuladong (java) class Solution { public int getMinimumDifference(TreeNode root) { traverse(root); return res; } TreeNode prev = null; int res = Integer.MAX_VALUE; // 遍历函数 void traverse(TreeNode root) { if (root == null) { return; } traverse(root.left); // 中序遍历位置 if (prev != null) { res = Math.min(res, root.val - prev.val); } prev = root; traverse(root.right); } } ``` ```javascript // by chatGPT (javascript) /** * @param {TreeNode} root * @return {number} */ var getMinimumDifference = function(root) { let prev = null; let res = Number.MAX_SAFE_INTEGER; // 遍历函数 function traverse(root) { if (root == null) { return; } traverse(root.left); // 中序遍历位置 if (prev != null) { res = Math.min(res, root.val - prev.val); } prev = root; traverse(root.right); } traverse(root); return res; } ``` ```python # by chatGPT (python) class Solution: def getMinimumDifference(self, root: TreeNode) -> int: self.prev = None self.res = float('inf') self.traverse(root) return self.res # 遍历函数 def traverse(self, root: TreeNode) -> None: if not root: return self.traverse(root.left) # 中序遍历位置 if self.prev: self.res = min(self.res, root.val - self.prev.val) self.prev = root self.traverse(root.right) ``` https://leetcode.cn/problems/minimum-absolute-difference-in-bst 的多语言解法👆 https://leetcode.cn/problems/minimum-add-to-make-parentheses-valid 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int minAddToMakeValid(string s) { // res 记录插入次数 int res = 0; // need 变量记录右括号的需求量 int need = 0; for (int i = 0; i < s.length(); i++) { if (s[i] == '(') { // 对右括号的需求 + 1 need++; } if (s[i] == ')') { // 对右括号的需求 - 1 need--; if (need == -1) { need = 0; // 需插入一个左括号 res++; } } } return res + need; } }; ``` ```go // by chatGPT (go) func minAddToMakeValid(s string) int { // res 记录插入次数 res := 0 // need 变量记录右括号的需求量 need := 0 for i := 0; i < len(s); i++ { if s[i] == '(' { // 对右括号的需求 + 1 need++ } if s[i] == ')' { // 对右括号的需求 - 1 need-- if need == -1 { need = 0 // 需插入一个左括号 res++ } } } return res + need } ``` ```java // by labuladong (java) class Solution { public int minAddToMakeValid(String s) { // res 记录插入次数 int res = 0; // need 变量记录右括号的需求量 int need = 0; for (int i = 0; i < s.length(); i++) { if (s.charAt(i) == '(') { // 对右括号的需求 + 1 need++; } if (s.charAt(i) == ')') { // 对右括号的需求 - 1 need--; if (need == -1) { need = 0; // 需插入一个左括号 res++; } } } return res + need; } } ``` ```javascript // by chatGPT (javascript) var minAddToMakeValid = function(s) { // res 记录插入次数 let res = 0; // need 变量记录右括号的需求量 let need = 0; for (let i = 0; i < s.length; i++) { if (s.charAt(i) == '(') { // 对右括号的需求 + 1 need++; } if (s.charAt(i) == ')') { // 对右括号的需求 - 1 need--; if (need == -1) { need = 0; // 需插入一个左括号 res++; } } } return res + need; }; ``` ```python # by chatGPT (python) class Solution: def minAddToMakeValid(self, s: str) -> int: # res 记录插入次数 res = 0 # need 变量记录右括号的需求量 need = 0 for i in range(len(s)): if s[i] == '(': # 对右括号的需求 + 1 need += 1 if s[i] == ')': # 对右括号的需求 - 1 need -= 1 if need == -1: need = 0 # 需插入一个左括号 res += 1 return res + need ``` https://leetcode.cn/problems/minimum-add-to-make-parentheses-valid 的多语言解法👆 https://leetcode.cn/problems/minimum-ascii-delete-sum-for-two-strings 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: // 备忘录 int memo[1001][1001]; /* 主函数 */ int minimumDeleteSum(string s1, string s2) { int m = s1.size(), n = s2.size(); // 备忘录值为 -1 代表未曾计算 memset(memo, -1, sizeof(memo)); return dp(s1, 0, s2, 0); } // 定义:将 s1[i..] 和 s2[j..] 删除成相同字符串, // 最小的 ASCII 码之和为 dp(s1, i, s2, j)。 int dp(string s1, int i, string s2, int j) { int res = 0; // base case if (i == s1.size()) { // 如果 s1 到头了,那么 s2 剩下的都得删除 for (; j < s2.size(); j++) res += s2[j]; return res; } if (j == s2.size()) { // 如果 s2 到头了,那么 s1 剩下的都得删除 for (; i < s1.size(); i++) res += s1[i]; return res; } if (memo[i][j] != -1) { return memo[i][j]; } if (s1[i] == s2[j]) { // s1[i] 和 s2[j] 都是在 lcs 中的,不用删除 memo[i][j] = dp(s1, i + 1, s2, j + 1); } else { // s1[i] 和 s2[j] 至少有一个不在 lcs 中,删一个 memo[i][j] = min( s1[i] + dp(s1, i + 1, s2, j), s2[j] + dp(s1, i, s2, j + 1) ); } return memo[i][j]; } }; ``` ```go // by chatGPT (go) func minimumDeleteSum(s1 string, s2 string) int { m, n := len(s1), len(s2) // 备忘录值为 -1 代表未曾计算 memo := make([][]int, m) for i := range memo { memo[i] = make([]int, n) for j := range memo[i] { memo[i][j] = -1 } } var dp func(s1 string, i int, s2 string, j int) int // 定义:将 s1[i..] 和 s2[j..] 删除成相同字符串, // 最小的 ASCII 码之和为 dp(s1, i, s2, j)。 dp = func(s1 string, i int, s2 string, j int) int { res := 0 // base case if i == len(s1) { // 如果 s1 到头了,那么 s2 剩下的都得删除 for ; j < len(s2); j++ { res += int(s2[j]) } return res } if j == len(s2) { // 如果 s2 到头了,那么 s1 剩下的都得删除 for ; i < len(s1); i++ { res += int(s1[i]) } return res } if memo[i][j] != -1 { return memo[i][j] } if s1[i] == s2[j] { // s1[i] 和 s2[j] 都是在 lcs 中的,不用删除 memo[i][j] = dp(s1, i + 1, s2, j + 1) } else { // s1[i] 和 s2[j] 至少有一个不在 lcs 中,删一个 memo[i][j] = min( int(s1[i]) + dp(s1, i + 1, s2, j), int(s2[j]) + dp(s1, i, s2, j + 1), ) } return memo[i][j] } return dp(s1, 0, s2, 0) } func min(a, b int) int { if a < b { return a } return b } ``` ```java // by labuladong (java) class Solution { // 备忘录 int memo[][]; /* 主函数 */ public int minimumDeleteSum(String s1, String s2) { int m = s1.length(), n = s2.length(); // 备忘录值为 -1 代表未曾计算 memo = new int[m][n]; for (int[] row : memo) Arrays.fill(row, -1); return dp(s1, 0, s2, 0); } // 定义:将 s1[i..] 和 s2[j..] 删除成相同字符串, // 最小的 ASCII 码之和为 dp(s1, i, s2, j)。 int dp(String s1, int i, String s2, int j) { int res = 0; // base case if (i == s1.length()) { // 如果 s1 到头了,那么 s2 剩下的都得删除 for (; j < s2.length(); j++) res += s2.charAt(j); return res; } if (j == s2.length()) { // 如果 s2 到头了,那么 s1 剩下的都得删除 for (; i < s1.length(); i++) res += s1.charAt(i); return res; } if (memo[i][j] != -1) { return memo[i][j]; } if (s1.charAt(i) == s2.charAt(j)) { // s1[i] 和 s2[j] 都是在 lcs 中的,不用删除 memo[i][j] = dp(s1, i + 1, s2, j + 1); } else { // s1[i] 和 s2[j] 至少有一个不在 lcs 中,删一个 memo[i][j] = Math.min( s1.charAt(i) + dp(s1, i + 1, s2, j), s2.charAt(j) + dp(s1, i, s2, j + 1) ); } return memo[i][j]; } } ``` ```javascript // by chatGPT (javascript) var minimumDeleteSum = function(s1, s2) { var m = s1.length, n = s2.length; // memo[i][j] 存储将s1[i...]和s2[j...]删除成相同字符串的最小ASCII码之和 var memo = new Array(m); for(var i=0; i int: m, n = len(s1), len(s2) # 备忘录值为 -1 代表未曾计算 memo = [[-1] * n for _ in range(m)] def dp(i: int, j: int) -> int: res = 0 # base case if i == m: # 如果 s1 到头了,那么 s2 剩下的都得删除 for k in range(j, n): res += ord(s2[k]) return res if j == n: # 如果 s2 到头了,那么 s1 剩下的都得删除 for k in range(i, m): res += ord(s1[k]) return res if memo[i][j] != -1: return memo[i][j] if s1[i] == s2[j]: # s1[i] 和 s2[j] 都是在 lcs 中的,不用删除 memo[i][j] = dp(i + 1, j + 1) else: # s1[i] 和 s2[j] 至少有一个不在 lcs 中,删一个 memo[i][j] = min( ord(s1[i]) + dp(i + 1, j), ord(s2[j]) + dp(i, j + 1) ) return memo[i][j] return dp(0, 0) ``` https://leetcode.cn/problems/minimum-ascii-delete-sum-for-two-strings 的多语言解法👆 https://leetcode.cn/problems/minimum-depth-of-binary-tree 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int minDepth(TreeNode* root) { if (root == NULL) return 0; queue q; q.push(root); // root 本身就是一层,depth 初始化为 1 int depth = 1; while (!q.empty()) { /* 层数 step */ int sz = q.size(); /* 将当前队列中的所有节点向四周扩散 */ for (int i = 0; i < sz; i++) { TreeNode* cur = q.front(); q.pop(); /* 判断是否到达终点 */ if (cur->left == NULL && cur->right == NULL) return depth; /* 将 cur 的相邻节点加入队列 */ if (cur->left != NULL) q.push(cur->left); if (cur->right != NULL) q.push(cur->right); } /* 这里增加步数 */ depth++; } return depth; } }; ``` ```go // by chatGPT (go) /** * Definition for TreeNode. * type TreeNode struct { * Val int * Left *TreeNode * Right *TreeNode * } */ func minDepth(root *TreeNode) int { if root == nil { return 0 } q := []*TreeNode{root} // root 本身就是一层,depth 初始化为 1 depth := 1 for len(q) != 0 { /** ![](../pictures/dijkstra/1.jpeg) */ sz := len(q) /* 遍历当前层的节点 */ for i := 0; i < sz; i++ { cur := q[0] q = q[1:] /* 判断是否到达叶子结点 */ if cur.Left == nil && cur.Right == nil { return depth } /* 将下一层节点加入队列 */ if cur.Left != nil { q = append(q, cur.Left) } if cur.Right != nil { q = append(q, cur.Right) } } /* 这里增加步数 */ depth++ } return depth } ``` ```java // by labuladong (java) // 「迭代」的递归思路 class Solution { private int minDepth = Integer.MAX_VALUE; private int currentDepth = 0; public int minDepth(TreeNode root) { if (root == null) { return 0; } traverse(root); return minDepth; } private void traverse(TreeNode root) { if (root == null) { return; } // 做选择:在进入节点时增加当前深度 currentDepth++; // 如果当前节点是叶子节点,更新最小深度 if (root.left == null && root.right == null) { minDepth = Math.min(minDepth, currentDepth); } traverse(root.left); traverse(root.right); // 撤销选择:在离开节点时减少当前深度 currentDepth--; } } // 「分解问题」的递归思路 class Solution2 { public int minDepth(TreeNode root) { // 基本情况:如果节点为空,返回深度为0 if (root == null) { return 0; } // 递归计算左子树的最小深度 int leftDepth = minDepth(root.left); // 递归计算右子树的最小深度 int rightDepth = minDepth(root.right); // 特殊情况处理:如果左子树为空,返回右子树的深度加1 if (leftDepth == 0) { return rightDepth + 1; } // 特殊情况处理:如果右子树为空,返回左子树的深度加1 if (rightDepth == 0) { return leftDepth + 1; } // 计算并返回最小深度:左右子树深度的最小值加1 return Math.min(leftDepth, rightDepth) + 1; } } // BFS 的思路 class Solution3 { public int minDepth(TreeNode root) { if (root == null) return 0; Queue q = new LinkedList<>(); q.offer(root); // root 本身就是一层,depth 初始化为 1 int depth = 1; while (!q.isEmpty()) { /** ![](../pictures/dijkstra/1.jpeg) */ int sz = q.size(); /* 遍历当前层的节点 */ for (int i = 0; i < sz; i++) { TreeNode cur = q.poll(); /* 判断是否到达叶子结点 */ if (cur.left == null && cur.right == null) return depth; /* 将下一层节点加入队列 */ if (cur.left != null) q.offer(cur.left); if (cur.right != null) q.offer(cur.right); } /* 这里增加步数 */ depth++; } return depth; } } ``` ```javascript // by chatGPT (javascript) /** * @param {TreeNode} root * @return {number} */ var minDepth = function(root) { if (root === null) return 0; var q = []; q.push(root); //root本身就是一层,depth初始化为 1 var depth = 1; while (q.length !== 0) { /** ![](../pictures/dijkstra/1.jpeg) */ var sz = q.length; /* 遍历当前层的节点 */ for (var i = 0; i < sz; i++) { var cur = q.shift(); /* 判断是否到达叶子结点 */ if (!cur.left && !cur.right) { return depth; } /* 将下一层节点加入队列 */ if (cur.left !== null) { q.push(cur.left); } if (cur.right !== null) { q.push(cur.right); } } /* 这里增加步数 */ depth++; } return depth; }; ``` ```python # by chatGPT (python) class Solution: def minDepth(self, root: TreeNode) -> int: if not root: return 0 q = collections.deque([root]) # root 本身就是一层,depth 初始化为 1 depth = 1 while q: # extend down -200> # ![](../pictures/dijkstra/1.jpeg) sz = len(q) ## 遍历当前层的节点 for i in range(sz): cur = q.popleft() ## 判断是否到达叶子结点 if not cur.left and not cur.right: return depth ## 将下一层节点加入队列 if cur.left: q.append(cur.left) if cur.right: q.append(cur.right) ## 这里增加步数 depth += 1 return depth ``` https://leetcode.cn/problems/minimum-depth-of-binary-tree 的多语言解法👆 https://leetcode.cn/problems/minimum-distance-between-bst-nodes 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int minDiffInBST(TreeNode* root) { traverse(root); return res; } TreeNode* prev = nullptr; int res = INT_MAX; // 遍历函数 void traverse(TreeNode* root) { if (root == nullptr) { return; } traverse(root->left); // 中序遍历位置 if (prev != nullptr) { res = min(res, root->val - prev->val); } prev = root; traverse(root->right); } }; ``` ```go // by chatGPT (go) func minDiffInBST(root *TreeNode) int { var ( prev *TreeNode res = math.MaxInt32 // 遍历函数 traverse func(*TreeNode) ) traverse = func(root *TreeNode) { if root == nil { return } traverse(root.Left) // 中序遍历位置 if prev != nil { res = min(res, root.Val - prev.Val) } prev = root traverse(root.Right) } traverse(root) return res } func min(a, b int) int { if a < b { return a } return b } ``` ```java // by labuladong (java) class Solution { public int minDiffInBST(TreeNode root) { traverse(root); return res; } TreeNode prev = null; int res = Integer.MAX_VALUE; // 遍历函数 void traverse(TreeNode root) { if (root == null) { return; } traverse(root.left); // 中序遍历位置 if (prev != null) { res = Math.min(res, root.val - prev.val); } prev = root; traverse(root.right); } } ``` ```javascript // by chatGPT (javascript) /** * @param {TreeNode} root * @return {number} */ var minDiffInBST = function(root) { let prev = null; let res = Number.MAX_SAFE_INTEGER; // 遍历函数 const traverse = (root) => { if (!root) { return; } traverse(root.left); // 中序遍历位置 if (prev) { res = Math.min(res, root.val - prev.val); } prev = root; traverse(root.right); } traverse(root); return res; }; ``` ```python # by chatGPT (python) class Solution: def minDiffInBST(self, root: TreeNode) -> int: self.prev = None self.res = float('inf') self.traverse(root) return self.res # 遍历函数 def traverse(self, root: TreeNode) -> None: if not root: return self.traverse(root.left) # 中序遍历位置 if self.prev: self.res = min(self.res, root.val - self.prev.val) self.prev = root self.traverse(root.right) ``` https://leetcode.cn/problems/minimum-distance-between-bst-nodes 的多语言解法👆 https://leetcode.cn/problems/minimum-falling-path-sum 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int minFallingPathSum(vector>& matrix) { int n = matrix.size(); int res = INT_MAX; // 备忘录里的值初始化为 66666 vector> memo(n, vector(n, 66666)); // 终点可能在 matrix[n-1] 的任意一列 for (int j = 0; j < n; j++) { res = min(res, dp(matrix, memo, n - 1, j)); } return res; } int dp(vector>& matrix, vector>& memo, int i, int j) { // 1、索引合法性检查 if (i < 0 || j < 0 || i >= matrix.size() || j >= matrix[0].size()) { return 99999; } // 2、base case if (i == 0) { return matrix[0][j]; } // 3、查找备忘录,防止重复计算 if (memo[i][j] != 66666) { return memo[i][j]; } // 进行状态转移 memo[i][j] = matrix[i][j] + three_min( dp(matrix, memo, i - 1, j), dp(matrix, memo, i - 1, j - 1), dp(matrix, memo, i - 1, j + 1) ); return memo[i][j]; } int three_min(int a, int b, int c) { return min(a, min(b, c)); } }; ``` ```go // by chatGPT (go) import ( "fmt" "math" ) func minFallingPathSum(matrix [][]int) int { n := len(matrix) res := math.MaxInt32 // 备忘录里的值初始化为 66666 memo := make([][]int, n) for i := 0; i < n; i++ { memo[i] = make([]int, n) for j := 0; j < n; j++ { memo[i][j] = 66666 } } // 终点可能在 matrix[n-1] 的任意一列 for j := 0; j < n; j++ { res = min(res, dp(matrix, n-1, j, memo)) } return res } // 备忘录 func dp(matrix [][]int, i, j int, memo [][]int) int { // 1、索引合法性检查 if i < 0 || j < 0 || i >= len(matrix) || j >= len(matrix[0]) { return 99999 } // 2、base case if i == 0 { return matrix[0][j] } // 3、查找备忘录,防止重复计算 if memo[i][j] != 66666 { return memo[i][j] } memo[i][j] = matrix[i][j] + min( dp(matrix, i-1, j, memo), dp(matrix, i-1, j-1, memo), dp(matrix, i-1, j+1, memo), ) return memo[i][j] } // 返回三个数中的最小值 func min(a, b, c int) int { return int(math.Min(float64(a), math.Min(float64(b), float64(c)))) } ``` ```java // by labuladong (java) class Solution { public int minFallingPathSum(int[][] matrix) { int n = matrix.length; int res = Integer.MAX_VALUE; // 备忘录里的值初始化为 66666 memo = new int[n][n]; for (int i = 0; i < n; i++) { Arrays.fill(memo[i], 66666); } // 终点可能在 matrix[n-1] 的任意一列 for (int j = 0; j < n; j++) { res = Math.min(res, dp(matrix, n - 1, j)); } return res; } // 备忘录 int[][] memo; int dp(int[][] matrix, int i, int j) { // 1、索引合法性检查 if (i < 0 || j < 0 || i >= matrix.length || j >= matrix[0].length) { return 99999; } // 2、base case if (i == 0) { return matrix[0][j]; } // 3、查找备忘录,防止重复计算 if (memo[i][j] != 66666) { return memo[i][j]; } // 进行状态转移 memo[i][j] = matrix[i][j] + min( dp(matrix, i - 1, j), dp(matrix, i - 1, j - 1), dp(matrix, i - 1, j + 1) ); return memo[i][j]; } /** ![](../pictures/备忘录基础/1.jpeg) */ int min(int a, int b, int c) { return Math.min(a, Math.min(b, c)); } } ``` ```javascript // by chatGPT (javascript) var minFallingPathSum = function(matrix) { var n = matrix.length, res = Number.MAX_VALUE; // 备忘录里的值初始化为 66666 var memo = new Array(n).map(() => new Array(n).fill(66666)); // 终点可能在 matrix[n-1] 的任意一列 for (var j = 0; j < n; j++) { res = Math.min(res, dp(matrix, n - 1, j)); } return res; function dp(matrix, i, j) { // 1、索引合法性检查 if (i < 0 || j < 0 || i >= matrix.length || j >= matrix[0].length) { return 99999; } // 2、base case if (i === 0) { return matrix[0][j]; } // 3、查找备忘录,防止重复计算 if (memo[i][j] !== 66666) { return memo[i][j]; } // 进行状态转移 memo[i][j] = matrix[i][j] + min( dp(matrix, i - 1, j), dp(matrix, i - 1, j - 1), dp(matrix, i - 1, j + 1) ); return memo[i][j]; } // 求三者最小值 function min(a, b, c) { return Math.min(a, Math.min(b, c)); } }; ``` ```python # by chatGPT (python) class Solution: def minFallingPathSum(self, matrix: List[List[int]]) -> int: n = len(matrix) res = float('inf') # 备忘录里的值初始化为 66666 memo = [[66666]*n for _ in range(n)] # 终点可能在 matrix[n-1] 的任意一列 for j in range(n): res = min(res, self.dp(matrix, memo, n-1, j)) return res def dp(self, matrix, memo, i, j): # 1. 索引合法性检查 if i < 0 or j < 0 or i >= len(matrix) or j >= len(matrix[0]): return 99999 # 2. base case if i == 0: return matrix[0][j] # 3. 查找备忘录,防止重复计算 if memo[i][j] != 66666: return memo[i][j] # 进行状态转移 memo[i][j] = matrix[i][j] + min( self.dp(matrix, memo, i-1, j), self.dp(matrix, memo, i-1, j-1), self.dp(matrix, memo, i-1, j+1) ) return memo[i][j] ``` https://leetcode.cn/problems/minimum-falling-path-sum 的多语言解法👆 https://leetcode.cn/problems/minimum-height-trees 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: vector findMinHeightTrees(int n, vector>& edges) { // 1、构建邻接表 vector> graph(n); for (auto& edge : edges) { // 无向图,等同于双向图 graph[edge[0]].push_back(edge[1]); graph[edge[1]].push_back(edge[0]); } // 2、找到所有的叶子节点 vector leaves; for (int i = 0; i < n; i++) { if (graph[i].size() == 1) { leaves.push_back(i); } } // 3、不断删除叶子节点,直到剩下的节点数小于等于 2 个 int remainNodeNum = n; while (remainNodeNum > 2) { // 删除当前叶子节点,计算新的叶子节点 remainNodeNum -= leaves.size(); vector newLeaves; for (auto leaf : leaves) { // 将被删除的叶子节点的邻接节点的度减 1 int neighbor = graph[leaf][0]; graph[neighbor].erase(find(graph[neighbor].begin(), graph[neighbor].end(), leaf)); // 如果邻接节点的度为 1,说明它也变成了叶子节点 if (graph[neighbor].size() == 1) { newLeaves.push_back(neighbor); } } leaves = newLeaves; } // 最后剩下的节点就是根节点 return leaves; } }; ``` ```go // by chatGPT (go) func findMinHeightTrees(n int, edges [][]int) []int { // 1、构建邻接表 graph := make([][]int, n) for i := 0; i < n; i++ { graph[i] = []int{} } for _, edge := range edges { // 无向图,等同于双向图 graph[edge[0]] = append(graph[edge[0]], edge[1]) graph[edge[1]] = append(graph[edge[1]], edge[0]) } // 2、找到所有的叶子节点 leaves := []int{} for i := 0; i < n; i++ { if len(graph[i]) == 1 { leaves = append(leaves, i) } } // 3、不断删除叶子节点,直到剩下的节点数小于等于 2 个 remainNodeNum := n for remainNodeNum > 2 { // 删除当前叶子节点,计算新的叶子节点 remainNodeNum -= len(leaves) newLeaves := []int{} for _, leaf := range leaves { // 将被删除的叶子节点的邻接节点的度减 1 neighbor := graph[leaf][0] graph[neighbor] = removeElement(graph[neighbor], leaf) // 如果邻接节点的度为 1,说明它也变成了叶子节点 if len(graph[neighbor]) == 1 { newLeaves = append(newLeaves, neighbor) } } leaves = newLeaves } // 最后剩下的节点就是根节点 return leaves } // 删除切片中的元素 func removeElement(slice []int, elem int) []int { index := -1 for i, v := range slice { if v == elem { index = i break } } if index == -1 { return slice } return append(slice[:index], slice[index+1:]...) } ``` ```java // by labuladong (java) class Solution { public List findMinHeightTrees(int n, int[][] edges) { // 1、构建邻接表 List> graph = new ArrayList<>(); for (int i = 0; i < n; i++) { graph.add(new ArrayList()); } for (int[] edge : edges) { // 无向图,等同于双向图 graph.get(edge[0]).add(edge[1]); graph.get(edge[1]).add(edge[0]); } // 2、找到所有的叶子节点 List leaves = new ArrayList<>(); for (int i = 0; i < n; i++) { if (graph.get(i).size() == 1) { leaves.add(i); } } // 3、不断删除叶子节点,直到剩下的节点数小于等于 2 个 int remainNodeNum = n; while (remainNodeNum > 2) { // 删除当前叶子节点,计算新的叶子节点 remainNodeNum -= leaves.size(); List newLeaves = new ArrayList<>(); for (int leaf : leaves) { // 将被删除的叶子节点的邻接节点的度减 1 int neighbor = graph.get(leaf).get(0); graph.get(neighbor).remove(leaf); // 如果邻接节点的度为 1,说明它也变成了叶子节点 if (graph.get(neighbor).size() == 1) { newLeaves.add(neighbor); } } // 最后剩下的节点就是根节点 return leaves; } } ``` ```javascript // by chatGPT (javascript) var findMinHeightTrees = function(n, edges) { // 1、构建邻接表 let graph = new Array(n); for(let i = 0; i < n; i++) { graph[i] = []; } for(let edge of edges) { // 无向图,等同于双向图 graph[edge[0]].push(edge[1]); graph[edge[1]].push(edge[0]); } // 2、找到所有的叶子节点 let leaves = []; for(let i = 0; i < n; i++) { if(graph[i].length === 1) { leaves.push(i); } } // 3、不断删除叶子节点,直到剩下的节点数小于等于 2 个 let remainNodeNum = n; while(remainNodeNum > 2) { // 删除当前叶子节点,计算新的叶子节点 remainNodeNum -= leaves.length; let newLeaves = []; for(let leaf of leaves) { // 将被删除的叶子节点的邻接节点的度减 1 let neighbor = graph[leaf][0]; graph[neighbor].splice(graph[neighbor].indexOf(leaf), 1); // 如果邻接节点的度为 1,说明它也变成了叶子节点 if(graph[neighbor].length === 1) { newLeaves.push(neighbor); } } leaves = newLeaves; } // 最后剩下的节点就是根节点 return leaves; }; ``` ```python # by chatGPT (python) class Solution: def findMinHeightTrees(self, n: int, edges: List[List[int]]) -> List[int]: # 1、构建邻接表 graph = [[] for _ in range(n)] for edge in edges: # 无向图,等同于双向图 graph[edge[0]].append(edge[1]) graph[edge[1]].append(edge[0]) # 2、找到所有的叶子节点 leaves = [] for i in range(n): if len(graph[i]) == 1: leaves.append(i) # 3、不断删除叶子节点,直到剩下的节点数小于等于 2 个 remainNodeNum = n while remainNodeNum > 2: # 删除当前叶子节点,计算新的叶子节点 remainNodeNum -= len(leaves) newLeaves = [] for leaf in leaves: # 将被删除的叶子节点的邻接节点的度减 1 neighbor = graph[leaf][0] graph[neighbor].remove(leaf) # 如果邻接节点的度为 1,说明它也变成了叶子节点 if len(graph[neighbor]) == 1: newLeaves.append(neighbor) leaves = newLeaves # 最后剩下的节点就是根节点 return leaves ``` https://leetcode.cn/problems/minimum-height-trees 的多语言解法👆 https://leetcode.cn/problems/minimum-insertions-to-balance-a-parentheses-string 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int minInsertions(string s) { int res = 0, need = 0; for (int i = 0; i < s.length(); i++) { if (s[i] == '(') { need += 2; if (need % 2 == 1) { res++; need--; } } if (s[i] == ')') { need--; if (need == -1) { res++; need = 1; } } } return res + need; } }; ``` ```go // by chatGPT (go) func minInsertions(s string) int { res := 0 need := 0 for i := 0; i < len(s); i++ { if s[i] == '(' { need += 2 if need%2 == 1 { res++ need-- } } else if s[i] == ')' { need-- if need == -1 { res++ need = 1 } } } return res + need } ``` ```java // by labuladong (java) class Solution { public int minInsertions(String s) { int res = 0, need = 0; for (int i = 0; i < s.length(); i++) { if (s.charAt(i) == '(') { need += 2; if (need % 2 == 1) { res++; need--; } } if (s.charAt(i) == ')') { need--; if (need == -1) { res++; need = 1; } } } return res + need; } } ``` ```javascript // by chatGPT (javascript) var minInsertions = function(s) { let res = 0, need = 0; for (let i = 0; i < s.length; i++) { if (s.charAt(i) == '(') { need += 2; if (need % 2 == 1) { res++; need--; } } if (s.charAt(i) == ')') { need--; if (need == -1) { res++; need = 1; } } } return res + need; }; ``` ```python # by chatGPT (python) class Solution: def minInsertions(self, s: str) -> int: res = 0 need = 0 # Traverse through string s for i in range(len(s)): if s[i] == '(': need += 2 if need % 2 == 1: res += 1 need -= 1 if s[i] == ')': need -= 1 if need == -1: res += 1 need = 1 return res + need ``` https://leetcode.cn/problems/minimum-insertions-to-balance-a-parentheses-string 的多语言解法👆 https://leetcode.cn/problems/minimum-number-of-arrows-to-burst-balloons 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: // 区间调度问题 int findMinArrowShots(vector>& intvs) { if (intvs.size() == 0) return 0; // 按 end 升序排序 sort(intvs.begin(), intvs.end(), [](const auto& a, const auto& b) { return a[1] < b[1]; }); // 至少有一个区间不相交 int count = 1; // 排序后,第一个区间就是 x int x_end = intvs[0][1]; for (auto& interval : intvs) { int start = interval[0]; // 把 >= 改成 > 就行了 if (start > x_end) { count++; x_end = interval[1]; } } return count; } }; ``` ```go // by chatGPT (go) import ( "fmt" "sort" ) // findMinArrowShots 是区间调度问题的解决方案 func findMinArrowShots(intvs [][]int) int { if len(intvs) == 0 { return 0 } // 按 end 升序排序 sort.Slice(intvs, func(i, j int) bool { return intvs[i][1] < intvs[j][1] }) // 至少有一个区间不相交 count := 1 // 排序后,第一个区间就是 x xEnd := intvs[0][1] for _, interval := range intvs { start := interval[0] // 把 >= 改成 > 就行了 if start > xEnd { count++ xEnd = interval[1] } } return count } ``` ```java // by labuladong (java) class Solution { // 区间调度问题 public int findMinArrowShots(int[][] intvs) { if (intvs.length == 0) return 0; // 按 end 升序排序 Arrays.sort(intvs, new Comparator() { public int compare(int[] a, int[] b) { return a[1] - b[1]; } }); // 至少有一个区间不相交 int count = 1; // 排序后,第一个区间就是 x int x_end = intvs[0][1]; for (int[] interval : intvs) { int start = interval[0]; // 把 >= 改成 > 就行了 if (start > x_end) { count++; x_end = interval[1]; } } return count; } } ``` ```javascript // by chatGPT (javascript) /** * @param {number[][]} intvs * @return {number} */ var findMinArrowShots = function(intvs) { if (intvs.length == 0) return 0; // 按 end 升序排序 intvs.sort((a, b) => { return a[1] - b[1]; }); // 至少有一个区间不相交 let count = 1; // 排序后,第一个区间就是 x let xEnd = intvs[0][1]; for (let interval of intvs) { let start = interval[0]; // 把 >= 改成 > 就行了 if (start > xEnd) { count++; xEnd = interval[1]; } } return count; } ``` ```python # by chatGPT (python) # 区间调度问题 class Solution: def findMinArrowShots(self, intvs: List[List[int]]) -> int: if not intvs: return 0 # 按 end 升序排序 intvs.sort(key=lambda x: x[1]) # 至少有一个区间不相交 count = 1 # 排序后,第一个区间就是 x x_end = intvs[0][1] for interval in intvs: start = interval[0] # 把 >= 改成 > 就行了 if start > x_end: count += 1 x_end = interval[1] return count ``` https://leetcode.cn/problems/minimum-number-of-arrows-to-burst-balloons 的多语言解法👆 https://leetcode.cn/problems/minimum-path-sum 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { private: int memo[205][205]; public: int minPathSum(vector>& grid) { int m = grid.size(); int n = grid[0].size(); memset(memo, -1, sizeof memo); return dp(grid, m - 1, n - 1); } int dp(vector>& grid, int i, int j) { // base case if (i == 0 && j == 0) { return grid[0][0]; } if (i < 0 || j < 0) { return INT_MAX; } // 避免重复计算 if (memo[i][j] != -1) { return memo[i][j]; } // 将计算结果记入备忘录 memo[i][j] = min( dp(grid, i - 1, j), dp(grid, i, j - 1) ) + grid[i][j]; return memo[i][j]; } }; ``` ```go // by chatGPT (go) import "math" func minPathSum(grid [][]int) int { m, n := len(grid), len(grid[0]) // 构造备忘录,初始值全部设为 -1 memo := make([][]int, m) for i := range memo { memo[i] = make([]int, n) for j := range memo[i] { memo[i][j] = -1 } } var dp func(i, j int) int dp = func(i, j int) int { // base case if i == 0 && j == 0 { return grid[0][0] } if i < 0 || j < 0 { return int(math.MaxInt64) } // 避免重复计算 if memo[i][j] != -1 { return memo[i][j] } // 将计算结果记入备忘录 memo[i][j] = min( dp(i-1, j), dp(i, j-1), ) + grid[i][j] return memo[i][j] } return dp(m-1, n-1) } func min(a, b int) int { if a < b { return a } return b } ``` ```java // by labuladong (java) class Solution { int[][] memo; public int minPathSum(int[][] grid) { int m = grid.length; int n = grid[0].length; // 构造备忘录,初始值全部设为 -1 memo = new int[m][n]; for (int[] row : memo) Arrays.fill(row, -1); return dp(grid, m - 1, n - 1); } int dp(int[][] grid, int i, int j) { // base case if (i == 0 && j == 0) { return grid[0][0]; } if (i < 0 || j < 0) { return Integer.MAX_VALUE; } // 避免重复计算 if (memo[i][j] != -1) { return memo[i][j]; } // 将计算结果记入备忘录 memo[i][j] = Math.min( dp(grid, i - 1, j), dp(grid, i, j - 1) ) + grid[i][j]; return memo[i][j]; } } ``` ```javascript // by chatGPT (javascript) var minPathSum = function(grid) { const m = grid.length; const n = grid[0].length; // 构造备忘录,初始值全部设为 -1 const memo = new Array(m); for (let i = 0; i < m; i++) { memo[i] = new Array(n).fill(-1); } return dp(grid, m - 1, n - 1); function dp(grid, i, j) { if (i === 0 && j === 0) { // base case return grid[0][0]; } if (i < 0 || j < 0) { // 越界 return Infinity; } // 避免重复计算 if (memo[i][j] !== -1) { return memo[i][j]; } // 将计算结果记入备忘录 memo[i][j] = Math.min( dp(grid, i - 1, j), dp(grid, i, j - 1) ) + grid[i][j]; return memo[i][j]; } }; ``` ```python # by chatGPT (python) class Solution: def minPathSum(self, grid: List[List[int]]) -> int: m = len(grid) n = len(grid[0]) # 构造备忘录,初始值全部设为 -1 memo = [[-1 for _ in range(n)] for _ in range(m)] return self.dp(grid, m - 1, n - 1, memo) def dp(self, grid: List[List[int]], i: int, j: int, memo: List[List[int]]) -> int: # base case if i == 0 and j == 0: return grid[0][0] if i < 0 or j < 0: return float("inf") # 避免重复计算 if memo[i][j] != -1: return memo[i][j] # 将计算结果记入备忘录 memo[i][j] = min( self.dp(grid, i - 1, j, memo), self.dp(grid, i, j - 1, memo) ) + grid[i][j] return memo[i][j] ``` https://leetcode.cn/problems/minimum-path-sum 的多语言解法👆 https://leetcode.cn/problems/minimum-window-substring 的多语言解法👇 ```cpp // by labuladong (cpp) class Solution { public: string minWindow(string s, string t) { unordered_map need, window; for (char c : t) need[c]++; int left = 0, right = 0; int valid = 0; // 记录最小覆盖子串的起始索引及长度 int start = 0, len = INT_MAX; /** ![](../pictures/slidingwindow/1.png) */ while (right < s.size()) { // c 是将移入窗口的字符 char c = s[right]; // 右移窗口 right++; // 进行窗口内数据的一系列更新 if (need.count(c)) { window[c]++; if (window[c] == need[c]) valid++; } // 判断左侧窗口是否要收缩 while (valid == need.size()) { /** ![](../pictures/slidingwindow/2.png) */ // 在这里更新最小覆盖子串 if (right - left < len) { start = left; len = right - left; } // d 是将移出窗口的字符 char d = s[left]; // 左移窗口 left++; // 进行窗口内数据的一系列更新 if (need.count(d)) { if (window[d] == need[d]) valid--; window[d]--; } } /** ![](../pictures/slidingwindow/4.png) */ } // 返回最小覆盖子串 return len == INT_MAX ? "" : s.substr(start, len); } }; ``` ```go // by mario_huang (go) func minWindow(s string, t string) string { need := map[byte]int{} window := map[byte]int{} for _, c := range []byte(t) { need[c]++ } left, right := 0, 0 valid := 0 // 记录最小覆盖子串的起始索引及长度 start, length := 0, math.MaxInt /** ![](../pictures/slidingwindow/1.png) */ for right < len(s) { // c 是将移入窗口的字符 c := s[right] // 右移窗口 right++ // 进行窗口内数据的一系列更新 if _, ok := need[c]; ok { window[c]++ if window[c] == need[c] { valid++ } } // 判断左侧窗口是否要收缩 for valid == len(need) { /** ![](../pictures/slidingwindow/2.png) */ // 在这里更新最小覆盖子串 if right-left < length { start = left length = right - left } // d 是将移出窗口的字符 d := s[left] // 左移窗口 left++ // 进行窗口内数据的一系列更新 if _, ok := need[d]; ok { if window[d] == need[d] { valid-- } window[d]-- } } /** ![](../pictures/slidingwindow/4.png) */ } // 返回最小覆盖子串 if length == math.MaxInt { return "" } else { return s[start : start+length] } } ``` ```java // by chatGPT (java) 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 left = 0, right = 0; int valid = 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); } } ``` ```javascript // by chatGPT (javascript) var minWindow = function(s, t) { const need = new Map(); const window = new Map(); // 统计 t 中每个字符出现的次数 for (let c of t) { need.set(c, need.has(c) ? need.get(c) + 1 : 1) } let left = 0, right = 0; let valid = 0; // 记录最小覆盖子串的起始索引及长度 let start = 0, len = Infinity; while (right < s.length) { // c 是将移入窗口的字符 let c = s[right]; // 右移窗口 right++; // 进行窗口内数据的一系列更新 if (need.has(c)) { window.set(c, window.has(c) ? window.get(c) + 1 : 1) if (window.get(c) === need.get(c)) valid++; } // 判断左侧窗口是否要收缩 while (valid === need.size) { // 在这里更新最小覆盖子串 if (right - left < len) { start = left; len = right - left; } // d 是将移出窗口的字符 let d = s[left]; // 左移窗口 left++; // 进行窗口内数据的一系列更新 if (need.has(d)) { if (window.get(d) === need.get(d)) valid--; window.set(d, window.get(d) - 1) } } } // 返回最小覆盖子串 return len === Infinity ? "" : s.substr(start, len); }; ``` ```python # by chatGPT (python) class Solution: def minWindow(self, s: str, t: str) -> str: from collections import Counter need = Counter(t) window = Counter() left, right = 0, 0 valid = 0 # 记录最小覆盖子串的起始索引及长度 start, length = 0, float('inf') """ ![](../pictures/slidingwindow/1.png) """ while right < len(s): # c 是将移入窗口的字符 c = s[right] right += 1 # 进行窗口内数据的一系列更新 if c in need: window[c] += 1 if window[c] == need[c]: valid += 1 # 判断左侧窗口是否要收缩 while valid == len(need): """ ![](../pictures/slidingwindow/2.png) """ # 在这里更新最小覆盖子串 if right - left < length: start = left length = right - left # d 是将移出窗口的字符 d = s[left] left += 1 # 进行窗口内数据的一系列更新 if d in need: if window[d] == need[d]: valid -= 1 window[d] -= 1 """ ![](../pictures/slidingwindow/4.png) """ # 返回最小覆盖子串 return '' if length == float('inf') else s[start:start+length] ``` https://leetcode.cn/problems/minimum-window-substring 的多语言解法👆 https://leetcode.cn/problems/missing-number 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int missingNumber(vector& nums) { int n = nums.size(); int res = 0; // 先和新补的索引异或一下 res ^= n; // 和其他的元素、索引做异或 for (int i = 0; i < n; i++) res ^= i ^ nums[i]; return res; } }; ``` ```go // by chatGPT (go) /* * @lc app=leetcode id=268 lang=golang * * [268] Missing Number * * https://leetcode.com/problems/missing-number/description/ * * algorithms * Easy (49.34%) * Total Accepted: 321.9K * Total Submissions: 647.5K * Testcase Example: '[3,0,1]' * * Given an array containing n distinct numbers taken from 0, 1, 2, ..., n, * find the one that is missing from the array. * * Example 1: * * * Input: [3,0,1] * Output: 2 * * * Example 2: * * * Input: [9,6,4,2,3,5,7,0,1] * Output: 8 * * * Note: * Your algorithm should run in linear runtime complexity. Could you implement * it using only constant extra space complexity? */ func missingNumber(nums []int) int { n := len(nums) res := 0 // 先和新补的索引异或一下 res ^= n // 和其他的元素、索引做异或 for i := 0; i < n; i++ { res ^= i ^ nums[i] } return res } ``` ```java // by labuladong (java) class Solution { public int missingNumber(int[] nums) { int n = nums.length; int res = 0; // 先和新补的索引异或一下 res ^= n; // 和其他的元素、索引做异或 for (int i = 0; i < n; i++) res ^= i ^ nums[i]; return res; } } ``` ```javascript // by chatGPT (javascript) var missingNumber = function(nums) { let n = nums.length; let res = 0; // 先和新补的索引异或一下 res ^= n; // 和其他的元素、索引做异或 for (let i = 0; i < n; i++) res ^= i ^ nums[i]; return res; } ``` ```python # by chatGPT (python) class Solution: def missingNumber(self, nums: List[int]) -> int: n = len(nums) res = 0 # 先和新补的索引异或一下 res ^= n # 和其他的元素、索引做异或 for i in range(n): res ^= i ^ nums[i] return res ``` https://leetcode.cn/problems/missing-number 的多语言解法👆 https://leetcode.cn/problems/most-stones-removed-with-same-row-or-column 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int removeStones(vector>& stones) { int n = stones.size(); // 一维坐标 -> 节点 ID unordered_map codeToId; for (int i = 0; i < n; i++) { codeToId[encode(stones[i])] = i; } // 记录每一行每一列有哪些节点 unordered_map> colIndexToCodes, rowIndexToCodes; for (auto point : stones) { int x = point[0], y = point[1]; rowIndexToCodes[x].push_back(encode(point)); colIndexToCodes[y].push_back(encode(point)); } // 启动 union find 算法 UF uf(n); // 把每一列的节点连通 for (auto& [index, col] : colIndexToCodes) { int firstId = codeToId[col[0]]; for (int i = 1; i < col.size(); i++) { int otherId = codeToId[col[i]]; uf.my_union(firstId, otherId); } } // 把每一行的节点连通 for (auto& [index, row] : rowIndexToCodes) { int firstId = codeToId[row[0]]; for (int i = 1; i < row.size(); i++) { int otherId = codeToId[row[i]]; uf.my_union(firstId, otherId); } } // 石头总数 - 连通分量数量就是被消除的石头个数 return n - uf.count(); } // 将二维坐标转化成一维索引 int encode(vector& point) { return point[0] * 10000 + point[1]; } }; // union find 算法模板 class UF { public: // 连通分量个数 int count; // 存储每个节点的父节点 vector parent; // n 为图中节点的个数 UF(int n) : count(n), parent(n) { for (int i = 0; i < n; i++) { parent[i] = i; } } // 将节点 p 和节点 q 连通 void my_union(int p, int q) { int rootP = find(p); int rootQ = find(q); if (rootP == rootQ) { return; } parent[rootQ] = rootP; // 两个连通分量合并成一个连通分量 count--; } // 判断节点 p 和节点 q 是否连通 bool connected(int p, int q) { int rootP = find(p); int rootQ = find(q); return rootP == rootQ; } int find(int x) { if (parent[x] != x) { parent[x] = find(parent[x]); } return parent[x]; } }; ``` ```go // by chatGPT (go) func removeStones(stones [][]int) int { n := len(stones) // 一维坐标 -> 节点 ID codeToId := make(map[int]int, n) for i := 0; i < n; i++ { codeToId[encode(stones[i])] = i } // 记录每一行每一列有哪些节点 colIndexToCodes := make(map[int][]int) rowIndexToCodes := make(map[int][]int) for _, point := range stones { x, y := point[0], point[1] rowIndexToCodes[x] = append(rowIndexToCodes[x], encode(point)) colIndexToCodes[y] = append(colIndexToCodes[y], encode(point)) } // 启动 union find 算法 uf := newUF(n) // 把每一列的节点连通 for _, col := range colIndexToCodes { firstId := codeToId[col[0]] for i := 1; i < len(col); i++ { otherId := codeToId[col[i]] uf.union(firstId, otherId) } } // 把每一行的节点连通 for _, row := range rowIndexToCodes { firstId := codeToId[row[0]] for i := 1; i < len(row); i++ { otherId := codeToId[row[i]] uf.union(firstId, otherId) } } // 石头总数 - 连通分量数量就是被消除的石头个数 return n - uf.count() } // 将二维坐标转化成一维索引 func encode(point []int) int { return point[0]*10000 + point[1] } type UF struct { // 连通分量个数 count int // 存储每个节点的父节点 parent []int } // n 为图中节点的个数 func newUF(n int) *UF { uf := new(UF) uf.count = n uf.parent = make([]int, n) for i := 0; i < n; i++ { uf.parent[i] = i } return uf } // 将节点 p 和节点 q 连通 func (uf *UF) union(p int, q int) { rootP := uf.find(p) rootQ := uf.find(q) if rootP == rootQ { return } uf.parent[rootQ] = rootP // 两个连通分量合并成一个连通分量 uf.count-- } // 判断节点 p 和节点 q 是否连通 func (uf *UF) connected(p int, q int) bool { rootP := uf.find(p) rootQ := uf.find(q) return rootP == rootQ } func (uf *UF) find(x int) int { if uf.parent[x] != x { uf.parent[x] = uf.find(uf.parent[x]) } return uf.parent[x] } // 返回图中的连通分量个数 func (uf *UF) count() int { return uf.count } ``` ```java // by labuladong (java) class Solution { public int removeStones(int[][] stones) { int n = stones.length; // 一维坐标 -> 节点 ID HashMap codeToId = new HashMap<>(); for (int i = 0; i < n; i++) { codeToId.put(encode(stones[i]), i); } // 记录每一行每一列有哪些节点 HashMap> colIndexToCodes = new HashMap<>(); HashMap> rowIndexToCodes = new HashMap<>(); for (int[] point : stones) { int x = point[0], y = point[1]; rowIndexToCodes.putIfAbsent(x, new ArrayList<>()); colIndexToCodes.putIfAbsent(y, new ArrayList<>()); rowIndexToCodes.get(x).add(encode(point)); colIndexToCodes.get(y).add(encode(point)); } // 启动 union find 算法 UF uf = new UF(n); // 把每一列的节点连通 for (int index : colIndexToCodes.keySet()) { List col = colIndexToCodes.get(index); int firstId = codeToId.get(col.get(0)); for (int i = 1; i < col.size(); i++) { int otherId = codeToId.get(col.get(i)); uf.union(firstId, otherId); } } // 把每一行的节点连通 for (int index : rowIndexToCodes.keySet()) { List row = rowIndexToCodes.get(index); int firstId = codeToId.get(row.get(0)); for (int i = 1; i < row.size(); i++) { int otherId = codeToId.get(row.get(i)); uf.union(firstId, otherId); } } // 石头总数 - 连通分量数量就是被消除的石头个数 return n - uf.count(); } // 将二维坐标转化成一维索引 int encode(int[] point) { return point[0] * 10000 + point[1]; } } // union find 算法模板 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; } } ``` ```javascript // by chatGPT (javascript) /** * @param {number[][]} stones * @return {number} */ var removeStones = function(stones) { const n = stones.length; // 一维坐标 -> 节点 ID let codeToId = new Map(); for (let i = 0; i < n; i++) { codeToId.set(encode(stones[i]), i); } // 记录每一行每一列有哪些节点 let colIndexToCodes = new Map(); let rowIndexToCodes = new Map(); stones.forEach(point => { const x = point[0], y = point[1]; rowIndexToCodes.set(x, rowIndexToCodes.get(x) || []); colIndexToCodes.set(y, colIndexToCodes.get(y) || []); rowIndexToCodes.get(x).push(encode(point)); colIndexToCodes.get(y).push(encode(point)); }); // 启动 union find 算法 let uf = new UF(n); // 把每一列的节点连通 for (let index of colIndexToCodes.keys()) { const col = colIndexToCodes.get(index); const firstId = codeToId.get(col[0]); for (let i = 1; i < col.length; i++) { const otherId = codeToId.get(col[i]); uf.union(firstId, otherId); } } // 把每一行的节点连通 for (let index of rowIndexToCodes.keys()) { const row = rowIndexToCodes.get(index); const firstId = codeToId.get(row[0]); for (let i = 1; i < row.length; i++) { const otherId = codeToId.get(row[i]); uf.union(firstId, otherId); } } // 石头总数 - 连通分量数量就是被消除的石头个数 return n - uf.count(); }; // 将二维坐标转化成一维索引 function encode(point) { return point[0] * 10000 + point[1]; } // union find 算法模板 class UF { // 连通分量个数 count = 0; // 存储每个节点的父节点 parent = []; // n 为图中节点的个数 constructor(n) { this.count = n; this.parent = new Array(n).fill(0); for (let i = 0; i < n; i++) { this.parent[i] = i; } } // 将节点 p 和节点 q 连通 union(p, q) { let rootP = this.find(p); let rootQ = this.find(q); if (rootP === rootQ) { return; } this.parent[rootQ] = rootP; // 两个连通分量合并成一个连通分量 this.count--; } // 判断节点 p 和节点 q 是否连通 connected(p, q) { let rootP = this.find(p); let rootQ = this.find(q); return rootP === rootQ; } find(x) { if (this.parent[x] !== x) { this.parent[x] = this.find(this.parent[x]); } return this.parent[x]; } // 返回图中的连通分量个数 count() { return this.count; } } ``` ```python # by chatGPT (python) class Solution: def removeStones(self, stones: List[List[int]]) -> int: n = len(stones) # 一维坐标 -> 节点 ID codeToId = {} for i in range(n): codeToId[self.encode(stones[i])] = i # 记录每一行每一列有哪些节点 colIndexToCodes = {} rowIndexToCodes = {} for point in stones: x, y = point[0], point[1] rowIndexToCodes.setdefault(x, []) colIndexToCodes.setdefault(y, []) rowIndexToCodes[x].append(self.encode(point)) colIndexToCodes[y].append(self.encode(point)) # 启动 union find 算法 uf = UF(n) # 把每一列的节点连通 for index in colIndexToCodes.keys(): col = colIndexToCodes[index] firstId = codeToId[col[0]] for i in range(1, len(col)): otherId = codeToId[col[i]] uf.union(firstId, otherId) # 把每一行的节点连通 for index in rowIndexToCodes.keys(): row = rowIndexToCodes[index] firstId = codeToId[row[0]] for i in range(1, len(row)): otherId = codeToId[row[i]] uf.union(firstId, otherId) # 石头总数 - 连通分量数量就是被消除的石头个数 return n - uf.count() # 将二维坐标转化成一维索引 def encode(self, point: List[int]) -> int: return point[0] * 10000 + point[1] # union find 算法模板 class UF: # 连通分量个数 def __init__(self, n: int): # 存储每个节点的父节点 self.parent = list(range(n)) self.count = n # 将节点 p 和节点 q 连通 def union(self, p: int, q: int) -> None: rootP = self.find(p) rootQ = self.find(q) if rootP == rootQ: return self.parent[rootQ] = rootP # 两个连通分量合并成一个连通分量 self.count -= 1 # 判断节点 p 和节点 q 是否连通 def connected(self, p: int, q: int) -> bool: rootP = self.find(p) rootQ = self.find(q) return rootP == rootQ def find(self, x: int) -> int: if self.parent[x] != x: self.parent[x] = self.find(self.parent[x]) return self.parent[x] # 返回图中的连通分量个数 def count(self) -> int: return self.count ``` https://leetcode.cn/problems/most-stones-removed-with-same-row-or-column 的多语言解法👆 https://leetcode.cn/problems/move-zeroes 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: void moveZeroes(vector& nums) { // 去除 nums 中的所有 0 // 返回去除 0 之后的数组长度 int p = removeElement(nums, 0); // 将 p 之后的所有元素赋值为 0 for (; p < nums.size(); p++) { nums[p] = 0; } } // 双指针技巧,复用 [27. 移除元素] 的解法。 int removeElement(vector& nums, int val) { int fast = 0, slow = 0; while (fast < nums.size()) { if (nums[fast] != val) { nums[slow] = nums[fast]; slow++; } fast++; } return slow; } }; ``` ```go // by chatGPT (go) func moveZeroes(nums []int) { // 去除 nums 中的所有 0 // 返回去除 0 之后的数组长度 p := removeElement(nums, 0) // 将 p 之后的所有元素赋值为 0 for ; p < len(nums); p++ { nums[p] = 0 } } // 双指针技巧,复用 [27. 移除元素] 的解法。 func removeElement(nums []int, val int) int { fast := 0 slow := 0 for fast < len(nums) { if nums[fast] != val { nums[slow] = nums[fast] slow++ } fast++ } return slow } ``` ```java // by labuladong (java) class Solution { public void moveZeroes(int[] nums) { // 去除 nums 中的所有 0 // 返回去除 0 之后的数组长度 int p = removeElement(nums, 0); // 将 p 之后的所有元素赋值为 0 for (; p < nums.length; p++) { nums[p] = 0; } } // 双指针技巧,复用 [27. 移除元素] 的解法。 int removeElement(int[] nums, int val) { int fast = 0, slow = 0; while (fast < nums.length) { if (nums[fast] != val) { nums[slow] = nums[fast]; slow++; } fast++; } return slow; } } ``` ```javascript // by chatGPT (javascript) var moveZeroes = function(nums) { // 去除 nums 中的所有 0 // 返回去除 0 之后的数组长度 var p = removeElement(nums, 0); // 将 p 之后的所有元素赋值为 0 for (; p < nums.length; p++) { nums[p] = 0; } }; // 双指针技巧,复用 [27. 移除元素] 的解法。 var removeElement = function(nums, val) { var fast = 0, slow = 0; while (fast < nums.length) { if (nums[fast] !== val) { nums[slow] = nums[fast]; slow++; } fast++; } return slow; }; ``` ```python # by chatGPT (python) class Solution: def moveZeroes(self, nums: List[int]) -> None: """ Do not return anything, modify nums in-place instead. """ # 去除 nums 中的所有 0 # 返回去除 0 之后的数组长度 p = self.removeElement(nums, 0) # 将 p 之后的所有元素赋值为 0 for i in range(p, len(nums)): nums[i] = 0 # 双指针技巧,复用 [27. 移除元素] 的解法。 def removeElement(self, nums: List[int], val: int) -> int: fast = 0 slow = 0 while fast < len(nums): if nums[fast] != val: nums[slow] = nums[fast] slow += 1 fast += 1 return slow ``` https://leetcode.cn/problems/move-zeroes 的多语言解法👆 https://leetcode.cn/problems/n-ary-tree-level-order-traversal 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: vector> levelOrder(Node* root) { vector> res; if (root == nullptr) { return res; } // 以下是标准的 BFS 算法遍历框架 queue q; q.push(root); while (!q.empty()) { int sz = q.size(); vector level; for (int i = 0; i < sz; i++) { Node* cur = q.front(); q.pop(); level.push_back(cur->val); // 多叉树可能有多个子节点,将所有子节点都加入队列 for (Node* child : cur->children) { q.push(child); } } // 当前层遍历完毕 res.push_back(level); } return res; } }; ``` ```go // by chatGPT (go) func levelOrder(root *Node) [][]int { res := [][]int{} if root == nil { return res } // 以下是标准的 BFS 算法遍历框架 q := []*Node{root} for len(q) != 0 { sz := len(q) level := []int{} for i := 0; i < sz; i++ { cur := q[0] q = q[1:] level = append(level, cur.Val) // 多叉树可能有多个子节点,将所有子节点都加入队列 for _, child := range cur.Children { q = append(q, child) } } // 当前层遍历完毕 res = append(res, level) } return res } ``` ```java // by labuladong (java) class Solution { public List> levelOrder(Node root) { List> res = new LinkedList<>(); if (root == null) { return res; } // 以下是标准的 BFS 算法遍历框架 Queue q = new LinkedList<>(); q.offer(root); while (!q.isEmpty()) { int sz = q.size(); List level = new LinkedList<>(); for (int i = 0; i < sz; i++) { Node cur = q.poll(); level.add(cur.val); // 多叉树可能有多个子节点,将所有子节点都加入队列 for (Node child : cur.children) { q.offer(child); } } // 当前层遍历完毕 res.add(level); } return res; } } ``` ```javascript // by chatGPT (javascript) // 将 Java 代码转化为 JavaScript 代码 var levelOrder = function(root) { // 创建一个数组 res 来存储遍历结果 const res = [] if (!root) { // 如果结点为 null 则返回 res 数组 return res } // 创建一个队列 q 用于 BFS 遍历 let q = [] q.push(root) while (q.length > 0) { // 记录当前队列中元素个数 const sz = q.length // 创建一个数组 level 来存储当前层所有结点的值 const level = [] for (let i = 0; i < sz; i++) { // 弹出队头结点 cur 并将其值加入 level 数组中 const cur = q.shift() level.push(cur.val) // 将当前结点的所有子节点加入队列 q 中 for (const child of cur.children) { q.push(child) } } // 将当前层的值 level 加入到 res 数组中 res.push(level) } // 返回遍历结果 return res } ``` ```python # by chatGPT (python) class Solution: def levelOrder(self, root: 'Node') -> List[List[int]]: res = [] if not root: return res # 以下是标准的 BFS 算法遍历框架 q = deque() q.append(root) while q: sz = len(q) level = [] for i in range(sz): cur = q.popleft() level.append(cur.val) # 多叉树可能有多个子节点,将所有子节点都加入队列 for child in cur.children: q.append(child) # 当前层遍历完毕 res.append(level) return res ``` https://leetcode.cn/problems/n-ary-tree-level-order-traversal 的多语言解法👆 https://leetcode.cn/problems/n-ary-tree-postorder-traversal 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: vector postorder(Node* root) { traverse(root); return res; } private: vector res; void traverse(Node* root) { if (root == nullptr) { return; } // 前序遍历位置 for (Node* child : root->children) { traverse(child); } // 后序遍历位置 res.push_back(root->val); } }; ``` ```go // by chatGPT (go) /** * Definition for a Node. * type Node struct { * Val int * Children []*Node * } */ func postorder(root *Node) []int { res := make([]int, 0) traverse(root, &res) // use closure to avoid using global variable return res } func traverse(root *Node, res *[]int) { if root == nil { return } // 前序遍历位置 for _, child := range root.Children { traverse(child, res) } // 后序遍历位置 *res = append(*res, root.Val) } ``` ```java // by labuladong (java) class Solution { public List postorder(Node root) { traverse(root); return res; } List res = new LinkedList<>(); void traverse(Node root) { if (root == null) { return; } // 前序遍历位置 for (Node child : root.children) { traverse(child); } // 后序遍历位置 res.add(root.val); } } ``` ```javascript // by chatGPT (javascript) /** * @param {Node} root * @return {number[]} */ var postorder = function(root) { var res = []; var traverse = function(root) { if (root === null) { return; } // 前序遍历位置 for (var i = 0; i < root.children.length; i++) { traverse(root.children[i]); } // 后序遍历位置 res.push(root.val); }; traverse(root); return res; } ``` ```python # by chatGPT (python) # Definition for a Node. # class Node: # def __init__(self, val=None, children=None): # self.val = val # self.children = children if children is not None else [] class Solution: def postorder(self, root: 'Node') -> List[int]: self.traverse(root) return self.res res = [] def traverse(self, root: 'Node') -> None: if not root: return # 前序遍历位置 for child in root.children: self.traverse(child) # 后序遍历位置 self.res.append(root.val) ``` https://leetcode.cn/problems/n-ary-tree-postorder-traversal 的多语言解法👆 https://leetcode.cn/problems/n-ary-tree-preorder-traversal 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: vector preorder(Node* root) { traverse(root); return res; } private: vector res; void traverse(Node* root) { if (!root) { return; } // 前序遍历位置 res.push_back(root->val); for (Node* child : root->children) { traverse(child); } // 后序遍历位置 } }; ``` ```go // by chatGPT (go) import "container/list" // Node Definition type Node struct { Val int Children []*Node } func preorder(root *Node) []int { var res []int traverse(root, &res) return res } func traverse(root *Node, res *[]int) { if root == nil { return } // 前序遍历位置 *res = append(*res, root.Val) for _, child := range root.Children { traverse(child, res) } // 后序遍历位置 } ``` ```java // by labuladong (java) class Solution { public List preorder(Node root) { traverse(root); return res; } List res = new LinkedList<>(); void traverse(Node root) { if (root == null) { return; } // 前序遍历位置 res.add(root.val); for (Node child : root.children) { traverse(child); } // 后序遍历位置 } } ``` ```javascript // by chatGPT (javascript) var preorder = function(root) { var res = []; var traverse = function(root) { if (root === null) { return; } // 前序遍历位置 res.push(root.val); for (var i = 0; i < root.children.length; i++) { traverse(root.children[i]); } // 后序遍历位置 } traverse(root); return res; } ``` ```python # by chatGPT (python) class Solution: def preorder(self, root: 'Node') -> List[int]: self.traverse(root) return self.res def __init__(self): self.res = [] def traverse(self, root: 'Node'): if not root: return # 前序遍历位置 self.res.append(root.val) for child in root.children: self.traverse(child) # 后序遍历位置 ``` https://leetcode.cn/problems/n-ary-tree-preorder-traversal 的多语言解法👆 https://leetcode.cn/problems/n-queens 的多语言解法👇 ```cpp // by labuladong (cpp) class Solution { public: vector> res; /* 输入棋盘边长 n,返回所有合法的放置 */ vector> solveNQueens(int n) { // '.' 表示空,'Q' 表示皇后,初始化空棋盘。 vector board(n, string(n, '.')); backtrack(board, 0); return res; } // 路径:board 中小于 row 的那些行都已经成功放置了皇后 // 选择列表:第 row 行的所有列都是放置皇后的选择 // 结束条件:row 超过 board 的最后一行 void backtrack(vector& board, int row) { // 触发结束条件 if (row == board.size()) { res.push_back(board); return; } int n = board[row].size(); for (int col = 0; col < n; col++) { // 排除不合法选择 if (!isValid(board, row, col)) { /** ![](../pictures/backtracking/7.jpg) */ continue; } // 做选择 board[row][col] = 'Q'; // 进入下一行决策 backtrack(board, row + 1); // 撤销选择 board[row][col] = '.'; } } /* 是否可以在 board[row][col] 放置皇后?*/ bool isValid(vector& board, int row, int col) { int n = board.size(); // 检查列是否有皇后互相冲突 for (int i = 0; i <= row; i++) { if (board[i][col] == 'Q') return false; } // 检查右上方是否有皇后互相冲突 for (int i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++) { if (board[i][j] == 'Q') return false; } // 检查左上方是否有皇后互相冲突 for (int i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) { if (board[i][j] == 'Q') return false; } return true; } }; ``` ```go // by chatGPT (go) type backtracking struct { res [][]string } func (b *backtracking) solveNQueens(n int) [][]string { board := make([]string, n) for i := 0; i < n; i++ { board[i] = strings.Repeat(".", n) } b.backtrack(board, 0) return b.res } func (b *backtracking) backtrack(board []string, row int) { if row == len(board) { temp := make([]string, len(board)) copy(temp, board) b.res = append(b.res, temp) return } n := len(board[row]) for col := 0; col < n; col++ { if !b.isValid(board, row, col) { // 排除不合法选择 continue } // 做选择 board[row] = board[row][:col] + "Q" + board[row][col+1:] // 进入下一行决策 b.backtrack(board, row+1) // 撤销选择 board[row] = board[row][:col] + "." + board[row][col+1:] } } func (b *backtracking) isValid(board []string, row, col int) bool { n := len(board) // 检查列是否有皇后互相冲突 for i := 0; i <= row; i++ { if board[i][col] == 'Q' { return false } } // 检查右上方是否有皇后互相冲突 for i, j := row-1, col+1; i >= 0 && j < n; i, j = i-1, j+1 { if board[i][j] == 'Q' { return false } } // 检查左上方是否有皇后互相冲突 for i, j := row-1, col-1; i >= 0 && j >= 0; i, j = i-1, j-1 { if board[i][j] == 'Q' { return false } } return true } ``` ```java // by chatGPT (java) class Solution { List> res = new ArrayList<>(); /** * 输入棋盘边长 n,返回所有合法的放置 */ public List> solveNQueens(int n) { // '.' 表示空,'Q' 表示皇后,初始化空棋盘。 List board = new ArrayList<>(); StringBuilder sb = new StringBuilder(); for (int i = 0; i < n; i++) { sb.append('.'); } for (int i = 0; i < n; i++) { board.add(sb.toString()); } backtrack(board, 0); return res; } /** * *路径:board 中小于 row 的那些行都已经成功放置了皇后 * *选择列表:第 row 行的所有列都是放置皇后的选择 * *结束条件:row 超过 board 的最后一行 */ private 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)) { /** ![](../pictures/backtracking/7.jpg) */ continue; } // 做选择 char[] arr = board.get(row).toCharArray(); arr[col] = 'Q'; board.set(row, String.valueOf(arr)); // 进入下一行决策 backtrack(board, row + 1); // 撤销选择 arr[col] = '.'; board.set(row, String.valueOf(arr)); } } /* 是否可以在 board[row][col] 放置皇后?*/ private 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; } } ``` ```javascript // by chatGPT (javascript) var solveNQueens = function(n) { var res = []; // '.' 表示空,'Q' 表示皇后,初始化空棋盘。 var board = new Array(n); for (var i = 0; i < n; i++) { board[i] = new Array(n).fill('.'); } backtrack(board, 0); return res; // 路径:board 中小于 row 的那些行都已经成功放置了皇后 // 选择列表:第 row 行的所有列都是放置皇后的选择 // 结束条件:row 超过 board 的最后一行 function backtrack(board, row) { // 触发结束条件 if (row === board.length) { res.push(Array.from(board, row => row.join(''))); return; } var n = board.length; for (var col = 0; col < n; col++) { // 排除不合法选择 if (!isValid(board, row, col)) { continue; } // 做选择 board[row][col] = 'Q'; // 进入下一行决策 backtrack(board, row + 1); // 撤销选择 board[row][col] = '.'; } } /* 是否可以在 board[row][col] 放置皇后?*/ function isValid(board, row, col) { var n = board.length; // 检查列是否有皇后互相冲突 for (var i = 0; i <= row; i++) { if (board[i][col] === 'Q') { return false; } } // 检查右上方是否有皇后互相冲突 for (var i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++) { if (board[i][j] === 'Q') { return false; } } // 检查左上方是否有皇后互相冲突 for (var i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) { if (board[i][j] === 'Q') { return false; } } return true; } }; ``` ```python # by chatGPT (python) class Solution: def __init__(self): self.res = [] def solveNQueens(self, n: int) -> List[List[str]]: board = [['.' for j in range(n)] for i in range(n)] # 初始化空棋盘 self.backtrack(board, 0) return self.res def backtrack(self, board: List[List[str]], row: int): if row == len(board): # 触发结束条件 self.res.append([''.join(board[i]) for i in range(len(board))]) return for col in range(len(board[row])): if not self.isValid(board, row, col): # 排除不合法选择 continue board[row][col] = 'Q' # 做选择 self.backtrack(board, row + 1) # 进入下一行决策 board[row][col] = '.' # 撤销选择 def isValid(self, board: List[List[str]], row: int, col: int) -> bool: n = len(board) # 检查列是否有皇后互相冲突 for i in range(row + 1): if board[i][col] == 'Q': return False # 检查右上方是否有皇后互相冲突 for i, j in zip(range(row - 1, -1, -1), range(col + 1, n)): if board[i][j] == 'Q': return False # 检查左上方是否有皇后互相冲突 for i, j in zip(range(row - 1, -1, -1), range(col - 1, -1, -1)): if board[i][j] == 'Q': return False return True ``` https://leetcode.cn/problems/n-queens 的多语言解法👆 https://leetcode.cn/problems/nZZqjQ 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int minEatingSpeed(vector& piles, int H) { int left = 1; int right = 1000000000 + 1; while (left < right) { int mid = left + (right - left) / 2; if (f(piles, mid) <= H) { right = mid; } else { left = mid + 1; } } return left; } // 定义:速度为 x 时,需要 f(x) 小时吃完所有香蕉 // f(x) 随着 x 的增加单调递减 int f(vector& piles, int x) { int hours = 0; for (int pile : piles) { hours += (pile + x - 1) / x; } return hours; } }; ``` ```go // by chatGPT (go) // Go Solution Code for LeetCode Problem 875: Koko Eating Bananas func minEatingSpeed(piles []int, H int) int { left := 1 right := 1000000000 + 1 for left < right { mid := left + (right - left) / 2 if f(piles, mid) <= H { right = mid } else { left = mid + 1 } } return left } // 定义:速度为 x 时,需要 f(x) 小时吃完所有香蕉 // f(x) 随着 x 的增加单调递减 func f(piles []int, x int) int { hours := 0 for i := 0; i < len(piles); i++ { hours += piles[i] / x if piles[i] % x > 0 { hours++ } } return hours } ``` ```java // by labuladong (java) class Solution { public int minEatingSpeed(int[] piles, int H) { int left = 1; int right = 1000000000 + 1; while (left < right) { int mid = left + (right - left) / 2; if (f(piles, mid) <= H) { right = mid; } else { left = mid + 1; } } return left; } // 定义:速度为 x 时,需要 f(x) 小时吃完所有香蕉 // f(x) 随着 x 的增加单调递减 int f(int[] piles, int x) { int hours = 0; for (int i = 0; i < piles.length; i++) { hours += piles[i] / x; if (piles[i] % x > 0) { hours++; } } return hours; } } ``` ```javascript // by chatGPT (javascript) /** * @param {number[]} piles * @param {number} H * @return {number} */ var minEatingSpeed = function(piles, H) { let left = 1; let right = 1000000000 + 1; while (left < right) { let mid = left + Math.floor((right - left) / 2); if (f(piles, mid) <= H) { right = mid; } else { left = mid + 1; } } return left; }; // 定义:速度为 x 时,需要 f(x) 小时吃完所有香蕉 // f(x) 随着 x 的增加单调递减 function f(piles, x) { let hours = 0; for (let i = 0; i < piles.length; i++) { hours += Math.floor(piles[i] / x); if (piles[i] % x > 0) { hours++; } } return hours; } ``` ```python # by chatGPT (python) class Solution: def minEatingSpeed(self, piles: List[int], H: int) -> int: left = 1 right = 1000000000 + 1 while left < right: mid = left + (right - left) // 2 if self.f(piles, mid) <= H: right = mid else: left = mid + 1 return left # 定义:速度为 x 时,需要 f(x) 小时吃完所有香蕉 # f(x) 随着 x 的增加单调递减 def f(self, piles: List[int], x: int) -> int: hours = 0 for i in range(len(piles)): hours += piles[i] // x if piles[i] % x > 0: hours += 1 return hours ``` https://leetcode.cn/problems/nZZqjQ 的多语言解法👆 https://leetcode.cn/problems/nearest-exit-from-entrance-in-maze 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int nearestExit(vector>& maze, vector& entrance) { int m = maze.size(); int n = maze[0].size(); vector> dirs {{0, 1}, {0, -1}, {1, 0}, {-1, 0}}; // BFS 算法的队列和 visited 数组 queue> queue; vector> visited(m, vector(n, false)); queue.push(entrance); visited[entrance[0]][entrance[1]] = true; // 启动 BFS 算法从 entrance 开始像四周扩散 int step = 0; while (!queue.empty()) { int sz = queue.size(); step++; // 扩散当前队列中的所有节点 for (int i = 0; i < sz; i++) { vector cur = queue.front(); queue.pop(); // 每个节点都会尝试向上下左右四个方向扩展一步 for (vector dir : dirs) { int x = cur[0] + dir[0]; int y = cur[1] + dir[1]; if (x < 0 || x >= m || y < 0 || y >= n || 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.push(vector{x, y}); } } } return -1; } }; ``` ```go // by chatGPT (go) func nearestExit(maze [][]byte, entrance []int) int { m, n := len(maze), len(maze[0]) dirs := [][]int{{0, 1}, {0, -1}, {1, 0}, {-1, 0}} // BFS 算法的队列和 visited 数组 queue := [][]int{entrance} visited := make([][]bool, m) for i := 0; i < m; i++ { visited[i] = make([]bool, n) } visited[entrance[0]][entrance[1]] = true // 启动 BFS 算法从 entrance 开始像四周扩散 step := 0 for len(queue) > 0 { sz := len(queue) step++ // 扩散当前队列中的所有节点 for i := 0; i < sz; i++ { cur := queue[0] queue = queue[1:] // 每个节点都会尝试向上下左右四个方向扩展一步 for _, dir := range dirs { x := cur[0] + dir[0] y := cur[1] + dir[1] if x < 0 || x >= m || y < 0 || y >= n || 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 = append(queue, []int{x, y}) } } } return -1 } ``` ```java // by labuladong (java) class Solution { public int nearestExit(char[][] maze, int[] entrance) { int m = maze.length; int n = maze[0].length; int[][] dirs = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}}; // BFS 算法的队列和 visited 数组 Queue 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 sz = queue.size(); step++; // 扩散当前队列中的所有节点 for (int i = 0; i < sz; i++) { int[] cur = queue.poll(); // 每个节点都会尝试向上下左右四个方向扩展一步 for (int[] dir : dirs) { int x = cur[0] + dir[0]; int y = cur[1] + dir[1]; if (x < 0 || x >= m || y < 0 || y >= n || 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; } } ``` ```javascript // by chatGPT (javascript) var nearestExit = function(maze, entrance) { const m = maze.length; const n = maze[0].length; const dirs = [[0, 1], [0, -1], [1, 0], [-1, 0]]; // BFS 算法的队列和 visited 数组 const queue = []; const visited = Array(m).fill(0).map(() => Array(n).fill(false)); queue.push(entrance); visited[entrance[0]][entrance[1]] = true; // 启动 BFS 算法从 entrance 开始像四周扩散 let step = 0; while (queue.length) { const sz = queue.length; step++; // 扩散当前队列中的所有节点 for (let i = 0; i < sz; i++) { const cur = queue.shift(); // 每个节点都会尝试向上下左右四个方向扩展一步 for (const dir of dirs) { const x = cur[0] + dir[0]; const y = cur[1] + dir[1]; if (x < 0 || x >= m || y < 0 || y >= n || 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.push([x, y]); } } } return -1; }; ``` ```python # by chatGPT (python) class Solution: def nearestExit(self, maze: List[List[str]], entrance: List[int]) -> int: m, n = len(maze), len(maze[0]) dirs = [(0, 1), (0, -1), (1, 0), (-1, 0)] # BFS 算法的队列和 visited 数组 queue = deque() visited = [[False] * n for _ in range(m)] queue.append(tuple(entrance)) visited[entrance[0]][entrance[1]] = True # 启动 BFS 算法从 entrance 开始像四周扩散 step = 0 while queue: sz = len(queue) step += 1 # 扩散当前队列中的所有节点 for _ in range(sz): cur_x, cur_y = queue.popleft() # 每个节点都会尝试向上下左右四个方向扩展一步 for dir_x, dir_y in dirs: nxt_x, nxt_y = cur_x + dir_x, cur_y + dir_y if nxt_x < 0 or nxt_x >= m or nxt_y < 0 or nxt_y >= n \ or visited[nxt_x][nxt_y] or maze[nxt_x][nxt_y] == '+': continue if nxt_x == 0 or nxt_x == m - 1 or nxt_y == 0 or nxt_y == n - 1: # 走到边界(出口) return step visited[nxt_x][nxt_y] = True queue.append((nxt_x, nxt_y)) return -1 ``` https://leetcode.cn/problems/nearest-exit-from-entrance-in-maze 的多语言解法👆 https://leetcode.cn/problems/network-delay-time 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int networkDelayTime(vector>& times, int n, int k) { // 节点编号是从 1 开始的,所以要一个大小为 n + 1 的邻接表 vector>> graph(n + 1); for (int i = 1; i <= n; i++) { graph[i] = vector>(); } // 构造图 for (auto& edge : times) { int from = edge[0]; int to = edge[1]; int weight = edge[2]; // from -> List<(to, weight)> // 邻接表存储图结构,同时存储权重信息 graph[from].emplace_back(to, weight); } // 启动 dijkstra 算法计算以节点 k 为起点到其他节点的最短路径 vector distTo = dijkstra(k, graph, n); // 找到最长的那一条最短路径 int res = 0; for (int i = 1; i < distTo.size(); i++) { if (distTo[i] == INT_MAX) { // 有节点不可达,返回 -1 return -1; } res = max(res, distTo[i]); } return res; } private: // 输入一个起点 start,计算从 start 到其他节点的最短距离 vector dijkstra(int start, vector>>& graph, int n) { // 定义:distTo[i] 的值就是起点 start 到达节点 i 的最短路径权重 vector distTo(n + 1, INT_MAX); // base case,start 到 start 的最短距离就是 0 distTo[start] = 0; // 优先级队列,distFromStart 较小的排在前面 priority_queue, vector>, greater>> pq; // 从起点 start 开始进行 BFS pq.emplace(0, start); while (!pq.empty()) { auto [curDistFromStart, curNodeID] = pq.top(); pq.pop(); if (curDistFromStart > distTo[curNodeID]) { continue; } // 将 curNode 的相邻节点装入队列 for (auto& [nextNodeID, weight] : graph[curNodeID]) { int distToNextNode = distTo[curNodeID] + weight; // 更新 dp table if (distTo[nextNodeID] > distToNextNode) { distTo[nextNodeID] = distToNextNode; pq.emplace(distToNextNode, nextNodeID); } } } return distTo; } }; ``` ```go // by chatGPT (go) import ( "container/heap" "math" ) func networkDelayTime(times [][]int, n int, k int) int { // 节点编号是从 1 开始的,所以要一个大小为 n + 1 的邻接表 graph := make([][][]int, n+1) for i := 1; i <= n; i++ { graph[i] = make([][]int, 0) } // 构造图 for _, edge := range times { from := edge[0] to := edge[1] weight := edge[2] // from -> [ [to, weight], ... ] // 邻接表存储图结构,同时存储权重信息 graph[from] = append(graph[from], []int{to, weight}) } // 启动 dijkstra 算法计算以节点 k 为起点到其他节点的最短路径 distTo := dijkstra(k, graph) // 找到最长的那一条最短路径 res := 0 for i := 1; i < len(distTo); i++ { if distTo[i] == math.MaxInt32 { // 有节点不可达,返回 -1 return -1 } res = max(res, distTo[i]) } return res } type State struct { // 图节点的 id id int // 从 start 节点到当前节点的距离 distFromStart int } type PriorityQueue []*State func (pq PriorityQueue) Len() int { return len(pq) } func (pq PriorityQueue) Less(i, j int) bool { return pq[i].distFromStart < pq[j].distFromStart } func (pq PriorityQueue) Swap(i, j int) { pq[i], pq[j] = pq[j], pq[i] } func (pq *PriorityQueue) Push(x interface{}) { item := x.(*State) *pq = append(*pq, item) } func (pq *PriorityQueue) Pop() interface{} { old := *pq n := len(old) item := old[n-1] old[n-1] = nil // avoid memory leak *pq = old[0 : n-1] return item } // 输入一个起点 start,计算从 start 到其他节点的最短距离 func dijkstra(start int, graph [][][]int) []int { // 定义:distTo[i] 的值就是起点 start 到达节点 i 的最短路径权重 distTo := make([]int, len(graph)) for i := 1; i < len(graph); i++ { distTo[i] = math.MaxInt32 } // base case,start 到 start 的最短距离就是 0 distTo[start] = 0 // 优先级队列,distFromStart 较小的排在前面 pq := make(PriorityQueue, 0) heap.Init(&pq) // 从起点 start 开始进行 BFS heap.Push(&pq, &State{id: start, distFromStart: 0}) for pq.Len() > 0 { curState := heap.Pop(&pq).(*State) curNodeID := curState.id curDistFromStart := curState.distFromStart if curDistFromStart > distTo[curNodeID] { continue } // 将 curNode 的相邻节点装入队列 for _, neighbor := range graph[curNodeID] { nextNodeID := neighbor[0] distToNextNode := distTo[curNodeID] + neighbor[1] // 更新 dp table if distTo[nextNodeID] > distToNextNode { distTo[nextNodeID] = distToNextNode heap.Push(&pq, &State{id: nextNodeID, distFromStart: distToNextNode}) } } } return distTo } func max(a, b int) int { if a > b { return a } return b } ``` ```java // by labuladong (java) class Solution { public int networkDelayTime(int[][] times, int n, int k) { // 节点编号是从 1 开始的,所以要一个大小为 n + 1 的邻接表 List[] graph = new LinkedList[n + 1]; for (int i = 1; i <= n; i++) { graph[i] = new LinkedList<>(); } // 构造图 for (int[] edge : times) { int from = edge[0]; int to = edge[1]; int weight = edge[2]; // from -> List<(to, weight)> // 邻接表存储图结构,同时存储权重信息 graph[from].add(new int[]{to, weight}); } // 启动 dijkstra 算法计算以节点 k 为起点到其他节点的最短路径 int[] distTo = dijkstra(k, graph); // 找到最长的那一条最短路径 int res = 0; for (int i = 1; i < distTo.length; i++) { if (distTo[i] == Integer.MAX_VALUE) { // 有节点不可达,返回 -1 return -1; } res = Math.max(res, distTo[i]); } return res; } class State { // 图节点的 id int id; // 从 start 节点到当前节点的距离 int distFromStart; State(int id, int distFromStart) { this.id = id; this.distFromStart = distFromStart; } } // 输入一个起点 start,计算从 start 到其他节点的最短距离 int[] dijkstra(int start, List[] graph) { // 定义:distTo[i] 的值就是起点 start 到达节点 i 的最短路径权重 int[] distTo = new int[graph.length]; Arrays.fill(distTo, Integer.MAX_VALUE); // base case,start 到 start 的最短距离就是 0 distTo[start] = 0; // 优先级队列,distFromStart 较小的排在前面 Queue pq = new PriorityQueue<>((a, b) -> { return a.distFromStart - b.distFromStart; }); // 从起点 start 开始进行 BFS pq.offer(new State(start, 0)); while (!pq.isEmpty()) { State curState = pq.poll(); int curNodeID = curState.id; int curDistFromStart = curState.distFromStart; if (curDistFromStart > distTo[curNodeID]) { continue; } // 将 curNode 的相邻节点装入队列 for (int[] neighbor : graph[curNodeID]) { int nextNodeID = neighbor[0]; int distToNextNode = distTo[curNodeID] + neighbor[1]; // 更新 dp table if (distTo[nextNodeID] > distToNextNode) { distTo[nextNodeID] = distToNextNode; pq.offer(new State(nextNodeID, distToNextNode)); } } } return distTo; } } ``` ```javascript // by chatGPT (javascript) var networkDelayTime = function(times, n, k) { // 节点编号是从 1 开始的,所以要一个大小为 n + 1 的邻接表 let graph = new Array(n + 1); for (let i = 1; i <= n; i++) { graph[i] = []; } // 构造图 for (let edge of times) { let from = edge[0]; let to = edge[1]; let weight = edge[2]; // from -> List<(to, weight)> // 邻接表存储图结构,同时存储权重信息 graph[from].push([to, weight]); } // 启动 dijkstra 算法计算以节点 k 为起点到其他节点的最短路径 let distTo = dijkstra(k, graph); // 找到最长的那一条最短路径 let res = 0; for (let i = 1; i < distTo.length; i++) { if (distTo[i] === Infinity) { // 有节点不可达,返回 -1 return -1; } res = Math.max(res, distTo[i]); } return res; }; class State { // 图节点的 id constructor(id, distFromStart) { this.id = id; // 从 start 节点到当前节点的距离 this.distFromStart = distFromStart; } } // 输入一个起点 start,计算从 start 到其他节点的最短距离 function dijkstra(start, graph) { // 定义:distTo[i] 的值就是起点 start 到达节点 i 的最短路径权重 let distTo = new Array(graph.length).fill(Infinity); // base case,start 到 start 的最短距离就是 0 distTo[start] = 0; // 优先级队列,distFromStart 较小的排在前面 let pq = []; // 从起点 start 开始进行 BFS pq.push(new State(start, 0)); pq.sort((a, b) => { return a.distFromStart - b.distFromStart; }); while (pq.length > 0) { let curState = pq.shift(); let curNodeID = curState.id; let curDistFromStart = curState.distFromStart; if (curDistFromStart > distTo[curNodeID]) { continue; } // 将 curNode 的相邻节点装入队列 for (let neighbor of graph[curNodeID]) { let nextNodeID = neighbor[0]; let distToNextNode = distTo[curNodeID] + neighbor[1]; // 更新 dp table if (distTo[nextNodeID] > distToNextNode) { distTo[nextNodeID] = distToNextNode; pq.push(new State(nextNodeID, distToNextNode)); pq.sort((a, b) => { return a.distFromStart - b.distFromStart; }); } } } return distTo; } ``` ```python # by chatGPT (python) import heapq from typing import List class Solution: def networkDelayTime(self, times: List[List[int]], n: int, k: int) -> int: # 节点编号是从 1 开始的,所以要一个大小为 n + 1 的邻接表 graph = [[] for _ in range(n+1)] for edge in times: from_node, to_node, weight = edge[0], edge[1], edge[2] # from -> List<(to, weight)> # 邻接表存储图结构,同时存储权重信息 graph[from_node].append((to_node, weight)) # 启动 dijkstra 算法计算以节点 k 为起点到其他节点的最短路径 dist_to = self.dijkstra(k, graph) # 找到最长的那一条最短路径 res = 0 for i in range(1, len(dist_to)): if dist_to[i] == float('inf'): # 有节点不可达,返回 -1 return -1 res = max(res, dist_to[i]) return res class State: # 图节点的 id def __init__(self, id: int, dist_from_start: int): self.id = id # 从 start 节点到当前节点的距离 self.dist_from_start = dist_from_start def __lt__(self, other): return self.dist_from_start < other.dist_from_start # 输入一个起点 start,计算从 start 到其他节点的最短距离 def dijkstra(self, start: int, graph: List[List[int]]) -> List[int]: # 定义:distTo[i] 的值就是起点 start 到达节点 i 的最短路径权重 dist_to = [float('inf')] * len(graph) # base case,start 到 start 的最短距离就是 0 dist_to[start] = 0 # 优先级队列,distFromStart 较小的排在前面 pq = [Solution.State(start, 0)] # 从起点 start 开始进行 BFS heapq.heapify(pq) while pq: cur_state = heapq.heappop(pq) cur_node_id = cur_state.id cur_dist_from_start = cur_state.dist_from_start if cur_dist_from_start > dist_to[cur_node_id]: continue # 将 cur_node 的相邻节点装入队列 for neighbor in graph[cur_node_id]: next_node_id, dist_to_next_node = neighbor[0], dist_to[cur_node_id] + neighbor[1] # 更新 dp table if dist_to[next_node_id] > dist_to_next_node: dist_to[next_node_id] = dist_to_next_node heapq.heappush(pq, Solution.State(next_node_id, dist_to_next_node)) return dist_to ``` https://leetcode.cn/problems/network-delay-time 的多语言解法👆 https://leetcode.cn/problems/next-greater-element-i 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: vector nextGreaterElement(vector& nums1, vector& nums2) { // 记录 nums2 中每个元素的下一个更大元素 vector greater = nextGreaterElement(nums2); // 转化成映射:元素 x -> x 的下一个最大元素 map greaterMap; for (int i = 0; i < nums2.size(); i++) { greaterMap[nums2[i]] = greater[i]; } // nums1 是 nums2 的子集,所以根据 greaterMap 可以得到结果 vector res(nums1.size()); for (int i = 0; i < nums1.size(); i++) { res[i] = greaterMap[nums1[i]]; } return res; } // 计算 nums 中每个元素的下一个更大元素 vector nextGreaterElement(vector& nums) { int n = nums.size(); // 存放答案的数组 vector res(n); stack s; // 倒着往栈里放 for (int i = n - 1; i >= 0; i--) { // 判定个子高矮 while (!s.empty() && s.top() <= nums[i]) { // 矮个起开,反正也被挡着了。。。 s.pop(); } // nums[i] 身后的下一个更大元素 res[i] = s.empty() ? -1 : s.top(); s.push(nums[i]); } return res; } }; ``` ```go // by chatGPT (go) import "fmt" func nextGreaterElement(nums1 []int, nums2 []int) []int { // 记录 nums2 中每个元素的下一个更大元素 greater := nextGreater(nums2) // 转化成映射:元素 x -> x 的下一个最大元素 greaterMap := make(map[int]int) for i, v := range nums2 { greaterMap[v] = greater[i] } // nums1 是 nums2 的子集,所以根据 greaterMap 可以得到结果 res := make([]int, len(nums1)) for i, v := range nums1 { res[i] = greaterMap[v] } return res } // 计算 nums 中每个元素的下一个更大元素 func nextGreater(nums []int) []int { n := len(nums) // 存放答案的数组 res := make([]int, n) s := []int{} // 倒着往栈里放 for i := n - 1; i >= 0; i-- { // 判定个子高矮 for len(s) != 0 && s[len(s)-1] <= nums[i] { // 矮个起开,反正也被挡着了。。。 s = s[:len(s)-1] } // nums[i] 身后的下一个更大元素 if len(s) == 0 { res[i] = -1 } else { res[i] = s[len(s)-1] } s = append(s, nums[i]) } return res } ``` ```java // by labuladong (java) class Solution { public int[] nextGreaterElement(int[] nums1, int[] nums2) { // 记录 nums2 中每个元素的下一个更大元素 int[] greater = nextGreaterElement(nums2); // 转化成映射:元素 x -> x 的下一个最大元素 HashMap greaterMap = new HashMap<>(); for (int i = 0; i < nums2.length; i++) { greaterMap.put(nums2[i], greater[i]); } // nums1 是 nums2 的子集,所以根据 greaterMap 可以得到结果 int[] res = new int[nums1.length]; for (int i = 0; i < nums1.length; i++) { res[i] = greaterMap.get(nums1[i]); } return res; } // 计算 nums 中每个元素的下一个更大元素 int[] nextGreaterElement(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] 身后的下一个更大元素 res[i] = s.isEmpty() ? -1 : s.peek(); s.push(nums[i]); } return res; } } ``` ```javascript // by chatGPT (javascript) var nextGreaterElement = function(nums1, nums2) { // 记录 nums2 中每个元素的下一个更大元素 var greater = nextGreater(nums2); // 转化成映射:元素 x -> x 的下一个最大元素 var greaterMap = new Map(); for (var i = 0; i < nums2.length; i++) { greaterMap.set(nums2[i], greater[i]); } // nums1 是 nums2 的子集,所以根据 greaterMap 可以得到结果 var res = new Array(nums1.length); for (var i = 0; i < nums1.length; i++) { res[i] = greaterMap.get(nums1[i]); } return res; }; // 计算 nums 中每个元素的下一个更大元素 var nextGreater = function(nums) { var n = nums.length; // 存放答案的数组 var res = new Array(n); var s = []; // 倒着往栈里放 for (var i = n - 1; i >= 0; i--) { // 判定个子高矮 while (s.length && s[s.length-1] <= nums[i]) { // 矮个起开,反正也被挡着了。。。 s.pop(); } // nums[i] 身后的下一个更大元素 res[i] = s.length == 0 ? -1 : s[s.length-1]; s.push(nums[i]); } return res; }; ``` ```python # by chatGPT (python) class Solution: def nextGreaterElement(self, nums1: List[int], nums2: List[int]) -> List[int]: # 记录 nums2 中每个元素的下一个更大元素 greater = self.nextGreater(nums2) # 转化成映射:元素 x -> x 的下一个最大元素 greaterMap = {} for i in range(len(nums2)): greaterMap[nums2[i]] = greater[i] # nums1 是 nums2 的子集,所以根据 greaterMap 可以得到结果 res = [] for num in nums1: res.append(greaterMap[num]) return res # 计算 nums 中每个元素的下一个更大元素 def nextGreater(self, nums: List[int]) -> List[int]: n = len(nums) # 存放答案的数组 res = [-1] * n s = [] # 倒着往栈里放 for i in range(n - 1, -1, -1): # 判定个子高矮 while s and s[-1] <= nums[i]: # 矮个起开,反正也被挡着了。。。 s.pop() # nums[i] 身后的下一个更大元素 if s: res[i] = s[-1] s.append(nums[i]) return res ``` https://leetcode.cn/problems/next-greater-element-i 的多语言解法👆 https://leetcode.cn/problems/nim-game 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: bool canWinNim(int n) { // 如果上来就踩到 4 的倍数,那就认输吧 // 否则,可以把对方控制在 4 的倍数,必胜 return n % 4 != 0; } }; ``` ```go // by chatGPT (go) func canWinNim(n int) bool { // 如果上来就踩到 4 的倍数,那就认输吧 // 否则,可以把对方控制在 4 的倍数,必胜 return n % 4 != 0 } ``` ```java // by labuladong (java) class Solution { public boolean canWinNim(int n) { // 如果上来就踩到 4 的倍数,那就认输吧 // 否则,可以把对方控制在 4 的倍数,必胜 return n % 4 != 0; } } ``` ```javascript // by chatGPT (javascript) var canWinNim = function(n) { // 如果上来就踩到 4 的倍数,那就认输吧 // 否则,可以把对方控制在 4 的倍数,必胜 return n % 4 !== 0; }; ``` ```python # by chatGPT (python) class Solution: def canWinNim(self, n: int) -> bool: # 如果上来就踩到 4 的倍数,那就认输吧 # 否则,可以把对方控制在 4 的倍数,必胜 return n % 4 != 0 ``` https://leetcode.cn/problems/nim-game 的多语言解法👆 https://leetcode.cn/problems/non-decreasing-subsequences 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: vector> findSubsequences(vector& nums) { if (nums.empty()) { return res; } backtrack(nums, 0); return res; } private: vector> res; // 记录回溯的路径 deque track; // 回溯算法主函数 void backtrack(vector& nums, int start) { if (track.size() >= 2) { // 找到一个合法答案 res.push_back(vector(track.begin(), track.end())); } // 用哈希集合防止重复选择相同元素 unordered_set used; // 回溯算法标准框架 for (int i = start; i < nums.size(); i++) { // 保证集合中元素都是递增顺序 if (!track.empty() && track.back() > nums[i]) { continue; } // 保证不要重复使用相同的元素 if (used.count(nums[i])) { continue; } // 选择 nums[i] used.insert(nums[i]); track.push_back(nums[i]); // 递归遍历下一层回溯树 backtrack(nums, i + 1); // 撤销选择 nums[i] track.pop_back(); } } }; ``` ```go // by chatGPT (go) func findSubsequences(nums []int) [][]int { res := [][]int{} if len(nums) == 0 { return res } track := []int{} backtrack(nums, 0, &track, &res) return res } // 回溯算法主函数 func backtrack(nums []int, start int, track *[]int, res *[][]int) { if len(*track) >= 2 { // 找到一个合法答案 temp := make([]int, len(*track)) copy(temp, *track) *res = append(*res, temp) } // 用哈希集合防止重复选择相同元素 used := make(map[int]bool) // 回溯算法标准框架 for i := start; i < len(nums); i++ { // 保证集合中元素都是递增顺序 if len(*track) > 0 && (*track)[len(*track)-1] > nums[i] { continue } // 保证不要重复使用相同的元素 if used[nums[i]] { continue } // 选择 nums[i] used[nums[i]] = true *track = append(*track, nums[i]) // 递归遍历下一层回溯树 backtrack(nums, i+1, track, res) // 撤销选择 nums[i] *track = (*track)[:len(*track)-1] } } ``` ```java // by labuladong (java) class Solution { public List> findSubsequences(int[] nums) { if (nums.length == 0) { return res; } backtrack(nums, 0); return res; } List> res = new LinkedList<>(); // 记录回溯的路径 LinkedList track = new LinkedList<>(); // 回溯算法主函数 void backtrack(int[] nums, int start) { if (track.size() >= 2) { // 找到一个合法答案 res.add(new LinkedList<>(track)); } // 用哈希集合防止重复选择相同元素 HashSet used = new HashSet<>(); // 回溯算法标准框架 for (int i = start; i < nums.length; i++) { // 保证集合中元素都是递增顺序 if (!track.isEmpty() && track.getLast() > nums[i]) { continue; } // 保证不要重复使用相同的元素 if (used.contains(nums[i])) { /** ![](../pictures/排列组合/9.jpeg) */ continue; } // 选择 nums[i] used.add(nums[i]); track.add(nums[i]); // 递归遍历下一层回溯树 backtrack(nums, i + 1); // 撤销选择 nums[i] track.removeLast(); } } } ``` ```javascript // by chatGPT (javascript) /** * @param {number[]} nums * @return {number[][]} */ var findSubsequences = function(nums) { var res = []; // 记录回溯的路径 var track = []; // 回溯算法主函数 var backtrack = function(nums, start) { if (track.length >= 2) { // 找到一个合法答案 res.push([...track]); } // 用哈希集合防止重复选择相同元素 var used = new Set(); // 回溯算法标准框架 for (var i = start; i < nums.length; i++) { // 保证集合中元素都是递增顺序 if (track.length!==0 && track[track.length-1] > nums[i]) { continue; } // 保证不要重复使用相同的元素 if (used.has(nums[i])) { /** ![](../pictures/排列组合/9.jpeg) */ continue; } // 选择 nums[i] used.add(nums[i]); track.push(nums[i]); // 递归遍历下一层回溯树 backtrack(nums, i + 1); // 撤销选择 nums[i] track.pop(); } } if (nums.length === 0) { return res; } backtrack(nums, 0); return res; }; ``` ```python # by chatGPT (python) class Solution: def findSubsequences(self, nums: List[int]) -> List[List[int]]: if len(nums) == 0: return self.res self.backtrack(nums, 0) return self.res # Initialize empty list for storing result res = [] # Initialize empty list for storing the path track = [] # Backtracking algorithm main function def backtrack(self, nums: List[int], start: int) -> None: # If a valid answer is found (path has length 2 or greater), add to the result list if len(self.track) >= 2: self.res.append(self.track[:]) # Use hash set to avoid selecting duplicate elements used = set() # Standard backtracking algorithm framework for i in range(start, len(nums)): # Ensure the elements in the track list are in increasing order if len(self.track) > 0 and self.track[-1] > nums[i]: continue # Avoid using the same element multiple times if nums[i] in used: # Extend up -200 # ![](../pictures/排列组合/9.jpeg) continue used.add(nums[i]) self.track.append(nums[i]) self.backtrack(nums, i+1) self.track.pop() ``` https://leetcode.cn/problems/non-decreasing-subsequences 的多语言解法👆 https://leetcode.cn/problems/non-overlapping-intervals 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int eraseOverlapIntervals(vector>& intervals) { int n = intervals.size(); return n - intervalSchedule(intervals); } // 区间调度算法,算出 intvs 中最多有几个互不相交的区间 int intervalSchedule(vector>& intvs) { if (intvs.empty()) return 0; // 按 end 升序排序 sort(intvs.begin(), intvs.end(), [](const vector& a, const vector& b){ return a[1] < b[1]; }); // 至少有一个区间不相交 int count = 1; // 排序后,第一个区间就是 x int x_end = intvs[0][1]; for (const auto& interval : intvs) { int start = interval[0]; if (start >= x_end) { // 找到下一个选择的区间了 count++; x_end = interval[1]; } } return count; } }; ``` ```go // by chatGPT (go) import "sort" func eraseOverlapIntervals(intervals [][]int) int { n := len(intervals) return n - intervalSchedule(intervals) } // 区间调度算法,算出 intvs 中最多有几个互不相交的区间 func intervalSchedule(intvs [][]int) int { if len(intvs) == 0 { return 0 } // 按 end 升序排序 sort.Slice(intvs, func(i, j int) bool { return intvs[i][1] < intvs[j][1] }) // 至少有一个区间不相交 count := 1 // 排序后,第一个区间就是 x xEnd := intvs[0][1] for _, interval := range intvs { start := interval[0] if start >= xEnd { // 找到下一个选择的区间了 count++ xEnd = interval[1] } } return count } ``` ```java // by labuladong (java) class Solution { public int eraseOverlapIntervals(int[][] intervals) { int n = intervals.length; return n - intervalSchedule(intervals); } // 区间调度算法,算出 intvs 中最多有几个互不相交的区间 int intervalSchedule(int[][] intvs) { if (intvs.length == 0) return 0; // 按 end 升序排序 Arrays.sort(intvs, new Comparator() { public int compare(int[] a, int[] b) { return a[1] - b[1]; } }); // 至少有一个区间不相交 int count = 1; // 排序后,第一个区间就是 x int x_end = intvs[0][1]; for (int[] interval : intvs) { int start = interval[0]; if (start >= x_end) { // 找到下一个选择的区间了 count++; x_end = interval[1]; } } return count; } } ``` ```javascript // by chatGPT (javascript) /** * @param {number[][]} intervals * @return {number} */ var eraseOverlapIntervals = function(intervals) { var n = intervals.length; return n - intervalSchedule(intervals); }; /** * 区间调度算法,算出 intvs 中最多有几个互不相交的区间 * @param {number[][]} intvs * @return {number} */ var intervalSchedule = function(intvs) { if (intvs.length === 0) return 0; // 按 end 升序排序 intvs.sort((a, b) => a[1] - b[1]); // 至少有一个区间不相交 var count = 1; // 排序后,第一个区间就是 x var x_end = intvs[0][1]; for (var i = 1; i < intvs.length; i++) { var start = intvs[i][0]; if (start >= x_end) { // 找到下一个选择的区间了 count++; x_end = intvs[i][1]; } } return count; }; ``` ```python # by chatGPT (python) class Solution: def eraseOverlapIntervals(self, intervals: List[List[int]]) -> int: n = len(intervals) return n - self.intervalSchedule(intervals) # 区间调度算法,算出 intvs 中最多有几个互不相交的区间 def intervalSchedule(self, intvs: List[List[int]]) -> int: if len(intvs) == 0: return 0 # 按 end 升序排序 intvs = sorted(intvs, key=lambda x:x[1]) # 至少有一个区间不相交 count = 1 # 排序后,第一个区间就是 x x_end = intvs[0][1] for interval in intvs: start = interval[0] if start >= x_end: # 找到下一个选择的区间了 count += 1 x_end = interval[1] return count ``` https://leetcode.cn/problems/non-overlapping-intervals 的多语言解法👆 https://leetcode.cn/problems/nth-digit 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int findNthDigit(int n) { // 位数(一位数,两位数...) int digit = 1; // 1,10,100, 1000 这样的后缀 long long base = 1; while (n > 9 * base * digit) { n -= 9 * base * digit; base *= 10; digit++; } // 此时假设 base = 1000,那么说明 n 是 100~999 中的某个三位数的某一位 // 哪个三位数呢?这样算: long long val = base + (n - 1) / digit; // 是这个三位数的第几位呢?这样算: int index = (n - 1) % digit; // 怎么把 val 的第 index 这一位数字抠出来呢?可以转化成字符串来算: return to_string(val)[index] - '0'; } }; ``` ```go // by chatGPT (go) func findNthDigit(n int) int { // 位数(一位数,两位数...) digit := 1 // 1,10,100, 1000 这样的后缀 base := int64(1) for n > 9*base*int64(digit) { n -= 9 * int(base) * digit base *= 10 digit++ } // 此时假设 base = 1000,那么说明 n 是 100~999 中的某个三位数的某一位 // 哪个三位数呢?这样算: val := base + (int64(n)-1)/int64(digit) // 是这个三位数的第几位呢?这样算: index := (n - 1) % digit // 怎么把 val 的第 index 这一位数字抠出来呢?可以转化成字符串来算: return int(strconv.Itoa(int(val))[index] - '0') } ``` ```java // by labuladong (java) class Solution { public int findNthDigit(int n) { // 位数(一位数,两位数...) int digit = 1; // 1,10,100, 1000 这样的后缀 long base = 1; while (n > 9 * base * digit) { n -= 9 * base * digit; base *= 10; digit++; } // 此时假设 base = 1000,那么说明 n 是 100~999 中的某个三位数的某一位 // 哪个三位数呢?这样算: long val = base + (n - 1) / digit; // 是这个三位数的第几位呢?这样算: int index = (n - 1) % digit; // 怎么把 val 的第 index 这一位数字抠出来呢?可以转化成字符串来算: return ("" + val).charAt(index) - '0'; } } ``` ```javascript // by chatGPT (javascript) var findNthDigit = function(n) { // 位数(一位数,两位数...) let digit = 1; // 1,10,100, 1000 这样的后缀 let base = 1; while (n > 9 * base * digit) { n -= 9 * base * digit; base *= 10; digit++; } // 此时假设 base = 1000,那么说明 n 是 100~999 中的某个三位数的某一位 // 哪个三位数呢?这样算: let val = base + Math.floor((n - 1) / digit); // 是这个三位数的第几位呢?这样算: let index = (n - 1) % digit; // 怎么把 val 的第 index 这一位数字抠出来呢?可以转化成字符串来算: return ("" + val).charAt(index) - '0'; }; ``` ```python # by chatGPT (python) class Solution: def findNthDigit(self, n: int) -> int: # 位数(一位数,两位数...) digit = 1 # 1,10,100, 1000 这样的后缀 base = 1 while n > 9 * base * digit: n -= 9 * base * digit base *= 10 digit += 1 # 此时假设 base = 1000,那么说明 n 是 100~999 中的某个三位数的某一位 # 哪个三位数呢?这样算: val = base + (n - 1) // digit # 是这个三位数的第几位呢?这样算: index = (n - 1) % digit # 怎么把 val 的第 index 这一位数字抠出来呢?可以转化成字符串来算: return int(str(val)[index]) ``` https://leetcode.cn/problems/nth-digit 的多语言解法👆 https://leetcode.cn/problems/number-of-1-bits 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: // you need to treat n as an unsigned value int hammingWeight(uint32_t n) { int res = 0; while (n != 0) { n = n & (n - 1); res++; } return res; } }; ``` ```go // by chatGPT (go) func hammingWeight(n uint32) int { res := 0 for n != 0 { n &= n - 1 res++ } return res } ``` ```java // by labuladong (java) public class Solution { // you need to treat n as an unsigned value public int hammingWeight(int n) { int res = 0; while (n != 0) { n = n & (n - 1); res++; } return res; } } ``` ```javascript // by chatGPT (javascript) var hammingWeight = function(n) { // you need to treat n as an unsigned value var res = 0; while (n !== 0) { n = n & (n - 1); res++; } return res; } ``` ```python # by chatGPT (python) class Solution: # you need to treat n as an unsigned value def hammingWeight(self, n: int) -> int: res = 0 while n != 0: n = n & (n-1) res += 1 return res ``` https://leetcode.cn/problems/number-of-1-bits 的多语言解法👆 https://leetcode.cn/problems/number-of-closed-islands 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: // 主函数:计算封闭岛屿的数量 int closedIsland(vector>& grid) { int m = grid.size(), n = grid[0].size(); 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); } // 遍历 grid,剩下的岛屿都是封闭岛屿 int res = 0; 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; } // 从 (i, j) 开始,将与之相邻的陆地都变成海水 void dfs(vector>& grid, int i, int j) { int m = grid.size(), n = grid[0].size(); if (i < 0 || j < 0 || i >= m || j >= n) { return; } if (grid[i][j] == 1) { // 已经是海水了 return; } // 将 (i, j) 变成海水 grid[i][j] = 1; // 淹没上下左右的陆地 dfs(grid, i + 1, j); dfs(grid, i, j + 1); dfs(grid, i - 1, j); dfs(grid, i, j - 1); } }; ``` ```go // by chatGPT (go) // 计算封闭岛屿的数量 func closedIsland(grid [][]int) int { m, n := len(grid), len(grid[0]) for j := 0; j < n; j++ { // 把靠上边的岛屿淹掉 dfs(grid, 0, j) // 把靠下边的岛屿淹掉 dfs(grid, m-1, j) } for i := 0; i < m; i++ { // 把靠左边的岛屿淹掉 dfs(grid, i, 0) // 把靠右边的岛屿淹掉 dfs(grid, i, n-1) } // 遍历 grid,剩下的岛屿都是封闭岛屿 res := 0 for i := 0; i < m; i++ { for j := 0; j < n; j++ { if grid[i][j] == 0 { res++ dfs(grid, i, j) } } } return res } // 从 (i, j) 开始,将与之相邻的陆地都变成海水 func dfs(grid [][]int, i, j int) { m, n := len(grid), len(grid[0]) if i < 0 || j < 0 || i >= m || j >= n { return } if grid[i][j] == 1 { // 已经是海水了 return } // 将 (i, j) 变成海水 grid[i][j] = 1 // 淹没上下左右的陆地 dfs(grid, i+1, j) dfs(grid, i, j+1) dfs(grid, i-1, j) dfs(grid, i, j-1) } ``` ```java // by labuladong (java) class Solution { // 主函数:计算封闭岛屿的数量 public int closedIsland(int[][] grid) { 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); } // 遍历 grid,剩下的岛屿都是封闭岛屿 int res = 0; 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; } // 从 (i, j) 开始,将与之相邻的陆地都变成海水 void dfs(int[][] grid, int i, int j) { int m = grid.length, n = grid[0].length; if (i < 0 || j < 0 || i >= m || j >= n) { return; } if (grid[i][j] == 1) { // 已经是海水了 return; } // 将 (i, j) 变成海水 grid[i][j] = 1; // 淹没上下左右的陆地 dfs(grid, i + 1, j); dfs(grid, i, j + 1); dfs(grid, i - 1, j); dfs(grid, i, j - 1); } } ``` ```javascript // by chatGPT (javascript) var closedIsland = function(grid) { var m = grid.length, n = grid[0].length; for (var j = 0; j < n; j++) { // 把靠上边的岛屿淹掉 dfs(grid, 0, j); // 把靠下边的岛屿淹掉 dfs(grid, m - 1, j); } for (var i = 0; i < m; i++) { // 把靠左边的岛屿淹掉 dfs(grid, i, 0); // 把靠右边的岛屿淹掉 dfs(grid, i, n - 1); } // 遍历 grid,剩下的岛屿都是封闭岛屿 var res = 0; for (var i = 0; i < m; i++) { for (var j = 0; j < n; j++) { if (grid[i][j] === 0) { res++; dfs(grid, i, j); } } } return res; } // 从 (i, j) 开始,将与之相邻的陆地都变成海水 function dfs(grid, i, j) { var m = grid.length, n = grid[0].length; if (i < 0 || j < 0 || i >= m || j >= n) { return; } if (grid[i][j] === 1) { // 已经是海水了 return; } // 将 (i, j) 变成海水 grid[i][j] = 1; // 淹没上下左右的陆地 dfs(grid, i + 1, j); dfs(grid, i, j + 1); dfs(grid, i - 1, j); dfs(grid, i, j - 1); } ``` ```python # by chatGPT (python) class Solution: def closedIsland(self, grid: List[List[int]]) -> int: m, n = len(grid), len(grid[0]) for j in range(n): # 把靠上边的岛屿淹掉 self.dfs(grid, 0, j) # 把靠下边的岛屿淹掉 self.dfs(grid, m - 1, j) for i in range(m): # 把靠左边的岛屿淹掉 self.dfs(grid, i, 0) # 把靠右边的岛屿淹掉 self.dfs(grid, i, n - 1) # 遍历 grid,剩下的岛屿都是封闭岛屿 res = 0 for i in range(m): for j in range(n): if grid[i][j] == 0: res += 1 self.dfs(grid, i, j) return res # 从 (i, j) 开始,将与之相邻的陆地都变成海水 def dfs(self, grid: List[List[int]], i: int, j: int) -> None: m, n = len(grid), len(grid[0]) if i < 0 or j < 0 or i >= m or j >= n: return if grid[i][j] == 1: # 已经是海水了 return # 将 (i, j) 变成海水 grid[i][j] = 1 # 淹没上下左右的陆地 self.dfs(grid, i + 1, j) self.dfs(grid, i, j + 1) self.dfs(grid, i - 1, j) self.dfs(grid, i, j - 1) ``` https://leetcode.cn/problems/number-of-closed-islands 的多语言解法👆 https://leetcode.cn/problems/number-of-distinct-islands 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int numDistinctIslands(vector>& grid) { int m = grid.size(), n = grid[0].size(); // 记录所有岛屿的序列化结果 unordered_set islands; for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { if (grid[i][j] == 1) { // 淹掉这个岛屿,同时存储岛屿的序列化结果 string s; // 初始的方向可以随便写,不影响正确性 dfs(grid, i, j, s, 's'); // 用字符代替数字,方便后续拼接 islands.insert(s); /** ![](../pictures/岛屿/6.png) */ } } } // 不相同的岛屿数量 return islands.size(); } private: void dfs(vector>& grid, int i, int j, string& s, char dir) { int m = grid.size(), n = grid[0].size(); if (i < 0 || j < 0 || i >= m || j >= n || grid[i][j] == 0) { return; } // 前序遍历位置:进入 (i, j) grid[i][j] = 0; s += dir; dfs(grid, i - 1, j, s, 'u'); // 上 dfs(grid, i + 1, j, s, 'd'); // 下 dfs(grid, i, j - 1, s, 'l'); // 左 dfs(grid, i, j + 1, s, 'r'); // 右 // 后序遍历位置:离开 (i, j) s += 'b'; } }; ``` ```go // by chatGPT (go) // LeetCode问题“不同岛屿的数量”的解决方案: https://leetcode.com/problems/number-of-distinct-islands/ // 作者:Obadiah Crowe // 时间:2021年7月29日 import ( "fmt" "strconv" "strings" ) func numDistinctIslands(grid [][]int) int { // 计算行数和列数 m, n := len(grid), len(grid[0]) // 存储所有不同岛屿的序列化结果 islands := make(map[string]bool) for i := 0; i < m; i++ { for j := 0; j < n; j++ { if grid[i][j] == 1 { // 淹没此岛屿,同时存储其序列化结果 var sb strings.Builder // 初始方向可以是任意的,不会影响正解 dfs(grid, i, j, &sb, 666) islands[sb.String()] = true /**<向上扩展-200> ![](../pictures/岛屿/6.png) */ } } } // 岛屿数量即为不同岛屿序列化结果的数量 return len(islands) } func dfs(grid [][]int, i, j int, sb *strings.Builder, dir int) { // 计算行数和列数 m, n := len(grid), len(grid[0]) // 如果(i, j)不在网格范围内, 或者是海洋, 直接返回 if i < 0 || j < 0 || i >= m || j >= n || grid[i][j] == 0 { return } // pre-order: 进入点(i, j) grid[i][j] = 0 // 淹没该岛屿 sb.WriteString(strconv.Itoa(dir) + ",") //记录方向 dfs(grid, i-1, j, sb, 1) // 上 dfs(grid, i+1, j, sb, 2) // 下 dfs(grid, i, j-1, sb, 3) // 左 dfs(grid, i, j+1, sb, 4) // 右 // post-order: 离开点(i, j) sb.WriteString(strconv.Itoa(-dir) + ",") // 记录方向 } ``` ```java // by labuladong (java) class Solution { public int numDistinctIslands(int[][] grid) { int m = grid.length, n = grid[0].length; // 记录所有岛屿的序列化结果 HashSet islands = new HashSet<>(); for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { if (grid[i][j] == 1) { // 淹掉这个岛屿,同时存储岛屿的序列化结果 StringBuilder sb = new StringBuilder(); // 初始的方向可以随便写,不影响正确性 dfs(grid, i, j, sb, 666); islands.add(sb.toString()); /** ![](../pictures/岛屿/6.png) */ } } } // 不相同的岛屿数量 return islands.size(); } private void dfs(int[][] grid, int i, int j, StringBuilder sb, int dir) { int m = grid.length, n = grid[0].length; if (i < 0 || j < 0 || i >= m || j >= n || grid[i][j] == 0) { return; } // 前序遍历位置:进入 (i, j) grid[i][j] = 0; sb.append(dir).append(','); dfs(grid, i - 1, j, sb, 1); // 上 dfs(grid, i + 1, j, sb, 2); // 下 dfs(grid, i, j - 1, sb, 3); // 左 dfs(grid, i, j + 1, sb, 4); // 右 // 后序遍历位置:离开 (i, j) sb.append(-dir).append(','); } } ``` ```javascript // by chatGPT (javascript) var numDistinctIslands = function(grid) { var m = grid.length, n = grid[0].length; // Record the serialization results of all islands var islands = new Set(); for(var i=0; i ![](../pictures/岛屿/6.png) */ } } } // The number of different islands return islands.size(); function dfs(grid, i, j, sb, dir) { if(i<0 || j<0 || i>=m || j>=n || grid[i][j] == 0) { return; } // Pre-order traversal position: enter (i, j) grid[i][j] = 0; sb.append(dir).append(','); dfs(grid, i-1, j, sb, 1); // up dfs(grid, i+1, j, sb, 2); // down dfs(grid, i, j-1, sb, 3); // left dfs(grid, i, j+1, sb, 4); // right // Post-order traversal position: leave (i, j) sb.append(-dir).append(','); } }; ``` ```python # by chatGPT (python) class Solution: def numDistinctIslands(self, grid: List[List[int]]) -> int: m, n = len(grid), len(grid[0]) islands = set() # 记录所有岛屿的序列化结果 for i in range(m): for j in range(n): if grid[i][j] == 1: # 如果岛屿是1 # 淹掉这个岛屿,同时存储岛屿的序列化结果 sb = [] # 没有StringBuilder,用数组代替 # 初始的方向可以随便写,不影响正确性 self.dfs(grid, i, j, sb, 666) islands.add("".join(sb)) #![](../pictures/岛屿/6.png) return len(islands) # 返回不同的岛屿数量 def dfs(self, grid: List[List[int]], i: int, j: int, sb: List[str], dir: int) -> None: m, n = len(grid), len(grid[0]) if i < 0 or j < 0 or i >= m or j >= n or grid[i][j] == 0: return # 前序遍历位置:进入 (i, j) grid[i][j] = 0 sb.append(str(dir)).append(',') self.dfs(grid, i - 1, j, sb, 1) # 上 self.dfs(grid, i + 1, j, sb, 2) # 下 self.dfs(grid, i, j - 1, sb, 3) # 左 self.dfs(grid, i, j + 1, sb, 4) # 右 # 后序遍历位置:离开 (i, j) sb.append(str(-dir)).append(',') ``` https://leetcode.cn/problems/number-of-distinct-islands 的多语言解法👆 https://leetcode.cn/problems/number-of-enclaves 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int numEnclaves(vector>& grid) { int m = grid.size(), n = grid[0].size(); for (int i = 0; i < m; i++) { dfs(grid, i, 0); dfs(grid, i, n - 1); } for (int j = 0; j < n; j++) { dfs(grid, 0, j); dfs(grid, m - 1, j); } int res = 0; for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { if (grid[i][j] == 1) { res += 1; } } } return res; } void dfs(vector>& grid, int i, int j) { int m = grid.size(), n = grid[0].size(); if (i < 0 || j < 0 || i >= m || j >= n || grid[i][j] == 0) { return; } grid[i][j] = 0; dfs(grid, i + 1, j); dfs(grid, i, j + 1); dfs(grid, i - 1, j); dfs(grid, i, j - 1); } }; ``` ```go // by chatGPT (go) // LeetCode 题目链接 https://leetcode-cn.com/problems/number-of-enclaves/ func numEnclaves(grid [][]int) int { m, n := len(grid), len(grid[0]) for i := 0; i < m; i++ { dfs(grid, i, 0) dfs(grid, i, n-1) } for j := 0; j < n; j++ { dfs(grid, 0, j) dfs(grid, m-1, j) } var res int for i := 0; i < m; i++ { for j := 0; j < n; j++ { if grid[i][j] == 1 { res += 1 } } } return res } func dfs(grid [][]int, i, j int) { m, n := len(grid), len(grid[0]) if i < 0 || j < 0 || i >= m || j >= n || grid[i][j] == 0 { return } grid[i][j] = 0 dfs(grid, i+1, j) dfs(grid, i, j+1) dfs(grid, i-1, j) dfs(grid, i, j-1) } ``` ```java // by labuladong (java) class Solution { public int numEnclaves(int[][] grid) { int m = grid.length, n = grid[0].length; for (int i = 0; i < m; i++) { dfs(grid, i, 0); dfs(grid, i, n - 1); } for (int j = 0; j < n; j++) { dfs(grid, 0, j); dfs(grid, m - 1, j); } int res = 0; for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { if (grid[i][j] == 1) { res += 1; } } } return res; } void dfs(int[][] grid, int i, int j) { int m = grid.length, n = grid[0].length; if (i < 0 || j < 0 || i >= m || j >= n || grid[i][j] == 0) { return; } grid[i][j] = 0; dfs(grid, i + 1, j); dfs(grid, i, j + 1); dfs(grid, i - 1, j); dfs(grid, i, j - 1); } } ``` ```javascript // by chatGPT (javascript) /** * @param {number[][]} grid * @return {number} */ var numEnclaves = function(grid) { var m = grid.length, n = grid[0].length; for (var i = 0; i < m; i++) { dfs(grid, i, 0); dfs(grid, i, n - 1); } for (var j = 0; j < n; j++) { dfs(grid, 0, j); dfs(grid, m - 1, j); } var res = 0; for (var row = 0; row < m; row++) { for (var col = 0; col < n; col++) { if (grid[row][col] === 1) { res += 1; } } } return res; }; function dfs(grid, i, j) { var m = grid.length, n = grid[0].length; if (i < 0 || j < 0 || i >= m || j >= n || grid[i][j] === 0) { return; } grid[i][j] = 0; dfs(grid, i + 1, j); dfs(grid, i, j + 1); dfs(grid, i - 1, j); dfs(grid, i, j - 1); } ``` ```python # by chatGPT (python) class Solution: def numEnclaves(self, grid: List[List[int]]) -> int: m, n = len(grid), len(grid[0]) for i in range(m): self.dfs(grid, i, 0) self.dfs(grid, i, n - 1) for j in range(n): self.dfs(grid, 0, j) self.dfs(grid, m - 1, j) res = 0 for i in range(m): for j in range(n): if grid[i][j] == 1: res += 1 return res def dfs(self, grid: List[List[int]], i: int, j: int) -> None: m, n = len(grid), len(grid[0]) if i < 0 or j < 0 or i >= m or j >= n or grid[i][j] == 0: return grid[i][j] = 0 self.dfs(grid, i + 1, j) self.dfs(grid, i, j + 1) self.dfs(grid, i - 1, j) self.dfs(grid, i, j - 1) ``` https://leetcode.cn/problems/number-of-enclaves 的多语言解法👆 https://leetcode.cn/problems/number-of-islands 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { // 主函数,计算岛屿数量 public: int numIslands(vector>& grid) { int res = 0; int m = grid.size(), n = grid[0].size(); // 遍历 grid 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; } private: // 从 (i, j) 开始,将与之相邻的陆地都变成海水 void dfs(vector>& grid, int i, int j) { int m = grid.size(), n = grid[0].size(); if (i < 0 || j < 0 || i >= m || j >= n) { // 超出索引边界 return; } if (grid[i][j] == '0') { // 已经是海水了 return; } // 将 (i, j) 变成海水 grid[i][j] = '0'; // 淹没上下左右的陆地 dfs(grid, i + 1, j); dfs(grid, i, j + 1); dfs(grid, i - 1, j); dfs(grid, i, j - 1); } }; ``` ```go // by chatGPT (go) func numIslands(grid [][]byte) int { res := 0 m, n := len(grid), len(grid[0]) // 遍历 grid for i := 0; i < m; i++ { for j := 0; j < n; j++ { if grid[i][j] == '1' { // 每发现一个岛屿,岛屿数量加一 res++ // 然后使用 DFS 将岛屿淹了 dfs(grid, i, j) } } } return res } // 从 (i, j) 开始,将与之相邻的陆地都变成海水 func dfs(grid [][]byte, i, j int) { m, n := len(grid), len(grid[0]) if i < 0 || j < 0 || i >= m || j >= n { // 超出索引边界 return } if grid[i][j] == '0' { // 已经是海水了 return } // 将 (i, j) 变成海水 grid[i][j] = '0' // 淹没上下左右的陆地 dfs(grid, i+1, j) dfs(grid, i, j+1) dfs(grid, i-1, j) dfs(grid, i, j-1) } ``` ```java // by labuladong (java) class Solution { // 主函数,计算岛屿数量 public int numIslands(char[][] grid) { int res = 0; int m = grid.length, n = grid[0].length; // 遍历 grid 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; } // 从 (i, j) 开始,将与之相邻的陆地都变成海水 void dfs(char[][] grid, int i, int j) { int m = grid.length, n = grid[0].length; if (i < 0 || j < 0 || i >= m || j >= n) { // 超出索引边界 return; } if (grid[i][j] == '0') { // 已经是海水了 return; } // 将 (i, j) 变成海水 grid[i][j] = '0'; // 淹没上下左右的陆地 dfs(grid, i + 1, j); dfs(grid, i, j + 1); dfs(grid, i - 1, j); dfs(grid, i, j - 1); } } ``` ```javascript // by chatGPT (javascript) var numIslands = function(grid) { var res = 0; var m = grid.length, n = grid[0].length; // 遍历 grid for (var i = 0; i < m; i++) { for (var j = 0; j < n; j++) { if (grid[i][j] == '1') { // 每发现一个岛屿,岛屿数量加一 res++; // 然后使用 DFS 将岛屿淹了 dfs(grid, i, j); } } } return res; }; // 从 (i, j) 开始,将与之相邻的陆地都变成海水 function dfs(grid, i, j) { var m = grid.length, n = grid[0].length; if (i < 0 || j < 0 || i >= m || j >= n) { // 超出索引边界 return; } if (grid[i][j] == '0') { // 已经是海水了 return; } // 将 (i, j) 变成海水 grid[i][j] = '0'; // 淹没上下左右的陆地 dfs(grid, i + 1, j); dfs(grid, i, j + 1); dfs(grid, i - 1, j); dfs(grid, i, j - 1); } ``` ```python # by chatGPT (python) class Solution: def numIslands(self, grid: List[List[str]]) -> int: res = 0 m = len(grid) n = len(grid[0]) # 遍历 grid for i in range(m): for j in range(n): if grid[i][j] == '1': # 每发现一个岛屿,岛屿数量加一 res += 1 # 然后使用 DFS 将岛屿淹了 self.dfs(grid, i, j) return res # 从 (i, j) 开始,将与之相邻的陆地都变成海水 def dfs(self, grid: List[List[str]], i: int, j: int) -> None: m = len(grid) n = len(grid[0]) if i < 0 or j < 0 or i >= m or j >= n: # 超出索引边界 return if grid[i][j] == '0': # 已经是海水了 return # 将 (i, j) 变成海水 grid[i][j] = '0' # 淹没上下左右的陆地 self.dfs(grid, i + 1, j) self.dfs(grid, i, j + 1) self.dfs(grid, i - 1, j) self.dfs(grid, i, j - 1) ``` https://leetcode.cn/problems/number-of-islands 的多语言解法👆 https://leetcode.cn/problems/number-of-students-unable-to-eat-lunch 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int countStudents(vector& students, vector& sandwiches) { //studentCount [0 or 1] 分别代表吃 0 和吃 1的学生数量 vector studentCount(2, 0); for (int type : students) { studentCount[type]++; } //遍历三明治栈,若栈顶的三明治无法被取走,则剩下的人都吃不上了 for (int type : sandwiches) { if (studentCount[type] == 0) { //两种喜好加起来就是剩下的学生数量 return studentCount[0] + studentCount[1]; } studentCount[type]--; } return 0; } }; ``` ```go // by chatGPT (go) func countStudents(students []int, sandwiches []int) int { // studentCount[0 or 1] 分别代表吃 0 和吃 1的学生数量 studentCount := [2]int{0, 0} for _, type_ := range students { studentCount[type_]++ } // 遍历三明治栈,若栈顶的三明治无法被取走,则剩下的人都吃不上了 for _, type_ := range sandwiches { if studentCount[type_] == 0 { // 两种喜好加起来就是剩下的学生数量 return studentCount[0] + studentCount[1] } studentCount[type_]-- } return 0 } ``` ```java // by labuladong (java) class Solution { public int countStudents(int[] students, int[] sandwiches) { // studentCount[0 or 1] 分别代表吃 0 和吃 1的学生数量 int[] studentCount = new int[2]; for (int type : students) { studentCount[type]++; } // 遍历三明治栈,若栈顶的三明治无法被取走,则剩下的人都吃不上了 for (int type : sandwiches) { if (studentCount[type] == 0) { // 两种喜好加起来就是剩下的学生数量 return studentCount[0] + studentCount[1]; } studentCount[type]--; } return 0; } } ``` ```javascript // by chatGPT (javascript) var countStudents = function(students, sandwiches) { // studentCount[0 or 1] 分别代表吃 0 和吃 1的学生数量 var studentCount = new Array(2).fill(0); for (var type of students) { studentCount[type]++; } // 遍历三明治栈,若栈顶的三明治无法被取走,则剩下的人都吃不上了 for (var type of sandwiches) { if (studentCount[type] == 0) { // 两种喜好加起来就是剩下的学生数量 return studentCount[0] + studentCount[1]; } studentCount[type]--; } return 0; } ``` ```python # by chatGPT (python) class Solution: def countStudents(self, students: List[int], sandwiches: List[int]) -> int: # studentCount[0 or 1] 分别代表吃 0 和吃 1的学生数量 studentCount = [0,0] for type in students: studentCount[type] += 1 # 遍历三明治栈,若栈顶的三明治无法被取走,则剩下的人都吃不上了 for type in sandwiches: if studentCount[type] == 0: # 两种喜好加起来就是剩下的学生数量 return studentCount[0] + studentCount[1] studentCount[type] -= 1 return 0 ``` https://leetcode.cn/problems/number-of-students-unable-to-eat-lunch 的多语言解法👆 https://leetcode.cn/problems/open-the-lock 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int openLock(vector& deadends, string target) { // 记录需要跳过的死亡密码 unordered_set deads(deadends.begin(), deadends.end()); // 记录已经穷举过的密码,防止走回头路 unordered_set visited; queue q; // 从起点开始启动广度优先搜索 int step = 0; q.push("0000"); visited.insert("0000"); while (!q.empty()) { int sz = q.size(); /* 将当前队列中的所有节点向周围扩散 */ for (int i = 0; i < sz; i++) { string cur = q.front(); q.pop(); /* 判断是否到达终点 */ if (deads.count(cur)) continue; if (cur == target) return step; /* 将一个节点的未遍历相邻节点加入队列 */ for (int j = 0; j < 4; j++) { string up = plusOne(cur, j); if (!visited.count(up)) { q.push(up); visited.insert(up); } string down = minusOne(cur, j); if (!visited.count(down)) { q.push(down); visited.insert(down); } } } /* 在这里增加步数 */ step++; } // 如果穷举完都没找到目标密码,那就是找不到了 return -1; } // 将 s[j] 向上拨动一次 string plusOne(string s, int j) { if (s[j] == '9') s[j] = '0'; else s[j] += 1; return s; } // 将 s[i] 向下拨动一次 string minusOne(string s, int j) { if (s[j] == '0') s[j] = '9'; else s[j] -= 1; return s; } }; ``` ```go // by chatGPT (go) import "fmt" func openLock(deadends []string, target string) int { // 记录需要跳过的死亡密码 deads := make(map[string]bool) for _, s := range deadends { deads[s] = true } // 记录已经穷举过的密码,防止走回头路 visited := make(map[string]bool) q := make([]string, 0) // 从起点开始启动广度优先搜索 step := 0 q = append(q, "0000") visited["0000"] = true for len(q) > 0 { sz := len(q) /* 将当前队列中的所有节点向周围扩散 */ for i := 0; i < sz; i++ { cur := q[0] q = q[1:] /* 判断是否到达终点 */ if deads[cur] { continue } if cur == target { return step } /* 将一个节点的未遍历相邻节点加入队列 */ for j := 0; j < 4; j++ { up := plusOne(cur, j) if !visited[up] { q = append(q, up) visited[up] = true } down := minusOne(cur, j) if !visited[down] { q = append(q, down) visited[down] = true } } } /* 在这里增加步数 */ step++ } // 如果穷举完都没找到目标密码,那就是找不到了 return -1 } // 将 s[j] 向上拨动一次 func plusOne(s string, j int) string { ch := []byte(s) if ch[j] == '9' { ch[j] = '0' } else { ch[j] += 1 } return string(ch) } // 将 s[i] 向下拨动一次 func minusOne(s string, j int) string { ch := []byte(s) if ch[j] == '0' { ch[j] = '9' } else { ch[j] -= 1 } return string(ch) } ``` ```java // by labuladong (java) class Solution { public int openLock(String[] deadends, String target) { // 记录需要跳过的死亡密码 Set deads = new HashSet<>(); for (String s : deadends) deads.add(s); // 记录已经穷举过的密码,防止走回头路 Set visited = new HashSet<>(); Queue q = new LinkedList<>(); // 从起点开始启动广度优先搜索 int step = 0; q.offer("0000"); visited.add("0000"); while (!q.isEmpty()) { int sz = q.size(); /* 将当前队列中的所有节点向周围扩散 */ for (int i = 0; i < sz; i++) { String cur = q.poll(); /* 判断是否到达终点 */ if (deads.contains(cur)) continue; if (cur.equals(target)) return step; /* 将一个节点的未遍历相邻节点加入队列 */ for (int j = 0; j < 4; j++) { String up = plusOne(cur, j); if (!visited.contains(up)) { q.offer(up); visited.add(up); } String down = minusOne(cur, j); if (!visited.contains(down)) { q.offer(down); visited.add(down); } } } /* 在这里增加步数 */ step++; } // 如果穷举完都没找到目标密码,那就是找不到了 return -1; } // 将 s[j] 向上拨动一次 String plusOne(String s, int j) { char[] ch = s.toCharArray(); if (ch[j] == '9') ch[j] = '0'; else ch[j] += 1; return new String(ch); } // 将 s[i] 向下拨动一次 String minusOne(String s, int j) { char[] ch = s.toCharArray(); if (ch[j] == '0') ch[j] = '9'; else ch[j] -= 1; return new String(ch); } } ``` ```javascript // by chatGPT (javascript) var openLock = function(deadends, target) { // 记录需要跳过的死亡密码 const deads = new Set(deadends); // 记录已经穷举过的密码,防止走回头路 const visited = new Set(); const q = []; // 从起点开始启动广度优先搜索 let step = 0; q.push("0000"); visited.add("0000"); while (q.length > 0) { let sz = q.length; /* 将当前队列中的所有节点向周围扩散 */ for (let i = 0; i < sz; i++) { let cur = q.shift(); /* 判断是否到达终点 */ if (deads.has(cur)) continue; if (cur === target) return step; /* 将一个节点的未遍历相邻节点加入队列 */ for (let j = 0; j < 4; j++) { let up = plusOne(cur, j); if (!visited.has(up)) { q.push(up); visited.add(up); } let down = minusOne(cur, j); if (!visited.has(down)) { q.push(down); visited.add(down); } } } /* 在这里增加步数 */ step++; } // 如果穷举完都没找到目标密码,那就是找不到了 return -1; } // 将 s[j] 向上拨动一次 function plusOne(s, j) { let ch = s.split(""); if (ch[j] === "9") ch[j] = "0"; else ch[j] = String(+ch[j]+1); return ch.join(""); } // 将 s[i] 向下拨动一次 function minusOne(s, j) { let ch = s.split(""); if (ch[j] === "0") ch[j] = "9"; else ch[j] = String(+ch[j]-1); return ch.join(""); } ``` ```python # by chatGPT (python) class Solution: def openLock(self, deadends: List[str], target: str) -> int: # 记录需要跳过的死亡密码 deads = set(deadends) # 记录已经穷举过的密码,防止走回头路 visited = set() q = collections.deque() # 从起点开始启动广度优先搜索 step = 0 q.append("0000") visited.add("0000") while q: sz = len(q) # 将当前队列中的所有节点向周围扩散 for i in range(sz): cur = q.popleft() # 判断是否到达终点 if cur in deads: continue if cur == target: return step # 将一个节点的未遍历相邻节点加入队列 for j in range(4): up = plusOne(cur,j) if up not in visited: q.append(up) visited.add(up) down = minusOne(cur,j) if down not in visited: q.append(down) visited.add(down) # 在这里增加步数 step += 1 # 如果穷举完都没找到目标密码,那就是找不到了 return -1 # 将 s[j] 向上拨动一次 def plusOne(s: str, j: int) -> str: ch = list(s) if ch[j] == '9': ch[j] = '0' else: ch[j] = chr(ord(ch[j])+1) return "".join(ch) # 将 s[i] 向下拨动一次 def minusOne(s: str, j: int) -> str: ch = list(s) if ch[j] == '0': ch[j] = '9' else: ch[j] = chr(ord(ch[j])-1) return "".join(ch) ``` https://leetcode.cn/problems/open-the-lock 的多语言解法👆 https://leetcode.cn/problems/out-of-boundary-paths 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { private: int memo[50][50][51]; int m, n; public: //定义一个记录出界路径数量的函数 int findPaths(int m, int n, int maxMove, int startRow, int startColumn) { this->m = m; this->n = n; // 备忘录初始化为特殊值 -1 memset(memo, -1, sizeof(memo)); // 按照定义,计算从 (startRow, startColumn) 开始,最大移动次数为 maxMove 的出界路径数量 return dp(maxMove, startRow, startColumn); } //从 (i,j) 出发,最大移动次数为 maxMove,出界的路径数量为 dp(maxMove, i, j) int dp(int maxMove, int i, int j) { // base case,找到一条出界路径 if (i < 0 || j < 0 || i >= m || j >= n) { return 1; } // 避免冗余计算 if (memo[i][j][maxMove] != -1) { return memo[i][j][maxMove]; } // 无法在有限的步数内出界 if (maxMove == 0) { return 0; } // 状态转移关系: // 在 maxMove 步之内从 (i, j) 踢出界的路径数量等于 // 在 maxMove - 1 步之内从 (i, j) 的相邻位置踢出界的路径数量之和 long res = 0; res += dp(maxMove - 1, i, j + 1); //向右走 res += dp(maxMove - 1, i + 1, j); //向下走 res += dp(maxMove - 1, i - 1, j); //向上走 res += dp(maxMove - 1, i, j - 1); //向左走 memo[i][j][maxMove] = (int) (res % 1000000007); // 保存结果 return memo[i][j][maxMove]; } }; ``` ```go // by chatGPT (go) // 定义一个三维备忘录 func findPaths(m int, n int, maxMove int, startRow int, startColumn int) int { memo := make([][][]int, m) for i := 0; i < m; i++ { memo[i] = make([][]int, n) for j := 0; j < n; j++ { memo[i][j] = make([]int, maxMove+1) for k := 0; k <= maxMove; k++ { // 备忘录初始化为 -1 memo[i][j][k] = -1 } } } // 按照定义,计算从 (startRow, startColumn) 开始,最大移动次数为 maxMove 的出界路径数量 return dp(memo, maxMove, startRow, startColumn, m, n) } // 定义:从 (i, j) 出发,最大移动次数为 maxMove,出界的路径数量为 dp(memo, maxMove, i, j) func dp(memo [][][]int, maxMove int, i int, j int, m int, n int) int { // base case,找到一条出界路径 if i < 0 || j < 0 || i >= m || j >= n { return 1 } // 避免冗余计算 if memo[i][j][maxMove] != -1 { return memo[i][j][maxMove] } // 无法在有限的步数内出界 if maxMove == 0 { return 0 } // 状态转移关系: // 在 maxMove 步之内从 (i, j) 踢出界的路径数量等于 // 在 maxMove - 1 步之内从 (i, j) 的相邻位置踢出界的路径数量之和 var res, mod int mod = 1e9 + 7 res += dp(memo, maxMove-1, i, j+1, m, n) res += dp(memo, maxMove-1, i+1, j, m, n) res += dp(memo, maxMove-1, i-1, j, m, n) res += dp(memo, maxMove-1, i, j-1, m, n) memo[i][j][maxMove] = res % mod return memo[i][j][maxMove] } ``` ```java // by labuladong (java) class Solution { int[][][] memo; int m, n; public int findPaths(int m, int n, int maxMove, int startRow, int startColumn) { this.m = m; this.n = n; memo = new int[m][n][maxMove + 1]; // 备忘录初始化为特殊值 -1 for (int[][] plane : memo) { for (int[] row : plane) { Arrays.fill(row, -1); } } // 按照定义,计算从 (startRow, startColumn) 开始,最大移动次数为 maxMove 的出界路径数量 return dp(maxMove, startRow, startColumn); } // 定义:从 (i, j) 出发,最大移动次数为 maxMove,出界的路径数量为 dp(maxMove, i, j) int dp(int maxMove, int i, int j) { // base case,找到一条出界路径 if (i < 0 || j < 0 || i >= m || j >= n) { return 1; } // 避免冗余计算 if (memo[i][j][maxMove] != -1) { return memo[i][j][maxMove]; } // 无法在有限的步数内出界 if (maxMove == 0) { return 0; } // 状态转移关系: // 在 maxMove 步之内从 (i, j) 踢出界的路径数量等于 // 在 maxMove - 1 步之内从 (i, j) 的相邻位置踢出界的路径数量之和 long res = 0; res += dp(maxMove - 1, i, j + 1); res += dp(maxMove - 1, i + 1, j); res += dp(maxMove - 1, i - 1, j); res += dp(maxMove - 1, i, j - 1); memo[i][j][maxMove] = (int) (res % 1000000007); return memo[i][j][maxMove]; } } ``` ```javascript // by chatGPT (javascript) /** * @param {number} m * @param {number} n * @param {number} maxMove * @param {number} startRow * @param {number} startColumn * @return {number} */ var findPaths = function(m, n, maxMove, startRow, startColumn) { // memo数组用于存储计算过的结果,避免重复计算 let memo = new Array(m); for (let i = 0; i < m; i++) { memo[i] = new Array(n); for (let j = 0; j < n; j++) { memo[i][j] = new Array(maxMove + 1).fill(-1); } } // 按照定义,计算从(startRow, startColumn)开始,最大移动次数为maxMove的出界路径数量 return dp(maxMove, startRow, startColumn); // 定义:从(i, j)出发,最大移动次数为maxMove,出界的路径数量为dp(maxMove, i, j) function dp(maxMove, i, j) { // base case,找到一条出界路径 if (i < 0 || j < 0 || i >= m || j >= n) { return 1; } // 避免冗余计算 if (memo[i][j][maxMove] !== -1) { return memo[i][j][maxMove]; } // 无法在有限的步数内出界 if (maxMove === 0) { return 0; } // 状态转移关系: // 在 maxMove 步之内从 (i, j) 踢出界的路径数量等于 // 在 maxMove - 1 步之内从 (i, j) 的相邻位置踢出界的路径数量之和 let res = 0; res += dp(maxMove - 1, i, j + 1); res += dp(maxMove - 1, i + 1, j); res += dp(maxMove - 1, i - 1, j); res += dp(maxMove - 1, i, j - 1); memo[i][j][maxMove] = res % 1000000007; return memo[i][j][maxMove]; } }; ``` ```python # by chatGPT (python) class Solution: def findPaths(self, m: int, n: int, maxMove: int, startRow: int, startColumn: int) -> int: memo = [[[-1 for _ in range(maxMove + 1)] for _ in range(n)] for _ in range(m)] def dp(maxMove: int, i: int, j: int) -> int: # base case,找到一条出界路径 if i < 0 or j < 0 or i >= m or j >= n: return 1 # 避免冗余计算 if memo[i][j][maxMove] != -1: return memo[i][j][maxMove] # 无法在有限的步数内出界 if maxMove == 0: return 0 # 状态转移关系: # 在 maxMove 步之内从 (i, j) 踢出界的路径数量等于 # 在 maxMove - 1 步之内从 (i, j) 的相邻位置踢出界的路径数量之和 res = 0 res += dp(maxMove - 1, i, j + 1) res += dp(maxMove - 1, i + 1, j) res += dp(maxMove - 1, i - 1, j) res += dp(maxMove - 1, i, j - 1) memo[i][j][maxMove] = res % 1000000007 return memo[i][j][maxMove] # 备忘录初始化为特殊值 -1 for plane in memo: for row in plane: for k in range(maxMove + 1): row[k] = -1 # 按照定义,计算从 (startRow, startColumn) 开始,最大移动次数为 maxMove 的出界路径数量 return dp(maxMove, startRow, startColumn) ``` https://leetcode.cn/problems/out-of-boundary-paths 的多语言解法👆 https://leetcode.cn/problems/palindrome-linked-list 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: bool isPalindrome(ListNode* head) { ListNode *slow, *fast; // Define two pointers slow = fast = head; // Initialize the pointers while (fast != nullptr && fast->next != nullptr) { // Loop until fast pointer reaches the end slow = slow->next; // Move slow pointer one step fast = fast->next->next; // Move fast pointer two step } if (fast != nullptr) // If fast pointer is not nullptr slow = slow->next; // Move the slow pointer one step ListNode *left = head; // Initialize left pointer to head ListNode *right = reverse(slow); // Reverse the right half of the list and make the right pointer point to the new head while (right != nullptr) { // Loop until right pointer is nullptr if (left->val != right->val) // If the values of left and right pointers are not equal return false; // Return false left = left->next; // Move the left pointer one step right = right->next; // Move the right pointer one step } return true; // Return true if all elements in the list are equal to their corresponding elements in the reversed list } ListNode* reverse(ListNode* head) { ListNode *pre = nullptr, *cur = head; // Define two pointers while (cur != nullptr) { // Loop until the last node of the list is reached ListNode *next = cur->next; // Store the next pointer temporarily cur->next = pre; // Reverse the pointer pre = cur; // Move the pre pointer one step cur = next; // Move the cur pointer one step forward } return pre; // Return the new head } }; ``` ```go // by chatGPT (go) func isPalindrome(head *ListNode) bool { slow, fast := head, head for fast != nil && fast.Next != nil { slow = slow.Next fast = fast.Next.Next } if fast != nil { slow = slow.Next } left, right := head, reverse(slow) for right != nil { if left.Val != right.Val { return false } left = left.Next right = right.Next } return true } func reverse(head *ListNode) *ListNode { var pre, cur *ListNode cur = head for cur != nil { next := cur.Next cur.Next = pre pre = cur cur = next } return pre } ``` ```java // by labuladong (java) class Solution { public boolean isPalindrome(ListNode head) { ListNode slow, fast; slow = fast = head; while (fast != null && fast.next != null) { slow = slow.next; fast = fast.next.next; } if (fast != null) slow = slow.next; ListNode left = head; ListNode right = reverse(slow); while (right != null) { if (left.val != right.val) return false; left = left.next; right = right.next; } return true; } 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; } } ``` ```javascript // by chatGPT (javascript) var isPalindrome = function(head) { let [slow, fast] = [head, head]; while (fast !== null && fast.next !== null) { slow = slow.next; fast = fast.next.next; } if (fast !== null) slow = slow.next; let left = head; let right = reverse(slow); while (right !== null) { if (left.val !== right.val) return false; left = left.next; right = right.next; } return true; }; function reverse(head) { let [pre, cur] = [null, head]; while (cur !== null) { let next = cur.next; cur.next = pre; pre = cur; cur = next; } return pre; } ``` ```python # by chatGPT (python) class Solution: def isPalindrome(self, head: ListNode) -> bool: slow, fast = head, head while fast and fast.next: slow = slow.next fast = fast.next.next if fast: slow = slow.next left = head right = self.reverse(slow) while right: if left.val != right.val: return False left = left.next right = right.next return True def reverse(self, head: ListNode) -> ListNode: pre, cur = None, head while cur: next_node = cur.next cur.next = pre pre = cur cur = next_node return pre ``` https://leetcode.cn/problems/palindrome-linked-list 的多语言解法👆 https://leetcode.cn/problems/palindrome-number 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: bool isPalindrome(int x) { if (x < 0) { return false; } int temp = x; // y 是 x 翻转后的数字 int y = 0; while (temp > 0) { int last_num = temp % 10; temp = temp / 10; // 从最高位生成数字的技巧 y = y * 10 + last_num; } return y == x; } }; ``` ```go // by chatGPT (go) // IsPalindrome 是判断一个整数是否为回文数的函数 func IsPalindrome(x int) bool { // 如果 x 是负数,那么它不可能为回文数,直接返回 false if x < 0 { return false } // temp 是 x 的副本 temp := x // y 是 x 翻转后的数字 y := 0 // 将 temp 逆序生成 y for temp > 0 { lastNum := temp % 10 temp = temp / 10 y = y*10 + lastNum } // 如果 x 和 y 相等,那么 x 就是回文数 return y == x } ``` ```java // by labuladong (java) class Solution { public boolean isPalindrome(int x) { if (x < 0) { return false; } int temp = x; // y 是 x 翻转后的数字 int y = 0; while (temp > 0) { int last_num = temp % 10; temp = temp / 10; // 从最高位生成数字的技巧 y = y * 10 + last_num; } return y == x; } } ``` ```javascript // by chatGPT (javascript) /** * @param {number} x * @return {boolean} */ var isPalindrome = function(x) { if (x < 0) { return false; } var temp = x; // y 是 x 翻转后的数字 var y = 0; while (temp > 0) { var last_num = temp % 10; temp = Math.floor(temp / 10); // 从最高位生成数字的技巧 y = y * 10 + last_num; } return y == x; }; ``` ```python # by chatGPT (python) class Solution: def isPalindrome(self, x: int) -> bool: if x < 0: return False temp = x # y 是 x 翻转后的数字 y = 0 while temp > 0: last_num = temp % 10 temp = temp // 10 # 从最高位生成数字的技巧 y = y * 10 + last_num return y == x ``` https://leetcode.cn/problems/palindrome-number 的多语言解法👆 https://leetcode.cn/problems/pancake-sorting 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { vector res; public: vector pancakeSort(vector& cakes) { sort(cakes, cakes.size()); return res; } void sort(vector& cakes, int n) { // base case if (n == 1) return; // 寻找最大饼的索引 int maxCake = 0; int maxCakeIndex = 0; for (int i = 0; i < n; i++) if (cakes[i] > maxCake) { maxCakeIndex = i; maxCake = cakes[i]; } // 第一次翻转,将最大饼翻到最上面 reverse(cakes, 0, maxCakeIndex); res.push_back(maxCakeIndex + 1); // 第二次翻转,将最大饼翻到最下面 reverse(cakes, 0, n - 1); res.push_back(n); //递归调用 sort(cakes, n - 1); } void reverse(vector& arr, int i, int j) { while (i < j) { int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; i++; j--; } } }; ``` ```go // by chatGPT (go) /** * Definition for singly-linked list. * type ListNode struct { * Val int * Next *ListNode * } */ func pancakeSort(cakes []int) []int { // 记录反转操作序列 res := []int{} sort(cakes, len(cakes), &res) return res } func sort(cakes []int, n int, res *[]int) { // base case if n == 1 { return } // 寻找最大饼的索引 maxCake := 0 maxCakeIndex := 0 for i := 0; i < n; i++ { if cakes[i] > maxCake { maxCakeIndex = i maxCake = cakes[i] } } // 第一次翻转,将最大饼翻到最上面 reverse(cakes, 0, maxCakeIndex) *res = append(*res, maxCakeIndex+1) // 第二次翻转,将最大饼翻到最下面 reverse(cakes, 0, n-1) *res = append(*res, n) // 递归调用 sort(cakes, n-1, res) } func reverse(arr []int, i int, j int) { for i < j { temp := arr[i] arr[i] = arr[j]; arr[j] = temp; i++ j-- } } ``` ```java // by labuladong (java) class Solution { // 记录反转操作序列 LinkedList res = new LinkedList<>(); public List pancakeSort(int[] cakes) { sort(cakes, cakes.length); return res; } void sort(int[] cakes, int n) { // base case if (n == 1) return; // 寻找最大饼的索引 int maxCake = 0; int maxCakeIndex = 0; for (int i = 0; i < n; i++) if (cakes[i] > maxCake) { maxCakeIndex = i; maxCake = cakes[i]; } // 第一次翻转,将最大饼翻到最上面 reverse(cakes, 0, maxCakeIndex); res.add(maxCakeIndex + 1); // 第二次翻转,将最大饼翻到最下面 reverse(cakes, 0, n - 1); res.add(n); /** ![](../pictures/pancakeSort/3.jpg) */ // 递归调用 sort(cakes, n - 1); /** ![](../pictures/pancakeSort/4.jpg) */ } void reverse(int[] arr, int i, int j) { while (i < j) { int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; i++; j--; } } } ``` ```javascript // by chatGPT (javascript) var pancakeSort = function(cakes) { // Record the flip sequence // 记录反转操作序列 const res = []; // Sort the cakes array and record the flipping sequence // 对蛋糕数组进行排序,并记录翻转序列 sort(cakes, cakes.length); // Return the flipping sequence // 返回翻转序列 return res; /** * Sort the cakes array in decreasing order recursively * 递归以递减的顺序对蛋糕数组排序 * @param {number[]} cakes - The array for sorting * @param {number} n - The length of the array */ function sort(cakes, n) { // Base case // 基本情况 if (n == 1) return; // Find the index of max cake // 找到最大蛋糕的索引 let maxCake = 0; let maxCakeIndex = 0; for (let i = 0; i < n; i++) { if (cakes[i] > maxCake) { maxCakeIndex = i; maxCake = cakes[i]; } } // First flip to bring the max cake to the top // 第一次翻转,将最大饼翻到最上面 reverse(cakes, 0, maxCakeIndex); res.push(maxCakeIndex + 1); // Second flip the max cake to bring it to the bottom // 第二次翻转,将最大饼翻到最下面 reverse(cakes, 0, n - 1); res.push(n); // Recursively call the sort function with length-1 // 递归调用长度减1的 sort 函数 sort(cakes, n - 1); } /** * Flip the cake array from index i to index j * 翻转蛋糕数组从 i 到 j 索引 * @param {number[]} arr - The array for flipping * @param {number} i - The start index * @param {number} j - The end index */ function reverse(arr, i, j) { while (i < j) { let temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; i++; j--; } } }; ``` ```python # by chatGPT (python) class Solution: def __init__(self): self.res = [] #记录反转操作序列 def pancakeSort(self, cakes: List[int]) -> List[int]: self.sort(cakes, len(cakes)) return self.res def sort(self, cakes: List[int], n: int) -> None: # base case if n == 1: return # 寻找最大饼的索引 maxCake = 0 maxCakeIndex = 0 for i in range(n): if cakes[i] > maxCake: maxCakeIndex = i maxCake = cakes[i] # 第一次翻转,将最大饼翻到最上面 self.reverse(cakes, 0, maxCakeIndex) self.res.append(maxCakeIndex + 1) # 第二次翻转,将最大饼翻到最下面 self.reverse(cakes, 0, n - 1) self.res.append(n) #递归调用 self.sort(cakes, n - 1) def reverse(self, arr: List[int], i: int, j: int) -> None: while i < j: arr[i], arr[j] = arr[j], arr[i] i += 1 j -= 1 ``` https://leetcode.cn/problems/pancake-sorting 的多语言解法👆 https://leetcode.cn/problems/partition-equal-subset-sum 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: bool canPartition(vector& nums) { int sum = 0; for (int num : nums) sum += num; // 和为奇数时,不可能划分成两个和相等的集合 if (sum % 2 != 0) return false; int n = nums.size(); sum = sum / 2; vector> dp(n + 1, vector(sum + 1, false)); // base case for (int i = 0; i <= n; i++) dp[i][0] = true; for (int i = 1; i <= n; i++) { for (int j = 1; j <= sum; j++) { if (j - nums[i - 1] < 0) { // 背包容量不足,不能装入第 i 个物品 dp[i][j] = dp[i - 1][j]; } else { // 装入或不装入背包 dp[i][j] = dp[i - 1][j] || dp[i - 1][j - nums[i - 1]]; } } } return dp[n][sum]; } }; ``` ```go // by chatGPT (go) func canPartition(nums []int) bool { var sum int for _, num := range nums { sum += num } // 和为奇数时,不可能划分成两个和相等的集合 if sum % 2 != 0 { return false } n := len(nums) sum = sum / 2 dp := make([][]bool, n+1) for i := range dp { dp[i] = make([]bool, sum+1) } // base case for i := 0; i <= n; i++ { dp[i][0] = true } for i := 1; i <= n; i++ { for j := 1; j <= sum; j++ { if j - nums[i-1] < 0 { // 背包容量不足,不能装入第 i 个物品 dp[i][j] = dp[i - 1][j] } else { // 装入或不装入背包 dp[i][j] = dp[i - 1][j] || dp[i - 1][j - nums[i - 1]] } } } return dp[n][sum] } ``` ```java // by labuladong (java) class Solution { public boolean canPartition(int[] nums) { int sum = 0; for (int num : nums) sum += num; // 和为奇数时,不可能划分成两个和相等的集合 if (sum % 2 != 0) return false; int n = nums.length; sum = sum / 2; boolean[][] dp = new boolean[n + 1][sum + 1]; // base case for (int i = 0; i <= n; i++) dp[i][0] = true; for (int i = 1; i <= n; i++) { for (int j = 1; j <= sum; j++) { if (j - nums[i - 1] < 0) { // 背包容量不足,不能装入第 i 个物品 dp[i][j] = dp[i - 1][j]; } else { // 装入或不装入背包 dp[i][j] = dp[i - 1][j] || dp[i - 1][j - nums[i - 1]]; } } } return dp[n][sum]; } } ``` ```javascript // by chatGPT (javascript) var canPartition = function(nums) { var sum = nums.reduce(function(acc, curr){return acc + curr}); // 和为奇数时,不可能划分成两个和相等的集合 if (sum % 2 !== 0) return false; var n = nums.length; sum = sum / 2; var dp = new Array(n + 1).fill().map(() => new Array(sum + 1).fill(false)); // base case for (var i = 0; i <= n; i++) dp[i][0] = true; for (var i = 1; i <= n; i++) { for (var j = 1; j <= sum; j++) { if (j - nums[i - 1] < 0) { // 背包容量不足,不能装入第 i 个物品 dp[i][j] = dp[i - 1][j]; } else { // 装入或不装入背包 dp[i][j] = dp[i - 1][j] || dp[i - 1][j - nums[i - 1]]; } } } return dp[n][sum]; }; ``` ```python # by chatGPT (python) class Solution: def canPartition(self, nums: List[int]) -> bool: sum_ = sum(nums) # 和为奇数时,不可能划分成两个和相等的集合 if sum_ % 2 != 0: return False n = len(nums) sum_ = sum_ // 2 dp = [[False] * (sum_ + 1) for _ in range(n + 1)] # base case for i in range(n + 1): dp[i][0] = True for i in range(1, n + 1): for j in range(1, sum_ + 1): if j - nums[i - 1] < 0: # 背包容量不足,不能装入第 i 个物品 dp[i][j] = dp[i - 1][j] else: # 装入或不装入背包 dp[i][j] = dp[i - 1][j] or dp[i - 1][j - nums[i - 1]] return dp[n][sum_] ``` https://leetcode.cn/problems/partition-equal-subset-sum 的多语言解法👆 https://leetcode.cn/problems/partition-to-k-equal-sum-subsets 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: bool canPartitionKSubsets(vector& nums, int k) { // 排除一些基本情况 if (k > nums.size()) return false; int sum = accumulate(nums.begin(), nums.end(), 0); if (sum % k != 0) return false; int used = 0; int target = sum / k; return backtrack(k, 0, nums, 0, used, target); } private: unordered_map memo; bool backtrack(int k, int bucket, vector& nums, int start, int used, int target) { if (k == 0) { return true; } if (bucket == target) { bool res = backtrack(k - 1, 0, nums, 0, used, target); memo[used] = res; return res; } if (memo.count(used)) { return memo[used]; } for (int i = start; i < nums.size(); i++) { if ((used >> i) & 1) { continue; } if (nums[i] + bucket > target) { continue; } used |= 1 << i; bucket += nums[i]; if (backtrack(k, bucket, nums, i + 1, used, target)) { return true; } used ^= 1 << i; bucket -= nums[i]; } return false; } }; ``` ```go // by chatGPT (go) func canPartitionKSubsets(nums []int, k int) bool { memo := make(map[int]bool) // 排除一些基本情况 if k > len(nums) { return false } sum := 0 for _, v := range nums { sum += v } if sum%k != 0 { return false } var backtrack func(k, bucket int, nums []int, start, used, target int) bool backtrack = func(k, bucket int, nums []int, start, used, target int) bool { // base case if k == 0 { // 所有桶都被装满了,而且 nums 一定全部用完了 return true } if bucket == target { // 装满了当前桶,递归穷举下一个桶的选择 // 让下一个桶从 nums[0] 开始选数字 if res, ok := memo[used]; ok { return res } res := backtrack(k-1, 0, nums, 0, used, target) // 缓存结果 memo[used] = res return res } if res, ok := memo[used]; ok { // 避免冗余计算 return res } for i := start; i < len(nums); i++ { // 剪枝 if (used>>i)&1 == 1 { // 判断第 i 位是否是 1 // nums[i] 已经被装入别的桶中 continue } if nums[i]+bucket > target { continue } // 做选择 newUsed := used | 1< nums.length) return false; int sum = 0; for (int v : nums) sum += v; if (sum % k != 0) return false; int used = 0; // 使用位图技巧 int target = sum / k; // k 号桶初始什么都没装,从 nums[0] 开始做选择 return backtrack(k, 0, nums, 0, used, target); } HashMap memo = new HashMap<>(); boolean backtrack(int k, int bucket, int[] nums, int start, int used, int target) { // base case if (k == 0) { // 所有桶都被装满了,而且 nums 一定全部用完了 return true; } if (bucket == target) { // 装满了当前桶,递归穷举下一个桶的选择 // 让下一个桶从 nums[0] 开始选数字 boolean res = backtrack(k - 1, 0, nums, 0, used, target); // 缓存结果 memo.put(used, res); return res; } if (memo.containsKey(used)) { // 避免冗余计算 return memo.get(used); } for (int i = start; i < nums.length; i++) { // 剪枝 if (((used >> i) & 1) == 1) { // 判断第 i 位是否是 1 // nums[i] 已经被装入别的桶中 continue; } if (nums[i] + bucket > target) { continue; } // 做选择 used |= 1 << i; // 将第 i 位置为 1 bucket += nums[i]; /** ![](../pictures/集合划分/5.jpeg) */ // 递归穷举下一个数字是否装入当前桶 if (backtrack(k, bucket, nums, i + 1, used, target)) { return true; } // 撤销选择 used ^= 1 << i; // 将第 i 位置为 0 bucket -= nums[i]; } return false; } } ``` ```javascript // by chatGPT (javascript) var canPartitionKSubsets = function(nums, k) { let memo = new Map(); function backtrack(k, bucket, nums, start, used, target) { // base case if (k == 0) { // 所有桶都被装满了,而且 nums 一定全部用完了 return true; } if (bucket == target) { // 装满了当前桶,递归穷举下一个桶的选择 // 让下一个桶从 nums[0] 开始选数字 let res = backtrack(k - 1, 0, nums, 0, used, target); // 缓存结果 memo.set(used, res); return res; } if (memo.has(used)) { // 避免冗余计算 return memo.get(used); } for (let i = start; i < nums.length; i++) { // 剪枝 if (((used >> i) & 1) == 1) { // 判断第 i 位是否是 1 // nums[i] 已经被装入别的桶中 continue; } if (nums[i] + bucket > target) { continue; } // 做选择 let newUsed = used | (1 << i); // 将第 i 位置为 1 let newBucket = bucket + nums[i]; /** ![](../pictures/集合划分/5.jpeg) */ // 递归穷举下一个数字是否装入当前桶 if (backtrack(k, newBucket, nums, i + 1, newUsed, target)) { return true; } // 撤销选择 newUsed ^= (1 << i); // 将第 i 位置为 0 newBucket -= nums[i]; } memo.set(used, false); return false; } // 排除一些基本情况 if (k > nums.length) return false; let sum = 0; for (let v of nums) sum += v; if (sum % k != 0) return false; let used = 0; // 使用位图技巧 let target = sum / k; // k 号桶初始什么都没装,从 nums[0] 开始做选择 return backtrack(k, 0, nums, 0, used, target); }; ``` ```python # by chatGPT (python) class Solution: def canPartitionKSubsets(self, nums: List[int], k: int) -> bool: # 排除一些基本情况 if k > len(nums): return False _sum = sum(nums) if _sum % k != 0: return False used = 0 # 使用位图技巧 target = _sum // k # k 号桶初始什么都没装,从 nums[0] 开始做选择 return self.backtrack(k, 0, nums, 0, used, target, {}) def backtrack(self, k: int, bucket: int, nums: List[int], start: int, used: int, target: int, memo: Dict[int, bool]) -> bool: # base case if k == 0: # 所有桶都被装满了,而且 nums 一定全部用完了 return True if bucket == target: # 装满了当前桶,递归穷举下一个桶的选择 # 让下一个桶从 nums[0] 开始选数字 if used in memo: return memo[used] res = self.backtrack(k - 1, 0, nums, 0, used, target, memo) # 缓存结果 memo[used] = res return res for i in range(start, len(nums)): # 剪枝 if used >> i & 1 == 1: # nums[i] 已经被装入别的桶中 continue if nums[i] + bucket > target: continue # 做选择 used |= 1 << i # 将第 i 位置为 1 bucket += nums[i] # 递归穷举下一个数字是否装入当前桶 if self.backtrack(k, bucket, nums, i + 1, used, target, memo): return True # 撤销选择 used ^= 1 << i # 将第 i 位置为 0 bucket -= nums[i] return False ``` https://leetcode.cn/problems/partition-to-k-equal-sum-subsets 的多语言解法👆 https://leetcode.cn/problems/pascals-triangle 的多语言解法👇 ```cpp // by chatGPT (cpp) // 迭代方式 class Solution { public: vector> generate(int numRows) { vector> res; if (numRows < 1) { return res; } // 先把第一层装进去作为 base case vector firstRow(1, 1); res.push_back(firstRow); // 开始一层一层生成,装入 res for (int i = 2; i <= numRows; i++) { vector prevRow = res.back(); res.push_back(generateNextRow(prevRow)); } return res; } // 输入上一层的元素,生成并返回下一层的元素 vector generateNextRow(vector& prevRow) { vector curRow(1, 1); for (int i = 0; i < prevRow.size() - 1; i++) { curRow.push_back(prevRow[i] + prevRow[i + 1]); } curRow.push_back(1); return curRow; } }; // 递归方式 class Solution2 { public: // 定义:输入 numRows,返回行数为 numRows 的杨辉三角 vector> generate(int numRows) { // 递归的 base case if (numRows == 1) { vector> triangle(1, vector(1, 1)); return triangle; } // 先递归生成高度为 numRows - 1 的杨辉三角 vector> triangle = generate(numRows - 1); // 根据最底层元素生成一行新元素 vector bottomRow = triangle.back(); vector newRow(1, 1); for (int i = 0; i < bottomRow.size() - 1; i++) { newRow.push_back(bottomRow[i] + bottomRow[i + 1]); } newRow.push_back(1); // 把新的一行放到杨辉三角底部 triangle.push_back(newRow); return triangle; } }; ``` ```go // by chatGPT (go) // 迭代方式 func generate(numRows int) [][]int { res := [][]int{} if numRows < 1 { return res } // 先把第一层装进去作为 base case firstRow := []int{1} res = append(res, firstRow) // 开始一层一层生成,装入 res for i := 2; i <= numRows; i++ { prevRow := res[len(res)-1] res = append(res, generateNextRow(prevRow)) } return res } // 输入上一层的元素,生成并返回下一层的元素 func generateNextRow(prevRow []int) []int { curRow := []int{1} for i := 0; i < len(prevRow)-1; i++ { curRow = append(curRow, prevRow[i]+prevRow[i+1]) } curRow = append(curRow, 1) return curRow } // 递归方式 func generate(numRows int) [][]int { // 递归的 base case if numRows == 1 { triangle := [][]int{} // 先把第一层装进去作为 base case firstRow := []int{1} triangle = append(triangle, firstRow) return triangle } // 先递归生成高度为 numRows - 1 的杨辉三角 triangle := generate(numRows - 1) // 根据最底层元素生成一行新元素 bottomRow := triangle[len(triangle)-1] newRow := []int{1} for i := 0; i < len(bottomRow)-1; i++ { newRow = append(newRow, bottomRow[i]+bottomRow[i+1]) } newRow = append(newRow, 1) // 把新的一行放到杨辉三角底部 triangle = append(triangle, newRow) return triangle } ``` ```java // by labuladong (java) // 迭代方式 class Solution { public List> generate(int numRows) { List> res = new ArrayList<>(); if (numRows < 1) { return res; } // 先把第一层装进去作为 base case List firstRow = new ArrayList<>(); firstRow.add(1); res.add(firstRow); // 开始一层一层生成,装入 res for (int i = 2; i <= numRows; i++) { List prevRow = res.get(res.size() - 1); res.add(generateNextRow(prevRow)); } return res; } // 输入上一层的元素,生成并返回下一层的元素 List generateNextRow(List prevRow) { List curRow = new ArrayList<>(); curRow.add(1); for (int i = 0; i < prevRow.size() - 1; i++) { curRow.add(prevRow.get(i) + prevRow.get(i + 1)); } curRow.add(1); return curRow; } } // 递归方式 class Solution2 { // 定义:输入 numRows,返回行数为 numRows 的杨辉三角 public List> generate(int numRows) { // 递归的 base case if (numRows == 1) { List> triangle = new ArrayList<>(); // 先把第一层装进去作为 base case List firstRow = new ArrayList<>(); firstRow.add(1); triangle.add(firstRow); return triangle; } // 先递归生成高度为 numRows - 1 的杨辉三角 List> triangle = generate(numRows - 1); // 根据最底层元素生成一行新元素 List bottomRow = triangle.get(triangle.size() - 1); List newRow = new ArrayList<>(); newRow.add(1); for (int i = 0; i < bottomRow.size() - 1; i++) { newRow.add(bottomRow.get(i) + bottomRow.get(i + 1)); } newRow.add(1); // 把新的一行放到杨辉三角底部 triangle.add(newRow); return triangle; } } ``` ```javascript // by chatGPT (javascript) var generate = function(numRows) { var res = [] if (numRows < 1) { return res; } // 先把第一层装进去作为 base case var firstRow = [1]; res.push(firstRow); // 开始一层一层生成,装入 res for (var i = 2; i <= numRows; i++) { var prevRow = res[res.length - 1]; res.push(generateNextRow(prevRow)); } return res; } // 输入上一层的元素,生成并返回下一层的元素 var generateNextRow = function(prevRow) { var curRow = [1]; for (var i = 0; i < prevRow.length - 1; i++) { curRow.push(prevRow[i] + prevRow[i + 1]); } curRow.push(1); return curRow; } // 定义:输入 numRows,返回行数为 numRows 的杨辉三角 var generate = function(numRows) { // 递归的 base case if (numRows == 1) { var triangle = []; // 先把第一层装进去作为 base case var firstRow = [1]; triangle.push(firstRow); return triangle; } // 先递归生成高度为 numRows - 1 的杨辉三角 var triangle = generate(numRows - 1); // 根据最底层元素生成一行新元素 var bottomRow = triangle[triangle.length - 1]; var newRow = [1]; for (var i = 0; i < bottomRow.length - 1; i++) { newRow.push(bottomRow[i] + bottomRow[i + 1]); } newRow.push(1); // 把新的一行放到杨辉三角底部 triangle.push(newRow); return triangle; } ``` ```python # by chatGPT (python) # 迭代方式 class Solution: def generate(self, numRows: int) -> List[List[int]]: res = [] if numRows < 1: return res # 先把第一层装进去作为 base case firstRow = [1] res.append(firstRow) # 开始一层一层生成,装入 res for i in range(2, numRows+1): prevRow = res[-1] res.append(self.generateNextRow(prevRow)) return res # 输入上一层的元素,生成并返回下一层的元素 def generateNextRow(self, prevRow: List[int]) -> List[int]: curRow = [1] for i in range(len(prevRow) - 1): curRow.append(prevRow[i] + prevRow[i + 1]) curRow.append(1) return curRow # 递归方式 class Solution2: # 定义:输入 numRows,返回行数为 numRows 的杨辉三角 def generate(self, numRows: int) -> List[List[int]]: # 递归的 base case if numRows == 1: triangle = [] # 先把第一层装进去作为 base case firstRow = [1] triangle.append(firstRow) return triangle # 先递归生成高度为 numRows - 1 的杨辉三角 triangle = self.generate(numRows - 1) # 根据最底层元素生成一行新元素 bottomRow = triangle[-1] newRow = [1] for i in range(len(bottomRow) - 1): newRow.append(bottomRow[i] + bottomRow[i + 1]) newRow.append(1) # 把新的一行放到杨辉三角底部 triangle.append(newRow) return triangle ``` https://leetcode.cn/problems/pascals-triangle 的多语言解法👆 https://leetcode.cn/problems/path-with-maximum-probability 的多语言解法👇 ```cpp // by chatGPT (cpp) #include using namespace std; // struct 不再需要声明,直接在结构体内进行定义 class Solution { public: double maxProbability(int n, vector>& edges, vector& succProb, int start, int end) { vector>> graph(n); for (int i = 0; i < edges.size(); i++) { int from = edges[i][0]; int to = edges[i][1]; double weight = succProb[i]; // 无向图其实就是双向图 graph[from].push_back(make_pair(weight, to)); graph[to].push_back(make_pair(weight, from)); } return dijkstra(start, end, graph); } struct State { int id; double distFromStart; State(int id, double distFromStart) : id(id), distFromStart(distFromStart) {} bool operator < (const State& rhs) const { return distFromStart < rhs.distFromStart; } }; double dijkstra(int start, int end, vector>>& graph) { int V = graph.size(); vector distTo(V, -1); distTo[start] = 1; priority_queue pq; pq.push(State(start, 1)); while (!pq.empty()) { State curState = pq.top(); pq.pop(); int curNodeID = curState.id; double curDistFromStart = curState.distFromStart; if (curNodeID == end) { return curDistFromStart; } if (curDistFromStart < distTo[curNodeID]) { continue; } for (auto neighbor : graph[curNodeID]) { int nextNodeID = neighbor.second; double distToNextNode = distTo[curNodeID] * neighbor.first; if (distTo[nextNodeID] < distToNextNode) { distTo[nextNodeID] = distToNextNode; pq.push(State(nextNodeID, distToNextNode)); } } } return 0.0; } }; ``` ```go // by chatGPT (go) type State struct { id int dist float64 } func maxProbability(n int, edges [][]int, succProb []float64, start int, end int) float64 { graph := make([][]State, n) for i := 0; i < n; i++ { graph[i] = make([]State, 0) } // 构造无向图 for i := 0; i < len(edges); i++ { from := edges[i][0] to := edges[i][1] weight := succProb[i] // 无向图其实就是双向图 graph[from] = append(graph[from], State{to, weight}) graph[to] = append(graph[to], State{from, weight}) } return dijkstra(start, end, graph) } func dijkstra(start int, end int, graph [][]State) float64 { // 图中节点的个数 V := len(graph) // 记录最短路径的权重,你可以理解为 dp table // 定义:distTo[i] 的值就是节点 start 到达节点 i 的最短路径权重 distTo := make([]float64, V) // dp table 初始化为正无穷 for i := 0; i < V; i++ { distTo[i] = -1 } // base case,start 到 start 的最短距离就是 0 distTo[start] = 1 // 优先级队列,distFromStart 较小的排在前面 pq := make(PriorityQueue, 0) heap.Init(&pq) //从起点 start 开始进行 BFS heap.Push(&pq, &State{id: start, dist: 1}) for pq.Len() > 0 { curState := heap.Pop(&pq).(*State) curNodeID := curState.id curDistFromStart := curState.dist // 在这里加一个判断就行了,其他代码不用改 if curNodeID == end { return curDistFromStart } if curDistFromStart < distTo[curNodeID] { // 已经有一条更短的路径到达 curNode 节点了 continue } // 将 curNode 的相邻节点装入队列 for _, neighbor := range graph[curNodeID] { nextNodeID := neighbor.id // 看看从 curNode 达到 nextNode 的距离是否会更短 distToNextNode := distTo[curNodeID] * neighbor.dist if distTo[nextNodeID] < distToNextNode { // 更新 dp table distTo[nextNodeID] = distToNextNode // 将这个节点以及距离放入队列 heap.Push(&pq, &State{nextNodeID, distToNextNode}) } } } return 0.0 } // 优先级队列数据结构,用于实现 BFS 广度优先搜索 type PriorityQueue []*State func (pq PriorityQueue) Len() int { return len(pq) } func (pq PriorityQueue) Less(i, j int) bool { return pq[i].dist > pq[j].dist } func (pq PriorityQueue) Swap(i, j int) { pq[i], pq[j] = pq[j], pq[i] } func (pq *PriorityQueue) Push(x interface{}) { item := x.(*State) *pq = append(*pq, item) } func (pq *PriorityQueue) Pop() interface{} { old := *pq n := len(old) item := old[n-1] *pq = old[0 : n-1] return item } ``` ```java // by labuladong (java) class Solution { public double maxProbability(int n, int[][] edges, double[] succProb, int start, int end) { List[] graph = new LinkedList[n]; for (int i = 0; i < n; i++) { graph[i] = new LinkedList<>(); } // 构造无向图 for (int i = 0; i < edges.length; i++) { int from = edges[i][0]; int to = edges[i][1]; double weight = succProb[i]; // 无向图其实就是双向图 graph[from].add(new double[]{(double)to, weight}); graph[to].add(new double[]{(double)from, weight}); } return dijkstra(start, end, graph); } class State { // 图节点的 id int id; // 从 start 节点到当前节点的距离 double distFromStart; State(int id, double distFromStart) { this.id = id; this.distFromStart = distFromStart; } } double dijkstra(int start, int end, List[] graph) { // 图中节点的个数 int V = graph.length; // 记录最短路径的权重,你可以理解为 dp table // 定义:distTo[i] 的值就是节点 start 到达节点 i 的最短路径权重 double[] distTo = new double[V]; // dp table 初始化为正无穷 Arrays.fill(distTo, -1); // base case,start 到 start 的最短距离就是 0 distTo[start] = 1; // 优先级队列,distFromStart 较小的排在前面 Queue pq = new PriorityQueue<>((a, b) -> { return Double.compare(b.distFromStart, a.distFromStart); }); // 从起点 start 开始进行 BFS pq.offer(new State(start, 1)); while (!pq.isEmpty()) { State curState = pq.poll(); int curNodeID = curState.id; double curDistFromStart = curState.distFromStart; // 在这里加一个判断就行了,其他代码不用改 if (curNodeID == end) { return curDistFromStart; } if (curDistFromStart < distTo[curNodeID]) { // 已经有一条更短的路径到达 curNode 节点了 continue; } // 将 curNode 的相邻节点装入队列 for (double[] neighbor : graph[curNodeID]) { int nextNodeID = (int)neighbor[0]; // 看看从 curNode 达到 nextNode 的距离是否会更短 double distToNextNode = distTo[curNodeID] * neighbor[1]; if (distTo[nextNodeID] < distToNextNode) { // 更新 dp table distTo[nextNodeID] = distToNextNode; // 将这个节点以及距离放入队列 pq.offer(new State(nextNodeID, distToNextNode)); } } } return 0.0; } } ``` ```javascript // by chatGPT (javascript) /** * @param {number} n * @param {number[][]} edges * @param {number[]} succProb * @param {number} start * @param {number} end * @return {number} */ var maxProbability = function(n, edges, succProb, start, end) { const graph = []; for (let i = 0; i < n; i++) { graph[i] = []; } // 构造无向图 for (let i = 0; i < edges.length; i++) { const from = edges[i][0]; const to = edges[i][1]; const weight = succProb[i]; // 无向图其实就是双向图 graph[from].push([to, weight]); graph[to].push([from, weight]); } return dijkstra(start, end, graph); }; class State { // 图节点的 id constructor(id, distFromStart) { this.id = id; // 从 start 节点到当前节点的距离 this.distFromStart = distFromStart; } } function dijkstra(start, end, graph) { // 图中节点的个数 const V = graph.length; // 记录最短路径的权重,你可以理解为 dp table // 定义:distTo[i] 的值就是节点 start 到达节点 i 的最短路径权重 const distTo = new Array(V).fill(-1); // dp table 初始化为正无穷 distTo[start] = 1; // 优先级队列,distFromStart 较小的排在前面 const pq = new PriorityQueue({ compare: (a, b) => { return b.distFromStart - a.distFromStart; } }); // 从起点 start 开始进行 BFS pq.enqueue(new State(start, 1)); while (!pq.isEmpty()) { const curState = pq.dequeue(); const curNodeID = curState.id; const curDistFromStart = curState.distFromStart; // 在这里加一个判断就行了,其他代码不用改 if (curNodeID === end) { return curDistFromStart; } if (curDistFromStart < distTo[curNodeID]) { // 已经有一条更短的路径到达 curNode 节点了 continue; } // 将 curNode 的相邻节点装入队列 for (const neighbor of graph[curNodeID]) { const nextNodeID = neighbor[0]; // 看看从 curNode 达到 nextNode 的距离是否会更短 const distToNextNode = distTo[curNodeID] * neighbor[1]; if (distTo[nextNodeID] < distToNextNode) { // 更新 dp table distTo[nextNodeID] = distToNextNode; // 将这个节点以及距离放入队列 pq.enqueue(new State(nextNodeID, distToNextNode)); } } } return 0.0; } ``` ```python # by chatGPT (python) import heapq from typing import List class Solution: def maxProbability(self, n: int, edges: List[List[int]], succProb: List[float], start: int, end: int) -> float: graph = [[] for _ in range(n)] for i in range(len(edges)): from_, to = edges[i][0], edges[i][1] weight = succProb[i] graph[from_].append((to, weight)) graph[to].append((from_, weight)) return self.dijkstra(start, end, graph) class State: def __init__(self, id_, distFromStart): self.id = id_ self.distFromStart = distFromStart def __lt__(self, other): return self.distFromStart > other.distFromStart def dijkstra(self, start, end, graph): V = len(graph) # 记录最短路径的权重,你可以理解为 dp table # 定义:distTo[i] 的值就是节点 start 到达节点 i 的最短路径权重 distTo = [-1] * V # dp table 初始化为正无穷 distTo[start] = 1 # 优先级队列,distFromStart 较小的排在前面 pq = [] # 从起点 start 开始进行 BFS heapq.heappush(pq, self.State(start, 1)) while pq: curState = heapq.heappop(pq) curNodeID = curState.id curDistFromStart = curState.distFromStart # 在这里加一个判断就行了,其他代码不用改 if curNodeID == end: return curDistFromStart if curDistFromStart < distTo[curNodeID]: # 已经有一条更短的路径到达 curNode 节点了 continue # 将 curNode 的相邻节点装入队列 for neighbor in graph[curNodeID]: nextNodeID = neighbor[0] # 看看从 curNode 达到 nextNode 的距离是否会更短 distToNextNode = distTo[curNodeID] * neighbor[1] if distTo[nextNodeID] < distToNextNode: # 更新 dp table distTo[nextNodeID] = distToNextNode # 将这个节点以及距离放入队列 heapq.heappush(pq, self.State(nextNodeID, distToNextNode)) return 0.0 ``` https://leetcode.cn/problems/path-with-maximum-probability 的多语言解法👆 https://leetcode.cn/problems/path-with-minimum-effort 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: // Dijkstra 算法,计算 (0, 0) 到 (m - 1, n - 1) 的最小体力消耗 int minimumEffortPath(vector>& heights) { int m = heights.size(), n = heights[0].size(); // 定义:从 (0, 0) 到 (i, j) 的最小体力消耗是 effortTo[i][j] vector> effortTo(m,vector(n, INT_MAX)); // dp table 初始化为正无穷 // base case,起点到起点的最小消耗就是 0 effortTo[0][0] = 0; // 优先级队列,effortFromStart 较小的排在前面 priority_queue,Comp> pq; // 第二个参数就是自定义语法格式,详见下方 // 从起点 (0, 0) 开始进行 BFS pq.push(State(0, 0, 0)); while (!pq.empty()) { State curState = pq.top(); pq.pop(); int curX = curState.x; int curY = curState.y; int curEffortFromStart = curState.effortFromStart; // 到达终点提前结束 if (curX == m - 1 && curY == n - 1) { return curEffortFromStart; } if (curEffortFromStart > effortTo[curX][curY]) { continue; } // 将 (curX, curY) 的相邻坐标装入队列 for (auto& neighbor : adj(heights, curX, curY)) { int nextX = neighbor[0]; int nextY = neighbor[1]; // 计算从 (curX, curY) 达到 (nextX, nextY) 的消耗 int effortToNextNode = max( effortTo[curX][curY], abs(heights[curX][curY] - heights[nextX][nextY]) ); // 更新 dp table if (effortTo[nextX][nextY] > effortToNextNode) { effortTo[nextX][nextY] = effortToNextNode; pq.push(State(nextX, nextY, effortToNextNode)); } } } // 正常情况不会达到这个 return return -1; } // 方向数组,上下左右的坐标偏移量 vector> dirs {{0, 1}, {1, 0}, {0, -1}, {-1, 0}}; // 返回坐标 (x, y) 的上下左右相邻坐标 vector> adj(vector>& matrix, int x, int y) { int m = matrix.size(), n = matrix[0].size(); // 存储相邻节点 vector> neighbors; for (auto& dir : dirs) { int nx = x + dir[0]; int ny = y + dir[1]; if (nx >= m || nx < 0 || ny >= n || ny < 0) { // 索引越界 continue; } neighbors.push_back({nx, ny}); } return neighbors; } struct State { // 矩阵中的一个位置 int x, y; // 从起点 (0, 0) 到当前位置的最小体力消耗(距离) int effortFromStart; State(int x, int y, int effortFromStart) { this->x = x; this->y = y; this->effortFromStart = effortFromStart; } }; struct Comp { bool operator() (State& a, State& b) { return a.effortFromStart > b.effortFromStart; } }; }; ``` ```go // by chatGPT (go) func minimumEffortPath(heights [][]int) int { m := len(heights) n := len(heights[0]) // 定义:从 (0, 0) 到 (i, j) 的最小体力消耗是 effortTo[i][j] effortTo := make([][]int, m) for i := range effortTo { effortTo[i] = make([]int, n) // dp table 初始化为正无穷 for j := range effortTo[i] { effortTo[i][j] = math.MaxInt32 } } // base case,起点到起点的最小消耗就是 0 effortTo[0][0] = 0 // 方向数组,上下左右的坐标偏移量 var dirs = [][2]int{{0, 1}, {1, 0}, {0, -1}, {-1, 0}} // 返回坐标 (x, y) 的上下左右相邻坐标 var adj = func(x, y int) [][]int { // 存储相邻节点 neighbors := make([][]int, 0) for _, dir := range dirs { nx, ny := x+dir[0], y+dir[1] if nx >= m || nx < 0 || ny >= n || ny < 0 { // 索引越界 continue } neighbors = append(neighbors, []int{nx, ny}) } return neighbors } // 优先级队列的类型定义 type PriorityQueue []*State func (pq PriorityQueue) Len() int { return len(pq) } func (pq PriorityQueue) Less(i, j int) bool { return pq[i].effortFromStart < pq[j].effortFromStart } func (pq PriorityQueue) Swap(i, j int) { pq[i], pq[j] = pq[j], pq[i] } func (pq *PriorityQueue) Push(x interface{}) { *pq = append(*pq, x.(*State)) } func (pq *PriorityQueue) Pop() interface{} { old := *pq n := len(old) item := old[n-1] *pq = old[0 : n-1] return item } // 从起点 (0, 0) 开始进行 BFS pq := make(PriorityQueue, 0) heap.Init(&pq) pq.Push(&State{0, 0, 0}) for pq.Len() > 0 { curState := heap.Pop(&pq).(*State) curX, curY, curEffortFromStart := curState.x, curState.y, curState.effortFromStart // 到达终点提前结束 if curX == m-1 && curY == n-1 { return curEffortFromStart } if curEffortFromStart > effortTo[curX][curY] { continue } // 将 (curX, curY) 的相邻坐标装入队列 for _, neighbor := range adj(curX, curY) { nextX, nextY := neighbor[0], neighbor[1] // 计算从 (curX, curY) 达到 (nextX, nextY) 的消耗 effortToNextNode := max( effortTo[curX][curY], abs(heights[curX][curY]-heights[nextX][nextY]), ) // 更新 dp table if effortTo[nextX][nextY] > effortToNextNode { effortTo[nextX][nextY] = effortToNextNode heap.Push(&pq, &State{nextX, nextY, effortToNextNode}) } } } // 正常情况不会达到这个 return return -1 } type State struct { // 矩阵中的一个位置 x, y int // 从起点 (0, 0) 到当前位置的最小体力消耗(距离) effortFromStart int } func max(x, y int) int { if x > y { return x } return y } func abs(x int) int { if x < 0 { return -x } return x } ``` ```java // by labuladong (java) class Solution { // Dijkstra 算法,计算 (0, 0) 到 (m - 1, n - 1) 的最小体力消耗 public int minimumEffortPath(int[][] heights) { int m = heights.length, n = heights[0].length; // 定义:从 (0, 0) 到 (i, j) 的最小体力消耗是 effortTo[i][j] int[][] effortTo = new int[m][n]; // dp table 初始化为正无穷 for (int i = 0; i < m; i++) { Arrays.fill(effortTo[i], Integer.MAX_VALUE); } // base case,起点到起点的最小消耗就是 0 effortTo[0][0] = 0; // 优先级队列,effortFromStart 较小的排在前面 Queue pq = new PriorityQueue<>((a, b) -> { return a.effortFromStart - b.effortFromStart; }); // 从起点 (0, 0) 开始进行 BFS pq.offer(new State(0, 0, 0)); while (!pq.isEmpty()) { State curState = pq.poll(); int curX = curState.x; int curY = curState.y; int curEffortFromStart = curState.effortFromStart; // 到达终点提前结束 if (curX == m - 1 && curY == n - 1) { return curEffortFromStart; } if (curEffortFromStart > effortTo[curX][curY]) { continue; } // 将 (curX, curY) 的相邻坐标装入队列 for (int[] neighbor : adj(heights, curX, curY)) { int nextX = neighbor[0]; int nextY = neighbor[1]; // 计算从 (curX, curY) 达到 (nextX, nextY) 的消耗 int effortToNextNode = Math.max( effortTo[curX][curY], Math.abs(heights[curX][curY] - heights[nextX][nextY]) ); // 更新 dp table if (effortTo[nextX][nextY] > effortToNextNode) { effortTo[nextX][nextY] = effortToNextNode; pq.offer(new State(nextX, nextY, effortToNextNode)); } } } // 正常情况不会达到这个 return return -1; } // 方向数组,上下左右的坐标偏移量 int[][] dirs = new int[][]{{0, 1}, {1, 0}, {0, -1}, {-1, 0}}; // 返回坐标 (x, y) 的上下左右相邻坐标 List adj(int[][] matrix, int x, int y) { int m = matrix.length, n = matrix[0].length; // 存储相邻节点 List neighbors = new ArrayList<>(); for (int[] dir : dirs) { int nx = x + dir[0]; int ny = y + dir[1]; if (nx >= m || nx < 0 || ny >= n || ny < 0) { // 索引越界 continue; } neighbors.add(new int[]{nx, ny}); } return neighbors; } class State { // 矩阵中的一个位置 int x, y; // 从起点 (0, 0) 到当前位置的最小体力消耗(距离) int effortFromStart; State(int x, int y, int effortFromStart) { this.x = x; this.y = y; this.effortFromStart = effortFromStart; } } } ``` ```javascript // by chatGPT (javascript) var minimumEffortPath = function(heights) { // Dijkstra 算法,计算 (0, 0) 到 (m - 1, n - 1) 的最小体力消耗 let m = heights.length, n = heights[0].length, // 定义:从 (0, 0) 到 (i, j) 的最小体力消耗是 effortTo[i][j] effortTo = Array.from({ length: m }, () => Array(n).fill(Number.MAX_SAFE_INTEGER)), // 方向数组,上下左右的坐标偏移量 dirs = [[0, 1], [1, 0], [0, -1], [-1, 0]], // 优先级队列,effortFromStart 较小的排在前面 pq = []; // 从起点 (0, 0) 开始进行 BFS pq.push(new State(0, 0, 0)); // base case,起点到起点的最小消耗就是 0 effortTo[0][0] = 0; class State { // 矩阵中的一个位置 // 从起点 (0, 0) 到当前位置的最小体力消耗(距离) constructor(x, y, effortFromStart) { this.x = x; this.y = y; this.effortFromStart = effortFromStart; } } function adj(matrix, x, y) { let m = matrix.length, n = matrix[0].length; // 存储相邻节点 let neighbors = []; for (let dir of dirs) { let nx = x + dir[0]; let ny = y + dir[1]; if (nx >= m || nx < 0 || ny >= n || ny < 0) { // 索引越界 continue; } neighbors.push([nx, ny]); } return neighbors; } while (pq.length != 0) { let curState = pq.shift(); let curX = curState.x; let curY = curState.y; let curEffortFromStart = curState.effortFromStart; // 到达终点提前结束 if (curX == m - 1 && curY == n - 1) { return curEffortFromStart; } if (curEffortFromStart > effortTo[curX][curY]) { continue; } // 将 (curX, curY) 的相邻坐标装入队列 for (let neighbor of adj(heights, curX, curY)) { let nextX = neighbor[0]; let nextY = neighbor[1]; // 计算从 (curX, curY) 达到 (nextX, nextY) 的消耗 let effortToNextNode = Math.max( effortTo[curX][curY], Math.abs(heights[curX][curY] - heights[nextX][nextY]) ); // 更新 dp table if (effortTo[nextX][nextY] > effortToNextNode) { effortTo[nextX][nextY] = effortToNextNode; pq.push(new State(nextX, nextY, effortToNextNode)); } } } // 正常情况不会达到这个 return return -1; }; ``` ```python # by chatGPT (python) import heapq class Solution: def minimumEffortPath(self, heights: List[List[int]]) -> int: m, n = len(heights), len(heights[0]) # 定义:从 (0, 0) 到 (i, j) 的最小体力消耗是 effortTo[i][j] effortTo = [[float('inf')] * n for _ in range(m)] # base case,起点到起点的最小消耗就是 0 effortTo[0][0] = 0 # 优先级队列,effortFromStart 较小的排在前面 pq = [] # 从起点 (0, 0) 开始进行 BFS heapq.heappush(pq, [0, 0, 0]) while pq: curEffortFromStart, curX, curY = heapq.heappop(pq) # 到达终点提前结束 if curX == m - 1 and curY == n - 1: return curEffortFromStart if curEffortFromStart > effortTo[curX][curY]: continue # 将 (curX, curY) 的相邻坐标装入队列 for nextX, nextY in self.adj(heights, curX, curY): # 计算从 (curX, curY) 达到 (nextX, nextY) 的消耗 effortToNextNode = max( effortTo[curX][curY], abs(heights[curX][curY] - heights[nextX][nextY]) ) # 更新 dp table if effortTo[nextX][nextY] > effortToNextNode: effortTo[nextX][nextY] = effortToNextNode heapq.heappush(pq, [effortToNextNode, nextX, nextY]) # 正常情况不会达到这个 return return -1 # 方向数组,上下左右的坐标偏移量 dirs = [[0, 1], [1, 0], [0, -1], [-1, 0]] # 返回坐标 (x, y) 的上下左右相邻坐标 def adj(self, matrix, x, y): m, n = len(matrix), len(matrix[0]) # 存储相邻节点 neighbors = [] for dir in self.dirs: nx = x + dir[0] ny = y + dir[1] if nx >= m or nx < 0 or ny >= n or ny < 0: # 索引越界 continue neighbors.append([nx, ny]) return neighbors ``` https://leetcode.cn/problems/path-with-minimum-effort 的多语言解法👆 https://leetcode.cn/problems/peak-index-in-a-mountain-array 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int peakIndexInMountainArray(vector& nums) { // 取两端都闭的二分搜索 int left = 0, right = nums.size() - 1; // 因为题目必然有解,所以设置 left == right 为结束条件 while (left < right) { int mid = left + (right - left) / 2; if (nums[mid] > nums[mid + 1]) { // mid 本身就是峰值或其左侧有一个峰值 right = mid; } else { // mid 右侧有一个峰值 left = mid + 1; } } return left; } }; ``` ```go // by chatGPT (go) func peakIndexInMountainArray(nums []int) int { // 取两端都闭的二分搜索 left, right := 0, len(nums)-1 // 因为题目必然有解,所以设置 left == right 为结束条件 for left < right { mid := left + (right-left)/2 if nums[mid] > nums[mid+1] { // mid 本身就是峰值或其左侧有一个峰值 right = mid } else { // mid 右侧有一个峰值 left = mid + 1 } } return left } ``` ```java // by labuladong (java) class Solution { public int peakIndexInMountainArray(int[] nums) { // 取两端都闭的二分搜索 int left = 0, right = nums.length - 1; // 因为题目必然有解,所以设置 left == right 为结束条件 while (left < right) { int mid = left + (right - left) / 2; if (nums[mid] > nums[mid + 1]) { // mid 本身就是峰值或其左侧有一个峰值 right = mid; } else { // mid 右侧有一个峰值 left = mid + 1; } } return left; } } ``` ```javascript // by chatGPT (javascript) /** * @param {number[]} nums * @return {number} */ var peakIndexInMountainArray = function(nums) { // 取两端都闭的二分搜索 let left = 0, right = nums.length - 1; // 因为题目必然有解,所以设置 left == right 为结束条件 while (left < right) { const mid = left + Math.floor((right - left) / 2); if (nums[mid] > nums[mid + 1]) { // mid 本身就是峰值或其左侧有一个峰值 right = mid; } else { // mid 右侧有一个峰值 left = mid + 1; } } return left; }; ``` ```python # by chatGPT (python) class Solution: def peakIndexInMountainArray(self, nums: List[int]) -> int: # 取两端都闭的二分搜索 left, right = 0, len(nums) - 1 # 因为题目必然有解,所以设置 left == right 为结束条件 while left < right: mid = left + (right - left) // 2 if nums[mid] > nums[mid + 1]: # mid 本身就是峰值或其左侧有一个峰值 right = mid else: # mid 右侧有一个峰值 left = mid + 1 return left ``` https://leetcode.cn/problems/peak-index-in-a-mountain-array 的多语言解法👆 https://leetcode.cn/problems/peeking-iterator 的多语言解法👇 ```cpp // by chatGPT (cpp) class PeekingIterator : public Iterator { private: Iterator* iter; // 迭代器 int nextElem; // 缓存下一个元素 public: PeekingIterator(const vector& nums) : Iterator(nums) { iter = &nums; // 初始化迭代器 if (Iterator::hasNext()) { // 检查是否有下一个元素 nextElem = Iterator::next(); // 取出下一个元素并缓存 } } int peek() { // 返回当前缓存的元素 return nextElem; } int next() { // 返回缓存的下一个元素,并更新缓存 int res = nextElem; if (Iterator::hasNext()) { nextElem = Iterator::next(); } else { nextElem = NULL; } return res; } bool hasNext() const { // 检查是否有下一个元素 return nextElem != NULL; } }; ``` ```go // by chatGPT (go) type Iterator interface { hasNext() bool next() int } type PeekingIterator struct { iter Iterator // 保存下一个元素的缓存 nextElem int } func Constructor(iter Iterator) *PeekingIterator { this := new(PeekingIterator) this.iter = iter // 把下一个元素提前拿出来并缓存起来 if iter.hasNext() { this.nextElem = iter.next() } return this } // 返回迭代器的下一个元素,但是不移动迭代器的指针 func (this *PeekingIterator) peek() int { return this.nextElem } //以下方法和 Iterator 接口中的一样。 func (this *PeekingIterator) next() int { res := this.nextElem // 如果迭代器还有下一个元素,就更新缓存 if this.iter.hasNext() { this.nextElem = this.iter.next() } else { this.nextElem = 0 } return res } func (this *PeekingIterator) hasNext() bool { return this.nextElem != 0 } ``` ```java // by labuladong (java) // Java Iterator interface reference: // https://docs.oracle.com/javase/8/docs/api/java/util/Iterator.html class PeekingIterator implements Iterator { private Iterator iter; // 把迭代器的下一个元素提前拿出来并缓存起来 private Integer nextElem; public PeekingIterator(Iterator iterator) { this.iter = iterator; this.nextElem = iterator.next(); } public Integer peek() { return nextElem; } @Override public Integer next() { Integer res = nextElem; // 更新 nextElem if (iter.hasNext()) { nextElem = iter.next(); } else { nextElem = null; } return res; } @Override public boolean hasNext() { return nextElem != null; } } ``` ```javascript // by chatGPT (javascript) /** * Javascript iterator reference: * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#The_iterator_protocol */ var PeekingIterator = function(iterator) { this.iter = iterator; // 把迭代器的下一个元素提前拿出来并缓存起来 this.nextElem = iterator.next(); }; PeekingIterator.prototype.peek = function() { return this.nextElem; }; PeekingIterator.prototype.next = function() { var res = this.nextElem; // 更新 nextElem if (this.iter.hasNext()) { this.nextElem = this.iter.next(); } else { this.nextElem = null; } return res; }; PeekingIterator.prototype.hasNext = function() { return this.nextElem !== null; }; ``` ```python # by chatGPT (python) # Python Iterator interface reference: # https://docs.python.org/3/library/stdtypes.html#iterator-types class PeekingIterator: def __init__(self, iterator): self.iter = iterator # 把迭代器的下一个元素提前拿出来并缓存起来 self.next_elem = self.iter.__next__() def peek(self): return self.next_elem def __next__(self): res = self.next_elem # 更新 next_elem try: self.next_elem = self.iter.__next__() except StopIteration: self.next_elem = None return res def __iter__(self): return self def __bool__(self): return self.next_elem is not None ``` https://leetcode.cn/problems/peeking-iterator 的多语言解法👆 https://leetcode.cn/problems/perfect-rectangle 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: bool isRectangleCover(vector>& rectangles) { int X1 = INT_MAX, Y1 = INT_MAX; int X2 = INT_MIN, Y2 = INT_MIN; set> points; int actual_area = 0; for(auto &r: rectangles){ //计算完美矩形的理论顶点坐标 X1 = min(X1, r[0]); Y1 = min(Y1, r[1]); X2 = max(X2, r[2]); Y2 = max(Y2, r[3]); //累加小矩形的面积 actual_area += (r[2] - r[0]) * (r[3] - r[1]); //记录最终形成的图形中的顶点 pair p1 = {r[0], r[1]}; pair p2 = {r[0], r[3]}; pair p3 = {r[2], r[1]}; pair p4 = {r[2], r[3]}; vector> cur_points = {p1, p2, p3, p4}; //如果坐标已经存在,那么从set里面删除 //如果坐标不存在,那么添加 for(auto &p: cur_points){ if(points.count(p)) points.erase(p); else points.insert(p); } } // 判断面积是否相同 int expected_area = (X2 - X1) * (Y2 - Y1); if(actual_area != expected_area) return false; // 判断最终留下的顶点个数是否为 4 if(points.size() != 4) return false; // 判断留下的 4 个顶点是否是完美矩形的顶点 if(!points.count({X1, Y1})) return false; if(!points.count({X1, Y2})) return false; if(!points.count({X2, Y1})) return false; if(!points.count({X2, Y2})) return false; // 面积和顶点都对应,说明矩形符合题意 return true; } }; ``` ```go // by chatGPT (go) func isRectangleCover(rectangles [][]int) bool { inf := 1<<31 - 1 X1, Y1 := inf, inf X2, Y2 := -inf, -inf points := make(map[[2]int]bool) actual_area := 0 for _, rectangle := range rectangles { // 计算完美矩形的理论顶点坐标 x1, y1, x2, y2 := rectangle[0], rectangle[1], rectangle[2], rectangle[3] X1, Y1 = min(X1, x1), min(Y1, y1) X2, Y2 = max(X2, x2), max(Y2, y2) // 累加小矩形的面积 actual_area += (x2 - x1) * (y2 - y1) // 记录最终形成的图形中的顶点 p1, p2 := [2]int{x1, y1}, [2]int{x1, y2} p3, p4 := [2]int{x2, y1}, [2]int{x2, y2} for _, p := range [][2]int{p1, p2, p3, p4} { if _, ok := points[p]; ok { delete(points, p) } else { points[p] = true } } } // 判断面积是否相同 expected_area := (X2 - X1) * (Y2 - Y1) if actual_area != expected_area { return false } // 判断最终留下的顶点个数是否为 4 if len(points) != 4 { return false } // 判断留下的 4 个顶点是否是完美矩形的顶点 if !points[[2]int{X1, Y1}] { return false } if !points[[2]int{X1, Y2}] { return false } if !points[[2]int{X2, Y1}] { return false } if !points[[2]int{X2, Y2}] { return false } // 面积和顶点都对应,说明矩形符合题意 return true } func min(a, b int) int { if a < b { return a } return b } func max(a, b int) int { if a > b { return a } return b } ``` ```java // by chatGPT (java) class Solution { public boolean isRectangleCover(int[][] rectangles) { int X1 = Integer.MAX_VALUE, Y1 = Integer.MAX_VALUE; int X2 = Integer.MIN_VALUE, Y2 = Integer.MIN_VALUE; Set points = new HashSet<>(); int actual_area = 0; for(int[] rectangle: rectangles){ // 计算完美矩形的理论顶点坐标 X1 = Math.min(X1, rectangle[0]); Y1 = Math.min(Y1, rectangle[1]); X2 = Math.max(X2, rectangle[2]); Y2 = Math.max(Y2, rectangle[3]); // 累加小矩形的面积 actual_area += (rectangle[2] - rectangle[0]) * (rectangle[3] - rectangle[1]); // 记录最终形成的图形中的顶点 String p1 = rectangle[0] + "," + rectangle[1]; String p2 = rectangle[0] + "," + rectangle[3]; String p3 = rectangle[2] + "," + rectangle[1]; String p4 = rectangle[2] + "," + rectangle[3]; for (String p : new String[] { p1, p2, p3, p4 }) { if (points.contains(p)) points.remove(p); else points.add(p); } } // 判断面积是否相同 int expected_area = (X2 - X1) * (Y2 - Y1); if(actual_area!=expected_area){ return false; } // 判断最终留下的顶点个数是否为 4 if(points.size()!=4){ return false; } // 判断留下的 4 个顶点是否是完美矩形的顶点 if (!points.contains(X1 + "," + Y1)) return false; if (!points.contains(X1 + "," + Y2)) return false; if (!points.contains(X2 + "," + Y1)) return false; if (!points.contains(X2 + "," + Y2)) return false; // 面积和顶点都对应,说明矩形符合题意 return true; } } ``` ```javascript // by chatGPT (javascript) /** * @param {number[][]} rectangles * @return {boolean} */ var isRectangleCover = function(rectangles) { let X1 = Infinity, Y1 = Infinity; // 记录最左下角点的坐标 let X2 = -Infinity, Y2 = -Infinity; // 记录最右上角点的坐标 let points = new Set(); // 记录每个小矩形的四个角落 let actual_area = 0; // 记录小矩形的面积之和 for(let i=0; i bool: X1, Y1 = float('inf'), float('inf') X2, Y2 = -float('inf'), -float('inf') points = set() actual_area = 0 for x1, y1, x2, y2 in rectangles: # 计算完美矩形的理论顶点坐标 X1, Y1 = min(X1, x1), min(Y1, y1) X2, Y2 = max(X2, x2), max(Y2, y2) # 累加小矩形的面积 actual_area += (x2 - x1) * (y2 - y1) # 记录最终形成的图形中的顶点 p1, p2 = (x1, y1), (x1, y2) p3, p4 = (x2, y1), (x2, y2) for p in [p1, p2, p3, p4]: if p in points: points.remove(p) else: points.add(p) # 判断面积是否相同 expected_area = (X2 - X1) * (Y2 - Y1) if actual_area != expected_area: return False # 判断最终留下的顶点个数是否为 4 if len(points) != 4: return False # 判断留下的 4 个顶点是否是完美矩形的顶点 if (X1, Y1) not in points: return False if (X1, Y2) not in points: return False if (X2, Y1) not in points: return False if (X2, Y2) not in points: return False # 面积和顶点都对应,说明矩形符合题意 return True ``` https://leetcode.cn/problems/perfect-rectangle 的多语言解法👆 https://leetcode.cn/problems/perfect-squares 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int numSquares(int n) { // 定义:和为 i 的平方数的最小数量是 dp[i] vector dp(n + 1); // base case dp[0] = 0; // 状态转移方程 for (int i = 1; i <= n; i++) { dp[i] = i; // 最坏的情况就是每个数都是 1,最多也就 i 个数 for (int j = 1; j * j <= i; j++) { // i - j * j 只要再加一个平方数 j * j 即可凑出 i dp[i] = min(dp[i], dp[i - j * j] + 1); } } return dp[n]; } }; ``` ```go // by chatGPT (go) func numSquares(n int) int { // 定义:和为 i 的平方数的最小数量是 dp[i] dp := make([]int, n+1) // base case dp[0] = 0 // 状态转移方程 for i := 1; i <= n; i++ { dp[i] = i for j := 1; j*j <= i; j++ { dp[i] = min(dp[i], dp[i-j*j]+1) } } return dp[n] } func min(a, b int) int { if a < b { return a } return b } ``` ```java // by labuladong (java) class Solution { public int numSquares(int n) { // 定义:和为 i 的平方数的最小数量是 dp[i] int[] dp = new int[n + 1]; Arrays.fill(dp, Integer.MAX_VALUE); // base case dp[0] = 0; // 状态转移方程 for (int i = 1; i <= n; i++) { for (int j = 1; j * j <= i; j++) { // i - j * j 只要再加一个平方数 j * j 即可凑出 i dp[i] = Math.min(dp[i], dp[i - j * j] + 1); } } return dp[n]; } } ``` ```javascript // by chatGPT (javascript) var numSquares = function(n) { // 定义:和为 i 的平方数的最小数量是 dp[i] const dp = new Array(n+1).fill(0); // base case dp[0] = 0; // 状态转移方程 for (let i = 1; i <= n; i++) { dp[i] = i; for (let j = 1; j * j <= i; j++) { // i - j * j 只要再加一个平方数 j * j 即可凑出 i dp[i] = Math.min(dp[i], dp[i - j * j] + 1); } } return dp[n]; }; ``` ```python # by chatGPT (python) class Solution: def numSquares(self, n: int) -> int: # 定义:和为 i 的平方数的最小数量是 dp[i] dp = [float('inf')] * (n+1) # base case dp[0] = 0 # 状态转移方程 for i in range(1, n+1): for j in range(1, int(i**0.5)+1): # i - j * j 只要再加一个平方数 j * j 即可凑出 i dp[i] = min(dp[i], dp[i - j*j] + 1) return dp[n] ``` https://leetcode.cn/problems/perfect-squares 的多语言解法👆 https://leetcode.cn/problems/permutation-in-string 的多语言解法👇 ```cpp // by labuladong (cpp) class Solution { public: // 判断 s 中是否存在 t 的排列 bool checkInclusion(string t, string s) { unordered_map need, window; for (char c : t) need[c]++; int left = 0, right = 0; int valid = 0; while (right < s.size()) { char c = s[right]; right++; // 进行窗口内数据的一系列更新 if (need.count(c)) { window[c]++; if (window[c] == need[c]) valid++; } // 判断左侧窗口是否要收缩 while (right - left >= t.size()) { // 在这里判断是否找到了合法的子串 if (valid == need.size()) return true; char d = s[left]; left++; // 进行窗口内数据的一系列更新 if (need.count(d)) { if (window[d] == need[d]) valid--; window[d]--; } } } // 未找到符合条件的子串 return false; } }; ``` ```go // by chatGPT (go) // 判断 s 中是否存在 t 的排列 func checkInclusion(t string, s string) bool { need, window := make(map[byte]int), make(map[byte]int) for i := range t { need[t[i]]++ } left, right := 0, 0 valid := 0 for right < len(s) { c := s[right] right++ // 进行窗口内数据的一系列更新 if _, ok := need[c]; ok { window[c]++ if window[c] == need[c] { valid++ } } // 判断左侧窗口是否要收缩 for right-left >= len(t) { // 在这里判断是否找到了符合条件的子串 if valid == len(need) { return true } d := s[left] left++ // 进行窗口内数据的一系列更新 if _, ok := need[d]; ok { if window[d] == need[d] { valid-- } window[d]-- } } } // 未找到符合条件的子串 return false } ``` ```java // by chatGPT (java) class Solution { public boolean checkInclusion(String t, String s) { //使用哈希表用于记录 t 中每个字符出现的次数 Map need = new HashMap<>(), window = new HashMap<>(); for (int i = 0; i < t.length(); i++) { char c = t.charAt(i); need.put(c, need.getOrDefault(c, 0) + 1); } //初始化窗口的左右边界及合法字符个数 int left = 0, right = 0, valid = 0; 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 (right - left >= t.length()) { //在这里判断是否找到了合法的子串 if (valid == need.size()) return true; //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 false; } } ``` ```javascript // by chatGPT (javascript) var checkInclusion = function(t, s) { // 初始化 need 和 window 哈希表 let need = new Map(); let window = new Map(); for (let c of t) { need.set(c, (need.get(c) || 0) + 1); } let left = 0; let right = 0; let valid = 0; while (right < s.length) { let c = s[right]; right++; // 进行窗口内数据的更新 if (need.has(c)) { window.set(c, (window.get(c) || 0) + 1); if (window.get(c) === need.get(c)) { valid++; } } // 判断左侧窗口是否需要收缩 while (right - left >= t.length) { // 更新结果 if (valid === need.size) { return true; } let d = s[left]; left++; // 进行窗口内数据的更新 if (need.has(d)) { if (window.get(d) === need.get(d)) { valid--; } window.set(d, (window.get(d) || 0) - 1); } } } // 未找到符合条件的子串 return false; }; ``` ```python # by chatGPT (python) class Solution: def checkInclusion(self, t: str, s: str) -> bool: # 创建字典,记录字符需要出现的次数 need, window = Counter(t), Counter() left, right, valid = 0, 0, 0 # 右指针前移,更新窗口内数据 while right < len(s): c = s[right] right += 1 # 如果该字符在需要的字典里,更新窗口内字典 if need[c]: window[c] += 1 # 如果窗口内字典该字符准确次数与需要的次数相同,计数器+1 if window[c] == need[c]: valid += 1 # 判断左侧窗口是否需要收缩 while right - left >= len(t): # 如果子串合法,返回True if valid == len(need): return True # 左指针前移了,需要从窗口内字典中减掉一个元素 d = s[left] left += 1 if need[d]: # 如果窗口内字典该字符准确次数与需要的次数相同,计数器-1 if window[d] == need[d]: valid -= 1 window[d] -= 1 # 未找到合法的子串,返回False return False ``` https://leetcode.cn/problems/permutation-in-string 的多语言解法👆 https://leetcode.cn/problems/permutations 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: vector> res; /* 主函数,输入一组不重复的数字,返回它们的全排列 */ vector> permute(vector& nums) { // 记录「路径」 deque track; // 「路径」中的元素会被标记为 true,避免重复使用 vector used(nums.size(), false); backtrack(nums, track, used); return res; } // 路径:记录在 track 中 // 选择列表:nums 中不存在于 track 的那些元素(used[i] 为 false) // 结束条件:nums 中的元素全都在 track 中出现 void backtrack(vector& nums, deque& track, vector& used) { // 触发结束条件 if (track.size() == nums.size()) { res.push_back(vector(track.begin(), track.end())); return; } for (int i = 0; i < nums.size(); i++) { // 排除不合法的选择 if (used[i]) { /** ![](../pictures/backtracking/6.jpg) */ // nums[i] 已经在 track 中,跳过 continue; } // 做选择 track.push_back(nums[i]); used[i] = true; // 进入下一层决策树 backtrack(nums, track, used); // 取消选择 track.pop_back(); used[i] = false; } } }; ``` ```go // by chatGPT (go) func permute(nums []int) [][]int { res := [][]int{} track := []int{} // 记录「路径」 used := make([]bool, len(nums)) // 「路径」中的元素会被标记为 true,避免重复使用 var backtrack func(int) // 定义回溯函数 backtrack = func(level int) { if level == len(nums) { // 触发结束条件 tmp := make([]int, len(track)) copy(tmp, track) res = append(res, tmp) return } // 枚举出所有可能的选择 for i := 0; i < len(nums); i++ { if used[i] { // 排除不合法的选择 continue } track = append(track, nums[i]) // 做选择 used[i] = true backtrack(level+1) // 进入下一层决策树 track = track[:len(track)-1] // 取消选择 used[i] = false } } backtrack(0) return res } ``` ```java // by labuladong (java) class Solution { List> res = new LinkedList<>(); /* 主函数,输入一组不重复的数字,返回它们的全排列 */ List> permute(int[] nums) { // 记录「路径」 LinkedList track = new LinkedList<>(); // 「路径」中的元素会被标记为 true,避免重复使用 boolean[] used = new boolean[nums.length]; backtrack(nums, track, used); return res; } // 路径:记录在 track 中 // 选择列表:nums 中不存在于 track 的那些元素(used[i] 为 false) // 结束条件:nums 中的元素全都在 track 中出现 void backtrack(int[] nums, LinkedList track, boolean[] used) { // 触发结束条件 if (track.size() == nums.length) { res.add(new LinkedList(track)); return; } for (int i = 0; i < nums.length; i++) { // 排除不合法的选择 if (used[i]) { /** ![](../pictures/backtracking/6.jpg) */ // nums[i] 已经在 track 中,跳过 continue; } // 做选择 track.add(nums[i]); used[i] = true; // 进入下一层决策树 backtrack(nums, track, used); // 取消选择 track.removeLast(); used[i] = false; } } } ``` ```javascript // by chatGPT (javascript) var permute = function(nums) { let res = []; let track = []; let used = new Array(nums.length).fill(false); // 路径:记录在 track 中 // 选择列表:nums 中不存在于 track 的那些元素(used[i] 为 false) // 结束条件:nums 中的元素全都在 track 中出现 const backtrack = (nums, track, used) => { // 触发结束条件 if (track.length === nums.length) { res.push([...track]); return; } for (let i = 0; i < nums.length; i++) { // 排除不合法的选择 if (used[i]) { // nums[i] 已经在 track 中,跳过 continue; } // 做选择 track.push(nums[i]); used[i] = true; // 进入下一层决策树 backtrack(nums, track, used); // 取消选择 track.pop(); used[i] = false; } } backtrack(nums, track, used); return res; } ``` ```python # by chatGPT (python) class Solution: def __init__(self): self.res = [] # 主函数,输入一组不重复的数字,返回它们的全排列 def permute(self, nums: List[int]) -> List[List[int]]: # 记录「路径」 track = [] # 「路径」中的元素会被标记为 true,避免重复使用 used = [False] * len(nums) self.backtrack(nums, track, used) return self.res # 路径:记录在 track 中 # 选择列表:nums 中不存在于 track 的那些元素(used[i] 为 false) # 结束条件:nums 中的元素全都在 track 中出现 def backtrack(self, nums: List[int], track: List[int], used: List[bool]) -> None: # 触发结束条件 if len(track) == len(nums): self.res.append(track[:]) return for i in range(len(nums)): # 排除不合法的选择 if used[i]: # nums[i] 已经在 track 中,跳过 continue # 做选择 track.append(nums[i]) used[i] = True # 进入下一层决策树 self.backtrack(nums, track, used) # 取消选择 track.pop() used[i] = False ``` https://leetcode.cn/problems/permutations 的多语言解法👆 https://leetcode.cn/problems/populating-next-right-pointers-in-each-node 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: // 主函数 Node* connect(Node* root) { if (root == nullptr) return nullptr; // 遍历「三叉树」,连接相邻节点 traverse(root->left, root->right); return root; } // 三叉树遍历框架 void traverse(Node* node1, Node* node2) { if (node1 == nullptr || node2 == nullptr) { return; } /**** 前序位置 ****/ // 将传入的两个节点穿起来 node1->next = node2; // 连接相同父节点的两个子节点 traverse(node1->left, node1->right); traverse(node2->left, node2->right); // 连接跨越父节点的两个子节点 traverse(node1->right, node2->left); } }; ``` ```go // by chatGPT (go) func connect(root *Node) *Node { if root == nil { return nil } // 二叉树层序遍历框架 q := make([]*Node, 0) q = append(q, root) for len(q) > 0 { sz := len(q) // 遍历一层 var pre *Node for i := 0; i < sz; i++ { cur := q[0] q = q[1:] // 链接当前层所有节点的 next 指针 if pre != nil { pre.Next = cur } pre = cur // 将下一层节点装入队列 if cur.Left != nil { q = append(q, cur.Left) } if cur.Right != nil { q = append(q, cur.Right) } } } return root } ``` ```java // by labuladong (java) class Solution { // 主函数 public Node connect(Node root) { if (root == null) return null; // 遍历「三叉树」,连接相邻节点 traverse(root.left, root.right); return root; } // 三叉树遍历框架 void traverse(Node node1, Node node2) { if (node1 == null || node2 == null) { return; } /**** 前序位置 ****/ // 将传入的两个节点穿起来 node1.next = node2; // 连接相同父节点的两个子节点 traverse(node1.left, node1.right); traverse(node2.left, node2.right); // 连接跨越父节点的两个子节点 traverse(node1.right, node2.left); } } ``` ```javascript // by chatGPT (javascript) var connect = function(root) { if (root === null) { return null; } // 二叉树层序遍历框架 const q = []; q.push(root); while (q.length > 0) { const sz = q.length; // 遍历一层 let pre = null; for (let i = 0; i < sz; i++) { const cur = q.shift(); // 链接当前层所有节点的 next 指针 if (pre !== null) { pre.next = cur; } pre = cur; // 将下一层节点装入队列 if (cur.left !== null) { q.push(cur.left); } if (cur.right !== null) { q.push(cur.right); } } } return root; }; ``` ```python # by chatGPT (python) # Definition for a Node. # class Node: # def __init__(self, val = 0, left = None, right = None, next = None): # self.val = val # self.left = left # self.right = right # self.next = next class Solution: # 主函数 def connect(self, root: 'Node') -> 'Node': if not root: return # 遍历「三叉树」,连接相邻节点 self.traverse(root.left, root.right) return root # 三叉树遍历框架 def traverse(self, node1, node2): # 终止条件 if not node1 or not node2: return # 将传入的两个节点穿起来 node1.next = node2 # 连接相同父节点的两个子节点 self.traverse(node1.left, node1.right) self.traverse(node2.left, node2.right) # 连接跨越父节点的两个子节点 self.traverse(node1.right, node2.left) ``` https://leetcode.cn/problems/populating-next-right-pointers-in-each-node 的多语言解法👆 https://leetcode.cn/problems/possible-bipartition 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { private: bool ok = true; vector color; vector visited; public: bool possibleBipartition(int n, vector>& dislikes) { // 图节点编号从 1 开始 color.resize(n + 1); visited.resize(n + 1); // 转化成邻接表表示图结构 vector> graph = buildGraph(n, dislikes); for (int v = 1; v <= n; v++) { if (!visited[v]) { traverse(graph, v); } } return ok; } // 建图函数 vector> buildGraph(int n, vector>& dislikes) { // 图节点编号为 1...n vector> graph(n + 1); for (int i = 1; i <= n; i++) { graph[i] = vector(); } for (auto& edge : dislikes) { int v = edge[1]; int w = edge[0]; // 「无向图」相当于「双向图」 // v -> w graph[v].push_back(w); // w -> v graph[w].push_back(v); } return graph; } // 和之前判定二分图的 traverse 函数完全相同 void traverse(vector>& graph, int v) { if (!ok) return; visited[v] = true; for (int w : graph[v]) { if (!visited[w]) { color[w] = !color[v]; traverse(graph, w); } else { if (color[w] == color[v]) { ok = false; } } } } }; ``` ```go // by chatGPT (go) import "fmt" func possibleBipartition(n int, dislikes [][]int) bool { color := make([]bool, n+1) visited := make([]bool, n+1) ok := true // 转化成邻接表表示图结构 graph := buildGraph(n, dislikes) var traverse func(int) traverse = func(v int) { if !ok { return } visited[v] = true for _, w := range graph[v] { if !visited[w] { color[w] = !color[v] traverse(w) } else { if color[w] == color[v] { ok = false } } } } // 全图遍历,防止非连通图出现 for v := 1; v <= n; v++ { if !visited[v] { traverse(v) } } return ok } // 建图函数 func buildGraph(n int, dislikes [][]int) []([]int) { graph := make([]([]int), n+1) for i := range graph { graph[i] = make([]int, 0) } for _, edge := range dislikes { v := edge[1] w := edge[0] // 「无向图」相当于「双向图」 // v -> w graph[v] = append(graph[v], w) // w -> v graph[w] = append(graph[w], v) } return graph } ``` ```java // by labuladong (java) 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]) { traverse(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 traverse(List[] graph, int v) { if (!ok) return; visited[v] = true; for (int w : graph[v]) { if (!visited[w]) { color[w] = !color[v]; traverse(graph, w); } else { if (color[w] == color[v]) { ok = false; } } } } } ``` ```javascript // by chatGPT (javascript) /** * @param {number} n * @param {number[][]} dislikes * @return {boolean} */ var possibleBipartition = function(n, dislikes) { let ok = true; let color = new Array(n + 1); let visited = new Array(n + 1); let graph = buildGraph(n, dislikes); // 建图函数 function buildGraph(n, dislikes) { // 图节点编号为 1...n let graph = new Array(n + 1); for (let i = 1; i <= n; i++) { graph[i] = new Array(); } for (let i = 0; i < dislikes.length; i++) { let v = dislikes[i][0]; let w = dislikes[i][1]; // 「无向图」相当于「双向图」 // v -> w graph[v].push(w); // w -> v graph[w].push(v); } return graph; } // 和之前判定二分图的 traverse 函数完全相同 function traverse(graph, v) { if (!ok) return; visited[v] = true; for (let i = 0; i < graph[v].length; i++) { let w = graph[v][i]; if (!visited[w]) { color[w] = !color[v]; traverse(graph, w); } else { if (color[w] == color[v]) { ok = false; } } } } for (let v = 1; v <= n; v++) { if (!visited[v]) { traverse(graph, v); } } return ok; }; ``` ```python # by chatGPT (python) class Solution: def __init__(self): self.ok = True self.color = None self.visited = None def possibleBipartition(self, n: int, dislikes: List[List[int]]) -> bool: # 图节点编号从 1 开始 self.color = [False] * (n + 1) self.visited = [False] * (n + 1) # 转化成邻接表表示图结构 graph = self.buildGraph(n, dislikes) for v in range(1, n+1): if not self.visited[v]: self.traverse(graph, v) return self.ok # 建图函数 def buildGraph(self, n: int, dislikes: List[List[int]]) -> List[List[int]]: # 图节点编号为 1...n graph = [[] for _ in range(n + 1)] for edge in dislikes: v = edge[1] w = edge[0] # 「无向图」相当于「双向图」 # v -> w graph[v].append(w) # w -> v graph[w].append(v) return graph # 和之前判定二分图的 traverse 函数完全相同 def traverse(self, graph: List[List[int]], v: int) -> None: if not self.ok: return self.visited[v] = True for w in graph[v]: if not self.visited[w]: self.color[w] = not self.color[v] self.traverse(graph, w) else: if self.color[w] == self.color[v]: self.ok = False ``` https://leetcode.cn/problems/possible-bipartition 的多语言解法👆 https://leetcode.cn/problems/power-of-two 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: bool isPowerOfTwo(int n) { if (n <= 0) return false; return (n & (n - 1)) == 0; } }; ``` ```go // by chatGPT (go) // Solution to LeetCode problem: Power of Two // https://leetcode.com/problems/power-of-two/ import "fmt" func isPowerOfTwo(n int) bool { if n <= 0 { return false } return (n & (n - 1)) == 0 } ``` ```java // by labuladong (java) class Solution { public boolean isPowerOfTwo(int n) { if (n <= 0) return false; return (n & (n - 1)) == 0; } } ``` ```javascript // by chatGPT (javascript) /** * @param {number} n * @return {boolean} */ var isPowerOfTwo = function(n) { if (n <= 0) return false; return (n & (n - 1)) == 0; }; ``` ```python # by chatGPT (python) class Solution: def isPowerOfTwo(self, n: int) -> bool: if n <= 0: return False return (n & (n - 1)) == 0 ``` https://leetcode.cn/problems/power-of-two 的多语言解法👆 https://leetcode.cn/problems/powx-n 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: double myPow(double a, int k) { if (k == 0) return 1; if (k == INT_MIN) { // 把 k 是 INT_MIN 的情况单独拿出来处理 // 避免 -k 整型溢出 return myPow(1 / a, -(k + 1)) / a; } if (k < 0) { return myPow(1 / a, -k); } if (k % 2 == 1) { // k 是奇数 return (a * myPow(a, k - 1)); } else { // k 是偶数 double sub = myPow(a, k / 2); return (sub * sub); } } }; ``` ```go // by chatGPT (go) // Golang Code func myPow(a float64, k int) float64 { if k == 0 { return 1 } if k == math.MinInt32 { // 把 k 是 INT_MIN 的情况单独拿出来处理 // 避免 -k 整型溢出 return myPow(1 / a, -(k + 1)) / a } if k < 0 { return myPow(1 / a, -k) } if k % 2 == 1 { // k 是奇数 return (a * myPow(a, k - 1)) } else { // k 是偶数 sub := myPow(a, k / 2) return (sub * sub) } } ``` ```java // by labuladong (java) class Solution { public double myPow(double a, int k) { if (k == 0) return 1; if (k == Integer.MIN_VALUE) { // 把 k 是 INT_MIN 的情况单独拿出来处理 // 避免 -k 整型溢出 return myPow(1 / a, -(k + 1)) / a; } if (k < 0) { return myPow(1 / a, -k); } if (k % 2 == 1) { // k 是奇数 return (a * myPow(a, k - 1)); } else { // k 是偶数 double sub = myPow(a, k / 2); return (sub * sub); } } } ``` ```javascript // by chatGPT (javascript) /** * @param {number} a * @param {number} k * @return {number} */ var myPow = function(a, k) { if (k === 0) return 1; if (k === -2147483648) { // 把 k 是 INT_MIN 的情况单独拿出来处理 // 避免 -k 整型溢出 return myPow(1 / a, -(k + 1)) / a; } if (k < 0) { return myPow(1 / a, -k); } if (k % 2 === 1) { // k 是奇数 return (a * myPow(a, k - 1)); } else { // k 是偶数 const sub = myPow(a, k / 2); return (sub * sub); } }; ``` ```python # by chatGPT (python) class Solution: def myPow(self, a: float, k: int) -> float: if k == 0: return 1 if k == -2147483648: # 把 k 是 INT_MIN 的情况单独拿出来处理 # 避免 -k 整型溢出 return self.myPow(1 / a, -(k + 1)) / a if k < 0: return self.myPow(1 / a, -k) if k % 2 == 1: # k 是奇数 return a * self.myPow(a, k - 1) else: # k 是偶数 sub = self.myPow(a, k // 2) return sub * sub ``` https://leetcode.cn/problems/powx-n 的多语言解法👆 https://leetcode.cn/problems/preimage-size-of-factorial-zeroes-function 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int preimageSizeFZF(int K) { // 左边界和右边界之差 + 1 就是答案 return (int)(right_bound(K) - left_bound(K) + 1); } // 逻辑不变,数据类型全部改成 long long trailingZeroes(long n) { long res = 0; for (long d = n; d / 5 > 0; d = d / 5) { res += d / 5; } return res; } /* 搜索 trailingZeroes(n) == K 的左侧边界 */ long left_bound(int target) { long lo = 0, hi = LONG_MAX; while (lo < hi) { long mid = lo + (hi - lo) / 2; if (trailingZeroes(mid) < target) { lo = mid + 1; } else if (trailingZeroes(mid) > target) { hi = mid; } else { hi = mid; } } return lo; } /* 搜索 trailingZeroes(n) == K 的右侧边界 */ long right_bound(int target) { long lo = 0, hi = LONG_MAX; while (lo < hi) { long mid = lo + (hi - lo) / 2; if (trailingZeroes(mid) < target) { lo = mid + 1; } else if (trailingZeroes(mid) > target) { hi = mid; } else { lo = mid + 1; } } return lo - 1; } }; ``` ```go // by chatGPT (go) import "math" func preimageSizeFZF(K int) int { // 左边界和右边界之差 + 1 就是答案 return int(right_bound(K) - left_bound(K) + 1) } // 逻辑不变,数据类型全部改成 int64 func trailingZeroes(n int64) int64 { var res int64 = 0 for d := n; d / 5 > 0; d = d / 5 { res += d / 5 } return res } /* 搜索 trailingZeroes(n) == K 的左侧边界 */ func left_bound(target int) int64 { var lo, hi int64 = 0, math.MaxInt64 for lo < hi { mid := lo + (hi - lo) / 2 if trailingZeroes(mid) < int64(target) { lo = mid + 1 } else if trailingZeroes(mid) > int64(target) { hi = mid } else { hi = mid } } return lo } /* 搜索 trailingZeroes(n) == K 的右侧边界 */ func right_bound(target int) int64 { var lo, hi int64 = 0, math.MaxInt64 for lo < hi { mid := lo + (hi - lo) / 2 if trailingZeroes(mid) < int64(target) { lo = mid + 1 } else if trailingZeroes(mid) > int64(target) { hi = mid } else { lo = mid + 1 } } return lo - 1 } ``` ```java // by labuladong (java) class Solution { public int preimageSizeFZF(int K) { // 左边界和右边界之差 + 1 就是答案 return (int)(right_bound(K) - left_bound(K) + 1); } // 逻辑不变,数据类型全部改成 long long trailingZeroes(long n) { long res = 0; for (long d = n; d / 5 > 0; d = d / 5) { res += d / 5; } return res; } /* 搜索 trailingZeroes(n) == K 的左侧边界 */ long left_bound(int target) { long lo = 0, hi = Long.MAX_VALUE; while (lo < hi) { long mid = lo + (hi - lo) / 2; if (trailingZeroes(mid) < target) { lo = mid + 1; } else if (trailingZeroes(mid) > target) { hi = mid; } else { hi = mid; } } return lo; } /* 搜索 trailingZeroes(n) == K 的右侧边界 */ long right_bound(int target) { long lo = 0, hi = Long.MAX_VALUE; while (lo < hi) { long mid = lo + (hi - lo) / 2; if (trailingZeroes(mid) < target) { lo = mid + 1; } else if (trailingZeroes(mid) > target) { hi = mid; } else { lo = mid + 1; } } return lo - 1; } } ``` ```javascript // by chatGPT (javascript) var preimageSizeFZF = function (K) { // 左边界和右边界之差 + 1 就是答案 return right_bound(K) - left_bound(K) + 1; }; // 逻辑不变,数据类型全部改成 long var trailingZeroes = function (n) { var res = 0; for (var d = n; Math.floor(d / 5) > 0; d = Math.floor(d / 5)) { res += Math.floor(d / 5); } return res; }; /* 搜索 trailingZeroes(n) == K 的左侧边界 */ var left_bound = function (target) { var lo = 0, hi = Number.MAX_VALUE; while (lo < hi) { var mid = lo + Math.floor((hi - lo) / 2); if (trailingZeroes(mid) < target) { lo = mid + 1; } else if (trailingZeroes(mid) > target) { hi = mid; } else { hi = mid; } } return lo; }; /* 搜索 trailingZeroes(n) == K 的右侧边界 */ var right_bound = function (target) { var lo = 0, hi = Number.MAX_VALUE; while (lo < hi) { var mid = lo + Math.floor((hi - lo) / 2); if (trailingZeroes(mid) < target) { lo = mid + 1; } else if (trailingZeroes(mid) > target) { hi = mid; } else { lo = mid + 1; } } return lo - 1; }; ``` ```python # by chatGPT (python) class Solution: def preimageSizeFZF(self, K: int) -> int: # 左边界和右边界之差 + 1 就是答案 return int(self.right_bound(K) - self.left_bound(K) + 1) # 逻辑不变,数据类型全部改成 long def trailingZeroes(self, n: int) -> int: res = 0 for d in range(n, 0, -1): while d % 5 == 0: res += 1 d //= 5 return res """ 搜索 trailingZeroes(n) == K 的左侧边界 """ def left_bound(self, target: int) -> int: lo, hi = 0, float('inf') while lo < hi: mid = lo + (hi - lo) // 2 if self.trailingZeroes(mid) < target: lo = mid + 1 elif self.trailingZeroes(mid) > target: hi = mid else: hi = mid return lo """ 搜索 trailingZeroes(n) == K 的右侧边界 """ def right_bound(self, target: int) -> int: lo, hi = 0, float('inf') while lo < hi: mid = lo + (hi - lo) // 2 if self.trailingZeroes(mid) < target: lo = mid + 1 elif self.trailingZeroes(mid) > target: hi = mid else: lo = mid + 1 return lo - 1 ``` https://leetcode.cn/problems/preimage-size-of-factorial-zeroes-function 的多语言解法👆 https://leetcode.cn/problems/product-of-the-last-k-numbers 的多语言解法👇 ```cpp // by chatGPT (cpp) class ProductOfNumbers { // 前缀积数组 // preProduct[i] / preProduct[j] 就是 [i, j] 之间的元素积 vector preProduct; public: ProductOfNumbers() { // 初始化放一个 1,便于计算后续添加元素的乘积 preProduct.push_back(1); } void add(int num) { if (num == 0) { // 如果添加的元素是 0,则前面的元素积都废了 preProduct.clear(); preProduct.push_back(1); return; } int n = preProduct.size(); // 前缀积数组中每个元素 preProduct.push_back(preProduct[n - 1] * num); } int getProduct(int k) { int n = preProduct.size(); if (k > n - 1) { // 不足 k 个元素,是因为最后 k 个元素存在 0 return 0; } // 计算最后 k 个元素积 return preProduct[n - 1] / preProduct[n - k - 1]; } }; ``` ```go // by chatGPT (go) type ProductOfNumbers struct { // 前缀积数组 // preProduct[i] / preProduct[j] 就是 [i, j] 之间的元素积 preProduct []int } func Constructor() ProductOfNumbers { // 初始化放一个 1,便于计算后续添加元素的乘积 return ProductOfNumbers{[]int{1}} } func (this *ProductOfNumbers) Add(num int) { if num == 0 { // 如果添加的元素是 0,则前面的元素积都废了 this.preProduct = []int{1} return } n := len(this.preProduct) // 前缀积数组中每个元素 this.preProduct = append(this.preProduct, this.preProduct[n-1]*num) } func (this *ProductOfNumbers) GetProduct(k int) int { n := len(this.preProduct) if k > n-1 { // 不足 k 个元素,是因为最后 k 个元素存在 0 return 0 } // 计算最后 k 个元素积 return this.preProduct[n-1] / this.preProduct[n-k-1] } ``` ```java // by labuladong (java) class ProductOfNumbers { // 前缀积数组 // preProduct[i] / preProduct[j] 就是 [i, j] 之间的元素积 ArrayList preProduct = new ArrayList<>(); public ProductOfNumbers() { // 初始化放一个 1,便于计算后续添加元素的乘积 preProduct.add(1); } public void add(int num) { if (num == 0) { // 如果添加的元素是 0,则前面的元素积都废了 preProduct.clear(); preProduct.add(1); return; } int n = preProduct.size(); // 前缀积数组中每个元素 preProduct.add(preProduct.get(n - 1) * num); } public int getProduct(int k) { int n = preProduct.size(); if (k > n - 1) { // 不足 k 个元素,是因为最后 k 个元素存在 0 return 0; } // 计算最后 k 个元素积 return preProduct.get(n - 1) / preProduct.get(n - k - 1); } } ``` ```javascript // by chatGPT (javascript) var ProductOfNumbers = function() { // 前缀积数组 // preProduct[i] / preProduct[j] 就是 [i, j] 之间的元素积 this.preProduct = [1]; }; ProductOfNumbers.prototype.add = function(num) { if (num === 0) { // 如果添加的元素是 0,则前面的元素积都废了 this.preProduct = [1]; return; } var n = this.preProduct.length; // 前缀积数组中每个元素 this.preProduct.push(this.preProduct[n - 1] * num); }; ProductOfNumbers.prototype.getProduct = function(k) { var n = this.preProduct.length; if (k > n - 1) { // 不足 k 个元素,是因为最后 k 个元素存在 0 return 0; } // 计算最后 k 个元素积 return this.preProduct[n - 1] / this.preProduct[n - k - 1]; }; ``` ```python # by chatGPT (python) class ProductOfNumbers: # 前缀积数组 # preProduct[i] / preProduct[j] 就是 [i, j] 之间的元素积 def __init__(self): self.preProduct = [1] def add(self, num: int) -> None: if num == 0: # 如果添加的元素是 0,则前面的元素积都废了 self.preProduct.clear() self.preProduct.append(1) return n = len(self.preProduct) # 前缀积数组中每个元素 self.preProduct.append(self.preProduct[n - 1] * num) def getProduct(self, k: int) -> int: n = len(self.preProduct) if k > n - 1: # 不足 k 个元素,是因为最后 k 个元素存在 0 return 0 # 计算最后 k 个元素积 return self.preProduct[n - 1] // self.preProduct[n - k - 1] ``` https://leetcode.cn/problems/product-of-the-last-k-numbers 的多语言解法👆 https://leetcode.cn/problems/qJnOS7 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int longestCommonSubsequence(string s1, string s2) { int m = s1.length(), n = s2.length(); // 定义:s1[0..i-1] 和 s2[0..j-1] 的 lcs 长度为 dp[i][j] vector> dp(m + 1, vector(n + 1, 0)); // 目标:s1[0..m-1] 和 s2[0..n-1] 的 lcs 长度,即 dp[m][n] // base case: dp[0][..] = dp[..][0] = 0 for (int i = 1; i <= m; i++) { for (int j = 1; j <= n; j++) { // 现在 i 和 j 从 1 开始,所以要减一 if (s1[i - 1] == s2[j - 1]) { // s1[i-1] 和 s2[j-1] 必然在 lcs 中 dp[i][j] = 1 + dp[i - 1][j - 1]; } else { // s1[i-1] 和 s2[j-1] 至少有一个不在 lcs 中 dp[i][j] = max(dp[i][j - 1], dp[i - 1][j]); } } } return dp[m][n]; } }; ``` ```go // by chatGPT (go) func longestCommonSubsequence(s1 string, s2 string) int { m, n := len(s1), len(s2) // 定义:s1[0..i-1] 和 s2[0..j-1] 的 lcs 长度为 dp[i][j] dp := make([][]int, m+1) for i := 0; i <= m; i++ { dp[i] = make([]int, n+1) } // 目标:s1[0..m-1] 和 s2[0..n-1] 的 lcs 长度,即 dp[m][n] // base case: dp[0][..] = dp[..][0] = 0 for i := 1; i <= m; i++ { for j := 1; j <= n; j++ { // 现在 i 和 j 从 1 开始,所以要减一 if s1[i-1] == s2[j-1] { // s1[i-1] 和 s2[j-1] 必然在 lcs 中 dp[i][j] = 1 + dp[i-1][j-1] } else { // s1[i-1] 和 s2[j-1] 至少有一个不在 lcs 中 dp[i][j] = max(dp[i][j-1], dp[i-1][j]) } } } return dp[m][n] } func max(a, b int) int { if a > b { return a } return b } ``` ```java // by labuladong (java) class Solution { public int longestCommonSubsequence(String s1, String s2) { int m = s1.length(), n = s2.length(); // 定义:s1[0..i-1] 和 s2[0..j-1] 的 lcs 长度为 dp[i][j] int[][] dp = new int[m + 1][n + 1]; // 目标:s1[0..m-1] 和 s2[0..n-1] 的 lcs 长度,即 dp[m][n] // base case: dp[0][..] = dp[..][0] = 0 for (int i = 1; i <= m; i++) { for (int j = 1; j <= n; j++) { // 现在 i 和 j 从 1 开始,所以要减一 if (s1.charAt(i - 1) == s2.charAt(j - 1)) { // s1[i-1] 和 s2[j-1] 必然在 lcs 中 dp[i][j] = 1 + dp[i - 1][j - 1]; } else { // s1[i-1] 和 s2[j-1] 至少有一个不在 lcs 中 dp[i][j] = Math.max(dp[i][j - 1], dp[i - 1][j]); } } } return dp[m][n]; } } ``` ```javascript // by chatGPT (javascript) var longestCommonSubsequence = function(s1, s2) { var m = s1.length, n = s2.length; // 定义:s1[0..i-1] 和 s2[0..j-1] 的 lcs 长度为 dp[i][j] var dp = new Array(m + 1); for(var i = 0;i int: m, n = len(s1), len(s2) # 定义:s1[0..i-1] 和 s2[0..j-1] 的 lcs 长度为 dp[i][j] dp = [[0] * (n + 1) for _ in range(m + 1)] # 目标:s1[0..m-1] 和 s2[0..n-1] 的 lcs 长度,即 dp[m][n] # base case: dp[0][..] = dp[..][0] = 0 for i in range(1, m + 1): for j in range(1, n + 1): # 现在 i 和 j 从 1 开始,所以要减一 if s1[i - 1] == s2[j - 1]: # s1[i-1] 和 s2[j-1] 必然在 lcs 中 dp[i][j] = 1 + dp[i - 1][j - 1] else: # s1[i-1] 和 s2[j-1] 至少有一个不在 lcs 中 dp[i][j] = max(dp[i][j - 1], dp[i - 1][j]) return dp[m][n] ``` https://leetcode.cn/problems/qJnOS7 的多语言解法👆 https://leetcode.cn/problems/qing-wa-tiao-tai-jie-wen-ti-lcof 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { // 备忘录 vector memo; int MOD = 1000000007; public: int numWays(int n) { if (n == 0) { return 1; } memo = vector(n + 1, 0); return dp(n); } // 定义:爬到第 n 级台阶的方法个数为 dp(n) int dp(int n) { // base case if (n <= 2) { return n; } if (memo[n] > 0) { return memo[n]; } // 状态转移方程: // 爬到第 n 级台阶的方法个数等于爬到 n - 1 的方法个数和爬到 n - 2 的方法个数之和。 memo[n] = (dp(n - 1) + dp(n - 2)) % MOD; return memo[n]; } }; ``` ```go // by chatGPT (go) func numWays(n int) int { memo := make([]int, n+1) var dp func(int) int dp = func(n int) int { // base case if n <= 2 { return n } if memo[n] > 0 { return memo[n] } // 状态转移方程: // 爬到第 n 级台阶的方法个数等于爬到 n - 1 的方法个数和爬到 n - 2 的方法个数之和。 memo[n] = dp(n-1) + dp(n-2) return memo[n] } return dp(n) } ``` ```java // by labuladong (java) class Solution { // 备忘录 int[] memo; public int climbStairs(int n) { memo = new int[n + 1]; return dp(n); } // 定义:爬到第 n 级台阶的方法个数为 dp(n) int dp(int n) { // base case if (n <= 2) { return n; } if (memo[n] > 0) { return memo[n]; } // 状态转移方程: // 爬到第 n 级台阶的方法个数等于爬到 n - 1 的方法个数和爬到 n - 2 的方法个数之和。 memo[n] = dp(n - 1) + dp(n - 2); return memo[n]; } } ``` ```javascript // by chatGPT (javascript) /** * @param {number} n * @return {number} */ var numWays = function(n) { // 备忘录 const memo = []; memo.length = n + 1; memo.fill(0); return dp(n, memo); }; /** * @param {number} n * @param {number[]} memo * @return {number} */ var dp = function(n, memo) { // base case if (n <= 2) { return n; } if (memo[n] > 0) { return memo[n]; } // 状态转移方程: // 爬到第 n 级台阶的方法个数等于爬到 n - 1 的方法个数和爬到 n - 2 的方法个数之和。 memo[n] = dp(n - 1, memo) + dp(n - 2, memo); return memo[n]; }; ``` ```python # by chatGPT (python) class Solution: # 备忘录 memo = [] MOD = 1e9 + 7 def numWays(self, n: int) -> int: self.memo = [-1] * (n + 1) return self.dp(n) # 定义:爬到第 n 级台阶的方法个数为 dp(n) def dp(self, n: int) -> int: # base case if n <= 2: return n if n else 1 if self.memo[n] != -1: return self.memo[n] # 状态转移方程: # 爬到第 n 级台阶的方法个数等于爬到 n - 1 的方法个数和爬到 n - 2 的方法个数之和。 self.memo[n] = self.dp(n - 1) + self.dp(n - 2) return round(self.memo[n] % self.MOD) ``` https://leetcode.cn/problems/qing-wa-tiao-tai-jie-wen-ti-lcof 的多语言解法👆 https://leetcode.cn/problems/qn8gGX 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: vector> kSmallestPairs(vector& nums1, vector& nums2, int k) { // 存储三元组 (num1[i], nums2[i], i) // i 记录 nums2 元素的索引位置,用于生成下一个节点 auto cmp = [](auto & a, auto & b) { return a[0] + a[1] > b[0] + b[1]; }; priority_queue, vector>, decltype(cmp)> pq(cmp); // 按照 23 题的逻辑初始化优先级队列 for (int i = 0; i < nums1.size(); i++) { pq.push({nums1[i], nums2[0], 0}); } vector> res; // 执行合并多个有序链表的逻辑 while (!pq.empty() && k > 0) { auto cur = pq.top(); pq.pop(); k--; // 链表中的下一个节点加入优先级队列 int next_index = cur[2] + 1; if (next_index < nums2.size()) { pq.push({cur[0], nums2[next_index], next_index}); } vector pair; pair.push_back(cur[0]); pair.push_back(cur[1]); res.push_back(pair); } return res; } }; ``` ```go // by chatGPT (go) import ( "container/heap" "fmt" ) type item struct { num1 int num2 int num2Index int } // 用于转换 item 为 heap 中实际存储的数据 type itemHeap []item func (h itemHeap) Len() int { return len(h) } func (h itemHeap) Less(i, j int) bool { return h[i].num1 + h[i].num2 < h[j].num1 + h[j].num2 } func (h itemHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] } func (h *itemHeap) Push(x interface{}) { *h = append(*h, x.(item)) } func (h *itemHeap) Pop() interface{} { old := *h n := len(old) x := old[n-1] *h = old[:n-1] return x } func kSmallestPairs(nums1 []int, nums2 []int, k int) [][]int { // 存储三元组 (num1[i], nums2[i], i) // i 记录 nums2 元素的索引位置,用于生成下一个节点 pq := &itemHeap{} heap.Init(pq) // 按照23题的逻辑初始化优先级队列 for i := 0; i < len(nums1); i++ { heap.Push(pq, item{nums1[i], nums2[0], 0}) } res := [][]int{} // 执行合并多个有序链表的逻辑 for pq.Len() > 0 && k > 0 { cur := heap.Pop(pq).(item) k-- // 链表中的下一个节点加入优先级队列 nextIndex := cur.num2Index + 1 if nextIndex < len(nums2) { heap.Push(pq, item{cur.num1, nums2[nextIndex], nextIndex}) } pair := []int{cur.num1, cur.num2} res = append(res, pair) } return res } ``` ```java // by labuladong (java) class Solution { public List> kSmallestPairs(int[] nums1, int[] nums2, int k) { // 存储三元组 (num1[i], nums2[i], i) // i 记录 nums2 元素的索引位置,用于生成下一个节点 PriorityQueue pq = new PriorityQueue<>((a, b) -> { // 按照数对的元素和升序排序 return (a[0] + a[1]) - (b[0] + b[1]); }); // 按照 23 题的逻辑初始化优先级队列 for (int i = 0; i < nums1.length; i++) { pq.offer(new int[]{nums1[i], nums2[0], 0}); } List> res = new ArrayList<>(); // 执行合并多个有序链表的逻辑 while (!pq.isEmpty() && k > 0) { int[] cur = pq.poll(); k--; // 链表中的下一个节点加入优先级队列 int next_index = cur[2] + 1; if (next_index < nums2.length) { pq.add(new int[]{cur[0], nums2[next_index], next_index}); } List pair = new ArrayList<>(); pair.add(cur[0]); pair.add(cur[1]); res.add(pair); } return res; } } ``` ```javascript // by chatGPT (javascript) /** * @param {number[]} nums1 * @param {number[]} nums2 * @param {number} k * @return {number[][]} */ var kSmallestPairs = function(nums1, nums2, k) { // 存储三元组 (num1[i], nums2[i], i) // i 记录 nums2 元素的索引位置,用于生成下一个节点 const pq = new PriorityQueue((a, b) => { // 按照数对的元素和升序排序 return (a[0] + a[1]) - (b[0] + b[1]); }); // 按照 23 题的逻辑初始化优先级队列 for (let i = 0; i < nums1.length; i++) { pq.offer([nums1[i], nums2[0], 0]); } const res = []; // 执行合并多个有序链表的逻辑 while (!pq.isEmpty() && k > 0) { const cur = pq.poll(); k--; // 链表中的下一个节点加入优先级队列 const next_index = cur[2] + 1; if (next_index < nums2.length) { pq.add([cur[0], nums2[next_index], next_index]); } const pair = []; pair.push(cur[0]); pair.push(cur[1]); res.push(pair); } return res; }; // An implementation of the PriorityQueue. class PriorityQueue { constructor(compare = (a, b) => a - b) { this.compare = compare; this.heap = []; } get size() { return this.heap.length; } isEmpty() { return this.size === 0; } peek() { return this.heap[0]; } offer(node) { this.heap.push(node); this._siftUp(); } poll() { const poppedValue = this.peek(); const bottom = this.size - 1; if (bottom > 0) { this._swap(0, bottom); } this.heap.pop(); this._siftDown(); return poppedValue; } _greater(i, j) { return this.compare(this.heap[i], this.heap[j]) < 0; } _swap(i, j) { [this.heap[i], this.heap[j]] = [this.heap[j], this.heap[i]]; } _siftUp() { let nodeIdx = this.size - 1; while (nodeIdx > 0 && this._greater(nodeIdx, Math.floor((nodeIdx + 1) / 2) - 1)) { const parentIdx = Math.floor((nodeIdx + 1) / 2) - 1; this._swap(nodeIdx, parentIdx); nodeIdx = parentIdx; } } _siftDown() { let nodeIdx = 0; while ( (2 * nodeIdx + 1 < this.size && this._greater(2 * nodeIdx + 1, nodeIdx)) || (2 * nodeIdx + 2 < this.size && this._greater(2 * nodeIdx + 2, nodeIdx)) ) { const greaterChildIdx = 2 * nodeIdx + 2 >= this.size || this._greater(2 * nodeIdx + 1, 2 * nodeIdx + 2) ? 2 * nodeIdx + 1 : 2 * nodeIdx + 2; this._swap(greaterChildIdx, nodeIdx); nodeIdx = greaterChildIdx; } } } ``` ```python # by chatGPT (python) class Solution: def kSmallestPairs(self, nums1: List[int], nums2: List[int], k: int) -> List[List[int]]: # 存储三元组 (num1[i], nums2[i], i) # i 记录 nums2 元素的索引位置,用于生成下一个节点 pq = [] for i in range(len(nums1)): heapq.heappush(pq, [nums1[i]+nums2[0], nums1[i], nums2[0], 0]) res = [] while pq and k > 0: _, num1, num2, idx2 = heapq.heappop(pq) res.append([num1, num2]) k -= 1 if idx2 < len(nums2)-1: heapq.heappush(pq, [num1+nums2[idx2+1], num1, nums2[idx2+1], idx2+1]) return res ``` https://leetcode.cn/problems/qn8gGX 的多语言解法👆 https://leetcode.cn/problems/que-shi-de-shu-zi-lcof 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int missingNumber(vector& nums) { // 搜索左侧的二分搜索 int left = 0, right = nums.size() - 1; while (left <= right) { int mid = left + (right - left) / 2; if (nums[mid] > mid) { // mid 和 nums[mid] 不对应,说明左边有元素缺失 right = mid - 1; } else { // mid 和 nums[mid] 对应,说明元素缺失在右边 left = mid + 1; } } return left; } }; ``` ```go // by chatGPT (go) // missingNumber returns the missing number in the given array using binary search func missingNumber(nums []int) int { // 搜索左侧的二分搜索 left, right := 0, len(nums)-1 for left <= right { mid := left + (right - left) / 2 if nums[mid] > mid { // mid 和 nums[mid] 不对应,说明左边有元素缺失 right = mid - 1 } else { // mid 和 nums[mid] 对应,说明元素缺失在右边 left = mid + 1 } } return left } ``` ```java // by labuladong (java) class Solution { public int missingNumber(int[] nums) { // 搜索左侧的二分搜索 int left = 0, right = nums.length - 1; while (left <= right) { int mid = left + (right - left) / 2; if (nums[mid] > mid) { // mid 和 nums[mid] 不对应,说明左边有元素缺失 right = mid - 1; } else { // mid 和 nums[mid] 对应,说明元素缺失在右边 left = mid + 1; } } return left; } } ``` ```javascript // by chatGPT (javascript) var missingNumber = function(nums) { // 搜索左侧的二分搜索 var left = 0, right = nums.length - 1; while (left <= right) { var mid = left + Math.floor((right - left) / 2); if (nums[mid] > mid) { // mid 和 nums[mid] 不对应,说明左边有元素缺失 right = mid - 1; } else { // mid 和 nums[mid] 对应,说明元素缺失在右边 left = mid + 1; } } return left; } ``` ```python # by chatGPT (python) class Solution: def missingNumber(self, nums: List[int]) -> int: # 搜索左侧的二分搜索 left, right = 0, len(nums) - 1 while left <= right: mid = left + (right - left) // 2 if nums[mid] > mid: # mid 和 nums[mid] 不对应,说明左边有元素缺失 right = mid - 1 else: # mid 和 nums[mid] 对应,说明元素缺失在右边 left = mid + 1 return left ``` https://leetcode.cn/problems/que-shi-de-shu-zi-lcof 的多语言解法👆 https://leetcode.cn/problems/random-flip-matrix 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { private: int m, n; // 抽象一维数组的长度 int len; // 已删除元素到尾部未删除元素的映射 unordered_map deletedToExist; // 这个 deletedToExist 存的到底是什么呢?举个例子你就明白了: // 假设数组 [1,2,3,4,5],我第一次随机选择了元素 2, // 然后我就会在 deletedToExist 记录一个映射 2 -> 5,并删除最后的元素 5 // 现在数组是 [1,2,3,4],我第二次随机选择又选到了 2, // 但不能重复选择同一个元素,所以我选择 5,因为 deletedToExist[2] = 5 // 然后我再更新 deletedToExist,记录 2 -> 4 并删除最后的元素 4 // 以此类推,这样就能保证随机选择到相同的元素时也能等概率地返回不同的元素 mt19937 rand_engine = mt19937(time(nullptr)); public: Solution(int m, int n) { this->m = m; this->n = n; this->len = m * n; this->deletedToExist = unordered_map(); } vector flip() { int rand_num = rand_engine() % len; // 这个随机数可能已经被删掉了(刚才已经被选过) int res = rand_num; if (deletedToExist.count(rand_num)) { res = deletedToExist[rand_num]; } // 把 rand 置换到数组尾部 int last = len - 1; // 尾部的那个元素也可能已经被删掉了 if (deletedToExist.count(last)) { last = deletedToExist[last]; } deletedToExist[rand_num] = last; // 把尾部的这个元素删掉 len--; // 一维坐标转化成二维坐标 return {res / n, res % n}; } void reset() { this->len = this->m * this->n; this->deletedToExist.clear(); } }; ``` ```go // by chatGPT (go) type Solution struct { m, n int // 抽象一维数组的长度 len int // 已删除元素到尾部未删除元素的映射 deletedToExist map[int]int // 这个 deletedToExist 存的到底是什么呢?举个例子你就明白了: // 假设数组 [1,2,3,4,5],我第一次随机选择了元素 2, // 然后我就会在 deletedToExist 记录一个映射 2 -> 5,并删除最后的元素 5 // 现在数组是 [1,2,3,4],我第二次随机选择又选到了 2, // 但不能重复选择同一个元素,所以我选择 5,因为 deletedToExist[2] = 5 // 然后我再更新 deletedToExist,记录 2 -> 4 并删除最后的元素 4 // 以此类推,这样就能保证随机选择到相同的元素时也能等概率地返回不同的元素 random *rand.Rand } func Constructor(m int, n int) Solution { return Solution{ m: m, n: n, len: m * n, deletedToExist: make(map[int]int), random: rand.New(rand.NewSource(time.Now().Unix())), } } func (this *Solution) Flip() []int { rand := this.random.Intn(this.len) // 这个随机数可能已经被删掉了(刚才已经被选过) res := rand if val, ok := this.deletedToExist[rand]; ok { res = val } // 把 rand 置换到数组尾部 last := this.len - 1 // 尾部的那个元素也可能已经被删掉了 if val, ok := this.deletedToExist[last]; ok { last = val } this.deletedToExist[rand] = last // 把尾部的这个元素删掉 this.len-- // 一维坐标转化成二维坐标 return []int{res / this.n, res % this.n} } func (this *Solution) Reset() { this.len = this.m * this.n this.deletedToExist = make(map[int]int) } ``` ```java // by labuladong (java) class Solution { int m, n; // 抽象一维数组的长度 int len; // 已删除元素到尾部未删除元素的映射 HashMap deletedToExist; // 这个 deletedToExist 存的到底是什么呢?举个例子你就明白了: // 假设数组 [1,2,3,4,5],我第一次随机选择了元素 2, // 然后我就会在 deletedToExist 记录一个映射 2 -> 5,并删除最后的元素 5 // 现在数组是 [1,2,3,4],我第二次随机选择又选到了 2, // 但不能重复选择同一个元素,所以我选择 5,因为 deletedToExist[2] = 5 // 然后我再更新 deletedToExist,记录 2 -> 4 并删除最后的元素 4 // 以此类推,这样就能保证随机选择到相同的元素时也能等概率地返回不同的元素 Random random = new Random(); public Solution(int m, int n) { this.m = m; this.n = n; this.len = m * n; this.deletedToExist = new HashMap<>(); } public int[] flip() { int rand = random.nextInt(len); // 这个随机数可能已经被删掉了(刚才已经被选过) int res = rand; if (deletedToExist.containsKey(rand)) { res = deletedToExist.get(rand); } // 把 rand 置换到数组尾部 int last = len - 1; // 尾部的那个元素也可能已经被删掉了 if (deletedToExist.containsKey(last)) { last = deletedToExist.get(last); } deletedToExist.put(rand, last); // 把尾部的这个元素删掉 len--; // 一维坐标转化成二维坐标 return new int[]{res / n, res % n}; } public void reset() { this.len = this.m * this.n; this.deletedToExist.clear(); } } ``` ```javascript // by chatGPT (javascript) /** * @param {number} m * @param {number} n */ var Solution = function(m, n) { this.m = m; this.n = n; //抽象一维数组的长度 this.len = m * n; //已删除元素到尾部未删除元素的映射 this.deletedToExist = new Map(); //这个deletedToExist存的到底是什么呢?举个例子你就明白了: //假设数组[1,2,3,4,5],我第一次随机选择了元素2, //然后我就会在deletedToExist记录一个映射2->5,并删除最后的元素5 //现在数组是[1,2,3,4],我第二次随机选择又选到了2, //但不能重复选择同一个元素,所以我选择5,因为deletedToExist[2]=5 //然后我再更新deletedToExist,记录2->4并删除最后的元素4 //以此类推,这样就能保证随机选择到相同的元素时也能等概率地返回不同的元素 this.random = Math.random; }; /** * @return {number[]} */ Solution.prototype.flip = function() { let rand = Math.floor(this.random() * this.len); //这个随机数可能已经被删掉了(刚才已经被选过) let res = rand; if (this.deletedToExist.has(rand)) { res = this.deletedToExist.get(rand); } //把rand置换到数组尾部 let last = this.len - 1; //尾部的那个元素也可能已经被删掉了 if (this.deletedToExist.has(last)) { last = this.deletedToExist.get(last); } this.deletedToExist.set(rand, last); //把尾部的这个元素删掉 this.len--; //一维坐标转化成二维坐标 return [Math.floor(res / this.n), res % this.n]; }; Solution.prototype.reset = function() { this.len = this.m * this.n; this.deletedToExist.clear(); }; ``` ```python # by chatGPT (python) class Solution: def __init__(self, m: int, n: int): self.m = m # 矩阵的行数 self.n = n # 矩阵的列数 self.len = m * n # 抽象一维数组的长度 # 已删除元素到尾部未删除元素的映射 self.deletedToExist = {} def flip(self) -> List[int]: rand = random.randint(0, self.len - 1) # 随机选择一个元素 res = rand # 这个随机数可能已经被删掉了(刚才已经被选过) if rand in self.deletedToExist: res = self.deletedToExist[rand] # 把随机选择的元素置换到数组尾部 last = self.len - 1 # 尾部的那个元素也可能已经被删掉了 if last in self.deletedToExist: last = self.deletedToExist[last] self.deletedToExist[rand] = last # 把尾部的元素标记为已删除 self.len -= 1 # 返回一维数组中索引为 res 的元素的二维坐标 return [res // self.n, res % self.n] def reset(self) -> None: self.len = self.m * self.n # 重置抽象一维数组的长度 self.deletedToExist.clear() # 清空已删除元素到尾部未删除元素的映射 ``` https://leetcode.cn/problems/random-flip-matrix 的多语言解法👆 https://leetcode.cn/problems/random-pick-index 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { private: vector nums; public: Solution(vector& nums) { this->nums = nums; } int pick(int target) { int count = 0, res = -1; for (int i = 0; i < nums.size(); i++) { if (nums[i] != target) { continue; } count++; if (rand() % count == 0) { res = i; } } return res; } }; ``` ```go // by chatGPT (go) // Constructor function that receives a nums array and returns a solution struct func Constructor(nums []int) Solution { randSource := rand.NewSource(time.Now().UnixNano()) randGen := rand.New(randSource) return Solution{nums, randGen} } // Pick function receives a target value and returns the index of the element in the nums array func Pick(s *Solution, target int) int { count := 0 res := -1 for i := 0; i < len(s.nums); i++ { if s.nums[i] != target { continue } count++ if s.rand.Intn(count) == 0 { res = i } } return res } ``` ```java // by labuladong (java) class Solution { int[] nums; Random rand; public Solution(int[] nums) { this.nums = nums; this.rand = new Random(); } public int pick(int target) { int count = 0, res = -1; for (int i = 0; i < nums.length; i++) { if (nums[i] != target) { continue; } count++; if (rand.nextInt(count) == 0) { res = i; } } return res; } } ``` ```javascript // by chatGPT (javascript) /** * @param {number[]} nums */ var Solution = function(nums) { this.nums = nums; this.rand = new Random(); }; /** * @param {number} target * @return {number} */ Solution.prototype.pick = function(target) { let count = 0; let res = -1; for (let i = 0; i < this.nums.length; i++) { if (this.nums[i] !== target) { continue; } count++; if (this.rand.nextInt(count) === 0) { res = i; } } return res; }; ``` ```python # by chatGPT (python) import random class Solution: def __init__(self, nums: List[int]): self.nums = nums self.rand = random.Random() def pick(self, target: int) -> int: count, res = 0, -1 for i in range(len(self.nums)): if self.nums[i] != target: continue count += 1 if self.rand.randint(1, count) == 1: res = i return res ``` https://leetcode.cn/problems/random-pick-index 的多语言解法👆 https://leetcode.cn/problems/random-pick-with-blacklist 的多语言解法👇 ```cpp // by labuladong (cpp) class Solution { public: int sz; unordered_map mapping; Solution(int N, vector& blacklist) { sz = N - blacklist.size(); for (int b : blacklist) { mapping[b] = 666; } int last = N - 1; for (int b : blacklist) { // 如果 b 已经在区间 [sz, N) // 可以直接忽略 if (b >= sz) { continue; } while (mapping.count(last)) { last--; } mapping[b] = last; last--; } } int pick() { // 随机选取一个索引 int index = rand() % sz; // 这个索引命中了黑名单, // 需要被映射到其他位置 if (mapping.count(index)) { return mapping[index]; } // 若没命中黑名单,则直接返回 return index; } }; ``` ```go // by chatGPT (go) type Solution struct { sz int mapping map[int]int } // 构造函数,初始化黑名单映射表 // N: [0, N) 中的数字 // blacklist: 黑名单中的数字 func Constructor(N int, blacklist []int) Solution { s := Solution{} s.sz = N - len(blacklist) s.mapping = make(map[int]int) for _, b := range blacklist { s.mapping[b] = 666 } last := N - 1 for _, b := range blacklist { // 已经在区间 [sz, N) 的数字可以直接忽略 if b >= s.sz { continue } // 找到可以映射到b的值 for _, ok := s.mapping[last]; ok; _, ok = s.mapping[last] { last-- } s.mapping[b] = last last-- } return s } // 从集合中随机选择一个不在黑名单中的数字 func (this *Solution) Pick() int { index := rand.Intn(this.sz) if val, ok := this.mapping[index]; ok { return val } return index } ``` ```java // by chatGPT (java) class Solution { int sz; Map mapping; public Solution(int N, int[] blacklist) { sz = N - blacklist.length; mapping = new HashMap<>(); for (int b : blacklist) { mapping.put(b, 666); // 标记黑名单 } int last = N - 1; for (int b : blacklist) { // 如果 b 已经在区间 [sz, N),可以直接忽略 if (b >= sz) { continue; } while (mapping.containsKey(last)) { // 找到可以映射的位置 last--; } mapping.put(b, last); // 映射 last--; } } public int pick() { // 随机选取一个索引 int index = (int)(Math.random() * sz); // 这个索引命中了黑名单,需要被映射到其他位置 if (mapping.containsKey(index)) { return mapping.get(index); } // 若没命中黑名单,则直接返回 return index; } } ``` ```javascript // by chatGPT (javascript) /** * @param {number} N * @param {number[]} blacklist */ var Solution = function(N, blacklist) { // 求出白名单的长度 this.sz = N - blacklist.length; // 创建一个字典,用于快速查找黑名单中的元素 this.mapping = {}; // 将所有黑名单中的元素标记为 666 blacklist.forEach((b) => { this.mapping[b] = 666; }); // 遍历黑名单中的元素,为之给定一个新的位置 let last = N - 1; // 注意遍历的顺序,需要将最后的黑名单尽量映射到白名单前面的位置 blacklist.forEach((b) => { // 如果该黑名单元素已经在白名单的范围内了,则不需要进行映射 if (b >= this.sz) { return; } // 找到还未被映射的最大的位置 while (this.mapping.hasOwnProperty(last)) { last--; } // 为当前黑名单元素 b 指定一个新的位置 this.mapping[b] = last; last--; }); }; /** * @return {number} */ Solution.prototype.pick = function() { // 随机选取一个索引值 let index = Math.floor(Math.random() * this.sz); // 如果该索引位是黑名单中的元素,则将其映射到新的位置上 if (this.mapping.hasOwnProperty(index)) { return this.mapping[index]; } // 否则直接返回该索引值 return index; }; ``` ```python # by chatGPT (python) class Solution: def __init__(self, N: int, blacklist: List[int]): # 需要返回的数字范围为[0, sz) self.sz = N - len(blacklist) # 将黑名单中的数字映射到666,即避免选中 self.mapping = {} for b in blacklist: self.mapping[b] = 666 # 对于黑名单中的数字,如果其在[0, sz)范围内, # 且不在mapping中,对其进行重新映射 last = N - 1 for b in blacklist: if b >= self.sz: continue # 在映射中查找不在黑名单中,且小于N的最大的数last while last in self.mapping: last -= 1 # 将b映射到last self.mapping[b] = last last -= 1 def pick(self) -> int: # 随机选择一个数字 index = random.randint(0, self.sz-1) # 如果所选数字在映射中,返回其映射值 if index in self.mapping: return self.mapping[index] # 如果所选数字不在映射中,直接返回 return index ``` https://leetcode.cn/problems/random-pick-with-blacklist 的多语言解法👆 https://leetcode.cn/problems/range-addition 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: vector getModifiedArray(int length, vector>& updates) { // nums 初始化为全 0 vector nums(length, 0); // 构造差分解法 Difference df(nums); for (auto update : updates) { int i = update[0]; int j = update[1]; int val = update[2]; df.increment(i, j, val); } return df.result(); } class Difference { private: // 差分数组 vector diff; public: Difference(vector& nums) { assert(nums.size() > 0); diff.resize(nums.size()); // 构造差分数组 diff[0] = nums[0]; for (int i = 1; i < nums.size(); i++) { diff[i] = nums[i] - nums[i - 1]; } } /* 给闭区间 [i, j] 增加 val(可以是负数)*/ void increment(int i, int j, int val) { diff[i] += val; if (j + 1 < diff.size()) { diff[j + 1] -= val; } } vector result() { vector res(diff.size()); // 根据差分数组构造结果数组 res[0] = diff[0]; for (int i = 1; i < diff.size(); i++) { res[i] = res[i - 1] + diff[i]; } return res; } }; }; ``` ```go // by chatGPT (go) func getModifiedArray(length int, updates [][]int) []int { // nums 初始化为全 0 var nums = make([]int, length) // 构造差分解法 df := Difference{nums} for _, update := range updates { i := update[0] j := update[1] val := update[2] df.increment(i, j, val) } return df.result() } type Difference struct { // 差分数组 diff []int } func (df *Difference) increment(i, j, val int) { df.diff[i] += val if j+1 < len(df.diff) { df.diff[j+1] -= val } } func (df *Difference) result() []int { res := make([]int, len(df.diff)) // 根据差分数组构造结果数组 res[0] = df.diff[0] for i := 1; i < len(df.diff); i++ { res[i] = res[i-1] + df.diff[i] } return res } ``` ```java // by labuladong (java) class Solution { public int[] getModifiedArray(int length, int[][] updates) { // nums 初始化为全 0 int[] nums = new int[length]; // 构造差分解法 Difference df = new Difference(nums); for (int[] update : updates) { int i = update[0]; int j = update[1]; int val = update[2]; df.increment(i, j, val); } return df.result(); } 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; } } } ``` ```javascript // by chatGPT (javascript) var getModifiedArray = function(length, updates) { // nums 初始化为全 0 var nums = new Array(length); nums.fill(0); // 构造差分解法 var df = new Difference(nums); for (var k = 0; k < updates.length; k++) { var i = updates[k][0]; var j = updates[k][1]; var val = updates[k][2]; df.increment(i, j, val); } return df.result(); }; var Difference = function(nums) { // 差分数组 var diff = []; if (nums.length > 0) { // 构造差分数组 diff[0] = nums[0]; for (var i = 1; i < nums.length; i++) { diff[i] = nums[i] - nums[i - 1]; } } /* 给闭区间 [i, j] 增加 val(可以是负数)*/ this.increment = function(i, j, val) { diff[i] += val; if (j + 1 < diff.length) { diff[j + 1] -= val; } } this.result = function() { var res = new Array(diff.length); // 根据差分数组构造结果数组 res[0] = diff[0]; for (var i = 1; i < diff.length; i++) { res[i] = res[i - 1] + diff[i]; } return res; } }; ``` ```python # by chatGPT (python) class Solution: def getModifiedArray(self, length: int, updates: List[List[int]]) -> List[int]: # nums 初始化为全 0 nums = [0]*length # 构造差分解法 df = Difference(nums) for update in updates: i = update[0] j = update[1] val = update[2] df.increment(i, j, val) return df.result() class Difference: # 差分数组 def __init__(self, nums: List[int]): assert len(nums) > 0 self.diff = [0]*len(nums) # 构造差分数组 self.diff[0] = nums[0] for i in range(1, len(nums)): self.diff[i] = nums[i] - nums[i - 1] # 给闭区间 [i, j] 增加 val(可以是负数) def increment(self, i: int, j: int, val: int) -> None: self.diff[i] += val if j + 1 < len(self.diff): self.diff[j + 1] -= val def result(self) -> List[int]: res = [0]*len(self.diff) # 根据差分数组构造结果数组 res[0] = self.diff[0] for i in range(1, len(self.diff)): res[i] = res[i - 1] + self.diff[i] return res ``` https://leetcode.cn/problems/range-addition 的多语言解法👆 https://leetcode.cn/problems/range-sum-query-2d-immutable 的多语言解法👇 ```cpp // by chatGPT (cpp) class NumMatrix { private: // preSum[i][j] 记录矩阵 [0, 0, i, j] 的元素和 vector> preSum; public: NumMatrix(vector>& matrix) { int m = matrix.size(), n = matrix[0].size(); if (m == 0 || n == 0) return; // 构造前缀和矩阵 preSum = vector>(m + 1, vector(n + 1, 0)); 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] 的元素和 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]; } }; ``` ```go // by chatGPT (go) type NumMatrix struct { // preSum[i][j] 记录矩阵 [0, 0, i, j] 的元素和 preSum [][]int } func Constructor(matrix [][]int) NumMatrix { m, n := len(matrix), len(matrix[0]) if m == 0 || n == 0 { return NumMatrix{} } // 构造前缀和矩阵 preSum := make([][]int, m+1) for i := 0; i <= m; i++ { preSum[i] = make([]int, n+1) } for i := 1; i <= m; i++ { for 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] } } return NumMatrix{preSum: preSum} } func (this *NumMatrix) SumRegion(x1 int, y1 int, x2 int, y2 int) int { // 目标矩阵之和由四个相邻矩阵运算获得 return this.preSum[x2+1][y2+1] - this.preSum[x1][y2+1] - this.preSum[x2+1][y1] + this.preSum[x1][y1] } ``` ```java // by labuladong (java) 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]; } } ``` ```javascript // by chatGPT (javascript) /** * @param {number[][]} matrix 矩阵 */ var NumMatrix = function(matrix) { const m = matrix.length, n = matrix[0].length; if (m == 0 || n == 0) return; // preSum[i][j] 记录矩阵 [0, 0, i, j] 的元素和 this.preSum = new Array(m + 1).fill().map(() => new Array(n + 1).fill(0)); for (let i = 1; i <= m; i++) { for (let j = 1; j <= n; j++) { // 计算每个矩阵 [0, 0, i, j] 的元素和 this.preSum[i][j] = this.preSum[i - 1][j] + this.preSum[i][j - 1] + matrix[i - 1][j - 1] - this.preSum[i - 1][j - 1]; } } }; /** * @param {number} x1 子矩阵左上角坐标x * @param {number} y1 子矩阵左上角坐标y * @param {number} x2 子矩阵右下角坐标x * @param {number} y2 子矩阵右下角坐标y * @return {number} 子矩阵元素和 */ NumMatrix.prototype.sumRegion = function(x1, y1, x2, y2) { // 目标矩阵之和由四个相邻矩阵运算获得 return this.preSum[x2 + 1][y2 + 1] - this.preSum[x1][y2 + 1] - this.preSum[x2 + 1][y1] + this.preSum[x1][y1]; }; ``` ```python # by chatGPT (python) class NumMatrix: # preSum[i][j] 记录矩阵 [0, 0, i, j] 的元素和 def __init__(self, matrix: List[List[int]]): m, n = len(matrix), len(matrix[0]) if m == 0 or n == 0: return # 构造前缀和矩阵 self.preSum = [[0] * (n + 1) for _ in range(m + 1)] for i in range(1, m + 1): for j in range(1, n + 1): # 计算每个矩阵 [0, 0, i, j] 的元素和 self.preSum[i][j] = self.preSum[i - 1][j] + self.preSum[i][j - 1] + matrix[i - 1][j - 1] - self.preSum[i - 1][j - 1] # 计算子矩阵 [x1, y1, x2, y2] 的元素和 def sumRegion(self, x1: int, y1: int, x2: int, y2: int) -> int: # 目标矩阵之和由四个相邻矩阵运算获得 return self.preSum[x2 + 1][y2 + 1] - self.preSum[x1][y2 + 1] - self.preSum[x2 + 1][y1] + self.preSum[x1][y1] ``` https://leetcode.cn/problems/range-sum-query-2d-immutable 的多语言解法👆 https://leetcode.cn/problems/range-sum-query-immutable 的多语言解法👇 ```cpp // by chatGPT (cpp) class NumArray { // 前缀和数组 private: vector preSum; public: /* 输入一个数组,构造前缀和 */ NumArray(vector& nums) { // preSum[0] = 0,便于计算累加和 preSum.resize(nums.size() + 1); // 计算 nums 的累加和 for (int i = 1; i < preSum.size(); i++) { preSum[i] = preSum[i - 1] + nums[i - 1]; } } /* 查询闭区间 [left, right] 的累加和 */ int sumRange(int left, int right) { return preSum[right + 1] - preSum[left]; } }; ``` ```go // by chatGPT (go) type NumArray struct { preSum []int } /* 输入一个数组,构造前缀和 */ func Constructor(nums []int) NumArray { // preSum[0] = 0,便于计算累加和 preSum := make([]int, len(nums)+1) // 计算 nums 的累加和 for i := 1; i < len(preSum); i++ { preSum[i] = preSum[i-1] + nums[i-1] } return NumArray{preSum} } /* 查询闭区间 [left, right] 的累加和 */ func (this *NumArray) SumRange(left int, right int) int { return this.preSum[right+1] - this.preSum[left] } ``` ```java // by labuladong (java) class NumArray { // 前缀和数组 private 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]; } } ``` ```javascript // by chatGPT (javascript) /** * @param {number[]} nums */ var NumArray = function(nums) { // 前缀和数组 this.preSum = []; // preSum[0] = 0,便于计算累加和 this.preSum[0] = 0; // 计算 nums 的累加和 for (let i = 1; i <= nums.length; i++) { this.preSum[i] = this.preSum[i - 1] + nums[i - 1]; } }; /** * @param {number} left * @param {number} right * @return {number} */ NumArray.prototype.sumRange = function(left, right) { return this.preSum[right + 1] - this.preSum[left]; }; ``` ```python # by chatGPT (python) class NumArray: # 前缀和数组 def __init__(self, nums: List[int]): self.preSum = [0] * (len(nums) + 1) for i in range(1, len(self.preSum)): self.preSum[i] = self.preSum[i - 1] + nums[i - 1] # 查询闭区间 [left, right] 的累加和 def sumRange(self, left: int, right: int) -> int: return self.preSum[right + 1] - self.preSum[left] ``` https://leetcode.cn/problems/range-sum-query-immutable 的多语言解法👆 https://leetcode.cn/problems/regular-expression-matching 的多语言解法👇 ```cpp // by labuladong (cpp) class Solution { public: // 备忘录 vector> memo; bool isMatch(string s, string p) { int m = s.size(), n = p.size(); memo = vector>(m, vector(n, -1)); // 指针 i,j 从索引 0 开始移动 return dp(s, 0, p, 0); } /* 计算 p[j..] 是否匹配 s[i..] */ bool dp(string& s, int i, string& p, int j) { int m = s.size(), n = p.size(); // base case if (j == n) { return i == m; } if (i == m) { if ((n - j) % 2 == 1) { return false; } for (; j + 1 < n; j += 2) { if (p[j + 1] != '*') { return false; } } return true; } // 查备忘录,防止重复计算 if (memo[i][j] != -1) { return memo[i][j]; } bool res = false; if (s[i] == p[j] || p[j] == '.') { if (j < n - 1 && p[j + 1] == '*') { res = dp(s, i, p, j + 2) || dp(s, i + 1, p, j); } else { res = dp(s, i + 1, p, j + 1); } } else { if (j < n - 1 && p[j + 1] == '*') { res = dp(s, i, p, j + 2); } else { res = false; } } // 将当前结果记入备忘录 memo[i][j] = res; return res; } }; ``` ```go // by chatGPT (go) func isMatch(s string, p string) bool { // 备忘录 memo := make([][]int, len(s)) for i := range memo { memo[i] = make([]int, len(p)) for j := range memo[i] { memo[i][j] = -1 } } // 指针 i,j 从索引 0 开始移动 return dp(s, 0, p, 0, memo) } /* 计算 p[j..] 是否匹配 s[i..] */ func dp(s string, i int, p string, j int, memo [][]int) bool { m, n := len(s), len(p) // base case if j == n { return i == m } if i == m { if (n-j)%2 == 1 { return false } for ; j+1 < n; j += 2 { if p[j+1] != '*' { return false } } return true } // 查备忘录,防止重复计算 if memo[i][j] != -1 { return memo[i][j] == 1 } var res bool if s[i] == p[j] || p[j] == '.' { if j < n-1 && p[j+1] == '*' { res = dp(s, i, p, j+2, memo) || dp(s, i+1, p, j, memo) } else { res = dp(s, i+1, p, j+1, memo) } } else { if j < n-1 && p[j+1] == '*' { res = dp(s, i, p, j+2, memo) } else { res = false } } // 将当前结果记入备忘录 memo[i][j] = 0 if res { memo[i][j] = 1 } return res } ``` ```java // by chatGPT (java) class Solution { // 备忘录 int[][] memo; public boolean isMatch(String s, String p) { int m = s.length(), n = p.length(); memo = new int[m][n]; for (int i = 0; i < m; i++) { Arrays.fill(memo[i], -1); } // 指针 i,j 从索引 0 开始移动 return dp(s, 0, p, 0); } /* 计算 p[j..] 是否匹配 s[i..] */ private boolean dp(String s, int i, String p, int j) { int m = s.length(), n = p.length(); // base case if (j == n) { return i == m; } if (i == m) { if ((n - j) % 2 == 1) { return false; } for (; j + 1 < n; j += 2) { if (p.charAt(j + 1) != '*') { return false; } } return true; } // 查备忘录,防止重复计算 if (memo[i][j] != -1) { return memo[i][j] == 1; } boolean res = false; if (s.charAt(i) == p.charAt(j) || p.charAt(j) == '.') { if (j < n - 1 && p.charAt(j + 1) == '*') { res = dp(s, i, p, j + 2) || dp(s, i + 1, p, j); } else { res = dp(s, i + 1, p, j + 1); } } else { if (j < n - 1 && p.charAt(j + 1) == '*') { res = dp(s, i, p, j + 2); } else { res = false; } } // 将当前结果记入备忘录 memo[i][j] = res ? 1 : 0; return res; } } ``` ```javascript // by chatGPT (javascript) var isMatch = function(s, p) { let m = s.length, n = p.length; // 备忘录 let memo = new Array(m + 1); for (let i = 0; i < memo.length; i++) { memo[i] = new Array(n + 1).fill(-1); } return dp(0, 0); /* 计算 p[j..] 是否匹配 s[i..] */ function dp(i, j) { // 查备忘录,防止重复计算 if (memo[i][j] !== -1) { return memo[i][j]; } let res = false; // base case if (j === n) { res = i === m; } else { let firstMatch = i < m && (p[j] === s[i] || p[j] === '.'); if (j + 1 < n && p[j + 1] === '*') { res = dp(i, j + 2) || (firstMatch && dp(i + 1, j)); } else { res = firstMatch && dp(i + 1, j + 1); } } // 将当前结果记入备忘录 memo[i][j] = res; return res; } }; ``` ```python # by chatGPT (python) class Solution: # 备忘录 memo = [] def isMatch(self, s: str, p: str) -> bool: m, n = len(s), len(p) self.memo = [[-1] * n for _ in range(m)] # 指针 i,j 从索引 0 开始移动 return self.dp(s, 0, p, 0) # 计算 p[j..] 是否匹配 s[i..] def dp(self, s: str, i: int, p: str, j: int) -> bool: m, n = len(s), len(p) # base case if j == n: return i == m if i == m: if (n - j) % 2 == 1: return False for k in range(j + 1, n, 2): if p[k] != '*': return False return True # 查备忘录,防止重复计算 if self.memo[i][j] != -1: return self.memo[i][j] res = False if s[i] == p[j] or p[j] == '.': if j < n - 1 and p[j + 1] == '*': res = self.dp(s, i, p, j + 2) or self.dp(s, i + 1, p, j) else: res = self.dp(s, i + 1, p, j + 1) else: if j < n - 1 and p[j + 1] == '*': res = self.dp(s, i, p, j + 2) else: res = False # 将当前结果记入备忘录 self.memo[i][j] = res return res ``` https://leetcode.cn/problems/regular-expression-matching 的多语言解法👆 https://leetcode.cn/problems/remove-covered-intervals 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int removeCoveredIntervals(vector>& intervals) { // 按照起点升序排列,起点相同时降序排列 sort(intervals.begin(), intervals.end(), [](vector& a, vector& b) { if (a[0] == b[0]) { return b[1] < a[1]; } return a[0] < b[0]; }); // 记录合并区间的起点和终点 int left = intervals[0][0]; int right = intervals[0][1]; int res = 0; for (int i = 1; i < intervals.size(); i++) { vector& intv = intervals[i]; // 情况一,找到覆盖区间 if (left <= intv[0] && right >= intv[1]) { res++; } // 情况二,找到相交区间,合并 if (right >= intv[0] && right <= intv[1]) { right = intv[1]; } // 情况三,完全不相交,更新起点和终点 if (right < intv[0]) { left = intv[0]; right = intv[1]; } } return intervals.size() - res; } }; ``` ```go // by chatGPT (go) import "sort" func removeCoveredIntervals(intervals [][]int) int { // 按照起点升序排列,起点相同时降序排列 sort.Slice(intervals, func(i, j int) bool { if intervals[i][0] == intervals[j][0] { return intervals[j][1] < intervals[i][1] } return intervals[i][0] < intervals[j][0] }) // 记录合并区间的起点和终点 left, right := intervals[0][0], intervals[0][1] res := 0 for i := 1; i < len(intervals); i++ { intv := intervals[i] // 情况一,找到覆盖区间 if left <= intv[0] && right >= intv[1] { res++ } // 情况二,找到相交区间,合并 if right >= intv[0] && right <= intv[1] { right = intv[1] } // 情况三,完全不相交,更新起点和终点 if right < intv[0] { left, right = intv[0], intv[1] } } return len(intervals) - res } ``` ```java // by labuladong (java) class Solution { public int removeCoveredIntervals(int[][] intervals) { // 按照起点升序排列,起点相同时降序排列 Arrays.sort(intervals, (a, b) -> { if (a[0] == b[0]) { return b[1] - a[1]; } return a[0] - b[0]; }); // 记录合并区间的起点和终点 int left = intervals[0][0]; int right = intervals[0][1]; int res = 0; for (int i = 1; i < intervals.length; i++) { int[] intv = intervals[i]; // 情况一,找到覆盖区间 if (left <= intv[0] && right >= intv[1]) { res++; } // 情况二,找到相交区间,合并 if (right >= intv[0] && right <= intv[1]) { right = intv[1]; } // 情况三,完全不相交,更新起点和终点 if (right < intv[0]) { left = intv[0]; right = intv[1]; } } return intervals.length - res; } } ``` ```javascript // by chatGPT (javascript) /** * @param {number[][]} intervals * @return {number} */ var removeCoveredIntervals = function(intervals) { // 按照起点升序排列,起点相同时降序排列 intervals.sort((a, b) => { if (a[0] == b[0]) { return b[1] - a[1]; } return a[0] - b[0]; }); // 记录合并区间的起点和终点 let left = intervals[0][0]; let right = intervals[0][1]; let res = 0; for (let i = 1; i < intervals.length; i++) { const intv = intervals[i]; // 情况一,找到覆盖区间 if (left <= intv[0] && right >= intv[1]) { res++; } // 情况二,找到相交区间,合并 if (right >= intv[0] && right <= intv[1]) { right = intv[1]; } // 情况三,完全不相交,更新起点和终点 if (right < intv[0]) { left = intv[0]; right = intv[1]; } } return intervals.length - res; }; ``` ```python # by chatGPT (python) class Solution: def removeCoveredIntervals(self, intervals: List[List[int]]) -> int: """ :type intervals: List[List[int]] :rtype: int """ # 按照起点升序排列,起点相同时降序排列 intervals.sort(key=lambda a: (a[0], -a[1])) # 记录合并区间的起点和终点 left, right = intervals[0][0], intervals[0][1] res = 0 for i in range(1, len(intervals)): intv = intervals[i] # 情况一,找到覆盖区间 if left <= intv[0] and right >= intv[1]: res += 1 # 情况二,找到相交区间,合并 if right >= intv[0] and right <= intv[1]: right = intv[1] # 情况三,完全不相交,更新起点和终点 if right < intv[0]: left, right = intv[0], intv[1] return len(intervals) - res ``` https://leetcode.cn/problems/remove-covered-intervals 的多语言解法👆 https://leetcode.cn/problems/remove-duplicate-letters 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: string removeDuplicateLetters(string s) { stack stk; // 维护一个计数器记录字符串中字符的数量 // 因为输入为 ASCII 字符,大小 256 够用了 int count[256] = {0}; for (int i = 0; i < s.length(); i++) { count[s[i]]++; } bool inStack[256] = {false}; for (char c : s) { // 每遍历过一个字符,都将对应的计数减一 count[c]--; if (inStack[c]) continue; while (!stk.empty() && stk.top() > c) { // 若之后不存在栈顶元素了,则停止 pop if (count[stk.top()] == 0) { break; } // 若之后还有,则可以 pop inStack[stk.top()] = false; stk.pop(); } stk.push(c); inStack[c] = true; } string res; while (!stk.empty()) { res.push_back(stk.top()); stk.pop(); } reverse(res.begin(), res.end()); return res; } }; ``` ```go // by chatGPT (go) func removeDuplicateLetters(s string) string { stk := []rune{} // 维护一个计数器记录字符串中字符的数量 // 因为输入为 ASCII 字符,大小 256 够用了 count := [256]int{} for _, c := range s { count[c]++ } inStack := [256]bool{} for _, c := range s { // 每遍历过一个字符,都将对应的计数减一 count[c]-- if inStack[c] { continue } for len(stk) > 0 && stk[len(stk)-1] > c { // 若之后不存在栈顶元素了,则停止 pop if count[stk[len(stk)-1]] == 0 { break } // 若之后还有,则可以 pop inStack[stk[len(stk)-1]] = false stk = stk[:len(stk)-1] } stk = append(stk, c) inStack[c] = true } sb := strings.Builder{} for i := len(stk) - 1; i >= 0; i-- { sb.WriteRune(stk[i]) } return sb.String() } ``` ```java // by labuladong (java) class Solution { public String removeDuplicateLetters(String s) { Stack stk = new Stack<>(); // 维护一个计数器记录字符串中字符的数量 // 因为输入为 ASCII 字符,大小 256 够用了 int[] count = new int[256]; for (int i = 0; i < s.length(); i++) { count[s.charAt(i)]++; } boolean[] inStack = new boolean[256]; for (char c : s.toCharArray()) { // 每遍历过一个字符,都将对应的计数减一 count[c]--; if (inStack[c]) continue; while (!stk.isEmpty() && stk.peek() > c) { // 若之后不存在栈顶元素了,则停止 pop if (count[stk.peek()] == 0) { break; } // 若之后还有,则可以 pop inStack[stk.pop()] = false; } stk.push(c); inStack[c] = true; } StringBuilder sb = new StringBuilder(); while (!stk.empty()) { sb.append(stk.pop()); } return sb.reverse().toString(); } } ``` ```javascript // by chatGPT (javascript) /** * @param {string} s * @return {string} */ var removeDuplicateLetters = function(s) { let stk = []; // 维护一个计数器记录字符串中字符的数量 // 因为输入为 ASCII 字符,大小 256 够用了 let count = Array(256).fill(0); for (let i = 0; i < s.length; i++) { count[s.charCodeAt(i)]++; } let inStack = Array(256).fill(false); for (let i = 0; i < s.length; i++) { let c = s.charAt(i); // 每遍历过一个字符,都将对应的计数减一 count[c.charCodeAt(0)]--; if (inStack[c.charCodeAt(0)]) continue; while (stk.length > 0 && stk[stk.length - 1] > c) { // 若之后不存在栈顶元素了,则停止 pop if (count[stk[stk.length - 1].charCodeAt(0)] == 0) { break; } // 若之后还有,则可以 pop inStack[stk.pop().charCodeAt(0)] = false; } stk.push(c); inStack[c.charCodeAt(0)] = true; } let sb = ""; while (stk.length > 0) { sb += stk.pop(); } return sb.split("").reverse().join(""); }; ``` ```python # by chatGPT (python) class Solution: def removeDuplicateLetters(self, s: str) -> str: stk = [] # 维护一个计数器记录字符串中字符的数量 # 因为输入为 ASCII 字符,大小 256 够用了 count = [0] * 256 for i in range(len(s)): count[ord(s[i])] += 1 inStack = [False] * 256 for c in s: # 每遍历过一个字符,都将对应的计数减一 count[ord(c)] -= 1 if inStack[ord(c)]: continue while len(stk) > 0 and stk[-1] > c: # 若之后不存在栈顶元素了,则停止 pop if count[ord(stk[-1])] == 0: break # 若之后还有,则可以 pop inStack[ord(stk.pop())] = False stk.append(c) inStack[ord(c)] = True sb = [] while len(stk) > 0: sb.append(stk.pop()) return "".join(sb)[::-1] ``` https://leetcode.cn/problems/remove-duplicate-letters 的多语言解法👆 https://leetcode.cn/problems/remove-duplicates-from-sorted-array 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int removeDuplicates(vector& nums) { if (nums.size() == 0) { return 0; } int slow = 0, fast = 0; while (fast < nums.size()) { if (nums[fast] != nums[slow]) { slow++; // 维护 nums[0..slow] 无重复 nums[slow] = nums[fast]; } fast++; } // 数组长度为索引 + 1 return slow + 1; } }; ``` ```go // by chatGPT (go) func removeDuplicates(nums []int) int { // 如果数组为空,直接返回 0 if len(nums) == 0 { return 0 } // 定义快慢指针,初始化都指向数组头部 slow, fast := 0, 0 // 快指针向后遍历数组,直到末尾 for fast < len(nums) { // 如果两个指针指向的元素不相同 if nums[fast] != nums[slow] { // 慢指针向后移动,并且将慢指针位置上的值设为快指针位置上的值 slow++ nums[slow] = nums[fast] } // 快指针继续向后移动 fast++ } // slow 指向数组的最后一个不重复元素的位置 // 数组长度为索引 + 1 return slow + 1 } ``` ```java // by labuladong (java) class Solution { public int removeDuplicates(int[] nums) { if (nums.length == 0) { return 0; } int slow = 0, fast = 0; while (fast < nums.length) { if (nums[fast] != nums[slow]) { slow++; // 维护 nums[0..slow] 无重复 nums[slow] = nums[fast]; } fast++; } // 数组长度为索引 + 1 return slow + 1; } } ``` ```javascript // by chatGPT (javascript) /** * @param {number[]} nums * @return {number} */ var removeDuplicates = function(nums) { if (nums.length === 0) { return 0; } let slow = 0, fast = 0; while (fast < nums.length) { if (nums[fast] !== nums[slow]) { slow++; // 维护 nums[0..slow] 无重复 nums[slow] = nums[fast]; } fast++; } // 数组长度为索引 + 1 return slow + 1; }; ``` ```python # by chatGPT (python) class Solution: def removeDuplicates(self, nums: List[int]) -> int: if len(nums) == 0: return 0 slow, fast = 0, 0 while fast < len(nums): if nums[fast] != nums[slow]: slow += 1 # 维护 nums[0..slow] 无重复 nums[slow] = nums[fast] fast += 1 # 数组长度为索引 + 1 return slow + 1 ``` https://leetcode.cn/problems/remove-duplicates-from-sorted-array 的多语言解法👆 https://leetcode.cn/problems/remove-duplicates-from-sorted-array-ii 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int removeDuplicates(vector& nums) { if (nums.size() == 0) { return 0; } // 快慢指针,维护 nums[0..slow] 为结果子数组 int slow = 0, fast = 0; // 记录一个元素重复的次数 int count = 0; while (fast < nums.size()) { if (nums[fast] != nums[slow]) { slow++; nums[slow] = nums[fast]; } else if (slow < fast && count < 2) { // 当一个元素重复次数不到 2 次时,也 slow++; nums[slow] = nums[fast]; } fast++; count++; if (fast < nums.size() && nums[fast] != nums[fast - 1]) { // 遇到不同的元素 count = 0; } } // 数组长度为索引 + 1 return slow + 1; } }; ``` ```go // by chatGPT (go) func removeDuplicates(nums []int) int { if len(nums) == 0 { return 0 } // 快慢指针,维护 nums[0..slow] 为结果子数组 slow, fast := 0, 0 // 记录一个元素重复的次数 count := 0 for fast < len(nums) { if nums[fast] != nums[slow] { slow++ nums[slow] = nums[fast] } else if slow < fast && count < 2 { // 当一个元素重复次数不到 2 次时,也 slow++ nums[slow] = nums[fast] } fast++ count++ if fast < len(nums) && nums[fast] != nums[fast - 1] { // 遇到不同的元素 count = 0 } } // 数组长度为索引 + 1 return slow + 1 } ``` ```java // by labuladong (java) class Solution { public int removeDuplicates(int[] nums) { if (nums.length == 0) { return 0; } // 快慢指针,维护 nums[0..slow] 为结果子数组 int slow = 0, fast = 0; // 记录一个元素重复的次数 int count = 0; while (fast < nums.length) { if (nums[fast] != nums[slow]) { // 此时,对于 nums[0..slow] 来说,nums[fast] 是一个新的元素,加进来 slow++; nums[slow] = nums[fast]; } else if (slow < fast && count < 2) { // 此时,对于 nums[0..slow] 来说,nums[fast] 重复次数小于 2,也加进来 slow++; nums[slow] = nums[fast]; } fast++; count++; if (fast < nums.length && nums[fast] != nums[fast - 1]) { // fast 遇到新的不同的元素时,重置 count count = 0; } } // 数组长度为索引 + 1 return slow + 1; } } ``` ```javascript // by chatGPT (javascript) var removeDuplicates = function(nums) { if (nums.length == 0) { return 0; } // 快慢指针,维护 nums[0..slow] 为结果子数组 let slow = 0, fast = 0; // 记录一个元素重复的次数 let count = 0; while (fast < nums.length) { if (nums[fast] != nums[slow]) { slow++; nums[slow] = nums[fast]; } else if (slow < fast && count < 2) { // 当一个元素重复次数不到 2 次时,也 slow++; nums[slow] = nums[fast]; } fast++; count++; if (fast < nums.length && nums[fast] != nums[fast - 1]) { // 遇到不同的元素 count = 0; } } // 数组长度为索引 + 1 return slow + 1; }; ``` ```python # by chatGPT (python) class Solution: def removeDuplicates(self, nums: List[int]) -> int: if len(nums) == 0: return 0 # 快慢指针,维护 nums[0..slow] 为结果子数组 slow, fast = 0, 0 # 记录一个元素重复的次数 count = 0 while fast < len(nums): if nums[fast] != nums[slow]: slow += 1 nums[slow] = nums[fast] elif slow < fast and count < 2: # 当一个元素重复次数不到 2 次时,也 slow += 1 nums[slow] = nums[fast] fast += 1 count += 1 if fast < len(nums) and nums[fast] != nums[fast - 1]: # 遇到不同的元素 count = 0 # 数组长度为索引 + 1 return slow + 1 ``` https://leetcode.cn/problems/remove-duplicates-from-sorted-array-ii 的多语言解法👆 https://leetcode.cn/problems/remove-duplicates-from-sorted-list 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: ListNode* deleteDuplicates(ListNode* head) { // 删除链表中重复的元素 if (head == NULL) return NULL; // 如果链表为空,直接返回NULL ListNode *slow = head, *fast = head; // 定义快慢指针,初始都指向头结点 while (fast != NULL) { // 只要快指针没有遍历完整个链表 if (fast->val != slow->val) { // 快慢指针值不同 slow->next = fast; // 慢指针连接新节点 slow = slow->next; // 慢指针向后移动一位 } fast = fast->next; // 快指针向后移动一位 } slow->next = NULL; // 断开与后面重复元素的连接 return head; // 返回头结点 } }; ``` ```go // by chatGPT (go) func deleteDuplicates(head *ListNode) *ListNode { if head == nil { return nil } slow, fast := head, head for fast != nil { if fast.Val != slow.Val { // nums[slow] = nums[fast]; slow.Next = fast // slow++; slow = slow.Next } // fast++ fast = fast.Next } // 断开与后面重复元素的连接 slow.Next = nil return head } ``` ```java // by labuladong (java) class Solution { public deleteDuplicates(ListNode head) { if (head == null) return null; ListNode slow = head, fast = head; while (fast != null) { if (fast.val != slow.val) { // nums[slow] = nums[fast]; slow.next = fast; // slow++; slow = slow.next; } // fast++ fast = fast.next; } // 断开与后面重复元素的连接 slow.next = null; return head; } } ``` ```javascript // by chatGPT (javascript) /** * @param {ListNode} head * @return {ListNode} */ var deleteDuplicates = function(head) { if (head === null) return null; var slow = head; var fast = head; while (fast !== null) { if (fast.val !== slow.val) { // nums[slow] = nums[fast]; slow.next = fast; // slow++; slow = slow.next; } // fast++ fast = fast.next; } // 断开与后面重复元素的连接 slow.next = null; return head; }; ``` ```python # by chatGPT (python) class Solution: def deleteDuplicates(self, head: ListNode) -> ListNode: if head == None: return None slow = head fast = head while fast != None: if fast.val != slow.val: # nums[slow] = nums[fast]; slow.next = fast # slow++; slow = slow.next # fast++ fast = fast.next # 断开与后面重复元素的连接 slow.next = None return head ``` https://leetcode.cn/problems/remove-duplicates-from-sorted-list 的多语言解法👆 https://leetcode.cn/problems/remove-element 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int removeElement(vector& nums, int val) { int fast = 0, slow = 0; while (fast < nums.size()) { if (nums[fast] != val) { nums[slow] = nums[fast]; slow++; } fast++; } return slow; } }; ``` ```go // by chatGPT (go) // 双指针法 func removeElement(nums []int, val int) int { var fast, slow int for fast < len(nums) { if nums[fast] != val { nums[slow] = nums[fast] slow++ } fast++ } return slow } ``` ```java // by labuladong (java) class Solution { public int removeElement(int[] nums, int val) { int fast = 0, slow = 0; while (fast < nums.length) { if (nums[fast] != val) { nums[slow] = nums[fast]; slow++; } fast++; } return slow; } } ``` ```javascript // by chatGPT (javascript) /** * @param {number[]} nums * @param {number} val * @return {number} */ var removeElement = function(nums, val) { let fast = 0, slow = 0; while (fast < nums.length) { if (nums[fast] !== val) { nums[slow] = nums[fast]; slow++; } fast++; } return slow; }; ``` ```python # by chatGPT (python) class Solution: def removeElement(self, nums: List[int], val: int) -> int: fast, slow = 0, 0 while fast < len(nums): if nums[fast] != val: nums[slow] = nums[fast] slow += 1 fast += 1 return slow ``` https://leetcode.cn/problems/remove-element 的多语言解法👆 https://leetcode.cn/problems/remove-nth-node-from-end-of-list 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: ListNode* removeNthFromEnd(ListNode* head, int n) { // 虚拟头结点 ListNode* dummy = new ListNode(-1); dummy->next = head; // 删除倒数第 n 个,要先找倒数第 n + 1 个节点 ListNode* x = findFromEnd(dummy, n + 1); // 删掉倒数第 n 个节点 x->next = x->next->next; return dummy->next; } // 返回链表的倒数第 k 个节点 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 个节点 return p2; } }; ``` ```go // by chatGPT (go) // 主函数 func removeNthFromEnd(head *ListNode, n int) *ListNode { // 虚拟头结点 dummy := &ListNode{Val: -1, Next: head} // 删除倒数第 n 个,要先找倒数第 n + 1 个节点 x := findFromEnd(dummy, n + 1) // 删掉倒数第 n 个节点 x.Next = x.Next.Next return dummy.Next } // 返回链表的倒数第 k 个节点 func findFromEnd(head *ListNode, k int) *ListNode { p1, p2 := head, head // p1 先走 k 步 for i := 0; i < k; i++ { p1 = p1.Next } // p1 和 p2 同时走 n - k 步 for p1 != nil { p1 = p1.Next p2 = p2.Next } // p2 现在指向第 n - k 个节点 return p2 } ``` ```java // by labuladong (java) class Solution { // 主函数 public ListNode removeNthFromEnd(ListNode head, int n) { // 虚拟头结点 ListNode dummy = new ListNode(-1); dummy.next = head; // 删除倒数第 n 个,要先找倒数第 n + 1 个节点 ListNode x = findFromEnd(dummy, n + 1); // 删掉倒数第 n 个节点 x.next = x.next.next; return dummy.next; } // 返回链表的倒数第 k 个节点 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 个节点 return p2; } } ``` ```javascript // by chatGPT (javascript) var removeNthFromEnd = function(head, n) { // 虚拟头结点 let dummy = new ListNode(-1); dummy.next = head; // 删除倒数第 n 个,要先找倒数第 n + 1 个节点 let x = findFromEnd(dummy, n + 1); // 删掉倒数第 n 个节点 x.next = x.next.next; return dummy.next; }; // 返回链表的倒数第 k 个节点 var findFromEnd = function(head, k) { let p1 = head; // p1 先走 k 步 for (let i = 0; i < k; i++) { p1 = p1.next; } let p2 = head; // p1 和 p2 同时走 n - k 步 while (p1 != null) { p2 = p2.next; p1 = p1.next; } // p2 现在指向第 n - k 个节点 return p2; }; ``` ```python # by chatGPT (python) # Definition for singly-linked list. # class ListNode: # def __init__(self, val=0, next=None): # self.val = val # self.next = next class Solution: def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode: # 虚拟头结点 dummy = ListNode(-1) dummy.next = head # 删除倒数第 n 个,要先找倒数第 n + 1 个节点 x = self.findFromEnd(dummy, n + 1) # 删掉倒数第 n 个节点 x.next = x.next.next return dummy.next # 返回链表的倒数第 k 个节点 def findFromEnd(self, head: ListNode, k: int) -> ListNode: p1 = head # p1 先走 k 步 for i in range(k): p1 = p1.next p2 = head # p1 和 p2 同时走 n - k 步 while p1 != None: p1 = p1.next p2 = p2.next # p2 现在指向第 n - k 个节点 return p2 ``` https://leetcode.cn/problems/remove-nth-node-from-end-of-list 的多语言解法👆 https://leetcode.cn/problems/reshape-the-matrix 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: vector> matrixReshape(vector>& mat, int r, int c) { int m = mat.size(), n = mat[0].size(); // 如果想成功 reshape,元素个数应该相同 if (r * c != m * n) { return mat; } vector> res(r, vector(c, 0)); for (int i = 0; i < m * n; i++) { set(res, i, get(mat, i)); } return res; } // 通过一维坐标访问二维数组中的元素 int get(vector>& matrix, int index) { int m = matrix.size(), n = matrix[0].size(); // 计算二维中的横纵坐标 int i = index / n, j = index % n; return matrix[i][j]; } // 通过一维坐标设置二维数组中的元素 void set(vector>& matrix, int index, int value) { int m = matrix.size(), n = matrix[0].size(); // 计算二维中的横纵坐标 int i = index / n, j = index % n; matrix[i][j] = value; } }; ``` ```go // by chatGPT (go) func matrixReshape(mat [][]int, r int, c int) [][]int { m := len(mat) n := len(mat[0]) if r*c != m*n { return mat } res := make([][]int,r) for i := range res { res[i] = make([]int,c) } for i := 0; i < m * n; i++ { set(res, i, get(mat, i)) } return res } // get 通过一维坐标访问二维数组中的元素 func get(matrix [][]int, index int) int { n := len(matrix[0]) i := index / n j := index % n return matrix[i][j] } // set 通过一维坐标设置二维数组中的元素 func set(matrix [][]int, index int, value int) { n := len(matrix[0]) i := index / n j := index % n matrix[i][j] = value } ``` ```java // by labuladong (java) class Solution { public int[][] matrixReshape(int[][] mat, int r, int c) { int m = mat.length, n = mat[0].length; // 如果想成功 reshape,元素个数应该相同 if (r * c != m * n) { return mat; } int[][] res = new int[r][c]; for (int i = 0; i < m * n; i++) { set(res, i, get(mat, i)); } return res; } // 通过一维坐标访问二维数组中的元素 int get(int[][] matrix, int index) { int m = matrix.length, n = matrix[0].length; // 计算二维中的横纵坐标 int i = index / n, j = index % n; return matrix[i][j]; } // 通过一维坐标设置二维数组中的元素 void set(int[][] matrix, int index, int value) { int m = matrix.length, n = matrix[0].length; // 计算二维中的横纵坐标 int i = index / n, j = index % n; matrix[i][j] = value; } } ``` ```javascript // by chatGPT (javascript) var matrixReshape = function(mat, r, c) { var m = mat.length, n = mat[0].length; // 如果想成功 reshape,元素个数应该相同 if (r * c !== m * n) { return mat; } var res = new Array(r); for (var i = 0; i < r; i++) { res[i] = new Array(c); } for (var i = 0; i < m * n; i++) { set(res, i, get(mat, i)); } return res; } // 通过一维坐标访问二维数组中的元素 var get = function(matrix, index) { var m = matrix.length, n = matrix[0].length; // 计算二维中的横纵坐标 var i = Math.floor(index / n), j = index % n; return matrix[i][j]; } // 通过一维坐标设置二维数组中的元素 var set = function(matrix, index, value) { var m = matrix.length, n = matrix[0].length; // 计算二维中的横纵坐标 var i = Math.floor(index / n), j = index % n; matrix[i][j] = value; } ``` ```python # by chatGPT (python) class Solution: def matrixReshape(self, mat: List[List[int]], r: int, c: int) -> List[List[int]]: m, n = len(mat), len(mat[0]) # 如果想成功 reshape,元素个数应该相同 if r * c != m * n: return mat res = [[0] * c for _ in range(r)] for i in range(m * n): self.set(res, i, self.get(mat, i)) return res # 通过一维坐标访问二维数组中的元素 def get(self, matrix:List[List[int]], index:int) -> int: m, n = len(matrix), len(matrix[0]) # 计算二维中的横纵坐标 i, j = index // n, index % n return matrix[i][j] # 通过一维坐标设置二维数组中的元素 def set(self, matrix:List[List[int]], index:int, value:int) -> None: m, n = len(matrix), len(matrix[0]) # 计算二维中的横纵坐标 i, j = index // n, index % n matrix[i][j] = value ``` https://leetcode.cn/problems/reshape-the-matrix 的多语言解法👆 https://leetcode.cn/problems/reverse-linked-list 的多语言解法👇 ```cpp // by chatGPT (cpp) /** * Definition for singly-linked list. * struct ListNode { * int val; * ListNode *next; * ListNode(int x) : val(x), next(NULL) {} * }; */ class Solution { public: ListNode* reverseList(ListNode* head) { if (head == NULL || head->next == NULL) { return head; } ListNode* last = reverseList(head->next); /** ![](../pictures/反转链表/3.jpg) */ head->next->next = head; /** ![](../pictures/反转链表/4.jpg) */ head->next = NULL; /** ![](../pictures/反转链表/5.jpg) */ return last; } }; ``` ```go // by chatGPT (go) func reverseList(head *ListNode) *ListNode { if head == nil || head.Next == nil { return head } last := reverseList(head.Next) /* ![](../pictures/反转链表/3.jpg) */ head.Next.Next = head /* ![](../pictures/反转链表/4.jpg) */ head.Next = nil /* ![](../pictures/反转链表/5.jpg) */ return last } ``` ```java // by labuladong (java) class Solution { public ListNode reverseList(ListNode head) { if (head == null || head.next == null) { return head; } ListNode last = reverseList(head.next); /** ![](../pictures/反转链表/3.jpg) */ head.next.next = head; /** ![](../pictures/反转链表/4.jpg) */ head.next = null; /** ![](../pictures/反转链表/5.jpg) */ return last; } } ``` ```javascript // by chatGPT (javascript) /** * Definition for singly-linked list. * function ListNode(val, next) { * this.val = (val===undefined ? 0 : val) * this.next = (next===undefined ? null : next) * } */ /** * @param {ListNode} head * @return {ListNode} */ var reverseList = function(head) { if (head === null || head.next === null) { return head; } var last = reverseList(head.next); /** ![](../pictures/反转链表/3.jpg) */ head.next.next = head; /** ![](../pictures/反转链表/4.jpg) */ head.next = null; /** ![](../pictures/反转链表/5.jpg) */ return last; }; ``` ```python # by chatGPT (python) class Solution: def reverseList(self, head: ListNode) -> ListNode: if head is None or head.next is None: return head last = self.reverseList(head.next) """ ![](../pictures/反转链表/3.jpg) """ head.next.next = head """ ![](../pictures/反转链表/4.jpg) """ head.next = None """ ![](../pictures/反转链表/5.jpg) """ return last ``` https://leetcode.cn/problems/reverse-linked-list 的多语言解法👆 https://leetcode.cn/problems/reverse-linked-list-ii 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: ListNode* reverseBetween(ListNode* head, int m, int n) { // base case if (m == 1) { return reverseN(head, n); } // 前进到反转的起点触发 base case head->next = reverseBetween(head->next, m - 1, n - 1); return head; } private: ListNode* successor = nullptr; // 后驱节点 // 反转以 head 为起点的 n 个节点,返回新的头结点 ListNode* reverseN(ListNode* head, int n) { if (n == 1) { // 记录第 n + 1 个节点 successor = head->next; return head; } // 以 head->next 为起点,需要反转前 n - 1 个节点 ListNode* last = reverseN(head->next, n - 1); head->next->next = head; // 让反转之后的 head 节点和后面的节点连起来 head->next = successor; return last; /** ![](../pictures/反转链表/7.jpg) */ } }; ``` ```go // by chatGPT (go) /** * Definition for singly-linked list. * type ListNode struct { * Val int * Next *ListNode * } */ func reverseBetween(head *ListNode, m int, n int) *ListNode { var successor *ListNode // 后驱节点 // 反转以 head 为起点的 n 个节点,返回新的头结点 var reverseN func(head *ListNode, n int) (*ListNode) reverseN = func(head *ListNode, n int) (*ListNode) { if n == 1 { // 记录第 n + 1 个节点 successor = head.Next return head } // 以 head.Next 为起点,需要反转前 n - 1 个节点 last := reverseN(head.Next, n - 1) head.Next.Next = head // 让反转之后的 head 节点和后面的节点连起来 head.Next = successor return last } // base case if m == 1 { return reverseN(head, n) } // 前进到反转的起点触发 base case head.Next = reverseBetween(head.Next, m - 1, n - 1) return head } ``` ```java // by labuladong (java) class Solution { public ListNode reverseBetween(ListNode head, int m, int n) { // base case if (m == 1) { return reverseN(head, n); } // 前进到反转的起点触发 base case head.next = reverseBetween(head.next, m - 1, n - 1); return head; } ListNode successor = null; // 后驱节点 // 反转以 head 为起点的 n 个节点,返回新的头结点 ListNode reverseN(ListNode head, int n) { if (n == 1) { // 记录第 n + 1 个节点 successor = head.next; return head; } // 以 head.next 为起点,需要反转前 n - 1 个节点 ListNode last = reverseN(head.next, n - 1); head.next.next = head; // 让反转之后的 head 节点和后面的节点连起来 head.next = successor; return last; /** ![](../pictures/反转链表/7.jpg) */ } } ``` ```javascript // by chatGPT (javascript) var reverseBetween = function(head, m, n) { let successor = null; // 反转以 head 为起点的 n 个节点,返回新的头结点 const reverseN = function(head, n) { if (n == 1) { // 记录第 n + 1 个节点 successor = head.next; return head; } const last = reverseN(head.next, n - 1); head.next.next = head; // 让反转之后的 head 节点和后面的节点连起来 head.next = successor; return last; /** ![](../pictures/反转链表/7.jpg) */ }; // base case if (m == 1) { return reverseN(head, n); } // 前进到反转的起点触发 base case head.next = reverseBetween(head.next, m - 1, n - 1); return head; }; ``` ```python # by chatGPT (python) class Solution: def reverseBetween(self, head: ListNode, m: int, n: int) -> ListNode: # base case if m == 1: return self.reverseN(head, n) # 前进到反转的起点触发 base case head.next = self.reverseBetween(head.next, m - 1, n - 1) return head successor = None # 后驱节点 # 反转以 head 为起点的 n 个节点,返回新的头结点 def reverseN(self, head: ListNode, n: int) -> ListNode: if n == 1: # 记录第 n + 1 个节点 self.successor = head.next return head # 以 head.next 为起点,需要反转前 n - 1 个节点 last = self.reverseN(head.next, n - 1) head.next.next = head # 让反转之后的 head 节点和后面的节点连起来 head.next = self.successor return last """ ![](../pictures/反转链表/7.jpg) """ ``` https://leetcode.cn/problems/reverse-linked-list-ii 的多语言解法👆 https://leetcode.cn/problems/reverse-nodes-in-k-group 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: ListNode* reverseKGroup(ListNode* head, int k) { if(head == NULL) return NULL; //区间[a,b)包含k个待反转元素 ListNode *a, *b; a = b = head; for (int i = 0; i < k; i++) { //不足k个,直接返回 if (b == NULL) return head; b = b->next; } //反转前k个元素 ListNode *newHead = reverse(a, b); //递归反转后续链表并连接起来 a->next = reverseKGroup(b, k); return newHead; } ListNode *reverse(ListNode *a, ListNode *b) { ListNode *pre, *cur, *nxt; cur = a; pre = NULL; while (cur != b) { nxt = cur->next; cur->next = pre; pre = cur; cur = nxt; } return pre; } }; ``` ```go // by chatGPT (go) // 给出一个链表和一个数k,比如1->2->3->4->5->6,k=2,那么你需要返回的结果是2->1->4->3->6->5。 func reverseKGroup(head *ListNode, k int) *ListNode { if head == nil { return nil } a, b := head, head // 找到需要翻转的区间 [a, b) for i := 0; i < k; i++ { if b == nil { return head } b = b.Next } // 反转区间内的链表 newHead := reverse(a, b) // 递归将后续链表的区间也翻转,然后再将它链接到新的区间内 a.Next = reverseKGroup(b, k) /** ![](../pictures/kgroup/6.jpg) */ return newHead } // 翻转区间 [a, b),包头不包尾 func reverse(a, b *ListNode) *ListNode { // 初始化三个指针 pre, cur, nxt := (*ListNode)(nil), a, a // 循环将当前节点指向前一个节点,然后将前一个节点和当前节点往后移动 for cur != b { nxt = cur.Next cur.Next = pre pre, cur = cur, nxt } // 返回新的头结点 return pre } ``` ```java // by labuladong (java) class Solution { public ListNode reverseKGroup(ListNode head, int k) { if (head == null) return null; // 区间 [a, b) 包含 k 个待反转元素 ListNode a, b; a = b = head; for (int i = 0; i < k; i++) { // 不足 k 个,不需要反转,base case if (b == null) return head; b = b.next; } // 反转前 k 个元素 ListNode newHead = reverse(a, b); // 递归反转后续链表并连接起来 a.next = reverseKGroup(b, k); /** ![](../pictures/kgroup/6.jpg) */ return newHead; } /* 反转区间 [a, b) 的元素,注意是左闭右开 */ ListNode reverse(ListNode a, ListNode b) { /** ![](../pictures/kgroup/8.gif) */ ListNode pre, cur, nxt; pre = null; cur = a; nxt = a; // while 终止的条件改一下就行了 while (cur != b) { nxt = cur.next; cur.next = pre; pre = cur; cur = nxt; } // 返回反转后的头结点 return pre; } } ``` ```javascript // by chatGPT (javascript) /** * @param {ListNode} head * @param {number} k * @return {ListNode} */ var reverseKGroup = function(head, k) { if (!head) return null; // 区间 [a, b) 包含 k 个待反转元素 let a = head, b = head; for (let i = 0; i < k; i++) { // 不足 k 个,不需要反转,base case if (!b) return head; b = b.next; } // 反转前 k 个元素 let newHead = reverse(a, b); // 递归反转后续链表并连接起来 a.next = reverseKGroup(b, k); /** ![](../pictures/kgroup/6.jpg) */ return newHead; }; /* 反转区间 [a, b) 的元素,注意是左闭右开 */ var reverse = function(a, b) { /** ![](../pictures/kgroup/8.gif) */ let pre = null, cur = a, nxt = a; // while 终止的条件改一下就行了 while (cur !== b) { nxt = cur.next; cur.next = pre; pre = cur; cur = nxt; } // 返回反转后的头结点 return pre; }; ``` ```python # by chatGPT (python) class Solution: def reverseKGroup(self, head: ListNode, k: int) -> ListNode: if not head: return None # 区间 [a, b) 包含 k 个待反转元素 a = b = head for i in range(k): # 不足 k 个,不需要反转,base case if not b: return head b = b.next # 反转前 k 个元素 newHead = self.reverse(a, b) # 递归反转后续链表并连接起来 a.next = self.reverseKGroup(b, k) """ ![](../pictures/kgroup/6.jpg) """ return newHead """ 反转区间 [a, b) 的元素,注意是左闭右开 """ def reverse(self, a: ListNode, b: ListNode) -> ListNode: """ ![](../pictures/kgroup/8.gif) """ pre, cur, nxt = None, a, a # while 终止的条件改一下就行了 while cur != b: nxt = cur.next cur.next = pre pre = cur cur = nxt # 返回反转后的头结点 return pre ``` https://leetcode.cn/problems/reverse-nodes-in-k-group 的多语言解法👆 https://leetcode.cn/problems/reverse-words-in-a-string 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: string reverseWords(string s) { string res; // 清洗一下数据,去除多余的空格 for (int i = 0; i < s.size(); i++) { char c = s[i]; if (c != ' ') { // 单词中的字母/数字 res += c; } else if (res.back() != ' ') { // 单词之间保留一个空格 res += ' '; } } // 末尾如果有空格,清除之 if (res.back() == ' ') { res.pop_back(); } // 清洗之后的字符串 char *chars = new char[res.size() + 1]; strcpy(chars, res.c_str()); int n = strlen(chars); // 进行单词的翻转,先整体翻转 reverse(chars, 0, n - 1); // 再把每个单词翻转 for (int i = 0; i < n; ) { for (int j = i; j < n; j++) { if (j + 1 == n || chars[j + 1] == ' ') { // chars[i..j] 是一个单词,翻转之 reverse(chars, i, j); // 把 i 置为下一个单词的首字母 i = j + 2; break; } } } // 最后得到题目想要的结果 return chars; } // 翻转 arr[i..j] void reverse(char* arr, int i, int j) { while (i < j) { char temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; i++; j--; } } }; ``` ```go // by chatGPT (go) func reverseWords(s string) string { sb := strings.Builder{} // 先清洗一下数据,把多于的空格都删掉 for i := 0; i < len(s); i++ { c := s[i] if c != ' ' { // 单词中的字母/数字 sb.WriteByte(c) } else if sb.Len() > 0 && sb.String()[sb.Len()-1] != ' ' { // 单词之间保留一个空格 sb.WriteByte(' ') } } // 末尾如果有空格,清除之 str := sb.String() if len(str) > 0 && str[len(str)-1] == ' ' { str = str[:len(str)-1] } // 清洗之后的字符串 chars := []byte(str) n := len(chars) // 进行单词的翻转,先整体翻转 reverse(chars, 0, n-1) // 再把每个单词翻转 for i := 0; i < n; { for j := i; j < n; j++ { if j+1 == n || chars[j+1] == ' ' { // chars[i..j] 是一个单词,翻转之 reverse(chars, i, j) // 把 i 置为下一个单词的首字母 i = j + 2 break } } } // 最后得到题目想要的结果 return string(chars) } // 翻转 arr[i..j] func reverse(arr []byte, i, j int) { for i < j { arr[i], arr[j] = arr[j], arr[i] i++ j-- } } ``` ```java // by labuladong (java) class Solution { public String reverseWords(String s) { StringBuilder sb = new StringBuilder(); // 先清洗一下数据,把多于的空格都删掉 for (int i = 0; i < s.length(); i++) { char c = s.charAt(i); if (c != ' ') { // 单词中的字母/数字 sb.append(c); } else if (!sb.isEmpty() && sb.charAt(sb.length() - 1) != ' ') { // 单词之间保留一个空格 sb.append(' '); } } if (sb.isEmpty()) { return ""; } // 末尾如果有空格,清除之 if (sb.charAt(sb.length() - 1) == ' ') { sb.deleteCharAt(sb.length() - 1); } // 清洗之后的字符串 char[] chars = sb.toString().toCharArray(); int n = chars.length; // 进行单词的翻转,先整体翻转 reverse(chars, 0, n - 1); // 再把每个单词翻转 for (int i = 0; i < n; ) { for (int j = i; j < n; j++) { if (j + 1 == n || chars[j + 1] == ' ') { // chars[i..j] 是一个单词,翻转之 reverse(chars, i, j); // 把 i 置为下一个单词的首字母 i = j + 2; break; } } } // 最后得到题目想要的结果 return new String(chars); } // 翻转 arr[i..j] void reverse(char[] arr, int i, int j) { while (i < j) { char temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; i++; j--; } } } ``` ```javascript // by chatGPT (javascript) var reverseWords = function(s) { let sb = ""; // 先清洗一下数据,把多于的空格都删掉 for (let i = 0; i < s.length; i++) { let c = s.charAt(i); if (c !== ' ') { // 单词中的字母/数字 sb += c; } else if (sb !== "" && sb[sb.length - 1] !== ' ') { // 单词之间保留一个空格 sb += ' '; } } // 末尾如果有空格,清除之 if (sb[sb.length - 1] === ' ') { sb = sb.substring(0, sb.length - 1); } // 清洗之后的字符串 let chars = sb.split(''); let n = chars.length; // 进行单词的翻转,先整体翻转 reverse(chars, 0, n - 1); // 再把每个单词翻转 for (let i = 0; i < n;) { for (let j = i; j < n; j++) { if (j + 1 === n || chars[j + 1] === ' ') { // chars[i..j] 是一个单词,翻转之 reverse(chars, i, j); // 把 i 置为下一个单词的首字母 i = j + 2; break; } } } // 最后得到题目想要的结果 return chars.join(""); }; // 翻转 arr[i..j] function reverse(arr, i, j) { while (i < j) { let temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; i++; j--; } } ``` ```python # by chatGPT (python) class Solution: def reverseWords(self, s: str) -> str: sb = [] # 先清洗一下数据,把多余的空格都删掉 for c in s: if c != ' ': # 单词中的字母/数字 sb.append(c) elif sb and sb[-1] != ' ': # 单词之间保留一个空格 sb.append(' ') # 末尾如果有空格,清除之 if sb and sb[-1] == ' ': sb.pop() # 清洗之后的字符串 chars = sb # 进行单词的翻转,先整体翻转 def reverse(l, r): while l < r: chars[l], chars[r] = chars[r], chars[l] l += 1 r -= 1 n = len(chars) reverse(0, n-1) # 再把每个单词翻转 start = end = 0 while end < n: if chars[end] == ' ': reverse(start, end-1) start = end + 1 end += 1 # 翻转最后一个单词 reverse(start, n-1) # 最后得到题目想要的结果 return "".join(chars) ``` https://leetcode.cn/problems/reverse-words-in-a-string 的多语言解法👆 https://leetcode.cn/problems/rotate-image 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: void rotate(vector>& matrix) { int n = matrix.size(); // 先沿对角线反转二维矩阵 for (int i = 0; i < n; i++) { for (int j = i; j < n; j++) { // swap(matrix[i][j], matrix[j][i]); int temp = matrix[i][j]; matrix[i][j] = matrix[j][i]; matrix[j][i] = temp; } } // 然后反转二维矩阵的每一行 for (auto& row : matrix) { ::reverse(row.begin(), row.end()); } } // 反转一维数组 void reverse(vector& arr) { int i = 0, j = arr.size() - 1; while (j > i) { // swap(arr[i], arr[j]); int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; i++; j--; } } }; ``` ```go // by chatGPT (go) func rotate(matrix [][]int) { n := len(matrix) // 先沿对角线反转二维矩阵 for i := 0; i < n; i++ { for j := i; j < n; j++ { matrix[i][j], matrix[j][i] = matrix[j][i], matrix[i][j] } } // 然后反转二维矩阵的每一行 for _, row := range matrix { reverse(row) } } // 反转一维数组 func reverse(arr []int) { i, j := 0, len(arr) - 1 for j > i { arr[i], arr[j] = arr[j], arr[i] i++ j-- } } ``` ```java // by labuladong (java) class Solution { public void rotate(int[][] matrix) { int n = matrix.length; // 先沿对角线反转二维矩阵 for (int i = 0; i < n; i++) { for (int j = i; j < n; j++) { // swap(matrix[i][j], matrix[j][i]); int temp = matrix[i][j]; matrix[i][j] = matrix[j][i]; matrix[j][i] = temp; } } // 然后反转二维矩阵的每一行 for (int[] row : matrix) { reverse(row); } } // 反转一维数组 void reverse(int[] arr) { int i = 0, j = arr.length - 1; while (j > i) { // swap(arr[i], arr[j]); int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; i++; j--; } } } ``` ```javascript // by chatGPT (javascript) /** * @param {number[][]} matrix * @return {void} Do not return anything, modify matrix in-place instead. */ var rotate = function(matrix) { var n = matrix.length; // 先沿对角线反转二维矩阵 for (var i = 0; i < n; i++) { for (var j = i; j < n; j++) { // swap(matrix[i][j], matrix[j][i]); var temp = matrix[i][j]; matrix[i][j] = matrix[j][i]; matrix[j][i] = temp; } } // 然后反转二维矩阵的每一行 for (var row of matrix) { reverse(row); } } // 反转一维数组 function reverse(arr) { var i = 0, j = arr.length - 1; while (j > i) { // swap(arr[i], arr[j]); var temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; i++; j--; } } ``` ```python # by chatGPT (python) class Solution: def rotate(self, matrix: List[List[int]]) -> None: n = len(matrix) # 先沿对角线反转二维矩阵 for i in range(n): for j in range(i, n): # swap(matrix[i][j], matrix[j][i]); temp = matrix[i][j] matrix[i][j] = matrix[j][i] matrix[j][i] = temp # 然后反转二维矩阵的每一行 for row in matrix: self.reverse(row) # 反转一维数组 def reverse(self, arr: List[int]) -> None: i, j = 0, len(arr) - 1 while j > i: # swap(arr[i], arr[j]); temp = arr[i] arr[i] = arr[j] arr[j] = temp i += 1 j -= 1 ``` https://leetcode.cn/problems/rotate-image 的多语言解法👆 https://leetcode.cn/problems/russian-doll-envelopes 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int maxEnvelopes(vector>& envelopes) { int n = envelopes.size(); // 按宽度升序排列,如果宽度一样,则按高度降序排列 sort(envelopes.begin(), envelopes.end(), [](vector& a, vector& b){ if(a[0] == b[0]){ return b[1] < a[1]; }else{ return a[0] < b[0]; } }); // 对高度数组寻找 LIS vector height(n); for (int i = 0; i < n; i++){ height[i] = envelopes[i][1]; } return lengthOfLIS(height); } /* 返回 nums 中 LIS 的长度 */ int lengthOfLIS(vector& nums) { int piles = 0, n = nums.size(); vector top(n); for (int i = 0; i < n; i++) { // 要处理的扑克牌 int poker = nums[i]; int left = 0, right = piles; // 二分查找插入位置 while (left < right) { int mid = (left + right) / 2; if (top[mid] >= poker) right = mid; else left = mid + 1; } if (left == piles) piles++; // 把这张牌放到牌堆顶 top[left] = poker; } // 牌堆数就是 LIS 长度 return piles; } }; ``` ```go // by chatGPT (go) import "sort" func maxEnvelopes(envelopes [][]int) int { n := len(envelopes) // 按宽度升序排列,如果宽度一样,则按高度降序排列 sort.Slice(envelopes, func(i, j int) bool { if envelopes[i][0] < envelopes[j][0] { return true } if envelopes[i][0] == envelopes[j][0] { return envelopes[i][1] > envelopes[j][1] } return false }) // 对高度数组寻找 LIS height := make([]int, n) for i := 0; i < n; i++ { height[i] = envelopes[i][1] } return lengthOfLIS(height) } /* 返回 nums 中 LIS 的长度 */ func lengthOfLIS(nums []int) int { piles, n := 0, len(nums) top := make([]int, n) for i := 0; i < n; i++ { // 要处理的扑克牌 poker := nums[i] left, right := 0, piles // 二分查找插入位置 for left < right { mid := (left + right) / 2 if top[mid] >= poker { right = mid } else { left = mid + 1 } } if left == piles { piles++ } // 把这张牌放到牌堆顶 top[left] = poker } // 牌堆数就是 LIS 长度 return piles } ``` ```java // by labuladong (java) class Solution { public int maxEnvelopes(int[][] envelopes) { int n = envelopes.length; // 按宽度升序排列,如果宽度一样,则按高度降序排列 Arrays.sort(envelopes, new Comparator() { public int compare(int[] a, int[] b) { return a[0] == b[0] ? b[1] - a[1] : a[0] - b[0]; } }); // 对高度数组寻找 LIS int[] height = new int[n]; for (int i = 0; i < n; i++) height[i] = envelopes[i][1]; return lengthOfLIS(height); } /* 返回 nums 中 LIS 的长度 */ public int lengthOfLIS(int[] nums) { int piles = 0, n = nums.length; int[] top = new int[n]; for (int i = 0; i < n; i++) { // 要处理的扑克牌 int poker = nums[i]; int left = 0, right = piles; // 二分查找插入位置 while (left < right) { int mid = (left + right) / 2; if (top[mid] >= poker) right = mid; else left = mid + 1; } if (left == piles) piles++; // 把这张牌放到牌堆顶 top[left] = poker; } // 牌堆数就是 LIS 长度 return piles; } } ``` ```javascript // by chatGPT (javascript) /** * @param {number[][]} envelopes * @return {number} */ var maxEnvelopes = function(envelopes) { const n = envelopes.length; // 按宽度升序排列,如果宽度一样,则按高度降序排列 envelopes.sort((a, b) => { if (a[0] === b[0]) { return b[1] - a[1]; } else { return a[0] - b[0]; } }); // 对高度数组寻找 LIS const height = new Array(n).fill(0).map((_, i) => envelopes[i][1]); return lengthOfLIS(height); } /** * @param {number[]} nums * @return {number} */ const lengthOfLIS = (nums) => { // piles 表示牌堆数 let piles = 0; const n = nums.length; const top = new Array(n).fill(0); for (let i = 0; i < n; i++) { // 要处理的扑克牌 let poker = nums[i]; let left = 0; let right = piles; // 二分查找插入位置 while (left < right) { let mid = Math.floor((left + right) / 2); if (top[mid] >= poker) { right = mid; } else { left = mid + 1; } } if (left === piles) piles++; // 把这张牌放到牌堆顶 top[left] = poker; } // 牌堆数就是 LIS 长度 return piles; }; ``` ```python # by chatGPT (python) class Solution: def maxEnvelopes(self, envelopes: List[List[int]]) -> int: n = len(envelopes) # 按宽度升序排列,如果宽度一样,则按高度降序排列 envelopes.sort(key=lambda x: (x[0], -x[1])) # 对高度数组寻找 LIS height = [0] * n for i in range(n): height[i] = envelopes[i][1] return self.lengthOfLIS(height) def lengthOfLIS(self, nums: List[int]) -> int: piles = 0 n = len(nums) top = [0] * n for i in range(n): # 要处理的扑克牌 poker = nums[i] left, right = 0, piles # 二分查找插入位置 while left < right: mid = (left + right) // 2 if top[mid] >= poker: right = mid else: left = mid + 1 if left == piles: piles += 1 # 把这张牌放到牌堆顶 top[left] = poker # 牌堆数就是 LIS 长度 return piles ``` https://leetcode.cn/problems/russian-doll-envelopes 的多语言解法👆 https://leetcode.cn/problems/same-tree 的多语言解法👇 ```cpp // by chatGPT (cpp) // 定义:输入两个根节点,返回以它们为根的两棵二叉树是否相同 class Solution { public: bool isSameTree(TreeNode* p, TreeNode* q) { // 判断一对节点是否相同 if (p == nullptr && q == nullptr) { return true; } if (p == nullptr || q == nullptr) { return false; } if (p->val != q->val) { return false; } // 判断其他节点是否相同 return isSameTree(p->left, q->left) && isSameTree(p->right, q->right); } }; ``` ```go // by chatGPT (go) // 定义:输入两个根节点,返回以它们为根的两棵二叉树是否相同 func isSameTree(p *TreeNode, q *TreeNode) bool { // 判断一对节点是否相同 if p == nil && q == nil { return true } if p == nil || q == nil { return false } if p.Val != q.Val { return false } // 判断其他节点是否相同 return isSameTree(p.Left, q.Left) && isSameTree(p.Right, q.Right) } ``` ```java // by labuladong (java) 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); } } ``` ```javascript // by chatGPT (javascript) /** * 定义:输入两个根节点,返回以它们为根的两棵二叉树是否相同 * @param {TreeNode} p * @param {TreeNode} q * @return {boolean} */ var isSameTree = function(p, 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); }; ``` ```python # by chatGPT (python) # 定义:输入两个根节点,返回以它们为根的两棵二叉树是否相同 class Solution: def isSameTree(self, p: TreeNode, q: TreeNode) -> bool: # 判断一对节点是否相同 if p == None and q == None: return True if p == None or q == None: return False if p.val != q.val: return False # 判断其他节点是否相同 return self.isSameTree(p.left, q.left) and self.isSameTree(p.right, q.right) ``` https://leetcode.cn/problems/same-tree 的多语言解法👆 https://leetcode.cn/problems/satisfiability-of-equality-equations 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: bool equationsPossible(vector& equations) { // 26 个英文字母 UF uf(26); // 先让相等的字母形成连通分量 for (string eq : equations) { if (eq[1] == '=') { char x = eq[0]; char y = eq[3]; uf.union_(x - 'a', y - 'a'); } } // 检查不等关系是否打破相等关系的连通性 for (string eq : equations) { if (eq[1] == '!') { char x = eq[0]; char y = eq[3]; // 如果相等关系成立,就是逻辑冲突 if (uf.connected(x - 'a', y - 'a')) return false; } } return true; } }; class UF { public: // 记录连通分量个数 int count; // 存储若干棵树 vector parent; // 记录树的“重量” vector size; UF(int n) { this->count = n; parent.resize(n); size.resize(n); for (int i = 0; i < n; i++) { parent[i] = i; size[i] = 1; } } /* 将 p 和 q 连通 */ void union_(int p, int q) { int rootP = find(p); int rootQ = find(q); if (rootP == rootQ) return; // 小树接到大树下面,较平衡 if (size[rootP] > size[rootQ]) { parent[rootQ] = rootP; size[rootP] += size[rootQ]; } else { parent[rootP] = rootQ; size[rootQ] += size[rootP]; } count--; } /* 判断 p 和 q 是否互相连通 */ bool connected(int p, int q) { int rootP = find(p); int rootQ = find(q); // 处于同一棵树上的节点,相互连通 return rootP == rootQ; } /* 返回节点 x 的根节点 */ int find(int x) { while (parent[x] != x) { // 进行路径压缩 parent[x] = parent[parent[x]]; x = parent[x]; } return x; } int getCount() { return count; } }; ``` ```go // by chatGPT (go) type UF struct { // 记录连通分量个数 Count int // 存储若干棵树 Parent []int // 记录树的“重量” Size []int } func NewUF(n int) *UF { u := UF{} u.Count = n u.Parent = make([]int, n) u.Size = make([]int, n) for i := 0; i < n; i++ { u.Parent[i] = i u.Size[i] = 1 } return &u } /* 将 p 和 q 连通 */ func (u *UF) Union(p, q int) { rootP := u.find(p) rootQ := u.find(q) if rootP == rootQ { return } // 小树接到大树下面,较平衡 if u.Size[rootP] > u.Size[rootQ] { u.Parent[rootQ] = rootP u.Size[rootP] += u.Size[rootQ] } else { u.Parent[rootP] = rootQ u.Size[rootQ] += u.Size[rootP] } u.Count-- } /* 判断 p 和 q 是否互相连通 */ func (u *UF) Connected(p, q int) bool { rootP := u.find(p) rootQ := u.find(q) // 处于同一棵树上的节点,相互连通 return rootP == rootQ } /* 返回节点 x 的根节点 */ func (u *UF) find(x int) int { for u.Parent[x] != x { // 进行路径压缩 u.Parent[x] = u.Parent[u.Parent[x]] x = u.Parent[x] } return x } func equationsPossible(equations []string) bool { // 26 个英文字母 uf := NewUF(26) // 先让相等的字母形成连通分量 for _, eq := range equations { if eq[1] == '=' { x := eq[0] y := eq[3] uf.Union(int(x-'a'), int(y-'a')) } } // 检查不等关系是否打破相等关系的连通性 for _, eq := range equations { if eq[1] == '!' { x := eq[0] y := eq[3] // 如果相等关系成立,就是逻辑冲突 if uf.Connected(int(x-'a'), int(y-'a')) { return false } } } return true } ``` ```java // by labuladong (java) class Solution { public boolean equationsPossible(String[] equations) { // 26 个英文字母 UF uf = new UF(26); // 先让相等的字母形成连通分量 for (String eq : equations) { if (eq.charAt(1) == '=') { char x = eq.charAt(0); char y = eq.charAt(3); uf.union(x - 'a', y - 'a'); } } // 检查不等关系是否打破相等关系的连通性 for (String eq : equations) { if (eq.charAt(1) == '!') { char x = eq.charAt(0); char y = eq.charAt(3); // 如果相等关系成立,就是逻辑冲突 if (uf.connected(x - 'a', y - 'a')) return false; } } return true; } } class UF { // 记录连通分量个数 private int count; // 存储若干棵树 private int[] parent; // 记录树的“重量” private int[] size; public UF(int n) { this.count = n; parent = new int[n]; size = new int[n]; for (int i = 0; i < n; i++) { parent[i] = i; size[i] = 1; } } /* 将 p 和 q 连通 */ public void union(int p, int q) { int rootP = find(p); int rootQ = find(q); if (rootP == rootQ) return; // 小树接到大树下面,较平衡 if (size[rootP] > size[rootQ]) { parent[rootQ] = rootP; size[rootP] += size[rootQ]; } else { parent[rootP] = rootQ; size[rootQ] += size[rootP]; } count--; } /* 判断 p 和 q 是否互相连通 */ public boolean connected(int p, int q) { int rootP = find(p); int rootQ = find(q); // 处于同一棵树上的节点,相互连通 return rootP == rootQ; } /* 返回节点 x 的根节点 */ private int find(int x) { while (parent[x] != x) { // 进行路径压缩 parent[x] = parent[parent[x]]; x = parent[x]; } return x; } public int count() { return count; } } ``` ```javascript // by chatGPT (javascript) var equationsPossible = function(equations) { // 26 个英文字母 let uf = new ufFunc(26); // 先让相等的字母形成连通分量 for (let i = 0 ; i this.size[rootQ]) { this.parent[rootQ] = rootP; this.size[rootP] += this.size[rootQ]; } else { this.parent[rootP] = rootQ; this.size[rootQ] += this.size[rootP]; } this.count--; } /* 判断 p 和 q 是否互相连通 */ connected(p, q) { let rootP = this.find(p); let rootQ = this.find(q); // 处于同一棵树上的节点,相互连通 return rootP == rootQ; } /* 返回节点 x 的根节点 */ find(x) { while (this.parent[x] != x) { // 进行路径压缩 this.parent[x] = this.parent[this.parent[x]]; x = this.parent[x]; } return x; } count() { return this.count; } } ``` ```python # by chatGPT (python) class Solution: def equationsPossible(self, equations: List[str]) -> bool: uf = UF(26) #26个字母 # 先让相等的字母形成连通分量 for eq in equations: if eq[1] == "=": x = ord(eq[0]) - ord('a') y = ord(eq[3]) - ord('a') uf.union(x, y) # 检查不等关系是否打破相等关系的连通性 for eq in equations: if eq[1] == "!": x = ord(eq[0]) - ord('a') y = ord(eq[3]) - ord('a') # 如果相等关系成立,就是逻辑冲突 if uf.connected(x, y): return False return True class UF: # 记录连通分量个数 def __init__(self, n): self.count = n # 存储若干棵树 self.parent = [i for i in range(n)] # 记录树的“重量” self.size = [1] * n # 将 p 和 q 连通 def union(self, p, q): rootP = self.find(p) rootQ = self.find(q) if rootP == rootQ: return # 小树接到大树下面,较平衡 if self.size[rootP] > self.size[rootQ]: self.parent[rootQ] = rootP self.size[rootP] += self.size[rootQ] else: self.parent[rootP] = rootQ self.size[rootQ] += self.size[rootP] self.count -= 1 # 判断 p 和 q 是否互相连通 def connected(self, p, q): rootP = self.find(p) rootQ = self.find(q) # 处于同一棵树上的节点,相互连通 return rootP == rootQ # 返回节点 x 的根节点 def find(self, x): while self.parent[x] != x: # 进行路径压缩 self.parent[x] = self.parent[self.parent[x]] x = self.parent[x] return x def count(self): return self.count ``` https://leetcode.cn/problems/satisfiability-of-equality-equations 的多语言解法👆 https://leetcode.cn/problems/search-a-2d-matrix 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: bool searchMatrix(vector>& matrix, int target) { int m = matrix.size(), n = matrix[0].size(); // 把二维数组映射到一维 int left = 0, right = m * n - 1; // 前文讲的标准的二分搜索框架 while(left <= right) { int mid = left + (right - left) / 2; if(get(matrix, mid) == target) return true; else if (get(matrix, mid) < target) left = mid + 1; else if (get(matrix, mid) > target) right = mid - 1; } return false; } // 通过一维坐标访问二维数组中的元素 int get(vector>& matrix, int index) { int m = matrix.size(), n = matrix[0].size(); // 计算二维中的横纵坐标 int i = index / n, j = index % n; return matrix[i][j]; } }; ``` ```go // by chatGPT (go) func searchMatrix(matrix [][]int, target int) bool { m, n := len(matrix), len(matrix[0]) // 把二维数组映射到一维 left, right := 0, m*n-1 // 前文讲的标准的二分搜索框架 for left <= right { mid := left + (right-left)/2 if get(matrix, mid) == target { return true } else if get(matrix, mid) < target { left = mid + 1 } else if get(matrix, mid) > target { right = mid - 1 } } return false } // 通过一维坐标访问二维数组中的元素 func get(matrix [][]int, index int) int { n := len(matrix[0]) // 计算二维中的横纵坐标 i, j := index/n, index%n return matrix[i][j] } ``` ```java // by labuladong (java) class Solution { public boolean searchMatrix(int[][] matrix, int target) { int m = matrix.length, n = matrix[0].length; // 把二维数组映射到一维 int left = 0, right = m * n - 1; // 前文讲的标准的二分搜索框架 while(left <= right) { int mid = left + (right - left) / 2; if(get(matrix, mid) == target) return true; else if (get(matrix, mid) < target) left = mid + 1; else if (get(matrix, mid) > target) right = mid - 1; } return false; } // 通过一维坐标访问二维数组中的元素 int get(int[][] matrix, int index) { int m = matrix.length, n = matrix[0].length; // 计算二维中的横纵坐标 int i = index / n, j = index % n; return matrix[i][j]; } } ``` ```javascript // by chatGPT (javascript) var searchMatrix = function(matrix, target) { var m = matrix.length, n = matrix[0].length; // 把二维数组映射到一维 var left = 0, right = m * n - 1; // 前文讲的标准的二分搜索框架 while(left <= right) { var mid = left + Math.floor((right - left) / 2); if(get(matrix, mid) == target) return true; else if (get(matrix, mid) < target) left = mid + 1; else if (get(matrix, mid) > target) right = mid - 1; } return false; }; // 通过一维坐标访问二维数组中的元素 var get = function(matrix, index) { var m = matrix.length, n = matrix[0].length; // 计算二维中的横纵坐标 var i = Math.floor(index / n), j = index % n; return matrix[i][j]; }; ``` ```python # by chatGPT (python) class Solution: def searchMatrix(self, matrix: List[List[int]], target: int) -> bool: m, n = len(matrix), len(matrix[0]) # 把二维数组映射到一维 left, right = 0, m * n - 1 # 前文讲的标准的二分搜索框架 while(left <= right): mid = left + (right - left) // 2 if(self.get(matrix, mid) == target): return True elif self.get(matrix, mid) < target: left = mid + 1 elif self.get(matrix, mid) > target: right = mid - 1 return False # 通过一维坐标访问二维数组中的元素 def get(self, matrix: List[List[int]], index: int) -> int: m, n = len(matrix), len(matrix[0]) # 计算二维中的横纵坐标 i, j = index // n, index % n return matrix[i][j] ``` https://leetcode.cn/problems/search-a-2d-matrix 的多语言解法👆 https://leetcode.cn/problems/search-a-2d-matrix-ii 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: bool searchMatrix(vector>& matrix, int target) { int m = matrix.size(), n = matrix[0].size(); // 初始化在右上角 int i = 0, j = n - 1; while (i < m && j >= 0) { if (matrix[i][j] == target) { return true; } if (matrix[i][j] < target) { // 需要大一点,往下移动 i++; } else { // 需要小一点,往左移动 j--; } } // while 循环中没有找到,则 target 不存在 return false; } }; ``` ```go // by chatGPT (go) func searchMatrix(matrix [][]int, target int) bool { m, n := len(matrix), len(matrix[0]) // 初始化在右上角 i, j := 0, n-1 for i < m && j >= 0 { if matrix[i][j] == target { return true } if matrix[i][j] < target { // 需要大一点,往下移动 i++ } else { // 需要小一点,往左移动 j-- } } // for 循环中没有找到,则 target 不存在 return false } ``` ```java // by labuladong (java) class Solution { public boolean searchMatrix(int[][] matrix, int target) { int m = matrix.length, n = matrix[0].length; // 初始化在右上角 int i = 0, j = n - 1; while (i < m && j >= 0) { if (matrix[i][j] == target) { return true; } if (matrix[i][j] < target) { // 需要大一点,往下移动 i++; } else { // 需要小一点,往左移动 j--; } } // while 循环中没有找到,则 target 不存在 return false; } } ``` ```javascript // by chatGPT (javascript) var searchMatrix = function(matrix, target) { var m = matrix.length, n = matrix[0].length; // 初始化在右上角 var i = 0, j = n - 1; while (i < m && j >= 0) { if (matrix[i][j] == target) { return true; } if (matrix[i][j] < target) { // 需要大一点,往下移动 i++; } else { // 需要小一点,往左移动 j--; } } // while 循环中没有找到,则 target 不存在 return false; }; ``` ```python # by chatGPT (python) class Solution: def searchMatrix(self, matrix: List[List[int]], target: int) -> bool: m, n = len(matrix), len(matrix[0]) # 初始化在右上角 i, j = 0, n - 1 while i < m and j >= 0: if matrix[i][j] == target: return True if matrix[i][j] < target: # 需要大一点,往下移动 i += 1 else: # 需要小一点,往左移动 j -= 1 # while 循环中没有找到,则 target 不存在 return False ``` https://leetcode.cn/problems/search-a-2d-matrix-ii 的多语言解法👆 https://leetcode.cn/problems/search-in-a-binary-search-tree 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: TreeNode* searchBST(TreeNode* root, int target) { if (root == NULL) { return NULL; } // 去左子树搜索 if (root->val > target) { return searchBST(root->left, target); } // 去右子树搜索 if (root->val < target) { return searchBST(root->right, target); } return root; } }; ``` ```go // by chatGPT (go) func searchBST(root *TreeNode, target int) *TreeNode { if root == nil { return nil } // 去左子树搜索 if root.Val > target { return searchBST(root.Left, target) } // 去右子树搜索 if root.Val < target { return searchBST(root.Right, target) } return root } ``` ```java // by labuladong (java) class Solution { public TreeNode searchBST(TreeNode root, int target) { if (root == null) { return null; } // 去左子树搜索 if (root.val > target) { return searchBST(root.left, target); } // 去右子树搜索 if (root.val < target) { return searchBST(root.right, target); } return root; } } ``` ```javascript // by chatGPT (javascript) /** * @param {TreeNode} root * @param {number} val * @return {TreeNode} */ var searchBST = function(root, val) { if (root === null) { return null; } // 去左子树搜索 if (root.val > val) { return searchBST(root.left, val); } // 去右子树搜索 if (root.val < val) { return searchBST(root.right, val); } return root; }; ``` ```python # by chatGPT (python) class Solution: def searchBST(self, root: TreeNode, target: int) -> TreeNode: if not root: return None # 去左子树搜索 if root.val > target: return self.searchBST(root.left, target) # 去右子树搜索 if root.val < target: return self.searchBST(root.right, target) return root ``` https://leetcode.cn/problems/search-in-a-binary-search-tree 的多语言解法👆 https://leetcode.cn/problems/search-insert-position 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int searchInsert(vector& nums, int target) { return left_bound(nums, target); } // 搜索左侧边界的二分算法 int left_bound(vector& nums, int target) { if (nums.size() == 0) return -1; int left = 0; int right = nums.size(); // 注意 while (left < right) { // 注意 int mid = left + (right - left) / 2; if (nums[mid] == target) { right = mid; } else if (nums[mid] < target) { left = mid + 1; } else if (nums[mid] > target) { right = mid; // 注意 } } return left; } }; ``` ```go // by chatGPT (go) import "fmt" func searchInsert(nums []int, target int) int { return leftBound(nums, target) } // leftBound is a binary search algorithm that searches for the left boundary func leftBound(nums []int, target int) int { if len(nums) == 0 { return -1 } left, right := 0, len(nums) // 注意 for left < right { // 注意 mid := left + (right-left)/2 if nums[mid] == target { right = mid } else if nums[mid] < target { left = mid + 1 } else if nums[mid] > target { right = mid // 注意 } } return left } ``` ```java // by labuladong (java) class Solution { public int searchInsert(int[] nums, int target) { return left_bound(nums, target); } // 搜索左侧边界的二分算法 int left_bound(int[] nums, int target) { if (nums.length == 0) return -1; int left = 0; int right = nums.length; // 注意 while (left < right) { // 注意 int mid = left + (right - left) / 2; if (nums[mid] == target) { right = mid; } else if (nums[mid] < target) { left = mid + 1; } else if (nums[mid] > target) { right = mid; // 注意 } } return left; } } ``` ```javascript // by chatGPT (javascript) /** * @param {number[]} nums * @param {number} target * @return {number} */ var searchInsert = function(nums, target) { return left_bound(nums, target); }; /** * 搜索左侧边界的二分算法 * @param {number[]} nums * @param {number} target * @return {number} */ var left_bound = function(nums, target) { if (nums.length == 0) return -1; let left = 0; let right = nums.length; // 注意 while (left < right) { // 注意 let mid = left + Math.floor((right - left) / 2); if (nums[mid] == target) { right = mid; } else if (nums[mid] < target) { left = mid + 1; } else if (nums[mid] > target) { right = mid; // 注意 } } return left; }; ``` ```python # by chatGPT (python) class Solution: def searchInsert(self, nums: List[int], target: int) -> int: return self.left_bound(nums, target) # 搜索左侧边界的二分算法 def left_bound(self, nums: List[int], target: int) -> int: if not nums: return -1 left = 0 right = len(nums) # 注意 while left < right: # 注意 mid = left + (right - left) // 2 if nums[mid] == target: right = mid elif nums[mid] < target: left = mid + 1 elif nums[mid] > target: right = mid # 注意 return left ``` https://leetcode.cn/problems/search-insert-position 的多语言解法👆 https://leetcode.cn/problems/seat-reservation-manager 的多语言解法👇 ```cpp // by chatGPT (cpp) class SeatManager { // 利用优先级队列自动排序,队头的元素就是最小的 priority_queue, greater> pq; public: SeatManager(int n) { // 初始化所有空闲座位 for (int i = 1; i <= n; i++) { pq.push(i); } } int reserve() { // 拿出队头元素(最小) int seat = pq.top(); pq.pop(); return seat; } void unreserve(int i) { pq.push(i); } }; ``` ```go // by chatGPT (go) import "container/heap" // 定义一个数组类型,并重写 Len(), Less(), Swap() 方法,实现堆 heap 的接口 type PriorityQueue []int func (pq PriorityQueue) Len() int { return len(pq) } func (pq PriorityQueue) Less(i, j int) bool { return pq[i] < pq[j] } func (pq PriorityQueue) Swap(i, j int) { pq[i], pq[j] = pq[j], pq[i] } func (pq *PriorityQueue) Push(x interface{}) { *pq = append(*pq, x.(int)) } func (pq *PriorityQueue) Pop() interface{} { old := *pq n := len(old) x := old[n-1] *pq = old[0 : n-1] return x } type SeatManager struct { pq PriorityQueue } func Constructor(n int) SeatManager { pq := make([]int, n) for i := 0; i < n; i++ { pq[i] = i + 1 } return SeatManager{pq: pq} } func (this *SeatManager) Reserve() int { return heap.Pop(&this.pq).(int) } func (this *SeatManager) Unreserve(seatNumber int) { heap.Push(&this.pq, seatNumber) } ``` ```java // by labuladong (java) class SeatManager { // 利用优先级队列自动排序,队头的元素就是最小的 PriorityQueue pq = new PriorityQueue<>(); public SeatManager(int n) { // 初始化所有空闲座位 for (int i = 1; i <= n; i++) { pq.offer(i); } } public int reserve() { // 拿出队头元素(最小) return pq.poll(); } public void unreserve(int i) { pq.offer(i); } } ``` ```javascript // by chatGPT (javascript) /** * @param {number} n */ var SeatManager = function(n) { /** * 利用优先队列(最小堆)自动排序,队头的元素就是最小的 * PriorityQueue 是 Java 里的一个内置类,JavaScript 中没有 * 可以手写实现一个最小堆,也可以转换为数组,使用数组 API 来模拟优先队列 */ this.pq = []; for (let i = 1; i <= n; i++) { this.pq.push(i); } this.pq.sort((a, b) => a - b); }; /** * @return {number} */ SeatManager.prototype.reserve = function() { /** * 拿出队头元素(最小) */ return this.pq.shift(); }; /** * @param {number} i * @return {void} */ SeatManager.prototype.unreserve = function(i) { this.pq.push(i); this.pq.sort((a, b) => a - b); }; /** * Your SeatManager object will be instantiated and called as such: * var obj = new SeatManager(n) * var param_1 = obj.reserve() * obj.unreserve(i) */ ``` ```python # by chatGPT (python) import heapq class SeatManager: # 利用优先级队列自动排序,队头的元素就是最小的 def __init__(self, n: int): self.pq = [i for i in range(1,n+1)] heapq.heapify(self.pq) def reserve(self) -> int: # 拿出队头元素(最小) return heapq.heappop(self.pq) def unreserve(self, seatNumber: int) -> None: heapq.heappush(self.pq, seatNumber) ``` https://leetcode.cn/problems/seat-reservation-manager 的多语言解法👆 https://leetcode.cn/problems/serialize-and-deserialize-binary-tree 的多语言解法👇 ```cpp // by chatGPT (cpp) class Codec { public: string SEP = ","; string NULL_STR = "#"; /* 主函数,将二叉树序列化为字符串 */ string serialize(TreeNode* root) { if(!root) return string(NULL_STR) + SEP; /* 前序遍历位置 */ string res = to_string(root->val) + SEP; /******************/ res += serialize(root->left); res += serialize(root->right); return res; } /* 主函数,将字符串反序列化为二叉树结构 */ TreeNode* deserialize(string data) { LinkedList nodes; string cur = ""; for(char &c: data) { if(c == SEP) { nodes.push_back(cur); cur = ""; } else cur += c; } return deserialize(nodes); } /* 辅助函数,通过 nodes 列表构造二叉树 */ TreeNode* deserialize(LinkedList &nodes) { if(nodes.empty()) return NULL_STRptr; /* 前序遍历位置 */ string first = nodes.front(); nodes.pop_front(); if(first == NULL_STR) return NULL_STRptr; TreeNode* root = new TreeNode(stoi(first)); /******************/ root->left = deserialize(nodes); root->right = deserialize(nodes); return root; } }; ``` ```go // by chatGPT (go) import ( "strconv" "strings" ) type Codec struct { SEP string NULL string } func Constructor() Codec { return Codec{ SEP: ",", NULL: "#", } } /* 辅助函数,将二叉树存入 StringBuilder */ func (this *Codec) serialize(root *TreeNode, sb *strings.Builder) { if root == nil { sb.WriteString(this.NULL) sb.WriteString(this.SEP) return } /******前序遍历位置******/ sb.WriteString(strconv.Itoa(root.Val)) sb.WriteString(this.SEP) /***********************/ this.serialize(root.Left, sb) this.serialize(root.Right, sb) } /* 主函数,将二叉树序列化为字符串 */ func (this *Codec) serializeTree(root *TreeNode) string { sb := &strings.Builder{} this.serialize(root, sb) return sb.String() } /* 辅助函数,通过 nodes 列表构造二叉树 */ func (this *Codec) deserialize(nodes *[]string) *TreeNode { if len(*nodes) == 0 { return nil } /******前序遍历位置******/ // 列表最左侧就是根节点 first := (*nodes)[0] *nodes = (*nodes)[1:] if first == this.NULL { return nil } root := &TreeNode{ Val: atoi(first), } /***********************/ root.Left = this.deserialize(nodes) root.Right = this.deserialize(nodes) return root } /* 主函数,将字符串反序列化为二叉树结构 */ func (this *Codec) deserializeTree(data string) *TreeNode { // 将字符串转化成列表 nodes := strings.Split(data, this.SEP) return this.deserialize(&nodes) } func atoi(s string) int { n, _ := strconv.Atoi(s) return n } ``` ```java // by labuladong (java) public class Codec { String SEP = ","; String 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; } } ``` ```javascript // by chatGPT (javascript) /** * @constructor */ var Codec = function() { // 用于连接节点字符串的分隔符 this.SEP = ","; // 空节点的表示 this.NULL = "#"; }; /** * Encodes a tree to a single string. * * @param {TreeNode} root * @return {string} */ Codec.prototype.serialize = function(root) { var sb = []; this.serializeHelper(root, sb); return sb.join(""); }; /** * 将字符串反序列化为二叉树结构 * Decodes your encoded data to tree. * * @param {string} data * @return {TreeNode} */ Codec.prototype.deserialize = function(data) { // 将字符串转化成列表 var nodes = data.split(this.SEP); return this.deserializeHelper(nodes); }; Codec.prototype.serializeHelper = function(root, sb) { if (root === null) { sb.push(this.NULL, this.SEP); return; } // 前序遍历位置 sb.push(root.val, this.SEP); this.serializeHelper(root.left, sb); this.serializeHelper(root.right, sb); }; Codec.prototype.deserializeHelper = function(nodes) { if (nodes.length === 0) return null; // 前序遍历位置:列表最左侧就是根节点 var val = nodes.shift(); if (val === this.NULL) return null; var root = new TreeNode(parseInt(val)); root.left = this.deserializeHelper(nodes); root.right = this.deserializeHelper(nodes); return root; }; ``` ```python # by chatGPT (python) class Codec: def __init__(self): self.SEP = ',' self.NULL = '#' # 将二叉树序列化为字符串 def serialize(self, root: TreeNode) -> str: sb = [] self._serialize(root, sb) return ''.join(sb) # 将二叉树存入列表 def _serialize(self, root, sb): if not root: sb.append(self.NULL).append(self.SEP) return # 前序遍历位置 sb.append(str(root.val)).append(self.SEP) self._serialize(root.left, sb) self._serialize(root.right, sb) # 将字符串反序列化为二叉树结构 def deserialize(self, data: str) -> TreeNode: # 将字符串转化成列表 nodes = data.split(self.SEP) return self._deserialize(nodes) # 通过 nodes 列表构造二叉树 def _deserialize(self, nodes): if not nodes: return None # 前序遍历位置 val = nodes.pop(0) if val == self.NULL: return None root = TreeNode(int(val)) root.left = self._deserialize(nodes) root.right = self._deserialize(nodes) return root ``` https://leetcode.cn/problems/serialize-and-deserialize-binary-tree 的多语言解法👆 https://leetcode.cn/problems/set-mismatch 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: vector findErrorNums(vector& nums) { int n=nums.size(); int dup=-1; for(int i=0;i0) // 将索引转换成元素 missing=i+1; return {dup,missing}; } }; ``` ```go // by chatGPT (go) func findErrorNums(nums []int) []int { n := len(nums) dup := -1 for i := 0; i < n; i++ { // 现在的元素是从 1 开始的 index := abs(nums[i]) - 1 if nums[index] < 0 { dup = abs(nums[i]) } else { nums[index] *= -1 } } missing := -1 for i := 0; i < n; i++ { if nums[i] > 0 { // 将索引转换成元素 missing = i + 1 } } return []int{dup, missing} } func abs(a int) int { if a < 0 { return -a } return a } ``` ```java // by labuladong (java) class Solution { public int[] findErrorNums(int[] nums) { int n = nums.length; int dup = -1; for (int i = 0; i < n; i++) { // 现在的元素是从 1 开始的 int index = Math.abs(nums[i]) - 1; if (nums[index] < 0) dup = Math.abs(nums[i]); else nums[index] *= -1; } int missing = -1; for (int i = 0; i < n; i++) if (nums[i] > 0) // 将索引转换成元素 missing = i + 1; return new int[]{dup, missing}; } } ``` ```javascript // by chatGPT (javascript) /** * @param {number[]} nums * @return {number[]} */ var findErrorNums = function(nums) { var n = nums.length; var dup = -1; for (var i = 0; i < n; i++) { // 现在的元素是从 1 开始的 var index = Math.abs(nums[i]) - 1; if (nums[index] < 0) dup = Math.abs(nums[i]); else nums[index] *= -1; } var missing = -1; for (var i = 0; i < n; i++) if (nums[i] > 0) // 将索引转换成元素 missing = i + 1; return [dup, missing]; }; ``` ```python # by chatGPT (python) class Solution: def findErrorNums(self, nums: List[int]) -> List[int]: n = len(nums) dup = -1 for i in range(n): # 现在的元素是从 1 开始的 index = abs(nums[i]) - 1 if nums[index] < 0: dup = abs(nums[i]) else: nums[index] *= -1 missing = -1 for i in range(n): if nums[i] > 0: # 将索引转换成元素 missing = i + 1 return [dup, missing] ``` https://leetcode.cn/problems/set-mismatch 的多语言解法👆 https://leetcode.cn/problems/shan-chu-lian-biao-de-jie-dian-lcof 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: ListNode* deleteNode(ListNode* head, int val) { // 存放删除 val 的链表 ListNode* dummy = new ListNode(-1); // q 指针负责生成结果链表 ListNode* q = dummy; // p 负责遍历原链表 ListNode* p = head; while (p != nullptr) { if (p->val != val) { // 把值不为 val 的节点接到结果链表上 q->next = p; q = q->next; } // 断开原链表中的每个节点的 next 指针 ListNode* temp = p->next; p->next = nullptr; p = temp; } return dummy->next; } }; ``` ```go // by chatGPT (go) func deleteNode(head *ListNode, val int) *ListNode { // 存放删除 val 的链表 dummy := &ListNode{ Val: -1, Next: nil, } // q 指针负责生成结果链表 q := dummy // p 负责遍历原链表 p := head for p != nil { if p.Val != val { // 把值不为 val 的节点接到结果链表上 q.Next = p q = q.Next } // 断开原链表中的每个节点的 next 指针 temp := p.Next p.Next = nil p = temp } return dummy.Next } ``` ```java // by labuladong (java) class Solution { public ListNode deleteNode(ListNode head, int val) { // 存放删除 val 的链表 ListNode dummy = new ListNode(-1); // q 指针负责生成结果链表 ListNode q = dummy; // p 负责遍历原链表 ListNode p = head; while (p != null) { if (p.val != val) { // 把值不为 val 的节点接到结果链表上 q.next = p; q = q.next; } // 断开原链表中的每个节点的 next 指针 ListNode temp = p.next; p.next = null; p = temp; } return dummy.next; } } ``` ```javascript // by chatGPT (javascript) /** * @param {ListNode} head * @param {number} val * @return {ListNode} */ var deleteNode = function(head, val) { // 存放删除 val 的链表 let dummy = new ListNode(-1); // q 指针负责生成结果链表 let q = dummy; // p 负责遍历原链表 let p = head; while (p != null) { if (p.val != val) { // 把值不为 val 的节点接到结果链表上 q.next = p; q = q.next; } // 断开原链表中的每个节点的 next 指针 let temp = p.next; p.next = null; p = temp; } return dummy.next; }; ``` ```python # by chatGPT (python) # Definition for singly-linked list. # class ListNode: # def __init__(self, val=0, next=None): # self.val = val # self.next = next class Solution: def deleteNode(self, head: ListNode, val: int) -> ListNode: # 存放删除 val 的链表 dummy = ListNode(-1) # q 指针负责生成结果链表 q = dummy # p 负责遍历原链表 p = head while p: if p.val != val: # 把值不为 val 的节点接到结果链表上 q.next = p q = q.next # 断开原链表中的每个节点的 next 指针 temp = p.next p.next = None p = temp return dummy.next ``` https://leetcode.cn/problems/shan-chu-lian-biao-de-jie-dian-lcof 的多语言解法👆 https://leetcode.cn/problems/shortest-path-in-binary-matrix 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int shortestPathBinaryMatrix(vector>& grid) { int m = grid.size(), n = grid[0].size(); if (grid[0][0] == 1 || grid[m - 1][n - 1] == 1) { return -1; } queue> q; // 需要记录走过的路径,避免死循环 vector> visited(m, vector(n, false)); // 初始化队列,从 (0, 0) 出发 q.push({0, 0}); visited[0][0] = true; int pathLen = 1; // 执行 BFS 算法框架,从值为 0 的坐标开始向八个方向扩散 vector> dirs = { {0, 1}, {0, -1}, {1, 0}, {-1, 0}, {1, 1}, {1, -1}, {-1, 1}, {-1, -1} }; while (!q.empty()) { int sz = q.size(); for (int __ = 0; __ < sz; __++) { auto cur = q.front(); q.pop(); int x = cur.first, y = cur.second; if (x == m - 1 && y == n - 1) { return pathLen; } // 向八个方向扩散 for (int i = 0; i < 8; i++) { int nextX = x + dirs[i][0]; int nextY = y + dirs[i][1]; // 确保相邻的这个坐标没有越界且值为 0 且之前没有走过 if (nextX >= 0 && nextX < m && nextY >= 0 && nextY < n && grid[nextX][nextY] == 0 && !visited[nextX][nextY]) { q.push({nextX, nextY}); visited[nextX][nextY] = true; } } } pathLen++; } return -1; } }; ``` ```go // by chatGPT (go) func shortestPathBinaryMatrix(grid [][]int) int { m, n := len(grid), len(grid[0]) if grid[0][0] == 1 || grid[m-1][n-1] == 1 { return -1 } q := make([][]int, 0) // 需要记录走过的路径,避免死循环 visited := make([][]bool, m) for i := 0; i < m; i++ { visited[i] = make([]bool, n) } // 初始化队列,从 (0, 0) 出发 q = append(q, []int{0, 0}) visited[0][0] = true pathLen := 1 // 执行 BFS 算法框架,从值为 0 的坐标开始向八个方向扩散 dirs := [][]int{ {0, 1}, {0, -1}, {1, 0}, {-1, 0}, {1, 1}, {1, -1}, {-1, 1}, {-1, -1}, } for len(q) > 0 { sz := len(q) for i := 0; i < sz; i++ { cur := q[i] x, y := cur[0], cur[1] if x == m-1 && y == n-1 { return pathLen } // 向八个方向扩散 for j := 0; j < 8; j++ { nextX := x + dirs[j][0] nextY := y + dirs[j][1] // 确保相邻的这个坐标没有越界且值为 0 且之前没有走过 if nextX >= 0 && nextX < m && nextY >= 0 && nextY < n && grid[nextX][nextY] == 0 && !visited[nextX][nextY] { q = append(q, []int{nextX, nextY}) visited[nextX][nextY] = true } } } pathLen++ q = q[sz:] } return -1 } ``` ```java // by labuladong (java) class Solution { 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; } Queue q = new LinkedList<>(); // 需要记录走过的路径,避免死循环 boolean[][] visited = new boolean[m][n]; // 初始化队列,从 (0, 0) 出发 q.offer(new int[]{0, 0}); visited[0][0] = true; int pathLen = 1; // 执行 BFS 算法框架,从值为 0 的坐标开始向八个方向扩散 int[][] dirs = new int[][]{ {0, 1}, {0, -1}, {1, 0}, {-1, 0}, {1, 1}, {1, -1}, {-1, 1}, {-1, -1} }; while (!q.isEmpty()) { int sz = q.size(); for (int __ = 0; __ < sz; __++) { int[] cur = q.poll(); int x = cur[0], y = cur[1]; if (x == m - 1 && y == n - 1) { return pathLen; } // 向八个方向扩散 for (int i = 0; i < 8; i++) { int nextX = x + dirs[i][0]; int nextY = y + dirs[i][1]; // 确保相邻的这个坐标没有越界且值为 0 且之前没有走过 if (nextX >= 0 && nextX < m && nextY >= 0 && nextY < n && grid[nextX][nextY] == 0 && !visited[nextX][nextY]) { q.offer(new int[]{nextX, nextY}); visited[nextX][nextY] = true; } } } pathLen++; } return -1; } } ``` ```javascript // by chatGPT (javascript) /** * @param {number[][]} grid * @return {number} */ var shortestPathBinaryMatrix = function(grid) { const m = grid.length, n = grid[0].length; if (grid[0][0] === 1 || grid[m - 1][n - 1] === 1) { return -1; } const q = []; // 需要记录走过的路径,避免死循环 const visited = new Array(m).fill(0).map(() => new Array(n).fill(false)); // 初始化队列,从 (0, 0) 出发 q.push([0, 0]); visited[0][0] = true; let pathLen = 1; // 执行 BFS 算法框架,从值为 0 的坐标开始向八个方向扩散 const dirs = [ [0, 1], [0, -1], [1, 0], [-1, 0], [1, 1], [1, -1], [-1, 1], [-1, -1] ]; while (q.length) { const sz = q.length; for (let __ = 0; __ < sz; __++) { const cur = q.shift(); const x = cur[0], y = cur[1]; if (x === m - 1 && y === n - 1) { return pathLen; } // 向八个方向扩散 for (let i = 0; i < 8; i++) { const nextX = x + dirs[i][0]; const nextY = y + dirs[i][1]; // 确保相邻的这个坐标没有越界且值为 0 且之前没有走过 if (nextX >= 0 && nextX < m && nextY >= 0 && nextY < n && grid[nextX][nextY] === 0 && !visited[nextX][nextY]) { q.push([nextX, nextY]); visited[nextX][nextY] = true; } } } pathLen++; } return -1; }; ``` ```python # by chatGPT (python) from collections import deque class Solution: def shortestPathBinaryMatrix(self, grid: List[List[int]]) -> int: # 获取矩阵大小 m, n = len(grid), len(grid[0]) # 如果起点或终点为障碍物,则无法到达终点 if grid[0][0] == 1 or grid[m - 1][n - 1] == 1: return -1 # 初始化队列、visited数组 q = deque() visited = [[False for _ in range(n)] for _ in range(m)] # 将起点加入队列,并标记为已访问 q.append([0, 0]) visited[0][0] = True # 记录路径长度 pathLen = 1 # 定义8个方向数组 dirs = [[0, 1], [0, -1], [1, 0], [-1, 0], [1, 1], [1, -1], [-1, 1], [-1, -1]] while q: sz = len(q) # 遍历当前层的所有结点 for _ in range(sz): cur = q.popleft() x, y = cur[0], cur[1] # 如果当前结点是目标点,则返回路径长度 if x == m - 1 and y == n - 1: return pathLen # 向八个方向扩散 for i in range(8): nextX = x + dirs[i][0] nextY = y + dirs[i][1] # 确保相邻的这个坐标没有越界且值为 0 且之前没有走过 if 0 <= nextX < m and 0 <= nextY < n and not visited[nextX][nextY] and grid[nextX][nextY] == 0: q.append([nextX, nextY]) visited[nextX][nextY] = True # 层数增加 pathLen += 1 # 如果无法到达终点,则返回 -1 return -1 ``` https://leetcode.cn/problems/shortest-path-in-binary-matrix 的多语言解法👆 https://leetcode.cn/problems/shu-ju-liu-zhong-de-zhong-wei-shu-lcof 的多语言解法👇 ```cpp // by chatGPT (cpp) class MedianFinder { private: priority_queue, greater> large; // 小顶堆 priority_queue, less> small; // 大顶堆 public: MedianFinder() {} /** ![](../pictures/中位数/2.jpeg) */ double findMedian() { if(large.size() < small.size()) { return small.top(); } else if(large.size() > small.size()) { return large.top(); } return (large.top() + small.top()) / 2.0; //如果元素一样多,中位数就是两个堆顶元素平均数 /** ![](../pictures/中位数/1.jpeg) */ } void addNum(int num) { if(small.size() >= large.size()) { small.push(num); large.push(small.top()); small.pop(); } else { large.push(num); small.push(large.top()); large.pop(); } } }; ``` ```go // by chatGPT (go) func addTwoNumbers(l1 *ListNode, l2 *ListNode) *ListNode { var carry int var result *ListNode var head *ListNode for l1 != nil || l2 != nil || carry != 0 { sum := 0 if l1 != nil { sum += l1.Val l1 = l1.Next } if l2 != nil { sum += l2.Val l2 = l2.Next } sum += carry carry = sum / 10 node := &ListNode{Val: sum % 10} // 求和的处理 if result == nil { result = node head = node } else { result.Next = node result = result.Next } } return head } ``` ```java // by labuladong (java) class MedianFinder { private PriorityQueue large; private PriorityQueue small; public MedianFinder() { // 小顶堆 large = new PriorityQueue<>(); // 大顶堆 small = new PriorityQueue<>((a, b) -> { return b - a; }); } public double findMedian() { /** ![](../pictures/中位数/2.jpeg) */ // 如果元素不一样多,多的那个堆的堆顶元素就是中位数 if (large.size() < small.size()) { return small.peek(); } else if (large.size() > small.size()) { return large.peek(); } // 如果元素一样多,两个堆堆顶元素的平均数是中位数 return (large.peek() + small.peek()) / 2.0; /** ![](../pictures/中位数/1.jpeg) */ } public void addNum(int num) { if (small.size() >= large.size()) { small.offer(num); large.offer(small.poll()); } else { large.offer(num); small.offer(large.poll()); } } } ``` ```javascript // by chatGPT (javascript) /** * 现有一个按非递减顺序排序的整数数组 nums。 * 请你删除重复出现的元素,使每个元素只出现一次。 * 返回删除后数组的新长度。 * 不要使用额外的数组空间,必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。 * * 给定 nums = [0,0,1,1,1,2,2,3,3,4], * * 返回 新长度5, 并且原数组 nums 的前五个元素为 0、1、2、3 和 4。 * * 你不需要考虑数组中超出新长度后面的元素。 */ /** * 思路很简单,因为数组已经是非递减排列的了,意思就是数组中之后会出现比当前下标的值还小的值都会在之前出现,所以我们可以考虑双指针法。 * 既然想到了双指针,那么要开一个变量储存数组中不重复的个数,这个变量命名为NonDuplicate即可。开始的时候它的值是1,因为第一个数一定是不重复的。 * 随后,我们遍历数组,比较i和i+1位置的值: * if(nums[i]!==nums[i+1]),则说明num[i+1]是不重复的,此时就将nums[j]赋值为nums[i+1], 非重复数的个数NonDuplicate++ * 最后再把nums数组中前NonDuplicate个数赋值为nums中各个元素,最后NonDuplicate就是数组中不重复的个数。 * 时间复杂度:O(n),因为数组只遍历了一次。 * 空间复杂度:O(1),没有利用额外的空间 */ var removeDuplicates = function(nums) { let NonDuplicate =1; let n = nums.length; let j=0; for(let i=0; i float: """ ![](../pictures/中位数/2.jpeg) """ # 如果元素不一样多,多的那个堆的堆顶元素就是中位数 if self.large.qsize() < self.small.qsize(): return self.small.queue[0] elif self.large.qsize() > self.small.qsize(): return self.large.queue[0] # 如果元素一样多,两个堆堆顶元素的平均数是中位数 return (self.large.queue[0] + self.small.queue[0]) / 2.0 """ ![](../pictures/中位数/1.jpeg) """ def addNum(self, num: int) -> None: if self.small.qsize() >= self.large.qsize(): self.small.put(num) self.large.put(self.small.get()) else: self.large.put(num) self.small.put(self.large.get()) ``` https://leetcode.cn/problems/shu-ju-liu-zhong-de-zhong-wei-shu-lcof 的多语言解法👆 https://leetcode.cn/problems/shu-zhi-de-zheng-shu-ci-fang-lcof 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: double myPow(double a, int k) { if (k == 0) return 1; if (k == INT_MIN) { // 把 k 是 INT_MIN 的情况单独拿出来处理 // 避免 -k 整型溢出 return myPow(1 / a, -(k + 1)) / a; } if (k < 0) { return myPow(1 / a, -k); } if (k % 2 == 1) { // k 是奇数 return (a * myPow(a, k - 1)); } else { // k 是偶数 double sub = myPow(a, k / 2); return (sub * sub); } } }; ``` ```go // by chatGPT (go) func myPow(a float64, k int) float64 { if k == 0 { // 如果 k 等于 0,直接返回 1 return 1 } if k == -2147483648 { // 把 k 是 INT_MIN 的情况单独拿出来处理 // 避免 -k 整型溢出 return myPow(1 / a, -(k + 1)) / a } if k < 0 { // 如果 k 小于 0,递归转换成 1 / a 的相反数 return myPow(1 / a, -k) } if k % 2 == 1 { // 如果 k 是奇数,递归运算 k - 1 return a * myPow(a, k - 1) } else { // 如果 k 是偶数,递归运算 k / 2 sub := myPow(a, k / 2) return sub * sub } } ``` ```java // by labuladong (java) class Solution { public double myPow(double a, int k) { if (k == 0) return 1; if (k == Integer.MIN_VALUE) { // 把 k 是 INT_MIN 的情况单独拿出来处理 // 避免 -k 整型溢出 return myPow(1 / a, -(k + 1)) / a; } if (k < 0) { return myPow(1 / a, -k); } if (k % 2 == 1) { // k 是奇数 return (a * myPow(a, k - 1)); } else { // k 是偶数 double sub = myPow(a, k / 2); return (sub * sub); } } } ``` ```javascript // by chatGPT (javascript) /** * @param {number} a * @param {number} k * @return {number} */ var myPow = function(a, k) { if (k === 0) return 1; if (k === -2147483648) { // 把 k 是 INT_MIN 的情况单独拿出来处理 // 避免 -k 整型溢出 return myPow(1 / a, -(k + 1)) / a; } if (k < 0) { return myPow(1 / a, -k); } if (k % 2 === 1) { // k 是奇数 return (a * myPow(a, k - 1)); } else { // k 是偶数 var sub = myPow(a, k / 2); return (sub * sub); } }; ``` ```python # by chatGPT (python) class Solution: def myPow(self, a: float, k: int) -> float: if k == 0: return 1 if k == -2147483648: # k 是 INT_MIN 的情况单独拿出来处理 # 避免 -k 整型溢出 return self.myPow(1 / a, -(k + 1)) / a if k < 0: return self.myPow(1 / a, -k) if k % 2 == 1: # k 是奇数 return a * self.myPow(a, k - 1) else: # k 是偶数 sub = self.myPow(a, k // 2) return sub * sub ``` https://leetcode.cn/problems/shu-zhi-de-zheng-shu-ci-fang-lcof 的多语言解法👆 https://leetcode.cn/problems/shu-zi-xu-lie-zhong-mou-yi-wei-de-shu-zi-lcof 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int findNthDigit(int n) { if (n == 0) { return 0; } return findNthDigit_100(n); } // 第 400 题的解法代码 int findNthDigit_400(int n) { // 位数(一位数,两位数...) int digit = 1; // 1,10,100, 1000 这样的后缀 long base = 1; while (n > 9 * base * digit) { n -= 9 * base * digit; base *= 10; digit++; } // 此时假设 base = 1000,那么说明 n 是 100~999 中的某个三位数的某一位 // 哪个三位数呢?这样算: long val = base + (n - 1) / digit; // 是这个三位数的第几位呢?这样算: int index = (n - 1) % digit; // 怎么把 val 的第 index 这一位数字抠出来呢?这样算: return (to_string(val))[index] - '0'; } int findNthDigit_100(int n) { // 按位计数是从 0 开始的,故需先 -1 n--; for (int bit = 1; bit <= 11; bit++) { int firstNum = pow(10, bit - 1); int lastNum = pow(10, bit) - 1; int bitNums = lastNum - firstNum + 1; int totalNums = bitNums * bit; if (n < totalNums) { int numTimes = n / bit; int numIndex = n % bit; int targetNum = firstNum + numTimes; string targetStr = to_string(targetNum); return targetStr[numIndex] - '0'; } n -= totalNums; } return -1; } }; ``` ```go // by chatGPT (go) import "strconv" func findNthDigit(n int) int { if n == 0 { return 0 } return findNthDigit400(n) } // 第 400 题的解法代码 func findNthDigit400(n int) int { // 位数(一位数,两位数...) digit := 1 // 1,10,100, 1000 这样的后缀 base := 1 for n > 9*base*digit { n -= 9 * base * digit base *= 10 digit++ } // 此时假设 base = 1000,那么说明 n 是 100~999 中的某个三位数的某一位 // 哪个三位数呢?这样算: val := base + (n-1)/digit // 是这个三位数的第几位呢?这样算: index := (n - 1) % digit // 怎么把 val 的第 index 这一位数字抠出来呢?这样算: return int(strconv.Itoa(val)[index] - '0') } ``` ```java // by labuladong (java) class Solution { public int findNthDigit(int n) { if (n == 0) { return 0; } return findNthDigit_100(n); } // 第 400 题的解法代码 int findNthDigit_400(int n) { // 位数(一位数,两位数...) int digit = 1; // 1,10,100, 1000 这样的后缀 long base = 1; while (n > 9 * base * digit) { n -= 9 * base * digit; base *= 10; digit++; } // 此时假设 base = 1000,那么说明 n 是 100~999 中的某个三位数的某一位 // 哪个三位数呢?这样算: long val = base + (n - 1) / digit; // 是这个三位数的第几位呢?这样算: int index = (n - 1) % digit; // 怎么把 val 的第 index 这一位数字抠出来呢?这样算: return ("" + val).charAt(index) - '0'; } } ``` ```javascript // by chatGPT (javascript) /** * @param {number} n * @return {number} */ var findNthDigit = function(n) { if (n == 0) { return 0; } return findNthDigit_100(n); }; // 第 400 题的解法代码 var findNthDigit_400 = function(n) { // 位数(一位数,两位数...) var digit = 1; // 1,10,100, 1000 这样的后缀 var base = 1; while (n > 9 * base * digit) { n -= 9 * base * digit; base *= 10; digit++; } // 此时假设 base = 1000,那么说明 n 是 100~999 中的某个三位数的某一位 // 哪个三位数呢?这样算: var val = base + Math.floor((n - 1) / digit); // 是这个三位数的第几位呢?这样算: var index = (n - 1) % digit; // 怎么把 val 的第 index 这一位数字抠出来呢?这样算: return parseInt(("" + val).charAt(index)); }; ``` ```python # by chatGPT (python) class Solution: def findNthDigit(self, n: int) -> int: if n == 0: return 0 return self.findNthDigit_400(n) # 第 400 题的解法代码 def findNthDigit_400(self, n: int) -> int: # 位数(一位数,两位数...) digit = 1 # 1,10,100, 1000 这样的后缀 base = 1 while n > 9 * base * digit: n -= 9 * base * digit base *= 10 digit += 1 # 此时假设 base = 1000,那么说明 n 是 100~999 中的某个三位数的某一位 # 哪个三位数呢?这样算: val = base + (n - 1) // digit # 是这个三位数的第几位呢?这样算: index = (n - 1) % digit # 怎么把 val 的第 index 这一位数字抠出来呢?这样算: return int(str(val)[index]) ``` https://leetcode.cn/problems/shu-zi-xu-lie-zhong-mou-yi-wei-de-shu-zi-lcof 的多语言解法👆 https://leetcode.cn/problems/shu-zu-zhong-chu-xian-ci-shu-chao-guo-yi-ban-de-shu-zi-lcof 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int majorityElement(vector& nums) { // 我们想寻找的那个众数 int target = 0; // 计数器(类比带电粒子例子中的带电性) int count = 0; for (int i = 0; i < nums.size(); i++) { if (count == 0) { // 当计数器为 0 时,假设 nums[i] 就是众数 target = nums[i]; // 众数出现了一次 count = 1; } else if (nums[i] == target) { // 如果遇到的是目标众数,计数器累加 count++; } else { // 如果遇到的不是目标众数,计数器递减 count--; } } // 回想带电粒子的例子 // 此时的 count 必然大于 0,此时的 target 必然就是目标众数 return target; } }; ``` ```go // by chatGPT (go) func majorityElement(nums []int) int { // 我们想寻找的那个众数 target := 0 // 计数器(类比带电粒子例子中的带电性) count := 0 for i := 0; i < len(nums); i++ { if count == 0 { // 当计数器为 0 时,假设 nums[i] 就是众数 target = nums[i] // 众数出现了一次 count = 1 } else if nums[i] == target { // 如果遇到的是目标众数,计数器累加 count++ } else { // 如果遇到的不是目标众数,计数器递减 count-- } } // 回想带电粒子的例子 // 此时的 count 必然大于 0,此时的 target 必然就是目标众数 return target } ``` ```java // by labuladong (java) class Solution { public int majorityElement(int[] nums) { // 我们想寻找的那个众数 int target = 0; // 计数器(类比带电粒子例子中的带电性) int count = 0; for (int i = 0; i < nums.length; i++) { if (count == 0) { // 当计数器为 0 时,假设 nums[i] 就是众数 target = nums[i]; // 众数出现了一次 count = 1; } else if (nums[i] == target) { // 如果遇到的是目标众数,计数器累加 count++; } else { // 如果遇到的不是目标众数,计数器递减 count--; } } // 回想带电粒子的例子 // 此时的 count 必然大于 0,此时的 target 必然就是目标众数 return target; } } ``` ```javascript // by chatGPT (javascript) var majorityElement = function(nums) { // 我们想寻找的那个众数 let target = 0; // 计数器(类比带电粒子例子中的带电性) let count = 0; for (let i = 0; i < nums.length; i++) { if (count == 0) { // 当计数器为 0 时,假设 nums[i] 就是众数 target = nums[i]; // 众数出现了一次 count = 1; } else if (nums[i] == target) { // 如果遇到的是目标众数,计数器累加 count++; } else { // 如果遇到的不是目标众数,计数器递减 count--; } } // 回想带电粒子的例子 // 此时的 count 必然大于 0,此时的 target 必然就是目标众数 return target; }; ``` ```python # by chatGPT (python) class Solution: def majorityElement(self, nums: List[int]) -> int: # 我们想寻找的那个众数 target = 0 # 计数器(类比带电粒子例子中的带电性) count = 0 for i in range(len(nums)): if count == 0: # 当计数器为 0 时,假设 nums[i] 就是众数 target = nums[i] # 众数出现了一次 count = 1 elif nums[i] == target: # 如果遇到的是目标众数,计数器累加 count += 1 else: # 如果遇到的不是目标众数,计数器递减 count -= 1 # 回想带电粒子的例子 # 此时的 count 必然大于 0,此时的 target 必然就是目标众数 return target ``` https://leetcode.cn/problems/shu-zu-zhong-chu-xian-ci-shu-chao-guo-yi-ban-de-shu-zi-lcof 的多语言解法👆 https://leetcode.cn/problems/shu-zu-zhong-de-ni-xu-dui-lcof 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int reversePairs(vector& nums) { if (nums.size() == 0) { return 0; } // 执行归并排序 sort(nums); return count; } void sort(vector& nums) { temp.resize(nums.size()); sort(nums, 0, nums.size() - 1); } // 归并排序 void sort(vector& nums, int lo, int hi) { if (lo == hi) { return; } int mid = lo + (hi - lo) / 2; sort(nums, lo, mid); sort(nums, mid + 1, hi); merge(nums, lo, mid, hi); } // 记录「翻转对」的个数 int count = 0; void merge(vector& nums, int lo, int mid, int hi) { for (int i = lo; i <= hi; i++) { temp[i] = nums[i]; } // 进行效率优化,维护左闭右开区间 [mid+1, end) 中的元素乘 2 小于 nums[i] // 为什么 end 是开区间?因为这样的话可以保证初始区间 [mid+1, mid+1) 是一个空区间 int end = mid + 1; for (int i = lo; i <= mid; i++) { // nums 中的元素可能较大,乘 2 可能溢出,所以转化成 long while (end <= hi && nums[i] > nums[end]) { end++; } count += end - (mid + 1); } // 数组双指针技巧,合并两个有序数组 int i = lo, j = mid + 1; for (int p = lo; p <= hi; p++) { if (i == mid + 1) { nums[p] = temp[j++]; } else if (j == hi + 1) { nums[p] = temp[i++]; } else if (temp[i] > temp[j]) { nums[p] = temp[j++]; } else { nums[p] = temp[i++]; } } } private: vector temp; }; ``` ```go // by chatGPT (go) func reversePairs(nums []int) int { if len(nums) == 0 { return 0 } // 执行归并排序并统计「翻转对」的个数 return sort(nums) } func sort(nums []int) int { var temp = make([]int, len(nums)) var count = 0 // 统计翻转对个数 sortHelper(nums, 0, len(nums) - 1, temp, &count) return count } // 归并排序 func sortHelper(nums []int, lo, hi int, temp []int, count *int) { if lo == hi { return } mid := lo + (hi - lo) / 2 sortHelper(nums, lo, mid, temp, count) sortHelper(nums, mid + 1, hi, temp, count) merge(nums, lo, mid, hi, temp, count) } func merge(nums []int, lo, mid, hi int, temp []int, count *int) { // 先将 nums 中 [lo, hi] 的数复制到 temp 中 for i := lo; i <= hi; i++ { temp[i] = nums[i] } // 进行效率优化,维护左闭右开区间 [mid+1, end) 中的元素乘 2 小于 nums[i] // 为什么 end 是开区间?因为这样的话可以保证初始区间 [mid+1, mid+1) 是一个空区间 end := mid + 1 for i := lo; i <= mid; i++ { // nums 中的元素可能较大,乘 2 可能溢出,所以转化成 int64 for end <= hi && int64(nums[i]) > int64(nums[end]) * 2 { end++ } *count += end - (mid + 1) } // 数组双指针技巧,合并两个有序数组 i, j := lo, mid + 1 for p := lo; p <= hi; p++ { if i == mid + 1 { nums[p] = temp[j] j++ } else if j == hi + 1 { nums[p] = temp[i] i++ } else if temp[i] > temp[j] { nums[p] = temp[j] j++ } else { nums[p] = temp[i] i++ } } } ``` ```java // by labuladong (java) class Solution { public int reversePairs(int[] nums) { if (nums.length == 0) { return 0; } // 执行归并排序 sort(nums); return count; } private int[] temp; public void sort(int[] nums) { temp = new int[nums.length]; sort(nums, 0, nums.length - 1); } // 归并排序 private void sort(int[] nums, int lo, int hi) { if (lo == hi) { return; } int mid = lo + (hi - lo) / 2; sort(nums, lo, mid); sort(nums, mid + 1, hi); merge(nums, lo, mid, hi); } // 记录「翻转对」的个数 private int count = 0; private void merge(int[] nums, int lo, int mid, int hi) { for (int i = lo; i <= hi; i++) { temp[i] = nums[i]; } // 进行效率优化,维护左闭右开区间 [mid+1, end) 中的元素乘 2 小于 nums[i] // 为什么 end 是开区间?因为这样的话可以保证初始区间 [mid+1, mid+1) 是一个空区间 int end = mid + 1; for (int i = lo; i <= mid; i++) { // nums 中的元素可能较大,乘 2 可能溢出,所以转化成 long while (end <= hi && nums[i] > nums[end]) { end++; } count += end - (mid + 1); } // 数组双指针技巧,合并两个有序数组 int i = lo, j = mid + 1; for (int p = lo; p <= hi; p++) { if (i == mid + 1) { nums[p] = temp[j++]; } else if (j == hi + 1) { nums[p] = temp[i++]; } else if (temp[i] > temp[j]) { nums[p] = temp[j++]; } else { nums[p] = temp[i++]; } } } } ``` ```javascript // by chatGPT (javascript) var reversePairs = function(nums) { if (nums.length == 0) { return 0; } // 执行归并排序 sort(nums); return count; }; function sort(nums) { var temp = new Array(nums.length); var count = new Array(1); sortUtil(nums, 0, nums.length - 1, temp, count); } // 归并排序 function sortUtil(nums, lo, hi, temp, count) { if (lo == hi) { return; } var mid = parseInt(lo + (hi - lo) / 2); sortUtil(nums, lo, mid, temp, count); sortUtil(nums, mid + 1, hi, temp, count); merge(nums, lo, mid, hi, temp, count); } // 记录「翻转对」的个数 function merge(nums, lo, mid, hi, temp, count) { for (var i = lo; i <= hi; i++) { temp[i] = nums[i]; } // 进行效率优化,维护左闭右开区间 [mid+1, end) 中的元素乘 2 小于 nums[i] // 为什么 end 是开区间?因为这样的话可以保证初始区间 [mid+1, mid+1) 是一个空区间 var end = mid + 1; for (var i = lo; i <= mid; i++) { // nums 中的元素可能较大,乘 2 可能溢出,所以转化成 long while (end <= hi && nums[i] > nums[end]) { end++; } count[0] += end - (mid + 1); } // 数组双指针技巧,合并两个有序数组 var i = lo, j = mid + 1; for (var p = lo; p <= hi; p++) { if (i == mid + 1) { nums[p] = temp[j++]; } else if (j == hi + 1) { nums[p] = temp[i++]; } else if (temp[i] > temp[j]) { nums[p] = temp[j++]; } else { nums[p] = temp[i++]; } } } ``` ```python # by chatGPT (python) class Solution: def __init__(self): self.count = 0 self.tmp = [] # 包装方法 def reversePairs(self, nums: List[int]) -> int: if not nums: return 0 # 执行归并排序 self.ms(nums, 0, len(nums) - 1) return self.count # 归并排序 def ms(self, nums, left, right): if left >= right: return mid = (left + right) >> 1 self.ms(nums, left, mid) self.ms(nums, mid + 1, right) self.merge(nums, left, mid, right) # 合并两个有序数组 def merge(self, nums, left, mid, right): i, j = left, mid + 1 t = [] # 进行效率优化,维护左闭右开区间 [mid+1, end) 中的元素乘 2 小于 nums[i] # 为什么 end 是开区间?因为这样的话可以保证初始区间 [mid+1, mid+1) 是一个空区间 end = mid + 1 while i <= mid and j <= right: if nums[i] <= nums[j]: t.append(nums[i]) i += 1 else: t.append(nums[j]) j += 1 self.count += (mid - i + 1) while i <= mid: t.append(nums[i]) i += 1 while j <= right: t.append(nums[j]) j += 1 nums[left:right + 1] = t ``` https://leetcode.cn/problems/shu-zu-zhong-de-ni-xu-dui-lcof 的多语言解法👆 https://leetcode.cn/problems/shu-zu-zhong-zhong-fu-de-shu-zi-lcof 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int findRepeatNumber(vector& nums) { for (int num : nums) { if (nums[abs(num)] < 0) { // 之前已经把对应索引的元素变成负数了, // 这说明 num 重复出现了两次 return abs(num); } else { // 把索引 num 的元素置为负数 nums[abs(num)] *= -1; } } // 如果没有在 for 循环中返回,说明重复的那个元素是 0 return 0; } }; ``` ```go // by chatGPT (go) func findRepeatNumber(nums []int) int { for _, num := range nums { if nums[abs(num)] < 0 { // 之前已经把对应索引的元素变成负数了, // 这说明 num 重复出现了两次 return abs(num) } else { // 把索引 num 的元素置为负数 nums[abs(num)] *= -1 } } // 如果没有在 for 循环中返回,说明重复的那个元素是 0 return 0 } func abs(num int) int { if num < 0 { return -num } return num } ``` ```java // by labuladong (java) class Solution { public int findRepeatNumber(int[] nums) { // 先把 nums 数组中的所有元素都加一,避免 0 的影响 for (int i = 0; i < nums.length; i++) { nums[i] = nums[i] + 1; } for (int num : nums) { // 该元素对应的索引 int index = Math.abs(num) - 1; if (nums[index] < 0) { // 之前已经把对应索引的元素变成负数了, // 这说明 num 重复出现了两次 // 注意结果要减一 return Math.abs(num) - 1; } else { // 把索引 num 的元素置为负数 nums[index] *= -1; } } return -1; } } ``` ```javascript // by chatGPT (javascript) /** * @param {number[]} nums * @return {number} */ var findRepeatNumber = function(nums) { for (let num of nums) { if (nums[Math.abs(num)] < 0) { // 之前已经把对应索引的元素变成负数了, // 这说明 num 重复出现了两次 return Math.abs(num); } else { // 把索引 num 的元素置为负数 nums[Math.abs(num)] *= -1; } } // 如果没有在 for 循环中返回,说明重复的那个元素是 0 return 0; }; ``` ```python # by chatGPT (python) class Solution: def findRepeatNumber(self, nums: List[int]) -> int: for num in nums: if nums[abs(num)] < 0: # 之前已经把对应索引的元素变成负数了, # 这说明 num 重复出现了两次 return abs(num) else: # 把索引 num 的元素置为负数 nums[abs(num)] *= -1 # 如果没有在 for 循环中返回,说明重复的那个元素是 0 return 0 ``` https://leetcode.cn/problems/shu-zu-zhong-zhong-fu-de-shu-zi-lcof 的多语言解法👆 https://leetcode.cn/problems/simplify-path 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: string simplifyPath(string path) { vector parts; int start = 0; for (int i = 1; i <= path.size(); ++i) { if (path[i] == '/' || i == path.size()) { string part = path.substr(start + 1, i - start - 1); if (part == "" || part == ".") { // part 为空或为 . ,忽略 } else if (part == "..") { if (!parts.empty()) parts.pop_back(); } else { parts.push_back(part); } start = i; } } string res = ""; for (string part : parts) { res += "/" + part; } return res.empty() ? "/" : res; } }; ``` ```go // by chatGPT (go) import ( "strings" ) func simplifyPath(path string) string { parts := strings.Split(path, "/") stk := make([]string, 0) // 借助栈计算最终的文件夹路径 for _, part := range parts { if part == "" || part == "." { continue } if part == ".." { if len(stk) != 0 { stk = stk[:len(stk)-1] } } else { stk = append(stk, part) } } // 栈中存储的文件夹组成路径 res := "" for i := len(stk) - 1; i >= 0; i-- { res = "/" + stk[i] + res } if res == "" { res = "/" } return res } ``` ```java // by labuladong (java) 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; } } ``` ```javascript // by chatGPT (javascript) var simplifyPath = function(path) { var parts = path.split("/"); var stk = []; // 借助栈计算最终的文件夹路径 for (var i = 0; i < parts.length; i++) { var part = parts[i]; if (part === "" || part === ".") { continue; } if (part === "..") { if (stk.length !== 0) stk.pop(); continue; } stk.push(part); } // 栈中存储的文件夹组成路径 var res = ""; while (stk.length !== 0) { res = "/" + stk.pop() + res; } return res === "" ? "/" : res; }; ``` ```python # by chatGPT (python) class Solution: def simplifyPath(self, path: str) -> str: parts = path.split("/") stk = [] # 借助栈计算最终的文件夹路径 for part in parts: if part == '' or part == '.': continue if part == '..': if stk: stk.pop() continue stk.append(part) # 栈中存储的文件夹组成路径 res = "" while stk: res = '/' + stk.pop() + res return res if res else '/' ``` https://leetcode.cn/problems/simplify-path 的多语言解法👆 https://leetcode.cn/problems/single-number 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int singleNumber(vector& nums) { int res = 0; for (int n : nums) { res ^= n; } return res; } }; ``` ```go // by chatGPT (go) /** * @param nums: List[int] * @return: int */ func singleNumber(nums []int) int { res := 0 for _, n := range nums { res ^= n } return res } ``` ```java // by labuladong (java) class Solution { public int singleNumber(int[] nums) { int res = 0; for (int n : nums) { res ^= n; } return res; } } ``` ```javascript // by chatGPT (javascript) /** * @param {number[]} nums * @return {number} */ var singleNumber = function(nums) { let res = 0; for (let n of nums) { res ^= n; } return res; }; ``` ```python # by chatGPT (python) class Solution: def singleNumber(self, nums: List[int]) -> int: res = 0 for n in nums: res ^= n return res ``` https://leetcode.cn/problems/single-number 的多语言解法👆 https://leetcode.cn/problems/single-threaded-cpu 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: vector getOrder(vector>& tasks) { int n = tasks.size(); // 把原始索引也添加上,方便后面排序用 vector> triples; for (int i = 0; i < n; i++) { triples.push_back({tasks[i][0], tasks[i][1], i}); } // 数组先按照任务的开始时间排序 sort(triples.begin(), triples.end(), [](auto a, auto b){ return a[0] < b[0]; }); // 按照任务的处理时间排序,如果处理时间相同,按照原始索引排序 auto cmp = [](vector& a, vector& b){ if (a[1] != b[1]) { // 比较处理时间 return a[1] > b[1]; } // 比较原始索引 return a[2] > b[2]; }; priority_queue, vector>, decltype(cmp)> pq(cmp); vector res; // 记录完成任务的时间线 int now = 0; int i = 0; while (res.size() < n) { if (!pq.empty()) { // 完成队列中的一个任务 vector triple = pq.top(); pq.pop(); res.push_back(triple[2]); // 每完成一个任务,就要推进时间线 now += triple[1]; } else if (i < n && triples[i][0] > now) { // 队列为空可能因为还没到开始时间, // 直接把时间线推进到最近任务的开始时间 now = triples[i][0]; } // 由于时间线的推进,会产生可以开始执行的任务 for (; i < n && triples[i][0] <= now; i++) { pq.push(triples[i]); } } return res; } }; ``` ```go // by chatGPT (go) // 定义任务 type task struct { // 入队时间,即开始时间 enqueTime int // 处理时间 interval int // 原始索引 id int } // 自定义优先队列,按照任务处理时间排序,如果处理时间相同,按照原始索引排序 type queue []task // 比较器,如果想要当前元素在队首,那么返回 true func (q queue) Less(i, j int) bool { if q[i].interval == q[j].interval { return q[i].id < q[j].id } return q[i].interval < q[j].interval } // 计算队列长度 func (q queue) Len() int { return len(q) } // 交换元素 func (q queue) Swap(i, j int) { q[i], q[j] = q[j], q[i] } // 入队 func (q *queue) Push(p interface{}) { *q = append(*q, p.(task)) } // 出队 func (q *queue) Pop() interface{} { top := (*q)[len(*q)-1] *q = (*q)[:len(*q)-1] return top } func getOrder(tasks [][]int) []int { // 任务数量 n := len(tasks) // 把原始索引也添加上,方便后面排序用 tasksWithID := make([]task, n) for i := range tasks { tasksWithID[i] = task{tasks[i][0], tasks[i][1], i} } // 数组先按照任务的开始时间排序 sort.Slice(tasksWithID, func(i, j int) bool { return tasksWithID[i].enqueTime < tasksWithID[j].enqueTime }) // 任务队列 q := make(queue, 0, n) // 结果数组 res := make([]int, 0, n) // 记录现在的时间线 i, now := 0, 0 // 当前还未完成的任务数目 for len(res) < n { // 时间线被推进,产生新任务 for ; i < n && tasksWithID[i].enqueTime <= now; i++ { q = append(q, tasksWithID[i]) } // 如果当前队列为空,因为时间线还未到第一个任务的开始时间,直接将时间线推进到第一个任务的开始时间,并将 i 移向下一个任务 if len(q) == 0 { now = tasksWithID[i].enqueTime i++ } else { // 处理队列中任务 heap.Init(&q) top := heap.Pop(&q).(task) res = append(res, top.id) now += top.interval } } return res } ``` ```java // by labuladong (java) class Solution { public int[] getOrder(int[][] tasks) { int n = tasks.length; // 把原始索引也添加上,方便后面排序用 ArrayList triples = new ArrayList<>(); for (int i = 0; i < tasks.length; i++) { triples.add(new int[]{tasks[i][0], tasks[i][1], i}); } // 数组先按照任务的开始时间排序 triples.sort((a, b) -> { return a[0] - b[0]; }); // 按照任务的处理时间排序,如果处理时间相同,按照原始索引排序 PriorityQueue pq = new PriorityQueue<>((a, b) -> { if (a[1] != b[1]) { // 比较处理时间 return a[1] - b[1]; } // 比较原始索引 return a[2] - b[2]; }); ArrayList res = new ArrayList<>(); // 记录完成任务的时间线 int now = 0; int i = 0; while (res.size() < n) { if (!pq.isEmpty()) { // 完成队列中的一个任务 int[] triple = pq.poll(); res.add(triple[2]); // 每完成一个任务,就要推进时间线 now += triple[1]; } else if (i < n && triples.get(i)[0] > now) { // 队列为空可能因为还没到开始时间, // 直接把时间线推进到最近任务的开始时间 now = triples.get(i)[0]; } // 由于时间线的推进,会产生可以开始执行的任务 for (; i < n && triples.get(i)[0] <= now; i++) { pq.offer(triples.get(i)); } } // Java 语言特性,将 List 转化成 int[] 格式 int[] arr = new int[n]; for (int j = 0; j < n; j++) { arr[j] = res.get(j); } return arr; } } ``` ```javascript // by chatGPT (javascript) /** * @param {number[][]} tasks * @return {number[]} */ var getOrder = function(tasks) { const n = tasks.length; // 把原始索引也添加上,方便后面排序用 const triples = []; for (let i = 0; i < tasks.length; i++) { triples.push([tasks[i][0], tasks[i][1], i]); } // 数组先按照任务的开始时间排序 triples.sort((a, b) => { return a[0] - b[0]; }); // 按照任务的处理时间排序,如果处理时间相同,按照原始索引排序 const pq = new PriorityQueue((a, b) => { if (a[1] !== b[1]) { // 比较处理时间 return a[1] - b[1]; } // 比较原始索引 return a[2] - b[2]; }); const res = []; // 记录完成任务的时间线 let now = 0; let i = 0; while (res.length < n) { if (!pq.isEmpty()) { // 完成队列中的一个任务 const triple = pq.poll(); res.push(triple[2]); // 每完成一个任务,就要推进时间线 now += triple[1]; } else if (i < n && triples[i][0] > now) { // 队列为空可能因为还没到开始时间, // 直接把时间线推进到最近任务的开始时间 now = triples[i][0]; } // 由于时间线的推进,会产生可以开始执行的任务 for (; i < n && triples[i][0] <= now; i++) { pq.offer(triples[i]); } } // JavaScript语言特性,不需要转化成int[]格式 return res; }; class PriorityQueue { constructor(comparator) { this.heap = []; this.comparator = comparator; } isEmpty() { return this.heap.length === 0; } size() { return this.heap.length; } peek() { return this.isEmpty() ? null : this.heap[0]; } offer(item) { this.heap.push(item); this.siftUp(this.heap.length - 1); } poll() { if (this.isEmpty()) { return null; } const item = this.heap[0]; this.heap[0] = this.heap[this.heap.length - 1]; this.heap.pop(); this.siftDown(0); return item; } heapify() { if (this.isEmpty()) { return; } for (let i = Math.floor(this.size() / 2) - 1; i >= 0; i--) { this.siftDown(i); } } siftUp(index) { let parent = Math.floor((index - 1) / 2); while (index > 0 && this.comparator(this.heap[index], this.heap[parent]) < 0) { const tmp = this.heap[index]; this.heap[index] = this.heap[parent]; this.heap[parent] = tmp; index = parent; parent = Math.floor((index - 1) / 2); } } siftDown(index) { let child = index * 2 + 1; while (child < this.size()) { if (child + 1 < this.size() && this.comparator(this.heap[child + 1], this.heap[child]) < 0) { child++; } if (this.comparator(this.heap[child], this.heap[index]) < 0) { const tmp = this.heap[index]; this.heap[index] = this.heap[child]; this.heap[child] = tmp; index = child; child = index * 2 + 1; } else { break; } } } } ``` ```python # by chatGPT (python) class Solution: def getOrder(self, tasks: List[List[int]]) -> List[int]: n = len(tasks) # 把原始索引也添加上,方便后面排序用 triples = [] for i in range(n): triples.append([tasks[i][0], tasks[i][1], i]) # 数组先按照任务的开始时间排序 triples.sort(key=lambda a: a[0]) # 按照任务的处理时间排序,如果处理时间相同,按照原始索引排序 pq = [] heapq.heapify(pq) for a in triples: heapq.heappush(pq, (a[1], a[2])) res = [] # 记录完成任务的时间线 now = 0 while len(res) < n: if pq: # 完成队列中的一个任务 triple = heapq.heappop(pq) res.append(triple[1]) # 每完成一个任务,就要推进时间线 now += triple[0] elif triples[0][0] > now: # 队列为空可能因为还没到开始时间, # 直接把时间线推进到最近任务的开始时间 now = triples[0][0] else: # 由于时间线的推进,会产生可以开始执行的任务 while triples and triples[0][0] <= now: a = triples.pop(0) heapq.heappush(pq, (a[1], a[2])) # Java 语言特性,将 List 转化成 int[] 格式 return res ``` https://leetcode.cn/problems/single-threaded-cpu 的多语言解法👆 https://leetcode.cn/problems/sliding-puzzle 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int slidingPuzzle(vector>& board) { int m = 2, n = 3; string target = "123450"; // 将 2x3 的数组转化成字符串作为 BFS 的起点 string start = ""; for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { start += to_string(board[i][j]); } } // 记录一维字符串的相邻索引 int neighbor[6][3] = { {1, 3}, {0, 4, 2}, {1, 5}, {0, 4}, {3, 1, 5}, {4, 2} }; /******* BFS 算法框架开始 *******/ queue q; unordered_set visited; // 从起点开始 BFS 搜索 q.push(start); visited.insert(start); int step = 0; while (!q.empty()) { int sz = q.size(); for (int i = 0; i < sz; i++) { string cur = q.front(); q.pop(); // 判断是否达到目标局面 if (target == cur) { return step; } // 找到数字 0 的索引 int idx = 0; for (; cur[idx] != '0'; idx++) ; // 将数字 0 和相邻的数字交换位置 for (int j = 0; j < 3; j++) { int adj = neighbor[idx][j]; string new_board = swap(cur, adj, idx); // 防止走回头路 if (visited.find(new_board) == visited.end()) { q.push(new_board); visited.insert(new_board); } } } step++; } /******* BFS 算法框架结束 *******/ return -1; } string swap(string str, int i, int j) { char temp = str[i]; str[i] = str[j]; str[j] = temp; return str; } }; ``` ```go // by chatGPT (go) import "fmt" func slidingPuzzle(board [][]int) int { m, n := 2, 3 sb := make([]byte, 0, 6) target := "123450" // 将 2x3 的数组转化成字符串作为 BFS 的起点 for i := 0; i < m; i++ { for j := 0; j < n; j++ { sb = append(sb, byte(board[i][j]+'0')) } } start := string(sb) // 记录一维字符串的相邻索引 neighbor := [][]int{ {1, 3}, {0, 4, 2}, {1, 5}, {0, 4}, {3, 1, 5}, {4, 2}, } /******* BFS 算法框架开始 *******/ q := []string{start} visited := make(map[string]bool) // 从起点开始 BFS 搜索 visited[start] = true step := 0 for len(q) > 0 { sz := len(q) for i := 0; i < sz; i++ { cur := q[i] // 判断是否达到目标局面 if cur == target { return step } // 找到数字 0 的索引 idx := 0 for cur[idx] != '0' { idx++ } // 将数字 0 和相邻的数字交换位置 for _, adj := range neighbor[idx] { newBoard := swap([]byte(cur), adj, idx) // 防止走回头路 if !visited[newBoard] { q = append(q, newBoard) visited[newBoard] = true } } } q = q[sz:] step++ } /******* BFS 算法框架结束 *******/ return -1 } func swap(chars []byte, i, j int) string { temp := chars[i] chars[i] = chars[j] chars[j] = temp return string(chars) } ``` ```java // by labuladong (java) class Solution { public int slidingPuzzle(int[][] board) { int m = 2, n = 3; StringBuilder sb = new StringBuilder(); String target = "123450"; // 将 2x3 的数组转化成字符串作为 BFS 的起点 for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { sb.append(board[i][j]); } } String start = sb.toString(); // 记录一维字符串的相邻索引 int[][] neighbor = new int[][]{ {1, 3}, {0, 4, 2}, {1, 5}, {0, 4}, {3, 1, 5}, {4, 2} }; /******* BFS 算法框架开始 *******/ Queue q = new LinkedList<>(); HashSet visited = new HashSet<>(); // 从起点开始 BFS 搜索 q.offer(start); visited.add(start); int step = 0; while (!q.isEmpty()) { int sz = q.size(); for (int i = 0; i < sz; i++) { String cur = q.poll(); // 判断是否达到目标局面 if (target.equals(cur)) { return step; } // 找到数字 0 的索引 int idx = 0; for (; cur.charAt(idx) != '0'; idx++) ; // 将数字 0 和相邻的数字交换位置 for (int adj : neighbor[idx]) { String new_board = swap(cur.toCharArray(), adj, idx); // 防止走回头路 if (!visited.contains(new_board)) { q.offer(new_board); visited.add(new_board); } } } step++; } /******* BFS 算法框架结束 *******/ return -1; } private String swap(char[] chars, int i, int j) { char temp = chars[i]; chars[i] = chars[j]; chars[j] = temp; return new String(chars); } } ``` ```javascript // by chatGPT (javascript) var slidingPuzzle = function(board) { const m = 2, n = 3; let sb = ''; const target = '123450'; // 将 2x3 的数组转化成字符串作为 BFS 的起点 for (let i = 0; i < m; i++) { for (let j = 0; j < n; j++) { sb += board[i][j]; } } let start = sb.toString(); // 记录一维字符串的相邻索引 const neighbor = [ [1, 3], [0, 4, 2], [1, 5], [0, 4], [3, 1, 5], [4, 2] ]; /******* BFS 算法框架开始 *******/ let q = []; const visited = new Set(); // 从起点开始 BFS 搜索 q.push(start); visited.add(start); let step = 0; while (q.length) { const sz = q.length; for (let i = 0; i < sz; i++) { const cur = q.shift(); // 判断是否达到目标局面 if (target === cur) { return step; } // 找到数字 0 的索引 let idx = 0; for (; cur.charAt(idx) !== '0'; idx++) ; // 将数字 0 和相邻的数字交换位置 for (const adj of neighbor[idx]) { const new_board = swap(cur.slice(0), adj, idx); // 防止走回头路 if (!visited.has(new_board)) { q.push(new_board); visited.add(new_board); } } } step++; } /******* BFS 算法框架结束 *******/ return -1; }; var swap = function(chars, i, j) { [chars[i], chars[j]] = [chars[j], chars[i]]; return chars.join(''); }; ``` ```python # by chatGPT (python) class Solution: def slidingPuzzle(self, board: List[List[int]]) -> int: m, n = 2, 3 sb = [] target = "123450" # 将 2x3 的数组转化成字符串作为 BFS 的起点 for i in range(m): for j in range(n): sb.append(str(board[i][j])) start = ''.join(sb) # 记录一维字符串的相邻索引 neighbor = [ [1, 3], [0, 4, 2], [1, 5], [0, 4], [3, 1, 5], [4, 2] ] # BFS 算法框架开始 q = collections.deque() visited = set() # 从起点开始 BFS 搜索 q.append(start) visited.add(start) step = 0 while q: sz = len(q) for i in range(sz): cur = q.popleft() # 判断是否达到目标局面 if target == cur: return step # 找到数字 0 的索引 idx = cur.find('0') # 将数字 0 和相邻的数字交换位置 for adj in neighbor[idx]: new_board = self.swap(cur, adj, idx) # 防止走回头路 if new_board not in visited: q.append(new_board) visited.add(new_board) step += 1 # BFS 算法框架结束 return -1 def swap(self, chars, i, j): chars = list(chars) chars[i], chars[j] = chars[j], chars[i] return ''.join(chars) ``` https://leetcode.cn/problems/sliding-puzzle 的多语言解法👆 https://leetcode.cn/problems/sliding-window-maximum 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: /* 单调队列的实现 */ class MonotonicQueue { private: deque q; public: void push(int n) { // 将小于 n 的元素全部删除 while (!q.empty() && q.back() < n) { q.pop_back(); } // 然后将 n 加入尾部 q.push_back(n); } int max() { return q.front(); } void pop(int n) { if (n == q.front()) { q.pop_front(); } } }; /* 解题函数的实现 */ vector maxSlidingWindow(vector& nums, int k) { MonotonicQueue window; vector res; for (int i = 0; i < nums.size(); i++) { if (i < k - 1) { //先填满窗口的前 k - 1 window.push(nums[i]); } else { // 窗口向前滑动,加入新数字 window.push(nums[i]); // 记录当前窗口的最大值 res.push_back(window.max()); // 移出旧数字 window.pop(nums[i - k + 1]); } } return res; } }; ``` ```go // by chatGPT (go) /* 单调队列的实现 */ type MonotonicQueue struct { q []int } func (mq *MonotonicQueue) push(n int) { // 将小于 n 的元素全部删除 for len(mq.q) > 0 && mq.q[len(mq.q)-1] < n { mq.q = mq.q[:len(mq.q)-1] } // 然后将 n 加入尾部 mq.q = append(mq.q, n) } func (mq *MonotonicQueue) max() int { return mq.q[0] } func (mq *MonotonicQueue) pop(n int) { if n == mq.q[0] { mq.q = mq.q[1:] } } /* 解题函数的实现 */ func maxSlidingWindow(nums []int, k int) []int { window := MonotonicQueue{make([]int, 0)} res := make([]int, 0) for i := 0; i < len(nums); i++ { if i < k-1 { // 先填满窗口的前 k - 1 window.push(nums[i]) } else { // 窗口向前滑动,加入新数字 window.push(nums[i]) // 记录当前窗口的最大值 res = append(res, window.max()) // 移出旧数字 window.pop(nums[i-k+1]) } } return res } ``` ```java // by labuladong (java) class Solution { /* 单调队列的实现 */ class MonotonicQueue { LinkedList q = new LinkedList<>(); public void push(int n) { // 将小于 n 的元素全部删除 while (!q.isEmpty() && q.getLast() < n) { /** ![](../pictures/单调队列/3.png) */ q.pollLast(); } // 然后将 n 加入尾部 q.addLast(n); } public int max() { return q.getFirst(); } public void pop(int n) { if (n == q.getFirst()) { q.pollFirst(); } } } /* 解题函数的实现 */ 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) { //先填满窗口的前 k - 1 window.push(nums[i]); } else { /** ![](../pictures/单调队列/1.png) */ // 窗口向前滑动,加入新数字 window.push(nums[i]); // 记录当前窗口的最大值 res.add(window.max()); // 移出旧数字 window.pop(nums[i - k + 1]); } } // 需要转成 int[] 数组再返回 int[] arr = new int[res.size()]; for (int i = 0; i < res.size(); i++) { arr[i] = res.get(i); } return arr; } } ``` ```javascript // by chatGPT (javascript) var maxSlidingWindow = function(nums, k) { /** * 单调队列的实现 */ class MonotonicQueue { constructor() { this.q = [] } push(n) { // 将小于 n 的元素全部删除 while (this.q.length !== 0 && this.q[this.q.length - 1] < n) { /** ![](../pictures/单调队列/3.png) */ this.q.pop() } // 然后将 n 加入尾部 this.q.push(n) } max() { return this.q[0] } pop(n) { if (this.q[0] === n) { this.q.shift() } } } /** * 解题函数的实现 */ const window = new MonotonicQueue() const res = [] for (let i = 0; i < nums.length; i++) { if (i < k - 1) { //先填满窗口的前 k - 1 window.push(nums[i]) } else { /** ![](../pictures/单调队列/1.png) */ // 窗口向前滑动,加入新数字 window.push(nums[i]) // 记录当前窗口的最大值 res.push(window.max()) // 移出旧数字 window.pop(nums[i - k + 1]) } } return res } ``` ```python # by chatGPT (python) class Solution: # Monotonic Queue Implementation class MonotonicQueue: def __init__(self): self.q = [] # Push elements into the queue def push(self, n): # remove all elements smaller than n from the tail of the queue while self.q and self.q[-1] < n: # extend down -300 # ![](../pictures/单调队列/3.png) self.q.pop() # then insert n at the tail self.q.append(n) # Get maximum element in the queue def max(self): return self.q[0] # Remove element n from the queue def pop(self, n): if n == self.q[0]: self.q.pop(0) # Implementing the solution function def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]: window = self.MonotonicQueue() res = [] for i in range(len(nums)): if i < k - 1: # fill up the first k-1 elements of the window initially window.push(nums[i]) else: # slide the window forward and add the new element window.push(nums[i]) # record the maximum value of the current window res.append(window.max()) # remove the old element window.pop(nums[i - k + 1]) # converting the recorded elements into an integer array return res ``` https://leetcode.cn/problems/sliding-window-maximum 的多语言解法👆 https://leetcode.cn/problems/smallest-subsequence-of-distinct-characters 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: string smallestSubsequence(string s) { stack stk; // 维护一个计数器记录字符串中字符的数量 // 因为输入为 ASCII 字符,大小 256 够用了 int count[256] = {0}; for (int i = 0; i < s.length(); i++) { count[s[i]]++; } bool inStack[256] = {false}; for (char c : s) { // 每遍历过一个字符,都将对应的计数减一 count[c]--; if (inStack[c]) continue; while (!stk.empty() && stk.top() > c) { // 若之后不存在栈顶元素了,则停止 pop if (count[stk.top()] == 0) { break; } // 若之后还有,则可以 pop inStack[stk.top()] = false; stk.pop(); } stk.push(c); inStack[c] = true; } string res; while (!stk.empty()) { res += stk.top(); stk.pop(); } reverse(res.begin(), res.end()); return res; } }; ``` ```go // by chatGPT (go) func smallestSubsequence(s string) string { stk := make([]byte, 0) // 维护一个计数器记录字符串中字符的数量 // 因为输入为 ASCII 字符,大小 256 够用了 count := make([]int, 256) for i := 0; i < len(s); i++ { count[s[i]]++ } inStack := make([]bool, 256) for _, c := range s { // 每遍历过一个字符,都将对应的计数减一 count[c]-- if inStack[c] { continue } for len(stk) > 0 && stk[len(stk)-1] > byte(c) { // 若之后不存在栈顶元素了,则停止 pop if count[stk[len(stk)-1]] == 0 { break } // 若之后还有,则可以 pop inStack[stk[len(stk)-1]] = false stk = stk[:len(stk)-1] } stk = append(stk, byte(c)) inStack[c] = true } sb := make([]byte, 0, len(s)) for i := len(stk) - 1; i >= 0; i-- { sb = append(sb, stk[i]) } return string(sb) } ``` ```java // by labuladong (java) class Solution { public String smallestSubsequence(String s) { Stack stk = new Stack<>(); // 维护一个计数器记录字符串中字符的数量 // 因为输入为 ASCII 字符,大小 256 够用了 int[] count = new int[256]; for (int i = 0; i < s.length(); i++) { count[s.charAt(i)]++; } boolean[] inStack = new boolean[256]; for (char c : s.toCharArray()) { // 每遍历过一个字符,都将对应的计数减一 count[c]--; if (inStack[c]) continue; while (!stk.isEmpty() && stk.peek() > c) { // 若之后不存在栈顶元素了,则停止 pop if (count[stk.peek()] == 0) { break; } // 若之后还有,则可以 pop inStack[stk.pop()] = false; } stk.push(c); inStack[c] = true; } StringBuilder sb = new StringBuilder(); while (!stk.empty()) { sb.append(stk.pop()); } return sb.reverse().toString(); } } ``` ```javascript // by chatGPT (javascript) /** * @param {string} s * @return {string} */ var smallestSubsequence = function(s) { // 创建一个“栈” data structure(使用 array 实现)来处理字符串 const stk = []; // 创建一个数组用于记录字符数量 // 因为输入为 ASCII 字符,大小 256 够用了 const count = new Array(256).fill(0); for(let i = 0; i < s.length; i++) { count[s.charCodeAt(i)]++; } // 创建一个 boolean 数组来记录字符是否出现在“栈”中 const inStack = new Array(256).fill(false); for(let c of s) { // 每遍历过一个字符,都将对应的计数减一 count[c.charCodeAt()]--; // 如果该字符已经在“栈”中,则跳过 if(inStack[c.charCodeAt()]) continue; // 如果该字符不在“栈”中,则需要将“栈”中比该字符大的字符出栈 while(stk.length && stk[stk.length - 1] > c) { // 如果弹出的字符在后面还会出现,则需要添加回来,停止出栈 if(count[stk[stk.length - 1].charCodeAt()] === 0) break; // 如果弹出的字符在后面不再出现,则可以继续出栈 inStack[stk.pop().charCodeAt()] = false; } // 将字符压入“栈”中,标记为出现 stk.push(c); inStack[c.charCodeAt()] = true; } // 构造结果字符串 return stk.join(''); }; ``` ```python # by chatGPT (python) class Solution: def smallestSubsequence(self, s: str) -> str: stk = [] # 维护一个计数器记录字符串中字符的数量 # 因为输入为 ASCII 字符,大小 256 够用了 count = [0] * 256 for i in range(len(s)): count[ord(s[i])] += 1 inStack = [False] * 256 for c in s: # 每遍历过一个字符,都将对应的计数减一 count[ord(c)] -= 1 if inStack[ord(c)]: continue while stk and stk[-1] > c: # 若之后不存在栈顶元素了,则停止 pop if count[ord(stk[-1])] == 0: break # 若之后还有,则可以 pop inStack[ord(stk.pop())] = False stk.append(c) inStack[ord(c)] = True return ''.join(stk) ``` https://leetcode.cn/problems/smallest-subsequence-of-distinct-characters 的多语言解法👆 https://leetcode.cn/problems/sort-characters-by-frequency 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: string frequencySort(string s) { vector chars(s.begin(), s.end()); // s 中的字符 -> 该字符出现的频率 unordered_map charToFreq; for (char ch : chars) { charToFreq[ch]++; } auto cmp = [](pair& entry1, pair& entry2) { return entry1.second < entry2.second; }; // 队列按照键值对中的值(字符出现频率)从大到小排序 priority_queue, vector>, decltype(cmp)> pq(cmp); // 按照字符频率排序 for (auto& entry : charToFreq) { pq.push(entry); } string res = ""; while (!pq.empty()) { // 把频率最高的字符排在前面 pair entry = pq.top(); pq.pop(); res += string(entry.second, entry.first); } return res; } }; ``` ```go // by chatGPT (go) // 题目地址 https://leetcode-cn.com/problems/sort-characters-by-frequency/ func frequencySort(s string) string { chars := []rune(s) // s 中的字符 -> 该字符出现的频率 charToFreq := make(map[rune]int) for _, ch := range chars { charToFreq[ch]++ } pq := priorityQueue{} // 队列按照键值对中的值(字符出现频率)从大到小排序 for k, v := range charToFreq { pq = append(pq, entry{k, v}) } sort.Sort(sort.Reverse(pq)) var sb strings.Builder for _, e := range pq { // 把频率最高的字符排在前面 s := strings.Repeat(string(e.ch), e.count) sb.WriteString(s) } return sb.String() } type entry struct { ch rune count int } type priorityQueue []entry func (pq priorityQueue) Len() int { return len(pq) } func (pq priorityQueue) Less(i, j int) bool { return pq[i].count < pq[j].count } func (pq priorityQueue) Swap(i, j int) { pq[i], pq[j] = pq[j], pq[i] } func (pq *priorityQueue) Push(x interface{}) { *pq = append(*pq, x.(entry)) } func (pq *priorityQueue) Pop() interface{} { old := *pq n := len(old) x := old[n-1] *pq = old[0 : n-1] return x } ``` ```java // by labuladong (java) class Solution { public String frequencySort(String s) { char[] chars = s.toCharArray(); // s 中的字符 -> 该字符出现的频率 HashMap charToFreq = new HashMap<>(); for (char ch : chars) { charToFreq.put(ch, charToFreq.getOrDefault(ch, 0) + 1); } PriorityQueue> pq = new PriorityQueue<>((entry1, entry2) -> { // 队列按照键值对中的值(字符出现频率)从大到小排序 return entry2.getValue().compareTo(entry1.getValue()); }); // 按照字符频率排序 for (Map.Entry entry : charToFreq.entrySet()) { pq.offer(entry); } StringBuilder sb = new StringBuilder(); while (!pq.isEmpty()) { // 把频率最高的字符排在前面 Map.Entry entry = pq.poll(); String part = String.valueOf(entry.getKey()).repeat(entry.getValue()); sb.append(part); } return sb.toString(); } } ``` ```javascript // by chatGPT (javascript) /** * @param {string} s * @return {string} */ var frequencySort = function(s) { let chars = s.split(''); let charToFreq = new Map(); chars.forEach(ch => { charToFreq.set(ch, charToFreq.get(ch) + 1 || 1); }); let pq = new PriorityQueue((a, b) => { // 队列按照键值对中的值(字符出现频率)从大到小排序 return b.value - a.value; }); // 按照字符频率排序 charToFreq.forEach((value, key) => { pq.offer({key, value}); }); let sb = []; while (!pq.isEmpty()) { // 把频率最高的字符排在前面 let {key, value} = pq.poll(); let part = key.repeat(value); sb.push(part); } return sb.join(''); }; class PriorityQueue { constructor(compareFn) { this.queue = []; this.compareFn = compareFn; } isEmpty() { return this.queue.length === 0; } offer(item) { this.queue.push(item); this.queue.sort(this.compareFn); } poll() { return this.queue.shift(); } } ``` ```python # by chatGPT (python) class Solution: def frequencySort(self, s: str) -> str: chars = list(s) # s 中的字符 -> 该字符出现的频率 charToFreq = {} for ch in chars: charToFreq[ch] = charToFreq.get(ch, 0) + 1 pq = [] for key, value in charToFreq.items(): # 创建优先队列 pq.append((value, key)) # 对优先队列中的元素按照出现频率进行排序 pq.sort(reverse=True) res = '' for freq, ch in pq: # 把频率最高的字符排在前面 part = ch * freq res += part return res ``` https://leetcode.cn/problems/sort-characters-by-frequency 的多语言解法👆 https://leetcode.cn/problems/sort-the-matrix-diagonally 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: vector> diagonalSort(vector>& mat) { int m = mat.size(), n = mat[0].size(); // 存储所有对角线的元素列表 unordered_map> diagonals; for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { // 横纵坐标之差可以作为一条对角线的 ID int diagonalID = i - j; diagonals[diagonalID].push_back(mat[i][j]); } } // 从数组末尾删除元素效率较高,所以我们把 vector 倒序排序 for (auto& diagonal: diagonals) { sort(diagonal.second.rbegin(), diagonal.second.rend()); } // 把排序结果回填二维矩阵 for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { vector& diagonal = diagonals[i - j]; mat[i][j] = diagonal.back(); diagonal.pop_back(); } } return mat; } }; ``` ```go // by chatGPT (go) // DiagonalSort function func DiagonalSort(mat [][]int) [][]int { m, n := len(mat), len(mat[0]) // 存储所有对角线的元素列表 diagonals := make(map[int][]int) for i := 0; i < m; i++ { for j := 0; j < n; j++ { // 横纵坐标之差可以作为一条对角线的 ID diagonalID := i - j if _, ok := diagonals[diagonalID]; !ok { diagonals[diagonalID] = make([]int, 0) } diagonals[diagonalID] = append(diagonals[diagonalID], mat[i][j]) } } // 从数组末尾删除元素效率较高,所以我们把 []int 倒序排序 for _, v := range diagonals { sort.Sort(sort.Reverse(sort.IntSlice(v))) } // 把排序结果回填二维矩阵 for i := 0; i < m; i++ { for j := 0; j < n; j++ { diagonal := diagonals[i-j] mat[i][j] = diagonal[len(diagonal)-1] diagonals[i-j] = diagonal[:len(diagonal)-1] } } return mat } ``` ```java // by labuladong (java) class Solution { public int[][] diagonalSort(int[][] mat) { int m = mat.length, n = mat[0].length; // 存储所有对角线的元素列表 HashMap> diagonals = new HashMap<>(); for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { // 横纵坐标之差可以作为一条对角线的 ID int diagonalID = i - j; diagonals.putIfAbsent(diagonalID, new ArrayList<>()); diagonals.get(diagonalID).add(mat[i][j]); } } // 从数组末尾删除元素效率较高,所以我们把 ArrayList 倒序排序 for (List diagonal: diagonals.values()) { Collections.sort(diagonal, Collections.reverseOrder()); } // 把排序结果回填二维矩阵 for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { ArrayList diagonal = diagonals.get(i - j); mat[i][j] = diagonal.remove(diagonal.size() - 1); } } return mat; } } ``` ```javascript // by chatGPT (javascript) /** * @param {number[][]} mat * @return {number[][]} */ var diagonalSort = function(mat) { let m = mat.length, n = mat[0].length; // 存储所有对角线的元素列表 let diagonals = new Map(); for (let i = 0; i < m; i++) { for (let j = 0; j < n; j++) { // 横纵坐标之差可以作为一条对角线的 ID let diagonalID = i - j; if (!diagonals.has(diagonalID)) { diagonals.set(diagonalID, []); } diagonals.get(diagonalID).push(mat[i][j]); } } // 从数组末尾删除元素效率较高,所以我们把数组倒序排序 for (let diagonal of diagonals.values()) { diagonal.sort((a, b) => b - a); } // 把排序结果回填二维矩阵 for (let i = 0; i < m; i++) { for (let j = 0; j < n; j++) { let diagonal = diagonals.get(i - j); mat[i][j] = diagonal.pop(); } } return mat; }; ``` ```python # by chatGPT (python) class Solution: def diagonalSort(self, mat: List[List[int]]) -> List[List[int]]: m, n = len(mat), len(mat[0]) # 存储所有对角线的元素列表 diagonals = {} for i in range(m): for j in range(n): # 横纵坐标之差可以作为一条对角线的 ID diagonalID = i - j if diagonalID not in diagonals: diagonals[diagonalID] = [] diagonals[diagonalID].append(mat[i][j]) # 从数组末尾删除元素效率较高,所以我们把列表倒序排序 for diagonal in diagonals.values(): diagonal.sort(reverse=True) # 把排序结果回填矩阵 for i in range(m): for j in range(n): diagonal = diagonals[i - j] mat[i][j] = diagonal.pop() return mat ``` https://leetcode.cn/problems/sort-the-matrix-diagonally 的多语言解法👆 https://leetcode.cn/problems/squares-of-a-sorted-array 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: vector sortedSquares(vector& nums) { int n = nums.size(); // 两个指针分别初始化在正负子数组绝对值最大的元素索引 int i = 0, j = n - 1; // 得到的有序结果是降序的 int p = n - 1; vector res(n); // 执行双指针合并有序数组的逻辑 while (i <= j) { if (abs(nums[i]) > abs(nums[j])) { res[p] = nums[i] * nums[i]; i++; } else { res[p] = nums[j] * nums[j]; j--; } p--; } return res; } }; ``` ```go // by chatGPT (go) func sortedSquares(nums []int) []int { n := len(nums) // 两个指针分别初始化在正负子数组绝对值最大的元素索引 i, j := 0, n-1 // 得到的有序结果是降序的 p := n - 1 res := make([]int, n) // 执行双指针合并有序数组的逻辑 for i <= j { if math.Abs(float64(nums[i])) > math.Abs(float64(nums[j])) { res[p] = nums[i] * nums[i] i++ } else { res[p] = nums[j] * nums[j] j-- } p-- } return res } ``` ```java // by labuladong (java) class Solution { public int[] sortedSquares(int[] nums) { int n = nums.length; // 两个指针分别初始化在正负子数组绝对值最大的元素索引 int i = 0, j = n - 1; // 得到的有序结果是降序的 int p = n - 1; int[] res = new int[n]; // 执行双指针合并有序数组的逻辑 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; } } ``` ```javascript // by chatGPT (javascript) /** * @param {number[]} nums * @return {number[]} */ var sortedSquares = function(nums) { let n = nums.length; // 两个指针分别初始化在正负子数组绝对值最大的元素索引 let i = 0, j = n - 1; // 得到的有序结果是降序的 let p = n - 1; let res = new Array(n); // 执行双指针合并有序数组的逻辑 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; }; ``` ```python # by chatGPT (python) class Solution: def sortedSquares(self, nums: List[int]) -> List[int]: n = len(nums) # 两个指针分别初始化在正负子数组绝对值最大的元素索引 i, j = 0, n - 1 # 得到的有序结果是降序的 p = n - 1 res = [0] * n # 执行双指针合并有序数组的逻辑 while i <= j: if abs(nums[i]) > abs(nums[j]): res[p] = nums[i] * nums[i] i += 1 else: res[p] = nums[j] * nums[j] j -= 1 p -= 1 return res ``` https://leetcode.cn/problems/squares-of-a-sorted-array 的多语言解法👆 https://leetcode.cn/problems/stone-game 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: bool stoneGame(vector& piles) { return true; } }; ``` ```go // by chatGPT (go) func stoneGame(piles []int) bool { return true } ``` ```java // by labuladong (java) class Solution { public boolean stoneGame(int[] piles) { return true; } } ``` ```javascript // by chatGPT (javascript) /** * @param {number[]} piles * @return {boolean} */ var stoneGame = function(piles) { return true; }; ``` ```python # by chatGPT (python) class Solution: def stoneGame(self, piles: List[int]) -> bool: return True ``` https://leetcode.cn/problems/stone-game 的多语言解法👆 https://leetcode.cn/problems/string-to-integer-atoi 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int myAtoi(string str) { int n = str.length(); int i = 0; // 记录正负号 int sign = 1; // 用 long 避免 int 溢出 long res = 0; // 跳过前导空格 while (i < n && str[i] == ' ') { i++; } if (i == n) { return 0; } // 记录符号位 if (str[i] == '-') { sign = -1; i++; } else if (str[i] == '+') { i++; } if (i == n) { return 0; } // 统计数字位 while (i < n && '0' <= str[i] && str[i] <= '9') { res = res * 10 + str[i] - '0'; if (res > INT_MAX) { break; } i++; } // 如果溢出,强转成 int 就会和真实值不同 if ((int) res != res) { return sign == 1 ? INT_MAX : INT_MIN; } return (int) res * sign; } }; ``` ```go // by chatGPT (go) import "math" func myAtoi(str string) int { n := len(str) i := 0 // 记录正负号 sign := 1 // 用 long 避免 int 溢出 var res int64 = 0 // 跳过前导空格 for i < n && str[i] == ' ' { i++ } if i == n { return 0 } // 记录符号位 if str[i] == '-' { sign = -1 i++ } else if str[i] == '+' { i++ } if i == n { return 0 } // 统计数字位 for i < n && '0' <= str[i] && str[i] <= '9' { res = res * 10 + int64(str[i]-'0') if res > math.MaxInt32 { break } i++ } // 如果溢出,强转成 int 就会和真实值不同 if res > math.MaxInt32 { if sign == 1 { return math.MaxInt32 } else { return math.MinInt32 } } return int(res) * sign } ``` ```java // by labuladong (java) class Solution { public int myAtoi(String str) { int n = str.length(); int i = 0; // 记录正负号 int sign = 1; // 用 long 避免 int 溢出 long res = 0; // 跳过前导空格 while (i < n && str.charAt(i) == ' ') { i++; } if (i == n) { return 0; } // 记录符号位 if (str.charAt(i) == '-') { sign = -1; i++; } else if (str.charAt(i) == '+') { i++; } if (i == n) { return 0; } // 统计数字位 while (i < n && '0' <= str.charAt(i) && str.charAt(i) <= '9') { res = res * 10 + str.charAt(i) - '0'; if (res > Integer.MAX_VALUE) { break; } i++; } // 如果溢出,强转成 int 就会和真实值不同 if ((int) res != res) { return sign == 1 ? Integer.MAX_VALUE : Integer.MIN_VALUE; } return (int) res * sign; } } ``` ```javascript // by chatGPT (javascript) var myAtoi = function(str) { var n = str.length; var i = 0; // 记录正负号 var sign = 1; // 用 long 避免 int 溢出 var res = 0; // 跳过前导空格 while (i < n && str.charAt(i) == ' ') { i++; } if (i === n) { return 0; } // 记录符号位 if (str.charAt(i) == '-') { sign = -1; i++; } else if (str.charAt(i) == '+') { i++; } if (i === n) { return 0; } // 统计数字位 while (i < n && '0' <= str.charAt(i) && str.charAt(i) <= '9') { res = res * 10 + str.charAt(i) - '0'; if (res > 2147483647) { break; } i++; } // 如果溢出,强转成 int 就会和真实值不同 if (Math.trunc(res) !== res) { return sign === 1 ? 2147483647 : -2147483648; } return res * sign; }; ``` ```python # by chatGPT (python) class Solution: def myAtoi(self, str: str) -> int: n = len(str) i = 0 # 记录正负号 sign = 1 # 用 long 避免 int 溢出 res = 0 # 跳过前导空格 while i < n and str[i] == ' ': i += 1 if i == n: return 0 # 记录符号位 if str[i] == '-': sign = -1 i += 1 elif str[i] == '+': i += 1 if i == n: return 0 # 统计数字位 while i < n and '0' <= str[i] <= '9': res = res * 10 + ord(str[i]) - ord('0') if res > 2 ** 31 - 1: break i += 1 # 如果溢出,强转成 int 就会和真实值不同 if res != int(res): return sign * (2 ** 31 - 1) if sign == 1 else -2 ** 31 return int(res) * sign ``` https://leetcode.cn/problems/string-to-integer-atoi 的多语言解法👆 https://leetcode.cn/problems/subsets 的多语言解法👇 ```cpp // by labuladong (cpp) class Solution { public: vector> res; vector> subsets(vector& nums) { // 记录走过的路径 vector track; backtrack(nums, 0, track); return res; } void backtrack(vector& nums, int start, vector& track) { res.push_back(track); for (int i = start; i < nums.size(); i++) { // 做选择 track.push_back(nums[i]); // 回溯 backtrack(nums, i + 1, track); // 撤销选择 track.pop_back(); } } }; ``` ```go // by chatGPT (go) // // subsets is a function that returns all possible subsets of an array of integers. func subsets(nums []int) [][]int { res := [][]int{} track := []int{} backtrack(nums, 0, track, &res) return res } func backtrack(nums []int, start int, track []int, res *[][]int) { temp := make([]int, len(track)) copy(temp, track) *res = append(*res, temp) for i := start; i < len(nums); i++ { // 做选择 track = append(track, nums[i]) // 回溯 backtrack(nums, i+1, track, res) // 撤销选择 track = track[:len(track)-1] } } ``` ```java // by chatGPT (java) class Solution { //定义二维数组res用于存储结果 List> res = new LinkedList<>(); public List> subsets(int[] nums) { //定义路径数组 List track = new LinkedList<>(); backtrack(nums, 0, track); return res; } public void backtrack(int[] nums, int start, List track) { //添加路径数组到结果数组中 res.add(new LinkedList<>(track)); //for循环遍历数组nums for (int i = start; i < nums.length; i++) { //做选择,将选择添加到路径数组中 track.add(nums[i]); //回溯,继续向后遍历 backtrack(nums, i + 1, track); //撤销选择,将选择从路径中删除 track.remove(track.size() - 1); } } } ``` ```javascript // by chatGPT (javascript) var subsets = function(nums) { var res = []; var track = []; // 记录走过的路径 backtrack(nums, 0, track); return res; function backtrack(nums, start, track) { res.push([...track]); for (var i = start; i < nums.length; i++) { // 做选择 track.push(nums[i]); // 回溯 backtrack(nums, i + 1, track); // 撤销选择 track.pop(); } } }; ``` ```python # by chatGPT (python) class Solution: def subsets(self, nums: List[int]) -> List[List[int]]: res = [] track = [] # 记录走过的路径 self.backtrack(nums, 0, track, res) return res def backtrack(self, nums, start, track, res): res.append(track[:]) for i in range(start, len(nums)): # 做选择 track.append(nums[i]) # 回溯 self.backtrack(nums, i + 1, track, res) # 撤销选择 track.pop() ``` https://leetcode.cn/problems/subsets 的多语言解法👆 https://leetcode.cn/problems/sudoku-solver 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: void solveSudoku(vector>& board) { backtrack(board, 0, 0); } bool backtrack(vector>& board, int i, int j) { int m = 9, n = 9; if (j == n) { // 穷举到最后一列的话就换到下一行重新开始。 return backtrack(board, i + 1, 0); } if (i == m) { // 找到一个可行解,触发 base case return true; } if (board[i][j] != '.') { // 如果有预设数字,不用我们穷举 return backtrack(board, i, j + 1); } for (char ch = '1'; ch <= '9'; ch++) { // 如果遇到不合法的数字,就跳过 if (!isValid(board, i, j, ch)) continue; board[i][j] = ch; // 如果找到一个可行解,立即结束 if (backtrack(board, i, j + 1)) { return true; } board[i][j] = '.'; } // 穷举完 1~9,依然没有找到可行解,此路不通 return false; } bool isValid(vector>& board, int r, int c, char n) { for (int i = 0; i < 9; i++) { // 判断行是否存在重复 if (board[r][i] == n) return false; // 判断列是否存在重复 if (board[i][c] == n) return false; // 判断 3 x 3 方框是否存在重复 if (board[(r/3)*3 + i/3][(c/3)*3 + i%3] == n) return false; } return true; } }; ``` ```go // by chatGPT (go) func solveSudoku(board [][]byte) { backtrack(board, 0, 0) } func backtrack(board [][]byte, i int, j int) bool { m, n := 9, 9 if j == n { // 穷举到最后一列的话就换到下一行重新开始。 return backtrack(board, i+1, 0) } if i == m { // 找到一个可行解,触发 base case return true } if board[i][j] != '.' { // 如果有预设数字,不用我们穷举 return backtrack(board, i, j+1) } for ch := '1'; ch <= '9'; ch++ { // 如果遇到不合法的数字,就跳过 if !isValid(board, i, j, byte(ch)) { continue } board[i][j] = byte(ch) // 如果找到一个可行解,立即结束 if backtrack(board, i, j+1) { return true } board[i][j] = '.' } // 穷举完 1~9,依然没有找到可行解,此路不通 return false } // 判断 board[i][j] 是否可以填入 n func isValid(board [][]byte, r int, c int, n byte) bool { for i := 0; i < 9; i++ { // 判断行是否存在重复 if board[r][i] == n { return false } // 判断列是否存在重复 if board[i][c] == n { return false } // 判断 3 x 3 方框是否存在重复 if board[(r/3)*3+i/3][(c/3)*3+i%3] == n { return false } } return true } ``` ```java // by labuladong (java) class Solution { public void solveSudoku(char[][] board) { backtrack(board, 0, 0); } boolean backtrack(char[][] board, int i, int j) { int m = 9, n = 9; if (j == n) { // 穷举到最后一列的话就换到下一行重新开始。 return backtrack(board, i + 1, 0); } if (i == m) { // 找到一个可行解,触发 base case return true; } if (board[i][j] != '.') { // 如果有预设数字,不用我们穷举 return backtrack(board, i, j + 1); } for (char ch = '1'; ch <= '9'; ch++) { // 如果遇到不合法的数字,就跳过 if (!isValid(board, i, j, ch)) continue; board[i][j] = ch; // 如果找到一个可行解,立即结束 if (backtrack(board, i, j + 1)) { return true; } board[i][j] = '.'; } // 穷举完 1~9,依然没有找到可行解,此路不通 return false; } // 判断 board[i][j] 是否可以填入 n boolean isValid(char[][] board, int r, int c, char n) { for (int i = 0; i < 9; i++) { // 判断行是否存在重复 if (board[r][i] == n) return false; // 判断列是否存在重复 if (board[i][c] == n) return false; // 判断 3 x 3 方框是否存在重复 if (board[(r/3)*3 + i/3][(c/3)*3 + i%3] == n) return false; } return true; } } ``` ```javascript // by chatGPT (javascript) /** * @param {character[][]} board * @return {void} Do not return anything, modify board in-place instead. */ var solveSudoku = function(board) { backtrack(board, 0, 0); }; var backtrack = function(board, i, j) { const m = 9, n = 9; if (j == n) { // 穷举到最后一列的话就换到下一行重新开始。 return backtrack(board, i + 1, 0); } if (i == m) { // 找到一个可行解,触发 base case return true; } if (board[i][j] != '.') { // 如果有预设数字,不用我们穷举 return backtrack(board, i, j + 1); } for (let ch = '1'; ch <= '9'; ch++) { // 如果遇到不合法的数字,就跳过 if (!isValid(board, i, j, ch)) continue; board[i][j] = ch; // 如果找到一个可行解,立即结束 if (backtrack(board, i, j + 1)) { return true; } board[i][j] = '.'; } // 穷举完 1~9,依然没有找到可行解,此路不通 return false; } var isValid = function(board, r, c, n) { for (let i = 0; i < 9; i++) { // 判断行是否存在重复 if (board[r][i] == n) return false; // 判断列是否存在重复 if (board[i][c] == n) return false; // 判断 3 x 3 方框是否存在重复 if (board[Math.floor(r/3)*3 + Math.floor(i/3)][Math.floor(c/3)*3 + i%3] == n) return false; } return true; }; ``` ```python # by chatGPT (python) class Solution: def solveSudoku(self, board: List[List[str]]) -> None: """ Do not return anything, modify board in-place instead. """ self.backtrack(board, 0, 0) def backtrack(self, board: List[List[str]], i: int, j: int) -> bool: m, n = 9, 9 if j == n: # 穷举到最后一列的话就换到下一行重新开始。 return self.backtrack(board, i + 1, 0) if i == m: # 找到一个可行解,触发 base case return True if board[i][j] != '.': # 如果有预设数字,不用我们穷举 return self.backtrack(board, i, j + 1) for ch in range(1, 10): ch = str(ch) # 如果遇到不合法的数字,就跳过 if not self.isValid(board, i, j, ch): continue board[i][j] = ch # 如果找到一个可行解,立即结束 if self.backtrack(board, i, j + 1): return True board[i][j] = '.' # 穷举完 1~9,依然没有找到可行解,此路不通 return False # 判断 board[i][j] 是否可以填入 n def isValid(self, board: List[List[str]], r: int, c: int, n: str) -> bool: for i in range(9): # 判断行是否存在重复 if board[r][i] == n: return False # 判断列是否存在重复 if board[i][c] == n: return False # 判断 3 x 3 方框是否存在重复 if board[(r // 3) * 3 + i // 3][(c // 3) * 3 + i % 3] == n: return False return True ``` https://leetcode.cn/problems/sudoku-solver 的多语言解法👆 https://leetcode.cn/problems/super-egg-drop 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int superEggDrop(int K, int N) { // m 最多不会超过 N 次(线性扫描) vector> dp(K + 1, vector(N + 1)); // base case: // dp[0][..] = 0 // dp[..][0] = 0 // C++默认初始化为0 int m = 0; while (dp[K][m] < N) { m++; for (int k = 1; k <= K; k++) dp[k][m] = dp[k][m - 1] + dp[k - 1][m - 1] + 1; } return m; } }; ``` ```go // by chatGPT (go) func superEggDrop(K int, N int) int { // m 最多不会超过 N 次(线性扫描) dp := make([][]int, K + 1) for i := 0; i <= K; i++ { dp[i] = make([]int, N + 1) } // base case: // dp[0][..] = 0 // dp[..][0] = 0 // Go 默认初始化数组都为 0 m := 0 for dp[K][m] < N { m++ for k := 1; k <= K; k++ { dp[k][m] = dp[k][m - 1] + dp[k - 1][m - 1] + 1 } } return m } ``` ```java // by labuladong (java) class Solution { public int superEggDrop(int K, int N) { // m 最多不会超过 N 次(线性扫描) int[][] dp = new int[K + 1][N + 1]; // base case: // dp[0][..] = 0 // dp[..][0] = 0 // Java 默认初始化数组都为 0 int m = 0; while (dp[K][m] < N) { m++; for (int k = 1; k <= K; k++) dp[k][m] = dp[k][m - 1] + dp[k - 1][m - 1] + 1; } return m; } } ``` ```javascript // by chatGPT (javascript) var superEggDrop = function(K, N) { // m 最多不会超过 N 次(线性扫描) var dp = new Array(K + 1).fill(0).map(x => new Array(N + 1).fill(0)); // base case: // dp[0][..] = 0 // dp[..][0] = 0 // JavaScript 默认初始化数组都为 undefined var m = 0; while (dp[K][m] < N) { m++; for (var k = 1; k <= K; k++) dp[k][m] = dp[k][m - 1] + dp[k - 1][m - 1] + 1; } return m; }; ``` ```python # by chatGPT (python) class Solution: def superEggDrop(self, K: int, N: int) -> int: # m 最多不会超过 N 次(线性扫描) dp = [[0] * (N + 1) for _ in range(K + 1)] # base case: # dp[0][..] = 0 # dp[..][0] = 0 # Java 默认初始化数组都为 0 m = 0 while dp[K][m] < N: m += 1 for k in range(1, K + 1): dp[k][m] = dp[k][m - 1] + dp[k - 1][m - 1] + 1 return m ``` https://leetcode.cn/problems/super-egg-drop 的多语言解法👆 https://leetcode.cn/problems/super-pow 的多语言解法👇 ```cpp // by labuladong (cpp) class Solution { public: int base = 1337; // 计算 a 的 k 次方然后与 base 求模的结果 int mypow(int a, int k) { // 对因子求模 a %= base; int res = 1; for (int _ = 0; _ < k; _++) { // 这里有乘法,是潜在的溢出点 res *= a; // 对乘法结果求模 res %= base; } return res; } int superPow(int a, vector& b) { if (b.empty()) return 1; int last = b.back(); b.pop_back(); int part1 = mypow(a, last); int part2 = mypow(superPow(a, b), 10); // 每次乘法都要求模 return (part1 * part2) % base; } }; ``` ```go // by chatGPT (go) const base = 1337 // 计算 a 的 k 次方然后与 base 求模的结果 func mypow(a, k int) int { // 对因子求模 a %= base res := 1 for i := 0; i < k; i++ { // 这里有乘法,是潜在的溢出点 res *= a // 对乘法结果求模 res %= base } return res } func superPow(a int, b []int) int { if len(b) == 0 { return 1 } last := b[len(b)-1] b = b[:len(b)-1] part1 := mypow(a, last) part2 := mypow(superPow(a, b), 10) // 每次乘法都要求模 return (part1 * part2) % base } ``` ```java // by chatGPT (java) class Solution { int base = 1337; // 计算 a 的 k 次方然后与 base 求模的结果 private int mypow(int a, int k) { // 对因子求模 a %= base; int res = 1; for (int i = 0; i < k; i++) { // 这里有乘法,是潜在的溢出点 res *= a; // 对乘法结果求模 res %= base; } return res; } public int superPow(int a, int[] b) { if (b.length == 0) return 1; int last = b[b.length - 1]; int[] newB = Arrays.copyOf(b, b.length - 1); int part1 = mypow(a, last); int part2 = mypow(superPow(a, newB), 10); // 每次乘法都要求模 return (part1 * part2) % base; } } ``` ```javascript // by chatGPT (javascript) var superPow = function(a, b) { const base = 1337; function mypow(a, k) { // 对因子求模 a %= base; let res = 1; for (let _ = 0; _ < k; _++) { // 这里有乘法,是潜在的溢出点 res *= a; // 对乘法结果求模 res %= base; } return res; } if (b.length === 0) return 1; const last = b.pop(); const part1 = mypow(a, last); const part2 = mypow(superPow(a, b), 10); // 每次乘法都要求模 return (part1 * part2) % base; }; ``` ```python # by chatGPT (python) class Solution: def __init__(self): self.base = 1337 # 计算 a 的 k 次方然后与 base 求模的结果 def mypow(self, a: int, k: int) -> int: # 对因子求模 a %= self.base res = 1 for _ in range(k): # 这里有乘法,是潜在的溢出点 res = (res * a) % self.base return res def superPow(self, a: int, b: List[int]) -> int: if not b: return 1 last = b.pop() part1 = self.mypow(a, last) part2 = self.mypow(self.superPow(a, b), 10) # 每次乘法都要求模 return (part1 * part2) % self.base ``` https://leetcode.cn/problems/super-pow 的多语言解法👆 https://leetcode.cn/problems/super-ugly-number 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int nthSuperUglyNumber(int n, vector& primes) { // 优先队列中装三元组 int[] {product, prime, pi} // 其中 product 代表链表节点的值,prime 是计算下一个节点所需的质数因子,pi 代表链表上的指针 priority_queue, vector>, greater>> pq; // 把多条链表的头结点加入优先级队列 for (int i = 0; i < primes.size(); i++) { pq.push({ 1, primes[i], 1 }); } // 可以理解为最终合并的有序链表(结果链表) vector ugly(n + 1); // 可以理解为结果链表上的指针 int p = 1; while (p <= n) { // 取三个链表的最小结点 vector pair = pq.top(); pq.pop(); int product = pair[0]; int prime = pair[1]; int index = pair[2]; // 避免结果链表出现重复元素 if (product != ugly[p - 1]) { // 接到结果链表上 ugly[p] = product; p++; } // 生成下一个节点加入优先级队列 vector nextPair = { ugly[index] * prime, prime, index + 1 }; pq.push(nextPair); } return ugly[n]; } }; ``` ```go // by chatGPT (go) func nthSuperUglyNumber(n int, primes []int) int { // 优先队列中装三元组 int[] {product, prime, pi} // 其中 product 代表链表节点的值,prime 是计算下一个节点所需的质数因子,pi 代表链表上的指针 pq := make(PriorityQueue, 0) heap.Init(&pq) // 把多条链表的头结点加入优先级队列 for i := 0; i < len(primes); i++ { pq.Push(&IntTriples{product: 1, prime: primes[i], pi: 1}) } // 可以理解为最终合并的有序链表(结果链表) ugly := make([]int, n+1) // 可以理解为结果链表上的指针 p := 1 for p <= n { // 取三个链表的最小结点 pair := heap.Pop(&pq).(*IntTriples) product, prime, index := pair.product, pair.prime, pair.pi // 避免结果链表出现重复元素 if product != ugly[p-1] { // 接到结果链表上 ugly[p] = product p++ } // 生成下一个节点加入优先级队列 nextPair := &IntTriples{product: ugly[index] * prime, prime: prime, pi: index + 1} heap.Push(&pq, nextPair) } return ugly[n] } // 优先队列的封装 type PriorityQueue []*IntTriples func (pq PriorityQueue) Len() int { return len(pq) } func (pq PriorityQueue) Less(i, j int) bool { return pq[i].product < pq[j].product } func (pq PriorityQueue) Swap(i, j int) { pq[i], pq[j] = pq[j], pq[i] } func (pq *PriorityQueue) Push(x interface{}) { item := x.(*IntTriples) *pq = append(*pq, item) } func (pq *PriorityQueue) Pop() interface{} { old := *pq n := len(old) item := old[n-1] *pq = old[0 : n-1] return item } // 封装三元组 type IntTriples struct { product int prime int pi int } ``` ```java // by labuladong (java) class Solution { public int nthSuperUglyNumber(int n, int[] primes) { // 优先队列中装三元组 int[] {product, prime, pi} // 其中 product 代表链表节点的值,prime 是计算下一个节点所需的质数因子,pi 代表链表上的指针 PriorityQueue pq = new PriorityQueue<>((a, b) -> { return a[0] - b[0]; }); // 把多条链表的头结点加入优先级队列 for (int i = 0; i < primes.length; i++) { pq.offer(new int[]{ 1, primes[i], 1 }); } // 可以理解为最终合并的有序链表(结果链表) int[] ugly = new int[n + 1]; // 可以理解为结果链表上的指针 int p = 1; while (p <= n) { // 取三个链表的最小结点 int[] pair = pq.poll(); int product = pair[0]; int prime = pair[1]; int index = pair[2]; // 避免结果链表出现重复元素 if (product != ugly[p - 1]) { // 接到结果链表上 ugly[p] = product; p++; } // 生成下一个节点加入优先级队列 int[] nextPair = new int[]{ugly[index] * prime, prime, index + 1}; pq.offer(nextPair); } return ugly[n]; } } ``` ```javascript // by chatGPT (javascript) var nthSuperUglyNumber = function(n, primes) { // 优先队列中装三元组 int[] {product, prime, pi} // 其中 product 代表链表节点的值,prime 是计算下一个节点所需的质数因子,pi 代表链表上的指针 let pq = new PriorityQueue((a, b) => { return a[0] - b[0]; }); // 把多条链表的头结点加入优先级队列 for (let i = 0; i < primes.length; i++) { pq.offer([ 1, primes[i], 1 ]); } // 可以理解为最终合并的有序链表(结果链表) let ugly = new Array(n + 1); // 可以理解为结果链表上的指针 let p = 1; while (p <= n) { // 取三个链表的最小结点 let pair = pq.poll(); let product = pair[0]; let prime = pair[1]; let index = pair[2]; // 避免结果链表出现重复元素 if (product != ugly[p - 1]) { // 接到结果链表上 ugly[p] = product; p++; } // 生成下一个节点加入优先级队列 let nextPair = [ugly[index] * prime, prime, index + 1]; pq.offer(nextPair); } return ugly[n]; }; ``` ```python # by chatGPT (python) import heapq class Solution: def nthSuperUglyNumber(self, n: int, primes: List[int]) -> int: # 优先队列中装三元组 int[] {product, prime, pi} # 其中 product 代表链表节点的值,prime 是计算下一个节点所需的质数因子,pi 代表链表上的指针 pq = [] # 把多条链表的头结点加入优先级队列 for i in range(len(primes)): heapq.heappush(pq, [1, primes[i], 1]) # 可以理解为最终合并的有序链表(结果链表) ugly = [0] * (n+1) # 可以理解为结果链表上的指针 p = 1 while p <= n: # 取三个链表的最小结点 product, prime, index = heapq.heappop(pq) # 避免结果链表出现重复元素 if product != ugly[p - 1]: # 接到结果链表上 ugly[p] = product p += 1 # 生成下一个节点加入优先级队列 nextPair = [ugly[index] * prime, prime, index + 1] heapq.heappush(pq, nextPair) return ugly[n] ``` https://leetcode.cn/problems/super-ugly-number 的多语言解法👆 https://leetcode.cn/problems/surrounded-regions 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: void solve(vector>& board) { if (board.empty()) return; int m = board.size(); int n = board[0].size(); // 给 dummy 留一个额外位置 UF uf = UF(m * n + 1); int dummy = m * n; // 将首列和末列的 O 与 dummy 连通 for (int i = 0; i < m; i++) { if (board[i][0] == 'O') uf.union(i * n, dummy); if (board[i][n - 1] == 'O') uf.union(i * n + n - 1, dummy); } // 将首行和末行的 O 与 dummy 连通 for (int j = 0; j < n; j++) { /** ![](../pictures/unionfind应用/3.jpg) */ if (board[0][j] == 'O') uf.union(j, dummy); if (board[m - 1][j] == 'O') uf.union(n * (m - 1) + j, dummy); } // 方向数组 d 是上下左右搜索的常用手法 vector> d = {{1, 0}, {0, 1}, {0, -1}, {-1, 0}}; 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 k = 0; k < 4; k++) { int x = i + d[k][0]; int y = j + d[k][1]; if (board[x][y] == 'O') uf.union(x * n + y, i * n + j); } // 所有不和 dummy 连通的 O,都要被替换 for (int i = 1; i < m - 1; i++) for (int j = 1; j < n - 1; j++) if (!uf.connected(dummy, i * n + j)) board[i][j] = 'X'; } }; class UF { public: // 记录连通分量个数 int count; // 存储若干棵树 vector parent; // 记录树的“重量” vector size; UF(int n) { this->count = n; parent = vector(n); size = vector(n); for (int i = 0; i < n; i++) { parent[i] = i; size[i] = 1; } } /* 将 p 和 q 连通 */ void union(int p, int q) { int rootP = find(p); int rootQ = find(q); if (rootP == rootQ) return; // 小树接到大树下面,较平衡 if (size[rootP] > size[rootQ]) { parent[rootQ] = rootP; size[rootP] += size[rootQ]; } else { parent[rootP] = rootQ; size[rootQ] += size[rootP]; } count--; } /* 判断 p 和 q 是否互相连通 */ bool connected(int p, int q) { int rootP = find(p); int rootQ = find(q); // 处于同一棵树上的节点,相互连通 return rootP == rootQ; } /* 返回节点 x 的根节点 */ int find(int x) { while (parent[x] != x) { // 进行路径压缩 parent[x] = parent[parent[x]]; x = parent[x]; } return x; } }; ``` ```go // by chatGPT (go) func solve(board [][]byte) { if len(board) == 0 { return } m := len(board) n := len(board[0]) // 给 dummy 留一个额外位置 uf := NewUF(m*n + 1) dummy := m*n // 将首列和末列的 O 与 dummy 连通 for i := 0; i < m; i++ { if board[i][0] == 'O' { uf.union(i*n, dummy) } if board[i][n-1] == 'O' { uf.union(i*n+n-1, dummy) } } // 将首行和末行的 O 与 dummy 连通 for j := 0; j < n; j++ { if board[0][j] == 'O' { uf.union(j, dummy) } if board[m-1][j] == 'O' { uf.union(n*(m-1)+j, dummy) } } // 方向数组 d 是上下左右搜索的常用手法 d := [][]int{{1, 0}, {0, 1}, {0, -1}, {-1, 0}} for i := 1; i < m-1; i++ { for j := 1; j < n-1; j++ { if board[i][j] == 'O' { // 将此 O 与上下左右的 O 连通 for k := 0; k < 4; k++ { x := i + d[k][0] y := j + d[k][1] if board[x][y] == 'O' { uf.union(x*n+y, i*n+j) } } } } } // 所有不和 dummy 连通的 O,都要被替换 for i := 1; i < m-1; i++ { for j := 1; j < n-1; j++ { if !uf.connected(dummy, i*n+j) { board[i][j] = 'X' } } } } type UF struct { // 记录连通分量个数 count int // 存储若干棵树 parent []int // 记录树的“重量” size []int } func NewUF(n int) *UF { u := &UF{count: n, parent: make([]int, n), size: make([]int, n)} for i := 0; i < n; i++ { u.parent[i] = i u.size[i] = 1 } return u } /* 将 p 和 q 连通 */ func (u *UF) union(p, q int) { rootP := u.find(p) rootQ := u.find(q) if rootP == rootQ { return } // 小树接到大树下面,较平衡 if u.size[rootP] > u.size[rootQ] { u.parent[rootQ] = rootP u.size[rootP] += u.size[rootQ] } else { u.parent[rootP] = rootQ u.size[rootQ] += u.size[rootP] } u.count-- } /* 判断 p 和 q 是否互相连通 */ func (u *UF) connected(p, q int) bool { return u.find(p) == u.find(q) } /* 返回节点 x 的根节点 */ func (u *UF) find(x int) int { for u.parent[x] != x { // 进行路径压缩 u.parent[x] = u.parent[u.parent[x]] x = u.parent[x] } return x } func (u *UF) Count() int { return u.count } ``` ```java // by labuladong (java) class Solution { public void solve(char[][] board) { if (board.length == 0) return; int m = board.length; int 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(i * n, dummy); if (board[i][n - 1] == 'O') uf.union(i * n + n - 1, dummy); } // 将首行和末行的 O 与 dummy 连通 for (int j = 0; j < n; j++) { /** ![](../pictures/unionfind应用/3.jpg) */ if (board[0][j] == 'O') uf.union(j, dummy); if (board[m - 1][j] == 'O') uf.union(n * (m - 1) + j, dummy); } // 方向数组 d 是上下左右搜索的常用手法 int[][] d = new int[][]{{1, 0}, {0, 1}, {0, -1}, {-1, 0}}; 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 k = 0; k < 4; k++) { int x = i + d[k][0]; int y = j + d[k][1]; if (board[x][y] == 'O') uf.union(x * n + y, i * n + j); } // 所有不和 dummy 连通的 O,都要被替换 for (int i = 1; i < m - 1; i++) for (int j = 1; j < n - 1; j++) if (!uf.connected(dummy, i * n + j)) board[i][j] = 'X'; } } class UF { // 记录连通分量个数 private int count; // 存储若干棵树 private int[] parent; // 记录树的“重量” private int[] size; public UF(int n) { this.count = n; parent = new int[n]; size = new int[n]; for (int i = 0; i < n; i++) { parent[i] = i; size[i] = 1; } } /* 将 p 和 q 连通 */ public void union(int p, int q) { int rootP = find(p); int rootQ = find(q); if (rootP == rootQ) return; // 小树接到大树下面,较平衡 if (size[rootP] > size[rootQ]) { parent[rootQ] = rootP; size[rootP] += size[rootQ]; } else { parent[rootP] = rootQ; size[rootQ] += size[rootP]; } count--; } /* 判断 p 和 q 是否互相连通 */ public boolean connected(int p, int q) { int rootP = find(p); int rootQ = find(q); // 处于同一棵树上的节点,相互连通 return rootP == rootQ; } /* 返回节点 x 的根节点 */ private int find(int x) { while (parent[x] != x) { // 进行路径压缩 parent[x] = parent[parent[x]]; x = parent[x]; } return x; } public int count() { return count; } } ``` ```javascript // by chatGPT (javascript) /** * @param {character[][]} board * @return {void} Do not return anything, modify board in-place instead. */ var solve = function(board) { if (board.length == 0) return; const m = board.length; const n = board[0].length; // 给 dummy 留一个额外位置 const uf = new UF(m * n + 1); const dummy = m * n; // 将首列和末列的 O 与 dummy 连通 for (let i = 0; i < m; i++) { if (board[i][0] == 'O') uf.union(i * n, dummy); if (board[i][n - 1] == 'O') uf.union(i * n + n - 1, dummy); } // 将首行和末行的 O 与 dummy 连通 const d = [[1, 0], [0, 1], [0, -1], [-1, 0]]; for (let j = 0; j < n; j++) { /** ![](../pictures/unionfind应用/3.jpg) */ if (board[0][j] == 'O') uf.union(j, dummy); if (board[m - 1][j] == 'O') uf.union(n * (m - 1) + j, dummy); } // 方向数组 d 是上下左右搜索的常用手法 for (let i = 1; i < m - 1; i++) for (let j = 1; j < n - 1; j++) if (board[i][j] == 'O') // 将此 O 与上下左右的 O 连通 for (let k = 0; k < 4; k++) { const x = i + d[k][0]; const y = j + d[k][1]; if (board[x][y] == 'O') uf.union(x * n + y, i * n + j); } // 所有不和 dummy 连通的 O,都要被替换 for (let i = 1; i < m - 1; i++) for (let j = 1; j < n - 1; j++) if (!uf.connected(dummy, i * n + j)) board[i][j] = 'X'; } class UF { // 记录连通分量个数 count; // 存储若干棵树 parent; // 记录树的“重量” size; constructor(n) { this.count = n; this.parent = []; this.size = []; for (let i = 0; i < n; i++) { this.parent[i] = i; this.size[i] = 1; } } /* 将 p 和 q 连通 */ union(p, q) { const rootP = this.find(p); const rootQ = this.find(q); if (rootP == rootQ) return; // 小树接到大树下面,较平衡 if (this.size[rootP] > this.size[rootQ]) { this.parent[rootQ] = rootP; this.size[rootP] += this.size[rootQ]; } else { this.parent[rootP] = rootQ; this.size[rootQ] += this.size[rootP]; } this.count--; } /* 判断 p 和 q 是否互相连通 */ connected(p, q) { const rootP = this.find(p); const rootQ = this.find(q); // 处于同一棵树上的节点,相互连通 return rootP == rootQ; } /* 返回节点 x 的根节点 */ find(x) { while (this.parent[x] != x) { // 进行路径压缩 this.parent[x] = this.parent[this.parent[x]]; x = this.parent[x]; } return x; } count() { return this.count; } } ``` ```python # by chatGPT (python) class Solution: def solve(self, board: List[List[str]]) -> None: """ Do not return anything, modify board in-place instead. """ if not board: return m, n = len(board), len(board[0]) # 给 dummy 留一个额外位置 uf = UF(m * n + 1) dummy = m * n # 将首列和末列的 O 与 dummy 连通 for i in range(m): if board[i][0] == 'O': uf.union(i * n, dummy) if board[i][n - 1] == 'O': uf.union(i * n + n - 1, dummy) # 将首行和末行的 O 与 dummy 连通 for j in range(n): # extend up -150 if board[0][j] == 'O': uf.union(j, dummy) if board[m - 1][j] == 'O': uf.union(n * (m - 1) + j, dummy) # 方向数组 d 是上下左右搜索的常用手法 d = [[1, 0], [0, 1], [0, -1], [-1, 0]] for i in range(1, m - 1): for j in range(1, n - 1): if board[i][j] == 'O': # 将此 O 与上下左右的 O 连通 for k in range(4): x = i + d[k][0] y = j + d[k][1] if board[x][y] == 'O': uf.union(x * n + y, i * n + j) # 所有不和 dummy 连通的 O,都要被替换 for i in range(1, m - 1): for j in range(1, n - 1): if not uf.connected(dummy, i * n + j): board[i][j] = 'X' class UF: # 记录连通分量个数 def __init__(self, n: int): self.count = n # 存储若干棵树 self.parent = list(range(n)) # 记录树的“重量” self.size = [1] * n # 将 p 和 q 连通 def union(self, p: int, q: int) -> None: rootP = self.find(p) rootQ = self.find(q) if rootP == rootQ: return # 小树接到大树下面,较平衡 if self.size[rootP] > self.size[rootQ]: self.parent[rootQ] = rootP self.size[rootP] += self.size[rootQ] else: self.parent[rootP] = rootQ self.size[rootQ] += self.size[rootP] self.count -= 1 # 判断 p 和 q 是否互相连通 def connected(self, p: int, q: int) -> bool: rootP = self.find(p) rootQ = self.find(q) # 处于同一棵树上的节点,相互连通 return rootP == rootQ # 返回节点 x 的根节点 def find(self, x: int) -> int: while self.parent[x] != x: # 进行路径压缩 self.parent[x] = self.parent[self.parent[x]] x = self.parent[x] return x def count(self) -> int: return self.count ``` https://leetcode.cn/problems/surrounded-regions 的多语言解法👆 https://leetcode.cn/problems/swap-nodes-in-pairs 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { // 定义:输入以 head 开头的单链表,将这个单链表中的每两个元素翻转, // 返回翻转后的链表头结点 public: ListNode* swapPairs(ListNode* head) { if (head == nullptr || head->next == nullptr) { //递归结束条件 return head; } ListNode* first = head; ListNode* second = head->next; ListNode* others = head->next->next; // 先把前两个元素翻转 second->next = first; // 利用递归定义,将剩下的链表节点两两翻转,接到后面 first->next = swapPairs(others); // 现在整个链表都成功翻转了,返回新的头结点 return second; /** ![](../pictures/kgroup/7.jpg) */ } }; ``` ```go // by chatGPT (go) // 定义:输入以 head 开头的单链表,将这个单链表中的每两个元素翻转, // 返回翻转后的链表头结点 func swapPairs(head *ListNode) *ListNode { if head == nil || head.Next == nil { return head } first := head second := head.Next others := head.Next.Next // 先把前两个元素翻转 second.Next = first // 利用递归定义,将剩下的链表节点两两翻转,接到后面 first.Next = swapPairs(others) // 现在整个链表都成功翻转了,返回新的头结点 return second } ``` ```java // by labuladong (java) class Solution { // 定义:输入以 head 开头的单链表,将这个单链表中的每两个元素翻转, // 返回翻转后的链表头结点 public ListNode swapPairs(ListNode head) { if (head == null || head.next == null) { return head; } ListNode first = head; ListNode second = head.next; ListNode others = head.next.next; // 先把前两个元素翻转 second.next = first; // 利用递归定义,将剩下的链表节点两两翻转,接到后面 first.next = swapPairs(others); // 现在整个链表都成功翻转了,返回新的头结点 return second; /** ![](../pictures/kgroup/7.jpg) */ } } ``` ```javascript // by chatGPT (javascript) /** * 定义:输入以 head 开头的单链表,将这个单链表中的每两个元素翻转, * 返回翻转后的链表头结点 */ var swapPairs = function(head) { if (head == null || head.next == null) { return head; } // 先把前两个元素翻转 let first = head; let second = head.next; let others = head.next.next; second.next = first; // 利用递归定义,将剩下的链表节点两两翻转,接到后面 first.next = swapPairs(others); // 现在整个链表都成功翻转了,返回新的头结点 return second; /** ![](../pictures/kgroup/7.jpg) */ }; ``` ```python # by chatGPT (python) # Definition for singly-linked list. # class ListNode: # def __init__(self, x): # self.val = x # self.next = None class Solution: def swapPairs(self, head: ListNode) -> ListNode: """ :type head: ListNode :rtype: ListNode 输入以 head 开头的单链表,将这个单链表中的每两个元素翻转, 返回翻转后的链表头结点 """ if not head or not head.next: # 如果当前节点 head 为空, 或者下一个节点 head.next 为空, 将 head 直接返回 return head # 定义三个变量, 分别为当前节点, 当前节点的下一个节点和下下个节点 first, second, others = head, head.next, head.next.next # 先把前两个元素翻转 second.next = first # 利用递归定义, 将剩下的链表节点两两翻转, 接到后面 first.next = self.swapPairs(others) # 现在整个链表都成功翻转了, 返回新的头结点 return second """ 原链表为:head → 1 → 2 → 3 → 4 → null, 要求链表为:head → 2 → 1 → 4 → 3 → null。 ![](../pictures/kgroup/7.jpg) """ ``` https://leetcode.cn/problems/swap-nodes-in-pairs 的多语言解法👆 https://leetcode.cn/problems/target-sum 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int findTargetSumWays(vector& nums, int target) { if (nums.empty()) return 0; return dp(nums, 0, target); } // 备忘录 unordered_map memo; int dp(vector& nums, int i, int remain) { // base case if (i == nums.size()) { if (remain == 0) return 1; return 0; } // 把它俩转成字符串才能作为哈希表的键 string key = to_string(i) + "," + to_string(remain); // 避免重复计算 if (memo.count(key)) { return memo[key]; } // 还是穷举 int result = dp(nums, i + 1, remain - nums[i]) + dp(nums, i + 1, remain + nums[i]); // 记入备忘录 memo[key] = result; return result; } }; ``` ```go // by chatGPT (go) func findTargetSumWays(nums []int, target int) int { // 如果nums为空,则直接返回0 if len(nums) == 0 { return 0 } // 定义备忘录 memo := make(map[string]int) // 调用DP函数 return dp(nums, 0, target, memo) } func dp(nums []int, i, remain int, memo map[string]int) int { // base case if i == len(nums) { if remain == 0 { return 1 } return 0 } // 把它俩转成字符串才能作为哈希表的键 key := strconv.Itoa(i) + "," + strconv.Itoa(remain) // 避免重复计算 if val, ok := memo[key]; ok { return val } // 还是穷举 result := dp(nums, i+1, remain-nums[i], memo) + dp(nums, i+1, remain+nums[i], memo) // 记入备忘录 memo[key] = result return result } ``` ```java // by labuladong (java) class Solution { public int findTargetSumWays(int[] nums, int target) { if (nums.length == 0) return 0; return dp(nums, 0, target); } // 备忘录 HashMap memo = new HashMap<>(); int dp(int[] nums, int i, int remain) { // base case if (i == nums.length) { if (remain == 0) return 1; return 0; } // 把它俩转成字符串才能作为哈希表的键 String key = i + "," + remain; // 避免重复计算 if (memo.containsKey(key)) { return memo.get(key); } // 还是穷举 int result = dp(nums, i + 1, remain - nums[i]) + dp(nums, i + 1, remain + nums[i]); // 记入备忘录 memo.put(key, result); return result; } } ``` ```javascript // by chatGPT (javascript) // 传入一个整数数组和目标值target,求有多少种方法使得数组中的元素之和为target,每个元素可以选择加或者减 var findTargetSumWays = function (nums, target) { // 数组长度为0直接返回0 if (nums.length === 0) return 0; // 备忘录哈希表,记录已经计算过的子问题的结果,避免重复计算 const memo = new Map(); // 递归函数 const dp = (nums, i, remain) => { // 如果已经遍历到了数组的末尾,判断remain是否等于0,如果等于0则返回1,否则返回0 if (i === nums.length) { if (remain === 0) return 1; return 0; } // 用i和remain作为键,判断是否已经计算过这个子问题,如果已经计算,则直接返回结果,否则继续计算 const key = i + "," + remain; if (memo.has(key)) { return memo.get(key); } // 分别递归计算加上和减去当前元素后能够得到目标值的方案数量,相加得到当前子问题的解 const result = dp(nums, i + 1, remain - nums[i]) + dp(nums, i + 1, remain + nums[i]); // 把计算结果存入备忘录 memo.set(key, result); // 返回当前子问题的解 return result; }; // 从0开始遍历数组,计算从每个索引开始的子问题,最终得到问题的解 return dp(nums, 0, target); }; ``` ```python # by chatGPT (python) class Solution: def findTargetSumWays(self, nums: List[int], target: int) -> int: if len(nums) == 0: return 0 # 调用备忘录函数 return self.dp(nums, 0, target, {}) def dp(self, nums, i, remain, memo): # base case if i == len(nums): if remain == 0: return 1 return 0 # 把它俩转成字符串才能作为哈希表的键 key = str(i) + "," + str(remain) # 避免重复计算 if key in memo: return memo[key] # 还是穷举 result = self.dp(nums, i + 1, remain - nums[i], memo) + self.dp(nums, i + 1, remain + nums[i], memo) # 记入备忘录 memo[key] = result return result ``` https://leetcode.cn/problems/target-sum 的多语言解法👆 https://leetcode.cn/problems/the-maze 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: bool hasPath(vector>& maze, vector& start, vector& destination) { int m = maze.size(), n = maze[0].size(); // 方向数组,方便上下左右移动 int dirs[4][2] = {{0,1},{0,-1},{1,0},{-1,0}}; // BFS 算法的队列和 visited 数组 queue> q; vector> visited(m, vector(n, false)); // 加入起点 q.push(start); visited[start[0]][start[1]] = true; // 启动 BFS 算法框架 while (!q.empty()) { vector cur = q.front(); q.pop(); // 向四个方向扩展 for (auto dir : dirs) { int x = cur[0], y = cur[1]; // 和其他题目不同的是,这里一直走到墙,而不是只走一步 while (x >= 0 && x < m && y >= 0 && y < n && maze[x][y] == 0) { x += dir[0]; y += dir[1]; } x -= dir[0]; y -= dir[1]; // 判断是否到达终点 if (x == destination[0] && y == destination[1]) { return true; } if (!visited[x][y]) { visited[x][y] = true; q.push({x, y}); } } } return false; } }; ``` ```go // by chatGPT (go) import "fmt" func hasPath(maze [][]int, start []int, destination []int) bool { m, n := len(maze), len(maze[0]) // 方向数组,方便上下左右移动 dirs := [][]int{{0, 1}, {0, -1}, {1, 0}, {-1, 0}} q := [][]int{start} visited := make([][]bool, m) for i := range visited { visited[i] = make([]bool, n) } visited[start[0]][start[1]] = true // 启动 BFS 算法框架 for len(q) > 0 { cur := q[0] q = q[1:] // 向四个方向扩展 for _, dir := range dirs { x, y := cur[0], cur[1] // 和其他题目不同的是,这里一直走到墙,而不是只走一步 for x >= 0 && x < m && y >= 0 && y < n && maze[x][y] == 0 { x += dir[0] y += dir[1] } x -= dir[0] y -= dir[1] // 判断是否到达终点 if x == destination[0] && y == destination[1] { return true } if !visited[x][y] { visited[x][y] = true q = append(q, []int{x, y}) } } } return false } func main() { maze := [][]int{ {0,0,1,0,0}, {0,0,0,0,0}, {0,0,0,1,0}, {1,1,0,1,1}, {0,0,0,0,0}, } start := []int{0, 4} destination := []int{4, 4} fmt.Println(hasPath(maze, start, destination)) // Output: true } ``` ```java // by labuladong (java) class Solution { public boolean hasPath(int[][] maze, int[] start, int[] destination) { int m = maze.length, n = maze[0].length; // 方向数组,方便上下左右移动 int[][] dirs = {{0,1},{0,-1},{1,0},{-1,0}}; // BFS 算法的队列和 visited 数组 Queue q = new LinkedList<>(); boolean[][] visited = new boolean[m][n]; // 加入起点 q.offer(start); visited[start[0]][start[1]] = true; // 启动 BFS 算法框架 while (!q.isEmpty()) { int[] cur = q.poll(); // 向四个方向扩展 for (int[] dir : dirs) { int x = cur[0], y = cur[1]; // 和其他题目不同的是,这里一直走到墙,而不是只走一步 while (x >= 0 && x < m && y >= 0 && y < n && maze[x][y] == 0) { x += dir[0]; y += dir[1]; } x -= dir[0]; y -= dir[1]; // 判断是否到达终点 if (x == destination[0] && y == destination[1]) { return true; } if (!visited[x][y]) { visited[x][y] = true; q.offer(new int[]{x, y}); } } } return false; } } ``` ```javascript // by chatGPT (javascript) var hasPath = function(maze, start, destination) { var m = maze.length, n = maze[0].length; // 方向数组,方便上下左右移动 var dirs = [[0,1],[0,-1],[1,0],[-1,0]]; // BFS 算法的队列和 visited 数组 var q = []; var visited = new Array(m); for (var i = 0; i < m; i++) { visited[i] = new Array(n).fill(false); } // 加入起点 q.push(start); visited[start[0]][start[1]] = true; // 启动 BFS 算法框架 while (q.length > 0) { var cur = q.shift(); // 向四个方向扩展 for (var i = 0; i < 4; i++) { var dir = dirs[i]; var x = cur[0], y = cur[1]; // 和其他题目不同的是,这里一直走到墙,而不是只走一步 while (x >= 0 && x < m && y >= 0 && y < n && maze[x][y] == 0) { x += dir[0]; y += dir[1]; } x -= dir[0]; y -= dir[1]; // 判断是否到达终点 if (x == destination[0] && y == destination[1]) { return true; } if (!visited[x][y]) { visited[x][y] = true; q.push([x, y]); } } } return false; }; ``` ```python # by chatGPT (python) from typing import List from queue import Queue class Solution: def hasPath(self, maze: List[List[int]], start: List[int], destination: List[int]) -> bool: m, n = len(maze), len(maze[0]) # 获取矩阵的行和列 dirs = [(0, 1), (0, -1), (1, 0), (-1, 0)] # 四个方向 q = Queue() # BFS 算法的队列 visited = [[False for _ in range(n)] for _ in range(m)] # visited 数组 q.put(start) # 加入起点 visited[start[0]][start[1]] = True # 设置该点已访问 # 启动 BFS 算法框架 while not q.empty(): x, y = q.get() # 向四个方向扩展 for dir in dirs: # 和其他题目不同的是,这里一直走到墙,而不是只走一步 nr, nc = x, y while 0 <= nr < m and 0 <= nc < n and not maze[nr][nc]: nr += dir[0] nc += dir[1] nr -= dir[0] # 走过墙后需要回退一步 nc -= dir[1] # 判断是否到达终点 if (nr, nc) == tuple(destination): return True if not visited[nr][nc]: visited[nr][nc] = True # 设置该点已访问 q.put((nr, nc)) return False ``` https://leetcode.cn/problems/the-maze 的多语言解法👆 https://leetcode.cn/problems/the-maze-ii 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int shortestDistance(vector>& maze, vector& start, vector& destination) { int m = maze.size(), n = maze[0].size(); // 方向数组,方便上下左右移动 vector> dirs = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}}; // BFS 算法的队列和 visited 数组 queue> q; vector> visited(m, vector(n, false)); // 加入起点 q.push(start); visited[start[0]][start[1]] = true; // 启动 BFS 算法框架 while (!q.empty()) { vector cur = q.front(); q.pop(); // 向四个方向扩展 for (auto dir : dirs) { int x = cur[0], y = cur[1]; int step = 0; // 和其他题目不同的是,这里一直走到墙,而不是只走一步,同时要记录走过的步数 while (x >= 0 && x < m && y >= 0 && y < n && maze[x][y] == 0) { x += dir[0]; y += dir[1]; step++; } x -= dir[0]; y -= dir[1]; step--; // 判断是否到达终点 if (x == destination[0] && y == destination[1]) { return step; } if (!visited[x][y]) { visited[x][y] = true; q.push({x, y}); } } } return -1; } }; ``` ```go // by chatGPT (go) import "container/list" func shortestDistance(maze [][]int, start []int, destination []int) int { m, n := len(maze), len(maze[0]) // 方向数组,方便上下左右移动 dirs := [][]int{{0,1},{0,-1},{1,0},{-1,0}} // BFS 算法的队列和 visited 数组 q := list.New() visited := make([][]bool, m) for i := range visited { visited[i] = make([]bool, n) } // 加入起点 q.PushBack(start) visited[start[0]][start[1]] = true // 启动 BFS 算法框架 for q.Len() != 0 { cur := q.Front().Value.([]int) q.Remove(q.Front()) // 向四个方向扩展 for _, dir := range dirs { x, y := cur[0], cur[1] step := 0 // 和其他题目不同的是,这里一直走到墙,而不是只走一步,同时要记录走过的步数 for x >= 0 && x < m && y >= 0 && y < n && maze[x][y] == 0 { x += dir[0] y += dir[1] step++ } x -= dir[0] y -= dir[1] step-- // 判断是否到达终点 if x == destination[0] && y == destination[1] { return step } if !visited[x][y] { visited[x][y] = true q.PushBack([]int{x, y}) } } } return -1 } ``` ```java // by labuladong (java) class Solution { public int shortestDistance(int[][] maze, int[] start, int[] destination) { int m = maze.length, n = maze[0].length; // 方向数组,方便上下左右移动 int[][] dirs = {{0,1},{0,-1},{1,0},{-1,0}}; // BFS 算法的队列和 visited 数组 Queue q = new LinkedList<>(); boolean[][] visited = new boolean[m][n]; // 加入起点 q.offer(start); visited[start[0]][start[1]] = true; int step = 0; // 启动 BFS 算法框架 while (!q.isEmpty()) { int[] cur = q.poll(); // 向四个方向扩展 for (int[] dir : dirs) { int x = cur[0], y = cur[1]; // 和其他题目不同的是,这里一直走到墙,而不是只走一步,同时要记录走过的步数 while (x >= 0 && x < m && y >= 0 && y < n && maze[x][y] == 0) { x += dir[0]; y += dir[1]; step++; } x -= dir[0]; y -= dir[1]; step--; // 判断是否到达终点 if (x == destination[0] && y == destination[1]) { return step; } if (!visited[x][y]) { visited[x][y] = true; q.offer(new int[]{x, y}); } } } return -1; } } ``` ```javascript // by chatGPT (javascript) var shortestDistance = function(maze, start, destination) { var m = maze.length, n = maze[0].length; // 方向数组,方便上下左右移动 var dirs = [[0,1],[0,-1],[1,0],[-1,0]]; // BFS 算法的队列和 visited 数组 var q = []; var visited = new Array(m); for (var i = 0; i < m; i++) { visited[i] = new Array(n).fill(false); } // 加入起点 q.push(start); visited[start[0]][start[1]] = true; // 启动 BFS 算法框架 while (q.length > 0) { var cur = q.shift(); // 向四个方向扩展 for (var dir of dirs) { var x = cur[0], y = cur[1]; var step = 0; // 和其他题目不同的是,这里一直走到墙,而不是只走一步,同时要记录走过的步数 while (x >= 0 && x < m && y >= 0 && y < n && maze[x][y] == 0) { x += dir[0]; y += dir[1]; step++; } x -= dir[0]; y -= dir[1]; step--; // 判断是否到达终点 if (x == destination[0] && y == destination[1]) { return step; } if (!visited[x][y]) { visited[x][y] = true; q.push([x, y]); } } } return -1; }; ``` ```python # by chatGPT (python) from queue import Queue class Solution: def shortestDistance(self, maze: List[List[int]], start: List[int], destination: List[int]) -> int: m, n = len(maze), len(maze[0]) # 方向数组,方便上下左右移动 dirs = [(0,1), (0,-1), (1,0), (-1,0)] # BFS 算法的队列和 visited 数组 q = Queue() visited = [[False for _ in range(n)] for _ in range(m)] # 加入起点 q.put(start) visited[start[0]][start[1]] = True # 启动 BFS 算法框架 while not q.empty(): cur = q.get() # 向四个方向扩展 for dir in dirs: x, y = cur[0], cur[1] step = 0 # 和其他题目不同的是,这里一直走到墙,而不是只走一步,同时要记录走过的步数 while x >= 0 and x < m and y >= 0 and y < n and maze[x][y] == 0: x += dir[0] y += dir[1] step += 1 x -= dir[0] y -= dir[1] step -= 1 # 判断是否到达终点 if x == destination[0] and y == destination[1]: return step if not visited[x][y]: visited[x][y] = True q.put((x, y)) return -1 ``` https://leetcode.cn/problems/the-maze-ii 的多语言解法👆 https://leetcode.cn/problems/ti-huan-kong-ge-lcof 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: string replaceSpace(string s) { string ans; for (char c : s) { if (c == ' ') { ans += "%20"; } else { ans += c; } } return ans; } }; ``` ```go // by chatGPT (go) /** * @author ylx * @date 2021/2/2 19:19 * @description Go 实现的 LeetCode 剑指 Offer 05. 替换空格 */ func replaceSpace(s string) string { var res strings.Builder for i := 0; i < len(s); i++ { if s[i] == ' ' { res.WriteString("%20") } else { res.WriteByte(s[i]) } } return res.String() } ``` ```java // by labuladong (java) class Solution { public String replaceSpace(String s) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < s.length(); i++) { char c = s.charAt(i); if (c == ' ') { sb.append("%20"); } else { sb.append(c); } } return sb.toString(); } } ``` ```javascript // by chatGPT (javascript) var replaceSpace = function(s) { var sb = []; for (var i = 0; i < s.length; i++) { var c = s.charAt(i); if (c === ' ') { sb.push('%20'); } else { sb.push(c); } } return sb.join(''); }; ``` ```python # by chatGPT (python) class Solution: def replaceSpace(self, s: str) -> str: sb = [] for c in s: if c == ' ': sb.append('%20') else: sb.append(c) return ''.join(sb) ``` https://leetcode.cn/problems/ti-huan-kong-ge-lcof 的多语言解法👆 https://leetcode.cn/problems/top-k-frequent-elements 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: vector topKFrequent(vector& nums, int k) { // nums 中的元素 -> 该元素出现的频率 unordered_map valToFreq; for (int v : nums) { valToFreq[v]++; } priority_queue, vector>, greater<>> pq; for (auto& [val, freq] : valToFreq) { pq.emplace(freq, val); if (pq.size() > k) { pq.pop(); } } vector res(k); int i = k - 1; while (!pq.empty()) { res[i] = pq.top().second; pq.pop(); i--; } return res; } }; class Solution2 { public: vector topKFrequent(vector& nums, int k) { // nums 中的元素 -> 该元素出现的频率 unordered_map valToFreq; for (int v : nums) { valToFreq[v]++; } // 频率 -> 这个频率有哪些元素 vector> freqToVals(nums.size() + 1); for (auto& [val, freq] : valToFreq) { freqToVals[freq].push_back(val); } vector res(k); int p = 0; // freqToVals 从后往前存储着出现最多的元素 for (int i = freqToVals.size() - 1; i > 0; i--) { for (int j = 0; j < freqToVals[i].size(); j++) { res[p] = freqToVals[i][j]; p++; if (p == k) { return res; } } } return res; } }; ``` ```go // by chatGPT (go) // 用优先级队列解决这道题 func topKFrequent(nums []int, k int) []int { // nums 中的元素 -> 该元素出现的频率 valToFreq := make(map[int]int) for _, v := range nums { valToFreq[v]++ } // 定义优先队列,队列按照键值对中的值(元素出现频率)从小到大排序。 pq := priorityQueue{} for key, value := range valToFreq { pq = pq.Push(MapEntry{key, value}) if len(pq) > k { pq.Pop() } } res := make([]int, k) for i := k - 1; i >= 0; i-- { // res 数组中存储前 k 个最大元素 res[i] = pq.Pop().key } return res } // 定义优先级队列 type MapEntry struct { key int value int } type priorityQueue []MapEntry func (pq priorityQueue) Len() int { return len(pq) } func (pq priorityQueue) Swap(i, j int) { pq[i], pq[j] = pq[j], pq[i] } func (pq priorityQueue) Less(i, j int) bool { return pq[i].value < pq[j].value } func (pq priorityQueue) Push(x interface{}) priorityQueue { item := x.(MapEntry) return append(pq, item) } func (pq priorityQueue) Pop() MapEntry { item := pq[pq.Len()-1] pq = pq[:pq.Len()-1] return item } // 用计数排序的方法解决这道题 func topKFrequent2(nums []int, k int) []int { // nums 中的元素 -> 该元素出现的频率 valToFreq := make(map[int]int) for _, v := range nums { valToFreq[v]++ } // 频率 -> 这个频率有哪些元素 freqToVals := make([][]int, len(nums)+1) for val, freq := range valToFreq { if _, ok := freqToVals[freq]; !ok { freqToVals[freq] = []int{} } freqToVals[freq] = append(freqToVals[freq], val) } res := make([]int, 0, k) // freqToVals 从后往前存储着出现最多的元素 for i := len(freqToVals) - 1; i > 0; i-- { if len(freqToVals[i]) == 0 { continue } res = append(res, freqToVals[i]...) if len(res) >= k { res = res[:k] break } } return res } ``` ```java // by labuladong (java) // 用优先级队列解决这道题 class Solution { public int[] topKFrequent(int[] nums, int k) { // nums 中的元素 -> 该元素出现的频率 HashMap valToFreq = new HashMap<>(); for (int v : nums) { valToFreq.put(v, valToFreq.getOrDefault(v, 0) + 1); } PriorityQueue> pq = new PriorityQueue<>((entry1, entry2) -> { // 队列按照键值对中的值(元素出现频率)从小到大排序 return entry1.getValue().compareTo(entry2.getValue()); }); for (Map.Entry entry : valToFreq.entrySet()) { pq.offer(entry); if (pq.size() > k) { // 弹出最小元素,维护队列内是 k 个频率最大的元素 pq.poll(); } } int[] res = new int[k]; for (int i = k - 1; i >= 0; i--) { // res 数组中存储前 k 个最大元素 res[i] = pq.poll().getKey(); } return res; } } // 用计数排序的方法解决这道题 class Solution2 { public int[] topKFrequent(int[] nums, int k) { // nums 中的元素 -> 该元素出现的频率 HashMap valToFreq = new HashMap<>(); for (int v : nums) { valToFreq.put(v, valToFreq.getOrDefault(v, 0) + 1); } // 频率 -> 这个频率有哪些元素 ArrayList[] freqToVals = new ArrayList[nums.length + 1]; for (int val : valToFreq.keySet()) { int freq = valToFreq.get(val); if (freqToVals[freq] == null) { freqToVals[freq] = new ArrayList<>(); } freqToVals[freq].add(val); } int[] res = new int[k]; int p = 0; // freqToVals 从后往前存储着出现最多的元素 for (int i = freqToVals.length - 1; i > 0; i--) { ArrayList valList = freqToVals[i]; if (valList == null) continue; for (int j = 0; j < valList.size(); j++) { // 将出现次数最多的 k 个元素装入 res res[p] = valList.get(j); p++; if (p == k) { return res; } } } return null; } } ``` ```javascript // by chatGPT (javascript) var topKFrequent = function(nums, k) { // nums 中的元素 -> 该元素出现的频率 const valToFreq = new Map(); for (let v of nums) { valToFreq.set(v, valToFreq.get(v) ? valToFreq.get(v) + 1: 1); } const pq = new PriorityQueue((a, b) => a.value < b.value); for (let [key, value] of valToFreq) { pq.offer({key, value}); if (pq.size() > k) { pq.poll(); } } const res = []; for (let i = 0; i < k; i++) { res.push(pq.poll().key); } return res; }; ``` ```python # by chatGPT (python) # 用优先级队列解决这道题 class Solution: def topKFrequent(self, nums: List[int], k: int) -> List[int]: # nums 中的元素 -> 该元素出现的频率 valToFreq = {} for v in nums: valToFreq[v] = valToFreq.get(v, 0) + 1 pq = [] # 按照键值对中的值(元素出现频率)从小到大排序 for entry in valToFreq.items(): heapq.heappush(pq, (entry[1], entry[0])) if len(pq) > k: # 弹出最小元素,维护队列内是 k 个频率最大的元素 heapq.heappop(pq) res = [] for i in range(k - 1, -1, -1): # res 数组中存储前 k 个最大元素 res.append(heapq.heappop(pq)[1]) return res[::-1] # 用计数排序的方法解决这道题 class Solution2: def topKFrequent(self, nums: List[int], k: int) -> List[int]: # nums 中的元素 -> 该元素出现的频率 valToFreq = {} for v in nums: valToFreq[v] = valToFreq.get(v, 0) + 1 # 频率 -> 这个频率有哪些元素 freqToVals = [[] for _ in range(len(nums) + 1)] for val, freq in valToFreq.items(): freqToVals[freq].append(val) res = [] # freqToVals 从后往前存储着出现最多的元素 for i in range(len(freqToVals) - 1, 0, -1): valList = freqToVals[i] for j in range(len(valList)): # 将出现次数最多的 k 个元素装入 res res.append(valList[j]) if len(res) == k: return res return res ``` https://leetcode.cn/problems/top-k-frequent-elements 的多语言解法👆 https://leetcode.cn/problems/top-k-frequent-words 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: vector topKFrequent(vector& words, int k) { // 字符串 -> 该字符串出现的频率 unordered_map wordToFreq; for (string word : words) { wordToFreq[word] += 1; } // 重载 lambda 表达式,处理相同频率的集合按字典序优先级 auto cmp = [&](const pair& a, const pair& b) { if (a.second == b.second) { return a.first < b.first; } return a.second > b.second; }; priority_queue, vector>, decltype(cmp)> pq(cmp); // 维护出现频率最多的 k 个字符串 for (auto it : wordToFreq) { pq.emplace(it.first, it.second); if (pq.size() > k) { pq.pop(); } } // 把出现次数最多的 k 个字符串返回 vector res(k, ""); for (int i = k - 1; i >= 0; --i) { res[i] = pq.top().first; pq.pop(); } return res; } }; ``` ```go // by chatGPT (go) func topKFrequent(words []string, k int) []string { // 字符串 -> 该字符串出现的频率 wordToFreq := make(map[string]int) for _, word := range words { wordToFreq[word]++ } pq := priorityQueue{} heap.Init(&pq) // entry结构体数组 type entry struct { key string value int } // 大顶堆函数 heapFunc := func(a, b *entry) bool { // 注意这里,如果出现频率相同,按照字符串字典序排序 if a.value == b.value { return a.key > b.key } // 队列按照字符串出现频率从小到大排序 return a.value < b.value } // 维护出现频率最多的 k 个单词 for key, value := range wordToFreq { en := &entry{key, value} if pq.Len() == k { if heapFunc(pq[0], en) { heap.Pop(&pq) heap.Push(&pq, en) } } else { heap.Push(&pq, en) } } // 把出现次数最多的 k 个字符串返回 res := make([]string, k) for i := k - 1; i >= 0; i-- { res[i] = heap.Pop(&pq).(*entry).key } return res } // 定义堆排序函数 type priorityQueue []*entry func (pq priorityQueue) Len() int { return len(pq) } func (pq priorityQueue) Less(i, j int) bool { // 注意这里,仿佛要反着来 return pq[i].value > pq[j].value } func (pq priorityQueue) Swap(i, j int) { pq[i], pq[j] = pq[j], pq[i] } func (pq *priorityQueue) Push(x interface{}) { // 注意类型转换 item := x.(*entry) *pq = append(*pq, item) } func (pq *priorityQueue) Pop() interface{} { old := *pq n := len(old) item := old[n-1] *pq = old[0 : n-1] return item } ``` ```java // by labuladong (java) class Solution { public List topKFrequent(String[] words, int k) { // 字符串 -> 该字符串出现的频率 HashMap wordToFreq = new HashMap<>(); for (String word : words) { wordToFreq.put(word, wordToFreq.getOrDefault(word, 0) + 1); } PriorityQueue> pq = new PriorityQueue<>( (entry1, entry2) -> { if (entry1.getValue().equals(entry2.getValue())) { // 如果出现频率相同,按照字符串字典序排序 return entry2.getKey().compareTo(entry1.getKey()); } // 队列按照字符串出现频率从小到大排序 return entry1.getValue().compareTo(entry2.getValue()); }); // 按照字符串频率升序排序 for (Map.Entry entry : wordToFreq.entrySet()) { pq.offer(entry); if (pq.size() > k) { // 维护出现频率最多的 k 个单词 pq.poll(); } } // 把出现次数最多的 k 个字符串返回 LinkedList res = new LinkedList<>(); while (!pq.isEmpty()) { res.addFirst(pq.poll().getKey()); } return res; } } ``` ```javascript // by chatGPT (javascript) var topKFrequent = function(words, k) { // 字符串 -> 该字符串出现的频率 let wordToFreq = new Map(); for (let word of words) { wordToFreq.set(word, wordToFreq.get(word) + 1 || 1); } let pq = new PriorityQueue((a, b) => { if (a.freq === b.freq) { // 如果出现频率相同,按照字符串字典序排序 return b.word.localeCompare(a.word); } // 队列按照字符串出现频率从小到大排序 return a.freq - b.freq; }); // 按照字符串频率升序排序 for (let [word, freq] of wordToFreq.entries()) { pq.offer({ word, freq }); if (pq.size > k) { // 维护出现频率最多的 k 个单词 pq.poll(); } } // 把出现次数最多的 k 个字符串返回 let res = []; while (!pq.isEmpty()) { res.push(pq.poll().word); } return res.reverse(); }; ``` ```python # by chatGPT (python) class Solution: def topKFrequent(self, words: List[str], k: int) -> List[str]: # 字符串 -> 该字符串出现的频率 wordToFreq = {} for word in words: wordToFreq[word] = wordToFreq.get(word, 0) + 1 pq = [] for word, freq in wordToFreq.items(): # 存储负值,让小根堆变成大根堆 pq.append((-freq, word)) # 构建小根堆 heapq.heapify(pq) # 取出前k个高频单词 res = [] for _ in range(k): freq, word = heapq.heappop(pq) res.append(word) return res ``` https://leetcode.cn/problems/top-k-frequent-words 的多语言解法👆 https://leetcode.cn/problems/transpose-matrix 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: vector> transpose(vector>& matrix) { int m = matrix.size(), n = matrix[0].size(); // 转置矩阵的长和宽颠倒 vector> res(n, vector(m)); for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { res[j][i] = matrix[i][j]; } } return res; } }; ``` ```go // by chatGPT (go) // leetcode format func transpose(matrix [][]int) [][]int { m, n := len(matrix), len(matrix[0]) // 转置矩阵的长和宽颠倒 res := make([][]int, n) for i := range res { res[i] = make([]int, m) } for i := 0; i < m; i++ { for j := 0; j < n; j++ { res[j][i] = matrix[i][j] } } return res } ``` ```java // by labuladong (java) 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; } } ``` ```javascript // by chatGPT (javascript) var transpose = function(matrix) { var m = matrix.length, n = matrix[0].length; // 转置矩阵的长和宽颠倒 var res = new Array(n).fill().map(() => new Array(m).fill(0)); for (var i = 0; i < m; i++) for (var j = 0; j < n; j++) { res[j][i] = matrix[i][j]; } return res; } ``` ```python # by chatGPT (python) class Solution: def transpose(self, matrix: List[List[int]]) -> List[List[int]]: m, n = len(matrix), len(matrix[0]) #转置矩阵的长和宽颠倒 res = [[0] * m for _ in range(n)] for i in range(m): for j in range(n): res[j][i] = matrix[i][j] return res ``` https://leetcode.cn/problems/transpose-matrix 的多语言解法👆 https://leetcode.cn/problems/trapping-rain-water 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int trap(vector& height) { if (height.empty()) { return 0; } int n = height.size(); int res = 0; // 数组充当备忘录 vector l_max(n); vector r_max(n); // 初始化 base case l_max[0] = height[0]; r_max[n - 1] = height[n - 1]; // 从左向右计算 l_max for (int i = 1; i < n; i++) { l_max[i] = max(height[i], l_max[i - 1]); } // 从右向左计算 r_max for (int i = n - 2; i >= 0; i--) { r_max[i] = max(height[i], r_max[i + 1]); } // 计算答案 for (int i = 1; i < n - 1; i++) { res += min(l_max[i], r_max[i]) - height[i]; } /** ![](../pictures/接雨水/1.jpg) */ return res; } }; ``` ```go // by chatGPT (go) func trap(height []int) int { if len(height) == 0 { return 0 } n := len(height) res := 0 // 数组充当备忘录 l_max := make([]int, n) r_max := make([]int, n) // 初始化 base case l_max[0] = height[0] r_max[n - 1] = height[n - 1] // 从左向右计算 l_max for i := 1; i < n; i++ { l_max[i] = max(height[i], l_max[i - 1]) } // 从右向左计算 r_max for i := n - 2; i >= 0; i-- { r_max[i] = max(height[i], r_max[i + 1]) } // 计算答案 for i := 1; i < n - 1; i++ { res += min(l_max[i], r_max[i]) - height[i] /** ![](../pictures/接雨水/1.jpg) */ } return res; } func max(a, b int) int { if a > b { return a } return b } func min(a, b int) int { if a < b { return a } return b } ``` ```java // by labuladong (java) class Solution { public int trap(int[] height) { if (height.length == 0) { return 0; } int n = height.length; int res = 0; // 数组充当备忘录 int[] l_max = new int[n]; int[] r_max = new int[n]; // 初始化 base case l_max[0] = height[0]; r_max[n - 1] = height[n - 1]; // 从左向右计算 l_max for (int i = 1; i < n; i++) l_max[i] = Math.max(height[i], l_max[i - 1]); // 从右向左计算 r_max for (int i = n - 2; i >= 0; i--) r_max[i] = Math.max(height[i], r_max[i + 1]); // 计算答案 for (int i = 1; i < n - 1; i++) res += Math.min(l_max[i], r_max[i]) - height[i]; /** ![](../pictures/接雨水/1.jpg) */ return res; } } ``` ```javascript // by chatGPT (javascript) var trap = function(height) { if (height.length == 0) { return 0; } var n = height.length; var res = 0; // 数组充当备忘录 var l_max = new Array(n); var r_max = new Array(n); // 初始化 base case l_max[0] = height[0]; r_max[n - 1] = height[n - 1]; // 从左向右计算 l_max for (var i = 1; i < n; i++) { l_max[i] = Math.max(height[i], l_max[i - 1]); } // 从右向左计算 r_max for (var i = n - 2; i >= 0; i--) { r_max[i] = Math.max(height[i], r_max[i + 1]); } // 计算答案 for (var i = 1; i < n - 1; i++) { res += Math.min(l_max[i], r_max[i]) - height[i]; } /** ![](../pictures/接雨水/1.jpg) */ return res; }; ``` ```python # by chatGPT (python) class Solution: def trap(self, height: List[int]) -> int: if not height: return 0 n = len(height) res = 0 # 数组充当备忘录 l_max = [0] * n r_max = [0] * n # 初始化 base case l_max[0] = height[0] r_max[n - 1] = height[n - 1] # 从左向右计算 l_max for i in range(1, n): l_max[i] = max(height[i], l_max[i - 1]) # 从右向左计算 r_max for i in range(n - 2, -1, -1): r_max[i] = max(height[i], r_max[i + 1]) # 计算答案 for i in range(1, n - 1): res += min(l_max[i], r_max[i]) - height[i] """ extend up -300 ![](../pictures/接雨水/1.jpg) """ return res ``` https://leetcode.cn/problems/trapping-rain-water 的多语言解法👆 https://leetcode.cn/problems/two-sum 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: vector twoSum(vector& nums, int target) { // 维护 val -> index 的映射 unordered_map valToIndex; for (int i = 0; i < nums.size(); i++) { // 查表,看看是否有能和 nums[i] 凑出 target 的元素 int need = target - nums[i]; if (valToIndex.count(need)) { return vector{valToIndex[need], i}; } // 存入 val -> index 的映射 valToIndex[nums[i]] = i; } return vector{}; } }; ``` ```go // by chatGPT (go) func twoSum(nums []int, target int) []int { // 维护 val -> index 的映射 valToIndex := make(map[int]int) for i, num := range nums { // 查表,看看是否有能和 nums[i] 凑出 target 的元素 need := target - num if valToIndex[need] != 0 { return []int{valToIndex[need] - 1, i} } // 存入 val -> index 的映射 valToIndex[num] = i + 1 } return nil } ``` ```java // by labuladong (java) class Solution { public int[] twoSum(int[] nums, int target) { // 维护 val -> index 的映射 HashMap valToIndex = new HashMap<>(); for (int i = 0; i < nums.length; i++) { // 查表,看看是否有能和 nums[i] 凑出 target 的元素 int need = target - nums[i]; if (valToIndex.containsKey(need)) { return new int[]{valToIndex.get(need), i}; } // 存入 val -> index 的映射 valToIndex.put(nums[i], i); } return null; } } ``` ```javascript // by chatGPT (javascript) var twoSum = function(nums, target) { // 维护 val -> index 的映射 var valToIndex = new Map(); for (var i = 0; i < nums.length; i++) { // 查表,看看是否有能和 nums[i] 凑出 target 的元素 var need = target - nums[i]; if (valToIndex.has(need)) { return [valToIndex.get(need), i]; } // 存入 val -> index 的映射 valToIndex.set(nums[i], i); } return null; }; ``` ```python # by chatGPT (python) class Solution: def twoSum(self, nums: List[int], target: int) -> List[int]: # 维护 val -> index 的映射 valToIndex = {} for i in range(len(nums)): # 查表,看看是否有能和 nums[i] 凑出 target 的元素 need = target - nums[i] if need in valToIndex: return [valToIndex[need], i] # 存入 val -> index 的映射 valToIndex[nums[i]] = i return [] ``` https://leetcode.cn/problems/two-sum 的多语言解法👆 https://leetcode.cn/problems/two-sum-less-than-k 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int twoSumLessThanK(vector& nums, int k) { // 数组双指针一般都要先排序 sort(nums.begin(), nums.end()); // 左右指针技巧 int lo = 0, hi = nums.size() - 1; int sum = -1; while (lo < hi) { if (nums[lo] + nums[hi] < k) { // 比目标值 k 小,则右移左指针 sum = max(sum, nums[lo] + nums[hi]); lo++; } else { // 比目标值 k 大,则左移右指针 hi--; } } return sum; } }; ``` ```go // by chatGPT (go) // import "sort" // TwoSumLessThanK 函数返回 nums 数组中任意两个数的最大和, // 但不超过 k 的和 func twoSumLessThanK(nums []int, k int) int { // 数组双指针一般都要先排序 sort.Ints(nums) // 左右指针技巧 lo, hi := 0, len(nums)-1 sum := -1 for lo < hi { if nums[lo]+nums[hi] < k { // 比目标值 k 小,则右移左指针 sum = max(sum, nums[lo]+nums[hi]) lo++ } else { // 比目标值 k 大,则左移右指针 hi-- } } return sum } // 辅助函数 max 返回两个整数中的较大值 func max(a, b int) int { if a > b { return a } return b } ``` ```java // by labuladong (java) class Solution { public int twoSumLessThanK(int[] nums, int k) { // 数组双指针一般都要先排序 Arrays.sort(nums); // 左右指针技巧 int lo = 0, hi = nums.length - 1; int sum = -1; while (lo < hi) { if (nums[lo] + nums[hi] < k) { // 比目标值 k 小,则右移左指针 sum = Math.max(sum, nums[lo] + nums[hi]); lo++; } else { // 比目标值 k 大,则左移右指针 hi--; } } return sum; } } ``` ```javascript // by chatGPT (javascript) var twoSumLessThanK = function(nums, k) { // 数组双指针一般都要先排序 nums.sort(function(a, b){return a - b}); // 左右指针技巧 var lo = 0, hi = nums.length - 1; var sum = -1; while (lo < hi) { if (nums[lo] + nums[hi] < k) { // 比目标值 k 小,则右移左指针 sum = Math.max(sum, nums[lo] + nums[hi]); lo++; } else { // 比目标值 k 大,则左移右指针 hi--; } } return sum; }; ``` ```python # by chatGPT (python) class Solution: def twoSumLessThanK(self, nums: List[int], k: int) -> int: # 数组双指针一般都要先排序 nums.sort() # 左右指针技巧 lo, hi = 0, len(nums) - 1 sum = -1 while lo < hi: if nums[lo] + nums[hi] < k: # 比目标值 k 小,则右移左指针 sum = max(sum, nums[lo] + nums[hi]) lo += 1 else: # 比目标值 k 大,则左移右指针 hi -= 1 return sum ``` https://leetcode.cn/problems/two-sum-less-than-k 的多语言解法👆 https://leetcode.cn/problems/uUsW3B 的多语言解法👇 ```cpp // by labuladong (cpp) class Solution { public: vector>res; vector> combine(int n, int k) { if (k <= 0 || n <= 0) return res; vector track; backtrack(n, k, 1, track); return res; } void backtrack(int n, int k, int start, vector& track) { // 到达树的底部 if (k == track.size()) { res.push_back(track); return; } // 注意 i 从 start 开始递增 for (int i = start; i <= n; i++) { // 做选择 track.push_back(i); backtrack(n, k, i + 1, track); // 撤销选择 track.pop_back(); } } }; ``` ```go // by chatGPT (go) /** * Definition for a binary tree node. * type TreeNode struct { * Val int * Left *TreeNode * Right *TreeNode * } */ func combine(n int, k int) [][]int { res := make([][]int, 0) if k <= 0 || n <= 0 { return res } track := make([]int, 0) backtrack(n, k, 1, track, &res) return res } func backtrack(n, k, start int, track []int, res *[][]int) { // 到达树的底部 if k == len(track) { temp := make([]int, len(track)) copy(temp, track) *res = append(*res, temp) return } // 注意 i 从 start 开始递增 for i := start; i <= n; i++ { // 做选择 track = append(track, i) backtrack(n, k, i+1, track, res) // 撤销选择 track = track[:len(track)-1] } } ``` ```java // by chatGPT (java) class Solution { List> res = new ArrayList<>(); public List> combine(int n, int k) { if (k <= 0 || n <= 0) return res; List track = new ArrayList<>(); backtrack(n, k, 1, track); return res; } private void backtrack(int n, int k, int start, List track) { // 到达树的底部 if (k == track.size()) { res.add(new ArrayList<>(track)); return; } // 注意 i 从 start 开始递增 for (int i = start; i <= n; i++) { // 做选择 track.add(i); backtrack(n, k, i + 1, track); // 撤销选择 track.remove(track.size() - 1); } } } ``` ```javascript // by chatGPT (javascript) var combine = function(n, k) { let res = []; if (k <= 0 || n <= 0) return res; let track = []; // 到达树的底部 const backtrack = function(n, k, start, track) { if (k === track.length) { res.push([...track]); return; } // 注意 i 从 start 开始递增 for (let i = start; i <= n; i++) { // 做选择 track.push(i); backtrack(n, k, i + 1, track); // 撤销选择 track.pop(); } }; backtrack(n, k, 1, track); return res; }; ``` ```python # by chatGPT (python) class Solution: def combine(self, n: int, k: int) -> List[List[int]]: res = [] if k <= 0 or n <= 0: return res def backtrack(n: int, k: int, start: int, track: List[int]) -> None: # 到达树的底部 if k == len(track): res.append(track) return # 注意 i 从 start 开始递增 for i in range(start, n + 1): # 做选择 track.append(i) backtrack(n, k, i + 1, track[:]) # 撤销选择 track.pop() backtrack(n, k, 1, []) return res ``` https://leetcode.cn/problems/uUsW3B 的多语言解法👆 https://leetcode.cn/problems/ugly-number 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: bool isUgly(int n) { if (n <= 0) return false; // 如果 n 是丑数,分解因子应该只有 2, 3, 5 while (n % 2 == 0) n /= 2; while (n % 3 == 0) n /= 3; while (n % 5 == 0) n /= 5; return n == 1; } }; ``` ```go // by chatGPT (go) func isUgly(n int) bool { if n <= 0 { return false } // 如果 n 是丑数,分解因子应该只有 2, 3, 5 for n % 2 == 0 { n /= 2 } for n % 3 == 0 { n /= 3 } for n % 5 == 0 { n /= 5 } return n == 1 } ``` ```java // by labuladong (java) class Solution { public boolean isUgly(int n) { if (n <= 0) return false; // 如果 n 是丑数,分解因子应该只有 2, 3, 5 while (n % 2 == 0) n /= 2; while (n % 3 == 0) n /= 3; while (n % 5 == 0) n /= 5; return n == 1; } } ``` ```javascript // by chatGPT (javascript) var isUgly = function(n) { if (n <= 0) return false; // 如果 n 是丑数,分解因子应该只有 2, 3, 5 while (n % 2 === 0) n /= 2; while (n % 3 === 0) n /= 3; while (n % 5 === 0) n /= 5; return n === 1; }; ``` ```python # by chatGPT (python) class Solution: def isUgly(self, n: int) -> bool: if n <= 0: return False # 如果 n 是丑数,分解因子应该只有 2, 3, 5 while n % 2 == 0: n //= 2 while n % 3 == 0: n //= 3 while n % 5 == 0: n //= 5 return n == 1 ``` https://leetcode.cn/problems/ugly-number 的多语言解法👆 https://leetcode.cn/problems/ugly-number-ii 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int nthUglyNumber(int n) { // 可以理解为三个指向有序链表头结点的指针 int p2 = 1, p3 = 1, p5 = 1; // 可以理解为三个有序链表的头节点的值 int product2 = 1, product3 = 1, product5 = 1; // 可以理解为最终合并的有序链表(结果链表) vector ugly(n + 1, 0); // 可以理解为结果链表上的指针 int p = 1; // 开始合并三个有序链表 while (p <= n) { // 取三个链表的最小结点 int min_val = min(min(product2, product3), product5); // 接到结果链表上 ugly[p] = min_val; p++; // 前进对应有序链表上的指针 if (min_val == product2) { product2 = 2 * ugly[p2]; p2++; } if (min_val == product3) { product3 = 3 * ugly[p3]; p3++; } if (min_val == product5) { product5 = 5 * ugly[p5]; p5++; } } // 返回第 n 个丑数 return ugly[n]; } }; ``` ```go // by chatGPT (go) func nthUglyNumber(n int) int { // 可以理解为三个指向有序链表头结点的指针 p2, p3, p5 := 1, 1, 1 // 可以理解为三个有序链表的头节点的值 product2, product3, product5 := 1, 1, 1 // 可以理解为最终合并的有序链表(结果链表) ugly := make([]int, n+1) // 可以理解为结果链表上的指针 p := 1 // 开始合并三个有序链表 for p <= n { // 取三个链表的最小结点 min := min(product2, product3, product5) // 接到结果链表上 ugly[p] = min p++ // 前进对应有序链表上的指针 if min == product2 { product2 = 2 * ugly[p2] p2++ } if min == product3 { product3 = 3 * ugly[p3] p3++ } if min == product5 { product5 = 5 * ugly[p5] p5++ } } // 返回第 n 个丑数 return ugly[n] } // 取三个数的最小值 func min(i, j, k int) int { if i < j { if i < k { return i } else { return k } } else { if j < k { return j } else { return k } } } ``` ```java // by labuladong (java) class Solution { public int nthUglyNumber(int n) { // 可以理解为三个指向有序链表头结点的指针 int p2 = 1, p3 = 1, p5 = 1; // 可以理解为三个有序链表的头节点的值 int product2 = 1, product3 = 1, product5 = 1; // 可以理解为最终合并的有序链表(结果链表) int[] ugly = new int[n + 1]; // 可以理解为结果链表上的指针 int p = 1; // 开始合并三个有序链表 while (p <= n) { // 取三个链表的最小结点 int min = Math.min(Math.min(product2, product3), product5); // 接到结果链表上 ugly[p] = min; p++; // 前进对应有序链表上的指针 if (min == product2) { product2 = 2 * ugly[p2]; p2++; } if (min == product3) { product3 = 3 * ugly[p3]; p3++; } if (min == product5) { product5 = 5 * ugly[p5]; p5++; } } // 返回第 n 个丑数 return ugly[n]; } } ``` ```javascript // by chatGPT (javascript) var nthUglyNumber = function(n) { // 可以理解为三个指向有序链表头结点的指针 let p2 = 1, p3 = 1, p5 = 1; // 可以理解为三个有序链表的头节点的值 let product2 = 1, product3 = 1, product5 = 1; // 可以理解为最终合并的有序链表(结果链表) let ugly = new Array(n + 1); // 可以理解为结果链表上的指针 let p = 1; // 开始合并三个有序链表 while (p <= n) { // 取三个链表的最小结点 let min = Math.min(Math.min(product2, product3), product5); // 接到结果链表上 ugly[p] = min; p++; // 前进对应有序链表上的指针 if (min === product2) { product2 = 2 * ugly[p2]; p2++; } if (min === product3) { product3 = 3 * ugly[p3]; p3++; } if (min === product5) { product5 = 5 * ugly[p5]; p5++; } } // 返回第 n 个丑数 return ugly[n]; }; ``` ```python # by chatGPT (python) class Solution: def nthUglyNumber(self,n: int) -> int: # 三个指向有序链表头结点的指针 p2,p3,p5 = 1,1,1 # 三个有序链表的头节点的值 product2, product3, product5 = 1, 1, 1 # 最终合并的有序链表(结果链表) ugly = [0] * (n + 1) # 结果链表上的指针 p = 1 # 开始合并三个有序链表 while p <= n: # 取三个链表的最小结点 minv = min(product2, product3, product5) # 接到结果链表上 ugly[p] = minv p += 1 # 前进对应有序链表上的指针 if minv == product2: product2 = 2 * ugly[p2] p2 += 1 if minv == product3: product3 = 3 * ugly[p3] p3 += 1 if minv == product5: product5 = 5 * ugly[p5] p5 += 1 # 返回第 n 个丑数 return ugly[n] ``` https://leetcode.cn/problems/ugly-number-ii 的多语言解法👆 https://leetcode.cn/problems/ugly-number-iii 的多语言解法👇 ```cpp // by chatGPT (cpp) // 二分搜索 + 数学解法 class Solution { public: int nthUglyNumber(int n, int a, int b, int c) { // 题目说本题结果在 [1, 2 * 10^9] 范围内, // 所以就按照这个范围初始化两端都闭的搜索区间 int left = 1, right = (int) 2e9; // 搜索左侧边界的二分搜索 while (left <= right) { int mid = left + (right - left) / 2; if (f(mid, a, b, c) < n) { // [1..mid] 中的元素个数不足 n,所以目标在右侧 left = mid + 1; } else { // [1..mid] 中的元素个数大于 n,所以目标在左侧 right = mid - 1; } } return left; } // 计算 [1..num] 之间有多少个能够被 a 或 b 或 c 整除的数字 long f(int num, int a, int b, int c) { long setA = num / a, setB = num / b, setC = num / c; long setAB = num / lcm(a, b); long setAC = num / lcm(a, c); long setBC = num / lcm(b, c); long setABC = num / lcm(lcm(a, b), c); // 集合论定理:A + B + C - A ∩ B - A ∩ C - B ∩ C + A ∩ B ∩ C return setA + setB + setC - setAB - setAC - setBC + setABC; /* ![](../pictures/丑数/1.jpg) */ } // 计算最大公因数(辗转相除/欧几里得算法) long gcd(long a, long b) { if (a < b) { // 保证 a > b return gcd(b, a); } if (b == 0) { return a; } return gcd(b, a % b); } // 最小公倍数 long lcm(long a, long b) { // 最小公倍数就是乘积除以最大公因数 return a * b / gcd(a, b); } }; // 用合并单链表的思路(超时) class Solution2 { public: int nthUglyNumber(int n, int a, int b, int c) { // 可以理解为三个有序链表的头结点的值 long productA = a, productB = b, productC = c; // 可以理解为合并之后的有序链表上的指针 int p = 1; long min = -666; // 开始合并三个有序链表,获取第 n 个节点的值 while (p <= n) { // 取三个链表的最小结点 min = std::min({productA, productB, productC}); p++; // 前进最小结点对应链表的指针 if (min == productA) { productA += a; } if (min == productB) { productB += b; } if (min == productC) { productC += c; } } return (int) min; } }; ``` ```go // by chatGPT (go) // 二分搜索 + 数学解法 func nthUglyNumber(n int, a int, b int, c int) int { // 题目说本题结果在 [1, 2 * 10^9] 范围内, // 所以就按照这个范围初始化两端都闭的搜索区间 left, right := 1, 2*int(1e9) // 搜索左侧边界的二分搜索 for left <= right { mid := left + (right-left)/2 if f(mid, a, b, c) < n { // [1..mid] 中的元素个数不足 n,所以目标在右侧 left = mid + 1 } else { // [1..mid] 中的元素个数大于 n,所以目标在左侧 right = mid - 1 } } return left } // 计算 [1..num] 之间有多少个能够被 a 或 b 或 c 整除的数字 func f(num int, a int, b int, c int) int { setA := num / a setB := num / b setC := num / c setAB := num / lcm(a, b) setAC := num / lcm(a, c) setBC := num / lcm(b, c) setABC := num / lcm(lcm(a, b), c) // 集合论定理:A + B + C - A ∩ B - A ∩ C - B ∩ C + A ∩ B ∩ C return setA + setB + setC - setAB - setAC - setBC + setABC } // 计算最大公因数(辗转相除/欧几里得算法) func gcd(a int64, b int64) int64 { if a < b { // 保证 a > b return gcd(b, a) } if b == 0 { return a } return gcd(b, a%b) } // 最小公倍数 func lcm(a int64, b int64) int64 { // 最小公倍数就是乘积除以最大公因数 return a * b / gcd(a, b) } ``` ```java // by labuladong (java) // 二分搜索 + 数学解法 class Solution { public int nthUglyNumber(int n, int a, int b, int c) { // 题目说本题结果在 [1, 2 * 10^9] 范围内, // 所以就按照这个范围初始化两端都闭的搜索区间 int left = 1, right = (int) 2e9; // 搜索左侧边界的二分搜索 while (left <= right) { int mid = left + (right - left) / 2; if (f(mid, a, b, c) < n) { // [1..mid] 中的元素个数不足 n,所以目标在右侧 left = mid + 1; } else { // [1..mid] 中的元素个数大于 n,所以目标在左侧 right = mid - 1; } } return left; } // 计算 [1..num] 之间有多少个能够被 a 或 b 或 c 整除的数字 long f(int num, int a, int b, int c) { long setA = num / a, setB = num / b, setC = num / c; long setAB = num / lcm(a, b); long setAC = num / lcm(a, c); long setBC = num / lcm(b, c); long setABC = num / lcm(lcm(a, b), c); // 集合论定理:A + B + C - A ∩ B - A ∩ C - B ∩ C + A ∩ B ∩ C return setA + setB + setC - setAB - setAC - setBC + setABC; /** ![](../pictures/丑数/1.jpg) */ } // 计算最大公因数(辗转相除/欧几里得算法) long gcd(long a, long b) { if (a < b) { // 保证 a > b return gcd(b, a); } if (b == 0) { return a; } return gcd(b, a % b); } // 最小公倍数 long lcm(long a, long b) { // 最小公倍数就是乘积除以最大公因数 return a * b / gcd(a, b); } } // 用合并单链表的思路(超时) class Solution2 { public int nthUglyNumber(int n, int a, int b, int c) { // 可以理解为三个有序链表的头结点的值 long productA = a, productB = b, productC = c; // 可以理解为合并之后的有序链表上的指针 int p = 1; long min = -666; // 开始合并三个有序链表,获取第 n 个节点的值 while (p <= n) { // 取三个链表的最小结点 min = Math.min(Math.min(productA, productB), productC); p++; // 前进最小结点对应链表的指针 if (min == productA) { productA += a; } if (min == productB) { productB += b; } if (min == productC) { productC += c; } } return (int) min; } } ``` ```javascript // by chatGPT (javascript) /** * @param {number} n * @param {number} a * @param {number} b * @param {number} c * @return {number} */ var nthUglyNumber = function(n, a, b, c) { // 题目说本题结果在 [1, 2 * 10^9] 范围内, // 所以就按照这个范围初始化两端都闭的搜索区间 let left = 1, right = 2e9; // 搜索左侧边界的二分搜索 while (left <= right) { let mid = left + (right - left) / 2; if (f(mid, a, b, c) < n) { // [1..mid] 中的元素个数不足 n,所以目标在右侧 left = mid + 1; } else { // [1..mid] 中的元素个数大于 n,所以目标在左侧 right = mid - 1; } } return left; // 计算 [1..num] 之间有多少个能够被 a 或 b 或 c 整除的数字 function f(num, a, b, c) { let setA = Math.floor(num / a), setB = Math.floor(num / b), setC = Math.floor(num / c); let setAB = Math.floor(num / lcm(a, b)); let setAC = Math.floor(num / lcm(a, c)); let setBC = Math.floor(num / lcm(b, c)); let setABC = Math.floor(num / lcm(lcm(a, b), c)); // 集合论定理:A + B + C - A ∩ B - A ∩ C - B ∩ C + A ∩ B ∩ C return setA + setB + setC - setAB - setAC - setBC + setABC; /** ![](../pictures/丑数/1.jpg) */ } // 计算最大公因数(辗转相除/欧几里得算法) function gcd(a, b) { if (a < b) { // 保证 a > b return gcd(b, a); } if (b === 0) { return a; } return gcd(b, a % b); } // 最小公倍数 function lcm(a, b) { // 最小公倍数就是乘积除以最大公因数 return a * b / gcd(a, b); } }; ``` ```python # by chatGPT (python) class Solution: def nthUglyNumber(self, n: int, a: int, b: int, c: int) -> int: def gcd(x: int, y: int) -> int: if x < y: # 保证 x > y return gcd(y, x) if y == 0: return x return gcd(y, x % y) def lcm(x: int, y: int) -> int: # 最小公倍数就是乘积除以最大公因数 return x * y // gcd(x, y) # 题目说本题结果在 [1, 2 * 10^9] 范围内, # 所以就按照这个范围初始化两端都闭的搜索区间 left, right = 1, 2 * 10 ** 9 # 搜索左侧边界的二分搜索 while left <= right: mid = (left + right) // 2 if (mid // a + mid // b + mid // c - mid // lcm(a, b) - mid // lcm(b, c) - mid // lcm(a, c) + mid // lcm(lcm(a, b), c)) < n: # [1..mid] 中的元素个数不足 n,所以目标在右侧 left = mid + 1 else: # [1..mid] 中的元素个数大于 n,所以目标在左侧 right = mid - 1 return left ``` https://leetcode.cn/problems/ugly-number-iii 的多语言解法👆 https://leetcode.cn/problems/unique-binary-search-trees 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: // 备忘录 int memo[20][20]; int numTrees(int n) { // 备忘录的值初始化为 0 memset(memo, 0, sizeof(memo)); return count(1, n); } int count(int lo, int hi) { if (lo > hi) return 1; // 查备忘录 if (memo[lo][hi] != 0) { return memo[lo][hi]; } int res = 0; for (int mid = lo; mid <= hi; mid++) { int left = count(lo, mid - 1); int right = count(mid + 1, hi); res += left * right; } // 将结果存入备忘录 memo[lo][hi] = res; return res; } }; ``` ```go // by chatGPT (go) func numTrees(n int) int { // 备忘录的值初始化为 0 memo := make([][]int, n+1) for i := 0; i <= n; i++ { memo[i] = make([]int, n+1) } var count func(lo, hi int) int count = func(lo, hi int) int { if lo > hi { return 1 } // 查备忘录 if memo[lo][hi] != 0 { return memo[lo][hi] } res := 0 for mid := lo; mid <= hi; mid++ { left := count(lo, mid-1) right := count(mid+1, hi) res += left * right } // 将结果存入备忘录 memo[lo][hi] = res return res } return count(1, n) } ``` ```java // by labuladong (java) class Solution { // 备忘录 int[][] memo; int numTrees(int n) { // 备忘录的值初始化为 0 memo = new int[n + 1][n + 1]; return count(1, n); } int count(int lo, int hi) { if (lo > hi) return 1; // 查备忘录 if (memo[lo][hi] != 0) { return memo[lo][hi]; } int res = 0; for (int mid = lo; mid <= hi; mid++) { int left = count(lo, mid - 1); int right = count(mid + 1, hi); res += left * right; } // 将结果存入备忘录 memo[lo][hi] = res; return res; } } ``` ```javascript // by chatGPT (javascript) var numTrees = function(n) { //备忘录 var memo = new Array(n+1).fill(0).map(() => new Array(n+1).fill(0)); //计算树的个数 function count(lo, hi) { //当左端点大于右端点时,返回1 if (lo > hi) return 1; //查看备忘录 if (memo[lo][hi] != 0) { return memo[lo][hi]; } var res = 0; //用mid遍历左右子树的所有根节点 for (var mid = lo; mid <= hi; mid++) { //计算左子树的个数 var left = count(lo, mid - 1); //计算右子树的个数 var right = count(mid + 1, hi); //计算总的方案数 res += left * right; } //将结果存入备忘录 memo[lo][hi] = res; return res; } //返回全部区间 [1, n] 的方案数 return count(1, n); }; ``` ```python # by chatGPT (python) class Solution: def __init__(self): # 备忘录 self.memo = [] def numTrees(self, n: int) -> int: # 备忘录的值初始化为 0 self.memo = [[0]*(n+1) for _ in range(n+1)] return self.count(1, n) def count(self, lo: int, hi: int) -> int: if lo > hi: return 1 # 查备忘录 if self.memo[lo][hi] != 0: return self.memo[lo][hi] res = 0 for mid in range(lo, hi+1): left = self.count(lo, mid - 1) right = self.count(mid + 1, hi) res += left * right # 将结果存入备忘录 self.memo[lo][hi] = res return res ``` https://leetcode.cn/problems/unique-binary-search-trees 的多语言解法👆 https://leetcode.cn/problems/unique-binary-search-trees-ii 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: /* 主函数 */ vector generateTrees(int n) { if (n == 0) return vector({}); // 构造闭区间 [1, n] 组成的 BST return build(1, n); } /* 构造闭区间 [lo, hi] 组成的 BST */ vector build(int lo, int hi) { vector res; // base case if (lo > hi) { res.push_back(nullptr); return res; } // 1、穷举 root 节点的所有可能。 for (int i = lo; i <= hi; i++) { // 2、递归构造出左右子树的所有合法 BST。 vector leftTree = build(lo, i - 1); vector rightTree = build(i + 1, hi); // 3、给 root 节点穷举所有左右子树的组合。 for (auto left : leftTree) { for (auto right : rightTree) { // i 作为根节点 root 的值 TreeNode* root = new TreeNode(i); root->left = left; root->right = right; res.push_back(root); } } } return res; } }; ``` ```go // by chatGPT (go) /* 主函数 */ func generateTrees(n int) []*TreeNode { if n == 0 { return []*TreeNode{} } // 构造闭区间 [1, n] 组成的 BST return build(1, n) } /* 构造闭区间 [lo, hi] 组成的 BST */ func build(lo, hi int) []*TreeNode { res := []*TreeNode{} // base case if lo > hi { res = append(res, nil) return res } // 1、穷举 root 节点的所有可能。 for i := lo; i <= hi; i++ { // 2、递归构造出左右子树的所有合法 BST。 leftTree := build(lo, i-1) rightTree := build(i+1, hi) // 3、给 root 节点穷举所有左右子树的组合。 for _, left := range leftTree { for _, right := range rightTree { // i 作为根节点 root 的值 root := &TreeNode{Val: i} root.Left = left root.Right = right res = append(res, root) } } } return res } ``` ```java // by labuladong (java) class Solution { /* 主函数 */ public List generateTrees(int n) { if (n == 0) return new LinkedList<>(); // 构造闭区间 [1, n] 组成的 BST return build(1, n); } /* 构造闭区间 [lo, hi] 组成的 BST */ List build(int lo, int hi) { List res = new LinkedList<>(); // base case if (lo > hi) { res.add(null); return res; } // 1、穷举 root 节点的所有可能。 for (int i = lo; i <= hi; i++) { // 2、递归构造出左右子树的所有合法 BST。 List leftTree = build(lo, i - 1); List rightTree = build(i + 1, hi); // 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; } } ``` ```javascript // by chatGPT (javascript) var generateTrees = function(n) { if (n == 0) return new Array(); // 构造闭区间 [1, n] 组成的 BST return build(1, n); } var build = function(lo, hi) { // 存储 BST 的结果集 var res = new Array(); // base case if (lo > hi) { // 注意:这里必须添加 null,否则在下面无法递归左右子树 res.push(null); return res; } // 1、穷举 root 节点的所有可能 for (var i = lo; i <= hi; i++) { // 2、递归构造出左右子树的所有合法 BST var leftTree = build(lo, i - 1); var rightTree = build(i + 1, hi); // 3、给 root 节点穷举所有左右子树的组合 for (var j = 0; j < leftTree.length; j++) { for (var k = 0; k < rightTree.length; k++) { // i 作为根节点 root 的值 var root = new TreeNode(i); root.left = leftTree[j]; root.right = rightTree[k]; res.push(root); } } } return res; }; ``` ```python # by chatGPT (python) class Solution: def generateTrees(self, n: int) -> List[TreeNode]: if n == 0: # 如果节点数量为 0,则返回一个空列表 return [] return self.build(1, n) # 构造闭区间 [1, n] 的 BST def build(self, lo: int, hi: int) -> List[TreeNode]: res = [] # 当 lo > hi 时,不存在节点,将 None 存入结果列表中,并返回 if lo > hi: res.append(None) return res # 1、穷举 root 节点的所有可能。 for i in range(lo, hi + 1): # 2、递归构造出左右子树的所有合法 BST。 leftTree = self.build(lo, i - 1) rightTree = self.build(i + 1, hi) # 3、给 root 节点穷举所有左右子树的组合。 for left in leftTree: for right in rightTree: # i 作为根节点 root 的值 root = TreeNode(i) root.left = left root.right = right res.append(root) return res ``` https://leetcode.cn/problems/unique-binary-search-trees-ii 的多语言解法👆 https://leetcode.cn/problems/unique-paths 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { // 备忘录 int[][] memo; public: int uniquePaths(int m, int n) { memo = new int*[m]; for(int i=0;i 0) { return memo[x][y]; } // 状态转移方程: // 到达 (x, y) 的路径数等于到达 (x - 1, y) 和 (x, y - 1) 路径数之和 memo[x][y] = dp(x - 1, y) + dp(x, y - 1); return memo[x][y]; } }; ``` ```go // by chatGPT (go) func uniquePaths(m int, n int) int { // 备忘录 memo := make([][]int, m) for i := 0; i < m; i++ { memo[i] = make([]int, n) } // 定义:从 (0, 0) 到 (x, y) 有 dp(x, y) 条路径 var dp func(int, int) int dp = func(x int, y int) int { // base case if x == 0 && y == 0 { return 1 } if x < 0 || y < 0 { return 0 } // 避免冗余计算 if memo[x][y] > 0 { return memo[x][y] } // 状态转移方程: // 到达 (x, y) 的路径数等于到达 (x - 1, y) 和 (x, y - 1) 路径数之和 memo[x][y] = dp(x-1, y) + dp(x, y-1) return memo[x][y] } return dp(m-1, n-1) } ``` ```java // by labuladong (java) class Solution { // 备忘录 int[][] memo; public int uniquePaths(int m, int n) { memo = new int[m][n]; return dp(m - 1, n - 1); } // 定义:从 (0, 0) 到 (x, y) 有 dp(x, y) 条路径 int dp(int x, int y) { // base case if (x == 0 && y == 0) { return 1; } if (x < 0 || y < 0) { return 0; } // 避免冗余计算 if (memo[x][y] > 0) { return memo[x][y]; } // 状态转移方程: // 到达 (x, y) 的路径数等于到达 (x - 1, y) 和 (x, y - 1) 路径数之和 memo[x][y] = dp(x - 1, y) + dp(x, y - 1); return memo[x][y]; } } ``` ```javascript // by chatGPT (javascript) var uniquePaths = function(m, n) { // 备忘录 const memo = new Array(m).fill(0).map(() => new Array(n).fill(0)); return dp(m - 1, n - 1); // 定义:从 (0, 0) 到 (x, y) 有 dp(x, y) 条路径 function dp(x, y) { // base case if (x === 0 && y === 0) { return 1; } if (x < 0 || y < 0) { return 0; } // 避免冗余计算 if (memo[x][y] > 0) { return memo[x][y]; } // 状态转移方程: // 到达 (x, y) 的路径数等于到达 (x - 1, y) 和 (x, y - 1) 路径数之和 memo[x][y] = dp(x - 1, y) + dp(x, y - 1); return memo[x][y]; } }; ``` ```python # by chatGPT (python) class Solution: # 备忘录 memo = [] def uniquePaths(self, m: int, n: int) -> int: self.memo = [[0] * n for _ in range(m)] return self.dp(m - 1, n - 1) # 定义:从 (0, 0) 到 (x, y) 有 dp(x, y) 条路径 def dp(self, x: int, y: int) -> int: # base case if x == 0 and y == 0: return 1 if x < 0 or y < 0: return 0 # 避免冗余计算 if self.memo[x][y] > 0: return self.memo[x][y] # 状态转移方程: # 到达 (x, y) 的路径数等于到达 (x - 1, y) 和 (x, y - 1) 路径数之和 self.memo[x][y] = self.dp(x - 1, y) + self.dp(x, y - 1) return self.memo[x][y] ``` https://leetcode.cn/problems/unique-paths 的多语言解法👆 https://leetcode.cn/problems/univalued-binary-tree 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: bool isUnivalTree(TreeNode* root) { if (root == nullptr) { return true; } prev = root->val; traverse(root); return isUnival; } int prev; bool isUnival = true; void traverse(TreeNode* root) { if (root == nullptr || !isUnival) { return; } if (root->val != prev) { isUnival = false; return; } traverse(root->left); traverse(root->right); } }; ``` ```go // by chatGPT (go) func isUnivalTree(root *TreeNode) bool { var ( prev int // Closure to maintain previous node's value isUnival bool = true // Closure to maintain result, whether tree is unival or not ) var traverse func(root *TreeNode) traverse = func(root *TreeNode) { if root == nil || !isUnival { return } if root.Val != prev { isUnival = false return } traverse(root.Left) traverse(root.Right) } if root == nil { return true } prev = root.Val traverse(root) return isUnival } ``` ```java // by labuladong (java) class Solution { public boolean isUnivalTree(TreeNode root) { if (root == null) { return true; } prev = root.val; traverse(root); return isUnival; } int prev; boolean isUnival = true; void traverse(TreeNode root) { if (root == null || !isUnival) { return; } if (root.val != prev) { isUnival = false; return; } traverse(root.left); traverse(root.right); } } ``` ```javascript // by chatGPT (javascript) /** * @param {TreeNode} root * @return {boolean} */ var isUnivalTree = function(root) { if (root == null) { return true; } var prev = root.val; var isUnival = true; var traverse = function(root) { if (root == null || !isUnival) { return; } if (root.val != prev) { isUnival = false; return; } traverse(root.left); traverse(root.right); } traverse(root); return isUnival; }; ``` ```python # by chatGPT (python) class Solution: def isUnivalTree(self, root: TreeNode) -> bool: if not root: return True self.prev = root.val self.isUnival = True self.traverse(root) return self.isUnival def traverse(self, root: TreeNode) -> None: if not root or not self.isUnival: return if root.val != self.prev: self.isUnival = False return self.traverse(root.left) self.traverse(root.right) ``` https://leetcode.cn/problems/univalued-binary-tree 的多语言解法👆 https://leetcode.cn/problems/valid-anagram 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: // 判断字符串 s 和 t 是否为字母异位词 bool isAnagram(string s, string t) { // 使用 encode 函数计算两个字符串中各字符出现的次数 int[] count1 = encode(s); int[] count2 = encode(t); // 判断两个字符串中所有字符出现的数量是否相同 for (int i = 0; i < count1.length; i++) { if (count1[i] != count2[i]) { return false; } } return true; } // 计算字符串中各字符出现的次数 int[] encode(string s) { int[] count = new int[26]; for (char c : s.toCharArray()) { int delta = c - 'a'; count[delta]++; } return count; } }; ``` ```go // by chatGPT (go) func isAnagram(s string, t string) bool { count1 := encode(s) count2 := encode(t) // 确保两个字符串中所有字符出现的数量相同 for i := 0; i < len(count1); i++ { if count1[i] != count2[i] { return false } } return true } // 计算字符的出现次数 func encode(s string) []int { count := make([]int, 26) for _, c := range s { delta := int(c - 'a') count[delta]++ } return count } ``` ```java // by labuladong (java) class Solution { public boolean isAnagram(String s, String t) { int[] count1 = encode(s); int[] count2 = encode(t); // 确保两个字符串中所有字符出现的数量相同 for (int i = 0; i < count1.length; i++) { if (count1[i] != count2[i]) { return false; } } return true; } // 计算字符的出现次数 int[] encode(String s) { int[] count = new int[26]; for (char c : s.toCharArray()) { int delta = c - 'a'; count[delta]++; } return count; } } ``` ```javascript // by chatGPT (javascript) var isAnagram = function(s, t) { var count1 = encode(s); var count2 = encode(t); // 确保两个字符串中所有字符出现的数量相同 for (var i = 0; i < count1.length; i++) { if (count1[i] !== count2[i]) { return false; } } return true; }; // 计算字符的出现次数 var encode = function(s) { var count = new Array(26).fill(0); for (var i = 0; i < s.length; i++) { var delta = s.charCodeAt(i) - 'a'.charCodeAt(0); count[delta]++; } return count; }; ``` ```python # by chatGPT (python) class Solution: def isAnagram(self, s: str, t: str) -> bool: count1 = self.encode(s) count2 = self.encode(t) # 确保两个字符串中所有字符出现的数量相同 for i in range(len(count1)): if count1[i] != count2[i]: return False return True # 计算字符的出现次数 def encode(self, s: str) -> List[int]: count = [0] * 26 for c in s: delta = ord(c) - ord('a') count[delta] += 1 return count ``` https://leetcode.cn/problems/valid-anagram 的多语言解法👆 https://leetcode.cn/problems/valid-palindrome 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: bool isPalindrome(string s) { // 先把所有字符转化成小写,并过滤掉空格和标点这类字符 string nstr; for(int i=0;i bool: # 先把所有字符转化成小写,并过滤掉空格和标点这类字符 sb = [] for c in s: if c.isalnum(): sb.append(c.lower()) # 然后对剩下的这些目标字符执行双指针算法,判断回文串 s = "".join(sb) # 一左一右两个指针相向而行 left, right = 0, len(s) - 1 while left < right: if s[left] != s[right]: return False left += 1 right -= 1 return True ``` https://leetcode.cn/problems/valid-palindrome 的多语言解法👆 https://leetcode.cn/problems/valid-parentheses 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: bool isValid(string str) { stack left; for (char c : str) { if (c == '(' || c == '{' || c == '[') left.push(c); else // 字符 c 是右括号 if (!left.empty() && leftOf(c) == left.top()) left.pop(); else // 和最近的左括号不匹配 return false; } // 是否所有的左括号都被匹配了 return left.empty(); } char leftOf(char c) { if (c == '}') return '{'; if (c == ')') return '('; return '['; } }; ``` ```go // by chatGPT (go) // 包名为 main // 定义一个字节数组栈类型 type Stack []byte // 入栈操作 func (s *Stack) push(str byte) { *s = append(*s, str) } // 出栈操作 func (s *Stack) pop() byte { if len(*s) == 0 { return 0 } res := (*s)[len(*s)-1] *s = (*s)[:len(*s)-1] return res } // 判断给定字符串是否是合法的括号序列 func isValid(str string) bool { // 定义一个栈 left 保存左括号 var left Stack // 遍历字符 for i := range str { c := str[i] // 当 c 是左括号时,入栈 left if c == '(' || c == '[' || c == '{' { left.push(c) } else { // 当 c 是右括号时 // 如果栈 left 非空,且栈顶的左括号和当前右括号匹配,则弹出栈顶元素 if len(left) != 0 && leftOf(c) == left.pop() { continue } else { // 当前左括号和最近的左括号不匹配 return false } } } // 是否所有的左括号都被匹配了 return len(left) == 0 } // 返回左括号 func leftOf(c byte) byte { if c == '}' { return '{' } else if c == ')' { return '(' } else { return '[' } } ``` ```java // by labuladong (java) class Solution { public boolean isValid(String str) { Stack left = new Stack<>(); for (char c : str.toCharArray()) { if (c == '(' || c == '{' || c == '[') left.push(c); else // 字符 c 是右括号 if (!left.isEmpty() && leftOf(c) == left.peek()) left.pop(); else // 和最近的左括号不匹配 return false; } // 是否所有的左括号都被匹配了 return left.isEmpty(); } char leftOf(char c) { if (c == '}') return '{'; if (c == ')') return '('; return '['; } } ``` ```javascript // by chatGPT (javascript) var isValid = function(str) { // 建立一个栈 let left = []; // 遍历字符串中的每一个字符 for (let c of str) { // 如果是左括号,则入栈 if (c == '(' || c == '{' || c == '[') left.push(c); else { // 字符 c 是右括号 // 如果栈不为空,并且最近入栈的左括号可以匹配,则出栈 if (left.length && leftOf(c) == left[left.length-1]) left.pop(); else // 和最近的左括号不匹配 return false; } } // 是否所有的左括号都被匹配了 return !left.length; } function leftOf(c) { if (c == '}') return '{'; if (c == ')') return '('; return '['; } ``` ```python # by chatGPT (python) class Solution: def isValid(self, s: str) -> bool: left = [] # 使用栈结构,存储所有待匹配的左括号 for c in s: if c == '(' or c == '{' or c == '[': left.append(c) # 如果字符 c 是左括号,则将其加入左括号栈 left 中 else: if left and self.leftOf(c) == left[-1]: # 如果字符 c 是右括号,则比较它与最近一次加入栈 left 中的左括号是否匹配 left.pop() # 如果匹配,则将最近的左括号出栈,否则返回 False else: return False return not left # 最后判断栈是否为空,如果是则说明所有的左括号都被匹配了,返回 True,否则返回 False def leftOf(self, c: str) -> str: if c == '}': return '{' elif c == ')': return '(' else: return '[' ``` https://leetcode.cn/problems/valid-parentheses 的多语言解法👆 https://leetcode.cn/problems/validate-binary-search-tree 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: bool isValidBST(TreeNode* root) { // 限定以root为根的子树节点必须满足max.val > root.val > min.val return checkValidBST(root, nullptr, nullptr); } // 限定root节点符合min和max的限制 bool checkValidBST(TreeNode* root, TreeNode* minNode, TreeNode* maxNode) { // base case if (root == nullptr) return true; // 若root.val不符合max和min的限制,说明不是合法BST if (minNode != nullptr && root->val <= minNode->val) return false; if (maxNode != nullptr && root->val >= maxNode->val) return false; // 限定左子树的最大值是root.val,右子树的最小值是root.val return checkValidBST(root->left, minNode, root) && checkValidBST(root->right, root, maxNode); } }; ``` ```go // by chatGPT (go) func isValidBST(root *TreeNode) bool { return isValid(root, nil, nil) } /* 限定以 root 为根的子树节点必须满足 max.val > root.val > min.val */ func isValid(root *TreeNode, min *TreeNode, max *TreeNode) bool { // base case if root == nil { return true } // 若 root.val 不符合 max 和 min 的限制,说明不是合法 BST if min != nil && root.Val <= min.Val { return false } if max != nil && root.Val >= max.Val { return false } // 限定左子树的最大值是 root.Val,右子树的最小值是 root.Val return isValid(root.Left, min, root) && isValid(root.Right, root, max) } ``` ```java // by labuladong (java) 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); } } ``` ```javascript // by chatGPT (javascript) /** * @param {TreeNode} root * @return {boolean} */ var isValidBST = function(root) { return helper(root, null, null); }; /** * 限定以 root 为根的子树节点必须满足 max.val > root.val > min.val * @param {TreeNode} root * @param {TreeNode} min * @param {TreeNode} max * @return {boolean} */ var helper = function(root, min, 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 helper(root.left, min, root) && helper(root.right, root, max); }; ``` ```python # by chatGPT (python) class Solution: def isValidBST(self, root: TreeNode) -> bool: return self.helper(root, None, None) # 限定以 root 为根的子树节点必须满足 max.val > root.val > min.val def helper(self, root, min_node, max_node): # base case if not root: return True # 若 root.val 不符合 max 和 min 的限制,说明不是合法 BST if min_node and root.val <= min_node.val: return False if max_node and root.val >= max_node.val: return False # 限定左子树的最大值是 root.val,右子树的最小值是 root.val return self.helper(root.left, min_node, root) and self.helper(root.right, root, max_node) ``` https://leetcode.cn/problems/validate-binary-search-tree 的多语言解法👆 https://leetcode.cn/problems/video-stitching 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int videoStitching(vector>& clips, int T) { if (T == 0) return 0; //按起点升序排列,起点相同的降序排列 //PS:其实起点相同的不用降序排列也可以,不过我觉得这样更清晰 sort(clips.begin(), clips.end(), [](vector& a, vector& b){ if (a[0] == b[0]) { return b[1] < a[1]; } return a[0] < b[0]; }); //记录选择的短视频个数 int res = 0; int curEnd = 0, nextEnd = 0; int i = 0, n = clips.size(); while (i < n && clips[i][0] <= curEnd) { //在第res个视频的区间内贪心选择下一个视频 while (i < n && clips[i][0] <= curEnd) { nextEnd = max(nextEnd, clips[i][1]); i++; } //找到下一个视频,更新curEnd res++; curEnd = nextEnd; if (curEnd >= T) { //已经可以拼出区间 [0, T] return res; } } //无法连续拼出区间 [0, T] return -1; } }; ``` ```go // by chatGPT (go) import "sort" func videoStitching(clips [][]int, T int) int { if T == 0 { return 0 } // 按起点升序排列,起点相同的降序排列 // PS:其实起点相同的不用降序排列也可以,不过我觉得这样更清晰 sort.Slice(clips, func(i, j int) bool { a, b := clips[i], clips[j] if a[0] == b[0] { return b[1] < a[1] } return a[0] < b[0] }) // 记录选择的短视频个数 var res int curEnd, nextEnd := 0, 0 i, n := 0, len(clips) for i < n && clips[i][0] <= curEnd { // 在第 res 个视频的区间内贪心选择下一个视频 for i < n && clips[i][0] <= curEnd { nextEnd = max(nextEnd, clips[i][1]) i++ } // 找到下一个视频,更新 curEnd res++ curEnd = nextEnd if curEnd >= T { // 已经可以拼出区间 [0, T] return res } } // 无法连续拼出区间 [0, T] return -1 } func max(a, b int) int { if a > b { return a } return b } ``` ```java // by labuladong (java) class Solution { public int videoStitching(int[][] clips, int T) { if (T == 0) return 0; // 按起点升序排列,起点相同的降序排列 // PS:其实起点相同的不用降序排列也可以,不过我觉得这样更清晰 Arrays.sort(clips, (a, b) -> { if (a[0] == b[0]) { return b[1] - a[1]; } return a[0] - b[0]; }); // 记录选择的短视频个数 int res = 0; int curEnd = 0, nextEnd = 0; int i = 0, n = clips.length; while (i < n && clips[i][0] <= curEnd) { // 在第 res 个视频的区间内贪心选择下一个视频 while (i < n && clips[i][0] <= curEnd) { nextEnd = Math.max(nextEnd, clips[i][1]); i++; } // 找到下一个视频,更新 curEnd res++; curEnd = nextEnd; if (curEnd >= T) { // 已经可以拼出区间 [0, T] return res; } } // 无法连续拼出区间 [0, T] return -1; } } ``` ```javascript // by chatGPT (javascript) var videoStitching = function(clips, T) { if (T == 0) return 0; // 按起点升序排列,起点相同的降序排列 // PS:其实起点相同的不用降序排列也可以,不过我觉得这样更清晰 clips.sort((a, b) => { if (a[0] == b[0]) { return b[1] - a[1]; } return a[0] - b[0]; }); // 记录选择的短视频个数 var res = 0; var curEnd = 0, nextEnd = 0; var i = 0, n = clips.length; while (i < n && clips[i][0] <= curEnd) { // 在第 res 个视频的区间内贪心选择下一个视频 while (i < n && clips[i][0] <= curEnd) { nextEnd = Math.max(nextEnd, clips[i][1]); i++; } // 找到下一个视频,更新 curEnd res++; curEnd = nextEnd; if (curEnd >= T) { // 已经可以拼出区间 [0, T] return res; } } // 无法连续拼出区间 [0, T] return -1; }; ``` ```python # by chatGPT (python) class Solution: def videoStitching(self, clips: List[List[int]], T: int) -> int: if T == 0: return 0 # 按起点升序排列,起点相同的降序排列 # PS:其实起点相同的不用降序排列也可以,不过我觉得这样更清晰 clips.sort(key=lambda x: (x[0], -x[1])) # 记录选择的短视频个数 res = 0 curEnd, nextEnd = 0, 0 i, n = 0, len(clips) while i < n and clips[i][0] <= curEnd: # 在第 res 个视频的区间内贪心选择下一个视频 while i < n and clips[i][0] <= curEnd: nextEnd = max(nextEnd, clips[i][1]) i += 1 # 找到下一个视频,更新 curEnd res += 1 curEnd = nextEnd if curEnd >= T: # 已经可以拼出区间 [0, T] return res # 无法连续拼出区间 [0, T] return -1 ``` https://leetcode.cn/problems/video-stitching 的多语言解法👆 https://leetcode.cn/problems/vvXgSW 的多语言解法👇 ```cpp // by chatGPT (cpp) // Definition for singly-linked list. // struct ListNode { // int val; // ListNode *next; // ListNode(int x) : val(x), next(NULL) {} // }; // Definition for singly-linked list. // struct ListNode { // int val; // ListNode *next; // ListNode(int x) : val(x), next(NULL) {} // }; // Definition for singly-linked list. // struct ListNode { // int val; // ListNode *next; // ListNode(int x) : val(x), next(NULL) {} // }; // Definition for singly-linked list. // struct ListNode { // int val; // ListNode *next; // ListNode(int x) : val(x), next(NULL) {} // }; // Definition for singly-linked list. // struct ListNode { // int val; // ListNode *next; // ListNode(int x) : val(x), next(NULL) {} // }; // Definition for singly-linked list. // struct ListNode { // int val; // ListNode *next; // ListNode(int x) : val(x), next(NULL) {} // }; // Definition for singly-linked list. // struct ListNode { // int val; // ListNode *next; // ListNode(int x) : val(x), next(NULL) {} // }; // Definition for singly-linked list. // struct ListNode { // int val; // ListNode *next; // ListNode(int x) : val(x), next(NULL) {} // }; // Definition for singly-linked list. // struct ListNode { // int val; // ListNode *next; // ListNode(int x) : val(x), next(NULL) {} // }; class Solution { public: ListNode* mergeKLists(vector& lists) { if (lists.size() == 0) { return nullptr; } // 虚拟头结点 ListNode* dummy = new ListNode(-1); ListNode* p = dummy; // 优先级队列,最小堆 auto cmp = [](ListNode* a, ListNode* b) { return a->val > b->val; }; priority_queue, decltype(cmp)> pq(cmp); // 将 k 个链表的头结点加入最小堆 for (auto head : lists) { if (head != nullptr) pq.push(head); } while (!pq.empty()) { // 获取最小节点,接到结果链表中 ListNode* node = pq.top(); p->next = node; pq.pop(); if (node->next != nullptr) { pq.push(node->next); } // p 指针不断前进 p = p->next; } return dummy->next; } }; ``` ```go // by chatGPT (go) func mergeKLists(lists []*ListNode) *ListNode { if len(lists) == 0 { return nil } // 虚拟头结点 dummy := &ListNode{} p := dummy // 优先级队列,最小堆 pq := make(PriorityQueue, 0) for _, head := range lists { if head != nil { pq.Push(head) } } for !pq.IsEmpty() { // 获取最小节点,接到结果链表中 node := pq.Pop().(*ListNode) p.Next = node if node.Next != nil { pq.Push(node.Next) } // p 指针不断前进 p = p.Next } return dummy.Next } // 优先级队列,最小堆 type PriorityQueue []*ListNode func (pq PriorityQueue) Len() int { return len(pq) } func (pq PriorityQueue) Less(i, j int) bool { return pq[i].Val < pq[j].Val } func (pq PriorityQueue) Swap(i, j int) { pq[i], pq[j] = pq[j], pq[i] } func (pq *PriorityQueue) Push(x interface{}) { *pq = append(*pq, x.(*ListNode)) } func (pq *PriorityQueue) Pop() interface{} { n := len(*pq) x := (*pq)[n-1] *pq = (*pq)[:n-1] return x } func (pq PriorityQueue) IsEmpty() bool { return len(pq) == 0 } ``` ```java // by labuladong (java) class Solution { public ListNode mergeKLists(ListNode[] lists) { if (lists.length == 0) return null; // 虚拟头结点 ListNode dummy = new ListNode(-1); ListNode p = dummy; // 优先级队列,最小堆 PriorityQueue pq = new PriorityQueue<>( lists.length, (a, b)->(a.val - b.val)); // 将 k 个链表的头结点加入最小堆 for (ListNode head : lists) { if (head != null) pq.add(head); } while (!pq.isEmpty()) { // 获取最小节点,接到结果链表中 ListNode node = pq.poll(); p.next = node; if (node.next != null) { pq.add(node.next); } // p 指针不断前进 p = p.next; } return dummy.next; } } ``` ```javascript // by chatGPT (javascript) var mergeKLists = function(lists) { if (lists.length == 0) return null; // 虚拟头结点 var dummy = new ListNode(-1); var p = dummy; // 优先级队列,最小堆 var pq = new PriorityQueue( lists.length, (a, b) => (a.val - b.val)); // 将 k 个链表的头结点加入最小堆 for (var head of lists) { if (head != null) pq.add(head); } while (!pq.isEmpty()) { // 获取最小节点,接到结果链表中 var node = pq.poll(); p.next = node; if (node.next != null) { pq.add(node.next); } // p 指针不断前进 p = p.next; } return dummy.next; } ``` ```python # by chatGPT (python) class Solution: def mergeKLists(self, lists: List[ListNode]) -> ListNode: if not lists: return None # 虚拟头结点 dummy = ListNode(-1) p = dummy # 优先级队列,最小堆 pq = [] for head in lists: if head: # 将 k 个链表的头结点加入最小堆 heapq.heappush(pq, (head.val, head)) while pq: # 获取最小节点,接到结果链表中 val, node = heapq.heappop(pq) p.next = node if node.next: heapq.heappush(pq, (node.next.val, node.next)) # p 指针不断前进 p = p.next return dummy.next ``` https://leetcode.cn/problems/vvXgSW 的多语言解法👆 https://leetcode.cn/problems/w6cpku 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: // 记录累加和 int sum = 0; TreeNode* convertBST(TreeNode* root) { traverse(root); return root; } // 遍历右子树 -> 维护累加和 -> 转换为累加树 -> 遍历左子树 void traverse(TreeNode* root) { if (root == nullptr) { return; } traverse(root->right); // 遍历右子树 sum += root->val; // 维护累加和 root->val = sum; // 将BST转换为累加树 traverse(root->left); // 遍历左子树 } }; ``` ```go // by chatGPT (go) /** * Definition for a binary tree node. * type TreeNode struct { * Val int * Left *TreeNode * Right *TreeNode * } */ func convertBST(root *TreeNode) *TreeNode { var sum int var traverse func(*TreeNode) //记录累加和 traverse = func(root *TreeNode) { if root == nil { return } traverse(root.Right) //维护累加和 sum += root.Val //将BST转化成累加树 root.Val = sum traverse(root.Left) } traverse(root) return root } ``` ```java // by labuladong (java) class Solution { public TreeNode convertBST(TreeNode root) { traverse(root); return root; } // 记录累加和 int sum = 0; void traverse(TreeNode root) { if (root == null) { return; } traverse(root.right); // 维护累加和 sum += root.val; // 将 BST 转化成累加树 root.val = sum; traverse(root.left); } } ``` ```javascript // by chatGPT (javascript) /** * @param {TreeNode} root * @return {TreeNode} */ var convertBST = function(root) { // 记录累加和 let sum = 0; function traverse(root) { if (root === null) { return; } traverse(root.right); // 维护累加和 sum += root.val; // 将 BST 转化成累加树 root.val = sum; traverse(root.left); } traverse(root); return root; } ``` ```python # by chatGPT (python) class Solution: def convertBST(self, root: TreeNode) -> TreeNode: self.sum = 0 #记录累加和 self.traverse(root) return root def traverse(self, root: TreeNode) -> None: if not root: return self.traverse(root.right) self.sum += root.val #维护累加和 root.val = self.sum #将 BST 转化成累加树 self.traverse(root.left) ``` https://leetcode.cn/problems/w6cpku 的多语言解法👆 https://leetcode.cn/problems/walls-and-gates 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: void wallsAndGates(vector>& rooms) { const int ROOM = INT_MAX; const int GATE = 0; int m = rooms.size(); int n = rooms[0].size(); queue> q; // 把所有的门放入队列,作为 BFS 遍历的起点 for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { if (rooms[i][j] == GATE) { q.push({i, j}); } } } vector> dirs = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}}; // 开始执行 BFS 算法,根据 BFS 算法的特性,第一次遍历到新节点所走的步数就是最短距离 while (!q.empty()) { auto cur = q.front(); q.pop(); int x = cur.first, y = cur.second; for (auto d : dirs) { int next_x = x + d[0], next_y = y + d[1]; if (next_x < 0 || next_y < 0 || next_x >= m || next_y >= n || rooms[next_x][next_y] != ROOM) { // 这个位置是障碍物或者已经计算出最短距离了,跳过 continue; } // 计算出 (next_x, next_y) 达到最近的门的距离 rooms[next_x][next_y] = rooms[x][y] + 1; q.push({next_x, next_y}); } } } }; ``` ```go // by chatGPT (go) func wallsAndGates(rooms [][]int) { const ROOM, GATE = math.MaxInt32, 0 m, n := len(rooms), len(rooms[0]) q := [][]int{} // 把所有的门放入队列,作为 BFS 遍历的起点 for i := 0; i < m; i++ { for j := 0; j < n; j++ { if rooms[i][j] == GATE { q = append(q, []int{i, j}) } } } dirs := [][]int{{1, 0}, {-1, 0}, {0, 1}, {0, -1}} // 开始执行 BFS 算法,根据 BFS 算法的特性,第一次遍历到新节点所走的步数就是最短距离 for len(q) > 0 { cur := q[0] q = q[1:] x, y := cur[0], cur[1] for _, d := range dirs { next_x, next_y := x+d[0], y+d[1] if next_x < 0 || next_y < 0 || next_x >= m || next_y >= n || rooms[next_x][next_y] != ROOM { // 这个位置是障碍物或者已经计算出最短距离了,跳过 continue } // 计算出 (next_x, next_y) 达到最近的门的距离 rooms[next_x][next_y] = rooms[x][y] + 1 q = append(q, []int{next_x, next_y}) } } } ``` ```java // by labuladong (java) class Solution { public void wallsAndGates(int[][] rooms) { final int ROOM = Integer.MAX_VALUE; final int GATE = 0; int m = rooms.length; int n = rooms[0].length; Queue q = new LinkedList<>(); // 把所有的门放入队列,作为 BFS 遍历的起点 for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { if (rooms[i][j] == GATE) { q.add(new int[]{i, j}); } } } int[][] dirs = new int[][]{{1, 0}, {-1, 0}, {0, 1}, {0, -1}}; // 开始执行 BFS 算法,根据 BFS 算法的特性,第一次遍历到新节点所走的步数就是最短距离 while (!q.isEmpty()) { int[] cur = q.poll(); int x = cur[0], y = cur[1]; for (int[] d : dirs) { int next_x = x + d[0], next_y = y + d[1]; if (next_x < 0 || next_y < 0 || next_x >= m || next_y >= n || rooms[next_x][next_y] != ROOM) { // 这个位置是障碍物或者已经计算出最短距离了,跳过 continue; } // 计算出 (next_x, next_y) 达到最近的门的距离 rooms[next_x][next_y] = rooms[x][y] + 1; q.offer(new int[]{next_x, next_y}); } } } } ``` ```javascript // by chatGPT (javascript) /** * @param {number[][]} rooms * @return {void} Do not return anything, modify rooms in-place instead. */ var wallsAndGates = function(rooms) { const ROOM = Infinity; const GATE = 0; let m = rooms.length; let n = rooms[0].length; let q = []; // 把所有的门放入队列,作为 BFS 遍历的起点 for (let i = 0; i < m; i++) { for (let j = 0; j < n; j++) { if (rooms[i][j] === GATE) { q.push([i, j]); } } } let dirs = [[1, 0], [-1, 0], [0, 1], [0, -1]]; // 开始执行 BFS 算法,根据 BFS 算法的特性,第一次遍历到新节点所走的步数就是最短距离 while (q.length) { let cur = q.shift(); let x = cur[0], y = cur[1]; for (let i = 0; i < dirs.length; i++) { let next_x = x + dirs[i][0], next_y = y + dirs[i][1]; if (next_x < 0 || next_y < 0 || next_x >= m || next_y >= n || rooms[next_x][next_y] !== ROOM) { // 这个位置是障碍物或者已经计算出最短距离了,跳过 continue; } // 计算出 (next_x, next_y) 达到最近的门的距离 rooms[next_x][next_y] = rooms[x][y] + 1; q.push([next_x, next_y]); } } }; ``` ```python # by chatGPT (python) from collections import deque class Solution: def wallsAndGates(self, rooms: List[List[int]]) -> None: """ Do not return anything, modify rooms in-place instead. """ ROOM = float('inf') GATE = 0 m = len(rooms) n = len(rooms[0]) q = deque() # 把所有的门放入队列,作为 BFS 遍历的起点 for i in range(m): for j in range(n): if rooms[i][j] == GATE: q.append([i, j]) dirs = [[1, 0], [-1, 0], [0, 1], [0, -1]] # 开始执行 BFS 算法,根据 BFS 算法的特性,第一次遍历到新节点所走的步数就是最短距离 while q: cur = q.popleft() x = cur[0] y = cur[1] for d in dirs: next_x = x + d[0] next_y = y + d[1] if next_x < 0 or next_y < 0 or next_x >= m or next_y >= n or rooms[next_x][next_y] != ROOM: # 这个位置是障碍物或者已经计算出最短距离了,跳过 continue # 计算出 (next_x, next_y) 达到最近的门的距离 rooms[next_x][next_y] = rooms[x][y] + 1 q.append([next_x, next_y]) ``` https://leetcode.cn/problems/walls-and-gates 的多语言解法👆 https://leetcode.cn/problems/wildcard-matching 的多语言解法👇 ```cpp // by labuladong (cpp) class Solution { public: // 备忘录,-1 代表还未计算,0 代表 false,1 代表 true vector> memo; bool isMatch(string s, string p) { if (p.empty()) { return s.empty(); } // 将 p 中相邻的 * 去除,以提升效率 string pp = remove_adj_star(p); int m = s.size(), n = pp.size(); // 备忘录初始化为 -1 memo = vector>(m, vector(n, -1)); // 执行自顶向下带备忘录的动态规划 return dp(s, 0, pp, 0); } // 删除相邻的 * 号,返回删除后的字符 string remove_adj_star(string p) { if (p.empty()) { return ""; } string pp; pp.push_back(p[0]); for (int i = 1; i < p.size(); i++) { if (p[i] == '*' && p[i - 1] == '*') { continue; } pp.push_back(p[i]); } return pp; } // 定义:判断 s[i..] 是否能被 p[j..] 匹配 bool dp(string& s, int i, string& p, int j) { // base case if (j == p.size() && i == s.size()) { return true; } if (i == s.size()) { for (int k = j; k < p.size(); k++) { if (p[k] != '*') { return false; } } return true; } if (j == p.size()) { return false; } if (memo[i][j] != -1) { return bool(memo[i][j]); } bool res = false; if (s[i] == p[j] || p[j] == '?') { // s[i] 和 p[j] 完成匹配 res = dp(s, i + 1, p, j + 1); } else if (p[j] == '*') { // s[i] 和 p[j] 不匹配,但 p[j] 是通配符 * // 可以匹配 0 个或多个 s 中的字符, // 只要有一种情况能够完成匹配即可 res = dp(s, i + 1, p, j) || dp(s, i, p, j + 1); } // 将 s[i] 和 p[j] 的匹配结果存储备忘录 memo[i][j] = res; return res; } }; ``` ```go // by chatGPT (go) func isMatch(s string, p string) bool { // 备忘录,-1 代表还未计算,0 代表 false,1 代表 true memo := make([][]int, len(s)) for i := range memo { memo[i] = make([]int, len(p)) for j := range memo[i] { memo[i][j] = -1 } } if len(p) == 0 { return len(s) == 0 } // 将 p 中相邻的 * 去除,以提升效率 pp := removeAdjStar(p) return dp(s, 0, pp, 0, memo) } // 删除相邻的 * 号,返回删除后的字符 func removeAdjStar(p string) string { if len(p) == 0 { return "" } pp := []byte{p[0]} for i := 1; i < len(p); i++ { if p[i] == '*' && p[i-1] == '*' { continue } pp = append(pp, p[i]) } return string(pp) } // 定义:判断 s[i..] 是否能被 p[j..] 匹配 func dp(s string, i int, p string, j int, memo [][]int) bool { // base case if j == len(p) && i == len(s) { return true } if i == len(s) { for k := j; k < len(p); k++ { if p[k] != '*' { return false } } return true } if j == len(p) { return false } if memo[i][j] != -1 { return memo[i][j] == 1 } var res bool if s[i] == p[j] || p[j] == '?' { // s[i] 和 p[j] 完成匹配 res = dp(s, i+1, p, j+1, memo) } else if p[j] == '*' { // s[i] 和 p[j] 不匹配,但 p[j] 是通配符 * // 可以匹配 0 个或多个 s 中的字符, // 只要有一种情况能够完成匹配即可 res = dp(s, i+1, p, j, memo) || dp(s, i, p, j+1, memo) } // 将 s[i] 和 p[j] 的匹配结果存储备忘录 memo[i][j] = 0 if res { memo[i][j] = 1 } return res } ``` ```java // by chatGPT (java) class Solution { // 备忘录,-1 代表还未计算,0 代表 false,1 代表 true private int[][] memo; public boolean isMatch(String s, String p) { if (p.isEmpty()) { return s.isEmpty(); } // 将 p 中相邻的 * 去除,以提升效率 String pp = removeAdjStar(p); int m = s.length(), n = pp.length(); // 备忘录初始化为 -1 memo = new int[m][n]; for (int i = 0; i < m; i++) { Arrays.fill(memo[i], -1); } // 执行自顶向下带备忘录的动态规划 return dp(s, 0, pp, 0); } // 删除相邻的 * 号,返回删除后的字符 private String removeAdjStar(String p) { if (p.isEmpty()) { return ""; } StringBuilder sb = new StringBuilder(); sb.append(p.charAt(0)); for (int i = 1; i < p.length(); i++) { if (p.charAt(i) == '*' && p.charAt(i - 1) == '*') { continue; } sb.append(p.charAt(i)); } return sb.toString(); } // 定义:判断 s[i..] 是否能被 p[j..] 匹配 private boolean dp(String s, int i, String p, int j) { // base case if (j == p.length() && i == s.length()) { return true; } if (i == s.length()) { for (int k = j; k < p.length(); k++) { if (p.charAt(k) != '*') { return false; } } return true; } if (j == p.length()) { return false; } if (memo[i][j] != -1) { return memo[i][j] == 1; } boolean res = false; if (s.charAt(i) == p.charAt(j) || p.charAt(j) == '?') { // s[i] 和 p[j] 完成匹配 res = dp(s, i + 1, p, j + 1); } else if (p.charAt(j) == '*') { // s[i] 和 p[j] 不匹配,但 p[j] 是通配符 * // 可以匹配 0 个或多个 s 中的字符, // 只要有一种情况能够完成匹配即可 res = dp(s, i + 1, p, j) || dp(s, i, p, j + 1); } // 将 s[i] 和 p[j] 的匹配结果存储备忘录 memo[i][j] = res ? 1 : 0; return res; } } ``` ```javascript // by chatGPT (javascript) var isMatch = function(s, p) { // 备忘录,-1 代表还未计算,0 代表 false,1 代表 true var memo = []; function remove_adj_star(p) { if (p.length === 0) { return ""; } var pp = p[0]; for (var i = 1; i < p.length; i++) { if (p[i] === '*' && p[i - 1] == '*') { continue; } pp += p[i]; } return pp; } // 定义:判断 s[i..] 是否能被 p[j..] 匹配 function dp(s, i, p, j) { // base case if (j === p.length && i === s.length) { return true; } if (i === s.length) { for (var k = j; k < p.length; k++) { if (p[k] !== '*') { return false; } } return true; } if (j === p.length) { return false; } if (typeof memo[i] !== "undefined" && typeof memo[i][j] !== "undefined" && memo[i][j] !== -1) { return memo[i][j] === 1; } var res = false; if (s[i] === p[j] || p[j] === '?') { // s[i] 和 p[j] 完成匹配 res = dp(s, i + 1, p, j + 1); } else if (p[j] === '*') { // s[i] 和 p[j] 不匹配,但 p[j] 是通配符 * // 可以匹配 0 个或多个 s 中的字符, // 只要有一种情况能够完成匹配即可 res = dp(s, i + 1, p, j) || dp(s, i, p, j + 1); } // 将 s[i] 和 p[j] 的匹配结果存储备忘录 if (typeof memo[i] === "undefined") { memo[i] = []; } memo[i][j] = res ? 1 : 0; return res; } // 将 p 中相邻的 * 去除,以提升效率 var pp = remove_adj_star(p); var m = s.length, n = pp.length; // 备忘录初始化为 -1 for (var i = 0; i < m; i++) { memo[i] = []; for (var j = 0; j < n; j++) { memo[i][j] = -1; } } // 执行自顶向下带备忘录的动态规划 return dp(s, 0, pp, 0); }; ``` ```python # by chatGPT (python) class Solution: def isMatch(self, s: str, p: str) -> bool: if not p: return not s # 将 p 中相邻的 * 去除,以提升效率 pp = self.remove_adj_star(p) m, n = len(s), len(pp) # 备忘录初始化为 -1 memo = [[-1] * n for _ in range(m)] # 执行自顶向下带备忘录的动态规划 return self.dp(s, 0, pp, 0, memo) # 删除相邻的 * 号,返回删除后的字符 def remove_adj_star(self, p: str) -> str: if not p: return "" pp = p[0] for i in range(1, len(p)): if p[i] == '*' and p[i - 1] == '*': continue pp += p[i] return pp # 定义:判断 s[i..] 是否能被 p[j..] 匹配 def dp(self, s: str, i: int, p: str, j: int, memo: List[List[int]]) -> bool: # base case if j == len(p) and i == len(s): return True if i == len(s): for k in range(j, len(p)): if p[k] != '*': return False return True if j == len(p): return False if memo[i][j] != -1: return bool(memo[i][j]) res = False if s[i] == p[j] or p[j] == '?': # s[i] 和 p[j] 完成匹配 res = self.dp(s, i + 1, p, j + 1, memo) elif p[j] == '*': # s[i] 和 p[j] 不匹配,但 p[j] 是通配符 * # 可以匹配 0 个或多个 s 中的字符, # 只要有一种情况能够完成匹配即可 res = self.dp(s, i + 1, p, j, memo) or self.dp(s, i, p, j + 1, memo) # 将 s[i] 和 p[j] 的匹配结果存储备忘录 memo[i][j] = res return res ``` https://leetcode.cn/problems/wildcard-matching 的多语言解法👆 https://leetcode.cn/problems/word-break-ii 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: vector res; // 记录回溯路径 deque track; vector wordBreak(string s, vector& wordDict) { // 根据函数定义,判断 s[0..] 是否能够被拼出 backtrack(s, 0, wordDict); return res; } // 回溯算法框架 void backtrack(string s, int i, vector& wordDict) { // base case,整个 s 都被拼出来了 if (i == s.length()) { res.emplace_back(track[0]); for (int j = 1; j < track.size(); ++j) { res.back() += " " + track[j]; } return; } if (i > s.length()) { return; } // 遍历所有单词,尝试匹配 s[i..] 的前缀 for (const string& word : wordDict) { int len = word.length(); // 单词太长,跳过 if (i + len > s.length()) { continue; } // 无法匹配,跳过 string subStr = s.substr(i, len); if (subStr != word) { continue; } // s[i..] 的前缀被 word 匹配,做选择 track.emplace_back(word); backtrack(s, i + len, wordDict); // 撤销选择 track.pop_back(); } } }; ``` ```go // by chatGPT (go) func wordBreak(s string, wordDict []string) []string { res := []string{} // 记录回溯路径 track := []string{} // 根据函数定义,判断 s[0..] 是否能够被拼出 backtrack(s, 0, wordDict, &track, &res) return res } // 回溯算法框架 func backtrack(s string, i int, wordDict []string, track *[]string, res *[]string) { // base case,整个 s 都被拼出来了 if i == len(s) { *res = append(*res, strings.Join(*track, " ")) return } if i > len(s) { return } // 遍历所有单词,尝试匹配 s[i..] 的前缀 for _, word := range(wordDict) { len := len(word) // 单词太长,跳过 if i + len > len(s) { continue } // 无法匹配,跳过 subStr := s[i:i+len] if subStr != word { continue } // s[i..] 的前缀被 word 匹配,做选择 *track = append(*track, word) backtrack(s, i + len, wordDict, track, res) // 撤销选择 *track = (*track)[:len(*track)-1] } } ``` ```java // by labuladong (java) class Solution { List res = new LinkedList<>(); // 记录回溯路径 LinkedList track = new LinkedList<>(); public List wordBreak(String s, List wordDict) { // 根据函数定义,判断 s[0..] 是否能够被拼出 backtrack(s, 0, wordDict); return res; } // 回溯算法框架 void backtrack(String s, int i, List wordDict) { // base case,整个 s 都被拼出来了 if (i == s.length()) { res.add(String.join(" ", track)); return; } if (i > s.length()) { return; } // 遍历所有单词,尝试匹配 s[i..] 的前缀 for (String word : wordDict) { int len = word.length(); // 单词太长,跳过 if (i + len > s.length()) { continue; } // 无法匹配,跳过 String subStr = s.substring(i, i + len); if (!subStr.equals(word)) { continue; } // s[i..] 的前缀被 word 匹配,做选择 track.addLast(word); backtrack(s, i + len, wordDict); // 撤销选择 track.removeLast(); } } } ``` ```javascript // by chatGPT (javascript) var wordBreak = function(s, wordDict) { let res = []; // 记录结果 let track = []; // 记录回溯路径 // 回溯算法框架 function backtrack(s, i, wordDict) { // base case,整个 s 都被拼出来了 if (i === s.length) { res.push(track.join(" ")); return; } if (i > s.length) { return; } // 遍历所有单词,尝试匹配 s[i..] 的前缀 for (let word of wordDict) { let len = word.length; // 单词太长,跳过 if (i + len > s.length) { continue; } // 无法匹配,跳过 let subStr = s.substring(i, i + len); if (subStr !== word) { continue; } // s[i..] 的前缀被 word 匹配,做选择 track.push(word); backtrack(s, i + len, wordDict); // 撤销选择 track.pop(); } } backtrack(s, 0, wordDict); return res; }; ``` ```python # by chatGPT (python) class Solution: def __init__(self): self.res = [] # 记录回溯路径 self.track = [] def wordBreak(self, s: str, wordDict: List[str]) -> List[str]: # 根据函数定义,判断 s[0..] 是否能够被拼出 self.backtrack(s, 0, wordDict) return self.res # 回溯算法框架 def backtrack(self, s: str, i: int, wordDict: List[str]) -> None: # base case,整个 s 都被拼出来了 if i == len(s): self.res.append(" ".join(self.track)) return if i > len(s): return # 遍历所有单词,尝试匹配 s[i..] 的前缀 for word in wordDict: length = len(word) # 单词太长,跳过 if i + length > len(s): continue # 无法匹配,跳过 sub_str = s[i:i + length] if sub_str != word: continue # s[i..] 的前缀被 word 匹配,做选择 self.track.append(word) self.backtrack(s, i + length, wordDict) # 撤销选择 self.track.pop() ``` https://leetcode.cn/problems/word-break-ii 的多语言解法👆 https://leetcode.cn/problems/word-pattern 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: bool wordPattern(string pattern, string s) { vector words; string sub_s = ""; //将单词存入vector中 for (int i = 0; i < s.size(); i++) { if (s[i] == ' ') { words.push_back(sub_s); sub_s = ""; } else { sub_s += s[i]; } } words.push_back(sub_s); if (pattern.size() != words.size()) { return false; } //记录pattern字符到单词的映射 unordered_map patternToWord; //记录哪些已经有pattern对应的单词 unordered_set wordSet; for (int i = 0; i < pattern.size(); i++) { char c = pattern[i]; string word = words[i]; if (!patternToWord.count(c)) { if (wordSet.count(word)) { //这个单词以前已经有其他模式字符对应了 return false; } //添加c->word的映射 patternToWord[c] = word; } else { //这个pattern字符已经出现过,确保和之前对应的单词相同 if (patternToWord[c] != word) { return false; } } //这个单词已经有模式字符对应 wordSet.insert(word); } return true; } }; class Solution2 { public: bool wordPattern(string pattern, string s) { vector words; string sub_s = ""; //将单词存入vector中 for (int i = 0; i < s.size(); i++) { if (s[i] == ' ') { words.push_back(sub_s); sub_s = ""; } else { sub_s += s[i]; } } words.push_back(sub_s); if (pattern.size() != words.size()) { return false; } //记录pattern字符到单词的映射 unordered_map wordToPattern; unordered_set patternCharSet; for (int i = 0; i < pattern.size(); i++) { char c = pattern[i]; string word = words[i]; if (!wordToPattern.count(word)) { //当前这个单词还没有对应的模式字符 if (patternCharSet.count(c)) { //对应的模式字符之前已经对应了其他单词 return false; } //添加word->c的映射 wordToPattern[word] = c; } else { //这个单词之前已经出现过,确保当前单词和之前对应的模式字符相同 if (wordToPattern[word] != c) { return false; } } patternCharSet.insert(c); } return true; } }; ``` ```go // by chatGPT (go) func wordPattern(pattern string, s string) bool { words := strings.Split(s, " ") if len(pattern) != len(words) { return false } // 记录 pattern 字符到单词的映射 patternToWord := make(map[byte]string) // 记录那些已经有 pattern 对应的单词 wordSet := make(map[string]bool) for i := 0; i < len(pattern); i++ { c := pattern[i] word := words[i] if _, ok := patternToWord[c]; !ok { if _, ok := wordSet[word]; ok { // 这个单词以前已经有其他模式字符对应了 return false } // 添加 c -> word 的映射 patternToWord[c] = word } else { // 这个 pattern 字符已经出现过,确保和之前对应的单词相同 if patternToWord[c] != word { return false } } // 这个单词已经有模式字符对应 wordSet[word] = true } return true } func wordPattern2(pattern string, s string) bool { words := strings.Split(s, " ") if len(pattern) != len(words) { return false } // 记录 word 到 pattern 字符的映射 wordToPattern := make(map[string]byte) // 记录那些已经有单词对应的 pattern 字符 patternCharSet := make(map[byte]bool) for i := 0; i < len(pattern); i++ { c := pattern[i] word := words[i] if _, ok := wordToPattern[word]; !ok { // 当前这个单词还没有对应的模式字符 if _, ok := patternCharSet[c]; ok { // 对应的模式字符之前已经对应了其他单词 return false } // 添加 word -> c 的映射 wordToPattern[word] = c } else { // 这个单词之前已经出现过,确保当前单词和之前对应的模式字符相同 if wordToPattern[word] != c { return false } } patternCharSet[c] = true } return true } ``` ```java // by labuladong (java) class Solution { public boolean wordPattern(String pattern, String s) { String[] words = s.split(" "); if (pattern.length() != words.length) { return false; } // 记录 pattern 字符到单词的映射 HashMap patternToWord = new HashMap<>(); // 记录那些已经有 pattern 对应的单词 HashSet wordSet = new HashSet<>(); for (int i = 0; i < pattern.length(); i++) { char c = pattern.charAt(i); String word = words[i]; if (!patternToWord.containsKey(c)) { if (wordSet.contains(word)) { // 这个单词以前已经有其他模式字符对应了 return false; } // 添加 c -> word 的映射 patternToWord.put(c, word); } else { // 这个 pattern 字符已经出现过,确保和之前对应的单词相同 if (!patternToWord.get(c).equals(word)) { return false; } } // 这个单词已经有模式字符对应 wordSet.add(word); } return true; } } class Solution2 { public boolean wordPattern(String pattern, String s) { String[] words = s.split(" "); if (pattern.length() != words.length) { return false; } // 记录 pattern 字符到单词的映射 HashMap wordToPattern = new HashMap<>(); HashSet patternCharSet = new HashSet<>(); for (int i = 0; i < pattern.length(); i++) { char c = pattern.charAt(i); String word = words[i]; if (!wordToPattern.containsKey(word)) { // 当前这个单词还没有对应的模式字符 if (patternCharSet.contains(c)) { // 对应的模式字符之前已经对应了其他单词 return false; } // 添加 word -> c 的映射 wordToPattern.put(word, c); } else { // 这个单词之前已经出现过,确保当前单词和之前对应的模式字符相同 if (!wordToPattern.get(word).equals(c)) { return false; } } patternCharSet.add(c); } return true; } } ``` ```javascript // by chatGPT (javascript) var wordPattern = function(pattern, s) { var words = s.split(" "); if (pattern.length !== words.length) { return false; } // 记录 pattern 字符到单词的映射 var patternToWord = new Map(); // 记录那些已经有 pattern 对应的单词 var wordSet = new Set(); for (var i = 0; i < pattern.length; i++) { var c = pattern.charAt(i); var word = words[i]; if (!patternToWord.has(c)) { if (wordSet.has(word)) { // 这个单词以前已经有其他模式字符对应了 return false; } // 添加 c -> word 的映射 patternToWord.set(c, word); } else { // 这个 pattern 字符已经出现过,确保和之前对应的单词相同 if (!patternToWord.get(c) === (word)) { return false; } } // 这个单词已经有模式字符对应 wordSet.add(word); } return true; }; var wordPattern2 = function(pattern, s) { var words = s.split(" "); if (pattern.length !== words.length) { return false; } // 记录 pattern 字符到单词的映射 var wordToPattern = new Map(); var patternCharSet = new Set(); for (var i = 0; i < pattern.length; i++) { var c = pattern.charAt(i); var word = words[i]; if (!wordToPattern.has(word)) { // 当前这个单词还没有对应的模式字符 if (patternCharSet.has(c)) { // 对应的模式字符之前已经对应了其他单词 return false; } // 添加 word -> c 的映射 wordToPattern.set(word, c); } else { // 这个单词之前已经出现过,确保当前单词和之前对应的模式字符相同 if (!wordToPattern.get(word) === c) { return false; } } patternCharSet.add(c); } return true; }; ``` ```python # by chatGPT (python) class Solution: def wordPattern(self, pattern: str, s: str) -> bool: words = s.split(" ") if len(pattern) != len(words): return False #记录 pattern 字符到单词的映射 patternToWord = {} #记录那些已经有 pattern 对应的单词 wordSet = set() for i in range(len(pattern)): c = pattern[i] word = words[i] if c not in patternToWord: if word in wordSet: #这个单词以前已经有其他模式字符对应了 return False #添加 c -> word 的映射 patternToWord[c] = word else: #这个 pattern 字符已经出现过,确保和之前对应的单词相同 if patternToWord[c] != word: return False #这个单词已经有模式字符对应 wordSet.add(word) return True class Solution2: def wordPattern(self, pattern: str, s: str) -> bool: words = s.split(" ") if len(pattern) != len(words): return False #记录 pattern 字符到单词的映射 wordToPattern = {} patternCharSet = set() for i in range(len(pattern)): c = pattern[i] word = words[i] if word not in wordToPattern: #当前这个单词还没有对应的模式字符 if c in patternCharSet: #对应的模式字符之前已经对应了其他单词 return False #添加 word -> c 的映射 wordToPattern[word] = c else: #这个单词之前已经出现过,确保当前单词和之前对应的模式字符相同 if wordToPattern[word] != c: return False patternCharSet.add(c) return True ``` https://leetcode.cn/problems/word-pattern 的多语言解法👆 https://leetcode.cn/problems/word-search 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { bool found = false; public: bool exist(vector>& board, string word) { int m = board.size(), n = board[0].size(); for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { dfs(board, i, j, word, 0); if (found) { // 如果找到了一个答案,直接返回true return true; } } } return false; // 没有找到答案,返回false } // 从 (i, j) 开始向四周搜索,试图匹配 word[p..] void dfs(vector>& board, int i, int j, string& word, int p) { if (p == word.size()) { // 整个word已经被匹配完,找到了一个答案 found = true; return; } if (found) { // 已经找到了一个答案,不用再搜索了 return; } int m = board.size(), n = board[0].size(); if (i < 0 || j < 0 || i >= m || j >= n) { // 在边界之外,返回 return; } if (board[i][j] != word[p]) { // 与当前字符不匹配,返回 return; } // 因为之前匹配的字符不能重复匹配,所以我们给它们添加一个负号标记 board[i][j] = (char)(-board[i][j]); 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]); // 回溯当前字符 } }; ``` ```go // by chatGPT (go) func exist(board [][]byte, word string) bool { m, n := len(board), len(board[0]) found := false var dfs func(i, j, p int) dfs = func(i, j, p int) { if p == len(word) { // 整个 word 已经被匹配完,找到了一个答案 found = true return } if found { // 已经找到了一个答案,不用再搜索了 return } if i < 0 || j < 0 || i >= m || j >= n { return } if board[i][j] != word[p] { return } // 已经匹配过的字符,我们给它添一个负号作为标记,避免走回头路 board[i][j] ^= 255 // word[p] 被 board[i][j] 匹配,开始向四周搜索 word[p+1..] dfs(i+1, j, p+1) dfs(i, j+1, p+1) dfs(i-1, j, p+1) dfs(i, j-1, p+1) board[i][j] ^= 255 } for i := 0; i < m; i++ { for j := 0; j < n; j++ { dfs(i, j, 0) if found { return true } } } return false } ``` ```java // by labuladong (java) class Solution { boolean found = false; public boolean exist(char[][] board, String word) { 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]); } } ``` ```javascript // by chatGPT (javascript) /** * @param {character[][]} board * @param {string} word * @return {boolean} */ var exist = function(board, word) { let found = false; let dfs = function(board, i, j, word, p){ // 当整个 word 已经被匹配完,找到了一个答案 if (p == word.length) { found = true; return; } // 已经找到了一个答案,不用再搜索了 if (found) { return; } let m = board.length, n = board[0].length; // 如果越界了,直接返回 if (i < 0 || j < 0 || i >= m || j >= n) { return; } // 如果当前字符和 word 的当前字符不相等,返回 if (board[i][j] != word.charAt(p)) { return; } // 已经匹配过的字符,我们给它添一个负号作为标记,避免走回头路 board[i][j] = -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] = -board[i][j]; } let m = board.length, n = board[0].length; for (let i = 0; i < m; i++) { for (let j = 0; j < n; j++) { dfs(board, i, j, word, 0); if (found) { return true; } } } return false; }; ``` ```python # by chatGPT (python) class Solution: def __init__(self): self.found = False def exist(self, board: List[List[str]], word: str) -> bool: m, n = len(board), len(board[0]) for i in range(m): for j in range(n): self.dfs(board, i, j, word, 0) # 如果找到了一个答案,则直接返回True if self.found: return True return False # 从 (i, j) 开始向四周搜索,试图匹配 word[p..] def dfs(self, board, i, j, word, p): if p == len(word): # 整个 word 已经被匹配完,找到了一个答案 self.found = True return if self.found: # 已经找到了一个答案,不用再搜索了 return m, n = len(board), len(board[0]) if i < 0 or j < 0 or i >= m or j >= n: return if board[i][j] != word[p]: return # 已经匹配过的字符,我们给它添一个负号作为标记,避免走回头路 board[i][j] = -board[i][j] # word[p] 被 board[i][j] 匹配,开始向四周搜索 word[p+1..] self.dfs(board, i+1, j, word, p+1) self.dfs(board, i, j+1, word, p+1) self.dfs(board, i-1, j, word, p+1) self.dfs(board, i, j-1, word, p+1) # 还原标记(恢复现场) board[i][j] = -board[i][j] ``` https://leetcode.cn/problems/word-search 的多语言解法👆 https://leetcode.cn/problems/wtcaE1 的多语言解法👇 ```cpp // by labuladong (cpp) class Solution { public: int lengthOfLongestSubstring(string s) { unordered_map window; int left = 0, right = 0; int res = 0; // 记录结果 while (right < s.size()) { char c = s[right]; right++; // 进行窗口内数据的一系列更新 window[c]++; // 判断左侧窗口是否要收缩 while (window[c] > 1) { char d = s[left]; left++; // 进行窗口内数据的一系列更新 window[d]--; } // 在这里更新答案 res = max(res, right - left); } return res; } }; ``` ```go // by chatGPT (go) func lengthOfLongestSubstring(s string) int { window := make(map[byte]int) // 用哈希表记录窗口中每个字符出现的次数 left, right := 0, 0 // 定义一些变量,记录窗口的左右边界和最长子串的长度 res := 0 for right < len(s) { // 当右边界没有到字符串的末尾时,执行循环 c := s[right] // 获取当前右边界指向的字符 right++ // 右边界后移一位 window[c]++ // 哈希表中加入当前字符,记录出现的次数 for window[c] > 1 { // 如果当前字符在窗口中的出现次数大于1,则进入循环 d := s[left] // 获取窗口中最左边的字符 left++ // 左边界右移一位 window[d]-- // 从哈希表中移除最左边的字符 } res = max(res, right - left) // 更新最长子串的长度 } return res // 返回最长子串的长度 } func max(a, b int) int { // 定义一个函数用于返回两个整数中的最大值 if a > b { // 如果a大于b return a // 返回a } return b // 否则返回b } ``` ```java // by chatGPT (java) 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.getOrDefault(d, 0) - 1); } // 在这里更新答案 res = Math.max(res, right - left); } return res; } } ``` ```javascript // by chatGPT (javascript) /** * @param {string} s * @return {number} */ var lengthOfLongestSubstring = function(s) { // 创建一个映射表,用于记录字母出现的次数 let window = {}; // 定义两个指针,分别表示窗口的左右边界 let left = 0; let right = 0; // 定义一个变量,用于记录最长的子串长度 let res = 0; // 当右边界没有超过字符串s的长度时,一直循环 while (right < s.length) { let c = s[right]; right++; // 更新映射表 window[c] = window[c] ? window[c] + 1 : 1; // 如果当前字母出现的次数大于1(即窗口内已经有了该字母),需要收缩左边界,使得窗口内不再有重复的字母 while (window[c] > 1) { let d = s[left]; left++; // 更新映射表 window[d]--; } // 更新当前最长的子串长度 res = Math.max(res, right - left); } return res; }; ``` ```python # by chatGPT (python) class Solution: def lengthOfLongestSubstring(self, s: str) -> int: # 创建一个哈希表 window = {} # 定义左右指针和结果变量 left, right = 0, 0 res = 0 # 当右指针小于字符串长度时 while right < len(s): c = s[right] right += 1 # 将字符添加到哈希表中并更新出现的次数 if c not in window: window[c] = 1 else: window[c] += 1 # 当窗口中字符出现次数大于 1 时,将左指针向右移动,更新哈希表中的值 while window[c] > 1: d = s[left] left += 1 window[d] -= 1 # 更新结果变量,记录最长子串长度 res = max(res, right - left) # 返回最长子串长度 return res ``` https://leetcode.cn/problems/wtcaE1 的多语言解法👆 https://leetcode.cn/problems/xu-lie-hua-er-cha-shu-lcof 的多语言解法👇 ```cpp // by chatGPT (cpp) class Codec { public: string SEP = ","; string EMPTY = "#"; /* 主函数,将二叉树序列化为字符串 */ string serialize(TreeNode* root) { string res = ""; serialize(root, res); return res; } /* 辅助函数,将二叉树存入字符串 */ void serialize(TreeNode* root, string& res) { if (root == nullptr) { res += EMPTY + SEP; return; } /******前序遍历位置******/ res += to_string(root->val) + SEP; /***********************/ serialize(root->left, res); serialize(root->right, res); } /* 主函数,将字符串反序列化为二叉树结构 */ TreeNode* deserialize(string data) { // 将字符串转化成列表 vector nodes; string s = ""; for (char& c : data) { if (c == SEP[0]) { nodes.emplace_back(s); s = ""; } else { s += c; } } if (!s.empty()) nodes.emplace_back(s); return deserialize(nodes); } /* 辅助函数,通过 nodes 列表构造二叉树 */ TreeNode* deserialize(vector& nodes) { if (nodes.empty()) return nullptr; /******前序遍历位置******/ // 列表最左侧就是根节点 string first = nodes[0]; nodes.erase(nodes.begin()); if (first == EMPTY) return nullptr; TreeNode* root = new TreeNode(stoi(first)); /***********************/ root->left = deserialize(nodes); root->right = deserialize(nodes); return root; } }; ``` ```go // by chatGPT (go) type Codec struct { SEP string NULL string } func Constructor() Codec { return Codec{ SEP: ",", NULL: "#", } } /* 辅助函数,将二叉树存入字符串 */ func (this *Codec) serialize(root *TreeNode, sb *strings.Builder) { if root == nil { sb.WriteString(this.NULL) sb.WriteString(this.SEP) return } /******前序遍历位置******/ sb.WriteString(strconv.Itoa(root.Val)) sb.WriteString(this.SEP) /***********************/ this.serialize(root.Left, sb) this.serialize(root.Right, sb) } /* 主函数,将二叉树序列化为字符串 */ func (this *Codec) serialize(root *TreeNode) string { var sb strings.Builder this.serialize(root, &sb) return sb.String() } /* 辅助函数,根据字符串构建二叉树 */ func (this *Codec) deserialize(nodes *[]string) *TreeNode { if len(*nodes) == 0 { return nil } /* 从 nodes 中取出一个元素作为当前子树的根节点 */ first := (*nodes)[0] if first == this.NULL { // 若该元素为 "#" ,代表其为空节点,直接弹出并返回 nil *nodes = (*nodes)[1:] return nil } /* 否则,该元素实际上是整数,将其转化为节点 */ val, _ := strconv.Atoi(first) root := &TreeNode{Val: val} /* 递归构造左右子树 */ *nodes = (*nodes)[1:] root.Left = this.deserialize(nodes) root.Right = this.deserialize(nodes) return root } /* 主函数,将字符串反序列化为二叉树结构 */ func (this *Codec) deserialize(data string) *TreeNode { nodes := strings.Split(data, this.SEP) return this.deserialize(&nodes) } ``` ```java // by labuladong (java) public class Codec { String SEP = ","; String 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; } } ``` ```javascript // by chatGPT (javascript) /** * Encodes a tree to a single string. * * @param {TreeNode} root * @return {string} */ var serialize = function(root) { if(!root) { return '#,'; // 把空节点转换为 "#," } return root.val + ',' + serialize(root.left) + serialize(root.right); // 先把当前节点的值存入字符串,然后递归地序列化左子树和右子树 }; /** * Decodes your encoded data to tree. * * @param {string} data * @return {TreeNode} */ var deserialize = function(data) { var nodes = data.split(','); // 把字符串转换成节点列表 var dfs = function() { var value = nodes.shift(); // 当前字符串对应的节点 if(value === '#') { // 遇到空节点,返回 null return null; } var node = new TreeNode(value); // 创建当前节点 node.left = dfs(); // 递归构造左子树 node.right = dfs(); // 递归构造右子树 return node; }; return dfs(); // 从根节点开始构造二叉树 }; /** * Your functions will be called as such: * deserialize(serialize(root)); */ ``` ```python # by chatGPT (python) class Codec: def __init__(self): self.SEP = "," self.NULL = "#" # 主函数,将二叉树序列化为字符串 def serialize(self, root: TreeNode) -> str: sb = [] self._serialize(root, sb) return ''.join(sb) # 辅助函数,将二叉树存入列表 def _serialize(self, root: TreeNode, sb: list): if not root: sb.append(self.NULL).append(self.SEP) return # 前序遍历 sb.append(str(root.val)).append(self.SEP) self._serialize(root.left, sb) self._serialize(root.right, sb) # 主函数,将字符串反序列化为二叉树结构 def deserialize(self, data: str) -> TreeNode: # 将字符串转化成列表 nodes = data.split(self.SEP) return self._deserialize(nodes) # 辅助函数,通过 nodes 列表构造二叉树 def _deserialize(self, nodes: list) -> TreeNode: if not nodes: return None # 前序遍历 first = nodes.pop(0) if first == self.NULL: return None root = TreeNode(int(first)) root.left = self._deserialize(nodes) root.right = self._deserialize(nodes) return root ``` https://leetcode.cn/problems/xu-lie-hua-er-cha-shu-lcof 的多语言解法👆 https://leetcode.cn/problems/xx4gT2 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int findKthLargest(vector& nums, int k) { priority_queue, greater> pq; // 小顶堆,堆顶是最小元素 for (auto e : nums) { // 每个元素都要过一遍二叉堆 pq.push(e); // 堆中元素多于 k 个时,删除堆顶元素 if (pq.size() > k) { pq.pop(); } } // pq 中剩下的是 nums 中 k 个最大元素, // 堆顶是最小的那个,即第 k 个最大元素 return pq.top(); } }; ``` ```go // by chatGPT (go) import "container/heap" func findKthLargest(nums []int, k int) int { pq := &minHeap{} heap.Init(pq) for _, e := range nums { // 每个元素都要过一遍小根堆 heap.Push(pq, e) // 堆中元素多于 k 个时,删除堆顶元素 if pq.Len() > k { heap.Pop(pq) } } // pq 中剩下的是 nums 中 k 个最大元素, // 堆顶是最小的那个,即第 k 个最大元素 return (*pq)[0] } type minHeap []int func (h minHeap) Len() int { return len(h) } func (h minHeap) Less(i, j int) bool { return h[i] < h[j] } func (h minHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] } func (h *minHeap) Push(x interface{}) { *h = append(*h, x.(int)) } func (h *minHeap) Pop() interface{} { old := *h n := len(old) x := old[n-1] *h = old[:n-1] return x } ``` ```java // by labuladong (java) class Solution { public int findKthLargest(int[] nums, int k) { // 小顶堆,堆顶是最小元素 PriorityQueue pq = new PriorityQueue<>(); for (int e : nums) { // 每个元素都要过一遍二叉堆 pq.offer(e); // 堆中元素多于 k 个时,删除堆顶元素 if (pq.size() > k) { pq.poll(); } } // pq 中剩下的是 nums 中 k 个最大元素, // 堆顶是最小的那个,即第 k 个最大元素 return pq.peek(); } } ``` ```javascript // by chatGPT (javascript) var findKthLargest = function(nums, k) { // 小顶堆,堆顶是最小元素 const pq = new PriorityQueue() for (let i = 0; i < nums.length; i++) { const e = nums[i] // 每个元素都要过一遍二叉堆 pq.offer(e) // 堆中元素多于 k 个时,删除堆顶元素 if (pq.size() > k) { pq.poll(); } } // pq 中剩下的是 nums 中 k 个最大元素, // 堆顶是最小的那个,即第 k 个最大元素 return pq.peek() }; ``` ```python # by chatGPT (python) import heapq class Solution: def findKthLargest(self, nums: List[int], k: int) -> int: # 小顶堆,堆顶是最小元素 pq = [] for e in nums: # 每个元素都要过一遍二叉堆 heapq.heappush(pq, e) # 堆中元素多于 k 个时,删除堆顶元素 if len(pq) > k: heapq.heappop(pq) # pq 中剩下的是 nums 中 k 个最大元素, # 堆顶是最小的那个,即第 k 个最大元素 return pq[0] ``` https://leetcode.cn/problems/xx4gT2 的多语言解法👆 https://leetcode.cn/problems/yong-liang-ge-zhan-shi-xian-dui-lie-lcof 的多语言解法👇 ```cpp // by chatGPT (cpp) class MyQueue { private: stack s1, s2; public: MyQueue() { s1 = stack(); s2 = stack(); } /** * 添加元素到队尾 */ void push(int x) { s1.push(x); } /** * 删除队头的元素并返回 */ int pop() { // 先调用 peek 保证 s2 非空 peek(); int val = s2.top(); s2.pop(); return val; } /** * 返回队头元素 */ int peek() { if (s2.empty()) // 把 s1 元素压入 s2 while (!s1.empty()) { int val = s1.top(); s1.pop(); s2.push(val); } return s2.top(); } /** * 判断队列是否为空 */ bool empty() { return s1.empty() && s2.empty(); } }; ``` ```go // by chatGPT (go) type MyQueue struct { s1, s2 *stack.Stack } func Constructor() MyQueue { return MyQueue{ s1: stack.New(), s2: stack.New(), } } /** * 添加元素到队尾 */ func (this *MyQueue) Push(x int) { this.s1.Push(x) } /** * 删除队头的元素并返回 */ func (this *MyQueue) Pop() int { // 先调用 Peek 保证 s2 非空 this.Peek() return this.s2.Pop().(int) } /** * 返回队头元素 */ func (this *MyQueue) Peek() int { if this.s2.Empty() { // 把 s1 元素压入 s2 for !this.s1.Empty() { this.s2.Push(this.s1.Pop()) } } return this.s2.Peek().(int) } /** * 判断队列是否为空 */ func (this *MyQueue) Empty() bool { return this.s1.Empty() && this.s2.Empty() } ``` ```java // by labuladong (java) class MyQueue { private Stack s1, s2; public MyQueue() { s1 = new Stack<>(); s2 = new Stack<>(); } /** * 添加元素到队尾 */ public void push(int x) { s1.push(x); } /** * 删除队头的元素并返回 */ public int pop() { // 先调用 peek 保证 s2 非空 peek(); return s2.pop(); } /** * 返回队头元素 */ public int peek() { if (s2.isEmpty()) // 把 s1 元素压入 s2 while (!s1.isEmpty()) s2.push(s1.pop()); return s2.peek(); } /** * 判断队列是否为空 */ public boolean empty() { return s1.isEmpty() && s2.isEmpty(); } } ``` ```javascript // by chatGPT (javascript) /** * Initialize your data structure here. */ var MyQueue = function() { this.s1 = []; this.s2 = []; }; /** * Push element x to the back of queue. * @param {number} x * @return {void} */ MyQueue.prototype.push = function(x) { this.s1.push(x); }; /** * Removes the element from in front of queue and returns that element. * @return {number} */ MyQueue.prototype.pop = function() { // 先调用 peek 保证 s2 非空 this.peek(); return this.s2.pop(); }; /** * Get the front element. * @return {number} */ MyQueue.prototype.peek = function() { if (this.s2.length === 0) { // 把 s1 元素压入 s2 while (this.s1.length !== 0) { this.s2.push(this.s1.pop()); } } return this.s2[this.s2.length - 1]; }; /** * Returns whether the queue is empty. * @return {boolean} */ MyQueue.prototype.empty = function() { return this.s1.length === 0 && this.s2.length === 0; }; ``` ```python # by chatGPT (python) class CQueue: def __init__(self): """ 初始化一个队列,使用两个堆栈 s1 和 s2 """ self.s1 = [] self.s2 = [] def appendTail(self, value): """ 添加元素到队尾 """ self.s1.append(value) def deleteHead(self): """ 删除队头的元素并返回 """ # 先调用 peek 保证 s2 非空 if self.peek() is None: return -1 else: return self.s2.pop() def peek(self): """ 返回队头元素 """ if not self.s2: # 把 s1 元素压入 s2 while self.s1: self.s2.append(self.s1.pop()) return self.s2[-1] if self.s2 else None def empty(self): """ 判断队列是否为空 """ return not self.s1 and not self.s2 ``` https://leetcode.cn/problems/yong-liang-ge-zhan-shi-xian-dui-lie-lcof 的多语言解法👆 https://leetcode.cn/problems/zai-pai-xu-shu-zu-zhong-cha-zhao-shu-zi-lcof 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int search(vector& nums, int target) { int left_index = left_bound(nums, target); if (left_index == -1) { return 0; } int right_index = right_bound(nums, target); // 根据左右边界即可推导出元素出现的次数 return right_index - left_index + 1; } int left_bound(vector& nums, int target) { int left = 0, right = nums.size() - 1; // 搜索区间为 [left, right] while (left <= right) { int mid = left + (right - left) / 2; if (nums[mid] < target) { // 搜索区间变为 [mid+1, right] left = mid + 1; } else if (nums[mid] > target) { // 搜索区间变为 [left, mid-1] right = mid - 1; } else if (nums[mid] == target) { // 收缩右侧边界 right = mid - 1; } } // 检查出界情况 if (left >= nums.size() || nums[left] != target) { /** ![](../pictures/二分查找/2.jpg) */ return -1; } return left; } int right_bound(vector& nums, int target) { int left = 0, right = nums.size() - 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; } } // 这里改为检查 right 越界的情况,见下图 if (right < 0 || nums[right] != target) { /** ![](../pictures/二分查找/4.jpg) */ return -1; } return right; } }; ``` ```go // by chatGPT (go) // 二分查找 // 在排序数组中查找元素的第一个和最后一个位置 func search(nums []int, target int) int { // 元素第一次出现的位置 leftIndex := leftBound(nums, target) if leftIndex == -1 { return 0 } // 元素最后一次出现的位置 rightIndex := rightBound(nums, target) // 根据左右边界即可推导出元素出现的次数 return rightIndex - leftIndex + 1 } // 二分查找,查找元素第一次出现的位置 func leftBound(nums []int, target int) int { left, right := 0, len(nums)-1 // 搜索区间为 [left, right] for left <= right { mid := left + (right-left)/2 if nums[mid] < target { // 搜索区间变为 [mid+1, right] left = mid + 1 } else if nums[mid] > target { // 搜索区间变为 [left, mid-1] right = mid - 1 } else if nums[mid] == target { // 收缩右侧边界 right = mid - 1 } } // 检查出界情况 if left >= len(nums) || nums[left] != target { // 该元素在数组中不存在的情况 return -1 } return left } // 二分查找,查找元素最后一次出现的位置 func rightBound(nums []int, target int) int { left, right := 0, len(nums)-1 // 搜索区间为 [left, right] for left <= right { mid := left + (right-left)/2 if nums[mid] < target { // 搜索区间变为 [mid+1, right] left = mid + 1 } else if nums[mid] > target { // 搜索区间变为 [left, mid-1] right = mid - 1 } else if nums[mid] == target { // 收缩左侧边界 left = mid + 1 } } // 检查出界情况 if right < 0 || nums[right] != target { // 该元素在数组中不存在的情况 return -1 } return right } ``` ```java // by labuladong (java) class Solution { public int search(int[] nums, int target) { int left_index = left_bound(nums, target); if (left_index == -1) { return 0; } int right_index = right_bound(nums, target); // 根据左右边界即可推导出元素出现的次数 return right_index - left_index + 1; } int left_bound(int[] nums, int target) { int left = 0, right = nums.length - 1; // 搜索区间为 [left, right] while (left <= right) { int mid = left + (right - left) / 2; if (nums[mid] < target) { // 搜索区间变为 [mid+1, right] left = mid + 1; } else if (nums[mid] > target) { // 搜索区间变为 [left, mid-1] right = mid - 1; } else if (nums[mid] == target) { // 收缩右侧边界 right = mid - 1; } } // 检查出界情况 if (left >= nums.length || nums[left] != target) { /** ![](../pictures/二分查找/2.jpg) */ return -1; } return left; } 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; } } // 这里改为检查 right 越界的情况,见下图 if (right < 0 || nums[right] != target) { /** ![](../pictures/二分查找/4.jpg) */ return -1; } return right; } } ``` ```javascript // by chatGPT (javascript) /** * @param {number[]} nums * @param {number} target * @return {number} */ var search = function(nums, target) { function left_bound(nums, target) { let left = 0 let right = nums.length - 1 // 搜索区间为 [left, right] while (left <= right) { let mid = left + Math.floor((right - left) / 2) if (nums[mid] < target) { // 搜索区间变为 [mid+1, right] left = mid + 1 } else if (nums[mid] > target) { // 搜索区间变为 [left, mid-1] right = mid - 1 } else if (nums[mid] === target) { // 收缩右侧边界 right = mid - 1 } } // 检查出界情况 if (left >= nums.length || nums[left] !== target) { /** ![](../pictures/二分查找/2.jpg) */ return -1; } return left; } function right_bound(nums, target) { let left = 0 let right = nums.length - 1 while (left <= right) { let mid = left + Math.floor((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 } } // 这里改为检查 right 越界的情况,见下图 if (right < 0 || nums[right] !== target) { /** ![](../pictures/二分查找/4.jpg) */ return -1; } return right; } let left_index = left_bound(nums, target); if (left_index === -1) { return 0; } let right_index = right_bound(nums, target); // 根据左右边界即可推导出元素出现的次数 return right_index - left_index + 1; }; ``` ```python # by chatGPT (python) class Solution: def search(self, nums: List[int], target: int) -> int: left_index = self.left_bound(nums, target) if left_index == -1: return 0 right_index = self.right_bound(nums, target) # 根据左右边界即可推导出元素出现的次数 return right_index - left_index + 1 def left_bound(self, nums: List[int], target: int) -> int: left, right = 0, len(nums) - 1 # 搜索区间为 [left, right] while left <= right: mid = left + (right - left) // 2 if nums[mid] < target: # 搜索区间变为 [mid+1, right] left = mid + 1 elif nums[mid] > target: # 搜索区间变为 [left, mid-1] right = mid - 1 elif nums[mid] == target: # 收缩右侧边界 right = mid - 1 # 检查出界情况 if left >= len(nums) or nums[left] != target: """ ![](../pictures/二分查找/2.jpg) """ return -1 return left def right_bound(self, nums: List[int], target: int) -> int: left, right = 0, len(nums) - 1 while left <= right: mid = left + (right - left) // 2 if nums[mid] < target: left = mid + 1 elif nums[mid] > target: right = mid - 1 elif nums[mid] == target: # 这里改成收缩左侧边界即可 left = mid + 1 # 这里改为检查 right 越界的情况,见下图 if right < 0 or nums[right] != target: """ ![](../pictures/二分查找/4.jpg) """ return -1 return right ``` https://leetcode.cn/problems/zai-pai-xu-shu-zu-zhong-cha-zhao-shu-zi-lcof 的多语言解法👆 https://leetcode.cn/problems/zheng-ze-biao-da-shi-pi-pei-lcof 的多语言解法👇 ```cpp // by labuladong (cpp) class Solution { public: // 备忘录 vector> memo; bool isMatch(string s, string p) { int m = s.size(), n = p.size(); memo = vector>(m, vector(n, -1)); // 指针 i,j 从索引 0 开始移动 return dp(s, 0, p, 0); } /* 计算 p[j..] 是否匹配 s[i..] */ bool dp(string& s, int i, string& p, int j) { int m = s.size(), n = p.size(); // base case if (j == n) { return i == m; } if (i == m) { if ((n - j) % 2 == 1) { return false; } for (; j + 1 < n; j += 2) { if (p[j + 1] != '*') { return false; } } return true; } // 查备忘录,防止重复计算 if (memo[i][j] != -1) { return memo[i][j]; } bool res = false; if (s[i] == p[j] || p[j] == '.') { if (j < n - 1 && p[j + 1] == '*') { res = dp(s, i, p, j + 2) || dp(s, i + 1, p, j); } else { res = dp(s, i + 1, p, j + 1); } } else { if (j < n - 1 && p[j + 1] == '*') { res = dp(s, i, p, j + 2); } else { res = false; } } // 将当前结果记入备忘录 memo[i][j] = res; return res; } }; ``` ```go // by chatGPT (go) func isMatch(s string, p string) bool { // 备忘录 memo := make([][]int, len(s)) for i := range memo { memo[i] = make([]int, len(p)) for j := range memo[i] { memo[i][j] = -1 } } // 指针 i,j 从索引 0 开始移动 return dp(s, 0, p, 0, memo) } /* 计算 p[j..] 是否匹配 s[i..] */ func dp(s string, i int, p string, j int, memo [][]int) bool { m, n := len(s), len(p) // base case if j == n { return i == m } if i == m { if (n-j)%2 == 1 { return false } for ; j+1 < n; j += 2 { if p[j+1] != '*' { return false } } return true } // 查备忘录,防止重复计算 if memo[i][j] != -1 { return memo[i][j] == 1 } var res bool if s[i] == p[j] || p[j] == '.' { if j < n-1 && p[j+1] == '*' { res = dp(s, i, p, j+2, memo) || dp(s, i+1, p, j, memo) } else { res = dp(s, i+1, p, j+1, memo) } } else { if j < n-1 && p[j+1] == '*' { res = dp(s, i, p, j+2, memo) } } // 将当前结果记入备忘录 if res { memo[i][j] = 1 } else { memo[i][j] = 0 } return res } ``` ```java // by chatGPT (java) class Solution { // 备忘录 private int[][] memo; public boolean isMatch(String s, String p) { int m = s.length(), n = p.length(); memo = new int[m][n]; for (int i = 0; i < m; i++) { Arrays.fill(memo[i], -1); } // 指针 i,j 从索引 0 开始移动 return dp(s, 0, p, 0); } /* 计算 p[j..] 是否匹配 s[i..] */ private boolean dp(String s, int i, String p, int j) { int m = s.length(), n = p.length(); // base case if (j == n) { return i == m; } if (i == m) { if ((n - j) % 2 == 1) { return false; } for (; j + 1 < n; j += 2) { if (p.charAt(j + 1) != '*') { return false; } } return true; } // 查备忘录,防止重复计算 if (memo[i][j] != -1) { return memo[i][j] == 1; } boolean res = false; if (s.charAt(i) == p.charAt(j) || p.charAt(j) == '.') { if (j < n - 1 && p.charAt(j + 1) == '*') { res = dp(s, i, p, j + 2) || dp(s, i + 1, p, j); } else { res = dp(s, i + 1, p, j + 1); } } else { if (j < n - 1 && p.charAt(j + 1) == '*') { res = dp(s, i, p, j + 2); } else { res = false; } } // 将当前结果记入备忘录 memo[i][j] = res ? 1 : 0; return res; } } ``` ```javascript // by chatGPT (javascript) var isMatch = function(s, p) { var m = s.length, n = p.length; var memo = new Array(m + 1); // 先构造一个数组 for (var i = 0; i <= m; i++) { memo[i] = new Array(n + 1).fill(false); } memo[0][0] = true; // 两个字符串为空串时,互相匹配 for (i = 0; i <= m; i++) { for (var j = 1; j <= n; j++) { if (p.charAt(j - 1) === '*') { // * 号能匹配 0 次或更多次 memo[i][j] = memo[i][j - 2] // 匹配 0 次 || (i > 0 && (s.charAt(i - 1) === p.charAt(j - 2) || p.charAt(j - 2) === '.') && memo[i - 1][j]); } else { if (i > 0 && (s.charAt(i - 1) === p.charAt(j - 1) || p.charAt(j - 1) === '.')) { memo[i][j] = memo[i - 1][j - 1]; } } } } return memo[m][n]; // 返回从两个字符串的尾部开始,是否相互匹配 }; ``` ```python # by chatGPT (python) class Solution: def isMatch(self, s: str, p: str) -> bool: # 备忘录 memo = [] # 创建大小为m*n的备忘录,初始值为-1 m = len(s) n = len(p) for i in range(m): memo.append([-1] * n) # 索引指针 i,j 从 0 开始移动 return self.dp(s, 0, p, 0, memo) # 计算 p[j..] 是否匹配 s[i..] def dp(self, s, i, p, j, memo): m = len(s) n = len(p) # 如果 j 达到结尾,判断 i 是否也到结尾 if j == n: return i == m # 如果 i 达到结尾,判断 j 接下来的字符能否匹配空串 if i == m: if (n - j) % 2 == 1: return False for k in range(j + 1, n, 2): if p[k] != '*': return False return True # 查备忘录,防止重复计算 if memo[i][j] != -1: return memo[i][j] res = False if s[i] == p[j] or p[j] == '.': if j < n - 1 and p[j + 1] == '*': res = self.dp(s, i, p, j + 2, memo) or self.dp(s, i + 1, p, j, memo) else: res = self.dp(s, i + 1, p, j + 1, memo) else: if j < n - 1 and p[j + 1] == '*': res = self.dp(s, i, p, j + 2, memo) else: res = False # 记入当前结果到备忘录 memo[i][j] = res return res ``` https://leetcode.cn/problems/zheng-ze-biao-da-shi-pi-pei-lcof 的多语言解法👆 https://leetcode.cn/problems/zhong-jian-er-cha-shu-lcof 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: // 存储 inorder 中值到索引的映射 unordered_map valToIndex; TreeNode* buildTree(vector& preorder, vector& inorder) { for (int i = 0; i < inorder.size(); i++) { valToIndex[inorder[i]] = i; } return build(preorder, 0, preorder.size() - 1, inorder, 0, inorder.size() - 1); } /* 定义:前序遍历数组为 preorder[preStart..preEnd], 中序遍历数组为 inorder[inStart..inEnd], 构造这个二叉树并返回该二叉树的根节点 */ TreeNode* build(vector& preorder, int preStart, int preEnd, vector& inorder, int inStart, int inEnd) { if (preStart > preEnd) { return nullptr; } // root 节点对应的值就是前序遍历数组的第一个元素 int rootVal = preorder[preStart]; // rootVal 在中序遍历数组中的索引 int index = valToIndex[rootVal]; int leftSize = index - inStart; // 先构造出当前根节点 TreeNode* root = new TreeNode(rootVal); /** ![](../pictures/二叉树系列2/4.jpeg) */ // 递归构造左右子树 root->left = build(preorder, preStart + 1, preStart + leftSize, inorder, inStart, index - 1); root->right = build(preorder, preStart + leftSize + 1, preEnd, inorder, index + 1, inEnd); return root; } }; ``` ```go // by chatGPT (go) func buildTree(preorder []int, inorder []int) *TreeNode { indexMap := make(map[int]int) for i, val := range inorder { indexMap[val] = i } return build(preorder, inorder, 0, len(preorder)-1, 0, len(inorder)-1, indexMap) } func build(preorder []int, inorder []int, preStart int, preEnd int, inStart int, inEnd int, indexMap map[int]int) *TreeNode { if preStart > preEnd { return nil } // root 节点对应的值就是前序遍历数组的第一个元素 rootVal := preorder[preStart] // rootVal 在中序遍历数组中的索引 index := indexMap[rootVal] leftSize := index - inStart // 先构造出当前根节点 root := &TreeNode{Val: rootVal} // 递归构造左右子树 root.Left = build(preorder, inorder, preStart+1, preStart+leftSize, inStart, index-1, indexMap) root.Right = build(preorder, inorder, preStart+leftSize+1, preEnd, index+1, inEnd, indexMap) return root } ``` ```java // by labuladong (java) class Solution { // 存储 inorder 中值到索引的映射 HashMap valToIndex = new HashMap<>(); public TreeNode buildTree(int[] preorder, int[] inorder) { for (int i = 0; i < inorder.length; i++) { valToIndex.put(inorder[i], i); } return build(preorder, 0, preorder.length - 1, inorder, 0, inorder.length - 1); } /* 定义:前序遍历数组为 preorder[preStart..preEnd], 中序遍历数组为 inorder[inStart..inEnd], 构造这个二叉树并返回该二叉树的根节点 */ TreeNode build(int[] preorder, int preStart, int preEnd, int[] inorder, int inStart, int inEnd) { if (preStart > preEnd) { return null; } // root 节点对应的值就是前序遍历数组的第一个元素 int rootVal = preorder[preStart]; // rootVal 在中序遍历数组中的索引 int index = valToIndex.get(rootVal); int leftSize = index - inStart; // 先构造出当前根节点 TreeNode root = new TreeNode(rootVal); /** ![](../pictures/二叉树系列2/4.jpeg) */ // 递归构造左右子树 root.left = build(preorder, preStart + 1, preStart + leftSize, inorder, inStart, index - 1); root.right = build(preorder, preStart + leftSize + 1, preEnd, inorder, index + 1, inEnd); return root; } } ``` ```javascript // by chatGPT (javascript) var buildTree = function(preorder, inorder) { // 存储 inorder 中值到索引的映射 const valToIndex = new Map(); for (let i = 0; i < inorder.length; i++) { valToIndex.set(inorder[i], i); } return build(preorder, 0, preorder.length - 1, inorder, 0, inorder.length - 1); }; /* 定义:前序遍历数组为 preorder[preStart..preEnd], 中序遍历数组为 inorder[inStart..inEnd], 构造这个二叉树并返回该二叉树的根节点 */ function build(preorder, preStart, preEnd, inorder, inStart, inEnd) { if (preStart > preEnd) { return null; } // root 节点对应的值就是前序遍历数组的第一个元素 const rootVal = preorder[preStart]; // rootVal 在中序遍历数组中的索引 const index = valToIndex.get(rootVal); const leftSize = index - inStart; // 先构造出当前根节点 const root = new TreeNode(rootVal); /** ![](../pictures/二叉树系列2/4.jpeg) */ // 递归构造左右子树 root.left = build(preorder, preStart + 1, preStart + leftSize, inorder, inStart, index - 1); root.right = build(preorder, preStart + leftSize + 1, preEnd, inorder, index + 1, inEnd); return root; } ``` ```python # by chatGPT (python) class Solution: def __init__(self): # 存储 inorder 中值到索引的映射 self.valToIndex = {} def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode: for i in range(len(inorder)): self.valToIndex[inorder[i]] = i return self.build(preorder, 0, len(preorder) - 1, inorder, 0, len(inorder) - 1) ''' 定义:前序遍历数组为 preorder[preStart..preEnd], 中序遍历数组为 inorder[inStart..inEnd], 构造这个二叉树并返回该二叉树的根节点 ''' def build(self, preorder: List[int], preStart: int, preEnd: int, inorder: List[int], inStart: int, inEnd: int) -> TreeNode: if preStart > preEnd: return None # root 节点对应的值就是前序遍历数组的第一个元素 rootVal = preorder[preStart] # rootVal 在中序遍历数组中的索引 index = self.valToIndex[rootVal] leftSize = index - inStart # 先构造出当前根节点 root = TreeNode(rootVal) """ ![](../pictures/二叉树系列2/4.jpeg) """ # 递归构造左右子树 root.left = self.build(preorder, preStart + 1, preStart + leftSize, inorder, inStart, index - 1) root.right = self.build(preorder, preStart + leftSize + 1, preEnd, inorder, index + 1, inEnd) return root ``` https://leetcode.cn/problems/zhong-jian-er-cha-shu-lcof 的多语言解法👆 https://leetcode.cn/problems/zi-fu-chuan-de-pai-lie-lcof 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: vector permutation(string s) { permuteUnique(s); // 根据 res 容量构造返回数组 vector arr = vector(res.size()); for (int i = 0; i < res.size(); i++) { arr[i] = res[i]; } return arr; } // 保存所有的排列结果 vector res; // 排列的过程中记录一条排列路径 string track; // 标记在这个排列路径中哪些元素已经被访问过 vector used; void permuteUnique(string nums) { // 先对排列的元素进行排序,相同元素正着排列 sort(nums.begin(), nums.end()); // 初始化 used 数组为 false used.resize(nums.size(), false); backtrack(nums); } void backtrack(string nums) { // 如果生成的路径长度等于元素长度,这就是一条完整的排列路径,添加到 res 中 if (track.length() == nums.length()) { res.push_back(track); return; } // 枚举排列路径中所有未使用的元素 for (int i = 0; i < nums.length(); i++) { // 如果这个元素已经被访问过,直接跳过 if (used[i]) { continue; } // 使用这个元素时,应对通过访问顺序来避免其中相同元素顺序不同的重复排列 if (i > 0 && nums[i] == nums[i - 1] && !used[i - 1]) { continue; } // 将这个元素添加到排列路径中 track.push_back(nums[i]); used[i] = true; backtrack(nums); track.pop_back(); used[i] = false; } } }; ``` ```go // by chatGPT (go) import "sort" func permutation(s string) []string { res := []string{} track := []byte{} used := make([]bool, len(s)) nums := []byte(s) sort.Slice(nums, func(i, j int) bool { return nums[i] < nums[j] }) backtrack(nums, &res, track, used) return res } func backtrack(nums []byte, res *[]string, track []byte, used []bool) { if len(track) == len(nums) { *res = append(*res, string(track)) return } for i := 0; i < len(nums); i++ { if used[i] { continue } // 新添加的剪枝逻辑,固定相同的元素在排列中的相对位置 if i > 0 && nums[i] == nums[i-1] && !used[i-1] { continue } track = append(track, nums[i]) used[i] = true backtrack(nums, res, track, used) track = track[:len(track)-1] used[i] = false } } ``` ```java // by labuladong (java) class Solution { public String[] permutation(String s) { permuteUnique(s.toCharArray()); String[] arr = new String[res.size()]; for (int i = 0; i < res.size(); i++) { arr[i] = res.get(i); } return arr; } List res = new ArrayList<>(); StringBuilder track = new StringBuilder(); boolean[] used; public List permuteUnique(char[] nums) { // 先排序,让相同的元素靠在一起 Arrays.sort(nums); used = new boolean[nums.length]; backtrack(nums); return res; } void backtrack(char[] nums) { if (track.length() == nums.length) { res.add(track.toString()); return; } for (int i = 0; i < nums.length; i++) { if (used[i]) { continue; } // 新添加的剪枝逻辑,固定相同的元素在排列中的相对位置 if (i > 0 && nums[i] == nums[i - 1] && !used[i - 1]) { continue; } track.append(nums[i]); used[i] = true; backtrack(nums); track.deleteCharAt(track.length() - 1); used[i] = false; } } } ``` ```javascript // by chatGPT (javascript) /** * @param {string} s * @return {string[]} */ var permutation = function(s) { let res = []; let track = []; let used; var permuteUnique = function(nums) { // 先排序,让相同的元素靠在一起 nums.sort(); used = new Array(nums.length).fill(false); backtrack(nums); return res; }; var backtrack = function(nums) { if (track.length === nums.length) { res.push(track.join("")); return; } for (let i = 0; i < nums.length; i++) { if (used[i]) { continue; } // 新添加的剪枝逻辑,固定相同的元素在排列中的相对位置 if (i > 0 && nums[i] === nums[i - 1] && !used[i - 1]) { continue; } track.push(nums[i]); used[i] = true; backtrack(nums); track.pop(); used[i] = false; } }; permuteUnique(s.split("")); let arr = new Array(res.size()); for (let i = 0; i < res.size(); i++) { arr[i] = res.get(i); } return arr; }; ``` ```python # by chatGPT (python) class Solution: def permutation(self, s: str) -> List[str]: self.permuteUnique(list(s)) # 将结果转化为字符串数组返回 arr = [None] * len(self.res) for i in range(len(self.res)): arr[i] = self.res[i] return arr # 存储结果的列表 res = [] # 存储每一次递归后得到的排列方案 track = "" # 记录每个字符是否被使用过 used = [] def permuteUnique(self, nums: List[str]) -> None: """ :type nums: List[int] :rtype: List[List[int]] """ # 先排序,让相同的元素靠在一起 nums.sort() # 初始化标记列表 self.used = [False] * len(nums) self.backtrack(nums) def backtrack(self, nums: List[str]) -> None: if len(self.track) == len(nums): self.res.append(self.track) return for i in range(len(nums)): if self.used[i]: continue # 新添加的剪枝逻辑,固定相同的元素在排列中的相对位置 if i > 0 and nums[i] == nums[i - 1] and not self.used[i - 1]: continue self.track += nums[i] self.used[i] = True self.backtrack(nums) self.track = self.track[:-1] self.used[i] = False ``` https://leetcode.cn/problems/zi-fu-chuan-de-pai-lie-lcof 的多语言解法👆 https://leetcode.cn/problems/zlDJc7 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: int openLock(vector& deadends, string target) { // 记录需要跳过的死亡密码 unordered_set deads(deadends.begin(), deadends.end()); // 记录已经穷举过的密码,防止走回头路 unordered_set visited; queue q; // 从起点开始启动广度优先搜索 int step = 0; q.push("0000"); visited.emplace("0000"); while (!q.empty()) { int sz = q.size(); /* 将当前队列中的所有节点向周围扩散 */ for (int i = 0; i < sz; i++) { string cur = q.front(); q.pop(); /* 判断是否到达终点 */ if (deads.find(cur) != deads.end()) continue; if (cur == target) return step; /* 将一个节点的未遍历相邻节点加入队列 */ for (int j = 0; j < 4; j++) { string up = plusOne(cur, j); if (visited.find(up) == visited.end()) { q.push(up); visited.emplace(up); } string down = minusOne(cur, j); if (visited.find(down) == visited.end()) { q.push(down); visited.emplace(down); } } } /* 在这里增加步数 */ step++; } // 如果穷举完都没找到目标密码,那就是找不到了 return -1; } // 将 s[j] 向上拨动一次 string plusOne(string s, int j) { if (s[j] == '9') s[j] = '0'; else s[j] += 1; return s; } // 将 s[i] 向下拨动一次 string minusOne(string s, int j) { if (s[j] == '0') s[j] = '9'; else s[j] -= 1; return s; } }; ``` ```go // by chatGPT (go) func openLock(deadends []string, target string) int { // 记录需要跳过的死亡密码 var deads = map[string]bool{} for _, s := range deadends { deads[s] = true } // 记录已经穷举过的密码,防止走回头路 var visited = map[string]bool{} q := []string{"0000"} // 从起点开始启动广度优先搜索 step := 0 visited["0000"] = true for len(q) > 0 { sz := len(q) /* 将当前队列中的所有节点向周围扩散 */ for i := 0; i < sz; i++ { cur := q[0] q = q[1:] /* 判断是否到达终点 */ if deads[cur] { continue } if cur == target { return step } /* 将一个节点的未遍历相邻节点加入队列 */ for j := 0; j < 4; j++ { up := plusOne(cur, j) if !visited[up] { q = append(q, up) visited[up] = true } down := minusOne(cur, j) if !visited[down] { q = append(q, down) visited[down] = true } } } /* 在这里增加步数 */ step++ } // 如果穷举完都没找到目标密码,那就是找不到了 return -1 } // 将 s[j] 向上拨动一次 func plusOne(s string, j int) string { ch := []byte(s) if ch[j] == '9' { ch[j] = '0' } else { ch[j] += 1 } return string(ch) } // 将 s[i] 向下拨动一次 func minusOne(s string, j int) string { ch := []byte(s) if ch[j] == '0' { ch[j] = '9' } else { ch[j] -= 1 } return string(ch) } ``` ```java // by labuladong (java) class Solution { public int openLock(String[] deadends, String target) { // 记录需要跳过的死亡密码 Set deads = new HashSet<>(); for (String s : deadends) deads.add(s); // 记录已经穷举过的密码,防止走回头路 Set visited = new HashSet<>(); Queue q = new LinkedList<>(); // 从起点开始启动广度优先搜索 int step = 0; q.offer("0000"); visited.add("0000"); while (!q.isEmpty()) { int sz = q.size(); /* 将当前队列中的所有节点向周围扩散 */ for (int i = 0; i < sz; i++) { String cur = q.poll(); /* 判断是否到达终点 */ if (deads.contains(cur)) continue; if (cur.equals(target)) return step; /* 将一个节点的未遍历相邻节点加入队列 */ for (int j = 0; j < 4; j++) { String up = plusOne(cur, j); if (!visited.contains(up)) { q.offer(up); visited.add(up); } String down = minusOne(cur, j); if (!visited.contains(down)) { q.offer(down); visited.add(down); } } } /* 在这里增加步数 */ step++; } // 如果穷举完都没找到目标密码,那就是找不到了 return -1; } // 将 s[j] 向上拨动一次 String plusOne(String s, int j) { char[] ch = s.toCharArray(); if (ch[j] == '9') ch[j] = '0'; else ch[j] += 1; return new String(ch); } // 将 s[i] 向下拨动一次 String minusOne(String s, int j) { char[] ch = s.toCharArray(); if (ch[j] == '0') ch[j] = '9'; else ch[j] -= 1; return new String(ch); } } ``` ```javascript // by chatGPT (javascript) var openLock = function (deadends, target) { // 记录需要跳过的死亡密码 const deads = new Set(deadends); // 记录已经穷举过的密码,防止走回头路 const visited = new Set(); const q = []; let step = 0; // 从起点开始启动广度优先搜索 q.push("0000"); visited.add("0000"); while (q.length > 0) { let sz = q.length; /* 将当前队列中的所有节点向周围扩散 */ for (let i = 0; i < sz; i++) { let cur = q.shift(); /* 判断是否到达终点 */ if (deads.has(cur)) { continue; } if (cur === target) { return step; } /* 将一个节点的未遍历相邻节点加入队列 */ for (let j = 0; j < 4; j++) { let up = plusOne(cur, j); if (!visited.has(up)) { q.push(up); visited.add(up); } let down = minusOne(cur, j); if (!visited.has(down)) { q.push(down); visited.add(down); } } } /* 在这里增加步数 */ step++; } // 如果穷举完都没找到目标密码,那就是找不到了 return -1; // 将 s[j] 向上拨动一次 function plusOne(s, j) { let ch = s.split(''); if (ch[j] === '9') { ch[j] = '0'; } else { ch[j] = (+ch[j] + 1).toString(); } return ch.join(''); } // 将 s[i] 向下拨动一次 function minusOne(s, j) { let ch = s.split(''); if (ch[j] === '0') { ch[j] = '9'; } else { ch[j] = (+ch[j] - 1).toString(); } return ch.join(''); } }; ``` ```python # by chatGPT (python) class Solution: def openLock(self, deadends: List[str], target: str) -> int: #记录需要跳过的死亡密码 deads = set(deadends) #记录已经穷举过的密码,防止走回头路 visited = set() q = deque() #从起点开始启动广度优先搜索 step = 0 q.append("0000") visited.add("0000") while q: sz = len(q) #将当前队列中的所有节点向周围扩散 for i in range(sz): cur = q.popleft() #判断是否到达终点 if cur in deads: continue if cur == target: return step # 将一个节点的未遍历相邻节点加入队列 for j in range(4): up = self.plusOne(cur, j) if up not in visited: q.append(up) visited.add(up) down = self.minusOne(cur, j) if down not in visited: q.append(down) visited.add(down) #在这里增加步数 step += 1 #如果穷举完都没找到目标密码,那就是找不到了 return -1 #将 s[j] 向上拨动一次 def plusOne(self, s: str, j: int) -> str: ch = list(s) if ch[j] == '9': ch[j] = '0' else: ch[j] = chr(ord(ch[j]) + 1) return "".join(ch) #将 s[i] 向下拨动一次 def minusOne(self, s: str, j: int) -> str: ch = list(s) if ch[j] == '0': ch[j] = '9' else: ch[j] = chr(ord(ch[j]) - 1) return "".join(ch) ``` https://leetcode.cn/problems/zlDJc7 的多语言解法👆 https://leetcode.cn/problems/zui-chang-bu-han-zhong-fu-zi-fu-de-zi-zi-fu-chuan-lcof 的多语言解法👇 ```cpp // by labuladong (cpp) class Solution { public: int lengthOfLongestSubstring(string s) { unordered_map window; int left = 0, right = 0; int res = 0; // 记录结果 while (right < s.size()) { char c = s[right]; right++; // 进行窗口内数据的一系列更新 window[c]++; // 判断左侧窗口是否要收缩 while (window[c] > 1) { char d = s[left]; left++; // 进行窗口内数据的一系列更新 window[d]--; } // 在这里更新答案 res = max(res, right - left); } return res; } }; ``` ```go // by chatGPT (go) /** * @param s: a string * @return: the length of the longest substring without repeating characters */ func lengthOfLongestSubstring(s string) int { window := make(map[byte]int) left, right := 0, 0 res := 0 // 记录结果 for right < len(s) { c := s[right] right++ // 进行窗口内数据的一系列更新 window[c]++ // 判断左侧窗口是否要收缩 for window[c] > 1 { d := s[left] left++ // 进行窗口内数据的一系列更新 window[d]-- } // 在这里更新答案 res = max(res, right - left) } return res } func max(x, y int) int { if x > y { return x } return y } ``` ```java // by chatGPT (java) 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; } } ``` ```javascript // by chatGPT (javascript) var lengthOfLongestSubstring = function(s) { // 哈希表记录窗口中字符出现的次数 var window = new Map(); // 左指针、右指针 var left = 0, right = 0; // 最长子串长度 var res = 0; while (right < s.length) { // 移动右指针,扩大窗口 var c = s.charAt(right); right++; // 更新哈希表 if(window.has(c)){ window.set(c, window.get(c) + 1); } else { window.set(c, 1); } // 如果当前字符在窗口中已经出现过,则考虑收缩窗口 while (window.get(c) > 1) { // 移动左指针,收缩窗口 var d = s.charAt(left); left++; // 更新哈希表 window.set(d, window.get(d) - 1); } // 更新最长子串长度 res = Math.max(res, right - left); } return res; }; ``` ```python # by chatGPT (python) class Solution: def lengthOfLongestSubstring(self, s: str) -> int: window = {} # 用于记录窗口中各字符出现的次数 left = right = res = 0 # left为窗口左边界,right为窗口右边界,[left, right)为当前窗口,res用来记录最长无重复子串的长度 while right < len(s): c = s[right] right += 1 window[c] = window.get(c, 0) + 1 # 将字符c的出现次数加1 while window[c] > 1: # 当右侧窗口中的字符c的个数大于1时,说明当前窗口中不符合条件,需要进行调整 d = s[left] left += 1 window[d] -= 1 # 将窗口左侧的字符d移出窗口,其在window中的出现次数减1 res = max(res, right - left) # 更新最长无重复子串的长度 return res ``` https://leetcode.cn/problems/zui-chang-bu-han-zhong-fu-zi-fu-de-zi-zi-fu-chuan-lcof 的多语言解法👆 https://leetcode.cn/problems/zui-xiao-de-kge-shu-lcof 的多语言解法👇 ```cpp // by chatGPT (cpp) // 二叉堆的解法思路 class Solution1 { public: vector getLeastNumbers(vector& arr, int k) { // 大顶堆,堆顶是最大元素 priority_queue pq; for (int e : arr) { // 每个元素都要过一遍二叉堆 pq.push(e); // 堆中元素多于 k 个时,删除堆顶元素 if (pq.size() > k) { pq.pop(); } } // pq 中剩下的是 arr 中最小的 k 个元素 vector res(k); int i = 0; while (!pq.empty()) { res[i] = pq.top(); pq.pop(); i++; } return res; } }; // 快速选择的解法思路 class Solution { public: vector getLeastNumbers(vector& arr, int k) { vector res(k); // 注意此题的 k 是元素个数而不是索引,所以和索引 p 做比较时要 - 1 // 首先随机打乱数组 shuffle(arr); int lo = 0, hi = arr.size() - 1; // 现在开始寻找第 k 大的元素 while (lo <= hi) { // 在 arr[lo..hi] 中选一个分界点 int p = partition(arr, lo, hi); if (p < k - 1) { // 第 k 大的元素在 arr[p+1..hi] 中 lo = p + 1; } else if (p > k - 1) { // 第 k 大的元素在 arr[lo..p-1] 中 hi = p - 1; } else { // arr[p] 就是第 k 大元素,又因为快速排序的性质, // arr[p] 左边的元素都比 arr[p] 小,所以现在 arr[0..k] 就是我们要找的答案 for (int i = 0; i < k; i++) { res[i] = arr[i]; } return res; } } return res; } // 对 nums[lo..hi] 进行切分 int partition(vector& nums, int lo, int hi) { int pivot = nums[lo]; // 关于区间的边界控制需格外小心,稍有不慎就会出错 // 我这里把 i, j 定义为开区间,同时定义: // [lo, i) <= pivot;(j, hi] > pivot // 之后都要正确维护这个边界区间的定义 int i = lo + 1, j = hi; // 当 i > j 时结束循环,以保证区间 [lo, hi] 都被覆盖 while (i <= j) { while (i < hi && nums[i] <= pivot) { i++; // 此 while 结束时恰好 nums[i] > pivot } while (j > lo && nums[j] > pivot) { j--; // 此 while 结束时恰好 nums[j] <= pivot } if (i >= j) { break; } // 此时 [lo, i) <= pivot && (j, hi] > pivot // 交换 nums[j] 和 nums[i] swap(nums[i], nums[j]); // 此时 [lo, i] <= pivot && [j, hi] > pivot } // 最后将 pivot 放到合适的位置,即 pivot 左边元素较小,右边元素较大 swap(nums[lo], nums[j]); return j; } // 洗牌算法,将输入的数组随机打乱 void shuffle(vector& nums) { srand((unsigned)time(NULL)); int n = nums.size(); for (int i = 0; i < n; i++) { // 生成 [i, n - 1] 的随机数 int r = i + rand() % (n - i); swap(nums[i], nums[r]); } } // 原地交换数组中的两个元素 void swap(int& a, int& b) { int temp = a; a = b; b = temp; } }; ``` ```go // by chatGPT (go) // 二叉堆的解法思路 func getLeastNumbers(arr []int, k int) []int { // 大顶堆,堆顶是最大元素 pq := make(IntHeap, 0) heap.Init(&pq) for _, e := range arr { // 每个元素都要过一遍二叉堆 heap.Push(&pq, e) // 堆中元素多于 k 个时,删除堆顶元素 if pq.Len() > k { heap.Pop(&pq) } } // pq 中剩下的是 arr 中最小的 k 个元素 res := make([]int, k) i := 0 for pq.Len() > 0 { res[i] = heap.Pop(&pq).(int) i++ } return res } type IntHeap []int func (h IntHeap) Len() int { return len(h) } func (h IntHeap) Less(i, j int) bool { return h[i] > h[j] } func (h IntHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] } func (h *IntHeap) Push(x interface{}) { *h = append(*h, x.(int)) } func (h *IntHeap) Pop() interface{} { old := *h n := len(old) x := old[n-1] *h = old[0 : n-1] return x } // 快速选择的解法思路 func getLeastNumbers(arr []int, k int) []int { res := make([]int, k) // 注意此题的 k 是元素个数而不是索引,所以和索引 p 做比较时要 - 1 // 首先随机打乱数组 shuffle(arr) lo, hi := 0, len(arr)-1 // 现在开始寻找第 k 大的元素 for lo <= hi { // 在 arr[lo..hi] 中选一个分界点 p := partition(arr, lo, hi) if p < k-1 { // 第 k 大的元素在 arr[p+1..hi] 中 lo = p + 1 } else if p > k-1 { // 第 k 大的元素在 arr[lo..p-1] 中 hi = p - 1 } else { // arr[p] 就是第 k 大元素,又因为快速排序的性质, // arr[p] 左边的元素都比 arr[p] 小,所以现在 arr[0..k] 就是我们要找的答案 copy(res, arr[:k]) return res } } return res } // 对 nums[lo..hi] 进行切分 func partition(nums []int, lo, hi int) int { pivot := nums[lo] // 关于区间的边界控制需格外小心,稍有不慎就会出错 // 我这里把 i, j 定义为开区间,同时定义: // [lo, i) <= pivot;(j, hi] > pivot // 之后都要正确维护这个边界区间的定义 i, j := lo+1, hi // 当 i > j 时结束循环,以保证区间 [lo, hi] 都被覆盖 for i <= j { for i < hi && nums[i] <= pivot { i++ // 此 while 结束时恰好 nums[i] > pivot } for j > lo && nums[j] > pivot { j-- // 此 while 结束时恰好 nums[j] <= pivot } if i >= j { break } // 此时 [lo, i) <= pivot && (j, hi] > pivot // 交换 nums[j] 和 nums[i] nums[i], nums[j] = nums[j], nums[i] // 此时 [lo, i] <= pivot && [j, hi] > pivot } // 最后将 pivot 放到合适的位置,即 pivot 左边元素较小,右边元素较大 nums[lo], nums[j] = nums[j], nums[lo] return j } // 洗牌算法,将输入的数组随机打乱 func shuffle(nums []int) { rand.Seed(time.Now().UnixNano()) n := len(nums) for i := 0; i < n; i++ { // 生成 [i, n - 1] 的随机数 r := i + rand.Intn(n-i) nums[i], nums[r] = nums[r], nums[i] } } ``` ```java // by labuladong (java) // 二叉堆的解法思路 class Solution1 { public int[] getLeastNumbers(int[] arr, int k) { // 大顶堆,堆顶是最大元素 PriorityQueue pq = new PriorityQueue<>((a, b) -> { return b - a; }); for (int e : arr) { // 每个元素都要过一遍二叉堆 pq.offer(e); // 堆中元素多于 k 个时,删除堆顶元素 if (pq.size() > k) { pq.poll(); } } // pq 中剩下的是 arr 中最小的 k 个元素 int[] res = new int[k]; int i = 0; while (!pq.isEmpty()) { res[i] = pq.poll(); i++; } return res; } } // 快速选择的解法思路 class Solution { public int[] getLeastNumbers(int[] arr, int k) { int[] res = new int[k]; // 注意此题的 k 是元素个数而不是索引,所以和索引 p 做比较时要 - 1 // 首先随机打乱数组 shuffle(arr); int lo = 0, hi = arr.length - 1; // 现在开始寻找第 k 大的元素 while (lo <= hi) { // 在 arr[lo..hi] 中选一个分界点 int p = partition(arr, lo, hi); if (p < k - 1) { // 第 k 大的元素在 arr[p+1..hi] 中 lo = p + 1; } else if (p > k - 1) { // 第 k 大的元素在 arr[lo..p-1] 中 hi = p - 1; } else { // arr[p] 就是第 k 大元素,又因为快速排序的性质, // arr[p] 左边的元素都比 arr[p] 小,所以现在 arr[0..k] 就是我们要找的答案 for (int i = 0; i < k; i++) { res[i] = arr[i]; } return res; } } return res; } // 对 nums[lo..hi] 进行切分 private static int partition(int[] nums, int lo, int hi) { int pivot = nums[lo]; // 关于区间的边界控制需格外小心,稍有不慎就会出错 // 我这里把 i, j 定义为开区间,同时定义: // [lo, i) <= pivot;(j, hi] > pivot // 之后都要正确维护这个边界区间的定义 int i = lo + 1, j = hi; // 当 i > j 时结束循环,以保证区间 [lo, hi] 都被覆盖 while (i <= j) { while (i < hi && nums[i] <= pivot) { i++; // 此 while 结束时恰好 nums[i] > pivot } while (j > lo && nums[j] > pivot) { j--; // 此 while 结束时恰好 nums[j] <= pivot } if (i >= j) { break; } // 此时 [lo, i) <= pivot && (j, hi] > pivot // 交换 nums[j] 和 nums[i] swap(nums, i, j); // 此时 [lo, i] <= pivot && [j, hi] > pivot } // 最后将 pivot 放到合适的位置,即 pivot 左边元素较小,右边元素较大 swap(nums, lo, j); return j; } // 洗牌算法,将输入的数组随机打乱 private static void shuffle(int[] nums) { Random rand = new Random(); int n = nums.length; for (int i = 0 ; i < n; i++) { // 生成 [i, n - 1] 的随机数 int r = i + rand.nextInt(n - i); swap(nums, i, r); } } // 原地交换数组中的两个元素 private static void swap(int[] nums, int i, int j) { int temp = nums[i]; nums[i] = nums[j]; nums[j] = temp; } } ``` ```javascript // by chatGPT (javascript) var getLeastNumbers = function(arr, k) { // 大顶堆,堆顶是最大元素 let pq = new PriorityQueue((a, b) => { return b - a; }); for (let e of arr) { // 每个元素都要过一遍二叉堆 pq.offer(e); // 堆中元素多于 k 个时,删除堆顶元素 if (pq.size() > k) { pq.poll(); } } // pq 中剩下的是 arr 中最小的 k 个元素 let res = new Array(k); let i = 0; while (pq.size() > 0) { res[i] = pq.poll(); i++; } return res; } class PriorityQueue { constructor(compareFn = (a, b) => a - b) { this.compareFn = compareFn; this.heap = []; } // 获取堆的大小 size() { return this.heap.length; } // 获取堆顶元素 peek() { if (this.heap.length === 0) { return null; } return this.heap[0]; } // 删除堆顶元素 poll() { if (this.heap.length === 0) { return null; } const top = this.heap[0]; const last = this.heap.pop(); if (this.heap.length > 0) { this.heap[0] = last; this.siftDown(0); } return top; } // 向堆中插入一个元素 offer(elem) { this.heap.push(elem); this.siftUp(this.heap.length - 1); } // 元素下滤操作 siftDown(k) { while (2 * k + 1 < this.heap.length) { let j = 2 * k + 1; if (j + 1 < this.heap.length && this.compareFn(this.heap[j + 1], this.heap[j]) < 0) { j++; } if (this.compareFn(this.heap[k], this.heap[j]) <= 0) { break; } this.swap(k, j); k = j; } } // 元素上滤操作 siftUp(k) { while (k > 0 && this.compareFn(this.heap[k], this.heap[Math.floor((k - 1) / 2)]) < 0) { this.swap(k, Math.floor((k - 1) / 2)); k = Math.floor((k - 1) / 2); } } // 交换堆中的两个元素 swap(i, j) { const temp = this.heap[i]; this.heap[i] = this.heap[j]; this.heap[j] = temp; } } var getLeastNumbers = function(arr, k) { let res = new Array(k); // 注意此题的 k 是元素个数而不是索引,所以和索引 p 做比较时要 - 1 // 首先随机打乱数组 shuffle(arr); let lo = 0, hi = arr.length - 1; // 现在开始寻找第 k 大的元素 while (lo <= hi) { // 在 arr[lo..hi] 中选一个分界点 let p = partition(arr, lo, hi); if (p < k - 1) { // 第 k 大的元素在 arr[p+1..hi] 中 lo = p + 1; } else if (p > k - 1) { // 第 k 大的元素在 arr[lo..p-1] 中 hi = p - 1; } else { // arr[p] 就是第 k 大元素,又因为快速排序的性质, // arr[p] 左边的元素都比 arr[p] 小,所以现在 arr[0..k] 就是我们要找的答案 for (let i = 0; i < k; i++) { res[i] = arr[i]; } return res; } } return res; } // 对 nums[lo..hi] 进行切分 function partition(nums, lo, hi) { let pivot = nums[lo]; // 关于区间的边界控制需格外小心,稍有不慎就会出错 // 我这里把 i, j 定义为开区间,同时定义: // [lo, i) <= pivot;(j, hi] > pivot // 之后都要正确维护这个边界区间的定义 let i = lo + 1, j = hi; // 当 i > j 时结束循环,以保证区间 [lo, hi] 都被覆盖 while (i <= j) { while (i < hi && nums[i] <= pivot) { i++; // 此 while 结束时恰好 nums[i] > pivot } while (j > lo && nums[j] > pivot) { j--; // 此 while 结束时恰好 nums[j] <= pivot } if (i >= j) { break; } // 此时 [lo, i) <= pivot && (j, hi] > pivot // 交换 nums[j] 和 nums[i] swap(nums, i, j); // 此时 [lo, i] <= pivot && [j, hi] > pivot } // 最后将 pivot 放到合适的位置,即 pivot 左边元素较小,右边元素较大 swap(nums, lo, j); return j; } // 洗牌算法,将输入的数组随机打乱 function shuffle(nums) { let rand = new Random(); let n = nums.length; for (let i = 0; i < n; i++) { // 生成 [i, n - 1] 的随机数 let r = i + rand.nextInt(n - i); swap(nums, i, r); } } // 原地交换数组中的两个元素 function swap(nums, i, j) { let temp = nums[i]; nums[i] = nums[j]; nums[j] = temp; } class Random { nextInt(bound) { return Math.floor(Math.random() * bound); } } ``` ```python # by chatGPT (python) # 二叉堆的解法思路 class Solution1: def getLeastNumbers(self, arr: List[int], k: int) -> List[int]: # 大顶堆,堆顶是最大元素 pq = [] for e in arr: # 每个元素都要过一遍二叉堆 heapq.heappush(pq, -e) # 堆中元素多于 k 个时,删除堆顶元素 if len(pq) > k: heapq.heappop(pq) # pq 中剩下的是 arr 中最小的 k 个元素 res = [] while pq: res.append(-heapq.heappop(pq)) return res[::-1] # 快速选择的解法思路 class Solution: def getLeastNumbers(self, arr: List[int], k: int) -> List[int]: res = [0] * k # 注意此题的 k 是元素个数而不是索引,所以和索引 p 做比较时要 - 1 # 首先随机打乱数组 self.shuffle(arr) lo, hi = 0, len(arr) - 1 # 现在开始寻找第 k 大的元素 while lo <= hi: # 在 arr[lo..hi] 中选一个分界点 p = self.partition(arr, lo, hi) if p < k - 1: # 第 k 大的元素在 arr[p+1..hi] 中 lo = p + 1 elif p > k - 1: # 第 k 大的元素在 arr[lo..p-1] 中 hi = p - 1 else: # arr[p] 就是第 k 大元素,又因为快速排序的性质, # arr[p] 左边的元素都比 arr[p] 小,所以现在 arr[0..k] 就是我们要找的答案 for i in range(k): res[i] = arr[i] return res return res # 对 nums[lo..hi] 进行切分 @staticmethod def partition(nums: List[int], lo: int, hi: int) -> int: pivot = nums[lo] # 关于区间的边界控制需格外小心,稍有不慎就会出错 # 我这里把 i, j 定义为开区间,同时定义: # [lo, i) <= pivot;(j, hi] > pivot # 之后都要正确维护这个边界区间的定义 i, j = lo + 1, hi # 当 i > j 时结束循环,以保证区间 [lo, hi] 都被覆盖 while i <= j: while i < hi and nums[i] <= pivot: i += 1 # 此 while 结束时恰好 nums[i] > pivot while j > lo and nums[j] > pivot: j -= 1 # 此 while 结束时恰好 nums[j] <= pivot if i >= j: break # 此时 [lo, i) <= pivot && (j, hi] > pivot # 交换 nums[j] 和 nums[i] nums[i], nums[j] = nums[j], nums[i] # 此时 [lo, i] <= pivot && [j, hi] > pivot # 最后将 pivot 放到合适的位置,即 pivot 左边元素较小,右边元素较大 nums[lo], nums[j] = nums[j], nums[lo] return j # 洗牌算法,将输入的数组随机打乱 @staticmethod def shuffle(nums: List[int]) -> None: n = len(nums) for i in range(n): # 生成 [i, n - 1] 的随机数 r = i + random.randint(0, n - i - 1) nums[i], nums[r] = nums[r], nums[i] ``` https://leetcode.cn/problems/zui-xiao-de-kge-shu-lcof 的多语言解法👆 https://leetcode.cn/problems/zuo-xuan-zhuan-zi-fu-chuan-lcof 的多语言解法👇 ```cpp // by chatGPT (cpp) class Solution { public: string reverseLeftWords(string s, int n) { return s.substr(n) + s.substr(0, n); } }; ``` ```go // by chatGPT (go) func reverseLeftWords(s string, n int) string { return s[n:] + s[:n] } ``` ```java // by labuladong (java) class Solution { public String reverseLeftWords(String s, int n) { return s.substring(n) + s.substring(0, n); } } ``` ```javascript // by chatGPT (javascript) /** * @param {string} s * @param {number} n * @return {string} */ var reverseLeftWords = function(s, n) { return s.substring(n) + s.substring(0, n); }; ``` ```python # by chatGPT (python) class Solution: def reverseLeftWords(self, s: str, n: int) -> str: return s[n:] + s[:n] ``` https://leetcode.cn/problems/zuo-xuan-zhuan-zi-fu-chuan-lcof 的多语言解法👆 ================================================ FILE: technical/cryptography.md ================================================ When we talk about passwords, the first thing we think of is the login password for our account. But from the view of cryptography, this is not really a good password. Why? Because account passwords rely on secrecy: you keep the password in your mind, others don't know it, so they cannot log in to your account. But cryptography thinks that any "secret" will be leaked one day. So, a good encryption algorithm should not rely on keeping the algorithm secret. Even if you know the algorithm, you should not be able to break it. To put it another way: even if I tell you my encryption method, you still can't figure out my password. One of the most amazing algorithms is the Diffie-Hellman key exchange. I was surprised when I first learned it. Two people say some numbers in public, and somehow end up with the same secret. But you, as a listener, can't know this secret. We will introduce this algorithm in detail later. The main goal of cryptography discussed in this article is to solve the problem of encrypting and decrypting information during data transfer. We must assume the data transfer process is unsafe, and all information may be eavesdropped. So, the sender must encrypt the message, and the receiver must know how to decrypt it. But if you let the receiver know how to decrypt, doesn't the eavesdropper also get this information? Next, **we will introduce symmetric encryption, key exchange, asymmetric encryption, digital signature, and public key certificate** to see how these methods solve the problem of secure transmission. ### 1. Symmetric Encryption Symmetric encryption, also called shared-key encryption, uses the same key to encrypt and decrypt data. For example, here is a simple way to do symmetric encryption. We know all information can be written as a sequence of 0s and 1s. If you XOR two same bit sequences, you get a result of 0. So, you can make a random bit sequence with the same length as the original information to use as the key. Then, you XOR the key with the original data to make the encrypted message. To decrypt, you just XOR the encrypted message with the same key, and you get the original information. This is a simple example, but it has problems. For example, the key must be as long as the original message. If the message is large, the key is also large. Also, making long, truly random bit sequences is expensive. Of course, many better symmetric encryption algorithms can fix these problems, like the Rijndael algorithm (also called AES), triple DES, and others. **These algorithms are strong, with huge key spaces, making brute-force attacks very hard. They are also efficient and fast.** **However, the weakness of all symmetric encryption is how to share the key.** Since the same key is used for both sending and receiving, the sender must find a way to give the key to the receiver. If an eavesdropper can steal the message, they can also steal the key. Even the best algorithm can't help in this case. Next, we will introduce two of the most common ways to solve the key sharing problem: the Diffie-Hellman key exchange algorithm and asymmetric encryption. ### 2. Key Exchange Algorithm A key is usually a very large number, used by algorithms for encryption and decryption. But the channel is not secure, so any data you send can be stolen. In other words, is there a way for two people to exchange a secret openly, so that the symmetric key can be safely delivered to the other side? The Diffie-Hellman key exchange algorithm can do this. **To be exact, this algorithm does not send a secret securely to the other side, but lets both parties "generate" the same secret in their minds through some shared numbers. This secret is something a third-party eavesdropper cannot generate.** Maybe this is what people call "having a connection" without saying a word. This algorithm is not very complicated. You can even try it with a friend to share a secret. I will draw its basic flow in a while. But first, you need to know: **Not all operations have reverse operations**. The simplest example is a one-way hash function. Given a number `a` and a hash function `f`, you can quickly calculate `f(a)`. But if you only have `f(a)` and `f`, it's almost impossible to find `a`. The key exchange algorithm takes advantage of this one-way property. Now, let's see how the key exchange algorithm works. By practice, we call the two parties Alice and Bob, and the person who tries to steal their communication Hack. First, Alice and Bob agree on two numbers `N` and `G` as the base numbers. This agreement can be eavesdropped by Hack, so let's put these two numbers in the middle, showing that all three know them: ![](../pictures/cryptography/1.jpg) Now, Alice and Bob each think of a number secretly, called `A` and `B`: ![](../pictures/cryptography/2.jpg) Next, Alice combines her secret number `A` with `G` through some operation to get a number `AG`, and sends it to Bob. Bob does the same with his secret number `B` and `G` to get `BG`, and sends it to Alice: ![](../pictures/cryptography/3.jpg) Now, the status is like this: ![](../pictures/cryptography/4.jpg) Note, just like the hash function example, knowing `AG` and `G` does not let you find out `A`, and it's the same for `BG`. Now, Alice can use `BG` and her own `A` to calculate a number `ABG`. Bob can use `AG` and his own `B` to also get `ABG`. This number is the shared secret for Alice and Bob. For Hack, even if he gets `G`, `AG`, and `BG` from the communication, he still cannot calculate `ABG` because the calculation cannot go backwards. ![](../pictures/cryptography/5.jpg) This is the main process. The specific way to pick numbers and do the math can be found easily online. I won't explain it in detail here. This algorithm can help Alice and Bob get a secret that others cannot find out, even when a third party is listening, and then use it as the key for symmetric encryption. But Hack can try another way to break it. Instead of simply listening, he pretends to be both Alice and Bob. This is called the "**man-in-the-middle attack**": ![](../pictures/cryptography/6.jpg) Like this, both parties do not know they are sharing the secret with Hack, so Hack can read or even change the data. **So, the key exchange algorithm does not completely solve the key distribution problem. Its weakness is that it cannot verify the other person’s identity.** That’s why people usually check identities before using the key exchange algorithm, for example, by using digital signatures. ### 3. Asymmetric Encryption The idea of asymmetric encryption is simple: don't try to secretly transmit the key. Instead, split the key into two parts: a public key for encryption and a private key for decryption. Only share the public key with others. When someone wants to send me data, they use my public key to encrypt it. I can then use my private key to decrypt it. An eavesdropper will have the public key and encrypted data, but they cannot decrypt it without the private key. Think of it like this: **the private key is the key, and the public key is the lock. You can give the lock to anyone, so they can lock up data and send it to you. But only you have the key to unlock it.** A common example is the RSA algorithm, which is a classic asymmetric encryption method. The actual implementation is complicated, so I won't explain it here—there are many resources online. In reality, asymmetric encryption is much slower than symmetric encryption. So, when sending large amounts of data, we don't use the public key to encrypt all the data directly. Instead, we use it to encrypt the symmetric key and send that to the other person. Both sides then use the faster symmetric encryption to send data. One thing to note is that, like the Diffie-Hellman algorithm, **asymmetric encryption can't verify who you are talking to, so it can still be attacked by a man-in-the-middle.** For example, if Hack intercepts Bob’s public key, and then sends their own public key to Alice pretending to be Bob, Alice could unknowingly send her secret data encrypted to Hack's public key. Hack can then use their private key to read it. So, both the Diffie-Hellman algorithm and RSA asymmetric encryption solve the key distribution problem in some way, but they both have similar weaknesses. What’s the difference in how these two methods are used? Simply put, you can choose which to use based on how they work: If both sides want to communicate securely using symmetric encryption without sharing the key directly, they can use the Diffie-Hellman algorithm to exchange the key. If you want anyone to be able to encrypt messages to you, but only you can read them, then use RSA asymmetric encryption and publish your public key. Next, let’s try to solve the problem of verifying who is sending the message. ### 4. Digital Signature Just now we talked about asymmetric encryption. You make your public key open so others can use it to encrypt data and send it to you. Then, only you can use your private key to decrypt it. In fact, **the private key can also be used to encrypt data. For the RSA algorithm, only the public key can unlock data encrypted by the private key**. A digital signature uses this feature of asymmetric keys, but the process is the opposite of public key encryption. **You still make your public key public, but you use your private key to encrypt data, and then publish the encrypted data. This is a digital signature**. You may ask, what is the point? If anyone with my public key can decrypt my private key’s encryption, why do I need to encrypt and send it? That’s right, but **the role of a digital signature is not to hide the content, but to prove your identity. It proves the data indeed comes from you**. Think about it. If only the matching public key can decrypt what you encrypt with your private key, then when some data can be decrypted with your public key, doesn't it mean you (the one holding the private key) created it? Of course, the encrypted data is just the signature. The signature should be sent along with the real data. The detailed process is: 1. Bob generates a public key and a private key. He publishes the public key and keeps the private key safe. 2. **Bob uses his private key to encrypt the data as a signature, then publishes the data along with the signature.** 3. Alice receives the data and the signature. She wants to check if the data really comes from Bob. So, she uses Bob’s public key to decrypt the signature, and compares the result with the received data. If they are the same, it means the data hasn’t been changed and really comes from Bob. Why is Alice so sure? After all, both the data and the signature can be swapped. The reasons are: 1. If anyone changes the data, Alice will find that the decrypted signature does not match the new data. 2. If anyone changes the signature, Bob’s public key will decrypt it into meaningless data, which will not match the original data. 3. If someone tries to change the data and make a matching new signature, they can't. Because they don’t have Bob’s private key, it’s impossible to create a new valid signature. In short, **digital signatures can prove to some degree where the data comes from**. But I say "to some degree" because this can still be attacked by a man-in-the-middle. If the public key is replaced by an attacker when being published, the receiver may do wrong verification. This problem cannot be avoided. It's a bit funny: digital signatures are a way to verify someone's identity, but the whole process relies on the other side's identity being true in the first place... It is something like the chicken and egg problem. **To really confirm someone’s identity, there must be a trusted starting point. Without that, all these steps just move the problem around and don’t really solve it**. ### 5. Public Key Certificate A certificate is actually just a public key plus a signature, issued by a trusted third-party organization. Introducing a trusted third party is a practical way to solve the trust cycle problem. The general process of certificate authentication is as follows: 1. Bob goes to a trusted authority to prove his real identity and provides his public key. 2. Alice wants to communicate with Bob, so she first asks the authority for Bob’s public key. The authority sends back a certificate, which contains Bob's public key and the authority’s signature on it. 3. Alice checks the signature to make sure the public key was really sent by the authority and has not been tampered with. 4. Alice uses this public key to encrypt data and starts communicating with Bob. ![](../pictures/cryptography/7.jpg) ::: note Note This is just to explain the concept: a certificate only needs to be installed once and is not requested from the authority every time. Usually, the server sends the certificate directly to the client, not the authority. ::: You might ask: for Alice to verify the certificate's validity with a digital signature, she needs the authority's (trusted) public key. Isn't this another looping trust problem? Legitimate browsers already have certificates for regular authorities (including their public keys) pre-installed. This lets browsers confirm the authority’s identity, so certificate authentication is trustworthy. When Bob gives his public key to the authority, he must also give a lot of personal info for identity verification, making this process reliable. With Bob’s trusted public key, communication between Alice and Bob is fully protected by encryption algorithms and is very secure. Today’s legitimate websites use the HTTPS protocol, which adds an SSL/TLS security layer between HTTP and TCP. After your browser and the web server complete the TCP handshake, the SSL layer performs its own handshake to exchange security parameters, including the website’s certificate. The browser can then verify the site's identity. After the SSL security layer is set up, all HTTP content is encrypted, keeping your data safe during transfer. This setup makes traditional man-in-the-middle attacks almost impossible. Attackers can only rely on scams or tricks instead of technology flaws. In fact, these tricks can be very effective. For example, I’ve found that some download websites distribute browsers that not only contain strange bookmarks and website links, but also some non-standard authority certificates. Anyone can apply for a certificate, and untrustworthy certificates can cause serious security risks. ### 6. Summary Symmetric encryption algorithms use the same key for encryption and decryption. They are hard to crack and encrypt data quickly, but the key distribution is a problem. The Diffie-Hellman key exchange algorithm lets both sides agree on a secret key. It solves part of the key distribution problem, but cannot verify the identity of the parties, so it is still vulnerable to man-in-the-middle attacks. Asymmetric encryption algorithms create a pair of keys. Encryption and decryption work are separated. RSA is a classic asymmetric encryption algorithm with two uses: If used for encryption, you can publish your public key for others to encrypt data, and only your private key can decrypt it. This keeps data secret. If used for digital signatures, you publish your public key, then use your private key to sign the data. Anyone can use your public key to verify the data was sent by you. However, in both uses, publishing the public key cannot prevent man-in-the-middle attacks. A public key certificate combines the public key and a digital signature, and is issued by a trusted certificate authority. Most browsers have trusted certificate authorities’ public keys built-in, so public key certificates can effectively prevent man-in-the-middle attacks. The SSL/TLS security layer in the HTTPS protocol uses several types of encryption together. **So, do not install untrusted browsers and do not install certificates from unknown sources.** Cryptography is only a small part of security. Even an HTTPS site verified by an official authority does not mean it is trustworthy. It only means data transmission is secure. Technology alone can never fully protect you. The most important thing is to be careful, increase your security awareness, and handle sensitive data with caution. ================================================ FILE: technical/linux-process.md ================================================ When it comes to processes, the most common interview question is the relationship between processes and threads. Here is the answer first: **in Linux, there is almost no difference between processes and threads**. In Linux, a process is just a data structure. Once you understand it, you can understand how file descriptors, redirection, and pipes work under the hood. In the end, we will also see why we say threads and processes are almost the same from the operating system’s point of view. ### 1. What Is a Process First, in an abstract way, our computer looks like this: ![](../pictures/linuxProcess/1.jpg) The big rectangle is the computer’s **memory space**. The small rectangles inside are **processes**. The circle at the bottom left is the **disk**. The shape at the bottom right is some **I/O devices**, such as mouse, keyboard, and monitor. Note that the memory space is divided into two parts: the upper part is **user space**, the lower part is **kernel space**. User space holds the resources used by user processes. For example, if you create an array in your code, that array is in user space. Kernel space holds system resources used by kernel processes. These are usually not accessible to user processes. But some kernel-space resources can be shared with user processes, such as some shared libraries. We write a hello program in C, compile it into an executable file, then run it in the terminal. It prints “hello world” and exits. From the OS view, a new process is created. This process reads the executable file into memory, then runs it, then exits. **The compiled executable is just a file**, not a process. To really run, the executable must be loaded into memory and wrapped as a process. The process is created by the OS. Each process has its own properties, such as process ID (PID), process state, open files, and so on. After the process is created, the OS loads your program into it, then your program runs. So how does the OS create a process? **For the OS, a process is just a data structure**. Let’s look directly at Linux source code: ```cpp struct task_struct { // process state long state; // virtual memory structure struct mm_struct *mm; // process ID pid_t pid; // pointer to the parent process struct task_struct __rcu *parent; // list of child processes struct list_head children; // pointer to filesystem information struct fs_struct *fs; // an array containing pointers to files opened by the process struct files_struct *files; }; ``` `task_struct` is the kernel’s description of a process. You can also call it the “process descriptor”. The full code is complex; here we only show some common fields. Two interesting fields are the `mm` pointer and the `files` pointer. `mm` points to the process’s virtual memory, which is where resources and executables are loaded. `files` points to an array that holds pointers to all files opened by this process. ### 2. What Is a File Descriptor Let’s talk about `files`. It is an array of file pointers. In general, a process reads input from `files[0]`, writes output to `files[1]`, and writes errors to `files[2]`. For example, in our view, C’s `printf` prints to the terminal. But from the process’s view, it writes data to `files[1]`. Similarly, `scanf` reads data from `files[0]`. **When a process is created, the first three entries of `files` are filled with default values: standard input, standard output, and standard error. What we call “file descriptor” is just the index of this file pointer array.** So by default, file descriptor 0 is input, 1 is output, 2 is error. We can draw a picture: ![](../pictures/linuxProcess/2.jpg) On a normal computer, the input stream is the keyboard, the output stream is the monitor, and the error stream is also the monitor. So this process connects to the kernel with three “lines”. Because hardware is managed by the kernel, our process must use “system calls” to ask the kernel to access hardware. ::: note Note Don’t forget, in Linux everything is abstracted as a file. Devices are also files, which can be read and written. ::: If our program needs other resources, such as opening a file to read or write, this is also simple. We make a system call and let the kernel open the file. That file will then be placed in the 4th position of `files`: ![](../pictures/linuxProcess/3.jpg) With this, **input redirection** is easy to understand. When a program wants to read data, it reads from `files[0]`. So if we point `files[0]` to a file, the program will read from that file instead of the keyboard: ```shell $ command < file.txt ``` ![](../pictures/linuxProcess/5.jpg) Similarly, **output redirection** is just pointing `files[1]` to a file. Then the program’s output will be written to that file, not to the monitor: ```shell $ command > file.txt ``` ![](../pictures/linuxProcess/4.jpg) Error redirection is the same idea, so we skip it. The **pipe operator** works in a similar way. It connects the output stream of one process to the input stream of another process with a “pipe”. Data flows in this pipe. This design is very elegant: ```shell $ cmd1 | cmd2 | cmd3 ``` ![](../pictures/linuxProcess/6.jpg) Now you can see how clever “everything is a file” is in Linux. No matter if it is a device, another process, a socket, or a real file, everything can be read and written. The OS puts them all into one simple `files` array. The process only needs a simple file descriptor to access the right resource. The OS hides the complex details, which decouples things well and is both simple and efficient. ### 3. What Is a Thread First we need to be clear: both multi-process and multi-thread are concurrency. Both can improve CPU usage. So the key question is: what is the difference between them? Why do we say threads and processes are almost the same in Linux? Because from the Linux kernel’s view, it does not really treat them as different things. We know that the system call `fork()` can create a new child process, and the function `pthread()` can create a new thread. **But both threads and processes are represented with the same `task_struct` structure. The only difference is which data areas are shared.** In other words, a thread looks just like a process. The difference is: some data areas of a thread are shared with its parent process, while a child process gets its own copy, not a shared area. For example, in threads, the `mm` structure and `files` structure are shared. Look at the two diagrams: ![](../pictures/linuxProcess/7.jpg) ![](../pictures/linuxProcess/8.jpg) So, in our multi-thread programs, we need locks to avoid multiple threads writing to the same area at the same time, or data may get corrupted. You may ask: **since processes and threads are similar, and processes do not share data so there is no data race problem, why are threads used more widely than processes?** Because in real life, concurrent tasks that share data are more common. For example, ten people withdraw 10 yuan each from one bank account. We want the shared account balance to decrease by 100 yuan in total, not ten separate copies each decreasing 10 yuan. We should also note: only in Linux are threads treated as processes that share some data. The kernel does not give them a special structure. Many other OSes treat threads and processes differently; threads have their own special data structures. In my opinion, this is less simple than Linux’s design and increases system complexity. In Linux, creating threads and processes is both very efficient. To handle the problem of copying memory when creating a new process, Linux uses a copy-on-write strategy. That is, it does not actually copy the parent’s memory at first. It copies only when a write happens. **So creating a new process or a new thread in Linux is very fast.** ================================================ FILE: technical/linux-shell.md ================================================ I personally like using Linux systems. Although Windows has a better graphical interface, its support for scripts is poor. At first, I was not used to command-line operations, but after getting used to it, I found that moving the mouse and clicking is actually what wastes the most time. **This article is not about specific Linux commands. Instead, I will explain some details and tips that are easy to confuse and can help you work more efficiently, using real situations as examples.** 1. The difference between standard input and command parameters. 2. Running commands in the background, but they all stop after closing the terminal. 3. The difference between single and double quotes in representing strings. 4. Sometimes, a command with `sudo` will say "command not found". 5. How to avoid typing repeated filenames, paths, or commands, and some other little tricks. ## 1. The Difference Between Standard Input and Parameters This is a common problem. Many people don't know when to use the pipe symbol `|` or file redirection `>` and `<`, and when to use variables like `$`. For example, I have a shell script called `connect.sh` for connecting to broadband, and it's in my home directory: ```shell $ where connect.sh /home/fdl/bin/connect.sh ``` If I want to delete this script and type as little as possible, how should I do it? I tried this before: ```shell $ where connect.sh | rm ``` Actually, this is wrong. The correct way is: ```shell $ rm $(where connect.sh) ``` The first command tries to send the result of `where` to `rm` as standard input. The second command sends the result as a command-line parameter. **Standard input means functions like `scanf` or `readline` in programming languages. Parameters are the string arrays passed to the main function as arguments.** In [Linux File Descriptors](https://labuladong.online/en/algo/other-skills/linux-process/), I said that pipe and redirection send data to a program's standard input, while `$(cmd)` gets the result of a command and passes it as a parameter. Using the example above, the source code of the `rm` command does not accept standard input, only command-line parameters to delete the specified file. In comparison, the `cat` command accepts both standard input and parameters: ```shell $ cat filename ...file text... $ cat < filename ...file text... $ echo 'hello world' | cat hello world ``` **If a command will block the terminal and wait for your input, it means it accepts standard input. Otherwise, it does not. For example, if you just run `cat` without any arguments, the terminal will wait for your input and print what you typed.** ## 2. Run Programs in the Background Suppose you log in to a server and run a Django web program: ```shell $ python manager.py runserver 0.0.0.0 Listening on 0.0.0.0:8080... ``` Now you can test the Django service using the server's IP address, but the terminal is blocked and won't respond to your input unless you use Ctrl-C or Ctrl-/ to stop the Python process. You can add `&` at the end of the command to run it in the background. This way, the terminal doesn't block and you can run other commands. However, if you log out from the server, you can't access the web page anymore. If you want the web service to keep running after you log out from the server, use `(cmd &)` like this: ```shell $ (python manager.py runserver 0.0.0.0 &) Listening on 0.0.0.0:8080... $ logout ``` **The underlying reason is:** Every terminal is a shell process. When you run something, the shell makes it a child process. Normally, the shell waits for the child process to finish before accepting new commands. If you add `&`, the shell does not wait and can continue to accept your commands. But if you close the shell terminal, all its child processes will stop. If you use `(cmd &)` to run a command, it will become a child of the `systemd` daemon process, instead of the shell. So, when you close the terminal, this command is not affected. There is another common way to keep a process running: ```shell $ nohup some_cmd & ``` The `nohup` command works for this too, but in my tests, using `(cmd &)` is more stable. ## 3. The Difference Between Single and Double Quotes Different shells may behave slightly differently, but one thing is certain: **if you use single quotes, characters like `$`, `(`, and `)` are not interpreted, but double quotes will interpret them.** You can test shell behavior with the `set -x` command, which will show what the shell is really doing: ![](../pictures/linuxshell/1.png) You can see that `echo $(cmd)` and `echo "$(cmd)"` have almost the same result, but there are some differences. Notice that with double quotes, the result adds single quotes automatically, but with no quotes, it does not. **In short, if the string from `$` contains spaces, you should use double quotes, otherwise you will get errors.** ## 4. sudo: command not found Sometimes, a command works for a normal user, but adding `sudo` gives a "command not found" error: ```shell $ connect.sh network-manager: Permission denied $ sudo connect.sh sudo: command not found ``` This happens because the `connect.sh` script is only in the user's environment: ```shell $ where connect.sh /home/fdl/bin/connect.sh ``` **When you use `sudo`, the system uses the environment variables defined in `/etc/sudoers`. That does not include the script's path.** The solution is to run the script with its full path: ```shell $ sudo /home/fdl/bin/connect.sh ``` ## 5. Typing similar filenames is annoying You can use curly braces and commas to extend file names easily. Here are some examples: ```shell $ echo {one,two,three}file onefile twofile threefile $ echo {one,two,three}{1,2,3} one1 one2 one3 two1 two2 two3 three1 three2 three3 ``` Every item in the braces is combined with the string outside the braces. **Do not add spaces inside the braces or around the commas, or it will not work.** This is very useful for commands like `cp`, `mv`, and `rm`: ```shell $ cp /very/long/path/file{,.bak} # This copies file to file.bak $ rm file{1,3,5}.txt # This removes file1.txt file3.txt file5.txt $ mv *.{c,cpp} src/ # This moves all .c and .cpp files to the src folder ``` ## 6. Typing long paths is annoying **Use `cd -` to go back to the previous directory.** For example: ```shell $ pwd /very/long/path # Go to home directory $ cd $ pwd /home/labuladong # Go back to the previous directory $ cd - $ pwd /very/long/path ``` **The special command `!$` replaces the last argument of the previous command.** Example: ```shell # No execute permission $ /usr/bin/script.sh zsh: permission denied: /usr/bin/script.sh $ chmod +x !$ chmod +x /usr/bin/script.sh ``` **The special command `!*` replaces all arguments of the previous command.** Example: ```shell # Created three script files $ file script1.sh script2.sh script3.sh # Give them all execute permission $ chmod +x !* chmod +x script1.sh script2.sh script3.sh ``` **You can add your common directories to the `CDPATH` environment variable.** When you use `cd`, the command will search these directories if it can't find the folder in the current location. For example, if you often go to `/var/log`, set it like this: ```shell $ export CDPATH='~:/var/log' # cd will now also search your home ~ and /var/log directories $ pwd /home/labuladong/musics $ cd mysql cd /var/log/mysql $ pwd /var/log/mysql $ cd my_pictures cd /home/labuladong/my_pictures ``` This is very handy. You do not need to type the full path every time, saving time. Note: These commands work with bash. Other shell interpreters also support searching directories with `cd`, but you may need to set it up differently. Please search for how to do it in your shell. ### 7. Typing repeated commands is troublesome **Use the special command `!!` to quickly repeat the last command**: ```shell $ apt install net-tools E: Could not open lock file - open (13: Permission denied) $ sudo !! sudo apt install net-tools [sudo] password for fdl: ``` What if a command is long and you can't remember the exact parameters? **In bash terminal, you can use `Ctrl+R` to search previous commands**. This key combination searches backwards, finding the most recent command that matches. For example, after pressing `Ctrl+R`, type `sudo`, and bash will show you the most recent command containing `sudo`. Press Enter to run it: ```shell (reverse-i-search)`sudo': sudo apt install git ``` But this method has some drawbacks: First, it only works in bash, not in zsh or other shells. Second, it only finds the most recent match. If you want an older command, it's not convenient. In this case, **the most common way is to use the `history` command with pipe `|` and `grep` to search historical commands**: ```shell # Filter all commands that include 'config' $ history | grep 'config' 7352 ./configure 7434 git config --global --unset https.proxy 9609 ifconfig 9985 clip -o | sed -z 's/ /, /g' | clip 10433 cd ~/.config ``` All the shell commands you use are recorded. The number in front is the command's index. After you find the command you want, you don't need to copy and paste. **Just use `!` plus the command number to run it again**. For example, to run the `git config` command shown above: ```shell $ !7434 git config --global --unset https.proxy # done ``` If you think typing `history | grep` is still too much, you can add a function to your shell config file (like `.bashrc` or `.zshrc`): ```shell his() { history | grep "$@" } ``` Now you only need to type `his 'some_keyword'` to search history. I usually don't use bash as my terminal. I recommend a very useful shell: zsh, which is also what I use. Zsh supports many plugins and is easy to customize. You can look up detailed setup guides online. ## Other Small Tips **1. The `yes` command automatically inputs `y` for confirmation** Sometimes when installing software, you get interactive prompts like: ```shell $ sudo apt install XXX ... XXX will use 996 MB disk space, continue? [y/n] ``` We usually keep pressing y. If you're automating the installation, these questions can get in the way. The `yes` command helps: ```shell $ yes | your_cmd ``` This will automatically answer `y` for every prompt and won't stop to ask you. If you have read [Linux File Descriptors](https://labuladong.online/en/algo/other-skills/linux-process/), you will understand why: The `yes` command simply prints a lot of `y`. By connecting its output to the input of `your_cmd`, any interactive prompt will get a `y` and newline, just like you typed it. **2. Special variable `$?` saves the return value of the previous command** In Linux shell, if the program ends normally, the return value is 0. If not, the return value is not 0. Reading the last command's return value may not seem useful in daily use. But if you're writing shell scripts, it's very useful. For example, you want to add a footer to many markdown files automatically. Some already have it, some don't. To avoid adding it again, you must check if the footer already exists. Here, `$?` and `grep` help: ```shell #!/bin/bash filename=$1 # Check if the last lines contain the keyword tail | grep 'page_footer' $filename # grep returns 0 if found, non-0 if not [ $? -ne 0 ] && { } ``` **3. Special variable `$$` saves the PID (process ID) of the current process** This may not be useful every day, but it's helpful in shell scripts. For example, when you need to create temporary files in `/tmp`, you need unique names. You can use the `$$` variable to get the process PID, which is always unique. You do not need to remember the file name. ================================================ FILE: technical/problem-solving-tips.md ================================================ First, let's address a question: Is it better to practice LeetCode problems directly on the website or on a local IDE? If the format is like the Nowcoder coding assessments, where you handle input and output by yourself, it's definitely better to write in an IDE. However, for LeetCode's format, I personally prefer solving problems directly on the website for two reasons: **1. Convenience** LeetCode has some custom data structures, such as `TreeNode` and `ListNode`. In a local environment, you would need to copy these classes over. Also, you can't test effectively in an IDE; after writing your code, you would still need to paste it into the website to run tests, so you might as well write it directly on the website. Algorithms aren't like engineering code; they are relatively small in scale, so the benefits of IDE auto-completion are negligible. **2. Practicality** During interviews, most interviewers expect you to solve algorithm problems directly on a website, preferably explaining your thought process as you code. If you are used to coding without IDE auto-completion and compiling in your mind during practice, you'll write code faster and more confidently during interviews. When I interviewed with Kuaishou, an interviewer asked me to [implement the LRU algorithm](https://labuladong.online/en/algo/data-structure/lru-cache/). I implemented both the doubly linked list and the hash-linked list directly on the website, and everything worked perfectly on the first try, which surprised the interviewer. During the fall recruitment, I was able to secure offers largely because my handwritten algorithm solutions exceeded interviewers' expectations. This was all thanks to the practice I had doing problems directly on the website. Of course, if you really don't want to solve problems on the website, you can use my VSCode or JetBrains plugin for solving problems. These plugins are perfectly integrated with the content on my website. Next, I'll introduce some practical "tricks" and debugging techniques to comprehensively improve your chances of passing coding assessments. ## Avoiding Complex Solutions As you may know, most coding test questions require you to handle input data yourself and then have your program print the output. The underlying principle of judging is to use the Linux redirection symbol `>` to write the output of your program to a file, and then compare your output to the correct answer. In some cases, the complexity of the problem becomes unnecessary. We can simplify our approach. For example, suppose a question asks you to reverse a singly linked list given as a space-separated string of characters, emphasizing that you must convert the input to a singly linked list before reversing it. What would you do? Would you define a `ListNode` class for the singly linked list node, write code to convert the input into a singly linked list, and then perform the tedious pointer operations to reverse the list? Remember, our goal is to get the problem accepted (AC), not to learn algorithmic thinking. The judging system cannot accurately assess algorithm logic, only whether your output is correct. So a clever approach is to store the input in an array, reverse it with a few lines of code using the [two-pointer technique](https://labuladong.online/en/algo/essential-technique/array-two-pointers-summary/), and print the result. I've seen many such problems. For instance, a problem may require you to reverse a singly linked list in groups and emphasize using recursion, like the algorithm in [Reverse Nodes in k-Group](https://labuladong.online/en/algo/data-structure/reverse-linked-list-recursion/). If you reverse using an array, it takes just two minutes to write the solution. Similarly, in the problem discussed in [Flatten Nested List](https://labuladong.online/en/algo/data-structure/flatten-nested-list-iterator/), the approach is quite clever. But if you encounter it in a test with input like a string `[1,[4,[6]]]`, you can simply use a regular expression to extract the numbers, resulting in a flattened list. ## Choosing a Programming Language From the perspective of solving algorithm problems, I personally recommend using Java as the programming language for coding interviews. This is because JetBrains' IntelliJ is exceptionally user-friendly. Compared to editors for other languages, it not only offers convenient shortcuts like `psvm` and `sout` (if you don't know these, it's time to hit the books), but it also helps catch many common mistakes, such as forgetting to increment variables in a `while` loop or mistakenly placing a `return` statement inside a loop due to oversight. C++ is also acceptable, but I find it not as convenient as Java. From what I remember, C++ doesn't even have a `split` function for strings, which alone makes me reluctant to use C++. Moreover, C++ has stricter time constraints. While other languages might have a time limit of 4000ms, C++ is limited to 2000ms, which seems unfair. No wonder when I see others writing algorithms in C++, they avoid using the standard library's `vector` container and instead opt for raw `int[]` arrays, which looks quite cumbersome to me. As for Python, I don’t use it much for problem-solving because I’m not a fan of dynamic languages—they can be difficult to debug. However, it does provide many useful features. If you are well-versed in Python, you can sometimes find shortcuts. For instance, in our previous discussion on the [expression evaluation algorithm](https://labuladong.online/en/algo/data-structure/implement-calculator/), which is a difficult-level algorithm, you can directly obtain the answer using Python's built-in `exec` function. This can certainly be advantageous in coding tests because, as mentioned earlier, the focus is on the result, and no one cares how you achieve it. ## Layered Code Structure Layering code is considered a good practice as it can increase coding speed and reduce debugging difficulty. Simply put, avoid writing all your code in the `main` function. The approach I always use is to have the `main` function handle input data, add a `solution` function to manage data processing and output the result, and then use another function like `backtrack` to handle the specific algorithm logic. For example, if I am solving a problem using dynamic programming with memoization, the general structure of the code would look like this: ```java public class Main { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); // mainly responsible for receiving data int N = scanner.nextInt(); int[][] orders = new int[N][2]; for (int i = 0; i < N; i++) { orders[i][0] = scanner.nextInt(); orders[i][1] = scanner.nextInt(); } // delegate solution to solve the problem solution(orders); } static void solution(int[][] orders) { // exclude some basic boundary cases if (orders.length == 0) { System.out.println("None"); return; } // delegate the dp function to execute specific algorithm logic int res = dp(orders, 0); // responsible for outputting the result System.out.println(res); } // memoization static HashMap memo = new HashMap<>(); static int dp(int[][] orders, int start) { // specific algorithm logic } } ``` Notice how clear it is when you structure the code this way: each function has its own primary task. If something goes wrong, it's easier to debug. It's not about writing the code in a highly standardized way. You can skip constraints like `private`, and even using pinyin for variable names is fine. The key point is not to write all the code directly in the `main` function. It becomes chaotic, and while it might not cause errors immediately, debugging will be quite challenging if issues arise. Losing track of the problem is something to be avoided. ## How to Debug Algorithms Errors in code are unavoidable. Sometimes, the entire idea might be wrong, or it could be a minor detail, such as swapping `i` and `j`. How do you troubleshoot such issues? I believe that general algorithm issues should not be too difficult to identify. Visual inspection should reveal most problems, and if not, printing the values of key variables should eventually uncover the issue. **The most challenging aspect is debugging recursive algorithms.** Without certain experience, the recursive process of functions is hard to understand correctly. Therefore, let's focus on how to efficiently debug recursive algorithms. Some readers might suggest copying the algorithm into an IDE and setting breakpoints to step through it. Isn't that sufficient? This method is certainly viable, but as discussed in previous articles, recursive functions are best understood from a global perspective, rather than delving into specific details. If you are not familiar enough with recursion and lack a global perspective, stepping through with breakpoints can easily lead to confusion. **My suggestion is to print key values directly within the recursive function and use indentation to visually observe the execution of the recursive function.** Indentation significantly enhances debugging efficiency. Besides the solution function, we define a new function `printIndent` and a global variable `count`: ```java // Global variable to record the recursion depth of the recursive function int count = 0; // Input n, print n tab indents void printIndent(int n) { for (int i = 0; i < n; i++) { printf(" "); } } ``` Next, here comes the strategy: **At the beginning of the recursive function, call `printIndent(count++)` and print key variables; then, before all `return` statements, call `printIndent(--count)` and print the return values**. Let's consider a concrete example. In the previous article [Dynamic Programming in Fallout 4](https://labuladong.online/en/algo/dynamic-programming/freedom-trail/), a recursive `dp` function was implemented. Its basic structure is as follows: ```java int dp(String ring, int i, String key, int j) { // base case if (j == key.length()) { return 0; } // state transition for (int k : charToIndex.get(key.charAt(j))) { int subProblem = dp(ring, k, key, j + 1); } return res; } ``` After debugging, this recursive `dp` function has been transformed into the following: ```java int count = 0; void printIndent(int n) { for (int i = 0; i < n; i++) { System.out.print(" "); } } int dp(String ring, int i, String key, int j) { // printIndent(count++); // printf("i = %d, j = %d ", i, j); if (j == key.length()) { // printIndent(--count); // printf("return 0 "); return 0; } for (int k : charToIndex.get(key.charAt(j))) { int subProblem = dp(ring, k, key, j + 1); } // printIndent(--count); // printf("return %d ", res); return res; } ``` **Simply add some print statements at the beginning of the function and at every `return` statement.** If you remove the comments and run a test case, the output would look like this: ![](../pictures/algo-debug-tech/1.jpg) By comparing the corresponding indentation, you can know the key parameters `i, j` entered each time in the recursion, as well as the result returned from each recursive call. **Most importantly, this provides a clear view of the recursive process. Have you noticed that it resembles a recursion tree?** ![](../pictures/algo-debug-tech/2.jpg) In the previous article [Detailed Explanation of Dynamic Programming Patterns](https://labuladong.online/en/algo/essential-technique/dynamic-programming-framework/), it was mentioned that understanding a recursive function is best achieved by drawing a recursion tree. With this print method, there's no need to draw the recursion tree yourself, and you can clearly see the return value of each recursion. **This is a small but effective technique to greatly enhance the "joy" of solving problems, more efficient than setting breakpoints in an IDE.** I have directly integrated this feature into the visualization panel, where the printed values in the recursive function automatically include indentation. For more details, please refer to [Visualization Panel Instructions](https://labuladong.online/en/algo/intro/visualize/). ## Exam Preparation Strategy It's not worth getting stuck on a single algorithm problem right before the exam. Instead, try to look at as many different types of problems as possible. Spend five minutes thinking about each one, and if you can't figure out the solution, directly check other people's answers. Just understanding the approach is enough; there's no need to write it out yourself, as it's often a waste of time. During the written test, the biggest fear is having no ideas. By reviewing various problem types, you will at least feel more at ease. As long as you have an approach in mind, solving a problem in 20 to 30 minutes on average is not too difficult. As previously mentioned, there is no problem that brute-force enumeration can't solve. You can directly use the [Backtracking Algorithm Framework](https://labuladong.online/en/algo/essential-technique/backtrack-framework/) to force a solution. If necessary, add a memoization technique, and it becomes the [Dynamic Programming Framework](https://labuladong.online/en/algo/essential-technique/dynamic-programming-framework/). Worst case, if you can't solve it, achieving 60% of the test cases with brute force is still acceptable. I won't say much more about strategies. They sound simple and can be easy to grasp, but the challenge is that you need to recognize them to understand them. In this article, I've briefly introduced some techniques for algorithm exams. Take your time to savor them~ ================================================ FILE: technical/session-and-cookie.md ================================================ Most people are familiar with cookies. For example, after logging into a website for a while, you will be asked to log in again. Or if you have tried web scraping, you may notice some websites can block your scraper. These things are related to cookies. If you understand how the server handles cookies and sessions, you can explain these situations, and even find ways to bypass some website restrictions. Let me explain step by step. ## Introduction to Session and Cookie The reason we have cookies is because HTTP is a stateless protocol. In other words, the server cannot remember you. Each time you refresh a page, you would have to log in again with your username and password. This is obviously not acceptable, so cookies work like tags. The server "tags" you, and every time you send a new request to the server, it can recognize you through the cookie. In short: **A cookie can be seen as a "variable" like `name=value`, stored in the browser. A session can be seen as a JSON object, stored on the server (usually in a cache database like Redis).** Note that I said "a cookie can be seen as a variable," but the server can set multiple cookies at once. So sometimes we say a cookie is "a group of key-value pairs," and that's correct too. Cookies can be set on the server side by using the HTTP SetCookie field. Here is a simple Servlet example: ```java public void doGet(HttpServletRequest request, HttpServletResponse response) { // Set two cookies Cookie cookie1 = new Cookie("name1", "value1"); response.addCookie(cookie1); Cookie cookie2 = new Cookie("name2", "value2"); response.addCookie(cookie2); // Write string to web page response.getWriter().println("html content"); } ``` When you visit the corresponding website, you can use the developer tools in your browser to check the details of the HTTP communication. You will see that the server sends two `SetCookie` commands: ![](../pictures/session/1.png) After this, every request from the browser contains these two cookies in the `Cookie` field: ![](../pictures/session/2.png) **So, the purpose of cookies is quite simple: the server gives each client (browser) a tag to help identify them.** Of course, HTTP can set more cookie parameters, like expiration time, or only allowing a cookie in a specific path. However, cookies have a size limit, and they are stored in the HTTP header. So every time a request is sent, cookies are transferred, which uses bandwidth. More importantly, some information is not good to keep on the client side, such as login status. **Session works together with cookies to solve these problems.** The most common example is logging in. After you log in successfully, the server creates a session to store your login information, for example: ```json { "user_id": 12345, "username": "Zhang San", "role": "admin", "login_time": "2025-11-01 10:00:00" } ``` Then, the server generates a random session ID (like `abcd1234`) and sends it to your browser with a cookie: `sessionID=abcd1234`. After that, when you visit any page, your browser always sends this cookie. The server uses the ID to find the right session and knows your user ID and permissions, so you don’t need to log in every time. Session data is usually stored in the server's memory database (like Redis), which is fast. These sessions are not saved forever—they have an expiration time. For example, if you do not use the website for three days, your session will be deleted automatically. That’s why some websites ask you to log in again after you haven’t used them for a while. By the way, once you understand cookies, you'll notice that some websites allow you to use a service a limited number of times if you don’t log in. How do they do this? They just set a session ID in your browser's cookie. If you delete your cookies, you can bypass the use limit. Of course, don’t overdo this, as websites need to make money too. These are the basics of cookies and sessions. Cookie is a part of the HTTP protocol and not very complex. Session is something that can be customized. Next, let’s look at how to implement session management in code. ## How Session Works The idea behind sessions is simple, but the implementation has some tricks. Usually, it needs three parts working together: `Manager`, `Provider`, and `Session`. ![](../pictures/session/4.jpg) Let's use the example where a logged-in user visits their profile page to understand the process: 1. The user (already logged in) visits the `/profile` page. The Handler function gets the request and reads the sessionID (like `abcd1234`) from the cookie in the HTTP header, then gives this ID to the `Manager`. 2. The `Manager` handles session management and stores global settings (like session timeout, cookie name, etc). It passes the sessionID to the internal `Provider` to look up the session. 3. The `Provider` is like a container. It stores sessions for all users in a hash table (for example: Zhang San's session, Li Si's session, and so on). It finds the `Session` object for this sessionID and returns it. 4. The `Session` object holds the user's login info (such as user_id, username, role, etc). The Handler reads this data and creates a personalized page for the user (for example: "Welcome back, Zhang San!") and returns it to the client. You may wonder: Why make it so complicated? Why not just use a hash table in the Handler to map `sessionID → login info`? Below is the reason why we split it into three layers. ### Why Do We Need the Session Layer? Although a session is just a key-value store (user_id, username, etc.), we can't simply use a hash table. There are two reasons: **1. Need to store extra data:** Besides login info, we also have to keep sessionID, last access time, expiry time, etc, to help clear expired sessions (like the LRU algorithm). **2. Support different storage methods:** If you just use an in-memory hash table, all users will be logged out when the server restarts, and it takes too much memory with lots of users. In real use, sessions can be kept in Redis, MySQL, or elsewhere. The `Session` interface gives a standard way to use sessions so that the storage details are hidden: ```java interface Session { // Set a key-value pair void set(String key, Object value); // Get the value of a key Object get(String key); // Delete a key void delete(String key); } ``` ### Why Do We Need the Provider Layer? The `Provider` manages all online users' sessions. In simple cases, a hash table (sessionID → Session) is enough, but real apps are more complex: **Need to clean up expired sessions automatically:** If a website has 10,000 users online, you can't manually delete expired sessions. You can use an LRU algorithm to remove them, which needs a hash-linked list data structure. ::: tip Tip For more details about the LRU algorithm, see [LRU Algorithm Explained](https://labuladong.online/en/algo/data-structure/lru-cache/). ::: The `Provider` hides these details and only shows simple add, delete, find, and update methods: ```java interface Provider { // Create and return a session Session sessionCreate(String sid); // Delete a session void sessionDestroy(String sid); // Find a session Session sessionRead(String sid); // Update a session void sessionUpdate(String sid); // Remove expired sessions using an LRU-like method void sessionGC(long maxLifeTime); } ``` ### Why Do We Need the Manager Layer? The `Manager` handles global settings, such as: - Session timeout (30 minutes? 2 hours?) - Cookie name (is it `sessionID` or `sid`?) - Where to store sessions (memory? Redis? MySQL?) The actual add, delete, find, and update work is done by `Provider` and `Session`. The `Manager` only manages configuration. This makes it easy to switch between implementations, like using memory in development but Redis in production. The main idea of this three-layer design is decoupling: The Session layer handles single user data; the Provider layer manages all users; the Manager handles global settings. Each part can be changed separately without affecting others.