大厂AI必备数据结构与算法——leetcode链表习题(四)详细文档

   

冲冲冲!开干

这篇的文章开头就真的不废话那么多了!!直接开干,记住:边学习,边记录,边实践,边温习!

这个故事好看吧?好看就赶快学!!!不好看也快学,不然没饭吃!!! 

视频资源:文章内容参考了黑马程序员的数据结构与算法视频,想深入了解的小伙伴们可以点击下方链接观看:

大厂必备数据结构与算法Java视频教程,java高级程序员必学的数据结构与算法

加油吧,未来的高手!!!

加油吧,未来的高手!!!

加油吧,未来的高手!!!

习题

E01. 反转单向链表-Leetcode 206

对应力扣题目 206. 反转链表 - 力扣(LeetCode)

输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]

输入:[1,2]
输出:[2,1]

输入:[]
输出:[]

方法1

构造一个新链表,从旧链表依次拿到每个节点,创建新节点添加至新链表头部,完成后新链表即是倒序的

public ListNode reverseList(ListNode o1) {
    ListNode n1 = null;
    ListNode p = o1;
    while (p != null) {
        n1 = new ListNode(p.val, n1);
        p = p.next;
    }
    return n1;
}

评价:简单直白,就是得新创建节点对象

方法2

与方法1 类似,构造一个新链表,从旧链表头部移除节点,添加到新链表头部,完成后新链表即是倒序的,区别在于原题目未提供节点外层的容器类,这里提供一个,另外一个区别是并不去构造新节点

static class List {
    ListNode head;

    public List(ListNode head) {
        this.head = head;
    }

    public ListNode removeFirst(){
        ListNode first = head;
        if (first != null) {
            head = first.next;
        }
        return first;
    }

    public void addFirst(ListNode first) {
        first.next = head;
        head = first;
    }
}

代码

public ListNode reverseList(ListNode head) {
    List list1 = new List(head);
    List list2 = new List(null);
    ListNode first;
    while ((first = list1.removeFirst()) != null) {
        list2.addFirst(first);
    }
    return list2.head;
}

评价:更加面向对象,如果实际写代码而非刷题,更多会这么做

方法3

递归,在时让 5→45→4,4→34→3 …

首先,写一个递归方法,返回值用来拿到最后一个节点

public ListNode reverseList(ListNode p) {
    if (p == null || p.next == null) { // 不足两个节点
        return p; // 最后一个节点
    }
    ListNode last = reverseList(p.next);
    return last;
}
  • 注意1:递归终止条件是 curr.next == null,目的是到最后一个节点就结束递归,与之前递归遍历不一样
  • 注意2:需要考虑空链表即 p == null 的情况

可以先测试一下

ListNode o5 = new ListNode(5, null);
ListNode o4 = new ListNode(4, o5);
ListNode o3 = new ListNode(3, o4);
ListNode o2 = new ListNode(2, o3);
ListNode o1 = new ListNode(1, o2);
ListNode n1 = new E01Leetcode206().reverseList(o1);
System.out.println(n1);

会打印

[5]

下面为伪码调用过程,假设节点分别是 1→2→3→4→5→null1→2→3→4→5→null,先忽略返回值

reverseList(ListNode p = 1) {
    reverseList(ListNode p = 2) {
    	reverseList(ListNode p = 3) {
    		reverseList(ListNode p = 4) {
    			reverseList(ListNode p = 5) {
    				if (p == null || p.next == null) {
                        return p; // 返回5
                    }
				}
                // 此时p是4, p.next是5
			}
            // 此时p是3, p.next是4
		}
        // 此时p是2, p.next是3
	}
    // 此时p是1, p.next是2
}

接下来,从 p = 4 开始,要让 5→45→4,4→34→3 …

reverseList(ListNode p = 1) {
    reverseList(ListNode p = 2) {
    	reverseList(ListNode p = 3) {
    		reverseList(ListNode p = 4) {
    			reverseList(ListNode p = 5) {
    				if (p == null || p.next == null) {
                        return p; // 返回5
                    }
				}
                // 此时p是4, p.next是5, 要让5指向4,代码写成 p.next.next=p
                // 还要注意4要指向 null, 否则就死链了
			}
            // 此时p是3, p.next是4
		}
        // 此时p是2, p.next是3
	}
    // 此时p是1, p.next是2
}

最终代码为:

public ListNode reverseList(ListNode p) {    
    if (p == null || p.next == null) { // 不足两个节点
        return p; // 最后一个节点
    }
    ListNode last = reverseList(p.next);
    p.next.next = p;
    p.next = null;
    return last;
}

Q:为啥不能在的过程中倒序?

A:比如

  • $ 1 \rightarrow 2 \rightarrow 3 $ 如果递的过程中让 2→12→1 那么此时 2→32→3 就被覆盖,不知道接下来递给谁
  • 而归的时候让 3→23→2 不会影响上一层的 1→21→2

评价:单向链表没有 prev 指针,但利用递归的特性【记住了】链表每次调用时相邻两个节点是谁

方法4

从链表每次拿到第二个节点,将其从链表断开,插入头部,直至它为 null 结束

  1. 设置指针 o1(旧头)、n1(新头)、o2(旧老二),分别指向第一,第一,第二节点

n1 o11→o22→3→4→5→null1n1 o1​→2o2​→3→4→5→null

  1. 将 o2 节点从链表断开,即 o1 节点指向第三节点

$ \frac{n1 \ o1}{1} \rightarrow 3 \rightarrow 4 \rightarrow 5 \rightarrow null$ ,o222o2​

  1. o2 节点链入链表头部,即

o22→n1 o11→3→4→5→null2o2​→1n1 o1​→3→4→5→null

  1. n1 指向 o2

n1 o22→o11→3→4→5→null2n1 o2​→1o1​→3→4→5→null

  1. o2 指向 o1 的下一个节点,即

n12→o11→o23→4→5→null2n1​→1o1​→3o2​→4→5→null

  1. 重复以上 2∼52∼5 步,直到 o2 指向 null

  2. 还应当考虑边界条件,即链表中不满两个元素时,无需走以上逻辑

参考答案

public ListNode reverseList(ListNode o1) {    
    if (o1 == null || o1.next == null) { // 不足两个节点
        return o1;
    }
    ListNode o2 = o1.next;
    ListNode n1 = o1;
    while (o2 != null) {
        o1.next = o2.next; 
        o2.next = n1;
        n1 = o2;
        o2 = o1.next;
    }
    return n1;
}

方法5

要点:把链表分成两部分,思路就是不断从链表2的头,往链表1的头搬移

  1. n1 指向 null,代表新链表一开始没有元素,o1 指向原链表的首节点

n1nullnulln1​,o11→2→3→4→5→null1o1​→2→3→4→5→null

  1. 开始循环,o2 指向原链表次节点

n1nullnulln1​,o11→o22→3→4→5→null1o1​→2o2​→3→4→5→null

  1. 搬移

o11→n1null1o1​→nulln1​ , o22→3→4→5→null2o2​→3→4→5→null

  1. 指针复位

n11→null1n1​→null , o1 o22→3→4→5→null2o1 o2​→3→4→5→null

  1. 重复 2∼42∼4 步
  2. 当 o1 = null 时退出循环

参考答案

public ListNode reverseList(ListNode o1) {
    if (o1 == null || o1.next == null) {
        return o1;
    }
    ListNode n1 = null;
    while (o1 != null) {
        ListNode o2 = o1.next;
        o1.next = n1;
        n1 = o1;
        o1 = o2;
    }
    return n1;
}

评价:本质上与方法2 相同,只是方法2更为面向对象

E02. 根据值删除节点-Leetcode 203

例如

输入:head = [1,2,6,3,6], val = 6
输出:[1,2,3]

输入:head = [], val = 1
输出:[]

输入:head = [7,7,7,7], val = 7
输出:[]

方法1

图中 s 代表 sentinel 哨兵(如果不加哨兵,则删除第一个节点要特殊处理),例如要删除 6

p1   p2
s -> 1 -> 2 -> 6 -> 3 -> 6 -> null
  • 如果 p2 不等于目标,则 p1,p2 不断后移
	 p1   p2
s -> 1 -> 2 -> 6 -> 3 -> 6 -> null

	 	  p1   p2
s -> 1 -> 2 -> 6 -> 3 -> 6 -> null
  • p2 == 6,删除它,注意 p1 此时保持不变,p2 后移
	 	  p1   p2
s -> 1 -> 2 -> 3 -> 6 -> null
  • p2 不等于目标,则 p1,p2 不断后移
	 	  	   p1   p2
s -> 1 -> 2 -> 3 -> 6 -> null
  • p2 == 6,删除它,注意 p1 此时保持不变,p2 后移
	 	  	   p1   p2
s -> 1 -> 2 -> 3 -> null
  • p2 == null 退出循环

最后代码

public ListNode removeElements(ListNode head, int val) {
    ListNode sentinel = new ListNode(-1, head);
    ListNode p1 = sentinel;
    ListNode p2;
    while ((p2 = p1.next) != null) {
        if (p2.val == val) {
            p1.next = p2.next;
        } else {
            p1 = p1.next;
        }
    }
    return sentinel.next;
}

方法2

思路,递归函数负责返回:从当前节点(我)开始,完成删除的子链表

  1. 若我与 v 相等,应该返回下一个节点递归结果
  2. 若我与 v 不等,应该返回我,但我的 next 应该更新(让我能带上后续删过的子链表)
removeElements(ListNode p=1, int v=6){
    1.next=removeElements(ListNode p=2, int v=6){
    	2.next=removeElements(ListNode p=6, int v=6){
    		removeElements(ListNode p=3, int v=6){
    			3.next=removeElements(ListNode p=6, int v=6){
    				removeElements(ListNode p=null, int v=6){
    					// 没有节点,返回
                        return null
					}
				}
                return 3
			}
		}
        return 2
    }
    return 1
}

代码

public ListNode removeElements(ListNode head, int val) {
    if (head == null) {
        return null;
    }
    if (head.val == val) {
        return removeElements(head.next, val);
    } else {
        head.next = removeElements(head.next, val);
        return head;
    }
}
E03. 删除倒数节点-Leetcode 19

例如

输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]

输入:head = [1], n = 1
输出:[]

输入:head = [1,2], n = 1
输出:[1]

另外题目提示

  • 链表至少一个节点
  • n 只会在合理范围

方法1

思路,写一个递归函数,用来返回下一个节点的倒数序号

recursion(ListNode p=1, int n=2) {
    recursion(ListNode p=2, int n=2) {
    	recursion(ListNode p=3, int n=2) {
    		recursion(ListNode p=4, int n=2) {
    			recursion(ListNode p=5, int n=2) {
    				recursion(ListNode p=null, int n=2) {
    					return 0; // 最内层序号0
					}
                    return 1; // 上一次返回值+1
				}
                return 2;
			}
            if(返回值 == n == 2) {
                // 删除 next
            }
            return 3;
		}
        return 4;
	}
    return 5;
}

但上述代码有一个问题,就是若删除的是第一个节点,它没有上一个节点,因此可以加一个哨兵来解决

代码

public ListNode removeNthFromEnd(ListNode head, int n) {
    ListNode sentinel = new ListNode(-1, head);
    recursion(sentinel, n);
    return sentinel.next;
}

public int recursion(ListNode p, int n) {
    if (p == null) {
        return 0;
    }
    int nth = recursion(p.next, n);
    if (nth == n) {
        p.next = p.next.next;
    }
    return nth + 1;
}

Q:p.next.next 不怕空指针吗?

A:

  • p 是待删除节点的上一个节点,如果能递归回到 p,那么 p.next 肯定有值,不会是 null
  • 且题目说明了 n >=1,不会因为 nth == 0 而让 p.next 指向最后的 null

方法2

快慢指针,p1 指向待删节点的上一个,p2 先走 n + 1 步

i=0
p2
s -> 1 -> 2 -> 3 -> 4 -> 5 -> null

     i=1
     p2
s -> 1 -> 2 -> 3 -> 4 -> 5 -> null

          i=2
          p2
s -> 1 -> 2 -> 3 -> 4 -> 5 -> null

               i=3 从此开始 p1 p2 依次向右平移, 直到 p2 移动到末尾
p1             p2
s -> 1 -> 2 -> 3 -> 4 -> 5 -> null

               p1             p2
s -> 1 -> 2 -> 3 -> 4 -> 5 -> null

代码

public ListNode removeNthFromEnd(ListNode head, int n) {
    ListNode s = new ListNode(-1, head);
    ListNode p1 = s;
    ListNode p2 = s;
    for (int i = 0; i < n + 1; i++) {
        p2 = p2.next;
    }
    while (p2 != null) {
        p1 = p1.next;
        p2 = p2.next;
    }
    p1.next = p1.next.next;
    return s.next;
}

方法3

public ListNode removeNthFromEnd(ListNode head, int n) {
    Composite c = recursion(head, n);
    return c.node;
}

static class Composite {
    ListNode node;
    int nth;

    public Composite(ListNode node, int nth) {
        this.node = node;
        this.nth = nth;
    }
}

public Composite recursion(ListNode p, int n) {
    if (p == null) {
        return new Composite(null, 1);
    }
    Composite c = recursion(p.next, n);
    if (c.nth != n) {
        p.next = c.node;
        c.node = p;
    }
    c.nth +=1;
    return c;
}
E04. 有序链表去重-Leetcode 83

例如

输入:head = [1,1,2]
输出:[1,2]

输入:head = [1,1,2,3,3]
输出:[1,2,3]

注意:重复元素保留一个

方法1

p1   p2
1 -> 1 -> 2 -> 3 -> 3 -> null
  • p1.val == p2.val 那么删除 p2,注意 p1 此时保持不变
p1   p2
1 -> 2 -> 3 -> 3 -> null
  • p1.val != p2.val 那么 p1,p2 向后移动
     p1   p2
1 -> 2 -> 3 -> 3 -> null
         
          p1   p2
1 -> 2 -> 3 -> 3 -> null     
  • p1.val == p2.val 那么删除 p2
          p1   p2
1 -> 2 -> 3 -> null   
  • 当 p2 == null 退出循环

代码

public ListNode deleteDuplicates(ListNode head) {
    // 链表节点 < 2
    if (head == null || head.next == null) {
        return head;
    }
    // 链表节点 >= 2
    ListNode p1 = head;
    ListNode p2;
    while ((p2 = p1.next) != null) {
        if (p1.val == p2.val) {
            p1.next = p2.next;
        } else {
            p1 = p1.next;
        }
    }
    return head;
}

方法2

递归函数负责返回:从当前节点(我)开始,完成去重的链表

  1. 若我与 next 重复,返回 next
  2. 若我与 next 不重复,返回我,但 next 应当更新
deleteDuplicates(ListNode p=1) {
    deleteDuplicates(ListNode p=1) {
        1.next=deleteDuplicates(ListNode p=2) {
            2.next=deleteDuplicates(ListNode p=3) {
                deleteDuplicates(ListNode p=3) {
					// 只剩一个节点,返回
                    return 3
                }                
            }
            return 2
        }
        return 1
    }
}

代码

public ListNode deleteDuplicates(ListNode p) {
    if (p == null || p.next == null) {
        return p;
    }
    if(p.val == p.next.val) {
        return deleteDuplicates(p.next);
    } else {
        p.next = deleteDuplicates(p.next);
        return p;
    }
}
E05. 有序链表去重-Leetcode 82

例如

输入:head = [1,2,3,3,4,4,5]
输出:[1,2,5]

输入:head = [1,1,1,2,3]
输出:[2,3]

注意:重复元素一个不留

方法1

递归函数负责返回:从当前节点(我)开始,完成去重的链表

  1. 若我与 next 重复,一直找到下一个不重复的节点,以它的返回结果为准
  2. 若我与 next 不重复,返回我,同时更新 next
deleteDuplicates(ListNode p = 1) {
    // 找下个不重复的
	deleteDuplicates(ListNode p = 1) {
        deleteDuplicates(ListNode p = 1) {
			deleteDuplicates(ListNode p = 2) {
                2.next=deleteDuplicates(ListNode p = 3) {
					// 只剩一个节点,返回
                    return 3
                }
                return 2
			}
        }
    }
}

代码

public ListNode deleteDuplicates(ListNode p) {
    if (p == null || p.next == null) {
        return p;
    }
    if (p.val == p.next.val) {
        ListNode x = p.next.next;
        while (x != null && x.val == p.val) {
            x = x.next;
        }
        return deleteDuplicates(x);
    } else {
        p.next = deleteDuplicates(p.next);
        return p;
    }
}

方法2

p1 是待删除的上一个节点,每次循环对比 p2、p3 的值

  • 如果 p2 与 p3 的值重复,那么 p3 继续后移,直到找到与 p2 不重复的节点,p1 指向 p3 完成删除
  • 如果 p2 与 p3 的值不重复,p1,p2,p3 向后平移一位,继续上面的操作
  • p2 或 p3 为 null 退出循环
    • p2 为 null 的情况,比如链表为 1 1 1 null
p1 p2 p3
s, 1, 1, 1, 2, 3, null

p1 p2    p3
s, 1, 1, 1, 2, 3, null

p1 p2       p3
s, 1, 1, 1, 2, 3, null

p1 p3
s, 2, 3, null

p1 p2 p3
s, 2, 3, null

   p1 p2 p3
s, 2, 3, null

代码

public ListNode deleteDuplicates(ListNode head) {
    if (head == null || head.next == null) {
        return head;
    }

    ListNode s = new ListNode(-1, head);
    ListNode p1 = s;
    ListNode p2;
    ListNode p3;
    while ((p2 = p1.next) != null && (p3 = p2.next) != null) {
        if (p2.val == p3.val) {
            while ((p3 = p3.next) != null 
                   && p3.val == p2.val) {
            }
            p1.next = p3;
        } else {
            p1 = p1.next;
        }
    }
    return s.next;
}
E06. 合并有序链表-Leetcode 21

输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]
    
输入:l1 = [], l2 = []
输出:[]

输入:l1 = [], l2 = [0]
输出:[0]

方法1

  • 谁小,把谁链给 p,p 和小的都向后平移一位
  • 当 p1、p2 有一个为 null,退出循环,把不为 null 的链给 p
p1
1	3	8	9	null

p2
2	4	null

p		
s	null

代码

public ListNode mergeTwoLists(ListNode p1, ListNode p2) {
    ListNode s = new ListNode(-1, null);
    ListNode p = s;
    while (p1 != null && p2 != null) {
        if (p1.val < p2.val) {
            p.next = p1;
            p1 = p1.next;
        } else {
            p.next = p2;
            p2 = p2.next;
        }
        p = p.next;
    }
    if (p1 != null) {
        p.next = p1;
    }
    if (p2 != null) {
        p.next = p2;
    }
    return s.next;
}
  • 可以自行验证中后两种情况

方法2

递归函数应该返回

  • 更小的那个链表节点,并把它剩余节点与另一个链表再次递归
  • 返回之前,更新此节点的 next
mergeTwoLists(p1=[1,3,8,9], p2=[2,4]) {
    1.next=mergeTwoLists(p1=[3,8,9], p2=[2,4]) {
        2.next=mergeTwoLists(p1=[3,8,9], p2=[4]) {            
            3.next=mergeTwoLists(p1=[8,9], p2=[4]) {
                4.next=mergeTwoLists(p1=[8,9], p2=null) {
                    return [8,9]
                }
                return 4
            }
            return 3
        }
        return 2
    }
	return 1
}
E07. 合并多个有序链表-Leetcode 23

输入:lists = [[1,4,5],[1,3,4],[2,6]]
输出:[1,1,2,3,4,4,5,6]
解释:链表数组如下:
[
  1->4->5,
  1->3->4,
  2->6
]
将它们合并到一个有序链表中得到。
1->1->2->3->4->4->5->6

方法1

递归

public ListNode mergeKLists(ListNode[] lists) {
    if (lists.length == 0) {
        return null;
    }
    return split(lists, 0, lists.length - 1);
}

public ListNode split(ListNode[] lists, int i, int j) {
    System.out.println(i + " " + j);
    if (j == i) {
        return lists[i];
    }
    int m = (i + j) >>> 1;
    return mergeTwoLists(
        split(lists, i, m),
        split(lists, m + 1, j)
    );
}

还可以用优先级队列求解,这个放在后面讲

E08. 查找链表中间节点-Leetcode 876

例如

输入:[1,2,3,4,5]
输出:此列表中的结点 3 (序列化形式:[3,4,5])

输入:[1,2,3,4,5,6]
输出:此列表中的结点 4 (序列化形式:[4,5,6])
  • 偶数节点时,中间点是靠右的那个

解法:快慢指针,快指针一次走两步,慢指针一次走一步,当快指针到链表结尾时,慢指针恰好走到链表的一半

public ListNode middleNode(ListNode head) {
    ListNode p1 = head;	// 慢指针,中间点
    ListNode p2 = head;	// 快指针
    while (p2 != null && p2.next != null) {
        p1 = p1.next;
        p2 = p2.next;
        p2 = p2.next;
    }
    return p1;
}
E09. 回文链表-Leetcode 234

所谓回文指正着读、反着读,结果一样,例如

[1,2,2,1]
[1,2,3,2,1]

它们都是回文链表,不是回文的例子

[1,2,3,1]  --反过来-->  [1,3,2,1]

解法

/*
    步骤1. 找中间点
    步骤2. 中间点后半个链表反转
    步骤3. 反转后链表与原链表逐一比较
*/
public boolean isPalindrome(ListNode head) {
    ListNode middle = middle(head);
    ListNode newHead = reverse(middle);
    while (newHead != null) {
        if (newHead.val != head.val) {
            return false;
        }
        newHead = newHead.next;
        head = head.next;
    }
    return true;
}

private ListNode reverse(ListNode o1) {
    ListNode n1 = null;
    while (o1 != null) {
        ListNode o2 = o1.next;
        o1.next = n1;
        n1 = o1;
        o1 = o2;
    }
    return n1;
}

private ListNode middle(ListNode head) {
    ListNode p1 = head; // 慢
    ListNode p2 = head; // 快
    while (p2 != null && p2.next != null) {
        p1 = p1.next;
        p2 = p2.next.next;
    }
    return p1;
}

优化后解法

public boolean isPalindrome(ListNode h1) {
    if (h1 == null || h1.next == null) {
        return true;
    }
    ListNode p1 = h1; 	// 慢指针,中间点
    ListNode p2 = h1; 	// 快指针
    ListNode n1 = null;	// 新头
    ListNode o1 = h1;	// 旧头
    // 快慢指针找中间点
    while (p2 != null && p2.next != null) {
        p1 = p1.next;
        p2 = p2.next.next;

        // 反转前半部分
        o1.next = n1;
        n1 = o1;
        o1 = p1;
    }
    if (p2 != null) { // 节点数为奇数
        p1 = p1.next;
    }
    // 同步比较新头和后半部分
    while (n1 != null) {
        if (n1.val != p1.val) {
            return false;
        }
        p1 = p1.next;
        n1 = n1.next;
    }
    return true;
}
E10. 环形链表-Leetcode 141

本题以及下题,实际是 Floyd’s Tortoise and Hare Algorithm (Floyd 龟兔赛跑算法)[^15]

除了 Floyd 判环算法外,还有其它的判环算法,详见 https://en.wikipedia.org/wiki/Cycle_detection

如果链表上存在环,那么在环上以不同速度前进的两个指针必定会在某个时刻相遇。算法分为两个阶段

阶段1

  • 龟一次走一步,兔子一次走两步
  • 当兔子能走到终点时,不存在环
  • 当兔子能追上龟时,可以判断存在环

阶段2

  • 从它们第一次相遇开始,龟回到起点,兔子保持原位不变
  • 龟和兔子一次都走一步
  • 当再次相遇时,地点就是环的入口

为什么呢?

  • 设起点到入口走 a 步(本例是 7),绕环一圈长度为 b(本例是 5),
  • 那么从起点开始,走 a + 绕环 n 圈,都能找到环入口
  • 第一次相遇时
    • 兔走了 a + 绕环 n 圈(本例 2 圈) + k,k 是它们相遇距环入口位置(本例 3,不重要)
    • 龟走了 a + 绕环 n 圈(本例 0 圈) + k,当然它绕的圈数比兔少
    • 兔走的距离是龟的两倍,所以龟走的 = 兔走的 - 龟走的 = 绕环 n 圈
  • 而前面分析过,如果走 a + 绕环 n 圈,都能找到环入口,因此从相遇点开始,再走 a 步,就是环入口

阶段1 参考代码(判断是否有环)

public boolean hasCycle(ListNode head) {
    ListNode h = head; // 兔
    ListNode t = head; // 龟
    while (h != null && h.next != null) {
        t = t.next;
        h = h.next.next;
        if(h == t){
            return true;
        }
    }
    return false;
}
E11. 环形链表-Leetcode 142

阶段2 参考代码(找到环入口)

public ListNode detectCycle(ListNode head) {
    ListNode t = head; // 龟
    ListNode h = head; // 兔
    while (h != null && h.next != null) {
        t = t.next;
        h = h.next.next;
        if (h == t) {
            t = head;
            while (true) {
                if (h == t) {
                    return h;
                }
                h = h.next;
                t = t.next;
            }
        }
    }
    return null;
}
  • 还有一道扩展题目,也可以用判环算法思想来解:就是 287 题,寻找重复数
Ex1. 删除节点-Leetcode 237

这道题目比较简单,留给大家自己练习

例如

输入:head = [4,5,1,9], node = 5
输出:[4,1,9]


输入:head = [4,5,1,9], node = 1
输出:[4,5,9]

注意:被删除的节点不是末尾节点

参考答案

public class Ex1Leetcode237 {
    /**
     *
     * @param node 待删除节点, 题目已说明肯定不是最后一个节点
     */
    public void deleteNode(ListNode node) {
        node.val = node.next.val;		// 下一个节点值赋值给待"删除"节点
        node.next = node.next.next;		// 把下一个节点删除
    }

    public static void main(String[] args) {
        ListNode o5 = new ListNode(5, null);
        ListNode o4 = new ListNode(4, o5);
        ListNode o3 = new ListNode(3, o4);
        ListNode o2 = new ListNode(2, o3);
        ListNode o1 = new ListNode(1, o2);
        System.out.println(o1);
        new E0xLeetcode237().deleteNode(o3);
        System.out.println(o1);
    }
}

输出

[1,2,3,4,5]
[1,2,4,5]
Ex2. 共尾链表-Leetcode 160

原题叫做相交链表,个人觉得用共尾链表更形象些,此题更像是一道脑筋急转弯,留给大家练习

例如,下图的两个链表 [1, 2, 4, 5] 与 [3, 4, 5] 它们中 [4, 5] 是相同的,此时应返回节点 4

非共尾的情况,如下图所示,此时返回 null

思路,称两个链表为 a=[1, 2, 4, 5],b=[3, 4, 5],图中用 N 代表 null

  1. 遍历 a,遇到 null 时改道遍历 b
  2. 与此同时,遍历 b,遇到 null 时改道遍历 a
  3. 在此过程中,如果遇到相同的节点,即为找寻目标,返回即可,如下图中的第二次出现的 4
  4. 相同节点应该比较其引用值,图中数字只是为了便于区分
1	2	4	5	N	3	4	5	N
3	4	5	N	1	2	4	5	N

如果两个链表长度相同,则可以更早找到目标,例如 a=[1, 4, 5],b=[3, 4, 5],第一次出现 4 时,即可返回

1	4	5	N	3	4	5	N
3	4	5	N	1	4	5	N

如果是非共尾的情况,如 a=[1, 2, 4],b=[3, 5],可以看到,唯一相等的情况,是遍历到最后那个 N 此时退出循环

1	2	4	N	3	5	N
3	5	N	1	2	4	N

代码

public ListNode getIntersectionNode(ListNode a, ListNode b) {
    ListNode p1 = a;
    ListNode p2 = b;
    while (true) {
        if (p1 == p2) {
            return p1;
        }
        if (p1 == null) {
            p1 = b;
        } else {
            p1 = p1.next;
        }
        if (p2 == null) {
            p2 = a;
        } else {
            p2 = p2.next;
        }            
    }
}

  结束啦,希望大家能有所成!!!

 

 你好,我是胡广。 致力于为帮助兄弟们的学习方式、面试困难、入职经验少走弯路而写博客 🌹🌹🌹 坚持每天两篇高质量文章输出,加油!!!🤩

 如果本篇文章帮到了你 不妨点个赞吧~ 我会很高兴的 😄 (^ ~ ^) 。想看更多 那就点个关注     吧 我会尽力带来有趣的内容 。

 😎感兴趣的可以先收藏起来,还有大家在毕设选题,项目以及论文编写等相关问题都可以      给我留言咨询,希望帮助更多的人

更多专栏:
📊 Java设计模式宝典:从入门到精通(持续更新)

📝 Java基础知识:GoGoGo(持续更新)

Java面试宝典:从入门到精通(持续更新)

🌟 程序员的那些事~(乐一乐)

🤩 Redis知识、及面试(持续更新)

🚀 Kafka知识文章专栏(持续更新)

🎨 Nginx知识讲解专栏(持续更新)

📡 ZooKeeper知识(持续更新)

🎯 各类神器推荐(持续更新)

🔍 工作流Activiti7——独孤九剑(持续更新)

☀️ 数据结构与算法-全是Java干货

☔️ 未完待续。。。

🐽 未完待续。。。

⚡️ 未完待续。。。

🌗 未完待续。。。

感谢订阅专栏 三连文章

评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员-杨胡广

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值