题目
找出 n 个自然数(1、2、3 … n)中取 r 个数的组合。例如 n = 5, r = 3 时的组合如下:
5 4 3
5 4 2
5 4 1
5 3 2
5 3 1
5 2 1
4 3 2
4 3 1
4 2 1
3 2 1
思路1
当 r 很小的时候,当然可以通过 r 重循环的方式解决。例如 r = 3, 使用三重循环即可,主要代码如下:
for (int i = 1; i <= n; i++) {
for (int j = i + 1; j <= n; j++) {
for (int k = j + 1; k <= n; k++) {
System.out.println(i + " " + j + " " + k + " ");
}
}
}
但是,当 r 很大时,用循环可以实现,但很不现实(代码冗余,且时间复杂度也会很大)。我们分析,为什么可以使用循环,因为,这个问题中包含子问题。
代码(Java)
package bean;
import java.util.Scanner;
public class ex1 {
private static int[] com = new int[100];
// R = r,表示要取的数的个数
private static int R;
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.print("请输入 n 和 r:");
int n = sc.nextInt();
int r = sc.nextInt();
R = r;
dfs(n, r);
}
// 递归函数
public static void dfs(int n, int r) {
// 递归结束的条件,数选够了就输出
if (r == 0) {
for (int i = R; i >= 1; i--) {
System.out.print(com[i] + " ");
}
System.out.println();
return;
}
for (int i = n; i >= r; i--) {
// 固定第 r 个数(n、n-1、n-2 ... r),剩余数中取剩下的 r - 1 个数
com[r] = i;
dfs(i - 1, r - 1);
}
}
}
运行结果
代码(c++)
#include <bits/stdc++.h>
using namespace std;
const int N = 100;
int com[N];
int R;
void dfs(int n, int r);
int main() {
cout << "请输入 n 和 r:";
int n, r;
cin >> n >> r;
R = r;
dfs(n, r);
return 0;
}
void dfs(int n, int r) {
// 递归结束的条件,数选够了就输出
if (r == 0) {
for (int i = R; i >= 1; i--) {
cout << com[i] << " ";
}
cout << endl;
return;
}
for (int i = n; i >= r; i--) {
// 固定第 r 个数(n、n-1、n-2 ... r),剩余数中取剩下的 r - 1 个数
com[r] = i;
dfs(i - 1, r - 1);
}
}
运行结果
思路2
对于每个数,有 “选” 或 “不选” 两种情况,因此,递归时分 “选” 或 “不选” 两种情况讨论即可。递归边界:递归到最后一个数或者选够了 r 个数。直接上代码:
代码(Java)
package bean;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
public class ex2 {
private static List<Integer> com = new ArrayList<>();
private static int n, r;
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.print("请输入 n 和 r:");
n = sc.nextInt();
r = sc.nextInt();
// 从 1 开始决策,最开始一个数都还没有选择
dfs(1, 0);
}
// loc 表示决策到第几个数,total 表示当前总共选了多少个数
private static void dfs(int loc, int total) {
// 数选够了 输出
if (total == r) {
for (var e : com) {
System.out.print(e + " ");
}
System.out.println();
return;
}
// 递归边界
if (loc > n) {
return;
}
// 选 loc
com.add(loc);
dfs(loc + 1, total + 1);
// 这里需要删除刚才选择的 loc (恢复现场)
com.remove(com.size() - 1);
// 不选 loc
dfs(loc + 1, total);
}
}
运行结果
代码(c++)
#include <bits/stdc++.h>
using namespace std;
vector<int> com;
int n, r;
// loc 表示决策到第几个数,total 表示当前总共选了多少个数
void dfs(int loc, int total);
int main() {
printf("请输入 n 和 r: ");
cin >> n >> r;
dfs(1, 0);
return 0;
}
void dfs(int loc, int total) {
// 数选够了 输出
if (total == r) {
for (auto e : com) {
cout << e << " ";
}
cout << endl;
return;
}
// 递归边界
if (loc > n) {
return;
}
// 选 loc
com.push_back(loc);
dfs(loc + 1, total + 1);
// 这里需要删除刚才选择的 loc (恢复现场)
com.pop_back();
// 不选 loc
dfs(loc + 1, total);
}