以一个题目来深刻理解回溯和动态规划。

LeetCode
https://leetcode.com/problems/combination-sum/description/
39. Combination Sum
Given a set of candidate numbers (C) (without duplicates) and a target number (T), find all unique combinations in C where the candidate numbers sums to T.

The same repeated number may be chosen from C unlimited number of times.

Note:
All numbers (including target) will be positive integers.
The solution set must not contain duplicate combinations.
For example, given candidate set [2, 3, 6, 7] and target 7,
A solution set is:
[
[7],
[2, 2, 3]
]
1.回溯算法
回溯法按深度优先策略搜索问题的解空间树。首先从根节点出发搜索解空间树,当算法搜索至解空间树的某一节点时,先利用剪枝函数判断该节点是否可行(即能得到问题的解)。如果不可行,则跳过对该节点为根的子树的搜索,逐层向其祖先节点回溯;否则,进入该子树,继续按深度优先策略搜索。
回溯法的基本行为是搜索,搜索过程使用剪枝函数来为了避免无效的搜索。剪枝函数包括两类:
1. 使用约束函数,剪去不满足约束条件的路径;
2. 使用限界函数,剪去不能得到最优解的路径。
问题的关键在于如何定义问题的解空间,转化成树(即解空间树)。解空间树分为两种:子集树和排列树。两种在算法结构和思路上大体相同。

对于本题我们的目标非常明确,就是利用剪枝函数剪掉不满足约束条件的值。首先定义问题的解空间树。
这里写图片描述
由上图解题思路就非常明确了。下面是代码实现。

    /*
    * backtrack
    * backtrack函数中nums表示当前的combination,remain表示combination的和与target的数值差。
    * index表示当前遍历cans的位置,是为了保持nums中从大到小的序列从而确保唯一性而设置的,
    * numcount是nums中有效元素的界限位置。
    * */
    public static List<List<Integer>> combinationSum(int[] candidates, int target){
        List<List<Integer>> result = new ArrayList<>();
        int[] length = new int[target];
        Arrays.sort(candidates);
        backtrack(result,length,candidates,target,0,0);
        return result;
    }
    public static void backtrack(List<List<Integer>> result,int[] nums,int[] cans,int remain,int index,int numcount){
        if(remain == 0){
            List<Integer> list = new ArrayList<>();
            for (int i = 0; i < numcount; i++) {
                list.add(nums[i]);
            }
            result.add(list);
            return;
        }else if(remain < 0){
            return;
        }else if(remain>0){
            for (int i = index; i < cans.length; i++) {
                nums[numcount] = cans[i];
                backtrack(result,nums,cans,remain-cans[i],index,numcount+1);
                index++;
            }
        }
    }

2.动态规划算法
动态规划算法基本思想与分治法类似,也是将待求解的问题分解为若干个子问题,按一定的顺序求解子阶段,前一子问题的解,为后一子问题的求解提供了有用的信息。在求解任一子问题时,列出各种可能的局部解,通过决策保留那些有可能达到最优的局部解,丢弃其他局部解。依次解决各子问题,最后一个子问题就是初始问题的解。
由于动态规划解决的问题多数有重叠子问题这个特点,为减少重复计算,对每一个子问题只解一次,将其不同阶段的不同状态保存在一个二维数组中。
与分治法最大的差别是:适合于用动态规划法求解的问题,经分解后得到的子问题往往不是互相独立的(即下一个子阶段的求解是建立在上一个子阶段的解的基础上,进行进一步的求解)。

本问题是求解和为一个指定target的所有集合。我们可以把他分解为求target为1,2,3…的子问题。
实现代码如下:
其中ksum用于求解target为k的解,每一次执行ksum时遍历cans,对于每一个cans元素,target则可以为cans和(target-cans[x])解集的和。,

   /*
    * dynamic program
    * 1.求和为target的List分解为和为1-target的list。(分解为子任务)
    * 2.单步循环candidates得到和为所求sub-target的所有可能组成方式。
    *   2.1 sub-target的combination在求解过程中list按asc顺序,避免防止重复。
    * */
 public static List<List<Integer>> combinationSumD(int[] candidates, int target) {
        int k = target/candidates[0];
        List<List<List<Integer>>> targetList = new ArrayList<>();
        List<List<Integer>> list = new ArrayList<>();
        targetList.add(list);
        Arrays.sort(candidates);
        for (int i = 1; i <=target ; i++) {
            List<List<Integer>> kSum = kSum(targetList,candidates,i);
            targetList.add(kSum);
        }
        return targetList.get(target);
    }
    public static List<List<Integer>> kSum( List<List<List<Integer>>> targetList,int[] candidates,int k){
        List<List<Integer>> list = new ArrayList<>();
        for (int i = 0; i < candidates.length; i++) {
            if(candidates[i] > k){
                break;
            }else if(candidates[i] == k){
                list.add(Arrays.asList(candidates[i]));
            }else{
                for(List<Integer> list1:targetList.get(k-candidates[i])){
                    if(candidates[i]>=list1.get(list1.size()-1)) {
                        List<Integer> tmp = new ArrayList<>(list1);
                        tmp.add(candidates[i]);
                        list.add(tmp);
                    }
                }
            }
        }
        return list;
    }
  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值