diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..9ecfba64 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.md linguist-language=cpp \ No newline at end of file diff --git a/README.md b/README.md index 3d158d31..cb6bb6e0 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,7 @@ ![剑指offer](https://img.fuiboom.com/img/leetcode_jzoffer.png) -刷题时间可以合理分配,如果打算准备面试了,建议前面两部分 一个半月 (6 周)时间刷完,最后剑指 offer 半个月刷完,边刷可以边投简历进行面试,遇到不会的不用着急,往模板上套就对了,如果面试管给你提示,那就好好做,不要错过这大好机会~ +刷题时间可以合理分配,如果打算准备面试了,建议前面两部分 一个半月 (6 周)时间刷完,最后剑指 offer 半个月刷完,边刷可以边投简历进行面试,遇到不会的不用着急,往模板上套就对了,如果面试官给你提示,那就好好做,不要错过这大好机会~ > 注意点:如果为了找工作刷题,遇到 hard 的题如果有思路就做,没思路先跳过,先把基础打好,再来刷 hard 可能效果会更好~ @@ -99,4 +99,6 @@ | ---- | ------------------------------------------------- | ------------------------------------------------------------------- | | ✅ | [wardseptember](https://github.com/wardseptember) | [notes(Java 实现)](https://github.com/wardseptember/notes) | | ✅ | [dashidhy](https://github.com/dashidhy) | [algorithm-pattern-python(Python 实现)](https://github.com/dashidhy/algorithm-pattern-python) | -| ✅ | [binzi56](https://github.com/binzi56) | [algorithm-pattern-c(c++ 实现)](https://github.com/binzi56/algorithm-pattern-c) | +| ✅ | [da183](https://github.com/da183) | [algorithm-pattern-cpp(C++ 实现)](https://github.com/da183/algorithm-pattern-cpp) | + +[自己的一点总结](punch_in/learned_along_the_journey.md) diff --git a/advanced_algorithm/backtrack.md b/advanced_algorithm/backtrack.md index bd923e61..b8ebd14a 100644 --- a/advanced_algorithm/backtrack.md +++ b/advanced_algorithm/backtrack.md @@ -6,11 +6,11 @@ ## 模板 -```go -result = [] -func backtrack(选择列表,路径): +```c++ +vector result; +void backtrack(选择列表,路径): if 满足结束条件: - result.add(路径) + result.push_back(路径); return for 选择 in 选择列表: 做选择 @@ -30,31 +30,26 @@ func backtrack(选择列表,路径): ![image.png](https://img.fuiboom.com/img/backtrack.png) -```go -func subsets(nums []int) [][]int { - // 保存最终结果 - result := make([][]int, 0) - // 保存中间结果 - list := make([]int, 0) - backtrack(nums, 0, list, &result) - return result +```c++ +vector> subsets(vector& nums) { + vector> result; + vector track; + backtrack(nums, 0, track, result); + return result; } -// nums 给定的集合 -// pos 下次添加到集合中的元素位置索引 -// 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] - } +void backtrack(const vector &nums, int pos, vector &track, vector> &result) { + // 插入当前组合 + result.push_back(track); + for (int i = pos; i < nums.size(); ++i) { + // 每个值,都有两种可能,包含和不包含 + // 先压入 + track.push_back(nums[i]); + // 递归处理包含当前值的情况 + backtrack(nums, i + 1, track, result); + // 弹出,向后遍历,处理不包含的情况 + track.pop_back(); + } } ``` @@ -62,41 +57,26 @@ func backtrack(nums []int, pos int, list []int, result *[][]int) { > 给定一个可能包含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。说明:解集不能包含重复的子集。 -```go -import ( - "sort" -) - -func subsetsWithDup(nums []int) [][]int { - // 保存最终结果 - result := make([][]int, 0) - // 保存中间结果 - list := make([]int, 0) - // 先排序 - sort.Ints(nums) - backtrack(nums, 0, list, &result) - return result +```c++ +// 对数据进行排序来判断重复元素 +vector> subsetsWithDup(vector &nums) { + vector> result; + vector track; + sort(nums.begin(), nums.end()); + backtrack(nums, 0, track, result); + return result; } -// nums 给定的集合 -// pos 下次添加到集合中的元素位置索引 -// 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++ { - // 排序之后,如果再遇到重复元素,则不选择此元素 - 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] - } +void backtrack(const vector &nums, int pos, vector &track, vector> &result) { + result.push_back(track); + for (int i = pos; i < nums.size(); ++i) { + if (i != pos && nums[i] == nums[i - 1]) { + continue; + } + track.push_back(nums[i]); + backtrack(nums, i + 1, track, result); + track.pop_back(); + } } ``` @@ -106,40 +86,51 @@ func backtrack(nums []int, pos int, list []int, result *[][]int) { 思路:需要记录已经选择过的元素,满足条件的结果才进行返回 -```go -func permute(nums []int) [][]int { - result := make([][]int, 0) - list := make([]int, 0) - // 标记这个元素是否已经添加到结果集 - visited := make([]bool, len(nums)) - backtrack(nums, visited, list, &result) - return result +```c++ +/* + * 这个问题可以看作有 n 个排列成一行的空格,从左往右依此填入题目给定的 n 个数,每个数只能使用一次 + * + * 回溯法 + * 不同于上面的子集问题,撤销选择时弹出了事,这里撤销后还要留待后续组合使用 + * 并且需要检查数字是否已经填入过 + * + * 思路: + * backtrack(fillingPos, result),fillingPos为当前填入的位置 + * 若fillingPos == n,则已经完成排列 + * 否则,填入"未填入过"的数字,递归处理下一个位置,最后撤销当前选择,尝试以其他"未填入过"的数字来填入fillingPos + * + * 优化: + * 不使用额外的数组来维护已经填入过的数字,降低空间复杂度 + * 对于nums、当前位置fillingPos + * [0, fillingPos - 1]——已填入过的数字 + * [fillingPos, n - 1]——未填入过的数字 + * 每次选择时把nums[i]与nums[fillingPos]进行交换 + * 撤销选择时把nums[i]与nums[fillingPos]再交换一次即可 + * + * 既然 [fillingPos, n - 1] 为未填入的数字 + * 偷个鸡,撤销选择时不交换,直接 ++i 可否? + * 并不能 + * 因为原本的 nums[i] 填入 fillingPos 这一可能性已经处理过了 + * 如果不交换回来,for循环遍历的时候会再用到同一个数字! + */ +vector> permute(vector &nums) { + vector> result; + backtrack(result, nums, 0, nums.size()); + return result; } -// nums 输入集合 -// visited 当前递归标记过的元素 -// list 临时结果集(路径) -// result 最终结果 -func backtrack(nums []int, visited []bool, list []int, result *[][]int) { - // 返回条件:临时结果和输入集合长度一致 才是全排列 - if len(list) == len(nums) { - ans := make([]int, len(list)) - copy(ans, list) - *result = append(*result, ans) - return +void backtrack(vector> &result, vector &nums, int fillingPos, int len) { + if (fillingPos == len) { + // n个数字都已排列完,保存并退出 + result.emplace_back(nums); + return; } - for i := 0; i < len(nums); i++ { - // 已经添加过的元素,直接跳过 - if visited[i] { - continue - } - // 添加元素 - list = append(list, nums[i]) - visited[i] = true - backtrack(nums, visited, list, result) - // 移除元素 - visited[i] = false - list = list[0 : len(list)-1] + for (int i = fillingPos; i < len; ++i) { + // 把nums[i]填入到当前位置 + swap(nums[i], nums[fillingPos]); + backtrack(result, nums, fillingPos + 1, len); + // 把顺序恢复回去 + swap(nums[i], nums[fillingPos]); } } ``` @@ -148,47 +139,41 @@ func backtrack(nums []int, visited []bool, list []int, result *[][]int) { > 给定一个可包含重复数字的序列,返回所有不重复的全排列。 -```go -import ( - "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 +```c++ +vector> permuteUnique(vector &nums) { + vector> result; + vector current; + vector visited(nums.size()); + sort(nums.begin(), nums.end()); + backtrack(nums, nums.size(), visited, current, result); + return result; } -// nums 输入集合 -// visited 当前递归标记过的元素 -// 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 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] - } +void backtrack(vector &nums, int len, vector &visited, + vector ¤t, vector> &result) { + if (current.size() == nums.size()) { + result.emplace_back(current); + return; + } + for (int i = 0; i < nums.size(); ++i) { + // 已经在组合中 + if (visited[i]) { + continue; + } + // 与上一个元素相同,并且没有访问过就跳过 + // 没访问过? + // 重复元素,只有第一个元素考虑排列组合,后面的不再考虑以免重复 + // 一旦第一个重复的元素被pop并设置visited为false,则其所有排列组合都已考虑完毕 + // 跳过后续的重复元素 + if (i > 0 && nums[i] == nums[i - 1] && !visited[i - 1]) { + continue; + } + current.emplace_back(nums[i]); + visited[i] = true; + backtrack(nums, len, visited, current, result); + visited[i] = false; + current.pop_back(); + } } ``` diff --git a/advanced_algorithm/binary_search_tree.md b/advanced_algorithm/binary_search_tree.md index f1d8aa93..ab3960d8 100644 --- a/advanced_algorithm/binary_search_tree.md +++ b/advanced_algorithm/binary_search_tree.md @@ -11,73 +11,62 @@ > 验证二叉搜索树 -```go -/** - * Definition for a binary tree node. - * type TreeNode struct { - * Val int - * Left *TreeNode - * Right *TreeNode - * } - */ -func isValidBST(root *TreeNode) bool { - return dfs(root).valid -} -type ResultType struct{ - max int - min int - valid bool -} -func dfs(root *TreeNode)(result ResultType){ - if root==nil{ - result.max=-1<<63 - result.min=1<<63-1 - result.valid=true - return - } +```c++ +struct Result { + TreeNode *maxNode; + TreeNode *minNode; + bool isValidate; - left:=dfs(root.Left) - right:=dfs(root.Right) + Result(bool validate = true, TreeNode *max = nullptr, TreeNode *min = nullptr) + : isValidate(validate), maxNode(max), minNode(min) { - // 1、满足左边最大值left.max && root.Valb{ - return a +}; +bool isValidBST(TreeNode *root) { + if (!root) { + return true; } - return b + return helper(root).isValidate; } -func Min(a,b int)int{ - if a>b{ - return b + +Result helper(TreeNode *root) { + if (!root) { + return {}; + } + auto left = helper(root->left); + auto right = helper(root->right); + if (!(left.isValidate && right.isValidate)) { + return {false}; + } + if (left.maxNode && left.maxNode->val >= root->val) { + return {false}; } - return a + if (right.minNode && right.minNode->val <= root->val) { + return {false}; + } + return { + true, + right.maxNode ? right.maxNode : root, + left.minNode ? left.minNode : root, + }; } - ``` [insert-into-a-binary-search-tree](https://leetcode-cn.com/problems/insert-into-a-binary-search-tree/) > 给定二叉搜索树(BST)的根节点和要插入树中的值,将值插入二叉搜索树。 返回插入后二叉搜索树的根节点。 保证原始二叉搜索树中不存在新值。 -```go -func insertIntoBST(root *TreeNode, val int) *TreeNode { - if root==nil{ - return &TreeNode{Val:val} +```c++ +TreeNode *insertIntoBST(TreeNode *root, int val) { + if (root == nullptr) { + return new TreeNode(val); } - if root.Valval > val) { + root->left = insertIntoBST(root->left, val); + } else { + root->right = insertIntoBST(root->right, val); } - return root + return root; } ``` @@ -85,43 +74,36 @@ func insertIntoBST(root *TreeNode, val int) *TreeNode { > 给定一个二叉搜索树的根节点 root 和一个值 key,删除二叉搜索树中的  key  对应的节点,并保证二叉搜索树的性质不变。返回二叉搜索树(有可能被更新)的根节点的引用。 -```go -/** - * Definition for a binary tree node. - * type TreeNode struct { - * Val int - * Left *TreeNode - * Right *TreeNode - * } - */ -func deleteNode(root *TreeNode, key int) *TreeNode { - // 删除节点分为三种情况: - // 1、只有左节点 替换为右 - // 2、只有右节点 替换为左 - // 3、有左右子节点 左子节点连接到右边最左节点即可 - if root ==nil{ - return root - } - if root.Valkey{ - root.Left=deleteNode(root.Left,key) - }else if root.Val==key{ - if root.Left==nil{ - return root.Right - }else if root.Right==nil{ - return root.Left - }else{ - cur:=root.Right - // 一直向左找到最后一个左节点即可 - for cur.Left!=nil{ - cur=cur.Left - } - cur.Left=root.Left - return root.Right - } - } - return root +```c++ +// 注意二叉搜索树的概念! +// 如果当前节点是其父节点的左子节点,则当前节点底下任何一个节点都要比该父节点小 +// 反之亦然 +TreeNode* deleteNode(TreeNode* root, int key) { + if (root == nullptr) { + return root; + } + if (root->val < key) { + root->right = deleteNode(root->right, key); + return root; + } + if (root->val > key) { + root->left = deleteNode(root->left, key); + return root; + } + // 每个节点中的值必须大于(或等于)存储在其左侧子树中的任何值。 + // 所以压根不需要考虑啥当前节点的父节点,必定当前节点的左右子树! + if (root->left == nullptr) { + return root->right; + } + if (root->right == nullptr) { + return root->left; + } + auto iter = root->right; + while (iter->left != nullptr) { + iter = iter->left; + } + iter->left = root->left; + return root->right; } ``` @@ -129,7 +111,16 @@ func deleteNode(root *TreeNode, key int) *TreeNode { > 给定一个二叉树,判断它是否是高度平衡的二叉树。 -```go +```c++ +struct ResultType { + int height; + bool valid; +}; + +bool isBalanced(TreeNode *root) { + return dfs(root).valid; +} + type ResultType struct{ height int valid bool diff --git a/advanced_algorithm/recursion.md b/advanced_algorithm/recursion.md index ccfa3757..e41ce61c 100644 --- a/advanced_algorithm/recursion.md +++ b/advanced_algorithm/recursion.md @@ -10,20 +10,17 @@ > 编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组  `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] - } +```c++ +void reverseString(vector &s) { + reverse(s, 0, s.size() - 1); } -func reverse(s []byte, i int, res *[]byte) { - if i == len(s) { - return - } - reverse(s, i+1, res) - *res = append(*res, s[i]) + +void reverse(vector &s, int left, int right) { + if (left >= right) { + return; + } + swap(s[left++], s[right--]); + reverse(s, left, right); } ``` @@ -32,23 +29,20 @@ func reverse(s []byte, i int, res *[]byte) { > 给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。 > **你不能只是单纯的改变节点内部的值**,而是需要实际的进行节点交换。 -```go -func swapPairs(head *ListNode) *ListNode { - // 思路:将链表翻转转化为一个子问题,然后通过递归方式依次解决 - // 先翻转两个,然后将后面的节点继续这样翻转,然后将这些翻转后的节点连接起来 - return helper(head) +```c++ +ListNode* swapPairs(ListNode* head) { + return swapper(head); } -func helper(head *ListNode)*ListNode{ - if head==nil||head.Next==nil{ - return head + +ListNode* swapper(ListNode *head) { + if (head == nullptr || head->next == nullptr) { + return head; } - // 保存下一阶段的头指针 - nextHead:=head.Next.Next - // 翻转当前阶段指针 - next:=head.Next - next.Next=head - head.Next=helper(nextHead) - return next + auto nextHead = head->next->next; + auto newHead = head->next; + newHead->next = head; + head->next = swapper(nextHead); + return newHead; } ``` @@ -56,34 +50,35 @@ func helper(head *ListNode)*ListNode{ > 给定一个整数 n,生成所有由 1 ... n 为节点所组成的二叉搜索树。 -```go -func generateTrees(n int) []*TreeNode { - if n==0{ - return nil +```c++ +vector generateTrees(int n) { + if (n == 0) { + return {}; } - return generate(1,n) - + return generate(1, n); } -func generate(start,end int)[]*TreeNode{ - if start>end{ - return []*TreeNode{nil} + +vector generate(int start, int end) { + if (start > end) { + // 记得返回nullptr,不可返回空vector + return {nullptr}; } - ans:=make([]*TreeNode,0) - for i:=start;i<=end;i++{ - // 递归生成所有左右子树 - lefts:=generate(start,i-1) - rights:=generate(i+1,end) - // 拼接左右子树后返回 - for j:=0;j ans; + for (int i = start; i <= end; ++i) { + // 取i作为根节点,生成所有左右子树 + auto lefts = generate(start, i - 1); + auto rights = generate(i + 1, end); + // 拼接 + for (auto &left : lefts) { + for (auto &right : rights) { + auto root = new TreeNode(i); + root->left = left; + root->right = right; + ans.push_back(root); } } } - return ans + return ans; } ``` @@ -96,23 +91,23 @@ func generate(start,end int)[]*TreeNode{ > F(N) = F(N - 1) + F(N - 2), 其中 N > 1. > 给定  N,计算  F(N)。 -```go -func fib(N int) int { - return dfs(N) +```c++ +unordered_map memo; + +int fib(int N) { + return fibWithMemo(N); } -var m map[int]int=make(map[int]int) -func dfs(n int)int{ - if n < 2{ - return n + +int fibWithMemo(int N) { + if (N < 2) { + return N; } - // 读取缓存 - if m[n]!=0{ - return m[n] + if (memo[N] != 0) { + return memo[N]; } - ans:=dfs(n-2)+dfs(n-1) - // 缓存已经计算过的值 - m[n]=ans - return ans + auto ret = fibWithMemo(N - 1) + fib(N - 2); + memo[N] = ret; + return ret; } ``` diff --git a/advanced_algorithm/slide_window.md b/advanced_algorithm/slide_window.md index 4e043302..0bd9b468 100644 --- a/advanced_algorithm/slide_window.md +++ b/advanced_algorithm/slide_window.md @@ -48,62 +48,56 @@ void slidingWindow(string s, string t) { > 给你一个字符串 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] +```c++ +// 根据 need 中每个字符所需的出现的次数,检查 win 是否满足条件 +bool checkWindow(unordered_map &win, unordered_map &need) { + for (const auto &item : need) { + if (win[item.first] < item.second) { + return false; + } + } + return true; +} + +// 给你一个字符串 S、一个字符串 T,请在字符串 S 里面找出:包含 T 所有字符的最小子串。 +string minWindow(string s, string t) { + unordered_map win, need; + // 初始判断条件 + // 注意:这里 T 中可能出现重复的字符,所以我们要记录字符的个数。 + + for (const auto &c : t) { + ++need[c]; + } + + auto left = 0; + auto right = -1; + auto minLen = numeric_limits::max(); + auto minBegin = -1; + // 注意:一定要进行类型转换,否则该判断会有问题 + // 隐式类型转换,int转成size_type,后者为unsigned! + // -1一转,直接溢出 + while (right < (int)s.size()) { + // right右移 + // 如果当前字符在字符串 T 中,增加计数 + if (need.find(s[++right]) != need.end()) { + ++win[s[right]]; + } + + // 如果当前win满足条件,left尽可能左移 + while (checkWindow(win, need)) { + // 更新最短长度及其起始位置 + if (right - left + 1 < minLen) { + minLen = right - left + 1; + minBegin = left; + } + // 如果当前字符在 T 中,更新win计数 + if (need.find(s[left]) != need.end()) { + --win[s[left]]; + } + ++left; + } + } + return minBegin == -1 ? "" : s.substr(minBegin, minLen); } ``` @@ -111,87 +105,80 @@ func minWindow(s string, t string) 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 +```c++ +bool checkInclusion(string s1, string s2) { + unordered_map win, need; + for (const auto &c : s1) { + ++need[c]; + } + auto left = 0; + auto right = 0; + auto matchNum = 0; + while (right < s2.size()) { + auto charToPush = s2[right++]; + if (need.find(charToPush) != need.end()) { + ++win[charToPush]; + if (win[charToPush] == need[charToPush]) { + ++matchNum; + } + } + while (right - left >= s1.size()) { + if (matchNum == need.size()) { + return true; + } + auto charToPop = s2[left++]; + if (need.find(charToPop) != need.end()) { + if (win[charToPop] == need[charToPop]) { + --matchNum; + } + --win[charToPop]; + } + } + } + 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 +```c++ +vector findAnagrams(string s, string p) { + unordered_map win, need; + for (const auto &c : p) { + ++need[c]; + } + + vector ret; + auto left = 0; + auto right = 0; + auto matchNum = 0; + while (right < s.size()) { + auto charToPush = s[right++]; + if (need.find(charToPush) != need.end()) { + ++win[charToPush]; + if (need[charToPush] == win[charToPush]) { + ++matchNum; + } + } + + while (right - left >= p.size()) { + if (matchNum == need.size() && right - left == p.size()) { + ret.push_back(left); + } + auto charToPop = s[left++]; + if (need.find(charToPop) == need.end()) { + continue; + } + + if (win[charToPop] == need[charToPop]) { + --matchNum; + } + --win[charToPop]; + } + } + return ret; } ``` @@ -204,36 +191,30 @@ func findAnagrams(s string, p string) []int { > 输出: 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]-- +```c++ +int lengthOfLongestSubstring(string s) { + unordered_set win; + auto left = 0; + auto right = 0; + auto maxLen = 0; + while (right < s.size()) { + auto charToPush = s[right++]; + if (win.find(charToPush) == win.end()) { + win.insert(charToPush); + maxLen = max(maxLen, right - left); + continue; + } + while (right >= left) { + auto charToPop = s[left++]; + // 左指针右移 + // 剔除重复字符之前的字符 + if (charToPush == charToPop) { + break; + } + win.erase(charToPop); } - // 计算结果 - ans=max(right-left,ans) - } - return ans -} -func max(a,b int)int{ - if a>b{ - return a } - return b + return maxLen; } ``` diff --git a/basic_algorithm/binary_search.md b/basic_algorithm/binary_search.md index 15f336a3..bd2b5d6a 100644 --- a/basic_algorithm/binary_search.md +++ b/basic_algorithm/binary_search.md @@ -19,32 +19,32 @@ > 给定一个  n  个元素有序的(升序)整型数组  nums 和一个目标值  target  ,写一个函数搜索  nums  中的 target,如果目标值存在返回下标,否则返回 -1。 -```go +```c++ // 二分搜索最常用模板 -func search(nums []int, target int) int { +int search(vector &nums, int target) { // 1、初始化start、end - start := 0 - end := len(nums) - 1 + auto start = 0; + auto end = nums.size() - 1; // 2、处理for循环 - for start+1 < end { - mid := start + (end-start)/2 + while (start + 1 < end) { + auto mid = start + (end - start) / 2; // 3、比较a[mid]和target值 - if nums[mid] == target { - end = mid - } else if nums[mid] < target { - start = mid - } else if nums[mid] > target { - end = mid + if (nums[mid] == target) { + return mid; + } else if (nums[mid] < target) { + start = mid; + } else { + end = mid; } } // 4、最后剩下两个元素,手动判断 - if nums[start] == target { - return start + if (nums[start] == target) { + return start; } - if nums[end] == target { - return end + if (nums[end] == target) { + return end; } - return -1 + return -1; } ``` @@ -58,24 +58,24 @@ func search(nums []int, target int) int { 如果是最简单的二分搜索,不需要找第一个、最后一个位置、或者是没有重复元素,可以使用模板#1,代码更简洁 -```go +```c++ // 无重复元素搜索时,更方便 -func search(nums []int, target int) int { - start := 0 - end := len(nums) - 1 - for start <= end { - mid := start + (end-start)/2 - if nums[mid] == target { - return mid - } else if nums[mid] < target { - start = mid+1 - } else if nums[mid] > target { - end = mid-1 +int search(vector &nums, int target) { + auto start = 0; + auto end = nums.size() - 1; + while (start <= end) { + auto mid = start + (end - start) / 2; + if (nums[mid] == target) { + return mid; + } else if (nums[mid] < target) { + start = mid + 1; + } else { + end = mid - 1; } } // 如果找不到,start 是第一个大于target的索引 // 如果在B+树结构里面二分搜索,可以return start - // 这样可以继续向子节点搜索,如:node:=node.Children[start] + // 这样可以继续向子节点搜索,如:node = node.Children[start] return -1 } ``` @@ -89,59 +89,48 @@ func search(nums []int, target int) int { 思路:核心点就是找第一个 target 的索引,和最后一个 target 的索引,所以用两次二分搜索分别找第一次和最后一次的位置 -```go -func searchRange (A []int, target int) []int { - if len(A) == 0 { - return []int{-1, -1} +```c++ +// 两次搜索,一次相等时往左一次往右以找出左右边界 +vector searchRange(vector &A, int target) { + vector ret = {-1, -1}; + if (A.empty()) { + return ret; } - result := make([]int, 2) - start := 0 - end := len(A) - 1 - for start+1 < end { - mid := start + (end-start)/2 - if A[mid] > target { - end = mid - } else if A[mid] < target { - start = mid + int begin = 0; + int end = A.size(); + while (begin + 1 < end) { + auto mid = begin + (end - begin) / 2; + if (A[mid] < target) { + begin = mid; } else { - // 如果相等,应该继续向左找,就能找到第一个目标值的位置 - end = mid + end = mid; } } - // 搜索左边的索引 - if A[start] == target { - result[0] = start - } else if A[end] == target { - result[0] = end + if (A[begin] == target) { + ret[0] = begin; + } else if (A[end] == target) { + ret[0] = end; } else { - result[0] = -1 - result[1] = -1 - return result + return ret; } - start = 0 - end = len(A) - 1 - for start+1 < end { - mid := start + (end-start)/2 - if A[mid] > target { - end = mid - } else if A[mid] < target { - start = mid + begin = 0; + end = A.size(); + while (begin + 1 < end) { + auto mid = begin + (end - begin) / 2; + if (A[mid] <= target) { + begin = mid; } else { - // 如果相等,应该继续向右找,就能找到最后一个目标值的位置 - start = mid + end = mid; } } - // 搜索右边的索引 - if A[end] == target { - result[1] = end - } else if A[start] == target { - result[1] = start + if (A[begin] == target) { + ret[1] = begin; + } else if (A[end] == target) { + ret[1] = end; } else { - result[0] = -1 - result[1] = -1 - return result + return ret; } - return result + return ret; } ``` @@ -149,30 +138,31 @@ func searchRange (A []int, target int) []int { > 给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。 -```go -func searchInsert(nums []int, target int) int { - // 思路:找到第一个 >= target 的元素位置 - start := 0 - end := len(nums) - 1 - for start+1 < end { - mid := start + (end-start)/2 - if nums[mid] == target { - // 标记开始位置 - start = mid - } else if nums[mid] > target { - end = mid +```c++ +int searchInsert(vector& nums, int target) { + if (nums.empty()) { + return 0; + } + + int begin = 0; + int end = nums.size() -1; + while (begin + 1 < end) { + auto mid = begin + (end - begin) / 2; + if (nums[mid] == target) { + return mid; + } else if (nums[mid] < target) { + begin = mid; } else { - start = mid + end = mid; } } - if nums[start] >= target { - return start - } else if nums[end] >= target { - return end - } else if nums[end] < target { // 目标值比所有值都大 - return end + 1 - } - return 0 + if (nums[begin] >= target) { + return begin; + } else if (nums[end] >= target) { + return end; + } else { + return end + 1; + } } ``` @@ -183,32 +173,46 @@ func searchInsert(nums []int, target int) int { > - 每行中的整数从左到右按升序排列。 > - 每行的第一个整数大于前一行的最后一个整数。 -```go -func searchMatrix(matrix [][]int, target int) bool { - // 思路:将2纬数组转为1维数组 进行二分搜索 - if len(matrix) == 0 || len(matrix[0]) == 0 { - return false +```c++ +bool searchMatrix(vector>& matrix, int target) { + if (matrix.empty() || matrix[0].empty()) { + return false; } - row := len(matrix) - col := len(matrix[0]) - start := 0 - end := row*col - 1 - for start+1 < end { - mid := start + (end-start)/2 - // 获取2纬数组对应值 - val := matrix[mid/col][mid%col] - if val > target { - end = mid - } else if val < target { - start = mid + + int begin = 0; + int end = matrix.size() - 1; + while (begin + 1 < end) { + auto mid = begin + (end - begin) / 2; + const auto &val = matrix[mid][0]; + if (val == target) { + return true; + } else if (val < target) { + begin = mid; } else { - return true + end = mid; } } - if matrix[start/col][start%col] == target || matrix[end/col][end%col] == target{ - return true + + if (matrix[begin][0] == target || matrix[end][0] == target) { + return true; } - return false + if (matrix[begin][0] > target || matrix[end][matrix[end].size()- 1] < target) { + return false; + } + const auto &row = matrix[end][0] < target ? matrix[end] : matrix[begin]; + begin = 0; + end = row.size() - 1; + while (begin + 1 < end) { + auto mid = begin + (end - begin) / 2; + if (row[mid] == target) { + return true; + } else if (row[mid] < target) { + begin = mid; + } else { + end = mid; + } + } + return row[begin] == target || row[end] == target; } ``` @@ -217,23 +221,19 @@ func searchMatrix(matrix [][]int, target int) bool { > 假设你有 n 个版本 [1, 2, ..., n],你想找出导致之后所有版本出错的第一个错误的版本。 > 你可以通过调用  bool isBadVersion(version)  接口来判断版本号 version 是否在单元测试中出错。实现一个函数来查找第一个错误的版本。你应该尽量减少对调用 API 的次数。 -```go -func firstBadVersion(n int) int { - // 思路:二分搜索 - start := 0 - end := n - for start+1 < end { - mid := start + (end - start)/2 - if isBadVersion(mid) { - end = mid - } else if isBadVersion(mid) == false { - start = mid +```c++ +int firstBadVersion(int n) { + int begin = 1; + int end = n; + while (begin + 1 < end) { + auto mid = begin + (end - begin) / 2; + if (isBadVersion(mid)) { + end = mid; + } else { + begin = mid; } } - if isBadVersion(start) { - return start - } - return end + return isBadVersion(begin) ? begin : end; } ``` @@ -242,28 +242,20 @@ func firstBadVersion(n int) int { > 假设按照升序排序的数组在预先未知的某个点上进行了旋转( 例如,数组  [0,1,2,4,5,6,7] 可能变为  [4,5,6,7,0,1,2] )。 > 请找出其中最小的元素。 -```go -func findMin(nums []int) int { - // 思路:/ / 最后一个值作为target,然后往左移动,最后比较start、end的值 - if len(nums) == 0 { - return -1 - } - start := 0 - end := len(nums) - 1 - - for start+1 < end { - mid := start + (end-start)/2 - // 最后一个元素值为target - if nums[mid] <= nums[end] { - end = mid +```c++ +int findMin(vector& nums) { + // 最后一个值作为target,以确定是否旋转 + int begin = 0; + int end = nums.size() - 1; + while (begin + 1 < end) { + auto mid = begin + (end - begin) / 2; + if (nums[mid] <= nums[end]) { + end = mid; } else { - start = mid + begin = mid; } } - if nums[start] > nums[end] { - return nums[end] - } - return nums[start] + return nums[begin] > nums[end] ? nums[end] : nums[begin]; } ``` @@ -273,34 +265,28 @@ func findMin(nums []int) int { > ( 例如,数组  [0,1,2,4,5,6,7] 可能变为  [4,5,6,7,0,1,2] )。 > 请找出其中最小的元素。(包含重复元素) -```go -func findMin(nums []int) int { - // 思路:跳过重复元素,mid值和end值比较,分为两种情况进行处理 - if len(nums) == 0 { - return -1 +```c++ +int findMin(vector &nums) { + if (nums.empty()) { + return -1; } - start := 0 - end := len(nums) - 1 - for start+1 < end { - // 去除重复元素 - for start < end && nums[end] == nums[end-1] { - end-- + auto left = 0; + auto right = nums.size() - 1; + while (left + 1 < right) { + while (left < right && nums[right] == nums[right - 1]) { + --right; } - for start < end && nums[start] == nums[start+1] { - start++ + while (left < right && nums[left] == nums[left + 1]) { + ++left; } - mid := start + (end-start)/2 - // 中间元素和最后一个元素比较(判断中间点落在左边上升区,还是右边上升区) - if nums[mid] <= nums[end] { - end = mid + auto mid = left + (right - left) / 2; + if (nums[mid] <= nums[right]) { + right = mid; } else { - start = mid + left = mid; } } - if nums[start] > nums[end] { - return nums[end] - } - return nums[start] + return (nums[left] >= nums[right]) ? nums[right] : nums[left]; } ``` @@ -311,41 +297,40 @@ func findMin(nums []int) int { > 搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回  -1 。 > 你可以假设数组中不存在重复的元素。 -```go -func search(nums []int, target int) int { - // 思路:/ / 两条上升直线,四种情况判断 - if len(nums) == 0 { - return -1 +```c++ +int search(vector &nums, int target) { + if (nums.empty()) { + return -1; } - start := 0 - end := len(nums) - 1 - for start+1 < end { - mid := start + (end-start)/2 - // 相等直接返回 - if nums[mid] == target { - return mid + auto left = 0; + auto right = nums.size() - 1; + while (left + 1 < right) { + auto mid = left + (right - left) / 2; + if (nums[mid] == target) { + return mid; } - // 判断在那个区间,可能分为四种情况 - if nums[start] < nums[mid] { - if nums[start] <= target && target <= nums[mid] { - end = mid + if (nums[left] < nums[mid]) { + if (nums[left] <= target && target <= nums[mid]) { + right = mid; } else { - start = mid + left = mid; } - } else if nums[end] > nums[mid] { - if nums[end] >= target && nums[mid] <= target { - start = mid + } else if (nums[mid] < nums[right]) { + // 这个判断是必须的!当只有两个数的时候,可能两个都不满足! + if (target >= nums[mid] && nums[right] >= target) { + right = mid; } else { - end = mid + left = mid; } } } - if nums[start] == target { - return start - } else if nums[end] == target { - return end + if (nums[left] == target) { + return left; + } else if (nums[right] == target) { + return right; + } else { + return -1; } - return -1 } ``` @@ -359,46 +344,39 @@ func search(nums []int, target int) int { > ( 例如,数组  [0,0,1,2,2,5,6]  可能变为  [2,5,6,0,0,1,2] )。 > 编写一个函数来判断给定的目标值是否存在于数组中。若存在返回  true,否则返回  false。(包含重复元素) -```go -func search(nums []int, target int) bool { - // 思路:/ / 两条上升直线,四种情况判断,并且处理重复数字 - if len(nums) == 0 { - return false +```c++ +bool search(vector& nums, int target) { + if (nums.empty()) { + return false; } - start := 0 - end := len(nums) - 1 - for start+1 < end { - // 处理重复数字 - for start < end && nums[start] == nums[start+1] { - start++ + auto left = 0; + auto right = nums.size() - 1; + while (left + 1 < right) { + while (left < right && nums[left] == nums[left + 1]) { + ++left; } - for start < end && nums[end] == nums[end-1] { - end-- + while (left < right && nums[right] == nums[right - 1]) { + --right; } - mid := start + (end-start)/2 - // 相等直接返回 - if nums[mid] == target { - return true + auto mid = left + (right - left) / 2; + if (nums[mid] == target) { + return true; } - // 判断在那个区间,可能分为四种情况 - if nums[start] < nums[mid] { - if nums[start] <= target && target <= nums[mid] { - end = mid + if (nums[left] < nums[mid]) { + if (nums[left] <= target && target <= nums[mid]) { + right = mid; } else { - start = mid + left = mid; } - } else if nums[end] > nums[mid] { - if nums[end] >= target && nums[mid] <= target { - start = mid + } else if (nums[mid] < nums[right]) { + if (nums[mid] <= target && nums[right] >= target) { + left = mid; } else { - end = mid + right = mid; } } } - if nums[start] == target || nums[end] == target { - return true - } - return false + return nums[left] == target || nums[right] == target; } ``` diff --git a/basic_algorithm/dp.md b/basic_algorithm/dp.md index 3bdee61b..76da4efe 100644 --- a/basic_algorithm/dp.md +++ b/basic_algorithm/dp.md @@ -44,44 +44,25 @@ 动态规划,自底向上 -```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] -} -func min(a, b int) int { - if a > b { - return b - } - return a +```c++ +minimumTotal(vector>& triangle) { + if (triangle.empty() || triangle[0].empty()) { + return 0; + } + auto rowNum = triangle.size(); + auto cache = triangle; + for (int row = rowNum - 2; row >= 0; --row) { + for (int col = 0; col < triangle[row].size(); ++col) { + cache[row][col] = min(cache[row + 1][col], cache[row + 1][col + 1]) + triangle[row][col]; + } + } + return cache[0][0]; } - ``` 动态规划,自顶向下 -```go +```c++ // 测试用例: // [ // [2], @@ -89,48 +70,30 @@ func min(a, b int) int { // [6,5,7], // [4,1,8,3] // ] -func minimumTotal(triangle [][]int) int { - if len(triangle) == 0 || len(triangle[0]) == 0 { - return 0 - } - // 1、状态定义:f[i][j] 表示从0,0出发,到达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] - } - } - // 递推求解 - for i := 1; i < l; i++ { - for j := 0; j < len(triangle[i]); j++ { - // 这里分为两种情况: - // 1、上一层没有左边值 - // 2、上一层没有右边值 - if j-1 < 0 { - f[i][j] = f[i-1][j] + triangle[i][j] - } else if j >= len(f[i-1]) { - f[i][j] = f[i-1][j-1] + triangle[i][j] +int minimumTotal(vector>& triangle) { + if (triangle.empty() || triangle[0].empty()) { + return 0; + } + auto cache = triangle; + for (int row = 1; row < triangle.size(); ++row) { + int colNum = triangle[row].size(); + int col = 0; + for (; col < colNum; ++col) { + if (col == 0) { + cache[row][col] = cache[row-1][col]; + } else if (col >= triangle[row - 1].size()) { + cache[row][col] = cache[row - 1][col - 1]; } else { - f[i][j] = min(f[i-1][j], f[i-1][j-1]) + triangle[i][j] + cache[row][col] = min(cache[row-1][col], cache[row-1][col-1]); } + cache[row][col] += triangle[row][col]; } } - result := f[l-1][0] - for i := 1; i < len(f[l-1]); i++ { - result = min(result, f[l-1][i]) + auto min = cache.back()[0]; + for (const auto &item : cache.back()) { + min = std::min(min, item); } - return result -} -func min(a, b int) int { - if a > b { - return b - } - return a + return min; } ``` @@ -138,7 +101,7 @@ func min(a, b int) int { 递归是一种程序的实现方式:函数的自我调用 -```go +```c++ Function(x) { ... Funciton(x-1); @@ -194,33 +157,25 @@ Function(x) { 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 { - // 思路:动态规划 - // f[i][j] 表示i,j到0,0的和最小 - if len(grid) == 0 || len(grid[0]) == 0 { - return 0 +```c++ +int minPathSum(vector>& grid) { + if (grid.empty() || grid[0].empty()) { + return 0; } - // 复用原来的矩阵列表 - // 初始化:f[i][0]、f[0][j] - for i := 1; i < len(grid); i++ { - grid[i][0] = grid[i][0] + grid[i-1][0] + auto rowNum = grid.size(); + auto colNum = grid[0].size(); + for (int i = 1; i < rowNum; ++i) { + grid[i][0] = grid[i-1][0] + grid[i][0]; } - for j := 1; j < len(grid[0]); j++ { - grid[0][j] = grid[0][j] + grid[0][j-1] + for (int i = 1; i < colNum; ++i) { + grid[0][i] = grid[0][i - 1] + grid[0][i]; } - for i := 1; i < len(grid); i++ { - for j := 1; j < len(grid[i]); j++ { - grid[i][j] = min(grid[i][j-1], grid[i-1][j]) + grid[i][j] + for (int i = 1; i < rowNum; ++i) { + for (int j = 1; j < colNum; ++j) { + grid[i][j] = min(grid[i - 1][j], grid[i][j - 1]) + grid[i][j]; } } - return grid[len(grid)-1][len(grid[0])-1] -} -func min(a, b int) int { - if a > b { - return b - } - return a + return grid[rowNum - 1][colNum - 1]; } ``` @@ -230,24 +185,18 @@ func min(a, b int) int { > 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“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] +```c++ +int uniquePaths(int m, int n) { + if (m == 0 || n == 0) { + return 0; + } + vector> cache(m, vector(n, 1)); + for (int i = 1; i < m; ++i) { + for (int j = 1; j < n; ++j) { + cache[i][j] = cache[i - 1][j] + cache[i][j - 1]; + } + } + return cache[m - 1][n - 1]; } ``` @@ -258,43 +207,32 @@ func uniquePaths(m int, n int) int { > 问总共有多少条不同的路径? > 现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径? -```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] +```c++ +int uniquePathsWithObstacles(vector>& obstacleGrid) { + if (obstacleGrid.empty() || obstacleGrid[0].empty() || obstacleGrid[0][0]) { + return 0; + } + + auto rowNum = obstacleGrid.size(); + auto colNum = obstacleGrid[0].size(); + auto &cache = obstacleGrid; + obstacleGrid[0][0] = !obstacleGrid[0][0]; + for (int i = 1; i < colNum; ++i) { + cache[0][i] = !cache[0][i] && cache[0][i - 1]; + } + for (int i = 1; i < rowNum; ++i) { + cache[i][0] = !cache[i][0] && cache[i - 1][0]; + } + for (int i = 1; i < rowNum; ++i) { + for (int j = 1; j < colNum; ++j) { + if (cache[i][j]) { + cache[i][j] = 0; + continue; + } + cache[i][j] = cache[i - 1][j] + cache[i][j - 1]; + } + } + return cache[rowNum - 1][colNum - 1]; } ``` @@ -304,19 +242,13 @@ func uniquePathsWithObstacles(obstacleGrid [][]int) int { > 假设你正在爬楼梯。需要  *n*  阶你才能到达楼顶。 -```go -func climbStairs(n int) int { - // f[i] = f[i-1] + f[i-2] - if n == 1 || n == 0 { - return n - } - f := make([]int, n+1) - f[1] = 1 - f[2] = 2 - for i := 3; i <= n; i++ { - f[i] = f[i-1] + f[i-2] +```c++ +int climbStairs(int n) { + vector ret(n + 1, 1); + for (int i = 2; i < n + 1; ++i) { + ret[i] = ret[i - 1] + ret[i - 2]; } - return f[n] + return ret[n]; } ``` @@ -326,26 +258,22 @@ func climbStairs(n int) int { > 数组中的每个元素代表你在该位置可以跳跃的最大长度。 > 判断你是否能够到达最后一个位置。 -```go -func canJump(nums []int) bool { - // 思路:看最后一跳 - // 状态:f[i] 表示是否能从0跳到i - // 推导:f[i] = OR(f[j],j= i { - f[i] = true - } +```c++ +bool canJump(vector& nums) { + if (nums.empty()) { + return true; + } + int farest = 0; + for (int i = 0; i < nums.size(); ++i) { + if (farest < i) { + return false; + } + farest = max(farest, nums[i] + i); + if (farest >= nums.size() - 1) { + return true; } } - return f[len(nums)-1] + return false; } ``` @@ -355,97 +283,60 @@ func canJump(nums []int) bool { > 数组中的每个元素代表你在该位置可以跳跃的最大长度。 > 你的目标是使用最少的跳跃次数到达数组的最后一个位置。 -```go -// v1动态规划(其他语言超时参考v2) -func jump(nums []int) int { - // 状态:f[i] 表示从起点到当前位置最小次数 - // 推导:f[i] = f[j],a[j]+j >=i,min(f[j]+1) - // 初始化:f[0] = 0 - // 结果:f[n-1] - f := make([]int, len(nums)) - f[0] = 0 - for i := 1; i < len(nums); i++ { - // f[i] 最大值为i - f[i] = i - // 遍历之前结果取一个最小值+1 - for j := 0; j < i; j++ { - if nums[j]+j >= i { - f[i] = min(f[j]+1,f[i]) - } +```c++ +int jump(vector& nums) { + int size = nums.size(); + vector cache(size); + cache[0] = 0; + // cache[i + 1] >= cache[i] + for (int i = 1, farest = 0; i < size; ++i) { + while (farest < size && farest + nums[farest] < i) { + ++farest; } + // 假设你总是可以到达数组的最后一个位置。 + // 可以认定farest + nums[farest]一定会大于i + cache[i] = cache[farest] + 1; } - return f[len(nums)-1] -} -func min(a, b int) int { - if a > b { - return b - } - return a + return cache.back(); } ``` -```go -// v2 动态规划+贪心优化 -func jump(nums []int) int { - n:=len(nums) - f := make([]int, n) - f[0] = 0 - for i := 1; i < n; i++ { - // 取第一个能跳到当前位置的点即可 - // 因为跳跃次数的结果集是单调递增的,所以贪心思路是正确的 - idx:=0 - for idx 给定一个字符串 _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)] -} -func min(a, b int) int { - 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 +```c++ +int minCut(string s) { + if (s.size() <= 1) { + return 0; + } + auto size = s.size(); + vector cache(size); + for (int i = 0; i < size; ++i) { + cache[i] = i; + } + vector> isPalindrome(size, vector(size)); + for (int right = 0; right < size; ++right) { + for (int left = 0; left <= right; ++left) { + // 如果头尾相同,可能为回文! + // len < 3时,直接判断为回文 + // len >= 3时,判断里面一层,左右往里缩一格 + isPalindrome[left][right] = s[right] == s[left] && (right - left < 3 || isPalindrome[left + 1][right - 1]); + } + } + for (int i = 1; i < size; ++i) { + if (isPalindrome[0][i]) { + cache[i] = 0; + continue; + } + for (int j = 0; j < i; ++j) { + if (isPalindrome[j + 1][i]) { + cache[i] = min(cache[j] + 1, cache[i]); + } + } + } + return cache.back(); } ``` @@ -457,91 +348,91 @@ func isPalindrome(s string, i, j int) bool { > 给定一个无序的整数数组,找到其中最长上升子序列的长度。 -```go -func lengthOfLIS(nums []int) int { - // f[i] 表示从0开始到i结尾的最长序列长度 - // f[i] = max(f[j])+1 ,a[j]& nums) { + if (nums.size() < 2) { + return nums.size(); + } + + vector cache(nums.size()); + cache[0] = 1; + for (int i = 1; i < nums.size(); ++i) { + cache[i] = 1; + for (int j = 0; j < i; ++j) { + if (nums[j] < nums[i]) { + cache[i] = max(cache[i], cache[j] + 1); } } } - result := f[0] - for i := 1; i < len(nums); i++ { - result = max(result, f[i]) - } - return result - -} -func max(a, b int) int { - if a > b { - return a + auto maxLen = cache[0]; + for (const auto &item : cache) { + maxLen = max(maxLen, item); + } + return maxLen; +} +#pragma endregion + +#pragma region 复杂度O(n log n) +// 题目给了提示,看到log n可以考虑往二分查找之类的上凑 +// 思路:保存最大长度及其末尾节点,通过判断是否大于末尾节点来决定是否能够组成子序列 +// 又,既然不能保证当前的"最大长度"会不会被其他组合反超 +// 干脆把整个过程,每个长度都保存下来,构成单调栈 +// 一方面通过栈顶来决定是否能够组合成更长的子序列 +// 另一方面通过查找,找出比当前节点小的第一个末尾进行更新,以确保其他组合的机会 +int lengthOfLIS(vector &nums) { + int size = nums.size(); + if (size < 2) { + return size; + } + + // tail[i]表示长度为i + 1的子序列的最小末尾值 + // tail必定单调递增,单调栈 + // 如果当前节点大于栈顶,即,比最长子序列的末尾还大,入栈(长度+1 + // 否则通过二分查找找到第一个比当前节点大的值,进行更新。子序列长度相同,末尾节点变小 + vector tail; + tail.push_back(nums.front()); + for (int i = 1; i < size; ++i) { + if (nums[i] > tail.back()) { + tail.push_back(nums[i]); + continue; + } + auto left = 0; + auto right = tail.size() - 1; + while (left < right) { + int mid = left + (right - left) / 2; + if (tail[mid] < nums[i]) { + left = mid + 1; + } else { + right = mid; + } + } + tail[left] = nums[i]; } - return b + return tail.size(); } +#pragma endregion ``` ### [word-break](https://leetcode-cn.com/problems/word-break/) > 给定一个**非空**字符串  *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,dict := maxLen(wordDict) - for i := 1; i <= len(s); i++ { - l := 0 - if i - max > 0 { - l = i - max - } - for j := l; j < i; j++ { - if f[j] && inDict(s[j:i],dict) { - f[i] = true - break - } - } - } - return f[len(s)] -} - - - -func maxLen(wordDict []string) (int,map[string]bool) { - dict := make(map[string]bool) - max := 0 - for _, v := range wordDict { - dict[v] = true - if len(v) > max { - max = len(v) - } - } - return max,dict -} - -func inDict(s string,dict map[string]bool) bool { - _, ok := dict[s] - return ok +```c++ +bool wordBreak(string s, vector &wordDict) { + unordered_set wordDictSet{wordDict.begin(), wordDict.end()}; + vector dp(s.size() + 1); + dp[0] = true; + for (int i = 1; i <= s.size(); ++i) { + for (int j = 0; j < i; ++j) { + if (dp[j] && wordDictSet.find(s.substr(j, i - j)) != wordDictSet.end()) { + dp[i] = true; + break; + } + } + } + return dp.back(); } - ``` 小结 @@ -561,50 +452,23 @@ func inDict(s string,dict map[string]bool) bool { > 一个字符串的   子序列   是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。 > 例如,"ace" 是 "abcde" 的子序列,但 "aec" 不是 "abcde" 的子序列。两个字符串的「公共子序列」是这两个字符串所共同拥有的子序列。 -```go -func longestCommonSubsequence(a string, b string) int { - // dp[i][j] a前i个和b前j个字符最长公共子序列 - // dp[m+1][n+1] - // ' a d c e - // ' 0 0 0 0 0 - // a 0 1 1 1 1 - // c 0 1 1 2 1 - // - dp:=make([][]int,len(a)+1) - for i:=0;i<=len(a);i++ { - dp[i]=make([]int,len(b)+1) - } - for i:=1;i<=len(a);i++ { - for j:=1;j<=len(b);j++ { - // 相等取左上元素+1,否则取左或上的较大值 - if a[i-1]==b[j-1] { - dp[i][j]=dp[i-1][j-1]+1 +```c++ +vector> dp(text1.size() + 1, vector(text2.size() + 1)); + for (int i = 1; i <= text1.size(); ++i) { + for (int j = 1; j <= text2.size(); ++j) { + if (text1[i - 1] == text2[j - 1]) { + dp[i][j] = dp[i - 1][j - 1] + 1; } else { - dp[i][j]=max(dp[i-1][j],dp[i][j-1]) + dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]); } } } - return dp[len(a)][len(b)] -} -func max(a,b int)int { - if a>b{ - return a - } - return b + return dp.back().back(); } ``` 注意点 -- go 切片初始化 - -```go -dp:=make([][]int,len(a)+1) -for i:=0;i<=len(a);i++ { - dp[i]=make([]int,len(b)+1) -} -``` - - 从 1 开始遍历到最大长度 - 索引需要减一 @@ -618,37 +482,30 @@ for i:=0;i<=len(a);i++ { 思路:和上题很类似,相等则不需要操作,否则取删除、插入、替换最小操作次数的值+1 -```go -func minDistance(word1 string, word2 string) int { - // dp[i][j] 表示a字符串的前i个字符编辑为b字符串的前j个字符最少需要多少次操作 - // 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) - dp:=make([][]int,len(word1)+1) - for i:=0;i> dp(word1.size(), vector(word2.size())); + for (int i = 0; i < word1.size(); ++i) { + dp[i][0] = i; + } + for (int i = 0; i < word2.size(); ++i) { + dp[0][i] = i; + } + for (int i = 1; i <= word1.size(); ++i) { + for (int j = 1; j <= word2.size(); ++j) { + if (word1[i - 1] == word2[j - 1]) { + dp[i][j] = dp[i - 1][j - 1]; + } else { + dp[i][j] = min(min(dp[i - 1][j], dp[i][j - 1]), dp[i - 1][j - 1]) + 1; } } } - return dp[len(word1)][len(word2)] -} -func min(a,b int)int{ - if a>b{ - return b - } - return a + return dp.back().back(); } ``` @@ -664,35 +521,23 @@ func min(a,b int)int{ 思路:和其他 DP 不太一样,i 表示钱或者容量 -```go -func coinChange(coins []int, amount int) int { - // 状态 dp[i]表示金额为i时,组成的最小硬币个数 - // 推导 dp[i] = min(dp[i-1], dp[i-2], dp[i-5])+1, 前提 i-coins[j] > 0 - // 初始化为最大值 dp[i]=amount+1 - // 返回值 dp[n] or dp[n]>amount =>-1 - dp:=make([]int,amount+1) - for i:=0;i<=amount;i++{ - dp[i]=amount+1 - } - dp[0]=0 - for i:=1;i<=amount;i++{ - for j:=0;j=0 { - dp[i]=min(dp[i],dp[i-coins[j]]+1) +```c++ +int coinChange(vector& coins, int amount) { + // 初始化为amount + 1,如果最后大于amount说明无解 + // dp[i]表示金额为i时,所需最少硬币个数 + // dp[i] = min(dp[i], dp[i - coin] + 1) + // dp[i - coin],倒扣当前面额 + // 注意不要越界,i - coin >= 0 + vector dp(amount + 1, amount + 1); + dp[0] = 0; + for (auto i = 1; i <= amount; ++i) { + for (const auto &coin : coins) { + if (i - coin >= 0) { + dp[i] = min(dp[i], dp[i - coin] + 1); } } } - if dp[amount] > amount { - return -1 - } - return dp[amount] - -} -func min(a,b int)int{ - if a>b{ - return b - } - return a + return dp.back() > amount ? -1 : dp.back(); } ``` @@ -704,34 +549,30 @@ func min(a,b int)int{ > 在 n 个物品中挑选若干物品装入背包,最多能装多满?假设背包的大小为 m,每个物品的大小为 A[i] -```go -func backPack (m int, A []int) int { +```c++ +int backPack(int m, vector &A) { // write your code here - // f[i][j] 前i个物品,是否能装j - // f[i][j] =f[i-1][j] f[i-1][j-a[i] j>a[i] - // f[0][0]=true f[...][0]=true - // f[n][X] - f:=make([][]bool,len(A)+1) - for i:=0;i<=len(A);i++{ - f[i]=make([]bool,m+1) - } - f[0][0]=true - for i:=1;i<=len(A);i++{ - for j:=0;j<=m;j++{ - f[i][j]=f[i-1][j] - if j-A[i-1]>=0 && f[i-1][j-A[i-1]]{ - f[i][j]=true + // dp[i][j] 前i个物品,是否能装j + // dp[i - 1][j] == true? 则不需要第i个物品也能装满 + // dp[i - 1][j - A[i - 1]]? 腾出第i个物品的空间,剩下空间能否装满 + // dp[i][j] = dp[i - 1][j] or dp[i - 1][j - A[i - 1]] + vector> dp(A.size() + 1, vector(m + 1)); + dp[0][0] = true; + for (int i = 1; i <= A.size(); ++i) { + for (int j = 0; j <= m; ++j) { + dp[i][j] = dp[i - 1][j]; + if (j - A[i - 1] >= 0 && dp[i - 1][j - A[i - 1]]) { + dp[i][j] = true; } } } - for i:=m;i>=0;i--{ - if f[len(A)][i] { - return i + for (int i = m; i >= 0; ++i) { + if (dp[A.size()][i]) { + return i; } } - return 0 + return 0; } - ``` ### [backpack-ii](https://www.lintcode.com/problem/backpack-ii/description) @@ -741,31 +582,22 @@ func backPack (m int, A []int) int { 思路:f[i][j] 前 i 个物品,装入 j 背包 最大价值 -```go -func backPackII (m int, A []int, V []int) int { +```c++ +int backPackII(int m, vector &A, vector &V) { // write your code here - // f[i][j] 前i个物品,装入j背包 最大价值 - // f[i][j] =max(f[i-1][j] ,f[i-1][j-A[i]]+V[i]) 是否加入A[i]物品 - // f[0][0]=0 f[0][...]=0 f[...][0]=0 - f:=make([][]int,len(A)+1) - for i:=0;i= 0{ - f[i][j]=max(f[i-1][j],f[i-1][j-A[i-1]]+V[i-1]) + // dp[i][j] 前i个物品,装入j背包 最大价值 + vector> dp(A.size() + 1, vector(V.size() + 1)); + for (int i = 1; i <= A.size(); ++i) { + for (int j = 0; j <= m; ++j) { + // 不选第i个物品,则dp[i][j] = dp[i - 1][j] + // 选了第i个物品,则dp[i][j] = dp[i - 1][j - A[i - 1]],倒扣i的大小 + dp[i][j] = dp[i - 1][j]; + if (j - A[i - 1] >= 0) { + dp[i][j] = max(dp[i][j], dp[i - 1][j - A[i - 1]] + V[i - 1]); } } } - return f[len(A)][m] -} -func max(a,b int)int{ - if a>b{ - return a - } - return b + return dp.back().back(); } ``` diff --git a/basic_algorithm/sort.md b/basic_algorithm/sort.md index 9eaa2cb8..ff167991 100644 --- a/basic_algorithm/sort.md +++ b/basic_algorithm/sort.md @@ -4,83 +4,77 @@ ### 快速排序 -```go -func QuickSort(nums []int) []int { - // 思路:把一个数组分为左右两段,左段小于右段 - quickSort(nums, 0, len(nums)-1) - return nums - +```c++ +template +static void QuickSort(T arr[], int len) { + quickSort(arr, 0, len - 1); } -// 原地交换,所以传入交换索引 -func quickSort(nums []int, start, end int) { - if start < end { - // 分治法:divide - pivot := partition(nums, start, end) - quickSort(nums, 0, pivot-1) - quickSort(nums, pivot+1, end) + +template +static void quickSort(T arr[], int begin, int end) { + if (begin >= end) { + return; } + auto pivot = partition(arr, begin, end); + quickSort(arr, begin, pivot - 1); + quickSort(arr, pivot + 1, end); } -// 分区 -func partition(nums []int, start, end int) int { - // 选取最后一个元素作为基准pivot - p := nums[end] - i := start - // 最后一个值就是基准所以不用比较 - for j := start; j < end; j++ { - if nums[j] < p { - swap(nums, i, j) - i++ + +template +static int partition(T arr[], int begin, int end) { + auto base = arr[end]; + auto lessInsert = begin; + for (int i = begin; i < end; ++i) { + if (arr[i] < base) { + swap(arr[lessInsert++], arr[i]); } } - // 把基准值换到中间 - swap(nums, i, end) - return i -} -// 交换两个元素 -func swap(nums []int, i, j int) { - t := nums[i] - nums[i] = nums[j] - nums[j] = t + swap(arr[lessInsert], arr[end]); + return lessInsert; } ``` ### 归并排序 -```go -func MergeSort(nums []int) []int { - return mergeSort(nums) +```c++ +template +static void MergeSort(T arr[], int len) { + auto tmp = new T[len]; + mergeSort(arr, 0, len - 1, tmp); + delete[] tmp; } -func mergeSort(nums []int) []int { - if len(nums) <= 1 { - return nums + +template +static void mergeSort(T arr[], int begin, int end, T tmp[]) { + if (begin + 1 >= end) { + return; } - // 分治法:divide 分为两段 - mid := len(nums) / 2 - left := mergeSort(nums[:mid]) - right := mergeSort(nums[mid:]) - // 合并两段数据 - result := merge(left, right) - return result -} -func merge(left, right []int) (result []int) { - // 两边数组合并游标 - l := 0 - r := 0 - // 注意不能越界 - for l < len(left) && r < len(right) { - // 谁小合并谁 - if left[l] > right[r] { - result = append(result, right[r]) - r++ - } else { - result = append(result, left[l]) - l++ - } + + auto mid = begin + (end - begin) / 2; + auto begin1 = begin; + auto end1 = mid; + auto begin2 = mid + 1; + auto end2 = end; + mergeSort(arr, begin1, end1, tmp); + mergeSort(arr, begin2, end2, tmp); + + // merge two parts + auto index = begin; + while (begin1 <= end1 && begin2 <= end2) { + tmp[index++] = arr[begin1] < arr[begin2] ? arr[begin1++] : arr[begin2++]; + } + + while (begin1 <= end1) { + tmp[index++] = arr[begin1++]; + } + + while (begin2 <= end2) { + tmp[index++] = arr[begin2++]; + } + + for (int i = begin; i <= end; ++i) { + arr[i] = tmp[i]; } - // 剩余部分合并 - result = append(result, left[l:]...) - result = append(result, right[r:]...) - return } ``` @@ -98,55 +92,41 @@ func merge(left, right []int) (result []int) { 核心代码 -```go -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 -} -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 - } -} -func swap(a []int, i, j int) { - a[i], a[j] = a[j], a[i] +```c++ +template +static void HeapSort(T arr[], int len) { + // right leaf of a root = 2(n + 1) since n is start from 0 + auto lastRoot = len / 2 - 1; + // 先自下而上构建堆 + for (int i = lastRoot; i >= 0; --i) { + makeHeap(arr, i, len); + } + + for (int j = len - 1; j >= 0; --j) { + // 最大值放到后面 + swap(arr[0], arr[j]); + // 底部都还是有序的,只动了根节点,自上而下更新堆 + makeHeap(arr, 0, --len); + } } +template +static void makeHeap(T arr[], int root, int len) { + auto left = root * 2 + 1; + auto right = (root + 1) * 2; + auto largest = root; + if (left < len && arr[left] > arr[largest]) { + largest = left; + } + if (right < len && arr[right] > arr[largest]) { + largest = right; + } + if (largest == root) { + return; + } + swap(arr[largest], arr[root]); + makeHeap(arr, largest, len); +} ``` ## 参考 diff --git a/data_structure/binary_op.md b/data_structure/binary_op.md index e77b8adc..4e732332 100644 --- a/data_structure/binary_op.md +++ b/data_structure/binary_op.md @@ -32,15 +32,15 @@ diff=(n&(n-1))^n > 给定一个**非空**整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。 -```go -func singleNumber(nums []int) int { +```c++ +int singleNumber(vector& nums) { // 10 ^10 == 00 // 两个数异或就变成0 - result:=0 - for i:=0;i 给定一个**非空**整数数组,除了某个元素只出现一次以外,其余每个元素均出现了三次。找出那个只出现了一次的元素。 -```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 +```c++ +// 思路:每一位,统计有多少个1,除以3取余数 +// 出现三次的,取余数为0,被过滤掉,剩下的都是只出现一次的那个数 +int singleNumber(vector& nums) { + auto ret = 0; + for (int i = 0; i < 32; ++i) { + auto sumOne = 0; + for (const auto &item : nums) { + sumOne += (item >> i) & 1; + } + ret ^= (sumOne % 3) << i; + } + return ret; } ``` @@ -69,43 +68,50 @@ func singleNumber(nums []int) int { > 给定一个整数数组  `nums`,其中恰好有两个元素只出现一次,其余所有元素均出现两次。 找出只出现一次的那两个元素。 -```go -func singleNumber(nums []int) []int { - // a=a^b - // b=a^b - // a=a^b - // 关键点怎么把a^b分成两部分,方案:可以通过diff最后一个1区分 - - diff:=0 - for i:=0;i singleNumber(vector& nums) { + auto diff = 0; + // 按位与,出现两次的数会被剔除掉 + for (const auto &item : nums) { + diff ^= item; } - result:=[]int{diff,diff} - // 去掉末尾的1后异或diff就得到最后一个1的位置 - diff=(diff&(diff-1))^diff - for i:=0;i result(2); + // 只出现一次的两个数不想等,则其异或的结果必然至少有一位为 1 + // 找到最后一个 '1' + diff &= (-diff); + // 再按该位上的值为1还是为0将所有的数分成两组进行异或把两个数分开 + for (const auto &num : nums) { + if ((diff & num) == 0) { + result[0] ^= num; + } else { + result[1] ^= num; } } - return result + return result; } ``` +> 关于 x & (-x) +> 在计算机系统中,数值一律用补码来表示和存储 +> 一个正数 x 与其相反数 -x,只有一位同时为 '1',并且总为 x 中的最后一位 '1' +> 正数的补码与原码相同 +> 负数的补码,将其原码除符号位外的所有位取反后加1 +> 10和-10用8位表示为: +> 10: 0000 1010 +> -10: 1111 1010 +> 因此,x & (-x) 可用于求出x中最后一个'1' [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))。 -```go -func hammingWeight(num uint32) int { - res:=0 - for num!=0{ - num=num&(num-1) - res++ +```c++ +int hammingWeight(int n) { + auto ret = 0; + while (n != 0) { + n = n & (n - 1); + ++ret; } - return res + return ret; } ``` @@ -113,34 +119,37 @@ func hammingWeight(num uint32) int { > 给定一个非负整数  **num**。对于  0 ≤ i ≤ num  范围中的每个数字  i ,计算其二进制数中的 1 的数目并将它们作为数组返回。 -```go -func countBits(num int) []int { - res:=make([]int,num+1) - - for i:=0;i<=num;i++{ - res[i]=count1(i) +```c++ +vector countBits(int num) { + vector ret(num+1); + for (int i = 0; i <= num; ++i) { + ret[i] = hammingWeight(i); } - return res + return ret; } -func count1(n int)(res int){ - for n!=0{ - n=n&(n-1) - res++ + +int hammingWeight(int n) { + auto ret = 0; + while (n != 0) { + n = n & (n - 1); + ++ret; } - return + + return ret; } ``` 另外一种动态规划解法 -```go -func countBits(num int) []int { - res:=make([]int,num+1) - for i:=1;i<=num;i++{ - // 上一个缺1的元素+1即可 - res[i]=res[i&(i-1)]+1 +```c++ +vector countBits(int num) { + vector ret(num + 1); + for (int i = 1; i <= num; ++i) { + // 对于 i,其二进制1的个数 = 移除其最后一个1剩余的1的个数 + 1 + // i & (i - 1)移除最后一个1 + ret[i] = ret[i & (i - 1)] + 1; } - return res + return ret; } ``` @@ -150,17 +159,17 @@ func countBits(num int) []int { 思路:依次颠倒即可 -```go -func reverseBits(num uint32) uint32 { - var res uint32 - var pow int=31 - for num!=0{ - // 把最后一位取出来,左移之后累加到结果中 - res+=(num&1)<>=1 - pow-- +```c++ +uint32_t reverseBits(uint32_t n) { + uint32_t ret = 0; // 记得初始化,否则通不过 + auto pow = 31; + while(n != 0) { + // 取最后一位进行左移 + ret += (n & 1) << pow; + n >>= 1; + --pow; } - return res + return ret; } ``` @@ -168,23 +177,29 @@ func reverseBits(num uint32) uint32 { > 给定范围 [m, n],其中 0 <= m <= n <= 2147483647,返回此范围内所有数字的按位与(包含 m, n 两端点)。 -```go -func rangeBitwiseAnd(m int, n int) int { - // m 5 1 0 1 - // 6 1 1 0 - // n 7 1 1 1 - // 把可能包含0的全部右移变成 - // m 5 1 0 0 - // 6 1 0 0 - // n 7 1 0 0 - // 所以最后结果就是m<>=1 - n>>=1 - count++ +```c++ +/* + * 思路: + * 只要有一位不同时位1,则按位与结果为0 + * 因此,从m和n最左边一个不同时为1的位起,其右边的位都将被剔除,如: + * m = 5 0101 + * n = 15 1111 + * 后面2位最终会变为0 + * 所以,掐头去尾取中间(左边起第二个1)么? + * 不,n再大也没用,m跟n最左边的1必须在同一位,否则结果直接为0 + * 毕竟终归是会有一个中间的数,10000,按位与完,m就没了 + * + * 所以,m、n同时右移,直到二者相等(剩下最左边的若干个1,或者为0) + * 再进行左移 + */ +int rangeBitwiseAnd(int m, int n) { + auto zeroNum = 0; + while (m != n) { + m >>= 1; + n >>= 1; + ++zeroNum; } - return m<val << " "; + preOrderTraversal(root->left); + preOrderTraversal(root->right); } ``` #### 前序非递归 -```go +```c++ // V3:通过非递归遍历 -func preorderTraversal(root *TreeNode) []int { - // 非递归 - if root == nil{ - return nil - } - result:=make([]int,0) - stack:=make([]*TreeNode,0) - - for root!=nil || len(stack)!=0{ - for root !=nil{ - // 前序遍历,所以先保存结果 - result=append(result,root.Val) - stack=append(stack,root) - root=root.Left +void preorderTraversal(TreeNode *root) { + if (!root) { + return; + } + auto stack = vector(); + while (root || !stack.empty()) { + if (!root) { + root = stack.back(); + stack.pop_back(); + } + cout << root->val << " "; + if (root->right) { + stack.push_back(root->right); } - // pop - node:=stack[len(stack)-1] - stack=stack[:len(stack)-1] - root=node.Right + root = root->left; } - return result } ``` #### 中序非递归 -```go +```c++ // 思路:通过stack 保存已经访问的元素,用于原路返回 -func inorderTraversal(root *TreeNode) []int { - result := make([]int, 0) - if root == nil { - return result - } - stack := make([]*TreeNode, 0) - for len(stack) > 0 || root != nil { - for root != nil { - stack = append(stack, root) - root = root.Left // 一直向左 +void inorderTraversal(TreeNode *root) { + if (!root) { + return; + } + auto stack = vector(); + + while (root || !stack.empty()) { + while (root) { + stack.push_back(root); + root = root->left; } - // 弹出 - val := stack[len(stack)-1] - stack = stack[:len(stack)-1] - result = append(result, val.Val) - root = val.Right + root = stack.back(); + stack.pop_back(); + cout << root->val << " "; + root = root->right; } - return result } ``` #### 后序非递归 -```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 +```c++ +void postorderTraversal(TreeNode *root) { + if (!root) { + return; + } + auto stack = vector(); + TreeNode *lastPop = nullptr; + while (root || !stack.empty()) { + // 每次找到最左下节点 + while (root) { + stack.push_back(root); + root = root->left; + } + root = stack.back(); + // 后序遍历,不能直接弹出,先检查是否有右子节点 + // 右节点要注意有可能已经遍历过了,又撤回来到当前节点 + // 只要保存上次弹出的节点即可!上次弹出的必定是其右子节点 + if (root->right && root->right != lastPop) { + root = root->right; + continue; + } + cout << root->val << " "; + stack.pop_back(); + lastPop = root; + root = nullptr; + } } ``` @@ -118,52 +109,50 @@ func postorderTraversal(root *TreeNode) []int { #### DFS 深度搜索-从上到下 -```go -type TreeNode struct { - Val int - Left *TreeNode - Right *TreeNode -} +```c++ +struct TreeNode { + int val; + TreeNode *left; + TreeNode *right; +}; -func preorderTraversal(root *TreeNode) []int { - result := make([]int, 0) - dfs(root, &result) - return result +vector preOrderTraversal(TreeNode *root) { + vector result; + dfs(root, result); + return result; } -// V1:深度遍历,结果指针作为参数传入到函数内部 -func dfs(root *TreeNode, result *[]int) { - if root == nil { - return +void dfs(TreeNode *root, vector &result) { + if (!root) { + return; } - *result = append(*result, root.Val) - dfs(root.Left, result) - dfs(root.Right, result) + result.push_back(root->val); + dfs(root->left, result); + dfs(root->right, result); } ``` #### DFS 深度搜索-从下向上(分治法) -```go +```c++ // V2:通过分治法遍历 -func preorderTraversal(root *TreeNode) []int { - result := divideAndConquer(root) - return result +vector preOrderTraversal(TreeNode *root) { + return divideAndConquer(root); } -func divideAndConquer(root *TreeNode) []int { - result := make([]int, 0) - // 返回条件(null & leaf) - if root == nil { - return result + +vector divideAndConquer(TreeNode *root) { + vector result; + if (!root) { + return result; } // 分治(Divide) - left := divideAndConquer(root.Left) - right := divideAndConquer(root.Right) + auto left = divideAndConquer(root->left); + auto right = divideAndConquer(root->right); // 合并结果(Conquer) - result = append(result, root.Val) - result = append(result, left...) - result = append(result, right...) - return result + result.push_back(root->val); + result.insert(result.end(), left.begin(), left.end()); + result.insert(result.end(), right.begin(), right.end()); + return result; } ``` @@ -173,35 +162,27 @@ func divideAndConquer(root *TreeNode) []int { #### BFS 层次遍历 -```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) - // 为什么要取length? - // 记录当前层有多少元素(遍历当前层,再添加下一层) - 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) - } +```c++ +vector levelOrder(TreeNode *root) { + vector result; + if (!root) { + return result; + } + queue queue; + queue.push(root); + while (!queue.empty()) { + root = queue.front(); + result.push_back(root->val); + if (root->left) { + queue.push(root->left); + } + if (root->right) { + queue.push(root->right); } - result = append(result, list) + queue.pop(); } - return result + + return result; } ``` @@ -221,86 +202,89 @@ func levelOrder(root *TreeNode) [][]int { - 分段处理 - 合并结果 -```go -func traversal(root *TreeNode) ResultType { +```c++ +ResultType traversal(TreeNode *root) { // nil or leaf - if root == nil { + if (!root) { // do something and return } // Divide - ResultType left = traversal(root.Left) - ResultType right = traversal(root.Right) + auto left = traversal(root->left); + auto right = traversal(root->right); // Conquer - ResultType result = Merge from left and right + auto result = merge(left, right); - return result + return result; } ``` #### 典型示例 -```go +```c++ // V2:通过分治法遍历二叉树 -func preorderTraversal(root *TreeNode) []int { - result := divideAndConquer(root) - return result +vector preOrderTraversal(TreeNode *root) { + return divideAndConquer(root); } -func divideAndConquer(root *TreeNode) []int { - result := make([]int, 0) - // 返回条件(null & leaf) - if root == nil { - return result + +vector divideAndConquer(TreeNode *root) { + vector result; + if (!root) { + return result; } // 分治(Divide) - left := divideAndConquer(root.Left) - right := divideAndConquer(root.Right) + auto left = divideAndConquer(root->left); + auto right = divideAndConquer(root->right); // 合并结果(Conquer) - result = append(result, root.Val) - result = append(result, left...) - result = append(result, right...) - return result + result.push_back(root->val); + result.insert(result.end(), left.begin(), left.end()); + result.insert(result.end(), right.begin(), right.end()); + return result; } ``` #### 归并排序   -```go -func MergeSort(nums []int) []int { - return mergeSort(nums) -} -func mergeSort(nums []int) []int { - if len(nums) <= 1 { - return nums - } - // 分治法:divide 分为两段 - mid := len(nums) / 2 - left := mergeSort(nums[:mid]) - right := mergeSort(nums[mid:]) - // 合并两段数据 - result := merge(left, right) - return result -} -func merge(left, right []int) (result []int) { - // 两边数组合并游标 - l := 0 - r := 0 - // 注意不能越界 - for l < len(left) && r < len(right) { - // 谁小合并谁 - if left[l] > right[r] { - result = append(result, right[r]) - r++ - } else { - result = append(result, left[l]) - l++ - } +```c++ +template +static void MergeSort(T arr[], int len) { + auto tmp = new T[len]; + mergeSort(arr, 0, len - 1, tmp); + delete[] tmp; +} + +template +static void mergeSort(T arr[], int begin, int end, T tmp[]) { + if (begin + 1 >= end) { + return; + } + + auto mid = begin + (end - begin) / 2; + auto begin1 = begin; + auto end1 = mid; + auto begin2 = mid + 1; + auto end2 = end; + mergeSort(arr, begin1, end1, tmp); + mergeSort(arr, begin2, end2, tmp); + + // merge two parts + auto index = begin; + while (begin1 <= end1 && begin2 <= end2) { + tmp[index++] = arr[begin1] < arr[begin2] ? arr[begin1++] : arr[begin2++]; + } + + while (begin1 <= end1) { + tmp[index++] = arr[begin1++]; + } + + while (begin2 <= end2) { + tmp[index++] = arr[begin2++]; + } + + for (int i = begin; i <= end; ++i) { + arr[i] = tmp[i]; } - // 剩余部分合并 - result = append(result, left[l:]...) - result = append(result, right[r:]...) - return } ``` @@ -310,40 +294,33 @@ func merge(left, right []int) (result []int) { #### 快速排序   -```go -func QuickSort(nums []int) []int { - // 思路:把一个数组分为左右两段,左段小于右段,类似分治法没有合并过程 - quickSort(nums, 0, len(nums)-1) - return nums - -} -// 原地交换,所以传入交换索引 -func quickSort(nums []int, start, end int) { - if start < end { - // 分治法:divide - 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++ - } - } - // 把中间的值换为用于比较的基准值 - swap(nums, i, end) - return i -} -func swap(nums []int, i, j int) { - t := nums[i] - nums[i] = nums[j] - nums[j] = t +```c++ +template +static void QuickSort(T arr[], int len) { + quickSort(arr, 0, len - 1); +} + +template +static void quickSort(T arr[], int begin, int end) { + if (begin >= end) { + return; + } + auto pivot = partition(arr, begin, end); + quickSort(arr, begin, pivot - 1); + quickSort(arr, pivot + 1, end); +} + +template +static int partition(T arr[], int begin, int end) { + auto base = arr[end]; + auto lessInsert = begin; + for (int i = begin; i < end; ++i) { + if (arr[i] < base) { + swap(arr[lessInsert++], arr[i]); + } + } + swap(arr[lessInsert], arr[end]); + return lessInsert; } ``` @@ -362,21 +339,17 @@ func swap(nums []int, i, j int) { 思路:分治法 -```go -func maxDepth(root *TreeNode) int { - // 返回条件处理 - if root == nil { - return 0 +```c++ +int maxDepth(TreeNode* root) { + if (!root) { + return 0; } // divide:分左右子树分别计算 - left := maxDepth(root.Left) - right := maxDepth(root.Right) - + auto leftDepth = maxDepth(root->left); + auto rightDepth = maxDepth(root->right); + // conquer:合并左右子树结果 - if left > right { - return left + 1 - } - return right + 1 + return (leftDepth > rightDepth ? leftDepth : rightDepth) + 1; } ``` @@ -390,29 +363,21 @@ func maxDepth(root *TreeNode) int { 因为需要返回是否平衡及高度,要么返回两个数据,要么合并两个数据, 所以用-1 表示不平衡,>0 表示树高度(二义性:一个变量有两种含义)。 -```go -func isBalanced(root *TreeNode) bool { - if maxDepth(root) == -1 { - return false - } - return true +```c++ +bool isBalanced(TreeNode *root) { + return maxDepth(root) != -1; } -func maxDepth(root *TreeNode) int { - // check - if root == nil { - return 0 - } - left := maxDepth(root.Left) - right := maxDepth(root.Right) - // 为什么返回-1呢?(变量具有二义性) - if left == -1 || right == -1 || left-right > 1 || right-left > 1 { - return -1 +int maxDepth(TreeNode *root) { + if (!root) { + return 0; } - if left > right { - return left + 1 + auto left = maxDepth(root->left); + auto right = maxDepth(root->right); + if (left == -1 || right == -1 || left - right > 1 || right - left > 1) { + return -1; } - return right + 1 + return (left > right ? left : right) + 1; } ``` @@ -428,45 +393,29 @@ func maxDepth(root *TreeNode) int { 思路:分治法,分为三种情况:左子树最大路径和最大,右子树最大路径和最大,左右子树最大加根节点最大,需要保存两个变量:一个保存子树最大路径和,一个保存左右加根节点和,然后比较这个两个变量选择最大值即可 -```go -type ResultType struct { - SinglePath int // 保存单边最大值 - MaxPath int // 保存最大值(单边或者两个单边+根的值) -} -func maxPathSum(root *TreeNode) int { - result := helper(root) - return result.MaxPath -} -func helper(root *TreeNode) ResultType { - // check - if root == nil { - return ResultType{ - SinglePath: 0, - MaxPath: -(1 << 31), - } - } - // Divide - left := helper(root.Left) - right := helper(root.Right) - - // Conquer - result := ResultType{} - // 求单边最大值 - if left.SinglePath > right.SinglePath { - result.SinglePath = max(left.SinglePath + root.Val, 0) - } else { - result.SinglePath = max(right.SinglePath + root.Val, 0) - } - // 求两边加根最大值 - maxPath := max(right.MaxPath, left.MaxPath) - result.MaxPath = max(maxPath,left.SinglePath+right.SinglePath+root.Val) - return result +```c++ +int maxPathSum(TreeNode *root) { + auto maxSum = std::numeric_limits::min(); + calcMaxPath(root, maxSum); + return maxSum; } -func max(a,b int) int { - if a > b { - return a + +int calcMaxPath(TreeNode *&root, int &maxSum) { + if (!root) { + return 0; } - return b + // 如果左右子树为负,直接丢弃该部分(不需要从一个叶子节点走到另一个叶子)设置为0 + auto left = max(calcMaxPath(root->left), 0); + auto right = max(calcMaxPath(root->right), 0); + + // 取当前值与当前"闭环"(左右子树+当前节点)的最大值 + // 注意,初始值要设置为最小数! + // 子树可以不舍弃负数,但是结果不能(必须至少经过一个节点) + // 当前节点为叶子时,right + left + root->val = 0 + 0 + root->val + maxSum = max(maxSum, left + right + root->val); + // 已经处理过同时涉及左右子树+当前节点的可能 + // 对于后续处理,只有一种可能,以当前节点为根的这颗树,取一条路径返回——左右子树最大值 + 当前节点 + return max(left, right) + root->val; } ``` @@ -478,33 +427,24 @@ func max(a,b int) int { 思路:分治法,有左子树的公共祖先或者有右子树的公共祖先,就返回子树的祖先,否则返回根节点 -```go -func lowestCommonAncestor(root, p, q *TreeNode) *TreeNode { - // check - if root == nil { - return root +```c++ +TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) { + if (!root) { + return nullptr; } // 相等 直接返回root节点即可 - if root == p || root == q { - return root + if (root == p || root == q) { + return root; } // Divide - left := lowestCommonAncestor(root.Left, p, q) - right := lowestCommonAncestor(root.Right, p, q) - - + auto left = lowestCommonAncestor(root->left, p, q); + auto right = lowestCommonAncestor(root->right, p, q); // Conquer // 左右两边都不为空,则根节点为祖先 - if left != nil && right != nil { - return root - } - if left != nil { - return left - } - if right != nil { - return right + if (left && right) { + return root; } - return nil + return left ? left : right; } ``` @@ -518,34 +458,30 @@ func lowestCommonAncestor(root, p, q *TreeNode) *TreeNode { 思路:用一个队列记录一层的元素,然后扫描这一层元素添加下一层元素到队列(一个数进去出来一次,所以复杂度 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) - // 为什么要取length? - // 记录当前层有多少元素(遍历当前层,再添加下一层) - 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 +```c++ +vector> levelOrder(TreeNode* root) { + vector> ret; + if (!root) { + return ret; + } + queue queue; + queue.push(root); + while (!queue.empty()) { + vector levelValues; + for (auto levelNodeNum = queue.size(); levelNodeNum > 0; --levelNodeNum) { + auto node = queue.front(); + queue.pop(); + levelValues.push_back(node->val); + if (node->left) { + queue.push(node->left); + } + if (node->right) { + queue.push(node->right); + } + } + ret.push_back(levelValues); + } + return ret; } ``` @@ -557,45 +493,42 @@ func levelOrder(root *TreeNode) [][]int { 思路:在层级遍历的基础上,翻转一下结果即可 -```go -func levelOrderBottom(root *TreeNode) [][]int { - result := levelOrder(root) - // 翻转结果 - reverse(result) - 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] - } -} -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) - // 为什么要取length? - // 记录当前层有多少元素(遍历当前层,再添加下一层) - 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 +```c++ +vector> levelOrderBottom(TreeNode *root) { + auto result = levelOrder(root); + reverse(result); + return result; +} + +vector> levelOrder(TreeNode *root) { + vector> ret; + if (!root) { + return ret; + } + queue queue; + queue.push(root); + while (!queue.empty()) { + vector levelValues; + for (auto levelNodeNum = queue.size(); levelNodeNum > 0; --levelNodeNum) { + auto node = queue.front(); + queue.pop(); + levelValues.push_back(node->val); + if (node->left) { + queue.push(node->left); + } + if (node->right) { + queue.push(node->right); + } + } + ret.push_back(levelValues); + } + return ret; +} + +void reverse(vector> &result) { + for (int i = 0, j = result.size() - 1; i < j; ++i, --j) { + swap(result[i], result[j]); + } } ``` @@ -605,45 +538,39 @@ func levelOrder(root *TreeNode) [][]int { > 给定一个二叉树,返回其节点值的锯齿形层次遍历。Z 字形遍历 -```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 -} -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 - } +```c++ +vector> zigzagLevelOrder(TreeNode* root) { + vector> ret; + if (!root) { + return ret; + } + queue queue; + queue.push(root); + auto l2r = true; + while (!queue.empty()) { + vector levelValues; + for (auto levelNodeNum = queue.size(); levelNodeNum > 0; --levelNodeNum) { + auto node = queue.front(); + queue.pop(); + levelValues.push_back(node->val); + if (node->left) { + queue.push(node->left); + } + if (node->right) { + queue.push(node->right); + } + } + if (l2r) { + ret.emplace_back(levelValues); + } else { + ret.emplace_back( + levelValues.rbegin(), + levelValues.rend() + ); + } + l2r = !l2r; + } + return ret; } ``` @@ -659,85 +586,72 @@ func reverse(nums []int) { 思路 2:分治法,判断左 MAX < 根 < 右 MIN -```go +```c++ // v1 -func isValidBST(root *TreeNode) bool { - result := make([]int, 0) - inOrder(root, &result) - // check order - for i := 0; i < len(result) - 1; i++{ - if result[i] >= result[i+1] { - return false +bool isValidBST(TreeNode *root) { + if (!root) { + return true; + } + // 思路 1:中序遍历,检查结果列表是否已经有序 + auto inOrderValues = vector(); + inOrder(root, inOrderValues); + for (auto iter = inOrderValues.cbegin() + 1; iter != inOrderValues.cend(); ++iter) { + if (*(iter - 1) >= *iter) { + return false; } } - return true + return true; } -func inOrder(root *TreeNode, result *[]int) { - if root == nil{ - return +void inOrder(TreeNode *root, vector &values) { + if (!root) { + return; } - inOrder(root.Left, result) - *result = append(*result, root.Val) - inOrder(root.Right, result) + inOrder(root->left, values); + values.push_back(root->val); + inOrder(root->right, values); } - - ``` -```go +```c++ // v2分治法 -type ResultType struct { - IsValid bool - // 记录左右两边最大最小值,和根节点进行比较 - Max *TreeNode - Min *TreeNode -} - -func isValidBST2(root *TreeNode) bool { - 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 - // 如果左边还有更小的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 +struct Result { + TreeNode *maxNode; + TreeNode *minNode; + bool isValidate; + + Result(bool validate = true, TreeNode *max = nullptr, TreeNode *min = nullptr) + : isValidate(validate), maxNode(max), minNode(min) { + + } +}; +bool isValidBST(TreeNode *root) { + if (!root) { + return true; + } + return helper(root).isValidate; +} + +Result helper(TreeNode *root) { + if (!root) { + return {}; + } + auto left = helper(root->left); + auto right = helper(root->right); + if (!(left.isValidate && right.isValidate)) { + return {false}; + } + if (left.maxNode && left.maxNode->val >= root->val) { + return {false}; + } + if (right.minNode && right.minNode->val <= root->val) { + return {false}; + } + return { + true, + right.maxNode ? right.maxNode : root, + left.minNode ? left.minNode : root, + }; } ``` @@ -749,19 +663,20 @@ func helper(root *TreeNode) ResultType { 思路:找到最后一个叶子节点满足插入条件即可 -```go +```c++ // DFS查找插入位置 -func insertIntoBST(root *TreeNode, val int) *TreeNode { - if root == nil { - root = &TreeNode{Val: val} - return root +TreeNode* insertIntoBST(TreeNode* root, int val) { + if (!root) { + root = new TreeNode(val); + return root; } - if root.Val > val { - root.Left = insertIntoBST(root.Left, val) + + if (root->val > val) { + root->left = insertIntoBST(root->left, val); } else { - root.Right = insertIntoBST(root.Right, val) + root->right = insertIntoBST(root->right, val); } - return root + return root; } ``` diff --git a/data_structure/linked_list.md b/data_structure/linked_list.md index 14585e91..8c1b3eb3 100644 --- a/data_structure/linked_list.md +++ b/data_structure/linked_list.md @@ -19,17 +19,16 @@ > 给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。 -```go -func deleteDuplicates(head *ListNode) *ListNode { - current := head - for current != nil { - // 全部删除完再移动到下一个元素 - for current.Next != nil && current.Val == current.Next.Val { - current.Next = current.Next.Next - } - current = current.Next - } - return head +```c++ +ListNode* deleteDuplicates(ListNode* head) { + auto current = head; + while (current) { + while (current->next && current->next->val == current->val) { + current->next = current->next->next; + } + current = current->next; + } + return head; } ``` @@ -39,28 +38,30 @@ func deleteDuplicates(head *ListNode) *ListNode { 思路:链表头结点可能被删除,所以用 dummy node 辅助删除 -```go -func deleteDuplicates(head *ListNode) *ListNode { - if head == nil { - return head - } - dummy := &ListNode{Val: 0} - dummy.Next = head - head = dummy - - var rmVal int - for head.Next != nil && head.Next.Next != nil { - if head.Next.Val == head.Next.Next.Val { - // 记录已经删除的值,用于后续节点判断 - rmVal = head.Next.Val - for head.Next != nil && head.Next.Val == rmVal { - head.Next = head.Next.Next - } +```c++ +ListNode* deleteDuplicates(ListNode* head) { + + if (!head) { + return head; + } + + auto dummy = ListNode(0); + dummy.next = head; + head = &dummy; + + int repeatVal; + while (head->next && head->next->next) { + if (head->next->val != head->next->next->val) { + head = head->next; } else { - head = head.Next + repeatVal = head->next->val; + while (head->next && head->next->val == repeatVal) { + head->next = head->next->next; + } } } - return dummy.Next + + return dummy.next; } ``` @@ -75,21 +76,17 @@ func deleteDuplicates(head *ListNode) *ListNode { 思路:用一个 prev 节点保存向前指针,temp 保存向后的临时指针 -```go -func reverseList(head *ListNode) *ListNode { - var prev *ListNode - for head != nil { - // 保存当前head.Next节点,防止重新赋值后被覆盖 - // 一轮之后状态:nil<-1 2->3->4 - // prev head - temp := head.Next - head.Next = prev - // pre 移动 - prev = head - // head 移动 - head = temp - } - return prev +```c++ +ListNode* reverseList(ListNode* head) { + ListNode *prev = nullptr; + ListNode *next = nullptr; + while (head) { + next = head->next; + head->next = prev; + prev = head; + head = next; + } + return prev; } ``` @@ -99,44 +96,34 @@ func reverseList(head *ListNode) *ListNode { 思路:先遍历到 m 处,翻转,再拼接后续,注意指针处理 -```go -func reverseBetween(head *ListNode, m int, n int) *ListNode { - // 思路:先遍历到m处,翻转,再拼接后续,注意指针处理 - // 输入: 1->2->3->4->5->NULL, m = 2, n = 4 - if head == nil { - return head - } - // 头部变化所以使用dummy node - dummy := &ListNode{Val: 0} - dummy.Next = head - head = dummy - // 最开始:0->1->2->3->4->5->nil - var pre *ListNode - var i = 0 - for i < m { - pre = head - head = head.Next - i++ - } - // 遍历之后: 1(pre)->2(head)->3->4->5->NULL - // i = 1 - var j = i - var next *ListNode - // 用于中间节点连接 - var mid = head - for head != nil && j <= n { - // 第一次循环: 1 nil<-2 3->4->5->nil - temp := head.Next - head.Next = next - next = head - head = temp - j++ - } - // 循环需要执行四次 - // 循环结束:1 nil<-2<-3<-4 5(head)->nil - pre.Next = next - mid.Next = head - return dummy.Next +```c++ +ListNode* reverseBetween(ListNode* head, int m, int n) { + if (!head) { + return nullptr; + } + auto dummy = ListNode(0); + dummy.next = head; + head = &dummy; + + ListNode *prev = nullptr; + auto index = 0; + for (; index < m; ++index) { + prev = head; + head = head->next; + } + + auto rend = head; + ListNode *newNext = nullptr; + for (; index <= n; ++index) { + auto nextNode = head->next; + head->next = newNext; + newNext = head; + head = nextNode; + } + prev->next = newNext; + rend->next = head; + + return dummy.next; } ``` @@ -146,33 +133,42 @@ func reverseBetween(head *ListNode, m int, n int) *ListNode { 思路:通过 dummy node 链表,连接各个元素 -```go -func mergeTwoLists(l1 *ListNode, l2 *ListNode) *ListNode { - dummy := &ListNode{Val: 0} - head := dummy - for l1 != nil && l2 != nil { - if l1.Val < l2.Val { - head.Next = l1 - l1 = l1.Next +```c++ +// 递归实现 +ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) { + if (!l1 || !l2) { + return l1 ? l1 : l2; + } + ListNode *ret; + if (l1->val < l2->val) { + ret = l1; + ret->next = mergeTwoLists(l1->next, l2); + } else { + ret = l2; + ret->next = mergeTwoLists(l1, l2->next); + } + return ret; +} + +// dummy node实现 +ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) { + if (!l1 || !l2) { + return l1 ? l1 : l2; + } + auto dummy = ListNode(0); + ListNode *iter = &dummy; + while (l1 && l2) { + if (l1->val < l2->val) { + iter->next = l1; + l1 = l1->next; } else { - head.Next = l2 - l2 = l2.Next + iter->next = l2; + l2 = l2->next; } - head = head.Next + iter = iter->next; } - // 连接l1 未处理完节点 - for l1 != nil { - head.Next = l1 - head = head.Next - l1 = l1.Next - } - // 连接l2 未处理完节点 - for l2 != nil { - head.Next = l2 - head = head.Next - l2 = l2.Next - } - return dummy.Next + iter->next = l1 ? l1 : l2; + return dummy.next; } ``` @@ -182,34 +178,29 @@ func mergeTwoLists(l1 *ListNode, l2 *ListNode) *ListNode { 思路:将大于 x 的节点,放到另外一个链表,最后连接这两个链表 -```go -func partition(head *ListNode, x int) *ListNode { - // 思路:将大于x的节点,放到另外一个链表,最后连接这两个链表 - // check - if head == nil { - return head - } - headDummy := &ListNode{Val: 0} - tailDummy := &ListNode{Val: 0} - tail := tailDummy - headDummy.Next = head - head = headDummy - for head.Next != nil { - if head.Next.Val < x { - head = head.Next - } else { - // 移除next) { + if (head->next->val < x) { + head = head->next; + continue; } + tail->next = head->next; + tail = tail->next; + head->next = head->next->next; } - // 拼接两个链表 - tail.Next = nil - head.Next = tailDummy.Next - return headDummy.Next + tail->next = nullptr; + head->next = greaterList.next; + return dummy.next; } ``` @@ -223,63 +214,53 @@ func partition(head *ListNode, x int) *ListNode { 思路:归并排序,找中点和合并操作 -```go -func sortList(head *ListNode) *ListNode { - // 思路:归并排序,找中点和合并操作 - return mergeSort(head) +```c++ +ListNode* sortList(ListNode* head) { + return mergeSort(head); } -func findMiddle(head *ListNode) *ListNode { - // 1->2->3->4->5 - slow := head - fast := head.Next - // 快指针先为nil - for fast !=nil && fast.Next != nil { - fast = fast.Next.Next - slow = slow.Next - } - return slow + +ListNode* mergeSort(ListNode *head) { + if (!head || !head->next) { + return head; + } + auto middle = findMiddle(head); + auto left = head; + auto right = middle->next; + middle->next = nullptr; + left = mergeSort(left); + right = mergeSort(right); + return mergeTwoLists(left, right); } -func mergeTwoLists(l1 *ListNode, l2 *ListNode) *ListNode { - dummy := &ListNode{Val: 0} - head := dummy - for l1 != nil && l2 != nil { - if l1.Val < l2.Val { - head.Next = l1 - l1 = l1.Next - } else { - head.Next = l2 - l2 = l2.Next - } - head = head.Next + +ListNode* findMiddle(ListNode *node) { + auto slow = node; + auto fast = node->next; + while (fast && fast->next) { + slow = slow->next; + fast = fast->next->next; } - // 连接l1 未处理完节点 - for l1 != nil { - head.Next = l1 - head = head.Next - l1 = l1.Next + return slow; +} + +ListNode *mergeTwoLists(ListNode *l1, ListNode *l2) { + if (!l1 || !l2) { + return l1 ? l1 : l2; } - // 连接l2 未处理完节点 - for l2 != nil { - head.Next = l2 - head = head.Next - l2 = l2.Next + auto dummy = ListNode(0); + ListNode *iter = &dummy; + while (l1 && l2) { + if (l1->val < l2->val) { + iter->next = l1; + l1 = l1->next; + } else { + iter->next = l2; + l2 = l2->next; + } + iter = iter->next; } - return dummy.Next -} -func mergeSort(head *ListNode) *ListNode { - // 如果只有一个节点 直接就返回这个节点 - if head == nil || head.Next == nil{ - return head - } - // find middle - middle := findMiddle(head) - // 断开中间节点 - tail := middle.Next - middle.Next = nil - left := mergeSort(head) - right := mergeSort(tail) - result := mergeTwoLists(left, right) - return result + // 这里直接接上即可,不用一个个拼接 + iter->next = l1 ? l1 : l2; + return dummy.next; } ``` @@ -296,70 +277,54 @@ func mergeSort(head *ListNode) *ListNode { 思路:找到中点断开,翻转后面部分,然后合并前后两个链表 -```go -func reorderList(head *ListNode) { - // 思路:找到中点断开,翻转后面部分,然后合并前后两个链表 - if head == nil { - return +```c++ +/* + * 给定一个单链表 L:L0→L1→…→Ln-1→Ln , + * 将其重新排列后变为: L0→Ln→L1→Ln-1→L2→Ln-2→… + */ +void reorderList(ListNode* head) { + if (!head) { + return; + } + auto middle = findMiddle(head); + auto left = head; + auto right = middle->next; + middle->next = nullptr; + right = revertList(right); + while (left && right) { + auto nextLeft = left->next; + auto nextRight = right->next; + left->next = right; + right->next = nextLeft; + left = nextLeft; + right = nextRight; } - mid := findMiddle(head) - tail := reverseList(mid.Next) - mid.Next = nil - head = mergeTwoLists(head, tail) -} -func findMiddle(head *ListNode) *ListNode { - fast := head.Next - slow := head - for fast != nil && fast.Next != nil { - fast = fast.Next.Next - slow = slow.Next - } - return slow } -func mergeTwoLists(l1 *ListNode, l2 *ListNode) *ListNode { - dummy := &ListNode{Val: 0} - head := dummy - toggle := true - for l1 != nil && l2 != nil { - // 节点切换 - if toggle { - head.Next = l1 - l1 = l1.Next - } else { - head.Next = l2 - l2 = l2.Next - } - toggle = !toggle - head = head.Next - } - // 连接l1 未处理完节点 - for l1 != nil { - head.Next = l1 - head = head.Next - l1 = l1.Next - } - // 连接l2 未处理完节点 - for l2 != nil { - head.Next = l2 - head = head.Next - l2 = l2.Next - } - return dummy.Next + +ListNode* revertList(ListNode *head) { + if (!head) { + return head; + } + auto prev = head; + head = head->next; + prev->next = nullptr; + while (head) { + auto next = head->next; + head->next = prev; + prev = head; + head = next; + } + return prev; } -func reverseList(head *ListNode) *ListNode { - var prev *ListNode - for head != nil { - // 保存当前head.Next节点,防止重新赋值后被覆盖 - // 一轮之后状态:nil<-1 2->3->4 - // prev head - temp := head.Next - head.Next = prev - // pre 移动 - prev = head - // head 移动 - head = temp - } - return prev + +ListNode* findMiddle(ListNode *node) { + auto slow = node; + auto fast = node->next; + while (fast && fast->next) { + slow = slow->next; + fast = fast->next->next; + } + return slow; } ``` @@ -370,23 +335,21 @@ func reverseList(head *ListNode) *ListNode { 思路:快慢指针,快慢指针相同则有环,证明:如果有环每走一步快慢指针距离会减 1 ![fast_slow_linked_list](https://img.fuiboom.com/img/fast_slow_linked_list.png) -```go -func hasCycle(head *ListNode) bool { - // 思路:快慢指针 快慢指针相同则有环,证明:如果有环每走一步快慢指针距离会减1 - if head == nil { - return false - } - fast := head.Next - slow := head - for fast != nil && fast.Next != nil { - // 比较指针是否相等(不要使用val比较!) - if fast == slow { - return true +```c++ +bool hasCycle(ListNode *head) { + if (!head) { + return false; + } + auto slow = head; + auto fast = head->next; + while (fast && fast->next) { + if (fast == slow) { + return true; } - fast = fast.Next.Next - slow = slow.Next + slow = slow->next; + fast = fast->next->next; } - return false + return false; } ``` @@ -397,31 +360,40 @@ func hasCycle(head *ListNode) bool { 思路:快慢指针,快慢相遇之后,慢指针回到头,快慢指针步调一致一起移动,相遇点即为入环点 ![cycled_linked_list](https://img.fuiboom.com/img/cycled_linked_list.png) -```go -func detectCycle(head *ListNode) *ListNode { - // 思路:快慢指针,快慢相遇之后,慢指针回到头,快慢指针步调一致一起移动,相遇点即为入环点 - if head == nil { - return head - } - fast := head.Next - slow := head - - for fast != nil && fast.Next != nil { - if fast == slow { - // 慢指针重新从头开始移动,快指针从第一次相交点下一个节点开始移动 - fast = head - slow = slow.Next // 注意 - // 比较指针对象(不要比对指针Val值) - for fast != slow { - fast = fast.Next - slow = slow.Next +```c++ +/* + * 思路: + * 设: 入环前的长度为x,相遇位置离入环位置为y,离"终点"距离为z,链表节点数为n,环的长度为c,相遇时已经绕环k次 + * n = x + y + z + * + * 相遇时 + * slow = x + y + * fast = 2(x + y) + 1 = n + y + kc + * => 2x + y + 1 = x + y + z + kc + * => x + 1 = z + kc + * => x = z - 1 + kc + * 所以除了从头走x外,还需要另一边先往前走一格 + */ +ListNode *detectCycle(ListNode *head) { + if (!head) { + return nullptr; + } + auto slow = head; + auto fast = head->next; + while (fast && fast->next) { + if (fast == slow) { + fast = head; + slow = slow->next; + while (fast != slow) { + slow = slow->next; + fast = fast->next; } - return slow + return slow; } - fast = fast.Next.Next - slow = slow.Next + fast = fast->next->next; + slow = slow->next; } - return nil + return nullptr; } ``` @@ -432,30 +404,41 @@ func detectCycle(head *ListNode) *ListNode { 另外一种方式是 fast=head,slow=head -```go -func detectCycle(head *ListNode) *ListNode { - // 思路:快慢指针,快慢相遇之后,其中一个指针回到头,快慢指针步调一致一起移动,相遇点即为入环点 - // nb+a=2nb+a - if head == nil { - return head - } - fast := head - slow := head - - for fast != nil && fast.Next != nil { - fast = fast.Next.Next - slow = slow.Next - if fast == slow { - // 指针重新从头开始移动 - fast = head - for fast != slow { - fast = fast.Next - slow = slow.Next +```c++ +/* + * 思路: + * 设: 入环前的长度为x,相遇位置离入环位置为y,离"终点"距离为z,链表节点数为n,环的长度为c,相遇时已经绕环k次 + * n = x + y + z + * + * 第一次相遇时 + * slow = x + y + * fast = 2(x + y) = n + y + kc + * => 2x + y = x + y + z + kc + * => x = z + kc + * 又,相遇位置离终点(也是入环位置)为z,z + kc还是入环位置; + * 起点到入环位置也为x + * + * 所以,把slow丢回起点,fast速度降为1继续走,必定在slow走到入环位置时与fast相遇 + */ +ListNode *detectCycle(ListNode *head) { + if (!head) { + return nullptr; + } + auto slow = head; + auto fast = head; + while (fast && fast->next) { + slow = slow->next; + fast = fast->next->next; + if (fast == slow) { + slow = head; + while (fast != slow) { + slow = slow->next; + fast = fast->next; } - return slow + return slow; } } - return nil + return nullptr; } ``` @@ -468,50 +451,47 @@ func detectCycle(head *ListNode) *ListNode { > 请判断一个链表是否为回文链表。 -```go -func isPalindrome(head *ListNode) bool { - // 1 2 nil - // 1 2 1 nil - // 1 2 2 1 nil - if head==nil{ - return true - } - slow:=head - // fast如果初始化为head.Next则中点在slow.Next - // fast初始化为head,则中点在slow - fast:=head.Next - for fast!=nil&&fast.Next!=nil{ - fast=fast.Next.Next - slow=slow.Next - } - - tail:=reverse(slow.Next) - // 断开两个链表(需要用到中点前一个节点) - slow.Next=nil - for head!=nil&&tail!=nil{ - if head.Val!=tail.Val{ - return false - } - head=head.Next - tail=tail.Next +```c++ +bool isPalindrome(ListNode* head) { + if (!head) { + return true; } - return true + /* + * fast 如果初始化为 head.Next 则中点在 slow.Next + * fast 初始化为 head,则中点在 slow + */ + auto slow = head; + auto fast = head; + while (fast && fast->next) { + slow = slow->next; + fast = fast->next->next; + } + + auto half = reverse(slow); + slow->next = nullptr; + while (head && half) { + if (head->val != half->val) { + return false; + } + head = head->next; + half = half->next; + } + return true; } -func reverse(head *ListNode)*ListNode{ - // 1->2->3 - if head==nil{ - return head +ListNode* reverse(ListNode *head) { + if (!head) { + return head; } - var prev *ListNode - for head!=nil{ - t:=head.Next - head.Next=prev - prev=head - head=t + ListNode *prev = nullptr; + while (head) { + auto next = head->next; + head->next = prev; + prev = head; + head = next; } - return prev + return prev; } ``` @@ -522,39 +502,40 @@ func reverse(head *ListNode)*ListNode{ 思路: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 +```c++ +Node* copyRandomList(Node* head) { + if (!head) { + return head; + } + + // clone node + auto iter = head; + while (iter) { + auto cloned = new Node(iter->val); + cloned->next = iter->next; + cloned->random = iter->random; + iter->next = cloned; + iter = cloned->next; + } + + // update random + iter = head; + while (iter) { + if (iter->random) { + iter->next->random = iter->random->next; + } + iter = iter->next->next; + } + + // split two list and make sure the original version isn't changed + iter = head; + auto cloneHead = head->next; + while (iter && iter->next) { + auto tmp = iter->next; + iter->next = iter->next->next; + iter = tmp; + } + return cloneHead; } ``` diff --git a/data_structure/stack_queue.md b/data_structure/stack_queue.md index 6f90a3ef..cfcbaf2b 100644 --- a/data_structure/stack_queue.md +++ b/data_structure/stack_queue.md @@ -18,67 +18,47 @@ 思路:用两个栈实现,一个最小栈始终保证最小值在顶部 -```go -type MinStack struct { - min []int - stack []int -} +```c++ +class MinStack { +private: + vector valueStack; + vector minStack; +public: + /** initialize your data structure here. */ + MinStack() { -/** initialize your data structure here. */ -func Constructor() MinStack { - return MinStack{ - min: make([]int, 0), - stack: make([]int, 0), } -} - -func (this *MinStack) Push(x int) { - min := this.GetMin() - if x < min { - this.min = append(this.min, x) - } else { - this.min = append(this.min, min) + void push(int x) { + valueStack.push_back(x); + auto curMin = getMin(); + if (curMin < x) { + minStack.push_back(curMin); + } else { + minStack.push_back(x); + } } - this.stack = append(this.stack, x) -} - -func (this *MinStack) Pop() { - if len(this.stack) == 0 { - return + void pop() { + valueStack.pop_back(); + minStack.pop_back(); } - this.stack = this.stack[:len(this.stack)-1] - this.min = this.min[:len(this.min)-1] -} - -func (this *MinStack) Top() int { - if len(this.stack) == 0 { - return 0 + int top() { + if (valueStack.empty()) { + return 0; + } + return valueStack.back(); } - return this.stack[len(this.stack)-1] -} - -func (this *MinStack) GetMin() int { - if len(this.min) == 0 { - return 1 << 31 + int getMin() { + if (minStack.empty()) { + return numeric_limits::max(); + } + return minStack.back(); } - min := this.min[len(this.min)-1] - return min -} - - -/** - * Your MinStack object will be instantiated and called as such: - * obj := Constructor(); - * obj.Push(x); - * obj.Pop(); - * param_3 := obj.Top(); - * param_4 := obj.GetMin(); - */ +}; ``` [evaluate-reverse-polish-notation](https://leetcode-cn.com/problems/evaluate-reverse-polish-notation/) @@ -88,41 +68,39 @@ func (this *MinStack) GetMin() int { 思路:通过栈保存原来的元素,遇到表达式弹出运算,再推入结果,重复这个过程 -```go -func evalRPN(tokens []string) int { - if len(tokens)==0{ - return 0 - } - stack:=make([]int,0) - for i:=0;i &tokens) { + if (tokens.empty()) { + return 0; + } + auto token = tokens.back(); + tokens.pop_back(); + if (token != "+" && token != "-" && token != "*" && token != "/") { + return atoi(token); + } + auto rhs = evalRPN(tokens); + auto lhs = evalRPN(tokens); + if (token == "+") { + return lhs + rhs; + } else if (token == "-") { + return lhs - rhs; + } else if (token == "*") { + return lhs * rhs; + } else if (token == "/") { + return lhs / rhs; } - return stack[0] + return -1; +} + +int atoi(const string &str) { + if (str[0] == '-') { + return -atoi(str.substr(1)); + } + int ret = 0; + for (const auto &item : str) { + ret = ret * 10 + item - '0'; + } + return ret; } ``` @@ -135,64 +113,77 @@ func evalRPN(tokens []string) int { 思路:通过栈辅助进行操作 -```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++ - } - // 注意索引边界 - 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) +```c++ +string decodeString(string s) { + if (s.empty()) { + return ""; + } + + vector stack; + for (const auto &c : s) { + if (c != ']') { + stack.push_back(c); + continue; + } + vector subStr; + while (stack.back() != '[') { + subStr.push_back(stack.back()); + stack.pop_back(); + } + stack.pop_back(); + + int digitBegin = stack.size() - 1; + for (; digitBegin != 0; --digitBegin) { + auto val = stack[digitBegin]; + if (!('0' <= val && val <= '9')) { + ++digitBegin; + break; + } + } + auto repeat = atoi({ + stack.begin() + digitBegin, + stack.end(), + }); + stack.resize(digitBegin); + for (int i = 0; i < repeat; ++i) { + stack.insert(stack.end(), subStr.rbegin(), subStr.rend()); + } + } + return { + stack.begin(), + stack.end() + }; +} + +int atoi(const string &str) { + if (str.empty()) { + return 0; + } + int value = 0; + for (const auto &c : str) { + value = value * 10 + c - '0'; + } + return value; } ``` 利用栈进行 DFS 递归搜索模板 -```go -boolean DFS(int root, int target) { - Set visited; - Stack s; - add root to s; - while (s is not empty) { - Node cur = the top element in s; +```c++ +bool DFS(Node root, Node target) { + set visited; + stack stack; + stack.push(root); + while (!stack.empty()) { + auto cur = stack.top(); return true if cur is target; - for (Node next : the neighbors of cur) { - if (next is not in visited) { - add next to s; - add next to visited; + for (auto next : the neighbors of cur) { + if (visited.find(target) == visited.end() { + stack.push(next); + visited.insert(target); } } - remove cur from s; + stack.pop(); } return false; } @@ -202,26 +193,26 @@ boolean DFS(int root, int target) { > 给定一个二叉树,返回它的*中序*遍历。 -```go +```c++ // 思路:通过stack 保存已经访问的元素,用于原路返回 -func inorderTraversal(root *TreeNode) []int { - result := make([]int, 0) - if root == nil { - return result - } - stack := make([]*TreeNode, 0) - for len(stack) > 0 || root != nil { - for root != nil { - stack = append(stack, root) - root = root.Left // 一直向左 +vector inorderTraversal(TreeNode* root) { + if (!root) { + return {}; + } + vector ret; + vector stack; + while (root || !stack.empty()) { + while (root) { + // 从最左下节点开始 + stack.push_back(root); + root = root->left; } - // 弹出 - val := stack[len(stack)-1] - stack = stack[:len(stack)-1] - result = append(result, val.Val) - root = val.Right + root = stack.back(); + stack.pop_back(); + ret.push_back(root->val); + root = root->right; } - return result + return ret; } ``` @@ -229,31 +220,25 @@ func inorderTraversal(root *TreeNode) []int { > 给你无向连通图中一个节点的引用,请你返回该图的深拷贝(克隆)。 -```go -func cloneGraph(node *Node) *Node { - visited:=make(map[*Node]*Node) - return clone(node,visited) +```c++ +Node* cloneGraph(Node* node) { + map newNodes; + return clone(node, newNodes); } -// 1 2 -// 4 3 -// 递归克隆,传入已经访问过的元素作为过滤条件 -func clone(node *Node,visited map[*Node]*Node)*Node{ - if node==nil{ - return nil - } - // 已经访问过直接返回 - if v,ok:=visited[node];ok{ - return v - } - newNode:=&Node{ - Val:node.Val, - Neighbors:make([]*Node,len(node.Neighbors)), - } - visited[node]=newNode - for i:=0;i &genNodes) { + if (!node) { + return nullptr; + } + if (genNodes.find(node) != genNodes.end()) { + return genNodes[node]; + } + auto newNode = new Node(node->val); + genNodes[node] = newNode; + for (const auto &neighbor : node->neighbors) { + newNode->neighbors.push_back(clone(neighbor, genNodes)); + } + return newNode; } ``` @@ -263,29 +248,29 @@ func clone(node *Node,visited map[*Node]*Node)*Node{ 思路:通过深度搜索遍历可能性(注意标记已访问元素) -```go - -func numIslands(grid [][]byte) int { - var count int - for i:=0;i=1{ - count++ +```c++ +int numIslands(vector> &grid) { + auto num = 0; + for (int i = 0; i < grid.size(); ++i) { + for (int j = 0; j < grid[i].size(); ++j) { + if (grid[i][j] == '1') { + flowTheIsland(grid, i, j); + ++num; } } } - return count + return num; } -func dfs(grid [][]byte,i,j int)int{ - if i<0||i>=len(grid)||j<0||j>=len(grid[0]){ - return 0 - } - if grid[i][j]=='1'{ - // 标记已经访问过(每一个点只需要访问一次) - grid[i][j]=0 - return dfs(grid,i-1,j)+dfs(grid,i,j-1)+dfs(grid,i+1,j)+dfs(grid,i,j+1)+1 + +void flowTheIsland(vector> &grid, int i, int j) { + if (i < 0 || i >= grid.size() || j < 0 || j >= grid[i].size() || grid[i][j] == '0') { + return; } - return 0 + grid[i][j] = '0'; + flowTheIsland(grid, i - 1, j); + flowTheIsland(grid, i + 1, j); + flowTheIsland(grid, i, j + 1); + flowTheIsland(grid, i, j - 1); } ``` @@ -302,44 +287,115 @@ func dfs(grid [][]byte,i,j int)int{ ![image.png](https://img.fuiboom.com/img/stack_rain2.png) -```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] - } - // 当前高度小于栈,则将栈内元素都弹出计算面积 - 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) - } - // 记录索引即可获取对应元素 - stack = append(stack, i) - } - return max +```c++ +#pragma region 暴力解法, 时间复杂度O(n^2)不满足 +int largestRectangleArea0(vector &heights) { + auto largest = 0; + for (int i = 0; i < heights.size(); ++i) { + auto area = calcArea(heights, i); + if (area > largest) { + largest = area; + } + } + return largest; +} + +int calcArea(const vector &heights, int col) { + auto height = heights[col]; + auto begin = col; + while (begin > 0) { + if (heights[begin - 1] >= height) { + --begin; + } else { + break; + } + } + auto end = col; + while (end < heights.size() - 1) { + if (heights[end + 1] >= height) { + ++end; + } else { + break; + } + } + return height * (end - begin + 1); } -func Max(a, b int) int { - if a > b { - return a - } - return b +#pragma endregion 暴力解法 + +#pragma region 单调栈 + 哨兵 +/* 若当前柱子的高度小于栈中元素,则弹出所有比它高的,并将当前柱子入栈 + * 因为会被最低的挡住 + * 结果就是栈中元素保持单调递增 + * 一次循环保存结果避免嵌套循环么 + * + * 使用“哨兵”来为左右边界占位,避免特殊处理 + * + * 注意事项: + * 判断的时候要使用>=,弹出一样高度的柱子,以获得正确的边界! + */ +int largestRectangleArea1(vector &heights) { + auto size = heights.size(); + vector left(size), right(size); + + stack monoStack; + for (int i = 0; i < size; ++i) { + while (!monoStack.empty() && heights[monoStack.top()] >= heights[i]) { + monoStack.pop(); + } + left[i] = monoStack.empty() ? -1 : monoStack.top(); + monoStack.push(i); + } + + monoStack = {}; + for (int i = size - 1; i >= 0; --i) { + while (!monoStack.empty() && heights[monoStack.top()] >= heights[i]) { + monoStack.pop(); + } + right[i] = monoStack.empty() ? size : monoStack.top(); + monoStack.push(i); + } + + auto largest = 0; + for (int i = 0; i < size; ++i) { + auto area = (right[i] - left[i] - 1) * heights[i]; + largest = largest > area ? largest : area; + } + return largest; +} +#pragma endregion + +#pragma region 单调栈 + 常数优化 +/* + * 少一次循环 + * + * 在从左往右遍历时 + * 入栈 = 左边界 + * 出栈 = “右边界” + * 出栈,是>=,不是>,得到的右边界不准确? + * 没关系,对于同高度的区域,最右边的柱子能够得到准确的高度!以它为准即可 + */ +int largestRectangleArea(vector &heights) { + auto size = heights.size(); + vector left(size), right(size, size); + + stack monoStack; + for (int i = 0; i < size; ++i) { + while (!monoStack.empty() && heights[monoStack.top()] >= heights[i]) { + right[monoStack.top()] = i; + monoStack.pop(); + } + left[i] = monoStack.empty() ? -1 : monoStack.top(); + monoStack.push(i); + } + + auto largest = 0; + for (int i = 0; i < size; ++i) { + auto area = (right[i] - left[i] - 1) * heights[i]; + largest = largest > area ? largest : area; + } + return largest; } +#pragma endregion ``` ## Queue 队列 @@ -350,109 +406,72 @@ func Max(a, b int) int { > 使用栈实现队列 -```go -type MyQueue struct { - stack []int - back []int -} +```c++ +class MyQueue { +private: + vector stack1; + vector stack2; +public: + /** Initialize your data structure here. */ + MyQueue() { -/** Initialize your data structure here. */ -func Constructor() MyQueue { - return MyQueue{ - stack: make([]int, 0), - back: make([]int, 0), } -} -// 1 -// 3 -// 5 - -/** Push element x to the back of queue. */ -func (this *MyQueue) Push(x int) { - for len(this.back) != 0 { - val := this.back[len(this.back)-1] - this.back = this.back[:len(this.back)-1] - this.stack = append(this.stack, val) + /** Push element x to the back of queue. */ + void push(int x) { + stack1.push_back(x); } - this.stack = append(this.stack, x) -} -/** Removes the element from in front of queue and returns that element. */ -func (this *MyQueue) Pop() int { - for len(this.stack) != 0 { - val := this.stack[len(this.stack)-1] - this.stack = this.stack[:len(this.stack)-1] - this.back = append(this.back, val) - } - if len(this.back) == 0 { - return 0 + /** Removes the element from in front of queue and returns that element. */ + int pop() { + auto ret = peek(); + stack2.pop_back(); + return ret; } - val := this.back[len(this.back)-1] - this.back = this.back[:len(this.back)-1] - return val -} -/** Get the front element. */ -func (this *MyQueue) Peek() int { - for len(this.stack) != 0 { - val := this.stack[len(this.stack)-1] - this.stack = this.stack[:len(this.stack)-1] - this.back = append(this.back, val) - } - if len(this.back) == 0 { - return 0 + /** Get the front element. */ + int peek() { + if (!stack2.empty()) { + return stack2.back(); + } + stack2.insert(stack2.end(), stack1.rbegin(), stack1.rend()); + stack1.clear(); + return peek(); } - val := this.back[len(this.back)-1] - return val -} -/** Returns whether the queue is empty. */ -func (this *MyQueue) Empty() bool { - return len(this.stack) == 0 && len(this.back) == 0 -} - -/** - * Your MyQueue object will be instantiated and called as such: - * obj := Constructor(); - * obj.Push(x); - * param_2 := obj.Pop(); - * param_3 := obj.Peek(); - * param_4 := obj.Empty(); - */ + /** Returns whether the queue is empty. */ + bool empty() { + return stack1.empty() && stack2.empty(); + } +}; ``` 二叉树层次遍历 -```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) - // 为什么要取length? - // 记录当前层有多少元素(遍历当前层,再添加下一层) - 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) +```c++ +vector> levelOrder(TreeNode *root) { + vector> ret; + if (!root) { + return ret; + } + queue queue; + queue.push(root); + while (!queue.empty()) { + vector levelValues; + for (auto levelNodeNum = queue.size(); levelNodeNum > 0; --levelNodeNum) { + auto node = queue.front(); + queue.pop(); + levelValues.push_back(node->val); + if (node->left) { + queue.push(node->left); } - if level.Right != nil { - queue = append(queue, level.Right) + if (node->right) { + queue.push(node->right); } } - result = append(result, list) + ret.push_back(levelValues); } - return result + return ret; } ``` @@ -461,52 +480,49 @@ func levelOrder(root *TreeNode) [][]int { > 给定一个由 0 和 1 组成的矩阵,找出每个元素到最近的 0 的距离。 > 两个相邻元素间的距离为 1 -```go -// BFS 从0进队列,弹出之后计算上下左右的结果,将上下左右重新进队列进行二层操作 -// 0 0 0 0 -// 0 x 0 0 -// x x x 0 -// 0 x 0 0 - -// 0 0 0 0 -// 0 1 0 0 -// 1 x 1 0 -// 0 1 0 0 - -// 0 0 0 0 -// 0 1 0 0 -// 1 2 1 0 -// 0 1 0 0 -func updateMatrix(matrix [][]int) [][]int { - q:=make([][]int,0) - for i:=0;i> updateMatrix(vector>& matrix) { + int row = matrix.size(), column = matrix[0].size(); + vector> dist(row, vector(column)); + vector> seen(row, vector(column)); + + queue> q; + for (int i = 0; i < row; ++i) { + for (int j = 0; j < column; ++j) { + if (matrix[i][j] == 0) { + q.emplace(i, j); + seen[i][j] = 1; } } } - directions:=[][]int{{0,1},{0,-1},{-1,0},{1,0}} - for len(q)!=0{ - // 出队列 - point:=q[0] - q=q[1:] - for _,v:=range directions{ - x:=point[0]+v[0] - y:=point[1]+v[1] - if x>=0&&x=0&&y> +void backtrack(选择列表,路径): if 满足结束条件: - result.add(路径) + result.emplace_back(路径) return for 选择 in 选择列表: 做选择 @@ -68,30 +77,25 @@ func backtrack(选择列表,路径): 答案代码 -```go -func subsets(nums []int) [][]int { - // 保存最终结果 - result := make([][]int, 0) - // 保存中间结果 - list := make([]int, 0) - backtrack(nums, 0, list, &result) - return result +```c++ +vector> subsets(vector &nums) { + vector> result; + vector track; + backtrack(nums, 0, track, result); + return result; } -// nums 给定的集合 -// pos 下次添加到集合中的元素位置索引 -// 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] +void backtrack(const vector &nums, int pos, vector &track, vector> &result) { + // 插入当前组合 + result.push_back(track); + for (int i = pos; i < nums.size(); ++i) { + // 每个值,都有两种可能,包含和不包含 + // 先压入,包含 + track.push_back(nums[i]); + // 递归处理包含当前值的情况 + backtrack(nums, i + 1, track, result); + // 弹出,向后遍历,处理不包含的情况 + track.pop_back(); } } ``` diff --git a/punch_in/learned_along_the_journey.md b/punch_in/learned_along_the_journey.md new file mode 100644 index 00000000..60b2f22c --- /dev/null +++ b/punch_in/learned_along_the_journey.md @@ -0,0 +1,55 @@ +[toc] +## 关于算法 +### 二进制有点难 +### 滑动窗口 +### 动态规划 +### 二分查找 +### 回溯法 + + +## C++相关 +### 注意边界 +太多次越界了,很多时候CLion并不会报错~~难道是我漏看了exit code?~~ +一提交运行直接崩了 + +### 初始化列表的使用要小心 +```c++ +vector ret(n + 1, 1); // 初始化为n+1个1 +vector ret{n + 1, 1}; // 初始化为n + 1, 1两个元素! +``` + +### 普通数组也可以使用range for进行遍历 + +### 越界? +mid * mid < x越界 +那就改写成mid < x / mid + +### push_back vs emplace_back +[C++ difference between emplace_back and push_back function +](http://candcplusplus.com/c-difference-between-emplace_back-and-push_back-function) +1. 如果对象的构造函数参数不止一个,push_back只能接受对象实例;emplace_back可以接受构造函数的参数! +push_back只能接受对象实例或者单参数版本的构造函数的参数(通过隐式类型转换,如果声明为explicit也不行 +而emplace_back可以接受多个构造函数参数 + +2. 性能 +对于内置类型没区别,对于自定义类型,emplace_back性能更好 +如果传入的是对象实例则没区别,如果传入的是构造对象参数 +* push_back +如果直接传入构造函数参数,则通过隐式类型转换创建临时对象 + 1. 调用构造函数创建临时对象 + 2. 在vector中创建一个临时对象的拷贝 + 3. 销毁临时对象 +* emplace_back +不会创建临时对象,而是直接在vector中创建对象。避免了创建不必要的临时对象 + +### decomposition declaration +C++17支持如下语法: +```c++ +queue> queue; +auto [i, j] = queue.front(); +``` + +## JetBrians产品中关于GitHub的fork、pull request使用 +[官方文档](https://www.jetbrains.com/help/idea/contribute-to-projects.html) +需要先**VCS | Git | Rebase my GitHub fork** +之后就可以看到upstream。日志、pull、Create Pull Request、View Pull Request都可以看得到upstream \ No newline at end of file