31天刷完剑指offer——(第2天) 链表

剑指offer 06. 从尾到头打印列表 (easy)


#pragma once
#include<vector>
#include<stack>

using std::vector;
using std::stack;

struct ListNode {
    int val;
    ListNode* next;
    ListNode(int x) : val(x), next(NULL) {}
};
 
/* (先反转链表(迭代)后打印)

class Solution {
public:
    vector<int> reversePrint(ListNode* head) {
        ListNode* p = head, * q = head;         // p为原链表head,q为反转链表的headf (q起始为head,即原链表第一个头节点)

        while (p != nullptr) {                  // 遍历整个原链表
            if (p == head) {                    // 如果是原链表第一个头节点,要特殊处理
                p = p->next;                    // p先向后移动指向原链表下一个结点(即第二结点)
                q->next = nullptr;              // q->next==nullptr(即将原链表的第一个头节点 作为此时反转链表的头节点,也是尾结点,next为nullptr)
                continue;
            }
            
            ListNode* t = p->next;              // 保存原链表中p->next(即p指向结点的下一个结点地址)
            p->next = q;                        // 关键: 这一步将原链表p指向结点 指向反转链表头节点q,从而p指向结点变为 反转链表的头结点 (每次循环都将原链表中的头节点p 转移到反转链表中)
            q = p;                              // 此时反转链表头节点q为 原链表头节点p
            p = t;                              // 转移后的 原链表头节点为 t指向的结点
        }

        vector<int> vec;
        while (q != nullptr) {                  // 遍历反转后的链表 并逐个将值val插入到vec中
            vec.push_back(q->val);
            q = q->next;
        }
        return vec;
        
    }
};


*/


// 使用栈
class Solution {
public:
    vector<int> reversePrint(ListNode* head) {
        stack<int> stk;
        while (head != nullptr) {           // 顺序遍历链表,将每个结点值 push到栈stk中
            stk.push(head->val);
            head = head->next;
        }

        vector<int> vec;
        while (!stk.empty()) {
            vec.push_back(stk.top());       // 利用栈的特性,将栈中的元素出栈 并存储在vec中
            stk.pop();
        }

        return vec;
    }
};

剑指offer 24. 反转链表 (easy)


#pragma once
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* reverseList(ListNode* head) {
        ListNode* p = nullptr;

        while (head != nullptr) {               // 遍历整个原链表
            ListNode* t = head->next;           // 保存原链表中head->next(即head指向结点的下一个结点地址)
            head->next = p;                     // 关键: 这一步将原链表head指向结点 指向反转链表头节点p,从而head指向结点变为 反转链表的头结点 (每次循环都将原链表中的头节点head 转移到反转链表中)
            p = head;                           // 此时反转链表头节点p为 原链表头节点head
            head = t;                           // 转移后的 原链表头节点为 t指向的结点
        }

        return p;
    }
};

*/


// (递归)
class Solution {
private:
    ListNode* rhead;        // 用来记录反转链表后的头节点,也就是原链表的尾结点

public:
    ListNode* dg(ListNode* head);
    ListNode* reverseList(ListNode* head) {
        dg(head);

        return rhead;
    }
     
};

ListNode* Solution::dg(ListNode* head) {
    if (head == nullptr || head->next == nullptr) {         // 递归到最后一个结点返回 (注意链表为空的情况)
        rhead = head;                          // 记录原链表的尾结点(即反转链表的头节点)
        return head;
    }

    ListNode* head_next = dg(head->next);      // 函数dg返回的结点即head->next,即当前结点的下一个结点
    head_next->next = head;                    // 当前结点的下一个结点指向自己
    head->next = nullptr;                      // 自己的next设为nullptr (head此时是 当前反转链表的尾结点)
 
    return head;                               // 传进来是head,返回的也是head(该法的核心在于,必须从原链表尾部往前反转,而不能从原链表头部往后反转)
}

剑指offer 35. 复杂链表复制 (medium)


#pragma once
#include<unordered_map>
#include<iostream>

using std::unordered_map;

class Node {
public:
    int val;
    Node* next;
    Node* random;

    Node(int _val) {
        val = _val;
        next = NULL;
        random = NULL;
    }
};


/* 直接法 (先构造复制链表,再查找并设置random指针)

class Solution {
public:
    Node* copyRandomList(Node* head) {
        Node* dummy = new Node(0);             // 复制的链表的dummy结点 (即head头节点的前一个结点)
        Node* cur = head;                      // cur指向当前结点
        Node* pre = dummy;                     // pre指向"当前复制链表"的最后一个结点,也就是下一个新结点p的前一个结点

        while (cur != nullptr) {               // 遍历整个原链表,构造复制的链表结点和next指针 (random指针无法在 构造链表的过程中 设置,因为random指向的结点 可能还未构造)
            Node* p = new Node(cur->val);           // new一个新结点p,其值val与原链表中对应的结点相同
            p->next = nullptr;                      // p的next设为nullptr (结点p将要是"当前复制链表"的尾结点)
            pre->next = p;                          // 将结点p接在"当前复制链表"尾部
            pre = p;                                // pre赋值为p,即向后移动 (因此pre又指向"当前复制链表"的最后一个结点)

            cur = cur->next;                        
        }

        for (Node* p = head, *q = dummy->next; p != nullptr; p = p->next, q = q->next) {      // p和q分别指向原链表和复制链表,同时遍历两条链表,设置复制链表的结点的random指针

            Node* random = p->random;                              // random为原链表中 p指向结点的random指针
            int distence = 0;
            for (Node* i = head; i != random; i = i->next) {       // 计算random指针指向的结点 离头节点的距离distence
                ++distence;
            }
            Node* j = dummy->next;
            while (distence != 0) {           // 根据距离distence在复制链表中 找到相应的结点的指针j
                j = j->next;
                --distence;
            }
            q->random = j;                    // 因此q指向结点 的random指针为j

        }

        return dummy->next;
    }
};

*/


/* 利用map存储 原链表结点指针和复制链表结点指针 的键值对 (相比上面方法更巧妙,无需计算距离和根据距离查找random,时间复杂度从N*N下降到N)

class Solution {
public:
    Node* copyRandomList(Node* head) {
        unordered_map<Node*, Node*> hash;

        for (Node* cur = head; cur != nullptr; cur = cur->next) {     // 遍历原链表
            Node* node_p = new Node(cur->val);                        // 先创建结点,并建立map中 原链表结点指针和复制链表结点指针 的映射关系
            hash[cur] = node_p;
        }

        for (Node* cur = head; cur != nullptr; cur = cur->next) {     // 遍历原链表
            hash[cur]->next = hash[cur->next];                        // 设置之前创建的 复制结点的next和random指针 (因为是复制链表,所以复制结点的next,random指针 指向结点的位置 与原链表相应结点的next,random指针指向结点位置 是相对应的)
            hash[cur]->random = hash[cur->random];                    // 根据原结点和复制结点的 这种映射关系,可以用来设置 复制结点的next和random指针 
        }

        return hash[head];
    }
};

*/


// 拼接+拆分(将复制结点插入 原链表中对应结点的后面,根据位置对应关系 并结合拼接链表,就能够通过一次遍历链表来设置 复制链表的结点的random,之后再拆分从而得到复制链表)
class Solution {
public:
    Node* copyRandomList(Node* head) {
        if (head == nullptr) {
            return nullptr;
        }

        for (Node* cur = head; cur != nullptr; cur = cur->next->next) {          // 拼接(将复制结点插入原链表的 cur指向的结点后面)
            Node* node_p = new Node(cur->val);
            node_p->next = cur->next;
            cur->next = node_p;
        }

        for (Node* cur = head; cur != nullptr; cur = cur->next->next) {          // 设置复制链表的结点的random指针(根据位置对应关系 并结合拼接链表知: 复制结点的random即 "cur指向结点的random指针指向结点 之后的结点的指针")
            if (cur->random != nullptr) {
                cur->next->random = cur->random->next;
            }
        }

        Node* pre = head, * cur = head->next, * copy_head = head->next;          // 拆分 (将原链表和复制链表拆分)
        while (pre->next->next != nullptr) {
            pre->next = pre->next->next;
            cur->next = cur->next->next;
            pre = pre->next;
            cur = cur->next;
        }
        pre->next = nullptr;            // 注意!虽然我们返回的是复制链表,但是拆分后的 原链表的尾结点next指针必须设置为nullptr,否则会出错!

        return copy_head;
    }
};
官方题解:https://leetcode-cn.com/problems/fu-za-lian-biao-de-fu-zhi-lcof/solution/fu-za-lian-biao-de-fu-zhi-by-leetcode-so-9ik5/

31天刷完剑指offer 文章被收录于专栏

数据结构与算法

全部评论

相关推荐

点赞 评论 收藏
转发
1 收藏 评论
分享
牛客网
牛客企业服务