标签:
最近在牛客上整理常用的一些算法思路,【常用算法思路分析系列】主要是针对一些高频算法笔试、面试题目的解题思路进行总结,大部分也给出了具体的代码实现,本篇文章是对排序相关题目的思路分析。
首先对一些常用算法按空间复杂度进行分类如下:
O(1):
冒泡排序、选择排序、插入排序、希尔排序、堆排序
O(logN)~O(N):
快速排序
O(N):
归并排序
O(M):
计数排序、基数排序
最优的一种:改进后的堆排序
(具体可看这篇总结文章:[大、小根堆应用总结一]堆排序的应用场景)
代码实现如下:
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;//本趟需要调整的值最终放到最后一个需要调整的结点处 }
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; }
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; }
对只包含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; } }
时间复杂度为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; }
[1,4,6,5,9,10],6
返回:2
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; }
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; }
上面有些题目思路没有具体分析,但在代码实现中已加入了注释,手动模拟一遍应该没有问题。下一篇将总结【常用算法思路分析系列】字符串相关的题目。
标签:
原文地址:http://blog.csdn.net/shakespeare001/article/details/51420086