选择排序
先从0~N上找出最小的交换到第一位 再从1~N上找出最小的交换到第二位 ……
public void selectionSort(int[] arr) {if (arr == null || arr.length < 2) {return;}for (int i = 0; i < arr.length - 1; i++) {int minIdx = i;for (int j = i + 1; j < arr.length; j++) {minIdx = arr[minIdx] > arr[j] ? j : minIdx;}swap(arr, i, minIdx);}}
冒泡排序
从第一个开始遇到后一个比自己小就与后一个交换位置
public void bubbleSort(int[] arr) {if (arr == null || arr.length < 2) {return;}for (int i = arr.length - 1; i > 0; i--) {for (int j = 0; j < i; j++) {if (arr[j] > arr[j + 1]) {ArrayUtils.swap(arr, j, j + 1);}}}}
插入排序
从头开始,每往后移动一位,就往前(已经移动过的)看,如果有比之前的数小,就交换 做到0 - i上有序
public void insertionSort(int[] arr) {if (arr == null || arr.length < 2) {return;}for (int i = 0; i < arr.length; i++) {for (int j = i; j > 0; j--) {if (arr[j] < arr[j - 1]) {ArrayUtils.swap(arr, j, j - 1);}}}}
归并排序
将整个数组分为两块儿 使其在左侧区域有序 使其在右侧区域有序 最后将两块儿区域进行合并,使得合并的区域有序(左右两个区域如果值相等,则先处理左侧区域)
public void mergeSort(int[] arr) {if (arr == null || arr.length < 2) {return;}process(arr, 0, arr.length - 1);}private void process(int[] arr, int l, int r) {if (l >= r) {return;}int mid = l + ((r - l) >> 1);//让左侧范围(l - mid)上有序process(arr, l, mid);//让右侧范围(mid+1 - r)上有序process(arr, mid + 1, r);//合并左右区域,使其在范围(l - r)上有序merge(arr, l, mid, r);}private void merge(int[] arr, int l, int mid, int r) {int[] help = new int[r - l + 1];//help数组的下标索引int hIdx = 0;//左侧区域的起始位置(结束位置为mid)int idxL = l;//右侧区域的起始位置(结束位置为r)int idxR = mid + 1;while (idxL <= mid && idxR <= r) {help[hIdx++] = arr[idxL] <= arr[idxR] ? arr[idxL++] : arr[idxR++];}while (idxL <= mid) {help[hIdx++] = arr[idxL++];}while (idxR <= r) {help[hIdx++] = arr[idxR++];}//将有序数据刷回源数组for (int i = 0; i < help.length; i++) {arr[l++] = help[i];}}
给定一个数组arr,求数组小和
在一个数组中,一个数左边比它小的数的总和,叫该数的小和,所有数的小和累加起来,叫数组小和 例子: [1,3,4,2,5] 1左边比1小的数:没有 3左边比3小的数:1 4左边比4小的数:1、3 2左边比2小的数:1 5左边比5小的数:1、3、4、 2 所以数组的小和为1+1+3+1+1+3+4+2=16 给定一个数组arr,求数组小和
解题思路:
将左边有多少个数比我小,转换为右边有多少个数比我大 在归并排序merge方法的基础上, 当拷贝左组值是产生小和,拷贝右组值时不产生小和 当左组数和右组数相等时,先拷贝右组数
public int smallSum(int[] arr) {if (arr == null) {return 0;}return process(arr, 0, arr.length - 1);}private int process(int[] arr, int l, int r) {if (l >= r) {return 0;}int mid = l + ((r - l) >> 1);int leftMinSum = process(arr, l, mid);int rightMinSum = process(arr, mid + 1, r);int currMinSum = merge(arr, l, mid, r);return leftMinSum + rightMinSum + currMinSum;}private int merge(int[] arr, int l, int mid, int r) {int[] help = new int[r - l + 1];int idx = 0;int lIdx = l;int rIdx = mid + 1;int minSum = 0;while (lIdx <= mid && rIdx <= r) {//拷贝左组产生小和minSum += arr[lIdx] < arr[rIdx] ? arr[lIdx] * (r - rIdx + 1) : 0;//相等时先拷贝右组help[idx++] = arr[lIdx] < arr[rIdx] ? arr[lIdx++] : arr[rIdx++];}while (lIdx <= mid) {help[idx++] = arr[lIdx++];}while (rIdx <= r) {help[idx++] = arr[rIdx++];}for (int i = 0; i < help.length; i++) {arr[l++] = help[i];}return minSum;}
给定一个数组arr,求数组的降序对总数量
在一个数组中,任何一个前面的数a,和任何一个后面的数b,如果(a,b)是降序的,就称为降序对
解题思路:
将右边有多少个数比我小,转换为左边有多少个数比我大 在归并排序merge方法的基础上, 当拷贝右组值是产生逆序对,拷贝左组值时不产生逆序对 当左组数和右组数相等时,先拷贝左组数
public int reverPairNumber(int[] arr) {if (arr == null || arr.length < 2) {return 0;}return process(arr, 0, arr.length - 1);}private int process(int[] arr, int l, int r) {if (l >= r) {return 0;}int mid = l + ((r - l) >> 1);int lCount = process(arr, l, mid);int rCount = process(arr, mid + 1, r);int currCount = merge(arr, l, mid, r);return lCount + rCount + currCount;}private int merge(int[] arr, int l, int mid, int r) {int[] help = new int[r - l + 1];int idx = 0;int lIdx = l;int rIdx = mid + 1;int count = 0;while (lIdx <= mid && rIdx <= r) {//当移动右组数据时产生逆序对count += arr[lIdx] > arr[rIdx] ? (mid - lIdx + 1) : 0;//当左组和右组数据相等时先移动左组help[idx++] = arr[lIdx] <= arr[rIdx] ? arr[lIdx++] : arr[rIdx++];}while (lIdx <= mid) {help[idx++] = arr[lIdx++];}while (rIdx <= r) {help[idx++] = arr[rIdx++];}for (int i = 0; i < help.length; i++) {arr[l++] = help[i];}return count;}
在一个数组中,对于任何一个数num,求有多少个(后面的数*2)依然<num,返回总个数
比如:[3,1,7,0,2] 3的后面有:1,0 1的后面有:0 7的后面有:0,2 0的后面没有 2的后面没有 所以总共有5个
解题思路: 原有的归并排序不改变 在merge之前,先通过滑动索引计算右边有多少个数*2小于左边的数
public int reversePairs(int[] arr) {if (arr == null || arr.length < 2) {return 0;}return process(arr, 0, arr.length - 1);}private int process(int[] arr, int l, int r) {if (l >= r) {return 0;}int mid = l + ((r - l) >> 1);int lCount = process(arr, l, mid);int rCount = process(arr, mid + 1, r);int currCount = merge(arr, l, mid, r);return lCount + rCount + currCount;}private int merge(int[] arr, int l, int mid, int r) {int count = 0;// [L....M] [M+1....R]// 目前囊括进来的数,是从[M+1, windowR)int windowR = mid + 1;for (int i = l; i <= mid; i++) {while (windowR <= r && (long) arr[i] > (long) arr[windowR] * 2) {windowR++;}count += windowR - mid - 1;}int[] help = new int[r - l + 1];int idx = 0;int lIdx = l;int rIdx = mid + 1;while (lIdx <= mid && rIdx <= r) {help[idx++] = arr[lIdx] <= arr[rIdx] ? arr[lIdx++] : arr[rIdx++];}while (lIdx <= mid) {help[idx++] = arr[lIdx++];}while (rIdx <= r) {help[idx++] = arr[rIdx++];}for (int i = 0; i < help.length; i++) {arr[l++] = help[i];}return count;}
给定一个数组arr,两个整数lower和upper,返回arr中有多少个子数组的累加和在[lower,upper]范围上
leetcode:https://leetcode-cn.com/problems/count-of-range-sum/ 1)利用前缀和 2)原数组[lower,upper]这个问题 转换为 在前缀和数组中求新指标问题,前缀和数组中每个数X新指标为[x-upper,x-lower] 3)前缀数组的每一个x,在x之前有多少个数是在新指标[x-upper,x-lower]上
public int countRangeSum(int[] arr, int lower, int upper) {long[] sum = new long[arr.length];sum[0] = arr[0];//求前缀和for (int i = 1; i < arr.length; i++) {sum[i] = sum[i - 1] + arr[i];}return process(sum, 0, sum.length - 1, lower, upper);}private int process(long[] arr, int l, int r, long lower, long upper) {if (l == r) {//arr[l] 表示区间[0,l]的区间和return arr[l] >= lower && arr[l] <= upper ? 1 : 0;}int mid = l + ((r - l) >> 1);int lCount = process(arr, l, mid, lower, upper);int rCount = process(arr, mid + 1, r, lower, upper);int mergeCount = merge(arr, l, mid, r, lower, upper);return lCount + rCount + mergeCount;}private int merge(long[] arr, int l, int mid, int r, long lower, long upper) {int mergeCount = 0;int windowL = l;int windowR = l;//将问题转换为针对右组任意数x,左组中有多少个数在区间[x-upper,x-lower]上//指针不回退,时间复杂度O(N)for (int i = mid + 1; i <= r; i++) {long min = arr[i] - upper;long max = arr[i] - lower;while (windowL <= mid && arr[windowL] < min) {windowL++;}while (windowR <= mid && arr[windowR] <= max) {windowR++;}mergeCount += windowR - windowL;}long[] help = new long[r - l + 1];int idx = 0;int lIdx = l;int rIdx = mid + 1;while (lIdx <= mid && rIdx <= r) {help[idx++] = arr[lIdx] <= arr[rIdx] ? arr[lIdx++] : arr[rIdx++];}while (lIdx <= mid) {help[idx++] = arr[lIdx++];}while (rIdx <= r) {help[idx++] = arr[rIdx++];}for (int i = 0; i < help.length; i++) {arr[l++] = help[i];}return mergeCount;}
荷兰国旗问题引出快排
给定一个数组,小于数组最后一个数的值放左边,等于数组最后一个数的值放中间,大于数组最后一个数的值放右边
public int[] partition(int[] arr) {return process(arr, 0, arr.length - 1);}public int[] process(int[] arr, int l, int r) {if (l > r) {return new int[]{-1, -1};}int idx = l;int lessIdx = l - 1;int ratherThanIdx = r;while (idx < ratherThanIdx) {if (arr[idx] == arr[r]) {idx++;} else if (arr[idx] < arr[r]) {swap(arr, idx++, ++lessIdx);} else {swap(arr, idx, --ratherThanIdx);}}swap(arr, ratherThanIdx, r);return new int[]{lessIdx + 1, ratherThanIdx};}
快排(递归版本)
时间复杂度最好:O(N*logN),最差:O(N2) 额外空间复杂度最好:O(logN),最差:O(N)
public void quickSort(int[] arr) {if (arr == null || arr.length < 2) {return;}partition(arr, 0, arr.length - 1);}public void partition(int[] arr, int l, int r) {if (l >= r) {return;}//随机一个数与范围上的最后一个数交换int randomIdx = (int) (Math.random() * (r - l + 1)) + l;swap(arr, randomIdx, r);int[] process = process(arr, l, r);partition(arr, l, process[0] - 1);partition(arr, process[1] + 1, r);}public int[] process(int[] arr, int l, int r) {if (l > r) {return new int[]{-1, -1};}if (l == r) {return new int[]{l, r};}int idx = l;//小于区域int lessIdx = l - 1;//大于区域int ratherThanIdx = r;while (idx < ratherThanIdx) {if (arr[idx] == arr[r]) {idx++;} else if (arr[idx] < arr[r]) {swap(arr, idx++, ++lessIdx);} else {swap(arr, idx, --ratherThanIdx);}}swap(arr, ratherThanIdx, r);return new int[]{lessIdx + 1, ratherThanIdx};}
快排(栈实现版本)
private static class Op {private int l;private int r;public Op(int l, int r) {this.l = l;this.r = r;}}public void quickSort2(int[] arr) {if (arr == null || arr.length < 2) {return;}Stack<Op> stack = new Stack<>();int random = (int) (Math.random() * (arr.length));swap(arr, random, arr.length - 1);int[] partition = partition(arr, 0, arr.length - 1);stack.push(new Op(0, partition[0] - 1));stack.push(new Op(partition[1] + 1, arr.length - 1));while (!stack.isEmpty()) {Op op = stack.pop();if (op.l < op.r) {random = op.l + (int) (Math.random() * (op.r - op.l + 1));swap(arr, random, op.r);partition = partition(arr, op.l, op.r);stack.push(new Op(op.l, partition[0] - 1));stack.push(new Op(partition[1] + 1, op.r));}}}private int[] partition(int[] arr, int l, int r) {if (l > r) {return new int[]{-1, -1};}if (l == r) {return new int[]{l, r};}int lIdx = l - 1;int rIdx = r;int idx = l;while (idx < rIdx) {if (arr[idx] < arr[r]) {swap(arr, idx++, ++lIdx);} else if (arr[idx] > arr[r]) {swap(arr, idx, --rIdx);} else {idx++;}}swap(arr, rIdx, r);return new int[]{lIdx + 1, rIdx};}private void swap(int[] arr, int i, int j) {int temp = arr[i];arr[i] = arr[j];arr[j] = temp;}
快排(队列实现版本)
private static class Op {private int l;private int r;public Op(int l, int r) {this.l = l;this.r = r;}}public static void quickSort3(int[] arr) {if (arr == null || arr.length < 2) {return;}Queue<Op> queue = new LinkedList<>();int random = (int) (Math.random() * (arr.length));swap(arr, random, arr.length - 1);int[] partition = partition(arr, 0, arr.length - 1);queue.offer(new Op(0, partition[0] - 1));queue.offer(new Op(partition[1] + 1, arr.length - 1));while (!queue.isEmpty()) {Op op = queue.poll();if (op.l < op.r) {random = op.l + (int) (Math.random() * (op.r - op.l + 1));swap(arr, random, op.r);partition = partition(arr, op.l, op.r);queue.offer(new Op(op.l, partition[0] - 1));queue.offer(new Op(partition[1] + 1, op.r));}}}private int[] partition(int[] arr, int l, int r) {if (l > r) {return new int[]{-1, -1};}if (l == r) {return new int[]{l, r};}int lIdx = l - 1;int rIdx = r;int idx = l;while (idx < rIdx) {if (arr[idx] < arr[r]) {swap(arr, idx++, ++lIdx);} else if (arr[idx] > arr[r]) {swap(arr, idx, --rIdx);} else {idx++;}}swap(arr, rIdx, r);return new int[]{lIdx + 1, rIdx};}private void swap(int[] arr, int i, int j) {int temp = arr[i];arr[i] = arr[j];arr[j] = temp;}
堆排序
// 堆排序额外空间复杂度O(1)public static void heapSort(int[] arr) {if (arr == null || arr.length < 2) {return;}// O(N*logN)// for (int i = 0; i < arr.length; i++) { // O(N)// heapInsert(arr, i); // O(logN)// }// O(N)for (int i = arr.length - 1; i >= 0; i--) {heapify(arr, i, arr.length);}int heapSize = arr.length;// O(N*logN)while (heapSize > 0) { // O(N)swap(arr, 0, --heapSize); // O(1)heapify(arr, 0, heapSize); // O(logN)}}// arr[index]刚来的数,往上public void heapInsert(int[] arr, int index) {while (arr[index] > arr[(index - 1) / 2]) {swap(arr, index, (index - 1) / 2);index = (index - 1) / 2;}}// arr[index]位置的数,能否往下移动public void heapify(int[] arr, int index, int heapSize) {int left = index * 2 + 1; // 左孩子的下标while (left < heapSize) { // 下方还有孩子的时候// 两个孩子中,谁的值大,把下标给largest// 1)只有左孩子,left -> largest// 2) 同时有左孩子和右孩子,右孩子的值<= 左孩子的值,left -> largest// 3) 同时有左孩子和右孩子并且右孩子的值> 左孩子的值, right -> largestint largest = left + 1 < heapSize && arr[left + 1] > arr[left] ? left + 1 : left;// 父和较大的孩子之间,谁的值大,把下标给largestlargest = arr[largest] > arr[index] ? largest : index;if (largest == index) {break;}swap(arr, largest, index);index = largest;left = index * 2 + 1;}}public void swap(int[] arr, int i, int j) {int tmp = arr[i];arr[i] = arr[j];arr[j] = tmp;}
已知一个几乎有序的数组,请选择一个合适的排序策略,对这个数组进行排序
几乎有序是指,如果把数组排好顺序的话,每个元素移动的距离一定不超过k,k相对于数组长度来说是比较小的
解题思路:时间复杂度O(N*logk) 建立一个大小为k+1 的小根堆,并将堆填充满 然后依次弹出一个再增加一个 没有多余的数添加后依次弹出堆里的数
给定很多线段,每个线段都有两个数[start, end],表示线段开始位置和结束位置,左右都是闭区间,返回线段最多重合区域中,包含了几条线段
规定: 1)线段的开始和结束位置一定都是整数值 2)线段重合区域的长度必须>=1
解题思路:
- 方案一
- 找出所有线段中开始位置的最小值,结束位置的最大值
- 从最小值开始依次遍历到最大值,将当前数加0.5,再依次看所有线段有多少经过该点
- 记录经过点最大的次数就是答案
- 方案二
- 将线段开始位置进行升序排列
- 准备小根堆,遍历排完序的所有线段
- 将堆中小于等于当前线段开始位置的数从堆中全部弹出
- 将当前线段结束位置加入堆中
- 此时堆中的元素个数就是与当前线段重合的线段数
public int maxCover(int[][] m) {Line[] lines = new Line[m.length];for (int i = 0; i < m.length; i++) {lines[i] = new Line(m[i][0], m[i][1]);}//先按照每条线段的开始位置进行升序排序Arrays.sort(lines, new StartComparator());// 小根堆,每一条线段的结尾数值,使用默认的PriorityQueue<Integer> heap = new PriorityQueue<>();int max = 0;//小于等于线段开始位置的数都从堆中弹出,最后将自己结束位置加入堆for (int i = 0; i < lines.length; i++) {// lines[i] -> cur 在黑盒中,把<=cur.start 东西都弹出while (!heap.isEmpty() && heap.peek() <= lines[i].start) {heap.poll();}heap.add(lines[i].end);//此时堆中数据的多少就代表该线段与多少线段重合max = Math.max(max, heap.size());}return max;}public class Line {public int start;public int end;public Line(int s, int e) {start = s;end = e;}}public class StartComparator implements Comparator<Line> {@Overridepublic int compare(Line o1, Line o2) {return o1.start - o2.start;}}
桶排序(计数排序)
一般要求样本是整数,且范围比较窄; 要求有改变,则改动很大
// only for 0~200 valuepublic static void countSort(int[] arr) {if (arr == null || arr.length < 2) {return;}int max = Integer.MIN_VALUE;for (int i = 0; i < arr.length; i++) {max = Math.max(max, arr[i]);}int[] bucket = new int[max + 1];for (int i = 0; i < arr.length; i++) {bucket[arr[i]]++;}int i = 0;for (int j = 0; j < bucket.length; j++) {while (bucket[j]-- > 0) {arr[i++] = j;}}}
基数排序
一般要求样本是10进制的正整数 要求有改变,则改动很大 步骤:
- 找出数组中最大的元素,并计算有多少位;如{12, 3, 45, 103, 265},最大值为265,最大位数3位
- 准备10个桶0-9,然后遍历源数组,并取出元素个位数字,然后将数据放入个位数字对应的桶中;如12,个位数为2,则12放入2号桶
- 将所有元素都放入桶之后,从0-9遍历10个桶,从每个桶中取出第二步放进去的数据(先进先出)
- 然后使用相同的方法(重复2、3步)处理十位、百位、千位……直到处理完最大位数,如果一个数在当前处理的位上没值,则记为0
// only for no-negative valuepublic static void radixSort(int[] arr) {if (arr == null || arr.length < 2) {return;}radixSort(arr, 0, arr.length - 1, maxbits(arr));}public static int maxbits(int[] arr) {int max = Integer.MIN_VALUE;for (int i = 0; i < arr.length; i++) {max = Math.max(max, arr[i]);}int res = 0;while (max != 0) {res++;max /= 10;}return res;}// arr[L..R]排序 , 最大值的十进制位数digitpublic static void radixSort(int[] arr, int L, int R, int digit) {final int radix = 10;int i = 0, j = 0;// 有多少个数准备多少个辅助空间int[] help = new int[R - L + 1];for (int d = 1; d <= digit; d++) { // 有多少位就进出几次// 10个空间// count[0] 当前位(d位)是0的数字有多少个// count[1] 当前位(d位)是(0和1)的数字有多少个// count[2] 当前位(d位)是(0、1和2)的数字有多少个// count[i] 当前位(d位)是(0~i)的数字有多少个int[] count = new int[radix]; // count[0..9]for (i = L; i <= R; i++) {// 103 1 3// 209 1 9j = getDigit(arr[i], d);count[j]++;}for (i = 1; i < radix; i++) {count[i] = count[i] + count[i - 1];}for (i = R; i >= L; i--) {j = getDigit(arr[i], d);help[count[j] - 1] = arr[i];count[j]--;}for (i = L, j = 0; i <= R; i++, j++) {arr[i] = help[j];}}}public static int getDigit(int x, int d) {return ((x / ((int) Math.pow(10, d - 1))) % 10);}
