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

一、颜色分类

题目解析

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

算法思路

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

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

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

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

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

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

遍历数组,遍历到i位置

  • nums[i] = 0:将该数据放入[0 , left]区域中,swap(nums[++left], nums[i++])。(left+1位置肯定是要么是1,要么就是i下标,交换left+1和i下标对应的元素;然后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中只存在三个区域(0、1、2),遍历结束。

初始化:

初始状态下,left等于-1,right等于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

相关推荐
唐墨1234 小时前
linux kernel 比较数据大小的宏定义
linux·算法
拂拉氏5 小时前
【知识讲解-题目讲解】 统计满足K约束的子字符串数量2 (滑动窗口+前缀和+二分查找+分析)
算法·前缀和·二分查找·滑动窗口
通信小呆呆9 小时前
电子战抗干扰技术:当“不被干扰”成为一种系统
算法·通信·mimo·isac
老歌老听老掉牙9 小时前
案例 7:二维 Rosenbrock 函数最小值(香蕉函数)
c++·pso
笑容掩饰我的失落9 小时前
区块链从小白到精通(八):从零实现一个标准 ERC-20 代币(完整可运行代码)
算法·区块链·php·智能合约·哈希算法
Severus_black10 小时前
【C++初阶】Stack、Queue算法题大练兵!
c语言·c++·算法·stl
2601_9679791212 小时前
机器学习必修课:经典算法与Python实战
python·算法·机器学习·实战·课程
程序猿打工仔13 小时前
计算机基础(C语言)考编、专技岗
c语言·开发语言·算法
通信小呆呆13 小时前
电子战干扰技术:从“蛮力压制”到“精准欺骗”,再到“认知博弈”
算法·通信·isac