给大家一个上下班能快速的刷LeetCode hot100的好去处😁
数据结构
1.数组
1.轮转数组_4
2. 缺失的第一个正数_5
3. 最大子数组和_52
/**
* 给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
*/
/**
* todo // 定义dp:以i结尾的最大子数组和 dp[i] = max(dp[i-1] + nums[i],nums[i])
*/
public class _52最大子数组和 {
public int maxSubArray(int[] nums) {
int max = nums[0];
// todo 1. 设置dp【i】:前i的最小值
int[] dp = new int[nums.length];
// todo 2. dp初始化
dp[0] = nums[0];
// todo 3. dp迭代
for (int i = 1; i < dp.length; i++) {
dp[i] = Math.max(nums[i], dp[i - 1] + nums[i]); // todo 4, 要么选自己,要不不选自己
max = Math.max(max, dp[i]);
}
return max;
}
}
4.合并区间_56
/**
* 以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间
*/
/**
* todo 先排个序
* 这里巧用链表,可以快速的获取到last,通过last数组的第二个元素和当前数组的第一个元素对比,
* 如果当前数组的第一个元素<=last数组的第二个元素, 就需要合并
* [[1,3],[2,6],[8,10],[15,18]]
*/
public class _56合并区间 {
public int[][] merge(int[][] intervals) {
if (intervals == null || intervals.length == 0) return new int[][]{};
// todo 1.不需要特殊处理
// todo 2.排个序
Arrays.sort(intervals, (a, b) -> Integer.compare(a[0], b[0]));
// todo 3. 用个linkedList存放结果
LinkedList<int[]> list = new LinkedList<>();
list.add(intervals[0]);
for (int i = 1; i < intervals.length; i++) {
int[] last = list.getLast();
if (intervals[i][0] <= last[1]) {
// todo 1. // 如果存在[[1,8],[2,6]],所以要max一下
int max = Math.max(last[1], intervals[i][1]);
last[1] = max;
} else {
// todo 2. 如果合并不了
list.add(intervals[i]);
}
}
return list.toArray(new int[list.size()][]);
}
}
5.除自身以外数组的乘机_238
/**
* 给你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。
* <p>
* 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。
* <p>
* 请 **不要使用除法,**且在 O(n) 时间复杂度内完成此题。
*/
/**
* todo 遍历2遍,第一遍 从前往后遍历,pre记录前i-1个乘机和,res记录前i-1的乘机和,res[i]=pre; pre*=nums[i]
* 第二遍,从后往前遍历,suf记录后i+1个乘机和,res[i]乘以suf, res[i]*=suf; suf*=nums[i]
*/
public class _238除自身以外数组的乘机 {
public int[] productExceptSelf(int[] nums) {
if (nums == null || nums.length == 0) {
return new int[]{};
}
int pre = 1; // todo 前i-1乘机和
int suf = 1; // todo 后i+1乘机和
int length = nums.length;
int[] res = new int[length]; // todo 第一次遍历存放前i-1的乘机,然后 * 后i+1的乘机
// todo 1. 正序
for (int i = 0; i < length; i++) {
res[i] = pre; // todo 2. 设置前i-1的pre
pre *= nums[i]; // todo 3. 前i-1 * 第i个值
}
// todo 2. 倒序
for (int i = length - 1; i >= 0; i--) {
res[i] *= suf;
suf *= nums[i];
}
return res;
}
}
2.矩阵
1.旋转图像_48
/**
* 给定一个 n × n 的二维矩阵 matrix 表示一个图像。请你将图像顺时针旋转 90 度。
* <p>
* 你必须在 原地 旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要 使用另一个矩阵来旋转图像。
*/
/**
* todo 1.按照对角线 翻转 2、按行翻转
*/
public class _48旋转图像 {
public void rotate(int[][] matrix) {
// 1. 不需要特殊考虑
// todo 2. 按照对角线 翻转
int length = matrix.length;
for (int i = 0; i < length; i++) {
for (int j = i; j < length; j++) {
int temp = matrix[i][j];
matrix[i][j] = matrix[j][i];
matrix[j][i] = temp;
}
}
// todo 3. 按行翻转
for (int i = 0; i < length; i++) {
// todo 4. 这里只要遍历一半就行了
for (int j = 0; j < length / 2; j++) {
int temp = matrix[i][j]; // todo 第j个
matrix[i][j] = matrix[i][length - 1 - j]; // todo 倒数j个
matrix[i][length - 1 - j] = temp;
}
}
}
}
2. 搜索二维矩阵二_240
/**
* todo 从左下角往右上角遍历
*/
public class _240搜索二维矩阵二 {
public boolean searchMatrix(int[][] matrix, int target) {
int row = matrix.length - 1;
int col = 0;
while (row >= 0 && col < matrix[0].length) {
if (matrix[row][col] < target) {
col++;
} else if (matrix[row][col] > target) { // todo 如果当前这个数字 大于target,就row--
row--;
} else {
return true;
}
}
return false;
}
}
3.链表
1.两数相加_2
/**
* 你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。
* 请你将两个数相加,并以相同形式返回一个表示和的链表。
* 你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
*/
/**
* todo dummyNode + 双指针往后遍历
*/
public class _2两数相加 {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
// todo 1.由于是非空,所以不需要特殊处理
ListNode dummy = new ListNode(-1); // todo 2.设计伪指针
ListNode cur = dummy;
int forward = 0;
while (l1 != null || l2 != null) {
//
int value1 = l1 == null ? 0 : l1.val;
int value2 = l2 == null ? 0 : l2.val;
int sum = value1 + value2 + forward;
cur.next = new ListNode(sum % 10); // todo 3.获取小数部分
forward = sum / 10; // todo 4.获取整数部分
if (l1 != null) {
l1 = l1.next;
}
if (l2 != null) {
l2 = l2.next;
}
cur = cur.next;
}
// todo 5.如果最后进位不为0
if (forward != 0) {
cur.next = new ListNode(forward);
}
return dummy.next;
}
}
2. 删除链表的倒数第N个节点_19
public class _19删除链表的倒数第N个节点 {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummy = new ListNode(-1, head); // todo 1.定义一个dummy节点
// todo 2.定义快慢指针
ListNode fast = head;
ListNode slow = dummy; // todo 注意slow指向dummy !!slow就是要删除的节点
// todo 3.删倒数第一个,那就多跑一步 删倒数第二个,那就多跑2步
for (int i = 0; i < n; i++) {
fast = fast.next;
}
while (fast != null) {
fast = fast.next;
slow = slow.next;
}
slow.next = slow.next.next;
return dummy.next;
}
}
3.合并两个有序链表_21
public class _21合并两个有序链表 {
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
ListNode res = new ListNode(0);
ListNode cur = res;
while (list1 != null && list2 != null) {
if (list1.val <= list2.val) {
cur.next = list1;
list1 = list1.next;
} else {
cur.next = list2;
list2 = list2.next;
}
cur = cur.next;
}
if (list1 != null) {
cur.next = list1;
}
if (list2 != null) {
cur.next = list2;
}
return res.next;
}
}
4. 合并k个升序链表_23
/**
* todo 使用优先级队列,把k个链表数据都丢到优先级队列中
*/
public class _23合并k个升序链表 {
public ListNode mergeKLists(ListNode[] lists) {
// todo 1、判断链表是否为null
if (lists == null)
return null;
// todo 2、创建优先级队列
PriorityQueue<ListNode> queue = new PriorityQueue<>(Comparator.comparingInt(x -> x.val));
// todo 3. 遍历每一条链表, 把数据放到优先级队列中
for (ListNode node : lists) {
while (node != null) {
queue.offer(node);
node = node.next;
}
}
ListNode dummy = new ListNode();
ListNode cur = dummy;
// todo 4、从队列中取数据,
while (!queue.isEmpty()) {
ListNode miniNode = queue.poll();
cur.next = miniNode;
cur = cur.next;
// todo 5、如果是最后一个
if (queue.isEmpty()) {
cur.next = null;
}
}
return dummy.next;
}
}
5. 两两交换链表中的节点_24
/**
* 给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。
*/
/**
* todo 创建dummy节点,
* 创建三个指针:
* 第一个指针为交换节点的前指针:current
* 第二个指针为交换的第一个节点:node1
* 第三个指针为交换的第二个节点: node2
* 交换过程:
* node1.next = node2.next;
* node2.next = node1
* current.next = node2;
* current = node1;
*/
public class _24两两交换链表中的节点 {
public ListNode swapPairs(ListNode head) {
// todo 1、创建一个dummy节点
ListNode dummy = new ListNode(-1, head);
// todo 2、创建一个current指向它,也就是交换的前置节点
ListNode current = dummy;
while (current.next != null && current.next.next != null) {
ListNode node1 = current.next;
ListNode node2 = current.next.next;
// todo 3、进行交换
node1.next = node2.next;
node2.next = node1;
current.next = node2;
// todo 4、跳到交换后的第二个节点,也就是下一次交换的 前置节点
current = node1;
}
return dummy.next;
}
}
6. K个一组翻转链表_25
public class _25K个一组翻转链表aaa {
public ListNode reverseKGroup(ListNode head, int k) {
return null;
}
}
7.复制带随机指针的链表_138
public ListNode copyRandomList(ListNode head) {
return null;
}
8.环形链表_141
/**
* 给你一个链表的头节点 head ,判断链表中是否有环。
* <p>
* 如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。
* <p>
* 如果链表中存在环 ,则返回 true 。 否则,返回 false 。
*/
/**
* todo pre: 环形链表,说明不管快慢指针怎么走,最终都会相遇!!!!!
* 1、快慢指针,快指针指向head.next,慢指针指向head
* 2、while(fast != slow) 如果快指针走到了null,说明没有环 , slow = slow.next, fast = fast.next.next
*/
public class _141环形链表 {
public boolean hasCycle(ListNode head) {
if (head == null || head.next == null) return false;
ListNode fast = head.next;
ListNode slow = head;
while (slow != fast) {
if (fast == null || fast.next == null) {
return false;
}
slow = slow.next;
fast = fast.next.next;
}
return true;
}
}
9. 环形链表2_142
/**
* todo:
* 快指针跑2步,慢指针跑1步,
* 1. 第一次相遇的时候,快跑了2b, 慢跑了b
* 2. 这时候让快在跑a步,也就是第二次相遇的时候,快跑了2b+a, 慢跑了b+a
* 3. 本质上利用快慢指针和2次相遇 屏蔽了快比慢多跑b,并且第二次相遇
*/
public class _142环形链表二aaa {
public ListNode detectCycle(ListNode head) {
// 1. 特殊情况,为空或者只有一个
if (head == null || head.next == null)
return null;
// 2. 设置快慢指针,快每次跑2步,慢每次跑1步
ListNode fast = head;
ListNode slow = head;
while (true) {
// 3. 如果fast跑到结尾了,说明不存在环
if (fast == null || fast.next == null)
return null;
// 4. fast跑2步,slow跑1步
fast = fast.next.next;
slow = slow.next;
// 5. 如果第一次相遇,就退出
if (fast == slow)
break;
}
fast = head;
// 6. 第二次相遇,快跑了2b+a,慢跑了b+a
while (fast != slow) {
fast = fast.next;
slow = slow.next;
}
return fast;
}
}
10.LRU缓存_146
public class _146LRU缓存 extends LinkedHashMap<Integer, Integer> {// 1.继承linkedHashMap
private int capacity;
public _146LRU缓存(int capacity) {
super(capacity, 0.75f, true);// 2.super构造方法(容量、 hash因子、 按顺序排列)
this.capacity = capacity;
}
public int get(int key) {
return super.getOrDefault(key, -1);
}
public void put(int key, int value) {
super.put(key, value);
}
@Override
public boolean removeEldestEntry(Map.Entry<Integer, Integer> eldest) {
return size() > capacity;
}// 3. 重写removeEldestEntry
}
11.排序链表_148
public class _148排序链表aaa {
public ListNode sortList(ListNode head) {
return null;
}
}
12. 相交链表_160
/**
* todo
* 1、使用双指针
* 2、当a走完,走b,当b走完,走a,如果有交点,最终会相遇,如果没有交点,就会同时走到null
*/
public class _160相交链表 {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode A = headA;
ListNode B = headB;
// todo 当a走完,走b;当b走完 走a;
while (A != B) {
A = A == null ? headB : A.next;
B = B == null ? headA : B.next;
}
return A;
}
}
13.反转链表_206
/**
* todo:使用迭代方法
* 1、准备2个指针 prev = null、cur = head
* 2、准备第三个指针,next = cur.next(因为反转之后cur的next会丢失,所以提前保存)
* 2、cur.next = prev;prev = cur、cur = next(完成交换、2个指针向前进一步)
*/
public class _206反转链表 {
/**
* 使用迭代
*
* @param head
* @return
*/
public ListNode reverseList2(ListNode head) {
ListNode cur = head;
ListNode pre = null;
while (cur != null) {
// 先保存cur.next, 因为反转之后,就不知道当前节点的next是谁
ListNode next = cur.next;
// 指针反转
cur.next = pre;
pre = cur;
cur = next;
}
return pre;
}
}
14. 回文链表_234
/**
* todo:将链表的数据放到list中,通过list下标来判断是否是回文链表
*/
public class _234回文链表 {
public boolean isPalindrome(ListNode head) {
if (head.next == null) {
return true;
}
List<ListNode> list = new ArrayList();
// todo 1、将head中的数据放到数组中
while (head != null) {
list.add(head);
head = head.next;
}
// todo 2、从第一个和最后一个分别开始判断是否相等
int i = 0;
int j = list.size() - 1;
while (i < j) {
if (list.get(i).val != list.get(j).val) {
return false;
} else {
i++;
j--;
}
}
return true;
}
}
4.栈
1.有效的括号_20
/**
* 给定一个只包括 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否有效。
* <p>
* 有效字符串需满足:
* <p>
* 左括号必须用相同类型的右括号闭合。
* 左括号必须以正确的顺序闭合。
* 每个右括号都有一个对应的相同类型的左括号。
*/
/**
* todo 使用栈来做,因为要做字符的匹配
* 1、如果进一个‘(’,就push一个')',对其他[]和{}都是同理的
* 2、如果是右边括号,就开始要判断了,1、如果stack为空(之前没有放进去一个左括号) 2、如果pop出来和之前放进去的不一样 都是false
* 3、做完一整个操作,如果栈还是为空,返回true,说明匹配完毕了
*/
public class _20有效的括号 {
public boolean isValid(String s) {
// todo 1.不需要特殊处理,因为length > =1
// todo 2.创建一个栈,利用栈的特性,先进后出,做匹配
Stack<Character> stack = new Stack<Character>();
// 3.
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (c == '(') {
stack.push(')');
} else if (c == '{') {
stack.push('}');
} else if (c == '[') {
stack.push(']');
}
// todo 如果不是这几个
else {
if (stack.isEmpty()) { // todo 比如直接就是}]这种
return false;
}
if (c != stack.pop()) { // todo 比如直接就是{[}]这种
return false;
}
}
}
if (stack.isEmpty()) {
return true;
}
return false;
}
}
2.柱状图中最大的矩形_84
3.最小栈_155
/**
* todo 使用2个栈来做
* 1、使用第一个栈main来保存所有的数据
* 2、使用第二个栈helper来完成min 函数,栈底->栈顶 是降序的
* push函数:main.push, 如果helper为空,或者数字比helper.peek小,helper.push
* pop函数: main.pop, 如果helper不为空,并且helper.peek和main.peek的值是一样的,helper.pop
* top函数; main.peek
* min函数: helper.peek
*/
public class _155最小栈 {
// 主栈
private Stack<Integer> main = new Stack();
// 栈底->栈顶 降序辅助栈,取的时候取栈顶就行
private Stack<Integer> helper = new Stack();
/**
* initialize your data structure here.
*/
public _155最小栈() {
}
// main正常push,辅助栈push的时候,看一下helper是否为空,或者push的数据是不是比helper栈顶数据小
public void push(int x) {
main.push(x);
if (helper.isEmpty() || x <= helper.peek()) {
helper.push(x);
}
}
public void pop() {
// 在main出栈的时候,保证helper栈元素和main一致,如果说helper和main一样的话,也要出栈
if (!helper.isEmpty() && main.peek().equals(helper.peek())) {
helper.pop();
}
main.pop();
}
public int top() {
return main.peek();
}
public int min() {
return helper.peek();
}
}
4.字符串解码_394
/**
* 给定一个经过编码的字符串,返回它解码后的字符串。
* <p>
* 编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。
* <p>
* 你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,且输入的方括号总是符合格式要求的。
* <p>
* 此外,你可以认为原始数据不包含数字,所有的数字只表示重复的次数 k ,例如不会出现像 3a 或 2[4] 的输入。
*/
import java.util.Stack;
/**
* 3[a2[c]]
* 逻辑都是统一的: (2 * c + a)* 3
*/
public class _394字符串解码aaa {
public String decodeString(String s) {
// 1. 不需要特殊处理
// 2. 准备容器
StringBuilder stringBuilder = new StringBuilder(); // 1.存放遍历过程中的字符串
int mul = 0; // 2.倍数
Stack<String> strStack = new Stack<String>(); // 3. 存放上一个[]里面的字符串
Stack<Integer> mulStack = new Stack<Integer>(); // 4. 存放上一个[]里面的倍数
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (c == '[') { // 5.将【左边的字符串和倍数都存到stack中
mulStack.push(mul);
strStack.push(stringBuilder.toString());
// 6.重置倍数 和字符串容器
mul = 0;
stringBuilder = new StringBuilder();
} else if (c == ']') {
// 7. 将【之前的倍数和字符串取出来, 最终等于 之前的字符串 + 倍数 * 【】中间的字符串
String pop = strStack.pop();
Integer count = mulStack.pop();
StringBuilder temp = new StringBuilder();
for (Integer integer = 0; integer < count; integer++) {
temp.append(stringBuilder);
}
stringBuilder = new StringBuilder();
stringBuilder.append(pop).append(temp);
} else if (c >= '0' && c <= '9') {
mul = 10 * mul + Integer.parseInt(c + ""); // 3. 记录倍数,防止 10、11这种
} else {
stringBuilder.append(c); // 4. 记录【之前的字符串
}
}
return stringBuilder.toString();
}
}
5.每日温度_739
5. 队列
6. 二叉树
1.二叉树的中序遍历_94
/**
* todo 中序遍历:递归(左子树)+ 操作+ 递归(右子数)
*/
public class _94二叉树的中序遍历 {
public List<Integer> inorderTraversal(TreeNode root) {
ArrayList<Integer> res = new ArrayList<>();
inorder(root, res);
return res;
}
public void inorder(TreeNode root, ArrayList<Integer> res) {
if (root == null) {
return;
}
inorder(root.left, res);
res.add(root.val);
inorder(root.right, res);
}
}
2.不同的二叉搜索树_96
/**
* 给你一个整数 n ,求恰由 n 个节点组成且节点值从 1 到 n 互不相同的 二叉搜索树 有多少种?返回满足题意的二叉搜索树的种数。
*/
public class _96不同的二叉搜索树again {
public int numTrees(int n) {
return 0;
}
}
3.验证二叉搜索树_98
/**
* 给你一个二叉树的根节点 root ,判断其是否是一个有效的二叉搜索树。
* <p>
* 有效 二叉搜索树定义如下:
* <p>
* 节点的左
* 子树
* 只包含 小于 当前节点的数。
* 节点的右子树只包含 大于 当前节点的数。
* 所有左子树和右子树自身必须也是二叉搜索树。
*/
/**
* todo:对于每一层在递归什么,比如root,或者root.left 都要保证一个什么东西:
* 当前节点在一个合法的范围,对于root就是 >minValue 和< maxValue,对于下一层的left >minValue 和小于root,对于下一层的right > root和小于maxValue
*/
public class _98验证二叉搜索树 {
public boolean isValidBST(TreeNode root) {
if (root == null) return true;
return isValidBST(root, Long.MIN_VALUE, Long.MAX_VALUE);
}
private boolean isValidBST(TreeNode node, long minValue, long maxValue) {
if (node == null) return true; // 递归终止条件
if (node.val <= minValue || node.val >= maxValue) return false;
return isValidBST(node.left, minValue, node.val) && isValidBST(node.right, node.val, maxValue);
}
}
4. 对称二叉树_101
/**
* todo 怎么判定一个二叉树是对称的,
* 1、root为空
* 2、root不为空,并且左子树和右子树是对称的
* 3、左右子树又是如何对称:1、left和right为null 2、left和right相等 3、left的left和right.right对称、left的right和right的left对称
*/
public class _101对称二叉树 {
public boolean isSymmetric(TreeNode root) {
// 一个树是对称的,意味着它的左子树和右子树是镜像对称的
return root == null || isMirror(root.left, root.right);
}
private boolean isMirror(TreeNode left, TreeNode right) {
// 如果两个节点都为空,那么它们是对称的
if (left == null && right == null) {
return true;
}
// 如果其中一个节点为空,另一个不为空,那么它们不是对称的
if (left == null || right == null) {
return false;
}
// 如果两个节点的值不同,那么它们不是对称的
if (left.val != right.val) {
return false;
}
// 递归检查当前节点的左子节点的左子树和右子节点的右子树,以及当前节点的右子节点的左子树和左子节点的右子树是否对称
return isMirror(left.left, right.right) && isMirror(left.right, right.left);
}
}
5. 二叉树的层序遍历_102
/**
* todo 用queue来解决
* 1、把root节点放到queue中
* 2、遍历queue,每一个层遍历,都创建一个List,然后把节点的left、right放到queue里面去
*/
public class _102二叉树的层序遍历 {
public List<List<Integer>> res = new ArrayList<>();
public Queue<TreeNode> queue = new ArrayDeque<TreeNode>();
public List<List<Integer>> levelOrder(TreeNode root) {
if (root == null) {
return res;
}
queue.add(root);
helper();
return res;
}
public void helper() {
while (!queue.isEmpty()) {
// 给每一次queue里面的数据新建一个list
List<Integer> level = new ArrayList<Integer>();
int size = queue.size();
for (int i = 0; i < size; i++) {
TreeNode node = queue.poll();
level.add(node.val);
if (node.left != null) {
queue.add(node.left);
}
if (node.right != null) {
queue.add(node.right);
}
}
res.add(level);
}
}
}
6. 二叉树的最大深度_104
/**
* 深度:从root往下有几个节点,就是多少深度
*/
/**
* todo 相当于后序遍历,左子树、右子数,最后操作(计算深度
*/
public class _104二叉树的最大深度 {
public int maxDepth(TreeNode root) {
// 如果节点为空,则该树的深度为0
if (root == null) {
return 0;
}
// 递归地计算左子树和右子树的最大深度
int leftDepth = maxDepth(root.left);
int rightDepth = maxDepth(root.right);
// 树的最大深度是左、右子树深度的最大值 + 1(当前节点自身)
return Math.max(leftDepth, rightDepth) + 1;
}
}
7. 从前序与中序遍历序列构造二叉树_105
/**
* 给定两个整数数组 preorder 和 inorder ,其中 preorder 是二叉树的先序遍历, inorder 是同一棵树的中序遍历,请构造二叉树并返回其根节点。
*/
/**
* todo
* 1、前序给我们带来什么,一下子能知道根是谁,
* 2、中序给我们什么,通过根 知道左子树的结束点在哪里,右子树开始在哪里
* <p> 3
* 9 20
* 15 7
* <p> 输入: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]
* 所以算法就是不断的执行递归函数 recur(int root, int left, int right):
* 1、从前序 + root位置创建跟节点
* 2、从根节点 + 中序遍历来获取根节点的位置i
* 3、root的左子树的范围【left, i-1】,root的右子树的范围【i+1, right】
* 4、继续递归调用root.left = recur(root + 1, left, i - 1), root.right = recur(root + (i-left) +1 ,i + 1,right)
*/
public class _105从前序与中序遍历序列构造二叉树 {
int[] preorder;
HashMap<Integer, Integer> dic = new HashMap<>();
public TreeNode _105从前序与中序遍历序列构造二叉树(int[] preorder, int[] inorder) {
this.preorder = preorder;
for (int i = 0; i < inorder.length; i++)
dic.put(inorder[i], i);
return recur(0, 0, inorder.length - 1);
}
TreeNode recur(int root, int left, int right) {
if (left > right)
return null; // 递归终止
TreeNode node = new TreeNode(preorder[root]); // 建立根节点
int i = dic.get(preorder[root]); // 划分根节点、左子树、右子树
node.left = recur(root + 1, left, i - 1); // 开启左子树递归
node.right = recur(root + (i - left) + 1, i + 1, right); // 开启右子树递归 // todo root+(index -left)+ 1
return node; // 回溯返回根节点
}
}
8. 将有序数组转换为二叉搜索树_108
/**
* todo:前序模版: 先创建root元素,然后左子树递归,右子树递归
*/
public class _108将有序数组转换为二叉搜索树 {
public TreeNode sortedArrayToBST(int[] nums) {
// 数组,左,右
return conversion(nums, 0, nums.length - 1);
}
TreeNode conversion(int[] nums, int left, int right) {
if (left > right)
return null;
int mid = (left + right) / 2;
TreeNode root = new TreeNode(nums[mid]);
root.left = conversion(nums, left, mid - 1);
root.right = conversion(nums, mid + 1, right);
return root;
}
}
9. 二叉树展开为链表_114
/**
* 给你二叉树的根结点 root ,请你将它展开为一个单链表:
*
* 展开后的单链表应该同样使用 TreeNode ,其中 right 子指针指向链表中下一个结点,而左子指针始终为 null 。
* 展开后的单链表应该与二叉树 先序遍历 顺序相同。
*/
/**
* todo:先序遍历将TreeNode放到集合中,遍历集合将TreeNode的left置空,right=集合的下一个treeNode
*/
public class _114二叉树展开为链表 {
public void flatten(TreeNode root) {
List<TreeNode> list = new ArrayList<TreeNode>();
// 前序遍历把节点放到list中
preOrder(root, list);
// 将list的结构重新改成想要的树
for (int i = 0; i < list.size(); i++) {
TreeNode treeNode = list.get(i);
treeNode.left = null;
treeNode.right = i == list.size() - 1 ? null : list.get(i + 1);
}
}
private void preOrder(TreeNode root, List<TreeNode> list) {
if (root == null) return;
list.add(root);
preOrder(root.left, list);
preOrder(root.right, list);
}
}
10. 二叉树的最大路径和_124
在这里插入代码片
11. 二叉树的右视图_199
在这里插入代码片
12. 翻转二叉树_226
/**
* todo 也是个后序操作的模版: 先递归左子树,后递归右子数,最后reverse
*/
public class _226翻转二叉树 {
public TreeNode invertTree(TreeNode root) {
// 如果节点为空,则直接返回null
if (root == null) {
return null;
}
// 递归翻转左子树和右子树
TreeNode left = invertTree(root.left);
TreeNode right = invertTree(root.right);
// 交换当前节点的左右子树
root.left = right;
root.right = left;
// 返回当前节点,作为其父节点的子节点
return root;
}
}
13.二叉搜索树中第k个小的元素_230
/**
* 给定一个二叉搜索树的根节点 root ,和一个整数 k ,请你设计一个算法查找其中第 k 小的元素(从 1 开始计数)。
*/
/**
* todo: 用中序遍历的模版,一直左子树递归,左子树出栈的时候,计数器+1,当计数器=k的时候,就是返回值,然后右子树递归
*/
public class _230二叉搜索树中第k小的元素 {
// todo 1、结果值
int res = 0;
// todo 2、从左子树开始出栈次序
int rank = 0;
public int kthSmallest(TreeNode root, int k) {
traverse(root, k);
return res;
}
void traverse(TreeNode root, int k) {
if (root == null)
return;
// todo 3、 左子树递归
traverse(root.left, k);
// todo 4、当运行到这里,说明左子树出栈,已经是最小的
rank++;
// todo 5.找到第k个,获取它的值
if (k == rank) {
res = root.val;
return;
}
traverse(root.right, k);
}
}
14. 二叉树的最近公共祖先_236
/**
* todo 1、将二叉树当前的节点和父亲节点放到 map中
* 2、开始遍历p节点,将p节点放到一个set中,并且不断从map中获取它的父节点,就是从子节点到父节点都放到这个集合中
* 3、开始遍历q节点到它的父节点,如果set中存在这些节点,就返回,相当于找到它们共有的父亲节点
*/
public class _236二叉树的最近公共祖先 {
public Map<Integer, TreeNode> nodeValue2parent = new HashMap<>();
Set<Integer> visited = new HashSet<>();
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
// 1、通过遍历二叉树将节点的值和它的父节点存到nodeValue2parent
dfs(root, null);
// 2.遍历p的父节点
while (p != null) {
visited.add(p.val);
p = nodeValue2parent.get(p.val);
}
// 3.遍历q的父节点,如果节点同时存在于visited
while (q != null) {
if (visited.contains(q.val)) {
return q;
}
q = nodeValue2parent.get(q.val);
}
return null;
}
public void dfs(TreeNode node, TreeNode parent) {
if (node != null) {
nodeValue2parent.put(node.val, parent);
dfs(node.left, node);
dfs(node.right, node);
}
}
}
15. 路径总和_437
public class _437路径总和 {
public int pathSum(TreeNode root, long targetSum) {
if (root == null) return 0;
return pathsFromNode(root, targetSum) + pathSum(root.left, targetSum) + pathSum(root.right, targetSum);
}
/**
* todo 到指定节点满足target的个数,连续
*/
public int pathsFromNode(TreeNode root, long targetSum) {
int count = 0;
if (root == null) return 0; // 递归终止条件
if (root.val == targetSum) count++;
// 继续往左右子树下探
count += pathsFromNode(root.left, targetSum - root.val);
count += pathsFromNode(root.right, targetSum - root.val);
return count;
}
}
16. 二叉树的直径_543
/**
* todo:和最大深度类似,不同的是,每次在 后序遍历完之后都要更新 一下最大直径
*/
public class _543二叉树的直径 {
static int ans = 0;
public static int diameterOfBinaryTree(TreeNode root) {
if (root == null) return 0;
depth(root);
return ans;
}
private static int depth(TreeNode node) {
if (node == null) return 0; //递归终止条件
int leftDepth = depth(node.left);
int rightDepth = depth(node.right);
ans = Math.max(ans, leftDepth + rightDepth);
return Math.max(leftDepth, rightDepth) + 1;
}
}
7. 图
8. hash
算法
1. 排序
1.归并排序
public class MergeSort {
public static void merge(int[] a, int low, int mid, int high) {
int[] temp = new int[high - low + 1];
int i = low;// 左指针
int j = mid + 1;// 右指针
int k = 0;
// 把较小的数先移到新数组中
while (i <= mid && j <= high) {
if (a[i] < a[j]) {
temp[k++] = a[i++];
} else {
temp[k++] = a[j++];
}
}
// 把左边剩余的数移入数组
while (i <= mid) {
temp[k++] = a[i++];
}
// 把右边边剩余的数移入数组
while (j <= high) {
temp[k++] = a[j++];
}
// 把新数组中的数覆盖a数组
for (int k2 = 0; k2 < temp.length; k2++) {
a[k2 + low] = temp[k2];
}
}
public static void mergeSort(int[] a, int low, int high) {
int mid = (low + high) / 2;
if (low < high) {
// 左边
mergeSort(a, low, mid);
// 右边
mergeSort(a, mid + 1, high);
// 左右归并
merge(a, low, mid, high);
System.out.println(Arrays.toString(a));
}
}
public static void main(String[] args) {
int a[] = {51, 46, 20, 18, 65, 97, 82, 30, 77, 50};
mergeSort(a, 0, a.length - 1);
System.out.println("排序结果:" + Arrays.toString(a));
}
}
2. 快速排序
/**
* [6, 72, 113, 11, 23] 6 比23小,array[point=0] = array【i=0】,array【i=0】= array[point=0] , point = 1
* [6, 72, 113, 11, 23]
* [6, 72, 113, 11, 23]
* [6, 11, 113, 72, 23] 11比23小,array【point=1】= array[i = 3], array[i=3] = array[point = 1], point = 2
* [6, 11, 23, 72, 113] 等遍历完成后,array[point = 2] = array[high], array[high] = array[point = 2]
* [6, 11, 23, 72, 113]
* 排序后的结果
* [6, 11, 23, 72, 113]
*/
public class QuickSort {
public static int partition(int[] array, int low, int high) {
System.out.println("这是一次");
// 取最后一个元素作为中心元素
int pivot = array[high];
// todo 如果array[i]小于中心元素,那就pointer跟这个array[i]替换,然后pointer++,也就是左边都是小于中心元素的
int pointer = low;
// 遍历数组中的所有元素,将比中心元素大的放在右边,比中心元素小的放在左边
for (int i = low; i < high; i++) {
if (array[i] <= pivot) {
// 将比中心元素小的元素和指针指向的元素交换位置
// 如果第一个元素比中心元素小,这里就是自己和自己交换位置,指针和索引都向下一位移动
// 如果元素比中心元素大,索引向下移动,指针指向这个较大的元素,直到找到比中心元素小的元素,并交换位置,指针向下移动
int temp = array[i];
array[i] = array[pointer];
array[pointer] = temp;
pointer++;
}
System.out.println(Arrays.toString(array));
System.out.println("pointer:" + pointer);
System.out.println("i:" + i);
}
// 将中心元素和指针指向的元素交换位置
int temp = array[pointer];
array[pointer] = array[high];
array[high] = temp;
System.out.println(Arrays.toString(array));
return pointer;
}
public static void quickSort(int[] array, int low, int high) {
if (low < high) {
// 获取划分子数组的位置
int position = partition(array, low, high);
// 左子数组递归调用
quickSort(array, low, position - 1);
// 右子数组递归调用
quickSort(array, position + 1, high);
}
}
public static void main(String[] args) {
//int[] array = {6, 15, 10, 11, 8};
int[] array = {6, 72, 113, 11, 23};
quickSort(array, 0, array.length - 1);
System.out.println("排序后的结果");
System.out.println(Arrays.toString(array));
}
}
2. 二分
1.二分查找_704
public class _704二分查找 {
public static int search(int[] nums, int target) {
if (null == nums || nums.length == 0) {
return -1;
}
int left = 0, right = nums.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] == target) {
return mid;
} else if (nums[mid] > target) {
right = mid - 1;
} else if (nums[mid] < target) {
left = mid + 1;
}
}
return -1;
}
public static void main(String[] args) {
int[] nums = {1, 1, 7, 10, 30};
int search = search(nums, 7);
System.out.println(search);
}
}
2. 二分查找(左边界)_704
public class _704二分查找_左边界 {
public static int search(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (target == nums[mid]) {
// todo 与常规二分区别, 继续往左边找
right = mid - 1;
} else if (target > nums[mid]) {
left = mid + 1;
} else if (target < nums[mid]) {
right = mid - 1;
}
}
// todo 与常规二分区别,最终left=right 是退出循环的,看left就行,left只会变大
if (right < 0 || left >= nums.length || target != nums[left]) {
return -1;
}
return left;
}
public static void main(String[] args) {
int[] nums = {1, 7, 7, 10, 30};
int search = search(nums, 7);
System.out.println(search);
}
}
3. 二分查找(右边界)_704
public class _704二分查找_右边界 {
public static int search(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (target == nums[mid]) {
left = mid + 1; // todo 与常规不同的,往右边找
} else if (target > nums[mid]) {
left = mid + 1;
} else if (target < nums[mid]) {
right = mid - 1;
}
}
if (right < 0 || left >= nums.length || nums[right] != target) {
return -1;
}
return right;
}
public static void main(String[] args) {
int[] nums = {1, 1, 7, 7, 30};
int search = search(nums, 7);
System.out.println(search);
}
}
4.
3. 双指针
1. 盛最多水的容器_11
/**
* 给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。
* 找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。
* 返回容器可以储存的最大水量。
*/
/**
* todo:使用双指针,left和right,判断left和right哪个高度大,如果height(left)>height(right) ?
* max(max,(right-left) * height[left++] :max(max,(right-left) * height[right--]))
*/
public class _11盛最多水的容器_again {
public int maxArea(int[] height) {
// todo 1. 不需要特殊处理
int max = 0;
int left = 0;
int right = height.length - 1;
// todo 2. 当left < right
while (left < right) {
// todo 3. 看左边和右边哪个高度大,如果左边,那就是从当前的容量和右边往左格的容量的最大值(这里height[left ++]表示先算面积,然后left++),
// 如果是右边大,那就是当前容量和左边往右一个的容量的最大值
max = height[left] < height[right] ?
Math.max(max, (right - left) * height[left++]) : Math.max(max, (right - left) * height[right--]);
}
return max;
}
}
2.三数之和_15
在这里插入代码片
3. 接雨水_42
4. 移动零_283
/**
* 给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。
* <p>
* 请注意 ,必须在不复制数组的情况下原地对数组进行操作。
*/
/**
* todo:用j指向不为0的数字,刚开始指向第一个,遍历nums数组,如果nums[i]不为0, nums[j]= nums[i]
* 总结:如果nums[i]不为0,就不断往nums[j]放,nums【j】依次放到不是零的数
*/
public class _283移动零 {
public void moveZeroes(int[] nums) {
if (nums == null) {
return;
}
int j = 0;
for (int i = 0; i < nums.length; i++) {
if (nums[i] != 0) {
int tmp = nums[i];
nums[i] = nums[j];
nums[j++] = tmp;
}
}
}
}
4. 滑动窗口
5. 贪心
1.跳跃游戏_55
/**
* 给你一个非负整数数组 nums ,你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。
* <p>
* 判断你是否能够到达最后一个下标,如果可以,返回 true ;否则,返回 false
*/
/**
* [2,3,1,1,4]:true,[3,2,1,0,4]:false
*/
public class _55跳跃游戏 {
public boolean canJump(int[] nums) {
if (nums == null || nums.length == 0) return false;
int maxReach = 0;
for (int i = 0; i < nums.length; i++) {
// todo 如果当前位置超过之前能到达的最大位置
if (i > maxReach) return false;
// todo 对于当前位置,取一个能跑的最远位置(之前的maxReach,还是当前跑的最远的位置)
maxReach = Math.max(maxReach, i + nums[i]);
// todo 如果最远位置已经能跑到最后了,就成功了
if (maxReach >= nums.length - 1) return true;
}
return false;
}
}
6. 回溯
1.电话号码的字母组合_17
在这里插入代码片