刷题 排序算法
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. 设备管理服…...
变量 varablie 声明- Rust 变量 let mut 声明与 C/C++ 变量声明对比分析
一、变量声明设计:let 与 mut 的哲学解析 Rust 采用 let 声明变量并通过 mut 显式标记可变性,这种设计体现了语言的核心哲学。以下是深度解析: 1.1 设计理念剖析 安全优先原则:默认不可变强制开发者明确声明意图 let x 5; …...
【网络】每天掌握一个Linux命令 - iftop
在Linux系统中,iftop是网络管理的得力助手,能实时监控网络流量、连接情况等,帮助排查网络异常。接下来从多方面详细介绍它。 目录 【网络】每天掌握一个Linux命令 - iftop工具概述安装方式核心功能基础用法进阶操作实战案例面试题场景生产场景…...

3.3.1_1 检错编码(奇偶校验码)
从这节课开始,我们会探讨数据链路层的差错控制功能,差错控制功能的主要目标是要发现并且解决一个帧内部的位错误,我们需要使用特殊的编码技术去发现帧内部的位错误,当我们发现位错误之后,通常来说有两种解决方案。第一…...

现代密码学 | 椭圆曲线密码学—附py代码
Elliptic Curve Cryptography 椭圆曲线密码学(ECC)是一种基于有限域上椭圆曲线数学特性的公钥加密技术。其核心原理涉及椭圆曲线的代数性质、离散对数问题以及有限域上的运算。 椭圆曲线密码学是多种数字签名算法的基础,例如椭圆曲线数字签…...
【python异步多线程】异步多线程爬虫代码示例
claude生成的python多线程、异步代码示例,模拟20个网页的爬取,每个网页假设要0.5-2秒完成。 代码 Python多线程爬虫教程 核心概念 多线程:允许程序同时执行多个任务,提高IO密集型任务(如网络请求)的效率…...
Spring AI与Spring Modulith核心技术解析
Spring AI核心架构解析 Spring AI(https://spring.io/projects/spring-ai)作为Spring生态中的AI集成框架,其核心设计理念是通过模块化架构降低AI应用的开发复杂度。与Python生态中的LangChain/LlamaIndex等工具类似,但特别为多语…...

RNN避坑指南:从数学推导到LSTM/GRU工业级部署实战流程
本文较长,建议点赞收藏,以免遗失。更多AI大模型应用开发学习视频及资料,尽在聚客AI学院。 本文全面剖析RNN核心原理,深入讲解梯度消失/爆炸问题,并通过LSTM/GRU结构实现解决方案,提供时间序列预测和文本生成…...
MySQL账号权限管理指南:安全创建账户与精细授权技巧
在MySQL数据库管理中,合理创建用户账号并分配精确权限是保障数据安全的核心环节。直接使用root账号进行所有操作不仅危险且难以审计操作行为。今天我们来全面解析MySQL账号创建与权限分配的专业方法。 一、为何需要创建独立账号? 最小权限原则…...

JVM虚拟机:内存结构、垃圾回收、性能优化
1、JVM虚拟机的简介 Java 虚拟机(Java Virtual Machine 简称:JVM)是运行所有 Java 程序的抽象计算机,是 Java 语言的运行环境,实现了 Java 程序的跨平台特性。JVM 屏蔽了与具体操作系统平台相关的信息,使得 Java 程序只需生成在 JVM 上运行的目标代码(字节码),就可以…...
IP如何挑?2025年海外专线IP如何购买?
你花了时间和预算买了IP,结果IP质量不佳,项目效率低下不说,还可能带来莫名的网络问题,是不是太闹心了?尤其是在面对海外专线IP时,到底怎么才能买到适合自己的呢?所以,挑IP绝对是个技…...