代码随想录训练营第四天

代码随想录: 404 | 代码随想录

24. 两两交换链表中的节点

交换过程中的步骤有点复杂, 要结合示意图想清楚

代码随想录链接: https://programmercarl.com/0024.两两交换链表中的节点.html#思路

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        ListNode* dummyHead = new ListNode(0, head);
        auto cur = dummyHead;
        while (cur->next != nullptr && cur->next->next != nullptr) {
            auto temp0 = cur->next;
            auto temp1 = cur->next->next->next;
            cur->next = cur->next->next;
            cur->next->next = temp0;
            cur->next->next->next = temp1;
            cur = cur->next->next;
        }
        head = dummyHead->next;
        delete dummyHead;
        return head;
    }
};

过程中产生了疑问:

dummyHead->next在循环中不会被改变吗, 为什么最后可以返回dummyHead->next, 于是我询问了Claude, 问题解决, 感觉应该能想到原因的, 神智有点不清了. 以下为Claude sonnet3.5的答案:

你提出了一个很好的问题。让我详细解释一下为什么 dummyHead->next 在循环中会改变,但最终仍然可以正确地返回反转后的链表头:

  1. dummyHead->next 确实会在循环的第一次迭代中被改变。但这个改变恰恰是我们想要的,因为它会指向新的链表头。
  2. 让我们一步步看循环的第一次迭代:
    • 初始状态:dummyHead -> 1 -> 2 -> 3 -> 4
    • 交换后: dummyHead -> 2 -> 1 -> 3 -> 4
  3. 在这个过程中,dummyHead->next 从指向节点1变成了指向节点2。这正是我们想要的结果,因为在交换对之后,节点2应该成为新的链表头。
  4. 在随后的迭代中,cur 会向前移动,但 dummyHead->next 不会再改变,因为我们始终在 cur 之后的节点上操作。
  5. 循环结束后,链表可能看起来像这样(假设有偶数个节点): dummyHead -> 2 -> 1 -> 4 -> 3 -> null
  6. 因此,当我们最后返回 dummyHead->next 时,它正好指向反转后的链表的新头部(在这个例子中是节点2)。

总结:

  • dummyHead->next 在第一次迭代中确实会改变,这个改变使它指向新的链表头。
  • 在后续迭代中,dummyHead->next 保持不变,而我们通过移动 cur 来处理链表的其余部分。
  • 最终,dummyHead->next 指向的正是我们想要的新链表头,所以可以直接返回它。

这种使用虚拟头节点(dummy head)的技巧非常有用,尤其是在需要修改链表头的情况下,它简化了边界情况的处理。

19. 删除链表的倒数第 N 个结点

一次遍历的重点是想到让快指针先走n步

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode *dummyHead = new ListNode(0, head);
        auto slow = dummyHead, fast = dummyHead;
        while (n-- && fast != nullptr) {
            fast = fast->next;
        }
        fast = fast->next;
        while (fast != nullptr) {
            fast = fast->next;
            slow = slow->next;
        }
        auto temp = slow->next;
        slow->next = slow->next->next;
        delete temp;
        head = dummyHead->next;
        delete dummyHead;
        return head;
    }
};

面试题 02.07. 链表相交

. - 力扣(LeetCode)

代码随想录比较容易理解的方法(但其实也不容易):

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* getIntersectionNode(ListNode* headA, ListNode* headB) {
        auto cur1 = headA;
        auto cur2 = headB;
        int lengthA = 0, lengthB = 0;
        while (cur1 != NULL) {
            lengthA++;
            cur1 = cur1->next;
        }
        while (cur2 != NULL) {
            lengthB++;
            cur2 = cur2->next;
        }
        cur1 = headA;
        cur2 = headB;
        if (lengthA < lengthB) {
            swap(cur1, cur2);
            swap(lengthA, lengthB);
        }
        int gap = lengthA - lengthB;
        while (gap--) {
            cur1 = cur1->next;
        }
        while (cur1 != NULL && cur2 != NULL) {
            if (cur1 == cur2) {
                return cur1;
            } else {
                cur1 = cur1->next;
                cur2 = cur2->next;
            }
        }
        return NULL;
    }
};

好聪明的解法, 我一辈子也想不到了:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        auto h1 = headA, h2 = headB;
        while (h1 != h2) {
            if (h1 != NULL) {
                h1 = h1->next;
            } else {
                h1 = headB;
            }
            if (h2 != NULL) {
                h2 = h2->next;
            } else {
                h2 = headA;
            }
        }
        return h1;
    }
};

解题思路(k神的题解): . - 力扣(LeetCode)

142. 环形链表 II

数学方面比较难理解, 看代码随想录吧: https://programmercarl.com/0142.环形链表II.html#思路

快指针每次移动两个节点, 慢指针每次移动一个节点, 如果有环, 那么他们一定会在环里相遇

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        auto fast = head;
        auto slow = head;
        while (fast != NULL && fast->next != NULL) {
            fast = fast->next->next;
            slow = slow->next;
            if (fast == slow) {
                auto index1 = fast;
                auto index2 = head;
                while (index1 != index2) {
                    index1 = index1->next;
                    index2 = index2->next;
                }
                return index1;
            }
        }
        return NULL;
    }
};

总结

如果涉及到增删改就使用虚拟头节点, 是为了

  1. 不用特判头节点的情况
  2. 头节点可能会被改变, 方便最后返回dummyHead→next

  • 5
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
代码随想录算法训练营是一个优质的学习和讨论平台,提供了丰富的算法训练内容和讨论交流机会。在训练营中,学员们可以通过观看视频讲解来学习算法知识,并根据讲解内容进行刷题练习。此外,训练营还提供了刷题建议,例如先看视频、了解自己所使用的编程语言、使用日志等方法来提高刷题效果和语言掌握程度。 训练营中的讨论内容非常丰富,涵盖了各种算法知识点和解题方法。例如,在第14天的训练营中,讲解了二叉树的理论基础、递归遍历、迭代遍历和统一遍历的内容。此外,在讨论中还分享了相关的博客文章和配图,帮助学员更好地理解和掌握二叉树的遍历方法。 训练营还提供了每日的讨论知识点,例如在第15天的讨论中,介绍了层序遍历的方法和使用队列来模拟一层一层遍历的效果。在第16天的讨论中,重点讨论了如何进行调试(debug)的方法,认为掌握调试技巧可以帮助学员更好地解决问题和写出正确的算法代码。 总之,代码随想录算法训练营是一个提供优质学习和讨论环境的平台,可以帮助学员系统地学习算法知识,并提供了丰富的讨论内容和刷题建议来提高算法编程能力。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [代码随想录算法训练营每日精华](https://blog.csdn.net/weixin_38556197/article/details/128462133)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值