刷题 排序算法
912. 排序数组
注意这道题目所有
O(n^2)
复杂度的算法都会超过时间限制,只有O(nlogn)
的可以通过
- 快速排序空间复杂度为
O(logn)
是由于递归的栈的调用
- 归并排序空间复杂度为
O(n)
是由于需要一个临时数组
(当然也需要栈的调用,但是O(logn)
<O(n)
的忽略了)
基于插入的排序算法
直接插入排序
类似于打扑克牌的操作 直接插入排序(算法过程, 效率分析, 稳定性分析)
- 时间复杂度:最好情况
O(n)
, 最坏情况O(n^2)
- 空间复杂度:
O(1)
- 稳定性:是稳定的
class Solution {
public:vector<int> sortArray(vector<int>& nums) {// 插入排序for (int i = 1; i < nums.size(); ++i) {int cur_val = nums[i];int j = i - 1;while (j >= 0 && nums[j] > cur_val) { // 寻找插入位置nums[j + 1] = nums[j];--j;}nums[j + 1] = cur_val;}return nums;}
};
折半插入排序
直接插入排序是使用
顺序查找的方法,从后往前寻找插入的位置
同理我们也可以使用二分查找
的方式来寻找插入的位置
折半查找减少了比较的次数
,将比较操作的时间复杂度降低为O(logn)
,但没有减少移动的次数
,整体时间复杂度还是O(n^2)
- 时间复杂度:最好情况
O(n)
, 最坏情况O(n^2)
- 空间复杂度:
O(1)
- 稳定性:是稳定的
class Solution {
public:int binarySearch(vector<int> nums, int right, int target) {// 找到第一个大于 target 的值int left = 0;// 使用左闭右闭区间while (left <= right) { // 区间不为空int mid = left + (right - left) / 2;// 循环不变量// nums[left - 1] <= target// nums[right + 1] > targetif (nums[mid] <= target) {left = mid + 1;} else if (nums[mid] > target) {right = mid - 1;}}return left;}vector<int> sortArray(vector<int>& nums) {// 折半插入排序for (int i = 1; i < nums.size(); ++i) {int cur_val = nums[i];int insert_pos = binarySearch(nums, i - 1, cur_val);for (int j = i - 1; j >= insert_pos; --j) {nums[j + 1] = nums[j]; }nums[insert_pos] = cur_val;}return nums;}
};
希尔排序 - 插入排序的改进 - 缩小增量排序
插入排序在序列基本有序
时效率较高
基于这个特点,希尔排序就是对数组分组进行插入排序,分组的组数就是 d
,也即增量,一种简单的增量序列就是从 num.size() / 2
开始,一直缩小到 1
,当然也可以采用其他的增量序列
- 时间复杂度:最好情况
O(n)
, 最坏情况O(n^2)
,平均复杂度O(n^1.3)
(了解即可) - 空间复杂度:
O(1)
- 稳定性:不稳定的
class Solution {
public:vector<int> sortArray(vector<int>& nums) {for (int d = nums.size() / 2; d >= 1; --d) {// 分组插入排序for (int k = 0; k < d; ++k) {// 组内进行插入排序for (int i = k + d; i < nums.size(); i += d) {int cur_val = nums[i];int j = i - d;while (j >= 0 && nums[j] > cur_val) {nums[j + d] = nums[j];j -= d;}nums[j + d] = cur_val;}}}return nums;}
};
基于交换的排序算法
冒泡排序
- 时间复杂度:最好情况
O(n)
, 最坏情况O(n^2)
- 空间复杂度:
O(1)
- 稳定性:稳定
class Solution {
public:vector<int> sortArray(vector<int>& nums) {// 冒泡排序for (int i = nums.size() - 1; i >= 1; --i) {bool swapped = false;for (int j = 0; j < i; ++j) {if (nums[j] > nums[j + 1]) {swap(nums[j], nums[j + 1]);swapped = true;}}if (!swapped) { // 没有发生交换,说明代码已经有序break;}}return nums;}
};
快速排序 图解 - 分治法
步骤:
- 随机选取一个位置 nums[i] = x
- 将大于 x 的值都移到 nums[i] 的左边,小于 x 的值都移动到 nums[i] 的右边
- 对 nums[0 ~i -1] 和 nums[i + 1 ~ n -1] 分别进行快速排序
- …
步骤中的核心问题:如何 将大于 x 的值都移到 nums[i] 的左边,小于 x 的值都移动到 nums[i] 的右边
?
- 时间复杂度:最好情况
O(n)
, 最坏情况O(n^2)
- 空间复杂度:
O(1)
- 稳定性:稳定
class Solution {
public:void quickSort(vector<int>& nums, int left, int right) {if (left >= right) return; // 递归终止条件int p = partition(nums, left, right);quickSort(nums, left, p - 1);quickSort(nums, p + 1, right);}int partition(vector<int>& nums, int left, int right) {int p = left + rand() % (right - left + 1); // 生成 [left ~ right] 区间内的随机数swap(nums[p], nums[right]); // 将 pivot 和末尾值交换int i = left;// 维护的区间: [left, i) 区间内的值小于等于 nums[right]// [j, right) 区间内的值大于 nums[right]for (int j = left; j < right; ++j) {if (nums[j] <= nums[right]) {// 此时不满足我们对区间的要求了// 调整区间使其满足要求// {nums[left] ... nums[i-1]} {[nums[i] ... nums[j]]}swap(nums[i], nums[j]);++i;// --> {nums[left] ... nums[i-1] nums[j]} { ... nums[i]]}}}swap(nums[i], nums[right]);return i;}vector<int> sortArray(vector<int>& nums) {srand(time(0)); // 以当前时间为随机数种子quickSort(nums, 0, nums.size() - 1);return nums;}
};
但是上面这段代码提交还是会超过时间限制,由于当前的快速排序在处理包含大量相同元素的数组时,表现不佳。快速排序在最坏情况下的时间复杂度是 O(n^2)
使用三向切分的快速排序
三向切分是对标准快速排序的一种改进,特别适用于处理大量重复元素的情况。它将数组分为三个部分:
- 小于基准的部分
- 等于基准的部分
- 大于基准的部分
通过三向切分,可以避免在处理大量重复元素时退化为 O(n²),使得时间复杂度保持在 O(n log n)。
class Solution {
public:void quickSort3Way(vector<int>& nums, int left, int right) {if (left >= right) return; // 递归终止条件int pivot = nums[left + rand() % (right - left + 1)]; // 选取随机基准int lt = left, i = left, gt = right; // 初始化 lt、i、gt 指针// [left ~ lt) 小于 pivot// [lt, gt] 等于 pivot// [gt + 1, right] 大于 pivot while (i <= gt) {if (nums[i] < pivot) {swap(nums[lt], nums[i]);++lt;++i;} else if (nums[i] > pivot) {swap(nums[i], nums[gt]);--gt; // 不能++i,因为换下来的这个数的值还没有跟 pivot 比较过} else {++i;}}// 递归处理小于和大于基准的部分quickSort3Way(nums, left, lt - 1);quickSort3Way(nums, gt + 1, right);}vector<int> sortArray(vector<int>& nums) {srand(time(0)); // 只需初始化一次随机数种子quickSort3Way(nums, 0, nums.size() - 1);return nums;}
};
选择排序
简单选择排序
- 时间复杂度:最好情况
O(n)
, 最坏情况O(n^2)
- 空间复杂度:
O(1)
- 稳定性:不稳定
不稳定性分析:
假设有一个数组 [4a, 2, 4b, 3],其中 4a 和 4b 是两个相同值的元素,但具有不同的初始顺序。
第一轮:选择 2 作为最小元素,然后与 4a 交换,数组变为 [
2
,4a
, 4b, 3]。第二轮:选择 3 作为最小元素,然后与 4a 交换,数组变为 [2,
3
, 4b,4a
]。 注意此时 4a 和 4b 的相对顺序已经被改变:原本 4a 在 4b 之前,现在 4a被排在了 4b 之后。因此,选择排序是不稳定的,因为它改变了相同值元素的初始顺序。
class Solution {
public:vector<int> sortArray(vector<int>& nums) {// 选择排序for (int i = 0; i < nums.size() - 1; ++i) {int min_idx = i;for (int j = i + 1; j < nums.size(); ++j) {if (nums[j] < nums[i]) {min_idx = j; // 最小值的索引}}swap(nums[i], nums[min_idx]); // 和最小值进行交换}return nums;}
};
堆排序 - 堆 - 完全二叉树 - 顺序存储
class Solution {
public:// 堆化函数:调整以 i 为根的子树,n 为堆的大小void heapify(vector<int>& nums, int n, int i) {int largest = i; // 初始化为根节点int left = 2 * i + 1; // 左孩子int right = 2 * i + 2; // 右孩子// 如果左孩子比根节点大if (left < n && nums[left] > nums[largest]) {largest = left;}// 如果右孩子比当前最大值还大if (right < n && nums[right] > nums[largest]) {largest = right;}// 如果最大值不是根节点,交换并继续堆化if (largest != i) {swap(nums[i], nums[largest]);// 递归对受影响的子树进行堆化heapify(nums, n, largest);}}vector<int> sortArray(vector<int>& nums) {int n = nums.size();// 从最后一个非叶子节点开始建堆,调整整个堆for (int i = n / 2 - 1; i >= 0; --i) {heapify(nums, n, i);}// 逐一将堆顶元素与末尾元素交换,并重新调整堆for (int i = n - 1; i > 0; --i) {// 将当前堆顶(最大值)与末尾元素交换swap(nums[0], nums[i]);// 重新对剩下的部分进行堆化heapify(nums, i, 0);}return nums;}
};
归并排序
可以将排序问题分解成 将左半边排序 + 将右半边排序 + 合并左右两侧
- 时间复杂度:
O(n log n)
- 空间复杂度:
O(n) (源于临时数组)
- 稳定性:稳定
class Solution {
public:void mergeArray(vector<int> &nums, vector<int>& tmp, int left, int right) {if (right == left) return; // 递归终止条件int mid = left + (right - left) / 2;mergeArray(nums, tmp, left, mid); // 对左半边进行排序mergeArray(nums, tmp, mid + 1, right); // 对右半边进行排序// 重要优化:如果左右两部分已经有序,可以跳过合并if (nums[mid] <= nums[mid + 1]) return;// 左右两侧均已完成排序,对二者进行合并int i = left, j = mid + 1, k = left;while (i <= mid && j <= right) {if (nums[i] <= nums[j]) {tmp[k++] = nums[i++]; } else {tmp[k++] = nums[j++];}}while (i <= mid) {tmp[k++] = nums[i++];}while (j <= right) {tmp[k++] = nums[j++];}copy(tmp.begin() + left, tmp.begin() + right + 1, nums.begin() + left);}vector<int> sortArray(vector<int>& nums) {vector<int> tmp(nums.size(), 0);mergeArray(nums, tmp, 0, nums.size() - 1);return nums;}
};
相关文章:

刷题 排序算法
912. 排序数组 注意这道题目所有 O(n^2) 复杂度的算法都会超过时间限制,只有 O(nlogn) 的可以通过 快速排序空间复杂度为 O(logn)是由于递归的栈的调用归并排序空间复杂度为 O(n) 是由于需要一个临时数组 (当然也需要栈的调用,但是 O(logn) < O(n) 的…...

【python3】tornado高性能编程
使用多进程充分利用cpu使用异步编程 asyncio import asyncio import time from abc import ABC from concurrent.futures import ProcessPoolExecutor from tornado import web, ioloop, genasync def async_task(name):print(f"start: {name}")st int(time.time()…...

构建高效购物推荐系统:SpringBoot实战
1系统概述 1.1 研究背景 如今互联网高速发展,网络遍布全球,通过互联网发布的消息能快而方便的传播到世界每个角落,并且互联网上能传播的信息也很广,比如文字、图片、声音、视频等。从而,这种种好处使得互联网成了信息传…...

docker tar包安装 docker-26.1.4.tgz
一、docker安装 1.先将docker安装包(docker-26.1.4.tgz)拷贝到DM系统中。 下载地址 Index of linux/static/stable/x86_64/ 1.先将docker安装包(docker-26.1.4.tgz)拷贝到DM系统中。 2.解压docker安装包 tar zxf docker-26.1.…...

Github 2024-10-12 Rust开源项目日报 Top10
根据Github Trendings的统计,今日(2024-10-12统计)共有10个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量Rust项目10JavaScript项目1Svelte项目1TypeScript项目1Rust: 构建可靠高效软件的开源项目 创建周期:5064 天开发语言:Rust协议类型:OtherSta…...

Spring Cloud 微服务架构及其应用:设计、实现与优化
引言 随着互联网技术的不断发展,传统的单体应用架构逐渐暴露出了一些问题,如扩展性差、维护复杂、部署不灵活等。为了解决这些问题,微服务架构应运而生。微服务是一种将应用程序分解为一组小的、自治的服务的架构模式,服务之间通过轻量级的通信协议(如HTTP)进行交互。Sp…...

Rider + xmake DX12 开发环境
Rider xmake DX12 开发环境 背景 如题,想要接近 UE 的开发流程 正文 大的流程就是 xmake 生成 vs 的 sln,用 Rider 进行开发 intellisense,断点调试 加了个脚本手动刷新 sln xmake project -k vsxmake -m "debug;release" -…...

控制台java原生工具打包jar文件
1、进入java源代码所在路径,或者包起始文件的所在路径 2、编译为class文件 我没配全局变量,这里使用jdk的完整路径来调用 3、jar命令进行打包 -cfe后面: svnHook.jar 指定jar包文件名 Request 包名入口类名,如果有包含包的话,应…...

MySQL主从同步
MySQL主从同步 作用 减少单台服务器的压力,防止单点故障 部署 主库 编辑/etc/mysql/mysql.conf.d/mysqld.cnf log_binmysql-bin server-id1 #服务器的id,再主从数据库里不能重复重启MySQL服务器 systemctl restart mysql连接mysql,并创建用于主从…...

ansible 学习之变量
参考文档: http://www.ansible.com.cn/docs/playbooks_variables.html#variables 合法的变量 ansible变量是有数字,字母,下划线组成并且变量始终应该以字母开头。 “foo_port”是个合法的变量名.”foo5”也是. “foo-port”, “foo port”, …...

【知识科普】Markdown语法内容看这一篇就够了
文章目录 1. 标题2. 段落3. 字体4. 分隔线5. 删除线6. 列表7. 区块引用8. 代码11. HTML元素12. 特殊字符13. 数学公式14. 其他高级技巧 Markdown是一种轻量级标记语言,其排版语法简洁,让人们能更多地关注内容本身而非排版。以下是对Markdown语法的详细解…...

什么是智能合约?
什么是智能合约? 智能合约,就是一段写在区块链上的代码,一旦某个事件触发合约中的条款,代码即自动执行。也就是说,满足条件就执行,不需要人为操控、不需要第三方信任。区块链的安全性和不可篡改性…...

Oracle低代码平台apex介绍
Oracle APEX(Application Express)是一个强大的低代码开发平台,它允许开发者快速构建企业级Web应用程序。该平台基于Oracle数据库,并充分利用了数据库的功能来提供安全、可扩展且易于维护的应用程序。 什么是Oracle APEX…...

【读书笔记·VLSI电路设计方法解密】问题12:制造MOSFET晶体管的主要工艺步骤是什么
VLSI芯片是在半导体材料上制造的,这种材料的导电性介于绝缘体和导体之间。通过一种称为掺杂的工艺引入杂质,可以改变半导体的电气特性。能够在半导体材料的细小且定义明确的区域内控制导电性,促使了半导体器件的发展。结合更简单的无源元件(电阻、电容和电感),这些器件被…...

内存分析工具的使用——AddressSanitizer
一、c/c中的内存问题 memory corruption,内存崩溃或者说内存损坏。在c/c程序中,有相当一部分的Bug是由内存引起的,也就是刚刚提到的内存崩溃。说得再通俗一些,往往和内存的非法访问有关。内存问题,轻则导致程序失能&a…...

linux使用nmcli 管理wifi的命令
在 Linux 系统中,nmcli 是 NetworkManager 的命令行工具,常用于管理网络连接,包括 WiFi。下面是一些常见的使用 nmcli 管理 WiFi 的命令。 1. 显示所有可用的 WiFi 网络 nmcli dev wifi list这个命令会列出当前可以扫描到的 WiFi 网络及其信…...
deepin20.9安装部署 |deepin20.9镜像下载 |基本命令 |手动分区 |开启远程ssh服务
下载deepin20.9 .iso 阿里云 https://mirrors.aliyun.com/deepin-cd/20.9/deepin-desktop-community-20.9-amd64.iso 注意安装过程略 小白参考 : Centos 7.9 安装 图解版 小白必看 最新_centos7.9-CSDN博客文章浏览阅读2.4k次,点赞34次,…...

使用PL/SQL Deverloper过程遇见的问题
目录 背景: ORA-01031权限问题: PL/SQL Deverloper显示Oravle中存在的所有表: PL/SQL Deverloper优点: 背景: PL/SQL Developer是由Allround Automations公司开发的一款集成开发环境(IDE),它专门面向Oracle数据库存储的程序单元的开发。随着越来越多…...

pikachu靶场总结(三)
五、RCE 1.RCE(remote command/code execute)概述 RCE漏洞,可以让攻击者直接向后台服务器远程注入操作系统命令或者代码,从而控制后台系统。 远程系统命令执行 一般出现这种漏洞,是因为应用系统从设计上需要给用户提供指定的远程命令操作的…...

onvif相关的http api有哪些功能点
ONVIF 提供了一系列 HTTP API,用于访问和控制支持 ONVIF 的设备。这些 API 基于 SOAP 协议,通过 HTTP 协议传输。主要的 API 分为几个关键的服务类别,每个类别都有特定的操作。以下是 ONVIF 相关的 HTTP API 概述: 1. 设备管理服…...

AI大模型是如何改变我们的日常生活的?
随着AI大模型的不断发展和优化,它已经在各个领域展现出了巨大的潜力和广泛的应用。无论是在科技创新、医疗健康、金融服务、教育培训还是日常生活中,AI大模型都有着重要的作用。它不仅可以帮助人们提高工作效率、提供个性化的服务,还能够改善…...

kubernetes部署Nexus(Helm3)
参考文献: https://help.sonatype.com/en/single-data-center-on-premises-deployment-example-using-kubernetes.htmlhttps://github.com/sonatype/helm3-chartshttps://support.sonatype.com/hc/en-us/articles/7706583820691-How-to-install-Nexus-Pro-instance…...

PDF无法导出中文
font/SIMSUN.TTC with Identity-H is not recognized. 查看BaseFont源码发现".ttc," 改为"SIMSUN.TTC,a"提示数字转换异常 改为"SIMSUN.TTC,11"提示数字索引必须介于0和1之间 改为0或1结果正常 BaseFont baseFont BaseFont.createFont("/U…...

【docker】mysql8.0 的 docker 安装
安装 指定mysql 的安装版本8.0.18 拉取镜像 docker pull mysql:8.0。18创建目录 mkdir -p /opt/docker_volumn/mysql/conf mkdir -p /opt/docker_volumn/mysql/log mkdir -p /opt/docker_volumn/mysql/data mkdir -p /opt/docker_volumn/mysql/mysql-files此步骤是为了将容…...

vue3中父组件与子组件关系的理解 ------类比java中的启动类,类,对象等概念来解释一下
编程时的一点感受: 感觉子组件本身像是java的一个类,父组件像是启动类,父组件里引用子组件像是创建子组件的对象 查找资料后,发现确实如此,在很多方面,Vue 组件确实可以与面向对象编程中的类进行类比。…...

Java设计模式——装饰模式
目录 模式动机 模式定义 模式结构 类图 代码分析 示例:动态添加功能的流 组件接口 具体组件 装饰抽象类 具体装饰类 客户端 模式分析 核心思想 动态扩展功能 组合优于继承 优点 动态扩展功能 组合优于继承 代码复用性高 符合开闭原则 缺点 增加…...

【TouchSocket 和 client.GetStream 区别】
TouchSocket 和 client.GetStream() 是用于网络通信的不同工具和方法,但它们的功能层面和适用范围也有明显区别。下面我来详细解释 TouchSocket 和 client.GetStream() 的差异。 1. TouchSocket TouchSocket 是一个完整的 网络通信框架,专注于为开发者…...

怎么利用商品详情API接口实现数据获取与应用?
在当今数字化的商业时代,高效获取和利用商品数据对于企业和开发者来说至关重要。商品详情 API 接口为我们提供了一种便捷的方式来获取丰富的商品信息,从而实现各种有价值的应用。本文将深入探讨如何利用商品详情 API 接口实现数据获取与应用。 一、商品…...
【AGC005D】~K Perm Counting(计数抽象成图)
容斥原理。 求出f(m) ,f(m)指代至少有m个位置不合法的方案数。 怎么求? 注意到位置为id,权值为v ,不合法的情况,当且仅当 v idk或 v id-k 因此,我们把每一个位置和权值抽象成点 ,不合法的情况之间连一…...

【React】setState (useState) 是怎么记住上一个状态值的?
在 React 中,setState 通过 React 内部的状态管理机制来记住上一个状态值。即使每次组件重新渲染时,函数组件会被重新执行,React 仍能通过其内部的状态管理系统保持和追踪组件的状态变化。下面详细解释其工作原理: 1. setState 的…...