剑指offer--链表(2)

文章介绍了两种方法来处理链表问题:1)使用双指针检测链表中的环入口节点;2)利用哈希表优化复杂链表的复制,包括处理随机指针。
摘要由CSDN通过智能技术生成

链表中环的入口结点_牛客题霸_牛客网 (nowcoder.com)

方法有两种:1 双指针 

                      2 哈希表

我们设链表在进入环之前的长度为a,也就是1->2;链表在进入环后的长度为b,也就是3->4->5->3。 同时设块指针的速度是慢指针的两倍。

那么在相遇的时候,说明两个指针以及进入环了,而且此时设快指针走了f步,慢指针走了s步,那么:

  • f = 2 * s (快指针走过的节点数一定是慢指针的两倍)
  • f = s + nb (当两者相遇时,快指针一定已经绕环走了n圈)

有s = nb,f = 2nb。那么慢指针走了nb步,而要进入环的入口,要走a+lb步(l为整数),也就是说,此时慢指针只需要走a步就可到达环的入口,那么我们不妨把快指针移动到表头,然后一步一步走a步,此时快慢指针必定相遇,就找到了环入口。

class Solution {
public:
    ListNode* EntryNodeOfLoop(ListNode* pHead) {
        if (pHead==nullptr) return nullptr;
 
        ListNode* fast = pHead;
        ListNode* slow = pHead;
 
        while (fast!=nullptr && fast->next!=nullptr)    //注意空值
        {
            fast = fast->next->next;     
            slow = slow->next;
 
            if (fast==slow) break;    //找到第一次相遇的地方
     
        }
         
        if (fast==nullptr || fast->next==nullptr) return nullptr;   //相遇的地方不能是空以及要成环
 
        fast = pHead;    //然后快指针从头开始一步一步到入口
        while (fast!=slow)
        {
            fast=fast->next;
            slow=slow->next;
        }
    return fast;
    }
};

方法2:哈希,遍历把链表节点与值存入哈希表中,记录节点出现的次数,如果节点对应的次数为2,那么找到环入口。

class Solution {
private:
    unordered_map<ListNode*, int> map;
public:
    ListNode* EntryNodeOfLoop(ListNode* pHead) {
        while (true)
        {
            if (pHead==nullptr) break;
            if (map[pHead]==2)
                return pHead;
            map[pHead]++;
            pHead=pHead->next;
             
        }
    return nullptr;
    }
};

 链表中倒数最后k个结点_牛客题霸_牛客网 (nowcoder.com)

 方法:1 哈希表

             2 哈希表改进版

1:哈希表。遍历链表把节点按顺序标号(比如:a->b->c->d map[a]=1,map[b]=2 ...),那么最后一个节点的号码就是这个链表的长度。再把倒数第几个节点转换成顺数第几个节点此时也就是该节点对应的号码,最后再次遍历链表,找到我们要找的那个号码对应的节点。

class Solution {
private:
    unordered_map<ListNode*, int> map;
public:
    ListNode* FindKthToTail(ListNode* pHead, int k) {
        int i=1;    //标号
        ListNode* pp=pHead;
        while (pp!=nullptr)
        {
            map[pp]=i;    //第一个节点的号码
            i++;
            pp=pp->next;
        }    
        int size=i;         //链表的长度
        int k1 = size-k;    //把要找的倒数第几个转化为顺数的第几个
        if (k1<0 || k==0) return nullptr;    //转化失败
                                             //1)链表长度小于要找的号码;2)0无效号码
 
        pp=pHead;
        while (true)    //找到我们要找的节点
        {
            if (map[pp]==k1)
            break;
            pp = pp->next;
            
        }
     
    return pp;
    }
};

哈希表改进:仔细想想,我们其实只要得到链表的长度就可以了,把要找的倒数第几个节点转化为顺数第几个其实也是建立在链表的长度之上的。

 ListNode* FindKthToTail(ListNode* pHead, int k) {
       //这里从0开始
        int size=0;
        ListNode* pp=pHead;
        while (pp)
        {
            size++;
            pp=pp->next;
        }
        if (size<k) return nullptr;    //如果要找的节点号码大于链表长,那么是不和逻辑的
 
        int k1=size-k;
        pp = pHead;
        for (int i=0;i!=k1;i++)
        {
            pp=pp->next;
        }
    return pp;
    }

复杂链表的复制_牛客题霸_牛客网 (nowcoder.com)

如果是一般的链表拷贝的话,我们只需要在原链表上一步一步向后找,然后把每次的节点链接到新链表后面就行。但是这里多了一个随机指针,也就是说,如果按照老方法,随机指针的位置你是不好处理的。因为链表不支持随机访问。

那么该怎么办呢?

这里参照一个大佬的做法:

  1. 将新链表链接在原表之后,如原1->新1->原2->新2.....
  2. 再处理随机指针,用双指针法,A指向原表,B指向原表下一个(也就是新表)
  3. 那么原表的随机指针就是 原->random ,新表的随机指针是  原->random->next  ,即 原->random = 原->random->next 
  4. 最后再用双指针将两条链表拆分即可

文字难懂用图表达。

解释一下:1的随机指针指向3  | 2的随机指针指向5 |  3的随机指针指向null | 4的随机指针指向2

5的随机指针指向null

这个时候       原->random == 原->random->next  那么我们就可以在原表上修改新表的random指针。

 RandomListNode* Clone(RandomListNode* pHead) {
        if (!pHead) return pHead;
        RandomListNode* cur=pHead;
        while (cur) {
            RandomListNode* tmp = new RandomListNode(cur->label);   //创建新节点
             
            //连接新节点
            tmp->next = cur->next;
            cur->next = tmp;
            cur =tmp->next;
        }
 
        RandomListNode* old = pHead, *clone = pHead->next;
         
        RandomListNode* newclone = pHead->next; //方便最后返回值
         
        //克隆链表的random
        while(old)
        {
            clone->random = old->random == nullptr ? nullptr : old->random->next;  //克隆链表的random
            if (old->next) old = old->next->next;       //注意判断空指针
            if (clone->next) clone = clone->next->next;
        }
 
         
        //链表拆分
        old = pHead,clone = pHead->next;
        while (old)
        {
            if (old->next) old->next = old->next->next;
            if (clone->next) clone->next = clone->next->next;
            old=old->next;clone=clone->next;
        }
    return newclone;
    }

法2:还是我们的老朋友哈希表。我们可以把原表的节点与新表的节点进行一个映射,那么我们在创建新表的时候就直接访问原表的节点的random,然后在对新表的random进行链接。实际上给新链表创建一个“随机访问” 的机会。

RandomListNode* Clone(RandomListNode* pHead) {
        if (pHead==nullptr) return pHead;
 
        unordered_map<RandomListNode*, RandomListNode*> mp;
         
        RandomListNode* newList = new RandomListNode (0);  //创建新链表头节点
        RandomListNode* cur = pHead;        //原始链表头
        RandomListNode* newNode = newList;  //新链表头
 
        while (cur)
        {
            RandomListNode* clone = new RandomListNode(cur->label);
            //记录
            mp[cur] = clone;
            //连接
            newNode->next = clone;
            newNode = newNode->next;
            cur = cur->next;    //遍历
        }
 
        for (auto node:mp)
        {
            if (node.first->random==nullptr)
                node.second->random=nullptr;
            else
                node.second->random = mp[node.first->random];
        }
        return newList->next;
    }

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值