leetcode第四题——寻找两个正序数组的中位数

前馈知识:

  所有排序总结与二分查找

    1.冒泡排序

    冒泡排序是所有排序方法中最简单,最易懂的排序,示意图如下:

    

  public class BubbleSort{

    public static void sort(int[] arr){

      for(int i=0;i<arr.length-1;i++){

        for(int j=arr.length-1;j>i;j--){

          if(arr[j]<arr[j-1]){

            swap(arr,j-1,j);

}      

}

}

  public static void swap(arr,i,j){

    tmp=arr[j];

    arr[j]=arr[i];

    arr[i]=tmp;

}

  分析:时间复杂度为o(n^2),空间复杂度为o(n).

  2.选择排序

    选择排序与冒泡排序比较类似,可以认为是选择排序是对于冒泡排序的一种优化。选择排序在确定了最小的数后才进行交换。

    

  public class SelectSort{

public static void sort(int[] arr){
        if(arr==null||arr.length==0){
            return;
        }
        for(int i=0;i<arr.length;i++){
           int minIndex=i;
           for(int j=i+1;j<arr.length;j++){
               if(arr[j]<arr[minIndex]){
                   minIndex=j;
               }
           }
           if(minIndex!=i){
               swap(arr,i,minIndex);
           }
        }

    }
    public static void swap(int[] arr, int i,int j){
        int tmp=arr[i];
        arr[i]=arr[j];
        arr[j]=tmp;
    }

}

    总结:如何更好理解选择排序与冒泡排序?

    冒泡排序拆解:

      i=0时,冒泡排序是从最后一位开始计算,与前一位进行比较,得出最小值后与i=0所在的值互换;

      接下来i=1,继续进行交换。

    选择排序拆解:

      i=0时,设i=minindex,则向后遍历,找到最小的数之后互换。

      i=1时,设i=minIndex,则依旧同上,进行交换。

    3.插入排序

      插入排序跟扑克牌有相似之处,插入排序要求插入数之前的数必须有序。

      确定第一个数,再跟第二个数比较,先后移,在插入。

      

public class InsertSort {
    public static void sort(int[] arr){
        if(arr==null||arr.length==0){
            return;
        }
        for(int i=1;i<arr.length;i++){
            int j=i;

            int target=arr[i];
            /**
             *
             *
             * 后移操作
             */
            while(j>0&&target<arr[j-1]){
                arr[j]=arr[j-1];
                j--;
            }
            /**
             *
             *
             * 插入操作
             */
            arr[j]=target;

        }



    }

    public static void main(String[] args) {

    }
}

    4.快速排序(quickSort)

    pivot:支点,中心

    思想:冒泡+二分+递归分治 

    上面留下来了一个问题为什么一定要j指针先动呢?首先这也不是绝对的,这取决于基准数的位置,因为在最后两个指针相遇的时候,要交换基准数到相遇的位置。一般选取第一个数作为基准数,那么就是在左边,所以最后相遇的数要和基准数交换,那么相遇的数一定要比基准数小。所以j指针先移动才能先找到比基准数小的数。

    快速排序是不稳定的,其时间平均时间复杂度是O(nlgn)。

    

package sort;

public class QuickSort {
    public static int partition(int[] arr,int left,int right){
        int paviotKey=arr[left];
        int paviotPointer=left;
        
        //交换开始,大的放右边,小的放左边
        while(left<right){
            while(left<right&&arr[left]<=paviotKey){
                left++;
            }
            while(left<right&&arr[right]>=paviotKey){
                right++;
            }
            swap(arr,left,right);
        }
        swap(arr,paviotPointer,left);
        return left;
    }
    //现在left所在的索引是标准数所在的索引
    public static void quickSort(int[] arr,int left,int right){
        if(left>=right){
            return;
        }
        int paviotPos=partition(arr,left,right);
        quickSort(arr,0,paviotPos-1);
        quickSort(arr,paviotPos+1,right);
    }
    public static void sort(int[] arr,int left,int right){
        if(arr==null||arr.length==0){
            return;
        }
        quickSort(arr,0,arr.length-1);
    }
    
    public static void swap(int[] arr,int left,int right){
        int tmp=arr[left];
        arr[left]=arr[right];
        arr[right]=tmp;
    }
    
    
    
    
    
}

    堆排序的原理:

      升序排序,造个大顶堆,将顶元素与尾元素兑换,在构造大顶堆。

      

    

public class HeapSort {
    public static void heapSort(int[] arr){
        if(arr==null||arr.length==0){
            return;
        }
        int len=arr.length;
        buildMaxHeap(arr,len);
        //开始重置堆
        for(int i=len-1;i>0;i--){
            swap(arr,0,i);
            len--;
            heapify(arr,0,len);
        }




    }
    private static void buildMaxHeap(int[] arr,int len){
       //从最后一个非叶节点开始向前遍历,调整节点性质,使之成为大顶堆
        //注意:最后一个非叶节点的索引值为Math.floor(len/2)-1
        for(int i = (int) (Math.floor(len/2)-1); i>=0; i--){
            heapify(arr,i,len);
        }



    }
    private static void heapify(int[] arr,int i,int len){
        int left=2*i+1;
        int right=2*i+2;
        int largestIndex=i;
        if(left<len&&arr[left]>arr[largestIndex]){
            largestIndex=left;
        }
        if(right<len&&arr[right]>arr[largestIndex]){
            largestIndex=right;
        }
        if (largestIndex!=i){
            swap(arr,i,largestIndex);
            heapify(arr,largestIndex,len);
        }
        
    }
    public static void swap(int[] arr, int i,int j){
        int tmp=arr[i];
        arr[i]=arr[j];
        arr[j]=tmp;
    }
}

    堆排序的核心:建好大顶堆后最后一个子节点与根节点互换,重新组成新的大顶堆。

    因为堆排序无关乎初始序列是否已经排序已经排序的状态,始终有两部分过程,构建初始的大顶堆的过程时间复杂度为O(n),交换及重建大顶堆的过程中,需要交换n-1次,重建大顶堆的过程根据完全二叉树的性质,[log2(n-1),log2(n-2)...1]逐步递减,近似为nlogn。所以它最好和最坏的情况时间复杂度都是O(nlogn),空间复杂度O(1)。

    ##希尔排序:

      将序列分割成几个若干序列进行插入排序,再将序列合并再进行一次插入排序。

      

       希尔排序:按照/2来分配增量,接下来继续分小组进行插入排序。时间复杂度为O(n^1.3)

      归并排序:递归划分子问题,然后合并解决问题。

      

      

public class MergeSort {
    public static void mergeSort(int[] arr){
        mSort(arr,0,arr.length-1);
    }
    /**
     *
     *
     * 递归分治
     */
    public static void mSort(int[] arr,int left,int right){
        if(left>=right){
            return;
        }
        int mid=(left+right)/2;
        mSort(arr,0,mid);
        mSort(arr,mid+1,right);
        merge(arr,left,mid,right);

    }
    public static void merge(int[] arr,int left,int mid,int right){
        int[] tmp=new int[right-left+1];
        int i=left;
        int j=mid+1;
        int k=0;
        while(i<=mid&&j<=right){
            if(arr[i]<=arr[j]){
                tmp[k++]=arr[i++];
            }else{
                tmp[k++]=arr[j++];
            }
        }
        for(int p=0;p<arr.length;p++){
            arr[left+p]=tmp[p];
        }
    }

}

    ##计数排序:最特殊的排序,时间复杂度可以认为是线性的,即O(n).

    如果在面试中有面试官要求你写一个O(n)时间复杂度的排序算法,你千万不要立刻说:这不可能!虽然前面基于比较的排序的下限是O(nlogn)。但是确实也有线性时间复杂度的排序,只不过有前提条件,就是待排序的数要满足一定的范围的整数,而且计数排序需要比较多的辅助空间。其基本思想是,用待排序的数作为计数数组的下标,统计每个数字的个数。然后依次输出即可得到有序序列。

    计数排序经典例题:数组里有20个随机数,取值范围为从0到10,要求用最快的速度把这20个整数从小到大进行排序。

    思路:以取值范围为下标,统计数字,然后遍历数组。

    

public class CountSort {
    public static void countSort(int[] arr){
        if(arr==null||arr.length==0){
            return;
        }
        int max=max(arr);
        int[] count=new int[max+1];
        Arrays.fill(count,0);
        for(int i=0;i<=max;i++){
            count[arr[i]]++;
        }
        int k=0;
        for(int i=0;i<=max;i++){
            for(int j=0;j<count[i];j++){
                arr[k++]=i;
            }
        }
        
    }
    public static int max(int[] arr){
        int max=Integer.MIN_VALUE;
        for (int ele:arr) {
            if(ele>max){
                max=ele;
            }
        }
        return max;
    }
}

    ##桶排序:

    

    

public class BucketSort {
    /**
     *
     *
     * 桶排序
     */
    public static void bucketSort(int[] arr){
        if(arr==null&&arr.length==0){
            return;
        }
        int bucketNums=10;//默认桶的数量为10
        List<List<Integer>> buckets = new ArrayList<>();
        for(int i=0;i<10;i++){
            buckets.add(new LinkedList<Integer>());
        }
        //划分桶
        for(int i=0;i<arr.length;i++){
            buckets.get(f(arr[i])).add(arr[i]);//arr[i]/10对应相应的那个桶
        }
        //对桶进行排序
        for(int i=0;i<buckets.size();i++){
            if(!buckets.get(i).isEmpty()){
                Collections.sort(buckets.get(i));
            }
        }
        //还原排好序的数组
        int k=0;
        for (List<Integer> bucket : buckets) {
            for (Integer ele : bucket) {
                arr[k++]=ele;

            }
        }
        /**
         *
         *
         * 映射函数
         */

    }

    public static int f(int x){
        return x/10;
    }



}

    基数排序:也是一种较为复杂的排序。限定条件,限定映射条件的重要度。比如设定区分度。

    

public class RadixSort {
    public static void radixSort(int[] arr){
        if(arr==null||arr.length==0){
            return;


        }
        int maxBit = getMaxBit(arr);
        for(int i=1;i<maxBit;i++){
            List<List<Integer>> buf=distribute(arr,i);//分配
            collecte(arr,buf);//收集
        }









    }
    public static List<List<Integer>> distribute(int[] arr,int iBit){
        List<List<Integer>> buf = new ArrayList<List<Integer>>();
        for(int j=0;j<10;j++){
            buf.add(new LinkedList<Integer>());

        }
        for(int i=0;i<arr.length;i++){
            buf.get(getNBit(arr[i],iBit)).add(arr[i]);

        }
        return buf;


    }

    public static void collecte(int[] arr,List<List<Integer>> buf){
        int k=0;
        for (List<Integer> bucket : buf) {
            for (Integer ele : bucket) {
                arr[k++]=ele;
            }
        }

    }
    public static int getMaxBit(int[] arr){
        int max = Integer.MIN_VALUE;
        for (int ele : arr) {
            int len=(ele+"").length();
            if(len>max){
                max=len;
            }
        }
        return max;

    }
    /**
     *
     *
     *
     * 获取x的第n位,如果没有则为0
     */
    public static int getNBit(int x,int n){
       String sx=x+"";
       if(sx.length()<n){
           return 0;

       }else{
           return sx.charAt(sx.length()-n)-'0';
       }


    }


}

    

·    

      ##好了,接下来我们看看题目:

      

     ##备注:Integer.MIN_VALUE:极小值,几乎用不到。

        Integer.MAX_VALUE:极大值,同样几乎用不到。

    

public class Solution4 {
    public double findMedianSortedArrays(int[] nums1,int[] nums2){
        if(nums1.length>nums2.length){
            int[] tmp=nums1;
            nums1=nums2;
            nums2=tmp;
        }
        /**
         *
         *
         *
         * 为了便于比较,我们选择互换
         */

        int m=nums1.length;
        int n=nums2.length;
        //分割线左边要求的元素数量为m+n+1/2
        int totalLeft=(m+n+1)/2;


        //在nums1的区间内寻找恰当的分割线。
        //使得nums1[i-1]<=nums2[j]&&nums2[j-1]<=nums1[i]
        int left=0;
        int right=m;
        while(left<right){
            int i=left+(right-left+1)/2;//原因:如果不加一,则会无限循环,理由如下:
            /**if right=i-1;-->right=right/2-1;
             * if left=i;-->i=(3/4)*right-->永远小于right造成死循环
             *
             *
             *
             *
             *
             *
             */
            int j=totalLeft-i;
            if(nums1[i-1]>=nums2[j]){
                right=i-1;
            }else{
                left=i;
            }
        }
        int i=left;//第一个数组的边界,即分割线的右边第一个数的索引值,对应了前面的数数量
        int j=totalLeft-i;//第二个数组的边界
        int nums1LeftMax=i==0?Integer.MIN_VALUE:nums1[i-1];
        int nums1RightMin=i==m?Integer.MAX_VALUE:nums1[i];
        int nums2LeftMax=j==0?Integer.MIN_VALUE:nums2[j-1];
        int nums2RightMin=j==n?Integer.MAX_VALUE:nums2[j];
        
        
        if(((m+n)%2)==1){
            return Math.max(nums1LeftMax,nums2LeftMax);
            
        }else {
            return (double)((Math.max(nums1LeftMax,nums2LeftMax)+Math.min(nums1RightMin,nums2RightMin))/2);
        }

    }
    /**
     * 
     * 这种方法思路总结:因为两个数组是有序数组,则按照总数划分后,第二个索引大的数组大致划分完毕,
     * 则需要配置第一行数组,
     * 使得第一行的数组划分后的左边小于
     * 右边的任意一个数。继续二分查找,然而考虑到一些特殊情况,比如分割线在数组尽头的情况,
     * 这种也是可以在包含范围之内的。
     * 
     * 时间复杂度O(log(min)(m,n)),空间复杂度O(1)
     */
    
原文地址:https://www.cnblogs.com/resort-033/p/13538264.html