From b0faf693eeaed307fea6a531f24d17019b15c10c Mon Sep 17 00:00:00 2001 From: zxyang Date: Sat, 20 Jun 2020 09:34:14 +0800 Subject: [PATCH 001/110] Add c++ bin_tree.md --- data_structure/binary_tree.md | 75 +++++++++++++++++++++-------------- 1 file changed, 45 insertions(+), 30 deletions(-) diff --git a/data_structure/binary_tree.md b/data_structure/binary_tree.md index d01a8ca4..0101bd4c 100644 --- a/data_structure/binary_tree.md +++ b/data_structure/binary_tree.md @@ -12,46 +12,61 @@ - 以根访问顺序决定是什么遍历 - 左子树都是优先右子树 + +#### TreeNode 定义 + +```cpp +struct TreeNode{ + int val; + TreeNode* left; + TreeNode* right; + TreeNode* parent; + TreeNode(int x): val(x), left(nullptr), right(nullptr), parent(nullptr) {} +}; +``` #### 前序递归 -```go -func preorderTraversal(root *TreeNode) { - if root==nil{ - return - } - // 先访问根再访问左右 - fmt.Println(root.Val) - preorderTraversal(root.Left) - preorderTraversal(root.Right) +```cpp +//前中后序遍历只在这个函数有区别 +void preOrderRecursion(TreeNode* root, vector& res){ + res.push_back(root->val); + preOrderRecursion(root->left, res); + preOrderRecursion(root->right, res); +} + +vector preOrderTraversal(TreeNode* root){ + if(root == nullptr) return {}; + + vector res; + + preOrderRecursion(root, res); + + return res; } ``` #### 前序非递归 -```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.pop(); + res.push_nack(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; } ``` From d777c0033447ca4e87fb198e783420e73c174344 Mon Sep 17 00:00:00 2001 From: zxyang Date: Sat, 20 Jun 2020 11:03:03 +0800 Subject: [PATCH 002/110] commit on bin_tree.md --- data_structure/binary_tree.md | 79 +++++++++++++++-------------------- 1 file changed, 33 insertions(+), 46 deletions(-) diff --git a/data_structure/binary_tree.md b/data_structure/binary_tree.md index 0101bd4c..68f57ec1 100644 --- a/data_structure/binary_tree.md +++ b/data_structure/binary_tree.md @@ -28,7 +28,7 @@ struct TreeNode{ #### 前序递归 ```cpp -//前中后序遍历只在这个函数有区别 +//前中后序遍历只在这个函数有区别, 调整push_back的顺序 void preOrderRecursion(TreeNode* root, vector& res){ res.push_back(root->val); preOrderRecursion(root->left, res); @@ -59,7 +59,8 @@ vector preorderTraversal(TreeNode* root){ st.push(root); while(!st.empty()){ - TreeNode* node = st.pop(); + TreeNode* node = st.top(); + st.pop(); res.push_nack(node->val); //注意先入右节点进栈 if(!node->right) st.push(node->right); @@ -72,58 +73,44 @@ vector preorderTraversal(TreeNode* root){ #### 中序非递归 -```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; //一直向左 + } + else if(!st.empty()){ + TreeNode* 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; + } ``` #### 后序非递归 -```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; + + } ``` From 3324922341515e20d63605fdcaad5620aa8e019c Mon Sep 17 00:00:00 2001 From: zxyang Date: Sat, 20 Jun 2020 11:10:58 +0800 Subject: [PATCH 003/110] Add postorder --- data_structure/binary_tree.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/data_structure/binary_tree.md b/data_structure/binary_tree.md index 68f57ec1..ae99fca1 100644 --- a/data_structure/binary_tree.md +++ b/data_structure/binary_tree.md @@ -109,8 +109,21 @@ vector postorderTraversal(TreeNode* root){ 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; } ``` From 5306a93ba259567bc5437ddd8e3994d7a5366013 Mon Sep 17 00:00:00 2001 From: zxyang Date: Thu, 25 Jun 2020 11:27:00 +0800 Subject: [PATCH 004/110] Finish knowledge of binary_tree --- data_structure/binary_tree.md | 312 +++++++++++++++++----------------- 1 file changed, 159 insertions(+), 153 deletions(-) diff --git a/data_structure/binary_tree.md b/data_structure/binary_tree.md index ae99fca1..0e6c6bd3 100644 --- a/data_structure/binary_tree.md +++ b/data_structure/binary_tree.md @@ -21,7 +21,7 @@ struct TreeNode{ TreeNode* left; TreeNode* right; TreeNode* parent; - TreeNode(int x): val(x), left(nullptr), right(nullptr), parent(nullptr) {} + TreeNode(int x): val(x), left(nullptr), right(nullptr), parent(nullptr) {}; }; ``` @@ -105,7 +105,7 @@ vector inorderTraversal(TreeNode* root){ ```cpp vector postorderTraversal(TreeNode* root){ // 通过栈辅助,同时插入nullptr节点判断右子节点是否弹出 - if(root == nullptr) return {} + if(root == nullptr) return {}; vector res; stack st; @@ -133,91 +133,75 @@ vector postorderTraversal(TreeNode* root){ #### 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; } + ``` ### 分治法应用 @@ -236,86 +220,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); +} + +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); } -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; + 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 + + assert isSorted(a, low, high); + } -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++ - } + +//判断是否是排序好的数组 +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; } ``` @@ -325,40 +322,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) - } +```cpp +void quickSort(vector& a){ + //思路:找到数组中某个index,将数组分成两段,左段小于右段,类似分治法,但没有合并 + random_shuffle(a); //重要!保证快排复杂度下限 + int n = a.size(); + quick_sort(a, 0, n - 1); } -// 分区 -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 + +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; } ``` From 38c8e0100dce2d2ff0bcd1f5566a54cac1c46b8c Mon Sep 17 00:00:00 2001 From: zxyang Date: Thu, 25 Jun 2020 16:29:35 +0800 Subject: [PATCH 005/110] Solve maxDepth(tree) isBalanced(tree) --- data_structure/binary_tree.md | 58 ++++++++++++++--------------------- 1 file changed, 23 insertions(+), 35 deletions(-) diff --git a/data_structure/binary_tree.md b/data_structure/binary_tree.md index 0e6c6bd3..4d0a7a37 100644 --- a/data_structure/binary_tree.md +++ b/data_structure/binary_tree.md @@ -21,7 +21,7 @@ struct TreeNode{ TreeNode* left; TreeNode* right; TreeNode* parent; - TreeNode(int x): val(x), left(nullptr), right(nullptr), parent(nullptr) {}; + TreeNode(int x): val(x), left(nullptr), right(nullptr), parent(nullptr) {} }; ``` @@ -383,21 +383,16 @@ void swap(vector& a, int i, int j){ 思路:分治法 -```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); // 注意加一 } ``` @@ -411,29 +406,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 +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; } - if left > right { - return left + 1 - } - return right + 1 + return 1 + max(leftDepth, rightDepth); + } ``` From 83d7189ecd55e6b522357f4d999980a4a50e24e7 Mon Sep 17 00:00:00 2001 From: zxyang Date: Thu, 25 Jun 2020 22:16:31 +0800 Subject: [PATCH 006/110] Finish binary_tree.md --- data_structure/binary_tree.md | 418 ++++++++++++++-------------------- 1 file changed, 166 insertions(+), 252 deletions(-) diff --git a/data_structure/binary_tree.md b/data_structure/binary_tree.md index 4d0a7a37..b7c8e36e 100644 --- a/data_structure/binary_tree.md +++ b/data_structure/binary_tree.md @@ -87,10 +87,10 @@ vector inorderTraversal(TreeNode* root){ root = root->left; //一直向左 } else if(!st.empty()){ - TreeNode* root = st.top(); //弹出 + root = st.top(); //弹出 st.pop(); res.push_back(root->val); - root = root->right;  //右子树 + root = root->right;  //转向右子树 } else break; } @@ -437,45 +437,22 @@ int maxDepth(TreeNode* root){ 思路:分治法,分为三种情况:左子树最大路径和最大,右子树最大路径和最大,左右子树最大加根节点最大,需要保存两个变量:一个保存子树最大路径和,一个保存左右加根节点和,然后比较这个两个变量选择最大值即可 -```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; } ``` @@ -487,33 +464,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 } ``` @@ -527,34 +490,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; } ``` @@ -564,47 +520,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; } ``` @@ -614,45 +567,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; } ``` @@ -668,85 +609,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 + return true; } -func inOrder(root *TreeNode, result *[]int) { - if root == nil{ - return +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; } - inOrder(root.Left, result) - *result = append(*result, root.Val) - inOrder(root.Right, result) + */ } - - ``` -```go +```cpp // 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 +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; + + return validate(root->left, mi, root->val) && validate(root->right, root->val, ma); } ``` @@ -758,19 +671,20 @@ 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) - } else { - root.Right = insertIntoBST(root.Right, val) + if(root->val > val){ + root->left = insertIntoBST(root->left, val); } - return root + else + root->right = insertIntoBST(root->right, val); + + return root; } ``` From ac3c27d4b2640f5b147560a370d86d575d9b7dbe Mon Sep 17 00:00:00 2001 From: zxyang Date: Thu, 25 Jun 2020 22:56:01 +0800 Subject: [PATCH 007/110] Add some leetcode questions for binary tree --- data_structure/binary_tree.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/data_structure/binary_tree.md b/data_structure/binary_tree.md index b7c8e36e..a7276650 100644 --- a/data_structure/binary_tree.md +++ b/data_structure/binary_tree.md @@ -705,3 +705,7 @@ TreeNode* insertIntoBST(TreeNode* root, int val) { - [ ] [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/) From 9d371282c312abdddd9f2dcd43ee06978ed68914 Mon Sep 17 00:00:00 2001 From: zxyang Date: Fri, 26 Jun 2020 11:03:38 +0800 Subject: [PATCH 008/110] First commit on linked list --- data_structure/linked_list.md | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/data_structure/linked_list.md b/data_structure/linked_list.md index ebcc1625..4afe0e36 100644 --- a/data_structure/linked_list.md +++ b/data_structure/linked_list.md @@ -10,6 +10,7 @@ - 插入一个节点到排序链表 - 从一个链表中移除一个节点 - 翻转链表 +- k个一组翻转链表 - 合并两个链表 - 找到链表的中间节点 @@ -19,17 +20,22 @@ > 给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。 -```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 +```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; } ``` From e196fc5c7d0aa8196f38b7137f535427f3c11795 Mon Sep 17 00:00:00 2001 From: zxyang Date: Sat, 27 Jun 2020 16:24:58 +0800 Subject: [PATCH 009/110] Stack_queue processing --- data_structure/stack_queue.md | 197 +++++++++++++--------------------- 1 file changed, 77 insertions(+), 120 deletions(-) diff --git a/data_structure/stack_queue.md b/data_structure/stack_queue.md index e1ed723c..90d614ea 100644 --- a/data_structure/stack_queue.md +++ b/data_structure/stack_queue.md @@ -6,9 +6,9 @@ ![image.png](https://img.fuiboom.com/img/stack.png) -根据这个特点可以临时保存一些数据,之后用到依次再弹出来,常用于 DFS 深度搜索 +根据这个特点可以临时保存一些数据,之后用到依次再弹出来,常用于 ```DFS``` 深度搜索 -队列一般常用于 BFS 广度搜索,类似一层一层的搜索 +队列一般常用于 ```BFS``` 广度搜索,类似一层一层的搜索 ## Stack 栈 @@ -16,61 +16,41 @@ > 设计一个支持 push,pop,top 操作,并能在常数时间内检索到最小元素的栈。 -思路:用两个栈实现,一个最小栈始终保证最小值在顶部 +思路: +- 用两个栈实现,一个最小栈始终保证最小值在顶部 +- 用pair数据结构保存值和当前栈中最小值 -```go -type MinStack struct { - min []int - stack []int -} +```cpp +class MinStack { +public: + stack> st; //value, minVal -/** initialize your data structure here. */ -func Constructor() MinStack { - return MinStack{ - min: make([]int, 0), - stack: make([]int, 0), + /** initialize your data structure here. */ + MinStack() { } -} - -func (this *MinStack) Push(x int) { - min := this.GetMin() - if x < min { - this.min = append(this.min, x) - } else { - this.min = append(this.min, min) + void push(int x) { + if(st.empty()){ + st.push(make_pair(x, x)); + return; + } + st.push(make_pair(x, min(x, st.top().second))); } - this.stack = append(this.stack, x) -} - -func (this *MinStack) Pop() { - if len(this.stack) == 0 { - return + void pop() { + st.pop(); } - this.stack = this.stack[:len(this.stack)-1] - this.min = this.min[:len(this.min)-1] -} - -func (this *MinStack) Top() int { - if len(this.stack) == 0 { - return 0 + int top() { + return st.top().first; } - return this.stack[len(this.stack)-1] -} - -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(); @@ -88,41 +68,23 @@ func (this *MinStack) GetMin() int { 思路:通过栈保存原来的元素,遇到表达式弹出运算,再推入结果,重复这个过程 -```go -func evalRPN(tokens []string) int { - if len(tokens)==0{ - return 0 - } - stack:=make([]int,0) - for i:=0;i& tokens) { + 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(); + } ``` @@ -202,26 +164,29 @@ boolean DFS(int root, int target) { > 给定一个二叉树,返回它的*中序*遍历。 -```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; //一直向左 } - // 弹出 - 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; + } ``` @@ -424,35 +389,27 @@ func (this *MyQueue) Empty() bool { 二叉树层次遍历 -```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; } ``` @@ -513,7 +470,7 @@ func updateMatrix(matrix [][]int) [][]int { ## 总结 - 熟悉栈的使用场景 - - 后出先出,保存临时值 + - 后进先出,保存临时值 - 利用栈 DFS 深度搜索 - 熟悉队列的使用场景 - 利用队列 BFS 广度搜索 From f4f99be758a71d0ccd7023f6c27c026b76943c9b Mon Sep 17 00:00:00 2001 From: zxyang Date: Sat, 27 Jun 2020 17:29:46 +0800 Subject: [PATCH 010/110] Processing linked list --- data_structure/linked_list.md | 263 ++++++++++++++++------------------ 1 file changed, 123 insertions(+), 140 deletions(-) diff --git a/data_structure/linked_list.md b/data_structure/linked_list.md index 4afe0e36..ba05766c 100644 --- a/data_structure/linked_list.md +++ b/data_structure/linked_list.md @@ -79,23 +79,25 @@ func deleteDuplicates(head *ListNode) *ListNode { > 反转一个单链表。 -思路:用一个 prev 节点保存向前指针,temp 保存向后的临时指针 +思路:用一个 prev 节点保存向前指针,node 保存向后的临时指针 -```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 +```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 + + return prev; + } ``` @@ -105,44 +107,35 @@ func reverseList(head *ListNode) *ListNode { 思路:先遍历到 m 处,翻转,再拼接后续,注意指针处理 -```go -func reverseBetween(head *ListNode, m int, n int) *ListNode { - // 思路:先遍历到m处,翻转,再拼接后续,注意指针处理 - // 输入: 1->2->3->4->5->NULL, m = 2, n = 4 - if head == nil { - return head - } - // 头部变化所以使用dummy node - dummy := &ListNode{Val: 0} - dummy.Next = head - head = dummy - // 最开始:0->1->2->3->4->5->nil - var pre *ListNode - var i = 0 - for i < m { - pre = head - head = head.Next - i++ +```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; } ``` @@ -150,35 +143,37 @@ func reverseBetween(head *ListNode, m int, n int) *ListNode { > 将两个升序链表合并为一个新的升序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 -思路:通过 dummy node 链表,连接各个元素 +思路: +- 通过 dummy node 链表,连接各个元素 +- 利用优先队列```priority_queue```来merge -```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 - } - // 连接l2 未处理完节点 - for l2 != nil { - head.Next = l2 - head = head.Next - l2 = l2.Next +```cpp +struct cmp{ + bool operator()(const ListNode* a, const ListNode* b){ + return a->val > b->val; + } +}; + +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; + } ``` @@ -376,57 +371,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; } ``` @@ -437,30 +423,27 @@ 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; } ``` From 201d7ebd3a0a774a098214affe435f1dc70142be Mon Sep 17 00:00:00 2001 From: zxyang Date: Sat, 27 Jun 2020 20:33:54 +0800 Subject: [PATCH 011/110] processing linked_list --- data_structure/linked_list.md | 62 ++++++++++++++--------------------- 1 file changed, 24 insertions(+), 38 deletions(-) diff --git a/data_structure/linked_list.md b/data_structure/linked_list.md index ba05766c..58d4c2f5 100644 --- a/data_structure/linked_list.md +++ b/data_structure/linked_list.md @@ -456,50 +456,36 @@ ListNode *detectCycle(ListNode *head) { > 请判断一个链表是否为回文链表。 -```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 - } - var prev *ListNode - for head!=nil{ - t:=head.Next - head.Next=prev - prev=head - head=t +ListNode* reverse(ListNode* curNode){ + ListNode* prev = nullptr; + ListNode* node = nullptr; + while(curNode != nullptr){ + node = curNode->next; + curNode->next = prev; + prev = curNode; + curNode = node; } - return prev + return prev; } ``` From d2f0cca67680b87b55319b476fdb0fa93d209c44 Mon Sep 17 00:00:00 2001 From: zxyang Date: Thu, 2 Jul 2020 09:27:40 +0800 Subject: [PATCH 012/110] =?UTF-8?q?Add=20=E5=8D=95=E8=B0=83=E6=A0=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- data_structure/linked_list.md | 83 +++++++++++++++-------------------- data_structure/stack_queue.md | 57 +++++++++--------------- 2 files changed, 55 insertions(+), 85 deletions(-) diff --git a/data_structure/linked_list.md b/data_structure/linked_list.md index 58d4c2f5..b4ed6958 100644 --- a/data_structure/linked_list.md +++ b/data_structure/linked_list.md @@ -41,32 +41,24 @@ ListNode* deleteDuplicates(ListNode* 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 - } +```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; } ``` @@ -183,34 +175,29 @@ ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) { 思路:将大于 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; } ``` diff --git a/data_structure/stack_queue.md b/data_structure/stack_queue.md index 90d614ea..57385311 100644 --- a/data_structure/stack_queue.md +++ b/data_structure/stack_queue.md @@ -267,43 +267,26 @@ 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; + } ``` From bc7e406c5ff3750265ba1b22ecadf0ec4898210b Mon Sep 17 00:00:00 2001 From: zxyang Date: Sun, 5 Jul 2020 17:24:26 +0800 Subject: [PATCH 013/110] Finish linked_list --- data_structure/linked_list.md | 290 ++++++++++++++++++---------------- data_structure/stack_queue.md | 2 +- 2 files changed, 152 insertions(+), 140 deletions(-) diff --git a/data_structure/linked_list.md b/data_structure/linked_list.md index b4ed6958..1b286993 100644 --- a/data_structure/linked_list.md +++ b/data_structure/linked_list.md @@ -211,63 +211,66 @@ ListNode* partition(ListNode* head, int x) { 思路:归并排序,找中点和合并操作 -```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; + } + else{ + head->next = head2; + head2 = head2->next; } - head = head.Next + head = head->next; } - // 连接l1 未处理完节点 - for l1 != nil { - head.Next = l1 - head = head.Next - l1 = l1.Next + + while(head1 != nullptr){ + head->next = head1; + head1 = head1->next; + head = head->next; } - // 连接l2 未处理完节点 - for l2 != nil { - head.Next = l2 - head = head.Next - l2 = l2.Next + while(head2 != nullptr){ + head->next = head2; + head2 = head2->next; + head = head->next; } - return dummy.Next -} -func mergeSort(head *ListNode) *ListNode { - // 如果只有一个节点 直接就返回这个节点 - if head == nil || head.Next == nil{ - return head - } - // find middle - middle := findMiddle(head) - // 断开中间节点 - tail := middle.Next - middle.Next = nil - left := mergeSort(head) - right := mergeSort(tail) - result := mergeTwoLists(left, right) - return result + + return dummy->next; } ``` @@ -284,70 +287,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; } ``` @@ -483,39 +496,38 @@ ListNode* reverse(ListNode* curNode){ 思路: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; } ``` diff --git a/data_structure/stack_queue.md b/data_structure/stack_queue.md index 57385311..b187fc2d 100644 --- a/data_structure/stack_queue.md +++ b/data_structure/stack_queue.md @@ -18,7 +18,7 @@ 思路: - 用两个栈实现,一个最小栈始终保证最小值在顶部 -- 用pair数据结构保存值和当前栈中最小值 +- 用```pair```数据结构保存值和当前栈中最小值 ```cpp class MinStack { From 660623f23a6702993baf6e3a7d155bcf990e30fc Mon Sep 17 00:00:00 2001 From: zxyang Date: Mon, 6 Jul 2020 07:32:18 +0800 Subject: [PATCH 014/110] Add wei dao tou print linked list --- data_structure/linked_list.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/data_structure/linked_list.md b/data_structure/linked_list.md index 1b286993..07315382 100644 --- a/data_structure/linked_list.md +++ b/data_structure/linked_list.md @@ -531,6 +531,32 @@ Node* copyRandomList(Node* head) { } ``` +### [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; +} +``` + ## 总结 链表必须要掌握的一些点,通过下面练习题,基本大部分的链表类的题目都是手到擒来~ @@ -558,3 +584,4 @@ Node* copyRandomList(Node* head) { - [ ] [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/) From 2b9a6a691ac2c374c0563455905f95e1ec78aef2 Mon Sep 17 00:00:00 2001 From: zxyang Date: Mon, 6 Jul 2020 09:36:00 +0800 Subject: [PATCH 015/110] processing stack_queue --- data_structure/stack_queue.md | 129 +++++++++++++++++----------------- 1 file changed, 64 insertions(+), 65 deletions(-) diff --git a/data_structure/stack_queue.md b/data_structure/stack_queue.md index b187fc2d..5d0cb853 100644 --- a/data_structure/stack_queue.md +++ b/data_structure/stack_queue.md @@ -2,13 +2,13 @@ ## 简介 -栈的特点是后入先出 +栈的特点是**后入先出** ![image.png](https://img.fuiboom.com/img/stack.png) 根据这个特点可以临时保存一些数据,之后用到依次再弹出来,常用于 ```DFS``` 深度搜索 -队列一般常用于 ```BFS``` 广度搜索,类似一层一层的搜索 +队列的特点是**先入先出**, 一般常用于 ```BFS``` 广度搜索,类似一层一层的搜索 ## Stack 栈 @@ -97,50 +97,54 @@ int evalRPN(vector& tokens) { 思路:通过栈辅助进行操作 -```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; @@ -194,31 +198,26 @@ vector inorderTraversal(TreeNode* root) { > 给你无向连通图中一个节点的引用,请你返回该图的深拷贝(克隆)。 -```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; } ``` From c3b30b7791e6960468ef39c0e80414b548ab8396 Mon Sep 17 00:00:00 2001 From: zxyang Date: Mon, 6 Jul 2020 10:48:49 +0800 Subject: [PATCH 016/110] polish format --- data_structure/linked_list.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data_structure/linked_list.md b/data_structure/linked_list.md index 07315382..c72fcad2 100644 --- a/data_structure/linked_list.md +++ b/data_structure/linked_list.md @@ -136,7 +136,7 @@ ListNode* reverseBetween(ListNode* head, int m, int n) { > 将两个升序链表合并为一个新的升序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 思路: -- 通过 dummy node 链表,连接各个元素 +- 通过 ```dummy node``` 链表,连接各个元素 - 利用优先队列```priority_queue```来merge ```cpp From 43231c24be6b57ead1e0d12c98c2239dbd69aaea Mon Sep 17 00:00:00 2001 From: zxyang Date: Mon, 6 Jul 2020 21:20:09 +0800 Subject: [PATCH 017/110] =?UTF-8?q?=E5=8A=A0=E5=85=A5=E5=8D=95=E8=B0=83?= =?UTF-8?q?=E6=A0=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- data_structure/stack_queue.md | 64 ++++++++++++++++++++++++----------- 1 file changed, 44 insertions(+), 20 deletions(-) diff --git a/data_structure/stack_queue.md b/data_structure/stack_queue.md index 5d0cb853..d393466d 100644 --- a/data_structure/stack_queue.md +++ b/data_structure/stack_queue.md @@ -227,29 +227,43 @@ Node* clone(Node* node, unordered_map& visited){ 思路:通过深度搜索遍历可能性(注意标记已访问元素) -```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]); } ``` @@ -266,7 +280,7 @@ func dfs(grid [][]byte,i,j int)int{ ![image.png](https://img.fuiboom.com/img/stack_rain2.png) -- 利用单调栈去做这件事情 +- 利用**单调栈**去做这件事情 ```cpp int largestRectangleArea(vector& heights) { @@ -456,6 +470,16 @@ func updateMatrix(matrix [][]int) [][]int { - 利用栈 DFS 深度搜索 - 熟悉队列的使用场景 - 利用队列 BFS 广度搜索 +- 单调栈 + - 单调递增/减栈:栈内元素保持递增/减的栈 + - 以单增栈为例: + - 如果新元素比栈顶元素大,就入栈 + - 否则一直弹出栈顶元素,知道栈顶元素比新元素小,再将其压栈 + - 用处: + - 栈内元素递增 + - 当元素出栈时,这个**新元素**是栈顶元素**往后**找第一个比之小的元素 + - 当元素出栈后,新**栈顶元素**是出栈元素**往前**找第一个比之小的元素 + - ## 练习 From ccf08641cfaee27617f604f3e171bbf91cde9aea Mon Sep 17 00:00:00 2001 From: zxyang Date: Wed, 8 Jul 2020 20:34:31 +0800 Subject: [PATCH 018/110] =?UTF-8?q?=E5=8F=8C=E6=A0=88=E5=AE=9E=E7=8E=B0?= =?UTF-8?q?=E9=98=9F=E5=88=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- data_structure/stack_queue.md | 111 +++++++++++++++------------------- 1 file changed, 48 insertions(+), 63 deletions(-) diff --git a/data_structure/stack_queue.md b/data_structure/stack_queue.md index d393466d..b9482d84 100644 --- a/data_structure/stack_queue.md +++ b/data_structure/stack_queue.md @@ -311,75 +311,60 @@ int largestRectangleArea(vector& heights) { > 使用栈实现队列 -```go -type MyQueue struct { - stack []int - back []int -} - -/** Initialize your data structure here. */ -func Constructor() MyQueue { - return MyQueue{ - stack: make([]int, 0), - back: make([]int, 0), - } -} - -// 1 -// 3 -// 5 +```cpp +class MyQueue { +public: + stack inSt; + stack outSt; + /** Initialize your data structure here. */ + MyQueue() { -/** 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) } - 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) + + /** Push element x to the back of queue. */ + void push(int x) { + inSt.push(x); } - if len(this.back) == 0 { - return 0 + + /** 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; + } + } - 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) + + /** Get the front element. */ + int peek() { + int ele = this->pop(); + outSt.push(ele); + return ele; } - if len(this.back) == 0 { - return 0 + + /** Returns whether the queue is empty. */ + bool empty() { + return inSt.empty() && outSt.empty(); } - 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(); */ ``` @@ -467,9 +452,9 @@ func updateMatrix(matrix [][]int) [][]int { - 熟悉栈的使用场景 - 后进先出,保存临时值 - - 利用栈 DFS 深度搜索 + - 利用栈 ```DFS``` 深度搜索 - 熟悉队列的使用场景 - - 利用队列 BFS 广度搜索 + - 利用队列 ```BFS``` 广度搜索 - 单调栈 - 单调递增/减栈:栈内元素保持递增/减的栈 - 以单增栈为例: @@ -479,7 +464,7 @@ func updateMatrix(matrix [][]int) [][]int { - 栈内元素递增 - 当元素出栈时,这个**新元素**是栈顶元素**往后**找第一个比之小的元素 - 当元素出栈后,新**栈顶元素**是出栈元素**往前**找第一个比之小的元素 - - + ## 练习 From ea7f0c36c8bbac1050936a69557af87185abd7d9 Mon Sep 17 00:00:00 2001 From: zxyang Date: Wed, 8 Jul 2020 21:17:44 +0800 Subject: [PATCH 019/110] Finish stack_queue --- data_structure/stack_queue.md | 48 +++++++++++++++++------------------ 1 file changed, 23 insertions(+), 25 deletions(-) diff --git a/data_structure/stack_queue.md b/data_structure/stack_queue.md index b9482d84..0be2dd1d 100644 --- a/data_structure/stack_queue.md +++ b/data_structure/stack_queue.md @@ -399,7 +399,7 @@ vector> levelOrder(TreeNode* root){ > 给定一个由 0 和 1 组成的矩阵,找出每个元素到最近的 0 的距离。 > 两个相邻元素间的距离为 1 -```go +```cpp // BFS 从0进队列,弹出之后计算上下左右的结果,将上下左右重新进队列进行二层操作 // 0 0 0 0 // 0 x 0 0 @@ -415,36 +415,34 @@ vector> levelOrder(TreeNode* root){ // 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; } ``` From a8de58f7896f7bf4e61671e8d1ef1f34672718f8 Mon Sep 17 00:00:00 2001 From: zxyang Date: Wed, 8 Jul 2020 21:55:37 +0800 Subject: [PATCH 020/110] binary op --- data_structure/binary_op.md | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/data_structure/binary_op.md b/data_structure/binary_op.md index e77b8adc..93ef959e 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; } ``` @@ -98,14 +97,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; } ``` From 01c1a083a1bd3135b1b5b1fe3b95b82030524531 Mon Sep 17 00:00:00 2001 From: zxyang Date: Thu, 9 Jul 2020 09:51:42 +0800 Subject: [PATCH 021/110] Processing binary_op --- data_structure/binary_op.md | 65 ++++++++++++++++++------------------- 1 file changed, 31 insertions(+), 34 deletions(-) diff --git a/data_structure/binary_op.md b/data_structure/binary_op.md index 93ef959e..7b8557b7 100644 --- a/data_structure/binary_op.md +++ b/data_structure/binary_op.md @@ -47,20 +47,21 @@ int singleNumber(vector& nums) { > 给定一个**非空**整数数组,除了某个元素只出现一次以外,其余每个元素均出现了三次。找出那个只出现了一次的元素。 -```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; + } ``` @@ -68,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; } ``` From 9f61dc72398011618bbbcbb7aa43415960e92067 Mon Sep 17 00:00:00 2001 From: zxyang Date: Thu, 9 Jul 2020 21:15:49 +0800 Subject: [PATCH 022/110] Finish binary op --- data_structure/binary_op.md | 90 ++++++++++++++++++------------------- 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/data_structure/binary_op.md b/data_structure/binary_op.md index 7b8557b7..5200a5a8 100644 --- a/data_structure/binary_op.md +++ b/data_structure/binary_op.md @@ -109,34 +109,34 @@ int hammingWeight(uint32_t n) { > 给定一个非负整数  **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; } ``` @@ -146,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; } ``` @@ -164,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< Date: Fri, 10 Jul 2020 09:55:53 +0800 Subject: [PATCH 023/110] Processing binary_op --- basic_algorithm/binary_search.md | 280 ++++++++++++------------------- 1 file changed, 109 insertions(+), 171 deletions(-) diff --git a/basic_algorithm/binary_search.md b/basic_algorithm/binary_search.md index 15f336a3..79d9b526 100644 --- a/basic_algorithm/binary_search.md +++ b/basic_algorithm/binary_search.md @@ -19,32 +19,25 @@ > 给定一个  n  个元素有序的(升序)整型数组  nums 和一个目标值  target  ,写一个函数搜索  nums  中的 target,如果目标值存在返回下标,否则返回 -1。 -```go +```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; } ``` @@ -58,21 +51,19 @@ func search(nums []int, target int) int { 如果是最简单的二分搜索,不需要找第一个、最后一个位置、或者是没有重复元素,可以使用模板#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] @@ -82,66 +73,45 @@ func search(nums []int, target int) int { ## 常见题目 -### [search-for-range](https://www.lintcode.com/problem/search-for-a-range/description) +### [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 +```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 + } ``` @@ -149,30 +119,21 @@ func searchRange (A []int, target int) []int { > 给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。 -```go -func searchInsert(nums []int, target int) int { - // 思路:找到第一个 >= target 的元素位置 - start := 0 - end := len(nums) - 1 - for start+1 < end { - mid := start + (end-start)/2 - if nums[mid] == target { - // 标记开始位置 - start = mid - } else if nums[mid] > target { - end = mid - } 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,32 +144,24 @@ 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 +```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; } - 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 - } - return false + + if(matrix[left/cols][left%cols] == target || matrix[right/cols][right%cols] == target) return true; + + return false; } ``` @@ -217,23 +170,16 @@ func searchMatrix(matrix [][]int, target int) bool { > 假设你有 n 个版本 [1, 2, ..., n],你想找出导致之后所有版本出错的第一个错误的版本。 > 你可以通过调用  bool isBadVersion(version)  接口来判断版本号 version 是否在单元测试中出错。实现一个函数来查找第一个错误的版本。你应该尽量减少对调用 API 的次数。 -```go -func firstBadVersion(n int) int { - // 思路:二分搜索 - start := 0 - end := n - for start+1 < end { - mid := start + (end - start)/2 - if isBadVersion(mid) { - end = mid - } else if isBadVersion(mid) == false { - start = mid - } +```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; } - if isBadVersion(start) { - return start - } - return end + if(isBadVersion(left)) return left; + return right; } ``` @@ -242,28 +188,20 @@ func firstBadVersion(n int) int { > 假设按照升序排序的数组在预先未知的某个点上进行了旋转( 例如,数组  [0,1,2,4,5,6,7] 可能变为  [4,5,6,7,0,1,2] )。 > 请找出其中最小的元素。 -```go -func findMin(nums []int) int { - // 思路:/ / 最后一个值作为target,然后往左移动,最后比较start、end的值 - if len(nums) == 0 { - return -1 - } - start := 0 - end := len(nums) - 1 +```cpp +int findMin(vector& nums) { + if(nums.empty()) return 0; - 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] + 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]); } ``` From 8f2f936df543807ef712d0a66fccb53300c96984 Mon Sep 17 00:00:00 2001 From: zxyang Date: Fri, 10 Jul 2020 21:25:33 +0800 Subject: [PATCH 024/110] Finish binary_op --- basic_algorithm/binary_search.md | 145 +++++++++++-------------------- 1 file changed, 49 insertions(+), 96 deletions(-) diff --git a/basic_algorithm/binary_search.md b/basic_algorithm/binary_search.md index 79d9b526..3e51f98a 100644 --- a/basic_algorithm/binary_search.md +++ b/basic_algorithm/binary_search.md @@ -211,34 +211,21 @@ int findMin(vector& nums) { > ( 例如,数组  [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值比较,分为两种情况进行处理 +```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]); } ``` @@ -249,41 +236,27 @@ 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 +```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; + } ``` @@ -297,46 +270,26 @@ func search(nums []int, target int) int { > ( 例如,数组  [0,0,1,2,2,5,6]  可能变为  [2,5,6,0,0,1,2] )。 > 编写一个函数来判断给定的目标值是否存在于数组中。若存在返回  true,否则返回  false。(包含重复元素) -```go -func search(nums []int, target int) bool { - // 思路:/ / 两条上升直线,四种情况判断,并且处理重复数字 - if len(nums) == 0 { - return false - } - start := 0 - end := len(nums) - 1 - for start+1 < end { - // 处理重复数字 - for start < end && nums[start] == nums[start+1] { - start++ - } - for start < end && nums[end] == nums[end-1] { - end-- - } - mid := start + (end-start)/2 - // 相等直接返回 - if nums[mid] == target { - return true +```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; } - // 判断在那个区间,可能分为四种情况 - 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[mid] && target <= nums[right]) left = mid; + else right = mid; } } - if nums[start] == target || nums[end] == target { - return true - } - return false + if(nums[left] == target || nums[right] == target) return true; + else return false; } ``` From 4db646e1c135d5d0dbc70d79a4d2a3fe7ac5d7fe Mon Sep 17 00:00:00 2001 From: zxyang Date: Fri, 10 Jul 2020 23:32:00 +0800 Subject: [PATCH 025/110] Finish sort func --- basic_algorithm/sort.md | 256 +++++++++++++++++++++++----------------- 1 file changed, 149 insertions(+), 107 deletions(-) diff --git a/basic_algorithm/sort.md b/basic_algorithm/sort.md index 9eaa2cb8..447e41bb 100644 --- a/basic_algorithm/sort.md +++ b/basic_algorithm/sort.md @@ -1,86 +1,139 @@ # 排序 +```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; +} +``` + ## 常考排序 ### 快速排序 -```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); } -// 原地交换,所以传入交换索引 -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) + +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; } -// 分区 -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); } -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; + + 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++]; } - // 分治法: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 +151,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 - } +```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); + } } -func swap(a []int, i, j int) { - a[i], a[j] = a[j], a[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; + } } ``` From 6c966bde4abd7ad3cc379f32bb2f5ca3b01c9c40 Mon Sep 17 00:00:00 2001 From: zxyang Date: Fri, 10 Jul 2020 23:32:50 +0800 Subject: [PATCH 026/110] Finish sort 2 --- basic_algorithm/sort.md | 57 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/basic_algorithm/sort.md b/basic_algorithm/sort.md index 447e41bb..169ad837 100644 --- a/basic_algorithm/sort.md +++ b/basic_algorithm/sort.md @@ -21,6 +21,63 @@ bool isSorted(vector& a, int i, int j){ ## 常考排序 +### 选择排序 +```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] < min) + min = j; + } + swap(a, i , j); + } + +} +``` + +### 插入排序 + +```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; + } + } + + } + +} +``` + ### 快速排序 ```cpp From 20947bf61c82eefbc55d440f4a11482477840554 Mon Sep 17 00:00:00 2001 From: zxyang Date: Sat, 11 Jul 2020 11:29:07 +0800 Subject: [PATCH 027/110] Processing dp --- basic_algorithm/dp.md | 42 ++++++++++++++++++------------------------ 1 file changed, 18 insertions(+), 24 deletions(-) diff --git a/basic_algorithm/dp.md b/basic_algorithm/dp.md index d251ca7d..aacd4429 100644 --- a/basic_algorithm/dp.md +++ b/basic_algorithm/dp.md @@ -39,8 +39,8 @@ 动态规划和 DFS 区别 -- 二叉树 子问题是没有交集,所以大部分二叉树都用递归或者分治法,即 DFS,就可以解决 -- 像 triangle 这种是有重复走的情况,**子问题是有交集**,所以可以用动态规划来解决 +- 二叉树 子问题是没有交集,所以大部分二叉树都用递归或者分治法,即 ```DFS```,就可以解决 +- 像 ```triangle``` 这种是有重复走的情况,**子问题是有交集**,所以可以用动态规划来解决 动态规划,自底向上 @@ -638,35 +638,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,6 +665,13 @@ func min(a,b int)int{ > 在 n 个物品中挑选若干物品装入背包,最多能装多满?假设背包的大小为 m,每个物品的大小为 A[i] +```cpp +int backPack(vetor& backpack, int m){ + if(backpack.empty()) return 0; + int n = backpack.size(); +} +``` + ```go func backPack (m int, A []int) int { // write your code here From 71363a04a393318ba8054c9f7aee1e5cf71e716f Mon Sep 17 00:00:00 2001 From: zxyang Date: Sun, 12 Jul 2020 09:46:17 +0800 Subject: [PATCH 028/110] Processing dp --- basic_algorithm/dp.md | 129 ++++++++++++++++++++++++------------------ 1 file changed, 73 insertions(+), 56 deletions(-) diff --git a/basic_algorithm/dp.md b/basic_algorithm/dp.md index aacd4429..160fbb29 100644 --- a/basic_algorithm/dp.md +++ b/basic_algorithm/dp.md @@ -146,14 +146,14 @@ Function(x) { } ``` -动态规划:是一种解决问 题的思想,大规模问题的结果,是由小规模问 题的结果运算得来的。动态规划可用递归来实现(Memorization Search) +动态规划:是一种解决问题的思想,大规模问题的结果,是由小规模问题的结果运算得来的。动态规划可用递归来实现(Memorization Search) ## 使用场景 满足两个条件 - 满足以下条件之一 - - 求最大/最小值(Maximum/Minimum ) + - 求最大/最小值(Maximum/Minimum) - 求是否可行(Yes/No ) - 求可行个数(Count(\*) ) - 满足不能排序或者交换(Can not sort / swap ) @@ -164,11 +164,11 @@ Function(x) { 1. **状态 State** - 灵感,创造力,存储小规模问题的结果 -2. 方程 Function +2. **方程 Function** - 状态之间的联系,怎么通过小的状态,来算大的状态 -3. 初始化 Intialization +3. **初始化 Intialization** - 最极限的小状态是什么, 起点 -4. 答案 Answer +4. **答案 Answer** - 最大的那个状态是什么,终点 ## 常见四种类型 @@ -180,7 +180,7 @@ Function(x) { > 注意点 > -> - 贪心算法大多题目靠背答案,所以如果能用动态规划就尽量用动规,不用贪心算法 +> - 贪心算法大多题目靠背答案,所以如果能用动态规划就尽量用动规,不用贪心算法。 ## 1、矩阵类型(10%) @@ -666,74 +666,91 @@ int coinChange(vector& coins, int amount) { > 在 n 个物品中挑选若干物品装入背包,最多能装多满?假设背包的大小为 m,每个物品的大小为 A[i] ```cpp -int backPack(vetor& backpack, int m){ - if(backpack.empty()) return 0; - int n = backpack.size(); -} -``` - -```go -func backPack (m int, A []int) int { +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]; } ``` From 97bf56e07f60e10a6495f8071fa0bdc582841acc Mon Sep 17 00:00:00 2001 From: zxyang Date: Sun, 12 Jul 2020 17:01:49 +0800 Subject: [PATCH 029/110] Processing dp --- basic_algorithm/dp.md | 307 ++++++++++++++++++++++++------------------ 1 file changed, 174 insertions(+), 133 deletions(-) diff --git a/basic_algorithm/dp.md b/basic_algorithm/dp.md index 160fbb29..9a751df0 100644 --- a/basic_algorithm/dp.md +++ b/basic_algorithm/dp.md @@ -189,38 +189,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] + - 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] +```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 +222,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 +248,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 +283,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] - } - return f[n] +```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 res; } ``` @@ -326,60 +305,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,7 +391,45 @@ func min(a, b int) int { > 给定一个字符串 _s_,将 _s_ 分割成一些子串,使每个子串都是回文串。 > 返回符合要求的最少分割次数。 -```go +```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 + 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]; +} +``` + +```cpp func minCut(s string) int { // state: f[i] "前i"个字符组成的子字符串需要最少几次cut(个数-1为索引) // function: f[i] = MIN{f[j]+1}, j < i && [j+1 ~ i]这一段是一个回文串 From 94a9c5090301cb13279e1428eb54dccf81d12a8d Mon Sep 17 00:00:00 2001 From: zxyang Date: Mon, 13 Jul 2020 21:56:28 +0800 Subject: [PATCH 030/110] Processing dp --- basic_algorithm/dp.md | 125 +++++++++++++++++++++--------------------- 1 file changed, 62 insertions(+), 63 deletions(-) diff --git a/basic_algorithm/dp.md b/basic_algorithm/dp.md index 9a751df0..61ee99aa 100644 --- a/basic_algorithm/dp.md +++ b/basic_algorithm/dp.md @@ -412,7 +412,8 @@ int minCut(string s) { // now lets populate the second table, every index in 'cuts' stores the minimum cuts needed // for the substring from that index till the end - vector cuts(n, 0); + //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--) { @@ -425,47 +426,21 @@ int minCut(string s) { cuts[startIndex] = minCuts; } - return cuts[0]; -} -``` - -```cpp -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 + 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]; +} } ``` @@ -477,40 +452,64 @@ 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*  是否可以被空格拆分为一个或多个在字典中出现的单词。 From fd049359d573405f54ce3603aa6f40e807d3eb68 Mon Sep 17 00:00:00 2001 From: zxyang Date: Tue, 14 Jul 2020 08:27:06 +0800 Subject: [PATCH 031/110] Finish dp --- basic_algorithm/dp.md | 200 ++++++++++++++++-------------------------- 1 file changed, 77 insertions(+), 123 deletions(-) diff --git a/basic_algorithm/dp.md b/basic_algorithm/dp.md index 61ee99aa..73b68e47 100644 --- a/basic_algorithm/dp.md +++ b/basic_algorithm/dp.md @@ -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] - } - } - // 递推求解 - 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] - } +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]); } } - 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; } ``` @@ -515,57 +502,42 @@ int bisearch(vector& nums, int num){ > 给定一个**非空**字符串  *s*  和一个包含**非空**单词列表的字典  *wordDict*,判定  *s*  是否可以被空格拆分为一个或多个在字典中出现的单词。 ```go -func wordBreak(s string, wordDict []string) bool { +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(); - 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)] -} + vector dp(n + 1, false); + dp[0] = true; -var dict = make(map[string]bool) + 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; + } + } + } -func maxLen(wordDict []string) int { - max := 0 - for _, v := range wordDict { - dict[v] = true - if len(v) > max { - max = len(v) - } - } - return max -} + return dp[n]; -func inDict(s string) bool { - _, ok := dict[s] - return ok } ``` 小结 -常见处理方式是给 0 位置占位,这样处理问题时一视同仁,初始化则在原来基础上 length+1,返回结果 f[n] +常见处理方式是给 0 位置占位,这样处理问题时一视同仁,初始化则在原来基础上```length+1```,返回结果 f[n] - 状态可以为前 i 个 -- 初始化 length+1 -- 取值 index=i-1 -- 返回值:f[n]或者 f[m][n] +- 初始化 ```length+1``` +- 取值 ```index=i-1``` +- 返回值:```f[n]```或者 ```f[m][n]``` ## Two Sequences DP(40%) @@ -575,8 +547,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 @@ -584,27 +556,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]; } ``` @@ -633,36 +598,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]; } ``` From 9481385858f5e08f0adb6a9eb79425af33180cce Mon Sep 17 00:00:00 2001 From: zxyang Date: Tue, 14 Jul 2020 09:25:47 +0800 Subject: [PATCH 032/110] Processing sliding_window --- advanced_algorithm/slide_window.md | 83 ++++++++++++------------------ 1 file changed, 34 insertions(+), 49 deletions(-) diff --git a/advanced_algorithm/slide_window.md b/advanced_algorithm/slide_window.md index 4e043302..11978717 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); } ``` From 7270d85113340c08532cebbdacc9ce303a75f7f9 Mon Sep 17 00:00:00 2001 From: zxyang Date: Tue, 14 Jul 2020 09:59:59 +0800 Subject: [PATCH 033/110] Finish sliding window --- advanced_algorithm/slide_window.md | 169 +++++++++++++---------------- 1 file changed, 75 insertions(+), 94 deletions(-) diff --git a/advanced_algorithm/slide_window.md b/advanced_algorithm/slide_window.md index 11978717..2c4f541c 100644 --- a/advanced_algorithm/slide_window.md +++ b/advanced_algorithm/slide_window.md @@ -96,42 +96,37 @@ string minWindow(string s, string t) { > 给定两个字符串  **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; } ``` @@ -140,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; } ``` @@ -190,35 +182,24 @@ func findAnagrams(s string, p string) []int { > 解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。 ```go -func lengthOfLongestSubstring(s string) int { +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; } ``` From 37b98a36719417b243fa6e91bbf3287bd43e9cd0 Mon Sep 17 00:00:00 2001 From: zxyang Date: Tue, 14 Jul 2020 10:00:48 +0800 Subject: [PATCH 034/110] Finish sliding window --- advanced_algorithm/slide_window.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/advanced_algorithm/slide_window.md b/advanced_algorithm/slide_window.md index 2c4f541c..23e0f97b 100644 --- a/advanced_algorithm/slide_window.md +++ b/advanced_algorithm/slide_window.md @@ -181,7 +181,7 @@ vector findAnagrams(string s, string p) { > 输出: 3 > 解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。 -```go +```cpp int lengthOfLongestSubstring(string s) { // 滑动窗口核心点:1、右指针右移 2、根据题意收缩窗口 3、左指针右移更新窗口 4、根据题意计算结果 int n = s.length(); From d9f3c43a8711e384de0ce814e833850a684b5a8d Mon Sep 17 00:00:00 2001 From: zxyang Date: Tue, 14 Jul 2020 22:57:47 +0800 Subject: [PATCH 035/110] Processing recursion --- advanced_algorithm/recursion.md | 85 +++++++++++++++------------------ 1 file changed, 38 insertions(+), 47 deletions(-) diff --git a/advanced_algorithm/recursion.md b/advanced_algorithm/recursion.md index ccfa3757..bc7010b3 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,24 +27,25 @@ 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/) @@ -96,23 +92,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; } ``` From e2270cb1b6f67cb56a2aa6a28a85682779ce17eb Mon Sep 17 00:00:00 2001 From: zxyang Date: Wed, 15 Jul 2020 08:45:56 +0800 Subject: [PATCH 036/110] Finish recursion --- advanced_algorithm/recursion.md | 45 +++++++++++++++------------------ 1 file changed, 21 insertions(+), 24 deletions(-) diff --git a/advanced_algorithm/recursion.md b/advanced_algorithm/recursion.md index bc7010b3..ae68a6d3 100644 --- a/advanced_algorithm/recursion.md +++ b/advanced_algorithm/recursion.md @@ -52,34 +52,31 @@ void reverseString(vector& s) { > 给定一个整数 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; } ``` From 5e8edac40c2c17c785bbf23b3ea7f4c2427304ab Mon Sep 17 00:00:00 2001 From: zxyang Date: Wed, 15 Jul 2020 09:08:13 +0800 Subject: [PATCH 037/110] Finish binary_search_tree --- advanced_algorithm/binary_search_tree.md | 178 +++++++++-------------- 1 file changed, 71 insertions(+), 107 deletions(-) diff --git a/advanced_algorithm/binary_search_tree.md b/advanced_algorithm/binary_search_tree.md index f1d8aa93..c62599b9 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); } -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); } - ``` ## 练习 From d0bbf4fed946c52247a68a7187ec87f2b9dcf46e Mon Sep 17 00:00:00 2001 From: zxyang Date: Wed, 15 Jul 2020 20:14:06 +0800 Subject: [PATCH 038/110] Finish binary_search_tree --- advanced_algorithm/binary_search_tree.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/advanced_algorithm/binary_search_tree.md b/advanced_algorithm/binary_search_tree.md index c62599b9..ea4ac878 100644 --- a/advanced_algorithm/binary_search_tree.md +++ b/advanced_algorithm/binary_search_tree.md @@ -23,7 +23,7 @@ * } */ bool isValidBST(TreeNode* root) { - return validate(root, LONG_MIN, LONG_MAX); + return validate(root, LONG_MIN, LONG_MAX); //给二叉树补充MIN和MAX } bool validate(TreeNode* root, long mi, long ma){ From 681a4ff101bd84798f12164bd33addb3dfc03d2b Mon Sep 17 00:00:00 2001 From: zxyang Date: Wed, 15 Jul 2020 21:40:42 +0800 Subject: [PATCH 039/110] Processing backtrack --- advanced_algorithm/backtrack.md | 56 +++++++++++++++++++++++---------- 1 file changed, 39 insertions(+), 17 deletions(-) diff --git a/advanced_algorithm/backtrack.md b/advanced_algorithm/backtrack.md index bd923e61..c21b6e0a 100644 --- a/advanced_algorithm/backtrack.md +++ b/advanced_algorithm/backtrack.md @@ -26,38 +26,60 @@ 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,返回该数组所有可能的子集(幂集)。说明:解集不能包含重复的子集。 From 6c35ddda4636d78de6c171e9de1e4edbcb81a368 Mon Sep 17 00:00:00 2001 From: zxyang Date: Wed, 15 Jul 2020 22:57:36 +0800 Subject: [PATCH 040/110] Processing backtrack --- advanced_algorithm/backtrack.md | 68 ++++++++++++++++++++------------- 1 file changed, 41 insertions(+), 27 deletions(-) diff --git a/advanced_algorithm/backtrack.md b/advanced_algorithm/backtrack.md index c21b6e0a..09c8a5d2 100644 --- a/advanced_algorithm/backtrack.md +++ b/advanced_algorithm/backtrack.md @@ -84,40 +84,54 @@ vector> subsets(vector& nums) { > 给定一个可能包含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。说明:解集不能包含重复的子集。 -```go -import ( - "sort" -) +```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; + } -func subsetsWithDup(nums []int) [][]int { - // 保存最终结果 - result := make([][]int, 0) - // 保存中间结果 - list := make([]int, 0) - // 先排序 - sort.Ints(nums) - backtrack(nums, 0, list, &result) - return result + 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(); } } ``` From b0d1426a4d9ff6fad6f4c594e962c4c11104e5c7 Mon Sep 17 00:00:00 2001 From: zxyang Date: Thu, 16 Jul 2020 22:42:20 +0800 Subject: [PATCH 041/110] Finish backtrack --- advanced_algorithm/backtrack.md | 147 +++++++++++++++++--------------- 1 file changed, 80 insertions(+), 67 deletions(-) diff --git a/advanced_algorithm/backtrack.md b/advanced_algorithm/backtrack.md index 09c8a5d2..cbc5107c 100644 --- a/advanced_algorithm/backtrack.md +++ b/advanced_algorithm/backtrack.md @@ -142,89 +142,102 @@ void backtrack(vector& nums, int idx, vector track, vector 思路:需要记录已经选择过的元素,满足条件的结果才进行返回 -```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(); + } + } ``` From d50597e7a968b77c53e31030a5f0fc28d23515e7 Mon Sep 17 00:00:00 2001 From: zxyang Date: Fri, 17 Jul 2020 07:33:32 +0800 Subject: [PATCH 042/110] Add sort.cpp tree.cpp to src --- src/sort.cpp | 216 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/tree.cpp | 98 +++++++++++++++++++++++ 2 files changed, 314 insertions(+) create mode 100644 src/sort.cpp create mode 100644 src/tree.cpp 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; + } + From 55a698f053ea10222beda99bad15f1c0c6dd679d Mon Sep 17 00:00:00 2001 From: zxyang Date: Sun, 19 Jul 2020 10:27:34 +0800 Subject: [PATCH 043/110] Add template for bfs --- data_structure/stack_queue.md | 2 + templates/BFS.md | 75 +++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+) create mode 100644 templates/BFS.md diff --git a/data_structure/stack_queue.md b/data_structure/stack_queue.md index 0be2dd1d..a6d7abce 100644 --- a/data_structure/stack_queue.md +++ b/data_structure/stack_queue.md @@ -305,6 +305,8 @@ int largestRectangleArea(vector& heights) { ## 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/) 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 From 0f5652776bfe90de32c9363cab89d1c3bb765ac5 Mon Sep 17 00:00:00 2001 From: zxyang Date: Mon, 20 Jul 2020 23:11:47 +0800 Subject: [PATCH 044/110] Add dp: perfect-squares --- basic_algorithm/dp.md | 53 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/basic_algorithm/dp.md b/basic_algorithm/dp.md index 73b68e47..2995b447 100644 --- a/basic_algorithm/dp.md +++ b/basic_algorithm/dp.md @@ -501,7 +501,7 @@ int bisearch(vector& nums, int num){ > 给定一个**非空**字符串  *s*  和一个包含**非空**单词列表的字典  *wordDict*,判定  *s*  是否可以被空格拆分为一个或多个在字典中出现的单词。 -```go +```cpp bool wordBreak(string s, vector& wordDict) { // f[i] 表示前i个字符是否可以被切分 // f[i] = f[j] && s[j+1~i] in wordDict @@ -527,8 +527,58 @@ bool wordBreak(string s, vector& wordDict) { return dp[n]; } +``` + +### [perfect-squares](https://leetcode-cn.com/problems/perfect-squares/) +> 给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, ...)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。 + +- 思路:动态规划法 +- 思路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); + } + 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的时候要剪枝,利用一个数组记录已经计算过的节点 小结 @@ -765,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%) From 92b90e55295c365cb1821283c459a76d2bca884b Mon Sep 17 00:00:00 2001 From: yangzx Date: Tue, 21 Jul 2020 11:47:31 +0800 Subject: [PATCH 045/110] =?UTF-8?q?Add=20dfs=20=E6=B7=B1=E5=BA=A6=E4=BC=98?= =?UTF-8?q?=E5=85=88=E9=81=8D=E5=8E=86=E6=A8=A1=E6=9D=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- templates/DFS.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 templates/DFS.md diff --git a/templates/DFS.md b/templates/DFS.md new file mode 100644 index 00000000..5e4c12c1 --- /dev/null +++ b/templates/DFS.md @@ -0,0 +1,5 @@ +# 深度优先遍历 +### dfs跟bfs区别在于**遍历顺序**的不同 + +> BFS中:**更早访问的节点距离根结点更近** +> DFS中,找到的第一条路径不一定是最短路径 \ No newline at end of file From fef5b016fda1c9838635d7e9d091953a4d7f403d Mon Sep 17 00:00:00 2001 From: yangzx Date: Tue, 21 Jul 2020 12:01:07 +0800 Subject: [PATCH 046/110] Finish dfs templates --- templates/DFS.md | 49 ++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/templates/DFS.md b/templates/DFS.md index 5e4c12c1..267b4e38 100644 --- a/templates/DFS.md +++ b/templates/DFS.md @@ -1,5 +1,50 @@ # 深度优先遍历 ### dfs跟bfs区别在于**遍历顺序**的不同 -> BFS中:**更早访问的节点距离根结点更近** -> DFS中,找到的第一条路径不一定是最短路径 \ No newline at end of file +- 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 visted; + 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; +} +``` From ba9cb5e541afc7676ee321b2c1cf4f6d69376c9a Mon Sep 17 00:00:00 2001 From: yangzx Date: Tue, 21 Jul 2020 13:27:10 +0800 Subject: [PATCH 047/110] Finish DFS template --- templates/DFS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/DFS.md b/templates/DFS.md index 267b4e38..51b54fab 100644 --- a/templates/DFS.md +++ b/templates/DFS.md @@ -24,7 +24,7 @@ boolean DFS(Node cur, Node target, Set visited) { > 以上算法实际调用了系统提供的**隐式栈**, 【**模板二**】 -- 要是递归栈太深,会导致溢出,可以利用显式栈优化 +- 要是递归栈太深,会导致溢出,可以利用**显式栈**优化 ```java /* From d6f3538620e0b275cb17330062234936d626dbb3 Mon Sep 17 00:00:00 2001 From: zxyang Date: Tue, 21 Jul 2020 22:25:28 +0800 Subject: [PATCH 048/110] Improve dfs --- templates/DFS.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/templates/DFS.md b/templates/DFS.md index 51b54fab..c5cc9552 100644 --- a/templates/DFS.md +++ b/templates/DFS.md @@ -14,17 +14,17 @@ 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 visted; + add next to visited; return true if DFS(next, target, visited) == true; } } return false; } ``` -> 以上算法实际调用了系统提供的**隐式栈**, +> 以上算法实际调用了系统提供的**隐式栈**. 【**模板二**】 -- 要是递归栈太深,会导致溢出,可以利用**显式栈**优化 +- 要是递归栈太深,会导致溢出,可以利用**显式栈**优化 ```java /* From 1ba414f9b4753b380487ba579617b80e5d761fb2 Mon Sep 17 00:00:00 2001 From: zxyang Date: Sun, 2 Aug 2020 13:40:05 +0800 Subject: [PATCH 049/110] Add puzzles --- puzzles/bayes.md | 11 +++++++++++ puzzles/probability.md | 0 2 files changed, 11 insertions(+) create mode 100644 puzzles/bayes.md create mode 100644 puzzles/probability.md diff --git a/puzzles/bayes.md b/puzzles/bayes.md new file mode 100644 index 00000000..64e81ebb --- /dev/null +++ b/puzzles/bayes.md @@ -0,0 +1,11 @@ +# 贝叶斯定理 +## 已知某些条件下,某事件的发生几率 + +> 通常,事件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的**先验概率** \ No newline at end of file diff --git a/puzzles/probability.md b/puzzles/probability.md new file mode 100644 index 00000000..e69de29b From 1a12571797ad4747a8a3439aa9bed14becde6e40 Mon Sep 17 00:00:00 2001 From: zxyang Date: Sun, 2 Aug 2020 13:41:01 +0800 Subject: [PATCH 050/110] Add bayes --- puzzles/bayes.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/puzzles/bayes.md b/puzzles/bayes.md index 64e81ebb..add823af 100644 --- a/puzzles/bayes.md +++ b/puzzles/bayes.md @@ -8,4 +8,8 @@ $$ P(A|B) = P(B|A)P(A)/P(B) $$ > - P(A|B)称为A的**后验概率** > - P(A)就是A的**先验概率** > - P(B|A)称为B的**后验概率** -> - P(B)就是B的**先验概率** \ No newline at end of file +> - P(B)就是B的**先验概率** + +贝叶斯定理用语言描述就是: +> 后验概率 = (似然性*先验概率)/ 标准化常量 + From 7dc1b197b13df26987ccdd6c1cfd0e2cbdb3655a Mon Sep 17 00:00:00 2001 From: zxyang Date: Sun, 2 Aug 2020 22:16:25 +0800 Subject: [PATCH 051/110] Add lines --- data_structure/linked_list.md | 1 + puzzles/probability.md | 3 +++ 2 files changed, 4 insertions(+) diff --git a/data_structure/linked_list.md b/data_structure/linked_list.md index c72fcad2..1af9f072 100644 --- a/data_structure/linked_list.md +++ b/data_structure/linked_list.md @@ -569,6 +569,7 @@ vector reversePrint(ListNode* head) { - 翻转链表 - 合并两个链表 - 找到链表的中间节点 +- 很多情况下,需要跟踪当前节点的前一个节点 ## 练习 diff --git a/puzzles/probability.md b/puzzles/probability.md index e69de29b..a0ce5c25 100644 --- a/puzzles/probability.md +++ b/puzzles/probability.md @@ -0,0 +1,3 @@ +# 概率分布 +## 随机变量的概率分布函数 + From 5971b8f5f0286fdea7e7313c73fd827448b67fc2 Mon Sep 17 00:00:00 2001 From: zxyang Date: Mon, 3 Aug 2020 22:20:21 +0800 Subject: [PATCH 052/110] Add trie and hash data_structure --- data_structure/hash.md | 143 ++++++++++++++++++++++++++++++++++ data_structure/linked_list.md | 3 + data_structure/trie.md | 2 + 3 files changed, 148 insertions(+) create mode 100644 data_structure/hash.md create mode 100644 data_structure/trie.md diff --git a/data_structure/hash.md b/data_structure/hash.md new file mode 100644 index 00000000..832e3686 --- /dev/null +++ b/data_structure/hash.md @@ -0,0 +1,143 @@ +# 哈希表 +## 哈希表包括哈希集合和哈希映射 +- 哈希集合:**集合**数据结构的实现之一,用于存储**非重复值** +- 哈希映射:**映射**数据结构的实现之一,用于存储```(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; +} +``` + diff --git a/data_structure/linked_list.md b/data_structure/linked_list.md index 1af9f072..7a51dd92 100644 --- a/data_structure/linked_list.md +++ b/data_structure/linked_list.md @@ -570,6 +570,9 @@ vector reversePrint(ListNode* head) { - 合并两个链表 - 找到链表的中间节点 - 很多情况下,需要跟踪当前节点的前一个节点 +- 与数组插入删除索引的复杂度对比 + +![复杂度分析](https://s1.ax1x.com/2020/08/02/atRrz6.md.png) ## 练习 diff --git a/data_structure/trie.md b/data_structure/trie.md new file mode 100644 index 00000000..f79d3eff --- /dev/null +++ b/data_structure/trie.md @@ -0,0 +1,2 @@ +# 前缀树 +## 又称```字典树``` From e468cbac183064321d61a59438818d1c93537ea2 Mon Sep 17 00:00:00 2001 From: zxyang Date: Tue, 4 Aug 2020 09:15:49 +0800 Subject: [PATCH 053/110] Add Trie and topological sort --- advanced_algorithm/topological_sort.md | 109 +++++++++++++++++++++++++ data_structure/trie.md | 1 + images/tupulogic_sort.png | Bin 0 -> 9360 bytes 3 files changed, 110 insertions(+) create mode 100644 advanced_algorithm/topological_sort.md create mode 100644 images/tupulogic_sort.png 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/data_structure/trie.md b/data_structure/trie.md index f79d3eff..3cd8e3da 100644 --- a/data_structure/trie.md +++ b/data_structure/trie.md @@ -1,2 +1,3 @@ # 前缀树 ## 又称```字典树``` + diff --git a/images/tupulogic_sort.png b/images/tupulogic_sort.png new file mode 100644 index 0000000000000000000000000000000000000000..d5cd7cda598e34cf540fbe90169cc4e4e0ded3b7 GIT binary patch literal 9360 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 Date: Tue, 4 Aug 2020 09:32:31 +0800 Subject: [PATCH 054/110] Implement trie --- data_structure/trie.md | 16 +++++++++++++++- images/trie.png | Bin 0 -> 46198 bytes 2 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 images/trie.png diff --git a/data_structure/trie.md b/data_structure/trie.md index 3cd8e3da..46b90e1b 100644 --- a/data_structure/trie.md +++ b/data_structure/trie.md @@ -1,3 +1,17 @@ # 前缀树 -## 又称```字典树``` +## 背景 + +```前缀树```, 又称```字典树```, 是```N```叉树的一种。它是用来```存储字符串```的,前缀树的每一个节点代表一个 ```字符串(前缀)```。每一个节点会有多个子节点,通往不同子节点的路径上有着不同的字符。子节点代表的字符串是由节点本身的 ```原始字符串``` ,以及 ```通往该子节点路径上所有的字符``` 组成的。 + +示例: + +![trie](../images/trie.png) + +节点的值是从根节点开始,与其经过的路径中的字符串按顺序组成。 + +前缀树的一个重要的特性是,节点所有的后代都与该节点相关的字符串有着共同的前缀。这就是 前缀树 名称的由来。 + +前缀树有着广泛的应用,例如自动补全,拼写检查等等。 + +> 根节点是```空字符串``` diff --git a/images/trie.png b/images/trie.png new file mode 100644 index 0000000000000000000000000000000000000000..03dec773057342092a07ad45324831f3621c7d2a GIT binary patch literal 46198 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 Date: Wed, 5 Aug 2020 07:22:52 +0800 Subject: [PATCH 055/110] Add trie --- data_structure/trie.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/data_structure/trie.md b/data_structure/trie.md index 46b90e1b..a2801d91 100644 --- a/data_structure/trie.md +++ b/data_structure/trie.md @@ -15,3 +15,6 @@ 前缀树有着广泛的应用,例如自动补全,拼写检查等等。 > 根节点是```空字符串``` + + + From 4bca221e4d36f4ecd43ee51f2368e5b578f24b91 Mon Sep 17 00:00:00 2001 From: zxyang Date: Thu, 6 Aug 2020 23:16:34 +0800 Subject: [PATCH 056/110] Add delete node in bst --- data_structure/binary_tree.md | 37 +++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/data_structure/binary_tree.md b/data_structure/binary_tree.md index a7276650..565bdc6f 100644 --- a/data_structure/binary_tree.md +++ b/data_structure/binary_tree.md @@ -688,6 +688,43 @@ TreeNode* insertIntoBST(TreeNode* root, int val) { } ``` +二叉搜索树的删除要复杂点,考虑子节点的数目分为以下三种: +- 目标节点没有子节点,则直接移除该目标节点 +- 目标节点只有一个子节点,用其子节点直接代替 +- 目标节点有两个子节点,需要用其中序后继节点或者前驱节点来替换,再删除该目标节点。 + +#### 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; +} +``` + ## 总结 - 掌握二叉树递归与非递归遍历 From faa08391925a7934a4d55a7200f5b8f784a05b20 Mon Sep 17 00:00:00 2001 From: zxyang Date: Fri, 7 Aug 2020 01:16:42 +0800 Subject: [PATCH 057/110] Finish trie intro --- data_structure/trie.md | 40 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/data_structure/trie.md b/data_structure/trie.md index a2801d91..478fd63d 100644 --- a/data_structure/trie.md +++ b/data_structure/trie.md @@ -16,5 +16,43 @@ > 根节点是```空字符串``` - +## 表示 + +```前缀树```的特别在于字符和子节点之间的对应关系,怎样高效表示这种对应关系是重点 + +- 数组 + - 假如只存储```a```到```z```的字符串,可以每个节点声明一个```26```维的数据保存子节点。 + - 对于特定字符```c```,使用```c-'a'```来作为索引查找数组中相应节点。 +```cpp +// change this value to adapt to different cases +#define N 26 + +struct TrieNode { + TrieNode* children[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 { + unordered_map children; + + // 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] + */ +``` +> 通过相应的字符来访问特定的子节点 ```更为容易``` 。但它可能比使用数组 ```稍慢一些``` 。但是,由于我们只存储我们需要的子节点,因此 ```节省了空间``` 。这个方法也更加 ```灵活``` ,因为我们不受到```固定长度和固定范围```的限制。 From af8733432684b7bd35d92e623a013a8b990d08c2 Mon Sep 17 00:00:00 2001 From: zxyang Date: Fri, 7 Aug 2020 01:21:55 +0800 Subject: [PATCH 058/110] Add trie --- data_structure/trie.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/data_structure/trie.md b/data_structure/trie.md index 478fd63d..0862d392 100644 --- a/data_structure/trie.md +++ b/data_structure/trie.md @@ -10,7 +10,7 @@ 节点的值是从根节点开始,与其经过的路径中的字符串按顺序组成。 -前缀树的一个重要的特性是,节点所有的后代都与该节点相关的字符串有着共同的前缀。这就是 前缀树 名称的由来。 +前缀树的一个重要的特性是,节点所有的后代都与该节点相关的字符串有着共同的前缀。这就是 ```前缀树``` 名称的由来。 前缀树有着广泛的应用,例如自动补全,拼写检查等等。 @@ -18,7 +18,7 @@ ## 表示 -```前缀树```的特别在于字符和子节点之间的对应关系,怎样高效表示这种对应关系是重点 +```前缀树```的特别在于字符和子节点之间的对应关系,怎样高效表示这种对应关系是重点。 - 数组 - 假如只存储```a```到```z```的字符串,可以每个节点声明一个```26```维的数据保存子节点。 @@ -56,3 +56,6 @@ struct TrieNode { ``` > 通过相应的字符来访问特定的子节点 ```更为容易``` 。但它可能比使用数组 ```稍慢一些``` 。但是,由于我们只存储我们需要的子节点,因此 ```节省了空间``` 。这个方法也更加 ```灵活``` ,因为我们不受到```固定长度和固定范围```的限制。 +> https://leetcode-cn.com/leetbook/read/trie/x7ke5m/ + + From 6e1fc0744d945a9be7563ae0aab55f8220c06314 Mon Sep 17 00:00:00 2001 From: yangzx Date: Fri, 7 Aug 2020 12:32:03 +0800 Subject: [PATCH 059/110] Change bst delete --- data_structure/binary_tree.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data_structure/binary_tree.md b/data_structure/binary_tree.md index 565bdc6f..adc98504 100644 --- a/data_structure/binary_tree.md +++ b/data_structure/binary_tree.md @@ -708,7 +708,7 @@ TreeNode* deleteNode(TreeNode* root, int key) { //分情况讨论 //只有左节点,替换为右节点 //只有右节点,替换为左节点 - //左右节点都存在,将左节点放到右子树的最左边节点上 + //左右节点都存在,将左子树节点放到右子树的最左边节点的左子树 if(root->right == nullptr) return root->left; else if(root->left == nullptr) return root->right; else{ From 357f38db64b94725912b1eafc1e0aa19875f1cad Mon Sep 17 00:00:00 2001 From: yangzx Date: Sat, 8 Aug 2020 10:30:02 +0800 Subject: [PATCH 060/110] Add height balanced bst --- data_structure/binary_tree.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/data_structure/binary_tree.md b/data_structure/binary_tree.md index adc98504..810d582f 100644 --- a/data_structure/binary_tree.md +++ b/data_structure/binary_tree.md @@ -725,6 +725,12 @@ TreeNode* deleteNode(TreeNode* root, int key) { } ``` +> 高度平衡的二叉搜索树是二叉搜索树中的一种,它的特殊之处在于```每个树节点的子树高度都不超过1```, 高度平衡保证二叉搜索树具有很多优良性质,能够在```O(logN)```时间复杂度上实现搜索插入和删除。 +> - 红黑树 +> - AVL树 +> - 伸展树 +> - 树堆 + ## 总结 - 掌握二叉树递归与非递归遍历 From 7a7cb26956a4c8586ace2ccb25abd28c2eafe837 Mon Sep 17 00:00:00 2001 From: zxyang Date: Mon, 10 Aug 2020 22:38:23 +0800 Subject: [PATCH 061/110] Processing hash/trie --- data_structure/hash.md | 17 +++++++++++++++++ data_structure/trie.md | 3 ++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/data_structure/hash.md b/data_structure/hash.md index 832e3686..afffb77c 100644 --- a/data_structure/hash.md +++ b/data_structure/hash.md @@ -141,3 +141,20 @@ ReturnType aggregateByKey_hashmap(vector& keys) { } ``` +### [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; +} +``` \ No newline at end of file diff --git a/data_structure/trie.md b/data_structure/trie.md index 0862d392..515637ea 100644 --- a/data_structure/trie.md +++ b/data_structure/trie.md @@ -10,7 +10,7 @@ 节点的值是从根节点开始,与其经过的路径中的字符串按顺序组成。 -前缀树的一个重要的特性是,节点所有的后代都与该节点相关的字符串有着共同的前缀。这就是 ```前缀树``` 名称的由来。 +前缀树的一个重要的特性是,节点所有的后代都与该节点相关的字符串有着共同的前缀,利用公共前缀来降低查询时间开销,这就是 ```前缀树``` 名称的由来。 前缀树有着广泛的应用,例如自动补全,拼写检查等等。 @@ -28,6 +28,7 @@ #define N 26 struct TrieNode { + bool isEnd; //该节点是否是一个串的结束 TrieNode* children[N]; // you might need some extra values according to different cases From 23b0edb0f55f4477507cbc45de8a938cc47cdb23 Mon Sep 17 00:00:00 2001 From: zxyang Date: Sat, 15 Aug 2020 10:26:22 +0800 Subject: [PATCH 062/110] Modify hash --- data_structure/hash.md | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/data_structure/hash.md b/data_structure/hash.md index afffb77c..f7acc495 100644 --- a/data_structure/hash.md +++ b/data_structure/hash.md @@ -157,4 +157,28 @@ bool containsNearbyDuplicate(vector& nums, int k) { } return false; } -``` \ No newline at end of file +``` + +### [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; +} +``` + From b30bd6303303d366f0e36fcc845da5c3a3a4dd3a Mon Sep 17 00:00:00 2001 From: zxyang Date: Sat, 15 Aug 2020 20:36:30 +0800 Subject: [PATCH 063/110] Add hash --- data_structure/hash.md | 78 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/data_structure/hash.md b/data_structure/hash.md index f7acc495..9555dc36 100644 --- a/data_structure/hash.md +++ b/data_structure/hash.md @@ -182,3 +182,81 @@ string dfs(TreeNode* root, vector& result, unordered_map } ``` +### [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; +} +``` From 7557f7c39d0b29d7ad62df97d2bc031914cb4fd1 Mon Sep 17 00:00:00 2001 From: zxyang Date: Sun, 16 Aug 2020 13:04:50 +0800 Subject: [PATCH 064/110] Add trie --- data_structure/trie.md | 107 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) diff --git a/data_structure/trie.md b/data_structure/trie.md index 515637ea..c6b0415a 100644 --- a/data_structure/trie.md +++ b/data_structure/trie.md @@ -59,4 +59,111 @@ struct TrieNode { > 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; + } +}; +``` + +哈希 +```java +public class Trie { + public class TrieNode{ + public int path; + public int end; + public HashMap next; + + public TrieNode(){ + path = 0; + end = 0; + next = new HashMap<>(); + } +} + + private TrieNode root; + public Trie(){ + root = new TrieNode(); + } + + public void insert(String word){ + if(word == null || word.equals("")) return ; + TrieNode node = root; + for(int i = 0; i Date: Sun, 16 Aug 2020 13:14:44 +0800 Subject: [PATCH 065/110] Implement trie with cpp code --- data_structure/trie.md | 87 ++++++++++++++++++++---------------------- 1 file changed, 42 insertions(+), 45 deletions(-) diff --git a/data_structure/trie.md b/data_structure/trie.md index c6b0415a..ca3f13a8 100644 --- a/data_structure/trie.md +++ b/data_structure/trie.md @@ -111,59 +111,56 @@ public: ``` 哈希 -```java -public class Trie { - public class TrieNode{ - public int path; - public int end; - public HashMap next; - - public TrieNode(){ - path = 0; - end = 0; - next = new HashMap<>(); - } -} - - private TrieNode root; - public Trie(){ - root = new TrieNode(); +```cpp +struct TrieNode { + bool isEnd = false; + unordered_map next; + ~TrieNode() { // 析构 + for (auto& [key, value] : next) { + if (value) delete value; + } } +}; - public void insert(String word){ - if(word == null || word.equals("")) return ; - TrieNode node = root; - for(int i = 0; i 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(); } - node = node.next.get(ch); - node.path++; + p = p->next[c]; } - node.end++; + p->isEnd = true; } - - public boolean search(String word){ - if(word == null || word.equals("")) return false; - TrieNode node = root; - for(int i = 0; inext.count(c)) { + return false; + } + p = p->next[c]; } - if(node.end == 0) return false; - return true; + return p->isEnd; } - public boolean startsWith(String word){ - if(word == null || word.equals("")) return false; - TrieNode node = root; - for(int i = 0; inext.count(c)) { + return false; + } + p = p->next[c]; } - return true; + return p != nullptr; } }; + ``` \ No newline at end of file From af17a383aa2645293023eef09d3be96cf69b8c29 Mon Sep 17 00:00:00 2001 From: zxyang Date: Mon, 17 Aug 2020 21:13:52 +0800 Subject: [PATCH 066/110] Add trie --- data_structure/trie.md | 150 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 149 insertions(+), 1 deletion(-) diff --git a/data_structure/trie.md b/data_structure/trie.md index ca3f13a8..780e99d4 100644 --- a/data_structure/trie.md +++ b/data_structure/trie.md @@ -162,5 +162,153 @@ public: 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); + } + } + } -``` \ No newline at end of file + 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 ""; + } + +}; +``` From 54daa1f5f850e62b028004a4dadeeb2028b5a599 Mon Sep 17 00:00:00 2001 From: zxyang Date: Tue, 18 Aug 2020 09:17:56 +0800 Subject: [PATCH 067/110] Impr trie --- data_structure/trie.md | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/data_structure/trie.md b/data_structure/trie.md index 780e99d4..7ec51a90 100644 --- a/data_structure/trie.md +++ b/data_structure/trie.md @@ -29,7 +29,7 @@ struct TrieNode { bool isEnd; //该节点是否是一个串的结束 - TrieNode* children[N]; + TrieNode* next[N]; // you might need some extra values according to different cases }; @@ -45,8 +45,15 @@ struct TrieNode { - 每个节点声明一个HashMap, 键是字符,值则是对应的子节点 ```cpp struct TrieNode { - unordered_map children; - + 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 }; From 7557922e09fd0fd93ded74f2d37ee5d57beee488 Mon Sep 17 00:00:00 2001 From: zxyang Date: Fri, 18 Sep 2020 14:30:05 +0800 Subject: [PATCH 068/110] Add cuda note --- ...26\347\250\213\347\254\224\350\256\260.md" | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 "cuda\347\274\226\347\250\213\347\254\224\350\256\260.md" diff --git "a/cuda\347\274\226\347\250\213\347\254\224\350\256\260.md" "b/cuda\347\274\226\347\250\213\347\254\224\350\256\260.md" new file mode 100644 index 00000000..df3bab23 --- /dev/null +++ "b/cuda\347\274\226\347\250\213\347\254\224\350\256\260.md" @@ -0,0 +1,44 @@ +# cuda高性能编程 + +> 记录下学习cuda并行编程的笔记,主要书籍是《GPU高性能编程》《cuda并行程序设计》,书籍比较短,因此只要一个笔记来记录所有的内容,包括示例程序和文字。 + +## 1. cuda是什么 + + cuda实际上是实现**并行计算**的一种程序形式。当前人们对计算力的需求在急速增长,不管是气象预测、地震预告,还是游戏渲染和自动驾驶,**CPU**的处理速度已经远远达不到人们的要求。但是近年来集成电路元器件中存在的各种严重限制,导致**CPU**的速度提升越来越困难,研究人员和制造商开始寻求其他的方式。 + +- 在2000年前,GPU的主要目标是通过可编程单元为屏幕上的**每个像素计算出对应颜色值**,进行像素着色。输入包括**颜色和纹理坐标**。研究人员发现可以输入其实可以是任意数据。 +- **GPU**有很高的吞吐量,输入可以是任意值,那么可不可以利用来加速其他运算呢?只需要抽象成渲染任务,拿到相应输出就好了。 +- 限制很多,出现**cuda**来进一步抽象,方便程序员们使用。 + +### cuda + +- 架构 + - 统一着色流水线 + - 图形计算+通用计算 +- 应用 + - 医学图像:3d成像,保证快速出图 + - 计算流体动力学 + - 环境科学 + +## 2.第一段代码 + +最经典的“Hello, World!”示例: + +```cpp +#include + +__global__ void kernel(void) { +} + +int main(void) { + kernel<<<1,1>>>(); + printf( "Hello, World!\n "); + return 0; +} +``` + +- \__global__修饰符告诉编译器,kernel函数将被编译为在**设备**运行,而不是**主机** +- kernel空函数的调用带了修饰符<<<1,1>>> + - 设计到主机跟设备之间的交互,上面修饰符就是完成这样的功能 + - + From 392414e8c40872fd5252dd4b14c7de41317695b7 Mon Sep 17 00:00:00 2001 From: zxyang Date: Fri, 18 Sep 2020 17:27:40 +0800 Subject: [PATCH 069/110] Delete cuda.md --- ...26\347\250\213\347\254\224\350\256\260.md" | 44 ------------------- 1 file changed, 44 deletions(-) delete mode 100644 "cuda\347\274\226\347\250\213\347\254\224\350\256\260.md" diff --git "a/cuda\347\274\226\347\250\213\347\254\224\350\256\260.md" "b/cuda\347\274\226\347\250\213\347\254\224\350\256\260.md" deleted file mode 100644 index df3bab23..00000000 --- "a/cuda\347\274\226\347\250\213\347\254\224\350\256\260.md" +++ /dev/null @@ -1,44 +0,0 @@ -# cuda高性能编程 - -> 记录下学习cuda并行编程的笔记,主要书籍是《GPU高性能编程》《cuda并行程序设计》,书籍比较短,因此只要一个笔记来记录所有的内容,包括示例程序和文字。 - -## 1. cuda是什么 - - cuda实际上是实现**并行计算**的一种程序形式。当前人们对计算力的需求在急速增长,不管是气象预测、地震预告,还是游戏渲染和自动驾驶,**CPU**的处理速度已经远远达不到人们的要求。但是近年来集成电路元器件中存在的各种严重限制,导致**CPU**的速度提升越来越困难,研究人员和制造商开始寻求其他的方式。 - -- 在2000年前,GPU的主要目标是通过可编程单元为屏幕上的**每个像素计算出对应颜色值**,进行像素着色。输入包括**颜色和纹理坐标**。研究人员发现可以输入其实可以是任意数据。 -- **GPU**有很高的吞吐量,输入可以是任意值,那么可不可以利用来加速其他运算呢?只需要抽象成渲染任务,拿到相应输出就好了。 -- 限制很多,出现**cuda**来进一步抽象,方便程序员们使用。 - -### cuda - -- 架构 - - 统一着色流水线 - - 图形计算+通用计算 -- 应用 - - 医学图像:3d成像,保证快速出图 - - 计算流体动力学 - - 环境科学 - -## 2.第一段代码 - -最经典的“Hello, World!”示例: - -```cpp -#include - -__global__ void kernel(void) { -} - -int main(void) { - kernel<<<1,1>>>(); - printf( "Hello, World!\n "); - return 0; -} -``` - -- \__global__修饰符告诉编译器,kernel函数将被编译为在**设备**运行,而不是**主机** -- kernel空函数的调用带了修饰符<<<1,1>>> - - 设计到主机跟设备之间的交互,上面修饰符就是完成这样的功能 - - - From c1f06165f2c7527ffbfaabf201e3f719816eed15 Mon Sep 17 00:00:00 2001 From: xyz Date: Sun, 1 Nov 2020 20:58:25 +0800 Subject: [PATCH 070/110] Update binary_tree.md --- data_structure/binary_tree.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data_structure/binary_tree.md b/data_structure/binary_tree.md index 810d582f..50466c95 100644 --- a/data_structure/binary_tree.md +++ b/data_structure/binary_tree.md @@ -61,7 +61,7 @@ vector preorderTraversal(TreeNode* root){ while(!st.empty()){ TreeNode* node = st.top(); st.pop(); - res.push_nack(node->val); + res.push_back(node->val); //注意先入右节点进栈 if(!node->right) st.push(node->right); if(!node->left) st.push(node->left); From 9b7055b0d5b9096d70cd7a2953e3af0c6acd0b9d Mon Sep 17 00:00:00 2001 From: zxyang Date: Tue, 3 Nov 2020 08:35:21 +0800 Subject: [PATCH 071/110] [yzx] Fix bugs --- basic_algorithm/binary_search.md | 2 +- basic_algorithm/sort.md | 5 +++-- puzzles/bayes.md | 30 +++++++++++++++--------------- 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/basic_algorithm/binary_search.md b/basic_algorithm/binary_search.md index 3e51f98a..234ec6ea 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 diff --git a/basic_algorithm/sort.md b/basic_algorithm/sort.md index 169ad837..dfbf8756 100644 --- a/basic_algorithm/sort.md +++ b/basic_algorithm/sort.md @@ -29,10 +29,11 @@ void selectionSort(vector& a){ for(int i = 0; i < n; i++){ int min = i; for(int j = i + 1; j < n; j++){ - if(a[j] < min) + if(a[j] < a[min]) min = j; } - swap(a, i , j); + if(min != i) + swap(a, i, min); } } diff --git a/puzzles/bayes.md b/puzzles/bayes.md index add823af..0153b09d 100644 --- a/puzzles/bayes.md +++ b/puzzles/bayes.md @@ -1,15 +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的**先验概率** - -贝叶斯定理用语言描述就是: -> 后验概率 = (似然性*先验概率)/ 标准化常量 - +# 贝叶斯定理 +## 已知某些条件下,某事件的发生几率 + +> 通常,事件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的**先验概率** + +贝叶斯定理用语言描述就是: +> 后验概率 = (似然性*先验概率)/ 标准化常量 + From 6481fff05b080eb1f19382b3589abb4fae6e41d0 Mon Sep 17 00:00:00 2001 From: zxyang Date: Tue, 17 Nov 2020 08:05:45 +0800 Subject: [PATCH 072/110] Fix sort.md --- basic_algorithm/sort.md | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/basic_algorithm/sort.md b/basic_algorithm/sort.md index dfbf8756..d4f07b02 100644 --- a/basic_algorithm/sort.md +++ b/basic_algorithm/sort.md @@ -45,7 +45,7 @@ void selectionSort(vector& a){ //插入排序 void insertionSort(vector& a){ int n = a.size(); - for(int i = 1; i < n; i ++){ + 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); @@ -65,7 +65,7 @@ void shellSort(vector& a){ int H = 1; while(H < n) H = 3 * H + 1; //壳的序列 - for(int h = H; h >= 1; h = (H - 1) / 3){ + 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){ @@ -85,7 +85,7 @@ void shellSort(vector& a){ //快速排序,快排 void quickSort(vector& a){ - random_shuffle(a); + random_shuffle(a); //保证快排复杂度下限 int n = a.size(); quick_sort(a, 0, n - 1); } @@ -100,13 +100,11 @@ void quick_sort(vector& a, int low, int 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); @@ -115,7 +113,6 @@ static int partition(vector& a, int low, int high){ // 选择k static int select(vector& a, int k){ - int low = 0, high = a.size() - 1; while(low < high){ int pivot = partition(a, low, high); @@ -123,9 +120,7 @@ static int select(vector& a, int k){ else if(pivot > k) high = pivot - 1; else return a[k]; } - return a[k]; - } //重复键值,3-way partition @@ -191,7 +186,6 @@ void buMergeSort(vector& a){ merge(a, aux, low, low + i - 1, min(n - 1, low + 2 * i - 1)); } } - } ``` From f830f120ae994283f8ba4e52ab72a1155044c5a2 Mon Sep 17 00:00:00 2001 From: zxyang Date: Sat, 16 Jan 2021 17:30:35 +0800 Subject: [PATCH 073/110] Add array in data_structure --- data_structure/array.md | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 data_structure/array.md diff --git a/data_structure/array.md b/data_structure/array.md new file mode 100644 index 00000000..aae8302d --- /dev/null +++ b/data_structure/array.md @@ -0,0 +1,39 @@ +# 数组 + +## 基本技能 + +数组是数据结构中最基础的一块,基础考点: + +- 边界处理 +- 数据的插入删除 +- 多维数组 + +## 常见题型 + +### [spiral-matrix](https://leetcode-cn.com/problems/spiral-matrix/) + +> 给定一个包含 *m* x *n* 个元素的矩阵(*m* 行, *n* 列),请按照顺时针螺旋顺序,返回矩阵中的所有元素。 + +```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; +} +``` + From f4061a715aec55fcec4bc6fc2f1560def8bf4977 Mon Sep 17 00:00:00 2001 From: zxyang Date: Sat, 16 Jan 2021 20:24:10 +0800 Subject: [PATCH 074/110] Add ju-zhen-lu-jing --- advanced_algorithm/backtrack.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/advanced_algorithm/backtrack.md b/advanced_algorithm/backtrack.md index cbc5107c..6988b013 100644 --- a/advanced_algorithm/backtrack.md +++ b/advanced_algorithm/backtrack.md @@ -241,6 +241,38 @@ void backtrack(vector& nums, vector track, vector& visited, vect } ``` +### [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/) From bf8d5fdcf91182b4152d5b8d5b308a63d29bc38a Mon Sep 17 00:00:00 2001 From: xyz Date: Thu, 6 Oct 2022 19:39:03 +0800 Subject: [PATCH 075/110] =?UTF-8?q?[yzx]1006:=E4=BA=8C=E5=88=86=E6=9F=A5?= =?UTF-8?q?=E6=89=BE=5Fpython?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TODO.md | 1 + basic_algorithm/binary_search.md | 212 +++++++++++++++++++++++++++++++ 2 files changed, 213 insertions(+) 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/basic_algorithm/binary_search.md b/basic_algorithm/binary_search.md index 234ec6ea..5b42b912 100644 --- a/basic_algorithm/binary_search.md +++ b/basic_algorithm/binary_search.md @@ -19,6 +19,7 @@ > 给定一个  n  个元素有序的(升序)整型数组  nums 和一个目标值  target  ,写一个函数搜索  nums  中的 target,如果目标值存在返回下标,否则返回 -1。 +**C++版本** ```cpp // 二分搜索最常用模板 int search(vector& nums, int target) { @@ -40,6 +41,22 @@ int search(vector& nums, int target) { 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 +``` 大部分二分查找类的题目都可以用这个模板,然后做一点特殊逻辑即可 @@ -72,6 +89,61 @@ int search(vector& nums, int target) { ``` ## 常见题目 +### [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/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/) @@ -80,6 +152,65 @@ int search(vector& nums, int target) { 思路:核心点就是找第一个 target 的索引,和最后一个 target 的索引,所以用两次二分搜索分别找第一次和最后一次的位置 +**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}; @@ -170,6 +301,26 @@ bool searchMatrix(vector>& matrix, int target) { > 假设你有 n 个版本 [1, 2, ..., n],你想找出导致之后所有版本出错的第一个错误的版本。 > 你可以通过调用  bool isBadVersion(version)  接口来判断版本号 version 是否在单元测试中出错。实现一个函数来查找第一个错误的版本。你应该尽量减少对调用 API 的次数。 +**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; @@ -187,7 +338,22 @@ int firstBadVersion(int n) { > 假设按照升序排序的数组在预先未知的某个点上进行了旋转( 例如,数组  [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]) +``` +**C++版本** ```cpp int findMin(vector& nums) { if(nums.empty()) return 0; @@ -236,6 +402,28 @@ int findMin(vector& nums) { > 搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回  -1 。 > 你可以假设数组中不存在重复的元素。 +**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; @@ -264,6 +452,30 @@ int search(vector& nums, int target) { > 面试时,可以直接画图进行辅助说明,空讲很容易让大家都比较蒙圈 +### [寻找峰值](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/) > 假设按照升序排序的数组在预先未知的某个点上进行了旋转。 From 18ed7bb852d9afc60fb6c1135dfee3ae4f69502c Mon Sep 17 00:00:00 2001 From: xyz Date: Thu, 6 Oct 2022 21:51:23 +0800 Subject: [PATCH 076/110] Update binary_search.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 找到k个最近元素 --- basic_algorithm/binary_search.md | 35 ++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/basic_algorithm/binary_search.md b/basic_algorithm/binary_search.md index 5b42b912..dbdd1210 100644 --- a/basic_algorithm/binary_search.md +++ b/basic_algorithm/binary_search.md @@ -245,6 +245,41 @@ int findTarget(vector& nums, int target, bool findLeft){ } ``` +### [找到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 +``` + + ### [search-insert-position](https://leetcode-cn.com/problems/search-insert-position/) From 56bad73daf3444dbf4433d5624a7fddde9c77467 Mon Sep 17 00:00:00 2001 From: xyz Date: Thu, 6 Oct 2022 22:00:45 +0800 Subject: [PATCH 077/110] Update binary_search.md some notes --- basic_algorithm/binary_search.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/basic_algorithm/binary_search.md b/basic_algorithm/binary_search.md index dbdd1210..96e9f146 100644 --- a/basic_algorithm/binary_search.md +++ b/basic_algorithm/binary_search.md @@ -251,8 +251,8 @@ int findTarget(vector& nums, int target, bool findLeft){ 思路有好几种: 1. 找到最小值,然后双指针 -2. 直接找k的子序列 -3. 删去最远的剩下的就是最近的k个子数组 +2. 直接找k成都的子序列 +3. 删去最远的剩下的就是最近的长度为k的子数组 **Python版本** From b1927a736577c743b36bbbb78a370e39f8514faa Mon Sep 17 00:00:00 2001 From: xyz Date: Fri, 7 Oct 2022 20:55:31 +0800 Subject: [PATCH 078/110] Update binary_search.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit K个最近子数组 --- basic_algorithm/binary_search.md | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/basic_algorithm/binary_search.md b/basic_algorithm/binary_search.md index 96e9f146..33b5a3a0 100644 --- a/basic_algorithm/binary_search.md +++ b/basic_algorithm/binary_search.md @@ -251,7 +251,7 @@ int findTarget(vector& nums, int target, bool findLeft){ 思路有好几种: 1. 找到最小值,然后双指针 -2. 直接找k成都的子序列 +2. 直接找k长度的子序列 3. 删去最远的剩下的就是最近的长度为k的子数组 @@ -279,7 +279,27 @@ class Solution: 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/) From 67d78a815c5bfea186b7e84c1c9e22ad2f404915 Mon Sep 17 00:00:00 2001 From: xyz Date: Mon, 10 Oct 2022 09:18:38 +0800 Subject: [PATCH 079/110] Update binary_search.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit lc367 有效完全平方数 --- basic_algorithm/binary_search.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/basic_algorithm/binary_search.md b/basic_algorithm/binary_search.md index 33b5a3a0..6804ccda 100644 --- a/basic_algorithm/binary_search.md +++ b/basic_algorithm/binary_search.md @@ -112,6 +112,29 @@ class Solution: ``` +### [有效的完全平方数](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 +``` + + + ### [猜数字大小](https://leetcode.cn/problems/guess-number-higher-or-lower/) > 猜数字游戏的规则如下: > 每轮游戏,我都会从 1 到 n 随机选择一个数字。 请你猜选出的是哪个数字。 From 9cb5c89cd184624b9a6cbf3c722d25ca9ffcaee2 Mon Sep 17 00:00:00 2001 From: xyz Date: Sat, 15 Oct 2022 10:42:12 +0800 Subject: [PATCH 080/110] Update binary_search.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 快速幂 --- basic_algorithm/binary_search.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/basic_algorithm/binary_search.md b/basic_algorithm/binary_search.md index 6804ccda..062e03a2 100644 --- a/basic_algorithm/binary_search.md +++ b/basic_algorithm/binary_search.md @@ -133,6 +133,27 @@ class Solution: else: return False ``` +### [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/guess-number-higher-or-lower/) From 3eef026809e5434395ff4664c984f92b386604d6 Mon Sep 17 00:00:00 2001 From: xyz Date: Sat, 15 Oct 2022 19:14:41 +0800 Subject: [PATCH 081/110] Update binary_search.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 数组交集 --- basic_algorithm/binary_search.md | 147 +++++++++++++++++++++++++++++-- 1 file changed, 141 insertions(+), 6 deletions(-) diff --git a/basic_algorithm/binary_search.md b/basic_algorithm/binary_search.md index 062e03a2..0eba55ec 100644 --- a/basic_algorithm/binary_search.md +++ b/basic_algorithm/binary_search.md @@ -155,18 +155,119 @@ class Solution: ``` +### [寻找比目标字母大的最小字母](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 + +``` + ### [猜数字大小](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 +> +> - -1:我选出的数字比你猜的数字小 pick < num +> - 1:我选出的数字比你猜的数字大 pick > num +> - 0:我选出的数字和你猜的数字一样。恭喜!你猜对了!pick == num **Python版本** ```python @@ -451,6 +552,21 @@ class Solution: 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]) +``` **C++版本** ```cpp @@ -477,6 +593,25 @@ int findMin(vector& nums) { > 请找出其中最小的元素。(包含重复元素) // 思路:跳过重复元素,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; From 96ba57651a3d89a45e45a8d4788013685ece50c5 Mon Sep 17 00:00:00 2001 From: xyz Date: Sun, 16 Oct 2022 11:58:44 +0800 Subject: [PATCH 082/110] Update binary_search.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Finish 二分查找 --- basic_algorithm/binary_search.md | 46 +++++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/basic_algorithm/binary_search.md b/basic_algorithm/binary_search.md index 0eba55ec..c872fd1a 100644 --- a/basic_algorithm/binary_search.md +++ b/basic_algorithm/binary_search.md @@ -64,7 +64,7 @@ class Solution: ![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,代码更简洁 @@ -255,6 +255,47 @@ def find(num, nums, find_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/) > 猜数字游戏的规则如下: @@ -750,6 +791,9 @@ bool search(vector& nums, int target) { ## 练习题 +- [ ] [x的平方根](https://leetcode.cn/problems/sqrtx/) +- [ ] [有效的完全平方数](https://leetcode.cn/problems/valid-perfect-square/) +- [ ] - [ ] [search-for-range](https://www.lintcode.com/problem/search-for-a-range/description) - [ ] [search-insert-position](https://leetcode-cn.com/problems/search-insert-position/) - [ ] [search-a-2d-matrix](https://leetcode-cn.com/problems/search-a-2d-matrix/) From 891fd56d07947ce0f14d0b290110e07e48fc7515 Mon Sep 17 00:00:00 2001 From: xyz Date: Sun, 16 Oct 2022 12:07:40 +0800 Subject: [PATCH 083/110] Update binary_search.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 整理了下 --- basic_algorithm/binary_search.md | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/basic_algorithm/binary_search.md b/basic_algorithm/binary_search.md index c872fd1a..18599355 100644 --- a/basic_algorithm/binary_search.md +++ b/basic_algorithm/binary_search.md @@ -160,6 +160,8 @@ class Solution: > > 在比较时,字母是依序循环出现的。举个例子: > - 如果目标字母 target = 'z' 并且字符列表为 letters = ['a', 'b'],则答案返回 'a' + + **Python版本** ```python class Solution: @@ -176,7 +178,9 @@ class Solution: ``` ### [两个数组的交集](https://leetcode.cn/problems/intersection-of-two-arrays/) -> 给定两个数组 nums1 和 nums2 ,返回 它们的交集 。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序 。 +> 给定两个数组 nums1 和 nums2 ,返回 它们的交集 。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序。 + + **Python版本** ```python class Solution: @@ -310,6 +314,7 @@ class Solution: > - 1:我选出的数字比你猜的数字大 pick > num > - 0:我选出的数字和你猜的数字一样。恭喜!你猜对了!pick == num + **Python版本** ```python # The guess API is already defined for you. @@ -787,18 +792,26 @@ bool search(vector& nums, int target) { - 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/) From 704d9b3c15b9c04adf05e74827d21305012393ff Mon Sep 17 00:00:00 2001 From: xyz Date: Sun, 16 Oct 2022 19:17:07 +0800 Subject: [PATCH 084/110] Update array.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 数组 --- data_structure/array.md | 84 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/data_structure/array.md b/data_structure/array.md index aae8302d..7c2b326b 100644 --- a/data_structure/array.md +++ b/data_structure/array.md @@ -10,6 +10,90 @@ ## 常见题型 +### [移除元素](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 + ``` + + ### [spiral-matrix](https://leetcode-cn.com/problems/spiral-matrix/) > 给定一个包含 *m* x *n* 个元素的矩阵(*m* 行, *n* 列),请按照顺时针螺旋顺序,返回矩阵中的所有元素。 From e35c8273884be82943e909dd37f83d5ffe31b445 Mon Sep 17 00:00:00 2001 From: xyz Date: Mon, 17 Oct 2022 09:19:04 +0800 Subject: [PATCH 085/110] Update array.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 有序数组 --- data_structure/array.md | 56 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/data_structure/array.md b/data_structure/array.md index 7c2b326b..7f651165 100644 --- a/data_structure/array.md +++ b/data_structure/array.md @@ -93,6 +93,62 @@ class Solution: 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 + +``` + ### [spiral-matrix](https://leetcode-cn.com/problems/spiral-matrix/) From 064ce8fe41f9c2db70c1e34cd73340e02fd000f2 Mon Sep 17 00:00:00 2001 From: xyz Date: Tue, 18 Oct 2022 08:51:37 +0800 Subject: [PATCH 086/110] Update array.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 比较含退格的字符串 --- data_structure/array.md | 50 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/data_structure/array.md b/data_structure/array.md index 7f651165..1e5fa896 100644 --- a/data_structure/array.md +++ b/data_structure/array.md @@ -149,6 +149,56 @@ class Solution: ``` +### [比较含退格的字符串](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 +``` + ### [spiral-matrix](https://leetcode-cn.com/problems/spiral-matrix/) From 88c6db1a2d0868d542265e993399c80016cb0087 Mon Sep 17 00:00:00 2001 From: xyz Date: Tue, 18 Oct 2022 08:54:48 +0800 Subject: [PATCH 087/110] Update array.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 练习题 --- data_structure/array.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/data_structure/array.md b/data_structure/array.md index 1e5fa896..96e32b99 100644 --- a/data_structure/array.md +++ b/data_structure/array.md @@ -227,3 +227,10 @@ vector spiralOrder(vector>& 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/) \ No newline at end of file From 6d9c0f158d5b3e849120e3c4f8918e7d2005f973 Mon Sep 17 00:00:00 2001 From: xyz Date: Wed, 19 Oct 2022 09:24:36 +0800 Subject: [PATCH 088/110] Update array.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 长度最小子数组:双指针 --- data_structure/array.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/data_structure/array.md b/data_structure/array.md index 96e32b99..98d0e6e3 100644 --- a/data_structure/array.md +++ b/data_structure/array.md @@ -199,11 +199,41 @@ class Solution: 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 +``` + + + ### [spiral-matrix](https://leetcode-cn.com/problems/spiral-matrix/) > 给定一个包含 *m* x *n* 个元素的矩阵(*m* 行, *n* 列),请按照顺时针螺旋顺序,返回矩阵中的所有元素。 +**C++版本** ```cpp vector spiralOrder(vector>& matrix) { int rows = matrix.size(); From 818b451de4f842619eb13f21b6740a916c43e564 Mon Sep 17 00:00:00 2001 From: xyz Date: Fri, 21 Oct 2022 09:13:35 +0800 Subject: [PATCH 089/110] Update array.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 水果成篮,长度最大子数组 --- data_structure/array.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/data_structure/array.md b/data_structure/array.md index 98d0e6e3..556d8ef5 100644 --- a/data_structure/array.md +++ b/data_structure/array.md @@ -226,7 +226,41 @@ class Solution: 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 +``` ### [spiral-matrix](https://leetcode-cn.com/problems/spiral-matrix/) From 819411eb8e877860074fefc44634260d385033ed Mon Sep 17 00:00:00 2001 From: xyz Date: Tue, 25 Oct 2022 09:18:25 +0800 Subject: [PATCH 090/110] Update array.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 最小覆盖子串 --- data_structure/array.md | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/data_structure/array.md b/data_structure/array.md index 556d8ef5..41e055cd 100644 --- a/data_structure/array.md +++ b/data_structure/array.md @@ -262,6 +262,46 @@ class Solution: 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 "" +``` + + + + ### [spiral-matrix](https://leetcode-cn.com/problems/spiral-matrix/) From 7499e15a72122a73d4c18d5dc1ff26cbe956889e Mon Sep 17 00:00:00 2001 From: xyz Date: Sat, 29 Oct 2022 15:18:03 +0800 Subject: [PATCH 091/110] Update array.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 最小覆盖子串 --- data_structure/array.md | 41 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/data_structure/array.md b/data_structure/array.md index 41e055cd..2afbfb82 100644 --- a/data_structure/array.md +++ b/data_structure/array.md @@ -262,7 +262,7 @@ class Solution: return res ``` -###[最小覆盖子串](https://leetcode.cn/problems/minimum-window-substring/) +### [最小覆盖子串](https://leetcode.cn/problems/minimum-window-substring/) > 给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 "" 。 > > **注意**: @@ -271,7 +271,7 @@ class Solution: 思路一:利用Counter计数器,比较方便但耗时非常多 -**Python版本** +**Python版本一** ```python from collections import Counter class Solution: @@ -299,7 +299,39 @@ class Solution: 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 "" +``` @@ -337,4 +369,7 @@ vector spiralOrder(vector>& matrix) { - [ ] [移动零](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/) \ No newline at end of file +- [ ] [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/) \ No newline at end of file From bc9dcb872554fcd23489b879a36d3cf9fc65e9bb Mon Sep 17 00:00:00 2001 From: xyz Date: Mon, 14 Nov 2022 08:59:32 +0800 Subject: [PATCH 092/110] Update array.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 旋转矩阵 --- data_structure/array.md | 59 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/data_structure/array.md b/data_structure/array.md index 2afbfb82..b84f3cb0 100644 --- a/data_structure/array.md +++ b/data_structure/array.md @@ -363,6 +363,65 @@ vector spiralOrder(vector>& matrix) { } ``` +**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 +``` + + + ## 练习题 - [ ] [移除元素](https://leetcode.cn/problems/remove-element/) - [ ] [删除有序数组中的重复项](https://leetcode.cn/problems/remove-duplicates-from-sorted-array/) From 5f8be9d061968946ce1b97d71bd44e505efa4d48 Mon Sep 17 00:00:00 2001 From: xyz Date: Mon, 14 Nov 2022 09:25:04 +0800 Subject: [PATCH 093/110] Update binary_search.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 矩阵中二分查找 --- basic_algorithm/binary_search.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/basic_algorithm/binary_search.md b/basic_algorithm/binary_search.md index 18599355..c416c9b1 100644 --- a/basic_algorithm/binary_search.md +++ b/basic_algorithm/binary_search.md @@ -521,6 +521,8 @@ int searchInsert(vector& nums, int target) { > - 每行中的整数从左到右按升序排列。 > - 每行的第一个整数大于前一行的最后一个整数。 + +**C++版本** ```cpp bool searchMatrix(vector>& matrix, int target) { if(matrix.empty() || matrix[0].empty()) return false; @@ -542,6 +544,25 @@ bool searchMatrix(vector>& matrix, int target) { } ``` +**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],你想找出导致之后所有版本出错的第一个错误的版本。 From be8e9bbb86ea712051c5b2082df6b98fea67e049 Mon Sep 17 00:00:00 2001 From: xyz Date: Tue, 15 Nov 2022 08:02:39 +0800 Subject: [PATCH 094/110] Update array.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 螺旋矩阵II --- data_structure/array.md | 53 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/data_structure/array.md b/data_structure/array.md index b84f3cb0..65a9b228 100644 --- a/data_structure/array.md +++ b/data_structure/array.md @@ -420,6 +420,55 @@ class Solution: 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 +``` + + ## 练习题 @@ -431,4 +480,6 @@ class Solution: - [ ] [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/) \ No newline at end of file +- [ ] [最小覆盖子串](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 From 3e6a90ba19f7f6c2cbe4202b7b3d794b26a36a53 Mon Sep 17 00:00:00 2001 From: xyz Date: Wed, 16 Nov 2022 08:37:23 +0800 Subject: [PATCH 095/110] Update linked_list.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 删除链表节点 --- data_structure/linked_list.md | 60 +++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/data_structure/linked_list.md b/data_structure/linked_list.md index 7a51dd92..1d13b7f6 100644 --- a/data_structure/linked_list.md +++ b/data_structure/linked_list.md @@ -16,10 +16,70 @@ ## 常见题型 +### [移除链表元素](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/) > 给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。 +**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; From 9220f9113dd830c0c0b6c5cc28343a371f478997 Mon Sep 17 00:00:00 2001 From: xyz Date: Wed, 16 Nov 2022 09:39:42 +0800 Subject: [PATCH 096/110] Update linked_list.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 删除链表中的重复元素II --- data_structure/linked_list.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/data_structure/linked_list.md b/data_structure/linked_list.md index 1d13b7f6..b5689f3a 100644 --- a/data_structure/linked_list.md +++ b/data_structure/linked_list.md @@ -105,6 +105,33 @@ ListNode* deleteDuplicates(ListNode* head) { 思路:链表头结点可能被删除,所以用 dummy node 辅助删除 +**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); From 6b3eaa6eba56b57deb785f8e40d3c7e70bc62e83 Mon Sep 17 00:00:00 2001 From: xyz Date: Tue, 27 Dec 2022 19:15:36 +0800 Subject: [PATCH 097/110] Update linked_list.md --- data_structure/linked_list.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/data_structure/linked_list.md b/data_structure/linked_list.md index b5689f3a..1acf51b9 100644 --- a/data_structure/linked_list.md +++ b/data_structure/linked_list.md @@ -160,6 +160,23 @@ ListNode* deleteDuplicates(ListNode* head) { 思路:用一个 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; From d2d876119fcd7e9b42f0efb4c1cc507a43b9ac1b Mon Sep 17 00:00:00 2001 From: xyz Date: Tue, 3 Jan 2023 11:12:50 +0800 Subject: [PATCH 098/110] Update linked_list.md --- data_structure/linked_list.md | 254 +++++++++++++++++++++++++++++++++- 1 file changed, 253 insertions(+), 1 deletion(-) diff --git a/data_structure/linked_list.md b/data_structure/linked_list.md index 1acf51b9..a454d92f 100644 --- a/data_structure/linked_list.md +++ b/data_structure/linked_list.md @@ -197,6 +197,186 @@ ListNode* reverseList(ListNode* head) { } ``` +### [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*  的链表。请使用一趟扫描完成反转。 @@ -235,6 +415,33 @@ ListNode* reverseBetween(ListNode* head, int m, int n) { } ``` +```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/) > 将两个升序链表合并为一个新的升序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 @@ -273,6 +480,31 @@ ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) { } ``` +```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*  的节点之前。 @@ -551,11 +783,31 @@ ListNode *detectCycle(ListNode *head) { } ``` -这两种方式不同点在于,**一般用 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/) > 请判断一个链表是否为回文链表。 From a8b23325593850b6b23f27ae76172553bec0f8e9 Mon Sep 17 00:00:00 2001 From: xyz Date: Tue, 3 Jan 2023 18:24:33 +0800 Subject: [PATCH 099/110] Update hash.md --- data_structure/hash.md | 181 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 181 insertions(+) diff --git a/data_structure/hash.md b/data_structure/hash.md index 9555dc36..d8ce70ee 100644 --- a/data_structure/hash.md +++ b/data_structure/hash.md @@ -141,6 +141,187 @@ ReturnType aggregateByKey_hashmap(vector& keys) { } ``` +### [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 +``` + +### [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 + + + + +### [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 From f9269daa6e0ccf5c1e9cffa3b7f0bcb35bb1ea63 Mon Sep 17 00:00:00 2001 From: xyz Date: Wed, 4 Jan 2023 15:38:23 +0800 Subject: [PATCH 100/110] Update hash.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 2sum,3sum,4sum,4sum-2 --- data_structure/hash.md | 103 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 102 insertions(+), 1 deletion(-) diff --git a/data_structure/hash.md b/data_structure/hash.md index d8ce70ee..44856869 100644 --- a/data_structure/hash.md +++ b/data_structure/hash.md @@ -254,12 +254,113 @@ class Solution: 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/) @@ -317,7 +418,7 @@ class Solution: if k not in m_d or m_d[k] < v: return False return True - ``` +``` From 5e6fd68a33f2fd74c997fe24abce80be1ac919c2 Mon Sep 17 00:00:00 2001 From: xyz Date: Wed, 4 Jan 2023 17:58:49 +0800 Subject: [PATCH 101/110] string + double pointer --- basic_algorithm/double_pointer.md | 82 ++++++++++++++++++++++++++++++ data_structure/string.md | 83 +++++++++++++++++++++++++++++++ 2 files changed, 165 insertions(+) create mode 100644 basic_algorithm/double_pointer.md create mode 100644 data_structure/string.md 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/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] +``` + From 7b2193029f628971d43449b3e94d84226450e10a Mon Sep 17 00:00:00 2001 From: xyz Date: Wed, 4 Jan 2023 21:21:25 +0800 Subject: [PATCH 102/110] Update stack_queue.md --- data_structure/stack_queue.md | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/data_structure/stack_queue.md b/data_structure/stack_queue.md index a6d7abce..9b5e6e5e 100644 --- a/data_structure/stack_queue.md +++ b/data_structure/stack_queue.md @@ -12,7 +12,22 @@ ## 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(双端队列)来模拟一个栈,只要是标准的栈操作即可。 + + + + +### [min-stack](https://leetcode-cn.com/problems/min-stack/) > 设计一个支持 push,pop,top 操作,并能在常数时间内检索到最小元素的栈。 @@ -61,7 +76,7 @@ public: */ ``` -[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 @@ -88,7 +103,7 @@ int evalRPN(vector& tokens) { } ``` -[decode-string](https://leetcode-cn.com/problems/decode-string/) +### [decode-string](https://leetcode-cn.com/problems/decode-string/) > 给定一个经过编码的字符串,返回它解码后的字符串。 > s = "3[a]2[bc]", 返回 "aaabcbc". @@ -164,7 +179,7 @@ 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/) > 给定一个二叉树,返回它的*中序*遍历。 @@ -194,7 +209,7 @@ vector inorderTraversal(TreeNode* root) { } ``` -[clone-graph](https://leetcode-cn.com/problems/clone-graph/) +### [clone-graph](https://leetcode-cn.com/problems/clone-graph/) > 给你无向连通图中一个节点的引用,请你返回该图的深拷贝(克隆)。 @@ -221,7 +236,7 @@ Node* clone(Node* node, unordered_map& visited){ } ``` -[number-of-islands](https://leetcode-cn.com/problems/number-of-islands/) +### [number-of-islands](https://leetcode-cn.com/problems/number-of-islands/) > 给定一个由  '1'(陆地)和 '0'(水)组成的的二维网格,计算岛屿的数量。一个岛被水包围,并且它是通过水平方向或垂直方向上相邻的陆地连接而成的。你可以假设网格的四个边均被水包围。 @@ -267,7 +282,7 @@ for(int i = 0; i < nums.size(); 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 。 > 求在该柱状图中,能够勾勒出来的矩形的最大面积。 @@ -309,7 +324,7 @@ int largestRectangleArea(vector& heights) { 常用于 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/) > 使用栈实现队列 @@ -396,7 +411,7 @@ vector> levelOrder(TreeNode* root){ } ``` -[01-matrix](https://leetcode-cn.com/problems/01-matrix/) +### [01-matrix](https://leetcode-cn.com/problems/01-matrix/) > 给定一个由 0 和 1 组成的矩阵,找出每个元素到最近的 0 的距离。 > 两个相邻元素间的距离为 1 From 771983c39c77e2c2f0c6cf92c7af16fba589847b Mon Sep 17 00:00:00 2001 From: xyz Date: Sun, 8 Jan 2023 10:55:37 +0800 Subject: [PATCH 103/110] Update stack_queue.md --- data_structure/stack_queue.md | 185 +++++++++++++++++++++++++++++++++- 1 file changed, 184 insertions(+), 1 deletion(-) diff --git a/data_structure/stack_queue.md b/data_structure/stack_queue.md index 9b5e6e5e..84162d30 100644 --- a/data_structure/stack_queue.md +++ b/data_structure/stack_queue.md @@ -12,7 +12,7 @@ ## Stack 栈 -### [/implement-queue-using-stacks](https://leetcode.cn/problems/implement-queue-using-stacks) +### [implement-queue-using-stacks](https://leetcode.cn/problems/implement-queue-using-stacks) > 请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(push、pop、peek、empty): > 实现 MyQueue 类: @@ -23,8 +23,154 @@ > 说明: > 你 只能 使用标准的栈操作 —— 也就是只有 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() +``` + +### [/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() + + 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 + + def top(self) -> int: + if self.empty(): + return None + else: + return self.deq[-1] + + 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() +``` + +用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/) @@ -103,6 +249,43 @@ int evalRPN(vector& tokens) { } ``` +```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]() + + + ### [decode-string](https://leetcode-cn.com/problems/decode-string/) > 给定一个经过编码的字符串,返回它解码后的字符串。 From eee966202bcd134c5ad001e51b3aa2710952a741 Mon Sep 17 00:00:00 2001 From: xyz Date: Sun, 8 Jan 2023 16:41:02 +0800 Subject: [PATCH 104/110] Update stack_queue.md --- data_structure/stack_queue.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/data_structure/stack_queue.md b/data_structure/stack_queue.md index 84162d30..37f430c4 100644 --- a/data_structure/stack_queue.md +++ b/data_structure/stack_queue.md @@ -282,7 +282,14 @@ class Solution: return int(stack.pop()) ``` -### [sliding-window-maximum]() +### [sliding-window-maximum](https://leetcode.cn/problems/sliding-window-maximum/) +> 给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。 +> 返回 滑动窗口中的最大值 。 +思路:这道题得想到一个数据结构,当滑动窗口一进一出,能够自动把最大值拱上来,只有最大堆了 +```python + + +``` From 4a4d26ef7c2a36fecbaf52393d728e30c6e9b853 Mon Sep 17 00:00:00 2001 From: xyz Date: Tue, 10 Jan 2023 09:01:06 +0800 Subject: [PATCH 105/110] Update stack_queue.md --- data_structure/stack_queue.md | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/data_structure/stack_queue.md b/data_structure/stack_queue.md index 37f430c4..3c8aada4 100644 --- a/data_structure/stack_queue.md +++ b/data_structure/stack_queue.md @@ -287,10 +287,24 @@ class Solution: > 返回 滑动窗口中的最大值 。 思路:这道题得想到一个数据结构,当滑动窗口一进一出,能够自动把最大值拱上来,只有最大堆了 ```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 ``` +或者考虑使用双边队列 + + ### [decode-string](https://leetcode-cn.com/problems/decode-string/) From 7e21a50a017b95da9d6984ff086eade95fbe305a Mon Sep 17 00:00:00 2001 From: xyz Date: Tue, 10 Jan 2023 09:25:10 +0800 Subject: [PATCH 106/110] Update stack_queue.md --- data_structure/stack_queue.md | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/data_structure/stack_queue.md b/data_structure/stack_queue.md index 3c8aada4..aaf19d8d 100644 --- a/data_structure/stack_queue.md +++ b/data_structure/stack_queue.md @@ -302,7 +302,35 @@ class Solution: ``` -或者考虑使用双边队列 +或者考虑构建一个单调队列,自定义其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 + +``` From 7b84ef6517b82c133a0bd6ac0e071a221f1a59d5 Mon Sep 17 00:00:00 2001 From: xyz Date: Sat, 14 Jan 2023 12:12:27 +0800 Subject: [PATCH 107/110] Update stack_queue.md --- data_structure/stack_queue.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/data_structure/stack_queue.md b/data_structure/stack_queue.md index aaf19d8d..d7b67c7a 100644 --- a/data_structure/stack_queue.md +++ b/data_structure/stack_queue.md @@ -332,6 +332,29 @@ class Solution: ``` +### [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]] +``` + From f3ee7fd663d334d76876d1cd385cc79bd8164f39 Mon Sep 17 00:00:00 2001 From: xyz Date: Sat, 14 Jan 2023 16:59:01 +0800 Subject: [PATCH 108/110] Update stack_queue.md --- data_structure/stack_queue.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data_structure/stack_queue.md b/data_structure/stack_queue.md index d7b67c7a..825f896c 100644 --- a/data_structure/stack_queue.md +++ b/data_structure/stack_queue.md @@ -333,7 +333,7 @@ class Solution: ``` ### [top-k-frequent-elements](https://leetcode.cn/problems/top-k-frequent-elements/) -> 给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。 +> 给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 **任意顺序** 返回答案。 思路:top-k经典题目,利用最小堆或者最大堆, python里面只有最小堆 From 73ee7b85a9c9c0ee884f0e15d1539d73efd4d2d0 Mon Sep 17 00:00:00 2001 From: xyz Date: Tue, 17 Jan 2023 09:24:38 +0800 Subject: [PATCH 109/110] Update binary_tree.md --- data_structure/binary_tree.md | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/data_structure/binary_tree.md b/data_structure/binary_tree.md index 50466c95..45da2f84 100644 --- a/data_structure/binary_tree.md +++ b/data_structure/binary_tree.md @@ -15,6 +15,14 @@ #### TreeNode 定义 +```python +class TreeNode: + def __init__(self, val=0, left=None, right=None): + self.val = val + self.left = left + self.right = right +``` + ```cpp struct TreeNode{ int val; @@ -26,17 +34,35 @@ struct TreeNode{ ``` #### 前序递归 +```python +# Definition for a binary tree node. +# class TreeNode: +# def __init__(self, val=0, left=None, right=None): +# self.val = val +# self.left = left +# self.right = right +class Solution: + def preOrderRecursion(self, root, res): + if root is None: return + res.append(root.val) #调整append位置,就是前/中/后序遍历的差别 + self.preOrderRecursion(root.left, res) + self.preOrderRecursion(root.right, res) + def preorderTraversal(self, root: Optional[TreeNode]) -> List[int]: + res = list() + self.preOrderRecursion(root, res) + return res +``` ```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){ - if(root == nullptr) return {}; vector res; @@ -46,7 +72,11 @@ vector preOrderTraversal(TreeNode* root){ } ``` + #### 前序非递归 +```python + +``` ```cpp // 通过非递归遍历: 用栈进行辅助 From 7af99f04ac03141ee9f7d99451c604645fdb49c6 Mon Sep 17 00:00:00 2001 From: xyz Date: Mon, 20 Feb 2023 07:47:56 +0800 Subject: [PATCH 110/110] Update binary_tree.md --- data_structure/binary_tree.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/data_structure/binary_tree.md b/data_structure/binary_tree.md index 45da2f84..0004c70b 100644 --- a/data_structure/binary_tree.md +++ b/data_structure/binary_tree.md @@ -101,6 +101,19 @@ vector preorderTraversal(TreeNode* root){ } ``` +#### 中序递归 +```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 +``` #### 中序非递归 ```cpp @@ -130,6 +143,22 @@ vector inorderTraversal(TreeNode* root){ } ``` + +#### 后续递归 +```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 + +``` + #### 后序非递归 ```cpp