第四天继续刷题,周六出差回来没空做题,周日补上。经过昨天训练链表的基本操作,两两交换链表中的节点和删除链表的倒数第N个节点这两题涉及模拟链表操作的题已经能比较熟悉地做出来,链表相交这题主要是把思路弄清楚就好,环形链表II先用哈希法做出来了,再学会快慢指针降低空间复杂度。
24. 两两交换链表中的节点
解题过程
- 一开始在想要不要设置虚拟头节点,后面想通了,一是为了最后的返回值方便,返回
dummyHead->next
就行,否则还挺复杂的,二是可以统一头节点和非头节点的写法,更简洁 - 之后就可以开始模拟两两交换节点了,交换节点需要两个节点都不是空指针
知识点
- 时间复杂度:O(n)
- 空间复杂度:O(1)
两两交换节点
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
ListNode* dummyHead = new ListNode();
dummyHead->next = head;
ListNode* pre = dummyHead;
ListNode* cur = head;
ListNode* tmp;
while (cur && cur->next) {
tmp = cur->next->next;
pre->next = cur->next;
cur->next->next = cur;
cur->next = tmp;
pre = cur;
cur = tmp;
}
head = dummyHead->next;
delete dummyHead;
return head;
}
};
19. 删除链表的倒数第 N 个结点
解题过程
- 使用虚拟头结点,方便处理头结点被删的情况
- 使用双指针,在一个循环扫描内实现删除
知识点
- 时间复杂度: O(n)
- 空间复杂度: O(1)
删除倒数第N个结点
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* dummyHead = new ListNode();
dummyHead->next = head;
ListNode* pre = dummyHead;
ListNode* cur = dummyHead;
while (n--) cur = cur->next;
while (cur->next) {
pre = pre->next;
cur = cur->next;
}
ListNode* tmp = pre->next;
pre->next = pre->next->next;
delete tmp;
head = dummyHead->next;
delete dummyHead;
return head;
}
};
160.链表相交
解题过程
- 求出两个链表的长度,并求出两个链表长度的差值,然后让长的链表指针移动,和短的链表末尾对齐的位置
知识点、
- 时间复杂度:O(n + m)
- 空间复杂度:O(1)
链表相交
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
int sizeA = 0;
int sizeB = 0;
ListNode* curA = headA;
ListNode* curB = headB;
while (curA) {
sizeA++;
curA = curA->next;
}
while (curB) {
sizeB++;
curB = curB->next;
}
curA = headA;
curB = headB;
if (sizeA > sizeB) {
for (int i = 0; i < sizeA - sizeB; i++) curA = curA->next;
}
if (sizeA < sizeB) {
for (int i = 0; i < sizeB - sizeA; i++) curB = curB->next;
}
while (curA) {
if (curA == curB) return curA;
curA = curA->next;
curB = curB->next;
}
return nullptr;
}
};
142.环形链表Ⅱ
解题过程
- 看完题就想到用哈希表来记录经过的节点,经过重复的节点就意味着进入了环形的开始节点,但这样空间复杂度就高了
- 随想录用快慢指针做的,没想出来,原理很巧妙,快指针一次移动两步,慢指针一次移动一步,若有环形链表则必在环内相遇,然后利用数学关系可以推测出环的入口
知识点
-
哈希法时间复杂度O(n),空间复杂度O(n)
-
双指针法法时间复杂度: O(n),快慢指针相遇前,指针走的次数小于链表长度,快慢指针相遇后,两个index指针走的次数也小于链表长度,总体为走的次数小于 2n 空间复杂度: O(1)
哈希法
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
ListNode* cur = head;
unordered_map<ListNode*, int>umap;
while (cur) {
umap[cur]++;
if (umap[cur] > 1) return cur;
cur = cur->next;
}
return nullptr;
}
};
双指针法
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
ListNode* fast = head;
ListNode* slow = head;
while (fast && fast->next) {
fast = fast->next;
fast = fast->next;
slow = slow->next;
if (fast == slow) {
ListNode* index1 = slow;
ListNode* index2 = head;
while (index1 != index2) {
index1 = index1->next;
index2 = index2->next;
}
return index1;
}
}
return nullptr;
}
};
链表总结
链表的理论基础
- 链表的种类主要为:单链表,双链表,循环链表
- 单链表是一种通过指针串联在一起的线性结构,每一个节点由两部分组成,一个是数据域一个是指针域(存放指向下一个节点的指针),最后一个节点的指针域指向null(空指针的意思)。链表的入口节点称为链表的头结点也就是head。
- 单链表中的指针域只能指向节点的下一个节点。双链表每一个节点有两个指针域,一个指向下一个节点,一个指向上一个节点。双链表既可以向前查询也可以向后查询。
- 循环链表,顾名思义,就是链表首尾相连。循环链表可以用来解决约瑟夫环问题。
- 链表的存储方式:链表的节点在内存中是分散存储的,通过指针连在一起。
- 链表进行增删改查的方法
- 链表插入元素复杂度为O(n),查找元素复杂度为O(1),与数组相反。
- 数组和链表在不同场景下的性能分析。
链表经典题目
- 虚拟头结点
- 203.移除链表元素
- 链表的一大问题就是操作当前节点必须要找前一个节点才能操作。这就造成了,头结点的尴尬,因为头结点没有前一个节点了。
- 每次对应头结点的情况都要单独处理,所以使用虚拟头结点的技巧,就可以解决这个问题。
-
链表的基本操作
-
- 707.设计链表
- 这是练习链表基础操作的非常好的一道题目,考察了:
- 获取链表第index个节点的数值
- 在链表的最前面插入一个节点
- 在链表的最后面插入一个节点
- 在链表第index个节点前面插入一个节点
- 删除链表的第index个节点的数值
- 注意这题可以定义一个链表节点结构体或类
- 反转链表
- 206.翻转链表
- 迭代法和递归法均可做出,主要弄清楚模拟翻转的步骤
- 删除倒数第N个节点
- 19.删除链表的倒数第N个节点
- 结合虚拟头结点 和 双指针法来移除链表倒数第N个节点。
- 链表相交
- 160.链表相交
- 使用双指针来找到两个链表的交点
- 环形链表
- 142.环形链表II
- 使用双指针(快慢指针)来寻找环的入口,数学关系可以自己推一下。