part2 – https://blog.csdn.net/qq_41080854/article/details/129224785
part3 – https://blog.csdn.net/qq_41080854/article/details/129239305
面试必刷101-牛客
- 1、链表反转
- 2、链表内指定区间反转
- 3. 链表中的节点每k个一组翻转
- 4、合并两个排序的链表
- 5、合并k个已排序的链表
- 6、判断链表中是否有环
- 7、链表中环的入口结点
- 8、链表中倒数最后k个结点
- 9、删除链表的倒数第n个节点
- 10、两个链表的第一个公共结点
- 11、链表相加(二)
- 12、两数相加
- 13、字符串相加
- 14、二进制求和
- 15、36进制相加
- 16、单链表的排序
- 17、判断一个链表是否为回文结构
- 18、判断是否为回文字符串
- 19、最长回文字符串
- 20、链表的奇偶重排
- 21、删除有序链表中重复的元素-I
- 22、删除有序链表中重复的元素-II
- 23、二分查找-I
- 24、二维数组中的查找
- 25、寻找峰值
- 24、数组中的逆序对
- 25、旋转数组的最小数字
- 26、比较版本号
- 27、二叉树的前序遍历
- 28、二叉树的中序遍历
- 29、二叉树的后序遍历
- 30、求二叉树的层序遍历
- 31、按之字形顺序打印二叉树
- 32、二叉树的最大深度
- 33、二叉树中和为某一值的路径(一)
- 34、二叉搜索树与双向链表
- 35、对称的二叉树
- 36、合并二叉树
- 37、二叉树的镜像
- 38、判断是不是二叉搜索树
- 39、判断是不是完全二叉树
- 40、判断是不是平衡二叉树
- 41、二叉搜索树的最近公共祖先
- 42、在二叉树中找到两个节点的最近公共祖先
- 43、序列化二叉树
- 44、重建二叉树
- 45、输出二叉树的右视图
1、链表反转
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
public class Solution {
public ListNode ReverseList(ListNode head) {
ListNode last = null;
ListNode next = null;
while(head != null){
next = head.next;
head.next = last;//反转
last = head;//更新装态
head = next;
}
return last;
}
}
2、链表内指定区间反转
import java.util.*;
public class Solution {
public ListNode reverseBetween (ListNode head, int m, int n) {
// write code here
ListNode dummy = new ListNode(-1);
dummy.next = head;
ListNode left = dummy;
ListNode right = dummy;
//走到m的前一个位置
for (int i = 0; i < m - 1; i++) {
left = left.next;
}
right = left;
ListNode start = left.next;
//right要走到n的位置上
for (int i = 0; i < n - m + 1; i++) {
right = right.next;
}
ListNode next = right.next;
//将n处的next置空
right.next = null;
left.next = reverse(start);
//修正start
start.next = next;
return dummy.next;
}
public ListNode reverse(ListNode head) {
ListNode last = null;
ListNode next = null;
while (head != null) {
next = head.next;
head.next = last;
last = head;
head = next;
}
return last;
}
}
3. 链表中的节点每k个一组翻转
public class Solution {
/**
*
* @param head ListNode类
* @param k int整型
* @return ListNode类
*/
public ListNode reverseKGroup (ListNode head, int k) {
// write code here
ListNode dummy = new ListNode(-1);
dummy.next = head;
ListNode left = dummy;
ListNode right = dummy;
while (right != null) {
for (int i = 0; i < k ; i++) {
right = right.next;
if (right == null) return dummy.next;
}
ListNode start = left.next;
ListNode next = right.next;
right.next = null;
//指向反转后的结点
left.next = myrevser(start);
//修正指针错误
start.next = next;
//重新指向下一个k分组的前一个节点
left = start;
right = left;
}
return dummy.next;
}
public ListNode myrevser(ListNode head) {
ListNode last = null;
ListNode next = null;
while (head != null) {
next = head.next;
head.next = last;
last = head;
head = next;
}
return last;
}
}
4、合并两个排序的链表
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
public class Solution {
public ListNode Merge(ListNode list1, ListNode list2) {
if (list1 == null) return list2;
if (list2 == null) return list1;
ListNode help = new ListNode(Integer.MIN_VALUE);
ListNode cur = help;
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;
}
cur.next = list1 == null ? list2 : list1;
return help.next;
}
}
5、合并k个已排序的链表
import java.util.*;
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public ListNode mergeKLists(ArrayList<ListNode> lists) {
return merger(lists, 0, lists.size() - 1);
}
public ListNode merger(ArrayList<ListNode> lists, int l, int r) {
if (l == r) return lists.get(l);
if (l > r) return null;
int mid = (l + r) >> 1;
ListNode leftmerge = merger(lists, l, mid);
ListNode rightmerge = merger(lists, mid + 1, r);
return mergerTowList(leftmerge, rightmerge);
}
public ListNode mergerTowList(ListNode left,ListNode right){
if (left == null) return right;
if (right == null) return left;
ListNode help = new ListNode(Integer.MIN_VALUE);
ListNode cur = help;
while(left != null && right != null){
if(left.val <= right.val){
cur.next = left;
left = left.next;
}else{
cur.next = right;
right = right.next;
}
cur = cur.next;
}
cur.next = left == null ? right : left;
return help.next;
}
}
6、判断链表中是否有环
public class Solution {
public boolean hasCycle(ListNode head) {
if(head == null) return false;
ListNode fast = head;
ListNode slow = head;
while(fast != null && fast.next != null){
fast = fast.next.next;
slow = slow.next;
if(slow == fast) return true;
}
return false;
}
}
使用两个指针, fast \textit{fast} fast 与 slow \textit{slow} slow。它们起始都位于链表的头部。随后, slow \textit{slow} slow指针每次向后移动一个位置,而 fast \textit{fast} fast指针向后移动两个位置。如果链表中存在环,则 fast \textit{fast} fast指针最终将再次与 slow \textit{slow} slow指针在环中相遇。
7、链表中环的入口结点
使用两个指针, fast \textit{fast} fast 与 slow \textit{slow} slow。它们起始都位于链表的头部。随后, slow \textit{slow} slow指针每次向后移动一个位置,而 fast \textit{fast} fast指针向后移动两个位置。如果链表中存在环,则 fast \textit{fast} fast指针最终将再次与 slow \textit{slow} slow指针在环中相遇。当发现 fast \textit{fast} fast 与 slow \textit{slow} slow相遇时,将 fast \textit{fast} fast指向链表头部;随后,它和 slow \textit{slow} slow每次向后移动一个位置。最终,它们会在入环点相遇。
public class Solution {
public ListNode EntryNodeOfLoop(ListNode pHead) {
if(pHead == null) return null;
ListNode fast = pHead;
ListNode slow = pHead;
while(fast != null && fast.next != null){
fast = fast.next.next;
slow = slow.next;
if(slow == fast){
fast = pHead;
while(fast != slow){
fast = fast.next;
slow = slow.next;
}
return fast;
}
}
return null;
}
}
8、链表中倒数最后k个结点
- 使用双指针则可以不用统计链表长度。
public ListNode FindKthToTail (ListNode pHead, int k) {
// write code here
ListNode fast = pHead;
ListNode slow = pHead;
for (int i = 0; i < k; i++) {
if(fast == null) return null;
fast = fast.next;
}
while (fast != null) {
fast = fast.next;
slow = slow.next;
}
return slow;
}
9、删除链表的倒数第n个节点
public ListNode removeNthFromEnd (ListNode head, int n) {
// write code here
ListNode dummy = new ListNode(-1);
dummy.next = head;
ListNode fast = head;
ListNode slow = dummy; //将slow位置提前
for (int i = 0; i < n ; i++) {//快慢指针,走到第n个节点的前一个
fast = fast.next;
}
while (fast != null) {
fast = fast.next;
slow = slow.next;
}
slow.next = slow.next.next;
return dummy.next;
}
10、两个链表的第一个公共结点
public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
int length = 0; // pHead1 - pHead2长度之差
if (pHead1 == null || pHead2 == null) return null;
ListNode cur1 = pHead1;
ListNode cur2 = pHead2;
while (cur1 != null) {
cur1 = cur1.next;
length++;
}
while (cur2 != null) {
cur2 = cur2.next;
length--;
}
// 尾端节点不一致判断没有节点
if (cur1 != cur2) return null;
// cur1表示长的那个链表
cur1 = length > 0 ? pHead1 : pHead2;
cur2 = cur1 == pHead1 ? pHead2 : pHead1;
length = Math.abs(length);
// 长链表走length步
for (int i = 0; i < length; i++) {
cur1 = cur1.next;
}
// 两个链表同时走直到相遇
while (cur1 != cur2) {
cur1 = cur1.next;
cur2 = cur2.next;
}
return cur1;
}
11、链表相加(二)
本题的主要难点在于链表中数位的顺序与我们做加法的顺序是相反的,为了逆序处理所有数位,我们可以使用栈:把所有数字压入栈中,再依次取出相加。计算过程中需要注意进位的情况。
public ListNode addInList (ListNode head1, ListNode head2) {
// write code here
Stack<Integer> s1 = new Stack<>();
Stack<Integer> s2 = new Stack<>();
//将链表中数据放入栈中
while (head1 != null) {
s1.push(head1.val);
head1 = head1.next;
}
while (head2 != null) {
s2.push(head2.val);
head2 = head2.next;
}
ListNode res = null; //返回的结果
int carry = 0; //进位
// 栈不为空或者存在进位时
while (!s1.isEmpty() || !s2.isEmpty() || carry != 0) {
int val1 = s1.isEmpty() ? 0 : s1.pop();
int val2 = s2.isEmpty() ? 0 : s2.pop();
int sum = val1 + val2 + carry;
carry = sum / 10; // 更新进位
int cur = sum % 10; // 个位数字
ListNode curNode = new ListNode(cur);
//更新结果并连线
curNode.next = res;
res = curNode;
}
return res;
}
12、两数相加
本题低位在前高位在后,可以直接从头开始相加
public ListNode addTwoNumbers(ListNode head1, ListNode head2) {
// write code here
ListNode res = new ListNode(0); //返回的结果
ListNode help = res;
int carry = 0; //进位
// 栈不为空或者存在进位时
while (head1!=null || head2!=null || carry != 0) {
int val1 = head1==null ? 0 : head1.val;
int val2 = head2==null ? 0 : head2.val;
int sum = val1 + val2 + carry;
carry = sum / 10; // 更新进位
int cur = sum % 10; // 个位数字
help.next = new ListNode(cur);
help = help.next;
if(head1!=null) head1 = head1.next;
if(head2!=null) head2 = head2.next;
}
return res.next;
}
13、字符串相加
public String addStrings(String num1, String num2) {
StringBuilder res = new StringBuilder("");
int num1Length = num1.length() - 1;
int num2Length = num2.length() - 1;
int carry = 0;//进位
while (num1Length >= 0 || num2Length >= 0 || carry != 0) {
int n1 = num1Length >= 0 ? num1.charAt(num1Length) - '0' : 0;
int n2 = num2Length >= 0 ? num2.charAt(num2Length) - '0' : 0;
int sum = n1 + n2 + carry;
carry = sum / 10; //更新进位
int cur = sum % 10; //个位上数字
res.append(cur);
num1Length--;
num2Length--;
}
return res.reverse().toString();
}
14、二进制求和
class Solution {
public String addBinary(String a, String b) {
StringBuilder ans = new StringBuilder();
int alength = a.length() - 1;
int blength = b.length() - 1;
int carry = 0;//进位
while(alength >= 0 || blength >= 0 || carry != 0){
int n1 = alength >= 0? a.charAt(alength) - '0':0;
int n2 = blength >= 0? b.charAt(blength) - '0':0;
int sum = n1 + n2 + carry;
int cur = sum % 2;
carry = sum / 2; //更新进位
ans.append(cur);
alength--;
blength--;
}
return ans.reverse().toString();
}
}
15、36进制相加
题目
实现一个 36 进制的加法 0-9 a-z。
示例
输入:[“abbbb”,“1”],输出:“abbbc”
String add36Strings(String num1, String num2) {
int carry = 0;
int i = num1.length() - 1, j = num2.length() - 1;
StringBuilder sb = new StringBuilder();
while (i >= 0 || j >= 0 || carry != 0) {
int x = i >= 0 ? getInt(num1.charAt(i)) : 0;
int y = j >= 0 ? getInt(num2.charAt(j)) : 0;
int sum = x + y + carry; //本次结果
sb.append(getChar(sum % 36));
carry = sum / 36; //进位
i--;
j--;
}
//翻转
return sb.reverse().toString();
}
private int getInt(char i) {
if (i >= '0' && i <= '9') {
return i - '0';
} else {
return i - 'a' + 10;
}
}
private char getChar(int i) {
if (i <= 9) {
return (char) (i + '0');
} else {
return (char) (i - 10 + 'a');
}
}
16、单链表的排序
对于链表最适合的是归并排序
public class Solution {
/**
*
* @param head ListNode类 the head node
* @return ListNode类
*/
public ListNode sortInList (ListNode head) {
// write code here
// 对于链表最适合的是归并排序
if(head == null) return null;
return sortPartList(head);
}
public ListNode sortPartList(ListNode head) {
if (head.next == null) return head; //链表只有一个节点
// 获取 mid -- 中点为slow
ListNode fast = head, slow = head;
ListNode pre = null;//中点的前一个节点
while (fast != null && fast.next != null) {
pre = slow;
slow = slow.next;
fast = fast.next.next;
}
pre.next = null; //断开拆成两个链表
ListNode leftNode = sortPartList(head);
ListNode rightNode = sortPartList(slow);
return merge(leftNode, rightNode);
}
public ListNode merge(ListNode left, ListNode right) {
ListNode dummy = new ListNode(0);
ListNode help = dummy;
while (left != null && right != null) {
if (left.val <= right.val) {
help.next = left;
left = left.next;
} else {
help.next = right;
right = right.next;
}
help = help.next;
}
help.next = left == null ? right : left;
return dummy.next;
}
}
快速方法
public ListNode sortInList (ListNode head) {
// write code here
//小根堆
Queue<ListNode> queue = new PriorityQueue<>((o1, o2) -> o1.val - o2.val);
ListNode help = new ListNode(0);
ListNode temp = help;
while(head != null){
queue.add(head);
head = head.next;
}
while(!queue.isEmpty()){
ListNode node = queue.poll();
node.next = null;//将节点next置空
temp.next = node;
temp = temp.next;//更新
}
return help.next;
}
17、判断一个链表是否为回文结构
利用快慢指针找到中点,将后半截反序,两个指针从两边遍历到中点,逐个比较
当慢指针走到中点时,将其下一个指针赋值为null,同时将后面的指针指向中点,然后遍历,返回true/false,然后在将后面的指针改回去
public boolean isPail (ListNode head) {
// write code here
ListNode fast = head, slow = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
}
// 中点即为slow
slow = reverse(slow);
fast = head;
while (slow != null) {
if (slow.val != fast.val) return false;
slow = slow.next;
fast = fast.next;
}
return true;
}
public ListNode reverse(ListNode head) {
ListNode last = null;
ListNode next = null;
while (head != null) {
next = head.next;
head.next = last;//reverse
last = head;
head = next;
}
return last;
}
18、判断是否为回文字符串
import java.util.*;
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
* @param str string字符串 待判断的字符串
* @return bool布尔型
*/
public boolean judge (String str) {
// write code here
int l = 0;
int r = str.length() - 1;
while (l < r) {
if (str.charAt(l) != str.charAt(r)) return false;
l++;
r--;
}
return true;
}
}
19、最长回文字符串
- step 1:遍历字符串每个字符。
- step 2:以每次遍历到的字符为中心(分奇数长度和偶数长度两种情况),不断向两边扩展。
- step 3:如果两边都是相同的就是回文,不断扩大到最大长度即是以这个字符(或偶数两个)为中心的最长回文子串。
- step 4:我们比较完每个字符为中心的最长回文子串,取最大值即可。
import java.util.*;
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param A string字符串
* @return int整型
*/
public int getLongestPalindrome (String A) {
// write code here
int res = 1;
for (int i = 0; i < A.length() - 1; i++) {
//分为奇数和偶数两种情况
res = Math.max(res, Math.max(helper(A, i, i), helper(A, i, i + 1)));
}
return res;
}
public int helper(String A, int begin, int end) {
//每个中心点开始扩展
while (begin >= 0 && end < A.length() && A.charAt(begin) == A.charAt(end)) {
begin--;
end++;
}
//返回长度
return end - begin - 1;
}
}
20、链表的奇偶重排
先一个左正蹬,把奇数节点串一块儿,再一个右鞭腿,把偶数节点串一起,然后啪,很快啊,把两个连成一条链表,可以说是训练有素,有bear来了
public ListNode oddEvenList (ListNode head) {
// write code here
if(head == null) return head;
ListNode odd = head;//奇数
ListNode even = head.next, evenHead = even; //偶数
while(even != null && even.next != null){
odd.next = even.next;
odd = odd.next;
even.next = odd.next;
even = even.next;
}
odd.next = evenHead;
return head;//奇数头是head
}
21、删除有序链表中重复的元素-I
import java.util.*;
public class Solution {
public ListNode deleteDuplicates (ListNode head) {
//一次遍历
//空链表
if(head == null) return null;
//遍历指针
ListNode cur = head;
//指针当前和下一位不为空
while(cur != null && cur.next != null){
//如果当前与下一位相等则忽略下一位
if(cur.val == cur.next.val)
cur.next = cur.next.next;
//否则指针正常遍历
else
cur = cur.next;
}
return head;
}
}
22、删除有序链表中重复的元素-II
删除重复元素,只要重复就全部给删除(一个也不留)
import java.util.*;
public class Solution {
public ListNode deleteDuplicates (ListNode head) {
//空链表
if(head == null) return null;
ListNode dummy = new ListNode(0);
//在链表前加一个表头
dummy.next = head;
ListNode helper = dummy;
while(helper.next != null && helper.next.next != null){
//遇到相邻两个节点值相同
if(helper.next.val == helper.next.next.val){
int temp = helper.next.val;
//将所有相同的都跳过
while (helper.next != null && helper.next.val == temp)
helper.next = helper.next.next;//忽略下一位
}
else
helper = helper.next;
}
//返回时去掉表头
return dummy.next;
}
}
23、二分查找-I
public int search (int[] nums, int target) {
// write code here
int left = 0, right = nums.length - 1 ;
while (left <= right) {
int mid = left + (right - left) / 2;
int num = nums[mid];
if (num == target) return mid;
else if (num > target) right = mid - 1;
else left = mid + 1;
}
return -1;
}
循环结束条件总结
情况一
如果搜索区间[left, right]中一定有目标值索引,那么循环截止条件是while(left < right)
情况二
如果搜索区间[left, right]中不一定有目标值索引,那么循环截止条件是while(left <= right);(一般用于搜索区间内是否有某个值)
24、二维数组中的查找
首先看四个角,左上与右下必定为最小值与最大值,而左下与右上就有规律了:左下元素大于它上方的元素,小于它右方的元素,右上元素与之相反。既然左下角元素有这么一种规律,相当于将要查找的部分分成了一个大区间和小区间,每次与左下角元素比较,我们就知道目标值应该在哪部分中,于是可以利用分治思维来做。
具体做法:
- step 1:首先获取矩阵的两个边长,判断特殊情况。
- step 2:首先以左下角为起点,若是它小于目标元素,则往右移动去找大的,若是他大于目标元素,则往上移动去找小的。
- step 3:若是移动到了矩阵边界也没找到,说明矩阵中不存在目标值。
public class Solution {
public boolean Find(int target, int [][] array) {
int r = array.length - 1;
int c = 0;
while(r >= 0 && c < array[0].length){
int cmp = array[r][c];
if(cmp == target) return true;
else if(cmp > target) r--;
else c++;
}
return false;
}
}
25、寻找峰值
public class Solution {
public int findPeakElement (int[] nums) {
// write code here
int left = 0;
int right = nums.length - 1;
int mid;
while (left < right) {
// 找到中间值
mid = (left + right) >> 1;
// 只要当前中间值 大于它的下一位,说明当前处于下坡状态,那么往数组的左边遍历,就可以找到峰值
if(nums[mid] > nums[mid + 1])
// 因为mid 比 它下一位大,所以当前mid有可能是山峰,这里向左遍历的时候需要把mid包含在内
right = mid;
else
// 否则当前中间值小于等于它的下一位时,说明山峰处于上坡状态,向右查找就可以找到峰值
// 因为mid小于它的下一位,所以这里将left赋值为mid+1,从大的那个数开始向右查找
left = mid + 1;
}
// 此时left 和 right相等时 上面的while退出
// 最终返回 left 或 right都可以
return right;
}
}
24、数组中的逆序对
public class Solution {
public int InversePairs(int [] array) {
//归并算法
//计数条件是左边大于右边
if (array == null || array.length < 2) return 0;
return (int)process(array, 0, array.length - 1) % 1000000007;
}
public long process(int [] array, int left, int right) {
if (left >= right) return 0;
int mid = left + (right - left) / 2;
long leftnum = process(array, left, mid);//左边排好序
long rightnum = process(array, mid + 1, right);//右边排好序
long mergernum = merge(array, left, mid,
right); //利用外排序将左右两边排好序
return leftnum + rightnum + mergernum;
}
public long merge(int [] array, int left, int mid, int right) {
int[] help = new int[right - left + 1];
int helpIndex = 0;//外排索引
int count = 0;//逆序对数量
int leftHead = left;//左边头索引
int rightHead = mid + 1;//右边头索引
while (leftHead <= mid && rightHead <= right) {
if (array[leftHead] <= array[rightHead]) {
help[helpIndex++] = array[leftHead++];
} else {
help[helpIndex++] = array[rightHead++];
count = count + mid - leftHead + 1;
}
}
while (leftHead <= mid) {
help[helpIndex++] = array[leftHead++];
}
while (rightHead <= right) {
help[helpIndex++] = array[rightHead++];
}
//覆盖回去
for (int i = 0; i < help.length; i++) {
array[left + i] = help[i];
}
return count % 1000000007;
}
}
25、旋转数组的最小数字
思路:
旋转数组将原本有序的数组分成了两部分有序的数组,因为在原始有序数组中,最小的元素一定是在首位,旋转后无序的点就是最小的数字。我们可以将旋转前的前半段命名为A,旋转后的前半段命名为B,旋转数组即将AB变成了BA,我们想知道最小的元素到底在哪里。
因为A部分和B部分都是各自有序的,所以我们还是想用分治来试试,每次比较中间值,确认目标值(最小元素)所在的区间。
具体做法:
- step 1:双指针指向旋转后数组的首尾,作为区间端点。
- step 2:若是区间中点值大于区间右界值,则最小的数字一定在中点右边。
- step 3:若是区间中点值等于区间右界值,则是不容易分辨最小数字在哪半个区间,比如[1,1,1,0,1],应该逐个缩减右界。
- step 4:若是区间中点值小于区间右界值,则最小的数字一定在中点左边。
- step 5:通过调整区间最后即可锁定最小值所在。
public int minNumberInRotateArray(int [] array) {
int left = 0, right = array.length - 1 ;
while (left < right) {
int mid = left + (right - left) / 2;
//区间中点值大于区间右界值,则最小的数字一定在中点右边。
//array[right] 是右边界
if (array[mid] > array[right]) left = mid + 1;
//区间中点值小于区间右界值,则最小的数字一定在中点左边。
else if (array[mid] < array[right]) right = mid;
else right--;//无法判断在哪一边 区间中点值等于区间右界值,则是不容易分辨最小数字在哪半个区间,比如[1,1,1,0,1],应该逐个缩减右界。
}
return array[left];
}
26、比较版本号
具体做法:
- step 1:利用两个指针表示字符串的下标,分别遍历两个字符串。
- step 2:每次截取点之前的数字字符组成数字,即在遇到一个点之前,直接取数字,加在前面数字乘10的后面。(因为int会溢出,这里采用long记录数字)
- step 3:然后比较两个数字大小,根据大小关系返回1或者-1,如果全部比较完都无法比较出大小关系,则返回0.
public int compare (String version1, String version2) {
// write code here
int len1 = version1.length(), len2 = version2.length();
int n1 = 0, n2 = 0;
while (n1 < len1 || n2 < len2) {
int num1 = 0, num2 = 0; //每个点间的数字
while (n1 < len1 && version1.charAt(n1) != '.') {
num1 = num1 * 10 + version1.charAt(n1++) - '0';
}
while (n2 < len2 && version2.charAt(n2) != '.') {
num2 = num2 * 10 + version2.charAt(n2++) - '0';
}
//判断每个点间的数字大小
if (num1 > num2) return 1;
else if (num1 < num2) return -1;
n1++;
n2++;//跳过点
}
return 0;
}
27、二叉树的前序遍历
//递归方式
public int[] preorderTraversal (TreeNode root) {
// write code here
List<Integer> list = new ArrayList<>();
preorder(root, list);
int[] res = new int[list.size()];
for (int i = 0; i < list.size(); i++) {
res[i] = list.get(i);
}
return res;
}
public void preorder(TreeNode node, List<Integer> list) {
if (node == null) return;
list.add(node.val);
preorder(node.left, list);
preorder(node.right, list);
}
//非递归方式
public int[] preorderTraversal (TreeNode root) {
//添加遍历结果的数组
List<Integer> list = new ArrayList();
Stack<TreeNode> s = new Stack<TreeNode>();
//空树返回空数组
if(root == null)
return new int[0];
//根节点先进栈
s.push(root);
while(!s.isEmpty()){
//每次栈顶就是访问的元素
TreeNode node = s.pop();
list.add(node.val);
//如果右边还有右子节点进栈
if(node.right != null)
s.push(node.right);
//如果左边还有左子节点进栈
if(node.left != null)
s.push(node.left);
}
//返回的结果
int[] res = new int[list.size()];
for(int i = 0; i < list.size(); i++)
res[i] = list.get(i);
return res;
}
28、二叉树的中序遍历
public int[] inorderTraversal (TreeNode root) {
// write code here
// 递归方式
List<Integer> list = new ArrayList<>();
inorder(root, list);
int[] res = new int[list.size()];
for (int i = 0; i < list.size(); i++) {
res[i] = list.get(i);
}
return res;
}
public void inorder(TreeNode root, List<Integer> list) {
if (root == null) return;
inorder(root.left, list);
list.add(root.val);
inorder(root.right, list);
}
public int[] inorderTraversal (TreeNode root) {
//非递归方式
//每棵子树,整棵树左边界依次进栈,依次弹出,处理,对右树循环
//添加遍历结果的数组
List<Integer> list = new ArrayList();
Stack<TreeNode> s = new Stack<TreeNode>();
//空树返回空数组
if(root == null)
return new int[0];
//当树节点不为空或栈中有节点时
while(root != null || !s.isEmpty()){
//每次找到最左节点
while(root != null){
s.push(root);
root = root.left;
}
//访问该节点
TreeNode node = s.pop();
list.add(node.val);
//进入右节点
root = node.right;
}
//返回的结果
int[] res = new int[list.size()];
for(int i = 0; i < list.size(); i++)
res[i] = list.get(i);
return res;
}
29、二叉树的后序遍历
public int[] postorderTraversal (TreeNode root) {
// write code here
List<Integer> list = new ArrayList<>();
postorder(root, list);
int[] res = new int[list.size()];
for (int i = 0; i < list.size(); i++) {
res[i] = list.get(i);
}
return res;
}
public void postorder(TreeNode root, List<Integer> list) {
if (root == null) return;
postorder(root.left, list);
postorder(root.right, list);
list.add(root.val);
}
//非递归,头入栈,放入另一个栈,先左后右
public int[] postorderTraversal (TreeNode root) {
// write code here
List<Integer> list = new ArrayList<>();
Stack<TreeNode> stack1 = new Stack<>();
Stack<TreeNode> stack2 = new Stack<>();
stack1.push(root);
if (root == null) return new int[0];
while (!stack1.isEmpty()) {
TreeNode node = stack1.pop();
stack2.push(node);
if (node.left != null) {
stack1.push(node.left);
}
if (node.right != null) {
stack1.push(node.right);
}
}
while (!stack2.isEmpty()) {
list.add(stack2.pop().val);
}
int[] res = new int[list.size()];
for (int i = 0; i < list.size(); i++) {
res[i] = list.get(i);
}
return res;
}
30、求二叉树的层序遍历
public ArrayList<ArrayList<Integer>> levelOrder (TreeNode root) {
// write code here
ArrayList<ArrayList<Integer>> array = new ArrayList<>();
if (root == null) return array;
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
ArrayList<Integer> temp = new ArrayList<Integer>();
int n = queue.size();//该层数量
while (n-- > 0) {
TreeNode node = queue.poll();
temp.add(node.val);
if(node.left!=null) queue.offer(node.left);
if(node.right!=null) queue.offer(node.right);
}
array.add(temp);
}
return array;
}
31、按之字形顺序打印二叉树
public ArrayList<ArrayList<Integer> > Print(TreeNode pRoot) {
ArrayList<ArrayList<Integer>> array = new ArrayList<>();
if (pRoot == null) return array;
Queue<TreeNode> queue = new LinkedList<>();
boolean flag = true;
queue.add(pRoot);
while (!queue.isEmpty()) {
ArrayList<Integer> temp = new ArrayList<Integer>();
int n = queue.size();//该层数量
while (n-- > 0) {
TreeNode node = queue.poll();
temp.add(node.val);
if (node.left != null) queue.add(node.left);
if (node.right != null) queue.add(node.right);
}
//奇数行反转,偶数行不反转
flag = !flag;
if (flag)
Collections.reverse(temp);
array.add(temp);
}
return array;
}
32、二叉树的最大深度
public int maxDepth (TreeNode root) {
// write code here
// 递归终止
if (root == null) {
return 0;
}
// dfs,先递归左子结点,再递归右子结点,最后求出每一子树的深度的最大值
return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1;
}
public int maxDepth (TreeNode root) {
// write code here
if (root == null) return 0;
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
int res = 0;
while (!queue.isEmpty()) {
int n = queue.size();//该层数量
while (n-- > 0) {
TreeNode node = queue.poll();
if (node.left != null) queue.offer(node.left);
if (node.right != null) queue.offer(node.right);
}
res++;
}
return res;
}
33、二叉树中和为某一值的路径(一)
遍历的方法我们可以选取二叉树常用的递归前序遍历,因为每次进入一个子节点,更新sum值以后,相当于对子树查找有没有等于新目标值的路径,因此这就是子问题,递归的三段式为:
- 终止条件: 每当遇到节点为空,意味着过了叶子节点,返回。每当检查到某个节点没有子节点,它就是叶子节点,此时sum减去叶子节点值刚好为0,说明找到了路径。
- 返回值: 将子问题中是否有符合新目标值的路径层层往上返回。
- 本级任务: 每一级需要检查是否到了叶子节点,如果没有则递归地进入子节点,同时更新sum值减掉本层的节点值。
具体做法:
- step 1:每次检查遍历到的节点是否为空节点,空节点就没有路径。
- step 2:再检查遍历到是否为叶子节点,且当前sum值等于节点值,说明可以刚好找到。
- step 3:检查左右子节点是否可以有完成路径的,如果任意一条路径可以都返回true,因此这里选用两个子节点递归的或。
public class Solution {
public boolean hasPathSum (TreeNode root, int sum) {
// write code here
if (root == null) return false;
return dfs(root, sum);
}
public boolean dfs(TreeNode node, int target) {
if (node == null) return false;// 目标路径不存在,开始回溯
target -= node.val; // 更新目标值
// 当前节点为叶子节点并且目标路径存在时,返回 true
if (node.left == null && node.right == null && target == 0) return true;
return dfs(node.left, target) || dfs(node.right, target);
}
}
34、二叉搜索树与双向链表
//递归
public class Solution {
private TreeNode head = null;//起始位置
private TreeNode pre = null;//不断更新的位置
public TreeNode Convert(TreeNode pRootOfTree) {
if (pRootOfTree == null) return null;
//中序遍历即为有序
Convert(pRootOfTree.left);
if (head == null) {
head = pRootOfTree;//最左端
pre = head;
} else {
pre.right = pRootOfTree;//右指针指向后继
pRootOfTree.left = pre;//左指针指向前继
pre = pRootOfTree; //更新位置
}
Convert(pRootOfTree.right);
return head;
}
}
//非递归
public class Solution {
public TreeNode Convert(TreeNode pRootOfTree) {
if (pRootOfTree == null)
return null;
//使用非递归方式
Stack<TreeNode> s = new Stack<>();
TreeNode head = null;
TreeNode pre = null;
boolean flag = true;
while (pRootOfTree != null || !s.isEmpty()) {
//直到没有左节点
while (pRootOfTree != null) {
s.push(pRootOfTree);
pRootOfTree = pRootOfTree.left;//更新最左边
}
TreeNode node = s.pop();//弹出
//处理
if (flag) {
//第一次最左边
head = node;
pre = head;
flag = false;
} else {
pre.right = node;//右指针指向后继
node.left = pre;//左指针指向前继
pre = node;//更新
}
pRootOfTree = node.right;//进入右边
}
return head;
}
}
35、对称的二叉树
遍历方式依据前序递归可以使用递归:
- 终止条件: 当进入子问题的两个节点都为空,说明都到了叶子节点,且是同步的,因此结束本次子问题,返回true;当进入子问题的两个节点只有一个为空,或是元素值不相等,说明这里的对称不匹配,同样结束本次子问题,返回false。
- 返回值: 每一级将子问题是否匹配的结果往上传递。
- 本级任务: 每个子问题,需要按照上述思路,“根左右”走左边的时候“根右左”走右边,“根左右”走右边的时候“根右左”走左边,一起进入子问题,需要两边都是匹配才能对称。
具体做法:
- step 1:两种方向的前序遍历,同步过程中的当前两个节点,同为空,属于对称的范畴。
- step 2:当前两个节点只有一个为空或者节点值不相等,已经不是对称的二叉树了。
- step 3:第一个节点的左子树与第二个节点的右子树同步递归对比,第一个节点的右子树与第二个节点的左子树同步递归比较。
public class Solution {
boolean isSymmetrical(TreeNode pRoot) {
if(pRoot == null) return true;
return recursion(pRoot.left, pRoot.right);
}
private boolean recursion(TreeNode left, TreeNode right) {
if (left == null && right == null) return true;//可以两个都为空
if (left == null || right == null || left.val != right.val) return false;//只有一个为空或者节点值不同,必定不对称
return recursion(left.left, right.right) && recursion(left.right, right.left);//每层对应的节点进入递归比较
}
}
//非递归
public class Solution {
boolean isSymmetrical(TreeNode pRoot) {
if(pRoot == null) return true;
Queue<TreeNode> q1 = new LinkedList<>();//左边子树
Queue<TreeNode> q2 = new LinkedList<>();//右边子树
q1.offer(pRoot.left);
q2.offer(pRoot.right);
while(!q1.isEmpty()&&!q2.isEmpty()){
TreeNode n1 = q1.poll();//leftNode
TreeNode n2 = q2.poll();//rightNode
//判断
if(n1 == null && n2 == null) continue;
if(n1 == null || n2 == null || n1.val != n2.val) return false;
q1.offer(n1.left);
q2.offer(n2.right);
q1.offer(n1.right);
q2.offer(n2.left);
}
return true;
}
}
36、合并二叉树
public TreeNode mergeTrees (TreeNode t1, TreeNode t2) {
// write code here
if (t1 == null) return t2;
if (t2 == null) return t1;
TreeNode node = new TreeNode(t1.val + t2.val);
node.left = mergeTrees(t1.left, t2.left);
node.right = mergeTrees(t1.right, t2.right);
return node;
}
37、二叉树的镜像
public TreeNode Mirror (TreeNode pRoot) {
// write code here
// 先序遍历,从顶向下交换
if(pRoot == null) return null;
//父问题 交换两个子节点的值。
TreeNode temp = pRoot.left;
pRoot.left = pRoot.right;
pRoot.right = temp;
Mirror(pRoot.left);//左子树交换
Mirror(pRoot.right);//右子树交换
return pRoot;
}
38、判断是不是二叉搜索树
class Solution {
long pre = Long.MIN_VALUE; //前一个左子树的值
public boolean isValidBST(TreeNode root) {
if(root == null) return true;
if(!isValidBST(root.left)) return false;
if(pre >= root.val) return false;
pre = root.val;
return isValidBST(root.right);
}
}
39、判断是不是完全二叉树
public class Solution {
public boolean isCompleteTree (TreeNode root) {
// write code here
// BFS
if(root == null) return true;
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
boolean isComplete = true;
while(!queue.isEmpty()){
TreeNode node = queue.poll();
if(node == null){//第一次遇到null
isComplete = false;
continue;
}
//遇到null后不应该再遇到非空节点了
if(!isComplete) return false;
queue.offer(node.left);
queue.offer(node.right);
}
return true;
}
}
40、判断是不是平衡二叉树
public class Solution {
public boolean IsBalanced_Solution(TreeNode root) {
if (root == null) return true;
//父问题:左右两个子树的高度差的绝对值不超过1
int left = deep(root.left);
int right = deep(root.right);
if (Math.abs(left - right) > 1) return false;
//子问题:左右两个子树都是一棵平衡二叉树。
return IsBalanced_Solution(root.left) && IsBalanced_Solution(root.right);
}
private int deep(TreeNode node) {
if (node == null) return 0;
//子问题:左右子树高度
int left = deep(node.left);
int right = deep(node.right);
//父问题:左右子树的最大高度
return Math.max(left,right) + 1;
}
}
41、二叉搜索树的最近公共祖先
利用二叉搜索树的性质:对于某一个节点若是p与q都小于等于这个这个节点值,说明p、q都在这个节点的左子树,而最近的公共祖先也一定在这个节点的左子树;若是p与q都大于等于这个节点,说明p、q都在这个节点的右子树,而最近的公共祖先也一定在这个节点的右子树。
具体做法:
- step 1:首先检查空节点,空树没有公共祖先。
- step 2:对于某个节点,比较与p、q的大小,若p、q在该节点两边说明这就是最近公共祖先。
- step 3:如果p、q都在该节点的左边,则递归进入左子树。
- step 4:如果p、q都在该节点的右边,则递归进入右子树。
public int lowestCommonAncestor (TreeNode root, int p, int q) {
if (root.val<p && root.val<q) {
return lowestCommonAncestor(root.right,p,q);
}
if (root.val>p && root.val>q) {
return lowestCommonAncestor(root.left,p,q);
}
return root.val;
}
42、在二叉树中找到两个节点的最近公共祖先
public int lowestCommonAncestor (TreeNode root, int o1, int o2) {
// write code here
if(root == null) return -1;
if(root.val == o1 || root.val == o2) return root.val; //和头有关
//和头无关
int left = lowestCommonAncestor(root.left, o1, o2); //左子树寻找公共祖先 , 子树信息
int right = lowestCommonAncestor(root.right, o1, o2); //右子树寻找公共祖先 , 子树信息
if(left != -1 && right != -1) return root.val; //左右子树找到了 整合出信息
return left == -1 ? right : left;
}
43、序列化二叉树
import java.util.*;
public class Solution {
StringBuilder sb = new StringBuilder();
String Serialize(TreeNode root) {
SerializeHelp(root, sb);
return sb.toString();
}
TreeNode Deserialize(String str) {
String[] values = str.split("_");
//队列中存放分隔后的数据,value和#
Queue<String> queue = new LinkedList<>();
for (int i = 0; i < values.length; i++) {
queue.offer(values[i]);
}
return DeserializeHelp(queue);
}
void SerializeHelp(TreeNode node, StringBuilder sb) {
//先序遍历 null 为#_ ,其他分隔符为_
if (node == null) {
sb.append("#_");
return; //结束
}
sb.append(node.val + "_");
SerializeHelp(node.left, sb);
SerializeHelp(node.right, sb);
}
TreeNode DeserializeHelp(Queue<String> queue) {
String value = queue.poll();
if (value.equals("#")) return null; //遇到# 表示空节点
TreeNode node = new TreeNode(Integer.valueOf(value));
node.left = DeserializeHelp(queue);
node.right = DeserializeHelp(queue);
return node;
}
}
44、重建二叉树
public class Solution {
HashMap<Integer, Integer> map = new HashMap<>();
public TreeNode reConstructBinaryTree(int [] pre,int [] vin) {
for(int i = 0; i < pre.length; i++){
map.put(vin[i], i);//存放中序遍历
}
return build(pre, vin, 0, pre.length - 1, 0, pre.length - 1);
}
private TreeNode build(int [] pre,int [] vin, int pl, int pr, int vl, int vr){
if(pl > pr || vl > vr) return null;
int r = map.get(pre[pl]) - vl;//获取中序遍历的根节点
TreeNode root = new TreeNode(pre[pl]);
//左子树
root.left = build(pre, vin, pl + 1, pl + r, vl, vl + r - 1);
//右子树
root.right = build(pre, vin, pl + r + 1, pr, vl + r + 1, vr);
return root;
}
}
45、输出二叉树的右视图
import java.util.*;
public class Solution {
public int[] solve (int[] xianxu, int[] zhongxu) {
// write code here
//1、重建树
TreeNode root = rebuildTree(xianxu, zhongxu);
//2、右视图
List<Integer> list = new ArrayList<>();
rightview(root, list);
//3、将list变为int[]
int[] ans = new int[list.size()];
for (int i = 0; i < list.size(); i++) {
ans[i] = list.get(i);
}
return ans;
}
private TreeNode rebuildTree(int[] xianxu, int[] zhongxu) {
int pl = xianxu.length;
int ml = zhongxu.length;
//分治算法
if (pl == 0 || ml == 0) return null;
TreeNode node = new TreeNode(xianxu[0]);//根
//找到中序根中对应的第一个序号
for (int i = 0; i < ml ; i++) {
if (xianxu[0] == zhongxu[i]) {
node.left = rebuildTree(Arrays.copyOfRange(xianxu, 1, i + 1),
Arrays.copyOfRange(zhongxu, 0, i));
node.right = rebuildTree(Arrays.copyOfRange(xianxu, i + 1, pl),
Arrays.copyOfRange(zhongxu, i + 1, ml));
break;
}
}
return node;
}
private void rightview(TreeNode root, List<Integer> list) {
//层序遍历
if (root == null) return;
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
int size = queue.size();
while (size-- != 0) {
TreeNode node = queue.poll();
if (node.left != null) queue.offer(node.left);
if (node.right != null) queue.offer(node.right);
if (size == 0) list.add(node.val); //层序的最后一个值
}
}
}
}