题目描述:
在长度为n的单调递增型数组a[n]中,1 <= a[i] <= 100,编写一个程序打印出和为m的所有组合。
例如:a[] = {1,2,3,5,6},m=11,打印出
5 6
2 3 6
1 2 3 5
对于这道题,我的第一感觉就是道类似0-1背包问题,给出的物品中,选出所有刚好能装满背包的物品
于是开始解决这道题就用回溯法
//rem表示剩余的值,flag用于标记每个位置的数是否被用到,k即为当前要计算的位置
public static void backtrace(int[] a,int m,int rem,boolean[] flag,int k) {
//剩余值为0了,应该输出并返回
if (rem == 0) {
output(a,flag);
System.out.println();
return;
}
//剩余值小于0,自然要返回了
if (rem < 0) {
return;
}
//位置小于等于0,肯定得返回,不然就栈溢出了
if (k <= 0) {
return;
}
for(int i = k;i >= 0;i--) {
//标记下当前位置的数被用到
flag[i] = true;
rem -= a[i];
// k-1 往前进行计算
backtrace(a, m, rem, flag, k-1);
//这一步回溯,再算一下当前位置的数不被用到的情况
flag[i] = false;
rem += a[i];
backtrace(a, m, rem, flag, k-1);
}
}
private static void output(int[] a, boolean[] flag) {
for(int i = 0;i < a.length;i++) {
if (flag[i]) {
System.out.print(a[i]+" ");
}
}
}
看上去逻辑好像没什么问题,运行一下结果看看
结果让我大吃一惊,除了第一第二个是对的,后面全部错了
有些结果被重复输出了,说明有些位置的数被重复用了,很明显,这种方法行不通
换种方法思考,每个位置的数只有选和不选两种情况,可以用0,1表示
比如 00011 则表示例的数组{1,2,3,5,6}中选了5和6,那么计算结果就是11
所以解决这题的方法可以穷举出所有n(数组长度)位的二进制数,并且把当前位置为1的数相加计算,结果为m即可
public static void binaryCal(int[] a,int m) {
int n = a.length;
//最大的数为2的n次方
int max = 1 << n;
for(int i = 1;i < max;i++) {
//转成二进制数
String binaryNum = Integer.toBinaryString(i);
//转成相同的位数,不足n位的在前补0
binaryNum = toSameLen(binaryNum,n);
char[] bitNum = binaryNum.toCharArray();
int sum = 0;
for(int j = 0;j < bitNum.length;j++) {
//二进制数当前位置为1,则加起来
if (bitNum[j] == '1') {
sum += a[j];
}
}
//和为m了,输出
if (sum == m) {
output(bitNum,a);
}
}
}
private static String toSameLen(String binaryNum, int len) {
//数的长度
int numLen = binaryNum.length();
if (numLen == len) {
return binaryNum;
}
StringBuilder sb = new StringBuilder();
//差几位补几个0
for(int i = 0;i < len - numLen;i++) {
sb.append(0);
}
return sb.append(binaryNum).toString();
}
private static void output(char[] bitNum, int[] a) {
for(int i = 0;i < bitNum.length;i++) {
if (bitNum[i] == '1') {
System.out.print(a[i]+" ");
}
}
System.out.println();
}
以上代码运行结果
跟范例输出的一模一样
虽然结果对了,代码也容易理解,但并不是最好的方法
①没有用到题目给出的单调递增条件
②每个数字都要做一次二进制转换,并且还要补0,太过耗时,如果数组长度越大,那么效率就越低