LeetCode-04中位数

/**
 * 已知两个有序数组,找到两个数组合并后的中位数。
 * [1,2] [3] 中位数 2.0
 * [1,3] [2,4] 中位数 2+3 / 2 = 2.5
 */
public class Test04 {
    public static void main(String[] args) {
        int[] nums1 = {1,3,4,9};
        int[] nums2 = {1,2,3,4,5,6,7,8,9,10};
//        System.out.println(findMedianSortedArrays01(nums1, nums2));
        System.out.println(findMedianSortedArrays02(nums1, nums2));
        System.out.println(findMedianSortedArrays03(nums1, nums2));
    }

    /**
     * 简单粗暴,先将两个数组合并,两个有序数组的合并也是归并排序中的⼀部分。然后根据奇数,还是偶数,
     * 返回中位数。
     * @param nums1
     * @param nums2
     * @return
     */
    static double findMedianSortedArrays01(int[] nums1, int[] nums2) {
        int[] nums;
        int m = nums1.length;
        int n = nums2.length;
        nums = new int[m + n];
        if (m == 0) {
            if (n % 2 == 0) {
                //偶数数据
                return (nums2[n / 2 - 1] + nums2[n / 2]) / 2.0;
            } else {
                return nums2[n / 2];
            }
        }
        if (n == 0) {
            if (m % 2 == 0) {
                return (nums1[m / 2 - 1] + nums1[m / 2]) / 2.0;
            } else {
                return nums1[m / 2];
            }
        }

        //  组合2个数组, 排序 并找出中位数
        int count = 0;
        int i = 0, j = 0;
        while (count != (m + n)) {
            if (i == m) {
                while (j != n) {
                    nums[count++] = nums2[j++];
                }
                break;
            }
            if (j == n) {
                while (i != m) {
                    nums[count++] = nums1[i++];
                }
                break;
            }
            // 比较2个数组 对应下标的大小
            if (nums1[i] < nums2[j]) {
                nums[count++] = nums1[i++];
            } else {
                nums[count++] = nums2[j++];
            }
        }

        //得到的是一个有序的新数组
        if (count % 2 == 0) {
            return (nums[count / 2 - 1] + nums[count / 2]) / 2.0;
        } else {
            return nums[count / 2];
        }
    }

    /**
     *  前提是2个有序数组
     *  不需要将两个数组真的合并,我们只需要找到中位数在哪⾥
     *      ⽤ len 表示合并后数组的⻓度,如果是奇数,我们需要知道第 (len + 1)/ 2 个数就可以了,如果遍历的话
     *      需要遍历 int ( len / 2 ) + 1 次。如果是偶数,我们需要知道第 len / 2 和 len / 2 + 1 个数,也是需要遍历 len
     *      / 2 + 1 次。所以遍历的话,奇数和偶数都是 len / 2 + 1 次。 所以 i <= len/2 从0开始 到 len/2, 循环了 len/2+1 
     *      返回中位数的话,奇数需要最后⼀次遍历的结果就可以了,偶数需要最后⼀次和上⼀次遍历的结果。所以我
     * 们⽤两个变量 left 和 right ,right 保存当前循环的结果,在每次循环前将 right 的值赋给 left 。
     * @param nums1
     * @param nums2
     * @return
     *
     * 时间复杂度:遍历 len/2 + 1 次,len = m + n ,所以时间复杂度依旧是 O(m + n)。
     * 空间复杂度:我们申请了常数个变量,也就是 m,n,len,left,right,aStart,bStart 以及 i 。
     * 总共 8 个变量,所以空间复杂度是 O(1)。
     */
    static double findMedianSortedArrays02(int[] nums1, int[] nums2) {
        int m = nums1.length;
        int n = nums2.length;
        int len = m + n;
        int left = -1, right = -1; //中位数的值
        int aStart = 0, bStart = 0; // 游标
        // 循环 len / 2 + 1 次
        for (int i = 0; i <= len / 2; i++) {
            //第 len / 2 次的值
            left = right;
            //中位数 取 nums1的情况  bStart >= n 超过 nums2的索引界限 不用比较
            if (aStart < m && (bStart >= n || nums1[aStart] < nums2[bStart])) {
                right = nums1[aStart++];
            } else {
                right = nums2[bStart++];
            }
        }
        // 计算中位数
        if ((len & 1) == 0) {
            return (left + right) / 2.0;}
        else {
            return right;
        }
    }

    /**
     * 题⽬的要求 O ( log ( m + n ) )。看到 log ,很明显,我们只有⽤到⼆分的⽅法才能达到。
     *
     * 由于数列是有序的,其实我们完全可以⼀半⼉⼀半⼉的排除。 假设我们要找第 k ⼩数,我们可以每次循环排除掉 k / 2 个数。
     * 更⼀般的情况 A [ 1 ],A [ 2 ],A [ 3 ],A [ k / 2] ... ,B[ 1 ],B [ 2 ],B [ 3 ],B[ k / 2] ... ,如果 A [ k / 2 ] <
     * B [ k / 2 ] ,那么 A [ 1 ],A [ 2 ],A [ 3 ],A [ k / 2] 都不可能是第 k ⼩的数字。
     * @param nums1
     * @param nums2
     * @return
     */
    static double findMedianSortedArrays03(int[] nums1, int[] nums2) {
        int n = nums1.length;
        int m = nums2.length;
        int left = (n + m + 1) / 2; // (14+1) / 2 =7  数组 第7,8个是中位数
        int right = (n + m + 2) / 2; // 14+2 / 2 = 8
            //将偶数和奇数的情况合并,如果是奇数,会求两次同样的 k 。  两个中位数之和
        return (getKth(nums1, 0, n - 1, nums2, 0, m - 1, left) + getKth(nums1, 0, n -
                1, nums2, 0, m - 1, right)) * 0.5;
    }

    /**
     *
     * @param nums1
     * @param start1
     * @param end1
     * @param nums2
     * @param start2
     * @param end2
     * @param k
     * @return
     */
    static int getKth(int[] nums1, int start1, int end1, int[] nums2, int
            start2, int end2, int k) {
        int len1 = end1 - start1 + 1;
        int len2 = end2 - start2 + 1;
     //让 len1 的⻓度⼩于 len2,这样就能保证如果有数组空了,⼀定是 len1
        if (len1 > len2) {
            return getKth(nums2, start2, end2, nums1, start1, end1,k);
        }
        if (len1 == 0) {
            return nums2[start2 + k - 1];
        }
        //走出递归的条件
        if (k == 1){
            return Math.min(nums1[start1], nums2[start2]);
        }
        //游标所在位置  第 x 个数 -1
        int i = start1 + Math.min(len1, k / 2) - 1;
        int j = start2 + Math.min(len2, k / 2) - 1;

        if (nums1[i] > nums2[j]) {
            return getKth(nums1, start1, end1, nums2, j + 1, end2, k - (j -start2 + 1));
        }
        else {
            return getKth(nums1, i + 1, end1, nums2, start2, end2, k - (i -start1 + 1));
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值