Leecode Hot100 刷题笔记本-优先队列(C++版)

210 阅读5分钟
  1. 215. 数组中的第K个最大元素 中等
  2. 253. 会议室 II 中等
  3. 347. 前 K 个高频元素 中等

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

Screen Shot 2023-08-31 at 6.15.53 PM.png

解法1: 快速排序

Screen Shot 2023-09-01 at 9.00.20 AM.png

class Solution {
public:
    int quickselect(vector<int> &nums, int l, int r, int k) {
        if (l == r)
            return nums[k];
        
        int partition = nums[l]; // 选择数组的第一个元素作为分区元素
        int i = l - 1, j = r + 1; // 初始化两个指针 i 和 j 分别指向数组的左右边界

        // 使用快速排序的思想,将数组分成两部分,使左边的元素都小于分区元素,右边的元素都大于分区元素
        while (i < j) {
            // 从左往右找到一个大于等于分区元素的元素
            do i++; while (nums[i] < partition);
            
            // 从右往左找到一个小于等于分区元素的元素
            do j--; while (nums[j] > partition);
            
            // 如果 i < j,交换它们,确保左边的元素小于分区元素,右边的元素大于分区元素
            if (i < j)
                swap(nums[i], nums[j]);
        }

        // 如果 k 小于等于 j,说明第 k 大的元素在左半部分,递归调用 quickselect 来查找
        if (k <= j)
            return quickselect(nums, l, j, k);
        // 否则,第 k 大的元素在右半部分,递归调用 quickselect 来查找
        else
            return quickselect(nums, j + 1, r, k);
    

    int findKthLargest(vector<int> &nums, int k) {
        int n = nums.size();
        // 由于快速选择查找的是第 k 大的元素,所以传入 n - k 作为 k 参数
        return quickselect(nums, 0, n - 1, n - k);
    }
};

Screen Shot 2023-09-01 at 9.18.57 AM.png

解法2: 堆排序
  • 我们也可以使用堆排序来解决这个问题——建立一个大根堆,做 k−1 次删除操作后堆顶元素就是我们要找的答案。
  • 需要掌握大根堆的实现方法
  • 利用一个一维数组通过下标来实现一个大根堆,大根堆其实就是一棵完全二叉树. 对于任意一个位置 i 上的节点,他的父节点下标为 (i-1)/2 他的左孩子下标为 2i+1 , 他的右孩子下标为 2i+2 对于0位置上的节点,(0-1)/2就是0,因此0位置上的节点的根节点就是他自己大根堆就是要保证每一棵子树的根节点是这棵树上最大的
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; i >= 0; --i) {
            maxHeapify(a, i, heapSize);
        } 
    }

    int findKthLargest(vector<int>& nums, int k) {
        int heapSize = nums.size();
        // 构建最大堆
        buildMaxHeap(nums, heapSize);
        // 逐步找到第k大的元素
        for (int i = nums.size() - 1; i >= nums.size() - k + 1; --i) {
            // 将堆顶元素(当前最大元素)与数组末尾元素交换
            swap(nums[0], nums[i]);
            // 减小堆的大小
            --heapSize;
            // 对交换后的堆进行维护,保持最大堆的性质
            maxHeapify(nums, 0, heapSize);
        }
        // 返回第k大的元素,即堆顶元素
        return nums[0];
    }
};

Screen Shot 2023-09-01 at 9.37.41 AM.png

253. 会议室 II

题目描述

给你一个会议时间安排的数组 intervals ,每个会议时间都会包括开始和结束的时间 intervals[i] = [starti, endi] ,为避免会议冲突,同时要考虑充分利用会议室资源,请你计算至少需要多少间会议室,才能满足这些会议安排。

示例

输入:intervals = [[0,30],[5,10],[15,20]] 输出:2

输入:intervals = [[7,10],[2,4]] 输出:1

题目思路

这题可以理解为上下公交车问题,每一个人上下车的时间不同,在车上的时间不同,所以在本题中所对应的会议开始结束时间不同,会议时长不同。求的是在车上的最多的人数。

class Solution {
public:
    int minMeetingRooms(vector<vector<int>>& intervals) {
        if (intervals.empty())
            return 0; // 如果会议列表为空,直接返回0,因为不需要会议室

        vector<int> begin_time(intervals.size()); // 存储所有会议的开始时间
        vector<int> end_time(intervals.size());   // 存储所有会议的结束时间

        // 将会议的开始时间和结束时间分别提取出来
        for (int i = 0; i < intervals.size(); i++) {
            begin_time[i] = intervals[i][0];
            end_time[i] = intervals[i][1];
        }

        // 对会议的开始时间和结束时间进行升序排序
        sort(begin_time.begin(), begin_time.end());
        sort(end_time.begin(), end_time.end());

        int room = 0;      // 记录会议室数量
        int free_room = 0; // 记录当前可用的空余会议室数量
        int endnum = 0;    // 记录已结束的会议个数

        // 遍历会议的开始时间
        for (int i = 0; i < intervals.size(); i++) {
            int time = begin_time[i]; // 获取当前会议的开始时间

            // 处理当前时间前已结束的会议,如果有会议已经结束,则释放对应的会议室
            while (endnum < intervals.size() && end_time[endnum] < time + 1) {
                endnum++;
                free_room++;
            }

            if (free_room > 0) // 如果有空余会议室,借用一个空余会议室
                free_room--;
            else // 如果没有空余会议室,需要新开一个会议室
                room++;
        }

        return room; // 返回所需的最少会议室数量
    }
};

347. 前 K 个高频元素

Screen Shot 2023-09-01 at 10.57.41 AM.png

解法1: 堆

Screen Shot 2023-09-01 at 10.59.35 AM.png

class Solution {
public:
    // 自定义比较函数 cmp,用于最大堆的排序
    static bool cmp(pair<int, int>& m, pair<int, int>& n) {
        return m.second > n.second; // 按照出现次数降序排序
    }

    vector<int> topKFrequent(vector<int>& nums, int k) {
        unordered_map<int, int> occurrences; // 用于统计数组中各个元素的出现次数
        for (auto& v : nums) {
            occurrences[v]++;
        }

        // 定义一个最大堆 priority_queue,其中 pair 的第一个元素代表数组的值,第二个元素代表了该值出现的次数
        // 使用自定义比较函数 cmp 来进行排序,以保证堆顶元素是出现次数最多的元素
        priority_queue<pair<int, int>, vector<pair<int, int>>, decltype(&cmp)> q(cmp);

        // 遍历哈希表中的元素
        for (auto& [num, count] : occurrences) {
            if (q.size() == k) {
                // 如果堆的大小已经达到了k,检查堆顶元素的出现次数
                // 如果当前元素的出现次数大于堆顶元素的出现次数,弹出堆顶元素,并将当前元素加入堆
                if (q.top().second < count) {
                    q.pop();
                    q.emplace(num, count);
                }
            } else {
                // 如果堆的大小还未达到k,直接将当前元素加入堆
                q.emplace(num, count);
            }
        }
        
        vector<int> ret;
        while (!q.empty()) {
            // 从堆中弹出元素,加入结果数组中
            ret.emplace_back(q.top().first);
            q.pop();
        }
        
        return ret; // 返回前k个高频元素组成的数组
    }
};

Screen Shot 2023-09-01 at 11.00.21 AM.png

解法2: 快速排序
class Solution {
public:
    // 自定义快速排序函数 qsort
    void qsort(vector<pair<int, int>>& v, int start, int end, vector<int>& ret, int k) {
        // 在[start, end]范围内随机选择一个数作为基准值
        int picked = rand() % (end - start + 1) + start;
        swap(v[picked], v[start]);

        int pivot = v[start].second; // 基准值的频率
        int index = start; // 指向当前大于等于基准值的元素的位置

        for (int i = start + 1; i <= end; i++) {
            // 使用双指针将不小于基准值的元素放到左侧,小于基准值的元素放到右侧
            if (v[i].second >= pivot) {
                swap(v[index + 1], v[i]);
                index++;
            }
        }
        swap(v[start], v[index]); // 将基准值放到正确的位置

        if (k <= index - start) {
            // 前 k 大的值在左侧的子数组里,递归处理左侧子数组
            qsort(v, start, index - 1, ret, k);
        } else {
            // 前 k 大的值等于左侧的子数组全部元素,加上右侧子数组中前 k - (index - start + 1) 大的值
            for (int i = start; i <= index; i++) {
                ret.push_back(v[i].first); // 添加到结果数组中
            }
            if (k > index - start + 1) {
                // 递归处理右侧子数组
                qsort(v, index + 1, end, ret, k - (index - start + 1));
            }
        }
    }

    vector<int> topKFrequent(vector<int>& nums, int k) {
        // 获取每个数字出现次数
        unordered_map<int, int> occurrences;
        for (auto& v: nums) {
            occurrences[v]++;
        }

        vector<pair<int, int>> values;
        for (auto& kv: occurrences) {
            values.push_back(kv); // 将数字和出现次数作为键值对存入values数组
        }

        vector<int> ret;
        qsort(values, 0, values.size() - 1, ret, k); // 使用自定义的快速排序函数排序
        return ret; // 返回前k个高频元素组成的数组
    }
};

Screen Shot 2023-09-01 at 11.47.47 AM.png