├── .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 | [](https://creativecommons.org/licenses/by/3.0/cn/)
4 | [](https://github.com/996icu/996.ICU/blob/master/LICENSE)
5 | [](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 | 
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 | [](https://creativecommons.org/licenses/by/3.0/cn/)
4 | [](https://github.com/996icu/996.ICU/blob/master/LICENSE)
5 | [](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 | 
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 | ```
--------------------------------------------------------------------------------