剑指offer 链表

本文介绍了链表的各种操作,包括JZ系列的删除链表节点、从尾到头打印链表、反转链表、合并两个排序链表、找到链表的公共节点、环的入口节点、倒数第k个节点、复杂链表的复制以及删除重复节点的方法。涉及空间复杂度和时间复杂度的优化策略。
摘要由CSDN通过智能技术生成

链表

JZ18 删除链表的节点

给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。返回删除后的链表的头节点。
1.此题对比原题有改动。
2.题目保证链表中节点的值互不相同。
3.该题只会输出返回的链表和结果做对比,所以若使用 C 或 C++ 语言,你不需要 free 或 delete 被删除的节点。
数据范围:0<=链表节点值<=10000,0<=链表长度<=10000

用p记录q的前一个指针,若q的值等于val,把p.next指向q.next,但头结点值等于val需要注意。

class Solution {
public:
    ListNode* deleteNode(ListNode* head, int val) {
        ListNode* p = head;
        ListNode* q = head->next;
        if(head->val == val)
            return head->next;
        while(q->val != val){
            p = p->next;
            q= q->next;
        }
        p->next = q->next;
        return head;
    }
};
class Solution:
    def deleteNode(self , head: ListNode, val: int) -> ListNode:
        p = head
        q = head.next
        if p.val == val:
            return head.next
        while q.val != val or q == None:
            p = p.next
            q = q.next
        if q.val == val:
            p.next = q.next
        return head

JZ6 从尾到头打印链表

输入一个链表的头节点,按链表从尾到头的顺序返回每个节点的值(用数组返回)。
在这里插入图片描述
1.从头遍历一遍链表放入数组,然后反转数组
2.递归遍历

class Solution {
public:
    vector<int> result;
    void printlist(ListNode* head){
        if(head == nullptr)
            return ;
        printlist(head->next);
        result.push_back(head->val);
    }
    vector<int> printListFromTailToHead(ListNode* head) {
        printlist(head);
        return result;
    }
};
class Solution:
    def printListFromTailToHead(self, head: ListNode) -> List[int]:
        result = []
        while head:
            result.append(head.val)
            head = head.next
        return result[::-1]

JZ24 反转链表

给定一个单链表的头结点pHead(该头节点是有值的,比如在下图,它的val是1),长度为n,反转该链表后,返回新链表的表头。

数据范围: 0≤n≤1000
要求:空间复杂度 O(1),时间复杂度 O(n)。

如当输入链表{1,2,3}时,
经反转后,原链表变为{3,2,1},所以对应的输出为{3,2,1}。
以上转换过程如下图所示:
在这里插入图片描述
p是当前指针,p_next是下一个指针,让p_next->next=p,这时还需要tmp记录p_next原有的下一个指针,循环直到p_next为空。

class Solution {
public:
    ListNode* ReverseList(ListNode* pHead) {
        if(pHead==nullptr)
            return pHead;
        ListNode* p = pHead;
        ListNode* p_next = pHead->next;
        p->next= nullptr;
        while(p_next){
            ListNode* tmp = p_next->next;
            p_next->next = p;
            p = p_next;
            p_next = tmp;
        }
        return p;
    }
};
class Solution:
    def ReverseList(self , head: ListNode) -> ListNode:
        if head==None:
            return head
        tmp = head
        tmp_next = head.next
        head.next = None
        while tmp_next:
            p = tmp_next.next
            tmp_next.next = tmp
            tmp = tmp_next
            tmp_next = p
        return tmp

JZ25 合并两个排序的链表

输入两个递增的链表,单个链表的长度为n,合并这两个链表并使新链表中的节点仍然是递增排序的。
数据范围:0≤n≤1000,−1000≤节点值≤1000
要求:空间复杂度 O(1),时间复杂度 O(n)
如输入{1,3,5},{2,4,6}时,合并后的链表为{1,2,3,4,5,6},所以对应的输出为{1,2,3,4,5,6},转换过程如下图所示:
在这里插入图片描述
1.迭代:新记录一个头结点是两个头结点小的,然后头结点后移,判断哪个小就接在后面,直到有一个链表为空,把非空的链表接在后面
2.递归:如果p1<p2则p1的下一个是p1原有的下一个结点和p2中小的哪个,返回p1。若p1或p2为空,则返回不空的结点。

// 递归
class Solution {
public:
    ListNode* Merge(ListNode* pHead1, ListNode* pHead2) {
        if(pHead1 == nullptr)
            return pHead2;
        if(pHead2 == nullptr)
            return pHead1;
        if(pHead1->val < pHead2->val){
            pHead1->next = Merge(pHead1->next, pHead2);
            return pHead1;
        }
        else{
            pHead2->next = Merge(pHead1, pHead2->next);
            return pHead2;
        }
    }
};
# 迭代
class Solution:
    def Merge(self , pHead1: ListNode, pHead2: ListNode) -> ListNode:
        if pHead1 == None:
            return pHead2
        if pHead2 == None:
            return pHead1
        if pHead1.val < pHead2.val:
            pHead = pHead1
            pHead1 = pHead1.next
        else:
            pHead = pHead2
            pHead2 = pHead2.next
        p = pHead
        while pHead1 and pHead2:
            if pHead1.val < pHead2.val:
                p.next = pHead1
                pHead1 = pHead1.next
            else:
                p.next = pHead2
                pHead2 = pHead2.next
            p = p.next
        if pHead1!=None:
            p.next = pHead1
        if pHead2!=None:
            p.next = pHead2
        return pHead

JZ52 两个链表的第一个公共结点

输入两个无环的单向链表,找出它们的第一个公共结点,如果没有公共节点则返回空。(注意因为传入数据是链表,所以错误测试数据的提示是用其他方式显示的,保证传入数据是正确的)
数据范围: n≤1000
要求:空间复杂度 O(1),时间复杂度 O(n)
例如,输入{1,2,3},{4,5},{6,7}时,两个无环的单向链表的结构如下图所示:
在这里插入图片描述
有公共结点:他俩终会有一次会在公共结点相遇,同样的速度走同样的距离。
无公共结点:他俩终会都走到终点(null),也是相等。

class Solution {
public:
    ListNode* FindFirstCommonNode( ListNode* pHead1, ListNode* pHead2) {
        ListNode* p1 = pHead1;
        ListNode* p2 = pHead2;
        while(pHead1 != pHead2){
            if(pHead1==nullptr)
                pHead1 = p1;
            else
                pHead1 = pHead1->next;
            if(pHead2==nullptr)
                pHead2 = p2;
            else
                pHead2 = pHead2->next;
        }
        return pHead1;
    }
};
class Solution:
    def FindFirstCommonNode(self , pHead1 , pHead2 ):
        p1 = pHead1
        p2 = pHead2
        while p1 != p2:
            p1 = p1.next if p1 else pHead1
            p2 = p2.next if p2 else pHead2
        return p1

JZ23 链表中环的入口结点

给一个长度为n链表,若其中包含环,请找出该链表的环的入口结点,否则,返回null。
数据范围: n≤10000,1<=结点值<=10000
要求:空间复杂度 O(1),时间复杂度 O(n)
例如,输入{1,2},{3,4,5}时,对应的环形链表如下图所示:

hash:记录第一次重复的结点:通过使用set或者map来存储已经遍历过的结点,当第一次出现重复的结点时,即为入口结点。
快慢指针:通过定义slow和fast指针,slow每走一步,fast走两步,若是有环,则一定会在环的某个结点处相遇(slow == fast),根据下图分析计算,可知从相遇处到入口结点的距离与头结点与入口结点的距离相同。
在这里插入图片描述

// hash 空间复杂度O(n)
class Solution {
public:
    ListNode* EntryNodeOfLoop(ListNode* pHead) {
        set<ListNode*> record;
        while(pHead){
            if(record.find(pHead)==record.end()){
                record.insert(pHead);
                pHead = pHead->next;
            }
            else{
                return pHead;
            }
        }
        return pHead;
    }
};
# 双指针
class Solution:
    def EntryNodeOfLoop(self, pHead):
        slow = pHead
        fast = pHead
        while fast != None and fast.next != None:
            slow = slow.next
            fast = fast.next.next
            if slow == fast:
                break
        if fast == None or fast.next == None:
            return None
        fast = pHead
        while fast != slow:
            fast = fast.next
            slow = slow.next
        return slow

JZ22 链表中倒数最后k个结点

输入一个长度为 n 的链表,设链表中的元素的值为 ai ,返回该链表中倒数第k个节点。
如果该链表长度小于k,请返回一个长度为 0 的链表。
数据范围:0≤n≤105,0≤ai≤109,0≤k≤109
要求:空间复杂度 O(n),时间复杂度 O(n)
进阶:空间复杂度 O(1),时间复杂度 O(n)
例如输入{1,2,3,4,5},2时,对应的链表结构如下图所示:
在这里插入图片描述
其中蓝色部分为该链表的最后2个结点,所以返回倒数第2个结点(也即结点值为4的结点)即可,系统会打印后面所有的节点来比较。

数组:数组记录所有元素,返回倒数第k个,但空间复杂度 O(n)
快慢指针:让快指针先走k步,慢指针再出发,当快指针到终点,慢指针就是倒数第k个结点。

// 快慢指针
public:
    ListNode* FindKthToTail(ListNode* pHead, int k) {
        if(pHead==nullptr || k == 0)
            return nullptr;
        ListNode* p = pHead;
        while(k--){
            if(p==nullptr)
                return nullptr;
            p = p->next;
        }
            ListNode* q = pHead;
        while(p){
            p = p->next;
            q = q->next;
        }
        return q;
        // write code here
    }
};
class Solution:
    def FindKthToTail(self , pHead: ListNode, k: int) -> ListNode:
        if pHead==None or k == 0:
            return None
        result = []
        while pHead:
            result.append(pHead)
            pHead = pHead.next
        if len(result) < k:
            return None
        return result[-k]

JZ35 复杂链表的复制

输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针random指向一个随机节点),请对此链表进行深拷贝,并返回拷贝后的头结点。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)。 下图是一个含有5个结点的复杂链表。图中实线箭头表示next指针,虚线箭头表示random指针。为简单起见,指向null的指针没有画出。
在这里插入图片描述
示例:
输入:{1,2,3,4,5,3,5,#,2,#}
输出:{1,2,3,4,5,3,5,#,2,#}
解析:我们将链表分为两段,前半部分{1,2,3,4,5}为ListNode,后半部分{3,5,#,2,#}是随机指针域表示。
以上示例前半部分可以表示链表为的ListNode:1->2->3->4->5
后半部分,3,5,#,2,#分别的表示为
1的位置指向3,2的位置指向5,3的位置指向null,4的位置指向2,5的位置指向null
如下图:
在这里插入图片描述
借助哈希表:先把单链复制下来,然后把结点值和结点对应在哈希表里,再循环原链把random拷贝下来。
链表拼接拆分:先把每个结点都复制一份插入链表,然后把random指针复制下来(复制的random位置再原rnadom的下指针),最后把链表复制结点和原节点的指针断卡连上下一个结点。
在这里插入图片描述

class Solution {
public:
    RandomListNode* Clone(RandomListNode* pHead) {
        if(pHead==nullptr)
            return nullptr;
        RandomListNode* p_head = pHead;
        while(p_head){
            RandomListNode* tmp = new RandomListNode(p_head->label);
            tmp->next = p_head->next;
            p_head->next = tmp;
            p_head = tmp->next;
        }
        p_head = pHead;
        while(p_head){
            RandomListNode* tmp = p_head->random;
            if(tmp==nullptr)
                p_head->next->random = nullptr;
            else
                p_head->next->random = tmp->next;
            if(p_head->next)
                p_head = p_head->next->next;
        }
        RandomListNode* result = pHead->next;
        p_head = pHead;
        RandomListNode* clone = pHead->next;
        while(p_head){
            if(p_head->next)
                p_head->next = p_head->next->next;
            if(clone->next)
                clone->next = clone->next->next;
            p_head = p_head->next;
            clone = clone->next;
        }
        return result;
    }
};
class Solution:
    def Clone(self, pHead):
        if pHead == None:
            return pHead
        p = RandomListNode(pHead.label)
        tmp = pHead.next
        dictionary = dict()
        dictionary[p.label] = p
        phead1 = p
        while tmp:
            q = RandomListNode(tmp.label)
            dictionary[tmp.label] = q
            p.next = q
            p = p.next
            tmp = tmp.next
        tmp = pHead
        p = phead1
        while tmp:
            random = tmp.random
            if random == None:
                p.random = None
            else:
                p.random = dictionary[random.label]
            tmp = tmp.next
            p = p.next
        return phead1

JZ76 删除链表中重复的结点

在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表 1->2->3->3->4->4->5 处理后为 1->2->5
数据范围:链表长度满足 0≤n≤1000,链表中的值满足1≤val≤1000
进阶:空间复杂度 O(n),时间复杂度 O(n)
例如输入{1,2,3,3,4,4,5}时,对应的输出为{1,2,5},对应的输入输出链表如下图所示:
在这里插入图片描述
暴力:循环一遍记录所有的重复结点值,再循环若结点在重复结点里,删除这个结点。注:需要设一个头结点。
进阶:遍历单链表的时候,检查当前节点与下一点是否为相同值,如果相同,继续查找祥同值的最大长度,然后指针改变指向。

class Solution {
public:
    ListNode* deleteDuplication(ListNode* pHead) {
        ListNode* root = new ListNode(-1);
        root->next = pHead;
        ListNode* p = pHead;
        ListNode* prep = root;
        while(p){
            if(p->next && p->val == p->next->val){
                p = p->next;
                while(p->next && p->val == p->next->val){
                    p = p->next;
                }
                p = p->next;
                prep->next = p;
            }
            else{
                prep = p;
                p = p->next;
            }
        }
        return root->next;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值