[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
}
相关推荐
PAK向日葵1 小时前
【算法导论】PDD 0817笔试题题解
算法·面试
uzong2 小时前
技术故障复盘模版
后端
GetcharZp3 小时前
基于 Dify + 通义千问的多模态大模型 搭建发票识别 Agent
后端·llm·agent
桦说编程3 小时前
Java 中如何创建不可变类型
java·后端·函数式编程
IT毕设实战小研3 小时前
基于Spring Boot 4s店车辆管理系统 租车管理系统 停车位管理系统 智慧车辆管理系统
java·开发语言·spring boot·后端·spring·毕业设计·课程设计
wyiyiyi4 小时前
【Web后端】Django、flask及其场景——以构建系统原型为例
前端·数据库·后端·python·django·flask
阿华的代码王国5 小时前
【Android】RecyclerView复用CheckBox的异常状态
android·xml·java·前端·后端
Jimmy5 小时前
AI 代理是什么,其有助于我们实现更智能编程
前端·后端·ai编程
AntBlack5 小时前
不当韭菜V1.1 :增强能力 ,辅助构建自己的交易规则
后端·python·pyqt
bobz9656 小时前
pip install 已经不再安全
后端