链表-双指针-虚拟节点-力扣

链表--双指针--虚拟节点

  • 力扣 142 环形链表求循环起点 重点
  • 力扣 21 合并两个有序链表
  • 力扣 86 分割链表
  • 力扣23 合并K个有序链表 -- 优先队列(二叉堆 小顶堆)重点
  • 力扣19 找倒数第N个元素 快慢指针 + 一次遍历 重点
  • 力扣876 快慢指针找中间节点
  • 力扣 160 相交链表 遍历“两遍”
  • 辅助测试类

力扣 142 环形链表求循环起点 重点

在这里插入图片描述

/*力扣142 环形链表II
     * 给定一个链表的头节点  head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
     * 如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。
     * 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。
     * 如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。
     * 不允许修改 链表。*/
    public static ListNode detectCycle(ListNode head) {
        ListNode dummy = new ListNode(-1);
        dummy.next = head;
        ListNode fast = dummy;
        ListNode slow = dummy;
        ListNode newHead = dummy;
        boolean symbol = false;

        while (fast != null && fast.next != null) {
            // 注意 Fast和Fast.next 都不为 null
            // 否则 Fast.next.next 会报空指针错误
            fast = fast.next.next;
            slow = slow.next;
            if (slow == fast) {
                //若能相等则 有环
                // slow 走K步 fast走K步
                // K = t + nS + M
                // 2K = t + mS + M
                // 非循环距离: t
                // 循环周长 :  S
                // 循环起点距离相遇点: M
                // 则 相遇 意味着有环 意味着 K = (m-n)S
                // 则 在Fast不再动的情况下 slow 再走 K 步 依然在这个相遇点
                // 则 其走K-M步一定在循环起点
                // 同理 在slow走第二个K的时候同时设置一个新的指针从头结点开始也走K-M步
                // 则两者应该再循环起点相遇
                symbol = true;
                break;
            }
        }
        if (symbol) {
            // 有环
            // slow走K-M步 fast不动 newHead 走K-M步
            // newHead 与 slow 在循环起点 重合
            while (newHead != slow) {
                newHead = newHead.next;
                slow = slow.next;
            }
            // newHead == slow
            return newHead;
        } else {
            // 无环
            return null;
        }
    }

力扣 21 合并两个有序链表

/*1、合并两个有序链表
         力扣 21
            将两个升序链表合并为一个新的 升序 链表并返回。
            新链表是通过拼接给定的两个链表的所有节点组成的
            两个链表的节点数目范围是 [0, 50]
            -100 <= Node.val <= 100
            l1 和 l2 均按 非递减顺序 排列
     */
    public static ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        ListNode dummy = new ListNode(-1);
        // 设置虚拟头结点
        ListNode p = dummy;
        //p和dummy都是存储同一个对象地址
        ListNode p1 = l1, p2 = l2;
        while (p1 != null && p2 != null) {
            if (p1.val < p2.val) {
                p.next = p1;
                p1 = p1.next;
                p = p.next;
            } else {
                p.next = p2;
                p2 = p2.next;
                p = p.next;
                // p要向前进
            }
        }
        if (p1 != null) {
            p.next = p1;
        }
        if (p2 != null) {
            p.next = p2;
        }
        return dummy.next;
    }

力扣 86 分割链表

/*
     * 给你一个链表的头节点 head 和一个特定值 x ,请你对链表进行分隔,
     * 使得所有 小于 x 的节点都出现在 大于或等于 x 的节点之前。
     * 你应当 保留 两个分区中每个节点的初始相对位置。*/
    public static ListNode partition(ListNode head, int x) {
        ListNode dummy1 = new ListNode(-1);
        ListNode dummy2 = new ListNode(-1);

        ListNode h = head;
        ListNode p1 = dummy1;
        ListNode p2 = dummy2;

        while (h != null) {
            if (h.val < x) {
                p1.next = h;
                // 向dummy1 的链表尾部添加 h节点
                h = h.next;
                // head 链表向后走一步
                p1 = p1.next;
                // p1 指向 dummy1链表末尾节点
                p1.next = null;
                // p1 末尾的next 设置为 null
            } else {
                p2.next = h;
                h = h.next;
                p2 = p2.next;
                p2.next = null;
            }
        }
        p1.next = dummy2.next;
        return dummy1.next;
    }

力扣23 合并K个有序链表 – 优先队列(二叉堆 小顶堆)重点

/*给你一个链表数组,每个链表都已经按升序排列。
    请你将所有链表合并到一个升序链表中,返回合并后的链表。*/
    public static ListNode mergeKLists(ListNode[] lists) {
        if (lists.length < 1) {
            // 如果用 < 1 的值创建优先队列 则会产生异常
            // IllegalArgumentException - if initialCapacity is less than 1
            return null;
        } else {
            PriorityQueue<ListNode> priorityQueue = new PriorityQueue<ListNode>(lists.length, new Comparator<ListNode>() {
                @Override
                public int compare(ListNode o1, ListNode o2) {
                    return o1.val - o2.val;
                }
            });
            // 优先级队列(二叉堆)  设置为最小堆  队列长度为lists的元素个数  有K个链表 传入 K 为队列长度

            ListNode dummy = new ListNode(-1); // 设置虚拟头结点 等待最后返回dummy.next
            ListNode p = dummy;

            for (ListNode listNode : lists) {
                if (listNode != null) {
                    priorityQueue.add(listNode);
                    // 如果 此链表不为空 则将头结点 存入最小堆的优先队列
                }
            }
            while (!priorityQueue.isEmpty()) {
                // 当优先队列内元素个数不为空 时
                ListNode temp = priorityQueue.poll();// temp 指向 队列头元素 同时队头出队
                p.next = temp;
                p = p.next;
                if (temp.next != null) {
                    priorityQueue.add(temp.next); // 刚出队的链表的下一个元素 进入队列
                }
            }
            return dummy.next;
        }
    }

力扣19 找倒数第N个元素 快慢指针 + 一次遍历 重点

/*力扣19 给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
     * 要求: 只遍历一次结点 思路:两个指针一个在前一个在后
     * 因为要往前走N步 设置虚拟头结点的情况下 代码更清楚简洁 不必要多判断
     * 只有一个元素的情况(因为如果不用虚拟头结点的话, 只有一个元素时起始节点就在此)*/
    public static ListNode removeNthFromEnd(ListNode head, int n) {
        // 题目默认head不为空 链表元素个数>=1
        // 先要找到倒数 第N个节点
        ListNode dummy = new ListNode(-1);
        // 设置虚拟头结点 方便向前走N步
        dummy.next = head;
        ListNode p1 = dummy;
        ListNode p2 = dummy;

        for (int i = 0; i < n; i++) {
            p1 = p1.next;
            // 向前走N步
        }
        if (p1 == null) {
            return null;
            // 链表不够长 总数都不够N
        }
        while (p1.next != null) {
            p1 = p1.next;
            p2 = p2.next;
        }
        // 当 p1 为 末尾元素时 退出循环
        // 此时 p2.next 指向 倒数第N个元素
        ListNode p3 = p2.next.next;
        p2.next = p3;
        // 删除倒数第N个元素

        return dummy.next;
    }

力扣876 快慢指针找中间节点

/*力扣876
    给你单链表的头结点 head ,请你找出并返回链表的中间结点。
    如果有两个中间结点,则返回第二个中间结点。
    思路: 虚拟节点 + 快慢指针 快的一次走两步 慢的一次走一步
          当快的指向空的时候 慢的刚好在中间节点的位置(同时适用于奇数个元素和偶数个元素的情况)*/
    public static ListNode middleNode(ListNode head) {
        ListNode dummy = new ListNode(-1);
        dummy.next = head;
        ListNode p1 = dummy;
        ListNode p2 = dummy;

        while (p1 != null) {
            p2 = p2.next;
            // 奇数个元素时 最后一步走之前 p1.next != null && p1.next.next == null 成立
            // 偶数个元素时 最后一步走之前 p1.next == null 成立
            // 所以 让p1=p1.next.next 为一大步 的情况下
            // 偶数个的情况下不能走最后一大步所以下面用了break
            // 但P2 必须要走这一步
            if (p1.next == null) {
                break;
            } else {
                p1 = p1.next.next;
            }
        }
        return p2;
    }

力扣 160 相交链表 遍历“两遍”

/*
     * 力扣160 相交链表
     * 给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。
     * 如果两个链表不存在相交节点,返回 null 。
     * 思路:两个指针同步向前走  A走到末尾之后下一步走B的头部,B走到末尾之后下一步走到A的头部
     * 则 两个指针会同时在第一个合并节点处同步到达 p==q 退出循环
     * 如果没有共同部分 则 会同时到null 依然满足p==q 退出循环 */
    public static ListNode getIntersectionNode(ListNode headA, ListNode headB) {
         /*
        很奇怪 我用虚拟头结点 就超时 放弃用虚拟头结点就通过力扣测试
        
        // 若用虚拟头结点 则要大量使用 p.next 而我们要 在末尾 重新转入 另一个链表表头
        // 故在转入的时候 不要用next 防止篡改 链表 导致死循环
        ListNode dummy = new ListNode(-1);
        dummy.next = headA;
        ListNode dummy2 = new ListNode(-1);
        dummy2.next = headB;

        ListNode p = dummy;
        ListNode q = dummy2;

        while (p != q) {
            if (p.next == null) {
                // 将P的位置改变 而不改变原链表结构
                p = headB;
            }
            if (q.next == null) {
                // 将Q的位置改变 而不改变原链表结构
                q = headA;
            }
            p = p.next;
            q = q.next;
        }
        return p;*/

        ListNode p = headA;
        ListNode q = headB;
        while (p != q) {
            if (p == null) {
                // 将P的位置改变 而不改变原链表结构
                p = headB;
            } else {
                p = p.next;
            }
            if (q == null) {
                // 将Q的位置改变 而不改变原链表结构
                q = headA;
            } else {
                q = q.next;
            }
        }
        return p;
    }

辅助测试类

package com.caoii.LinkedList;

public class ListNode {
    public int val;
    public ListNode next;

    ListNode() {
    }

    public ListNode(int val) {
        this.val = val;
    }

    ListNode(int val, ListNode next) {
        this.val = val;
        this.next = next;
    }
}
package com.caoii;/*
 *@program:labu-pratice-study
 *@package:com.caoii
 *@author: Alan
 *@Time: 2024/4/14  11:38
 *@description: 双指针解决链表问题相关应用的测试
 */

import com.caoii.LinkedList.DoublePointerInLinkedList;
import com.caoii.LinkedList.ListNode;
import org.junit.jupiter.api.Test;

public class DoublePointerTest {

    /*力扣21 有序链表合并*/
    @Test
    public void test_01() {
        ListNode l1 = new ListNode(1);
        ListNode p1 = l1;
        p1.next = new ListNode(2);
        p1 = p1.next;
        p1.next = new ListNode(4);

        ListNode l2 = new ListNode(1);
        ListNode p2 = l2;
        p2.next = new ListNode(3);
        p2 = p2.next;
        p2.next = new ListNode(9);

        ListNode returnListNode = DoublePointerInLinkedList.mergeTwoLists(l1, l2);
        ListNode p = returnListNode;
        while (p != null) {
            System.out.print(p.val + " ");
            p = p.next;
        }
        System.out.println();
    }

    /*力扣86 将一个链表分割成两个链表再合并*/
    @Test
    public void test_02() {
        ListNode l1 = new ListNode(1);
        ListNode p1 = l1;
        p1.next = new ListNode(4);
        p1 = p1.next;
        p1.next = new ListNode(3);
        p1 = p1.next;
        p1.next = new ListNode(2);
        p1 = p1.next;
        p1.next = new ListNode(5);
        p1 = p1.next;
        p1.next = new ListNode(2);


        ListNode returnListNode = DoublePointerInLinkedList.partition(l1, 3);
        ListNode p = returnListNode;
        while (p != null) {
            System.out.print(p.val + " ");
            p = p.next;
        }
        System.out.println();
    }

    /*力扣 23 合并K个有序链表*/
    @Test
    public void test_03() {
        ListNode l1 = new ListNode(1);
        ListNode p1 = l1;
        p1.next = new ListNode(4);
        p1 = p1.next;
        p1.next = new ListNode(5);

        ListNode l2 = new ListNode(1);
        ListNode p2 = l2;
        p2.next = new ListNode(3);
        p2 = p2.next;
        p2.next = new ListNode(4);

        ListNode l3 = new ListNode(2);
        ListNode p3 = l3;
        p3.next = new ListNode(6);


        ListNode[] lists = new ListNode[]{
                l1, l2, l3
        };
        ListNode returnListNode = DoublePointerInLinkedList.mergeKLists(lists);
        ListNode p = returnListNode;
        while (p != null) {
            System.out.print(p.val + " ");
            p = p.next;
        }
        System.out.println();
    }


    /*力扣19 一次遍历 删除倒数第N个元素 */
    @Test
    public void test_04() {
        ListNode l1 = new ListNode(1);
        ListNode p1 = l1;
        p1.next = new ListNode(2);
        p1 = p1.next;
        p1.next = new ListNode(3);
        p1 = p1.next;
        p1.next = new ListNode(4);
        p1 = p1.next;
        p1.next = new ListNode(5);

        ListNode returnListNode = DoublePointerInLinkedList.removeNthFromEnd(l1, 2);
        ListNode p = returnListNode;
        while (p != null) {
            System.out.print(p.val + " ");
            p = p.next;
        }
        System.out.println();
    }

    /*力扣876 找链表的中间节点 奇数个找中间 偶数个找中间两个的后一个 */
    @Test
    public void test_05() {
        ListNode l1 = new ListNode(1);
        ListNode p1 = l1;
        p1.next = new ListNode(2);
        p1 = p1.next;
        p1.next = new ListNode(3);
        p1 = p1.next;
        p1.next = new ListNode(4);
        p1 = p1.next;
        p1.next = new ListNode(5);
        p1 = p1.next;
        p1.next = new ListNode(6);

        ListNode returnListNode = DoublePointerInLinkedList.middleNode(l1);
        ListNode p = returnListNode;
        // 输出从中间节点开始的后半截
        while (p != null) {
            System.out.print(p.val + " ");
            p = p.next;
        }
        System.out.println();
    }

    /*力扣142 环形链表求循环起点 */
    @Test
    public void test_06() {
        ListNode l1 = new ListNode(3);
        ListNode p1 = l1;
        p1.next = new ListNode(2);
        p1 = p1.next;
        ListNode temp = p1;
        // temp 辅助形成循环链表
        p1.next = new ListNode(0);
        p1 = p1.next;
        p1.next = new ListNode(-4);
        p1 = p1.next;
        p1.next = temp;
        // 形成循环链表

        ListNode returnListNode = DoublePointerInLinkedList.detectCycle(l1);
        ListNode p = returnListNode;
        System.out.println("循环起点的值:" + p.val);
    }

    /*力扣160 相交链表求第一个交点 */
    @Test
    public void test_07() {
        ListNode l1 = new ListNode(-3);
        ListNode l2 = new ListNode(30);
        ListNode p1 = l1;
        ListNode p2 = l2;

        ListNode temp = new ListNode(300);
        ListNode p3 = temp;
        p3.next = new ListNode(400);
        p3 = p3.next;
        p3.next = new ListNode(500);
        p3 = p3.next;


        p1.next = new ListNode(-4);
        p1 = p1.next;
        p1.next = new ListNode(-5);
        p1 = p1.next;
        p1.next = temp;

        p2.next = new ListNode(40);
        p2 = p2.next;
        p2.next = new ListNode(50);
        p2 = p2.next;
        p2.next = new ListNode(60);
        p2 = p2.next;
        p2.next = temp;


        ListNode returnListNode = DoublePointerInLinkedList.getIntersectionNode(l1, l2);
        ListNode p = returnListNode;
        System.out.println("第一个交点的值:" + p.val);
    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

28、链表-两数相加

思路&#xff1a; 有几个方面需要考虑 双指针遍历&#xff0c;如果出现和大于10那么向前进1如果长度不一样那么长的部分直接落下并且考虑进1 的问题 代码如下&#xff1a; class Solution {public ListNode addTwoNumbers(ListNode l1, ListNode l2) {if (l1null||l2null){…

网络编程【InetAddress , TCP 、UDP 、HTTP 案例】

day38上 网络编程 InetAddress 理解&#xff1a;表示主机类 一个域名 对应 多个IP地址 public static void main(String[] args) throws UnknownHostException {//获取本机的IP地址 // InetAddress localHost InetAddress.getLocalHost(); // System.out.println(localHos…

前端标记语言HTML

HTML&#xff08;HyperText Markup Language&#xff09;是一种用于创建网页的标准标记语言。它是构建和设计网页及应用的基础&#xff0c;通过定义各种元素和属性&#xff0c;HTML使得开发者能够组织和格式化文本、图像、链接等内容。 HTML的基本结构 文档类型声明&#xff0…

终端工具命令行颜色配置(解决终端工具连上服务器之后,无颜色问题)

本期主题&#xff1a; 讲解使用mobaxterm等终端工具连上服务器&#xff0c;但是命令行没有颜色的问题 目录 1. 问题描述2. 原因解释3.测试 1. 问题描述 使用终端工具&#xff08;Mobaxterm等&#xff09;连上服务器之后&#xff0c;发现终端工具没有颜色&#xff0c;如下图&am…

基于java的社区生活超市管理系统

开发语言&#xff1a;Java 框架&#xff1a;ssm 技术&#xff1a;JSP JDK版本&#xff1a;JDK1.8 服务器&#xff1a;tomcat7 数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09; 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;eclipse/myeclip…

YOLOv9/YOLOv8算法改进【NO.117】 使用Wasserstein Distance Loss改进小目标的检测效果

前 言 YOLO算法改进系列出到这&#xff0c;很多朋友问改进如何选择是最佳的&#xff0c;下面我就根据个人多年的写作发文章以及指导发文章的经验来看&#xff0c;按照优先顺序进行排序讲解YOLO算法改进方法的顺序选择。具体有需求的同学可以私信我沟通&#xff1a; 首推…

基于Python豆瓣电影数据可视化分析系统的设计与实现

大数据可视化项目——基于Python豆瓣电影数据可视化分析系统的设计与实现 2024最新项目 项目介绍 本项目旨在通过对豆瓣电影数据进行综合分析与可视化展示&#xff0c;构建一个基于Python的大数据可视化系统。通过数据爬取收集、清洗、分析豆瓣电影数据&#xff0c;我们提供了…

bugku-web-文件包含2

页面源码 <!-- upload.php --><!doctype html><html><head><meta charset"utf-8"/><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" content"widthdevice-widt…

Springboot+Vue项目-基于Java+MySQL的高校心理教育辅导系统(附源码+演示视频+LW)

大家好&#xff01;我是程序猿老A&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。 &#x1f49e;当前专栏&#xff1a;Java毕业设计 精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; &#x1f380; Python毕业设计 &…

算法中的复杂度(先做个铺垫)

文章目录 定义与分类时间复杂度概念大O的渐进表示法举例情况注意内涵 空间复杂度最优解 定义与分类 复杂度&#xff1a;衡量算法效率的标准时间效率&#xff1a;衡量这个算法的运行速度&#xff0c;也就是我们常说的时间复杂度空间效率&#xff1a;衡量这个算法所需要的额外空…

【DA-CLIP】encode_image图像编码过程和IRSDE对image_context,、degra_context的使用

背景&#xff1a; 编码过程&#xff1a; with torch.no_grad(), torch.cuda.amp.autocast():# 这一行开始一个上下文管理器&#xff0c;用于关闭梯度计算&#xff08;torch.no_grad()&#xff09;&#xff0c;这对于推理阶段是必要的&#xff0c;因为我们不需要计算反向传播。…

NVM的安装与配置

目录 一、简介二、下载2.1、windows环境下载地址2.2、安装 三、配置3.1、查看可安装版本3.2、安装版本3.3、使用和切换版本3.4、模块配置 四、其他4.1、全局安装pnpm4.2、常用nvm命令 一、简介 NVM&#xff0c;全称为Node Version Manager&#xff0c;是一个流行的命令行工具&a…

【OpenHarmony】TDD-FUZZ环境配置

零、参考 1、AttributeError: ‘ElementTree‘ object has no attribute ‘getiterator‘&#xff1a;https://blog.csdn.net/suhao0911/article/details/110950742 一、创建工作目录 1、新建工作目录如&#xff1a;D:\0000_TDD_FUZZ\0000_ohos_tdd_fuzz。 2、gitee上下载 t…

简历上写熟悉Linux下常用命令?直接寄

大家写简历技术栈时&#xff0c;都觉得越多越好&#xff0c;其中一条&#xff0c;熟悉Linux下常用命令&#xff1f;其实开发中Linux不是必备考点&#xff0c;除了运维&#xff0c;真正用的多的仅仅cd ls mkdir等&#xff0c;但当面试官问到上面命令时&#xff0c;是不是就傻眼了…

three.js(1):three.js简介

1 什么是three.js three.js&#xff0c;一个WebGL引擎&#xff0c;基于JavaScript&#xff0c;可直接运行GPU驱动游戏与图形驱动应用于浏览器。其库提供的特性与API以绘制3D场景于浏览器。 2 下载地址 three.js下载地址:https://github.com/mrdoob/three.js 3 目录介绍 下载…

vue3 uniapp微信登录

根据最新的微信小程序官方的规定&#xff0c;uniapp中的uni.getUserInfo方法不再返回用户头像和昵称、以及手机号 首先&#xff0c;需获取appID&#xff0c;appSecret&#xff0c;如下图 先调用uni.getUserInfo方法获取code&#xff0c;然后调用后台的api&#xff0c;传入code&…

微信登录功能-保姆级教学

目录 一、使用组件 二、登录功能 2.1 步骤 2.2 首先找到网页权限 复制demo 代码 这里我们需要修改两个参数 三、前端代码 3.1 api 里weiXinApi.ts 3.2 api里的 index.ts 3.3 pinia.ts 3.4 My.vue 四、后端代码 4.1 WeiXinController 4.2 Access_Token.Java 4.3 We…

vue列表列表过滤

对已知的列表进行数据过滤(根据输入框里面的内容进行数据过滤) 编写案例 通过案例来演示说明 效果就是这样的 输入框是模糊查询 想要实现功能&#xff0c;其实就两大步&#xff0c;1获取输入框内容 2根据输入内容进行数据过滤 绑定收集数据 我们可以使用v-model去双向绑定 …

车机系统与 Android 的关系概述

前言&#xff1a;搞懂 Android 系统和汽车到底有什么关系。 文章目录 一、基本概念1、Android Auto1&#xff09;是什么2&#xff09;功能 2、Google Assistant3、Android Automotive1、Android Auto 和 Android Automotive 的区别 4、App1&#xff09;App 的开发2&#xff09;…

【VS2019】x64 Native Tools Command Prompt for Vs 2019使用conda命令进入环境

【VS2019】x64 Native Tools Command Prompt for Vs 2019使用conda命令进入环境 安装完VS2019后&#xff0c;打开终端x64 Native Tools Command Prompt for Vs 2019&#xff0c;直接运行conda会出现‘conda’ 不是内部或外部命令&#xff0c;也不是可运行的程序 原因分析&am…