【链表复习】C++ 链表复习及题目解析 (3)

news2024/11/27 10:31:40

目录

剑指offer 中的链表题目

JZ6 从尾到头打印链表

JZ18 删除链表的结点

JZ24 反转链表

JZ25 合并两个排序的链表

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

JZ23 链表中环的入口结点

JZ22 链表中倒数第k 个结点

JZ35 复杂链表的复制

JZ76 删除链表中重复的结点

本次给大家带来所有的在剑指Offer 中的题目,请大家在学习之余也复习一下链表的相关知识。

剑指offer 中的链表题目

JZ6 从尾到头打印链表

思路:

我们都知道链表无法逆序访问,那肯定无法直接遍历链表得到从尾到头的逆序结果。但是我们都知道递归是到达底层后才会往上回溯,因此我们可以考虑递归遍历链表,因此三段式如下:

  • 终止条件: 递归进入链表尾,即节点为空节点时结束递归。

  • 返回值: 每次返回子问题之后的全部输出。

  • 本级任务: 每级子任务递归地进入下一级,等下一级的子问题输出数组返回时,将自己的节点值添加在数组末尾。

具体做法:

  • step 1:从表头开始往后递归进入每一个节点。

  • step 2:遇到尾节点后开始返回,每次返回依次添加一个值进入输出数组。

  • step 3:直到递归返回表头。

解法1:

 void _printListFromTailToHead(ListNode* head, vector<int>& res){
 if(head) {
 _printListFromTailToHead(head->next, res);
 res.push_back(head->val);
 }
 }
 vector<int> printListFromTailToHead(ListNode* head) {
 vector<int> res;
 _printListFromTailToHead(head, res);
 return res;
 }

解法2:

我先从前往后迭代,而后反转链表,也可以。

 /**
 * struct ListNode {
 *       int val;
 *       struct ListNode *next;
 *       ListNode(int x) :
 *             val(x), next(NULL) {
 *       }
 * };
 */
 class Solution {
 public:
     vector<int> printListFromTailToHead(ListNode* head) {
         vector<int> ret;
         while(head){
             ret.push_back(head->val);
             head = head->next;
        }
         int n = ret.size();
         int left = 0, right = n - 1;
         while(left < right){
             int tmp = ret[left];
             ret[left] = ret[right];
             ret[right] = tmp;
             left++, right--;
        }
         return ret;
    }
 };
 ​

解法3: 使用栈 先正序把val 输入到栈中,然后取栈顶元素放入vector中。

JZ18 删除链表的结点

 /**
  * struct ListNode {
  *int val;
  *struct ListNode *next;
  *ListNode(int x) : val(x), next(nullptr) {}
  * };
  */
 class Solution {
 public:
     /**
      * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
      *
      * 
      * @param head ListNode类 
      * @param val int整型 
      * @return ListNode类
      */
     ListNode* deleteNode(ListNode* head, int val) {
         if(!head) return nullptr;
         // write code here
         ListNode* newHead = new ListNode(-1);
         newHead->next = head;
         for(ListNode* node = newHead; node->next ; node = node->next){
             if(node->next->val == val) node->next = node->next->next;
        }
         return newHead->next;
    }
 };

JZ24 反转链表

链表的反转是老生常谈的一个问题了,同时也是面试中常考的一道题。最简单的一种方式就是使用栈,因为栈是先进后出的。实现原理就是把链表节点一个个入栈,当全部入栈完之后再一个个出栈,出栈的时候在把出栈的结点串成一个新的链表。

 /**
  * struct ListNode {
  *int val;
  *struct ListNode *next;
  *ListNode(int x) : val(x), next(nullptr) {}
  * };
  */
 class Solution {
 public:
     /**
      * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
      *
      * 
      * @param head ListNode类 
      * @return ListNode类
      */
     ListNode* ReverseList(ListNode* head) {
         // write code here
 stack<ListNode*> s;
 while(head){
 s.push(head);
 head = head->next;
 }
 ListNode* newHead = new ListNode(-1);
 ListNode* node = newHead;
 while(!s.empty()){
 ListNode* top = s.top();
 node->next = top;
 top->next = nullptr;
 node = top;
 s.pop();
 }
 return newHead->next;
    }
 };
 ​

双指针解法:

 /*
 struct ListNode {
 int val;
 struct ListNode *next;
 ListNode(int x) :
 val(x), next(NULL) {
 }
 };*/
 class Solution {
 public:
     ListNode* ReverseList(ListNode* pHead) {
 if(!pHead || !pHead->next){
 return pHead;
 }
 ListNode* curr = pHead;
 ListNode* prev = nullptr;
 while(curr){
 ListNode* next = curr->next;
 curr->next = prev;
 prev = curr;
 curr = next;
 }
 return prev;
    }
 };

JZ25 合并两个排序的链表

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

思路:升序排列的方法就是比较两个链表的结点中val 的大小,取小的一个尾插到新链表中。注意在这题中可以使用虚拟头结点来简化过程。

我的解法1:

 class Solution {
 public:
     ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
         ListNode* pcurr1 = list1;
         ListNode* pcurr2 = list2;
         ListNode* head = new ListNode;
         ListNode* newCurr = head;
         while(pcurr1 && pcurr2){
             if(pcurr1->val > pcurr2->val){
                 newCurr->next = pcurr2;
                 pcurr2 = pcurr2->next;
            }else{
                 newCurr->next = pcurr1;
                 pcurr1 = pcurr1->next;
            }
             newCurr = newCurr->next;
        }
       //来判断到底是谁终止了,直接修改newCurr 到未终止的结点的指向即可。
         newCurr->next = (pcurr1 == nullptr) ? pcurr2 : pcurr1;
         return head->next;
    }
 };

我的解法2: 递归如果 list1 或者 list2 一开始就是空链表 ,那么没有任何操作需要合并,所以我们只需要返回非空链表。否则,我们要判断 list1 和 list2 哪一个链表的头节点的值更小,然后递归地决定下一个添加到结果里的节点。如果两个链表有一个为空,递归结束。

 class Solution {
 public:
     ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
         if(!list1)  return list2;
         else if(!list2) return list1;
         else if(list1->val < list2->val){
             list1->next = mergeTwoLists(list1->next, list2);
             return list1;
        }else{
             list2->next = mergeTwoLists(list1, list2->next);
             return list2;
        }
    }
 };

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

思路:我们可以采用简单的双指针法,可以假设A 长度为m, B 长度为n,如果相交,A 和 B相交片段长度为 x, 不相交的片段长度分别为a 和b。

那么如果我们采用双指针法:

当链表 headA 和 headB 都不为空时,创建两个指针 pA和 pB,初始时分别指向两个链表的头节点 headA 和 headB,然后将两个指针依次遍历两个链表的每个节点。具体做法如下:

  • 每步操作需要同时更新指针 pA 和 pB。

  • 如果指针 pA不为空,则将指针 pA 移到下一个节点;如果指针 pB不为空,则将指针 pB 移到下一个节点。

  • 如果指针 pA为空,则将指针 pA 移到链表 headB的头节点;如果指针 pB 为空,则将指针 pB 移到链表 headA 的头节点。

  • 当指针 pA 和 pB 指向同一个节点或者都为空时,返回它们指向的节点或者 null。

那么如果二者相交,则 m = a + x, n = b + x。pA 和 pB 一定在走过 a + b + x 长度时相等。如果二者不相交,则一定不会出现相等(可以分类讨论)。

 /*
 struct ListNode {
 int val;
 struct ListNode *next;
 ListNode(int x) :
 val(x), next(NULL) {
 }
 };*/
 class Solution {
 public:
     ListNode* FindFirstCommonNode( ListNode* pHead1, ListNode* pHead2) {
 if(!pHead1 || !pHead2) return nullptr;
         ListNode* pA = pHead1;
 ListNode* pB = pHead2;
 while(pA != pB){
 if(pA == nullptr) pA = pHead2;
 else pA = pA->next;
 if(pB == nullptr) pB = pHead1;
 else pB = pB->next;
 }
 if(pA == nullptr) return nullptr;
 else return pA;
 }
 };

JZ23 链表中环的入口结点

环的题目中,我们知道如何判断是否有环(快慢指针),那么同样的,入口结点也可以用快慢指针解决。只需要:

  • 让快慢指针从链表头出发,快2慢1,如果有环最终必会相遇(这也是判断是否有环的方法)

  • 将快指针回调回链表头,开始快慢都每次各走一步,相遇点即为入口结点。

下面是证明方法:

假设 环如图所示:

 

fast = a + b + n * (b + c); slow = a + b;

同时,fast = 2 slow; --> a = (n - 1)b + n * c;

所以如果让 fast 置为链表头,一次走一步,走 a 长度的,那么 slow 也会走 a 长度,因为 a = (n - 1)b + n * c,因为slow 原本在 a + b 位置, 所以相加,正好得到: a + a + b = (n - 1)b + n * c + a + b = a + n (b + c); 所以恰好会在链表环入口处相遇。

 ​
 /*
 struct ListNode {
     int val;
     struct ListNode *next;
     ListNode(int x) :
         val(x), next(NULL) {
     }
 };
 */
 class Solution {
 public:
     ListNode* EntryNodeOfLoop(ListNode* pHead) {
         ListNode* fast = pHead;
         ListNode* slow = pHead;
         while(fast){
             slow = slow->next;
             if(fast->next) fast = fast->next->next;
             else return nullptr;
             if(fast == slow){
                 fast = pHead;
                 while(fast != slow){
                     fast = fast->next;
                     slow = slow->next;
                }
                 return fast;
            }
        }
         return nullptr;
    }
 };

JZ22 链表中倒数第k 个结点

我们需要找倒数第k 个节点,那么完全也可以利用双指针法去解,让快指针先走 k步,然后快慢同步走,快指针走到链表尾的时候,由于慢指针和快指针相差k 步,所以慢指针指向的就是第k 个结点。

 /**
  * struct ListNode {
  *int val;
  *struct ListNode *next;
  *ListNode(int x) : val(x), next(nullptr) {}
  * };
  */
 class Solution {
 public:
     /**
      * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
      *
      * 
      * @param pHead ListNode类 
      * @param k int整型 
      * @return ListNode类
      */
     ListNode* FindKthToTail(ListNode* pHead, int k) {
         // write code here
         if(!pHead || k <= 0) return nullptr;
         ListNode* pfast = pHead;
         ListNode* pslow = pHead;
         for(int i = k; i > 0; --i){
             if(pfast) pfast = pfast->next;
             else if(!pfast && i == 0) return pHead;
             else return nullptr;
        }
         while(pfast){
             pfast = pfast->next, pslow = pslow->next;
        }
         return pslow;
    }
 };

JZ35 复杂链表的复制

我们首先将该链表中每一个节点拆分为两个相连的节点,例如对于链表 A→B→C,我们可以将其拆分为 A→A′→B→B′→C→C′ 。对于任意一个原节点 S,其拷贝节点 S′ 即为其后继节点。

这样,我们可以直接找到每一个拷贝节点 S′ 的随机指针应当指向的节点,即为其原节点 SSS 的随机指针指向的节点 T 的后继节点 T‘ 。需要注意原节点的随机指针可能为空,我们需要特别判断这种情况。

当我们完成了拷贝节点的随机指针的赋值,我们只需要将这个链表按照原节点与拷贝节点的种类进行拆分即可,只需要遍历一次。同样需要注意最后一个拷贝节点的后继节点为空,我们需要特别判断这种情况。

复杂度分析

时间复杂度:O(n),其中 n 是链表的长度。我们只需要遍历该链表三次。读者们也可以自行尝试在计算拷贝节点的随机指针的同时计算其后继指针,这样只需要遍历两次。 空间复杂度:O(1)。注意返回值不计入空间复杂度。

 /*
 // 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* newNode = 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;
             newNode->random = (node->random != nullptr) ? node->random : nullptr;
             //修改新链表random的指向
        }
         Node* headNew = head->next;
         for(Node* node = head; node != nullptr; node = node->next){
             Node* nodeNew = node->next; 
             node->next = node->next->next; //修改原链表的next。
             nodeNew->next = (nodeNew->next != nullptr) ? nodeNew->next->next : nullptr; 
             //修改新链表的next指向
        }
         return headNew;
    }
 };

JZ76 删除链表中重复的结点

注意读题,是排序的链表,所以我们可以使用双指针法,从左往右,如果left 所指的值和 right 所指是否相等,如果相等那么right 右移, 看看新的right 是否也符合相等条件,直至不符合条件,让前面的prev (解答中的node)指向新 right 即可,就是因为我们需要修改前面的指向,所以需要node 变量来记录之前的位置。也恰好是因为prev 不方便,所以我们使用哨兵位头节点来简化。如果不等,那么直接node,left,right都向右移动一个。

 ​
 /*
 struct ListNode {
     int val;
     struct ListNode *next;
     ListNode(int x) :
         val(x), next(NULL) {
     }
 };
 */
 class Solution {
 public:
     ListNode* deleteDuplication(ListNode* pHead) {
         ListNode* newHead = new ListNode(-1);
         newHead->next = pHead;
         for(ListNode* node = newHead; node->next && node->next->next;){
             ListNode* left = node->next;
             ListNode* right = left->next;
             if(left->val != right->val){
                 node = node->next, left = left->next, right = right->next;
            }else{
                 while(right && left->val == right->val) right = right->next;
                 node->next = right;
            }
        }
         return newHead->next;
    }
 };

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/651003.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

用注解实现方法开关

一、自定义注解的基本使用 java.lang.annotation 提供了四种元注解&#xff0c;专门注解其他的注解&#xff08;在自定义注解的时候&#xff0c;需要使用到元注解&#xff09;&#xff1a; Documented – 注解是否将包含在JavaDoc中 Retention – 什么时候使用该注解 Target –…

【算法】动态规划-斐波那契模型

文章目录 结论斐波那契模型第 N 个泰波那契数三步问题使用最小花费爬楼梯**方法1&#xff1a;**以i位置为结尾....方法2&#xff1a;以i位置为起点.... 解码方法 结论 对于线性dp&#xff0c;一般是用经验题目要求来定义状态表示&#xff1a; 以某个位置为结尾…以某个位置为…

React 通过一个输入内容加入列表案例熟悉 Hook 基本使用

我们创建一个react项目 在src下创建components文件夹 在下面创建一个index.jsx index.jsx 参考代码如下 import React, { useState } from "react";const useInputValue (initialValue) > {const [value,setValue] useState(initialValue);return {value,onCha…

【2023,学点儿新Java-01】从查看本机 jdk版本 开始 | Java基础全程脉络图、Java工程师全程技术路线、Java职业晋升路线图

一个人把生命耗尽&#xff0c;应该是为了一些美好的东西&#xff0c;值得的东西&#xff0c;用蓬勃如烈火的生命力 去战胜一个又一个人生的悲剧&#xff0c;这本身就是人生的意义之一吧&#xff01; &#x1f3af;作者主页&#xff1a; 追光者♂&#x1f525; &#x1f338;个人…

Win10 系统专业版远程桌面如何才能多用户同时登录使用?

环境&#xff1a; Win10专业版19041 RDPWrap-v1.6.2 dell5493笔记本 问题描述&#xff1a; Win10 系统专业版远程桌面如何才能多用户同时登录使用&#xff1f; 解决方案&#xff1a; 安装RDPWrap 1.关闭remote desktop services服务 安装RDP之前&#xff0c;要先关闭re…

8年性能测试工程师告诉你,压力测试详解,如何做压力测试...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 什么是压力测试&a…

灯塔工厂PLC智慧边缘计算机嵌入式linux系统

随着工业物联网的发展&#xff0c;越来越多的企业开始将工业自动化设备接入到物联网平台&#xff0c;以实现设备远程监控、运维和管理。其中&#xff0c;PLC智能网关作为连接PLC和物联网平台的重要设备&#xff0c;发挥着至关重要的作用。本文将介绍PLC数据采集控制网关的功能和…

文心一言 VS 讯飞星火 VS chatgpt (42)-- 算法导论5.4 6题

六、假设将n个球投人 n 个箱子里&#xff0c;其中每次投球独立&#xff0c;并且每个球等可能落入任何箱子。空箱子的数目期望是多少?正好有一个球的箱子的数目期望是多少? 文心一言&#xff1a; 这是一个典型的概率问题。首先&#xff0c;我们可以通过简单的计算来理解问题…

【论文】YOLOv7运行train.py和test.py时,打印不出来计算量GFLOPs解决方法

目录 前提案例解决步骤运行方式打印效果 前提 我在AutoDL云服务器上&#xff0c;会运行失败&#xff0c;不晓得为啥&#xff0c;我感觉可能是CUDA版本的问题&#xff0c;CUDA版本低了就会没有那么深的计算量吧 AutoDL上&#xff08;显卡RTX3090&#xff0c;4GB&#xff09;安…

java的网络编程

一、网络编程的三要素 IP、端口号、协议。 特殊IP地址127.0.0.1&#xff0c;也可以是localhost&#xff0c;是回送地址也称回环地址&#xff0c;也称本机IP&#xff0c;永远只会寻找当前所在本机。 常见cmd命令&#xff1a; ipconfig:查看本机IP地址&#xff1b; ping:检查网络…

Innovus: dbGet 快速学习教程

dbGet是innovus/encounter工具自带的"database access command"命令中的一部分&#xff0c;它几乎可以用来获取设计相关的一切信息。 输入dbGet 按[Tab]键&#xff0c;能看到三个选项&#xff0c;分别是head / top /selected。这三个选项所代表的意义如下: head --…

ubuntu 22.04安装mysql 8.0与避坑指南

MySQL 是一个开源数据库管理系统&#xff0c;可作为流行的 LAMP&#xff08;Linux、Apache、MySQL、PHP/Python/Perl&#xff09;堆栈的一部分安装。 它实现了关系模型并使用结构化查询语言&#xff08; SQL&#xff09;来管理其数据。 本教程将介绍如何在 Ubuntu 22.04 服务器…

appium+python在Android端的环境配置

一、安装配置JDK 一、安装环境 1、本机系统&#xff1a;Windows 10&#xff08;64位&#xff09; 2、JDK版本&#xff1a;1.8&#xff08;64位&#xff09; 二、下载安装 1、JDK和JRE简介 Java环境分JDK和JRE &#xff0c;JDK就是Java Development Kit。简单的说JDK是面向…

JMeter安装图文及入门教程,(附视频教程)

目录 一、JMeter介绍 二、下载配置安装 三、JMeter入门压测实例 总结&#xff1a; 一、JMeter介绍 JMeter是Apache组织开发的基于Java的压力测试工具。用于对软件做压力测试&#xff0c;它最初被设计用于Web应用测试&#xff0c;但后来扩展到其他测试领域。它可以用于测试静…

Go语言并发之context标准库

1、Go语言并发之context标准库 Go中的 goroutine 之间没有父与子的关系&#xff0c;也就没有所谓子进程退出后的通知机制&#xff0c;多个 goroutine 都是平行地 被调度&#xff0c;多个 goroutine 如何协作工作涉及通信、同步、通知和退出四个方面。 通信&#xff1a;chan 通…

ResNet

论文信息 论文名称&#xff1a;Deep Residual Learning for Image Recognition 论文地址&#xff1a;https://arxiv.org/pdf/1512.03385.pdf 发表期刊&#xff1a;CVPR 发表年份&#xff1a;2016 主要问题 在引言中作者提出了一个问题&#xff1a;训练一个更好的网络是否像堆…

这个网站,多希望你早点知道,越早越好!

这是一个有趣、神奇的个人博客网站。 这是一个马斯克经常上的网站&#xff0c;而且马斯克还在推特上关注了这个网站的账号。 网站地址&#xff1a;https://waitbutwhy.com/ 这个网站上的内容并不多&#xff0c;网站2013年创建的&#xff0c;至今已有10年&#xff0c;一共才产出…

python爬虫工程师,如何从零开始部署Scrapyd+Feapder+Gerapy?

突然被告知要连着上整整十一天的班&#xff0c;有一点点累&#xff0c;简单更新一下内容吧&#xff0c;水个积分 关注公众号&#xff1a;python技术训练营&#xff0c;精选优质文档&#xff0c;好玩的项目 内容&#xff1a; 1.面试专题几十个大厂面试题 2.入门基础教程 3.11模块…

活动邀请函五秒钟下载即用

在日常中&#xff0c;人们都是以纸质的邀请函发送给被邀请者&#xff0c;不仅需要花费大量的精力和时间去书写发送活动邀请函&#xff0c;还存在着被邀请人没有及时收到活动邀请函而错过参与的时间等。而这样只需制作一份就可以全网分享&#xff0c;用户短时间就能收到活动邀请…

可变参数列表

"多少人都&#xff0c;生来纯洁完美&#xff0c;心底从不染漆黑。" 我们想要实现一个函数&#xff0c;这个函数的功能是返回一个整形的最大值。 emm&#xff0c;似乎有那点味道。但这应用场景似乎很受限制&#xff0c;因为这个函数比较的有效区间&#xff0c;只能装下…