LeetCode 剑指 Offer II 链表(021-029)

021. 删除链表的倒数第 n 个结点

题目:

给定一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。

示例:
在这里插入图片描述

输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]

思路:
快慢指针:慢指针为头结点,快慢指针相差n,然后快指针到最后一个节点的时候,
慢指针刚好到倒数第n+1个节点,然后删除倒数n节点
注意:增加头结点,防止要删除首节点

class Solution {
    
    
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
    
    
        ListNode *dummy = new ListNode(-1);
        dummy->next = head;
        ListNode *slow = dummy;
        ListNode *fast = dummy;
        while(n--) {
    
    
            fast = fast->next;
        }
        while(fast != nullptr && fast->next != nullptr) {
    
    
            fast = fast->next;
            slow = slow->next;
        }
        slow->next = slow->next->next;
        return dummy->next;
    }
};

022. 链表中环的入口节点

题目:

给定一个链表,返回链表开始入环的第一个节点。 从链表的头节点开始沿着 next 指针进入环的第一个节点为环的入口节点。如果链表无环,则返回null。
为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,
则在该链表中没有环。注意,pos 仅仅是用于标识环的情况,并不会作为参数传递到函数中。
说明:不允许修改给定的链表。

示例:
在这里插入图片描述

输入:head = [3,2,0,-4], pos = 1
输出:返回索引为 1 的链表节点
解释:链表中有一个环,其尾部连接到第二个节点。

思路:
方法一:哈希表
可以通过哈希表记录,如果遇到哈希表里面有的就相当于第一个入环口

方法二:快慢指针
a为环外长度,b为入环口到相遇的长度,c为相遇到入环口长度
a + n * (b + c) + b = 2 * (a + b)第一次相遇的路程
得 a = (n - 1) * (b + c) + b;
所以 fast 一次一步再走 a 必然会遇到 slow

//快慢指针
class Solution {
    
    
public:
    ListNode *detectCycle(ListNode *head) {
    
    
        ListNode *fast = head;
        ListNode *slow = head;
        while(1) {
    
    //第一次相遇
            //遇到空指针相当于不是循环,return
            if(fast == nullptr || fast->next == nullptr) 
                return nullptr;
            slow = slow->next;
            fast = fast->next->next;
            if(slow == fast) break;
        }
        fast = head;
        while(fast != slow) {
    
    
            fast = fast->next;
            slow = slow->next;
        }
        return slow;
    }
};

023. 两个链表的第一个重合节点

题目:

给定两个单链表的头节点 headA 和 headB ,请找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null 。
图示两个链表在节点 c1 开始相交:
题目数据 保证 整个链式结构中不存在环。
注意,函数返回结果后,链表必须 保持其原始结构 。

示例:
在这里插入图片描述

输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3
输出:Intersected at ‘8’
解释:相交节点的值为 8 (注意,如果两个链表相交则不能为 0)。
从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,0,1,8,4,5]。
在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。

思路:
方法一:哈希表
把a加入哈希表,遍历b判断是否有节点在哈希表中,有则返回,无则不相交

方法二:双指针
a = a + b, b = b + a
如果a和b相交的话,在遍历对方那部分的时候就可以找到相交的节点

//双指针
class Solution {
    
    
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
    
    
        if(headA == nullptr || headB == nullptr) return nullptr;
        ListNode *curA = headA;
        ListNode *curB = headB;
        while(curA != curB) {
    
    
            curA = curA == nullptr? headB : curA->next;
            curB = curB == nullptr? headA : curB->next;
        }
        return curA;
    }
};

024. 反转链表

题目:

给定单链表的头节点 head ,请反转链表,并返回反转后的链表的头节点。

示例:
在这里插入图片描述

输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]

思路:
创建两个双指针,从头结点和首节点开始一直反转

class Solution {
    
    
public:
    ListNode* reverseList(ListNode* head) {
    
    
        ListNode *pre = nullptr;
        ListNode *cur = head;
        while(cur != nullptr) {
    
    
            ListNode* next = cur->next;
            cur->next = pre;
            pre = cur;
            cur = next;
        }
        return pre;
    }
};

025. 链表中的两数相加

题目:

给定两个 非空链表 l1和 l2 来代表两个非负整数。数字最高位位于链表开始位置。它们的每个节点只存储一位数字。将这两数相> 加会返回一个新的链表。
可以假设除了数字 0 之外,这两个数字都不会以零开头。

示例:
在这里插入图片描述

输入:l1 = [7,2,4,3], l2 = [5,6,4]
输出:[7,8,0,7]

思路:
方法一:用两个栈(麻烦)
先用两个栈进行反转,相加,但最后还要反转链表

方法二:翻转链表
翻转链表后相加,再翻转

class Solution {
    
    
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
    
    
        l1 = reverseList(l1);
        l2 = reverseList(l2);
        return reverseList(addList(l1, l2));
    }
    //链表相加
    ListNode* addList(ListNode* l1, ListNode* l2) {
    
    
        int ans = 0;
        ListNode *node = new ListNode(-1);
        ListNode *dummy = node;
        while(l1 || l2) {
    
    
            int x = 0, y = 0;
            if(l1) {
    
    
                x = l1->val;
                l1 = l1->next;
            }
            if(l2) {
    
    
                y = l2->val;
                l2 = l2->next;
            }
            int num = (x + y + ans) % 10;
            ans = (x + y + ans) / 10;
            node->next = new ListNode(num);
            node = node->next;
        }
        if(ans) {
    
    //进位的话再加上
            node->next = new ListNode(ans);
            node = node->next;
        }
        return dummy->next;
    }
    //翻转链表
    ListNode* reverseList(ListNode* head) {
    
    
        ListNode *pre = nullptr;
        ListNode *cur = head;
        while(cur) {
    
    
            ListNode *next = cur->next;
            cur->next = pre;
            pre = cur;
            cur = next;
        }
        return pre;
    }
};

026. 重排链表

题目:

给定一个单链表 L 的头节点 head ,单链表 L 表示为:
L0 → L1 → … → Ln-1 → Ln
请将其重新排列后变为:
L0 → Ln → L1 → Ln-1 → L2 → Ln-2 → …
不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。

示例:
在这里插入图片描述

输入: head = [1,2,3,4]
输出: [1,4,2,3]

思路:
方法一:线性表
用线性表存储,然后用下表访问线性表,创建新链表

方法二:寻找链表中点 + 链表逆序 + 合并链表

//方法二
class Solution {
    
    
public:
    void reorderList(ListNode* head) {
    
    
        //快慢指针找中点
        ListNode *fast = head;
        ListNode *slow = head;//中点
        while(fast != nullptr && fast->next != nullptr) {
    
    
            fast = fast->next->next;
            slow = slow->next;
        }
        // 翻转后半部分
        fast = nullptr;
        while(slow != nullptr) {
    
    
            ListNode *next = slow->next;
            slow->next = fast;
            fast = slow;
            slow = next;
        }
        slow = head;
        //slow为前半部分链表,fast为后半部分链表
        while(slow || fast) {
    
    
            if(slow) {
    
    
                ListNode *next1 = slow->next;
                slow->next = fast;
                slow = next1;
            }
            if(fast) {
    
    
                ListNode *next2 = fast->next;
                fast->next = slow;
                fast = next2;
            }
        }
    }
};

027. 回文链表

题目:

给定一个链表的 头节点 head ,请判断其是否为回文链表。
如果一个链表是回文,那么链表节点序列从前往后看和从后往前看是相同的。

示例:
在这里插入图片描述

输入: head = [1,2,3,3,2,1]
输出: true

思路:

  • 寻找中点
  • 翻转后半部分链表
  • 判断前后部分是否回文
class Solution {
    
    
public:
    bool isPalindrome(ListNode* head) {
    
    
        //寻找中点
        ListNode *fast = head;
        ListNode *slow = head;
        while(fast != nullptr && fast->next != nullptr) {
    
    
            fast = fast->next->next;
            slow = slow->next;
        }
        //翻转后半部分
        fast = reverseList(slow);
        slow = head;//前半部分
        //判断是否回文
        bool ok = true;
        //fast <= slow, 所以while要用fast,下面证明:
        //例如:[1,2,1] 此时 slow = 1 -> 2, fast = 1 -> 2,因为2翻转的时候前面的1还指向2
        //[1,2,2,1] 此时slow = 1 -> 2 -> 2, fast = 1 -> 2 
        while(ok && fast != nullptr) {
    
    
            if(slow->val != fast->val) {
    
    
                ok = false;
                break;
            }
            fast = fast->next;
            slow = slow->next;
        }
        return ok;
    }
    //翻转链表
    ListNode* reverseList(ListNode* head) {
    
    
        ListNode *pre = nullptr;
        ListNode *cur = head;
        while(cur) {
    
    
            ListNode *next = cur->next;
            cur->next = pre;
            pre = cur;
            cur = next;
        }
        return pre;
    }
};

028. 展平多级双向链表

题目:

多级双向链表中,除了指向下一个节点和前一个节点指针之外,它还有一个子链表指针,可能指向单独的双向链表。这些子列表>也可能会有一个或多个自己的子项,依此类推,生成多级数据结构,如下面的示例所示。
给定位于列表第一级的头节点,请扁平化列表,即将这样的多级双向链表展平成普通的双向链表,使所有结点出现在单级双链表中。

示例:

输入:head = [1,2,3,4,5,6,null,null,null,7,8,9,10,null,null,11,12]
输出:[1,2,3,7,8,11,12,9,10,4,5,6]
解释:
在这里插入图片描述
扁平化后的链表如下图:
在这里插入图片描述

思路:
DFS深搜:
将图片顺时针旋转90°,看成一个二叉树,前序遍历
创建一个新节点,用于遍历时连接前后节点
在这里插入图片描述

class Solution {
    
    
private:
    //头结点
    Node *prevNode = new Node(-1);
public:
    Node* flatten(Node* head) {
    
    
        dfs(head);
        if(head != nullptr)
            head->prev = nullptr;
        return head;
    }
    //*preNode跟着dfs遍历,遍历一个连接一个
    void dfs(Node* root) {
    
    
        if(root == nullptr) return ;
        //cout << root->val;
        Node *left = root->child;
        Node *right = root->next;
        
        prevNode->next = root;
        root->prev = prevNode;
        prevNode = root;

        dfs(left);
        root->child = nullptr;
        dfs(right);
    }
};

029. 排序的循环链表

题目:

给定循环单调非递减列表中的一个点,写一个函数向这个列表中插入一个新元素 insertVal ,使这个列表仍然是循环升序的。
给定的可以是这个列表中任意一个顶点的指针,并不一定是这个列表中最小元素的指针。
如果有多个满足条件的插入位置,可以选择任意一个位置插入新的值,插入后整个列表仍然保持有序。
如果列表为空(给定的节点是 null),需要创建一个循环有序列表并返回这个节点。否则。请返回原先给定的节点。

示例:
在这里插入图片描述

输入:head = [3,4,1], insertVal = 2
输出:[3,4,1,2]
解释:在上图中,有一个包含三个元素的循环有序列表,你获得值为 3 的节点的指针,我们需要向表中插入元素 2 。新插入的节点应该在 1 和 3 之间,插入之后,整个列表如上图所示,最后返回节点 3 。

思路:
三种情况可以插入:

  • 插入数 >= 当前节点,<= 下一个节点
  • 当前节点 > 下一个节点时,表示到了结尾
    • 如果插入数 >= 当前节点,则为最大数
    • 如果插入数 <= 下一个节点,则为最小数

特例:

  • 如果空链表直接插入
  • 如果只有一个元素,直接插入
class Solution {
    
    
public:
    Node* insert(Node* head, int insertVal) {
    
    
        if(head == nullptr) {
    
    
            head = new Node(insertVal);
            head->next = head;
            return head;
        }
        Node *cur = head;
        while(cur->next != head) {
    
    //重点,如果只有一个数的时候正好跳过
            if((cur->val <= insertVal && insertVal <= cur->next->val) ||
                (cur->val > cur->next->val && insertVal >= cur->val) ||
                (cur->val > cur->next->val && insertVal <= cur->next->val)) {
    
    
                break;
            }
            cur = cur->next;
        }
        Node *next = cur->next;
        cur->next = new Node(insertVal);
        cur->next->next = next;
        return head;
    }
};

Guess you like

Origin blog.csdn.net/qq_45911678/article/details/121587999