刷穿LeetCode——Task14

这篇博客记录刷题第14天的学习体会与心得。

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

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

示例 1:

输入: [3,2,1,5,6,4] 和 k = 2
输出: 5

示例 2:

输入: [3,2,3,1,2,4,5,5,6] 和 k = 4
输出: 4

说明:

你可以假设 k 总是有效的,且 1 ≤ k ≤ 数组的长度。

分析:最简单的思路是先排序,然后取倒数第 k k k 个元素,所以这道题其实是在考排序,直接调用C++ sort.()函数也太对不起 medium级别了,下面我们用快速排序,以及最小顶堆取数组第K大元素。

  1. 快速排序[5]
  • 快速排序包括将原数组 a [ l ⋯ r ] a[l \cdots r] a[lr] 划分,递归调用快速排序对两个子数组 a [ l ⋯ q − 1 ] a[l \cdots q-1] a[lq1] a [ q + 1 ⋯ r ] a[q+1 \cdots r] a[q+1r] 排序这两步,保证 a [ l ⋯ q − 1 ] a[l \cdots q-1] a[lq1] 中每个元素小于等于 a [ q ] a[q] a[q],且 a [ q ] a[q] a[q]小于等于 a [ q + 1 ⋯ r ] a[q+1 \cdots r] a[q+1r] 中每个元素。
  • 当某次划分的 q q q 为倒数第 k k k 个下标,就直接返回 a [ q ] a[q] a[q];否则若 q q q 比倒数第 k k k 个下标小,就递归右区间,否则递归左区间。
  • 快速排序的性能和「划分」出的子数组的长度密切相关,我们可以引入随机化加速这个过程,用partition之前加一步随机,将时间复杂度的期望降到 O ( n ) O(n) O(n).
class Solution {
public:

    int quickSelect(vector<int>& a, int l, int r, int index) {
        int q = randomPartition(a, l, r);
        if (q == index) {
            return a[q];
        }
        else {
            return q < index ? quickSelect(a, q+1, r, index) : quickSelect(a, l, q-1, index);
        }
    } 

    inline int randomPartition(vector<int>& a, int l, int r) {   //划分子数组前随机取右边界
        int i = rand() % (r - l + 1) + 1;
        swap(a[i], a[r]);
        return partition(a, l, r);
    }

    inline int partition(vector<int>& a, int l, int r) {   //划分
        int x = a[r], i = l - 1;
        for (int j = l; j < r; ++j) {
            if (a[j] <= x) {       //将小于a[r]元素挪到其左边
                swap(a[++i], a[j]);
            }
        }
        swap(a[i+1], a[r]);   //a[r]位于i+1位置
        return i + 1;         //i+i即为分界点
    }
    int findKthLargest(vector<int>& nums, int k) {
        srand()设置rand()产生随机数时的随机数种子
        srand(time(0));
        return quickSelect(nums, 0, nums.size() - 1, nums.size() - k);
    }
};
  1. 堆排序

先回顾一下堆的知识[6,7]。

堆(Heap)是一种数据结构,具有以下的特点:
1)完全二叉树;
2)heap中存储的值是偏序;

Min-heap(最小堆/小顶堆/小根堆): 父节点的值小于或等于子节点的值; Max-heap(最大堆/大顶堆/大根堆): 父节点的值大于或等于子节点的值;

  • 堆的存储:
    一般都用数组来表示堆,i 结点的父结点下标就为 ( i – 1 ) / 2 (i–1)/2 (i1)/2。它的左右子结点下标分别为 2 i + 1 2i+1 2i+1 2 i + 2 2i +2 2i+2

  • 堆的创建
    以某个元素为根结点向下调整为堆

  • 堆的插入
    插入一个元素:先将结点插入到堆的尾部,再将该结点逐层向上调整,直到依旧构成一个堆。调整方法是看每一个子树是否符合大(小)根堆的特点,不符合的话则调整叶子和根的位置。

  • 堆的删除
    按定义,堆中每次只能删除根节点。为了便于重建堆,实际的操作是将堆中最后一个数据的值赋给根结点,然后再从根结点开始进行一次从上向下的调整,使其仍然满足最大(小)堆特点,本质是一个交换排序的“下沉”过程。

  • 堆排序
    以最大堆为例,堆建好之后堆中第0个数据(根节点)是堆中最大的数据。取出这个数据再执行下堆的删除操作。这样堆中第0个数据又是堆中最大的数据,重复上述步骤取出一个排序数组。

因此,这道题我们构建一个最大堆,做 k − 1 k-1 k1 次删除操作后堆顶元素就是第 k k k 大元素。

  • 不用语言自带堆操作,实现最大堆排序,官方代码如下:
class Solution {
public:
    void adjustmaxHeap(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]);
            adjustmaxHeap(a, largest, heapSize);
        }
    }

    void buildMaxHeap(vector<int>& a, int heapSize) {
        for (int i = heapSize / 2; i >= 0; --i) {
            adjustmaxHeap(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;
            adjustmaxHeap(nums, 0, heapSize);
        }
        return nums[0];
    }
};


  • C++中有优先队列priority_queue[4],或者STL堆操作[7],可以直接使用。这里利用priority_queue 实现最小堆排序如下:当最小堆中不足 k k k 个元素时,不经选择压入元素;此后若 nums 元素比最小堆堆顶元素大,则弹出堆顶并压入较大元素,如此筛选,最终最小堆中剩下的是前 k k k 个最大元素,此时堆顶元素即为所求,代码如下:
class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) {
        priority_queue<int, vector<int>, greater<int>> minHeap;   //greater对应从小到大出队列
        for (int num : nums) {
            if (minHeap.size() < k) {
                minHeap.push(num);
            } else if (minHeap.top() < num) {
                minHeap.pop();
                minHeap.push(num);
            }
        }
        return minHeap.top();
    }
};

217. 存在重复元素

给定一个整数数组,判断是否存在重复元素。

如果存在一值在数组中出现至少两次,函数返回 true 。如果数组中每个元素都不相同,则返回 false 。

示例 1:

输入: [1,2,3,1]
输出: true

示例 2:

输入: [1,2,3,4]
输出: false

示例 3:

输入: [1,1,1,3,3,4,3,2,4,2]
输出: true

解题思路:

  • 暴力搜索,将数组中每个元素减去其它元素,若存在差值 = 0,则返回 true。该方法时间复杂度为 O ( n 2 ) O(n^2) O(n2),会超时;
  • 先对数组排序,则重复元素必定会出现在相邻位置,对排序后数组进行扫描,如果存在相邻的两个元素相等,则返回 true。该方法时间复杂度取决于排序算法,利用C++ 自带的sort()函数[1]可以实现 O ( n l o g n ) O(n logn) O(nlogn)的时间复杂度;
class Solution {
public:
    bool containsDuplicate(vector<int>& nums) {
        if (nums.empty())
            return false;
        sort(nums.begin(), nums.end());
        for (int i = 0; i < nums.size()-1; i++) {
            if (nums[i] == nums[i+1]) {
                return true;
            }
        }
        return false;
    }
};
  • 利用哈希表,对于数组中每个元素,我们将它插入到哈希表中。如果插入一个元素时发现该元素已经存在于哈希表中,则说明存在重复的元素。哈希表查找可以实现 O ( n ) O(n) O(n) 的时间复杂度,考虑C++ undered_set是基于哈希表构建[2, 3],这里我们可以直接使用。
class Solution {
public:
    bool containsDuplicate(vector<int>& nums) {
        unordered_set<int> numSet;
        numSet.reserve(nums.size());             //预分配空间

        for (int i=0; i < nums.size(); ++i) {
       		//count( )方法--返回某个值元素个数
       		//也可以使用numSet.find(nums[i]) != s.end())判断
            if (numSet.count(nums[i])) return true;  
            numSet.insert(nums[i]);
        }
        return false;
    }
};

230. 二叉搜索树中第K小的元素

给定一个二叉搜索树,编写一个函数 kthSmallest 来查找其中第 k 个最小的元素。

说明: 你可以假设 k 总是有效的,1 ≤ k ≤ 二叉搜索树元素个数。

示例 1:

输入: root = [3,1,4,null,2], k = 1
  3
  / \
 1 4
  \
   2
输出: 1

示例 2:

输入: root = [5,3,6,2,4,null,null,1], k = 3
    5
    / \
   3 6
   / \
  2 4
  /
1
输出: 3
进阶:
如果二叉搜索树经常被修改(插入/删除操作)并且你需要频繁地查找第 k 小的值,你将如何优化 kthSmallest 函数?

参考

[1] C++ 中的sort()排序函数用法
[2] C++ set与map、unordered_map、unordered_set与哈希表
[3] 用哈希表封装unordered_map/set
[4] c++优先队列(priority_queue)用法详解
[5] 数组中的第K个最大元素【官方题解】
[6] 数据结构——堆(Heap)大根堆、小根堆
[7] 堆,堆的创建,插入,删除,建立
[8] C++ STL堆操作

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值