0
点赞
收藏
分享

微信扫一扫

算法:那些年遇见的数组第 k 小

宁静的猫 2022-04-05 阅读 30

一个无序数组中的第 k 小

整体思路,维护一个大根堆,遍历每个数的时候,判断它是不是比堆顶小,如果小的话,就删除堆顶的,并把当前值添加进去。最后堆顶即为第 k 小。总体时间复杂度为 O ( N l o g k ) O(Nlogk) O(Nlogk),空间复杂度为 O ( k ) O(k) O(k)

//   public static int findKthLargest(int[] nums, int k) {
//     if (nums == null || nums.length == 0 || k > nums.length) return -1;/
//       return getKthMin(nums, nums.length - k + 1);
//    }

//
//    新添加的数 在 i  位置,进行堆化
public static void heapInsert(int[] heap, int i) {
    while(heap[(i - 1) / 2] < heap[i]) {
        swap(heap, i, (i-1)/2);
        i = (i - 1) / 2;
    }
}
//    i 位置的值,发生了变化,重新调整堆
public static void heapify(int[] heap, int i) {
    int left = 2 * i + 1;
    int n = heap.length;
    while (left < n) {
        // 找到孩子中的较大的那个索引值
        int largestIndex = (left + 1 < n) && heap[left + 1] > heap[left] ? left + 1:left;
        // 父亲已经大于孩子了
        if (heap[i] > heap[largestIndex]) {
            break;
        }
        swap(heap, i, largestIndex);
        i = largestIndex;
        left = 2 * i + 1;
    }
}
//    堆得到 第 k 小的数
//    维护一个大根堆
public static  int getKthMin(int[] nums, int k) {
    int[] heap = new int[k];
    
 //    for (int i = k-1; i >= 0; i--) {
 //       heap[i] = nums[i];
 //       heapify(heap, i);
 //   }
    for (int i = 0; i < k; i++) {
        heap[i] = nums[i];
        heapInsert(heap, i);
    }
    for (int i = k; i < nums.length; i++) {
        ///  当前这个数可以添加进去
        if (nums[i] < heap[0]) {
            heap[0] = nums[i];
            heapify(heap, 0);
        }
    }
    return heap[0];
}
public static void swap(int[] nums, int i, int j) {
    int temp = nums[i];
    nums[i] = nums[j];
    nums[j] = temp;
}

快排思路

标准的快排程序是每次选择一个基准值,然后通过这个基准值调 partition 程序,将源数组分为 3 部分,并且返回中间部分的起始和结束坐标。当 k 在这个坐标范围内时,就得到了第 k 小的值。此方法时间复杂度是 O ( N ) O(N) O(N)的,不过是基于概率的 O(N),因为每次选择的基准值是随机选择的,可能情况下排除掉最多元素,可能情况下一个也排除不了。
在这里插入图片描述

public int getKthSmallest(int[] nums, int k) {
    int l = 0, r = nums.length - 1;
    while (l <= r) {
        //  在 l...r 上随机选择一个值,作为基准值
        int pivot = nums[l + (int) (Math.random() * (r - l + 1))];
        int[] p = partition(nums, l, r, pivot);
        if (k - 1 >= p[0] && k - 1 <= p[1]) {
            return nums[p[0]];
        }
        if (k - 1 < p[0]) {
            r = p[0] - 1;
        }
        if (k - 1 > p[1]) {
            l = p[1] + 1;
        }
    }
    return -1;
}

public int[] partition(int[] nums, int l, int r, int pivot) {
    int less = l - 1, more = r + 1;
    while (l < more) {
        if (nums[l] < pivot) {
            swap(nums, ++less, l++);
        } else if (nums[l] > pivot) {
            swap(nums, --more, l);
        } else {
            l++;
        }
    }
    return new int[]{less + 1, more - 1};
}

BFPRT

此方法和快排的区别在于选取基准值的不同,复杂度也是O(N)的,不过是确定性的O(N),因为其选取基准值是通过了一定的方法,并且可以保证每次至少排除 3 / 10 N 3/10N 3/10N 的数据。

//arr L...R 位置上,如果排序的话,返回 index位置的数。
public static int bfprt(int[] arr, int L, int R, int index) {
        if (L == R) {
            return arr[L];
        }
// 数组 L...R 中每 5 个数作为一组,找到组内的中位数,这些中位数在形成一个数组,找到这个数组的中位数,构成基准值。
        int pivot = mOfM(arr, L, R);
        int[] range = partition(arr, L, R, pivot);
        if (index >= range[0] && index <= range[1]) {
            return arr[index];
        } else if (index-1 < range[0]) {
            return bfprt(arr, L, range[0] - 1, index);
        } else {
            return bfprt(arr, range[1] + 1, R, index);
        }
    }

    public static int mOfM(int[] arr, int L, int R) {
        int size = R - L + 1;
        int offset = size % 5 == 0 ? 0 : 1;
        int[] mArr = new int[size / 5 + offset];
        for (int team = 0; team < mArr.length; team++) {
            int teamFirst = L + team * 5;
            mArr[team] = getMedian(arr, teamFirst, Math.min(R, teamFirst + 4));
        }
        // marr中,找到中位数
        // marr(0, marr.len - 1,  mArr.length / 2 )
        return bfprt(mArr, 0, mArr.length - 1, mArr.length / 2);
    }

    public static  int getMedian(int[] arr, int L, int R) {
        insertionSort(arr, L, R);
        return arr[(L + R) / 2];
    }

    public static void insertionSort(int[] arr, int L, int R) {
        for (int i = L + 1; i <= R; i++) {
            for (int j = i - 1; j >= L && arr[j] > arr[j + 1]; j--) {
                swap(arr, j, j + 1);
            }
        }
    }

两个有序数组中的第 k 小

整体思路呢利用两个数组的有序性和k的取值范围,一次性排除掉一批数,底层利用求两个等长有序数组的上中位数的操作,算法复杂度达到了 O ( l o g ( m i n ( m , n ) ) ) O(log(min(m, n))) O(log(min(m,n)))

//        得到两个有序数组中的第 k 小的数
//    基本原型,得到两个等长有序数组的上中位数
public static int getKthSmallest(int[] nums1, int[] nums2, int k) {
//        无效的 k
//        if (k > (nums1.length + nums2.length)) return -1;
    int[] longs = nums1.length >= nums2.length ? nums1 : nums2;
    int[] shorts = longs == nums1 ? nums2 : nums1;
    int l = longs.length, s = shorts.length;
    //       根据 k 的范围来进行划分
    // p1: k 小于等于 短数组长度
    if (k <= s) {
        return getUpMedian(shorts, 0, k - 1, longs, 0, k - 1);
    }
    // p2: k 小于等于 长数组长度
    if (k <= l) {
        if (longs[k - s - 1] >= shorts[s - 1]) {
            return longs[k - s - 1];
        }
        //            短的要全部用上了,肯定是 s - 1了
        return getUpMedian(shorts, 0, s - 1, longs, k - s, k - 1);
    }
    // p3: k 大于长数组长度
    if (shorts[k - l - 1] >= longs[l - 1]) {
        return shorts[k - l - 1];
    }
    if (longs[k - s - 1] >= shorts[s - 1]) {
        return longs[k - l - 1];
    }
    return getUpMedian(shorts, k - l, s - 1, longs, k - s, l - 1);

}

//    分为奇数和偶数两种情况
//    保证输入的两个边界范围是有效的,并且等长的
public static int getUpMedian(int[] A, int l1, int r1, int[] B, int l2, int r2) {
    while (l1 < r1) {
        int mid1 = (l1 + r1) / 2;
        int mid2 = (l2 + r2) / 2;
        // 两个mid 相等,说明 就是上中位数了
        if (A[mid1] == B[mid2]) {
            return A[mid1];
        }
        if ((((r1 - l1 + 1) & 1)) == 0) {
            // 如果长度是偶数的话
            if (A[mid1] > B[mid2]) {
                r1 = mid1;
                l2 = mid2 + 1;
            } else {
                r2 = mid2;
                l1 = mid1 + 1;
            }
        } else {
            // 那就只能是奇数了
            if (A[mid1] > B[mid2]) {
                if (B[mid2] >= A[mid1 - 1]) {
                    return B[mid2];
                }
                r1 = mid1 - 1;
                l2 = mid2 + 1;
            } else {
                if (A[mid1] >= B[mid2 - 1]) {
                    return A[mid1];
                }
                r2 = mid2 - 1;
                l1 = mid1 + 1;
            }
        }
    }
    return Math.min(A[l1], B[l2]);
}

//        得到两个有序数组的中位数,如果是偶数长度,上中位数+下中位数和除以2。
//    public static double findMedianSortedArrays(int[] nums1, int[] nums2) {
//        if (nums1 == null && nums2 == null) return -1;
//        if ((nums1 == null ^ nums2 == null) || (nums1.length == 0 ^ nums2.length == 0)) {
//            int[] temp = (nums1 == null || nums1.length == 0) ? nums2 : nums1;
//            if ((temp.length & 1) == 1) {
//                return temp[temp.length / 2];
//            } else {
//                return (temp[temp.length / 2] + temp[temp.length / 2 - 1] )/ 2.0;
//            }
//        }
//        int n1 = nums1.length;
//        int n2 = nums2.length;
//        int all = n1 + n2;
//        if ((all & 1) == 1) {
//            return getKthSmallest(nums1, nums2, all / 2 + 1);
//        }
//        return (getKthSmallest(nums1, nums2, all / 2) + getKthSmallest(nums1, nums2, all/ 2 + 1)) / 2.0;
//    }

`

举报

相关推荐

0 条评论