今天,带来链表相关算法的讲解。文中不足错漏之处望请斧正!
理论基础点这里
反转链表
1. 思路
链表操作的本质是修改连接关系,本题我们需要反转链表,也就是每次都让当前节点的next指向自己的上一个。而题目给的是单链表,所以我们需要始终记录前一个节点的地址,来满足当前节点被反转的条件;其次,我们当前节点迭代时要按原本顺序迭代。
2. 参考代码
class Solution6 {
public:
ListNode *reverseList(ListNode *head) {
// 每次都让当前节点的next指向前一个节点, 迭代时仍然向后迭代
ListNode *prev = nullptr;
ListNode *cur = head;
ListNode *next = nullptr;
while (cur != nullptr) {
next = cur->next; // 保存, 等会要覆盖
cur->next = prev; // 改变链接关系:让当前节点的next指向前一个节点
prev = cur;
cur = next; // 迭代时仍然向后迭代
}
return prev; // 当cur走到空, prev正好是最后一个节点, 即新的反转链表的头节点
}
};
两两交换链表节点
1. 思路
像这种需要改变链接关系的场景,我们都可以给上一个虚拟头节点,来保证改变链接关系的逻辑相同。
具体如何交换呢?
1. 要能找得到
《移除链表元素》中,我们已经谈过,要操作节点1和节点2,我们必须要能找得到节点1和节点2。
2. 统一操作逻辑
对单链表来说,当我们要从头开始操作的时候,逻辑和正常的交换不一样,所以我们选择加入虚拟头结点,来统一交换逻辑。操作逻辑是什么呢?如图。
3. 何时结束交换?
我们给一个cur,一开始指向dummyHead,它来操作后两个节点。
前提是后面还有两个节点可以操作,所以当cur->next != nullptr && cur->next->next != nullptr
的时候我们可以进行操作,反之结束。
这里是有细节的:
- cur不可能为空,因为我们给他初始化为dummyHead
- cur→next的判断必须放在前面,因为表达式是从左到右判断,如果cur→next→next的判断放在前面,当cur→next==nullptr,对cur→next→next的判断就对空指针解引用了
4. 改变链接需谨慎
修改节点间的链接关系,很容易丢失节点或陷入死循环。
如上图,要交换1和2,操作应该是:
- cur指向2
- 2指向1
- 1指向3
对应代码就是
cur->next = cur->next->next
- …
这里其实已经出问题了!cur->next
被修改,代表1是丢失了。后续我们还需要让1指向3,找不到1了就没法玩。
同理,直接让2指向1以后,3也丢失了,1和3都找不到了。怎么办?
我们可以提前保存,直接把1、2、3保存为node1、node2、node3。
也可以不保存节点2,因为cur指向2后,2是可以找到的,但我们这样写可读性高点。
最后,cur应该怎么迭代呢?
应该移动两位。
2. 参考代码
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
ListNode *dummy = new ListNode(-1, head);
ListNode *cur = dummy;
while (cur->next && cur->next->next) { // 后面还有两个节点才能交换
ListNode *node1 = cur->next;
ListNode *node2 = cur->next->next;
ListNode *node3 = cur->next->next->next;
cur->next = node2; // cur->node2
node2->next = node1; // node2->node1
node1->next = node3; // node1->node3
cur = cur->next->next; // 移动2位
}
head = dummy->next;
delete dummy;
dummy = nullptr;
return head;
}
};
今天的分享就到这里了,感谢您能看到这里。
这里是培根的blog,期待与你共同进步!