【常用算法思路分析系列】排序高频题集
来源:互联网 发布: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范围内整数。保证n和m均小于等于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
- 【常用算法思路分析系列】排序高频题集
- 【常用算法思路分析系列】字符串高频题集
- 【常用算法思路分析系列】栈和队列高频题集(修改版)
- 【常用算法思路分析系列】链表相关高频题集
- 【常用算法思路分析系列】与二分搜索相关高频题
- 算法笔试面试高频题之二-(排序算法)
- 常用排序算法分析
- 常用排序算法分析
- 常用排序算法分析
- 常用排序算法、时间复杂度、实现思路
- 排序算法相关的笔试面试高频题
- 常用的排序算法分析
- 常用排序算法稳定性分析
- 常用排序算法稳定性分析
- 常用排序算法稳定性分析
- 常用排序算法稳定性分析
- 常用排序算法简要分析
- 常用排序算法稳定性分析
- Spring MVC事务配置
- 手机开发实战19——GPRS接入方式
- 基于Dubbo框架构建分布式服务(第一天)
- Eclipse搭建SSH(Struts2+Spring3+Hibernate3)框架项目教程
- 模板
- 【常用算法思路分析系列】排序高频题集
- 2015 Visible Side Assist Driving System advanced driver assistance system
- html中table的基本格式
- handler机制的原理
- php中session的用法
- log4j mybatis 日志
- java中NIO总结
- USB设备驱动开发之远程访问USB设备(一USB设备数据采集端)
- uva1601(双向BFS经典题)