【递归】部分和问题

作者: 一字马胡
转载标志 【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打印出来。更为高效的解放将在未来进行补充。

    原文作者:一字马胡
    原文地址: https://www.jianshu.com/p/4c0cd6603ad3
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞