题目说明可以,k和n的范围为[1, 200000],也就是不大于20万的正整数。输入数据的范围为不大于十亿的非负整数。
那么可以得到如下重要信息:
* 1. 若是对整个数组进行排序,在k和n很接近的时候比较适合。若是k远小于n,那么不必要对整个数组进行排序。最坏情况下n^2为400亿,平均情况下nlogn为17*20万~18*20万(2^16为65536来进行估算)数量级
* 2. 若是进行位图法或者位图+统计,空间消耗太大达到十亿量级,而且遍历整个数组,也会使时间达到十亿量级。
使用分治的思想
时间复杂度为O(n + klogk),其中O(n)为partition函数,O(klok)是对数组中前k个数字进行排序(因为partition后,前k个数字是无序的)。
输入上使用StreamTokenizer
import java.util.Arrays;
import java.util.Scanner;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StreamTokenizer;
public class Main {
private int partition(int[] nums, int low, int high) {
int pivot = nums[low];
while (low < high) {
while (low < high && nums[high] >= pivot) {
--high;
}
nums[low] = nums[high];
while (low < high && nums[low] <= pivot) {
++low;
}
nums[high] = nums[low];
}
nums[low] = pivot;
return low;
}
private void topK(int[] nums, int low, int high, int k) {
if (k <= 0 || low >= high) {
return;
}
int pivotLoc = partition(nums, low, high);
if (pivotLoc == k - 1) {
return;
} else if (pivotLoc > k - 1) {
topK(nums, low, pivotLoc - 1, k);
} else {
topK(nums, pivotLoc + 1, high, k);
}
}
public static void main(String[] args) throws IOException {
Main M = new Main();
StreamTokenizer st = new StreamTokenizer(new BufferedReader(
new InputStreamReader(System.in)));
while (st.nextToken() != StreamTokenizer.TT_EOF) {
int n = (int)st.nval;
st.nextToken();
int k = (int)st.nval;
int[] nums = new int[n];
for (int i = 0; i < n; ++i) {
st.nextToken();
nums[i] = (int)st.nval;
}
M.topK(nums, 0, n - 1, k);
Arrays.sort(nums, 0, k);
System.out.print(nums[0]);
for (int i = 1; i < k; ++i) {
System.out.print(" " + nums[i]);
}
System.out.println();
}
} // main
} // class
/**************************************************************
Problem: 1371
User: buptxxz
Language: Java
Result: Time Limit Exceed
****************************************************************/
输入上使用readLine
可以看出,是超时的,那么在读取文字的时候,是否可以在输入输出上进行优化呢?下面是将输入输出改成一次性读一行的方式,根据结果可知,不仅速度更慢,而且在空间上消耗更大。
import java.util.Arrays;
import java.util.Scanner;
public class Main {
/** partition 方法和topK方法与上同,省略!
*/
public static void main(String[] args) {
Main M = new Main();
Scanner cin = new Scanner(System.in);
while(cin.hasNext()) {
String line1 = cin.nextLine();
String[] nk = line1.split(" ");
int n = Integer.parseInt(nk[0]);
int k = Integer.parseInt(nk[1]);
String line2 = cin.nextLine();
String[] strnum = line2.split(" ");
int[] nums = new int[n];
for (int i = 0; i < n; ++i) {
nums[i] = Integer.parseInt(strnum[i]);
}
M.topK(nums, 0, n - 1, k);
Arrays.sort(nums, 0, k);
System.out.print(nums[0]);
for (int i = 1; i < k; ++i) {
System.out.print(" " + nums[i]);
}
System.out.println();
}
} // main
} // class
/**************************************************************
Problem: 1371
User: buptxxz
Language: Java
Result: Time Limit Exceed
****************************************************************/
输入上使用cin.nextInt()
观察结果可以知道,使用这样的方式最慢。
import java.util.Arrays;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Main M = new Main();
Scanner cin = new Scanner(System.in);
while (cin.hasNext()) {
int n = cin.nextInt();
int k = cin.nextInt();
int[] nums = new int[n];
for (int i = 0; i < n; ++i) {
nums[i] = cin.nextInt();
}
M.topK(nums, 0, n - 1, k);
Arrays.sort(nums, 0, k);
System.out.print(nums[0]);
for (int i = 1; i < k; ++i) {
System.out.print(" " + nums[i]);
}
System.out.println();
}
cin.close();
} // main
} // class
关于输入速度的结论:追求高一点的输入速度,使用BufferedReader的方式。
使用最大堆
时间复杂度O(nlogk)
观察后面的结果可以知道,当数据量比较小的时候,速度还是可以的。但是最后一个用例超时,说明当数据量比较大,尤其是k比较大的时候,严重超时。这说明,使用容器类,会严重拖慢速度,它们看起来很美,然而用起来可是不一定。
import java.util.Comparator;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StreamTokenizer;
import java.util.Queue;
import java.util.PriorityQueue;
public class Main {
public static void main(String[] args) throws IOException {
StreamTokenizer st = new StreamTokenizer(new BufferedReader(
new InputStreamReader(System.in)));
while (st.nextToken() != StreamTokenizer.TT_EOF) {
int n = (int)st.nval;
st.nextToken();
int k = (int)st.nval;
if (k > n || k <= 0) {
continue;
}
// 使用一个最大堆:当堆的容量大于k时,删除堆顶
Queue<Integer> heap = new PriorityQueue<Integer>(1, new myComparator());
for (int i = 0; i < n; ++i) {
st.nextToken();
int x = (int)st.nval;
heap.add(x);
if (heap.size() > k) { // 当堆的容量大于k时,删除堆顶.(写在内部,以减少访问次数)
heap.poll();
}
}
// 将堆中元素读取出来
int[] nums = new int[k];
for (int i = 0; i < k; ++i) {
nums[i] = heap.poll();
}
// 输出元素
System.out.print(nums[k - 1]);
for (int i = k - 2; i >= 0; --i) {
System.out.print(" " + nums[i]);
}
System.out.println();
}
} // main
} // class
/**
* 定义逆序排序
* @author xxz
*
*/
class myComparator implements Comparator<Object> {
public int compare(Object a, Object b) {
if ((Integer)a < (Integer)b) {
return 1;
} else if ((Integer)a > (Integer)b) {
return -1;
} else {
return 0;
}
}
}