1023组成最小数(20分) - 用不到20行代码破解20分的题目
开始前先叭叭几句
最近要参加编程赛,于是时隔N久,已经大半年没写过C++代码的老菜鸡又打开了PTA刷C++的题练手。好久不写代码,上手15分+的字符数组题目还真的是会头疼(甚至写多了JavaScript总是会忘了C/C++里的==
老哥……)。刚好写到PAT (Basic Level) Practice (中文)1023组成最小数(20分)这道题目的时候,发现写完字符数组换个思路写数值数组真的一身畅轻。虽然这道题目本身难度也不大,但是换了一个新思路以后,20分的题一数代码不到20行,一行代码都已经不止值1分了!然后写完题目以后就忍不住写这篇文章分享一下思路了嘿嘿嘿嘿。
我用的思路也是从陈姥姥的课上学来的,具体怎么解释这个思路已经不记得了,大概就是设置一个数组把它当做一块存储区域,好像是逻辑链表之类的吧。哎呀不管了不管了,菜鸡话不多说,直接开始解题。
1 题目内容
给定数字0-9
各若干个。你可以以任意顺序排列这些数字,但必须全部使用。目标是使得最后得到的数尽可能小(注意0
不能做首位)。例如:给定两个0
,两个 1
,三个5
,一个8
,我们得到的最小的数就是 10015558
。
现给定数字,请编写程序输出能够组成的最小的数。
输入格式:
输入在一行中给出 10个非负整数,顺序表示我们拥有数字0
、数字 1
、……数字9
的个数。整数间用一个空格分隔。10个数字的总个数不超过 50,且至少拥有 1 个非0
的数字。
输出格式:
在一行中输出能够组成的最小的数。
输入样例:
2 2 0 0 0 3 0 0 1 0
输出样例:
10015558
2 题目分析
2.1 常规思路
这道题目读完以后给我们最直观的感受就是这是一道排序题:给定了一组数字以后,首先把首位确定为非0
的最小的数字,然后把剩下的数字按升序排列,最后把这个序列按顺序输出,这组数字就排出来了。按照这种思路,我们只需用一个数组把给定的每个数字按照数量存起来,然后排序即可。
但是这道题给定的输入格式很不一般:题目并没有直接给出序列里有哪些数字,而是按照顺序给出了有几个0
、几个1
……的这种形式。如果根据这个输入存储组成这个序列的数字,就要设置9个循环,按照输入的N1个1
、N2个2
……N9个9
,分别设置9个循环进行存储。这种方式着实有些不方便。因此,我们考虑更换一种思路。
2.2 不排序、按顺序存输入
观察输入的格式,我们会发现给定的输入有10个值,第i
个值代表着对应的数字i-1
的数量。所以我们直接定义一个数组a[10]
,然后把输入的10个数对应存入a[0]
、a[1]
、……a[9]
中,这样数组里的每个元素a[i]
就代表着数字i
在这个给定序列中的数量。利用这个方法存储有一个最明显的优势:我们知道首位的非0
最小数确定下来以后,后面的数字只要按照升序排列所有的数字即可,而0
到9
已经是排列好的升序,并且每个数字需要出现多少次也已经确认了(即a[n]
),所以,我们接下来的工作就是直接输出了
3 代码实现
3.1 代码框架
前文中我们已经明确,我们只需定义一个数组直接对应存储输入的10个数字即可。所以框架部分我们只需定义好数组和循环变量读取输入,剩下的就都放在核心算法的代码中去处理即可。
int a[10], i;
for(i = 0;i < 10;i++) //直接一行scanf也不是不行
cin >> a[i];
/*----------BEGIN----------*/
//核心算法:直接输出序列
/*-----------END-----------*/
return 0;
这里的输入为了保持C++语言的代码风格所以我还是用的循环配合cin
,如果直接使用scanf
函数的话也没有问题。
3.2 核心算法:序列输出
输出序列主要分为两部分的工作:
- 确定首位
- 输出余下序列
3.2.1 确定首位
要求输出序列尽可能小的情况下,首位的数字就必须尽可能的小,同时要求非0
。a[i]
的值我们可以理解为数字i
在生成序列中要出现的次数,只要a[i]
不为0
,那就意味着i
一定要出现在序列里。所以从i = 1
开始遍历a[i]
,利用if
判断a[i] != 0
时,序列首位即为i
。
确定首位为i
后,我们还有事要做。首位i
已经出现过一次了,那么后面i
就少出现了一次,所以我们就让a[i]--
。这时的数组a[i]
存储的就是首位以外每个数字i
在序列中出现的次数。同时,我们还要break
一下,跳出我们的循环,去做后面的事情。
这时我们还要考虑一个问题:当我们已经确定好首位为i
的时候,我们应该怎么处理这个数呢?存起来吗?不!我们直接输出!我们只要确认了序列的下一位数是几,我们就直接输出!毫不拖沓!所以:
/*----------BEGIN----------*/
for(i = 1;i < 10;i++) //从1开始循环
if(a[i] != 0)
{
cout << i;
a[i]--;
break; //要记得break
}/*要记得我们输出要求的是输出最终的序列,
所以输出的是i不是a[i],a[i]表示的是i出现的次数*/
//……
/*-----------END-----------*/
3.2.2 输出余下序列
其实做到这里,思路已经大致理清晰了。唯一要求非0
的首位已经确定了,因此在剩下的部分,我们直接把剩下的数字尽可能小的先输出。这次我们从a[0]
开始遍历数组。只要a[i]
的值不为0
,就说明i
需要在序列中出现,而且要出现a[i]
次。
可以预见我们需要两个循环:一个外循环i控制遍历数组、一个内循环按照a[i]
的值控制输出。这里我们不引入第二个循环变量j
,直接使用a[i]
控制循环,如下:
/*----------BEGIN----------*/
//……
for(i = 0;i < 10;i++)
while(a[i]--)
cout << i;
/*-----------END-----------*/
这里while(a[i]--)
的使用方法可能对一些小伙伴来说还不太熟悉。这是在“给定N
,N
代表接下来将输入几组数据”的这种题目中比较常见的用法(while(N--){}
)。把这一步也做完以后,其实我们代码的核心部分就已经完成了!
3.3 整体代码
框架和核心部分已经搭好了,那么就把#include
等所有部分都组合到一起就好啦。如果输入的时候想要用scanf
的话,记得#include<cstdio>
哦!
#include<iostream>
using namespace std;
int main()
{
int a[10], i;
for(i = 0;i < 10;i++)
cin >> a[i];
for(i = 1;i < 10;i++)
if(a[i] != 0)
{
cout << i;
a[i]--;
break;
}
for(i = 0;i < 10;i++)
while(a[i]--)
cout << i;
return 0;
}
放到PTA里编译,AC!
4 再拓展一下思路
这道题我们目前采取的办法是存储下来输入的所有数值以后再去判断、处理、然后输出。但是某种模糊当中的直觉告诉我,这里应该有某种方法,使得这道题能够存储更少的输入内容然后去做后面的处理(好吧我坦白,上陈姥姥的数据结构课以后的后遗症……)。
我目前想到的是至少要把0的出现次数存下来,因为首位不能是0,得先把首位确定下来以后才能去输出0、把0全部输出以后才能见一个输出一个……这里可以留下来大家想一想,或者大佬看到以后可以直接把最简洁的算法直接bia在我的评论区里。我知道我还很菜,但我也在努力学习呢(bushi)!