一、樹形Dp
題
叉樹節點間的最大距離問題
從二叉樹的節點a出發,可以向上或者向下走,但沿途的節點只能經過一次,到達節點b時路 徑上的節點個數叫作a到b的距離,那麼二叉樹任何兩個節點之間都有距離,求整棵樹上的最 大距離。
/**
* @Author: 郜宇博
*/
public class TreeDp {
public static void main(String[] args) {
Node head2 = new Node(1);
head2.left = new Node(2);
head2.right = new Node(3);
head2.right.left = new Node(4);
head2.right.right = new Node(5);
head2.right.left.left = new Node(6);
head2.right.right.right = new Node(7);
head2.right.left.left.left = new Node(8);
head2.right.right.right.right = new Node(9);
System.out.println(maxDistance(head2));
}
public static class Node{
public Node left;
public Node right;
public int value;
public Node(int value) {
this.value = value;
}
}
public static class Result{
public int childMaxDistance;
public int childHeight;
public Result(int childMaxDistance, int childHeight) {
this.childMaxDistance = childMaxDistance;
this.childHeight = childHeight;
}
}
/**
* 獲取節點間最大距離
* 三種情況:
* 一、最大距離不帶本節點
* 1.最大距離是從左數獲取的
* 2,是從右樹獲取的
* 二、最大距離帶本節點
* 3.左子樹Height+右子數Hight+1
*
* 此時需要三個引數:子樹的最大距離,左子樹的高,右子樹的高
* 因此需要額外定製一個返回型別包含該數的最大距離,該數的高
* 從子樹分別獲取這兩個返回值
*/
public static int maxDistance(Node head){
return process(head).childMaxDistance;
}
public static Result process(Node head){
//空樹
if (head == null){
return new Result(0,0);
}
//1.最大距離是從左數獲取的
Result p1 = process(head.left);
//2.最大距離是從右數獲取的
Result p2 = process(head.right);
//3.最大距離經過自己
int maxDistanceP3 = p1.childHeight + p2.childHeight + 1;
//從子樹獲取完資訊,自己也要提供資訊
//高度
int height = Math.max(p1.childHeight,p2.childHeight) + 1;
//最大距離
//就是這三個可能性中最大的
int maxDistance = Math.max( maxDistanceP3,Math.max(p1.childMaxDistance,p2.childMaxDistance) );
return new Result(maxDistance,height);
}
}
最大開心值
/**
* @Author: 郜宇博
*/
public class MaxHappy {
public static void main(String[] args) {
}
public static class Emplyee{
public int happy;
public List<Emplyee> nexts;
public Emplyee(int happy) {
this.happy = happy;
}
}
/**
* happy值最大:
* 一、當本節點參加
* Happy = 本節點happy + 下級員工們不參加的MaxHappy
* 二、當本節點不參加
* happy = 下級員工們參加的MaxHappy 和 不參加的MaxHappy 的較大值
*
* 因此需要構造返回型別有兩個引數: 參加的maxHappy和不參加的maxHappy
*/
public static class Result{
public int comeMaxHappy;
public int unComeMaxHappy;
public Result(int comeMaxHappy, int unComeMaxHappy) {
this.comeMaxHappy = comeMaxHappy;
this.unComeMaxHappy = unComeMaxHappy;
}
}
public static int maxHappy(Emplyee head){
return Math.max( process(head).comeMaxHappy, process(head).unComeMaxHappy);
}
public static Result process(Emplyee emplyee){
//base case
if (emplyee == null){
return new Result(0,0);
}
//基層員工
if (emplyee.nexts == null){
return new Result(emplyee.happy,0);
}
int UncomeHappy = 0;
int comeHappy = 0;
//獲取各個員工的result
for (Emplyee e: emplyee.nexts){
Result result = process(e);
//不參加
//happy = 下級員工們參加的MaxHappy 和 不參加的MaxHappy 的較大值
UncomeHappy += Math.max( result.comeMaxHappy,result.unComeMaxHappy);
//參加
//Happy = 本節點happy + 下級員工們不參加的MaxHappy
comeHappy += emplyee.happy + result.unComeMaxHappy;
}
return new Result(comeHappy,UncomeHappy);
}
}
二、Morris遍歷
Morris遍歷細節
假設來到當前節點cur,開始時cur來到頭節點位置
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為空時遍歷停止
public static void morrisTravel(Node head){
if (head == null){
return;
}
Node cur = head;
Node mostRightNode;
while (cur != null){
mostRightNode = cur.left;
//cur有左孩子
if (mostRightNode != null){
//找到左子樹的最右節點,並且保證mostRight不會移動回cur
while (mostRightNode.right != null && mostRightNode.right != cur){
mostRightNode = mostRightNode.right;
}
//看mostRight是否有right
if (mostRightNode.right == null){
//沒有right,則新增right指向cur
mostRightNode.right = cur;
//並且cur向左遍歷
cur = cur.left;
//繼續這一迴圈
continue;
}
//指向了cur
else {
//斷開連線
mostRightNode.right = null;
//cur向右移動
}
}
//cur沒有左孩子,cur向右移動
cur = cur.right;
}
}
先序遍歷
//先序:能回來兩次的節點,第一次列印,第二次不列印
// 只能到一次的節點,直接列印
public static void morrisPreTravel(Node head){
if (head == null){
return;
}
Node cur = head;
Node mostRightNode;
while (cur != null){
mostRightNode = cur.left;
//cur有左孩子
//進入if:能回cur兩次的
if (mostRightNode != null){
//找到左子樹的最右節點,並且保證mostRight不會移動回cur
while (mostRightNode.right != null && mostRightNode.right != cur){
mostRightNode = mostRightNode.right;
}
//看mostRight是否有right
if (mostRightNode.right == null){
//第一次來到當前節點
System.out.print(cur.value+" ");
//沒有right,則新增right指向cur
mostRightNode.right = cur;
//並且cur向左遍歷
cur = cur.left;
//繼續這一迴圈
continue;
}
//指向了cur
else {
//第二次來到cur
//不列印
//斷開連線
mostRightNode.right = null;
//cur向右移動
}
}
//沒有左子樹的,走到else
else {
System.out.print(cur.value+" ");
}
//cur沒有左孩子,cur向右移動
cur = cur.right;
}
}
中序
//中序:能回來兩次的節點,第一次不列印,第二次列印
// 只能到一次的節點,直接列印
public static void morrisMediaTravel(Node head){
if (head == null){
return;
}
Node cur = head;
Node mostRightNode;
while (cur != null){
mostRightNode = cur.left;
//cur有左孩子
//進入if:能回cur兩次的
if (mostRightNode != null){
//找到左子樹的最右節點,並且保證mostRight不會移動回cur
while (mostRightNode.right != null && mostRightNode.right != cur){
mostRightNode = mostRightNode.right;
}
//看mostRight是否有right
if (mostRightNode.right == null){
//沒有right,則新增right指向cur
mostRightNode.right = cur;
//並且cur向左遍歷
cur = cur.left;
//繼續這一迴圈
continue;
}
//指向了cur
else {
//第二次來到cur
//不列印
//斷開連線
mostRightNode.right = null;
//cur向右移動
}
}
System.out.print(cur.value+" ");
//cur沒有左孩子,cur向右移動
cur = cur.right;
}
}
後續
//後續:能回來兩次的節點,第二次的手列印左樹的右邊界 逆序
//當while完成後,列印整棵樹的右邊界
public static void morrisLastTravel(Node head){
if (head == null){
return;
}
Node cur = head;
Node mostRightNode;
while (cur != null){
mostRightNode = cur.left;
//cur有左孩子
//進入if:能回cur兩次的
if (mostRightNode != null){
//找到左子樹的最右節點,並且保證mostRight不會移動回cur
while (mostRightNode.right != null && mostRightNode.right != cur){
mostRightNode = mostRightNode.right;
}
//看mostRight是否有right
if (mostRightNode.right == null){
//沒有right,則新增right指向cur
mostRightNode.right = cur;
//並且cur向左遍歷
cur = cur.left;
//繼續這一迴圈
continue;
}
//指向了cur
else {
//第二次來到cur
//不列印
//斷開連線
mostRightNode.right = null;
printEdge(cur.left);
//cur向右移動
}
}
//cur沒有左孩子,cur向右移動
cur = cur.right;
}
//while後,列印整棵樹左樹的右邊界
printEdge(head);
}
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;
}
題
判斷是否為線索二叉樹
可以根據中序遍歷改編
//中序:能回來兩次的節點,第一次不列印,第二次列印
// 只能到一次的節點,直接列印
public static boolean morrisMediaTravel(Node head){
if (head == null){
return true ;
}
Node cur = head;
Node mostRightNode;
int preNodeValue = Integer.MIN_VALUE;
while (cur != null){
mostRightNode = cur.left;
//cur有左孩子
//進入if:能回cur兩次的
if (mostRightNode != null){
//找到左子樹的最右節點,並且保證mostRight不會移動回cur
while (mostRightNode.right != null && mostRightNode.right != cur){
mostRightNode = mostRightNode.right;
}
//看mostRight是否有right
if (mostRightNode.right == null){
//沒有right,則新增right指向cur
mostRightNode.right = cur;
//並且cur向左遍歷
cur = cur.left;
//繼續這一迴圈
continue;
}
//指向了cur
else {
//第二次來到cur
//不列印
//斷開連線
mostRightNode.right = null;
//cur向右移動
}
}
//System.out.print(cur.value+" ");
if (cur.value < preNodeValue){
return false;
}
preNodeValue = cur.value;
//cur沒有左孩子,cur向右移動
cur = cur.right;
}
return true;
}
三、位運算
技巧公式
(n >> i) & 1: 取出整數 n在二進位制下的第 i 位
n & (~n + 1): 用來求數字的二進位制表示的最右邊的 1
x ^ y: x + y無進位相加
x & y: x + y 應該進位的數字
題
返回較大值
給定兩個有符號32位整數a和b,返回a和b中較大的(不用比較)
/**
* @Author: 郜宇博
*/
public class getMaxNoIf {
public static void main(String[] args) {
int a = -2147483647;
int b = 2147480000;
System.out.println(getMax(a,b));
}
/**
* 可能會越界
a >0 , b < 0時,可能越界 此時sc:1,所以符號不同是,返回a是否大於0就可以
符號不相同時,a的符號值代表是否應該返回a,
符號相同時,a-b的符號值代表是否應該返回a
也就是returnA: difSab * sa + sameSaB * sc(sc>0代表a大)
returnB: ~ returnA
*/
public static int getMax(int a, int b){
//a的符號
int sa = sign(a);
int sb = sign(b);
int sc = sign (a-b);
//檢視a和b是否相同符號,相同為0 不同為1
int difSab = sa ^ sb;
int sameSab = invert(difSab);
int returnA = difSab * sa + sameSab * sc;
int returnB = invert(returnA);
return returnA * a + returnB * b;
}
//取反
public static int invert(int a){
return a ^ 1;
}
//得到a的符號,非負返回1,負數返回0
public static int sign(int a){
//得到二進位制最左邊的符號位
int sign = a >> 31;
//& 1
int res = invert(sign & 1);
return res;
}
}
判斷是否2,4的冪
判斷一個32位正數是不是2的冪、4的冪
/**
* @Author: 郜宇博
*/
public class TwoPower {
public static void main(String[] args) {
System.out.println(isFourPower(18));
}
/**
* 方法一:
可以先獲取到a二進位制最右邊的數,然後和原來a比較,相同就是2的冪
方法二:
因為如果只有一個1,那麼減掉1後,就會把二進位制打散如 1000 - 1 = 0111
所以將減一後的數 & 原來的數,如果為0 就是2的冪
本方法方法二
*/
public static boolean isTwoPower(int a){
return (a & ( a-1)) == 0;
}
/**
* 起碼是2的冪才能是4的冪,所以應該先判斷是否是2的冪
* 因為4的冪的二進位制,1肯定在偶數位上,所以就可以 & 0101010101...
* 如果等於0就不是
*/
public static boolean isFourPower(int a){
int isFour = a & (0x55555555);
return isTwoPower(a) && (isFour != 0);
}
}
加減乘除
給定兩個有符號32位整數a和b,不能使用算術運算子,分別實現a和b的加、減、乘、除運 算
/**
* @Author: 郜宇博
沒有除
*/
public class AddMinusMultiDivdeByBit {
public static void main(String[] args) {
System.out.println(add(1,3));
System.out.println(minus(1,3));
System.out.println(multi(20,3));
}
//獲取無進位相加和進位的數, 迴圈,直到進位的數為0
public static int add(int a,int b){
//兩數異或後的結果,也就是無進位相加
int sum = a;
while (b != 0){
sum = a ^ b;
//b是進位結果
b = (a & b) << 1;
a = sum;
}
return sum;
}
// a - b == a + (-b)
// -b == (~b)+1
public static int minus(int a, int b){
return add(a ,negNum(b));
}
private static int negNum(int b) {
return add( ~b,1 );
}
//如果b末尾為0,則不加到res上,
public static int multi(int a, int b){
int res = 0;
while (b != 0){
//看b的最後一位是不是0
if ((b & 1) != 0){
//更新結果
res = add(res ,a);
}
//a 左移一位
a <<= 1;
//b無符號右移一位
b >>>= 1;
}
return res;
}
}