leetcode练习
分类
题单:code
解答:解答
code
[1. 两数之和](#1. 两数之和)
[2. 两数相加](#2. 两数相加)
[3. 无重复字符的最长子串](#3. 无重复字符的最长子串)
[5. 最长回文子串](#5. 最长回文子串)
[19. 删除链表的倒数第 N 个结点](#19. 删除链表的倒数第 N 个结点)
[20. 有效的括号](#20. 有效的括号)
[21. 合并两个有序链表](#21. 合并两个有序链表)
[23. 合并K个升序链表](#23. 合并K个升序链表)
[25. k 个一组翻转链表](#25. k 个一组翻转链表)
[26. 删除有序数组中的重复项](#26. 删除有序数组中的重复项)
[27. 移除元素](#27. 移除元素)
[34. 在排序数组中查找元素的第一个和最后一个位置](#34. 在排序数组中查找元素的第一个和最后一个位置)
[76. 最小覆盖子串](#76. 最小覆盖子串)
[86. 分隔链表](#86. 分隔链表)
[92. 反转链表II](92. 反转链表 II)
[94. 二叉树的中序遍历](#94. 二叉树的中序遍历)
[100. 相同的树](#100. 相同的树)
[101. 对称二叉树](#101. 对称二叉树)
[102. 二叉树的层序遍历](#102. 二叉树的层序遍历)
[104. 二叉树的最大深度](#104. 二叉树的最大深度)
[105. 从前序与中序遍历序列构造二叉树](#105. 从前序与中序遍历序列构造二叉树)
[106. 从中序与后序遍历序列构造二叉树](#106. 从中序与后序遍历序列构造二叉树)
[107. 二叉树的层序遍历 II](#107. 二叉树的层序遍历 II)
[111. 二叉树的最小深度](#111. 二叉树的最小深度)
[112. 路径总和](#112. 路径总和)
[113. 路径总和 II](#113. 路径总和 II)
[114. 二叉树展开为链表](#114. 二叉树展开为链表)
[141. 环形链表](#141. 环形链表)
[142. 环形链表 II](#142. 环形链表 II)
[144. 二叉树的前序遍历](#144. 二叉树的前序遍历)
[145. 二叉树的后序遍历](#145. 二叉树的后序遍历)
[160. 相交链表](#160. 相交链表)
[167. 两数之和 II - 输入有序数组](#167. 两数之和 II - 输入有序数组)
[206. 反转链表](#206. 反转链表)
[226. 翻转二叉树](#226. 翻转二叉树)
[234. 回文链表](#234. 回文链表)
[257. 二叉树的所有路径](#257. 二叉树的所有路径)
[283. 移动零](#283. 移动零)
[303. 区域和检索 - 数组不可变](#303. 区域和检索 - 数组不可变)
[344. 反转字符串](#344. 反转字符串)
[438. 找到字符串中所有字母异位词](#438. 找到字符串中所有字母异位词)
[509. 斐波那契数](#509. 斐波那契数)
[567. 字符串的排列](#567. 字符串的排列)
[654. 最大二叉树](#654. 最大二叉树)
[876. 链表的中间结点](#876. 链表的中间结点)
simple
[1. 两数之和](#1. 两数之和)
[20. 有效的括号](#20. 有效的括号)
[21. 合并两个有序链表](#21. 合并两个有序链表)
[26. 删除有序数组中的重复项](#26. 删除有序数组中的重复项)
[27. 移除元素](#27. 移除元素)
[94. 二叉树的中序遍历](#94. 二叉树的中序遍历)
[100. 相同的树](#100. 相同的树)
[101. 对称二叉树](#101. 对称二叉树)
[104. 二叉树的最大深度](#104. 二叉树的最大深度)
[111. 二叉树的最小深度](#111. 二叉树的最小深度)
[112. 路径总和](#112. 路径总和)
[141. 环形链表](#141. 环形链表)
[144. 二叉树的前序遍历](#144. 二叉树的前序遍历)
[145. 二叉树的后序遍历](#145. 二叉树的后序遍历)
[160. 相交链表](#160. 相交链表)
[206. 反转链表](#206. 反转链表)
[226. 翻转二叉树](#226. 翻转二叉树)
[234. 回文链表](#234. 回文链表)
[257. 二叉树的所有路径](#257. 二叉树的所有路径)
[283. 移动零](#283. 移动零)
[303. 区域和检索 - 数组不可变](#303. 区域和检索 - 数组不可变)
[344. 反转字符串](#344. 反转字符串)
[509. 斐波那契数](#509. 斐波那契数)
[876. 链表的中间结点](#876. 链表的中间结点)
middle
[2. 两数相加](#2. 两数相加)
[3. 无重复字符的最长子串](#3. 无重复字符的最长子串)
[5. 最长回文子串](#5. 最长回文子串)
[19. 删除链表的倒数第 N 个结点](#19. 删除链表的倒数第 N 个结点)
[23. 合并K个升序链表](#23. 合并K个升序链表)
[34. 在排序数组中查找元素的第一个和最后一个位置](#34. 在排序数组中查找元素的第一个和最后一个位置)
[86. 分隔链表](#86. 分隔链表)
[92. 反转链表II](92. 反转链表 II)
[102. 二叉树的层序遍历](#102. 二叉树的层序遍历)
[105. 从前序与中序遍历序列构造二叉树](#105. 从前序与中序遍历序列构造二叉树)
[106. 从中序与后序遍历序列构造二叉树](#106. 从中序与后序遍历序列构造二叉树)
[107. 二叉树的层序遍历 II](#107. 二叉树的层序遍历 II)
[113. 路径总和 II](#113. 路径总和 II)
[114. 二叉树展开为链表](#114. 二叉树展开为链表)
[142. 环形链表 II](#142. 环形链表 II)
[167. 两数之和 II - 输入有序数组](#167. 两数之和 II - 输入有序数组)
[438. 找到字符串中所有字母异位词](#438. 找到字符串中所有字母异位词)
[567. 字符串的排列](#567. 字符串的排列)
[654. 最大二叉树](#654. 最大二叉树)
hard
[25. k 个一组翻转链表](#25. k 个一组翻转链表)
[76. 最小覆盖子串](#76. 最小覆盖子串)
数组
[1. 两数之和](#1. 两数之和)
[26. 删除有序数组中的重复项](#26. 删除有序数组中的重复项)
[27. 移除元素](#27. 移除元素)
[34. 在排序数组中查找元素的第一个和最后一个位置](#34. 在排序数组中查找元素的第一个和最后一个位置)
[167. 两数之和 II - 输入有序数组](#167. 两数之和 II - 输入有序数组)
[283. 移动零](#283. 移动零)
[303. 区域和检索 - 数组不可变](#303. 区域和检索 - 数组不可变)
链表
[2. 两数相加](#2. 两数相加)
[19. 删除链表的倒数第 N 个结点](#19. 删除链表的倒数第 N 个结点)
[21. 合并两个有序链表](#21. 合并两个有序链表)
[23. 合并K个升序链表](#23. 合并K个升序链表)
[25. k 个一组翻转链表](#25、k 个一组翻转链表)
[92. 反转链表II](#92. 反转链表)
[141. 环形链表](#141. 环形链表)
[142. 环形链表 II](#142. 环形链表 II)
[160. 相交链表](#160. 相交链表)
[206. 反转链表](206. 反转链表)
[234. 回文链表](#234. 回文链表)
[876. 链表的中间结点](#876. 链表的中间结点)
字符串
[3. 无重复字符的最长子串](#3. 无重复字符的最长子串)
[5. 最长回文子串](#5. 最长回文子串)
[20. 有效的括号](#20. 有效的括号)
[76. 最小覆盖子串](#76. 最小覆盖子串)
[344. 反转字符串](#344. 反转字符串)
[438. 找到字符串中所有字母异位词](#438. 找到字符串中所有字母异位词)
[567. 字符串的排列](#567. 字符串的排列)
二叉树
[94. 二叉树的中序遍历](#94. 二叉树的中序遍历)
[100. 相同的树](#100. 相同的树)
[101. 对称二叉树](#101. 对称二叉树)
[102. 二叉树的层序遍历](#102. 二叉树的层序遍历)
[104. 二叉树的最大深度](#104. 二叉树的最大深度)
[105. 从前序与中序遍历序列构造二叉树](#105. 从前序与中序遍历序列构造二叉树)
[106. 从中序与后序遍历序列构造二叉树](#106. 从中序与后序遍历序列构造二叉树)
[107. 二叉树的层序遍历 II](#107. 二叉树的层序遍历 II)
[111. 二叉树的最小深度](#111. 二叉树的最小深度)
[112. 路径总和](#112. 路径总和)
[113. 路径总和 II](#113. 路径总和 II)
[114. 二叉树展开为链表](#114. 二叉树展开为链表)
[144. 二叉树的前序遍历](#144. 二叉树的前序遍历)
[145. 二叉树的后序遍历](#145. 二叉树的后序遍历)
[226. 翻转二叉树](#226. 翻转二叉树)
[257. 二叉树的所有路径](#257. 二叉树的所有路径)
[654. 最大二叉树](#654. 最大二叉树)
递归和迭代
[2. 两数相加](#2. 两数相加)
[25. k 个一组翻转链表](#25. k 个一组翻转链表)
[92. 反转链表II](92. 反转链表 II)
[94. 二叉树的中序遍历](#94. 二叉树的中序遍历)
[100. 相同的树](#100. 相同的树)
[101. 对称二叉树](#101. 对称二叉树)
[102. 二叉树的层序遍历](#102. 二叉树的层序遍历)
[104. 二叉树的最大深度](#104. 二叉树的最大深度)
[105. 从前序与中序遍历序列构造二叉树](#105. 从前序与中序遍历序列构造二叉树)
[106. 从中序与后序遍历序列构造二叉树](#106. 从中序与后序遍历序列构造二叉树)
[107. 二叉树的层序遍历 II](#107. 二叉树的层序遍历 II)
[111. 二叉树的最小深度](#111. 二叉树的最小深度)
[112. 路径总和](#112. 路径总和)
[113. 路径总和 II](#113. 路径总和 II)
[114. 二叉树展开为链表](#114. 二叉树展开为链表)
[144. 二叉树的前序遍历](#144. 二叉树的前序遍历)
[145. 二叉树的后序遍历](#145. 二叉树的后序遍历)
[206. 反转链表](#206. 反转链表)
[226. 翻转二叉树](#226. 翻转二叉树)
[234. 回文链表](#234. 回文链表)
[257. 二叉树的所有路径](#257. 二叉树的所有路径)
[654. 最大二叉树](#654. 最大二叉树)
滑动窗口
[3. 无重复字符的最长子串](#3. 无重复字符的最长子串)
[76. 最小覆盖子串](#76. 最小覆盖子串)
[438. 找到字符串中所有字母异位词](#438. 找到字符串中所有字母异位词)
[567. 字符串的排列](#567. 字符串的排列)
Map
[1. 两数之和](#1. 两数之和)
Stack
[20. 有效的括号](#20. 有效的括号)
[234. 回文链表](#234. 回文链表)
堆
[23. 合并K个升序链表](#23. 合并K个升序链表)
双指针
[19. 删除链表的倒数第 N 个结点](#19. 删除链表的倒数第 N 个结点)
[21. 合并两个有序链表](#21. 合并两个有序链表)
[86. 分隔链表](#86. 分隔链表)
[160. 相交链表](#160. 相交链表)
左右指针
[5. 最长回文子串](#5. 最长回文子串)
[167. 两数之和 II - 输入有序数组](#167. 两数之和 II - 输入有序数组)
[344. 反转字符串](#344. 反转字符串)
快慢指针
[26. 删除有序数组中的重复项](#26. 删除有序数组中的重复项)
[27. 移除元素](#27. 移除元素)
[141. 环形链表](#141. 环形链表)
[142. 环形链表 II](#142. 环形链表 II)
[283. 移动零](#283. 移动零)
[876. 链表的中间结点](#876. 链表的中间结点)
二分查找
[34. 在排序数组中查找元素的第一个和最后一个位置](#34. 在排序数组中查找元素的第一个和最后一个位置)
前缀和
[303. 区域和检索 - 数组不可变](#303. 区域和检索 - 数组不可变)
动态规划
排序
[100000. 冒泡排序](#100000. 冒泡排序)
[100001. 插入排序](#100001. 插入排序)
[100002. 选择排序](#100002. 选择排序)
[100003. 希尔排序](#100003. 希尔排序)
[100004. 快速排序](#100004. 快速排序)
[100005. 归并排序](#100005. 归并排序)
解答
1. 两数之和
class Solution {
public int[] twoSum(int[] nums, int target) {
Map<Integer, Integer> map = new HashMap<>();
//b = target - a,存储 b 值,遍历后面数字是否等于 b
for (int i = 0; i < nums.length; i++) {
//获取当前值
Integer index = map.get(nums[i]);
if (index != null) {
return new int[]{index, i};
}
//存储差值
int diff = target - nums[i];
map.put(diff, i);
}
return new int[0];
}
}
2. 两数相加
class Solution {
//逆序存储,个位在前
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
//构建虚拟节点
ListNode dummy = new ListNode(-1);
ListNode p = dummy;
//记录进位
int carry = 0;
while (l1 != null || l2 != null || carry > 0) {
int val = carry;
if (l1 != null) {
val += l1.val;
l1 = l1.next;
}
if (l2 != null) {
val += l2.val;
l2 = l2.next;
}
//记录进位
carry = val / 10;
val = val % 10;
p.next = new ListNode(val);
p = p.next;
}
return dummy.next;
}
}
3. 无重复字符的最长子串
class Solution {
//滑动窗口
public int lengthOfLongestSubstring(String s) {
//定义字典:记录窗口内的元素,使用 map 或 set
//这里使用map<字符,字符数量>,这里记录数量而不记录下标是为了通用性
Map<Character, Integer> map = new HashMap<>();
//定义左右窗口
int right = 0, left = 0;
int maxLen = 0;
//右窗口退出条件
while (right < s.length()) {
//形成公式:right++形成[) 区间
char rc = s.charAt(right);
right++;
//统计重复字数
map.put(rc, map.getOrDefault(rc, 0)+1);
//左窗口退出条件是不能有重复字符
while (map.get(rc) > 1) {
//获取到字符后 left 直接++,形成套路
char lc = s.charAt(left);
left++;
//收缩窗口,减去字符数量
map.put(lc, map.get(lc)-1);
}
//在这里更新答案,在 left 中更新答案会有单字符字符串为 0 的问题
maxLen = Math.max(maxLen, right - left);
}
return maxLen;
}
}
5. 最长回文子串
class Solution {
public String longestPalindrome(String s) {
String res = "";
int index = 0;
while (index < s.length()) {
//以 index 为中心,找奇数回文串
String s1 = palindrome(s, index, index);
//以 index 和 index+1为中心,找偶数回文串
String s2 = palindrome(s, index, index+1);
res = res.length() >= s1.length() ? res : s1;
res = res.length() >= s2.length() ? res : s2;
index++;
}
return res;
}
/**
* 左右指针,由中心向外扩散找回文
* 当 left==right时是奇数回文串
* 当 left!=right时是偶数回文串
* @param s
* @param left
* @param right
* @return
*/
public String palindrome(String s, int left, int right) {
//条件是一个半开区间,注意 left 和 right 的临界判断
while (left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)) {
left--;
right++;
}
//sub 函数也是截取的也是半开区间,right 不用调整
return s.substring(left+1, right);
}
}
19. 删除链表的倒数第 N 个结点
/**
* 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 {
//双指针,两个指针间间隔 n+1 个节点,则第二个指针遍历到尾的时候,第一个指针在要删除节点的前一个
public ListNode removeNthFromEnd(ListNode head, int n) {
//因为删除的可能是头结点,应该使用虚拟节点填充。并且使用虚拟节点刚好可以解决 n+1问题
ListNode dummy = new ListNode(-1);
dummy.next = head;
ListNode p1 = dummy;
ListNode p2 = head;
//p2 先走 n 步
for (int i = 0; i < n; i++) {
p2 = p2.next;
}
//p1 和 p2同时走
while (p2 != null) {
p1 = p1.next;
p2 = p2.next;
}
//此时 p1 在要删除节点的前一个
ListNode temp = p1.next;
p1.next = p1.next.next;
temp.next = null;
return dummy.next;
}
}
20. 有效的括号
class Solution {
//要求左括号必须以正确的顺序闭合,实际就是([)]这种是不行的,所以比较简单,用栈就行
public boolean isValid(String s) {
Stack<Character> left = new Stack<>();
for (char c : s.toCharArray()) {
if (c == '(' || c == '[' || c == '{') {
left.push(c);
} else {
if (!left.isEmpty() && leftOf(c) == left.peek()) {
left.pop();
} else {
//这里一定要返回否则,开头是右括号的就匹配不上了
return false;
}
}
}
return left.isEmpty();
}
char leftOf(char c) {
if (c == ')') return '(';
if (c == ']') return '[';
return '{';
}
}
21. 合并两个有序链表
/**
* 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 mergeTwoLists(ListNode list1, ListNode list2) {
//新建链表,把两个节点分别加入
ListNode dummy = new ListNode(-1);
ListNode p = dummy;
while (list1 != null && list2 != null) {
// 比较 p1 和 p2 两个指针,将值较小的的节点接到 p 指针
if (list1.val > list2.val) {
p.next = list2;
list2 = list2.next;
} else {
p.next = list1;
list1 = list1.next;
}
p = p.next;
}
if (list1 != null) {
p.next = list1;
}
if (list2 != null) {
p.next = list2;
}
return dummy.next;
}
}
23. 合并K个升序链表
/**
* 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.length == 0) {
return null;
}
//需要新建链表,使用虚拟节点
ListNode dummy = new ListNode(-1);
ListNode p = dummy;
//最小堆
PriorityQueue<ListNode> queue = new PriorityQueue<ListNode>((a,b) -> (a.val - b.val));
//这里使用最小堆真是帅,每个链表只用加入头结点就行,本身是升序链表,很好得利用了链表特性
for (ListNode head : lists) {
if (head != null) {
queue.add(head);
}
}
while (!queue.isEmpty()) {
//获取最小节点,接到结果链中
ListNode node = queue.poll();
p.next = node;
if (node.next != null) {
queue.add(node.next);
}
p = p.next;
}
return dummy.next;
}
}
25. K 个一组翻转链表
/**
* 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 reverseKGroup(ListNode head, int k) {
return recisionGroup(head,k);
}
/**
* 递归
* 类似翻转指定区间的链表:
* 将单个节点换成组的概念
* @param head
* @param k
* @return
*/
private ListNode recisionGroup(ListNode head,int k) {
if (head == null) {
return head;
}
ListNode start = head, end = head;
//出口:返回最后一组链表反转后的头节点
for (int i = 0; i < k; i++) {
if (end == null) return head;
end = end.next;
}
ListNode newHead = iterition(start,end);
//不能注入 end.next,iterition返回的是【a,b)
start.next = recisionGroup(end,k);
return newHead;
}
/**
* 迭代反转 【a,b) 节点间链表,返回的是 b 的前置节点
* @param head
* @param end
* @return
*/
private ListNode iterition(ListNode head,ListNode end) {
if (head == null) {
return head;
}
ListNode newHead = null;
while (head != end) {
ListNode temp = head.next;
head.next = newHead;
newHead = head;
head = temp;
}
return newHead;
}
/**
* 迭代法
* @param head
* @param k
* @return
*/
public ListNode iterator(ListNode head, int k) {
ListNode dummy = new ListNode(-1);
dummy.next = head;
//分为前中后三块,保留前尾,中头尾,后头
//记录反转的潜质
ListNode pre = dummy;
for (;head != null;) {
//标记需要翻转的头尾
ListNode tempHead = head,tempTail = head;
for (int i=1; i < k && head != null;i++,head = head.next) {
tempTail = head.next;
if (tempTail == null) {
return dummy.next;
}
}
//记录反转的后置
ListNode back = tempTail.next;
//反转前切割出需要翻转的部分
tempTail.next = null;
//反转
ListNode newHead = iterator(tempHead);
//反转后的头接后置
tempHead.next = back;
//反转的头接前置
pre.next = newHead;
//更新前置
pre = tempHead;
//更新 head
head = back;
}
return dummy.next;
}
/**
* 反转链表
* @param head
* @param tail
* @return
*/
private ListNode iterator(ListNode head) {
ListNode newHead = null;
while (head != null) {
ListNode temp = head.next;
head.next = newHead;
newHead = head;
head = temp;
}
return newHead;
}
}
26. 删除有序数组中的重复项
class Solution {
/**
* 数组是升序的,直接快慢指针就行
* 但是需要注意的是,原数组不变,所以快慢指针的运行方式是:
* fast增加一步如果相等,fast 继续加,直到不相等后,slow 加,把 fast 的值赋给 slow
* @param nums
* @return
*/
public int removeDuplicates(int[] nums) {
int slow = 0, fast = 0;
for (;fast < nums.length;) {
//注意覆盖方式
if (nums[slow] != nums[fast]) {
nums[++slow] = nums[fast];
}
fast++;
}
//slow 表示 index,所以实际值应该+1
return slow + 1;
}
}
27. 移除元素
class Solution {
/**
* 这个和原地重复删除元素类似,也是快慢指针,快指针找不等于 val,慢指针指向相等的准备覆盖
* @param nums
* @param val
* @return
*/
public int removeElement(int[] nums, int val) {
int slow = 0, fast = 0;
while (fast < nums.length) {
if (nums[fast] != val) {
//这里需要后++,这类题应该根据示例推导
nums[slow++] = nums[fast];
}
fast++;
}
return slow;
}
}
34. 在排序数组中查找元素的第一个和最后一个位置
class Solution {
/**
* 看到有序数组,就要想一下双指针、二分解法是能可行
* 实际遍历一次就行,但是有复杂度要求,所以用二分查找。
* @param nums
* @param target
* @return
*/
public int[] searchRange(int[] nums, int target) {
return new int[]{leftBound(nums,target), rightBound(nums,target)};
}
/**
* 二分查找,但是对于等于的判断,不进行值返回
* @param nums
* @param target
* @return
*/
public int leftBound(int[] nums, int target) {
int left = 0, right = nums.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] > target) {
right = mid - 1;
} else if (nums[mid] == target) {
right = mid - 1;
}
}
if (left >= nums.length || nums[left] != target) {
return -1;
}
return left;
}
public int rightBound(int[] nums, int target) {
int left = 0, right = nums.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] > target) {
right = mid - 1;
} else if (nums[mid] == target) {
left = mid + 1;
}
}
if (right < 0 || nums[right] != target) {
return -1;
}
return right;
}
}
76. 最小覆盖子串
class Solution {
//还是滑动窗口问题,与 3 的问题类似,不过需要返回子串
public String minWindow(String s, String t) {
//统计 t 中的字符数量
Map<Character, Integer> subMap = new HashMap<>();
for (int i = 0; i < t.length(); i++) {
char ch = t.charAt(i);
subMap.put(ch, subMap.getOrDefault(ch, 0)+1);
}
//定义左右窗口
int right = 0, left = 0;
//记录目标子串开始下标,最大长度,s 串中满足 t 中字符数量的字符个数
int start = 0, minLen = Integer.MAX_VALUE, vaild = 0;
//记录父串的字典
Map<Character, Integer> map = new HashMap<>();
//不变的右窗口退出条件
while (right < s.length()) {
//不变的右窗口移动
char rc = s.charAt(right);
right++;
//更新字典
map.put(rc, map.getOrDefault(rc, 0) + 1);
//如果字符个数相等,说明已经有一个字符数量满足了
if (map.get(rc).equals(subMap.get(rc))) {
vaild++;
}
//思考:左窗口什么时候更新?审题:包含 t 所有字母的最小子串,因此满足一个 t 的时候
while (vaild == subMap.size()) {
//思考:start 什么时候变,maxLen 什么时候变
//获取最小的串
if (right - left < minLen) {
start = left;
minLen = right - left;
}
char lc = s.charAt(left);
left++;
//如果该字符是 t 中的,说明移动到关键字符了,则需要将 vaild-1
//这里必须要进行等比,否则仅subMap.containsKty(lc)无法判定 vaild 有效
//map 中数量可能是大于 subMap,不做这个判定 map 减一个也同样满足vaild,则逻辑就会出错
if (map.get(lc).equals(subMap.get(lc))) {
vaild--;
}
map.put(lc, map.get(lc)-1);
}
}
return minLen == Integer.MAX_VALUE ? "" : s.substring(start, start+minLen);
}
}
86. 分隔链表
/**
* 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 {
/**
* 可以和合并升序链表一样,构造新链表,把符合的节点从 head 中剔除,最后再把 head 拼接到新链表尾部
* 这样实际就产生了两条链表,就使用两个虚拟节点,注意拆分节点,别把事情想复杂了
* @param head
* @param x
* @return
*/
public ListNode partition(ListNode head, int x) {
//存小于 x 的节点
ListNode dummy1 = new ListNode(-1);
ListNode p1 = dummy1;
//存大于等于 x 的节点
ListNode dummy2 = new ListNode(-1);
ListNode p2 = dummy2;
while (head != null) {
//拆分链表
if (head.val < x) {
p1.next = head;
p1 = p1.next;
} else {
p2.next = head;
p2 = p2.next;
}
//断开每个节点
ListNode temp = head.next;
head.next = null;
head = temp;
}
p1.next = dummy2.next;
return dummy1.next;
}
}
92. 反转链表 II
/**
* 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 reverseBetween(ListNode head, int left, int right) {
return iterator(head,left,right);
}
/**
* 递归
* 1,2,3,4,5 假如m=2,n=4,则对于递归我们不管其mn 的值,只要知道最后结果,
* 则:ListNode last = recursion(head.next, m-1, n-1)
* head.next = last;
* 这样递归主体就有了,剩下讨论终止条件,拆解问题规模,最小规模变成了m=1,因此终止条件变成了if (m==1)
* m==1 此时链表实际变成了反转前n 个节点
* @param head
* @param left
* @param right
* @return
*/
public ListNode recursion(ListNode head, int m, int n) {
if (m == 1) {
return recursionTopN(head, n);
}
ListNode last = recursion(head.next, m-1, n-1);
head.next = last;
return head;
}
/**
* 递归前 n 个节点
* 之前已经递归过反转链表,现在是指定前 n 个,需要记录下后驱节点,在最后将反转的部分的尾节点指向该结点
* @param head
* @param left
* @param right
* @return
*/
//记录后去
ListNode successor = null;
public ListNode recursionTopN(ListNode head, int n) {
if (n == 1) {
// 记录第 n + 1 个节点
successor = head.next;
return head;
}
ListNode last = recursionTopN(head.next, n-1);
head.next.next = head;
// 让反转之后的 head 节点和后面的节点连起来
head.next = successor;
return last;
}
/**
* 迭代
* @param head
* @param m
* @param n
* @return
*/
public ListNode iterator(ListNode head, int m, int n) {
if (head == null || m == n) {
return head;
}
// 拆分成前中后三个部分,1,2,3,4,5,m2n4,则遍历[2,4]部分将每个元素头插进前部分之后
// 需要记录三个节点,原始头,前部分的尾,斩开链接时的当前节点,当前节点的下一个节点
// m 可能为 1,因此 head 可能会变化,使用虚拟节点
// 以上思路肯定可行,写法很多,但面试时间不够,使用官方解法
ListNode dummy = new ListNode(-1);
//原始头
dummy.next = head;
//前部分的尾,这里不能给 null,必须要在 head 前一位,因为 head 是要反转的当前节点
ListNode frontTail = dummy;
//走完这里,就进入m-n 区间
for (int i = 1; i < m; i++) {
frontTail = head;
head = head.next;
}
//倒转一段链表,head-12345
//当前节点为 1,把 2 插入到head 后 1 前,继续走把 3 插到 head 后,就实现了链表反转
for (int i = m; i < n; i++) {
ListNode next = head.next;
//把 1 指向 3
head.next = next.next;
//把 2 指向 head 后
next.next = frontTail.next;
frontTail.next = next;
}
return dummy.next;
}
}
94. 二叉树的中序遍历
/**
* 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 List<Integer> inorderTraversal(TreeNode root) {
//中序:左根右
return deymicRescursion(root);
}
List<Integer> res = new ArrayList<>();
/**
* 遍历思维:一次遍历,借助外部变量
* @param root
*/
public void rescursion(TreeNode root) {
if (root == null) {
return;
}
rescursion(root.left);
res.add(root.val);
rescursion(root.right);
}
/**
* 分解思维:一棵二叉树的前序遍历结果 = 根节点 + 左子树的前序遍历结果 + 右子树的前序遍历结果。
* @param root
*/
public List<Integer> deymicRescursion(TreeNode root) {
if (root == null) {
return new ArrayList<>();
}
List<Integer> result = new ArrayList<>();
result.addAll(deymicRescursion(root.left));
result.add(root.val);
result.addAll(deymicRescursion(root.right));
return result;
}
}
100. 相同的树
/**
* 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) {
return dymicRecursion2(p, q);
}
/**
* 递归:问题拆解左子树相等 && 右子树相等,结束条件:任意一方为 null
* @param p
* @param q
* @return
*/
public boolean dymicRecursion(TreeNode p, TreeNode q) {
if (p == null || q == null) {
return p == null && q == null;
}
return p.val == q.val && dymicRecursion(p.left, q.left) && dymicRecursion(p.right, q.right);
}
/**
* 递归第一个版本,比较繁琐:问题拆解左子树相等 && 右子树相等,结束条件:任意一方为 null
* @param p
* @param q
* @return
*/
public boolean dymicRecursion2(TreeNode p, TreeNode q) {
if (p == null || q == null) {
return p == null && q == null;
}
boolean isSame = p.val == q.val;
if (!isSame) {
return isSame;
}
boolean left = dymicRecursion(p.left, q.left);
isSame = isSame && left;
if (!isSame) {
return isSame;
}
boolean right = dymicRecursion(p.right, q.right);
return isSame && right;
}
}
101. 对称二叉树
/**
* 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) {
if (root == null) {
return true;
}
return isSymmetricByIteration(root);
}
/**
* 动态规划,拆解思想
* 看着说是对称的树,但是仅进行一棵树的递归遍历无法对比上
* 因此应该把他看成两棵树,用来比较两棵树是否相同
* 前序:根左右;中序:左根右;后续:左右根
* 因为是对称,所以左右树传入顺序需要注意
* @param left 左子树 right 右子树
* @return
*/
public boolean isSymmetricBetweenTree(TreeNode left, TreeNode right) {
if (left == null || right == null) {
return left == null && right == null;
}
//左子树的左节点与有字数的右节点比较 左子树的右节点与右子树的左节点比较
return left.val == right.val
&& isSymmetricBetweenTree(left.left, right.right)
&& isSymmetricBetweenTree(left.right, right.left);
}
/**
* 迭代思路很简单,层序遍历,需要使用两个队列,一个队列搞不定,和递归一样,分成两棵子树,比较两棵子树的值,注意顺序
* 这里层序遍历不用 for 循环去从左到右打印顺序,只需要while 一直取值就好了
* @param root
* @return
*/
public boolean isSymmetricByIteration(TreeNode root) {
if (root == null) {
return true;
}
Queue<TreeNode> queueLeft = new LinkedList<>();
Queue<TreeNode> queueRight = new LinkedList<>();
//添加两棵子树
queueLeft.offer(root.left);
queueRight.offer(root.right);
//只用一层循环不断取值
while (!queueLeft.isEmpty() && !queueRight.isEmpty()) {
TreeNode left = queueLeft.poll();
TreeNode right = queueRight.poll();
//注意三个 if 条件,这样的安排更加合理优雅
if (left == null && right == null) {
continue;
}
if ((left == null && right != null) || (left != null && right == null)) {
return false;
}
if (left.val != right.val) {
return false;
}
//注意添加顺序
queueLeft.offer(left.left);
queueRight.offer(right.right);
queueLeft.offer(left.right);
queueRight.offer(right.left);
}
return true;
}
}
102. 二叉树的层序遍历
/**
* 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 {
/**
* 层序遍历就一个,队列
* @param root
* @return
*/
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> res = new ArrayList<>();
if (root == null) {
return res;
}
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
//while控制从上到下
while (!queue.isEmpty()) {
int qSize = queue.size();
List<Integer> levels = new ArrayList<>();
//for 控制从左到右
for (int i = 0; i < qSize; i++) {
TreeNode target = queue.poll();
levels.add(target.val);
//添加左节点,注意这里是 target不是 root
if (target.left != null) {
queue.offer(target.left);
}
//添加右节点
if (target.right != null) {
queue.offer(target.right);
}
}
res.add(levels);
}
return res;
}
}
104. 二叉树的最大深度
/**
* 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) {
return dymicRecursion(root);
}
int maxLen = 0;
int depth = 0;
/**
* 回溯方式:遍历整个二叉树,借助外部变量
* @param root
*/
public void recursion(TreeNode root) {
if (root == null) {
return;
}
//解释depth加减含义:
//比如这样一棵树:1234,123这两棵子树,不用考虑复杂情况,迭代会帮你处理复杂情况,只考虑最简单的就行
//这两棵树下,前置遍历先进入1234,depth=4,然后离开左子树递归返回了,depth逐渐减到 0,节点返回到 1 又开始遍历右子树深度
//所以 depth++是前置进入该结点的时候,depth--是后置离开该结点的时候
depth++;
recursion(root.left);
recursion(root.right);
maxLen = Math.max(maxLen, depth);
depth--;
}
/**
* 分解法,动态规划:当前节点最大深度,就是左子树或右子树最深的,递归条件是当到达叶子节点时,其节点深度为 0
* @param root
* @return
*/
public int dymicRecursion(TreeNode root) {
if (root == null) {
return 0;
}
//左子树深度
int left = dymicRecursion(root.left) + 1;
//右子树深度
int right = dymicRecursion(root.right) + 1;
//当前节点左右子树中最深的
return Math.max(left, right);
}
}
105. 从前序与中序遍历序列构造二叉树
/**
* 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) {
for (int i = 0; i < inorder.length; i++) {
//存储节点值在中序中的下标
valToIndex.put(inorder[i], i);
}
return buildTree(preorder,0, preorder.length - 1, inorder, 0, inorder.length - 1);
}
//优化:存储节点中下标,否则每次都需要遍历数组找 val 对应下标
Map<Integer, Integer> valToIndex = new HashMap<>();
/**
* 递归,前序确定根节点,中序确定左右子树
* 这个和最大树类似,前者是取最大节点;这个是根据前序取根节点
* @return
*/
public TreeNode buildTree(int[] preorder, int preStart, int preEnd,
int[] inorder, int inStart, int inEnd) {
//根据前序找根节点
if (preStart > preEnd) {
return null;
}
//确定根节点
int rootVal = preorder[preStart];
TreeNode root = new TreeNode(rootVal);
//根据根节点获取在中序的位置,就可以根据 inIndex 确定左右子树部分
int inIndex = valToIndex.get(rootVal);
//根据中序左右子树数量,根绝数量计算前序左右子树部分
int leftSize = inIndex - inStart;
root.left = buildTree(preorder, preStart + 1, preStart + leftSize, inorder, inStart, inIndex - 1);
root.right = buildTree(preorder, preStart + leftSize + 1, preEnd, inorder, inIndex + 1, inEnd);
return root;
}
}
106. 从中序与后序遍历序列构造二叉树
/**
* 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[] inorder, int[] postorder) {
for (int i = 0; i < inorder.length; i++) {
valToIndex.put(inorder[i], i);
}
return buildTree(inorder, 0, inorder.length - 1, postorder, 0, postorder.length - 1);
}
//优化:存储中序节点对应下标,否则需要每次遍历中序找到对应下标
Map<Integer, Integer> valToIndex = new HashMap<>();
public TreeNode buildTree(int[] inoder, int inStart, int inEnd,
int[] postorder, int postStart, int postEnd) {
//退出条件,这里使用后序确定的根节点,所以用它退出
if (postStart > postEnd) {
return null;
}
//构建节点:根据后序找到根节点
int rootVal = postorder[postEnd];
TreeNode root = new TreeNode(rootVal);
//根据中序下标,切分出左右子树
int inIndex = valToIndex.get(rootVal);
int leftSize = inIndex - inStart;
//这里需要注意 前序头节点在前面,前序是(preStart + 1, preStart + leftSize), (preStart + leftSize + 1, preEnd)
//后续是(postStart, postStart + leftSize - 1),(postStart + leftSize, postEnd - 1),两个都需要排除当前根节点,但是根节点所在位置不同
root.left = buildTree(inoder, inStart, inIndex - 1, postorder, postStart, postStart + leftSize - 1);
root.right = buildTree(inoder, inIndex + 1, inEnd, postorder, postStart + leftSize, postEnd - 1);
return root;
}
}
107. 二叉树的层序遍历 II
/**
* 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 {
/**
* labuladong的还是用队列,只是把结果原来添加到队尾变成了添加到队头,很巧妙,我都没想到
* 我的思路是用栈,细想是很麻烦,而且需要遍历两遍,脑子废了
* @param root
* @return
*/
public List<List<Integer>> levelOrderBottom(TreeNode root) {
LinkedList<List<Integer>> res = new LinkedList<>();
if (root == null) {
return res;
}
Queue<TreeNode> q = new LinkedList<>();
q.offer(root);
// while 循环控制从上向下一层层遍历
while (!q.isEmpty()) {
int sz = q.size();
// 记录这一层的节点值
List<Integer> level = new LinkedList<>();
// for 循环控制每一层从左向右遍历
for (int i = 0; i < sz; i++) {
TreeNode cur = q.poll();
level.add(cur.val);
if (cur.left != null)
q.offer(cur.left);
if (cur.right != null)
q.offer(cur.right);
}
// 把每一层添加到头部,就是自底向上的层序遍历。
res.addFirst(level);
}
return res;
}
}
111. 二叉树的最小深度
/**
* 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 minDepth(TreeNode root) {
if (root == null) {
return 0;
}
return minDepthByIteration(root);
}
/**
* labuladong 思路,使用层序遍历方法
* @param root
* @return
*/
public int minDepthByIteration(TreeNode root) {
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
//root本身算一层,初始化为 1
int depth = 1;
while (!queue.isEmpty()) {
int size = queue.size();
for (int i = 0; i < size;i++) {
TreeNode node = queue.poll();
//左右子树都为null,说明到了最短那一层的叶子结点
if (node.left == null && node.right == null) {
return depth;
}
if (node.left != null) {
queue.add(node.left);
}
if (node.right != null) {
queue.add(node.right);
}
}
depth++;
}
return depth;
}
/**
* 递归迭代树,借助外部变量,尝试用这种方法,失败。递归没法识别叶子结点,如果根左为null,则最小为 1,但实际这条线不算有效路径
* depth.stream().min(Integer::compare).get();
* @param root
*/
List<Integer> depth = new ArrayList<>();
public void traverse(TreeNode root, int level) {
if (root.left == null) {
depth.add(level);
return;
}
traverse(root.left, level + 1);
traverse(root.right, level + 1);
}
}
112. 路径总和
/**
* 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 hasPathSum(TreeNode root, int targetSum) {
return hasPathSumByIteration(root,targetSum);
}
/**
* 动态规划法:递归,目的是求根节点到叶子节点的路径,注意是叶子节点,仅仅到中途是不算的
* @param root
* @param targetSum
* @return
*/
public boolean hasPathSumByRecursion(TreeNode root, int targetSum) {
//避免叶子节点为空的情况,因为下面是用叶子节点判断的,这一步判断可以规避根节点为 null,并且可以避免在递归时root.left != null 的判定
if (root == null) {
return false;
}
//左右子节点都为 null,说明到达当前层的叶子节点
if (root.left == null && root.right == null) {
//到达当前层的时候,主要要减去 root.val,因为这里是在叶子节点判空,不是在叶子节点之后的进行判空的
return targetSum - root.val == 0;
}
return hasPathSum(root.left, targetSum - root.val) || hasPathSum(root.right, targetSum - root.val);
}
/**
* 迭代法,层序遍历,使用两个队列,一个装节点,一个装值,值和节点顺序对应起来比较
* @param root
* @param targetSum
* @return
*/
public boolean hasPathSumByIteration(TreeNode root, int targetSum) {
if (root == null) {
return false;
}
Queue<TreeNode> queueNode = new LinkedList<>();
Queue<Integer> queueValue = new LinkedList<>();
queueNode.offer(root);
queueValue.offer(root.val);
while (!queueNode.isEmpty()) {
TreeNode node = queueNode.poll();
Integer value = queueValue.poll();
//左右节点都为空,到达叶子节点,比较之前累加的值和目标值是否相等
//这里注意queueValue存的就是累加的值,在上一层就已经加过了,不用在加一次
if (node.left == null && node.right == null && value == targetSum) {
return true;
}
if (node.left != null) {
queueNode.offer(node.left);
queueValue.offer(node.left.val+value);
}
if (node.right != null) {
queueNode.offer(node.right);
queueValue.offer(node.right.val+value);
}
}
return false;
}
}
113. 路径总和 II
/**
* 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 List<List<Integer>> pathSum(TreeNode root, int targetSum) {
return dfsByDynamic(root,targetSum,new ArrayList<>(),new ArrayList<>());
}
/**
* 递归方式,动态规划,不借助外部变量
* @param root
* @param targetSum
* @param ans
* @param path
* @return
*/
public List<List<Integer>> dfsByDynamic(TreeNode root, int targetSum, List<List<Integer>> ans, List<Integer> path) {
if (root == null) {
return new ArrayList<>();
}
path.add(root.val);
if (root.left == null && root.right == null && targetSum == root.val) {
ans.add(path);
}
//这一步要把原来 path 的内容传到下一层级节点
dfsByDynamic(root.left, targetSum - root.val, ans, new ArrayList<>(path));
dfsByDynamic(root.right, targetSum - root.val, ans, new ArrayList<>(path));
return ans;
}
List<List<Integer>> ans = new ArrayList<>();
List<Integer> path = new ArrayList<>();
/**
* 深度优先搜索,递归,借助外部变量
* 其实这个和 "路径总和I" 是一样的,但是自己没想出来
*/
public void dfs(TreeNode root,int targetSum) {
if (root == null) {
return;
}
//这一步必须放在 if 上层,因为 if 需要存储最终结果
path.add(root.val);
//到达叶子节点,加入该结果(这里一定要注意,是targetSum == root,因为是到达的叶子节点,而不是叶子节点的下一层)
if (root.left == null && root.right == null && targetSum == root.val) {
ans.add(new ArrayList<>(path));
}
dfs(root.left, targetSum - root.val);
dfs(root.right, targetSum - root.val);
//为什么这里要移除? 实际上因为是深度遍历,所以肯定是前序遍历到底,到达叶子节点后返回到上一层,
//如果该叶子节点满足加入该路径;如果不满足,就退回到上一层,到上一层的另一个叶子节点,所以退出时把前一个叶子节点删除
//我之前就是没想到这个,只想到这么多路径怎么存,实际上 dfs 是一条一条遍历的
path.remove(path.size() - 1);
}
}
114. 二叉树展开为链表
/**
* 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 void flatten(TreeNode root) {
if (root == null) return;
//递归
flatten(root.left);
flatten(root.right);
//后序遍历,拉平左右子树
TreeNode left = root.left;
TreeNode right = root.right;
//左子树置空
root.left = null;
//左子树赋给右子树
root.right = left;
//将原先的右子树拼接到当前右子树末端
TreeNode p = root;
while (p.right != null) {
p = p.right;
}
p.right = right;
}
}
144. 二叉树的前序遍历
/**
* 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 List<Integer> preorderTraversal(TreeNode root) {
//前序:根左右
return deymicRecursion(root);
}
//遍历思维:借助外部变量
List<Integer> res = new ArrayList<>();
/**
* 递归
* @param root
* @return
*/
public void recursion(TreeNode root) {
if (root == null) {
return;
}
res.add(root.val);
recursion(root.left);
recursion(root.right);
}
/**
* 分解问题:动态规划方式
* @param root
* @return
*/
public List<Integer> deymicRecursion(TreeNode root) {
if (root == null) {
return new ArrayList<>();
}
List<Integer> result = new ArrayList<>();
result.add(root.val);
result.addAll(deymicRecursion(root.left));
result.addAll(deymicRecursion(root.right));
return result;
}
}
141. 环形链表
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
/**
* 快慢指针:快指针走完一圈时,慢指针刚好走一半;快指针走完第二圈时,慢指针走完第一圈
* 因此如果存在环,则快指针走完第二圈时刚好和慢指针相遇
* 也可以用 set 遍历链表如果重复就是环了
* @param head
* @return
*/
public boolean hasCycle(ListNode head) {
ListNode fast = head, slow = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
if (slow == fast) {
return true;
}
}
return false;
}
}
142. 环形链表 II
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
/**
* 公式推导:
* 假设这是一个链表,带环的是一个循环结构;假设起点到第一个节点的距离为L,第一个节点到相遇的位置为X,整个环的大小为C
* 那么slow一次走一步,fast一次走两步
* 有slow和fast从起点到相遇的位置为
* slow:L+X
* fast:L+X+NC(因为不知道环的大小,所以不知道slow到相遇的位置时,fast转了几圈)
* 有这样的一个公式
* 2(L+X)=L+X+NC
* 化简L=NC-X
*/
public class Solution {
/**
* 这个公式也就是:当快慢相遇时,此时slow指向 head,和 fast 一起走L步,则相遇点就是换起点
* 也可以用 set 遍历链表如果重复就是环了
* @param head
* @return
*/
public ListNode detectCycle(ListNode head) {
ListNode slow = head;
ListNode fast = head;
boolean res = false;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
if (slow == fast) {
res = true;
break;
}
}
if (!res) {
return null;
}
//此时slow指向 head,和 fast 一起走L步
slow = head;
while (slow != fast) {
slow = slow.next;
fast = fast.next;
}
return slow;
}
}
145. 二叉树的后序遍历
/**
* 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 List<Integer> postorderTraversal(TreeNode root) {
//后序:左右根
return dymicRecursion(root);
}
List<Integer> res = new ArrayList<>();
/**
* 回溯思维:借助外部变量,将遍历到的每个节点加入结果集
* @param root
*/
public void recursion(TreeNode root) {
if (root == null) {
return;
}
recursion(root.left);
recursion(root.right);
res.add(root.val);
}
/**
* 分解思维思维:后序结果集 = 左子树结果集 + 右子树结果集 + 根节点结果集
* @param root
*/
public List<Integer> dymicRecursion(TreeNode root) {
if (root == null) {
return new ArrayList<>();
}
List<Integer> result = new ArrayList<>();
result.addAll(dymicRecursion(root.left));
result.addAll(dymicRecursion(root.right));
result.add(root.val);
return result;
}
}
160. 相交链表
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
/**
* 三种方法:
* 1、先计算两条链表长度,让两条链表到达尾部距离相同,也就是从相同长度走
* 2、利用 set,把一条链表节点加入 set,再遍历另一条链表对比
* 3、写法最简单,当一条链表走到尾时,接入另一条链表的头;另一条链表也这样遍历,则两条链表长度相同,并且能够到达相交点
* @param headA
* @param headB
* @return
*/
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
return getIntersectionNodeByFixed(headA,headB);
}
public ListNode getIntersectionNodeByFixed(ListNode headA, ListNode headB) {
ListNode p1 = headA, p2 = headB;
while (p1 != p2) {
p1 = p1 == null ? headB : p1.next;
p2 = p2 == null ? headA : p2.next;
}
return p1;
}
/**
* 借助 hashSet,遍历其中一个链表
* @param headA
* @param headB
* @return
*/
public ListNode getIntersectionNodeBySet(ListNode headA, ListNode headB) {
Set<ListNode> set = new HashSet<>();
while (headA != null) {
set.add(headA);
headA = headA.next;
}
while (headB != null) {
if (set.contains(headB)) {
return headB;
}
headB = headB.next;
}
return null;
}
}
167. 两数之和 II - 输入有序数组
class Solution {
/**
* 升降序就考虑双指针了,就这还中等题
* @param numbers
* @param target
* @return
*/
public int[] twoSum(int[] numbers, int target) {
int left = 0, right = numbers.length - 1;
while (left < right) {
int val = numbers[right] + numbers[left];
if (val == target) {
return new int[]{left + 1, right + 1};
} else if (val > target) {
right = right - 1;
} else if (val < target) {
left = left + 1;
}
}
return new int[]{-1, -1};
}
}
206. 反转链表
/**
* 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 reverseList(ListNode head) {
return iterator(head);
}
/**
* 递归
* @param head
* @return
*/
private ListNode recursion(ListNode head) {
if (head == null || head.next == null) {
return head;
}
//返回的是最后一个节点
ListNode last = recursion(head.next);
// 1-->2-->3-->4 ==> 1.next.next => 2.next = 1
head.next.next = head;
head.next = null;
return last;
}
/**
* 迭代
* @param head
* @return
*/
private ListNode iterator(ListNode head) {
ListNode newHead = null;
//涉及到斩断连接需要
while (head != null) {
//保存原来链表
ListNode temp = head.next;
//斩断当前节点链接,指向新链表
head.next = newHead;
//更新新链表头结点
newHead = head;
//迭代旧链表
head = temp;
}
return newHead;
}
}
226. 翻转二叉树
/**
* 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 invertTree(TreeNode root) {
return invertTreeByDynamic(root);
}
/**
* 遍历思路:迭代
* @param root
*/
public void invertTreeByRecursion(TreeNode root) {
if (root == null) {
return;
}
TreeNode temp = root.left;
root.left = root.right;
root.right = temp;
//这里不用担心原左右子树变了,子节点怎么办。从图里来看,其实子树的左右节点交换后恰好是满足条件的
invertTreeByRecursion(root.left);
invertTreeByRecursion(root.right);
}
/**
* 分解问题思路,动态规划
* @param root
* @return
*/
public TreeNode invertTreeByDynamic(TreeNode root) {
if (root == null) {
return null;
}
TreeNode left = invertTreeByDynamic(root.left);
TreeNode right = invertTreeByDynamic(root.right);
root.left = right;
root.right = left;
return root;
}
}
234. 回文链表
/**
* 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 boolean isPalindrome(ListNode head) {
left = head;
return traverse(head);
}
/**
* 使用栈,首先计算数量,判断奇偶,再遍历遍历进行比较
* @param head
* @return
*/
public boolean isPalindromeByStack(ListNode head) {
Stack<ListNode> stack = new Stack<ListNode>();
ListNode p = head;
while (p != null) {
stack.add(p);
p = p.next;
}
//无论奇偶都只用比较其中一半
int compare = stack.size()/2;
for (int i = 0; i < compare; i++) {
ListNode last = stack.pop();
if (head.val != last.val) {
return false;
}
head = head.next;
}
return true;
}
// 左侧指针
ListNode left;
/**
* 递归,因为二叉树是链表的一种形式,所以链表也可以前后序遍历,所以如果在后序打印节点值,实际上就是链表的倒序,有了倒序和正序比较就可以了
* 这种方式实际也是借助栈,但是是借助递归的栈
* @param right
* @return
*/
boolean traverse(ListNode right) {
if (right == null) return true;
boolean res = traverse(right.next);
// 后序遍历代码
res = res && (right.val == left.val);
left = left.next;
return res;
}
}
257. 二叉树的所有路径
/**
* 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 List<String> binaryTreePaths(TreeNode root) {
List<List<Integer>> ans = getTreePath(root, new ArrayList<>(), new ArrayList<>());
return ans.stream().map(v -> v.stream().map(String::valueOf).collect(Collectors.joining("->"))).collect(Collectors.toList());
}
public List<List<Integer>> getTreePath(TreeNode root, List<Integer> path, List<List<Integer>> ans) {
if (root == null) {
return new ArrayList<>();
}
path.add(root.val);
if (root.left == null && root.right == null) {
ans.add(new ArrayList<>(path));
}
getTreePath(root.left, new ArrayList<>(path), ans);
getTreePath(root.right, new ArrayList<>(path), ans);
path.remove(path.size() - 1);
return ans;
}
//代码更简洁
public List<String> getTreePath(TreeNode root, String path, List<String> ans) {
if (root == null) {
return new ArrayList<>();
}
path = path + root.val;
if (root.left == null && root.right == null) {
ans.add(path);
}
getTreePath(root.left, path + "->", ans);
getTreePath(root.right, path + "->", ans);
return ans;
}
}
283. 移动零
class Solution {
/**
* 因为要保持相对顺序,不能使用左右指针交换,使用快慢指针
* 快指针找非0的,覆盖到慢指针,最后快指针结束后,把慢指针后的值赋0
* @param nums
*/
public void moveZeroes(int[] nums) {
int slow = 0, fast = 0;
while (fast < nums.length) {
//覆盖 0 值,把非 0 的覆盖到 0
if (nums[fast] != 0) {
nums[slow++] = nums[fast];
}
fast++;
}
//后面的全部赋0
while (slow < nums.length) {
nums[slow++] = 0;
}
}
}
303. 区域和检索 - 数组不可变
class NumArray {
int[] preSum;
/**
* 该场景实际是一道生产中的应用题,以空间换时间,即数组不可变下,快速计算和
* 因为数组,就采用前缀和。新生成一个数组,该数组存储每个节点存储原数组的累加值
* @param nums
*/
public NumArray(int[] nums) {
this.preSum = new int[nums.length + 1];
//从 1 开始遍历
for (int i = 1; i < preSum.length; i++) {
//prevalue 0
//nums 123 prevalue[1] = 0+1
preSum[i] = preSum[i - 1] + nums[i - 1];
}
}
public int sumRange(int left, int right) {
return preSum[right + 1] - preSum[left];
}
}
344. 反转字符串
class Solution {
/**
* 一想就是双指针了
* @param s
*/
public void reverseString(char[] s) {
int left = 0, right = s.length - 1;
while (left < right) {
char temp = s[right];
s[right] = s[left];
s[left] = temp;
left++;
right--;
}
}
}
438. 找到字符串中所有字母异位词
class Solution {
//滑动窗口
public List<Integer> findAnagrams(String s, String p) {
Map<Character, Integer> subMap = new HashMap<>();
for (int i = 0; i < p.length(); i++) {
char ch = p.charAt(i);
subMap.put(ch, subMap.getOrDefault(ch, 0) + 1);
}
//定义左右窗口
int right = 0, left = 0;
//定义窗口
Map<Character, Integer> map = new HashMap<>();
//记录 s 中满足的 p 字符数量
int vaild = 0;
//记录起始索引
List<Integer> res = new ArrayList<>();
while (right < s.length()) {
char rc = s.charAt(right);
right++;
map.put(rc, map.getOrDefault(rc, 0) + 1);
//字符数量相同
if (map.get(rc).equals(subMap.get(rc))) {
vaild++;
}
//左窗口什么时候更新?要求 p 的异位子串,两个要求:len 相同 && 字符数量一致
//在右窗口更新时已满足字符数量一致,此时 len 如果不相同更新左窗口,找到 len 相同时刻,则说明找到了一个子串
while (right - left >= p.length()) {
if (right - left == p.length() && vaild == subMap.size()) {
res.add(left);
}
char lc = s.charAt(left);
left++;
if (map.get(lc).equals(subMap.get(lc))) {
vaild--;
}
//这儿的数据更新一定要放在 vaild 更新之后
map.put(lc, map.getOrDefault(lc, 0) - 1);
}
}
return res;
}
}
//思路不是很清晰,左窗口更新条件判定条件错误,导致代码冗余,有参考意义
class Solution {
//滑动窗口
public List<Integer> findAnagrams(String s, String p) {
Map<Character, Integer> subMap = new HashMap<>();
for (int i = 0; i < p.length(); i++) {
char ch = p.charAt(i);
subMap.put(ch, subMap.getOrDefault(ch, 0) + 1);
}
//定义左右窗口
int right = 0, left = 0;
//定义窗口
Map<Character, Integer> map = new HashMap<>();
//记录 s 中满足的 p 字符数量
int vaild = 0;
//记录起始索引
List<Integer> res = new ArrayList<>();
while (right < s.length()) {
char rc = s.charAt(right);
right++;
map.put(rc, map.getOrDefault(rc, 0) + 1);
//字符数量相同
if (map.get(rc).equals(subMap.get(rc))) {
vaild++;
}
//左窗口什么时候更新?右窗口不断搜索,当窗口中数量满足 p 中字符的时候,说明此时窗口中可能有这样的一个子串了
//此时左窗口在subMap.size==vaild的时候,更新缩小范围,这个条件标注说明此时窗口中可能有满足的子串,否则退出继续在右窗口中找
while (vaild == subMap.size()) {
//什么时候才能判定找到了符合条件的子串:直到 map 和 subMap 的 size 相同 && vaild 不变的时候(简化为 subMap.size==vaild)
//这里无法做判断,可能出现虽然 size 相等,但是map--a4,subMap--a1,虽然 size 和 vaild 相等,但是不是异位词,除非加上 vaule 数量的比较
if (map.size() == subMap.size() && compareTwoMap(subMap, map)) {
res.add(left);
}
char lc = s.charAt(left);
left++;
//数据更新,移动左窗口需要更新 vaild,才能退出
//必须在相等的时候更新 vaild,因为 map 中的字符数量可能大于 subMap:map--c4,sub--c--2,这样即使 map 减少一个vaild 其实也还满足
if (map.get(lc).equals(subMap.get(lc))) {
vaild--;
}
map.put(lc, map.getOrDefault(lc, 0) - 1);
if (map.get(lc) < 1) {
map.remove(lc);
}
}
}
return res;
}
private boolean compareTwoMap(Map<Character, Integer> map1, Map<Character, Integer> map2) {
for (Map.Entry<Character, Integer> entry : map1.entrySet()) {
Character key = entry.getKey();
if (!entry.getValue().equals(map2.get(key))) {
return false;
}
}
return true;
}
}
509. 斐波那契数
class Solution {
/**
* 从斐波那契数列看动态规划
*
* @param n
* @return
*/
public int fib(int n) {
return fibWithDp(n);
}
/**
* 单纯的递归解法,自顶向下
* @param n
* @return
*/
public int fibOnlyRecursion(int n) {
if (n == 0) {
return 0;
}
if (n == 1) {
return 1;
}
return fib(n - 1) + fib(n - 2);
}
/**
* 带 dp 的解法,自底向上
* @param n
* @return
*/
public int fibWithDp(int n) {
if (n == 0 || n == 1) {
return n;
}
int dp_0 = 0, dp_1 = 1;
for (int i = 2; i <= n; i++) {
int dp = dp_0 + dp_1;
dp_0 = dp_1;
dp_1 = dp;
}
return dp_1;
}
}
567. 字符串的排列
class Solution {
//滑动窗口,438异位子串的降阶版
public boolean checkInclusion(String s1, String s2) {
Map<Character, Integer> subMap = new HashMap<>();
for (int i = 0; i < s1.length(); i++) {
char ch = s1.charAt(i);
subMap.put(ch, subMap.getOrDefault(ch, 0)+1);
}
int right = 0, left = 0;
Map<Character, Integer> map = new HashMap<>();
//记录满足 s2 的字符数
int vaild = 0;
while (right < s2.length()) {
char rc = s2.charAt(right);
right++;
map.put(rc, map.getOrDefault(rc, 0)+1);
if (map.get(rc).equals(subMap.get(rc))) {
vaild++;
}
//什么时候更新左窗口?包含排列即:s2 中子串和 s1 len 相等 && 字符必须相同
//右窗口中已经满足 vaild 相等,因此左窗口在 len 大于等于 s1 时进行更新,一直更新到窗口中不满足 s1 条件的位置为止
//这里必须要用>=,等于时也还可能满足s1 情况,必须更新到不满足s1情况为止
while (right - left >= s1.length()) {
if (right - left == s1.length() && vaild == subMap.size()) {
return true;
}
char lc = s2.charAt(left);
left++;
if (map.get(lc).equals(subMap.get(lc))) {
vaild--;
}
map.put(lc, map.getOrDefault(lc, 0)-1);
}
}
return false;
}
}
654. 最大二叉树
/**
* 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 constructMaximumBinaryTree(int[] nums) {
return buildTree(nums, 0, nums.length - 1);
}
public TreeNode buildTree(int[] nums, int start, int end) {
// nums 为空的时候,上层节点的左右子树达到叶子节点,直接返回 null,则上层 root.left = null
// if (nums == null || nums.length == 0) {
// return null;
// }
//不能用 nums 判断,这里用的不是截断,使用 start 和 end 控制范围,所以用它进行判断
//这里必须是大于,==的话说明还有一个值
if (start > end) {
return null;
}
//找最大值和其下标
int maxVal = Integer.MIN_VALUE;
int index = Integer.MAX_VALUE;
//查找 start 到 end 间的最大值
for (int i = start; i <= end; i++) {
if (nums[i] > maxVal) {
maxVal = nums[i];
index = i;
}
}
TreeNode root = new TreeNode(maxVal);
//构建左子树
root.left = buildTree(nums, start, index - 1);
root.right = buildTree(nums, index + 1, end);
return root;
}
}
876. 链表的中间结点
/**
* 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 {
/**
* 快慢指针,快指针每次走两步,慢指针每次走一步,特点:当快指针到达链表尾时,慢指针刚好在链表中间
*也可以用最朴实的办法,计算数量求中心点
* @param head
* @return
*/
public ListNode middleNode(ListNode head) {
ListNode slow = head;
ListNode fast = head.next;
while (fast != null) {
slow = slow.next;
fast = fast.next;
if (fast != null) {
fast = fast.next;
}
}
return slow;
}
//更优雅
public ListNode middleNodeMoreGentle(ListNode head) {
ListNode slow = head;
ListNode fast = head;
//这里做了两个判断,否则可能有空的问题
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
}
return slow;
}
}
100000. 冒泡排序
/**
* 思想:每遍历一轮把最大的交换到最后
* @param arr
* @param comp
*/
public static void bubbleSort (Integer[] arr, Comparator<Integer> comp) {
//注意边界值,以及 j<len-i-1
for (int i = 0; i < arr.length - 1; i++) {
for (int j = 0; j < arr.length - i - 1; j++) {
if (comp.compare(arr[j], arr[j+1]) > 0) {
Integer temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
}
100001. 插入排序
/**
* 思想:认为前面是有序的,从第 2 个数开始,把它往前面有序的序列里插
* @param arr
* @param comp
*/
public static void insertSort(Integer[] arr, Comparator<Integer> comp) {
for (int i = 1; i < arr.length; i++) {
//保存待插入数据
Integer inserting = arr[i];
int j = i - 1;
//找到第一个比 待插入数据小 的数据
while (j >= 0 && comp.compare(inserting, arr[j]) >= 0) {
//将比 待插入数据大 的数据向后移
arr[j+1] = arr[j];
j--;
}
//插入第一个小的数据之后,注意这里是 j+1
arr[j+1] = inserting;
}
}
100002. 选择排序
/**
* 每一趟找出最小的下标,交换下和第一个交换下标
* @param arr
*/
public static void selectSort(Integer[] arr) {
for (int i = 0; i < arr.length; i++) {
int minIndex = i;
// 把当前遍历的数和后面所有的数进行比较,并记录下最小的数的下标
for (int j = i + 1; j < arr.length; j++) {
if (arr[j] < arr[minIndex]) {
// 记录最小的数的下标
minIndex = j;
}
}
// 如果最小的数和当前遍历的下标不一致,则交换
if (i != minIndex) {
int temp = arr[i];
arr[i] = arr[minIndex];
arr[minIndex] = temp;
}
}
}
100003. 希尔排序
/**
* 1. 希尔排序把序列按下标的一定增量(步长)分组,对每组分别使用插入排序。
* 2. 随着增量(步长)减少,一直到一,算法结束,整个序列变为有序。
* @param arr
*/
public static void shellSort(Integer[] arr) {
for (int gap = arr.length / 2; gap > 0; gap /= 2) {
for (int i = gap; i < arr.length; i++) {
for (int j = i - gap; j >= 0; j -= gap) {
// 如果当前元素大于加上步长后的那个元素,说明交换
if (arr[j] > arr[j + gap]) {
int temp = arr[j];
arr[j] = arr[j + gap];
arr[j + gap] = temp;
}
}
}
}
}
100004. 快速排序
public static void quickSort(Integer[] arr) {
quickSort(arr, 0, arr.length-1);
}
/**
* 思想:选定一个基准值,分别从头尾遍历比大小,放在基准值两边,并重复这个过程
* 1.先把待排序的数组拆成左右两个区间,左边都比中间的基准数小,右边都比基准数大。
* 2.递归,接着左右两边各自再做同样的操作,完成后再拆分再继续,一直到各区间只有一个数为止。
* @param arr
* @param start
* @param end
*/
public static void quickSort(Integer[] arr, int start, int end) {
//一定要注意这个判断
if (start >= end) {
return;
}
//基准值
int standard = arr[start];
int low = start, high = end;
//找一次
while (low < high) {
//从后向前找到比基准值小的数据,注意是<=
while (low < high && standard <= arr[high]) {
high--;
}
//将比基准值小的放在基准值之前
arr[low] = arr[high];
//从前向后找到比基准值大的数据,注意是<=
while (low < high && standard >= arr[low]) {
low++;
}
//将比基准值大的数据放在基准值之后
arr[high] = arr[low];
}
//当high==low一次遍历结束,将基准值放在中间,则基准值左边是小于它的,右边是大于它的
arr[low] = standard;
//排基准值右边的数
quickSort(arr, start, low - 1);
//排基准值左边的数
quickSort(arr, high + 1, end);
}
100005. 归并排序
public static void mergeSort(int[] arr) {
mergeSort(arr,0,arr.length-1);
}
/**
* 思想:把两个有序的数组分别遍历按序放到同一个数组
* @param arr 待排序的数组
* @param left 起始坐标
* @param right 终点坐标
*/
public static void mergeSort(int[] arr ,int left ,int right) {
//如果起始坐标不小于终点坐标,那么代表已经拆分为一个元素一个序列的程度了,故直接返回
if ( left >= right ) {
return;
}
//声明一个变量记录中间位置的坐标,即(起始坐标 + 终点坐标)/ 2 ;
int middle = (left + right) >> 1;
//拆分序列,拆分方式:递归本方法两次,一次传入左边区间的序列,
//即起始坐标到中间位置的坐标范围,一次传入右边区间的序列,即中间位置坐标 + 1 到终点坐标的范围;
mergeSort(arr, left , middle);
mergeSort(arr, middle+1,right);
sort(arr,left,middle,right);
}
/**
* @param arr 待排序数组
* @param left 起始坐标
* @param middle 中间位置坐标
* @param right 终点坐标
*/
public static void sort(int[] arr, int left, int middle, int right){
//建立一个辅助数组,长度为待排序元素的长度
int[] temp = new int[right-left+1];
//存储起始坐标,即左边序列的起始坐标;
int i = left;
//存储中间位置坐标 + 1 ,即右边序列的起始坐标;
int j = middle + 1;
//表示辅助数组的起始坐标;
int t = 0;
//将左边区间的元素依次和右边区间元素进行对比;
//对比出来将较小的元素优先放入辅助数组直到某一个序列坐标超出;
while ( i <= middle && j <= right ){
temp[t++] = arr[i] < arr[j] ? arr[i++] : arr[j++];
}
//再对两个序列进行判断,如果未超过区间的最大坐标表示未遍历完,
//则将未参与遍历的元素依次放入辅助数组;( 值得注意的是,
//每一个进入判断的两个序列都是相对有序的,因为递归的性质是由下至上的
//,也就是说两个区间要么是单个元素,要么就是一个已经排序过的区间 )
while ( i <= middle ) {
temp[t++] = arr[i++];
}
while ( j <= right ) {
temp[t++] = arr[j++];
}
//最后将辅助数组拷贝到待排序数组的相应位置即可
System.arraycopy(temp, 0, arr, left, temp.length);
}