剑指 Offer 30. 包含min函数的栈
定义栈的数据结构,请在该类型中实现一个能够得到栈的最小元素的 min 函数在该栈中,调用 min、push 及 pop 的时间复杂度都是 O(1)。
辅助栈:
class MinStack {
//栈A正常push()pop();栈B维护最小堆。
Stack<Integer> A, B;//LIFO,后进先出
public MinStack() {
A = new Stack<>();
B = new Stack<>();
}
public void push(int x) {
//add和push一样的,只是最后返回值不一样,add返回布尔类型,而push则返回插入元素的类型。
A.add(x);
if(B.empty() || B.peek() >= x)
B.add(x);
}
public void pop() {
if(A.pop().equals(B.peek()))
B.pop();
}
public int top() {
return A.peek();
}
public int min() {
return B.peek();
}
}
剑指 Offer 31. 栈的压入、弹出序列
输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如,序列 {1,2,3,4,5} 是某栈的压栈序列,序列 {4,5,3,2,1} 是该压栈序列对应的一个弹出序列,但 {4,3,5,1,2} 就不可能是该压栈序列的弹出序列。
class Solution {
public boolean validateStackSequences(int[] pushed, int[] popped) {
Stack<Integer> stack = new Stack<>();
int i = 0;
//遍历[]pushed入栈stack,如果栈顶等于popped[]的第一个则栈顶出栈,看最后是否stack为空
//stack:LIFO,后进先出
for(int num : pushed) {
stack.push(num); // num入栈
while(!stack.isEmpty() && stack.peek() == popped[i]) { // 循环判断与出栈
stack.pop();
i++;
}
}
return stack.isEmpty();
}
}
剑指 Offer 32 - II. 从上到下打印二叉树 II
从上到下按层打印二叉树,同一层的节点按从左到右的顺序打印,每一层打印到一行。
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
Queue<TreeNode> queue = new LinkedList<>();
List<List<Integer>> res = new ArrayList<>();
if(root != null) queue.add(root);//初始化queue,添加root节点
while(!queue.isEmpty()) {
List<Integer> tmp = new ArrayList<>();
//遍历一层的节点,遍历到则在queue中去掉,然后在tmp中加入遍历到的queue的节点值,并将tmp加入到res中;
//queue中则加入左节点、右节点,然后进入下一层while循环
//队列接口的poll()方法返回并移除容器前面的元素
for(int i = queue.size(); i > 0; i--) {
TreeNode node = queue.poll();
tmp.add(node.val);
if(node.left != null) queue.add(node.left);
if(node.right != null) queue.add(node.right);
}
res.add(tmp);
}
return res;
}
}
剑指 Offer 32 - III. 从上到下打印二叉树 III
请实现一个函数按照之字形顺序打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右到左的顺序打印,第三行再按照从左到右的顺序打印,其他行以此类推。
层序遍历 + 双端队列:
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
Queue<TreeNode> queue = new LinkedList<>();
List<List<Integer>> res = new ArrayList<>();
if(root != null) queue.add(root);
while(!queue.isEmpty()) {
LinkedList<Integer> tmp = new LinkedList<>();
for(int i = queue.size(); i > 0; i--) {
TreeNode node = queue.poll();
//tmp.add(node.val) = tmp.addLast(node.val)
//看要加入的前一行的res.size() % 2 是否== 0
if(res.size() % 2 == 0) tmp.addLast(node.val); // 偶数层->队列头部
else tmp.addFirst(node.val); // 奇数层->队列尾部
if(node.left != null) queue.add(node.left);
if(node.right != null) queue.add(node.right);
}
res.add(tmp);
}
return res;
}
}
层序遍历 + 双端队列(奇偶层逻辑分离):
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
Queue<TreeNode> queue = new LinkedList<>();
List<List<Integer>> res = new ArrayList<>();
if(root != null) queue.add(root);
//把奇数偶数划分为一组来看
while(!queue.isEmpty()) {
// 打印奇数层
LinkedList<Integer> tmp = new LinkedList<>();
for(int i = queue.size(); i > 0; i--) {
TreeNode node = queue.poll();
// 从左向右打印
tmp.addLast(node.val);
if(node.left != null) queue.add(node.left);
if(node.right != null) queue.add(node.right);
}
res.add(tmp);
if(queue.isEmpty()) break;
// 打印偶数层
tmp = new LinkedList<>();
for(int i = queue.size(); i > 0; i--) {
TreeNode node = queue.poll();
// 从右向左打印
tmp.addFirst(node.val);
if(node.left != null) queue.add(node.left);
if(node.right != null) queue.add(node.right);
}
res.add(tmp);
}
return res;
}
}
层序遍历 + 倒序:
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
Queue<TreeNode> queue = new LinkedList<>();
List<List<Integer>> res = new ArrayList<>();
if(root != null) queue.add(root);
while(!queue.isEmpty()) {
List<Integer> tmp = new ArrayList<>();
for(int i = queue.size(); i > 0; i--) {
TreeNode node = queue.poll();
tmp.add(node.val);
if(node.left != null) queue.add(node.left);
if(node.right != null) queue.add(node.right);
}
//如果上一层为奇数,则倒一下序
if(res.size() % 2 == 1) Collections.reverse(tmp);
res.add(tmp);
}
return res;
}
}
剑指 Offer 33. 二叉搜索树的后序遍历序列
输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历结果。如果是则返回 true,否则返回 false。假设输入的数组的任意两个数字都互不相同。
后序遍历:左、右、根 。
二叉搜索树:左子树中所有节点的值 << 根节点的值;右子树中所有节点的值 >> 根节点的值;其左、右子树也分别为二叉搜索树。
递归分治:
class Solution {
public boolean verifyPostorder(int[] postorder) {
return recur(postorder, 0, postorder.length - 1);
}
boolean recur(int[] postorder, int i, int j) {
if(i >= j) return true; //说明此子树节点数量≤1,无需判别正确性,因此直接返回true
int p = i;
while(postorder[p] < postorder[j]) p++; //寻找第一个大于根节点的节点
int m = p;
while(postorder[p] > postorder[j]) p++; //判断右子树是否满足要求,最后p=j则满足要求
//最终结果为该树、左子树和右子树的与
return p == j && recur(postorder, i, m - 1) && recur(postorder, m, j - 1);
}
}
辅助单调栈???
剑指 Offer 34. 二叉树中和为某一值的路径
给你二叉树的根节点 root 和一个整数目标和 targetSum ,找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。
叶子节点 是指没有子节点的节点。
深度优先搜索:
/**
* 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 { //深度优先搜索
List<List<Integer>> ret = new LinkedList<List<Integer>>();
Deque<Integer> path = new LinkedList<Integer>();
public List<List<Integer>> pathSum(TreeNode root, int target) {
dfs(root, target);
return ret;
}
public void dfs(TreeNode root, int target) {
if (root == null) {
return;
}
path.offerLast(root.val);
/**************递归核心代码*****************/
target -= root.val;
if (root.left == null && root.right == null && target == 0) {
ret.add(new LinkedList<Integer>(path));
}
dfs(root.left, target);
dfs(root.right, target);
/*******************************************/
path.pollLast();
}
}
剑指 Offer 35. 复杂链表的复制
请实现 copyRandomList 函数,复制一个复杂链表。在复杂链表中,每个节点除了有一个 next 指针指向下一个节点,还有一个 random 指针指向链表中的任意节点或者 null。
哈希表:
/*
// Definition for a Node.
class Node {
int val;
Node next;
Node random;
public Node(int val) {
this.val = val;
this.next = null;
this.random = null;
}
}
*/
class Solution {
Map<Node, Node> cachedNode = new HashMap<Node, Node>();
public Node copyRandomList(Node head) {
if (head == null) {
return null;
}
if (!cachedNode.containsKey(head)) {
Node headNew = new Node(head.val);
cachedNode.put(head, headNew);
headNew.next = copyRandomList(head.next);
headNew.random = copyRandomList(head.random);
}
return cachedNode.get(head);
}
}
迭代 + 节点拆分:
class Solution {
public Node copyRandomList(Node head) {
//开始为A→B→C
if (head == null) {
return null;
}
//变成A→A′→B→B′→C→C′,每个节点各复制了一个
for (Node node = head; node != null; node = node.next.next) {
Node nodeNew = new Node(node.val);
nodeNew.next = node.next;
node.next = nodeNew;
}
//上面那个链表的每个数的副本的random指向任意节点的副本
for (Node node = head; node != null; node = node.next.next) {
Node nodeNew = node.next;
nodeNew.random = (node.random != null) ? node.random.next : null;
}
//变成A′→B′→C′
Node headNew = head.next; //return A后面的
for (Node node = head; node != null; node = node.next) {
//依次链接下下个
Node nodeNew = node.next;
node.next = node.next.next;
nodeNew.next = (nodeNew.next != null) ? nodeNew.next.next : null;
}
return headNew;
}
}
剑指 Offer 36. 二叉搜索树与双向链表
输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的循环双向链表。要求不能创建任何新的节点,只能调整树中节点指针的指向。
双向链表+中序遍历:
//排序的循环双向链表->二叉搜索树中序遍历:左根右
class Solution {
Node pre, head;
public Node treeToDoublyList(Node root) {
if(root == null)
return null;
dfs(root); //dfs完后pre成了中序遍历的尾部
head.left = pre; //循环:head.left = tail; tail.right = head;
pre.right = head;
return head;
}
void dfs(Node cur) { //链表内部双向
if(cur == null)
return;
dfs(cur.left);
if(pre != null)
pre.right = cur;
else head = cur; //pre=null的话,也就是没有这个根?头节点移动
cur.left = pre;
pre = cur; //pre指针后移
dfs(cur.right);
}
}
剑指 Offer 37. 序列化二叉树
请实现两个函数,分别用来序列化和反序列化二叉树。
你需要设计一个算法来实现二叉树的序列化与反序列化。这里不限定你的序列 / 反序列化算法执行逻辑,你只需要保证一个二叉树可以被序列化为一个字符串并且将这个字符串反序列化为原始的树结构。
剑指 Offer 38. 字符串的排列
输入一个字符串,打印出该字符串中字符的所有排列。
你可以以任意顺序返回这个字符串数组,但里面不能有重复元素。
类似于leetcode 46. 全排列(略)
回溯:
class Solution {
List<String> res = new LinkedList<>();
char[] c;
public String[] permutation(String s) {
c = s.toCharArray();
dfs(0);
return res.toArray(new String[res.size()]);
}
void dfs(int x) {
if(x == c.length - 1) {
res.add(String.valueOf(c)); // 终止条件:添加排列方案;将char数组c转换成字符串
return;
}
HashSet<Character> set = new HashSet<>();
for(int i = x; i < c.length; i++) {
if(set.contains(c[i])) continue; // 重复,因此剪枝,因为题目中“里面不能有重复元素”
set.add(c[i]);
//参考剑指 Offer 17. 打印从1到最大的n位数
swap(i, x); // 交换,将c[i]固定在第x位
dfs(x + 1); // 开启固定第x+1位字符
swap(i, x); // 恢复交换
}
}
void swap(int a, int b) {
char tmp = c[a];
c[a] = c[b];
c[b] = tmp;
}
}
类似于leetcode 31.下一个排列(略)
下一个排列:
class Solution {
public String[] permutation(String s) {
List<String> res = new ArrayList<String>();
char[] c = s.toCharArray();
//首先对给定的字符串中的字符进行排序,即可得到当前字符串的第一个排列
Arrays.sort(c);
//利用nextPermutation把所有的排列加进res中
do {
res.add(new String(c));
} while (nextPermutation(c));
// int size = res.size();
// String[] resArr = new String[size];
// for (int i = 0; i < size; i++) {
// resArr[i] = res.get(i);
// }
// return resArr;
return res.toArray(new String[res.size()]);
}
//相当于手写c++中nextPermutation函数
//然后我们不断地计算当前字符串的字典序中下一个更大的排列,直到不存在更大的排列为止即可
public boolean nextPermutation(char[] arr) {
int i = arr.length - 2;
while (i >= 0 && arr[i] >= arr[i + 1]) { //有一个大的则把它往前换
i--;
}
if (i < 0) {
return false;
}
int j = arr.length - 1;
while (j >= 0 && arr[i] >= arr[j]) {
j--;
}
swap(arr, i, j);
reverse(arr, i + 1);
return true;
}
public void swap(char[] arr, int i, int j) {
char temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
public void reverse(char[] arr, int start) {
int left = start, right = arr.length - 1;
while (left < right) {
swap(arr, left, right);
left++;
right--;
}
}
}
剑指 Offer 39. 数组中出现次数超过一半的数字
数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。
你可以假设数组是非空的,并且给定的数组总是存在多数元素。
leetcode 169. 多数元素(略)
剑指 Offer 40. 最小的k个数
输入整数数组 arr ,找出其中最小的 k 个数。例如,输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4。
4种解法秒杀TopK(快排/堆/二叉搜索树/计数排序)❤️@Sweetiee⭐
排序:
class Solution {
public int[] getLeastNumbers(int[] arr, int k) {
int[] res = new int[k];
Arrays.sort(arr);
for (int i = 0; i < k; i++) {
res[i] = arr[i];
}
return res;
}
}
大根堆(前 K 小):
// 保持堆的大小为K,然后遍历数组中的数字,遍历的时候做如下判断:
// 1. 若目前堆的大小小于K,将当前数字放入堆中。
// 2. 否则判断当前数字与大根堆堆顶元素的大小关系,如果当前数字比大根堆堆顶还大,这个数就直接跳过;
// 反之如果当前数字比大根堆堆顶小,先poll掉堆顶,再将该数字放入堆中。
class Solution {
public int[] getLeastNumbers(int[] arr, int k) {
if (k == 0 || arr.length == 0) {
return new int[0];
}
// 默认是小根堆,实现大根堆需要重写一下比较器。
Queue<Integer> pq = new PriorityQueue<>((v1, v2) -> v2 - v1);
for (int num: arr) {
if (pq.size() < k) {
pq.offer(num);
} else if (num < pq.peek()) {
pq.poll();
pq.offer(num);
}
}
// 返回堆中的元素
int[] res = new int[pq.size()];
int i = 0;
for(int num: pq) {
res[i++] = num;
}
return res;
}
}
二叉搜索树:
class Solution {
public int[] getLeastNumbers(int[] arr, int k) {
if (k == 0 || arr.length == 0) {
return new int[0];
}
// TreeMap的key是数字, value是该数字的个数。
// cnt表示当前map总共存了多少个数字。
TreeMap<Integer, Integer> map = new TreeMap<>();
int cnt = 0;
for(int num: arr) {
// 1. 遍历数组,若当前map中的数字个数小于k,则map中当前数字对应个数+1
if (cnt < k) {
map.put(num, map.getOrDefault(num, 0) + 1);
cnt++;
continue;
}
// 2. 否则,取出map中最大的Key(即最大的数字), 判断当前数字与map中最大数字的大小关系:
// lastEntry() 方法用于返回此映射中的最大的键,或null,如果映射键 - 值映射关系为空。
// 若当前数字比map中最大的数字还大,就直接忽略;
// 若当前数字比map中最大的数字小,则将当前数字加入map中,并将map中的最大数字的个数-1。
Map.Entry<Integer, Integer> entry = map.lastEntry();
if (num < entry.getKey()) {
map.put(num, map.getOrDefault(num, 0) + 1);
if (entry.getValue() == 1) {
map.pollLastEntry();
} else {
map.put(entry.getKey(), entry.getValue() - 1);
}
}
}
// 最后返回map中的元素
int[] res = new int[k];
int i = 0;
for (Map.Entry<Integer, Integer> entry: map.entrySet()) {
int freq = entry.getValue();
while (freq-- > 0) {
res[i++] = entry.getKey();
}
}
return res;
}
}
快排:(最快!!!快排模板记牢!!!)