回溯算法复习总结

本文详细介绍了回溯算法的基本原理,通过实例演示如何在组合问题中应用dfs,包括组合总数、去重策略,如电话号码组合、组合和2、全排列等。重点强调了递归终止条件和剪枝技巧,以及在不同场景下的代码实现和优化。
摘要由CSDN通过智能技术生成

敲响警钟之回溯算法真的很重要ballball了看看他

首先要明白所谓的回溯算法其实本质上就是递归的衍生,其实就是dfs

看一下回溯算法的模板

void backtracking(参数待定){
	if(设置终止条件){
		//设置所谓的终止条件,就是设置一个条件,当判断已经到达叶子结点之后应该进行如何的操作.
		xx.add();
		return;
	}
	for(){
		//这个for也是经典,会在这里遍历剩余集合的节点,有什么节点我们可以看着来
	}
}

切记,一整个回溯的算法,或者叫dfs算法,更像是一颗数的结构。在对于树做一个暴力遍历,求出每一种情况,然后进行求解。

77.组合

给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。

  1. 说明了返回的值是K个数的组合。那么所谓终止的条件,就是所谓的叶子结点,它的条件就是这个组合的长度达到了K。
  2. 这个是组合,并不是排序。所以我们开始规定不能有重复元素
class Solution {
    List<Integer> path = new LinkedList();//定义路径长度
    List<List<Integer>> res = new LinkedList();//设定返回值
    public List<List<Integer>> combine(int n, int k) {
        backtracking(n,k,1);
        return res;
    }

    public void backtracking(int n,int k,int startindex){
        if(path.size() == k){
            //终止条件就是Path的路径长度,其实在这用startindex就去重了,但是对于去重的这个理念我理解的还是不太ok
            res.add(new LinkedList(path));
            return;
        }

        //对于迭代循环
        for(int i = startindex;i<=n;i++){
            path.add(i);
            backtracking(n,k,i+1);
            path.remove(path.size()-1);
        } 
    }
}

一个基础的回溯算法写好了

这个时候我们假设一种情况也就是 n=4 k=4的时候。这个时候明显只有一种排列情况也就是[1,2,3,4]

在这里插入图片描述
核心点是,你后期的元素不够支撑。比如第一次取3,后面还能取1,但是需要的元素是3个,只能有两个元素给你用,当然不够。在这个地方就能进行剪枝

if((n-startindex+1) < k-path.size()) return;

综上,加入这一条语句。

17 电话号码的组合方式
这题还算简单pass

40 组合总数2

这题的大体思路还是相同的,但是在去重这一点很重要。这题的去重思路真的,一开始我的脑子里只有,先把结果查出来,然后对于结果去重这一个思路。
但是现在给出的思路是,纵向的代表同一个组合中的元素,横向的代表的是不同组合中的元素。纵向的是可以重复的,而横向的是不可以的。

    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        if(candidates == null || candidates.length==0) return res;
        Arrays.sort(candidates);
        visited = new boolean[candidates.length];
        
        backtracking(candidates,target,0);
        return res;
    }

    public void backtracking(int[] candidates,int target,int startindex){
        if(sum == target){
            //设置终止条件,相等的情况
            res.add(new LinkedList(path));
            return;
        }else if(sum > target) return;
    
        for(int i = startindex;i<candidates.length;i++){
            if(i>0 && candidates[i] == candidates[i-1] && !visited[i-1]) continue;
            visited[i] = true;
            sum += candidates[i];
            path.add(candidates[i]);
            backtracking(candidates,target,i+1);
            sum -= candidates[i];
            path.remove(path.size()-1);
            visited[i] = false;
        }
    }

39 组合总和

这道题很有意思,第一次做的时候做出来了,第二次做反而卡克了。
有一点在第一次没考虑竟然误打误撞做出来了。那就是对于相同元素的把握这个和分割的题目是一个道理。我一直在考虑怎么分割,怎么去做这个事情。其实情况已经包含了,我不知道如何去形容这种感觉很神奇自己体悟。

    int sum = 0;
    List<Integer> path = new LinkedList();
    List<List<Integer>> res = new LinkedList();
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        if(candidates == null || candidates.length == 0) return res;
        backtracking(candidates,target,0);
        return res;
    }

    public void backtracking(int[] candidates,int target,int startindex){
        if(sum == target){
            //设置终止条件,终止条件就是sum=target呗
            res.add(new ArrayList(path));
            return;
        }else if(sum > target) return;

        for(int i = startindex;i<candidates.length;i++){
            sum += candidates[i];
            path.add(candidates[i]);
            backtracking(candidates,target,i);
            sum -= candidates[i];

            path.remove(path.size()-1);
        }
    }

40 组合3

这题的核心在于去重,这个回溯算法也是分为横向和纵向的。横向的表示这一层的节点,纵向的表示同一个排列中的节点。排完序后,你去去重就是同一层的节点不能使用第二次,看看代码中是如何保证的,十分精妙。

    int sum = 0;
    List<Integer> path = new LinkedList();  //记录路径
    List<List<Integer>> res = new LinkedList(); //返回值
    boolean[] visited;
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        visited = new boolean[candidates.length];
        Arrays.sort(candidates);
         backtracking(candidates,target,0);
         return res;     
    }

    public void backtracking(int[] candidates,int target,int startindex){
        if(sum == target){
            res.add(new LinkedList(path));
            return;
        }else if(sum > target) return;

        for(int i = startindex;i<candidates.length;i++){
            if(i>0 && candidates[i]== candidates[i-1]){
                if(!visited[i-1]) continue;
            }
            visited[i] = true;
            sum += candidates[i];
            path.add(candidates[i]);
            backtracking(candidates,target,i+1);
            sum -= candidates[i];
            path.remove(path.size()-1);
            visited[i] = false;
        }
    }

46全排列

这个全排列的题目提醒了我两点

  1. visited[] 数组的用处是非常大的
  2. 如果每一次进入递归需要用到整个数组的时候,这个情况是不需要要startindex这个参数的
    List<List<Integer>> res = new LinkedList();
    List<Integer> path = new LinkedList();
    boolean[] visited;
    public List<List<Integer>> permute(int[] nums) {
        visited = new boolean[nums.length];
        backtracking(nums);
        return res;
    }
    public void backtracking(int[] nums){
        if(path.size() == nums.length){
            res.add(new LinkedList(path));
            return;
        }
        for(int i=0;i<nums.length;i++){
            if(visited[i]) continue;
            visited[i] = true;
            path.add(nums[i]);
            backtracking(nums);
            visited[i] = false;
            path.remove(path.size()-1);
        }
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值