题目
解题思路
本题背景比较新颖,但其实抽象出来就是字母R和D
的淘汰游戏。
给一个只包含R
和D
的字符串s。当遍历到该字母时,会淘汰掉排在它之后的另一字母。
以下说明此题的几个TIPS:(分析时易陷入的误区)
注意1:此处一定是淘汰掉在该字母之后的敌对字母。(因为要抵消掉后面敌对字母的淘汰权利,前面的敌对字母已经行使过淘汰权了。)当后面没有敌对字母时,才淘汰位于该字母之前的敌对字母。
如果该字符串中不存在敌对字母,则该方“宣布胜利”。否则,会出现相反的结果。
例如:s = "RDDRRDD" 。
- 如果s[2]的D淘汰掉s[3]的R,则最后结果为D方胜利,正确。
- 如果s[2]的D淘汰掉s[0]的R,则最后结果为R方胜利,错误。
注意2:此处不可以通过R,D字母的数量来判断输赢。因为这还与他们出现的相对顺序有关。
例如:s= "RRDDD" 的胜利方为R,而R的数量却小于D的数量。
注意3:此处淘汰要进行到最后,而不能通过遍历一次后,R,D剩余的数量的相对大小进行判断输赢。
例如:s="RDRDRDDRDRDRDR"遍历一次淘汰后,s="RRRDDDD",此时Rcnt<Dcnt,但是最后胜方为R。
当认识到上面的易错点之后,呈现下面几种方法:
- 老实法:每一次都寻找敌对方进行淘汰,且利用string中的erase函数实现淘汰。
- 老实法的优化:每次都寻找敌对方,通过将对应位置修改为’0’来模拟淘汰。
- 贪心法:不用寻找敌对方,通过person变量记录当前淘汰状态。
代码
法一:老实法
每一次都寻找敌对方进行淘汰,且利用string中的erase
函数实现淘汰。
这是我看完题目之后的第一反应:一遇到R就去找D禁言!!
但是时间复杂度爆炸了!!
class Solution {
public:
string predictPartyVictory(string senate) {
int i = 0;
int opposite_index;//记录要淘汰掉的字符的
char opposite;//要淘汰掉的字符
while (senate.size() != 1)//最后只剩1个字母的时候,退出循环
{
opposite = senate[i] == 'R' ? 'D' : 'R';
opposite_index = senate.find(opposite, i);//从[i,senate.size()-1]中寻找敌对字母
if (opposite_index == senate.npos)
{
opposite_index = senate.find(opposite);//这个时候划掉的是前面的,所以i不用变。
if (opposite_index == senate.npos)//此时整个字符串没有敌对字母,退出
break;
}
else//划掉的是后面的,所以下一个要遍历的字母的下标要++
i++;
senate.erase(opposite_index,1);//从opposite_index开始,删除1个元素
if (i >= senate.size())//下标越界
i = 0;
}
return senate[i] == 'R' ? "Radiant" : "Dire";
}
};
法二:老实法的优化
每次都寻找敌对方,通过将对应位置修改为’0’来模拟淘汰。
此方法是法一的优化,即无需通过反复调用erase来实现淘汰。大大降低时间复杂度!!!
下面给出两种处理:
- 处理1:对
senate[i]=R\D
进行分类讨论,代码过长,重复率过高
- 处理2:将
senate
翻译成int数组,用0,1表示R,D,使代码重用。(但实际上复杂度并没有降低,反而升高!)
string predictPartyVictory(string senate) {//处理1
int i = 0;
int opposite_index;//记录要淘汰掉的字符的
int Rcnt = 0, Dcnt = 0;//记录已经遍历过的R,D的数目(不包含后面还没有遍历的)
do
{
Rcnt = 0; Dcnt = 0;
for (int i = 0; i < senate.size(); i++)
{
if (senate[i] == 'R')
{
Rcnt++;
opposite_index = senate.find('D', i);
if (opposite_index != senate.npos)
senate[opposite_index] = '0';
else
{
opposite_index = senate.find('D');
if (opposite_index != senate.npos)
{
senate[opposite_index] = '0';
Dcnt--;
}
else
return "Radiant";
}
}
else if(senate[i]=='D')
{
Dcnt++;
opposite_index = senate.find('R', i);
if (opposite_index != senate.npos)
senate[opposite_index] = '0';
else
{
opposite_index = senate.find('R');
if (opposite_index != senate.npos)
{
senate[opposite_index] = '0';
Rcnt--;
}
else
return "Dire";
}
}
}
} while (Rcnt&&Dcnt);
return Rcnt!=0 ? "Radiant" : "Dire";
}
string predictPartyVictory(string senate) {//处理2
int i = 0;
char opposite;//要淘汰掉的字符
int opposite_index;//记录要淘汰掉的字符的
vector<int> v(senate.size());//将string替换为int表示:R=0, D=1
for (int i = 0; i < senate.size(); i++)
{
v[i] = senate[i] == 'R' ? 0 : 1;
}
int cnt[2] = { 0,0 };//cnt[0],cnt[1]分别记录已经遍历过的R,D的数目(不包含后面还没有遍历的)
do
{
cnt[0] = 0; cnt[1] = 0;
for (int i = 0; i < v.size(); i++)
{
if (senate[i] != 'R'&&senate[i] != 'D')//由于此时senate含有0,所以要加这个if!!!
continue;
opposite = senate[i] == 'R' ? 'D' : 'R';
cnt[v[i]]++;
opposite_index = senate.find(opposite, i);
if (opposite_index != senate.npos)
senate[opposite_index] = '0';
else
{
opposite_index = senate.find(opposite);
if (opposite_index != senate.npos)
{
senate[opposite_index] = '0';
cnt[v[i] == 0 ? 1 : 0]--;//另一个字母的cnt--
}
else
return v[i]==0?"Radiant":"Dire";
}
}
} while (cnt[0] && cnt[1]);
return cnt[0] != 0 ? "Radiant" : "Dire";
}
法三:贪心法
其实,根本不需要寻找敌对方,可以通过person
变量记录当前淘汰状态。
person
:当person
>0时,表示R方可以淘汰D方;person
<0时,表示D方可以淘汰R方。
string predictPartyVictory(string senate) {
bool R = true, D = true;//R,D标记senate中是否还有R,D
int person = 0;//标记变量person,当person>0时,表示R方可以淘汰D方;person<0时,表示D方可以淘汰R方。
while (R&&D)//R\D标记本轮循环中,senate是否存在R\D.(且是淘汰前的序列中)
{
R = false;
D = false;
for (int i=0;i<senate.size();i++)
{
if (senate[i] == 'R')
{
R = true;
if (person < 0)//D方有权淘汰R方
senate[i] = '0';
person++;//无论有没有D淘汰掉R,person都++。cause有淘汰时,D的淘汰权用掉一次,person++;没有淘汰时,R的淘汰权增加1,person++.
}
else if (senate[i] == 'D')
{
D = true;
if (person > 0)
senate[i] = '0';
person--;
}
}
}
return person > 0 ? "Radiant" : "Dire";
}
如果题解对你有启发或者帮助的话,不妨点个赞~
Thanks for your reading!!