这节我们来汇总一些LeetCode题,我也没有对它们分类,索性称为杂谈吧。
这个系列介绍的题我没有赋予标签,虽然它们都是有具体的方法,但是之所以单独拿出来,是因为它们带有一些独特的内涵在其中,还是值得细细“品味”的。
插张图,很多题倘若没有思路,不妨暴力求解出发,不断优化,往往会得到不错的效果。
就像这图所示,我们先介绍一道回溯加剪枝的题。
可能语焉不详,还请多多海涵。
组合总和
给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的数字可以无限制重复被选取。
说明:
所有数字(包括 target)都是正整数。
解集不能包含重复的组合。
示例 1:
输入: candidates = [2,3,6,7], target = 7,
所求解集为:
[
[7],
[2,2,3]
]
示例 2:
输入: candidates = [2,3,5], target = 8,
所求解集为:
[
[2,2,2,2],
[2,3,3],
[3,5]
]
来源。
某种程度上说,个人觉得这个题其实算穷举,数字可以无限制使用。但是真的去穷举不仅仅不好写,而且时空代价高。
这个题可以算是树的搜索,满足条件的是一条路径,但是列举所有的情况不现实,我们需要剪枝。
在解题区,这位大佬已经说得很清楚了,传送门。
我个人的理解,也写在了我个人的我的解题中。
若有不对还请多多指出。
我们先给数组排序,从candidates某一个元素candidates[i]开始,如果它比target小,candidates[i]可以暂存到path中,从candidates[i]处继续开始dfs,因为candidates 中的数字可以无限制重复被选取。如果target==0,这是一个满足条件的组合。如果target比candidates[i]还小了,说明这次暂存的元素不合理,把它弹出(pop_back()),那它就会从candidates的下一个元素出发,继续dfs,直到结束整个过程。
程序如下:
class Solution {
public:
vector path;
vector> res;
vector> combinationSum(vector& candidates, int target) {
sort(candidates.begin(),candidates.end());
dfs(candidates,target,0);
return res;
}
void dfs(vector& candidates, int target,int start){
if(target == 0){
res.push_back(path);
return ;
}
for(int i=start;i= candidates[i]){
path.push_back(candidates[i]);
dfs(candidates,target-candidates[i],i);
path.pop_back();
}
}
}
};
接下来的这个题就很有意思了。
石子游戏
亚历克斯和李用几堆石子在做游戏。偶数堆石子排成一行,每堆都有正整数颗石子 piles[i] 。
游戏以谁手中的石子最多来决出胜负。石子的总数是奇数,所以没有平局。
亚历克斯和李轮流进行,亚历克斯先开始。 每回合,玩家从行的开始或结束处取走整堆石头。 这种情况一直持续到没有更多的石子堆为止,此时手中石子最多的玩家获胜。
假设亚历克斯和李都发挥出最佳水平,当亚历克斯赢得比赛时返回 true ,当李赢得比赛时返回 false 。
示例:
输入:[5,3,4,5]
输出:true
解释:
亚历克斯先开始,只能拿前 5 颗或后 5 颗石子 。
假设他取了前 5 颗,这一行就变成了 [3,4,5] 。
如果李拿走前 3 颗,那么剩下的是 [4,5],亚历克斯拿走后 5 颗赢得 10 分。
如果李拿走后 5 颗,那么剩下的是 [3,4],亚历克斯拿走后 4 颗赢得 9 分。
这表明,取前 5 颗石子对亚历克斯来说是一个胜利的举动,所以我们返回 true 。
提示:
2 <= piles.length <= 500
piles.length 是偶数。
1 <= piles[i] <= 500
sum(piles) 是奇数。
来源。
这个题要是用贪心算法做,那就不行了。
以我一开始的想法为例:
class Solution {
public:
bool stoneGame(vector& piles) {
if(piles.size()<2){
return true;
}
int sum_ya=0;
int sum_li=0;
int p1=0;
int p2=piles.size()-1;
for(int i=1;i<=piles.size();i++){
if(i%2==1){
if(piles[p1]sum_li;
}
};
能通过很多样例,但是,局部最优有时候却不是全局最优。
看一个样例:
[2,1,10,3]
亚历克斯要是拿局部最优3,那10就被李拿走了,他就输了。
但是啊,亚历克斯先手,他可以拿2,3被李拿走了,他再拿10,赢了。
那问题来了,我们该怎么做呢,动态规划是一种方法,这个题的dp解法将在以后的博客中给出,这里我们不用这种方法。
倘若你要给出一些样例,会发现,好像先手的亚历克斯总能赢,细化一点问题。
两个石子,先手肯定赢,那四个石子呢,细心推理发现,先手还能赢,再往后呢...
我们发现似乎先手总能赢,那事实确如此,有兴趣的可以去证明或者查看解题评论区。
那结果:
class Solution {
public:
bool stoneGame(vector& piles) {
return true;
}
};
动态规划解法会在之后博客给出。
可怜的小猪
有 1000 只水桶,其中有且只有一桶装的含有毒药,其余装的都是水。它们从外观看起来都一样。如果小猪喝了毒药,它会在 15 分钟内死去。
问题来了,如果需要你在一小时内,弄清楚哪只水桶含有毒药,你最少需要多少只猪?
回答这个问题,并为下列的进阶问题编写一个通用算法。
进阶:
假设有 n 只水桶,猪饮水中毒后会在 m 分钟内死亡,你需要多少猪(x)就能在 p 分钟内找出 “有毒” 水桶?这 n 只水桶里有且仅有一只有毒的桶。
提示:
可以允许小猪同时饮用任意数量的桶中的水,并且该过程不需要时间。
小猪喝完水后,必须有 m 分钟的冷却时间。在这段时间里,只允许观察,而不允许继续饮水。
任何给定的桶都可以无限次采样(无限数量的猪)。
来源。
这个题是一道难题,难点不是程序,而是思路,思路对了,很简单。
量子力学很火热,那这个题中的猪也可以算是量子状态下的猪,这个题涉及到信息论方面的知识。
想明白一个问题,一只猪在规定时间内做多能帮你测试几桶水?
清楚了这个,猪的数目就简单了
先给出结果,仔细想一想就会恍然大悟了。
详细过程可以见官方解答,传送门。
class Solution {
public:
int poorPigs(int buckets, int minutesToDie, int minutesToTest) {
return ceil(log(buckets)/(log(int(minutesToTest/minutesToDie)+1)));
}
};
求众数 II
给定一个大小为 n 的数组,找出其中所有出现超过 ⌊ n/3 ⌋ 次的元素。
说明: 要求算法的时间复杂度为 O(n),空间复杂度为 O(1)。
示例 1:
输入: [3,2,3]
输出: [3]
示例 2:
输入: [1,1,1,3,3,2,2,2]
输出: [1,2]
来源。
没有这个要求,其实不难,用map很容易出结果,奇怪的是这种方法居然AC了。。。
class Solution {
public:
vector majorityElement(vector& nums) {
map m;
int len = nums.size();
for(int i=0;i res;
for(auto _m : m){
if(_m.second > len/3){
res.push_back(_m.first);
}
}
return res;
}
};
满足这个要求,这个题还是不好做,这其中蕴含一种经典的算法,摩尔投票。
详细过程可见评论区,传送门。
归纳一下便是:
如果至多选一个代表,那他的票数至少要超过一半(⌊ 1/2 ⌋)的票数;
如果至多选两个代表,那他们的票数至少要超过⌊ 1/3 ⌋的票数;
如果至多选m个代表,那他们的票数至少要超过⌊ 1/(m+1) ⌋的票数。
之后碰到这样的问题,而且要求达到线性的时间复杂度以及常量级的空间复杂度,直接套上摩尔投票法。
本节内容暂告一段落,之后讲继续更新。