day3_prefixSum

一、前缀和技巧

重点

前缀和技巧适用于快速、频繁地计算一个索引区间内的元素之和

个人理解;预计算,空间换时间

1.(一维数组的前缀和)303区域和检索-数组不可变

获取闭区间值 [left,right] -> preSum[right + 1] - preSum[left],其中preSum[right+1]表示累加到right时的总和

class NumArray {// 前缀和数组private int[] preSum;/* 输入一个数组,构造前缀和 */public NumArray(int[] nums) {// preSum[0] = 0,便于计算累加和preSum = new int[nums.length + 1];// 计算 nums 的累加和for (int i = 1; i < preSum.length; i++) {preSum[i] = preSum[i - 1] + nums[i - 1];}}/* 查询闭区间 [left, right] 的累加和 */public int sumRange(int left, int right) {return preSum[right + 1] - preSum[left];}
}/*** Your NumArray object will be instantiated and called as such:* NumArray obj = new NumArray(nums);* int param_1 = obj.sumRange(left,right);*/
2.(二维数组的前缀和)

同样的预计算

class NumMatrix {
// 定义:preSum[i][j] 记录 matrix 中子矩阵 [0, 0, i-1, j-1] 的元素和private int[][] preSum;public NumMatrix(int[][] matrix) {int m = matrix.length, n = matrix[0].length;if (m == 0 || n == 0) return;// 构造前缀和矩阵preSum = new int[m + 1][n + 1];for (int i = 1; i <= m; i++) {for (int j = 1; j <= n; j++) {// 计算每个矩阵 [0, 0, i, j] 的元素和preSum[i][j] = preSum[i-1][j] + preSum[i][j-1] + matrix[i - 1][j - 1] - preSum[i-1][j-1];}}}// 计算子矩阵 [x1, y1, x2, y2] 的元素和public int sumRegion(int x1, int y1, int x2, int y2) {// 目标矩阵之和由四个相邻矩阵运算获得return preSum[x2+1][y2+1] - preSum[x1][y2+1] - preSum[x2+1][y1] + preSum[x1][y1];}
}/*** Your NumMatrix object will be instantiated and called as such:* NumMatrix obj = new NumMatrix(matrix);* int param_1 = obj.sumRegion(row1,col1,row2,col2);*/

在这里插入图片描述

  1. 任何一个小矩阵可以由上图的矩阵计算得到
  2. 而下面这四个矩阵对应的是小矩阵的四个顶点(根据参数可推)
  3. 所以预计算每个以(0,0),(x,y)结尾的矩阵的值,经过计算即可得到

二、二叉树(纲领篇)

参考链接东哥带你刷二叉树(纲领篇) | labuladong 的算法笔记

先在开头总结一下,二叉树解题的思维模式分两类:

1、是否可以通过遍历一遍二叉树得到答案?

如果可以,用一个 traverse 函数配合外部变量来实现,这叫「遍历」的思维模式。

2、是否可以定义一个递归函数,通过子问题(子树)的答案推导出原问题的答案?

如果可以,写出这个递归函数的定义,并充分利用这个函数的返回值,这叫「分解问题」的思维模式。

无论使用哪种思维模式,你都需要思考:

如果单独抽出一个二叉树节点,它需要做什么事情?需要在什么时候(前/中/后序位置)做?其他的节点不用你操心,递归函数会帮你在所有节点上执行相同的操作。

1.二叉树的重要性

举个例子,比如两个经典排序算法 快速排序 和 归并排序,对于它俩,你有什么理解?

如果你告诉我,快速排序就是个二叉树的前序遍历,归并排序就是个二叉树的后序遍历,那么我就知道你是个算法高手了

如果你一眼就识破这些排序算法的底细,还需要背这些经典算法吗?不需要。你可以手到擒来,从二叉树遍历框架就能扩展出算法了。

说了这么多,旨在说明,二叉树的算法思想的运用广泛,甚至可以说,只要涉及递归,都可以抽象成二叉树的问题

2.深入理解前中后序

根据几个问题引发思考

1、你理解的二叉树的前中后序遍历是什么,仅仅是三个顺序不同的 List 吗?

2、请分析,后序遍历有什么特殊之处?

3、请分析,为什么多叉树没有中序遍历?

鄙人是肯定答不上来的

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

你也注意到了,只要是递归形式的遍历,都可以有前序位置和后序位置,分别在递归之前和递归之后。

所谓前序位置,就是刚进入一个节点(元素)的时候,后序位置就是即将离开一个节点(元素)的时候,那么进一步,你把代码写在不同位置,代码执行的时机也不同:

在这里插入图片描述

比如说,如果让你倒序打印一条单链表上所有节点的值,你怎么搞?

实现方式当然有很多,但如果你对递归的理解足够透彻,可以利用后序位置来操作

/* 递归遍历单链表,倒序打印链表元素 */
void traverse(ListNode head) {if (head == null) {return;}traverse(head.next);// 后序位置print(head.val);
}

教科书里只会问你前中后序遍历结果分别是什么,所以对于一个只上过大学数据结构课程的人来说,他大概以为二叉树的前中后序只不过对应三种顺序不同的 List<Integer> 列表。

但是我想说,前中后序是遍历二叉树过程中处理每一个节点的三个特殊时间点,绝不仅仅是三个顺序不同的 List:

  1. 前序位置的代码在刚刚进入一个二叉树节点的时候执行;
  2. 后序位置的代码在将要离开一个二叉树节点的时候执行;
  3. 中序位置的代码在一个二叉树节点左子树都遍历完,即将开始遍历右子树的时候执行。

在这里插入图片描述

这里你也可以理解为什么多叉树没有中序位置,因为二叉树的每个节点只会进行唯一一次左子树切换右子树,而多叉树节点可能有很多子节点,会多次切换子树去遍历,所以多叉树节点没有「唯一」的中序遍历位置。

重点1

二叉树的所有问题,就是让你在前中后序位置注入巧妙的代码逻辑,去达到自己的目的,你只需要单独思考每一个节点应该做什么,其他的不用你管,抛给二叉树遍历框架,递归会在所有节点上做相同的操作

3.两种解题思路

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

在这里插入图片描述

4.后序位置的特殊之处

中序位置主要用在 BST 场景中,你完全可以把 BST 的中序遍历认为是遍历有序数组。

前序位置本身其实没有什么特别的性质,之所以你发现好像很多题都是在前序位置写代码,实际上是因为我们习惯把那些对前中后序位置不敏感的代码写在前序位置罢了。

你可以发现,前序位置的代码执行是自顶向下的,而后序位置的代码执行是自底向上的:

在这里插入图片描述

重点2

但这里面大有玄妙,意味着前序位置的代码只能从函数参数中获取父节点传递来的数据,而后序位置的代码不仅可以获取参数数据,还可以获取到子树通过函数返回值传递回来的数据

举具体的例子,现在给你一棵二叉树,我问你两个简单的问题:

1、如果把根节点看做第 1 层,如何打印出每一个节点所在的层数?

2、如何打印出每个节点的左右子树各有多少节点?

第一个问题从根节点就能给出答案,而第二个问题必须遍历完子树之后才能给出答案

结合这两个简单的问题,你品味一下后序位置的特点,只有后序位置才能通过返回值获取子树的信息。

那么换句话说,一旦你发现题目和子树有关,那大概率要给函数设置合理的定义和返回值,在后序位置写代码了

例题lc543题 二叉树的直径

5.以树的视角看 动归/回溯/DFS算法的区别和联系

DFS 算法和回溯算法非常类似,只是在细节上有所区别

这个细节上的差别是什么呢?其实就是「做选择」和「撤销选择」到底在 for 循环外面还是里面的区别,DFS 算法在外面,回溯算法在里面。

为什么有这个区别?还是要结合着二叉树理解。这一部分我就把回溯算法、DFS 算法、动态规划三种经典的算法思想,以及它们和二叉树算法的联系和区别,用一句话来说明:

动归/DFS/回溯算法都可以看做二叉树问题的扩展,只是它们的关注点不同

  • 动态规划算法属于分解问题的思路,它的关注点在整棵「子树」
  • 回溯算法属于遍历的思路,它的关注点在节点间的「树枝」
  • DFS 算法属于遍历的思路,它的关注点在单个「节点」

三个例子解释三种情况

1.计算一棵二叉树有多少个节点?

// 定义:输入一棵二叉树,返回这棵二叉树的节点总数
int count(TreeNode root) {if (root == null) {return 0;}// 我这个节点关心的是我的两个子树的节点总数分别是多少int leftCount = count(root.left);int rightCount = count(root.right);// 后序位置,左右子树节点数加上自己就是整棵树的节点数return leftCount + rightCount + 1;
}

你看,这就是动态规划分解问题的思路,它的着眼点永远是结构相同的整个子问题,类比到二叉树上就是「子树」

你再看看具体的动态规划问题,比如 动态规划框架套路详解 中举的斐波那契的例子,我们的关注点在一棵棵子树的返回值上:

2.使用遍历的思路写一个traverse函数,打印出遍历这棵二叉树的过程

回溯算法遍历的思路,它的着眼点永远是在节点之间移动的过程,类比到二叉树上就是[树枝]

3.把二叉树的每个节点值都+1

void traverse(TreeNode root) {if (root == null) return;// 遍历过的每个节点的值加一root.val++;traverse(root.left);traverse(root.right);
}

你看,这就是 DFS 算法遍历的思路,它的着眼点永远是在单一的节点上,类比到二叉树上就是处理每个「节点」

你再看看具体的 DFS 算法问题,比如 一文秒杀所有岛屿题目 中讲的前几道题,我们的关注点是 grid 数组的每个格子(节点),我们要对遍历过的格子进行一些处理,所以我说是用 DFS 算法解决这几道题的:

有了这些铺垫,你就很容易理解为什么回溯算法和 DFS 算法代码中「做选择」和「撤销选择」的位置不同了,看下面两段代码:

// DFS 算法把「做选择」「撤销选择」的逻辑放在 for 循环外面
void dfs(Node root) {if (root == null) return;// 做选择print("我已经进入节点 %s 啦", root)for (Node child : root.children) {dfs(child);}// 撤销选择print("我将要离开节点 %s 啦", root)
}// 回溯算法把「做选择」「撤销选择」的逻辑放在 for 循环里面
void backtrack(Node root) {if (root == null) return;for (Node child : root.children) {// 做选择print("我站在节点 %s 到节点 %s 的树枝上", root, child)backtrack(child);// 撤销选择print("我将要离开节点 %s 到节点 %s 的树枝上", child, root)}
}

看到了吧,你回溯算法必须把「做选择」和「撤销选择」的逻辑放在 for 循环里面,否则怎么拿到「树枝」的两个端点?

6.层序遍历(简单过一下)

二叉树题型主要是用来培养递归思维的,而层序遍历属于迭代遍历,也比较简单,这里就过一下代码框架吧:

// 输入一棵二叉树的根节点,层序遍历这棵二叉树
void levelTraverse(TreeNode root) {if (root == null) return;Queue<TreeNode> q = new LinkedList<>();q.offer(root);// 从上到下遍历二叉树的每一层while (!q.isEmpty()) {int sz = q.size();// 从左到右遍历每一层的每个节点for (int i = 0; i < sz; i++) {TreeNode cur = q.poll();// 将下一层节点放入队列if (cur.left != null) {q.offer(cur.left);}if (cur.right != null) {q.offer(cur.right);}}}
}

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

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

相关文章

[Vision Board创客营]--使用openmv识别阿尼亚

文章目录 [Vision Board创客营]使用openmv识别阿尼亚介绍环境搭建训练模型上传图片生成模型 使用结语 [Vision Board创客营]使用openmv识别阿尼亚 &#x1f680;&#x1f680;五一和女朋友去看了《间谍过家家 代号&#xff1a;白》&#xff0c;入坑二刺螈&#xff08;QQ头像也换…

卷积特征图与感受野

特征图尺寸和感受野是卷积神经网络中非常重要的两个概念&#xff0c;今天来看一下&#xff0c;如何计算特征尺寸和感受野。 特征图尺寸 卷积特征图&#xff0c;是图片经过卷积核处理之后的尺寸。计算输出特征的尺寸&#xff0c;需要给出卷积核的相关参数包括&#xff1a; 输…

cubic 相比 bbr 并非很糟糕

迷信 bbr 的人是被它的大吞吐所迷惑&#xff0c;我也不想再解释&#xff0c;但我得反过来说一下 cubic 并非那么糟。 想搞大吞吐的&#xff0c;看看我这个 pixie 算法&#xff1a;https://github.com/marywangran/pixie&#xff0c;就着它的思路改就是了。 cubic 属于 aimd-ba…

嵌入式C语言高级教程:实现基于STM32的智能健康监测手环

智能健康监测手环能够实时监控用户的生理参数&#xff0c;如心率、体温和活动量&#xff0c;对于健康管理和疾病预防非常有帮助。本教程将指导您如何在STM32微控制器上实现一个基本的智能健康监测手环。 一、开发环境准备 硬件要求 微控制器&#xff1a;STM32L476RG&#xf…

完美撤离暗区突围测试资格获取指南 超简单的暗区突围资格申请

完美撤离&#xff01;暗区突围测试资格获取指南 超简单的暗区突围资格申请&#xff01; 最近游戏圈关注度最高的一件事莫过于暗区突围国际服的上线&#xff0c;随着暗区突围PC端的上线&#xff0c;这款游戏的测试资格申请成为了玩家们心头的一个大问题&#xff0c;许多玩家爱不…

二叉树进阶 --- 中

目录 1. find 的递归实现 2. insert 的递归实现 3. erase 的递归实现 3.1. 被删除的节点右孩子为空 3.2. 被删除的节点左孩子为空 3.3. 被删除的节点左右孩子都不为空 4. 析构函数的实现 5. copy constructor的实现 6. 赋值运算符重载 7. 搜索二叉树的完整实现 1. fi…

【Linux】从零开始认识动静态库 -动态库

送给大家一句话&#xff1a; 我不要你风生虎啸&#xff0c; 我愿你老来无事饱加餐。 – 梁实秋 《我把活着欢喜过了》 ଘ(੭ˊᵕˋ)੭* ੈ✩‧₊˚ଘ(੭ˊᵕˋ)੭* ੈ✩‧₊˚ଘ(੭ˊᵕˋ)੭* ੈ✩‧₊˚ ଘ(੭ˊᵕˋ)੭* ੈ✩‧₊˚ଘ(੭ˊᵕˋ)੭* ੈ✩‧₊˚ଘ(੭ˊᵕˋ)੭…

基于CCS5.5的双音多频(DTMF)信号检测仿真实验(①检测型音频文件②输入生成音频并检测)

DTMF的优点 我们知道,DTMF根本上仍然是频谱分析,基础还是DFT,但DFT通常需要对一整段数据做变换,而DTMF不同,每输入一个采样点就计算一次,更有利于硬件实现。 基于CCS的双音多频(DTMF)信号检测原理 公式详细推导 详细的公式推导在下面这篇博客中已经进行了详细的描述,…

示例十、红外遥控器

通过以下几个示例来具体展开学习,了解红外遥控器原理及特性&#xff0c;学习红外遥控器的应用&#xff08;干货版&#xff09;&#xff1a; 示例十、红外遥控器 ino文件源码&#xff1a; //Arduino C demo #include "IRremote.h"IRrecv irrecv(4); decode_results …

python自动化办公的代码

以下是一个简单的Python自动化办公代码示例&#xff0c;用于实现一些基本的自动化任务&#xff0c;例如打开文件、读取数据、写入数据和保存文件等。 python import os # 打开文件 def open_file(filename): try: file open(filename, r) data file.read() file.close() ret…

词令蚂蚁庄园今日答案如何在微信小程序查看蚂蚁庄园今天问题的正确答案?

词令蚂蚁庄园今日答案如何在微信小程序查看蚂蚁庄园今天问题的正确答案&#xff1f; 1、打开微信&#xff0c;点击搜索框&#xff1b; 2、打开搜索页面&#xff0c;选择小程序搜索&#xff1b; 3、在搜索框&#xff0c;输入词令搜索点击进入词令微信小程序&#xff1b; 4、打开…

验证码生成--kaptcha

验证码生成与点击重新获取验证码 如图所示&#xff0c;本文档仅展示了验证码的生成和刷新显示。 1. 概述 系统通过生成随机验证码图像和文本。 2. 代码分析 2.1. Maven依赖 <dependency><groupId>com.github.penggle</groupId><artifactId>kaptch…