问题描述:
输入一个高精度的正整数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;
}
经验总结:
想出的解法有误,需在已有思路基础上,进一步质疑那些下意思认为”必然“的条件,这样对问题的本质,会有更深的理解。