【代码随想录算法训练营第四天】 | 24. 两两交换链表中的节点、707.设计链表、206.反转链表、142.环形链表II

代码随想录算法训练营第四天 | 24. 两两交换链表中的节点、707.设计链表、206.反转链表、142.环形链表II

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

思路
  • 使用正常思路即可,记得用虚拟头节点可以让算法更简单,思路如下:

    在这里插入图片描述

/**
 * 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);
        dummyHead->next = head;
        ListNode *cur = dummyHead;
        while(cur->next != nullptr && cur->next->next != nullptr) {
            ListNode *tmp1 = cur->next;
            ListNode *tmp2 = cur->next->next->next;
            //步骤一
            cur->next = cur->next->next;
            //步骤二
            cur->next->next = tmp1;
            //步骤三,这一步是指向下一组的虚拟头节点
            cur->next->next->next = tmp2;

            //cur后移两位
            cur = cur->next->next;
        }
        ListNode *result = dummyHead->next;
        delete dummyHead;
        return result;
    }
};
看完代码随想录之后的想法
递归版本
  • 使用虚拟头节点再删除,这样就不用把头节点分开判断了

    /**
     * Definition for singly-linked list.
     * struct ListNode {
     *     int val;
     *     struct ListNode *next;
     * };
     */
    //递归版本
    struct ListNode* swapPairs(struct ListNode* head){
        //递归结束条件:头节点不存在或头节点的下一个节点不存在。此时不需要交换,直接返回head
        if(!head || !head->next)
            return head;
        //创建一个节点指针类型保存头结点下一个节点
        struct ListNode *newHead = head->next;
        //更改头结点+2位节点后的值,并将头结点的next指针指向这个更改过的list
        head->next = swapPairs(newHead->next);
        //将新的头结点的next指针指向老的头节点
        newHead->next = head;
        return newHead;
    }
    
迭代版本
//迭代版本
struct ListNode* swapPairs(struct ListNode* head){
    //使用双指针避免使用中间变量
    typedef struct ListNode ListNode;
    ListNode *fakehead = (ListNode *)malloc(sizeof(ListNode));
    fakehead->next = head;
    ListNode* right = fakehead->next;
    ListNode* left = fakehead;
    while(left && right && right->next ){
        left->next = right->next;
        right->next = left->next->next;
        left->next->next = right;
        left = right;
        right = left->next;
    }
    return fakehead->next;
}

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

思路
  • 要求使用一趟扫描实现,没思路,难道不是必须完全走一遍才行
看完代码随想录之后的想法
双指针法
  • 又是双指针,先让fast走n个节点,然后slow和fast一起走,当fast到末尾时,slow就是倒数第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);
            dummyHead->next = head;
            ListNode *fast = dummyHead;
            ListNode *slow = dummyHead;
            for(int i = 0; i < n; i++) {
                fast = fast->next;
            }
            while(fast->next != nullptr) {
                fast = fast->next;
                slow = slow->next;
            }
            ListNode *tmp = slow->next;
            slow->next = slow->next->next;
            delete tmp;
            return dummyHead->next;
        }
    };
    

160.链表相交

思路
  • 盲猜快慢指针,双指针
  • 具体怎么做不知道
看完代码随想录之后的想法
双指针法
  • 让末尾对其即可,找二者第一次相等的值,返回交点(阿哲)

    /**
     * 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, lenB = 0;
            //计算各自链表的长度
            while(curA != nullptr) {
                lenA++;
                curA = curA->next;
            }
            while(curB != nullptr) {
                lenB++;
                curB = curB->next;
            }
            //把较长链表指针后移,或者直接让A作为最长链表
            //注意赋值要先赋哦
            curA = headA;
            curB = headB;
            if (lenB > lenA) {
                swap(lenA, lenB);
                swap(curA, curB);
            }
            int gap = lenA - lenB;
            while(gap--) {
                curA = curA->next;
            }
            //注意如果没有相交要返回null
            while(curA != nullptr) {
                //注意题目,题目中是对象相等,而不是值相等
                if (curA == curB) {
                    return curA;
                }
                curA = curA->next;
                curB = curB->next;
            }
            return NULL;
        }
    };
    

142.环形链表II

思路
  • 快慢指针,一个指针是另一个指针速度的两倍,如果二者相遇,则有环,如果二者不相遇,则无环,结束条件是有指针为null

  • 但是这样无法找到入环的第一个节点,怎么办?以下代码并不能找到入口

    /**
     * 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->next != nullptr && slow->next != nullptr && fast->next->next != nullptr ) {
                if(fast == slow) {
                    return 
                }
                fast = fast->next->next;
                slow = slow->next;
            }
            return NULL;
        }
    };
    
看完代码随想录之后的想法
相遇节点法
  • 假设从头结点到环形入口节点 的节点数为x。 环形入口节点到 fast指针与slow指针相遇节点 节点数为y。 从相遇节点 再到环形入口节点节点数为 z

在这里插入图片描述

  • 那么相遇时: slow指针走过的节点数为: x + y, fast指针走过的节点数:x + y + n (y + z),n为fast指针在环内走了n圈才遇到slow指针, (y+z)为 一圈内节点的个数A。

    因为fast指针是一步走两个节点,slow指针一步走一个节点, 所以 fast指针走过的节点数 = slow指针走过的节点数 * 2:

    (x + y) * 2 = x + y + n (y + z)
    

    两边消掉一个(x+y): x + y = n (y + z)

    因为要找环形的入口,那么要求的是x,因为x表示 头结点到 环形入口节点的的距离。

    所以要求x ,将x单独放在左面:x = n (y + z) - y ,

    再从n(y+z)中提出一个 (y+z)来,整理公式之后为如下公式:x = (n - 1) (y + z) + z 注意这里n一定是大于等于1的,因为 fast指针至少要多走一圈才能相遇slow指针。

    这个公式说明什么呢?

    先拿n为1的情况来举例,意味着fast指针在环形里转了一圈之后,就遇到了 slow指针了。

    当 n为1的时候,公式就化解为 x = z

    这就意味着,从头结点出发一个指针,从相遇节点 也出发一个指针,这两个指针每次只走一个节点, 那么当这两个指针相遇的时候就是 环形入口的节点

    也就是在相遇节点处,定义一个指针index1,在头结点处定一个指针index2。

    让index1和index2同时移动,每次移动一个节点, 那么他们相遇的地方就是 环形入口的节点。

  • 快慢指针相遇,此时从head 和 相遇点,同时查找直至相遇

    /**
     * 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;
            //slow不用判断,一定是fast先到
            while(fast != nullptr && fast->next != nullptr ) {
                fast = fast->next->next;
                slow = slow->next;
                //快慢指针相遇,此时从head 和 相遇点,同时查找直至相遇
                if(fast == slow) {
                    ListNode *ind1 = fast;
                    ListNode *ind2 = head;
                    while(ind1 != ind2) {
                        ind1 = ind1->next;
                        ind2 = ind2->next;
                    }
                    return ind2;
                }
            }
            return NULL;
        }
    };
    

总结

  • 使用 虚拟头节点, 链表的增删改会方便很多

在这里插入图片描述

遇到困难

  • 对while循环和链表的指针的思考还不够细致
  • 有一些没必要的判断,总是根据经验去判断
  • 需要对递归法有新的理解

今日收获

  • 今日打卡晚了两天,以后算法题要加紧速度
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值