diff --git "a/LeetCode/344円272円214円345円210円206円346円237円245円346円211円276円.md" "b/LeetCode/344円272円214円345円210円206円346円237円245円346円211円276円.md" index 8ed14a1..dee21b0 100644 --- "a/LeetCode/344円272円214円345円210円206円346円237円245円346円211円276円.md" +++ "b/LeetCode/344円272円214円345円210円206円346円237円245円346円211円276円.md" @@ -7,14 +7,46 @@ * [搜索旋转排序数组II](#搜索旋转排序数组II) (`medium` `二分`) * [在排序数组中查找元素的第一个和最后一个位置](#在排序数组中查找元素的第一个和最后一个位置) (`medium` `二分`) * [寻找两个有序数组的中位数](#寻找两个有序数组的中位数) (`hard` `二分` `分治`) + * [H指数II](#H指数II) (`medium` `二分`) # 二分查找总结 +## H指数II + +[LeetCode中文](https://leetcode.cn/problems/h-index-ii/description/) + +题解:二分查找,关键是找到二分判断的条件,参考如下题解的思路,二分的代码不用参考 + +https://leetcode.cn/problems/h-index-ii/solutions/2504326/tu-jie-yi-tu-zhang-wo-er-fen-da-an-si-ch-d15k + +```c++ +class Solution { +public: + int hIndex(vector& citations) { + int len = citations.size(); + int l = 0, r = len; + while (l < r) { + // +1处理,倾向于偏右计算m值,防止死循环 + int m = (l + r + 1) / 2; + if (citations[len - m]>= m) { + l = m; + } else { + r = m - 1; + } + } + return l; + } +}; +``` + + ## 博客总结 -参考 [二分搜索法总结](#http://www.cnblogs.com/grandyang/p/6854825.html) +[二分搜索法总结](http://www.cnblogs.com/grandyang/p/6854825.html) + +[二分查找边界问题总结](https://leetcode.cn/problems/find-first-and-last-position-of-element-in-sorted-array/solution/tu-jie-er-fen-zui-qing-xi-yi-dong-de-jia-ddvc/) ## x的平方根 @@ -108,10 +140,10 @@ class Solution: 首先判断数组是否已经旋转,如果`nums[0] < num[len-1]`那么数组没有旋转,直接返回`nums[0]`;否则,数组旋转。 -二分查找:定义两个指针l和r分别指向数组两端,取`m = (r + l)/2`,找到中间那个数`nums[m]`和`nums[l]`和`nums[r]`比较: +二分查找:定义两个指针l和r分别指向数组两端,取`m = (r + l)/2`,找到中间那个数`nums[m]`和`nums[r]`比较: -1. 如果`nums[m] < nums[l]`,则在右半段搜索; -2. 如果`nums[m]> nums[l]`,则在左半段搜索; +1. 如果`nums[m] < nums[r]`,则在右半段搜索; +2. 如果`nums[m]> nums[r]`,则在左半段搜索; 直到l和r相邻,返回`nums[r]` @@ -122,23 +154,21 @@ class Solution: class Solution { public: int findMin(vector& nums) { - int len = nums.size(); - if(len == 0) return -1; - int l = 0,r = len-1; - if(nums[l] < nums[r]) return nums[l]; - while(nums[l]> nums[r]) - { - if(r - l == 1) - { - return nums[r]; - } - - int m = l + (r - l)/2; - if(nums[m]> nums[r]) l = m; - else r = m; + if (nums.empty()) throw "nums is empty"; + int len = nums.size(); + if (nums[0] < nums[len - 1]) { + return nums.front(); + } + int l = 0, r = len - 1; + while (l < r) { + int m = (l + r) / 2; + if (nums[m]> nums[r]) { + l = m + 1; + } else { + r = m; } - - return nums[0]; + } + return nums[r]; } }; ``` @@ -180,7 +210,7 @@ public: ### 解答 -思路和 [寻找旋转排序数组中的最小值](#寻找旋转排序数组中的最小值) 一样,但是由于数组存在重复元素,会影响二分查找的效果,要特殊处理这种情况,当`nums[m] == num[l] && nums[m] == nums[r]`时,二分查找退化为遍历数组查找。 +思路和 [寻找旋转排序数组中的最小值](#寻找旋转排序数组中的最小值) 一样,但是由于数组存在重复元素,会影响二分查找的效果,要特殊处理这种情况,当`nums[m] == num[r]`时,r左移直到`nums[m] != nums[r]` - 时间复杂度:O(*log n*)~O(*n*) - 空间复杂度:O(1) @@ -188,40 +218,24 @@ public: ```c++ class Solution { public: - int normalSearch(vector& nums) - { - int len = nums.size(); - int res = nums[0]; - bool flag = false; - for(int i=1;i& nums) { - int len = nums.size(); - if(len == 0) return -1; - int l = 0,r = len-1; - if(nums[l] < nums[r]) return nums[l]; - while(l < r) - { - if(r - l == 1) return nums[r]; - - int m = l + (r - l)/2; - if(nums[m] == nums[l] && nums[m] == nums[r]) - { - return normalSearch(nums); - } - if(nums[m]> nums[r]) l = m; - else r = m; + if (nums.empty()) throw "nums is empty"; + int len = nums.size(); + if (nums[0] < nums[len - 1]) { + return nums.front(); + } + int l = 0, r = len - 1; + while (l < r) { + int m = (l + r) / 2; + if (nums[m]> nums[r]) { + l = m + 1; + } else if (nums[m] < nums[r]) { + r = m; + } else { + --r; } - - return nums[0]; + } + return nums[r]; } }; ``` @@ -340,30 +354,28 @@ public: class Solution { public: int search(vector& nums, int target) { - int len = nums.size(); - int l = 0,r = len-1; - while(l <= r) - { - int m = (r + l)/2; - if(nums[m] == target) - return m; - else if(nums[m]> nums[r]) - { - if(target>= nums[l] && target < nums[m]) - r = m-1; - else - l = m+1; - } - else if(nums[m] <= nums[r]) - { - if(target> nums[m] && target <= nums[r]) - l = m+1; - else - r = m-1; - } + if (nums.empty()) return -1; + int l = 0, r = nums.size() - 1; + while (l < r) { + int m = (l + r) / 2; + if (target == nums[m]) { + return m; } - - return -1; + if (nums[m]> nums[r]) { + if (target>= nums[l] && target <= nums[m]) { + r = m; + } else { + l = m + 1; + } + } else { + if (target> nums[m] && target <= nums[r]) { + l = m + 1; + } else { + r = m; + } + } + } + return target == nums[r] ? r : -1; } }; ``` @@ -412,30 +424,27 @@ public: class Solution { public: bool search(vector& nums, int target) { - int len = nums.size(); - int l = 0,r = len-1; - while(l <= r) - { - int m = (r + l)/2; - if(nums[m] == target) return true; - else if(nums[m]> nums[r])//nums[m]位于左边有序段 - { - if(target>= nums[l] && target < nums[m]) - r = m-1; - else - l = m+1; - } - else if(nums[m] < nums[r])//nums[m]位于右边有序段 - { - if(target> nums[m] && target <= nums[r]) - l = m+1; - else - r = m-1; - } - else --r; + if (nums.empty()) return false; + int l = 0, r = nums.size() - 1; + while (l < r) { + int m = (l + r) / 2; + if (nums[m]> nums[r]) { + if (target>= nums[l] && target <= nums[m]) { + r = m; + } else { + l = m + 1; + } + } else if (nums[m] < nums[r]) { + if (target> nums[m] && target <= nums[r]) { + l = m + 1; + } else { + r = m; + } + } else { + --r; } - - return false; + } + return nums[r] == target; } }; ``` @@ -664,7 +673,7 @@ public: #### 方法2:二分查找 -[参见博客](http://www.cnblogs.com/grandyang/p/4465932.html) +[参见解法三](https://leetcode.cn/problems/median-of-two-sorted-arrays/solutions/8999/xiang-xi-tong-su-de-si-lu-fen-xi-duo-jie-fa-by-w-2/) - 时间复杂度O(*log(m+n)*) - 空间复杂度O(1) @@ -672,25 +681,23 @@ public: ```c++ class Solution { public: - double recursionFind(const vector& nums1,int i,const vector& nums2,int j,int k) - { - if(i>= nums1.size()) return nums2[j + k - 1]; - if(j>= nums2.size()) return nums1[i + k - 1]; - if(k == 1) return min(nums1[i],nums2[j]); - - int val1 = ( i + k/2 - 1 < nums1.size() ) ? nums1[i + k/2 - 1] : INT_MAX; - int val2 = ( j + k/2 - 1 < nums2.size() ) ? nums2[j + k/2 - 1] : INT_MAX; - if(val1 < val2) - return recursionFind(nums1,i + k/2,nums2,j,k - k/2); - else - return recursionFind(nums1,i,nums2,j + k/2,k - k/2); + int recursion(const vector& nums1, const vector& nums2, int k, int start1, int start2) { + int len1 = nums1.size(), len2 = nums2.size(); + if (start1>= len1) return nums2[start2 + k - 1]; + if (start2>= len2) return nums1[start1 + k - 1]; + if (k == 1) return min(nums1[start1], nums2[start2]); + int n1 = min(start1 + k / 2 - 1, len1 - 1); + int n2 = min(start2 + k / 2 - 1, len2 - 1); + if (nums1[n1] < nums2[n2]) { + return recursion(nums1, nums2, k - (n1 - start1 + 1), n1 + 1, start2); + } else { + return recursion(nums1, nums2, k - (n2 - start2 + 1), start1, n2 + 1); + } } - double findMedianSortedArrays(vector& nums1, vector& nums2) { - int len1 = nums1.size(),len2 = nums2.size(); - int k1 = (len1 + len2 + 1)/2,k2 = (len1 + len2 + 2)/2; - - return (recursionFind(nums1,0,nums2,0,k1) + recursionFind(nums1,0,nums2,0,k2)) / 2.0; + int len1 = nums1.size(), len2 = nums2.size(); + int k1 = (len1 + len2 + 1) / 2, k2 = (len1 + len2 + 2) / 2; + return double(recursion(nums1, nums2, k1, 0, 0) + recursion(nums1, nums2, k2, 0, 0)) / 2.0; } }; ``` diff --git "a/LeetCode/344円272円214円345円217円211円346円240円221円.md" "b/LeetCode/344円272円214円345円217円211円346円240円221円.md" index 80820cd..4bcecf7 100644 --- "a/LeetCode/344円272円214円345円217円211円346円240円221円.md" +++ "b/LeetCode/344円272円214円345円217円211円346円240円221円.md" @@ -17,6 +17,7 @@ * [求根到叶子节点数字之和](#求根到叶子节点数字之和) (`medium` `路径`) * [路径总和II](#路径总和II) (`medium` `路径`) * [从前序与中序遍历序列构造二叉树](#从前序与中序遍历序列构造二叉树) (`medium` `分治`) + * [二叉树最大宽度](#二叉树最大宽度) (`medium` `dfs` `bfs`) * **中序遍历** * [二叉树的中序遍历](#二叉树的中序遍历) (`medium`) * **后序遍历** @@ -24,14 +25,114 @@ * [平衡二叉树](#平衡二叉树) (`easy`) * [具有所有最深结点的最小子树](#具有所有最深结点的最小子树) (`medium`) * [二叉树中的最大路径和](#二叉树中的最大路径和) (`hard` `动态规划`) - * [二叉树的后序遍历](#二叉树的后序遍历)(`hard`) + * [二叉树的后序遍历](#二叉树的后序遍历)(`easy`) * **其他** * [对称二叉树](#对称二叉树) (`easy`) * [二叉树的序列化与反序列化](#二叉树的序列化与反序列化) (`hard`) + * [监控二叉树](#监控二叉树) (`hard` `贪心`) # 二叉树总结 + +## 二叉树最大宽度 + +[LeetCode中文](https://leetcode.cn/problems/maximum-width-of-binary-tree/description/) + +题解:https://leetcode.cn/problems/maximum-width-of-binary-tree/solutions/1779284/by-muse-77-7hwf/ + +bfs和dfs两种解法,需要优化数值过大超出范围的问题 + +```c++ +/** + * Definition for a binary tree node. + * struct TreeNode { + * int val; + * TreeNode *left; + * TreeNode *right; + * TreeNode() : val(0), left(nullptr), right(nullptr) {} + * TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} + * TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {} + * }; + */ +class Solution { +public: + void dfs(TreeNode* p, int level, long long idx, int& res) { + if (mp.find(level) == mp.end()) { + mp[level] = idx; + } + res = max(res, int(idx - mp[level] + 1)); + idx = idx - mp[level] + 1; + if (p->left) { + dfs(p->left, level + 1, idx * 2, res); + } + if (p->right) { + dfs(p->right, level + 1, idx * 2 + 1, res); + } + } + int widthOfBinaryTree(TreeNode* root) { + int res = 0; + dfs(root, 1, 1, res); + return res; + } + +private: + unordered_map mp; +}; +``` + + +## 监控二叉树 + +[LeetCode中文](https://leetcode.cn/problems/binary-tree-cameras/description/) + +题解:贪心+后序遍历 + +https://leetcode.cn/problems/binary-tree-cameras/solutions/423212/968-jian-kong-er-cha-shu-di-gui-shang-de-zhuang-ta + +```c++ +/** + * Definition for a binary tree node. + * struct TreeNode { + * int val; + * TreeNode *left; + * TreeNode *right; + * TreeNode() : val(0), left(nullptr), right(nullptr) {} + * TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} + * TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {} + * }; + */ +class Solution { +public: + int dfs(int& res, TreeNode* p) { + if (!p) return 2; + int left = dfs(res, p->left), right = dfs(res, p->right); + // 0 - 无覆盖; 1 - 安装摄像头; 2 - 有覆盖 + // 左右只要有一个无覆盖,当前节点都需要安装摄像头 + if (left == 0 || right == 0) { + res++; + return 1; + } + // 左右只要有一个安装摄像头且没有无覆盖,当前节点就是有覆盖 + if (left == 1 || right == 1) { + return 2; + } + // 以上情况之外,就只有左右都等于2,都有覆盖,当前节点可以无覆盖 + return 0; + } + int minCameraCover(TreeNode* root) { + if (!root->left && !root->right) return 1; + int res = 0; + if (dfs(res, root) == 0) { + res++; + }; + return res; + } + +}; +``` + + ## 二叉树的最大深度 [LeetCode中文](https://leetcode-cn.com/problems/maximum-depth-of-binary-tree/) diff --git "a/LeetCode/344円275円215円350円277円220円347円256円227円.md" "b/LeetCode/344円275円215円350円277円220円347円256円227円.md" index 0b48287..f7e4357 100644 --- "a/LeetCode/344円275円215円350円277円220円347円256円227円.md" +++ "b/LeetCode/344円275円215円350円277円220円347円256円227円.md" @@ -4,11 +4,40 @@ * [两整数之和](#两整数之和) (`easy`) * [只出现一次的数字](#只出现一次的数字) (`easy`) * [颠倒二进制位](#颠倒二进制位) (`easy`) + * [只出现一次的数字II](#只出现一次的数字II) (`medium`) # 位运算总结 +## 只出现一次的数字II + +[LeetCode中文](https://leetcode.cn/problems/single-number-ii/description/) + +题解:https://leetcode.cn/problems/single-number-ii/solutions/746993/zhi-chu-xian-yi-ci-de-shu-zi-ii-by-leetc-23t6/ + +```c++ +class Solution { +public: + int singleNumber(vector& nums) { + vector vec(32, 0); + for (int i = 0; i < 32; i++) { + for (auto& num : nums) { + vec[i] += num & 1; + num>>= 1; + } + } + int res = 0; + for (int i = 31; i>= 0; i--) { + res <<= 1; + res |= vec[i] % 3; + } + return res; + } +}; +``` + + ## 位1的个数 [LeetCode中文](https://leetcode-cn.com/problems/number-of-1-bits/) @@ -298,19 +327,13 @@ public: ```c++ class Solution { public: - uint32_t reverseBits(uint32_t n) { - uint32_t res = 0; - for(int i=0;i<32;i++) - { - res = res << 1; - if(n & 1) - { - res += 1; - } - - n = n>> 1; + int reverseBits(int n) { + int res = 0; + for (int i = 0; i < 32; i++) { + res <<= 1; + res |= n & 1; + n>>= 1; } - return res; } }; diff --git "a/LeetCode/345円210円206円346円262円273円.md" "b/LeetCode/345円210円206円346円262円273円.md" new file mode 100644 index 0000000..5a6a8ab --- /dev/null +++ "b/LeetCode/345円210円206円346円262円273円.md" @@ -0,0 +1,115 @@ +**分治算法** + * [为运算表达式设计优先级](#为运算表达式设计优先级) (`medium`) + * [至少有K个重复字符的最长子串](#至少有K个重复字符的最长子串) (`medium` `递归`) + +# 分治算法 + +## 至少有K个重复字符的最长子串 + +[LeetCode中文](https://leetcode.cn/problems/longest-substring-with-at-least-k-repeating-characters/description/) + +**解法**:分治思想,递归解决子问题 + +https://leetcode.cn/problems/longest-substring-with-at-least-k-repeating-characters/solutions/623432/zhi-shao-you-kge-zhong-fu-zi-fu-de-zui-c-o6ww/ + +时间复杂度:O(n * 26) + +```c++ +class Solution { +public: + int longestSubstring(string s, int k) { + return dfs(s, 0, s.size() - 1, k); + } + int dfs(const string& s, int l, int r, int k) { + if (r - l + 1 < k) return 0; + vector mp(26, 0); + for (int i = l; i <= r; ++i) { + ++mp[s[i] - 'a']; + } + char ch = 0; + for (int i = 0; i < 26; ++i) { + if (mp[i]> 0 && mp[i] < k) { + ch = 'a' + i; + break; + } + } + if (ch == 0) { + return r - l + 1; + } + while (l <= r && s[l] == ch) { + ++l; + } + int idx = l; + int res = 0; + while (idx <= r) { + if (s[idx] == ch) { + res = max(res, dfs(s, l, idx - 1, k)); + while (idx <= r && s[idx] == ch) { + ++idx; + } + l = idx; + } else { + if (idx == r) { + res = max(res, dfs(s, l, idx, k)); + } + ++idx; + } + } + return res; + } +}; +``` + + +## 为运算表达式设计优先级 + +[LeetCode中文](https://leetcode.cn/problems/different-ways-to-add-parentheses/description/) + +解法:分治,子问题是 `num1 op num2` + +**方法1: 递归** + +```c++ +class Solution { +public: + vector diffWaysToCompute(string expression) { + return recursive(expression, 0, expression.size() - 1); + } + vector recursive(const string& exp, int l, int r) { + vector res; + if (r - l <= 1) { + if (res.empty()) { + int i = l; + int tmp = 0; + while (i <= r) { + tmp = tmp * 10 + (exp[i] - '0'); + ++i; + } + res.push_back(tmp); + } + return res; + } + for (int i = l; i <= r; ++i) { + char op = exp[i]; + if (op == '+' || op == '-' || op == '*') { + const vector& left = recursive(exp, l, i - 1); + const vector& right = recursive(exp, i + 1, r); + for (const int a : left) { + for (const int b : right) { + int val; + if (op == '+') { + val = a + b; + } else if (op == '-') { + val = a - b; + } else { + val = a * b; + } + res.push_back(val); + } + } + } + } + return res; + } +}; +``` diff --git "a/LeetCode/345円211円215円347円274円200円345円222円214円.md" "b/LeetCode/345円211円215円347円274円200円345円222円214円.md" new file mode 100644 index 0000000..9cac747 --- /dev/null +++ "b/LeetCode/345円211円215円347円274円200円345円222円214円.md" @@ -0,0 +1,36 @@ +* **数组类总结** + * [和为K的子数组](#和为K的子数组) + * [路径总和III](https://github.com/Miller-Xie/Code/blob/master/LeetCode/%E5%9B%9E%E6%BA%AF.md#%E8%B7%AF%E5%BE%84%E6%80%BB%E5%92%8Ciii) + + +# 数组类总结 + +## 和为K的子数组 + +[LeetCode中文](https://leetcode.cn/problems/subarray-sum-equals-k/description/) + +解法:哈希表 + 前缀和 + +```c++ +class Solution { +public: + int subarraySum(vector& nums, int k) { + unordered_map mp; + mp[0] = 1; + int pre = 0; + int res = 0; + for (int i = 0; i < nums.size(); i++) { + pre += nums[i]; + if (mp.find(pre - k) != mp.end()) { + res += mp[pre - k]; + } + if (mp.find(pre) != mp.end()) { + mp[pre] += 1; + } else { + mp[pre] = 1; + } + } + return res; + } +}; +``` diff --git "a/LeetCode/345円212円250円346円200円201円350円247円204円345円210円222円.md" "b/LeetCode/345円212円250円346円200円201円350円247円204円345円210円222円.md" index 801775e..aac0179 100644 --- "a/LeetCode/345円212円250円346円200円201円350円247円204円345円210円222円.md" +++ "b/LeetCode/345円212円250円346円200円201円350円247円204円345円210円222円.md" @@ -3,25 +3,572 @@ * [爬楼梯](#爬楼梯) (`easy`) * [买卖股票的最佳时机](#买卖股票的最佳时机) (`easy`) * [买卖股票的最佳时机III](#买卖股票的最佳时机III) (`hard` `分治`) - * [最大子序和](#最大子序和) (`easy`) - * [打家劫舍](#打家劫舍) (`easy`) + * [买卖股票的最佳时机含冷冻期](#买卖股票的最佳时机含冷冻期) (`medium` `状态dp`) + * [打家劫舍](#打家劫舍) (`medium`) + * [打家劫舍III](#打家劫舍III) (`medium` `树形动态规划`) * [最小路径和](#最小路径和) (`medium`) * [不同路径](#不同路径) (`medium`) * [三角形最小路径和](#三角形最小路径和) (`medium`) - * [最大上升子序列](#最大上升子序列) (`medium` `贪心`) * [俄罗斯套娃信封问题](#俄罗斯套娃信封问题) (`hard` `贪心`) * [递增的三元子序列](#递增的三元子序列) (`medium` `贪心`) * [最大正方形](#最大正方形) (`medium`) - * [摆动序列](#摆动序列) (`medium` `贪心`) - * [零钱兑换](#零钱兑换) (`medium` `背包问题`) * [最大回文子串](#最大回文子串) (`medium`) * [无重复字符的最长子串](#无重复字符的最长子串) (`medium` `hash` `滑动窗口`) * [戳气球](#戳气球) (`hard`) -* [最长等差数列](#最长等差数列) (`medium`) - * [最长递增子序列的个数](#最长递增子序列的个数) (`medium`) + * [最长等差数列](#最长等差数列) (`medium`) + * [接雨水](#接雨水) (`hard`) + * [编辑距离](#编辑距离) (`medium` `dp`) + * [完全平方数](#完全平方数) (`medium` `dp` `记忆化`) + * [单词拆分](#单词拆分) (`medium` `dp` `记忆化`) + * [最长有效括号](#最长有效括号) (`hard` `dp`) + * [最大整除子集](#最大整除子集) (`medium` `状态转移技巧`) + * 背包问题 + * [最后一块石头的重量II](#最后一块石头的重量II) (`medium` `01背包`) + * [分割等和子集](#分割等和子集) (`medium` `01背包`) + * [零钱兑换](#零钱兑换) (`medium` `完全背包`) + * 子序列问题 + * [最大上升子序列](#最大上升子序列) (`medium` `贪心`) + * [不同的子序列](#不同的子序列) (`hard` `dp`) + * [摆动序列](#摆动序列) (`medium` `贪心`) + * [最长递增子序列的个数](#最长递增子序列的个数) (`medium`) + * 子数组问题 + * [最大子数组和](#最大子数组和) (`medium`) + * [乘积最大子数组](#乘积最大子数组) (`medium`) + * [环形子数组的最大和](#环形子数组的最大和) (`medium`) - # 动态规划总结 + +## 环形子数组的最大和 + +[LeetCode中文](https://leetcode.cn/problems/maximum-sum-circular-subarray/description/) + +题解:https://leetcode.cn/problems/maximum-sum-circular-subarray/solutions/1152143/wo-hua-yi-bian-jiu-kan-dong-de-ti-jie-ni-892u + +```c++ +class Solution { +public: + int maxSubarraySumCircular(vector& nums) { + int len = nums.size(); + int max_v = INT_MIN, min_v = INT_MAX, cur_max = 0, cur_min = 0, total = 0; + for (int i = 0; i < len; i++) { + int a = nums[i]; + cur_max = max(cur_max + a, a); + cur_min = min(cur_min + a, a); + max_v = max(max_v, cur_max); + min_v = min(min_v, cur_min); + total += a; + } + return max_v> 0 ? max(max_v, total - min_v) : max_v; + } +}; +``` + + +## 最大整除子集 + +[LeetCode中文](https://leetcode.cn/problems/largest-divisible-subset/description/) + +题解:动态规划,状态转移记录有技巧 + +https://leetcode.cn/problems/largest-divisible-subset/solutions/738882/gong-shui-san-xie-noxiang-xin-ke-xue-xi-0a3jc + + +```c++ +class Solution { +public: + vector largestDivisibleSubset(vector& nums) { + vector res; + int len = nums.size(); + sort(nums.begin(), nums.end()); + vector dp(len, 1), state(len, 0); + int max_len = 1, max_idx = 0; + for (int i = 1; i < len; i++) { + int idx = -1; + for (int j = i - 1; j>= 0; j--) { + if (nums[i] % nums[j] == 0) { + if (dp[j] + 1> dp[i]) { + dp[i] = dp[j] + 1; + idx = j; + } + } + } + state[i] = idx; + if (dp[i]> max_len) { + max_len = dp[i]; + max_idx = i; + } + } + int a = max_idx; + while (res.size() < max_len) { + res.push_back(nums[a]); + a = state[a]; + } + return res; + } +}; +``` + + +## 不同的子序列 + +[LeetCode中文](https://leetcode.cn/problems/distinct-subsequences/) + +题解:设`dp[i][j]`代表t[0]-t[j]在s[0]-s[i]的子序列中出现的个数 + +动态转移方程: + +```c++ +if (s[i] == t[j]) { + dp[i][j] = (0LL + dp[i - 1][j - 1] + dp[i - 1][j]) % INT_MAX; +} else { + dp[i][j] = dp[i - 1][j]; +} +``` + +```c++ +class Solution { +public: + int numDistinct(string s, string t) { + int len1 = s.size(), len2 = t.size(); + vector> dp(len1, vector(len2, 0)); + int cnt = 0; + for (int i = 0; i < len1; i++) { + if (s[i] == t[0]) cnt++; + dp[i][0] = cnt; + } + if (s[0] == t[0]) dp[0][0] = 1; + for (int i = 1; i < len1; i++) { + for (int j = 1; j < len2; j++) { + if (s[i] == t[j]) { + dp[i][j] = (0LL + dp[i - 1][j - 1] + dp[i - 1][j]) % INT_MAX; + } else { + dp[i][j] = dp[i - 1][j]; + } + } + } + return dp[len1 - 1][len2 - 1]; + } +}; +``` + + +## 最后一块石头的重量II + +[LeetCode中文](https://leetcode.cn/problems/last-stone-weight-ii/description/) + +背包问题通用题解:https://leetcode.cn/problems/last-stone-weight-ii/solutions/805162/yi-pian-wen-zhang-chi-tou-bei-bao-wen-ti-5lfv + + +```c++ +class Solution { +public: + int lastStoneWeightII(vector& stones) { + int total = accumulate(stones.begin(), stones.end(), 0); + int target = total / 2; + vector dp(target + 1, 0); + int len = stones.size(); + // dp[i][j] = max(dp[i - 1][j], dp[i-1][j-v[i] + v[i]]) + // 优化后: dp[j] = max(dp[j], dp[j-v[i]] + v[i]) + dp[0] = 0; + for (int i = 0; i < len; i++) { + for (int j = target; j>= 1; j--) { + if (j>= stones[i]) dp[j] = max(dp[j], dp[j - stones[i]] + stones[i]); + } + } + return total - 2 * dp[target]; + // vector> dp(len, vector(target + 1, 0)); + // for (int i = 0; i < len; i++) { + // for (int j = 1; j <= target; j++) { + // if (i == 0) { + // if (j>= stones[i]) dp[i][j] = stones[i]; + // continue; + // } + // if (j>= stones[i]) { + // dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - stones[i]] + stones[i]); + // } else { + // dp[i][j] = dp[i - 1][j]; + // } + // } + // } + // return total - 2 * dp[len - 1][target]; + } +}; +``` + + +## 最长有效括号 + +[LeetCode中文](https://leetcode.cn/problems/longest-valid-parentheses/) + +题解:设`dp[i]`为以s[i]结尾的字符串最长有效括号长度 + +``` +if s[i] == '(': + dp[i] = 0 +if s[i] == ')': + if s[i - 1] == '(': + dp[i] = dp[i - 2] + 2 + else if s[i - dp[i - 1] - 1] == '(': + dp[i] = dp[i - 1] + 2 + dp[i - dp[i - 1] - 2] (需要i - dp[i - 1] - 2>= 0) +``` + +```c++ +class Solution { +public: + int longestValidParentheses(string s) { + int len = s.size(); + if (len < 2) return 0; + int res = 0; + vector dp(len, 0); + if (s[0] == '(' && s[1] == ')') { + dp[1] = 2; + res = 2; + } + for (int i = 2; i < len; i++) { + if (s[i] == ')') { + if (s[i - 1] == '(') { + dp[i] = dp[i - 2] + 2; + } else { + int pre = i - dp[i - 1] - 1; + if (pre>= 0 && s[pre] == '(') { + dp[i] = dp[i - 1] + 2; + if (pre>= 1) { + dp[i] = dp[pre - 1] + dp[i]; + } + } + } + } + res = max(res, dp[i]); + } + return res; + } +}; +``` + +## 乘积最大子数组 + +[LeetCode中文](https://leetcode.cn/problems/maximum-product-subarray/description/) + +题解:动态规划 + +设以nums[i]结尾的子数组最大乘积是dp[i][0],最小乘积是dp[i][1],动态转移方程如下: + +``` +if nums[i]>= 0: + dp[i][0] = max(dp[i][0] * nums[i], nums[i]) + dp[i][1] = min(dp[i][1] * nums[i], nums[i]) + +if nums[i] < 0: + dp[i][0] = max(dp[i][1] * nums[i], nums[i]) + dp[i][1] = min(dp[i][0] * nums[i], nums[i]) +``` + +**注意**: nums[i] < 0的时候,状态转移的过程中最小值和最大值回交替使用,需要用tmp暂存某一个值防止被覆盖 + +```c++ +class Solution { +public: + int maxProduct(vector& nums) { + int len = nums.size(); + if (len == 0) return 0; + int imax = nums[0], imin = nums[0]; + int res = imax; + for (int i = 1; i < len; i++) { + if (nums[i]>= 0) { + imax = max(imax * nums[i], nums[i]); + imin = min(imin * nums[i], nums[i]); + } else { + int tmp = imax; + imax = max(imin * nums[i], nums[i]); + imin = min(tmp * nums[i], nums[i]); + } + res = max(imax, res); + } + return res; + } +}; +``` + +## 单词拆分 + +[LeetCode中文](https://leetcode.cn/problems/word-break/) + +解答:动态规划 + 记忆化 + +用一个set记录所有的单词 + +设dp[i]代表0〜i的字串能否被字典中的单词拼接 + +dp[i] = dp[j] 且 单词s.substr(j + 1, i - j)在set中, 0 <= j < i + + +```c++ +class Solution { +public: + bool wordBreak(string s, vector& wordDict) { + unordered_set st; + for (const auto& a : wordDict) { + st.insert(a); + } + int len = s.size(); + vector dp(len + 1, false); + dp[0] = true; + for (int i = 1; i <= len; ++i) { + for (int j = 0; j <= i - 1; ++j) { + if (dp[j] && st.find(s.substr(j, i - j)) != st.end()) { + dp[i] = true; + break; + } + } + } + return dp[len]; + } +}; +``` + +## 完全平方数 + +[LeetCode中文](https://leetcode.cn/problems/perfect-squares/description/) + +解法:动态规划 + 记忆化 + +先用bool数组flag记录1〜n范围内的数字是否为完全平方数 + +设dp[i]代表和为数字i的完全平方数的最小数量 + +如果flag[i] = true, dp[i] = 1 +否则,dp[i] = min(dp[i - j * j] + 1, j * j <= i) + +```c++ +class Solution { +public: + int numSquares(int n) { + if (n <= 1) return n; + vector flag(n + 1, false); + for (int i = 1; i * i <= n; i++) { + flag[i * i] = true; + } + vector dp(n + 1, INT_MAX); + dp[0] = 0; + dp[1] = 1; + for (int i = 2; i <= n; i++) { + if (flag[i]) { + dp[i] = 1; + continue; + } + for (int j = 1; j * j <= i; j++) { + if (dp[i - j * j] != INT_MAX) { + dp[i] = min(dp[i], dp[i - j * j] + 1); + } + } + } + return dp[n]; + } +}; +``` + +## 编辑距离 + +[LeetCode中文](https://leetcode.cn/problems/edit-distance/) + +解法:动态规划 + +https://leetcode.cn/problems/edit-distance/solutions/6455/zi-di-xiang-shang-he-zi-ding-xiang-xia-by-powcai-3/ + +```c++ +class Solution { +public: + int minDistance(string word1, string word2) { + if (word1.empty()) return word2.size(); + if (word2.empty()) return word1.size(); + int len1 = word1.size(), len2 = word2.size(); + vector> dp(len1, vector(len2)); + bool flag = false; + for (int j = 0; j < len2; j++) { + if (word2[j] == word1[0]) flag = true; + if (flag) dp[0][j] = j; + else dp[0][j] = j + 1; + } + flag = false; + for (int i = 0; i < len1; ++i) { + if (word1[i] == word2[0]) flag = true; + if (flag) dp[i][0] = i; + else dp[i][0] = i + 1; + } + for (int i = 1; i < len1; i++) { + for (int j = 1; j < len2; j++) { + if (word1[i] == word2[j]) { + dp[i][j] = dp[i - 1][j - 1]; + } else { + dp[i][j] = min(min(dp[i - 1][j - 1], dp[i - 1][j]), dp[i][j - 1]) + 1; + } + } + } + return dp[len1 - 1][len2 - 1]; + } +}; +``` + +## 打家劫舍III + +[LeetCode中文](https://leetcode.cn/problems/house-robber-iii/solutions/47828/san-chong-fang-fa-jie-jue-shu-xing-dong-tai-gui-hu/) + +解法:树形动态规划 + 记忆化 + 递归 + +```c++ +/** + * Definition for a binary tree node. + * struct TreeNode { + * int val; + * TreeNode *left; + * TreeNode *right; + * TreeNode() : val(0), left(nullptr), right(nullptr) {} + * TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} + * TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {} + * }; + */ +class Solution { +public: + int rob(TreeNode* root) { + return max(pickRecursive(root), unpickRecursive(root)); + } + int pickRecursive(TreeNode* root) { + if (!root) return 0; + if (pick_mp.find(root) != pick_mp.end()) { + return pick_mp[root]; + } + int res = root->val + unpickRecursive(root->left) + unpickRecursive(root->right); + pick_mp[root] = res; + return res; + } + int unpickRecursive(TreeNode* root) { + if (!root) return 0; + if (unpicked_mp.find(root) != unpicked_mp.end()) { + return unpicked_mp[root]; + } + int res = max(pickRecursive(root->left), unpickRecursive(root->left)) + max(pickRecursive(root->right), unpickRecursive(root->right)); + unpicked_mp[root] = res; + return res; + } + +private: + unordered_map
pick_mp; + unordered_map
unpicked_mp; +}; +``` + +## 买卖股票的最佳时机含冷冻期 + +[LeetCode中文](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-with-cooldown/) + +题解:同一天的股票情况分三种: +* dp[i][0]: 今天不持有,没有卖出 +* dp[i][1]: 今天不持有,且卖出 +* dp[i][2]: 今天持有(1. 今天买入 2. 今天没买入,之前就持有) + +动态转移方程: + +``` +dp[i][0] = max(dp[i - 1][0], dp[i - 1][1]); +dp[i][1] = dp[i - 1][2] + prices[i]; +dp[i][2] = max(dp[i - 1][0] - prices[i], dp[i - 1][2]); +``` + +```c++ +class Solution { +public: + int maxProfit(vector& prices) { + if (prices.empty()) return 0; + int n = prices.size(); + vector> dp(n, vector(3)); + dp[0][0] = 0; // 今天不持有,没有卖出 + dp[0][1] = 0; // 今天不持有,且卖出 + dp[0][2] = -1 * prices[0]; // 今天持有(1. 今天买入 2. 今天没买入,之前就持有) + for (int i = 1; i < n; ++i) { + dp[i][0] = max(dp[i - 1][0], dp[i - 1][1]); + dp[i][1] = dp[i - 1][2] + prices[i]; + dp[i][2] = max(dp[i - 1][0] - prices[i], dp[i - 1][2]); + } + return max(dp[n - 1][0], dp[n - 1][1]); + } +}; +``` + +## 分割等和子集 + +[LeetCode中文](https://leetcode.cn/problems/partition-equal-subset-sum/description/) + +解法:01背包问题 +题意等价于从数组中找出一些数字的和等于数组中所有数字之和的一半,设为target +设dp[i][j]是从[0, i]中选数字之和是否能组成j(bool类型),状态转移方程: + +dp[i][j] = dp[i - 1][j] || dp[i - 1][j - nums[i]] (nums[i) < j) +dp[i][j] = true (nums[i] == j) +dp[i][j] = dp[i - 1][j] (nums[i)> j) + +初始条件: +dp[i][0] = true (i = 0 ~ nums.size() - 1) +dp[nums[0]][0] = true + +**扩展**:更多背包问题参考 [背包九讲](https://zhuanlan.zhihu.com/p/139368825) + +二维数组代码: +```c++ +class Solution { +public: + bool canPartition(vector& nums) { + if (nums.size() == 0) return true; + if (nums.size() == 1) return false; + int sum = accumulate(nums.begin(), nums.end(), 0); + if (sum % 2 == 1) return false; + int target = sum / 2; + int len = nums.size(); + vector> dp(len, vector(target + 1, false)); + for (int i = 0; i < len; ++i) { + dp[i][0] = true; + } + if (nums[0] < target) dp[0][nums[0]] = true; + for (int i = 1; i < len; ++i) { + for (int j = 1; j <= target; ++j) { + if (j == nums[i]) dp[i][j] = true; + else if (j> nums[i]) { + dp[i][j] = dp[i - 1][j] || dp[i - 1][j - nums[i]]; + } else { + dp[i][j] = dp[i - 1][j]; + } + } + } + return dp[len - 1][target]; + } +}; +``` + +时间和空间优化后,一维数组代码: + +```c++ +class Solution { +public: + bool canPartition(vector& nums) { + if (nums.size() == 0) return true; + if (nums.size() == 1) return false; + int sum = accumulate(nums.begin(), nums.end(), 0); + if (sum % 2 == 1) return false; + int target = sum / 2; + if (*max_element(nums.begin(), nums.end())> target) return false; + int len = nums.size(); + vector dp(target + 1, false); + if (nums[0] < target) dp[nums[0]] = true; + dp[0] = true; + for (int i = 1; i < len; ++i) { + for (int j = target; j>= 0; --j) { + if (nums[i] <= j) dp[j] = dp[j] || dp[j - nums[i]]; + } + } + return dp[target]; + } +}; +``` + ## 爬楼梯 [LeetCode中文](https://leetcode-cn.com/problems/climbing-stairs) @@ -180,7 +727,7 @@ class Solution: -## 最大子序和 +## 最大子数组和 [LeetCode中文](https://leetcode-cn.com/problems/maximum-subarray) @@ -965,7 +1512,7 @@ public: ``` dp1[i] = max{dp2[j]+1 , 0 =< j <= i-1 且 nums[i]> nums[j]} -dp2[i] = max{dp1[j]+1 , 0 =< j <= i-1 且 nums[i]> nums[j]} +dp2[i] = max{dp1[j]+1 , 0 =< j <= i-1 且 nums[i] < nums[j]} ``` 遍历结束之后,整个数组的最长摆动子序列就是`max(dp1.back(),dp2.back())`。 @@ -1959,6 +2506,13 @@ class Solution: ### 解答 +动态规划,设`dp_len[i]`代表以nums[i]结尾的最长上升子序列长度,`dp_num[i]`代表以nums[i]结尾的最长上升子序列数量,状态转移方程如下: + +``` +dp_len[i] = max{dp_len[j] + 1, j < i && nums[j] < nums[i]} +dp_num[i] = sum{dp_num[j], j < i && nums[j] < nums[i] && dp_len[j] = dp_len[i] - 1} +``` + **Python代码** ```python @@ -1980,3 +2534,36 @@ class Solution: return sum(x for i, x in enumerate(dp_num) if dp_len[i] == max_len) ``` + +## 接雨水 + +[LeetCode中文](https://leetcode.cn/problems/trapping-rain-water) + + +### 解答 + +[题解](https://leetcode.cn/problems/trapping-rain-water/solution/xiang-xi-tong-su-de-si-lu-fen-xi-duo-jie-fa-by-w-8/) + +```c++ +class Solution { +public: + int trap(vector& height) { + if (height.size() < 3) return 0; + int len = height.size(); + vector left_max(len),right_max(len); + for (int i = 1; i < len; ++i) { + left_max[i] = max(height[i - 1], left_max[i - 1]); + } + for (int j = len -2; j>= 0; --j) { + right_max[j] = max(right_max[j + 1], height[j + 1]); + } + int res = 0; + for (int i = 1; i < height.size() - 1; ++i) { + int a = min(left_max[i], right_max[i]); + if (a> height[i]) res += a - height[i]; + } + return res; + } +}; +``` + diff --git "a/LeetCode/345円215円225円350円260円203円346円240円210円.md" "b/LeetCode/345円215円225円350円260円203円346円240円210円.md" new file mode 100644 index 0000000..c5b5bdd --- /dev/null +++ "b/LeetCode/345円215円225円350円260円203円346円240円210円.md" @@ -0,0 +1,130 @@ +* **单调栈总结** + * [每日温度](#每日温度) (`medium`) + * [柱状图中最大的矩形](#柱状图中最大的矩形) (`hard`) + * [最大矩形](#最大矩形) (`hard`) + * [去除重复字母](https://github.com/Miller-Xie/Code/blob/master/LeetCode/%E8%B4%AA%E5%BF%83.md#%E5%8E%BB%E9%99%A4%E9%87%8D%E5%A4%8D%E5%AD%97%E6%AF%8D) (`hard` `贪心`) + + +# 单调栈总结 + +## 最大矩形 + +[LeetCode中文](ghttps://leetcode.cn/problems/maximal-rectangle/) + +解法:https://leetcode.cn/problems/maximal-rectangle/solutions/9535/xiang-xi-tong-su-de-si-lu-fen-xi-duo-jie-fa-by-1-8/ +思路还是单调栈,不过是[柱状图中最大的矩形](#柱状图中最大的矩形)的进阶 + +```c++ +class Solution { +public: + int maximalRectangle(vector>& matrix) { + int row = matrix.size(), col = matrix[0].size(); + int res = 0; + vector heights(col, 0); + vector left(col, 0), right(col, 0); + vector sta; + for (int i = 0; i < row; ++i) { + for (int j = 0; j < col; ++j) { + heights[j] = matrix[i][j] == '1' ? heights[j] + 1 : 0; + while (!sta.empty() && heights[j] <= heights[sta.back()]) { + sta.pop_back(); + } + if (sta.empty()) { + left[j] = 0; + } else { + left[j] = sta.back() + 1; + } + sta.push_back(j); + } + sta.clear(); + for (int j = col - 1; j>= 0; --j) { + while (!sta.empty() && heights[j] <= heights[sta.back()]) { + sta.pop_back(); + } + if (sta.empty()) { + right[j] = col - 1; + } else { + right[j] = sta.back() - 1; + } + sta.push_back(j); + } + for (int j = 0; j < col; ++j) { + res = max(res, (right[j] - left[j] + 1) * heights[j]); + } + sta.clear(); + } + return res; + } +}; +``` + +## 每日温度 + +[LeetCode中文](https://leetcode.cn/problems/daily-temperatures/) + +解法: 最基本的单调栈实现 + +```c++ +class Solution { +public: + vector dailyTemperatures(const vector& temperatures) { + stack> sta; + int n = temperatures.size(); + vector res(n, 0); + for (int i = n - 1; i>= 0; --i) { + int cur = temperatures[i]; + while (!sta.empty() && cur>= sta.top().second) { + sta.pop(); + } + if (sta.empty()) res[i] = 0; + else res[i] = sta.top().first - i; + sta.push({i, cur}); + } + return res; + } +}; +``` + +## 柱状图中最大的矩形 + +[LeetCode中文](https://leetcode.cn/problems/largest-rectangle-in-histogram/) + +解法:利用单调栈,求出左边和右边最后一个>=当前元素的位置,然后重新遍历的时候,计算每个元素形成的最大巨星面积 + +```c++ +class Solution { +public: + int largestRectangleArea(vector& heights) { + stack sta, sta1; + int len = heights.size(); + vector left_mp(len), right_mp(len); + for (int i = 0; i < len; ++i) { + while (!sta.empty() && heights[i] <= heights[sta.top()]) { + sta.pop(); + } + if (sta.empty()) { + left_mp[i] = 0; + } else { + left_mp[i] = sta.top() + 1; + } + sta.push(i); + } + for (int i = len - 1; i>= 0; --i) { + while (!sta1.empty() && heights[i] <= heights[sta1.top()]) { + sta1.pop(); + } + if (sta1.empty()) { + right_mp[i] = len - 1; + } else { + right_mp[i] = sta1.top() - 1; + } + sta1.push(i); + } + int res = 0; + for (int i = 0; i < len; ++i) { + res = max(res, (right_mp[i] - left_mp[i] + 1) * heights[i]); + } + return res; + } +}; +``` diff --git "a/LeetCode/345円215円225円350円260円203円351円230円237円345円210円227円.md" "b/LeetCode/345円215円225円350円260円203円351円230円237円345円210円227円.md" new file mode 100644 index 0000000..3f379cf --- /dev/null +++ "b/LeetCode/345円215円225円350円260円203円351円230円237円345円210円227円.md" @@ -0,0 +1,40 @@ +* **单调队列** + * [滑动窗口最大值](#滑动窗口最大值) (`hard` `滑动窗口`) + +# 单调队列总结 + +## 滑动窗口最大值 + +[LeetCode中文](https://leetcode.cn/problems/sliding-window-maximum/description/) + +参考题解:https://leetcode.cn/problems/sliding-window-maximum/solutions/2361228/239-hua-dong-chuang-kou-zui-da-zhi-dan-d-u6h0 + + +```c++ +class Solution { +public: + vector maxSlidingWindow(vector& nums, int k) { + int len = nums.size(); + deque que; + int l = 0, r = 0; + vector res; + while (r < len) { + while (!que.empty() && que.back() < nums[r]) { + que.pop_back(); + } + que.push_back(nums[r]); + r++; + while (r - l> k) { + if (nums[l] == que.front()) { + que.pop_front(); + } + l++; + } + if (r - l == k) { + res.push_back(que.front()); + } + } + return res; + } +}; +``` diff --git "a/LeetCode/345円233円236円346円272円257円.md" "b/LeetCode/345円233円236円346円272円257円.md" index fd91967..dc24f8f 100644 --- "a/LeetCode/345円233円236円346円272円257円.md" +++ "b/LeetCode/345円233円236円346円272円257円.md" @@ -4,15 +4,207 @@ * [括号生成](#括号生成) (`medium`) * [电话号码的字母组合](#电话号码的字母组合) (`medium`) * [全排列](#全排列) (`medium`) + * [全排列II](#全排列II) (`medium`) * [格雷编码](#格雷编码) (`medium`) * [累加数](#累加数) (`medium`) * [将数组拆分成斐波那契序列](#将数组拆分成斐波那契序列) (`medium` `贪心`) * [解数独](#解数独) (`hard` `哈希`) + * [路径总和III](#路径总和III) (`medium` `回溯` `哈希`) + * [N皇后](#n皇后) (`hard` `回溯`) + * [分割回文串](#分割回文串) (`medium` `dp`) + * [划分为k个相等的子集](#划分为k个相等的子集) (`medium` `剪枝`) # 回溯法总结 +## 划分为k个相等的子集 + +[LeetCode中文](https://leetcode.cn/problems/partition-to-k-equal-sum-subsets/description/) + +题解:回溯 + 剪枝优化,几种剪枝优化思路很值得学习! + +https://leetcode.cn/problems/partition-to-k-equal-sum-subsets/solutions/1441006/by-lfool-d9o7/ + +```c++ +class Solution { +public: + static bool dfs(const vector& nums, int idx, vector& bucket, int target) { + // 剪枝优化2:遍历完整个数组,说明所有桶都满足要求,不需要再次检查,直接返回true + if (idx == nums.size()) return true; + for (int i = 0; i < bucket.size(); i++) { + // 剪枝优化3:相同大小的桶,元素只需要随意放入其中一个就行,默认放入第一个 + if (i> 0 && bucket[i] == bucket[i - 1]) continue; + if (bucket[i] + nums[idx]> target) continue; + bucket[i] += nums[idx]; + if (dfs(nums, idx + 1, bucket, target)) { + return true; + } + bucket[i] -= nums[idx]; + } + return false; + } + bool canPartitionKSubsets(vector& nums, int k) { + int total = accumulate(nums.begin(), nums.end(), 0); + // 剪枝优化1:数组降序排序,提高元素放入桶的命中率 + sort(nums.begin(), nums.end(), greater()); + if (total % k != 0) return false; + int target = total / k; + int len = nums.size(); + vector bucket(k, 0); + return dfs(nums, 0, bucket, target); + } +}; +``` + +## 全排列II + +[LeetCode中文](https://leetcode.cn/problems/permutations-ii/description/) + +详细题解:https://leetcode.cn/problems/permutations-ii/solutions/71776/hot-100-47quan-pai-lie-ii-python3-hui-su-kao-lu-zh + +回溯 + 剪枝 +[画像:image] + +```c++ +class Solution { +public: + void dfs(const vector& nums, vector>& res, vector& tmp, vector& vis, int n) { + if (n>= nums.size()) { + res.push_back(tmp); + return; + } + for (int i = 0; i < nums.size(); i++) { + if (i> 0 && nums[i] == nums[i - 1] && !vis[i - 1]) { + continue; + } + if (!vis[i]) { + tmp.push_back(nums[i]); + vis[i] = true; + dfs(nums, res, tmp, vis, n + 1); + tmp.pop_back(); + vis[i] = false; + } + } + } + vector> permuteUnique(vector& nums) { + sort(nums.begin(), nums.end()); + vector vis(nums.size(), false); + vector> res; + vector tmp; + dfs(nums, res, tmp, vis, 0); + return res; + } +}; +``` + + +## 分割回文串 + +[LeetCode中文](https://leetcode.cn/problems/palindrome-partitioning/) + +解法:回溯 + 动态规划 + +设dp[i][j]代表i到j的字符串是否回文 + +d = 1, dp[i][j] = true +d =2, dp[i][j] = (s[i] == s[j]) +d> 2, dp[i][j] = dp[i + 1][j - 1] && (s[i] == s[j]) + +然后进行回溯,每一层递归选择下一步分割的位置,到结尾时记录当前dfs的结果 + +```c++ +class Solution { +public: + void recursion(const vector>& dp, const string& s, int start, vector>& res, vector& tmp) { + if (start>= s.size()) { + res.push_back(tmp); + return; + } + for (int i = start; i < s.size(); i++) { + if (dp[start][i]) { + tmp.push_back(s.substr(start, i - start + 1)); + recursion(dp, s, i + 1, res, tmp); + tmp.pop_back(); + } + } + } + vector> partition(string s) { + int len = s.size(); + vector> dp(len, vector(len, false)); + for (int i = 0; i < len; i++) { + dp[i][i] = true; + } + for (int d = 2; d <= len; d++) { + for (int i = 0; i + d - 1 < len; i++) { + int j = i + d - 1; + if (d == 2) dp[i][j] = s[i] == s[j]; + else { + dp[i][j] = dp[i + 1][j - 1] && (s[i] == s[j]); + } + } + } + vector> res; + vector tmp; + recursion(dp, s, 0, res, tmp); + return res; + } +}; +``` + +## N皇后 + +[LeetCode中文](https://leetcode.cn/problems/n-queens/) + +解法:回溯 + 状态记录 + +https://www.hello-algo.com/chapter_backtracking/n_queens_problem/ + + + +## 路径总和III + +[LeetCode中文](https://leetcode.cn/problems/path-sum-iii/description/) + +解答:记忆化搜索 + 回溯 +https://leetcode.cn/problems/path-sum-iii/solutions/596361/dui-qian-zhui-he-jie-fa-de-yi-dian-jie-s-dey6/ + +```c++ +/** + * Definition for a binary tree node. + * struct TreeNode { + * int val; + * TreeNode *left; + * TreeNode *right; + * TreeNode() : val(0), left(nullptr), right(nullptr) {} + * TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} + * TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {} + * }; + */ +class Solution { +public: + int pathSum(TreeNode* root, int targetSum) { + unordered_map mp; // key: 前缀和 value: 前缀和等于key的节点数量 + long prefix_sum = 0; + return pathSumImpl(root, targetSum, mp, prefix_sum); + } + int pathSumImpl(TreeNode* root, int target, unordered_map& mp, long prefix_sum) { + if (root == nullptr) return 0; + int res = 0; + prefix_sum += root->val; + if (prefix_sum == target) res += 1; + if (mp.find(prefix_sum - target) != mp.end()) { + res += mp[prefix_sum - target]; + } + mp[prefix_sum]++; + res += pathSumImpl(root->left, target, mp, prefix_sum); + res += pathSumImpl(root->right, target, mp, prefix_sum); + mp[prefix_sum]--; + return res; + } +}; +``` + ## 子集 [LeetCode中文](https://leetcode-cn.com/problems/subsets/) diff --git "a/LeetCode/345円233円276円.md" "b/LeetCode/345円233円276円.md" index f9d94c8..ded6183 100644 --- "a/LeetCode/345円233円276円.md" +++ "b/LeetCode/345円233円276円.md" @@ -7,11 +7,14 @@ * [单词搜索](#单词搜索) (`medium`) * [单词搜索II](#单词搜索II) (`hard` `字典树`) * [矩阵中的最长递增路径](#矩阵中的最长递增路径) (`hard` `动态规划`) + * [以图判树](#以图判树) (`medium`) * **并查集** * [冗余连接](#冗余连接) (`medium`) * [朋友圈](#朋友圈) (`medium`) + * [除法求解](#除法求解) (`medium`) * **最短路径** * [最短路径.一](#最短路径一) (`hard` `迪杰斯特拉算法`) + * [网络延迟时间](#网络延迟时间) (`medium`) * **最小生成树** * [最小生成树一.Prim算法](#最小生成树一prim算法) (`hard`) * [最小生成树二.Kruscal算法](#最小生成树二kruscal算法) (`hard`) @@ -20,6 +23,206 @@ * [课程表II](#课程表II) (`medium` `拓扑排序`) # 图总结 + +## 网络延迟时间 + +[LeetCode中文](https://leetcode.cn/problems/network-delay-time/description/) + +题解:https://leetcode.cn/problems/network-delay-time/solutions/910056/gong-shui-san-xie-yi-ti-wu-jie-wu-chong-oghpz + +解法一:朴素的迪杰斯特拉算法,时间复杂度 `O(N^2)` + +```c++ +class Solution { +public: + int networkDelayTime(vector>& times, int n, int k) { + vector dis(n + 1, INT_MAX); + vector vis(n + 1, false); + vector> graph(n + 1, vector(n + 1, -1)); + for (auto time : times) { + graph[time[0]][time[1]] = time[2]; + } + dis[k] = 0; + int cnt = 0; + for (int p = 1; p <= n; p++) { + int tmp = -1, min_dis = INT_MAX; + for (int i = 1; i <= n; i++) { + if (!vis[i] && dis[i] < min_dis) { + min_dis = dis[i]; + tmp = i; + } + } + // tmp = -1,还没结束循环,说明有些未访问的节点不可到达 + if (tmp < 0) return -1; + // if (tmp < 0 && p < n) return -1; + vis[tmp] = true; + bool flag = false; + for (int i = 1; i <= n; i++) { + if (!vis[i] && graph[tmp][i]>= 0) { + dis[i] = min(dis[i], dis[tmp] + graph[tmp][i]); + flag = true; + } + } + } + int res = INT_MIN; + for (int i = 1; i <= n; i++) { + res = max(dis[i], res); + } + return res; + } +}; +``` + + + +## 除法求解 + +[LeetCode中文](https://leetcode.cn/problems/evaluate-division/) + +**解法**:带权值并查集 + 分子分母表达式转换 + + +```c++ +class UnionSet { + public: + UnionSet(const set st) { + for (auto iter = st.begin(); iter != st.end(); ++iter) { + const string& str = *iter; + parent_mp_[str] = str; + weight_mp_[str] = 1.0; + size_mp_[str] = 1; + } + } + + bool IsSameSet(const string& str1, const string& str2) { + return FindRoot(str1) == FindRoot(str2); + } + + void Union(const string& str1, const string& str2, double val) { + const string& root1 = FindRoot(str1); + const string& root2 = FindRoot(str2); + double d1 = GetDivideToRoot(str1); + double d2 = GetDivideToRoot(str2); + if (size_mp_[root1]> size_mp_[root2]) { + parent_mp_[root2] = root1; + weight_mp_[root2] = d1 / d2 / val; + size_mp_[root1] = size_mp_[root1] + size_mp_[root2]; + size_mp_.erase(root2); + } else { + parent_mp_[root1] = root2; + weight_mp_[root1] = d2 / d1 * val; + size_mp_[root2] = size_mp_[root1] + size_mp_[root2]; + size_mp_.erase(root1); + } + } + + double GetRes(const string& str1, const string& str2) { + if (str1 == str2) { + return 1.0; + } + if (!IsSameSet(str1, str2)) { + return -1.0; + } + return GetDivideToRoot(str1) / GetDivideToRoot(str2); + } + + private: + // 当前节点 -> 父节点映射 + unordered_map parent_mp_; + // 当前节点 / 父节点 的结果 + unordered_map weight_mp_, size_mp_; + + string FindRoot(const string& str) { + if (parent_mp_[str] != str) { + return FindRoot(parent_mp_[str]); + } + return str; + } + + double GetDivideToRoot(const string& str) { + double res = 1.0; + string tmp = str; + while (parent_mp_[tmp] != tmp) { + res *= weight_mp_[tmp]; + tmp = parent_mp_[tmp]; + } + return res; + } +}; + +class Solution { +public: + vector calcEquation(vector>& equations, vector& values, vector>& queries) { + set st; + for (const auto& equation : equations) { + st.insert(equation[0]); + st.insert(equation[1]); + } + UnionSet union_set(st); + for (int i = 0; i < equations.size(); ++i) { + const string& s1 = equations[i][0]; + const string& s2 = equations[i][1]; + if (union_set.IsSameSet(s1, s2)) continue; + union_set.Union(s1, s2, values[i]); + } + vector res; + res.reserve(queries.size()); + for (const auto& query : queries) { + if (st.find(query[0]) == st.end() || st.find(query[0]) == st.end()) { + res.emplace_back(-1.0); + } else { + res.emplace_back(union_set.GetRes(query[0], query[1])); + } + } + return res; + } +}; +``` + +## 以图判树 + +[LeetCode中文](https://leetcode.cn/problems/graph-valid-tree/) + +**解法**:建立图的数据结构,dfs整个图,并记录已经访问过的node,如果遍历到某个节点发现与他相连的节点数大于1且都访问过,说明图中存在环;另外还有一种特殊情况考虑,所有的节点构成了多个隔离的环,这种情况遍历一次的节点数不等于总结点数 + +```c++ +class Solution { +public: + bool validTree(int n, vector>& edges) { + if (n < 1) return true; + vector visited(n ,false); + vector> graph(n, vector()); + for (const auto edge : edges) { + graph[edge[0]].push_back(edge[1]); + graph[edge[1]].push_back(edge[0]); + } + int cnt = 0; + visited[0] = true; + return recursive(graph, visited, 0, cnt) && cnt == n; + } + + bool recursive(const vector>& graph, vector& visited, int n, int& cnt) { + bool flag = false; + ++cnt; + for (const int a : graph[n]) { + if (!visited[a]) { + visited[a] = true; + if (!recursive(graph, visited, a, cnt)) { + return false; + } + visited[a] = false; + flag = true; + } + } + if (!flag && graph[n].size()> 1) { + return false; + } + return true; + } +}; +``` + + ## 课程表 [LeetCode中文](https://leetcode-cn.com/problems/course-schedule/) @@ -64,42 +267,34 @@ ```c++ class Solution { public: - bool canFinish(int numCourses, vector>& prerequisites) { - vector> graph(numCourses,vector()); - vector degree(numCourses,0); - for(auto a : prerequisites) - { - int from = a.second; - int to = a.first; + bool canFinish(int numCourses, vector>& prerequisites) { + vector> graph(numCourses, vector()); + vector degree(numCourses, 0); + for (const auto& vec : prerequisites) { + int from = vec[1]; + int to = vec[0]; graph[from].push_back(to); - degree[to]++; + ++degree[to]; } - queue que; - for(int i=0;i= numCourses) return true; } - - if(cnt == numCourses) return true; - return false; } }; diff --git "a/LeetCode/345円255円227円345円205円270円346円240円221円.md" "b/LeetCode/345円255円227円345円205円270円346円240円221円.md" index 1a014a3..3580cac 100644 --- "a/LeetCode/345円255円227円345円205円270円346円240円221円.md" +++ "b/LeetCode/345円255円227円345円205円270円346円240円221円.md" @@ -2,8 +2,108 @@ * [实现Trie(前缀树)](#实现Trie前缀树) (`medium`) * [键值映射](#键值映射) (`medium`) * [单词替换](#单词替换) (`medium`) + * [添加与搜索单词-数据结构设计](#添加与搜索单词-数据结构设计) (`medium`) # 字典树总结 +## 添加与搜索单词-数据结构设计 + +[LeetCode中文](https://leetcode.cn/problems/design-add-and-search-words-data-structure/solutions/) + +**解法**: 前缀树 + 正则匹配 + +```c++ +struct TrieTreeNode { + std::vector
sons; + int num; + explicit TrieTreeNode(int a) : num(a) { + sons.resize(26); + for (int i = 0; i < sons.size(); ++i) { + sons[i] = nullptr; + } + } +}; + +class TrieTree { +public: + TrieTree() { + root = new TrieTreeNode(0); + } + + void AddStr(const std::string& str) { + AddStrRecursive(str, root, 0); + } + + bool Exists(const std::string& str) { + return ExistsRecursive(str, root, 0); + } + +private: + TrieTreeNode* root = nullptr; + void AddStrRecursive(const std::string& str, TrieTreeNode* node, int idx) { + if (idx == str.size()) { + ++node->num; + return; + } + int a = str[idx] - 'a'; + if (node->sons[a] == nullptr) { + node->sons[a] = new TrieTreeNode(0); + } + AddStrRecursive(str, node->sons[a], idx + 1); + } + + bool ExistsRecursive(const std::string& str, const TrieTreeNode* node, int idx) { + if (node == nullptr) { + return false; + } + if (idx == str.size()) { + if (node->num> 0) { + return true; + } else { + return false; + } + } + if (str[idx] == '.') { + for (int i = 0; i < 26; ++i) { + if (node->sons[i] != nullptr && ExistsRecursive(str, node->sons[i], idx + 1)) { + return true; + } + } + return false; + } + int a = str[idx] - 'a'; + if (node->sons[a] == nullptr) { + return false; + } + return ExistsRecursive(str, node->sons[a], idx + 1); + } +}; + +class WordDictionary { +public: + WordDictionary() { + tree_ = new TrieTree(); + } + + void addWord(string word) { + tree_->AddStr(word); + } + + bool search(string word) { + return tree_->Exists(word); + } + +private: + TrieTree* tree_; +}; + +/** + * Your WordDictionary object will be instantiated and called as such: + * WordDictionary* obj = new WordDictionary(); + * obj->addWord(word); + * bool param_2 = obj->search(word); + */ +``` + ## 实现Trie(前缀树) [LeetCode中文](https://leetcode-cn.com/problems/implement-trie-prefix-tree) diff --git "a/LeetCode/345円255円227円347円254円246円344円270円262円.md" "b/LeetCode/345円255円227円347円254円246円344円270円262円.md" index c39b727..8635103 100644 --- "a/LeetCode/345円255円227円347円254円246円344円270円262円.md" +++ "b/LeetCode/345円255円227円347円254円246円344円270円262円.md" @@ -13,10 +13,97 @@ * [字母异位词分组](#字母异位词分组) (`medium` `哈希`) * [字符串相乘](#字符串相乘) (`medium` `数学`) * [字符串转整数(atoi)](#字符串转整数atoi) (`medium` `数学`) - * [字符串的排列](#字符串的排列) (`medium` `哈希` `滑动窗口`) - * [最小覆盖子串](#最小覆盖子串) (`hard` `滑动窗口` `哈希`) + * **滑动窗口** + * [字符串的排列](#字符串的排列) (`medium` `哈希`) + * [最小覆盖子串](#最小覆盖子串) (`hard` `哈希`) + * [找到字符串中所有字母异位词](#找到字符串中所有字母异位词) (`medium` `hash`) + * [串联所有单词的子串](#串联所有单词的子串) (`hard` `hash`) # 字符串类总结 + +## 串联所有单词的子串 +[LeetCode中文](https://leetcode.cn/problems/substring-with-concatenation-of-all-words/description/) + +题解:滑动窗口进阶 +https://leetcode.cn/problems/substring-with-concatenation-of-all-words/solutions/1616997/chuan-lian-suo-you-dan-ci-de-zi-chuan-by-244a/ + +题解中的时间复杂度分析有误,应该是o(n) (n为字符串s的长度) + +```c++ +class Solution { +public: + vector findSubstring(string s, vector& words) { + if (words.empty() || s.empty()) return {}; + int d = words[0].size(), total = words.size() * d; + unordered_map need; + for (auto word : words) { + need[word]++; + } + int len = s.size(); + vector res; + int start = 0; + while (start < d) { + int l = start, r = start; + unordered_map window; + while (r < len) { + window[s.substr(r, d)]++; + r += d; + while (r - l> total) { + string w = s.substr(l, d); + window[w]--; + if (window[w] == 0) { + window.erase(w); + } + l += d; + } + if (window == need) { + res.push_back(l); + } + } + start++; + } + return res; + } +}; +}; +``` + + +## 找到字符串中所有字母异位词 +[LeetCode中文](https://leetcode.cn/problems/find-all-anagrams-in-a-string/description/) + +题解:滑动窗口通用题解 https://leetcode.cn/problems/find-all-anagrams-in-a-string/solutions/9749/hua-dong-chuang-kou-tong-yong-si-xiang-jie-jue-zi-/ + +```c++ +class Solution { +public: + vector findAnagrams(string s, string p) { + unordered_map window, need; + for (auto ch : p) { + need[ch]++; + } + vector res; + int l = 0, r = 0; + int len = s.size(); + while (r < len) { + window[s[r++]]++; + while (r - l> p.size()) { + window[s[l]]--; + if (window[s[l]] == 0) { + window.erase(s[l]); + } + l++; + } + if (window == need) { + res.push_back(l); + } + } + return res; + } +}; +``` + + ## 机器能否返回原点 [LeetCode中文](https://leetcode-cn.com/problems/robot-return-to-origin) @@ -911,7 +998,7 @@ public: * 所有输入均为小写字母。 * 不考虑答案输出的顺序。 -### 解答 +### 解法1 利用hash表,具体思路: @@ -921,7 +1008,7 @@ public: 设`strs`中字符串元素的平均长度`k`,`strs`的长度`n`,那么 -* 时间复杂度:O(*n log k*) +* 时间复杂度:O(*n klog k*) * 空间复杂度:O(*nk*) ```c++ @@ -952,6 +1039,41 @@ public: }; ``` +### 解法2 + +因为字母都是小写字母,可以利用计数来排序 + +时间复杂度:O(nk) + +```c++ +class Solution { +public: + vector> groupAnagrams(vector& strs) { + unordered_map> mp; + for (string s : strs) { + mp[strSort(s)].push_back(s); + } + vector> anagrams; + for (auto p : mp) { + anagrams.push_back(p.second); + } + return anagrams; + } +private: + string strSort(string s) { + int counter[26] = {0}; + for (char c : s) { + counter[c - 'a']++; + } + string t; + for (int c = 0; c < 26; c++) { + t += string(counter[c], c + 'a'); + } + return t; + } +}; +``` + ## 字符串相乘 @@ -1165,18 +1287,9 @@ public: 思路:滑动窗口 + hash表 -首先使用一个`map`统计字符串`t`中每个字符出现的次数,要解决这个问题,就是要找到`s`的子串中,包含所有`map`中的字符,并且字符出现次数大于等于该字符在`map`中计数的子串。满足要求的最短子串就是答案。 - -使用一个变量`cnt`记录字符串`t`的长度,这个长度将用于判断滑动窗口中是否包含所有`t`中的字符。使用一个变量`min`表示满足要求的子串的最小长度,一个变量`start`表示该子串的起始下标。我们按如下假设遍历s进行处理: - -1. 当遇到一个`t`中的字符时,将其`map`中的计数减1(可能小于0,因为s中可能包含多个这样的字符),同时判断: - 1. 如果减1之前计数大于0,说明这个字符应该含入滑动窗口中,此时`cnt`减1; - 2. 如果减1之前计数小于等于0,说明这个字符在s中出现了很多次,此时`cnt`不变; -2. 当遇到一个不在t中出现的字符时,跳过。 - -显然,当`cnt`为0时,我们找到了一个满足要求的滑动窗口,这个滑动窗口中,包含了所有`t`中的字符。因此,我们比较这个滑动窗口的长度与`min`的值,如果小于`min`,说明找到了一个新的滑动窗口,因此更新`min`和`start`。 +直接套用通用模板,非常easy -既然当`cnt`为0时,找到了一个满足要求的滑动窗口,那么下一步该怎么做?注意到我们找到的第一个滑动窗口是位于最左边,此时`map`中一些字符的计数可能小于0,因为`t`中的某些字符在该滑动窗口中出现了很多次。此时我们从左边开始释放字符,目的是希望在右边能找到一个新的相同字符,从而得到一个新的满足要求的滑动窗口。但是从左边开始,第一个在`t`中的字符可能是一个冗余的字符(即`map`中的计数小于0),因此释放了这个字符后,滑动窗口中还是拥有满足条件的字符,那么此时回收应该只增加其在`map`中的计数,但是不需要从右边开始滑动窗口(即不增加`cnt`)。因此,只有当遇到一个在`t`中,并且`map`中计数为0的字符,才需要将`cnt`加1。因为`map`计数为0说明滑动窗口中这个字符的数量"恰好"满足要求,因此可以开始从右边滑动窗口,也就是说,我们还应该从右边找到1个这样字符,使得`map`中其计数递减后,又变为0。 +https://leetcode.cn/problems/find-all-anagrams-in-a-string/solutions/9749/hua-dong-chuang-kou-tong-yong-si-xiang-jie-jue-zi- 设字符串s的长度为m,字符串t的长度为n,那么: @@ -1186,64 +1299,28 @@ public: ```c++ class Solution { public: - string minWindow(string s, string t) { - map mp; - for(auto ch : t) - { - ++mp[ch]; + vector findAnagrams(string s, string p) { + unordered_map window, need; + for (auto ch : p) { + need[ch]++; } - - int m = s.size(); - int n = t.size(); - if(m < n) return ""; - - int cnt = n; - int start = 0,idx = 0,Min = INT_MAX,len = 0; - bool flag = false; - for(int i=0;i 0) - { - --cnt; + vector res; + int l = 0, r = 0; + int len = s.size(); + while (r < len) { + window[s[r++]]++; + while (r - l> p.size()) { + window[s[l]]--; + if (window[s[l]] == 0) { + window.erase(s[l]); } - --mp[ch]; + l++; } - - while(cnt == 0) - { - len = i-idx+1; - if(Min> len) - { - start = idx; - Min = len; - } - - char ch1 = s[idx]; - if(mp.find(ch1) != mp.end()) - { - if(mp[ch1]>= 0) - { - cnt++; - } - - mp[ch1]++; - } - - idx++; + if (window == need) { + res.push_back(l); } - } - - return Min == INT_MAX ? "" : s.substr(start,Min); + return res; } }; ``` @@ -1295,7 +1372,7 @@ public: #### 方法2 -跳出全排列的思维定势,只需要遍历`s2`,然后每当遇到在`s1`中的字母时,以这个字母位置开始从`s2`截取与`s1`同等长度的字符串,和`s1`比较是否为同一个全排列组。而比较两个字符串是否为同一个排列,可以用哈希表,记录下两个字符串中字母的出现次数然后逐个字母进行比较。同时利用滑动窗口的思想,在遍历`s2`的过程中,**动态**地改变从`s2`截取的字符串对应的哈希表`hash1`,每遍历一个新的字母,就在`hash`中将它的次数加一,由于窗口大小限制为`s1`的长度,那么此时窗口左侧的字母在`hash`中的次数就需要减一,然后和`s1`对应的哈希表`hash`比较,如果相等,则返回`true`;否则,继续遍历下一个字母。如果遍历结束都没有发现相同的哈希表,那么返回`false`。 +滑动串口,和 [找到字符串中所有字母异位词](#找到字符串中所有字母异位词) 解法完全一样 设`s1`的长度为*m*,`s2`的长度为*n* diff --git "a/LeetCode/346円216円222円345円272円217円.md" "b/LeetCode/346円216円222円345円272円217円.md" index d6aebd9..4fac337 100644 --- "a/LeetCode/346円216円222円345円272円217円.md" +++ "b/LeetCode/346円216円222円345円272円217円.md" @@ -4,9 +4,19 @@ * [数组中的第K个最大元素](#数组中的第K个最大元素) (`medium` `快速排序` `堆`) * [前K个高频元素](#前K个高频元素) (`medium` `桶排序` `堆`) * [最大间距](#最大间距) (`hard` `桶排序`) + * [摆动排序II](#摆动排序II) (`medium` `快速选择`) # 排序总结 + +## 摆动排序II + +[LeetCode中文](https://leetcode.cn/problems/wiggle-sort-ii/description/) + +**题解**:快速选择 + 数组反序穿插 + +详见:https://leetcode.cn/problems/wiggle-sort-ii/solutions/45144/yi-bu-yi-bu-jiang-shi-jian-fu-za-du-cong-onlognjia/ + ## 颜色分类 [LeetCode中文](https://leetcode-cn.com/problems/sort-colors) @@ -286,6 +296,80 @@ public: }; ``` +#### 方法3 归并排序非递归 + +```c++ +/** + * Definition for singly-linked list. + * struct ListNode { + * int val; + * ListNode *next; + * ListNode() : val(0), next(nullptr) {} + * ListNode(int x) : val(x), next(nullptr) {} + * ListNode(int x, ListNode *next) : val(x), next(next) {} + * }; + */ + +class Solution { +public: + ListNode* mergeSort(ListNode* head) { + if (!head || !head->next) return head; + ListNode *slow = head, *fast = head; + int len = 0; + while (slow) { + slow = slow->next; + ++len; + } + slow = head; + ListNode node(0), *p = &node; + ListNode* tmp; + for (int step = 1; step <= len; step *= 2) { + slow = fast = head; + ListNode *s_end, *f_end; + p = &node; + while (slow && fast) { + fast = slow; + for (int j = 0; j < step && fast; ++j) { + fast = fast->next; + } + s_end = f_end = fast; + for (int j = 0; j < step && f_end; ++j) { + f_end = f_end->next; + } + while (slow != s_end && fast != f_end) { + if (slow->val < fast->val) { + p->next = slow; + p = p->next; + slow = slow->next; + } else { + p->next = fast; + p = p->next; + fast = fast->next; + } + } + while (slow != s_end) { + p->next = slow; + p = p->next; + slow = slow->next; + } + while (fast != f_end) { + p->next = fast; + p = p->next; + fast = fast->next; + } + p->next = f_end; + slow = f_end; + } + } + return node.next; + } + + ListNode* sortList(ListNode* head) { + return mergeSort(head); + } +}; +``` + ## 数组中的第K个最大元素 diff --git "a/LeetCode/346円225円260円345円255円246円.md" "b/LeetCode/346円225円260円345円255円246円.md" index 31821ce..a1601d4 100644 --- "a/LeetCode/346円225円260円345円255円246円.md" +++ "b/LeetCode/346円225円260円345円255円246円.md" @@ -1,4 +1,5 @@ - **数学类总结** + - [丑数II](#丑数II) - [Excel表列序号](#Excel表列序号) (`easy`) - [回文数 ](#回文数) (`easy`) - [快乐数](#快乐数) (`easy`) @@ -6,7 +7,7 @@ - [3的幂](#3的幂) (`easy`) - [加一](#加一) (`easy`) - [阶乘后的零](#阶乘后的零) (`easy`) - - [整数反转](#整数反转) (`easy`) + - [整数反转](#整数反转) (`medium`) - [计数质数](#计数质数) (`easy`) - [Pow(x,n)](#Powxn) (`medium`) - [分数到小数](#分数到小数) (`medium`) @@ -14,6 +15,50 @@ # 数学类总结 +## 丑数II + +[LeetCode中文](https://leetcode.cn/problems/ugly-number-ii/description/) + +两种方法,堆或者三指针 + +详细解法:https://leetcode.cn/problems/ugly-number-ii/solutions/714340/gong-shui-san-xie-yi-ti-shuang-jie-you-x-3nvs + +```c++ +class Solution { +public: + int nthUglyNumber(int n) { + priority_queue, greater> que; + set st; + que.push(1); + st.insert(1); + int cnt = 0, res = 1; + while (1) { + int cur = que.top(); + que.pop(); + cnt++; + if (cnt == n) { + res = cur; + break; + } + if (cur <= INT_MAX / 2 && st.find(cur * 2) == st.end()) { + que.push(cur * 2); + st.insert(cur * 2); + } + if (cur <= INT_MAX / 3 && st.find(cur * 3) == st.end()) { + que.push(cur * 3); + st.insert(cur * 3); + } + if (cur <= INT_MAX / 5 && st.find(cur * 5) == st.end()) { + que.push(cur * 5); + st.insert(cur * 5); + } + } + return res; + } +}; +``` + + ## Excel表列序号 [LeetCode中文](https://leetcode-cn.com/problems/excel-sheet-column-number/) diff --git "a/LeetCode/346円225円260円347円273円204円.md" "b/LeetCode/346円225円260円347円273円204円.md" index 8d30759..b5498d9 100644 --- "a/LeetCode/346円225円260円347円273円204円.md" +++ "b/LeetCode/346円225円260円347円273円204円.md" @@ -7,6 +7,7 @@ * [除自身以外数组的乘积](#除自身以外数组的乘积) (`medium`) * [搜索二维矩阵II](#搜索二维矩阵II) (`medium` `二分查找`) * **双指针** + * [有序数组的平方](#有序数组的平方) (`easy` `双指针` `数组合并`) * [移动零](#移动零) (`easy` `双指针`) * [判断子序列](#判断子序列) (`medium` `双指针`) * [盛最多水的容器](#盛最多水的容器) (`medium` `双指针`) @@ -21,7 +22,11 @@ * [存在重复元素](#存在重复元素) (`easy` `哈希`) * [两数之和](#两数之和) (`easy` `哈希`) * [求众数](#求众数) (`easy` `哈希`) + * [多数元素](#多数元素) (`medium` `摩尔投票`) * [有效的数独](#有效的数独) (`medium` `哈希`) + * [划分字母区间](#划分字母区间) (`medium` `哈希`) + * **滑动窗口** + * [长度最小的子数组](#长度最小的子数组) (`medium`) * **其他** * [杨辉三角](#杨辉三角) (`easy` `迭代`) * [缺失数字](#缺失数字) (`easy` `位运算` `数学`) @@ -29,11 +34,146 @@ * [合并区间](#合并区间) (`medium` `排序`) * [下一个排列](#下一个排列) (`medium`) * [寻找重复数](#寻找重复数) (`medium` `交换`) - * [缺失的第一个正数](#缺失的第一个正数) (`hard` `交换`) + * [缺失的第一个正数](#缺失的第一个正数) (`hard` `交换`) + * [数组中重复的数字](#数组中重复的数字) (`medium` `交换`) # 数组类总结 +## 划分字母区间 + +[LeetCode中文](https://leetcode.cn/problems/partition-labels/description/) + +题解:https://leetcode.cn/problems/partition-labels/solutions/2884165/zui-yi-dong-bu-fu-lai-zhan-by-zealous-ho-j17e + +巧妙地解法,时间复杂度只需要 O(N) + +```c++ +class Solution { +public: + vector partitionLabels(string s) { + unordered_map mp1; + unordered_set st; + for (auto ch : s) mp1[ch]++; + vector res; + int cnt = 0; + for (int i = 0; i < s.size(); i++) { + char ch = s[i]; + mp1[ch]--; + if (mp1[ch]> 0) { + st.insert(ch); + } else { + st.erase(ch); + } + cnt++; + if (st.empty()) { + res.push_back(cnt); + cnt = 0; + } + } + return res; + } +}; +``` + + +## 有序数组的平方 + +[LeetCode中文](https://leetcode.cn/problems/squares-of-a-sorted-array/description/) + +题解:类似合并有序数组,双指针 + +https://leetcode.cn/problems/squares-of-a-sorted-array/solutions/2806253/xiang-xiang-shuang-zhi-zhen-cong-da-dao-blda6 + +```c++ +class Solution { +public: + vector sortedSquares(vector& nums) { + int len = nums.size(); + vector res(len); + int l = 0, r = len - 1, p = len - 1; + while (p>= 0) { + int a = nums[l] * nums[l], b = nums[r] * nums[r]; + if (a> b) { + res[p--] = a; + l++; + } else { + res[p--] = b; + r--; + } + } + return res; + } +}; +``` + + +## 长度最小的子数组 + +[LeetCode中文](https://leetcode.cn/problems/minimum-size-subarray-sum/description/) + +题解:滑动串口,注意边界值 + +```c++ +class Solution { +public: + int minSubArrayLen(int target, vector& nums) { + if (nums.empty()) return 0; + int l = 0, r = 0, tmp = 0; + int res = INT_MAX; + while (l <= r && r <= nums.size()) { + if (tmp < target) { + if (r < nums.size()) tmp += nums[r]; + r++; + continue; + } else { + res = min(res, r - l); + while (l <= r && tmp>= target) { + res = min(res, r - l); + tmp = tmp - nums[l]; + l++; + } + } + } + return res == INT_MAX ? 0 : res; + } +}; +``` + +## 数组中重复的数字 + +[LeetCode中文](https://leetcode.cn/problems/find-all-duplicates-in-an-array/description/) + +题解:遍历数组交换 + 标记重复的数字 + +https://leetcode.cn/problems/find-all-duplicates-in-an-array/solutions/1476879/by-ac_oier-0m3c/ + +```c++ +class Solution { +public: + vector findDuplicates(vector& nums) { + vector res; + int i = 0; + while (i < nums.size()) { + if (nums[i] == 0 || nums[i] == i + 1) { + ++i; + continue; + } + else if (nums[nums[i] - 1] == nums[i]) { + res.push_back(nums[i]); + nums[i] = 0; + ++i; + } else { + int tmp = nums[nums[i] - 1]; + nums[nums[i] - 1] = nums[i]; + nums[i] = tmp; + } + } + return res; + } +}; +``` + ## 两个数组的交集 [LeetCode中文](https://leetcode-cn.com/problems/intersection-of-two-arrays/) @@ -2888,3 +3028,56 @@ class Solution: return out_list ``` +# 多数元素 + +[LeetCode中文](https://leetcode.cn/problems/majority-element-ii/) + +### 解答 + +摩尔投票法 +要求> N / k为众数,则这样的数最多有k - 1个 因此维护两个候选变量cand1和cand2 两个阶段,抵消 + 计数 cand1和cand2的初始值任意,这里设为0 只要发现有计数为0,则马上更新新的cand 每考察到一个数,只有以下5种情况 + +是cand1,则计数器++ +是cand2,则计数器++ +都不是,且两个计数器都> 0,则两个计数器-- +都不是,且count1是0,则让新的数成为cand1,且count1++ +都不是,且count2是0,则让新的数成为cand2,且count2++ +易错点 +本质上是三个一比 +当且仅当碰到的数既不是cand1也不是cand2,且两个计数器都> 0时,计数器才会--,其他情况一律不减 +cand1和cand2可能一样,所以在计数阶段每个数只能算到一个候选人头上,即必须用if else语句 + + +```c++ +class Solution { +public: + vector majorityElement(vector& nums) { + vector res{}; + int res1, res2, cnt1, cnt2; + res1 = res2 = cnt1 = cnt2 = 0; + for (const auto& a : nums) { + if (a == res1) ++cnt1; + else if (a == res2) ++cnt2; + else if (cnt1> 0 && cnt2> 0) { + --cnt1; + --cnt2; + } else if (cnt1 == 0) { + res1 = a; + ++cnt1; + } else { + res2 = a; + ++cnt2; + } + } + cnt1 = 0, cnt2 = 0; + for (const auto& a : nums) { + if (a == res1) ++cnt1; + else if (a == res2) ++cnt2; + } + if (cnt1> nums.size() / 3) res.push_back(res1); + if (cnt2> nums.size() / 3) res.push_back(res2); + return res; + } +}; +``` + diff --git "a/LeetCode/346円240円210円345円222円214円351円230円237円345円210円227円.md" "b/LeetCode/346円240円210円345円222円214円351円230円237円345円210円227円.md" index 4978619..e830438 100644 --- "a/LeetCode/346円240円210円345円222円214円351円230円237円345円210円227円.md" +++ "b/LeetCode/346円240円210円345円222円214円351円230円237円345円210円227円.md" @@ -4,12 +4,149 @@ * [有效的括号](#有效的括号) (`easy`) * [逆波兰表达式求值](#逆波兰表达式求值) (`medium`) * [简化路径](#简化路径) (`medium`) + * [基本计算器II](#基本计算器II) (`medium`) + * [基本计算器III](#基本计算器III) (`hard`) * **队列** * [设计循环队列](#设计循环队列) (`medium`) * [设计循环双端队列](#设计循环双端队列) (`medium`) * [滑动窗口最大值](#滑动窗口最大值) (`hard` `滑动窗口` `双向队列`) # 栈和队列总结 + +# 基本计算器III + +[LeetCode中文](https://leetcode.cn/problems/basic-calculator-iii/description/) + +通用解法:https://leetcode.cn/problems/basic-calculator/solutions/646865/shuang-zhan-jie-jue-tong-yong-biao-da-sh-olym/ + +```c++ +class Solution { +public: + int calculate(string s) { + mp['+'] = 1; + mp['-'] = 1; + mp['*'] = 2; + mp['/'] = 2; + stack ops; + ops.push('('); + stack nums; + int num = 0; + int len = s.size(); + for (int i = 0; i < len; ++i) { + char ch = s[i]; + if (ch == ' ') { + continue; + } else if (ch == '(') { + ops.push(ch); + } else if (isNum(ch)) { + num = 0; + while (i < len && isNum(s[i])) { + num = num * 10 + (s[i] - '0'); + ++i; + } + nums.push(num); + --i; + } else { + calc(ops, nums, ch); + if (ch != ')') { + ops.push(ch); + } + } + } + calc(ops, nums, ')'); + return nums.empty() ? 0 : nums.top(); + } + bool isNum(char ch) { + return ch>= '0' && ch <= '9'; + } + void calc(stack& ops, stack& nums, char ch) { + if (nums.size() < 2) return; + if (ops.empty()) return; + while (!ops.empty() && ops.top() != '(' && nums.size()>= 2 && (ch == ')' || mp[ops.top()]>= mp[ch])) { + int num2 = nums.top(); + nums.pop(); + int num1 = nums.top(); + nums.pop(); + char op = ops.top(); + ops.pop(); + nums.push(getByOp(num1, num2, op)); + } + if (ch == ')' && !ops.empty() && ops.top() == '(') { + ops.pop(); + } + } + int getByOp(int num1, int num2, char op) { + int num = 0; + if (op == '+') { + num = num1 + num2; + } else if (op == '-') { + num = num1 - num2; + } else if (op == '*') { + num = num1 * num2; + } else { + num = num1 / num2; + } + return num; + } + +private: + unordered_map mp; +}; +``` + +# 基本计算器II + +[LeetCode中文](https://leetcode.cn/problems/basic-calculator-ii/description/) + +题解:https://leetcode.cn/problems/basic-calculator-ii/solutions/648647/ji-ben-ji-suan-qi-ii-by-leetcode-solutio-cm28/ + +```c++ +class Solution { +public: + int calculate(string s) { + stack sta; + char flag = '$'; + int num = 0; + for (char ch : s) { + if (ch == ' ') continue; + else if (ch>= '0' && ch <= '9') { + num = num * 10 + (ch - '0'); + } else { + if (flag == '-') { + num = -1 * num; + } else if (flag == '*') { + num = sta.top() * num; + sta.pop(); + } else if (flag == '/') { + num = sta.top() / num; + sta.pop(); + } + flag = ch; + sta.push(num); + num = 0; + } + } + if (flag == '-') { + num = -1 * num; + } else if (flag == '*') { + num = sta.top() * num; + sta.pop(); + } else if (flag == '/') { + num = sta.top() / num; + sta.pop(); + } + sta.push(num); + + int res = 0; + while (!sta.empty()) { + res += sta.top(); + sta.pop(); + } + return res; + } +}; +``` + ## 最小栈 [LeetCode中文](https://leetcode-cn.com/problems/min-stack/) @@ -99,6 +236,48 @@ private: */ ``` +**优化**:只使用一个栈 + +思路:[解答](https://leetcode.cn/problems/min-stack/solutions/42521/xiang-xi-tong-su-de-si-lu-fen-xi-duo-jie-fa-by-38/) 中的解答二 + +```c++ +class MinStack { +public: + MinStack() { + min_element = INT_MAX; + } + + void push(int val) { + if (val <= min_element) { + sta.push(min_element); + min_element = val; + } + sta.push(val); + } + + void pop() { + if (sta.top() == min_element) { + sta.pop(); + min_element = sta.top(); + } + sta.pop(); + } + + int top() { + return sta.top(); + } + + int getMin() { + return min_element; + } + +private: + stack sta; + int min_element; +}; + +``` + ## 有效的括号 @@ -772,4 +951,4 @@ public: return res; } }; -``` \ No newline at end of file +``` diff --git "a/LeetCode/346円250円241円346円213円237円.md" "b/LeetCode/346円250円241円346円213円237円.md" new file mode 100644 index 0000000..2de3cae --- /dev/null +++ "b/LeetCode/346円250円241円346円213円237円.md" @@ -0,0 +1,35 @@ +* **模拟总结** + * [小行星碰撞](#小行星碰撞) (`栈`) + + +# 模拟总结 + +## 小行星碰撞 + +[LeetCode中文](https://leetcode.cn/problems/asteroid-collision/description/) + +题解:https://leetcode.cn/problems/asteroid-collision/solutions/1663442/xing-xing-peng-zhuang-by-leetcode-soluti-u3k0 + +使用栈来模拟行星运动 + +```c++ +class Solution { +public: + vector asteroidCollision(vector& asteroids) { + vector sta; + for (int aid : asteroids) { + bool alive = true; + while (alive && !sta.empty() && aid < 0 && sta.back()> 0) { + alive = sta.back() < -1 * aid; + if (sta.back() <= -1 * aid) { + sta.pop_back(); + } + } + if (alive) { + sta.push_back(aid); + } + } + return sta; + } +}; +``` diff --git "a/LeetCode/350円256円260円345円277円206円345円214円226円346円220円234円347円264円242円.md" "b/LeetCode/350円256円260円345円277円206円345円214円226円346円220円234円347円264円242円.md" new file mode 100644 index 0000000..e688cdb --- /dev/null +++ "b/LeetCode/350円256円260円345円277円206円345円214円226円346円220円234円347円264円242円.md" @@ -0,0 +1,98 @@ +**记忆化搜索** + * [打家劫舍III](#打家劫舍III) (`medium` `树形动态规划`) + * [路径总和III](#路径总和III) (`medium` `前缀和`) + +# 记忆化搜索 + +## 路径总和III + +[LeetCode中文](https://leetcode.cn/problems/path-sum-iii/) + + +```c++ +/** + * Definition for a binary tree node. + * struct TreeNode { + * int val; + * TreeNode *left; + * TreeNode *right; + * TreeNode() : val(0), left(nullptr), right(nullptr) {} + * TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} + * TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {} + * }; + */ +class Solution { +public: + int pathSum(TreeNode* root, int targetSum) { + unordered_map mp; // key: 前缀和 value: 前缀和等于key的节点数量 + long prefix_sum = 0; + return pathSumImpl(root, targetSum, mp, prefix_sum); + } + int pathSumImpl(TreeNode* root, int target, unordered_map& mp, long prefix_sum) { + if (root == nullptr) return 0; + int res = 0; + prefix_sum += root->val; + if (prefix_sum == target) res += 1; + if (mp.find(prefix_sum - target) != mp.end()) { + res += mp[prefix_sum - target]; + } + mp[prefix_sum]++; + res += pathSumImpl(root->left, target, mp, prefix_sum); + res += pathSumImpl(root->right, target, mp, prefix_sum); + mp[prefix_sum]--; + return res; + } +}; +``` + +## 打家劫舍III + +[LeetCode中文](https://leetcode.cn/problems/house-robber-iii/description/) + +解法:树形动态规划 + 记忆化搜索 + +题解: https://leetcode.cn/problems/house-robber-iii/solutions/361038/da-jia-jie-she-iii-by-leetcode-solution/ + +打家劫舍系列扩展:https://leetcode.cn/problems/house-robber-iii/solutions/67467/tong-yong-si-lu-tuan-mie-da-jia-jie-she-wen-ti-b-2/ + +```c++ +/** + * Definition for a binary tree node. + * struct TreeNode { + * int val; + * TreeNode *left; + * TreeNode *right; + * TreeNode() : val(0), left(nullptr), right(nullptr) {} + * TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} + * TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {} + * }; + */ +class Solution { +public: + int rob(TreeNode* root) { + return max(pickRecursive(root), unpickRecursive(root)); + } + int pickRecursive(TreeNode* root) { + if (!root) return 0; + if (pick_mp.find(root) != pick_mp.end()) { + return pick_mp[root]; + } + int res = root->val + unpickRecursive(root->left) + unpickRecursive(root->right); + pick_mp[root] = res; + return res; + } + int unpickRecursive(TreeNode* root) { + if (!root) return 0; + if (unpicked_mp.find(root) != unpicked_mp.end()) { + return unpicked_mp[root]; + } + int res = max(pickRecursive(root->left), unpickRecursive(root->left)) + max(pickRecursive(root->right), unpickRecursive(root->right)); + unpicked_mp[root] = res; + return res; + } + +private: + unordered_map
pick_mp; + unordered_map
unpicked_mp; +}; +``` diff --git "a/LeetCode/350円256円276円350円256円241円.md" "b/LeetCode/350円256円276円350円256円241円.md" index 2a5880f..84d8fe9 100644 --- "a/LeetCode/350円256円276円350円256円241円.md" +++ "b/LeetCode/350円256円276円350円256円241円.md" @@ -1,6 +1,6 @@ * **设计类总结** * [常数时间插入、删除和获取随机元素](#常数时间插入删除和获取随机元素) (`medium` `哈希`) - * [LRU缓存机制](#LRU缓存机制) (`hard` `系统设计` `哈希`) + * [LRU缓存机制](#LRU缓存机制) (`medium` `系统设计` `哈希`) # 设计类总结 diff --git "a/LeetCode/350円264円252円345円277円203円.md" "b/LeetCode/350円264円252円345円277円203円.md" index dcd6176..4778b5c 100644 --- "a/LeetCode/350円264円252円345円277円203円.md" +++ "b/LeetCode/350円264円252円345円277円203円.md" @@ -6,18 +6,205 @@ * [任务调度器](#任务调度器) (`medium`) * [分割数组为连续子序列](#分割数组为连续子序列) (`medium`) * [跳跃游戏](#跳跃游戏) (`medium` `动态规划`) + * [跳跃游戏II](#跳跃游戏II) (`medium` `贪心`) * [使数组唯一的最大增量](#使数组唯一的最大增量) (`medium` ) * [分发糖果](#分发糖果) (`hard`) * [根据身高重建队列](#根据身高重建队列) (`medium`) * [救生艇](#救生艇) (`medium`) * [用最少数量的箭引爆气球](#用最少数量的箭引爆气球) (`medium`) - * [移掉K位数字](#移掉K位数字) (`medium`) - * [无重叠区间](#无重叠区间) (`medium`) + * [移掉K位数字](#移掉K位数字) (`medium` `单调栈`) * [加油站](#加油站) (`medium`) - + * [去除重复字母](#去除重复字母) (`贪心` `哈希` `单调栈`) + * [任务调度器](#任务调度器) (`贪心` `哈希表`) + * 区间问题 + * [无重叠区间](#无重叠区间) (`medium`) + * [合并区间](https://github.com/Miller-Xie/Code/blob/master/LeetCode/%E6%95%B0%E7%BB%84.md#%E5%90%88%E5%B9%B6%E5%8C%BA%E9%97%B4) (`medium`) + * [环形子数组的最大和](https://github.com/Miller-Xie/Code/blob/master/LeetCode/%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92.md#%E7%8E%AF%E5%BD%A2%E5%AD%90%E6%95%B0%E7%BB%84%E7%9A%84%E6%9C%80%E5%A4%A7%E5%92%8C) (`medium`) + * 括号问题 + * [判断一个括号字符串是否有效](#判断一个括号字符串是否有效) (`medium`) + * [有效的括号字符串](#有效的括号字符串) (`栈` `贪心` `dp`) + # 贪心总结 + +# 有效的括号字符串 + +[LeetCode中文](https://leetcode.cn/problems/valid-parenthesis-string/description/) + +题解:两种方法 + +* 动态规划 +* 贪心 + 栈 + +相关题解:https://leetcode.cn/problems/valid-parenthesis-string/solutions/992347/you-xiao-de-gua-hao-zi-fu-chuan-by-leetc-osi3 + +```c++ +class Solution { +public: + bool checkValidString(string s) { + int cnt = 0; + stack sta1, sta2; + for (int i = 0; i < s.size(); i++) { + char ch = s[i]; + if (ch == '*') sta2.push(i); + else if (ch == '(') { + sta1.push(i); + } else { + if (sta1.empty() && sta2.empty()) return false; + if (!sta1.empty()) sta1.pop(); + else { + sta2.pop(); + } + } + } + if (sta1.empty()) return true; + if (sta2.size() < sta1.size()) return false; + while (!sta1.empty() && !sta2.empty()) { + int a = sta1.top(), b = sta2.top(); + if (a> b) return false; + sta1.pop(); + sta2.pop(); + } + return true; + } +}; +``` + + +# 任务调度器 + +[LeetCode中文](https://leetcode.cn/problems/task-scheduler/description/) + +题解:桶的思想,参考 https://leetcode.cn/problems/task-scheduler/solutions/196302/tong-zi-by-popopop + +```c++ +class Solution { +public: + int leastInterval(vector& tasks, int n) { + int max_cnt = 0, len = tasks.size(); + vector mp(26, 0); + for (auto& task : tasks) { + mp[task - 'A']++; + max_cnt = max(max_cnt, mp[task - 'A']); + } + int max_task_num = 0, task_num = 0; + for (auto& a : mp) { + if (a == max_cnt) max_task_num++; + if (a> 0) task_num++; + } + int res = (max_cnt - 1) * (n + 1) + max_task_num; + return max(len, res); + } +}; +``` + + +## 跳跃游戏II + +[LeetCode中文](https://leetcode.cn/problems/jump-game-ii/description/) + +题解:和 [跳跃游戏](#跳跃游戏) 贪心方法类似,但是需要选择下一步的位置,来保证走得最远,在走的过程中记录步数 + +```c++ +class Solution { +public: + int jump(vector& nums) { + if (nums.size() <= 1) return 0; + int len = nums.size(); + int Max = 0 + nums[0]; + if (Max>= len - 1) return 1; + int step = 0; + int idx = 0; + while (idx < len) { + int end = idx + nums[idx]; + for (int i = idx + 1; i <= end && i < len; i++) { + if (i + nums[i]> Max) { + idx = i; + Max = i + nums[i]; + } + } + step++; + if (Max>= len - 1) { + step++; + break; + } + } + return step; + } +}; +``` + + +## 去除重复字母 + +[LeetCode中文](https://leetcode.cn/problems/remove-duplicate-letters/description/) + +利用字母的相对大小比较来选择是否丢弃字母,贪心思路比较巧妙 + +https://leetcode.cn/problems/remove-duplicate-letters/solutions/290200/yi-zhao-chi-bian-li-kou-si-dao-ti-ma-ma-zai-ye-b-4/ + +和 [移掉K位数字](#移掉K位数字) 思路很相似,主要区别是需要考虑重复元素,在从左往右遍历的时候,如果当前元素cur已经在stack中存在,直接丢弃 + +```c++ +class Solution { +public: + string removeDuplicateLetters(string s) { + string res; + unordered_set st; + unordered_map mp; + for (const char& ch : s) { + ++mp[ch]; + } + for (const char ch : s) { + if (st.find(ch) != st.end()) { + --mp[ch]; + continue; + } + while (!res.empty() && res.back()> ch && mp[res.back()]> 1) { + char top = res.back(); + res.pop_back(); + --mp[top]; + st.erase(top); + } + res.push_back(ch); + st.insert(ch); + } + return res; + } +}; +``` + +## 判断一个括号字符串是否有效 + +[LeetCode中文](https://leetcode.cn/problems/check-if-a-parentheses-string-can-be-valid/description/) + +题解: https://leetcode.cn/problems/check-if-a-parentheses-string-can-be-valid/solutions/1176807/qian-hou-ge-bian-li-yi-ci-fen-bie-pan-du-w5nu/ + +```c++ +class Solution { +public: + bool canBeValid(string s, string locked) { + if(s.size() % 2 == 1) return false; + int r = 0, l = 0; + for (int i = 0; i < s.size(); ++i) { + if (s[i] == ')' && locked[i] == '1') { + ++r; + if (i + 1 < 2 * r) return false; + } + } + int n = s.size(); + for (int i = n - 1; i>= 0; --i) { + if (s[i] == '(' && locked[i] == '1') { + ++l; + if (n - i < 2 * l) return false; + } + } + return true; + } +}; +``` + ## 买卖股票的最佳时机II [LeetCode中文](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-ii/) @@ -644,6 +831,12 @@ public: ### 解答 +通俗解释:矮个子插队,高个子看不见;所以我们可以先安排高个子的位置,再通过插队的方式安排矮个子的位置 + +详细处理:对数组排序,第一个元素(身高)降序,第二个元素(人数)升序,然后遍历数组,根据第二个元素(人数)插入到结果数组 + +题解:https://leetcode.cn/problems/queue-reconstruction-by-height/solutions/486493/xian-pai-xu-zai-cha-dui-dong-hua-yan-shi-suan-fa-g + **C++代码** ```c++ diff --git "a/LeetCode/351円200円222円345円275円222円.md" "b/LeetCode/351円200円222円345円275円222円.md" new file mode 100644 index 0000000..ff547dd --- /dev/null +++ "b/LeetCode/351円200円222円345円275円222円.md" @@ -0,0 +1,55 @@ +* **递归总结** + * [字符串解码](#字符串解码) (`递归` `回溯`) + + +# 递归总结 + +## 字符串解码 + +[LeetCode中文](https://leetcode.cn/problems/decode-string/description/) + + + +```c++ +class Solution { +public: + string recursion(const string& s, int idx) { + if (idx>= s.size()) return ""; + stack> sta; + string res; + while (idx < s.size()) { + if (s[idx]>= '1' && s[idx] <= '9') { + int a = 0; + while (idx < s.size() && (s[idx]>= '0' && s[idx] <= '9')) { + a = a * 10 + (s[idx] - '0'); + idx++; + } + if (s[idx] == '[') { + sta.push({res, a}); + idx++; + res = ""; + } + } else if (s[idx] == ']') { + auto pr = sta.top(); + sta.pop(); + idx++; + string tmp = pr.first; + for (int i = 0; i < pr.second; i++) { + tmp += res; + } + res = tmp; + } else if (s[idx]>= 'a' && s[idx] <= 'z') { + while (idx < s.size() && (s[idx]>= 'a' && s[idx] <= 'z')) { + res += s[idx]; + idx++; + } + } + } + return res; + } + string decodeString(string s) { + int idx = 0; + return recursion(s, idx); + } +}; +``` diff --git "a/LeetCode/351円223円276円350円241円250円.md" "b/LeetCode/351円223円276円350円241円250円.md" index f8ef0f5..02768ca 100644 --- "a/LeetCode/351円223円276円350円241円250円.md" +++ "b/LeetCode/351円223円276円350円241円250円.md" @@ -1,5 +1,6 @@ * **链表类总结** * [反转链表](#反转链表) (`easy` `三指针` `dummy节点`) + * [反转链表II](#反转链表II) (`medium` `双指针`) * [合并两个有序链表](#合并两个有序链表) (`easy` `双指针`) * [删除排序链表中的重复元素](#删除排序链表中的重复元素) (`easy` `双指针`) * [回文链表](#回文链表) (`easy` `反转链表`) @@ -12,10 +13,127 @@ * [两数相加](#两数相加) (`medium` `数学`) * [删除链表的倒数第N个节点](#删除链表的倒数第N个节点) (`medium`) * [复制带随机指针的链表](#复制带随机指针的链表) (`medium`) + * [k个一组翻转链表](#k个一组翻转链表) (`hard`) # 链表类总结 + +## k个一组翻转链表 + +[LeetCode中文](https://leetcode.cn/problems/reverse-nodes-in-k-group/description/) + +解答:翻转链表的进阶版每翻转一组,都需要实时维护当前头结点cur_head、当前尾结点cur_tail和下一组头结点cur_head,通过cur、next、pre三个变量实现每组的翻转 + +```c++ +/** + * Definition for singly-linked list. + * struct ListNode { + * int val; + * ListNode *next; + * ListNode() : val(0), next(nullptr) {} + * ListNode(int x) : val(x), next(nullptr) {} + * ListNode(int x, ListNode *next) : val(x), next(next) {} + * }; + */ +class Solution { +public: + ListNode* reverseKGroup(ListNode* head, int k) { + ListNode *next_head = head, *cur = head, *pre = nullptr, *next = nullptr, *cur_head = head, *cur_tail = nullptr; + int cnt = 1; + while (cnt < k && cur_head && cur_head->next) { + cur_head = cur_head->next; + cnt++; + } + next_head = cur_head->next; + ListNode node(0, nullptr), *dummy = &node; + dummy->next = cur_head; + while (cur) { + if (cur_tail) { + cur_tail->next = cur_head; + } + cur_tail = cur; + while (pre != cur_head) { + next = cur->next; + cur->next = pre; + pre = cur; + cur = next; + } + cur = next_head; + if (!cur) break; + cur_head = cur; + int cnt = 1; + while (cnt < k && cur_head && cur_head->next) { + cur_head = cur_head->next; + cnt++; + } + if (cnt < k) { + cur_tail->next = cur; + break; + } + next_head = cur_head->next; + pre = nullptr; + next = nullptr; + } + return node.next; + } +}; +``` + +## 反转链表II + +[LeetCode中文](https://leetcode.cn/problems/reverse-linked-list-ii/) + +```c++ +/** + * Definition for singly-linked list. + * struct ListNode { + * int val; + * ListNode *next; + * ListNode() : val(0), next(nullptr) {} + * ListNode(int x) : val(x), next(nullptr) {} + * ListNode(int x, ListNode *next) : val(x), next(next) {} + * }; + */ +class Solution { +public: + ListNode* reverseBetween(ListNode* head, int left, int right) { + ListNode *l_pre = nullptr, *r_next = nullptr, *l_p, *r_p; + ListNode* p = head; + int idx = 1; + while (p) { + if (idx == left) { + l_p = p; + } + if (idx + 1 == left) { + l_pre = p; + } + if (idx == right) { + r_p = p; + r_next = p->next; + break; + } + p = p->next; + idx++; + } + if (!l_p || !r_p) return head; + ListNode *pre = r_next, *cur = l_p, *next = nullptr; + while (pre != r_p) { + next = cur->next; + cur->next = pre; + pre = cur; + cur = next; + } + if (l_pre) { + l_pre->next = pre; + return head; + } + return pre; + } +}; +``` + + ## 反转链表 [LeetCode中文](https://leetcode-cn.com/problems/reverse-linked-list/) @@ -1341,56 +1459,42 @@ public: > 参考**剑指Offer 第二版** 面试题22: 链表中倒数第k个节点 -* python代码 - -```python -# Definition for singly-linked list. -# class ListNode: -# def __init__(self, x): -# self.val = x -# self.next = None +* C++代码 -class Solution: - def removeNthFromEnd(self, head, n): - """ - :type head: ListNode - :type n: int - :rtype: ListNode - """ - if not head: - return None - - l = 0 - p = head - while p: - p = p.next - l += 1 - - if l < n: - return head - if l == 1: - return None - - """特殊情况:删除末尾元素""" - if n == 1: - p = head - while p.next.next: - p = p.next - #p.val = p.next.val - p.next = p.next.next - return head - - p1 = p2 = head - cnt = 1 - while p1.next and p2.next: - if cnt>= n: - p1 = p1.next - cnt += 1 - p2 = p2.next - - p1.val = p1.next.val - p1.next = p1.next.next - return head +```c++ +/** + * Definition for singly-linked list. + * struct ListNode { + * int val; + * ListNode *next; + * ListNode() : val(0), next(nullptr) {} + * ListNode(int x) : val(x), next(nullptr) {} + * ListNode(int x, ListNode *next) : val(x), next(next) {} + * }; + */ +class Solution { +public: + ListNode* removeNthFromEnd(ListNode* head, int n) { + if (!head) return head; + ListNode node(0), *pre = &node; + pre->next = head; + ListNode *slow = pre, *fast = pre; + for (int i = 1; i <= n; ++i) { + if (fast) { + fast = fast->next; + } else { + break; + } + } + if (!fast) return nullptr; + while (slow->next && fast->next) { + fast = fast->next; + slow = slow->next; + } + slow->next = slow->next->next; + return node.next; + } +}; ``` @@ -1405,7 +1509,7 @@ class Solution: 要求返回这个链表的深度拷贝。 -### 解答 +### 解法1 > 参考**剑指Offer 第二版** 面试题35:复杂链表的复制 @@ -1473,3 +1577,47 @@ public: } }; ``` +### 解法2 + +哈希表 + +```c++ +/* +// Definition for a Node. +class Node { +public: + int val; + Node* next; + Node* random; + + Node(int _val) { + val = _val; + next = NULL; + random = NULL; + } +}; +*/ + +class Solution { +public: + Node* copyRandomList(Node* head) { + unordered_map mp; + Node* cur = head; + Node node(0); + Node* dummy = &node; + while (cur) { + Node* tmp = new Node(cur->val); + dummy->next = tmp; + dummy = dummy->next; + mp.insert({cur, tmp}); + cur = cur->next; + } + cur = head; + while (cur) { + mp[cur]->random = mp[cur->random]; + cur = cur->next; + } + return node.next; + } +}; +``` diff --git a/README.md b/README.md index f6530ff..ba06397 100644 --- a/README.md +++ b/README.md @@ -15,15 +15,22 @@ * [堆](https://github.com/xcg1995/Code/blob/master/LeetCode/堆.md) * [线段树](https://github.com/xcg1995/Code/blob/master/LeetCode/线段树.md) * [字典树](https://github.com/xcg1995/Code/blob/master/LeetCode/字典树.md) + * [单调栈](https://github.com/xcg1995/Code/blob/master/LeetCode/单调栈.md) + * [单调队列](https://github.com/Miller-Xie/Code/blob/master/LeetCode/%E5%8D%95%E8%B0%83%E9%98%9F%E5%88%97.md) + * [前缀和](https://github.com/Miller-Xie/Code/blob/master/LeetCode/前缀和.md) * **算法** * [二分查找](https://github.com/xcg1995/Code/blob/master/LeetCode/二分查找.md) * [排序](https://github.com/xcg1995/Code/blob/master/LeetCode/排序.md) + * [递归](https://github.com/Miller-Xie/Code/blob/master/LeetCode/%E9%80%92%E5%BD%92.md) * [动态规划](https://github.com/xcg1995/Code/blob/master/LeetCode/动态规划.md) + * [分治](https://github.com/Miller-Xie/Code/blob/master/LeetCode/%E5%88%86%E6%B2%BB.md) + * [记忆化搜索](https://github.com/Miller-Xie/Code/blob/master/LeetCode/%E8%AE%B0%E5%BF%86%E5%8C%96%E6%90%9C%E7%B4%A2.md) * [贪心](https://github.com/xcg1995/Code/blob/master/LeetCode/贪心.md) * [回溯](https://github.com/xcg1995/Code/blob/master/LeetCode/回溯.md) * [位运算](https://github.com/Making-It/Code/blob/master/LeetCode/位运算.md) * [数学](https://github.com/xcg1995/Code/blob/master/LeetCode/数学.md) * [设计](https://github.com/xcg1995/Code/blob/master/LeetCode/设计.md) + * [模拟](https://github.com/Miller-Xie/Code/blob/master/LeetCode/%E6%A8%A1%E6%8B%9F.md) * [其他](https://github.com/xcg1995/Code/blob/master/LeetCode/其他.md) * **数据库** * [mysql](https://github.com/xcg1995/Code/blob/master/LeetCode/数据库.md)

AltStyle によって変換されたページ (->オリジナル) /