代码随想录算法训练营第四天| 24. 两两交换链表中的节点、19. 删除链表的倒数第 N 个结点、面试题 02.07. 链表相交、42. 环形链表 II

代码随想录算法训练营第四天| 24. 两两交换链表中的节点、19. 删除链表的倒数第 N 个结点、面试题 02.07. 链表相交、42. 环形链表 II

文章目录

  • 代码随想录算法训练营第四天| 24. 两两交换链表中的节点、19. 删除链表的倒数第 N 个结点、面试题 02.07. 链表相交、42. 环形链表 II
    • 1. [24. 两两交换链表中的节点](https://leetcode.cn/problems/swap-nodes-in-pairs/)
      • 1.1 自己看到题目的第一想法
      • 1.2 看完代码随想录之后的想法
      • 1.3 自己实现过程中遇到哪些困难
      • 1.4 完整代码
    • 2. [19. 删除链表的倒数第 N 个结点](https://leetcode.cn/problems/remove-nth-node-from-end-of-list/)
      • 2.1 自己看到题目的第一想法
      • 2.2 看完代码随想录之后的想法
      • 2.3 自己实现过程中遇到哪些困难
      • 2.4 完整代码
    • 3. [面试题 02.07. 链表相交](https://leetcode.cn/problems/intersection-of-two-linked-lists-lcci/)
      • 3.1 自己看到题目的第一想法
      • 3.2 看完代码随想录之后的想法
      • 3.3 自己实现过程中遇到哪些困难(总结)
      • 3.4 完整代码
    • 4. [142. 环形链表 II](https://leetcode.cn/problems/linked-list-cycle-ii/)
      • 4.1 自己看到题目的第一想法
      • 4.2 看完代码随想录之后的想法
      • 4.3 自己实现过程中遇到哪些困难(总结)
      • 4.4 完整代码
    • 其他问题
      • NULL和nullptr的区别

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

1.1 自己看到题目的第一想法

两两交换,奇数怎么处理呢?最后一个不交换?

1.2 看完代码随想录之后的想法

1.3 自己实现过程中遇到哪些困难

  1. 最后返回dummyHead->next,而不是返回head; 因为交换之后,head不再是链表的头结点了
  2. 不需要删除循环中申请的tmp指针,因为tmp和tmp1没有开辟新的空间。随着循环进行结束,tmp和tmp1根据链表的长度为奇数还是偶数,指向不同的地方。
    若长度为奇数,tmp1指向最后一个节点,tmp指向倒数第二个节点
    若长度为偶数,tmp指向最后一个节点,tmp1指向nullptr

1.4 完整代码

/**
 * 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) {
    //在这段代码中,Solution 类中的 swapPairs 函数被声明为返回 ListNode* 类型的指针。函数返回类型的 * 符号表示返回一个指向 ListNode 类型对象的指针。
        ListNode* dummyHead = new ListNode(0);
        dummyHead->next = head;
        ListNode* cur = dummyHead;

        while ((cur->next != nullptr) && (cur->next->next != nullptr)) {
            ListNode* tmp = cur->next;
            ListNode* tmp1 = cur->next->next->next;
            cur->next = cur->next->next;
            cur->next->next = tmp;
            cur->next->next->next = tmp1;
            cur = cur->next->next;
        }
        // delete dummyHead;

        return dummyHead->next;


    }
};

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

2.1 自己看到题目的第一想法

遍历求长度、删除第len-n+1个,分删除的是首节点和非首节点两类
添加虚拟头结点可以改进,不用分类

/**
 * 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);
        dummyHead->next = head; 
        // 遍历求长度
        ListNode* cur = dummyHead;
        int len =0;
        while (cur) {
            cur = cur->next;
            len++;
        }
        int number = len - n + 1;   // 倒数第n个为整数第len-n+2个     

        // 删除第number个.
        ListNode* cur2 = dummyHead;
        for (int i = 1; i < number - 1; i++) {
            cur2 = cur2->next;  // 指向需要删除的前1项
        }
        ListNode* tmp = cur2->next;
        cur2->next = tmp->next;

        return dummyHead->next;

    }
    
};

2.2 看完代码随想录之后的想法

2.3 自己实现过程中遇到哪些困难

  • 注意删除首节点的特殊情况,不能删除dummyHead,应该返回dummyHead->next,而不是返回head,因为head有可能已经被删除了

2.4 完整代码

/**
 * 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);
        dummyHead->next = head;
        ListNode* fast = dummyHead;
        ListNode* slow = dummyHead;
        for (int i = 0; i < n + 1; i++) {
            fast = fast->next;
        }
        while (fast) {
            fast = fast->next;
            slow = slow->next;
        }
        ListNode* tmp = slow->next;
        slow->next = tmp->next;
        delete tmp;
        // delete dummyHead;
        return dummyHead->next;



        // 自己的方法:直接法
        // // 添加虚拟头结点
        // ListNode* dummyHead = new ListNode(0);
        // dummyHead->next = head; 
        // // 遍历求长度
        // ListNode* cur = dummyHead;
        // int len =0;
        // while (cur) {
        //     cur = cur->next;
        //     len++;
        // }
        // int number = len - n + 1;   // 倒数第n个为整数第len-n+2个     

        // // 删除第number个.
        // ListNode* cur2 = dummyHead;
        // for (int i = 1; i < number - 1; i++) {
        //     cur2 = cur2->next;  // 指向需要删除的前1项
        // }
        // ListNode* tmp = cur2->next;
        // cur2->next = tmp->next;

        // return dummyHead->next;

    }
    
};

3. 面试题 02.07. 链表相交

3.1 自己看到题目的第一想法

暴力法,双层循环,判断两个链表每个节点的下一个节点,是否相同,不大可行,复杂度太高

3.2 看完代码随想录之后的想法

3.3 自己实现过程中遇到哪些困难(总结)

怎么判断哪个链表长,哪个链表短?除了分两类讨论还有别的办法嘛?
答:swap交换头结点,交换链表长度,始终让A链表长度大于等于B链表
求完长度之后应该将curA重新指向链表头

3.4 完整代码

/**
 * 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) {
        ListNode* curA = headA;
        ListNode* curB = headB;
        int lenA = 0;
        int lenB = 0;
        while (curA) {
            curA = curA->next;
            lenA++;
        }
        while (curB) {
            curB = curB->next;
            lenB++;
        }
        curA = headA;       // 求完长度之后应该将curA重新指向链表头
        curB = headB;
        if (lenB > lenA) {
            swap (lenA, lenB);
            swap (curA, curB);
        }
        int subLen = lenA - lenB;
        while (subLen--) {
            curA = curA->next;
        }
        while (curA) {
            if (curA == curB) {
                return curA;
            }
            curA = curA->next;
            curB = curB->next;
        }
        return NULL;
        
    }
};

4. 142. 环形链表 II

4.1 自己看到题目的第一想法

没想法

4.2 看完代码随想录之后的想法

本质上是一个数学问题,画图解出来答案之后,代码就很好写了

4.3 自己实现过程中遇到哪些困难(总结)

不能为了图省事,将本来用于循环的指针另作他用

4.4 完整代码

/**
 * 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) {
        ListNode* fast = head;
        ListNode* slow = head;
        while (fast != nullptr && fast->next != nullptr) {
            fast = fast->next->next;
            slow = slow->next;
            if (fast == slow) {
                ListNode* index1 = head;    // 这里需重新定义两个节点,如果重新使用fast和slow,next执行一次之后就跳出循环了
                ListNode* index2 = fast;
                index1 = index1->next;
                index2 = index2->next;
                if (index1 == index2) {
                    return index1;
                }
            } 

        }
        return NULL;
        
    }
};

其他问题

NULL和nullptr的区别

在C++中,NULL和nullptr都用于表示空指针,但它们在语义上有一些区别。

NULL是一个宏定义,通常被定义为整数0。在较早的C++版本中,NULL被用作表示空指针的常量。然而,NULL的使用存在一些问题,因为它可以被隐式地转换为整数类型,这可能导致一些不明确或错误的行为。

nullptr是C++11引入的新关键字,专门用于表示空指针。nullptr是一个特殊的字面量,不会被隐式转换为其他类型,因此可以更安全地用于指针比较和函数重载等场景。使用nullptr可以避免一些与NULL相关的潜在问题。

下面是一个简单的示例,说明了NULL和nullptr之间的区别:

#include 

void foo(int i) {
    std::cout << "foo(int)" << std::endl;
}

void foo(char* ptr) {
    std::cout << "foo(char*)" << std::endl;
}

int main() {
    foo(NULL);     // 输出 "foo(int)",存在二义性,编译器可能会选择不同的重载版本
    foo(nullptr);  // 输出 "foo(char*)",nullptr被精确匹配到foo(char*)重载版本
    return 0;
}

在上述示例中,使用NULL作为参数调用foo函数会导致二义性,因为NULL可以被隐式转换为整数类型,因此foo(int)和foo(char*)两个重载版本都是可行的。而使用nullptr作为参数调用foo函数,nullptr只能匹配到foo(char*)重载版本,因为nullptr类型为nullptr_t,只能精确匹配指针类型。

总之,为了避免潜在的问题和提供更好的类型安全性,建议在C++中使用nullptr代替NULL来表示空指针。

你可能感兴趣的:(代码随想录,算法,链表,数据结构)