[Golang修仙之路] 算法专题:回溯(递归)

Announce:回溯这块我主要是跟着labuladong的算法笔记学习,他b站有视频讲解,也有自己的网站。

1. 递归的两种思维方式

1.1 思维方式一: 分解问题

把原问题分解为规模更小的子问题。这类思维方式的特点是:

  • 递归函数往往有明确的含义。
  • 递归函数往往有返回值。

1.1.1 例1

斐波那契数列。

递归函数的含义是:返回第N个斐波那契数列的值。

原问题是2个更小的子问题之和。

1.1.2 例2

二叉树的最大深度。

递归函数的含义是:以root为根节点的树的最大深度。

原问题是 MAX(左子树的最大深度,右子树的最大深度)+ 1

1.2 思维方式二: 遍历

这种思维方式,写出的递归函数往往没有返回值。

2. 回溯时间复杂度计算⭐️

递归函数本身的时间复杂度 * 递归函数调用次数

递归函数调用次数 = 多叉树节点个数(N!)

3. 回溯法解决:排列/组合/子集 问题

再次声明,这篇文章仅仅是个人学习笔记,原创来自:labladong

问题分类:

逐一解释,我说实话,不如直接通过例题来说明。

  • 元素无重复:
    • 就是输入的数组应该是没有重复元素的,如[1,2,3]
    • 而不能是有重复元素的,如[1,1,2]
  • 不可以复选:
    • 一个元素只能使用一次,不能无限使用。(有点像01背包和无穷背包那种概念)

3.1 排列

3.1.1 什么是排列?

ini 复制代码
nums = [1,2,3]
[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

3.1.2 如何思考:排列树

排列树:

3.1.3 condition-1: 元素无重,不可复选

例题:LCR 083. 全排列

代码:

golang 复制代码
func permute(nums []int) (res [][]int) {
    n := len(nums)
    used := make([]bool, n)
    track := []int{}
    dfs(nums, track, used, &res)
    return
}

// used 是排列 + 无重复不可复选 问题独有的
func dfs(nums []int, track []int, used []bool, res *[][]int) {
    // 退出条件
    if len(track) == len(nums) {
        tmp := make([]int, len(track))
        copy(tmp, track)
        *res = append(*res, tmp)
        return
    }
    // for循环
    for i, _ := range nums {
        if used[i] {
            continue
        }
        // 往下"递"
        track = append(track, nums[i])
        used[i] = true
        dfs(nums, track, used, res)
        // 往上"归"
        track = track[:len(track)-1]
        used[i] = false
    }
}

3.1.4 condition-2: 元素可重,不可复选

核心:排序 + nums[i] == nums[i-1] && !used[i-1]

这张图真是哥们儿理解了之后自己画的。

例题:LCR 084. 全排列 II

代码:

golang 复制代码
func permuteUnique(nums []int) (res [][]int) {
    sort.Ints(nums)
    path := []int{}
    used := make([]bool, len(nums))
    var dfs func(nums []int, used []bool)
    dfs = func(nums []int, used []bool) {
        if len(path) == len(nums) {
            res = append(res, append([]int(nil), path...))
            return
        }
        for i := range nums {
            if i > 0 && nums[i] == nums[i-1] && !used[i-1] {
                continue
            }
            if used[i] {
                continue
            }
            path = append(path, nums[i])
            used[i] = true
            dfs(nums, used)
            used[i] = false
            path = path[:len(path)-1]
        }
    }
    dfs(nums, used)
    return
}

3.2 组合/子集

3.2.1 什么是组合?

ini 复制代码
输入: n = 4, k = 2 (n代表可以选择的元素是1,2 ... n, k代表从中选出2个元素)
输出:
[
  [2,4],
  [3,4],
  [2,3],
  [1,2],
  [1,3],
  [1,4],
]

3.2.2 什么是子集?

ini 复制代码
输入: nums = [1,2,3]
输出: [[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]

3.2.3 如何思考:子集/组合树

组合/子集树:

3.3 condition-1: 元素无重,不可复选

3.3.1 组合

例题:LCR 080. 组合

代码:

golang 复制代码
func combine(n int, k int) (res [][]int) {
    var dfs func(n, start int, path []int)
    dfs = func(n, start int, path []int) {
        if len(path) == k {
            tmp := make([]int, k)
            copy(tmp, path)
            res = append(res, tmp)
            return
        }
        for i := start; i <= n; i++ {
            path = append(path, i)
            dfs(n, i + 1, path)
            path = path[:len(path)-1]
        }
    }
    path := []int{}
    dfs(n, 1, path)
    return
}

3.3.2 子集

例题:LCR 079. 子集

代码:

golang 复制代码
func subsets(nums []int) (res [][]int) {
    var dfs func(nums []int, path []int, start int)
    dfs = func(nums, path []int, start int) {
        tmp := make([]int, len(path))
        copy(tmp, path)
        res = append(res, tmp)
        for i := start; i < len(nums); i++ {
            path = append(path, nums[i])
            dfs(nums, path, i+1)
            path = path[:len(path)-1]
        }
    }
    path := []int{}
    dfs(nums, path, 0)
    return
}
相关推荐
紫荆鱼13 小时前
设计模式-状态模式(State)
c++·后端·设计模式·状态模式
A接拉起00714 小时前
如何丝滑迁移 Mongodb 数据库
后端·mongodb·架构
qincloudshaw14 小时前
Linux系统下安装JDK并设置环境变量
后端
程序定小飞14 小时前
基于springboot的民宿在线预定平台开发与设计
java·开发语言·spring boot·后端·spring
沐怡旸14 小时前
【穿越Effective C++】条款7:为多态基类声明virtual析构函数——C++多态资源管理的基石
c++·面试
代码扳手15 小时前
Golang 实战:用 Watermill 构建订单事件流系统,一文掌握概念与应用
后端·go
麻木森林15 小时前
利用Apipost 的AI能力轻松破解接口测试的效率与质量困局
后端·api
Achieve前端实验室15 小时前
【每日一面】async/await 的原理
前端·javascript·面试
紫荆鱼15 小时前
设计模式-代理模式(Proxy)
c++·后端·设计模式·代理模式
调试人生的显微镜16 小时前
Web 开发的工具全攻略 一套高效、实用、可落地的前端工作体系
后端