Day4 | 链表Part 2
24. 两两交换链表中的节点
暴力思路:分别对节点数=0/1,2/3和>=4时分情况讨论:
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
//如果为空或只有一个节点,则返回节点
if (head == NULL || head->next == NULL)
return head;
ListNode* node1 = head;
ListNode* node2 = node1->next;
ListNode* newHead = node2;
ListNode* tmp = node2->next;
//如果有两个或三个节点,则手动将前两个节点进行调换。
if (tmp == NULL || tmp->next == NULL){
node2->next = node1;
node1->next = tmp;
return newHead;
}
// 如果节点数大于等于四个,两两对换,如果接下来的节点数目不足两个,则停止;
while (tmp && tmp->next){
node2->next = node1;
node1->next = tmp->next;
node1 = tmp;
node2 = tmp->next;
tmp = node2->next;
}
// 当tmp或tmp->next为空时,最后两个节点还未对换,需要手动操作
node2->next = node1;
node1->next = tmp;
return newHead;
}
};
标准答案里使用哑头,做得很漂亮。这里我拿到题第一反应是将2节点取出来,让其指向1节点,再让1节点指向4节点。但是标准答案的做法相当于增加了一步,,让1节点先指向3节点,再在下一次循环的第一步让其指向4节点。为什么这样做就能将基本情况都涵盖在内?本题和反转链表的标准解答中,共同点是以某个节点为当前节点,而不是以两个节点为当前节点。具体为何还需要进一步思考。
因为交换后奇节点还需要考虑连接到后面交换好的偶节点头上,当时做题的时候也在想能不能从后向前进行操作,但觉得从后向前没有办法记住前一个节点。能记住的办法就是递归,本题递归的解法看起来思路要更加清晰一些,只不过空间复杂度就变为了O(n)。
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
if (head == nullptr || head->next == nullptr) {
return head;
}
ListNode* newHead = head->next;
head->next = swapPairs(newHead->next);
newHead->next = head;
return newHead;
}
};
19. 删除链表的倒数第N个节点
暴力解法,时间复杂度为O(2n)。
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* dummyHead = new ListNode(0);
dummyHead->next = head;
int count = 0;
ListNode* cur = dummyHead;
while (cur->next){
cur = cur->next;
count++;
}
cur = dummyHead;
while (count - n > 0)
{
cur = cur->next;
count--;
}
cur->next = cur->next->next;
return dummyHead->next;
}
};
复杂度为O(n)的解法:快慢指针,先让快指针走n+1步。
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* dummyHead = new ListNode(0);
dummyHead->next = head;
ListNode* fast = dummyHead;
ListNode* slow = dummyHead;
while (n && fast->next != NULL)
{
fast = fast->next;
n--;
}
fast = fast->next;
while (fast != NULL)
{
fast = fast->next;
slow = slow->next;
}
slow->next = slow->next->next;
return dummyHead->next;
}
};
160. 相交链表
暴力解法:
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
if (headA == NULL || headB == NULL)
return 0;
//分别获取两表的最后一个节点,并记录长度。如果不是同一地址,则不相交
ListNode* curA = headA;
ListNode* curB = headB;
int lengthA = 1;
int lengthB = 1;
while (curA->next){
curA = curA->next;
lengthA++;
}
while (curB->next){
curB = curB->next;
lengthB++;
}
if (curA != curB)
return 0;
//根据长度之差同步两表当前节点
curA = headA;
curB = headB;
int n = abs(lengthA - lengthB);
if (lengthA > lengthB){
while (n--)
curA = curA->next;
}
else{
while(n--)
curB = curB->next;
}
while(curA != curB){
curA = curA->next;
curB = curB->next;
}
return curA;
}
};
双指针做法:很巧妙,还需要日后结合下一道环形链表再品一品。
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
if (headA == nullptr || headB == nullptr) {
return nullptr;
}
ListNode *pA = headA, *pB = headB;
while (pA != pB) {
pA = pA == nullptr ? headB : pA->next;
pB = pB == nullptr ? headA : pB->next;
}
return pA;
}
};
142. 环形链表II
之前做过,后续还要更深入地思考这类题目的本质。
C++语法及特性/快捷键
- 将光标所在行上移:Alt + ↑
总结
- 对于链表的题目的暴力解法更加熟悉了;
- 从做题的思考中能稍微获得一点乐趣了。