《剑指2》第四章 链表

一、知识

1.哨兵节点:为了简化处理链表边界条件引入(如果操作可能产生新的头结点)

正常的链表插入操作:需要判断链表为空

正常的链表删除操作:需要判断链表为空;被删除的节点是头结点

注:找到删除节点的前一个节点进行操作比较容易

合理应用哨兵节点,就不再需要单独处理这些特殊的输入,从而杜绝由于忘记处理这些特殊输入而出现Bug的可能性。

2.双指针:

前后双指针(查找链表倒数第k个节点)、

快慢双指针:查找链表中心节点、链表中的环

3.循环链表

给定循环链表的头结点head,则可以通过判断链表中的一个指针p是否等于head来判断是否遍历完链表,防止死循环。

4.将一个节点插入cur与next之间,只需要使用到cur节点即可

    insertNode->next = cur->next;
    cur->next = insertNode;

二、题目

1.删除倒数第k个节点

思路:前后指针,前一个指针先移动k-1步(倒数第一个节点距离倒数第k个节点有k-1个长度)

struct ListNode* removeNthFromEnd(struct ListNode* head, int n){
    struct ListNode* dummy = (struct ListNode*)malloc(sizeof(struct ListNode));
    dummy->next = head;
    struct ListNode* front = head;
    struct ListNode* back = dummy;
    for(int i=0;i<n-1;i++)
        front = front->next;
    while(front->next != NULL)
    {
        front = front->next;
        back = back->next;
    }
    back->next = back->next->next;
    return dummy->next;
}

2.链表中环的入口节点

思路:

1.通过快慢指针找到判断是否有环并找到相遇节点

2.不需要得到环中节点数目,因为若相遇,说明慢指针走k步,快指针走了2k步,快指针多走的(2k-k)= k 步,使其相遇,说明k为环中节点数目的倍数。所以将一个新的指向头结点的指针和相遇节点的指针一起走,再次相遇即为入口节点

3.注意:新的指针在头结点时相当于已经走了一步,因此需要在头节点前插入哨兵节点,从哨兵节点处开始走,保证慢指针能走k步。

struct ListNode *MeetingNode(struct ListNode *head);
struct ListNode *detectCycle(struct ListNode *head) {
    if(head == NULL)
        return NULL;
    struct ListNode *meet = MeetingNode(head);
    if(meet == NULL)
        return NULL;
    struct ListNode *dummy = (struct ListNode *)malloc(sizeof(struct ListNode));
    dummy->next = head;
    struct ListNode *node = dummy;
    while(meet != node)
    {
        meet = meet->next;
        node = node->next;
    }
    return node;
}

struct ListNode *MeetingNode(struct ListNode *head)
{
    if(head == NULL | head->next == NULL)
        return NULL;
    struct ListNode *slow = head;
    struct ListNode *fast = slow->next;
    while(slow != NULL && fast != NULL)
    {
        if(slow == fast)
            return slow;
        slow = slow->next;
        fast = fast->next;
        if(fast != NULL)
            fast = fast->next;
    }
    return NULL;
}

3.两个链表的第1个重合节点

方法一:栈(弹栈比较)

方法二:遍历长度lenA和lenB,长的先走abs(lenA-lenB)

struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
    int lenA = 0;
    int lenB = 0;
    struct ListNode *pA = headA;
    struct ListNode *pB = headB;
    struct ListNode *longer = (struct ListNode *)malloc(sizeof(struct ListNode *));
    struct ListNode *shorter = (struct ListNode *)malloc(sizeof(struct ListNode *));
    while(pA != NULL)
    {
        lenA++;
        pA = pA->next;
    }
    while(pB != NULL)
    {
        lenB++;
        pB = pB->next;
    }
    int difflen = lenA > lenB ? (lenA-lenB) : (lenB-lenA);
    longer = lenA > lenB ? headA : headB;
    shorter = lenA > lenB ? headB : headA;
    for(int i=0;i<difflen;i++)
        longer = longer->next;
    while(longer != NULL && shorter != NULL)
    {
        if(longer == shorter)
            return longer;
        longer = longer->next;
        shorter = shorter->next;
    }
    return NULL;
}

4.反转链表

思路:三个指针,前两个用来翻转,后一个指针记录节点用来防止链表断开

tips:在循环中声明end比事先声明好end要好,因为若事先声明好end,会有mid为空,mid->next无法访问的情况。

就一开始的情况而言,NULL充当了哨兵节点的作用。

struct ListNode* reverseList(struct ListNode* head){
    struct ListNode* front = NULL;
    struct ListNode* mid = head;
    while(mid != NULL)
    {
        struct ListNode* end = mid->next;
        mid->next = front;
        front = mid;
        mid = end;
    }
    return front;
}

5.链表中数字相加

思路:链表翻转再相加

struct ListNode* reverseList(struct ListNode* head);
struct ListNode* addList(struct ListNode* l1,struct ListNode* l2);
struct ListNode* addTwoNumbers(struct ListNode* l1, struct ListNode* l2){
    l1 = reverseList(l1);
    l2 = reverseList(l2);
    struct ListNode* ret = addList(l1,l2);
    return reverseList(ret);
}

struct ListNode* addList(struct ListNode* l1,struct ListNode* l2)
{
    struct ListNode* ret = (struct ListNode*)malloc(sizeof(struct ListNode));
    ret->val = 0;
    struct ListNode* p = ret;
    int flag = 0;
    while(l1 != NULL || l2 != NULL)
    {
        int l1_val = l1 != NULL ? l1->val : 0;
        int l2_val = l2 != NULL ? l2->val : 0;
        int sum = l1_val + l2_val + flag;
        if(sum>=10)
        {
            flag = 1;
            sum -= 10;
        }
        else
            flag = 0;
        struct ListNode* newNode = (struct ListNode*)malloc(sizeof(struct ListNode));
        newNode->val = sum;
        p->next = newNode;
        p = p->next;
        l1 = l1 == NULL ? NULL : l1->next;
        l2 = l2 == NULL ? NULL : l2->next;
    }
    if(flag == 1)
    {
        struct ListNode* newNode = (struct ListNode*)malloc(sizeof(struct ListNode));
        newNode->val = 1;
        p->next = newNode;
        p = p->next;
    }
    p->next = NULL;
    return ret->next;
}
struct ListNode* reverseList(struct ListNode* head){
    struct ListNode* front = NULL;
    struct ListNode* mid = head;
    while(mid != NULL)
    {
        struct ListNode* end = mid->next;
        mid->next = front;
        front = mid;
        mid = end;
    }
    return front;
}

6.重排链表

思路:
1.找到中间节点
2.翻转后半段链表
3.合并

struct ListNode* reverseList(struct ListNode* head);
struct ListNode* middleNode(struct ListNode* head);
void mergeList(struct ListNode* l1, struct ListNode* l2);

void reorderList(struct ListNode* head){
    struct ListNode* mid = middleNode(head);
    struct ListNode* reverse = reverseList(mid->next);
    mid->next = NULL;
    struct ListNode* p1 = head;
    struct ListNode* p2 = reverse;
    mergeList(p1,p2);
}

struct ListNode* reverseList(struct ListNode* head){
    struct ListNode* front = NULL;
    struct ListNode* mid = head;
    while(mid != NULL)
    {
        struct ListNode* end = mid->next;
        mid->next = front;
        front = mid;
        mid = end;
    }
    return front;
}

struct ListNode* middleNode(struct ListNode* head)
{
    struct ListNode* slow = head;
    struct ListNode* fast = head;
    while(fast != NULL && fast->next != NULL)
    {
        slow = slow->next;
        fast = fast->next->next;
    }
    return slow;
}

void mergeList(struct ListNode* l1, struct ListNode* l2)
{
    struct ListNode* temp_l1 = l1;
    struct ListNode* temp_l2 = l2;
    while(l1 != NULL && l2 != NULL)
    {
        temp_l1 = l1->next;
        temp_l2 = l2->next;

        l1->next = l2;
        l2->next = temp_l1;
        
        l1 = temp_l1;
        l2 = temp_l2;
    }
}

作者:wu-ming-8e
链接:https://leetcode-cn.com/problems/LGjMqU/solution/jian-zhi-offer-ii-026-zhong-pai-lian-bia-uku9/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

7.回文链表

思路:

1.找到中间节点
2.翻转后半段链表
3.比较

struct ListNode* middleNode(struct ListNode* head);
struct ListNode* reverseList(struct ListNode* head);
bool equalsList(struct ListNode* p1,struct ListNode* p2);
bool isPalindrome(struct ListNode* head){
    struct ListNode* mid = middleNode(head);
    struct ListNode* p1 = head;
    struct ListNode* p2 = mid->next;
    mid->next = NULL;
    p2 = reverseList(p2);
    return equalsList(p1,p2);
}

struct ListNode* middleNode(struct ListNode* head)
{
    struct ListNode* dummy = (struct ListNode*)malloc(sizeof(struct ListNode));
    dummy->next = head;
    struct ListNode* fast = dummy;
    struct ListNode* slow = dummy;
    while(fast != NULL && fast->next != NULL)
    {
        slow = slow->next;
        fast = fast->next->next;
    }
    return slow;
}

struct ListNode* reverseList(struct ListNode* head)
{
    struct ListNode* front = NULL;
    struct ListNode* mid = head;
    while(mid != NULL)
    {
        struct ListNode* end = mid->next;
        mid->next = front;

        front = mid;
        mid = end;
    }
    return front;
}

bool equalsList(struct ListNode* p1,struct ListNode* p2)
{
    while(p1 != NULL && p2 != NULL)
    {
        if(p1->val != p2->val)
            return false;
        
        p1 = p1->next;
        p2 = p2->next;
    }
    return true;
}

8.展平多级双向链表

思路:递归

class Solution {
    public Node flatten(Node head) {
        flattenGetTail(head);
        return head;
    }

    private Node flattenGetTail(Node head){
        Node node = head;
        Node tail = null;
        while(node != null){
            Node next = node.next;
            if(node.child != null){
                Node child = node.child;
                Node childTail = flattenGetTail(node.child);

                node.child = null;
                node.next = child;
                child.prev = node;
                childTail.next = next;
                if(next != null){
                    next.prev = childTail;
                }
                tail = childTail;
            }else{
                tail = node;
            }
            node = next;
        }
        return tail;
    }
}

9.排序的循环链表

思路:

1.在链表中找到相邻的两个节点cur与next,如果插入节点的值在二者之间,则插入;否则若找不到合适的节点,则将新节点插入到最大与最小节点之间(注意最小节点不一定是head,head并不一定是最小的,只是链表中的一个节点),即最大节点的下一个。

2.特殊情况:原链表没有节点和只有一个节点

void insertCore(struct Node* head, struct Node* insertNode);
struct Node* insert(struct Node* head, int insertVal) {
    struct Node* insertNode = (struct Node*)malloc(sizeof(struct Node));
    insertNode->val = insertVal;
    if(head == NULL)
    {
        head = insertNode;
        head->next = insertNode;
    }        
    else if(head == head->next)
    {
        head->next = insertNode;
        insertNode->next = head;
    }
    else
        insertCore(head,insertNode);
    return head;
}

void insertCore(struct Node* head, struct Node* insertNode)
{
    struct Node* cur = head;
    struct Node* next = head->next;
    struct Node* biggest = head;
    while(!(cur->val <= insertNode->val && insertNode->val <= next->val) && next != head)
    {
        if(next->val >= biggest->val)
            biggest = next;
        cur = next;
        next = next->next;
    }
    if(cur->val <= insertNode->val && insertNode->val <= next->val)
    {
        insertNode->next = cur->next;
        cur->next = insertNode;
    }
    else
    {
        insertNode->next = biggest->next;
        biggest->next = insertNode;
    }
}

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值