LeetCode 之 数组中的第K个最大元素(一)

题目要求给出数组中第 k 大的元素,我最直接的想法就是对整个数组进行排序,然后返回数组中第 k 大的元素。
在这里插入图片描述

1. 冒泡排序

要想完成对数组 nums 的排序,冒泡排序需要进行 nums.size() - 1 轮排序,因为最后一个元素无需进行排序。冒泡排序每轮排序都会将待排数组中的最大值放到数组的末尾。

因此,要想找到数组中第 k 大的元素,只需进行 k 轮排序。
冒泡排序可以从小到大排序(正序),也可以从大到小排序(倒序)。
下面分别介绍正序和倒序实现。

1.1 从小到大的冒泡排序

如果采用从小到大的原则进行排序,nums.size() - 1 为数组最后的元素,也是数组中的最大值。那么第 k 大的元素在数组中的第 nums.size() - k 位上。

冒泡排序采用双层循环,内层循环为一趟排序,外层循环为 k 趟排序。第 k 趟排序后,数组索引 nums.size() - k 上为数组中第 k 大的元素。
算法的时间复杂度为 O(n²) ,还有很大的优化空间,因此可以考虑时间复杂度较低的快速排序。

class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) {
        for(int last_position = nums.size() - 1; last_position >= nums.size() - k; last_position--)
        {
        	// 设置标准位,优化最坏情况(整个数组都是逆序的)
            int flag = 0;
            for(int i = 0; i < last_position; i++)
            {
                if(nums[i] > nums[i + 1])
                {
                    swap(nums[i], nums[i + 1]);
                    flag = 1;
                }
            }
            // 如果一趟排序后,没有发生任何元素交换,说明数组已经是有序的,直接退出即可
            if(flag == 0)   break;
        }
        // 返回第 k 大的元素
        return nums[nums.size() - k];
    }
};

1.2 从大到小的冒泡排序

采用从大到小的原则进行排序,那么数组的最大值就在数组的首位,数组索引为 0 。因此,第 k 大的元素在数组中的索引为 k - 1 。最后返回 nums[k - 1] 。

class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) {
        for(int last_position = nums.size() - 1; last_position >= k - 1; last_position--)
        {
            for(int j = 0; j < last_position; j++)
            {
                if(nums[j] < nums[j + 1])
                {
                    swap(nums[j], nums[j + 1]);
                }
            }
        }
        return nums[k - 1];
    }
};

2. 快速选择排序

快速选择排序是快速排序的升级版,快速排序需要递归 选取主元 左边的数组 和 选取主元 右边的数组。但是,我们的目的不是给整个数组排序,而是找到数组中第 k 大的元素,那么我们还需要同时递归主元的左右两侧的数组吗?

快速排序首先会选定主元,然后将主元放到数组中合适的位置,即主元左边的元素都小于主元,主元右边的元素都大于主元(采用从小到大排序)。一般用 partition 函数来完成放置主元的任务。调用 partition 函数后主元就被排好序了,之后不会再改变位置了。

利用这个特性,我们可以比较数组中第 k 大元素与主元位置间的关系。注意此处所说的数组中第 k 大元素指的是排好序后第 k 大元素所在的位置。
当二者相等时,说明主元就是数组中第 k 大元素,直接返回主元即可。
当主元位置大于数组中第 k 大元素时,说明当前的主元大于数组中第 k 大元素。因此可以从主元左边的数组中再选择一个元素作主元,并判断新的主元和数组中第 k 大元素之间的位置关系。
当主元位置小于数组中第 k 大元素时,说明当前的主元小于数组中第 k 大元素。因此可以从主元右边的数组中再选择一个元素作主元,并判断新的主元和数组中第 k 大元素之间的位置关系。
我们可以使用递归和非递归的方式实现快速选择排序,下面分别来介绍。

2.1 快速排序优化

一般情况下,我们默认选择数组的首元素为主元。
但是如果数组是逆序排列的话,那么算法将遇到最坏情况,时间复杂度为O(n²)。
因此我们需要选定一个更为合适的主元,可以用随机化的方式选择主元。

int idx = left + rand() % (right - left + 1);
swap(nums[idx], nums[left]);
int pivot = nums[left];

idx 为 [left,right] 之间的一个数,即数组中的一个数,但这个数是随机选取的。
每调用一次 partition 函数选取一次 idx,然后交换索引为 idx 和 left 的数组元素,
并将索引为 left 的数组元素赋给 pivot 主元。这样就实现了随机选取数组元素作为主元的目的。

2.2 快速选择排序(非递归)

从小到大排序的非递归实现

class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) {
        int left = 0;
        int right = nums.size() - 1;
        int n = nums.size();
        while(left < right)
        {
            int index = partition(nums, left, right);
            if(index == n - k)
            {
                return nums[n - k];
            }
            else if(index < n - k)
            {
                left = index + 1;
            }
            else if(index > n - k)
            {
                right = index - 1;
            }
        }
        return nums[n - k];
    }
    // 把主元放在合适的位置上
    int partition(vector<int>& nums, int left, int right)
    {
        int idx = left + rand() % (right - left + 1);
        swap(nums[idx], nums[left]);
        int pivot = nums[left]; 

        while(left < right)
        {
            while(left < right && nums[right] >= pivot)
            {
                right--;
            }
            nums[left] = nums[right];
            while(left < right && nums[left] <= pivot)
            {
                left++;
            }
            nums[right] = nums[left];
        }
        nums[left] = pivot;
        return left;
    }
};

从大到小排序的非递归实现

class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) {
        int left = 0;
        int right = nums.size() - 1;
        
        while(left < right)
        {
            int index = partition(nums, left, right);
            if(index == k - 1)
                return nums[k - 1];
            else if(index > k - 1)
            {

                right = index - 1;
            }
            else if(index < k - 1)
            {
                left = index + 1;
            }
        }
        return nums[k - 1];
    }
    int partition(vector<int>& nums, int left, int right)
    {
        int idx = left + rand() % (right - left + 1);
        swap(nums[idx], nums[left]);
        int pivot  = nums[left];
        while(left < right)
        {
            while(left < right && nums[right] <= pivot)
            {
                right--;
            }
            nums[left] = nums[right];
            while(left < right && nums[left] >= pivot)
            {
                left++;
            }
            nums[right] = nums[left];
        }
        nums[left] = pivot;
        return left;
    }
};

2.3 快速选择排序(递归)

下面给出从小到大排序的递归实现。

class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) {
        int n = nums.size();
        quicksort(nums, 0, n - 1, k);
        return nums[n - k];
    }

    void quicksort(vector<int>& nums, int left, int right, int k)
    {
        int n = nums.size();
        int index = partition(nums, left, right);
        if(left < right)
        {
            if(index == n - k)
            {
                return;
            }
            else if(index < n - k)
            {
                quicksort(nums, index + 1, right, k);
            }
            else if(index > n - k)
            {
                quicksort(nums, left, index - 1, k);
            }
        }
        return;
    }

    int partition(vector<int>& nums, int left, int right)
    {
        int idx = left + rand() % (right - left + 1);
        swap(nums[idx], nums[left]);
        int pivot = nums[left];

        while(left < right)
        {
            while(left < right && nums[right] >= pivot)
            {
                right--;
            }
            nums[left] = nums[right];
            while(left < right && nums[left] <= pivot)
            {
                left++;
            }
            nums[right] = nums[left];
        }
        nums[left] = pivot;
        return left;
    }
};

从大到小排序的递归实现

class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) {
        int left = 0;
        int right = nums.size() - 1;
        return quicksort(nums, left, right, k);
    }

    int quicksort(vector<int>& nums, int left, int right, int k)
    {
        int index = partition(nums, left, right);
        if(left < right)
        {
            if(index == k - 1)
                return nums[k - 1];
            else if(index > k - 1)
                return quicksort(nums, left, index - 1, k);
            else if(index < k - 1)
                return quicksort(nums, index + 1, right, k);
        } 
        return nums[k - 1];       
    }

    int partition(vector<int>& nums, int left, int right)
    {
        int idx = left + rand() % (right - left + 1);
        swap(nums[idx], nums[left]);
        int pivot  = nums[left];
        while(left < right)
        {
            while(left < right && nums[right] <= pivot)
            {
                right--;
            }
            nums[left] = nums[right];
            while(left < right && nums[left] >= pivot)
            {
                left++;
            }
            nums[right] = nums[left];
        }
        nums[left] = pivot;
        return left;
    }
};

参考链接

  1. c++/python3/java (1)大根堆–调库 (2)手撸大根堆 (3)快排3种写法
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值