diff --git a/TODO.md b/TODO.md index 044090f8..1c785541 100644 --- a/TODO.md +++ b/TODO.md @@ -3,6 +3,7 @@ ## v1 - [ ] 完善文档细节 +- [ ] python和C++实现 - [ ] 工程实现用到的算法解析 - [ ] 周赛计划 - [ ] 面试体系计划 diff --git a/advanced_algorithm/backtrack.md b/advanced_algorithm/backtrack.md index bd923e61..6988b013 100644 --- a/advanced_algorithm/backtrack.md +++ b/advanced_algorithm/backtrack.md @@ -26,76 +26,112 @@ func backtrack(选择列表,路径): > 给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。 +> 第一种是回溯法深度遍历 +> 第二种是队列广度优先遍历 + + 遍历过程 ![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 +```cpp +vector> subsets(vector& nums) { + if(nums.empty()) return {}; + //保存最终结果 + vector> res; + vector list; + backtrack(nums, 0, list, res); + return res; } - // nums 给定的集合 // pos 下次添加到集合中的元素位置索引 // list 临时结果集合(每次需要复制保存) // result 最终结果 -func backtrack(nums []int, pos int, list []int, result *[][]int) { +void backtrack(vector& nums, int idx, vector list, vector>& res){ // 把临时结果复制出来保存到最终结果 - ans := make([]int, len(list)) - copy(ans, list) - *result = append(*result, ans) + res.push_back(list); // 选择、处理结果、再撤销选择 - for i := pos; i < len(nums); i++ { - list = append(list, nums[i]) - backtrack(nums, i+1, list, result) - list = list[0 : len(list)-1] + for(int i = idx; i < nums.size(); i++){ + list.push_back(nums[i]); + backtrack(nums, i + 1, list, res); + list.pop_back(); //回溯 } } ``` +```cpp +vector> subsets(vector& nums) { + int n = nums.size(); + vector> res; + res.push_back({}); //先插入空集 + + //广度遍历,插入数字 + for(int i = 0; i < n; i++){ + int num = nums[i]; + int k = res.size(); + for(int j = 0; j < k; j++){ + vector tmp = res[j]; + tmp.push_back(num); + res.push_back(tmp); + } + } + + return res; +} +``` + ### [subsets-ii](https://leetcode-cn.com/problems/subsets-ii/) > 给定一个可能包含重复元素的整数数组 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 +```cpp +vector> subsetsWithDup(vector& nums) { + if(nums.empty()) return {}; + sort(nums.begin(), nums.end()); //让重复元素紧挨着 + + vector> res; + res.push_back({}); + + int last = res.size(); + for(int i = 0; i < nums.size(); i++){ + int n = res.size(); + int low = 0; + if(i > 0 && nums[i] == nums[i-1]) low = last; + + for(int j = low; j < n; j++){ + vector tmp = res[j]; + tmp.push_back(nums[i]); + res.push_back(tmp); + } + last = n; + } + + return res; } +``` +```cpp +vector> subsetsWithDup(vector& nums) { + if(nums.empty()) return {}; + sort(nums.begin(), nums.end()); //让重复元素紧挨着 + + vector> res; + vector track; + backtrack(nums, 0, track, res); + return res; +} // 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(vector& nums, int idx, vector track, vector>& res){ + res.push_back(track); +// 选择时需要剪枝、处理、撤销选择 + for(int i = idx; i < nums.size(); i++){ + if(i > idx && nums[i] == nums[i-1]) continue; //剪枝去重 + track.push_back(nums[i]); + backtrack(nums, i + 1, track, res); + track.pop_back(); } } ``` @@ -106,92 +142,137 @@ 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 -} +```cpp +vector> permute(vector& nums) { + int n = nums.size(); + vector> res; + + vector track; + vector visited(n, false); + backtrack(nums, track, visited, res); + return res; +} // 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 - } - 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] - } +void backtrack(vector& nums, vector track, vector& visited, vector>& res){ + // 返回条件:临时结果和输入集合长度一致 才是全排列 + if(track.size() == nums.size()){ + res.push_back(track); + return; + } + for(int i = 0; i < nums.size(); i++){ + if(visited[i]) continue; //移除添加过的元素 + track.push_back(nums[i]); + visited[i] = true; + backtrack(nums, track, visited, res); + visited[i] = false; + track.pop_back(); + } } ``` + +```cpp +vector> permute(vector& nums) { + int n = nums.size(); + vector> res; + if(n <= 1){ + res.push_back(nums); + return res; + } + queue> qe; + qe.push({}); + for(int i = 0; i < nums.size(); i++){ + int m = qe.size(); + for(int k = 0; k < m; k++){ + vector last = qe.front(); + qe.pop(); + for(int j = 0; j <= last.size(); j++){ + vector tmp = last; + tmp.insert(tmp.begin() + j, nums[i]); + if(i < nums.size() - 1) qe.push(tmp); //队列保存上一轮结果 + else res.push_back(tmp); //存到最终结果里 + } + } + } + + return res; +} + +``` + ### [permutations-ii](https://leetcode-cn.com/problems/permutations-ii/) > 给定一个可包含重复数字的序列,返回所有不重复的全排列。 -```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 +```cpp +vector> permuteUnique(vector& nums) { + if(nums.size() < 2) return {nums}; + sort(nums.begin(), nums.end()); + int n = nums.size(); + + vector> res; + vector track; + vector visited(n, false); + backtrack(nums, track, visited, res); + return res; } -// 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) +void backtrack(vector& nums, vector track, vector& visited, vector>& res){ + if(track.size() == nums.size()){ + res.push_back(track); + return; } - 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] + for(int i = 0; i < nums.size(); i++){ + if(visited[i]) continue; + // 上一个元素和当前相同,并且没有访问过就跳过 + if(i > 0 && nums[i] == nums[i-1] && !visited[i-1]) continue; + visited[i] = true; + track.push_back(nums[i]); + backtrack(nums, track, visited, res); + visited[i] = false; + track.pop_back(); + } + +} +``` + +### [ju-zhen-zhong-de-lu-jing-lcof](https://leetcode-cn.com/problems/ju-zhen-zhong-de-lu-jing-lcof/) + +> 请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。 + +```cpp +bool exist(vector>& board, string word) { + for(int i = 0; i < board.size(); i++){ + for(int j = 0; j < board[0].size(); j++){ + if(bfs(board, i, j, word, 0)) return true; + } + } + + return false; +} + +bool bfs(vector>& board, int i, int j, string word, int idx){ + if(idx >= word.length() || i < 0 || i >= board.size() || j < 0 || j >= board[0].size() || + board[i][j] != word[idx]) + return false; + board[i][j] = '0'; + if(idx == word.length() - 1) return true; + bool flag = bfs(board, i-1, j, word, idx+1) || + bfs(board, i+1, j, word, idx+1) || + bfs(board, i, j-1, word, idx+1) || + bfs(board, i, j+1, word, idx+1); + board[i][j] = word[idx]; //别忘了回溯 + return flag; } ``` + + ## 练习 - [ ] [subsets](https://leetcode-cn.com/problems/subsets/) diff --git a/advanced_algorithm/binary_search_tree.md b/advanced_algorithm/binary_search_tree.md index f1d8aa93..ea4ac878 100644 --- a/advanced_algorithm/binary_search_tree.md +++ b/advanced_algorithm/binary_search_tree.md @@ -10,8 +10,10 @@ [validate-binary-search-tree](https://leetcode-cn.com/problems/validate-binary-search-tree/) > 验证二叉搜索树 +> 一种思路是递归判断左右子树和中间root的值的大小 +> 一种思路是先中序遍历,然后判断遍历数组是否有序 -```go +```cpp /** * Definition for a binary tree node. * type TreeNode struct { @@ -20,64 +22,35 @@ * Right *TreeNode * } */ -func isValidBST(root *TreeNode) bool { - return dfs(root).valid +bool isValidBST(TreeNode* root) { + return validate(root, LONG_MIN, LONG_MAX); //给二叉树补充MIN和MAX } -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 - } - left:=dfs(root.Left) - right:=dfs(root.Right) +bool validate(TreeNode* root, long mi, long ma){ + if(root == nullptr) return true; + if(root->val <= mi || root->val >= ma) return false; - // 1、满足左边最大值left.max && root.Valb{ - return a - } - return b -} -func Min(a,b int)int{ - if a>b{ - return b - } - return a + return validate(root->left, mi, root->val) && validate(root->right, root->val, ma); } - ``` [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} +```cpp +TreeNode* insertIntoBST(TreeNode* root, int val) { + if(root == nullptr) { + root = new TreeNode(val); + return root; } - if root.Valval > val){ + root->left = insertIntoBST(root->left, val); } - return root + else + root->right = insertIntoBST(root->right, val); + + return root; } ``` @@ -85,7 +58,7 @@ func insertIntoBST(root *TreeNode, val int) *TreeNode { > 给定一个二叉搜索树的根节点 root 和一个值 key,删除二叉搜索树中的  key  对应的节点,并保证二叉搜索树的性质不变。返回二叉搜索树(有可能被更新)的根节点的引用。 -```go +```cpp /** * Definition for a binary tree node. * type TreeNode struct { @@ -94,34 +67,35 @@ func insertIntoBST(root *TreeNode, val int) *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 +TreeNode* deleteNode(TreeNode* root, int key) { +// 删除节点分为三种情况: +// 1、只有左节点 替换为右 +// 2、只有右节点 替换为左 +// 3、有左右子节点 左子节点连接到右边最左节点即可 + if(root == nullptr) return root; + + if(root->val < key){ + root->right = deleteNode(root->right, key); + } else if(root->val > key){ + root->left = deleteNode(root->left, key); + } else{ + //分情况讨论 + //只有左节点,替换为右节点 + //只有右节点,替换为左 + //左右节点都存在,将左节点放到右节点的最左边节点上 + if(root->right == nullptr) return root->left; + else if(root->left == nullptr) return root->right; + else{ + TreeNode* cur = root->right; + while(cur->left != nullptr) + cur = cur->left; + + cur->left = root->left; + return root->right; } } - return root + + return root; } ``` @@ -129,42 +103,32 @@ func deleteNode(root *TreeNode, key int) *TreeNode { > 给定一个二叉树,判断它是否是高度平衡的二叉树。 -```go -type ResultType struct{ - height int - valid bool -} -func isBalanced(root *TreeNode) bool { - return dfs(root).valid -} -func dfs(root *TreeNode)(result ResultType){ - if root==nil{ - result.valid=true - result.height=0 - return - } - left:=dfs(root.Left) - right:=dfs(root.Right) - // 满足所有特点:二叉搜索树&&平衡 - if left.valid&&right.valid&&abs(left.height,right.height)<=1{ - result.valid=true - } - result.height=Max(left.height,right.height)+1 - return -} -func abs(a,b int)int{ - if a>b{ - return a-b - } - return b-a +```cpp +/** + * Definition for a binary tree node. + * struct TreeNode { + * int val; + * TreeNode *left; + * TreeNode *right; + * TreeNode(int x) : val(x), left(NULL), right(NULL) {} + * }; + */ + +bool isBalanced(TreeNode* root) { + if(maxDepth(root) == -1) return false; + return true; } -func Max(a,b int)int{ - if a>b{ - return a + +int maxDepth(TreeNode* root){ + if(root == nullptr) return 0; + int leftDepth = maxDepth(root->left); + int rightDepth = maxDepth(root->right); + if(leftDepth == -1 || rightDepth == -1 || leftDepth - rightDepth > 1 || rightDepth - leftDepth > 1) + { + return -1; } - return b + return 1 + max(leftDepth, rightDepth); } - ``` ## 练习 diff --git a/advanced_algorithm/recursion.md b/advanced_algorithm/recursion.md index ccfa3757..ae68a6d3 100644 --- a/advanced_algorithm/recursion.md +++ b/advanced_algorithm/recursion.md @@ -10,20 +10,15 @@ > 编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组  `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] - } -} -func reverse(s []byte, i int, res *[]byte) { - if i == len(s) { - return - } - reverse(s, i+1, res) - *res = append(*res, s[i]) +```cpp +void reverseString(vector& s) { + if(s.size() <= 1) return; + + int left = 0, right = s.size() - 1; + while(left < right){ + swap(s[left++], s[right--]); + } + return; } ``` @@ -32,58 +27,56 @@ func reverse(s []byte, i int, res *[]byte) { > 给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。 > **你不能只是单纯的改变节点内部的值**,而是需要实际的进行节点交换。 -```go -func swapPairs(head *ListNode) *ListNode { - // 思路:将链表翻转转化为一个子问题,然后通过递归方式依次解决 - // 先翻转两个,然后将后面的节点继续这样翻转,然后将这些翻转后的节点连接起来 - return helper(head) -} -func helper(head *ListNode)*ListNode{ - if head==nil||head.Next==nil{ - return head +```cpp + ListNode* swapPairs(ListNode* head) { + //用递归算法写 + // 思路:将链表翻转转化为一个子问题,然后通过递归方式依次解决 + // 先翻转两个,然后将后面的节点继续这样翻转,然后将这些翻转后的节点连接起来 + return swapListNode(head); + } + + ListNode* swapListNode(ListNode* head){ + if(head == nullptr || head->next == nullptr) return head; + + // 保存下一阶段的头指针 + ListNode* nextHead = head->next->next; + // 翻转当前阶段指针 + ListNode* next = head->next; + next->next = head; + head->next = swapListNode(nextHead); + return next; } - // 保存下一阶段的头指针 - nextHead:=head.Next.Next - // 翻转当前阶段指针 - next:=head.Next - next.Next=head - head.Next=helper(nextHead) - return next -} ``` [unique-binary-search-trees-ii](https://leetcode-cn.com/problems/unique-binary-search-trees-ii/) > 给定一个整数 n,生成所有由 1 ... n 为节点所组成的二叉搜索树。 -```go -func generateTrees(n int) []*TreeNode { - if n==0{ - return nil - } - return generate(1,n) - +```cpp +vector generateTrees(int n) { + if(n == 0) return vector {}; + return generate(1, n); } -func generate(start,end int)[]*TreeNode{ - if start>end{ - return []*TreeNode{nil} - } - ans:=make([]*TreeNode,0) - for i:=start;i<=end;i++{ - // 递归生成所有左右子树 - lefts:=generate(start,i-1) - rights:=generate(i+1,end) - // 拼接左右子树后返回 - for j:=0;j generate(int low, int hi){ + if(low > hi) return {nullptr}; + + vector res; + for(int i = low; i <= hi; i++){ + //递归生成左右子树 + vector lefts = generate(low, i - 1); + vector rights = generate(i + 1, hi); + //拼接两边 + for(int j = 0; j < lefts.size(); j++){ + for(int k = 0; k < rights.size(); k++){ + TreeNode* root = new TreeNode(i); + root->left = lefts[j]; + root->right = rights[k]; + res.push_back(root); } } } - return ans + return res; } ``` @@ -96,23 +89,18 @@ 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) -} -var m map[int]int=make(map[int]int) -func dfs(n int)int{ - if n < 2{ - return n - } - // 读取缓存 - if m[n]!=0{ - return m[n] +```cpp +int fib(int N) { + if(N <= 1) return N; + int f0 = 0; + int f1 = 1; + int res = 0; + for(int i = 2; i <= N; i++){ + res = f0 + f1; + f0 = f1; + f1 = res; } - ans:=dfs(n-2)+dfs(n-1) - // 缓存已经计算过的值 - m[n]=ans - return ans + return res; } ``` diff --git a/advanced_algorithm/slide_window.md b/advanced_algorithm/slide_window.md index 4e043302..23e0f97b 100644 --- a/advanced_algorithm/slide_window.md +++ b/advanced_algorithm/slide_window.md @@ -47,63 +47,48 @@ void slidingWindow(string s, string t) { [minimum-window-substring](https://leetcode-cn.com/problems/minimum-window-substring/) > 给你一个字符串 S、一个字符串 T,请在字符串 S 里面找出:包含 T 所有字母的最小子串 - -```go -func minWindow(s string, t string) string { - // 保存滑动窗口字符集 - win := make(map[byte]int) - // 保存需要的字符集 - need := make(map[byte]int) - for i := 0; i < len(t); i++ { - need[t[i]]++ - } - // 窗口 - left := 0 - right := 0 - // match匹配次数 - match := 0 - start := 0 - end := 0 - min := math.MaxInt64 - var c byte - for right < len(s) { - c = s[right] - right++ - // 在需要的字符集里面,添加到窗口字符集里面 - if need[c] != 0 { - win[c]++ - // 如果当前字符的数量匹配需要的字符的数量,则match值+1 - if win[c] == need[c] { - match++ - } +```cpp +string minWindow(string s, string t) { + int m = s.length(); + int n = t.length(); + + unordered_map mp; + for(int i = 0; i < n; i++) mp[t[i]]++; // 保存需要的字符集 + + int winStart = 0; + int matched = 0; //t里面已经匹配好的字母次数 + int start = 0; + int minL = INT_MAX; //最小窗口的长度 + for(int winEnd = 0; winEnd < m; winEnd++){ + char w = s[winEnd]; + if(mp.find(w) != mp.end()){ + mp[w]--; + if(mp[w] == 0) matched++; // 如果当前字符的数量匹配需要的字符的数量,则match值+1 } // 当所有字符数量都匹配之后,开始缩紧窗口 - for match == len(need) { - // 获取结果 - if right-left < min { - min = right - left - start = left - end = right + while(matched == mp.size()){ + if(minL > winEnd - winStart + 1){ + start = winStart; + minL = winEnd - winStart + 1; } - c = s[left] - left++ - // 左指针指向不在需要字符集则直接跳过 - if need[c] != 0 { - // 左指针指向字符数量和需要的字符相等时,右移之后match值就不匹配则减一 - // 因为win里面的字符数可能比较多,如有10个A,但需要的字符数量可能为3 - // 所以在压死骆驼的最后一根稻草时,match才减一,这时候才跳出循环 - if win[c] == need[c] { - match-- + // 左指针指向字符数量和需要的字符相等时,右移之后match值就不匹配则减一 + // 因为win里面的字符数可能比较多,如有10个A,但需要的字符数量可能为3 + // 所以在压死骆驼的最后一根稻草时,match才减一,这时候才跳出循环 + // minL = min(minL, winEnd - winStart + 1); + char w = s[winStart]; + if(mp.find(w) != mp.end()){ + if(mp[w] == 0){ + matched--; + // break; } - win[c]-- + mp[w]++; } + winStart++; } } - if min == math.MaxInt64 { - return "" - } - return s[start:end] + + return minL == INT_MAX?"":s.substr(start, minL); } ``` @@ -111,42 +96,37 @@ 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++ - } +```cpp +bool checkInclusion(string s1, string s2) { + int n1 = s1.length(); + int n2 = s2.length(); + + unordered_map mp; + for(int i = 0; i < n1; i++) mp[s1[i]]++; + + int winStart = 0; + int matched = 0; + + for(int winEnd = 0; winEnd < n2; winEnd++){ + char w = s2[winEnd]; + if(mp.find(w) != mp.end()){ + mp[w]--; + if(mp[w] == 0) matched++; //相同字母都匹配完了 } - // 当窗口长度大于字符串长度,缩紧窗口 - 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]-- + + if(matched == mp.size()) return true; + + if(winEnd - winStart + 1 >= n1){ //窗口长度大于等于pattern的长度时,收缩窗口 + char w = s2[winStart]; + if(mp.find(w) != mp.end()){ + if(mp[w] == 0) matched--; + mp[w]++; } + winStart++; } } - return false + + return false; } ``` @@ -155,43 +135,40 @@ func checkInclusion(s1 string, s2 string) bool { > 给定一个字符串  **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++ - } +```cpp +//跟上一题一样,本题只不过要遍历全部位置,同时记录下满足条件的起始index +vector findAnagrams(string s, string p) { + int m = s.length(); + int n = p.length(); + + unordered_map mp; + for(int i = 0; i < n; i++) mp[p[i]]++; + + vector res; + int winStart = 0; + int matched = 0; + + for(int winEnd = 0; winEnd < m; winEnd++){ + char w = s[winEnd]; + if(mp.find(w) != mp.end()){ + mp[w]--; + if(mp[w] == 0) matched++; } - // 当窗口长度大于字符串长度,缩紧窗口 - 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]-- + + if(matched == mp.size()) res.push_back(winStart); + + if(winEnd - winStart + 1 >= n){ + char w = s[winStart]; + if(mp.find(w) != mp.end()){ + if(mp[w] == 0) matched--; + mp[w]++; } + winStart++; } - } - return ans + + } + + return res; } ``` @@ -204,36 +181,25 @@ func findAnagrams(s string, p string) []int { > 输出: 3 > 解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。 -```go -func lengthOfLongestSubstring(s string) int { +```cpp +int lengthOfLongestSubstring(string s) { // 滑动窗口核心点:1、右指针右移 2、根据题意收缩窗口 3、左指针右移更新窗口 4、根据题意计算结果 - if len(s)==0{ - return 0 - } - win:=make(map[byte]int) - left:=0 - right:=0 - ans:=1 - for right1{ - d:=s[left] - left++ - win[d]-- - } - // 计算结果 - ans=max(right-left,ans) - } - return ans -} -func max(a,b int)int{ - if a>b{ - return a - } - return b + int n = s.length(); + if(n <= 1) return n; + + int winStart = 0; + int maxL = INT_MIN; + unordered_map mp; + for(int winEnd = 0; winEnd < n; winEnd++){ + char w = s[winEnd]; + if(mp.find(w) != mp.end()){ + winStart = max(mp[w] + 1, winStart); + } + mp[w] = winEnd; + + maxL = max(maxL, winEnd - winStart + 1); + } + return maxL; } ``` diff --git a/advanced_algorithm/topological_sort.md b/advanced_algorithm/topological_sort.md new file mode 100644 index 00000000..89970ea7 --- /dev/null +++ b/advanced_algorithm/topological_sort.md @@ -0,0 +1,109 @@ +# 拓扑排序 + +## 背景 +拓扑排序(Topological Sort)是为了在一堆有前后依赖关系的元素中,找到一条线性顺序,保证所有元素都能被访问到。比如你选课的时候,某些课程之间有限制(只能选了线代之后才能学高数),那么要找到一个合理的选课顺序就是典型的拓扑排序。 + +> Topological Sort of a directed graph (a graph with unidirectional edges) is a linear ordering of its vertices such that for every directed edge ```(U, V)``` from vertex ```U``` to vertex ```V```, ```U``` comes before ```V``` in the ordering. + +Given a directed graph, find the topological ordering of its vertices. + +示例1: + +```shell +Input: Vertices=4, Edges=[3, 2], [3, 0], [2, 0], [2, 1] +Output: Following are the two valid topological sorts for the given graph: +1) 3, 2, 0, 1 +2) 3, 2, 1, 0 +``` +![demo1](../images/tupulogic_sort.png) + + +## 伪代码 +拓扑排序得用```BFS```去解 + +a. Initialization + + where the ‘key’ will be the parent vertex number and the value +To find the sources, we will keep a HashMap to count the in-degrees i.e., count of incoming edges of each vertex. Any vertex with ‘0’ in-degree will be a source. + +b. Build the graph and find in-degrees of all vertices + +We will build the graph from the input and populate the in-degrees HashMap. + +c. Find all sources + +All vertices with ‘0’ in-degrees will be our sources and we will store them in a Queue. + + +d. Sort + +For each source, do the following things: +Add it to the sorted list. +Get all of its children from the graph. +Decrement the in-degree of each child by 1. +If a child’s in-degree becomes ‘0’, add it to the sources Queue. +Repeat step 1, until the source Queue is empty. + +## 模板 + +```cpp +class TopologicalSort { + public: + static vector sort(int vertices, const vector>& edges) { + vector sortedOrder; + if (vertices <= 0) { + return sortedOrder; + } + + // a. Initialize the graph + unordered_map inDegree; // count of incoming edges for every vertex + unordered_map> graph; // adjacency list graph + for (int i = 0; i < vertices; i++) { + inDegree[i] = 0; + graph[i] = vector(); + } + + // b. Build the graph + for (int i = 0; i < edges.size(); i++) { + int parent = edges[i][0], child = edges[i][1]; + graph[parent].push_back(child); // put the child into it's parent's list + inDegree[child]++; // increment child's inDegree + } + + // c. Find all sources i.e., all vertices with 0 in-degrees + queue sources; + for (auto entry : inDegree) { + if (entry.second == 0) { + sources.push(entry.first); + } + } + + // d. For each source, add it to the sortedOrder and subtract one from all of its children's + // in-degrees if a child's in-degree becomes zero, add it to the sources queue + while (!sources.empty()) { + int vertex = sources.front(); + sources.pop(); + sortedOrder.push_back(vertex); + vector children = + graph[vertex]; // get the node's children to decrement their in-degrees + for (auto child : children) { + inDegree[child]--; + if (inDegree[child] == 0) { + sources.push(child); + } + } + } + + if (sortedOrder.size() != + vertices) { // topological sort is not possible as the graph has a cycle + return vector(); + } + + return sortedOrder; + } +}; +``` + + + + diff --git a/basic_algorithm/binary_search.md b/basic_algorithm/binary_search.md index 15f336a3..c416c9b1 100644 --- a/basic_algorithm/binary_search.md +++ b/basic_algorithm/binary_search.md @@ -4,7 +4,7 @@ 给一个**有序数组**和目标值,找第一次/最后一次/任何一次出现的索引,如果没有出现返回-1 -模板四点要素 +模板**四点**要素 - 1、初始化:start=0、end=len-1 - 2、循环退出条件:start + 1 < end @@ -19,34 +19,44 @@ > 给定一个  n  个元素有序的(升序)整型数组  nums 和一个目标值  target  ,写一个函数搜索  nums  中的 target,如果目标值存在返回下标,否则返回 -1。 -```go +**C++版本** +```cpp // 二分搜索最常用模板 -func search(nums []int, target int) int { - // 1、初始化start、end - start := 0 - end := len(nums) - 1 - // 2、处理for循环 - for start+1 < end { - 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 - } - } - // 4、最后剩下两个元素,手动判断 - if nums[start] == target { - return start +int search(vector& nums, int target) { + if(nums.empty()) return -1; + + int left = 0, right = nums.size() - 1; + //处理循环 + while(left + 1 < right){ + int middle = left + (right - left) / 2; + if(nums[middle] == target) return middle; + else if(nums[middle] < target) left = middle; //比较middle跟target的值 + else right = middle; } - if nums[end] == target { - return end - } - return -1 + + //最后还剩下left right两个值,做单独判断 + if(nums[left] == target) return left; + if(nums[right] == target) return right; + + return -1; } ``` +**python3版本** +```python +class Solution: + def search(self, nums: List[int], target: int) -> int: + left = 0 + right = len(nums) - 1 + while left + 1 < right: + mid = left + (right - left) // 2 + if nums[mid] == target: return mid + elif nums[mid] < target: left = mid + else: right = mid + + if nums[left] == target: return left + if nums[right] == target: return right + return -1 +``` 大部分二分查找类的题目都可以用这个模板,然后做一点特殊逻辑即可 @@ -54,25 +64,23 @@ func search(nums []int, target int) int { ![binary_search_template](https://img.fuiboom.com/img/binary_search_template.png) -所以用模板#3 就对了,详细的对比可以这边文章介绍:[二分搜索模板](https://leetcode-cn.com/explore/learn/card/binary-search/212/template-analysis/847/) +**所以用模板#3 就对了**,详细的对比可以这边文章介绍:[二分搜索模板](https://leetcode-cn.com/explore/learn/card/binary-search/212/template-analysis/847/) 如果是最简单的二分搜索,不需要找第一个、最后一个位置、或者是没有重复元素,可以使用模板#1,代码更简洁 -```go +```cpp // 无重复元素搜索时,更方便 -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) { + if(nums.empty()) return -1; + int left = 0, right = nums.size() - 1; + //处理循环 + while(left <= right){ + int middle = left + (right - left) / 2; + if(nums[middle] == target) return middle; + else if(nums[middle] < target) left = middle-1; //比较middle跟target的值 + else right = middle+1; } + // 如果找不到,start 是第一个大于target的索引 // 如果在B+树结构里面二分搜索,可以return start // 这样可以继续向子节点搜索,如:node:=node.Children[start] @@ -81,98 +89,428 @@ func search(nums []int, target int) int { ``` ## 常见题目 +### [x的平方根](https://leetcode.cn/problems/sqrtx/) +> 给你一个非负整数 x ,计算并返回 x 的 算术平方根 。 +> 由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 。 +> 注意:不允许使用任何内置指数函数和算符,例如 pow(x, 0.5) 或者 x ** 0.5 。 + +**Python版本** +```python +class Solution: + def mySqrt(self, x: int) -> int: + if x == 0: return 0 + left, right = 1, x//2 + 1 + while left + 1 < right: + mid = left + (right - left) // 2 + if mid * mid == x: return mid + elif mid * mid < x: left = mid + else: right = mid + + if left * left > x: return left - 1 + elif right * right <= x: return right + else: return left + +``` + +### [有效的完全平方数](https://leetcode.cn/problems/valid-perfect-square/) +> 给定一个 正整数 num ,编写一个函数,如果 num 是一个完全平方数,则返回 true ,否则返回 false 。 +> 进阶:不要 使用任何内置的库函数,如  sqrt 。 + +思路:只要`num`大于4,其平方数肯定不会超过`num//2`,这样可以减少一次二分运算,提升速度. + +**Python版本** +```python +class Solution: + def isPerfectSquare(self, num: int) -> bool: + left, right = 1, num // 2 + 1 + while left + 1 < right: + mid = left + (right - left) // 2 + if mid * mid == num: return True + elif mid * mid < num: left = mid + else: + right = mid + if left * left == num or right * right == num: return True + else: return False +``` -### [search-for-range](https://www.lintcode.com/problem/search-for-a-range/description) +### [Pow(x, n)](https://leetcode.cn/problems/powx-n/) +> 实现 pow(x, n) ,即计算 x 的整数 n 次幂函数(即,x^n )。 + +思路:使用**快速幂**思想,二分法解决,需要注意奇偶数的不同 + +**Python版本** +```python +class Solution: + def myPow(self, x: float, n: int) -> float: + if x == 0: return 0 + res = 1.0 + m = abs(n) + while m > 0: + if m % 2: res = res * x + x = x * x + m = m // 2 + + if n < 0: return 1. / res + return res +``` + + +### [寻找比目标字母大的最小字母](https://leetcode.cn/problems/find-smallest-letter-greater-than-target/) +> 给你一个排序后的字符列表 letters ,列表中只包含小写英文字母。另给出一个目标字母 target,请你寻找在这一有序列表里比目标字母大的最小字母。 +> +> 在比较时,字母是依序循环出现的。举个例子: +> - 如果目标字母 target = 'z' 并且字符列表为 letters = ['a', 'b'],则答案返回 'a' + + +**Python版本** +```python +class Solution: + def nextGreatestLetter(self, letters: List[str], target: str) -> str: + if letters[0] > target or target >= letters[-1]: return letters[0] + left, right = 0, len(letters) - 1 + while left + 1 < right: + mid = left + (right - left) // 2 + if letters[mid] <= target: left = mid #尽量往左移动 + else: right = mid + + if letters[left] > target: return letters[left] + return letters[right] +``` + +### [两个数组的交集](https://leetcode.cn/problems/intersection-of-two-arrays/) +> 给定两个数组 nums1 和 nums2 ,返回 它们的交集 。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序。 + + +**Python版本** +```python +class Solution: + def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]: + nums1, nums2 = sorted(set(nums1)), sorted(set(nums2)) + if len(nums1) <= len(nums2): return inter(nums1, nums2) + else: return inter(nums2, nums1) + +def inter(nums1, nums2): + res = list() + for i, num in enumerate(nums1): + if find(num, nums2): res.append(num) + return res + +def find(num, nums): + left, right = 0, len(nums) - 1 + while left + 1 < right: + mid = left + (right - left) // 2 + if nums[mid] == num: return True + elif nums[mid] < num: left = mid + else: right = mid + if nums[left] != num and nums[right] != num: return False + return True +``` + +### [两个数组的交集II](https://leetcode.cn/problems/intersection-of-two-arrays-ii/) +> 给你两个整数数组 nums1 和 nums2 ,请你以数组形式返回两数组的交集。返回结果中每个元素出现的次数,应与元素在两个数组中都出现的次数一致(如果出现次数不一致,则考虑取较小值)。可以不考虑输出结果的顺序。 + +// 思路:需要考虑找到的数量 + +**Python版本** +```python +class Solution: + def intersect(self, nums1: List[int], nums2: List[int]) -> List[int]: + nums1, nums2 = sorted(nums1), sorted(nums2) + if len(nums1) <= len(nums2): return inter(nums1, nums2) + else: return inter(nums2, nums1) + + +def inter(nums1, nums2): + res = list() + i = 0 + while i < len(nums1): + num = nums1[i] + cnt1 = 1 + while i+1 < len(nums1) and nums1[i+1] == num: + cnt1 += 1 + i += 1 + cnt2 = findNum(num, nums2) + res.extend([num] * min(cnt1, cnt2)) + i += 1 + return res + +def findNum(num, nums): + left = find(num, nums, find_left=True) + if left == -1: return 0 + right = find(num, nums, find_left=False) + return right - left + 1 + +def find(num, nums, find_left): + left, right = 0, len(nums) - 1 + while left + 1 < right: + mid = left + (right - left) // 2 + if nums[mid] < num: left = mid + elif nums[mid] > num: right = mid + else: + if find_left: right = mid + else: left = mid + if nums[left] != num and nums[right] != num: return -1 + if find_left: + if nums[left] == num: return left + else: return right + else: + if nums[right] == num: return right + else: return left + +``` + +### [两数之和II-输入有序数组](https://leetcode.cn/problems/two-sum-ii-input-array-is-sorted/) +> 给你一个下标从 1 开始的整数数组 numbers ,该数组已按 非递减顺序排列  ,请你从数组中找出满足相加之和等于目标数 target 的两个数。如果设这两个数分别是 numbers[index1] 和 numbers[index2] ,则 1 <= index1 < index2 <= numbers.length 。 +> +> 以长度为 2 的整数数组 [index1, index2] 的形式返回这两个整数的下标 index1 和 index2。 + +> 你可以假设每个输入 只对应唯一的答案 ,而且你 **不可以** 重复使用相同的元素。你所设计的解决方案必须只使用常量级的额外空间。 + +//思路: 两种解题思路,一是二分,第二种是双指针,肯定双指针耗时更优 +**Python版本一:二分查找** +```python +class Solution: + def twoSum(self, numbers: List[int], target: int) -> List[int]: + ''' + 这里使用二分法来找; 也可以使用双指针法 + ''' + for i, num in enumerate(numbers): + right = find(target - num, i + 1, len(numbers) - 1, numbers) + if right != -1: return [i+1, right+1] + return [-1, -1] +def find(num, lo, hi, nums): + while lo + 1 < hi: + mid = lo + (hi - lo) // 2 + if nums[mid] >= num: hi = mid + else: lo = mid + if nums[lo] == num: return lo + elif nums[hi] == num: return hi + else: return -1 +``` +**Python版本二:双指针** +```python +class Solution: + def twoSum(self, numbers: List[int], target: int) -> List[int]: + left, right = 0, len(numbers) - 1 + while left < right: + if numbers[left] + numbers[right] == target: return [left + 1, right + 1] + elif numbers[left] + numbers[right] > target: right = right - 1 + else: left = left + 1 + return [-1, -1] +``` + + + +### [猜数字大小](https://leetcode.cn/problems/guess-number-higher-or-lower/) +> 猜数字游戏的规则如下: +> +> 每轮游戏,我都会从 1 到 n 随机选择一个数字。 请你猜选出的是哪个数字。 +> +> 如果你猜错了,我会告诉你,你猜测的数字比我选出的数字是大了还是小了。 +> +> 你可以通过调用一个预先定义好的接口 int guess(int num) 来获取猜测结果,返回值一共有 3 种可能的情况(-1,1 或 0): +> +> - -1:我选出的数字比你猜的数字小 pick < num +> - 1:我选出的数字比你猜的数字大 pick > num +> - 0:我选出的数字和你猜的数字一样。恭喜!你猜对了!pick == num + + +**Python版本** +```python +# The guess API is already defined for you. +# @param num, your guess +# @return -1 if my number is lower, 1 if my number is higher, otherwise return 0 +# def guess(num: int) -> int: + +class Solution: + def guessNumber(self, n: int) -> int: + left, right = 1, n + while left + 1 < right: + mid = left + (right - left) // 2 + if guess(mid) == 0: return mid + elif guess(mid) == -1: right = mid + else: left = mid + if guess(left) == 0: return left + else: return right + +``` + + +### [search-for-range](https://leetcode-cn.com/problems/find-first-and-last-position-of-element-in-sorted-array/) > 给定一个包含 n 个整数的排序数组,找出给定目标值 target 的起始和结束位置。 > 如果目标值不在数组中,则返回`[-1, -1]` 思路:核心点就是找第一个 target 的索引,和最后一个 target 的索引,所以用两次二分搜索分别找第一次和最后一次的位置 -```go -func searchRange (A []int, target int) []int { - if len(A) == 0 { - return []int{-1, -1} - } - 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 - } else { - // 如果相等,应该继续向左找,就能找到第一个目标值的位置 - end = mid +**Python3版本** +```python +class Solution: + def searchRange(self, nums: List[int], target: int) -> List[int]: + if len(nums) == 0: return [-1, -1] + + left, right = 0, len(nums) - 1 + while left + 1 < right: + mid = left + (right - left) // 2 + if nums[mid] < target: left = mid + elif nums[mid] > target: right = mid + else: + left = mid + right = mid + while left > 0 and nums[left-1] == target: left = left - 1 + while right < len(nums) - 1 and nums[right+1] == target: right = right + 1 + return [left, right] + if nums[left] == target: + if nums[right] == target: return [left, right] + else: return [left, left] + else: + if nums[right] == target: return [right, right] + else: return [-1, -1] +``` + +```python +class Solution: + def searchRange(self, nums: List[int], target: int) -> List[int]: + if len(nums) == 0: return [-1, -1] + ''' + 分解成找两次,找最左边一次,找最右边一次 + ''' + left = findTarget(nums, target, True) + if left == -1: return [-1, -1] + right = findTarget(nums, target, False) + return [left, right] + +def findTarget(nums, target, find_left): + left, right = 0, len(nums) - 1 + while left + 1 < right: + mid = left + (right - left) // 2 + if nums[mid] < target: left = mid + elif nums[mid] > target: right = mid + else: + if find_left: right = mid + else: left = mid + + if find_left: + if nums[left] == target: return left + elif nums[right] == target: return right + else: return -1 + else: + if nums[right] == target: return right + elif nums[left] == target: return left + else: return -1 + +``` + +**C++版本** +```cpp +vector searchRange(vector& nums, int target) { + if(nums.empty()) return {-1, -1}; + int low = findTarget(nums, target, true); + if(low == -1) return {-1, -1}; + int high = findTarget(nums, target, false); + return {low, high}; +} + +int findTarget(vector& nums, int target, bool findLeft){ + int left = 0, right = nums.size() - 1; + while(left + 1 < right){ + int middle = left + (right - left)/2; + if(nums[middle] < target) left = middle; + else if(nums[middle] > target) right = middle; + else{ + if(findLeft) right = middle; + else left = middle; } } - // 搜索左边的索引 - if A[start] == target { - result[0] = start - } else if A[end] == target { - result[0] = end - } else { - result[0] = -1 - result[1] = -1 - return result - } - 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 - } else { - // 如果相等,应该继续向右找,就能找到最后一个目标值的位置 - start = mid - } + + if(findLeft) { + if(nums[left] == target) return left; + else if(nums[right] == target) return right; + else return -1; } - // 搜索右边的索引 - if A[end] == target { - result[1] = end - } else if A[start] == target { - result[1] = start - } else { - result[0] = -1 - result[1] = -1 - return result + else{ + if(nums[right] == target) return right; + else if(nums[left] == target) return left; + else return -1; } - return result + } ``` +### [找到k个最接近的元素](https://leetcode.cn/problems/find-k-closest-elements/) +> 给定一个 排序好 的数组 arr ,两个整数 k 和 x ,从数组中找到最靠近 x(两数之差最小)的 k 个数。返回的结果必须要是按升序排好的。 + +思路有好几种: + +1. 找到最小值,然后双指针 +2. 直接找k长度的子序列 +3. 删去最远的剩下的就是最近的长度为k的子数组 + + +**Python版本** +```python +### 方法一 +class Solution: + def findClosestElements(self, arr: List[int], k: int, x: int) -> List[int]: + if len(arr) == k: return arr + left, right = 0, len(arr) - 1 + + while left + 1 < right: + mid = left + (right - left) // 2 + if arr[mid] >= x: right = mid + else: left = mid + + res = list() + while len(res) < k: + if right >= len(arr) or (left >= 0 and abs(arr[left]-x) <= abs(arr[right]-x)): + res.insert(0, arr[left]) + left = left - 1 + else: + res.append(arr[right]) + right = right + 1 + return res +``` + +```python +### 方法二 +class Solution: + def findClosestElements(self, arr: List[int], k: int, x: int) -> List[int]: + ''' + 直接定位mid为左边界; + 查找mid+k的结果是否满足要求; + 不满足的话二分法左右调整mid. + ''' + if len(arr) == k: return arr + left, right = 0, len(arr) - k + while left + 1 < right: + mid = left + (right - left) // 2 + if x - arr[mid] <= arr[mid + k] - x: + right = mid + else: + left = mid + + if abs(arr[left] - x) <= abs(arr[right + k - 1] - x): return arr[left: left + k] + else: return arr[right: right + k] +``` ### [search-insert-position](https://leetcode-cn.com/problems/search-insert-position/) > 给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。 -```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 - } else { - start = mid - } - } - if nums[start] >= target { - return start - } else if nums[end] >= target { - return end - } else if nums[end] < target { // 目标值比所有值都大 - return end + 1 +```cpp +int searchInsert(vector& nums, int target) { + if(nums.empty()) return 0; + int left = 0, right = nums.size() - 1; + + while(left + 1 < right){ + int middle = left + (right - left) / 2; + if(nums[middle] == target) return middle; + else if(nums[middle] < target) left = middle; + else right = middle; } - return 0 + + if(target <= nums[left]) return left; + else if(target <= nums[right]) return right; + else return right + 1; } ``` @@ -183,57 +521,83 @@ 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 - } - 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 - } else { - return true - } - } - if matrix[start/col][start%col] == target || matrix[end/col][end%col] == target{ - return true + +**C++版本** +```cpp +bool searchMatrix(vector>& matrix, int target) { + if(matrix.empty() || matrix[0].empty()) return false; + int rows = matrix.size(); + int cols = matrix[0].size(); + + int left = 0, right = rows * cols - 1; + while(left + 1 < right){ + int mid = left + (right - left) / 2; + int valM = matrix[mid/cols][mid%cols]; + if(target == valM) return true; + else if(target < valM) right = mid; + else left = mid; } - return false + + if(matrix[left/cols][left%cols] == target || matrix[right/cols][right%cols] == target) return true; + + return false; } ``` +**Python版本** +```python +class Solution: + def searchMatrix(self, matrix: List[List[int]], target: int) -> bool: + rows = len(matrix) + cols = len(matrix[0]) + left, right = 0, rows * cols - 1 + while left + 1 < right: + mid = left + (right - left) // 2 + val = matrix[mid//cols][mid%cols] + if val == target: return True + elif val < target: left = mid + else: right = mid + + if matrix[left//cols][left%cols] == target or matrix[right//cols][right%cols] == target: + return True + else: return False +``` + ### [first-bad-version](https://leetcode-cn.com/problems/first-bad-version/) > 假设你有 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 - } - } - if isBadVersion(start) { - return start +**Python3版本** +```python +# The isBadVersion API is already defined for you. +# def isBadVersion(version: int) -> bool: + +class Solution: + def firstBadVersion(self, n: int) -> int: + left, right = 1, n + + while left + 1 < right: + mid = left + (right - left) // 2 + if not isBadVersion(mid): left = mid + else: right = mid + + if isBadVersion(left): return left + else: return right + +``` + +**C++版本** +```cpp +int firstBadVersion(int n) { + int left = 1, right = n; + while(left + 1 < right){ + int mid = left + (right - left) / 2; + if(isBadVersion(mid)) right = mid; + else left = mid; } - return end + if(isBadVersion(left)) return left; + return right; } ``` @@ -241,29 +605,51 @@ func firstBadVersion(n int) int { > 假设按照升序排序的数组在预先未知的某个点上进行了旋转( 例如,数组  [0,1,2,4,5,6,7] 可能变为  [4,5,6,7,0,1,2] )。 > 请找出其中最小的元素。 +**Python版本** +```python +class Solution: + def findMin(self, nums: List[int]) -> int: + left, right = 0, len(nums) - 1 + if nums[left] <= nums[right]: return nums[left] # 说明还是升序排列 + + while left + 1 < right: + mid = left + (right - left) // 2 + if nums[mid] >= nums[right]: left = mid + else: right = mid + + return min(nums[left], nums[right]) +``` +**版本二** +```python +class Solution: + def findMin(self, nums: List[int]) -> int: + if nums[0] < nums[-1]: return nums[0] ## 说明顺序没变 + left, right = 0, len(nums) - 1 + while left + 1 < right: + mid = left + (right - left) // 2 + if nums[mid] > nums[left]: + left = mid + else: + right = mid + + return min(nums[left], nums[right]) +``` -```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 - } else { - start = mid - } - } - if nums[start] > nums[end] { - return nums[end] +**C++版本** +```cpp +int findMin(vector& nums) { + if(nums.empty()) return 0; + + int left = 0, right = nums.size() - 1; + if(nums[right] > nums[left]) return nums[left]; //说明没有旋转 + + while(left + 1 < right){ + int mid = left + (right - left) / 2; + if(nums[mid] > nums[left]) left = mid; + else right = mid; } - return nums[start] + + return min(nums[left], nums[right]); } ``` @@ -273,34 +659,40 @@ 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 - } - start := 0 - end := len(nums) - 1 - for start+1 < end { - // 去除重复元素 - for start < end && nums[end] == nums[end-1] { - end-- - } - for start < end && nums[start] == nums[start+1] { - start++ - } - mid := start + (end-start)/2 - // 中间元素和最后一个元素比较(判断中间点落在左边上升区,还是右边上升区) - if nums[mid] <= nums[end] { - end = mid - } else { - start = mid - } - } - if nums[start] > nums[end] { - return nums[end] +// 思路:跳过重复元素,mid值和end值比较,分为两种情况进行处理 + +**Python版本** +```python +class Solution: + def findMin(self, nums: List[int]) -> int: + if nums[0] < nums[-1]: return nums[0] + left, right = 0, len(nums) - 1 + while left + 1 < right: + while left < right and nums[left] == nums[left+1]: left = left + 1 + while left < right and nums[right] == nums[right-1]: right = right - 1 + mid = left + (right - left) // 2 + if nums[mid] >= nums[right]: left = mid + else: right = mid + + return min(nums[left], nums[right]) +``` + + +**C++版本** +```cpp +int findMin(vector& nums) { + if(nums.empty()) return 0; + int left = 0, right = nums.size() - 1; + if(nums[left] < nums[right]) return nums[left]; // 说明没旋转 + while(left + 1 < right){ + //去重 + while(left < right && nums[left + 1] == nums[left]) left++; + while(left < right && nums[right - 1] == nums[right]) right--; + int mid = left + (right - left) / 2; + if(nums[mid] >= nums[left]) left = mid; + else right = mid; } - return nums[start] + return min(nums[left], nums[right]); } ``` @@ -311,41 +703,49 @@ func findMin(nums []int) int { > 搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回  -1 。 > 你可以假设数组中不存在重复的元素。 -```go -func search(nums []int, target int) int { - // 思路:/ / 两条上升直线,四种情况判断 - if len(nums) == 0 { - return -1 - } - start := 0 - end := len(nums) - 1 - for start+1 < end { - mid := start + (end-start)/2 - // 相等直接返回 - if nums[mid] == target { - return mid +**Python版本** +```python +class Solution: + def search(self, nums: List[int], target: int) -> int: + left, right = 0, len(nums) - 1 + + while left + 1 < right: + mid = left + (right - left) // 2 + if nums[mid] == target: return mid + if nums[mid] > nums[left]: + if target >= nums[left] and target <= nums[mid]: right = mid + else: left = mid + else: + if target <= nums[right] and target >= nums[mid]: left = mid + else: right = mid + + if nums[left] == target: return left + elif nums[right] == target: return right + else: return -1 +``` + +**C++版本** +```cpp +int search(vector& nums, int target) { + if(nums.empty()) return -1; + int left = 0, right = nums.size() - 1; + while(left + 1 < right){ + int mid = left + (right - left) / 2; + if(nums[mid] == target) return mid; + if(nums[mid] > nums[left]){ + if(target >= nums[left] && target <= nums[mid]) right = mid; + else left = mid; } - // 判断在那个区间,可能分为四种情况 - if nums[start] < nums[mid] { - if nums[start] <= target && target <= nums[mid] { - end = mid - } else { - start = mid - } - } else if nums[end] > nums[mid] { - if nums[end] >= target && nums[mid] <= target { - start = mid - } else { - end = mid - } + else{ + if(target <= nums[right] && target >= nums[mid]) left = mid; + else right = mid; } } - if nums[start] == target { - return start - } else if nums[end] == target { - return end - } - return -1 + + if(nums[left] == target) return left; + else if(nums[right] == target) return right; + else return -1; + } ``` @@ -353,52 +753,56 @@ func search(nums []int, target int) int { > 面试时,可以直接画图进行辅助说明,空讲很容易让大家都比较蒙圈 +### [寻找峰值](https://leetcode.cn/problems/find-peak-element/) +> 峰值元素是指其值严格大于左右相邻值的元素。 +> 给你一个整数数组 nums,找到峰值元素并返回其索引。数组可能包含多个峰值,在这种情况下,返回 任何一个峰值 所在位置即可。 +> 你可以假设 nums[-1] = nums[n] = -∞ 。 +> 你必须实现时间复杂度为 O(log n) 的算法来解决此问题。 +思路:规定了算法复杂度,那就是二分匹配法 + +**Python3版本** +```python +class Solution: + def findPeakElement(self, nums: List[int]) -> int: + left, right = 0, len(nums) - 1 + while left + 1 < right: + mid = left + (right - left) // 2 + if nums[mid] < nums[mid+1]: left = mid + else: right = mid + + if nums[left] < nums[right]: return right + else: return left +``` + +**前面几道题说明了,二分匹配中,不一定直接对比mid和target, left和mid, mid本身, mid和相邻(mid+1),都是能做compare的。** + + ### [search-in-rotated-sorted-array-ii](https://leetcode-cn.com/problems/search-in-rotated-sorted-array-ii/) > 假设按照升序排序的数组在预先未知的某个点上进行了旋转。 > ( 例如,数组  [0,0,1,2,2,5,6]  可能变为  [2,5,6,0,0,1,2] )。 > 编写一个函数来判断给定的目标值是否存在于数组中。若存在返回  true,否则返回  false。(包含重复元素) -```go -func search(nums []int, target int) bool { - // 思路:/ / 两条上升直线,四种情况判断,并且处理重复数字 - if len(nums) == 0 { - return false - } - start := 0 - end := len(nums) - 1 - for start+1 < end { - // 处理重复数字 - for start < end && nums[start] == nums[start+1] { - start++ +```cpp +bool search(vector& nums, int target) { + if(nums.empty()) return false; + int left = 0, right = nums.size() - 1; + while(left + 1 < right){ + while(left < right && nums[left+1] == nums[left]) left++; + while(left < right && nums[right-1] == nums[right]) right--; + int mid = left + (right - left) / 2; + if(nums[mid] == target) return true; + if(nums[mid] >= nums[left]){ //注意是等号 + if(target >= nums[left] && target <= nums[mid]) right = mid; + else left = mid; } - for start < end && nums[end] == nums[end-1] { - end-- + else{ + if(target >= nums[mid] && target <= nums[right]) left = mid; + else right = mid; } - mid := start + (end-start)/2 - // 相等直接返回 - if nums[mid] == target { - return true - } - // 判断在那个区间,可能分为四种情况 - if nums[start] < nums[mid] { - if nums[start] <= target && target <= nums[mid] { - end = mid - } else { - start = mid - } - } else if nums[end] > nums[mid] { - if nums[end] >= target && nums[mid] <= target { - start = mid - } else { - end = mid - } - } - } - if nums[start] == target || nums[end] == target { - return true } - return false + if(nums[left] == target || nums[right] == target) return true; + else return false; } ``` @@ -409,15 +813,26 @@ func search(nums []int, target int) bool { - 1、初始化:start=0、end=len-1 - 2、循环退出条件:start + 1 < end - 3、比较中点和目标值:A[mid] ==、 <、> target + - target不一定是既定的数字,也可能是start/end/mid+1对应的值,灵活应用 - 4、判断最后两个元素是否符合:A[start]、A[end] ? target ## 练习题 +- [ ] [x的平方根](https://leetcode.cn/problems/sqrtx/) +- [ ] [有效的完全平方数](https://leetcode.cn/problems/valid-perfect-square/) +- [ ] [Pow(x, n)](https://leetcode.cn/problems/powx-n/) +- [ ] [寻找比目标字母大的最小字母](https://leetcode.cn/problems/find-smallest-letter-greater-than-target/) +- [ ] [两个数组的交集](https://leetcode.cn/problems/intersection-of-two-arrays/) +- [ ] [两个数组的交集II](https://leetcode.cn/problems/intersection-of-two-arrays-ii/) +- [ ] [两数之和II-输入有序数组](https://leetcode.cn/problems/two-sum-ii-input-array-is-sorted/) +- [ ] [猜数字大小](https://leetcode.cn/problems/guess-number-higher-or-lower/) - [ ] [search-for-range](https://www.lintcode.com/problem/search-for-a-range/description) +- [ ] [找到k个最接近的元素](https://leetcode.cn/problems/find-k-closest-elements/) - [ ] [search-insert-position](https://leetcode-cn.com/problems/search-insert-position/) - [ ] [search-a-2d-matrix](https://leetcode-cn.com/problems/search-a-2d-matrix/) - [ ] [first-bad-version](https://leetcode-cn.com/problems/first-bad-version/) - [ ] [find-minimum-in-rotated-sorted-array](https://leetcode-cn.com/problems/find-minimum-in-rotated-sorted-array/) - [ ] [find-minimum-in-rotated-sorted-array-ii](https://leetcode-cn.com/problems/find-minimum-in-rotated-sorted-array-ii/) - [ ] [search-in-rotated-sorted-array](https://leetcode-cn.com/problems/search-in-rotated-sorted-array/) +- [ ] [寻找峰值](https://leetcode.cn/problems/find-peak-element/) - [ ] [search-in-rotated-sorted-array-ii](https://leetcode-cn.com/problems/search-in-rotated-sorted-array-ii/) diff --git a/basic_algorithm/double_pointer.md b/basic_algorithm/double_pointer.md new file mode 100644 index 00000000..d4ac5650 --- /dev/null +++ b/basic_algorithm/double_pointer.md @@ -0,0 +1,82 @@ +# 双指针 + +双指针法经常应用在数组,字符串与链表的题目上。 +对于很多问题,双指针法能带来更简洁的解法,带来更快的性能。 + +### [remove-element](https://leetcode.cn/problems/remove-element) +> 给你一个数组 nums 和一个值 val,你需要 **原地** 移除所有数值等于 **val** 的元素,并返回移除后数组的新长度。 + +> 不要使用额外的数组空间,你必须仅使用 **O(1)** 额外空间并 **原地** 修改输入数组。 + +> 元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。 + +很经典的题目,有多种解法。但文中只要求给出新数组的长度,因此使用双指针如下: + +```python +class Solution: + def removeElement(self, nums: List[int], val: int) -> int: + bef = 0 + for aft in range(len(nums)): + if nums[aft] != val: + nums[bef], nums[aft] = nums[aft], nums[bef] + bef += 1 + + return bef +``` +### [reverse-string] (https://leetcode.cn/problems/reverse-string/) +> 编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出。 +> 不要给另外的数组分配额外的空间,你**必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题**。 + +```python +class Solution: + def reverseString(self, s: List[str]) -> None: + left, right = 0, len(s) - 1 + while left < right: + s[left], s[right] = s[right], s[left] + left += 1 + right -= 1 +``` +当然python自带函数 +```python +class Solution: + def reverseString(self, s: List[str]) -> None: + """ + Do not return anything, modify s in-place instead. + """ + s[:] = s[::-1] +``` +### [ti-huan-kong-ge-lcof](https://leetcode.cn/problems/ti-huan-kong-ge-lcof/) +> 请实现一个函数,把字符串 s 中的每个空格替换成"%20"。 + +注意:实现函数,就不能用python的官方函数 + +```python +class Solution: + def replaceSpace(self, s: str) -> str: + ### 双指针解法 + kongge = [i for i in s if i == ' '] + cnt = len(kongge) + ns = ['' for i in range(len(s) + 3 * cnt)] + new = 0 + for org in range(len(s)): + if s[org] == ' ': + ns[new:new+3] = ['%', '2', '0'] + new = new + 3 + else: + ns[new] = s[org] + new = new + 1 + + return ''.join(ns) +``` + +数组:移除元素 +字符串:反转字符串 +字符串:替换空格 +字符串:翻转字符串里的单词 +链表:翻转链表 +链表:删除链表的倒数第 N 个结点 +链表:链表相交 +链表:环形链表 +双指针:三数之和 +双指针:四数之和 +双指针:总结篇! \ No newline at end of file diff --git a/basic_algorithm/dp.md b/basic_algorithm/dp.md index d251ca7d..2995b447 100644 --- a/basic_algorithm/dp.md +++ b/basic_algorithm/dp.md @@ -39,8 +39,8 @@ 动态规划和 DFS 区别 -- 二叉树 子问题是没有交集,所以大部分二叉树都用递归或者分治法,即 DFS,就可以解决 -- 像 triangle 这种是有重复走的情况,**子问题是有交集**,所以可以用动态规划来解决 +- 二叉树 子问题是没有交集,所以大部分二叉树都用递归或者分治法,即 ```DFS```,就可以解决 +- 像 ```triangle``` 这种是有重复走的情况,**子问题是有交集**,所以可以用动态规划来解决 动态规划,自底向上 @@ -81,7 +81,7 @@ func min(a, b int) int { 动态规划,自顶向下 -```go +```cpp // 测试用例: // [ // [2], @@ -89,48 +89,35 @@ 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] +int minimumTotal(vector>& triangle) { + if(triangle.size() == 0 || triangle[0].size() == 0) return 0; + + int m = triangle.size(); + vector> dp; // 1、状态定义:f[i][j] 表示从0,0出发,到达i,j的最短路径 + + + int sum = 0; + for(int i = 0; i < m; i++){ + vector row = triangle[i]; + sum += row[0]; + vector rd(row.size(), 0); + rd[0] = sum; + dp.push_back(rd); // 初始化 + } + + int res = dp[m-1][0]; + for(int i = 1; i < m; i++){ + vector row = triangle[i]; + for(int j = 1; j < row.size(); j++){ + if(j < row.size() - 1) + dp[i][j] = min(dp[i-1][j], dp[i-1][j-1]) + row[j]; //上一层有左值 + else + dp[i][j] = dp[i-1][j-1] + row[j]; //上一层没有左值 + if(i == m - 1) res = min(res, dp[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] - } else { - f[i][j] = min(f[i-1][j], f[i-1][j-1]) + triangle[i][j] - } - } - } - result := f[l-1][0] - for i := 1; i < len(f[l-1]); i++ { - result = min(result, f[l-1][i]) - } - return result -} -func min(a, b int) int { - if a > b { - return b - } - return a + + return res; } ``` @@ -146,14 +133,14 @@ Function(x) { } ``` -动态规划:是一种解决问 题的思想,大规模问题的结果,是由小规模问 题的结果运算得来的。动态规划可用递归来实现(Memorization Search) +动态规划:是一种解决问题的思想,大规模问题的结果,是由小规模问题的结果运算得来的。动态规划可用递归来实现(Memorization Search) ## 使用场景 满足两个条件 - 满足以下条件之一 - - 求最大/最小值(Maximum/Minimum ) + - 求最大/最小值(Maximum/Minimum) - 求是否可行(Yes/No ) - 求可行个数(Count(\*) ) - 满足不能排序或者交换(Can not sort / swap ) @@ -164,11 +151,11 @@ Function(x) { 1. **状态 State** - 灵感,创造力,存储小规模问题的结果 -2. 方程 Function +2. **方程 Function** - 状态之间的联系,怎么通过小的状态,来算大的状态 -3. 初始化 Intialization +3. **初始化 Intialization** - 最极限的小状态是什么, 起点 -4. 答案 Answer +4. **答案 Answer** - 最大的那个状态是什么,终点 ## 常见四种类型 @@ -180,7 +167,7 @@ Function(x) { > 注意点 > -> - 贪心算法大多题目靠背答案,所以如果能用动态规划就尽量用动规,不用贪心算法 +> - 贪心算法大多题目靠背答案,所以如果能用动态规划就尽量用动规,不用贪心算法。 ## 1、矩阵类型(10%) @@ -189,38 +176,30 @@ Function(x) { > 给定一个包含非负整数的  *m* x *n*  网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。 思路:动态规划 -1、state: f[x][y]从起点走到 x,y 的最短路径 -2、function: f[x][y] = min(f[x-1][y], f[x][y-1]) + A[x][y] -3、intialize: f[0][0] = A[0][0]、f[i][0] = sum(0,0 -> i,0)、 f[0][i] = sum(0,0 -> 0,i) -4、answer: f[n-1][m-1] - -```go -func minPathSum(grid [][]int) int { - // 思路:动态规划 - // f[i][j] 表示i,j到0,0的和最小 - if len(grid) == 0 || len(grid[0]) == 0 { - 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] - } - for j := 1; j < len(grid[0]); j++ { - grid[0][j] = grid[0][j] + grid[0][j-1] - } - 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] + - 1、state: f[x][y]从起点走到 x,y 的最短路径 + - 2、function: f[x][y] = min(f[x-1][y], f[x][y-1]) + A[x][y] + - 3、intialize: f[0][0] = A[0][0]、f[i][0] = sum(0,0 -> i,0)、 f[0][i] = sum(0,0 -> 0,i) + - 4、answer: f[n-1][m-1] + +```cpp +int minPathSum(vector>& grid) { + if(grid.empty() || grid[0].empty()) return 0; + int m = grid.size(); + int n = grid[0].size(); + + // vector> dp(m, vector(n, INT_MAX)); + // dp[0][0] = grid[0][0]; + for(int i = 0; i < m; i++){ + for(int j = 0; j < n; j++){ + int left = INT_MAX; + int up = INT_MAX; + if(i-1 >= 0) left = grid[i-1][j]; + if(j-1 >= 0) up = grid[i][j-1]; + if(i != 0 || j != 0 ) grid[i][j] = min(left, up) + 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[m-1][n-1]; } ``` @@ -230,24 +209,22 @@ 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] +```cpp +int uniquePaths(int m, int n) { + vector> dp(m, vector(n)); + + dp[0][0] = 1; + for(int i = 0; i < m; i++){ + for(int j = 0; j < n; j++){ + int left = 0; + int up = 0; + if(i - 1 >= 0) left = dp[i-1][j]; + if(j - 1 >= 0) up = dp[i][j-1]; + if(i != 0 || j != 0)dp[i][j] = left + up; + } + } + + return dp[m-1][n-1]; } ``` @@ -258,43 +235,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] +```cpp +int uniquePathsWithObstacles(vector>& obstacleGrid) { + if(obstacleGrid.size() == 0 || obstacleGrid[0].size() == 0 || obstacleGrid[0][0]) return 0; + + int m = obstacleGrid.size(); + int n =obstacleGrid[0].size(); + + for(int i = 0; i < m; i++){ + for(int j = 0; j < n; j++){ + if(obstacleGrid[i][j]){ + obstacleGrid[i][j] = 0; + continue; + } + if(i == 0 && j == 0){ + obstacleGrid[i][j] = 1; + continue; + } + int left = 0; + int up = 0; + if(i - 1 >= 0) left = obstacleGrid[i-1][j]; + if(j - 1 >= 0) up = obstacleGrid[i][j-1]; + obstacleGrid[i][j] = left + up; + } + } + + return obstacleGrid[m-1][n-1]; } ``` @@ -304,19 +270,19 @@ 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] +```cpp +int climbStairs(int n) { + if(n <= 2) return n; + int res = 0; + int f1 = 1; + int f2 = 2; + for(int i = 3; i <= n; i++){ + res = f1 + f2; //复用变量 + f1 = f2; + f2 = res; } - return f[n] + + return res; } ``` @@ -326,60 +292,84 @@ 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 - } +```cpp +bool canJump(vector& nums) { +// 第一种解法:会超时,时间复杂度高 +// 思路:看最后一跳 +// 状态:f[i] 表示是否能从0跳到i +// 推导:f[i] = OR(f[j],j dp(nums.size()); + dp[0] = true; + for(int i = 1; i < nums.size(); i++){ + for(int j = 0; j < i; j++){ + if(dp[j] && j + nums[j] >= i) dp[i] = true; } } - return f[len(nums)-1] + return dp[nums.size() - 1]; } ``` +```cpp +bool canJump(vector& nums) { + //version2: 直接找最远能跳到的距离 + if(nums.empty()) return true; + int res = 0; + for(int i = 0; i < nums.size(); i++){ + if(i > res) return false; + res = max(res, i + nums[i]); + } + + return true; + +} +``` + + ### [jump-game-ii](https://leetcode-cn.com/problems/jump-game-ii/) > 给定一个非负整数数组,你最初位于数组的第一个位置。 > 数组中的每个元素代表你在该位置可以跳跃的最大长度。 > 你的目标是使用最少的跳跃次数到达数组的最后一个位置。 -```go -func jump(nums []int) int { +```cpp +int jump(vector& nums) { + // version 1 // 状态: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]) - } + if(nums.empty()) return 0; + vector dp(nums.size(), INT_MAX); + dp[0] = 0; //第一个位置不需要跳跃. + for(int i = 1; i < nums.size(); i++){ + for(int j = 0; j < i; j++){ + if(j + nums[j] >= i) dp[i] = min(dp[i], dp[j] + 1); } } - return f[len(nums)-1] + + return dp[nums.size() - 1]; } -func min(a, b int) int { - if a > b { - return b +``` + +```cpp +int jump(vector& nums) { + // version 2: 贪心算法 + if(nums.empty()) return 0; + int end = 0; + int res = 0; + int maxPos = 0; + for(int i = 0; i < nums.size() - 1; i++){ + maxPos = max(maxPos, i + nums[i]); + if(i == end){ + end = maxPos; + res++; + } } - return a + return res; } ``` @@ -388,43 +378,56 @@ func min(a, b int) int { > 给定一个字符串 _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 +```cpp +int minCut(string s) { + if(s.length() == 0) return 0; + int n = s.length(); + vector> isPalind(n, vector(n, false)); // 记录下s总那些是位置之间是回文子串 + + for(int i = 0; i < n; i++) isPalind[i][i] = true; //单个字母都是回文 + + for(int start = n - 1; start >= 0; start--){ + for(int end = start + 1; end < n; end++){ + if(s[start] == s[end]){ + if(end - start > 1) + isPalind[start][end] = isPalind[start+1][end-1]; + else isPalind[start][end] = true; + } + else isPalind[start][end] = false; + } + } + + // now lets populate the second table, every index in 'cuts' stores the minimum cuts needed + // for the substring from that index till the end + //cuts verson1: + /*vector cuts(n, 0); + for (int startIndex = n - 1; startIndex >= 0; startIndex--) { + int minCuts = n; // maximum cuts + for (int endIndex = n - 1; endIndex >= startIndex; endIndex--) { + if (isPalind[startIndex][endIndex]) { + // we can cut here as we got a palindrome + // also we dont need any cut if the whole substring is a palindrome + minCuts = (endIndex == n - 1) ? 0 : min(minCuts, 1 + cuts[endIndex + 1]); + } + } + cuts[startIndex] = minCuts; + } + + return cuts[0];*/ + + //////version 2 + vector cuts(n + 1); + cuts[0] = -1; + cuts[1] = 0; + for(int i = 1; i <= n; i++){ + cuts[i] = i - 1; + for(int j = 0; j < i; j++){ + if(isPalind[j][i-1]) + cuts[i] = min(cuts[i], cuts[j] + 1); + } + } + return cuts[n]; +} } ``` @@ -436,96 +439,155 @@ func isPalindrome(s string, i, j int) bool { > 给定一个无序的整数数组,找到其中最长上升子序列的长度。 -```go -func lengthOfLIS(nums []int) int { +```cpp +int lengthOfLIS(vector& nums) { // f[i] 表示从0开始到i结尾的最长序列长度 // f[i] = max(f[j])+1 ,a[j] dp(n, 1); + int res = 0; + for(int i = 1; i < n; i++){ + for(int j = 0; j < i; j++){ + if(nums[i] > nums[j]) dp[i] = max(dp[i], dp[j] + 1); } + res = max(res, dp[i]); } - result := f[0] - for i := 1; i < len(nums); i++ { - result = max(result, f[i]) + + return res; +} +``` +> 本题还有第二种解法,利用二分查找,降低复杂度 +```cpp +int lengthOfLIS(vector& nums) { + if(nums.empty() || nums.size() == 1) return nums.size(); + int n = nums.size(); + + vector seq; + seq.push_back(nums[0]); + + for(int i = 1; i < n; i++){ + int res = seq.size(); + if(seq[res-1] < nums[i]){ + seq.push_back(nums[i]); + } + else{ // 二分找到大于等于nums[i]的数 + int k = bisearch(seq, nums[i]); + seq[k] = nums[i]; + } } - return result + return seq.size(); } -func max(a, b int) int { - if a > b { - return a + +int bisearch(vector& nums, int num){ + int left = 0, right = nums.size() - 1; + while(left + 1 < right){ + int mid = left + (right - left) / 2; + if(nums[mid] < num) left = mid; + else right = mid; } - return b + + if(nums[left] >= num) return left; + else return right; } ``` + ### [word-break](https://leetcode-cn.com/problems/word-break/) > 给定一个**非空**字符串  *s*  和一个包含**非空**单词列表的字典  *wordDict*,判定  *s*  是否可以被空格拆分为一个或多个在字典中出现的单词。 -```go -func wordBreak(s string, wordDict []string) bool { +```cpp +bool wordBreak(string s, vector& wordDict) { // f[i] 表示前i个字符是否可以被切分 // f[i] = f[j] && s[j+1~i] in wordDict // f[0] = true // return f[len] + int n = s.length(); + int m = wordDict.size(); + + vector dp(n + 1, false); + dp[0] = true; + + for(int i = 1; i <= n; i++){ + for(int j = 0; j < m; j++){ + string word = wordDict[j]; + int w = word.length(); + if(i >= w && s.substr(i-w, w) == word && dp[i-w]){ + dp[i] = true; + break; + } + } + } + + return dp[n]; - if len(s) == 0 { - return true - } - f := make([]bool, len(s)+1) - f[0] = true - max := maxLen(wordDict) - for i := 1; i <= len(s); i++ { - for j := i - max; j < i && j >= 0; j++ { - if f[j] && inDict(s[j:i]) { - f[i] = true - break - } - } - } - return f[len(s)] } +``` -var dict = make(map[string]bool) +### [perfect-squares](https://leetcode-cn.com/problems/perfect-squares/) +> 给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, ...)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。 -func maxLen(wordDict []string) int { - max := 0 - for _, v := range wordDict { - dict[v] = true - if len(v) > max { - max = len(v) - } - } - return max -} +- 思路:动态规划法 +- 思路2:广度优先搜索法 + +```cpp +int numSquares(int n) { + vector dp(n + 1, n); + dp[0] = 0; + for(int i = 1; i <= n; i++){ + int N = sqrt(i); + for(int j = 1; j <= N; j++) + dp[i] = min(dp[i], dp[i - j * j] + 1); + } -func inDict(s string) bool { - _, ok := dict[s] - return ok + return dp[n]; } +``` + +```cpp +int numSquares(int n) { + + queue qe; + vector visited(n+1, false); + qe.push(n); + visited[n] = true; + int steps = 0; + + while(!qe.empty()){ + int m = qe.size(); + for(int i = 0; i < m; i++){ + int N = qe.front(); + qe.pop(); + if(N == 0) return steps; + for(int j = 1; j <= sqrt(N); j++){ + int k = N - j * j; + if(!visited[k]){ + qe.push(k); + visited[k] = true; + } + } + } + steps++; + } + return steps; +} ``` +- 注意bfs的时候要剪枝,利用一个数组记录已经计算过的节点 小结 -常见处理方式是给 0 位置占位,这样处理问题时一视同仁,初始化则在原来基础上 length+1,返回结果 f[n] +常见处理方式是给 0 位置占位,这样处理问题时一视同仁,初始化则在原来基础上```length+1```,返回结果 f[n] - 状态可以为前 i 个 -- 初始化 length+1 -- 取值 index=i-1 -- 返回值:f[n]或者 f[m][n] +- 初始化 ```length+1``` +- 取值 ```index=i-1``` +- 返回值:```f[n]```或者 ```f[m][n]``` ## Two Sequences DP(40%) @@ -535,8 +597,8 @@ func inDict(s string) bool { > 一个字符串的   子序列   是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。 > 例如,"ace" 是 "abcde" 的子序列,但 "aec" 不是 "abcde" 的子序列。两个字符串的「公共子序列」是这两个字符串所共同拥有的子序列。 -```go -func longestCommonSubsequence(a string, b string) int { +```cpp +int longestCommonSubsequence(string text1, string text2) { // dp[i][j] a前i个和b前j个字符最长公共子序列 // dp[m+1][n+1] // ' a d c e @@ -544,27 +606,20 @@ func longestCommonSubsequence(a string, b string) int { // 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 - } else { - dp[i][j]=max(dp[i-1][j],dp[i][j-1]) - } + int s1 = text1.length(); + int s2 = text2.length(); + + vector> dp(s1 + 1, vector(s2 + 1)); + dp[0][0] = 0; + + for(int i = 1; i <= s1; i++){ + for(int j = 1; j <= s2; j++){ + if(text1[i-1] == text2[j-1]) dp[i][j] = 1 + dp[i-1][j-1]; + else 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[s1][s2]; } ``` @@ -593,36 +648,25 @@ for i:=0;i<=len(a);i++ { 思路:和上题很类似,相等则不需要操作,否则取删除、插入、替换最小操作次数的值+1 ```go -func minDistance(word1 string, word2 string) int { +int minDistance(string word1, string word2) { // 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(s1 + 1, vector(s2 + 1)); + + for(int i = 0; i <= s1; i++) dp[i][0] = i; + for(int j = 0; j <= s2; j++) dp[0][j] = j; + + for(int i = 1; i <= s1; i++){ + for(int j = 1; j <= s2; j++){ + if(word1[i-1] == word2[j-1]) dp[i][j] = dp[i-1][j-1]; //相等不需要操作 + else dp[i][j] = min(dp[i-1][j], min(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[s1][s2]; } ``` @@ -638,35 +682,22 @@ func min(a,b int)int{ 思路:和其他 DP 不太一样,i 表示钱或者容量 -```go -func coinChange(coins []int, amount int) int { +```cpp // 状态 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) - } +int coinChange(vector& coins, int amount) { + if(coins.empty()) return -1; + vector dp(amount + 1, amount+1); + dp[0] = 0; + for(int i = 1; i <= amount; i++){ + for(int j = 0; j < coins.size(); j++){ + if(i >= coins[j]) dp[i] = min(dp[i-coins[j]] + 1, dp[i]); } } - if dp[amount] > amount { - return -1 - } - return dp[amount] -} -func min(a,b int)int{ - if a>b{ - return b - } - return a + return dp[amount] == amount+1?-1: dp[amount]; } ``` @@ -678,68 +709,92 @@ func min(a,b int)int{ > 在 n 个物品中挑选若干物品装入背包,最多能装多满?假设背包的大小为 m,每个物品的大小为 A[i] -```go -func backPack (m int, A []int) int { +```cpp +int backPack(vector& A, int m) { // 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 - } + if(A.empty()) return 0; + int n = A.size(); + vector> dp(n, vector(m + 1)); //最多能装多少东西 + + for(int i = 0; i < n; i++){ + dp[i][0] = 0; //背包大小为0,能装下最多也是0 + } + + for(int i = 0; i <= m; i++){ + if(A[0] <= i) dp[0][i] = A[0]; //只有一件物品 + } + + for(int i = 1; i < n; i++){ + for(int j = 1; j <= m; j++){ + int weight1 = dp[i-1][j]; + int weight2 = 0; + if(A[i] <= j) weight2 = dp[i-1][j-A[i]] + A[i]; + dp[i][j] = max(weight1, weight2); } } - for i:=m;i>=0;i--{ - if f[len(A)][i] { - return i + + return dp[n-1][m]; +} +``` +观察上面循环过程就会发现,每次迭代只用到```i```和```i-1```两行,所以可以优化如下: +```cpp +int backPack(vector& A, int m){ + if(A.empty()) return 0; + int n = A.size(); + vector> dp(2, vector(m + 1)); //只需要保存上一次循环结果就行了 + + for(int i = 0; i <= m; i++) + if(A[0] <= i) dp[0][i] = dp[1][i] = A[0]; //只有一件物品 + + for(int i = 1; i < n; i++){ + for(int j = 1; j <= m; j++){ + int weight1 = dp[i%2-1][j]; + int weight2 = 0; + if(A[i] <= j) weight2 = dp[i%2-1][j-A[i]] + A[i]; + dp[i%2][j] = max(weight1, weight2); } } - return 0 -} + return dp[n%2 - 1][m]; + +} ``` + ### [backpack-ii](https://www.lintcode.com/problem/backpack-ii/description) > 有 `n` 个物品和一个大小为 `m` 的背包. 给定数组 `A` 表示每个物品的大小和数组 `V` 表示每个物品的价值. > 问最多能装入背包的总价值是多大? 思路:f[i][j] 前 i 个物品,装入 j 背包 最大价值 +- 跟上一题类似,只不过重量换成了利润 +- 可以优化成上一题 -```go -func backPackII (m int, A []int, V []int) int { +```cpp +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]) - } + if(A.empty()) return 0; + int n = A.size(); + vector> dp(n, vector(m + 1)); + + for(int i = 0; i < n; i++){ + dp[i][0] = 0; + } + + for(int i = 0; i <= m; i++){ + if(A[0] <= i) dp[0][i] = V[0]; + } + + for(int i = 1; i < n; i++){ + for(int j = 1; j <= m; j++){ + int profit1 = dp[i-1][j]; + int profit2 = 0; + if(A[i] <= j) profit2 = dp[i-1][j-A[i]] + V[i]; + dp[i][j] = max(profit1, profit2); } } - return f[len(A)][m] -} -func max(a,b int)int{ - if a>b{ - return a - } - return b + + return dp[n-1][m]; } ``` @@ -760,6 +815,7 @@ Sequence (40%) - [ ] [palindrome-partitioning-ii](https://leetcode-cn.com/problems/palindrome-partitioning-ii/) - [ ] [longest-increasing-subsequence](https://leetcode-cn.com/problems/longest-increasing-subsequence/) - [ ] [word-break](https://leetcode-cn.com/problems/word-break/) +- [ ] [perfect-squares](https://leetcode-cn.com/problems/perfect-squares/) Two Sequences DP (40%) diff --git a/basic_algorithm/sort.md b/basic_algorithm/sort.md index 9eaa2cb8..d4f07b02 100644 --- a/basic_algorithm/sort.md +++ b/basic_algorithm/sort.md @@ -1,86 +1,191 @@ # 排序 +```cpp +//help函数 +//交换数组a中的i和j +void swap(vector & a, int i, int j ){ + int tmp = a[i]; + a[i] = a[j]; + a[j] = tmp; +} + +//判断是否是排序好的数组 +bool isSorted(vector& a, int i, int j){ + for(int k = i; k < j; k++){ + if(a[k] > a[k + 1]) return false; + } + + return true; +} +``` + ## 常考排序 +### 选择排序 +```cpp +//选择排序 +void selectionSort(vector& a){ + int n = a.size(); + for(int i = 0; i < n; i++){ + int min = i; + for(int j = i + 1; j < n; j++){ + if(a[j] < a[min]) + min = j; + } + if(min != i) + swap(a, i, min); + } + +} +``` + +### 插入排序 + +```cpp +//插入排序 +void insertionSort(vector& a){ + int n = a.size(); + for(int i = 1; i < n; i++){ + for(int j = i; j > 0; j--){ + if(a[j] < a[j - 1]){ + swap(a, j, j - 1); + } + else break; + } + } +} +``` + +### 希尔排序(壳排序) +```cpp +//壳排序 +void shellSort(vector& a){ + + int n = a.size(); + int H = 1; + while(H < n) H = 3 * H + 1; //壳的序列 + + for(int h = H; h >= 1; h = (h - 1) / 3){ + // 插入排序 + for(int i = 1; i < n; i++){ + for(int j = i; j > h; j-=h){ + if(a[j] < a[j - h]) swap(a, j, j - h); + else break; + } + } + + } + +} +``` + ### 快速排序 -```go -func QuickSort(nums []int) []int { - // 思路:把一个数组分为左右两段,左段小于右段 - quickSort(nums, 0, len(nums)-1) - return nums +```cpp +//快速排序,快排 +void quickSort(vector& a){ + + random_shuffle(a); //保证快排复杂度下限 + int n = a.size(); + quick_sort(a, 0, n - 1); +} +void quick_sort(vector& a, int low, int high){ + if(low >= high) return; + int pivot = partition(a, low, high); + quick_sort(a, low, pivot - 1); + quick_sort(a, pivot + 1, high); } -// 原地交换,所以传入交换索引 -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) + +static int partition(vector& a, int low, int high){ + int i = low, j = high + 1; + while(i < j){ + while(a[++i] < a[low]) + if(i == high) break; + + while(a[low] < a[--j]) + if(j == low) break; + swap(a, i, j); } + swap(a, low, j); + return j; } -// 分区 -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++ - } + +// 选择k +static int select(vector& a, int k){ + int low = 0, high = a.size() - 1; + while(low < high){ + int pivot = partition(a, low, high); + if(pivot < k) low = pivot + 1; + else if(pivot > k) high = pivot - 1; + else return a[k]; } - // 把基准值换到中间 - swap(nums, i, end) - return i + return a[k]; } -// 交换两个元素 -func swap(nums []int, i, j int) { - t := nums[i] - nums[i] = nums[j] - nums[j] = t + +//重复键值,3-way partition +void quick3ways(vector& a, int low, int high){ + // See page 289 for public sort() that calls this method. + if (high <= low) return; + int lt = low, i = low + 1, gt = high; + int v = a[low]; + while (i <= gt) + { + if (a[i] < v) swap(a, lt++, i++); + else if (a[i] > v) swap(a, i, gt--); + else i++; + } // Now a[lo..lt-1] < v = a[lt..gt] < a[gt+1..hi]. sort(a, lo, lt - 1); + quick3ways(a, low, lt - 1); + quick3ways(a, gt + 1, high); } ``` ### 归并排序 -```go -func MergeSort(nums []int) []int { - return mergeSort(nums) +```cpp +//归并排序 +/*version1 递归*/ +void mergeSort(vector& a){ + int n = a.size(); + vector aux(n); + merge_sort(a, aux, 0, n - 1); +} + +void merge_sort(vector& a, vector& aux, int low, int high){ + if(low >= high) return; + + int middle = low + (high - low)/2; + merge_sort(a, aux, low, middle); + merge_sort(a, aux, middle + 1, high); + merge(a, aux, low, middle, high); } -func mergeSort(nums []int) []int { - if len(nums) <= 1 { - return nums + +void merge(vector& a, vector& aux, int low, int middle, int high){ + assert isSorted(a, low, middle); + assert isSorted(a, middle + 1, high); + + for(int k = low; k <= high; k++) + aux[k] = a[k]; + + int i = low, j = middle + 1; + int k = low; + for(int k = low; k <= high; k++){ + if(i > middle) a[k] = aux[j++]; + else if(j > high) a[k] = aux[i++]; + else if(a[i] > a[j]) a[k] = aux[j++]; + else a[k] = aux[i++]; } - // 分治法: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++ +} + +/*version 2 bottom-up 归并排序 */ +void buMergeSort(vector& a){ + int n = a.size(); + vector aux(n); + for(int i = 1; i <= n; i = 2 * i){ + for(int low = 0; low < n - i; low += 2 * i){ + merge(a, aux, low, low + i - 1, min(n - 1, low + 2 * i - 1)); } } - // 剩余部分合并 - result = append(result, left[l:]...) - result = append(result, right[r:]...) - return } ``` @@ -98,53 +203,42 @@ 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] +```cpp +//堆排序 +//核心代码 +void heapSort(vector& a){ + // 1. 无序数组a + // 2. 构建成一个大根堆 + for(int i = a.size() / 2 - 1; i >= 0; i--){ + sink(a, i, a.size()); + } + //3. 交换a[0] 和 a[a.size()-1] + //4. 然后把前面这段数组持续下沉保持堆结构,如此循环 + for (int i = a.size() - 1; i >= 1; i--){ + // 从后往前填充 + swap(a, 0, i); + // 前面的长度减一 + sink(a, 0, i); + } +} + +void sink(vector& a, int i, int length){ + while(true){ + // 左节点索引(从0开始,所以左节点为i*2+1) + int l = 2 * i + 1; + //右节点索引 + int r = 2 * i + 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); + i = idx; + } } ``` diff --git a/data_structure/array.md b/data_structure/array.md new file mode 100644 index 00000000..65a9b228 --- /dev/null +++ b/data_structure/array.md @@ -0,0 +1,485 @@ +# 数组 + +## 基本技能 + +数组是数据结构中最基础的一块,基础考点: + +- 边界处理 +- 数据的插入删除 +- 多维数组 + +## 常见题型 + +### [移除元素](https://leetcode.cn/problems/remove-element/) +> 给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。 +> 不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。 +> 元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。 + +思路:两种方法,一是倒序便利,不断删除等于val的元素,剩下的就是新书组;二是正向遍历去使用快慢指针,不断把不等于val的元素给变换到数组的前边 + +**Python版本一** +```python +class Solution: + def removeElement(self, nums: List[int], val: int) -> int: + if len(nums) == 0: return 0 + i = len(nums) - 1 + while i >= 0: + if nums[i] == val: nums.pop(i) + i = i - 1 + return len(nums) +``` + +**Python版本二** +```python +class Solution: + def removeElement(self, nums: List[int], val: int) -> int: + if len(nums) == 0: return 0 + slow = 0 + for i in range(len(nums)): + if nums[i] == val: continue + else: + nums[slow] = nums[i] + slow = slow + 1 + return slow +``` + +### [删除有序数组中的重复项](https://leetcode.cn/problems/remove-duplicates-from-sorted-array/) +>给你一个 升序排列 的数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。 +> +>由于在某些语言中不能改变数组的长度,所以必须将结果放在数组nums的第一部分。更规范地说,如果在删除重复项之后有 k 个元素,那么 nums 的前 k 个元素应该保存最终结果。 +> +>将最终结果插入 nums 的前 k 个位置后返回 k 。 +> +>不要使用额外的空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。 + +思路: 同样的快慢指针 + +**Python版本** +```python +class Solution: + def removeDuplicates(self, nums: List[int]) -> int: + slow, i = 0, 0 + while i < len(nums): + while i < len(nums) - 1 and nums[i] == nums[i + 1]: + i = i + 1 + nums[slow] = nums[i] + slow = slow + 1 + i = i + 1 + + return slow +``` + +### [移动零](https://leetcode.cn/problems/move-zeroes/) +> 给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。 +> 请注意 ,必须在不复制数组的情况下原地对数组进行操作。 + +**Python版本** +```python +class Solution: + def moveZeroes(self, nums: List[int]) -> None: + """ + Do not return anything, modify nums in-place instead. + """ + slow = 0 + for i in range(len(nums)): + if nums[i] == 0: continue + else: + nums[slow] = nums[i] + slow = slow + 1 + + while slow < len(nums): + nums[slow] = 0 + slow = slow + 1 + return nums + ``` + +### [有序数组的平方](https://leetcode.cn/problems/squares-of-a-sorted-array/) +> 给你一个按 非递减顺序 排序的整数数组 nums,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。 + +思路:本题经典的双指针法,但有两种思路,一是从小到大去排列,一种是从大到小逆序排列,但第二种更直接,第一种需要先找到最小值在的位置,而最大值我们是知道的,不是在数组头部就是数组尾部。 + +**Python版本一** +```python +class Solution: + def sortedSquares(self, nums: List[int]) -> List[int]: + pos = 0 + while pos < len(nums) and nums[pos] < 0: pos += 1 + + neg_nums = nums[0:pos] + neg_nums = neg_nums[::-1] # reverse + pos_nums = nums[pos:len(nums)] + + res = list() + ni, pi = 0, 0 + while ni < len(neg_nums) or pi < len(pos_nums): + if ni >= len(neg_nums): + res.append(pos_nums[pi] * pos_nums[pi]) + pi += 1 + continue + if pi >= len(pos_nums): + res.append(neg_nums[ni] * neg_nums[ni]) + ni += 1 + continue + if abs(neg_nums[ni]) < pos_nums[pi]: + res.append(neg_nums[ni] * neg_nums[ni]) + ni += 1 + else: + res.append(pos_nums[pi] * pos_nums[pi]) + pi += 1 + return res +``` + +**Python版本二** +```python + +class Solution: + def sortedSquares(self, nums: List[int]) -> List[int]: + res = [0] * len(nums) + i, j, r = 0, len(nums) - 1, len(nums) - 1 + while i <= j: + if nums[i] * nums[i] > nums[j] * nums[j]: + res[r] = nums[i] * nums[i] + i = i + 1 + else: + res[r] = nums[j] * nums[j] + j = j - 1 + r = r - 1 + + return res + +``` + +### [比较含退格的字符串](https://leetcode.cn/problems/backspace-string-compare/) +> 给定 s 和 t 两个字符串,当它们分别被输入到空白的文本编辑器后,如果两者相等,返回 true 。# 代表退格字符。注意:如果对空文本输入退格字符,文本继续为空。 + +思路:两种方法,直接堆栈和双指针 + +**Python版本一** +```python +class Solution: + def backspaceCompare(self, s: str, t: str) -> bool: + s_b = backspace(s) + t_b = backspace(t) + return s_b == t_b + +def backspace(s): + s_b = list() + i = 0 + while i < len(s): + if s[i] != '#': s_b.append(s[i]) + else: + if len(s_b): s_b.pop() + i = i + 1 + return ''.join(s_b) + +``` + +**Python版本二** +```python +class Solution: + def backspaceCompare(self, s: str, t: str) -> bool: + ## 倒着遍历 + sr, tr = len(s) - 1, len(t) - 1 + while sr >= 0 or tr >= 0: + skip = 0 + while skip >= 0 and sr >= 0: + if s[sr] != '#': skip -= 1 + else: skip += 1 + sr -= 1 + st = s[sr+1] if skip < 0 else '' + + skip = 0 + while skip >= 0 and tr >= 0: + if t[tr] != '#': skip -= 1 + else: skip += 1 + tr -= 1 + tt = t[tr+1] if skip < 0 else '' + + if st != tt: return False + return True +``` + +### [长度最小的子数组](https://leetcode.cn/problems/minimum-size-subarray-sum/) +> 给定一个含有 n 个正整数的数组和一个正整数 target 。 +> 找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl+1, ..., numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0 。 + +思路:经典的双指针法解决,一个记录start,另一个正常遍历 + +**Python版本** +```python +class Solution: + def minSubArrayLen(self, target: int, nums: List[int]) -> int: + ## 双指针 + s = 0 + start = 0 + res = len(nums) + 1 + for i, num in enumerate(nums): + s += num + while s >= target and start <= i: + subL = i - start + 1 + if subL < res: res = subL + s -= nums[start] + start = start + 1 + + + if res == len(nums) + 1: return 0 + else: return res +``` + +### [水果成篮](https://leetcode.cn/problems/fruit-into-baskets/) +>你正在探访一家农场,农场从左到右种植了一排果树。这些树用一个整数数组 fruits 表示,其中 fruits[i] 是第 i 棵树上的水果 种类 。 +> +> 你想要尽可能多地收集水果。然而,农场的主人设定了一些严格的规矩,你必须按照要求采摘水果: +> +> 你只有 两个 篮子,并且每个篮子只能装 单一类型 的水果。每个篮子能够装的水果总量没有限制。 +你可以选择任意一棵树开始采摘,你必须从 每棵 树(包括开始采摘的树)上 恰好摘一个水果 。采摘的水果应当符合篮子中的水果类型。每采摘一次,你将会向右移动到下一棵树,并继续采摘。 +一旦你走到某棵树前,但水果不符合篮子的水果类型,那么就必须停止采摘。 +给你一个整数数组 fruits ,返回你可以收集的水果的 **最大** 数目。 + +思路:跟上面的长度最小子数组类似,本题则是求解长度最大子数组 +**Python版本** +```python +from collections import Counter +class Solution: + def totalFruit(self, fruits: List[int]) -> int: + ''' + 滑动窗口,窗口内子数组满足只有两个unique数字,寻找最大长度窗口. + ''' + res = 0 + start = 0 + window = Counter() + for end in range(len(fruits)): + num_e = fruits[end] + window[num_e] += 1 + while len(window) > 2 and start <= end: + num_s = fruits[start] + window[num_s] -= 1 + if window[num_s] == 0: window.pop(num_s) + start = start + 1 + subL = end - start + 1 + res = max(res, subL) + + return res +``` + +### [最小覆盖子串](https://leetcode.cn/problems/minimum-window-substring/) +> 给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 "" 。 +> +> **注意**: +> 对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。 +> 如果 s 中存在这样的子串,我们保证它是唯一的答案。 + +思路一:利用Counter计数器,比较方便但耗时非常多 + +**Python版本一** +```python +from collections import Counter +class Solution: + def minWindow(self, s: str, t: str) -> str: + if len(s) < len(t): return "" + start = 0 + tc = Counter(t) + wc = Counter() + res = len(s) + 1 + min_s = 0 + for end in range(len(s)): + wc[s[end]] += 1 + while wc >= tc and start <= end: + subL = end - start + 1 + if res > subL: + res = subL + min_s = start + wc[s[start]] -= 1 + if wc[s[start]] == 0: wc.pop(s[start]) + start += 1 + + if res <= len(s): + return s[min_s:min_s + res] + else: + return "" +``` + +**Python版本二** +```python +from collections import Counter +class Solution: + def minWindow(self, s: str, t: str) -> str: + ct = Counter() + for ch in t: ct[ch] += 1 + need = len(t) + start = 0 + res = len(s) + 1 + wind = [-1, -1] + for end in range(len(s)): + ch = s[end] + if ch in ct: + if ct[ch] > 0: need -= 1 + ct[ch] -= 1 + + while need == 0 and start <= end: + subL = end - start + 1 + if res > subL: + res = subL + wind = [start, end+1] + ch = s[start] + if ch in ct: + if ct[ch] == 0: need += 1 + ct[ch] += 1 + start = start + 1 + + if res != len(s) + 1: + return s[wind[0]:wind[1]] + else: return "" + +``` + + + +### [spiral-matrix](https://leetcode-cn.com/problems/spiral-matrix/) + +> 给定一个包含 *m* x *n* 个元素的矩阵(*m* 行, *n* 列),请按照顺时针螺旋顺序,返回矩阵中的所有元素。 + +**C++版本** +```cpp +vector spiralOrder(vector>& matrix) { + int rows = matrix.size(); + if( rows == 0) return {}; + int cols = matrix[0].size(); + int rl = 0, rh = rows - 1; + int cl = 0, ch = cols - 1; + + vector res; + while(true) { + for(int i = cl; i <= ch; i++) res.push_back(matrix[rl][i]); //从左往右 + if(++rl > rh) break; // 下一步从上往下,考虑边界 + for(int i = rl; i <= rh; i++) res.push_back(matrix[i][ch]); //从上往下 + if(--ch < cl) break; //下一步从右往左,考虑边界 + for(int i = ch; i >= cl; i--) res.push_back(matrix[rh][i]); // 从右往左 + if(--rh < rl) break; // 下一步从下往上,考虑边界;注意rl已经增加了 + for(int i = rh; i >= rl; i--) res.push_back(matrix[i][cl]); + if(++cl > ch) break; //下一步循环从右往左,考虑边界 + } + return res; +} +``` + +**Python版本一** +```python +class Solution: + def spiralOrder(self, matrix: List[List[int]]) -> List[int]: + rows = len(matrix) + cols = len(matrix[0]) + rl, rh = 0, rows - 1 + cl, ch = 0, cols - 1 + + res = list() + while True: + i = cl + while i <= ch: + res.append(matrix[rl][i]) + i = i + 1 + rl = rl + 1 + if rl > rh: break + + i = rl + while i <= rh: + res.append(matrix[i][ch]) + i = i + 1 + ch = ch - 1 + if ch < cl: break + + i = ch + while i >= cl: + res.append(matrix[rh][i]) + i = i - 1 + rh = rh - 1 + if rh < rl: break + + i = rh + while i >= rl: + res.append(matrix[i][cl]) + i = i - 1 + cl = cl + 1 + if cl > ch: break + + return res +``` + +新思路:我们可以每次旋转90度matrix,然后直接取第一行的数即可。 + +**Python版本二** +```python +class Solution: + def spiralOrder(self, matrix: List[List[int]]) -> List[int]: + res = list() + while matrix: + res.extend(matrix.pop(0)) + # 矩阵旋转90度 + matrix = list(zip(*matrix))[::-1] + + return res +``` + +### [spiral-matrix II](https://leetcode.cn/problems/spiral-matrix-ii/) +> 螺旋矩阵II: 给你一个正整数 n ,生成一个包含 1 到 n2 所有元素,且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix 。 + +**思路**:跟螺旋矩阵I一样,只是倒过来 + +**Python版本** +```python +class Solution: + def generateMatrix(self, n: int) -> List[List[int]]: + matrix = [[0 for i in range(n)] for i in range(n)] + rl, rh, cl, ch = 0, n - 1, 0, n - 1 + cnt = 1 + while True: + i = cl + while i <= ch: + matrix[rl][i] = cnt + cnt = cnt + 1 + i = i + 1 + rl = rl + 1 + if rl > rh: break + + i = rl + while i <= rh: + matrix[i][ch] = cnt + cnt = cnt + 1 + i = i + 1 + ch = ch - 1 + if ch < cl: break + + i = ch + while i >= cl: + matrix[rh][i] = cnt + cnt = cnt + 1 + i = i - 1 + rh = rh - 1 + if rh < rl: break + + i = rh + while i >= rl: + matrix[i][cl] = cnt + cnt = cnt + 1 + i = i - 1 + cl = cl + 1 + if cl > ch: break + + return matrix +``` + + + + +## 练习题 +- [ ] [移除元素](https://leetcode.cn/problems/remove-element/) +- [ ] [删除有序数组中的重复项](https://leetcode.cn/problems/remove-duplicates-from-sorted-array/) +- [ ] [移动零](https://leetcode.cn/problems/move-zeroes/) +- [ ] [有序数组的平方](https://leetcode.cn/problems/squares-of-a-sorted-array/) +- [ ] [比较含退格的字符串](https://leetcode.cn/problems/backspace-string-compare/) +- [ ] [spiral-matrix](https://leetcode-cn.com/problems/spiral-matrix/) +- [ ] [长度最小的子数组](https://leetcode.cn/problems/minimum-size-subarray-sum/) +- [ ] [水果成篮](https://leetcode.cn/problems/fruit-into-baskets/) +- [ ] [最小覆盖子串](https://leetcode.cn/problems/minimum-window-substring/) +- [ ] [spiral-matrix](https://leetcode-cn.com/problems/spiral-matrix/) +- [ ] [spiral-matrix II](https://leetcode.cn/problems/spiral-matrix-ii/) \ No newline at end of file diff --git a/data_structure/binary_op.md b/data_structure/binary_op.md index e77b8adc..5200a5a8 100644 --- a/data_structure/binary_op.md +++ b/data_structure/binary_op.md @@ -32,15 +32,14 @@ diff=(n&(n-1))^n > 给定一个**非空**整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。 -```go -func singleNumber(nums []int) int { - // 10 ^10 == 00 - // 两个数异或就变成0 - result:=0 - for i:=0;i& nums) { + int a = 0; + for(int i = 0; i < nums.size(); i++){ + a = a^nums[i]; } - return result + + return a; } ``` @@ -48,20 +47,21 @@ func singleNumber(nums []int) int { > 给定一个**非空**整数数组,除了某个元素只出现一次以外,其余每个元素均出现了三次。找出那个只出现了一次的元素。 -```go -func singleNumber(nums []int) int { - // 统计每位1的个数 - var result int - for i := 0; i < 64; i++ { - sum := 0 - for j := 0; j < len(nums); j++ { - // 统计1的个数 - sum += (nums[j] >> i) & 1 - } - // 还原位00^10=10 或者用| 也可以 - result ^= (sum % 3) << i - } - return result +```cpp +int singleNumber(vector& nums) { + // 统计位数上1出现的次数 + int res = 0; + + for(int i = 0; i < 32; i++){ + int sum = 0; + + for(int j = 0; j < nums.size(); j++) + sum += (nums[j] >> i) & 1; //右移 + + res ^= (sum % 3) << i; //左移,这里可以推广到其他每个元素出现k次,找只出现1次的元素. + } + return res; + } ``` @@ -69,28 +69,24 @@ 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) { + if(nums.empty()) return {-1, -1}; + int a = 0; + for(int i = 0; i < nums.size(); i++){ + a ^= nums[i]; } - result:=[]int{diff,diff} - // 去掉末尾的1后异或diff就得到最后一个1的位置 - diff=(diff&(diff-1))^diff - for i:=0;i res{a, a}; + //得到末尾的1 + a = (a&(a-1)) ^ a; + for(int i = 0; i < nums.size(); i++){ + if((a & nums[i]) == 0) + res[0] ^= nums[i]; + else + res[1] ^= nums[i]; } - return result + + return res; } ``` @@ -98,14 +94,14 @@ func singleNumber(nums []int) []int { > 编写一个函数,输入是一个无符号整数,返回其二进制表达式中数字位数为 ‘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++ +```cpp +int hammingWeight(uint32_t n) { + int res = 0; + while(n > 0){ + n = n & (n-1); + res++; } - return res + return res; } ``` @@ -113,34 +109,34 @@ 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) +```cpp +vector countBits(int num) { + vector res; + for(int i = 0; i <= num; i++){ + res.push_back(count(i)); } - return res + return res; } -func count1(n int)(res int){ - for n!=0{ - n=n&(n-1) - res++ + +int count(int n){ + int res = 0; + while(n){ + n = n&(n-1); + res++; } - return + return res; } ``` 另外一种动态规划解法 -```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 +```cpp +vector countBits(int num) { + vector res(num+1, 0); + for(int i = 1; i <= num; ++ i) { + res[i] = res[i&(i-1)] + 1; } - return res + return res; } ``` @@ -150,17 +146,16 @@ 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-- +```cpp +uint32_t reverseBits(uint32_t n) { + uint32_t res = 0; + int i = 31; + while(n){ + res += (n&1)<>=1; + i--; } - return res + return res; } ``` @@ -168,23 +163,24 @@ 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++ +```cpp +// 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; + i++; } - return m< List[int]: + res = list() + self.preOrderRecursion(root, res) + return res +``` -```go -func preorderTraversal(root *TreeNode) { - if root==nil{ - return - } - // 先访问根再访问左右 - fmt.Println(root.Val) - preorderTraversal(root.Left) - preorderTraversal(root.Right) +```cpp +//前中后序遍历只在这个函数有区别, 调整push_back的顺序 +void preOrderRecursion(TreeNode* root, vector& res){ + if(root == nullptr) return {}; + res.push_back(root->val); + preOrderRecursion(root->left, res); + preOrderRecursion(root->right, res); +} + +vector preOrderTraversal(TreeNode* root){ + + vector res; + + preOrderRecursion(root, res); + + return res; } ``` + #### 前序非递归 +```python + +``` -```go -// V3:通过非递归遍历 -func preorderTraversal(root *TreeNode) []int { +```cpp +// 通过非递归遍历: 用栈进行辅助 +vector preorderTraversal(TreeNode* root){ // 非递归 - if root == nil{ - return nil + if(root == nullptr) return {}; + vector res; + + stack st; + st.push(root); + + while(!st.empty()){ + TreeNode* node = st.top(); + st.pop(); + res.push_back(node->val); + //注意先入右节点进栈 + if(!node->right) st.push(node->right); + if(!node->left) st.push(node->left); } - 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 - } - // pop - node:=stack[len(stack)-1] - stack=stack[:len(stack)-1] - root=node.Right - } - return result + + return res; } ``` +#### 中序递归 +```python +class Solution: + def inorderRecursion(self, root, res): + if root is None: return + self.inorderRecursion(root.left, res) + res.append(root.val) + self.inorderRecursion(root.right, res) + def inorderTraversal(self, root: Optional[TreeNode]) -> List[int]: + res = list() + self.inorderRecursion(root, res) + return res +``` #### 中序非递归 -```go +```cpp // 思路:通过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 == nullptr) return {}; + vector res; + + stack st; + + while(true){ + if(root != nullptr){ + st.push(root); + root = root->left; //一直向左 } - // 弹出 - val := stack[len(stack)-1] - stack = stack[:len(stack)-1] - result = append(result, val.Val) - root = val.Right + else if(!st.empty()){ + root = st.top(); //弹出 + st.pop(); + res.push_back(root->val); + root = root->right;  //转向右子树 + } + else break; } - return result + + return res; + } ``` + +#### 后续递归 +```python +class Solution: + def postorderRecursion(self, root, res): + if root is None: return + self.postorderRecursion(root.left, res) + self.postorderRecursion(root.right, res) + res.append(root.val) + def postorderTraversal(self, root: Optional[TreeNode]) -> List[int]: + res = list() + self.postorderRecursion(root, res) + return res + +``` + #### 后序非递归 -```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 +```cpp +vector postorderTraversal(TreeNode* root){ + // 通过栈辅助,同时插入nullptr节点判断右子节点是否弹出 + if(root == nullptr) return {}; + vector res; + + stack st; + st.push(root); + while(!st.empty()){ + TreeNode* node = st.top(); + if(node == nullptr){ //说明是根节点了 + st.pop(); + res.push_back(st.top()->val); + st.pop(); + continue; + } + st.push(nullptr); + if(node->right != nullptr) st.push(node->right); + if(node->left != nullptr) st.push(node->left); + } + + return res; } ``` @@ -118,91 +192,75 @@ func postorderTraversal(root *TreeNode) []int { #### DFS 深度搜索-从上到下 -```go -type TreeNode struct { - Val int - Left *TreeNode - Right *TreeNode -} - -func preorderTraversal(root *TreeNode) []int { - result := make([]int, 0) - dfs(root, &result) - return result +```cpp +vector preorderTraversal(TreeNode* root){ + vector res; + dfs(root, res); + return res; } // V1:深度遍历,结果指针作为参数传入到函数内部 -func dfs(root *TreeNode, result *[]int) { - if root == nil { - return - } - *result = append(*result, root.Val) - dfs(root.Left, result) - dfs(root.Right, result) +void dfs(TreeNode* root, vector& res) { + if(root == nullptr) return; + res.push_back(root->val); + dfs(root->left, res); + dfs(root->right, res); } ``` #### DFS 深度搜索-从下向上(分治法) -```go +```cpp // V2:通过分治法遍历 -func preorderTraversal(root *TreeNode) []int { - result := divideAndConquer(root) - return result +vector preorderTraversal(TreeNode* root){ + vector res = divideAndConquer(root); + return res; } -func divideAndConquer(root *TreeNode) []int { - result := make([]int, 0) + +vector divideAndConquer(TreeNode* root){ // 返回条件(null & leaf) - if root == nil { - return result - } + if(root == nullptr) return {}; // 分治(Divide) - left := divideAndConquer(root.Left) - right := divideAndConquer(root.Right) + vector res_left = divideAndConquer(root->left); + vector res_right = divideAndConquer(root->right); // 合并结果(Conquer) - result = append(result, root.Val) - result = append(result, left...) - result = append(result, right...) - return result + vector res; + res.push_back(root->val); + res.insert(res.end(), res_left.begin(), res_right.end()); + + return res; } ``` 注意点: -> DFS 深度搜索(从上到下) 和分治法区别:前者一般将最终结果通过指针参数传入,后者一般递归返回结果最后合并 +> DFS 深度搜索(从上到下) 和分治法(从下到上)区别:前者一般将最终结果通过指针参数传入,后者一般递归返回结果最后合并,会return一个值 #### BFS 层次遍历 -```go -func levelOrder(root *TreeNode) [][]int { +```cpp +vector> levelOrder(TreeNode* root){ + vector> res; + if(root == nullptr) return res; + queue nodes; + nodes.push(root); // 通过上一层的长度确定下一层的元素 - 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) - } + while(!nodes.empty()){ + int levelSize = nodes.size(); //记录当前层有多少元素,遍历当前层,并添加下一层 + vector levelVals; + for(int i = 0; i < levelSize; i++){ + TreeNode* tmpNode = nodes.front(); + nodes.pop(); + levelVals.push_back(tmpNode->val); + if(tmpNode->left != nullptr) nodes.push(tmpNode->left); + if(tmpNode->right != nullptr) nodes.push(tmpNode->right); } - result = append(result, list) + res.push_back(levelVals); } - return result + + return res; } + ``` ### 分治法应用 @@ -221,86 +279,99 @@ func levelOrder(root *TreeNode) [][]int { - 分段处理 - 合并结果 -```go -func traversal(root *TreeNode) ResultType { - // nil or leaf - if root == nil { - // do something and return +```cpp +template +T traversal(TreeNode* root){ + // null + if(root == nullptr) { + //do something and return } // Divide - ResultType left = traversal(root.Left) - ResultType right = traversal(root.Right) + T left = traversal(root->left); + T right = traversal(root->right); // Conquer - ResultType result = Merge from left and right + T res = Merge from left and right - return result + return res; } ``` #### 典型示例 -```go +```cpp // V2:通过分治法遍历二叉树 -func preorderTraversal(root *TreeNode) []int { - result := divideAndConquer(root) - return result +vector preorderTraversal(TreeNode* root){ + vector res = divideAndConquer(root); + return res; } -func divideAndConquer(root *TreeNode) []int { - result := make([]int, 0) - // 返回条件(null & leaf) - if root == nil { - return result - } - // 分治(Divide) - left := divideAndConquer(root.Left) - right := divideAndConquer(root.Right) - // 合并结果(Conquer) - result = append(result, root.Val) - result = append(result, left...) - result = append(result, right...) - return result + +vector divideAndConquer(TreeNode* root){ + vector res; + if(root == nullptr) return res; //处理异常 + + // Divide + vector left = divideAndConquer(root->left); + vector right = divideAndConquer(root->right); + + // Conquer + res.push_back(root->val); + res.insert(res.end(), left.begin(), left.end()); + res.insert(res.end(), right.begin(), right.end()); + + return res; } ``` #### 归并排序   -```go -func MergeSort(nums []int) []int { - return mergeSort(nums) +```cpp + +void mergeSort(vector& a){ + int n = a.size(); + vector aux; + merge_sort(a, aux, 0, n - 1); } -func mergeSort(nums []int) []int { - if len(nums) <= 1 { - return nums + +void merge_sort(vector& a, vector& aux, int low, int high){ + if(low >= high) return; + + // 分治: middle分成两半 + int middle = low + (high - low) / 2; + merge_sort(a, aux, low, middle); + merge_sort(a, aux, middle + 1, high); + + // 合并 + merge(a, aux, low, middle, high); +} + +void merge(vector& a, vector& aux, int low, int middle, int high){ + assert isSorted(a, low, middle); + assert isSorted(a, middle + 1, high); + + for(int k = low; k < high; k++) + aux[k] = a[k]; + + int i = low, j = middle + 1; + for(int k = low; k < high; k++){ + if(i > middle) a[k] = aux[j++]; + else if(j > high) a[k] = aux[i++]; + else if(aux[i] > aux[j]) a[k] = aux[j++]; + else a[k] = aux[i++]; } - // 分治法: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++ - } + + assert isSorted(a, low, high); + +} + +//判断是否是排序好的数组 +bool isSorted(vector& a, int i, int j){ + for(int k = i; k < j; k++){ + if(a[k] > a[k + 1]) return false; } - // 剩余部分合并 - result = append(result, left[l:]...) - result = append(result, right[r:]...) - return + + return true; } ``` @@ -310,40 +381,49 @@ 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 +```cpp +void quickSort(vector& a){ + //思路:找到数组中某个index,将数组分成两段,左段小于右段,类似分治法,但没有合并 + random_shuffle(a); //重要!保证快排复杂度下限 + int n = a.size(); + quick_sort(a, 0, n - 1); +} + +void quick_sort(vector& a, int low, int high){ + if(low >= high) return; //异常 + + int pivot = partion(a, low, high); //找到某个数 + // 分治 + quick_sort(a, low, pivot - 1); + quick_sort(a, pivot + 1, high); +} + +int partion(vector& a, int low, int high){ + if(low == high) return low; + + srand(time(0)); + int pivotIdx = low + rand() % (high - low); + swap(a, pivotIdx, high); + + int pivot = a[high]; + for(int i = low; i < high; i++){ + // all elements less than 'pivot' will be before the index 'low'. + if(a[i] < pivot){ + swap(a, i, low); + low++; + } + } + + // put the pivot into the right place. + swap(a, low, high); + + return low; +} + +void swap(vector& a, int i, int j){ + int tmp = a[i]; + a[i] = a[j]; + a[j] = tmp; } ``` @@ -362,21 +442,16 @@ func swap(nums []int, i, j int) { 思路:分治法 -```go -func maxDepth(root *TreeNode) int { - // 返回条件处理 - if root == nil { - return 0 - } - // divide:分左右子树分别计算 - left := maxDepth(root.Left) - right := maxDepth(root.Right) +```cpp +int maxDepth(TreeNode* root){ + if (root == nullptr) return 0; - // conquer:合并左右子树结果 - if left > right { - return left + 1 - } - return right + 1 + // Divide + int leftDepth = maxDepth(root->left); + int rightDepth = maxDepth(root->right); + + // Conquer + return 1 + max(leftDepth, rightDepth); // 注意加一 } ``` @@ -390,29 +465,22 @@ func maxDepth(root *TreeNode) int { 因为需要返回是否平衡及高度,要么返回两个数据,要么合并两个数据, 所以用-1 表示不平衡,>0 表示树高度(二义性:一个变量有两种含义)。 -```go -func isBalanced(root *TreeNode) bool { - if maxDepth(root) == -1 { - return false - } - return true +```cpp +bool isBalanced(TreeNode* root) { + if(maxDepth(root) == -1) return false; + return true; } -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 - } - if left > right { - return left + 1 +int maxDepth(TreeNode* root){ + if(root == nullptr) return 0; + int leftDepth = maxDepth(root->left); + int rightDepth = maxDepth(root->right); + if(leftDepth == -1 || rightDepth == -1 || leftDepth - rightDepth > 1 || rightDepth - leftDepth > 1) + { + return -1; } - return right + 1 + return 1 + max(leftDepth, rightDepth); + } ``` @@ -428,45 +496,22 @@ 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) +```cpp +int maxPathSum(TreeNode* root) { + int maxSum = INT_MIN; + dfs(root, maxSum); + return maxSum; +} - // 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 -} -func max(a,b int) int { - if a > b { - return a - } - return b +int dfs(TreeNode* root, int& maxSum){ + if(root == nullptr) return 0; + int leftPathSum = dfs(root->left, maxSum); + leftPathSum = max(leftPathSum, 0); //注意跟0比较,小于0,那这条边就不添加了 + int rightPathSum = dfs(root->right, maxSum); + rightPathSum = max(rightPathSum, 0); + + maxSum = max(root->val + leftPathSum + rightPathSum, maxSum); + return max(leftPathSum, rightPathSum) + root->val; } ``` @@ -478,33 +523,19 @@ func max(a,b int) int { 思路:分治法,有左子树的公共祖先或者有右子树的公共祖先,就返回子树的祖先,否则返回根节点 -```go -func lowestCommonAncestor(root, p, q *TreeNode) *TreeNode { - // check - if root == nil { - return root - } - // 相等 直接返回root节点即可 - if root == p || root == q { - return root - } +```cpp +TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) { + if(root == nullptr) return nullptr; // check + if(root == p || root == q) return root; // 相等,返回root + // Divide - left := lowestCommonAncestor(root.Left, p, q) - right := lowestCommonAncestor(root.Right, p, q) + TreeNode* leftNode = lowestCommonAncestor(root->left, p, q); + TreeNode* rightNode = lowestCommonAncestor(root->right, p, q); + // conquer + if(leftNode != nullptr && rightNode != nullptr) return root; - // Conquer - // 左右两边都不为空,则根节点为祖先 - if left != nil && right != nil { - return root - } - if left != nil { - return left - } - if right != nil { - return right - } - return nil + return leftNode==nullptr?rightNode:leftNode; // check } ``` @@ -518,34 +549,27 @@ 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 +```cpp +vector> levelOrder(TreeNode* root) { + if(root == nullptr) return {}; + vector> res; + queue nodes; + nodes.push(root); + + while(!nodes.empty()){ + int levelSize = nodes.size(); + vector levelVals; + for(int i = 0; i < levelSize; i++){ + TreeNode* node = nodes.front(); + nodes.pop(); + levelVals.push_back(node->val); + if(node->left != nullptr) nodes.push(node->left); + if(node->right != nullptr) nodes.push(node->right); + } + res.push_back(levelVals); + } + + return res; } ``` @@ -555,47 +579,44 @@ 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 +思路: + +-- 在层级遍历的基础上,翻转一下结果即可 + +-- 使用```deque```在前端插入结果,最后再复制回```vector``` + +```cpp +vector> levelOrderBottom(TreeNode* root) { + if(root == nullptr) return {}; + + vector> res; + // deque> dq; + queue nodes; + nodes.push(root); + + while(!nodes.empty()){ + int levelSize = nodes.size(); + vector levelVals; + for(int i = 0; i < levelSize; i++){ + TreeNode* node = nodes.front(); + nodes.pop(); + levelVals.push_back(node->val); + if(node->left != nullptr) nodes.push(node->left); + if(node->right != nullptr) nodes.push(node->right); + } + res.push_back(levelVals); + // dq.push_front(levelVals); + } + + reverse(res.begin(), res.end()); //翻转 + + // copy to vector + // while(!dq.empty()){ + // res.push_back(dq.front()); + // dq.pop_front(); + // } + + return res; } ``` @@ -605,45 +626,33 @@ 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 - } +思路:跟上面模板一样,注意reverse就行了 + +```cpp +vector> zigzagLevelOrder(TreeNode* root) { + if(root == nullptr) return {}; + + vector> res; + queue nodes; + nodes.push(root); + + bool isReverse = false; + while(!nodes.empty()){ + int levelSize = nodes.size(); + vector levelVals; + for(int i = 0; i < levelSize; i++){ + TreeNode* node = nodes.front(); + nodes.pop(); + levelVals.push_back(node->val); + if(node->left != nullptr) nodes.push(node->left); + if(node->right != nullptr) nodes.push(node->right); + } + if(isReverse) reverse(levelVals.begin(), levelVals.end()); + res.push_back(levelVals); + isReverse = !isReverse; + } + + return res; } ``` @@ -659,85 +668,57 @@ func reverse(nums []int) { 思路 2:分治法,判断左 MAX < 根 < 右 MIN -```go +```cpp // 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 == nullptr) return true; + // 中序遍历,判断是否升序 + vector res; + + inOrder(root, res); + for(int i = 0; i < res.size() - 1; i++){ + if(res[i] >= res[i + 1]) return false; + } + return true; +} + +void inOrder(TreeNode* root, vector& res){ + if(root == nullptr) return; + //// v1: 递归 + inOrder(root->left, res); + res.push_back(root->val); + inOrder(root->right, res); + //// v2: 非递归 + /* + stack st; + while(true){ + if(root != nullptr){ + st.push(root); + root = root->left; + } + else if(!st.empty()){ + root = st.top(); + st.pop(); + res.push_back(root->val); + root = root->right; } + else break; } - return true + */ } +``` -func inOrder(root *TreeNode, result *[]int) { - if root == nil{ - return - } - inOrder(root.Left, result) - *result = append(*result, root.Val) - inOrder(root.Right, result) +```cpp +// v2分治法 +bool isValidBST(TreeNode* root) { + return validate(root, LONG_MIN, LONG_MAX); } +bool validate(TreeNode* root, long mi, long ma){ + if(root == nullptr) return true; + if(root->val <= mi || root->val >= ma) return false; -``` - -```go -// 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 + return validate(root->left, mi, root->val) && validate(root->right, root->val, ma); } ``` @@ -749,22 +730,66 @@ func helper(root *TreeNode) ResultType { 思路:找到最后一个叶子节点满足插入条件即可 -```go +```cpp // 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 == nullptr) { + 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) + else + root->right = insertIntoBST(root->right, val); + + return root; +} +``` + +二叉搜索树的删除要复杂点,考虑子节点的数目分为以下三种: +- 目标节点没有子节点,则直接移除该目标节点 +- 目标节点只有一个子节点,用其子节点直接代替 +- 目标节点有两个子节点,需要用其中序后继节点或者前驱节点来替换,再删除该目标节点。 + +#### delete-node-in-a-bst + +[delete-node-in-a-bst](https://leetcode-cn.com/problems/delete-node-in-a-bst/) +```cpp +TreeNode* deleteNode(TreeNode* root, int key) { + if(root == nullptr) return root; + + if(root->val < key){ + root->right = deleteNode(root->right, key); + } else if(root->val > key){ + root->left = deleteNode(root->left, key); + } else{ + //分情况讨论 + //只有左节点,替换为右节点 + //只有右节点,替换为左节点 + //左右节点都存在,将左子树节点放到右子树的最左边节点的左子树 + if(root->right == nullptr) return root->left; + else if(root->left == nullptr) return root->right; + else{ + TreeNode* cur = root->right; + while(cur->left != nullptr) + cur = cur->left; + + cur->left = root->left; + return root->right; + } } - return root + + return root; } ``` +> 高度平衡的二叉搜索树是二叉搜索树中的一种,它的特殊之处在于```每个树节点的子树高度都不超过1```, 高度平衡保证二叉搜索树具有很多优良性质,能够在```O(logN)```时间复杂度上实现搜索插入和删除。 +> - 红黑树 +> - AVL树 +> - 伸展树 +> - 树堆 + ## 总结 - 掌握二叉树递归与非递归遍历 @@ -782,3 +807,7 @@ func insertIntoBST(root *TreeNode, val int) *TreeNode { - [ ] [binary-tree-zigzag-level-order-traversal](https://leetcode-cn.com/problems/binary-tree-zigzag-level-order-traversal/) - [ ] [validate-binary-search-tree](https://leetcode-cn.com/problems/validate-binary-search-tree/) - [ ] [insert-into-a-binary-search-tree](https://leetcode-cn.com/problems/insert-into-a-binary-search-tree/) +- [ ] [path-sum](https://leetcode-cn.com/problems/path-sum/) +- [ ] [path-sum-ii](https://leetcode-cn.com/problems/path-sum-ii/) +- [ ] [sum-root-to-leaf-numbers](https://leetcode-cn.com/problems/sum-root-to-leaf-numbers/) +- [ ] [path-sum-iii](https://leetcode-cn.com/problems/path-sum-iii/) diff --git a/data_structure/hash.md b/data_structure/hash.md new file mode 100644 index 00000000..44856869 --- /dev/null +++ b/data_structure/hash.md @@ -0,0 +1,544 @@ +# 哈希表 +## 哈希表包括哈希集合和哈希映射 +- 哈希集合:**集合**数据结构的实现之一,用于存储**非重复值** +- 哈希映射:**映射**数据结构的实现之一,用于存储```(key, value)```键值对 + +### 哈希集合 + +- 集合的实现之一,存储```不重复值```的数据结构。 +```cpp + +#include // 0. include the library + +int main() { + // 1. initialize a hash set + unordered_set hashset; + // 2. insert a new key + hashset.insert(3); + hashset.insert(2); + hashset.insert(1); + // 3. delete a key + hashset.erase(2); + // 4. check if the key is in the hash set + if (hashset.count(2) <= 0) { + cout << "Key 2 is not in the hash set." << endl; + } + // 5. get the size of the hash set + cout << "The size of hash set is: " << hashset.size() << endl; + // 6. iterate the hash set + for (auto it = hashset.begin(); it != hashset.end(); ++it) { + cout << (*it) << " "; + } + cout << "are in the hash set." << endl; + // 7. clear the hash set + hashset.clear(); + // 8. check if the hash set is empty + if (hashset.empty()) { + cout << "hash set is empty now!" << endl; + } +} +``` + +哈希集合可用来查重,```template```如下 +```cpp +/* + * Template for using hash set to find duplicates. + */ +bool findDuplicates(vector& keys) { + // Replace Type with actual type of your key + unordered_set hashset; + for (Type key : keys) { + if (hashset.count(key) > 0) { + return true; + } + hashset.insert(key); + } + return false; +} + +``` + +### 哈希映射 +- 用于存储```(key, value)```键值对的一种实现。 + +```cpp +#include // 0. include the library + +int main() { + // 1. initialize a hash map + unordered_map hashmap; + // 2. insert a new (key, value) pair + hashmap.insert(make_pair(0, 0)); + hashmap.insert(make_pair(2, 3)); + // 3. insert a new (key, value) pair or update the value of existed key + hashmap[1] = 1; + hashmap[1] = 2; + // 4. get the value of a specific key + cout << "The value of key 1 is: " << hashmap[1] << endl; + // 5. delete a key + hashmap.erase(2); + // 6. check if a key is in the hash map + if (hashmap.count(2) <= 0) { + cout << "Key 2 is not in the hash map." << endl; + } + // 7. get the size of the hash map + cout << "the size of hash map is: " << hashmap.size() << endl; + // 8. iterate the hash map + for (auto it = hashmap.begin(); it != hashmap.end(); ++it) { + cout << "(" << it->first << "," << it->second << ") "; + } + cout << "are in the hash map." << endl; + // 9. clear the hash map + hashmap.clear(); + // 10. check if the hash map is empty + if (hashmap.empty()) { + cout << "hash map is empty now!" << endl; + } +} + +``` + +场景一: +根据键值拿到更多信息,帮助我们做出判断 +```cpp +/* + * Template for using hash map to find duplicates. + * Replace ReturnType with the actual type of your return value. + */ +ReturnType aggregateByKey_hashmap(vector& keys) { + // Replace Type and InfoType with actual type of your key and value + unordered_map hashtable; + for (Type key : keys) { + if (hashmap.count(key) > 0) { + if (hashmap[key] satisfies the requirement) { + return needed_information; + } + } + // Value can be any information you needed (e.g. index) + hashmap[key] = value; + } + return needed_information; +} +``` + +场景二:按键聚合 +```cpp +/* + * Template for using hash map to find duplicates. + * Replace ReturnType with the actual type of your return value. + */ +ReturnType aggregateByKey_hashmap(vector& keys) { + // Replace Type and InfoType with actual type of your key and value + unordered_map hashtable; + for (Type key : keys) { + if (hashmap.count(key) > 0) { + update hashmap[key]; + } + // Value can be any information you needed (e.g. index) + hashmap[key] = value; + } + return needed_information; +} +``` + +### [valid-anagram] (https://leetcode.cn/problems/valid-anagram/) +> 给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。 +> 注意:若 s 和 t 中每个字符出现的次数都相同,则称 s 和 t 互为字母异位词。 + +```python +class Solution: + def isAnagram(self, s: str, t: str) -> bool: + record = dict() + for val in s: + if val in record: record[val] += 1 + else: record[val] = 1 + + for val in t: + if val not in record: return False + else: record[val] -= 1 + + for k, v in record.items(): + if v != 0: return False + + return True + +``` +或者使用```collections```中的```defaultdict```数据结构 +```python +from collections import defaultdict +class Solution: + def str2dict(self, s): + s_dict = defaultdict(int) + for v in s: + s_dict[v] += 1 + return s_dict + def isAnagram(self, s: str, t: str) -> bool: + return self.str2dict(s) == self.str2dict(t) +``` +或者使用```collections```中的```Counter``` 数据结构 +```python +from collections import Counter +class Solution: + def str2cnt(self, s): + return Counter(s) + def isAnagram(self, s: str, t: str) -> bool: + return self.str2cnt(s) == self.str2cnt(t) +``` +### [find-common-characters](https://leetcode.cn/problems/find-common-characters/) +> 给你一个字符串数组 words ,请你找出所有在 words 的每个字符串中都出现的共用字符( 包括重复字符),并以数组形式返回。你可以按 任意顺序 返回答案。 +```python +class Solution: + def intersection(self, list1, list2): + list3 = list() + for val in list1: + if val in list2: + list3.append(val) + list2.remove(val) + return list3 + def commonChars(self, words: List[str]) -> List[str]: + left_keys = list(words[0]) + for word in words[1:]: + left_keys = self.intersection(left_keys, list(word)) + return left_keys +``` + +### [intersection-of-two-arrays](https://leetcode.cn/problems/intersection-of-two-arrays/) +> 给定两个数组 nums1 和 nums2 ,返回 它们的交集 。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序 。 +```python +class Solution: + def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]: + return list(set(nums1).intersection(set(nums2))) +``` + +### [happy-number](https://leetcode.cn/problems/happy-number/) +> 编写一个算法来判断一个数 n 是不是快乐数。 + +> 「快乐数」 定义为: + +> 对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。 +> 然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。 +> 如果这个过程 结果为 1,那么这个数就是快乐数。 +> 如果 n 是 快乐数 就返回 true ;不是,则返回 false 。 +```python +class Solution: + def eleSum(self, n): + res = 0 + while n > 0: + t = n % 10 + res += t * t + n = n // 10 + return res + def isHappy(self, n: int) -> bool: + res = list() + while True: + n = self.eleSum(n) + if n == 1: return True + if n in res: return False + res.append(n) +``` + + +### [two-sum](https://leetcode.cn/problems/two-sum/) +> 给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。 + +> 你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。 + +> 你可以按任意顺序返回答案。 +```python +class Solution: + def twoSum(self, nums: List[int], target: int) -> List[int]: + hashmap = dict() + for i, num in enumerate(nums): + if hashmap.get(target - num) is not None: + return [i, hashmap.get(target - num)] + hashmap[num] = i +``` + +### [3sum](https://leetcode.cn/problems/3sum/) +> 给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请 +> 你返回所有和为 0 且不重复的三元组。 + +> 注意:**答案中不可以包含重复的三元组**。 + +思路:hash表的方法也能用,但使用双指针的解法更快 + +```python +class Solution: + def threeSum(self, nums: List[int]) -> List[List[int]]: + if len(nums) < 3: return [] + + nums = sorted(nums) + res = list() + i = 0 + while i < len(nums): + if nums[i] > 0: break + target = 0 - nums[i] + left, right = i + 1, len(nums) - 1 + while left < right: + sum2 = nums[left] + nums[right] + if sum2 == target: + res.append([nums[i], nums[left], nums[right]]) + while left < right and nums[left+1] == nums[left]:left += 1 + while right > left and nums[right-1] == nums[right]: right -= 1 + left += 1 + right -= 1 + elif sum2 < target: left += 1 + else: right -= 1 + + while i + 1 < len(nums) and nums[i+1] == nums[i]: i += 1 + i += 1 + + return res +``` + +### [4sum](https://leetcode.cn/problems/4sum/) +>给你一个由 n 个整数组成的数组 nums ,和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] (若两个四元组元素一一>对应,则认为两个四元组重复): + +> - 0 <= a, b, c, d < n +> - a、b、c 和 d 互不相同 +> - nums[a] + nums[b] + nums[c] + nums[d] == target +>你可以按 任意顺序 返回答案 。 + +跟上面的3sum一样,利用双指针进行转换,注意循环过程中剪枝和去重。 + +```python +class Solution: + def fourSum(self, nums: List[int], target: int) -> List[List[int]]: + if len(nums) < 4: return [] + nums.sort() + + i = 0 + ans = list() + while i < len(nums) - 3: + if nums[i] * 4 > target: break + tar3 = target - nums[i] + j = i + 1 + while j < len(nums) - 2: + if nums[j] * 3 > tar3: break + tar2 = tar3 - nums[j] + left, right = j + 1, len(nums)-1 + while left < right: + if nums[left] * 2 > tar2: break + sum2 = nums[left] + nums[right] + if sum2 == tar2: + ans.append([nums[i], nums[j], nums[left], nums[right]]) + while left < right and nums[left+1] == nums[left]: left += 1 + while left < right and nums[right-1] == nums[right]: right -= 1 + left += 1 + right -= 1 + elif sum2 < tar2: left += 1 + else: right -= 1 + while j + 1 < len(nums) and nums[j+1] == nums[j]: j+= 1 + j += 1 + + while i + 1 < len(nums) and nums[i + 1] == nums[i]: i += 1 + i += 1 + + return ans +``` + +### [4sum-ii](https://leetcode.cn/problems/4sum-ii/) +> 给你四个整数数组 nums1、nums2、nums3 和 nums4 ,数组长度都是 n ,请你计算有多少个元组 (i, j, k, l) 能满足: +> - 0 <= i, j, k, l < n +> - nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0 + +思路:这个题目跟前面的3sum和4sum不一样,只让找出个数且没有去重逻辑,跟2sum比较类似,所以使用hash表比较方便 + +```python +from collections import defaultdict +class Solution: + def fourSumCount(self, nums1: List[int], nums2: List[int], nums3: List[int], nums4: List[int]) -> int: + res = 0 + n = len(nums1) + hashmap = defaultdict(int) + for i in range(n): + for j in range(n): + hashmap[nums1[i] + nums2[j]] += 1 + + for i in range(n): + for j in range(n): + res += hashmap[-(nums3[i] + nums4[j])] + + return res +``` + + +### [ransom-note](https://leetcode.cn/problems/ransom-note/) +> 给你两个字符串:ransomNote 和 magazine ,判断 ransomNote 能不能由 magazine 里面的字符构成。 +> 如果可以,返回 true ;否则返回 false 。 +> magazine 中的每个字符只能在 ransomNote 中使用一次。 + +思路:利用所谓哈希表或者字典,或者```Counter```数据结构,三种实现方式。 + +```python +class Solution: + def canConstruct(self, ransomNote: str, magazine: str) -> bool: + ran_dict = dict() + for v in ransomNote: + if v in ran_dict: ran_dict[v] += 1 + else: ran_dict[v] = 1 + + for v in magazine: + if v in ran_dict and ran_dict[v] > 0: ran_dict[v] -= 1 + + for k, v in ran_dict.items(): + if v > 0: return False + + return True +``` + +```python +from collections import defaultdict +class Solution: + def str2dict(self, s): + tmp = defaultdict(int) + for k in s: + tmp[k] += 1 + return tmp + def canConstruct(self, ransomNote: str, magazine: str) -> bool: + r_d = self.str2dict(ransomNote) + m_d = self.str2dict(magazine) + + for k, v in r_d.items(): + if k not in m_d or m_d[k] < v: return False + + return True +``` + +```python +from collections import Counter +class Solution: + def str2dict(self, s): + return Counter(s) + def canConstruct(self, ransomNote: str, magazine: str) -> bool: + r_d = self.str2dict(ransomNote) + m_d = self.str2dict(magazine) + + for k, v in r_d.items(): + if k not in m_d or m_d[k] < v: return False + + return True +``` + + + + +### [contains-duplicate-ii](https://leetcode-cn.com/problems/contains-duplicate-ii/) +> 判断是否存在重复元素,且index差绝对值不大于K + +采用```hashset```和滑动窗口的思想 +```cpp +bool containsNearbyDuplicate(vector& nums, int k) { + unordered_set hashset; + + for(int i = 0; i < nums.size(); i++){ + if(hashset.find(nums[i]) != hashset.end()) return true; + hashset.insert(nums[i]); + if(hashset.size() > k) + hashset.erase(nums[i-k]); + } + return false; +} +``` + +### [find-duplicate-subtrees](https://leetcode-cn.com/problems/find-duplicate-subtrees/) +> 找到重复的子树 + +思路: 找重复的数字或者其他item的时候就要想到哈希表,本题还需要一个步骤就是树的序列化 +```cpp +vector findDuplicateSubtrees(TreeNode* root) { + //序列化加dfs + vector res; + unordered_map mp; + dfs(root, res, mp); + return res; +} + +string dfs(TreeNode* root, vector& result, unordered_map& mp){ + if(root == nullptr) return ""; + string st = to_string(root->val) + "," + dfs(root->left, result, mp) + "," + dfs(root->right, result, mp); + if(mp[st] == 1) result.push_back(root); + + mp[st]++; + return st; +} +``` + +### [group-shifted-stringss](https://leetcode-cn.com/problems/group-shifted-strings/) +> 移位字符串分组 +```cpp +vector> groupStrings(vector& strings) { + unordered_map> mp; + + for(int i = 0; i < strings.size(); i++){ + string word = strings[i]; + string code = "0"; + for(int j = 1; j < word.size(); j++){ + int tmp = word[j] - word[0]; + if(tmp < 0) tmp += 26; + code += tmp + '0'; + } + mp[code].push_back(strings[i]); + } + + vector> res; + for(auto it = mp.begin(); it != mp.end(); ++it){ + res.push_back(it->second); + } + + return res; +} +``` + +### [valid-sudoku](https://leetcode-cn.com/problems/valid-sudoku/) +> 判断有效的数独 + +思路: 判断9行,9列,9个box中没有重复数字出现 +```cpp +bool isValidSudoku(vector>& board) { + int rows[9][10] = {0}; + int cols[9][10] = {0}; + int box[9][10] = {0}; + + for(int i = 0; i < 9; i++){ + for(int j = 0; j < 9; j++){ + if(board[i][j] == '.') continue; + int cur = board[i][j] - '0'; + //判断行 + if(rows[i][cur]) return false; + if(cols[j][cur]) return false; + if(box[3 * (i/3) + j/3][cur]) return false; + + rows[i][cur] = 1; + cols[j][cur] = 1; + box[3 * (i/3) + j/3][cur] = 1; + } + } + + return true; +} +``` + +### [find-duplicate-subtrees](https://leetcode-cn.com/problems/find-duplicate-subtrees/) +> 寻找重复子树 + +思路:看到重复二字就知道需要使用哈希,还需要对子树进行编码 + +```cpp +vector findDuplicateSubtrees(TreeNode* root) { + //序列化加dfs + vector res; + unordered_map mp; + dfs(root, res, mp); + return res; +} + +string dfs(TreeNode* root, vector& result, unordered_map& mp){ + if(root == nullptr) return ""; + string st = to_string(root->val) + "," + dfs(root->left, result, mp) + "," + dfs(root->right, result, mp); + if(mp[st] == 1) result.push_back(root); + + mp[st]++; + return st; +} +``` diff --git a/data_structure/linked_list.md b/data_structure/linked_list.md index ebcc1625..a454d92f 100644 --- a/data_structure/linked_list.md +++ b/data_structure/linked_list.md @@ -10,57 +10,142 @@ - 插入一个节点到排序链表 - 从一个链表中移除一个节点 - 翻转链表 +- k个一组翻转链表 - 合并两个链表 - 找到链表的中间节点 ## 常见题型 +### [移除链表元素](https://leetcode.cn/problems/remove-linked-list-elements) + +> 给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点 。 + +```Python +# Definition for singly-linked list. +# class ListNode: +# def __init__(self, val=0, next=None): +# self.val = val +# self.next = next +class Solution: + def removeElements(self, head: Optional[ListNode], val: int) -> Optional[ListNode]: + dummy = ListNode() + dummy.next = head + + prev, cur = dummy, head + while cur: + if cur.val == val: + cur = cur.next + prev.next = cur + else: + prev = cur + cur = cur.next + return dummy.next + +``` + +更简洁的话可以写成下面这样 +```python +class Solution: + def removeElements(self, head: Optional[ListNode], val: int) -> Optional[ListNode]: + dummy = ListNode() + dummy.next = head + + cur = dummy + while cur.next: + if cur.next.val == val: + cur.next = cur.next.next + else: + cur = cur.next + return dummy.next +``` + + + ### [remove-duplicates-from-sorted-list](https://leetcode-cn.com/problems/remove-duplicates-from-sorted-list/) > 给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。 -```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 +**Python版本** +```python +class Solution: + def deleteDuplicates(self, head: Optional[ListNode]) -> Optional[ListNode]: + if head is None: return head + cur = head + while cur.next: + if cur.val == cur.next.val: + cur.next = cur.next.next + else: + cur = cur.next + return head +``` + +**C++版本** +```cpp +ListNode* deleteDuplicates(ListNode* head) { + if(head == nullptr) return head; + + ListNode* cur = head; + ListNode* prev = head; + + while(cur->next != nullptr){ + cur = cur->next; + if(cur->val != prev->val) { //删除所有相同元素,再移动到下一个元素 + prev->next = cur; + prev = cur; } - current = current.Next } - return head + prev->next = nullptr; + return head; } ``` ### [remove-duplicates-from-sorted-list-ii](https://leetcode-cn.com/problems/remove-duplicates-from-sorted-list-ii/) -> 给定一个排序链表,删除所有含有重复数字的节点,只保留原始链表中   没有重复出现的数字。 +> 给定一个排序链表,删除所有含有重复数字的节点,只保留原始链表中,没有重复出现的数字。 思路:链表头结点可能被删除,所以用 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 - } - } else { - head = head.Next - } +**Python版本** +```python +# Definition for singly-linked list. +# class ListNode: +# def __init__(self, val=0, next=None): +# self.val = val +# self.next = next +class Solution: + def deleteDuplicates(self, head: Optional[ListNode]) -> Optional[ListNode]: + if head is None: return head + dummy = ListNode(val = -300, next=head) + + cur = dummy + while cur.next: + node = cur.next + while node is not None and cur.next.val == node.val: + node = node.next + if cur.next.next != node: cur.next = node # 说明经过了删除 + else: cur = cur.next + + return dummy.next +``` +- 因为原始节点可能被删除,使用辅助节点node +- 辅助节点(不断往后面走)跟原始节点进行比较 + + +**C++版本** +```cpp +ListNode* deleteDuplicates(ListNode* head) { + ListNode* dummy = new ListNode(-1); + dummy->next = head; + ListNode* cur = dummy; + + while(cur->next){ + ListNode* node = cur->next; + while(node != nullptr && node->val == cur->next->val) node = node->next; + if(cur->next->next != node) cur->next = node; + else cur = cur->next; } - return dummy.Next + + return dummy->next; } ``` @@ -73,143 +158,382 @@ 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 - } +思路:用一个 prev 节点保存向前指针,node 保存向后的临时指针 + +**Python版本** +```python +def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]: + if head is None: return head + + prev = None + cur = head + while cur != None: + node = cur.next + cur.next = prev + prev = cur + cur = node return prev +``` + + +**C++版本** +```cpp +ListNode* reverseList(ListNode* head) { + if(head == nullptr) return head; + ListNode* prev = nullptr; + ListNode* cur = head; + + ListNode* node = nullptr; + + while(cur != nullptr){ + node = cur->next; + cur->next = prev; + prev = cur; + cur = node; + } + + return prev; + } ``` +### [design-linked-list](https://leetcode.cn/problems/design-linked-list/description/) +> 设计链表的实现。您可以选择使用单链表或双链表。单链表中的节点应该具有两个属性:val 和 next。val 是当前节点的值,next 是指向下一个节点的指针/引用。如果要使用双向链表,则还需要一个属性 prev 以指示链表中的上一个节点。假设链表中的所有节点都是 0-index 的。 + +```python +class ListNode: + def __init__(self, val=0, next=None): + self.val = val + self.next = next + +class MyLinkedList: + + def __init__(self): + self.head = ListNode(-1) + self.num = 0 + + def get(self, index: int) -> int: + if index + 1 > self.num: return -1 + node = self.head + while index > 0: + node = node.next + index -= 1 + return node.val + + def addAtHead(self, val: int) -> None: + if self.head.val == -1: self.head.val = val + else: + node = ListNode(val) + node.next = self.head + self.head = node + + self.num += 1 + + def addAtTail(self, val: int) -> None: + if self.head.val == -1: self.head.val = val + else: + node = self.head + while node.next: node = node.next + node.next = ListNode(val) + + self.num += 1 + + def addAtIndex(self, index: int, val: int) -> None: + if index <= 0: self.addAtHead(val) + elif index == self.num: self.addAtTail(val) + elif index > self.num: return None + else: + node = self.head + while index-1 > 0: + node = node.next + index -= 1 + tmp = ListNode(val) + node_next = node.next + node.next = tmp + tmp.next = node_next + self.num += 1 + + def deleteAtIndex(self, index: int) -> None: + if index + 1 > self.num: return None + self.num -= 1 + node = self.head + if index == 0: + self.head = self.head.next + return None + while index-1 > 0: + node = node.next + index -= 1 + if node.next is not None: + tmp = node.next.next + node.next = tmp + else: + return ListNode(-1) + + +# Your MyLinkedList object will be instantiated and called as such: +# obj = MyLinkedList() +# param_1 = obj.get(index) +# obj.addAtHead(val) +# obj.addAtTail(val) +# obj.addAtIndex(index,val) +# obj.deleteAtIndex(index) +``` + +### [swap-nodes-in-pairs](https://leetcode.cn/problems/swap-nodes-in-pairs/) +> 给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。 + +思路:使用dummy节点,同时考虑两个节点的关系,prev和cur +```python +class Solution: + def swapPairs(self, head: Optional[ListNode]) -> Optional[ListNode]: + dummy = ListNode(next=head) + prev = dummy + cur = head + while cur and cur.next: + node1 = cur.next + node = node1.next + + prev.next = node1 + node1.next = cur + + prev = cur + cur = node + + prev.next = cur + + return dummy.next +``` + +### [remove-nth-node-from-end-of-list](https://leetcode.cn/problems/remove-nth-node-from-end-of-list/) +> 给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。 + +思路:稍微费点脑子,```前后指针``` + +```python +class Solution: + def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]: + dummy = ListNode(next=head) + ## 双指针 + slow, fast = dummy, dummy + while n > 0: + fast = fast.next + n -= 1 + while fast.next is not None: + slow = slow.next + fast = fast.next + prev = slow + node = None + if slow.next is not None: node = slow.next.next + prev.next = node + + return dummy.next + +``` + +### [intersection-of-two-linked-lists-lcci](https://leetcode.cn/problems/intersection-of-two-linked-lists-lcci/) +> 给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null 。 + +思路:很经典的题目,跟上面一题一样,长的链表就先走一步没,对其链表头再一起往前走 ```前后指针``` + +```python +# Definition for singly-linked list. +# class ListNode: +# def __init__(self, x): +# self.val = x +# self.next = None + +class Solution: + def getListLen(self, head:ListNode): + num = 0 + while head is not None: + head = head.next + num += 1 + return num + + def getSameNode(self, headA, headB): + while headA is not None: + if headA == headB: return headA + else: + headA = headA.next + headB = headB.next + return None + + def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode: + ### 双指针 + numA = self.getListLen(headA) + numB = self.getListLen(headB) + diff = abs(numA - numB) + if numA > numB: + while diff > 0: + headA = headA.next + diff -= 1 + else: + while diff > 0: + headB = headB.next + diff -= 1 + + return self.getSameNode(headA, headB) + +``` + + ### [reverse-linked-list-ii](https://leetcode-cn.com/problems/reverse-linked-list-ii/) > 反转从位置  *m*  到  *n*  的链表。请使用一趟扫描完成反转。 思路:先遍历到 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++ +```cpp +ListNode* reverseBetween(ListNode* head, int m, int n) { + ListNode* prev = nullptr; // 利用nullptr指针 + ListNode* cur = head; + + for(int i = 0; i < m - 1; i++){ + prev = cur; + cur = cur->next; } - // 遍历之后: 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++ + + ListNode* firstPart = prev; //第一段最后 + ListNode* lastSecondPart = cur; //翻转之后是第二段最后 + + //翻转之后,prev是第二段开头,cur是第三段开头 + ListNode* temp = nullptr; + for(int i = 0; i < n - m + 1; i++){ + temp = cur->next; + cur->next = prev; + prev = cur; + cur = temp; } - // 循环需要执行四次 - // 循环结束:1 nil<-2<-3<-4 5(head)->nil - pre.Next = next - mid.Next = head - return dummy.Next + + //三段连接起来 + if(firstPart == nullptr) head = prev; //注意head一起翻转的情况 + else firstPart->next = prev; + + lastSecondPart->next = cur; + + return head; } ``` +```python +class Solution: + def reverseBetween(self, head: Optional[ListNode], left: int, right: int) -> Optional[ListNode]: + # if head.next is None: return head + ## 三段式 + dummy = ListNode(next=head) + prev, cur = dummy, head + for i in range(left-1): + prev = cur + cur = cur.next + + begin, end = cur, cur + cur = cur.next + for i in range(right-left): + node = cur.next + cur.next = end + end = cur + cur = node + + # 连起来 + prev.next = end + begin.next = cur + + return dummy.next + +``` + ### [merge-two-sorted-lists](https://leetcode-cn.com/problems/merge-two-sorted-lists/) > 将两个升序链表合并为一个新的升序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 -思路:通过 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 - } else { - head.Next = l2 - l2 = l2.Next - } - head = head.Next - } - // 连接l1 未处理完节点 - for l1 != nil { - head.Next = l1 - head = head.Next - l1 = l1.Next +思路: +- 通过 ```dummy node``` 链表,连接各个元素 +- 利用优先队列```priority_queue```来merge + +```cpp +struct cmp{ + bool operator()(const ListNode* a, const ListNode* b){ + return a->val > b->val; } - // 连接l2 未处理完节点 - for l2 != nil { - head.Next = l2 - head = head.Next - l2 = l2.Next +}; + +ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) { + if(l1 == nullptr && l2 == nullptr) return nullptr; + priority_queue, cmp> minHeap; + + ListNode* head = nullptr; + ListNode* cur = nullptr; + + if(l1 != nullptr) minHeap.push(l1); + if(l2 != nullptr) minHeap.push(l2); + + while(!minHeap.empty()){ + ListNode* node = minHeap.top(); + minHeap.pop(); + if(head == nullptr) head = node; + else cur->next = node; + cur = node; + if(node->next != nullptr) minHeap.push(node->next); } - return dummy.Next + return head; + } ``` +```python +class Solution: + def merge(self, main_node, branch_node): + main_node.next = branch_node + main_node = branch_node + branch_node = branch_node.next + return main_node, branch_node + def mergeTwoLists(self, list1: Optional[ListNode], list2: Optional[ListNode]) -> Optional[ListNode]: + + dummy = ListNode() + cur = dummy + while list1 is not None or list2 is not None: + if list1 is None: + cur, list2 = self.merge(cur, list2) + elif list2 is None: + cur, list1 = self.merge(cur, list1) + else: + if list1.val > list2.val: + cur, list2 = self.merge(cur, list2) + else: + cur, list1 = self.merge(cur, list1) + + return dummy.next +``` + ### [partition-list](https://leetcode-cn.com/problems/partition-list/) > 给定一个链表和一个特定值 x,对链表进行分隔,使得所有小于  *x*  的节点都在大于或等于  *x*  的节点之前。 思路:将大于 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 == nullptr) return head; + ListNode* dummy = new ListNode(-1); + dummy->next = head; + ListNode* cur = dummy; + + ListNode* tail = new ListNode(-1); + ListNode* node = tail; + + while(cur->next != nullptr){ + if(cur->next->val < x) cur = cur->next; + else{ + ListNode* tmp = cur->next; + cur->next = cur->next->next; + node->next = tmp; + node = node->next; } } - // 拼接两个链表 - tail.Next = nil - head.Next = tailDummy.Next - return headDummy.Next + node->next = nullptr; //注意链表尾部 + + cur->next = tail->next; // 链接起来 + return dummy->next; } ``` @@ -223,63 +547,66 @@ func partition(head *ListNode, x int) *ListNode { 思路:归并排序,找中点和合并操作 -```go -func sortList(head *ListNode) *ListNode { - // 思路:归并排序,找中点和合并操作 - return mergeSort(head) +```cpp +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 + +ListNode* findMiddle(ListNode* head){ + ListNode* slow = head; + ListNode* fast = head->next; // 为了得到even nodes下的第一个节点 + + while(fast != nullptr && fast->next != nullptr){ + slow = slow->next; + fast = fast->next->next; } - return slow + + return slow; } -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 + +ListNode* mergeSort(ListNode* head){ + if(head == nullptr || head->next == nullptr) return head; + + ListNode* middle = findMiddle(head); + + ListNode* halfHead = middle->next; + middle->next = nullptr; + + ListNode* newHead = mergeSort(head); + ListNode* newHalf = mergeSort(halfHead); + ListNode* node = merge(newHead, newHalf); + return node; +} + +ListNode* merge(ListNode* head1, ListNode* head2){ + ListNode* dummy = new ListNode(-1); + ListNode* head = dummy; + + while(head1 != nullptr && head2 != nullptr){ + if(head1->val < head2->val){ + head->next = head1; + head1 = head1->next; } - head = head.Next - } - // 连接l1 未处理完节点 - for l1 != nil { - head.Next = l1 - head = head.Next - l1 = l1.Next + else{ + head->next = head2; + head2 = head2->next; + } + head = head->next; } - // 连接l2 未处理完节点 - for l2 != nil { - head.Next = l2 - head = head.Next - l2 = l2.Next + + while(head1 != nullptr){ + head->next = head1; + head1 = head1->next; + head = head->next; } - return dummy.Next -} -func mergeSort(head *ListNode) *ListNode { - // 如果只有一个节点 直接就返回这个节点 - if head == nil || head.Next == nil{ - return head + while(head2 != nullptr){ + head->next = head2; + head2 = head2->next; + head = head->next; } - // find middle - middle := findMiddle(head) - // 断开中间节点 - tail := middle.Next - middle.Next = nil - left := mergeSort(head) - right := mergeSort(tail) - result := mergeTwoLists(left, right) - return result + + return dummy->next; } ``` @@ -296,70 +623,80 @@ func mergeSort(head *ListNode) *ListNode { 思路:找到中点断开,翻转后面部分,然后合并前后两个链表 -```go -func reorderList(head *ListNode) { - // 思路:找到中点断开,翻转后面部分,然后合并前后两个链表 - if head == nil { - return +```cpp +void reorderList(ListNode* head) { + if(head == nullptr || head->next == nullptr) return; + // 找到中间节点 + ListNode* middle = findMiddle(head); + //翻转第二个链表 + ListNode* secondHalf = reverse(middle->next); + middle->next = nullptr; + //merge + merge(head, secondHalf); +} + +//找到中间节点 +ListNode* findMiddle(ListNode* head){ + ListNode* slow = head; + ListNode* fast = head->next; + + while(fast != nullptr && fast->next != nullptr){ + slow = slow->next; + fast = fast->next->next; } - mid := findMiddle(head) - tail := reverseList(mid.Next) - mid.Next = nil - head = mergeTwoLists(head, tail) + + return slow; } -func findMiddle(head *ListNode) *ListNode { - fast := head.Next - slow := head - for fast != nil && fast.Next != nil { - fast = fast.Next.Next - slow = slow.Next + +//翻转链表 +ListNode* reverse(ListNode* head){ + ListNode* cur = head; + ListNode* prev = nullptr;; + + ListNode* node = nullptr; + while(cur != nullptr){ + node = cur->next; + cur->next = prev; + prev = cur; + cur = node; } - return slow + return prev; } -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 + +//merge +void merge(ListNode* head, ListNode* head2){ + ListNode* head1 = head; + + ListNode* dummy = new ListNode(-1); + ListNode* node = dummy; + + bool insertFirst = true; + while(head1 != nullptr && head2 != nullptr){ + if(insertFirst){ + node->next = head1; + head1 = head1->next; } - toggle = !toggle - head = head.Next - } - // 连接l1 未处理完节点 - for l1 != nil { - head.Next = l1 - head = head.Next - l1 = l1.Next + else{ + node->next = head2; + head2 = head2->next; + } + node = node->next; + insertFirst = !insertFirst; } - // 连接l2 未处理完节点 - for l2 != nil { - head.Next = l2 - head = head.Next - l2 = l2.Next + + while(head1 != nullptr){ + node->next = head1; + head1 = head1->next; + node = node->next; } - return dummy.Next -} -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 + + while(head2 != nullptr){ + node->next = head2; + head2 = head2->next; + node = node->next; } - return prev + + head = dummy->next; } ``` @@ -370,57 +707,48 @@ 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 { - if fast.Val == slow.Val { - return true - } - fast = fast.Next.Next - slow = slow.Next +```cpp +bool hasCycle(ListNode *head) { + if(head == nullptr) return false; + + ListNode* fast = head; + ListNode* slow = head; + + while(fast != nullptr && fast->next != nullptr){ + slow = slow->next; + fast = fast->next->next; + if(slow == fast) return true; } - return false + + return false; } ``` -### [linked-list-cycle-ii](https://leetcode-cn.com/problems/https://leetcode-cn.com/problems/linked-list-cycle-ii/) +### [linked-list-cycle-ii](https://leetcode-cn.com/problems/linked-list-cycle-ii/) > 给定一个链表,返回链表开始入环的第一个节点。  如果链表无环,则返回  `null`。 思路:快慢指针,快慢相遇之后,慢指针回到头,快慢指针步调一致一起移动,相遇点即为入环点 ![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 - } - return slow - } - fast = fast.Next.Next - slow = slow.Next - } - return nil +```cpp +ListNode *detectCycle(ListNode *head) { + + if(head == nullptr || head->next == nullptr) return nullptr; + ListNode* slow = head; + ListNode* fast = head; + while(fast != nullptr && fast->next != nullptr){ + slow = slow->next; + fast = fast->next->next; + if(slow == fast) break; + } + if(fast == nullptr || fast->next == nullptr) return nullptr; + fast = head; // 从头开始 + while(slow != fast){ + fast = fast->next; + slow = slow->next; + } + return slow; } ``` @@ -431,86 +759,89 @@ 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 +思路:快慢指针,快慢相遇之后,其中一个指针回到头,快慢指针步调一致一起移动,相遇点即为入环点 +```cpp +ListNode *detectCycle(ListNode *head) { + + if(head == nullptr || head->next == nullptr) return nullptr; + ListNode* slow = head; + ListNode* fast = head; + while(fast != nullptr && fast->next != nullptr){ + slow = slow->next; + fast = fast->next->next; + if(slow == fast){ + fast = head; + while(fast != slow){ + fast = fast->next; + slow = slow->next; } - return slow + return slow; } } - return nil + + return nullptr; } ``` -这两种方式不同点在于,**一般用 fast=head.Next 较多**,因为这样可以知道中点的上一个节点,可以用来删除等操作。 +这两种方式不同点在于,**一般用 fast=head.Next 较多**,因为这样可以知道终点的上一个节点,可以用来删除等操作。 - fast 如果初始化为 head.Next 则中点在 slow.Next - fast 初始化为 head,则中点在 slow +```python +class Solution: + def detectCycle(self, head: Optional[ListNode]) -> Optional[ListNode]: + if head is None or head.next is None: return None + # 快慢指针 + slow, fast = head, head + while fast is not None and fast.next is not None: + slow = slow.next + fast = fast.next.next + if slow == fast: break + if slow != fast: return None + + fast = head + while slow != fast: + slow = slow.next + fast = fast.next + + return slow +``` + ### [palindrome-linked-list](https://leetcode-cn.com/problems/palindrome-linked-list/) > 请判断一个链表是否为回文链表。 -```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 +```cpp +bool isPalindrome(ListNode* head) { + if(head == nullptr || head->next == nullptr) return true; + ListNode* slow = head; + ListNode* fast = head; + while(fast != nullptr && fast->next != nullptr){ + slow = slow->next; + fast = fast->next->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 + ListNode* rev = reverse(slow); + ListNode* prev = head; + while(rev != nullptr && prev != nullptr){ + if(rev->val != prev->val) return false; + rev = rev->next; + prev = prev->next; } - return true - + return true; } -func reverse(head *ListNode)*ListNode{ - // 1->2->3 - if head==nil{ - return head +ListNode* reverse(ListNode* curNode){ + ListNode* prev = nullptr; + ListNode* node = nullptr; + while(curNode != nullptr){ + node = curNode->next; + curNode->next = prev; + prev = curNode; + curNode = node; } - var prev *ListNode - for head!=nil{ - t:=head.Next - head.Next=prev - prev=head - head=t - } - return prev + return prev; } ``` @@ -521,39 +852,64 @@ 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 +```cpp +Node* copyRandomList(Node* head) { + if(head == nullptr) return head; + //复制节点,放在原节点的后面 + Node* cur = head; + Node* tmp = nullptr; + while(cur != nullptr){ + Node* cloneNode = new Node(cur->val, cur->next, nullptr); + tmp = cur->next; + cur->next = cloneNode; + cur = tmp; + } + + //处理random指针 + cur = head; + while(cur != nullptr){ + if(cur->random != nullptr){ + cur->next->random = cur->random->next; + } + cur = cur->next->next; + } + + //分离两个链表 + cur = head; + Node* clone = head->next; + while(cur != nullptr && cur->next != nullptr){ + tmp = cur->next; + cur->next = cur->next->next; + cur = tmp; + } + + return clone; +} +``` + +### [cong-wei-dao-tou-da-yin-lian-biao-lcof](https://leetcode-cn.com/problems/cong-wei-dao-tou-da-yin-lian-biao-lcof/) + +> 从尾到头打印链表 + +思路: +- reverse链表,再次序打印链表的值 +- 不能改动链表的话,借用栈的辅助 + +```cpp +vector reversePrint(ListNode* head) { + if(head == nullptr) return {}; + stack st; + while(head != nullptr){ + st.push(head->val); + head = head->next; + } + + vector res; + while(!st.empty()){ + res.push_back(st.top()); + st.pop(); + } + return res; } ``` @@ -569,6 +925,10 @@ func copyRandomList(head *Node) *Node { - 翻转链表 - 合并两个链表 - 找到链表的中间节点 +- 很多情况下,需要跟踪当前节点的前一个节点 +- 与数组插入删除索引的复杂度对比 + +![复杂度分析](https://s1.ax1x.com/2020/08/02/atRrz6.md.png) ## 练习 @@ -584,3 +944,4 @@ func copyRandomList(head *Node) *Node { - [ ] [linked-list-cycle-ii](https://leetcode-cn.com/problems/https://leetcode-cn.com/problems/linked-list-cycle-ii/) - [ ] [palindrome-linked-list](https://leetcode-cn.com/problems/palindrome-linked-list/) - [ ] [copy-list-with-random-pointer](https://leetcode-cn.com/problems/copy-list-with-random-pointer/) +- [ ] [cong-wei-dao-tou-da-yin-lian-biao-lcof](https://leetcode-cn.com/problems/cong-wei-dao-tou-da-yin-lian-biao-lcof/) diff --git a/data_structure/stack_queue.md b/data_structure/stack_queue.md index e1ed723c..825f896c 100644 --- a/data_structure/stack_queue.md +++ b/data_structure/stack_queue.md @@ -2,75 +2,216 @@ ## 简介 -栈的特点是后入先出 +栈的特点是**后入先出** ![image.png](https://img.fuiboom.com/img/stack.png) -根据这个特点可以临时保存一些数据,之后用到依次再弹出来,常用于 DFS 深度搜索 +根据这个特点可以临时保存一些数据,之后用到依次再弹出来,常用于 ```DFS``` 深度搜索 -队列一般常用于 BFS 广度搜索,类似一层一层的搜索 +队列的特点是**先入先出**, 一般常用于 ```BFS``` 广度搜索,类似一层一层的搜索 ## Stack 栈 -[min-stack](https://leetcode-cn.com/problems/min-stack/) +### [implement-queue-using-stacks](https://leetcode.cn/problems/implement-queue-using-stacks) +> 请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(push、pop、peek、empty): +> 实现 MyQueue 类: + +> - void push(int x) 将元素 x 推到队列的末尾 +> - int pop() 从队列的开头移除并返回元素 +> - int peek() 返回队列开头的元素 +> - boolean empty() 如果队列为空,返回 true ;否则,返回 false +> 说明: +> 你 只能 使用标准的栈操作 —— 也就是只有 push to top, peek/pop from top, size, 和 is empty 操作是合法的。 +> 你所使用的语言也许不支持栈。你可以使用 list 或者 deque(双端队列)来模拟一个栈,只要是标准的栈操作即可。 +```python +class MyQueue: + + def __init__(self): + self.stack = list() + + def push(self, x: int) -> None: + self.stack.append(x) + + def pop(self) -> int: + tmp = None + if len(self.stack): + tmp = self.stack[0] + self.stack = self.stack[1:] + return tmp + + def peek(self) -> int: + return self.stack[0] + + def empty(self) -> bool: + return len(self.stack) == 0 + + +# Your MyQueue object will be instantiated and called as such: +# obj = MyQueue() +# obj.push(x) +# param_2 = obj.pop() +# param_3 = obj.peek() +# param_4 = obj.empty() +``` -> 设计一个支持 push,pop,top 操作,并能在常数时间内检索到最小元素的栈。 +### [/implement-stack-using-queues](https://leetcode.cn/problems/implement-stack-using-queues/) +> 请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(push、top、pop 和 empty)。 +```python +class MyStack: -思路:用两个栈实现,一个最小栈始终保证最小值在顶部 + def __init__(self): + self.deq = list() -```go -type MinStack struct { - min []int - stack []int -} + def push(self, x: int) -> None: + self.deq.append(x) + def pop(self) -> int: + tmp = None + if not self.empty(): + tmp = self.deq.pop() + return tmp -/** initialize your data structure here. */ -func Constructor() MinStack { - return MinStack{ - min: make([]int, 0), - stack: make([]int, 0), - } -} + def top(self) -> int: + if self.empty(): + return None + else: + return self.deq[-1] + def empty(self) -> bool: + return len(self.deq) == 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) - } - this.stack = append(this.stack, x) -} +# Your MyStack object will be instantiated and called as such: +# obj = MyStack() +# obj.push(x) +# param_2 = obj.pop() +# param_3 = obj.top() +# param_4 = obj.empty() +``` + +用collections中的deuqe类 +```python +from collections import deque +class MyStack: + + def __init__(self): + self.deq = deque() + + + def push(self, x: int) -> None: + self.deq.appendleft(x) + + def pop(self) -> int: + tmp = None + if len(self.deq): + tmp = self.deq.popleft() + return tmp + + def top(self) -> int: + if self.empty(): + return None + else: + return self.deq[0] + + def empty(self) -> bool: + return len(self.deq) == 0 + + +# Your MyStack object will be instantiated and called as such: +# obj = MyStack() +# obj.push(x) +# param_2 = obj.pop() +# param_3 = obj.top() +# param_4 = obj.empty() +``` + +### [valid-parentheses](https://leetcode.cn/problems/valid-parentheses) +> 给定一个只包括 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否有效。 + +> 有效字符串需满足: + +> 左括号必须用相同类型的右括号闭合。 +> 左括号必须以正确的顺序闭合。 +> 每个右括号都有一个对应的相同类型的左括号。 + +括号匹配是栈的拿手好戏。 +```python +class Solution: + def paired(self, a, b): + if (a == '(' and b == ')') or (a == '{' and b == '}' ) or ( a == '[' and b == ']' ): + return True + else: + return False + def isValid(self, s: str) -> bool: + + s= list(s) + if len(s) % 2 == 1: return False + stack = list() + for i, val in enumerate(s): + if len(stack) == 0 or not self.paired(stack[-1], val): + stack.append(val) + else: + stack.pop() + + return len(stack) == 0 +``` + +### [remove-all-adjacent-duplicates-in-string](https://leetcode.cn/problems/remove-all-adjacent-duplicates-in-string/) +> 给出由小写字母组成的字符串 S,重复项删除操作会选择两个相邻且相同的字母,并删除它们。 +> 在 S 上反复执行重复项删除操作,直到无法继续删除。 +> 在完成所有重复项删除操作后返回最终的字符串。答案保证唯一。 +```python +class Solution: + def removeDuplicates(self, s: str) -> str: + stack = list() + for val in s: + if len(stack) and stack[-1] == val: stack.pop() + else: stack.append(val) + + return ''.join(stack) + +``` + + +### [min-stack](https://leetcode-cn.com/problems/min-stack/) + +> 设计一个支持 push,pop,top 操作,并能在常数时间内检索到最小元素的栈。 + +思路: +- 用两个栈实现,一个最小栈始终保证最小值在顶部 +- 用```pair```数据结构保存值和当前栈中最小值 -func (this *MinStack) Pop() { - if len(this.stack) == 0 { - return +```cpp +class MinStack { + +public: + stack> st; //value, minVal + + /** initialize your data structure here. */ + MinStack() { } - this.stack = this.stack[:len(this.stack)-1] - this.min = this.min[:len(this.min)-1] -} + void push(int x) { + if(st.empty()){ + st.push(make_pair(x, x)); + return; + } + st.push(make_pair(x, min(x, st.top().second))); + } -func (this *MinStack) Top() int { - if len(this.stack) == 0 { - return 0 + void pop() { + st.pop(); } - return this.stack[len(this.stack)-1] -} + int top() { + return st.top().first; + } -func (this *MinStack) GetMin() int { - if len(this.min) == 0 { - return 1 << 31 + int getMin() { + return st.top().second; } - min := this.min[len(this.min)-1] - return min } - /** * Your MinStack object will be instantiated and called as such: * obj := Constructor(); @@ -81,52 +222,143 @@ func (this *MinStack) GetMin() int { */ ``` -[evaluate-reverse-polish-notation](https://leetcode-cn.com/problems/evaluate-reverse-polish-notation/) +### [evaluate-reverse-polish-notation](https://leetcode-cn.com/problems/evaluate-reverse-polish-notation/) > **波兰表达式计算** > **输入:** ["2", "1", "+", "3", "*"] > **输出:** 9 > **解释:** ((2 + 1) \* 3) = 9 思路:通过栈保存原来的元素,遇到表达式弹出运算,再推入结果,重复这个过程 -```go -func evalRPN(tokens []string) int { - if len(tokens)==0{ - return 0 - } - stack:=make([]int,0) - for i:=0;i& tokens) { + stack st; + for(int i = 0; i < tokens.size(); i++){ + string k = tokens[i]; + if(k == "+" || k == "-" || k == "*" || k == "/"){ + int a = st.top(); st.pop(); + int b = st.top(); st.pop(); + if(tokens[i] == "+") st.push(b + a); + if(tokens[i] == "-") st.push(b - a); + if(tokens[i] == "*") st.push(b * a); + if(tokens[i] == "/") st.push(b / a); //注意后pop出来的是除数 } + else st.push(stoi(k)); } - return stack[0] + return st.top(); + } ``` -[decode-string](https://leetcode-cn.com/problems/decode-string/) +```python +class Solution: + def evalRPN(self, tokens: List[str]) -> int: + stack = list() + for val in tokens: + if len(stack) > 0 and val in ['+', '-', '*', '/']: + op1 = stack.pop(-1) + op2 = stack.pop(-1) + if val == '+': stack.append(op2 + op1) + elif val == '-': stack.append(op2 - op1) + elif val == '*': stack.append(op2 * op1) + else: stack.append(int(op2 / op1)) + else: + stack.append(int(val)) + + return int(stack.pop()) +``` +简洁点 +```python +class Solution: + def evalRPN(self, tokens: List[str]) -> int: + stack = list() + for val in tokens: + if len(stack) > 0 and val in ['+', '-', '*', '/']: + op1 = stack.pop(-1) + op2 = stack.pop(-1) + stack.append(int(eval(f'{op2} {val} {op1}'))) + else: + stack.append(int(val)) + + return int(stack.pop()) +``` + +### [sliding-window-maximum](https://leetcode.cn/problems/sliding-window-maximum/) +> 给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。 +> 返回 滑动窗口中的最大值 。 +思路:这道题得想到一个数据结构,当滑动窗口一进一出,能够自动把最大值拱上来,只有最大堆了 +```python +import heapq +class Solution: + def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]: #最大堆队列 + hq = list() + ans = list() + for i, num in enumerate(nums): + while hq and hq[0][1] <= i - k: + heapq.heappop(hq) + heapq.heappush(hq, [-num,i]) + if i >= k - 1: + ans.append(-hq[0][0]) + return ans + +``` + +或者考虑构建一个单调队列,自定义其pop,push等操作。 +```python +from collections import deque +class MonoQueue: + def __init__(self): + self.dq = deque() + def pop(self, val): + if self.dq and self.dq[0] == val: + self.dq.popleft() + def push(self, val): + while self.dq and self.dq[-1] < val: + self.dq.pop() + self.dq.append(val) + + def front(self): + return self.dq[0] + +class Solution: + def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]: #最大堆队列 + mq = MonoQueue() + ans = list() + for i, num in enumerate(nums): + mq.push(num) + if i >= k - 1: + ans.append(mq.front()) + mq.pop(nums[i-k+1]) + return ans + +``` + +### [top-k-frequent-elements](https://leetcode.cn/problems/top-k-frequent-elements/) +> 给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 **任意顺序** 返回答案。 + +思路:top-k经典题目,利用最小堆或者最大堆, python里面只有最小堆 + +```python +import heapq +from collections import defaultdict +class Solution: + def topKFrequent(self, nums: List[int], k: int) -> List[int]: + # 最小堆 + hashmap = defaultdict(int) + for i, num in enumerate(nums): hashmap[num] += 1 + + hq = list() + for key, freq in hashmap.items(): + heapq.heappush(hq, (freq, key)) + if len(hq) > k: # 最小堆长度大于k + heapq.heappop(hq) + # 倒排输出 + return [item[1] for item in hq[::-1]] +``` + + + + +### [decode-string](https://leetcode-cn.com/problems/decode-string/) > 给定一个经过编码的字符串,返回它解码后的字符串。 > s = "3[a]2[bc]", 返回 "aaabcbc". @@ -135,50 +367,54 @@ 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) +```cpp +string decodeString(string s) { + if(s.length() == 0) return ""; + int n = s.length(); + + stack st; + for(int i = 0; i < n; i++){ + if(s[i] == ']'){ + string tmp = ""; + while(!st.empty() && st.top() != '['){ + tmp += st.top(); + st.pop(); + } + // pop out [ + if(!st.empty()) st.pop(); + string num = ""; + //get the number + while(!st.empty() && st.top() >= '0' && st.top() <= '9'){ + num += st.top(); + st.pop(); + } + + int count = 0; + for(int t = num.length() - 1; t >= 0; t--) + count = count * 10 + num[t] - '0'; + + //repeat number times + for(int j = 0; j < count; j++){ + for(int k = tmp.length() - 1; k >= 0; k--) st.push(tmp[k]); //倒过来输入到```stack```里面去 + } + } + else st.push(s[i]); + } + + vector res; + while(!st.empty()){ + res.push_back(st.top()); + st.pop(); + } + reverse(res.begin(), res.end()); + string ss(res.begin(), res.end()); + return ss; } ``` -利用栈进行 DFS 递归搜索模板 +**利用栈进行 ```DFS``` 递归搜索模板** -```go +```cpp boolean DFS(int root, int target) { Set visited; Stack s; @@ -198,98 +434,110 @@ boolean DFS(int root, int target) { } ``` -[binary-tree-inorder-traversal](https://leetcode-cn.com/problems/binary-tree-inorder-traversal/) +### [binary-tree-inorder-traversal](https://leetcode-cn.com/problems/binary-tree-inorder-traversal/) > 给定一个二叉树,返回它的*中序*遍历。 -```go +```cpp // 思路:通过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 == nullptr) return {}; + stack st; + vector res; + + while(true){ + if(root){ + st.push(root); + root = root->left; //一直向左 + } + else if(!st.empty()){ + root = st.top(); + st.pop(); //弹出 + res.push_back(root->val); + root = root->right; //转到右子树 } - // 弹出 - val := stack[len(stack)-1] - stack = stack[:len(stack)-1] - result = append(result, val.Val) - root = val.Right + else break; } - return result + + return res; + } ``` -[clone-graph](https://leetcode-cn.com/problems/clone-graph/) +### [clone-graph](https://leetcode-cn.com/problems/clone-graph/) > 给你无向连通图中一个节点的引用,请你返回该图的深拷贝(克隆)。 -```go -func cloneGraph(node *Node) *Node { - visited:=make(map[*Node]*Node) - return clone(node,visited) +```cpp +Node* cloneGraph(Node* node) { + + unordered_map visited; + return clone(node, visited); } -// 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& visited){ + if(node == nullptr) return nullptr; + + if(visited.find(node) != visited.end()) return visited[node]; + + vector listNode(node->neighbors.size(), nullptr); + Node* cloneNode = new Node(node->val, listNode); + visited[node] = cloneNode; + for(int i = 0; i < node->neighbors.size(); i++){ + cloneNode->neighbors[i] = clone(node->neighbors[i], visited); } - return newNode + + return cloneNode; } ``` -[number-of-islands](https://leetcode-cn.com/problems/number-of-islands/) +### [number-of-islands](https://leetcode-cn.com/problems/number-of-islands/) > 给定一个由  '1'(陆地)和 '0'(水)组成的的二维网格,计算岛屿的数量。一个岛被水包围,并且它是通过水平方向或垂直方向上相邻的陆地连接而成的。你可以假设网格的四个边均被水包围。 思路:通过深度搜索遍历可能性(注意标记已访问元素) -```go - -func numIslands(grid [][]byte) int { - var count int - for i:=0;i=1{ - count++ - } +```cpp +int numIslands(vector>& grid) { + if(grid.empty()) return 0; + int count = 0; + int rows = grid.size(); + int cols = grid[0].size(); + + for(int r = 0; r < rows; r++){ + for(int c = 0; c < cols; c++){ + if(grid[r][c] == '1') count++; + dfs(grid, r, c); } } - return count + + return count; } -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 dfs(vector>& grid, int i, int j){ + if(i < 0 || i >= grid.size() || j < 0 || j >= grid[0].size()) return; + if(grid[i][j] == '0') return; + grid[i][j] = '0'; //标记为已经访问 + dfs(grid, i-1, j); + dfs(grid, i+1, j); + dfs(grid, i, j-1); + dfs(grid, i, j+1); +} +``` + +**单调栈** +```cpp +stack st; +for(int i = 0; i < nums.size(); i++){ + while(!st.empty() && st.top() > nums[i]){ //单增栈 + st.pop(); + //do something } - return 0 + st.push(nums[i]); } ``` -[largest-rectangle-in-histogram](https://leetcode-cn.com/problems/largest-rectangle-in-histogram/) +### [largest-rectangle-in-histogram](https://leetcode-cn.com/problems/largest-rectangle-in-histogram/) > 给定 _n_ 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。 > 求在该柱状图中,能够勾勒出来的矩形的最大面积。 @@ -302,166 +550,128 @@ 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 -} -func Max(a, b int) int { - if a > b { - return a - } - return b +- 利用**单调栈**去做这件事情 + +```cpp +int largestRectangleArea(vector& heights) { + // 单调栈 + int n = heights.size(); + if(n == 0) return 0; + heights.push_back(0); + int res = INT_MIN; + stack st; + for(int i = 0; i < n + 1; i++){ + while(!st.empty() && heights[i] <= heights[st.top()]){ + int idx = st.top(); st.pop(); + int idx2 = st.size() > 0?st.top():-1; + res = max(res, heights[idx] * (i - idx2 - 1)); + } + st.push(i); + } + return res; + } ``` ## Queue 队列 +![queue.jpg](https://s1.ax1x.com/2020/07/19/URER7d.md.jpg) + 常用于 BFS 宽度优先搜索 -[implement-queue-using-stacks](https://leetcode-cn.com/problems/implement-queue-using-stacks/) +### [implement-queue-using-stacks](https://leetcode-cn.com/problems/implement-queue-using-stacks/) > 使用栈实现队列 -```go -type MyQueue struct { - stack []int - back []int -} +```cpp +class MyQueue { +public: + stack inSt; + stack outSt; + /** 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) { + inSt.push(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) + + /** Removes the element from in front of queue and returns that element. */ + int pop() { + if(!outSt.empty()){ + int ele = outSt.top(); + outSt.pop(); + return ele; + } + else { + while(inSt.size() > 1){ + outSt.push(inSt.top()); + inSt.pop(); + } + int ele = inSt.top(); + inSt.pop(); + return ele; + } + } - if len(this.back) == 0 { - return 0 + + /** Get the front element. */ + int peek() { + int ele = this->pop(); + outSt.push(ele); + return ele; } - 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) + + /** Returns whether the queue is empty. */ + bool empty() { + return inSt.empty() && outSt.empty(); } - if len(this.back) == 0 { - return 0 - } - 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(); + * MyQueue* obj = new MyQueue(); + * obj->push(x); + * int param_2 = obj->pop(); + * int param_3 = obj->peek(); + * bool param_4 = obj->empty(); */ ``` 二叉树层次遍历 -```go -func levelOrder(root *TreeNode) [][]int { +```cpp +vector> levelOrder(TreeNode* root){ + vector> res; + if(root == nullptr) return res; + queue nodes; + nodes.push(root); // 通过上一层的长度确定下一层的元素 - 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) - } + while(!nodes.empty()){ + int levelSize = nodes.size(); //记录当前层有多少元素,遍历当前层,并添加下一层 + vector levelVals; + for(int i = 0; i < levelSize; i++){ + TreeNode* tmpNode = nodes.front(); + nodes.pop(); + levelVals.push_back(tmpNode->val); + if(tmpNode->left != nullptr) nodes.push(tmpNode->left); + if(tmpNode->right != nullptr) nodes.push(tmpNode->right); } - result = append(result, list) + res.push_back(levelVals); } - return result + + return res; } ``` -[01-matrix](https://leetcode-cn.com/problems/01-matrix/) +### [01-matrix](https://leetcode-cn.com/problems/01-matrix/) > 给定一个由 0 和 1 组成的矩阵,找出每个元素到最近的 0 的距离。 > 两个相邻元素间的距离为 1 -```go +```cpp // BFS 从0进队列,弹出之后计算上下左右的结果,将上下左右重新进队列进行二层操作 // 0 0 0 0 // 0 x 0 0 @@ -477,46 +687,54 @@ func levelOrder(root *TreeNode) [][]int { // 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) { + if(matrix.empty()) return {}; + + queue> q; + vector> directions{{-1, 0}, {1, 0}, {0, 1}, {0, -1}}; + //矩阵中为0的元素作为bfs的第一层 + for(int i = 0; i < matrix.size(); i++){ + for(int j = 0; j < matrix[0].size(); j++){ + if(matrix[i][j] == 0) q.push(make_pair(i, j)); + else matrix[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 point = q.front(); + q.pop(); + for(int i = 0; i < 4; i++){ + int x = point.first + directions[i][0]; + int y = point.second + directions[i][1]; + if(x >= 0 && x < matrix.size() && y >= 0 && y < matrix[0].size() && matrix[x][y] == -1){ + matrix[x][y] = 1 + matrix[point.first][point.second]; + q.push(make_pair(x, y)); } } } - return matrix + return matrix; } ``` ## 总结 - 熟悉栈的使用场景 - - 后出先出,保存临时值 - - 利用栈 DFS 深度搜索 + - 后进先出,保存临时值 + - 利用栈 ```DFS``` 深度搜索 - 熟悉队列的使用场景 - - 利用队列 BFS 广度搜索 + - 利用队列 ```BFS``` 广度搜索 +- 单调栈 + - 单调递增/减栈:栈内元素保持递增/减的栈 + - 以单增栈为例: + - 如果新元素比栈顶元素大,就入栈 + - 否则一直弹出栈顶元素,知道栈顶元素比新元素小,再将其压栈 + - 用处: + - 栈内元素递增 + - 当元素出栈时,这个**新元素**是栈顶元素**往后**找第一个比之小的元素 + - 当元素出栈后,新**栈顶元素**是出栈元素**往前**找第一个比之小的元素 + ## 练习 diff --git a/data_structure/string.md b/data_structure/string.md new file mode 100644 index 00000000..c9bb9cc4 --- /dev/null +++ b/data_structure/string.md @@ -0,0 +1,83 @@ +# 字符串 +## 字符串应用比较多 +**核心关键点** +- 跟int的转换 +- 翻转,旋转,替换,大小写等常规操作 +- ```KMP算法``` +- 用```python```刷字符串相关题目比C++好多了 + +### [reverse-string] (https://leetcode.cn/problems/reverse-string/) +> 编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出。 +> 不要给另外的数组分配额外的空间,你**必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题**。 + +```python +class Solution: + def reverseString(self, s: List[str]) -> None: + left, right = 0, len(s) - 1 + while left < right: + s[left], s[right] = s[right], s[left] + left += 1 + right -= 1 +``` +当然python自带函数 +```python +class Solution: + def reverseString(self, s: List[str]) -> None: + """ + Do not return anything, modify s in-place instead. + """ + s[:] = s[::-1] +``` + +### [reverse-string-ii](https://leetcode.cn/problems/reverse-string-ii) +> 给定一个字符串 s 和一个整数 k,从字符串开头算起,每计数至 2k 个字符,就反转这 2k 字符中的前 k 个字符。 + +> - 如果剩余字符少于 k 个,则将剩余字符全部反转。 +> - 如果剩余字符小于 2k 但大于或等于 k 个,则反转前 k 个字符,其余字符保持原样。 + +思路:注意反转的头尾位置即可 + +```python +class Solution: + def reverseStr(self, s: str, k: int) -> str: + s = list(s) + steps = len(s) // (2*k) + for i in range(steps+1): + end = min(len(s), i*2*k+k) + s[i*2*k:end] = s[i*2*k:end][::-1] + return ''.join(s) +``` + +### [ti-huan-kong-ge-lcof](https://leetcode.cn/problems/ti-huan-kong-ge-lcof/) +> 请实现一个函数,把字符串 s 中的每个空格替换成"%20"。 + +注意:实现函数,就不能用python的官方函数 + +```python +class Solution: + def replaceSpace(self, s: str) -> str: + sl = s.split(' ') + return '%20'.join(sl) +``` + +### [reverse-words-in-a-string](https://leetcode.cn/problems/reverse-words-in-a-string/) +> 给你一个字符串 s ,请你反转字符串中 单词 的顺序。 +> 单词 是由非空格字符组成的字符串。s 中使用至少一个空格将字符串中的 单词 分隔开。 +> 返回 单词 顺序颠倒且 单词 之间用单个空格连接的结果字符串。 +> 注意:输入字符串 s中可能会存在前导空格、尾随空格或者单词间的多个空格。返回的结果字符串中,单词间应当仅用单个空格分隔,且不包含任何额外的空格。 +```python + +``` + + + +### [zuo-xuan-zhuan-zi-fu-chuan-lcof](https://leetcode.cn/problems/zuo-xuan-zhuan-zi-fu-chuan-lcof/) +> 字符串的左旋转操作是把字符串前面的若干个字符转移到字符串的尾部。请定义一个函数实现字符串左旋转操作的功能。比如,输入字符串"abcdefg"和数字2,该函数将返回左旋转两位得到的结果"cdefgab"。 + +切片 +```python +class Solution: + def reverseLeftWords(self, s: str, n: int) -> str: + return s[n:] + s[:n] +``` + diff --git a/data_structure/trie.md b/data_structure/trie.md new file mode 100644 index 00000000..7ec51a90 --- /dev/null +++ b/data_structure/trie.md @@ -0,0 +1,321 @@ +# 前缀树 + +## 背景 + +```前缀树```, 又称```字典树```, 是```N```叉树的一种。它是用来```存储字符串```的,前缀树的每一个节点代表一个 ```字符串(前缀)```。每一个节点会有多个子节点,通往不同子节点的路径上有着不同的字符。子节点代表的字符串是由节点本身的 ```原始字符串``` ,以及 ```通往该子节点路径上所有的字符``` 组成的。 + +示例: + +![trie](../images/trie.png) + +节点的值是从根节点开始,与其经过的路径中的字符串按顺序组成。 + +前缀树的一个重要的特性是,节点所有的后代都与该节点相关的字符串有着共同的前缀,利用公共前缀来降低查询时间开销,这就是 ```前缀树``` 名称的由来。 + +前缀树有着广泛的应用,例如自动补全,拼写检查等等。 + +> 根节点是```空字符串``` + +## 表示 + +```前缀树```的特别在于字符和子节点之间的对应关系,怎样高效表示这种对应关系是重点。 + +- 数组 + - 假如只存储```a```到```z```的字符串,可以每个节点声明一个```26```维的数据保存子节点。 + - 对于特定字符```c```,使用```c-'a'```来作为索引查找数组中相应节点。 +```cpp +// change this value to adapt to different cases +#define N 26 + +struct TrieNode { + bool isEnd; //该节点是否是一个串的结束 + TrieNode* next[N]; + + // you might need some extra values according to different cases +}; + +/** Usage: + * Initialization: TrieNode root = new TrieNode(); + * Return a specific child node with char c: (root->children)[c - 'a'] + */ +``` + +- Map + - 使用```HashMap```存储子节点 + - 每个节点声明一个HashMap, 键是字符,值则是对应的子节点 +```cpp +struct TrieNode { + bool isEnd=false; //该节点是否是一个串的结束 + unordered_map next; + + //析构函数 + ~TrieNode(){ + for(auto item: next){ + if(item.second != nullptr) delete item.second; + } + } + // you might need some extra values according to different cases +}; + +/** Usage: + * Initialization: TrieNode root = new TrieNode(); + * Return a specific child node with char c: (root->children)[c] + */ +``` +> 通过相应的字符来访问特定的子节点 ```更为容易``` 。但它可能比使用数组 ```稍慢一些``` 。但是,由于我们只存储我们需要的子节点,因此 ```节省了空间``` 。这个方法也更加 ```灵活``` ,因为我们不受到```固定长度和固定范围```的限制。 + +> https://leetcode-cn.com/leetbook/read/trie/x7ke5m/ + +### [implement-trie-prefix-tree](https://leetcode-cn.com/problems/implement-trie-prefix-tree/) +> 实现前缀树的```insert```,```search```和```startWith```三个操作 + +思路:两种实现,一种是使用数组,一种是使用哈希表,前者查找更快,但浪费空间,后者节省内存空间,但查找效率上低一级。 + +```cpp +class Trie { +private: + bool isEnd; + Trie* next[26]; +public: + Trie() { + isEnd = false; + memset(next, 0, sizeof(next)); + } + + void insert(string word) { + Trie* node = this; + for (char c : word) { + if (node->next[c-'a'] == NULL) { + node->next[c-'a'] = new Trie(); + } + node = node->next[c-'a']; + } + node->isEnd = true; + } + + bool search(string word) { + Trie* node = this; + for (char c : word) { + node = node->next[c - 'a']; + if (node == NULL) { + return false; + } + } + return node->isEnd; + } + + bool startsWith(string prefix) { + Trie* node = this; + for (char c : prefix) { + node = node->next[c-'a']; + if (node == NULL) { + return false; + } + } + return true; + } +}; +``` + +哈希 +```cpp +struct TrieNode { + bool isEnd = false; + unordered_map next; + ~TrieNode() { // 析构 + for (auto& [key, value] : next) { + if (value) delete value; + } + } +}; + +class Trie { +private: + std::unique_ptr root; // 智能指针 + +public: + Trie(): root(new TrieNode()) {} + + void insert(string word) { + TrieNode *p = root.get(); + for (const char c : word) { + if (!p->next.count(c)) { + p->next[c] = new TrieNode(); + } + p = p->next[c]; + } + p->isEnd = true; + } + + bool search(string word) { + TrieNode *p = root.get(); + for (const char c : word) { + if (!p->next.count(c)) { + return false; + } + p = p->next[c]; + } + return p->isEnd; + } + + bool startsWith(string prefix) { + TrieNode *p = root.get(); + for (const char c : prefix) { + if (!p->next.count(c)) { + return false; + } + p = p->next[c]; + } + return p != nullptr; + } +}; +``` + +### [map-sum-pairs](https://leetcode-cn.com/problems/map-sum-pairs/) +> 键值映射 +```cpp +class MapSum { + struct TrieNode { + bool isEnd = false; + int val; + unordered_map next; + //析构函数 + ~TrieNode() { + for(auto item: next){ + if(item.second != nullptr) delete item.second; + } + } + }; + +private: + TrieNode* root; +public: + /** Initialize your data structure here. */ + MapSum() { + root = new TrieNode(); + } + + void insert(string key, int val) { + TrieNode* p = root; + for(int i = 0; i < key.length(); i++){ + char c = key[i]; + if(p->next.find(c) == p->next.end()) p->next[c] = new TrieNode(); + p = p->next[c]; + } + p->isEnd = true; + p->val = val; + return; + } + + int sum(string prefix) { + TrieNode* p = root; + int sum = 0; + for(int i = 0; i < prefix.length(); i++){ + char c = prefix[i]; + if(p->next.find(c) != p->next.end()) + p = p->next[c]; + else return 0; + } + //宽度优先遍历 + queue qe; + qe.push(p); + while(!qe.empty()){ + int n = qe.size(); + for(int i = 0; i < n; i++){ + TrieNode* node = qe.front(); + if(node->isEnd) sum += node->val; + qe.pop(); + for(auto item: node->next){ + qe.push(item.second); + } + } + } + + return sum; + } +}; + +/** + * Your MapSum object will be instantiated and called as such: + * MapSum* obj = new MapSum(); + * obj->insert(key,val); + * int param_2 = obj->sum(prefix); + */ +``` + +### [replace-words](https://leetcode-cn.com/problems/replace-words/) +> 单词替换 + +```cpp +class Solution { +struct TrieNode{ + bool isEnd=false; + unordered_map next; + ~TrieNode(){ + for(auto item:next){ + if(item.second != nullptr) delete item.second; + } + } +}; +private: + TrieNode* root; +public: + string replaceWords(vector& dict, string sentence) { + root = new TrieNode(); + + insertDict(dict); + + string res = ""; + string word = ""; + int i = 0; + int n = sentence.length(); + while(i < n){ + if(sentence[i] == ' ' && word.length() > 0){ + string ser = search(word); + if(ser.length()) res += ser; + else res += word; + res += ' '; + word = ""; + } + else word += sentence[i]; + i++; + } + + string ser = search(word); + if(ser.length()) res += ser; + else res += word; + + return res; + } + + void insertDict(vector& dict){ + for(int i = 0; i < dict.size(); i++) insert(dict[i]); + return; + } + void insert(string& word){ + TrieNode* p = root; + for(int i = 0; i < word.length(); i++){ + char c = word[i]; + if(p->next.find(c) == p->next.end()) p->next[c] = new TrieNode(); + p = p->next[c]; + } + p->isEnd = true; + } + + string search(string& word){ + TrieNode* p = root; + string res = ""; + for(int i = 0; i < word.length(); i++){ + char c = word[i]; + if(p->isEnd) return res; + if(p->next.find(c) != p->next.end()){ + res += c; + p = p->next[c]; + } + else return ""; + } + return ""; + } + +}; +``` diff --git a/images/trie.png b/images/trie.png new file mode 100644 index 00000000..03dec773 Binary files /dev/null and b/images/trie.png differ diff --git a/images/tupulogic_sort.png b/images/tupulogic_sort.png new file mode 100644 index 00000000..d5cd7cda Binary files /dev/null and b/images/tupulogic_sort.png differ diff --git a/puzzles/bayes.md b/puzzles/bayes.md new file mode 100644 index 00000000..0153b09d --- /dev/null +++ b/puzzles/bayes.md @@ -0,0 +1,15 @@ +# 贝叶斯定理 +## 已知某些条件下,某事件的发生几率 + +> 通常,事件A在事件B已发生的条件下发生的几率,与事件B在事件A已发生的条件下发生的几率是不一样的。然而,这两者是有确定的关系的,贝叶斯定理就是这种关系的陈述。贝叶斯公式的一个用途,即透过已知的三个几率而推出第四个几率。贝叶斯定理跟随机变量的条件几率以及边缘几率分布有关。 + +$$ P(A|B) = P(B|A)P(A)/P(B) $$ +> A以及B都是随机事件,P(B)不能为0。P(A|B)就是事件B发生后事件A发生的概率 +> - P(A|B)称为A的**后验概率** +> - P(A)就是A的**先验概率** +> - P(B|A)称为B的**后验概率** +> - P(B)就是B的**先验概率** + +贝叶斯定理用语言描述就是: +> 后验概率 = (似然性*先验概率)/ 标准化常量 + diff --git a/puzzles/probability.md b/puzzles/probability.md new file mode 100644 index 00000000..a0ce5c25 --- /dev/null +++ b/puzzles/probability.md @@ -0,0 +1,3 @@ +# 概率分布 +## 随机变量的概率分布函数 + diff --git a/src/sort.cpp b/src/sort.cpp new file mode 100644 index 00000000..2fdbe7c9 --- /dev/null +++ b/src/sort.cpp @@ -0,0 +1,216 @@ +#include +#include + +using namespace std; + +//交换数组a中的i和j +void swap(vector & a, int i, int j ){ + int tmp = a[i]; + a[i] = a[j]; + a[j] = tmp; +} + +//判断是否是排序好的数组 +bool isSorted(vector& a, int i, int j){ + for(int k = i; k < j; k++){ + if(a[k] > a[k + 1]) return false; + } + + return true; +} + + +//选择排序 +void selectionSort(vector& a){ + int n = a.size(); + for(int i = 0; i < n; i++){ + int min = i; + for(int j = i + 1; j < n; j++){ + if(a[j] < min) + min = j; + } + swap(a, i , j); + } + +} + +//插入排序 +void insertionSort(vector& a){ + int n = a.size(); + for(int i = 1; i < n; i ++){ + for(int j = i; j > 0; j--){ + if(a[j] < a[j - 1]){ + swap(a, j, j - 1); + } + else break; + } + } +} + +//壳排序 +void shellSort(vector& a){ + + int n = a.size(); + int H = 1; + while(H < n) H = 3 * H + 1; //壳的序列 + + for(int h = H; h >= 1; h = (H - 1) / 3){ + // 插入排序 + for(int i = 1; i < n; i++){ + for(int j = i; j > h; j-=h){ + if(a[j] < a[j - h]) swap(a, j, j - h); + else break; + } + } + + } + +} + + +//归并排序 +/*version1 递归*/ +void mergeSort(vector& a){ + int n = a.size(); + vector aux(n); + merge_sort(a, aux, 0, n - 1); +} + +void merge_sort(vector& a, vector& aux, int low, int high){ + if(low >= high) return; + + int middle = low + (high - low)/2; + merge_sort(a, aux, low, middle); + merge_sort(a, aux, middle + 1, high); + merge(a, aux, low, middle, high); +} + +void merge(vector& a, vector& aux, int low, int middle, int high){ + assert isSorted(a, low, middle); + assert isSorted(a, middle + 1, high); + + for(int k = low; k <= high; k++) + aux[k] = a[k]; + + int i = low, j = middle + 1; + int k = low; + for(int k = low; k <= high; k++){ + if(i > middle) a[k] = aux[j++]; + else if(j > high) a[k] = aux[i++]; + else if(a[i] > a[j]) a[k] = aux[j++]; + else a[k] = aux[i++]; + } +} + +/*version 2 bottom-up 归并排序 */ +void buMergeSort(vector& a){ + int n = a.size(); + vector aux(n); + for(int i = 1; i <= n; i = 2 * i){ + for(int low = 0; low < n - i; low += 2 * i){ + merge(a, aux, low, low + i - 1, min(n - 1, low + 2 * i - 1)); + } + } + +} + + +//快速排序,快排 +void quickSort(vector& a){ + + random_shuffle(a); + int n = a.size(); + quick_sort(a, 0, n - 1); +} + +void quick_sort(vector& a, int low, int high){ + if(low >= high) return; + int pivot = partition(a, low, high); + quick_sort(a, low, pivot - 1); + quick_sort(a, pivot + 1, high); +} + +static int partition(vector& a, int low, int high){ + int i = low, j = high + 1; + while(i < j){ + + while(a[++i] < a[low]) + if(i == high) break; + + while(a[low] < a[--j]) + if(j == low) break; + + swap(a, i, j); + } + swap(a, low, j); + return j; +} + +// 选择k +static int select(vector& a, int k){ + + int low = 0, high = a.size() - 1; + while(low < high){ + int pivot = partition(a, low, high); + if(pivot < k) low = pivot + 1; + else if(pivot > k) high = pivot - 1; + else return a[k]; + } + + return a[k]; + +} + +//重复键值,3-way partition +void quick3ways(vector& a, int low, int high){ + // See page 289 for public sort() that calls this method. + if (high <= low) return; + int lt = low, i = low + 1, gt = high; + int v = a[low]; + while (i <= gt) + { + if (a[i] < v) swap(a, lt++, i++); + else if (a[i] > v) swap(a, i, gt--); + else i++; + } // Now a[lo..lt-1] < v = a[lt..gt] < a[gt+1..hi]. sort(a, lo, lt - 1); + quick3ways(a, low, lt - 1); + quick3ways(a, gt + 1, high); +} + +//堆排序 +//核心代码 +void heapSort(vector& a){ + // 1. 无序数组a + // 2. 构建成一个大根堆 + for(int i = a.size() / 2 - 1; i >= 0; i--){ + sink(a, i, a.size()); + } + //3. 交换a[0] 和 a[a.size()-1] + //4. 然后把前面这段数组持续下沉保持堆结构,如此循环 + for (int i = a.size() - 1; i >= 1; i--){ + // 从后往前填充 + swap(a, 0, i); + // 前面的长度减一 + sink(a, 0, i); + } +} + +void sink(vector& a, int i, int length){ + while(true){ + // 左节点索引(从0开始,所以左节点为i*2+1) + int l = 2 * i + 1; + //右节点索引 + int r = 2 * i + 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); + i = idx; + } +} + diff --git a/src/tree.cpp b/src/tree.cpp new file mode 100644 index 00000000..00cded7c --- /dev/null +++ b/src/tree.cpp @@ -0,0 +1,98 @@ +#include +#include + +using namespace std; + +// 二叉树的前中后序遍历 +// 二叉树和链表的转换 +template struct treeNode{ + T val; + treeNode* left; + treeNode* right; + treeNode* parent; +}; + +// 前序 +//递归 +template +void preOrderRecursion(treeNode* root){ + if (root == nullptr) return; + cout<val<<" "<left); + preOrderRecursion(root->right); +} + +void preOrder(treeNoder* root){ + if (root == nullptr) return; + stack*> treeStack; + treeStack.push(root); + while(!treeStack.empty()){ + treeNode* node = treeStack.pop(); + if(node->right != nullptr) treeStack.push(node->right); + if(node->left != nullptr) treeStack.push(node->left); + } +} + +// 中序 +template +void inOrderRecursion(treeNode* root){ + if (root == nullptr) return; + inOrderRecursion(root->left); + cout<val<right); +} + +template +void inOrder(treeNode* root){ + if (root == nullptr) return; + stack*> S; + + while(true){ + if(root != nullptr){ + S.push(root); + root = root->left; + } + else if(!S.empty()){ + treeNode* root = S.pop(); + cout<val<right; //遍历右子树 + } + else break; + } +} + +//后续 +// 递归 +template +void postOrder(treeNode* root){ + if(root == nullptr) return; + postOrder(root->left); + postOrder(root->right); + cout<val< postorderTraversal(TreeNode* root) { + if (root == nullptr) return {}; + stack stk; + stk.push(root); + vector res; + while (!stk.empty()) { + TreeNode* node = stk.top(); + if (node == nullptr) { + stk.pop(); + res.push_back(stk.top()->val); + stk.pop(); + continue; + } + stk.push(nullptr); + if (node->right) { + stk.push(node->right); + } + if (node->left) { + stk.push(node->left); + } + } + return res; + } + diff --git a/templates/BFS.md b/templates/BFS.md new file mode 100644 index 00000000..1571f58d --- /dev/null +++ b/templates/BFS.md @@ -0,0 +1,75 @@ +# 广度优先搜索 +## 遍历和找出最短路径。通常用于树和图结构。 + +> 在特定问题中执行**BFS**之前确定结点和边缘非常重要。通常来说,结点是实际结点或是状态。而边缘将是实际边缘或可能的转换 + +【**模板一**】 + +```java +/** + * Return the length of the shortest path between root and target node. + */ +int BFS(Node root, Node target) { + Queue queue; // store all nodes which are waiting to be processed + int step = 0; // number of steps neeeded from root to current node + // initialize + add root to queue; + // BFS + while (queue is not empty) { + step = step + 1; + // iterate the nodes which are already in the queue + int size = queue.size(); + for (int i = 0; i < size; ++i) { + Node cur = the first node in queue; + return step if cur is target; + for (Node next : the neighbors of cur) { + add next to queue; + } + remove the first node from queue; + } + } + return -1; // there is no path from root to target +} +``` +- 每一轮中,队列中的节点是**等待处理的节点**。 +- 每个更外一层的```while```循环之后,我们```距离根节点更远一步```,用```step```记录根节点到当前节点的距离 + + +【**模板二**】 + +> 有时**不会访问一个结点两次**很重要,否则会陷入无限循环,我们需要加入一个哈希表```used```判断是否已经访问过本节点 + +```java +/** + * Return the length of the shortest path between root and target node. + */ +int BFS(Node root, Node target) { + Queue queue; // store all nodes which are waiting to be processed + Set used; // store all the used nodes + int step = 0; // number of steps neeeded from root to current node + // initialize + add root to queue; + add root to used; + // BFS + while (queue is not empty) { + step = step + 1; + // iterate the nodes which are already in the queue + int size = queue.size(); + for (int i = 0; i < size; ++i) { + Node cur = the first node in queue; + return step if cur is target; + for (Node next : the neighbors of cur) { + if (next is not in used) { + add next to queue; + add next to used; + } + } + remove the first node from queue; + } + } + return -1; // there is no path from root to target +} +``` +> 两种情况不考虑哈希表 +> - 确定完全没有循环,比如树遍历 +> - 明确要把多次访问的节点都添加到队列中 \ No newline at end of file diff --git a/templates/DFS.md b/templates/DFS.md new file mode 100644 index 00000000..c5cc9552 --- /dev/null +++ b/templates/DFS.md @@ -0,0 +1,50 @@ +# 深度优先遍历 +### dfs跟bfs区别在于**遍历顺序**的不同 + +- BFS中:**更早访问的节点距离根结点更近** +- DFS中,找到的第一条路径**不一定是最短路径** + +【**模板一**】 +- 模板一是**递归**实现的 +```java +/* + * Return true if there is a path from cur to target. + */ +boolean DFS(Node cur, Node target, Set visited) { + return true if cur is target; + for (next : each neighbor of cur) { + if (next is not in visited) { + add next to visited; + return true if DFS(next, target, visited) == true; + } + } + return false; +} +``` +> 以上算法实际调用了系统提供的**隐式栈**. + +【**模板二**】 +- 要是递归栈太深,会导致溢出,可以利用**显式栈**优化 + +```java +/* + * Return true if there is a path from cur to target. + */ +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; + 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; + } + } + remove cur from s; + } + return false; +} +```