线性时间选择算法

目录

一,测试框架

二,平均运行时间为O(n)的算法

三,最坏运行时间为O(n)的算法

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

力扣 2387. 行排序矩阵的中位数


背景:

给出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);
	}
};
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值