持续创作,加速成长!这是我参与「掘金日新计划 · 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次
- 先序 中 左 右 即顺序遍历后第一次出现就打印
- 中序 左 中 右 即顺序遍历后第二次出现就打印
- 后序 右 中 左 即顺序遍历后第三次出现就打印
先序遍历的思路
- 每次从栈中弹出一个节点cur
- 打印cur
- 先右再左(如果有)压入栈中
- 周而复始,即递归
只需要一个栈的压入取出,将右先压入栈再压左,取出时将左先弹出再弹右,实现了头左右
代码实现
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();
}
后序遍历的思路,这里需要两个栈
- 弹出头节点
- cur放入收栈
- 先左再右把节点压入栈中
- 弹出压入收栈中
- 周而复始 直至结束
此时收栈为头右左的顺序,将其倒序输出即可得到后序遍历的结果
深度优先遍历即先序遍历
完成二叉树的宽度优先遍历 (求一颗树的宽度)
注意 节点从右往左放,即从尾节点放起
方法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会有一点难度,不过思路大体上差不多。