双指针思想
双指针指的是两个指针,而这俩个指针可以随意步数的前进后退
一般是一次一步往后走
一个数组,请把奇数放到奇数下标,偶数放到偶数下标
双指针
a在0位置,b在1位置,两个指针每次走两步,则一个始终指向奇数位置,一个为偶数位置
从后往前遍历,如果当前数为奇数,与b交换,b往后两步,
如果当前数为偶数则与a 交换,a 往后两步,
当前数再重复判断,直到满足题目要求再往前遍历
二叉树的Morris遍历 (面试用)
一种遍历二叉树的方式,并且时间复杂度O(N),额外空间复杂度O(1)
通过利用原树中大量空闲指针的方式,达到节省空间的目的
一般的递归和迭代方法都是
时间复杂O(n)
空间复杂度 递归栈开的空间为O(h)
流程
1)如果cur没有左孩子,cur向右移动(cur = cur.right)
2)如果cur有左孩子,找到左子树上最右的节点mostRight:
a.如果mostRight的右指针指向空,让其指向cur,
然后cur向左移动(cur = cur.left)
b.如果mostRight的右指针指向cur,让其指向null,
然后cur向右移动(cur = cur.right)
3)cur为空时遍历停止
实质:
对于没有左子树的节点只到达一次,
对于有左子树的节点会到达两次
morris遍历时间复杂度依然是O(N)
public static class Node {
public int value;
Node left;
Node right;
public Node(int data) {
this.value = data;
}
}
public static void morris(Node head) {
if (head == null) {
return;
}
Node cur = head;
Node mostRight = null;
while (cur != null) {
mostRight = cur.left;
if (mostRight != null) {
// while 为找到最右节点
//要么第一次到达最右节点,要么第二次到达时,最右节点指向了当前节点,则此时遍历的节点就是最右节点
while (mostRight.right != null && mostRight.right != cur) {
mostRight = mostRight.right;
}
//第一次来到我自己
if (mostRight.right == null) {
mostRight.right = cur;
cur = cur.left;
continue;
} else {
// 此时最右节点指向了cur,需要将指针置为空
mostRight.right = null;
}
}
cur = cur.right;
}
}
先序遍历
1
2 3
4 5 6 7
morris 序列 1 2 4 2 5 3 6 3 7
打印 morris 序列中第一次出现的元素就是先序遍历
(有左孩子即会出现两次)
先序 1 2 4 5 3 6 7
//第一次到达时,打印
public static void morrisPre(Node head) {
if (head == null) {
return;
}
Node cur = head;
Node mostRight = null;
while (cur != null) {
mostRight = cur.left;
if (mostRight != null) {
while (mostRight.right != null && mostRight.right != cur) {
mostRight = mostRight.right;
}
if (mostRight.right == null) {
// 有左树,第二次到达时打印
System.out.print(cur.value + " ");
mostRight.right = cur;
cur = cur.left;
continue;
} else {
mostRight.right = null;
}
} else {
// 没有左树,第一次就打印
System.out.print(cur.value + " ");
}
cur = cur.right;
}
System.out.println();
}
中序遍历
对于出现两次的元素,第二次打印,只出现一次的元素,第一次出现就打印
morris 序列 1 2 4 2 5 3 6 3 7
中序 4 2 5 1 6 3 7
public static void morrisIn(Node head) {
if (head == null) {
return;
}
Node cur = head;
Node mostRight = null;
while (cur != null) {
mostRight = cur.left;
if (mostRight != null) {
while (mostRight.right != null && mostRight.right != cur) {
mostRight = mostRight.right;
}
if (mostRight.right == null) {
mostRight.right = cur;
cur = cur.left;
continue;
} else {
mostRight.right = null;
}
}
//只能到达一次的第一次打印,可以到达两次的第二次到达才打印
System.out.print(cur.value + " ");
cur = cur.right;
}
System.out.println();
}
后序遍历
对于可以到达两次的元素,第二次回到该元素时,逆序打印右边界
整个树遍历后,逆序打印整棵树的右边界
原理为:
一个树可以被右边界分解掉,由左往右,每次逆序打印其右边界,就是后序遍历
中途要求逆序打印,但morris 要求O(1)空间,即排除了使用额外空间的办法
可以使用反转链表来实现逆序打印
public static void morrisPos(Node head) {
if (head == null) {
return;
}
Node cur = head;
Node mostRight = null;
while (cur != null) {
mostRight = cur.left;
if (mostRight != null) {
while (mostRight.right != null && mostRight.right != cur) {
mostRight = mostRight.right;
}
if (mostRight.right == null) {
mostRight.right = cur;
cur = cur.left;
continue;
} else {
mostRight.right = null;
//第二次回到该节点时,逆序打印此节点
printEdge(cur.left);
}
}
cur = cur.right;
}
// 结束之后,打印整个树的右边界
printEdge(head);
System.out.println();
}
//先链表反转,遍历后再反转回去
public static void printEdge(Node head) {
Node tail = reverseEdge(head);
Node cur = tail;
while (cur != null) {
System.out.print(cur.value + " ");
cur = cur.right;
}
reverseEdge(tail);
}
public static Node reverseEdge(Node from) {
Node pre = null;
Node next = null;
while (from != null) {
next = from.right;
from.right = pre;
pre = from;
from = next;
}
return pre;
}
判断是否为二叉查找树
中序遍历为递增序列则为二叉查找树
使用morris 遍历
public static boolean isBST(Node head) {
if (head == null) {
return;
}
Node cur = head;
Node mostRight = null;
Integer pre= null;
while (cur != null) {
mostRight = cur.left;
if (mostRight != null) {
while (mostRight.right != null && mostRight.right != cur) {
mostRight = mostRight.right;
}
if (mostRight.right == null) {
mostRight.right = cur;
cur = cur.left;
continue;
} else {
mostRight.right = null;
}
}
// 如果上一个值大于等于当前值,则这个树一定不是 bst
if(pre !=null && pre>=cur.value ){
return false;
}
pre =cur.value;
cur = cur.right;
}
return true;
}
求二叉树最小高度
给定一棵二叉树的头节点head
求以head为头的树中,最小深度是多少?
递归办法
public static class Node {
public int val;
public Node left;
public Node right;
public Node(int x) {
val = x;
}
}
public static int minHeight1(Node head) {
if (head == null) {
return 0;
}
return p(head);
}
// 返回x为头的树,最小深度是多少
// 使用递归求解
public static int p(Node x) {
if (x.left == null && x.right == null) {
return 1;
}
// 左右子树起码有一个不为空
int leftH = Integer.MAX_VALUE;
if (x.left != null) {
leftH = p(x.left);
}
int rightH = Integer.MAX_VALUE;
if (x.right != null) {
rightH = p(x.right);
}
return 1 + Math.min(leftH, rightH);
}
morris 办法
需要做到以下两点
每到一个节点,可以知道它的高度
每到一个节点,可以判断出是否为叶子节点
// 根据morris遍历改写
public static int minHeight2(Node head) {
if (head == null) {
return 0;
}
Node cur = head;
Node mostRight = null;
int curLevel = 0;
int minHeight = Integer.MAX_VALUE;
while (cur != null) {
mostRight = cur.left;
if (mostRight != null) {
int rightBoardSize = 1;
while (mostRight.right != null && mostRight.right != cur) {
// 由下往上的元素层数
rightBoardSize++;
mostRight = mostRight.right;
}
if (mostRight.right == null) { // 第一次到达
curLevel++;
mostRight.right = cur;
cur = cur.left;
continue;
} else {
// 第二次到达,需要减去rightBoardSize
if (mostRight.left == null) {
//此时到达叶节点,计算最小高度
minHeight = Math.min(minHeight, curLevel);
}
curLevel -= rightBoardSize;
mostRight.right = null;
}
} else {
// 只有一次到达
curLevel++;
}
cur = cur.right;
}
int finalRight = 1;
cur = head;
while (cur.right != null) {
finalRight++;
cur = cur.right;
}
// 单独去找一下最右部分的最小高度
if (cur.left == null && cur.right == null) {
minHeight = Math.min(minHeight, finalRight);
}
return minHeight;
}