当前位置: 首页 > news >正文

【Fifty Project - D21】

今日完成记录

TimePlan完成情况
9:00 - 10:00爬楼梯
12:00 - 14:00Leetcode
14:00 - 15:00《挪威的森林》

Leetcode

每日一题

今天的每日一题是个easy:给定一个数组,要求统计其中数位为偶数的数的数量。数字范围在0-1e5 我就直接写了个遍历加if判断了。

hard我唯唯诺诺,easy我重拳出击!
每日一题居然这么快就不滑动窗口了?

树上倍增算法

上周周赛当时说要补的题结果转头就忘光光了,昨天重新去看了题目,发现自己的思路还是太暴力了,看了0x3f的题解发现原来要用树上倍增,今天就来重新复习一下树上倍增算法吧

树上倍增算法是一种典型的空间换时间算法,通过记忆更多的祖先以实现快速查找第k个祖先,也是一个典型的充分利用二进制思想的算法。
具体来说,给定一棵n个节点的树,对于任意节点记忆最多指定的 2 m 2^m 2m个祖先,其中 2 m > = n 2^m >= n 2m>=n,也就是m = 32 - Integer.numberOfLeadingZeros(n),用pa数组记录,pa是一个n * m的数组,其中pa[i][j]表示节点i的第 2 j 2^j 2j个祖先,便可以通过特定的方法实现O(logn)复杂度查询任意节点的第k个祖先。

接下来我们说一下为什么要这么构造pa,以及如何利用pa查询任意祖先。

如下图是一个15个节点的满二叉树,根据上面的定义得到pa数组是一个15*4的数组
十五个节点的满二叉树
该算法的目的是通过每个节点记录m个祖先实现O(logn)查询任意节点的任意祖先。

先不考虑如何实现O(logn)的问题,我们通过最暴力的做法从一般到特殊地去实现。最暴力的做法是每次向上跳一级,每次都找自己的父节点,然后k–直到k=0就找到了目标祖先,这种方法因为每次只跳一级,所以需要跳k次。那么想一想如果每次多跳几级,是不是可以少跳几次呢?假设每次跳2级(先不考虑复杂情况,也就是k为奇数,也不考虑怎么实现每次跳2级),那么实际上我们只需要跳k / 2次。至此我们发现了一点:目标是k,实际上每次可以跳任意次数,可以直接找爹,也可以找爷爷,当然如果你能实现也可以直接找祖先。也可以得到一个结论:记一次寻找祖先需要j次,那么 k = a 0 + a 1 + . . . + a j − 1 k = a_0 + a_1 + ... + a_{j - 1} k=a0+a1+...+aj1,其中 a i a_i ai表示第i次跳的级数。

接下来我们需要想一想,怎么用一种通用的方法拆解这个k,以及如何记忆节点的这些祖先节点,首先我们不难发现如果希望得到k祖先的跳跃次数更少,那么相应的记忆的祖先就会更多,相反亦然。那么接下来我们从最暴力的方法开始,从两个极端开始考虑:时间最小和空间最小。
时间最小:每次都是O(1)查询到k祖先,也就是只跳一次,对于任意k都可以直接查询到,那只能是记忆每个节点的每个祖先,那么pa数组大小应该是n * n,并且构造这个数组也得花费n * n 时间。
空间最小:极端情况就是上面提到的暴力解法,pa只记录每个节点的父节点,那么大小是 n * 1,而每次查询只能逐级向上跳,也就是时间复杂度O(n)。

很明显上面两种极端思想都不是我们想要的,我们需要在时间和空间上有所折中。树上倍增算法就利用了二进制实现了对k的拆分以及pa数组的记忆。首先任意一个数可以很容易实现二进制拆分,如15 = 1 + 2 + 4 + 8. 然后pa数组记录每个节点的 2 i 2 ^ i 2i的祖先,上面的满二叉树对应的pa数组如下表

node\pa0123
1-1-1-1-1
21-1-1-1
31-1-1-1
421-1-1
521-1-1
631-1-1
731-1-1
842-1-1
942-1-1
1052-1-1
1152-1-1
1263-1-1
1363-1-1
1473-1-1
1573-1-1

说完树上倍增是如何实现k的拆分以及pa数组的构造,接下来说一下如何利用二进制和pa进行k数组的查询,如上例子,我们查询节点10的第三个祖先,根据二进制拆分k = 1 + 2,那么接下来只需要从节点10向上跳两次,一次跳1个祖先一次跳2个祖先。pa存储的便是每个节点的2的整数幂个祖先,所以从pa可以直接取到节点10的 2 0 2^0 20祖先即pa[10][0] = 5,接下来找节点5的 2 1 2 ^ 1 21祖先即pa[5][1] = 1,得节点10的第三个祖先是1.

代码实现pa数组的构造

private void buildPa(int[] p){int n = p.length;int m = 32 - Integer.numberOfLeadingZeros(n);this.pa = new int[n][m];for(int i = 0; i < n; i ++) pa[i][0] = p[i];for(int i = 1; i < m; i ++){// 这层循环逐步构造pa[j][i] 因为pa[j][0]已经初始化好了,就可以根据pa[j][0]构造pa[j][1],如此循环下去for(int j = 0; j < n; j ++ ){// 谨记摇摇车上唱的“爸爸的爸爸是爷爷”// pa[j][i]就是我们要找的爷爷,所以先看看爸爸是否存在 -1则不存在 存在的话就找一下爸爸的爸爸pa[j][i] = pa[j][i - 1] == -1 ? -1 : pa[pa[j][i - 1]][i - 1];}}
}

代码实现如何通过pa找到节点的k祖先

public int findKthAncestor(int node, int k){// 分解k 实际实现就是从他的二进制表示中最高位(非0)开始向低位遍历for(int i = 32 - Integer.numberOfLeadingZeros(k); i > 0 && node != -1; i --){if((k >> (i - 1) & 1) == 1) node = pa[node][i];}return node;
}

树上倍增算法拓展还可以用于两个节点u和v的最近公共祖先查询,首先利用一次dfs标记每个节点的深度并存储。查询u和v的最近公共祖先首先通过深度对比,统一二者的深度(更深的那个先跳到更浅节点的同一高度),此时有两种情况,一个是指向了同一个节点(浅者为两者最近公共祖先),另一个则是不同节点,那么此时两者共同向上跳,尽可能远地向上跳。
对于情况2,设i = log(n) 下取整,让u和v按照下面策略同时向上跳, 直到i为0:

  • 如果pa[u][i] == -1 那么说明跳不了这么高,lca在下面,i–
  • 如果pa[u][i] != -1, 如果pa[u][i] == pa[v][i],那么说明lca在当前点或者在当前点的下面,令i–;如果pa[u][i] != pa[v][i] 说明lca在当前的上面,那么令u = pa[u][i], v = pa[v][i],i–

重复上述操作直到i = 0,此时lca = pa[u][0]。也就是走到最后会停在lca的子节点处,这也正是为什么pa[u][i] == pa[v][i]时依然直接i-- 而不是赋值u和v到新的节点。
树上倍增算法板子题
针对图的路径存在性查询II:上周周赛要补的题,题干是给定长度为n的数组nums(无序),这是n个节点的权重,现规定如果两个节点权重的差值小于等于maxDiff则存在一条无向边,现给出q个询问,每次询问两个节点的最短路径长度,不通则返回-1.
思路:这个题从针对图的路径存在性查询I的思路过来,自然想到首先应该处理nums数组,带下标排序,使nums升序,这样可以用双指针很快处理出哪些节点存在边。题目的q数据规模是1e5,n也是1e5,所以每次查询应该是O(logn),这里就需要结合树上倍增算法,通过双指针得到直接父节点,然后用树上倍增记忆每个节点的 2 i 2^i 2i祖先,然后用求lca的方法找到从孙节点到祖先节点的跳数,即为两点的最小路径长度。详细讲解请参考0x3f大佬的题解。

其中还学到了很好的带下标排序写法(之前都是写一个内部类存排序元素和下标再排序)

Integer[] idx = new Integer[n];
Arrays.setAll(idx, i -> i);
Arrays.sort(idx, (o1, o2) -> nums[o1] - nums[o2]);

《挪威的森林》

今天阅读了第四章,绿子出场了,好像也是一个过往很艰难的女孩,然后莫名其妙地两人就在一起了,emm算在一起吗,“我”还在等着直子新的回信… 复杂的剧情下“我”又和一个被绿了的女孩one night stand。很难评~

http://www.xdnf.cn/news/230563.html

相关文章:

  • w314基于java无人超市管理系统设计与实现
  • 【数据库原理及安全实验】实验五 数据库备份与恢复
  • 短视频矩阵系统贴牌开发实战:批量剪辑文件夹功能设计与实现
  • mybatis-plus 枚举实现模版,导入,导出
  • JVM——Java的基本类型的实现
  • 【ArcGISPro学习笔记】布局输出时图例总是有省略号怎么办?
  • 大连理工大学选修课——机器学习笔记(1):概述
  • 【c++】【STL】list详解
  • Laravel + Vue 3 (Vite、TypeScript) SPA 设置全攻略
  • 在Windows系统上如何用Manifest管理嵌入式项目
  • SVTAV1 编码函数 svt_aom_is_pic_skipped
  • 逻辑回归在信用卡欺诈检测中的实战应用
  • 解决GoLand无法Debug的问题
  • GCC-C语言“自定义段”
  • 2025东三省B题深圳杯B题数学建模挑战赛数模思路代码文章教学
  • AI Agent新范式:FastGPT+MCP协议实现工具增强型智能体构建
  • 2024睿抗CAIP-编程技能赛-本科组(省赛)题解
  • 软考:硬件中的CPU架构、存储系统(Cache、虚拟内存)、I/O设备与接口
  • iview内存泄漏
  • Copilot重磅更新:引用文件夹创建Word文档
  • OpenCV 4.7企业级开发实战:从图像处理到目标检测的全方位指南
  • 二进制如何与三生原理实现统一?
  • LVGL -按键介绍 下
  • C# 高效操作excel文件
  • JavaWeb学习打卡-Day6-SpringBean管理、SpringBoot自动装配、Maven高级
  • JConsole监控centos服务器中的springboot的服务
  • AbMole小百科:OK432如何为肿瘤和免疫研究开辟新路径?
  • huggingface下载数据和模型,部分下载,本地缓存等常见问题踩坑
  • 计算机视觉综合实训室解决方案
  • Java 未来技术栈:从云原生到 AI 融合的企业级技术演进路线