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

news2024/11/16 20:41:13

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

  • 力扣 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);
    }
}

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

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

相关文章

vue简单使用三(class样式绑定)

目录 对象的形式绑定&#xff1a; 数组的形式绑定&#xff1a; 内联样式Style 对象的形式绑定&#xff1a; 可以看到class中有两个值 数组的形式绑定&#xff1a; 可以看到也有两个值 内联样式Style style样式设置成功 完整代码&#xff1a; <!DOCTYPE html> <html…

快速列表quicklist

目录 为什么使用快速列表quicklist 对比双向链表 对比压缩列表ziplist quicklist结构 节点结构quicklistNode quicklist 管理ziplist信息的结构quicklistEntry 迭代器结构quicklistIter quicklist的API 1.创建快速列表 2.创建快速列表节点 3.头插quicklistPushHead …

qemu源码解析一

基于qemu9.0.0 简介 QEMU是一个开源的虚拟化软件&#xff0c;它能够模拟各种硬件设备&#xff0c;支持多种虚拟化技术&#xff0c;如TCG、Xen、KVM等 TCG 是 QEMU 中的一个组件&#xff0c;它可以将高级语言编写的代码&#xff08;例如 C 代码&#xff09;转换为可在虚拟机中…

关于部署ELK和EFLK的相关知识

文章目录 一、ELK日志分析系统1、ELK简介1.2 ElasticSearch1.3 Logstash1.4 Kibana&#xff08;展示数据可视化界面&#xff09;1.5 Filebeat 2、使用ELK的原因3、完整日志系统的基本特征4、ELK的工作原理 二、部署ELK日志分析系统1、服务器配置2、关闭防火墙3、ELK ElasticSea…

Niobe开发板OpenHarmony内核编程开发——定时器

本示例将演示如何在Niobe Wifi IoT开发板上使用cmsis 2.0 接口进行定时器开发 Timer API分析 osTimerNew() /// Create and Initialize a timer./// \param[in] func function pointer to callback function./// \param[in] type \ref osTimerOnce …

【菜狗学前端】原生Ajax笔记(包含原生ajax的get/post传参方式、返回数据等)

这回图片少&#xff0c;给手动替换了~祝看得愉快&#xff0c;学的顺畅&#xff01;哈哈 一 原生ajax经典四步 (一) 原生ajax经典四步 第一步&#xff1a;创建网络请求的AJAX对象&#xff08;使用XMLHttpRequest&#xff09; JavaScript let xhr new XMLHttpRequest() 第二…

网络编程【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…

【GPT-4最新研究】GPT-4与科学探索:揭秘语言模型在科学领域的无限可能

各位朋友们&#xff0c;你们知道吗&#xff1f;自然语言处理领域最近取得了巨大的突破&#xff01;大型语言模型&#xff08;LLM&#xff09;的出现&#xff0c;简直就像打开了新世界的大门。它们不仅在语言理解、生成和翻译方面表现出色&#xff0c;还能涉足许多其他领域&…

如何在Windows安装LocalSend并结合内网穿透实现公网跨平台远程文件互传

文章目录 1. 在Windows上安装LocalSend2. 安装Cpolar内网穿透3. 公网访问LocalSend4. 固定LocalSend公网地址 本篇文章介绍在Windows中部署开源免费文件传输工具——LocalSend&#xff0c;并且结合cpolar内网穿透实现公网远程下载传输文件。 localsend是一款基于局域网的文件传…

计算机视觉实验五——图像分割

计算机视觉实验五——图像分割 一、实验目标二、实验内容1.了解图割操作&#xff0c;实现用户交互式分割&#xff0c;通过在一幅图像上为前景和背景提供一些标记或利用边界框选择一个包含前景的区域&#xff0c;实现分割①图片准备②代码③运行结果④代码说明 2.采用聚类法实现…

【Orange pi 系列】Notebook1 初探开发板

记录学习香橙派开发板的心路历程 Notebook1 初探开发板 Orange pi 5 plus 开发板Orange pi AI pro 开发板烧录系统 Orange pi 5 plus 开发板 Orange pi AI pro 开发板 烧录系统 分别给5plus和AI pro安装了Orange OS 和OpenEuler OS

【C语言】每日一题,快速提升(1)!

调整数组使奇数全部都位于偶数前面 题目&#xff1a; 输入一个整数数组&#xff0c;实现一个函数 来调整该数组中数字的顺序使得数组中所有的奇数位于数组的前半部分 所有偶数位于数组的后半部分 解题思路&#xff1a; 给定两个下标left和right&#xff0c;left放在数组的起始…

计算机视觉——手机目标检测数据集

这是一个手机目标检测的数据集&#xff0c;数据集的标注工具是labelimg,数据格式是voc格式&#xff0c;要训练yolo模型的话&#xff0c;可以使用脚本改成txt格式&#xff0c;数据集标注了手机&#xff0c;标签名&#xff1a;telephone,数据集总共有1960张&#xff0c;有一部分是…

使用spring-ai快速对接ChatGpt

什么是spring-ai Spring AI 是一个与 Spring 生态系统紧密集成的项目&#xff0c;旨在简化在基于 Spring 的应用程序中使用人工智能&#xff08;AI&#xff09;技术的过程。 简化集成&#xff1a;Spring AI 为开发者提供了方便的工具和接口&#xff0c;使得在 Spring 应用中集…

ChatGPT 和 Elasticsearch:使用 Elastic 数据创建自定义 GPT

作者&#xff1a;Sandra Gonzales ChatGPT Plus 订阅者现在有机会创建他们自己的定制版 ChatGPT&#xff0c;称为 GPT&#xff0c;这替代了之前博客文章中讨论的插件。基于本系列的第一部分的基础 —— 我们深入探讨了在 Elastic Cloud 中设置 Elasticsearch 数据和创建向量嵌…

个人网站制作 Part 19 添加在线聊天支持 | Web开发项目

文章目录 &#x1f469;‍&#x1f4bb; 基础Web开发练手项目系列&#xff1a;个人网站制作&#x1f680; 添加在线聊天支持&#x1f528;使用在线聊天工具&#x1f527;步骤 1: 选择在线聊天工具&#x1f527;步骤 2: 注册Tawk.to账户&#x1f527;步骤 3: 获取嵌入代码 &…

申请OV SSL证书的好处

什么是OV SSL证书&#xff1a; OV SSL证书也叫组织验证型SSL证书&#xff0c;是众多SSL证书当中最受广大用户欢迎的一种类型。因为它不仅需要验证域名的所有权&#xff0c;还需要对企业的相关身份信息进行审核&#xff0c;确保企业是一个真实存在的合法实体。除了这些&#xf…

Web前端 Javascript笔记3

1、垃圾回收机制 内存中的生命周期 1、内存分配 2、内存使用&#xff08;读写&#xff09; 3、内存回收&#xff0c;使用完毕之后&#xff0c;垃圾回收器完成 内存泄漏&#xff1a;该回收的&#xff0c;由于某些未知因素&#xff0c;未释放&#xff0c;叫做内存泄漏 栈&#xf…

【系统分析师】需求工程☆

文章目录 0、需求工程概述1、需求的分类2、需求获取3、需求分析3.1 结构化需求分析-SA3.1.1DFD- 数据流图3.1.2 STD-状态转换图3.1.3 ER图-实体联系图 3.2 面向对象需求分析-OOA3.2.1 工具-UML图3.2.2 UML分类3.2.3 用例图 ☆3.2.4 类图 / 对象图 ☆3.2.5 顺序图3.2.6 活动图3.…

prompt 工程整理(未完、持续更新)

工作期间会将阅读的论文、一些个人的理解整理到个人的文档中&#xff0c;久而久之就积累了不少“个人”能够看懂的脉络和提纲&#xff0c;于是近几日准备将这部分略显杂乱的内容重新进行梳理。论文部分以我个人的理解对其做了一些分类&#xff0c;并附上一些简短的理解&#xf…