【左程云 数据结构与算法笔记】P7 二叉树

149 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第15天,点击查看活动详情

下面是我整理的跟着b站左程云的数据结构与算法学习笔记,欢迎大家一起学习。

两个单链表相交的一系列问题
题目:两个可能有环也可能无环的单链表,头节点head1和head2,实现一个函数,如果两个链表相交,返回相交的第一个节点。如果不相交,返回null
要求:如果两个链表长度之和为N,时间复杂度达到O(N),额外空间复杂度达到O(1)

不是比值,而是比地址,即两个链表中所指向的地址相同即相等
解题思路:
首先要判断链表是否有环

即实现一个函数,有环则则返回第一个环,没有则返回null

实现思路1

可以将所有东西放在hashMap中,每次放的时候先取一下,若不为空,则直接返回这个数

实现思路2

用一个快指针以及一个慢指针一开始都在开始处,快指针一次走两步,慢指针一次走一步,若存在环,快指针不会马上到null,并且在环中会慢慢追上慢指针,只要存在环,两者一定相遇而且转圈数<=2

思路小总结:快慢指针一定会在环上相遇,相遇的时候,快指针到头变成一次走一步,慢指针停在原地,两者一次都走一步,再次相遇的时候,一定在第一个入环的节点处
代码实现:

	public static Node getLoopNode(Node head) {
		if (head == null || head.next == null || head.next.next == null) {
			return null;
		}
		Node n1 = head.next; // n1 -> slow
		Node n2 = head.next.next; // n2 -> fast
		while (n1 != n2) {
			if (n2.next == null || n2.next.next == null) {
				return null;
			}
			n2 = n2.next.next;
			n1 = n1.next;
		}
		n2 = head; // n2 -> walk again from head
		while (n1 != n2) {
			n1 = n1.next;
			n2 = n2.next;
		}
		return n1;
	}

注意 每个节点只有一个next,考虑情况不用考虑太多情况

链表相交情况1 两条链表都不存在环

也有可能到某一个节点之后 后面都共有

两个链表若相交,最后面的节点一定相等
当长链表走完两者相差的数量,两条链表相当于在同一个起跑线上,不断向后遍历即可得到相交的第一个节点
先判断end1和end2是否相等,若不相等,则链表没有相交
解题思路:先将两条链表的长度相减得到两条链表的相差长度n,判断两条链表的最后节点是否相同,若不相同返回null,若相同,
将长链表变成cur1,短链表变成cur2
长链表先走链表相差长度n,两条链表一起向后遍历next,即可得到相交的第一个节点
代码实现:

public static Node noLoop(Node head1, Node head2) {
		if (head1 == null || head2 == null) {
			return null;
		}
		Node cur1 = head1;
		Node cur2 = head2;
		int n = 0;
		while (cur1.next != null) {
			n++;
			cur1 = cur1.next;
		}
		while (cur2.next != null) {
			n--;
			cur2 = cur2.next;
		}
		if (cur1 != cur2) {
			return null;
		}
		cur1 = n > 0 ? head1 : head2;
		cur2 = cur1 == head1 ? head2 : head1;
		n = Math.abs(n);
		while (n != 0) {
			n--;
			cur1 = cur1.next;
		}
		while (cur1 != cur2) {
			cur1 = cur1.next;
			cur2 = cur2.next;
		}
		return cur1;
		}

链表相交情况2 只有一条存在环

无论如何,两者不可能存在相交的节点,有相交时一定都没有环或都有环

链表相交情况3 两条存都在环

  • 没有出现相交的方法 ->返回null
  • 先出现相交节点后面共同的节点和环 ->返回第一个共同的节点
  • 在共同环相遇之前没有相遇 ->返回loop1或loop2

    代码实现
//两个有环链表之间的问题
	public static Node bothLoop(Node head1, Node loop1, Node head2, Node loop2) {
		Node cur1 = null;
		Node cur2 = null;
		if (loop1 == loop2) {
			cur1 = head1;
			cur2 = head2;
			int n = 0;
			while (cur1 != loop1) {
				n++;
				cur1 = cur1.next;
			}
			while (cur2 != loop2) {
				n--;
				cur2 = cur2.next;
			}
			cur1 = n > 0 ? head1 : head2;
			cur2 = cur1 == head1 ? head2 : head1;
			n = Math.abs(n);
			while (n != 0) {
				n--;
				cur1 = cur1.next;
			}
			while (cur1 != cur2) {
				cur1 = cur1.next;
				cur2 = cur2.next;
			}
			return cur1;
		} else {
			cur1 = loop1.next;
			while (cur1 != loop1) {
				if (cur1 == loop2) {
					return loop1;
				}
				cur1 = cur1.next;
			}
			return null;
		}
	}

主函数代码

		if (head1 == null || head2 == null) {
			return null;
		}
		Node loop1 = getLoopNode(head1);
		Node loop2 = getLoopNode(head2);
		if (loop1 == null && loop2 == null) {
			return noLoop(head1, head2);
		}
		if (loop1 != null && loop2 != null) {
			return bothLoop(head1, loop1, head2, loop2);
		}
		return null;
	}

二叉树


哈希表遍历方法
通过不断的递归,即不断向左遍历,遍历完之后返回树节点,再访问右节点,访问完之后再返回树节点,每个节点都会递归3次

  • 先序 中 左 右 即顺序遍历后第一次出现就打印
  • 中序 左 中 右 即顺序遍历后第二次出现就打印
  • 后序 右 中 左 即顺序遍历后第三次出现就打印

先序遍历的思路

  1. 每次从栈中弹出一个节点cur
  2. 打印cur
  3. 先右再左(如果有)压入栈中
  4. 周而复始,即递归

只需要一个栈的压入取出,将右先压入栈再压左,取出时将左先弹出再弹右,实现了头左右

代码实现

public static void preOrderUnRecur(Node head) {  
    System.out.println("preOrder:");  
    if (head!=null){  
        Stack<Node> stack = new Stack<>();  
        stack.add(head); 
        //关键循环 
        while (!stack.isEmpty()){  
            head=stack.pop();  
            if (head.right!=null){  
                stack.push(head.right);  
            }  
            if (head.left!=null){  
                stack.push(head.left);  
            }  
        }  
    }  
    System.out.println();  
}

中序遍历的思路 采用非递归的思路

每颗子树,整棵树左边界进栈,依次弹出的过程中,打印,对弹出节点右树重复

即不断遍历左根数并依次打进栈中,取出时打印并找其右节点,若有则压入栈中,没有则这个节点结束,取下一个出栈的节点

代码实现:

public static void inOrderUnRecur(Node head) {  
    System.out.print("in-order: ");  
    if (head != null) {  
        Stack<Node> stack = new Stack<Node>();  
        while (!stack.isEmpty() || head != null) {  
            if (head != null) {  
                //不停的左边界进栈  
                stack.push(head);  
                head = head.left;  
            } else {  
                //不停的寻找右边界  
                head = stack.pop();  
                System.out.print(head.value + " ");  
                head = head.right;  
            }  
        }  
    }  
    System.out.println();  
}

后序遍历的思路,这里需要两个栈

  1. 弹出头节点
  2. cur放入收栈
  3. 先左再右把节点压入栈中
  4. 弹出压入收栈中
  5. 周而复始 直至结束

    此时收栈为头右左的顺序,将其倒序输出即可得到后序遍历的结果
    深度优先遍历即先序遍历
    完成二叉树的宽度优先遍历 (求一颗树的宽度)

    注意 节点从右往左放,即从尾节点放起
    方法1: 借助hashMap
    设置一个hashmap,当节点进去时,得到他的level,因为头节点的级别设置为1,且放左右孩子的level都有加1,当当前节点的级别等于当前级别时(curlNodeLevel=curLevel),如头节点的情况,当前级别的节点数curLevelNodes加加,当不相同时,即开始下一级的数,将上一级的节点数结算与max作比较得到最大宽度并获得每一节点的等级level

代码实现

public static int widthFirst(Node head){  
    if (head==null){  
        return 0;  
    }  
    Queue<Node> queue = new LinkedList<>();  
    HashMap<Node, Integer> levelMap = new HashMap<>();  
    levelMap.put(head, 1);  
    int curLevel=1;  
    int curLevelNodes=0;  
    int max=Integer.MIN_VALUE;  
  
    queue.add(head);  
    while (!queue.isEmpty()){  
        Node cur = queue.poll();  
        int curlNodeLevel = levelMap.get(cur);  
        if (curlNodeLevel==curLevel){  
            curLevelNodes++;  
        }else {  
            max=Math.max(max, curLevelNodes);  
            curLevel++;  
            curLevelNodes=1;  
        }  
  
        System.out.println(cur.value);  
         if (cur.left!=null){  
            levelMap.put(cur.left, curlNodeLevel+1);  
            queue.add(cur.left);  
        }  
        if (cur.right!=null){  
            levelMap.put(cur.right, curlNodeLevel+1);  
            queue.add(cur.right);  
        }  
  
    }  
    return max;  
}

此时max即为最大宽度
思路2 不借助hashMap的方法
需要准备4个变量

  • NodeCurend 当前level下的最后一个节点
  • nextEnd 存进栈中的最后一个节点
  • curLevel 当前等级拥有的节点数
  • max 最大宽度数
    NodeCurend默认为第一个头节点,每次有节点压入栈中都要将nextEnd置换为该节点,将节点取出时,将其左右节点压栈,如果等于NodeCurend即标志当前节点下的最后一个节点时,将nextend的值赋与NodeCurend并将nextEnd设为空,curLevel为当前等级下的节点数,与max作比较得到最大宽度并将本身设置为0,每次取出时都要将curLevel++
public static int noHashGetMaxWidth(Node head){  
    if (head==null){  
        return 0;  
    }  
    Queue<Node> queue = new LinkedList<>();  
    queue.add(head);  
    Node nodeCurend=null;//当前level下的最后一个节点  
    Node nextEnd=head;//存进栈中的最后一个节点  
    int curLevel=0;  
    int max=0;  
    while (!queue.isEmpty()) {  
        Node cur = queue.poll();  
        //当当前节点为该等级下的最后一个节点时,统计宽度  
        curLevel++;  
        if (cur==nodeCurend){  
            max=Math.max(curLevel, max);  
            nodeCurend=nextEnd;  
            curLevel=0;  
        }else {  
            nextEnd=cur;  
        }  
        System.out.println(cur.value);  
  
        if (cur.left!=null){  
            queue.add(cur.left);  
        }  
        if (cur.right!=null){  
            queue.add(cur.right);  
        }  
    }  
    return max;  
}

总结

不使用HashMap会有一点难度,不过思路大体上差不多。