删数问题 贪心

问题描述:
输入一个高精度的正整数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;
}

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

©️2020 CSDN 皮肤主题: 1024 设计师:上身试试 返回首页