649. Dota2 参议院
Dota2 的世界里有两个阵营:Radiant(天辉)和 Dire(夜魇)
Dota2 参议院由来自两派的参议员组成。现在参议院希望对一个 Dota2 游戏里的改变作出决定。他们以一个基于轮为过程的投票进行。在每一轮中,每一位参议员都可以行使两项权利中的一项:
禁止一名参议员的权利:
参议员可以让另一位参议员在这一轮和随后的几轮中丧失所有的权利。
宣布胜利:
如果参议员发现有权利投票的参议员都是同一个阵营的,他可以宣布胜利并决定在游戏中的有关变化。
给定一个字符串代表每个参议员的阵营。字母 “R” 和 “D” 分别代表了 Radiant(天辉)和 Dire(夜魇)。然后,如果有 n 个参议员,给定字符串的大小将是 n。
以轮为基础的过程从给定顺序的第一个参议员开始到最后一个参议员结束。这一过程将持续到投票结束。所有失去权利的参议员将在过程中被跳过。
假设每一位参议员都足够聪明,会为自己的政党做出最好的策略,你需要预测哪一方最终会宣布胜利并在 Dota2 游戏中决定改变。输出应该是 Radiant 或 Dire。
示例 1:
输入:"RD"
输出:"Radiant"
解释:第一个参议员来自 Radiant 阵营并且他可以使用第一项权利让第二个参议员失去权力,因此第二个参议员将被跳过因为他没有任何权利。然后在第二轮的时候,第一个参议员可以宣布胜利,因为他是唯一一个有投票权的人
示例 2:
输入:"RDD"
输出:"Dire"
解释:
第一轮中,第一个来自 Radiant 阵营的参议员可以使用第一项权利禁止第二个参议员的权利
第二个来自 Dire 阵营的参议员会被跳过因为他的权利被禁止
第三个来自 Dire 阵营的参议员可以使用他的第一项权利禁止第一个参议员的权利
因此在第二轮只剩下第三个参议员拥有投票的权利,于是他可以宣布胜利
提示:
给定字符串的长度在 [1, 10,000] 之间.
这题有三种方式(不是方法,是方式)去解决。我直接告诉我是栈或者队列,官方题解的队列非常经典,我也成功的没想出来。md!
官方镇楼:
这是队列的方式,和链表合并非常相似,代码简洁好看,思路清晰简单。
两个队列 r、d 队列,存储 r、d 在字符串中的位置。它有一个思想很有意思:
- 比较两个队列的队首元素大小,队首元素较小者,比如 r,有优先裁决权,将对方的距离其最近的元素 kill,也就是队列 d 的首元素弹出。
- r 队列的首元素 + n,添加到对尾,表示下一轮的位置(就这个操作,很有意思)
- 循环直到某个队列为空
这个算法的复杂度为 O(N)
class Solution {
public:
string predictPartyVictory(string senate) {
int n = senate.size();
queue<int> radiant, dire;
for(int i=0; i<n; i++)
if(senate[i] == 'R')
radiant.push(i);
else
dire.push(i);
while( !radiant.empty() && !dire.empty()) {
if(radiant.front() < dire.front()) {
dire.pop();
radiant.push(radiant.front()+n);
radiant.pop();
}
else {
radiant.pop();
dire.push(dire.front()+n);
dire.pop();
}
}
if(radiant.empty())
return "Dire";
else
return "Radiant";
}
};
我的解法非常麻烦,但思路清晰:
- 统计字符串中双方活着的参议员 num_r、num_d,如果 其中一方为 0 或者 另外一方有半数的优势,则取胜
- 遍历字符串,对于每个活着的参议员,kill 距离其最近的对方参议员
- 循环 1, 2, 直到一方取胜
这个代码写的其实相当复杂,也是最蠢的方法,没有任何技巧可言。
class Solution {
public:
string check(string& senate, bool *live) {
int num_R=0, num_D=0;
for(int i=0; i<senate.size(); i++) {
if(live[i] && senate[i]=='R')
num_R++;
else if(live[i] &&senate[i]=='D')
num_D++;
}
if(num_D == 0 || num_R>=2*num_D)
return "Radiant";
if(num_R == 0 || num_D>=2*num_R)
return "Dire";
return "unkown";
}
void kill(string& senate, int idx, bool *live, char who) {
for(int i=idx+1; i<senate.size(); i++)
if(live[i] && senate[i]==who) {
live[i] = false;
return;
}
for(int i=0; i<idx; i++)
if(live[i] && senate[i]==who) {
live[i] = false;
return;
}
}
string predictPartyVictory(string senate) {
bool live[senate.size()];
for(int i=0; i<senate.size(); i++)
live[i] = true;
while(check(senate, live) == "unkown") {
for(int i=0; i<senate.size(); i++)
if(live[i] && senate[i]=='R')
kill(senate, i, live, 'D');
else if(live[i] && senate[i]=='D')
kill(senate, i, live, 'R');
}
return check(senate, live);
}
};
第三个方式,既不需要数据结构,也不需要来回遍历。每遇到一个 D, 记录person 需要杀死 一个R,遍历过程中,需要杀死 R 为 person++,已经杀死 R 为 person–。这个思路解决:需要杀的人在远方的问题。
个人觉得第一个方法最好,第三个最炫,第二个最蠢。如果内存足够,代码应该既优美,又高效。