算法: 链表题目练习

news2024/11/5 20:05:19

文章目录

  • 链表题目练习
    • 两数相加
    • 两两交换链表中的节点
    • 重排链表
    • 合并 K 个升序链表
    • K 个一组翻转链表
  • 总结


链表题目练习

两数相加

在这里插入图片描述
坑:

  • 两个链表都遍历完后,可能需要进位.
    class Solution {
        public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
            ListNode cur1 = l1;
            ListNode cur2 = l2;
            ListNode head = new ListNode(0);
            ListNode tail = head;
            int sum = 0;
            while (cur1 != null || cur2 != null) {
                ListNode newNode = new ListNode();
                tail.next = newNode;
                tail = newNode;
                if (cur1 != null) {
                    sum += cur1.val;
                    cur1 = cur1.next;
                }
                if (cur2 != null) {
                    sum += cur2.val;
                    cur2 = cur2.next;
                }
                if (sum >= 10) {
                    newNode.val = sum % 10;
                    sum = 1;
                } else {
                    newNode.val = sum;
                    sum = 0;
                }
            }
            // 遍历完两个链表 处理一下进位
            if (sum != 0) {
                ListNode newNode = new ListNode(sum);
                tail.next = newNode;
            }

            return head.next;
        }
    }

题解代码:

    class Solution {
        public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
            ListNode cur1 = l1, cur2 = l2;
            ListNode head = new ListNode();
            ListNode tail = head;
            int sum = 0;
            while (cur1 != null || cur2 != null || sum != 0) {
                if (cur1 != null) {
                    sum += cur1.val;
                    cur1 = cur1.next;
                }
                if (cur2 != null) {
                    sum += cur2.val;
                    cur2 = cur2.next;
                }
                ListNode newNode = new ListNode(sum % 10);
                tail.next = newNode;
                tail = newNode;
                sum /= 10;
            }
            return head.next;
        }
    }

两两交换链表中的节点

在这里插入图片描述
简简单单,一遍过~

草图 :
在这里插入图片描述

    class Solution {
        public ListNode swapPairs(ListNode head) {
            ListNode virtualHead = new ListNode();
            virtualHead.next = head;
            ListNode cur = head, prev = virtualHead;
            while (cur != null && cur.next != null) {
                ListNode next = cur.next;
                prev.next = next;
                cur.next = next.next;
                next.next = cur;
                prev = cur;
                cur = cur.next;
            }
            return virtualHead.next;
        }
    }

重排链表

在这里插入图片描述
没想出来,看题解思路懂哩~

分成三步走:

  1. 找到原链表的中间节点(可以使用快慢指针解决)。
  2. 将原链表的右半部分翻转。
  3. 将原链表的两端合并。
    • 因为两链表的长度相差不超过 1,所以可以直接合并~

磕磕绊绊总算是写出来了~
看似是一道题,其实是三道题~

在合并两个链表时卡了一下.

坑:

  • 前面有指针指向 中间节点 ,链表翻转后指针的指向大概是这样的
    在这里插入图片描述
class Solution {
    public void reorderList(ListNode head) {
        // 寻找中间节点
        ListNode fast = head, slow = head;
        while (fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;
        }
        // 此时 slow 为中间节点
        // 翻转 slow 以及 slow 后面的节点
        ListNode behind = new ListNode();
        ListNode cur = slow;
        while (cur != null) {
            ListNode next = cur.next;
            cur.next = behind.next;
            behind.next = cur;
            cur = next;
        }
        // 合并两个链表
        ListNode cur1 = head, cur2 = behind.next;
        while (cur2.next != null && cur1.next != null) {
            ListNode next1 = cur1.next, next2 = cur2.next;
            cur2.next = next1;
            cur1.next = cur2;
            cur1 = next1;
            cur2 = next2;
        }
    }
}

看了题解之后,发现可以把整个链表拆成两份.
而且,发现从中间节点的后一个开始翻转链表也可以过,这样就可以在中间节点这个位置把链表分成两份:

  • 一份是 中间节点之前(包含中间节点)
  • 另一份是 中间节点之后

题解代码:

    class Solution {
        public void reorderList(ListNode head) {
            // 1.寻找中间节点
            ListNode slow = head, fast = slow;
            while (fast != null && fast.next != null) {
                slow = slow.next;
                fast = fast.next.next;
            }

            ListNode head2 = new ListNode(-1);
            // 2.逆序 head2 链表
            ListNode cur = slow.next;
            // 拆分成两个链表
            slow.next = null;
            while (cur != null) {
                ListNode next = cur.next;
                cur.next = head2.next;
                head2.next = cur;
                cur = next;
            }
            // 3. 合并两个链表
            ListNode head3 = new ListNode(-1);
            ListNode cur2 = head;
            ListNode cur3 = head2.next;
            ListNode prev = head3;
            while (cur2 != null) {
                prev.next = cur2;
                prev = cur2;
                cur2 = cur2.next;
                if (cur3 != null) {
                    prev.next = cur3;
                    prev = cur3;
                    cur3 = cur3.next;
                }
            }
        }
    }

合并 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 {
    private ListNode mergeLists(ListNode l1, ListNode l2) {
        if (l1 == null)
            return l2;
        if (l2 == null)
            return l1;
        ListNode head = new ListNode(-1);
        ListNode cur1 = l1, cur2 = l2, tail = head;
        while (cur1 != null && cur2 != null) {
            if (cur1.val <= cur2.val) {
                tail.next = cur1;
                tail = cur1;
                cur1 = cur1.next;
            } else {
                tail.next = cur2;
                tail = cur2;
                cur2 = cur2.next;
            }

        }
        while (cur1 != null) {
            tail.next = cur1;
            tail = cur1;
            cur1 = cur1.next;
        }
        while (cur2 != null) {
            tail.next = cur2;
            tail = cur2;
            cur2 = cur2.next;
        }
        return head.next;
    }

    public ListNode mergeKLists(ListNode[] lists) {
        int n = lists.length;
        if (n <= 0)
            return null;
        ListNode head = lists[0];
        for (int i = 1; i < n; i++) {
            head = mergeLists(head, lists[i]);
        }
        return head;
    }
}

方法二: 使用优先级队列优化.

  • 给每个链表都指定一个指针(用来遍历链表),把每一个指针指向的节点放到优先级队列里.不断取出值最小的那个节点,尾插到结果链表中.

忘了怎么在java中自定义排序优先级队列了。

/**
 * 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) {
        int n = lists.length;
        if (n == 0)
            return null;
        // 指针数组
        ListNode[] arr = new ListNode[n];
        // 默认是小根堆
        PriorityQueue<ListNode> heap = new PriorityQueue<>(
                new Comparator<ListNode>() {
                    @Override
                    public int compare(ListNode o1, ListNode o2) {
                        return o1.val - o2.val;
                    }
                });
        // 结果
        ListNode ret = new ListNode(-1);
        ListNode tail = ret;
        // 把指针对应起来
        for (int i = 0; i < n; i++) {
            arr[i] = lists[i];
        }

        for (int i = 0; i < n; i++) {
            if (arr[i] != null) {
                heap.add(arr[i]);
            }
        }
        while (!heap.isEmpty()) {
            // 最小的出堆
            ListNode min = heap.poll();
            // 拼到结果后面
            tail.next = min;
            tail = tail.next;
            if (min.next != null) {
                // 不为空,入堆
                heap.add(min.next);
            }
        }
        return ret.next;
    }
}

看了题解代码后,发现自己写的代码浪费了很多空间,我为什么要 new 一个指针数组???

题解代码:

        /**
         * 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) {
                int n = lists.length;
                if (n == 0)
                    return null;
                // 1. 创建一个小根堆
                PriorityQueue<ListNode> heap = new PriorityQueue<>((v1, v2) -> v1.val - v2.val);
                // 2. 把所有的头结点放进小根堆中
                for (ListNode head : lists) {
                    if (head != null)
                        heap.offer(head);
                }
                // 3.合并链表
                ListNode ret = new ListNode(-1);
                ListNode tail = ret;

                while (!heap.isEmpty()) {
                    // 最小的出堆
                    ListNode min = heap.poll();
                    // 拼到结果后面
                    tail.next = min;
                    tail = tail.next;
                    if (min.next != null) {
                        // 不为空,入堆
                        heap.add(min.next);
                    }
                }
                return ret.next;
            }
        }

方法三:使用 分治 - 递归 解决

好难想到。

代码:

        /**
         * 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 mergeLists(ListNode l1, ListNode l2) {
                if (l1 == null)
                    return l2;
                if (l2 == null)
                    return l1;
                ListNode head = new ListNode(-1);
                ListNode tail = head;
                while (l1 != null && l2 != null) {
                    if (l1.val <= l2.val) {
                        tail.next = l1;
                        l1 = l1.next;
                    } else {
                        tail.next = l2;
                        l2 = l2.next;
                    }
                    tail = tail.next;
                }
                while (l1 != null) {
                    tail.next = l1;
                    l1 = l1.next;
                    tail = tail.next;
                }

                while (l2 != null) {
                    tail.next = l2;
                    l2 = l2.next;
                    tail = tail.next;
                }

                return head.next;
            }

            // 递归
            public ListNode merge(ListNode[] lists, int start, int end) {
                if (start >= end)
                    return lists[start];

                int mid = start + (end - start) / 2;
                ListNode l1 = merge(lists, start, mid);
                ListNode l2 = merge(lists, mid + 1, end);

                return mergeLists(l1, l2);
            }

            public ListNode mergeKLists(ListNode[] lists) {
                int n = lists.length;
                if (n == 0)
                    return null;

                return merge(lists, 0, n - 1);
            }
        }

自己的代码中的合并两个有序链表的代码写的不是很好,最后的 while 可以换成 if 来写的,这是链表,不是数组,不用循环那么多次。。

题解代码:

        /**
         * 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 mergeLists(ListNode l1, ListNode l2) {
                if (l1 == null)
                    return l2;
                if (l2 == null)
                    return l1;
                ListNode head = new ListNode(-1);
                ListNode tail = head;
                while (l1 != null && l2 != null) {
                    if (l1.val <= l2.val) {
                        tail.next = l1;
                        l1 = l1.next;
                    } else {
                        tail.next = l2;
                        l2 = l2.next;
                    }
                    tail = tail.next;
                }
                if (l1 != null)
                    tail.next = l1;

                if (l2 != null)
                    tail.next = l2;

                return head.next;
            }

            // 递归
            public ListNode merge(ListNode[] lists, int start, int end) {
                if (start >= end)
                    return lists[start];
                // 1. 平分数组
                int mid = start + (end - start) / 2;

                // 2. 递归处理左右两个部分
                ListNode l1 = merge(lists, start, mid);
                ListNode l2 = merge(lists, mid + 1, end);

                // 3. 合并两个有序链表
                return mergeLists(l1, l2);
            }

            public ListNode mergeKLists(ListNode[] lists) {
                int n = lists.length;
                if (n == 0)
                    return null;

                return merge(lists, 0, n - 1);
            }
        }

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 reverse(ListNode head, int k) {
        ListNode phead = new ListNode(-1);
        ListNode cur = head, next = cur.next;
        while (k-- > 0) {
            cur.next = phead.next;
            phead.next = cur;
            cur = next;
            if (next != null)
                next = next.next;
            else
                break;
        }
        return phead.next;
    }

    public ListNode reverseKGroup(ListNode head, int k) {
        ListNode phead = new ListNode(-1);
        phead.next = head;
        ListNode slow = phead, fast = head;

        while (fast != null) {
            int tmp = k;
            while (tmp > 0) {
                if (fast == null) {
                    break;
                }
                fast = fast.next;
                tmp--;
            }

            if (tmp > 0)
                break;
            slow.next = reverse(slow.next, k);
            tmp = k;
            // 写成这样你要清楚:
            // 等出循环的时候 tmp = -1
            // 因为在最后一次的判断时 tmp 也要 --
            while (tmp-- > 0) {
                slow = slow.next;
            }
            slow.next = fast;
        }
        return phead.next;
    }
}

题解代码:

/**
 * 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. 先求出要逆序多少组
        int n = 0;
        ListNode cur = head;
        while (cur != null) {
            cur = cur.next;
            n++;
        }
        n /= k;

        // 2. 重复 n 次,长度为 k 的链表的逆序
        ListNode newHead = new ListNode(-1);
        ListNode prev = newHead;
        cur = head;
        for (int i = 0; i < n; i++) {
            // 标记当前逆序后的最后一个节点
            ListNode tmp = cur;
            for (int j = 0; j < k; j++) {
                ListNode next = cur.next;
                cur.next = prev.next;
                prev.next = cur;
                cur = next;
            }
            prev = tmp;
        }
        // 处理剩下的节点不够 k 个的情况
        prev.next = cur;

        return newHead.next;
    }
}

总结

链表常用技巧 :

  1. 画图是个好东西(感觉好像已经说过好几遍了).
  2. 可以引入一个头结点
    • 便于处理边界情况
    • 方便我们对链表操作
  3. 在插入新节点时,可以先把新节点的指针指向都调整好.然后再去调整前一个节点和后一个节点.
    或者直接新建一个指针,指向后一个节点,这样更容易操作~
  4. 有时候会用到快慢双指针

本文到这里就结束啦~

在这里插入图片描述

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

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

相关文章

HTML 基础标签——元数据标签 <meta>

文章目录 1. `<meta>` 标签概述2. 属性详解2.1 `charset` 属性2.2 `name` 属性2.3 `content` 属性2.4 `http-equiv` 属性3. 其他常见属性小结在 HTML 文档中,元数据标签 <meta> 是一种重要的标签,用于提供关于文档的信息,这些信息不直接显示在网页内容中,但对于…

新闻稿件管理系统:SpringBoot框架深度解析

2相关技术 2.1 MYSQL数据库 MySQL是一个真正的多用户、多线程SQL数据库服务器。 是基于SQL的客户/服务器模式的关系数据库管理系统&#xff0c;它的有点有有功能强大、使用简单、管理方便、安全可靠性高、运行速度快、多线程、跨平台性、完全网络化、稳定性等&#xff0c;非常…

【网络】自定义协议——序列化和反序列化

> 作者&#xff1a;დ旧言~ > 座右铭&#xff1a;松树千年终是朽&#xff0c;槿花一日自为荣。 > 目标&#xff1a;了解什么是序列化和分序列&#xff0c;并且自己能手撕网络版的计算器。 > 毒鸡汤&#xff1a;有些事情&#xff0c;总是不明白&#xff0c;所以我不…

CTFshow之信息收集第1关到10关。详细讲解

得而不惜就该死&#xff01; --章总 开始新的篇章&#xff01; 零、目录 一、实验准备 1、ctf网站&#xff1a;ctf.show 2、工具&#xff1a;chrome浏览器、hackbar插件 3、burpsuite抓包工具 二、实验技巧 &#xff08;一&#xff09;F12摸奖 源码泄露 &#xff08;二…

Redis ——发布订阅

问题引入&#xff1a; 服务器A、B、C、D&#xff0c;客户端&#xff1a;C1&#xff0c;C2&#xff0c;C3&#xff0c;C4&#xff1b; 客户端基于集群聊天服务器的负载均衡分配&#xff1b; C1 被分配到A&#xff0c;C2 被分配到B&#xff0c;C3 被分配到C&#xff0c;C4 被分…

【漏洞复现】某平台-QRcodeBuildAction-LoginSSO-delay-mssql-sql注入漏洞

《Java代码审计》http://mp.weixin.qq.com/s?__bizMzkwNjY1Mzc0Nw&mid2247484219&idx1&sn73564e316a4c9794019f15dd6b3ba9f6&chksmc0e47a67f793f371e9f6a4fbc06e7929cb1480b7320fae34c32563307df3a28aca49d1a4addd&scene21#wechat_redirect 《Web安全》h…

API网关 - JWT认证 ; 原理概述与具体实践样例

API网关主要提供的能力&#xff0c;就是协议转换&#xff0c;安全&#xff0c;限流等能力。 本文主要是分享 如何基于API网关实现 JWT 认证 。 包含了JWT认证的流程&#xff0c;原理&#xff0c;与具体的配置样例 API网关认证的重要性 在现代Web应用和微服务架构中&#x…

Excel:vba实现批量插入图片

实现的效果&#xff1a; 实现的代码&#xff1a; Sub InsertImageNamesAndPictures()Dim PicPath As StringDim PicName As StringDim PicFullPath As StringDim RowNum As IntegerDim Pic As ObjectDim Name As String 防止表格里面有脏数据Cells.Clear 遍历工作表中的每个图…

什么是 OpenTelemetry?

OpenTelemetry 定义 OpenTelemetry (OTel) 是一个开源可观测性框架&#xff0c;允许开发团队以单一、统一的格式生成、处理和传输遥测数据&#xff08;telemetry data&#xff09;。它由云原生计算基金会 (CNCF) 开发&#xff0c;旨在提供标准化协议和工具&#xff0c;用于收集…

电商美工必备神器:千鹿 AI 轻松解决场景图主图尺寸问题

前言 在电商领域&#xff0c;美工在做详情页设计时&#xff0c;常常会为图片尺寸问题而苦恼。而 AI 扩图在此刻就成为了美工们的得力助手。其中&#xff0c;场景图主图太小是一个让人颇为头疼的难题。千鹿 AI 作为一款强大的工具&#xff0c;能够一键将图片改成指定尺寸&#…

关于 PDF 抽取的吐槽

今天一下午写了8&#xff0c;9个 PDF 抽取的脚本。最后又回归最开始简单的模式了&#xff0c;要疯了&#xff0c;谁懂啊。 我是下午的工作是这样的(我是这么疯的) 最开始使用最简单的策略&#xff0c;先使用 PyPDF2.PdfReader(file) 读取文件&#xff0c;然后在每一页使用 pag…

无人机避障——(局部规划方法)DWA(动态窗口法)

传统的DWA算法更加倾向于车辆等差速无人车&#xff0c;旋翼无人机是全速的&#xff0c;全向的。 全局路径是通过A*算法生成的 局部路径规划效果&#xff1a; DWA算法效果&#xff1a; 过程图&#xff1a; 完整过程&#xff1a; PID算法效果&#xff1a; 过程图&#xff1a…

知识吾爱纯净版小程序系统 leibiao SQL注入漏洞复现(XVE-2024-30663)

0x01 产品简介 知识吾爱纯净版小程序系统是一款基于微信小程序平台开发的知识付费应用,旨在帮助用户快速建立自己的知识付费平台,实现支付变现和流量主收益。它提供了简洁明了的用户界面和良好的用户体验,同时注重用户隐私保护,确保用户信息的安全存储和传输。 0x02 漏洞…

基于web的便捷饭店点餐小程序的设计与实现(lw+演示+源码+运行)

摘 要 互联网发展至今&#xff0c;无论是其理论还是技术都已经成熟&#xff0c;而且它广泛参与在社会中的方方面面。它让信息都可以通过网络传播&#xff0c;搭配信息管理工具可以很好地为人们提供服务。针对高校教师成果信息管理混乱&#xff0c;出错率高&#xff0c;信息安全…

全面解析:深度学习技术及其应用

&#x1f493; 博客主页&#xff1a;瑕疵的CSDN主页 &#x1f4dd; Gitee主页&#xff1a;瑕疵的gitee主页 ⏩ 文章专栏&#xff1a;《热点资讯》 全面解析&#xff1a;深度学习技术及其应用 全面解析&#xff1a;深度学习技术及其应用 全面解析&#xff1a;深度学习技术及其应…

项目实战使用gitee

1.创建本地仓库 2.进行提交到本地仓库 创建仓库后在idea中会显示图标&#xff0c;点击绿色的√进行快速提交 3.绑定远程仓库 4.番外篇-创建gitee仓库 注意不要勾选其他

【鸿蒙新闻】10月29日警用鸿蒙开发者大会在北京胜利召开,开启智慧应用新时代!

10月29日&#xff0c;在公安部科技信息化局、公安部装备财务局指导下&#xff0c;由公安部第一研究所主办&#xff0c;鼎桥通信技术有限公司、OpenHarmony生态委员会及公共安全专委会协办的警用鸿蒙开发者大会在北京胜利召开。会议以“拥抱警鸿创新生态 开启智慧应用新时代”为…

Spring Boot 3.3 【九】Redis 的五种数据结构深入浅出(String List Set Hash Zset)

如果觉得本文能够帮到您&#xff0c;请关注&#x1f31f;、点赞&#x1f44d;、收藏&#x1f4da;&#xff0c;让这份美好延续下去&#xff01; 一、Redis 数据结构简介 在现代应用开发中&#xff0c;高效的数据存储和管理是构建强大系统的关键。Redis 作为一种高性能的内存数…

命令行参数、环境变量、地址空间

命令行参数&#xff1a; int main(int argc, char *argv[ ])&#xff0c;main的参数可带可不带。argc参数通常代表后面的char *argv的元素个数有多少。 在linux中会把输入的字符串存到char *argv[ ]中&#xff0c;在数组的结尾为NULL。 命令行参数可以让同一个程序可以通过不同…

持续优化,构建更好地 auto git commit 体验

几个月前&#xff0c;受到一篇推文的启发 https://x.com/mtrainier2020/status/1802941902964277379 &#xff0c;我突然想到可以借助 git alias 添加一些小命令&#xff0c;加速我的 git workflow 流程&#xff0c;于是我花了两个小时的时间进行工程封装&#xff0c;并发布了 …