第十一天打卡——回溯

文章讨论了如何将子集、组合、分割和递增子序列等问题抽象为树形结构,并运用回溯算法解决,涉及去重技巧。还介绍了全排列问题和重新安排行程的解决方案,包括N皇后问题的独特回溯处理方式和数独的解法.
摘要由CSDN通过智能技术生成

78.子集 

把 子集问题、组合问题、分割问题都抽象为一棵树的话,那么组合问题和分割问题都是收集树的叶子节点,而子集问题是找树的所有节点!

由于子集问题也算是一种组合问题,它的结果集是无序的,因此在回溯算法的for循环遍历中,也需要用到startIndex指针

子集问题抽象成树形结构为:

 思路比较简单,根据树形结构图可以实现出来。回溯三步走

1、明确参数返回值

2、明确终止条件:startIndex >= nums.length(类似分割问题)

        其实可以不需要加终止条件,因为startIndex >= nums.size(),本层for循环本来也结束了

3、明确单次逻辑:

        求取子集问题,不需要任何剪枝!因为子集就是要遍历整棵树。

result.push_back(path); // 收集子集,要放在终止条件上面,否则会漏掉空集

90.子集II 

78.子集 (opens new window)区别就是集合里有重复元素了,需要对求取的子集要去重。

本题和40.组合总和II (opens new window)是一个套路。理解“树层去重”和“树枝去重”

去重需要先对集合排序

代码实现:

同样没有使用used数组,先排序后根据startIndex指针实现了for循环去重。

ps:如果要是全排列的话,每次要从0开始遍历,为了跳过已入栈的元素,需要使用used。


491.递增子序列

难点:

1、子集长度>1,即不能把全部节点纳入结果集

2、在不对序列重新排序的情况下去重

新的去重逻辑

新建一个set集合,用来记录本层中的元素是否重复使用了。详细看以下代码

//递增子序列
    Deque<Integer> path = new LinkedList<>();
    List<List<Integer>> result = new ArrayList<>();
    public List<List<Integer>> findSubsequences(int[] nums) {
        findBacktrack(nums, 0);
        return result;
    }
    public void findBacktrack(int[] nums, int startIndex){
        if (path.size() > 1){  // 如果path>1,存入结果集
            result.add(new ArrayList<>(path));
        }
        if (startIndex >= nums.length) return;  // 递归到序列尾部,结束
        //是记录本层元素是否重复使用,新的一层uset都会重新定义(清空),所以要知道uset只负责本层!所以没有对应的remove操作
        HashSet<Integer> sets = new HashSet<>();
        for (int i = startIndex; i < nums.length; i++){
            if ((path.size() != 0 && nums[i] < path.getLast())||sets.contains(nums[i])){
                continue;
            }
            sets.add(nums[i]); // 记录这个元素在本层用过了,本层后面不能再用了
            path.addLast(nums[i]);
            findBacktrack(nums, i+1);
            path.removeLast();
        }
    }

46.全排列

之前学习了77.组合问题 (opens new window)、 131.分割回文串 (opens new window)78.子集问题 (opens new window),接下来看一看排列问题。

排列问题需要一个used数组,标记已经选择的元素。

used数组,其实就是记录树枝层面此时path里都有哪些元素使用了,一个排列里一个元素只能使用一次。

排列问题抽象成树形结构:

 代码实现:常规的回溯三步走。


47.全排列 II

对46.全排列问题的树层方面去重,与40.组合总和II (opens new window)90.子集II (opens new window)是一样的套路。

先排序后去重。

代码实现:利用used数组

used[i] == 1 (true),说明同一树枝nums[i]使用过

used[i] == 0 (false),说明将在同一树层使用nums[i]

判断同一树层是否重复有两点

1、当前值与前一个值相等。    

2、前一个值将在本树层使用。used[i-1] != true


332.重新安排行程 

递归三步走:

1、明确参数和返回值:

        我们不遍历整个树,找到结果就返回,所以有返回值。

2、明确终止条件:

        最终结果的路径=tickets列表长度+1

3、明确单次递归逻辑:

        如果used=false且单个行程的第一个地点和当前pathString路径最后一个地点一致,递归并回溯。

//重新安排行程
List<String> pathString = new LinkedList<>();
List<String> resultString = new LinkedList<>();
public List<String> findItinerary(List<List<String>> tickets) {
    Collections.sort(tickets, (a, b) -> a.get(1).compareTo(b.get(1))); //先排序
    boolean[] used = new boolean[tickets.size()];
    pathString.add("JFK");
    findItineraryBacktrack(tickets, used);
    return resultString;
}
//明确返回值:我们不遍历整个树,找到结果就返回,所以有返回值。
public static boolean findItineraryBacktrack(List<List<String>> tickets, boolean[] used){
    //明确终止条件:最终结果的路径=tickets列表长度+1
    if (pathString.size() == tickets.size() + 1){  
        resultString = new LinkedList<>(pathString);
        return true;
    }
    //明确单次递归逻辑:如果used=false且单个行程的第一个地点和当前pathString路径最后一个地点一致,递归并回溯
    for (int i = 0; i < tickets.size(); i++){
        if (!used[i] && tickets.get(i).get(0).equals(pathString.get(pathString.size() - 1))){
            used[i] = true;
            pathString.add(tickets.get(i).get(1));
            if (findItineraryBacktrack(tickets, used)){
                return true;
            }
            pathString.remove(pathString.size()-1);
                used[i] = false;
        }
    }
    return false;
}    

51. N皇后 

回溯三步走:

1、明确参数和返回值:

        参数:int n;int row(表示第几行);char[][] chessboard(二维字符数组,记录棋盘状态)

2、明确终止条件:

        遇到叶子结点就终止,并把当前棋盘格存入结果集。

        叶子结点:row == n 在上图,属于棋盘第4行。但三皇后无解,不存在叶子结点

3、明确单次递归逻辑:

        for(int col = 0; col < n; ++col) 循环条件  (++col和col++在for循环中的区别是?)

        判断该行当前情况下的皇后是否合法。

        如果合法,递归,进入下一行,回溯。(回溯比较特殊,递归是数组值设为Q,回溯数组值设为. )


37. 解数独 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值