杨氏矩阵找第N大(小)的O(N)线性算法
来源:互联网 发布:2016年淘宝考试答案 编辑:程序博客网 时间:2024/05/23 11:45
杨氏矩阵:一个N*N的矩阵,它的每行每列都单调递增(或者宽松一些,单调不减),即a[i][j]<=a[i+1][j], a[i][j]<=a[i][j+1]。
遇到的两道面试题:
1. 输出杨氏矩阵中最小的N个数。
2. 两个升序数组A和B,长度都是N。从两个数组中分别取出一个数,相加得到一个和。求这N*N个和的前N小。
本质上第2题可以转化成第一题:把A[0]+B[k]的结果填入矩阵第一行,A[1]+B[k]的结果填入第二行……就得到一个杨氏矩阵。所以现在就只考虑第1题咯。
此题常见的一种做法:N路归并,用一个大小为N的堆,可以O(NlgN)得到解。但是利用杨氏矩阵的性质,这题是有O(N)的算法的……
(为了方便,把矩阵记为num)
首先根据杨氏矩阵的性质得到最关键的一点:前N小的数,肯定不大于矩阵中的num[sqrt(N)+1][sqrt(N)+1]。
(为了方便,令M = sqrt(N)+1)
因此要找的前N小的数,肯定在矩阵的前M行和前M列中。
所以,要找的是一个M*N的矩阵和另外一个(N-M)*M的矩阵。
这样的规模相当于M*(2N-M),相当于M*N
这样问题可以转化为:在M个长度为N的有序数组中,查找前N小的数。(*)
除了之前提到的方法,此题还有一个比较容易想到的方法:二分上界并计数。在INT_MIN~INT_MAX中二分第K大数的上界,每次对所有数组二分统计其中不大于上界的数的个数。总体的复杂度是O(R*M*lgN)。其中R是最坏情况下二分的次数。对于32位整数,最多二分32次,R=32。但是对于浮点数,需要的二分次数会增多。
在这个思路的基础上加以改进,把R改进为lgN,便可得到线性算法。
基本思路是,把二分时取数的范围从INT_MIN~INT_MAX缩小到这MN个数中。每次从这些数中选一个,来作为计数的上界。
选的方法:
每一轮计数时,先找出这M个数组的中位数,作为每个数组潜在的切分点,然后选择这些切分点的中位数作为上界。O(M)选出M个切分点,O(MlgM)把这些数排个序再选中间的,所以这一步可以O(MlgM)(注:无序数组选中位数有均摊O(M)的算法)。
但是为了每一轮都能缩小查找范围,所以对于每个数组,还要维护一个“潜在切分点的可能区间”,选择该数组的新切分点时,取这个区间的中位数。实际上就是对每个数组,维护一个二分切分点的过程信息。
这样一轮统计过后,某些偏大(或偏小)的切分点所在区间长度就需要减半。并且,至少有半数区间的长度是要减半的。(对于一个数组,不大于中位数的数的个数至少是一半。“不小于”同理)
由于所有数组一共有MN个数,因此在lg(MN)轮后,所有区间长度都会减到1。
整理一下复杂度。一共要进行lg(MN)次计数;每次计数需要O(MlgM)找切分点的中位数,以及O(MlgN)对一个数组计数。因此整体的复杂度是:
O(lg(MN)*(MlgM+MlgN)) = O(sqrt(N)*(lgN)^2) = o(sqrt(N)*sqrt(N)) = o(N)
ps. 所以这个算法复杂度其实是低于O(N)的.
(*)附代码:用以上算法实现在M个有序数组中,查找第K小的数。
转自:
http://wolf5x.cc/blog/algorithm/young-tableau-smallest-kth#comment-123
#include <iostream>#include <vector>#include <algorithm>using namespace std;// i, partition_point_lower_index_i, partition_point_upper_index_itypedef pair<int, pair<int,int> > PartRange;typedef vector<vector<int> > VVI;class PartComparator { const VVI &ary;public: PartComparator(const VVI &a): ary(a){} bool operator()(const PartRange &x, const PartRange &y) const { return ary[x.first][(x.second.first+x.second.second)/2] < ary[y.first][(y.second.first+y.second.second)/2]; }};// Get the count of numbers less than or equal to upperint getCount(VVI &num, int upper) { int ret = 0; for (int i = 0; i < num.size(); i++) { ret += upper_bound(num[i].begin(), num[i].end(), upper) - num[i].begin(); } return ret;}int chooseKthSmallest(VVI num, int k) { int n = num.size(); vector<PartRange> part(n); for (int i = 0; i < n; i++) { part[i] = make_pair(i, make_pair(0, num[i].size()-1)); } int ans = 1<<30; // INT_MAX; while(part.size() > 0) { // sort all the medians sort(part.begin(), part.end(), PartComparator(num)); // choose the median of medians int mid = part.size()/2; int upper = num[part[mid].first][(part[mid].second.first+part[mid].second.second)/2]; int count = getCount(num, upper); if (count >= k) { // update answer ans = min(ans, upper); // halve the median intervals of which the median is too large for(int i = 0; i < part.size(); i++) { int mid = (part[i].second.first+part[i].second.second)/2; if (num[part[i].first][mid] >= upper) { part[i].second.second = mid-1; } } } else { // halve the median intervals of which the median is too small for (int i = 0; i < part.size(); i++) { int mid = (part[i].second.first+part[i].second.second)/2; if (num[part[i].first][mid] <= upper) { part[i].second.first = mid+1; } } } // remove the empty median intervals for (int i = part.size()-1; i >= 0; i--) { if (part[i].second.first > part[i].second.second) { swap(part[i], part[part.size()-1]); part.erase(part.end()-1); } } } return ans;}int main() { int v[][3] = {{1,2,3},{2,3,4},{3,4,5}}; vector<int> vec0(v[0],v[0]+3); vector<int> vec1(v[1],v[1]+3); vector<int> vec2(v[2],v[2]+3); int arr[] = {1,2,2,3,4,4,4,4,5,6,7,8,9,9,10}; vector<int> vec3(arr, arr+sizeof(arr)/sizeof(int)); VVI num; num.push_back(vec0); num.push_back(vec1); num.push_back(vec2); int up = distance(vec3.begin(), upper_bound(vec3.begin(), vec3.end(), 11)); int low = distance(vec3.begin(), lower_bound(vec3.begin(), vec3.end(), 11)); int res = chooseKthSmallest(num, 3); return 0;}
- 杨氏矩阵找第N大(小)的O(N)线性算法
- 找第N大的数(线性算法)
- O(n)线性时间找第K大,中位数
- O(n)查找第k小(大)的数
- 找中位数O(n)算法
- 找第K小的数(O(N))(运用随机思想)
- 已知数组A[1...n] ,确定第K小元素 算法的时间复杂度O(n)
- 寻找第k小的元素或者第k大的元素 -- O(n)
- 第九章中位数和顺序统计学 之 “寻找第i小元素之最坏情况线性时间的选择 最坏运行时间就为O(n)算法”
- 分治算法 求第k小元素 O(n) < O(nlog2^n)
- 寻找第K小元素O(N)算法
- 第K小的数 快速排序 选择前K大O(n)
- 矩阵乘法求线性递推式的第n项
- 数组中查找第k小元素的复杂度为O(n)的算法
- O(n)算法得到数组中任意第k大的数字
- 《算法导论》线性时间O(n)排序
- 找n个数字中第k小的元素
- 排序N个比N^7小的数,要求的算法是O(n)
- Maven的工程依赖和JAR包依赖
- Linux内核架构 Linux设备驱动 Linux电源管理 Linux音频子系统 Linux中断子系统 Linux时间管理系统 Linux输入子系统
- 文章推荐
- PHP内核API
- StarUML使用说明-指导手册
- 杨氏矩阵找第N大(小)的O(N)线性算法
- Leetcode_decode-ways
- 开源项目开发基本知识
- iReport专题学习之父报表 08
- 二叉树应用总结
- AsyncTask实例分析
- HBase架构
- 如何去掉一个文件中重复的数据行
- 健身感悟