前言
每次的刷题我都会把市面上所有的题都刷一遍,并把高度类似的题放在一起,写了自己的总结(引言部分都是我相对大家说的话和总结)大家要是想看最后总结的话可以直接看每道题的后面,方便大家食用。同时我也写了自己的经验及刷题过程的方法和解题思路。
目录
一、链表基本操作
①反转链表(头插法)
💡对于链表的反转,我们之前在学实现链表的操作的时候,我们有写过头插法的实现,不懂的可以看看我这一篇文章:https://blog.csdn.net/qq_73752061/article/details/139098232?spm=1001.2014.3001.5502 既然学过头插法,那么这题也就不难了。对于链表的反转本质,无非就是头插法的实现,因为头插法的就是就是将链表倒序排列的,所以要实现链表的反转,也就将链表倒序过来就可以实现链表的反转。
public ListNode reverseList() {
if(head == null) return null;
if(head.next == null) return head;
//cur从第二个节点开始
ListNode cur = head.next;
//第一个节点next置为空
head.next = null;
while(cur != null) {
//记录下来 当前需要翻转的节点的下一个节点
ListNode curNext = cur.next;
cur.next = head;
head = cur;
cur = curNext;
}
return head;
}
二、双指针
②找到链表的中间节点
1.暴力解法
两个for循环遍历两遍列表,第一遍先找出链表的长度,第二遍用链表的长度除以二即可找出。
❓那有什么方法可以遍历一遍就找出链表中间节点吗
💡暴力解法第一遍是遍历出了链表长度,而第二遍才找到了中间位置。那我们怎么样可以一遍遍历出呢?
2.双指针
那我们就可以模拟暴力解法,定义两个指针一个快指针,一个慢指针,让快指针先走完,并且走完时,慢指针刚好在链表的中间(一半)。
❓那么我怎么怎么样可以确定在快指针走完时,慢指针走一半呢?
💡刚好两指针路程一样,只要快指针速度是慢指针速度的两倍及每次(时间相同)快指针走两步,慢指针走一步。那么它们(快指针)停下来的位置就是(慢指针)两倍。
这个原理也是很题应用到双指针,找到特殊位置的原理。有了这一原理那么链表题都可以迎刃而解了。
public ListNode middleNode() {
ListNode fast = head;
ListNode slow = head;
while(fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
}
return slow;
}
tips:在这里插入一个比较综合的题目
综合题:链表回文
❓要判断一个链表的回文,我们可以先想到数组的回文,数组的回文是怎么做的?
💡定义一个left指针,定义一个right指针;然后left向前(++)right向后(--)然后对比每次数组的内容是否相同,而链表最大的问题,链表是单向的,是不可以倒着遍历的。
但是我们有一种方法可以把它给翻转过来(也就是前面所学的链表反转),找到链表的中间节点,然后将后面的链表翻转过来,就可以像数组一样判断回文的方式了。
public boolean chkPalindrome(ListNode head) {
// 1. 找到中间位置
ListNode fast = head;
ListNode slow = head;
while(fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
}
//2. 开始翻转
ListNode cur = slow.next;
while(cur != null) {
ListNode curNext = cur.next;//记录一下下一个节点
cur.next = slow;
slow = cur;
cur = curNext;
}
//3. 此时翻转完成,开始判断是否回文
while(head != slow) {
if(head.val != slow.val) {
return false;
}
if(head.next == slow) {
return true;
}
head = head.next;
slow = slow.next;
}
return true;
}
③求链表倒数第k个节点
1.暴力解法
仍然是遍历两边,第一遍找出链表个数,再找出倒数第k个节点。
2.双指针
🍬那么既然这样就很简单了,和上题是差不多一样的。问题就是如何找出倒数第k个节点。
💡可以先让快指针先走k-1步,这样快指针和慢指针就相差k个节点。当它们同时走的时候,快指针走完,慢指针也就到了倒数第k个位置。
public ListNode FindKthToTail(int k) {
if(k <= 0 || head == null) {
return null;
}
ListNode fast = head;
ListNode slow = head;
for (int i = 0; i < k-1; i++) {
fast = fast.next;
if(fast == null) {
return null;
}
}
while (fast.next != null) {
fast = fast.next;
slow = slow.next;
}
return slow;
}
④求链表第一个公共节点(追及相遇)
1.暴力解法
遍历两个链表的每一个节点,比较是否相同。(很显然这种方法是很不可取的)
2.优化
(从题目中找规律)
如果两个链表相交,那么相交点之后的长度是相同的。自此,我们需要做的事情是,让两个链表从同距离末尾同等距离的位置开始遍历。
因为相交后两节点长度相同,我们必须消除两个链表的长度差。
⭐️先让长的链表走差值步,然后两个链表一起同频走,两链表相遇点就是公共的节点。
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode pl = headA;
ListNode ps = headB;
int lenA = getLength(headA);
int lenB = getLength(headB);
int c = 0;
// 让较长的链表先走差值步数
if (lenA < lenB) {
c = lenB - lenA;
for (int i = 0; i < c; i++) {
ps = ps.next;
}
} else {
c = lenA - lenB;
for (int i = 0; i < c; i++) {
pl = pl.next;
}
}
// 同时遍历两个链表,直到找到交点或其中一个链表遍历完
while (pl != null && ps != null) {
if (pl == ps) {
return pl;
}
pl = pl.next;
ps = ps.next;
}
// 如果没有交点,返回null
return null;
}
public int getLength(ListNode head) {
int length = 0;
ListNode current = head;
while (current != null) {
length++;
current = current.next;
}
return length;
}
}
⑤判断链表是否有环(追及相遇)
💡假设这道题有环,有环的特点是什么?
🌸那就是追及相遇了,下面给你一个场景,你和你的男女朋友在操场跑步,你们都从宿舍出发,而男宝宝一般都会比较快一点,女宝宝都会比较慢一点,然而在一个环里快的追慢的,最终结果是不是会在环里相遇呢?(没错就是追及相遇问题)
而男宝宝就是快指针,女宝宝就慢指针。
⭐️当然这里有很重要的一点就是:不一定是快指针比慢指针快就能在环里相遇,要知道链表是一种特殊的相遇(不是插肩而过的相遇,而是要两个人一起停下来才能算相遇)
所以说有一种情况是不能相遇的:当环里有两个节点的时候,男宝宝先进入操场,女宝宝后进入操场,两个人位于不同位置,两个人的速度为3:1。既下面这种情况:
所以说最好的情况就是两人2:1的速度方式,能一步一步消除差距,从而不会出现跳过的情况。
public boolean hasCycle() {
if(head == null) return false;
ListNode fast = head;
ListNode slow = head;
while(fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
if(fast == slow) {
return true;
}
}
return false;
}
⑥求链表环内的入口(追及相遇、数学推理)
假设从头结点到环形入口节点 的节点数为x。 环形入口节点到 fast指针与slow指针相遇节点 节点数为y。 从相遇节点 再到环形入口节点节点数为 z
slow指针走过的节点数为:
x + y
, fast指针走过的节点数:x + y + n(y + z)
,n为fast指针在环内走了n圈才遇到slow指针(假设最快走一圈能遇到女宝宝,n=1),因为fast指针是一步走两个节点,slow指针一步走一个节点, 所以 fast指针走过的节点数 = slow指针走过的节点数 * 2
(时间相同,速度是两倍,距离也就是两倍)及
(x + y) * 2 = x + y + (y + z)
两边消掉一个(x+y):
x + y = (y + z)
所以来说 最终结论就是 x=z,也就是说相遇点离入口点的距离跟出发点跟入口点的距离一样。
⭐️所以只要当两者相遇的时候,把快指针放在头节点,让他们同时走,再相遇的节点就是入口节点。
public ListNode detectCycle() {
if(head == null) return null;
ListNode fast = head;
ListNode slow = head;
while(fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
if(fast == slow) {
break;
}
}
if(fast == null || fast.next == null) {
return null;
}
fast = head;
while(fast != slow) {
fast = fast.next;
slow = slow.next;
}
return fast;
}