回溯總結-慚愧慚愧

關於回溯有一個很好的文章
【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);
        }
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章