【手撕数据结构】链表高频面试题

news2024/12/26 11:07:06

目录

  • 移除链表元素
  • 反转链表
  • 链表的中间节点
  • 倒数第k个节点
    • 反转链表(初阶)
    • 快慢指针法(进阶)
  • 合并两个有序链表
  • 链表分割
  • 链表的回文结构

移除链表元素

在这里插入图片描述

思路:由题目可知我们需要在给定的一个链表中移除值为val的节点,这里需要注意的情况就是全是val的链表移除后为空链表和传过来的链表是空链表的情况,如果直接对不是val的节点进行连接,返回头结点会无法处理头结点也是val的情况,这里不妨我们用两个指针开始都设置NULL,用一个指针指向头结点开始判断,必须是从头结点开始遍历不等于val的节点才能连接,不然就不能连接。这时候就处理了两种情况

typedef struct ListNode ListNode;
struct ListNode* removeElements(struct ListNode* head, int val) {
    ListNode* NewHead = NULL;   //一开始就为空处理全是相同元素的情况和传过来是空链表的情况
    ListNode* NewTail = NULL;
    ListNode* pcur = head;
    while(pcur)
    {
        if(pcur->val != val)
        {
            if(NewHead == NULL)
            {
                NewHead = NewTail = pcur;
            }
            else
            {
                NewTail->next = pcur;
                NewTail = NewTail->next;
            }
           
        }
         pcur = pcur->next;
    }
    if(NewTail != NULL) //防止堆空链表和移除后是空链表的空指针解引用
    {
        NewTail->next = NULL;
    }
    return NewHead;
}

注意最后一个节点连接的时候,万一他的下一个节点还是val值,必须把他的next指针设置NULL;

反转链表

给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。

在这里插入图片描述
思路:由题目可知,我们需要把给定的链表所以节点反转,并返回新的头结点。这里要注意链表的最后一个节点是一个NULL,所以我们反转的链表的尾节点也是一个NULL。反转无非就是改变next指针的指向即以下

  1. 定义三个指针n1,n2,n3.n1指向NULL(新尾节点),n2指向原头结点,n3指向第二个节点
  2. 把n2头节点的next指针指向n1,然后n1移到n2,n2移到n3,以此类推改变节点的next指针方向
  3. 直到n2指向NULL,循环结束,返回n1

注意:为什么定义三个指针:定义两个指针不行吗?不行,因为我们要先改变节点next指针的方向,但是如果直接改变了这个节点的next指针我们如何找到下一个节点呢?所以定义三个指针

n1:记录指针指向将要反转的结点反转后要指向的位置。
n2:记录指针指向将要反转的结点。
n3:记录指针指向将要反转的结点的下一个结点。

在这里插入图片描述

注意,这时这3个指针统一后移时,n3指针的后移将失败,因为n3后移前指向的是NULL,我们不能执行以下这句代码:

n3 = n3->next;

所以我们后移n3指针前需判断其是否为空。

typedef struct ListNode ListNode;
struct ListNode* reverseList(struct ListNode* head) {
    if(head == NULL)
    {
        return NULL;
    }
    ListNode* n1 = NULL;
    ListNode* n2 = head;
    ListNode* n3 = head->next;
    while(n2)
    {
        n2->next = n1;
        n1 = n2;
        n2 = n3;
        if(n3)
        {
            n3 = n3->next;
        }
        
    }
    return n1;
}

链表的中间节点

给你单链表的头结点 head ,请你找出并返回链表的中间结点。

如果有两个中间结点,则返回第二个中间结点。(给定的链表是非空链表)

思路:快慢指针法,定义两个指针,两个都指向头节点,慢指针每次走一步,快指针每次走两步,最终返回的中间节点就是慢指针

注意:当fast指向空或者fast指针的next指针指向的节点为空的时候,就停止遍历返回慢指针

在这里插入图片描述
在这里插入图片描述
偶数个有两个中间节点(如上面3,4),通常我们取的是第二个中间节点,也是为什么fast快指针也从头结点开始遍历。这样不用单独处理slow指向第一个中间节点的情况

 typedef struct ListNode ListNode;
struct ListNode* middleNode(struct ListNode* head) {
    ListNode* slow = head;
    ListNode* fast = head;
    while(fast && fast->next)
    {
        slow = slow->next;
        fast = fast->next->next;
    }
    return slow;
}

倒数第k个节点

实现一种算法,找出单向链表中倒数第 k 个节点。返回该节点的值。

反转链表(初阶)

在这里插入图片描述
思路:倒数第二个节点,是不是就是反转链表后的第二个节点?所以我们结合前面的反转链表的三指针法,然后堆反转链表变量k次就可以找的倒数的第k个节点

typedef struct ListNode ListNode;
int kthToLast(struct ListNode* head, int k){
    ListNode* n1 = NULL;
    ListNode* n2 = head;
    ListNode* n3 = n2->next;
    while(n2)
    {
        n2->next = n1;
        n1 =n2;
        n2 = n3;
        if(n3)
        {
            n3 = n3->next;
        }
    }
    while(--k)
    {
        n1 = n1->next;
    }
    return n1->val;

}

快慢指针法(进阶)

如果改一下题目,不能改变原链表的结构并且k不一定有效(有可能大于总结点数)该如何做呢?这样反转链表就不适用了吧。这时候就又可以用我们的快慢指针了

思路:这里的"快慢指针"不是一个指针走得快,一个指针走的慢。这里我们是为了找倒数第k个节点。我们每个链表的最后一个节点都是NULL,那么从倒数第k个节点开始走k步是不是就是null呢?所以我们不妨先让快指针fast走k步,然后慢指针和快指针再同时向后移动,直到fast快指针指向null时slow慢指针就是倒数第k个节点

注意:如果fast在走k步时就提前遇到NULL,那么说明k大于总节点数。这时不存在倒数第k个节点。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

合并两个有序链表

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

在这里插入图片描述
在这里插入图片描述

思路:创建一个新链表,对两个链表进行比较,那个链表的节点数据小,就插入到新链表中,直到其中两个链表其中一个为空停止(不存在同时为空的情况,数据一致也会有一个数据还没插入)。然后判断两个链表谁还没有为空,将不为空的链表尾插到新链表中。

注意:

  • 两个链表为空直接返回NULL
  • 其中一个链表为空返回不为空的链表
    在这里插入图片描述
typedef struct ListNode ListNode;
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) {
    if(list1 == NULL)
    {
        return list2;
    }
    if(list2 == NULL)
    {
        return list1;       //这样既可以处理其中一个为空,又可以处理同时为空的情况
    }
    ListNode* NewHead = NULL, *NewTail = NULL;
    ListNode* l1 = list1, *l2 = list2;
    while(l1 && l2)
    {
        if(l1 -> val < l2->val)
        {
            if(NewHead == NULL)
            {
                NewHead = NewTail = l1;
            }
            else
            {
                NewTail->next = l1;
                NewTail = NewTail->next;
            }
            l1 = l1->next;
        }
        else
        {
            if(NewHead == NULL)
            {
                NewHead = NewTail = l2;
            }
            else
            {
                NewTail->next = l2;
                NewTail = NewTail->next;
            }
             l2 = l2->next;
        }
    }
    if(l1)
    {
        NewTail->next = l1;
    }
    else
    {
        NewTail->next = l2;
    }
    return NewHead;
}

上面的链表结构我们是用一个空链表来存储,会导致插入头结点的时候总是会判断是不是NULL,导致代码冗余,这里我们不再使用空链表,使用带头的链表来存储,这样就直接可以在带头链表的下一个节点的位置插入。(为什么不在这个带头节点直接插入,这是动态开辟的空间,后面需要释放)

在这里插入图片描述

在这里插入图片描述

 typedef struct ListNode ListNode;
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) {
    if(list1 == NULL)
    {
        return list2;
    }
    if(list2 == NULL)
    {
        return list1;       //这样既可以处理其中一个为空,又可以处理同时为空的情况
    }
    ListNode* NewHead,*NewTail;
    NewHead = NewTail =(ListNode*)malloc(sizeof(ListNode));
    ListNode* l1 = list1, *l2 = list2;
    while(l1 && l2)
    {
        if(l1 -> val < l2->val)
        {
            NewTail->next = l1;
            NewTail = NewTail->next;
            l1 = l1->next;
        }
        else
        {
            NewTail->next = l2;
            NewTail = NewTail->next;
            l2 = l2->next;
        }
    }
    if(l1)
    {
        NewTail->next = l1;
    }
    else
    {
        NewTail->next = l2;
    }
    ListNode* ret = NewHead->next;
    free(NewHead);
    NewHead = NULL;
    return ret;
}

链表分割

描述
现有一链表的头指针 ListNode* pHead,给一定值x,编写一段代码将所有小于x的结点排在其余结点之前,且不能改变原来的数据顺序,返回重新排列后的链表的头指针。

思路:由题目可知,我们需要把小于x的节点排在其他指点之前,不能改变原来的数据顺序
不改变数据顺序:
在这里插入图片描述

所以这里不是让我们排升序

我们不妨创建两个链表,一个链表存储小于x的数据,一个链表存吃啥大于等于x的数据,然后把两个链表的首尾相连即可

1.把小于x的结点尾插到less链表,把大于x的结点尾插到greater链表
在这里插入图片描述
2.将less链表与greater链表链接起来。
在这里插入图片描述
注意:
1.链接后的链表的最后一个结点的指针域需要置空,否则可能造成链表成环。
2.返回的头指针应是lessHead->next,而不是lessHead。因为这是头节点

这是带头版本的做法

#include <cstddef>

class Partition {

public:

    ListNode* partition(ListNode* pHead, int x) {

        // write code here

        //创建两个非空链表,小链表(存储小于定值x)和大链表(存储大于等于定值x)

        ListNode* LessNewhead,*LessNewtail;

        ListNode* GreaterNewhead,*GreaterNewtail;

        LessNewhead=LessNewtail=(ListNode*)malloc(sizeof(ListNode));

        GreaterNewhead=GreaterNewtail=(ListNode*)malloc(sizeof(ListNode));

        if(LessNewhead==NULL && GreaterNewhead==NULL)

        {

            perror("malloc failed");

            exit(1);

        }

    ListNode* pcur=pHead;

    while(pcur)

    {

        if(pcur->val<x)

        {

            LessNewtail->next=pcur;

            LessNewtail=LessNewtail->next;

        }

        else

        {

            GreaterNewtail->next=pcur;

            GreaterNewtail=GreaterNewtail->next;

        }

        pcur=pcur->next;

    }

    //将小链表的尾连接到大链表的头

    LessNewtail->next=GreaterNewhead->next; //这里nGext是因为大链表的头还是指向第一个无效空间

    GreaterNewtail->next=NULL;      //一定要把大链表的下一个节点设置NULL,因为大链表村上的是大于等于x的节点,若其中一个节点的next指针指向小链表节点,则会死循环形成环,如 5 1 3 6 2,其中小链表:1 2,大链表:5 3 6,因为6的下一个节点是2,下一次循环又会指向小链表的节点2,导致 2 5 3 6,2 5 3 6....死循环

    ListNode* ret=LessNewhead->next;

    free(LessNewhead);

    free(GreaterNewhead);

    LessNewhead=NULL;

    GreaterNewhead=NULL;

    return ret;

    }

但是不带头版本又多了一个判断

/*
struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) : val(x), next(NULL) {}
};*/
#include <cmath>
#include <functional>
#include <sys/ucontext.h>
class Partition {
public:
    ListNode* partition(ListNode* pHead, int x) {
        // write code here
       ListNode* LessHead = NULL, *LessTail = NULL;
       ListNode* GreaterHead = NULL, *GreaterTail = NULL;
       ListNode* pcur = pHead;
        while (pcur) {
            if(pcur->val < x)
            {
                if(LessHead == NULL)
                {
                    LessHead = LessTail = pcur;
                }
                else 
                {
                    LessTail->next = pcur;
                    LessTail = LessTail->next;
                }
            }
            else 
            {
                if(GreaterHead == NULL)
                {
                    GreaterHead = GreaterTail =pcur;
                }
                else 
                {
                   GreaterTail->next = pcur;
                   GreaterTail = GreaterTail->next;
                }
            }
            pcur = pcur->next;
        }
         if (GreaterTail) {
            GreaterTail->next = nullptr;
        }
        if (LessTail) {
            LessTail->next = GreaterHead;
            return LessHead;
        } else {
            return GreaterHead;
        }
    }
  

};

在这里插入图片描述
为什么要判断连接的链表为空呢,因为题目也没说给的数据全部比x大或者全部比x小呀
例如:4 4 4 4 ,x=3
如果直接对LessTail解引用此时Less这个链表为空,就是对空指针解引用
在这里插入图片描述
有的人就要问了,那为啥带头的不判断,带头本身就有一个头结点不存在链表为null的情况,也就不会对null解引用
在这里插入图片描述

这里还是推荐带头的写法,会清除对头结点插入判断的代码冗余

链表的回文结构

在这里插入图片描述
我们需要找到传入链表的中间结点,并将中间结点及其后面结点进行反转,然后再将原链表的前半部分与反转后的后半部分进行比较,若相同,则该链表是回文结构,否则,不是回文结构。
1.找中间节点并返回
在这里插入图片描述
2.对中间节点及以后得节点进行反转
在这里插入图片描述
3.比较链表的前半部分与后半部分的结点值,若相同则是回文结构,否则,不是回文结构。
在这里插入图片描述
注意:就算传入的链表是结点数为奇数的回文结构,该思路也可以成功判断。
例如,以下链表反转其后半部分后,我们看似链表应该是这样的。
在这里插入图片描述
但反转后的链表并不是这样的,而应该是下面这样:

因为我们反转的是中间结点及其后面的结点,并没有对前面的结点进行任何操作,所以结点5所指向的结点应该还是结点7。
在这里插入图片描述

于是该链表的比较过程应该是这样的:1等于1,3等于3,5等于5,7等于7,然后RHead指针指向NULL。所以判断该链表是回文结构。

/*
struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) : val(x), next(NULL) {}
};*/
#include <list>
class PalindromeList {
public:
    ListNode* FIndMiddle(ListNode* phead)
    {
        ListNode* slow = phead;
        ListNode* fast = phead;
        while(fast && fast->next)
        {
            slow = slow->next;
            fast = fast->next;
        }
        return slow;
    }
    ListNode* ReverseList(ListNode* phead)
    {
        ListNode* n1 = NULL;
        ListNode* n2 = phead;
        ListNode* n3 = n2->next;
        while(n2)
        {
            n2->next = n1;
            n1 = n2;
            n2 = n3;
            if(n3)
            {
                n3 = n3->next;
            }
        }
        return n1;
    }
    bool chkPalindrome(ListNode* A) {
        // write code here
        //找到中间节点
        ListNode* mid = FIndMiddle(A);
        //将中间节点之后反转
        ListNode* right = ReverseList(mid);
        ListNode* pcur = A;
        while(right)
        {
            if(right->val != pcur->val)
            {
                return false;
            }
            right = right->next;
            pcur = pcur->next;
        }
        return true;
    }
};

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

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

相关文章

收藏!2024年GPU算力最新排名

​GPU&#xff08;图形处理单元&#xff09;算力的提升是驱动当代科技革命的核心力量之一&#xff0c;尤其在人工智能、深度学习、科学计算和超级计算机领域展现出了前所未有的影响力。2024年的GPU技术发展&#xff0c;不仅体现在游戏和图形处理的传统优势上&#xff0c;更在跨…

吴恩达老师机器学习-ex2

有借鉴网上的部分 第一题 导入库&#xff0c;读取数据并且展示前五行&#xff08;基本操作&#xff09; import numpy as np import pandas as pd import matplotlib.pyplot as plt #读取数据 path "./ex2data1.txt" data pd.read_csv(path,headerNone,names[&q…

Netty技术全解析:PooledByteBufAllocator源码视角下的详解

❃博主首页 &#xff1a; 「码到三十五」 &#xff0c;同名公众号 :「码到三十五」&#xff0c;wx号 : 「liwu0213」 ☠博主专栏 &#xff1a; <mysql高手> <elasticsearch高手> <源码解读> <java核心> <面试攻关> ♝博主的话 &#xff1a…

MySQL---JDBC

一、JDBC是什么&#xff1f; JDBC(Java Database Connectivity):是Java访问数据库的解决方案。 JDBC定义了一套标准的接口&#xff0c;即访问数据库的通用API&#xff0c;不同数据库的厂商根据各自数据库的特点实现这些接口。 JDBC希望用相同的方式访问不同的数据库&#xff0c…

spring:xml定义bean

spring有两种方法定义bean&#xff0c;一种是使用xml标签&#xff0c;一种是注解。在这里介绍使用xml标签定义bean。 我的文件目录如上图所示&#xff0c;在这里创建了bean.xml文件&#xff0c;bean.xml文件中定义了bean。 文件内容&#xff1a; <?xml version"1.0&…

Java线程池动态内存队列思路

背景 在我们定义线程池时候&#xff0c;需要创建一个对列用来存储未执行而排队的任务&#xff0c;这个队列长度问题一直都是需要开发人员斟酌考虑点。在阿里巴巴开发手册中有怎么一个规则如&#xff1a; 说明: Executors返回的线程池对象的弊端如下 FixedThreadPool和SingleT…

【前端面试】七、算法-递归

遍历方法总结 链式调用 数组的很多操作可以构成链式操作&#xff0c;类似这样的格式&#xff1a;…map().filter(…).sort(…).map(….)链式操作就是对象方法返回类型是自身的。比如map是属于数组的方法&#xff0c;它返回数组&#xff0c;所以构成了链式操作优势&#xff1a;…

【iOS】——持久化

在iOS开发中&#xff0c;数据持久化是非常重要的&#xff0c;因为它允许应用程序在不同会话之间保存用户数据、设置、偏好等信息。 为什么数据持久化 数据保存&#xff1a; 目的&#xff1a;将应用程序中的数据保存到非易失性存储中&#xff0c;以便在应用程序关闭或重启后仍…

眼镜清洗机哪个品牌好?性价比高的超声波眼镜清洗机

清洁眼镜、化妆刷、项链等物品其实是挺麻烦的&#xff0c;尤其是化妆刷这种经常使用的物品&#xff0c;需要用专门的清洁剂并保持一定的清洗频率。眼镜的日常清洁主要是用眼镜布擦拭镜片上的灰尘和指纹&#xff0c;但对于顽固的污渍或油脂&#xff0c;只有超声波清洗机能提供快…

六西格玛管理法

六西格玛管理法是一种旨在提高业务流程效率和减少缺陷的管理策略。它最初由摩托罗拉公司在1980年代末期提出&#xff0c;并随后被通用电气等公司广泛应用和发展。六西格玛的核心理念是通过减少过程变异性来提高产品质量和服务水平。 六西格玛的含义&#xff1a; 统计学概念&am…

一款功能全面的卸载工具,强大,免费,小巧

HiBit Uninstaller是一款功能全面的卸载工具&#xff0c;它不仅可以卸载Windows程序&#xff0c;还提供了诸如注册表清理、垃圾文件清理等多种系统优化功能。该软件以其小巧、强大、免费的特点受到用户的欢迎&#xff0c;尤其适合处理顽固软件和流氓程序的卸载问题。 主要功能…

WPF的MVVM架构:如何通过数据绑定简化UI逻辑

WPF的MVVM架构&#xff1a;如何通过数据绑定简化UI逻辑 目录 MVVM模式概述数据绑定在MVVM中的作用实现MVVM模式的步骤MVVM模式中的常见问题与解决方案实践示例总结 MVVM模式概述 MVVM&#xff08;Model-View-ViewModel&#xff09;是一种设计模式&#xff0c;用于WPF应用程序…

机器学习(五) -- 无监督学习(2) --降维1

系列文章目录及链接 上篇&#xff1a;机器学习&#xff08;五&#xff09; -- 无监督学习&#xff08;1&#xff09; --聚类2 下篇&#xff1a;机器学习&#xff08;五&#xff09; -- 无监督学习&#xff08;2&#xff09; --降维2 前言 tips&#xff1a;标题前有“***”的内…

热门超声波清洗机有哪些?小型超声波清洗机推荐

在繁忙的工作和生活中&#xff0c;许多人常常会因为种种原因忽略日常的小事&#xff0c;比如忘记清洁手表、眼镜、首饰等常用物品。实际上&#xff0c;这些物品表面不仅积累了灰尘和污垢&#xff0c;特别是跟眼部朝夕相处的眼镜&#xff0c;还可能滋生各种致病细菌&#xff0c;…

Vue3-如何自己写一个“返回顶部”功能

功能描述&#xff1a; 在屏幕的右下角固定一个“返回顶部”按钮&#xff0c;只有当用户滚动屏幕一定程度后出现&#xff0c;否则隐藏。 点击按钮&#xff0c;网页平滑的滚动到页面顶部。 环境&#xff1a;Vue3,js&#xff0c;antd 具体思路&#xff1a; 1、给窗口挂载滚动事…

Python 学习中的 API,如何调用API ?

1.1 API的定义 API&#xff0c;全称是Application Programming Interface&#xff08;应用程序编程接口&#xff09;。它是一组定义好的协议和工具&#xff0c;用于在软件应用程序之间进行通信。API可以简化软件开发&#xff0c;使不同的应用程序能够相互协作。它是软件开发中…

阿里云服务器 Ubuntu18.04 安装 mysql8.0并允许外部连接

参考教程&#xff1a; 官网教程 参考教程一 首先彻底删除mysql5.7 dpkg --list|grep mysql #查看 sudo apt-get remove mysql-common #卸载 sudo apt-get autoremove --purge mysql-server-5.7 #版本自己修改 dpkg -l|grep ^rc|awk {print$2}|sudo xargs dpkg -P #清除残留数…

LeetCode Hot100 将有序数组转换为二叉搜索树

给你一个整数数组 nums &#xff0c;其中元素已经按 升序 排列&#xff0c;请你将其转换为一棵 平衡 二叉搜索树。 示例 1&#xff1a; 输入&#xff1a;nums [-10,-3,0,5,9] 输出&#xff1a;[0,-3,9,-10,null,5] 解释&#xff1a;[0,-10,5,null,-3,null,9] 也将被视为正确…

电商老司机教您批量下载1688高清主图、详情图、sku及视频信息

图片在电商中至关重要&#xff0c;高质量的商品图片能吸引顾客注意&#xff0c;提升购买欲望。它们是展示商品特性和细节的主要方式&#xff0c;有助于增强消费者信任&#xff0c;减少退换货率。好的图片还能优化搜索排名&#xff0c;提高转化率。简而言之&#xff0c;图片是电…

Luma AI的战略转向:从Nerf到视频生成领域的背后故事

引言 今天我们将深入探讨Luma AI近期引发关注的视频生成模型——Dream Machine。Luma AI从最初的3D重建和生成业务逐步转向视频生成领域的背后&#xff0c;隐藏着什么样的战略考量和技术演进&#xff1f;让我们通过Luma AI首席科学家宋佳铭的最新访谈&#xff0c;揭开这场技术…