回溯总结-惭愧惭愧

关于回溯有一个很好的文章
【https://leetcode-cn.com/problems/subsets/solution/xiang-xi-jie-shao-di-gui-hui-su-de-tao-lu-by-reedf/】

回溯法有一个书写模板,大致如下:

1. 一定范围的解释:根据具体的题目意思,决定范围的起止位置。
一般结束的位置是集合的末尾,起始位置可能是0、first、first+1,具体的还是要根据题目,可能配合visited数组更加方便理解。

void func(index, currAns, answer)
	if(达到终止条件){
		//判断当前结果是否符合条件
		answer.add(currAns)
	}
	//当前结点及同一层次的其它未搜索结点
	//使用循环扫描
	for(i: 一定范围){
		//将当前结点加入解
		currAns.add(第index个结点)
		//对第index结点的子结构进行递归
		func(index+1, currAns, answer)
		//将第index结点移出解
		currAns.add(第index个结点)
	}
}

题目:leetcode 46 全排列问题

给定nums数组,输出元素的全排列。
起止位置的决定:全排列问题不允许元素重用,使用visited数组。
代码如下:

public List<List<Integer>> permute(int[] nums) {
        List<List<Integer>> answer=new ArrayList<>();
        if(nums==null||nums.length==0) return answer;
		
        List<Integer> numsList=new ArrayList<>();
        boolean[] visited=new boolean[nums.length];
		//转换为list
        for(int i=0;i<nums.length;i++){
            numsList.add(nums[i]);
        }
        permuteCore(numsList,visited,new ArrayList<>(),answer);
        return answer;
    }

    public void permuteCore(List<Integer> numsList,boolean[] visited, List<Integer> currAns, List<List<Integer>> answer){
        if (currAns.size() == numsList.size()) {
        //两个size相等,说明得到了一个全排列,重新包装一下放到答案集合
            answer.add(new ArrayList<>(currAns));
            return;
        }
		//注意这里的边界,因为是全排列,需要分别以每一个元素作为起点
		//与子集问题的不同点
        for(int i=0;i<numsList.size();i++){
        	//已经加过的就不要重复添加
            if(visited[i]) continue;
            currAns.add(numsList.get(i));
            visited[i]=true;
            permuteCore(numsList,visited,currAns,answer);
            currAns.remove(numsList.get(i));
            visited[i]=false;
        }
    }

题目:leetcode 78 子集问题

给定一个集合,输出集合所有子集
代码如下:

public List<List<Integer>> subsets(int[] nums) {
        List<List<Integer>> answer=new ArrayList<>();
        if (nums == null || nums.length == 0) {
            return answer;
        }

        subsetsCore(nums,0,answer,new ArrayList<>());
        return answer;
    }

    public void subsetsCore(int[] nums, int index, List<List<Integer>> answer, List<Integer> currentAns) {
    	//这里比较难一些,为什么每一个遍历到的结点都需要添加到解集合
    	//因为整个搜索树的所有结点,包括根结点空集,都是一种合法子集
    	//在回溯到上一个结点之后是不会重复添加的,回溯之后会立刻向下一个结点遍历
        answer.add(new ArrayList<>(currentAns));
        for(int i=index;i<nums.length;i++){
            currentAns.add(nums[i]);
            subsetsCore(nums,i+1,answer,currentAns);
            currentAns.remove(currentAns.size()-1);
        }
    }

题目:LeetCode39 组合总和

给定一个元素集合与一个整数target,给出所有可能构成target的元素集合。元素可重用。
这个代码循环的边界确定比较有意思,因为组合的解允许元素重用,但是不允许元素相同排列不同的解存在,所以每次递归循环都从当前元素开始。
代码如下:

	public List<List<Integer>> combinationSum(int[] candidates, int target) {
        List<List<Integer>> answer=new ArrayList<>();
        if (candidates == null || candidates.length == 0 || target <= 0) {
            return answer;
        }

        Arrays.sort(candidates);
        combinationSumCore(0,candidates,target,candidates[0],new ArrayList<>(),answer);
        return answer;
    }

    public void combinationSumCore(int first, int[] cadidates, int target, int min,
                               List<Integer> currAns, List<List<Integer>> answer){
        if(target==0){
            answer.add(new ArrayList<>(currAns));
            return;
        }

        if(target<min){
            return;
        }

        for(int i=first;i<cadidates.length;i++){
            currAns.add(cadidates[i]);
            combinationSumCore(i,cadidates,target-cadidates[i],cadidates[i],currAns,answer);
            currAns.remove(currAns.size()-1);
        }
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章