删数问题 贪心

问题描述:
输入一个高精度的正整数n,去掉其中任意s个数字后剩下的数字按原左右次序组成一个新的正整数。编程对给定的n和s,寻找一种方案使得剩下的数字组成的新数最小。
输出新的正整数。(n不超过240位)
输入数据均不需判错。
输入:
n
s
输出:
最后剩下的最小的数
输入样例:
175438
4
输出样例:
13

掉进自己挖的坑

最初的思路是——选数(挖坑开始):由于这里给的是要删除的数的个数,想着也就意味这n个数,删s个数,那么就要选k个数(k == n - s)来作为最后的输出。
怎么选呢?k位数当然是高位数值越低越好。**这里当时想的是最后输出的数要有k位,那么最高为数不能选0,不然就不是k位了。**所以选第一个数与选后面的数分为两种情况——选的第一个(位)数最小为1,而后面的数最小可选0;
n位数中,选出k位数作为最小的值输出,在选每一个数时,可供选择的数是有范围的——要保证选了当前的数作为k为数的第i位,后面k-i位数在n中还有数可选。
例如:输入: 763521 3,删三个数,选三个数,那么第1位只能从7635中选一个,而不能选2或者1,如果选1,第二位数和三位数就没得选了(选2意味着要去掉4个数)。所以选择第i位数时,被选数组中可选的范围是,数组下标k+1 ~ n-(n-s-p-1), (k为被选作第i-1位的数在备选数组中的下标;p为已选出的数的个数)
如上面的例子中选第一位数时,可选的下标范围1 ~ 4(设被选的数组下标从1开始),第一位选3,选第二位数时,可选的下标范围是4 ~ 5。
选完k个数后直接,就可以输出结果。
下面是代码:

#include<iostream>
#include<cstring>
using namespace std;

int main() 
{
	char a[243];
	int s, min, k, p = 0, len, l[241] = {0};
	cin >> a;
	cin >> s;
	len = strlen(a);
	int z = 0;
	while(a[z] == '0')//去掉串首的零 
		z++;
	//此时z既表示零的个数,又是串首最后一个零的后一位的下标
	min = a[z];
	k = z;
	if(len == s || len-z == s) 
		return 0;
		
	for(int i = z; i < z+s+1; ++i)//从第一个元素的最大选择范围内选除0以外最小的数,作为第一个元素 
	{
		if(a[i] == '0')
			break;
		
		if(a[i] < min)
		{
			min = a[i];
			k = i;
		}
	}
	l[k] = 1; 
	p++;					//已选择的元素的个数加一
	for(int i = k+1; i < len; i = k+1)
	{
		if(len-s == p)
			break;
		min = a[i];
		k = i;
		for(int j = i; j < z+s+p+1; j++)//z+s+p+1 <=> n-(n-s-z-p)+1当前可选的上界 
		{
			if(a[j] < min)
			{
				min = a[j];
				k = j;
				if(min == '0')
					break;
			}
		}
		l[k] = 1;
		p++;
	}

	for(int i = 0; i < len; i++)
	{
		if(l[i])
			cout << a[i];
	}	
	return 0;
}

然而,这只是悲剧的开始,submit后只有66分,百思不得其解,最后还是学长帮忙找到了corner case:
10081
2
结果多少?1!
错误源于我思路中的黑体部分,下意思的把只能k位结果,第一位不能位0作为了必然条件,然而第一位可以选0,每位都能选0,哭笑不得。

下面是纠正后的选数思路的正确代码:

#include<iostream>
#include<cstring>
using namespace std;

int main() 
{
	char a[243];
	int s, min, k, p = 0, len, l[241] = {0};
	cin >> a;
	cin >> s;
	len = strlen(a);
	int z = 0;
	while(a[z] == '0')//去掉串首的零 
		z++;
	//此时z既表示零的个数,又是串首最后一个零的后一位的下标
	min = a[z];
	k = z;
	if(len == s || len-z == s) 
		return 0;
		
	for(int i = z; i < len; i = k+1)
	{
		if(len-s == p)
			break;
		min = a[i];
		k = i;
		for(int j = i; j < z+s+p+1; j++)//z+s+p+1 <=> n-(n-s-z-p)+1当前可选的上界 
		{
			if(a[j] < min)
			{
				min = a[j];
				k = j;
				if(min == '0')
					break;
			}
		}
		l[k] = 1;
		p++;
	}

	bool flag = false;
	for(int i = 0; i < len; i++)
	{
		if(l[i] && a[i] != '0')
			flag = true;
		if(l[i] && flag || i == k && !flag )//若选出的全是0,控制只显示最后一个0 
			cout << a[i];
	}
	return 0;
}

以及直接删数思路的代码:


#include<iostream>
#include<cstring>
using namespace std;

int main()
{
	char a[243];
	int s, len;
	cin >> a >> s;
	len = strlen(a);
	for(int i = 0; i < s; i++)
	{
		for(int j = 0; j < len-1; j++)//串首开始找 
			if(a[j] > a[j+1])
			{
				for(int k = j; k < len-1; k++)
					a[k] = a[k+1];
				break;	
			}
		len--;
	}
//	int j = 0;//两种去掉串首的零的写法。
//	while(a[j] == '0')
//		j++;
//	for(int i = j; i < len; i++)
//	{
//		cout << a[i];
//	}
	bool flag = false;
	for(int i = 0; i < len; i++) 
	{
		if(a[i] != '0')
			flag = true;
		if(flag = true)
			cout << a[i];
	 } 
	return 0;
}

经验总结:
想出的解法有误,需在已有思路基础上,进一步质疑那些下意思认为”必然“的条件,这样对问题的本质,会有更深的理解。

问题是指在一个给定字串中,通过除某些字,使得剩下的字串最小或最大。贪心算法是一种在每一步选择中都采取在当前状态下最好或最优(即最有利)的选择,从而希望导致结果是全局最好或最优的算法。 在问题中,贪心算法的设计思想如下: 1. **从左到右遍历字串**:从左到右依次检查每一位字。 2. **比较相邻字**:对于每一位字,比较它与前一位字的大小。 3. **除较大的字**:如果当前字小于前一位字,则除前一位字。因为除较大的字可以使得整个字串更小。 4. **重复上述步骤**:重复上述步骤,直到没有可以除的字为止。 具体步骤如下: 1. 从左到右遍历字串,找到第一个使得第i位字大于第i+1位字的位置。 2. 除第i位字。 3. 重复上述步骤,直到整个字串按递增顺序排列。 例如,给定字串“1432219”,要求通过除3个字,使得剩下的字串最小。 1. 遍历字串,找到第一个使得第i位字大于第i+1位字的位置,即1和4,4和3,3和2。 2. 除第一个满足条件的字,即4。 3. 继续遍历,找到第一个使得第i位字大于第i+1位字的位置,即1和3,3和2。 4. 除第一个满足条件的字,即3。 5. 继续遍历,找到第一个使得第i位字大于第i+1位字的位置,即2和1。 6. 除第一个满足条件的字,即2。 7. 继续遍历,找到第一个使得第i位字大于第i+1位字的位置,即2和1。 8. 除第一个满足条件的字,即2。 9. 最终得到的最小字串为“1219”。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值