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

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

给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。
请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。

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

大顶堆

class Solution {
public:
	void heapSortHelper(vector<int>& nums, int start, int len) {
		int child = 2*start + 1;
		int tmp = nums[start];

		while(child < len) {
			if (child < len - 1 && nums[child] < nums[child + 1])
				child++;
			if(tmp >= nums[child])
				break;
			nums[start] = nums[child];
			start = child;
			child = 2*child+1;
		}
		nums[start] = tmp;
	}

    int findKthLargest(vector<int>& nums, int k) {
        int len = nums.size();
		if(len < k) return 0;
		int non_leaf_index = (len - 1)/2;
		int tmp;
		for(int i = non_leaf_index; i >= 0; i--) {
			heapSortHelper(nums, i, len);
		}
	
		// 执行 k - 1 次
		for(int i = len - 1; i >= len + 1 - k; i--) {
			tmp = nums[i];
			nums[i] = nums[0];
			nums[0] = tmp;
			heapSortHelper(nums, 0, i);
		}
		
		// 此时堆顶元素就是第k大元素
		return nums[0];
    }
};

时间复杂度:O(nlogn),建堆的时间代价是 O(n),删除的总代价是 O(klogn)
空间复杂度:O(1)

堆排序的源码

#include <iostream>
#include <vector>

using namespace std;

void printVector(vector<int> &v) {
	for(int i = 0; i < v.size(); i++) {
		cout << v[i] << " ";
	}
	cout << endl;
}

void heapSortHelper(vector<int>& nums, int start, int len) {
	int child = 2*start + 1;
	int tmp = nums[start];

	while(child < len) {
		if (child < len - 1 && nums[child] < nums[child + 1])
			child++;
		if(tmp >= nums[child])
			break;
		nums[start] = nums[child];
		start = child;
		child = 2*child+1;
	}
	nums[start] = tmp;
}

void heapSort(vector<int>& nums) {
	int len = nums.size();
	int non_leaf_index = (len - 1)/2;
	int tmp;
	for(int i = non_leaf_index; i >= 0; i--) {
		heapSortHelper(nums, i, len);
	}
	printVector(nums);
	
	for(int i = len - 1; i > 0; i--) {
		tmp = nums[i];
		nums[i] = nums[0];
		nums[0] = tmp;
		heapSortHelper(nums, 0, i);
	}
	printVector(nums);
}

int main ()
{
	vector<int> nums = {5,4,2,7,1,9,0,3,4};
	heapSort(nums);
	return 0;
}

引入随机化的快排做法(参考别人)

class Solution {
public:
    int quickSelect(vector<int>& a, int l, int r, int index) {
        int q;
		// l = 3, r = 5 的时候
		// i 的取值范围是 1,2,3
		int i = rand() % (r - l + 1) + l;
        swap(a[i], a[r]);
        q = partition(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 partition(vector<int>& a, int l, int r) {
        int x = a[r], i = l;
        for (int j = l; j < r; j++) {
            if (a[j] <= x) {
                swap(a[i], a[j]);
				i++;
            }
        }
        swap(a[i + 1], a[r]);
        return i + 1;
    }

    int findKthLargest(vector<int>& nums, int k) {
		// srand,随机数发生器的初始化函数
        srand(time(0));

		// 传入 第一个位置和最后一个位置
		// 传入 期望的位置,对于长度为 len 的升序数组的倒数第k个位置存储的就是数组第k大元素
		// 倒数第 1 个 len-1
		// 倒数第 k 个 len-k
        return quickSelect(nums, 0, nums.size() - 1, nums.size() - k);
    }
};

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

只要某次划分的 q 为倒数第 k 个下标时,就已经找到了答案
只需要确保 a[start, end]中,a[start, q-1] 所有元素比 a[q]小,a[q+1, end] 所有元素比 a[q] 大

在分解的过程当中,对子数组进行划分,如果划分得到的 q 正好就是需要的下标,直接返回 a[q];
否则,如果 q 比目标下标小,就递归右子区间,否则递归左子区间。
这样就可以把原来递归两个区间变成只递归一个区间,提高了时间效率。
这就是「快速选择」算法。

快速排序的性能和「划分」出的子数组的长度密切相关。
直观地理解如果每次规模为 n 的问题都划分成 1 和 n - 1,
每次递归的时候又向 n - 1 的集合中递归,这种情况是最坏的,时间代价是 O(n^2)。
引入随机化来加速这个过程,它的时间代价的期望是 O(n)

上文的若干接口

srand(time(0)); //先设置种子
rand(); //产生随机数
srand是种下随机种子数,每回种下的种子不一样,用rand得到的随机数就不一样。
为了每回种下一个不一样的种子,选用time(0),time(0)是得到当前时时间值(每时每刻时间不一样)。

解析

inline int partition(vector<int>& a, int l, int r) {
    int x = a[r], i = l;
	// 这不就是个快慢指针嘛
    for (int j = l; j < r; ++j) {
        if (a[j] <= x) {
            swap(a[i], a[j]);
            i++;
        }
    }
    swap(a[i], a[r]);
    return i;
}

l r
2 9 4 7 3 6 5
0 1 2 3 4 5 6
i
j

j = 0
2 9 4 7 3 6 5
0 1 2 3 4 5 6
i

j = 1
2 9 4 7 3 6 5
0 1 2 3 4 5 6
i

j = 2

2 4 9 7 3 6 5
0 1 2 3 4 5 6
i

  j = 3

2 4 9 7 3 6 5
0 1 2 3 4 5 6
i

    j = 4

2 4 3 7 9 6 5
0 1 2 3 4 5 6
i

      j = 5

2 4 3 7 9 6 5
0 1 2 3 4 5 6
i

最后
j = 4
2 4 3 5 9 6 7
0 1 2 3 4 5 6
i
返回 i = 3
i 左边的数比 i 的小
i 右边的数比 i 的大

本人重写
3 2 1 5 6 4
l r
3 2 1 5 6 4
l r
3 2 1 5 6 4
l r
3 2 1 5 6 4
l r
3 2 1 6 5 4
lr
3 2 1 6 5 4
r l

1 2
lr
1 2
r l

2 1
lr
2 1
l
1 2
l

2 4 3 5 9 6 7
l r
2 4 3 5 9 6 7
l r
2 4 3 5 9 6 7
l r
2 4 3 5 9 6 7
l r
2 4 3 5 9 6 7
l r
2 4 3 5 6 9 7
lr
2 4 3 5 6 9 7
r l
2 4 3 5 6 7 9
r l

inline int partition(vector<int>& a, int l, int r) {
	int index = r;
	r--;
	while(l <= r) {
		if (a[l] > a[index]) {
			swap(a[l],a[r]);
			r--;
		} else {
			l++;
		}
	}
	swap(a[l],a[index]);
    return l;
}

最终

class Solution {
public:
	// 《算法导论》9.2:期望为线性的选择算法
    int quickSelect(vector<int>& a, int l, int r, int index) {
        int i = rand() % (r - l + 1) + l;
        swap(a[i], a[r]);

        int q = partition(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 partition(vector<int>& a, int l, int r) {
        int index = r;
        r--;
        while(l <= r) {
            if (a[l] > a[index]) {
                swap(a[l],a[r]);
                r--;
            } else {
                l++;
            }
        }
        swap(a[l],a[index]);
        return l;
    }

    int findKthLargest(vector<int>& nums, int k) {
        srand(time(0));
		// 传入 第一个位置和最后一个位置
		// 传入 期望的位置,对于长度为 len 的升序数组的倒数第k个位置存储的就是数组第k大元素
		// 倒数第 1 个 len-1
		// 倒数第 k 个 len-k
        return quickSelect(nums, 0, nums.size() - 1, nums.size() - k);
    }
};

又手敲了一遍降序

这个更好理解

class Solution {
public:
	int randomSortHelper(vector<int>& nums, int start, int end) {
		int new_end = end - 1;
		while(start <= new_end) {
			if(nums[start] >= nums[end])
				start++;
			else {
				swap(nums[start], nums[new_end]);
				new_end--;
			}
		}
		
		swap(nums[start], nums[end]);
		return start;
	}

	// 降序查找,找index位置的值
	int randomSort(vector<int>& nums, int start, int end, int index) {
		int tmp = rand() % (end - start + 1) + start;
		swap(nums[tmp], nums[end]);
		int new_index = randomSortHelper(nums, start, end);
		
		// 这里是if不是while
		if(new_index != index) {
			if(new_index > index) {
				new_index = randomSort(nums, start, new_index - 1, index);
			} else {
				new_index = randomSort(nums, new_index + 1, end, index);
			}
		}
		
		return nums[index];
	}

    int findKthLargest(vector<int>& nums, int k) {
		srand(time(0));
		int len = nums.size();
		if(len < k)
			return 0;
		// 第k大的位置位于降序全排序数组的k-1位置
		// 因为0位置是最大元素
		return randomSort(nums, 0, len-1, k - 1);
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值