最近在牛客上整理常用的一些算法思路,【常用算法思路分析系列】主要是针对一些高频算法笔试、面试题目的解题思路进行总结,大部分也给出了具体的代码实现,本篇文章是对排序相关题目的思路分析。
首先对一些常用算法按空间复杂度进行分类如下:
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
时间复杂度为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;
}
上面有些题目思路没有具体分析,但在代码实现中已加入了注释,手动模拟一遍应该没有问题。下一篇将总结【常用算法思路分析系列】字符串相关的题目。