回溯算法专题

news2024/11/13 8:00:37

回溯算法专题

  • 框架篇
    • 全排列问题
    • N 皇后问题
      • 如果只需要一个合法答案,怎么办?
  • 快速排序
    • LeetCode 912. 排序数组
    • 解题思路
    • 代码实现
    • LeetCode 215. 数组中的第K个最大元素
    • 解题思路
    • 代码实现
    • 总结

不要纠结,干就完事了,熟练度很重要!!!多练习,多总结!!!

框架篇

直接上回溯算法框架。解决一个回溯问题,实际上就是一个决策树的遍历过程。你只需要思考 3 个问题:

  1. 路径:也就是已经做出的选择。
  2. 选择列表:也就是你当前可以做的选择。
  3. 结束条件:也就是到达决策树底层,无法再做选择的条件。
result = []
def backtrack(路径, 选择列表):
    if 满足结束条件:
        result.add(路径)
        return

    for 选择 in 选择列表:
        做选择
        backtrack(路径, 选择列表)
        撤销选择

其核心就是 for 循环里面的递归,在递归调用之前「做选择」,在递归调用之后「撤销选择」。

全排列问题

在这里插入图片描述
不妨把这棵树称为回溯算法的「决策树」。为啥说这是决策树呢,因为你在每个节点上其实都在做决策。
可以选择 1 那条树枝,也可以选择 3 那条树枝。为啥只能在 1 和 3 之中选择呢?因为 2 这个树枝在你身后,这个选择你之前做过了,而全排列是不允许重复使用数字的。
现在可以解答开头的几个名词:[2]就是「路径」,记录你已经做过的选择;[1,3]就是「选择列表」,表示你当前可以做出的选择;「结束条件」就是遍历到树的底层,在这里就是选择列表为空的时候。
如果明白了这几个名词,可以把「路径」和「选择列表」作为决策树上每个节点的属性,比如下图列出了几个节点的属性:
在这里插入图片描述
在这里插入图片描述
前序遍历的代码在进入某一个节点之前的那个时间点执行,后序遍历代码在离开某个节点之后的那个时间点执行。

回想我们刚才说的,「路径」和「选择」是每个节点的属性,函数在树上游走要正确维护节点的属性,那么就要在这两个特殊时间点搞点动作:
在这里插入图片描述

List<List<Integer>> res = new LinkedList<>();

/* 主函数,输入一组不重复的数字,返回它们的全排列 */
List<List<Integer>> permute(int[] nums) {
    // 记录「路径」
    LinkedList<Integer> track = new LinkedList<>();
    backtrack(nums, track);
    return res;
}

// 路径:记录在 track 中
// 选择列表:nums 中不存在于 track 的那些元素
// 结束条件:nums 中的元素全都在 track 中出现
void backtrack(int[] nums, LinkedList<Integer> track) {
    // 触发结束条件
    if (track.size() == nums.length) {
        res.add(new LinkedList(track));
        return;
    }

    for (int i = 0; i < nums.length; i++) {
        // 排除不合法的选择
        if (track.contains(nums[i]))
            continue;
        // 做选择
        track.add(nums[i]);
        // 进入下一层决策树
        backtrack(nums, track);
        // 取消选择
        track.removeLast();
    }
}

必须说明的是,不管怎么优化,都符合回溯框架,而且时间复杂度都不可能低于 O(N!),因为穷举整棵决策树是无法避免的。这也是回溯算法的一个特点,不像动态规划存在重叠子问题可以优化,回溯算法就是纯暴力穷举,复杂度一般都很高。

N 皇后问题

给你一个 N×N 的棋盘,让你放置 N 个皇后,使得它们不能互相攻击。
(皇后可以攻击同一行、同一列、左上左下右上右下四个方向的任意单位)
在这里插入图片描述

vector<vector<string>> res;

/* 输入棋盘边长 n,返回所有合法的放置 */
vector<vector<string>> solveNQueens(int n) {
    // '.' 表示空,'Q' 表示皇后,初始化空棋盘。
    vector<string> board(n, string(n, '.'));
    backtrack(board, 0);
    return res;
}

// 路径:board 中小于 row 的那些行都已经成功放置了皇后
// 选择列表:第 row 行的所有列都是放置皇后的选择
// 结束条件:row 超过 board 的最后一行
void backtrack(vector<string>& board, int row) {
    // 触发结束条件
    if (row == board.size()) {
        res.push_back(board);
        return;
    }

    int n = board[row].size();
    for (int col = 0; col < n; col++) {
        // 排除不合法选择
        if (!isValid(board, row, col)) 
            continue;
        // 做选择
        board[row][col] = 'Q';
        // 进入下一行决策
        backtrack(board, row + 1);
        // 撤销选择
        board[row][col] = '.';
    }
}


/* 是否可以在 board[row][col] 放置皇后? */
bool isValid(vector<string>& board, int row, int col) {
    int n = board.size();
    // 检查列是否有皇后互相冲突
    for (int i = 0; i < n; i++) {
        if (board[i][col] == 'Q')
            return false;
    }
    // 检查右上方是否有皇后互相冲突
    for (int i = row - 1, j = col + 1; 
            i >= 0 && j < n; i--, j++) {
        if (board[i][j] == 'Q')
            return false;
    }
    // 检查左上方是否有皇后互相冲突
    for (int i = row - 1, j = col - 1;
            i >= 0 && j >= 0; i--, j--) {
        if (board[i][j] == 'Q')
            return false;
    }
    return true;
}

函数backtrack依然像个在决策树上游走的指针,每个节点就表示在board[row][col]上放置皇后,通过isValid函数可以将不符合条件的情况剪枝:
在这里插入图片描述

如果只需要一个合法答案,怎么办?

// 函数找到一个答案后就返回 true
bool backtrack(vector<string>& board, int row) {
    // 触发结束条件
    if (row == board.size()) {
        res.push_back(board);
        return true;
    }
    ...
    for (int col = 0; col < n; col++) {
        ...
        board[row][col] = 'Q';

        if (backtrack(board, row + 1))
            return true;

        board[row][col] = '.';
    }

    return false;
}

快速排序

LeetCode 912. 排序数组

在这里插入图片描述

解题思路

快速排序是先将一个元素排好序,然后再将剩下的元素排好序。
快速排序的核心无疑是 partition 函数, partition 函数的作用是在 nums[lo…hi] 中寻找一个分界点 p,通过交换元素使得 nums[lo…p-1] 都小于等于 nums[p],且 nums[p+1…hi] 都大于 nums[p]。
从二叉树的视角,我们可以把子数组 nums[lo…hi] 理解成二叉树节点上的值,srot 函数理解成二叉树的遍历函数。参照二叉树的前序遍历顺序。

代码实现

class Solution {
    public int[] sortArray(int[] nums) {
        sort(nums, 0, nums.length-1);
        return nums;
    }

    public void sort(int[] nums, int lo, int hi){
        if(lo > hi){
            return ;
        }

        int j = partition(nums, lo, hi);
        sort(nums, lo, j-1);
        sort(nums, j+1, hi);
    }

    public int partition(int[] nums, int lo, int hi){
        int pivot = nums[lo];
        int i = lo+1, j = hi;
        while(i <= j){
            while(i < hi && nums[i] < pivot){
                i++;
            }

            while(j > lo && nums[j] > pivot){
                j--;
            }
            if(i >= j){
                break;
            }
            swap(nums, i, j);
        }
        swap(nums, lo, j);
        return j;
    }

    public void swap(int[] nums, int i, int j){
        int tmp = nums[i];
        nums[i] = nums[j];
        nums[j] = tmp;
    }
}

LeetCode 215. 数组中的第K个最大元素

在这里插入图片描述

解题思路

二叉堆思路简单,但复杂度较高。既然是找第K大元素,按照快速排序思路 ,前序遍历思维 每次找到一个有序的分界点,根据分界点去找结果的点,最后返回即可。

题目问「第 k 个最大的元素」,相当于数组升序排序后「排名第 n - k 的元素」,为了方便表述,后文另 k’ = n - k。
如何知道「排名第 k’ 的元素」呢?其实在快速排序算法 partition 函数执行的过程中就可以略见一二。
刚说了,partition 函数会将 nums[p] 排到正确的位置,使得 nums[lo…p-1] < nums[p] < nums[p+1…hi]:
这时候,虽然还没有把整个数组排好序,但我们已经让 nums[p] 左边的元素都比 nums[p] 小了,也就知道 nums[p] 的排名了。
那么我们可以把 p 和 k’ 进行比较,如果 p < k’ 说明第 k’ 大的元素在 nums[p+1…hi] 中,如果 p > k’ 说明第 k’ 大的元素在 nums[lo…p-1] 中。

代码实现

class Solution {
    public int findKthLargest(int[] nums, int k) {
        k = nums.length-k;
        int low = 0, high = nums.length-1;
        while(low <= high){
            int p = partition(nums, low, high);
            if(p > k){
                high = p-1;
            }else if(p < k){
                low = p+1;
            }else{
                return nums[p];
            }
        }
        return -1;
    }

    public int partition(int[] nums, int low, int high){
        int pivot = nums[low];
        int i = low+1, j = high;
        while(i <= j){
            while(i < high && nums[i] < pivot){
                i++;
            }

            while(j > low && nums[j] > pivot){
                j--;
            }
            if(i > j){
                break;
            }
            swap(nums, i, j);
        }
        swap(nums, low, j);
        return j;
    }

    public void swap(int[] nums, int i, int j){
        int tmp = nums[i];
        nums[i] = nums[j];
        nums[j] = tmp;
    }
}
int findKthLargest(int[] nums, int k) {
    // 小顶堆,堆顶是最小元素
    PriorityQueue<Integer> 
        pq = new PriorityQueue<>();
    for (int e : nums) {
        // 每个元素都要过一遍二叉堆
        pq.offer(e);
        // 堆中元素多于 k 个时,删除堆顶元素
        if (pq.size() > k) {
            pq.poll();
        }
    }
    // pq 中剩下的是 nums 中 k 个最大元素,
    // 堆顶是最小的那个,即第 k 个最大元素
    return pq.peek();
}

总结

本题来源于Leetcode中 归属于回溯算法类型题目。
同许多在算法道路上不断前行的人一样,不断练习,修炼自己!
如有博客中存在的疑问或者建议,可以在下方留言一起交流,感谢各位!

觉得本博客有用的客官,可以给个点赞+收藏哦! 嘿嘿

喜欢本系列博客的可以关注下,以后除了会继续更新面试手撕代码文章外,还会出其他系列的文章!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/449049.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

e与phi不互素 --- 四道题详记

文章目录 题一([MoeCTF2022]signin)题目描述&#xff1a;题目分析&#xff1a; 题二(unusualrsa5)题目描述&#xff1a;题目分析&#xff1a; 题三([0ctf 2016]RSA?)题目描述&#xff1a;题目分析&#xff1a; 题四(2022ctfshow卷王杯现代密码签到)题目描述&#xff1a;题目分…

【Python】re模块

一、re模块简介及操作方法 正则表达式其本身就是一种小型的&#xff0c;高度专业化的编程语言。在Python中&#xff0c;它被内 嵌在了re模块里面&#xff0c;正则表达式模式被编译成一系列的字节码&#xff0c;然后由用C编写的匹 配引擎执行。 1、re.search方法 re.search 扫描…

“王炸”组合竞逐「行泊一体」

去年开始&#xff0c;在中国市场&#xff0c;「行泊一体」的热度&#xff0c;不亚于当年特斯拉推出FSD&#xff0c;甚至更加火热。从上游芯片、传感器&#xff0c;到域控制器、智能驾驶系统供应商以及车企&#xff0c;都在公开场合不断普及这个组合功能。 「行泊一体」市场的启…

MongoDBRedis基础知识

MongoDB&Redis基础知识 1. MongoDB简介2. Redis 关系型数据库遵循ACID原则&#xff1a; 原子性一致性独立性持久性 分布式系统&#xff1a;由多台计算机和通信的软件组件通过计算机网络连接组成&#xff0c;分布式系统是建立在网络之上的软件系统&#xff0c;因为软件的特…

操作系统论文导读(七):Response-Time Analysis for Mixed Criticality Systems——混合关键系统的响应时间分析

目录 一、论文核心思想 二、案例引入 三、基础定义 四、分区关键性调度 (PC) 五、SMC调度 5.1 调度流程 5.2 响应时间分析&#xff08;考虑EDF分配&#xff09; 5.3 优先级分配 六、AMC调度 6.1 调度流程 6.2 响应时间分析&#xff08;考虑EDF分配&#xff09; 6.3 AMC…

Cesium 核心概念 核心接口

Cesimum 可以做什么 Cesium 是一个开源的3D地球可视化引擎&#xff0c;它可以在Web浏览器中以高性能和高质量呈现全球范围内的地球表面数据。 Cesium 可以用于以下领域&#xff1a; 地理信息系统&#xff1a;Cesium 可以呈现地球表面上的各种地理信息数据&#xff0c;包括卫…

【Python入门】NumPy 数组裁切及数据类型

前言 嗨喽~大家好呀&#xff0c;这里是魔王呐 ❤ ~! 裁切数组 python 中裁切的意思是将元素从一个给定的索引带到另一个给定的索引。 我们像这样传递切片而不是索引&#xff1a;[start&#xff1a;end]。 我们还可以定义步长&#xff0c;如下所示&#xff1a;[start&#x…

RabbitMQ实现消息的延迟推送或延迟发送

一、RabbitMQ是什么&#xff1f; 1.RabbitMQ简介 RabbitMQ是有erlang语言开发&#xff0c;基于AMQP&#xff08;Advanced Message Queue 高级消息队列协议&#xff09;协议实现的消息队列。 常见的消息队列有&#xff1a;RabbitMQ、Kafka 和 ActiveMQ 2.RabbitMQ的优点 Rab…

设计模式 --- 创建者模式

一、概述 创建型模式的主要关注点是“怎样创建对象&#xff1f;”&#xff0c;它的主要特点是“将对象的创建与使用分离”。 这样可以降低系统的耦合度&#xff0c;使用者不需要关注对象的创建细节。 创建型模式分为&#xff1a; 单例模式 工厂方法模式 抽象工程模式 原型…

【linux】yum “应用商店” 的基本用法

好多工具 yum软件包查看软件包安装软件卸载软件 yum 通俗的讲&#xff0c;这就似我们手机上的应用商店&#xff0c;只不过是在linux下的。 我们可以用yum来下载东西。 软件包 在Linux下安装软件, 一个通常的办法是下载到程序的源代码, 并进行编译, 得到可执行程序。 但是这样…

【Linux 命令】chroot

文章目录 一、背景二、语法三、案例3.1 命令3.2 系统调用3.3 查找服务是否存在于 chrooted 监禁内 四、注意事项 chroot 用来在指定根目录运行命令&#xff08;即指定 / 的位置&#xff09;&#xff0c;不可操作指定目录之外的地方。其是一种非常简单的资源隔离化操作&#xff…

idea 配置docker 进行上传镜像,部署启动容器

前言 在我们开发测试过程中&#xff0c;需要频繁的更新docker镜像&#xff0c;然而默认情况下&#xff0c;docker的2375端口是关闭的&#xff0c;下面介绍如何打开端口。 修改docker配置文件 操作步骤&#xff1a; 1.1、修改配置 登录docker所在服务器&#xff0c;修改docker…

web前端实验5

实 验 报 告 课 程 Web前端应用开发 实验项目 Jquery AJAX编程 成 绩 专业班级 班内序号 指导教师 姓 名 学 号 实验日期 实验目的及要求&#xff1a; &#xff08;1&#xff09; 理解和掌握Jquery AJAX的get方式请求 &#xff08;2&#xff09; 理解和掌握Jquery AJAX的pos…

释放ChatGPT潜能:4款高效插件让你的AI助手更强大

你的ChatGPT页面是什么样的&#xff1f;是这样的吗&#xff1f; 今天我要向大家推荐四款「ChatGPT免费插件」。这些插件是我们在与GPT日常交流中&#xff0c;使用频率最高的四个工具。 一旦安装这些插件&#xff0c;你的GPT将立即变身为「超级ChatGPT」。使用起来更为流畅&am…

BEV+Transformer对无人驾驶硬件体系的巨大改变

摘要&#xff1a; BEVTransformer彻底终结了2D直视图CNN时代&#xff0c;BEVTransformer对智能驾驶硬件系统有着什么样的影响&#xff1f;背后的受益者又是谁&#xff1f; 图片来源&#xff1a;特斯拉 BEVTransformer是目前智能驾驶领域最火热的话题&#xff0c;没有之一&…

【大数据平台开发】【Web框架】001Django框架简介

【大数据平台开发】【Web框架】001Django框架简介 文章目录 【大数据平台开发】【Web框架】001Django框架简介一. Django简介与安装1.1 Django安装1.2 Django简介1.3 框架功能简介 二. Django框架的最小程序2.1 新建工程2.2 修改工程2.3 运行工程2.4 django-admin与manage.py2.…

(二)EalsticSearch 辅助工具 Kibana 介绍与安装

1、什么是 kibana &#xff1f; Kibana 是一个针对Elasticsearch的开源分析及可视化平台&#xff0c;用来搜索、查看交互存储在Elasticsearch索引中的数据。使用Kibana&#xff0c;可以通过各种图表进行高级数据分析及展示。 Kibana让海量数据更容易理解。它操作简单&#xff…

Python高光谱遥感数据处理与机器学习

Python高光谱遥感数据处理与机器学习 第一章、高光谱基础 高光谱遥感简介 什么是高光谱遥感&#xff1f; 高光谱遥感为什么重要&#xff1f; 高光谱遥感与其他遥感技术的区别是什么&#xff1f; 高光谱遥感的历史和发展 高光谱传感器与数据获取 高光谱传感器类型 如何获…

RHCE第五次作业

目录 一、判断当前磁盘剩余空间是否有20G&#xff0c;如果小于20G&#xff0c;则将报警邮件发送给管理员&#xff0c;每天检查一次磁盘剩余空间 1.创建脚本test1.sh 2.下载邮件服务并执行 3.测试 4.做计划 二、 判断web服务是否运行&#xff08;1、查看进程的方式判断该程序…

Node内置模块 【操作系统os模块】

文章目录 &#x1f31f;前言&#x1f31f;os模块&#x1f31f;使用&#x1f31f;属性&#x1f31f;方法&#x1f31f;获取操作系统临时目录&#x1f31f;获取操作系统主机名&#x1f31f;获取操作系统CPU架构&#x1f31f;识别操作系统平台&#x1f31f;获取操作系统发行版本&a…