版权声明:本文为博主原创文章,未经博主允许不得转载。 https://cloud.tencent.com/developer/article/1434620
挑战程序竞赛系列(18):3.1查找第k大的值
详细代码可以fork下Github上leetcode项目,不定期更新。
练习题如下:
- POJ 3685: Matrix
- POJ 3579:Median
POJ 3685: Matrix
可以发现每一列关于i是单调递增的,利用这个性质就可以二分了。这种关于查找第k大的二分模式还和我之前遇到的一般二分模式有所区别,可以观察它的while循环结构:
代码语言:javascript复制 while (rt - lf > 1){
long mid = (rt lf) / 2;
if (check(mid)) rt = mid;
else lf = mid;
}
System.out.println(rt);
嘿,这样就能找到第k大的值了,神奇。简单说明一下,因为lf和rt只会赋值成mid,所以不管抛弃左半部分还是右半部分,抛弃的都是lf-1和rt 1,当遇到<m
的情况,lf = mid,表面lf-1的值都不可能是第m小的值,而>=m
的情况比较特殊,在rt 1的右边也不可能是第m小的值,在rt处能够满足>=m
。这说明,mid还在lf,rt范围内,接着迭代,直到rt - lf == 1的情况,为什么不是到rt == lf的情况呢?
没有必要,因为我们知道,能够使得lf = mid的情况,是check(mid)过了,而在check的返回是cnt<m
的情况,所以可以这么理解:
在check(lf)处时,已经是
<m
的情况,所以没必要把真正的rt上界更新到lf。
另外一点也可以帮助理解这个循环式:
起码第m小的元素可能存在重复的情况,所以我们真正关注的在于
<m
的情况,具体有>=m
的个数是多少并不关心,只要符合>=m
,这个第m小的数总能被求出。
或者可以这么理解:
找到一个区间l, l 1,满足check(l) 是 < m 的情况,而check(l 1)时,则是>=m的情况,那么自然地,这个第m小的值就是l 1了。
说明这值一定存在,否则check的累加值怎么会改变呢?
代码如下:
代码语言:javascript复制 static int n;
static long m;
public static void main(String[] args) throws IOException {
Scanner in = new Scanner(System.in);
int N = in.nextInt();
while (N-->0){
n = in.nextInt();
m = in.nextLong();
long lf = -25000000001l;
long rt = 25000000001l;
while (rt - lf > 1){
long mid = (rt lf) / 2;
if (check(mid)) rt = mid;
else lf = mid;
}
System.out.println(rt);
}
in.close();
}
//注意,需要改成long而不是int,否则在计算时,会溢出,导致WA
public static long f(long i, long j){
return i * i 100000 * i j * j - 100000 * j i * j;
}
public static boolean check(long mid){
long cnt = 0;
for (int j = 1; j <= n; j){
cnt = binarySearch(j, mid);
}
return cnt >= m;
}
public static long binarySearch(int j, long key){
int lf = 1, rt = n;
while (lf < rt){
int mid = lf (rt - lf 1) / 2;
if (f(mid, j) > key) rt = mid - 1;
else lf = mid;
}
if (f(lf, j) <= key) return lf;
return 0;
}
POJ 3579:Median
有了上一题的基础,这道题就不难解决了,但没想到用Scanner居然会超时,在网上看到了一种解决方案,终于过了。
首先能想到的是对原数组进行排序,之所以排序,是因为好统计<=mid
的次数,而且很直观的一个想法是,一旦排序,长度为1的间隔一定比长度为2的间隔来得小,而这些值都是我们求mid需要考虑的,有序性能很好的加快搜索。
代码如下:
代码语言:javascript复制 static int[] nums;
static int N;
static int C;
public static void main(String[] args) throws IOException {
StreamTokenizer st = new StreamTokenizer(new BufferedReader(
new InputStreamReader(System.in)));
while (st.nextToken()!=StreamTokenizer.TT_EOF) {
N = (int)st.nval;
nums = new int[N];
for (int i = 0; i < N; i){
st.nextToken();
nums[i] = (int)st.nval;
}
Arrays.sort(nums);
C = N * (N - 1) / 2;
int lf = 0, rt = nums[N-1] - nums[0] 1;
while (rt - lf > 1){
int mid = (lf rt) / 2;
if (valid(mid)) rt = mid;
else lf = mid;
}
System.out.println(rt);
}
}
public static boolean valid(int mid){
int cnt = 0;
for (int i = 0; i < N - 1; i){
int pos = binarySearch(nums, i 1, N - 1, nums[i] mid);
if (pos != -1){
cnt = (pos - i);
}
}
return cnt >= (C 1) / 2;
}
public static int binarySearch(int[] nums, int lf, int rt, int key){
while (lf < rt){
int mid = lf (rt - lf 1) / 2;
if (nums[mid] > key) rt = mid - 1;
else lf = mid;
}
if (nums[lf] <= key) return lf;
return -1;
}