最小的k个数

题目说明可以,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
****************************************************************/

最小的k个数_第1张图片

输入上使用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
****************************************************************/

最小的k个数_第2张图片

输入上使用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

最小的k个数_第3张图片

关于输入速度的结论:追求高一点的输入速度,使用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 heap = new PriorityQueue(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 {
    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;
        }
    }
} 
  

最小的k个数_第4张图片

你可能感兴趣的:(剑指offer刷题)