【算法通关村】链表反转经典问题解析

🚩本文已收录至算法学习之旅

一.基础反转

我们通常有两种方法反转链表,一种是直接操作链表实现反转操作,一种是建立虚拟头节点辅助实现反转操作

力扣习题链接:206. 反转链表
在这里插入图片描述

(1) 直接操作实现反转

我们需要一个变量pre来保存当前节点的上一个节点,否则无法进行反转操作也就是指向上一个节点。我们还需要一个节点next来保存当前节点的下一个节点,因为我们一旦操作当前节点的指针域后将会丢失下一个节点的地址。知道需要两个变量进行辅助,接下来我们就可以严格按照一定的顺序来反转指针的。1.先记录当前节点的下一个指针(不记录将会在下一步操作中丢失) 2.令当前节点指向前一个节点(如此我们便完成了这两个节点的反转操作,可以开始移动指针进行下两个节点之间的操作) 3.令pre指针指向当前指针(移动后当前指针变成了前一个指针,用于后续节点的反转)4. 将当前指针移向下一个指针(正是我们通过next变量保存了才能找到当前节点的下一个指针,否则就在第2步中丢失了后续节点)5.如此往复直到当前指针移动到null,我们也完成了反转操作。

	public ListNode reverseList(ListNode head) {// 记录前一个节点ListNode prev = null;ListNode curr = head;while (curr != null) {// 记录后一个节点ListNode next = curr.next;// 令当前节点指向前一个节点curr.next = prev;// 保存当前节点prev = curr;// 移动到下一个节点curr = next;}return prev;}

强烈建议各位手动模拟,注意链表节点与节点之间是如何进行链接的,体会每个变量的作用,示意图如下:
在这里插入图片描述

(2) 虚拟节点辅助实现反转

在上一篇中,我们发现处理头节点与中间节点和尾部节点方法不一致,如果单独处理则比较麻烦,因此我们可以先建立一个虚拟头节点并且指向链表头节点head,这样我们的操作便统一了。通过使用虚拟节点辅助实现反转,我们可以每次从旧的链表拆下来一个结点接到ans后面,然后将其他线调整好就可以了。

public static ListNode reverseList(ListNode head) {ListNode ans = new ListNode(-1);ListNode cur = head;while (cur != null) {ListNode next = cur.next;cur.next = ans.next;ans.next = cur;cur = next;}return ans.next;
}

与直接反转类似,强烈建议各位手动模拟,注意链表节点与节点之间是如何进行链接的,体会每个变量的作用,示意图如下:

在这里插入图片描述

二.经典问题分析

(1) 指定区间反转

力扣链接:反转链表 II

在这里插入图片描述

解决这道题目我们有两种方法,一种是头插法,一种是穿针引线法。

(1.1) 头插法

头插法反转的整体思想:在需要反转的区间里,每遍历到一个节点,让这个新节点来到反转部分的起始位置。

在这里插入图片描述

这个插入过程就是前面所学习的带虚拟结点的插入操作,每走一步都要考虑各种指针怎么指,既要将结点摘下来接到对应的位置上,还要保证后续结点能够找到。

public ListNode reverseBetween(ListNode head, int left, int right) {// 设置 dummyNode 是这一类问题的一般做法(统一节点不同位置处理)ListNode dummyNode = new ListNode(-1);dummyNode.next = head;ListNode pre = dummyNode;// 找到待反转区间的前一个节点for (int i = 0; i < left - 1; i++) {pre = pre.next;}// 指向反转区间的起始节点ListNode cur = pre.next;ListNode next;// 遍历区间内的节点并将其移向待反转区间的起始位置for (int i = 0; i < right - left; i++) {next = cur.next;cur.next = next.next;next.next = pre.next;pre.next = next;}return dummyNode.next;
}

(1.2) 穿针引线法

穿针引线法的整体思想:首先找到待反转区间的开始节点与结束节点以及反转区间的前一个节点以及后一个节点。将区间内节点进行反转,最后再按照我们记录的标识点进行连接即可。

在这里插入图片描述

public ListNode reverseBetween(ListNode head, int left, int right) {// 因为头节点有可能发生变化,使用虚拟头节点可以避免复杂的分类讨论ListNode dummyNode = new ListNode(-1);dummyNode.next = head;ListNode pre = dummyNode;// 第 1 步:从虚拟头节点走 left - 1 步,来到 left 节点的前一个节点for (int i = 0; i < left - 1; i++) {pre = pre.next;}// 第 2 步:从 pre 再走 right - left + 1 步,来到 right 节点ListNode rightNode = pre;for (int i = 0; i < right - left + 1; i++) {rightNode = rightNode.next;}// 第 3 步:切出一个子链表ListNode leftNode = pre.next;ListNode succ = rightNode.next;// 模拟设置为链表末尾节点指向nullrightNode.next = null;// 第 4 步:反转链表的子区间reverseLinkedList(leftNode);// 第 5 步:接回到原来的链表中pre.next = rightNode;leftNode.next = succ;return dummyNode.next;
}// 反转链表private void reverseLinkedList(ListNode head) {ListNode pre = null;ListNode cur = head;while (cur != null) {ListNode next = cur.next;cur.next = pre;pre = cur;cur = next;}}

(2) 两两交换链表中的节点

力扣链接:两两交换链表中的节点

在这里插入图片描述

当链表存在两个及以上未遍历到的节点时,我们按照如下步骤模拟反转就行了。示意图如下:

在这里插入图片描述

    public ListNode swapPairs(ListNode head) {ListNode dummyNode = new ListNode();dummyNode.next = head;ListNode cur = dummyNode.next,pre = dummyNode;//在至少存在两个节点时进行翻转while(cur != null && cur.next != null){ ListNode next = cur.next;cur.next = next.next;next.next = cur;pre.next = next;pre = cur;cur = cur.next;}return dummyNode.next;}

(3) 两数相加

力扣链接: 两数相加 II

在这里插入图片描述

我们可以用栈轻松解决就不在此赘述代码实现:先将两个链表的元素分别压栈,然后再一起出栈,将两个结果分别计算。之后对计算结果取模,模数保存到新的链表中,进位保存到下一轮。以此往复即可。

我们主要讲解如何针对链表操作,由于本题的链表从首到尾刚好是从最高位到最低位,而我们正常加法运算都是最低尾开始计算的,因此我们需要先将两条链表进行反转操作,然后再同时遍历每一位模拟加法运算即可轻松解决问题。

class Solution {public ListNode addTwoNumbers(ListNode l1, ListNode l2) {// 定义虚拟头节点开始记录新链表ListNode ans = new ListNode();// 反转两条链表ListNode rl1 = reverseListNode(l1);ListNode rl2 = reverseListNode(l2);// 记录进位int add = 0;// 链表不为空,进位不为0则持续计算while (rl1 != null || rl2 != null || add != 0) {int sum = 0;// 若不为空,则累加上该节点的值,同时后移一位if (rl1 != null) {sum += rl1.val;rl1 = rl1.next;}if (rl2 != null) {sum += rl2.val;rl2 = rl2.next;}// 加上上一轮的进位sum += add;// 记录本轮的进位add = sum / 10;// 计算进位后的数sum %= 10;// 从低位到高位拼接新的链表// ans -> 0 -> 7  8  ==>  ans -> 8 -> 0 -> 7  ListNode node = new ListNode(sum);node.next = ans.next;ans.next = node;}return ans.next;}// 直接操作反转链表public static ListNode reverseListNode(ListNode head) {ListNode pre = null;while (head != null) {ListNode next = head.next;head.next = pre;pre = head;head = next;}return pre;}
}

(4) 回文链表

力扣链接:回文链表

在这里插入图片描述

在上一篇中我们讲解了判断回文链表可以使用集合+双指针或者栈来解决,我们还可以通过将双指针+反转一半链表再进行比较判断来实现。

class Solution {public boolean isPalindrome(ListNode head) {if (head == null) {return true;}// 找到前半部分链表的尾节点并反转后半部分链表ListNode firstHalfEnd = endOfFirstHalf(head);ListNode secondHalfStart = reverseList(firstHalfEnd.next);// 判断是否回文ListNode p1 = head;ListNode p2 = secondHalfStart;boolean result = true;while (result && p2 != null) {if (p1.val != p2.val) {result = false;}p1 = p1.next;p2 = p2.next;}        // 还原链表并返回结果firstHalfEnd.next = reverseList(secondHalfStart);return result;}// 反转链表private ListNode reverseList(ListNode head) {ListNode prev = null;ListNode curr = head;while (curr != null) {ListNode nextTemp = curr.next;curr.next = prev;prev = curr;curr = nextTemp;}return prev;}// 通过双指针找到后半部分第一个节点private ListNode endOfFirstHalf(ListNode head) {ListNode fast = head;ListNode slow = head;while (fast.next != null && fast.next.next != null) {fast = fast.next.next;slow = slow.next;}return slow;}
}

(5) K 个一组翻转链表

力扣链接:K 个一组翻转链表

在这里插入图片描述

这一题其实可以看做是指定区间反转的进阶形式,只要我们足够细心,其实这一题并不难。我们同样要使用到穿针引线法来解决问题,相当于使用穿针引线法反转若干个指定区间。

    public ListNode reverseKGroup(ListNode head, int k) {ListNode dummyNode = new ListNode();dummyNode.next = head;// 记录穿针引线法需要使用的四个标记点ListNode pre = dummyNode, right, left, succ;// 反转若干个指定区间while (true) {right = pre;// 1.寻找到反转区间right节点for (int i = 0; i < k; i++) {// 当一组不足k个时,表明不需要反转了,直接返回结果if (right.next == null) {return dummyNode.next;}right = right.next;}// 2.记录left节点以及succ节点left = pre.next;succ = right.next;// 3.反转指定区间right.next = null;right = reverse(left);// 4.穿针引线拼接链表pre.next = right;left.next = succ;// 5.更新为下一个区间的起始节点的前一个节点pre = left;}}// 反转链表public static ListNode reverse(ListNode head) {ListNode pre = null;while (head != null) {ListNode next = head.next;head.next = pre;pre = head;head = next;}return pre;}

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

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

相关文章

Jmeter接口自动化测试 —— Jmeter变量的使用

​在使用jmeter进行接口测试时&#xff0c;我们难免会遇到需要从上下文中获取测试数据的情况&#xff0c;这个时候就需要引入变量了。 定义变量 添加->配置元件->用户自定义的变量 添加->配置元件->CSV 数据文件设置 变量的调用方式&#xff1a;${变量名} 变量的…

Qt6.5类库实例大全:QWidget

哈喽大家好&#xff0c;我是20YC小二&#xff01;欢迎扫码关注公众号&#xff0c;现在可免费领取《C程序员》在线视频教程哦&#xff01; ~下面开始今天的分享内容~ 1. QWidget介绍 QWidget 是 Qt 框架中的一个核心类&#xff0c;用于创建图形用户界面(GUI)应用程序的基本可视…

iic应用篇

一.iic的优点 1. IIC总线物理链路简单&#xff0c;硬件实现方便&#xff0c;扩展性非常好&#xff08;1个主机控制器可以根据需求增加从机数量&#xff0c;同时删减从机数量也不会影响总线通信&#xff09;&#xff1b;IIC总线只需要SDA和SCL两条信号线&#xff0c;相比于PCI/…

融了超24亿一分钱不花,放银行吃利息,这家存储创企厉害了

​引言&#xff1a;AI与大模型风起云涌&#xff0c;催生了这匹存储“黑马” 【全球存储观察 &#xff5c; 科技热点关注】 这家总部设在美国的存储初创公司&#xff0c;真的赶上AI与大模型时代的风口了。Vast Data公司最新再次获得E轮融资1.18亿美元&#xff0c;但是这个存储…

【MySQL】:表的约束(上)

表的约束 一.非空约束二.default约束三.列描述四.zerofill五.主键1.单个主键2.复合主键 真正约束字段的是数据类型&#xff0c;但是数据类型约束很单一&#xff0c;需要有一些额外的约束&#xff0c;更好的保证数据的合法性&#xff0c;从业务逻辑角度保证数据的正确性。比如有…

TrustGeo代码理解(一)main.py

代码链接&#xff1a;https://github.com/ICDM-UESTC/TrustGeo 一、导入各种模块和数据库 # -*- coding: utf-8 -*- import torch.nnfrom lib.utils import * import argparse, os import numpy as np import random from lib.model import * import copy from thop import p…

sillyGirl(傻妞机器人)安装以及对接go-cqhttp(2023年12月)

目录 编写的原因 下载傻妞 注意&#xff01;&#xff01;注意&#xff01;&#xff01;&#xff01;注意&#xff01;&#xff01;&#xff01;&#xff01; 同样的下载go-cqhttp 安装以及配置 go-cqhttp 下载screen 创建go-cqhttp窗口 创建sillyGirl窗口 常见错误 编写…

Python玩转PDF:几招搞定的高效操作方法

更多资料获取 &#x1f4da; 个人网站&#xff1a;ipengtao.com 当在Python中操作PDF时&#xff0c;有几种常见的方法&#xff0c;每种方法都有其独特的优点和用例。在本文中&#xff0c;我们将深入探讨这些方法&#xff0c;并提供丰富的示例代码&#xff0c;以帮助大家更好地…

「Verilog学习笔记」可置位计数器

专栏前言 本专栏的内容主要是记录本人学习Verilog过程中的一些知识点&#xff0c;刷题网站用的是牛客网 timescale 1ns/1nsmodule count_module(input clk,input rst_n,input set,input [3:0] set_num,output reg [3:0]number,output reg zero);reg [3:0] cnt ; always (posed…

3-分布式存储之Ceph

任务背景 虽然使用了分布式的glusterfs存储, 但是对于爆炸式的数据增长仍然感觉力不从心。对于大数据与云计算等技术的成熟, 存储也需要跟上步伐. 所以这次我们选用对象存储. 任务要求 1, 搭建ceph集群 2, 实现对象存储的应用 任务拆解 1, 了解ceph 2, 搭建ceph集群 3, 了…

深度学习 | Pytorch深度学习实践 (Chapter 12 Basic RNN)

十二、Basic RNN —— 实际上就是对线性层的复用 使用RNN最重要的两点&#xff1a; 了解序列数据的维度&#xff1b;循环过程所用的权重共享机制&#xff1b; 一般就是自己写个循环&#xff0c;权重层重复用就行了&#xff1b; 回顾&#xff1a;-----------------------------…

破局创新,天翼云HBlock如何以小见大、以柔克刚?

引言&#xff1a;另辟蹊径开拓创新 不走传统存储厂商的“寻常路” 【全球存储观察 &#xff5c; 科技热点关注】 在分布式块存储领域&#xff0c;大部分厂商的安装软件套件大小都在GB级。然而&#xff0c;天翼云破天荒地将存储资源盘活系统HBlock的软件安装包浓缩到了170MB&a…

linux中proc与sys的区别

在Linux系统中&#xff0c;/sys目录和/proc目录都是特殊的虚拟文件系统&#xff0c;用于提供对系统内核和设备信息的访问。 虽然它们的作用有一些重叠&#xff0c;但它们在功能和用途上有一些区别。 功能&#xff1a; /sys目录主要用于提供对设备和驱动程序的信息和配置的访…

Python-乒乓球小游戏【附完整源码】

乒乓球小游戏 乒乓球小游戏是一个简单而有趣的2D页面交互式游戏&#xff0c;玩家可以通过键盘输入来控制球拍上下移动来接球&#xff0c;从而体验乒乓球的乐趣。该游戏有单人和双人两种模式 运行效果&#xff1a; 一&#xff1a;主程序&#xff1a; import sys import cfg …

Jupyter Notebook: 交互式数据科学和编程工具

Jupyter Notebook: 功能强大的交互式编程和数据科学工具 简介 Jupyter Notebook是一个开源的Web应用程序&#xff0c;广泛用于数据分析、科学计算、可视化以及机器学习等领域。它允许创建和共享包含实时代码、方程式、可视化和解释性文本的文档。总而言之&#xff0c;我认为它…

3D Font

在游戏中使用3D文本 只需添加预制件并立即生成您的文本。 特点: *真实3D字母&#xff0c;可用作游戏对象*移动友好低聚 *VR兼容 *WebGL兼容 *30种以上不同字体 *材料和颜色可定制 WebGL演示 https://indiechest.itch.io/3d-font-engine 下载&#xff1a; ​​Unity资源商店链…

【tips】base64编码怎么反显出图片

格式 <img width"400" height"300" src"data:image/jpeg;base64,这里存放base64的图片内容/>实际的数据展示是这样的 然后把以上的文件内容放置到html文件中 最终展示样例 点击这个 展示出来是这样的

opencv 十五 红外图像中虹膜的提取

一、算法需求 在医疗检测中&#xff0c;需要使用红外相机拍摄眼睛照片&#xff0c;然后提取出虹膜的区域。在拍摄过程瞳孔需要进行运动&#xff0c;其通常不在正前方&#xff0c;无法形成圆形&#xff0c;不能使用常规的霍夫圆检测进行提取定位。且在在红外图像中&#xff0c;…

将输入的字符串反向输出(c语言)

#include<stdio.h> #include<string.h> int main() {int i, j, k;char s[50], temp;gets(s);//输入k strlen(s);//计算字符的长度//反向输出for (i 0, j k - 1;i < k / 2;i, j--){temp s[i];s[i] s[j];s[j] temp;}puts(s);//输出 }

最近面试了一位5年的测试,一问三不知,还反怼我...

最近看了很多简历&#xff0c;很多候选人年限不小&#xff0c;但是做的都是一些非常传统的项目&#xff0c;想着也不能通过简历就直接否定一个人&#xff0c;何况现在大环境越来 越难&#xff0c;大家找工作也不容易&#xff0c;于是就打算见一见。 在沟通中发现&#xff0c;由…