记录一下练习时做的leetcode上的第649题,感觉本题思路很绕也有一定难度。
649.Dota2参议院
Dota2 的世界里有两个阵营:Radiant(天辉)和 Dire(夜魇)
Dota2 参议院由来自两派的参议员组成。现在参议院希望对一个 Dota2 游戏里的改变作出决定。他们以一个基于轮为过程的投票进行。在每一轮中,每一位参议员都可以行使两项权利中的 一 项:
禁止一名参议员的权利:参议员可以让另一位参议员在这一轮和随后的几轮中丧失 所有的权利 。
宣布胜利:如果参议员发现有权利投票的参议员都是 同一个阵营的 ,他可以宣布胜利并决定在游戏中的有关变化。
给你一个字符串 senate 代表每个参议员的阵营。字母 'R' 和 'D'分别代表了 Radiant(天辉)和 Dire(夜魇)。然后,如果有 n 个参议员,给定字符串的大小将是 n。
以轮为基础的过程从给定顺序的第一个参议员开始到最后一个参议员结束。这一过程将持续到投票结束。所有失去权利的参议员将在过程中被跳过。
假设每一位参议员都足够聪明,会为自己的政党做出最好的策略,你需要预测哪一方最终会宣布胜利并在 Dota2 游戏中决定改变。输出应该是 "Radiant" 或 "Dire" 。
示例 1:
输入:senate = "RD"
输出:"Radiant"
解释:
第 1 轮时,第一个参议员来自 Radiant 阵营,他可以使用第一项权利让第二个参议员失去所有权利。
这一轮中,第二个参议员将会被跳过,因为他的权利被禁止了。
第 2 轮时,第一个参议员可以宣布胜利,因为他是唯一一个有投票权的人。
示例 2:
输入:senate = "RDD"
输出:"Dire"
解释:
第 1 轮时,第一个来自 Radiant 阵营的参议员可以使用第一项权利禁止第二个参议员的权利。
这一轮中,第二个来自 Dire 阵营的参议员会将被跳过,因为他的权利被禁止了。
这一轮中,第三个来自 Dire 阵营的参议员可以使用他的第一项权利禁止第一个参议员的权利。
因此在第二轮只剩下第三个参议员拥有投票的权利,于是他可以宣布胜利
思路:
1.本题乍看上去有些复杂,最初的不成熟想法是直接看R和D的人头数,哪一方人头数多哪一方就能赢。但简单一推理就会发现这是不成立的,结果不仅与人头数有关,还与排列的顺序有关:如果我有三个R五个D按照RRRDDDD排序,第一轮结束后只剩下RRD,最终的胜利是属于人头更少的R的。
2.既然如此,我们继续找例子来模拟整个场景,模拟过程中就会发现问题:假如轮到当前成员且当前成员能够行使权力时,那么我们应当禁止自己之前的敌人的权利,还是自己之后的敌人的权利?显然是选择禁止之后敌人的权利,因为之后的敌人仍然有权利在当轮继续禁止我方阵营成员,而之前的敌人当前轮已经没有额外行动的机会。
3.明白了以上这一点后,我们的大体思路已经掌握,接下来我们需要考虑遍历过程中遇到的问题:如何判断当前成员是否已经被禁止权利?我们可以用一个整型变量来记录当前成员之前的敌方成员个数,如果大于0则说明自己将被之前的敌人禁止。除此之外,每一轮遍历完后我们需要根据字符串内R和D的情况来判断要不要开始新一轮的遍历,因此还需要布尔变量来判断当前字符串中还有没有R和D。
class Solution {
public:
string predictPartyVictory(string senate) {
bool R = true, D = true;//布尔变量为true表示本轮循环结束后字符串中仍旧有对应的阵营人员
//flag用来判断该阵营成员之前有多少个敌方阵营成员,进而判断自己是否被消灭
//flag > 0时R在D之前出现,此时R将消灭D;flag < 0时D在R之前出现,此时D将消灭R
int flag = 0;
//直到R和D其中一方为false时结束循环
while(R && D){
R = false;
D = false;
for(int i = 0; i < senate.size(); i++){
if(senate[i] == 'R'){
//若flag < 0,说明这之前有D的成员,自己将被消灭
if(flag < 0) senate[i] = 0;
else{
R = true;
}
flag++;
}
if(senate[i] == 'D'){
//若flag > 0,说明这之前有R的成员,自己将被消灭
if(flag > 0) senate[i] = 0;
else{
D = true;
}
flag--;
}
}
}
return R == true? "Radiant" : "Dire";
}
};
启发:
1.本题是使用的贪心算法,而其局部最优的思路并没有那么的好想,最主要的问题就是判断行使禁止权利时到底禁止之前的敌人还是之后的敌人。因为之后的敌人仍然在本轮有权利禁止掉我方其他成员,因此应当优先禁止自己之后的敌人,做到局部最优。
2.本题如何判断自己是否被禁止的思路也很巧妙,只用了一个整型变量就能实现记录自己之前的敌人数量(通过正负来区别不同的阵营),相比起使用两个整型变量来分别记录进一步优化了代码。
3.本题还会有一个疑惑:既然我们都是优先禁止掉自己之后的敌人,那么处在字符串首的第一个成员岂不是永远不会被后面的敌人禁止掉?其实是不会的,我们通以RDDRD为例子,并且用代码模拟整个过程,第一轮遍历完毕后R和D均为true,s[0]的R禁止了s[1]的D,s[2]的D禁止了s[3]的R,而s[4]则没有进行禁止操作,但在遍历完s[4]之后此时的flag计数为-1!此时的字符串也等价于RDD。第二轮遍历开始后,s[0]因为判断flag < 0说明之前有敌人而会被消灭,其实是等价于上一轮最后s[4]的D禁止掉了s[0]的R。