回溯算法归纳

回溯的两种思路

看不同的解题方法,形成不同的思维。
先说结论。回溯解题思路1:是对可选择每个元素,采取不选择、选择两种策略,不断递归下去。最近看花花酱的视频,得到思路2:要完成目标分为n个阶段,每个阶段会有不同的选择,在每个阶段尝试不同选择。
下面,以具体leetcode39为例,来说明。

题目描述

输入:一个不包含重复元素的数组candidates,一个int target
输出:用candidates中的元素组成target,输出有多少种组合。输出结果不能重复。
规则:candidates中的元素可以使用多次。
例如candidates=[2,3,6,7] target=7,返回
[
[7],
[2, 2, 3]
]

按照思路1解决

结果集是包含多种解法,用List<List> result 表示。每一种解法是一个List list。
对于候选数组candidates中的每个元素,我们可能使用,也可能不使用。
例如对于index = 0,我们可能使用candidates[0],也可能不使用。
不使用candidates[0],target不变,list不变,index+1,进入选择candidates[1]阶段。
使用candidates[0],target变小,list添加使用candidates[0],index+1,进入选择candidates[1]阶段。注意因为题目说一个元素可以重复使用,那candidates[0]最多可以使用target/candidates[0]次。

在代码中使用path记录每个元素选择多少次。path[0]=candidates[0]选择的次数。
退出条件:当target=0,问题解决,添加结果集。下标超出范围的时候,或者target=0,退出循环。

	private List<List<Integer>> result = new ArrayList<List<Integer>>();
	private int[] path;
	private int[] candidates;
	/**
	 * path[i] = 使用nums[i]元素的次数
	 * @param candidates
	 * @param target
	 * @return
	 */
	public List<List<Integer>> combinationSumV2(int[] candidates, int target) {
		result.clear();
		path = new int[candidates.length];
		this.candidates = candidates;
		dfsV2(0, target);
		return result;
	}

	private void dfsV2(int idx, int target) {
		if (target == 0) {
			List<Integer> solution = new ArrayList<Integer>();
			for (int i = 0; i < path.length; i++) {
				for (int j = 1; j <= path[i]; j++) {
					solution.add(candidates[i]);
				}
			}
			result.add(solution);
			return;
		}
		if (idx >= candidates.length || target < 0) {
			return;
		}
		int maxCount = target / candidates[idx];
		for (int i = 0; i <= maxCount; i++) {
			path[idx] = i;
			dfsV2( idx + 1, target - i * candidates[idx]);
			path[idx] = 0;
		}
	}

按思路2解决

target=7,可以选择2,3,6,7任意一个元素,也就是candidates中下标从0到4的任意一个元素。选择2,list添加2,进入状态target=5。
target=5,可以选择2,3。因为6,7大于5,不用选择剪枝。选择2,list添加2,进入target=3。
target=3,可以选择2,3。因为6,7大于3,不用选择剪枝。选择2,list添加2,进入target=1。

退出条件:当target=0,问题解决,添加结果集。

按照这个思路画递归树,发现有重复的解。可以通过限制下标起点解决。例如图中选择3之后,可以选择的元素就在3,6,7之间,因为3,6,7都大于2,所以就不用继续递归了。所以递归树上的状态需要加上起始下标。修改递归树。

	public List<List<Integer>> combinationSumV3(int[] candidates, int target) {
		result.clear();
		Arrays.sort(candidates);
		this.candidates = candidates;
		if(candidates==null || candidates.length==0) return result;
		dfsV3(0,target,new ArrayList<Integer>());
		return result;
	}

	/**
	 * 在当前状态下,可以选择从start到数组结尾的任意一个元素到结果集
	 *
	 * @param start
	 * @param target
	 * @param list
	 */
	private void dfsV3(int start, int target,ArrayList<Integer> list) {
		if(target == 0) {
			//注意结果需要完全拷贝
			result.add(new ArrayList<Integer>(list));
			return;
		}
		if(start>= candidates.length) return;
		for(int i = start;i<candidates.length;i++){
			if(candidates[i] > target) break;
			list.add(candidates[i]);
			dfsV3(i,target-candidates[i],list);
			list.remove(list.size() - 1);
		}

	}

每一次dfs是树的一个高度。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值