作者: 一字马胡
转载标志 【2017-12-10】
更新日志
日期 | 更新内容 | 备注 |
---|---|---|
2017-12-10 | 学习递归 | 关于递归的算法问题 |
问题描述
给定一个长度为n的整数数组a1,a2,a3....,判断是否可以从这组数中挑选若干个数,使得这些被挑选的数的和恰好为k。
限制条件:
- 1 <= n <= 20
- -10^8 <= ai <= 10^8
- -10^8 <= k <= 10^8
题意分析
这是一种可以提取共性的问题,也就是说题目给出了一组元素(Element),而你的程序需要对每个元素进行判断,判断这个元素是否可选,接下来你需要分析,如果一个元素可选,会对结果造成什么影响,如果不可选呢?说到这里,你应该想到了一棵二叉树了吧,每个节点(Node)就是一个Element,而它的两个分叉就是可选和不可选两种状态下的结果。下面展示了一个简单的思路图:
在上面的图片中,Root Node属于choose动作的起点,在这里可以进行一些初始化,然后开始进行选择,如果选择当前Element,则状态转移到Check Node,否则转移到UnChecked Node,然后到达相应的Node上之后再进行一些状态更新,然后做同样的处理,很明显,这里面有递归的过程。
对于本例子中的问题,从第一个Element开始进行选择,每一个Element都是可选或者不可选两种状态,我们应该全局持有一个sum,标志这目前的状态,并且每次进行选择之后都需要做一下判断,如果已经满足要求,那么就应该立刻返回,结束递归,这也叫剪枝,当然,还可以做一些其他的剪枝,比如选择了某个Element之后,sum和大于要求了,那么该路径肯定不符合要求,没有必要递归下去了,可以立刻结束。
基于上面的分析,本例使用上面的算法时间复杂度为O(2^n)。
问题解决
下面是该问题的某个解决方案,使用java语言:
package io.hujian.common;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
/**
* project: algo-collection
* package: io.hujian.common
* Author: <p> hujian</p>
* Created on
* 2017/12/3 10:13.
* <p>
* #Rich Comment layer#
*
*/
public class partSumProblem {
public static void main(String[] args) {
int[] array = new int[]{3, 5, 9, 10, 2, 4};
List<Integer> chooseEleList = new ArrayList<Integer>();
boolean result = new Solver(array, 17).check(0, 0, chooseEleList);
System.out.println(result);
chooseEleList.forEach(new Consumer<Integer>() {
public void accept(Integer integer) {
System.out.print(integer + " ");
}
});
}
}
class Solver {
private final int[] array;
private final int length;
private final int requireSum;
public Solver(int[] array, int requireSum) {
this.array = array;
this.length = array.length;
this.requireSum = requireSum;
}
/**
* 递归检测是否已经符合要求了
*
* @param index 当前的choose Element
* @param partSum 当前的sum
* @return 客服满足要求
*/
public boolean check(int index, int partSum, List<Integer> chooseEleList) {
if (index == length) { // 已经到头了,防止数组越界,只能返回了
return partSum == requireSum;
}
if (partSum == requireSum) { // 判断是否满足要求了
return true;
}
if (partSum > requireSum) { // 是否可以剪枝
return false;
}
/* 试试check这条路 */
if (check(index + 1, partSum, chooseEleList)) {
return true;
}
/* 试试uncheck这条路 */
if (check(index + 1, partSum + array[index], chooseEleList)) {
chooseEleList.add(array[index]);
return true;
}
/* 无路可走了 */
return false;
}
}
如果有这样的路径,那么会将选择出来的Element打印出来。更为高效的解放将在未来进行补充。