我爱学算法之—— 分治-快排

一、颜色分类

题目解析

这道题,给定一个数组nums,其中只存在012(红色、白色、蓝色);我们要将该数组分类,并按照颜色(红色、白色、蓝色)进行排序,简单来说就是将整个数组排序。

算法思路

对这道题,题目要求不使用库内置的sort,我们就可以自己实现一种排序(快排),将数组排序后返回。(红色、白色、蓝色;也就是0、1、2)。

这道题除了实现排序,我们还可以利用指针进行数组划分。思路类似我爱学算法之 ------ 感受双指针带来的快感(上)中**移动0**这道题。

在移动0中,是使用双指针进行数组划分,将数组划分成两个区域(遍历过程中是三个区域)

而在这道题中,我们要将数组划分成三个区域:012。我们可以使用三指针来进行数组划分。

所以,在遍历数组过程中,就会将数组划分成四个区域:

搞懂了数组划分,那就只需搞清楚遍历过程中,遇到012,分别应该做什么操作即可。

遍历数组,遍历到i位置

  • nums[i] = 0:将该数据放入[0 , left]区域中,swap(nums[++left], nums[i++])。(left+1位置肯定是要么是1,要么就是i下标,交换left+1i下标对应的元素;然后i++0区域多了一个元素,left++
  • nums[i] = 1:将数据放到[left+1 , i]区域中,i++
  • nums[i] = 2:将数据放到[right , n-1]区域中,swap(nums[--right], nums[i])。(right-1位置属于区域[i , right-1],是未处理数据,所以交换之后不能让i++;而区域[right , n-1]中多了有数据,right--

i遍历到right位置时,数组nums中只存在三个区域(012),遍历结束。

初始化:

初始状态下,left等于-1right等于n

代码实现

cpp 复制代码
class Solution {
public:
    void sortColors(vector<int>& nums) {
        int left = -1, right = nums.size();
        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]);
        }
    }
};

二、排序数组

题目解析

这道题,就是一个排序数组,题目要求不使用内置函数,时间复杂度为O(n * logn),很显然就是快速排序了。

算法思路

对于快速排序,这里就不详细介绍了,可以参考博主之前文章【排序算法(二)】------冒泡排序、快速排序和归并排序--->深层解析

这里就以挖坑法来实现快速排序:

挖坑法:

  • 以最左边元素为基准值,坑位pit
  • 从右往左,找<基准值的,找到后将该值放到坑位,并更新坑位。
  • 从左往右,找>基准值的,找到后将该值放到坑位,并更新坑位。

遍历结束,将基准值填入坑位位置;这样坑位左侧的值都是<基准值的,坑位右侧的值都是>基准值的;

然后对左侧区间和右侧区间分别进行快速排序。

对于挖坑法实现的快速排序(这道题是可以通过的),这里可以对其进行优化:

优化一:

对于基准值的选择,可以使用随机选择(每次随机从[left , right]中选取一个值作为基准值)。

优化二:

对于快速排序,右边找小,左边找大 ,这个过程,对于等于基准值这种情况,要么是放到右侧、要么是放在左侧;这样如果数组中所有元素都相同,这种情况快速排序的时间复杂度就是O(n^2)

我们可以整个过程(Partition)进行优化,将数组划分成三个区域(小于基准值、等于基准值、大于基准值)。

这样如果数组中所有元素都相同时,只需一次递归遍历。

而对于数组划分,可上述颜色分类一样,无非就是遍历过程中判断元素和基准值的大小关系。

代码实现

cpp 复制代码
class Solution {
public:
    void qsort(vector<int>& nums, int l, int r) {
        if (l >= r)
            return;
        // 随机选择基准值
        int key = nums[l + rand() % (r - l + 1)];
        // partition
        int left = l - 1, right = r + 1;
        int i = l;
        while (i < right) {
            if (nums[i] < key)
                swap(nums[++left], nums[i++]);
            else if (nums[i] == key)
                i++;
            else
                swap(nums[--right], nums[i]);
        }
        // 对<key区间 >key区间进行快速排序
        qsort(nums, l, left);
        qsort(nums, right, r);
    }
    vector<int> sortArray(vector<int>& nums) {
        srand(time(NULL));
        qsort(nums, 0, nums.size() - 1);
        return nums;
    }
};

三、数组中的第K个最大元素

题目解析

这道题要求找出数组中第k个最大的元素,典型的TOPK问题。

算法思路

思路一:

对于这道题,找出第k个最大的元素,肯定是可以使用优先级队列(堆)解决的。

建立小堆,遍历数组nums,维持堆中数据个数为k

最终堆顶元素就是第k个最大的元素。

时间复杂度:O(n * logk)

思路二:

这里题目要求我们实现时间复杂度为O(n)的算法,而使用优先级队列解决TopK问题时间复杂度为O(n * logk)

快速选择排序:

在上述排序数组题目中,我们使用数组划分 来代替快速排序的partition,将数组划分成了三部分。


< key的区间长度为a= key的区间长度为b> key的区间长度为c

  • 如果c >=k,那第k个最大的元素肯定在> key区间中。

> key区间继续进行快速选择排序即可。(>key区间中,找第k个最大的元素

  • 如果b + c >= k,那第k个最大的元素一定就是key(区间[left+1 , right-1]=key的)
  • 如果b + c < k,那第k个最大的元素一定在< key的区间中。

< key区间继续进行快速选择排序即可。(< key区间中,找第k-b-c个最大的元素

代码实现

cpp 复制代码
class Solution {
public:
    void qsort(vector<int>& nums, int l, int r) {
        if (l >= r)
            return;
        int key = nums[l + rand() % (r - l + 1)];
        int left = l - 1, right = r + 1;
        int i = l;
        while (i < right) {
            if (nums[i] < key)
                swap(nums[++left], nums[i++]);
            else if (nums[i] == key)
                i++;
            else
                swap(nums[--right], nums[i]);
        }
        qsort(nums, l, left);
        qsort(nums, right, r);
    }
    vector<int> inventoryManagement(vector<int>& stock, int cnt) {
        srand(time(NULL));
        qsort(stock, 0, stock.size() - 1);
        vector<int> ret(cnt, 0);
        for (int i = 0; i < cnt; i++)
            ret[i] = stock[i];
        return ret;
    }
};

四、库存管理 III

题目解析

这道题就非常简单了,给定一个数组stock和一个整数cnt,要求我们返回一个数组,这个数组中存储的是stock中最小的cnt个数。

算法思路

这道题相对就简单很多了,这道题也算是一直TopK问题。

思路一:

解决TopK问题,创建大堆,维持堆中个数为cnt,最后将堆中元素转化成数组vector返回即可。

思路二:

将数组排序(快速排序),然后将数组前cnt个元素组成一个新的数组,然后返回。

代码实现

这里就实现将数组排序,然后将前cnt个元素组成新的数组。

cpp 复制代码
class Solution {
public:
    void qsort(vector<int>& nums, int l, int r) {
        if (l >= r)
            return;
        int key = nums[l + rand() % (r - l + 1)];
        int left = l - 1, right = r + 1;
        int i = l;
        while (i < right) {
            if (nums[i] < key)
                swap(nums[++left], nums[i++]);
            else if (nums[i] == key)
                i++;
            else
                swap(nums[--right], nums[i]);
        }
        qsort(nums, l, left);
        qsort(nums, right, r);
    }
    vector<int> inventoryManagement(vector<int>& stock, int cnt) {
        srand(time(NULL));
        qsort(stock, 0, stock.size() - 1);
        vector<int> ret(cnt);
        for (int i = 0; i < cnt; i++)
            ret[i] = stock[i];
        return ret;
    }
};

本篇文章到这里就结束了,感谢支持

我的博客即将同步至腾讯云开发者社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=2oul0hvapjsws

相关推荐
未知陨落2 小时前
LeetCode:77.买卖股票的最佳时机
算法·leetcode
DeeGLMath2 小时前
排序算法的并行加速实现
算法·排序算法·joblib·sortingx
charlie1145141912 小时前
精读C++20设计模式——行为型设计模式:命令模式
c++·学习·设计模式·程序设计·命令模式·c++20
AA陈超3 小时前
虚幻引擎UE5专用服务器游戏开发-32 使用Gameplay Tags阻止连招触发
c++·游戏·ue5·游戏引擎·虚幻
机器学习之心3 小时前
量子遗传算法是一种将量子计算原理与遗传算法相结合的智能优化算法,代表了进化计算的一个有趣分支
算法·量子计算
Miraitowa_cheems3 小时前
LeetCode算法日记 - Day 59: 字母大小写全排列、优美的排列
java·数据结构·算法·leetcode·决策树·职场和发展·深度优先
金色熊族3 小时前
ubuntu20.04编译qt源码5.15.3
linux·c++·qt
智能化咨询3 小时前
【C++】异常介绍:高级应用与性能优化
c++
未知陨落4 小时前
LeetCode:81.爬楼梯
算法·leetcode