算法-算法的基本框架思想


本文目录

  • 算法的基本框架思想
    • 一、二叉树的基本框架
      • 1、二叉树的前序遍历
      • 2、二叉树的前序遍历优化
      • 2、二叉树的遍历基本框架
    • 二、回溯算法的基本框架
      • 1、基本框架
      • 2、核心框架
      • 3、全排列的核心框架
      • 4、核心思想
    • 三、动态规划的基本框架
      • 1、自顶向下递归的动态规划
      • 2、自顶向下递归的动态规划
      • 0-1 背包的解题框架
    • 四、链表的基本框架
      • 1、迭代遍历单链表
      • 2、递归遍历单链表
    • 五、数组的基本框架
      • 1、迭代遍历数组
      • 2、递归遍历数组
    • 六、双指针的基本框架
      • 1、快慢指针框架
      • 1、左右指针框架
    • 七、滑动窗口的基本框架
      • 1、滑动窗口算法框架
    • 八、分治算法
    • 九、递归算法
    • 十、涉及字符串算法
    • 十一、Rabin-Karp 算法基础
    • 十二、二分算法基础


提示:

我想说算法的本质就是穷举
穷举有两个关键难点:无遗漏、无冗余。

算法的基本框架思想

一、二叉树的基本框架

二叉树题目的重要性,我提到二叉树算法是所有递归算法的根本,动态规划、回溯算法、图论算法等高级算法底层都是二叉树算法的思想。

1、很多动态规划问题就是在遍历一棵树,你如果对树的遍历操作烂熟于心,起码知道怎么把思路转化成代码,也知道如何提取别人解法的核心思路。

2、再看看回溯算法,后文 回溯算法详解 干脆直接说了,回溯算法就是个 N 叉树的前后序遍历问题,没有例外。

3、比如全排列问题吧,本质上全排列就是在遍历下面这棵树,到叶子节点的路径就是一个全排列:

1、二叉树的前序遍历

List<Integer> res = new LinkedList<>();// 返回前序遍历结果
List<Integer> preorder(TreeNode root) {traverse(root);return res;
}// 二叉树遍历函数
void traverse(TreeNode root) {if (root == null) {return;}// 前序遍历位置res.add(root.val);traverse(root.left);traverse(root.right);
}

2、二叉树的前序遍历优化

// 定义:输入一棵二叉树的根节点,返回这棵树的前序遍历结果
List<Integer> preorder(TreeNode root) {List<Integer> res = new LinkedList<>();if (root == null) {return res;}// 前序遍历的结果,root.val 在第一个res.add(root.val);// 后面接着左子树的前序遍历结果res.addAll(preorder(root.left));// 最后接着右子树的前序遍历结果res.addAll(preorder(root.right));return res;
}

2、二叉树的遍历基本框架

void traverse(TreeNode root) {if (root == null) {return;}// 前序位置traverse(root.left);// 中序位置traverse(root.right);// 后序位置
}

1、二叉树模型几乎是所有高级算法的基础,尤其是那么多人说对递归的理解不到位,更应该好好刷二叉树相关题目。

2、二叉树题目的递归解法可以分两类思路,第一类是遍历一遍二叉树得出答案,第二类是通过分解问题计算出答案,这两类思路分别对应着 回溯算法核心框架 和 动态规划核心框架。

二、回溯算法的基本框架

1、路径:也就是已经做出的选择。

2、选择列表:也就是你当前可以做的选择。

3、结束条件:也就是到达决策树底层,无法再做选择的条件

1、基本框架

result = []
def backtrack(路径, 选择列表):if 满足结束条件:result.add(路径)returnfor 选择 in 选择列表:做选择backtrack(路径, 选择列表)撤销选择

2、核心框架

###  回溯算法核心框架 // 记录所有全排列
List<List<Integer>> res = new LinkedList<>();
LinkedList<Integer> track = new LinkedList<>();/* 主函数,输入一组不重复的数字,返回它们的全排列 */
List<List<Integer>> permute(int[] nums) {backtrack(nums);return res;
}// 回溯算法框架
void backtrack(int[] nums) {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.removeLast();}
}

3、全排列的核心框架

## 全排序的主要算法 
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();}
}        

4、核心思想

1、回溯算法是对树形或者图形结构执行一次深度优先遍历,实际上类似枚举的搜索尝试过程,在遍历的过程中寻找问题的解。

2、深度优先遍历有个特点:当发现已不满足求解条件时,就返回,尝试别的路径。此时对象类型变量就需要重置成为和之前一样,称为「状态重置」。

3、许多复杂的,规模较大的问题都可以使用回溯法,有「通用解题方法」的美称。实际上,回溯算法就是暴力搜索算法,它是早期的人工智能里使用的算法,借助计算机强大的计算能力帮助我们找到问题的解。

三、动态规划的基本框架

1、动态规划常常适用于有重叠子问题和最优子结构性质的问题,并且记录所有子问题的结果,因此动态规划方法所耗时间往往远少于朴素解法。

2、动态规划有自底向上和自顶向下两种解决问题的方式。自顶向下即记忆化递归,自底向上就是递推。

3、使用动态规划解决的问题有个明显的特点,一旦一个子问题的求解得到结果,以后的计算过程就不会修改它,这样的特点叫做无后效性,求解问题的过程形成了一张有向无环图。动态规划只解决每个子问题一次,具有天然剪枝的功能,从而减少计算量。

4、动态规划系列问题的核心原理,无非就是先写出暴力穷举解法(状态转移方程),加个备忘录就成自顶向下的递归解法了,再改一改就成自底向上的递推迭代解法了, 动态规划的降维打击 里也讲过如何分析优化动态规划算法的空间复杂度。

1、自顶向下递归的动态规划

def dp(状态1, 状态2, ...):for 选择 in 所有可能的选择:# 此时的状态已经因为做了选择而改变result = 求最值(result, dp(状态1, 状态2, ...))return result

2、自顶向下递归的动态规划

# 初始化 base case
dp[0][0][...] = base case
# 进行状态转移
for 状态1 in 状态1的所有取值:for 状态2 in 状态2的所有取值:for ...dp[状态1][状态2][...] = 求最值(选择1,选择2...

0-1 背包的解题框架

int knapsack(int W, int N, int[] wt, int[] val) {assert N == wt.length;// base case 已初始化int[][] dp = new int[N + 1][W + 1];for (int i = 1; i <= N; i++) {for (int w = 1; w <= W; w++) {if (w - wt[i - 1] < 0) {// 这种情况下只能选择不装入背包dp[i][w] = dp[i - 1][w];} else {// 装入或者不装入背包,择优dp[i][w] = Math.max(dp[i - 1][w - wt[i-1]] + val[i-1], dp[i - 1][w]);}}}return dp[N][W];
}

四、链表的基本框架

1、单链表常考的技巧就是双指针

## 返回链表的倒数第 k 个节点
ListNode findFromEnd(ListNode head, int k) {ListNode p1 = head;// p1 先走 k 步for (int i = 0; i < k; i++) {p1 = p1.next;}ListNode p2 = head;// p1 和 p2 同时走 n - k 步while (p1 != null) {p2 = p2.next;p1 = p1.next;}// p2 现在指向第 n - k + 1 个节点,即倒数第 k 个节点return p2;
}

head->1->2->3->4->5->6->7->8->9->10->null
一共10个节点

1、迭代遍历单链表

void traverse(ListNode head) {for (ListNode p = head; p != null; p = p.next) {}
}

x

2、递归遍历单链表


void traverse(ListNode head) {if (head == null) {return;}// 前序位置traverse(head.next);// 后序位置
}

五、数组的基本框架

1、数组常用的技巧有很大一部分还是双指针相关的技巧,说白了是教你如何聪明地进行穷举。

2、数字组合的和等于目标和嘛。比较聪明的方式是先排序,利用双指针技巧快速计算结果。

1、迭代遍历数组

void traverse(int[] arr) {for (int i = 0; i < arr.length; i++) {}
}

2、递归遍历数组


void traverse(int[] arr, int i) {if (i == arr.length) {return;}// 前序位置traverse(arr, i + 1);// 后序位置
}

六、双指针的基本框架

1、双指针从广义上来说,是指用两个变量在线性结构上遍历而解决的问题。

2、对于数组,指两个变量在数组上相向移动解决的问题。

3、对于链表,指两个变量在链表上同向移动解决的问题,也称为「快慢指针」问题。

4、在处理数组和链表相关问题时,双指针技巧是经常用到的,

5、双指针技巧主要分为两类:左右指针和快慢指针。

6、所谓左右指针,就是两个指针相向而行或者相背而行。

7、而所谓快慢指针,就是两个指针同向而行,一快一慢。

8、只要数组有序,就应该想到双指针技巧。这道题的解法有点类似二分查找,通过调节 left 和 right 就可以调整 sum 的大小。

1、快慢指针框架

Node slow = head.next;Node fast = head.next;while(fast != null&& fast.next != null) {// 快指针走两步fast = fast.next.next;// 慢指针走一步slow = slow.next;
}

1、左右指针框架

int left = 0, right = 0;while (right < s.size()) {// 增大窗口window.add(s[right]);right++;while (window needs shrink) {// 缩小窗口window.remove(s[left]);left++;}
}

七、滑动窗口的基本框架

1、滑动窗口算法技巧,典型的快慢双指针,快慢指针中间就是滑动的「窗口」,主要用于解决子串问题。

2、滑动窗口也是有其限制的,就是你必须明确的知道什么时候应该扩大窗口,什么时候该收缩窗口。

3、滑动窗口指的是这样一类问题的求解方法,在数组上通过双指针同向移动而解决的一类问题。

4、使用滑动窗口解决的问题通常是暴力解法的优化,掌握这一类问题最好的办法就是练习,然后思考清楚为什么可以使用滑动窗口。

1、滑动窗口算法框架

## 其中两处 ... 表示的更新窗口数据的地方,到时候你直接往里面填就行了。
## 这两个 ... 处的操作分别是扩大和缩小窗口的更新操作,等会你会发现它们操作是完全对称的
void slidingWindow(string s) {unordered_map<char, int> window;int left = 0, right = 0;while (right < s.size()) {// c 是将移入窗口的字符char c = s[right];// 增大窗口right++;// 进行窗口内数据的一系列更新.../*** debug 输出的位置 ***/// 注意在最终的解法代码中不要 print// 因为 IO 操作很耗时,可能导致超时printf("window: [%d, %d)\n", left, right);/********************/// 判断左侧窗口是否要收缩while (window needs shrink) {// d 是将移出窗口的字符char d = s[left];// 缩小窗口left++;// 进行窗口内数据的一系列更新...}}
}

八、分治算法

1、分治法是构建基于多项分支递归的一种很重要的算法范式。字面上的解释是「分而治之」,就是把一个复杂的问题分成两个或更多的相同或相似的子问题,直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。

2、这个技巧是很多高效算法的基础,如排序算法(快速排序、归并排序)、傅立叶变换(快速傅立叶变换)。

九、递归算法

1、递归是计算机科学中的一个重要概念。它是许多其他算法和数据结构的基础。

2、每当递归函数调用自身时,它都会将给定的问题拆解为子问题。递归调用继续进行,直到到子问题成为一个不可以拆分的、可以直接求解的最简单问题。

3、为了确保递归函数不会导致无限循环,它需要包含:
一个简单的基本案例(basic case)(或一些案例), 能够不使用递归来产生答案的终止方案。
一组规则,也称作递推关系(recurrence relation),可将所有其他情况拆分到基本案例。
注意,函数可能会有多个位置进行自我调用(这是分治算法)。

十、涉及字符串算法

字符串往往由特定字符集内有限的字符组合而成,根据其特点,对字符串的 操作 可以归结为以下几类:

1、字符串的比较、连接操作(不同编程语言实现方式有所不同);
2、涉及子串的操作,比如前缀,后缀等;
3、字符串间的匹配操作,如 KMP 算法、BM 算法等。

字符串排序,按字典排列字符串,可以调用Arrays.sort API,也可以使用PriorityQueue,还可以自己实现Comparator

十一、Rabin-Karp 算法基础

1、算法的核心思路就是不断向最低位(个位)添加数字

2、删除数字的最高位

用 R 表示数字的进制数,用 L 表示数字的位数,就可以总结出如下公式:

/* 在最低位添加一个数字 */
int number = 8264;
// number 的进制
int R = 10;
// 想在 number 的最低位添加的数字
int appendVal = 3;
// 运算,在最低位添加一位
number = R * number + appendVal;
// 此时 number = 82643
/* 在最高位删除一个数字 */
int number = 8264;
// number 的进制
int R = 10;
// number 最高位的数字
int removeVal = 8;
// 此时 number 的位数
int L = 4;
// 运算,删除最高位数字
number = number - removeVal * R^(L-1);
// 此时 number = 264

十二、二分算法基础

int binarySearch(int[] nums, int target) {int left = 0; int right = nums.length - 1; // 注意while(left <= right) {int mid = left + (right - left) / 2;if(nums[mid] == target)return mid; else if (nums[mid] < target)left = mid + 1; // 注意else if (nums[mid] > target)right = mid - 1; // 注意}return -1;
}

1、初始化 right 的赋值是 nums.length - 1,即最后一个元素的索引,而不是 nums.length。

2、这二者可能出现在不同功能的二分查找中,区别是:前者相当于两端都闭区间 [left, right],后者相当于左闭右开区间 [left, right),因为索引大小为 nums.length 是越界的。

3、这个算法中使用的是前者 [left, right] 两端都闭的区间。这个区间其实就是每次进行搜索的区间。

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

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

相关文章

线程池的使用

线程池的作用 降低线程创建和销毁的开销&#xff1a;线程的创建和销毁是比较昂贵的操作。通过使用线程池&#xff0c;可以避免频繁地创建和销毁线程&#xff0c;而是复用线程池中已经存在的线程&#xff0c;从而降低了开销。 控制并发度&#xff1a;通过控制线程池中线程的数量…

【华为OD题库-009】食堂供餐-Java

题目 某公司员工食堂以盒饭方式供餐。为将员工取餐排队时间降低为0&#xff0c;食堂的供餐速度必须要足够快。现在需要根据以往员工取餐的统计信息&#xff0c;计算出一个刚好能达成排队时间为0的最低供餐速度。即&#xff0c;食堂在每个单位时间内必须至少做出多少份盒饭才能满…

ADC、DMA以及串口之间的联系和区别?

ADC、DMA和串口都是嵌入式系统中常用的模块&#xff0c;它们之间有以下联系和区别&#xff1a; 联系&#xff1a; ADC和DMA都是用于数据采集和传输的模块&#xff0c;ADC可以将模拟信号转换为数字信号&#xff0c;DMA可以在不经过CPU的情况下实现数据的高速传输。而串口则是一…

同城服务共享台球室小程序系统:打造智能化的台球体验

一、引言 随着科技的发展和人们生活节奏的加快&#xff0c;对于休闲娱乐的需求也在不断增长。台球作为一种广受欢迎的休闲活动&#xff0c;其智能化和便利性的需求也日益凸显。本文将探讨如何通过同城服务共享台球室小程序系统&#xff0c;打造智能化的台球体验。 二、系统设…

MySQL(14):视图

数据库对象 对象描述表(TABLE)表是存储数据的逻辑单元&#xff0c;以行和列的形式存在&#xff0c;列就是字段&#xff0c;行就是记录数据字典就是系统表&#xff0c;存放数据库相关信息的表。系统表的数据通常由数据库系统维护&#xff0c;程序员通常不应该修改&#xff0c;只…

mysql 讲解(1)

文章目录 前言一、基本的命令行操作二、操作数据库语句2.1、创建数据库2.2、删除数据库2.3、使用数据库2.4 查看所有数据库 三、列的数据类型3.1 字符串3.2 数值3.3 时间日期3.4 空3.5 int 和 varchar问题总结&#xff1a; 四、字段属性4.1 UnSigned4.2 ZEROFILL4.3 Auto_InCre…

ARPG----C++学习记录05 Section12 动画蒙太奇,收拿剑,MetaSound,调整动画

代码更新 https://github.com/BAOfanTing/ARPG_Game_Code/commit/c629270e49496ba1bcbaf03780d23c1842ca5e7a Animation Montages动画蒙太奇 蒙太奇的工作流程 新建一个鼠标左键的按键映射&#xff0c;下载一些攻击动画&#xff0c;重定向给我们的人物&#xff0c;新建一个动画…

PCL中的离群点去噪-StatisticalOutlierRemoval

作用是去除稀疏离群噪点。在采集点云的过程中&#xff0c;由于测量噪声的影响&#xff0c;会引入部分离群噪点&#xff0c;它们在点云空间中分布稀疏。在估算点云局部特征&#xff08;例如计算采样点处的法向量和曲率变化率&#xff09;时&#xff0c;这些噪点可能导致错误的计…

若依系统富文本框上传图片报错!

报错如下&#xff1a; 原因&#xff1a;如图&#xff0c;富文本路径中存在 / 字符&#xff0c;导致上传出错。 解决方案&#xff1a;将富文本框内容在前端进行加密&#xff0c;后端再解密。 前端&#xff1a; 安装 crypto-js 插件 npm install crypto-js 创建工具类 :在 sr…

使用MVS-GaN HEMT紧凑模型促进基于GaN的射频和高电压电路设计

标题&#xff1a;Facilitation of GaN-Based RF- and HV-Circuit Designs Using MVS-GaN HEMT Compact Model 来源&#xff1a;IEEE TRANSACTIONS ON ELECTRON DEVICES&#xff08;19年&#xff09; 摘要—本文阐述了基于物理的紧凑器件模型在研究器件行为细微差异对电路和系统…

signed char表示的最大数据范围是多少?

signed char表示的最大数据范围是多少。 1.首先要明白负数在计算机内是以补码的形式存储的&#xff0c;最高位1代表是负数&#xff0c;最高位0代表正数。 2.char数据类型占据一个字节。 3.一个字节表示的最大负数是(1)111_1111即-127&#xff1b;一个字节表示的最大正数是(0)11…

好心提醒下,幼师姐妹们要知道啊

幼师家人们在不在&#xff1f;在不在&#xff1f; 不会还有姐妹在自己写教案&#xff0c;写总结&#xff0c;写评语啥的吧&#xff0c;这个好东西真的要知道啊&#xff01;&#xff01; 只要输入关键词&#xff0c;马上就能得到你想要的内容&#xff0c;真的很强啊&#xff0…

Pytorch从零开始实战09

Pytorch从零开始实战——YOLOv5-Backbone模块实现 本系列来源于365天深度学习训练营 原作者K同学 文章目录 Pytorch从零开始实战——YOLOv5-Backbone模块实现环境准备数据集模型选择开始训练可视化模型预测总结 环境准备 本文基于Jupyter notebook&#xff0c;使用Python3.…

【java学习—十四】Class类(2)

文章目录 1. Class类2. Class类的常用方法3. 实例化Class类对象&#xff08;四种方法&#xff09; 1. Class类 在 Object 类中定义了以下的方法&#xff0c;此方法将被所有子类继承&#xff1a; public final Class getClass() 以上的方法返回值的类型是一个 Class 类&#xf…

服务器、云服务器、高防服务器都有什么优势呢?

云服务器的优点可以从以下几方面讲&#xff1a; 第一&#xff0c;从技术方面来说&#xff0c;云服务器含有云计算机技术&#xff0c;而云计算技术综合了各种软件和硬件技术。而那些独立的服务器是独立的&#xff0c;不会整合这些技术。 第二&#xff0c;云服务器的安全性能更…

web3 React dapp进行事件订阅

好啊&#xff0c;上文web3 React Dapp书写订单 买入/取消操作 我们已经写好了 填充和取消订单 这就已经是非常大的突破了 但是 留下了一个问题 那就是 我们执行完之后 订单的数据没有直接更新 每次都需要我们手动刷新 才能看到结果 那么 今天我们就来看解决这个问题的事件订阅 …

MapReduce 读写数据库

MapReduce 读写数据库 经常听到小伙伴吐槽 MapReduce 计算的结果无法直接写入数据库&#xff0c; 实际上 MapReduce 是有操作数据库实现的 本案例代码将实现 MapReduce 数据库读写操作和将数据表中数据复制到另外一张数据表中 准备数据表 create database htu; use htu; creat…

android手机平板拓展电脑音频

&#xff08;1&#xff09;首先确保电脑上有声卡&#xff0c;就是电脑右下角小喇叭能调音量&#xff0c;不管电脑会不会响&#xff0c;如果小喇叭标记了个错误&#xff0c;说明没有声卡&#xff0c;安装图上的虚拟声卡软件。 &#xff08;2&#xff09;图上第一个PC免安装及局…

vscode launch.json

有时新的服务器进行调试时&#xff0c;需要设置调试的launch.json的结果 然后就可以打开一个launch.json 其内容如下 {// 使用 IntelliSense 了解相关属性。 // 悬停以查看现有属性的描述。// 欲了解更多信息&#xff0c;请访问: https://go.microsoft.com/fwlink/?linkid83…

JAVA 版小程序商城免费搭建 多商家入驻 直播带货 商城系统 B2B2C 商城源码之 B2B2C产品概述

1. 涉及平台 平台管理、商家端&#xff08;PC端、手机端&#xff09;、买家平台&#xff08;H5/公众号、小程序、APP端&#xff08;IOS/Android&#xff09;、微服务平台&#xff08;业务服务&#xff09; 2. 核心架构 Spring Cloud、Spring Boot、Mybatis、Redis 3. 前端框架…