├── .gitignore ├── LICENSE ├── README.md ├── SUMMARY.md ├── TODO.md ├── advanced_algorithm ├── backtrack.md ├── binary_search_tree.md ├── recursion.md └── slide_window.md ├── basic_algorithm ├── binary_search.md ├── dp.md └── sort.md ├── data_structure ├── binary_op.md ├── binary_tree.md ├── linked_list.md └── stack_queue.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 └── 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 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 greyireland 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 算法模板 2 | 3 | ![来刷题了](https://img.fuiboom.com/img/title.png) 4 | 5 | 算法模板,最科学的刷题方式,最快速的刷题路径,一个月从入门到 offer,你值得拥有 🐶~ 6 | 7 | 算法模板顾名思义就是刷题的套路模板,掌握了刷题模板之后,刷题也变得好玩起来了~ 8 | 9 | > 此项目是自己找工作时,从 0 开始刷 LeetCode 的心得记录,通过各种刷题文章、专栏、视频等总结了一套自己的刷题模板。 10 | > 11 | > 这个模板主要是介绍了一些通用的刷题模板,以及一些常见问题,如到底要刷多少题,按什么顺序来刷题,如何提高刷题效率等。 12 | 13 | ## 在线文档 14 | 15 | 在线文档 Gitbook:[算法模板 🔥](https://greyireland.gitbook.io/algorithm-pattern/) 16 | 17 | ## 核心内容 18 | 19 | ### 入门篇 🐶 20 | 21 | - [go 语言入门](./introduction/golang.md) 22 | - [算法快速入门](./introduction/quickstart.md) 23 | 24 | ### 数据结构篇 🐰 25 | 26 | - [二叉树](./data_structure/binary_tree.md) 27 | - [链表](./data_structure/linked_list.md) 28 | - [栈和队列](./data_structure/stack_queue.md) 29 | - [二进制](./data_structure/binary_op.md) 30 | 31 | ### 基础算法篇 🐮 32 | 33 | - [二分搜索](./basic_algorithm/binary_search.md) 34 | - [排序算法](./basic_algorithm/sort.md) 35 | - [动态规划](./basic_algorithm/dp.md) 36 | 37 | ### 算法思维 🦁 38 | 39 | - [递归思维](./advanced_algorithm/recursion.md) 40 | - [滑动窗口思想](./advanced_algorithm/slide_window.md) 41 | - [二叉搜索树](./advanced_algorithm/binary_search_tree.md) 42 | - [回溯法](./advanced_algorithm/backtrack.md) 43 | 44 | ## 心得体会 45 | 46 | 文章大部分是对题目的思路介绍,和一些问题的解析,有了思路还是需要自己手动写写的,所以每篇文章最后都有对应的练习题 47 | 48 | 刷完这些练习题,基本对数据结构和算法有自己的认识体会,基本大部分面试题都能写得出来,国内的 BAT、TMD 应该都不是问题 49 | 50 | 从 4 月份找工作开始,从 0 开始刷 LeetCode,中间大概花了一个半月(6 周)左右时间刷完 240 题。 51 | 52 | ![一个半月刷完240题](https://img.fuiboom.com/img/leetcode_time.png) 53 | 54 | ![刷题记录](https://img.fuiboom.com/img/leetcode_record.png) 55 | 56 | 开始刷题时,确实是无从下手,因为从序号开始刷,刷到几道题就遇到 hard 的题型,会卡住很久,后面去评论区看别人怎么刷题,也去 Google 搜索最好的刷题方式,发现按题型刷题会舒服很多,基本一个类型的题目,一天能做很多,慢慢刷题也不再枯燥,做起来也很有意思,最后也收到不错的 offer(最后去了宇宙系)。 57 | 58 | 回到最开始的问题,面试到底要刷多少题,其实这个取决于你想进什么样公司,你定的目标如果是国内一线大厂,个人感觉大概 200 至 300 题基本就满足大部分面试需要了。第二个问题是按什么顺序刷及如何提高效率,这个也是本 repo 的目的,给你指定了一个刷题的顺序,以及刷题的模板,有了方向和技巧后,就去动手吧~ 希望刷完之后,你也能自己总结一套属于自己的刷题模板,有所收获,有所成长~ 59 | 60 | ## 推荐的刷题路径 61 | 62 | 按此 repo 目录刷一遍,如果中间有题目卡住了先跳过,然后刷题一遍 LeetCode 探索基础卡片,最后快要面试时刷题一遍剑指 offer。 63 | 64 | 为什么这么要这么刷,因为 repo 里面的题目是按类型归类,都是一些常见的高频题,很有代表性,大部分都是可以用模板加一点变形做出来,刷完后对大部分题目有基本的认识。然后刷一遍探索卡片,巩固一下一些基础知识点,总结这些知识点。最后剑指 offer 是大部分公司的出题源头,刷完面试中基本会遇到现题或者变形题,基本刷完这三部分,大部分国内公司的面试题应该就没什么问题了~ 65 | 66 | 1、 [algorithm-pattern 练习题](https://greyireland.gitbook.io/algorithm-pattern/) 67 | 68 | ![练习题](https://img.fuiboom.com/img/repo_practice.png) 69 | 70 | 2、 [LeetCode 卡片](https://leetcode-cn.com/explore/) 71 | 72 | ![探索卡片](https://img.fuiboom.com/img/leetcode_explore.png) 73 | 74 | 3、 [剑指 offer](https://leetcode-cn.com/problemset/lcof/) 75 | 76 | ![剑指offer](https://img.fuiboom.com/img/leetcode_jzoffer.png) 77 | 78 | 刷题时间可以合理分配,如果打算准备面试了,建议前面两部分 一个半月 (6 周)时间刷完,最后剑指 offer 半个月刷完,边刷可以边投简历进行面试,遇到不会的不用着急,往模板上套就对了,如果面试管给你提示,那就好好做,不要错过这大好机会~ 79 | 80 | > 注意点:如果为了找工作刷题,遇到 hard 的题如果有思路就做,没思路先跳过,先把基础打好,再来刷 hard 可能效果会更好~ 81 | 82 | ## 面试资源 83 | 84 | 分享一些计算机的经典书籍,大部分对面试应该都有帮助,强烈推荐 🌝 85 | 86 | [我看过的 100 本书](https://github.com/greyireland/awesome-programming-books-1) 87 | 88 | ## 更新计划 89 | 90 | 持续更新中,觉得还可以的话点个 **star** 收藏呀 ⭐️~ 91 | 92 | 【 Github 】[https://github.com/greyireland/algorithm-pattern](https://github.com/greyireland/algorithm-pattern) ⭐️ 93 | 94 | ## 完成打卡 95 | 96 | 完成计划之后,可以提交 Pull requests,在下面添加自己的项目仓库,完成自己的算法模板打卡呀~ 97 | 98 | | 完成 | 用户 | 项目地址 | 99 | | ---- | ------------------------------------------------- | ------------------------------------------------------------------- | 100 | | ✅ | [easyui](https://github.com/easyui/) | [algorithm-pattern-swift(Swift 实现)](https://github.com/easyui/algorithm-pattern-swift),[在线文档 Gitbook](https://zyj.gitbook.io/algorithm-pattern-swift/) | 101 | | ✅ | [wardseptember](https://github.com/wardseptember) | [notes(Java 实现)](https://github.com/wardseptember/notes) | 102 | | ✅ | [dashidhy](https://github.com/dashidhy) | [algorithm-pattern-python(Python 实现)](https://github.com/dashidhy/algorithm-pattern-python) | 103 | | ✅ | [binzi56](https://github.com/binzi56) | [algorithm-pattern-c(c++ 实现)](https://github.com/binzi56/algorithm-pattern-c) | 104 | | ✅ | [lvseouren](https://github.com/lvseouren) | [algorithm-study-record(c++ 实现)](https://github.com/lvseouren/algorithm-study-record) | 105 | | ✅ | [chienmy](https://github.com/chienmy) | [algorithm-pattern-java(Java 实现)](https://github.com/chienmy/algorithm-pattern-java), [在线文档 Gitbook](https://chienmy.gitbook.io/algorithm-pattern-java/) | 106 | | ✅ | [ligecarryme](https://github.com/ligecarryme) | [algorithm-pattern-JavaScript(JS+TS实现)](https://github.com/ligecarryme/algorithm-pattern-JavaScript) | 107 | | ✅ | [Esdeath](https://github.com/Esdeath) | [algorithm-pattern-dart(dart实现)](https://github.com/Esdeath/algorithm-pattern-dart),[在线文档 Gitbook](https://ayaseeri.gitbook.io/algorithm-pattern-dart/) | 108 | | ✅ | [longpi1](https://github.com/longpi1) | [algorithm-pattern-golang(golang实现)](https://github.com/longpi1/algorithm-pattern) 109 | | ✅ | [tpxxn](https://github.com/tpxxn) | [algorithm-pattern-CSharp(C# 实现)](https://github.com/tpxxn/algorithm-pattern-CSharp) -------------------------------------------------------------------------------- /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 | ```go 34 | func subsets(nums []int) [][]int { 35 | // 保存最终结果 36 | result := make([][]int, 0) 37 | // 保存中间结果 38 | list := make([]int, 0) 39 | backtrack(nums, 0, list, &result) 40 | return result 41 | } 42 | 43 | // nums 给定的集合 44 | // pos 下次添加到集合中的元素位置索引 45 | // list 临时结果集合(每次需要复制保存) 46 | // result 最终结果 47 | func backtrack(nums []int, pos int, list []int, result *[][]int) { 48 | // 把临时结果复制出来保存到最终结果 49 | ans := make([]int, len(list)) 50 | copy(ans, list) 51 | *result = append(*result, ans) 52 | // 选择、处理结果、再撤销选择 53 | for i := pos; i < len(nums); i++ { 54 | list = append(list, nums[i]) 55 | backtrack(nums, i+1, list, result) 56 | list = list[0 : len(list)-1] 57 | } 58 | } 59 | ``` 60 | 61 | ### [subsets-ii](https://leetcode-cn.com/problems/subsets-ii/) 62 | 63 | > 给定一个可能包含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。说明:解集不能包含重复的子集。 64 | 65 | ```go 66 | import ( 67 | "sort" 68 | ) 69 | 70 | func subsetsWithDup(nums []int) [][]int { 71 | // 保存最终结果 72 | result := make([][]int, 0) 73 | // 保存中间结果 74 | list := make([]int, 0) 75 | // 先排序 76 | sort.Ints(nums) 77 | backtrack(nums, 0, list, &result) 78 | return result 79 | } 80 | 81 | // nums 给定的集合 82 | // pos 下次添加到集合中的元素位置索引 83 | // list 临时结果集合(每次需要复制保存) 84 | // result 最终结果 85 | func backtrack(nums []int, pos int, list []int, result *[][]int) { 86 | // 把临时结果复制出来保存到最终结果 87 | ans := make([]int, len(list)) 88 | copy(ans, list) 89 | *result = append(*result, ans) 90 | // 选择时需要剪枝、处理、撤销选择 91 | for i := pos; i < len(nums); i++ { 92 | // 排序之后,如果再遇到重复元素,则不选择此元素 93 | if i != pos && nums[i] == nums[i-1] { 94 | continue 95 | } 96 | list = append(list, nums[i]) 97 | backtrack(nums, i+1, list, result) 98 | list = list[0 : len(list)-1] 99 | } 100 | } 101 | ``` 102 | 103 | ### [permutations](https://leetcode-cn.com/problems/permutations/) 104 | 105 | > 给定一个   没有重复   数字的序列,返回其所有可能的全排列。 106 | 107 | 思路:需要记录已经选择过的元素,满足条件的结果才进行返回 108 | 109 | ```go 110 | func permute(nums []int) [][]int { 111 | result := make([][]int, 0) 112 | list := make([]int, 0) 113 | // 标记这个元素是否已经添加到结果集 114 | visited := make([]bool, len(nums)) 115 | backtrack(nums, visited, list, &result) 116 | return result 117 | } 118 | 119 | // nums 输入集合 120 | // visited 当前递归标记过的元素 121 | // list 临时结果集(路径) 122 | // result 最终结果 123 | func backtrack(nums []int, visited []bool, list []int, result *[][]int) { 124 | // 返回条件:临时结果和输入集合长度一致 才是全排列 125 | if len(list) == len(nums) { 126 | ans := make([]int, len(list)) 127 | copy(ans, list) 128 | *result = append(*result, ans) 129 | return 130 | } 131 | for i := 0; i < len(nums); i++ { 132 | // 已经添加过的元素,直接跳过 133 | if visited[i] { 134 | continue 135 | } 136 | // 添加元素 137 | list = append(list, nums[i]) 138 | visited[i] = true 139 | backtrack(nums, visited, list, result) 140 | // 移除元素 141 | visited[i] = false 142 | list = list[0 : len(list)-1] 143 | } 144 | } 145 | ``` 146 | 147 | ### [permutations-ii](https://leetcode-cn.com/problems/permutations-ii/) 148 | 149 | > 给定一个可包含重复数字的序列,返回所有不重复的全排列。 150 | 151 | ```go 152 | import ( 153 | "sort" 154 | ) 155 | 156 | func permuteUnique(nums []int) [][]int { 157 | result := make([][]int, 0) 158 | list := make([]int, 0) 159 | // 标记这个元素是否已经添加到结果集 160 | visited := make([]bool, len(nums)) 161 | sort.Ints(nums) 162 | backtrack(nums, visited, list, &result) 163 | return result 164 | } 165 | 166 | // nums 输入集合 167 | // visited 当前递归标记过的元素 168 | // list 临时结果集 169 | // result 最终结果 170 | func backtrack(nums []int, visited []bool, list []int, result *[][]int) { 171 | // 临时结果和输入集合长度一致 才是全排列 172 | if len(list) == len(nums) { 173 | subResult := make([]int, len(list)) 174 | copy(subResult, list) 175 | *result = append(*result, subResult) 176 | } 177 | for i := 0; i < len(nums); i++ { 178 | // 已经添加过的元素,直接跳过 179 | if visited[i] { 180 | continue 181 | } 182 | // 上一个元素和当前相同,并且没有访问过就跳过 183 | if i != 0 && nums[i] == nums[i-1] && !visited[i-1] { 184 | continue 185 | } 186 | list = append(list, nums[i]) 187 | visited[i] = true 188 | backtrack(nums, visited, list, result) 189 | visited[i] = false 190 | list = list[0 : len(list)-1] 191 | } 192 | } 193 | ``` 194 | 195 | ## 练习 196 | 197 | - [ ] [subsets](https://leetcode-cn.com/problems/subsets/) 198 | - [ ] [subsets-ii](https://leetcode-cn.com/problems/subsets-ii/) 199 | - [ ] [permutations](https://leetcode-cn.com/problems/permutations/) 200 | - [ ] [permutations-ii](https://leetcode-cn.com/problems/permutations-ii/) 201 | 202 | 挑战题目 203 | 204 | - [ ] [combination-sum](https://leetcode-cn.com/problems/combination-sum/) 205 | - [ ] [letter-combinations-of-a-phone-number](https://leetcode-cn.com/problems/letter-combinations-of-a-phone-number/) 206 | - [ ] [palindrome-partitioning](https://leetcode-cn.com/problems/palindrome-partitioning/) 207 | - [ ] [restore-ip-addresses](https://leetcode-cn.com/problems/restore-ip-addresses/) 208 | - [ ] [permutations](https://leetcode-cn.com/problems/permutations/) 209 | -------------------------------------------------------------------------------- /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 | ```go 15 | /** 16 | * Definition for a binary tree node. 17 | * type TreeNode struct { 18 | * Val int 19 | * Left *TreeNode 20 | * Right *TreeNode 21 | * } 22 | */ 23 | func isValidBST(root *TreeNode) bool { 24 | return dfs(root).valid 25 | } 26 | type ResultType struct{ 27 | max int 28 | min int 29 | valid bool 30 | } 31 | func dfs(root *TreeNode)(result ResultType){ 32 | if root==nil{ 33 | result.max=-1<<63 34 | result.min=1<<63-1 35 | result.valid=true 36 | return 37 | } 38 | 39 | left:=dfs(root.Left) 40 | right:=dfs(root.Right) 41 | 42 | // 1、满足左边最大值left.max && root.Valb{ 53 | return a 54 | } 55 | return b 56 | } 57 | func Min(a,b int)int{ 58 | if a>b{ 59 | return b 60 | } 61 | return a 62 | } 63 | 64 | ``` 65 | 66 | [insert-into-a-binary-search-tree](https://leetcode-cn.com/problems/insert-into-a-binary-search-tree/) 67 | 68 | > 给定二叉搜索树(BST)的根节点和要插入树中的值,将值插入二叉搜索树。 返回插入后二叉搜索树的根节点。 保证原始二叉搜索树中不存在新值。 69 | 70 | ```go 71 | func insertIntoBST(root *TreeNode, val int) *TreeNode { 72 | if root==nil{ 73 | return &TreeNode{Val:val} 74 | } 75 | if root.Val 给定一个二叉搜索树的根节点 root 和一个值 key,删除二叉搜索树中的  key  对应的节点,并保证二叉搜索树的性质不变。返回二叉搜索树(有可能被更新)的根节点的引用。 87 | 88 | ```go 89 | /** 90 | * Definition for a binary tree node. 91 | * type TreeNode struct { 92 | * Val int 93 | * Left *TreeNode 94 | * Right *TreeNode 95 | * } 96 | */ 97 | func deleteNode(root *TreeNode, key int) *TreeNode { 98 | // 删除节点分为三种情况: 99 | // 1、只有左节点 替换为右 100 | // 2、只有右节点 替换为左 101 | // 3、有左右子节点 左子节点连接到右边最左节点即可 102 | if root ==nil{ 103 | return root 104 | } 105 | if root.Valkey{ 108 | root.Left=deleteNode(root.Left,key) 109 | }else if root.Val==key{ 110 | if root.Left==nil{ 111 | return root.Right 112 | }else if root.Right==nil{ 113 | return root.Left 114 | }else{ 115 | cur:=root.Right 116 | // 一直向左找到最后一个左节点即可 117 | for cur.Left!=nil{ 118 | cur=cur.Left 119 | } 120 | cur.Left=root.Left 121 | return root.Right 122 | } 123 | } 124 | return root 125 | } 126 | ``` 127 | 128 | [balanced-binary-tree](https://leetcode-cn.com/problems/balanced-binary-tree/) 129 | 130 | > 给定一个二叉树,判断它是否是高度平衡的二叉树。 131 | 132 | ```go 133 | type ResultType struct{ 134 | height int 135 | valid bool 136 | } 137 | func isBalanced(root *TreeNode) bool { 138 | return dfs(root).valid 139 | } 140 | func dfs(root *TreeNode)(result ResultType){ 141 | if root==nil{ 142 | result.valid=true 143 | result.height=0 144 | return 145 | } 146 | left:=dfs(root.Left) 147 | right:=dfs(root.Right) 148 | // 满足所有特点:二叉搜索树&&平衡 149 | if left.valid&&right.valid&&abs(left.height,right.height)<=1{ 150 | result.valid=true 151 | } 152 | result.height=Max(left.height,right.height)+1 153 | return 154 | } 155 | func abs(a,b int)int{ 156 | if a>b{ 157 | return a-b 158 | } 159 | return b-a 160 | } 161 | func Max(a,b int)int{ 162 | if a>b{ 163 | return a 164 | } 165 | return b 166 | } 167 | 168 | ``` 169 | 170 | ## 练习 171 | 172 | - [ ] [validate-binary-search-tree](https://leetcode-cn.com/problems/validate-binary-search-tree/) 173 | - [ ] [insert-into-a-binary-search-tree](https://leetcode-cn.com/problems/insert-into-a-binary-search-tree/) 174 | - [ ] [delete-node-in-a-bst](https://leetcode-cn.com/problems/delete-node-in-a-bst/) 175 | - [ ] [balanced-binary-tree](https://leetcode-cn.com/problems/balanced-binary-tree/) 176 | -------------------------------------------------------------------------------- /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 | ```go 14 | func reverseString(s []byte) { 15 | res := make([]byte, 0) 16 | reverse(s, 0, &res) 17 | for i := 0; i < len(s); i++ { 18 | s[i] = res[i] 19 | } 20 | } 21 | func reverse(s []byte, i int, res *[]byte) { 22 | if i == len(s) { 23 | return 24 | } 25 | reverse(s, i+1, res) 26 | *res = append(*res, s[i]) 27 | } 28 | ``` 29 | 30 | [swap-nodes-in-pairs](https://leetcode-cn.com/problems/swap-nodes-in-pairs/) 31 | 32 | > 给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。 33 | > **你不能只是单纯的改变节点内部的值**,而是需要实际的进行节点交换。 34 | 35 | ```go 36 | func swapPairs(head *ListNode) *ListNode { 37 | // 思路:将链表翻转转化为一个子问题,然后通过递归方式依次解决 38 | // 先翻转两个,然后将后面的节点继续这样翻转,然后将这些翻转后的节点连接起来 39 | return helper(head) 40 | } 41 | func helper(head *ListNode)*ListNode{ 42 | if head==nil||head.Next==nil{ 43 | return head 44 | } 45 | // 保存下一阶段的头指针 46 | nextHead:=head.Next.Next 47 | // 翻转当前阶段指针 48 | next:=head.Next 49 | next.Next=head 50 | head.Next=helper(nextHead) 51 | return next 52 | } 53 | ``` 54 | 55 | [unique-binary-search-trees-ii](https://leetcode-cn.com/problems/unique-binary-search-trees-ii/) 56 | 57 | > 给定一个整数 n,生成所有由 1 ... n 为节点所组成的二叉搜索树。 58 | 59 | ```go 60 | func generateTrees(n int) []*TreeNode { 61 | if n==0{ 62 | return nil 63 | } 64 | return generate(1,n) 65 | 66 | } 67 | func generate(start,end int)[]*TreeNode{ 68 | if start>end{ 69 | return []*TreeNode{nil} 70 | } 71 | ans:=make([]*TreeNode,0) 72 | for i:=start;i<=end;i++{ 73 | // 递归生成所有左右子树 74 | lefts:=generate(start,i-1) 75 | rights:=generate(i+1,end) 76 | // 拼接左右子树后返回 77 | for j:=0;j 斐波那契数,通常用  F(n) 表示,形成的序列称为斐波那契数列。该数列由  0 和 1 开始,后面的每一项数字都是前面两项数字的和。也就是: 95 | > F(0) = 0,   F(1) = 1 96 | > F(N) = F(N - 1) + F(N - 2), 其中 N > 1. 97 | > 给定  N,计算  F(N)。 98 | 99 | ```go 100 | func fib(N int) int { 101 | return dfs(N) 102 | } 103 | var m map[int]int=make(map[int]int) 104 | func dfs(n int)int{ 105 | if n < 2{ 106 | return n 107 | } 108 | // 读取缓存 109 | if m[n]!=0{ 110 | return m[n] 111 | } 112 | ans:=dfs(n-2)+dfs(n-1) 113 | // 缓存已经计算过的值 114 | m[n]=ans 115 | return ans 116 | } 117 | ``` 118 | 119 | ## 练习 120 | 121 | - [ ] [reverse-string](https://leetcode-cn.com/problems/reverse-string/) 122 | - [ ] [swap-nodes-in-pairs](https://leetcode-cn.com/problems/swap-nodes-in-pairs/) 123 | - [ ] [unique-binary-search-trees-ii](https://leetcode-cn.com/problems/unique-binary-search-trees-ii/) 124 | - [ ] [fibonacci-number](https://leetcode-cn.com/problems/fibonacci-number/) 125 | -------------------------------------------------------------------------------- /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 | ```go 52 | func minWindow(s string, t string) string { 53 | // 保存滑动窗口字符集 54 | win := make(map[byte]int) 55 | // 保存需要的字符集 56 | need := make(map[byte]int) 57 | for i := 0; i < len(t); i++ { 58 | need[t[i]]++ 59 | } 60 | // 窗口 61 | left := 0 62 | right := 0 63 | // match匹配次数 64 | match := 0 65 | start := 0 66 | end := 0 67 | min := math.MaxInt64 68 | var c byte 69 | for right < len(s) { 70 | c = s[right] 71 | right++ 72 | // 在需要的字符集里面,添加到窗口字符集里面 73 | if need[c] != 0 { 74 | win[c]++ 75 | // 如果当前字符的数量匹配需要的字符的数量,则match值+1 76 | if win[c] == need[c] { 77 | match++ 78 | } 79 | } 80 | 81 | // 当所有字符数量都匹配之后,开始缩紧窗口 82 | for match == len(need) { 83 | // 获取结果 84 | if right-left < min { 85 | min = right - left 86 | start = left 87 | end = right 88 | } 89 | c = s[left] 90 | left++ 91 | // 左指针指向不在需要字符集则直接跳过 92 | if need[c] != 0 { 93 | // 左指针指向字符数量和需要的字符相等时,右移之后match值就不匹配则减一 94 | // 因为win里面的字符数可能比较多,如有10个A,但需要的字符数量可能为3 95 | // 所以在压死骆驼的最后一根稻草时,match才减一,这时候才跳出循环 96 | if win[c] == need[c] { 97 | match-- 98 | } 99 | win[c]-- 100 | } 101 | } 102 | } 103 | if min == math.MaxInt64 { 104 | return "" 105 | } 106 | return s[start:end] 107 | } 108 | ``` 109 | 110 | [permutation-in-string](https://leetcode-cn.com/problems/permutation-in-string/) 111 | 112 | > 给定两个字符串  **s1**  和  **s2**,写一个函数来判断  **s2**  是否包含  **s1 **的排列。 113 | 114 | ```go 115 | func checkInclusion(s1 string, s2 string) bool { 116 | win := make(map[byte]int) 117 | need := make(map[byte]int) 118 | for i := 0; i < len(s1); i++ { 119 | need[s1[i]]++ 120 | } 121 | left := 0 122 | right := 0 123 | match := 0 124 | for right < len(s2) { 125 | c := s2[right] 126 | right++ 127 | if need[c] != 0 { 128 | win[c]++ 129 | if win[c] == need[c] { 130 | match++ 131 | } 132 | } 133 | // 当窗口长度大于字符串长度,缩紧窗口 134 | for right-left >= len(s1) { 135 | // 当窗口长度和字符串匹配,并且里面每个字符数量也匹配时,满足条件 136 | if match == len(need) { 137 | return true 138 | } 139 | d := s2[left] 140 | left++ 141 | if need[d] != 0 { 142 | if win[d] == need[d] { 143 | match-- 144 | } 145 | win[d]-- 146 | } 147 | } 148 | } 149 | return false 150 | } 151 | 152 | ``` 153 | 154 | [find-all-anagrams-in-a-string](https://leetcode-cn.com/problems/find-all-anagrams-in-a-string/) 155 | 156 | > 给定一个字符串  **s **和一个非空字符串  **p**,找到  **s **中所有是  **p **的字母异位词的子串,返回这些子串的起始索引。 157 | 158 | ```go 159 | func findAnagrams(s string, p string) []int { 160 | win := make(map[byte]int) 161 | need := make(map[byte]int) 162 | for i := 0; i < len(p); i++ { 163 | need[p[i]]++ 164 | } 165 | left := 0 166 | right := 0 167 | match := 0 168 | ans:=make([]int,0) 169 | for right < len(s) { 170 | c := s[right] 171 | right++ 172 | if need[c] != 0 { 173 | win[c]++ 174 | if win[c] == need[c] { 175 | match++ 176 | } 177 | } 178 | // 当窗口长度大于字符串长度,缩紧窗口 179 | for right-left >= len(p) { 180 | // 当窗口长度和字符串匹配,并且里面每个字符数量也匹配时,满足条件 181 | if right-left == len(p)&& match == len(need) { 182 | ans=append(ans,left) 183 | } 184 | d := s[left] 185 | left++ 186 | if need[d] != 0 { 187 | if win[d] == need[d] { 188 | match-- 189 | } 190 | win[d]-- 191 | } 192 | } 193 | } 194 | return ans 195 | } 196 | ``` 197 | 198 | [longest-substring-without-repeating-characters](https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/) 199 | 200 | > 给定一个字符串,请你找出其中不含有重复字符的   最长子串   的长度。 201 | > 示例  1: 202 | > 203 | > 输入: "abcabcbb" 204 | > 输出: 3 205 | > 解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。 206 | 207 | ```go 208 | func lengthOfLongestSubstring(s string) int { 209 | // 滑动窗口核心点:1、右指针右移 2、根据题意收缩窗口 3、左指针右移更新窗口 4、根据题意计算结果 210 | if len(s)==0{ 211 | return 0 212 | } 213 | win:=make(map[byte]int) 214 | left:=0 215 | right:=0 216 | ans:=1 217 | for right1{ 223 | d:=s[left] 224 | left++ 225 | win[d]-- 226 | } 227 | // 计算结果 228 | ans=max(right-left,ans) 229 | } 230 | return ans 231 | } 232 | func max(a,b int)int{ 233 | if a>b{ 234 | return a 235 | } 236 | return b 237 | } 238 | ``` 239 | 240 | ## 总结 241 | 242 | - 和双指针题目类似,更像双指针的升级版,滑动窗口核心点是维护一个窗口集,根据窗口集来进行处理 243 | - 核心步骤 244 | - right 右移 245 | - 收缩 246 | - left 右移 247 | - 求结果 248 | 249 | ## 练习 250 | 251 | - [ ] [minimum-window-substring](https://leetcode-cn.com/problems/minimum-window-substring/) 252 | - [ ] [permutation-in-string](https://leetcode-cn.com/problems/permutation-in-string/) 253 | - [ ] [find-all-anagrams-in-a-string](https://leetcode-cn.com/problems/find-all-anagrams-in-a-string/) 254 | - [ ] [longest-substring-without-repeating-characters](https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/) 255 | -------------------------------------------------------------------------------- /basic_algorithm/binary_search.md: -------------------------------------------------------------------------------- 1 | # 二分搜索 2 | 3 | ## 二分搜索模板 4 | 5 | 给一个**有序数组**和目标值,找第一次/最后一次/任何一次出现的索引,如果没有出现返回-1 6 | 7 | 模板四点要素 8 | 9 | - 1、初始化:start=0、end=len-1 10 | - 2、循环退出条件:start + 1 < end 11 | - 3、比较中点和目标值:A[mid] ==、 <、> target 12 | - 4、判断最后两个元素是否符合:A[start]、A[end] ? target 13 | 14 | 时间复杂度 O(logn),使用场景一般是有序数组的查找 15 | 16 | 典型示例 17 | 18 | [binary-search](https://leetcode-cn.com/problems/binary-search/) 19 | 20 | > 给定一个  n  个元素有序的(升序)整型数组  nums 和一个目标值  target  ,写一个函数搜索  nums  中的 target,如果目标值存在返回下标,否则返回 -1。 21 | 22 | ```go 23 | // 二分搜索最常用模板 24 | func search(nums []int, target int) int { 25 | // 1、初始化start、end 26 | start := 0 27 | end := len(nums) - 1 28 | // 2、处理for循环 29 | for start+1 < end { 30 | mid := start + (end-start)/2 31 | // 3、比较a[mid]和target值 32 | if nums[mid] == target { 33 | end = mid 34 | } else if nums[mid] < target { 35 | start = mid 36 | } else if nums[mid] > target { 37 | end = mid 38 | } 39 | } 40 | // 4、最后剩下两个元素,手动判断 41 | if nums[start] == target { 42 | return start 43 | } 44 | if nums[end] == target { 45 | return end 46 | } 47 | return -1 48 | } 49 | ``` 50 | 51 | 大部分二分查找类的题目都可以用这个模板,然后做一点特殊逻辑即可 52 | 53 | 另外二分查找还有一些其他模板如下图,大部分场景模板#3 都能解决问题,而且还能找第一次/最后一次出现的位置,应用更加广泛 54 | 55 | ![binary_search_template](https://img.fuiboom.com/img/binary_search_template.png) 56 | 57 | 所以用模板#3 就对了,详细的对比可以这边文章介绍:[二分搜索模板](https://leetcode-cn.com/explore/learn/card/binary-search/212/template-analysis/847/) 58 | 59 | 如果是最简单的二分搜索,不需要找第一个、最后一个位置、或者是没有重复元素,可以使用模板#1,代码更简洁 60 | 61 | ```go 62 | // 无重复元素搜索时,更方便 63 | func search(nums []int, target int) int { 64 | start := 0 65 | end := len(nums) - 1 66 | for start <= end { 67 | mid := start + (end-start)/2 68 | if nums[mid] == target { 69 | return mid 70 | } else if nums[mid] < target { 71 | start = mid+1 72 | } else if nums[mid] > target { 73 | end = mid-1 74 | } 75 | } 76 | // 如果找不到,start 是第一个大于target的索引 77 | // 如果在B+树结构里面二分搜索,可以return start 78 | // 这样可以继续向子节点搜索,如:node:=node.Children[start] 79 | return -1 80 | } 81 | ``` 82 | 83 | ## 常见题目 84 | 85 | ### [search-for-range](https://www.lintcode.com/problem/search-for-a-range/description) 86 | 87 | > 给定一个包含 n 个整数的排序数组,找出给定目标值 target 的起始和结束位置。 88 | > 如果目标值不在数组中,则返回`[-1, -1]` 89 | 90 | 思路:核心点就是找第一个 target 的索引,和最后一个 target 的索引,所以用两次二分搜索分别找第一次和最后一次的位置 91 | 92 | ```go 93 | func searchRange (A []int, target int) []int { 94 | if len(A) == 0 { 95 | return []int{-1, -1} 96 | } 97 | result := make([]int, 2) 98 | start := 0 99 | end := len(A) - 1 100 | for start+1 < end { 101 | mid := start + (end-start)/2 102 | if A[mid] > target { 103 | end = mid 104 | } else if A[mid] < target { 105 | start = mid 106 | } else { 107 | // 如果相等,应该继续向左找,就能找到第一个目标值的位置 108 | end = mid 109 | } 110 | } 111 | // 搜索左边的索引 112 | if A[start] == target { 113 | result[0] = start 114 | } else if A[end] == target { 115 | result[0] = end 116 | } else { 117 | result[0] = -1 118 | result[1] = -1 119 | return result 120 | } 121 | start = 0 122 | end = len(A) - 1 123 | for start+1 < end { 124 | mid := start + (end-start)/2 125 | if A[mid] > target { 126 | end = mid 127 | } else if A[mid] < target { 128 | start = mid 129 | } else { 130 | // 如果相等,应该继续向右找,就能找到最后一个目标值的位置 131 | start = mid 132 | } 133 | } 134 | // 搜索右边的索引 135 | if A[end] == target { 136 | result[1] = end 137 | } else if A[start] == target { 138 | result[1] = start 139 | } else { 140 | result[0] = -1 141 | result[1] = -1 142 | return result 143 | } 144 | return result 145 | } 146 | ``` 147 | 148 | ### [search-insert-position](https://leetcode-cn.com/problems/search-insert-position/) 149 | 150 | > 给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。 151 | 152 | ```go 153 | func searchInsert(nums []int, target int) int { 154 | // 思路:找到第一个 >= target 的元素位置 155 | start := 0 156 | end := len(nums) - 1 157 | for start+1 < end { 158 | mid := start + (end-start)/2 159 | if nums[mid] == target { 160 | // 标记开始位置 161 | start = mid 162 | } else if nums[mid] > target { 163 | end = mid 164 | } else { 165 | start = mid 166 | } 167 | } 168 | if nums[start] >= target { 169 | return start 170 | } else if nums[end] >= target { 171 | return end 172 | } else if nums[end] < target { // 目标值比所有值都大 173 | return end + 1 174 | } 175 | return 0 176 | } 177 | ``` 178 | 179 | ### [search-a-2d-matrix](https://leetcode-cn.com/problems/search-a-2d-matrix/) 180 | 181 | > 编写一个高效的算法来判断  m x n  矩阵中,是否存在一个目标值。该矩阵具有如下特性: 182 | > 183 | > - 每行中的整数从左到右按升序排列。 184 | > - 每行的第一个整数大于前一行的最后一个整数。 185 | 186 | ```go 187 | func searchMatrix(matrix [][]int, target int) bool { 188 | // 思路:将2纬数组转为1维数组 进行二分搜索 189 | if len(matrix) == 0 || len(matrix[0]) == 0 { 190 | return false 191 | } 192 | row := len(matrix) 193 | col := len(matrix[0]) 194 | start := 0 195 | end := row*col - 1 196 | for start+1 < end { 197 | mid := start + (end-start)/2 198 | // 获取2纬数组对应值 199 | val := matrix[mid/col][mid%col] 200 | if val > target { 201 | end = mid 202 | } else if val < target { 203 | start = mid 204 | } else { 205 | return true 206 | } 207 | } 208 | if matrix[start/col][start%col] == target || matrix[end/col][end%col] == target{ 209 | return true 210 | } 211 | return false 212 | } 213 | ``` 214 | 215 | ### [first-bad-version](https://leetcode-cn.com/problems/first-bad-version/) 216 | 217 | > 假设你有 n 个版本 [1, 2, ..., n],你想找出导致之后所有版本出错的第一个错误的版本。 218 | > 你可以通过调用  bool isBadVersion(version)  接口来判断版本号 version 是否在单元测试中出错。实现一个函数来查找第一个错误的版本。你应该尽量减少对调用 API 的次数。 219 | 220 | ```go 221 | func firstBadVersion(n int) int { 222 | // 思路:二分搜索 223 | start := 0 224 | end := n 225 | for start+1 < end { 226 | mid := start + (end - start)/2 227 | if isBadVersion(mid) { 228 | end = mid 229 | } else if isBadVersion(mid) == false { 230 | start = mid 231 | } 232 | } 233 | if isBadVersion(start) { 234 | return start 235 | } 236 | return end 237 | } 238 | ``` 239 | 240 | ### [find-minimum-in-rotated-sorted-array](https://leetcode-cn.com/problems/find-minimum-in-rotated-sorted-array/) 241 | 242 | > 假设按照升序排序的数组在预先未知的某个点上进行了旋转( 例如,数组  [0,1,2,4,5,6,7] 可能变为  [4,5,6,7,0,1,2] )。 243 | > 请找出其中最小的元素。 244 | 245 | ```go 246 | func findMin(nums []int) int { 247 | // 思路:/ / 最后一个值作为target,然后往左移动,最后比较start、end的值 248 | if len(nums) == 0 { 249 | return -1 250 | } 251 | start := 0 252 | end := len(nums) - 1 253 | 254 | for start+1 < end { 255 | mid := start + (end-start)/2 256 | // 最后一个元素值为target 257 | if nums[mid] <= nums[end] { 258 | end = mid 259 | } else { 260 | start = mid 261 | } 262 | } 263 | if nums[start] > nums[end] { 264 | return nums[end] 265 | } 266 | return nums[start] 267 | } 268 | ``` 269 | 270 | ### [find-minimum-in-rotated-sorted-array-ii](https://leetcode-cn.com/problems/find-minimum-in-rotated-sorted-array-ii/) 271 | 272 | > 假设按照升序排序的数组在预先未知的某个点上进行了旋转 273 | > ( 例如,数组  [0,1,2,4,5,6,7] 可能变为  [4,5,6,7,0,1,2] )。 274 | > 请找出其中最小的元素。(包含重复元素) 275 | 276 | ```go 277 | func findMin(nums []int) int { 278 | // 思路:跳过重复元素,mid值和end值比较,分为两种情况进行处理 279 | if len(nums) == 0 { 280 | return -1 281 | } 282 | start := 0 283 | end := len(nums) - 1 284 | for start+1 < end { 285 | // 去除重复元素 286 | for start < end && nums[end] == nums[end-1] { 287 | end-- 288 | } 289 | for start < end && nums[start] == nums[start+1] { 290 | start++ 291 | } 292 | mid := start + (end-start)/2 293 | // 中间元素和最后一个元素比较(判断中间点落在左边上升区,还是右边上升区) 294 | if nums[mid] <= nums[end] { 295 | end = mid 296 | } else { 297 | start = mid 298 | } 299 | } 300 | if nums[start] > nums[end] { 301 | return nums[end] 302 | } 303 | return nums[start] 304 | } 305 | ``` 306 | 307 | ### [search-in-rotated-sorted-array](https://leetcode-cn.com/problems/search-in-rotated-sorted-array/) 308 | 309 | > 假设按照升序排序的数组在预先未知的某个点上进行了旋转。 310 | > ( 例如,数组  [0,1,2,4,5,6,7]  可能变为  [4,5,6,7,0,1,2] )。 311 | > 搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回  -1 。 312 | > 你可以假设数组中不存在重复的元素。 313 | 314 | ```go 315 | func search(nums []int, target int) int { 316 | // 思路:/ / 两条上升直线,四种情况判断 317 | if len(nums) == 0 { 318 | return -1 319 | } 320 | start := 0 321 | end := len(nums) - 1 322 | for start+1 < end { 323 | mid := start + (end-start)/2 324 | // 相等直接返回 325 | if nums[mid] == target { 326 | return mid 327 | } 328 | // 判断在那个区间,可能分为四种情况 329 | if nums[start] < nums[mid] { 330 | if nums[start] <= target && target <= nums[mid] { 331 | end = mid 332 | } else { 333 | start = mid 334 | } 335 | } else if nums[end] > nums[mid] { 336 | if nums[end] >= target && nums[mid] <= target { 337 | start = mid 338 | } else { 339 | end = mid 340 | } 341 | } 342 | } 343 | if nums[start] == target { 344 | return start 345 | } else if nums[end] == target { 346 | return end 347 | } 348 | return -1 349 | } 350 | ``` 351 | 352 | 注意点 353 | 354 | > 面试时,可以直接画图进行辅助说明,空讲很容易让大家都比较蒙圈 355 | 356 | ### [search-in-rotated-sorted-array-ii](https://leetcode-cn.com/problems/search-in-rotated-sorted-array-ii/) 357 | 358 | > 假设按照升序排序的数组在预先未知的某个点上进行了旋转。 359 | > ( 例如,数组  [0,0,1,2,2,5,6]  可能变为  [2,5,6,0,0,1,2] )。 360 | > 编写一个函数来判断给定的目标值是否存在于数组中。若存在返回  true,否则返回  false。(包含重复元素) 361 | 362 | ```go 363 | func search(nums []int, target int) bool { 364 | // 思路:/ / 两条上升直线,四种情况判断,并且处理重复数字 365 | if len(nums) == 0 { 366 | return false 367 | } 368 | start := 0 369 | end := len(nums) - 1 370 | for start+1 < end { 371 | // 处理重复数字 372 | for start < end && nums[start] == nums[start+1] { 373 | start++ 374 | } 375 | for start < end && nums[end] == nums[end-1] { 376 | end-- 377 | } 378 | mid := start + (end-start)/2 379 | // 相等直接返回 380 | if nums[mid] == target { 381 | return true 382 | } 383 | // 判断在那个区间,可能分为四种情况 384 | if nums[start] < nums[mid] { 385 | if nums[start] <= target && target <= nums[mid] { 386 | end = mid 387 | } else { 388 | start = mid 389 | } 390 | } else if nums[end] > nums[mid] { 391 | if nums[end] >= target && nums[mid] <= target { 392 | start = mid 393 | } else { 394 | end = mid 395 | } 396 | } 397 | } 398 | if nums[start] == target || nums[end] == target { 399 | return true 400 | } 401 | return false 402 | } 403 | ``` 404 | 405 | ## 总结 406 | 407 | 二分搜索核心四点要素(必背&理解) 408 | 409 | - 1、初始化:start=0、end=len-1 410 | - 2、循环退出条件:start + 1 < end 411 | - 3、比较中点和目标值:A[mid] ==、 <、> target 412 | - 4、判断最后两个元素是否符合:A[start]、A[end] ? target 413 | 414 | ## 练习题 415 | 416 | - [ ] [search-for-range](https://www.lintcode.com/problem/search-for-a-range/description) 417 | - [ ] [search-insert-position](https://leetcode-cn.com/problems/search-insert-position/) 418 | - [ ] [search-a-2d-matrix](https://leetcode-cn.com/problems/search-a-2d-matrix/) 419 | - [ ] [first-bad-version](https://leetcode-cn.com/problems/first-bad-version/) 420 | - [ ] [find-minimum-in-rotated-sorted-array](https://leetcode-cn.com/problems/find-minimum-in-rotated-sorted-array/) 421 | - [ ] [find-minimum-in-rotated-sorted-array-ii](https://leetcode-cn.com/problems/find-minimum-in-rotated-sorted-array-ii/) 422 | - [ ] [search-in-rotated-sorted-array](https://leetcode-cn.com/problems/search-in-rotated-sorted-array/) 423 | - [ ] [search-in-rotated-sorted-array-ii](https://leetcode-cn.com/problems/search-in-rotated-sorted-array-ii/) 424 | -------------------------------------------------------------------------------- /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 | ```go 48 | func minimumTotal(triangle [][]int) int { 49 | if len(triangle) == 0 || len(triangle[0]) == 0 { 50 | return 0 51 | } 52 | // 1、状态定义:f[i][j] 表示从i,j出发,到达最后一层的最短路径 53 | var l = len(triangle) 54 | var f = make([][]int, l) 55 | // 2、初始化 56 | for i := 0; i < l; i++ { 57 | for j := 0; j < len(triangle[i]); j++ { 58 | if f[i] == nil { 59 | f[i] = make([]int, len(triangle[i])) 60 | } 61 | f[i][j] = triangle[i][j] 62 | } 63 | } 64 | // 3、递推求解 65 | for i := len(triangle) - 2; i >= 0; i-- { 66 | for j := 0; j < len(triangle[i]); j++ { 67 | f[i][j] = min(f[i+1][j], f[i+1][j+1]) + triangle[i][j] 68 | } 69 | } 70 | // 4、答案 71 | return f[0][0] 72 | } 73 | func min(a, b int) int { 74 | if a > b { 75 | return b 76 | } 77 | return a 78 | } 79 | 80 | ``` 81 | 82 | 动态规划,自顶向下 83 | 84 | ```go 85 | // 测试用例: 86 | // [ 87 | // [2], 88 | // [3,4], 89 | // [6,5,7], 90 | // [4,1,8,3] 91 | // ] 92 | func minimumTotal(triangle [][]int) int { 93 | if len(triangle) == 0 || len(triangle[0]) == 0 { 94 | return 0 95 | } 96 | // 1、状态定义:f[i][j] 表示从0,0出发,到达i,j的最短路径 97 | var l = len(triangle) 98 | var f = make([][]int, l) 99 | // 2、初始化 100 | for i := 0; i < l; i++ { 101 | for j := 0; j < len(triangle[i]); j++ { 102 | if f[i] == nil { 103 | f[i] = make([]int, len(triangle[i])) 104 | } 105 | f[i][j] = triangle[i][j] 106 | } 107 | } 108 | // 递推求解 109 | for i := 1; i < l; i++ { 110 | for j := 0; j < len(triangle[i]); j++ { 111 | // 这里分为两种情况: 112 | // 1、上一层没有左边值 113 | // 2、上一层没有右边值 114 | if j-1 < 0 { 115 | f[i][j] = f[i-1][j] + triangle[i][j] 116 | } else if j >= len(f[i-1]) { 117 | f[i][j] = f[i-1][j-1] + triangle[i][j] 118 | } else { 119 | f[i][j] = min(f[i-1][j], f[i-1][j-1]) + triangle[i][j] 120 | } 121 | } 122 | } 123 | result := f[l-1][0] 124 | for i := 1; i < len(f[l-1]); i++ { 125 | result = min(result, f[l-1][i]) 126 | } 127 | return result 128 | } 129 | func min(a, b int) int { 130 | if a > b { 131 | return b 132 | } 133 | return a 134 | } 135 | ``` 136 | 137 | ## 递归和动规关系 138 | 139 | 递归是一种程序的实现方式:函数的自我调用 140 | 141 | ```go 142 | Function(x) { 143 | ... 144 | Funciton(x-1); 145 | ... 146 | } 147 | ``` 148 | 149 | 动态规划:是一种解决问 题的思想,大规模问题的结果,是由小规模问 题的结果运算得来的。动态规划可用递归来实现(Memorization Search) 150 | 151 | ## 使用场景 152 | 153 | 满足两个条件 154 | 155 | - 满足以下条件之一 156 | - 求最大/最小值(Maximum/Minimum ) 157 | - 求是否可行(Yes/No ) 158 | - 求可行个数(Count(\*) ) 159 | - 满足不能排序或者交换(Can not sort / swap ) 160 | 161 | 如题:[longest-consecutive-sequence](https://leetcode-cn.com/problems/longest-consecutive-sequence/)  位置可以交换,所以不用动态规划 162 | 163 | ## 四点要素 164 | 165 | 1. **状态 State** 166 | - 灵感,创造力,存储小规模问题的结果 167 | 2. 方程 Function 168 | - 状态之间的联系,怎么通过小的状态,来算大的状态 169 | 3. 初始化 Intialization 170 | - 最极限的小状态是什么, 起点 171 | 4. 答案 Answer 172 | - 最大的那个状态是什么,终点 173 | 174 | ## 常见四种类型 175 | 176 | 1. Matrix DP (10%) 177 | 1. Sequence (40%) 178 | 1. Two Sequences DP (40%) 179 | 1. Backpack (10%) 180 | 181 | > 注意点 182 | > 183 | > - 贪心算法大多题目靠背答案,所以如果能用动态规划就尽量用动规,不用贪心算法 184 | 185 | ## 1、矩阵类型(10%) 186 | 187 | ### [minimum-path-sum](https://leetcode-cn.com/problems/minimum-path-sum/) 188 | 189 | > 给定一个包含非负整数的  *m* x *n*  网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。 190 | 191 | 思路:动态规划 192 | 1、state: f[x][y]从起点走到 x,y 的最短路径 193 | 2、function: f[x][y] = min(f[x-1][y], f[x][y-1]) + A[x][y] 194 | 3、intialize: f[0][0] = A[0][0]、f[i][0] = sum(0,0 -> i,0)、 f[0][i] = sum(0,0 -> 0,i) 195 | 4、answer: f[n-1][m-1] 196 | 197 | ```go 198 | func minPathSum(grid [][]int) int { 199 | // 思路:动态规划 200 | // f[i][j] 表示i,j到0,0的和最小 201 | if len(grid) == 0 || len(grid[0]) == 0 { 202 | return 0 203 | } 204 | // 复用原来的矩阵列表 205 | // 初始化:f[i][0]、f[0][j] 206 | for i := 1; i < len(grid); i++ { 207 | grid[i][0] = grid[i][0] + grid[i-1][0] 208 | } 209 | for j := 1; j < len(grid[0]); j++ { 210 | grid[0][j] = grid[0][j] + grid[0][j-1] 211 | } 212 | for i := 1; i < len(grid); i++ { 213 | for j := 1; j < len(grid[i]); j++ { 214 | grid[i][j] = min(grid[i][j-1], grid[i-1][j]) + grid[i][j] 215 | } 216 | } 217 | return grid[len(grid)-1][len(grid[0])-1] 218 | } 219 | func min(a, b int) int { 220 | if a > b { 221 | return b 222 | } 223 | return a 224 | } 225 | ``` 226 | 227 | ### [unique-paths](https://leetcode-cn.com/problems/unique-paths/) 228 | 229 | > 一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。 230 | > 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。 231 | > 问总共有多少条不同的路径? 232 | 233 | ```go 234 | func uniquePaths(m int, n int) int { 235 | // f[i][j] 表示i,j到0,0路径数 236 | f := make([][]int, m) 237 | for i := 0; i < m; i++ { 238 | for j := 0; j < n; j++ { 239 | if f[i] == nil { 240 | f[i] = make([]int, n) 241 | } 242 | f[i][j] = 1 243 | } 244 | } 245 | for i := 1; i < m; i++ { 246 | for j := 1; j < n; j++ { 247 | f[i][j] = f[i-1][j] + f[i][j-1] 248 | } 249 | } 250 | return f[m-1][n-1] 251 | } 252 | ``` 253 | 254 | ### [unique-paths-ii](https://leetcode-cn.com/problems/unique-paths-ii/) 255 | 256 | > 一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。 257 | > 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。 258 | > 问总共有多少条不同的路径? 259 | > 现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径? 260 | 261 | ```go 262 | func uniquePathsWithObstacles(obstacleGrid [][]int) int { 263 | // f[i][j] = f[i-1][j] + f[i][j-1] 并检查障碍物 264 | if obstacleGrid[0][0] == 1 { 265 | return 0 266 | } 267 | m := len(obstacleGrid) 268 | n := len(obstacleGrid[0]) 269 | f := make([][]int, m) 270 | for i := 0; i < m; i++ { 271 | for j := 0; j < n; j++ { 272 | if f[i] == nil { 273 | f[i] = make([]int, n) 274 | } 275 | f[i][j] = 1 276 | } 277 | } 278 | for i := 1; i < m; i++ { 279 | if obstacleGrid[i][0] == 1 || f[i-1][0] == 0 { 280 | f[i][0] = 0 281 | } 282 | } 283 | for j := 1; j < n; j++ { 284 | if obstacleGrid[0][j] == 1 || f[0][j-1] == 0 { 285 | f[0][j] = 0 286 | } 287 | } 288 | for i := 1; i < m; i++ { 289 | for j := 1; j < n; j++ { 290 | if obstacleGrid[i][j] == 1 { 291 | f[i][j] = 0 292 | } else { 293 | f[i][j] = f[i-1][j] + f[i][j-1] 294 | } 295 | } 296 | } 297 | return f[m-1][n-1] 298 | } 299 | ``` 300 | 301 | ## 2、序列类型(40%) 302 | 303 | ### [climbing-stairs](https://leetcode-cn.com/problems/climbing-stairs/) 304 | 305 | > 假设你正在爬楼梯。需要  *n*  阶你才能到达楼顶。 306 | 307 | ```go 308 | func climbStairs(n int) int { 309 | // f[i] = f[i-1] + f[i-2] 310 | if n == 1 || n == 0 { 311 | return n 312 | } 313 | f := make([]int, n+1) 314 | f[1] = 1 315 | f[2] = 2 316 | for i := 3; i <= n; i++ { 317 | f[i] = f[i-1] + f[i-2] 318 | } 319 | return f[n] 320 | } 321 | ``` 322 | 323 | ### [jump-game](https://leetcode-cn.com/problems/jump-game/) 324 | 325 | > 给定一个非负整数数组,你最初位于数组的第一个位置。 326 | > 数组中的每个元素代表你在该位置可以跳跃的最大长度。 327 | > 判断你是否能够到达最后一个位置。 328 | 329 | ```go 330 | func canJump(nums []int) bool { 331 | // 思路:看最后一跳 332 | // 状态:f[i] 表示是否能从0跳到i 333 | // 推导:f[i] = OR(f[j],j= i { 344 | f[i] = true 345 | } 346 | } 347 | } 348 | return f[len(nums)-1] 349 | } 350 | ``` 351 | 352 | ### [jump-game-ii](https://leetcode-cn.com/problems/jump-game-ii/) 353 | 354 | > 给定一个非负整数数组,你最初位于数组的第一个位置。 355 | > 数组中的每个元素代表你在该位置可以跳跃的最大长度。 356 | > 你的目标是使用最少的跳跃次数到达数组的最后一个位置。 357 | 358 | ```go 359 | // v1动态规划(其他语言超时参考v2) 360 | func jump(nums []int) int { 361 | // 状态:f[i] 表示从起点到当前位置最小次数 362 | // 推导:f[i] = f[j],a[j]+j >=i,min(f[j]+1) 363 | // 初始化:f[0] = 0 364 | // 结果:f[n-1] 365 | f := make([]int, len(nums)) 366 | f[0] = 0 367 | for i := 1; i < len(nums); i++ { 368 | // f[i] 最大值为i 369 | f[i] = i 370 | // 遍历之前结果取一个最小值+1 371 | for j := 0; j < i; j++ { 372 | if nums[j]+j >= i { 373 | f[i] = min(f[j]+1,f[i]) 374 | } 375 | } 376 | } 377 | return f[len(nums)-1] 378 | } 379 | func min(a, b int) int { 380 | if a > b { 381 | return b 382 | } 383 | return a 384 | } 385 | ``` 386 | 387 | ```go 388 | // v2 动态规划+贪心优化 389 | func jump(nums []int) int { 390 | n:=len(nums) 391 | f := make([]int, n) 392 | f[0] = 0 393 | for i := 1; i < n; i++ { 394 | // 取第一个能跳到当前位置的点即可 395 | // 因为跳跃次数的结果集是单调递增的,所以贪心思路是正确的 396 | idx:=0 397 | for idx 给定一个字符串 _s_,将 _s_ 分割成一些子串,使每个子串都是回文串。 410 | > 返回符合要求的最少分割次数。 411 | 412 | ```go 413 | func minCut(s string) int { 414 | // state: f[i] "前i"个字符组成的子字符串需要最少几次cut(个数-1为索引) 415 | // function: f[i] = MIN{f[j]+1}, j < i && [j+1 ~ i]这一段是一个回文串 416 | // intialize: f[i] = i - 1 (f[0] = -1) 417 | // answer: f[s.length()] 418 | if len(s) == 0 || len(s) == 1 { 419 | return 0 420 | } 421 | f := make([]int, len(s)+1) 422 | f[0] = -1 423 | f[1] = 0 424 | for i := 1; i <= len(s); i++ { 425 | f[i] = i - 1 426 | for j := 0; j < i; j++ { 427 | if isPalindrome(s, j, i-1) { 428 | f[i] = min(f[i], f[j]+1) 429 | } 430 | } 431 | } 432 | return f[len(s)] 433 | } 434 | func min(a, b int) int { 435 | if a > b { 436 | return b 437 | } 438 | return a 439 | } 440 | func isPalindrome(s string, i, j int) bool { 441 | for i < j { 442 | if s[i] != s[j] { 443 | return false 444 | } 445 | i++ 446 | j-- 447 | } 448 | return true 449 | } 450 | ``` 451 | 452 | 注意点 453 | 454 | - 判断回文字符串时,可以提前用动态规划算好,减少时间复杂度 455 | 456 | ### [longest-increasing-subsequence](https://leetcode-cn.com/problems/longest-increasing-subsequence/) 457 | 458 | > 给定一个无序的整数数组,找到其中最长上升子序列的长度。 459 | 460 | ```go 461 | func lengthOfLIS(nums []int) int { 462 | // f[i] 表示从0开始到i结尾的最长序列长度 463 | // f[i] = max(f[j])+1 ,a[j] b { 488 | return a 489 | } 490 | return b 491 | } 492 | ``` 493 | 494 | ### [word-break](https://leetcode-cn.com/problems/word-break/) 495 | 496 | > 给定一个**非空**字符串  *s*  和一个包含**非空**单词列表的字典  *wordDict*,判定  *s*  是否可以被空格拆分为一个或多个在字典中出现的单词。 497 | 498 | ```go 499 | func wordBreak(s string, wordDict []string) bool { 500 | // f[i] 表示前i个字符是否可以被切分 501 | // f[i] = f[j] && s[j+1~i] in wordDict 502 | // f[0] = true 503 | // return f[len] 504 | 505 | if len(s) == 0 { 506 | return true 507 | } 508 | f := make([]bool, len(s)+1) 509 | f[0] = true 510 | max,dict := maxLen(wordDict) 511 | for i := 1; i <= len(s); i++ { 512 | l := 0 513 | if i - max > 0 { 514 | l = i - max 515 | } 516 | for j := l; j < i; j++ { 517 | if f[j] && inDict(s[j:i],dict) { 518 | f[i] = true 519 | break 520 | } 521 | } 522 | } 523 | return f[len(s)] 524 | } 525 | 526 | 527 | 528 | func maxLen(wordDict []string) (int,map[string]bool) { 529 | dict := make(map[string]bool) 530 | max := 0 531 | for _, v := range wordDict { 532 | dict[v] = true 533 | if len(v) > max { 534 | max = len(v) 535 | } 536 | } 537 | return max,dict 538 | } 539 | 540 | func inDict(s string,dict map[string]bool) bool { 541 | _, ok := dict[s] 542 | return ok 543 | } 544 | 545 | ``` 546 | 547 | 小结 548 | 549 | 常见处理方式是给 0 位置占位,这样处理问题时一视同仁,初始化则在原来基础上 length+1,返回结果 f[n] 550 | 551 | - 状态可以为前 i 个 552 | - 初始化 length+1 553 | - 取值 index=i-1 554 | - 返回值:f[n]或者 f[m][n] 555 | 556 | ## Two Sequences DP(40%) 557 | 558 | ### [longest-common-subsequence](https://leetcode-cn.com/problems/longest-common-subsequence/) 559 | 560 | > 给定两个字符串  text1 和  text2,返回这两个字符串的最长公共子序列。 561 | > 一个字符串的   子序列   是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。 562 | > 例如,"ace" 是 "abcde" 的子序列,但 "aec" 不是 "abcde" 的子序列。两个字符串的「公共子序列」是这两个字符串所共同拥有的子序列。 563 | 564 | ```go 565 | func longestCommonSubsequence(a string, b string) int { 566 | // dp[i][j] a前i个和b前j个字符最长公共子序列 567 | // dp[m+1][n+1] 568 | // ' a d c e 569 | // ' 0 0 0 0 0 570 | // a 0 1 1 1 1 571 | // c 0 1 1 2 1 572 | // 573 | dp:=make([][]int,len(a)+1) 574 | for i:=0;i<=len(a);i++ { 575 | dp[i]=make([]int,len(b)+1) 576 | } 577 | for i:=1;i<=len(a);i++ { 578 | for j:=1;j<=len(b);j++ { 579 | // 相等取左上元素+1,否则取左或上的较大值 580 | if a[i-1]==b[j-1] { 581 | dp[i][j]=dp[i-1][j-1]+1 582 | } else { 583 | dp[i][j]=max(dp[i-1][j],dp[i][j-1]) 584 | } 585 | } 586 | } 587 | return dp[len(a)][len(b)] 588 | } 589 | func max(a,b int)int { 590 | if a>b{ 591 | return a 592 | } 593 | return b 594 | } 595 | ``` 596 | 597 | 注意点 598 | 599 | - go 切片初始化 600 | 601 | ```go 602 | dp:=make([][]int,len(a)+1) 603 | for i:=0;i<=len(a);i++ { 604 | dp[i]=make([]int,len(b)+1) 605 | } 606 | ``` 607 | 608 | - 从 1 开始遍历到最大长度 609 | - 索引需要减一 610 | 611 | ### [edit-distance](https://leetcode-cn.com/problems/edit-distance/) 612 | 613 | > 给你两个单词  word1 和  word2,请你计算出将  word1  转换成  word2 所使用的最少操作数   614 | > 你可以对一个单词进行如下三种操作: 615 | > 插入一个字符 616 | > 删除一个字符 617 | > 替换一个字符 618 | 619 | 思路:和上题很类似,相等则不需要操作,否则取删除、插入、替换最小操作次数的值+1 620 | 621 | ```go 622 | func minDistance(word1 string, word2 string) int { 623 | // dp[i][j] 表示a字符串的前i个字符编辑为b字符串的前j个字符最少需要多少次操作 624 | // dp[i][j] = OR(dp[i-1][j-1],a[i]==b[j],min(dp[i-1][j],dp[i][j-1],dp[i-1][j-1])+1) 625 | dp:=make([][]int,len(word1)+1) 626 | for i:=0;ib{ 649 | return b 650 | } 651 | return a 652 | } 653 | ``` 654 | 655 | 说明 656 | 657 | > 另外一种做法:MAXLEN(a,b)-LCS(a,b) 658 | 659 | ## 零钱和背包(10%) 660 | 661 | ### [coin-change](https://leetcode-cn.com/problems/coin-change/) 662 | 663 | > 给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回  -1。 664 | 665 | 思路:和其他 DP 不太一样,i 表示钱或者容量 666 | 667 | ```go 668 | func coinChange(coins []int, amount int) int { 669 | // 状态 dp[i]表示金额为i时,组成的最小硬币个数 670 | // 推导 dp[i] = min(dp[i-1], dp[i-2], dp[i-5])+1, 前提 i-coins[j] > 0 671 | // 初始化为最大值 dp[i]=amount+1 672 | // 返回值 dp[n] or dp[n]>amount =>-1 673 | dp:=make([]int,amount+1) 674 | for i:=0;i<=amount;i++{ 675 | dp[i]=amount+1 676 | } 677 | dp[0]=0 678 | for i:=1;i<=amount;i++{ 679 | for j:=0;j=0 { 681 | dp[i]=min(dp[i],dp[i-coins[j]]+1) 682 | } 683 | } 684 | } 685 | if dp[amount] > amount { 686 | return -1 687 | } 688 | return dp[amount] 689 | 690 | } 691 | func min(a,b int)int{ 692 | if a>b{ 693 | return b 694 | } 695 | return a 696 | } 697 | ``` 698 | 699 | 注意 700 | 701 | > dp[i-a[j]] 决策 a[j]是否参与 702 | 703 | ### [backpack](https://www.lintcode.com/problem/backpack/description) 704 | 705 | > 在 n 个物品中挑选若干物品装入背包,最多能装多满?假设背包的大小为 m,每个物品的大小为 A[i] 706 | 707 | ```go 708 | func backPack (m int, A []int) int { 709 | // write your code here 710 | // f[i][j] 前i个物品,是否能装j 711 | // f[i][j] =f[i-1][j] f[i-1][j-a[i] j>a[i] 712 | // f[0][0]=true f[...][0]=true 713 | // f[n][X] 714 | f:=make([][]bool,len(A)+1) 715 | for i:=0;i<=len(A);i++{ 716 | f[i]=make([]bool,m+1) 717 | } 718 | f[0][0]=true 719 | for i:=1;i<=len(A);i++{ 720 | for j:=0;j<=m;j++{ 721 | f[i][j]=f[i-1][j] 722 | if j-A[i-1]>=0 && f[i-1][j-A[i-1]]{ 723 | f[i][j]=true 724 | } 725 | } 726 | } 727 | for i:=m;i>=0;i--{ 728 | if f[len(A)][i] { 729 | return i 730 | } 731 | } 732 | return 0 733 | } 734 | 735 | ``` 736 | 737 | ### [backpack-ii](https://www.lintcode.com/problem/backpack-ii/description) 738 | 739 | > 有 `n` 个物品和一个大小为 `m` 的背包. 给定数组 `A` 表示每个物品的大小和数组 `V` 表示每个物品的价值. 740 | > 问最多能装入背包的总价值是多大? 741 | 742 | 思路:f[i][j] 前 i 个物品,装入 j 背包 最大价值 743 | 744 | ```go 745 | func backPackII (m int, A []int, V []int) int { 746 | // write your code here 747 | // f[i][j] 前i个物品,装入j背包 最大价值 748 | // f[i][j] =max(f[i-1][j] ,f[i-1][j-A[i]]+V[i]) 是否加入A[i]物品 749 | // f[0][0]=0 f[0][...]=0 f[...][0]=0 750 | f:=make([][]int,len(A)+1) 751 | for i:=0;i= 0{ 758 | f[i][j]=max(f[i-1][j],f[i-1][j-A[i-1]]+V[i-1]) 759 | } 760 | } 761 | } 762 | return f[len(A)][m] 763 | } 764 | func max(a,b int)int{ 765 | if a>b{ 766 | return a 767 | } 768 | return b 769 | } 770 | ``` 771 | 772 | ## 练习 773 | 774 | Matrix DP (10%) 775 | 776 | - [ ] [triangle](https://leetcode-cn.com/problems/triangle/) 777 | - [ ] [minimum-path-sum](https://leetcode-cn.com/problems/minimum-path-sum/) 778 | - [ ] [unique-paths](https://leetcode-cn.com/problems/unique-paths/) 779 | - [ ] [unique-paths-ii](https://leetcode-cn.com/problems/unique-paths-ii/) 780 | 781 | Sequence (40%) 782 | 783 | - [ ] [climbing-stairs](https://leetcode-cn.com/problems/climbing-stairs/) 784 | - [ ] [jump-game](https://leetcode-cn.com/problems/jump-game/) 785 | - [ ] [jump-game-ii](https://leetcode-cn.com/problems/jump-game-ii/) 786 | - [ ] [palindrome-partitioning-ii](https://leetcode-cn.com/problems/palindrome-partitioning-ii/) 787 | - [ ] [longest-increasing-subsequence](https://leetcode-cn.com/problems/longest-increasing-subsequence/) 788 | - [ ] [word-break](https://leetcode-cn.com/problems/word-break/) 789 | 790 | Two Sequences DP (40%) 791 | 792 | - [ ] [longest-common-subsequence](https://leetcode-cn.com/problems/longest-common-subsequence/) 793 | - [ ] [edit-distance](https://leetcode-cn.com/problems/edit-distance/) 794 | 795 | Backpack & Coin Change (10%) 796 | 797 | - [ ] [coin-change](https://leetcode-cn.com/problems/coin-change/) 798 | - [ ] [backpack](https://www.lintcode.com/problem/backpack/description) 799 | - [ ] [backpack-ii](https://www.lintcode.com/problem/backpack-ii/description) 800 | -------------------------------------------------------------------------------- /basic_algorithm/sort.md: -------------------------------------------------------------------------------- 1 | # 排序 2 | 3 | ## 常考排序 4 | 5 | ### 快速排序 6 | 7 | ```go 8 | func QuickSort(nums []int) []int { 9 | // 思路:把一个数组分为左右两段,左段小于右段 10 | quickSort(nums, 0, len(nums)-1) 11 | return nums 12 | 13 | } 14 | // 原地交换,所以传入交换索引 15 | func quickSort(nums []int, start, end int) { 16 | if start < end { 17 | // 分治法:divide 18 | pivot := partition(nums, start, end) 19 | quickSort(nums, 0, pivot-1) 20 | quickSort(nums, pivot+1, end) 21 | } 22 | } 23 | // 分区 24 | func partition(nums []int, start, end int) int { 25 | // 选取最后一个元素作为基准pivot 26 | p := nums[end] 27 | i := start 28 | // 最后一个值就是基准所以不用比较 29 | for j := start; j < end; j++ { 30 | if nums[j] < p { 31 | swap(nums, i, j) 32 | i++ 33 | } 34 | } 35 | // 把基准值换到中间 36 | swap(nums, i, end) 37 | return i 38 | } 39 | // 交换两个元素 40 | func swap(nums []int, i, j int) { 41 | t := nums[i] 42 | nums[i] = nums[j] 43 | nums[j] = t 44 | } 45 | ``` 46 | 47 | ### 归并排序 48 | 49 | ```go 50 | func MergeSort(nums []int) []int { 51 | return mergeSort(nums) 52 | } 53 | func mergeSort(nums []int) []int { 54 | if len(nums) <= 1 { 55 | return nums 56 | } 57 | // 分治法:divide 分为两段 58 | mid := len(nums) / 2 59 | left := mergeSort(nums[:mid]) 60 | right := mergeSort(nums[mid:]) 61 | // 合并两段数据 62 | result := merge(left, right) 63 | return result 64 | } 65 | func merge(left, right []int) (result []int) { 66 | // 两边数组合并游标 67 | l := 0 68 | r := 0 69 | // 注意不能越界 70 | for l < len(left) && r < len(right) { 71 | // 谁小合并谁 72 | if left[l] > right[r] { 73 | result = append(result, right[r]) 74 | r++ 75 | } else { 76 | result = append(result, left[l]) 77 | l++ 78 | } 79 | } 80 | // 剩余部分合并 81 | result = append(result, left[l:]...) 82 | result = append(result, right[r:]...) 83 | return 84 | } 85 | ``` 86 | 87 | ### 堆排序 88 | 89 | 用数组表示的完美二叉树 complete binary tree 90 | 91 | > 完美二叉树 VS 其他二叉树 92 | 93 | ![image.png](https://img.fuiboom.com/img/tree_type.png) 94 | 95 | [动画展示](https://www.bilibili.com/video/av18980178/) 96 | 97 | ![image.png](https://img.fuiboom.com/img/heap.png) 98 | 99 | 核心代码 100 | 101 | ```go 102 | package main 103 | 104 | func HeapSort(a []int) []int { 105 | // 1、无序数组a 106 | // 2、将无序数组a构建为一个大根堆 107 | for i := len(a)/2 - 1; i >= 0; i-- { 108 | sink(a, i, len(a)) 109 | } 110 | // 3、交换a[0]和a[len(a)-1] 111 | // 4、然后把前面这段数组继续下沉保持堆结构,如此循环即可 112 | for i := len(a) - 1; i >= 1; i-- { 113 | // 从后往前填充值 114 | swap(a, 0, i) 115 | // 前面的长度也减一 116 | sink(a, 0, i) 117 | } 118 | return a 119 | } 120 | func sink(a []int, i int, length int) { 121 | for { 122 | // 左节点索引(从0开始,所以左节点为i*2+1) 123 | l := i*2 + 1 124 | // 右节点索引 125 | r := i*2 + 2 126 | // idx保存根、左、右三者之间较大值的索引 127 | idx := i 128 | // 存在左节点,左节点值较大,则取左节点 129 | if l < length && a[l] > a[idx] { 130 | idx = l 131 | } 132 | // 存在右节点,且值较大,取右节点 133 | if r < length && a[r] > a[idx] { 134 | idx = r 135 | } 136 | // 如果根节点较大,则不用下沉 137 | if idx == i { 138 | break 139 | } 140 | // 如果根节点较小,则交换值,并继续下沉 141 | swap(a, i, idx) 142 | // 继续下沉idx节点 143 | i = idx 144 | } 145 | } 146 | func swap(a []int, i, j int) { 147 | a[i], a[j] = a[j], a[i] 148 | } 149 | 150 | ``` 151 | 152 | ## 参考 153 | 154 | [十大经典排序](https://www.cnblogs.com/onepixel/p/7674659.html) 155 | 156 | [二叉堆](https://labuladong.gitbook.io/algo/shu-ju-jie-gou-xi-lie/er-cha-dui-xiang-jie-shi-xian-you-xian-ji-dui-lie) 157 | 158 | ## 练习 159 | 160 | - [ ] 手写快排、归并、堆排序 161 | -------------------------------------------------------------------------------- /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 | ```go 36 | func singleNumber(nums []int) int { 37 | // 10 ^10 == 00 38 | // 两个数异或就变成0 39 | result:=0 40 | for i:=0;i 给定一个**非空**整数数组,除了某个元素只出现一次以外,其余每个元素均出现了三次。找出那个只出现了一次的元素。 50 | 51 | ```go 52 | func singleNumber(nums []int) int { 53 | // 统计每位1的个数 54 | var result int 55 | for i := 0; i < 64; i++ { 56 | sum := 0 57 | for j := 0; j < len(nums); j++ { 58 | // 统计1的个数 59 | sum += (nums[j] >> i) & 1 60 | } 61 | // 还原位00^10=10 或者用| 也可以 62 | result ^= (sum % 3) << i 63 | } 64 | return result 65 | } 66 | ``` 67 | 68 | [single-number-iii](https://leetcode-cn.com/problems/single-number-iii/) 69 | 70 | > 给定一个整数数组  `nums`,其中恰好有两个元素只出现一次,其余所有元素均出现两次。 找出只出现一次的那两个元素。 71 | 72 | ```go 73 | func singleNumber(nums []int) []int { 74 | // a=a^b 75 | // b=a^b 76 | // a=a^b 77 | // 关键点怎么把a^b分成两部分,方案:可以通过diff最后一个1区分 78 | 79 | diff:=0 80 | for i:=0;i 编写一个函数,输入是一个无符号整数,返回其二进制表达式中数字位数为 ‘1’  的个数(也被称为[汉明重量](https://baike.baidu.com/item/%E6%B1%89%E6%98%8E%E9%87%8D%E9%87%8F))。 100 | 101 | ```go 102 | func hammingWeight(num uint32) int { 103 | res:=0 104 | for num!=0{ 105 | num=num&(num-1) 106 | res++ 107 | } 108 | return res 109 | } 110 | ``` 111 | 112 | [counting-bits](https://leetcode-cn.com/problems/counting-bits/) 113 | 114 | > 给定一个非负整数  **num**。对于  0 ≤ i ≤ num  范围中的每个数字  i ,计算其二进制数中的 1 的数目并将它们作为数组返回。 115 | 116 | ```go 117 | func countBits(num int) []int { 118 | res:=make([]int,num+1) 119 | 120 | for i:=0;i<=num;i++{ 121 | res[i]=count1(i) 122 | } 123 | return res 124 | } 125 | func count1(n int)(res int){ 126 | for n!=0{ 127 | n=n&(n-1) 128 | res++ 129 | } 130 | return 131 | } 132 | ``` 133 | 134 | 另外一种动态规划解法 135 | 136 | ```go 137 | func countBits(num int) []int { 138 | res:=make([]int,num+1) 139 | for i:=1;i<=num;i++{ 140 | // 上一个缺1的元素+1即可 141 | res[i]=res[i&(i-1)]+1 142 | } 143 | return res 144 | } 145 | ``` 146 | 147 | [reverse-bits](https://leetcode-cn.com/problems/reverse-bits/) 148 | 149 | > 颠倒给定的 32 位无符号整数的二进制位。 150 | 151 | 思路:依次颠倒即可 152 | 153 | ```go 154 | func reverseBits(num uint32) uint32 { 155 | var res uint32 156 | var pow int=31 157 | for num!=0{ 158 | // 把最后一位取出来,左移之后累加到结果中 159 | res+=(num&1)<>=1 161 | pow-- 162 | } 163 | return res 164 | } 165 | ``` 166 | 167 | [bitwise-and-of-numbers-range](https://leetcode-cn.com/problems/bitwise-and-of-numbers-range/) 168 | 169 | > 给定范围 [m, n],其中 0 <= m <= n <= 2147483647,返回此范围内所有数字的按位与(包含 m, n 两端点)。 170 | 171 | ```go 172 | func rangeBitwiseAnd(m int, n int) int { 173 | // m 5 1 0 1 174 | // 6 1 1 0 175 | // n 7 1 1 1 176 | // 把可能包含0的全部右移变成 177 | // m 5 1 0 0 178 | // 6 1 0 0 179 | // n 7 1 0 0 180 | // 所以最后结果就是m<>=1 184 | n>>=1 185 | count++ 186 | } 187 | return m< 0 || root != nil { 69 | for root != nil { 70 | stack = append(stack, root) 71 | root = root.Left // 一直向左 72 | } 73 | // 弹出 74 | val := stack[len(stack)-1] 75 | stack = stack[:len(stack)-1] 76 | result = append(result, val.Val) 77 | root = val.Right 78 | } 79 | return result 80 | } 81 | ``` 82 | 83 | #### 后序非递归 84 | 85 | ```go 86 | func postorderTraversal(root *TreeNode) []int { 87 | // 通过lastVisit标识右子节点是否已经弹出 88 | if root == nil { 89 | return nil 90 | } 91 | result := make([]int, 0) 92 | stack := make([]*TreeNode, 0) 93 | var lastVisit *TreeNode 94 | for root != nil || len(stack) != 0 { 95 | for root != nil { 96 | stack = append(stack, root) 97 | root = root.Left 98 | } 99 | // 这里先看看,先不弹出 100 | node:= stack[len(stack)-1] 101 | // 根节点必须在右节点弹出之后,再弹出 102 | if node.Right == nil || node.Right == lastVisit { 103 | stack = stack[:len(stack)-1] // pop 104 | result = append(result, node.Val) 105 | // 标记当前这个节点已经弹出过 106 | lastVisit = node 107 | } else { 108 | root = node.Right 109 | } 110 | } 111 | return result 112 | } 113 | ``` 114 | 115 | 注意点 116 | 117 | - 核心就是:根节点必须在右节点弹出之后,再弹出 118 | 119 | #### DFS 深度搜索-从上到下 120 | 121 | ```go 122 | type TreeNode struct { 123 | Val int 124 | Left *TreeNode 125 | Right *TreeNode 126 | } 127 | 128 | func preorderTraversal(root *TreeNode) []int { 129 | result := make([]int, 0) 130 | dfs(root, &result) 131 | return result 132 | } 133 | 134 | // V1:深度遍历,结果指针作为参数传入到函数内部 135 | func dfs(root *TreeNode, result *[]int) { 136 | if root == nil { 137 | return 138 | } 139 | *result = append(*result, root.Val) 140 | dfs(root.Left, result) 141 | dfs(root.Right, result) 142 | } 143 | ``` 144 | 145 | #### DFS 深度搜索-从下向上(分治法) 146 | 147 | ```go 148 | // V2:通过分治法遍历 149 | func preorderTraversal(root *TreeNode) []int { 150 | result := divideAndConquer(root) 151 | return result 152 | } 153 | func divideAndConquer(root *TreeNode) []int { 154 | result := make([]int, 0) 155 | // 返回条件(null & leaf) 156 | if root == nil { 157 | return result 158 | } 159 | // 分治(Divide) 160 | left := divideAndConquer(root.Left) 161 | right := divideAndConquer(root.Right) 162 | // 合并结果(Conquer) 163 | result = append(result, root.Val) 164 | result = append(result, left...) 165 | result = append(result, right...) 166 | return result 167 | } 168 | ``` 169 | 170 | 注意点: 171 | 172 | > DFS 深度搜索(从上到下) 和分治法区别:前者一般将最终结果通过指针参数传入,后者一般递归返回结果最后合并 173 | 174 | #### BFS 层次遍历 175 | 176 | ```go 177 | func levelOrder(root *TreeNode) [][]int { 178 | // 通过上一层的长度确定下一层的元素 179 | result := make([][]int, 0) 180 | if root == nil { 181 | return result 182 | } 183 | queue := make([]*TreeNode, 0) 184 | queue = append(queue, root) 185 | for len(queue) > 0 { 186 | list := make([]int, 0) 187 | // 为什么要取length? 188 | // 记录当前层有多少元素(遍历当前层,再添加下一层) 189 | l := len(queue) 190 | for i := 0; i < l; i++ { 191 | // 出队列 192 | level := queue[0] 193 | queue = queue[1:] 194 | list = append(list, level.Val) 195 | if level.Left != nil { 196 | queue = append(queue, level.Left) 197 | } 198 | if level.Right != nil { 199 | queue = append(queue, level.Right) 200 | } 201 | } 202 | result = append(result, list) 203 | } 204 | return result 205 | } 206 | ``` 207 | 208 | ### 分治法应用 209 | 210 | 先分别处理局部,再合并结果 211 | 212 | 适用场景 213 | 214 | - 快速排序 215 | - 归并排序 216 | - 二叉树相关问题 217 | 218 | 分治法模板 219 | 220 | - 递归返回条件 221 | - 分段处理 222 | - 合并结果 223 | 224 | ```go 225 | func traversal(root *TreeNode) ResultType { 226 | // nil or leaf 227 | if root == nil { 228 | // do something and return 229 | } 230 | 231 | // Divide 232 | ResultType left = traversal(root.Left) 233 | ResultType right = traversal(root.Right) 234 | 235 | // Conquer 236 | ResultType result = Merge from left and right 237 | 238 | return result 239 | } 240 | ``` 241 | 242 | #### 典型示例 243 | 244 | ```go 245 | // V2:通过分治法遍历二叉树 246 | func preorderTraversal(root *TreeNode) []int { 247 | result := divideAndConquer(root) 248 | return result 249 | } 250 | func divideAndConquer(root *TreeNode) []int { 251 | result := make([]int, 0) 252 | // 返回条件(null & leaf) 253 | if root == nil { 254 | return result 255 | } 256 | // 分治(Divide) 257 | left := divideAndConquer(root.Left) 258 | right := divideAndConquer(root.Right) 259 | // 合并结果(Conquer) 260 | result = append(result, root.Val) 261 | result = append(result, left...) 262 | result = append(result, right...) 263 | return result 264 | } 265 | ``` 266 | 267 | #### 归并排序   268 | 269 | ```go 270 | func MergeSort(nums []int) []int { 271 | return mergeSort(nums) 272 | } 273 | func mergeSort(nums []int) []int { 274 | if len(nums) <= 1 { 275 | return nums 276 | } 277 | // 分治法:divide 分为两段 278 | mid := len(nums) / 2 279 | left := mergeSort(nums[:mid]) 280 | right := mergeSort(nums[mid:]) 281 | // 合并两段数据 282 | result := merge(left, right) 283 | return result 284 | } 285 | func merge(left, right []int) (result []int) { 286 | // 两边数组合并游标 287 | l := 0 288 | r := 0 289 | // 注意不能越界 290 | for l < len(left) && r < len(right) { 291 | // 谁小合并谁 292 | if left[l] > right[r] { 293 | result = append(result, right[r]) 294 | r++ 295 | } else { 296 | result = append(result, left[l]) 297 | l++ 298 | } 299 | } 300 | // 剩余部分合并 301 | result = append(result, left[l:]...) 302 | result = append(result, right[r:]...) 303 | return 304 | } 305 | ``` 306 | 307 | 注意点 308 | 309 | > 递归需要返回结果用于合并 310 | 311 | #### 快速排序   312 | 313 | ```go 314 | func QuickSort(nums []int) []int { 315 | // 思路:把一个数组分为左右两段,左段小于右段,类似分治法没有合并过程 316 | quickSort(nums, 0, len(nums)-1) 317 | return nums 318 | 319 | } 320 | // 原地交换,所以传入交换索引 321 | func quickSort(nums []int, start, end int) { 322 | if start < end { 323 | // 分治法:divide 324 | pivot := partition(nums, start, end) 325 | quickSort(nums, 0, pivot-1) 326 | quickSort(nums, pivot+1, end) 327 | } 328 | } 329 | // 分区 330 | func partition(nums []int, start, end int) int { 331 | p := nums[end] 332 | i := start 333 | for j := start; j < end; j++ { 334 | if nums[j] < p { 335 | swap(nums, i, j) 336 | i++ 337 | } 338 | } 339 | // 把中间的值换为用于比较的基准值 340 | swap(nums, i, end) 341 | return i 342 | } 343 | func swap(nums []int, i, j int) { 344 | t := nums[i] 345 | nums[i] = nums[j] 346 | nums[j] = t 347 | } 348 | ``` 349 | 350 | 注意点: 351 | 352 | > 快排由于是原地交换所以没有合并过程 353 | > 传入的索引是存在的索引(如:0、length-1 等),越界可能导致崩溃 354 | 355 | 常见题目示例 356 | 357 | #### maximum-depth-of-binary-tree 358 | 359 | [maximum-depth-of-binary-tree](https://leetcode-cn.com/problems/maximum-depth-of-binary-tree/) 360 | 361 | > 给定一个二叉树,找出其最大深度。 362 | 363 | 思路:分治法 364 | 365 | ```go 366 | func maxDepth(root *TreeNode) int { 367 | // 返回条件处理 368 | if root == nil { 369 | return 0 370 | } 371 | // divide:分左右子树分别计算 372 | left := maxDepth(root.Left) 373 | right := maxDepth(root.Right) 374 | 375 | // conquer:合并左右子树结果 376 | if left > right { 377 | return left + 1 378 | } 379 | return right + 1 380 | } 381 | ``` 382 | 383 | #### balanced-binary-tree 384 | 385 | [balanced-binary-tree](https://leetcode-cn.com/problems/balanced-binary-tree/) 386 | 387 | > 给定一个二叉树,判断它是否是高度平衡的二叉树。 388 | 389 | 思路:分治法,左边平衡 && 右边平衡 && 左右两边高度 <= 1, 390 | 因为需要返回是否平衡及高度,要么返回两个数据,要么合并两个数据, 391 | 所以用-1 表示不平衡,>0 表示树高度(二义性:一个变量有两种含义)。 392 | 393 | ```go 394 | func isBalanced(root *TreeNode) bool { 395 | if maxDepth(root) == -1 { 396 | return false 397 | } 398 | return true 399 | } 400 | func maxDepth(root *TreeNode) int { 401 | // check 402 | if root == nil { 403 | return 0 404 | } 405 | left := maxDepth(root.Left) 406 | right := maxDepth(root.Right) 407 | 408 | // 为什么返回-1呢?(变量具有二义性) 409 | if left == -1 || right == -1 || left-right > 1 || right-left > 1 { 410 | return -1 411 | } 412 | if left > right { 413 | return left + 1 414 | } 415 | return right + 1 416 | } 417 | ``` 418 | 419 | 注意 420 | 421 | > 一般工程中,结果通过两个变量来返回,不建议用一个变量表示两种含义 422 | 423 | #### binary-tree-maximum-path-sum 424 | 425 | [binary-tree-maximum-path-sum](https://leetcode-cn.com/problems/binary-tree-maximum-path-sum/) 426 | 427 | > 给定一个**非空**二叉树,返回其最大路径和。 428 | 429 | 思路:分治法,分为三种情况:左子树最大路径和最大,右子树最大路径和最大,左右子树最大加根节点最大,需要保存两个变量:一个保存子树最大路径和,一个保存左右加根节点和,然后比较这个两个变量选择最大值即可 430 | 431 | ```go 432 | type ResultType struct { 433 | SinglePath int // 保存单边最大值 434 | MaxPath int // 保存最大值(单边或者两个单边+根的值) 435 | } 436 | func maxPathSum(root *TreeNode) int { 437 | result := helper(root) 438 | return result.MaxPath 439 | } 440 | func helper(root *TreeNode) ResultType { 441 | // check 442 | if root == nil { 443 | return ResultType{ 444 | SinglePath: 0, 445 | MaxPath: -(1 << 31), 446 | } 447 | } 448 | // Divide 449 | left := helper(root.Left) 450 | right := helper(root.Right) 451 | 452 | // Conquer 453 | result := ResultType{} 454 | // 求单边最大值 455 | if left.SinglePath > right.SinglePath { 456 | result.SinglePath = max(left.SinglePath + root.Val, 0) 457 | } else { 458 | result.SinglePath = max(right.SinglePath + root.Val, 0) 459 | } 460 | // 求两边加根最大值 461 | maxPath := max(right.MaxPath, left.MaxPath) 462 | result.MaxPath = max(maxPath,left.SinglePath+right.SinglePath+root.Val) 463 | return result 464 | } 465 | func max(a,b int) int { 466 | if a > b { 467 | return a 468 | } 469 | return b 470 | } 471 | ``` 472 | 473 | #### lowest-common-ancestor-of-a-binary-tree 474 | 475 | [lowest-common-ancestor-of-a-binary-tree](https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-tree/) 476 | 477 | > 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。 478 | 479 | 思路:分治法,有左子树的公共祖先或者有右子树的公共祖先,就返回子树的祖先,否则返回根节点 480 | 481 | ```go 482 | func lowestCommonAncestor(root, p, q *TreeNode) *TreeNode { 483 | // check 484 | if root == nil { 485 | return root 486 | } 487 | // 相等 直接返回root节点即可 488 | if root == p || root == q { 489 | return root 490 | } 491 | // Divide 492 | left := lowestCommonAncestor(root.Left, p, q) 493 | right := lowestCommonAncestor(root.Right, p, q) 494 | 495 | 496 | // Conquer 497 | // 左右两边都不为空,则根节点为祖先 498 | if left != nil && right != nil { 499 | return root 500 | } 501 | if left != nil { 502 | return left 503 | } 504 | if right != nil { 505 | return right 506 | } 507 | return nil 508 | } 509 | ``` 510 | 511 | ### BFS 层次应用 512 | 513 | #### binary-tree-level-order-traversal 514 | 515 | [binary-tree-level-order-traversal](https://leetcode-cn.com/problems/binary-tree-level-order-traversal/) 516 | 517 | > 给你一个二叉树,请你返回其按  **层序遍历**  得到的节点值。 (即逐层地,从左到右访问所有节点) 518 | 519 | 思路:用一个队列记录一层的元素,然后扫描这一层元素添加下一层元素到队列(一个数进去出来一次,所以复杂度 O(logN)) 520 | 521 | ```go 522 | func levelOrder(root *TreeNode) [][]int { 523 | result := make([][]int, 0) 524 | if root == nil { 525 | return result 526 | } 527 | queue := make([]*TreeNode, 0) 528 | queue = append(queue, root) 529 | for len(queue) > 0 { 530 | list := make([]int, 0) 531 | // 为什么要取length? 532 | // 记录当前层有多少元素(遍历当前层,再添加下一层) 533 | l := len(queue) 534 | for i := 0; i < l; i++ { 535 | // 出队列 536 | level := queue[0] 537 | queue = queue[1:] 538 | list = append(list, level.Val) 539 | if level.Left != nil { 540 | queue = append(queue, level.Left) 541 | } 542 | if level.Right != nil { 543 | queue = append(queue, level.Right) 544 | } 545 | } 546 | result = append(result, list) 547 | } 548 | return result 549 | } 550 | ``` 551 | 552 | #### binary-tree-level-order-traversal-ii 553 | 554 | [binary-tree-level-order-traversal-ii](https://leetcode-cn.com/problems/binary-tree-level-order-traversal-ii/) 555 | 556 | > 给定一个二叉树,返回其节点值自底向上的层次遍历。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历) 557 | 558 | 思路:在层级遍历的基础上,翻转一下结果即可 559 | 560 | ```go 561 | func levelOrderBottom(root *TreeNode) [][]int { 562 | result := levelOrder(root) 563 | // 翻转结果 564 | reverse(result) 565 | return result 566 | } 567 | func reverse(nums [][]int) { 568 | for i, j := 0, len(nums)-1; i < j; i, j = i+1, j-1 { 569 | nums[i], nums[j] = nums[j], nums[i] 570 | } 571 | } 572 | func levelOrder(root *TreeNode) [][]int { 573 | result := make([][]int, 0) 574 | if root == nil { 575 | return result 576 | } 577 | queue := make([]*TreeNode, 0) 578 | queue = append(queue, root) 579 | for len(queue) > 0 { 580 | list := make([]int, 0) 581 | // 为什么要取length? 582 | // 记录当前层有多少元素(遍历当前层,再添加下一层) 583 | l := len(queue) 584 | for i := 0; i < l; i++ { 585 | // 出队列 586 | level := queue[0] 587 | queue = queue[1:] 588 | list = append(list, level.Val) 589 | if level.Left != nil { 590 | queue = append(queue, level.Left) 591 | } 592 | if level.Right != nil { 593 | queue = append(queue, level.Right) 594 | } 595 | } 596 | result = append(result, list) 597 | } 598 | return result 599 | } 600 | ``` 601 | 602 | #### binary-tree-zigzag-level-order-traversal 603 | 604 | [binary-tree-zigzag-level-order-traversal](https://leetcode-cn.com/problems/binary-tree-zigzag-level-order-traversal/) 605 | 606 | > 给定一个二叉树,返回其节点值的锯齿形层次遍历。Z 字形遍历 607 | 608 | ```go 609 | func zigzagLevelOrder(root *TreeNode) [][]int { 610 | result := make([][]int, 0) 611 | if root == nil { 612 | return result 613 | } 614 | queue := make([]*TreeNode, 0) 615 | queue = append(queue, root) 616 | toggle := false 617 | for len(queue) > 0 { 618 | list := make([]int, 0) 619 | // 记录当前层有多少元素(遍历当前层,再添加下一层) 620 | l := len(queue) 621 | for i := 0; i < l; i++ { 622 | // 出队列 623 | level := queue[0] 624 | queue = queue[1:] 625 | list = append(list, level.Val) 626 | if level.Left != nil { 627 | queue = append(queue, level.Left) 628 | } 629 | if level.Right != nil { 630 | queue = append(queue, level.Right) 631 | } 632 | } 633 | if toggle { 634 | reverse(list) 635 | } 636 | result = append(result, list) 637 | toggle = !toggle 638 | } 639 | return result 640 | } 641 | func reverse(nums []int) { 642 | for i := 0; i < len(nums)/2; i++ { 643 | nums[i], nums[len(nums)-1-i] = nums[len(nums)-1-i], nums[i] 644 | } 645 | } 646 | ``` 647 | 648 | ### 二叉搜索树应用 649 | 650 | #### validate-binary-search-tree 651 | 652 | [validate-binary-search-tree](https://leetcode-cn.com/problems/validate-binary-search-tree/) 653 | 654 | > 给定一个二叉树,判断其是否是一个有效的二叉搜索树。 655 | 656 | 思路 1:中序遍历,检查结果列表是否已经有序 657 | 658 | 思路 2:分治法,判断左 MAX < 根 < 右 MIN 659 | 660 | ```go 661 | // v1 662 | func isValidBST(root *TreeNode) bool { 663 | result := make([]int, 0) 664 | inOrder(root, &result) 665 | // check order 666 | for i := 0; i < len(result) - 1; i++{ 667 | if result[i] >= result[i+1] { 668 | return false 669 | } 670 | } 671 | return true 672 | } 673 | 674 | func inOrder(root *TreeNode, result *[]int) { 675 | if root == nil{ 676 | return 677 | } 678 | inOrder(root.Left, result) 679 | *result = append(*result, root.Val) 680 | inOrder(root.Right, result) 681 | } 682 | 683 | 684 | ``` 685 | 686 | ```go 687 | // v2分治法 688 | type ResultType struct { 689 | IsValid bool 690 | // 记录左右两边最大最小值,和根节点进行比较 691 | Max *TreeNode 692 | Min *TreeNode 693 | } 694 | 695 | func isValidBST2(root *TreeNode) bool { 696 | result := helper(root) 697 | return result.IsValid 698 | } 699 | func helper(root *TreeNode) ResultType { 700 | result := ResultType{} 701 | // check 702 | if root == nil { 703 | result.IsValid = true 704 | return result 705 | } 706 | 707 | left := helper(root.Left) 708 | right := helper(root.Right) 709 | 710 | if !left.IsValid || !right.IsValid { 711 | result.IsValid = false 712 | return result 713 | } 714 | if left.Max != nil && left.Max.Val >= root.Val { 715 | result.IsValid = false 716 | return result 717 | } 718 | if right.Min != nil && right.Min.Val <= root.Val { 719 | result.IsValid = false 720 | return result 721 | } 722 | 723 | result.IsValid = true 724 | // 如果左边还有更小的3,就用更小的节点,不用4 725 | // 5 726 | // / \ 727 | // 1 4 728 | //   / \ 729 | //   3 6 730 | result.Min = root 731 | if left.Min != nil { 732 | result.Min = left.Min 733 | } 734 | result.Max = root 735 | if right.Max != nil { 736 | result.Max = right.Max 737 | } 738 | return result 739 | } 740 | ``` 741 | 742 | #### insert-into-a-binary-search-tree 743 | 744 | [insert-into-a-binary-search-tree](https://leetcode-cn.com/problems/insert-into-a-binary-search-tree/) 745 | 746 | > 给定二叉搜索树(BST)的根节点和要插入树中的值,将值插入二叉搜索树。 返回插入后二叉搜索树的根节点。 747 | 748 | 思路:找到最后一个叶子节点满足插入条件即可 749 | 750 | ```go 751 | // DFS查找插入位置 752 | func insertIntoBST(root *TreeNode, val int) *TreeNode { 753 | if root == nil { 754 | root = &TreeNode{Val: val} 755 | return root 756 | } 757 | if root.Val > val { 758 | root.Left = insertIntoBST(root.Left, val) 759 | } else { 760 | root.Right = insertIntoBST(root.Right, val) 761 | } 762 | return root 763 | } 764 | ``` 765 | 766 | ## 总结 767 | 768 | - 掌握二叉树递归与非递归遍历 769 | - 理解 DFS 前序遍历与分治法 770 | - 理解 BFS 层次遍历 771 | 772 | ## 练习 773 | 774 | - [ ] [maximum-depth-of-binary-tree](https://leetcode-cn.com/problems/maximum-depth-of-binary-tree/) 775 | - [ ] [balanced-binary-tree](https://leetcode-cn.com/problems/balanced-binary-tree/) 776 | - [ ] [binary-tree-maximum-path-sum](https://leetcode-cn.com/problems/binary-tree-maximum-path-sum/) 777 | - [ ] [lowest-common-ancestor-of-a-binary-tree](https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-tree/) 778 | - [ ] [binary-tree-level-order-traversal](https://leetcode-cn.com/problems/binary-tree-level-order-traversal/) 779 | - [ ] [binary-tree-level-order-traversal-ii](https://leetcode-cn.com/problems/binary-tree-level-order-traversal-ii/) 780 | - [ ] [binary-tree-zigzag-level-order-traversal](https://leetcode-cn.com/problems/binary-tree-zigzag-level-order-traversal/) 781 | - [ ] [validate-binary-search-tree](https://leetcode-cn.com/problems/validate-binary-search-tree/) 782 | - [ ] [insert-into-a-binary-search-tree](https://leetcode-cn.com/problems/insert-into-a-binary-search-tree/) 783 | -------------------------------------------------------------------------------- /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 | ```go 23 | func deleteDuplicates(head *ListNode) *ListNode { 24 | current := head 25 | for current != nil { 26 | // 全部删除完再移动到下一个元素 27 | for current.Next != nil && current.Val == current.Next.Val { 28 | current.Next = current.Next.Next 29 | } 30 | current = current.Next 31 | } 32 | return head 33 | } 34 | ``` 35 | 36 | ### [remove-duplicates-from-sorted-list-ii](https://leetcode-cn.com/problems/remove-duplicates-from-sorted-list-ii/) 37 | 38 | > 给定一个排序链表,删除所有含有重复数字的节点,只保留原始链表中   没有重复出现的数字。 39 | 40 | 思路:链表头结点可能被删除,所以用 dummy node 辅助删除 41 | 42 | ```go 43 | func deleteDuplicates(head *ListNode) *ListNode { 44 | if head == nil { 45 | return head 46 | } 47 | dummy := &ListNode{Val: 0} 48 | dummy.Next = head 49 | head = dummy 50 | 51 | var rmVal int 52 | for head.Next != nil && head.Next.Next != nil { 53 | if head.Next.Val == head.Next.Next.Val { 54 | // 记录已经删除的值,用于后续节点判断 55 | rmVal = head.Next.Val 56 | for head.Next != nil && head.Next.Val == rmVal { 57 | head.Next = head.Next.Next 58 | } 59 | } else { 60 | head = head.Next 61 | } 62 | } 63 | return dummy.Next 64 | } 65 | ``` 66 | 67 | 注意点 68 | • A->B->C 删除 B,A.next = C 69 | • 删除用一个 Dummy Node 节点辅助(允许头节点可变) 70 | • 访问 X.next 、X.value 一定要保证 X != nil 71 | 72 | ### [reverse-linked-list](https://leetcode-cn.com/problems/reverse-linked-list/) 73 | 74 | > 反转一个单链表。 75 | 76 | 思路:用一个 prev 节点保存向前指针,temp 保存向后的临时指针 77 | 78 | ```go 79 | func reverseList(head *ListNode) *ListNode { 80 | var prev *ListNode 81 | for head != nil { 82 | // 保存当前head.Next节点,防止重新赋值后被覆盖 83 | // 一轮之后状态:nil<-1 2->3->4 84 | // prev head 85 | temp := head.Next 86 | head.Next = prev 87 | // pre 移动 88 | prev = head 89 | // head 移动 90 | head = temp 91 | } 92 | return prev 93 | } 94 | ``` 95 | 96 | ### [reverse-linked-list-ii](https://leetcode-cn.com/problems/reverse-linked-list-ii/) 97 | 98 | > 反转从位置  *m*  到  *n*  的链表。请使用一趟扫描完成反转。 99 | 100 | 思路:先遍历到 m 处,翻转,再拼接后续,注意指针处理 101 | 102 | ```go 103 | func reverseBetween(head *ListNode, m int, n int) *ListNode { 104 | // 思路:先遍历到m处,翻转,再拼接后续,注意指针处理 105 | // 输入: 1->2->3->4->5->NULL, m = 2, n = 4 106 | if head == nil { 107 | return head 108 | } 109 | // 头部变化所以使用dummy node 110 | dummy := &ListNode{Val: 0} 111 | dummy.Next = head 112 | head = dummy 113 | // 最开始:0->1->2->3->4->5->nil 114 | var pre *ListNode 115 | var i = 0 116 | for i < m { 117 | pre = head 118 | head = head.Next 119 | i++ 120 | } 121 | // 遍历之后: 1(pre)->2(head)->3->4->5->NULL 122 | // i = 1 123 | var j = i 124 | var next *ListNode 125 | // 用于中间节点连接 126 | var mid = head 127 | for head != nil && j <= n { 128 | // 第一次循环: 1 nil<-2 3->4->5->nil 129 | temp := head.Next 130 | head.Next = next 131 | next = head 132 | head = temp 133 | j++ 134 | } 135 | // 循环需要执行四次 136 | // 循环结束:1 nil<-2<-3<-4 5(head)->nil 137 | pre.Next = next 138 | mid.Next = head 139 | return dummy.Next 140 | } 141 | ``` 142 | 143 | ### [merge-two-sorted-lists](https://leetcode-cn.com/problems/merge-two-sorted-lists/) 144 | 145 | > 将两个升序链表合并为一个新的升序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 146 | 147 | 思路:通过 dummy node 链表,连接各个元素 148 | 149 | ```go 150 | func mergeTwoLists(l1 *ListNode, l2 *ListNode) *ListNode { 151 | dummy := &ListNode{Val: 0} 152 | head := dummy 153 | for l1 != nil && l2 != nil { 154 | if l1.Val < l2.Val { 155 | head.Next = l1 156 | l1 = l1.Next 157 | } else { 158 | head.Next = l2 159 | l2 = l2.Next 160 | } 161 | head = head.Next 162 | } 163 | // 连接l1 未处理完节点 164 | for l1 != nil { 165 | head.Next = l1 166 | head = head.Next 167 | l1 = l1.Next 168 | } 169 | // 连接l2 未处理完节点 170 | for l2 != nil { 171 | head.Next = l2 172 | head = head.Next 173 | l2 = l2.Next 174 | } 175 | return dummy.Next 176 | } 177 | ``` 178 | 179 | ### [partition-list](https://leetcode-cn.com/problems/partition-list/) 180 | 181 | > 给定一个链表和一个特定值 x,对链表进行分隔,使得所有小于  *x*  的节点都在大于或等于  *x*  的节点之前。 182 | 183 | 思路:将大于 x 的节点,放到另外一个链表,最后连接这两个链表 184 | 185 | ```go 186 | func partition(head *ListNode, x int) *ListNode { 187 | // 思路:将大于x的节点,放到另外一个链表,最后连接这两个链表 188 | // check 189 | if head == nil { 190 | return head 191 | } 192 | headDummy := &ListNode{Val: 0} 193 | tailDummy := &ListNode{Val: 0} 194 | tail := tailDummy 195 | headDummy.Next = head 196 | head = headDummy 197 | for head.Next != nil { 198 | if head.Next.Val < x { 199 | head = head.Next 200 | } else { 201 | // 移除 当头节点不确定的时候,使用哑巴节点 219 | 220 | ### [sort-list](https://leetcode-cn.com/problems/sort-list/) 221 | 222 | > 在  *O*(*n* log *n*) 时间复杂度和常数级空间复杂度下,对链表进行排序。 223 | 224 | 思路:归并排序,找中点和合并操作 225 | 226 | ```go 227 | func sortList(head *ListNode) *ListNode { 228 | // 思路:归并排序,找中点和合并操作 229 | return mergeSort(head) 230 | } 231 | func findMiddle(head *ListNode) *ListNode { 232 | // 1->2->3->4->5 233 | slow := head 234 | fast := head.Next 235 | // 快指针先为nil 236 | for fast !=nil && fast.Next != nil { 237 | fast = fast.Next.Next 238 | slow = slow.Next 239 | } 240 | return slow 241 | } 242 | func mergeTwoLists(l1 *ListNode, l2 *ListNode) *ListNode { 243 | dummy := &ListNode{Val: 0} 244 | head := dummy 245 | for l1 != nil && l2 != nil { 246 | if l1.Val < l2.Val { 247 | head.Next = l1 248 | l1 = l1.Next 249 | } else { 250 | head.Next = l2 251 | l2 = l2.Next 252 | } 253 | head = head.Next 254 | } 255 | // 连接l1 未处理完节点 256 | for l1 != nil { 257 | head.Next = l1 258 | head = head.Next 259 | l1 = l1.Next 260 | } 261 | // 连接l2 未处理完节点 262 | for l2 != nil { 263 | head.Next = l2 264 | head = head.Next 265 | l2 = l2.Next 266 | } 267 | return dummy.Next 268 | } 269 | func mergeSort(head *ListNode) *ListNode { 270 | // 如果只有一个节点 直接就返回这个节点 271 | if head == nil || head.Next == nil{ 272 | return head 273 | } 274 | // find middle 275 | middle := findMiddle(head) 276 | // 断开中间节点 277 | tail := middle.Next 278 | middle.Next = nil 279 | left := mergeSort(head) 280 | right := mergeSort(tail) 281 | result := mergeTwoLists(left, right) 282 | return result 283 | } 284 | ``` 285 | 286 | 注意点 287 | 288 | - 快慢指针 判断 fast 及 fast.Next 是否为 nil 值 289 | - 递归 mergeSort 需要断开中间节点 290 | - 递归返回条件为 head 为 nil 或者 head.Next 为 nil 291 | 292 | ### [reorder-list](https://leetcode-cn.com/problems/reorder-list/) 293 | 294 | > 给定一个单链表  *L*:*L*→*L*→…→*L\_\_n*→*L* 295 | > 将其重新排列后变为: *L*→*L\_\_n*→*L*→*L\_\_n*→*L*→*L\_\_n*→… 296 | 297 | 思路:找到中点断开,翻转后面部分,然后合并前后两个链表 298 | 299 | ```go 300 | func reorderList(head *ListNode) { 301 | // 思路:找到中点断开,翻转后面部分,然后合并前后两个链表 302 | if head == nil { 303 | return 304 | } 305 | mid := findMiddle(head) 306 | tail := reverseList(mid.Next) 307 | mid.Next = nil 308 | head = mergeTwoLists(head, tail) 309 | } 310 | func findMiddle(head *ListNode) *ListNode { 311 | fast := head.Next 312 | slow := head 313 | for fast != nil && fast.Next != nil { 314 | fast = fast.Next.Next 315 | slow = slow.Next 316 | } 317 | return slow 318 | } 319 | func mergeTwoLists(l1 *ListNode, l2 *ListNode) *ListNode { 320 | dummy := &ListNode{Val: 0} 321 | head := dummy 322 | toggle := true 323 | for l1 != nil && l2 != nil { 324 | // 节点切换 325 | if toggle { 326 | head.Next = l1 327 | l1 = l1.Next 328 | } else { 329 | head.Next = l2 330 | l2 = l2.Next 331 | } 332 | toggle = !toggle 333 | head = head.Next 334 | } 335 | // 连接l1 未处理完节点 336 | for l1 != nil { 337 | head.Next = l1 338 | head = head.Next 339 | l1 = l1.Next 340 | } 341 | // 连接l2 未处理完节点 342 | for l2 != nil { 343 | head.Next = l2 344 | head = head.Next 345 | l2 = l2.Next 346 | } 347 | return dummy.Next 348 | } 349 | func reverseList(head *ListNode) *ListNode { 350 | var prev *ListNode 351 | for head != nil { 352 | // 保存当前head.Next节点,防止重新赋值后被覆盖 353 | // 一轮之后状态:nil<-1 2->3->4 354 | // prev head 355 | temp := head.Next 356 | head.Next = prev 357 | // pre 移动 358 | prev = head 359 | // head 移动 360 | head = temp 361 | } 362 | return prev 363 | } 364 | ``` 365 | 366 | ### [linked-list-cycle](https://leetcode-cn.com/problems/linked-list-cycle/) 367 | 368 | > 给定一个链表,判断链表中是否有环。 369 | 370 | 思路:快慢指针,快慢指针相同则有环,证明:如果有环每走一步快慢指针距离会减 1 371 | ![fast_slow_linked_list](https://img.fuiboom.com/img/fast_slow_linked_list.png) 372 | 373 | ```go 374 | func hasCycle(head *ListNode) bool { 375 | // 思路:快慢指针 快慢指针相同则有环,证明:如果有环每走一步快慢指针距离会减1 376 | if head == nil { 377 | return false 378 | } 379 | fast := head.Next 380 | slow := head 381 | for fast != nil && fast.Next != nil { 382 | // 比较指针是否相等(不要使用val比较!) 383 | if fast == slow { 384 | return true 385 | } 386 | fast = fast.Next.Next 387 | slow = slow.Next 388 | } 389 | return false 390 | } 391 | ``` 392 | 393 | ### [linked-list-cycle-ii](https://leetcode-cn.com/problems/linked-list-cycle-ii/) 394 | 395 | > 给定一个链表,返回链表开始入环的第一个节点。  如果链表无环,则返回  `null`。 396 | 397 | 思路:快慢指针,快慢相遇之后,慢指针回到头,快慢指针步调一致一起移动,相遇点即为入环点 398 | ![cycled_linked_list](https://img.fuiboom.com/img/cycled_linked_list.png) 399 | 400 | ```go 401 | func detectCycle(head *ListNode) *ListNode { 402 | // 思路:快慢指针,快慢相遇之后,慢指针回到头,快慢指针步调一致一起移动,相遇点即为入环点 403 | if head == nil { 404 | return head 405 | } 406 | fast := head.Next 407 | slow := head 408 | 409 | for fast != nil && fast.Next != nil { 410 | if fast == slow { 411 | // 慢指针重新从头开始移动,快指针从第一次相交点下一个节点开始移动 412 | fast = head 413 | slow = slow.Next // 注意 414 | // 比较指针对象(不要比对指针Val值) 415 | for fast != slow { 416 | fast = fast.Next 417 | slow = slow.Next 418 | } 419 | return slow 420 | } 421 | fast = fast.Next.Next 422 | slow = slow.Next 423 | } 424 | return nil 425 | } 426 | ``` 427 | 428 | 坑点 429 | 430 | - 指针比较时直接比较对象,不要用值比较,链表中有可能存在重复值情况 431 | - 第一次相交后,快指针需要从下一个节点开始和头指针一起匀速移动 432 | 433 | 另外一种方式是 fast=head,slow=head 434 | 435 | ```go 436 | func detectCycle(head *ListNode) *ListNode { 437 | // 思路:快慢指针,快慢相遇之后,其中一个指针回到头,快慢指针步调一致一起移动,相遇点即为入环点 438 | // nb+a=2nb+a 439 | if head == nil { 440 | return head 441 | } 442 | fast := head 443 | slow := head 444 | 445 | for fast != nil && fast.Next != nil { 446 | fast = fast.Next.Next 447 | slow = slow.Next 448 | if fast == slow { 449 | // 指针重新从头开始移动 450 | fast = head 451 | for fast != slow { 452 | fast = fast.Next 453 | slow = slow.Next 454 | } 455 | return slow 456 | } 457 | } 458 | return nil 459 | } 460 | ``` 461 | 462 | 这两种方式不同点在于,**一般用 fast=head.Next 较多**,因为这样可以知道中点的上一个节点,可以用来删除等操作。 463 | 464 | - fast 如果初始化为 head.Next 则中点在 slow.Next 465 | - fast 初始化为 head,则中点在 slow 466 | 467 | ### [palindrome-linked-list](https://leetcode-cn.com/problems/palindrome-linked-list/) 468 | 469 | > 请判断一个链表是否为回文链表。 470 | 471 | ```go 472 | func isPalindrome(head *ListNode) bool { 473 | // 1 2 nil 474 | // 1 2 1 nil 475 | // 1 2 2 1 nil 476 | if head==nil{ 477 | return true 478 | } 479 | slow:=head 480 | // fast如果初始化为head.Next则中点在slow.Next 481 | // fast初始化为head,则中点在slow 482 | fast:=head.Next 483 | for fast!=nil&&fast.Next!=nil{ 484 | fast=fast.Next.Next 485 | slow=slow.Next 486 | } 487 | 488 | tail:=reverse(slow.Next) 489 | // 断开两个链表(需要用到中点前一个节点) 490 | slow.Next=nil 491 | for head!=nil&&tail!=nil{ 492 | if head.Val!=tail.Val{ 493 | return false 494 | } 495 | head=head.Next 496 | tail=tail.Next 497 | } 498 | return true 499 | 500 | } 501 | 502 | func reverse(head *ListNode)*ListNode{ 503 | // 1->2->3 504 | if head==nil{ 505 | return head 506 | } 507 | var prev *ListNode 508 | for head!=nil{ 509 | t:=head.Next 510 | head.Next=prev 511 | prev=head 512 | head=t 513 | } 514 | return prev 515 | } 516 | ``` 517 | 518 | ### [copy-list-with-random-pointer](https://leetcode-cn.com/problems/copy-list-with-random-pointer/) 519 | 520 | > 给定一个链表,每个节点包含一个额外增加的随机指针,该指针可以指向链表中的任何节点或空节点。 521 | > 要求返回这个链表的 深拷贝。 522 | 523 | 思路:1、hash 表存储指针,2、复制节点跟在原节点后面 524 | 525 | ```go 526 | func copyRandomList(head *Node) *Node { 527 | if head == nil { 528 | return head 529 | } 530 | // 复制节点,紧挨到到后面 531 | // 1->2->3 ==> 1->1'->2->2'->3->3' 532 | cur := head 533 | for cur != nil { 534 | clone := &Node{Val: cur.Val, Next: cur.Next} 535 | temp := cur.Next 536 | cur.Next = clone 537 | cur = temp 538 | } 539 | // 处理random指针 540 | cur = head 541 | for cur != nil { 542 | if cur.Random != nil { 543 | cur.Next.Random = cur.Random.Next 544 | } 545 | cur = cur.Next.Next 546 | } 547 | // 分离两个链表 548 | cur = head 549 | cloneHead := cur.Next 550 | for cur != nil && cur.Next != nil { 551 | temp := cur.Next 552 | cur.Next = cur.Next.Next 553 | cur = temp 554 | } 555 | // 原始链表头:head 1->2->3 556 | // 克隆的链表头:cloneHead 1'->2'->3' 557 | return cloneHead 558 | } 559 | ``` 560 | 561 | ## 总结 562 | 563 | 链表必须要掌握的一些点,通过下面练习题,基本大部分的链表类的题目都是手到擒来~ 564 | 565 | - null/nil 异常处理 566 | - dummy node 哑巴节点 567 | - 快慢指针 568 | - 插入一个节点到排序链表 569 | - 从一个链表中移除一个节点 570 | - 翻转链表 571 | - 合并两个链表 572 | - 找到链表的中间节点 573 | 574 | ## 练习 575 | 576 | - [ ] [remove-duplicates-from-sorted-list](https://leetcode-cn.com/problems/remove-duplicates-from-sorted-list/) 577 | - [ ] [remove-duplicates-from-sorted-list-ii](https://leetcode-cn.com/problems/remove-duplicates-from-sorted-list-ii/) 578 | - [ ] [reverse-linked-list](https://leetcode-cn.com/problems/reverse-linked-list/) 579 | - [ ] [reverse-linked-list-ii](https://leetcode-cn.com/problems/reverse-linked-list-ii/) 580 | - [ ] [merge-two-sorted-lists](https://leetcode-cn.com/problems/merge-two-sorted-lists/) 581 | - [ ] [partition-list](https://leetcode-cn.com/problems/partition-list/) 582 | - [ ] [sort-list](https://leetcode-cn.com/problems/sort-list/) 583 | - [ ] [reorder-list](https://leetcode-cn.com/problems/reorder-list/) 584 | - [ ] [linked-list-cycle](https://leetcode-cn.com/problems/linked-list-cycle/) 585 | - [ ] [linked-list-cycle-ii](https://leetcode-cn.com/problems/linked-list-cycle-ii/) 586 | - [ ] [palindrome-linked-list](https://leetcode-cn.com/problems/palindrome-linked-list/) 587 | - [ ] [copy-list-with-random-pointer](https://leetcode-cn.com/problems/copy-list-with-random-pointer/) 588 | -------------------------------------------------------------------------------- /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 | ```go 22 | type MinStack struct { 23 | min []int 24 | stack []int 25 | } 26 | 27 | 28 | /** initialize your data structure here. */ 29 | func Constructor() MinStack { 30 | return MinStack{ 31 | min: make([]int, 0), 32 | stack: make([]int, 0), 33 | } 34 | } 35 | 36 | 37 | func (this *MinStack) Push(x int) { 38 | min := this.GetMin() 39 | if x < min { 40 | this.min = append(this.min, x) 41 | } else { 42 | this.min = append(this.min, min) 43 | } 44 | this.stack = append(this.stack, x) 45 | } 46 | 47 | 48 | func (this *MinStack) Pop() { 49 | if len(this.stack) == 0 { 50 | return 51 | } 52 | this.stack = this.stack[:len(this.stack)-1] 53 | this.min = this.min[:len(this.min)-1] 54 | } 55 | 56 | 57 | func (this *MinStack) Top() int { 58 | if len(this.stack) == 0 { 59 | return 0 60 | } 61 | return this.stack[len(this.stack)-1] 62 | } 63 | 64 | 65 | func (this *MinStack) GetMin() int { 66 | if len(this.min) == 0 { 67 | return 1 << 31 68 | } 69 | min := this.min[len(this.min)-1] 70 | return min 71 | } 72 | 73 | 74 | /** 75 | * Your MinStack object will be instantiated and called as such: 76 | * obj := Constructor(); 77 | * obj.Push(x); 78 | * obj.Pop(); 79 | * param_3 := obj.Top(); 80 | * param_4 := obj.GetMin(); 81 | */ 82 | ``` 83 | 84 | [evaluate-reverse-polish-notation](https://leetcode-cn.com/problems/evaluate-reverse-polish-notation/) 85 | 86 | > **波兰表达式计算** > **输入:** `["2", "1", "+", "3", "*"]` > **输出:** 9 87 | > 88 | > **解释:** `((2 + 1) * 3) = 9` 89 | 90 | 思路:通过栈保存原来的元素,遇到表达式弹出运算,再推入结果,重复这个过程 91 | 92 | ```go 93 | func evalRPN(tokens []string) int { 94 | if len(tokens)==0{ 95 | return 0 96 | } 97 | stack:=make([]int,0) 98 | for i:=0;i 给定一个经过编码的字符串,返回它解码后的字符串。 133 | > s = "3[a]2[bc]", 返回 "aaabcbc". 134 | > s = "3[a2[c]]", 返回 "accaccacc". 135 | > s = "2[abc]3[cd]ef", 返回 "abcabccdcdcdef". 136 | 137 | 思路:通过栈辅助进行操作 138 | 139 | ```go 140 | func decodeString(s string) string { 141 | if len(s) == 0 { 142 | return "" 143 | } 144 | stack := make([]byte, 0) 145 | for i := 0; i < len(s); i++ { 146 | switch s[i] { 147 | case ']': 148 | temp := make([]byte, 0) 149 | for len(stack) != 0 && stack[len(stack)-1] != '[' { 150 | v := stack[len(stack)-1] 151 | stack = stack[:len(stack)-1] 152 | temp = append(temp, v) 153 | } 154 | // pop '[' 155 | stack = stack[:len(stack)-1] 156 | // pop num 157 | idx := 1 158 | for len(stack) >= idx && stack[len(stack)-idx] >= '0' && stack[len(stack)-idx] <= '9' { 159 | idx++ 160 | } 161 | // 注意索引边界 162 | num := stack[len(stack)-idx+1:] 163 | stack = stack[:len(stack)-idx+1] 164 | count, _ := strconv.Atoi(string(num)) 165 | for j := 0; j < count; j++ { 166 | // 把字符正向放回到栈里面 167 | for j := len(temp) - 1; j >= 0; j-- { 168 | stack = append(stack, temp[j]) 169 | } 170 | } 171 | default: 172 | stack = append(stack, s[i]) 173 | 174 | } 175 | } 176 | return string(stack) 177 | } 178 | ``` 179 | 180 | 利用栈进行 DFS 递归搜索模板 181 | 182 | ```go 183 | boolean DFS(int root, int target) { 184 | Set visited; 185 | Stack s; 186 | add root to s; 187 | while (s is not empty) { 188 | Node cur = the top element in s; 189 | return true if cur is target; 190 | for (Node next : the neighbors of cur) { 191 | if (next is not in visited) { 192 | add next to s; 193 | add next to visited; 194 | } 195 | } 196 | remove cur from s; 197 | } 198 | return false; 199 | } 200 | ``` 201 | 202 | [binary-tree-inorder-traversal](https://leetcode-cn.com/problems/binary-tree-inorder-traversal/) 203 | 204 | > 给定一个二叉树,返回它的*中序*遍历。 205 | 206 | ```go 207 | // 思路:通过stack 保存已经访问的元素,用于原路返回 208 | func inorderTraversal(root *TreeNode) []int { 209 | result := make([]int, 0) 210 | if root == nil { 211 | return result 212 | } 213 | stack := make([]*TreeNode, 0) 214 | for len(stack) > 0 || root != nil { 215 | for root != nil { 216 | stack = append(stack, root) 217 | root = root.Left // 一直向左 218 | } 219 | // 弹出 220 | val := stack[len(stack)-1] 221 | stack = stack[:len(stack)-1] 222 | result = append(result, val.Val) 223 | root = val.Right 224 | } 225 | return result 226 | } 227 | ``` 228 | 229 | [clone-graph](https://leetcode-cn.com/problems/clone-graph/) 230 | 231 | > 给你无向连通图中一个节点的引用,请你返回该图的深拷贝(克隆)。 232 | 233 | ```go 234 | func cloneGraph(node *Node) *Node { 235 | visited:=make(map[*Node]*Node) 236 | return clone(node,visited) 237 | } 238 | // 1 2 239 | // 4 3 240 | // 递归克隆,传入已经访问过的元素作为过滤条件 241 | func clone(node *Node,visited map[*Node]*Node)*Node{ 242 | if node==nil{ 243 | return nil 244 | } 245 | // 已经访问过直接返回 246 | if v,ok:=visited[node];ok{ 247 | return v 248 | } 249 | newNode:=&Node{ 250 | Val:node.Val, 251 | Neighbors:make([]*Node,len(node.Neighbors)), 252 | } 253 | visited[node]=newNode 254 | for i:=0;i 给定一个由  '1'(陆地)和 '0'(水)组成的的二维网格,计算岛屿的数量。一个岛被水包围,并且它是通过水平方向或垂直方向上相邻的陆地连接而成的。你可以假设网格的四个边均被水包围。 264 | 265 | 思路:通过深度搜索遍历可能性(注意标记已访问元素) 266 | 267 | ```go 268 | 269 | func numIslands(grid [][]byte) int { 270 | var count int 271 | for i:=0;i=1{ 274 | count++ 275 | } 276 | } 277 | } 278 | return count 279 | } 280 | func dfs(grid [][]byte,i,j int)int{ 281 | if i<0||i>=len(grid)||j<0||j>=len(grid[0]){ 282 | return 0 283 | } 284 | if grid[i][j]=='1'{ 285 | // 标记已经访问过(每一个点只需要访问一次) 286 | grid[i][j]=0 287 | return dfs(grid,i-1,j)+dfs(grid,i,j-1)+dfs(grid,i+1,j)+dfs(grid,i,j+1)+1 288 | } 289 | return 0 290 | } 291 | ``` 292 | 293 | [largest-rectangle-in-histogram](https://leetcode-cn.com/problems/largest-rectangle-in-histogram/) 294 | 295 | > 给定 _n_ 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。 296 | > 求在该柱状图中,能够勾勒出来的矩形的最大面积。 297 | 298 | 思路:求以当前柱子为高度的面积,即转化为寻找小于当前值的左右两边值 299 | 300 | ![image.png](https://img.fuiboom.com/img/stack_rain.png) 301 | 302 | 用栈保存小于当前值的左的元素 303 | 304 | ![image.png](https://img.fuiboom.com/img/stack_rain2.png) 305 | 306 | ```go 307 | func largestRectangleArea(heights []int) int { 308 | if len(heights) == 0 { 309 | return 0 310 | } 311 | stack := make([]int, 0) 312 | max := 0 313 | for i := 0; i <= len(heights); i++ { 314 | var cur int 315 | if i == len(heights) { 316 | cur = 0 317 | } else { 318 | cur = heights[i] 319 | } 320 | // 当前高度小于栈,则将栈内元素都弹出计算面积 321 | for len(stack) != 0 && cur <= heights[stack[len(stack)-1]] { 322 | pop := stack[len(stack)-1] 323 | stack = stack[:len(stack)-1] 324 | h := heights[pop] 325 | // 计算宽度 326 | w := i 327 | if len(stack) != 0 { 328 | peek := stack[len(stack)-1] 329 | w = i - peek - 1 330 | } 331 | max = Max(max, h*w) 332 | } 333 | // 记录索引即可获取对应元素 334 | stack = append(stack, i) 335 | } 336 | return max 337 | } 338 | func Max(a, b int) int { 339 | if a > b { 340 | return a 341 | } 342 | return b 343 | } 344 | ``` 345 | 346 | ## Queue 队列 347 | 348 | 常用于 BFS 宽度优先搜索 349 | 350 | [implement-queue-using-stacks](https://leetcode-cn.com/problems/implement-queue-using-stacks/) 351 | 352 | > 使用栈实现队列 353 | 354 | ```go 355 | type MyQueue struct { 356 | stack []int 357 | back []int 358 | } 359 | 360 | /** Initialize your data structure here. */ 361 | func Constructor() MyQueue { 362 | return MyQueue{ 363 | stack: make([]int, 0), 364 | back: make([]int, 0), 365 | } 366 | } 367 | 368 | // 1 369 | // 3 370 | // 5 371 | 372 | /** Push element x to the back of queue. */ 373 | func (this *MyQueue) Push(x int) { 374 | for len(this.back) != 0 { 375 | val := this.back[len(this.back)-1] 376 | this.back = this.back[:len(this.back)-1] 377 | this.stack = append(this.stack, val) 378 | } 379 | this.stack = append(this.stack, x) 380 | } 381 | 382 | /** Removes the element from in front of queue and returns that element. */ 383 | func (this *MyQueue) Pop() int { 384 | for len(this.stack) != 0 { 385 | val := this.stack[len(this.stack)-1] 386 | this.stack = this.stack[:len(this.stack)-1] 387 | this.back = append(this.back, val) 388 | } 389 | if len(this.back) == 0 { 390 | return 0 391 | } 392 | val := this.back[len(this.back)-1] 393 | this.back = this.back[:len(this.back)-1] 394 | return val 395 | } 396 | 397 | /** Get the front element. */ 398 | func (this *MyQueue) Peek() int { 399 | for len(this.stack) != 0 { 400 | val := this.stack[len(this.stack)-1] 401 | this.stack = this.stack[:len(this.stack)-1] 402 | this.back = append(this.back, val) 403 | } 404 | if len(this.back) == 0 { 405 | return 0 406 | } 407 | val := this.back[len(this.back)-1] 408 | return val 409 | } 410 | 411 | /** Returns whether the queue is empty. */ 412 | func (this *MyQueue) Empty() bool { 413 | return len(this.stack) == 0 && len(this.back) == 0 414 | } 415 | 416 | /** 417 | * Your MyQueue object will be instantiated and called as such: 418 | * obj := Constructor(); 419 | * obj.Push(x); 420 | * param_2 := obj.Pop(); 421 | * param_3 := obj.Peek(); 422 | * param_4 := obj.Empty(); 423 | */ 424 | ``` 425 | 426 | 二叉树层次遍历 427 | 428 | ```go 429 | func levelOrder(root *TreeNode) [][]int { 430 | // 通过上一层的长度确定下一层的元素 431 | result := make([][]int, 0) 432 | if root == nil { 433 | return result 434 | } 435 | queue := make([]*TreeNode, 0) 436 | queue = append(queue, root) 437 | for len(queue) > 0 { 438 | list := make([]int, 0) 439 | // 为什么要取length? 440 | // 记录当前层有多少元素(遍历当前层,再添加下一层) 441 | l := len(queue) 442 | for i := 0; i < l; i++ { 443 | // 出队列 444 | level := queue[0] 445 | queue = queue[1:] 446 | list = append(list, level.Val) 447 | if level.Left != nil { 448 | queue = append(queue, level.Left) 449 | } 450 | if level.Right != nil { 451 | queue = append(queue, level.Right) 452 | } 453 | } 454 | result = append(result, list) 455 | } 456 | return result 457 | } 458 | ``` 459 | 460 | [01-matrix](https://leetcode-cn.com/problems/01-matrix/) 461 | 462 | > 给定一个由 0 和 1 组成的矩阵,找出每个元素到最近的 0 的距离。 463 | > 两个相邻元素间的距离为 1 464 | 465 | ```go 466 | // BFS 从0进队列,弹出之后计算上下左右的结果,将上下左右重新进队列进行二层操作 467 | // 0 0 0 0 468 | // 0 x 0 0 469 | // x x x 0 470 | // 0 x 0 0 471 | 472 | // 0 0 0 0 473 | // 0 1 0 0 474 | // 1 x 1 0 475 | // 0 1 0 0 476 | 477 | // 0 0 0 0 478 | // 0 1 0 0 479 | // 1 2 1 0 480 | // 0 1 0 0 481 | func updateMatrix(matrix [][]int) [][]int { 482 | q:=make([][]int,0) 483 | for i:=0;i=0&&x=0&&y 给定一个  haystack 字符串和一个 needle 字符串,在 haystack 字符串中找出 needle 字符串出现的第一个位置 (从 0 开始)。如果不存在,则返回  -1。 17 | 18 | 思路:核心点遍历给定字符串字符,判断以当前字符开头字符串是否等于目标字符串 19 | 20 | ```go 21 | func strStr(haystack string, needle string) int { 22 | if len(needle) == 0 { 23 | return 0 24 | } 25 | var i, j int 26 | // i不需要到len-1 27 | for i = 0; i < len(haystack)-len(needle)+1; i++ { 28 | for j = 0; j < len(needle); j++ { 29 | if haystack[i+j] != needle[j] { 30 | break 31 | } 32 | } 33 | // 判断字符串长度是否相等 34 | if len(needle) == j { 35 | return i 36 | } 37 | } 38 | return -1 39 | } 40 | ``` 41 | 42 | 需要注意点 43 | 44 | - 循环时,i 不需要到 len-1 45 | - 如果找到目标字符串,len(needle)==j 46 | 47 | 示例 2 48 | 49 | [subsets](https://leetcode-cn.com/problems/subsets/) 50 | 51 | > 给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。 52 | 53 | 思路:这是一个典型的应用回溯法的题目,简单来说就是穷尽所有可能性,算法模板如下 54 | 55 | ```go 56 | result = [] 57 | func backtrack(选择列表,路径): 58 | if 满足结束条件: 59 | result.add(路径) 60 | return 61 | for 选择 in 选择列表: 62 | 做选择 63 | backtrack(选择列表,路径) 64 | 撤销选择 65 | ``` 66 | 67 | 通过不停的选择,撤销选择,来穷尽所有可能性,最后将满足条件的结果返回 68 | 69 | 答案代码 70 | 71 | ```go 72 | func subsets(nums []int) [][]int { 73 | // 保存最终结果 74 | result := make([][]int, 0) 75 | // 保存中间结果 76 | list := make([]int, 0) 77 | backtrack(nums, 0, list, &result) 78 | return result 79 | } 80 | 81 | // nums 给定的集合 82 | // pos 下次添加到集合中的元素位置索引 83 | // list 临时结果集合(每次需要复制保存) 84 | // result 最终结果 85 | func backtrack(nums []int, pos int, list []int, result *[][]int) { 86 | // 把临时结果复制出来保存到最终结果 87 | ans := make([]int, len(list)) 88 | copy(ans, list) 89 | *result = append(*result, ans) 90 | // 选择、处理结果、再撤销选择 91 | for i := pos; i < len(nums); i++ { 92 | list = append(list, nums[i]) 93 | backtrack(nums, i+1, list, result) 94 | list = list[0 : len(list)-1] 95 | } 96 | } 97 | ``` 98 | 99 | 说明:后面会深入讲解几个典型的回溯算法问题,如果当前不太了解可以暂时先跳过 100 | 101 | ## 面试注意点 102 | 103 | 我们大多数时候,刷算法题可能都是为了准备面试,所以面试的时候需要注意一些点 104 | 105 | - 快速定位到题目的知识点,找到知识点的**通用模板**,可能需要根据题目**特殊情况做特殊处理**。 106 | - 先去朝一个解决问题的方向!**先抛出可行解**,而不是最优解!先解决,再优化! 107 | - 代码的风格要统一,熟悉各类语言的代码规范。 108 | - 命名尽量简洁明了,尽量不用数字命名如:i1、node1、a1、b2 109 | - 常见错误总结 110 | - 访问下标时,不能访问越界 111 | - 空值 nil 问题 run time error 112 | 113 | ## 练习 114 | 115 | - [ ] [strStr](https://leetcode-cn.com/problems/implement-strstr/) 116 | - [ ] [subsets](https://leetcode-cn.com/problems/subsets/) 117 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------