zoukankan      html  css  js  c++  java
  • 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)
         */
        
  • 相关阅读:
    http4j
    EmbeddedBrowser
    curl v www.linode.com查看请求及响应信息
    JRUN
    PAC Manager: Ubuntu 上强大的 SSH 帐号管理工具,可取代 SecureCRT
    centos下载地址
    Web应用调试:现在是Weinre和JSConsole,最终会是WebKit的远程调试协议
    用ClusterSSH管理多台Linux服务器(2)
    Ubuntu + IntelliJ + Maven + Jetty + JRebel
    Java的连接池程序
  • 原文地址:https://www.cnblogs.com/resort-033/p/13538264.html
Copyright © 2011-2022 走看看