当前位置: 首页 > news >正文

每日一算:颜色分类

题目描述

给定一个包含红色(用 0 表示)、白色(用 1 表示)和蓝色(用 2 表示)共 n 个元素的数组 nums,要求原地排序,使相同颜色的元素相邻,且按 “红→白→蓝” 的顺序排列。需在不使用库内置 sort 函数的前提下解决,进阶要求是 “仅用常数空间的一趟扫描算法”。

示例 1

  • 输入:nums = [2,0,2,1,1,0]
  • 输出:[0,0,1,1,2,2]

示例 2

  • 输入:nums = [2,0,1]
  • 输出:[0,1,2]

核心约束

  • 元素仅为 0、1、2 三种值;
  • 需原地排序(空间复杂度尽可能低);
  • 进阶要求:时间复杂度 O (n)(一趟扫描),空间复杂度 O (1)。

解法一:计数排序(基础思路,两趟扫描)

思路解析

计数排序的核心是 “统计每种元素的出现次数,再按顺序填充回原数组”,适合元素值范围有限的场景(本题仅 0、1、2)。
步骤:

  1. 统计次数:遍历数组,用三个变量分别记录 0、1、2 的出现次数;
  2. 原地填充:按 “0→1→2” 的顺序,根据统计次数将对应值填充回原数组。

代码实现(C++)

#include <vector>
using namespace std;class Solution {
public:void sortColors(vector<int>& nums) {int count0 = 0, count1 = 0, count2 = 0;// 第一趟:统计 0、1、2 的出现次数for (int num : nums) {if (num == 0) count0++;else if (num == 1) count1++;else count2++;}// 第二趟:按顺序填充回原数组int idx = 0;// 填充 0while (count0--) {nums[idx++] = 0;}// 填充 1while (count1--) {nums[idx++] = 1;}// 填充 2while (count2--) {nums[idx++] = 2;}}
};

复杂度分析

  • 时间复杂度:O (n),两趟遍历数组(统计 + 填充),每趟均为 O (n);
  • 空间复杂度:O (1),仅用三个计数变量,无额外空间开销。

优缺点

  • 优点:逻辑简单,易实现,空间复杂度最优,适合元素值范围固定的场景;
  • 缺点:需两趟扫描,不满足进阶要求的 “一趟扫描”,且仅适用于有限值的排序(通用性弱)。

解法二:双指针(一趟扫描,进阶思路)

思路解析

双指针的核心是 “用两个指针划分区域,一趟遍历中完成元素交换”,将数组分为三个区间:

  • [0, left-1]:已排序的 0;
  • [left, right]:待排序的元素(1 或未处理的 0/2);
  • [right+1, n-1]:已排序的 2。

步骤:

  1. 初始化 left = 0(0 的右边界)、right = nums.size() - 1(2 的左边界)、i = 0(当前遍历指针);
  2. 遍历数组,根据 nums[i] 的值调整区域:
    • 若 nums[i] == 0:交换 nums[i] 和 nums[left]left++(0 的区域扩大),i++(当前元素已处理);
    • 若 nums[i] == 2:交换 nums[i] 和 nums[right]right--(2 的区域扩大),不移动 i(交换后 nums[i] 是未处理的元素,需重新判断);
    • 若 nums[i] == 1:直接 i++(1 无需交换,留在待排序区域);
  3. 当 i > right 时,所有元素处理完毕(待排序区域为空)。

代码实现(C++)

#include <vector>
using namespace std;class Solution {
public:void sortColors(vector<int>& nums) {int n = nums.size();int left = 0;    // 0 的右边界([0, left-1] 是 0)int right = n-1; // 2 的左边界([right+1, n-1] 是 2)int i = 0;       // 当前遍历指针while (i <= right) {if (nums[i] == 0) {// 交换到 0 的区域,left 右移,i 右移(当前元素已处理)swap(nums[i], nums[left]);left++;i++;} else if (nums[i] == 2) {// 交换到 2 的区域,right 左移,i 不移动(新元素需重新判断)swap(nums[i], nums[right]);right--;} else {// 遇到 1,直接跳过i++;}}}
};

关键细节

  • 交换 nums[i] 和 nums[right] 后,i 不移动:因为交换过来的元素可能是 0 或 2(如原 nums[right] 是 0),需重新判断是否需要进一步交换;
  • 循环终止条件 i <= right:当 i > right 时,待排序区域 [left, right] 为空,所有元素已归位。

复杂度分析

  • 时间复杂度:O (n),每个元素仅被遍历一次(i 和 right 均单向移动,总步数为 n);
  • 空间复杂度:O (1),仅用三个指针变量,满足进阶要求的 “常数空间”。

优缺点

  • 优点:一趟扫描完成排序,时间和空间复杂度均最优,是本题的标准进阶解法;
  • 缺点:指针移动逻辑需仔细理解(尤其是 i 不移动的场景),对初学者有一定思维门槛。

解法三:单指针(一趟预处理 + 一趟填充,过渡思路)

思路解析

单指针的核心是 “先将 0 归位,再将 1 归位”,本质是 “两次局部的一趟扫描”,介于计数排序和双指针之间。
步骤:

  1. 第一阶段:用指针 p 记录 0 的右边界,遍历数组,将所有 0 交换到 [0, p-1] 区域,p 随交换右移;
  2. 第二阶段:从 p 开始遍历(此时 [0, p-1] 已全为 0),用 p 记录 1 的右边界,将所有 1 交换到 [p, q-1] 区域(q 为新指针),剩余区域自然为 2。

代码实现(C++)

#include <vector>
using namespace std;class Solution {
public:void sortColors(vector<int>& nums) {int n = nums.size();int p = 0; // 记录当前 0 的右边界(第一阶段)/ 1 的右边界(第二阶段)// 第一阶段:将所有 0 移到数组左侧for (int i = 0; i < n; i++) {if (nums[i] == 0) {swap(nums[i], nums[p]);p++;}}// 第二阶段:将所有 1 移到 0 的右侧(从 p 开始,因为 [0,p-1] 已全为 0)for (int i = p; i < n; i++) {if (nums[i] == 1) {swap(nums[i], nums[p]);p++;}}// 剩余 [p, n-1] 区域自然为 2,无需处理}
};

复杂度分析

  • 时间复杂度:O (n),两趟局部遍历(第一趟全数组,第二趟从 p 开始,总步数仍为 O (n));
  • 空间复杂度:O (1),仅用一个指针变量。

优缺点

  • 优点:逻辑比双指针更直观,分阶段处理降低理解难度,适合过渡学习;
  • 缺点:需两趟扫描(虽总步数为 O (n),但非严格意义上的 “一趟”),不满足进阶的 “一趟扫描” 要求。

三种解法对比

解法时间复杂度空间复杂度扫描次数核心优势适用场景
计数排序O(n)O(1)2逻辑最简单,易实现元素值范围固定,无需一趟扫描
双指针O(n)O(1)1一趟扫描,效率最优进阶要求,追求极致性能
单指针O(n)O(1)2逻辑直观,过渡学习理解双指针前的铺垫

进阶总结:双指针解法的核心思想

双指针解法之所以能实现 “一趟扫描 + 常数空间”,关键在于:

  1. 区域划分:用 left 和 right 提前划分出 “已排序的 0 区域” 和 “已排序的 2 区域”,将待处理元素限制在中间,避免重复处理;
  2. 指针单向移动left 只右移(0 区域只扩大),right 只左移(2 区域只扩大),i 只右移(或因交换 2 时暂停),确保每个元素仅被遍历一次;
  3. 原地交换:通过交换元素实现区域调整,无需额外空间存储,满足 “原地排序” 要求。

这种 “划分区域 + 指针协同” 的思路,在排序(如快速排序的分区)、数组分区(如分隔正数和负数)等问题中均有广泛应用,是解决 “线性时间 + 常数空间” 问题的重要技巧。

实际应用场景

  • 图像处理:对像素值进行分类(如黑白灰三色图像的像素排序);
  • 数据过滤:将数据按 “有效 / 待审核 / 无效” 三类划分,且需原地调整;
  • 嵌入式开发:内存受限场景下的轻量级排序(无额外内存可用)。

根据场景需求选择解法:追求简单选计数排序,追求性能选双指针,学习过渡选单指针。


文章转载自:

http://wZFjvwSE.qbyqf.cn
http://xUh9zJUD.qbyqf.cn
http://kS8sDa7e.qbyqf.cn
http://rwi9a6Ii.qbyqf.cn
http://kvmLVzSB.qbyqf.cn
http://orF1dxzV.qbyqf.cn
http://vh0oMUVy.qbyqf.cn
http://SrdNIsxl.qbyqf.cn
http://GJbNxNMu.qbyqf.cn
http://HSECrhMu.qbyqf.cn
http://1HpIdo8K.qbyqf.cn
http://ZvDHC3vm.qbyqf.cn
http://XrnWuMaE.qbyqf.cn
http://Qpdyj3zX.qbyqf.cn
http://4h5gpmFj.qbyqf.cn
http://jU3fhvvC.qbyqf.cn
http://cEpHI0Gr.qbyqf.cn
http://nkhnMmIv.qbyqf.cn
http://9BS8W9qn.qbyqf.cn
http://OK4SDyDR.qbyqf.cn
http://PISHTIy9.qbyqf.cn
http://FpYbnZWT.qbyqf.cn
http://mb3j5WcM.qbyqf.cn
http://jBCMpPxu.qbyqf.cn
http://PTgVKsYx.qbyqf.cn
http://H4VRkr1E.qbyqf.cn
http://qvsqWsuO.qbyqf.cn
http://CwwWYly9.qbyqf.cn
http://pX4ktKze.qbyqf.cn
http://T1WHpRIn.qbyqf.cn
http://www.dtcms.com/a/369111.html

相关文章:

  • 使用自定义固定公网URL地址远程访问公司内网OA办公系统,本地无需公网IP和专线让外网访问
  • pthread_join函数
  • 视觉项目,怎么选主机
  • AI生成内容的版权问题解析与实操指南
  • Oracle软件在主机平台的应用(课程下载)
  • TVS防护静电二极管选型需要注意哪些参数?-ASIM阿赛姆
  • 数据传输优化-异步不阻塞处理增强首屏体验
  • 通信安全员【单选题】考试题库及答案
  • 【开题答辩全过程】以 基于springboot的职业学校教务管理系统设计与实现为例,包含答辩的问题和答案
  • ImmutableMap
  • Oracle 10g → Oracle 19c 升级后问题解决方案(Pro*C 项目)
  • 使用MS-SWIF框架对大模型进行SFT微调
  • 使用PyTorch构建卷积神经网络(CNN)实现CIFAR-10图像分类
  • 非靶向模型中毒攻击和靶向模型中毒攻击
  • 步步高S9:AI重塑学习体验,定义智能教育新范式
  • 与优秀者同行,“复制经验”是成功的最快捷径
  • 2025 IT行业含金量超高的8大证书推荐
  • 《Keil 开发避坑指南:STM32 头文件加载异常与 RTE 配置问题全解决》
  • 基于STM32设计的激光充电控制系统(华为云IOT)_277
  • Kubernetes(四):Service
  • Android studio 既想拍照又想拿到Bitmap
  • 自由泳动作分解与技巧详解
  • 音响皇帝BO,牵手全球第一AR眼镜雷鸟,耳机党坐不住了?
  • Redis 高级数据结构:Bitmap、HyperLogLog、GEO 深度解析
  • 深度学习——迁移学习
  • 【uniapp】打包为h5在保留头部标题的同时配置网站标题不跟随页面路由更新
  • uni-app iOS 日志与崩溃分析全流程 多工具协作的实战指南
  • bat脚本- 将jar 包批量安装到 Maven 本地仓库
  • 力扣hot100:旋转图像(48)(详细图解以及核心思路剖析)
  • U盘文件系统转换指南:方法、原因与注意事项