首先来首歌曲来放松一下吧!

题目链接:92. 递归实现指数型枚举


题目背景:

多种做法!

题目描述

从 1~n 这 n 个整数中随机选取任意多个,输出所有可能的选择方案。

输入格式

输入一个整数n。

输出格式

每行输出一种方案。

同一行内的数必须升序排列,相邻两个数用恰好1个空格隔开。

对于没有选任何数的方案,输出空行。

本题有自定义校验器(SPJ),各行(不同方案)之间的顺序任意。

数据范围

1≤n≤15

输入样例:

1
3

输出样例:

1
2
3
4
5
6
7
8

3
2
2 3
1
1 3
1 2
1 2 3

题目分析:

题目要求:

输出任任意位的组合(从小到大),包括0位组合(即输出空行),不限制输出顺序!

解题思路:

详见下方四种解法:

一篇好理解的题解,点击这里!

题解:

注意:选择0位也是一种情况,千万别忘记!要输出一个空行!

解法一:本人能想到的最粗糙的做法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <iostream> 

using namespace std;

const int N = 15;
int n, b[N];
bool flag[N];

void dfs(int k, int cnt, int pos)
{
if(cnt >= k)
{
for(int i = 0; i < k; i++) cout << b[i] << " ";
cout << endl;
return;
}

for(int i = pos + 1; i <= n; i++)
{
if(!flag[i])
{
b[cnt] = i;
flag[i] = true;
dfs(k, cnt + 1, i);
flag[i] = false;
}
}
}

int main()
{
cin >> n;
for(int i = 0; i <= n; i++) dfs(i, 0, 0);
return 0;
}

解法二:使用二进制优化:

和之前做过的类似:将每一位的选择变成0和1的二进制即可,和直接使用数组存储(解法一)不一样的点:

  • 添加第 i 位时:state |= 1 <<(i - 1);
  • 最后还要恢复原状态:state ^= 1 << (i - 1);

用到了异或和按位或,不懂的去学习一下!

为何数组不需要恢复状态,而二进制需要恢复状态?

因为数组存储时时从下标为0开始存储,一次dfs结束后,会进入下一次for循环,此时,数组存储的下标仍然是cnt,没有变化,即进行了覆盖,不会影响;

然而:使用二进制就不一样了,二进制存储的是每一位的状态,没有下标的关系,如果不进行恢复状态,下一次for循环可不是数组的原位覆盖,而是在新的地方置为了1,这样state状态就会多一位被选择的数,造成错误答案!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <iostream> 

using namespace std;

const int N = 15;
int n, b[N];
bool flag[N];

void dfs(int state, int k, int cnt, int pos)
{
if(cnt >= k)
{
for(int i = 0; i < n; i++)
if(state >> i & 1) cout << i + 1 << " ";
cout << endl;
return;
}

for(int i = pos + 1; i <= n; i++)
{
if(!flag[i])
{
state |= 1 <<(i - 1);
flag[i] = true;
dfs(state, k, cnt + 1, i);
flag[i] = false;
state ^= 1 << (i - 1);
}
}
}

int main()
{
cin >> n;
for(int i = 0; i <= n; i++) dfs(0, i, 0, 0);
return 0;
}

解法三:状态压缩非递归

类似于第二种解法:用state来存储二进制集合,当然共有 2 n种,然后第二层for循环直接去从第0位开始扫描即可,遇到1 就输出,遇到0就跳过即可!

本人最喜欢这种做法,既好理解又简洁!推荐!!!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream> 

using namespace std;

const int N = 15;
int n, state[N];
bool flag[N];

int main()
{
cin >> n;
for(int state = 0; state < 1 << n; state++)
{
for(int j = 0; j < n; j++)
if(state >> j & 1) cout << j + 1 << " ";
cout << endl;
}

return 0;
}

解法四:状态压缩递归形式(来自yxc大神)

也就是第三种解法的递归写法,仍然是每一位数都有两种选择,选与不选,所以,此处的dfs的两个参数,第一个代表当前扫描到了哪一位,state表示当前状态的二进制为情况!

两种状态:

  • dfs(u + 1, state); :不选,state 没有进行改变!
  • dfs(u + 1, state | (1 << u)); :选择,state 已经将 u + 1加入到了state中!

退出条件,扫描到最后一项时进行输出和判断!

总而言之:就像是一个二叉搜索树,都有选与不选两种情况,答案则在最后的叶子节点上!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <iostream> 

using namespace std;

const int N = 15;
int n;

void dfs(int u, int state)
{
if(u == n)
{
for(int i = 0; i < n; i++)
if(state >> i & 1) cout << i + 1 << " ";
cout << endl;
return;
}
dfs(u + 1, state);
dfs(u + 1, state | (1 << u));
}

int main()
{
cin >> n;
dfs(0, 0);
return 0;
}