链表必刷题一

2024-03-01 22:10
文章标签 链表 必刷题

本文主要是介绍链表必刷题一,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

链表必刷题一

    • 一、移除链表元素
      • 1. leetcode 203
      • 2.代码实现
      • 3. 代码分析
    • 二、 反转链表
      • 1. leetcode 206
      • 2. 代码实现
      • 3. 代码分析
    • 三、链表的中间节点
      • 1. leetcode 879
      • 2. 代码实现
      • 3. 代码分析
    • 四、链表中倒数第 k 个节点
      • 1. 牛客网链接
      • 2. 代码实现
      • 3. 代码分析
    • 五、合并两个有序链表
      • 1. leetcode 21
      • 2. 代码实现
      • 3. 代码分析
    • 六 、分割链表
      • 1. 牛客网链接
      • 2. 代码实现
      • 3. 代码分析

一、移除链表元素

1. leetcode 203

题目要求:
指定一个元素,移除链表中与之对应的所有元素。
leetcode 203

2.代码实现

class Solution {public ListNode removeElements(ListNode head, int val) {//链表为空if(head == null){return null;}//创建两个指针,一个用来遍历,一个用来充当前驱ListNode cur = head.next;ListNode prev = head;while(cur != null){if(cur.val == val){prev.next = cur.next;}else{prev = cur;}cur = cur.next;}//考虑头结点if(head.val == val){return head.next;}return head;}
}

3. 代码分析

思路:
创建两个指针,一个是 prev,称为前驱指针,用来串联下一个节点;另一个是 cur,用来遍历整个链表,同时在跑的过程进行 val 值的判断。

下面是几种必须要考虑到的情况:

情况(1):待删除的节点非连续分布
步骤分析
情况(2):考虑尾节点,也考虑到了连续排分布
经过分析下来,我们发现情况(2)全部符合情况(1)的代码
步骤分析
情况(3):考虑头节点也是要删除的节点
步骤分析

二、 反转链表

1. leetcode 206

题目要求:给你单链表的头节点 head ,请你反转链表,并返回反转后的链表的表头。
leetcode 206
效果

2. 代码实现

class Solution {public ListNode reverseList(ListNode head) {if(head == null){return null;}ListNode prev = null;ListNode cur = head;while(cur != null){ListNode cur2 = cur.next;cur.next = prev;prev = cur;cur = cur2;}return prev;}
}

3. 代码分析

思路:

(1)创建两个指针,一个指针是 prev,最初指向 null,之后用来串联反转后的节点;另一个指针是 cur,最初指向 head,之后用来遍历整个链表中的节点。prev 和 cur不断地向后走,当 cur == null 的时候,那么就停下来,此时的 head 就是 prev。
如下两行代码可以实现上述操作
cur.next = prev;
prev = cur;

(2)然而,此题的关键是,如何让 prev 和 head 正确地往链表后面跑。
因为上面两行代码会改变 cur 的指向,也就是说,cur 不再能够按照原先的路线跑了,基于这个原因,我们要创建一个新的指针 cur2,这个指针的作用就是在反转操作前,把 cur 的下一个位置存起来,并放在 while 循环的第一行,这样一来,就可以正确遍历了。
ListNode cur2 = cur.next;

下面的图辅助理解:
分析
分析

三、链表的中间节点

1. leetcode 879

题目要求:给定一个头结点为 head 的非空单链表,返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点。
leetcode 879
结果

2. 代码实现

class Solution {public ListNode middleNode(ListNode head) {//创建快慢指针ListNode fast = head;ListNode slow = head;  while(fast != null && fast.next != null){fast = fast.next.next;slow = slow.next;}return slow;}
}

3. 代码分析

思路:
创建两个指针,一个是 fast,一次走两步,另一个是 slow,一次走一步。两个指针同时移动,当 fast 停下来的时候,slow 就是中间节点。原因就是:fast 更快,走的更远,fast 速度是 slow 的一半,而 slow 对应的路程是 fast 的一半。

fast = fast.next.next;
slow = slow.next;

此外我们应该注意:while 循环的条件应写成( fast != null && fast.next != null),而不应该写成(fast.next != null && fast != null),因为考虑到当 fast == null 时,fast.next 就会造成空指针异常。

情况(1):节点的总数是奇数
1
情况(2):节点的总数是偶数
2

四、链表中倒数第 k 个节点

1. 牛客网链接

题目要求:输入一个链表,输出该链表中倒数第k个结点。
OJ链接
在这里插入图片描述

2. 代码实现

public class Solution {public ListNode FindKthToTail(ListNode head,int k) {//空链表和k<=0,不满足要求,返回 nullif(head == null || k <= 0){return null;}ListNode fast = head;ListNode slow = head;//让 fast 先走for(int i = 0; i < k-1; i++){fast = fast.next;//下面判断是否 k > 链表长度if(fast == null){ return null;}}//fast 和 slow 同步走while(fast.next != null){fast = fast.next;slow = slow.next;}return slow;}
}

3. 代码分析

这道题目,我们先理解一个例子,再阐明思路。
例子如下,假设我们要找的是 k = 2,即对应下图的倒数第二个节点。现在我们分三步走:
(1)创建快慢指针 fast 和 slow 为头节点
(2)slow 不动,fast 先走 k - 1 步
(3)在(1)的基础上,fast 和 slow 每次走一步,直到 fast.next == null 的时候,那么 slow 指向的位置就是 k = 2 的位置
分析
思路:
如上图的例子,假设正常情况下,链表长度 length = 5
当 k = 1,k - 1 = 0
当 k = 2,k - 1 = 1
当 k = 3,k - 1 = 2
当 k = 4,k - 1 = 3
当 k = 5,k - 1 = 4
k - 1 就是我们 fast 要先走的路程,之后 fast 和 slow 指针再保持一步一步走,最后通过判断 fast.next 是否为 null即可。

因为我们知道 slow 指针是每次只能走一步的,那么我们只能通过设计 fast 路程来保持后面的 fast 和 slow 路程的同步!也就是说目标是 slow,而 fast 是用来实现 slow 的。
读者可以自己试一试这一规律,这很有意思。

此外,我们应该考虑三种特殊情况:
(1)链表为空
链表为空,无论 k 的值是多少,自然就返回 null

(2)k <= 0
假设 k = -1,链表有倒数第 -1 个节点吗?这个时候肯定也返回 null

(3)k > 链表的长度 length
假设 length = 5,k = 6,那么,说让你找链表中倒数第 6 个节点,自然也是找不到的,所以返回 null

针对情况(1)和(2),以下代码可以解决

if(head == null || k <= 0){return null;
}

针对情况(3),有两种解决方法
方法1: 通过遍历链表,得出 length 的值,如果用 if 判断的结果是(length - k < 0),那么就返回 null.

方法2: 在 fast 走 k - 1 步的时候,我们在 for 循环中进行判断,如果 fast == null,那么就返回 null

读者们应该更为理解第①种解决方案,而我当初做题也是这么想的,可是第一种方案要多遍历链表一次,也就是说效率更低,时间复杂度更大。所以接下来利用图解阐明第②种解决方案。
分析
如上图分析,假设链表长度为 5,k = 6,那么 k - 1 = 5,经过之前的分析,我们要先让 fast 指针先往后跑 5 步,以此造成的结果就是,fast 已经指向 null,所以就更别谈让后面的 slow 继续跑了,所以直接 return null。假设 k = 7,8,9,那么可想而知,fast 都不知道跑哪去了。
所以经过分析,这种表达方式就是在于表明 k 不能大于链表长度,一旦 k > 链表长度,fast 直接为 null.

五、合并两个有序链表

1. leetcode 21

题目要求:将两个升序链表合并为一个新的升序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
leetcode 21
输出结果

2. 代码实现

class Solution {public ListNode mergeTwoLists(ListNode head1, ListNode head2) {//创建一个傀儡节点ListNode newhead = new ListNode(-1);ListNode cur = newhead;//开始循环,并判断傀儡节点连接的下一个节点是属于哪一个链表while(head1 != null && head2 != null){if(head1.val <= head2.val){cur.next = head1;cur = cur.next;head1 = head1.next;}else{cur.next = head2;cur = cur.next;head2 = head2.next;}}//如果链表1本身为空,或者链表1走完了,就走链表2if(head1 == null){cur.next = head2;}//如果链表2本身为空,或者链表2走完了,就走链表1if(head2 == null){cur.next = head1;}return newhead.next;}
}

3. 代码分析

思路:
(1) 创建一个虚拟的傀儡节点 newhead,newhead 里面存的是数据域是 -1,指针域 next 是 null。目的是用来将两个链表串联起来,此外还应该创建一个 cur 充当傀儡节点的替身,因为最后的 newhead 要充当新的头结点,所以要保持 newhead 的位置不能动。

(2)head1 和 head2 每经过一个节点的时候,就比较两个节点中存的 val 值,由于此题是排序是升序的情况,那么谁的值小,就放在新的链表前面。

(3)最后考虑特殊情况
① 原始的两个链表是否都为空,是否某个链表为空,另一个链表不为空。
② 当其中的一个链表元素已经走完了,那么下一步该怎么办。
基于以上两种特殊的情况,首先我们应该确定好 while 循环的条件是 (head1 != null && head2 != null),其次我们应该添加两个 if 的条件,代码如上,我已经标出注释。

下图辅助理解:
分析一
分析二

六 、分割链表

1. 牛客网链接

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

OJ链接
结果

2. 代码实现

public class Partition {public ListNode partition(ListNode head, int x) {//1. 创建五个指针//创建两个傀儡节点 newhead1 和 newhead2//再创建这两个傀儡节点的替身,cur1,和 cur2//创建一个指针 sign 用来遍历原来的链表ListNode newhead1 = new ListNode(-1);ListNode newhead2 = new ListNode(-2);ListNode cur1 = newhead1;ListNode cur2 = newhead2;ListNode sign = head;//2. 进行区间划分//比 x 值小的放在前一个区间比 x 值大的放在后一个区间while(sign != null){if(sign.val < x){cur1.next = sign;cur1 = cur1.next;sign = sign.next;}else{cur2.next = sign;cur2 = cur2.next;sign = sign.next;}}//3. 傀儡节点失效,重新定义两个区间的头结点newhead1 = newhead1.next;newhead2 = newhead2.next;//4. 连接两个区间cur1.next = newhead2;//5. 考虑特殊情况if(newhead2 != null){cur2.next = null;}if(newhead1 == null){return newhead2;}//左右区间都为空return newhead1;}
}

3. 代码分析

思路:

我们先来看看正常情况下的思路分析,注意,这里给的 x 值,可以是非链表元素中的 val

(1) 创建五个指针
① 创建两个虚拟的傀儡节点 newhead1 和 newhead2,其分别存放数据域和指针域。newhead1 中存放的是 -1 和 null,newhead2 存放的是 -2 和 null。目的是用来分割链表,当节点对应的 val < x 的时候,我们把这个节点放在前一个区间,也就是属于 newhead1 节点的区间;同理,当节点对应的 val > x 的时候,我们把这个节点放在后一个区间,也就是属于 newhead2 节点的区间。

② 与此同时,我们再创建这两个傀儡节点的替身,cur1 和 cur2,让 cur1 和 cur2 起到串联节点的作用,因为在程序的最后,我们要对 newhead1 和 newhead2 头节点进行操作,所以作为两段区间新的头结点,所以这两个位置不能动它,我们只能让 cur1 和 cur2去跑。

③ 创建一个指针 sign 用来遍历原来的链表,拿 sign.val 和 x 进行比较,小的放在前一区间,大的放在后一区间。

(2)当区间划分完之后,我们要注意真正的头节点位置,两个傀儡节点在原先创建的时候,它们之中默认指向的是 null,而经过串联新的节点之后,其对应的 next 域可能会发生改变,所以我们实现如下两行代码。这表示:有节点就指向下一个节点的地址,无节点,就指向 null。这一步很关键!

newhead1 = newhead1.next;
newhead2 = newhead2.next;

给大家展示清晰的傀儡节点的内部结构,和创建普通的节点一样,只要创建了一个新的节点,其节点本身就会被系统分配内存,其中有个数据域,有一个指针域,而因为它是孤立的,所以它们的指针域指向 null
分析

下面请看我的分析,我给出了一个链表,假设我们将 x = 6 设置成中间线。
x < 6 的放在前一个区间,这个区间是通过傀儡节点 newhead1 引导的;x > 6 的放在后一个区间,这个区间是通过傀儡节点 newhead2 引导的。

分析
下图辅助理解:

分析
分析
分析

(3)上面的是普遍情况,我们再看看几个特殊情况,这依然很关键:
分析
请读者对照上图序号并看下面我给出的列举,我为大家列出来了所有情况,分别是:

① 正常情况

② 前一个区间为空,后一个区间不为空
我们只要设置相关条件,并满足使用代码即可

cur2.next = null; //尾节点置空
return newhead2 //将后一区间的头节点作为返回节点

③ 后一个区间为空,前一个区间不为空
我们无须设置相关条件,因为在之前有一行代码已经满足要求
cur1.next = newhead2 <=> null
想象一下,后一区间为空,那么 newhead2 == null ,这就导致了前一区间的尾节点变成了空,符合链表要求。

④ 两个区间都为空
我们无须设置相关条件
因为:不管是 return newhead1 还是 return newhead2 都为空

虽然牛客网官方定义这道题较难,但我觉得只是很多人在做题的时候失去了耐心,只要将图画好,就能迎刃而解!

结尾
Over. 谢谢观看哟~

这篇关于链表必刷题一的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/763989

相关文章

csu1329(双向链表)

题意:给n个盒子,编号为1到n,四个操作:1、将x盒子移到y的左边;2、将x盒子移到y的右边;3、交换x和y盒子的位置;4、将所有的盒子反过来放。 思路分析:用双向链表解决。每个操作的时间复杂度为O(1),用数组来模拟链表,下面的代码是参考刘老师的标程写的。 代码如下: #include<iostream>#include<algorithm>#include<stdio.h>#

深入手撕链表

链表 分类概念单链表增尾插头插插入 删尾删头删删除 查完整实现带头不带头 双向链表初始化增尾插头插插入 删查完整代码 数组 分类 #mermaid-svg-qKD178fTiiaYeKjl {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-

建立升序链表

题目1181:遍历链表 时间限制:1 秒 内存限制:32 兆 特殊判题:否 提交:2744 解决:1186 题目描述: 建立一个升序链表并遍历输出。 输入: 输入的每个案例中第一行包括1个整数:n(1<=n<=1000),接下来的一行包括n个整数。 输出: 可能有多组测试数据,对于每组数据, 将n个整数建立升序链表,之后遍历链表并输出。 样例输

【每日一题】LeetCode 2181.合并零之间的节点(链表、模拟)

【每日一题】LeetCode 2181.合并零之间的节点(链表、模拟) 题目描述 给定一个链表,链表中的每个节点代表一个整数。链表中的整数由 0 分隔开,表示不同的区间。链表的开始和结束节点的值都为 0。任务是将每两个相邻的 0 之间的所有节点合并成一个节点,新节点的值为原区间内所有节点值的和。合并后,需要移除所有的 0,并返回修改后的链表头节点。 思路分析 初始化:创建一个虚拟头节点

学习记录:js算法(二十八):删除排序链表中的重复元素、删除排序链表中的重复元素II

文章目录 删除排序链表中的重复元素我的思路解法一:循环解法二:递归 网上思路 删除排序链表中的重复元素 II我的思路网上思路 总结 删除排序链表中的重复元素 给定一个已排序的链表的头 head , 删除所有重复的元素,使每个元素只出现一次 。返回 已排序的链表 。 图一 图二 示例 1:(图一)输入:head = [1,1,2]输出:[1,2]示例 2:(图

【数据结构与算法 | 灵神题单 | 删除链表篇】力扣3217, 82, 237

总结,删除链表节点问题使用到列表,哈希表,递归比较容易超时,我觉得使用计数排序比较稳,处理起来也不是很难。 1. 力扣3217:从链表中移除在数组中的节点 1.1 题目: 给你一个整数数组 nums 和一个链表的头节点 head。从链表中移除所有存在于 nums 中的节点后,返回修改后的链表的头节点。 示例 1: 输入: nums = [1,2,3], head = [1,2,3,

c++ 链表详细介绍

链表是数据结构的一种,由节点组成,每个节点包含数据和指向下一个节点的指针。链表在C++中的实现可以是单链表、双链表或循环链表。以下是链表的详细介绍: 1. 单链表 结构: 节点(Node):每个节点包含数据和一个指针(next),指向链表中的下一个节点。 示例结构: struct Node {int data;Node* next;Node(int d) : data(d), next(

带头结点的线性链表的基本操作

持续了好久,终于有了这篇博客,链表的操作需要借助图像模型进行反复学习,这里尽可能的整理并记录下自己的思考,以备后面复习,和大家分享。需要说明的是,我们从实际应用角度出发重新定义了线性表。 一. 定义 从上一篇文章可以看到,由于链表在空间的合理利用上和插入、删除时不需要移动等优点,因此在很多场合下,它是线性表的首选存储结构。然而,它也存在某些实现的缺点,如求线性表的长度时不如顺序存储结构的

数据结构基础(栈,队列,数组,链表,树)

栈:后进先出,先进后出 队列:先进先出,后进后出 数组:查询速度快,通过地址值和索引定位,查询任意数据消耗时长相同,在内存中是连续存储的,删除效率低,要将原始数据删除,然后后面的数据前移,添加效率低,添加索引位置的元素,剩下的都需要向前后移动 链表:节点的存储位置(地址)里面存储本身的数据值,和下一个节点的地址值,链表中的节点是独立对象,在内存中是不连续的。查询速度慢,无论查询哪个数据都要从

(六十四)第 10 章 内部排序(静态链表的插入排序)

示例代码 staticLinkList.h // 静态链表的插入排序实现头文件#ifndef STATIC_LINK_LIST_H#define STATIC_LINK_LIST_H#include "errorRecord.h"#define SIZE 100#define NUM 8typedef int InfoType;typedef int KeyType;ty