问题总览
| 类型 | 问题描述 | 完成 |
|---|---|---|
| 215. 数组中的第K个最大元素 | ✅ | |
| 912. 排序数组 | ✅ | |
| 148. 排序链表 | ✅ | |
| 315. 计算右侧小于当前元素的个数 | ✅ | |
| 493. 翻转对 | ✅ | |
| 327. 区间和的个数 | ||
| 剑指 Offer 51. 数组中的逆序对 |
快速排序
//快速排序的标准写法
class Solution {
public int[] sortArray(int[] nums) {
int len = nums.length;
// 打乱数组,避免出现极端情况
random(nums);
// 快速排序
sort(nums, 0, len - 1);
return nums;
}
public void sort(int[] nums, int lo, int hi) {
// 不要忘记base条件
if (lo >= hi) {
return;
}
int mid = partition(nums, lo, hi);
// 再分别对左子树和右子树排序
sort(nums, lo, mid - 1);
sort(nums, mid + 1, hi);
}
public int partition(int[] nums, int lo, int hi) {
int p = lo;
int i = lo + 1;
int j = hi;
while (i <= j) {
while (i < hi && nums[i] <= nums[p]) {
i++;
}
while (j > lo && nums[j] > nums[p]) {
j--;
}
// 已经是排好序的,不需要处理
if (i >= j) {
break;
}
swap(nums, i, j);
}
// 最后把作为标准的第一个元素与j互换,并返回j的位置,此时[lo,hi]内左边都是小于等于nums[p],右边都是大于等于nums[p]
swap(nums, p, j);
return j;
}
public void random(int[] nums) {
Random random = new Random();
int len = nums.length;
for (int i = 0; i < len; i++) {
int r = i + random.nextInt(len - i);
swap(nums, i, r);
}
}
public void swap(int[] nums, int i, int j) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
}
快速选择算法
用大顶堆或者小顶堆都可以解决,时间复杂度为O(nlogk),空间复杂度为O(k)。
class Solution {
public int findKthLargest(int[] nums, int k) {
PriorityQueue<Integer> queue = new PriorityQueue<>();
for (int num : nums) {
queue.add(num);
if(queue.size() > k){
queue.poll();
}
}
// 第K大的
return queue.poll();
}
}
用快速选择算法,时间复杂度可以认为是O(n)。
快速选择算法的精髓是,partition函数每次可以确保左边的都是比自己小,不管这些元素内部是有没有排序的。这样我们可以找到第len-k个元素,这个位置后有k-1个比自己大的。
最好的情况只需要partition每次都是二分:
N + N/2 + N/4 + N/8 + ... + 1 = 2N = O(N)
最坏的情况每次都要遍历所有元素:N + (N - 1) + (N - 2) + ... + 1 = O(N^2)
import java.util.PriorityQueue;
import java.util.Random;
//leetcode submit region begin(Prohibit modification and deletion)
class Solution {
public int findKthLargest(int[] nums, int k) {
// 还是先打散数组
random(nums);
int lo = 0;
int hi = nums.length - 1;
int target = nums.length - k;
while (lo <= hi) {
int p = partition(nums, lo, hi);
if (target == p) {
return nums[p];
} else if (target < p) {
hi = p - 1;
} else {
lo = p + 1;
}
}
// 没找到
return -1;
}
public int partition(int[] nums, int lo, int hi) {
int p = lo;
int i = lo + 1;
int j = hi;
while (i <= j) {
while (i < hi && nums[i] <= nums[p]) {
i++;
}
while (j > lo && nums[j] > nums[p]) {
j--;
}
// 已经是排好序的,不需要处理
if (i >= j) {
break;
}
swap(nums, i, j);
}
// 最后把作为标准的第一个元素与j互换,并返回j的位置,此时[lo,hi]内左边都是小于等于nums[p],右边都是大于等于nums[p]
swap(nums, p, j);
return j;
}
public void random(int[] nums) {
Random random = new Random();
int len = nums.length;
for (int i = 0; i < len; i++) {
int r = i + random.nextInt(len - i);
swap(nums, i, r);
}
}
public void swap(int[] nums, int i, int j) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
}
//leetcode submit region end(Prohibit modification and deletion)
归并排序
归并排序就是对左边和右边分别排好序,然后再将左边和右边合并。
//归并排序的标准写法
class Solution {
int[] temp;
public int[] sortArray(int[] nums) {
int len = nums.length;
temp = new int[len];
sort(nums, 0, len - 1);
return nums;
}
public void sort(int[] nums, int lo, int hi) {
if (lo == hi) {
return;
}
int mid = lo + (hi - lo) / 2;
// 左边排序
sort(nums, lo, mid);
// 右边排序
sort(nums, mid + 1, hi);
// 重新合并排好序的两边
merge(nums, lo, mid, hi);
}
public void merge(int[] nums, int lo, int mid, int hi) {
for (int i = lo; i <= hi; i++) {
temp[i] = nums[i];
}
int i = lo;
int j = mid + 1;
for (int k = lo; k <= hi; k++) {
if (i == mid + 1) {
nums[k] = temp[j++];
} else if (j == hi + 1) {
nums[k] = temp[i++];
} else if (temp[i] < temp[j]) {
nums[k] = temp[i++];
} else {
nums[k] = temp[j++];
}
}
}
}
//leetcode submit region end(Prohibit modification and deletion)
class Solution {
public ListNode sortList(ListNode head) {
return sort(head, null);
}
public ListNode sort(ListNode head, ListNode tail) {
if (head == null) {
return null;
}
// 将tail断开,不包含tail
if (head.next == tail) {
head.next = null;
return head;
}
// 找到链表的中点,并将其分成两段
ListNode mid = getMid(head, tail);
// 再分别对左子树和右子树排序
ListNode left = sort(head, mid);
ListNode right = sort(mid, tail);
return merge(left, right);
}
// 获取中点,这里注意fast是走到tail而不是走到null
public ListNode getMid(ListNode head, ListNode tail) {
ListNode fast = head;
ListNode slow = head;
while (fast != tail && fast.next != tail) {
fast = fast.next.next;
slow = slow.next;
}
return slow;
}
// 合并链表
public ListNode merge(ListNode head1, ListNode head2) {
ListNode dummyHead = new ListNode(0);
ListNode temp = dummyHead, temp1 = head1, temp2 = head2;
// 这里采用分隔链表的思路,不用创建新的节点
while (temp1 != null && temp2 != null) {
if (temp1.val <= temp2.val) {
temp.next = temp1;
temp1 = temp1.next;
} else {
temp.next = temp2;
temp2 = temp2.next;
}
temp = temp.next;
}
if (temp1 != null) {
temp.next = temp1;
} else if (temp2 != null) {
temp.next = temp2;
}
return dummyHead.next;
}
}
//leetcode submit region end(Prohibit modification and deletion)
class Solution {
private class Pair {
int val, id;
Pair(int val, int id) {
// 记录数组的元素值
this.val = val;
// 记录元素在数组中的原始索引
this.id = id;
}
}
// 归并排序所用的辅助数组
private Pair[] temp;
// 记录每个元素后面比自己小的元素个数
private int[] count;
// 主函数
public List<Integer> countSmaller(int[] nums) {
int n = nums.length;
count = new int[n];
temp = new Pair[n];
Pair[] arr = new Pair[n];
// 记录元素原始的索引位置,以便在 count 数组中更新结果
for (int i = 0; i < n; i++) {
arr[i] = new Pair(nums[i], i);
}
// 执行归并排序,本题结果被记录在 count 数组中
sort(arr, 0, n - 1);
List<Integer> res = new LinkedList<>();
for (int c : count) {
res.add(c);
}
return res;
}
// 归并排序
private void sort(Pair[] arr, int lo, int hi) {
if (lo == hi) {
return;
}
int mid = lo + (hi - lo) / 2;
sort(arr, lo, mid);
sort(arr, mid + 1, hi);
merge(arr, lo, mid, hi);
}
// 合并两个有序数组
private void merge(Pair[] arr, int lo, int mid, int hi) {
for (int i = lo; i <= hi; i++) {
temp[i] = arr[i];
}
int i = lo, j = mid + 1;
for (int p = lo; p <= hi; p++) {
if (i == mid + 1) {
arr[p] = temp[j++];
} else if (j == hi + 1) {
arr[p] = temp[i++];
// 更新 count 数组
count[arr[p].id] += j - mid - 1;
} else if (temp[i].val > temp[j].val) {
arr[p] = temp[j++];
} else {
arr[p] = temp[i++];
// 更新 count 数组
count[arr[p].id] += j - mid - 1;
}
}
}
}
老实说,这个思路就是每次看到了死记硬背一下,自己怎么都想不到。
相同问题:剑指 Offer 51. 数组中的逆序对
class Solution {
int count;
int[] temp;
public int reversePairs(int[] nums) {
int n = nums.length;
temp = new int[n];
sort(nums, 0, n - 1);
return count;
}
public void sort(int[] nums, int lo, int hi) {
if (lo == hi) {
return;
}
int mid = lo + (hi - lo) / 2;
sort(nums, lo, mid);
sort(nums, mid + 1, hi);
merge(nums, lo, mid, hi);
}
public void merge(int[] nums, int lo, int mid, int hi) {
for (int i = lo; i <= hi; i++) {
temp[i] = nums[i];
}
// 对左边的所有元素,去找右边的符合翻转对的索引
int end = mid + 1;
for (int i = lo; i <= mid; i++) {
while (end <= hi && (long) nums[i] > (long) 2 * nums[end]) {
end++;
}
count += end - mid - 1;
}
int i = lo;
int j = mid + 1;
for (int k = lo; k <= hi; k++) {
if (i == mid + 1) {
nums[k] = temp[j++];
} else if (j == hi + 1) {
nums[k] = temp[i++];
} else if (temp[i] > temp[j]) {
nums[k] = temp[j++];
} else {
nums[k] = temp[i++];
}
}
}
}
桶排序
字典序
| 类型 | 问题描述 | 完成 |
|---|---|---|
| 386. 字典序排数 | ✅ | |
| 440. 字典序的第K小数字 | ||
| 1061. 按字典序排列最小的等效字符串 |
暴力解法也能过。
class Solution {
public List<Integer> lexicalOrder(int n) {
PriorityQueue<Integer> queue = new PriorityQueue<>((o1, o2) -> {
return (o1 + "").compareTo(o2 + "");
});
for (int i = 1; i <= n; i++) {
queue.offer(i);
}
List<Integer> ans = new ArrayList<>();
for (int i = 0; i < n; i++) {
ans.add(queue.poll());
}
return ans;
}
}
参考官方题解。
class Solution {
public List<Integer> lexicalOrder(int n) {
int j = 1;
List<Integer> ans = new ArrayList<>();
// i表示层数
for (int i = 0; i < n; i++) {
ans.add(j);
if (j * 10 <= n) {
// 还可以遍历下一层,下一层从*10开始
j *= 10;
} else {
// 下一层肯定会超出n,回到最初的位置下一层继续下一个数
while (j % 10 == 9 || j + 1 > n) {
j = j / 10;
}
j++;
}
}
return ans;
}
}
拓扑排序
| 类型 | 问题描述 | 完成 |
|---|---|---|
| 207. 课程表 | ||
| 210. 课程表 II |