1. 数据存储方式
- 数组(顺序存储):需要考虑扩容问题
- 链表(链式存储):需要分配空间存储节点指针
2. 数据结构的实现
3. 数据结构的操作
基本操作:遍历 -> 访问 -> CRUD
数组遍历访问
void traverse(int[] arr) {
for (int i = 0; i < arr.length; i++) {
//迭代访问 arr[i]
}
}
单链表遍历访问
class ListNode {
int val;
ListNode next;
}
//使用循环
void traverse(ListNode head) {
for (ListNode p = head; p != null; p = p.next) {
//迭代访问 p.val
}
}
//使用递归
void traverse(ListNode head) {
//递归访问 head.val
traverse(head.next);
}
二叉树遍历访问
class ListNode {
int val;
ListNode left;
ListNode right;
}
//前序遍历,结果形式总是 [ 根节点, [左子树的前序遍历结果], [右子树的前序遍历结果] ]
void traverse(ListNode head) {
//递归访问 head.val
traverse(head.left);
traverse(head.right);
}
//中序遍历,结果形式总是 [ [左子树的中序遍历结果], 根节点, [右子树的中序遍历结果] ]
void traverse(ListNode head) {
traverse(head.left);
//递归访问 head.val
traverse(head.right);
}
//后序遍历,结果形式总是[ [左子树的中序遍历结果], [右子树的中序遍历结果], 根节点 ]
void traverse(ListNode head) {
traverse(head.left);
traverse(head.right);
//递归访问 head.val
}
递归
- 明确函数的功能,重点关注如何处理子问题的结果来获得整个问题的解
- 寻找递归结束条件,即参数满足什么条件,递归应该结束并返回结果
- 缩小参数范围,即寻找参数间的关系,使问题保持一致的情况下缩小规模
4. 数组(查、改快)
4.1 二分查找
- target 唯一时,返回其索引
public int binarySearch(int[] nums, int target) {
int l = 0;
int h = nums.length - 1; //区间右闭
while (l <= h) { //因为区间右闭,所以要加等号,终止条件 l == h + 1
int m = l + (h - l) / 2; //防止大数溢出
if (nums[m] == target) {
return m;
} else if (nums[m] < target) {
l = m + 1;
} else if (nums[m] > target) {
h = m - 1;
}
}
return -1;
}
lower_bound 最下边界问题(leetcode 875、1011)
- target 不唯一时或不存在时,返回第一个大于或等于 target 元素的索引
public int lower_bound(int[] nums, int target) {
int l = 0;
int h = nums.length; //区间右开
while (l < h) { //因为区间右开,不需要等号,终止条件 l == h
int m = l + (h - l) / 2; //[l, m) m [m+1, h)
if (nums[m] == target) {
h = m;
} else if (nums[m] < target) {
l = m + 1;
} else {
h = m;
}
}
return l;
}
upper_bound 最上边界问题
- target 不唯一时或不存在时,返回第一个大于 target 元素的索引
public int upper_bound(int[] nums, int target) {
int l = 0;
int h = nums.length; //区间右开
while (l < h) { //因为区间右开,不需要等号,终止条件 l == h
int m = l + (h - l) / 2; //[l, m) m [m+1, h)
if (nums[m] == target) {
l = m + 1;
} else if (nums[m] < target) {
l = m + 1;
} else {
h = m;
}
}
return l;
}
5. 链表(增、删快)
单链表 增 操作
cur.next = prev.next;
prev.next = cur;
单链表 删 操作
prev.next = prev.next.next;
善用 快慢指针 或 哈希表 可以解决一些链表问题(leetcode 142、19)
快慢指针模板如下:
ListNode slow = head;
ListNode fast = head;
/* 获取空节点的下一个节点将导致空指针错误
因此在运行 fast = fast.next.next 之前,需要检查 fast 和 fast.next 不为空
注意fast的步长,以及满足何种条件最终和slow可以同步,防止终止条件不正确导致无限循环 */
while (slow != null && fast != null && fast.next != null) {
slow = slow.next; // move slow pointer one step each time
fast = fast.next.next; // move fast pointer two steps each time
if (slow == fast) { // change this condition to fit specific problem
return true;
}
}
return false; // change return value to fit specific problem
使用 递归 反转链表
ListNode reverse(ListNode head) {
if (head == null) return null;
if (head.next == null) return head;
ListNode last = reverse(head.next); //反转子链表
head.next.next = head; //子链表最后的节点由指向null改为指向head
head.next = null; //head称为最后节点,重新指向null
return last; //返回反转后的链表
}
6. 二叉树
6.1 广度优先搜索(BFS)
- 层序遍历使用队列作为辅助结构
- 核心为当前层的所有节点依次出队,同时下一层的所有节点依次入队
leetcode 102 二叉树的层序遍历
迭代法:
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> list = new ArrayList<>();
if (root == null) return list;
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
List<Integer> level = new ArrayList<>(); //当前层的节点值列表
int curlev = queue.size(); //当前层节点个数
for (int i = 1; i <= curlev; i++) { //将当前层每个节点出队,其子节点入队
TreeNode node = queue.poll();
level.add(node.val);
if (node.left != null) {
queue.offer(node.left);
}
if (node.right != null) {
queue.offer(node.right);
}
}
list.add(level);
}
return list;
}
}
递归法
class Solution {
List<List<Integer>> list = new ArrayList<>();
public List<List<Integer>> levelOrder(TreeNode root) {
if (root == null) return list;
addVal(root, 0); //给指定层依次添加对应节点的节点值
return list;
}
public void addVal(TreeNode root, int levelNum) {
if (list.size() == levelNum) {
List<Integer> level = new ArrayList<>();
list.add(level);
}
list.get(levelNum).add(root.val);
if (root.left != null) {
addVal(root.left, levelNum + 1);
}
if (root.right != null) {
addVal(root.right, levelNum + 1);
}
}
}
6.2 序列化
String SEP = ",";
String NULL = "#";
String serialize(TreeNode root) {
StringBuilder sb = new StringBuilder();
serialize(root, sb);
return sb.toString();
}
void serialize(TreeNode root, StringBuilder sb) {
if (root == null) {
sb.append(NULL).append(SEP);
}
//前序
sb.append(root.val).append(SEP);
serialize(root.left, sb);
serialize(root.right, sb);
}