[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];
    }
};

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

相关推荐
我是一颗柠檬15 分钟前
【Java项目技术亮点】加权轮询负载均衡算法
java·算法·负载均衡
灯厂码农20 分钟前
C语言动态内存分配完全指南(malloc、calloc、realloc、free)
java·c语言·算法
凯瑟琳.奥古斯特2 小时前
K次取反最大化数组和解法(力扣1005)
开发语言·c++·算法·leetcode·职场和发展
林中青木2 小时前
CT重构原理及C++代码实现
c++·计算机视觉·重构
满天星83035772 小时前
Protobuf的介绍及使用
c++
☆cwlulu2 小时前
调试排查工具介绍(gdb、strace、Valgrind等)
开发语言·c++·嵌入式硬件·ubuntu
Jerry2 小时前
LeetCode 203. 移除链表元素
算法
地平线开发者2 小时前
征程 6 | 工具链 QAT ObserverBase 源码解析
算法
卷无止境2 小时前
C++ 存储类说明符(Storage Class Specifier)大横评
c++·后端