代码随想录之链表篇
T203-移除链表元素
见LeetCode第203题[移除链表元素]
题目描述
给你一个链表的头节点 head
和一个整数 val
,请你删除链表中所有满足 Node.val == val
的节点,并返回 新的头节点 。
示例
我的思路
- 遍历链表,双指针删除
- 慢指针指向目标节点的上一个元素,并将慢指针的
next
指向当前节点的next
public ListNode removeElements(ListNode head, int val) {
ListNode h = new ListNode(); // 虚拟头结点
h.next = head;
ListNode pre = h;
ListNode cur = head;
while (cur != null) {
if (cur.val == val) {
cur = cur.next;
pre.next = cur;
} else {
cur = cur.next;
pre = pre.next;
}
}
return h.next;
}
- 时间复杂度:遍历一遍链表,时间复杂度为
O(N)
- 空间复杂度:额外空间为两个指针,空间复杂度为
O(N)
T707-设计链表
见LeetCode第707题[设计链表]
题目描述
你可以选择使用单链表或者双链表,设计并实现自己的链表。
单链表中的节点应该具备两个属性:val
和 next
。val
是当前节点的值,next
是指向下一个节点的指针/引用。
如果是双向链表,则还需要属性 prev
以指示链表中的上一个节点。假设链表中的所有节点下标从 0 开始。
实现 MyLinkedList
类:
MyLinkedList()
初始化MyLinkedList
对象。int get(int index)
获取链表中下标为index
的节点的值。如果下标无效,则返回-1
。void addAtHead(int val)
将一个值为val
的节点插入到链表中第一个元素之前。在插入完成后,新节点会成为链表的第一个节点。void addAtTail(int val)
将一个值为val
的节点追加到链表中作为链表的最后一个元素。void addAtIndex(int index, int val)
将一个值为val
的节点插入到链表中下标为index
的节点之前。如果index
等于链表的长度,那么该节点会被追加到链表的末尾。如果index
比长度更大,该节点将 不会插入 到链表中。void deleteAtIndex(int index)
如果下标有效,则删除链表中下标为index
的节点。
我的思路
- 使用单链表实现
/**
* 单链表的增删改查操作
*/
public class MyLinkedList {
public ListNode head;
public int length;
public MyLinkedList() {
head = new ListNode();
}
public int get(int index) {
if (index >= this.length) {
return -1;
}
int curIndex = 0;
ListNode p = head;
while (curIndex <= index) {
p = p.next;
curIndex++;
}
return p.val;
}
public void addAtHead(int val) {
ListNode cur = new ListNode(val);
cur.next = head.next;
head.next = cur;
length++; //长度加 1
}
public void addAtTail(int val) {
ListNode newNode = new ListNode(val);
ListNode p = head;
while (p.next != null) {
p = p.next;
}
p.next = newNode;
length++;
}
public void addAtIndex(int index, int val) {
if (index > length) return;
ListNode p = head;
ListNode newNode = new ListNode(val);
int curIndex = 0;
while (curIndex < index) {
p = p.next;
curIndex++;
}
newNode.next = p.next;
p.next = newNode;
length++;
}
public void deleteAtIndex(int index) {
if (index >= length) return;
ListNode p = head;
int curIndex = 0;
while (curIndex < index) {
p = p.next;
curIndex++;
}
p.next = p.next.next;
this.length--;
}
}
T206-反转链表
见LeetCode第206题[反转链表]
题目描述
给你单链表的头节点 head
,请你反转链表,并返回反转后的链表。
我的思路
- 遍历链表中的每个节点,使用头插法反转链表,头插法需要三个额外变量,分别指向
pre | cur | next
- 虚拟头结点
h -> head
- 对于当前节点
p
ListNode temp = p.next
,保存p
的下一个节点p.next = h.next
,将p
指向虚拟头结点的下一个节点h.next = p
,头结点的next
指针指向p
p -> temp
,p
指向temp
- 循环条件为
p != null
if (head == null || head.next == null) return head;
ListNode h = new ListNode();
h.next = head;
ListNode p = head.next;
ListNode pre = head; // 为了确保从原始链表将节点抠出来的时候,没有指针指向 cur
while (p != null) {
ListNode temp = p.next;
pre.next = temp;
p.next = h.next;
h.next = p;
p = temp;
}
return h.next;
- 时间复杂度:遍历一遍链表,
O(N)
- 空间复杂度:额外变量
pre | cur | nextNode
,空间复杂度为O(1)
T24-两两交换链表中的节点
见LeetCode第24题[两两交换链表中的节点]
题目描述
给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。
我的思路
- 如果想要交换两个节点的位置,需要4个临时变量,即
preNode | target1 | target2 | nextNode
- 具体的交换操作为:
target1.next = nextNode
target2.next = target1
preNode.next = target2
- 交换完毕之后需要的更新操作:
preNode = target2
target1 = nextNode
:非空target2 = nextNode.next
:非空nextNode = nextNode.next.next;
:
/**
* 两两交换链表中的节点
* @param head
* @return
*/
public ListNode swapPairs(ListNode head) {
if (head == null || head.next == null) return head;
ListNode h = new ListNode();
h.next = head;
ListNode pre = h;
ListNode target1 = head;
ListNode target2 = target1.next;
ListNode nextNode = target2.next;
while (true) {
target1.next = nextNode;
target2.next = target1;
pre.next = target2;
// 变量更新
pre = target1;
if (nextNode == null || nextNode.next == null) break;
target1 = nextNode;
target2 = target1.next;
nextNode = target2.next;
}
return h.next;
}
- 时间复杂度:
O(N)
,整体上遍历了链表中的每一个节点- 空间复杂度:
O(1)
,使用了4个临时指针帮助交换节点
T19-删除链表的倒数第N个节点
见LeetCode第19题[删除链表的倒数第N个节点]
题目描述
给你一个链表,删除链表的倒数第 n
个结点,并且返回链表的头结点。
我的思路
- 虚拟头结点 + 快慢双指针
- 快指针先走
n
步,然后快慢指针一起走 - 快指针到达最后一个节点,即
fast.next == null
,此时慢指针到达目标节点的前一个节点pre
- 直接将慢指针的
next
指向slow.next.next
即可
/**
* 删除倒数第 n 个节点
* @param head
* @param n
* @return
*/
public ListNode removeNthFromEnd(ListNode head, int n) {
// 虚拟头结点
ListNode h = new ListNode();
h.next = head;
// 快慢指针初始化,指向虚拟头结点
ListNode fast = h;
ListNode slow = h;
// 快指针先行 n 步
for (int i = 0; i < n; i++) {
fast = fast.next;
}
// 快指针到达最后一个节点,慢指针指向目标节点的 前驱节点
while (fast.next != null) {
fast = fast.next;
slow = slow.next;
}
// 将 目标节点 即 slow.next 移除
slow.next = slow.next.next;
return h.next;
}
- 时间复杂度:
O(N)
,快指针遍历了链表中的每一个节点- 空间复杂度:
O(1)
,额外空间为快慢两个指针
T160-相交链表
见LeetCode第160题[相交链表]
题目描述
给你两个单链表的头节点 headA
和 headB
,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null
。
图示两个链表在节点 c1
开始相交**:**
我的思路
- 首先遍历计算出两个链表的长度为
lenA | lenB
- 循环
lenA + lenB
次,如果两个链表有交点,则在循环中一定会相交 - 如果执行到循环结束还找不到
pA == pB
,直接返回fasle
/**
* 链表的相交节点
* @param headA
* @param headB
* @return
*/
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
if (headA == null || headB == null) return null;
// 计算两个链表的长度
int lenA = 0;
ListNode pA = headA;
while (pA != null) {
lenA++;
pA = pA.next;
}
pA = headA;
int lenB = 0;
ListNode pB = headB;
while (pB != null) {
lenB++;
pB = pB.next;
}
pB = headB;
// 循环路程为 A B 两个链表长度之和
int count = 0;
while (count < lenA + lenB) {
if (pA == pB) return pA;
pA = pA.next == null ? headB : pA.next;
pB = pB.next == null ? headA : pB.next;
count++;
}
return null;
}
T142-环形链表II
见LeetCode第142题[环形链表II].
题目描述
给定一个链表的头节点 head
,返回链表开始入环的第一个节点。 如果链表无环,则返回 null
。
如果链表中有某个节点,可以通过连续跟踪 next
指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos
来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos
是 -1
,则在该链表中没有环。注意:pos
不作为参数进行传递,仅仅是为了标识链表的实际情况。
不允许修改 链表。
输入:head = [3,2,0,-4], pos = 1
输出:返回索引为 1 的链表节点
解释:链表中有一个环,其尾部连接到第二个节点。
我的思路
- 这是一道追及问题
|----------------|----------|------------------|
A x B y C z D
假设如下:
- A: 起点 B: 链表中的环节点 C: 快慢指针相遇的节点 D: 终点
- 他们之间的距离分别使用:``x y z` 表示
- 快指针的一次走两步,速度是慢指针的2倍,那么相遇的时候,慢指针的路程
s_slow = 1/2 s_fast
根据上述信息我们可以列出如下方程:
经过简单的化简之后可得:
也就是起点到环节点的距离等于相遇节点到尾结点的距离。
因此,相遇的时候跳出循环,我们将慢指针拨回起点,快指针往前走一步, 快指针和慢指针以相同的速度前进。快慢指针再次相遇的时候就到达了环节点。
- 初始化:
fast = head.next; slow = head;
- 循环条件:
fast != null && fast.next != null
/**
* 返回链表中的环节点
* @param head
* @return
*/
public ListNode detectCycle(ListNode head) {
if (head == null || head.next == null) return null;
ListNode fast = head.next;
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;
// 慢指针回拨,快指针降速
slow = head;
fast = fast.next;
while (fast != slow) {
slow = slow.next;
fast = fast.next;
}
return slow;
}
- 时间复杂度:如果有环的话,遍历了不到两遍链表;而无环情况则遍历了一遍链表。所以复杂度为
O(N)
- 空间复杂度:额外空间存储快慢指针,空间复杂度为
O(1)