学习记录:js算法(二十七):重排链表、删除链表的倒数第 N 个结点

news2024/12/27 10:53:52

文章目录

    • 重排链表
      • 我的思路
      • 网上思路
    • 删除链表的倒数第 N 个结点
      • 我的思路
      • 网上思路
    • 总结

重排链表

给定一个单链表 L 的头节点 head ,单链表 L 表示为:
L0 → L1 → … → Ln - 1 → Ln
请将其重新排列后变为:
L0 → Ln → L1 → Ln - 1 → L2 → Ln - 2 → …
不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。

图一
在这里插入图片描述

图二
在这里插入图片描述

示例1:(图一)
输入:head = [1,2,3,4]
输出:[1,4,2,3]


示例2:(图二)
输入:head = [1,2,3,4,5]
输出:[1,5,2,4,3]

我的思路

  1. 找到中间节点
  2. 反转后半部分链表
  3. 合并两个链表

网上思路
递归

我的思路

var reorderList = function(head) {
    if (!head || !head.next) return;
    // Step 1: 找到中间节点
    let slow = head, fast = head;
    while (fast.next && fast.next.next) {
        slow = slow.next;
        fast = fast.next.next;
    }
    // Step 2: 反转后半部分链表
    let secondHalf = slow.next;
    slow.next = null;
    let prev = null;
    while (secondHalf) {
        let next = secondHalf.next;
        secondHalf.next = prev;
        prev = secondHalf;
        secondHalf = next;
    }

    // Step 3: 合并两个链表
    let first = head, second = prev;
    while (second) {
        let t1 = first.next, t2 = second.next;
        first.next = second;
        second.next = t1;
        first = t1;
        second = t2;
    }
};

讲解

  1. 找到中间节点:首先,我们需要找到链表的中间节点。这可以通过快慢指针技术来实现。快指针每次移动两步,慢指针每次移动一步。当快指针到达链表尾部时,慢指针将指向链表的中间节点。
  2. 反转后半部分链表:找到中间节点后,我们需要反转链表的后半部分。这可以通过迭代的方式实现,每次将当前节点的下一个节点指向前一个节点。
  3. 合并两个链表:最后,我们将链表的前半部分和反转后的后半部分交错合并。我们交替地从两个链表中取出节点,将它们连接在一起。

网上思路

const reorderList = (head) => {
    // 1. 设置链表栈
    const headList = [];

    // 2. 设置链表长度
    let length = 0;

    // 3. 设置链表位置
    let pos = 0;

    // 4. 设置链表一半
    let harf = 0;

    // 5. 递归方法体
    const recursion = (head) => {

        // 5.1 链表位置 + 1
        pos++;

        // 5.2 链表长度 + 1
        length++;

        // 5.3 如果到链表尾
        if (!head) {

            // 5.4 获取链表一半长度,5 -> 3;4 -> 2
            harf = Math.round(pos / 2);

            // 5.5 终止链表递归
            return;
        }

        // 5.6 进一步递归
        recursion(head.next);

        // 5.7 链表位置 - 1
        pos--;

        /*
          5.8 链表长度为奇数的情况
          假设链表为 1->2->3->4->null,它的长度为 5(算上 null),harf 为 3
          那么,pos 大于 harf,即 pos 为 4 的时候,收入链表栈 headList 中
          pos 为 3 的时候,head.next 为 null
          pos 小于 3 - 1 的时候,我们推出链表栈 headList 中的元素
        */

        /*
          5.9 链表长度为偶数的情况
          假设链表为 1->2->3->4->5->null,它的长度为 6,harf 为 3
          那么,pos 大于 harf,即 pos 为 5 和 4 的时候,收入链表栈 headList 中
          pos 为 3 的时候,head.next 为 null
          pos 小于 3 的时候,我们推出链表栈 headList 中的元素
        */
        if (pos > harf) {
            headList.push(head);
        } else if (pos === harf) {
            head.next = null;
        } else if (length % 2 !== 0 && pos < harf - 1) {
            const slice = headList.pop();
            slice.next = head.next;
            head.next = slice;
        } else if (length % 2 === 0 && pos < harf) {
            const slice = headList.pop();
            slice.next = head.next;
            head.next = slice;
        }
    };

    // 6. 递归当前链表
    recursion(head);

    // 7. 返回链表(测试用,提交的时候不需要)
    return head;
};

讲解
详情见代码和注释

删除链表的倒数第 N 个结点

给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。

图三
在这里插入图片描述

示例 1(图三)
输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]

示例 2:
输入:head = [1], n = 1
输出:[]

示例 3:
输入:head = [1,2], n = 1
输出:[1]

我的思路
双指针
网上思路
递归+迭代

我的思路

var removeNthFromEnd = function (head, n) {
    // 创建一个哑节点作为辅助,防止删除头节点时的边界问题
    let dummy = new ListNode(0);
    dummy.next = head;

    let slow = dummy;
    let fast = dummy;

    // 让fast先走n+1步,因为dummy的存在,所以fast走到n步时,slow恰好在正确的位置
    for (let i = 0; i <= n; i++) {
        fast = fast.next;
    }

    // 现在两个指针一起走,当fast走到链表尾部时,slow刚好在待删节点的前一个位置
    while (fast !== null) {
        slow = slow.next;
        fast = fast.next;
    }

    // 删除slow.next指向的节点
    slow.next = slow.next.next;

    // 返回真正的头节点
    return dummy.next;
};

讲解
解决这个问题,一个常见的策略是使用“快慢指针”技巧。这个方法的关键是先让一个指针(快指针)提前走 n步,然后两个指针(快指针和慢指针)一起前进,当快指针到达链表尾部时,慢指针所在的位置就是要删除的节点的前一个位置。接下来,只需要做适当的指针调整就可以删除目标节点。

  1. 初始化两个指针:slow和fast ,均指向链表头部。
  2. 快指针先走:让 fast指针 先向前移动 n步
  3. 同步前进:如果 fast 还没有到达链表尾部,那么 slow和fast 同时向前移动。
  4. 定位删除节点:当 fast 到达链表尾部时,slow所指向的节点就是待删除节点的前一个节点。
  5. 删除节点:调整指针,跳过待删除的节点。
  6. 特殊情况处理:如果要删除的是头节点,需要特殊处理。

网上思路

// 方法一:递归
const removeNthFromEnd = (head, n) => {
  // 1. 设置倒数节点
  let lastN = 0;

  // 2. 设置递归
  const recursion = (head) => {

    // 2.1 如果抵达链表尾,返回 null
    if (!head) {
      return null;
    }

    // 2.2 设置 next 表示下一个节点
    const next = recursion(head.next);

    // 2.3 每次递归的【归】环节,lastN + 1
    lastN++;

    // 2.4 如果 lastN 和 n 相同,则进行删节点操作
    if (lastN === n) {
      head = next;
    }

    // 2.5 再【归】一层后,修正 next 指向
    if (lastN === n + 1) {
      head.next = next;
    }

    // 2.6 返回最终节点
    return head;
  };

  // 3. 调用递归
  return recursion(head);
};
// 方法二:迭代
const removeNthFromEnd = (head, n) => {
  /* —————— 第一部分 —————— */

  // 1. 设置 nowHead 为数组型链表:[1, 2, 3]...
  const nowHead = [head];

  // 2. 设置 tempHead 来扫描一趟,获取 nowHead
  let tempHead = head;

  // 3. 每次让 tempHead 前进,添加到 nowHead 中
  while (tempHead.next) {
    nowHead.push(tempHead.next);
    tempHead = tempHead.next;
  }

  /* —————— 第二部分 —————— */

  // 1. 设置倒数数字
  let lastN = 0;

  // 2. 判断是否需要删除头部
  let isHead = true;

  // 3. 如果 nowHead 存在内容
  while (nowHead.length) {

    // 3.1 倒数数字 + 1
    lastN++;

    // 3.2 获取当前部分的链表
    const now = nowHead.pop();

    // 3.3 如果不是头部,例如删除 [1, 2, 3] 中的 2 或者 3
    if (lastN - 1 === n) {
      
      // 3.3.1 表明我们删掉了非头部的节点
      isHead = false;

      // 3.3.2 让它指向 next.next
      now.next = now.next.next;
    }
  }

  // 4. 如果我们没有做删除操作,说明需要删除头部
  if (isHead) {
    head = head.next;
  }

  // 5. 返回最终链表
  return head;
};

讲解
具体解法见注释

总结

今天较忙,网上思路暂未自己讲解,得自己先看注释,后续有时间再写

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

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

相关文章

oracle数据库安装和配置

​ 大家好&#xff0c;我是程序员小羊&#xff01; 前言&#xff1a; Oracle 数据库的安装和配置是一个较为复杂的过程&#xff0c;涉及多个步骤和配置项。以下将详细介绍如何在 Linux 和 Windows 系统中安装 Oracle 数据库并进行基础配置。 一、Oracle 数据库安装前的准备 …

结账打印--SAAS本地化及未来之窗行业应用跨平台架构

一代码 var 打印数据 {shopname:"广发系统"};var 打印渲染2 打印模板.解析(打印模板,打印数据x,"wlzc");console.log("未来之城");console.log(打印渲染2);var 对话框_打印_id "多大啥事";var 对话框_打印_内容 未来之窗_打印数据渲…

ICM20948 DMP代码详解(6)

接前一篇文章&#xff1a;ICM20948 DMP代码详解&#xff08;5&#xff09; 前一篇文章解析了EMP-App中的入口函数main()中重点关注的第1段代码&#xff0c;本回继续往下进行解析。为了便于理解和回顾&#xff0c;再次贴出main函数源码&#xff1a; int main (void) {int rc 0…

一次关于生产环境服务无故宕机的排查过程

故事的开始 这个故事是在一年之前&#xff0c;当时我们的系统运行在客户的k8s环境上。然后很神奇的是每个月底我们都会服务宕机&#xff0c;当然我们开启了多个实例。当时的容器线条就像心跳图一样&#xff08;或许有些描述的不太准确&#xff0c;我没有找到当时那个像心电图一…

【Map】、集合总结

Map(*)——映射 比较之前的集合 List 为什么使用map <k,v>&#xff1a;key–value Api–>尽量用k去操作value put<k,v> package com.ffyc.map;import java.util.HashMap; import java.util.Map;/*** 映射*/ public class MapDemo {public static void main(St…

Linux下的Makefile与进度条程序

目录 Linux下的Makefile与进度条程序 Makefile与make Makefile与make介绍 创建第一个Makefile并使用make Makefile文件基本格式介绍 Makefile依赖方法执行过程 Makefile通用写法 进度条程序 实现效果 前置知识 回车(\r)与换行(\n) 输出缓冲区 实现进度条 Linux下的…

vue+ThreeJS:从0 到1 搭建开发环境

文章目录 一、下载安装&#xff08;懒人版&#xff09;二、顺序安装1&#xff0c;下载安装nodejs2&#xff0c;安装vue-cli3&#xff0c;创建vue-three 项目。4&#xff0c;安装threeJS5&#xff0c;安装element UI &#xff08;选装&#xff09;最终package.json文件如下&…

C语言深入理解指针3

1.字符指针变量 在指针类型中char*是字符指针 int main() {char ch w;char* pc &ch;//pc是字符指针变量//字符指针变量是用来存放地址的const char* p "abcsefghi";// 不是将abcdefghi\0存放到p中// 而是将首字符a的地址存放在p中// "abcsefghi"是…

逻辑代数的基本规则

目录 逻辑代数的基本规则 带入规则 反演规则 对偶规则 逻辑代数的基本规则 带入规则 将逻辑等式两边的某一变量均用同一个逻辑函数代替&#xff0c;等式仍然成立。 可以用A非代替A&#xff0c;也可以用C代替B。 也可使用BC这样一个整体代替B。 反演规则 可以把与换或&#x…

营养作用的对象是有区别的 第八篇

除了7大营养素 还需要补充其他营养素 食品营养学 临床营养学 大众营养学 食品营养学 你要早点就开始预防

怎么强制撤销excel工作表保护?

经常不是用的Excel文件设置了工作表保护&#xff0c;偶尔打开文件的时候想要编辑文件&#xff0c;但是发现忘记了密码&#xff0c;那么这种情况&#xff0c;我们怎么强制撤销excel工作表保护&#xff1f;今天分享两种解决方法。 方法一、 将excel文件转换为其他文件格式&…

C语言进阶【1】--字符函数和字符串函数【1】

本章概述 字符分类函数字符转换函数strlen的使用和模拟实现strcpy的使用和模拟实现strcat的使用和模拟实现strcmp的使用和模拟实现彩蛋时刻&#xff01;&#xff01;&#xff01; 字符分类函数 字符&#xff1a; 这个概念&#xff0c;我们在以前的文章中讲过了。我们键盘输入的…

通信工程学习:什么是MPC多媒体个人计算机、MCS多媒体计算机系统

一、MPC多媒体个人计算机&#xff08;Multimedia Personal Computer&#xff09; 1、MPC多媒体个人计算机定义 多媒体个人计算机&#xff08;MPC&#xff09;是指具备处理多媒体信息&#xff08;如音频、视频、图像、动画和文本等&#xff09;能力的个人计算机。它不仅具备传统…

html记账本改写:保存数据 localStorage。

<!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><title>记账本改写</title><style>table {user-select: none;/* width: 100%; */border-collapse: collapse;}table,th,td {border: 1px solid…

数据集 3DPW-开源户外三维人体建模-姿态估计-人体关键点-人体mesh建模 >> DataBall

3DPW 3DPW-开源户外三维人体建模数据集-姿态估计-人体关键点-人体mesh建模 开源户外三维人体数据集 inproceedings{vonMarcard2018, title {Recovering Accurate 3D Human Pose in The Wild Using IMUs and a Moving Camera}, author {von Marcard, Timo and Henschel, Robe…

从“游戏科学”到玄机科技:《黑神话:悟空》的视角打开动漫宇宙

近日&#xff0c;中国游戏界迎来了一场前所未有的盛事——由游戏科学公司开发的《黑神话&#xff1a;悟空》正式上线&#xff0c;并迅速成为全球玩家热议的焦点。在居高不下的讨论热度中&#xff0c;有人说他的成功在于对《西游记》为背景进行改编&#xff0c;对原著进行了分析…

读软件设计的要素04概念的关系

1. 概念的关系 1.1. 概念是独立的&#xff0c;彼此间无须相互依赖 1.1.1. 一个概念是应该独立地被理解、设计和实现的 1.1.2. 独立性是概念的简单性和可重用性的关键 1.2. 软件存在依赖性 1.2.1. 不是说一个概念需要依赖另一个概念才能正确运行 1.2.2. 只有当一个概念存在…

1 模拟——67. 二进制求和

1 模拟 67. 二进制求和 给你两个二进制字符串 a 和 b &#xff0c;以二进制字符串的形式返回它们的和。 示例 1&#xff1a; 输入:a "11", b "1" 输出&#xff1a;"100" 示例 2&#xff1a; 输入&#xff1a;a "1010", b "…

单GPU一分钟生成16K高清图像!新加坡国立发布LinFusion:无缝兼容Stable Diffusion插件

论文链接&#xff1a;https://arxiv.org/pdf/2409.02097 Git链接&#xff1a;https://lv-linfusion.github.io/ 亮点直击 本文研究了Mamba的非因果和归一化感知版本&#xff0c;并提出了一种新颖的线性注意力机制&#xff0c;解决了扩散模型在高分辨率视觉生成中的挑战。 本文…

Vue——day11之生命周期

目录 生命周期的八个阶段 生命周期执行的流程图 代码示例 总结 Vue的生命周期是指在Vue实例创建、挂载、更新和销毁过程中&#xff0c;会触发的一系列钩子函数。这些钩子函数可以用来在不同的生命周期阶段执行相应的逻辑操作。 生命周期的八个阶段 Vue的生命周期可以分为…