数据结构与算法面试专题——堆排序
完全二叉树
完全二叉树中如果每棵子树的最大值都在顶部就是大根堆
完全二叉树中如果每棵子树的最小值都在顶部就是小根堆
设计目标:完全二叉树的设计目标是高效地利用存储空间,同时便于进行层次遍历和数组存储。它的结构使得每个节点的子节点都可以通过简单的计算得到,从而实现快速的节点访问。
实现原理:完全二叉树是一棵满二叉树,除了最后一层外,每一层都被完全填充。最后一层的节点都集中在左边。这种结构可以用数组来存储,其中数组的索引对应节点的位置。例如,对于索引为i的节点,其左子节点的索引为2i+1,右子节点的索引为2i+2,父节点的索引为(i-1)/2。
优点:空间利用率高,易于实现数组存储。层次遍历简单,可以通过数组索引快速访问节点。
缺点:插入和删除节点可能比较耗时,特别是当需要保持完全二叉树的结构时,可能需要重新调整整个结构。
堆结构
堆结构就是用数组实现的完全二叉树结构
设计目标:堆结构的设计目标是实现高效的优先级队列和排序算法。它基于完全二叉树的结构,通过维护堆序性(父节点的值大于或等于子节点的值,或者小于或等于子节点的值)来实现快速的插入和删除操作。
实现原理:堆结构通常用数组来实现,其中数组的索引对应节点的位置。在堆中,每个父节点的值都满足一定的条件,例如最大堆中父节点的值大于或等于子节点的值,最小堆中父节点的值小于或等于子节点的值。插入操作通过在数组末尾添加元素,然后通过上浮(Percolate Up)来维护堆序性。删除操作通常是指删除堆顶元素,然后通过下沉(Percolate Down)来维护堆序性。
优点:插入和删除操作的时间复杂度为O(log n),在优先级队列和排序算法中性能优越。空间利用率高,用数组实现简单。
缺点:对于范围查询等操作不友好,无法高效地进行任意元素的查找和删除操作。
如何理解“堆”?
堆本质是一种特殊的树。关于这个树,只要满足两点要求,它就是一个堆:
- 堆是一个完全二叉树;
- 堆中每一个节点的值都必须大于等于(或小于等于)其子树中每个节点的值。
第一点,堆必须是一个完全二叉树。完全二叉树要求,除了最后一层,其他层的节点个数都是满的,最后一层的节点都靠左排列。
第二点,堆中的每个节点的值必须大于等于(或者小于等于)其子树中每个节点的值。实际上,我们还可以换一种说法,堆中每个节点的值都大于等于(或者小于等于)其左右子节点的值。这两种表述是等价的。
对于每个节点的值都大于等于子树中每个节点值的堆,我们叫作“大顶堆”。对于每个节点的值都小于等于子树中每个节点值的堆,我们叫作“小顶堆”。
堆结构的heapInsert操作
将一个新元素插入到堆结构中,并维持堆的性质(即父节点的值大于或等于子节点的值,或者小于或等于子节点的值,视堆的类型而定)。
示例
假设有一个最大堆,堆数组为 [100, 85, 90, 80, 75, 70],现在需要插入元素 95。
-
插入后数组变为
[100, 85, 90, 80, 75, 70, 95]。 -
新元素的位置是 6(从 0 开始计数)。
-
比较新元素与父节点(位置
(6-1)/2=2,值 90):95 > 90,交换两者位置,数组变为[100, 85, 95, 80, 75, 70, 90]。 -
继续比较新元素与新的父节点(位置
(2-1)/2=0,值 100):95 < 100,无需交换。 -
插入完成,堆性质得以维持。
优点:能够在 O(log n) 时间复杂度内完成插入操作。
缺点:需要调整堆的结构,可能导致多次数据交换。
堆结构的heapify操作
在堆结构中,当某个节点的子节点可能违反堆的性质时,通过调整该节点及其子节点,维持堆的性质。
示例
假设有一个最大堆,堆数组为 [95, 85, 90, 80, 75, 70],现在需要对位置 0 的节点进行 heapify 操作。
-
该节点的值为 95,子节点为 85 和 90。
-
95 大于子节点,无需调整。
-
如果堆数组为
[75, 85, 90, 80, 75, 70],需要对位置 0 的节点进行 heapify。 -
比较子节点 85 和 90,选择较大的 90 作为候选。
-
交换 75 和 90,数组变为
[90, 85, 75, 80, 75, 70]。 -
继续 heapify 位置 2 的节点(原来的 75)。
-
比较子节点 80 和 75,交换 75 和 80,数组变为
[90, 85, 80, 75, 75, 70]。 -
继续 heapify 位置 3 的节点(原来的 75),没有子节点,调整完成。
优点:能够在 O(log n) 时间复杂度内完成调整操作。
缺点:需要遍历节点的子节点,可能导致多次数据交换。
堆结构的增大
当堆中的元素数量增加,需要扩展堆的存储空间。
-
通常使用动态数据结构,如动态数组或链表,来实现堆的动态增长。
-
当需要增大堆时,分配新的内存空间,并将原堆中的元素复制到新空间中。
示例
假设堆当前的存储数组大小为 10,已满。需要插入一个新元素,堆将自动增大到大小为 20。
堆结构的减少
当堆中的元素数量减少,可能需要缩小堆的存储空间,以节省内存。
-
释放堆中多余的存储空间,调整存储数组的大小。
-
可以通过动态数据结构的特性,如动态数组的收缩功能,实现堆的缩小。
示例
假设堆当前的存储数组大小为 20,但只有 5 个元素。可以将堆缩小到大小为 10,释放多余的空间。
优先级队列结构
设计目标
优先级队列的设计目标是允许高效地访问和操作优先级最高的元素。与普通队列中的先进先出(FIFO)原则不同,优先级队列中的元素根据其优先级进行排序,并且优先级最高的元素会被优先处理。
实现原理
优先级队列通常使用堆结构来实现,能够高效地维护元素的优先级顺序。
1. 插入操作(Enqueue)
将新元素添加到堆的末尾。
通过上浮(Percolate Up)操作,将新元素与父节点进行比较和交换,直到满足堆的性质。
2. 删除操作(Dequeue)
删除堆顶元素(优先级最高的元素)。
将堆的最后一个元素移动到堆顶。
通过下沉(Percolate Down)操作,将该元素与子节点进行比较和交换,直到满足堆的性质。
优点
插入和删除操作的时间复杂度为 O(log n),在需要动态维护优先级的场景中性能优越。
空间利用率高,可以用数组实现,节省存储空间。
缺点
不支持高效的随机访问和查询操作,无法快速找到任意元素。
对于某些操作(如批量插入),可能需要重新构建整个堆,导致较高的开销。
堆排序
堆排序是一种原地的、时间复杂度为O(n log n)的排序算法。
堆排序不是稳定的排序算法,因为在排序的过程,存在将堆的最后一个节点跟堆顶节点互换的操作,所以就有可能改变值相同数据的原始相对顺序。
实现步骤
-
先让整个数组都变成大根堆结构,建立堆的过程:
-
从上到下的方法,时间复杂度为O(N * logN)
-
从下到上的方法,时间复杂度为O(N)
-
-
把堆的最大值和堆末尾的值交换,然后减少堆的大小之后,再去调整堆,一直周而复始,时间复杂度为O(N * logN)
-
堆的大小减小成0之后,排序完成
实现代码
/*** 对给定的整数数组进行堆排序。* 堆排序是一种基于堆数据结构的排序算法,它的时间复杂度为O(N log N),空间复杂度为O(1)。** @param arr 待排序的整数数组*/public void heapSort(int[] arr) {// 如果数组为空或者长度小于2,直接返回,因为不需要排序if (arr == null || arr.length < 2) {return;}// O(N*logN)// 从数组的最后一个元素开始,依次将每个元素调整为大根堆for (int i = arr.length - 1; i >= 0; i--) {heapify(arr, i, arr.length);}// 初始化堆的大小为数组的长度int heapSize = arr.length;// 将堆顶元素(最大值)与堆的最后一个元素交换,并将堆的大小减1swap(arr, 0, --heapSize);// O(N*logN)// 当堆的大小大于0时,继续进行排序while (heapSize > 0) { // O(N)// 将堆顶元素调整为大根堆heapify(arr, 0, heapSize); // O(logN)// 将堆顶元素(最大值)与堆的最后一个元素交换,并将堆的大小减1swap(arr, 0, --heapSize); // O(1)}}/*** 将指定索引位置的元素插入到堆中,并调整堆结构,使其满足大根堆的性质。* 该方法会将新元素与其父节点比较,如果新元素大于父节点,则交换它们的位置,* 并继续向上比较,直到新元素小于等于父节点或到达堆顶。** @param arr 包含堆元素的数组* @param index 要插入的元素的索引*/public void heapInsert(int[] arr, int index) {// 当当前元素大于其父节点时,交换它们的位置,并更新当前元素的索引while (arr[index] > arr[(index - 1) / 2]) {swap(arr, index, (index - 1) / 2);index = (index - 1) / 2;}}/*** 调整指定索引位置的元素,使其满足大根堆的性质。* 该方法会将指定元素与其子节点比较,如果该元素小于其子节点中的最大值,* 则交换它们的位置,并继续向下比较,直到该元素大于等于其子节点或到达叶子节点。** @param arr 包含堆元素的数组* @param index 要调整的元素的索引* @param heapSize 当前堆的大小*/public void heapify(int[] arr, int index, int heapSize) {// 计算左孩子的下标int left = index * 2 + 1; // 当左孩子存在时,继续循环while (left < heapSize) { // 两个孩子中,谁的值大,把下标给largest// 1)只有左孩子,left -> largest// 2) 同时有左孩子和右孩子,右孩子的值<= 左孩子的值,left -> largest// 3) 同时有左孩子和右孩子并且右孩子的值> 左孩子的值, right -> largest// 找出左右孩子中值较大的那个的下标int largest = left + 1 < heapSize && arr[left + 1] > arr[left] ? left + 1 : left;// 比较父节点和较大孩子的值,将较大值的下标赋给largestlargest = arr[largest] > arr[index] ? largest : index;// 如果父节点已经是最大的,跳出循环if (largest == index) {break;}// 交换父节点和较大孩子的位置swap(arr, largest, index);// 更新当前节点的下标index = largest;// 计算新的左孩子的下标left = index * 2 + 1;}}/*** 交换数组中两个指定索引位置的元素。** @param arr 包含元素的数组* @param i 第一个元素的索引* @param j 第二个元素的索引*/public void swap(int[] arr, int i, int j) {// 使用临时变量交换两个元素的值int tmp = arr[i];arr[i] = arr[j];arr[j] = tmp;}
过程分析
我们可以把堆排序的过程大致分解成两个大的步骤,建堆和排序。
建堆
我们首先将数组原地建成一个堆。所谓“原地”就是,不借助另一个数组,就在原数组上操作。建堆的过程,有两种思路。
第一种是在堆中插入一个元素。尽管数组中包含个数据,但是我们可以假设,起初堆中只包含一个数据,就是下标为1的数据。然后,我们调用插入操作,将下标从2到n的数据依次插入到堆中。这样我们就将包含n个数据的数组,组织成了堆。
第二种实现思路,跟第一种截然相反。第一种建堆思路的处理过程是从前往后处理数组数据,并且每个数据插入堆中时,都是从下往上堆化。而第二种实现思路,是从后往前处理数组,并且每个数据都是从上往下堆化。
逐层调整,直到整个数组满足堆的条件。
- 从最后一个非叶子节点开始,逐层向上进行 heapify 操作。
- 最后一个非叶子节点的索引为:floor((n-2)/2),其中 n 是数组的长度。
- 使用 heapify 调整以该节点为根的子树。
排序
建堆结束之后,数组中的数据已经是按照大顶堆的特性来组织的。数组中的第一个元素就是堆顶,也就是最大的元素。我们把它跟最后一个元素交换,那最大元素就放到了下标为n的位置。
当堆顶元素移除之后,我们把下标为n的元素放到堆顶,然后再通过堆化的方法,将剩下的n-1个元素重新构建成堆。堆化完成之后,我们再取堆顶的元素,放到下标是n一1的位置,一直重复这个过程,直到最后堆中只剩下标为1的一个元素,排序工作就完成了。
为什么快速排序要比堆排序性能好?
堆排序数据访问的方式没有快速排序友好
快速排序的数据访问方式:
-
快速排序是基于分治法(Divide and Conquer)的排序算法。它通过选择一个基准值(pivot),将数组划分为两个子数组,左侧子数组的元素小于或等于基准值,右侧子数组的元素大于或等于基准值。然后递归地对这两个子数组进行排序。在分割过程中,快速排序对数组中的元素进行连续的比较和交换,这种操作在内存中的数据访问模式通常是连续的,与现代计算机的缓存机制非常匹配。连续的内存访问使得缓存命中率更高,减少了内存访问的延迟,从而提高了排序的效率。
-
例如,当对一个已部分排序的数组进行快速排序时,基准值的选择和分割操作会使得数组被分割为较小的子数组,而这些子数组在内存中是连续存储的。对这些子数组进行递归排序时,缓存能够有效地预取(cache prefetch)这些连续的数据,减少了从主存中加载数据的次数,加快了排序的速度。
堆排序的数据访问方式:
-
堆排序依赖于堆数据结构,需要频繁地访问父节点和子节点。在堆排序过程中,数据的访问模式是随机的,无法充分利用内存的局部性原理。堆的父节点和子节点在内存中可能不是连续存储的,因此访问这些节点时,常常会导致缓存未命中。缓存未命中会增加内存访问的延迟,降低排序的效率。
-
例如,在调整堆结构时,需要比较父节点和子节点的值,并根据比较结果进行交换。这种访问模式使得数据在内存中的跳转较为频繁,无法像快速排序那样利用缓存的预取特性,导致性能下降。
对于同样的数据,在排序过程中,堆排序算法的数据交换次数要多于快速排序
快速排序的数据交换次数:
-
快速排序的平均时间复杂度为O(n log n),其数据交换次数相对较少。在快速排序的分割过程中,每个元素会被交换到正确的位置,但整个过程中交换的次数通常比堆排序少。快速排序的递归结构使得大部分元素的比较和交换发生在较小的子数组中,减少了不必要的数据移动。
-
例如,对一个随机分布的数组进行快速排序时,每次分割操作会使得元素被快速地放置在接近其最终位置的地方。这减少了后续排序过程中需要移动的数据量,从而降低了数据交换的总次数。
堆排序的数据交换次数:
-
堆排序的主要操作是构建堆和调整堆结构。在构建堆的过程中,需要多次比较和交换元素。每次插入或删除元素时,堆结构的维护需要进行多次比较和交换,以确保父节点的值大于或等于子节点的值(对于最大堆而言)。在堆排序的整个过程中,数据交换的次数相对较多。
-
例如,当构建一个最大堆时,每个元素都需要与它的子节点进行比较和交换。如果一个元素的值小于它的子节点中的较大者,就需要交换它们的位置,并继续对子节点进行调整。这个过程会涉及到多次数据交换,直到堆的性质被满足。在堆排序的调整过程中,这样的交换操作会频繁发生,导致数据交换次数增加,从而影响了排序的性能。
相关文章:
数据结构与算法面试专题——堆排序
完全二叉树 完全二叉树中如果每棵子树的最大值都在顶部就是大根堆 完全二叉树中如果每棵子树的最小值都在顶部就是小根堆 设计目标:完全二叉树的设计目标是高效地利用存储空间,同时便于进行层次遍历和数组存储。它的结构使得每个节点的子节点都可以通过简…...
《On Java进阶卷》阅读笔记(五)
第7章 IO系统 I/O流: IO有很多不同的来源和去处,如文件、控制台网络连接等,而且还涉及需求以很多种方式,如顺序读取、随机访问、缓冲、字符、按行读取、按字读取等。 Java8的函数式流相关的类和IO流之间并无关联。 IO流隐藏了…...
《代码随想录》刷题笔记——回溯篇【java实现】
文章目录 组合组合总和 III电话号码的字母组合组合总和组合总和II思路代码实现 分割回文串※思路字符串分割回文串判断效率优化※ 复原 IP 地址优化版本 子集子集 II使用usedArr辅助去重不使用usedArr辅助去重 递增子序列※全排列全排列 II重新安排行程题意代码 N 皇后解数独直…...
数值积分:通过复合梯形法计算
在物理学和工程学中,很多问题都可以通过数值积分来求解,特别是当我们无法得到解析解时。数值积分是通过计算积分区间内离散点的函数值来近似积分的结果。在这篇博客中,我将讨论如何使用 复合梯形法 来进行数值积分,并以一个简单的…...
AcWing——3624. 三值字符串
双指针解法 #include<iostream> #include<unordered_map> using namespace std; int main() {int n; cin >> n;while(n--){unordered_map<char, int> tree;string s; cin >> s;int ans 0x7fffffff; for(int i 0, j 0; j < (int)s.size();…...
【JavaEE进阶】验证码案例
目 🌲实现说明 🎄Hutool介绍 🌳准备工作 🌴约定前后端交互接口 🚩接口定义 🚩实现服务器后端代码 🚩前端代码 🚩整体测试 🌲实现说明 随着安全性的要求越来越⾼…...
Uniapp 短视频去水印解析工具开发实现
最近搞了一个有意思的小工具——短视频去水印解析器!这玩意儿可以把短视频中的水印给抹掉,还能提取视频、封面等资源。整个项目用了 Uniapp 开发,做完后体验了一下,发现还挺顺手。今天就来跟大家聊聊实现思路和代码细节~ 需求分析…...
计算机网络-八股-学习摘要
一:HTTP的基本概念 全称: 超文本传输协议 从三个方面介绍HTTP协议 1,超文本:我们先来理解「文本」,在互联网早期的时候只是简单的字符文字,但现在「文本」的涵义已经可以扩展为图片、视频、压缩包等&am…...
编程速递-庆祝Delphi诞生30周年!
庆祝Delphi 30周年纪念是一个特别的时刻。 回到1995年,也就是30年前,在微软Windows和互联网时代的曙光初现之时,Borland Delphi的创建者们无法想象,当时使用Borland Delphi构建的应用程序至今仍在运行——为全世界数十亿人服务。…...
每天五分钟深度学习框架pytorch:搭建谷歌的Inception网络模块
本文重点 前面我们学习了VGG,从现在开始我们将学习谷歌公司推出的GoogLeNet。当年ImageNet竞赛的第二名是VGG,而第一名就是GoogLeNet,它的模型设计拥有很多的技巧,这个model证明了一件事:用更多的卷积,更深的层次可以得到更好的结构 GoogLeNet的网络结构 如图所示就是Go…...
性能测试流程、主流性能工具
性能测试流程 性能测试流程 测试测试需求分析 性能测试计划和方案 测什么: 测试背景 测试目的 测试范围 谁来测: 进度和分工 交付清单 怎么测: 测试策略 性能测试用例设计 性能测试测试执行 性能分析和调优 性能测试报告 测试报告是…...
DeepSeek4j 已开源,支持思维链,自定义参数,Spring Boot Starter 轻松集成,快速入门!建议收藏
DeepSeek4j Spring Boot Starter 快速入门 简介 DeepSeek4j 是一个专为 Spring Boot 设计的 AI 能力集成启动器,可快速接入 DeepSeek 大模型服务。通过简洁的配置和易用的 API,开发者可轻松实现对话交互功能。 环境要求 JDK 8Spring Boot 2.7Maven/Gr…...
无耳科技 Solon v3.0.8 发布,Java 企业级应用开发框架
Solon 框架! Solon 是新一代,Java 企业级应用开发框架。是杭州无耳科技有限公司的“根级”开源项目(最近“杭州六小龙”很火啊,我们也是杭州的哦)。从零开始构建(No Spring、No Java-EE、No Servlet&#…...
【吾爱出品】针对红警之类老游戏适用WIN10和11的补丁cnc-ddraw7.1汉化版
针对红警之类老游戏适用WIN10和11的补丁cnc-ddraw7.1汉化版 链接:https://pan.xunlei.com/s/VOJ8PZd4avMubnDzHQAeZDxWA1?pwdnjwm# 直接复制到游戏安装目录,保持与游戏主程序同目录下。...
使用 playwright 自定义 js 下载的路径和文件名
遇到一个问题,点击按钮自动下载文件,路径和文件名都不能自定义,可以用 playwright 来解决这个问题 from playwright.sync_api import sync_playwright import os import time class ExcelDownloader: def __init__(self, download_pat…...
Kafka分区管理大师指南:扩容、均衡、迁移与限流全解析
#作者:孙德新 文章目录 分区分配操作(kafka-reassign-partitions.sh)1.1 分区扩容、数据均衡、迁移(kafka-reassign-partitions.sh)1.2、修改topic分区partition的副本数(扩缩容副本)1.3、Partition Reassign场景限流1.4、节点内副本移动到不…...
3.从零开始学会Vue--{{生命周期,工程化,组件化}}
1.生命周期钩子 1.是什么 生命周期 概念:就是一个Vue实例从创建 到 销毁 的整个过程 生命周期包括:① 创建 ② 挂载 ③ 更新 ④ 销毁 四个阶段 1.创建阶段:创建响应式数据 2.挂载阶段:渲染模板 3.更新阶段:修改…...
Python--网络编程
3. 网络编程与Socket 3.1 Socket基础 创建Socket import socket# TCP Socket tcp_socket socket.socket(socket.AF_INET, socket.SOCK_STREAM)# UDP Socket udp_socket socket.socket(socket.AF_INET, socket.SOCK_DGRAM)服务器端函数 函数描述bind((host, port))绑定…...
【java】方法的基本内存原理(栈和堆)
java内存主要分为栈和堆,方法相关的部分主要在栈内存里,每个方法调用时会在栈里创建一个栈帧,存放局部变量和方法执行的信息。执行完后栈帧被销毁,局部变量消失。而对象实例存在堆里,由垃圾回收器管理。 **Java方法内…...
SQLMesh 系列教程4- 详解模型特点及模型类型
SQLMesh 作为一款强大的数据建模工具,以其灵活的模型设计和高效的增量处理能力脱颖而出。本文将详细介绍 SQLMesh 模型的特点和类型,帮助读者快速了解其强大功能。我们将深入探讨不同模型类型(如增量模型、全量模型、SCD Type 2 等࿰…...
超短脉冲激光自聚焦效应
前言与目录 强激光引起自聚焦效应机理 超短脉冲激光在脆性材料内部加工时引起的自聚焦效应,这是一种非线性光学现象,主要涉及光学克尔效应和材料的非线性光学特性。 自聚焦效应可以产生局部的强光场,对材料产生非线性响应,可能…...
逻辑回归:给不确定性划界的分类大师
想象你是一名医生。面对患者的检查报告(肿瘤大小、血液指标),你需要做出一个**决定性判断**:恶性还是良性?这种“非黑即白”的抉择,正是**逻辑回归(Logistic Regression)** 的战场&a…...
Linux相关概念和易错知识点(42)(TCP的连接管理、可靠性、面临复杂网络的处理)
目录 1.TCP的连接管理机制(1)三次握手①握手过程②对握手过程的理解 (2)四次挥手(3)握手和挥手的触发(4)状态切换①挥手过程中状态的切换②握手过程中状态的切换 2.TCP的可靠性&…...
12.找到字符串中所有字母异位词
🧠 题目解析 题目描述: 给定两个字符串 s 和 p,找出 s 中所有 p 的字母异位词的起始索引。 返回的答案以数组形式表示。 字母异位词定义: 若两个字符串包含的字符种类和出现次数完全相同,顺序无所谓,则互为…...
SpringCloudGateway 自定义局部过滤器
场景: 将所有请求转化为同一路径请求(方便穿网配置)在请求头内标识原来路径,然后在将请求分发给不同服务 AllToOneGatewayFilterFactory import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; impor…...
html css js网页制作成品——HTML+CSS榴莲商城网页设计(4页)附源码
目录 一、👨🎓网站题目 二、✍️网站描述 三、📚网站介绍 四、🌐网站效果 五、🪓 代码实现 🧱HTML 六、🥇 如何让学习不再盲目 七、🎁更多干货 一、👨…...
Spring是如何解决Bean的循环依赖:三级缓存机制
1、什么是 Bean 的循环依赖 在 Spring框架中,Bean 的循环依赖是指多个 Bean 之间互相持有对方引用,形成闭环依赖关系的现象。 多个 Bean 的依赖关系构成环形链路,例如: 双向依赖:Bean A 依赖 Bean B,同时 Bean B 也依赖 Bean A(A↔B)。链条循环: Bean A → Bean…...
uniapp 开发ios, xcode 提交app store connect 和 testflight内测
uniapp 中配置 配置manifest 文档:manifest.json 应用配置 | uni-app官网 hbuilderx中本地打包 下载IOS最新SDK 开发环境 | uni小程序SDK hbulderx 版本号:4.66 对应的sdk版本 4.66 两者必须一致 本地打包的资源导入到SDK 导入资源 | uni小程序SDK …...
Chrome 浏览器前端与客户端双向通信实战
Chrome 前端(即页面 JS / Web UI)与客户端(C 后端)的交互机制,是 Chromium 架构中非常核心的一环。下面我将按常见场景,从通道、流程、技术栈几个角度做一套完整的分析,特别适合你这种在分析和改…...
算法打卡第18天
从中序与后序遍历序列构造二叉树 (力扣106题) 给定两个整数数组 inorder 和 postorder ,其中 inorder 是二叉树的中序遍历, postorder 是同一棵树的后序遍历,请你构造并返回这颗 二叉树 。 示例 1: 输入:inorder [9,3,15,20,7…...
