├── .github └── ISSUE_TEMPLATE │ └── -----.md ├── README.md ├── answer └── 0027 │ ├── main.go │ └── main.py └── docs ├── .nojekyll ├── README.md ├── _coverpage.md ├── _img ├── icon.png ├── qrcode.png ├── questions │ ├── 150.png │ └── 4-dp-state.png ├── weekly-122.png ├── weekly-131.png ├── weekly-133.png ├── weekly-134.png ├── weekly-137.png └── weekly-138.png ├── _navbar.md ├── _sidebar.md ├── algorithm ├── backtrack │ └── README.md ├── bit │ └── README.md ├── divide-and-conquer │ └── README.md ├── double-pointer │ └── README.md ├── dynamic │ └── README.md ├── greedy │ └── README.md ├── math │ └── README.md ├── other │ └── README.md ├── recursion │ └── README.md ├── research │ ├── bfs │ │ └── README.md │ ├── binary-search │ │ └── README.md │ └── dfs │ │ └── README.md ├── sliding-window │ └── README.md └── sort │ ├── README.md │ ├── bubble │ └── README.md │ ├── heap │ └── README.md │ ├── other │ └── README.md │ └── quick │ └── README.md ├── biweekly └── README.md ├── concept ├── base-algorithm │ └── README.md ├── data-structure │ └── README.md └── sort │ └── README.md ├── data-structure ├── array │ └── README.md ├── graph │ └── README.md ├── hash │ └── README.md ├── heap │ └── README.md ├── linked_list │ └── README.md ├── queue │ └── README.md ├── stack │ └── README.md ├── string │ └── README.md └── tree │ ├── README.md │ ├── bfs │ └── README.md │ ├── bst │ └── README.md │ ├── dfs │ └── README.md │ ├── n-ary │ └── README.md │ ├── other │ └── README.md │ ├── recursion │ └── README.md │ └── trie │ └── README.md ├── design └── README.md ├── favicon.ico ├── index.html ├── offer └── README.md ├── other └── README.md └── weekly └── README.md /.github/ISSUE_TEMPLATE/-----.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 添加待解题 3 | about: 我是莫得感情的刷题机器 4 | title: '' 5 | labels: TODO-J, TODO-C 6 | assignees: JalanJiang, csming1995 7 | 8 | --- 9 | 10 | - 原题链接:[题目名](url) 11 | - 题解链接: 12 | 13 | ---- 14 | 15 | - [ ] 输出题解 16 | - [ ] 时间复杂度 17 | - [ ] 是否最优解 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | [![LICENSE](https://img.shields.io/badge/License-CC%20BY%203.0%20CN-lightgrey.svg)](https://creativecommons.org/licenses/by/3.0/cn/) 4 | [![LICENSE](https://img.shields.io/badge/license-Anti%20996-blue.svg)](https://github.com/996icu/996.ICU/blob/master/LICENSE) 5 | [![Badge](https://img.shields.io/badge/link-996.icu-red.svg)](https://996.icu/#/zh_CN) 6 | 7 | > 愿所有的热爱都能一直长久地燃烧下去。 8 | 9 | 点击此处:[在线阅读](http://jalan.space/leetcode-notebook/) 10 | 11 | ## 这是什么? 12 | 13 | LeetCode 解题本,基于**算法与数据结构**,对做过的 LeetCode 练习题进行归纳与总结。该项目代号为 [Rum](https://zh.wikipedia.org/wiki/%E5%85%B0%E5%A7%86%E9%85%92),朗姆酒是鸡尾酒的热带气息基酒,暗指「算法与数据结构是程序员的基底」。 14 | 15 | 项目内题解主要由 Python 书写,包含少量 Java/Go/PHP/Swift 版本,后续会陆续补充完全。 16 | 17 | 项目使用 [docsify](https://docsify.js.org/#/) 构建,搭建教程见 [《docsify 入坑指南与我放弃 Gitbook 的那些理由》](http://jalan.space/2019/06/21/2019/begin-docsify/) 18 | 19 | 欢迎关注我的公众号「编程拯救世界」(CodeWarrior_),加入编程世界一起冒险,一起成长! 20 | 21 | ![](./docs/_img/qrcode.png) 22 | 23 | 如果你对分享题解或做题感兴趣,欢迎加入[刷题小组](https://github.com/leetcode-notebook)。 24 | 25 | ## 基本概念 26 | 27 | (更新中……) 28 | 29 | - [基础算法](concept/base-algorithm/) 30 | 31 | ## 题解分类 32 | 33 | ### 数据结构 34 | 35 | * [数组](data-structure/array/) 36 | * [字符串](data-structure/string/) 37 | * [链表](data-structure/linked_list/) 38 | * 树 39 | * [递归](data-structure/tree/recursion/) 40 | * [层次遍历(BFS)](data-structure/tree/bfs/) 41 | * [前中后序遍历(DFS)](data-structure/tree/dfs/) 42 | * [字典树](data-struct/tree/trie/) 43 | * [其他](data-structure/tree/other/) 44 | * [堆](data-structure/heap/) 45 | * [栈](data-structure/stack/) 46 | * [哈希表](data-structure/hash/) 47 | 48 | ### 算法思想 49 | 50 | * [递归](algorithm/recursion/) 51 | * 排序 52 | * [堆排序](algorithm/sort/heap/) 53 | * [快速排序](algorithm/sort/quick/) 54 | * [冒泡排序](algorithm/sort/bubble/) 55 | * [其他](algorithm/sort/other/) 56 | * 搜索 57 | * [深度优先](algorithm/research/dfs/) 58 | * [广度优先](algorithm/research/bfs/) 59 | * [二分查找](algorithm/research/binary-search/) 60 | * [动态规划](algorithm/dynamic/) 61 | * [分治](algorithm/divide-and-conquer/) 62 | * [贪心](algorithm/greedy/) 63 | * [位运算](algorithm/bit/) 64 | * [数学题](algorithm/math/) 65 | * [回溯](algorithm/backtrack/) 66 | * [双指针](algorithm/double-pointer/) 67 | * [其他](algorithm/other/) 68 | 69 | ## 关于我们 70 | 71 | ### 🐱Jalan 72 | 73 | - 输出 Python(主)/PHP/Go/Swift 题解 74 | - [LeetCode 主页](https://leetcode-cn.com/jalan/):完成题解 **308**,参与竞赛 **22** 次 75 | - 博客:[忘归](http://jalan.space) 76 | 77 | ### 🎃Csming 78 | 79 | - 输出 Java 题解 80 | - [LeetCode 主页](https://leetcode-cn.com/u/csming1995/):完成题解 **315**,参与竞赛 **18** 次 81 | - 博客:[Csming](https://csming1995.github.io/) 82 | 83 | ## 如何贡献 84 | 85 | - 💪 项目相关规范见 [Wiki](https://github.com/JalanJiang/leetcode-notebook/wiki)。 86 | - 🐔 菜鸡一枚,有不足之处或有更好的解法欢迎各位大佬 Issue 或 Pull Request,感恩! 87 | 88 | ## 特别鸣谢 89 | 90 | - [LeetCode 中国](https://leetcode-cn.com/) 91 | - [CSNotes](https://cyc2018.github.io/CS-Notes/#/) 92 | - [OI Wiki](https://oi-wiki.org/basic/) 93 | - 陪我一起披荆斩棘的 [Csming](https://csming1995.github.io/) 94 | - 陪我一起喝酒吃肉的 [mymmon](https://segmentfault.com/u/mymmon) 95 | - 我的小猫咪 [薯条](http://jalan.space/cat) 🐱 -------------------------------------------------------------------------------- /answer/0027/main.go: -------------------------------------------------------------------------------- 1 | func removeElement(nums []int, val int) int { 2 | length := len(nums) 3 | if length == 0 { 4 | return 0 5 | } 6 | 7 | i := 0 8 | j := 0 9 | for j < length { 10 | if nums[j] == val { 11 | // 去找一个不是 val 的值 12 | j++ 13 | } else { 14 | // 互换 15 | nums[i], nums[j] = nums[j], nums[i] 16 | // i 在前进的过程中走的是 j 走过的路,一定不会再碰到 val 17 | i++ 18 | j++ 19 | } 20 | } 21 | 22 | return length - (j - i) 23 | } -------------------------------------------------------------------------------- /answer/0027/main.py: -------------------------------------------------------------------------------- 1 | # python3 2 | 3 | class Solution: 4 | def removeElement(self, nums, val): 5 | """ 6 | :type nums: List[int] 7 | :type val: int 8 | :rtype: int 9 | """ 10 | length = len(nums) 11 | i = 0 12 | j = 0 13 | while j < length: 14 | if nums[j] != val: 15 | nums[i] = nums[j] 16 | i = i + 1 17 | j = j + 1 18 | else: 19 | j = j + 1 20 | res = length - (j - i) 21 | return res -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JalanJiang/leetcode-notebook/8b55ef9d0ae6f7e025d27bdb9bf5d45f9c68e1cd/docs/.nojekyll -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | [![LICENSE](https://img.shields.io/badge/License-CC%20BY%203.0%20CN-lightgrey.svg)](https://creativecommons.org/licenses/by/3.0/cn/) 4 | [![LICENSE](https://img.shields.io/badge/license-Anti%20996-blue.svg)](https://github.com/996icu/996.ICU/blob/master/LICENSE) 5 | [![Badge](https://img.shields.io/badge/link-996.icu-red.svg)](https://996.icu/#/zh_CN) 6 | 7 | > 愿所有的热爱都能一直长久地燃烧下去。 8 | 9 | 点击此处:[在线阅读](http://jalan.space/leetcode-notebook/) 10 | 11 | ## Rum 12 | 13 | - 🐱 LeetCode 解题本,基于**算法与数据结构**,对做过的 LeetCode 练习题进行归纳与总结。 14 | - 🍸 项目代号 [Rum](https://zh.wikipedia.org/wiki/%E5%85%B0%E5%A7%86%E9%85%92)([朗姆酒](https://zh.wikipedia.org/wiki/%E5%85%B0%E5%A7%86%E9%85%92)),鸡尾酒的热带气息基酒,暗指「算法与数据结构是程序员的基底」。 15 | - 👩‍💻 题解主要由 Python 书写,包含少量 Java / Go / PHP / Swift 版本 16 | - ⚔️ 使用 [docsify](https://docsify.js.org/#/) 构建,搭建教程见 [《docsify 入坑指南与我放弃 Gitbook 的那些理由》](http://jalan.space/2019/06/21/2019/begin-docsify/) 17 | 18 | 欢迎关注我的公众号:`CodeWarrior_`。加入编程世界一起冒险,一起成长! 19 | 20 |
21 | 22 | ### 基本概念 23 | 24 | (更新中……) 25 | 26 | - [基础算法](concept/base-algorithm/) 27 | 28 | ### 题解分类 29 | 30 | #### 数据结构 31 | 32 | * [数组](data-structure/array/) 33 | * [字符串](data-structure/string/) 34 | * [链表](data-structure/linked_list/) 35 | * 树 36 | * [递归](data-structure/tree/recursion/) 37 | * [层次遍历(BFS)](data-structure/tree/bfs/) 38 | * [前中后序遍历(DFS)](data-structure/tree/dfs/) 39 | * [字典树](data-struct/tree/trie/) 40 | * [其他](data-structure/tree/other/) 41 | * [堆](data-structure/heap/) 42 | * [栈](data-structure/stack/) 43 | * [哈希表](data-structure/hash/) 44 | 45 | #### 算法思想 46 | 47 | * [递归](algorithm/recursion/) 48 | * 排序 49 | * [堆排序](algorithm/sort/heap/) 50 | * [快速排序](algorithm/sort/quick/) 51 | * [冒泡排序](algorithm/sort/bubble/) 52 | * [其他](algorithm/sort/other/) 53 | * 搜索 54 | * [深度优先](algorithm/research/dfs/) 55 | * [广度优先](algorithm/research/bfs/) 56 | * [二分查找](algorithm/research/binary-search/) 57 | * [动态规划](algorithm/dynamic/) 58 | * [分治](algorithm/divide-and-conquer/) 59 | * [贪心](algorithm/greedy/) 60 | * [位运算](algorithm/bit/) 61 | * [数学题](algorithm/math/) 62 | * [回溯](algorithm/backtrack/) 63 | * [双指针](algorithm/double-pointer/) 64 | * [其他](algorithm/other/) 65 | 66 | ## 题解目录 67 | 68 | 整理中…… 69 | 70 | | # | Title | Solution | Difficulty | 71 | | ---- | ---- | ---- | ---- | 72 | | 0027 | [移除元素](https://leetcode-cn.com/problems/remove-element/) | [Python](https://github.com/JalanJiang/leetcode-notebook/blob/master/answer/0027/main.py), [Go](https://github.com/JalanJiang/leetcode-notebook/blob/master/answer/0027/main.go) | Easy | 73 | 74 | ## 关于我们 75 | 76 | ### 🐱Jalan 77 | 78 | - 输出 Python(主)/PHP/Go/Swift 题解 79 | - [LeetCode 主页](https://leetcode-cn.com/jalan/):完成题解 **308**,参与竞赛 **22** 次 80 | - 博客:[忘归](http://jalan.space) 81 | 82 | ### 🎃Csming 83 | 84 | - 输出 Java 题解 85 | - [LeetCode 主页](https://leetcode-cn.com/u/csming1995/):完成题解 **315**,参与竞赛 **18** 次 86 | - 博客:[Csming](https://csming1995.github.io/) 87 | 88 | ## 如何贡献 89 | 90 | - 💪 项目相关规范见 [Wiki](https://github.com/JalanJiang/leetcode-notebook/wiki)。 91 | - 🐔 菜鸡一枚,有不足之处或有更好的解法欢迎各位大佬 Issue 或 Pull Request,感恩! 92 | 93 | ## 特别鸣谢 94 | 95 | - [LeetCode 中国](https://leetcode-cn.com/) 96 | - [CSNotes](https://cyc2018.github.io/CS-Notes/#/) 97 | - [OI Wiki](https://oi-wiki.org/basic/) 98 | - 陪我一起披荆斩棘的 [Csming](https://csming1995.github.io/) 99 | - 陪我一起喝酒吃肉的 [mymmon](https://segmentfault.com/u/mymmon) 100 | - 我的小猫咪 [薯条](http://jalan.space/cat) 🐱 -------------------------------------------------------------------------------- /docs/_coverpage.md: -------------------------------------------------------------------------------- 1 | ![logo](_img/icon.png) 2 | # RUM 3 | ## LeetCode 题解 4 | > 愿所有的热爱都能一直长久地燃烧下去。 5 | 6 | * 每日更新,欢迎 Watch~ 7 | * 每天一道算法题,和我一起变强吧! 8 | 9 | [GitHub](https://github.com/JalanJiang/leetcode-notebook) 10 | [Blog](http://jalan.space) -------------------------------------------------------------------------------- /docs/_img/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JalanJiang/leetcode-notebook/8b55ef9d0ae6f7e025d27bdb9bf5d45f9c68e1cd/docs/_img/icon.png -------------------------------------------------------------------------------- /docs/_img/qrcode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JalanJiang/leetcode-notebook/8b55ef9d0ae6f7e025d27bdb9bf5d45f9c68e1cd/docs/_img/qrcode.png -------------------------------------------------------------------------------- /docs/_img/questions/150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JalanJiang/leetcode-notebook/8b55ef9d0ae6f7e025d27bdb9bf5d45f9c68e1cd/docs/_img/questions/150.png -------------------------------------------------------------------------------- /docs/_img/questions/4-dp-state.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JalanJiang/leetcode-notebook/8b55ef9d0ae6f7e025d27bdb9bf5d45f9c68e1cd/docs/_img/questions/4-dp-state.png -------------------------------------------------------------------------------- /docs/_img/weekly-122.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JalanJiang/leetcode-notebook/8b55ef9d0ae6f7e025d27bdb9bf5d45f9c68e1cd/docs/_img/weekly-122.png -------------------------------------------------------------------------------- /docs/_img/weekly-131.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JalanJiang/leetcode-notebook/8b55ef9d0ae6f7e025d27bdb9bf5d45f9c68e1cd/docs/_img/weekly-131.png -------------------------------------------------------------------------------- /docs/_img/weekly-133.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JalanJiang/leetcode-notebook/8b55ef9d0ae6f7e025d27bdb9bf5d45f9c68e1cd/docs/_img/weekly-133.png -------------------------------------------------------------------------------- /docs/_img/weekly-134.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JalanJiang/leetcode-notebook/8b55ef9d0ae6f7e025d27bdb9bf5d45f9c68e1cd/docs/_img/weekly-134.png -------------------------------------------------------------------------------- /docs/_img/weekly-137.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JalanJiang/leetcode-notebook/8b55ef9d0ae6f7e025d27bdb9bf5d45f9c68e1cd/docs/_img/weekly-137.png -------------------------------------------------------------------------------- /docs/_img/weekly-138.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JalanJiang/leetcode-notebook/8b55ef9d0ae6f7e025d27bdb9bf5d45f9c68e1cd/docs/_img/weekly-138.png -------------------------------------------------------------------------------- /docs/_navbar.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JalanJiang/leetcode-notebook/8b55ef9d0ae6f7e025d27bdb9bf5d45f9c68e1cd/docs/_navbar.md -------------------------------------------------------------------------------- /docs/_sidebar.md: -------------------------------------------------------------------------------- 1 | * [一、简介](/) 2 | * 二、概念 3 | * [2.1 数据结构](concept/data-structure/) 4 | * [2.2 基础算法](concept/base-algorithm/) 5 | * [2.3 排序专题](concept/sort/) 6 | * 三、例题 7 | * 3.1 数据结构 8 | * [数组](data-structure/array/) 9 | * [字符串](data-structure/string/) 10 | * [链表](data-structure/linked_list/) 11 | * 树 12 | * [递归](data-structure/tree/recursion/) 13 | * [层次遍历(BFS)](data-structure/tree/bfs/) 14 | * [前中后序遍历(DFS)](data-structure/tree/dfs/) 15 | * [字典树](data-structure/tree/trie/) 16 | * [其他](data-structure/tree/other/) 17 | * [堆](data-structure/heap/) 18 | * [栈](data-structure/stack/) 19 | * [哈希表](data-structure/hash/) 20 | * 3.2 算法思想 21 | * [递归](algorithm/recursion/) 22 | * 排序 23 | * [堆排序](algorithm/sort/heap/) 24 | * [快速排序](algorithm/sort/quick/) 25 | * [冒泡排序](algorithm/sort/bubble/) 26 | * [其他](algorithm/sort/other/) 27 | * 搜索 28 | * [深度优先](algorithm/research/dfs/) 29 | * [广度优先](algorithm/research/bfs/) 30 | * [二分查找](algorithm/research/binary-search/) 31 | * [动态规划](algorithm/dynamic/) 32 | * [分治](algorithm/divide-and-conquer/) 33 | * [贪心](algorithm/greedy/) 34 | * [位运算](algorithm/bit/) 35 | * [数学题](algorithm/math/) 36 | * [回溯](algorithm/backtrack/) 37 | * [双指针](algorithm/double-pointer/) 38 | * [滑动窗口](algorithm/sliding-window/) 39 | * [其他](algorithm/other/) 40 | * [3.3 设计](design/README.md) 41 | * 3.3 竞赛 42 | * [周赛](weekly/) 43 | * [双周赛](biweekly/) 44 | * [其他](other/) 45 | * [3.4 剑指 offer 系列](offer/) -------------------------------------------------------------------------------- /docs/algorithm/backtrack/README.md: -------------------------------------------------------------------------------- 1 | ## 39. 组合总和 2 | 3 | [原题链接](https://leetcode-cn.com/problems/combination-sum/) 4 | 5 | ### 思路 6 | 7 | 回溯法。 8 | 9 | 1. 先对 `candidates` 进行递增排序 10 | 2. 外层循环遍历排序后的 `candidates`, 11 | 1. 如果遍历到的数 `nums[i] == target`,则直接把 `nums[i]` 添加到结果列表中 12 | 2. 如果遍历到的数 `nums[i] != target`,则进入 `backtrack` 递归 13 | 14 | 然后说一下 `backtrack`: 15 | 16 | 1. 遍历 `candidates`,遍历范围为 `nums[i]` 的下标 `i` 到 `len(candidates) - 1` 17 | 2. 判断剩余值: 18 | 1. 如果等于 0,则加入结果列表,结束递归 19 | 2. 如果大于 0,则继续递归 20 | 3. 如果小于 0,则结束递归 21 | 22 | ```python 23 | class Solution(object): 24 | def combinationSum(self, candidates, target): 25 | """ 26 | :type candidates: List[int] 27 | :type target: int 28 | :rtype: List[List[int]] 29 | """ 30 | 31 | nums = sorted(candidates) 32 | res = [] 33 | for i in range(len(nums)): 34 | # self.backtrack(target, i, [], nums, res) 35 | if nums[i] == target: 36 | res.append([nums[i]]) 37 | else: 38 | self.backtrack(target - nums[i], i, [nums[i]], nums, res) 39 | return res 40 | 41 | 42 | def backtrack(self, cur, index, combination, nums, res): 43 | for i in range(index, len(nums)): 44 | num = nums[i] 45 | tmp = cur - num 46 | if tmp == 0: 47 | combination.append(num) 48 | res.append(combination) 49 | return 50 | elif tmp > 0: 51 | # 继续往下走 52 | self.backtrack(tmp, i, combination + [num], nums, res) 53 | else: 54 | return 55 | ``` 56 | 57 | ## 46. 全排列 58 | 59 | [原题链接](https://leetcode-cn.com/problems/permutations/) 60 | 61 | ### 思路:回溯 62 | 63 | [参考题解](https://leetcode-cn.com/problems/permutations/solution/hui-su-suan-fa-xiang-jie-by-labuladong-2/) 64 | 65 | ```python 66 | class Solution: 67 | def permute(self, nums: List[int]) -> List[List[int]]: 68 | def track_back(nums, track): 69 | if len(nums) == len(track): 70 | ans.append(track[:]) 71 | # 遍历集合 72 | for n in nums: 73 | if n in track: 74 | # 已经在决策树中 75 | continue 76 | # 加入决策 77 | track.append(n) 78 | track_back(nums, track) 79 | # 回溯 80 | track.pop() 81 | 82 | ans = [] 83 | track = [] 84 | track_back(nums, track) 85 | return ans 86 | ``` 87 | 88 | ## 216. 组合总和 III 89 | 90 | [原题链接](https://leetcode-cn.com/problems/combination-sum-iii/) 91 | 92 | 回溯模板: 93 | 94 | ``` 95 | backtracking() { 96 | if (终止条件) { 97 | 存放结果; 98 | } 99 | 100 | for (选择:选择列表(可以想成树中节点孩子的数量)) { 101 | 递归,处理节点; 102 | backtracking(); 103 | 回溯,撤销处理结果 104 | } 105 | } 106 | ``` 107 | 108 | ### 题解 109 | 110 | 111 | 112 | #### **Python** 113 | 114 | ```python 115 | class Solution: 116 | def combinationSum3(self, k: int, n: int) -> List[List[int]]: 117 | ans = [] 118 | ''' 119 | element: 数组内答案 120 | start: 遍历的开始位置 121 | num: 剩余数字 122 | ''' 123 | def dfs(element, start, num): 124 | # 符合条件,加入最终答案,结束递归 125 | if len(element) == k or num < 0: 126 | if len(element) == k and num == 0: 127 | # print(element) 128 | # 深拷贝 129 | ans.append(element[:]) 130 | return 131 | 132 | for i in range(start, 10): 133 | # 加入当前值 134 | element.append(i) 135 | # 递归 136 | dfs(element, i + 1, num - i) 137 | # 撤销选择,即回溯 138 | element.pop() 139 | 140 | dfs([], 1, n) 141 | return ans 142 | ``` 143 | 144 | #### **Go** 145 | 146 | ```go 147 | func combinationSum3(k int, n int) [][]int { 148 | ans := [][]int{} 149 | dfs(&ans, []int{}, 1, n, k) 150 | return ans 151 | } 152 | 153 | func dfs(ans *[][]int, element []int, start int, num int, k int) { 154 | if len(element) == k || num <= 0 { 155 | if len(element) == k && num == 0 { 156 | temp := make([]int, k) 157 | copy(temp, element) 158 | *ans = append(*ans, temp) 159 | } 160 | return 161 | } 162 | 163 | for i:=start; i < 10; i++ { 164 | element = append(element, i) 165 | dfs(ans, element, i + 1, num - i, k) 166 | element = element[:len(element) - 1] 167 | } 168 | } 169 | ``` 170 | 171 | 172 | 173 | ## 357. 计算各个位数不同的数字个数 174 | 175 | [原题链接](https://leetcode-cn.com/problems/count-numbers-with-unique-digits/) 176 | 177 | ### 解法一:回溯 178 | 179 | 用 `tags` 数组标记 0~9 数字出现的次数,再调用递归后进行回溯。注意对 0 进行特殊处理。 180 | 181 | ```python 182 | class Solution: 183 | ans = 0 184 | tags = [] # 记录数字出现的次数 185 | def countNumbersWithUniqueDigits(self, n: int) -> int: 186 | # 0-9 的数字 187 | self.tags = [0 for _ in range(10)] 188 | # 枚举所有数字:回溯 189 | """ 190 | r: 轮次 191 | num: 数字 192 | """ 193 | def dfs(r, num = 0): 194 | if r <= 0: 195 | # 结束递归 196 | return 197 | 198 | for i in range(10): 199 | # 循环 0-9 200 | if num % 10 != i and self.tags[i] == 0: 201 | # 条件枝剪 202 | self.ans += 1 203 | # 数字出现标记 204 | self.tags[i] = 1 205 | dfs(r - 1, num * 10 + i) 206 | # 回溯 207 | self.tags[i] = 0 208 | 209 | dfs(n) 210 | # 补充 0 211 | return self.ans + 1 212 | ``` 213 | 214 | ### 解法二:动态规划 215 | 216 | 排列组合。 217 | 218 | - `f(0) = 1` 219 | - `f(1) = 9` 220 | - `f(2) = 9 * 9 + f(1)` 221 | - 第一个数字选择 1~9 222 | - 第二个数在 0~9 中选择和第一个数不同的数 223 | - `f(3) = 9 * 9 * 8 + f(2)` 224 | 225 | 可以推出动态规划方程: 226 | 227 | ``` 228 | dp[i] = sum + dp[i - 1] 229 | ``` 230 | 231 | ```python 232 | class Solution: 233 | def countNumbersWithUniqueDigits(self, n: int) -> int: 234 | dp = [0 for _ in range(n + 1)] 235 | # n = 1 时 236 | dp[0] = 1 237 | 238 | for i in range(1, n + 1): 239 | s = 9 240 | for j in range(1, i): 241 | s *= (10 - j) 242 | dp[i] = s + dp[i - 1] 243 | 244 | return dp[n] 245 | ``` -------------------------------------------------------------------------------- /docs/algorithm/bit/README.md: -------------------------------------------------------------------------------- 1 | ## 136. 只出现一次的数字 2 | 3 | [原题链接](https://leetcode-cn.com/problems/single-number/comments/) 4 | 5 | ### 关键词 6 | 7 | - 异或 8 | 9 | ### 解法一 10 | 11 | 使用额外空间。 12 | 13 | ```Python 14 | class Solution(object): 15 | def singleNumber(self, nums): 16 | """ 17 | :type nums: List[int] 18 | :rtype: int 19 | """ 20 | d = set() 21 | for n in nums: 22 | if n in d: 23 | d.remove(n) 24 | else: 25 | d.add(n) 26 | return d.pop() 27 | ``` 28 | 29 | #### Python 字典 30 | 31 | - 创建:`d = dict()` 32 | - 添加:`add()` 33 | - 随机弹出:`pop()` 34 | 35 | ### 解法二 36 | 37 | 巧妙使用异或运算: 38 | 39 | - 相同为 0,不同为 1 40 | - 0 与任何数异或都等于该数本身 41 | 42 | 43 | 44 | #### **Python** 45 | 46 | ```python 47 | class Solution: 48 | def singleNumber(self, nums): 49 | """ 50 | :type nums: List[int] 51 | :rtype: int 52 | """ 53 | a = 0 54 | for num in nums: 55 | a = a ^ num 56 | return a 57 | ``` 58 | 59 | #### **Go** 60 | 61 | ```go 62 | func singleNumber(nums []int) int { 63 | ans := 0 64 | for _, n := range nums { 65 | ans ^= n 66 | } 67 | return ans 68 | } 69 | ``` 70 | 71 | 72 | 73 | 74 | ## 190. 颠倒二进制位 75 | 76 | [原题链接](https://leetcode-cn.com/problems/reverse-bits/) 77 | 78 | ### 解一 79 | 80 | Python 一行代码: 81 | 82 | 1. `n` 转为二进制并去掉前缀 `0b` 83 | 2. 左侧填充 0 到 32 位 84 | 3. 翻转字符串 85 | 4. 转为十进制表示 86 | 87 | 知识点: 88 | 89 | - [Python bin() 函数](https://www.runoob.com/python/python-func-bin.html) 90 | - [Python zfill()方法](https://www.runoob.com/python/att-string-zfill.html) 91 | - [python进制转换(二进制、十进制和十六进制)及注意事项](https://m.pythontab.com/article/86) 92 | 93 | ``` 94 | class Solution: 95 | # @param n, an integer 96 | # @return an integer 97 | def reverseBits(self, n): 98 | return int(bin(n)[2:].zfill(32)[::-1], base=2) 99 | ``` 100 | 101 | ### 解二 102 | 103 | 二进制移位。 104 | 105 | 取出 n 的最低位,加入结果 res 中。然后 n 右移,res 左移。循环此过程。 106 | 107 | ```python 108 | class Solution: 109 | # @param n, an integer 110 | # @return an integer 111 | def reverseBits(self, n): 112 | res = 0 113 | count = 32 114 | 115 | while count: 116 | res <<= 1 117 | # 取出 n 的最低位数加到 res 中 118 | res += n&1 119 | n >>= 1 120 | count -= 1 121 | 122 | return int(bin(res), 2) 123 | ``` 124 | 125 | 126 | ## 191. 位1的个数 127 | 128 | [原题链接](https://leetcode-cn.com/problems/number-of-1-bits/) 129 | 130 | ### 解一 131 | 132 | 调用函数懒蛋法。 133 | 134 | ```python 135 | class Solution(object): 136 | def hammingWeight(self, n): 137 | """ 138 | :type n: int 139 | :rtype: int 140 | """ 141 | return str(bin(n)).count('1') 142 | ``` 143 | 144 | ### 解二 145 | 146 | 手动循环计算 1 的个数。 147 | 148 | ```python 149 | class Solution(object): 150 | def hammingWeight(self, n): 151 | """ 152 | :type n: int 153 | :rtype: int 154 | """ 155 | n = bin(n) 156 | count = 0 157 | for c in n: 158 | if c == "1": 159 | count += 1 160 | return count 161 | ``` 162 | 163 | ### 解三 164 | 165 | 十进制转二进制的方式。每次对 2 取余判断是否是 1,是的话就 `count = count + 1`。 166 | 167 | ```python 168 | class Solution(object): 169 | def hammingWeight(self, n): 170 | """ 171 | :type n: int 172 | :rtype: int 173 | """ 174 | count = 0 175 | while n: 176 | res = n % 2 177 | if res == 1: 178 | count += 1 179 | n //= 2 180 | return count 181 | ``` 182 | 183 | ### 解四 184 | 185 | 位运算法。 186 | 187 | 把 `n` 与 `1` 进行与运算,将得到 `n` 的最低位数字。因此可以取出最低位数,再将 `n` 右移一位。循环此步骤,直到 `n` 等于零。 188 | 189 | ```python 190 | class Solution(object): 191 | def hammingWeight(self, n): 192 | """ 193 | :type n: int 194 | :rtype: int 195 | """ 196 | count = 0 197 | while n: 198 | count += n&1 199 | n >>= 1 200 | return count 201 | ``` 202 | 203 | ## 289. 生命游戏 204 | 205 | [原题链接](https://leetcode-cn.com/problems/game-of-life/) 206 | 207 | ### 思路 208 | 209 | 用两位二进制代表示状态: 210 | 211 | - 高位:下一个状态 212 | - 低位:现在的状态 213 | 214 | 因此,初始状态为 `00` 与 `01`。 215 | 216 | 步骤: 217 | 218 | 1. 遍历矩阵,更新高位 219 | 2. 再次遍历矩阵,更新最终值 220 | 221 | ```python 222 | class Solution: 223 | def gameOfLife(self, board: List[List[int]]) -> None: 224 | """ 225 | Do not return anything, modify board in-place instead. 226 | """ 227 | m = len(board) 228 | n = len(board[0]) 229 | # 更新矩阵 230 | for i in range(m): 231 | for j in range(n): 232 | lives = self.getLives(board, i, j) 233 | if board[i][j] & 1 == 1: 234 | # 活细胞 235 | if lives == 2 or lives == 3: 236 | board[i][j] = 3 # 01->11 237 | else: 238 | # 死细胞 239 | if lives == 3: 240 | board[i][j] = 2 # 00->10 241 | 242 | # 最终矩阵 243 | for i in range(m): 244 | for j in range(n): 245 | board[i][j] = board[i][j] >> 1 246 | 247 | def getLives(self, board, i, j): 248 | """ 249 | 计算活细胞个数 250 | """ 251 | m = len(board) 252 | n = len(board[0]) 253 | # 8 个方向 254 | directions = [[0, 1], [0, -1], [1, 0], [-1, 0], [1, 1], [-1, -1], [1, -1], [-1, 1]] 255 | 256 | lives = 0 257 | 258 | for direction in directions: 259 | d_x = direction[0] + i 260 | d_y = direction[1] + j 261 | 262 | if (d_x >= 0 and d_x < m) and (d_y >= 0 and d_y < n): 263 | if board[d_x][d_y] & 1 == 1: 264 | lives += 1 265 | 266 | return lives 267 | ``` 268 | 269 | ## 371. 两整数之和 270 | 271 | [原题链接](https://leetcode-cn.com/problems/sum-of-two-integers/) 272 | 273 | ### 思路 274 | 275 | 题目说不能使用运算符 `+` 和 `-`,那么我们就要使用其他方式来替代这两个运算符的功能。 276 | 277 | ### 位运算中的加法 278 | 279 | 我们先来观察下位运算中的两数加法,其实来来回回就只有下面这四种: 280 | 281 | ``` 282 | 0 + 0 = 0 283 | 0 + 1 = 1 284 | 1 + 0 = 1 285 | 1 + 1 = 0(进位 1) 286 | ``` 287 | 288 | 仔细一看,这可不就是相同位为 0,不同位为 1 的**异或运算**结果嘛~ 289 | 290 | ### 异或和与运算操作 291 | 292 | 我们知道,在位运算操作中,**异或**的一个重要特性是**无进位加法**。我们来看一个例子: 293 | 294 | ``` 295 | a = 5 = 0101 296 | b = 4 = 0100 297 | 298 | a ^ b 如下: 299 | 300 | 0 1 0 1 301 | 0 1 0 0 302 | ------- 303 | 0 0 0 1 304 | ``` 305 | 306 | `a ^ b` 得到了一个**无进位加法**结果,如果要得到 `a + b` 的最终值,我们还要找到**进位**的数,把这二者相加。在位运算中,我们可以使用**与**操作获得进位: 307 | 308 | ``` 309 | a = 5 = 0101 310 | b = 4 = 0100 311 | 312 | a & b 如下: 313 | 314 | 0 1 0 1 315 | 0 1 0 0 316 | ------- 317 | 0 1 0 0 318 | ``` 319 | 320 | 由计算结果可见,`0100` 并不是我们想要的进位,`1 + 1` 所获得的进位应该要放置在它的更高位,即左侧位上,因此我们还要把 `0100` 左移一位,才是我们所要的进位结果。 321 | 322 | 那么问题就容易了,总结一下: 323 | 324 | 1. `a + b` 的问题拆分位 `(a 和 b 的无进位结果) + (a 和 b 的进位结果)` 325 | 2. 无进位加法使用**异或运算**计算得出 326 | 3. 进位结果使用**与运算**和**移位运算**计算得出 327 | 4. 循环此过程,直到进位为 0 328 | 329 | ### 在 Python 中的特殊处理 330 | 331 | 在 Python 中,整数不是 32 位的,也就是说你一直循环左移并不会存在溢出的现象,这就需要我们手动对 Python 中的整数进行处理,手动模拟 32 位 INT 整型。 332 | 333 | 具体做法是将整数对 `0x100000000` 取模,保证该数从 32 位开始到最高位都是 0。 334 | 335 | ### 具体实现 336 | 337 | ```python 338 | class Solution(object): 339 | def getSum(self, a, b): 340 | """ 341 | :type a: int 342 | :type b: int 343 | :rtype: int 344 | """ 345 | # 2^32 346 | MASK = 0x100000000 347 | # 整型最大值 348 | MAX_INT = 0x7FFFFFFF 349 | MIN_INT = MAX_INT + 1 350 | while b != 0: 351 | # 计算进位 352 | carry = (a & b) << 1 353 | # 取余范围限制在 [0, 2^32-1] 范围内 354 | a = (a ^ b) % MASK 355 | b = carry % MASK 356 | return a if a <= MAX_INT else ~((a % MIN_INT) ^ MAX_INT) 357 | ``` 358 | 359 | 当然,如果你在 Python 中想要偷懒也行,毕竟 life is short…… 360 | 361 | ```python 362 | class Solution(object): 363 | def getSum(self, a, b): 364 | """ 365 | :type a: int 366 | :type b: int 367 | :rtype: int 368 | """ 369 | return sum([a, b]) 370 | ``` 371 | 372 | 373 | ## 461. 汉明距离 374 | 375 | [原题链接](https://leetcode-cn.com/problems/hamming-distance/comments/) 376 | 377 | ### 思路 378 | 379 | 异或运算:对应位置相同为 0,不同为 1。计算 1 的个数即可。 380 | 381 | ```python 382 | class Solution(object): 383 | def hammingDistance(self, x, y): 384 | """ 385 | :type x: int 386 | :type y: int 387 | :rtype: int 388 | """ 389 | 390 | string = bin(x ^ y) 391 | count = 0 392 | 393 | for s in string: 394 | if s == "1": 395 | count += 1 396 | 397 | return count 398 | ``` 399 | 400 | 看到评论里大佬的一行写法:`return bin(x ^ y).count('1')`。 401 | 402 | 403 | -------------------------------------------------------------------------------- /docs/algorithm/divide-and-conquer/README.md: -------------------------------------------------------------------------------- 1 | ## 23. 合并K个排序链表 2 | 3 | [原题链接](https://leetcode-cn.com/problems/merge-k-sorted-lists/) 4 | 5 | ### 解一:顺序合并 6 | 7 | ```python 8 | # Definition for singly-linked list. 9 | # class ListNode: 10 | # def __init__(self, x): 11 | # self.val = x 12 | # self.next = None 13 | 14 | class Solution: 15 | def mergeKLists(self, lists: List[ListNode]) -> ListNode: 16 | length = len(lists) 17 | i = 0 18 | ans = ListNode(0) 19 | head = ans 20 | while len(lists) > 0: 21 | min_index = 0 22 | min_val = float('inf') 23 | min_node = None 24 | for i in range(len(lists)): 25 | # 遍历数组 26 | node = lists[i] 27 | if node is None: 28 | continue 29 | if node.val < min_val: 30 | min_index = i 31 | min_val = node.val 32 | min_node = node 33 | # 最小值进入链表 34 | if head is not None: 35 | head.next = min_node 36 | head = head.next 37 | # 处理最小值 38 | # next_node = min_node.next 39 | if min_node is None or min_node.next is None: 40 | del lists[min_index] 41 | else: 42 | lists[min_index] = min_node.next 43 | return ans.next 44 | ``` 45 | 46 | k 个链表,n 个节点 47 | 48 | - 时间复杂度:$O(k*n)$ 49 | - 空间复杂度:$O(n)$ 50 | 51 | ## 241. 为运算表达式设计优先级 52 | 53 | [原题链接](https://leetcode-cn.com/problems/different-ways-to-add-parentheses/) 54 | 55 | ### 思路 56 | 57 | 分治 + 递归。 58 | 59 | 遇到运算符时: 60 | 61 | 1. 计算运算符左边的结果集 62 | 2. 计算运算符右边的结果集 63 | 64 | 65 | 66 | #### ** Python ** 67 | 68 | ```python 69 | class Solution: 70 | def diffWaysToCompute(self, input: str) -> List[int]: 71 | # 如果只有数字,直接返回 72 | if input.isdigit(): 73 | return [int(input)] 74 | 75 | res = [] 76 | for i, char in enumerate(input): 77 | if char in ['+', '-', '*']: 78 | # 遇到运算符,计算左右两侧的结果集 79 | left = self.diffWaysToCompute(input[:i]) 80 | right = self.diffWaysToCompute(input[i+1:]) 81 | for l in left: 82 | for r in right: 83 | if char == '+': 84 | res.append(l + r) 85 | elif char == '-': 86 | res.append(l - r) 87 | else: 88 | res.append(l * r) 89 | 90 | return res 91 | ``` 92 | 93 | #### ** Java ** 94 | 95 | ```java 96 | class Solution { 97 | public List diffWaysToCompute(String input) { 98 | return partition(input); 99 | } 100 | 101 | private List partition(String input) { 102 | List result = new LinkedList<>(); 103 | if (!input.contains("+") && !input.contains("-") && !input.contains("*")) { 104 | result.add(Integer.valueOf(input)); 105 | return result; 106 | } 107 | 108 | for (int i = 0; i < input.length(); i++) { 109 | char value = input.charAt(i); 110 | if (value == '+' || value == '-' || value == '*') { 111 | for (int a: partition(input.substring(0, i))) { 112 | for (int b: partition(input.substring(i + 1))) { 113 | if (value == '+') { 114 | result.add(a + b); 115 | } else if (value == '-') { 116 | result.add(a - b); 117 | } else { 118 | result.add(a * b); 119 | } 120 | } 121 | } 122 | } 123 | } 124 | return result; 125 | } 126 | } 127 | ``` 128 | 129 | #### **Go** 130 | 131 | ```go 132 | import ( 133 | "fmt" 134 | "strconv" 135 | ) 136 | 137 | func diffWaysToCompute(input string) []int { 138 | // 如果是数字,直接返回 139 | if isDigit(input) { 140 | tmp, _ := strconv.Atoi(input) 141 | return []int{tmp} 142 | } 143 | 144 | // 空切片 145 | var res []int 146 | // 遍历字符串 147 | for index, c := range input { 148 | tmpC := string(c) 149 | fmt.Print(tmpC) 150 | if tmpC == "+" || tmpC == "-" || tmpC == "*" { 151 | // 如果是运算符,则计算左右两边的算式 152 | left := diffWaysToCompute(input[:index]) 153 | right := diffWaysToCompute(input[index+1:]) 154 | 155 | for _, leftNum := range left { 156 | for _, rightNum := range right { 157 | var addNum int 158 | if tmpC == "+" { 159 | addNum = leftNum + rightNum 160 | } else if tmpC == "-" { 161 | addNum = leftNum - rightNum 162 | } else { 163 | addNum = leftNum * rightNum 164 | } 165 | res = append(res, addNum) 166 | } 167 | } 168 | } 169 | } 170 | 171 | return res 172 | } 173 | 174 | // 判断是否为全数字 175 | func isDigit(input string) bool { 176 | _, err := strconv.Atoi(input) 177 | if err != nil { 178 | return false 179 | } 180 | return true 181 | } 182 | ``` 183 | 184 | 185 | 186 | ## 395. 至少有K个重复字符的最长子串 187 | 188 | [原题链接](https://leetcode-cn.com/problems/longest-substring-with-at-least-k-repeating-characters/) 189 | 190 | ### 思路 191 | 192 | 分治。 193 | 194 | 核心思想:如果某个字符 `x` 在整个字符串中出现的次数 ` right: 214 | return 0 215 | 216 | # 用字典存储所有字符出现的情况 217 | hash_map = dict() 218 | for i in range(left, right + 1): 219 | c = s[i] 220 | if hash_map.get(c, None) == None: 221 | hash_map[c] = [] 222 | hash_map[c].append(i) 223 | 224 | for key in hash_map.keys(): 225 | # 某个字符出现的次数 226 | counter = len(hash_map[key]) 227 | # 如果字符出现次数 < k,那么该字符不可能出现在最终要求的子串中,在该字符的位置对原字符串进行截断 228 | if counter > 0 and counter < k: 229 | # 该字符首次出现的位置 230 | pos = hash_map[key][0] 231 | # 根据该字符的位置对字符串进行截断,判断左右两个截断的字字符哪个更长 232 | return max(find(s, k, left, pos - 1), find(s, k, pos + 1, right)) 233 | 234 | return right - left + 1 235 | 236 | return find(s, k, 0, len(s) - 1) 237 | ``` 238 | 239 | ## 932. 漂亮数组 240 | 241 | [原题链接](https://leetcode-cn.com/problems/beautiful-array/) 242 | 243 | ### 思路 244 | 245 | ```python 246 | class Solution: 247 | def beautifulArray(self, N: int) -> List[int]: 248 | m = {1: [1]} 249 | def f(N): 250 | if N not in m: 251 | odds = f((N + 1) // 2) 252 | evens = f(N // 2) 253 | m[N] = [2 * x - 1 for x in odds] + [2 * x for x in evens] 254 | return m[N] 255 | return f(N) 256 | ``` -------------------------------------------------------------------------------- /docs/algorithm/double-pointer/README.md: -------------------------------------------------------------------------------- 1 | ## 16. 最接近的三数之和 2 | 3 | [原题链接](https://leetcode-cn.com/problems/3sum-closest/) 4 | 5 | ### 思路 6 | 7 | 排序 + 双指针。 8 | 9 | 10 | 11 | #### ** Python ** 12 | 13 | ```python 14 | class Solution: 15 | def threeSumClosest(self, nums: List[int], target: int) -> int: 16 | length = len(nums) 17 | if length < 3: 18 | return 0 19 | # 排序 20 | nums.sort() 21 | ans = nums[0] + nums[1] + nums[2] 22 | for i in range(length): 23 | num = nums[i] 24 | start = i + 1 25 | end = length - 1 26 | while start < end: 27 | s = num + nums[start] + nums[end] 28 | # 如果计算出的和 s 更接近 target,则更新 ans 29 | if abs(s - target) < abs(ans - target): 30 | ans = s 31 | if s < target: 32 | start += 1 33 | elif s > target: 34 | end -= 1 35 | else: 36 | return ans 37 | return ans 38 | ``` 39 | 40 | #### ** PHP ** 41 | 42 | ```php 43 | class Solution { 44 | 45 | /** 46 | * @param Integer[] $nums 47 | * @param Integer $target 48 | * @return Integer 49 | */ 50 | function threeSumClosest($nums, $target) { 51 | $length = count($nums); 52 | if ($length < 3) { 53 | return 0; 54 | } 55 | sort($nums); 56 | $ans = $nums[0] + $nums[1] + $nums[2]; 57 | $sum = 0; 58 | for ($i = 0; $i < $length; $i++) { 59 | $num = $nums[$i]; 60 | $start = $i + 1; 61 | $end = $length - 1; 62 | while($start < $end) { 63 | $sum = $num + $nums[$start] + $nums[$end]; 64 | if(abs($sum - $target) < abs($ans - $target)) { 65 | $ans = $sum; 66 | } 67 | if ($sum < $target) { 68 | $start++; 69 | } elseif ($sum > $target) { 70 | $end--; 71 | } else { 72 | return $ans; 73 | } 74 | } 75 | } 76 | return $ans; 77 | } 78 | } 79 | ``` 80 | 81 | 82 | 83 | ## 18. 四数之和 84 | 85 | [原题链接](https://leetcode-cn.com/problems/4sum/) 86 | 87 | ### 思路 88 | 89 | 和三数之和思路差不多,只不过改为固定两个位置,然后再加上双指针。 90 | 91 | 注意一些枝剪条件,可以让算法更快。 92 | 93 | ```python 94 | class Solution: 95 | def fourSum(self, nums: List[int], target: int) -> List[List[int]]: 96 | length = len(nums) 97 | if length < 4: 98 | return [] 99 | nums.sort() 100 | res = [] 101 | # 双重循环固定两个数 102 | # 外层循环,固定第一个数 103 | for i in range(length - 3): 104 | 105 | # 枝剪 1:第一个数字遇到重复值时跳出循环,防止出现重复结果 106 | if i > 0 and nums[i] == nums[i - 1]: 107 | continue 108 | # 枝剪 2: 最小的和大于 target 跳出循环 109 | if nums[i] + nums[i + 1] + nums[i + 2] + nums[i + 3] > target: 110 | break 111 | # 枝剪 3: 当前 i 还太小,寻找下一个 112 | if nums[i] + nums[length - 1] + nums[length - 2] + nums[length - 3] < target: 113 | continue 114 | 115 | a = nums[i] 116 | # 内层循环,固定第二个数 117 | for j in range(i + 1, length - 2): 118 | 119 | # 枝剪 1 120 | if j - i > 1 and nums[j] == nums[j - 1]: 121 | continue 122 | # 枝剪 2 123 | if nums[i] + nums[j] + nums[j + 1] + nums[j + 2] > target: 124 | break 125 | # 枝剪 3 126 | if nums[i] + nums[j] + nums[length - 1] + nums[length - 2] < target: 127 | continue 128 | 129 | b = nums[j] 130 | # 双指针 131 | left = j + 1 132 | right = length - 1 133 | 134 | while left < right: 135 | s = a + b + nums[left] + nums[right] 136 | # 结果等于目标值 137 | if s == target: 138 | res.append([a, b, nums[left], nums[right]]) 139 | # 继续缩小两指针范围,寻找下一个目标 140 | while left < right and nums[left] == nums[left + 1]: 141 | left += 1 142 | while left < right and nums[right] == nums[right - 1]: 143 | right -= 1 144 | left += 1 145 | right -= 1 146 | elif s > target: 147 | right -= 1 148 | else: 149 | left += 1 150 | 151 | return res 152 | ``` 153 | 154 | ## 392. 判断子序列 155 | 156 | [原题链接](https://leetcode-cn.com/problems/is-subsequence/) 157 | 158 | ### 思路 159 | 160 | 双指针。 161 | 162 | 指针 `i` 指向 `s` 第一个字符,指针 `j` 指向 `t` 第一个字符。逐一判断 `i` 所指向的字符是否在 `t` 中存在。 163 | 164 | - 如果 `s[i] != t[j]`:`j = j + 1`,继续比对下一个字符 165 | - 如果 `s[i] == t[j]`:`i = i + 1`,`j = j + 1`,继续进行下一个 `s[i]` 的查找 166 | 167 | 168 | 169 | #### ** Python ** 170 | 171 | ```python 172 | class Solution(object): 173 | def isSubsequence(self, s, t): 174 | """ 175 | :type s: str 176 | :type t: str 177 | :rtype: bool 178 | """ 179 | s_length = len(s) 180 | t_length = len(t) 181 | 182 | i = 0 183 | j = 0 184 | 185 | while i < s_length and j < t_length: 186 | if s[i] == t[j]: 187 | i += 1 188 | j += 1 189 | 190 | return i == s_length 191 | ``` 192 | 193 | #### ** Swift ** 194 | 195 | ```swift 196 | class Solution { 197 | func isSubsequence(_ s: String, _ t: String) -> Bool { 198 | var sLength = s.count 199 | var tLength = t.count 200 | 201 | var i = 0 202 | var j = 0 203 | 204 | var sArray = Array(s) 205 | var tArray = Array(t) 206 | 207 | while i < sLength && j < tLength { 208 | var sc = sArray[i] 209 | var tc = tArray[j] 210 | 211 | if sc == tc { 212 | i += 1 213 | } 214 | j += 1 215 | } 216 | 217 | return i == sLength 218 | } 219 | } 220 | ``` 221 | 222 | ## 763. 划分字母区间 223 | 224 | [原题链接](https://leetcode-cn.com/problems/partition-labels/) 225 | 226 | ### 思路 227 | 228 | - 哈希 229 | - 双指针 230 | 231 | ```python 232 | class Solution: 233 | def partitionLabels(self, S: str) -> List[int]: 234 | # 字母最后出现的位置 235 | letter_ends = dict() 236 | for i in range(len(S)): 237 | s = S[i] 238 | letter_ends[s] = i 239 | 240 | ans = list() 241 | start = 0 242 | 243 | while start < len(S): 244 | begin = start 245 | # 字母最后出现的位置 246 | end = letter_ends[S[start]] 247 | while start < end: 248 | letter = S[start] 249 | letter_end = letter_ends[letter] 250 | # 如果字母最后出现位置大于 end,对 end 进行更新 251 | if letter_end > end: 252 | end = letter_end 253 | start += 1 254 | ans.append(end - begin + 1) 255 | start = end + 1 256 | 257 | return ans 258 | ``` 259 | 260 | ### 复杂度 261 | 262 | - 时间复杂度:`O(n)` 263 | - 空间复杂度:`O(26)` 264 | 265 | 266 | 267 | ## 面试题 10.01. 合并排序的数组 268 | 269 | [原题链接](https://leetcode-cn.com/problems/sorted-merge-lcci/) 270 | 271 | ### 逆向双指针 272 | 273 | - 定义两个指针 `cur_a` 与 `cur_b`,分别指向 A 数组与 B 数组的尾部,再定义一个指针 `cur` 指向 A 数组当前可以赋值的元素位置 274 | - 比较 `cur_a` 与 `cur_b` 指向的两个元素,把较大的元素赋值给 `cur` 所在位置 275 | 276 | 277 | 278 | #### **Python** 279 | 280 | ```python 281 | class Solution: 282 | def merge(self, A: List[int], m: int, B: List[int], n: int) -> None: 283 | """ 284 | Do not return anything, modify A in-place instead. 285 | """ 286 | # 双指针:指向两个比较的数 287 | cur_a = m - 1 288 | cur_b = n - 1 289 | # 指向要赋值的位置 290 | cur = len(A) - 1 291 | 292 | while cur_a >= 0 and cur_b >= 0: 293 | a = A[cur_a] 294 | b = B[cur_b] 295 | # 取较大的放在后面 296 | if a >= b: 297 | A[cur] = a 298 | cur_a -= 1 299 | else: 300 | A[cur] = b 301 | cur_b -= 1 302 | cur -= 1 303 | 304 | while cur_b >= 0: 305 | A[cur] = B[cur_b] 306 | cur_b -= 1 307 | cur -= 1 308 | ``` 309 | 310 | #### **Go** 311 | 312 | ```go 313 | func merge(A []int, m int, B []int, n int) { 314 | curA := m - 1 315 | curB := n - 1 316 | cur := len(A) - 1 317 | 318 | for curA >= 0 && curB >= 0 { 319 | a := A[curA] 320 | b := B[curB] 321 | if a >= b { 322 | A[cur] = a 323 | curA -= 1 324 | } else { 325 | A[cur] = b 326 | curB -= 1 327 | } 328 | cur -= 1 329 | } 330 | 331 | for curB >= 0 { 332 | A[cur] = B[curB] 333 | curB -= 1 334 | cur -= 1 335 | } 336 | } 337 | ``` 338 | 339 | -------------------------------------------------------------------------------- /docs/algorithm/math/README.md: -------------------------------------------------------------------------------- 1 | ## 29. 两数相除 2 | 3 | [原题链接](https://leetcode-cn.com/problems/divide-two-integers/) 4 | 5 | ### 解法一 6 | 7 | 手写除法。但后面想想好像还是用了除法。。。明天改进一版 8 | 9 | ```python 10 | class Solution(object): 11 | def divide(self, dividend, divisor): 12 | """ 13 | :type dividend: int 14 | :type divisor: int 15 | :rtype: int 16 | """ 17 | res = 0 18 | flag = 1 19 | if (dividend > 0 and divisor < 0) or (dividend < 0 and divisor > 0): 20 | flag = -1 21 | 22 | dividend = abs(dividend) 23 | divisor = abs(divisor) 24 | 25 | dividend_str = str(dividend) 26 | dividend_len = len(dividend_str) 27 | 28 | i = 0 29 | mod = 0 30 | while i < dividend_len: 31 | cur_int = mod * 10 + int(dividend_str[i]) 32 | res = res * 10 + cur_int / divisor 33 | mod = cur_int % divisor 34 | i = i + 1 35 | 36 | res = res * flag 37 | 38 | if res < -2**31: 39 | return -2**31 40 | if res > 2**31 - 1: 41 | return 2**31 - 1 42 | 43 | return res 44 | ``` 45 | 46 | ### 解法二 47 | 48 | 参考了大佬的代码。 49 | 50 | 思路总结起来就是:判断除数里可以减去多少个被除数。但如果每次都只减去除数,循环的次数太多了,所以把除数每次乘以 2,如果发现除数大于被除数了,再从头开始。 51 | 52 | 因为不能使用乘法,所以用移位运算代替。 53 | 54 | ```python 55 | class Solution(object): 56 | def divide(self, dividend, divisor): 57 | """ 58 | :type dividend: int 59 | :type divisor: int 60 | :rtype: int 61 | """ 62 | res = 0 63 | flag = 1 64 | if (dividend > 0 and divisor < 0) or (dividend < 0 and divisor > 0): 65 | flag = -1 66 | 67 | dividend = abs(dividend) 68 | divisor = abs(divisor) 69 | 70 | while dividend >= divisor: 71 | tmp = divisor 72 | i = 1 73 | while dividend >= tmp: 74 | dividend -= tmp 75 | res += i 76 | 77 | i <<= 1 78 | tmp <<= 1 79 | 80 | if flag == -1: 81 | res = -res 82 | 83 | return min(max(-2**31, res), 2**31-1) 84 | ``` 85 | 86 | 87 | ## 171. Excel表列序号 88 | 89 | [原题链接](https://leetcode-cn.com/problems/excel-sheet-column-number/) 90 | 91 | ### 思路 92 | 93 | 首先观察一下列名称: 94 | 95 | ``` 96 | A -> 1 97 | B -> 2 98 | C -> 3 99 | ... 100 | Z -> 26 101 | AA -> 27 102 | AB -> 28 103 | ... 104 | ``` 105 | 106 | 如果列名称只有 1 位,那么直接将该位转为对应的数字即可,A~Z 分别代表着 1~26。 107 | 108 | 当我们将 26 个字母都使用完后,想表示更大的数字,1 位字母显然是不够的。所以有了 `AA` 这种写法,最左边的 `A` 代表着**进位**,这和十进制其实是一样的,只不过变成了 26 进一。 109 | 110 | ```python 111 | class Solution(object): 112 | def titleToNumber(self, s): 113 | """ 114 | :type s: str 115 | :rtype: int 116 | """ 117 | s_length = len(s) 118 | word_map = {'A': 1, 'B': 2, 'C': 3, 'D': 4, 'E': 5, 'F': 6, 'G': 7, 'H': 8, 'I': 9, 'J': 10, 'K': 11, 'L': 12, 'M': 13, 'N': 14, 'O': 15, 'P': 16, 'Q': 17, 'R': 18, 'S': 19, 'T': 20, 'U': 21, 'V': 22, 'W': 23, 'X': 24, 'Y': 25, 'Z': 26} 119 | res = 0 120 | for i in range(s_length - 1, -1, -1): 121 | word = s[i] 122 | j = s_length - 1 - i 123 | base = 26**j 124 | res += word_map[word] * base 125 | return res 126 | ``` 127 | 128 | 129 | ## 172. 阶乘后的零 130 | 131 | [原题链接](https://leetcode-cn.com/problems/factorial-trailing-zeroes/) 132 | 133 | ### 思路 134 | 135 | 题目问阶乘的结果有几个零,如果用笨方法求出阶乘然后再算 0 的个数会超出时间限制。 136 | 137 | 然后我们观察一下,5 的阶乘结果是 120,零的个数为 1: 138 | 139 | ``` 140 | 5! = 5 * 4 * 3 * 2 * 1 = 120 141 | ``` 142 | 143 | 末尾唯一的零来自于 `2 * 5`。很显然,如果需要产生零,阶乘中的数需要包含 2 和 5 这两个因子。 144 | 145 | 例如:`4 * 10 = 40` 也会产生零,因为 `4 * 10 = ( 2 * 2 ) * ( 2 * 5)` 。 146 | 147 | 因此,我们只要数一数组成阶乘的数中共有多少对 2 和 5 的组合即可。又因为 5 的个数一定比 2 少,问题简化为计算 5 的个数就可以了。 148 | 149 | ```python 150 | class Solution(object): 151 | def trailingZeroes(self, n): 152 | """ 153 | :type n: int 154 | :rtype: int 155 | """ 156 | 157 | res = 0 158 | 159 | while n >= 5: 160 | res += n / 5 161 | n //= 5 162 | 163 | return res 164 | ``` 165 | 166 | 167 | ## 202. 快乐数 168 | 169 | [原题链接](https://leetcode-cn.com/problems/happy-number/) 170 | 171 | ### 思路 172 | 173 | 快乐就完事的普通做法。 174 | 175 | 大家可能比较纠结具体要在什么时机返回 `False`,这里用 Python 的集合对中间数做了存储,如果发现计算出来的数之前曾经出现过,就说明已经进入了不快乐循环,此时返回 `False`。 176 | 177 | ```python 178 | class Solution(object): 179 | def isHappy(self, n): 180 | """ 181 | :type n: int 182 | :rtype: bool 183 | """ 184 | already = set() 185 | 186 | while n != 1: 187 | num = 0 188 | while n > 0: 189 | tmp = n % 10 190 | num += tmp**2 191 | n //= 10 192 | 193 | if num in already: 194 | return False 195 | else: 196 | already.add(num) 197 | 198 | n = num 199 | 200 | return True 201 | ``` 202 | 203 | ---- 204 | 205 | 看到评论里有大佬发现规律: 206 | 207 | > 不是快乐数的数称为不快乐数(unhappy number),所有不快乐数的数位平方和计算,最後都会进入 4 → 16 → 37 → 58 → 89 → 145 → 42 → 20 → 4 的循环中。 208 | 209 | 也是牛皮。 210 | 211 | 212 | ## 204. 计数质数 213 | 214 | [原题链接](https://leetcode-cn.com/problems/count-primes/) 215 | 216 | ### 超时法 217 | 218 | ```python 219 | class Solution(object): 220 | def countPrimes(self, n): 221 | """ 222 | :type n: int 223 | :rtype: int 224 | """ 225 | count = 0 226 | for i in range(2, n): 227 | if self.isPrime(i) == True: 228 | print i 229 | count += 1 230 | return count 231 | 232 | def isPrime(self, n): 233 | 234 | if n == 2: 235 | return True 236 | if n == 3: 237 | return True 238 | 239 | for i in range(2, n - 1): 240 | if n % i == 0: 241 | return False 242 | return True 243 | ``` 244 | 245 | ### 厄拉多塞筛法 246 | 247 | [厄拉多塞筛法](https://blog.csdn.net/u013291076/article/details/45575967) 248 | 249 | ```python 250 | class Solution(object): 251 | def countPrimes(self, n): 252 | """ 253 | :type n: int 254 | :rtype: int 255 | """ 256 | if n <= 2: 257 | return 0 258 | res = [1] * n 259 | res[0] = 0 260 | res[1] = 0 261 | 262 | i = 2 263 | while i * i < n: 264 | for j in range(2, (n - 1) // i + 1): 265 | res[i * j] = 0 266 | i += 1 267 | 268 | return sum(res) 269 | ``` 270 | 271 | 272 | ## 326. 3的幂 273 | 274 | [原题链接](https://leetcode-cn.com/problems/power-of-three/) 275 | 276 | ### 解法一 277 | 278 | 非递归 279 | 280 | ```python 281 | class Solution(object): 282 | def isPowerOfThree(self, n): 283 | """ 284 | :type n: int 285 | :rtype: bool 286 | """ 287 | if n == 1: 288 | return True 289 | 290 | while n != 0: 291 | if n == 3: 292 | return True 293 | if n % 3 == 0: 294 | n = n / 3 295 | else: 296 | return False 297 | if n == 3: 298 | return True 299 | return False 300 | ``` 301 | 302 | 303 | ## 412. Fizz Buzz 304 | 305 | [原题链接](https://leetcode-cn.com/problems/fizz-buzz/comments/) 306 | 307 | ### 思路 308 | 309 | 比较简单,不多说了。 310 | 311 | ```python 312 | class Solution(object): 313 | def fizzBuzz(self, n): 314 | """ 315 | :type n: int 316 | :rtype: List[str] 317 | """ 318 | res = [] 319 | for i in range(1, n + 1): 320 | res.append(self.getRes(i)) 321 | return res 322 | 323 | def getRes(self, n): 324 | mark = 0 325 | if n % 3 == 0: 326 | if n % 5 == 0: 327 | return "FizzBuzz" 328 | else: 329 | return "Fizz" 330 | else: 331 | if n % 5 == 0: 332 | return "Buzz" 333 | else: 334 | return str(n) 335 | ``` 336 | 337 | 338 | ## 507. 完美数 339 | 340 | [原题链接](https://leetcode-cn.com/problems/perfect-number/) 341 | 342 | ### 思路 343 | 344 | 1. 从 `1 ~ sqrt(num)` 的范围循环找出 `num` 的因子 `factor` 345 | 2. 如果 `factor != 1`,那么可以求出另一个与之对应的因子 `num / factor`。这里注意要判断 `num / factor` 是否与 `factor` 相等,避免相同因子重复添加 346 | 3. 计算所有因子和,判断是否与 `num` 相等 347 | 348 | ```python 349 | import math 350 | 351 | class Solution(object): 352 | def checkPerfectNumber(self, num): 353 | """ 354 | :type num: int 355 | :rtype: bool 356 | """ 357 | 358 | if num <= 1: 359 | return False 360 | 361 | s = 0 362 | for factor in range(1, int(math.sqrt(num)) + 1): 363 | # 判断是否是因子 364 | if num % factor == 0: 365 | s += factor 366 | # 避免相同因子重复添加 367 | if factor != 1 and num / factor != factor: 368 | s += num / factor 369 | 370 | if s == num: 371 | return True 372 | else: 373 | return False 374 | ``` 375 | 376 | 377 | ## 633. 平方数之和 378 | 379 | [原题链接](https://leetcode-cn.com/problems/sum-of-square-numbers/) 380 | 381 | ### 思路 382 | 383 | 双指针 384 | 385 | - i 从 0 开始 386 | - j 从可取的最大数 int(math.sqrt(c)) 开始 387 | - total = i * i + j * j 388 | - total > c: j = j - 1,将 total 值减小 389 | - total < c: i = i + 1,将 total 值增大 390 | - total == c:返回 True 391 | 392 | ```python 393 | import math 394 | 395 | class Solution(object): 396 | def judgeSquareSum(self, c): 397 | """ 398 | :type c: int 399 | :rtype: bool 400 | """ 401 | j = int(math.sqrt(c)) 402 | i = 0 403 | while i <= j: 404 | total = i * i + j * j 405 | if total > c: 406 | j = j - 1 407 | elif total < c: 408 | i = i + 1 409 | else: 410 | return True 411 | return False 412 | ``` 413 | 414 | ## 640. 求解方程 415 | 416 | [原题链接](https://leetcode-cn.com/problems/solve-the-equation/) 417 | 418 | ### 思路 419 | 420 | 求解方程就是要把方程简化,求出 `x` 的个数 `x_count` 和剩余数值 `num`: 421 | 422 | ``` 423 | x_count * x = num 424 | ``` 425 | 426 | 最终需要求的结果就是 `num / x_count`。 427 | 428 | 我们可以把方程式根据等号分为左右两个式子,对左右两个式子进行遍历,分别求出 `x` 的数量和剩余数值。 429 | 430 | 在遍历过程中: 431 | 432 | 1. 遇到字符为 `+` 或 `-`(即遇到运算符):对该运算符之前的公式进行结算 433 | 2. 遇到字符不是运算符: 434 | 1. 遇到字符为 `x`:根据 `x` 前面跟着的数字和运算符,对 `x` 的个数进行结算 435 | 2. 遇到字符为数字:把该数字记录下来(我在这里用了 Python 的列表进行记录),当遇到下一个运算符或符号 `x` 时进行结算 436 | 437 | ⚠️注: 会遇到 `0x` 这样的写法。 438 | 439 | ```python 440 | class Solution: 441 | def solveEquation(self, equation: str) -> str: 442 | formula = equation.split("=") 443 | 444 | def get_count(formula): 445 | num_list = [] 446 | pre_symbol = "+" 447 | x_count = 0 448 | num = 0 449 | for c in formula: 450 | # 不是运算符 451 | if c != "+" and c != "-": 452 | if c == "x": 453 | add_x = 1 if len(num_list) == 0 else int("".join(num_list)) 454 | if pre_symbol == "+": 455 | x_count += add_x 456 | else: 457 | x_count -= add_x 458 | num_list = [] # 清空列表 459 | else: 460 | # 是数字 461 | num_list.append(c) 462 | # 是运算符 463 | else: 464 | if len(num_list) != 0: 465 | num = eval(str(num) + pre_symbol + "".join(num_list)) 466 | pre_symbol = c 467 | num_list = [] 468 | # 最后遗漏的数字 469 | if len(num_list) != 0: 470 | num = eval(str(num) + pre_symbol + "".join(num_list)) 471 | return [x_count, num] 472 | 473 | left = get_count(formula[0]) 474 | right = get_count(formula[1]) 475 | 476 | x_count = left[0] - right[0] 477 | num = left[1] - right[1] 478 | 479 | # 计算结果 480 | if x_count == 0 and num == 0: 481 | return "Infinite solutions" 482 | if x_count == 0 and num != 0: 483 | return "No solution" 484 | return "x=" + str(-num // x_count) 485 | ``` 486 | 487 | ## 836. 矩形重叠 488 | 489 | [原题链接](https://leetcode-cn.com/problems/rectangle-overlap/) 490 | 491 | ### 思路 492 | 493 | 判断「不相交」的条件。 494 | 495 | 496 | 497 | #### **Python** 498 | 499 | ```python 500 | class Solution: 501 | def isRectangleOverlap(self, rec1: List[int], rec2: List[int]) -> bool: 502 | ax1, ay1 = rec1[0], rec1[1] 503 | ax2, ay2 = rec1[2], rec1[3] 504 | bx1, by1 = rec2[0], rec2[1] 505 | bx2, by2 = rec2[2], rec2[3] 506 | # 判断是否相交 507 | if ax2 <= bx1 or ax1 >= bx2 or ay2 <= by1 or ay1 >= by2: 508 | return False 509 | return True 510 | ``` 511 | 512 | #### **Go** 513 | 514 | ```go 515 | func isRectangleOverlap(rec1 []int, rec2 []int) bool { 516 | ax1, ay1 := rec1[0], rec1[1] 517 | ax2, ay2 := rec1[2], rec1[3] 518 | bx1, by1 := rec2[0], rec2[1] 519 | bx2, by2 := rec2[2], rec2[3] 520 | if ax2 <= bx1 || ax1 >= bx2 || ay2 <= by1 || ay1 >= by2 { 521 | return false 522 | } 523 | return true 524 | } 525 | ``` 526 | 527 | 528 | 529 | ## 1103. 分糖果 II 530 | 531 | [原题链接](https://leetcode-cn.com/problems/distribute-candies-to-people/) 532 | 533 | ### 等差数列 534 | 535 | ```python 536 | class Solution: 537 | def distributeCandies(self, candies: int, num_people: int) -> List[int]: 538 | s = 0 539 | x = 1 540 | people = 0 541 | while s + x < candies: 542 | people += 1 543 | s += x 544 | x += 1 545 | # 只够发到 x - 1 个人,第 x 个人可能没有足额糖果 546 | 547 | # 计算可以发几轮(总轮数) 548 | r = people // num_people 549 | # print(people, r) 550 | # 多发一排人数 551 | other = people % num_people 552 | # 最后可能有剩余糖果分给最后一个人 553 | 554 | # 等差数列 d = n 555 | # 给每个小朋友发 556 | res = [0 for _ in range(num_people)] 557 | for i in range(num_people): 558 | res[i] = (i + 1) * r + (r * (r - 1) * num_people) // 2 559 | 560 | # 给最后一排分糖果 561 | other_begin = r * num_people + 1 562 | for i in range(other): 563 | res[i] += other_begin 564 | other_begin += 1 565 | 566 | if s < candies: 567 | index = i + 1 568 | if i + 1 == num_people: 569 | index = 0 570 | res[index] += candies - s 571 | 572 | # print(res) 573 | return res 574 | ``` 575 | 576 | ## 1276. 不浪费原料的汉堡制作方案 577 | 578 | [原题链接](https://leetcode-cn.com/problems/number-of-burgers-with-no-waste-of-ingredients/) 579 | 580 | ### 思路 581 | 582 | 数学题,解方程。 583 | 584 | 设有 `x` 个巨无霸,`y` 个小皇堡。那么有: 585 | 586 | $$ 587 | 4 \times x + 2y = tomatoSlices 588 | $$ 589 | 590 | $$ 591 | x + y = cheeseSlices 592 | $$ 593 | 594 | 约束项: 595 | 596 | - `x` 与 `y` 需大于等于 0 597 | - `tomatoSlices` 需要是偶数 598 | 599 | 600 | 601 | #### **Python** 602 | 603 | ```python 604 | class Solution: 605 | def numOfBurgers(self, tomatoSlices: int, cheeseSlices: int) -> List[int]: 606 | if tomatoSlices % 2 != 0 or tomatoSlices > 4 * cheeseSlices or tomatoSlices < 2 * cheeseSlices: 607 | return [] 608 | 609 | y = 2 * cheeseSlices - tomatoSlices // 2 610 | x = cheeseSlices - y 611 | 612 | return [x, y] 613 | ``` 614 | 615 | #### **Go** 616 | 617 | ```go 618 | func numOfBurgers(tomatoSlices int, cheeseSlices int) []int { 619 | if tomatoSlices % 2 != 0 || tomatoSlices > 4 * cheeseSlices || tomatoSlices < 2 * cheeseSlices { 620 | return make([]int, 0) 621 | } 622 | 623 | res := make([]int, 2) 624 | res[1] = 2 * cheeseSlices - tomatoSlices / 2 625 | res[0] = cheeseSlices - res[1] 626 | 627 | return res 628 | } 629 | ``` 630 | 631 | -------------------------------------------------------------------------------- /docs/algorithm/other/README.md: -------------------------------------------------------------------------------- 1 | ## 384. 打乱数组 2 | 3 | [原题链接](https://leetcode-cn.com/problems/shuffle-an-array/) 4 | 5 | ### 思路 6 | 7 | 使用 [FisherYates 洗牌算法](https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle),伪代码如下: 8 | 9 | ``` 10 | -- To shuffle an array a of n elements (indices 0..n-1): 11 | for i from n−1 downto 1 do 12 | j ← random integer such that 0 ≤ j ≤ i 13 | exchange a[j] and a[i] 14 | ``` 15 | 16 | ```python 17 | import random 18 | 19 | class Solution(object): 20 | 21 | def __init__(self, nums): 22 | """ 23 | :type nums: List[int] 24 | """ 25 | self.init = list(nums) 26 | self.nums = nums 27 | self.length = len(nums) 28 | 29 | 30 | 31 | def reset(self): 32 | """ 33 | Resets the array to its original configuration and return it. 34 | :rtype: List[int] 35 | """ 36 | self.nums = list(self.init) 37 | return self.nums 38 | 39 | 40 | def shuffle(self): 41 | """ 42 | Returns a random shuffling of the array. 43 | :rtype: List[int] 44 | """ 45 | for i in reversed(range(self.length)): 46 | index = random.randint(0, i) 47 | self.nums[i], self.nums[index] = self.nums[index], self.nums[i] 48 | return self.nums 49 | 50 | 51 | # Your Solution object will be instantiated and called as such: 52 | # obj = Solution(nums) 53 | # param_1 = obj.reset() 54 | # param_2 = obj.shuffle() 55 | ``` 56 | 57 | 当然啦,直接用 `shuffle` 方法也可以。 58 | 59 | ### 参考 60 | 61 | - [Python shuffle() 函数](https://www.runoob.com/python/func-number-shuffle.html) 62 | 63 | ## 846. 一手顺子 64 | 65 | [原题链接](https://leetcode-cn.com/problems/hand-of-straights/submissions/) 66 | 67 | ### 思路 68 | 69 | 70 | 71 | #### ** Java ** 72 | 73 | 题目的要求是判断输入的数组,是否能被分隔成每 W 个数字一组,并且每组的数据都是连续的。 74 | 75 | 这个算法考虑先计算出每个数字出现的个数,然后从小到大取数字,以组成连续的数组。若,数字不够取,则返回 false,否则返回 true。 76 | 77 | 如,示例1: 78 | 79 | - 输入: hand = [1,1,2,3,2,3,6,2,3,4,7,8] W = 3 80 | - 输出: true 81 | - 上述数组可以分割为: [1, 2, 3], [2, 3, 4], [6, 7, 8] 82 | 83 | 首先,根据 W = 3,则,可知每个分割后的子数组长度为 3。 84 | 85 | 其次,我们可以将上述数组中的数字出现次数做一个统计: 86 | 87 | [1: 2 次,2: 3 次,3: 3 次,4: 1 次,5: 1 次,6: 1 次,7: 1 次] 88 | 89 | 那么,我们从小到大遍历我们统计的结果。 90 | 91 | **1. 从 1 开始,那么以 1 开头的子数组为: 1、2、3。那么,当前我们需要将 1 的次数使用完,所以需要将 1,2,3 依次 -2。得到:** 92 | 93 | [1: 0 次,2: 1 次,3: 1 次,4: 1 次,5: 1 次,6: 1 次,7: 1 次] 94 | 95 | 此时,如果 2, 3 出现的次数有小于 2 的,说明存在以 1 开头的连续数组存在长度不为3。 96 | 97 | **2. 现在 1 的个数为 0,那么现在遍历到 2 。以 1 开头的子数组为: 2、3、4。那么,我们在统计结果上,将此时已经使用过的数组以及使用次数减一,得到:** 98 | 99 | [1: 0 次,2: 0 次,3: 0 次,4: 0 次,5: 1 次,6: 1 次,7: 1 次] 100 | 101 | **3. 现在 2, 3 和 4 的个数为 0,那么现在遍历到 5 。以 5 开头的子数组为: 5、6、7。那么,我们在统计结果上,将此时已经使用过的数组以及使用次数减一,得到:** 102 | 103 | [1: 0 次,2: 0 次,3: 0 次,4: 0 次,5: 0 次,6: 0 次,7: 0 次] 104 | 105 | 数组中所有的数字被使用尽,且未发现无法组成连续子数组的情况,那么返回 true。 106 | 107 | ```java 108 | public class Solution { 109 | public boolean isNStraightHand(int[] hand, int W) { 110 | if (hand == null || W < 0 || W > hand.length) return false; 111 | 112 | if (hand.length % W != 0) return false; 113 | 114 | Map hands = new TreeMap<>(); 115 | for (int i : hand) { 116 | hands.put(i, hands.getOrDefault(i, 0) + 1); 117 | } 118 | 119 | Integer[] keyset = new Integer[hands.keySet().size()]; 120 | hands.keySet().toArray(keyset); 121 | 122 | for (int i = 0; i < hands.keySet().size(); i++) { 123 | int num = keyset[i]; 124 | int startTimes = hands.getOrDefault(num, 0); 125 | if (startTimes <= 0) { 126 | continue; 127 | } 128 | 129 | for (int j = 0; j < W; j++) { 130 | int times = hands.getOrDefault(num + j, 0); 131 | if (times < startTimes) return false; 132 | hands.put(num + j, times - startTimes); 133 | } 134 | } 135 | return true; 136 | } 137 | } 138 | ``` 139 | 140 | #### ** Python ** 141 | 142 | 1. 创建一个字典 `hand_counter`,用于统计每张牌出现的数量。例如 `hand_counter[i] = x` 表示牌 `i` 出现 `x` 次 143 | 2. 根据字典的 Key 值进行升序排序,获得排序后的列表 `sort_hand` 144 | 3. 取连续牌。若要取的牌 `i` 有 `n` 张,那么需要将 `i, i + 1, ..., i + W - 1` 牌都取走 `n` 张。若在取牌过程中某张牌的数量不够则返回 `False`,整个取牌过程顺利则返回 `True` 145 | 146 | ##### 手写实现 147 | 148 | ```python 149 | class Solution: 150 | def isNStraightHand(self, hand: List[int], W: int) -> bool: 151 | length = len(hand) 152 | # 无法整除直接返回 false 153 | if length % W != 0: 154 | return False 155 | 156 | # 初始化辅助空间 157 | hand_counter = dict() 158 | 159 | for i in range(length): 160 | hand_counter[hand[i]] = hand_counter.get(hand[i], 0) + 1 161 | # 排序 162 | sort_hand = sorted(hand_counter) 163 | 164 | for h in sort_hand: 165 | if hand_counter[h] == 0: 166 | continue 167 | elif hand_counter[h] < 0: 168 | return False 169 | else: 170 | for i in range(1, W): 171 | if hand_counter.get(h + i, 0) == 0: 172 | return False 173 | else: 174 | hand_counter[h + i] -= hand_counter[h] 175 | 176 | return True 177 | ``` 178 | 179 | ##### 借助 Counter 实现 180 | 181 | ```python 182 | from collections import Counter 183 | 184 | class Solution: 185 | def isNStraightHand(self, hand: List[int], W: int) -> bool: 186 | hand_counter = Counter(hand) 187 | # 按 key 值排序结果 188 | sort_hand = sorted(hand_counter) 189 | 190 | for h in sort_hand: 191 | # 牌不够取了 192 | if hand_counter[h] < 0: 193 | return False 194 | elif hand_counter[h] > 0: 195 | for i in range(1, W): 196 | if hand_counter[h + i] == 0: 197 | return False 198 | else: 199 | hand_counter[h + i] -= hand_counter[h] 200 | else: 201 | # == 0 时跳过 202 | continue 203 | 204 | return True 205 | ``` 206 | 207 | 208 | 209 | ### 复杂度 210 | 211 | - 时间复杂度 212 | - 排序复杂度 `O(nlgn)` 213 | - 循环复杂度 `O(n)` 214 | - 空间复杂度 `O(n)` -------------------------------------------------------------------------------- /docs/algorithm/recursion/README.md: -------------------------------------------------------------------------------- 1 | ## 226. 翻转二叉树 2 | 3 | [原题链接](https://leetcode-cn.com/problems/invert-binary-tree/) 4 | 5 | ### 思路 6 | 7 | 递归。 8 | 9 | ```python 10 | # Definition for a binary tree node. 11 | # class TreeNode: 12 | # def __init__(self, x): 13 | # self.val = x 14 | # self.left = None 15 | # self.right = None 16 | 17 | class Solution: 18 | def invertTree(self, root: TreeNode) -> TreeNode: 19 | if root is None: 20 | return root 21 | 22 | # 翻转左子树 23 | left = self.invertTree(root.left) 24 | # 翻转右子树 25 | right = self.invertTree(root.right) 26 | 27 | root.left, root.right = right, left 28 | 29 | return root 30 | ``` 31 | 32 | ## 341. 扁平化嵌套列表迭代器 33 | 34 | [原题链接](https://leetcode-cn.com/problems/flatten-nested-list-iterator/) 35 | 36 | ### 解一:递归法 37 | 38 | 用一个队列辅助,设计递归函数 `generate(item)`: 39 | 40 | - 如果传入的 `item` 为列表,对 `item` 进行遍历,遍历出的元素再进入 `generate()` 进行递归 41 | - 如果传入的 `item` 不为列表(即为 `NestedInteger` 对象),调用 `item.isInteger()` 判断元素是否为整型: 42 | - 元素为整型:直接加入辅助队列 43 | - 元素为列表:继续传入 `generate()` 进行递归 44 | 45 | ```python 46 | # """ 47 | # This is the interface that allows for creating nested lists. 48 | # You should not implement it, or speculate about its implementation 49 | # """ 50 | #class NestedInteger: 51 | # def isInteger(self) -> bool: 52 | # """ 53 | # @return True if this NestedInteger holds a single integer, rather than a nested list. 54 | # """ 55 | # 56 | # def getInteger(self) -> int: 57 | # """ 58 | # @return the single integer that this NestedInteger holds, if it holds a single integer 59 | # Return None if this NestedInteger holds a nested list 60 | # """ 61 | # 62 | # def getList(self) -> [NestedInteger]: 63 | # """ 64 | # @return the nested list that this NestedInteger holds, if it holds a nested list 65 | # Return None if this NestedInteger holds a single integer 66 | # """ 67 | 68 | class NestedIterator: 69 | def __init__(self, nestedList: [NestedInteger]): 70 | self.data_list = [] 71 | # 构造列表 72 | self.generate(nestedList) 73 | # print(self.data_list) 74 | 75 | def generate(self, item): 76 | """ 77 | 构造列表 78 | """ 79 | # 传入数据,如果是列表,进行递归 80 | if isinstance(item, list): 81 | # 如果是列表,循环 82 | for i in item: 83 | self.generate(i) 84 | else: 85 | if item.isInteger(): 86 | # 是数字,加入队列 87 | self.data_list.append(item.getInteger()) 88 | else: 89 | # 是列表,取出列表 90 | self.generate(item.getList()) 91 | 92 | def next(self) -> int: 93 | data = self.data_list[0] 94 | del self.data_list[0] 95 | return data 96 | 97 | def hasNext(self) -> bool: 98 | return len(self.data_list) != 0 99 | 100 | # Your NestedIterator object will be instantiated and called as such: 101 | # i, v = NestedIterator(nestedList), [] 102 | # while i.hasNext(): v.append(i.next()) 103 | ``` 104 | 105 | 但该方法在构造器中一次性将列表元素全部提取,并不符合「迭代器」的概念。 106 | 107 | ### 解法二:栈 108 | 109 | **用栈模拟递归的过程**。使用一个辅助栈,在 `hasNext()` 时不断处理栈顶的元素。使用 `flag` 标记栈顶是否已处理,并使用 `top` 记录迭代器的下一个数字。 110 | 111 | 一开始将整个 `nestedList` 压入栈中,每次处理栈时,都将栈顶元素取出: 112 | 113 | - 如果栈顶元素是列表:取出列表首个元素,将列表入栈,并将首个元素入栈 114 | - 如果栈顶元素不是列表: 115 | - 如果栈顶元素是整型:将 `flag` 设置为 `True`,并将该整型赋值给 `top` 116 | - 如果栈顶元素不是整型:使用 `getList()` 取出列表,并将列表入栈 117 | 118 | 当栈不为空且 `falg == False` 时,不断循环处理栈顶,直到取出下一个迭代值,将 `flag` 置为 `True`。 119 | 120 | ```python 121 | # """ 122 | # This is the interface that allows for creating nested lists. 123 | # You should not implement it, or speculate about its implementation 124 | # """ 125 | #class NestedInteger: 126 | # def isInteger(self) -> bool: 127 | # """ 128 | # @return True if this NestedInteger holds a single integer, rather than a nested list. 129 | # """ 130 | # 131 | # def getInteger(self) -> int: 132 | # """ 133 | # @return the single integer that this NestedInteger holds, if it holds a single integer 134 | # Return None if this NestedInteger holds a nested list 135 | # """ 136 | # 137 | # def getList(self) -> [NestedInteger]: 138 | # """ 139 | # @return the nested list that this NestedInteger holds, if it holds a nested list 140 | # Return None if this NestedInteger holds a single integer 141 | # """ 142 | 143 | class NestedIterator: 144 | def __init__(self, nestedList: [NestedInteger]): 145 | self.stack = [] 146 | # 初始化栈 147 | self.stack.append(nestedList) 148 | self.top = 0 # 下一个元素 149 | self.flag = False # 栈顶是否已处理 150 | 151 | def next(self) -> int: 152 | self.flag = False # 栈顶恢复未处理状态 153 | return self.top 154 | 155 | def hasNext(self) -> bool: 156 | if len(self.stack) == 0: 157 | return False 158 | 159 | # 栈顶待处理且栈不为空 160 | while len(self.stack) > 0 and not self.flag: 161 | # 取出栈顶 162 | top = self.stack.pop() 163 | if isinstance(top, list): 164 | # 如果是列表,取出首元素,并将两者都压入栈 165 | if len(top) > 0: 166 | first = top[0] 167 | del top[0] 168 | self.stack.append(top) 169 | self.stack.append(first) 170 | else: 171 | # 如果不是列表 172 | if top.isInteger(): 173 | # 如果是整数,直接取出 174 | self.top = top.getInteger() 175 | self.flag = True 176 | else: 177 | # 取出 List 压入栈 178 | self.stack.append(top.getList()) 179 | return self.flag 180 | 181 | # Your NestedIterator object will be instantiated and called as such: 182 | # i, v = NestedIterator(nestedList), [] 183 | # while i.hasNext(): v.append(i.next()) 184 | ``` 185 | 186 | ## 698. 划分为k个相等的子集 187 | 188 | [原题链接](https://leetcode-cn.com/problems/partition-to-k-equal-sum-subsets/) 189 | 190 | ### 思路 191 | 192 | 递归求解。 193 | 194 | 拆分的子问题为:凑齐一个和为 `avg` 的子集。 195 | 196 | 因此,我们先求出真个数组划分为 k 个子集后每个子集的和,即 `avg = sum / k`。 197 | 198 | 在递归函数中: 199 | 200 | - 遍历函数 `nums`(已从大到小排序),逐一**凑**出子集 201 | - 进行下一层递归条件:凑齐一个和为 `avg` 的子集 202 | - 递归函数结束条件:已凑齐 `k` 个子集 203 | 204 | ```python 205 | class Solution: 206 | def canPartitionKSubsets(self, nums: List[int], k: int) -> bool: 207 | length = len(nums) 208 | if length < k: 209 | return False 210 | 211 | s = sum(nums) 212 | # 求平均值 213 | avg, mod = divmod(s, k) 214 | # 不能整除,返回 False 215 | if mod: 216 | return False 217 | 218 | # 降序排序 219 | nums.sort(reverse=True) 220 | # 存储已经使用的下标 221 | used = set() 222 | def dfs(start, value, cnt): 223 | if value == avg: 224 | return dfs(0, 0, cnt - 1) 225 | 226 | if cnt == 0: 227 | return True 228 | 229 | for i in range(start, length): 230 | if i not in used and value + nums[i] <= avg: 231 | used.add(i) 232 | if dfs(i + 1, value + nums[i], cnt): 233 | return True 234 | used.remove(i) 235 | 236 | return False 237 | 238 | return dfs(0, 0, k) 239 | ``` 240 | 241 | ## 779. 第K个语法符号 242 | 243 | [原题链接](https://leetcode-cn.com/problems/k-th-symbol-in-grammar/) 244 | 245 | ### 思路 246 | 247 | 通过观察规律可知:**第 N 行第 K 个数是由第 N - 1 行第 (K + 1) / 2 个数得来的**。 248 | 249 | 并且: 250 | 251 | - 当上一行的数字为 0 时,生成的数字是 `1 - (K % 2)` 252 | - 当上一行的数字为 1 时,生成的数字是 `K % 2` 253 | 254 | 递归函数设计: 255 | 256 | - 函数作用:返回第 `N` 行第 `K` 个数 257 | - 当 `N == 1` 时结束递归 258 | 259 | #### 具体实现 260 | 261 | 262 | 263 | #### **Python** 264 | 265 | ```python 266 | class Solution(object): 267 | def kthGrammar(self, N, K): 268 | if N == 1: 269 | return 0 270 | # 得到第 N 行第 K 位的数字 271 | return self.kthGrammar(N - 1, (K + 1) // 2) ^ (1 - K % 2) 272 | ``` 273 | 274 | #### **Go** 275 | 276 | ```go 277 | func kthGrammar(N int, K int) int { 278 | if N == 1 { 279 | return 0 280 | } 281 | return kthGrammar(N - 1, (K + 1) / 2) ^ (1 - K % 2) 282 | } 283 | ``` 284 | 285 | 286 | 287 | #### 复杂度 288 | 289 | - 时间复杂度:$O(N)$ 290 | - 空间复杂度:$O(N)$ -------------------------------------------------------------------------------- /docs/algorithm/research/bfs/README.md: -------------------------------------------------------------------------------- 1 | ## 200. 岛屿数量 2 | 3 | [原题链接](https://leetcode-cn.com/problems/number-of-islands/) 4 | 5 | ### BFS 6 | 7 | 广度优先搜索 BFS。总结一下思想就是:一旦发现岛屿就找到与它相邻的岛屿并将它们沉没,那么下次再发现的岛屿就是新大陆了。 8 | 9 | 具体思路如下: 10 | 11 | 1. 初始化一个队列,用于放置**已经找到却还没有进行广度优先遍历**的岛屿 12 | 2. 遍历二维数组,如果发现岛屿(即发现 "1"), 13 | 1. 陆地数量+1 14 | 2. 将岛屿加入队列 15 | 3. 将岛屿沉没("1" -> "0"),防止后续重复计算 16 | 4. 使用广度优先遍历,查找该岛屿的四个相邻位置上是否还有岛屿,如果发现了岛屿就把它加入队列并且沉没 17 | 18 | 19 | 20 | #### **Python** 21 | 22 | ```python 23 | class Solution(object): 24 | def numIslands(self, grid): 25 | """ 26 | :type grid: List[List[str]] 27 | :rtype: int 28 | """ 29 | m = len(grid) 30 | if m == 0: 31 | return 0 32 | n = len(grid[0]) 33 | # 初始化一个队列 34 | q = list() 35 | res = 0 36 | # 定义岛屿的四个相邻位置 37 | area = [(0, -1), (-1, 0), (0, 1), (1, 0)] 38 | 39 | for i in range(m): 40 | for j in range(n): 41 | cur = grid[i][j] 42 | # 遍历过程中发现了岛屿 43 | if cur == '1': 44 | # 陆地数量+1 45 | res += 1 46 | # 入队列 47 | q.append((i, j)) 48 | # 将岛屿沉没 49 | grid[i][j] = '0' 50 | 51 | # 如果队列不为空,就循环队列,将队列里已经存在的岛屿进行 BFS 52 | while len(q) > 0: 53 | # 取出岛屿坐标 54 | node_x, node_y = q[0] 55 | del q[0] 56 | # 判断岛屿的四个相邻位置是否还存在岛屿 57 | for k in range(len(area)): 58 | area_x, area_y = area[k] 59 | node_area_x, node_area_y = node_x + area_x, node_y + area_y 60 | if (node_area_x >= 0 and node_area_x < m) and (node_area_y >= 0 and node_area_y < n): 61 | if grid[node_area_x][node_area_y] == '1': 62 | # 相邻位置存在岛屿:入队列、把岛屿沉没 63 | q.append((node_area_x, node_area_y)) 64 | grid[node_area_x][node_area_y] = '0' 65 | 66 | return res 67 | ``` 68 | 69 | #### **Go** 70 | 71 | ```go 72 | func numIslands(grid [][]byte) int { 73 | var ans int 74 | 75 | m := len(grid) 76 | if m == 0 { 77 | return ans 78 | } 79 | n := len(grid[0]) 80 | 81 | for i := 0; i < m; i++ { 82 | for j := 0; j < n; j++ { 83 | if grid[i][j] == '1' { 84 | ans += 1 85 | // 是岛屿,bfs 86 | tmp := []int{i, j} 87 | queue := make([][]int, 0) 88 | queue = append(queue, tmp) 89 | for len(queue) > 0 { 90 | position := queue[0] 91 | // 删除第一个元素 92 | queue = queue[1:] 93 | x := position[0] 94 | y := position[1] 95 | // 四个方向 (1, 0) (-1, 0) (0, 1) (0, -1) 96 | if x + 1 < m && grid[x + 1][y] == '1' { 97 | tmp := []int{x + 1, y} 98 | grid[x + 1][y] = '0' 99 | queue = append(queue, tmp) 100 | } 101 | if x - 1 >= 0 && grid[x - 1][y] == '1' { 102 | tmp := []int{x - 1, y} 103 | grid[x - 1][y] = '0' 104 | queue = append(queue, tmp) 105 | } 106 | if y + 1 < n && grid[x][y + 1] == '1' { 107 | tmp := []int{x, y + 1} 108 | grid[x][y + 1] = '0' 109 | queue = append(queue, tmp) 110 | } 111 | if y - 1 >= 0 && grid[x][y - 1] == '1' { 112 | tmp := []int{x, y - 1} 113 | grid[x][y - 1] = '0' 114 | queue = append(queue, tmp) 115 | } 116 | } 117 | // print(queue) 118 | } 119 | } 120 | } 121 | 122 | return ans 123 | } 124 | ``` 125 | 126 | 127 | 128 | ## 542. 01 矩阵 129 | 130 | [原题链接](https://leetcode-cn.com/problems/01-matrix/) 131 | 132 | ### BFS:以 1 为源(超时) 133 | 134 | ```python 135 | class Solution: 136 | def updateMatrix(self, matrix: List[List[int]]) -> List[List[int]]: 137 | m = len(matrix) 138 | if m == 0: 139 | return [] 140 | n = len(matrix[0]) 141 | 142 | ans = [[0 for _ in range(n)] for _ in range(m)] 143 | 144 | for i in range(m): 145 | for j in range(n): 146 | if matrix[i][j] == 0: 147 | # 0 不处理 148 | continue 149 | mark = [[0 for _ in range(n)] for _ in range(m)] 150 | step = 0 151 | queue = [(i, j, step)] 152 | while len(queue) > 0: 153 | # bfs 154 | x, y, s = queue[0][0], queue[0][1], queue[0][2] 155 | del queue[0] 156 | if mark[x][y]: 157 | # 已经访问过,跳过 158 | continue 159 | # 处理 160 | mark[x][y] = 1 # 访问标记 161 | if matrix[x][y] == 0: 162 | # 找到 0,进行标记,不继续遍历 163 | ans[i][j] = s 164 | break 165 | 166 | # 否则加入上下左右 167 | directions = [[0, 1], [0, -1], [-1, 0], [1, 0]] 168 | for d in directions: 169 | n_x, n_y = x + d[0], y + d[1] 170 | if n_x >= 0 and n_x < m and n_y >= 0 and n_y < n and mark[n_x][n_y] == 0: 171 | # 坐标符合要求 172 | # print(n_x, n_y, s + 1) 173 | queue.append((n_x, n_y, s + 1)) 174 | 175 | return ans 176 | ``` 177 | 178 | ### BFS:以 0 为源 179 | 180 | 把所有 0 放入队列,每个 0 逐个向外 BFS 一圈标记相邻的 1,再把 BFS 到的 1 入队列…… 181 | 182 | ```python 183 | class Solution: 184 | def updateMatrix(self, matrix: List[List[int]]) -> List[List[int]]: 185 | m = len(matrix) 186 | if m == 0: 187 | return [] 188 | n = len(matrix[0]) 189 | 190 | queue = [] 191 | 192 | for i in range(m): 193 | for j in range(n): 194 | # 把 0 放入队列 195 | if matrix[i][j] == 0: 196 | queue.append((i, j)) 197 | else: 198 | # 把 1 标记为未访问过的结点 199 | matrix[i][j] = -1 200 | directions = [[0, 1], [0, -1], [1, 0], [-1, 0]] 201 | while len(queue) > 0: 202 | x, y = queue[0][0], queue[0][1] 203 | del queue[0] 204 | for d in directions: 205 | n_x, n_y = x + d[0], y + d[1] 206 | if n_x >= 0 and n_x < m and n_y >= 0 and n_y < n and matrix[n_x][n_y] == -1: 207 | matrix[n_x][n_y] = matrix[x][y] + 1 208 | queue.append((n_x, n_y)) 209 | 210 | return matrix 211 | ``` 212 | 213 | - 时间复杂度:$O(m * n)$ 214 | 215 | ## 1162. 地图分析 216 | 217 | [原题链接](https://leetcode-cn.com/problems/as-far-from-land-as-possible/) 218 | 219 | ### 解一:广度优先搜索 220 | 221 | - 先遍历 `grid` 一次,把陆地都存入队列,作为 BFS 第一层 222 | - 将队列中的陆地元素取出,分别向上下左右走一步,如果遇到了海洋则继续加入队列,作为 BFS 下一层 223 | - 遇到的海洋标记为 `2` 防止重复遍历 224 | 225 | 这样以来,BFS 走的最大层数就是最后要求的最远距离(将 曼哈顿距离 转为步数处理)。 226 | 227 | ```python 228 | class Solution: 229 | def maxDistance(self, grid: List[List[int]]) -> int: 230 | m = len(grid) 231 | if m == 0: 232 | return -1 233 | n = len(grid[0]) 234 | queue = [] 235 | for i in range(m): 236 | for j in range(n): 237 | if grid[i][j] == 1: 238 | # 发现陆地,加入队列 239 | queue.append([i, j]) 240 | 241 | if len(queue) == 0 or len(queue) == m * n: 242 | return -1 243 | 244 | distance = -1 245 | while len(queue) > 0: 246 | distance += 1 247 | length = len(queue) 248 | for i in range(length): 249 | # 取出所有位置 250 | first = queue[0] 251 | x, y = first[0], first[1] 252 | del queue[0] 253 | # 上下左右四个方向 254 | if x - 1 >= 0 and grid[x - 1][y] == 0: 255 | # 避免重复判断 256 | grid[x - 1][y] = 2 257 | queue.append([x - 1, y]) 258 | if x + 1 < m and grid[x + 1][y] == 0: 259 | grid[x + 1][y] = 2 260 | queue.append([x + 1, y]) 261 | if y - 1 >= 0 and grid[x][y - 1] == 0: 262 | grid[x][y - 1] = 2 263 | queue.append([x, y - 1]) 264 | if y + 1 < n and grid[x][y + 1] == 0: 265 | grid[x][y + 1] = 2 266 | queue.append([x, y + 1]) 267 | 268 | return distance 269 | ``` -------------------------------------------------------------------------------- /docs/algorithm/research/dfs/README.md: -------------------------------------------------------------------------------- 1 | ## 39. Combination Sum 2 | 3 | [原题链接](https://leetcode.com/problems/combination-sum/description/) 4 | 5 | ### 题目 6 | 7 | Given a set of candidate numbers (candidates) (without duplicates) and a target number (target), find all unique combinations in candidates where the candidate numbers sums to target. 8 | 9 | The same repeated number may be chosen from candidates unlimited number of times. 10 | 11 | Note: 12 | 13 | - All numbers (including target) will be positive integers. 14 | - The solution set must not contain duplicate combinations. 15 | 16 | Example 1: 17 | 18 | Input: candidates = [2,3,6,7], target = 7, 19 | A solution set is: 20 | [ 21 | [7], 22 | [2,2,3] 23 | ] 24 | 25 | Example 2: 26 | 27 | Input: candidates = [2,3,5], target = 8, 28 | A solution set is: 29 | [ 30 | [2,2,2,2], 31 | [2,3,3], 32 | [3,5] 33 | ] 34 | 35 | ### Python 36 | 37 | ```python 38 | class Solution(object): 39 | def combinationSum(self, candidates, target): 40 | """ 41 | :type candidates: List[int] 42 | :type target: int 43 | :rtype: List[List[int]] 44 | """ 45 | res = [] 46 | candidates.sort() 47 | self.dfs(target, 0, [], res, candidates) 48 | return res 49 | 50 | def dfs(self, target, index, tmp_list, res, nums): 51 | if target < 0: 52 | return 53 | if target == 0: 54 | res.append(tmp_list) 55 | return 56 | for i in xrange(index, len(nums)): 57 | self.dfs(target - nums[i], i, tmp_list + [nums[i]], res, nums) 58 | ``` 59 | 60 | 61 | ## 40. Combination Sum II 62 | 63 | [原题链接](https://leetcode.com/problems/combination-sum-ii/description/) 64 | 65 | ### 题目 66 | 67 | Given a collection of candidate numbers (candidates) and a target number (target), find all unique combinations in candidates where the candidate numbers sums to target. 68 | 69 | Each number in candidates may only be used once in the combination. 70 | 71 | Note: 72 | 73 | - All numbers (including target) will be positive integers. 74 | - The solution set must not contain duplicate combinations. 75 | 76 | Example 1: 77 | 78 | Input: candidates = [10,1,2,7,6,1,5], target = 8, 79 | A solution set is: 80 | [ 81 | [1, 7], 82 | [1, 2, 5], 83 | [2, 6], 84 | [1, 1, 6] 85 | ] 86 | 87 | Example 2: 88 | 89 | Input: candidates = [2,5,2,1,2], target = 5, 90 | A solution set is: 91 | [ 92 | [1,2,2], 93 | [5] 94 | ] 95 | 96 | ### Python 97 | 98 | ```python 99 | class Solution(object): 100 | def combinationSum2(self, candidates, target): 101 | """ 102 | :type candidates: List[int] 103 | :type target: int 104 | :rtype: List[List[int]] 105 | """ 106 | res = [] 107 | candidates.sort() 108 | self.dfs(target, 0, [], res, candidates) 109 | return res 110 | 111 | def dfs(self, target, index, tmp_list, res, nums): 112 | if target < 0: 113 | return 114 | if target == 0: 115 | if tmp_list not in res: 116 | res.append(tmp_list) 117 | return 118 | for i in xrange(index, len(nums)): 119 | self.dfs(target - nums[i], i + 1, tmp_list + [nums[i]], res, nums) 120 | ``` 121 | 122 | ## 365. 水壶问题 123 | 124 | [原题链接](https://leetcode-cn.com/problems/water-and-jug-problem/) 125 | 126 | ### 解一:深度优先搜索 127 | 128 | 列举每次倒水的几种情况: 129 | 130 | 1. 把 x 倒空 131 | 2. 把 x 倒满 132 | 3. 把 y 倒空 133 | 4. 把 y 倒满 134 | 5. 把 x 倒入 y,直到 y 满或 x 空 135 | 6. 把 y 倒入 x,直到 x 满或 y 空 136 | 137 | 因为 Python 中会超过最大递归次数,所以用栈模拟递归过程。 138 | 139 | ```python 140 | class Solution: 141 | def canMeasureWater(self, x: int, y: int, z: int) -> bool: 142 | # (x 剩余,y 剩余) 143 | stack = [(0, 0)] 144 | # 标记重复 145 | mark = set() 146 | while len(stack) > 0: 147 | remain_x, remain_y = stack.pop() 148 | if remain_x == z or remain_y == z or remain_x + remain_y == z: 149 | return True 150 | # 是否已标记过该情况 151 | if (remain_x, remain_y) in mark: 152 | continue 153 | mark.add((remain_x, remain_y)) 154 | # 分情况讨论 155 | # 倒满 x 156 | stack.append((x, remain_y)) 157 | # 倒满 y 158 | stack.append((remain_x, y)) 159 | # 清空 x 160 | stack.append((0, remain_y)) 161 | # 清空 y 162 | stack.append((remain_x, 0)) 163 | # 把 x 倒进 y,直到 y 满或 x 空 164 | stack.append((remain_x - min(remain_x, y - remain_y), remain_y + min(remain_x, y - remain_y))) 165 | # 把 y 倒进 x,直到 x 满或 y 空 166 | stack.append((remain_x + min(remain_y, x - remain_x), remain_y - min(remain_y, x - remain_x))) 167 | return False 168 | ``` 169 | 170 | ## 695. 岛屿的最大面积 171 | 172 | [原题链接](https://leetcode-cn.com/problems/max-area-of-island/) 173 | 174 | ### 深度优先搜索 175 | 176 | 设计一个递归函数:输入坐标 `(i, j)`,返回该位置能构成岛屿的最大面积。 177 | 178 | - 如果该位置不是岛屿,返回 0 179 | - 如果该位置是岛屿,计算其上下左右位置的面积并相加(此过程不断递归) 180 | 181 | ```python 182 | class Solution: 183 | def maxAreaOfIsland(self, grid: List[List[int]]) -> int: 184 | res = 0 185 | for i in range(len(grid)): 186 | for j in range(len(grid[0])): 187 | res = max(self.dfs(grid, i, j), res) 188 | return res 189 | 190 | def dfs(self, grid, i, j): 191 | """ 192 | 返回面积 193 | """ 194 | if i < 0 or j < 0 or i >= len(grid) or j >= len(grid[0]) or grid[i][j] != 1: 195 | return 0 196 | # 避免重复计算 197 | grid[i][j] = 0 198 | # 是岛屿,面积为 1 199 | ans = 1 200 | # 四个方向 201 | for di, dj in [[0, -1], [0, 1], [-1, 0], [1, 0]]: 202 | # 计算四个方向总面积 203 | ans += self.dfs(grid, i + di, j + dj) 204 | return ans 205 | ``` -------------------------------------------------------------------------------- /docs/algorithm/sliding-window/README.md: -------------------------------------------------------------------------------- 1 | ## 219. 存在重复元素 II 2 | 3 | [原题链接](https://leetcode-cn.com/problems/contains-duplicate-ii/) 4 | 5 | ### 思路 6 | 7 | 滑动窗口 8 | 9 | ```python 10 | class Solution(object): 11 | def containsNearbyDuplicate(self, nums, k): 12 | """ 13 | :type nums: List[int] 14 | :type k: int 15 | :rtype: bool 16 | """ 17 | if len(nums) <= 1 or k <= 0: 18 | return False 19 | 20 | record = set() 21 | for i in range(len(nums)): 22 | num = nums[i] 23 | 24 | if num in record: 25 | return True 26 | 27 | record.add(num) 28 | if len(record) >= k + 1: 29 | record.remove(nums[i - k]) 30 | 31 | return False 32 | ``` 33 | 34 | 超时方法: 35 | 36 | ```python 37 | class Solution: 38 | def containsNearbyDuplicate(self, nums: List[int], k: int) -> bool: 39 | length = len(nums) 40 | i = 0 41 | while i < length: 42 | j = i + 1 43 | while j < length and j <= i + k: 44 | if nums[i] == nums[j]: 45 | return True 46 | j += 1 47 | i += 1 48 | return False 49 | ``` 50 | 51 | ## 239. 滑动窗口最大值 52 | 53 | [原题链接](https://leetcode-cn.com/problems/sliding-window-maximum/) 54 | 55 | ### 解一:暴力 56 | 57 | 不断对比得出每个窗口的最大值。 58 | 59 | ```python 60 | class Solution: 61 | def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]: 62 | length = len(nums) 63 | if length == 0 or k == 0: 64 | return [] 65 | max_num = max(nums[:k]) 66 | res = [max_num] 67 | for i in range(k, length): 68 | if nums[i] > max_num: 69 | max_num = nums[i] 70 | else: 71 | if nums[i - k] == max_num: 72 | max_num = max(nums[i - k + 1:i+1]) 73 | res.append(max_num) 74 | 75 | return res 76 | ``` 77 | 78 | ### 解二:动态规划 79 | 80 | 把数组按 k 从左到右分为 n 个窗口。 81 | 82 | - `left[i]` 表示从左到右遍历时从窗口开始位置(左边界)到下标 `i` 窗口内的最大值 83 | - `right[i]` 表示从右到左遍历时从窗口开始位置(右边界)到下标 `i` 窗口内的最大值 84 | 85 | 当窗口移动到下标 `x` 位置时,要求的结果为 `max(right[x], left[x + k - 1])`。 86 | 87 | ```python 88 | class Solution: 89 | def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]: 90 | res = [] 91 | length = len(nums) 92 | if length == 0 or k == 0: 93 | return [] 94 | 95 | # 构造 left 96 | left = [0 for _ in range(length)] 97 | for i in range(length): 98 | if i % k == 0: 99 | left[i] = nums[i] 100 | else: 101 | left[i] = max(left[i - 1], nums[i]) 102 | 103 | # 构造 right 104 | right = [0 for _ in range(length)] 105 | for i in range(length - 1, -1, -1): 106 | if i % k == 0 or i == length - 1: 107 | right[i] = nums[i] 108 | else: 109 | right[i] = max(right[i + 1], nums[i]) 110 | 111 | for i in range(length - k + 1): 112 | res.append(max(right[i], left[i + k - 1])) 113 | 114 | return res 115 | ``` 116 | 117 | 该方法时间复杂度 $O(n)$,空间复杂度 $O(n)$。 118 | 119 | 120 | ## 567. 字符串的排列 121 | 122 | [原题链接](https://leetcode-cn.com/problems/permutation-in-string/) 123 | 124 | ### 思路 125 | 126 | 计算 s2 中是否存在子串与 s1 拥有相同字母数 127 | 128 | - 先计算 s1 每个字母出现次数 129 | - 在 s2 上放置 "滑动窗口",计算与 s1 等长的字串是否拥有与 s1 相同的字符数 130 | 131 | ```python 132 | class Solution: 133 | def checkInclusion(self, s1: str, s2: str) -> bool: 134 | s1_length = len(s1) 135 | s2_length = len(s2) 136 | if s1_length > s2_length: 137 | return False 138 | 139 | count1 = [0 for _ in range(26)] 140 | count2 = [0 for _ in range(26)] 141 | ord_a = ord('a') 142 | 143 | # s1 字符个数记录 144 | for c in s1: 145 | count1[ord(c) - ord_a] += 1 146 | 147 | # s2 第一个窗口字符记录 148 | for i in range(s1_length): 149 | count2[ord(s2[i]) - ord_a] += 1 150 | 151 | """ 152 | 是否匹配 153 | """ 154 | def is_match(count1, count2): 155 | for i in range(len(count1)): 156 | if count1[i] != count2[i]: 157 | return False 158 | return True 159 | 160 | if is_match(count1, count2): 161 | return True 162 | 163 | for i in range(s1_length, s2_length): 164 | left = ord(s2[i - s1_length]) - ord_a 165 | count2[left] -= 1 166 | count2[ord(s2[i]) - ord_a] += 1 167 | if is_match(count1, count2): 168 | return True 169 | 170 | return False 171 | ``` 172 | 173 | - 时间复杂度:$O(l1 + (l2 - l1) * 26)$($l1$ 为字符串 s1 长度,$l2$ 为字符串 s2 长度) 174 | - 空间复杂度:$O(1)$ 175 | 176 | ### 优化方案 177 | 178 | 维护一个变量 `count` 用于记录相同字母的位数,如果最终 `count == 26` 则说明完全包含。 179 | 180 | ```python 181 | class Solution: 182 | def checkInclusion(self, s1: str, s2: str) -> bool: 183 | length1 = len(s1) 184 | length2 = len(s2) 185 | if length2 < length1: 186 | return False 187 | 188 | count1 = [0 for _ in range(26)] 189 | count2 = [0 for _ in range(26)] 190 | 191 | ord_a = ord('a') 192 | 193 | for i in range(length1): 194 | c1 = s1[i] 195 | c2 = s2[i] 196 | count1[ord(c1) - ord_a] += 1 197 | count2[ord(c2) - ord_a] += 1 198 | 199 | # 计算相同字母位数 200 | count = 0 201 | for i in range(26): 202 | if count1[i] == count2[i]: 203 | count += 1 204 | 205 | for i in range(length1, length2): 206 | 207 | if count == 26: 208 | return True 209 | 210 | left_c = s2[i - length1] 211 | left_index = ord(left_c) - ord_a 212 | right_c = s2[i] 213 | right_index = ord(right_c) - ord_a 214 | 215 | if count2[left_index] == count1[left_index]: 216 | count -= 1 217 | if count2[right_index] == count1[right_index]: 218 | count -= 1 219 | 220 | count2[left_index] -= 1 221 | count2[right_index] += 1 222 | 223 | if count2[left_index] == count1[left_index]: 224 | count += 1 225 | if count2[right_index] == count1[right_index]: 226 | count += 1 227 | 228 | return count == 26 229 | ``` 230 | 231 | - 时间复杂度:$O(l1 + (l2 - l1))$($l1$ 为字符串 s1 长度,$l2$ 为字符串 s2 长度) 232 | - 空间复杂度:$O(1)$ 233 | 234 | ## 1248. 统计「优美子数组」 235 | 236 | [原题链接](https://leetcode-cn.com/problems/count-number-of-nice-subarrays/) 237 | 238 | ### 滑动窗口 239 | 240 | 记录奇数的位置。固定 k 个奇数,子数组的个数 = 第一个奇数左边偶数的个数 * 最后一个奇数右边偶数的个数。 241 | 242 | ```python 243 | class Solution: 244 | def numberOfSubarrays(self, nums: List[int], k: int) -> int: 245 | ans = 0 246 | # 开始位置 247 | odd = [-1] 248 | # 记录奇数下标 249 | for i in range(len(nums)): 250 | if nums[i] % 2 == 1: 251 | odd.append(i) 252 | # 添加结束位置 253 | odd.append(len(nums)) 254 | 255 | # 遍历奇数数组 256 | for j in range(1, len(odd) - k): 257 | # 从第 i 个到 i + k - 1 258 | # 第 i 个奇数前面的偶数个数 * 第 i + k - 1 后面的偶数个数 259 | ans += (odd[j] - odd[j - 1]) * (odd[j + k] - odd[j + k - 1]) 260 | 261 | return ans 262 | ``` -------------------------------------------------------------------------------- /docs/algorithm/sort/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ## 堆排序 5 | 6 | - 最大堆 7 | - 节点的元素都要不小于其孩子 8 | - 常用与升序排序 9 | - 最小堆: 10 | - 节点元素都不大于其左右孩子 11 | - 常用于降序排序 12 | 13 | ### 复杂度 14 | 15 | ### 资料 16 | 17 | - [图解排序算法(三)之堆排序](https://www.cnblogs.com/chengxiao/p/6129630.html) 18 | - [堆排序详解](https://www.cnblogs.com/0zcl/p/6737944.html) 19 | 20 | 21 | 22 | 23 | 24 | ## 快速排序 25 | 26 | 27 | 28 | 29 | ## 179. 最大数 30 | 31 | [原题链接](https://leetcode-cn.com/problems/largest-number/) 32 | 33 | ### 思路 34 | 35 | 需要自定义排序算法,我这里用的是冒泡的思想,核心思路就是比较两个数 `a + b` 和 `b + a` 的排列哪个更大。 36 | 37 | 例如在 `[a, b]` 的情况下,如果 `b + a` 的组合产生的数字大于 `a + b` 组合,则交换 `a` `b` 的位置,即把能产生更大结果的 `b` 字符"**冒**"到上面来,变为 `[b, a]`。 38 | 39 | ```python 40 | class Solution(object): 41 | def largestNumber(self, nums): 42 | """ 43 | :type nums: List[int] 44 | :rtype: str 45 | """ 46 | length = len(nums) 47 | 48 | # 冒泡排序思想 49 | i = length 50 | while i > 0: 51 | for j in range(i - 1): 52 | # 自定义的排序算法:比较 a+b 和 b+a 哪个更大 53 | a = nums[j] 54 | b = nums[j + 1] 55 | ab = int(str(a) + str(b)) 56 | ba = int(str(b) + str(a)) 57 | if ba > ab: 58 | nums[j], nums[j + 1] = nums[j + 1], nums[j] 59 | i -= 1 60 | 61 | # 结果字符串拼接 62 | res = "" 63 | for n in nums: 64 | # 这里防止 [0, 0, 0] 的情况下 res 返回多个 0 65 | if res == "" and n == 0: 66 | continue 67 | res += str(n) 68 | 69 | if res == "": 70 | return "0" 71 | else: 72 | return res 73 | ``` 74 | 75 | 76 | ## 315. 计算右侧小于当前元素的个数 77 | 78 | [原题链接](https://leetcode-cn.com/problems/count-of-smaller-numbers-after-self/) 79 | 80 | ### 解法一:暴力破解 81 | 82 | 暴力破解时间复杂度 `O(n^2)` 过高,导致超时。 83 | 84 | ```python 85 | class Solution(object): 86 | def countSmaller(self, nums): 87 | """ 88 | :type nums: List[int] 89 | :rtype: List[int] 90 | """ 91 | length = len(nums) 92 | res = [0 for _ in range(length)] 93 | 94 | for i in range(length): 95 | t = nums[i] 96 | count = 0 97 | for j in range(i, length): 98 | if nums[j] < t: 99 | count += 1 100 | res[i] = count 101 | 102 | return res 103 | ``` 104 | 105 | ### 解法二:二叉搜索树 106 | 107 | 利用二叉搜索树的特性:左边节点的值小于等于当前节点值,右边节点的值大于等于当前节点值。 108 | 109 | 那么实现算法首先要构建一颗二叉搜索树: 110 | 111 | 1. 定义树的节点结构 `TreeNode` 112 | 2. 实现树的节点插入方法 `insertNode` 113 | 114 | 其中, `insertNode` 方法需要实现几个功能: 115 | 116 | 1. 构建二叉树 117 | 2. 维护每个节点中其左子树节点数量值 `count`:如果新加入的节点需要加入当前节点的左子树,则当前节点的 `count += 1` 118 | 3. 计算出新加入节点 `nums[i]` 的 "右侧小于当前元素的个数",即题目所求值 `res[i]` 119 | 120 | 附 Python 代码: 121 | 122 | 123 | ```python 124 | # 定义一个树的节点类 125 | class TreeNode(object): 126 | def __init__(self, val): 127 | self.left = None 128 | self.right = None 129 | self.val = val # 节点值 130 | self.count = 0 # 左子树节点数量 131 | 132 | class Solution(object): 133 | def countSmaller(self, nums): 134 | """ 135 | :type nums: List[int] 136 | :rtype: List[int] 137 | """ 138 | length = len(nums) 139 | root = None 140 | # 结果集 141 | res = [0 for _ in range(length)] 142 | # nums 反序加入搜索树 143 | for i in reversed(range(length)): 144 | root = self.insertNode(root, nums[i], res, i) 145 | return res 146 | 147 | # 往二叉搜索树中插入新的节点 148 | def insertNode(self, root, val, res, res_index): 149 | if root == None: 150 | root = TreeNode(val) 151 | elif val <= root.val: # 小于当前节点值则放入左子树 152 | # root 的左侧节点数量值 +1 153 | root.count += 1 154 | root.left = self.insertNode(root.left, val, res, res_index) 155 | elif val > root.val: # 大于当前节点值则放入右子树 156 | # 计算题目所求的结果 157 | res[res_index] += root.count + 1 158 | root.right = self.insertNode(root.right, val, res, res_index) 159 | 160 | return root 161 | ``` 162 | 163 | ---- 164 | 165 | 这里再解释几个问题: 166 | 167 | 一、为什么 `nums` 需要反序加入二叉搜索树? 168 | 169 | 如果反序加入二叉搜索树,对于节点 `nums[i]` 来说,当它加入二叉搜索树时,它右边的元素都已经在树中了,那么它加入时就可以直接确认 "右侧小于当前元素的个数",也就是说我们可以在构建二叉搜索树的过程中直接求出 `res[i]`。 170 | 171 | 二、`res[res_index] += root.count + 1` 是为什么? 172 | 173 | 这句表达式的解释是: 174 | 175 | 当 `nums[res_index]` 加入 `root` 的右子树时,小于 `nums[res_index]` 的元素个数为:`root` 左子树的所有节点 + `root` 本身。 176 | 177 | 那个 `+1` 代表的就是 `root` 自己啦。 178 | 179 | ---- 180 | 181 | 唠唠叨叨的也不知道有没有讲清楚,就这样吧…… `_(┐「ε:)_` 182 | 183 | 184 | 185 | 186 | ## 324. 摆动排序 II 187 | 188 | [原题链接](https://leetcode-cn.com/problems/wiggle-sort-ii/submissions/) 189 | 190 | ### 思路 191 | 192 | 1. 将 nums 排序,分为大数列表与小数列表 193 | 2. 对列表进行反转 194 | 3. 对列表进行穿插求得结果,穿插规则如下: 195 | 196 | ``` 197 | [小数1,大数1,小数2,大数2 ……] 198 | ``` 199 | 200 | ```python 201 | class Solution(object): 202 | def wiggleSort(self, nums): 203 | """ 204 | :type nums: List[int] 205 | :rtype: None Do not return anything, modify nums in-place instead. 206 | """ 207 | nums.sort() 208 | half = len(nums[::2]) 209 | 210 | nums[::2], nums[1::2] = nums[:half][::-1], nums[half:][::-1] 211 | ``` 212 | 213 | 214 | 215 | -------------------------------------------------------------------------------- /docs/algorithm/sort/bubble/README.md: -------------------------------------------------------------------------------- 1 | ## 179. 最大数 2 | 3 | [原题链接](https://leetcode-cn.com/problems/largest-number/) 4 | 5 | ### 思路 6 | 7 | 需要自定义排序算法,我这里用的是冒泡的思想,核心思路就是比较两个数 `a + b` 和 `b + a` 的排列哪个更大。 8 | 9 | 例如在 `[a, b]` 的情况下,如果 `b + a` 的组合产生的数字大于 `a + b` 组合,则交换 `a` `b` 的位置,即把能产生更大结果的 `b` 字符"**冒**"到上面来,变为 `[b, a]`。 10 | 11 | ```python 12 | class Solution(object): 13 | def largestNumber(self, nums): 14 | """ 15 | :type nums: List[int] 16 | :rtype: str 17 | """ 18 | length = len(nums) 19 | 20 | # 冒泡排序思想 21 | i = length 22 | while i > 0: 23 | for j in range(i - 1): 24 | # 自定义的排序算法:比较 a+b 和 b+a 哪个更大 25 | a = nums[j] 26 | b = nums[j + 1] 27 | ab = int(str(a) + str(b)) 28 | ba = int(str(b) + str(a)) 29 | if ba > ab: 30 | nums[j], nums[j + 1] = nums[j + 1], nums[j] 31 | i -= 1 32 | 33 | # 结果字符串拼接 34 | res = "" 35 | for n in nums: 36 | # 这里防止 [0, 0, 0] 的情况下 res 返回多个 0 37 | if res == "" and n == 0: 38 | continue 39 | res += str(n) 40 | 41 | if res == "": 42 | return "0" 43 | else: 44 | return res 45 | ``` -------------------------------------------------------------------------------- /docs/algorithm/sort/heap/README.md: -------------------------------------------------------------------------------- 1 | ## 215. Kth Largest Element in an Array 2 | 3 | [原题链接](https://leetcode-cn.com/problems/kth-largest-element-in-an-array/comments/) 4 | 5 | ### 思路 6 | 7 | - 堆排 8 | - 在重新调整堆时弹出第 k 个元素 9 | - 时间复杂度 O(NlogK),空间复杂度 O(K) 10 | 11 | ```python 12 | class Solution(object): 13 | def findKthLargest(self, nums, k): 14 | """ 15 | :type nums: List[int] 16 | :type k: int 17 | :rtype: int 18 | """ 19 | length = len(nums) 20 | # 构建大顶堆 21 | for i in reversed(range(0, length / 2)): 22 | self.adjustHeap(nums, i, length) 23 | 24 | count = 0 25 | for j in reversed(range(0, length)): 26 | count = count + 1 27 | if count == k: 28 | return nums[0] 29 | self.swap(nums, 0, j) 30 | length = length - 1 31 | self.adjustHeap(nums, 0, length) 32 | 33 | 34 | def adjustHeap(self, nums, i, length): 35 | while True: 36 | k = i * 2 + 1 #left node 37 | if k >= length: 38 | return 39 | if k + 1 < length and nums[k+1] > nums[k]: 40 | k = k + 1 41 | if nums[k] > nums[i]: 42 | self.swap(nums, i, k) 43 | i = k 44 | else: 45 | return 46 | 47 | def swap(self, nums, i, j): 48 | temp = nums[i] 49 | nums[i] = nums[j] 50 | nums[j] = temp 51 | ``` 52 | 53 | 之前傻逼了,全部排序完才弹出。 54 | 55 | ```python 56 | # -*- coding: utf-8 -*- 57 | class Solution(object): 58 | def findKthLargest(self, nums, k): 59 | """ 60 | :type nums: List[int] 61 | :type k: int 62 | :rtype: int 63 | """ 64 | # 构建堆 65 | length = len(nums) 66 | for i in reversed(range(0, length/2)): 67 | nums = self.adjustHeap(nums, i, length) 68 | # 交换元素 69 | for j in reversed(range(0, length)): 70 | nums = self.swap(nums, j, 0) 71 | length = length - 1 72 | nums = self.adjustHeap(nums, 0, length) 73 | return nums[-k] 74 | 75 | def adjustHeap(self, nums, i, length): 76 | """ 77 | :type nums: List[int] heap 78 | :type i: int 当前节点 79 | :type length: int heap长度 80 | """ 81 | while True: 82 | k = i * 2 + 1 83 | if k >= length: 84 | break 85 | if (k + 1 < length) and (nums[k + 1] > nums[k]): 86 | k = k + 1 87 | if nums[k] > nums[i]: 88 | self.swap(nums, k, i) 89 | i = k 90 | else: 91 | break 92 | return nums 93 | 94 | def swap(self, nums, a, b): 95 | """ 96 | :param nums: List[int] heap 97 | :param a: int 98 | :param b: int 99 | :return: List[int] 100 | """ 101 | temp = nums[a] 102 | nums[a] = nums[b] 103 | nums[b] = temp 104 | return nums 105 | ``` 106 | 107 | ## 347. 前K个高频元素 108 | 109 | [原题链接](https://leetcode-cn.com/problems/top-k-frequent-elements/) 110 | 111 | ### 思路 112 | 113 | 1. 使用哈希表对每个元素出现的次数进行统计 114 | 2. 根据元素出现次数进行排序处理 115 | 116 | 根据题目对复杂度的要求,选择了堆排序。需要维护一个大顶堆。 117 | 118 | ```python 119 | class Solution(object): 120 | def topKFrequent(self, nums, k): 121 | """ 122 | :type nums: List[int] 123 | :type k: int 124 | :rtype: List[int] 125 | """ 126 | count_dict = dict() 127 | for n in nums: 128 | count_dict[n] = count_dict.get(n, 0) + 1 129 | 130 | count_list = list() 131 | for key in count_dict: 132 | count_list.append((key, count_dict[key])) 133 | 134 | length = len(count_list) 135 | for i in range(length/2 - 1, -1, -1): 136 | self.adjust_heap(count_list, i, length) 137 | 138 | res = list() 139 | count = 0 140 | for j in range(length - 1, -1, -1): 141 | count += 1 142 | res.append(count_list[0][0]) 143 | # 将堆顶元素弹出 144 | self.swap(count_list, 0, j) 145 | if count == k: 146 | return res 147 | # 维护堆 148 | length -= 1 149 | self.adjust_heap(count_list, 0, length) 150 | 151 | def adjust_heap(self, nums, i, length): 152 | """ 153 | 维护堆 154 | """ 155 | while True: 156 | k = i * 2 + 1 157 | if k >= length: 158 | return 159 | # 左右节点取较小数 160 | if k + 1 < length and nums[k + 1][1] > nums[k][1]: 161 | k += 1 162 | if nums[k][1] > nums[i][1]: 163 | self.swap(nums, k, i) 164 | i = k 165 | else: 166 | return 167 | 168 | def swap(self, nums, i, j): 169 | """ 170 | 元素交换 171 | """ 172 | tmp = nums[i] 173 | nums[i] = nums[j] 174 | nums[j] = tmp 175 | ``` 176 | 177 | ### 复杂度 178 | 179 | - 时间复杂度:`O(nlogn)` 180 | - 空间复杂度:`O(n)` -------------------------------------------------------------------------------- /docs/algorithm/sort/other/README.md: -------------------------------------------------------------------------------- 1 | ## 315. 计算右侧小于当前元素的个数 2 | 3 | [原题链接](https://leetcode-cn.com/problems/count-of-smaller-numbers-after-self/) 4 | 5 | ### 解法一:暴力破解 6 | 7 | 暴力破解时间复杂度 `O(n^2)` 过高,导致超时。 8 | 9 | ```python 10 | class Solution(object): 11 | def countSmaller(self, nums): 12 | """ 13 | :type nums: List[int] 14 | :rtype: List[int] 15 | """ 16 | length = len(nums) 17 | res = [0 for _ in range(length)] 18 | 19 | for i in range(length): 20 | t = nums[i] 21 | count = 0 22 | for j in range(i, length): 23 | if nums[j] < t: 24 | count += 1 25 | res[i] = count 26 | 27 | return res 28 | ``` 29 | 30 | ### 解法二:二叉搜索树 31 | 32 | 利用二叉搜索树的特性:左边节点的值小于等于当前节点值,右边节点的值大于等于当前节点值。 33 | 34 | 那么实现算法首先要构建一颗二叉搜索树: 35 | 36 | 1. 定义树的节点结构 `TreeNode` 37 | 2. 实现树的节点插入方法 `insertNode` 38 | 39 | 其中, `insertNode` 方法需要实现几个功能: 40 | 41 | 1. 构建二叉树 42 | 2. 维护每个节点中其左子树节点数量值 `count`:如果新加入的节点需要加入当前节点的左子树,则当前节点的 `count += 1` 43 | 3. 计算出新加入节点 `nums[i]` 的 "右侧小于当前元素的个数",即题目所求值 `res[i]` 44 | 45 | 附 Python 代码: 46 | 47 | 48 | ```python 49 | # 定义一个树的节点类 50 | class TreeNode(object): 51 | def __init__(self, val): 52 | self.left = None 53 | self.right = None 54 | self.val = val # 节点值 55 | self.count = 0 # 左子树节点数量 56 | 57 | class Solution(object): 58 | def countSmaller(self, nums): 59 | """ 60 | :type nums: List[int] 61 | :rtype: List[int] 62 | """ 63 | length = len(nums) 64 | root = None 65 | # 结果集 66 | res = [0 for _ in range(length)] 67 | # nums 反序加入搜索树 68 | for i in reversed(range(length)): 69 | root = self.insertNode(root, nums[i], res, i) 70 | return res 71 | 72 | # 往二叉搜索树中插入新的节点 73 | def insertNode(self, root, val, res, res_index): 74 | if root == None: 75 | root = TreeNode(val) 76 | elif val <= root.val: # 小于当前节点值则放入左子树 77 | # root 的左侧节点数量值 +1 78 | root.count += 1 79 | root.left = self.insertNode(root.left, val, res, res_index) 80 | elif val > root.val: # 大于当前节点值则放入右子树 81 | # 计算题目所求的结果 82 | res[res_index] += root.count + 1 83 | root.right = self.insertNode(root.right, val, res, res_index) 84 | 85 | return root 86 | ``` 87 | 88 | ---- 89 | 90 | 这里再解释几个问题: 91 | 92 | 一、为什么 `nums` 需要反序加入二叉搜索树? 93 | 94 | 如果反序加入二叉搜索树,对于节点 `nums[i]` 来说,当它加入二叉搜索树时,它右边的元素都已经在树中了,那么它加入时就可以直接确认 "右侧小于当前元素的个数",也就是说我们可以在构建二叉搜索树的过程中直接求出 `res[i]`。 95 | 96 | 二、`res[res_index] += root.count + 1` 是为什么? 97 | 98 | 这句表达式的解释是: 99 | 100 | 当 `nums[res_index]` 加入 `root` 的右子树时,小于 `nums[res_index]` 的元素个数为:`root` 左子树的所有节点 + `root` 本身。 101 | 102 | 那个 `+1` 代表的就是 `root` 自己啦。 103 | 104 | ---- 105 | 106 | 唠唠叨叨的也不知道有没有讲清楚,就这样吧…… `_(┐「ε:)_` 107 | 108 | ## 324. 摆动排序 II 109 | 110 | [原题链接](https://leetcode-cn.com/problems/wiggle-sort-ii/submissions/) 111 | 112 | ### 思路 113 | 114 | 1. 将 nums 排序,分为大数列表与小数列表 115 | 2. 对列表进行反转 116 | 3. 对列表进行穿插求得结果,穿插规则如下: 117 | 118 | ``` 119 | [小数1,大数1,小数2,大数2 ……] 120 | ``` 121 | 122 | ```python 123 | class Solution(object): 124 | def wiggleSort(self, nums): 125 | """ 126 | :type nums: List[int] 127 | :rtype: None Do not return anything, modify nums in-place instead. 128 | """ 129 | nums.sort() 130 | half = len(nums[::2]) 131 | 132 | nums[::2], nums[1::2] = nums[:half][::-1], nums[half:][::-1] 133 | ``` 134 | 135 | ## 973. 最接近原点的 K 个点 136 | 137 | [原题链接](https://leetcode-cn.com/problems/k-closest-points-to-origin/) 138 | 139 | 本质是排序。按照欧几里得距离从小到大对 `points` 进行升序排序,然后取出前 K 个点即可。 140 | 141 | 其实就是三个步骤: 142 | 143 | 1. 计算距离 144 | 2. 排序 145 | 3. 取 K 个 146 | 147 | ### 解一:偷懒大法 148 | 149 | 直接用系统提供的排序函数进行排序。 150 | 151 | ```python 152 | class Solution: 153 | def kClosest(self, points: List[List[int]], K: int) -> List[List[int]]: 154 | distance = [] 155 | res = [] 156 | for i in range(len(points)): 157 | point = points[i] 158 | dis = point[0]**2 + point[1]**2 159 | distance.append([i, dis]) 160 | 161 | distance.sort(key=lambda x:x[1]) 162 | 163 | for i in range(K): 164 | res.append(points[distance[i][0]]) 165 | return res 166 | ``` 167 | 168 | 上面写的比较啰嗦,可以用列表生成式简化一下: 169 | 170 | ```python 171 | class Solution: 172 | def kClosest(self, points: List[List[int]], K: int) -> List[List[int]]: 173 | points.sort(key=lambda x: x[0]**2 + x[1] ** 2) 174 | return points[:K] 175 | ``` 176 | 177 | ### 解二:快速排序 178 | 179 | 这里的快速排序无需使整个数组有序,只需要筛选出最小的 K 个值即可。 180 | 181 | 假设第一次排序后,哨兵值 `pivot` 将原数组分为两个部分: 182 | 183 | 1. 左侧部分,元素值均小于 `pivot`,假设下标范围是 `[begin, i - 1]`,长度为 `left_length` 184 | 2. 右侧部分,元素值均大于或等于 `pivot`,假设下标范围是 `[i + 1, end]`,长度为 `right_length` 185 | 186 | 此时: 187 | 188 | - 如果 `left_length >= K`,最小的 `K` 个值均在左侧,因此下轮递归只需对左侧部分进行排序 189 | - 如果 `left_length < K`,我们已经获得了 `left_length` 个最小值,因此下轮递归只需对右侧部分进行排序 190 | 191 | 该算法的时间复杂度为 O(n), 空间复杂度为 O(1): 192 | 193 | 1.空间复杂度不用多说, 我们直接在原数组上做操作,所以不用开出多余的空间,故空间复杂度为O(1) 194 | 195 | 2.时间复杂度为 O(n): 在理想的情况下,我们每次定位到的 `pivot` 都为 end - start 的一半的话。那么下一次查找 `pivot` 的时间就为上一次的 1/2。 196 | 197 | 那么,最终的时间复杂度就为: O(n + n/2 + n/4 + ... + 1) , 约等于 O(n)。 198 | 199 | 200 | #### ** Python ** 201 | 202 | ```python 203 | class Solution: 204 | def kClosest(self, points: List[List[int]], K: int) -> List[List[int]]: 205 | # 计算欧几里得距离 206 | distance = lambda i: points[i][0] ** 2 + points[i][1] ** 2 207 | 208 | def work(i, j, K): 209 | if i > j: 210 | return 211 | # 记录初始值 212 | oi, oj = i, j 213 | # 取最左边为哨兵值 214 | pivot = distance(oi) 215 | while i != j: 216 | while i < j and distance(j) >= pivot: 217 | j -= 1 218 | while i < j and distance(i) <= pivot: 219 | i += 1 220 | if i < j: 221 | # 交换值 222 | points[i], points[j] = points[j], points[i] 223 | 224 | # 交换哨兵 225 | points[i], points[oi] = points[oi], points[i] 226 | 227 | # 递归 228 | if K <= i - oi + 1: 229 | # 左半边排序 230 | work(oi, i - 1, K) 231 | else: 232 | # 右半边排序 233 | work(i + 1, oj, K - (i - oi + 1)) 234 | 235 | work(0, len(points) - 1, K) 236 | return points[:K] 237 | ``` 238 | 239 | #### ** PHP ** 240 | 241 | ```php 242 | class Solution { 243 | 244 | /** 245 | * @param Integer[][] $points 246 | * @param Integer $K 247 | * @return Integer[][] 248 | */ 249 | function kClosest($points, $K) { 250 | $length = count($points); 251 | $this->quickSelect($points, 0, $length - 1, $K); 252 | return array_slice($points, 0, $K); 253 | } 254 | 255 | // 快速选择 256 | function quickSelect(&$points, $i, $j, $k) { 257 | 258 | // 递归结束条件 259 | if ($i > $j) { 260 | return; 261 | } 262 | 263 | $begin = $i; 264 | $end = $j; 265 | // 哨兵 266 | $pivot = $this->getDistance($points, $begin); 267 | 268 | while ($i != $j) { 269 | while ($i < $j && $this->getDistance($points, $j) >= $pivot) { 270 | $j--; 271 | } 272 | while ($i < $j && $this->getDistance($points, $i) <= $pivot) { 273 | $i++; 274 | } 275 | if ($i < $j) { 276 | // 交换 277 | $tmp = $points[$i]; 278 | $points[$i] = $points[$j]; 279 | $points[$j] = $tmp; 280 | } 281 | } 282 | 283 | // 交换哨兵 284 | $tmp = $points[$begin]; 285 | $points[$begin] = $points[$i]; 286 | $points[$i] = $tmp; 287 | 288 | // 递归 289 | if ($i - $begin + 1 >= $k) { 290 | $this->quickSelect($points, $begin, $i - 1, $k); 291 | } else { 292 | $this->quickSelect($points, $i + 1, $end, $k - ($i - $begin + 1)); 293 | } 294 | } 295 | 296 | // 计算欧几里得距离 297 | function getDistance($points, $x) { 298 | return $points[$x][0]*$points[$x][0] + $points[$x][1] * $points[$x][1]; 299 | } 300 | } 301 | ``` 302 | 303 | #### ** Java ** 304 | 305 | ```java 306 | class Solution { 307 | public int[][] kClosest(int[][] points, int K) { 308 | int start = 0; 309 | int end = points.length - 1; 310 | while (start < end) { 311 | # 计算欧几里得距离 312 | int index = patition(points, start, end); 313 | if (index == K) { 314 | break; 315 | } else if (index < K) { 316 | start = index + 1; 317 | } else { 318 | end = index - 1; 319 | } 320 | } 321 | 322 | return Arrays.copyOf(points, K); 323 | } 324 | 325 | private int patition(int[][] points, int start, int end) { 326 | int i = start; 327 | int j = end + 1; 328 | int mid = distance(points[i][0], points[i][1]); 329 | while (true) { 330 | while (distance(points[++i][0], points[i][1]) < mid && i < end); 331 | while (distance(points[--j][0], points[j][1]) > mid && j > start); 332 | if (i >= j) { 333 | break; 334 | } 335 | swap(points, i, j); 336 | } 337 | swap(points, start, j); 338 | return j; 339 | } 340 | 341 | private int distance(int a, int b) { 342 | return a * a + b * b; 343 | } 344 | 345 | private void swap(int[][] points, int a, int b) { 346 | int[] temp = points[a]; 347 | points[a] = points[b]; 348 | points[b] = temp; 349 | } 350 | } 351 | ``` 352 | 353 | 354 | 355 | ### 解三:堆排序 356 | 357 | 这个问题的本质其实就是对于点到原点的距离,求 Top K 元素。那么,除了排序的方法,以及快速排序以外,还可以利用 `堆` 来得到 Top K 的元素。 358 | 359 | 360 | 361 | #### ** Java ** 362 | 363 | ```java 364 | class Solution { 365 | public int[][] kClosest(int[][] points, int K) { 366 | // 在 `Java` 里面,可以利用优先队列:PriorityQueue 来处理,其内部实现是堆。 367 | Queue priorityQueue = new PriorityQueue<>(K, (o1, o2) -> o1[0] * o1[0] + o1[1] * o1[1] - o2[0] * o2[0] - o2[1] * o2[1]); 368 | for (int[] point : points) { 369 | priorityQueue.add(point); 370 | } 371 | int[][] result = new int[K][2]; 372 | for (int i = 0; i < K; i++) { 373 | result[i] = priorityQueue.poll(); 374 | } 375 | return result; 376 | } 377 | } 378 | ``` 379 | 380 | #### ** Python ** 381 | 382 | ```python 383 | from heapq import heappush, heappop 384 | 385 | class Solution: 386 | def kClosest(self, points: List[List[int]], K: int) -> List[List[int]]: 387 | queue = [] 388 | distance = lambda x: points[x][0]**2 + points[x][1]**2 389 | length = len(points) 390 | for i in range(length): 391 | heappush(queue, (distance(i), points[i])) 392 | res = [] 393 | for i in range(K): 394 | res.append(heappop(queue)[1]) 395 | return res 396 | ``` 397 | 398 | 知识点:[python 堆排序 heapq](https://www.jianshu.com/p/e003872fa7b9) 399 | 400 | 401 | 402 | -------------------------------------------------------------------------------- /docs/algorithm/sort/quick/README.md: -------------------------------------------------------------------------------- 1 | ## 75. Sort Colors 2 | 3 | https://leetcode.com/problems/sort-colors/description/ 4 | 5 | ### 方法一:快排 6 | 7 | ```python 8 | # -*- coding: utf-8 -*- 9 | 10 | 11 | class Solution(object): 12 | def sortColors(self, nums): 13 | """ 14 | :type nums: List[int] 15 | :rtype: void Do not return anything, modify nums in-place instead. 16 | """ 17 | self.quickSort(nums, 0, len(nums)-1) 18 | #print nums 19 | 20 | def quickSort(self, nums, left, right): 21 | if left > right: 22 | return 23 | i = left 24 | j = right 25 | tmp = nums[i] 26 | while i != j: 27 | while nums[j] >= tmp and j > i: 28 | j = j - 1 29 | while nums[i] <= tmp and i < j: 30 | i = i + 1 31 | if i < j: 32 | t = nums[i] 33 | nums[i] = nums[j] 34 | nums[j] = t 35 | # 交换基准数 36 | nums[left] = nums[i] 37 | nums[i] = tmp 38 | self.quickSort(nums, left, i-1) 39 | self.quickSort(nums, i+1, right) 40 | ``` 41 | 42 | ### 方法二:冒泡排序 43 | 44 | ```python 45 | # -*- coding: utf-8 -*- 46 | 47 | 48 | class Solution(object): 49 | def sortColors(self, nums): 50 | """ 51 | :type nums: List[int] 52 | :rtype: void Do not return anything, modify nums in-place instead. 53 | """ 54 | #self.quickSort(nums, 0, len(nums)-1) 55 | self.popSort(nums) 56 | 57 | 58 | def popSort(self, nums): 59 | i = 0 60 | j = len(nums) - 1 61 | while i < j: 62 | for index in range(0, j): 63 | a = nums[index] 64 | b = nums[index + 1] 65 | if a > b: 66 | nums[index + 1] = a 67 | nums[index] = b 68 | j = j - 1 69 | ``` 70 | 71 | ### 方法三:选择排序 72 | 73 | ```python 74 | # -*- coding: utf-8 -*- 75 | 76 | 77 | class Solution(object): 78 | def sortColors(self, nums): 79 | """ 80 | :type nums: List[int] 81 | :rtype: void Do not return anything, modify nums in-place instead. 82 | """ 83 | self.selectSort(nums) 84 | 85 | def selectSort(self, nums): 86 | i = 0 87 | while i < len(nums): 88 | index = 0 89 | min = nums[i] 90 | for j in range(i, len(nums)): 91 | if nums[j] < min: 92 | min = nums[j] #找到最小值 93 | index = j 94 | if i != j: 95 | tmp = nums[i] 96 | nums[i] = min 97 | nums[index] = tmp 98 | i = i + 1 99 | ``` -------------------------------------------------------------------------------- /docs/concept/base-algorithm/README.md: -------------------------------------------------------------------------------- 1 | ## 枚举 2 | 3 | 也称作**穷举**,从问题的所有可能解的集合里一一枚举各元素,用给定的条件判断哪些是**有用**的元素,这些元素即为解。 4 | 5 | 1. 给出解空间 —— 要枚举哪些元素 6 | 2. 减少枚举空间 7 | 3. 选择合适的枚举顺序 8 | 9 | ---- 10 | 11 | ## 递归 12 | 13 | > 递推的思维是正常人的思维,总是看着眼前的问题思考对策,解决问题是将来时;递归的思维,逼迫我们倒着思考,看到问题的尽头,把解决问题的过程看做过去时。 14 | 15 | 递归指:某个函数**直接或间接地调用自身**。这样一来,原问题就被转换为许多性质相同但规模更小的子问题。 16 | 17 | ### 关注点 18 | 19 | - 需要关注:如何把原问题划分成符合条件的子问题 20 | - 不需要关注:这个子问题是如何被解决的 21 | - 技巧:明白一个函数的作用并相信它能完成这个任务,但千万不要试图跳进细节 22 | 23 | ### 特征 24 | 25 | 1. 结束条件 26 | 2. 自我调用 27 | 28 | ``` 29 | int func(传入数值) { 30 | if (终止条件) return 最小子问题解; 31 | // 调用自己去解决规模更小的子问题,直到到达结束条件 32 | return func(缩小规模); 33 | } 34 | ``` 35 | 36 | ### 练习 37 | 38 | - 经典问题:归并排序 39 | - [LeetCode 递归练习](https://leetcode-cn.com/tag/recursion/) 40 | - [递归题解](algorithm/recursion/) 41 | - [树:递归题解](data-structure/tree/recursion) 42 | 43 | ---- 44 | 45 | ## 分治 46 | 47 | 分治算法的三个步骤:分解 -> 解决 -> 合并。 48 | 49 | 1. 分解原问题为结构相同的子问题 50 | 2. 分解到某个容易求解的边界之后,进行递归求解 51 | 3. 将子问题的解合并成原问题的解 52 | 53 | ### 归并排序 54 | 55 | [归并排序](http://jalan.space/interview/algorithm/base/sort/merge-sort.html) 是典型的分治算法。 56 | 57 | ``` 58 | void merge_sort(一个数组) { 59 | if (可以很容易处理) return; 60 | merge_sort(左半个数组); 61 | merge_sort(右半个数组); 62 | merge(左半个数组, 右半个数组); 63 | } 64 | ``` 65 | 66 | ### 练习 67 | 68 | - [LeetCode 分治练习](https://leetcode-cn.com/tag/divide-and-conquer/?utm_source=LCUS&utm_medium=banner_redirect&utm_campaign=transfer2china) 69 | - [分治题解](algorithm/divide-and-conquer/) 70 | 71 | ---- 72 | 73 | ## 贪心 74 | 75 | - 模拟「贪心」的人做出的决策 76 | - 每次都按照某种指标选取最优的操作 77 | - **只关注眼前,不考虑以后可能造成的影响** 78 | 79 | 常见做法有: 80 | 81 | 1. **离线**:按某顺序排序,并按某顺序处理(例如从大到小) 82 | 2. **在线**:每次取范围内最大/最小的东西,并更新范围数据 83 | 84 | ### 练习 85 | 86 | - [LeetCode 贪心练习](https://leetcode-cn.com/tag/greedy/) 87 | - [贪心题解](algorithm/greedy/) 88 | 89 | ---- 90 | 91 | ## 二分查找 92 | 93 | - 用来在一个有序数组中查找某一元素 94 | - 对于一个长度为 n 的数组,至多会进行 $O(log_n)$ 次查找 95 | 96 | ⚠️注:这里的有序是广义的有序,如果一个数组中的左侧或者右侧都满足某一种条件,而另一侧都不满足这种条件,也可以看作是一种有序。 97 | 98 | ### 练习 99 | 100 | - [LeetCode 练习](https://leetcode-cn.com/tag/binary-search/) 101 | - [二分题解](algorithm/research/binary-search/) 102 | -------------------------------------------------------------------------------- /docs/concept/data-structure/README.md: -------------------------------------------------------------------------------- 1 | ## 栈 -------------------------------------------------------------------------------- /docs/concept/sort/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JalanJiang/leetcode-notebook/8b55ef9d0ae6f7e025d27bdb9bf5d45f9c68e1cd/docs/concept/sort/README.md -------------------------------------------------------------------------------- /docs/data-structure/graph/README.md: -------------------------------------------------------------------------------- 1 | ## 133. 克隆图 2 | 3 | [原题链接](https://leetcode-cn.com/problems/clone-graph/) 4 | 5 | ### DFS 6 | 7 | 用字典 `mark` 记录遍历过的节点,`mark[node] = clone` 用于对应 `node` 的拷贝节点 `clone`。 8 | 9 | ```python 10 | """ 11 | # Definition for a Node. 12 | class Node: 13 | def __init__(self, val = 0, neighbors = []): 14 | self.val = val 15 | self.neighbors = neighbors 16 | """ 17 | class Solution: 18 | def cloneGraph(self, node: 'Node') -> 'Node': 19 | # 记录访问过的节点 20 | mark = dict() 21 | 22 | def dfs(node): 23 | if node is None: 24 | return node 25 | if node in mark: 26 | return mark[node] 27 | 28 | clone = Node(node.val, []) 29 | mark[node] = clone # node 对应的克隆节点为 clone,记录在字典中 30 | for n in node.neighbors: 31 | clone.neighbors.append(dfs(n)) 32 | 33 | # 返回克隆节点 34 | return clone 35 | 36 | return dfs(node) 37 | ``` 38 | 39 | ### BFS 40 | 41 | 用辅助队列实现 BFS。 42 | 43 | ```python 44 | """ 45 | # Definition for a Node. 46 | class Node: 47 | def __init__(self, val = 0, neighbors = []): 48 | self.val = val 49 | self.neighbors = neighbors 50 | """ 51 | class Solution: 52 | def cloneGraph(self, node: 'Node') -> 'Node': 53 | if node is None: 54 | return node 55 | 56 | # 记录 node 对应的拷贝 57 | mark = dict() 58 | # 辅助队列 59 | queue = list() 60 | queue.append(node) 61 | clone = Node(node.val, []) 62 | 63 | mark[node] = clone 64 | 65 | while len(queue) > 0: 66 | # 取出一个节点 67 | node = queue[0] 68 | del queue[0] 69 | # 遍历邻接节点 70 | for n in node.neighbors: 71 | if n not in mark: 72 | mark[n] = Node(n.val, []) 73 | # 新节点入队列 74 | queue.append(n) 75 | mark[node].neighbors.append(mark[n]) 76 | 77 | return clone 78 | ``` 79 | 80 | ## 210. 课程表 II 81 | 82 | [原题链接](https://leetcode-cn.com/problems/course-schedule-ii/) 83 | 84 | ### 思路 85 | 86 | 在图中找到循环。 87 | 88 | ```python 89 | class Solution: 90 | def findOrder(self, numCourses: int, prerequisites: List[List[int]]) -> List[int]: 91 | length = len(prerequisites) 92 | if length == 0: 93 | return [i for i in range(numCourses)] 94 | # n = len(prerequisites[0]) 95 | 96 | # 存储依赖关系 97 | degree = [0 for _ in range(numCourses)] 98 | relations = dict() 99 | for pre in prerequisites: 100 | nxt = pre[0] 101 | cur = pre[1] 102 | # 依赖关系 103 | if cur not in relations: 104 | relations[cur] = [nxt] 105 | else: 106 | relations[cur].append(nxt) 107 | # nxt 课程的度 + 1 108 | degree[nxt] += 1 109 | 110 | 111 | # 入度为 0 的入队 112 | queue = [] 113 | for i in range(numCourses): 114 | if degree[i] == 0: 115 | queue.append(i) 116 | 117 | # 遍历队列 118 | ans = [] 119 | while len(queue) > 0: 120 | first = queue[0] 121 | del queue[0] 122 | # if first not in ans: 123 | ans.append(first) 124 | # 获取下一批度为 0 的课程入队 125 | for course in relations.get(first, []): 126 | degree[course] -= 1 127 | if degree[course] == 0: 128 | queue.append(course) 129 | 130 | return ans if len(ans) == numCourses else [] 131 | ``` 132 | 133 | ## 997. 找到小镇的法官 134 | 135 | [原题链接](https://leetcode-cn.com/problems/find-the-town-judge) 136 | 137 | ### 思路 138 | 139 | 这是一道关于图的题。 140 | 141 | 用一个二维数组存储每个节点的入度和出度,入度 = N - 1 且 出度 = 0 的节点为法官。 142 | 143 | 144 | ```python 145 | class Solution: 146 | def findJudge(self, N: int, trust: List[List[int]]) -> int: 147 | node = [[0, 0] for _ in range(N + 1)] 148 | 149 | # 记录 150 | for t in trust: 151 | node[t[0]][0] += 1 152 | node[t[1]][1] += 1 153 | 154 | for i in range(1, N + 1): 155 | n = node[i] 156 | if n[1] == N - 1 and n[0] == 0: 157 | return i 158 | 159 | return -1 160 | ``` -------------------------------------------------------------------------------- /docs/data-structure/heap/README.md: -------------------------------------------------------------------------------- 1 | ## 295. 数据流的中位数 2 | 3 | [原题链接](https://leetcode-cn.com/problems/find-median-from-data-stream/comments/) 4 | 5 | ### 笨蛋解法 6 | 7 | 这题用 `O(n)` 是会超时的。 8 | 9 | ```python 10 | class MedianFinder(object): 11 | 12 | def __init__(self): 13 | """ 14 | initialize your data structure here. 15 | """ 16 | self.nums = list() 17 | 18 | 19 | def addNum(self, num): 20 | """ 21 | :type num: int 22 | :rtype: None 23 | """ 24 | length = len(self.nums) 25 | if length == 0: 26 | self.nums.append(num) 27 | else: 28 | for i in range(len(self.nums)): 29 | tmp = self.nums[i] 30 | if num <= tmp: 31 | self.nums.insert(i, num) 32 | break 33 | if i == len(self.nums) - 1: 34 | self.nums.append(num) 35 | print self.nums 36 | 37 | 38 | def findMedian(self): 39 | """ 40 | :rtype: float 41 | """ 42 | length = len(self.nums) 43 | if length % 2 == 0: 44 | right = length / 2 45 | left = right - 1 46 | return float(self.nums[left] + self.nums[right]) / 2 47 | else: 48 | return self.nums[(length - 1) / 2] 49 | 50 | 51 | 52 | # Your MedianFinder object will be instantiated and called as such: 53 | # obj = MedianFinder() 54 | # obj.addNum(num) 55 | # param_2 = obj.findMedian() 56 | ``` 57 | 58 | ### 双堆 59 | 60 | 1. 元素入最小堆, 61 | 2. 弹出最小堆栈顶元素,放入最大堆中(保证最小堆的堆,都比最大堆的堆顶大) 62 | 3. 最后即可取出最小堆的最大元素和最大堆的最小元素(即有序数组中中间的两个元素) 63 | 64 | ```python 65 | from heapq import * 66 | class MedianFinder(object): 67 | 68 | def __init__(self): 69 | """ 70 | initialize your data structure here. 71 | """ 72 | self.max_h = [] 73 | self.min_h = [] 74 | heapify(self.max_h) 75 | heapify(self.min_h) 76 | 77 | 78 | def addNum(self, num): 79 | """ 80 | :type num: int 81 | :rtype: None 82 | """ 83 | heappush(self.min_h, num) 84 | heappush(self.max_h, -heappop(self.min_h)) 85 | if len(self.min_h) < len(self.max_h): 86 | heappush(self.min_h, -heappop(self.max_h)) 87 | 88 | def findMedian(self): 89 | """ 90 | :rtype: float 91 | """ 92 | max_len = len(self.max_h) 93 | min_len = len(self.min_h) 94 | 95 | if max_len == min_len: 96 | return (-self.max_h[0] + self.min_h[0]) / 2. 97 | else: 98 | return self.min_h[0] * 1. 99 | 100 | 101 | 102 | # Your MedianFinder object will be instantiated and called as such: 103 | # obj = MedianFinder() 104 | # obj.addNum(num) 105 | # param_2 = obj.findMedian() 106 | ``` 107 | 108 | ### 参考资料 109 | 110 | - [python堆排序heapq](https://www.jianshu.com/p/e003872fa7b9) 111 | 112 | 113 | -------------------------------------------------------------------------------- /docs/data-structure/queue/README.md: -------------------------------------------------------------------------------- 1 | ## 622. 设计循环队列 2 | 3 | [原题链接](https://leetcode-cn.com/problems/design-circular-queue/) 4 | 5 | ### 解一 6 | 7 | 没有过多考虑,直接用 list 实现,没有践行「循环」的概念。 8 | 9 | ```python 10 | class MyCircularQueue: 11 | 12 | def __init__(self, k: int): 13 | """ 14 | Initialize your data structure here. Set the size of the queue to be k. 15 | """ 16 | self.list_data = [] 17 | self.list_length = k 18 | 19 | def enQueue(self, value: int) -> bool: 20 | """ 21 | Insert an element into the circular queue. Return true if the operation is successful. 22 | """ 23 | if len(self.list_data) >= self.list_length: 24 | return False 25 | self.list_data.append(value) 26 | return True 27 | 28 | def deQueue(self) -> bool: 29 | """ 30 | Delete an element from the circular queue. Return true if the operation is successful. 31 | """ 32 | if len(self.list_data) > 0: 33 | del(self.list_data[0]) 34 | return True 35 | return False 36 | 37 | def Front(self) -> int: 38 | """ 39 | Get the front item from the queue. 40 | """ 41 | if len(self.list_data) == 0: 42 | return -1 43 | return self.list_data[0] 44 | 45 | def Rear(self) -> int: 46 | """ 47 | Get the last item from the queue. 48 | """ 49 | if len(self.list_data) == 0: 50 | return -1 51 | return self.list_data[-1] 52 | 53 | def isEmpty(self) -> bool: 54 | """ 55 | Checks whether the circular queue is empty or not. 56 | """ 57 | return len(self.list_data) == 0 58 | 59 | def isFull(self) -> bool: 60 | """ 61 | Checks whether the circular queue is full or not. 62 | """ 63 | return len(self.list_data) == self.list_length 64 | 65 | 66 | # Your MyCircularQueue object will be instantiated and called as such: 67 | # obj = MyCircularQueue(k) 68 | # param_1 = obj.enQueue(value) 69 | # param_2 = obj.deQueue() 70 | # param_3 = obj.Front() 71 | # param_4 = obj.Rear() 72 | # param_5 = obj.isEmpty() 73 | # param_6 = obj.isFull() 74 | ``` 75 | 76 | ### 解二:循环队列 77 | 78 | 用数组来实现,「循环」的意思就是没有头和尾的区别。且设计数组时选择「浪费一个位置」,让数组满和空两种状态不冲突。 79 | 80 | 设两个指针,`front` 指向头部,`rear` 指向尾部。 81 | 82 | - 当 `front == rear` 时队列为空 83 | - 当 `(rear + 1) % length == front` 时队列为满 84 | 85 | ```python 86 | class MyCircularQueue: 87 | 88 | def __init__(self, k: int): 89 | """ 90 | Initialize your data structure here. Set the size of the queue to be k. 91 | """ 92 | # 浪费一个位置 93 | self.k = k + 1 94 | # 设置双指针 95 | self.front = 0 96 | self.rear = 0 97 | self.list_data = [0 for _ in range(self.k)] 98 | 99 | def enQueue(self, value: int) -> bool: 100 | """ 101 | Insert an element into the circular queue. Return true if the operation is successful. 102 | """ 103 | if self.isFull(): 104 | return False 105 | self.list_data[self.rear] = value 106 | self.rear = (self.rear + 1) % self.k 107 | return True 108 | 109 | def deQueue(self) -> bool: 110 | """ 111 | Delete an element from the circular queue. Return true if the operation is successful. 112 | """ 113 | if self.isEmpty(): 114 | return False 115 | self.front = (self.front + 1) % self.k 116 | return True 117 | 118 | def Front(self) -> int: 119 | """ 120 | Get the front item from the queue. 121 | """ 122 | if self.isEmpty(): 123 | return -1 124 | return self.list_data[self.front] 125 | 126 | def Rear(self) -> int: 127 | """ 128 | Get the last item from the queue. 129 | """ 130 | if self.isEmpty(): 131 | return -1 132 | return self.list_data[(self.rear - 1 + self.k) % self.k] 133 | 134 | def isEmpty(self) -> bool: 135 | """ 136 | Checks whether the circular queue is empty or not. 137 | """ 138 | return self.front == self.rear 139 | 140 | def isFull(self) -> bool: 141 | """ 142 | Checks whether the circular queue is full or not. 143 | """ 144 | return (self.rear + 1) % self.k == self.front 145 | 146 | 147 | # Your MyCircularQueue object will be instantiated and called as such: 148 | # obj = MyCircularQueue(k) 149 | # param_1 = obj.enQueue(value) 150 | # param_2 = obj.deQueue() 151 | # param_3 = obj.Front() 152 | # param_4 = obj.Rear() 153 | # param_5 = obj.isEmpty() 154 | # param_6 = obj.isFull() 155 | ``` 156 | 157 | ## 641. 设计循环双端队列 158 | 159 | [原题链接](https://leetcode-cn.com/problems/design-circular-deque/) 160 | 161 | ### 思路 162 | 163 | 同上题(622. 设计循环队列)。 164 | 165 | ```python 166 | class MyCircularDeque: 167 | 168 | def __init__(self, k: int): 169 | """ 170 | Initialize your data structure here. Set the size of the deque to be k. 171 | """ 172 | self.k = k + 1 173 | self.front = 0 174 | self.rear = 0 175 | self.list_data = [0 for _ in range(self.k)] 176 | 177 | def insertFront(self, value: int) -> bool: 178 | """ 179 | Adds an item at the front of Deque. Return true if the operation is successful. 180 | """ 181 | if self.isFull(): 182 | return False 183 | self.front = (self.front - 1) % self.k 184 | self.list_data[self.front] = value 185 | return True 186 | 187 | def insertLast(self, value: int) -> bool: 188 | """ 189 | Adds an item at the rear of Deque. Return true if the operation is successful. 190 | """ 191 | if self.isFull(): 192 | return False 193 | self.list_data[self.rear] = value 194 | self.rear = (self.rear + 1) % self.k 195 | return True 196 | 197 | def deleteFront(self) -> bool: 198 | """ 199 | Deletes an item from the front of Deque. Return true if the operation is successful. 200 | """ 201 | if self.isEmpty(): 202 | return False 203 | self.front = (self.front + 1) % self.k 204 | return True 205 | 206 | def deleteLast(self) -> bool: 207 | """ 208 | Deletes an item from the rear of Deque. Return true if the operation is successful. 209 | """ 210 | if self.isEmpty(): 211 | return False 212 | self.rear = (self.rear - 1) % self.k 213 | return True 214 | 215 | def getFront(self) -> int: 216 | """ 217 | Get the front item from the deque. 218 | """ 219 | if self.isEmpty(): 220 | return -1 221 | return self.list_data[self.front] 222 | 223 | def getRear(self) -> int: 224 | """ 225 | Get the last item from the deque. 226 | """ 227 | if self.isEmpty(): 228 | return -1 229 | return self.list_data[(self.rear - 1 + self.k) % self.k] 230 | 231 | def isEmpty(self) -> bool: 232 | """ 233 | Checks whether the circular deque is empty or not. 234 | """ 235 | return self.front == self.rear 236 | 237 | def isFull(self) -> bool: 238 | """ 239 | Checks whether the circular deque is full or not. 240 | """ 241 | return (self.rear + 1) % self.k == self.front 242 | 243 | 244 | # Your MyCircularDeque object will be instantiated and called as such: 245 | # obj = MyCircularDeque(k) 246 | # param_1 = obj.insertFront(value) 247 | # param_2 = obj.insertLast(value) 248 | # param_3 = obj.deleteFront() 249 | # param_4 = obj.deleteLast() 250 | # param_5 = obj.getFront() 251 | # param_6 = obj.getRear() 252 | # param_7 = obj.isEmpty() 253 | # param_8 = obj.isFull() 254 | ``` 255 | 256 | ## 752. 打开转盘锁 257 | 258 | [原题链接](https://leetcode-cn.com/problems/open-the-lock/) 259 | 260 | ### 思路 261 | 262 | BFS 263 | 264 | ```python 265 | class Solution: 266 | def openLock(self, deadends: List[str], target: str) -> int: 267 | queue = list() 268 | queue.append(('0000', 0)) # 0000 也有可能是死亡数字 269 | already = {'0000'} # 节点是否已经遍历过 270 | while len(queue): # 当队列不为空时,循环队列 271 | # 取第一个元素 272 | node = queue[0] 273 | del queue[0] 274 | # 节点判断 275 | if node[0] == target: 276 | return node[1] 277 | if node[0] in deadends: 278 | continue 279 | # 获取周边节点 280 | negihbors = self.get_neighbors(node) 281 | for n in negihbors: 282 | if n[0] not in already: 283 | already.add(n[0]) 284 | queue.append(n) 285 | return -1 286 | 287 | def get_neighbors(self, node): 288 | number = node[0] 289 | # print(number) 290 | depth = node[1] 291 | negihbors = [] 292 | directions = {1, -1} 293 | for i in range(len(number)): 294 | # 循环位 295 | for d in directions: 296 | new_position = str((int(number[i]) + d) % 10) 297 | item = (number[:i] + new_position + number[i+1:], depth + 1) 298 | negihbors.append(item) 299 | return negihbors 300 | ``` 301 | 302 | ## 933. 最近的请求次数 303 | 304 | [原题链接](https://leetcode-cn.com/problems/number-of-recent-calls/) 305 | 306 | ### 思路 307 | 308 | ```python 309 | class RecentCounter: 310 | 311 | def __init__(self): 312 | self.ping_list = [] 313 | 314 | def ping(self, t: int) -> int: 315 | first = t - 3000 316 | self.ping_list.append(t) 317 | while first > self.ping_list[0]: 318 | del(self.ping_list[0]) 319 | 320 | return len(self.ping_list) 321 | 322 | 323 | # Your RecentCounter object will be instantiated and called as such: 324 | # obj = RecentCounter() 325 | # param_1 = obj.ping(t) 326 | ``` -------------------------------------------------------------------------------- /docs/data-structure/tree/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JalanJiang/leetcode-notebook/8b55ef9d0ae6f7e025d27bdb9bf5d45f9c68e1cd/docs/data-structure/tree/README.md -------------------------------------------------------------------------------- /docs/data-structure/tree/bfs/README.md: -------------------------------------------------------------------------------- 1 | ## 102. 二叉树的层次遍历 2 | 3 | [原题链接](https://leetcode-cn.com/problems/binary-tree-level-order-traversal/) 4 | 5 | ### 思路 6 | 7 | 借助队列实现: 8 | 9 | - 初始化队列 `q`,把根节点 `root` 塞进去 10 | - 当 `q` 不为空时,遍历队列,依次取出队列中的节点: 11 | - 若该节点的左节点不为空:左节点入队列 12 | - 若该节点的右节点不为空:右节点入队列 13 | 14 | 15 | 16 | #### **Python** 17 | 18 | ```python 19 | # Definition for a binary tree node. 20 | # class TreeNode: 21 | # def __init__(self, x): 22 | # self.val = x 23 | # self.left = None 24 | # self.right = None 25 | 26 | class Solution: 27 | def levelOrder(self, root: TreeNode) -> List[List[int]]: 28 | queue = [] 29 | queue.append(root) 30 | res = [] 31 | while len(queue) > 0: 32 | length = len(queue) 33 | tmp = [] 34 | for i in range(length): 35 | node = queue[0] 36 | del queue[0] 37 | if node is None: 38 | continue 39 | tmp.append(node.val) 40 | queue.append(node.left) 41 | queue.append(node.right) 42 | if len(tmp) > 0: 43 | res.append(tmp) 44 | return res 45 | ``` 46 | 47 | #### **Go** 48 | 49 | ```go 50 | /** 51 | * Definition for a binary tree node. 52 | * type TreeNode struct { 53 | * Val int 54 | * Left *TreeNode 55 | * Right *TreeNode 56 | * } 57 | */ 58 | func levelOrder(root *TreeNode) [][]int { 59 | queue := make([]*TreeNode, 0) 60 | queue = append(queue, root) 61 | res := make([][]int, 0) 62 | for len(queue) > 0 { 63 | tmp := make([]int, 0) 64 | length := len(queue) 65 | for i := 0; i < length; i++ { 66 | q := queue[0] 67 | queue = queue[1:] 68 | if q == nil { 69 | continue 70 | } 71 | tmp = append(tmp, q.Val) 72 | queue = append(queue, q.Left) 73 | queue = append(queue, q.Right) 74 | } 75 | if len(tmp) > 0 { 76 | res = append(res, tmp) 77 | } 78 | } 79 | return res 80 | } 81 | ``` 82 | 83 | ## 107. 二叉树的层序遍历 II 84 | 85 | [原题链接](https://leetcode-cn.com/problems/binary-tree-level-order-traversal-ii/) 86 | 87 | 与 106 类似,只是结果输出顺序不同。 88 | 89 | ```python 90 | # Definition for a binary tree node. 91 | # class TreeNode: 92 | # def __init__(self, x): 93 | # self.val = x 94 | # self.left = None 95 | # self.right = None 96 | 97 | class Solution: 98 | def levelOrderBottom(self, root: TreeNode) -> List[List[int]]: 99 | ans = [] 100 | q = [root] 101 | while len(q) != 0: 102 | # 遍历一层 103 | tmp = [] 104 | for i in range(len(q)): 105 | top = q[0] 106 | del q[0] 107 | if top is None: 108 | continue 109 | tmp.append(top.val) 110 | q.append(top.left) 111 | q.append(top.right) 112 | if len(tmp) != 0: 113 | ans.append(tmp) 114 | 115 | return ans[::-1] 116 | ``` 117 | 118 | 119 | 120 | ## 108. 将有序数组转换为二叉搜索树 121 | 122 | [原题链接](https://leetcode-cn.com/problems/convert-sorted-array-to-binary-search-tree/submissions/) 123 | 124 | ### 思路 125 | 126 | 二叉树中序遍历的逆过程。二分+递归解法。 127 | 128 | 左右等分建立左右子树,中间节点作为子树根节点,递归该过程。 129 | 130 | ```python 131 | # Definition for a binary tree node. 132 | # class TreeNode(object): 133 | # def __init__(self, x): 134 | # self.val = x 135 | # self.left = None 136 | # self.right = None 137 | 138 | class Solution(object): 139 | def sortedArrayToBST(self, nums): 140 | """ 141 | :type nums: List[int] 142 | :rtype: TreeNode 143 | """ 144 | if not nums: 145 | return None 146 | else: 147 | mid = (len(nums)) // 2 148 | node = TreeNode(nums[mid]) 149 | 150 | left = nums[0:mid] 151 | right = nums[mid+1:len(nums)] 152 | 153 | node.left = self.sortedArrayToBST(left) 154 | node.right = self.sortedArrayToBST(right) 155 | 156 | return node 157 | ``` 158 | 159 | ## 117. 填充每个节点的下一个右侧节点指针 II 160 | 161 | [原题链接](https://leetcode-cn.com/problems/populating-next-right-pointers-in-each-node-ii/) 162 | 163 | ### 解一:实用了额外空间 164 | 165 | 层次遍历的变种考点。 166 | 167 | ```python 168 | """ 169 | # Definition for a Node. 170 | class Node(object): 171 | def __init__(self, val, left, right, next): 172 | self.val = val 173 | self.left = left 174 | self.right = right 175 | self.next = next 176 | """ 177 | class Solution(object): 178 | def connect(self, root): 179 | """ 180 | :type root: Node 181 | :rtype: Node 182 | """ 183 | if root is None: 184 | return root 185 | 186 | q = list() 187 | q.append(root) 188 | 189 | while len(q) > 0: 190 | q_length = len(q) 191 | 192 | for i in range(q_length): 193 | node = q[0] 194 | del q[0] 195 | 196 | if i + 1 < q_length: 197 | node.next = q[0] 198 | 199 | if node.left is not None: 200 | q.append(node.left) 201 | 202 | if node.right is not None: 203 | q.append(node.right) 204 | 205 | return root 206 | ``` 207 | 208 | ### 解二:常数空间 + 递归 209 | 210 | - 不断找到下一个可关联的右侧不为空节点 211 | - 注意:先构造右子树 212 | 213 | 214 | 215 | #### **Python** 216 | 217 | ```python 218 | """ 219 | # Definition for a Node. 220 | class Node: 221 | def __init__(self, val: int = 0, left: 'Node' = None, right: 'Node' = None, next: 'Node' = None): 222 | self.val = val 223 | self.left = left 224 | self.right = right 225 | self.next = next 226 | """ 227 | class Solution: 228 | def connect(self, root: 'Node') -> 'Node': 229 | self.handler(root) 230 | return root 231 | 232 | def handler(self, root): 233 | if root is None or (root.left is None and root.right is None): 234 | return 235 | 236 | # 处理左节点 237 | if root.left is not None: 238 | if root.right is not None: 239 | # 如果存在右节点:指向右节点 240 | root.left.next = root.right 241 | else: 242 | # 如果不存在右节点;一直往下找到第一个存在的右侧节点 243 | root.left.next = self.get_next(root) 244 | 245 | # 处理右节点 246 | # 使用 next 指针 247 | if root.right is not None: 248 | root.right.next = self.get_next(root) 249 | 250 | # 先递归右子树 251 | self.handler(root.right) 252 | self.handler(root.left) 253 | 254 | 255 | def get_next(self, root): 256 | next_node = root.next 257 | while next_node is not None: 258 | if next_node.left is not None: 259 | return next_node.left 260 | if next_node.right is not None: 261 | return next_node.right 262 | next_node = next_node.next 263 | return None 264 | ``` 265 | 266 | 267 | 268 | ## 199. 二叉树的右视图 269 | 270 | [原题链接](https://leetcode-cn.com/problems/binary-tree-right-side-view/) 271 | 272 | ### BFS 层级别离 273 | 274 | ```python 275 | # Definition for a binary tree node. 276 | # class TreeNode: 277 | # def __init__(self, x): 278 | # self.val = x 279 | # self.left = None 280 | # self.right = None 281 | 282 | class Solution: 283 | def rightSideView(self, root: TreeNode) -> List[int]: 284 | ans = [] 285 | queue = [root] 286 | while len(queue) > 0: 287 | q_length = len(queue) 288 | for i in range(q_length): 289 | first = queue[0] 290 | del queue[0] 291 | if first is None: 292 | continue 293 | if i == q_length - 1: 294 | ans.append(first.val) 295 | if first.left is not None: 296 | queue.append(first.left) 297 | if first.right is not None: 298 | queue.append(first.right) 299 | 300 | return ans 301 | ``` 302 | 303 | - 时间复杂度:$O(n)$ 304 | - 空间复杂度:$O(n)$ 305 | 306 | ## 297. 二叉树的序列化与反序列化 307 | 308 | [原题链接](https://leetcode-cn.com/problems/serialize-and-deserialize-binary-tree/) 309 | 310 | ### BFS:层序遍历 311 | 312 | - 序列化:将题中二叉树利用辅助队列层序遍历为 `1,2,3,null,null,4,5` 313 | - 反序列化:字符串转为数组,将第一个节点入队列,依旧以队列的方式进行反序列化 314 | 315 | ```python 316 | # Definition for a binary tree node. 317 | # class TreeNode(object): 318 | # def __init__(self, x): 319 | # self.val = x 320 | # self.left = None 321 | # self.right = None 322 | 323 | class Codec: 324 | def serialize(self, root): 325 | """Encodes a tree to a single string. 326 | 327 | :type root: TreeNode 328 | :rtype: str 329 | """ 330 | str_list = [] 331 | queue = [] 332 | queue.append(root) 333 | while len(queue) > 0: 334 | q_length = len(queue) 335 | for i in range(q_length): 336 | # 取出队列头部节点 337 | first = queue[0] 338 | del queue[0] 339 | if first is None: 340 | str_list.append("N") 341 | continue 342 | str_list.append(str(first.val)) 343 | # 左右节点入队列 344 | queue.append(first.left) 345 | queue.append(first.right) 346 | # print(str_list) 347 | return ','.join(str_list) 348 | 349 | def deserialize(self, data): 350 | """Decodes your encoded data to tree. 351 | 352 | :type data: str 353 | :rtype: TreeNode 354 | """ 355 | str_list = data.split(',') 356 | # 取出第一个节点 357 | first = str_list[0] 358 | root = self.get_node(first) 359 | queue = [] 360 | queue.append(root) 361 | del str_list[0] 362 | while len(queue) > 0: 363 | q_length = len(queue) 364 | for i in range(q_length): 365 | first = queue[0] 366 | del queue[0] 367 | if first is None: 368 | continue 369 | # 构造它的左右节点 370 | str_list_length = len(str_list) 371 | if str_list_length >= 2: 372 | left_node = self.get_node(str_list[0]) 373 | del str_list[0] 374 | right_node = self.get_node(str_list[0]) 375 | del str_list[0] 376 | elif str_list_length == 1: 377 | left_node = self.get_node(str_list[0]) 378 | right_node = None 379 | del str_list[0] 380 | else: 381 | left_node = None 382 | right_node = None 383 | first.left = left_node 384 | first.right = right_node 385 | if left_node is not None: 386 | queue.append(left_node) 387 | if right_node is not None: 388 | queue.append(right_node) 389 | 390 | return root 391 | 392 | def get_node(self, root_val): 393 | if root_val == 'N': 394 | return None 395 | else: 396 | return TreeNode(int(root_val)) 397 | 398 | # Your Codec object will be instantiated and called as such: 399 | # codec = Codec() 400 | # codec.deserialize(codec.serialize(root)) 401 | ``` 402 | 403 | ## 513. 找树左下角的值 404 | 405 | [原题链接](https://leetcode-cn.com/problems/find-bottom-left-tree-value/comments/) 406 | 407 | ### 思路 408 | 409 | - 层次遍历 410 | - 队列存储,右子节点先入队列,左子节点再入队列。 411 | 412 | ```python 413 | from collections import deque 414 | 415 | class Solution(object): 416 | def findBottomLeftValue(self, root): 417 | """ 418 | :type root: TreeNode 419 | :rtype: int 420 | """ 421 | q = deque([root]) 422 | while q: 423 | node = q.popleft() 424 | if node.right: 425 | q.append(node.right) 426 | if node.left: 427 | q.append(node.left) 428 | return node.val 429 | ``` 430 | 431 | ## 637. 二叉树的层平均值 432 | 433 | [原题链接](https://leetcode-cn.com/problems/average-of-levels-in-binary-tree/description/) 434 | 435 | ### 思路 436 | 437 | BFS 438 | 439 | - 往队列中 push 根节点 440 | - 当队列不为空的时候,遍历整个队列 441 | - 每遍历队列中的一个节点,就取出它的值并将其 pop。此时顺带检查它的左右子节点是否为空 442 | - 如果不为空,就将其子节点push进入队列中 443 | - 这样,刚好遍历完一层节点,下一层的节点就全部存到队列中了。一层层循环下去直到最后一层,问题便顺利得到了解决 444 | 445 | ```python 446 | from collections import deque 447 | 448 | class Solution: 449 | def averageOfLevels(self, root): 450 | """ 451 | :type root: TreeNode 452 | :rtype: List[float] 453 | """ 454 | res = [] 455 | q = deque([root]) 456 | while q: 457 | curr_sum = 0 458 | node_num_this_layer = len(q) 459 | for i in range(0, node_num_this_layer): 460 | node = q.popleft(); 461 | curr_sum += node.val 462 | if node.left: 463 | q.append(node.left) 464 | if node.right: 465 | q.append(node.right) 466 | res.append(1.0*curr_sum/node_num_this_layer) 467 | return res 468 | ``` -------------------------------------------------------------------------------- /docs/data-structure/tree/bst/README.md: -------------------------------------------------------------------------------- 1 | ## 220. 存在重复元素 III 2 | 3 | [原题链接](https://leetcode-cn.com/problems/contains-duplicate-iii/) 4 | 5 | ### 思路 6 | 7 | 这道题的中文翻译真的很迷,先对题目再做一个翻译: 8 | 9 | 给定一个整数数组,判断数组中是否有两个不同的索引 `i` 和 `j`,使得:存在 `abs(i - j) <= k` 时,有 `abs(nums[i] - nums[j]) <=t`。 10 | 11 | ### 解一:暴力破解(超时) 12 | 13 | 取出元素 `nums[i]`,在 `i + 1 ~ i + k` 的范围内遍历是否存在 `abs(nums[i] - nums[j]) <= t` 的数,如果存在则返回 `True`。 14 | 15 | ```python 16 | class Solution: 17 | def containsNearbyAlmostDuplicate(self, nums: List[int], k: int, t: int) -> bool: 18 | num_length = len(nums) 19 | for i in range(num_length): 20 | for j in range(i + 1, i + k + 1): 21 | if j < num_length: 22 | if abs(nums[i] - nums[j]) <= t: 23 | return True 24 | return False 25 | ``` 26 | 27 | ### 解二:桶 28 | 29 | 使用 `nums[i] // (t + 1)` 计算桶编号,这样保证同一个桶中的元素差值不会超过 `t`,因此只要命中同一个桶即可返回 `True`。 30 | 31 | 因此题需要维护长度为 `k` 的滑动窗口,因此当元素大于 `K` 时需要清理桶中元素。 32 | 33 | ```python 34 | class Solution: 35 | def containsNearbyAlmostDuplicate(self, nums: List[int], k: int, t: int) -> bool: 36 | if t < 0: 37 | return False 38 | bucket = dict() 39 | for i in range(len(nums)): 40 | bucket_num = nums[i] // (t + 1) 41 | if bucket_num in bucket: 42 | # 命中 43 | return True 44 | # 检查前后两个桶,因为范围是 2t 45 | if bucket_num + 1 in bucket and abs(bucket[bucket_num + 1] - nums[i]) <= t: 46 | return True 47 | if bucket_num - 1 in bucket and abs(bucket[bucket_num - 1] - nums[i]) <= t: 48 | return True 49 | bucket[bucket_num] = nums[i] 50 | if len(bucket) > k: 51 | # 弹出元素 52 | bucket.pop(nums[i - k] // (t + 1)) 53 | return False 54 | ``` 55 | 56 | ```go 57 | func containsNearbyAlmostDuplicate(nums []int, k int, t int) bool { 58 | // var bucket map[int]int 59 | if t < 0 { 60 | return false 61 | } 62 | var bucket = make(map[int]int) 63 | for i := 0; i < len(nums); i++ { 64 | // 计算桶编号 65 | bucketNum := getBucketKey(nums[i], t + 1) 66 | // 是否命中桶 67 | if _, ok := bucket[bucketNum]; ok { 68 | // 存在 69 | return true 70 | } 71 | // 是否命中相邻桶 72 | if val, ok := bucket[bucketNum - 1]; ok { 73 | if val - nums[i] <= t && val - nums[i] >= -t { 74 | return true 75 | } 76 | } 77 | if val, ok := bucket[bucketNum + 1]; ok { 78 | if val - nums[i] <= t && val - nums[i] >= -t { 79 | return true 80 | } 81 | } 82 | bucket[bucketNum] = nums[i] 83 | // 多余元素弹出 84 | if len(bucket) > k { 85 | delete(bucket, getBucketKey(nums[i - k], t + 1)) 86 | } 87 | } 88 | return false 89 | } 90 | 91 | func getBucketKey(num int, t int) int { 92 | if num < 0 { 93 | return (num + 1) / (t - 1) 94 | } else { 95 | return num / t 96 | } 97 | } 98 | ``` 99 | 100 | ### 解三:平衡二叉树 101 | 102 | 103 | -------------------------------------------------------------------------------- /docs/data-structure/tree/dfs/README.md: -------------------------------------------------------------------------------- 1 | ## 94. 二叉树的中序遍历 2 | 3 | [原题链接](https://leetcode-cn.com/problems/binary-tree-inorder-traversal/submissions/) 4 | 5 | ### 解法一 6 | 7 | - 递归 8 | 9 | ``` 10 | void dfs(TreeNode root) { 11 | dfs(root.left); 12 | visit(root); 13 | dfs(root.right); 14 | } 15 | ``` 16 | 17 | 18 | 19 | #### **Python** 20 | 21 | ```python 22 | class Solution(object): 23 | def inorderTraversal(self, root): 24 | """ 25 | :type root: TreeNode 26 | :rtype: List[int] 27 | """ 28 | l = [] 29 | self.visitNode(root, l) 30 | return l 31 | 32 | def visitNode(self, root, l): 33 | if root is None: 34 | return 35 | self.visitNode(root.left, l) 36 | l.append(root.val) 37 | self.visitNode(root.right, l) 38 | ``` 39 | 40 | #### **Go** 41 | 42 | ```go 43 | /** 44 | * Definition for a binary tree node. 45 | * type TreeNode struct { 46 | * Val int 47 | * Left *TreeNode 48 | * Right *TreeNode 49 | * } 50 | */ 51 | var res []int 52 | 53 | func inorderTraversal(root *TreeNode) []int { 54 | res = make([]int, 0) 55 | handler(root) 56 | return res 57 | } 58 | 59 | func handler(root *TreeNode) { 60 | if root == nil { 61 | return 62 | } 63 | handler(root.Left) 64 | res = append(res, root.Val) 65 | handler(root.Right) 66 | } 67 | ``` 68 | 69 | 70 | 71 | ### 解法二 72 | 73 | 非递归法。 74 | 75 | - 寻找当前节点的左节点,依次入栈 76 | 77 | #### **Python** 78 | 79 | 80 | 81 | ```python 82 | class Solution(object): 83 | def inorderTraversal(self, root): 84 | """ 85 | :type root: TreeNode 86 | :rtype: List[int] 87 | """ 88 | stack = [] 89 | cur = root 90 | res = [] 91 | while stack or cur: 92 | while cur: 93 | stack.append(cur) 94 | cur = cur.left 95 | node = stack.pop() 96 | res.append(node.val) 97 | # 下一个节点轮到右节点(左 -> 中 -> 右) 98 | cur = node.right 99 | return res 100 | ``` 101 | 102 | #### **Go** 103 | 104 | ```go 105 | /** 106 | * Definition for a binary tree node. 107 | * type TreeNode struct { 108 | * Val int 109 | * Left *TreeNode 110 | * Right *TreeNode 111 | * } 112 | */ 113 | 114 | func inorderTraversal(root *TreeNode) []int { 115 | if root == nil { 116 | return nil 117 | } 118 | stack := make([]*TreeNode, 0) 119 | res := make([]int, 0) 120 | for root != nil || len(stack) > 0 { 121 | for root != nil { 122 | stack = append(stack, root) 123 | root = root.Left 124 | } 125 | // 取栈顶 126 | top := stack[len(stack) - 1] 127 | res = append(res, top.Val) 128 | // 删除栈顶 129 | stack = stack[:len(stack) - 1] 130 | root = top.Right 131 | } 132 | return res 133 | } 134 | ``` 135 | 136 | 137 | 138 | ## 98. 验证二叉搜索树 139 | 140 | [原题链接](https://leetcode-cn.com/problems/validate-binary-search-tree/submissions/) 141 | 142 | ### 解一:中序遍历 143 | 144 | 中序遍历为升序 145 | 146 | ```python 147 | # Definition for a binary tree node. 148 | # class TreeNode(object): 149 | # def __init__(self, x): 150 | # self.val = x 151 | # self.left = None 152 | # self.right = None 153 | 154 | class Solution(object): 155 | def isValidBST(self, root): 156 | """ 157 | :type root: TreeNode 158 | :rtype: bool 159 | """ 160 | res = [] 161 | self.middleOrder(res, root) 162 | 163 | for i in range(1, len(res)): 164 | if res[i - 1] >= res[i]: 165 | return False 166 | return True 167 | 168 | def middleOrder(self, res, root): 169 | if root is not None: 170 | self.middleOrder(res, root.left) 171 | res.append(root.val) 172 | self.middleOrder(res, root.right) 173 | else: 174 | return 175 | ``` 176 | 177 | ### 解二:递归 178 | 179 | 180 | 181 | #### **Python** 182 | 183 | ```python 184 | # Definition for a binary tree node. 185 | # class TreeNode: 186 | # def __init__(self, x): 187 | # self.val = x 188 | # self.left = None 189 | # self.right = None 190 | 191 | class Solution: 192 | def isValidBST(self, root: TreeNode) -> bool: 193 | return self.helper(root, None, None) 194 | 195 | def helper(self, root, low, high): 196 | if root is None: 197 | return True 198 | if low is not None and root.val <= low: 199 | return False 200 | if high is not None and root.val >= high: 201 | return False 202 | return self.helper(root.left, low, root.val) and self.helper(root.right, root.val, high) 203 | ``` 204 | 205 | #### **Go** 206 | 207 | ```go 208 | /** 209 | * Definition for a binary tree node. 210 | * type TreeNode struct { 211 | * Val int 212 | * Left *TreeNode 213 | * Right *TreeNode 214 | * } 215 | */ 216 | func isValidBST(root *TreeNode) bool { 217 | return helper(root, -1<<63, 1<<63-1) 218 | } 219 | 220 | func helper(root *TreeNode, low int, high int) bool { 221 | if root == nil { 222 | return true 223 | } 224 | if root.Val <= low { 225 | return false 226 | } 227 | if root.Val >= high { 228 | return false 229 | } 230 | return helper(root.Left, low, root.Val) && helper(root.Right, root.Val, high) 231 | } 232 | ``` 233 | 234 | ### 解三:栈辅助中序遍历 235 | 236 | ```python 237 | # Definition for a binary tree node. 238 | # class TreeNode: 239 | # def __init__(self, val=0, left=None, right=None): 240 | # self.val = val 241 | # self.left = left 242 | # self.right = right 243 | class Solution: 244 | def isValidBST(self, root: TreeNode) -> bool: 245 | stack = [] 246 | pre = float('-inf') 247 | while root is not None or len(stack) > 0: 248 | # 左侧压入栈 249 | while root is not None: 250 | stack.append(root) 251 | root = root.left 252 | # 弹出栈顶 253 | top = stack.pop() 254 | # 判断终序遍历前 1 个数是否小于当前数 255 | if top.val <= pre: 256 | return False 257 | pre = top.val 258 | root = top.right 259 | 260 | return True 261 | ``` 262 | 263 | 264 | 265 | ## 105. 从前序与中序遍历序列构造二叉树 266 | 267 | [原题链接](https://leetcode-cn.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal/) 268 | 269 | ### 思路 270 | 271 | 先来了解一下前序遍历和中序遍历是什么。 272 | 273 | - 前序遍历:遍历顺序为 父节点->左子节点->右子节点 274 | - 后续遍历:遍历顺序为 左子节点->父节点->右子节点 275 | 276 | 我们可以发现:**前序遍历的第一个元素为根节点,而在后序遍历中,该根节点所在位置的左侧为左子树,右侧为右子树。** 277 | 278 | 例如在例题中: 279 | 280 | > 前序遍历 preorder = [3,9,20,15,7] 281 | > 中序遍历 inorder = [9,3,15,20,7] 282 | 283 | `preorder` 的第一个元素 3 是整棵树的根节点。`inorder` 中 3 的左侧 `[9]` 是树的左子树,右侧 `[15, 20, 7]` 构成了树的右子树。 284 | 285 | 所以构建二叉树的问题本质上就是: 286 | 287 | 1. 找到各个子树的根节点 `root` 288 | 2. 构建该根节点的左子树 289 | 3. 构建该根节点的右子树 290 | 291 | 整个过程我们可以用递归来完成。 292 | 293 | ```python 294 | # Definition for a binary tree node. 295 | # class TreeNode(object): 296 | # def __init__(self, x): 297 | # self.val = x 298 | # self.left = None 299 | # self.right = None 300 | 301 | class Solution(object): 302 | def buildTree(self, preorder, inorder): 303 | """ 304 | :type preorder: List[int] 305 | :type inorder: List[int] 306 | :rtype: TreeNode 307 | """ 308 | if len(inorder) == 0: 309 | return None 310 | # 前序遍历第一个值为根节点 311 | root = TreeNode(preorder[0]) 312 | # 因为没有重复元素,所以可以直接根据值来查找根节点在中序遍历中的位置 313 | mid = inorder.index(preorder[0]) 314 | # 构建左子树 315 | root.left = self.buildTree(preorder[1:mid+1], inorder[:mid]) 316 | # 构建右子树 317 | root.right = self.buildTree(preorder[mid+1:], inorder[mid+1:]) 318 | 319 | return root 320 | ``` 321 | 322 | ## 144. 二叉树的前序遍历 323 | 324 | [原题链接](https://leetcode-cn.com/problems/binary-tree-preorder-traversal/submissions/) 325 | 326 | ### 解法一 327 | 328 | 递归法 329 | 330 | ```python 331 | class Solution(object): 332 | def preorderTraversal(self, root): 333 | """ 334 | :type root: TreeNode 335 | :rtype: List[int] 336 | """ 337 | l = [] 338 | self.visitNode(root, l) 339 | return l 340 | 341 | def visitNode(self, root, l): 342 | if root is None: 343 | return 344 | l.append(root.val) 345 | self.visitNode(root.left, l) 346 | self.visitNode(root.right, l) 347 | ``` 348 | 349 | ### 解法二 350 | 351 | 非递归,使用栈辅助。 352 | 353 | - 将 root 右节点、左节点依次压入栈 354 | - 循环弹出栈顶节点,再按节点的右、左节点依次入栈 355 | 356 | ```python 357 | class Solution(object): 358 | def preorderTraversal(self, root): 359 | """ 360 | :type root: TreeNode 361 | :rtype: List[int] 362 | """ 363 | stack = [] 364 | stack.append(root) 365 | res = [] 366 | while stack: 367 | node = stack.pop() 368 | if node is None: 369 | continue 370 | res.append(node.val) 371 | if node.right: 372 | stack.append(node.right) 373 | if node.left: 374 | stack.append(node.left) 375 | return res 376 | ``` 377 | 378 | ## 145. 二叉树的后序遍历 379 | 380 | [原题链接](https://leetcode-cn.com/problems/binary-tree-postorder-traversal/) 381 | 382 | ### 解法一:递归 383 | 384 | 后序遍历。 385 | 386 | 387 | 388 | #### **Python** 389 | 390 | ```python 391 | # Definition for a binary tree node. 392 | # class TreeNode: 393 | # def __init__(self, x): 394 | # self.val = x 395 | # self.left = None 396 | # self.right = None 397 | 398 | class Solution: 399 | def postorderTraversal(self, root: TreeNode) -> List[int]: 400 | res = [] 401 | self.handler(root, res) 402 | return res 403 | 404 | def handler(self, root, res): 405 | if root is None: 406 | return 407 | self.handler(root.left, res) 408 | self.handler(root.right, res) 409 | res.append(root.val) 410 | ``` 411 | 412 | #### **Go** 413 | 414 | ```go 415 | /** 416 | * Definition for a binary tree node. 417 | * type TreeNode struct { 418 | * Val int 419 | * Left *TreeNode 420 | * Right *TreeNode 421 | * } 422 | */ 423 | var res []int 424 | 425 | func postorderTraversal(root *TreeNode) []int { 426 | res = make([]int, 0) 427 | handler(root) 428 | return res 429 | } 430 | 431 | func handler(root *TreeNode) { 432 | if root == nil { 433 | return 434 | } 435 | handler(root.Left) 436 | handler(root.Right) 437 | res = append(res, root.Val) 438 | } 439 | ``` 440 | 441 | 442 | 443 | ### 解法二:迭代 444 | 445 | - 后序遍历顺序为 left->right->root,反序后为:root->right->left 446 | - 用栈实现 root->right->left 的顺序,再将列表反序 447 | 448 | 449 | 450 | #### **Python** 451 | 452 | ```python 453 | # Definition for a binary tree node. 454 | # class TreeNode: 455 | # def __init__(self, x): 456 | # self.val = x 457 | # self.left = None 458 | # self.right = None 459 | 460 | class Solution: 461 | def postorderTraversal(self, root: TreeNode) -> List[int]: 462 | # 后序遍历:左 -> 右 -> 中 463 | # 反过来:中 -> 右 -> 左 464 | stack = [] 465 | stack.append(root) 466 | res = [] 467 | while len(stack) > 0: 468 | top = stack.pop() 469 | if top is None: 470 | continue 471 | # 用栈先将左节点入栈 472 | stack.append(top.left) 473 | stack.append(top.right) 474 | res.append(top.val) 475 | res.reverse() 476 | return res 477 | ``` 478 | 479 | #### **Go** 480 | 481 | ```go 482 | /** 483 | * Definition for a binary tree node. 484 | * type TreeNode struct { 485 | * Val int 486 | * Left *TreeNode 487 | * Right *TreeNode 488 | * } 489 | */ 490 | var res []int 491 | 492 | func postorderTraversal(root *TreeNode) []int { 493 | res := make([]int, 0) 494 | stack := make([]*TreeNode, 0) 495 | stack = append(stack, root) 496 | for len(stack) > 0 { 497 | top := stack[len(stack) - 1] 498 | // 删除栈顶 499 | stack = stack[:len(stack) - 1] 500 | if top == nil { 501 | continue 502 | } 503 | stack = append(stack, top.Left) 504 | stack = append(stack, top.Right) 505 | res = append(res, top.Val) 506 | } 507 | res = reverse(res) 508 | return res 509 | } 510 | 511 | func reverse(res []int) []int { 512 | for i, j := 0, len(res) - 1; i < j; i, j = i + 1, j - 1 { 513 | res[i], res[j] = res[j], res[i] 514 | } 515 | return res 516 | } 517 | ``` 518 | 519 | 520 | 521 | ## 230. 二叉搜索树中第K小的元素 522 | 523 | [原题链接](https://leetcode-cn.com/problems/kth-smallest-element-in-a-bst/) 524 | 525 | ### 思路 526 | 527 | 二叉搜索树的中序遍历是递增序列 528 | 529 | ```python 530 | # Definition for a binary tree node. 531 | # class TreeNode(object): 532 | # def __init__(self, x): 533 | # self.val = x 534 | # self.left = None 535 | # self.right = None 536 | 537 | class Solution(object): 538 | def kthSmallest(self, root, k): 539 | """ 540 | :type root: TreeNode 541 | :type k: int 542 | :rtype: int 543 | """ 544 | res = [] 545 | self.visitNode(root, res) 546 | return res[k - 1] 547 | 548 | def visitNode(self, root, res): 549 | if root is None: 550 | return 551 | self.visitNode(root.left, res) 552 | res.append(root.val) 553 | self.visitNode(root.right, res) 554 | ``` -------------------------------------------------------------------------------- /docs/data-structure/tree/n-ary/README.md: -------------------------------------------------------------------------------- 1 | ## 429. N叉树的层序遍历 2 | 3 | [原题链接](https://leetcode-cn.com/problems/n-ary-tree-level-order-traversal/) 4 | 5 | ### 解一:迭代 6 | 7 | 用队列辅助。 8 | 9 | ```python 10 | """ 11 | # Definition for a Node. 12 | class Node: 13 | def __init__(self, val=None, children=None): 14 | self.val = val 15 | self.children = children 16 | """ 17 | class Solution: 18 | def levelOrder(self, root: 'Node') -> List[List[int]]: 19 | queue = [] 20 | res = [] 21 | queue.append(root) 22 | while len(queue) > 0: 23 | q_length = len(queue) 24 | tmp = [] 25 | for i in range(q_length): 26 | first = queue[0] 27 | del queue[0] 28 | if first is None: 29 | continue 30 | tmp.append(first.val) 31 | for child in first.children: 32 | queue.append(child) 33 | if len(tmp) > 0: 34 | res.append(tmp) 35 | return res 36 | ``` 37 | 38 | - 时间复杂度:$O(n)$,n 为节点数量 39 | - 空间复杂度:$O(n)$ 40 | 41 | ### 解二:递归 42 | 43 | ```python 44 | """ 45 | # Definition for a Node. 46 | class Node: 47 | def __init__(self, val=None, children=None): 48 | self.val = val 49 | self.children = children 50 | """ 51 | class Solution: 52 | def levelOrder(self, root: 'Node') -> List[List[int]]: 53 | res = [] 54 | self.helper(res, root, 0) 55 | return res 56 | 57 | def helper(self, res, root, level): 58 | if root is None: 59 | return 60 | if len(res) == level: 61 | res.append([]) 62 | res[level].append(root.val) 63 | for child in root.children: 64 | self.helper(res, child, level + 1) 65 | ``` 66 | 67 | - 时间复杂度:$O(n)$ 68 | - 空间复杂度:最坏 $O(n)$,最好 $O(logn)$ 69 | 70 | ## 559. N叉树的最大深度 71 | 72 | [原题链接](https://leetcode-cn.com/problems/maximum-depth-of-n-ary-tree/) 73 | 74 | ### 递归 75 | 76 | ```python 77 | """ 78 | # Definition for a Node. 79 | class Node: 80 | def __init__(self, val=None, children=None): 81 | self.val = val 82 | self.children = children 83 | """ 84 | class Solution: 85 | def maxDepth(self, root: 'Node') -> int: 86 | if root is None: 87 | return 0 88 | if len(root.children) == 0: 89 | return 1 90 | max_depth = 0 91 | for child in root.children: 92 | max_depth = max(max_depth, self.maxDepth(child)) 93 | return max_depth + 1 94 | ``` 95 | 96 | ## 589. N叉树的前序遍历 97 | 98 | [原题链接](https://leetcode-cn.com/problems/n-ary-tree-preorder-traversal/) 99 | 100 | ### 解一:递归 101 | 102 | ```python 103 | """ 104 | # Definition for a Node. 105 | class Node: 106 | def __init__(self, val=None, children=None): 107 | self.val = val 108 | self.children = children 109 | """ 110 | class Solution: 111 | def preorder(self, root: 'Node') -> List[int]: 112 | res = [] 113 | self.helper(root, res) 114 | return res 115 | 116 | def helper(self, root, res): 117 | if root is None: 118 | return 119 | res.append(root.val) 120 | children = root.children 121 | for child in children: 122 | self.helper(child, res) 123 | ``` 124 | 125 | ### 解二:遍历 126 | 127 | 用栈辅助。 128 | 129 | 1. 首先,将 `root` 压入栈中 130 | 2. 在栈不为空时,对栈进行遍历,每次弹出栈顶元素 131 | 3. 若栈顶元素节点不为空,则将该节点值放入结果集中,且将该节点的子节点**从右至左**压入栈中(这样弹出时就是从左至右,符合前序遍历的顺序) 132 | 133 | ```python 134 | """ 135 | # Definition for a Node. 136 | class Node: 137 | def __init__(self, val=None, children=None): 138 | self.val = val 139 | self.children = children 140 | """ 141 | class Solution: 142 | def preorder(self, root: 'Node') -> List[int]: 143 | stack = [] 144 | stack.append(root) 145 | res = [] 146 | while len(stack) > 0: 147 | top = stack.pop() 148 | if top is None: 149 | continue 150 | res.append(top.val) 151 | # 反序插入子节点 152 | children = top.children 153 | for i in range(len(children) - 1, -1, -1): 154 | child = children[i] 155 | stack.append(child) 156 | return res 157 | ``` 158 | 159 | ## 590. N叉树的后序遍历 160 | 161 | [原题链接](https://leetcode-cn.com/problems/n-ary-tree-postorder-traversal/) 162 | 163 | ### 解一:递归 164 | 165 | ```python 166 | """ 167 | # Definition for a Node. 168 | class Node: 169 | def __init__(self, val=None, children=None): 170 | self.val = val 171 | self.children = children 172 | """ 173 | class Solution: 174 | def postorder(self, root: 'Node') -> List[int]: 175 | res = [] 176 | self.healper(root, res) 177 | return res 178 | 179 | def healper(self, root, res): 180 | if root is None: 181 | return 182 | children = root.children 183 | for child in children: 184 | self.healper(child, res) 185 | res.append(root.val) 186 | ``` 187 | 188 | ### 解二:迭代 189 | 190 | 用一个辅助栈。 191 | 192 | 后序遍历的顺序是:子节点从左至右 -> 根节点。因此我们可以先把「根节点 -> 子节点从右至左」写入结果集中,在返回时再将结果集反序。 193 | 194 | ```python 195 | """ 196 | # Definition for a Node. 197 | class Node: 198 | def __init__(self, val=None, children=None): 199 | self.val = val 200 | self.children = children 201 | """ 202 | class Solution: 203 | def postorder(self, root: 'Node') -> List[int]: 204 | res = [] 205 | stack = [] 206 | stack.append(root) 207 | while len(stack): 208 | top = stack.pop() 209 | if top is None: 210 | continue 211 | res.append(top.val) 212 | for child in top.children: 213 | stack.append(child) 214 | return res[::-1] 215 | ``` -------------------------------------------------------------------------------- /docs/data-structure/tree/other/README.md: -------------------------------------------------------------------------------- 1 | ## 101. 对称二叉树 2 | 3 | [原题链接](https://leetcode-cn.com/problems/symmetric-tree/description/) 4 | 5 | ### 解一:递归 6 | 7 | 递归法,判断二叉树是否为镜像对称。 8 | 9 | 镜像特点: 10 | 11 | - 根节点:左节点值 = 右节点值 12 | - 非根节点:左节点值 = 兄弟节点的右节点值 13 | 14 | 因此,可对每两个镜像节点做如下判断: 15 | 16 | ``` 17 | if 节点A为空: 18 | if 节点B为空: 19 | return 是镜像 20 | else: 21 | return 不是镜像 22 | else: 23 | if 节点B为空: 24 | return 不是镜像 25 | else: 26 | if 节点A值 == 节点B值: 27 | return 是镜像 28 | else: 29 | return 不是镜像 30 | ``` 31 | 32 | 对接下来的节点进行递归: 33 | 34 | - A节点的左节点与B节点的右节点 35 | - A节点的右节点与B节点的左节点 36 | 37 | 38 | 39 | #### **Python** 40 | 41 | ```python 42 | # Definition for a binary tree node. 43 | # class TreeNode: 44 | # def __init__(self, x): 45 | # self.val = x 46 | # self.left = None 47 | # self.right = None 48 | 49 | class Solution: 50 | def isSymmetric(self, root): 51 | """ 52 | :type root: TreeNode 53 | :rtype: bool 54 | """ 55 | if root is None: 56 | return True 57 | else: 58 | return self.checkElement(root.left, root.right) 59 | 60 | def checkElement(self, left_root, right_root): 61 | if left_root is None or right_root is None: 62 | return left_root == right_root 63 | if left_root.val != right_root.val: 64 | return False 65 | return self.checkElement(left_root.left, right_root.right) and self.checkElement(left_root.right, right_root.left) 66 | ``` 67 | 68 | #### **Go** 69 | 70 | ```go 71 | /** 72 | * Definition for a binary tree node. 73 | * type TreeNode struct { 74 | * Val int 75 | * Left *TreeNode 76 | * Right *TreeNode 77 | * } 78 | */ 79 | func isSymmetric(root *TreeNode) bool { 80 | if root == nil { 81 | return true 82 | } 83 | return symmetric(root.Left, root.Right) 84 | } 85 | 86 | func symmetric(left *TreeNode, right *TreeNode) bool { 87 | if left == nil || right == nil { 88 | return left == right 89 | } 90 | if left.Val != right.Val { 91 | return false 92 | } 93 | return symmetric(left.Left, right.Right) && symmetric(left.Right, right.Left) 94 | } 95 | ``` 96 | 97 | 98 | 99 | ### 解二:迭代 100 | 101 | 102 | 103 | #### **Python** 104 | 105 | ```python 106 | # Definition for a binary tree node. 107 | # class TreeNode: 108 | # def __init__(self, x): 109 | # self.val = x 110 | # self.left = None 111 | # self.right = None 112 | 113 | class Solution: 114 | def isSymmetric(self, root: TreeNode) -> bool: 115 | if root is None: 116 | return True 117 | queue = [] 118 | queue.append(root.left) 119 | queue.append(root.right) 120 | while len(queue) > 0: 121 | left = queue[0] 122 | del queue[0] 123 | right = queue[0] 124 | del queue[0] 125 | 126 | if left is None: 127 | if right is None: 128 | pass 129 | else: 130 | return False 131 | else: 132 | if right is None: 133 | return False 134 | else: 135 | queue.append(left.left) 136 | queue.append(right.right) 137 | queue.append(left.right) 138 | queue.append(right.left) 139 | if left.val != right.val: 140 | return False 141 | 142 | return True 143 | ``` 144 | 145 | #### **Go** 146 | 147 | ```go 148 | /** 149 | * Definition for a binary tree node. 150 | * type TreeNode struct { 151 | * Val int 152 | * Left *TreeNode 153 | * Right *TreeNode 154 | * } 155 | */ 156 | func isSymmetric(root *TreeNode) bool { 157 | queue := make([]*TreeNode, 0) 158 | if root == nil { 159 | return true 160 | } 161 | queue = append(queue, root.Left) 162 | queue = append(queue, root.Right) 163 | for len(queue) > 0 { 164 | left := queue[0] 165 | queue = queue[1:] 166 | right := queue[0] 167 | queue = queue[1:] 168 | 169 | if left == nil { 170 | if right != nil { 171 | return false 172 | } 173 | } else { 174 | if right == nil { 175 | return false 176 | } else { 177 | queue = append(queue, left.Left) 178 | queue = append(queue, right.Right) 179 | queue = append(queue, left.Right) 180 | queue = append(queue, right.Left) 181 | if left.Val != right.Val { 182 | return false 183 | } 184 | } 185 | } 186 | } 187 | return true 188 | } 189 | ``` 190 | 191 | 192 | 193 | ## 687. 最长同值路径 194 | 195 | [原题链接](https://leetcode-cn.com/problems/longest-univalue-path/description/) 196 | 197 | ### 思路 198 | 199 | 树的路径问题,使用递归。 200 | 201 | - 对左右节点调用递归函数,分别得到: 202 | - 左节点相同值路径 203 | - 右节点相同值路径 204 | - 同值路径计算: 205 | - 左节点存在且与当前节点值相同,left++;不同:left=0 206 | - 右节点存在且与当前节点值相同,right++;不同:right=0 207 | - 结果 `result = max(result, left + right)` 208 | 209 | ### Python 210 | 211 | ```python 212 | # Definition for a binary tree node. 213 | # class TreeNode: 214 | # def __init__(self, x): 215 | # self.val = x 216 | # self.left = None 217 | # self.right = None 218 | 219 | class Solution(object): 220 | max_length = 0 221 | def longestUnivaluePath(self, root): 222 | """ 223 | :type root: TreeNode 224 | :rtype: int 225 | """ 226 | if not root: 227 | return 0 228 | self.getMaxLen(root, root.val) 229 | return self.max_length 230 | 231 | 232 | def getMaxLen(self, root, val): 233 | if not root: 234 | return 0 235 | 236 | left = self.getMaxLen(root.left, root.val) 237 | right = self.getMaxLen(root.right, root.val) 238 | 239 | if root.left and root.left.val == root.val: 240 | left = left + 1 241 | else: 242 | left = 0 243 | 244 | if root.right and root.right.val == root.val: 245 | right = right + 1 246 | else: 247 | right = 0 248 | 249 | self.max_length = max(self.max_length, left + right) 250 | 251 | # 选择较大路径值与 parent 相连 252 | return max(left, right) 253 | ``` -------------------------------------------------------------------------------- /docs/data-structure/tree/trie/README.md: -------------------------------------------------------------------------------- 1 | ## 820. 单词的压缩编码 2 | 3 | [原题链接](https://leetcode-cn.com/problems/short-encoding-of-words/) 4 | 5 | ### 解一:字典树 6 | 7 | 将 word 反序后放入字典树,字典树中所有可走到叶子结点的路径就是没有后缀的词。因此最终答案是求解: 8 | 9 | ``` 10 | sum(树中每条路径长度 + 1) 11 | ``` 12 | 13 | ```python 14 | class Solution: 15 | 16 | res = 0 17 | 18 | def minimumLengthEncoding(self, words: List[str]) -> int: 19 | words = [x[::-1] for x in words] 20 | root = TrieNode() 21 | 22 | for word in words: 23 | tmp = root 24 | for c in word: 25 | if c not in tmp.dict: 26 | tmp.dict[c] = TrieNode() 27 | tmp = tmp.dict[c] 28 | 29 | self.get_trie_node_count(root, 0) 30 | return self.res 31 | 32 | def get_trie_node_count(self, root, cnt): 33 | cur_length = len(root.dict) 34 | if cur_length == 0: 35 | self.res += cnt + 1 #到叶子结点的路径长度 36 | return 37 | for k, node in root.dict.items(): 38 | self.get_trie_node_count(node, cnt + 1) 39 | 40 | """ 41 | 字典数 Node 42 | """ 43 | class TrieNode: 44 | def __init__(self): 45 | self.dict = dict() 46 | ``` 47 | 48 | - 时间复杂度:$O(\sum w_{i})$($w_{i}$ 为 `words[i]` 的长度) 49 | - 空间复杂度:$O(\sum w_{i})$,所有后缀存储的空间开销 50 | 51 | ### 解二:暴力破解 52 | 53 | 即直接找出可共享后缀的词到底有多少。 54 | 55 | 56 | 57 | #### **Python** 58 | 59 | 使用集合去重,然后枚举每个单词的后缀,把该后缀从集合中删除。 60 | 61 | ```python 62 | class Solution: 63 | def minimumLengthEncoding(self, words: List[str]) -> int: 64 | # 去重 65 | word_unique = set(words) 66 | for word in words: 67 | # 枚举后缀 68 | tmp = '' 69 | for i in range(1, len(word)): 70 | word_unique.discard(word[i:]) 71 | return sum(len(word) + 1 for word in word_unique) 72 | ``` 73 | 74 | - 时间复杂度:$O(\sum w_{i}^2)$($w_{i}$ 为 `words[i]` 的长度),遍历每个单词 + 截取后缀 75 | - 空间复杂度:$O(\sum w_{i})$,所有后缀存储的空间开销 76 | 77 | 2020.03.28 复盘: 78 | 79 | ```python 80 | class Solution: 81 | def minimumLengthEncoding(self, words: List[str]) -> int: 82 | if len(words) == 1: 83 | return len(words[0]) + 1 84 | # 单词整合 85 | # 结果:剩余单词长度 + 剩余单词数量(# 数量) 86 | # 倒过来比较 87 | reverse_words = [word[::-1] for word in words] 88 | # 排序 89 | reverse_words.sort() 90 | res = 0 91 | for i in range(1, len(reverse_words)): 92 | pre_word = reverse_words[i - 1] 93 | cur_word = reverse_words[i] 94 | # 双指针 95 | for j in range(len(pre_word)): 96 | if pre_word[j] != cur_word[j]: 97 | # pre_word 需要单独处理 98 | res += len(pre_word) + 1 99 | break 100 | # 需要加上最后一个 cur_word 101 | res += len(cur_word) + 1 102 | return res 103 | ``` 104 | 105 | #### **Java** 106 | 107 | ```java 108 | class Solution { 109 | public int minimumLengthEncoding(String[] words) { 110 | String[] resWords = new String[words.length]; 111 | int len = 0; 112 | 113 | for (String word : words) { 114 | boolean isContains = false; 115 | for (int i = 0; i < len; i++) { 116 | String w = resWords[i]; 117 | if (w.endsWith(word)) { 118 | isContains = true; 119 | break; 120 | } else if (word.endsWith(w)) { 121 | isContains = true; 122 | resWords[i] = word; 123 | } 124 | } 125 | if (!isContains) { 126 | resWords[len] = word; 127 | len++; 128 | } 129 | } 130 | 131 | int result = len; 132 | for (int i = 0; i < len; i++) { 133 | result += resWords[i].length(); 134 | } 135 | return result; 136 | } 137 | } 138 | ``` 139 | 140 | -------------------------------------------------------------------------------- /docs/design/README.md: -------------------------------------------------------------------------------- 1 | ## 460. LFU缓存 2 | 3 | [原题链接](https://leetcode-cn.com/problems/lfu-cache/) 4 | 5 | ### 解一:简单直白法 6 | 7 | 存储每个页的访问频率 `cnt` 和最近访问标记 `mark`。 8 | 9 | ```python 10 | class Page: 11 | def __init__(self, val, cnt, mark): 12 | self.val = val 13 | self.cnt = cnt 14 | self.mark = mark 15 | 16 | class LFUCache: 17 | 18 | def __init__(self, capacity: int): 19 | self.cap = capacity 20 | self.cache = dict() 21 | self.mark = 0 22 | 23 | def get(self, key: int) -> int: 24 | if key not in self.cache: 25 | return -1 26 | self.cache[key].cnt += 1 27 | self.mark += 1 28 | self.cache[key].mark = self.mark 29 | return self.cache[key].val 30 | 31 | def put(self, key: int, value: int) -> None: 32 | if key in self.cache: 33 | cur_cnt = self.cache[key].cnt + 1 34 | self.mark += 1 35 | self.cache[key] = Page(value, cur_cnt, self.mark) 36 | return 37 | 38 | # 判断是否超过 39 | if len(self.cache) < self.cap: 40 | # 直接写入 41 | self.cache[key] = Page(value, 0, self.mark) 42 | return 43 | 44 | cnt = float('inf') 45 | mark = float('inf') 46 | del_key = None 47 | # 获取最近最少使用的键,cnt 最小,然后 mark 最小 48 | for k in self.cache: 49 | page = self.cache[k] 50 | if page.cnt < cnt: 51 | cnt = page.cnt 52 | mark = page.mark 53 | del_key = k 54 | if page.cnt == cnt and page.mark < mark: 55 | mark = page.mark 56 | del_key = k 57 | 58 | if del_key is not None: 59 | self.cache.pop(del_key) 60 | # 写入新的值 61 | self.mark += 1 62 | self.cache[key] = Page(value, 0, self.mark) 63 | 64 | # Your LFUCache object will be instantiated and called as such: 65 | # obj = LFUCache(capacity) 66 | # param_1 = obj.get(key) 67 | # obj.put(key,value) 68 | ``` -------------------------------------------------------------------------------- /docs/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JalanJiang/leetcode-notebook/8b55ef9d0ae6f7e025d27bdb9bf5d45f9c68e1cd/docs/favicon.ico -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Document 6 | 7 | 8 | 9 | 10 | 11 | 12 | 18 | 19 | 20 | 25 |
Loading ...
26 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /docs/offer/README.md: -------------------------------------------------------------------------------- 1 | ## 面试题 01.06. 字符串压缩 2 | 3 | [原题链接](https://leetcode-cn.com/problems/compress-string-lcci/) 4 | 5 | ### 思路 6 | 7 | 8 | 9 | #### **Python** 10 | 11 | ```python 12 | class Solution: 13 | def compressString(self, S: str) -> str: 14 | s_length = len(S) 15 | if s_length == 0: 16 | return S 17 | res = '' 18 | cur = '' 19 | count = 0 20 | for s in S: 21 | if s != cur: 22 | if cur != '': 23 | res += cur + str(count) 24 | cur = s 25 | count = 1 26 | else: 27 | count += 1 28 | # 尾部处理 29 | res += cur + str(count) 30 | return res if len(res) < s_length else S 31 | ``` 32 | 33 | #### **Go** 34 | 35 | Go 字符串相关知识点: 36 | 37 | - `rune` -> `string`:`string(rune)` 38 | - `int` -> `string`: `strconv.Itoa(int)` 39 | 40 | ```go 41 | func compressString(S string) string { 42 | sLength := len(S) 43 | if sLength == 0 { 44 | return S 45 | } 46 | var res string 47 | var cur rune 48 | var empty rune 49 | var count int 50 | for _, s := range S { 51 | if s != cur { 52 | if cur != empty { 53 | res += string(cur) + strconv.Itoa(count) 54 | } 55 | cur = s 56 | count = 1 57 | } else { 58 | count += 1 59 | } 60 | } 61 | res += string(cur) + strconv.Itoa(count) 62 | if len(res) < sLength { 63 | return res 64 | } 65 | return S 66 | } 67 | ``` 68 | 69 | 70 | 71 | ## 面试题 01.07. 旋转矩阵 72 | 73 | [原题链接](https://leetcode-cn.com/problems/rotate-matrix-lcci/) 74 | 75 | ### 思路 76 | 77 | 找出要进行顺时针旋转的四个位置分别是:`(i, j) (j, n-i-1) (n-i-1, n-j-1)` 78 | 79 | ```python 80 | class Solution: 81 | def rotate(self, matrix: List[List[int]]) -> None: 82 | """ 83 | Do not return anything, modify matrix in-place instead. 84 | """ 85 | n = len(matrix) 86 | # 旋转交换的四个位置:(i, j) (j, n-i-1) (n-i-1, n-j-1) (n-j-1, i) 87 | # 枚举范围:宽 (n + 1)/2 高 n/2 88 | for i in range(n // 2): 89 | for j in range((n + 1) // 2): 90 | temp = matrix[i][j] 91 | matrix[i][j] = matrix[n - j - 1][i] 92 | matrix[n - j - 1][i] = matrix[n - i - 1][n - j - 1] 93 | matrix[n - i - 1][n - j - 1] = matrix[j][n - i - 1] 94 | matrix[j][n - i - 1] = temp 95 | ``` 96 | 97 | ## 面试题03. 数组中重复的数字 98 | 99 | [原题链接](https://leetcode-cn.com/problems/shu-zu-zhong-zhong-fu-de-shu-zi-lcof/) 100 | 101 | 找出数组中重复的数字。 102 | 103 | 104 | 在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。 105 | 106 | 示例 1: 107 | 108 | ``` 109 | 输入: 110 | [2, 3, 1, 0, 2, 5, 3] 111 | 输出:2 或 3 112 | ``` 113 | 114 | 限制: 115 | 116 | `2 <= n <= 100000` 117 | 118 | ### 哈希 119 | 120 | 121 | 122 | #### **Python** 123 | 124 | ```python 125 | class Solution: 126 | def findRepeatNumber(self, nums: List[int]) -> int: 127 | m = dict() 128 | for n in nums: 129 | if n in m: 130 | return n 131 | m[n] = True 132 | ``` 133 | 134 | #### **Go** 135 | 136 | ```go 137 | func findRepeatNumber(nums []int) int { 138 | m := make([]int, len(nums)) 139 | for _, e := range nums { 140 | if m[e] == 1 { 141 | return e 142 | } 143 | m[e] = 1 144 | } 145 | return -1 146 | } 147 | ``` 148 | 149 | 150 | 151 | ## 面试题06. 从尾到头打印链表 152 | 153 | [原题链接](https://leetcode-cn.com/problems/cong-wei-dao-tou-da-yin-lian-biao-lcof/) 154 | 155 | 输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)。 156 | 157 | **示例 1:** 158 | 159 | ``` 160 | 输入:head = [1,3,2] 161 | 输出:[2,3,1] 162 | ``` 163 | 164 | 限制: 165 | 166 | `0 <= 链表长度 <= 10000` 167 | 168 | ### 解一:栈 169 | 170 | 根据「从尾到头反过来」的描述很容易想到有着「先进后出」特征的数据结构 —— **栈**。 171 | 172 | 我们可以利用栈完成这一逆序过程,将链表节点值按顺序逐个压入栈中,再按各个节点值的弹出顺序返回即可。 173 | 174 | ```python 175 | # Definition for singly-linked list. 176 | # class ListNode: 177 | # def __init__(self, x): 178 | # self.val = x 179 | # self.next = None 180 | 181 | class Solution: 182 | def reversePrint(self, head: ListNode) -> List[int]: 183 | stack = list() 184 | while head is not None: 185 | stack.append(head.val) 186 | head = head.next 187 | 188 | return stack[::-1] 189 | ``` 190 | 191 | 复杂度: 192 | 193 | - 时间复杂度:$O(n)$ 194 | - 空间复杂度:$O(n)$ 195 | 196 | ### 解二:递归 197 | 198 | 递归也能模拟栈的逆序过程。 199 | 200 | 我们将链表节点传入递归函数,递归函数设计如下: 201 | 202 | - 递归函数作用:将链表节点值逆序存入结果集 203 | - 结束条件:当节点为空时 204 | - 递归调用条件:当下一个节点不为空时 205 | 206 | ```python 207 | # Definition for singly-linked list. 208 | # class ListNode: 209 | # def __init__(self, x): 210 | # self.val = x 211 | # self.next = None 212 | 213 | class Solution: 214 | def reversePrint(self, head: ListNode) -> List[int]: 215 | res = [] 216 | self.reverse(head, res) 217 | return res 218 | 219 | def reverse(self, head, res): 220 | if head is None: 221 | return 222 | if head.next is not None: 223 | # 下一个节点不为空:递归调用 224 | self.reverse(head.next, res) 225 | res.append(head.val) 226 | ``` 227 | 228 | 简化一下: 229 | 230 | 231 | 232 | #### **Python** 233 | 234 | ```python 235 | # Definition for singly-linked list. 236 | # class ListNode: 237 | # def __init__(self, x): 238 | # self.val = x 239 | # self.next = None 240 | 241 | class Solution: 242 | def reversePrint(self, head: ListNode) -> List[int]: 243 | if head is None: 244 | return [] 245 | res = self.reversePrint(head.next) 246 | res.append(head.val) 247 | return res 248 | ``` 249 | 250 | #### **Go** 251 | 252 | ```go 253 | /** 254 | * Definition for singly-linked list. 255 | * type ListNode struct { 256 | * Val int 257 | * Next *ListNode 258 | * } 259 | */ 260 | func reversePrint(head *ListNode) []int { 261 | if head == nil { 262 | return []int{} 263 | } 264 | res := reversePrint(head.Next) 265 | return append(res, head.Val) 266 | } 267 | ``` 268 | 269 | 270 | 271 | 复杂度: 272 | 273 | - 时间复杂度:$O(n)$ 274 | - 空间复杂度:$O(n)$ 275 | 276 | ## 面试题 17.16. 按摩师 277 | 278 | [原题链接](https://leetcode-cn.com/problems/the-masseuse-lcci/) 279 | 280 | ### 动态规划 281 | 282 | - 用 `dp[0][i]` 表示不接 i 预约可以获得的最长预约时间 283 | - 用 `dp[1][i]` 表示接 i 预约可以获得的最长预约时间 284 | 285 | ```python 286 | class Solution: 287 | def massage(self, nums: List[int]) -> int: 288 | # dp[0][i] 不接 289 | # dp[1][i] 接 290 | length = len(nums) 291 | if length == 0: 292 | return 0 293 | dp = [[0 for _ in range(length)] for _ in range(2)] 294 | # 转移方程:dp[0][i] = max(dp[1][i - 1], dp[0][i - 1]) 295 | # dp[1][i] = max(dp[0][i - 1] + nums[i]) 296 | dp[1][0] = nums[0] 297 | for i in range(1, length): 298 | dp[0][i] = max(dp[1][i - 1], dp[0][i - 1]) 299 | dp[1][i] = dp[0][i - 1] + nums[i] 300 | return max(dp[0][length - 1], dp[1][length - 1]) 301 | ``` 302 | 303 | ## 面试题 08.11. 硬币 304 | 305 | [原题链接](https://leetcode-cn.com/problems/coin-lcci/) 306 | 307 | ### 思路 308 | 309 | 1. 遍历所有硬币 310 | 2. `dp[i - coin]` 表示去掉 coin 面额后的组合数量 311 | 312 | ```python 313 | class Solution: 314 | def waysToChange(self, n: int) -> int: 315 | coins = [1, 5, 10, 25] 316 | dp = [0 for _ in range(n + 1)] 317 | dp[0] = 1 318 | for coin in coins: 319 | for i in range(coin, n + 1): 320 | dp[i] = (dp[i] + dp[i - coin]) % 1000000007 321 | return dp[n] 322 | ``` 323 | 324 | ## 面试题 16.03. 交点 325 | 326 | [原题链接](https://leetcode-cn.com/problems/intersection-lcci/) 327 | 328 | ### 思路 329 | 330 | 解方程题目。。。就是代码写得太丑了。 331 | 332 | ```python 333 | class Solution: 334 | def intersection(self, start1: List[int], end1: List[int], start2: List[int], end2: List[int]) -> List[float]: 335 | # kx + b = y 336 | # 调换位置:x1 < x2, x3 < x4,避免后续判断 337 | x1, y1 = start1[0], start1[1] 338 | x2, y2 = end1[0], end1[1] 339 | if x1 > x2: 340 | x1, x2 = x2, x1 341 | y1, y2 = y2, y1 342 | x3, y3 = start2[0], start2[1] 343 | x4, y4 = end2[0], end2[1] 344 | if x3 > x4: 345 | x3, x4 = x4, x3 346 | y3, y4 = y4, y3 347 | 348 | # 判断是否有交点 349 | if x2 < x3 or x4 < x1 or max(y1, y2) < min(y3, y4) or max(y3, y4) < min(y1, y2): 350 | # 此时两线段不会有交点 351 | return ans 352 | 353 | # 计算斜率和参数 354 | b1, b2 = 0, 0 355 | if x1 == x2: 356 | k1 = None 357 | else: 358 | k1 = (y2 - y1) / (x2 - x1) 359 | b1 = y1 - k1 * x1 360 | if x3 == x4: 361 | k2 = None 362 | else: 363 | k2 = (y4 - y3) / (x4 - x3) 364 | b2 = y3 - k2 * x3 365 | 366 | # print(k1, b1, k2, b2) 367 | 368 | # 判断具体条件 369 | if k1 is None and k2 is None: 370 | if x1 == x3: 371 | # 垂直重合,判断 y 的交点 372 | if min(y1, y2) <= max(y3, y4): 373 | return [x1, min(y1, y2)] 374 | if min(y3, y4) <= max(y1, y2): 375 | return [x1, min(y3, y4)] 376 | return [] 377 | 378 | if k1 is None: 379 | return [x1, k2 * x1 + b2] 380 | 381 | if k2 is None: 382 | return [x3, k1 * x3 + b1] 383 | 384 | if k1 == k2: 385 | # 平行或重叠 386 | if b1 == b2: 387 | # 相交 388 | # 找 x 区间的相交点 389 | if x3 >= x1 and x3 <= x2: 390 | return [x3, y3] 391 | if x4 >= x1 and x4 <= x2: 392 | return [x4, y4] 393 | if x1 >= x3 and x1 <= x4: 394 | return [x1, y1] 395 | if x2 >= x3 and x2 <= x4: 396 | return [x2, y2] 397 | return [] 398 | 399 | # 相交,求交点 400 | x = (b2 - b1) / (k1 - k2) 401 | y = k1 * x + b1 402 | if x < x1 or x < x3 or x > x2 or x > x4 or y < min(y1, y2) or y < min(y3, y4) or y > max(y1, y2) or y > max(y3, y4): 403 | return [] 404 | return [x, y] 405 | ``` 406 | 407 | ## 面试题24. 反转链表 408 | 409 | [原题链接](https://leetcode-cn.com/problems/fan-zhuan-lian-biao-lcof/) 410 | 411 | ### 解一 412 | 413 | ```python 414 | # Definition for singly-linked list. 415 | # class ListNode: 416 | # def __init__(self, x): 417 | # self.val = x 418 | # self.next = None 419 | 420 | class Solution: 421 | def reverseList(self, head: ListNode) -> ListNode: 422 | pre = None 423 | while head: 424 | next_node = head.next 425 | head.next = pre 426 | pre = head 427 | head = next_node 428 | return pre 429 | ``` 430 | 431 | ### 解二:递归法 432 | 433 | ```python 434 | # Definition for singly-linked list. 435 | # class ListNode: 436 | # def __init__(self, x): 437 | # self.val = x 438 | # self.next = None 439 | 440 | class Solution: 441 | def reverseList(self, head: ListNode) -> ListNode: 442 | if head is None or head.next is None: 443 | return head 444 | # 自顶向下,把 head 的后面的头传进去翻转,得到的是翻转链表的尾巴,后面链表翻转完的尾巴就是 head.next 445 | cur = self.reverseList(head.next) 446 | # 翻转最后一个 head。由于链表翻转完的尾巴就是 head.next,要让 head 变为最后一个,那就是 head.next.next = head 447 | head.next.next = head 448 | # 断开链接 449 | head.next = None 450 | return cur 451 | ``` 452 | 453 | ## 面试题56 - I. 数组中数字出现的次数 454 | 455 | [原题链接](https://leetcode-cn.com/problems/shu-zu-zhong-shu-zi-chu-xian-de-ci-shu-lcof/) 456 | 457 | ### 思路:异或 458 | 459 | 1. 求出所有数异或的结果 460 | 2. 结果中为 1 的位表示两个数不同数字的位 461 | 3. 根据这个为 1 的位置把 `nums` 分成两组分别异或就可以得出结果 462 | 463 | ```python 464 | class Solution: 465 | def singleNumbers(self, nums: List[int]) -> List[int]: 466 | ret = 0 467 | for n in nums: 468 | ret ^= n 469 | # 找出从右到左第一个为 1 的数 470 | index = 0 471 | while ret & 1 == 0: 472 | # 右移 1 位 473 | ret >>= 1 474 | index += 1 475 | a, b = 0, 0 476 | for n in nums: 477 | if (n >> index) & 1 == 0: 478 | a ^= n 479 | else: 480 | b ^= n 481 | return [a, b] 482 | ``` 483 | 484 | ## 面试题57 - II. 和为s的连续正数序列 485 | 486 | [原题链接](https://leetcode-cn.com/problems/he-wei-sde-lian-xu-zheng-shu-xu-lie-lcof/) 487 | 488 | ### 滑动窗口法 489 | 490 | ```python 491 | class Solution: 492 | def findContinuousSequence(self, target: int) -> List[List[int]]: 493 | s = 0 494 | tmp = [] 495 | res = [] 496 | for i in range(1, target): 497 | s += i 498 | tmp.append(i) 499 | while s >= target: 500 | if s == target: 501 | res.append(tmp[:]) 502 | s -= tmp[0] 503 | del tmp[0] 504 | return res 505 | ``` 506 | 507 | ## 面试题59 - II. 队列的最大值 508 | 509 | [原题链接](https://leetcode-cn.com/problems/dui-lie-de-zui-da-zhi-lcof/) 510 | 511 | ### 思路 512 | 513 | 用一个双端队列,将队列最大值永远放在双端队列的队首。 514 | 515 | 516 | 517 | #### **Python** 518 | 519 | ```python 520 | class MaxQueue: 521 | 522 | def __init__(self): 523 | self.m = 0 524 | self.queue = [] 525 | self.deque = [] 526 | 527 | def max_value(self) -> int: 528 | if len(self.queue) == 0: 529 | return -1 530 | return self.deque[0] 531 | 532 | def push_back(self, value: int) -> None: 533 | self.queue.append(value) 534 | # 维护 deque 535 | while len(self.deque) > 0 and self.deque[-1] < value: 536 | # 从尾部删除这个值 537 | self.deque.pop() 538 | self.deque.append(value) 539 | 540 | def pop_front(self) -> int: 541 | if len(self.queue) == 0: 542 | return -1 543 | first = self.queue[0] 544 | del self.queue[0] 545 | if first == self.deque[0]: 546 | del self.deque[0] 547 | return first 548 | 549 | # Your MaxQueue object will be instantiated and called as such: 550 | # obj = MaxQueue() 551 | # param_1 = obj.max_value() 552 | # obj.push_back(value) 553 | # param_3 = obj.pop_front() 554 | ``` 555 | 556 | #### **Go** 557 | 558 | ```go 559 | type MaxQueue struct { 560 | Queue []int 561 | Deque []int 562 | } 563 | 564 | 565 | func Constructor() MaxQueue { 566 | var maxQueue MaxQueue 567 | maxQueue.Queue = make([]int, 0) 568 | maxQueue.Deque = make([]int, 0) 569 | return maxQueue 570 | } 571 | 572 | 573 | func (this *MaxQueue) Max_value() int { 574 | if len(this.Queue) == 0 { 575 | return -1 576 | } 577 | return this.Deque[0] 578 | } 579 | 580 | 581 | func (this *MaxQueue) Push_back(value int) { 582 | // 维护双端队列 583 | this.Queue = append(this.Queue, value) 584 | for len(this.Deque) > 0 && value > this.Deque[len(this.Deque) - 1] { 585 | // 最后一个值出队 586 | this.Deque = this.Deque[:len(this.Deque) - 1] 587 | } 588 | this.Deque = append(this.Deque, value) 589 | } 590 | 591 | 592 | func (this *MaxQueue) Pop_front() int { 593 | if len(this.Queue) == 0 { 594 | return -1 595 | } 596 | first := this.Queue[0] 597 | if first == this.Deque[0] { 598 | // 删除第一个元素 599 | this.Deque = this.Deque[1:] 600 | } 601 | this.Queue = this.Queue[1:] 602 | return first 603 | } 604 | 605 | 606 | /** 607 | * Your MaxQueue object will be instantiated and called as such: 608 | * obj := Constructor(); 609 | * param_1 := obj.Max_value(); 610 | * obj.Push_back(value); 611 | * param_3 := obj.Pop_front(); 612 | */ 613 | ``` 614 | 615 | 616 | 617 | ## 面试题62. 圆圈中最后剩下的数字 618 | 619 | [原题链接](https://leetcode-cn.com/problems/yuan-quan-zhong-zui-hou-sheng-xia-de-shu-zi-lcof/) 620 | 621 | ### 思路 622 | 623 | 我们用 `f(n, m)` 表示从 n 个数中每次删除第 m 个数(共删除了 n - 1 次),最后留下的那个数的**序号**。 624 | 625 | 我们从 `f(n, m)` 场景下删除的第 1 个数是从序号 0 开始,向后数 m 个数得到的。删除第一个数后,将剩下 n - 1 个数,此时场景变为 `f(n - 1, m)`,用于表示从 n - 1 个数中每次删除第 m 个数最后留下的数的**序号**。 626 | 627 | 在往下看之前,我们先达成一个共识:**`f(n, m)` 与 `f(n - 1, m)` 得到的数是同一个数(最后剩下的数在每一轮中都不可能被删除),只是在它们所在的场景下,这个数字的序号不同罢了。** 628 | 629 | 那么,何谓「所在场景,序号不同」? 630 | 631 | 这里所说的「序号」与**所在场景下首次选取删除数字的出发点有关**,我们直接看下题目给出的 `n = 5, m = 3` 这个例子,已知答案为 3。 632 | 633 | ### 不同场景下的不同序号 634 | 635 | #### f(n, m) 场景 636 | 637 | 此时 `n = 5`,由于我们从第 1 个数字出发,所以从第 1 个数字开始编号: 638 | 639 | ``` 640 | 数字: 641 | 0 1 2 3 4 642 | 序号: 643 | 0 1 2 3 4 644 | ``` 645 | 646 | 可以看到答案 3 在该场景下的序号为 3。 647 | 648 | #### f(n - 1, m) 场景 649 | 650 | 此时,我们已经在 `f(n, m)` 场景下删除一个数了,这个数是 2,因此我们要从 3 开始重新编号: 651 | 652 | ``` 653 | 数字: 654 | 0 1 3 4 655 | 序号: 656 | 2 3 0 1 657 | ``` 658 | 659 | 答案 3 在该场景下的序号为 0。 660 | 661 | ### 两者序号的关系 662 | 663 | 我们知道,从 `f(n - m)` 场景下删除的第一个数的**序号**是 `(m - 1) % n`,那么 `f(n - 1, m)` 场景将使用被删除数字的下一个数,即序号 `m % n` 作为它的 0 序号。 664 | 665 | 设 `f(n - 1, m)` 的结果为 `x`,`x` 是从 `f(n, m)` 场景下序号为 `m % n` 的数字出发所获得的结果,因此,我们可以得出:`m % n + x` 是该数字在 `f (n, m)` 场景下的结果序号。即: 666 | 667 | ``` 668 | f(n, m) = m % n + x 669 | ``` 670 | 但由于 `m % n + x` 可能会超过 n 的范围,所以我们再取一次模: 671 | 672 | ``` 673 | f(n , m) = (m % n + x) % n = (m + x) % n 674 | ``` 675 | 676 | 将 `f(n - 1, m)` 代回,得到递推公式: 677 | 678 | ``` 679 | f(n, m) = (m + f(n - 1, m)) % n 680 | ``` 681 | 682 | 有了递推公式后,想递归就递归,想迭代就迭代咯~ 683 | 684 | ### 具体实现 685 | 686 | ```python 687 | sys.setrecursionlimit(100000) 688 | 689 | class Solution: 690 | def lastRemaining(self, n: int, m: int) -> int: 691 | return self.f(n, m) 692 | 693 | def f(self, n, m): 694 | if n == 0: 695 | return 0 696 | x = self.f(n - 1, m) 697 | return (m + x) % n 698 | ``` 699 | 700 | 各位大佬的图实在画得太好了,我就不献丑了(逃 701 | 702 | ## 面试题68 - I. 二叉搜索树的最近公共祖先 703 | 704 | 同:235. 二叉搜索树的最近公共祖先 -------------------------------------------------------------------------------- /docs/other/README.md: -------------------------------------------------------------------------------- 1 | ## 2019 力扣杯全国秋季编程大赛 2 | 3 | [点击前往](https://leetcode-cn.com/contest/season/2019-fall/) 4 | 5 | ### 1. 猜数字 6 | 7 | [原题链接](https://leetcode-cn.com/contest/season/2019-fall/problems/guess-numbers/) 8 | 9 | #### 思路 10 | 11 | ```python 12 | class Solution: 13 | def game(self, guess: List[int], answer: List[int]) -> int: 14 | res = 0 15 | for i in range(3): 16 | if guess[i] == answer[i]: 17 | res += 1 18 | return res 19 | ``` 20 | 21 | ### 2. 分式化简 22 | 23 | [原题链接](https://leetcode-cn.com/contest/season/2019-fall/problems/deep-dark-fraction/) 24 | 25 | #### 思路 26 | 27 | 是一个不断“取反、相加”的循环。 28 | 29 | ```python 30 | class Solution: 31 | def fraction(self, cont: List[int]) -> List[int]: 32 | length = len(cont) 33 | res = [cont[length - 1], 1] 34 | for i in range(length - 2, -1, -1): 35 | # 取反 36 | cur = cont[i] 37 | left = cur * res[0] 38 | res = [res[1] + left, res[0]] 39 | 40 | return res 41 | ``` 42 | 43 | ### 3. 机器人大冒险 44 | 45 | [原题链接](https://leetcode-cn.com/contest/season/2019-fall/problems/programmable-robot/) 46 | 47 | #### 思路 48 | 49 | 先记录一波数据: 50 | 51 | 1. 把执行一次命令能走到的点都记录下来,记为 `path` 52 | 2. 把命令中 `R` 的次数记作 `px`,`U` 的次数记作 `py` 53 | 54 | 那么拿到一个点 `(x, y)` 时,求出需要循环执行命令的次数: 55 | 56 | ``` 57 | r = min(x // px, y // py) 58 | ``` 59 | 60 | 循环 `r` 次命令后剩余的步数为:`(x - r * px, y - r * py)`。若 `(x - r * px, y - r * py)` 在 `path` 中,则说明可以走到点 `(x, y)`。 61 | 62 | ```python 63 | class Solution: 64 | def robot(self, command: str, obstacles: List[List[int]], x: int, y: int) -> bool: 65 | path = set() 66 | path.add((0, 0)) 67 | 68 | px = 0 69 | py = 0 70 | for c in command: 71 | if c == "R": 72 | px += 1 73 | else: 74 | py += 1 75 | path.add((px, py)) 76 | 77 | def can_arrive(x, y): 78 | x_round = x // px 79 | y_round = y // py 80 | r = min(x_round, y_round) 81 | lx = x - r * px 82 | ly = y - r * py 83 | if (lx, ly) in path: 84 | return True 85 | else: 86 | return False 87 | 88 | if not can_arrive(x, y): 89 | return False 90 | 91 | for ob in obstacles: 92 | if ob[0] > x or ob[1] > y: 93 | continue 94 | if can_arrive(ob[0], ob[1]): 95 | return False 96 | 97 | return True 98 | ``` 99 | 100 | ### 4. 覆盖 101 | 102 | [原题链接](https://leetcode-cn.com/contest/season/2019-fall/problems/broken-board-dominoes/) 103 | 104 | 状态压缩 + 动态规划完成状态转移。 105 | 106 | 一个格子无非只有如下几种情况: 107 | 108 | 1. 放骨牌 109 | 1. 横着放 110 | 2. 竖着放 111 | 2. 不放骨牌 112 | 1. 不是坏格子,选择不放 113 | 2. 是坏格子,不可以放 114 | 115 | 来看一个 `n = 3, m = 3` 的栗子: 116 | 117 | 118 | 119 | 我们从上往下、从左往右对所有格子进行遍历。此时我们遍历到黄色格子 `0`,判断该格子能否放置骨牌: 120 | 121 | 1. 如果格子 `0` 是一个坏掉的格子:不能放置骨牌 122 | 2. 如果格子 `0` 不是一个坏格子: 123 | 1. 如果上侧的格子 `3` 是一个未被占用的好格子:格子 `0` 可以竖放骨牌 124 | 2. 如果左侧的格子 `1` 是一个未被占用的好格子:格子 `0` 可以横放骨牌 125 | 3. 如果上侧格子 `3` 和左侧格子 `1` 都无法放置骨牌:格子 `0` 无法放置骨牌 126 | 127 | 由此,我们可以发现,对于一个格子来说,**它的前 `m` 个格子的状态决定了该格子放置骨牌的可能性**。 128 | 129 | #### 状态表示 130 | 131 | 由于需要记录 `m` 个格子的状态,而一个格子又只有 2 种状态(放骨牌或不放骨牌),很自然地想到可以用**`m` 位二进制数**来表示这 `m` 个格子的状态。 132 | 133 | - `0` 表示格子不放置骨牌 134 | - `1` 表示格子放置骨牌 135 | 136 | `m` 个格子就有 $2^m$ 种状态。 137 | 138 | #### 状态转移 139 | 140 | 我们从始至终都只需要用到**所遍历到格子的前 `m` 个格子的状态**,因此需要不断更新这 `m` 个格子的状态,即进行**状态转移**。上述栗子中,`(3, 2, 1)` 格子的状态就需要转移为 `(2, 1, 0)` 格子的状态。 141 | 142 | 这个步骤用位运算实现: 143 | 144 | 1. 坏格子:`砍掉状态最高位 -> 左移 1 位 -> 最低位置 1` 145 | 2. 不放骨牌:`砍掉状态最高位 -> 左移 1 位` 146 | 3. 横放骨牌:`砍掉状态最高位 -> 左移 1 位 -> 最低两位置 1` 147 | 4. 竖放骨牌:`砍掉状态最高位 -> 左移 1 位 -> 最低位置 1` 148 | 149 | #### 动态规划 150 | 151 | 如果一个格子有多种放置骨牌的方法,又该怎么办?答案是用**动态规划**选择能放置最多骨牌数量的最优解。 152 | 153 | 我们开辟一个 $2^m$ 大小的数组空间 `dp`。数组的标为状态值,数组的值用于表示该状态下能摆放的最多骨牌数,当值为 `-1` 时表示该状态为非法状态。 154 | 155 | #### 步骤总结 156 | 157 | 1. 从左往右、从上往下遍历格子(意图为计算下一个状态 `next_dp`) 158 | 2. 在当前格子中遍历所有 $2^m$ 种状态 159 | 3. 若状态非法,继续步骤 2 160 | 4. 若状态合法,根据状态转移计算出下一个状态 `next_state` 以及该状态下对应的 `dp` 161 | 162 | #### 代码 163 | 164 | 复杂度为 $O(m * n * 2^m)$。 165 | 166 | ```python 167 | class Solution: 168 | def domino(self, n: int, m: int, broken: List[List[int]]) -> int: 169 | # broken 记录 170 | broken_map = dict() 171 | for b in broken: 172 | if b[0] not in broken_map: 173 | broken_map[b[0]] = dict() 174 | broken_map[b[0]][b[1]] = True 175 | 176 | """ 177 | 是坏格子 178 | """ 179 | def is_broken(a, b): 180 | if a in broken_map and b in broken_map[a]: 181 | return True 182 | else: 183 | return False 184 | 185 | """ 186 | 获取下一个状态 187 | put: 0-不放,1-横放,2-竖放,3-坏格子 188 | """ 189 | def next_state(state, put): 190 | # 去除高位 191 | state = ~(1 << (m - 1)) & state 192 | 193 | if put == 0: 194 | # 不放牌 195 | return state << 1 196 | elif put == 1: 197 | # 横着放 198 | return ((state | 1) << 1) | 1 199 | elif put == 2: 200 | # 竖着放 201 | return (state << 1) | 1 202 | else: 203 | # 坏格子 204 | return (state << 1) | 1 205 | 206 | # 创建 dp 207 | dp = [-1] * (1 << m) 208 | dp[(1 << m) - 1] = 0 # 全 1 的置 0 209 | 210 | for i in range(n): 211 | for j in range(m): 212 | next_dp = [-1] * (1 << m) 213 | # 遍历所有状态 214 | for state in range(1 << m): 215 | # 非法状态 216 | if dp[state] == -1: 217 | continue 218 | 219 | if is_broken(i, j): 220 | # 是坏格子 221 | next_dp[next_state(state, 3)] = max(next_dp[next_state(state, 3)], dp[state]) # 是从 dp 转义还是保持现状 222 | continue 223 | 224 | # 不放 225 | next_dp[next_state(state, 0)] = max(next_dp[next_state(state, 0)], dp[state]) 226 | 227 | # 可以竖放 228 | if i != 0 and (1 << (m - 1)) & state == 0: 229 | next_dp[next_state(state, 2)] = max(next_dp[next_state(state, 2)], dp[state] + 1) 230 | 231 | # 可以横放 232 | if j != 0 and 1 & state == 0: 233 | next_dp[next_state(state, 1)] = max(next_dp[next_state(state, 1)], dp[state] + 1) 234 | 235 | dp = next_dp 236 | 237 | return max(dp) 238 | ``` 239 | 240 | ### 5. 发 LeetCoin 241 | 242 | [原题链接](https://leetcode-cn.com/contest/season/2019-fall/problems/coin-bonus/) 243 | 244 | 把题中所有上级下级关系转化为一棵树。如果每次操作和查询都挨个遍历树的所有子节点是不现实的,分分钟超出时间限制。为了节省时间,我们需要对每个节点单独记录几个属性: 245 | 246 | 1. 节点的父节点(用于向上更新父节点值)`parent` 247 | 2. 节点和其子节点的 coin 总数 `coin` 248 | 3. 通过操作 2 操作该节点时下发的 coin 数量 `children_coin` 249 | 4. 该节点包含的子节点数 `count` 250 | 251 | 假设我们对节点 `node` 执行以下操作: 252 | 253 | #### 操作 1 254 | 255 | 操作 1 发放 `val` 数量的 LeetCoin: 256 | 257 | 1. 更新 `node` 节点的 `coin += val` 258 | 2. 更新其所有父节点的 `coin += val` 259 | 260 | #### 操作 2 261 | 262 | 操作 2 发放 `val` 数量的 LeetCoin,节点 `node` 共有 `node.count` 个子节点,因此共加上 LeetCoin `all_coin = node.count * val` 个: 263 | 264 | 1. 更新 `node` 节点的 `coin += all_coin` 265 | 2. 更新 `node` 节点的所有父节点 `coin += all_coin` 266 | 3. 更新 `node` 节点的 `children_coin += val` 267 | 268 | #### 操作 3 269 | 270 | `node` 节点与其子节点的所有 LeetCoin 和为: 271 | 272 | ``` 273 | node.coin + (node.parent.children_coin * node.count + node.parent.parnet.children_coin * node.count ......) 274 | ``` 275 | 276 | 因为每个节点的 `children_coin` 代表对子节点产生的 LeetCoin 影响,因此计算每个节点与其子节点的 LeetCoin 之和时,需要往前遍历它的所有父节点,并加上 `children_coin * node.count`。 277 | 278 | #### 实现 279 | 280 | ```python 281 | class Node: 282 | def __init__(self): 283 | self.parent = None # 父节点 284 | self.children = [] # 子节点数组 285 | self.coin = 0 # 子树总coin 286 | self.children_coin = 0 # 2 操作分配的 coin(影响到所有子树) 287 | self.count = 1 # 子树节点数 288 | 289 | class Solution: 290 | def bonus(self, n: int, leadership: List[List[int]], operations: List[List[int]]) -> List[int]: 291 | # 初始化数据 292 | nodes = dict() 293 | for i in range(1, n + 1): 294 | nodes[i] = Node() 295 | 296 | # 记录从属关系 297 | for leader, worker in leadership: 298 | lnode = nodes[leader] 299 | wnode = nodes[worker] 300 | lnode.children.append(wnode) 301 | wnode.parent = lnode 302 | 303 | """ 304 | 计算 count 305 | """ 306 | def cal_count(root): 307 | for child in root.children: 308 | cal_count(child) 309 | root.count += child.count 310 | return 311 | 312 | """ 313 | 更新父项 314 | """ 315 | def update_parents(node, coin): 316 | while node: 317 | node.coin += coin 318 | node = node.parent 319 | 320 | """ 321 | 获取查询结果 322 | """ 323 | def get_res(node): 324 | res = node.coin 325 | node_count = node.count 326 | parent = node.parent 327 | while parent: 328 | res += parent.children_coin * node_count 329 | parent = parent.parent 330 | return res 331 | 332 | # 计算 count 333 | cal_count(nodes[1]) 334 | 335 | res = [] 336 | for op in operations: 337 | worker = op[1] 338 | if op[0] == 1: 339 | # 操作 1:个人发放 340 | nodes[worker].coin += op[2] 341 | update_parents(nodes[worker].parent, op[2]) # 更新它的所有父节点 342 | elif op[0] == 2: 343 | # 操作 2:团队发放 344 | all_coin = nodes[worker].count * op[2] 345 | # 当前节点加上数量 346 | nodes[worker].children_coin += op[2] 347 | nodes[worker].coin += all_coin 348 | # 更新父节点 349 | update_parents(nodes[worker].parent, all_coin) 350 | else: 351 | res.append((get_res(nodes[worker])) % (10**9 + 7)) 352 | return res 353 | ``` --------------------------------------------------------------------------------