Leetcode刷题感想——回溯法

回溯法
回溯法往往用来找出一个问题的所有可能解(而不是简单的回答出有多少个解,如果是问解的个数,可能有更好的办法,也就是动态规划)。

回溯法是DFS的一种,但又和DFS不一样,DFS往往求解连通面积,可达性的问题,而回溯法往往求解满足某一条件的所有组合,比如二叉树的路径和,排列组合等经典问题。

回溯法在代码层面,最明显的特征就是

  1. 终止条件的判断
  2. 如果当前组合满足条件,加入结果集
  3. 开始遍历元素
  4. 把某一元素加入组合
  5. 递归调用
  6. 再把该元素剔除组合

257. 二叉树所有路径 为例,进行简单的解释:

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public List<String> binaryTreePaths(TreeNode root) {
        if(root == null){
            return new ArrayList<String>();
        }

        List<String> res = new ArrayList<String>();
        backtracking(root,new ArrayList<Integer>(),res);

        return res;
    }

    private void backtracking(TreeNode node, List<Integer> list,List<String> res){
        if (node == null)
            return;
		//4,把某一元素加入组合
        list.add(node.val);
		//1 ,终止条件的判断
        if(node.left == null && node.right == null){  
        	//2,如果当前组合满足条件,加入结果集     
            res.add(buildPath(list));    
        }else{
        	//3 开始遍历元素 这个例子有些特殊,可以看作是遍历左右子树
        	//5 递归调用backtracking
            backtracking(node.left, list, res);
            backtracking(node.right, list, res);
        }
        //6,再把该元素剔除组合
        list.remove(list.size()-1);
    }

    private String buildPath(List<Integer> list){
        int size = list.size();
        StringBuilder sb = new StringBuilder();
        for(int i =0;i<size;i++){
            sb.append(list.get(i));
            if(i != size-1)
            sb.append("->");
        }
        return sb.toString();
    }
}

其实这些是最基本的,如果想要达到回溯法的进阶要求,还得往下看。

其实回溯法最经典的应用当属排列组合,这是两个问题,往往有以下几种形式:
排列+无重复元素
排列+有重复元素
组合+无重复元素
组合+有重复元素

区别在哪里呢?

排列与组合的区别
排列要考虑元素的顺序,顺序不一样,是不一样的组合,需要引入boolean数组进行标识,是否已经在当前的组合里了。
组合不需要考虑顺序。

有重复元素和无重复元素
如果有重复元素,往往需要排序+利用boolean数组去重,因此经常会用到这一段代码:

for(int i = 0;i<nums.length;i++){
            if(visited[i])
                continue;

            //相对于无重复元素 新增逻辑
            if(i>0&&nums[i]==nums[i-1]&&!visited[i-1])
                continue;

            visited[i] = true;
            list.add(nums[i]);
            backtracking(nums,res,list,visited);
            list.remove(list.size()-1);
            visited[i] = false;
        }

无重复元素,也要注意,元素个数是否可以无限制。

问题杂记:

  • 初始化
  List<List<Integer>> res = new ArrayList<>();
  • 加入结果集时要当心
if(list.size() == nums.length){
            //这里有一个坑,不能直接res.add(list);
            //因为list后面会变化,这样做相当于,res里所有元素都指向list的引用
            //会一模一样           
            res.add(new ArrayList<>(list));
            return;
        }
  • 剪枝

也就是遍历的时候不是从0开始,这个要看情况使用,往往用于组合+无重复元素

//不是从0开始
        for(int i = startIndex;i<candidates.length;i++){

            list.add(candidates[i]);
            backtracking(candidates,leftValue-candidates[i],i,res,list);
            list.remove(list.size()-1);
            
        }
  • boolean数组

什么情况下需要它呢?
1 有重复元素时(主要还是用来去重)
2 无重复元素+排列(标记该元素是否使用过)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值