日野弥生:勉強しよう

LeetCode 430 - 扁平化多级双向链表

发表于2025年01月30日

#链表 #递归 #深度优先搜索 #回溯

深度优先搜索,dfs过程需要先处理child分支,后处理next分支。函数返回前处理prev节点。 很容易想到dfs过程需要以prev、curr、next节点作为输入参数。

但是,发现在扁平化过程中,next指针会发生改变,所以需要记录原next指针,即originNext。

注意,处理完child分支后,需要将分支置空;同时,由于深度优先搜索时,走到最后(即next指针和child指针均为空)的结点,需要记录当前位置,所以需要visit指针,便于在回溯时把最后位置的节点和回溯过程中的next节点继续连接。而,visit节点用过后就要被置为空,在递归过程中会因深度的不同的不断被改变,所以这个指针应该传引用。

由于visit指针会在遍历到不同深度的最后一个结点时赋值,所以visit指针在被传递时需要置空。

class Solution {
public:
    /* visit指针会被修改 */
    void dfs(Node *prev, Node *curr, Node *next, Node * &visit){
        if(prev)
            prev->next = curr;
            
        /* 记录原本的next指针 */
        Node *originNext = curr->next;

        /* 处理完子分支后置空 */
        if(curr->child)
            dfs(curr, curr->child, originNext, visit);
        curr->child = nullptr;

        /* 原next指针在处理完子分支后会继续处理 */
        if(originNext)
        {
            /* 如果已经记录visit,说明已经走到头,目前正在回溯状态 */
            if(visit)
            {
                /* 直接传递visit作为prev,会导致该深度遍历栈后的所有prev出错,因为visit节点只能做一次prev,所以利用临时指针作为入参,而把visit置空 */
                Node *tempVisit = visit;
                visit = nullptr;
                dfs(tempVisit, originNext, originNext->next, visit);
            }
            else
                /* 如果visit为空,则正常遍历原next指针 */
                dfs(curr, originNext, originNext->next, visit);
        }

        /* 修改每个节点的prev指针 */
        curr->prev = prev;

        /* 走到头了,回溯前记录该深度下的最后一个节点到visit指针 */
        if(!curr->next && !curr->child)
            visit = curr;
    }
    Node* flatten(Node* head) {
        if(!head) return head;
        Node *visit = nullptr;
        dfs(nullptr, head, head->next, visit);
        return head;
    }
};

官方题解解析

class Solution {
public:
    Node* flatten(Node* head) {
        function< Node*(Node*) > dfs = [&](Node* curr) {
            Node* cur = curr;
            /* 记录链表的最后一个节点,也就是我们前述的visit指针类似的作用 */
            Node* last = nullptr;

            while (cur) {
                Node* next = cur->next;
                //  如果有子节点,那么首先处理子节点
                if (cur->child)
                {
                    Node* child_last = dfs(cur->child);

                    /* 遍历子分支后,next会被改变,所以更新next, 提前记录下来后再把子分支的最后一个节点和当前next相连 */
                    next = cur->next;
                    //  将 curr 与 child 相连
                    cur->next = cur->child;
                    cur->child->prev = cur;

                    //  如果 next 不为空,就将 last 与 next 相连
                    if (next)
                    {
                        child_last->next = next;
                        next->prev = child_last;
                    }

                    // 将 child 置为空
                    cur->child = nullptr;
                    last = child_last;
                }
                else
                    last = cur;
                cur = next;
            }
            return last;
        };

        dfs(head);
        return head;
    }
};

回溯法

利用双向队列在每次进入子分支前,记录当前留下节点的next指针。先进后出

class Solution {
public:
    Node* flatten(Node* head) {
        Node *curr = head, *prev = nullptr;
        deque< Node *> stack{};

        /* 只有当前节点为空,且队列里没有元素时,遍历工作才结束 */
        while (curr || !stack.empty())
        {
            /* 当前为空时,表面已经到某一深度的最后一个结点,此时取出队列里的第一个next指针并连接 */
            if (!curr)
            {
                curr = stack.front();
                stack.pop_front();
                curr->prev = prev;
                prev->next = curr;
            }
            /* 进入子分支前,记录当前结点的next指针 */
            if (curr->child)
            {
                if (curr->next)
                    stack.push_front(curr->next);
                curr->child->prev = curr;
                curr->next = curr->child;

                /* 处理完子分支后置空 */
                curr->child = nullptr;
            }
            /* 向前遍历 */
            prev = curr;
            curr = curr->next;
        }
        return head;
    }
};

フラッシュタブ:LeetCode

题目链接:https://leetcode.cn/problems/flatten-a-multilevel-doubly-linked-list/

上一篇

LeetCode 21 - 合并两个有序链表

下一篇

LeetCode 2095 - 删除链表的中间节点