2018级E1-C1递归(非递归的另类解法)
题目
题面
从1~n中选取任意多(大于0)个数字,输出所有可能的选择方案
输入
一行一个整数n(1=<n<=10)
输出
多行,每行一种方案
同一行内的数必须升序排列,相邻两个数用恰好1个空格隔开。
方案按照字典序由小到大输出。
输入样例
3
输出样例
1
1 2
1 2 3
1 3
2
2 3
3
分析
-
解题背景
刚看到这个题,如此短小的题面令我愉悦,想想就是水题,DFS可以直接过,没有一点难度,但是能不能在这个水题上玩出花样呢?正好最近学习了BUAA(CMU)的 面向程序设计的硬件基础 一课,对二进制数有了更加敏锐的感觉。 -
开始思考
回到题目上,这实际上就是一种选数问题,对于1~n这n个数,可以选择出现与不出现,每个数都有两种选择,于是总共有2n种结果,其中包含了所有数均不出现的情况,按照题目要求,这种情况不应该输出,故结果应为2n-1种。 -
深入思考
如果我拿0表示所在位数号不出现,1表示所在位数号出现,根据上面的分析,便有2n种01序列(暂时将全0序列也包括在内),而这些序列,正好可以对应到0~2n-1这些十进制数。 -
举个例子
若n=4,则十进制数11,也就是(1011),可以对应着结果之一:1 3 4(从左往右分别编号1~n)
解题思路
有了上面的分析,我可以总结出如下解题思路:
- 输入n。
- 生成1~2n-1这些数字的二进制序列。(可以自己写一个函数解决)
- 遍历上述序列,根据每个序列每一位的01情况,生成相应的结果序列,此时应该注意到,生成的结果是不按照字典序的,而我暂时没想到好的算法,在生成二进制序列时就决定一个顺序,使之生成的结果已经按照字典序排列, 正因如此,还要有第四、五步。
- 将上述的结果序列,转化成string类型
- 将string类型的结果序列全部放入set容器中,快速地自动按照字典序排列(这时候又出现一个问题,数字’10’对应着两个字符,在输出的时候要特判一下)
- 遍历set容器,输出结果序列
代码如下
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <string>
#include <set>
#include <cmath>
using namespace std;
int nums[2000][10];//用于存放01序列
int cnt1,cnt2;//两个维度的计数器
set<string> myArray;//存放string类型的结果序列
void dTob(int n){//将十进制数转化为二进制序列
while(n){
nums[cnt1][cnt2++] = n % 2;
n /= 2;
}
cnt1++;
cnt2 = 0;
}
int main()
{
int n;
scanf("%d", &n);
for(int i = 0;i <= (int)pow(2,n);i++){//将0~2^n的数转化成01序列,实际上只需要1~2^n-1
dTob(i);
}
for(int i = 0;i < cnt1;i++){
string lines;
for(int j = 0;j < n;j++){//生成string类型的结果序列
if(nums[i][j]){
lines += '0' + j + 1;
lines += ' ';
}
}
myArray.insert(lines);
}
set<string>::iterator it = myArray.find("1 ");
for(;it != myArray.end();it++){
for(int i = 0;i < (*it).size();i++){
if((*it)[i] == '0' + 10)//特判数字10
cout << 10;
else
cout << (*it)[i];
}
cout << endl;
}
return 0;
}
进一步的思考
当做到最后一题时,我笑了,用上述思路可以清晰地得到由一个即将有序的序列逆推回来的初始序列(再想想,我又哭了,其中有些不符合要求的序列无法筛掉。。。)