1.对数器
implements Comparator
的类,需要实现compare(T o1, T o2)
方法,这个方法返回一个int
类型的值,int是负数时,返回前面那个对象,否则返回后面那个对象,即:
- 如果返回值是负数:这意味着
o1
小于o2
。在排序时,o1
会排在o2
前面。 - 如果返回值是正数:这意味着
o1
大于o2
。在排序时,o1
会排在o2
后面。 - 如果返回值是零:这意味着
o1
等于o2
。它们的相对顺序不会改变。
例如下面的Student类,有name,id,age三个属性,现在按照age属性进行升序排序:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class Student {
private String name;
private int id;
private int age;
public String getName() {
return name;
}
public int getId() {
return id;
}
public int getAge() {
return age;
}
public Student(String name, int id, int age) {
this.name = name;
this.id = id;
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", id=" + id +
", age=" + age +
'}';
}
}
class ageComparator implements Comparator<Student> {
@Override
public int compare(Student o1, Student o2) {
return o1.getAge() - o2.getAge();
}
}
测试:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class TestAge{
public static void printStudents(Student[] students) {
for (Student student : students) {
System.out.println(student);
}
}
public static void main(String[] args) {
Student s1 = new Student("张三", 9, 10);
Student s2 = new Student("里斯", 0, 28);
Student s3 = new Student("万物", 7, 23);
Student s4 = new Student("糟熘", 2, 9);
Student[] students = {s1, s2, s3, s4};
Arrays.sort(students, new ageComparator());
printStudents(students);
}
}
如果容器不是数组,而是一个ArrayList,在调用sort方法时,传入即可:arrayList.sort(new ageComparator());
1
2
3
4
5
6
7
8
9
10
ArrayList<Student> arrayList = new ArrayList<>();
arrayList.add(s1);
arrayList.add(s2);
arrayList.add(s3);
arrayList.add(s4);
arrayList.sort(new ageComparator());
for (Student student :arrayList) {
System.out.println(student);
}
2.优先级队列/小根堆
java内置的优先级队列PriorityQueue
其实就是小根堆,每次你从队列中取出元素时,它总是返回优先级最高的那个元素。(Integer的话就是返回最小,小根堆)
常用方法:
- 添加元素 (
add()
或offer()
) - 获取并移除堆顶元素 (
poll()
) - 获取堆顶元素但不移除 (
peek()
)
另外,PriorityQueue
完全支持存储重复的元素。
优先级队列时间复杂度是O(logn)
比如:
1
2
3
4
5
6
PriorityQueue<Integer> heap = new PriorityQueue<>();
heap.add(10);
heap.add(1);
heap.add(11);
heap.add(5);
System.out.println(heap.peek()); // 返回1
如果你需要一个大根堆(根节点最大),可以通过给 PriorityQueue
传入一个自定义比较器来实现。
比如:
1
2
PriorityQueue<Integer> maxHeap = new PriorityQueue<>((a, b) -> b - a); // 降序
PriorityQueue<Integer> maxHeap = new PriorityQueue<>(new MyComparator()); // 自定义
TreeMap和TreeSet同理。
补一个简单的知识点:String如何比较?—-字典序
逐个字符比较:从两个字符串的第一个字符开始比较。
第一个不同字符决定顺序:(基于字母顺序)
- 如果第一个不同字符在字母表中的位置靠前,则整个字符串靠前。
- 如果第一个不同字符在字母表中的位置靠后,则整个字符串靠后。
短字符串优先:如果一个字符串是另一个字符串的前缀,那么较短的那个字符串排在前面。
在Java中,
String
类的compareTo()
方法就是用来执行字典顺序比较的。如果你想忽略大小写,则可以使用compareToIgnoreCase()
方法。
3.力扣23-合并k个升序链表
力扣:https://leetcode.cn/problems/merge-k-sorted-lists/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode mergeKLists(ListNode[] lists) {
if (lists == null || lists.length == 0 ) return null;
if (lists.length == 1) return lists[0];
// 小根堆
PriorityQueue<ListNode> heap = new PriorityQueue<>((o1, o2)->o1.val-o2.val);
for (ListNode l:lists) {
if (l != null) {
heap.add(l);
}
}
ListNode head = heap.poll();
if (head != null && head.next != null) heap.add(head.next);
ListNode cur = head;
while (!heap.isEmpty()) {
ListNode poll = heap.poll();
if (poll != null && poll.next != null) heap.add(poll.next);
cur.next = poll;
cur = cur.next;
}
return head;
}
}
估计时间复杂度:假设一共有k个链表,节点总数是n,
那么小根堆的大小最大是k,小根堆的调整O(logk),每个节点进来/出去,一共发生n次,所以时间复杂度O(nlogk)
4.递归序的实质
递归序,又称为遍历序或欧拉序(Euler tour),其本质是将一棵树的遍历过程,转化为一个一维序列(数组)。
简单来说,它记录了在树的深度优先遍历(DFS)过程中,每个节点被访问的完整路径。
从根节点出发,总是优先向左走。当沿着树的边缘移动时,每当经过一个节点,无论是第一次、第二次还是最后一次,你都把它记录下来。
这个“经过并记录”的过程,就是生成递归序的实质。
具体来说,当一个节点 x
被访问时,有三种情况:
- 第一次访问 (
pre-order
阶段):当你从父节点来到x
的时候,这是你第一次见到它。 - 从左子树返回 (
in-order
阶段):当你走完x
的左子树,回到x
准备进入右子树时。 - 从右子树返回 (
post-order
阶段):当你走完x
的右子树,准备从x
返回到父节点时。
递归序就是把这三种情况下的访问都记录下来。
递归序将复杂的树结构问题,降维成了一维数组上的子序列问题,使得原本需要多次遍历才能解决的问题,现在只需要对一个数组进行简单的查找和计算即可。
1
2
3
4
5
6
7
8
9
10
public static void f(Node head) {
if (head == null) {
return;
}
// 1
f(head.left);
// 2
f(head.right);
// 3
}
5.判断是否为相等的树
力扣:https://leetcode.cn/problems/same-tree/description/
牛客:(填函数风格)https://www.nowcoder.com/practice/5a3b2cf4211249c89d6ced7987aeb775
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public boolean isSameTree(TreeNode p, TreeNode q) {
// (p == null 且q != null) 或者(p != null 且q == null)
if (p == null ^ q == null) {
return false;
}
if (p == null && q == null) {
return true;
}
return (p.val == q.val) && (isSameTree(p.left, q.left)) && (isSameTree(p.right, q.right));
}
}
6.判断一棵树是否是镜面树(对称二叉树)
力扣:https://leetcode.cn/problems/symmetric-tree
牛客:填函数风格:https://www.nowcoder.com/practice/ff05d44dfdb04e1d83bdbdab320efbcb?tpId=0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public boolean isSymmetric(TreeNode root) {
return isMirrors(root, root);
}
public boolean isMirrors(TreeNode r1, TreeNode r2) {
if (r1 == null ^ r2 == null) {
return false;
}
if (r1 == null && r2 == null) {
return true;
}
return (r1.val == r2.val) && (isMirrors(r1.left, r2.right)) && (isMirrors(r1.right, r2.left));
}
}
7.返回一棵树的最大深度
力扣:https://leetcode.cn/problems/maximum-depth-of-binary-tree/description/
牛客(填函数风格):https://www.nowcoder.com/practice/435fb86331474282a3499955f0a41e8b
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public int maxDepth(TreeNode root) {
if (root == null) {
return 0;
}
return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1;
}
}
8.从前序与中序遍历序列构造二叉树
力扣105: https://leetcode.cn/problems/construct-binary-tree-from-preorder-and-inorder-traversal/
给定两个整数数组 preorder
和 inorder
,其中 preorder
是二叉树的先序遍历, inorder
是同一棵树的中序遍历,请构造二叉树并返回其根节点。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public TreeNode buildTree(int[] preorder, int[] inorder) {
if (preorder == null || inorder == null || preorder.length != inorder.length) return null;
HashMap<Integer, Integer> map = new HashMap<>();
for (int i = 0;i < inorder.length;i++){
map.put(inorder[i], i);
}
return f(preorder, 0, preorder.length - 1, inorder, 0, inorder.length - 1, map);
}
public TreeNode f(int[] preorder,int L1, int R1, int[] inorder, int L2, int R2, HashMap<Integer, Integer> map) {
if (L1 > R1) {
return null;
}
TreeNode head = new TreeNode(preorder[L1]);
if (L1 == R1) {
return head;
}
// 找头
int find = map.get(head.val);
head.left = f(preorder,L1 + 1 , L1 +find - L2,inorder, L2, find - 1, map);
head.right = f(preorder, L1 +find - L2 + 1, R1, inorder, find + 1, R2, map);
return head;
}
}
时间复杂度分析
- 哈希表构建:
for (int i = 0; i < inorder.length; i++)
循环需要O(N)
的时间,其中N
是节点总数。 - 递归过程:
f
函数被调用的次数等于树的节点数,即N
。- 在每次
f
函数调用中,主要操作是哈希表查找(map.get()
),其时间复杂度是O(1)
。 - 因此,递归过程的总时间复杂度为
O(N * 1) = O(N)
。
- 总时间复杂度:
O(N)
(构建哈希表)+O(N)
(递归) =O(N)
。
9.从中序和后序遍历构造二叉树
类似题目:
从中序和后序遍历构造二叉树(牛客,填函数风格):https://www.nowcoder.com/practice/b0d07d0edc7f495696aecd265d5ef1b9
同理,从中序和后序遍历序列构造二叉树的解法页类似,但是特别注意下面这2行下标的问题,很容易错,建议带入具体的例子。测试链接:
1
2
root.left = g(inorder, L1, find - 1, postorder, L2, L2 + find - L1 - 1, map);
root.right = g(inorder, find + 1, R1, postorder, L2 + find - L1, R2 - 1, map);
完整解法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import java.util.*;
/*
* public class TreeNode {
* int val = 0;
* TreeNode left = null;
* TreeNode right = null;
* }
*/
public class Solution {
/**
*
* @param inorder int整型一维数组
* @param postorder int整型一维数组
* @return TreeNode类
*/
public TreeNode buildTree (int[] inorder, int[] postorder) {
if (inorder == null || postorder == null || inorder.length != postorder.length) {
return null;
}
// 准备一个HashMap 中序快速索引
HashMap<Integer, Integer> inMap = new HashMap<>();
for (int i = 0;i < inorder.length;i++) {
inMap.put(inorder[i], i);
}
return g(inorder, 0, inorder.length - 1, postorder, 0, postorder.length - 1, inMap);
}
public TreeNode g(int[] inorder,int L1, int R1, int[] postorder, int L2, int R2, HashMap<Integer, Integer> map) {
if (L2 > R2) {
return null;
}
TreeNode root = new TreeNode(postorder[R2]);
if (L2 == R2) {
return root;
}
// 找头
int find = map.get(root.val);
root.left = g(inorder, L1, find - 1, postorder, L2, L2 + find - L1 - 1, map);
root.right = g(inorder, find + 1, R1, postorder, L2 + find - L1, R2 - 1, map);
return root;
}
}
类似的,时间复杂度也是O(N)
分析如下:
- 哈希表构建:
O(N)
,其中N
是节点总数。 - 递归过程:
g
函数被调用的次数等于树的节点数,即N
。- 在每次调用中,哈希表查找的时间复杂度是
O(1)
。 - 因此,递归过程的总时间复杂度为
O(N * 1) = O(N)
。
- 总时间复杂度:
O(N)
(构建哈希表)+O(N)
(递归) =O(N)
。