LeetCode算法题中的回溯算法小结

前言

对于回溯算法,起初刷leetcode题目时,每次编写都需要看题解或者评论,很少自己独立写出代码。后来通过自己的不断领悟,总算是找到了一个技巧编写回溯算法。下面通过分析如何从问题到代码的转变,来总结一下自己对于回溯算法的理解。

概述

刷了几道算法题之后,发现回溯算法解决的主要问题是组合问题。比如数字 1 2 3 4 5的不重复的全排列,字母a b c d的全排列。这类问题先从人类思维入手,然后再转为代码,个人认为是有一套公式的。

问题分析

人类思维

对于数字 [1, 2, 3 , 4, 5]的全排列,先以数字1开头的排列为例子,首先来梳理一下人如何解决这个问题。

  1. 首先选取数字1作为第一位。

    1.0 选取2作为第二位。
         		1.0.1 选取3作为第三位
         			1.0.1.1 选取4作为第四位
         				1.0.1.1.1 选取5作为第五位
         			1.0.1.2 选取5作为第四位
         				1.0.1.2.1 选取4作为第五位
         		1.0.2选取4作为第三位
         		1.0.3选取5作为第三位
    1.1 选取3作为第二位
    1.2 选取4作为第二位
    1.3 选取5作为第二位
    

根据上面的分析可以得到结果为12345和12354,其他的情况以此类推。

计算机算法思维

在上文的分析中,我们可以看出问题的解决是按照层次来解决的,每一层代表的是位数,每一位上选择从未被选择的任意数字。那么这里,可以使用树这种数据结构来将上文的思路进行转换一下。树的简图如下
在这里插入图片描述
结果的位数是已知的,所以树的层数也是有限的而且已知。从根节点到达每一个叶子节点的路径都能组成一个排列。例如 1 -> 2 -> 3 -> 4 -> 5组成排列12345。那么这个数字全排列问题就可以转化为构建树,然后遍历所有根节点到叶子节点的路径即可得到结果。

具体实现

通过Java语音中的数据结构以及递归可以对上述思想作具体的代码实现。一般这种题目数字都是放到一个数组中,然后返回一个List结果集。Java代码如下:

public static void main(String[] args) {
        System.out.println(new QuanPaiLie().fullArrangement(new int[]{1, 2, 3}));
   }
public List<String> fullArrangement(int[] nums) {
        List<String> result = new ArrayList<>();//结果集
        boolean[] flag = new boolean[nums.length];//标志位,标志当前数字是否可用
        dfs(result, nums, flag, "");
        return result;
    }

    /**
     * @param result 结果集
     * @param nums   数字集合
     * @param flag   数字标记
     * @param path   路径
     */
    private void dfs(List<String> result, int[] nums, boolean[] flag, String path) {
        //递归结束条件:由于path是记录路径节点的,那么只要path的长度等于nums的长度,说明当前路径已经到了叶子节点处
        //结束递归
        if (path.length() == nums.length) {
            result.add(path);//将当前结果添加到结果集中,结束本次递归
            return;
        }
        //以每个数字作为根节点构建树
        //一共需要构建三棵树,根节点分别为1 2 3
        for (int i = 0; i < nums.length; i++) {
            if (flag[i])//如果当前数字被使用过,则不构建这个数字节点
                continue;
            flag[i] = true;//当前数字被使用,树下面的每一层都不会出现这个数字节点,避免重复
            path += nums[i];//将当前数字添加到路径中
            dfs(result, nums, flag, path);//进入当前树的下一层,下一层每个节点又被当成一棵新树的根节点被处理
            //当前树遍历完成,将数字标记为可使用状态,在其他树中可被作为一个节点。
            flag[i] = false;
            //退回当前数字,避免出现重复
            path = path.substring(0, path.length() - 1);
        }
    }

总结

通过最近刷的几题关于回溯算法的题目,发现跟上面这个代码都非常相似。不同的地方就是可能会用到剪枝或者树的层数不限但是有限定条件,但是上面这个回溯代码可以用作最基础的回溯代码框架使用。剪枝一般都是在for循环中加上限制条件,减少递归层数。带有限定条件的一般都是递归结束条件发生变化。回溯算法一般都是使用递归求解,编写递归代码首先要找到递归结束条件,其次就是递归体代码的编写。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值