• 给定数组arr和整数k,返回第k小的数值对的解法


    问题描述

            长度为N的数组arr,一定可以组成N^2个数值对。
            例如arr = [3,1,2],数值对有(3,3) (3,1) (3,2) (1,3) (1,1) (1,2) (2,3) (2,1) (2,2),也就是任意两个数都有数值对,而且自己和自己也算数值对。
            数值对怎么排序?规定,第一维数据从小到大,第一维数据一样的,第二维数组也从小到大。所以上面的数值对排序的结果为:(1,1)(1,2)(1,3)(2,1)(2,2)(2,3)(3,1)(3,2)(3,3)
            给定一个数组arr,和整数k,返回第k小的数值对。

    思路

            假设数组有序,数组的长度为n,那么第 k 小的数值对的第一元素在原素组的下标其实可以算出来是 f=(k-1)/n 。我们重新遍历数组可以计算出小于下标 f 的数有 a 个,等于下标 f 的数有 b 个。此时我们我们分析可知在数值对中第一个元素下标为 f 之前共有 a*n个,即此时问题转化为我们需要在数值对找到第一个元素下标为 f 的第  (k -a*n)=s 个元素。小于下标 f 的元素每个都有 b 次出现,我们就可以计算出数值对中第二个元素的下标为 (s-1)/b。

            此题已知的数组并不是一个有序数组,我们需要进行排序。在排序算法中最好的时间复杂度也是O(N*logN)。但是我们重新分析题会发现我们并不是需要数组全部排序,我们只是需要知道在无序数组中的第i小的数字是什么即可(快排改进)。根据这一需求我们可以进行优化,把原本O(N*logN)的时间复杂度降为O(N)。

    代码

     方案一:首先定义一个类Pair,使用暴力递归的方式解题,最后使用系统提供的方法进行排序。

            该方法的时间复杂度为 O(N^2*log(N^2))。

    1. public static class Pair {
    2. public int x;
    3. public int y;
    4. public Pair(int x, int y) {
    5. this.x = x;
    6. this.y = y;
    7. }
    8. }
    9. public static class PairComparator implements Comparator {
    10. @Override
    11. public int compare(Pair o1, Pair o2) {
    12. return o1.x != o2.x ? o1.x - o2.x : o1.y - o2.y;
    13. }
    14. }
    15. public static int[] kthMinPair1(int[] arr, int k) {
    16. int N = arr.length;
    17. if (k > N * N) {
    18. return null;
    19. }
    20. Pair[] pairs = new Pair[N * N];
    21. int index = 0;
    22. for (int i = 0; i < N; i++) {
    23. for (int j = 0; j < N; j++) {
    24. pairs[index++] = new Pair(arr[i], arr[j]);
    25. }
    26. }
    27. Arrays.sort(pairs, new PairComparator());
    28. return new int[]{pairs[k - 1].x, pairs[k - 1].y};
    29. }

    方案二:按照上面思路中的第一段思想进行解题。

            时间复杂度为O(N*logN)。

    1. public static int[] kthMinPair2(int[] arr, int k) {
    2. int N = arr.length;
    3. if (k > N * N) {
    4. return null;
    5. }
    6. //O(N*logN)
    7. Arrays.sort(arr);
    8. //第k小的数值对,第一位数字
    9. int firstNum = arr[(k - 1) / N];
    10. int lessFirstNumSize = 0;//数出比firstNum小的数有几个
    11. int firstNumSize = 0;//数组中==firstNum的数有几个
    12. //<= firstNum
    13. for (int i = 0; i < N && arr[i] <= firstNum; i++) {
    14. if (arr[i] < firstNum) {
    15. lessFirstNumSize++;
    16. } else {
    17. firstNumSize++;
    18. }
    19. }
    20. int rest = k - (lessFirstNumSize * N);
    21. return new int[]{firstNum, arr[(rest - 1) / firstNumSize]};
    22. }

    方案三:按照思路中第二段思想,即改写快排,实现在无序数组中查找第index小的数字。

            时间复杂度为O(N)。

    1. public static int getMinKth(int[] arr, int index) {
    2. int L = 0;
    3. int R = arr.length - 1;
    4. int pivot = 0;
    5. int[] range = null;
    6. while (L < R) {
    7. pivot = arr[L + (int) (Math.random() * (R - L + 1))];
    8. range = partition(arr, L, R, pivot);
    9. if (index < range[0]) {
    10. R = range[0] - 1;
    11. } else if (index > range[1]) {
    12. L = range[1] + 1;
    13. } else {
    14. return pivot;
    15. }
    16. }
    17. return arr[L];
    18. }
    19. public static int[] partition(int[] arr, int L, int R, int pivot) {
    20. int less = L - 1;
    21. int more = R + 1;
    22. int cur = L;
    23. while (cur < more) {
    24. if (arr[cur] < pivot) {
    25. swap(arr, ++less, cur++);
    26. } else if (arr[cur] > pivot) {
    27. swap(arr, cur, --more);
    28. } else {
    29. cur++;
    30. }
    31. }
    32. return new int[]{less + 1, more - 1};
    33. }
    34. public static void swap(int[] arr, int x, int y) {
    35. int tmp = arr[x];
    36. arr[x] = arr[y];
    37. arr[y] = tmp;
    38. }

  • 相关阅读:
    初始 JDBC
    游戏开发中的设计模式
    flutter系列之:使用AnimationController来控制动画效果
    AndroidStudio 运行报错:Invalid keystore format
    关于C/C++中const关键字作用的一些想法
    【Qt QML】TabBar的用法
    机器学习实训(3)——训练模型(补充)
    OCR基本原理
    MYSQL经典面试题
    [游戏开发][Shader]ShaderToy通用模板转Unity-CG语言
  • 原文地址:https://blog.csdn.net/z1171127310/article/details/127710063