链表解题笔记

刷完了Leetcode所有的链表简单—中等题,归纳一下用过的技巧。

一、快慢指针

相位差快慢

例题:实现一种算法,找出单向链表中倒数第 k 个节点。返回该节点的值。

class Solution {
public:
    int kthToLast(ListNode* head, int k) {
        ListNode* fast = head, *slow = head;
        for(int i = 0; i < k - 1; i++)
        {
            fast = fast->next;
        }
        while(fast->next != NULL)
        {
            fast = fast->next;
            slow = slow->next;
        }
        return slow->val;
    }
};

先让快指针走k-1步,再让快慢指针同速度前进。当快指针走到尾部,慢指针自然在倒数第k个节点。

步长快慢

例题:给定一个带有头结点 head 的非空单链表,返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点。

class Solution {
public:
    ListNode* middleNode(ListNode* head) {
        if(head == NULL)
        {
            return NULL;
        }
        ListNode* fast = head;
        ListNode* low = head;
        while(fast->next != NULL)
        {
            low = low->next;
            fast = fast->next->next;
            if(fast == NULL)
            {
                break;
            }
        }
        return low;
    }
};

让快慢指针同时从头指针出发,快指针一次走两步,慢指针一次走一步,当快指针到达尾部,慢指针自然在正中间。

二、缓冲区

也就是另外建立存储数据的区域,例题:编写代码,移除未排序链表中的重复节点。保留最开始出现的节点。

class Solution {
public:
    ListNode* removeDuplicateNodes(ListNode* head) {
        ListNode* cur = head, *prev;
        int data[20001] = {0};
        while(cur != NULL)
        {
            if(data[cur->val] == 0)
            {
                data[cur->val]++;
                prev = cur;
                cur = cur->next;
            }
            else if(data[cur->val] == 1)
            {
                prev->next = cur->next;
                delete cur;
                cur = prev->next;
            }
        }
        return head;
    }
};

这里将每个数据放入对应的桶中,如果桶中已经有数据,自然判断出重复。

三、使用STL

同样也是使用了缓冲区,不过stl的一些方法可以大大简便操作

使用vector

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

class Solution {
public:
        void reorderList(ListNode* head) {
            if (!head) return;
            vector<ListNode*> vec;
            ListNode* cur = head;

            while (cur) {
                vec.push_back(cur);
                cur = cur->next;
            }

            int left = 0;
            int right = vec.size() - 1;
            while (left < right) {
                vec[left]->next = vec[right];
                vec[right--]->next = vec[++left];
            }
            vec[left]->next = nullptr;
        }
};

vector简便了取未操作过的首、尾节点的方法

使用stack

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

class Solution {
public:
    Node* flatten(Node* head) {
        if (!head) return head;
        stack<Node*> n;
        Node* p = head, *temp;
        while (true)
        {
            while(true)
            {
                if (p->child)
                {
                    if (p->next)
                        n.push(p->next);
                    p->next = p->child;
                    p->next->prev = p;
                    p->child = NULL;
                }
                if(!(p->next))
                    break;
                p = p->next;
            }
            if (n.empty())
                break;
            temp = p;
            p = n.top();
            n.pop();
            temp->next = p;
            p->prev = temp;
        }
        return head;
    }
};

使用了stack保证了后进先出,使得扁平化从最分支到主枝进行。

使用map

map确保了键值对的唯一性,可以快速解决重复值问题。例题:给你一个链表的头节点 head,请你编写代码,反复删去链表中由 总和 值为 0 的连续节点组成的序列,直到不存在这样的序列为止。删除完毕后,请你返回最终结果链表的头节点。

class Solution {
public:
    ListNode* removeZeroSumSublists(ListNode* head) {
        map<int, ListNode*> map;
        int sum = 0;
        ListNode* dummy = new ListNode(0), *cur = dummy;
        dummy->next = head;
        while (cur)
        {
            sum += cur->val;
            map[sum] = cur;
            cur = cur->next;
        }
        for(cur = dummy, sum = 0; cur; cur = cur->next)
        {
            sum += cur->val;
            if (map.find(sum) != map.end())
                cur->next = map[sum]->next;
        }
        return dummy->next;
    }
};

从头开始计算每到达一个节点时,前面所有节点值的总和,并且存在map中,如果有一个序列和为0,那么必有两个总和相等。再一次遍历时,如果某个总和在map中但不是当前节点,那么map中对应的节点和当前节点之间的和必定是0.

四、递归

链表的节点是相似的,可以使用递归解决。例题:给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。返回删除后的链表的头节点。

class Solution {
public:
    ListNode* deleteNode(ListNode* head, int val) {
        if(!head) return NULL;
        if(head->val == val) return head->next;
        head->next = deleteNode(head->next,val);
        return head;
    }
};

通过递归,从头结点开始,下一个节点等于删除的只便跳过,重新连接链表。

五、补齐链表

当操作的两个链表长度不等时,可以先补齐至相同长度再操作。

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

void addLinkList(struct ListNode* l1, struct ListNode* l2,int *cout)
{       
        if(l1==NULL||l2==NULL)
            return;
        if(l1->next!=NULL&&l2->next!=NULL)
            addLinkList(l1->next,l2->next,cout);
        int Sum=l1->val+l2->val+*cout;
        if(Sum>=10)
        {
            *cout=1;
            l1->val=Sum%10;
        }
        else
        {
            *cout=0;
            l1->val=Sum;
        }
        return;
}

六、原地修改

当复制的链表具有其他信息,不便于直接复制时,可以先原地复制,再间隔地挑出来。

例题:请实现 copyRandomList 函数,复制一个复杂链表。在复杂链表中,每个节点除了有一个 next 指针指向下一个节点,还有一个 random 指针指向链表中的任意节点或者 null。

class Solution {
public:
    Node* copyRandomList(Node* head) {
        if(!head) return NULL;
        Node* cur = head, *prev, *newhead;
        while(cur)
        {
            Node* newNode = new Node(cur->val);
            newNode->random = cur->random;
            newNode->next = cur->next;
            cur->next = newNode;
            cur = cur->next->next;
        }
        prev = head;
        newhead = head->next;
        while(prev)
        {
            cur = prev->next;
            if(cur->random)
                cur->random = cur->random->next;
            prev = prev->next->next;
        }
        prev = head;
        while(prev)
        {
            cur = prev->next;
            prev->next = prev->next->next;
            prev = prev->next;
            cur->next = prev ? prev->next : NULL;
        }
        return newhead;
    }
};

七、结合其他算法

例如结合BFS、DFS等搜索办法,DP、DC等优化算法等等