【Leetcode 649】贪心法&老实法 详细分析:参议院淘汰游戏

题目

Leetcode 649 Dota2 参议院淘汰游戏

解题思路

本题背景比较新颖,但其实抽象出来就是字母R和D的淘汰游戏。
给一个只包含RD的字符串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。

当认识到上面的易错点之后,呈现下面几种方法:

  1. 老实法:每一次都寻找敌对方进行淘汰,且利用string中的erase函数实现淘汰。
  2. 老实法的优化:每次都寻找敌对方,通过将对应位置修改为’0’来模拟淘汰。
  3. 贪心法:不用寻找敌对方,通过person变量记录当前淘汰状态。

代码

法一:老实法

每一次都寻找敌对方进行淘汰,且利用string中的erase函数实现淘汰。
这是我看完题目之后的第一反应:一遇到R就去找D禁言!!
但是时间复杂度爆炸了!!

微信图片_20200413215529.png

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. 处理1:对senate[i]=R\D进行分类讨论,代码过长,重复率过高

微信图片_20200413215529.png

  1. 处理2:将senate翻译成int数组,用0,1表示R,D,使代码重用。(但实际上复杂度并没有降低,反而升高!

微信图片_20200416183553.png

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方。

微信图片_20200416183553.png

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!!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值