【剑指offer】题型总结 - 链表

本文介绍了三种方法解决从尾到头打印链表问题,包括借助栈、逆序vector和递归。同时,详细解析了两种链表反转策略,以及复杂链表复制的三种解决方案,涉及原地修改、哈希表和回溯等技巧,并分析了各种方法的时间和空间复杂度。
摘要由CSDN通过智能技术生成

链表

06. 从尾到头打印链表

题意

  • 逆序输出单向链表

解法1 借助栈

逆序输出 --》 先进后出 --》 栈

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    vector<int> reversePrint(ListNode* head) {
        stack<int> st;
        vector<int> res;

        while(head!=NULL)
        {
            st.push(head->val);
            head=head->next;
        }

        while(!st.empty())
        {
            res.push_back(st.top());
            st.pop();
        }
        
        return res;

    }
};
复杂度分析

时间复杂度 O(N):遍历链表;
空间复杂度 O(N):临时存储变量的栈。

解法2 逆序 vector

利用 reverse 函数 逆序 数组。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    vector<int> reversePrint(ListNode* head) {
        stack<int> st;
        vector<int> res;

        while(head!=NULL)
        {
            res.push_back(head->val);
            head=head->next;
        }

        reverse(res.begin(), res.end());
        return res;

    }
};
复杂度分析

时间复杂度 O(N):遍历链表;
空间复杂度 O(N):临时存储变量的 vector。

reverse

可以原地反转 vector / string / char[]

解法3 递归

/**
 * Definition for singly-linked list.
 * 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 {
private:
    vector<int> res;
public:
    void recur(ListNode* head)
    {
        if(head == nullptr)
            return;
        recur(head->next);
        res.emplace_back(head->val);
    }
    vector<int> reverseBookList(ListNode* head) {
        recur(head);
        return res;
    }
};
复杂度分析

时间复杂度: O ( N ) O(N) O(N),遍历链表;
空间复杂度: O ( N ) O(N) O(N),递归栈共 n n n 层。


24. 反转链表

题意

  • 反转链表

解法1 新建反转链表

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode* ans=NULL;

        while(head != NULL)
        {
            if(ans == NULL)
                ans = new ListNode(head->val);
            else
            {
                ListNode* tmp = new ListNode(head->val);    // new,本质上是对指针做初始化
                tmp->next = ans;    // 链表指针这里next存的是指针指向的空间地址?对,next本身就是一个指针,所以本质上next的这个内存空间存的是指针指向的空间地址。
                ans = tmp;
            }
            head = head->next;
        }
        return ans;
    }
};
复杂度分析

时间复杂度 O(N):遍历链表;
空间复杂度 O(N):新建链表。

解法2 原地修改

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode* ans;
        if(head == NULL) return NULL;
        ListNode* q = head->next;
        ListNode* p = NULL;

        while(head!=NULL)
        {
            q = head->next;
            head->next = p;
            p = head;
            head = q;

        }
        return p;
    }
};
复杂度分析

时间复杂度 O(N):遍历链表;
空间复杂度 O(1):原地修改。

解法3 递归

假设 nk+1 到 nm 都实现了反转,也就是说,现在要反转nk -> nk+1 为 nk <- nk+1,那么只要 nk->next->next=nk; nk->next=nullptr;

而为了得到反转后的链表头 head,从递归最尾端开始,将 newHead 返回。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
       if(head==nullptr || head->next == nullptr)   return head;

       ListNode* newHead = reverseList(head->next);
       head->next->next = head;
       head->next = nullptr;

       return newHead;
    }
};
复杂度分析

时间复杂度 O(N):对链表的每个节点实现递归;
空间复杂度 O(N):空间复杂度主要取决于递归调用的栈空间,最多为 N 层。


35. 复杂链表的复制

题意

  • 复制一个含有 nextrandom 的链表。

解答1 利用哈希表实现

因为 random 指向的节点可能是还没复制到的节点,所以 nextrandom 需要分开复制。

  • 第一次遍历,复制 valnext
  • 第二次遍历,复制 random
/*
// Definition for a Node.
class Node {
public:
    int val;
    Node* next;
    Node* random;
    
    Node(int _val) {
        val = _val;
        next = NULL;
        random = NULL;
    }
};
*/
class Solution {
public:
    Node* copyRandomList(Node* head) {
        if(head==nullptr) return nullptr;

        Node* newHead = new Node(head->val);
        Node* cur = newHead, *old = head;
        unordered_map<Node*, Node*> mp;

        while(old!=nullptr)
        {
            Node* nxt = nullptr;
            if(old->next!=nullptr)
                nxt = new Node(old->next->val);
            cur->next = nxt;
            mp[old] = cur;

            cur = cur->next;
            old = old->next;
        }

        mp[nullptr] = nullptr; 

        cur = newHead;
        while(head!=nullptr)
        {
            cur->random = mp[head->random];
            head = head->next;
            cur = cur->next;
        }
        return newHead;
    }
};
复杂度分析

时间复杂度 O(N):两次遍历;
空间复杂度 O(N):复制的链表和 map

解答2 节点拆分

在每个节点 n 后复制一个节点 n’,然后将链表拆分成原链表和复制后的链表。

但是同样的,nextrandom 需要分两次遍历复制,再加上拆分,一共遍历 轮。

ps. 注意 nullptr 的处理。

/*
// Definition for a Node.
class Node {
public:
    int val;
    Node* next;
    Node* random;
    
    Node(int _val) {
        val = _val;
        next = NULL;
        random = NULL;
    }
};
*/
class Solution {
public:
    Node* copyRandomList(Node* head) {
        if (head == nullptr) {
            return nullptr;
        }
        for(Node* node=head; node!=nullptr; node=node->next->next)
        {
            Node* nodeNew = new Node(node->val);
            nodeNew->next = node->next;
            node->next = nodeNew;
        }
        for(Node* node=head; node!=nullptr; node=node->next->next)
        {
            Node* nodeNew = node->next;
            nodeNew->random = node->random == nullptr ? nullptr : node->random->next;
        }
        Node* headNew = head->next;
        for(Node* node=head; node!=nullptr; node=node->next)
        {
            Node* nodeNew = node->next;
            node->next = node->next->next;
            nodeNew->next = nodeNew->next == nullptr ? nullptr : nodeNew->next->next;
        }
        return headNew;
    }
};
复杂度分析

时间复杂度 O(N):三次遍历;
空间复杂度 O(N):复制的链表。

解法3 回溯

实际上这样的回溯本质上还是先实现了 next 的复制,然后回溯到了 newHead 进行 random 的复制。

/*
// Definition for a Node.
class Node {
public:
    int val;
    Node* next;
    Node* random;
    
    Node(int _val) {
        val = _val;
        next = NULL;
        random = NULL;
    }
};
*/
class Solution {
public:
    unordered_map<Node*, Node*> mp;

    Node* copyRandomList(Node* head) {
        if (head == nullptr) {
            return nullptr;
        }
        
        if(!mp.count(head))
        {
            Node* headNew = new Node(head->val);
            mp[head] = headNew;
            headNew->next = copyRandomList(head->next);
            headNew->random = copyRandomList(head->random);
        }
        
        return mp[head];
        
    }
};
复杂度分析

时间复杂度 O(N):每个节点都要调用两次复制函数 copyRandomList
空间复杂度 O(N):复制的链表和哈希表。


总结

A. reverse

可以原地反转 vector / string / char[]

B. 指针与 new

p->next 应当被视为一个指针。

-> 是指针用来访问结构体内成员的,所以实际上 p->next 表示的是指针 p 指向的结构体中的 next 成员,而 next 成员正是一个指针,因此,p->next 应当被视为一个指针。

指针与 new

C. map 的基本使用

map的基本使用

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值