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

想要精通算法和SQL的成长之路 - 滑动窗口和大小根堆

想要精通算法和SQL的成长之路 - 滑动窗口和大小根堆

  • 前言
  • 一. 大小根堆
  • 二. 数据流的中位数
    • 1.1 初始化
    • 1.2 插入操作
    • 1.3 完整代码
  • 三. 滑动窗口中位数
    • 3.1 在第一题的基础上改造
    • 3.2 栈的remove操作

前言

想要精通算法和SQL的成长之路 - 系列导航

一. 大小根堆

先来说下大小根堆是什么:
在这里插入图片描述

  • 大根堆:栈顶元素最大(上图左侧部分),栈底至栈顶元素值递增。
  • 小根堆:栈顶元素最小(上图右侧部分),栈底至栈顶元素值递减。

Java当中,可以用什么来表示大小根堆?

小根堆:

Queue<Integer> small = new PriorityQueue<>();
// 或者 x - y 是计算,在特殊情况下可能造成精度越界的情况
Queue<Integer> small = new PriorityQueue<>((x, y) -> x - y);
// 或者,Integer.compare 是纯比较,不会出现精度越界
Queue<Integer> small = new PriorityQueue<>((x, y) -> Integer.compare(x, y));
// 或者
Queue<Integer> small = new PriorityQueue<>(Integer::compare);

大根堆:

Queue<Integer> big = new PriorityQueue<>((x, y) -> y - x);

大小根堆的常规操作:

  • 获取栈顶元素:peek();
  • 栈顶元素移除:poll();

二. 数据流的中位数

原题链接
在这里插入图片描述
在这里插入图片描述

再说下我们的思路:

  1. 同时维护大小根堆,并且约定小根堆的元素个数总是 >= 大根堆元素个数(最多个数多一个)。
  2. 如果元素个数是奇数,那么中位数就是小根堆堆顶元素。
  3. 如果元素个数是偶数,那么中位数就是(大根堆堆顶 + 小根堆堆顶) / 2。

1.1 初始化

Queue<Integer> big, small;/*** big                      small* 最小值 ---> 大根堆顶 中位数 小根堆顶 ---> 最大值*/
public MedianFinder() {small = new PriorityQueue<>();// 小根堆,堆顶元素最小(存储比中位数大的部分)big = new PriorityQueue<>((x, y) -> y - x);// 大根堆,堆顶元素最大(存储比中位数小的部分)
}

1.2 插入操作

插入的时候,我们考虑到两种情况:

  • 如果大小根堆的元素个数相等,我们优先把新元素加入到小根堆。
  • 否则,将元素加入到大根堆。

但是,我们并不知道以下三者的关系:

  • 大根堆堆顶元素值。
  • 当前待加入元素值。
  • 小根堆堆顶元素值。

而我们需要去维护他们,一定满足:大根堆堆顶元素值 < 小根堆堆顶元素值。

咋办呢?以第一种情况为例,我们可以:

  • 先把元素加入到大根堆。那么经过排序后,大根堆的堆顶元素就是最大的那个(可能是当前元素,也可能不是)。此时大根堆Size > 小根堆Size
  • 把大根堆堆顶元素移除,加入到小根堆。小根堆经过排序后,这样就能保证大根堆堆顶元素值 < 小根堆堆顶元素值。

写成代码就是:

public void addNum(int num) {// 如果大小根堆 的 大小 一样,我们往小根堆放元素。让小根堆size >= 大根堆sizeif (big.size() == small.size()) {// 方式一定是先让放大根堆,再把大根堆的堆顶元素移除到小根堆big.add(num);small.add(big.poll());} else {small.add(num);big.add(small.poll());}
}

1.3 完整代码

那么查询函数就更简单了,结合上面的思路,我们得到完整代码如下:

public class MedianFinder {Queue<Integer> big, small;/*** big                      small* 最小值 ---> 大根堆顶 中位数 小根堆顶 ---> 最大值*/public MedianFinder() {small = new PriorityQueue<>();// 小根堆,堆顶元素最小(存储比中位数大的部分)big = new PriorityQueue<>((x, y) -> y - x);// 大根堆,堆顶元素最大(存储比中位数小的部分)}public void addNum(int num) {// 如果大小根堆 的 大小 一样,我们往小根堆放元素。让小根堆size >= 大根堆sizeif (big.size() == small.size()) {// 方式一定是先让放大根堆,再把大根堆的堆顶元素移除到小根堆big.add(num);small.add(big.poll());} else {small.add(num);big.add(small.poll());}}public double findMedian() {return small.size() == big.size() ? (small.peek() + big.peek()) / 2.0 : small.peek();}
}

三. 滑动窗口中位数

原题链接
在这里插入图片描述
思路如下:

  1. 我们先创建一个窗口,把前k个数字通过大小根堆的方式去维护(题目一的思路)。
  2. 后续每次滑动窗口的移动,都带来两个变数:一个旧元素会从窗口出移除(但是从大根堆移除还是小根堆移除?),一个新元素会加入到窗口中(加入到大根堆还是小根堆?)
  3. 由于第二步的变数,可能导致大小根堆的Size不均衡。我们的目的:让小根堆的Size >= 大根堆Size,最多多一个元素。
  4. 因此每次滑动窗口的移动,我们还需要维护大小根堆。

3.1 在第一题的基础上改造

首先考虑到精度的问题,我们的大小根堆不能在根据差值来比较了,而是:

right = new PriorityQueue<>((x, y) -> Integer.compare(x, y));// 小根堆,堆顶元素最小(存储比中位数大的部分)
left = new PriorityQueue<>((x, y) -> Integer.compare(y, x));// 大根堆,堆顶元素最大(存储比中位数小的部分)

其次,求中位数的时候,也需要大小根堆的堆顶元素,先除以2,再和相加:

if (left.size() == right.size()) {return (left.peek() / 2.0) + (right.peek() / 2.0);

最终代码如下:

public class Test480 {Queue<Integer> left, right;public double[] medianSlidingWindow(int[] nums, int k) {right = new PriorityQueue<>((x, y) -> Integer.compare(x, y));// 小根堆,堆顶元素最小(存储比中位数大的部分)left = new PriorityQueue<>((x, y) -> Integer.compare(y, x));// 大根堆,堆顶元素最大(存储比中位数小的部分)int len = nums.length;// 结果集double[] res = new double[len - k + 1];// 创建大小根堆for (int i = 0; i < k; i++) {right.add(nums[i]);}for (int i = 0; i < k / 2; i++) {left.add(right.poll());}// 初始化第一个中位数res[0] = findMedian();for (int i = k; i < len; i++) {// 滑动窗口长度固定,每次移动,都有一个元素要删除和一个元素要新加入int del = nums[i - k], add = nums[i];if (add >= right.peek()) {right.add(add);} else {left.add(add);}// 如果待删除元素在小根堆,在小根堆处删除,否则在大根堆中删除if (del >= right.peek()) {right.remove(del);} else {left.remove(del);}// 维护大小根堆的元素个数adjust();res[i - k + 1] = findMedian();}return res;}void adjust() {while (left.size() > right.size()) {right.add(left.poll());}while (right.size() - left.size() > 1) {left.add(right.poll());}}public double findMedian() {if (left.size() == right.size()) {return (left.peek() / 2.0) + (right.peek() / 2.0);} else {return right.peek() * 1.0;}}
}

这个写法其实是没问题的,但是在元素个数非常大的情况下,就容易超时:
在这里插入图片描述

3.2 栈的remove操作

问题处在优先队列的的一个元素remove操作:
在这里插入图片描述
它是先查找(复杂度O(N)),再进行删除(复杂度O(logN)),所以会超时。因此我们这里可以引入红黑树来进行替代。

有这么几个需要注意的地方:

  1. 我们用TreeSet存储元素的时候,不再是元素值,而是元素的下标。 因为题目中同一个窗口的元素可能重复。元素值相等的时候,根据下标大小来比较。
Comparator<Integer> comparator = (x, y) -> nums[x] != nums[y] ? Integer.compare(nums[x], nums[y]) : x - y;
right = new TreeSet<>(comparator);// 小根堆,堆顶元素最小(存储比中位数大的部分)
left = new TreeSet<>(comparator.reversed());// 大根堆,堆顶元素最大(存储比中位数小的部分)
  1. 滑动窗口移动的时候。需要删除对应的元素下标 ,由于存在重复值,我们需要大小根堆都把这个下标给剔除。
  2. peek函数替代为first函数。poll函数替代为pollFirst函数。

完整代码如下:

public class Test480 {TreeSet<Integer> left, right;int[] nums;public double[] medianSlidingWindow(int[] nums, int k) {this.nums = nums;Comparator<Integer> comparator = (x, y) -> nums[x] != nums[y] ? Integer.compare(nums[x], nums[y]) : x - y;right = new TreeSet<>(comparator);// 小根堆,堆顶元素最小(存储比中位数大的部分)left = new TreeSet<>(comparator.reversed());// 大根堆,堆顶元素最大(存储比中位数小的部分)int len = nums.length;// 结果集double[] res = new double[len - k + 1];// 创建大小根堆for (int i = 0; i < k; i++) {addToWindow(i);}res[0] = findMedian();for (int i = k; i < len; i++) {// 滑动窗口长度固定,每次移动,都有一个元素要删除和一个元素要新加入left.remove(i - k);right.remove(i - k);addToWindow(i);res[i - k + 1] = findMedian();}return res;}void addToWindow(int index) {// 我们总是把新元素先统一加入到大根堆。right.add(index);left.add(right.pollFirst());// 然后再维护大小while (left.size() > right.size()) {right.add(left.pollFirst());}}public double findMedian() {if (left.size() == right.size()) {return (nums[left.first()] / 2.0) + (nums[right.first()] / 2.0);} else {return nums[right.first()] * 1.0;}}
}

相关文章:

想要精通算法和SQL的成长之路 - 滑动窗口和大小根堆

想要精通算法和SQL的成长之路 - 滑动窗口和大小根堆 前言一. 大小根堆二. 数据流的中位数1.1 初始化1.2 插入操作1.3 完整代码 三. 滑动窗口中位数3.1 在第一题的基础上改造3.2 栈的remove操作 前言 想要精通算法和SQL的成长之路 - 系列导航 一. 大小根堆 先来说下大小根堆是什…...

Python算法练习 10.15

leetcode 2130 链表的最大孪生和 在一个大小为 n 且 n 为 偶数 的链表中&#xff0c;对于 0 < i < (n / 2) - 1 的 i &#xff0c;第 i 个节点&#xff08;下标从 0 开始&#xff09;的孪生节点为第 (n-1-i) 个节点 。 比方说&#xff0c;n 4 那么节点 0 是节点 3 的孪…...

智能防眩目前照灯系统控制器ADB

经纬恒润的自适应远光系统—— ADB&#xff08;Adaptive Driving Beam&#xff09; 是一种能够根据路况自适应变换远光光型的智能远光控制系统。根据本车行驶状态、环境状态以及道路车辆状态&#xff0c;ADB 系统自动为驾驶员开启或退出远光。同时&#xff0c;根据车辆前方视野…...

若依 ruoyi 路径 地址 # 井号去除

export default new Router({mode: history, // history 去掉url中的# 、hash 包含#号scrollBehavior: () > ({ y: 0 }),routes: constantRoutes })...

Elasticsearch 和 Arduino:一起变得更好!

作者&#xff1a;Enrico Zimuel 使用 Arduino IoT 设备与 Elasticsearch 和 Elastic Cloud 进行通信的简单方法 在 Elastic&#xff0c;我们不断寻找简化搜索体验的新方法&#xff0c;并开始关注物联网世界。 来自物联网的数据收集可能非常具有挑战性&#xff0c;尤其是当我们…...

基于Ubuntu环境Git 服务器搭建及使用

多人合作开发的时候 常常会需要使用代码管理平台&#xff0c;保持代码一致和解决冲突。在工作中我使用过SVN和TFS&#xff0c;本文说明另外一种平台&#xff0c;Git&#xff0c;下面是基于Ubuntu环境安装并简单使用Git服务器。 确认安装git apt install git levilevi-ThinkPa…...

【quartus13.1/Verilog】swjtu西南交大:计组课程设计

实验目的&#xff1a; 通过学习简单的指令系统及其各指令的操作流程&#xff0c;用 Verilog HDL 语言实 现简单的处理器模块&#xff0c;并通过调用存储器模块&#xff0c;将处理器模块和存储器模块连接形成简 化的计算机核心部件组成的系统。 二. 实验内容 1. 底层用 Verilog…...

基于springboot的网上点餐系统论文开题报告

一、选题背景 随着互联网和移动互联网技术的快速发展&#xff0c;网上点餐成为了人们越来越喜欢的一种点餐方式。一些具有创新意识的餐厅也开始逐渐尝试利用互联网技术来提升用户的点餐体验。因此&#xff0c;开发一个基于Spring Boot的网上点餐系统就显得非常必要和重要。 二…...

Hadoop3教程(九):MapReduce框架原理概述

文章目录 简介参考文献 简介 这属于整个MR中最核心的一块&#xff0c;后续小节会展开描述。 整个MR处理流程&#xff0c;是分为Map阶段和Reduce阶段。 一般&#xff0c;我们称Map阶段的进程是MapTask&#xff0c;称Reduce阶段是ReduceTask。 其完整的工作流程如图&#xff…...

使用PyTorch加载数据集:简单指南

&#x1f497;&#x1f497;&#x1f497;欢迎来到我的博客&#xff0c;你将找到有关如何使用技术解决问题的文章&#xff0c;也会找到某个技术的学习路线。无论你是何种职业&#xff0c;我都希望我的博客对你有所帮助。最后不要忘记订阅我的博客以获取最新文章&#xff0c;也欢…...

【考研数学】线性代数第六章 —— 二次型(2,基本定理及二次型标准化方法)

文章目录 引言一、二次型的基本概念及其标准型1.2 基本定理1.3 二次型标准化方法1. 配方法2. 正交变换法 写在最后 引言 了解了关于二次型的基本概念以及梳理了矩阵三大关系后&#xff0c;我们继续往后学习二次型的内容。 一、二次型的基本概念及其标准型 1.2 基本定理 定理…...

Raven2靶机渗透

1. 信息收集 1.1 主机探测 sudo arp-scan -l1.2 端口扫描 nmap -p- -A 192.168.16.185开放了80端口&#xff0c;尝试登录网址查看信息&#xff0c;通过浏览器插件找出指纹 1.3 目录扫描 访问登录界面&#xff0c;发现remember Me怀疑是shiro界面 登录/vendor/界面&#xff0…...

UE5中双pass解决半透明材质乱序问题

透明度材质乱序问题一直是半透明效果时遇到的比较多的问题&#xff0c;用多pass方案只能说一定程度上解决&#xff0c;当遇到多半透明物体穿插等情况时&#xff0c;仍然不能完美解决。 双pass方案Unity用的比较多&#xff0c;因为Unity支持多个pass绘制。在UE中我们可以以复制多…...

Cisdem Video Player for mac(高清视频播放器) v5.6.0中文版

Cisdem Video Player mac是一款功能强大的视频播放器&#xff0c;适用于 macOS 平台。它可用于播放不同格式的视频文件&#xff0c;并具有一些实用的特性和功能。 Cisdem Video Player mac 中文版软件特点 多格式支持&#xff1a;Cisdem Video Player 支持几乎所有常见的视频格…...

数据库管理-第109期 19c OCM考后感(20231015)

数据库管理-第109期 19c OCM考后感&#xff08;202301015&#xff09; 距离上一篇又过了两周多&#xff0c;为啥又卡了这么久&#xff0c;主要是后面几个问题&#xff1a;1. 9月1日的19c OCM upgrade考试木有过&#xff0c;因为有一次免费补考机会就又预约了10月8日的考试&…...

初出茅庐的小李博客之SPI工作模式

SPI的工作模式 SPI&#xff08;Serial Peripheral Interface&#xff09;是一种同步串行通信协议&#xff0c;常用于连接微控制器和外围设备。SPI有四种模式&#xff0c;分别是0、1、2、3模式。 0模式&#xff1a;时钟空闲时为低电平&#xff0c;数据在时钟的下降沿采样&#…...

SpringCloud-Bus

一、介绍 &#xff08;1&#xff09;bus搭配config可以实现客户端配置自动刷新 &#xff08;2&#xff09;bus支持两种消息代理&#xff0c;rabbitmq和kafka &#xff08;3&#xff09;使用topic模式分发消息 二、项目搭建&#xff08;广播&#xff09; &#xff08;1&#…...

Adobe2024 全家桶更新了,PS、Ai、AE、PR应用尽有

Adobe2024 全家桶更新了&#xff0c;包含的PS、Ai、AE、PR......个人学习&#xff0c;专业领域都是必不可少的软件都有&#xff0c;需要的不要错过了。 如果你不知道从哪里安装这些工具&#xff0c;小编为大家带来了破J版资源&#xff0c;附上详细的安装包及安装教程。 Mac软件…...

【斗破年番】彩鳞换装美翻,雁落天惨死,萧炎暗杀慕兰三老遇险,彩鳞霸气护夫

Hello,小伙伴们&#xff0c;我是小郑继续为大家深度解析斗破苍穹年番资讯。 斗破苍穹动画已经更新了&#xff0c;小医仙与萧炎相认&#xff0c;三国联军撤退&#xff0c;随后彩鳞与萧炎以及小医仙夜晚相会&#xff0c;一起制定了刺杀行动。从官方公布的第68集预告&#xff0c;彩…...

华为端到端战略管理体系(DSTE开发战略到执行)的运作日历图/逻辑图及DSTE三大子流程介绍

华为端到端战略管理体系&#xff08;DSTE开发战略到执行&#xff09;的运作日历图/逻辑图及DSTE三大子流程介绍 本文作者 | 谢宁&#xff0c;《华为战略管理法&#xff1a;DSTE实战体系》、《智慧研发管理》作者 添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#…...

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式

一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明&#xff1a;假设每台服务器已…...

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)

题目&#xff1a;3442. 奇偶频次间的最大差值 I 思路 &#xff1a;哈希&#xff0c;时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况&#xff0c;哈希表这里用数组即可实现。 C版本&#xff1a; class Solution { public:int maxDifference(string s) {int a[26]…...

利用ngx_stream_return_module构建简易 TCP/UDP 响应网关

一、模块概述 ngx_stream_return_module 提供了一个极简的指令&#xff1a; return <value>;在收到客户端连接后&#xff0c;立即将 <value> 写回并关闭连接。<value> 支持内嵌文本和内置变量&#xff08;如 $time_iso8601、$remote_addr 等&#xff09;&a…...

C++:std::is_convertible

C++标志库中提供is_convertible,可以测试一种类型是否可以转换为另一只类型: template <class From, class To> struct is_convertible; 使用举例: #include <iostream> #include <string>using namespace std;struct A { }; struct B : A { };int main…...

【Oracle APEX开发小技巧12】

有如下需求&#xff1a; 有一个问题反馈页面&#xff0c;要实现在apex页面展示能直观看到反馈时间超过7天未处理的数据&#xff0c;方便管理员及时处理反馈。 我的方法&#xff1a;直接将逻辑写在SQL中&#xff0c;这样可以直接在页面展示 完整代码&#xff1a; SELECTSF.FE…...

基于当前项目通过npm包形式暴露公共组件

1.package.sjon文件配置 其中xh-flowable就是暴露出去的npm包名 2.创建tpyes文件夹&#xff0c;并新增内容 3.创建package文件夹...

Linux云原生安全:零信任架构与机密计算

Linux云原生安全&#xff1a;零信任架构与机密计算 构建坚不可摧的云原生防御体系 引言&#xff1a;云原生安全的范式革命 随着云原生技术的普及&#xff0c;安全边界正在从传统的网络边界向工作负载内部转移。Gartner预测&#xff0c;到2025年&#xff0c;零信任架构将成为超…...

鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个生活电费的缴纳和查询小程序

一、项目初始化与配置 1. 创建项目 ohpm init harmony/utility-payment-app 2. 配置权限 // module.json5 {"requestPermissions": [{"name": "ohos.permission.INTERNET"},{"name": "ohos.permission.GET_NETWORK_INFO"…...

在Ubuntu24上采用Wine打开SourceInsight

1. 安装wine sudo apt install wine 2. 安装32位库支持,SourceInsight是32位程序 sudo dpkg --add-architecture i386 sudo apt update sudo apt install wine32:i386 3. 验证安装 wine --version 4. 安装必要的字体和库(解决显示问题) sudo apt install fonts-wqy…...

推荐 github 项目:GeminiImageApp(图片生成方向,可以做一定的素材)

推荐 github 项目:GeminiImageApp(图片生成方向&#xff0c;可以做一定的素材) 这个项目能干嘛? 使用 gemini 2.0 的 api 和 google 其他的 api 来做衍生处理 简化和优化了文生图和图生图的行为(我的最主要) 并且有一些目标检测和切割(我用不到) 视频和 imagefx 因为没 a…...