想要精通算法和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;}}
}

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

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

相关文章

PHP筆記

​ 前言因緣際會下還是開始學習php了。經歷了風風雨雨終於在今年暑假要去加拿大留學了&#xff0c;php會是第二年的其中一門必修課程&#xff0c;加上最近前端也真的蠻心累&#xff0c;也許有一門精進的後端語言&#xff0c;日後轉職會有更寬廣的道路&#xff0c;對自己說加油&…

Qt 布局(QLayout 类QStackedWidget 类) 总结

一、QLayout类(基本布局) QLayout类是Qt框架中用于管理和排列QWidget控件的布局类。它提供了一种方便而灵活的方式来自动布局QWidget控件。QLayout类允许您以一种简单的方式指定如何安排控件&#xff0c;并能够自动处理控件的位置和大小&#xff0c;以使其适应更改的父窗口的大…

竞赛选题 深度学习OCR中文识别 - opencv python

文章目录 0 前言1 课题背景2 实现效果3 文本区域检测网络-CTPN4 文本识别网络-CRNN5 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; **基于深度学习OCR中文识别系统 ** 该项目较为新颖&#xff0c;适合作为竞赛课题方向&#xff0c;…

【LeetCode刷题(数据结构)】:另一颗树的子树

给你两棵二叉树 root 和 subRoot 检验 root 中是否包含和 subRoot 具有相同结构和节点值的子树。如果存在&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 二叉树 tree 的一棵子树包括 tree 的某个节点和这个节点的所有后代节点。tree 也可以看做它自身的一棵子…

数据库安全-H2 databaseElasticsearchCouchDBInfluxdb漏洞复现

目录 数据库安全-H2 database&Elasticsearch&CouchDB&Influxdb 复现influxdb-未授权访问-jwt 验证H2database-未授权访问-配置不当CouchDB-权限绕过配合 RCE-漏洞CouchDB 垂直权限绕过Couchdb 任意命令执行 RCE ElasticSearch-文件写入&RCE-漏洞Elasticsearch写…

CentOS 挂载新磁盘以及磁盘扩容操作教程

1.搭载新加磁盘 # 查看磁盘 fdisk -l #新盘&#xff08;/dev/sdb&#xff09;创建分区 #虚拟机 fdisk /dev/sdb #阿里云 fdisk /dev/vdb #创建/dev/sdb1为新的PV&#xff08;物理卷&#xff09; 【创建物理卷命令】 #虚拟机 pvcreate /dev/sdb1 #阿里云 pvcreate /dev/vdb1 查…

c 语言基础题目:L1-037 A除以B

真的是简单题哈 —— 给定两个绝对值不超过100的整数A和B&#xff0c;要求你按照“A/B商”的格式输出结果。 输入格式&#xff1a; 输入在第一行给出两个整数A和B&#xff08;−100≤A,B≤100&#xff09;&#xff0c;数字间以空格分隔。 输出格式&#xff1a; 在一行中输出结…

【C语言】通讯录的简单实现

通讯录的内容 contect.h #pragma once // 包含头文件 #include <stdio.h> #include <string.h> #include <assert.h> #include <stdlib.h>// 使用枚举常量定义功能 enum Function {quit, // 注意这是逗号&#xff0c;不是分号save,addition,delete,s…

27 mysql 组合索引 的存储以及使用

前言 这里来看一下 mysql 中索引的 增删改查 查询在前面的系列文章中都有使用到 这里 来看一下 增删改 的相关实现 索引记录 和 数据记录 的处理方式是一致的 这里来看一下 组合索引 的相关, 以及 特性 组合索引的存储以及使用 创建数据表如下, 除了主键之外, 创建了…

辅助驾驶功能开发-功能对标篇(3)-NOP领航辅助系统-蔚来

1.横向对标参数 厂商蔚来车型ES6/8ET7ET5ES7上市时间201920222022Q32022Q2方案7v5R+1DMS11v5R1L+1DMS11v5R1L+1DMS11v5R1L+1DMS摄像头前视摄像头1*(三目)2*(8M,长短焦距)2*(8M,长短焦距)2*(8M,长短焦距)侧视摄像头/4*(8M)4*(8M)4*(8M)后视摄像头/1*(8M)1*(8M)1*(8M)环视摄像头4…

Spring Boot 生成二维码

效果图 1.maven依赖 <dependency> <groupId>com.google.zxing</groupId> <artifactId>javase</artifactId>

js的入口函数

JavaScript的入口函数是指在HTML页面加载完毕后&#xff0c;JavaScript代码开始执行的函数。这个函数一般被称为onload函数&#xff0c;它的作用是在页面加载完成后执行一些初始化操作&#xff0c;或者对页面进行动态修改。 在HTML页面中&#xff0c;可以通过以下方式设置入口…

光电柴微电网日前调度报告

摘要 微电网是目前国内外应用较为广泛的一种绿色可再生能源&#xff0c;近几年我国微电网产业的发展十分迅速。然后&#xff0c;越来越多的微电网系统建立并网&#xff0c;微电网产生的电能受外界因素影响较大&#xff0c;具有一定的随机性和波动性&#xff0c;给并网后的电力系…

Sketch macOS 支持m1 m2 Sketch 2023最新中文版

SketchUp Pro 2023是一款功能强大的三维建模软件&#xff0c;适用于建筑设计师、室内设计师、工程师和其他创意专业人士。以下是SketchUp Pro 2023的一些主要特点和功能&#xff1a; 三维建模&#xff1a;SketchUp Pro 2023允许用户以直观的方式创建三维模型。通过简单的绘图工…

Swagger使用

Swagger 简介 号称世界上最流行的API框架&#xff1b;Restful API 文档在线生成工具 —> API文档与API定义同步更新直接运行&#xff0c;可以在线测试 API 接口&#xff1b;支持各种语言&#xff1b;&#xff08;Java&#xff0c;PHP…&#xff09; 官网 Spring Boot 集…

flask 发送ajax

前端 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Title</title> </head> <body> <script src"https://cdn.lyshark.com/javascript/jquery/3.5.1/jquery.min.js"…

实施 DevSecOps 最佳实践

DevSecOps 是一个框架&#xff0c;它将开发 (Dev)、IT 运营 (Ops) 和安全 (Sec) 流程的实践融合到一个简化的流程中。使用这种方法&#xff0c;DevSecOps 团队能够确保将安全性集成到软件开发生命周期中&#xff0c;确保以“安全第一”的心态构建、部署和维护软件。在本教程中&…

【QT】Ubuntu 搭建 QT 环境(图形化界面安装)

介于直接使用源码编译安装 QT 耗时较长&#xff0c;而且需要手动编写脚本进行编译&#xff0c;难度较大&#xff0c;这里选择直接以图形化界面的方式安装 QT 。 目录 1、下载 QT 安装包 2、安装 QT 3、添加环境变量 4、cmake 引入 QT 库 5、Failed to find “GL/gl.h“ in…

vue音频制作

Vue 音频制作指的是使用 Vue.js 框架开发音频制作相关的 Web 应用程序。Vue.js 是一种现代化的 JavaScript 框架&#xff0c;它可以帮助开发者更快速、更高效地构建交互式的 Web 应用程序。 音频制作在 Vue.js 中的实现可以通过使用一些开源音频库和插件来实现&#xff0c;如 …

机器学习-有监督算法-决策树和支持向量机

目录 决策树ID3C4.5CART 支持向量积 决策树 训练&#xff1a;构造树&#xff0c;测试&#xff1a;从模型从上往下走一遍。建树方法&#xff1a;ID3&#xff0c;C4.5&#xff0c;CART ID3 以信息论为基础&#xff0c;以信息增益为衡量标准熵越小&#xff0c;混乱程度越小&…