5.1 常用的排序算法
快速排序(Quicksort)
此处采用左闭右闭的二分写法。
private static void quickSort(int[] nums, int start, int end){
if(start+1 >= end) return;
int left=start, right=end-1, key=nums[left];
while (left < right){
while (left<right && nums[right]>= key){
right--;
}
nums[left] = nums[right];
while (left<right && nums[left]<=key){
left++;
}
nums[right] = nums[left];
}
nums[left] = key;
quickSort(nums, start, left);
quickSort(nums, left+1, end);
}
归并排序(Merge Sort)
private static void mergeSort(int[] nums, int[] tmp, int left, int right){
if(left>=right) return;
int mid = (left+right)/2;
// 二路归并,所以是2个mergeSort()
mergeSort(nums, tmp, left, mid);
mergeSort(nums, tmp, mid+1, right);
merge(nums, tmp, left, mid, right);
}
private static void merge(int[] nums, int[] tmp, int start, int mid, int end){
int left_start=start, right_start=mid+1, i=0;
while (left_start<=mid && right_start<= end){
// tmp中为升序,挑取nums[]左右两分支中的,小的那个(从左往右遍历)
tmp[i++] = nums[left_start]<nums[right_start]? nums[left_start++]: nums[right_start++];
}
// 若左边分支还有剩余,则全部拷贝进tmp[]
while (left_start<=mid){
tmp[i++] = nums[left_start++];
}
// 若右边分支还有剩余,全部拷贝进tmp[]
while (right_start<=end){
tmp[i++] = nums[right_start++];
}
// tmp[:i] => nums[start: start+i] 左闭右开
System.arraycopy(tmp, 0, nums, start, i);
}
插入排序(Insertion Sort)
private static void insertionSort(int[] nums){
int tmp;
for(int i=0; i<nums.length; i++){
for(int j=i; j>0 && nums[j]<nums[j-1]; j--){
tmp = nums[j];
nums[j] = nums[j-1];
nums[j-1] = tmp;
}
}
}
冒泡排序(Bubble Sort)
private static void bubbleSort(int[] nums){
boolean swapped;
int tmp;
for(int i=1; i<nums.length; i++){
swapped = false;
for(int j=1; j<(nums.length-i)+1; j++){
if(nums[j] < nums[j-1]){
tmp = nums[j];
nums[j] = nums[j-1];
nums[j-1] = tmp;
swapped = true;
}
}
if(!swapped) break;
}
}
选择排序(Selection Sort)
private static void selectionSort(int[] nums){
int mid, tmp;
for(int i=0; i<nums.length-1; i++){
mid = i;
for(int j=i+1; j<nums.length; j++){
if(nums[j] < nums[mid]){
mid = j;
}
}
tmp = nums[mid];
nums[mid] = nums[i];
nums[i] = tmp;
}
}
5.2 快速选择
-
Kth Largest Element in an Array
题目描述
在一个未排序的数组中,找到第K大的数字。
输入输出样例
输入一个数组和一个目标值K,输出第K大的数字,默认有解。
Input: [3,2,1,5,6,4] and k = 2 Output: 5
题解
快速选择一般用于求解Kth Element问题,可以在O(n)时间复杂度和O(1)空间复杂度完成求解。
快速选择的实现和快速排序相似,只不过需要找到第K大的枢(pivot)即可,不需要对其左右再进行排序,与快速排序一样,快速选择一般需要先打乱数组,否则最坏情况下时间复杂度为O(n^2)。此处省略打乱步骤。
private static int quickSelect(int[] nums, int k){ int left=0, right=nums.length-1, mid, target=nums.length-k; while (left < right){ mid = helpFunc(nums, left, right); if(mid < target){ left = mid + 1; }else if(mid == target){ return nums[mid]; }else{ right = mid + 1; } } return nums[left]; } // 辅助函数 - 快速选择 private static int helpFunc(int[] nums, int left, int right){ int i=left+1, j=right, tmp; while (true){ while (i<right && nums[i]<=nums[left]){ i++; } while (left<j && nums[j]>=nums[left]){ j--; } if(i>=j) break; tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; } tmp = nums[left]; nums[left] = nums[j]; nums[j] = tmp; return j; }
5.3 桶排序
-
Top K Frequent Elements(Medium)
题目描述
给定一个数组,求前K个最频繁的数字。
输入输出样例
输入是一个数组和一个目标值K,输出是一个长度为K的数组。
Input: nums = [1,1,1,1,2,2,3,4], k = 2 Output: [1,2]
题解
顾名思义,桶排序的意思是为每个值设立一个桶,桶内记录这个值出现的次数(或其他属性),然后对桶进行排序。先通过桶排序得到4个桶[1,2,3,4],他们的值分别为[4,2,1,1],表示每个数字出现的次数。
紧接着,按桶内的属性对4个桶进行排序(升序),这里可以使用任意排序算法,后K个桶就是需要的。
private static Integer[] topKFrequent(int[] nums, int k){ HashMap<Integer, Integer> map = new HashMap<>(); for(int num: nums){ map.put(num, map.containsKey(num)? map.get(num)+1: 0); } // 桶数组->map的键数组 Integer[] keys = map.keySet().toArray(new Integer[0]); // 按值的大小,即出现的频率排序,并截取前k个 return Arrays.stream(keys).sorted(new Compare(map)).limit(k).toArray(Integer[]::new); } private static class Compare implements Comparator<Integer>{ private final HashMap<Integer, Integer> map; public Compare(HashMap<Integer, Integer> map) { this.map = map; } // 按map的value降序排列 @Override public int compare(Integer o1, Integer o2) { return map.get(o2)-map.get(o1); } }
5.4 练习
基础难度
-
Sort Characters By Frequency(Medium)
桶排序的变形题。
题目描述
给定一个字符串,按照字符的出现频率降序排列。
输入输出样例
Input: "tree" Output: "eert" Explanation: 'e' appears twice while 'r' and 't' both appear once. So 'e' must appear before both 'r' and 't'. Therefore "eetr" is also a valid answer.
题解
和桶排序思路相同,只不过第二步从对桶的排序变成了对原数组的排序。
private static String sortCharacterByFreq(String str){ HashMap<Character, Integer> map = new HashMap<>(); for (char c: str.toCharArray()) { map.put(c, map.containsKey(c)? map.get(c)+1: 0); } // 原字符串转Character[] Character[] keys = str.chars().mapToObj(c->(char)c).toArray(Character[]::new); StringBuilder stringBuilder = new StringBuilder(); Arrays.stream(keys).sorted(new Compare(map)).forEach(stringBuilder::append); return stringBuilder.toString(); } // 按字符出现的频率降序排列 private static class Compare implements Comparator<Character>{ private final HashMap<Character, Integer> map; public Compare(HashMap<Character, Integer> map) { this.map = map; } @Override public int compare(Character o1, Character o2) { return map.get(o2)-map.get(o1); } }
进阶难度
-
Sort Colors(Medium)
经典的荷兰国旗问题,考察如何对三个重复且打乱的值进行排序。
题目描述
给一个数组,里面有红白蓝三种颜色,分别使用0,1,2来表示。要求对它进行排序,相同颜色需要挨着,不同颜色需要间隔。不能使用库的sort方法。
样例输入输出
input: [2,0,2,1,1,0] output: [0,0,1,1,2,2]
题解
和上一个题目相同,只是从字符变成了指定的数字0,1,2。
private static int[] sortColors(int[] colors){ HashMap<Integer, Integer> map = new HashMap<>(); for(int color: colors){ map.put(color, map.containsKey(color)? map.get(color)+1: 1); } int[] res = new int[colors.length]; int index=0; for(int i=0;i<=2;i++){ for(int j=0;j<map.get(i);j++){ res[index] = i; index++; } } return res; }
-