├── .gitignore ├── README.md ├── SUMMARY.md ├── TODO.md ├── advanced_algorithm ├── backtrack.md ├── binary_search_tree.md ├── bst_bfs.py ├── recursion.md └── slide_window.md ├── basic_algorithm ├── binary_search.md ├── dp.md ├── graph │ ├── README.md │ ├── bfs_dfs.md │ ├── graph_representation.md │ ├── mst.md │ ├── shortest_path.md │ └── topological_sorting.md ├── heapsort.py ├── mergesort.py ├── quicksort.py └── sort.md ├── data_structure ├── binary_op.md ├── binary_tree.md ├── heap.md ├── linked_list.md ├── stack_queue.md └── union_find.md ├── images ├── backtrack.png ├── binary_search_template.png ├── cycled_linked_list.png ├── dp_dc.png ├── dp_memory_search.png ├── dp_triangle.png ├── fast_slow_linked_list.png ├── heap.png ├── leetcode_explore.png ├── leetcode_jzoffer.png ├── leetcode_record.png ├── leetcode_time.png ├── repo_practice.png ├── stack.png ├── stack_rain.png ├── stack_rain2.png ├── title.png └── tree_type.png ├── introduction ├── golang.md ├── python.md └── quickstart.md ├── practice_algorithm ├── bplus.md ├── data_index.md └── skiplist.md └── src ├── main.go └── sort ├── heap_sort.go └── heap_sort_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 说明 2 | 3 | 本项目为原项目 [algorithm-pattern](https://github.com/greyireland/algorithm-pattern) 的 Python3 语言实现版本,原项目使用 go 语言实现,目前已获 ![GitHub stars](https://img.shields.io/github/stars/greyireland/algorithm-pattern?style=social)。在原项目基础上,本项目添加了优先级队列,并查集,图相关算法等内容,基本覆盖了所有基础数据结构和算法,非常适合找工刷题的同学快速上手。以下为原项目 README,目录部分增加了本项目的新内容。 4 | 5 | # 算法模板 6 | 7 | 算法模板,最科学的刷题方式,最快速的刷题路径,一个月从入门到 offer,你值得拥有 🐶~ 8 | 9 | 算法模板顾名思义就是刷题的套路模板,掌握了刷题模板之后,刷题也变得好玩起来了~ 10 | 11 | > 此项目是自己找工作时,从 0 开始刷 LeetCode 的心得记录,通过各种刷题文章、专栏、视频等总结了一套自己的刷题模板。 12 | > 13 | > 这个模板主要是介绍了一些通用的刷题模板,以及一些常见问题,如到底要刷多少题,按什么顺序来刷题,如何提高刷题效率等。 14 | 15 | ## 在线文档 16 | 17 | 在线文档 Gitbook:[算法模板 🔥](https://greyireland.gitbook.io/algorithm-pattern/) 18 | 19 | ## 核心内容 20 | 21 | ### 入门篇 🐶 22 | 23 | - [使用 Python3 写算法题](./introduction/python.md) 24 | - [算法快速入门](./introduction/quickstart.md) 25 | 26 | ### 数据结构篇 🐰 27 | 28 | - [二叉树](./data_structure/binary_tree.md) 29 | - [链表](./data_structure/linked_list.md) 30 | - [栈和队列](./data_structure/stack_queue.md) 31 | - [优先级队列 (堆)](./data_structure/heap.md) 32 | - [并查集](./data_structure/union_find.md) 33 | - [二进制](./data_structure/binary_op.md) 34 | 35 | ### 基础算法篇 🐮 36 | 37 | - [二分搜索](./basic_algorithm/binary_search.md) 38 | - [排序算法](./basic_algorithm/sort.md) 39 | - [动态规划](./basic_algorithm/dp.md) 40 | - [图相关算法](./basic_algorithm/graph/) 41 | 42 | ### 算法思维 🦁 43 | 44 | - [递归思维](./advanced_algorithm/recursion.md) 45 | - [滑动窗口思想](./advanced_algorithm/slide_window.md) 46 | - [二叉搜索树](./advanced_algorithm/binary_search_tree.md) 47 | - [回溯法](./advanced_algorithm/backtrack.md) 48 | 49 | ## 心得体会 50 | 51 | 文章大部分是对题目的思路介绍,和一些问题的解析,有了思路还是需要自己手动写写的,所以每篇文章最后都有对应的练习题 52 | 53 | 刷完这些练习题,基本对数据结构和算法有自己的认识体会,基本大部分面试题都能写得出来,国内的 BAT、TMD 应该都不是问题 54 | 55 | 从 4 月份找工作开始,从 0 开始刷 LeetCode,中间大概花了一个半月(6 周)左右时间刷完 240 题。 56 | 57 | ![一个半月刷完240题](https://img.fuiboom.com/img/leetcode_time.png) 58 | 59 | ![刷题记录](https://img.fuiboom.com/img/leetcode_record.png) 60 | 61 | 开始刷题时,确实是无从下手,因为从序号开始刷,刷到几道题就遇到 hard 的题型,会卡住很久,后面去评论区看别人怎么刷题,也去 Google 搜索最好的刷题方式,发现按题型刷题会舒服很多,基本一个类型的题目,一天能做很多,慢慢刷题也不再枯燥,做起来也很有意思,最后也收到不错的 offer(最后去了宇宙系)。 62 | 63 | 回到最开始的问题,面试到底要刷多少题,其实这个取决于你想进什么样公司,你定的目标如果是国内一线大厂,个人感觉大概 200 至 300 题基本就满足大部分面试需要了。第二个问题是按什么顺序刷及如何提高效率,这个也是本 repo 的目的,给你指定了一个刷题的顺序,以及刷题的模板,有了方向和技巧后,就去动手吧~ 希望刷完之后,你也能自己总结一套属于自己的刷题模板,有所收获,有所成长~ 64 | 65 | ## 推荐的刷题路径 66 | 67 | 按此 repo 目录刷一遍,如果中间有题目卡住了先跳过,然后刷题一遍 LeetCode 探索基础卡片,最后快要面试时刷题一遍剑指 offer。 68 | 69 | 为什么这么要这么刷,因为 repo 里面的题目是按类型归类,都是一些常见的高频题,很有代表性,大部分都是可以用模板加一点变形做出来,刷完后对大部分题目有基本的认识。然后刷一遍探索卡片,巩固一下一些基础知识点,总结这些知识点。最后剑指 offer 是大部分公司的出题源头,刷完面试中基本会遇到现题或者变形题,基本刷完这三部分,大部分国内公司的面试题应该就没什么问题了~ 70 | 71 | 1、 [algorithm-pattern 练习题](https://greyireland.gitbook.io/algorithm-pattern/) 72 | 73 | ![练习题](https://img.fuiboom.com/img/repo_practice.png) 74 | 75 | 2、 [LeetCode 卡片](https://leetcode-cn.com/explore/) 76 | 77 | ![探索卡片](https://img.fuiboom.com/img/leetcode_explore.png) 78 | 79 | 3、 [剑指 offer](https://leetcode-cn.com/problemset/lcof/) 80 | 81 | ![剑指offer](https://img.fuiboom.com/img/leetcode_jzoffer.png) 82 | 83 | 刷题时间可以合理分配,如果打算准备面试了,建议前面两部分 一个半月 (6 周)时间刷完,最后剑指 offer 半个月刷完,边刷可以边投简历进行面试,遇到不会的不用着急,往模板上套就对了,如果面试官给你提示,那就好好做,不要错过这大好机会~ 84 | 85 | > 注意点:如果为了找工作刷题,遇到 hard 的题如果有思路就做,没思路先跳过,先把基础打好,再来刷 hard 可能效果会更好~ 86 | 87 | ## 面试资源 88 | 89 | 分享一些计算机的经典书籍,大部分对面试应该都有帮助,强烈推荐 🌝 90 | 91 | [我看过的 100 本书](https://github.com/greyireland/awesome-programming-books-1) 92 | 93 | ## 后续 94 | 95 | 持续更新中,觉得还可以的话点个 **star** 收藏呀 ⭐️~ 96 | 97 | 【 Github 】[https://github.com/greyireland/algorithm-pattern](https://github.com/greyireland/algorithm-pattern) ⭐️ 98 | -------------------------------------------------------------------------------- /SUMMARY.md: -------------------------------------------------------------------------------- 1 | # 算法模板 2 | 3 | ## 入门篇 4 | 5 | - [go 语言入门](introduction/golang.md) 6 | - [算法快速入门](introduction/quickstart.md) 7 | 8 | ## 数据结构篇 9 | 10 | - [二叉树](data_structure/binary_tree.md) 11 | - [链表](data_structure/linked_list.md) 12 | - [栈和队列](data_structure/stack_queue.md) 13 | - [二进制](data_structure/binary_op.md) 14 | 15 | ## 基础算法篇 16 | 17 | - [二分搜索](basic_algorithm/binary_search.md) 18 | - [排序算法](basic_algorithm/sort.md) 19 | - [动态规划](basic_algorithm/dp.md) 20 | 21 | ## 算法思维 22 | 23 | - [递归思维](advanced_algorithm/recursion.md) 24 | - [滑动窗口思想](advanced_algorithm/slide_window.md) 25 | - [二叉搜索树](advanced_algorithm/binary_search_tree.md) 26 | - [回溯法](advanced_algorithm/backtrack.md) 27 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | # 计划 2 | 3 | ## v1 4 | 5 | - [ ] 完善文档细节 6 | - [ ] 工程实现用到的算法解析 7 | - [ ] 周赛计划 8 | - [ ] 面试体系计划 9 | -------------------------------------------------------------------------------- /advanced_algorithm/backtrack.md: -------------------------------------------------------------------------------- 1 | # 回溯法 2 | 3 | ## 背景 4 | 5 | 回溯法(backtrack)常用于遍历列表所有子集,是 DFS 深度搜索一种,一般用于全排列,穷尽所有可能,遍历的过程实际上是一个决策树的遍历过程。时间复杂度一般 O(N!),它不像动态规划存在重叠子问题可以优化,回溯算法就是纯暴力穷举,复杂度一般都很高。 6 | 7 | ## 模板 8 | 9 | ```go 10 | result = [] 11 | func backtrack(选择列表,路径): 12 | if 满足结束条件: 13 | result.add(路径) 14 | return 15 | for 选择 in 选择列表: 16 | 做选择 17 | backtrack(选择列表,路径) 18 | 撤销选择 19 | ``` 20 | 21 | 核心就是从选择列表里做一个选择,然后一直递归往下搜索答案,如果遇到路径不通,就返回来撤销这次选择。 22 | 23 | ## 示例 24 | 25 | ### [subsets](https://leetcode-cn.com/problems/subsets/) 26 | 27 | > 给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。 28 | 29 | 遍历过程 30 | 31 | ![image.png](https://img.fuiboom.com/img/backtrack.png) 32 | 33 | ```Python 34 | class Solution: 35 | def subsets(self, nums: List[int]) -> List[List[int]]: 36 | 37 | n = len(nums) 38 | result = [] 39 | 40 | def backtrack(start, k, route=[]): 41 | if len(route) == k: 42 | result.append(route.copy()) 43 | return 44 | 45 | for i in range(start, n): 46 | route.append(nums[i]) 47 | backtrack(i + 1, k) 48 | route.pop() 49 | 50 | return 51 | 52 | for k in range(n + 1): 53 | backtrack(0, k) 54 | 55 | return result 56 | ``` 57 | 58 | ### [subsets-ii](https://leetcode-cn.com/problems/subsets-ii/) 59 | 60 | > 给定一个可能包含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。说明:解集不能包含重复的子集。 61 | 62 | ```Python 63 | class Solution: 64 | def subsetsWithDup(self, nums: List[int]) -> List[List[int]]: 65 | 66 | nums = sorted(nums) 67 | n = len(nums) 68 | result = [] 69 | 70 | def backtrack(start, k, route=[]): 71 | 72 | if len(route) == k: 73 | result.append(route.copy()) 74 | return 75 | 76 | last = None 77 | for i in range(start, n): 78 | if nums[i] != last: 79 | route.append(nums[i]) 80 | backtrack(i + 1, k) 81 | last = route.pop() 82 | 83 | return 84 | 85 | for k in range(n + 1): 86 | backtrack(0, k) 87 | 88 | return result 89 | ``` 90 | 91 | ### [permutations](https://leetcode-cn.com/problems/permutations/) 92 | 93 | > 给定一个没有重复数字的序列,返回其所有可能的全排列。 94 | 95 | - 思路 1:需要记录已经选择过的元素,满足条件的结果才进行返回,需要额外 O(n) 的空间 96 | 97 | ```Python 98 | class Solution: 99 | def permute(self, nums: List[int]) -> List[List[int]]: 100 | 101 | n = len(nums) 102 | result = [] 103 | 104 | in_route = [False] * n 105 | 106 | def backtrack(route=[]): 107 | 108 | if len(route) == n: 109 | result.append(route.copy()) 110 | return 111 | 112 | for i in range(n): 113 | if not in_route[i]: 114 | route.append(nums[i]) 115 | in_route[i] = True 116 | backtrack() 117 | route.pop() 118 | in_route[i] = False 119 | 120 | return 121 | 122 | backtrack() 123 | return result 124 | ``` 125 | 126 | - 思路 2: 针对此题的更高级的回溯,利用原有的数组,每次回溯将新选择的元素与当前位置元素交换,回溯完成再换回来 127 | 128 | ```Python 129 | class Solution: 130 | def permute(self, nums: List[int]) -> List[List[int]]: 131 | 132 | n = len(nums) 133 | result = [] 134 | 135 | def backtrack(idx=0): 136 | if idx == n: 137 | result.append(nums.copy()) 138 | for i in range(idx, n): 139 | nums[idx], nums[i] = nums[i], nums[idx] 140 | backtrack(idx + 1) 141 | nums[idx], nums[i] = nums[i], nums[idx] 142 | return 143 | 144 | backtrack() 145 | return result 146 | ``` 147 | 148 | ### [permutations-ii](https://leetcode-cn.com/problems/permutations-ii/) 149 | 150 | > 给定一个可包含重复数字的序列,返回所有不重复的全排列。 151 | 152 | 注意此题(貌似)无法使用上题的思路 2,因为交换操作会打乱排序。 153 | 154 | ```Python 155 | class Solution: 156 | def permuteUnique(self, nums: List[int]) -> List[List[int]]: 157 | 158 | nums = sorted(nums) 159 | n = len(nums) 160 | result = [] 161 | 162 | in_route = [False] * n 163 | 164 | def backtrack(route=[]): 165 | 166 | if len(route) == n: 167 | result.append(route.copy()) 168 | return 169 | 170 | last = None 171 | for i in range(n): 172 | if not in_route[i] and nums[i] != last: 173 | route.append(nums[i]) 174 | in_route[i] = True 175 | backtrack() 176 | last = route.pop() 177 | in_route[i] = False 178 | 179 | return 180 | 181 | backtrack() 182 | return result 183 | ``` 184 | 185 | ### [combination-sum](https://leetcode-cn.com/problems/combination-sum/) 186 | 187 | ```Python 188 | class Solution: 189 | def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]: 190 | 191 | n = len(candidates) 192 | result = [] 193 | 194 | def backtrack(first=0, route=[], route_sum=0): 195 | 196 | if route_sum == target: 197 | result.append(route.copy()) 198 | return 199 | 200 | if route_sum > target: 201 | return 202 | 203 | for i in range(first, n): 204 | route.append(candidates[i]) 205 | route_sum += candidates[i] 206 | backtrack(i, route, route_sum) 207 | route_sum -= route.pop() 208 | 209 | return 210 | 211 | backtrack() 212 | return result 213 | ``` 214 | 215 | ### [letter-combinations-of-a-phone-number](https://leetcode-cn.com/problems/letter-combinations-of-a-phone-number/) 216 | 217 | ```Python 218 | class Solution: 219 | def letterCombinations(self, digits: str) -> List[str]: 220 | 221 | n = len(digits) 222 | result = [] 223 | 224 | if n == 0: 225 | return result 226 | 227 | num2char = { 228 | '2': ['a', 'b', 'c'], 229 | '3': ['d', 'e', 'f'], 230 | '4': ['g', 'h', 'i'], 231 | '5': ['j', 'k', 'l'], 232 | '6': ['m', 'n', 'o'], 233 | '7': ['p', 'q', 'r', 's'], 234 | '8': ['t', 'u', 'v'], 235 | '9': ['w', 'x', 'y', 'z'] 236 | } 237 | 238 | def backtrack(idx=0, route=[]): 239 | if idx == n: 240 | result.append(''.join(route)) 241 | return 242 | 243 | for c in num2char[digits[idx]]: 244 | route.append(c) 245 | backtrack(idx + 1, route) 246 | route.pop() 247 | 248 | return 249 | 250 | backtrack() 251 | return result 252 | ``` 253 | 254 | ### [palindrome-partitioning](https://leetcode-cn.com/problems/palindrome-partitioning/) 255 | 256 | ```Python 257 | class Solution: 258 | def partition(self, s: str) -> List[List[str]]: 259 | 260 | N = len(s) 261 | Pal = collections.defaultdict(set) 262 | 263 | def isPal(i, j): 264 | if i < j: 265 | return j in Pal[i] 266 | return True 267 | 268 | for j in range(N): 269 | for i in range(j + 1): 270 | if s[i] == s[j] and isPal(i + 1, j - 1): 271 | Pal[i].add(j) 272 | 273 | result = [] 274 | 275 | def backtrack(first=0, route=[]): 276 | 277 | if first == N: 278 | result.append(route[:]) 279 | return 280 | 281 | for i in Pal[first]: 282 | route.append(s[first:i+1]) 283 | backtrack(i + 1) 284 | route.pop() 285 | 286 | return 287 | 288 | backtrack() 289 | return result 290 | ``` 291 | 292 | ### [restore-ip-addresses](https://leetcode-cn.com/problems/restore-ip-addresses/) 293 | 294 | ```Python 295 | class Solution: 296 | def restoreIpAddresses(self, s: str) -> List[str]: 297 | 298 | n = len(s) 299 | result = [] 300 | 301 | if n > 12: 302 | return result 303 | 304 | def Valid_s(i, j): 305 | return i < j and j <= n and ((s[i] != '0' and int(s[i:j]) < 256) or (s[i] == '0' and i == j - 1)) 306 | 307 | def backtrack(start=0, route=[]): 308 | 309 | if len(route) == 3: 310 | if Valid_s(start, n): 311 | result.append('.'.join(route) + '.' + s[start:]) 312 | return 313 | 314 | for i in range(start, start + 3): 315 | if Valid_s(start, i + 1): 316 | route.append(s[start:i + 1]) 317 | backtrack(i + 1, route) 318 | route.pop() 319 | 320 | return 321 | 322 | backtrack() 323 | return result 324 | ``` 325 | 326 | 327 | 328 | ## 练习 329 | 330 | - [ ] [subsets](https://leetcode-cn.com/problems/subsets/) 331 | - [ ] [subsets-ii](https://leetcode-cn.com/problems/subsets-ii/) 332 | - [ ] [permutations](https://leetcode-cn.com/problems/permutations/) 333 | - [ ] [permutations-ii](https://leetcode-cn.com/problems/permutations-ii/) 334 | 335 | - [ ] [combination-sum](https://leetcode-cn.com/problems/combination-sum/) 336 | - [ ] [letter-combinations-of-a-phone-number](https://leetcode-cn.com/problems/letter-combinations-of-a-phone-number/) 337 | - [ ] [palindrome-partitioning](https://leetcode-cn.com/problems/palindrome-partitioning/) 338 | - [ ] [restore-ip-addresses](https://leetcode-cn.com/problems/restore-ip-addresses/) 339 | -------------------------------------------------------------------------------- /advanced_algorithm/binary_search_tree.md: -------------------------------------------------------------------------------- 1 | # 二叉搜索树 2 | 3 | ## 定义 4 | 5 | - 每个节点中的值必须大于(或等于)存储在其左侧子树中的任何值。 6 | - 每个节点中的值必须小于(或等于)存储在其右子树中的任何值。 7 | 8 | ## 应用 9 | 10 | ### [validate-binary-search-tree](https://leetcode-cn.com/problems/validate-binary-search-tree/) 11 | 12 | > 验证二叉搜索树 13 | 14 | ```Python 15 | class Solution: 16 | def isValidBST(self, root: TreeNode) -> bool: 17 | 18 | if root is None: 19 | return True 20 | 21 | s = [(root, float('-inf'), float('inf'))] 22 | while len(s) > 0: 23 | node, low, up = s.pop() 24 | if node.left is not None: 25 | if node.left.val <= low or node.left.val >= node.val: 26 | return False 27 | s.append((node.left, low, node.val)) 28 | if node.right is not None: 29 | if node.right.val <= node.val or node.right.val >= up: 30 | return False 31 | s.append((node.right, node.val, up)) 32 | return True 33 | ``` 34 | 35 | ### [insert-into-a-binary-search-tree](https://leetcode-cn.com/problems/insert-into-a-binary-search-tree/) 36 | 37 | > 给定二叉搜索树(BST)的根节点和要插入树中的值,将值插入二叉搜索树。 返回插入后二叉搜索树的根节点。 保证原始二叉搜索树中不存在新值。 38 | 39 | ```Python 40 | class Solution: 41 | def insertIntoBST(self, root: TreeNode, val: int) -> TreeNode: 42 | 43 | if root is None: 44 | return TreeNode(val) 45 | 46 | if val > root.val: 47 | root.right = self.insertIntoBST(root.right, val) 48 | else: 49 | root.left = self.insertIntoBST(root.left, val) 50 | 51 | return root 52 | ``` 53 | 54 | ### [delete-node-in-a-bst](https://leetcode-cn.com/problems/delete-node-in-a-bst/) 55 | 56 | > 给定一个二叉搜索树的根节点 root 和一个值 key,删除二叉搜索树中的  key  对应的节点,并保证二叉搜索树的性质不变。返回二叉搜索树(有可能被更新)的根节点的引用。 57 | 58 | ```Python 59 | class Solution: 60 | def deleteNode(self, root: TreeNode, key: int) -> TreeNode: 61 | 62 | # try to find the node 63 | dummy = TreeNode(left=root) 64 | parent, node = dummy, root 65 | isleft = True 66 | while node is not None and node.val != key: 67 | parent = node 68 | isleft = key < node.val 69 | node = node.left if isleft else node.right 70 | 71 | # if found 72 | if node is not None: 73 | if node.right is None: 74 | if isleft: 75 | parent.left = node.left 76 | else: 77 | parent.right = node.left 78 | elif node.left is None: 79 | if isleft: 80 | parent.left = node.right 81 | else: 82 | parent.right = node.right 83 | else: 84 | p, n = node, node.left 85 | while n.right is not None: 86 | p, n = n, n.right 87 | if p != node: 88 | p.right = n.left 89 | else: 90 | p.left = n.left 91 | n.left, n.right = node.left, node.right 92 | if isleft: 93 | parent.left = n 94 | else: 95 | parent.right = n 96 | 97 | return dummy.left 98 | ``` 99 | 100 | ### [balanced-binary-tree](https://leetcode-cn.com/problems/balanced-binary-tree/) 101 | 102 | > 给定一个二叉树,判断它是否是高度平衡的二叉树。 103 | 104 | ```Python 105 | class Solution: 106 | def isBalanced(self, root: TreeNode) -> bool: 107 | 108 | # post-order iterative 109 | 110 | s = [[TreeNode(), -1, -1]] 111 | node, last = root, None 112 | while len(s) > 1 or node is not None: 113 | if node is not None: 114 | s.append([node, -1, -1]) 115 | node = node.left 116 | if node is None: 117 | s[-1][1] = 0 118 | else: 119 | peek = s[-1][0] 120 | if peek.right is not None and last != peek.right: 121 | node = peek.right 122 | else: 123 | if peek.right is None: 124 | s[-1][2] = 0 125 | last, dl, dr = s.pop() 126 | if abs(dl - dr) > 1: 127 | return False 128 | d = max(dl, dr) + 1 129 | if s[-1][1] == -1: 130 | s[-1][1] = d 131 | else: 132 | s[-1][2] = d 133 | 134 | return True 135 | ``` 136 | 137 | ### [valid-bfs-of-bst](./bst_bfs.py) 138 | 139 | > 给定一个整数数组,求问此数组是不是一个 BST 的 BFS 顺序。 140 | 141 | 此题是面试真题,但是没有在 leetcode 上找到原题。由于做法比较有趣也很有 BST 的特点,补充在这供参考。 142 | 143 | ```Python 144 | import collections 145 | 146 | def bst_bfs(A): 147 | 148 | N = len(A) 149 | interval = collections.deque([(float('-inf'), A[0]), (A[0], float('inf'))]) 150 | 151 | for i in range(1, N): 152 | while interval: 153 | lower, upper = interval.popleft() 154 | if lower < A[i] < upper: 155 | interval.append((lower, A[i])) 156 | interval.append((A[i], upper)) 157 | break 158 | 159 | if not interval: 160 | return False 161 | 162 | return True 163 | 164 | if __name__ == "__main__": 165 | A = [10, 8, 11, 1, 9, 0, 5, 3, 6, 4, 12] 166 | print(bst_bfs(A)) 167 | A = [10, 8, 11, 1, 9, 0, 5, 3, 6, 4, 7] 168 | print(bst_bfs(A)) 169 | ``` 170 | 171 | ## 练习 172 | 173 | - [ ] [validate-binary-search-tree](https://leetcode-cn.com/problems/validate-binary-search-tree/) 174 | - [ ] [insert-into-a-binary-search-tree](https://leetcode-cn.com/problems/insert-into-a-binary-search-tree/) 175 | - [ ] [delete-node-in-a-bst](https://leetcode-cn.com/problems/delete-node-in-a-bst/) 176 | - [ ] [balanced-binary-tree](https://leetcode-cn.com/problems/balanced-binary-tree/) 177 | -------------------------------------------------------------------------------- /advanced_algorithm/bst_bfs.py: -------------------------------------------------------------------------------- 1 | import collections 2 | 3 | def bst_bfs(A): 4 | 5 | N = len(A) 6 | interval = collections.deque([(float('-inf'), A[0]), (A[0], float('inf'))]) 7 | 8 | for i in range(1, N): 9 | while interval: 10 | lower, upper = interval.popleft() 11 | if lower < A[i] < upper: 12 | interval.append((lower, A[i])) 13 | interval.append((A[i], upper)) 14 | break 15 | 16 | if not interval: 17 | return False 18 | 19 | return True 20 | 21 | if __name__ == "__main__": 22 | A = [10, 8, 11, 1, 9, 0, 5, 3, 6, 4, 12] 23 | print(bst_bfs(A)) 24 | A = [10, 8, 11, 1, 9, 0, 5, 3, 6, 4, 7] 25 | print(bst_bfs(A)) -------------------------------------------------------------------------------- /advanced_algorithm/recursion.md: -------------------------------------------------------------------------------- 1 | # 递归 2 | 3 | ## 介绍 4 | 5 | 将大问题转化为小问题,通过递归依次解决各个小问题 6 | 7 | ## 示例 8 | 9 | ### [reverse-string](https://leetcode-cn.com/problems/reverse-string/) 10 | 11 | > 编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组  `char[]`  的形式给出。 12 | 13 | ```Python 14 | class Solution: 15 | def reverseString(self, s: List[str]) -> None: 16 | """ 17 | Do not return anything, modify s in-place instead. 18 | """ 19 | def rev_rec(s, i, j): 20 | if i >= j: 21 | return 22 | s[i], s[j] = s[j], s[i] 23 | rev_rec(s, i + 1, j - 1) 24 | return 25 | 26 | rev_rec(s, 0, len(s) - 1) 27 | 28 | return 29 | ``` 30 | 31 | ### [swap-nodes-in-pairs](https://leetcode-cn.com/problems/swap-nodes-in-pairs/) 32 | 33 | > 给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。 34 | > **你不能只是单纯的改变节点内部的值**,而是需要实际的进行节点交换。 35 | 36 | ```Python 37 | class Solution: 38 | def swapPairs(self, head: ListNode) -> ListNode: 39 | 40 | if head is not None and head.next is not None: 41 | head_next_pair = self.swapPairs(head.next.next) 42 | p = head.next 43 | head.next = head_next_pair 44 | p.next = head 45 | head = p 46 | 47 | return head 48 | ``` 49 | 50 | ### [unique-binary-search-trees-ii](https://leetcode-cn.com/problems/unique-binary-search-trees-ii/) 51 | 52 | > 给定一个整数 n,生成所有由 1 ... n 为节点所组成的二叉搜索树。 53 | 54 | 注意:此题用来训练递归思维有理论意义,但是实际上算法返回的树并不是 deep copy,多个树之间会共享子树。 55 | 56 | ```Python 57 | class Solution: 58 | def generateTrees(self, n: int) -> List[TreeNode]: 59 | 60 | def generateTrees_rec(i, j): 61 | 62 | if i > j: 63 | return [None] 64 | 65 | result = [] 66 | for m in range(i, j + 1): 67 | left = generateTrees_rec(i, m - 1) 68 | right = generateTrees_rec(m + 1, j) 69 | 70 | for l in left: 71 | for r in right: 72 | result.append(TreeNode(m, l, r)) 73 | 74 | return result 75 | 76 | return generateTrees_rec(1, n) if n > 0 else [] 77 | ``` 78 | 79 | ## 递归 + 备忘录 (recursion with memorization, top-down DP) 80 | 81 | ### [fibonacci-number](https://leetcode-cn.com/problems/fibonacci-number/) 82 | 83 | > 斐波那契数,通常用  F(n) 表示,形成的序列称为斐波那契数列。该数列由  0 和 1 开始,后面的每一项数字都是前面两项数字的和。也就是: 84 | > F(0) = 0,   F(1) = 1 85 | > F(N) = F(N - 1) + F(N - 2), 其中 N > 1. 86 | > 给定  N,计算  F(N)。 87 | 88 | ```Python 89 | class Solution: 90 | def fib(self, N: int) -> int: 91 | 92 | mem = [-1] * (N + 2) 93 | 94 | mem[0], mem[1] = 0, 1 95 | 96 | def fib_rec(n): 97 | if mem[n] == -1: 98 | mem[n] = fib_rec(n - 1) + fib_rec(n - 2) 99 | return mem[n] 100 | 101 | return fib_rec(N) 102 | ``` 103 | 104 | ## 练习 105 | 106 | - [ ] [reverse-string](https://leetcode-cn.com/problems/reverse-string/) 107 | - [ ] [swap-nodes-in-pairs](https://leetcode-cn.com/problems/swap-nodes-in-pairs/) 108 | - [ ] [unique-binary-search-trees-ii](https://leetcode-cn.com/problems/unique-binary-search-trees-ii/) 109 | - [ ] [fibonacci-number](https://leetcode-cn.com/problems/fibonacci-number/) 110 | 111 | -------------------------------------------------------------------------------- /advanced_algorithm/slide_window.md: -------------------------------------------------------------------------------- 1 | # 滑动窗口 2 | 3 | ## 模板 4 | 5 | ```cpp 6 | /* 滑动窗口算法框架 */ 7 | void slidingWindow(string s, string t) { 8 | unordered_map need, window; 9 | for (char c : t) need[c]++; 10 | 11 | int left = 0, right = 0; 12 | int valid = 0; 13 | while (right < s.size()) { 14 | // c 是将移入窗口的字符 15 | char c = s[right]; 16 | // 右移窗口 17 | right++; 18 | // 进行窗口内数据的一系列更新 19 | ... 20 | 21 | /*** debug 输出的位置 ***/ 22 | printf("window: [%d, %d)\n", left, right); 23 | /********************/ 24 | 25 | // 判断左侧窗口是否要收缩 26 | while (window needs shrink) { 27 | // d 是将移出窗口的字符 28 | char d = s[left]; 29 | // 左移窗口 30 | left++; 31 | // 进行窗口内数据的一系列更新 32 | ... 33 | } 34 | } 35 | } 36 | ``` 37 | 38 | 需要变化的地方 39 | 40 | - 1、右指针右移之后窗口数据更新 41 | - 2、判断窗口是否要收缩 42 | - 3、左指针右移之后窗口数据更新 43 | - 4、根据题意计算结果 44 | 45 | ## 示例 46 | 47 | ### [minimum-window-substring](https://leetcode-cn.com/problems/minimum-window-substring/) 48 | 49 | > 给你一个字符串 S、一个字符串 T,请在字符串 S 里面找出:包含 T 所有字母的最小子串 50 | 51 | ```Python 52 | class Solution: 53 | def minWindow(self, s: str, t: str) -> str: 54 | 55 | target = collections.defaultdict(int) 56 | window = collections.defaultdict(int) 57 | 58 | for c in t: 59 | target[c] += 1 60 | 61 | min_size = len(s) + 1 62 | min_str = '' 63 | 64 | l, r, count, num_char = 0, 0, 0, len(target) 65 | 66 | while r < len(s): 67 | c = s[r] 68 | r += 1 69 | 70 | if c in target: 71 | window[c] += 1 72 | 73 | if window[c] == target[c]: 74 | count += 1 75 | 76 | if count == num_char: 77 | while l < r and count == num_char: 78 | c = s[l] 79 | l += 1 80 | 81 | if c in target: 82 | window[c] -= 1 83 | 84 | if window[c] == target[c] - 1: 85 | count -= 1 86 | 87 | if min_size > r - l + 1: 88 | min_size = r - l + 1 89 | min_str = s[l - 1:r] 90 | 91 | return min_str 92 | ``` 93 | 94 | ### [permutation-in-string](https://leetcode-cn.com/problems/permutation-in-string/) 95 | 96 | > 给定两个字符串  **s1**  和  **s2**,写一个函数来判断  **s2**  是否包含  **s1 **的排列。 97 | 98 | ```Python 99 | class Solution: 100 | def checkInclusion(self, s1: str, s2: str) -> bool: 101 | 102 | target = collections.defaultdict(int) 103 | 104 | for c in s1: 105 | target[c] += 1 106 | 107 | r, num_char = 0, len(target) 108 | 109 | while r < len(s2): 110 | if s2[r] in target: 111 | l, count = r, 0 112 | window = collections.defaultdict(int) 113 | while r < len(s2): 114 | c = s2[r] 115 | if c not in target: 116 | break 117 | window[c] += 1 118 | if window[c] == target[c]: 119 | count += 1 120 | if count == num_char: 121 | return True 122 | while window[c] > target[c]: 123 | window[s2[l]] -= 1 124 | if window[s2[l]] == target[s2[l]] - 1: 125 | count -= 1 126 | l += 1 127 | r += 1 128 | else: 129 | r += 1 130 | 131 | return False 132 | ``` 133 | 134 | ### [find-all-anagrams-in-a-string](https://leetcode-cn.com/problems/find-all-anagrams-in-a-string/) 135 | 136 | > 给定一个字符串  **s **和一个非空字符串  **p**,找到  **s **中所有是  **p **的字母异位词的子串,返回这些子串的起始索引。 137 | 138 | ```Python 139 | class Solution: 140 | def findAnagrams(self, s: str, p: str) -> List[int]: 141 | 142 | target = collections.defaultdict(int) 143 | 144 | for c in p: 145 | target[c] += 1 146 | 147 | r, num_char = 0, len(target) 148 | 149 | results = [] 150 | while r < len(s): 151 | if s[r] in target: 152 | l, count = r, 0 153 | window = collections.defaultdict(int) 154 | while r < len(s): 155 | c = s[r] 156 | if c not in target: 157 | break 158 | window[c] += 1 159 | if window[c] == target[c]: 160 | count += 1 161 | if count == num_char: 162 | results.append(l) 163 | window[s[l]] -= 1 164 | count -= 1 165 | l += 1 166 | while window[c] > target[c]: 167 | window[s[l]] -= 1 168 | if window[s[l]] == target[s[l]] - 1: 169 | count -= 1 170 | l += 1 171 | r += 1 172 | else: 173 | r += 1 174 | 175 | return results 176 | ``` 177 | 178 | ### [longest-substring-without-repeating-characters](https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/) 179 | 180 | > 给定一个字符串,请你找出其中不含有重复字符的   最长子串   的长度。 181 | > 示例  1: 182 | > 183 | > 输入: "abcabcbb" 184 | > 输出: 3 185 | > 解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。 186 | 187 | ```Python 188 | class Solution: 189 | def lengthOfLongestSubstring(self, s: str) -> int: 190 | 191 | last_idx = {} 192 | 193 | l, max_length = 0, 0 194 | for r, c in enumerate(s): 195 | if c in last_idx and last_idx[c] >= l: 196 | max_length = max(max_length, r - l) 197 | l = last_idx[c] + 1 198 | last_idx[c] = r 199 | 200 | return max(max_length, len(s) - l) # note that the last substring is not judged in the loop 201 | ``` 202 | 203 | ## 总结 204 | 205 | - 和双指针题目类似,更像双指针的升级版,滑动窗口核心点是维护一个窗口集,根据窗口集来进行处理 206 | - 核心步骤 207 | - right 右移 208 | - 收缩 209 | - left 右移 210 | - 求结果 211 | 212 | ## 练习 213 | 214 | - [ ] [minimum-window-substring](https://leetcode-cn.com/problems/minimum-window-substring/) 215 | - [ ] [permutation-in-string](https://leetcode-cn.com/problems/permutation-in-string/) 216 | - [ ] [find-all-anagrams-in-a-string](https://leetcode-cn.com/problems/find-all-anagrams-in-a-string/) 217 | - [ ] [longest-substring-without-repeating-characters](https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/) 218 | -------------------------------------------------------------------------------- /basic_algorithm/binary_search.md: -------------------------------------------------------------------------------- 1 | # 二分搜索 2 | 3 | 给一个**有序数组**和目标值,找第一次/最后一次/任何一次出现的索引,时间复杂度 O(logN)。 4 | 5 | ## 模板 6 | 7 | 常用的二分搜索模板有如下三种形式: 8 | 9 | ![binary_search_template](https://img.fuiboom.com/img/binary_search_template.png) 10 | 11 | 其中,模板 1 和 3 是最常用的,几乎所有二分查找问题都可以用其中之一轻松实现。模板 2 更高级一些,用于解决某些类型的问题。详细的对比可以参考 Leetcode 上的文章:[二分搜索模板](https://leetcode-cn.com/explore/learn/card/binary-search/212/template-analysis/847/)。 12 | 13 | ### [binary-search](https://leetcode-cn.com/problems/binary-search/) 14 | 15 | > 给定一个  n  个元素有序的(升序)整型数组  nums 和一个目标值  target  ,写一个函数搜索  nums  中的 target,如果目标值存在返回下标,否则返回 -1。 16 | 17 | - 模板 3 的实现 18 | 19 | ```Python 20 | class Solution: 21 | def search(self, nums: List[int], target: int) -> int: 22 | 23 | l, r = 0, len(nums) - 1 24 | 25 | while l + 1 < r: 26 | mid = l + (r - l) // 2 27 | if nums[mid] < target: 28 | l = mid 29 | else: 30 | r = mid 31 | 32 | if nums[l] == target: 33 | return l 34 | elif nums[r] == target: 35 | return r 36 | else: 37 | return -1 38 | ``` 39 | 40 | - 如果是最简单的二分搜索,不需要找第一个、最后一个位置,或者是没有重复元素,可以使用模板 1,代码更简洁。同时,如果搜索失败,left 是第一个大于 target 的索引,right 是最后一个小于 target 的索引。 41 | 42 | ```Python 43 | class Solution: 44 | def search(self, nums: List[int], target: int) -> int: 45 | 46 | l, r = 0, len(nums) - 1 47 | 48 | while l <= r: 49 | mid = l + (r - l) // 2 50 | if nums[mid] == target: 51 | return mid 52 | elif nums[mid] > target: 53 | r = mid - 1 54 | else: 55 | l = mid + 1 56 | 57 | return -1 58 | ``` 59 | 60 | - 模板 2 的实现 61 | 62 | ```Python 63 | class Solution: 64 | def search(self, nums: List[int], target: int) -> int: 65 | 66 | l, r = 0, len(nums) - 1 67 | 68 | while l < r: 69 | mid = l + (r - l) // 2 70 | if nums[mid] < target: 71 | l = mid + 1 72 | else: 73 | r = mid 74 | 75 | if nums[l] == target: 76 | return l 77 | 78 | return -1 79 | ``` 80 | 81 | ## 常见题目 82 | 83 | ### [find-first-and-last-position-of-element-in-sorted-array](https://leetcode-cn.com/problems/find-first-and-last-position-of-element-in-sorted-array/) 84 | 85 | > 给定一个包含 n 个整数的排序数组,找出给定目标值 target 的起始和结束位置。如果目标值不在数组中,则返回`[-1, -1]` 86 | 87 | - 思路:核心点就是找第一个 target 的索引,和最后一个 target 的索引,所以用两次二分搜索分别找第一次和最后一次的位置,下面是使用模板 3 的解法 88 | 89 | ```Python 90 | class Solution: 91 | def searchRange(self, nums, target): 92 | Range = [-1, -1] 93 | if len(nums) == 0: 94 | return Range 95 | 96 | l, r = 0, len(nums) - 1 97 | while l + 1 < r: 98 | mid = l + (r - l) // 2 99 | if nums[mid] < target: 100 | l = mid 101 | else: 102 | r = mid 103 | 104 | if nums[l] == target: 105 | Range[0] = l 106 | elif nums[r] == target: 107 | Range[0] = r 108 | else: 109 | return Range 110 | 111 | l, r = 0, len(nums) - 1 112 | while l + 1 < r: 113 | mid = l + (r - l) // 2 114 | if nums[mid] <= target: 115 | l = mid 116 | else: 117 | r = mid 118 | 119 | if nums[r] == target: 120 | Range[1] = r 121 | else: 122 | Range[1] = l 123 | 124 | return Range 125 | ``` 126 | 127 | - 使用模板 2 的解法 128 | 129 | ```Python 130 | class Solution: 131 | def searchRange(self, nums, target): 132 | Range = [-1, -1] 133 | if len(nums) == 0: 134 | return Range 135 | 136 | l, r = 0, len(nums) - 1 137 | while l < r: 138 | mid = l + (r - l) // 2 139 | if nums[mid] < target: 140 | l = mid + 1 141 | else: 142 | r = mid 143 | 144 | if nums[l] == target: 145 | Range[0] = l 146 | else: 147 | return Range 148 | 149 | l, r = 0, len(nums) - 1 150 | while l < r: 151 | mid = l + (r - l + 1) // 2 152 | if nums[mid] > target: 153 | r = mid - 1 154 | else: 155 | l = mid 156 | 157 | Range[1] = l 158 | return Range 159 | ``` 160 | 161 | ### [search-insert-position](https://leetcode-cn.com/problems/search-insert-position/) 162 | 163 | > 给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。 164 | 165 | - 使用模板 1,若不存在,左边界为第一个大于目标值的索引(插入位置),右边界为最后一个小于目标值的索引 166 | 167 | ```Python 168 | class Solution: 169 | def searchInsert(self, nums: List[int], target: int) -> int: 170 | 171 | l, r = 0, len(nums) - 1 172 | 173 | while l <= r: 174 | mid = l + (r - l) // 2 175 | if nums[mid] == target: 176 | return mid 177 | elif nums[mid] > target: 178 | r = mid - 1 179 | else: 180 | l = mid + 1 181 | 182 | return l 183 | ``` 184 | 185 | ### [search-a-2d-matrix](https://leetcode-cn.com/problems/search-a-2d-matrix/) 186 | 187 | > 编写一个高效的算法来判断  m x n  矩阵中,是否存在一个目标值。该矩阵具有如下特性: 188 | > 189 | > 1. 每行中的整数从左到右按升序排列。 190 | > 191 | > 2. 每行的第一个整数大于前一行的最后一个整数。 192 | 193 | - 两次二分,首先定位行数,接着定位列数 194 | 195 | ```Python 196 | class Solution: 197 | def searchMatrix(self, matrix: List[List[int]], target: int) -> bool: 198 | 199 | if len(matrix) == 0 or len(matrix[0]) == 0: 200 | return False 201 | 202 | l, r = 0, len(matrix) - 1 203 | 204 | while l <= r: 205 | mid = l + (r - l) // 2 206 | if matrix[mid][0] == target: 207 | return True 208 | elif matrix[mid][0] < target: 209 | l = mid + 1 210 | else: 211 | r = mid - 1 212 | 213 | row = r 214 | l, r = 0, len(matrix[0]) - 1 215 | while l <= r: 216 | mid = l + (r - l) // 2 217 | if matrix[row][mid] == target: 218 | return True 219 | elif matrix[row][mid] < target: 220 | l = mid + 1 221 | else: 222 | r = mid - 1 223 | 224 | return False 225 | ``` 226 | 227 | ### [find-minimum-in-rotated-sorted-array](https://leetcode-cn.com/problems/find-minimum-in-rotated-sorted-array/) 228 | 229 | > 假设按照升序排序的数组在预先未知的某个点上进行了旋转,例如,数组 [0, 1, 2, 4, 5, 6, 7] 可能变为 [4, 5, 6, 7, 0, 1, 2]。请找出其中最小的元素。假设数组中无重复元素。 230 | 231 | - 使用二分搜索,当中间元素大于右侧元素时意味着拐点即最小元素在右侧,否则在左侧 232 | 233 | ```Python 234 | class Solution: 235 | def findMin(self, nums: List[int]) -> int: 236 | 237 | l , r = 0, len(nums) - 1 238 | 239 | while l < r: 240 | mid = l + (r - l) // 2 241 | if nums[mid] > nums[r]: # 数组有重复时,若 nums[l] == nums[mid] == nums[r],无法判断移动方向 242 | l = mid + 1 243 | else: 244 | r = mid 245 | 246 | return nums[l] 247 | ``` 248 | 249 | ### [find-minimum-in-rotated-sorted-array-ii](https://leetcode-cn.com/problems/find-minimum-in-rotated-sorted-array-ii/) 250 | 251 | > 假设按照升序排序的数组在预先未知的某个点上进行了旋转,例如,数组 [0, 1, 2, 4, 5, 6, 7] 可能变为 [4, 5, 6, 7, 0, 1, 2]。请找出其中最小的元素。数组中可能包含重复元素。 252 | 253 | ```Python 254 | class Solution: 255 | def findMin(self, nums: List[int]) -> int: 256 | 257 | l , r = 0, len(nums) - 1 258 | 259 | while l < r: 260 | mid = l + (r - l) // 2 261 | if nums[mid] > nums[r]: 262 | l = mid + 1 263 | elif nums[mid] < nums[r] or nums[mid] != nums[l]: 264 | r = mid 265 | else: # nums[l] == nums[mid] == nums[r] 266 | l += 1 267 | 268 | return nums[l] 269 | ``` 270 | 271 | ### [search-in-rotated-sorted-array](https://leetcode-cn.com/problems/search-in-rotated-sorted-array/) 272 | 273 | > 假设按照升序排序的数组在预先未知的某个点上进行了旋转,例如,数组 [0, 1, 2, 4, 5, 6, 7] 可能变为 [4, 5, 6, 7, 0, 1, 2]。搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回  -1。假设数组中不存在重复的元素。 274 | 275 | ```Python 276 | class Solution: 277 | def search(self, nums: List[int], target: int) -> int: 278 | 279 | l , r = 0, len(nums) - 1 280 | 281 | while l <= r: 282 | mid = l + (r - l) // 2 283 | if nums[mid] == target: 284 | return mid 285 | elif nums[mid] > target: 286 | if nums[l] > target and nums[mid] > nums[r]: 287 | l = mid + 1 288 | else: 289 | r = mid - 1 290 | else: 291 | if nums[r] < target and nums[mid] < nums[l]: 292 | r = mid - 1 293 | else: 294 | l = mid + 1 295 | return -1 296 | ``` 297 | 298 | ### [search-in-rotated-sorted-array-ii](https://leetcode-cn.com/problems/search-in-rotated-sorted-array-ii/) 299 | 300 | > 假设按照升序排序的数组在预先未知的某个点上进行了旋转,例如,数组 [0, 0, 1, 2, 2, 5, 6] 可能变为 [2, 5, 6, 0, 0, 1, 2]。编写一个函数来判断给定的目标值是否存在于数组中,若存在返回  true,否则返回  false。数组中可能包含重复元素。 301 | 302 | ```Python 303 | class Solution: 304 | def search(self, nums: List[int], target: int) -> int: 305 | 306 | l , r = 0, len(nums) - 1 307 | 308 | while l <= r: 309 | if nums[l] == nums[r] and nums[l] != target: 310 | l += 1 311 | r -= 1 312 | continue 313 | mid = l + (r - l) // 2 314 | if nums[mid] == target: 315 | return True 316 | elif nums[mid] > target: 317 | if nums[l] > target and nums[mid] > nums[r]: 318 | l = mid + 1 319 | else: 320 | r = mid - 1 321 | else: 322 | if nums[r] < target and nums[mid] < nums[l]: 323 | r = mid - 1 324 | else: 325 | l = mid + 1 326 | return False 327 | ``` 328 | 329 | ## 隐含的二分搜索 330 | 331 | 有时用到二分搜索的题目并不会直接给你一个有序数组,它隐含在题目中,需要你去发现或者构造。一类常见的隐含的二分搜索的问题是求某个有界数据的最值,以最小值为例,当数据比最小值大时都符合条件,比最小值小时都不符合条件,那么符合/不符合条件就构成了一种有序关系,再加上数据有界,我们就可以使用二分搜索来找数据的最小值。注意,数据的界一般也不会在题目中明确提示你,需要你自己去发现。 332 | 333 | ### [koko-eating-bananas](https://leetcode-cn.com/problems/koko-eating-bananas/) 334 | 335 | ```Python 336 | class Solution: 337 | def minEatingSpeed(self, piles: List[int], H: int) -> int: 338 | 339 | l, r = 1, max(piles) 340 | 341 | while l < r: 342 | mid = l + (r - l) // 2 343 | if sum([-pile // mid for pile in piles]) < -H: 344 | l = mid + 1 345 | else: 346 | r = mid 347 | 348 | return l 349 | ``` 350 | 351 | ## 总结 352 | 353 | 二分搜索核心四点要素(必背&理解) 354 | 355 | - 1、初始化:start=0、end=len-1 356 | - 2、循环退出条件:start + 1 < end 357 | - 3、比较中点和目标值:A[mid] ==、 <、> target 358 | - 4、判断最后两个元素是否符合:A[start]、A[end] ? target 359 | 360 | ## 练习题 361 | 362 | - [ ] [search-for-range](https://www.lintcode.com/problem/search-for-a-range/description) 363 | - [ ] [search-insert-position](https://leetcode-cn.com/problems/search-insert-position/) 364 | - [ ] [search-a-2d-matrix](https://leetcode-cn.com/problems/search-a-2d-matrix/) 365 | - [ ] [first-bad-version](https://leetcode-cn.com/problems/first-bad-version/) 366 | - [ ] [find-minimum-in-rotated-sorted-array](https://leetcode-cn.com/problems/find-minimum-in-rotated-sorted-array/) 367 | - [ ] [find-minimum-in-rotated-sorted-array-ii](https://leetcode-cn.com/problems/find-minimum-in-rotated-sorted-array-ii/) 368 | - [ ] [search-in-rotated-sorted-array](https://leetcode-cn.com/problems/search-in-rotated-sorted-array/) 369 | - [ ] [search-in-rotated-sorted-array-ii](https://leetcode-cn.com/problems/search-in-rotated-sorted-array-ii/) 370 | -------------------------------------------------------------------------------- /basic_algorithm/dp.md: -------------------------------------------------------------------------------- 1 | # 动态规划 2 | 3 | ## 背景 4 | 5 | 先从一道题目开始~ 6 | 7 | 如题  [triangle](https://leetcode-cn.com/problems/triangle/) 8 | 9 | > 给定一个三角形,找出自顶向下的最小路径和。每一步只能移动到下一行中相邻的结点上。 10 | 11 | 例如,给定三角形: 12 | 13 | ```text 14 | [ 15 | [2], 16 | [3,4], 17 | [6,5,7], 18 | [4,1,8,3] 19 | ] 20 | ``` 21 | 22 | 自顶向下的最小路径和为  11(即,2 + 3 + 5 + 1 = 11)。 23 | 24 | 使用 DFS(遍历 或者 分治法) 25 | 26 | 遍历 27 | 28 | ![image.png](https://img.fuiboom.com/img/dp_triangle.png) 29 | 30 | 分治法 31 | 32 | ![image.png](https://img.fuiboom.com/img/dp_dc.png) 33 | 34 | 优化 DFS,缓存已经被计算的值(称为:记忆化搜索 本质上:动态规划) 35 | 36 | ![image.png](https://img.fuiboom.com/img/dp_memory_search.png) 37 | 38 | 动态规划就是把大问题变成小问题,并解决了小问题重复计算的方法称为动态规划 39 | 40 | 动态规划和 DFS 区别 41 | 42 | - 二叉树 子问题是没有交集,所以大部分二叉树都用递归或者分治法,即 DFS,就可以解决 43 | - 像 triangle 这种是有重复走的情况,**子问题是有交集**,所以可以用动态规划来解决 44 | 45 | 动态规划,自底向上 46 | 47 | ```Python 48 | class Solution: 49 | def minimumTotal(self, triangle: List[List[int]]) -> int: 50 | if len(triangle) == 0: 51 | return 0 52 | 53 | dp = triangle[-1].copy() 54 | 55 | for i in range(-2, -len(triangle) - 1, -1): 56 | for j in range(len(triangle[i])): 57 | dp[j] = triangle[i][j] + min(dp[j], dp[j + 1]) 58 | 59 | return dp[0] 60 | 61 | ``` 62 | 63 | 动态规划,自顶向下 64 | 65 | ```Python 66 | class Solution: 67 | def minimumTotal(self, triangle: List[List[int]]) -> int: 68 | if len(triangle) == 0: 69 | return 0 70 | 71 | dp = triangle[0] 72 | for row in triangle[1:]: 73 | dp_new = [row[0] + dp[0]] 74 | for i in range(len(dp) - 1): 75 | dp_new.append(row[i+1] + min(dp[i], dp[i+1])) 76 | dp_new.append(row[-1] + dp[-1]) 77 | dp = dp_new 78 | 79 | return min(dp) 80 | ``` 81 | 82 | ## 递归和动规关系 83 | 84 | 递归是一种程序的实现方式:函数的自我调用 85 | 86 | ```go 87 | Function(x) { 88 | ... 89 | Funciton(x-1); 90 | ... 91 | } 92 | ``` 93 | 94 | 动态规划:是一种解决问题的思想,大规模问题的结果,是由小规模问题的结果运算得来的。动态规划可用递归来实现(Memorization Search) 95 | 96 | ## 使用场景 97 | 98 | 满足两个条件 99 | 100 | - 满足以下条件之一 101 | - 求最大/最小值(Maximum/Minimum ) 102 | - 求是否可行(Yes/No ) 103 | - 求可行个数(Count(\*) ) 104 | - 满足不能排序或者交换(Can not sort / swap ) 105 | 106 | 如题:[longest-consecutive-sequence](https://leetcode-cn.com/problems/longest-consecutive-sequence/)  位置可以交换,所以不用动态规划 107 | 108 | ## 四点要素 109 | 110 | 1. **状态 State** 111 | - 灵感,创造力,存储小规模问题的结果 112 | 2. 方程 Function 113 | - 状态之间的联系,怎么通过小的状态,来算大的状态 114 | 3. 初始化 Intialization 115 | - 最极限的小状态是什么, 起点 116 | 4. 答案 Answer 117 | - 最大的那个状态是什么,终点 118 | 119 | ## 常见四种类型 120 | 121 | 1. Matrix DP (10%) 122 | 1. Sequence (40%) 123 | 1. Two Sequences DP (40%) 124 | 1. Backpack (10%) 125 | 126 | > 注意点 127 | > 128 | > - 贪心算法大多题目靠背答案,所以如果能用动态规划就尽量用动规,不用贪心算法 129 | 130 | ## 1、矩阵类型(10%) 131 | 132 | ### [minimum-path-sum](https://leetcode-cn.com/problems/minimum-path-sum/) 133 | 134 | > 给定一个包含非负整数的  *m* x *n*  网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。 135 | 136 | 思路:动态规划 137 | 138 | 1. state: f(x, y) 从起点走到 (x, y) 的最短路径 139 | 140 | 2. function: f(x, y) = min(f(x - 1, y), f(x, y - 1]) + A(x, y) 141 | 142 | 3. intialize: f(0, 0) = A(0, 0)、f(i, 0) = sum(0,0 -> i,0)、 f(0, i) = sum(0,0 -> 0,i) 143 | 144 | 4. answer: f(n - 1, m - 1) 145 | 146 | 5. 2D DP -> 1D DP 147 | 148 | ```Python 149 | class Solution: 150 | def minPathSum(self, grid: List[List[int]]) -> int: 151 | m, n = len(grid), len(grid[0]) 152 | 153 | dp = [0] * n 154 | dp[0] = grid[0][0] 155 | for i in range(1, n): 156 | dp[i] = dp[i-1] + grid[0][i] 157 | 158 | for i in range(1, m): 159 | dp[0] += grid[i][0] 160 | for j in range(1, n): 161 | dp[j] = grid[i][j] + min(dp[j-1], dp[j]) 162 | return dp[-1] 163 | ``` 164 | 165 | ### [unique-paths](https://leetcode-cn.com/problems/unique-paths/) 166 | 167 | > 一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。 168 | > 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。 169 | > 问总共有多少条不同的路径? 170 | 171 | ```Python 172 | class Solution: 173 | def uniquePaths(self, m: int, n: int) -> int: 174 | 175 | if m < n: 176 | m, n = n, m 177 | 178 | dp = [1] * n 179 | 180 | for i in range(1, m): 181 | for j in range(1, n): 182 | dp[j] += dp[j - 1] 183 | 184 | return dp[-1] 185 | ``` 186 | 187 | ### [unique-paths-ii](https://leetcode-cn.com/problems/unique-paths-ii/) 188 | 189 | > 一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。 190 | > 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。 191 | > 问总共有多少条不同的路径? 192 | > 现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径? 193 | 194 | ```Python 195 | class Solution: 196 | def uniquePathsWithObstacles(self, G: List[List[int]]) -> int: 197 | 198 | m, n = len(G), len(G[0]) 199 | 200 | dp = [1] if G[0][0] == 0 else [0] 201 | for i in range(1, n): 202 | new = dp[i-1] if G[0][i] == 0 else 0 203 | dp.append(new) 204 | 205 | for i in range(1, m): 206 | dp[0] = 0 if G[i][0] == 1 else dp[0] 207 | for j in range(1, n): 208 | dp[j] = dp[j-1] + dp[j] if G[i][j] == 0 else 0 209 | 210 | return dp[-1] 211 | ``` 212 | 213 | ## 2、序列类型(40%) 214 | 215 | ### [climbing-stairs](https://leetcode-cn.com/problems/climbing-stairs/) 216 | 217 | > 假设你正在爬楼梯。需要  *n*  阶你才能到达楼顶。 218 | 219 | ```Python 220 | class Solution: 221 | def climbStairs(self, n: int) -> int: 222 | if n < 2: return n 223 | 224 | step1, step2 = 2, 1 225 | 226 | for _ in range(n - 2): 227 | step1, step2 = step1 + step2, step1 228 | 229 | return step1 230 | ``` 231 | 232 | ### [jump-game](https://leetcode-cn.com/problems/jump-game/) 233 | 234 | > 给定一个非负整数数组,你最初位于数组的第一个位置。 235 | > 数组中的每个元素代表你在该位置可以跳跃的最大长度。 236 | > 判断你是否能够到达最后一个位置。 237 | 238 | 解法:直接DP无法得到O(n)的解,考虑间接DP 239 | 240 | - tail to head 241 | ```Python 242 | class Solution: 243 | def canJump(self, nums: List[int]) -> bool: 244 | 245 | left = len(nums) - 1 # most left index that can reach the last index 246 | 247 | for i in range(len(nums) - 2, -1, -1): 248 | 249 | left = i if i + nums[i] >= left else left # DP 250 | 251 | return left == 0 252 | ``` 253 | - head to tail 254 | ```Python 255 | class Solution: 256 | def canJump(self, nums: List[int]) -> bool: 257 | 258 | max_pos = nums[0] # furthest index can reach 259 | 260 | for i in range(1, len(nums)): 261 | if max_pos < i: 262 | return False 263 | max_pos = max(max_pos, i + nums[i]) # DP 264 | 265 | return True 266 | ``` 267 | 268 | ### [jump-game-ii](https://leetcode-cn.com/problems/jump-game-ii/) 269 | 270 | > 给定一个非负整数数组,你最初位于数组的第一个位置。 271 | > 数组中的每个元素代表你在该位置可以跳跃的最大长度。 272 | > 你的目标是使用最少的跳跃次数到达数组的最后一个位置。 273 | 274 | ```Python 275 | class Solution: 276 | def jump(self, nums: List[int]) -> int: 277 | 278 | cur_max = 0 279 | step_max = 0 280 | step = 0 281 | 282 | for i in range(len(nums)): 283 | 284 | if cur_max < i: # can't reach i, don't have to consider in this problem 285 | return float('inf') 286 | 287 | if step_max < i: # can't reach i in current number of steps 288 | step += 1 289 | step_max = cur_max 290 | 291 | cur_max = max(cur_max, i + nums[i]) # DP 292 | 293 | return min_step 294 | ``` 295 | 296 | ### [palindrome-partitioning-ii](https://leetcode-cn.com/problems/palindrome-partitioning-ii/) 297 | 298 | > 给定一个字符串 _s_,将 _s_ 分割成一些子串,使每个子串都是回文串。 299 | > 返回符合要求的最少分割次数。 300 | 301 | - Why is hard 302 | 303 | 仅目标DP, 判断回文时间复杂度高 -> 目标DP + 回文二维DP, 回文DP空间复杂度高 -> 一点trick, 回文DP空间复杂度降为线性 304 | 305 | ```Python 306 | class Solution: 307 | 308 | def minCut(self, s: str) -> int: 309 | 310 | dp_min = [0] * len(s) 311 | dp_pal = [True] * len(s) 312 | 313 | def isPal(i, j): 314 | dp_pal[i] = (s[i] == s[j] and dp_pal[i+1]) 315 | return dp_pal[i] 316 | 317 | for j in range(1, len(s)): 318 | 319 | min_cut = dp_min[j - 1] + 1 320 | 321 | if isPal(0, j): 322 | min_cut = 0 323 | 324 | for i in range(1, j): 325 | if isPal(i, j): 326 | min_cut = min(min_cut, dp_min[i - 1] + 1) 327 | 328 | dp_min[j] = min_cut 329 | 330 | return dp_min[-1] 331 | ``` 332 | 333 | ### [longest-increasing-subsequence](https://leetcode-cn.com/problems/longest-increasing-subsequence/) 334 | 335 | > 给定一个无序的整数数组,找到其中最长上升子序列的长度。 336 | 337 | - DP(i) 等于以第i个数结尾的最长上升子序列的长度,容易想但不是最优 338 | ```Python 339 | class Solution: 340 | def lengthOfLIS(self, nums: List[int]) -> int: 341 | 342 | if len(nums) == 0: return 0 343 | 344 | dp_max = [1] * len(nums) 345 | 346 | for j in range(1, len(nums)): 347 | for i in range(j): 348 | if nums[j] > nums[i]: 349 | dp_max[j] = max(dp_max[j], dp_max[i] + 1) 350 | 351 | return max(dp_max) 352 | ``` 353 | - 最优算法使用 greedy + binary search,比较tricky 354 | ```Python 355 | class Solution: 356 | def lengthOfLIS(self, nums: List[int]) -> int: 357 | 358 | if len(nums) == 0: return 0 359 | 360 | seq = [nums[0]] 361 | 362 | for i in range(1, len(nums)): 363 | ins = bisect.bisect_left(seq, nums[i]) 364 | if ins == len(seq): 365 | seq.append(nums[i]) 366 | else: 367 | seq[ins] = nums[i] 368 | 369 | return len(seq) 370 | ``` 371 | 372 | ### [word-break](https://leetcode-cn.com/problems/word-break/) 373 | 374 | > 给定一个**非空**字符串  *s*  和一个包含**非空**单词列表的字典  *wordDict*,判定  *s*  是否可以被空格拆分为一个或多个在字典中出现的单词。 375 | 376 | ```Python 377 | class Solution: 378 | def wordBreak(self, s: str, wordDict: List[str]) -> bool: 379 | 380 | dp = [False] * (len(s) + 1) 381 | dp[-1] = True 382 | 383 | for j in range(len(s)): 384 | for i in range(j+1): 385 | if dp[i - 1] and s[i:j+1] in wordDict: 386 | dp[j] = True 387 | break 388 | 389 | return dp[len(s) - 1] 390 | 391 | ``` 392 | 393 | 小结 394 | 395 | 常见处理方式是给 0 位置占位,这样处理问题时一视同仁,初始化则在原来基础上 length+1,返回结果 f[n] 396 | 397 | - 状态可以为前 i 个 398 | - 初始化 length+1 399 | - 取值 index=i-1 400 | - 返回值:f[n]或者 f[m][n] 401 | 402 | ## Two Sequences DP(40%) 403 | 404 | ### [longest-common-subsequence](https://leetcode-cn.com/problems/longest-common-subsequence/) 405 | 406 | > 给定两个字符串  text1 和  text2,返回这两个字符串的最长公共子序列。 407 | > 一个字符串的   子序列   是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。 408 | > 例如,"ace" 是 "abcde" 的子序列,但 "aec" 不是 "abcde" 的子序列。两个字符串的「公共子序列」是这两个字符串所共同拥有的子序列。 409 | 410 | - 二维DP若只与当前行和上一行有关,可将空间复杂度降到线性 411 | 412 | ```Python 413 | class Solution: 414 | def longestCommonSubsequence(self, t1: str, t2: str) -> int: 415 | 416 | if t1 == '' or t2 == '': 417 | return 0 418 | 419 | if len(t1) < len(t2): 420 | t1, t2 = t2, t1 421 | 422 | dp = [int(t2[0] == t1[0])] * len(t2) # previous row 423 | dp_new = [0] * len(t2) # current row 424 | 425 | for j in range(1, len(t2)): 426 | dp[j] = 1 if t2[j] == t1[0] else dp[j - 1] 427 | 428 | for i in range(1, len(t1)): 429 | dp_new[0] = 1 if dp[0] == 1 or t2[0] == t1[i] else 0 430 | for j in range(1, len(t2)): 431 | if t2[j] != t1[i]: 432 | dp_new[j] = max(dp[j], dp_new[j - 1]) 433 | else: 434 | dp_new[j] = dp[j - 1] + 1 435 | dp, dp_new = dp_new, dp 436 | 437 | return dp[-1] 438 | ``` 439 | 440 | ### [edit-distance](https://leetcode-cn.com/problems/edit-distance/) 441 | 442 | > 给你两个单词  word1 和  word2,请你计算出将  word1  转换成  word2 所使用的最少操作数   443 | > 你可以对一个单词进行如下三种操作: 444 | > 插入一个字符 445 | > 删除一个字符 446 | > 替换一个字符 447 | 448 | 思路:和上题很类似,相等则不需要操作,否则取删除、插入、替换最小操作次数的值+1 449 | 450 | ```Python 451 | class Solution: 452 | def minDistance(self, w1: str, w2: str) -> int: 453 | 454 | if w1 == '': return len(w2) 455 | if w2 == '': return len(w1) 456 | 457 | m, n = len(w1), len(w2) 458 | if m < n: 459 | w1, w2, m, n = w2, w1, n, m 460 | 461 | dp = [int(w1[0] != w2[0])] * n 462 | dp_new = [0] * n 463 | 464 | for j in range(1, n): 465 | dp[j] = dp[j - 1] + int(w2[j] != w1[0] or dp[j - 1] != j) 466 | 467 | for i in range(1, m): 468 | dp_new[0] = dp[0] + int(w2[0] != w1[i] or dp[0] != i) 469 | 470 | for j in range(1, n): 471 | dp_new[j] = min(dp[j - 1] + int(w2[j] != w1[i]), dp[j] + 1, dp_new[j - 1] + 1) 472 | 473 | dp, dp_new = dp_new, dp 474 | 475 | 476 | return dp[-1] 477 | ``` 478 | 479 | 说明 480 | 481 | > 另外一种做法:MAXLEN(a,b)-LCS(a,b) 482 | 483 | ## 零钱和背包(10%) 484 | 485 | ### [coin-change](https://leetcode-cn.com/problems/coin-change/) 486 | 487 | > 给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回  -1。 488 | 489 | 思路:和其他 DP 不太一样,i 表示钱或者容量 490 | 491 | ```Python 492 | class Solution: 493 | def coinChange(self, coins: List[int], amount: int) -> int: 494 | 495 | dp = [0] * (amount + 1) 496 | 497 | for i in range(1, len(dp)): 498 | dp[i] = float('inf') 499 | 500 | for coin in coins: 501 | if i >= coin and dp[i - coin] + 1 < dp[i]: 502 | dp[i] = dp[i - coin] + 1 503 | 504 | return -1 if dp[amount] == float('inf') else dp[amount] 505 | ``` 506 | 507 | 508 | ### [backpack](https://www.lintcode.com/problem/backpack/description) 509 | 510 | > 在 n 个物品中挑选若干物品装入背包,最多能装多满?假设背包的大小为 m,每个物品的大小为 A[i] 511 | 512 | ```Python 513 | class Solution: 514 | def backPack(self, m, A): 515 | 516 | n = len(A) 517 | 518 | dp = [0] * (m + 1) 519 | dp_new = [0] * (m + 1) 520 | 521 | for i in range(n): 522 | for j in range(1, m + 1): 523 | use_Ai = 0 if j - A[i] < 0 else dp[j - A[i]] + A[i] 524 | dp_new[j] = max(dp[j], use_Ai) 525 | 526 | dp, dp_new = dp_new, dp 527 | 528 | return dp[-1] 529 | 530 | ``` 531 | 532 | ### [backpack-ii](https://www.lintcode.com/problem/backpack-ii/description) 533 | 534 | > 有 `n` 个物品和一个大小为 `m` 的背包. 给定数组 `A` 表示每个物品的大小和数组 `V` 表示每个物品的价值. 535 | > 问最多能装入背包的总价值是多大? 536 | 537 | 思路:dp(i, j) 为前 i 个物品,装入 j 背包的最大价值 538 | 539 | ```Python 540 | class Solution: 541 | def backPackII(self, m, A, V): 542 | 543 | n = len(A) 544 | 545 | dp = [0] * (m + 1) 546 | dp_new = [0] * (m + 1) 547 | 548 | for i in range(n): 549 | for j in range(1, m + 1): 550 | use_Ai = 0 if j - A[i] < 0 else dp[j - A[i]] + V[i] # previous problem is a special case of this problem that V(i) = A(i) 551 | dp_new[j] = max(dp[j], use_Ai) 552 | 553 | dp, dp_new = dp_new, dp 554 | 555 | return dp[-1] 556 | 557 | ``` 558 | 559 | ## 补充 560 | 561 | ### [maximum-product-subarray](https://leetcode-cn.com/problems/maximum-product-subarray/) 562 | 563 | > 最大乘积子串 564 | 565 | 处理负数情况稍微有点复杂,注意需要同时 DP 正数乘积和负数乘积 566 | 567 | ```Python 568 | class Solution: 569 | def maxProduct(self, nums: List[int]) -> int: 570 | 571 | max_product = float('-inf') 572 | 573 | dp_pos, dp_neg = 0, 0 574 | 575 | for num in nums: 576 | if num > 0: 577 | dp_pos, dp_neg = max(num, num * dp_pos), dp_neg * num 578 | else: 579 | dp_pos, dp_neg = dp_neg * num, min(num, dp_pos * num) 580 | 581 | if dp_pos != 0: 582 | max_product = max(max_product, dp_pos) 583 | elif dp_neg != 0: 584 | max_product = max(max_product, dp_neg) 585 | else: 586 | max_product = max(max_product, 0) 587 | 588 | return max_product 589 | ``` 590 | 591 | ### [decode-ways](https://leetcode-cn.com/problems/decode-ways/) 592 | 593 | > 1 到 26 分别对应 a 到 z,给定输入数字串,问总共有多少种译码方法 594 | 595 | 常规 DP 题,注意处理edge case即可 596 | 597 | ```Python 598 | class Solution: 599 | def numDecodings(self, s: str) -> int: 600 | 601 | def valid_2(i): 602 | if i < 1: 603 | return 0 604 | num = int(s[i-1:i+1]) 605 | return int(num > 9 and num < 27) 606 | 607 | dp_1, dp_2 = 1, 0 608 | for i in range(len(s)): 609 | dp_1, dp_2 = dp_1 * int(s[i] != '0') + dp_2 * valid_2(i), dp_1 610 | 611 | return dp_1 612 | ``` 613 | 614 | ### [best-time-to-buy-and-sell-stock-with-cooldown](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-with-cooldown/) 615 | 616 | > 给定股票每天的价格,每天可以买入卖出,买入后必须卖出才可以进行下一次购买,卖出后一天不可以购买,问可以获得的最大利润 617 | 618 | 经典的维特比译码类问题,找到状态空间和状态转移关系即可 619 | 620 | ```Python 621 | class Solution: 622 | def maxProfit(self, prices: List[int]) -> int: 623 | 624 | buy, buy_then_nothing, sell, sell_then_nothing = float('-inf'), float('-inf'), float('-inf'), 0 625 | 626 | for p in prices: 627 | buy, buy_then_nothing, sell, sell_then_nothing = sell_then_nothing - p, max(buy, buy_then_nothing), max(buy, buy_then_nothing) + p, max(sell, sell_then_nothing) 628 | 629 | return max(buy, buy_then_nothing, sell, sell_then_nothing) 630 | ``` 631 | 632 | ### [word-break-ii](https://leetcode-cn.com/problems/word-break-ii/) 633 | 634 | > 给定字符串和可选的单词列表,求字符串所有的分割方式 635 | 636 | 思路:此题 DP 解法容易想但并不是好做法,因为和 word-break 不同,此题需要返回所有可行分割而不是找到一组就可以。这里使用 个人推荐 backtrack with memoization。 637 | 638 | ```Python 639 | class Solution: 640 | def wordBreak(self, s: str, wordDict: List[str]) -> List[str]: 641 | 642 | n = len(s) 643 | result = [] 644 | mem = collections.defaultdict(list) 645 | wordDict = set(wordDict) 646 | 647 | def backtrack(first=0, route=[]): 648 | if first == n: 649 | result.append(' '.join(route)) 650 | return True 651 | 652 | if first not in mem: 653 | for next_first in range(first + 1, n + 1): 654 | if s[first:next_first] in wordDict: 655 | route.append(s[first:next_first]) 656 | if backtrack(next_first, route): 657 | mem[first].append(next_first) 658 | route.pop() 659 | if len(mem[first]) > 0: 660 | return True 661 | elif len(mem[first]) > 0: 662 | for next_first in mem[first]: 663 | route.append(s[first:next_first]) 664 | backtrack(next_first) 665 | route.pop() 666 | return True 667 | 668 | return False 669 | 670 | backtrack() 671 | return result 672 | ``` 673 | 674 | ### [burst-balloons](https://leetcode-cn.com/problems/burst-balloons/) 675 | 676 | > n 个气球排成一行,每个气球上有一个分数,每次戳爆一个气球得分为该气球分数和相邻两气球分数的乘积,求最大得分 677 | 678 | 此题主要难点是构造 DP 的状态,过程为逆着气球戳爆的顺序 679 | 680 | ```Python 681 | class Solution: 682 | def maxCoins(self, nums: List[int]) -> int: 683 | 684 | n = len(nums) 685 | nums.append(1) 686 | dp = [[0] * (n + 1) for _ in range(n + 1)] 687 | 688 | for dist in range(2, n + 2): 689 | for left in range(-1, n - dist + 1): 690 | right = left + dist 691 | max_coin = float('-inf') 692 | left_right = nums[left] * nums[right] 693 | for j in range(left + 1, right): 694 | max_coin = max(max_coin, left_right * nums[j] + dp[left][j] + dp[j][right]) 695 | dp[left][right] = max_coin 696 | nums.pop() 697 | return dp[-1][n] 698 | ``` 699 | 700 | 701 | 702 | ## 练习 703 | 704 | Matrix DP (10%) 705 | 706 | - [ ] [triangle](https://leetcode-cn.com/problems/triangle/) 707 | - [ ] [minimum-path-sum](https://leetcode-cn.com/problems/minimum-path-sum/) 708 | - [ ] [unique-paths](https://leetcode-cn.com/problems/unique-paths/) 709 | - [ ] [unique-paths-ii](https://leetcode-cn.com/problems/unique-paths-ii/) 710 | 711 | Sequence (40%) 712 | 713 | - [ ] [climbing-stairs](https://leetcode-cn.com/problems/climbing-stairs/) 714 | - [ ] [jump-game](https://leetcode-cn.com/problems/jump-game/) 715 | - [ ] [jump-game-ii](https://leetcode-cn.com/problems/jump-game-ii/) 716 | - [ ] [palindrome-partitioning-ii](https://leetcode-cn.com/problems/palindrome-partitioning-ii/) 717 | - [ ] [longest-increasing-subsequence](https://leetcode-cn.com/problems/longest-increasing-subsequence/) 718 | - [ ] [word-break](https://leetcode-cn.com/problems/word-break/) 719 | 720 | Two Sequences DP (40%) 721 | 722 | - [ ] [longest-common-subsequence](https://leetcode-cn.com/problems/longest-common-subsequence/) 723 | - [ ] [edit-distance](https://leetcode-cn.com/problems/edit-distance/) 724 | 725 | Backpack & Coin Change (10%) 726 | 727 | - [ ] [coin-change](https://leetcode-cn.com/problems/coin-change/) 728 | - [ ] [backpack](https://www.lintcode.com/problem/backpack/description) 729 | - [ ] [backpack-ii](https://www.lintcode.com/problem/backpack-ii/description) 730 | -------------------------------------------------------------------------------- /basic_algorithm/graph/README.md: -------------------------------------------------------------------------------- 1 | # 图相关算法 2 | 3 | 图 (graph) 是一种相当复杂的数据结构,相关算法也多种多样,以下总结一些比较基础且常见的图算法。 4 | 5 | ### [深度优先搜索,广度优先搜索](./bfs_dfs.md) 6 | 7 | ### [拓扑排序](./topological_sorting.md) 8 | 9 | ### [最小生成树](./mst.md) 10 | 11 | ### [最短路径](./shortest_path.md) 12 | 13 | ### [图的表示](./graph_representation.md) 14 | 15 | -------------------------------------------------------------------------------- /basic_algorithm/graph/bfs_dfs.md: -------------------------------------------------------------------------------- 1 | # 深度优先搜索,广度优先搜索 2 | 3 | ### 深度优先搜索模板 4 | 5 | - 先序,递归 6 | 7 | ```Python 8 | def DFS(x): 9 | visit(x) 10 | for n in neighbor(x): 11 | if not visited(n): 12 | DFS(n) 13 | return 14 | ``` 15 | 16 | - 先序,迭代,出栈时访问 17 | 18 | ```Python 19 | def DFS(x): 20 | dfs = [x] # implement by a stack 21 | while dfs: 22 | v = dfs.pop() 23 | if not visited(v): 24 | visit(v) 25 | for n in neighbor(v): 26 | if not visited(n): 27 | dfs.append(n) 28 | return 29 | ``` 30 | 31 | - 后序,递归 32 | 33 | ```Python 34 | def DFS(x): # used when need to aggregate results from children 35 | discovering(x) 36 | for n in neighbor(x): 37 | if not discovering(n) and not visited(n): 38 | DFS(n) 39 | visit(x) 40 | return 41 | ``` 42 | 43 | ### 广度优先搜索模板 44 | 45 | 相对于 dfs 可能收敛更慢,但是可以用来找不带权的最短路径 46 | 47 | - 以结点为单位搜索 48 | 49 | ```Python 50 | def BFS(x): 51 | visit(x) 52 | bfs = collections.deque([x]) 53 | while bfs: 54 | v = bfs.popleft() 55 | for n in neighbor(v): 56 | if not visited(n): 57 | visit(n) 58 | bfs.append(n) 59 | return 60 | ``` 61 | 62 | - 以层为单位搜索,典型应用是找不带权的最短路径 63 | 64 | ```Python 65 | def BFS(x): 66 | visit(x) 67 | bfs = collections.deque([x]) 68 | while bfs: 69 | num_level = len(bfs) 70 | for _ in range(num_level) 71 | v = bfs.popleft() 72 | for n in neighbor(v): 73 | if not visited(v): 74 | visit(n) 75 | bfs.append(n) 76 | return 77 | ``` 78 | 79 | ## 例题 80 | 81 | ### [walls-and-gates](https://leetcode-cn.com/problems/walls-and-gates/) 82 | 83 | > 给定一个二维矩阵,矩阵中元素 -1 表示墙或是障碍物,0 表示一扇门,INF (2147483647) 表示一个空的房间。你要给每个空房间位上填上该房间到最近门的距离,如果无法到达门,则填 INF 即可。 84 | 85 | - 思路:典型的多源最短路径问题,将所有源作为 BFS 的第一层即可 86 | 87 | ```Python 88 | inf = 2147483647 89 | 90 | class Solution: 91 | def wallsAndGates(self, rooms: List[List[int]]) -> None: 92 | """ 93 | Do not return anything, modify rooms in-place instead. 94 | """ 95 | 96 | if not rooms or not rooms[0]: 97 | return 98 | 99 | M, N = len(rooms), len(rooms[0]) 100 | 101 | bfs = collections.deque([]) 102 | 103 | for i in range(M): 104 | for j in range(N): 105 | if rooms[i][j] == 0: 106 | bfs.append((i, j)) 107 | 108 | dist = 1 109 | while bfs: 110 | num_level = len(bfs) 111 | for _ in range(num_level): 112 | r, c = bfs.popleft() 113 | 114 | if r - 1 >= 0 and rooms[r - 1][c] == inf: 115 | rooms[r - 1][c] = dist 116 | bfs.append((r - 1, c)) 117 | 118 | if r + 1 < M and rooms[r + 1][c] == inf: 119 | rooms[r + 1][c] = dist 120 | bfs.append((r + 1, c)) 121 | 122 | if c - 1 >= 0 and rooms[r][c - 1] == inf: 123 | rooms[r][c - 1] = dist 124 | bfs.append((r, c - 1)) 125 | 126 | if c + 1 < N and rooms[r][c + 1] == inf: 127 | rooms[r][c + 1] = dist 128 | bfs.append((r, c + 1)) 129 | 130 | dist += 1 131 | 132 | return 133 | ``` 134 | 135 | ### [shortest-bridge](https://leetcode-cn.com/problems/shortest-bridge/) 136 | 137 | > 在给定的 01 矩阵 A 中,存在两座岛 (岛是由四面相连的 1 形成的一个连通分量)。现在,我们可以将 0 变为 1,以使两座岛连接起来,变成一座岛。返回必须翻转的 0 的最小数目。 138 | > 139 | 140 | - 思路:DFS 遍历连通分量找边界,从边界开始 BFS找最短路径 141 | 142 | ```Python 143 | class Solution: 144 | def shortestBridge(self, A: List[List[int]]) -> int: 145 | 146 | M, N = len(A), len(A[0]) 147 | neighors = ((-1, 0), (1, 0), (0, -1), (0, 1)) 148 | 149 | dfs = [] 150 | bfs = collections.deque([]) 151 | 152 | for i in range(M): 153 | for j in range(N): 154 | if A[i][j] == 1: # start from a 1 155 | dfs.append((i, j)) 156 | break 157 | if dfs: 158 | break 159 | 160 | while dfs: 161 | r, c = dfs.pop() 162 | if A[r][c] == 1: 163 | A[r][c] = -1 164 | 165 | for dr, dc in neighors: 166 | nr, nc = r + dr, c + dc 167 | if 0<= nr < M and 0 <= nc < N: 168 | if A[nr][nc] == 0: # meet and edge 169 | A[nr][nc] = -2 170 | bfs.append((nr, nc)) 171 | elif A[nr][nc] == 1: 172 | dfs.append((nr, nc)) 173 | 174 | flip = 1 175 | while bfs: 176 | num_level = len(bfs) 177 | for _ in range(num_level): 178 | r, c = bfs.popleft() 179 | 180 | for dr, dc in neighors: 181 | nr, nc = r + dr, c + dc 182 | if 0<= nr < M and 0 <= nc < N: 183 | if A[nr][nc] == 0: 184 | A[nr][nc] = -2 185 | bfs.append((nr, nc)) 186 | elif A[nr][nc] == 1: 187 | return flip 188 | flip += 1 189 | ``` 190 | 191 | ### [sliding-puzzle](https://leetcode-cn.com/problems/sliding-puzzle) 192 | 193 | ```Python 194 | class Solution: 195 | def slidingPuzzle(self, board: List[List[int]]) -> int: 196 | 197 | next_move = { 198 | 0: [1, 3], 199 | 1: [0, 2, 4], 200 | 2: [1, 5], 201 | 3: [0, 4], 202 | 4: [1, 3, 5], 203 | 5: [2, 4] 204 | } 205 | 206 | start = tuple(itertools.chain(*board)) 207 | target = (1, 2, 3, 4, 5, 0) 208 | 209 | if start == target: 210 | return 0 211 | 212 | SPT = set([start]) 213 | bfs = collections.deque([(start, start.index(0))]) 214 | 215 | step = 1 216 | while bfs: 217 | num_level = len(bfs) 218 | for _ in range(num_level): 219 | state, idx0 = bfs.popleft() 220 | 221 | for next_step in next_move[idx0]: 222 | next_state = list(state) 223 | next_state[idx0], next_state[next_step] = next_state[next_step], next_state[idx0] 224 | next_state = tuple(next_state) 225 | 226 | if next_state == target: 227 | return step 228 | 229 | if next_state not in SPT: 230 | SPT.add(next_state) 231 | bfs.append((next_state, next_step)) 232 | step += 1 233 | return -1 234 | ``` 235 | 236 | -------------------------------------------------------------------------------- /basic_algorithm/graph/graph_representation.md: -------------------------------------------------------------------------------- 1 | # 图的表示 2 | 3 | 图的邻接表和邻接矩阵表示最为常用,但是有时需要建图时这两种表示效率不是很高,因为需要构造每个结点和每一条边。此时使用一些隐式的表示方法可以提升建图效率。 4 | 5 | ### [word-ladder](https://leetcode-cn.com/problems/word-ladder/) 6 | 7 | ```Python 8 | class Solution: 9 | def ladderLength(self, beginWord: str, endWord: str, wordList: List[str]) -> int: 10 | 11 | N, K = len(wordList), len(beginWord) 12 | 13 | find_end = False 14 | for i in range(N): 15 | if wordList[i] == endWord: 16 | find_end = True 17 | break 18 | 19 | if not find_end: 20 | return 0 21 | 22 | wordList.append(beginWord) 23 | N += 1 24 | 25 | # clustering nodes for efficiency compare to adjacent list 26 | cluster = collections.defaultdict(list) 27 | for i in range(N): 28 | node = wordList[i] 29 | for j in range(K): 30 | cluster[node[:j] + '*' + node[j + 1:]].append(node) 31 | 32 | # bidirectional BFS 33 | visited_start, visited_end = set([beginWord]), set([endWord]) 34 | bfs_start, bfs_end = collections.deque([beginWord]), collections.deque([endWord]) 35 | step = 2 36 | while bfs_start and bfs_end: 37 | 38 | # start 39 | num_level = len(bfs_start) 40 | while num_level > 0: 41 | node = bfs_start.popleft() 42 | for j in range(K): 43 | key = node[:j] + '*' + node[j + 1:] 44 | for n in cluster[key]: 45 | if n in visited_end: 46 | return step * 2 - 2 47 | if n not in visited_start: 48 | visited_start.add(n) 49 | bfs_start.append(n) 50 | num_level -= 1 51 | 52 | # end 53 | num_level = len(bfs_end) 54 | while num_level > 0: 55 | node = bfs_end.popleft() 56 | for j in range(K): 57 | key = node[:j] + '*' + node[j + 1:] 58 | for n in cluster[key]: 59 | if n in visited_start: 60 | return step * 2 - 1 61 | if n not in visited_end: 62 | visited_end.add(n) 63 | bfs_end.append(n) 64 | num_level -= 1 65 | step += 1 66 | 67 | return 0 68 | ``` 69 | 70 | -------------------------------------------------------------------------------- /basic_algorithm/graph/mst.md: -------------------------------------------------------------------------------- 1 | # 最小生成树 2 | 3 | ### [minimum-risk-path](https://www.lintcode.com/problem/minimum-risk-path/description) 4 | 5 | > 地图上有 m 条无向边,每条边 (x, y, w) 表示位置 m 到位置 y 的权值为 w。从位置 0 到 位置 n 可能有多条路径。我们定义一条路径的危险值为这条路径中所有的边的最大权值。请问从位置 0 到 位置 n 所有路径中最小的危险值为多少? 6 | 7 | 最小危险值为最小生成树中 0 到 n 路径上的最大边权。以此题为例给出最小生成树的两种经典算法。 8 | 9 | - 算法 1: [Kruskal's algorithm]([https://en.wikipedia.org/wiki/Kruskal%27s_algorithm](https://en.wikipedia.org/wiki/Kruskal's_algorithm)),使用[并查集](../../data_structure/union_find.md)实现。 10 | 11 | ```Python 12 | # Kruskal's algorithm 13 | class Solution: 14 | def getMinRiskValue(self, N, M, X, Y, W): 15 | 16 | # Kruskal's algorithm with union-find 17 | parent = list(range(N + 1)) 18 | rank = [1] * (N + 1) 19 | 20 | def find(x): 21 | if parent[parent[x]] != parent[x]: 22 | parent[x] = find(parent[x]) 23 | return parent[x] 24 | 25 | def union(x, y): 26 | px, py = find(x), find(y) 27 | if px == py: 28 | return False 29 | 30 | if rank[px] > rank[py]: 31 | parent[py] = px 32 | elif rank[px] < rank[py]: 33 | parent[px] = py 34 | else: 35 | parent[px] = py 36 | rank[py] += 1 37 | 38 | return True 39 | 40 | edges = sorted(zip(W, X, Y)) 41 | 42 | for w, x, y in edges: 43 | if union(x, y) and find(0) == find(N): # early return without constructing MST 44 | return w 45 | ``` 46 | 47 | - 算法 2: [Prim's algorithm]([https://en.wikipedia.org/wiki/Prim%27s_algorithm](https://en.wikipedia.org/wiki/Prim's_algorithm)),使用[优先级队列 (堆)](../../data_structure/heap.md)实现 48 | 49 | ```Python 50 | # Prim's algorithm 51 | class Solution: 52 | def getMinRiskValue(self, N, M, X, Y, W): 53 | 54 | # construct graph 55 | adj = collections.defaultdict(list) 56 | for i in range(M): 57 | adj[X[i]].append((Y[i], W[i])) 58 | adj[Y[i]].append((X[i], W[i])) 59 | 60 | # Prim's algorithm with min heap 61 | MST = collections.defaultdict(list) 62 | min_heap = [(w, 0, v) for v, w in adj[0]] 63 | heapq.heapify(min_heap) 64 | 65 | while N not in MST: 66 | w, p, v = heapq.heappop(min_heap) 67 | if v not in MST: 68 | MST[p].append((v, w)) 69 | MST[v].append((p, w)) 70 | for n, w in adj[v]: 71 | if n not in MST: 72 | heapq.heappush(min_heap, (w, v, n)) 73 | 74 | # dfs to search route from 0 to n 75 | dfs = [(0, None, float('-inf'))] 76 | while dfs: 77 | v, p, max_w = dfs.pop() 78 | for n, w in MST[v]: 79 | cur_max_w = max(max_w, w) 80 | if n == N: 81 | return cur_max_w 82 | if n != p: 83 | dfs.append((n, v, cur_max_w)) 84 | ``` 85 | 86 | -------------------------------------------------------------------------------- /basic_algorithm/graph/shortest_path.md: -------------------------------------------------------------------------------- 1 | # 最短路径问题 2 | 3 | ## BFS 4 | 5 | 在处理不带权图的最短路径问题时可以使用 BFS。 6 | 7 | ### [walls-and-gates](https://leetcode-cn.com/problems/walls-and-gates/) 8 | 9 | > 给定一个二维矩阵,矩阵中元素 -1 表示墙或是障碍物,0 表示一扇门,INF (2147483647) 表示一个空的房间。你要给每个空房间位上填上该房间到最近门的距离,如果无法到达门,则填 INF 即可。 10 | 11 | - 思路:典型的多源最短路径问题,将所有源作为 BFS 的第一层即可 12 | 13 | ```Python 14 | inf = 2147483647 15 | 16 | class Solution: 17 | def wallsAndGates(self, rooms: List[List[int]]) -> None: 18 | """ 19 | Do not return anything, modify rooms in-place instead. 20 | """ 21 | 22 | if not rooms or not rooms[0]: 23 | return 24 | 25 | M, N = len(rooms), len(rooms[0]) 26 | 27 | bfs = collections.deque([]) 28 | 29 | for i in range(M): 30 | for j in range(N): 31 | if rooms[i][j] == 0: 32 | bfs.append((i, j)) 33 | 34 | dist = 1 35 | while bfs: 36 | num_level = len(bfs) 37 | for _ in range(num_level): 38 | r, c = bfs.popleft() 39 | 40 | if r - 1 >= 0 and rooms[r - 1][c] == inf: 41 | rooms[r - 1][c] = dist 42 | bfs.append((r - 1, c)) 43 | 44 | if r + 1 < M and rooms[r + 1][c] == inf: 45 | rooms[r + 1][c] = dist 46 | bfs.append((r + 1, c)) 47 | 48 | if c - 1 >= 0 and rooms[r][c - 1] == inf: 49 | rooms[r][c - 1] = dist 50 | bfs.append((r, c - 1)) 51 | 52 | if c + 1 < N and rooms[r][c + 1] == inf: 53 | rooms[r][c + 1] = dist 54 | bfs.append((r, c + 1)) 55 | 56 | dist += 1 57 | 58 | return 59 | ``` 60 | 61 | ### [shortest-bridge](https://leetcode-cn.com/problems/shortest-bridge/) 62 | 63 | > 在给定的 01 矩阵 A 中,存在两座岛 (岛是由四面相连的 1 形成的一个连通分量)。现在,我们可以将 0 变为 1,以使两座岛连接起来,变成一座岛。返回必须翻转的 0 的最小数目。 64 | 65 | - 思路:DFS 遍历连通分量找边界,从边界开始 BFS找最短路径 66 | 67 | ```Python 68 | class Solution: 69 | def shortestBridge(self, A: List[List[int]]) -> int: 70 | 71 | M, N = len(A), len(A[0]) 72 | neighors = ((-1, 0), (1, 0), (0, -1), (0, 1)) 73 | 74 | dfs = [] 75 | bfs = collections.deque([]) 76 | 77 | for i in range(M): 78 | for j in range(N): 79 | if A[i][j] == 1: # start from a 1 80 | dfs.append((i, j)) 81 | break 82 | if dfs: 83 | break 84 | 85 | while dfs: 86 | r, c = dfs.pop() 87 | if A[r][c] == 1: 88 | A[r][c] = -1 89 | 90 | for dr, dc in neighors: 91 | nr, nc = r + dr, c + dc 92 | if 0<= nr < M and 0 <= nc < N: 93 | if A[nr][nc] == 0: # meet and edge 94 | A[nr][nc] = -2 95 | bfs.append((nr, nc)) 96 | elif A[nr][nc] == 1: 97 | dfs.append((nr, nc)) 98 | 99 | flip = 1 100 | while bfs: 101 | num_level = len(bfs) 102 | for _ in range(num_level): 103 | r, c = bfs.popleft() 104 | 105 | for dr, dc in neighors: 106 | nr, nc = r + dr, c + dc 107 | if 0<= nr < M and 0 <= nc < N: 108 | if A[nr][nc] == 0: 109 | A[nr][nc] = -2 110 | bfs.append((nr, nc)) 111 | elif A[nr][nc] == 1: 112 | return flip 113 | flip += 1 114 | ``` 115 | 116 | ## Dijkstra's Algorithm 117 | 118 | 用于求解单源最短路径问题。思想是 greedy 构造 shortest path tree (SPT),每次将当前距离源点最短的不在 SPT 中的结点加入SPT,与构造最小生成树 (MST) 的 Prim's algorithm 非常相似。可以用 priority queue (heap) 实现。 119 | 120 | ### [network-delay-time](https://leetcode-cn.com/problems/network-delay-time/) 121 | 122 | - 标准的单源最短路径问题,使用朴素的 Dijikstra 算法即可。 123 | 124 | ```Python 125 | class Solution: 126 | def networkDelayTime(self, times: List[List[int]], N: int, K: int) -> int: 127 | 128 | # construct graph 129 | graph_neighbor = collections.defaultdict(list) 130 | for s, e, t in times: 131 | graph_neighbor[s].append((e, t)) 132 | 133 | # Dijkstra 134 | SPT = {} 135 | min_heap = [(0, K)] 136 | 137 | while min_heap: 138 | delay, node = heapq.heappop(min_heap) 139 | if node not in SPT: 140 | SPT[node] = delay 141 | for n, d in graph_neighbor[node]: 142 | if n not in SPT: 143 | heapq.heappush(min_heap, (d + delay, n)) 144 | 145 | return max(SPT.values()) if len(SPT) == N else -1 146 | ``` 147 | 148 | ### [cheapest-flights-within-k-stops](https://leetcode-cn.com/problems/cheapest-flights-within-k-stops/) 149 | 150 | - 在标准的单源最短路径问题上限制了路径的边数,因此需要同时维护当前 SPT 内每个结点最短路径的边数,当遇到边数更小的路径 (边权和可以更大) 时结点需要重新入堆,以更新后继在边数上限内没达到的结点。 151 | 152 | ```Python 153 | class Solution: 154 | def findCheapestPrice(self, n: int, flights: List[List[int]], src: int, dst: int, K: int) -> int: 155 | 156 | # construct graph 157 | graph_neighbor = collections.defaultdict(list) 158 | for s, e, p in flights: 159 | graph_neighbor[s].append((e, p)) 160 | 161 | # modified Dijkstra 162 | prices, steps = {}, {} 163 | min_heap = [(0, 0, src)] 164 | 165 | while len(min_heap) > 0: 166 | price, step, node = heapq.heappop(min_heap) 167 | 168 | if node == dst: # early return 169 | return price 170 | 171 | if node not in prices: 172 | prices[node] = price 173 | 174 | steps[node] = step 175 | if step <= K: 176 | step += 1 177 | for n, p in graph_neighbor[node]: 178 | if n not in prices or step < steps[n]: 179 | heapq.heappush(min_heap, (p + price, step, n)) 180 | 181 | return -1 182 | ``` 183 | 184 | ## 补充 185 | 186 | ## Bidrectional BFS 187 | 188 | 当求点对点的最短路径时,BFS遍历结点数目随路径长度呈指数增长,为缩小遍历结点数目可以考虑从起点 BFS 的同时从终点也做 BFS,当路径相遇时得到最短路径。 189 | 190 | ### [word-ladder](https://leetcode-cn.com/problems/word-ladder/) 191 | 192 | ```Python 193 | class Solution: 194 | def ladderLength(self, beginWord: str, endWord: str, wordList: List[str]) -> int: 195 | 196 | N, K = len(wordList), len(beginWord) 197 | 198 | find_end = False 199 | for i in range(N): 200 | if wordList[i] == endWord: 201 | find_end = True 202 | break 203 | 204 | if not find_end: 205 | return 0 206 | 207 | wordList.append(beginWord) 208 | N += 1 209 | 210 | # clustering nodes for efficiency compare to adjacent list 211 | cluster = collections.defaultdict(list) 212 | for i in range(N): 213 | node = wordList[i] 214 | for j in range(K): 215 | cluster[node[:j] + '*' + node[j + 1:]].append(node) 216 | 217 | # bidirectional BFS 218 | visited_start, visited_end = set([beginWord]), set([endWord]) 219 | bfs_start, bfs_end = collections.deque([beginWord]), collections.deque([endWord]) 220 | step = 2 221 | while bfs_start and bfs_end: 222 | 223 | # start 224 | num_level = len(bfs_start) 225 | while num_level > 0: 226 | node = bfs_start.popleft() 227 | for j in range(K): 228 | key = node[:j] + '*' + node[j + 1:] 229 | for n in cluster[key]: 230 | if n in visited_end: # if meet, route from start larger by 1 than route from end 231 | return step * 2 - 2 232 | if n not in visited_start: 233 | visited_start.add(n) 234 | bfs_start.append(n) 235 | num_level -= 1 236 | 237 | # end 238 | num_level = len(bfs_end) 239 | while num_level > 0: 240 | node = bfs_end.popleft() 241 | for j in range(K): 242 | key = node[:j] + '*' + node[j + 1:] 243 | for n in cluster[key]: 244 | if n in visited_start: # if meet, route from start equals route from end 245 | return step * 2 - 1 246 | if n not in visited_end: 247 | visited_end.add(n) 248 | bfs_end.append(n) 249 | num_level -= 1 250 | step += 1 251 | 252 | return 0 253 | ``` 254 | 255 | ## A* Algorithm 256 | 257 | 当需要求解有目标的最短路径问题时,BFS 或 Dijkstra's algorithm 可能会搜索过多冗余的其他目标从而降低搜索效率,此时可以考虑使用 A* algorithm。原理不展开,有兴趣可以自行搜索。实现上和 Dijkstra’s algorithm 非常相似,只是优先级需要加上一个到目标点距离的估值,这个估值严格小于等于真正的最短距离时保证得到最优解。当 A* algorithm 中的距离估值为 0 时 退化为 BFS 或 Dijkstra’s algorithm。 258 | 259 | ### [sliding-puzzle](https://leetcode-cn.com/problems/sliding-puzzle) 260 | 261 | - 方法 1:BFS。为了方便对比 A* 算法写成了与其相似的形式。 262 | 263 | ```Python 264 | class Solution: 265 | def slidingPuzzle(self, board: List[List[int]]) -> int: 266 | 267 | next_move = { 268 | 0: [1, 3], 269 | 1: [0, 2, 4], 270 | 2: [1, 5], 271 | 3: [0, 4], 272 | 4: [1, 3, 5], 273 | 5: [2, 4] 274 | } 275 | 276 | start = tuple(itertools.chain(*board)) 277 | target = (1, 2, 3, 4, 5, 0) 278 | target_wrong = (1, 2, 3, 5, 4, 0) 279 | 280 | SPT = set() 281 | bfs = collections.deque([(0, start, start.index(0))]) 282 | 283 | while bfs: 284 | step, state, idx0 = bfs.popleft() 285 | 286 | if state == target: 287 | return step 288 | 289 | if state == target_wrong: 290 | return -1 291 | 292 | if state not in SPT: 293 | SPT.add(state) 294 | 295 | for next_step in next_move[idx0]: 296 | next_state = list(state) 297 | next_state[idx0], next_state[next_step] = next_state[next_step], next_state[idx0] 298 | next_state = tuple(next_state) 299 | 300 | if next_state not in SPT: 301 | bfs.append((step + 1, next_state, next_step)) 302 | return -1 303 | ``` 304 | 305 | - 方法 2:A* algorithm 306 | 307 | ```Python 308 | class Solution: 309 | def slidingPuzzle(self, board: List[List[int]]) -> int: 310 | 311 | next_move = { 312 | 0: [1, 3], 313 | 1: [0, 2, 4], 314 | 2: [1, 5], 315 | 3: [0, 4], 316 | 4: [1, 3, 5], 317 | 5: [2, 4] 318 | } 319 | 320 | start = tuple(itertools.chain(*board)) 321 | target, target_idx = (1, 2, 3, 4, 5, 0), (5, 0, 1, 2, 3, 4) 322 | target_wrong = (1, 2, 3, 5, 4, 0) 323 | 324 | @functools.lru_cache(maxsize=None) 325 | def taxicab_dist(x, y): 326 | return abs(x // 3 - y // 3) + abs(x % 3 - y % 3) 327 | 328 | def taxicab_sum(state, t_idx): 329 | result = 0 330 | for i, num in enumerate(state): 331 | result += taxicab_dist(i, t_idx[num]) 332 | return result 333 | 334 | SPT = set() 335 | min_heap = [(0 + taxicab_sum(start, target_idx), 0, start, start.index(0))] 336 | 337 | while min_heap: 338 | cur_cost, step, state, idx0 = heapq.heappop(min_heap) 339 | 340 | if state == target: 341 | return step 342 | 343 | if state == target_wrong: 344 | return -1 345 | 346 | if state not in SPT: 347 | SPT.add(state) 348 | 349 | for next_step in next_move[idx0]: 350 | next_state = list(state) 351 | next_state[idx0], next_state[next_step] = next_state[next_step], next_state[idx0] 352 | next_state = tuple(next_state) 353 | next_cost = step + 1 + taxicab_sum(next_state, target_idx) 354 | 355 | if next_state not in SPT: 356 | heapq.heappush(min_heap, (next_cost, step + 1, next_state, next_step)) 357 | return -1 358 | ``` 359 | 360 | -------------------------------------------------------------------------------- /basic_algorithm/graph/topological_sorting.md: -------------------------------------------------------------------------------- 1 | # 拓扑排序 2 | 3 | 图的拓扑排序 (topological sorting) 一般用于给定一系列偏序关系,求一个全序关系的题目中。以元素为结点,以偏序关系为边构造有向图,然后应用拓扑排序算法即可得到全序关系。 4 | 5 | ### [course-schedule-ii](https://leetcode-cn.com/problems/course-schedule-ii/) 6 | 7 | > 给定课程的先修关系,求一个可行的修课顺序 8 | 9 | 非常经典的拓扑排序应用。下面给出 3 种实现方法,可以当做模板使用。 10 | 11 | - 方法 1:DFS 的递归实现 12 | 13 | ```Python 14 | NOT_VISITED = 0 15 | DISCOVERING = 1 16 | VISITED = 2 17 | 18 | class Solution: 19 | def findOrder(self, numCourses: int, prerequisites: List[List[int]]) -> List[int]: 20 | 21 | # construct graph 22 | graph_neighbor = collections.defaultdict(list) 23 | for course, pre in prerequisites: 24 | graph_neighbor[pre].append(course) 25 | 26 | # recursive postorder DFS for topological sort 27 | tsort_rev = [] 28 | status = [NOT_VISITED] * numCourses 29 | 30 | def dfs(course): 31 | status[course] = DISCOVERING 32 | for n in graph_neighbor[course]: 33 | if status[n] == DISCOVERING or (status[n] == NOT_VISITED and not dfs(n)): 34 | return False 35 | tsort_rev.append(course) 36 | status[course] = VISITED 37 | return True 38 | 39 | for course in range(numCourses): 40 | if status[course] == NOT_VISITED and not dfs(course): 41 | return [] 42 | 43 | return tsort_rev[::-1] 44 | ``` 45 | 46 | - 方法 2:DFS 的迭代实现 47 | 48 | ```Python 49 | NOT_VISITED = 0 50 | DISCOVERING = 1 51 | VISITED = 2 52 | 53 | class Solution: 54 | def findOrder(self, numCourses: int, prerequisites: List[List[int]]) -> List[int]: 55 | 56 | # construct graph 57 | graph_neighbor = collections.defaultdict(list) 58 | for course, pre in prerequisites: 59 | graph_neighbor[pre].append(course) 60 | 61 | # iterative postorder DFS for topological sort 62 | tsort_rev = [] 63 | status = [NOT_VISITED] * numCourses 64 | 65 | dfs = [] 66 | for course in range(numCourses): 67 | if status[course] == NOT_VISITED: 68 | dfs.append(course) 69 | status[course] = DISCOVERING 70 | 71 | while dfs: 72 | if graph_neighbor[dfs[-1]]: 73 | n = graph_neighbor[dfs[-1]].pop() 74 | if status[n] == DISCOVERING: 75 | return [] 76 | if status[n] == NOT_VISITED: 77 | dfs.append(n) 78 | status[n] = DISCOVERING 79 | else: 80 | tsort_rev.append(dfs.pop()) 81 | status[tsort_rev[-1]] = VISITED 82 | 83 | return tsort_rev[::-1] 84 | ``` 85 | 86 | - 方法 3:[Kahn's algorithm](https://en.wikipedia.org/wiki/Topological_sorting#Kahn's_algorithm) 87 | 88 | ```Python 89 | class Solution: 90 | def findOrder(self, numCourses: int, prerequisites: List[List[int]]) -> List[int]: 91 | 92 | # construct graph with indegree data 93 | graph_neighbor = collections.defaultdict(list) 94 | indegree = collections.defaultdict(int) 95 | 96 | for course, pre in prerequisites: 97 | graph_neighbor[pre].append(course) 98 | indegree[course] += 1 99 | 100 | # Kahn's algorithm 101 | src_cache = [] # can also use queue 102 | for i in range(numCourses): 103 | if indegree[i] == 0: 104 | src_cache.append(i) 105 | 106 | tsort = [] 107 | while src_cache: 108 | tsort.append(src_cache.pop()) 109 | for n in graph_neighbor[tsort[-1]]: 110 | indegree[n] -= 1 111 | if indegree[n] == 0: 112 | src_cache.append(n) 113 | 114 | return tsort if len(tsort) == numCourses else [] 115 | ``` 116 | 117 | ### [alien-dictionary](https://leetcode-cn.com/problems/alien-dictionary/) 118 | 119 | ```Python 120 | class Solution: 121 | def alienOrder(self, words: List[str]) -> str: 122 | 123 | N = len(words) 124 | 125 | if N == 0: 126 | return '' 127 | 128 | if N == 1: 129 | return words[0] 130 | 131 | # construct graph 132 | indegree = {c: 0 for word in words for c in word} 133 | graph = collections.defaultdict(list) 134 | 135 | for i in range(N - 1): 136 | first, second = words[i], words[i + 1] 137 | len_f, len_s = len(first), len(second) 138 | find_different = False 139 | for j in range(min(len_f, len_s)): 140 | f, s = first[j], second[j] 141 | if f != s: 142 | if s not in graph[f]: 143 | graph[f].append(s) 144 | indegree[s] += 1 145 | find_different = True 146 | break 147 | 148 | if not find_different and len_f > len_s: 149 | return '' 150 | 151 | tsort = [] 152 | src_cache = [c for c in indegree if indegree[c] == 0] 153 | 154 | while src_cache: 155 | tsort.append(src_cache.pop()) 156 | for n in graph[tsort[-1]]: 157 | indegree[n] -= 1 158 | if indegree[n] == 0: 159 | src_cache.append(n) 160 | 161 | return ''.join(tsort) if len(tsort) == len(indegree) else '' 162 | ``` 163 | 164 | ### [sequence-reconstruction](https://leetcode-cn.com/problems/sequence-reconstruction/) 165 | 166 | Kahn's algorithm 可以判断拓扑排序是否唯一。 167 | 168 | ```Python 169 | class Solution: 170 | def sequenceReconstruction(self, org: List[int], seqs: List[List[int]]) -> bool: 171 | 172 | N = len(org) 173 | inGraph = [False] * (N + 1) 174 | graph_set = collections.defaultdict(set) 175 | for seq in seqs: 176 | if seq: 177 | if seq[0] > N or seq[0] < 1: 178 | return False 179 | inGraph[seq[0]] = True 180 | for i in range(1, len(seq)): 181 | if seq[i] > N or seq[i] < 1: 182 | return False 183 | inGraph[seq[i]] = True 184 | graph_set[seq[i - 1]].add(seq[i]) 185 | 186 | indegree = collections.defaultdict(int) 187 | for node in graph_set: 188 | for n in graph_set[node]: 189 | indegree[n] += 1 190 | 191 | num_valid, count0, src = 0, -1, 0 192 | for i in range(1, N + 1): 193 | if inGraph[i] and indegree[i] == 0: 194 | count0 += 1 195 | src = i 196 | 197 | i = 0 198 | while count0 == i and src == org[i]: 199 | num_valid += 1 200 | for n in graph_set[src]: 201 | indegree[n] -= 1 202 | if indegree[n] == 0: 203 | count0 += 1 204 | src = n 205 | i += 1 206 | 207 | return num_valid == N 208 | ``` 209 | 210 | -------------------------------------------------------------------------------- /basic_algorithm/heapsort.py: -------------------------------------------------------------------------------- 1 | def heap_adjust(A, start=0, end=None): 2 | if end is None: 3 | end = len(A) 4 | 5 | while start is not None and start < end // 2: 6 | l, r = start * 2 + 1, start * 2 + 2 7 | swap = None 8 | 9 | if A[l] > A[start]: 10 | swap = l 11 | if r < end and A[r] > A[start] and (swap is None or A[r] > A[l]): 12 | swap = r 13 | 14 | if swap is not None: 15 | A[start], A[swap] = A[swap], A[start] 16 | 17 | start = swap 18 | 19 | return 20 | 21 | def heapsort(A): 22 | 23 | # construct max heap 24 | n = len(A) 25 | for i in range(n // 2 - 1, -1, -1): 26 | heap_adjust(A, i) 27 | 28 | # sort 29 | for i in range(n - 1, 0, -1): 30 | A[0], A[i] = A[i], A[0] 31 | heap_adjust(A, end=i) 32 | 33 | return A 34 | 35 | # test 36 | if __name__ == '__main__': 37 | a = [7, 6, 8, 5, 2, 1, 3, 4, 0, 9, 10] 38 | print(a) 39 | print(heapsort(a)) -------------------------------------------------------------------------------- /basic_algorithm/mergesort.py: -------------------------------------------------------------------------------- 1 | def merge(A, B): 2 | C = [] 3 | i, j = 0, 0 4 | while i < len(A) and j < len(B): 5 | if A[i] <= B[j]: 6 | C.append(A[i]) 7 | i += 1 8 | else: 9 | C.append(B[j]) 10 | j += 1 11 | 12 | if i < len(A): 13 | C += A[i:] 14 | 15 | if j < len(B): 16 | C += B[j:] 17 | 18 | return C 19 | 20 | def mergsort(A): 21 | n = len(A) 22 | if n < 2: 23 | return A[:] 24 | 25 | left = mergsort(A[:n // 2]) 26 | right = mergsort(A[n // 2:]) 27 | 28 | return merge(left, right) 29 | 30 | if __name__ == '__main__': 31 | a = [7, 6, 8, 5, 2, 1, 3, 4, 0, 9, 10] 32 | print(a) 33 | print(mergsort(a)) -------------------------------------------------------------------------------- /basic_algorithm/quicksort.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | def partition(nums, left, right): 4 | if left >= right: 5 | return 6 | 7 | pivot_idx = random.randint(left, right) 8 | pivot = nums[pivot_idx] 9 | 10 | nums[right], nums[pivot_idx] = nums[pivot_idx], nums[right] 11 | 12 | partition_idx = left 13 | for i in range(left, right): 14 | if nums[i] < pivot: 15 | nums[partition_idx], nums[i] = nums[i], nums[partition_idx] 16 | partition_idx += 1 17 | 18 | nums[right], nums[partition_idx] = nums[partition_idx], nums[right] 19 | 20 | partition(nums, partition_idx + 1, right) 21 | partition(nums, left, partition_idx - 1) 22 | 23 | return 24 | 25 | def quicksort(A): 26 | partition(A, 0, len(A) - 1) 27 | return A 28 | 29 | if __name__ == '__main__': 30 | a = [7, 6, 8, 5, 2, 1, 3, 4, 0, 9, 10] 31 | print(a) 32 | print(quicksort(a)) -------------------------------------------------------------------------------- /basic_algorithm/sort.md: -------------------------------------------------------------------------------- 1 | # 排序 2 | 3 | ## 常考排序 4 | 5 | ### 快速排序 6 | 7 | ```Python 8 | import random 9 | 10 | def partition(nums, left, right): 11 | if left >= right: 12 | return 13 | 14 | pivot_idx = random.randint(left, right) 15 | pivot = nums[pivot_idx] 16 | 17 | nums[right], nums[pivot_idx] = nums[pivot_idx], nums[right] 18 | 19 | partition_idx = left 20 | for i in range(left, right): 21 | if nums[i] < pivot: 22 | nums[partition_idx], nums[i] = nums[i], nums[partition_idx] 23 | partition_idx += 1 24 | 25 | nums[right], nums[partition_idx] = nums[partition_idx], nums[right] 26 | 27 | partition(nums, partition_idx + 1, right) 28 | partition(nums, left, partition_idx - 1) 29 | 30 | return 31 | 32 | def quicksort(A): 33 | partition(A, 0, len(A) - 1) 34 | return A 35 | 36 | if __name__ == '__main__': 37 | a = [7, 6, 8, 5, 2, 1, 3, 4, 0, 9, 10] 38 | print(a) 39 | print(quicksort(a)) 40 | ``` 41 | 42 | ### 归并排序 43 | 44 | ```Python 45 | def merge(A, B): 46 | C = [] 47 | i, j = 0, 0 48 | while i < len(A) and j < len(B): 49 | if A[i] <= B[j]: 50 | C.append(A[i]) 51 | i += 1 52 | else: 53 | C.append(B[j]) 54 | j += 1 55 | 56 | if i < len(A): 57 | C += A[i:] 58 | 59 | if j < len(B): 60 | C += B[j:] 61 | 62 | return C 63 | 64 | def mergsort(A): 65 | n = len(A) 66 | if n < 2: 67 | return A[:] 68 | 69 | left = mergsort(A[:n // 2]) 70 | right = mergsort(A[n // 2:]) 71 | 72 | return merge(left, right) 73 | 74 | if __name__ == '__main__': 75 | a = [7, 6, 8, 5, 2, 1, 3, 4, 0, 9, 10] 76 | print(a) 77 | print(mergsort(a)) 78 | ``` 79 | 80 | ### 堆排序 81 | 82 | 用数组表示的完美二叉树 complete binary tree 83 | 84 | > 完美二叉树 VS 其他二叉树 85 | 86 | ![image.png](https://img.fuiboom.com/img/tree_type.png) 87 | 88 | [动画展示](https://www.bilibili.com/video/av18980178/) 89 | 90 | ![image.png](https://img.fuiboom.com/img/heap.png) 91 | 92 | 核心代码 93 | 94 | ```Python 95 | def heap_adjust(A, start=0, end=None): 96 | if end is None: 97 | end = len(A) 98 | 99 | while start is not None and start < end // 2: 100 | l, r = start * 2 + 1, start * 2 + 2 101 | swap = None 102 | 103 | if A[l] > A[start]: 104 | swap = l 105 | if r < end and A[r] > A[start] and (swap is None or A[r] > A[l]): 106 | swap = r 107 | 108 | if swap is not None: 109 | A[start], A[swap] = A[swap], A[start] 110 | 111 | start = swap 112 | 113 | return 114 | 115 | def heapsort(A): 116 | 117 | # construct max heap 118 | n = len(A) 119 | for i in range(n // 2 - 1, -1, -1): 120 | heap_adjust(A, i) 121 | 122 | # sort 123 | for i in range(n - 1, 0, -1): 124 | A[0], A[i] = A[i], A[0] 125 | heap_adjust(A, end=i) 126 | 127 | return A 128 | 129 | # test 130 | if __name__ == '__main__': 131 | a = [7, 6, 8, 5, 2, 1, 3, 4, 0, 9, 10] 132 | print(a) 133 | print(heapsort(a)) 134 | ``` 135 | 136 | ## 题目 137 | 138 | ### [kth-largest-element-in-an-array](https://leetcode-cn.com/problems/kth-largest-element-in-an-array/) 139 | 140 | - 思路 1: sort 后取第 k 个,最简单直接,复杂度 O(N log N) 代码略 141 | 142 | - 思路 2: 使用最小堆,复杂度 O(N log k) 143 | 144 | ```Python 145 | class Solution: 146 | def findKthLargest(self, nums: List[int], k: int) -> int: 147 | # note that in practice there is a more efficient python build-in function heapq.nlargest(k, nums) 148 | min_heap = [] 149 | 150 | for num in nums: 151 | if len(min_heap) < k: 152 | heapq.heappush(min_heap, num) 153 | else: 154 | if num > min_heap[0]: 155 | heapq.heappushpop(min_heap, num) 156 | 157 | return min_heap[0] 158 | ``` 159 | 160 | - 思路 3: [Quick select](https://en.wikipedia.org/wiki/Quickselect),方式类似于快排,每次 partition 后检查 pivot 是否为第 k 个元素,如果是则直接返回,如果比 k 大,则继续 partition 小于 pivot 的元素,如果比 k 小则继续 partition 大于 pivot 的元素。相较于快排,quick select 每次只需 partition 一侧,因此平均复杂度为 O(N)。 161 | 162 | ```Python 163 | class Solution: 164 | def findKthLargest(self, nums: List[int], k: int) -> int: 165 | 166 | k -= 1 # 0-based index 167 | 168 | def partition(left, right): 169 | pivot_idx = random.randint(left, right) 170 | pivot = nums[pivot_idx] 171 | 172 | nums[right], nums[pivot_idx] = nums[pivot_idx], nums[right] 173 | 174 | partition_idx = left 175 | for i in range(left, right): 176 | if nums[i] > pivot: 177 | nums[partition_idx], nums[i] = nums[i], nums[partition_idx] 178 | partition_idx += 1 179 | 180 | nums[right], nums[partition_idx] = nums[partition_idx], nums[right] 181 | 182 | return partition_idx 183 | 184 | left, right = 0, len(nums) - 1 185 | while True: 186 | partition_idx = partition(left, right) 187 | if partition_idx == k: 188 | return nums[k] 189 | elif partition_idx < k: 190 | left = partition_idx + 1 191 | else: 192 | right = partition_idx - 1 193 | ``` 194 | 195 | 196 | 197 | ## 参考 198 | 199 | [十大经典排序](https://www.cnblogs.com/onepixel/p/7674659.html) 200 | 201 | [二叉堆](https://labuladong.gitbook.io/algo/shu-ju-jie-gou-xi-lie/er-cha-dui-xiang-jie-shi-xian-you-xian-ji-dui-lie) 202 | 203 | ## 练习 204 | 205 | - [ ] 手写快排、归并、堆排序 206 | -------------------------------------------------------------------------------- /data_structure/binary_op.md: -------------------------------------------------------------------------------- 1 | # 二进制 2 | 3 | ## 常见二进制操作 4 | 5 | ### 基本操作 6 | 7 | a=0^a=a^0 8 | 9 | 0=a^a 10 | 11 | 由上面两个推导出:a=a^b^b 12 | 13 | ### 交换两个数 14 | 15 | a=a^b 16 | 17 | b=a^b 18 | 19 | a=a^b 20 | 21 | ### 移除最后一个 1 22 | 23 | a=n&(n-1) 24 | 25 | ### 获取最后一个 1 26 | 27 | diff=(n&(n-1))^n 28 | 29 | ## 常见题目 30 | 31 | ### [single-number](https://leetcode-cn.com/problems/single-number/) 32 | 33 | > 给定一个**非空**整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。 34 | 35 | ```Python 36 | class Solution: 37 | def singleNumber(self, nums: List[int]) -> int: 38 | 39 | out = 0 40 | for num in nums: 41 | out ^= num 42 | 43 | return out 44 | ``` 45 | 46 | ### [single-number-ii](https://leetcode-cn.com/problems/single-number-ii/) 47 | 48 | > 给定一个**非空**整数数组,除了某个元素只出现一次以外,其余每个元素均出现了三次。找出那个只出现了一次的元素。 49 | 50 | ```Python 51 | class Solution: 52 | def singleNumber(self, nums: List[int]) -> int: 53 | seen_once = seen_twice = 0 54 | 55 | for num in nums: 56 | seen_once = ~seen_twice & (seen_once ^ num) 57 | seen_twice = ~seen_once & (seen_twice ^ num) 58 | 59 | return seen_once 60 | ``` 61 | 62 | ### [single-number-iii](https://leetcode-cn.com/problems/single-number-iii/) 63 | 64 | > 给定一个整数数组  `nums`,其中恰好有两个元素只出现一次,其余所有元素均出现两次。 找出只出现一次的那两个元素。 65 | 66 | ```Python 67 | class Solution: 68 | def singleNumber(self, nums: int) -> List[int]: 69 | # difference between two numbers (x and y) which were seen only once 70 | bitmask = 0 71 | for num in nums: 72 | bitmask ^= num 73 | 74 | # rightmost 1-bit diff between x and y 75 | diff = bitmask & (-bitmask) 76 | 77 | x = 0 78 | for num in nums: 79 | # bitmask which will contain only x 80 | if num & diff: 81 | x ^= num 82 | 83 | return [x, bitmask^x] 84 | ``` 85 | 86 | ### [number-of-1-bits](https://leetcode-cn.com/problems/number-of-1-bits/) 87 | 88 | > 编写一个函数,输入是一个无符号整数,返回其二进制表达式中数字位数为 ‘1’  的个数(也被称为[汉明重量](https://baike.baidu.com/item/%E6%B1%89%E6%98%8E%E9%87%8D%E9%87%8F))。 89 | 90 | ```Python 91 | class Solution: 92 | def hammingWeight(self, n: int) -> int: 93 | num_ones = 0 94 | while n > 0: 95 | num_ones += 1 96 | n &= n - 1 97 | return num_ones 98 | ``` 99 | 100 | ### [counting-bits](https://leetcode-cn.com/problems/counting-bits/) 101 | 102 | > 给定一个非负整数  **num**。对于  0 ≤ i ≤ num  范围中的每个数字  i ,计算其二进制数中的 1 的数目并将它们作为数组返回。 103 | 104 | - 思路:利用上一题的解法容易想到 O(nk) 的解法,k 为位数。但是实际上可以利用动态规划将复杂度降到 O(n),想法其实也很简单,即当前数的 1 个数等于比它少一个 1 的数的结果加 1。下面给出三种 DP 解法 105 | 106 | ```Python 107 | # x <- x // 2 108 | class Solution: 109 | def countBits(self, num: int) -> List[int]: 110 | 111 | num_ones = [0] * (num + 1) 112 | 113 | for i in range(1, num + 1): 114 | num_ones[i] = num_ones[i >> 1] + (i & 1) # 注意位运算的优先级 115 | 116 | return num_ones 117 | ``` 118 | 119 | ```Python 120 | # x <- x minus right most 1 121 | class Solution: 122 | def countBits(self, num: int) -> List[int]: 123 | 124 | num_ones = [0] * (num + 1) 125 | 126 | for i in range(1, num + 1): 127 | num_ones[i] = num_ones[i & (i - 1)] + 1 128 | 129 | return num_ones 130 | ``` 131 | 132 | ```Python 133 | # x <- x minus left most 1 134 | class Solution: 135 | def countBits(self, num: int) -> List[int]: 136 | 137 | num_ones = [0] * (num + 1) 138 | 139 | left_most = 1 140 | 141 | while left_most <= num: 142 | for i in range(left_most): 143 | if i + left_most > num: 144 | break 145 | num_ones[i + left_most] = num_ones[i] + 1 146 | left_most <<= 1 147 | 148 | return num_ones 149 | ``` 150 | 151 | ### [reverse-bits](https://leetcode-cn.com/problems/reverse-bits/) 152 | 153 | > 颠倒给定的 32 位无符号整数的二进制位。 154 | 155 | 思路:简单想法依次颠倒即可。更高级的想法是考虑到处理超长比特串时可能出现重复的pattern,此时如果使用 cache 记录出现过的 pattern 并在重复出现时直接调用结果可以节约时间复杂度,具体可以参考 leetcode 给出的解法。 156 | 157 | ```Python 158 | import functools 159 | 160 | class Solution: 161 | def reverseBits(self, n): 162 | ret, power = 0, 24 163 | while n: 164 | ret += self.reverseByte(n & 0xff) << power 165 | n = n >> 8 166 | power -= 8 167 | return ret 168 | 169 | # memoization with decorator 170 | @functools.lru_cache(maxsize=256) 171 | def reverseByte(self, byte): 172 | return (byte * 0x0202020202 & 0x010884422010) % 1023 173 | ``` 174 | 175 | ### [bitwise-and-of-numbers-range](https://leetcode-cn.com/problems/bitwise-and-of-numbers-range/) 176 | 177 | > 给定范围 [m, n],其中 0 <= m <= n <= 2147483647,返回此范围内所有数字的按位与(包含 m, n 两端点)。 178 | 179 | 思路:直接从 m 到 n 遍历一遍显然不是最优。一个性质,如果 m 不等于 n,则结果第一位一定是 0 (中间必定包含一个偶数)。利用这个性质,类似的将 m 和 n 右移后我们也可以判断第三位、第四位等等,免去了遍历的时间复杂度。 180 | 181 | ```Python 182 | class Solution: 183 | def rangeBitwiseAnd(self, m: int, n: int) -> int: 184 | 185 | shift = 0 186 | while m < n: 187 | shift += 1 188 | m >>= 1 189 | n >>= 1 190 | 191 | return m << shift 192 | ``` 193 | 194 | ## 练习 195 | 196 | - [ ] [single-number](https://leetcode-cn.com/problems/single-number/) 197 | - [ ] [single-number-ii](https://leetcode-cn.com/problems/single-number-ii/) 198 | - [ ] [single-number-iii](https://leetcode-cn.com/problems/single-number-iii/) 199 | - [ ] [number-of-1-bits](https://leetcode-cn.com/problems/number-of-1-bits/) 200 | - [ ] [counting-bits](https://leetcode-cn.com/problems/counting-bits/) 201 | - [ ] [reverse-bits](https://leetcode-cn.com/problems/reverse-bits/) 202 | -------------------------------------------------------------------------------- /data_structure/binary_tree.md: -------------------------------------------------------------------------------- 1 | # 二叉树 2 | 3 | ## 知识点 4 | 5 | ### 二叉树遍历 6 | 7 | **前序遍历**:**先访问根节点**,再前序遍历左子树,再前序遍历右子树 8 | **中序遍历**:先中序遍历左子树,**再访问根节点**,再中序遍历右子树 9 | **后序遍历**:先后序遍历左子树,再后序遍历右子树,**再访问根节点** 10 | 11 | 注意点 12 | 13 | - 以根访问顺序决定是什么遍历 14 | - 左子树都是优先右子树 15 | 16 | #### 递归模板 17 | 18 | - 递归实现二叉树遍历非常简单,不同顺序区别仅在于访问父结点顺序 19 | 20 | ```Python 21 | def preorder_rec(root): 22 | if root is None: 23 | return 24 | visit(root) 25 | preorder_rec(root.left) 26 | preorder_rec(root.right) 27 | return 28 | 29 | def inorder_rec(root): 30 | if root is None: 31 | return 32 | inorder_rec(root.left) 33 | visit(root) 34 | inorder_rec(root.right) 35 | return 36 | 37 | def postorder_rec(root): 38 | if root is None: 39 | return 40 | postorder_rec(root.left) 41 | postorder_rec(root.right) 42 | visit(root) 43 | return 44 | ``` 45 | 46 | #### [前序非递归](https://leetcode-cn.com/problems/binary-tree-preorder-traversal/) 47 | 48 | - 本质上是图的DFS的一个特例,因此可以用栈来实现 49 | 50 | ```Python 51 | class Solution: 52 | def preorderTraversal(self, root: TreeNode) -> List[int]: 53 | 54 | preorder = [] 55 | if root is None: 56 | return preorder 57 | 58 | s = [root] 59 | while len(s) > 0: 60 | node = s.pop() 61 | preorder.append(node.val) 62 | if node.right is not None: 63 | s.append(node.right) 64 | if node.left is not None: 65 | s.append(node.left) 66 | 67 | return preorder 68 | ``` 69 | 70 | #### [中序非递归](https://leetcode-cn.com/problems/binary-tree-inorder-traversal/) 71 | 72 | ```Python 73 | class Solution: 74 | def inorderTraversal(self, root: TreeNode) -> List[int]: 75 | s, inorder = [], [] 76 | node = root 77 | while len(s) > 0 or node is not None: 78 | if node is not None: 79 | s.append(node) 80 | node = node.left 81 | else: 82 | node = s.pop() 83 | inorder.append(node.val) 84 | node = node.right 85 | return inorder 86 | ``` 87 | 88 | #### [后序非递归](https://leetcode-cn.com/problems/binary-tree-postorder-traversal/) 89 | 90 | ```Python 91 | class Solution: 92 | def postorderTraversal(self, root: TreeNode) -> List[int]: 93 | 94 | s, postorder = [], [] 95 | node, last_visit = root, None 96 | 97 | while len(s) > 0 or node is not None: 98 | if node is not None: 99 | s.append(node) 100 | node = node.left 101 | else: 102 | peek = s[-1] 103 | if peek.right is not None and last_visit != peek.right: 104 | node = peek.right 105 | else: 106 | last_visit = s.pop() 107 | postorder.append(last_visit.val) 108 | 109 | 110 | return postorder 111 | ``` 112 | 113 | 注意点 114 | 115 | - 核心就是:根节点必须在右节点弹出之后,再弹出 116 | 117 | DFS 深度搜索-从下向上(分治法) 118 | 119 | ```Python 120 | class Solution: 121 | def preorderTraversal(self, root: TreeNode) -> List[int]: 122 | 123 | if root is None: 124 | return [] 125 | 126 | left_result = self.preorderTraversal(root.left) 127 | right_result = self.preorderTraversal(root.right) 128 | 129 | return [root.val] + left_result + right_result 130 | ``` 131 | 132 | 注意点: 133 | 134 | > DFS 深度搜索(从上到下) 和分治法区别:前者一般将最终结果通过指针参数传入,后者一般递归返回结果最后合并 135 | 136 | #### [BFS 层次遍历](https://leetcode-cn.com/problems/binary-tree-level-order-traversal/) 137 | 138 | ```Python 139 | class Solution: 140 | def levelOrder(self, root: TreeNode) -> List[List[int]]: 141 | 142 | levels = [] 143 | if root is None: 144 | return levels 145 | 146 | bfs = collections.deque([root]) 147 | 148 | while len(bfs) > 0: 149 | levels.append([]) 150 | 151 | level_size = len(bfs) 152 | for _ in range(level_size): 153 | node = bfs.popleft() 154 | levels[-1].append(node.val) 155 | 156 | if node.left is not None: 157 | bfs.append(node.left) 158 | if node.right is not None: 159 | bfs.append(node.right) 160 | 161 | return levels 162 | ``` 163 | 164 | ### 分治法应用 165 | 166 | 先分别处理局部,再合并结果 167 | 168 | 适用场景 169 | 170 | - 快速排序 171 | - 归并排序 172 | - 二叉树相关问题 173 | 174 | 分治法模板 175 | 176 | - 递归返回条件 177 | - 分段处理 178 | - 合并结果 179 | 180 | ## 常见题目示例 181 | 182 | ### [maximum-depth-of-binary-tree](https://leetcode-cn.com/problems/maximum-depth-of-binary-tree/) 183 | 184 | > 给定一个二叉树,找出其最大深度。 185 | 186 | - 思路 1:分治法 187 | 188 | ```Python 189 | class Solution: 190 | def maxDepth(self, root: TreeNode) -> int: 191 | 192 | if root is None: 193 | return 0 194 | 195 | return 1 + max(self.maxDepth(root.left), self.maxDepth(root.right)) 196 | ``` 197 | 198 | - 思路 2:层序遍历 199 | 200 | ```Python 201 | class Solution: 202 | def maxDepth(self, root: TreeNode) -> List[List[int]]: 203 | 204 | depth = 0 205 | if root is None: 206 | return depth 207 | 208 | bfs = collections.deque([root]) 209 | 210 | while len(bfs) > 0: 211 | depth += 1 212 | level_size = len(bfs) 213 | for _ in range(level_size): 214 | node = bfs.popleft() 215 | if node.left is not None: 216 | bfs.append(node.left) 217 | if node.right is not None: 218 | bfs.append(node.right) 219 | 220 | return depth 221 | ``` 222 | 223 | ### [balanced-binary-tree](https://leetcode-cn.com/problems/balanced-binary-tree/) 224 | 225 | > 给定一个二叉树,判断它是否是高度平衡的二叉树。 226 | 227 | - 思路 1:分治法,左边平衡 && 右边平衡 && 左右两边高度 <= 1, 228 | 229 | ```Python 230 | class Solution: 231 | def isBalanced(self, root: TreeNode) -> bool: 232 | 233 | def depth(root): 234 | 235 | if root is None: 236 | return 0, True 237 | 238 | dl, bl = depth(root.left) 239 | dr, br = depth(root.right) 240 | 241 | return max(dl, dr) + 1, bl and br and abs(dl - dr) < 2 242 | 243 | _, out = depth(root) 244 | 245 | return out 246 | ``` 247 | 248 | - 思路 2:使用后序遍历实现分治法的迭代版本 249 | 250 | ```Python 251 | class Solution: 252 | def isBalanced(self, root: TreeNode) -> bool: 253 | 254 | s = [[TreeNode(), -1, -1]] 255 | node, last = root, None 256 | while len(s) > 1 or node is not None: 257 | if node is not None: 258 | s.append([node, -1, -1]) 259 | node = node.left 260 | if node is None: 261 | s[-1][1] = 0 262 | else: 263 | peek = s[-1][0] 264 | if peek.right is not None and last != peek.right: 265 | node = peek.right 266 | else: 267 | if peek.right is None: 268 | s[-1][2] = 0 269 | last, dl, dr = s.pop() 270 | if abs(dl - dr) > 1: 271 | return False 272 | d = max(dl, dr) + 1 273 | if s[-1][1] == -1: 274 | s[-1][1] = d 275 | else: 276 | s[-1][2] = d 277 | 278 | return True 279 | ``` 280 | 281 | ### [binary-tree-maximum-path-sum](https://leetcode-cn.com/problems/binary-tree-maximum-path-sum/) 282 | 283 | > 给定一个**非空**二叉树,返回其最大路径和。 284 | 285 | - 思路:分治法。最大路径的可能情况:左子树的最大路径,右子树的最大路径,或通过根结点的最大路径。其中通过根结点的最大路径值等于以左子树根结点为端点的最大路径值加以右子树根结点为端点的最大路径值再加上根结点值,这里还要考虑有负值的情况即负值路径需要丢弃不取。 286 | 287 | ```Python 288 | class Solution: 289 | def maxPathSum(self, root: TreeNode) -> int: 290 | 291 | self.maxPath = float('-inf') 292 | 293 | def largest_path_ends_at(node): 294 | if node is None: 295 | return float('-inf') 296 | 297 | e_l = largest_path_ends_at(node.left) 298 | e_r = largest_path_ends_at(node.right) 299 | 300 | self.maxPath = max(self.maxPath, node.val + max(0, e_l) + max(0, e_r), e_l, e_r) 301 | 302 | return node.val + max(e_l, e_r, 0) 303 | 304 | largest_path_ends_at(root) 305 | return self.maxPath 306 | ``` 307 | 308 | ### [lowest-common-ancestor-of-a-binary-tree](https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-tree/) 309 | 310 | > 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。 311 | 312 | - 思路:分治法,有左子树的公共祖先或者有右子树的公共祖先,就返回子树的祖先,否则返回根节点 313 | 314 | ```Python 315 | class Solution: 316 | def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode': 317 | 318 | if root is None: 319 | return None 320 | 321 | if root == p or root == q: 322 | return root 323 | 324 | left = self.lowestCommonAncestor(root.left, p, q) 325 | right = self.lowestCommonAncestor(root.right, p, q) 326 | 327 | if left is not None and right is not None: 328 | return root 329 | elif left is not None: 330 | return left 331 | elif right is not None: 332 | return right 333 | else: 334 | return None 335 | ``` 336 | 337 | ### BFS 层次应用 338 | 339 | ### [binary-tree-zigzag-level-order-traversal](https://leetcode-cn.com/problems/binary-tree-zigzag-level-order-traversal/) 340 | 341 | > 给定一个二叉树,返回其节点值的锯齿形层次遍历。Z 字形遍历 342 | 343 | - 思路:在BFS迭代模板上改用双端队列控制输出顺序 344 | 345 | ```Python 346 | class Solution: 347 | def zigzagLevelOrder(self, root: TreeNode) -> List[List[int]]: 348 | 349 | levels = [] 350 | if root is None: 351 | return levels 352 | 353 | s = collections.deque([root]) 354 | 355 | start_from_left = True 356 | while len(s) > 0: 357 | levels.append([]) 358 | level_size = len(s) 359 | 360 | if start_from_left: 361 | for _ in range(level_size): 362 | node = s.popleft() 363 | levels[-1].append(node.val) 364 | if node.left is not None: 365 | s.append(node.left) 366 | if node.right is not None: 367 | s.append(node.right) 368 | else: 369 | for _ in range(level_size): 370 | node = s.pop() 371 | levels[-1].append(node.val) 372 | if node.right is not None: 373 | s.appendleft(node.right) 374 | if node.left is not None: 375 | s.appendleft(node.left) 376 | 377 | start_from_left = not start_from_left 378 | 379 | 380 | return levels 381 | ``` 382 | 383 | ### 二叉搜索树应用 384 | 385 | ### [validate-binary-search-tree](https://leetcode-cn.com/problems/validate-binary-search-tree/) 386 | 387 | > 给定一个二叉树,判断其是否是一个有效的二叉搜索树。 388 | 389 | - 思路 1:中序遍历后检查输出是否有序,缺点是如果不平衡无法提前返回结果, 代码略 390 | 391 | - 思路 2:分治法,一个二叉树为合法的二叉搜索树当且仅当左右子树为合法二叉搜索树且根结点值大于右子树最小值小于左子树最大值。缺点是若不用迭代形式实现则无法提前返回,而迭代实现右比较复杂。 392 | 393 | ```Python 394 | class Solution: 395 | def isValidBST(self, root: TreeNode) -> bool: 396 | 397 | if root is None: return True 398 | 399 | def valid_min_max(node): 400 | 401 | isValid = True 402 | if node.left is not None: 403 | l_isValid, l_min, l_max = valid_min_max(node.left) 404 | isValid = isValid and node.val > l_max 405 | else: 406 | l_isValid, l_min = True, node.val 407 | 408 | if node.right is not None: 409 | r_isValid, r_min, r_max = valid_min_max(node.right) 410 | isValid = isValid and node.val < r_min 411 | else: 412 | r_isValid, r_max = True, node.val 413 | 414 | 415 | return l_isValid and r_isValid and isValid, l_min, r_max 416 | 417 | return valid_min_max(root)[0] 418 | ``` 419 | 420 | - 思路 3:利用二叉搜索树的性质,根结点为左子树的右边界,右子树的左边界,使用先序遍历自顶向下更新左右子树的边界并检查是否合法,迭代版本实现简单且可以提前返回结果。 421 | 422 | ```Python 423 | class Solution: 424 | def isValidBST(self, root: TreeNode) -> bool: 425 | 426 | if root is None: 427 | return True 428 | 429 | s = [(root, float('-inf'), float('inf'))] 430 | while len(s) > 0: 431 | node, low, up = s.pop() 432 | if node.left is not None: 433 | if node.left.val <= low or node.left.val >= node.val: 434 | return False 435 | s.append((node.left, low, node.val)) 436 | if node.right is not None: 437 | if node.right.val <= node.val or node.right.val >= up: 438 | return False 439 | s.append((node.right, node.val, up)) 440 | return True 441 | ``` 442 | 443 | #### [insert-into-a-binary-search-tree](https://leetcode-cn.com/problems/insert-into-a-binary-search-tree/) 444 | 445 | > 给定二叉搜索树(BST)的根节点和要插入树中的值,将值插入二叉搜索树。 返回插入后二叉搜索树的根节点。 446 | 447 | - 思路:如果只是为了完成任务则找到最后一个叶子节点满足插入条件即可。但此题深挖可以涉及到如何插入并维持平衡二叉搜索树的问题,并不适合初学者。 448 | 449 | ```Python 450 | class Solution: 451 | def insertIntoBST(self, root: TreeNode, val: int) -> TreeNode: 452 | 453 | if root is None: 454 | return TreeNode(val) 455 | 456 | node = root 457 | while True: 458 | if val > node.val: 459 | if node.right is None: 460 | node.right = TreeNode(val) 461 | return root 462 | else: 463 | node = node.right 464 | else: 465 | if node.left is None: 466 | node.left = TreeNode(val) 467 | return root 468 | else: 469 | node = node.left 470 | ``` 471 | 472 | ## 总结 473 | 474 | - 掌握二叉树递归与非递归遍历 475 | - 理解 DFS 前序遍历与分治法 476 | - 理解 BFS 层次遍历 477 | 478 | ## 练习 479 | 480 | - [ ] [maximum-depth-of-binary-tree](https://leetcode-cn.com/problems/maximum-depth-of-binary-tree/) 481 | - [ ] [balanced-binary-tree](https://leetcode-cn.com/problems/balanced-binary-tree/) 482 | - [ ] [binary-tree-maximum-path-sum](https://leetcode-cn.com/problems/binary-tree-maximum-path-sum/) 483 | - [ ] [lowest-common-ancestor-of-a-binary-tree](https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-tree/) 484 | - [ ] [binary-tree-level-order-traversal](https://leetcode-cn.com/problems/binary-tree-level-order-traversal/) 485 | - [ ] [binary-tree-level-order-traversal-ii](https://leetcode-cn.com/problems/binary-tree-level-order-traversal-ii/) 486 | - [ ] [binary-tree-zigzag-level-order-traversal](https://leetcode-cn.com/problems/binary-tree-zigzag-level-order-traversal/) 487 | - [ ] [validate-binary-search-tree](https://leetcode-cn.com/problems/validate-binary-search-tree/) 488 | - [ ] [insert-into-a-binary-search-tree](https://leetcode-cn.com/problems/insert-into-a-binary-search-tree/) 489 | -------------------------------------------------------------------------------- /data_structure/heap.md: -------------------------------------------------------------------------------- 1 | # 优先级队列 (堆) 2 | 3 | 用到优先级队列 (priority queue) 或堆 (heap) 的题一般需要维护一个动态更新的池,元素会被频繁加入到池中或从池中被取走,每次取走的元素为池中优先级最高的元素 (可以简单理解为最大或者最小)。用堆来实现优先级队列是效率非常高的方法,加入或取出都只需要 O(log N) 的复杂度。 4 | 5 | ## Kth largest/smallest 6 | 7 | 找数据中第 K 个最大/最小数据是堆的一个典型应用。以找最大为例,遍历数据时,使用一个最小堆来维护当前最大的 K 个数据,堆顶数据为这 K 个数据中最小,即是你想要的第 k 个最大数据。每检查一个新数据,判断是否大于堆顶,若大于,说明堆顶数据小于了 K 个值,不是我们想找的第 K 个最大,则将新数据 push 进堆并 pop 掉堆顶,若小于则不操作,这样当遍历完全部数据后堆顶即为想要的结果。找最小时换成最大堆即可。 8 | 9 | ### [kth-largest-element-in-a-stream](https://leetcode-cn.com/problems/kth-largest-element-in-a-stream/) 10 | 11 | > 设计一个找到数据流中第K大元素的类。 12 | 13 | ```Python 14 | class KthLargest: 15 | 16 | def __init__(self, k: int, nums: List[int]): 17 | self.K = k 18 | self.min_heap = [] 19 | for num in nums: 20 | if len(self.min_heap) < self.K: 21 | heapq.heappush(self.min_heap, num) 22 | elif num > self.min_heap[0]: 23 | heapq.heappushpop(self.min_heap, num) 24 | 25 | def add(self, val: int) -> int: 26 | if len(self.min_heap) < self.K: 27 | heapq.heappush(self.min_heap, val) 28 | elif val > self.min_heap[0]: 29 | heapq.heappushpop(self.min_heap, val) 30 | 31 | return self.min_heap[0] 32 | ``` 33 | 34 | ### [kth-smallest-element-in-a-sorted-matrix](https://leetcode-cn.com/problems/kth-smallest-element-in-a-sorted-matrix/) 35 | 36 | > 给定一个 n x n 矩阵,其中每行和每列元素均按升序排序,找到矩阵中第 k 小的元素。 37 | 38 | - 此题使用 heap 来做并不是最优做法,相当于 N 个 sorted list 里找第 k 个最小,列有序的条件没有充分利用,但是却是比较容易想且比较通用的做法。 39 | 40 | ```Python 41 | class Solution: 42 | def kthSmallest(self, matrix: List[List[int]], k: int) -> int: 43 | 44 | N = len(matrix) 45 | 46 | min_heap = [] 47 | for i in range(min(k, N)): # 这里用了一点列有序的性质,第k个最小只可能在前k行中(k行以后的数至少大于了k个数) 48 | min_heap.append((matrix[i][0], i, 0)) 49 | 50 | heapq.heapify(min_heap) 51 | 52 | while k > 0: 53 | num, r, c = heapq.heappop(min_heap) 54 | 55 | if c < N - 1: 56 | heapq.heappush(min_heap, (matrix[r][c + 1], r, c + 1)) 57 | 58 | k -= 1 59 | 60 | return num 61 | ``` 62 | 63 | ### [find-k-pairs-with-smallest-sums](https://leetcode-cn.com/problems/find-k-pairs-with-smallest-sums/) 64 | 65 | ```Python 66 | class Solution: 67 | def kSmallestPairs(self, nums1: List[int], nums2: List[int], k: int) -> List[List[int]]: 68 | 69 | m, n = len(nums1), len(nums2) 70 | result = [] 71 | 72 | if m * n == 0: 73 | return result 74 | 75 | min_heap = [(nums1[0] + nums2[0], 0, 0)] 76 | seen = set() 77 | 78 | while min_heap and len(result) < k: 79 | _, i1, i2 = heapq.heappop(min_heap) 80 | result.append([nums1[i1], nums2[i2]]) 81 | if i1 < m - 1 and (i1 + 1, i2) not in seen: 82 | heapq.heappush(min_heap, (nums1[i1 + 1] + nums2[i2], i1 + 1, i2)) 83 | seen.add((i1 + 1, i2)) 84 | if i2 < n - 1 and (i1, i2 + 1) not in seen: 85 | heapq.heappush(min_heap, (nums1[i1] + nums2[i2 + 1], i1, i2 + 1)) 86 | seen.add((i1, i2 + 1)) 87 | 88 | return result 89 | ``` 90 | 91 | ## Greedy + Heap 92 | 93 | Heap 可以高效地取出或更新当前池中优先级最高的元素,因此适用于一些需要 greedy 算法的场景。 94 | 95 | ### [maximum-performance-of-a-team](https://leetcode-cn.com/problems/maximum-performance-of-a-team/) 96 | 97 | > 公司有 n 个工程师,给两个数组 speed 和 efficiency,其中 speed[i] 和 efficiency[i] 分别代表第 i 位工程师的速度和效率。请你返回由最多 k 个工程师组成的团队的最大表现值。表现值的定义为:一个团队中所有工程师速度的和乘以他们效率值中的最小值。 98 | > 99 | 100 | - [See my review here.](https://leetcode.com/problems/maximum-performance-of-a-team/discuss/741822/Met-this-problem-in-my-interview!!!-(Python3-greedy-with-heap)) [或者这里(中文)](https://leetcode-cn.com/problems/maximum-performance-of-a-team/solution/greedy-with-min-heap-lai-zi-zhen-shi-mian-shi-de-j/) 101 | 102 | ```Python 103 | # similar question: LC 857 104 | class Solution: 105 | def maxPerformance(self, n, speed, efficiency, k): 106 | 107 | people = sorted(zip(speed, efficiency), key=lambda x: -x[1]) 108 | 109 | result, sum_speed = 0, 0 110 | min_heap = [] 111 | 112 | for i, (s, e) in enumerate(people): 113 | if i < k: 114 | sum_speed += s 115 | heapq.heappush(min_heap, s) 116 | elif s > min_heap[0]: 117 | sum_speed += s - heapq.heappushpop(min_heap, s) 118 | 119 | result = max(result, sum_speed * e) 120 | 121 | return result #% 1000000007 122 | ``` 123 | 124 | ### [ipo](https://leetcode-cn.com/problems/ipo/) 125 | 126 | - 贪心策略为每次做当前成本范围内利润最大的项目。 127 | 128 | ```Python 129 | class Solution: 130 | def findMaximizedCapital(self, k: int, W: int, Profits: List[int], Capital: List[int]) -> int: 131 | N = len(Profits) 132 | projects = sorted([(-Profits[i], Capital[i]) for i in range(N)], key=lambda x: x[1]) 133 | 134 | projects.append((0, float('inf'))) 135 | 136 | max_profit_heap = [] 137 | 138 | for i in range(N + 1): 139 | while projects[i][1] > W and len(max_profit_heap) > 0 and k > 0: 140 | W -= heapq.heappop(max_profit_heap) 141 | k -= 1 142 | 143 | if projects[i][1] > W or k == 0: 144 | break 145 | 146 | heapq.heappush(max_profit_heap, projects[i][0]) 147 | 148 | return W 149 | ``` 150 | 151 | ### [meeting-rooms-ii](https://leetcode-cn.com/problems/meeting-rooms-ii/) 152 | 153 | - 此题用 greedy + heap 解并不是很 intuitive,存在复杂度相同但更简单直观的做法。 154 | 155 | ```Python 156 | class Solution: 157 | def minMeetingRooms(self, intervals: List[List[int]]) -> int: 158 | 159 | if len(intervals) == 0: return 0 160 | 161 | intervals.sort(key=lambda item: item[0]) 162 | end_times = [intervals[0][1]] 163 | 164 | for interval in intervals[1:]: 165 | if end_times[0] <= interval[0]: 166 | heapq.heappop(end_times) 167 | 168 | heapq.heappush(end_times, interval[1]) 169 | 170 | return len(end_times) 171 | ``` 172 | 173 | ### [reorganize-string](https://leetcode-cn.com/problems/reorganize-string/) 174 | 175 | > 给定一个字符串 S,检查是否能重新排布其中的字母,使得任意两相邻的字符不同。若可行,输出任意可行的结果。若不可行,返回空字符串。 176 | 177 | - 贪心策略为每次取前两个最多数量的字母加入到结果。 178 | 179 | ```Python 180 | class Solution: 181 | def reorganizeString(self, S: str) -> str: 182 | 183 | max_dup = (len(S) + 1) // 2 184 | counts = collections.Counter(S) 185 | 186 | heap = [] 187 | for c, f in counts.items(): 188 | if f > max_dup: 189 | return '' 190 | heap.append([-f, c]) 191 | heapq.heapify(heap) 192 | 193 | result = [] 194 | while len(heap) > 1: 195 | first = heapq.heappop(heap) 196 | result.append(first[1]) 197 | first[0] += 1 198 | second = heapq.heappop(heap) 199 | result.append(second[1]) 200 | second[0] += 1 201 | 202 | if first[0] < 0: 203 | heapq.heappush(heap, first) 204 | if second[0] < 0: 205 | heapq.heappush(heap, second) 206 | 207 | if len(heap) == 1: 208 | result.append(heap[0][1]) 209 | 210 | return ''.join(result) 211 | ``` 212 | 213 | ### Prim's Algorithm 214 | 215 | 实现上是 greedy + heap 的一个应用,用于构造图的最小生成树 (MST)。 216 | 217 | ### [minimum-risk-path](https://www.lintcode.com/problem/minimum-risk-path/description) 218 | 219 | > 地图上有 m 条无向边,每条边 (x, y, w) 表示位置 m 到位置 y 的权值为 w。从位置 0 到 位置 n 可能有多条路径。我们定义一条路径的危险值为这条路径中所有的边的最大权值。请问从位置 0 到 位置 n 所有路径中最小的危险值为多少? 220 | 221 | - 最小危险值为最小生成树中 0 到 n 路径上的最大边权。 222 | 223 | ```Python 224 | class Solution: 225 | def getMinRiskValue(self, N, M, X, Y, W): 226 | 227 | # construct graph 228 | adj = collections.defaultdict(list) 229 | for i in range(M): 230 | adj[X[i]].append((Y[i], W[i])) 231 | adj[Y[i]].append((X[i], W[i])) 232 | 233 | # Prim's algorithm with min heap 234 | MST = collections.defaultdict(list) 235 | min_heap = [(w, 0, v) for v, w in adj[0]] 236 | heapq.heapify(min_heap) 237 | 238 | while N not in MST: 239 | w, p, v = heapq.heappop(min_heap) 240 | if v not in MST: 241 | MST[p].append((v, w)) 242 | MST[v].append((p, w)) 243 | for n, w in adj[v]: 244 | if n not in MST: 245 | heapq.heappush(min_heap, (w, v, n)) 246 | 247 | # dfs to search route from 0 to n 248 | dfs = [(0, None, float('-inf'))] 249 | while dfs: 250 | v, p, max_w = dfs.pop() 251 | for n, w in MST[v]: 252 | cur_max_w = max(max_w, w) 253 | if n == N: 254 | return cur_max_w 255 | if n != p: 256 | dfs.append((n, v, cur_max_w)) 257 | ``` 258 | 259 | ### Dijkstra's Algorithm 260 | 261 | 实现上是 greedy + heap 的一个应用,用于求解图的单源最短路径相关的问题,生成的树为最短路径树 (SPT)。 262 | 263 | ### [network-delay-time](https://leetcode-cn.com/problems/network-delay-time/) 264 | 265 | - 标准的单源最短路径问题,使用朴素的的 Dijikstra 算法即可,可以当成模板使用。 266 | 267 | ```Python 268 | class Solution: 269 | def networkDelayTime(self, times: List[List[int]], N: int, K: int) -> int: 270 | 271 | # construct graph 272 | graph_neighbor = collections.defaultdict(list) 273 | for s, e, t in times: 274 | graph_neighbor[s].append((e, t)) 275 | 276 | # Dijkstra 277 | SPT = {} 278 | min_heap = [(0, K)] 279 | 280 | while min_heap: 281 | delay, node = heapq.heappop(min_heap) 282 | if node not in SPT: 283 | SPT[node] = delay 284 | for n, d in graph_neighbor[node]: 285 | if n not in SPT: 286 | heapq.heappush(min_heap, (d + delay, n)) 287 | 288 | return max(SPT.values()) if len(SPT) == N else -1 289 | ``` 290 | 291 | ### [cheapest-flights-within-k-stops](https://leetcode-cn.com/problems/cheapest-flights-within-k-stops/) 292 | 293 | - 在标准的单源最短路径问题上限制了路径的边数,因此需要同时维护当前 SPT 内每个结点最短路径的边数,当遇到边数更小的路径 (边权和可以更大) 时结点需要重新入堆,以更新后继在边数上限内没达到的结点。 294 | 295 | ```Python 296 | class Solution: 297 | def findCheapestPrice(self, n: int, flights: List[List[int]], src: int, dst: int, K: int) -> int: 298 | 299 | # construct graph 300 | graph_neighbor = collections.defaultdict(list) 301 | for s, e, p in flights: 302 | graph_neighbor[s].append((e, p)) 303 | 304 | # modified Dijkstra 305 | prices, steps = {}, {} 306 | min_heap = [(0, 0, src)] 307 | 308 | while len(min_heap) > 0: 309 | price, step, node = heapq.heappop(min_heap) 310 | 311 | if node == dst: # early return 312 | return price 313 | 314 | if node not in prices: 315 | prices[node] = price 316 | 317 | steps[node] = step 318 | if step <= K: 319 | step += 1 320 | for n, p in graph_neighbor[node]: 321 | if n not in prices or step < steps[n]: 322 | heapq.heappush(min_heap, (p + price, step, n)) 323 | 324 | return -1 325 | ``` 326 | -------------------------------------------------------------------------------- /data_structure/linked_list.md: -------------------------------------------------------------------------------- 1 | # 链表 2 | 3 | ## 基本技能 4 | 5 | 链表相关的核心点 6 | 7 | - null/nil 异常处理 8 | - dummy node 哑巴节点 9 | - 快慢指针 10 | - 插入一个节点到排序链表 11 | - 从一个链表中移除一个节点 12 | - 翻转链表 13 | - 合并两个链表 14 | - 找到链表的中间节点 15 | 16 | ## 常见题型 17 | 18 | ### [remove-duplicates-from-sorted-list](https://leetcode-cn.com/problems/remove-duplicates-from-sorted-list/) 19 | 20 | > 给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。 21 | 22 | ```Python 23 | class Solution: 24 | def deleteDuplicates(self, head: ListNode) -> ListNode: 25 | 26 | if head is None: 27 | return head 28 | 29 | current = head 30 | 31 | while current.next is not None: 32 | if current.next.val == current.val: 33 | current.next = current.next.next 34 | else: 35 | current = current.next 36 | 37 | return head 38 | ``` 39 | 40 | ### [remove-duplicates-from-sorted-list-ii](https://leetcode-cn.com/problems/remove-duplicates-from-sorted-list-ii/) 41 | 42 | > 给定一个排序链表,删除所有含有重复数字的节点,只保留原始链表中   没有重复出现的数字。 43 | 44 | - 思路:链表头结点可能被删除,所以用 dummy node 辅助删除 45 | 46 | ```Python 47 | class Solution: 48 | def deleteDuplicates(self, head: ListNode) -> ListNode: 49 | 50 | if head is None: 51 | return head 52 | 53 | dummy = ListNode(next=head) 54 | 55 | current, peek = dummy, head 56 | find_dup = False 57 | while peek.next is not None: 58 | if peek.next.val == peek.val: 59 | find_dup = True 60 | peek.next = peek.next.next 61 | else: 62 | if find_dup: 63 | find_dup = False 64 | current.next = current.next.next 65 | else: 66 | current = current.next 67 | peek = peek.next 68 | 69 | if find_dup: 70 | current.next = current.next.next 71 | 72 | return dummy.next 73 | ``` 74 | 75 | 注意点 76 | • A->B->C 删除 B,A.next = C 77 | • 删除用一个 Dummy Node 节点辅助(允许头节点可变) 78 | • 访问 X.next 、X.value 一定要保证 X != nil 79 | 80 | ### [reverse-linked-list](https://leetcode-cn.com/problems/reverse-linked-list/) 81 | 82 | > 反转一个单链表。 83 | 84 | - 思路:将当前结点放置到头结点 85 | 86 | ```Python 87 | class Solution: 88 | def reverseList(self, head: ListNode) -> ListNode: 89 | 90 | if head is None: 91 | return head 92 | 93 | tail = head 94 | while tail.next is not None: 95 | # put tail.next to head 96 | tmp = tail.next 97 | tail.next = tail.next.next 98 | tmp.next = head 99 | head = tmp 100 | 101 | return head 102 | ``` 103 | - Recursive method is tricky 104 | ```Python 105 | class Solution: 106 | def reverseList(self, head: ListNode) -> ListNode: 107 | 108 | if head is None or head.next is None: 109 | return head 110 | 111 | rev_next = self.reverseList(head.next) 112 | head.next.next = head 113 | head.next = None 114 | 115 | return rev_next 116 | ``` 117 | 118 | ### [reverse-linked-list-ii](https://leetcode-cn.com/problems/reverse-linked-list-ii/) 119 | 120 | > 反转从位置  *m*  到  *n*  的链表。请使用一趟扫描完成反转。 121 | 122 | - 思路:先找到 m 处, 再反转 n - m 次即可 123 | 124 | ```Python 125 | class Solution: 126 | def reverseBetween(self, head: ListNode, m: int, n: int) -> ListNode: 127 | 128 | if head is None: 129 | return head 130 | 131 | n -= m # number of times of reverse 132 | 133 | curr = dummy = ListNode(next=head) 134 | while m > 1: # find node at m - 1 135 | curr = curr.next 136 | m -= 1 137 | 138 | start = curr.next 139 | while n > 0: # reverse n - m times 140 | tmp = start.next 141 | start.next = tmp.next 142 | tmp.next = curr.next 143 | curr.next = tmp 144 | n -= 1 145 | return dummy.next 146 | ``` 147 | 148 | ### [merge-two-sorted-lists](https://leetcode-cn.com/problems/merge-two-sorted-lists/) 149 | 150 | > 将两个升序链表合并为一个新的升序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 151 | 152 | - 思路:通过 dummy node 链表,连接各个元素 153 | 154 | ```Python 155 | class Solution: 156 | def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode: 157 | 158 | tail = dummy = ListNode() 159 | while l1 is not None and l2 is not None: 160 | if l1.val > l2.val: 161 | tail.next = l2 162 | l2 = l2.next 163 | else: 164 | tail.next = l1 165 | l1 = l1.next 166 | tail = tail.next 167 | 168 | if l1 is None: 169 | tail.next = l2 170 | else: 171 | tail.next = l1 172 | 173 | return dummy.next 174 | ``` 175 | 176 | ### [partition-list](https://leetcode-cn.com/problems/partition-list/) 177 | 178 | > 给定一个链表和一个特定值 x,对链表进行分隔,使得所有小于  *x*  的节点都在大于或等于  *x*  的节点之前。 179 | 180 | - 思路:将大于 x 的节点,放到另外一个链表,最后连接这两个链表 181 | 182 | ```go 183 | class Solution: 184 | def partition(self, head: ListNode, x: int) -> ListNode: 185 | 186 | p = l = ListNode() 187 | q = s = ListNode(next=head) 188 | 189 | while q.next is not None: 190 | if q.next.val < x: 191 | q = q.next 192 | else: 193 | p.next = q.next 194 | q.next = q.next.next 195 | p = p.next 196 | 197 | p.next = None 198 | q.next = l.next 199 | 200 | return s.next 201 | ``` 202 | 203 | 哑巴节点使用场景 204 | 205 | > 当头节点不确定的时候,使用哑巴节点 206 | 207 | ### [sort-list](https://leetcode-cn.com/problems/sort-list/) 208 | 209 | > 在  *O*(*n* log *n*) 时间复杂度和常数级空间复杂度下,对链表进行排序。 210 | 211 | - 思路:归并排序,slow-fast找中点 212 | 213 | ```Python 214 | class Solution: 215 | 216 | def _merge(self, l1, l2): 217 | tail = l_merge = ListNode() 218 | 219 | while l1 is not None and l2 is not None: 220 | if l1.val > l2.val: 221 | tail.next = l2 222 | l2 = l2.next 223 | else: 224 | tail.next = l1 225 | l1 = l1.next 226 | tail = tail.next 227 | 228 | if l1 is not None: 229 | tail.next = l1 230 | else: 231 | tail.next = l2 232 | 233 | return l_merge.next 234 | 235 | def _findmid(self, head): 236 | slow, fast = head, head.next 237 | while fast is not None and fast.next is not None: 238 | fast = fast.next.next 239 | slow = slow.next 240 | 241 | return slow 242 | 243 | def sortList(self, head: ListNode) -> ListNode: 244 | if head is None or head.next is None: 245 | return head 246 | 247 | mid = self._findmid(head) 248 | tail = mid.next 249 | mid.next = None # break from middle 250 | 251 | return self._merge(self.sortList(head), self.sortList(tail)) 252 | ``` 253 | 254 | 注意点 255 | 256 | - 快慢指针 判断 fast 及 fast.Next 是否为 nil 值 257 | - 递归 mergeSort 需要断开中间节点 258 | - 递归返回条件为 head 为 nil 或者 head.Next 为 nil 259 | 260 | ### [reorder-list](https://leetcode-cn.com/problems/reorder-list/) 261 | 262 | > 给定一个单链表  *L*:*L*→*L*→…→*L\_\_n*→*L* 263 | > 将其重新排列后变为: *L*→*L\_\_n*→*L*→*L\_\_n*→*L*→*L\_\_n*→… 264 | 265 | - 思路:找到中点断开,翻转后面部分,然后合并前后两个链表 266 | 267 | ```Python 268 | class Solution: 269 | 270 | def reverseList(self, head: ListNode) -> ListNode: 271 | 272 | prev, curr = None, head 273 | 274 | while curr is not None: 275 | curr.next, prev, curr = prev, curr, curr.next 276 | 277 | return prev 278 | 279 | def reorderList(self, head: ListNode) -> None: 280 | """ 281 | Do not return anything, modify head in-place instead. 282 | """ 283 | if head is None or head.next is None or head.next.next is None: 284 | return 285 | 286 | slow, fast = head, head.next 287 | while fast is not None and fast.next is not None: 288 | fast = fast.next.next 289 | slow = slow.next 290 | 291 | h, m = head, slow.next 292 | slow.next = None 293 | 294 | m = self.reverseList(m) 295 | 296 | while h is not None and m is not None: 297 | p = m.next 298 | m.next = h.next 299 | h.next = m 300 | h = h.next.next 301 | m = p 302 | 303 | return 304 | ``` 305 | 306 | ### [linked-list-cycle](https://leetcode-cn.com/problems/linked-list-cycle/) 307 | 308 | > 给定一个链表,判断链表中是否有环。 309 | 310 | - 思路1:Hash Table 记录所有结点判断重复,空间复杂度 O(n) 非最优,时间复杂度 O(n) 但必然需要 n 次循环 311 | - 思路2:快慢指针,快慢指针相同则有环,证明:如果有环每走一步快慢指针距离会减 1,空间复杂度 O(1) 最优,时间复杂度 O(n) 但循环次数小于等于 n 312 | ![fast_slow_linked_list](https://img.fuiboom.com/img/fast_slow_linked_list.png) 313 | 314 | ```Python 315 | class Solution: 316 | def hasCycle(self, head: ListNode) -> bool: 317 | 318 | slow = fast = head 319 | 320 | while fast is not None and fast.next is not None: 321 | slow = slow.next 322 | fast = fast.next.next 323 | if fast == slow: 324 | return True 325 | 326 | return False 327 | ``` 328 | 329 | ### [linked-list-cycle-ii](https://leetcode-cn.com/problems/linked-list-cycle-ii/) 330 | 331 | > 给定一个链表,返回链表开始入环的第一个节点。  如果链表无环,则返回  `null`。 332 | 333 | - 思路:快慢指针,快慢相遇之后,慢指针回到头,快慢指针步调一致一起移动,相遇点即为入环点。 334 | 335 | ![cycled_linked_list](https://img.fuiboom.com/img/cycled_linked_list.png) 336 | 337 | ```Python 338 | class Solution: 339 | def detectCycle(self, head: ListNode) -> ListNode: 340 | 341 | slow = fast = head 342 | 343 | while fast is not None and fast.next is not None: 344 | slow = slow.next 345 | fast = fast.next.next 346 | 347 | if slow == fast: 348 | slow = head 349 | while fast != slow: 350 | fast = fast.next 351 | slow = slow.next 352 | return slow 353 | 354 | return None 355 | ``` 356 | 357 | 坑点 358 | 359 | - 指针比较时直接比较对象,不要用值比较,链表中有可能存在重复值情况 360 | - 第一次相交后,快指针需要从下一个节点开始和头指针一起匀速移动 361 | 362 | 363 | 注意,此题中使用 slow = fast = head 是为了保证最后找环起始点时移动步数相同,但是作为找中点使用时**一般用 fast=head.Next 较多**,因为这样可以知道中点的上一个节点,可以用来删除等操作。 364 | 365 | - fast 如果初始化为 head.Next 则中点在 slow.Next 366 | - fast 初始化为 head,则中点在 slow 367 | 368 | ### [palindrome-linked-list](https://leetcode-cn.com/problems/palindrome-linked-list/) 369 | 370 | > 请判断一个链表是否为回文链表。 371 | 372 | - 思路:O(1) 空间复杂度的解法需要破坏原链表(找中点 -> 反转后半个list -> 判断回文),在实际应用中往往还需要复原(后半个list再反转一次后拼接),操作比较复杂,这里给出更工程化的做法 373 | 374 | ```Python 375 | class Solution: 376 | def isPalindrome(self, head: ListNode) -> bool: 377 | 378 | s = [] 379 | slow = fast = head 380 | while fast is not None and fast.next is not None: 381 | s.append(slow.val) 382 | slow = slow.next 383 | fast = fast.next.next 384 | 385 | if fast is not None: 386 | slow = slow.next 387 | 388 | while len(s) > 0: 389 | if slow.val != s.pop(): 390 | return False 391 | slow = slow.next 392 | 393 | return True 394 | ``` 395 | 396 | ### [copy-list-with-random-pointer](https://leetcode-cn.com/problems/copy-list-with-random-pointer/) 397 | 398 | > 给定一个链表,每个节点包含一个额外增加的随机指针,该指针可以指向链表中的任何节点或空节点。 399 | > 要求返回这个链表的 深拷贝。 400 | 401 | - 思路1:hash table 存储 random 指针的连接关系 402 | 403 | ```Python 404 | class Solution: 405 | def copyRandomList(self, head: 'Node') -> 'Node': 406 | 407 | if head is None: 408 | return None 409 | 410 | parent = collections.defaultdict(list) 411 | 412 | out = Node(0) 413 | o, n = head, out 414 | while o is not None: 415 | n.next = Node(o.val) 416 | n = n.next 417 | if o.random is not None: 418 | parent[o.random].append(n) 419 | o = o.next 420 | 421 | o, n = head, out.next 422 | while o is not None: 423 | if o in parent: 424 | for p in parent[o]: 425 | p.random = n 426 | o = o.next 427 | n = n.next 428 | 429 | return out.next 430 | ``` 431 | 432 | - 思路2:复制结点跟在原结点后面,间接维护连接关系,优化空间复杂度,建立好新 list 的 random 链接后分离 433 | 434 | ```Python 435 | class Solution: 436 | def copyRandomList(self, head: 'Node') -> 'Node': 437 | 438 | if head is None: 439 | return None 440 | 441 | p = head 442 | while p is not None: 443 | p.next = Node(p.val, p.next) 444 | p = p.next.next 445 | 446 | p = head 447 | while p is not None: 448 | if p.random is not None: 449 | p.next.random = p.random.next 450 | p = p.next.next 451 | 452 | new = head.next 453 | o, n = head, new 454 | while n.next is not None: 455 | o.next = n.next 456 | n.next = n.next.next 457 | o = o.next 458 | n = n.next 459 | o.next = None 460 | 461 | return new 462 | ``` 463 | 464 | ## 总结 465 | 466 | 链表必须要掌握的一些点,通过下面练习题,基本大部分的链表类的题目都是手到擒来~ 467 | 468 | - null/nil 异常处理 469 | - dummy node 哑巴节点 470 | - 快慢指针 471 | - 插入一个节点到排序链表 472 | - 从一个链表中移除一个节点 473 | - 翻转链表 474 | - 合并两个链表 475 | - 找到链表的中间节点 476 | 477 | ## 练习 478 | 479 | - [ ] [remove-duplicates-from-sorted-list](https://leetcode-cn.com/problems/remove-duplicates-from-sorted-list/) 480 | - [ ] [remove-duplicates-from-sorted-list-ii](https://leetcode-cn.com/problems/remove-duplicates-from-sorted-list-ii/) 481 | - [ ] [reverse-linked-list](https://leetcode-cn.com/problems/reverse-linked-list/) 482 | - [ ] [reverse-linked-list-ii](https://leetcode-cn.com/problems/reverse-linked-list-ii/) 483 | - [ ] [merge-two-sorted-lists](https://leetcode-cn.com/problems/merge-two-sorted-lists/) 484 | - [ ] [partition-list](https://leetcode-cn.com/problems/partition-list/) 485 | - [ ] [sort-list](https://leetcode-cn.com/problems/sort-list/) 486 | - [ ] [reorder-list](https://leetcode-cn.com/problems/reorder-list/) 487 | - [ ] [linked-list-cycle](https://leetcode-cn.com/problems/linked-list-cycle/) 488 | - [ ] [linked-list-cycle-ii](https://leetcode-cn.com/problems/https://leetcode-cn.com/problems/linked-list-cycle-ii/) 489 | - [ ] [palindrome-linked-list](https://leetcode-cn.com/problems/palindrome-linked-list/) 490 | - [ ] [copy-list-with-random-pointer](https://leetcode-cn.com/problems/copy-list-with-random-pointer/) 491 | -------------------------------------------------------------------------------- /data_structure/stack_queue.md: -------------------------------------------------------------------------------- 1 | # 栈和队列 2 | 3 | ## 简介 4 | 5 | 栈的特点是后入先出 6 | 7 | ![image.png](https://img.fuiboom.com/img/stack.png) 8 | 9 | 根据这个特点可以临时保存一些数据,之后用到依次再弹出来,常用于 DFS 深度搜索 10 | 11 | 队列一般常用于 BFS 广度搜索,类似一层一层的搜索 12 | 13 | ## Stack 栈 14 | 15 | ### [min-stack](https://leetcode-cn.com/problems/min-stack/) 16 | 17 | > 设计一个支持 push,pop,top 操作,并能在常数时间内检索到最小元素的栈。 18 | 19 | - 思路:用两个栈实现或插入元组实现,保证当前最小值在栈顶即可 20 | 21 | ```Python 22 | class MinStack: 23 | 24 | def __init__(self): 25 | self.stack = [] 26 | 27 | def push(self, x: int) -> None: 28 | if len(self.stack) > 0: 29 | self.stack.append((x, min(x, self.stack[-1][1]))) 30 | else: 31 | self.stack.append((x, x)) 32 | 33 | def pop(self) -> int: 34 | return self.stack.pop()[0] 35 | 36 | def top(self) -> int: 37 | return self.stack[-1][0] 38 | 39 | def getMin(self) -> int: 40 | return self.stack[-1][1] 41 | ``` 42 | 43 | ### [evaluate-reverse-polish-notation](https://leetcode-cn.com/problems/evaluate-reverse-polish-notation/) 44 | 45 | > **波兰表达式计算** > **输入:** ["2", "1", "+", "3", "*"] > **输出:** 9 46 | > **解释:** ((2 + 1) \* 3) = 9 47 | 48 | - 思路:通过栈保存原来的元素,遇到表达式弹出运算,再推入结果,重复这个过程 49 | 50 | ```Python 51 | class Solution: 52 | def evalRPN(self, tokens: List[str]) -> int: 53 | 54 | def comp(or1, op, or2): 55 | if op == '+': 56 | return or1 + or2 57 | 58 | if op == '-': 59 | return or1 - or2 60 | 61 | if op == '*': 62 | return or1 * or2 63 | 64 | if op == '/': 65 | abs_result = abs(or1) // abs(or2) 66 | return abs_result if or1 * or2 > 0 else -abs_result 67 | 68 | stack = [] 69 | 70 | for token in tokens: 71 | if token in ['+', '-', '*', '/']: 72 | or2 = stack.pop() 73 | or1 = stack.pop() 74 | stack.append(comp(or1, token, or2)) 75 | else: 76 | stack.append(int(token)) 77 | 78 | return stack[0] 79 | ``` 80 | 81 | ### [decode-string](https://leetcode-cn.com/problems/decode-string/) 82 | 83 | > 给定一个经过编码的字符串,返回它解码后的字符串。 84 | > s = "3[a]2[bc]", 返回 "aaabcbc". 85 | > s = "3[a2[c]]", 返回 "accaccacc". 86 | > s = "2[abc]3[cd]ef", 返回 "abcabccdcdcdef". 87 | 88 | - 思路:通过两个栈进行操作,一个用于存数,另一个用来存字符串 89 | 90 | ```Python 91 | class Solution: 92 | def decodeString(self, s: str) -> str: 93 | 94 | stack_str = [''] 95 | stack_num = [] 96 | 97 | num = 0 98 | for c in s: 99 | if c >= '0' and c <= '9': 100 | num = num * 10 + int(c) 101 | elif c == '[': 102 | stack_num.append(num) 103 | stack_str.append('') 104 | num = 0 105 | elif c == ']': 106 | cur_str = stack_str.pop() 107 | stack_str[-1] += cur_str * stack_num.pop() 108 | else: 109 | stack_str[-1] += c 110 | 111 | return stack_str[0] 112 | ``` 113 | 114 | ### [binary-tree-inorder-traversal](https://leetcode-cn.com/problems/binary-tree-inorder-traversal/) 115 | 116 | > 给定一个二叉树,返回它的*中序*遍历。 117 | 118 | - [reference](https://en.wikipedia.org/wiki/Tree_traversal#In-order) 119 | 120 | ```Python 121 | class Solution: 122 | def inorderTraversal(self, root: TreeNode) -> List[int]: 123 | 124 | stack, inorder = [], [] 125 | node = root 126 | 127 | while len(stack) > 0 or node is not None: 128 | if node is not None: 129 | stack.append(node) 130 | node = node.left 131 | else: 132 | node = stack.pop() 133 | inorder.append(node.val) 134 | node = node.right 135 | 136 | return inorder 137 | ``` 138 | 139 | ### [clone-graph](https://leetcode-cn.com/problems/clone-graph/) 140 | 141 | > 给你无向连通图中一个节点的引用,请你返回该图的深拷贝(克隆)。 142 | 143 | - BFS 144 | 145 | ```Python 146 | class Solution: 147 | def cloneGraph(self, start: 'Node') -> 'Node': 148 | 149 | if start is None: 150 | return None 151 | 152 | visited = {start: Node(start.val, [])} 153 | bfs = collections.deque([start]) 154 | 155 | while len(bfs) > 0: 156 | curr = bfs.popleft() 157 | curr_copy = visited[curr] 158 | for n in curr.neighbors: 159 | if n not in visited: 160 | visited[n] = Node(n.val, []) 161 | bfs.append(n) 162 | curr_copy.neighbors.append(visited[n]) 163 | 164 | return visited[start] 165 | ``` 166 | 167 | - DFS iterative 168 | 169 | ```Python 170 | class Solution: 171 | def cloneGraph(self, start: 'Node') -> 'Node': 172 | 173 | if start is None: 174 | return None 175 | 176 | if not start.neighbors: 177 | return Node(start.val) 178 | 179 | visited = {start: Node(start.val, [])} 180 | dfs = [start] 181 | 182 | while len(dfs) > 0: 183 | peek = dfs[-1] 184 | peek_copy = visited[peek] 185 | if len(peek_copy.neighbors) == 0: 186 | for n in peek.neighbors: 187 | if n not in visited: 188 | visited[n] = Node(n.val, []) 189 | dfs.append(n) 190 | peek_copy.neighbors.append(visited[n]) 191 | else: 192 | dfs.pop() 193 | 194 | return visited[start] 195 | ``` 196 | 197 | ### [number-of-islands](https://leetcode-cn.com/problems/number-of-islands/) 198 | 199 | > 给定一个由  '1'(陆地)和 '0'(水)组成的的二维网格,计算岛屿的数量。一个岛被水包围,并且它是通过水平方向或垂直方向上相邻的陆地连接而成的。你可以假设网格的四个边均被水包围。 200 | 201 | High-level problem: number of connected component of graph 202 | 203 | - 思路:通过深度搜索遍历可能性(注意标记已访问元素) 204 | 205 | ```Python 206 | class Solution: 207 | def numIslands(self, grid: List[List[str]]) -> int: 208 | 209 | if not grid or not grid[0]: 210 | return 0 211 | 212 | m, n = len(grid), len(grid[0]) 213 | 214 | def dfs_iter(i, j): 215 | dfs = [] 216 | dfs.append((i, j)) 217 | while len(dfs) > 0: 218 | i, j = dfs.pop() 219 | if grid[i][j] == '1': 220 | grid[i][j] = '0' 221 | if i - 1 >= 0: 222 | dfs.append((i - 1, j)) 223 | if j - 1 >= 0: 224 | dfs.append((i, j - 1)) 225 | if i + 1 < m: 226 | dfs.append((i + 1, j)) 227 | if j + 1 < n: 228 | dfs.append((i, j + 1)) 229 | return 230 | 231 | num_island = 0 232 | for i in range(m): 233 | for j in range(n): 234 | if grid[i][j] == '1': 235 | num_island += 1 236 | dfs_iter(i, j) 237 | 238 | return num_island 239 | ``` 240 | 241 | ### [largest-rectangle-in-histogram](https://leetcode-cn.com/problems/largest-rectangle-in-histogram/) 242 | 243 | > 给定 _n_ 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。 244 | > 求在该柱状图中,能够勾勒出来的矩形的最大面积。 245 | 246 | - 思路 1:蛮力法,比较每个以 i 开始 j 结束的最大矩形,A(i, j) = (j - i + 1) * min_height(i, j),时间复杂度 O(n^2) 无法 AC。 247 | 248 | ```Python 249 | class Solution: 250 | def largestRectangleArea(self, heights: List[int]) -> int: 251 | 252 | max_area = 0 253 | 254 | n = len(heights) 255 | for i in range(n): 256 | min_height = heights[i] 257 | for j in range(i, n): 258 | min_height = min(min_height, heights[j]) 259 | max_area = max(max_area, min_height * (j - i + 1)) 260 | 261 | return max_area 262 | ``` 263 | 264 | - 思路 2: 设 A(i, j) 为区间 [i, j) 内最大矩形的面积,k 为 [i, j) 内最矮 bar 的坐标,则 A(i, j) = max((j - i) * heights[k], A(i, k), A(k+1, j)), 使用分治法进行求解。时间复杂度 O(nlogn),其中使用简单遍历求最小值无法 AC (最坏情况退化到 O(n^2)),使用线段树优化后勉强 AC。 265 | 266 | ```Python 267 | class Solution: 268 | def largestRectangleArea(self, heights: List[int]) -> int: 269 | 270 | n = len(heights) 271 | 272 | seg_tree = [None] * n 273 | seg_tree.extend(list(zip(heights, range(n)))) 274 | for i in range(n - 1, 0, -1): 275 | seg_tree[i] = min(seg_tree[2 * i], seg_tree[2 * i + 1], key=lambda x: x[0]) 276 | 277 | def _min(i, j): 278 | min_ = (heights[i], i) 279 | i += n 280 | j += n 281 | while i < j: 282 | if i % 2 == 1: 283 | min_ = min(min_, seg_tree[i], key=lambda x: x[0]) 284 | i += 1 285 | if j % 2 == 1: 286 | j -= 1 287 | min_ = min(min_, seg_tree[j], key=lambda x: x[0]) 288 | i //= 2 289 | j //= 2 290 | 291 | return min_ 292 | 293 | def LRA(i, j): 294 | if i == j: 295 | return 0 296 | min_k, k = _min(i, j) 297 | return max(min_k * (j - i), LRA(k + 1, j), LRA(i, k)) 298 | 299 | return LRA(0, n) 300 | ``` 301 | 302 | - 思路 3:包含当前 bar 最大矩形的边界为左边第一个高度小于当前高度的 bar 和右边第一个高度小于当前高度的 bar。 303 | 304 | ```Python 305 | class Solution: 306 | def largestRectangleArea(self, heights: List[int]) -> int: 307 | 308 | n = len(heights) 309 | 310 | stack = [-1] 311 | max_area = 0 312 | 313 | for i in range(n): 314 | while len(stack) > 1 and heights[stack[-1]] > heights[i]: 315 | h = stack.pop() 316 | max_area = max(max_area, heights[h] * (i - stack[-1] - 1)) 317 | stack.append(i) 318 | 319 | while len(stack) > 1: 320 | h = stack.pop() 321 | max_area = max(max_area, heights[h] * (n - stack[-1] - 1)) 322 | 323 | return max_area 324 | ``` 325 | 326 | ## Queue 队列 327 | 328 | 常用于 BFS 宽度优先搜索 329 | 330 | ### [implement-queue-using-stacks](https://leetcode-cn.com/problems/implement-queue-using-stacks/) 331 | 332 | > 使用栈实现队列 333 | 334 | ```Python 335 | class MyQueue: 336 | 337 | def __init__(self): 338 | self.cache = [] 339 | self.out = [] 340 | 341 | def push(self, x: int) -> None: 342 | """ 343 | Push element x to the back of queue. 344 | """ 345 | self.cache.append(x) 346 | 347 | def pop(self) -> int: 348 | """ 349 | Removes the element from in front of queue and returns that element. 350 | """ 351 | if len(self.out) == 0: 352 | while len(self.cache) > 0: 353 | self.out.append(self.cache.pop()) 354 | 355 | return self.out.pop() 356 | 357 | def peek(self) -> int: 358 | """ 359 | Get the front element. 360 | """ 361 | if len(self.out) > 0: 362 | return self.out[-1] 363 | else: 364 | return self.cache[0] 365 | 366 | def empty(self) -> bool: 367 | """ 368 | Returns whether the queue is empty. 369 | """ 370 | return len(self.cache) == 0 and len(self.out) == 0 371 | ``` 372 | 373 | ### [binary-tree-level-order-traversal](https://leetcode-cn.com/problems/binary-tree-level-order-traversal/) 374 | 375 | > 二叉树的层序遍历 376 | 377 | ```Python 378 | class Solution: 379 | def levelOrder(self, root: TreeNode) -> List[List[int]]: 380 | 381 | levels = [] 382 | if root is None: 383 | return levels 384 | 385 | bfs = collections.deque([root]) 386 | 387 | while len(bfs) > 0: 388 | levels.append([]) 389 | 390 | level_size = len(bfs) 391 | for _ in range(level_size): 392 | node = bfs.popleft() 393 | levels[-1].append(node.val) 394 | 395 | if node.left is not None: 396 | bfs.append(node.left) 397 | if node.right is not None: 398 | bfs.append(node.right) 399 | 400 | return levels 401 | ``` 402 | 403 | ### [01-matrix](https://leetcode-cn.com/problems/01-matrix/) 404 | 405 | > 给定一个由 0 和 1 组成的矩阵,找出每个元素到最近的 0 的距离。 406 | > 两个相邻元素间的距离为 1 407 | 408 | - 思路 1: 从 0 开始 BFS, 遇到距离最小值需要更新的则更新后重新入队更新后续结点 409 | 410 | ```Python 411 | class Solution: 412 | def updateMatrix(self, matrix: List[List[int]]) -> List[List[int]]: 413 | 414 | if len(matrix) == 0 or len(matrix[0]) == 0: 415 | return matrix 416 | 417 | m, n = len(matrix), len(matrix[0]) 418 | dist = [[float('inf')] * n for _ in range(m)] 419 | 420 | bfs = collections.deque([]) 421 | for i in range(m): 422 | for j in range(n): 423 | if matrix[i][j] == 0: 424 | dist[i][j] = 0 425 | bfs.append((i, j)) 426 | 427 | neighbors = [(-1, 0), (1, 0), (0, -1), (0, 1)] 428 | while len(bfs) > 0: 429 | i, j = bfs.popleft() 430 | for dn_i, dn_j in neighbors: 431 | n_i, n_j = i + dn_i, j + dn_j 432 | if n_i >= 0 and n_i < m and n_j >= 0 and n_j < n: 433 | if dist[n_i][n_j] > dist[i][j] + 1: 434 | dist[n_i][n_j] = dist[i][j] + 1 435 | bfs.append((n_i, n_j)) 436 | 437 | return dist 438 | ``` 439 | 440 | - 思路 2: 2-pass DP,dist(i, j) = max{dist(i - 1, j), dist(i + 1, j), dist(i, j - 1), dist(i, j + 1)} + 1 441 | 442 | ```Python 443 | class Solution: 444 | def updateMatrix(self, matrix: List[List[int]]) -> List[List[int]]: 445 | 446 | if len(matrix) == 0 or len(matrix[0]) == 0: 447 | return matrix 448 | 449 | m, n = len(matrix), len(matrix[0]) 450 | 451 | dist = [[float('inf')] * n for _ in range(m)] 452 | 453 | for i in range(m): 454 | for j in range(n): 455 | if matrix[i][j] == 1: 456 | if i - 1 >= 0: 457 | dist[i][j] = min(dist[i - 1][j] + 1, dist[i][j]) 458 | if j - 1 >= 0: 459 | dist[i][j] = min(dist[i][j - 1] + 1, dist[i][j]) 460 | else: 461 | dist[i][j] = 0 462 | 463 | for i in range(-1, -m - 1, -1): 464 | for j in range(-1, -n - 1, -1): 465 | if matrix[i][j] == 1: 466 | if i + 1 < 0: 467 | dist[i][j] = min(dist[i + 1][j] + 1, dist[i][j]) 468 | if j + 1 < 0: 469 | dist[i][j] = min(dist[i][j + 1] + 1, dist[i][j]) 470 | 471 | return dist 472 | ``` 473 | 474 | ## 补充:单调栈 475 | 476 | 顾名思义,单调栈即是栈中元素有单调性的栈,典型应用为用线性的时间复杂度找左右两侧第一个大于/小于当前元素的位置。 477 | 478 | ### [largest-rectangle-in-histogram](https://leetcode-cn.com/problems/largest-rectangle-in-histogram/) 479 | 480 | ```Python 481 | class Solution: 482 | def largestRectangleArea(self, heights) -> int: 483 | heights.append(0) 484 | stack = [-1] 485 | result = 0 486 | for i in range(len(heights)): 487 | while stack and heights[i] < heights[stack[-1]]: 488 | cur = stack.pop() 489 | result = max(result, heights[cur] * (i - stack[-1] - 1)) 490 | stack.append(i) 491 | return result 492 | ``` 493 | 494 | ### [trapping-rain-water](https://leetcode-cn.com/problems/trapping-rain-water/) 495 | 496 | ```Python 497 | class Solution: 498 | def trap(self, height: List[int]) -> int: 499 | 500 | stack = [] 501 | result = 0 502 | 503 | for i in range(len(height)): 504 | while stack and height[i] > height[stack[-1]]: 505 | cur = stack.pop() 506 | if not stack: 507 | break 508 | result += (min(height[stack[-1]], height[i]) - height[cur]) * (i - stack[-1] - 1) 509 | stack.append(i) 510 | 511 | return result 512 | ``` 513 | 514 | ## 补充:单调队列 515 | 516 | 单调栈的拓展,可以从数组头 pop 出旧元素,典型应用是以线性时间获得区间最大/最小值。 517 | 518 | ### [sliding-window-maximum](https://leetcode-cn.com/problems/sliding-window-maximum/) 519 | 520 | > 求滑动窗口中的最大元素 521 | 522 | ```Python 523 | class Solution: 524 | def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]: 525 | 526 | N = len(nums) 527 | if N * k == 0: 528 | return [] 529 | 530 | if k == 1: 531 | return nums[:] 532 | 533 | # define a max queue 534 | maxQ = collections.deque() 535 | 536 | result = [] 537 | for i in range(N): 538 | if maxQ and maxQ[0] == i - k: 539 | maxQ.popleft() 540 | 541 | while maxQ and nums[maxQ[-1]] < nums[i]: 542 | maxQ.pop() 543 | 544 | maxQ.append(i) 545 | 546 | if i >= k - 1: 547 | result.append(nums[maxQ[0]]) 548 | 549 | return result 550 | ``` 551 | 552 | ### [shortest-subarray-with-sum-at-least-k](https://leetcode-cn.com/problems/shortest-subarray-with-sum-at-least-k/) 553 | 554 | ```Python 555 | class Solution: 556 | def shortestSubarray(self, A: List[int], K: int) -> int: 557 | N = len(A) 558 | cdf = [0] 559 | for num in A: 560 | cdf.append(cdf[-1] + num) 561 | 562 | result = N + 1 563 | minQ = collections.deque() 564 | 565 | for i, csum in enumerate(cdf): 566 | 567 | while minQ and csum <= cdf[minQ[-1]]: 568 | minQ.pop() 569 | 570 | while minQ and csum - cdf[minQ[0]] >= K: 571 | result = min(result, i - minQ.popleft()) 572 | 573 | minQ.append(i) 574 | 575 | return result if result < N + 1 else -1 576 | ``` 577 | 578 | ## 总结 579 | 580 | - 熟悉栈的使用场景 581 | - 后入先出,保存临时值 582 | - 利用栈 DFS 深度搜索 583 | - 熟悉队列的使用场景 584 | - 利用队列 BFS 广度搜索 585 | 586 | ## 练习 587 | 588 | - [ ] [min-stack](https://leetcode-cn.com/problems/min-stack/) 589 | - [ ] [evaluate-reverse-polish-notation](https://leetcode-cn.com/problems/evaluate-reverse-polish-notation/) 590 | - [ ] [decode-string](https://leetcode-cn.com/problems/decode-string/) 591 | - [ ] [binary-tree-inorder-traversal](https://leetcode-cn.com/problems/binary-tree-inorder-traversal/) 592 | - [ ] [clone-graph](https://leetcode-cn.com/problems/clone-graph/) 593 | - [ ] [number-of-islands](https://leetcode-cn.com/problems/number-of-islands/) 594 | - [ ] [largest-rectangle-in-histogram](https://leetcode-cn.com/problems/largest-rectangle-in-histogram/) 595 | - [ ] [implement-queue-using-stacks](https://leetcode-cn.com/problems/implement-queue-using-stacks/) 596 | - [ ] [01-matrix](https://leetcode-cn.com/problems/01-matrix/) 597 | -------------------------------------------------------------------------------- /data_structure/union_find.md: -------------------------------------------------------------------------------- 1 | # 并查集 2 | 3 | 用于处理不相交集合 (disjoint sets) 合并及查找的问题,典型应用有连通分量检测,环路检测等。原理和复杂度分析等可以参考[维基百科](https://en.wikipedia.org/wiki/Disjoint-set_data_structure)。 4 | 5 | ### [redundant-connection](https://leetcode-cn.com/problems/redundant-connection/) 6 | 7 | ```Python 8 | class Solution: 9 | def findRedundantConnection(self, edges: List[List[int]]) -> List[int]: 10 | 11 | parent = list(range(len(edges) + 1)) 12 | rank = [1] * (len(edges) + 1) 13 | 14 | def find(x): 15 | if parent[parent[x]] != parent[x]: 16 | parent[x] = find(parent[x]) # path compression 17 | return parent[x] 18 | 19 | def union(x, y): 20 | px, py = find(x), find(y) 21 | if px == py: 22 | return False 23 | # union by rank 24 | if rank[px] > rank[py]: 25 | parent[py] = px 26 | elif rank[px] < rank[py]: 27 | parent[px] = py 28 | else: 29 | parent[px] = py 30 | rank[py] += 1 31 | return True 32 | 33 | for edge in edges: 34 | if not union(edge[0], edge[1]): 35 | return edge 36 | ``` 37 | 38 | ### [accounts-merge](https://leetcode-cn.com/problems/accounts-merge/) 39 | 40 | ```Python 41 | class Solution: 42 | def accountsMerge(self, accounts: List[List[str]]) -> List[List[str]]: 43 | 44 | parent = [] 45 | rank = [] 46 | 47 | def find(x): 48 | if parent[parent[x]] != parent[x]: 49 | parent[x] = find(parent[x]) 50 | return parent[x] 51 | 52 | def union(x, y): 53 | px, py = find(x), find(y) 54 | if px == py: 55 | return 56 | if rank[px] > rank[py]: 57 | parent[py] = px 58 | elif rank[px] < rank[py]: 59 | parent[px] = py 60 | else: 61 | parent[px] = py 62 | rank[py] += 1 63 | return 64 | 65 | email2name = {} 66 | email2idx = {} 67 | i = 0 68 | for acc in accounts: 69 | for email in acc[1:]: 70 | email2name[email] = acc[0] 71 | if email not in email2idx: 72 | parent.append(i) 73 | rank.append(1) 74 | email2idx[email] = i 75 | i += 1 76 | union(email2idx[acc[1]], email2idx[email]) 77 | 78 | result = collections.defaultdict(list) 79 | for email in email2name: 80 | result[find(email2idx[email])].append(email) 81 | 82 | return [[email2name[s[0]]] + sorted(s) for s in result.values()] 83 | ``` 84 | 85 | ### [longest-consecutive-sequence](https://leetcode-cn.com/problems/longest-consecutive-sequence/) 86 | 87 | ```Python 88 | class Solution: 89 | def longestConsecutive(self, nums: List[int]) -> int: 90 | 91 | parent = {num: num for num in nums} 92 | length = {num: 1 for num in nums} 93 | 94 | def find(x): 95 | if parent[parent[x]] != parent[x]: 96 | parent[x] = find(parent[x]) 97 | return parent[x] 98 | 99 | def union(x, y): 100 | px, py = find(x), find(y) 101 | if px == py: 102 | return 103 | # union by size 104 | if length[px] > length[py]: 105 | parent[py] = px 106 | length[px] += length[py] 107 | else: 108 | parent[px] = py 109 | length[py] += length[px] 110 | return 111 | 112 | max_length = 0 113 | for num in nums: 114 | if num + 1 in parent: 115 | union(num + 1, num) 116 | if num - 1 in parent: 117 | union(num - 1, num) 118 | 119 | max_length = max(max_length, length[parent[num]]) 120 | 121 | return max_length 122 | ``` 123 | 124 | ### Kruskal's algorithm 125 | 126 | ### [minimum-risk-path](https://www.lintcode.com/problem/minimum-risk-path/description) 127 | 128 | > 地图上有 m 条无向边,每条边 (x, y, w) 表示位置 m 到位置 y 的权值为 w。从位置 0 到 位置 n 可能有多条路径。我们定义一条路径的危险值为这条路径中所有的边的最大权值。请问从位置 0 到 位置 n 所有路径中最小的危险值为多少? 129 | 130 | - 最小危险值为最小生成树中 0 到 n 路径上的最大边权。 131 | 132 | ```Python 133 | # Kruskal's algorithm 134 | class Solution: 135 | def getMinRiskValue(self, N, M, X, Y, W): 136 | 137 | # Kruskal's algorithm with union-find 138 | parent = list(range(N + 1)) 139 | rank = [1] * (N + 1) 140 | 141 | def find(x): 142 | if parent[parent[x]] != parent[x]: 143 | parent[x] = find(parent[x]) 144 | return parent[x] 145 | 146 | def union(x, y): 147 | px, py = find(x), find(y) 148 | if px == py: 149 | return False 150 | 151 | if rank[px] > rank[py]: 152 | parent[py] = px 153 | elif rank[px] < rank[py]: 154 | parent[px] = py 155 | else: 156 | parent[px] = py 157 | rank[py] += 1 158 | 159 | return True 160 | 161 | edges = sorted(zip(W, X, Y)) 162 | 163 | for w, x, y in edges: 164 | if union(x, y) and find(0) == find(N): # early return without constructing MST 165 | return w 166 | ``` -------------------------------------------------------------------------------- /images/backtrack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dashidhy/algorithm-pattern-python/aa95b273ca64204c6c740c792982dd95b7010445/images/backtrack.png -------------------------------------------------------------------------------- /images/binary_search_template.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dashidhy/algorithm-pattern-python/aa95b273ca64204c6c740c792982dd95b7010445/images/binary_search_template.png -------------------------------------------------------------------------------- /images/cycled_linked_list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dashidhy/algorithm-pattern-python/aa95b273ca64204c6c740c792982dd95b7010445/images/cycled_linked_list.png -------------------------------------------------------------------------------- /images/dp_dc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dashidhy/algorithm-pattern-python/aa95b273ca64204c6c740c792982dd95b7010445/images/dp_dc.png -------------------------------------------------------------------------------- /images/dp_memory_search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dashidhy/algorithm-pattern-python/aa95b273ca64204c6c740c792982dd95b7010445/images/dp_memory_search.png -------------------------------------------------------------------------------- /images/dp_triangle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dashidhy/algorithm-pattern-python/aa95b273ca64204c6c740c792982dd95b7010445/images/dp_triangle.png -------------------------------------------------------------------------------- /images/fast_slow_linked_list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dashidhy/algorithm-pattern-python/aa95b273ca64204c6c740c792982dd95b7010445/images/fast_slow_linked_list.png -------------------------------------------------------------------------------- /images/heap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dashidhy/algorithm-pattern-python/aa95b273ca64204c6c740c792982dd95b7010445/images/heap.png -------------------------------------------------------------------------------- /images/leetcode_explore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dashidhy/algorithm-pattern-python/aa95b273ca64204c6c740c792982dd95b7010445/images/leetcode_explore.png -------------------------------------------------------------------------------- /images/leetcode_jzoffer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dashidhy/algorithm-pattern-python/aa95b273ca64204c6c740c792982dd95b7010445/images/leetcode_jzoffer.png -------------------------------------------------------------------------------- /images/leetcode_record.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dashidhy/algorithm-pattern-python/aa95b273ca64204c6c740c792982dd95b7010445/images/leetcode_record.png -------------------------------------------------------------------------------- /images/leetcode_time.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dashidhy/algorithm-pattern-python/aa95b273ca64204c6c740c792982dd95b7010445/images/leetcode_time.png -------------------------------------------------------------------------------- /images/repo_practice.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dashidhy/algorithm-pattern-python/aa95b273ca64204c6c740c792982dd95b7010445/images/repo_practice.png -------------------------------------------------------------------------------- /images/stack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dashidhy/algorithm-pattern-python/aa95b273ca64204c6c740c792982dd95b7010445/images/stack.png -------------------------------------------------------------------------------- /images/stack_rain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dashidhy/algorithm-pattern-python/aa95b273ca64204c6c740c792982dd95b7010445/images/stack_rain.png -------------------------------------------------------------------------------- /images/stack_rain2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dashidhy/algorithm-pattern-python/aa95b273ca64204c6c740c792982dd95b7010445/images/stack_rain2.png -------------------------------------------------------------------------------- /images/title.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dashidhy/algorithm-pattern-python/aa95b273ca64204c6c740c792982dd95b7010445/images/title.png -------------------------------------------------------------------------------- /images/tree_type.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dashidhy/algorithm-pattern-python/aa95b273ca64204c6c740c792982dd95b7010445/images/tree_type.png -------------------------------------------------------------------------------- /introduction/golang.md: -------------------------------------------------------------------------------- 1 | # GO 快速入门 2 | 3 | ## 基本语法 4 | 5 | [Go 语言圣经](https://books.studygolang.com/gopl-zh/) 6 | 7 | ## 常用库 8 | 9 | ### 切片 10 | 11 | go 通过切片模拟栈和队列 12 | 13 | 栈 14 | 15 | ```go 16 | // 创建栈 17 | stack:=make([]int,0) 18 | // push压入 19 | stack=append(stack,10) 20 | // pop弹出 21 | v:=stack[len(stack)-1] 22 | stack=stack[:len(stack)-1] 23 | // 检查栈空 24 | len(stack)==0 25 | ``` 26 | 27 | 队列 28 | 29 | ```go 30 | // 创建队列 31 | queue:=make([]int,0) 32 | // enqueue入队 33 | queue=append(queue,10) 34 | // dequeue出队 35 | v:=queue[0] 36 | queue=queue[1:] 37 | // 长度0为空 38 | len(queue)==0 39 | ``` 40 | 41 | 注意点 42 | 43 | - 参数传递,只能修改,不能新增或者删除原始数据 44 | - 默认 s=s[0:len(s)],取下限不取上限,数学表示为:[) 45 | 46 | ### 字典 47 | 48 | 基本用法 49 | 50 | ```go 51 | // 创建 52 | m:=make(map[string]int) 53 | // 设置kv 54 | m["hello"]=1 55 | // 删除k 56 | delete(m,"hello") 57 | // 遍历 58 | for k,v:=range m{ 59 | println(k,v) 60 | } 61 | ``` 62 | 63 | 注意点 64 | 65 | - map 键需要可比较,不能为 slice、map、function 66 | - map 值都有默认值,可以直接操作默认值,如:m[age]++ 值由 0 变为 1 67 | - 比较两个 map 需要遍历,其中的 kv 是否相同,因为有默认值关系,所以需要检查 val 和 ok 两个值 68 | 69 | ### 标准库 70 | 71 | sort 72 | 73 | ```go 74 | // int排序 75 | sort.Ints([]int{}) 76 | // 字符串排序 77 | sort.Strings([]string{}) 78 | // 自定义排序 79 | sort.Slice(s,func(i,j int)bool{return s[i] 给定一个  haystack 字符串和一个 needle 字符串,在 haystack 字符串中找出 needle 字符串出现的第一个位置 (从 0 开始)。如果不存在,则返回  -1。 15 | 16 | - 思路:核心点遍历给定字符串字符,判断以当前字符开头字符串是否等于目标字符串 17 | 18 | ```Python 19 | class Solution: 20 | def strStr(self, haystack: str, needle: str) -> int: 21 | L, n = len(needle), len(haystack) 22 | 23 | for start in range(n - L + 1): 24 | if haystack[start:start + L] == needle: 25 | return start 26 | return -1 27 | ``` 28 | 29 | 需要注意点 30 | 31 | - 循环时,i 不需要到 len-1 32 | - 如果找到目标字符串,len(needle) == j 33 | 34 | ### [示例 2:subsets](https://leetcode-cn.com/problems/subsets/) 35 | 36 | > 给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。 37 | 38 | - 思路:这是一个典型的应用回溯法的题目,简单来说就是穷尽所有可能性,算法模板如下 39 | 40 | ```go 41 | result = [] 42 | func backtrack(选择列表,路径): 43 | if 满足结束条件: 44 | result.add(路径) 45 | return 46 | for 选择 in 选择列表: 47 | 做选择 48 | backtrack(选择列表,路径) 49 | 撤销选择 50 | ``` 51 | 52 | - 通过不停的选择,撤销选择,来穷尽所有可能性,最后将满足条件的结果返回。答案代码: 53 | 54 | ```Python 55 | class Solution: 56 | def subsets(self, nums: List[int]) -> List[List[int]]: 57 | 58 | n = len(nums) 59 | result = [] 60 | 61 | def backtrack(start, k, route=[]): 62 | if len(route) == k: 63 | result.append(route.copy()) 64 | return 65 | 66 | for i in range(start, n): 67 | route.append(nums[i]) 68 | backtrack(i + 1, k) 69 | route.pop() 70 | 71 | return 72 | 73 | for k in range(n + 1): 74 | backtrack(0, k) 75 | 76 | return result 77 | ``` 78 | 79 | 说明:后面会深入讲解几个典型的回溯算法问题,如果当前不太了解可以暂时先跳过 80 | 81 | ## 面试注意点 82 | 83 | 我们大多数时候,刷算法题可能都是为了准备面试,所以面试的时候需要注意一些点 84 | 85 | - 快速定位到题目的知识点,找到知识点的**通用模板**,可能需要根据题目**特殊情况做特殊处理**。 86 | - 先去朝一个解决问题的方向!**先抛出可行解**,而不是最优解!先解决,再优化! 87 | - 代码的风格要统一,熟悉各类语言的代码规范。 88 | - 命名尽量简洁明了,尽量不用数字命名如:i1、node1、a1、b2 89 | - 常见错误总结 90 | - 访问下标时,不能访问越界 91 | - 空值 nil 问题 run time error 92 | 93 | ## 练习 94 | 95 | - [ ] [strStr](https://leetcode-cn.com/problems/implement-strstr/) 96 | - [ ] [subsets](https://leetcode-cn.com/problems/subsets/) 97 | -------------------------------------------------------------------------------- /practice_algorithm/bplus.md: -------------------------------------------------------------------------------- 1 | # b+ tree (MySQL 索引实现) 2 | -------------------------------------------------------------------------------- /practice_algorithm/data_index.md: -------------------------------------------------------------------------------- 1 | # 数据索引(kafka 稀疏索引) 2 | -------------------------------------------------------------------------------- /practice_algorithm/skiplist.md: -------------------------------------------------------------------------------- 1 | # skiplist(Redis Zset 实现) 2 | -------------------------------------------------------------------------------- /src/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | fmt.Println("hello world") 7 | } 8 | -------------------------------------------------------------------------------- /src/sort/heap_sort.go: -------------------------------------------------------------------------------- 1 | package sort 2 | 3 | func HeapSort(a []int) []int { 4 | // 1、无序数组a 5 | // 2、将无序数组a构建为一个大根堆 6 | for i := len(a)/2 - 1; i >= 0; i-- { 7 | sink(a, i, len(a)) 8 | } 9 | // 3、交换a[0]和a[len(a)-1] 10 | // 4、然后把前面这段数组继续下沉保持堆结构,如此循环即可 11 | for i := len(a) - 1; i >= 1; i-- { 12 | // 从后往前填充值 13 | swap(a, 0, i) 14 | // 前面的长度也减一 15 | sink(a, 0, i) 16 | } 17 | return a 18 | } 19 | func sink(a []int, i int, length int) { 20 | for { 21 | // 左节点索引(从0开始,所以左节点为i*2+1) 22 | l := i*2 + 1 23 | // 有节点索引 24 | r := i*2 + 2 25 | // idx保存根、左、右三者之间较大值的索引 26 | idx := i 27 | // 存在左节点,左节点值较大,则取左节点 28 | if l < length && a[l] > a[idx] { 29 | idx = l 30 | } 31 | // 存在有节点,且值较大,取右节点 32 | if r < length && a[r] > a[idx] { 33 | idx = r 34 | } 35 | // 如果根节点较大,则不用下沉 36 | if idx == i { 37 | break 38 | } 39 | // 如果根节点较小,则交换值,并继续下沉 40 | swap(a, i, idx) 41 | // 继续下沉idx节点 42 | i = idx 43 | } 44 | } 45 | func swap(a []int, i, j int) { 46 | a[i], a[j] = a[j], a[i] 47 | } 48 | -------------------------------------------------------------------------------- /src/sort/heap_sort_test.go: -------------------------------------------------------------------------------- 1 | package sort 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestHeapSort(t *testing.T) { 9 | type args struct { 10 | a []int 11 | } 12 | tests := []struct { 13 | name string 14 | args args 15 | want []int 16 | }{ 17 | {"", args{a: []int{7, 8, 9, 2, 3, 5}}, []int{2, 3, 5, 7, 8, 9}}, 18 | } 19 | for _, tt := range tests { 20 | t.Run(tt.name, func(t *testing.T) { 21 | if got := HeapSort(tt.args.a); !reflect.DeepEqual(got, tt.want) { 22 | t.Errorf("HeapSort() = %v, want %v", got, tt.want) 23 | } 24 | }) 25 | } 26 | } 27 | --------------------------------------------------------------------------------