剑指中的链表总结c++

  剑指链表总结

剑指 Offer 06. 从尾到头打印链表

在这里插入图片描述  可以用朴素的数组记录,最后倒序一下就好了。

class Solution {
public:
    vector<int> reversePrint(ListNode* head) {
        vector<int> ans;
        if(head == nullptr){
            return ans;
        }
        while(head!=nullptr){//遍历,都加到数组里面
            ans.push_back(head->val);
            head = head->next;
        }
        reverse(ans.begin(),ans.end());//倒序一下!
        return ans;
    }
};

  当然也可以用栈。

class Solution {
public:
    vector<int> reversePrint(ListNode* head) {
        vector<int> ans;
        stack<ListNode*> mystack;
        if(head == nullptr){
            return ans;
        }
        while(head!=nullptr){//遍历链表,节点放栈里
            mystack.push(head);
            head = head->next;
        }
        while(!mystack.empty()){//从栈里依次取出
            ans.push_back(mystack.top()->val);
            mystack.pop();
        }

        return ans;
    }
};

剑指 Offer 22. 链表中倒数第k个节点

在这里插入图片描述  最好的办法就是快慢指针,让快指针领先慢指针k-1步,随后一起出发,快指针到头时慢指针指向的就是答案。
  为什么是先走k-1步呢?因为题目中k的设定和程序常见的设定不同,最后一个节点不是倒数第0而是倒数第1,所以快指针要少走一步。

class Solution {
public:
    ListNode* getKthFromEnd(ListNode* head, int k) {
        if(head == NULL) return head;
        ListNode* fast = new ListNode(0);
        fast->next = head;
        ListNode* slow = new ListNode(0);
        slow->next = head;//创立快慢指针,都指向头结点
        for(int i=1;i<k;i++){//快指针先移动个k-1次。
            fast = fast->next;
        }
        while(fast->next!=NULL){
            fast = fast->next;
            slow = slow->next;
        }
        return slow;
    }
};

剑指 Offer II 024. 反转链表

在这里插入图片描述
  反转链表,典!
  核心思路是,每次只改变一个节点的一个指向,不要贪心,改变了之后再更新pre和head,直到head到达空节点。

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode* pre = nullptr;
        while(head!=nullptr){
            ListNode* temp = head->next;//记录cur的下一个节点是为了cur位置的移动,因为cur->next之后变了
            head->next = pre;//每次只改变 cur pre之间的关系
            pre = head;//更换pre cur的位置
            head = temp;
        }
        return pre;//cur到了空节点,所以返回pre
    }
};

剑指 Offer 25. 合并两个排序的链表

在这里插入图片描述
  合并排序链表也很典。
  核心思想是留下个哑结点当头部索引,用一个节点去不断扩展链表节点。
  这里为什么要用pre->next遍历而不是cur,是因为这样省去了判断头结点该是哪个链表头结点的麻烦。

class Solution {
public:
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        ListNode* prehead = new ListNode(0);//用于标识头部的哑指针
        ListNode* pre = prehead;//用于遍历,构建新链表的指针

        while(l1!=nullptr&&l2!=nullptr){//如果有一个链表空了就不用比了
            if(l1->val < l2->val){//注意是用pre->next遍历
                pre->next = l1;
                pre = pre->next;
                l1 = l1->next;
            }else{
                pre->next = l2;
                pre = pre->next;
                l2 = l2->next;
            }
        }

        if(l1){//处理剩余串!
            pre->next = l1;
        }
        if(l2){
            pre->next = l2;
        }
        return prehead->next;
    }
};

剑指 Offer 35. 复杂链表的复制

在这里插入图片描述
  复杂链表复制,我们采用哈希表的方式,记录新链表节点和老链表节点的对应关系。
  这里用到了一个哈希表,哈希表的key是老链表的节点,value是新链表上与该老链表节点对应的节点(也就是val值一样)。
  最后的效果是hashmap[newnode]就是老链表上的节点newnode对应的新节点。
  所以新链表节点的下一个节点hashmap[newnode]->next就是hashmap[newnode->next],即老链表对应的节点的下一个节点对应的新节点。
  稍微有点绕可以多想下。

class Solution {
public:
    Node* copyRandomList(Node* head) {
       unordered_map<Node*,Node*> hashmap;//哈希键值对key是老链表当前节点,value是新链表的当前节点,两个节点的val是相等的
       Node* newnode = head;//head的一个复制品

       while(head != NULL){//遍历链表
           Node* temp = new Node(head->val);
           hashmap[head] = temp;//构建键值对,key是当前节点,value是一个当当前节点值相同的新节点,即拷贝节点
           head = head->next;
       } 
       head = newnode;//给head重定位到初始位置,head要当最终答案的头节点索引,所以head不去动了,动newnode
       //给拷贝的新节点进行组装,组装成一个新链表
       while(newnode != NULL){//遍历链表
           hashmap[newnode]->next = hashmap[newnode->next];//等号左边 右边都是拷贝节点
           hashmap[newnode]->random = hashmap[newnode->random];
           newnode = newnode->next; 
       }
       return hashmap[head];
    }
};

剑指 Offer 52. 两个链表的第一个公共节点

在这里插入图片描述  这个题的思路就在图里面。
  如果两个链表确实有交点的话,我们可以抽象将这两个链表拼接起来,然后同时遍历一定能找到相交的节点。

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        if(headA==NULL||headB==NULL){
            return NULL;
        }
        ListNode* cur1 = headA;//定义两个节点,分别对两个链表遍历
        ListNode* cur2 = headB;
        while(cur1 != cur2){//如果不相同就继续遍历
            cur1 = cur1==NULL?headB:cur1->next;//如果走到了空节点就从另一个链表的起点开始!
            cur2 = cur2==NULL?headA:cur2->next;//如果没走到空节点就先遍历着自己!
        }
        return cur1;
    }
};

判断链表是否有环

  判断是否有环,典!

class Solution {
public:
    bool hasCycle(ListNode* head) {
        if(head==NULL||head->next==NULL){
            return false;
        }
        ListNode* slow = head;//慢指针初始化
        ListNode* fast = head->next;//fast要和slow初始的时候错开
        while(fast->next!=NULL&&fast->next->next!=NULL){//fast在前,如果没环肯定是fast先碰到空节点
            if(fast==slow) return true;//如果相撞说明有环
            fast = fast->next->next;//快指针一次移动两步,所以需要提前两步判断是否要结束
            slow = slow->next;//慢指针一次移动一步
        }
        return false;
    }
};

剑指 Offer II 022. 链表中环的入口节点

在这里插入图片描述
  这题蛮有意思,明显感觉和是刚刚的判断是否有环有关。
  这道题已经告诉我们有环了,所以我们使用判断环的函数得到环里的一个节点。不管这个节点是哪个,我们至少清楚它在环里面。
  在环里面,我们让它在环里走一圈,就能得出环的长度。
  通过对图的观察我们发现,如果使用快慢指针,让快指针先走n步,n是环的长度,两个再一起同速走,最后会在环的起点相遇。

class Solution {
public:
    ListNode* getpoint(ListNode* head){//这个函数是判断有无环的,返回环里面的一个节点
        if(head==NULL||head->next==NULL) return NULL;
        ListNode* fast = head->next;
        ListNode* slow = head;
        while(fast->next!=NULL&&fast->next->next!=NULL){
            if(fast == slow) return fast;
            fast = fast->next->next;
            slow = slow->next;
        }
        return NULL;
    }
    ListNode *detectCycle(ListNode *head) {
        ListNode* point = getpoint(head);
        if(point==NULL) return NULL;
        int count = 1;
        for(ListNode* node = point;node->next!=point;node=node->next){//遍历链表,计算环的长度
            count++;
        }
        ListNode* fast = head;
        ListNode* slow = head;
        while(count--){//快指针先走过环的长度
            fast = fast->next;
        }
        while(slow!=fast){//同速遍历然后相遇!
            slow = slow->next;
            fast = fast->next;
        }
        return slow;
    }
};

剑指 Offer 18. 删除链表的节点

  删除定值节点,核心其实是哑结点,有了哑结点可以有效解决头结点是否会删除的问题

class Solution {
public:
    ListNode* deleteNode(ListNode* head, int val) {
        ListNode* dummy = new ListNode(0);
        ListNode* pre = dummy;
        dummy->next = head;
        
        while(head!=NULL){
            if(head->val == val){//如果head需要删除,就让pre直接指向head的下一个节点
                head = head->next;
                pre->next = head;
            }else{//用pre和head遍历链表
                pre = head;
                head = head->next;
            }
        }
        return dummy->next;
    }
};

删除排序链表中的重复元素

在这里插入图片描述
  说到删除链表节点,这不得不喊起来删除重复元素两兄弟。
  第一个兄弟是删除重复的链表节点,令每种只剩一个。
  只剩一个实际上非常好想,我们直接遍历链表,如果当前节点的下一个节点值和当前节点相等,我们就一直删掉下一个节点就好了,最后会将当前节点当做剩下的重复节点。

class Solution {
public:
    ListNode* deleteDuplicates(ListNode* head) {
        if(head==nullptr) return head;
        ListNode* cur = head;//按思路头结点不可能删除,不需要哑指针!
        while(cur->next!=nullptr){//当前节点一直和下一个比
            if(cur->val==cur->next->val){//如果遇到了一样的
                int temp = cur->val;
                while(cur->next!=nullptr&&cur->next->val==temp){//就把后面一样的都删了
                    cur->next=cur->next->next;
                }
            }else{
                cur=cur->next;
            }
        }
        return head;
    }
};

删除排序链表中的重复元素 II

在这里插入图片描述
  第二个重复元素删除他来了。
  这一次要求就比较绝绝子,要把相同的元素斩草除根。
  首先头结点可能是保不住了,这个题肯定是要用哑指针的。
  遇到这种头结点难保的题,其实我们可以用一种稍微定式一些的思路:我们用pre->next遍历链表而不是cur,这样等于是保留了当前节点的前一个节点,这样我们删除当前节点就很方便了!

class Solution {
public:
    ListNode* deleteDuplicates(ListNode* head) {
        if (!head || !head->next)
            return head;
        
        ListNode* dummy = new ListNode(-1);//设置哑节点
        dummy->next = head;//哑结点下一个要指向头结点
        ListNode* pre = dummy;//cur初始化为哑结点,今后的数据比较都是在cur->next和cur->next->next中进行
        while(pre->next&&pre->next->next){
            if(pre->next->next->val == pre->next->val){//出现重复节点
                int temp = pre->next->val;
                while(pre->next&&pre->next->val==temp){//一直跳过重复节点
                    pre->next = pre->next->next;//等于是不断删除当前节点,体现了pre->next遍历的好处!
                }
            }else{
                pre = pre->next;
            }
        }
        return dummy->next;
    }
};

K 个一组翻转链表

在这里插入图片描述
  链表里面也有狠角色这里补充一下。
  拿笔画一画就知道,这个题如果用迭代会很困难。
  所以这里用到了链表的迭代法。
  该迭代的核心思想是先检测能不能凑够k个,如果凑不到就直接返回这一部分的头部;如果能凑够就翻转从head开始的k个节点,此时head是该部分的尾节点,然后通过递归得到下一部分的头结点并加入到当前的尾结点后方。

class Solution {
public:
    ListNode* reverseKGroup(ListNode* head, int k) {
        ListNode* last = head;//这一块用来计算剩下节点能不能到k?如果不能到k直接返回head
        for(int i=0;i<k;i++){
            if(last==nullptr) return head; 
            last = last->next;
        }
        //如果剩下的节点够的话,就把这一组反转了
        ListNode* pre = head;
        ListNode* cur = head->next;
        for(int i=0;i<k-1;i++){//因为head是pre,所以只翻k-1次!
            ListNode* temp = cur->next;
            cur->next = pre;
            pre = cur;
            cur = temp;
        }
        //看出来了吗,本质是链表的后序遍历,从尾向头
        ListNode* next = reverseKGroup(cur,k);//递归得到下一段k个节点反转后的头结点,如果不到k就直接拿原链表
        head->next = next;//当前的head是反转以后的尾节点,给它接上后面的
        return pre; //反转以后头结点是pre,将其返回
    }
};
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值