LC打怪录 选择排序 215.Kth Largest Element in an Array

题目链接:力扣

选择排序知识

  1. 设第一个元素为比较元素,依次和后面的元素比较,比较完所有元素并找到最小元素,记录最小元素下标,和第0个下表元素进行交换。
  2. 在未排序区域中,重复上述操作,以此类推找出剩余最小元素将它换到前面,即完成排序。

解析

现在让我们思考一下,冒泡排序和选择排序有什么异同?

相同点:都是两层循环,时间复杂度都为 O(n 2 ); 都只使用有限个变量,空间复杂度 O(1)。
不同点:冒泡排序在比较过程中就不断交换;而选择排序增加了一个变量保存最小值 / 最大值的下标,遍历完成后才交换,减少了交换次数。
事实上,冒泡排序和选择排序还有一个非常重要的不同点,那就是:冒泡排序法是稳定的,选择排序法是不稳定的。

排序算法的稳定性
假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i] = r[j],且 r[i] 在 r[j] 之前,而在排序后的序列中,r[i] 仍在 r[j] 之前,则称这种排序算法是稳定的;否则称为不稳定的。

理解了稳定性的定义后,我们就能分析出:冒泡排序中,只有左边的数字大于右边的数字时才会发生交换,相等的数字之间不会发生交换,所以它是稳定的。

选择排序算法如何实现稳定排序呢

实现的方式有很多种,这里给出一种最简单的思路:新开一个数组,将每轮找出的最小值依次添加到新数组中,选择排序算法就变成稳定的了。

二元选择排序

选择排序算法也是可以优化的,既然每轮遍历时找出了最小值,何不把最大值也顺便找出来呢?这就是二元选择排序的思想。

我们使用 minIndex 记录最小值的下标,maxIndex 记录最大值的下标。每次遍历后,将最小值交换到首位,最大值交换到末尾,就完成了排序。

由于每一轮遍历可以排好两个数字,所以最外层的遍历只需遍历一半即可。

二元选择排序中有一句很重要的代码,它位于交换最小值和交换最大值的代码中间:


def selectionSort(arr):for i in range(len(arr) - 1):minIndex = i  # 记录最小元素的索引# 找出最小元素for j in range(i + 1, len(arr)):  if arr[j] < arr[minIndex]:minIndex = j# i不是最小元素时,将i和最小元素进行交换if i != minIndex:arr[i], arr[minIndex] = arr[minIndex], arr[i]return arr
if __name__=="__main__":nums = [1, 42, 65, 876, 34, 656, 4, 6757, 89, 24, 65, 42]print("start:", nums)

 方法1: bf排序

class Solution:def findKthLargest(self, nums: List[int], k: int) -> int:random.shuffle(nums)#将数组从大到小排序nums.sort(reverse=True)return nums[k-1]

执行用时:208 ms

时间复杂:O(nlogn)

方法2: 快速选择 quick select

快排的改进,快排是一种分治思想的实现,没做一层快排可以将数组分成两份并确定一个数的位置。分析题目可以知道,要找到第 k 个最大的元素,找到这个元素被划分在哪边就可以了。

快速排序(英语:Quicksort),又称分区交换排序(partition-exchange sort),简称快排,一种排序算法,最早由东尼·霍尔(Tony Hoare )提出。在平均状况下,排序 n 个项目要  O(nlogn) 次比较。在最坏状况下则需要 O(n 2 ) 次比较,但这种状况并不常见。事实上,快速排序Θ(nlogn) 通常明显比其他演算法更快,因为它的内部循环(inner loop)可以在大部分的架构上很有效率地达成。

快速排序使用分治法(Divide and conquer)策略来把一个序列(list)分为较小和较大的 2 个子序列,然后递归地排序两个子序列。

以「升序排列」为例,其基本步骤为 [摘自@维基百科]:

1. 挑选基准值:从数列中挑出一个元素,称为“基准”(pivot);
2.  分割(partition):重新排序数列,所有比基准值小的元素摆放在基准前面,所有比基准值大的元素摆在基准后面(与基准值相等的数可以到任何一边)。在这个分割结束之后,对基准值的排序就已经完成;
3. 递归排序子序列:递归地将小于基准值元素的子序列和大于基准值元素的子序列排序。

递归到最底部的判断条件是数列的大小是零或一,此时该数列显然已经有序.

class Solution:def findKthLargest(self, nums: List[int], k: int) -> int:def partition(arr: List[int], low: int, high: int) -> int:pivot = arr[low]                                        # 选取最左边为pivotleft, right = low, high     # 双指针while left < right:while left<right and arr[right] >= pivot:          # 找到右边第一个<pivot的元素right -= 1arr[left] = arr[right]                             # 并将其移动到left处while left<right and arr[left] <= pivot:           # 找到左边第一个>pivot的元素left += 1arr[right] = arr[left]                             # 并将其移动到right处arr[left] = pivot           # pivot放置到中间left=right处return leftdef randomPartition(arr: List[int], low: int, high: int) -> int:pivot_idx = random.randint(low, high)                   # 随机选择pivotarr[low], arr[pivot_idx] = arr[pivot_idx], arr[low]     # pivot放置到最左边return partition(arr, low, high)                        # 调用partition函数def topKSplit(arr: List[int], low: int, high: int, k: int) -> int:# mid = partition(arr, low, high)                   # 以mid为分割点【非随机选择pivot】mid = randomPartition(arr, low, high)               # 以mid为分割点【随机选择pivot】if mid == k-1:                                      # 第k小元素的下标为k-1return arr[mid]                                 #【找到即返回】elif mid < k-1:return topKSplit(arr, mid+1, high, k)           # 递归对mid右侧元素进行排序else:return topKSplit(arr, low, mid-1, k)            # 递归对mid左侧元素进行排序n = len(nums)return topKSplit(nums, 0, n-1, n-k+1)                   # 第k大元素即为第n-k+1小元素

这个代码实现了快速选择算法的一个变种,用来找出数组中第 k 大的元素。这个实现采用了“快速排序”的分区思想,并通过随机选择轴点(pivot)来提高算法的效率和避免最坏情况的发生。以下是代码的逐步解析:

partition 函数

  • 这个函数接受一个数组 arr 和两个指针 lowhigh 作为参数,用来确定数组的操作区间。
  • 它首先选择 low 索引处的元素作为轴点(pivot)。
  • 使用两个指针 leftright 从数组的两端开始,向中间移动,并根据元素与轴点的比较结果进行交换,直到两个指针相遇。
  • 最终,轴点元素被放置在其最终位置上,该位置左边的所有元素都不大于轴点,右边的所有元素都不小于轴点。
  • 函数返回轴点的最终位置

这个代码实现了快速选择算法的一个变种,用来找出数组中第 k 大的元素。这个实现采用了“快速排序”的分区思想,并通过随机选择轴点(pivot)来提高算法的效率和避免最坏情况的发生。以下是代码的逐步解析:

randomPartition 函数

  • 为了避免在特定的数组顺序下陷入最坏情况(如已排序的数组),该函数首先在 lowhigh 范围内随机选择一个轴点索引 pivot_idx
  • 然后,它将选定的轴点与区间的第一个元素交换,确保随机选择的轴点被移到了区间的开头。
  • 最后,调用 partition 函数执行实际的分区操作。

topKSplit 函数

  • 这个函数是快速选择算法的核心,它递归地在数组的一个子区间内查找第 k 小(或第 k 大)的元素。
  • 它首先调用 randomPartition 对当前考虑的数组区间进行分区,然后根据分区后轴点的位置与 k 的关系决定下一步的操作。
  • 如果轴点恰好是第 k-1 个元素(因为数组索引从0开始),那么就找到了第 k 小的元素,直接返回。
  • 如果轴点的位置小于 k-1,说明第 k 小的元素位于轴点右侧的区间内,因此对右侧区间递归调用 topKSplit
  • 如果轴点的位置大于 k-1,说明第 k 小的元素位于轴点左侧的区间内,因此对左侧区间递归调用 topKSplit

主函数 findKthLargest

  • 最后,findKthLargest 函数通过调用 topKSplit 并传入整个数组、起始索引 0、结束索引 n-1n-k+1(因为第 k 大元素是第 n-k+1 小元素)来找到第 k 大的元素。

3 partiton

class Solution:def findKthLargest(self, nums: List[int], k: int) -> int:def quick_select(nums, k):pivot = random.choice(nums)big, equal, small = [], [], []# 将大于、小于、等于 pivot 的元素划分至 big, small, equal 中for num in nums:if num > pivot:big.append(num)elif num < pivot:small.append(num)else:equal.append(num)if k <= len(big):# 第 k 大元素在 big 中,递归划分return quick_select(big, k)if len(nums) - len(small) < k:# 第 k 大元素在 small 中,递归划分return quick_select(small, k - len(nums) + len(small))# 第 k 大元素在 equal 中,直接返回 pivotreturn pivotreturn quick_select(nums, k)
  1. 快速选择函数 quick_select

这是一个内部定义的辅助函数,用于实现快速选择算法。它接受当前考虑的数组 nums 和目标 k 作为参数。

2. 选择轴点

pivot = random.choice(nums)nums 中随机选择一个元素作为轴点(Pivot)。这种随机化策略有助于提高算法的平均性能,避免在特定情况下的性能退化。

3. 分区

算法遍历数组 nums,根据元素与轴点的大小关系,将其分配到三个列表中:big(存储所有大于轴点的元素)、equal(存储所有等于轴点的元素)、small(存储所有小于轴点的元素)。

4. 递归选择

  • 如果 k 小于等于 big 列表的长度,说明第 k 大的元素在 big 中,因此递归地在 big 中寻找第 k 大的元素。
  • n-1-k+1
  • 如果 k 大于 nums 减去 small 列表长度的结果(即 k 在减去所有小于轴点的元素后仍大于 bigequal 的总长度),说明第 k 大的元素在 small 中。此时,需要在 small 中寻找新的第 k - (len(nums) - len(small)) 大的元素,因为我们已经排除了一部分更大的元素。
  • 如果上述两种情况都不满足,说明第 k 大的元素在 equal 中,由于 equal 中的所有元素都等于轴点值 pivot,因此直接返回 pivot

5. 返回结果

  • 最终,通过调用 quick_select(nums, k) 执行快速选择逻辑,并返回找到的第 k 大的元素。

时间复杂度:快速选择算法的平均时间复杂度为 O(n),但在最坏情况下可能会达到 O(n2)。

通过随机选择轴点,快速选择算法能够在大多数情况下避免最坏情况的发生,从而保持较高的效率。

精讲:. - 力扣(LeetCode)

. - 力扣(LeetCode)

空间复杂度 O(logN) : 划分函数的平均递归深度为O(logN) 

方法3: 堆

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

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

相关文章

力扣每日一题 用队列实现栈 模拟

Problem: 225. 用队列实现栈 文章目录 思路复杂度Code 思路 &#x1f468;‍&#x1f3eb; 力扣官解 辅助队列存栈顶元素主队列存逆序序列 复杂度 时间复杂度: 添加时间复杂度, 示例&#xff1a; O ( n ) O(n) O(n) 空间复杂度: 添加空间复杂度, 示例&#xff1a; O ( …

js监听网页iframe里面元素变化其实就是监听iframe变化

想要监听网页里面iframe标签内容变化&#xff0c;需要通过监听网页dom元素变化&#xff0c;然后通过查询得到iframe标签&#xff0c;再通过iframe.contentWindow.document得到ifram内的document&#xff0c;然后再使用选择器得到body元素&#xff0c;有了body元素&#xff0c;就…

2024年华为OD机试真题-贪吃的猴子-Python-OD统一考试(C卷)

题目描述: 一只贪吃的猴子,来到一个果园,发现许多串香蕉排成一行,每串香蕉上有若干根香蕉。每串香蕉的根数由数组numbers给出。猴子获取香蕉,每次都只能从行的开头或者末尾获取,并且只能获取N次,求猴子最多能获取多少根香蕉。 输入描述: 第一行为数组numbers的长度 第二…

Java和JavaScript之间的主要区别与联系

目录 概况 主要区别 联系 总结 概况 Java和JavaScript&#xff0c;尽管名字相似&#xff0c;但它们在编程世界中却扮演着截然不同的角色。Java&#xff0c;一种强类型、面向对象的编程语言&#xff0c;广泛应用于企业级应用和安卓应用开发。它的设计理念是一次编写&#x…

使用协程库httpx并发请求

httpx和aiohttp都是比较常用的异步请求库&#xff0c;当然requests多线程或requestsgevent也是不错的选择。 一个使用httpx进行并发请求的脚本如下&#xff1a; import functools import sys import timeimport anyio import httpxasync def fetch(client, results, index) -…

详解 JavaScript 中的数组

详解 JavaScript 中的数组 创建数组 注&#xff1a;在JS中的数组不要求元素的类型&#xff0c;元素类型可以一样&#xff0c;也可以不一样 1.使用 new 关键字创建 let array new Array()2.使用字面量方式创建(常用) let array1 [1,2,3,"4"]获取数组元素 使用下…

西安-腾讯云-Python面试经验--一面凉经

自我介绍手撕链表排序操作系统 a. 线程和进程区别 b. 线程安全 c. 如何保证线程安全 d. 线程崩溃&#xff0c;会不会影响所在的进程 e. 什么是守护进程&#xff0c;僵尸进程&#xff0c;孤儿进程 f. 如何产生一个守护进程 g. 如何避免僵尸进程或者孤儿进程redis a. 持久化方式有…

【STK】手把手教你利用STK进行仿真-STK软件简介05 STK部分第三方分析模块介绍

1.导弹建模工具MMT 导弹建模工具MMT(Missile Modeling Tools)是STK在导弹分析领域的扩展分析应用,它是由四个独立的应用程序组成的相互支持与关联的系统,由第三方研究机构开发,能够与STK基本航天分析环境进行联合仿真分析。MMT主要用于导弹总体设计(包括弹道导弹、巡航导弹…

python进阶:可迭代对象和迭代器

一、Iterable&#xff08;可迭代对象&#xff09; 1、可迭代对象&#xff1a;能够进行迭代操作的对象。 可以理解为&#xff1a;能够使用for循环遍历的都是可迭代对象&#xff1b;**所有的可迭代对象&#xff0c;偶可以用内置函数iter转换为迭代器** 2、可迭代对象包括&…

蓝桥杯题练习:平地起高楼

题目要求 function convertToTree(regions, rootId "0") {// TODO: 在这里写入具体的实现逻辑// 将平铺的结构转化为树状结构&#xff0c;并将 rootId 下的所有子节点数组返回// 如果不存在 rootId 下的子节点&#xff0c;则返回一个空数组}module.exports convert…

网络防御保护——课堂笔记

一.内容安全 攻击可能只是一个点&#xff0c;防御需要全方面进行 IAE引擎 DFI和DPI技术 --- 深度检测技术 DPI ---深度包检测技术 ---主要针对完整的数据包&#xff08;数据包分片&#xff0c;分段需要重组&#xff09;&#xff0c;之后对数据包的内容进行识别。&#xff08;应…

ifcplusplus 示例 函数中英文 对照分析以及流程图

有需求&#xff0c;需要分析 ifc c渲染&#xff0c;分析完&#xff0c;有 230个函数&#xff0c;才能完成一个加载&#xff0c;3d加载真的是大工程&#xff01; 示例代码流程图 函数中英文对照表&#xff0c;方便 日后开发&#xff0c;整理思路顺畅&#xff01;&#xff01;&am…

C++三级专项 digit函数

在程序中定义一函数dight(n,k),他能分离出整数n从右边数第k个数字。 输入 正整数n和k。 输出 一个数字。 输入样例 31859 3 输出样例 8解析&#xff1a;递归&#xff0c;详情看code. 不准直接抄&#xff01;&#xff01;&#xff01; #include <iostream> usin…

包装类和综合练习

包装类 基本数据类型对应的应用类型。 jdk5以后对包装类新增了&#xff1a;自动拆箱、自动装箱 我们以后如何获取包装类对象&#xff1a; 不需要new,不需要调用方法&#xff0c;直接赋值即可 package MyApi.a09jdkdemo;public class A_01IntergerDemo1 {public static voi…

C语言——指针的进阶——第1篇——(第26篇)

坚持就是胜利 文章目录 一、字符指针1、面试题 二、指针数组三、数组指针1、数组指针的定义2、&数组名 VS 数组名3、数组指针的使用&#xff08;1&#xff09;二维数组传参&#xff0c;形参是 二维数组 的形式&#xff08;2&#xff09;二维数组传参&#xff0c;形参是 指针…

【RT-Thread应用笔记】英飞凌PSoC 62 + CYW43012 WiFi延迟和带宽测试

文章目录 一、安装SDK二、创建项目三、编译下载3.1 编译代码3.2 下载程序 四、WiFi测试4.1 扫描测试4.2 连接测试 五、延迟测试5.1 ping百度5.2 ping路由器 六、带宽测试6.1 添加netutils软件包6.2 iperf命令参数6.3 PC端的iperf6.4 iperf测试准备工作6.5 进行iperf带宽测试6.6…

未来三年AI的深度发展:AIGC、视频AI与虚拟世界构建

人工智能&#xff08;AI&#xff09;正站在科技演进的前沿&#xff0c;未来三年将见证其在多领域实现更深层次的突破。以下是对AI发展方向的深度探讨以及其对各行业的深远影响&#xff1a; 1. AIGC的演进与全面提升&#xff1a; AIGC&#xff0c;即AI通用性能力&#xff0c;将…

AI前沿-YOLOV9算法

AI前沿-YOLOV9算法 关注B站查看更多手把手教学&#xff1a; 肆十二-的个人空间-肆十二-个人主页-哔哩哔哩视频 (bilibili.com) 今天我们来一起说下最近刚出的YOLOV9算法 论文和源码 该算法的原始论文地址为&#xff1a;https://arxiv.org/abs/2402.13616 该算法的原始代码地…

Muduo库编译学习(1)

1.muduo库简介 muduo是由Google大佬陈硕开发&#xff0c;是一个基于非阻塞IO和事件驱动的现代C网络库&#xff0c;原生支持one loop per thread这种IO模型&#xff0c;该库只支持Linux系统&#xff0c;网上大佬对其褒贬不一&#xff0c;作为小白用来学习就无可厚非了。 git仓库…

b站小土堆pytorch学习记录——P14 torchvision中的数据集使用

文章目录 一、前置知识如何查看torchvision的数据集 二、代码&#xff08;附注释&#xff09;及运行结果 一、前置知识 如何查看torchvision的数据集 &#xff08;1&#xff09;打开官网 https://pytorch.org/ pytorch官网 &#xff08;2&#xff09;打开torchvision 在Do…