【LeetCode】4、2つのソート配列の中央値

トピックグレード:ハード

件名の説明:

  2つのソート列がありnums1nums2それぞれサイズmとnのは。

  2つのソート配列の中央値を検索します。全体的な実行時間の複雑さはO(ログ(M + N))であるべきです。

  あなたは、想定し得るnums1をしてnums2は両方とも空にすることはできません。

  例1:

nums1 = [1, 3]
nums2 = [2]
The median is 2.0

  例2:

nums1 = [1, 2]
nums2 = [3, 4]
The median is (2 + 3)/2 = 2.5

  問題の意味は:すべての要素の中央値を見つけるために、2メートルの長さおよびNソートされた配列では、2つの配列を与えられました。必要とされる時間複雑度は、O(ログ(M + N)です )。


問題解決のアイデア:

  このタイトルは、難易度は比較的困難であるのレベルに属しています。ここでは単純なものから複雑なものへのソリューションの5種類を与えられました。

  解決法1:2つの配列をマージし、中央値を見つけます

  一見このテーマでは、我々は非常に直感的な暴力的な解決策を持っていると思います。二つの配列は、ちょうど2つのアレイ、増加配列の合成をマージするために、中央値は全体的な要件、小規模から大規模に並べられ、あなたは直接、真ん中の要素を見つけることができます。

  O(M + N)の2つの配列時の複雑さ、中央値の併合のみO(1)規則配列を見つけるための時間ので、O(M + N)の合計時間の複雑さ、また必要付加的な長さmのアレイ+ nは、空間複雑度はO(M + N)となるように。

  コードは以下の通りであります:

public double findMedianSortedArrays(int[] nums1, int[] nums2) {
    int m = nums1==null?0:nums1.length;
    int n = nums2==null?0:nums2.length;
    if(m==0) //数组1空
        return findMedianSortedArrays(nums2);
    if(n==0) //数组2空
        return findMedianSortedArrays(nums1);
    
    int[] nums=new int[m+n];
    int count=0;
    for(int i=0,j=0;i<m || j<n;){  //合并为一个数组
        if(i==m)
            nums[count++]=nums2[j++];
        else if(j==n)
            nums[count++]=nums1[i++];
        else{
            if(nums1[i]<nums2[j])
                nums[count++]=nums1[i++];
            else
                nums[count++]=nums2[j++];
        }  
    }
    return findMedianSortedArrays(nums);
}
//在一个有序数组中找中位数
public double findMedianSortedArrays(int[] nums){
    int len=nums.length;
    if(len%2==0)
        return (nums[len/2]+nums[len/2-1])/2.0;
    else
        return nums[len/2];
}

  対処方法2:改善されたマージ

  暴力行為から、我々は実際に私たちの目標は、中央値を見つけるために、単純だったので、全体の配列をマージする必要はありませんが終了していることを見つけることができ、我々は唯一の中央値を求めることができるマージする必要があります。即ち:配列の長さがあれば、マージの半分の長さは、中央値を見つけることができるようにM + Nマージが、あろう。

  奇数(LEN = M + N)lenの場合、中央値、第2がlen / 2 + 1をマージした後に数の中央値であり、lenが偶数である場合、2つの平均は、中間数の後にマージされるべきです、第LEN / 2と2つの数の最大len / 2 + 1の数の平均数です。私たちは、インデックス0で、最初のいくつかの数字について話していることに注意してコードを書くときに注意を払うように、数1です。

  したがって、2つの溶液のアイデアは、中央値が決定されるまでのみマージすることであり、アレイ全体をマージする必要はないが保存されている、唯一のサイクル中に2つの変数を維持する必要があることができ、lenの/ 2のと数LEN / 2 + 1の数。

  半分だけマージするので、時間計算量は依然としてO(M + N)であるO(/ 2 LEN)、であるが、(1)空間の複雑さOを落としました。

  コードは以下の通りであります:

public double findMedianSortedArrays(int[] nums1, int[] nums2) {
    int m = nums1==null?0:nums1.length;
    int n = nums2==null?0:nums2.length;
    
    int i=0,j=0;
    int first=-1,second=-1;
    //不需要全部保存,只保存两个数即可
    for(int count=0;count<=(m+n)/2;count++){  //找到中位数就停止
        first=second;
        if(i==m)
            second=nums2[j++];
        else if(j==n)
            second=nums1[i++];
        else{
            if(nums1[i]<nums2[j])
                second=nums1[i++];
            else
                second=nums2[j++];
        }  
    }
    
    if((m+n)%2==0)
        return (first+second)/2.0;
    else
        return second;
}

  解決策3:最初は、kの小さな数字を見つけます

  もちろん、解決策は、IとII、理解することは比較的容易ものの、しかし、我々は、トピックの実際の時間の複雑さを見るには、要件を満たしていませんでした。時間計算量は、ログ時間の複雑さになるために、通常の状況下では、要求ログレベルの対象である、または約2情報を検索するか、二分木に関連しています。ここでは、データ構造体の配列ですので、我々は半分にいくつかの以上の考察を探しています。

  ここでは、第三の問題解決のアイデアを与える:秩序配列の中央値は、実際にlenを/ 2個の配列である(偶数は2つの数の平均値です)。だから、kの小さな数字の配列のために変換することができ、中央値を探します。長さが奇数であり、我々は直接見つける限り、我々は小さいkの数を確認する方法を知っているように、その後、LEN / 2 + 1小さい数字は中央値であり、さらにそれが最初/ LEN / 2小さな及びLENを発見します2つの+ 1小数値を平均しました。

  だから、私たちの問題は、2つの命じた配列内のkの少数に取得する方法です

  ここで、より良い方法がある:kの小さな数を見つけるために、我々は、K / 2桁の二つの配列を比較する、すなわちnums1 [K / 2-1]とnums2 [K / 2-1]を行います、比較、それは前にそれを数字と、それを除外することができる最初のk個の小さな数字ではありませんので、小さい何かあれば、内部に残っているデータを見つけるために、このプロセスを繰り返します。

  実際には、この原理を理解することは困難ではない。以下に示すように、nums2より小さいnums1 [K / 2-1] [K / 2-1]は、最初全てのそれが前nums1の数未満である場合、[K / 2-1] 、[K / 2-1]配列nums2 nums2を想定することもK / 2の合計数まで[K / 2-1] nums1より全て小さいの前に数(必ずしもより実際未満)、よりも小さいです1 + K / 2-1ヶ月、すなわち2つのK-2、即ちnums1 [K / 2-1] K-1までは小さくてもよく、kは小さくすることができないことと、その前の図ように、あなたは除外することができます。

  二つの数が等しい場合、逆に、もしnums1 [K / 2-1] nums2より大きい場合は、[K / 2-1]は、実質的に任意の、nums2 [K / 2-1]と、それ以前のいくつかを排除することができます配列は、それを除外選択し、それはまだ、K / 2番号を除外しています。



  したがって、私たちの第三ソリューションは以下のとおりです。残りのk-配列を探し続け、常に比較、比較的小さな配列のk / 2の要素でk個の要素の半分を取り、前に除外することができる、と小さな要素(注:起因はK / 2の要素を除外しているため、そうkの値を引いて、除外される要素の数)。

  明らかに、これは反復プロセスであり、それをここに、再帰的にすることによって解決することができる配列の境界を横切ると終了条件再帰発行し、K / 2、及び小さい方の配列の長さを比較します。さらに、それぞれの時間は、従って、K / 2要素、及びを除去するため、以下に示す特定のコードを参照して、終了条件である、最終的なアレイが空になる持っている、またはK 1となる必要があります。

  最後に、溶液の複雑三たび我々のサイクルをk / 2の要素を除外した溶液を分析し、時間計算量はO(logK)であり、kはM + Nの半分であり、時間の複雑さはO(あります(M + N)ログ)余分なスペースを使用しない、空間の複雑さはO(1)です。

class Solution {
    //解法三:相当于求两个有序数组的第k小数字
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
        int m = nums1==null?0:nums1.length;
        int n = nums2==null?0:nums2.length;
        int len=m+n;
        if(len%2==0) //偶数
        return (findKthNum(nums1,nums2,0,0,len/2) + findKthNum(nums1,nums2,0,0,len/2 + 1)) / 2.0;
        else
            return findKthNum(nums1,nums2,0,0,len/2+1);
    }
    
    //找第k小数字,i和j表示两个数组的起始位置
    public int findKthNum(int[] nums1,int[] nums2,int i,int j,int k){
        int m=nums1.length;
        int n=nums2.length;
        if(i>=m) //nums1空
            return nums2[j+k-1];
        if(j>=n) //nums2空
            return nums1[i+k-1];
        if(k==1)
            return Math.min(nums1[i],nums2[j]);
        
        int index=k/2-1;
        int nums1Index=Math.min(m-1,i+index);
        int nums2Index=Math.min(n-1,j+index);
        if(nums1[nums1Index]<nums2[nums2Index])
            return findKthNum(nums1,nums2,nums1Index+1,j,k-(nums1Index-i+1));
        else
            return findKthNum(nums1,nums2,i,nums2Index+1,k-(nums2Index-j+1));
    }
}

  解決策4:データ・ストリームの中央値

  あなたが演習に関連した「安全性を証明するためにオファー」を行っている場合は、我々は中央のタイトルがあることも忘れてはならない、あなたはを参照することができます:安全オファー、中央値のデータ・ストリームを証明するために、[63] この問題では、データが1つのデータストリーム1から読み出したので、我々は適切なデータ構造を保存することを選択し、原因の中央値の性質のために、我々のデータ構造は、最大値と最小ヒープ・スタックの最良の選択ですので、 、右側のデータ・ストレージの最小ヒープと左の最大データ・ストレージ・ヒープ、と実装、および2つのスタック、データの最大ヒープの全てに平均データは、この時点では、データの最小ヒープ未満であることを保証するために、我々は、ヒープのトップを言いますデータは中央値です。

  質問に触発され、もちろん、我々はデータストリームとしてデータの2つの配列を置くことができ、順番に最大値と最小ヒープヒープに追加し、その後、あなたは問題が解決取得するアルゴリズムを使用することができますまだそのような時間の複雑性O(ログ(M + N) )が、余分なスペースの2つのスタックを使用します。

  :参照溶液の実装安全性を証明する[63]オファー、データ・ストリーム内のビットの数

  解決策5:シンコペーション、左から右へ、最小の最大値よりも小さいです

  4人用から、我々は、アレイ内の同じ効果を達成するために余分なスペースを使用することを考慮に失敗することはできません、これはでLeetCodeに与えられているこの問題の問題の解法アルゴリズムです。

  我々は、2つの半分にカットの位置に位置I、JアレイBで想定アレイAで切断1つの位置を切断することができる+ M mに0の合計の配列、長さmの配列を、切断し、そして場合I、J、左半分合成の左側の左側、iとjの右半分の右合成の右側にあります次いで四のための同様の、我々が保証できる場合に、(1)均等に両側に分けられたすべてのデータ、(2)左側の要素が右側の要素よりも小さい、次いでメディアンだけ左に右の最小値と最大値(に関連付けられます溶液4種類)のヒープの最上部に相当します。



  上記2つの条件にして、キー嘘を満たしているiとjの位置を決定する方法を検討するために、我々ポイント:

  (1)もしm + nが偶数の場合、データの数の左右に等しくなるべきであり、中央値は、/ 2(最大値+最小値の右半分の左半分)です。このとき、図では、我々は、すなわち、データのデータ長に等しい右長に任せることを見ることができi+j=m-i+n-j、それをj=(m+n)/2-i

  M + Nが奇数である場合(2)、次いで、大部分の右半分の左半分の長さの比、及び中央値は、左半分の最大値です。この場合、ある右長さに左のデータ長=データ+ 1、すなわちi+j = m-i + n-j + 1、それをj=(m+n+1)/2-i

  M + N(M + N)/ 2、(M + N + 1)/偶数のとき以来 2は同じであるので、上記の二つのケースが組み合わされてもよい、つまり、我々は、位置Iを決定するためにのみ必要jは位置を得ることができる、すなわち、満たしますj=(m+n+1)/2-i

  左の右に最小未満の最大値と同じくらい長く私と私たちの関係上記jが条件を考慮に基づいており、その後、第二の条件を考慮して、右の要素がより少ない取得するには、左側の要素、 。なぜなら、すなわち順序付けられた配列は、左2例にのみ一定の最大値、A [I-1]、のいずれかB [j-1]、右の最小値が必要があり、また2つだけのケースでは、いずれかのA [いずれかのi]は、どちらかB [j]は、同じ2例:

  (1)A [I-1]最大の左側が、それはより小さくなければならない場合には[I]及びB [J]未満は、第2の条件が満たされた場合のみ、B [j]は、比較され、より多くの場合B [j]は、すべきである。この時間私を低下は、Jを増加させた(大きいので、私は、小さなJ、次に[I-1] B [j]がより大きくなる場合。



  (2)B [J-1]最大の左側には、それはB [j]がより小さいこと、そして必要がある場合は場合のみ[i]が、比較される[i]は、第2の条件が満たされているよりも小さい、より場合より[i]は、この時間がなければならないIを増加させることが、J減少(iが小さい場合ので、より大きなjは、その後、Bは[J-1]のみ[i]がAよりも大きいです



  分析到这里,我们应该就知道如何去找合适的i的位置,我们可以通过二分的方法去查找,然后通过上述两种情况的比较增大或者减小i的范围,进而找到合适的i的位置,并通过j的公式求出j,这样我们就可以很容易的找到中位数

  注意:这里还有一个问题就是:由于 0 <= i <= m ,为了保证 0 <= j <= n ,我们必须保证 m <= n ,因此我们每次都是对较短的那个数组进行切分。

  因此,我们可以看到此解法和数据流中位数基于堆的解法实际上有异曲同工之妙,只不过数据结构不同。由于是对较短的数组进行二分,因此此算法的时间复杂度为O(log(min(m,n))).

    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
       //解法4:切分法,左边最大小于右边最小
        int m = nums1==null?0:nums1.length;
        int n = nums2==null?0:nums2.length;
        
        if(m>n) //保证m是小于n的
            return findMedianSortedArrays(nums2,nums1);
        
        //通过二分查找,在较短的那个数组里找一个位置i,使得左边最大小于右边最小
        int low=0,high=m;
        while(low<=high){
            int i=(low+high)/2;
            int j=(m+n+1)/2-i;
            if(j!=0 && i!=m && nums1[i] < nums2[j-1]) //此时应该增大i
                low=i+1;
            else if(i!=0 && j!=n && nums1[i-1]> nums2[j]) //此时应该减小i
                high=i-1;
            else{ //此时i符合条件,找到了i
                //找到左边最大的
                int maxLeft=0;
                if(i==0)
                    maxLeft=nums2[j-1];
                else if(j==0)
                    maxLeft=nums1[i-1];
                else
                    maxLeft=Math.max(nums1[i-1],nums2[j-1]);
                
                if((m+n)%2!=0) //奇数,左边最大的就是中位数
                    return maxLeft;
                
                //找到右边最小的
                int minRight=0;
                if(i==m)
                    minRight=nums2[j];
                else if(j==n)
                    minRight=nums1[i];
                else
                    minRight=Math.min(nums1[i],nums2[j]);
                return (maxLeft+minRight)/2.0;  
            }
        }
        return 0.0;
    }

总结

  作为第一道hard级别的题目,此题确实是有一定难度的,我们给出的五种解法中,时间复杂度逐步降低,最重要的是这里体会到二分查找的灵活运用,建议先看一下剑指Offer:数据流的中位数那道题目,对比这里的解法四和解法五可以比较容易理解。

おすすめ

転載: www.cnblogs.com/gzshan/p/10967691.html