算法日记:分治-快排(颜色分类,排序数组,数组中的第k个最大元素 面试题17.14.最小k个数)

🎬 胖咕噜的稞达鸭个人主页
🔥 个人专栏 : 《数据结构《C++初阶高阶》
《Linux系统学习》
《算法日记》

⛺️技术的杠杆,撬动整个世界!


颜色分类

解法:三指针

将数组中的0,1,2按照顺序排序,不可以使用sort()

算法:

i从数组索引为0的位置开始遍历,left在数组下标索引为0的位置,right在索引为n-1的位置。

如果i遍历到的数字nums[i]== 0,跟nums[left]的位置交换,与此同时left++;

如果i遍历到的nums[ i ] == 1;则 i ++;

如果i遍历到的nums[ i ] == 2;则交换nums[ right ]和nums[i ] ,于此同时right--

循环的结束条件是i 跟right相遇。

将一段数组分成三份,依次存储0,1,2,用left,right,i分段将数组分为4段。

复制代码
[0,left]:全部都是0
[left+1,i-1]:全部都是1
[i,right-1]:待扫描的元素
[right,n-1]:全部都是2
cpp 复制代码
class Solution {
public:
    void sortColors(vector<int>& nums) {
        int left = -1,right = nums.size();//left设置为1的位置上:初始还没有找到0的位置,right设置为nums.size()的位置初始化还没有找到2的位置
        int i = 0;
        while(i<right)
        {
            if(nums[i] == 0)swap(nums[++left],nums[i++]);
            else if(nums[i] == 1)i++;
            else swap(nums[--right],nums[i]);//i不用++,不然交换过去的i是的还没有被处理的,而不是2
        }
    }
};

912.排序数组

[912. 排序数组 - 力扣(LeetCode)](https://leetcode.cn/problems/sort-an-array/description/)\](排序数组) ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/3e5aa1802b76461e985a13801ed2b90b.png) **解法一:快速排序** ```cpp class Solution { public: vector sortArray(vector& nums) { srand(time(NULL));//种下一颗随机数的种子 qsort(nums,0,nums.size()-1); return nums; } //快排 void qsort(vector& nums,int l,int r) { if(l >= r)return; //数组分类 int key = getRandom(nums,l,r); int i = l,left = l-1,right = r+1; while(i < right) { if(nums[i] < key)swap(nums[++left],nums[i++]); else if(nums[i] == key)i++; else swap(nums[--right],nums[i]); } //数组分为三块[l,left][left+1,right - 1][right,r] qsort(nums,l,left); qsort(nums,right,r); } int getRandom(vector& nums,int left,int right) { int r = rand(); return nums[r % (right - left+1)+left]; } }; ``` **解法二:归并排序** 将数组划分为两块,两块数组都有序之后合并到一起,找两个指针,`ptr1,ptr2`。分别遍历这两个数组,`ptr1`从数组1开始,`int ptr1 = left`;ptr2从数组2开始,`int ptr2 = mid + 1;`哪个在指向数字的过程中遇到的数小就插入,随后要处理未完成的数组,数组1有余留就插入到`tmp`中,数组2同理。 最后按照顺序返回排好序的数组。 ```cpp class Solution { vectortmp; public: vector sortArray(vector& nums) { tmp.resize(nums.size()); mergeSort(nums,0,nums.size()-1); return nums; } void mergeSort(vector&nums,int left,int right) { if(left >= right)return; //1.选择中间点划分区间 int mid = (right + left) >> 1;//相加之后右移一位 //2,把左右区间排序 mergeSort(nums,left,mid); mergeSort(nums,mid+1,right); //3.合并两个有序数组 int ptr1 = left,ptr2 = mid + 1,i = 0; while(ptr1 <= mid && ptr2 <= right) { tmp[i++] = nums[ptr1] <= nums[ptr2] ? nums[ptr1++] : nums[ptr2++]; } //4.处理未完成的数组 while(ptr1 <= mid)tmp[i++] = nums[ptr1++]; while(ptr2 <= right)tmp[i++] = nums[ptr2++]; //5.还原 for(int i = left;i <= right;i++) nums[i] = tmp[i -left]; } }; ``` ### 数组中的第k个最大元素 \[[215. 数组中的第K个最大元素 - 力扣(LeetCode)](https://leetcode.cn/problems/kth-largest-element-in-an-array/description/)\](数组中的第k个最大元素) ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/d42b176a3df94770ad568cebc4c31209.png) ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/aded156d866a4e53ab679dfd5e4f2a42.jpeg) **判断逻辑**: 1. 如果前c个最大的元素已经包含了第k大 → 去大于区找 2. 如果前(c+b)个最大的元素包含了第k大 → 第k大就是key 3. 否则 → 去小于区找(调整k的值) ###### 具体示例分析 ###### 示例1:找第2大 数组:[5, 3, 7, 3, 1, 3, 9] k=2 三路划分后:小于区[1], 等于区[3,3,3], 大于区[9,7,5] c=3, b=3 判断: 1. c=3 >= k=2 ✓ → 第2大在大于区 2. 在大于区[9,7,5]中找第2大 → 返回7 ###### 示例2:找第4大 数组:[5, 3, 7, 3, 1, 3, 9] k=4 三路划分后:小于区[1], 等于区[3,3,3], 大于区[9,7,5] c=3, b=3 判断: 1. c=3 >= k=4? ✗ 2. b+c=6 >= k=4? ✓ → 第4大在等于区,返回key=3 ###### 示例3:找第7大(最小) 数组:[5, 3, 7, 3, 1, 3, 9] k=7 三路划分后:小于区[1], 等于区[3,3,3], 大于区[9,7,5] c=3, b=3 判断: 1. c=3 >= k=7? ✗ 2. b+c=6 >= k=7? ✗ 3. 第3种情况 → 在小于区[1]找第(k-b-c)=7-6=1大 返回1 ```cpp class Solution { public: int findKthLargest(vector& nums, int k) { //种一棵随机数种子 srand(time(NULL));//随机树种子 return qsort(nums,0,nums.size()-1,k); } int getRandom(vector&nums,int left,int right) { int r = rand(); return nums[r % (right - left + 1) + left]; } int qsort(vector&nums,int l,int r,int k) { if(l == r)return nums[l];//如果数组中l和r是相等的,不用进行下面的操作了,直接返回 //1.随机选择基准元素 int key = getRandom(nums,l,r);//在l,r这段区间随机返回一个随机数 //2.随机选择基准元素将数组分为3组:[l,left][left-1,right+1][right,r] int i = l,left = l-1,right = r + 1; while(i < right) { if(nums[i]= k)return qsort(nums,right,r,k); else if(b + c >= k)return key; else return qsort(nums,l,left,k - b -c); } }; ``` ### 面试题17.14.最小K个数 \[[面试题 17.14. 最小K个数 - 力扣(LeetCode)](https://leetcode.cn/problems/smallest-k-lcci/description/)\](最小K个数) ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/8b9e6e8a65794929be80dafafa343476.png) 解法:随机数作为基准元素 + 数组分为三段 --------------------------- | | | | l left right r [l,left]:key的元素 返回数组中最小的K个数,首先要从\[l,left\]中寻找, 假设`【l,left】`中有a个数字,`int a = left - l + 1;` 假设`【left,right】`中有b个数字,`int b = right - left - 1;` 如果要找的k的个数小于a,就在a区间中寻找; 如果要找的k的个数小于`a + b`这一大个区间,返回key; 实在找不到可以在剩下的`k - a- b`个数字中(也就是大于key)的区间中找。 ```cpp class Solution { public: vector smallestK(vector& arr, int k) { srand(time(NULL)); qsort(arr,0,arr.size()-1,k); return {arr.begin(),arr.begin()+k};//最后返回的是一个数组 } void qsort(vector&arr,int l,int r,int k) { if(l >= r)return; int i = l,left = l - 1,right = r + 1; int key = getRandom(arr,l,r); while(i < right) { if(arr[i] < key)swap(arr[i++],arr[++left]); else if(arr[i] == key)i++; else swap(arr[i],arr[--right]); } //分情况讨论 int a = left - l + 1; int b = right - left - 1; if(a > k)return qsort(arr,l,left,k); else if(a + b >= k)return ; else return qsort(arr,right,r,k - a- b); } int getRandom(vector&arr,int left,int right) { int r = rand(); return arr[ r % (right - left + 1) + left]; } }; ``` ### 遇到分区/选择类算法题的通用解法思路 **核心模式识别** 当你看到以下特征时,考虑使用分区/选择算法: 1. 需要重新排序数组元素(如按颜色、值排序) 2. 寻找第K大/第K小的元素 3. 寻找前K个最小/最大的元素 4. 题目要求O(n)时间复杂度或禁止使用sort() **四步解题框架** 第一步:分析问题类型 > 如果是排序/分类 → 直接用三指针分区(如颜色分类) > > 如果是第K个元素 → 用快速选择 > > 如果是前K个元素 → 用改进的快速选择 第二步:选择分区策略 ```cpp // 三路分区模板(最常用) int left = l - 1, right = r + 1; // 扩展边界 int i = l; // 当前指针 int key = getRandom(nums, l, r); // 随机基准值 while (i < right) { if (nums[i] < key) swap(nums[++left], nums[i++]); else if (nums[i] == key) i++; else swap(nums[--right], nums[i]); } // 分区结果: // [l, left] < key // [left+1, right-1] == key // [right, r] > key ``` 第三步:判断递归方向 cpp ```cpp // 对于第K大元素: int lessCount = left - l + 1; // 小于区的数量 int equalCount = right - left - 1; // 等于区的数量 int greaterCount = r - right + 1; // 大于区的数量 // 判断逻辑: if (greaterCount >= k) { // 在大于区找第k大 return qsort(nums, right, r, k); } else if (greaterCount + equalCount >= k) { // 第k大在等于区 return key; } else { // 在小于区找第(k - greaterCount - equalCount)大 return qsort(nums, l, left, k - greaterCount - equalCount); } ``` 第四步:边界处理 ```cpp // 递归终止条件 if (l >= r) return nums[l]; // 第K大元素 if (l >= r) return; // 排序/分类 if (k == 0) return {}; // 前K个元素 ``` **实战决策树** ```bash 问题类型 ├── 重新排序/分类(如颜色分类) │ ├── 固定规则(如0,1,2)→ 三指针遍历交换 │ └── 任意规则 → 三路分区 ├── 寻找单个元素(第K大/小) │ ├── 第K大 → 按大于区、等于区、小于区顺序判断 │ └── 第K小 → 按小于区、等于区、大于区顺序判断 └── 寻找多个元素(最小/大的K个数) ├── K个数需要排序 → 先快速选择,再对前K个排序 └── K个数无需排序 → 直接分区到正确位置 ``` **关键技巧提醒** 随机化基准值是避免最坏情况的关键 ```cpp srand(time(NULL)); // 随机种子 int r = rand(); return nums[r % (right - left + 1) + left]; ``` 指针边界处理 ```bash 初始时让 left = l-1, right = r+1 ``` 这样分区后区间更清晰 **i指针的处理** ```bash 交换到左边的元素可以 i++(已处理) 交换到右边的元素不要 i++(需要重新处理) ``` **复杂度分析** 平均:O(n) 或 O(n log n) 最坏:O(n²) → 通过随机化避免 **常见易错点** 错误 正确做法 忘记处理相等情况 一定要有 `else if(nums[i]==key)i++` 交换后错误移动指针 从左换来的可以i++,从右换来的不能i++ 递归方向判断错误 画图明确各分区的元素数量 边界条件漏掉 总是检查 if(l\>=r) ### 一句话总结 "随机基准三路分,数量判断定区间,递归缩小范围找"------遇到这类问题,先随机选基准值,三路分区,根据各区间元素数量决定下一步操作,递归缩小范围直到找到答案。

相关推荐
独自破碎E4 分钟前
【数组】分糖果问题
java·开发语言·算法
10岁的博客7 分钟前
C语言造轮子大赛
java·c语言·数据结构
努力努力再努力wz7 分钟前
【Linux网络系列】:打破 HTTP 明文诅咒,在Linux 下用 C++ 手搓 HTTPS 服务器全过程!(附实现源码)
linux·服务器·网络·数据结构·c++·http·https
@Aurora.10 分钟前
优选算法【专题七:分治】
数据结构·算法·排序算法
程序员敲代码吗12 分钟前
C++与硬件交互编程
开发语言·c++·算法
8K超高清12 分钟前
博冠8K广播级讯道摄像机获国际设计大奖
网络·算法·fpga开发·接口隔离原则·智能硬件
你怎么知道我是队长13 分钟前
C语言---排序算法1---冒泡排序法
c语言·算法·排序算法
qq_5375626714 分钟前
C++与Java性能对比
开发语言·c++·算法
忆锦紫15 分钟前
图像锐化算法:Robert/Sobel/Laplacian锐化算法及MATLAB实现
图像处理·算法·计算机视觉·matlab
m0_6860416115 分钟前
C++中的策略模式应用
开发语言·c++·算法