1 模板
1.1 辅助与测试函数
1.1.1 交换函数 swap()
public static void swap(int[] arr, int i, int j) {
int temp;
temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
public static void swap(int[] arr, int i, int j) {
arr[i] = arr[i] ^ arr[j];
arr[j] = arr[i] ^ arr[j];
arr[i] = arr[i] ^ arr[j];
}
1.1.2 测试函数
public static void test(){
int testTime = 500000;
int maxSize = 100;
int maxValue = 100;
boolean succeed = true;
for (int i = 0; i < testTime; i++) {
int[] original = generateRandomArray(maxSize, maxValue);
int[] arr1 = copyArray(original);
int[] arr2 = copyArray(original);
sortMethod(arr1);
comparator(arr2);
if (!isEqual(arr1, arr2)) {
succeed = false;
print(original);
print(arr1);
print(arr2);
break;
}
}
System.out.println(succeed ? "pass!" : "failed!");
}
public static int[] generateRandomArray(int maxSize, int maxValue) {
int[] arr = new int[(int) (Math.random() * (maxSize - 1))];
for (int i = 0; i < arr.length; i++) {
arr[i] = (int) (Math.random() * maxValue);
}
return arr;
}
public static void comparator(int[] arr) {
Arrays.sort(arr);
}
public static int[] copyArray(int[] arr) {
if (arr == null) {
return arr;
}
int[] res = new int[arr.length];
for (int i = 0; i < arr.length; i++) {
res[i] = arr[i];
}
return res;
}
public static boolean isEqual(int[] arr1, int[] arr2) {
if ((arr1 == null && arr2 != null) || (arr1 != null && arr2 == null)) {
return false;
}
if (arr1 == null && arr2 == null) {
return true;
}
if (arr1.length != arr2.length) {
return false;
}
for (int i = 0; i < arr1.length; i++) {
if (arr1[i] != arr2[i]) {
return false;
}
}
return true;
}
public static void print(int[] arr) {
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
System.out.println();
}
1.2 排序模板
1.2.1 冒泡排序
时间复杂度:O(n*n)
稳定性:稳定
空间复杂度:O(1)
public static void bubbleSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
for (int e = arr.length - 1; e > 0; e--) {
for (int i = 0; i < e; i++) {
if (arr[i] > arr[i + 1]) {
swap(arr, i, i + 1);
}
}
}
}
1.2.2 插入排序
时间复杂度:O(n*n)
稳定性:稳定
空间复杂度:O(1)
思想:
无序部分插入有序部分,例如:
有序部分 ] 无序部分
3]5 8 4 7
3 5]8 4 7
3 5 8]4 7
3 4 5 8]7
3 4 5 7 8]
public static void insertionSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
for (int i = 1; i < arr.length; i++) {
for (int j = i - 1; j >= 0 && arr[j] >= arr[j + 1]; j--) {
swap(arr, j, j + 1);
}
}
}
1.2.3 选择排序
时间复杂度:O(n*n)
稳定性:不稳定
空间复杂度:O(1)
思想:
找到最小的放入有序部分。
public static void selectionSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
for (int i = 0; i < arr.length; i++) {
int minIndex = i;
for (int j = i + 1; j < arr.length; j++) {
if (arr[j] < arr[minIndex]){
minIndex = j;
}
}
swap(arr, i, minIndex);
}
}
1.2.4 堆排序
时间复杂度:O(nlogn)
稳定性:不稳定
空间复杂度:O(1)
特点:1、堆是一个完全二叉树。2、每个结点大于或者等于它的任意一个结点。
完全二叉树:除最后一层每一层都被填满,如果最后一层没有填满那么它的叶节点全部是偏向左的。
存储:如果堆的大小是已知的那么可以将堆存储在ArrayList里面,位置i处的节点左孩子是2i+1,右孩子是2i+2,父节点是(i-1)/2。
过程:将需要排序的数据全部加入到堆中,然后迭代地返回根结点。根是最大数的节点。
/**
* 堆排序
* <p>
* 大根堆 -> 升序
* 小根堆 -> 降序
* <p>
* 堆不需要二叉树,应该用数组存储
* (i - 1) / 2 : parentNode
* i * 2 + 1 : leftChild
* i * 2 + 2 : rightChild
* <p>
* heapInsert 插入数据
* heapify 交换位置后调整堆
*/
public static void heapSort(int[] nums) {
if (nums == null || nums.length == 0) {
return;
}
for (int i = 0; i < nums.length; i++) {
heapInsert(nums, i);
}
int size = nums.length;
while (size > 0) {
swap(nums, 0, size - 1);
size--;
heapify(nums, 0, size);
}
}
private static void heapInsert(int[] nums, int index) {
while (nums[index] > nums[(index - 1) / 2]) {
swap(nums, index, (index - 1) / 2);
index = (index - 1) / 2;
}
}
private static void heapify(int[] nums, int index, int size) {
int left = (index * 2 + 1);
while (left < size) {
int maxIndex = left + 1 < size && nums[left] < nums[left + 1] ? left + 1 : left;
maxIndex = nums[index] > nums[maxIndex] ? index : maxIndex;
if (maxIndex == index) {
break;
}
swap(nums, maxIndex, index);
index = maxIndex;
left = index * 2 + 1;
}
}
1.2.5 快速排序
时间复杂度:O(nlogn),长期期望
稳定性:不稳定
空间复杂度:
最坏情况下归并排序效率大于快速排序,平均情况下两者的效率时间相同。归并排序需要的更多的额外空间。
public static void heapSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
for (int i = 0; i < arr.length; i++) {
heapInsert(arr, i);
}
int size = arr.length;
while (size > 0){
swap(arr,0,size - 1);
size--;
heapify(arr,0,size);
}
}
public static void heapInsert(int[] arr, int index) {
while (arr[index] > arr[(index - 1) / 2]) {
swap(arr, index, (index - 1)/2);
index = (index - 1) / 2;
}
}
public static void heapify(int[] arr, int index, int size) {
int left = index * 2 + 1;
while (left < size) {
int largest = left + 1 < size && arr[left + 1] > arr[left] ? left + 1 : left;
largest = arr[largest] > arr[index] ? largest : index;
if (largest == index) {
break;
}
swap(arr, largest, index);
index = largest;
left = index * 2 + 1;
}
}
思想:
荷兰国旗问题,根据目标值按 小于,等于,大于 分割。
public static void quickSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
quickSort(arr, 0, arr.length - 1);
}
public static void quickSort(int[] arr, int l, int r) {
if (l < r) {
int p = arr[(int) (l + Math.random() * (r - l + 1))];
int partition[] = partition(arr, l, r, p);
quickSort(arr, l, partition[0] - 1);
quickSort(arr, partition[1] + 1, r);
}
}
public static int[] partition(int[] arr, int l, int r, int p) {
int less = l - 1;
int more = r + 1;
while (l < more) {
if (arr[l] < p) {
swap(arr, ++less, l++);
} else if (arr[l] > p) {
swap(arr, l, --more);
} else {
l++;
}
}
return new int[]{less + 1, more - 1};
}
1.2.6 归并排序
时间复杂度:O(nlogn)
稳定性:稳定
空间复杂度:O(n)
思想:
两个有序数组的合并。
public static void mergeSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
mergeSort(arr, 0, arr.length - 1);
}
public static void mergeSort(int[] arr, int l, int r) {
if (l == r) {
return;
}
int m = l + (r - l) / 2;
mergeSort(arr, l, m);
mergeSort(arr, m + 1, r);
merge(arr, l, m, r);
}
public static void merge(int[] arr, int l, int m, int r) {
int[] help = new int[r - l + 1];
int i = 0;
int p1 = l;
int p2 = m + 1;
while (p1 <= m && p2 <= r){
help[i++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++];
}
while (p1 <= m){
help[i++] = arr[p1++];
}
while (p2 <= r){
help[i++] = arr[p2++];
}
for (i = 0;i < help.length;i++){
arr[l + i] = help[i];
}
}
1.2.7 计数排序(特殊的桶排序)
时间复杂度:O(n+N)
空间复杂度:O(n+N)
稳定性:不稳定
适用类型:小整数的排序,整数过大使用基数排序。
public static void countingSort(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;
bucket[j]--;
}
}
}
1.2.8 基数排序
时间复杂度:O(dn)基数位置的最大值。例如213基数位置的最大值是3,相当于进行了d次桶排序。
稳定性:稳定
空间复杂度:O(dn)
特点:使用十个桶,桶排序是稳定的。
public 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) {
max = max / 10;
res++;
}
System.out.println(res);
return res;
}
public static void radixSort(int[] arr, int begin, int end, int digit) {
final int radix = 10;
int i = 0, j = 0;
int[] count = new int[radix];
int[] bucket = new int[end - begin + 1];
for (int d = 1; d <= digit; d++) {
for (i = 0; i < radix; i++) {
count[i] = 0;
}
for (i = begin; i <= end; i++) {
j = getDigit(arr[i],d);
count[j]++;
}
for (i = 1; i < radix; i++) {
count[i] = count[i] + count[i - 1];
}
for (i = end; i >= begin; i--) {
j = getDigit(arr[i], d);
bucket[count[j] - 1] = arr[i];
count[j]--;
}
for (i = begin, j = 0; i <= end; i++, j++) {
arr[i] = bucket[j];
}
}
}
public static int getDigit(int x, int d) {
return ((x / (int) Math.pow(10, d - 1)) % 10);
}
2 排序相关例题
2.1 LeetCode 88. 合并两个有序数组
题目描述:
给你两个有序整数数组 nums1 和 nums2,请你将 nums2 合并到 nums1 中,使 nums1 成为一个有序数组。
说明:
初始化 nums1 和 nums2 的元素数量分别为 m 和 n 。
你可以假设 nums1 有足够的空间(空间大小大于或等于 m + n)来保存 nums2 中的元素。
用例:
输入:
nums1 = [1,2,3,0,0,0], m = 3
nums2 = [2,5,6], n = 3
输出: [1,2,2,3,5,6]
实现思路:
归并排序。
参考代码:
public void merge(int[] nums1, int m, int[] nums2, int n) {
for(int i = m - 1 ; i >= 0 ; i--){
nums1[i + nums1.length - m] = nums1[i];
}
int i = 0;
int p1 = nums1.length - m;
int p2 = 0;
while(p1 < nums1.length && p2 < n){
nums1[i++] = nums1[p1] <= nums2[p2] ? nums1[p1++] : nums2[p2++];
}
while(p1 < nums1.length){
nums1[i++] = nums1[p1++];
}
while(p2 < n){
nums1[i++] = nums2[p2++];
}
}
2.2 剑指 Offer 45. 把数组排成最小的数
题目描述:
输入一个非负整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。
用例:
示例 1:
输入: [10,2]
输出: “102”
示例 2:
输入: [3,30,34,5,9]
输出: “3033459”
实现思路:
- 全排列,再对String进行排序。组合数为 (N!),复杂度较大。
- 定义StrComparetor,然后排序。时间复杂度O(nlogn)。
参考代码:
public String minNumber(int[] nums) {
String[] strs = new String[nums.length];
for (int i = 0; i < nums.length; i++) {
strs[i] = String.valueOf(nums[i]);
}
Arrays.sort(strs,new StrComparetor());
StringBuilder res = new StringBuilder();
for (int i = 0; i < strs.length; i++) {
res.append(strs[i]);
}
return res.toString();
}
public static class StrComparetor implements Comparator<String>{
@Override
public int compare(String m, String n) {
String mn = m + n;
String nm = n + m;
return mn.compareTo(nm);
}
}
2.3 LeetCode 75. 颜色分类(荷兰国旗问题)
题目描述:
给定一个包含红色、白色和蓝色,一共 n 个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。
此题中,我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。
用例:
示例:
输入: [2,0,2,1,1,0]
输出: [0,0,1,1,2,2]
实现思路:
- 计数排序。
- 快速排序。
参考代码:
计数排序
public void sortColors(int[] nums) {
int[] count = new int[3];
for(int i = 0; i < nums.length ;i++){
count[nums[i]]++;
}
int i = 0;
for(int j = 0; j < count.length ; j++){
while(count[j] > 0){
nums[i++] = j;
count[j]--;
}
}
}
快速排序
public void sortColors(int[] nums) {
int l = 0;
int r = nums.length - 1;
int less = l - 1;
int more = r + 1;
int p = 1;
while(l < more){
if(nums[l] < p){
swap(nums,++less,l++);
}else if(nums[l] > p){
swap(nums,l,--more);
}else{
l++;
}
}
}
public void swap(int[] nums,int i,int j){
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
2.4 n数和问题
2.4.1 LeetCode 1. 两数之和
题目描述:
给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。
用例:
给定 nums = [2, 7, 11, 15], target = 9
因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]
实现思路:
Hash记录
参考代码:
public int[] twoSum(int[] nums, int target) {
HashMap<Integer,Integer> map = new HashMap();
int[] res = new int[2];
for(int i = 0 ; i < nums.length ; i++){
if(map.containsKey(target - nums[i])){
res[0] = map.get(target - nums[i]);
res[1] = i;
return res;
}
map.put(nums[i],i);
}
return res;
}
2.4.2 LeetCode 15. 三数之和
题目描述:
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。
注意:答案中不可以包含重复的三元组。
用例:
给定数组 nums = [-1, 0, 1, 2, -1, -4],
满足要求的三元组集合为:
[
[-1, 0, 1],
[-1, -1, 2]
]
实现思路:
排序,双指针,将3和问题转变为2和问题。注意去重。
参考代码:
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> resList = new LinkedList();
Arrays.sort(nums);
int i = 0;
int pre;
while (i < nums.length) {
int s = i + 1;
int e = nums.length - 1;
int t = 0 - nums[i];
while (s < e) {
int sum = nums[s] + nums[e];
if (sum < t) {
s++;
} else if (sum > t) {
e--;
} else {
List<Integer> res = new LinkedList();
res.add(nums[i]);
res.add(nums[s]);
res.add(nums[e]);
resList.add(res);
pre = nums[s];
while(s < e && pre == nums[s]){
s++;
}
}
}
pre = nums[i];
while (i < nums.length && pre == nums[i]) {
i++;
}
}
return resList;
}
2.4.3 LeetCode 18. 四数之和
题目描述:
给定一个包含 n 个整数的数组 nums 和一个目标值 target,判断 nums 中是否存在四个元素 a,b,c 和 d ,使得 a + b + c + d 的值与 target 相等?找出所有满足条件且不重复的四元组。
用例:
给定数组 nums = [1, 0, -1, 0, -2, 2],和 target = 0。
满足要求的四元组集合为:
[
[-1, 0, 0, 1],
[-2, -1, 1, 2],
[-2, 0, 0, 2]
]
实现思路:
同三数和。
参考代码:
public List<List<Integer>> fourSum(int[] nums, int target) {
Arrays.sort(nums);
List<List<Integer>> resList = new LinkedList();
int i = 0;
int pre;
while(i < nums.length){
int j = i + 1;
while(j < nums.length){
int s = j + 1;
int e = nums.length - 1;
int t = target - nums[i] - nums[j];
while(s < e){
int sum = nums[s] + nums[e];
if(sum < t){
s++;
}else if(sum > t){
e--;
}else{
List<Integer> res = new LinkedList();
res.add(nums[i]);
res.add(nums[j]);
res.add(nums[s]);
res.add(nums[e]);
resList.add(res);
pre = nums[s];
while(s < e && pre == nums[s]){
s++;
}
}
}
pre = nums[j];
while(j < nums.length && pre == nums[j]){
j++;
}
}
pre = nums[i];
while(i < nums.length && pre == nums[i]){
i++;
}
}
return resList;
}
2.5 优先级队列
题目描述:
实现优先级队列。
实现思路:
堆排序。
参考代码:
public class MyPriorityQueue {
private int size;
private int[] queue;
private int capacity;
public MyPriorityQueue(int capacity) {
this.capacity = capacity;
queue = new int[capacity];
size = 0;
}
public boolean offer(Integer x) {
if (size == capacity) {
capacity = capacity * 2;
queue = Arrays.copyOf(queue, capacity);
}
queue[size] = x;
shiftUp(size);
size++;
return true;
}
public Integer poll(){
if (size == 0){
return null;
}
int res = queue[0];
swap(0, size - 1);
size--;
shiftDown(0);
return res;
}
private void shiftUp(int index) {
while (queue[index] > queue[(index - 1) / 2]) {
swap(index, (index - 1) / 2);
index = (index - 1) / 2;
}
}
private void shiftDown(int index){
int left = index * 2 + 1;
while (left < size){
int maxIndex = left + 1 < size && queue[left + 1] > queue[left] ? left + 1 : left;
maxIndex = queue[index] > queue[maxIndex] ? index : maxIndex;
if(maxIndex == index){
break;
}
swap(index,maxIndex);
index = maxIndex;
left = index * 2 + 1;
}
}
public boolean isEmpty(){
return size == 0;
}
public int getSize(){
return size;
}
private void swap(int i, int j) {
int t = queue[i];
queue[i] = queue[j];
queue[j] = t;
}
}