【常用算法思路分析系列】排序高频题集

来源:互联网 发布:ionic vue.js 编辑:程序博客网 时间:2024/06/05 08:06

最近在牛客上整理常用的一些算法思路,【常用算法思路分析系列】主要是针对一些高频算法笔试、面试题目的解题思路进行总结,大部分也给出了具体的代码实现,本篇文章是对排序相关题目的思路分析。


1、简单分类

首先对一些常用算法按空间复杂度进行分类如下:

O(1)
冒泡排序、选择排序、插入排序、希尔排序、堆排序
O(logN)~O(N)
快速排序
O(N)
归并排序
O(M)
计数排序、基数排序


2、对一个基本有序的有序的数组排序,选择哪种排序算法?

基本有序:指如果把数组排好序的话,每个元素移动的距离不超过K,并且K相对于数组长度很小。

(1)对于时间复杂度为O(N)的排序算法:
    (计数排序、基数排序)
对于上述两种算法,由于不知道数组中元素的表示范围,不适合

(2)对于时间复杂度为O(N2)的排序算法
    插入排序
    按题说明,基本有序的元素,每个元素移动的距离不超过K,因此插入排序中每个元素向前移动的距离也不会超过K,故此时插入排序的时间复杂度为O(N*K),属于O(N2)中这种情况下比较好的一种实现
    
(3)对于时间复杂度为O(N*logN)的排序算法    
    快速排序
    与数组原始顺序无关,每次都是随机选择一个元素进行划分,因此此时还是O(N*logN),属于O(N*logN)较好的一种。
    归并排序:
    同样,与数组原始顺序无关。也算好此时还是O(N*logN)。

最优的一种改进后的堆排序

(具体可看这篇总结文章:[大、小根堆应用总结一]堆排序的应用场景)

代码实现如下:

public static int[] heapSort(int[] A, int n, int k) {          if(A == null || A.length == 0 || n < k){              return null;          }          int[] heap = new int[k];          for(int i = 0; i < k; i++){              heap[i] = A[i];          }          buildMinHeap(heap,k);//先建立一个小堆          for(int i = k; i < n; i++){              A[i-k] = heap[0];//难处堆顶最小元素              heap[0] = A[i];              adjust(heap,0,k);          }          for(int i = n-k;i < n; i++){              A[i] = heap[0];              heap[0] = heap[k-1];              adjust(heap,0,--k);//缩小调整的范围          }          return A;      }      //建立一个小根堆      private static void buildMinHeap(int[] a, int len) {          for(int i = (len-1) / 2; i >= 0; i--){              adjust(a,i,len);          }      }      //往下调整,使得重新复合小根堆的性质      private static void adjust(int[] a, int k, int len) {          int temp = a[k];          for(int i = 2 * k + 1; i < len; i = i * 2 + 1){              if(i < len - 1 && a[i+1] < a[i])//如果有右孩子结点,并且右孩子结点值小于左海子结点值                  i++;//取K较小的子节点的下标              if(temp <= a[i]) break;//筛选结束,不用往下调整了              else{//需要往下调整                  a[k] = a[i];                  k = i;//k指向需要调整的新的结点              }          }          a[k] = temp;//本趟需要调整的值最终放到最后一个需要调整的结点处      }  

3、判断数组中是否有重复值,要求空间复杂度为O(1)

    (1) 如果没有空间复杂度限制,用哈希表实现。比如使用HashMap(或者利用字符的ASCII值作为下标的int数组),此时,时间复杂度为O(N),空间复杂度为O(N)。
    (2)对于有空间复杂度限制,我们可以先排序,再判断的思路。因为排好序后,重复值排在了相邻位置。

此时,问题就转化为了,空间复杂度限制为O(1)的情况下,考察经典排序算法,怎么实现一个最快的算法。
根据上面空间复杂度的统计,可以知道使用非递归实现的堆排序最快。(具体可看这篇总结文章:[大、小根堆应用总结一]堆排序的应用场景
实现代码如下:

public static boolean checkDuplicate(int[] a, int n) {        if(a == null || a.length == 0 || a.length == 1)            return false;        heapSort(a,n);        for(int i = 1; i < n; i++){            if(a[i] == a[i-1]){                return true;            }        }        return false;    }    private static void heapSort(int[] a,int n){        for(int i = (n-1) / 2; i >= 0; i--){            adjustDown(a,i,n);        }        int temp;        for(int i = n-1; i > 0; i--){//只需要n-1趟            temp = a[0];//交换堆顶元素            a[0] = a[i];            a[i] = temp;            adjustDown(a,0,i);        }    }    private static void adjustDown(int[] a , int k,int n){        int temp = a[k];        for(int i = 2 * k + 1; i < n; i = i * 2 + 1){            if(i < n-1 && a[i] < a[i+1])//有右孩子结点,并且有孩子结点值大于左海子结点值,将i指向右孩子                i++;            if(temp >= a[i])                 break;            else{//需要向下调整                a[k] = a[i];                k = i;//指向新的可能需要调整的结点            }        }        a[k] = temp;    }

4、把两个有序数组合并成一个数组,第一个数组空间正好可以容纳两个数组的元素

比如数组A、B有序,A可以容纳两个数组的元素。
思路:
可以从A、B的末尾开始遍历比较,把较大的元素放到A的末尾,依次下去...
这里的关键是从A、B的末尾开始,这样可以尽量把A有用的部分保留下来。
代码实现如下:

public static int[] mergeAB(int[] A, int[] B, int n, int m) {        if(A == null || B == null || A.length < n+m){            return null;        }        int k = n + m - 1;        int i = n - 1;//A的下标指示器        int j = m - 1;//B的下标        while(i >= 0 && j >= 0){            if(A[i] < B[j]){                A[k--] = B[j];                j--;            }else{                A[k--] = A[i];                i--;            }        }        if(j >= 0){//表示数据B中还有元素,将B中剩余的元素放到A的前面            while(k >= 0 && j >= 0){                A[k--] = B[j--];            }        }        return A;    }

5、荷兰国旗问题

对只包含0,1,2三种元素值的数组进行排序,使得所有的0都在1的左边,所有的1在中间,所有的2在1的右边。要求使用交换、原地排序,而不是利用计数进行排序。

本题主要过程与快排划分过程类似,定义两个指针i0和i2,分别指向0区域和2区域,从头开始遍历,遇到1,继续;遇到0,交换0区域的后一位(即1区域的第一位)和当前遍历指向的元素,然后0区域向后扩大一位;遇到2,交换2区域的前一位(即1区域)和当前遍历指向的元素,2区域向前扩大一位。

时间复杂度为O(n),空间复杂度为O(1)。代码实现如下:

public class ThreeColor {       public static void main(String[] args) {        int[] a = {1,1,0,2,1,0,1,0,2,1,2,1,1,0,2,2,1};        sortThreeColor(a,a.length);        for(int i = 0; i < a.length; i++){            System.out.print(a[i]+" ");        }    }    public static int[] sortThreeColor(int[] A, int n) {        int i0 = -1;//指向0区域的指针,0区域初始大小为0        int i2 = n;//指向2区域的指针,2区域初始大小为0        int temp;        for(int i = 0; i < i2; i++){//注意!!!,这里是i<i2,即小于2区域的位置处            if(A[i] == 1)                continue;            if(A[i] == 0){                //交换0区域的后一位和i指向的元素                temp = A[i];                A[i] = A[i0 + 1];                A[i0 + 1] = temp;                //0区域向后扩大一位                i0++;            }else if(A[i] == 2){                //交换2区域的前一位和i指向的元素                temp = A[i];                A[i] = A[i2 - 1];                A[i2 - 1] = temp;                //2区域向前扩大一位                i2--;                //注意!!!由于2区域前面的数据元素是没有经过搜索的,因此当把它交换过来的时候,指针i应该停留在原处,这里先-1在for中+1,相当于i没有变化                i--;            }        }        return A;    }}

6、有序矩阵(二维数组)查找

现在有一个行和列都排好序的矩阵,请设计一个高效算法,快速查找矩阵中是否含有值x。
给定一个int矩阵mat,同时给定矩阵大小nxm及待查找的数x,请返回一个bool值,代表矩阵中是否存在x。所有矩阵中数字及x均为int范围内整数。保证nm均小于等于1000。
具体思路分析请看:[剑指Offer]二维数组中的查找

时间复杂度为O(m+n),代码如下:

public static boolean findX(int[][] mat, int n, int m, int x) {        //从矩阵的左下角开始查找(只能是从左下角或者右上角,只有这两个角符合二叉排序树的特征)        int i = n - 1;        int j = 0;        while(i >= 0 && j < m){            if(mat[i][j] == x)                return true;            if(x < mat[i][j]){                i--;            }else{                j++;            }        }        return false;    }

7、最短排序子数组:对于一个数组,请设计一个高效算法计算需要排序的最短子数组的长度

给定一个int数组A和数组的大小n,计算这个数组需要排序的最短子数组长度。(原序列位置从0开始标号,若原序列有序,返回0)。保证A中元素均为正整数。
测试样例:
[1,4,6,5,9,10],6
返回:2

解法:
需要分别计算出前面已拍好序的位置,和后面已排好序的位置。
时间复杂度为O(N),空间复杂度为O(1),代码如下:
public static int shortestSubsequence(int[] A, int n) {        //left和right初始顺序一个相等!!!表示A可能初始有序        int left = 0;//跟随从右到左过程中需要排序的位置        int right = 0;//跟随从左到右过程中的一个需要排序的位置        if(A == null || n == 0 || n == 1){            return 0;        }        int max = A[0];        int min = A[n-1];        for(int i = 0; i < n; i++){//先从左到右遍历一遍,记录遍历中的最大值,将其与当前值进行比较            if(A[i] > max)                max = A[i];            if(A[i] < max)                right = i;//相当于是记录到最右边需要排序的位置        }        for(int i = n-1; i >= 0; i--){//再从右往左遍历,记录遍历中的最小值,将其与当前值进行比较            if(A[i] < min)                min = A[i];            if(A[i] > min)                left = i;//相当于是记录到最左边需要排序的位置        }        if(left == right)            return 0;        else            return right - left + 1;    }

8、相邻两数最大差值

有一个整形数组A,请设计一个复杂度为O(n)的算法,算出排序后相邻两数的最大差值。
最优解时间复杂度为O(N),空间复杂度为O(N),代码如下:

public static int maxGap(int[] A, int n) {        if(A == null || n < 2)            return 0;        int min = A[0];        int max = A[0];        for(int i = 1; i < n; i++){            if(A[i] > max)                max = A[i];            if(A[i] < min)                min = A[i];        }        float gap = (max - min) * 1.0f / n;//将max-min分为n等分        boolean[] hasNum = new boolean[n + 1];//当前桶编号是否有元素在里面        int[] maxs = new int[n + 1];//存放某个桶中的最大值        int[] mins = new int[n + 1];//存放某个桶中的最小值        for(int i = 0; i < n; i++){            int p = (int) ((A[i] - min) / gap);    //计算当前元素所属桶编号            if(hasNum[p]){//如果该桶编号已经有值                maxs[p] = maxs[p] < A[i] ? A[i] : maxs[p];                mins[p] = mins[p] > A[i] ? A[i] : mins[p];            }else{                maxs[p] = A[i];                mins[p] = A[i];            }            hasNum[p] = true;        }        int i = 0;        int res = 0;        int lastMax = 0;        while(i <= n){            if(hasNum[i++]){//找到第一个有元素的桶                lastMax = maxs[i-1];//                i++;                break;            }        }        while(i <= n){            if(hasNum[i]){                res = mins[i] - lastMax > res ? mins[i] - lastMax : res;                lastMax = maxs[i];            }            i++;        }        return res;    }


上面有些题目思路没有具体分析,但在代码实现中已加入了注释,手动模拟一遍应该没有问题。下一篇将总结【常用算法思路分析系列】字符串相关的题目。






7 0
原创粉丝点击