diff --git a/README.md b/README.md index 6a360f27..66ed90e6 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,29 @@ -# 算法模板 +# Introduction -![来刷题了](https://img.fuiboom.com/img/title.png) +## Getting Super Powers + +Becoming a super hero is a fairly straight forward process: + +``` +$ give me super-powers +``` + +{% hint style="info" %} + Super-powers are granted randomly so please submit an issue if you're not happy with yours. +{% endhint %} + +Once you're strong enough, save the world: + +{% code title="hello.sh" %} +```bash +# Ain't no code for that yet, sorry +echo 'You got to trust me on this, I saved the world' +``` +{% endcode %} + + + +![来刷题了](https://img.fuiboom.com/img/title.png) 算法模板,最科学的刷题方式,最快速的刷题路径,一个月从入门到 offer,你值得拥有~ @@ -16,28 +39,28 @@ ### 入门篇 🐶 -- [go 语言入门](introduction/golang.md) -- [算法快速入门](introduction/quickstart.md) +* [go 语言入门](ru-men-pian/golang.md) +* [算法快速入门](ru-men-pian/quickstart.md) ### 数据结构篇 🐰 -- [二叉树](data_structure/binary_tree.md) -- [链表](data_structure/linked_list.md) -- [栈和队列](data_structure/stack_queue.md) -- [二进制](data_structure/binary_op.md) +* [二叉树](shu-ju-jie-gou-pian/binary_tree.md) +* [链表](shu-ju-jie-gou-pian/linked_list.md) +* [栈和队列](shu-ju-jie-gou-pian/stack_queue.md) +* [二进制](shu-ju-jie-gou-pian/binary_op.md) ### 基础算法篇 🐮 -- [二分搜索](basic_algorithm/binary_search.md) -- [排序算法](basic_algorithm/sort.md) -- [动态规划](basic_algorithm/dp.md) +* [二分搜索](ji-chu-suan-fa-pian/binary_search.md) +* [排序算法](ji-chu-suan-fa-pian/sort.md) +* [动态规划](ji-chu-suan-fa-pian/dp.md) ### 算法思维 🦁 -- [递归思维](advanced_algorithm/recursion.md) -- [滑动窗口思想](advanced_algorithm/slide_window.md) -- [二叉搜索树](advanced_algorithm/binary_search_tree.md) -- [回溯法](advanced_algorithm/backtrack.md) +* [递归思维](suan-fa-si-wei/recursion.md) +* [滑动窗口思想](suan-fa-si-wei/slide_window.md) +* [二叉搜索树](suan-fa-si-wei/binary_search_tree.md) +* [回溯法](suan-fa-si-wei/backtrack.md) ## 心得体会 @@ -45,11 +68,11 @@ 刷完这些练习题,基本对数据结构和算法有自己的认识体会,基本大部分面试题都能写得出来,国内的 BAT、TMD 应该都不是问题 -从 4 月份找工作开始,从 0 开始刷 LeetCode,中间大概花了一个半月(6 周)左右时间刷完 240 题。 +从 4 月份找工作开始,从 0 开始刷 LeetCode,中间大概花了一个半月\(6 周\)左右时间刷完 240 题。 -![一个半月刷完240题](https://img.fuiboom.com/img/leetcode_time.png) +![一个半月刷完240题](https://img.fuiboom.com/img/leetcode_time.png) -![刷题记录](https://img.fuiboom.com/img/leetcode_record.png) +![刷题记录](https://img.fuiboom.com/img/leetcode_record.png) 开始刷题时,确实是无从下手,因为从序号开始刷,刷到几道题就遇到 hard 的题型,会卡住很久,后面去评论区看别人怎么刷题,也去 Google 搜索最好的刷题方式,发现按题型刷题会舒服很多,基本一个类型的题目,一天能做很多,慢慢刷题也不再枯燥,做起来也很有意思,最后也收到不错的 offer(最后去了宇宙系)。 @@ -61,15 +84,15 @@ 1、 [algorithm-pattern 练习题](https://greyireland.gitbook.io/algorithm-pattern/) -![练习题](https://img.fuiboom.com/img/repo_practice.png) +![练习题](https://img.fuiboom.com/img/repo_practice.png) 2、 [LeetCode 卡片](https://leetcode-cn.com/explore/) -![探索卡片](https://img.fuiboom.com/img/leetcode_explore.png) +![探索卡片](https://img.fuiboom.com/img/leetcode_explore.png) 3、 [剑指 offer](https://leetcode-cn.com/problemset/lcof/) -![剑指offer](https://img.fuiboom.com/img/leetcode_jzoffer.png) +![剑指offer](https://img.fuiboom.com/img/leetcode_jzoffer.png) 刷题时间可以合理分配,如果打算准备面试了,建议前面两部分 一个半月 (6 周)时间刷完,最后剑指 offer 半个月刷完,边刷可以边投简历进行面试,遇到不会的不用着急,往模板上套就对了~ @@ -86,3 +109,4 @@ 持续更新中,觉得还可以的话点个 **star** 收藏呀 ⭐️~ 【 Github 】[https://github.com/greyireland/algorithm-pattern](https://github.com/greyireland/algorithm-pattern) ⭐️ + diff --git a/SUMMARY.md b/SUMMARY.md index 8257ee7e..4caa6ce7 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -1,26 +1,29 @@ -# 算法模板 +# Table of contents + +* [Introduction](README.md) ## 入门篇 -- [go 语言入门](introduction/golang.md) -- [算法快速入门](introduction/quickstart.md) +* [go 语言入门](ru-men-pian/golang.md) +* [算法快速入门](ru-men-pian/quickstart.md) ## 数据结构篇 -- [二叉树](data_structure/binary_tree.md) -- [链表](data_structure/linked_list.md) -- [栈和队列](data_structure/stack_queue.md) -- [二进制](data_structure/binary_op.md) +* [二叉树](shu-ju-jie-gou-pian/binary_tree.md) +* [链表](shu-ju-jie-gou-pian/linked_list.md) +* [栈和队列](shu-ju-jie-gou-pian/stack_queue.md) +* [二进制](shu-ju-jie-gou-pian/binary_op.md) ## 基础算法篇 -- [二分搜索](basic_algorithm/binary_search.md) -- [排序算法](basic_algorithm/sort.md) -- [动态规划](basic_algorithm/dp.md) +* [二分搜索](ji-chu-suan-fa-pian/binary_search.md) +* [排序算法](ji-chu-suan-fa-pian/sort.md) +* [动态规划](ji-chu-suan-fa-pian/dp.md) ## 算法思维 -- [递归思维](advanced_algorithm/recursion.md) -- [滑动窗口思想](advanced_algorithm/slide_window.md) -- [二叉搜索树](advanced_algorithm/binary_search_tree.md) -- [回溯法](advanced_algorithm/backtrack.md) +* [递归思维](suan-fa-si-wei/recursion.md) +* [滑动窗口思想](suan-fa-si-wei/slide_window.md) +* [二叉搜索树](suan-fa-si-wei/binary_search_tree.md) +* [回溯法](suan-fa-si-wei/backtrack.md) + diff --git a/advanced_algorithm/slide_window.md b/advanced_algorithm/slide_window.md deleted file mode 100644 index 4e043302..00000000 --- a/advanced_algorithm/slide_window.md +++ /dev/null @@ -1,254 +0,0 @@ -# 滑动窗口 - -## 模板 - -```cpp -/* 滑动窗口算法框架 */ -void slidingWindow(string s, string t) { - unordered_map need, window; - for (char c : t) need[c]++; - - int left = 0, right = 0; - int valid = 0; - while (right < s.size()) { - // c 是将移入窗口的字符 - char c = s[right]; - // 右移窗口 - right++; - // 进行窗口内数据的一系列更新 - ... - - /*** debug 输出的位置 ***/ - printf("window: [%d, %d)\n", left, right); - /********************/ - - // 判断左侧窗口是否要收缩 - while (window needs shrink) { - // d 是将移出窗口的字符 - char d = s[left]; - // 左移窗口 - left++; - // 进行窗口内数据的一系列更新 - ... - } - } -} -``` - -需要变化的地方 - -- 1、右指针右移之后窗口数据更新 -- 2、判断窗口是否要收缩 -- 3、左指针右移之后窗口数据更新 -- 4、根据题意计算结果 - -## 示例 - -[minimum-window-substring](https://leetcode-cn.com/problems/minimum-window-substring/) - -> 给你一个字符串 S、一个字符串 T,请在字符串 S 里面找出:包含 T 所有字母的最小子串 - -```go -func minWindow(s string, t string) string { - // 保存滑动窗口字符集 - win := make(map[byte]int) - // 保存需要的字符集 - need := make(map[byte]int) - for i := 0; i < len(t); i++ { - need[t[i]]++ - } - // 窗口 - left := 0 - right := 0 - // match匹配次数 - match := 0 - start := 0 - end := 0 - min := math.MaxInt64 - var c byte - for right < len(s) { - c = s[right] - right++ - // 在需要的字符集里面,添加到窗口字符集里面 - if need[c] != 0 { - win[c]++ - // 如果当前字符的数量匹配需要的字符的数量,则match值+1 - if win[c] == need[c] { - match++ - } - } - - // 当所有字符数量都匹配之后,开始缩紧窗口 - for match == len(need) { - // 获取结果 - if right-left < min { - min = right - left - start = left - end = right - } - c = s[left] - left++ - // 左指针指向不在需要字符集则直接跳过 - if need[c] != 0 { - // 左指针指向字符数量和需要的字符相等时,右移之后match值就不匹配则减一 - // 因为win里面的字符数可能比较多,如有10个A,但需要的字符数量可能为3 - // 所以在压死骆驼的最后一根稻草时,match才减一,这时候才跳出循环 - if win[c] == need[c] { - match-- - } - win[c]-- - } - } - } - if min == math.MaxInt64 { - return "" - } - return s[start:end] -} -``` - -[permutation-in-string](https://leetcode-cn.com/problems/permutation-in-string/) - -> 给定两个字符串  **s1**  和  **s2**,写一个函数来判断  **s2**  是否包含  **s1 **的排列。 - -```go -func checkInclusion(s1 string, s2 string) bool { - win := make(map[byte]int) - need := make(map[byte]int) - for i := 0; i < len(s1); i++ { - need[s1[i]]++ - } - left := 0 - right := 0 - match := 0 - for right < len(s2) { - c := s2[right] - right++ - if need[c] != 0 { - win[c]++ - if win[c] == need[c] { - match++ - } - } - // 当窗口长度大于字符串长度,缩紧窗口 - for right-left >= len(s1) { - // 当窗口长度和字符串匹配,并且里面每个字符数量也匹配时,满足条件 - if match == len(need) { - return true - } - d := s2[left] - left++ - if need[d] != 0 { - if win[d] == need[d] { - match-- - } - win[d]-- - } - } - } - return false -} - -``` - -[find-all-anagrams-in-a-string](https://leetcode-cn.com/problems/find-all-anagrams-in-a-string/) - -> 给定一个字符串  **s **和一个非空字符串  **p**,找到  **s **中所有是  **p **的字母异位词的子串,返回这些子串的起始索引。 - -```go -func findAnagrams(s string, p string) []int { - win := make(map[byte]int) - need := make(map[byte]int) - for i := 0; i < len(p); i++ { - need[p[i]]++ - } - left := 0 - right := 0 - match := 0 - ans:=make([]int,0) - for right < len(s) { - c := s[right] - right++ - if need[c] != 0 { - win[c]++ - if win[c] == need[c] { - match++ - } - } - // 当窗口长度大于字符串长度,缩紧窗口 - for right-left >= len(p) { - // 当窗口长度和字符串匹配,并且里面每个字符数量也匹配时,满足条件 - if right-left == len(p)&& match == len(need) { - ans=append(ans,left) - } - d := s[left] - left++ - if need[d] != 0 { - if win[d] == need[d] { - match-- - } - win[d]-- - } - } - } - return ans -} -``` - -[longest-substring-without-repeating-characters](https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/) - -> 给定一个字符串,请你找出其中不含有重复字符的   最长子串   的长度。 -> 示例  1: -> -> 输入: "abcabcbb" -> 输出: 3 -> 解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。 - -```go -func lengthOfLongestSubstring(s string) int { - // 滑动窗口核心点:1、右指针右移 2、根据题意收缩窗口 3、左指针右移更新窗口 4、根据题意计算结果 - if len(s)==0{ - return 0 - } - win:=make(map[byte]int) - left:=0 - right:=0 - ans:=1 - for right1{ - d:=s[left] - left++ - win[d]-- - } - // 计算结果 - ans=max(right-left,ans) - } - return ans -} -func max(a,b int)int{ - if a>b{ - return a - } - return b -} -``` - -## 总结 - -- 和双指针题目类似,更像双指针的升级版,滑动窗口核心点是维护一个窗口集,根据窗口集来进行处理 -- 核心步骤 - - right 右移 - - 收缩 - - left 右移 - - 求结果 - -## 练习 - -- [ ] [minimum-window-substring](https://leetcode-cn.com/problems/minimum-window-substring/) -- [ ] [permutation-in-string](https://leetcode-cn.com/problems/permutation-in-string/) -- [ ] [find-all-anagrams-in-a-string](https://leetcode-cn.com/problems/find-all-anagrams-in-a-string/) -- [ ] [longest-substring-without-repeating-characters](https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/) diff --git a/basic_algorithm/binary_search.md b/ji-chu-suan-fa-pian/binary_search.md similarity index 77% rename from basic_algorithm/binary_search.md rename to ji-chu-suan-fa-pian/binary_search.md index 15f336a3..e2c33c50 100644 --- a/basic_algorithm/binary_search.md +++ b/ji-chu-suan-fa-pian/binary_search.md @@ -6,18 +6,18 @@ 模板四点要素 -- 1、初始化:start=0、end=len-1 -- 2、循环退出条件:start + 1 < end -- 3、比较中点和目标值:A[mid] ==、 <、> target -- 4、判断最后两个元素是否符合:A[start]、A[end] ? target +* 1、初始化:start=0、end=len-1 +* 2、循环退出条件:start + 1 < end +* 3、比较中点和目标值:A\[mid\] ==、 <、> target +* 4、判断最后两个元素是否符合:A\[start\]、A\[end\] ? target -时间复杂度 O(logn),使用场景一般是有序数组的查找 +时间复杂度 O\(logn\),使用场景一般是有序数组的查找 典型示例 [binary-search](https://leetcode-cn.com/problems/binary-search/) -> 给定一个  n  个元素有序的(升序)整型数组  nums 和一个目标值  target  ,写一个函数搜索  nums  中的 target,如果目标值存在返回下标,否则返回 -1。 +> 给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。 ```go // 二分搜索最常用模板 @@ -50,13 +50,13 @@ func search(nums []int, target int) int { 大部分二分查找类的题目都可以用这个模板,然后做一点特殊逻辑即可 -另外二分查找还有一些其他模板如下图,大部分场景模板#3 都能解决问题,而且还能找第一次/最后一次出现的位置,应用更加广泛 +另外二分查找还有一些其他模板如下图,大部分场景模板\#3 都能解决问题,而且还能找第一次/最后一次出现的位置,应用更加广泛 -![binary_search_template](https://img.fuiboom.com/img/binary_search_template.png) +![binary\_search\_template](https://img.fuiboom.com/img/binary_search_template.png) -所以用模板#3 就对了,详细的对比可以这边文章介绍:[二分搜索模板](https://leetcode-cn.com/explore/learn/card/binary-search/212/template-analysis/847/) +所以用模板\#3 就对了,详细的对比可以这边文章介绍:[二分搜索模板](https://leetcode-cn.com/explore/learn/card/binary-search/212/template-analysis/847/) -如果是最简单的二分搜索,不需要找第一个、最后一个位置、或者是没有重复元素,可以使用模板#1,代码更简洁 +如果是最简单的二分搜索,不需要找第一个、最后一个位置、或者是没有重复元素,可以使用模板\#1,代码更简洁 ```go // 无重复元素搜索时,更方便 @@ -84,8 +84,7 @@ func search(nums []int, target int) int { ### [search-for-range](https://www.lintcode.com/problem/search-for-a-range/description) -> 给定一个包含 n 个整数的排序数组,找出给定目标值 target 的起始和结束位置。 -> 如果目标值不在数组中,则返回`[-1, -1]` +> 给定一个包含 n 个整数的排序数组,找出给定目标值 target 的起始和结束位置。 如果目标值不在数组中,则返回`[-1, -1]` 思路:核心点就是找第一个 target 的索引,和最后一个 target 的索引,所以用两次二分搜索分别找第一次和最后一次的位置 @@ -178,10 +177,10 @@ func searchInsert(nums []int, target int) int { ### [search-a-2d-matrix](https://leetcode-cn.com/problems/search-a-2d-matrix/) -> 编写一个高效的算法来判断  m x n  矩阵中,是否存在一个目标值。该矩阵具有如下特性: +> 编写一个高效的算法来判断 m x n 矩阵中,是否存在一个目标值。该矩阵具有如下特性: > -> - 每行中的整数从左到右按升序排列。 -> - 每行的第一个整数大于前一行的最后一个整数。 +> * 每行中的整数从左到右按升序排列。 +> * 每行的第一个整数大于前一行的最后一个整数。 ```go func searchMatrix(matrix [][]int, target int) bool { @@ -214,8 +213,7 @@ func searchMatrix(matrix [][]int, target int) bool { ### [first-bad-version](https://leetcode-cn.com/problems/first-bad-version/) -> 假设你有 n 个版本 [1, 2, ..., n],你想找出导致之后所有版本出错的第一个错误的版本。 -> 你可以通过调用  bool isBadVersion(version)  接口来判断版本号 version 是否在单元测试中出错。实现一个函数来查找第一个错误的版本。你应该尽量减少对调用 API 的次数。 +> 假设你有 n 个版本 \[1, 2, ..., n\],你想找出导致之后所有版本出错的第一个错误的版本。 你可以通过调用 bool isBadVersion\(version\) 接口来判断版本号 version 是否在单元测试中出错。实现一个函数来查找第一个错误的版本。你应该尽量减少对调用 API 的次数。 ```go func firstBadVersion(n int) int { @@ -239,8 +237,7 @@ func firstBadVersion(n int) int { ### [find-minimum-in-rotated-sorted-array](https://leetcode-cn.com/problems/find-minimum-in-rotated-sorted-array/) -> 假设按照升序排序的数组在预先未知的某个点上进行了旋转( 例如,数组  [0,1,2,4,5,6,7] 可能变为  [4,5,6,7,0,1,2] )。 -> 请找出其中最小的元素。 +> 假设按照升序排序的数组在预先未知的某个点上进行了旋转\( 例如,数组 \[0,1,2,4,5,6,7\] 可能变为 \[4,5,6,7,0,1,2\] \)。 请找出其中最小的元素。 ```go func findMin(nums []int) int { @@ -269,9 +266,7 @@ func findMin(nums []int) int { ### [find-minimum-in-rotated-sorted-array-ii](https://leetcode-cn.com/problems/find-minimum-in-rotated-sorted-array-ii/) -> 假设按照升序排序的数组在预先未知的某个点上进行了旋转 -> ( 例如,数组  [0,1,2,4,5,6,7] 可能变为  [4,5,6,7,0,1,2] )。 -> 请找出其中最小的元素。(包含重复元素) +> 假设按照升序排序的数组在预先未知的某个点上进行了旋转 \( 例如,数组 \[0,1,2,4,5,6,7\] 可能变为 \[4,5,6,7,0,1,2\] \)。 请找出其中最小的元素。\(包含重复元素\) ```go func findMin(nums []int) int { @@ -306,10 +301,7 @@ func findMin(nums []int) int { ### [search-in-rotated-sorted-array](https://leetcode-cn.com/problems/search-in-rotated-sorted-array/) -> 假设按照升序排序的数组在预先未知的某个点上进行了旋转。 -> ( 例如,数组  [0,1,2,4,5,6,7]  可能变为  [4,5,6,7,0,1,2] )。 -> 搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回  -1 。 -> 你可以假设数组中不存在重复的元素。 +> 假设按照升序排序的数组在预先未知的某个点上进行了旋转。 \( 例如,数组 \[0,1,2,4,5,6,7\] 可能变为 \[4,5,6,7,0,1,2\] \)。 搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回 -1 。 你可以假设数组中不存在重复的元素。 ```go func search(nums []int, target int) int { @@ -355,9 +347,7 @@ func search(nums []int, target int) int { ### [search-in-rotated-sorted-array-ii](https://leetcode-cn.com/problems/search-in-rotated-sorted-array-ii/) -> 假设按照升序排序的数组在预先未知的某个点上进行了旋转。 -> ( 例如,数组  [0,0,1,2,2,5,6]  可能变为  [2,5,6,0,0,1,2] )。 -> 编写一个函数来判断给定的目标值是否存在于数组中。若存在返回  true,否则返回  false。(包含重复元素) +> 假设按照升序排序的数组在预先未知的某个点上进行了旋转。 \( 例如,数组 \[0,0,1,2,2,5,6\] 可能变为 \[2,5,6,0,0,1,2\] \)。 编写一个函数来判断给定的目标值是否存在于数组中。若存在返回 true,否则返回 false。\(包含重复元素\) ```go func search(nums []int, target int) bool { @@ -406,18 +396,19 @@ func search(nums []int, target int) bool { 二分搜索核心四点要素(必背&理解) -- 1、初始化:start=0、end=len-1 -- 2、循环退出条件:start + 1 < end -- 3、比较中点和目标值:A[mid] ==、 <、> target -- 4、判断最后两个元素是否符合:A[start]、A[end] ? target +* 1、初始化:start=0、end=len-1 +* 2、循环退出条件:start + 1 < end +* 3、比较中点和目标值:A\[mid\] ==、 <、> target +* 4、判断最后两个元素是否符合:A\[start\]、A\[end\] ? target ## 练习题 -- [ ] [search-for-range](https://www.lintcode.com/problem/search-for-a-range/description) -- [ ] [search-insert-position](https://leetcode-cn.com/problems/search-insert-position/) -- [ ] [search-a-2d-matrix](https://leetcode-cn.com/problems/search-a-2d-matrix/) -- [ ] [first-bad-version](https://leetcode-cn.com/problems/first-bad-version/) -- [ ] [find-minimum-in-rotated-sorted-array](https://leetcode-cn.com/problems/find-minimum-in-rotated-sorted-array/) -- [ ] [find-minimum-in-rotated-sorted-array-ii](https://leetcode-cn.com/problems/find-minimum-in-rotated-sorted-array-ii/) -- [ ] [search-in-rotated-sorted-array](https://leetcode-cn.com/problems/search-in-rotated-sorted-array/) -- [ ] [search-in-rotated-sorted-array-ii](https://leetcode-cn.com/problems/search-in-rotated-sorted-array-ii/) +* [ ] [search-for-range](https://www.lintcode.com/problem/search-for-a-range/description) +* [ ] [search-insert-position](https://leetcode-cn.com/problems/search-insert-position/) +* [ ] [search-a-2d-matrix](https://leetcode-cn.com/problems/search-a-2d-matrix/) +* [ ] [first-bad-version](https://leetcode-cn.com/problems/first-bad-version/) +* [ ] [find-minimum-in-rotated-sorted-array](https://leetcode-cn.com/problems/find-minimum-in-rotated-sorted-array/) +* [ ] [find-minimum-in-rotated-sorted-array-ii](https://leetcode-cn.com/problems/find-minimum-in-rotated-sorted-array-ii/) +* [ ] [search-in-rotated-sorted-array](https://leetcode-cn.com/problems/search-in-rotated-sorted-array/) +* [ ] [search-in-rotated-sorted-array-ii](https://leetcode-cn.com/problems/search-in-rotated-sorted-array-ii/) + diff --git a/basic_algorithm/dp.md b/ji-chu-suan-fa-pian/dp.md similarity index 60% rename from basic_algorithm/dp.md rename to ji-chu-suan-fa-pian/dp.md index d251ca7d..77bcbf94 100644 --- a/basic_algorithm/dp.md +++ b/ji-chu-suan-fa-pian/dp.md @@ -4,7 +4,7 @@ 先从一道题目开始~ -如题  [triangle](https://leetcode-cn.com/problems/triangle/) +如题 [triangle](https://leetcode-cn.com/problems/triangle/) > 给定一个三角形,找出自顶向下的最小路径和。每一步只能移动到下一行中相邻的结点上。 @@ -19,7 +19,7 @@ ] ``` -自顶向下的最小路径和为  11(即,2 + 3 + 5 + 1 = 11)。 +自顶向下的最小路径和为 11(即,2 + 3 + 5 + 1 = 11)。 使用 DFS(遍历 或者 分治法) @@ -39,44 +39,43 @@ 动态规划和 DFS 区别 -- 二叉树 子问题是没有交集,所以大部分二叉树都用递归或者分治法,即 DFS,就可以解决 -- 像 triangle 这种是有重复走的情况,**子问题是有交集**,所以可以用动态规划来解决 +* 二叉树 子问题是没有交集,所以大部分二叉树都用递归或者分治法,即 DFS,就可以解决 +* 像 triangle 这种是有重复走的情况,**子问题是有交集**,所以可以用动态规划来解决 动态规划,自底向上 ```go func minimumTotal(triangle [][]int) int { - if len(triangle) == 0 || len(triangle[0]) == 0 { - return 0 - } - // 1、状态定义:f[i][j] 表示从i,j出发,到达最后一层的最短路径 - var l = len(triangle) - var f = make([][]int, l) - // 2、初始化 - for i := 0; i < l; i++ { - for j := 0; j < len(triangle[i]); j++ { - if f[i] == nil { - f[i] = make([]int, len(triangle[i])) - } - f[i][j] = triangle[i][j] - } - } - // 3、递推求解 - for i := len(triangle) - 2; i >= 0; i-- { - for j := 0; j < len(triangle[i]); j++ { - f[i][j] = min(f[i+1][j], f[i+1][j+1]) + triangle[i][j] - } - } - // 4、答案 - return f[0][0] + if len(triangle) == 0 || len(triangle[0]) == 0 { + return 0 + } + // 1、状态定义:f[i][j] 表示从i,j出发,到达最后一层的最短路径 + var l = len(triangle) + var f = make([][]int, l) + // 2、初始化 + for i := 0; i < l; i++ { + for j := 0; j < len(triangle[i]); j++ { + if f[i] == nil { + f[i] = make([]int, len(triangle[i])) + } + f[i][j] = triangle[i][j] + } + } + // 3、递推求解 + for i := len(triangle) - 2; i >= 0; i-- { + for j := 0; j < len(triangle[i]); j++ { + f[i][j] = min(f[i+1][j], f[i+1][j+1]) + triangle[i][j] + } + } + // 4、答案 + return f[0][0] } func min(a, b int) int { - if a > b { - return b - } - return a + if a > b { + return b + } + return a } - ``` 动态规划,自顶向下 @@ -140,59 +139,55 @@ func min(a, b int) int { ```go Function(x) { - ... - Funciton(x-1); - ... + ... + Funciton(x-1); + ... } ``` -动态规划:是一种解决问 题的思想,大规模问题的结果,是由小规模问 题的结果运算得来的。动态规划可用递归来实现(Memorization Search) +动态规划:是一种解决问 题的思想,大规模问题的结果,是由小规模问 题的结果运算得来的。动态规划可用递归来实现\(Memorization Search\) ## 使用场景 满足两个条件 -- 满足以下条件之一 - - 求最大/最小值(Maximum/Minimum ) - - 求是否可行(Yes/No ) - - 求可行个数(Count(\*) ) -- 满足不能排序或者交换(Can not sort / swap ) +* 满足以下条件之一 + * 求最大/最小值(Maximum/Minimum ) + * 求是否可行(Yes/No ) + * 求可行个数(Count\(\*\) ) +* 满足不能排序或者交换(Can not sort / swap ) -如题:[longest-consecutive-sequence](https://leetcode-cn.com/problems/longest-consecutive-sequence/)  位置可以交换,所以不用动态规划 +如题:[longest-consecutive-sequence](https://leetcode-cn.com/problems/longest-consecutive-sequence/) 位置可以交换,所以不用动态规划 ## 四点要素 1. **状态 State** - - 灵感,创造力,存储小规模问题的结果 + * 灵感,创造力,存储小规模问题的结果 2. 方程 Function - - 状态之间的联系,怎么通过小的状态,来算大的状态 + * 状态之间的联系,怎么通过小的状态,来算大的状态 3. 初始化 Intialization - - 最极限的小状态是什么, 起点 + * 最极限的小状态是什么, 起点 4. 答案 Answer - - 最大的那个状态是什么,终点 + * 最大的那个状态是什么,终点 ## 常见四种类型 -1. Matrix DP (10%) -1. Sequence (40%) -1. Two Sequences DP (40%) -1. Backpack (10%) +1. Matrix DP \(10%\) +2. Sequence \(40%\) +3. Two Sequences DP \(40%\) +4. Backpack \(10%\) > 注意点 > -> - 贪心算法大多题目靠背答案,所以如果能用动态规划就尽量用动规,不用贪心算法 +> * 贪心算法大多题目靠背答案,所以如果能用动态规划就尽量用动规,不用贪心算法 ## 1、矩阵类型(10%) ### [minimum-path-sum](https://leetcode-cn.com/problems/minimum-path-sum/) -> 给定一个包含非负整数的  *m* x *n*  网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。 +> 给定一个包含非负整数的 _m_ x _n_ 网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。 -思路:动态规划 -1、state: f[x][y]从起点走到 x,y 的最短路径 -2、function: f[x][y] = min(f[x-1][y], f[x][y-1]) + A[x][y] -3、intialize: f[0][0] = A[0][0]、f[i][0] = sum(0,0 -> i,0)、 f[0][i] = sum(0,0 -> 0,i) -4、answer: f[n-1][m-1] +思路:动态规划 1、state: f\[x\]\[y\]从起点走到 x,y 的最短路径 2、function: f\[x\]\[y\] = min\(f\[x-1\]\[y\], f\[x\]\[y-1\]\) + A\[x\]\[y\] 3、intialize: f\[0\]\[0\] = A\[0\]\[0\]、f\[i\]\[0\] = sum\(0,0 -> i,0\)、 f\[0\]\[i\] = sum\(0,0 -> 0,i\) 4、answer: f\[n-1\]\[m-1\] ```go func minPathSum(grid [][]int) int { @@ -226,75 +221,70 @@ func min(a, b int) int { ### [unique-paths](https://leetcode-cn.com/problems/unique-paths/) -> 一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。 -> 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。 -> 问总共有多少条不同的路径? +> 一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。 问总共有多少条不同的路径? ```go func uniquePaths(m int, n int) int { - // f[i][j] 表示i,j到0,0路径数 - f := make([][]int, m) - for i := 0; i < m; i++ { - for j := 0; j < n; j++ { - if f[i] == nil { - f[i] = make([]int, n) - } - f[i][j] = 1 - } - } - for i := 1; i < m; i++ { - for j := 1; j < n; j++ { - f[i][j] = f[i-1][j] + f[i][j-1] - } - } - return f[m-1][n-1] + // f[i][j] 表示i,j到0,0路径数 + f := make([][]int, m) + for i := 0; i < m; i++ { + for j := 0; j < n; j++ { + if f[i] == nil { + f[i] = make([]int, n) + } + f[i][j] = 1 + } + } + for i := 1; i < m; i++ { + for j := 1; j < n; j++ { + f[i][j] = f[i-1][j] + f[i][j-1] + } + } + return f[m-1][n-1] } ``` ### [unique-paths-ii](https://leetcode-cn.com/problems/unique-paths-ii/) -> 一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。 -> 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。 -> 问总共有多少条不同的路径? -> 现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径? +> 一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。 问总共有多少条不同的路径? 现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径? ```go func uniquePathsWithObstacles(obstacleGrid [][]int) int { - // f[i][j] = f[i-1][j] + f[i][j-1] 并检查障碍物 - if obstacleGrid[0][0] == 1 { - return 0 - } - m := len(obstacleGrid) - n := len(obstacleGrid[0]) - f := make([][]int, m) - for i := 0; i < m; i++ { - for j := 0; j < n; j++ { - if f[i] == nil { - f[i] = make([]int, n) - } - f[i][j] = 1 - } - } - for i := 1; i < m; i++ { - if obstacleGrid[i][0] == 1 || f[i-1][0] == 0 { - f[i][0] = 0 - } - } - for j := 1; j < n; j++ { - if obstacleGrid[0][j] == 1 || f[0][j-1] == 0 { - f[0][j] = 0 - } - } - for i := 1; i < m; i++ { - for j := 1; j < n; j++ { - if obstacleGrid[i][j] == 1 { - f[i][j] = 0 - } else { - f[i][j] = f[i-1][j] + f[i][j-1] - } - } - } - return f[m-1][n-1] + // f[i][j] = f[i-1][j] + f[i][j-1] 并检查障碍物 + if obstacleGrid[0][0] == 1 { + return 0 + } + m := len(obstacleGrid) + n := len(obstacleGrid[0]) + f := make([][]int, m) + for i := 0; i < m; i++ { + for j := 0; j < n; j++ { + if f[i] == nil { + f[i] = make([]int, n) + } + f[i][j] = 1 + } + } + for i := 1; i < m; i++ { + if obstacleGrid[i][0] == 1 || f[i-1][0] == 0 { + f[i][0] = 0 + } + } + for j := 1; j < n; j++ { + if obstacleGrid[0][j] == 1 || f[0][j-1] == 0 { + f[0][j] = 0 + } + } + for i := 1; i < m; i++ { + for j := 1; j < n; j++ { + if obstacleGrid[i][j] == 1 { + f[i][j] = 0 + } else { + f[i][j] = f[i-1][j] + f[i][j-1] + } + } + } + return f[m-1][n-1] } ``` @@ -302,7 +292,7 @@ func uniquePathsWithObstacles(obstacleGrid [][]int) int { ### [climbing-stairs](https://leetcode-cn.com/problems/climbing-stairs/) -> 假设你正在爬楼梯。需要  *n*  阶你才能到达楼顶。 +> 假设你正在爬楼梯。需要 _n_ 阶你才能到达楼顶。 ```go func climbStairs(n int) int { @@ -322,9 +312,7 @@ func climbStairs(n int) int { ### [jump-game](https://leetcode-cn.com/problems/jump-game/) -> 给定一个非负整数数组,你最初位于数组的第一个位置。 -> 数组中的每个元素代表你在该位置可以跳跃的最大长度。 -> 判断你是否能够到达最后一个位置。 +> 给定一个非负整数数组,你最初位于数组的第一个位置。 数组中的每个元素代表你在该位置可以跳跃的最大长度。 判断你是否能够到达最后一个位置。 ```go func canJump(nums []int) bool { @@ -351,9 +339,7 @@ func canJump(nums []int) bool { ### [jump-game-ii](https://leetcode-cn.com/problems/jump-game-ii/) -> 给定一个非负整数数组,你最初位于数组的第一个位置。 -> 数组中的每个元素代表你在该位置可以跳跃的最大长度。 -> 你的目标是使用最少的跳跃次数到达数组的最后一个位置。 +> 给定一个非负整数数组,你最初位于数组的第一个位置。 数组中的每个元素代表你在该位置可以跳跃的最大长度。 你的目标是使用最少的跳跃次数到达数组的最后一个位置。 ```go func jump(nums []int) int { @@ -385,52 +371,51 @@ func min(a, b int) int { ### [palindrome-partitioning-ii](https://leetcode-cn.com/problems/palindrome-partitioning-ii/) -> 给定一个字符串 _s_,将 _s_ 分割成一些子串,使每个子串都是回文串。 -> 返回符合要求的最少分割次数。 +> 给定一个字符串 _s_,将 _s_ 分割成一些子串,使每个子串都是回文串。 返回符合要求的最少分割次数。 ```go func minCut(s string) int { - // state: f[i] "前i"个字符组成的子字符串需要最少几次cut(个数-1为索引) - // function: f[i] = MIN{f[j]+1}, j < i && [j+1 ~ i]这一段是一个回文串 - // intialize: f[i] = i - 1 (f[0] = -1) - // answer: f[s.length()] - if len(s) == 0 || len(s) == 1 { - return 0 - } - f := make([]int, len(s)+1) - f[0] = -1 - f[1] = 0 - for i := 1; i <= len(s); i++ { - f[i] = i - 1 - for j := 0; j < i; j++ { - if isPalindrome(s, j, i-1) { - f[i] = min(f[i], f[j]+1) - } - } - } - return f[len(s)] + // state: f[i] "前i"个字符组成的子字符串需要最少几次cut(个数-1为索引) + // function: f[i] = MIN{f[j]+1}, j < i && [j+1 ~ i]这一段是一个回文串 + // intialize: f[i] = i - 1 (f[0] = -1) + // answer: f[s.length()] + if len(s) == 0 || len(s) == 1 { + return 0 + } + f := make([]int, len(s)+1) + f[0] = -1 + f[1] = 0 + for i := 1; i <= len(s); i++ { + f[i] = i - 1 + for j := 0; j < i; j++ { + if isPalindrome(s, j, i-1) { + f[i] = min(f[i], f[j]+1) + } + } + } + return f[len(s)] } func min(a, b int) int { - if a > b { - return b - } - return a + if a > b { + return b + } + return a } func isPalindrome(s string, i, j int) bool { - for i < j { - if s[i] != s[j] { - return false - } - i++ - j-- - } - return true + for i < j { + if s[i] != s[j] { + return false + } + i++ + j-- + } + return true } ``` 注意点 -- 判断回文字符串时,可以提前用动态规划算好,减少时间复杂度 +* 判断回文字符串时,可以提前用动态规划算好,减少时间复杂度 ### [longest-increasing-subsequence](https://leetcode-cn.com/problems/longest-increasing-subsequence/) @@ -472,68 +457,65 @@ func max(a, b int) int { ### [word-break](https://leetcode-cn.com/problems/word-break/) -> 给定一个**非空**字符串  *s*  和一个包含**非空**单词列表的字典  *wordDict*,判定  *s*  是否可以被空格拆分为一个或多个在字典中出现的单词。 +> 给定一个**非空**字符串 _s_ 和一个包含**非空**单词列表的字典 _wordDict_,判定 _s_ 是否可以被空格拆分为一个或多个在字典中出现的单词。 ```go func wordBreak(s string, wordDict []string) bool { - // f[i] 表示前i个字符是否可以被切分 - // f[i] = f[j] && s[j+1~i] in wordDict - // f[0] = true - // return f[len] - - if len(s) == 0 { - return true - } - f := make([]bool, len(s)+1) - f[0] = true - max := maxLen(wordDict) - for i := 1; i <= len(s); i++ { - for j := i - max; j < i && j >= 0; j++ { - if f[j] && inDict(s[j:i]) { - f[i] = true - break - } - } - } - return f[len(s)] + // f[i] 表示前i个字符是否可以被切分 + // f[i] = f[j] && s[j+1~i] in wordDict + // f[0] = true + // return f[len] + + if len(s) == 0 { + return true + } + f := make([]bool, len(s)+1) + f[0] = true + max := maxLen(wordDict) + for i := 1; i <= len(s); i++ { + for j := i - max; j < i && j >= 0; j++ { + if f[j] && inDict(s[j:i]) { + f[i] = true + break + } + } + } + return f[len(s)] } var dict = make(map[string]bool) func maxLen(wordDict []string) int { - max := 0 - for _, v := range wordDict { - dict[v] = true - if len(v) > max { - max = len(v) - } - } - return max + max := 0 + for _, v := range wordDict { + dict[v] = true + if len(v) > max { + max = len(v) + } + } + return max } func inDict(s string) bool { - _, ok := dict[s] - return ok + _, ok := dict[s] + return ok } - ``` 小结 -常见处理方式是给 0 位置占位,这样处理问题时一视同仁,初始化则在原来基础上 length+1,返回结果 f[n] +常见处理方式是给 0 位置占位,这样处理问题时一视同仁,初始化则在原来基础上 length+1,返回结果 f\[n\] -- 状态可以为前 i 个 -- 初始化 length+1 -- 取值 index=i-1 -- 返回值:f[n]或者 f[m][n] +* 状态可以为前 i 个 +* 初始化 length+1 +* 取值 index=i-1 +* 返回值:f\[n\]或者 f\[m\]\[n\] ## Two Sequences DP(40%) ### [longest-common-subsequence](https://leetcode-cn.com/problems/longest-common-subsequence/) -> 给定两个字符串  text1 和  text2,返回这两个字符串的最长公共子序列。 -> 一个字符串的   子序列   是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。 -> 例如,"ace" 是 "abcde" 的子序列,但 "aec" 不是 "abcde" 的子序列。两个字符串的「公共子序列」是这两个字符串所共同拥有的子序列。 +> 给定两个字符串 text1 和 text2,返回这两个字符串的最长公共子序列。 一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。 例如,"ace" 是 "abcde" 的子序列,但 "aec" 不是 "abcde" 的子序列。两个字符串的「公共子序列」是这两个字符串所共同拥有的子序列。 ```go func longestCommonSubsequence(a string, b string) int { @@ -570,7 +552,7 @@ func max(a,b int)int { 注意点 -- go 切片初始化 +* go 切片初始化 ```go dp:=make([][]int,len(a)+1) @@ -579,16 +561,13 @@ for i:=0;i<=len(a);i++ { } ``` -- 从 1 开始遍历到最大长度 -- 索引需要减一 +* 从 1 开始遍历到最大长度 +* 索引需要减一 ### [edit-distance](https://leetcode-cn.com/problems/edit-distance/) -> 给你两个单词  word1 和  word2,请你计算出将  word1  转换成  word2 所使用的最少操作数   -> 你可以对一个单词进行如下三种操作: -> 插入一个字符 -> 删除一个字符 -> 替换一个字符 +> 给你两个单词 word1 和 word2,请你计算出将 word1 转换成 word2 所使用的最少操作数 +> 你可以对一个单词进行如下三种操作: 插入一个字符 删除一个字符 替换一个字符 思路:和上题很类似,相等则不需要操作,否则取删除、插入、替换最小操作次数的值+1 @@ -628,13 +607,13 @@ func min(a,b int)int{ 说明 -> 另外一种做法:MAXLEN(a,b)-LCS(a,b) +> 另外一种做法:MAXLEN\(a,b\)-LCS\(a,b\) ## 零钱和背包(10%) ### [coin-change](https://leetcode-cn.com/problems/coin-change/) -> 给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回  -1。 +> 给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。 思路:和其他 DP 不太一样,i 表示钱或者容量 @@ -672,11 +651,11 @@ func min(a,b int)int{ 注意 -> dp[i-a[j]] 决策 a[j]是否参与 +> dp\[i-a\[j\]\] 决策 a\[j\]是否参与 ### [backpack](https://www.lintcode.com/problem/backpack/description) -> 在 n 个物品中挑选若干物品装入背包,最多能装多满?假设背包的大小为 m,每个物品的大小为 A[i] +> 在 n 个物品中挑选若干物品装入背包,最多能装多满?假设背包的大小为 m,每个物品的大小为 A\[i\] ```go func backPack (m int, A []int) int { @@ -705,15 +684,13 @@ func backPack (m int, A []int) int { } return 0 } - ``` ### [backpack-ii](https://www.lintcode.com/problem/backpack-ii/description) -> 有 `n` 个物品和一个大小为 `m` 的背包. 给定数组 `A` 表示每个物品的大小和数组 `V` 表示每个物品的价值. -> 问最多能装入背包的总价值是多大? +> 有 `n` 个物品和一个大小为 `m` 的背包. 给定数组 `A` 表示每个物品的大小和数组 `V` 表示每个物品的价值. 问最多能装入背包的总价值是多大? -思路:f[i][j] 前 i 个物品,装入 j 背包 最大价值 +思路:f\[i\]\[j\] 前 i 个物品,装入 j 背包 最大价值 ```go func backPackII (m int, A []int, V []int) int { @@ -745,29 +722,30 @@ func max(a,b int)int{ ## 练习 -Matrix DP (10%) +Matrix DP \(10%\) + +* [ ] [triangle](https://leetcode-cn.com/problems/triangle/) +* [ ] [minimum-path-sum](https://leetcode-cn.com/problems/minimum-path-sum/) +* [ ] [unique-paths](https://leetcode-cn.com/problems/unique-paths/) +* [ ] [unique-paths-ii](https://leetcode-cn.com/problems/unique-paths-ii/) -- [ ] [triangle](https://leetcode-cn.com/problems/triangle/) -- [ ] [minimum-path-sum](https://leetcode-cn.com/problems/minimum-path-sum/) -- [ ] [unique-paths](https://leetcode-cn.com/problems/unique-paths/) -- [ ] [unique-paths-ii](https://leetcode-cn.com/problems/unique-paths-ii/) +Sequence \(40%\) -Sequence (40%) +* [ ] [climbing-stairs](https://leetcode-cn.com/problems/climbing-stairs/) +* [ ] [jump-game](https://leetcode-cn.com/problems/jump-game/) +* [ ] [jump-game-ii](https://leetcode-cn.com/problems/jump-game-ii/) +* [ ] [palindrome-partitioning-ii](https://leetcode-cn.com/problems/palindrome-partitioning-ii/) +* [ ] [longest-increasing-subsequence](https://leetcode-cn.com/problems/longest-increasing-subsequence/) +* [ ] [word-break](https://leetcode-cn.com/problems/word-break/) -- [ ] [climbing-stairs](https://leetcode-cn.com/problems/climbing-stairs/) -- [ ] [jump-game](https://leetcode-cn.com/problems/jump-game/) -- [ ] [jump-game-ii](https://leetcode-cn.com/problems/jump-game-ii/) -- [ ] [palindrome-partitioning-ii](https://leetcode-cn.com/problems/palindrome-partitioning-ii/) -- [ ] [longest-increasing-subsequence](https://leetcode-cn.com/problems/longest-increasing-subsequence/) -- [ ] [word-break](https://leetcode-cn.com/problems/word-break/) +Two Sequences DP \(40%\) -Two Sequences DP (40%) +* [ ] [longest-common-subsequence](https://leetcode-cn.com/problems/longest-common-subsequence/) +* [ ] [edit-distance](https://leetcode-cn.com/problems/edit-distance/) -- [ ] [longest-common-subsequence](https://leetcode-cn.com/problems/longest-common-subsequence/) -- [ ] [edit-distance](https://leetcode-cn.com/problems/edit-distance/) +Backpack & Coin Change \(10%\) -Backpack & Coin Change (10%) +* [ ] [coin-change](https://leetcode-cn.com/problems/coin-change/) +* [ ] [backpack](https://www.lintcode.com/problem/backpack/description) +* [ ] [backpack-ii](https://www.lintcode.com/problem/backpack-ii/description) -- [ ] [coin-change](https://leetcode-cn.com/problems/coin-change/) -- [ ] [backpack](https://www.lintcode.com/problem/backpack/description) -- [ ] [backpack-ii](https://www.lintcode.com/problem/backpack-ii/description) diff --git a/basic_algorithm/sort.md b/ji-chu-suan-fa-pian/sort.md similarity index 67% rename from basic_algorithm/sort.md rename to ji-chu-suan-fa-pian/sort.md index 9eaa2cb8..a91ed3c0 100644 --- a/basic_algorithm/sort.md +++ b/ji-chu-suan-fa-pian/sort.md @@ -1,4 +1,4 @@ -# 排序 +# 排序算法 ## 常考排序 @@ -103,50 +103,49 @@ package main func HeapSort(a []int) []int { // 1、无序数组a - // 2、将无序数组a构建为一个大根堆 - for i := len(a)/2 - 1; i >= 0; i-- { - sink(a, i, len(a)) - } - // 3、交换a[0]和a[len(a)-1] - // 4、然后把前面这段数组继续下沉保持堆结构,如此循环即可 - for i := len(a) - 1; i >= 1; i-- { - // 从后往前填充值 - swap(a, 0, i) - // 前面的长度也减一 - sink(a, 0, i) - } - return a + // 2、将无序数组a构建为一个大根堆 + for i := len(a)/2 - 1; i >= 0; i-- { + sink(a, i, len(a)) + } + // 3、交换a[0]和a[len(a)-1] + // 4、然后把前面这段数组继续下沉保持堆结构,如此循环即可 + for i := len(a) - 1; i >= 1; i-- { + // 从后往前填充值 + swap(a, 0, i) + // 前面的长度也减一 + sink(a, 0, i) + } + return a } func sink(a []int, i int, length int) { - for { - // 左节点索引(从0开始,所以左节点为i*2+1) - l := i*2 + 1 - // 有节点索引 - r := i*2 + 2 - // idx保存根、左、右三者之间较大值的索引 - idx := i - // 存在左节点,左节点值较大,则取左节点 - if l < length && a[l] > a[idx] { - idx = l - } - // 存在有节点,且值较大,取右节点 - if r < length && a[r] > a[idx] { - idx = r - } - // 如果根节点较大,则不用下沉 - if idx == i { - break - } - // 如果根节点较小,则交换值,并继续下沉 - swap(a, i, idx) - // 继续下沉idx节点 - i = idx - } + for { + // 左节点索引(从0开始,所以左节点为i*2+1) + l := i*2 + 1 + // 有节点索引 + r := i*2 + 2 + // idx保存根、左、右三者之间较大值的索引 + idx := i + // 存在左节点,左节点值较大,则取左节点 + if l < length && a[l] > a[idx] { + idx = l + } + // 存在有节点,且值较大,取右节点 + if r < length && a[r] > a[idx] { + idx = r + } + // 如果根节点较大,则不用下沉 + if idx == i { + break + } + // 如果根节点较小,则交换值,并继续下沉 + swap(a, i, idx) + // 继续下沉idx节点 + i = idx + } } func swap(a []int, i, j int) { - a[i], a[j] = a[j], a[i] + a[i], a[j] = a[j], a[i] } - ``` ## 参考 @@ -157,4 +156,5 @@ func swap(a []int, i, j int) { ## 练习 -- [ ] 手写快排、归并、堆排序 +* [ ] 手写快排、归并、堆排序 + diff --git a/introduction/golang.md b/ru-men-pian/golang.md similarity index 79% rename from introduction/golang.md rename to ru-men-pian/golang.md index 4c0317c6..26a9b4c3 100644 --- a/introduction/golang.md +++ b/ru-men-pian/golang.md @@ -1,4 +1,4 @@ -# GO 快速入门 +# go 语言入门 ## 基本语法 @@ -40,8 +40,8 @@ len(queue)==0 注意点 -- 参数传递,只能修改,不能新增或者删除原始数据 -- 默认 s=s[0:len(s)],取下限不取上限,数学表示为:[) +* 参数传递,只能修改,不能新增或者删除原始数据 +* 默认 s=s\[0:len\(s\)\],取下限不取上限,数学表示为:\[\) ### 字典 @@ -62,9 +62,9 @@ for k,v:=range m{ 注意点 -- map 键需要可比较,不能为 slice、map、function -- map 值都有默认值,可以直接操作默认值,如:m[age]++ 值由 0 变为 1 -- 比较两个 map 需要遍历,其中的 kv 是否相同,因为有默认值关系,所以需要检查 val 和 ok 两个值 +* map 键需要可比较,不能为 slice、map、function +* map 值都有默认值,可以直接操作默认值,如:m\[age\]++ 值由 0 变为 1 +* 比较两个 map 需要遍历,其中的 kv 是否相同,因为有默认值关系,所以需要检查 val 和 ok 两个值 ### 标准库 @@ -88,7 +88,6 @@ math.MinInt32 // 实际值:-1<<31 // int64 最大最小值(int默认是int64) math.MaxInt64 math.MinInt64 - ``` copy @@ -121,9 +120,9 @@ fmt.Printf("%d%s%c\n", num, str, b) // 111 // 字符串转数字 num,_:=strconv.Atoi() str:=strconv.Itoa() - ``` ## 刷题注意点 -- leetcode 中,全局变量不要当做返回值,否则刷题检查器会报错 +* leetcode 中,全局变量不要当做返回值,否则刷题检查器会报错 + diff --git a/introduction/quickstart.md b/ru-men-pian/quickstart.md similarity index 75% rename from introduction/quickstart.md rename to ru-men-pian/quickstart.md index d2957394..f601cd29 100644 --- a/introduction/quickstart.md +++ b/ru-men-pian/quickstart.md @@ -1,9 +1,8 @@ -# 快速开始 +# 算法快速入门 ## 数据结构与算法 -数据结构是一种数据的表现形式,如链表、二叉树、栈、队列等都是内存中一段数据表现的形式。 -算法是一种通用的解决问题的模板或者思路,大部分数据结构都有一套通用的算法模板,所以掌握这些通用的算法模板即可解决各种算法问题。 +数据结构是一种数据的表现形式,如链表、二叉树、栈、队列等都是内存中一段数据表现的形式。 算法是一种通用的解决问题的模板或者思路,大部分数据结构都有一套通用的算法模板,所以掌握这些通用的算法模板即可解决各种算法问题。 后面会分专题讲解各种数据结构、基本的算法模板、和一些高级算法模板,每一个专题都有一些经典练习题,完成所有练习的题后,你对数据结构和算法会有新的收获和体会。 @@ -13,7 +12,7 @@ [strStr](https://leetcode-cn.com/problems/implement-strstr/) -> 给定一个  haystack 字符串和一个 needle 字符串,在 haystack 字符串中找出 needle 字符串出现的第一个位置 (从 0 开始)。如果不存在,则返回  -1。 +> 给定一个 haystack 字符串和一个 needle 字符串,在 haystack 字符串中找出 needle 字符串出现的第一个位置 \(从 0 开始\)。如果不存在,则返回 -1。 思路:核心点遍历给定字符串字符,判断以当前字符开头字符串是否等于目标字符串 @@ -41,8 +40,8 @@ func strStr(haystack string, needle string) int { 需要注意点 -- 循环时,i 不需要到 len-1 -- 如果找到目标字符串,len(needle)==j +* 循环时,i 不需要到 len-1 +* 如果找到目标字符串,len\(needle\)==j 示例 2 @@ -102,15 +101,16 @@ func backtrack(nums []int, pos int, list []int, result *[][]int) { 我们大多数时候,刷算法题可能都是为了准备面试,所以面试的时候需要注意一些点 -- 快速定位到题目的知识点,找到知识点的**通用模板**,可能需要根据题目**特殊情况做特殊处理**。 -- 先去朝一个解决问题的方向!**先抛出可行解**,而不是最优解!先解决,再优化! -- 代码的风格要统一,熟悉各类语言的代码规范。 - - 命名尽量简洁明了,尽量不用数字命名如:i1、node1、a1、b2 -- 常见错误总结 - - 访问下标时,不能访问越界 - - 空值 nil 问题 run time error +* 快速定位到题目的知识点,找到知识点的**通用模板**,可能需要根据题目**特殊情况做特殊处理**。 +* 先去朝一个解决问题的方向!**先抛出可行解**,而不是最优解!先解决,再优化! +* 代码的风格要统一,熟悉各类语言的代码规范。 + * 命名尽量简洁明了,尽量不用数字命名如:i1、node1、a1、b2 +* 常见错误总结 + * 访问下标时,不能访问越界 + * 空值 nil 问题 run time error ## 练习 -- [ ] [strStr](https://leetcode-cn.com/problems/implement-strstr/) -- [ ] [subsets](https://leetcode-cn.com/problems/subsets/) +* [ ] [strStr](https://leetcode-cn.com/problems/implement-strstr/) +* [ ] [subsets](https://leetcode-cn.com/problems/subsets/) + diff --git a/data_structure/binary_op.md b/shu-ju-jie-gou-pian/binary_op.md similarity index 70% rename from data_structure/binary_op.md rename to shu-ju-jie-gou-pian/binary_op.md index e77b8adc..47946f37 100644 --- a/data_structure/binary_op.md +++ b/shu-ju-jie-gou-pian/binary_op.md @@ -20,11 +20,11 @@ a=a^b ### 移除最后一个 1 -a=n&(n-1) +a=n&\(n-1\) ### 获取最后一个 1 -diff=(n&(n-1))^n +diff=\(n&\(n-1\)\)^n ## 常见题目 @@ -50,24 +50,24 @@ func singleNumber(nums []int) int { ```go func singleNumber(nums []int) int { - // 统计每位1的个数 - var result int - for i := 0; i < 64; i++ { - sum := 0 - for j := 0; j < len(nums); j++ { - // 统计1的个数 - sum += (nums[j] >> i) & 1 - } - // 还原位00^10=10 或者用| 也可以 - result ^= (sum % 3) << i - } - return result + // 统计每位1的个数 + var result int + for i := 0; i < 64; i++ { + sum := 0 + for j := 0; j < len(nums); j++ { + // 统计1的个数 + sum += (nums[j] >> i) & 1 + } + // 还原位00^10=10 或者用| 也可以 + result ^= (sum % 3) << i + } + return result } ``` [single-number-iii](https://leetcode-cn.com/problems/single-number-iii/) -> 给定一个整数数组  `nums`,其中恰好有两个元素只出现一次,其余所有元素均出现两次。 找出只出现一次的那两个元素。 +> 给定一个整数数组 `nums`,其中恰好有两个元素只出现一次,其余所有元素均出现两次。 找出只出现一次的那两个元素。 ```go func singleNumber(nums []int) []int { @@ -96,7 +96,7 @@ func singleNumber(nums []int) []int { [number-of-1-bits](https://leetcode-cn.com/problems/number-of-1-bits/) -> 编写一个函数,输入是一个无符号整数,返回其二进制表达式中数字位数为 ‘1’  的个数(也被称为[汉明重量](https://baike.baidu.com/item/%E6%B1%89%E6%98%8E%E9%87%8D%E9%87%8F))。 +> 编写一个函数,输入是一个无符号整数,返回其二进制表达式中数字位数为 ‘1’ 的个数(也被称为[汉明重量](https://baike.baidu.com/item/%E6%B1%89%E6%98%8E%E9%87%8D%E9%87%8F))。 ```go func hammingWeight(num uint32) int { @@ -111,7 +111,7 @@ func hammingWeight(num uint32) int { [counting-bits](https://leetcode-cn.com/problems/counting-bits/) -> 给定一个非负整数  **num**。对于  0 ≤ i ≤ num  范围中的每个数字  i ,计算其二进制数中的 1 的数目并将它们作为数组返回。 +> 给定一个非负整数 **num**。对于 0 ≤ i ≤ num 范围中的每个数字 i ,计算其二进制数中的 1 的数目并将它们作为数组返回。 ```go func countBits(num int) []int { @@ -166,7 +166,7 @@ func reverseBits(num uint32) uint32 { [bitwise-and-of-numbers-range](https://leetcode-cn.com/problems/bitwise-and-of-numbers-range/) -> 给定范围 [m, n],其中 0 <= m <= n <= 2147483647,返回此范围内所有数字的按位与(包含 m, n 两端点)。 +> 给定范围 \[m, n\],其中 0 <= m <= n <= 2147483647,返回此范围内所有数字的按位与(包含 m, n 两端点)。 ```go func rangeBitwiseAnd(m int, n int) int { @@ -190,9 +190,10 @@ func rangeBitwiseAnd(m int, n int) int { ## 练习 -- [ ] [single-number](https://leetcode-cn.com/problems/single-number/) -- [ ] [single-number-ii](https://leetcode-cn.com/problems/single-number-ii/) -- [ ] [single-number-iii](https://leetcode-cn.com/problems/single-number-iii/) -- [ ] [number-of-1-bits](https://leetcode-cn.com/problems/number-of-1-bits/) -- [ ] [counting-bits](https://leetcode-cn.com/problems/counting-bits/) -- [ ] [reverse-bits](https://leetcode-cn.com/problems/reverse-bits/) +* [ ] [single-number](https://leetcode-cn.com/problems/single-number/) +* [ ] [single-number-ii](https://leetcode-cn.com/problems/single-number-ii/) +* [ ] [single-number-iii](https://leetcode-cn.com/problems/single-number-iii/) +* [ ] [number-of-1-bits](https://leetcode-cn.com/problems/number-of-1-bits/) +* [ ] [counting-bits](https://leetcode-cn.com/problems/counting-bits/) +* [ ] [reverse-bits](https://leetcode-cn.com/problems/reverse-bits/) + diff --git a/data_structure/binary_tree.md b/shu-ju-jie-gou-pian/binary_tree.md similarity index 69% rename from data_structure/binary_tree.md rename to shu-ju-jie-gou-pian/binary_tree.md index d01a8ca4..093fec2b 100644 --- a/data_structure/binary_tree.md +++ b/shu-ju-jie-gou-pian/binary_tree.md @@ -4,14 +4,12 @@ ### 二叉树遍历 -**前序遍历**:**先访问根节点**,再前序遍历左子树,再前序遍历右子树 -**中序遍历**:先中序遍历左子树,**再访问根节点**,再中序遍历右子树 -**后序遍历**:先后序遍历左子树,再后序遍历右子树,**再访问根节点** +**前序遍历**:**先访问根节点**,再前序遍历左子树,再前序遍历右子树 **中序遍历**:先中序遍历左子树,**再访问根节点**,再中序遍历右子树 **后序遍历**:先后序遍历左子树,再后序遍历右子树,**再访问根节点** 注意点 -- 以根访问顺序决定是什么遍历 -- 左子树都是优先右子树 +* 以根访问顺序决定是什么遍历 +* 左子树都是优先右子树 #### 前序递归 @@ -84,37 +82,37 @@ func inorderTraversal(root *TreeNode) []int { ```go func postorderTraversal(root *TreeNode) []int { - // 通过lastVisit标识右子节点是否已经弹出 - if root == nil { - return nil - } - result := make([]int, 0) - stack := make([]*TreeNode, 0) - var lastVisit *TreeNode - for root != nil || len(stack) != 0 { - for root != nil { - stack = append(stack, root) - root = root.Left - } - // 这里先看看,先不弹出 - node:= stack[len(stack)-1] - // 根节点必须在右节点弹出之后,再弹出 - if node.Right == nil || node.Right == lastVisit { - stack = stack[:len(stack)-1] // pop - result = append(result, node.Val) - // 标记当前这个节点已经弹出过 - lastVisit = node - } else { - root = node.Right - } - } - return result + // 通过lastVisit标识右子节点是否已经弹出 + if root == nil { + return nil + } + result := make([]int, 0) + stack := make([]*TreeNode, 0) + var lastVisit *TreeNode + for root != nil || len(stack) != 0 { + for root != nil { + stack = append(stack, root) + root = root.Left + } + // 这里先看看,先不弹出 + node:= stack[len(stack)-1] + // 根节点必须在右节点弹出之后,再弹出 + if node.Right == nil || node.Right == lastVisit { + stack = stack[:len(stack)-1] // pop + result = append(result, node.Val) + // 标记当前这个节点已经弹出过 + lastVisit = node + } else { + root = node.Right + } + } + return result } ``` 注意点 -- 核心就是:根节点必须在右节点弹出之后,再弹出 +* 核心就是:根节点必须在右节点弹出之后,再弹出 #### DFS 深度搜索-从上到下 @@ -211,15 +209,15 @@ func levelOrder(root *TreeNode) [][]int { 适用场景 -- 快速排序 -- 归并排序 -- 二叉树相关问题 +* 快速排序 +* 归并排序 +* 二叉树相关问题 分治法模板 -- 递归返回条件 -- 分段处理 -- 合并结果 +* 递归返回条件 +* 分段处理 +* 合并结果 ```go func traversal(root *TreeNode) ResultType { @@ -264,7 +262,7 @@ func divideAndConquer(root *TreeNode) []int { } ``` -#### 归并排序   +#### 归并排序 ```go func MergeSort(nums []int) []int { @@ -308,49 +306,48 @@ func merge(left, right []int) (result []int) { > 递归需要返回结果用于合并 -#### 快速排序   +#### 快速排序 ```go func QuickSort(nums []int) []int { - // 思路:把一个数组分为左右两段,左段小于右段,类似分治法没有合并过程 - quickSort(nums, 0, len(nums)-1) - return nums + // 思路:把一个数组分为左右两段,左段小于右段,类似分治法没有合并过程 + quickSort(nums, 0, len(nums)-1) + return nums } // 原地交换,所以传入交换索引 func quickSort(nums []int, start, end int) { - if start < end { + if start < end { // 分治法:divide - pivot := partition(nums, start, end) - quickSort(nums, 0, pivot-1) - quickSort(nums, pivot+1, end) - } + pivot := partition(nums, start, end) + quickSort(nums, 0, pivot-1) + quickSort(nums, pivot+1, end) + } } // 分区 func partition(nums []int, start, end int) int { - p := nums[end] - i := start - for j := start; j < end; j++ { - if nums[j] < p { - swap(nums, i, j) - i++ - } - } + p := nums[end] + i := start + for j := start; j < end; j++ { + if nums[j] < p { + swap(nums, i, j) + i++ + } + } // 把中间的值换为用于比较的基准值 - swap(nums, i, end) - return i + swap(nums, i, end) + return i } func swap(nums []int, i, j int) { - t := nums[i] - nums[i] = nums[j] - nums[j] = t + t := nums[i] + nums[i] = nums[j] + nums[j] = t } ``` 注意点: -> 快排由于是原地交换所以没有合并过程 -> 传入的索引是存在的索引(如:0、length-1 等),越界可能导致崩溃 +> 快排由于是原地交换所以没有合并过程 传入的索引是存在的索引(如:0、length-1 等),越界可能导致崩溃 常见题目示例 @@ -386,9 +383,7 @@ func maxDepth(root *TreeNode) int { > 给定一个二叉树,判断它是否是高度平衡的二叉树。 -思路:分治法,左边平衡 && 右边平衡 && 左右两边高度 <= 1, -因为需要返回是否平衡及高度,要么返回两个数据,要么合并两个数据, -所以用-1 表示不平衡,>0 表示树高度(二义性:一个变量有两种含义)。 +思路:分治法,左边平衡 && 右边平衡 && 左右两边高度 <= 1, 因为需要返回是否平衡及高度,要么返回两个数据,要么合并两个数据, 所以用-1 表示不平衡,>0 表示树高度(二义性:一个变量有两种含义)。 ```go func isBalanced(root *TreeNode) bool { @@ -514,38 +509,38 @@ func lowestCommonAncestor(root, p, q *TreeNode) *TreeNode { [binary-tree-level-order-traversal](https://leetcode-cn.com/problems/binary-tree-level-order-traversal/) -> 给你一个二叉树,请你返回其按  **层序遍历**  得到的节点值。 (即逐层地,从左到右访问所有节点) +> 给你一个二叉树,请你返回其按 **层序遍历** 得到的节点值。 (即逐层地,从左到右访问所有节点) -思路:用一个队列记录一层的元素,然后扫描这一层元素添加下一层元素到队列(一个数进去出来一次,所以复杂度 O(logN)) +思路:用一个队列记录一层的元素,然后扫描这一层元素添加下一层元素到队列(一个数进去出来一次,所以复杂度 O\(logN\)) ```go func levelOrder(root *TreeNode) [][]int { - result := make([][]int, 0) - if root == nil { - return result - } - queue := make([]*TreeNode, 0) - queue = append(queue, root) - for len(queue) > 0 { - list := make([]int, 0) + result := make([][]int, 0) + if root == nil { + return result + } + queue := make([]*TreeNode, 0) + queue = append(queue, root) + for len(queue) > 0 { + list := make([]int, 0) // 为什么要取length? // 记录当前层有多少元素(遍历当前层,再添加下一层) - l := len(queue) - for i := 0; i < l; i++ { + l := len(queue) + for i := 0; i < l; i++ { // 出队列 - level := queue[0] - queue = queue[1:] - list = append(list, level.Val) - if level.Left != nil { - queue = append(queue, level.Left) - } - if level.Right != nil { - queue = append(queue, level.Right) - } - } - result = append(result, list) - } - return result + level := queue[0] + queue = queue[1:] + list = append(list, level.Val) + if level.Left != nil { + queue = append(queue, level.Left) + } + if level.Right != nil { + queue = append(queue, level.Right) + } + } + result = append(result, list) + } + return result } ``` @@ -565,37 +560,37 @@ func levelOrderBottom(root *TreeNode) [][]int { return result } func reverse(nums [][]int) { - for i, j := 0, len(nums)-1; i < j; i, j = i+1, j-1 { - nums[i], nums[j] = nums[j], nums[i] - } + for i, j := 0, len(nums)-1; i < j; i, j = i+1, j-1 { + nums[i], nums[j] = nums[j], nums[i] + } } func levelOrder(root *TreeNode) [][]int { - result := make([][]int, 0) - if root == nil { - return result - } - queue := make([]*TreeNode, 0) - queue = append(queue, root) - for len(queue) > 0 { - list := make([]int, 0) + result := make([][]int, 0) + if root == nil { + return result + } + queue := make([]*TreeNode, 0) + queue = append(queue, root) + for len(queue) > 0 { + list := make([]int, 0) // 为什么要取length? // 记录当前层有多少元素(遍历当前层,再添加下一层) - l := len(queue) - for i := 0; i < l; i++ { + l := len(queue) + for i := 0; i < l; i++ { // 出队列 - level := queue[0] - queue = queue[1:] - list = append(list, level.Val) - if level.Left != nil { - queue = append(queue, level.Left) - } - if level.Right != nil { - queue = append(queue, level.Right) - } - } - result = append(result, list) - } - return result + level := queue[0] + queue = queue[1:] + list = append(list, level.Val) + if level.Left != nil { + queue = append(queue, level.Left) + } + if level.Right != nil { + queue = append(queue, level.Right) + } + } + result = append(result, list) + } + return result } ``` @@ -607,43 +602,43 @@ func levelOrder(root *TreeNode) [][]int { ```go func zigzagLevelOrder(root *TreeNode) [][]int { - result := make([][]int, 0) - if root == nil { - return result - } - queue := make([]*TreeNode, 0) - queue = append(queue, root) - toggle := false - for len(queue) > 0 { - list := make([]int, 0) - // 记录当前层有多少元素(遍历当前层,再添加下一层) - l := len(queue) - for i := 0; i < l; i++ { - // 出队列 - level := queue[0] - queue = queue[1:] - list = append(list, level.Val) - if level.Left != nil { - queue = append(queue, level.Left) - } - if level.Right != nil { - queue = append(queue, level.Right) - } - } - if toggle { - reverse(list) - } - result = append(result, list) - toggle = !toggle - } - return result + result := make([][]int, 0) + if root == nil { + return result + } + queue := make([]*TreeNode, 0) + queue = append(queue, root) + toggle := false + for len(queue) > 0 { + list := make([]int, 0) + // 记录当前层有多少元素(遍历当前层,再添加下一层) + l := len(queue) + for i := 0; i < l; i++ { + // 出队列 + level := queue[0] + queue = queue[1:] + list = append(list, level.Val) + if level.Left != nil { + queue = append(queue, level.Left) + } + if level.Right != nil { + queue = append(queue, level.Right) + } + } + if toggle { + reverse(list) + } + result = append(result, list) + toggle = !toggle + } + return result } func reverse(nums []int) { - for i := 0; i < len(nums)/2; i++ { - t := nums[i] - nums[i] = nums[len(nums)-1-i] - nums[len(nums)-1-i] = t - } + for i := 0; i < len(nums)/2; i++ { + t := nums[i] + nums[i] = nums[len(nums)-1-i] + nums[len(nums)-1-i] = t + } } ``` @@ -657,7 +652,7 @@ func reverse(nums []int) { 思路 1:中序遍历,检查结果列表是否已经有序 -思路 2:分治法,判断左 MAX < 根 < 右 MIN +思路 2:分治法,判断左 MAX < 根 < 右 MIN ```go // v1 @@ -681,63 +676,61 @@ func inOrder(root *TreeNode, result *[]int) { *result = append(*result, root.Val) inOrder(root.Right, result) } - - ``` ```go // v2分治法 type ResultType struct { - IsValid bool + IsValid bool // 记录左右两边最大最小值,和根节点进行比较 - Max *TreeNode - Min *TreeNode + Max *TreeNode + Min *TreeNode } func isValidBST2(root *TreeNode) bool { - result := helper(root) - return result.IsValid + result := helper(root) + return result.IsValid } func helper(root *TreeNode) ResultType { - result := ResultType{} - // check - if root == nil { - result.IsValid = true - return result - } - - left := helper(root.Left) - right := helper(root.Right) - - if !left.IsValid || !right.IsValid { - result.IsValid = false - return result - } - if left.Max != nil && left.Max.Val >= root.Val { - result.IsValid = false - return result - } - if right.Min != nil && right.Min.Val <= root.Val { - result.IsValid = false - return result - } - - result.IsValid = true + result := ResultType{} + // check + if root == nil { + result.IsValid = true + return result + } + + left := helper(root.Left) + right := helper(root.Right) + + if !left.IsValid || !right.IsValid { + result.IsValid = false + return result + } + if left.Max != nil && left.Max.Val >= root.Val { + result.IsValid = false + return result + } + if right.Min != nil && right.Min.Val <= root.Val { + result.IsValid = false + return result + } + + result.IsValid = true // 如果左边还有更小的3,就用更小的节点,不用4 // 5 // / \ // 1 4 - //   / \ - //   3 6 - result.Min = root - if left.Min != nil { - result.Min = left.Min - } - result.Max = root - if right.Max != nil { - result.Max = right.Max - } - return result + // / \ + // 3 6 + result.Min = root + if left.Min != nil { + result.Min = left.Min + } + result.Max = root + if right.Max != nil { + result.Max = right.Max + } + return result } ``` @@ -767,18 +760,19 @@ func insertIntoBST(root *TreeNode, val int) *TreeNode { ## 总结 -- 掌握二叉树递归与非递归遍历 -- 理解 DFS 前序遍历与分治法 -- 理解 BFS 层次遍历 +* 掌握二叉树递归与非递归遍历 +* 理解 DFS 前序遍历与分治法 +* 理解 BFS 层次遍历 ## 练习 -- [ ] [maximum-depth-of-binary-tree](https://leetcode-cn.com/problems/maximum-depth-of-binary-tree/) -- [ ] [balanced-binary-tree](https://leetcode-cn.com/problems/balanced-binary-tree/) -- [ ] [binary-tree-maximum-path-sum](https://leetcode-cn.com/problems/binary-tree-maximum-path-sum/) -- [ ] [lowest-common-ancestor-of-a-binary-tree](https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-tree/) -- [ ] [binary-tree-level-order-traversal](https://leetcode-cn.com/problems/binary-tree-level-order-traversal/) -- [ ] [binary-tree-level-order-traversal-ii](https://leetcode-cn.com/problems/binary-tree-level-order-traversal-ii/) -- [ ] [binary-tree-zigzag-level-order-traversal](https://leetcode-cn.com/problems/binary-tree-zigzag-level-order-traversal/) -- [ ] [validate-binary-search-tree](https://leetcode-cn.com/problems/validate-binary-search-tree/) -- [ ] [insert-into-a-binary-search-tree](https://leetcode-cn.com/problems/insert-into-a-binary-search-tree/) +* [ ] [maximum-depth-of-binary-tree](https://leetcode-cn.com/problems/maximum-depth-of-binary-tree/) +* [ ] [balanced-binary-tree](https://leetcode-cn.com/problems/balanced-binary-tree/) +* [ ] [binary-tree-maximum-path-sum](https://leetcode-cn.com/problems/binary-tree-maximum-path-sum/) +* [ ] [lowest-common-ancestor-of-a-binary-tree](https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-tree/) +* [ ] [binary-tree-level-order-traversal](https://leetcode-cn.com/problems/binary-tree-level-order-traversal/) +* [ ] [binary-tree-level-order-traversal-ii](https://leetcode-cn.com/problems/binary-tree-level-order-traversal-ii/) +* [ ] [binary-tree-zigzag-level-order-traversal](https://leetcode-cn.com/problems/binary-tree-zigzag-level-order-traversal/) +* [ ] [validate-binary-search-tree](https://leetcode-cn.com/problems/validate-binary-search-tree/) +* [ ] [insert-into-a-binary-search-tree](https://leetcode-cn.com/problems/insert-into-a-binary-search-tree/) + diff --git a/data_structure/linked_list.md b/shu-ju-jie-gou-pian/linked_list.md similarity index 79% rename from data_structure/linked_list.md rename to shu-ju-jie-gou-pian/linked_list.md index ebcc1625..f63c0546 100644 --- a/data_structure/linked_list.md +++ b/shu-ju-jie-gou-pian/linked_list.md @@ -4,14 +4,14 @@ 链表相关的核心点 -- null/nil 异常处理 -- dummy node 哑巴节点 -- 快慢指针 -- 插入一个节点到排序链表 -- 从一个链表中移除一个节点 -- 翻转链表 -- 合并两个链表 -- 找到链表的中间节点 +* null/nil 异常处理 +* dummy node 哑巴节点 +* 快慢指针 +* 插入一个节点到排序链表 +* 从一个链表中移除一个节点 +* 翻转链表 +* 合并两个链表 +* 找到链表的中间节点 ## 常见题型 @@ -35,7 +35,7 @@ func deleteDuplicates(head *ListNode) *ListNode { ### [remove-duplicates-from-sorted-list-ii](https://leetcode-cn.com/problems/remove-duplicates-from-sorted-list-ii/) -> 给定一个排序链表,删除所有含有重复数字的节点,只保留原始链表中   没有重复出现的数字。 +> 给定一个排序链表,删除所有含有重复数字的节点,只保留原始链表中 没有重复出现的数字。 思路:链表头结点可能被删除,所以用 dummy node 辅助删除 @@ -64,10 +64,7 @@ func deleteDuplicates(head *ListNode) *ListNode { } ``` -注意点 -• A->B->C 删除 B,A.next = C -• 删除用一个 Dummy Node 节点辅助(允许头节点可变) -• 访问 X.next 、X.value 一定要保证 X != nil +注意点 • A->B->C 删除 B,A.next = C • 删除用一个 Dummy Node 节点辅助(允许头节点可变) • 访问 X.next 、X.value 一定要保证 X != nil ### [reverse-linked-list](https://leetcode-cn.com/problems/reverse-linked-list/) @@ -95,7 +92,7 @@ func reverseList(head *ListNode) *ListNode { ### [reverse-linked-list-ii](https://leetcode-cn.com/problems/reverse-linked-list-ii/) -> 反转从位置  *m*  到  *n*  的链表。请使用一趟扫描完成反转。 +> 反转从位置 _m_ 到 _n_ 的链表。请使用一趟扫描完成反转。 思路:先遍历到 m 处,翻转,再拼接后续,注意指针处理 @@ -178,7 +175,7 @@ func mergeTwoLists(l1 *ListNode, l2 *ListNode) *ListNode { ### [partition-list](https://leetcode-cn.com/problems/partition-list/) -> 给定一个链表和一个特定值 x,对链表进行分隔,使得所有小于  *x*  的节点都在大于或等于  *x*  的节点之前。 +> 给定一个链表和一个特定值 x,对链表进行分隔,使得所有小于 _x_ 的节点都在大于或等于 _x_ 的节点之前。 思路:将大于 x 的节点,放到另外一个链表,最后连接这两个链表 @@ -219,7 +216,7 @@ func partition(head *ListNode, x int) *ListNode { ### [sort-list](https://leetcode-cn.com/problems/sort-list/) -> 在  *O*(*n* log *n*) 时间复杂度和常数级空间复杂度下,对链表进行排序。 +> 在 _O_\(_n_ log _n_\) 时间复杂度和常数级空间复杂度下,对链表进行排序。 思路:归并排序,找中点和合并操作 @@ -285,14 +282,13 @@ func mergeSort(head *ListNode) *ListNode { 注意点 -- 快慢指针 判断 fast 及 fast.Next 是否为 nil 值 -- 递归 mergeSort 需要断开中间节点 -- 递归返回条件为 head 为 nil 或者 head.Next 为 nil +* 快慢指针 判断 fast 及 fast.Next 是否为 nil 值 +* 递归 mergeSort 需要断开中间节点 +* 递归返回条件为 head 为 nil 或者 head.Next 为 nil ### [reorder-list](https://leetcode-cn.com/problems/reorder-list/) -> 给定一个单链表  *L*:*L*→*L*→…→*L\_\_n*→*L* -> 将其重新排列后变为: *L*→*L\_\_n*→*L*→*L\_\_n*→*L*→*L\_\_n*→… +> 给定一个单链表 _L_:_L_→_L_→…→_L\_\_n_→_L_ 将其重新排列后变为: _L_→_L\_\_n_→_L_→_L\_\_n_→_L_→_L\_\_n_→… 思路:找到中点断开,翻转后面部分,然后合并前后两个链表 @@ -367,8 +363,7 @@ func reverseList(head *ListNode) *ListNode { > 给定一个链表,判断链表中是否有环。 -思路:快慢指针,快慢指针相同则有环,证明:如果有环每走一步快慢指针距离会减 1 -![fast_slow_linked_list](https://img.fuiboom.com/img/fast_slow_linked_list.png) +思路:快慢指针,快慢指针相同则有环,证明:如果有环每走一步快慢指针距离会减 1 ![fast\_slow\_linked\_list](https://img.fuiboom.com/img/fast_slow_linked_list.png) ```go func hasCycle(head *ListNode) bool { @@ -391,10 +386,9 @@ func hasCycle(head *ListNode) bool { ### [linked-list-cycle-ii](https://leetcode-cn.com/problems/https://leetcode-cn.com/problems/linked-list-cycle-ii/) -> 给定一个链表,返回链表开始入环的第一个节点。  如果链表无环,则返回  `null`。 +> 给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 `null`。 -思路:快慢指针,快慢相遇之后,慢指针回到头,快慢指针步调一致一起移动,相遇点即为入环点 -![cycled_linked_list](https://img.fuiboom.com/img/cycled_linked_list.png) +思路:快慢指针,快慢相遇之后,慢指针回到头,快慢指针步调一致一起移动,相遇点即为入环点 ![cycled\_linked\_list](https://img.fuiboom.com/img/cycled_linked_list.png) ```go func detectCycle(head *ListNode) *ListNode { @@ -426,8 +420,8 @@ func detectCycle(head *ListNode) *ListNode { 坑点 -- 指针比较时直接比较对象,不要用值比较,链表中有可能存在重复值情况 -- 第一次相交后,快指针需要从下一个节点开始和头指针一起匀速移动 +* 指针比较时直接比较对象,不要用值比较,链表中有可能存在重复值情况 +* 第一次相交后,快指针需要从下一个节点开始和头指针一起匀速移动 另外一种方式是 fast=head,slow=head @@ -460,8 +454,8 @@ func detectCycle(head *ListNode) *ListNode { 这两种方式不同点在于,**一般用 fast=head.Next 较多**,因为这样可以知道中点的上一个节点,可以用来删除等操作。 -- fast 如果初始化为 head.Next 则中点在 slow.Next -- fast 初始化为 head,则中点在 slow +* fast 如果初始化为 head.Next 则中点在 slow.Next +* fast 初始化为 head,则中点在 slow ### [palindrome-linked-list](https://leetcode-cn.com/problems/palindrome-linked-list/) @@ -516,44 +510,43 @@ func reverse(head *ListNode)*ListNode{ ### [copy-list-with-random-pointer](https://leetcode-cn.com/problems/copy-list-with-random-pointer/) -> 给定一个链表,每个节点包含一个额外增加的随机指针,该指针可以指向链表中的任何节点或空节点。 -> 要求返回这个链表的 深拷贝。 +> 给定一个链表,每个节点包含一个额外增加的随机指针,该指针可以指向链表中的任何节点或空节点。 要求返回这个链表的 深拷贝。 思路:1、hash 表存储指针,2、复制节点跟在原节点后面 ```go func copyRandomList(head *Node) *Node { - if head == nil { - return head - } - // 复制节点,紧挨到到后面 - // 1->2->3 ==> 1->1'->2->2'->3->3' - cur := head - for cur != nil { - clone := &Node{Val: cur.Val, Next: cur.Next} - temp := cur.Next - cur.Next = clone - cur = temp - } - // 处理random指针 - cur = head - for cur != nil { - if cur.Random != nil { - cur.Next.Random = cur.Random.Next - } - cur = cur.Next.Next - } - // 分离两个链表 - cur = head - cloneHead := cur.Next - for cur != nil && cur.Next != nil { - temp := cur.Next - cur.Next = cur.Next.Next - cur = temp - } - // 原始链表头:head 1->2->3 - // 克隆的链表头:cloneHead 1'->2'->3' - return cloneHead + if head == nil { + return head + } + // 复制节点,紧挨到到后面 + // 1->2->3 ==> 1->1'->2->2'->3->3' + cur := head + for cur != nil { + clone := &Node{Val: cur.Val, Next: cur.Next} + temp := cur.Next + cur.Next = clone + cur = temp + } + // 处理random指针 + cur = head + for cur != nil { + if cur.Random != nil { + cur.Next.Random = cur.Random.Next + } + cur = cur.Next.Next + } + // 分离两个链表 + cur = head + cloneHead := cur.Next + for cur != nil && cur.Next != nil { + temp := cur.Next + cur.Next = cur.Next.Next + cur = temp + } + // 原始链表头:head 1->2->3 + // 克隆的链表头:cloneHead 1'->2'->3' + return cloneHead } ``` @@ -561,26 +554,27 @@ func copyRandomList(head *Node) *Node { 链表必须要掌握的一些点,通过下面练习题,基本大部分的链表类的题目都是手到擒来~ -- null/nil 异常处理 -- dummy node 哑巴节点 -- 快慢指针 -- 插入一个节点到排序链表 -- 从一个链表中移除一个节点 -- 翻转链表 -- 合并两个链表 -- 找到链表的中间节点 +* null/nil 异常处理 +* dummy node 哑巴节点 +* 快慢指针 +* 插入一个节点到排序链表 +* 从一个链表中移除一个节点 +* 翻转链表 +* 合并两个链表 +* 找到链表的中间节点 ## 练习 -- [ ] [remove-duplicates-from-sorted-list](https://leetcode-cn.com/problems/remove-duplicates-from-sorted-list/) -- [ ] [remove-duplicates-from-sorted-list-ii](https://leetcode-cn.com/problems/remove-duplicates-from-sorted-list-ii/) -- [ ] [reverse-linked-list](https://leetcode-cn.com/problems/reverse-linked-list/) -- [ ] [reverse-linked-list-ii](https://leetcode-cn.com/problems/reverse-linked-list-ii/) -- [ ] [merge-two-sorted-lists](https://leetcode-cn.com/problems/merge-two-sorted-lists/) -- [ ] [partition-list](https://leetcode-cn.com/problems/partition-list/) -- [ ] [sort-list](https://leetcode-cn.com/problems/sort-list/) -- [ ] [reorder-list](https://leetcode-cn.com/problems/reorder-list/) -- [ ] [linked-list-cycle](https://leetcode-cn.com/problems/linked-list-cycle/) -- [ ] [linked-list-cycle-ii](https://leetcode-cn.com/problems/https://leetcode-cn.com/problems/linked-list-cycle-ii/) -- [ ] [palindrome-linked-list](https://leetcode-cn.com/problems/palindrome-linked-list/) -- [ ] [copy-list-with-random-pointer](https://leetcode-cn.com/problems/copy-list-with-random-pointer/) +* [ ] [remove-duplicates-from-sorted-list](https://leetcode-cn.com/problems/remove-duplicates-from-sorted-list/) +* [ ] [remove-duplicates-from-sorted-list-ii](https://leetcode-cn.com/problems/remove-duplicates-from-sorted-list-ii/) +* [ ] [reverse-linked-list](https://leetcode-cn.com/problems/reverse-linked-list/) +* [ ] [reverse-linked-list-ii](https://leetcode-cn.com/problems/reverse-linked-list-ii/) +* [ ] [merge-two-sorted-lists](https://leetcode-cn.com/problems/merge-two-sorted-lists/) +* [ ] [partition-list](https://leetcode-cn.com/problems/partition-list/) +* [ ] [sort-list](https://leetcode-cn.com/problems/sort-list/) +* [ ] [reorder-list](https://leetcode-cn.com/problems/reorder-list/) +* [ ] [linked-list-cycle](https://leetcode-cn.com/problems/linked-list-cycle/) +* [ ] [linked-list-cycle-ii](https://leetcode-cn.com/problems/https://leetcode-cn.com/problems/linked-list-cycle-ii/) +* [ ] [palindrome-linked-list](https://leetcode-cn.com/problems/palindrome-linked-list/) +* [ ] [copy-list-with-random-pointer](https://leetcode-cn.com/problems/copy-list-with-random-pointer/) + diff --git a/data_structure/stack_queue.md b/shu-ju-jie-gou-pian/stack_queue.md similarity index 76% rename from data_structure/stack_queue.md rename to shu-ju-jie-gou-pian/stack_queue.md index e1ed723c..956b6e84 100644 --- a/data_structure/stack_queue.md +++ b/shu-ju-jie-gou-pian/stack_queue.md @@ -83,8 +83,7 @@ func (this *MinStack) GetMin() int { [evaluate-reverse-polish-notation](https://leetcode-cn.com/problems/evaluate-reverse-polish-notation/) -> **波兰表达式计算** > **输入:** ["2", "1", "+", "3", "*"] > **输出:** 9 -> **解释:** ((2 + 1) \* 3) = 9 +> **波兰表达式计算** > **输入:** \["2", "1", "+", "3", "_"\] > **输出:** 9 **解释:** \(\(2 + 1\) \_ 3\) = 9 思路:通过栈保存原来的元素,遇到表达式弹出运算,再推入结果,重复这个过程 @@ -128,51 +127,48 @@ func evalRPN(tokens []string) int { [decode-string](https://leetcode-cn.com/problems/decode-string/) -> 给定一个经过编码的字符串,返回它解码后的字符串。 -> s = "3[a]2[bc]", 返回 "aaabcbc". -> s = "3[a2[c]]", 返回 "accaccacc". -> s = "2[abc]3[cd]ef", 返回 "abcabccdcdcdef". +> 给定一个经过编码的字符串,返回它解码后的字符串。 s = "3\[a\]2\[bc\]", 返回 "aaabcbc". s = "3\[a2\[c\]\]", 返回 "accaccacc". s = "2\[abc\]3\[cd\]ef", 返回 "abcabccdcdcdef". 思路:通过栈辅助进行操作 ```go func decodeString(s string) string { - if len(s) == 0 { - return "" - } - stack := make([]byte, 0) - for i := 0; i < len(s); i++ { - switch s[i] { - case ']': - temp := make([]byte, 0) - for len(stack) != 0 && stack[len(stack)-1] != '[' { - v := stack[len(stack)-1] - stack = stack[:len(stack)-1] - temp = append(temp, v) - } - // pop '[' - stack = stack[:len(stack)-1] - // pop num - idx := 1 - for len(stack) >= idx && stack[len(stack)-idx] >= '0' && stack[len(stack)-idx] <= '9' { - idx++ - } + if len(s) == 0 { + return "" + } + stack := make([]byte, 0) + for i := 0; i < len(s); i++ { + switch s[i] { + case ']': + temp := make([]byte, 0) + for len(stack) != 0 && stack[len(stack)-1] != '[' { + v := stack[len(stack)-1] + stack = stack[:len(stack)-1] + temp = append(temp, v) + } + // pop '[' + stack = stack[:len(stack)-1] + // pop num + idx := 1 + for len(stack) >= idx && stack[len(stack)-idx] >= '0' && stack[len(stack)-idx] <= '9' { + idx++ + } // 注意索引边界 - num := stack[len(stack)-idx+1:] - stack = stack[:len(stack)-idx+1] - count, _ := strconv.Atoi(string(num)) - for j := 0; j < count; j++ { + num := stack[len(stack)-idx+1:] + stack = stack[:len(stack)-idx+1] + count, _ := strconv.Atoi(string(num)) + for j := 0; j < count; j++ { // 把字符正向放回到栈里面 - for j := len(temp) - 1; j >= 0; j-- { - stack = append(stack, temp[j]) - } - } - default: - stack = append(stack, s[i]) - - } - } - return string(stack) + for j := len(temp) - 1; j >= 0; j-- { + stack = append(stack, temp[j]) + } + } + default: + stack = append(stack, s[i]) + + } + } + return string(stack) } ``` @@ -200,7 +196,7 @@ boolean DFS(int root, int target) { [binary-tree-inorder-traversal](https://leetcode-cn.com/problems/binary-tree-inorder-traversal/) -> 给定一个二叉树,返回它的*中序*遍历。 +> 给定一个二叉树,返回它的_中序_遍历。 ```go // 思路:通过stack 保存已经访问的元素,用于原路返回 @@ -259,12 +255,11 @@ func clone(node *Node,visited map[*Node]*Node)*Node{ [number-of-islands](https://leetcode-cn.com/problems/number-of-islands/) -> 给定一个由  '1'(陆地)和 '0'(水)组成的的二维网格,计算岛屿的数量。一个岛被水包围,并且它是通过水平方向或垂直方向上相邻的陆地连接而成的。你可以假设网格的四个边均被水包围。 +> 给定一个由 '1'(陆地)和 '0'(水)组成的的二维网格,计算岛屿的数量。一个岛被水包围,并且它是通过水平方向或垂直方向上相邻的陆地连接而成的。你可以假设网格的四个边均被水包围。 思路:通过深度搜索遍历可能性(注意标记已访问元素) ```go - func numIslands(grid [][]byte) int { var count int for i:=0;i 给定 _n_ 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。 -> 求在该柱状图中,能够勾勒出来的矩形的最大面积。 +> 给定 _n_ 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。 求在该柱状图中,能够勾勒出来的矩形的最大面积。 思路:求以当前柱子为高度的面积,即转化为寻找小于当前值的左右两边值 @@ -304,41 +298,41 @@ func dfs(grid [][]byte,i,j int)int{ ```go func largestRectangleArea(heights []int) int { - if len(heights) == 0 { - return 0 - } - stack := make([]int, 0) - max := 0 - for i := 0; i <= len(heights); i++ { - var cur int - if i == len(heights) { - cur = 0 - } else { - cur = heights[i] - } + if len(heights) == 0 { + return 0 + } + stack := make([]int, 0) + max := 0 + for i := 0; i <= len(heights); i++ { + var cur int + if i == len(heights) { + cur = 0 + } else { + cur = heights[i] + } // 当前高度小于栈,则将栈内元素都弹出计算面积 - for len(stack) != 0 && cur <= heights[stack[len(stack)-1]] { - pop := stack[len(stack)-1] - stack = stack[:len(stack)-1] - h := heights[pop] + for len(stack) != 0 && cur <= heights[stack[len(stack)-1]] { + pop := stack[len(stack)-1] + stack = stack[:len(stack)-1] + h := heights[pop] // 计算宽度 - w := i - if len(stack) != 0 { - peek := stack[len(stack)-1] - w = i - peek - 1 - } - max = Max(max, h*w) - } + w := i + if len(stack) != 0 { + peek := stack[len(stack)-1] + w = i - peek - 1 + } + max = Max(max, h*w) + } // 记录索引即可获取对应元素 - stack = append(stack, i) - } - return max + stack = append(stack, i) + } + return max } func Max(a, b int) int { - if a > b { - return a - } - return b + if a > b { + return a + } + return b } ``` @@ -458,8 +452,7 @@ func levelOrder(root *TreeNode) [][]int { [01-matrix](https://leetcode-cn.com/problems/01-matrix/) -> 给定一个由 0 和 1 组成的矩阵,找出每个元素到最近的 0 的距离。 -> 两个相邻元素间的距离为 1 +> 给定一个由 0 和 1 组成的矩阵,找出每个元素到最近的 0 的距离。 两个相邻元素间的距离为 1 ```go // BFS 从0进队列,弹出之后计算上下左右的结果,将上下左右重新进队列进行二层操作 @@ -512,20 +505,21 @@ func updateMatrix(matrix [][]int) [][]int { ## 总结 -- 熟悉栈的使用场景 - - 后出先出,保存临时值 - - 利用栈 DFS 深度搜索 -- 熟悉队列的使用场景 - - 利用队列 BFS 广度搜索 +* 熟悉栈的使用场景 + * 后出先出,保存临时值 + * 利用栈 DFS 深度搜索 +* 熟悉队列的使用场景 + * 利用队列 BFS 广度搜索 ## 练习 -- [ ] [min-stack](https://leetcode-cn.com/problems/min-stack/) -- [ ] [evaluate-reverse-polish-notation](https://leetcode-cn.com/problems/evaluate-reverse-polish-notation/) -- [ ] [decode-string](https://leetcode-cn.com/problems/decode-string/) -- [ ] [binary-tree-inorder-traversal](https://leetcode-cn.com/problems/binary-tree-inorder-traversal/) -- [ ] [clone-graph](https://leetcode-cn.com/problems/clone-graph/) -- [ ] [number-of-islands](https://leetcode-cn.com/problems/number-of-islands/) -- [ ] [largest-rectangle-in-histogram](https://leetcode-cn.com/problems/largest-rectangle-in-histogram/) -- [ ] [implement-queue-using-stacks](https://leetcode-cn.com/problems/implement-queue-using-stacks/) -- [ ] [01-matrix](https://leetcode-cn.com/problems/01-matrix/) +* [ ] [min-stack](https://leetcode-cn.com/problems/min-stack/) +* [ ] [evaluate-reverse-polish-notation](https://leetcode-cn.com/problems/evaluate-reverse-polish-notation/) +* [ ] [decode-string](https://leetcode-cn.com/problems/decode-string/) +* [ ] [binary-tree-inorder-traversal](https://leetcode-cn.com/problems/binary-tree-inorder-traversal/) +* [ ] [clone-graph](https://leetcode-cn.com/problems/clone-graph/) +* [ ] [number-of-islands](https://leetcode-cn.com/problems/number-of-islands/) +* [ ] [largest-rectangle-in-histogram](https://leetcode-cn.com/problems/largest-rectangle-in-histogram/) +* [ ] [implement-queue-using-stacks](https://leetcode-cn.com/problems/implement-queue-using-stacks/) +* [ ] [01-matrix](https://leetcode-cn.com/problems/01-matrix/) + diff --git a/advanced_algorithm/backtrack.md b/suan-fa-si-wei/backtrack.md similarity index 55% rename from advanced_algorithm/backtrack.md rename to suan-fa-si-wei/backtrack.md index bd923e61..41b19269 100644 --- a/advanced_algorithm/backtrack.md +++ b/suan-fa-si-wei/backtrack.md @@ -2,7 +2,7 @@ ## 背景 -回溯法(backtrack)常用于遍历列表所有子集,是 DFS 深度搜索一种,一般用于全排列,穷尽所有可能,遍历的过程实际上是一个决策树的遍历过程。时间复杂度一般 O(N!),它不像动态规划存在重叠子问题可以优化,回溯算法就是纯暴力穷举,复杂度一般都很高。 +回溯法(backtrack)常用于遍历列表所有子集,是 DFS 深度搜索一种,一般用于全排列,穷尽所有可能,遍历的过程实际上是一个决策树的遍历过程。时间复杂度一般 O\(N!\),它不像动态规划存在重叠子问题可以优化,回溯算法就是纯暴力穷举,复杂度一般都很高。 ## 模板 @@ -32,12 +32,12 @@ func backtrack(选择列表,路径): ```go func subsets(nums []int) [][]int { - // 保存最终结果 - result := make([][]int, 0) - // 保存中间结果 - list := make([]int, 0) - backtrack(nums, 0, list, &result) - return result + // 保存最终结果 + result := make([][]int, 0) + // 保存中间结果 + list := make([]int, 0) + backtrack(nums, 0, list, &result) + return result } // nums 给定的集合 @@ -45,16 +45,16 @@ func subsets(nums []int) [][]int { // list 临时结果集合(每次需要复制保存) // result 最终结果 func backtrack(nums []int, pos int, list []int, result *[][]int) { - // 把临时结果复制出来保存到最终结果 - ans := make([]int, len(list)) - copy(ans, list) - *result = append(*result, ans) - // 选择、处理结果、再撤销选择 - for i := pos; i < len(nums); i++ { - list = append(list, nums[i]) - backtrack(nums, i+1, list, result) - list = list[0 : len(list)-1] - } + // 把临时结果复制出来保存到最终结果 + ans := make([]int, len(list)) + copy(ans, list) + *result = append(*result, ans) + // 选择、处理结果、再撤销选择 + for i := pos; i < len(nums); i++ { + list = append(list, nums[i]) + backtrack(nums, i+1, list, result) + list = list[0 : len(list)-1] + } } ``` @@ -64,18 +64,18 @@ func backtrack(nums []int, pos int, list []int, result *[][]int) { ```go import ( - "sort" + "sort" ) func subsetsWithDup(nums []int) [][]int { - // 保存最终结果 - result := make([][]int, 0) - // 保存中间结果 - list := make([]int, 0) - // 先排序 - sort.Ints(nums) - backtrack(nums, 0, list, &result) - return result + // 保存最终结果 + result := make([][]int, 0) + // 保存中间结果 + list := make([]int, 0) + // 先排序 + sort.Ints(nums) + backtrack(nums, 0, list, &result) + return result } // nums 给定的集合 @@ -83,26 +83,26 @@ func subsetsWithDup(nums []int) [][]int { // list 临时结果集合(每次需要复制保存) // result 最终结果 func backtrack(nums []int, pos int, list []int, result *[][]int) { - // 把临时结果复制出来保存到最终结果 - ans := make([]int, len(list)) - copy(ans, list) - *result = append(*result, ans) - // 选择时需要剪枝、处理、撤销选择 - for i := pos; i < len(nums); i++ { + // 把临时结果复制出来保存到最终结果 + ans := make([]int, len(list)) + copy(ans, list) + *result = append(*result, ans) + // 选择时需要剪枝、处理、撤销选择 + for i := pos; i < len(nums); i++ { // 排序之后,如果再遇到重复元素,则不选择此元素 - if i != pos && nums[i] == nums[i-1] { - continue - } - list = append(list, nums[i]) - backtrack(nums, i+1, list, result) - list = list[0 : len(list)-1] - } + if i != pos && nums[i] == nums[i-1] { + continue + } + list = append(list, nums[i]) + backtrack(nums, i+1, list, result) + list = list[0 : len(list)-1] + } } ``` ### [permutations](https://leetcode-cn.com/problems/permutations/) -> 给定一个   没有重复   数字的序列,返回其所有可能的全排列。 +> 给定一个 没有重复 数字的序列,返回其所有可能的全排列。 思路:需要记录已经选择过的元素,满足条件的结果才进行返回 @@ -150,17 +150,17 @@ func backtrack(nums []int, visited []bool, list []int, result *[][]int) { ```go import ( - "sort" + "sort" ) func permuteUnique(nums []int) [][]int { - result := make([][]int, 0) - list := make([]int, 0) - // 标记这个元素是否已经添加到结果集 - visited := make([]bool, len(nums)) - sort.Ints(nums) - backtrack(nums, visited, list, &result) - return result + result := make([][]int, 0) + list := make([]int, 0) + // 标记这个元素是否已经添加到结果集 + visited := make([]bool, len(nums)) + sort.Ints(nums) + backtrack(nums, visited, list, &result) + return result } // nums 输入集合 @@ -168,41 +168,42 @@ func permuteUnique(nums []int) [][]int { // list 临时结果集 // result 最终结果 func backtrack(nums []int, visited []bool, list []int, result *[][]int) { - // 临时结果和输入集合长度一致 才是全排列 - if len(list) == len(nums) { - subResult := make([]int, len(list)) - copy(subResult, list) - *result = append(*result, subResult) - } - for i := 0; i < len(nums); i++ { - // 已经添加过的元素,直接跳过 - if visited[i] { - continue - } + // 临时结果和输入集合长度一致 才是全排列 + if len(list) == len(nums) { + subResult := make([]int, len(list)) + copy(subResult, list) + *result = append(*result, subResult) + } + for i := 0; i < len(nums); i++ { + // 已经添加过的元素,直接跳过 + if visited[i] { + continue + } // 上一个元素和当前相同,并且没有访问过就跳过 - if i != 0 && nums[i] == nums[i-1] && !visited[i-1] { - continue - } - list = append(list, nums[i]) - visited[i] = true - backtrack(nums, visited, list, result) - visited[i] = false - list = list[0 : len(list)-1] - } + if i != 0 && nums[i] == nums[i-1] && !visited[i-1] { + continue + } + list = append(list, nums[i]) + visited[i] = true + backtrack(nums, visited, list, result) + visited[i] = false + list = list[0 : len(list)-1] + } } ``` ## 练习 -- [ ] [subsets](https://leetcode-cn.com/problems/subsets/) -- [ ] [subsets-ii](https://leetcode-cn.com/problems/subsets-ii/) -- [ ] [permutations](https://leetcode-cn.com/problems/permutations/) -- [ ] [permutations-ii](https://leetcode-cn.com/problems/permutations-ii/) +* [ ] [subsets](https://leetcode-cn.com/problems/subsets/) +* [ ] [subsets-ii](https://leetcode-cn.com/problems/subsets-ii/) +* [ ] [permutations](https://leetcode-cn.com/problems/permutations/) +* [ ] [permutations-ii](https://leetcode-cn.com/problems/permutations-ii/) 挑战题目 -- [ ] [combination-sum](https://leetcode-cn.com/problems/combination-sum/) -- [ ] [letter-combinations-of-a-phone-number](https://leetcode-cn.com/problems/letter-combinations-of-a-phone-number/) -- [ ] [palindrome-partitioning](https://leetcode-cn.com/problems/palindrome-partitioning/) -- [ ] [restore-ip-addresses](https://leetcode-cn.com/problems/restore-ip-addresses/) -- [ ] [permutations](https://leetcode-cn.com/problems/permutations/) +* [ ] [combination-sum](https://leetcode-cn.com/problems/combination-sum/) +* [ ] [letter-combinations-of-a-phone-number](https://leetcode-cn.com/problems/letter-combinations-of-a-phone-number/) +* [ ] [palindrome-partitioning](https://leetcode-cn.com/problems/palindrome-partitioning/) +* [ ] [restore-ip-addresses](https://leetcode-cn.com/problems/restore-ip-addresses/) +* [ ] [permutations](https://leetcode-cn.com/problems/permutations/) + diff --git a/advanced_algorithm/binary_search_tree.md b/suan-fa-si-wei/binary_search_tree.md similarity index 87% rename from advanced_algorithm/binary_search_tree.md rename to suan-fa-si-wei/binary_search_tree.md index f1d8aa93..54abd4b3 100644 --- a/advanced_algorithm/binary_search_tree.md +++ b/suan-fa-si-wei/binary_search_tree.md @@ -2,8 +2,8 @@ ## 定义 -- 每个节点中的值必须大于(或等于)存储在其左侧子树中的任何值。 -- 每个节点中的值必须小于(或等于)存储在其右子树中的任何值。 +* 每个节点中的值必须大于(或等于)存储在其左侧子树中的任何值。 +* 每个节点中的值必须小于(或等于)存储在其右子树中的任何值。 ## 应用 @@ -60,7 +60,6 @@ func Min(a,b int)int{ } return a } - ``` [insert-into-a-binary-search-tree](https://leetcode-cn.com/problems/insert-into-a-binary-search-tree/) @@ -83,7 +82,7 @@ func insertIntoBST(root *TreeNode, val int) *TreeNode { [delete-node-in-a-bst](https://leetcode-cn.com/problems/delete-node-in-a-bst/) -> 给定一个二叉搜索树的根节点 root 和一个值 key,删除二叉搜索树中的  key  对应的节点,并保证二叉搜索树的性质不变。返回二叉搜索树(有可能被更新)的根节点的引用。 +> 给定一个二叉搜索树的根节点 root 和一个值 key,删除二叉搜索树中的 key 对应的节点,并保证二叉搜索树的性质不变。返回二叉搜索树(有可能被更新)的根节点的引用。 ```go /** @@ -164,12 +163,12 @@ func Max(a,b int)int{ } return b } - ``` ## 练习 -- [ ] [validate-binary-search-tree](https://leetcode-cn.com/problems/validate-binary-search-tree/) -- [ ] [insert-into-a-binary-search-tree](https://leetcode-cn.com/problems/insert-into-a-binary-search-tree/) -- [ ] [delete-node-in-a-bst](https://leetcode-cn.com/problems/delete-node-in-a-bst/) -- [ ] [balanced-binary-tree](https://leetcode-cn.com/problems/balanced-binary-tree/) +* [ ] [validate-binary-search-tree](https://leetcode-cn.com/problems/validate-binary-search-tree/) +* [ ] [insert-into-a-binary-search-tree](https://leetcode-cn.com/problems/insert-into-a-binary-search-tree/) +* [ ] [delete-node-in-a-bst](https://leetcode-cn.com/problems/delete-node-in-a-bst/) +* [ ] [balanced-binary-tree](https://leetcode-cn.com/problems/balanced-binary-tree/) + diff --git a/advanced_algorithm/recursion.md b/suan-fa-si-wei/recursion.md similarity index 68% rename from advanced_algorithm/recursion.md rename to suan-fa-si-wei/recursion.md index ccfa3757..c318c5ca 100644 --- a/advanced_algorithm/recursion.md +++ b/suan-fa-si-wei/recursion.md @@ -1,4 +1,4 @@ -# 递归 +# 递归思维 ## 介绍 @@ -8,29 +8,28 @@ [reverse-string](https://leetcode-cn.com/problems/reverse-string/) -> 编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组  `char[]`  的形式给出。 +> 编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 `char[]` 的形式给出。 ```go func reverseString(s []byte) { - res := make([]byte, 0) - reverse(s, 0, &res) - for i := 0; i < len(s); i++ { - s[i] = res[i] - } + res := make([]byte, 0) + reverse(s, 0, &res) + for i := 0; i < len(s); i++ { + s[i] = res[i] + } } func reverse(s []byte, i int, res *[]byte) { - if i == len(s) { - return - } - reverse(s, i+1, res) - *res = append(*res, s[i]) + if i == len(s) { + return + } + reverse(s, i+1, res) + *res = append(*res, s[i]) } ``` [swap-nodes-in-pairs](https://leetcode-cn.com/problems/swap-nodes-in-pairs/) -> 给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。 -> **你不能只是单纯的改变节点内部的值**,而是需要实际的进行节点交换。 +> 给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。 **你不能只是单纯的改变节点内部的值**,而是需要实际的进行节点交换。 ```go func swapPairs(head *ListNode) *ListNode { @@ -54,7 +53,7 @@ func helper(head *ListNode)*ListNode{ [unique-binary-search-trees-ii](https://leetcode-cn.com/problems/unique-binary-search-trees-ii/) -> 给定一个整数 n,生成所有由 1 ... n 为节点所组成的二叉搜索树。 +> 给定一个整数 n,生成所有由 1 ... n 为节点所组成的二叉搜索树。 ```go func generateTrees(n int) []*TreeNode { @@ -91,10 +90,7 @@ func generate(start,end int)[]*TreeNode{ [fibonacci-number](https://leetcode-cn.com/problems/fibonacci-number/) -> 斐波那契数,通常用  F(n) 表示,形成的序列称为斐波那契数列。该数列由  0 和 1 开始,后面的每一项数字都是前面两项数字的和。也就是: -> F(0) = 0,   F(1) = 1 -> F(N) = F(N - 1) + F(N - 2), 其中 N > 1. -> 给定  N,计算  F(N)。 +> 斐波那契数,通常用 F\(n\) 表示,形成的序列称为斐波那契数列。该数列由 0 和 1 开始,后面的每一项数字都是前面两项数字的和。也就是: F\(0\) = 0, F\(1\) = 1 F\(N\) = F\(N - 1\) + F\(N - 2\), 其中 N > 1. 给定 N,计算 F\(N\)。 ```go func fib(N int) int { @@ -118,7 +114,8 @@ func dfs(n int)int{ ## 练习 -- [ ] [reverse-string](https://leetcode-cn.com/problems/reverse-string/) -- [ ] [swap-nodes-in-pairs](https://leetcode-cn.com/problems/swap-nodes-in-pairs/) -- [ ] [unique-binary-search-trees-ii](https://leetcode-cn.com/problems/unique-binary-search-trees-ii/) -- [ ] [fibonacci-number](https://leetcode-cn.com/problems/fibonacci-number/) +* [ ] [reverse-string](https://leetcode-cn.com/problems/reverse-string/) +* [ ] [swap-nodes-in-pairs](https://leetcode-cn.com/problems/swap-nodes-in-pairs/) +* [ ] [unique-binary-search-trees-ii](https://leetcode-cn.com/problems/unique-binary-search-trees-ii/) +* [ ] [fibonacci-number](https://leetcode-cn.com/problems/fibonacci-number/) + diff --git a/suan-fa-si-wei/slide_window.md b/suan-fa-si-wei/slide_window.md new file mode 100644 index 00000000..79fa5cd4 --- /dev/null +++ b/suan-fa-si-wei/slide_window.md @@ -0,0 +1,251 @@ +# 滑动窗口思想 + +## 模板 + +```cpp +/* 滑动窗口算法框架 */ +void slidingWindow(string s, string t) { + unordered_map need, window; + for (char c : t) need[c]++; + + int left = 0, right = 0; + int valid = 0; + while (right < s.size()) { + // c 是将移入窗口的字符 + char c = s[right]; + // 右移窗口 + right++; + // 进行窗口内数据的一系列更新 + ... + + /*** debug 输出的位置 ***/ + printf("window: [%d, %d)\n", left, right); + /********************/ + + // 判断左侧窗口是否要收缩 + while (window needs shrink) { + // d 是将移出窗口的字符 + char d = s[left]; + // 左移窗口 + left++; + // 进行窗口内数据的一系列更新 + ... + } + } +} +``` + +需要变化的地方 + +* 1、右指针右移之后窗口数据更新 +* 2、判断窗口是否要收缩 +* 3、左指针右移之后窗口数据更新 +* 4、根据题意计算结果 + +## 示例 + +[minimum-window-substring](https://leetcode-cn.com/problems/minimum-window-substring/) + +> 给你一个字符串 S、一个字符串 T,请在字符串 S 里面找出:包含 T 所有字母的最小子串 + +```go +func minWindow(s string, t string) string { + // 保存滑动窗口字符集 + win := make(map[byte]int) + // 保存需要的字符集 + need := make(map[byte]int) + for i := 0; i < len(t); i++ { + need[t[i]]++ + } + // 窗口 + left := 0 + right := 0 + // match匹配次数 + match := 0 + start := 0 + end := 0 + min := math.MaxInt64 + var c byte + for right < len(s) { + c = s[right] + right++ + // 在需要的字符集里面,添加到窗口字符集里面 + if need[c] != 0 { + win[c]++ + // 如果当前字符的数量匹配需要的字符的数量,则match值+1 + if win[c] == need[c] { + match++ + } + } + + // 当所有字符数量都匹配之后,开始缩紧窗口 + for match == len(need) { + // 获取结果 + if right-left < min { + min = right - left + start = left + end = right + } + c = s[left] + left++ + // 左指针指向不在需要字符集则直接跳过 + if need[c] != 0 { + // 左指针指向字符数量和需要的字符相等时,右移之后match值就不匹配则减一 + // 因为win里面的字符数可能比较多,如有10个A,但需要的字符数量可能为3 + // 所以在压死骆驼的最后一根稻草时,match才减一,这时候才跳出循环 + if win[c] == need[c] { + match-- + } + win[c]-- + } + } + } + if min == math.MaxInt64 { + return "" + } + return s[start:end] +} +``` + +[permutation-in-string](https://leetcode-cn.com/problems/permutation-in-string/) + +> 给定两个字符串 **s1** 和 **s2**,写一个函数来判断 **s2** 是否包含 **s1** 的排列。 + +```go +func checkInclusion(s1 string, s2 string) bool { + win := make(map[byte]int) + need := make(map[byte]int) + for i := 0; i < len(s1); i++ { + need[s1[i]]++ + } + left := 0 + right := 0 + match := 0 + for right < len(s2) { + c := s2[right] + right++ + if need[c] != 0 { + win[c]++ + if win[c] == need[c] { + match++ + } + } + // 当窗口长度大于字符串长度,缩紧窗口 + for right-left >= len(s1) { + // 当窗口长度和字符串匹配,并且里面每个字符数量也匹配时,满足条件 + if match == len(need) { + return true + } + d := s2[left] + left++ + if need[d] != 0 { + if win[d] == need[d] { + match-- + } + win[d]-- + } + } + } + return false +} +``` + +[find-all-anagrams-in-a-string](https://leetcode-cn.com/problems/find-all-anagrams-in-a-string/) + +> 给定一个字符串 **s** 和一个非空字符串 **p**,找到 **s** 中所有是 **p** 的字母异位词的子串,返回这些子串的起始索引。 + +```go +func findAnagrams(s string, p string) []int { + win := make(map[byte]int) + need := make(map[byte]int) + for i := 0; i < len(p); i++ { + need[p[i]]++ + } + left := 0 + right := 0 + match := 0 + ans:=make([]int,0) + for right < len(s) { + c := s[right] + right++ + if need[c] != 0 { + win[c]++ + if win[c] == need[c] { + match++ + } + } + // 当窗口长度大于字符串长度,缩紧窗口 + for right-left >= len(p) { + // 当窗口长度和字符串匹配,并且里面每个字符数量也匹配时,满足条件 + if right-left == len(p)&& match == len(need) { + ans=append(ans,left) + } + d := s[left] + left++ + if need[d] != 0 { + if win[d] == need[d] { + match-- + } + win[d]-- + } + } + } + return ans +} +``` + +[longest-substring-without-repeating-characters](https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/) + +> 给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。 示例 1: +> +> 输入: "abcabcbb" 输出: 3 解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。 + +```go +func lengthOfLongestSubstring(s string) int { + // 滑动窗口核心点:1、右指针右移 2、根据题意收缩窗口 3、左指针右移更新窗口 4、根据题意计算结果 + if len(s)==0{ + return 0 + } + win:=make(map[byte]int) + left:=0 + right:=0 + ans:=1 + for right1{ + d:=s[left] + left++ + win[d]-- + } + // 计算结果 + ans=max(right-left,ans) + } + return ans +} +func max(a,b int)int{ + if a>b{ + return a + } + return b +} +``` + +## 总结 + +* 和双指针题目类似,更像双指针的升级版,滑动窗口核心点是维护一个窗口集,根据窗口集来进行处理 +* 核心步骤 + * right 右移 + * 收缩 + * left 右移 + * 求结果 + +## 练习 + +* [ ] [minimum-window-substring](https://leetcode-cn.com/problems/minimum-window-substring/) +* [ ] [permutation-in-string](https://leetcode-cn.com/problems/permutation-in-string/) +* [ ] [find-all-anagrams-in-a-string](https://leetcode-cn.com/problems/find-all-anagrams-in-a-string/) +* [ ] [longest-substring-without-repeating-characters](https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/) +