演算法基提升礎學習2

橡皮筋兒發表於2021-12-07

一、樹形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;
    }
}

相關文章