From ea0d3d2aff71ff21a4212f8a30652d8eb7ccddfc Mon Sep 17 00:00:00 2001 From: youngyangyang04 <826123027@qq.com> Date: Sat, 12 Dec 2020 17:37:38 +0800 Subject: [PATCH] Update --- README.md | 4 ++ problems/0416.分割等和子集.md | 51 +++++++++++++- problems/0473.火柴拼正方形.md | 42 +++++++++++ problems/0649.Dota2参议院.md | 70 +++++++++++++++++++ problems/0698.划分为k个相等的子集.md | 42 +++++++++++ 5 files changed, 208 insertions(+), 1 deletion(-) create mode 100644 problems/0473.火柴拼正方形.md create mode 100644 problems/0649.Dota2参议院.md create mode 100644 problems/0698.划分为k个相等的子集.md diff --git a/README.md b/README.md index 59c095d9..96d112a0 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,7 @@ * [关于时间复杂度,你不知道的都在这里!](https://mp.weixin.qq.com/s/LWBfehW1gMuEnXtQjJo-sw) * [O(n)的算法居然超时了,此时的n究竟是多大?](https://mp.weixin.qq.com/s/73ryNsuPFvBQkt6BbhNzLA) * [通过一道面试题目,讲一讲递归算法的时间复杂度!](https://mp.weixin.qq.com/s/I6ZXFbw09NR31F5CJR_geQ) + * [本周小结!(算法性能分析系列一)](https://mp.weixin.qq.com/s/5m8xDbGUeGgYJsESeg5ITQ) * 数组 * [必须掌握的数组理论知识](https://mp.weixin.qq.com/s/X7R55wSENyY62le0Fiawsg) @@ -327,6 +328,7 @@ |[0454.四数相加II](https://github.com/youngyangyang04/leetcode/blob/master/problems/0454.四数相加II.md) |哈希表 |中等| **哈希**| |[0455.分发饼干](https://github.com/youngyangyang04/leetcode/blob/master/problems/0455.分发饼干.md) |贪心 |简单| **贪心**| |[0459.重复的子字符串](https://github.com/youngyangyang04/leetcode/blob/master/problems/0459.重复的子字符串.md) |字符创 |简单| **KMP**| +|[0473.火柴拼正方形](https://github.com/youngyangyang04/leetcode/blob/master/problems/0473.火柴拼正方形.md) |深度优先搜索|中等| **回溯算法** 和698.划分为k个相等的子集差不多| |[0474.一和零](https://github.com/youngyangyang04/leetcode/blob/master/problems/0474.一和零.md) |动态规划 |中等| **多重背包** 好题目| |[0486.预测赢家](https://github.com/youngyangyang04/leetcode/blob/master/problems/0486.预测赢家.md) |动态规划 |中等| **递归** **记忆递归** **动态规划**| |[0491.递增子序列](https://github.com/youngyangyang04/leetcode/blob/master/problems/0491.递增子序列.md) |深度优先搜索 |中等|**深度优先搜索/回溯算法** 这个去重有意思| @@ -345,9 +347,11 @@ |[0590.N叉树的后序遍历](https://github.com/youngyangyang04/leetcode/blob/master/problems/0590.N叉树的后序遍历.md) |N叉树 |简单|**递归** **栈/迭代**| |[0617.合并二叉树](https://github.com/youngyangyang04/leetcode/blob/master/problems/0617.合并二叉树.md) |树 |简单|**递归** **迭代**| |[0637.二叉树的层平均值](https://github.com/youngyangyang04/leetcode/blob/master/problems/0637.二叉树的层平均值.md) |树 |简单|**广度优先搜索/队列**| +|[0649.Dota2参议院](https://github.com/youngyangyang04/leetcode/blob/master/problems/0649.Dota2参议院.md) |贪心 |简单|**贪心算法** 简单的贪心策略但代码实现很有技巧| |[0654.最大二叉树](https://github.com/youngyangyang04/leetcode/blob/master/problems/0654.最大二叉树.md) |树 |中等|**递归**| |[0685.冗余连接II](https://github.com/youngyangyang04/leetcode/blob/master/problems/0685.冗余连接II.md) | 并查集/树/图 |困难|**并查集**| |[0669.修剪二叉搜索树](https://github.com/youngyangyang04/leetcode/blob/master/problems/0669.修剪二叉搜索树.md) | 二叉搜索树/二叉树 |简单|**递归** **迭代**| +|[0698.划分为k个相等的子集](https://github.com/youngyangyang04/leetcode/blob/master/problems/0698.划分为k个相等的子集.md) |回溯算法|中等|动态规划 **回溯算法** 这其实是组合问题,使用了两次递归,好题| |[0700.二叉搜索树中的搜索](https://github.com/youngyangyang04/leetcode/blob/master/problems/0700.二叉搜索树中的搜索.md) |二叉搜索树 |简单|**递归** **迭代**| |[0701.二叉搜索树中的插入操作](https://github.com/youngyangyang04/leetcode/blob/master/problems/0701.二叉搜索树中的插入操作.md) |二叉搜索树 |简单|**递归** **迭代**| |[0705.设计哈希集合](https://github.com/youngyangyang04/leetcode/blob/master/problems/0705.设计哈希集合.md) |哈希表 |简单|**模拟**| diff --git a/problems/0416.分割等和子集.md b/problems/0416.分割等和子集.md index 1dc1b86a..9cf5d0c2 100644 --- a/problems/0416.分割等和子集.md +++ b/problems/0416.分割等和子集.md @@ -1,6 +1,11 @@ -和Leetcode 473:火柴拼正方形和Leetcode 698:划分为k个相等的子集是 +* 473. 火柴拼正方形 (回溯算法) +* 698. 划分为k个相等的子集 + +一起再回忆一下回溯算法 + +|[0473.火柴拼正方形](https://github.com/youngyangyang04/leetcode/blob/master/problems/0473.火柴拼正方形.md) |深度优先搜索|中等| **回溯算法** 和698.划分为k个相等的子集差不多| ## 思路 @@ -82,6 +87,8 @@ public: 本来是想用回溯暴力搜索出所有答案的,各种剪枝,还是超时了,不想在调了,放弃回溯,直接上01背包吧。 +**需要尝试一下记忆化递归** + 回溯搜索超时的代码如下: ``` @@ -119,3 +126,45 @@ public: }; ``` + +``` +class Solution { +private: + bool backtracking(vector& nums, + int k, + int target, // 子集目标和 + int cur, // 当前目标和 + int startIndex, // 起始位置 + vector& used) { // 标记是否使用过 + if (k == 0) return true; // 找到了k个相同子集 + if (cur == target) { // 发现一个合格子集,然后重新开始寻找 + return backtracking(nums, k - 1, target, 0, 0, used); // k-1 + } + for (int i = startIndex; i < nums.size(); i++) { + if (cur + nums[i] <= target && !used[i]) { + used[i] = true; + if (backtracking(nums, k, target, cur + nums[i], i + 1, used)) { + return true; + } + used[i] = false; + } + } + return false; + } + +public: + bool canPartition(vector& nums) { + if (nums.size() < 2) return false; // 火柴数量小于4凑不上正方形 + int sum = 0; + for (int i = 0; i < nums.size(); i++) { + sum += nums[i]; + } + if (sum % 2 != 0) return false; + int target = sum / 2; + vector used(nums.size(), false); + + return backtracking(nums, 2, target, 0, 0, used); + + } +}; +``` diff --git a/problems/0473.火柴拼正方形.md b/problems/0473.火柴拼正方形.md new file mode 100644 index 00000000..fccfedfb --- /dev/null +++ b/problems/0473.火柴拼正方形.md @@ -0,0 +1,42 @@ + +698.划分为k个相等的子集 的代码几乎不用改动,就可以AC +``` +class Solution { +private: + bool backtracking(vector& nums, + int k, + int target, // 子集目标和 + int cur, // 当前目标和 + int startIndex, // 起始位置 + vector& used) { // 标记是否使用过 + if (k == 0) return true; // 找到了k个相同子集 + if (cur == target) { // 发现一个合格子集,然后重新开始寻找 + return backtracking(nums, k - 1, target, 0, 0, used); // k-1 + } + for (int i = startIndex; i < nums.size(); i++) { + if (cur + nums[i] <= target && !used[i]) { + used[i] = true; + if (backtracking(nums, k, target, cur + nums[i], i + 1, used)) { + return true; + } + used[i] = false; + } + } + return false; + } +public: + bool makesquare(vector& nums) { + if (nums.size() < 4) return false; // 火柴数量小于4凑不上正方形 + int sum = 0; + for (int i = 0; i < nums.size(); i++) { + sum += nums[i]; + } + if (sum % 4 != 0) return false; + int target = sum / 4; + vector used(nums.size(), false); + + return backtracking(nums, 4, target, 0, 0, used); + + } +}; +``` diff --git a/problems/0649.Dota2参议院.md b/problems/0649.Dota2参议院.md new file mode 100644 index 00000000..d6a2a12f --- /dev/null +++ b/problems/0649.Dota2参议院.md @@ -0,0 +1,70 @@ + +## 思路 + +这道题 题意太绕了,我举一个更形象的例子给大家捋顺一下。 + +例如输入"RRDDD",执行过程应该是什么样呢? + +* 第一轮:senate[0]的R消灭senate[2]的D,senate[1]的R消灭senate[3]的D,senate[4]的D消灭senate[0]的R,此时剩下"RD",第一轮结束! +* 第二轮:senate[0]的R消灭senate[1]的D,第二轮结束 +* 第三轮:只有R了,R胜利 + +估计不少同学都困惑,R和D数量相同怎么办,究竟谁赢,**其实这是一个持续消灭的过程!** 即:如果同时存在R和D就继续进行下一轮消灭,轮数直到只剩下R或者D为止! + +那么每一轮消灭的策略应该是什么呢? + +例如:RDDRD + +第一轮:senate[0]的R消灭senate[1]的D,那么senate[2]的D,是消灭senate[0]的R还是消灭senate[3]的R呢? + +当然是消灭senate[3]的R,因为当轮到这个R的时候,它可以消灭senate[4]的D。 + +**所以消灭的策略是,尽量消灭自己后面的对手,因为前面的对手已经使用过权利了,而后序的对手依然可以使用权利消灭自己的同伴!** + +那么局部最优:有一次权利机会,就消灭自己后面的对手。全局最优:为自己的阵营赢取最大利益。 + +局部最优可以退出全局最优,举不出反例,那么试试贪心。 + +如果对贪心算法理论基础还不了解的话,可以看看这篇:[关于贪心算法,你该了解这些!](https://mp.weixin.qq.com/s/O935TaoHE9Eexwe_vSbRAg) ,相信看完之后对贪心就有基本的了解了。 + +## 代码实现 + +实现代码,在每一轮循环的过程中,去过模拟优先消灭身后的对手,其实是比较麻烦的。 + +这里有一个技巧,就是用一个变量记录当前参议员之前有几个敌对对手了,进而判断自己是否被消灭了。这个变量我用flag来表示。 + +C++代码如下: + + +``` +class Solution { +public: + string predictPartyVictory(string senate) { + // R = true表示本轮循环结束后,字符串里依然有R。D同理 + bool R = true, D = true; + // 当flag大于0时,R在D前出现,R可以消灭D。当flag小于0时,D在R前出现,D可以消灭R + int flag = 0; + while (R && D) { // 一旦R或者D为false,就结束循环,说明本轮结束后只剩下R或者D了 + R = false; + D = false; + for (int i = 0; i < senate.size(); i++) { + if (senate[i] == 'R') { + if (flag < 0) senate[i] = 0; // 消灭R,R此时为false + else R = true; // 如果没被消灭,本轮循环结束有R + flag++; + } + if (senate[i] == 'D') { + if (flag > 0) senate[i] = 0; + else D = true; + flag--; + } + } + } + // 循环结束之后,R和D只能有一个为true + return R == true ? "Radiant" : "Dire"; + } +}; +``` + +**如果感觉题解对你有帮助,不要吝啬给一个👍吧!** + diff --git a/problems/0698.划分为k个相等的子集.md b/problems/0698.划分为k个相等的子集.md new file mode 100644 index 00000000..23382bbe --- /dev/null +++ b/problems/0698.划分为k个相等的子集.md @@ -0,0 +1,42 @@ + +使用回溯法,首先要明确这是组合问题,即集合里是不强调顺序的,所以要用startIndex + +``` +class Solution { +private: + bool backtracking(vector& nums, + int k, + int target, // 子集目标和 + int cur, // 当前目标和 + int startIndex, // 起始位置 + vector& used) { // 标记是否使用过 + if (k == 0) return true; // 找到了k个相同子集 + if (cur == target) { // 发现一个合格子集,然后重新开始寻找 + return backtracking(nums, k - 1, target, 0, 0, used); // k-1 + } + for (int i = startIndex; i < nums.size(); i++) { + if (cur + nums[i] <= target && !used[i]) { + used[i] = true; + if (backtracking(nums, k, target, cur + nums[i], i + 1, used)) { + return true; + } + used[i] = false; + } + } + return false; + } +public: + bool canPartitionKSubsets(vector& nums, int k) { + //sort(nums.begin(), nums.end()); 不需要排序 + int sum = 0; + for (int i = 0; i < nums.size(); i++) { + sum += nums[i]; + } + if (sum % k != 0) return false; + int target = sum / k; + vector used(nums.size(), false); + + return backtracking(nums, k, target, 0, 0, used); + } +}; +```