【专题七】分治——快速排序

📝前言说明:

  • 本专栏主要记录本人的基础算法学习以及LeetCode刷题记录,按专题划分
  • 每题主要记录:(1)本人解法 + 本人屎山代码;(2)优质解法 + 优质代码;(3)精益求精,更好的解法和独特的思想(如果有的话)
  • 文章中的理解仅为个人理解。如有错误,感谢纠错

🎬个人简介:努力学习ing
📋本专栏:C++刷题专栏
📋其他专栏:C语言入门基础python入门基础C++学习笔记Linux
🎀CSDN主页 愚润泽


什么是分治——快速排序

  • 分治:分而治之。把大问题划分成多个相同的小问题,直到划分到一个可以迅速解决的子问题(可以递归)。
  • 快排:之前我们所用的快排是把区间划分成:<=point>point两个区间
  • 快排优化:
    • 分三块区域:把区间划分成:<point==point>point三个区间
    • point的时候,随机选择基准元素

75. 颜色分类

题目链接:https://leetcode.cn/problems/sort-colors/description/
在这里插入图片描述


优质解

思路:

  • 题意:这道题相当于,选取1作为基准元素,把数组划分成三部分<1==1>1
  • 解题方法(三指针):
    • ileftright指针:
      • i遍历数组;
      • left指向<1区间的右边界(因为这个区间的左边是0);
      • right指向>1区间的左边界(因为这个区间的右边是n-1
    • 在扫描的时候,数组会被划分成四个区域:
      • [0, left]:全是 0
      • [left + 1, i - 1]:全是 1
      • [i, right - 1]:全是带扫描的元素
      • [right, n - 1]:全是 2
    • i扫描时对于元素的处理:
      • nums[i] == 0left++左区间扩大,然后交换nums[left]nums[i],交换完以后,i++继续遍历,因为前面的元素都已经遍历过了。
      • nums[i] == 2right--右区间扩大,然后交换nums[right]nums[i],交换完以后,i不用++,因为交换过来的是没有遍历过的。
      • nums[i] == 1:直接i++

代码:

class Solution {
public:
    void sortColors(vector<int>& nums) 
    {
        int n = nums.size();
        for(int i = 0, left = -1, right = n; i < right; i++)
        {
            if(nums[i] == 1)
                continue;
            else if(nums[i] == 0)
                swap(nums[++left], nums[i]);
            else
                swap(nums[--right], nums[i--]); // i-- 和 for 的i++ 抵消
        }
    }
};

时间复杂度:O(n)
空间复杂度:O(1)


912. 排序数组

题目链接:https://leetcode.cn/problems/sort-an-array/description/
在这里插入图片描述


优质解

思路:

  • 随机方式选基准 + 三块区域的快排
    • 三块区域的快排:和75. 颜色分类这道题一样
    • 随机方式选基准point
      • 用随机数,然后映射到区间上,实现等概率随机选
      • 映射方式,即:pi = a % (right - left + 1) + leftpi是随机基准的下标)
    • 关于随机数生成:
      • rand()生成的是伪随机数(和种子有关)
      • srand() 可以来设置随机数种子
      • 通常使用time(nullptr) 返回的时间戳作为srand()的参数,来设置不同的种子(因为时间是不断变化的)

代码:

class Solution {
public:
    int getrandpoint(vector<int>& nums, int left, int right)
    {
        int a = rand(); // 生成随机数
        return nums[(a % (right - left + 1)) + left];
    }
    void qsort(vector<int>& nums, int l, int r)
    {
        if(l >= r) return;
        int point = getrandpoint(nums, l, r);
        int i = l, right = r + 1, left = l - 1; // 注意开始时是空区间
        while(i < right) 
        {
            if(nums[i] == point)
                i++;
            else if(nums[i] < point)
                swap(nums[++left], nums[i++]);
            else
                swap(nums[--right], nums[i]);
        }
        qsort(nums, l, left);
        qsort(nums, right, r);
    }
    vector<int> sortArray(vector<int>& nums) 
    {
        srand(time(nullptr)); // 设置随机种子
        qsort(nums, 0, nums.size() - 1);
        return nums;
    }
};

时间复杂度:平均:O(nlogn),最坏:O(n^2)【每次选的都是最大 / 最小元素,几乎不会出现】
空间复杂度:平均:O(logn)【调用栈的深度】,最坏:O(n)【几乎不会出现】


215. 数组中的第K个最大元素

题目链接:https://leetcode.cn/problems/kth-largest-element-in-an-array/description/
在这里插入图片描述


优质解

思路:

  • top k问题:第 k 大,第 k 小,前 k 大,前 k 小
  • 解决方法:堆排序: 或 快速选择算法(基于快排)
  • 快速选择算法:
    • 分情况讨论不一样:
      • 在分好区间以后,不是直接左右区间都再排,而是根据 k 判断第 k 大再哪个区间,然后仅需去那个区间找。
    • 设从左到右,区间的元素依次为a、b、c
      • k <= c:则在c区域,继续在c里面找第k
      • c < k < = b + c:则在b区域,直接返回point(因为这个区间全是== point
      • b + c < k:则在a区域,变成:找a区域里面的第k - b - c大的元素

代码(快速选择)

class Solution {
public:
    // 获得随机基准
    int getrandpoint(vector<int>& nums, int left, int right)
    {
        int a = rand(); // 生成随机数
        return nums[(a % (right - left + 1)) + left];
    }
    // 
    int qselect(vector<int>& nums, int k, int l, int r)
    {
        if(l == r) return nums[l];  // 区间长度为 1 的时候,一定是目标值
        int point = getrandpoint(nums, l, r);
        int i = l, left = l - 1, right = r + 1;
        // 区域划分
        while(i < right)
        {
            if(nums[i] < point) swap(nums[++left], nums[i++]);
            else if(nums[i] == point) i++;
            else swap(nums[--right], nums[i]);
        }

        // 分情况讨论去那个区间寻找
        int c = r - right + 1, b = right - left - 1;
        if(c >= k) return qselect(nums, k, right, r);
        else if(c + b >= k) return point;
        else return qselect(nums, k - b - c, l, left);
    }
    int findKthLargest(vector<int>& nums, int k) {
        srand(time(nullptr));
        return qselect(nums, k, 0, nums.size() - 1);
    }
};

时间复杂度:O(n)
空间复杂度:O(logn)

代码(堆排序,作者:力扣官方题解):

class Solution {
public:
    void maxHeapify(vector<int>& a, int i, int heapSize) {
        int l = i * 2 + 1, r = i * 2 + 2, largest = i;
        if (l < heapSize && a[l] > a[largest]) {
            largest = l;
        } 
        if (r < heapSize && a[r] > a[largest]) {
            largest = r;
        }
        if (largest != i) {
            swap(a[i], a[largest]);
            maxHeapify(a, largest, heapSize);
        }
    }

    void buildMaxHeap(vector<int>& a, int heapSize) {
        for (int i = heapSize / 2 - 1; i >= 0; --i) {
            maxHeapify(a, i, heapSize);
        } 
    }

    int findKthLargest(vector<int>& nums, int k) {
        int heapSize = nums.size();
        buildMaxHeap(nums, heapSize);
        for (int i = nums.size() - 1; i >= nums.size() - k + 1; --i) {
            swap(nums[0], nums[i]);
            --heapSize;
            maxHeapify(nums, 0, heapSize);
        }
        return nums[0];
    }
};

时间复杂度:O(nlogn)
空间复杂度:O(logn) ,因为这里是递归的,有递归栈调用


LCR 159. 库存管理 III

题目链接:https://leetcode.cn/problems/zui-xiao-de-kge-shu-lcof/description/

在这里插入图片描述

个人解

思路:

  • 这就是 top k 的前 k 小问题
  • 我们可以找到第 k 个数,然后把 第 k 个数前面的元素都排好了,最后提取出来就行

用时:
屎山代码:

class Solution {
public:
    int getrandpoint(vector<int>& stock, int l, int r)
    {
        return stock[rand() % (r - l + 1) + l];
    }

    void qselect(vector<int>& stock, int cnt, int l, int r)
    {
        if(l >= r) return; // l == r 只有一个元素的区间, l > r 空区间
        int i = l, left = l - 1, right = r + 1;
        int point = getrandpoint(stock, l, r);
        
        // 分区
        while(i < right)
        {
            if(stock[i] < point) swap(stock[++left], stock[i++]);
            else if(stock[i] == point) i++;
            else swap(stock[--right], stock[i]);
        }
        
        // 分类讨论
        int a = left - l + 1, b = right - left - 1;
        if(cnt <= a)
            return qselect(stock, cnt, l, left);
        else if(cnt <= a + b) // 中间部分不需要判断是第几个,因为都是相同的,直接取就行
            return;
        else
            return qselect(stock, cnt - a - b, right, r); 
    }
    vector<int> inventoryManagement(vector<int>& stock, int cnt) 
    {
        srand(time(nullptr));
        qselect(stock, cnt, 0, stock.size() - 1);
        vector<int> ans;
        for (int i = 0; i < cnt; i++)
            ans.push_back(stock[i]);
        return ans;
    }
};

时间复杂度:期望:O(n)
空间复杂度:期望:O(logn)


🌈我的分享也就到此结束啦🌈
要是我的分享也能对你的学习起到帮助,那简直是太酷啦!
若有不足,还请大家多多指正,我们一起学习交流!
📢公主,王子:点赞👍→收藏⭐→关注🔍
感谢大家的观看和支持!祝大家都能得偿所愿,天天开心!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

愚润泽

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值