【算法训练-数组 三】数组中的第K个最大元素(TOPK问题|寻找第K大)

废话不多说,喊一句号子鼓励自己:程序员永不失业,程序员走向架构!本篇Blog的主题是【寻找第K大】,使用【数组】这个基本的数据结构来实现,这个高频题的站点是:CodeTop,筛选条件为:目标公司+最近一年+出现频率排序,由高到低的去牛客TOP101去找,只有两个地方都出现过才做这道题(CodeTop本身汇聚了LeetCode的来源),确保刷的题都是高频要面试考的题。
在这里插入图片描述

名曲目标题后,附上题目链接,后期可以依据解题思路反复快速练习,题目按照题干的基本数据结构分类,且每个分类的第一篇必定是对基础数据结构的介绍

数组中的第K个最大元素【MID】

一道中等难度使用快排可以解决的题

题干

在这里插入图片描述

输入:
[1,3,5,2,2],5,3

返回值:
2
输入:
[10,10,9,9,8,7,5,6,4,3,4,2],12,3

返回值:
9

说明:
去重后的第3大是8,但本题要求包含重复的元素,不用去重,所以输出9      

解题思路

使用快速排序的思路来解决,快速排序(Quick Sort)是一种基于分治思想的排序算法,它通过将数组分成较小和较大的两部分,并分别对这两部分进行排序,最终将整个数组排序。快速排序是一种高效的排序算法,通常在平均情况下具有较快的执行速度。

下面是快速排序的基本思想和步骤:

  1. [划分]选择基准元素(Pivot): 从数组中选择一个元素作为基准元素。

  2. [划分]划分(Partition): 将数组分成两部分,使得基准元素左边的元素都小于等于基准元素,右边的元素都大于基准元素。这一步骤通常称为“划分”。

  3. [解决]递归排序: 递归地对基准元素左边和右边的子数组进行排序。也就是说,对小于基准元素的子数组和大于基准元素的子数组分别执行快速排序。

  4. [合并] 合并: 由于子数组都是在原数组中进行排序,所以最终整个数组也就被排序了。

这些步骤使得较大问题被分解成较小的子问题,这些子问题又能通过递归地应用快速排序来解决。在最好情况下,每次划分都能将数组均匀分成两半,这使得算法的时间复杂度为O(n log n)。

然而,需要注意的是,快速排序的性能高度依赖于基准元素的选择。最坏情况下,如果每次划分都使数组分成极不平衡的两部分,算法的时间复杂度可能会退化到O(n^2)。为了应对这种情况,通常可以选择合适的基准元素,如随机选择或者采用三数取中等方法。

总之,快速排序是一种常用且高效的排序算法,尤其适用于大规模数据的排序。

代码实现

给出代码实现基本档案

基本数据结构数组
辅助数据结构
算法快速排序(分治算法)、二分查找
技巧双指针

import java.util.*;


public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     *
     * @param a int整型一维数组
     * @param n int整型
     * @param K int整型
     * @return int整型
     */
    public int findKth (int[] a, int n, int K) {
        return quikSort(a, 0, n - 1, K);
    }

    public int quikSort(int[] a, int start, int end, int K) {
        // 找到基准元素的位置,这个位置是第 pivot+1(因为数组下标从0开始) 大的元素
        int pivot = partion(a, start, end);
        if (K == pivot + 1 ) {
            // 正好命中
            return a[pivot]  ;
        } else if (K < pivot + 1) {
            // K小于基准位置,说明它是更大的数,在基准位置左边搜索
            return quikSort(a, start, pivot - 1, K);
        } else {
            // K大于基准位置,说明它是更小的数,在基准位置右边搜索,
            return quikSort(a, pivot + 1, end, K );
        }

    }

    // 获取一个基准元素,倒排,所以大于基准元素的数在左,小于基准元素的数在右
    private int partion(int[] a, int start, int end) {
        int pivotValue = a[start];
        int i = start, j = end;
        while (i != j) {
            // (a[j]只有大于基准元素才停止,因为大的放基准元素左边
            while (a[j] <= pivotValue && i < j) {
                // 一定要j先走,因为j先走ij就会在j的位置交汇,而基准元素的左边一定要比自己大,所以为了保证后续基准元素交换正确,一定要保证交汇位置元素值大于等于基准元素,所以要优先满足j的条件(即找到大于基准元素的值并停下),如果i先走,在靠近交互点时,i会越过大于基准元素的值与j交汇。
                j--;
            }

            // (a[i]只有小于基准元素才停止,因为大的放基准元素左边
            while (a[i] >= pivotValue && i < j) {
                i++;
            }

            if (i < j) {
                swap(a, i, j);
            }
        }

        // 全部交换完后,基准元素交换
        swap(a, start, j);
        return j;
    }

    // 辅助函数,交换数组元素
    private void swap(int[] a, int i, int j) {
        int temp = a[i];
        a[i] = a[j];
        a[j] = temp;
    }
}

复杂度分析

快速排序的时间复杂度和空间复杂度如下:

时间复杂度:

  • 平均情况: 在平均情况下,快速排序的时间复杂度为O(n log n),其中n是待排序数组的长度。这是因为每次划分都能将数组大致均匀地分成两部分,导致递归的深度大约为log n,而每次划分的过程需要O(n)的时间。

  • 最坏情况: 在最坏情况下,如果每次划分都导致一个极不平衡的分割(例如每次选取的基准元素都是当前子数组的最大或最小元素),那么快速排序的时间复杂度可能退化到O(n^2)。这是因为需要执行n次划分,每次划分都需要O(n)的时间。为了避免最坏情况,通常采用随机选择基准元素或者三数取中法来减少极端情况的发生。

  • 最好情况: 快速排序的最好情况时间复杂度为O(n log n),与平均情况相同。这种情况发生在每次划分都能将数组准确地分成相等的两部分时。

空间复杂度:

快速排序的空间复杂度主要取决于递归调用的深度和每次划分所使用的额外空间。

  • 递归调用的深度: 在递归调用中,每次只需要保存一个基准元素的索引和部分数组的边界信息。因此,递归调用的深度为O(log n)

  • 每次划分所使用的额外空间: 每次划分需要O(1)的额外空间来存储基准元素和进行交换。

综合考虑,快速排序的空间复杂度为O(log n)。这是因为虽然递归调用的深度为O(log n),但在每层递归中所需的额外空间是常数级别的。这使得快速排序在空间上比某些其他排序算法(如归并排序)更加节省。

拓展知识:分治算法

分治法是一种解决问题的算法设计范式,它将一个问题分解成多个相似的子问题,然后解决这些子问题,并将它们的解合并以得出原始问题的解。分治法的核心思想是将大问题分解成更小的、相似的子问题,通过解决子问题来解决原始问题。

分治法通常包含三个步骤:分解(Divide)、解决(Conquer)、合并(Combine)。

  1. 分解(Divide): 将原始问题划分为更小、相似的子问题。这一步骤通常是递归地进行的,即将问题逐步分解为更小规模的子问题。

  2. 解决(Conquer): 递归地解决子问题。当子问题足够小,可以直接求解时,就停止分解,转而解决这些子问题。

  3. 合并(Combine): 将子问题的解合并以得出原始问题的解。这是分治法的关键步骤,将各个子问题的解整合起来形成更大问题的解。

分治法通常用于解决一些可以被分解成相似子问题的问题,如排序、搜索、求解最短路径等。典型的分治算法包括归并排序快速排序。以下是一个分治法的示例:

归并排序:

  1. 分解(Divide): 将数组分成两半,分别对这两半进行排序。

  2. 解决(Conquer): 对分解得到的子数组递归地进行排序,直到子数组长度足够小。

  3. 合并(Combine): 将排好序的子数组合并,得到完整的有序数组。

分治法的优点在于它可以将问题分解成独立的子问题,每个子问题的求解都相对简单。这使得算法设计和理解变得更加清晰。然而,分治法有时会在子问题的合并阶段引入额外的开销,因此在设计分治算法时需要权衡分解和合并的成本。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

设计模式--代理模式(Proxy Pattern)

一、什么是代理模式&#xff08;Proxy Pattern&#xff09; 代理模式&#xff08;Proxy Pattern&#xff09;是一种结构型设计模式&#xff0c;它允许一个对象&#xff08;代理&#xff09;充当另一个对象&#xff08;真实对象&#xff09;的接口&#xff0c;以控制对该对象的…

HCIP-HCS华为私有云的使用

1、概述 华为公有云&#xff08;HC&#xff09;、华为私有云&#xff08;HCS&#xff09;华为混合云&#xff08;HCSO&#xff09;。6.3 之前叫FusionSphere OpenStack&#xff0c;6.3.1 版本开始叫FusionCloud&#xff0c;6.5.1 版本开始叫Huawei Cloud Stack (HCS)华为私有云…

前端调用电脑摄像头

项目中需要前端调用&#xff0c;所以做了如下操作 先看一下效果吧 主要是基于vue3&#xff0c;通过canvas把画面转成base64的形式&#xff0c;然后是把base64转成 file文件&#xff0c;最后调用了一下上传接口 以下是代码 进入页面先调用一下摄像头 navigator.mediaDevices.ge…

无涯教程-Android - List View函数

Android ListView 是垂直滚动列表中显示的视图&#xff0c;使用 Adapter 从列表(如数组或数据库)中获取内容的列表项会自动插入列表中。 适配器(Adapter)实际上是UI组件和将数据填充到UI组件中的数据源之间的桥梁&#xff0c;适配器保存数据并将数据发送到适配器视图&#xff0…

基于猎食者算法优化的BP神经网络(预测应用) - 附代码

基于猎食者算法优化的BP神经网络&#xff08;预测应用&#xff09; - 附代码 文章目录 基于猎食者算法优化的BP神经网络&#xff08;预测应用&#xff09; - 附代码1.数据介绍2.猎食者优化BP神经网络2.1 BP神经网络参数设置2.2 猎食者算法应用 4.测试结果&#xff1a;5.Matlab代…

认识SQL sever

目录 一、数据库的概念 1.1数据库的基本概念 1.2对数据库的了解 二、数据库的分类 2.1关系型数据库&#xff08;RDBMS&#xff09;&#xff1a; 2.2非关系型数据库&#xff08;NoSQL&#xff09;&#xff1a; 2.3混合数据库&#xff1a; 2.4数据仓库&#xff1a; 2.5嵌…

静态路由(详细理解+实例精讲)

系列文章目录 华为数通学习&#xff08;6&#xff09; 前言 一&#xff0c;静态路由 二&#xff0c;静态路由配置 三&#xff0c;缺省路由 四&#xff0c;缺省路由应用场景 总结 前言 随着华为公司的不断发展&#xff0c;数据通信这门技术也越来越重要&#xff0c;很多人…

PyQt6 GUI界面设计和Nuitka包生成exe程序(全笔记)

PyQt6 GUI界面设计和Nuitka包,生成exe程序全笔记 目录一、PyQt6包安装1.1 进行环境配置和安装1.2 检查包是否安装成功。1.3 运行desinger.exe二、GUI界面设计,写程序,并能运行成功。三、Nuitka打包生成exe程序3.1 做Nuitka安装准备工作(1)安装C编译器,设置环境变量3.2 安…

自动化运维工具—Ansible

一、Ansible概述1.1 Ansible是什么1.2 Ansible的特性1.3 Ansible的特点1.4 Ansible数据流向 二、Ansible 环境安装部署三、Ansible 命令行模块&#xff08;1&#xff09;command 模块&#xff08;2&#xff09;shell 模块&#xff08;3&#xff09;cron 模块&#xff08;4&…

【Java 动态数据统计图】前后端对接数据格式(Map返回数组格式数据)六(120)

说明&#xff1a; 前端使用&#xff1a;vue3.0 ECharts可视化库 前后端对接数据格式&#xff1a;无非就是前端把后端返回的数据处理为自己想要的格式&#xff0c;或者&#xff0c;后端给前端处理好想要的格式&#xff1b; 针对前后端的柱状图&#xff0c;趋势图等数据对接&…

亚马逊宣布弃用低代码,Honeycode 服务即将停止。

AWS 宣布终止低代码服务 Honeycode。新客户不能注册或升级账户计划&#xff0c;现有客户的应用程序将在 2024 年 2 月 29 日前继续运行。在 2023 年 7 月 31 日之后&#xff0c;用户将不再需要支付 Honeycode 使用费。 Honeycode 是一项于2020年6月推出的完全托管服务&#xf…

【【萌新的STM32学习23----数据通信的基本类型】】

萌新的STM32学习23----数据通信的基本类型 数据通信的基本概念 数据通信方式可以分为串行通信&#xff0c;并行通信 串行通信&#xff1a; 数据逐位按顺序依次传输 并行&#xff1a; 数据各位通过多条线同时传输 串行通信&#xff1a; 传输效率低&#xff0c;抗干扰能力强&am…

智慧景区方案:AI与视频融合技术如何助力景区监管智能化升级?

随着经济的发展&#xff0c;人们对生活的需求也不再局限于温饱层面&#xff0c;越来越多的人们开始追求文化、艺术的高层次需求&#xff0c;旅游也逐渐成为人们日常放松的一种方式。由于我国人口多、易扎堆等特点&#xff0c;景区的运营监管方式也亟需改革。TSINGSEE青犀智能分…

微服务·架构组件之注册与发现

引言 微服务架构在现代软件开发中越来越受欢迎&#xff0c;它通过将系统拆分为多个小型、自治的服务来提高可维护性、可扩展性和灵活性。然而随着服务数量的增多&#xff0c;服务之间的通信何发现变得更加复杂。本报告旨在深入探讨微服务中的注册与发现&#xff0c;介绍其背景…

NRF52832一主多从ble_app_multilink_central

下载官方SDK后打开路径&#xff1a;nRF5SDK153059ac345\nRF5_SDK_15.3.0_59ac345\examples\ble_central\ble_app_multilink_central\pca10040\s132\arm5_no_packs 下的工程文件&#xff0c;确定把log开启 编译后下载完程序(要下载协议栈&#xff0c;这里用6.1.1的)&#xff0c…

对于论文Semi-Supervised Classification with Graph Convolutional Networks,小白的学习理解

参考笔记&#xff1a;论文笔记&#xff1a;Semi-Supervised Classification with Graph Convolutional Networks_hongbin_xu的博客-CSDN博客 论文笔记&#xff1a;SEMI-SUPERVISED CLASSIFICATION WITH GRAPH CONVOLUTIONAL NETWORKS_semi supervised classification_饮冰l的博…

Linux CentOS安装抓包解包工具Wireshark图形化界面

1.Wireshark介绍 Wireshark 是一个开源的网络协议分析工具&#xff0c;它能够捕获和分析网络数据包&#xff0c;提供深入的网络故障排除、网络性能优化和安全审计等功能。它支持跨多个操作系统&#xff0c;包括 Windows、macOS 和 Linux。 2.Wireshark主要使用方法 捕获数据…

K8S容器OOM killed排查

背景 数据服务平台南海容器k8s设置的内存上限2GB&#xff0c;多次容器被OOM killed。 启动命令 java -XX:MaxRAMPercentage70.0 -XX:HeapDumpOnOutOfMemoryError -XX:HeapDumpPath/apps/logs/ ***.jar排查过程 1 当收到实例内存超过95%告警时&#xff0c;把jvm进程堆dump下…

htmx-使HTML更强大

‍本文作者是360奇舞团开发工程师 htmx 让我们先来看一段俳句: javascript fatigue: longing for a hypertext already in hand 这个俳句很有意思&#xff0c;是开源项目htmx文档中写的&#xff0c;意思是说&#xff0c;我们已经有了超文本&#xff0c;为什么还要去使用javascr…

三、原型模式

一、什么是原型模式 原型&#xff08;Prototype&#xff09;模式的定义如下&#xff1a;用一个已经创建的实例作为原型&#xff0c;通过复制该原型对象来创建一个和原型相同或相似的新对象。在这里&#xff0c;原型实例指定了要创建的对象的种类。用这种方式创建对象非常高效&a…