本文为个人学习总结,无任何商业用途,若涉及侵权,联系删除。
链表基础知识
- 数组是在内存中是连续分布的,但是链表在内存中不是连续分布的。
- 单链表的构造函数:
// 单链表
struct ListNode {
int val; // 节点上存储的元素
ListNode *next; // 指向下一个节点的指针
ListNode(int x) : val(x), next(NULL) {} // 节点的构造函数
};
class ListNode:
def __init__(self, val, next=None):
self.val = val
self.next = next
- 链表的增添和删除都是O(1)操作,也不会影响到其他节点。
一、移除链表元素
注意事项:
- c/c++ 移除链表节点记得释放移除节点的内存
- 设置一个虚拟节点便于遍历移除操作
例题: 删除链表的倒数第N个节点, 移除链表元素
二、翻转链表
例题:反转链表
// 双指针法
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode* temp; // 保存cur的下一个节点
ListNode* cur = head;
ListNode* pre = NULL;
while(cur) {
temp = cur->next; // 保存一下 cur的下一个节点,因为接下来要改变cur->next
cur->next = pre; // 翻转操作
// 更新pre 和 cur指针
pre = cur;
cur = temp;
}
return pre;
}
};
//递归法
class Solution {
public:
ListNode* reverse(ListNode* pre,ListNode* cur){
if(cur == NULL) return pre;
ListNode* temp = cur->next;
cur->next = pre;
// 可以和双指针法的代码进行对比,如下递归的写法,其实就是做了这两步
// pre = cur;
// cur = temp;
return reverse(cur,temp);
}
ListNode* reverseList(ListNode* head) {
// 和双指针法初始化是一样的逻辑
// ListNode* cur = head;
// ListNode* pre = NULL;
return reverse(NULL, head);
}
};
三、链表相交
思路:双指针指向两链表头,分别遍历两次链表,节点相同时为相交节点
例题: 两个链表是否相交
ListNode getIntersectionNode(ListNode headA, ListNode headB) {
// p1 指向 A 链表头结点,p2 指向 B 链表头结点
ListNode p1 = headA, p2 = headB;
while (p1 != p2) { // 遍历A与B结束后还没有找到相同节点,两指针都指向空节点,退出循环。
// p1 走一步,如果走到 A 链表末尾,转到 B 链表
if (p1 == null) p1 = headB;
else p1 = p1.next;
// p2 走一步,如果走到 B 链表末尾,转到 A 链表
if (p2 == null) p2 = headA;
else p2 = p2.next;
}
return p1;
}
四、环形链
思路:快慢指针,快指针速度时慢指针的两倍,若相交,则有环;相交后,一个指针回到原点,再相同速度直到相交为环入口。
例题:环形链表I 、环形链表II
ListNode detectCycle(ListNode head) {
ListNode fast, slow;
fast = slow = head;
while (fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
if (fast == slow) break;
}
// 上面的代码类似 hasCycle 函数
if (fast == null || fast.next == null) {
// fast 遇到空指针说明没有环
return null;
}
// 重新指向头结点
slow = head;
// 快慢指针同步前进,相交点就是环起点
while (slow != fast) {
fast = fast.next;
slow = slow.next;
}
return slow;
}