【算法题攻略】快速排序 和 归并排序

文章目录

  • 前言
  • 一、快速排序的习题讲解
    • [1. 排序数组(模板题,详细介绍快排算法的优化过程)](#1. 排序数组(模板题,详细介绍快排算法的优化过程))
    • [2. 数组中的第K个最大元素(快速选择算法)](#2. 数组中的第K个最大元素(快速选择算法))
    • [3. 练习题:库存管理 III](#3. 练习题:库存管理 III)
  • 二、归并排序的习题讲解
    • [1. 排序数组(模板题,归并排序的算法思路)](#1. 排序数组(模板题,归并排序的算法思路))
    • [2. 数组中的逆序对(hard)](#2. 数组中的逆序对(hard))
    • [3. 计算右侧小于当前元素的个数](#3. 计算右侧小于当前元素的个数)

前言

快速排序 和 归并排序的算法思路详见:【算法】常见排序算法(插入排序、选择排序、交换排序和归并排序)


一、快速排序的习题讲解

1. 排序数组(模板题,详细介绍快排算法的优化过程)

912. 排序数组

  • 题目描述:

给你一个整数数组 nums,请你将该数组升序排列。

你必须在 不使用任何内置函数 的情况下解决问题,时间复杂度为 O(nlog(n)),并且空间复杂度尽可能小。

示例 :

  • 输入:nums = 5,1,1,2,0,0

  • 输出:0,0,1,1,2,5

  • 解释:请注意,nums 的值不一定唯一。
    提示:

  • 1 <= nums.length <= 5 * 10^4

  • -5 * 10^4 <= numsi <= 5 * 10^4

  • 代码示例:
cpp 复制代码
class Solution {
public:
    void swap(int* a, int* b)
    {
    	int tmp = *a;
	    *a = *b;
	    *b = tmp;
    }

    // 使用三数取中法选择三者的中位数作为基准值并将其换到begin位置
    void median_select(vector<int>& vec, int begin, int end)
    {
        int mid = begin + (end - begin) / 2;

        if (vec[begin] > vec[mid])           // 目标: vec[mid] >= vec[begin]
            swap(&vec[begin], &vec[mid]);
        if(vec[mid] > vec[end])              // 目标: vec[end] >= vec[mid]
            swap(&vec[mid], &vec[end]);      // 前两步,使vec[end]位置一定是最大值
        
        if(vec[begin] < vec[mid])            // 目标: vec[begin] >= vec[mid]
            swap(&vec[begin], &vec[mid]);
        // 再从begin 和 end位置选出一个大的值放在begin位置,
        // begin位置上保存这三个位置中间的值,后续依然可以选第一个元素为基准值划分函数 
    }

    // 采用三路划分的快速排序
    void quicksort(vector<int>& nums, int begin, int end)
    {
        median_select(nums, begin, end);

        int key = nums[begin];
        int left = begin;
        int right = end;
        int cur = begin + 1;

        while(cur <= right)
        {
            if(nums[cur] < key)
            {
                swap(&nums[left], &nums[cur]);
                left++;
                cur++;
            }
            else if(nums[cur] > key)
            {
                swap(&nums[right], &nums[cur]);
                right--;
            }
            else if(nums[cur] == key)
                cur++;
        }

        if(begin < left - 1)
            quicksort(nums, begin, left - 1);
        if(right + 1 < end)
            quicksort(nums, right + 1, end);
    }

    vector<int> sortArray(vector<int>& nums) 
    {
        if(nums.size() > 1)
            quicksort(nums, 0, nums.size() - 1);
        return nums;
    }
};

2. 数组中的第K个最大元素(快速选择算法)

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

  • 题目描述:

给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。

请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。

你必须设计并实现时间复杂度为 O(n) 的算法解决此问题。

示例1 :

  • 输入:3, 2, 1, 5, 6, 4, k = 2

  • 输出:5
    示例2 :

  • 输入:3, 2, 3, 1, 2, 4, 5, 5, 6, k = 4

  • 输出:4
    提示:

  • 1 <= k <= nums.length <= 10^5

  • -10^4 <= numsi <= 10^4

  • 代码演示:
cpp 复制代码
class Solution {
public:
    void swap(int* a, int* b)
    {
    	int tmp = *a;
	    *a = *b;
	    *b = tmp;
    }

    // 使用三数取中法选择三者的中位数作为基准值并将其换到begin位置  
    void median_select(vector<int>& vec, int begin, int end)
    {
        int mid = begin + (end - begin) / 2;

        if (vec[begin] > vec[mid])           // 目标: vec[mid] >= vec[begin]
            swap(&vec[begin], &vec[mid]);
        if(vec[mid] > vec[end])              // 目标: vec[end] >= vec[mid]
            swap(&vec[mid], &vec[end]);      // 前两步,使vec[end]位置一定是最大值
        
        if(vec[begin] < vec[mid])            // 目标: vec[begin] >= vec[mid]
            swap(&vec[begin], &vec[mid]);
        // 再从begin 和 end位置选出一个大的值放在begin位置,
        // begin位置上保存这三个位置中间的值,后续依然可以选第一个元素为基准值划分函数 
    }

    // 采用三路划分的快速排序
    int quicksort(vector<int>& nums, int begin, int end, int k)
    {
        median_select(nums, begin, end);

        int key = nums[begin];
        int left = begin;
        int right = end;
        int cur = begin + 1;

        while(cur <= right)
        {
            if(nums[cur] < key)
            {
                swap(&nums[left], &nums[cur]);
                left++;
                cur++;
            }
            else if(nums[cur] > key)
            {
                swap(&nums[right], &nums[cur]);
                right--;
            }
            else if(nums[cur] == key)
                cur++;
        }

        int a = left - begin;      // 小于key的元素个数
        int b = right - left + 1;  // 等于key的元素个数
        int c = end - right;       // 大于key的元素个数

        if(c >= k)
            return quicksort(nums, right + 1, end, k);
        else if(b+c >= k)
            return key;
        else
            return quicksort(nums, begin, left - 1, k-b-c);
    }

    int findKthLargest(vector<int>& nums, int k) 
    {
        return quicksort(nums, 0, nums.size() - 1, k);
    }
};

3. 练习题:库存管理 III

LCR 159. 库存管理 III

  • 题目描述:

仓库管理员以数组 stock 形式记录商品库存表,其中 stocki 表示对应商品库存余量。请返回库存余量最少的 cnt 个商品余量,返回 顺序不限。

示例1 :

  • 输入:stock = 2, 5, 7, 4, cnt = 1

  • 输出:2
    示例2 :

  • 输入:stock = 0, 2, 3, 6, cnt = 2

  • 输出:0, 22, 0
    提示:

  • 0 <= cnt <= stock.length <= 10000

  • 0 <= stocki <= 10000

二、归并排序的习题讲解

1. 排序数组(模板题,归并排序的算法思路)

912. 排序数组

  • 题目描述:

给你一个整数数组 nums,请你将该数组升序排列。

你必须在 不使用任何内置函数 的情况下解决问题,时间复杂度为 O(nlog(n)),并且空间复杂度尽可能小。

示例 :

  • 输入:nums = 5,1,1,2,0,0

  • 输出:0,0,1,1,2,5

  • 解释:请注意,nums 的值不一定唯一。
    提示:

  • 1 <= nums.length <= 5 * 10^4

  • -5 * 10^4 <= numsi <= 5 * 10^4

  • 代码演示:
cpp 复制代码
class Solution {
public:
    // 归并排序
    void merge_sort(vector<int>& vec, int begin, int end, vector<int>& tmp)
    {
        int mid = begin + (end - begin) / 2;
        if (begin < mid)
            merge_sort(vec, begin, mid, tmp);
        if(mid + 1 < end)
            merge_sort(vec, mid + 1, end, tmp);

        int left1 = begin;
        int right1 = mid;
        int left2 = mid + 1;
        int right2 = end;
        int index = begin;
        while (left1 <= right1 && left2 <= right2)
        {
            if (vec[left1] <= vec[left2])
            {
                tmp[index] = vec[left1];
                index++, left1++;
            }
            else
            {
                tmp[index] = vec[left2];
                index++, left2++;
            }
        }
        while (left1 <= right1)
        {
            tmp[index] = vec[left1];
            index++, left1++;
        }
        while (left2 <= right2)
        {
            tmp[index] = vec[left2];
            index++, left2++;
        }

        while (begin <= end)
        {
            vec[begin] = tmp[begin];
            begin++;
        }
    }

    vector<int> sortArray(vector<int>& nums) 
    {
        if(nums.size() > 1)
        {
            vector<int> tmp(nums.size());
            merge_sort(nums, 0, nums.size()-1, tmp);
        }
        return nums;
    }
};

2. 数组中的逆序对(hard)

LCR 170. 交易逆序对的总数

  • 题目描述:

在股票交易中,如果前一天的股价高于后一天的股价,则可以认为存在一个「交易逆序对」。请设计一个程序,输入一段时间内的股票交易记录 record,返回其中存在的「交易逆序对」总数。

示例 1:

  • 输入:record = 9, 7, 5, 4, 6

  • 输出:8

  • 解释:交易中的逆序对为 (9, 7), (9, 5), (9, 4), (9, 6), (7, 5), (7, 4), (7, 6), (5, 4)。
    提示:

  • 0 <= record.length <= 50000


cpp 复制代码
class Solution {
public:
    // 归并排序 增加第 5 个参数:count_total(统计逆序对的个数)
    void merge_sort(vector<int>& arr, int begin, int end, vector<int>& tmp, long long *count_total)
    {
        int mid = begin + (end - begin) / 2;
        if (begin < mid)
            merge_sort(arr, begin, mid, tmp, count_total);
        if (mid + 1 < end)
            merge_sort(arr, mid + 1, end, tmp, count_total);
        int left1 = begin;
        int right1 = mid;
        int left2 = mid + 1;
        int right2 = end;
        int n = begin;
        while (left1 <= right1 && left2 <= right2)
        {
            if (arr[left1] <= arr[left2])
            {
                tmp[n++] = arr[left1++];
            }
            else  // 在归并排序的过程中,统计满足条件的逆序对个数
            {
                *count_total += right1 - left1 + 1; 
                tmp[n++] = arr[left2++];
            }
        }
        while (left1 <= right1)
        {
            tmp[n++] = arr[left1++];
        }
        while (left2 <= right2)
        {
            tmp[n++] = arr[left2++];
        }
        while (begin <= end)
        {
            arr[begin] = tmp[begin];
            begin++;
        }
    }

    int reversePairs(vector<int>& record) {
        long long count = 0;
        if(record.size() < 2)
            return 0;
        vector<int> tmp(record.size());
        merge_sort(record, 0, record.size() - 1, tmp, &count);

        return count;
    }
};

3. 计算右侧小于当前元素的个数

315. 计算右侧小于当前元素的个数

  • 题目描述:

给你一个整数数组 nums ,按要求返回一个新数组 counts 。数组 counts 有该性质: countsi 的值是 numsi 右侧小于 numsi 的元素的数量。

示例 1:

  • 输入:nums = 5, 2, 6, 1

  • 输出: 2, 1, 1, 0

  • 解释:
    5 的右侧有 2 个更小的元素 (2 和 1)
    2 的右侧仅有 1 个更小的元素 (1)
    6 的右侧有 1 个更小的元素 (1)
    1 的右侧有 0 个更小的元素

    示例 2:
    输入:nums = -1
    输出:0
    示例 3:
    输入:nums = -1, -1
    输出:0, 0
    提示:

  • 1 <= nums.length <= 10^5

  • -10^4 <= numsi <= 10^4

  • 代码示例:
cpp 复制代码
class Solution {
public:
    void merge_sort(vector<int>& arr, int begin, int end, vector<int>& tmp, vector<vector<int>>& index, vector<int>& ret)
    {
        int mid = begin + (end - begin) / 2;
        if (begin < mid)
            merge_sort(arr, begin, mid, tmp, index, ret);
        if (mid + 1 < end)
            merge_sort(arr, mid + 1, end, tmp, index, ret);
        int left1 = begin;
        int right1 = mid;
        int left2 = mid + 1;
        int right2 = end;
        int n = begin;
        while (left1 <= right1 && left2 <= right2)
        {
            if (arr[left1] > arr[left2])  // 在归并排序的过程中,统计满足条件的逆序对个数
            {
                index[1][n] = index[0][left1];
                ret[index[1][n]] += right2 - left2 + 1;  // 增加对应下标 的逆序对个数
                tmp[n++] = arr[left1++];
            }
            else if(arr[left1] <= arr[left2])
            {
                index[1][n] = index[0][left2];
                tmp[n++] = arr[left2++];
            }
        }
        while (left1 <= right1)
        {
            index[1][n] = index[0][left1];
            tmp[n++] = arr[left1++];
        }
        while (left2 <= right2)
        {
            index[1][n] = index[0][left2];
            tmp[n++] = arr[left2++];
        }
        while (begin <= end)
        {
            arr[begin] = tmp[begin];
            index[0][begin] = index[1][begin];
            begin++;
        }
    }

    vector<int> countSmaller(vector<int>& nums) 
    {
        if(nums.size() == 1)
            return vector<int>(1, 0);
        vector<int> ret(nums.size(), 0);
        // 要对 index数组进行归并排序,也需要一个临时数组,
        // 所以直接创建两个数组,index[1]作为下标绑定数组,index[2]作为临时数组
        vector<vector<int>> index(2, vector<int>(nums.size()));

        for(int i = 0;i < nums.size();i++)
        {
            index[0][i] = i;
        }

        vector<int> tmp(nums.size());
        merge_sort(nums, 0, nums.size() - 1, tmp, index, ret);

        return ret;
    }
};

相关推荐
Qres82125 分钟前
算法复键——树状数组
数据结构·算法
凡人叶枫34 分钟前
Effective C++ 条款41:了解隐式接口和编译期多态
java·开发语言·c++·effective c++
凡人叶枫39 分钟前
Effective C++ 条款42:了解 typename 的双重意义
java·linux·服务器·c++
小胖xiaopangss1 小时前
BRpc使用
c++·rpc
-森屿安年-1 小时前
63. 不同路径 II
c++·算法·动态规划
chase_my_dream1 小时前
Cartographer详细讲解
c++·人工智能·自动驾驶
森G1 小时前
75、服务器源码解析---------云视频服务项目
linux·服务器·网络·c++·qt
碧海蓝天20222 小时前
C++法则24:在标准 C++ 中,没有任何可移植的方式判断指针 T* pt 指向的内存位置是否已经 构造了对象,程序员必须手动跟踪哪些元素已构造。
java·开发语言·c++
charlie1145141912 小时前
现代C++指南:Lambda,让我们用另一种方式持有函数
开发语言·c++
森G2 小时前
77、线程池原理和实现------服务器源码解析----云视频服务项目
服务器·c++·qt