目录
背景:
给出n个元素,在O(n)的时间内找出第i小的元素。
一,测试框架
和排序算法常见排序算法_csuzhucong的博客-CSDN博客一文类似:
#include <iostream>
using namespace std;
template<typename T>
bool cmp(T a, T b)
{
return a < b;
}
template<typename T>
void exchange(T* a, T* b)
{
T tmp = *a;
*a = *b;
*b = tmp;
}
template<typename T>
T Select(T* arr, int len, int ith)
{
return ......
}
int main()
{
int arr[] = { 1,4,2,6,3,8,9,7 };
cout << Select(arr, sizeof(arr) / sizeof(int), 3);
return 0;
}
二,平均运行时间为O(n)的算法
和快速排序类似,我们每次选择一个值,把大于它的和小于它的分开成两拨,然后只需要在其中一拨里面继续寻找即可。
template<typename T>
int Partition(T* arr, int start, int end) //[start,end]闭区间
{
T x = arr[end];
int i = start - 1;
for (int j = start; j < end; j++) {
if (cmp(arr[j], x)) {
exchange(arr + ++i, arr + j);
}
}
exchange(arr + ++i, arr + end);
return i;
}
template<typename T>
T Select(T* arr, int len, int ith)
{
if (len <= 1)return arr[0];
int part = Partition(arr, 0, len - 1);
if (ith == part)return arr[ith];
if (ith < part)return Select(arr, part, ith);
return Select(arr + part + 1, len - part - 1, ith - part - 1);
}
平均时间为O(n),最坏时间为O(n^2)
三,最坏运行时间为O(n)的算法
算法思路:
分析:
可以证明至少有3n/10-6个元素大于x,也至少有3n/10-6个元素小于x,所以用x作为划分数组的分界值就一定很均衡。
时间复杂度:
T(n)= T(n/5) + T(7n/10+6) + O(n)
可以推算出,T(n)= O(n)
代码:
#include <iostream>
#include<algorithm>
using namespace std;
template<typename T>
bool cmp(T a, T b)
{
return a < b;
}
template<typename T>
void exchange(T* a, T* b)
{
T tmp = *a;
*a = *b;
*b = tmp;
}
template<typename T>
int Partition(T* arr, int start, int end, T x) //[start,end]闭区间
{
int i = start - 1;
for (int j = start; j < end; j++) {
if (arr[j] == x)exchange(arr + j, arr + end);
if (cmp(arr[j], x)) {
exchange(arr + ++i, arr + j);
}
}
exchange(arr + ++i, arr + end);
return i;
}
template<typename T>
T Select(T* arr, int len, int ith);
template<typename T>
T SelectPart(T* arr, int len)
{
if (len < 5)return arr[0];
int z = len / 5;
T* p = new T[z];
for (int i = 0; i < z; i++) {
sort(arr + i * 5, arr + i * 5 + 5, cmp<T>);
p[i] = arr[i * 5 + 2];
}
return Select(p, z, z / 2);
}
template<typename T>
T Select(T* arr, int len, int ith)
{
if (len <= 1)return arr[0];
T parti = SelectPart(arr, len);
int part = Partition(arr, 0, len - 1, parti);
if (ith == part)return arr[ith];
if (ith < part)return Select(arr, part, ith);
return Select(arr + part + 1, len - part - 1, ith - part - 1);
}
int main()
{
int arr[] = { 1,4,2,6,3,8,9,7 };
cout << Select(arr, sizeof(arr) / sizeof(int), 3);
return 0;
}
力扣 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 ≤ 数组的长度。
代码一:
template<typename T>
bool cmp(T a, T b)
{
return a < b;
}
template<typename T>
void exchange(T* a, T* b)
{
T tmp = *a;
*a = *b;
*b = tmp;
}
template<typename T>
int Partition(T* arr, int start, int end) //[start,end]闭区间
{
T x = arr[end];
int i = start - 1;
for (int j = start; j < end; j++) {
if (cmp(arr[j], x)) {
exchange(arr + ++i, arr + j);
}
}
exchange(arr + ++i, arr + end);
return i;
}
template<typename T>
T Select(T* arr, int len, int ith)
{
if (len <= 1)return arr[0];
int part = Partition(arr, 0, len - 1);
if (ith == part)return arr[ith];
if (ith < part)return Select(arr, part, ith);
return Select(arr + part + 1, len - part - 1, ith - part - 1);
}
template<typename T>
T* vecToArr(vector<T>& v)
{
return v.data();
}
class Solution {
public:
int findKthLargest(vector<int>& nums, int k) {
return Select(vecToArr(nums),nums.size(),nums.size()-k);
}
};
56ms AC
代码二:
template<typename T>
bool cmp(T a, T b)
{
return a < b;
}
template<typename T>
void exchange(T* a, T* b)
{
T tmp = *a;
*a = *b;
*b = tmp;
}
template<typename T>
int Partition(T* arr, int start, int end, T x) //[start,end]闭区间
{
int i = start - 1;
for (int j = start; j < end; j++) {
if (arr[j] == x)exchange(arr + j, arr + end);
if (cmp(arr[j], x)) {
exchange(arr + ++i, arr + j);
}
}
exchange(arr + ++i, arr + end);
return i;
}
template<typename T>
T Select(T* arr, int len, int ith);
template<typename T>
T SelectPart(T* arr, int len)
{
if (len < 5)return arr[0];
int z = len / 5;
T* p = new T[z];
for (int i = 0; i < z; i++) {
sort(arr + i * 5, arr + i * 5 + 5, cmp<T>);
p[i] = arr[i * 5 + 2];
}
return Select(p, z, z / 2);
}
template<typename T>
T Select(T* arr, int len, int ith)
{
if (len <= 1)return arr[0];
T parti = SelectPart(arr, len);
int part = Partition(arr, 0, len - 1, parti);
if (ith == part)return arr[ith];
if (ith < part)return Select(arr, part, ith);
return Select(arr + part + 1, len - part - 1, ith - part - 1);
}
template<typename T>
T* vecToArr(vector<T>& v)
{
T* p = new T[v.size()];
for (int i = 0; i < v.size(); i++)p[i] = v[i];
return p;
}
class Solution {
public:
int findKthLargest(vector<int>& nums, int k) {
return Select(vecToArr(nums),nums.size(),nums.size()-k);
}
};
8ms AC
力扣 2387. 行排序矩阵的中位数
给定一个包含 奇数 个整数的 m x n
矩阵 grid
,其中每一行按 非递减 的顺序排序,返回矩阵的 中位数。
你必须以 O(m * log(n))
的时间复杂度来解决这个问题。
示例 1:
输入: grid = [[1,1,2],[2,3,3],[1,3,4]] 输出: 2 解释: 矩阵的元素按顺序排列为 1,1,1,2,2,3,3,3,4。中位数是 2。
示例 2:
输入: grid = [[1,1,3,3,4]] 输出: 3 解释: 矩阵的元素按顺序排列为 1,1,3,3,4。中位数是 3。
提示:
m == grid.length
n == grid[i].length
1 <= m, n <= 500
m
和n
都是奇数。1 <= grid[i][j] <= 106
grid[i]
按非递减顺序排序
思路:
(1)前言:我们需要两次用到线性时间选择算法,这个算法请自行学完。
(2)首先,学过线性时间选择算法的,很容易发现,本题的矩阵内的数据关系,以及求解的问题和时间复杂度要求,都很像线性时间选择算法。
于是不难想到,本题的解法框架和线性时间选择算法是差不多的。
(3)其次,我们照葫芦画瓢,看看如何描述这个递归问题。
对于m个长为n的有序数组,我们取每个数组的中位数,再用线性时间选择算法求所有中位数的中位数,总耗时O(m)
然后...然后我们发现这个思路是错的,总时间复杂度不达标
(4)然后,我们灵活变通,还是利用类似的思路
我们把每一行的数据均匀分为d段,得到一个m行d列的新矩阵,利用编码技巧,我们不做拷贝操作,这一步耗时为0
然后递归找到这个新矩阵的中位数,再用这个数作为分界线,每一行的d段中最多只留下一段,其他段都可以确定全部大于中位数或全部小于中位数,
被丢弃的这些段,刚好一半小于中位数一半大于中位数。
于是,问题化作子问题:剩下的m行,每行m/d个数,找出这些数的中位数。
(5)时间复杂度
f(m,n)=f(m,d)+f(m,n/d)
取d=根号n,则f(m,n)=m log n
(6)更严谨的表述
其实上面的表述不太严谨,应该把问题描述成求m行,每行n个数,求所有数中第k大的数。
按照k/(mn)的比例,递归地在m行d列的新矩阵中找到对应比例的数,经过删减之后,化为子问题:剩下的m行,每行m/d个数,找出这些数中第k'大的数
代码分为4个部分:
(1)线性时间选择算法
(2)BsearchLowerBound类
功能类似于c++的lower_bound,但是我这里有个参数step
即在闭区间[low,high]中,从low开始,每隔step取一个数,得到的数组。
为了性能达到要求,我们只能用编码技巧去实现,而不能把这个数组拷贝出来。
(3)BsearchUperBound类
类似于BsearchLowerBound类
(4)功能主体:Solution类
首先matrixMedian是对外的接口,通过简单的转换,转换为更通用的问题,即matrixKth函数。
matrixKth函数是一个迭代式的实现,每次通过freshRange去缩小搜索范围,这块比较难,需要保证每一次缩小都是有效的。
反复缩小之后,要么得到一些全都相同的数,要么缩小后的总范围小于4m,此时可以直接转化成数组,用线性时间选择算法来求解。
PS:这个4 是纯粹的常数,和m、n、grid[i][j]的值范围都无关。
PS:和线性时间选择算法类型,我这里的freshRange函数也是递归调用matrixKth。
template<typename T>
bool cmp(T a, T b)
{
return a < b;
}
template<typename T>
void exchange(T* a, T* b)
{
T tmp = *a;
*a = *b;
*b = tmp;
}
template<typename T>
int Partition(T* arr, int start, int end, T x) //[start,end]闭区间
{
int i = start - 1;
for (int j = start; j < end; j++) {
if (arr[j] == x)exchange(arr + j, arr + end);
if (cmp(arr[j], x)) {
exchange(arr + ++i, arr + j);
}
}
exchange(arr + ++i, arr + end);
return i;
}
template<typename T>
T Select(T* arr, int len, int ith);
template<typename T>
T SelectPart(T* arr, int len)
{
if (len < 5)return arr[0];
int z = len / 5;
T* p = new T[z];
for (int i = 0; i < z; i++) {
sort(arr + i * 5, arr + i * 5 + 5, cmp<T>);
p[i] = arr[i * 5 + 2];
}
return Select(p, z, z / 2);
}
template<typename T>
T Select(T* arr, int len, int ith)
{
if (len <= 1)return arr[0];
T parti = SelectPart(arr, len);
int part = Partition(arr, 0, len - 1, parti);
if (ith == part)return arr[ith];
if (ith < part)return Select(arr, part, ith);
return Select(arr + part + 1, len - part - 1, ith - part - 1);
}
template<typename T>
T* vecToArr(vector<T>& v)
{
T* p = new T[v.size()];
for (int i = 0; i < v.size(); i++)p[i] = v[i];
return p;
}
class BsearchLowerBound
{
public:
BsearchLowerBound(vector<int>&v) :v{ v } {}
int lower_bound(int low, int high, int step, int x)
{
this->step = step;
target = x;
return find(low, high);
}
int find(int low, int high)
{
if (!isOk(high))return high + step;
if (isOk(low))return low;
int mid;
while (high - low > step) {
mid = (high - low) / step / 2 * step + low;
if (isOk(mid))high = mid;
else low = mid;
}
return high;
}
vector<int>&v;
int target;
int step;
virtual bool isOk(int x) const //若isOk(x)且!isOk(y)则必有y<x
{
return v[x] >= target;
}
};
class BsearchUperBound
{
public:
BsearchUperBound(vector<int>&v) :v{ v } {}
int uper_bound(int low, int high, int step, int x)
{
this->step = step;
target = x;
return find(low, high);
}
int find(int low, int high)
{
if (!isOk(high))return high + step;
if (isOk(low))return low;
int mid;
while (high - low > step) {
mid = (high - low) / step / 2 * step + low;
if (isOk(mid))high = mid;
else low = mid;
}
return high;
}
vector<int>&v;
int target;
int step;
virtual bool isOk(int x) const //若isOk(x)且!isOk(y)则必有y<x
{
return v[x] > target;
}
virtual int getGap(int) {
return step;
}
};
class Solution {
public:
int matrixMedian(vector<vector<int>>& grid) {
m = grid.size();
vector<int>low(m, 0);
vector<int>high(m, grid[0].size() - 1);
int k = (m*grid[0].size() + 1) / 2;
return matrixKth(grid, low, high, 1, k);
}
int m;
//求第k大的数,每一行的范围是[low,high]
int matrixKth(vector<vector<int>>& grid, vector<int>low, vector<int>high, int step, int k) {
int s = 0, mins = INT_MAX, maxs = 0;
for (int i = 0; i < m; i++) {
if (low[i] > high[i])continue;
s += (high[i] - low[i]) / step + 1;
mins = min(mins, grid[i][low[i]]);
maxs = max(maxs, grid[i][low[i] + (high[i] - low[i]) / step * step]);
}
if (mins == maxs)return mins;
if (s <= m * 4)return matrixKthEndStep(grid, low, high, step, k);
freshRange(grid, low, high, step, s, k);
return matrixKth(grid, low, high, step, k);
}
//matrixKth的迭代停止场景
int matrixKthEndStep(vector<vector<int>>& grid, vector<int>&low, vector<int>&high, int step, int k) {
vector<int>v;
for (int i = 0; i < m; i++) {
for (int j = low[i]; j <= high[i]; j += step)v.push_back(grid[i][j]);
}
return findKthLargest(v, k);
}
void freshRange(vector<vector<int>>& grid, vector<int>&low, vector<int>&high, int step, int s, int &k) {
int d = sqrt(s / m);
int parti1 = matrixKth(grid, low, high, step*d, max(k / d - m, 1));
int parti2 = matrixKth(grid, low, high, step*d, min(k / d + m, s / d));
int s1 = 0, s2 = 0, s3 = 0;
vector<int>low2 = low;
vector<int>low3 = high;
for (int i = 0; i < m; i++) {
if (low[i] > high[i])continue;
high[i] = low[i] + (high[i] - low[i]) / step * step;
low2[i] = BsearchLowerBound(grid[i]).lower_bound(low[i], high[i], step, parti2);
low3[i] = BsearchUperBound(grid[i]).uper_bound(low[i], high[i], step, parti1) - step;
s1 += (low2[i] - low[i]) / step;
s2 += (low3[i] - low2[i]) / step + 1;
s3 += (high[i] - low3[i]) / step;
}
low = low2, high = low3;
k -= s3;
}
//求数组中的第K个最大元素
int findKthLargest(vector<int>& nums, int k) {
return Select(vecToArr(nums), nums.size(), nums.size() - k);
}
};