[LC优选算法#5] 分治:快排 | 颜色分类 | 排序数组 | 第K大元素

1. 分治-快排思想

之前的博客中介绍过快速排序的思想,详细请跳转此篇博客:

C语言:排序(二)

分治快排算法的思想和传统的快速排序有所不同,具体体现在:

  • 快速排序 :选取一个参照数key,将区间分为两部分,左侧元素<=key,右侧元素>key
  • 分治快排算法 :选取一个参照数key,将区间分为三部分,左侧元素<=key,中间元素==key,右侧元素>key

相比快速排序,分治快排规避了当元素全部为key时,排序效率退化为O(N^2)的情况,实现了排序效率稳定在O(NlogN)。具体的计算分析可以阅读《算法导论》概率求期望的部分。

2. 例题分析

2.1 颜色分类

颜色分类

解题思路:

题目明确要求将数组分为0,1,2有序的三块,因此选择分治快排的思想解决。

这里我们需要用三个指针left,right,i来将数组分块:

指针ileft的下一个位置出发,依次向后移动,并与left或right指向的元素进行交换。以下是关于i位置元素的分类讨论:

需要注意的是,当nums[i]为2时,在交换后不能让i++,因为i左边的区间依旧是待处理区间,交换过来的元素需要在下次循环进行二次处理。

下面是一个数组具体的排序过程:

注意点 :由于指针i是和指针的left和right的下一个位置进行交换,因此left的起始位置是-1right的起始位置是nums.size() - 1;循环的结束条件应设置为i < right

cpp 复制代码
class Solution {
public:
    void sortColors(vector<int>& nums)
    {
        int n = nums.size();
        for(int left=-1, right=n, i=0; i < right;)
        {
            if(nums[i] == 0)
            {
                swap(nums[++left], nums[i++]);
            }
            else if(nums[i] == 2)
            {
                swap(nums[--right], nums[i]); //不要i++!
            }
            else
            {
                i++;
            }
        }
    }
};

2.2 排序数组

排序数组

题目要求用O(NlogN)的时间复杂度解决,且空间复杂度尽量小,因此选择数组分三块+随机选取key元素的快速排序算法解决。

解题思路

快速排序-数组分三块 :选取一个参照数key,将区间分为三部分 ,左侧元素<=key,中间元素==key,右侧元素>key,不断递归直至数组有序。具体过程如图:

优化点 :随机选择key,让数组排序时的区间更加平衡:

cpp 复制代码
class Solution {
public:
    vector<int> sortArray(vector<int>& nums)
    {
        int n = nums.size();
        srand(time(NULL));
        QuickSort(nums, 0, n - 1);

        return nums;
    }

    void QuickSort(vector<int>& nums, int l, int r)
    {
        if(l >= r) return;

        int key = getRandom(nums, l, r);
        int left = l - 1;
        int right = r + 1;

        for(int i = l; i < right;)
        {
            if(nums[i] < key)
            {
                swap(nums[++left], nums[i++]);
            }
            else if(nums[i] > key)
            {
                swap(nums[--right], nums[i]);
            }
            else
            {
                i++;
            }
        }

        QuickSort(nums, l, left); //注意递归区间!
        QuickSort(nums, right, r);

    }

    int getRandom(vector<int>& nums, int l, int r)
    {
        int num = rand();
        return nums[num % (r - l + 1) + l];
    }
};

2.3 数组中的第K个最大元素

数组中的第K个最大元素

这道题是比较经典的TopK问题,这个问题包含以下变式:

  1. 求第K个最大 / 最小的元素
  2. 求前K个最大 / 最小的元素

对于这种问题,一般的解法是堆排序 或者快速排序 ,时间复杂度都是O(NlogN)

由于题目要求用O(N)的时间复杂度解决问题,但普通排序的时间复杂度基本都是O(NlogN),因此这里推荐用快速排序算法,并在排序的过程中去对应区间找对应的第K大元素即可。

解题思路

  • 快速排序算法 :依旧延用上一题的思路,采用数组分三块+随机选key的方式解决;
  • 找第K大的元素 :在排序过程中添加一个步骤,就是分别计算出三个区间中的元素个数,依次和K比较,判断第K大的元素落在哪个区间,再单独去这个区间找。
cpp 复制代码
class Solution {
public:
    
    int findKthLargest(vector<int>& nums, int k)
    {
        srand(time(NULL));
        return QuickSort(nums, 0, nums.size()-1, k);
    }

    int QuickSort(vector<int>& nums, int l, int r, int k)
    {
        if(l == r) return nums[l]; //区间一定会存在

        //1.随机选择基准元素
        int key = getRandom(nums, l, r);

        //2.根据基准元素将数组分为三块
        int left = l - 1;
        int right = r + 1;
        int i = l;
        while(i < right)
        {
            if(nums[i] < key)
            {
                swap(nums[++left], nums[i++]);
            }
            else if(nums[i] > key)
            {
                swap(nums[--right], nums[i]);
            }
            else
            {
                i++;
            }
        }

        //3.分情况讨论
        int c = r - right + 1;
        int b = right - left - 1;

        if(k <= c) return QuickSort(nums, right, r, k);
        else if(k <= b + c) return key; //注意是b + c!
        else return QuickSort(nums, l, left, k - b - c); //注意是k - b - c!
    }

    int getRandom(vector<int>& nums, int l, int r)
    {
        int num = rand();
        return nums[num % (r - l + 1) + l];
    }
};

// 本期内容就到这里啦,如果对你有帮助,请三连支持!我是青云,我们下期见^_~

相关推荐
青山木1 小时前
Hot 100 --- 滑动窗口最大值
java·数据结构·算法·leetcode·动态规划
青山木1 小时前
Hot 100 --- 除自身以外数组的乘积
java·数据结构·算法
Frank学习路上1 小时前
【C++】面试:STL容器与算法
c++·算法·面试
凡人叶枫1 小时前
Effective C++ 条款33:避免遮掩继承而来的名字
linux·服务器·开发语言·c++·嵌入式开发
10岁的博客1 小时前
NOIP2010普及组「接水问题」详解:模拟算法与优先队列解法
开发语言·c++·算法
凡人叶枫1 小时前
Effective C++ 条款31:将文件间的编译依存关系降至最低
linux·开发语言·c++·php·嵌入式开发·effective c++
彼岸星光ぐ>1 小时前
排序算法对比
数据结构·算法·排序算法
liulilittle1 小时前
整数溢出陷阱:用除法安全比较乘积
c++