首页 > 技术文章 > leetcode-两个数组中位数

iceywu 2020-01-11 19:43 原文

问题描述:

给定两个大小为 m 和 n 的有序数组 nums1 和 nums2。

请你找出这两个有序数组的中位数,并且要求算法的时间复杂度为 O(log(m + n))。

你可以假设 nums1 和 nums2 不会同时为空。

示例 1:

nums1 = [1, 3]
nums2 = [2]

则中位数是 2.0

我的解答:

package cn;

import java.util.Scanner;

public class CalMedianArray {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入第一个数组:(用逗号隔开)");
        String s = sc.nextLine();
        String[] split = s.split(",");
        int[] arr = new int[split.length];
        for (int i = 0; i < split.length; i++) {
            int val = Integer.parseInt(split[i]);
            arr[i] = val;
        }
        System.out.println("请输入第二个数组:(用逗号隔开)");
        String s2 = sc.nextLine();
        String[] split2 = s2.split(",");
        int[] arr2 = new int[split2.length];
        for (int i = 0; i < split2.length; i++) {
            int val = Integer.parseInt(split2[i]);
            arr2[i] = val;
        }
        double medium = calMedian(arr, arr2);
        System.out.println("两个数组的中位数为:" + medium);

    }

    private static double calMedian(int[] num1, int[] num2) {
        int m = num1.length;
        int n = num2.length;
        int l = m + n;
        //两个都不为空时
        int i = 0, j = 0, index = 0;
        double mid=0,last=0;
        while (index < l/2-1) {
            if (i < m && j < n) {
                if (num1[i] < num2[j]) {
                    mid = num2[j];
                    last = num1[i];
                    index++;
                    i++;
                }
                if (num1[i] > num2[j]) {
                    mid = num1[i];
                    last = num2[j];
                    index++;
                    j++;
                }
                if (num1[i] == num2[j]) {
                    mid = num1[i];
                    last = mid;
                    index += 2;
                    i++;
                    j++;
                }
            } else if (i >= m) {
                mid = num2[j];
                j++;
            } else {
                mid = num1[i];
                i++;
            }
        }
            //(m+n)为奇数时
            if (l % 2 != 0) return mid;
            else return (last + mid) / 2.0;
        }
    }

运行结果不正确!!!

分析:

(1)默认的是从小到大的顺序,我模仿以前合并两个有序链表的思路,想着两两比较得到最大值,然后将较小值的索引往后移一位,继续比较。

(2)因为合并后的链表长度可能为奇数也可能为偶数,所以每次不仅对mid赋值,也对last赋值,如果是偶数则返回(last+mid)/2。

(3)该算法不正确的原因是,我从一开始就犯了一个错误:两两比较得到的最小值一定比后面所有值都小,但是两两比较的最大值有可能比后面几个值都大,这样把最大值赋值给mid就是错误的!

举个例子:

num1[]: 2,3,5

num2[]: 1,6,8,9

正确的mid顺序应该是:2,3,5,6,8,9

但是2和6比较直接将6作为mid,顺序就变成:2,3,6,6,8,9

解决思路:

仍然两两比较得到最小值,作为新链表的值,再获得中位数

 private static double calMedian(int[] num1, int[] num2) {
        int m = num1.length;
        int n = num2.length;
        int l = m + n;
        //两个都不为空时
        int[] num3 = new int[l];
        int i = 0, j = 0;
        for (int index = 0; index < l/2+1; index++) {
           if(i < m  && j < n) {
                if (num1[i] <= num2[j]) {
                    num3[index] = num1[i];
                    i++;
                } else {
                    num3[index] = num2[j];
                    j++;
                }
            }
            else if(i >= m && j < n) {
                num3[index] = num2[j];
                j++;
            }
            else if(i < m) {
                num3[index] = num1[i];
                i++;
            }
        }
        //(m+n)为奇数时
        if (l % 2 != 0) return (double) num3[l / 2];
            //偶数
        else return (num3[l / 2] + num3[l / 2 - 1]) / 2.0;
    }

分析:

这种算法虽然能做出来,但是时间复杂度为O(m+n),不满足条件!!

正确解答:

class Solution {
    public double findMedianSortedArrays(int[] A, int[] B) {
            int la = A.length;//数组 A 的长度
            int lb = B.length;//数组 B 的长度

            if (la > lb) {//如果数组 A 比较长,则交换 A、B 数组
                int[] temp = A;
                A = B;
                B = temp;

                int tempL = la;
                la = lb;
                lb = tempL;
            }

            int aMin = 0;     //A 数组折半查找左边界
            int aMax = la;    //A 数组折半查找右边界

            // halfLen 的作用就是中点坐标,当 A 数组中折半查找向右移动时,B 数组以 halfLen 为相对点向左移动,以保持始终均分
            int halfLen = (la + lb + 1) >> 1;
            //二分查找

            //情况一: A 数组为空,中位数在 B 数组
            //情况二: A 数组较短
            //  1) A 数组元素都较小,中位数在B数组
            //  2) A 数组元素都较大,中位数在B数组
            //  3) A、B 元素大小分布基本相当,中位数为被分割的两数组左半部分较大的那一个和右半部分较小的那一个之和的一半
            //情况三: A、B 等长
            //  1) A 数组元素都比B数组元素小,中位数为 A 数组尾元素和B数组首元素之和的一半
            //  2) B 数组元素都比A数组元素小,中位数为 B 数组尾元素和A数组首元素之和的一半
            //  3) A、B 元素大小分布基本相当,中位数为被分割的两数组左半部分较大的那一个和右半部分较小的那一个之和的一半
            while (aMin <= aMax) {
                int aIndex = (aMin + aMax) >> 1;  //A数组中分割点
                int bIndex = halfLen - aIndex;  //B数组中分割点

                //数组 A 分割点相邻左边那个元素比数组 B 分割点相邻右边那个元素大,则应该将数组 A 分割点向右移,数组 B 分割点向左移
                //数组 A 分割点有向左移趋势,需检查左边界
                if (aIndex > aMin && A[aIndex - 1] > B[bIndex]) {
                    aMax = aIndex - 1;
                    //数组 A 分割点相邻右边那个元素比数组 B 分割点相邻左边那个元素大,则应该将数组 A 分割点向左移,数组 B 分割点向右移
                    //数组 A 分割点有向右移趋势,需检查右边界
                } else if (aIndex < aMax && B[bIndex - 1] > A[aIndex]) {
                    aMin = aIndex + 1;
                    //得出结果
                } else {

                    int leftPart = 0;
                    //情况一,情况二2,情况三2
                    if (aIndex == 0) {
                        leftPart = B[bIndex-1];
                        //情况三1
                    } else if (bIndex == 0) {
                        leftPart = A[la - 1];
                        //情况二1,情况二3,情况三3
                    } else {
                        leftPart = Math.max(A[aIndex - 1], B[bIndex-1]);
                    }

                    //元素个数总和为奇数
                    if ((la + lb) % 2 == 1) {
                        return leftPart;
                    }

                    //情况一: A 数组为空,中位数在 B 数组
                    //情况二: A 数组较短
                    //  1) A 数组元素都较小,中位数在B数组
                    //  2) A 数组元素都较大,中位数在B数组
                    //  3) A、B 元素大小分布基本相当,中位数为被分割的两数组左半部分较大的那一个和右半部分较小的那一个之和的一半
                    //情况三: A、B 等长
                    //  1) A 数组元素都比B数组元素小,中位数为 A 数组尾元素和B数组首元素之和的一半
                    //  2) B 数组元素都比A数组元素小,中位数为 B 数组尾元素和A数组首元素之和的一半
                    //  3) A、B 元素大小分布基本相当,中位数为被分割的两数组左半部分较大的那一个和右半部分较小的那一个之和的一半

                    //元素个数总和为偶数
                    int rightPart = 0;
                    //情况一,情况二1
                    if (aIndex == la) {
                        rightPart = B[bIndex];
                        //情况三2
                    } else if (bIndex == lb) {
                        rightPart = A[0];
                        //情况二2、3,情况三1、3
                    }else {
                        rightPart = Math.min(A[aIndex], B[bIndex]);
                    }
                    return (leftPart + rightPart) / 2.0;
                }

            }
            return 0;
    }
}

分析:感觉自己还不是很懂,今天太晚了,等有时间再来看一看。

推荐阅读