【算法训练-链表 零】链表高频算法题看这一篇就够了

news2024/12/26 23:50:25

一轮的算法训练完成后,对相关的题目有了一个初步理解了,接下来进行专题训练,以下这些题目就是汇总的高频题目
在这里插入图片描述

题目题干直接给出对应博客链接,这里只给出简单思路、代码实现、复杂度分析

反转链表

依据难度等级分别为反转链表、区间反转链表、K个一组反转链表,【算法训练-链表 一】【反转链表】反转链表、区间反转链表、K个一组反转链表

反转链表【EASY】

LeetCode地址,双指针移动逐个扭转指针方向,关键词:双指针,临时节点
在这里插入图片描述


/**
 * Definition for singly-linked list.
 * public class ListNode {
 * int val;
 * ListNode next;
 * ListNode() {}
 * ListNode(int val) { this.val = val; }
 * ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode reverseList(ListNode head) {
        // 1 入参校验,如果链表为空或者只有一个节点,直接返回
        if (head == null || head.next == null) {
            return head;
        }

        // 2 定义双指针节点进行遍历反转操作
        ListNode pre = null;
        ListNode cur = head;

        // 3 遍历链表,进行反转操作
        while (cur!= null) {
            // 1 定义临时节点存储当前节点的下一个节点
            ListNode pNext = cur.next;
            // 2 当前节点断开指向上一个节点
            cur.next = pre;
            // 3 双指针向前移动
            pre = cur;
            cur = pNext;
        }

        // 4 返回尾节点,也就是新的头节点
        return pre;
    }
}

时间复杂度:O(N),遍历了一遍链表
空间复杂度:O(1), 额外使用了常数级的指针变量

区间反转链表【MID】

LeetCode地址,双指针到指定位置就位,然后进行区间内的反转操作,关键词:双指针,临时节点,虚拟头节点
在这里插入图片描述


/**
 * Definition for singly-linked list.
 * public class ListNode {
 * int val;
 * ListNode next;
 * ListNode() {}
 * ListNode(int val) { this.val = val; }
 * ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode reverseBetween(ListNode head, int left, int right) {
        // 1 入参校验,如果链表为空或者只有一个节点,直接返回
        if (head == null || head.next == null) {
            return head;
        }
        // 如果整数left和right相等或者left大于right,直接返回
        if (left == right || left > right) {
            return head;
        }

        // 2 定义虚拟头节点与双指针,如果头节点也被反转了就丢了,所以需要虚拟头节点
        ListNode dummyHead = new ListNode(-1);
        dummyHead.next = head;
        ListNode pre = dummyHead;
        ListNode cur = head;

        // 3 双指针同时向前left-1步走到修改位置
        for (int i = 1; i < left; i++) {
            cur = cur.next;
            pre = pre.next;
        }

        // 4 双指针开始在区间内进行反转操作1-2-3-4-5
        for (int i = left; i < right; i++) {
            // 记录节点3
            ListNode pNext = cur.next;
            // 节点2指向节点4
            cur.next = pNext.next;
            // 节点3指向节点2
            pNext.next = pre.next;
            // 节点1指向节点3 : 1-3-2-4-5, cur一直指向节点2,pre一直指向节点1,下一轮4和3-2整体交换
            pre.next = pNext;
        }

        // 5 返回区间反转后的链表
        return dummyHead.next;
    }
}

时间复杂度:O(N),遍历了一遍链表
空间复杂度:O(1), 额外使用了常数级的指针变量

K个一组反转链表【HARD】

LeetCode地址,递归的将区间进行反转然后进行拼接,关键词:双指针,递归
在这里插入图片描述


/**
 * Definition for singly-linked list.
 * public class ListNode {
 * int val;
 * ListNode next;
 * ListNode() {}
 * ListNode(int val) { this.val = val; }
 * ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode reverseKGroup(ListNode head, int k) {
        // 1 入参校验,如果链表为空或者只有一个节点或者k小于2,直接返回
        if (head == null || head.next == null || k < 2) {
            return head;
        }

        // 2 设置区间尾节点等于头节点
        ListNode tail = head;
        for (int i = 0; i < k; i++) {
            // 如果tail为空,说明链表长度小于k,无需反转,直接返回这段子区间的头节点
            if (tail == null) {
                return head;
            }
            // 如果tail不为空,tail指针向前移动,移动k步,直到移动到下一区间头节点
            tail = tail.next;
        }

        // 3 对区间进行反转操作,返回新的头节点
        ListNode newHead = reverse(head, tail);

        // 4 反转后,head变成了当前区间尾节点,tail作为下一子区间头节点传入方法递归,区间连接
        head.next = reverseKGroup(tail, k);

        // 5 返回新的头节点
        return newHead;
    }

    // 区间反转链表
    private ListNode reverse(ListNode head, ListNode tail) {
        // 1 入参判断,如果链表为空或者只有一个节点,直接返回
        if (head == null || head.next == null) {
            return head;
        }

        // 2 定义双指针节点进行遍历反转操作
        ListNode pre = null;
        ListNode cur = head;

        // 3 遍历链表,进行反转操作,tail为下一区间的头节点,所以这里是cur != tail
        while (cur != tail) {
            // 记录当前节点的下一个节点
            ListNode pNext = cur.next;
            // 当前节点断开指向上一个节点
            cur.next = pre;
            // 双指针向前移动
            pre = cur;
            cur = pNext;
        }

        // 4 返回尾节点,也就是新的头节点
        return pre;
    }
}

时间复杂度:O(N),虽然递归反转,但链表只遍历了一遍
空间复杂度:O(N/K)*O(1),递归的最大栈深度,也就是递归深度 *每次递归的空间复杂度

合并链表

依据难度分为合并两个有序链表以及合并K个有序链表,【算法训练-链表 二】【合并链表】合并两个有序链表、合并K个有序链表

合并两个有序链表【EASY】

LeetCode地址,主要思路就是双指针在两个有序链表上漫游,将较小的依次补全到新的链表上,关键词:双指针
在这里插入图片描述


/**
 * Definition for singly-linked list.
 * public class ListNode {
 * int val;
 * ListNode next;
 * ListNode() {}
 * ListNode(int val) { this.val = val; }
 * ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
        // 1 入参校验,如果链表为空直接返回
        if (list1 == null && list2 == null) {
            return null;
        }
        if (list1 == null) {
            return list2;
        }
        if (list2 == null) {
            return list1;
        }

        // 2 定义虚拟头节点和两个漫游指针
        ListNode dummyHead = new ListNode(-1);
        ListNode p1 = list1;
        ListNode p2 = list2;
        ListNode p = dummyHead;

        // 3 双指针分别在两个链表漫游
        while (p1 != null && p2 != null) {
            // 1 下一个节点挂p1
            if (p1.val <= p2.val) {
                p.next = p1;
                p1 = p1.next;
            } else {
                // 2 下一个节点挂p2
                p.next = p2;
                p2 = p2.next;
            }
            p = p.next;
        }

        // 4 如果其中一个链表空了则挂另一个链表
        if (p1 == null) {
            p.next = p2;
        }
        if (p2 == null) {
            p.next = p1;
        }

        // 5 返回头结点
        return dummyHead.next;
    }
}

时间复杂度:O(N+M),分别对两个链表进行了遍历
空间复杂度:O(1), 额外使用了常数级的指针变量

合并K个有序链表【HARD】

LeetCode地址,应用分治的思想,先将K个有序链表进行划分,然后两两合并,关键词:双指针,分治
在这里插入图片描述
划分完子问题,子问题处理完返回到上一层级
在这里插入图片描述


/**
 * Definition for singly-linked list.
 * public class ListNode {
 * int val;
 * ListNode next;
 * ListNode() {}
 * ListNode(int val) { this.val = val; }
 * ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode mergeKLists(ListNode[] lists) {
        return divideMerge(lists, 0, lists.length-1);
    }

    // 划分子问题
    private ListNode divideMerge(ListNode[] lists, int left, int right) {
        // 1 终止条件,划分到最后的单个链表
        if (left > right) {
            return null;
        }
        // 2 划分到最小单个链表直接返回
        if (left == right) {
            return lists[left];
        }
        // 3 合并两个链表
        int mid = left + (right - left) / 2;
        return mergeTwoLists(divideMerge(lists, left, mid), divideMerge(lists, mid + 1, right));
    }

    // 合并两个有序链表
    private ListNode mergeTwoLists(ListNode pHead1, ListNode pHead2) {
        // 1 入参校验,如果链表为空直接返回
        if (pHead1 == null && pHead2 == null) {
            return null;
        }
        if (pHead1 == null) {
            return pHead2;
        }
        if (pHead2 == null) {
            return pHead1;
        }

        // 2 定义虚拟头节点和两个漫游指针
        ListNode dummyHead = new ListNode(-1);
        ListNode p1 = pHead1;
        ListNode p2 = pHead2;
        ListNode cur = dummyHead;

        // 3 双指针分别在两个链表漫游
        while (p1 != null && p2 != null) {
            // 1 下一个节点挂p1
            if (p1.val <= p2.val) {
                cur.next = p1;
                p1 = p1.next;
            } else {
                // 2 下一个节点挂p2
                cur.next = p2;
                p2 = p2.next;
            }
            cur = cur.next;
        }

        // 4 如果其中一个链表空了则挂另一个链表
        if (p1 == null) {
            cur.next = p2;
        }
        if (p2 == null) {
            cur.next = p1;
        }

        // 5 返回头结点
        return dummyHead.next;
    }
}

时间复杂度:O(NLogN),二分分治进行了LogN次划分,每次遍历时间复杂度为O(N)
空间复杂度:O(LogN), 递归栈深度为LogN

链表查找

依据难度为:查找链表中倒数第K个节点,【算法训练-链表 六】【链表查找】:链表中倒数第k个节点

链表中倒数第k个节点【EASY】

LeetCode地址,解题思路就是应用快慢指针,快指针先于慢指针K步,然后同时移动,这样快指针到达链表尾部,慢指针刚好在倒数第K个节点,关键词:快慢指针


/**
 * Definition for singly-linked list.
 * public class ListNode {
 * int val;
 * ListNode next;
 * ListNode() {}
 * ListNode(int val) { this.val = val; }
 * ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode trainingPlan(ListNode head, int cnt) {
        // 1 入参校验如果head为空或者cnt小于1,返回head
        if (head == null || head.next == null || cnt < 1) {
            return head;
        }

        // 2 定义快慢指针都指向头结点
        ListNode fast = head;
        ListNode slow = head;

        // 3 快指针先前进cnt步
        for (int i = 1; i < cnt; i++) {
            // fast为null,证明给的用例cnt大于链表总长度,返回null
            if (fast == null) {
                return null;
            }
            fast = fast.next;
        }

        // 4 快慢指针同时前进,当快指针为尾节点时,慢指针位置就是倒数第K个
        while (fast.next != null) {
            fast = fast.next;
            slow = slow.next;
        }

        // 5 返回slow的位置
        return slow;
    }
}

时间复杂度:O(N),遍历了一遍链表
空间复杂度:O(1), 额外使用了常数级的指针变量

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

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

相关文章

2023数字科技生态展,移远通信解锁新成就

11月10日&#xff0c;以“数字科技&#xff0c;焕新启航”为主题的中国电信2023数字科技生态大会暨2023数字科技生态展在广州盛大启幕。作为物联网行业的龙头标杆&#xff0c;同时更与中国电信连续多年维持稳定友好的合作关系&#xff0c;移远通信受邀参加本次展会。 在本次展会…

Docker - DockerFile

Docker - DockerFile DockerFile 描述 dockerfile 是用来构建docker镜像的文件&#xff01;命令参数脚本&#xff01; 构建步骤&#xff1a; 编写一个dockerfile 文件docker build 构建成为一个镜像docker run 运行脚本docker push 发布镜像&#xff08;dockerhub&#xff0…

你真的会使用 MySQL中EXPLAIN吗

EXPLAIN是MySQL数据库中一个强大的工具&#xff0c;用于查询性能分析和优化。通过EXPLAIN&#xff0c;你可以查看MySQL查询的执行计划&#xff0c;了解MySQL是如何执行你的查询语句的。这篇文章将详细介绍EXPLAIN的使用&#xff0c;帮助你更好地理解和优化MySQL查询。 为什么使…

卫星通信和800MHz双管齐下,中国电信对中国移动发起新挑战

依靠国内某科技企业的宣传&#xff0c;卫星通信大热&#xff0c;中国电信也由此成为受益者&#xff0c;日前中国电信又大举招标25万座800MHz 5G基站&#xff0c;显示出中国电信积极以技术优势挑战中国移动。 一、中国电信急起直追 自从4G时代以来&#xff0c;中国电信就在国内通…

web3 React dapp进行事件订阅

好啊&#xff0c;上文web3 React Dapp书写订单 买入/取消操作 我们已经写好了 填充和取消订单 这就已经是非常大的突破了 但是 留下了一个问题 那就是 我们执行完之后 订单的数据没有直接更新 每次都需要我们手动刷新 才能看到结果 那么 今天我们就来看解决这个问题的事件订阅 …

ISP图像处理Pipeline

参考&#xff1a;1. 键盘摄影(七)——深入理解图像信号处理器 ISP2. Understanding ISP Pipeline3. ISP图像处理流程介绍4. ISP系统综述5. ISP(图像信号处理)之——图像处理概述6. ISP 框架7. ISP(图像信号处理)算法概述、工作原理、架构、处理流程8. ISP全流程简介9. ISP流程介…

spring boot中使用Bean Validation做优雅的参数校验

一、Bean Validation简介 Bean Validation是Java定义的一套基于注解的数据校验规范&#xff0c;目前已经从JSR 303的1.0版本升级到JSR 349的1.1版本&#xff0c;再到JSR 380的2.0版本&#xff08;2.0完成于2017.08&#xff09;&#xff0c;目前最新稳定版2.0.2&#xff08;201…

互联网Java工程师面试题·微服务篇·第二弹

目录 18、什么是 Spring 引导的执行器&#xff1f; 19、什么是 Spring Cloud&#xff1f; 20、Spring Cloud 解决了哪些问题&#xff1f; 21、在 Spring MVC 应用程序中使用 WebMvcTest 注释有什么用处&#xff1f; 22、你能否给出关于休息和微服务的要点&#xff1f; 23、…

正点原子嵌入式linux驱动开发——Linux DAC驱动

上一篇笔记中学习了ADC驱动&#xff0c;STM32MP157 也有DAC外设&#xff0c;DAC也使用的IIO驱动框架。本章就来学习一下如下在Linux下使用STM32MP157上的DAC。 DAC简介 ADC是模数转换器&#xff0c;负责将外界的模拟信号转换为数字信号。DAC刚好相反&#xff0c;是数模转换器…

mysql数据库可以执行定时任务

在一些业务需要中&#xff0c;经常需要一些定时任务。如Java的schedule&#xff0c;nodejs的node-schedule等。今天第一次接触了使用数据库的存储过程来执行定时任务。 本篇文章以MySQL数据库为例&#xff0c;介绍通过数据库设置定时任务的方法。本文中以介绍操作过程为主&…

注册并实名认证华为开发者账号流程

文 | Promise Sun 1. 打开华为开发者网址&#xff1a; https://www.harmonyos.com 2.注册华为开发者账号&#xff1a; 1&#xff09;注册时可以选择手机号或者邮箱两种方式注册&#xff0c;建议选择手机号注册。 2&#xff09;根据提示填写信息注册即可。 3.开发者实名认证&am…

P6入门:项目初始化7-项目详情之代码/分类码Code

前言 使用项目详细信息查看和编辑有关所选项目的详细信息&#xff0c;在项目创建完成后&#xff0c;初始化项目是一项非常重要的工作&#xff0c;涉及需要设置的内容包括项目名&#xff0c;ID,责任人&#xff0c;日历&#xff0c;预算&#xff0c;资金&#xff0c;分类码等等&…

lc307.区域和检索 - 数组可修改

暴力解法 创建方法&#xff0c;通过switch-case判断所需要调用的方法。 public class RegionsAndSertches {public static void main(String[] args) {String[] str new String[]{"NumArray", "sumRange", "update", "sumRange"};i…

算法笔记-第五章-分数的四则运算

分数的四则运算 分数约分分数加法分数减法分数乘法分数除法分数的输出 分数约分 #include <cstdio> #include <algorithm> using namespace std; struct Fraction {//用结构体表示分子和分母int up, down; }; int gcd(int a, int b) {//求出最大公约数if (b 0) {r…

RTOS实时操作系统在嵌入式开发中的应用

随着各种嵌入式系统应用的日益复杂和对实时性要求的提高&#xff0c;使用实时操作系统&#xff08;RTOS&#xff09;成为嵌入式开发中的一种重要选择。STM32微控制器作为一种强大的嵌入式处理器&#xff0c;与各种RTOS相结合&#xff0c;能够提供更高效、可靠并且易于维护的系统…

CDN加速技术:节点部署的专业指南

随着互联网的迅猛发展&#xff0c;网站的访问量也在不断增加。为了提供更快、更稳定的用户体验&#xff0c;许多网站都采用了剑盾上云CDN&#xff08;内容分发网络&#xff09;技术。在CDN加速中&#xff0c;节点的合理部署是关键一环&#xff0c;决定了加速效果的优劣。本文将…

Android launchWhenXXX 和 repeatOnLifecycle

文章目录 Android launchWhenXXX 和 repeatOnLifecyclelifecycleScope和viewModelScopelaunchWhenXXXrepeatOnLifecycleflowWithLifecycle总结 Android launchWhenXXX 和 repeatOnLifecycle lifecycleScope和viewModelScope LiveData优点&#xff1a; 避免内存泄露风险&…

算法笔记-第五章-质因子分解

算法笔记-第五章-质因子分解 小试牛刀质因子2的个数丑数 质因子分解最小最大质因子约数个数 小试牛刀 质因子2的个数 #include<cstdio> int main() {int n; scanf_s("%d", &n); int count 0; while (n % 2 0) {count; n / 2; }printf("%…

P36[11-1]SPI通信协议

SPI相比于IIC的优缺点: 1.SPI传输速度快(IIC高电平驱动能力较弱,因此无法高速传输) 2.使用简单 3.通信线多 SCK(SCLK,CK,CLK):串行时钟线 MOSI(DO):主机输出,从机输入 MISO(DI): 主机输入,从机输出 SS(NSS,CS):从机选择(有多少个从机,主机就要用几根SS分别与从机连接…

人工智能基础_机器学习026_L1正则化_套索回归权重衰减梯度下降公式_原理解读---人工智能工作笔记0066

然后我们继续来看套索回归,也就是线性回归,加上了一个L1正则化对吧,然后我们看这里 L1正则化的公式是第二个,然后第一个是原来的线性回归,然后 最后一行紫色的,是J= J0+L1 对吧,其实就是上面两个公式加起来 然后我们再去看绿色的 第一行,其实就是原来线性回归的梯度下降公式…