【数据结构与算法】之链表经典算法大集合

news/2024/10/27 21:59:10/

本文主要内容是几个关于链表的初级经典算法的分享,都采用Java语言实现,话不多说,立马开始!

注意:以下代码有关链表算法实现均基于以下链表节点类:

java">//链表节点类
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; }
}

相关教程:

一、合并两个有序链表

问题描述:

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]输入:l1 = [], l2 = []
输出:[]输入:l1 = [], l2 = [0]
输出:[0]

图示:

方法一:双指针二路归并

思路分析:

  • 谁小,把谁链给 p,p 和小的都向后平移一位

  • 当 p1、p2 有一个为 null,退出循环,把不为 null 的链给 p

java">p1
|
1	3	8	9	nullp2
|
2	4	null--------------------------p
|		
s	null

代码实现:

java">public ListNode mergeTwoLists(ListNode p1, ListNode p2) {ListNode s = new ListNode(-1, null);ListNode p = s;while (p1 != null && p2 != null) {if (p1.val < p2.val) {p.next = p1;p1 = p1.next;} else {p.next = p2;p2 = p2.next;}p = p.next;}if (p1 != null) {p.next = p1;}if (p2 != null) {p.next = p2;}return s.next;
}

方法二:递归

思路分析:

递归函数应该返回

  • 更小的那个链表节点,并把它剩余节点与另一个链表再次递归

  • 返回之前,更新此节点的 next

java">//伪代码分析
mergeTwoLists(p1=[1,3,8,9], p2=[2,4]) {1.next=mergeTwoLists(p1=[3,8,9], p2=[2,4]) {2.next=mergeTwoLists(p1=[3,8,9], p2=[4]) {            3.next=mergeTwoLists(p1=[8,9], p2=[4]) {4.next=mergeTwoLists(p1=[8,9], p2=null) {return [8,9]}return 4}return 3}return 2}return 1
}

代码实现:

java">public ListNode mergeTwoLists(ListNode list1, ListNode list2) {if(list1 == null){return list2;}if(list2 == null){return list1;}//递归调用if(list1.val < list2.val){list1.next = mergeTwoLists(list1.next,list2);return list1;}else {list2.next = mergeTwoLists(list1,list2.next);return list2;}}

二、合并多个有序链表

问题描述:

给你一个链表数组,每个链表都已经按升序排列。将所有链表合并到一个升序链表中,返回合并后的链表

java">示例 1:输入:lists = [[1,4,5],[1,3,4],[2,6]]
输出:[1,1,2,3,4,4,5,6]
解释:链表数组如下:
[1->4->5,1->3->4,2->6
]
将它们合并到一个有序链表中得到。
1->1->2->3->4->4->5->6示例 2:输入:lists = []
输出:[]示例 3:输入:lists = [[]]
输出:[]

方法一:递归

思路分析:

这里与合并两个有序链表的思路是一致的,只不过这里采用了分而治之的思想,分:将多个链表不断切分,递归切分为两个有序链表;治:将切分后的两个链表再调用合并两个有序链表的方法,从而实现最终多个有序链表的合并。

代码实现:

java">    public ListNode mergeKLists(ListNode[] lists) {if(lists.length == 0){return null;}return split(lists,0,lists.length - 1);}// 递归切分链表数组,分而治之// i表示链表数组起始索引,j表示链表数组最后索引public ListNode split(ListNode[] lists, int i, int j) {if (i == j) {return lists[i];}int m = (i+j) >>> 1;return mergeTwoLists(split(lists,i,m),split(lists,m+1,j));}// 合并两个有序链表public ListNode mergeTwoLists(ListNode p1, ListNode p2) {ListNode s = new ListNode(-1, null);ListNode p = s;while (p1 != null && p2 != null) {if (p1.val < p2.val) {p.next = p1;p1 = p1.next;} else {p.next = p2;p2 = p2.next;}p = p.next;}if (p1 != null) {p.next = p1;}if (p2 != null) {p.next = p2;}return s.next;}

方法二:优先队列

思路分析:

  • 创建一个最小堆(优先队列),用于存储所有链表的头节点。
  • 每次从堆中取出最小的节点,将其添加到结果链表中,并将该节点的下一个节点添加回堆中。
  • 重复步骤2,直到堆为空。

代码实现:

java">import java.util.PriorityQueue;class ListNode {int val;ListNode next;ListNode(int x) { val = x; }
}public class Solution {public ListNode mergeKLists(ListNode[] lists) {PriorityQueue<ListNode> heap = new PriorityQueue<>(lists.length, (a, b) -> a.val - b.val);for (ListNode node : lists) {if (node != null) {heap.offer(node);}}ListNode dummy = new ListNode(0);ListNode tail = dummy;while (!heap.isEmpty()) {ListNode smallest = heap.poll();tail.next = smallest;tail = smallest;if (smallest.next != null) {heap.offer(smallest.next);}}return dummy.next;}
}

三、查找链表中间节点

问题描述:

给你单链表的头结点 head ,请你找出并返回链表的中间结点。

如果有两个中间结点,则返回第二个中间结点。

示例 1:

java">奇数节点时,是中间节点
输入:head = [1,2,3,4,5]
输出:[3,4,5]
解释:链表只有一个中间结点,值为 3 

示例 2:

java">偶数节点时,中间节点是靠右的那个
输入:head = [1,2,3,4,5,6]
输出:[4,5,6]
解释:该链表有两个中间结点,值分别为 3 和 4 ,返回第二个结点

方法一:快慢指针法

思路分析:

快慢指针,快指针一次走两步,慢指针一次走一步,当快指针到链表结尾时,慢指针恰好走到链表的一半

java">  p1p2|1	3	5   6   8   nullp1  p2|   |1	3	5   6   8   nullp1      p2|       |1	3	5   6   8   null此时慢指针 p1 指向中间节点

代码实现:

java">public ListNode middleNode(ListNode head) {ListNode p1 = head;	// 慢指针,中间点ListNode p2 = head;	// 快指针while (p2 != null && p2.next != null) {p1 = p1.next;p2 = p2.next.next;}return p1;
}

方法二:递归

思路分析:

  • 如果链表为空或只有一个节点,直接返回头节点。
  • 递归地找到链表后半部分的中间节点。
  • 如果后半部分有奇数个节点,返回后半部分的中间节点。
  • 如果后半部分有偶数个节点,返回前半部分的最后一个节点。

代码实现:

java">    public ListNode findMiddle(ListNode head) {return findMiddleRecursive(head, null);}private ListNode findMiddleRecursive(ListNode curr, ListNode prev) {if (curr == null) return prev;ListNode next = curr.next;curr.next = prev;ListNode result = findMiddleRecursive(next, curr);// 恢复链表结构curr.next = next;return result;}

四、回文链表判断

问题描述:

给你一个单链表的头节点 head ,请你判断该链表是否为回文链表。如果是,返回 true ;否则,返回 false 。

示例 1:

输入:head = [1,2,2,1]
输出:true

示例 2:

输入:head = [1,2]
输出:false

方法一:快慢指针和反转链表

思路分析:

整体思路是先找到链表中间节点,然后反转后半部分链表,最后比较前半部分和反转后的后半部分是否相同,从而判断链表是否为回文链表

代码实现:

java">/*步骤1. 找中间点步骤2. 中间点后半个链表反转步骤3. 反转后链表与原链表逐一比较
*/
public boolean isPalindrome(ListNode head) {ListNode middle = middle(head);ListNode newHead = reverse(middle);while (newHead != null) {if (newHead.val != head.val) {return false;}newHead = newHead.next;head = head.next;}return true;
}
// 反转链表
private ListNode reverse(ListNode o1) {ListNode n1 = null;while (o1 != null) {ListNode o2 = o1.next;o1.next = n1;n1 = o1;o1 = o2;}return n1;
}// 找到中间节点
private ListNode middle(ListNode head) {ListNode p1 = head; // 慢ListNode p2 = head; // 快while (p2 != null && p2.next != null) {p1 = p1.next;p2 = p2.next.next;}return p1;
}

以上代码经过优化后如下:

java">public boolean isPalindrome(ListNode h1) {if (h1 == null || h1.next == null) {return true;}ListNode p1 = h1; 	// 慢指针,中间点ListNode p2 = h1; 	// 快指针ListNode n1 = null;	// 新头ListNode o1 = h1;	// 旧头// 快慢指针找中间点while (p2 != null && p2.next != null) {p1 = p1.next;p2 = p2.next.next;// 反转前半部分o1.next = n1;n1 = o1;o1 = p1;}if (p2 != null) { // 节点数为奇数p1 = p1.next;}// 同步比较新头和后半部分while (n1 != null) {if (n1.val != p1.val) {return false;}p1 = p1.next;n1 = n1.next;}return true;
}

方法二:递归

思路分析:

  • 递归到链表的中间:首先,我们需要找到链表的中间节点。这可以通过递归实现,每次递归调用处理链表的后一半,直到链表的长度为1或2,这时我们可以认为到达了中间。

  • 递归反转后半部分链表:找到中间节点后,我们继续递归,但是这次是为了反转链表的后半部分。我们可以在到达中间节点后开始反转链表

  • 递归比较前后节点:在反转了后半部分链表之后,我们可以从原始链表的头部和反转后的后半部分的头部开始,递归比较对应节点的值,直到所有节点都被比较完毕或者发现不匹配的节点。

代码实现:

java">public class ListNode {int val;ListNode next;ListNode(int x) { val = x; }
}public class Solution {/*** 判断链表是否为回文链表* @param head 链表的头节点* @return 是否为回文链表*/public boolean isPalindrome(ListNode head) {if (head == null || head.next == null) {return true;}// 步骤1:找到链表的中间节点ListNode middle = findMiddle(head);// 步骤2:反转链表的后半部分ListNode reversedHead = reverse(middle);// 步骤3:比较前半部分和反转后的后半部分return compareTwoLists(head, reversedHead);}/*** 通过快慢指针法找到链表的中间节点* @param head 链表的头节点* @return 中间节点*/private ListNode findMiddle(ListNode head) {if (head == null) return null;ListNode fast = head, slow = head;while (fast.next != null && fast.next.next != null) {slow = slow.next;       // 慢指针每次走一步fast = fast.next.next;  // 快指针每次走两步}return slow;  // 返回中间节点}/*** 递归反转链表* @param head 需要反转的链表的头节点* @return 反转后链表的头节点*/private ListNode reverse(ListNode head) {if (head == null || head.next == null) {return head;}ListNode newHead = reverse(head.next);head.next.next = head;  // 将当前节点的下一个节点的next指向当前节点,实现反转head.next = null;       // 将当前节点的next设置为nullreturn newHead;        // 返回新的头节点}/*** 递归比较两个链表是否相等* @param l1 第一个链表的头节点* @param l2 第二个链表的头节点* @return 两个链表是否相等*/private boolean compareTwoLists(ListNode l1, ListNode l2) {if (l1 == null || l2 == null) {return l1 == l2;  // 如果两个链表都为空,则认为它们相等}if (l1.val != l2.val) {return false;  // 如果当前节点的值不相等,则链表不相等}return compareTwoLists(l1.next, l2.next);  // 递归比较下一个节点}
}

五、删除链表中间节点

问题描述:

有一个单链表的 head,我们想删除它其中的一个节点 node

给你一个需要删除的节点 node 。你将 无法访问 第一个节点  head

链表的所有值都是 唯一的,并且保证给定的节点 node 不是链表中的最后一个节点。

删除给定的节点。注意,删除节点并不是指从内存中删除它。这里的意思是:

  • 给定节点的值不应该存在于链表中。
  • 链表中的节点数应该减少 1。
  • node 前面的所有值顺序相同。
  • node 后面的所有值顺序相同。

示例一:

java">输入:head = [4,5,1,9], node = 5
输出:[4,1,9]
解释:指定链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9

示例二:

java">输入:head = [4,5,1,9], node = 1
输出:[4,5,9]
解释:指定链表中值为 1 的第三个节点,那么在调用了你的函数之后,该链表应变为 4 -> 5 -> 9

思路分析:

  • 节点值替换

    • 代码的第一行 node.val = node.next.val; 是将待删除节点的值替换为它的下一个节点的值。这样做的目的是为了保留下一个节点的值,因为如果直接删除下一个节点,它的值就会丢失。
  • 跳过下一个节点

    • 代码的第二行 node.next = node.next.next; 是将待删除节点的 next 指针指向下一个节点的 next 节点,从而跳过下一个节点。这样,原本的下一个节点(现在值已经被复制到了待删除节点)就从链表中被“删除”了,因为它的前驱节点不再指向它。

这种方法的关键在于,待删除的节点 node 并不是真正的被删除,而是它的值被替换为了下一个节点的值,并且它的 next 指针被调整以跳过下一个节点。这样做的好处是不需要找到待删除节点的前驱节点,可以在任意位置删除节点(只要该节点的下一个节点存在)。

代码实现:

java">    /**** @param node 待删除节点, 已说明肯定不是最后一个节点*/public void deleteNode(ListNode node) {node.val = node.next.val;		// 下一个节点值赋值给待"删除"节点node.next = node.next.next;		// 把下一个节点删除}

总结

以上就是关于链表的经典初级算法的集合,希望通过以上的联系,可以让大家更好的掌握关于链表算法。下期见,谢谢~


http://www.ppmy.cn/news/1542443.html

相关文章

Spring Boot框架:论坛网站开发的新选择

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

Python小游戏9——天天酷跑

安装Pygame库。如果你还没有安装&#xff0c;可以使用以下命令&#xff1a; bash pip install pygame 游戏代码&#xff1a; python import pygame import random # 初始化Pygame pygame.init() # 屏幕尺寸 SCREEN_WIDTH 800 SCREEN_HEIGHT 600 screen pygame.display.set_m…

JavaCV 之均值滤波:图像降噪与模糊的权衡之道

&#x1f9d1; 博主简介&#xff1a;CSDN博客专家&#xff0c;历代文学网&#xff08;PC端可以访问&#xff1a;https://literature.sinhy.com/#/literature?__c1000&#xff0c;移动端可微信小程序搜索“历代文学”&#xff09;总架构师&#xff0c;15年工作经验&#xff0c;…

交叉编译 perl-5.40.0(riscv64)

交叉编译 perl-5.40.0&#xff08;riscv64&#xff09; https://arsv.github.io/perl-cross/usage.html https://github.com/arsv/perl-cross 借助 perl-cross 进行交叉编译 https://www.perl.org/get.html#unix_like 这里获取 perl-5.40.0 的源码 https://github.com/arsv/pe…

科技狂潮下的新蓝海:元宇宙将如何重塑我们的世界?

内容概要 在这股科技狂潮的浪潮中&#xff0c;元宇宙犹如一颗璀璨的明珠&#xff0c;吸引着无数眼球。它的出现并非偶然&#xff0c;而是虚拟现实与增强现实技术的突破成果&#xff0c;改变了我们认知世界的方式。想象一下&#xff0c;过去只能在电影中看到的情节&#xff0c;…

Spring Boot技术栈在厨艺分享平台中的应用

4 系统设计 4.1系统概要设计 厨艺交流平台并没有使用C/S结构&#xff0c;而是基于网络浏览器的方式去访问服务器&#xff0c;进而获取需要的数据信息&#xff0c;这种依靠浏览器进行数据访问的模式就是现在用得比较广泛的适用于广域网并且没有网速限制要求的B/S结构&#xff0c…

数据结构---链表(二)【不带头双向非循环】

文章目录 双向链表的模拟实现带头单向非循环链表的模拟实现ArrayList 和 LinkedList 的区别 双向链表中有 val 域&#xff0c;next 域 &#xff0c;prev 域。next 指向下一个节点&#xff0c;prev指向前一个节点 双向链表的模拟实现 import linkedlistdemo.MySingleList;impo…

希尔贝壳与西湖大学音频信息与信号处理实验室联合发布的论文被国际顶级会议 NeurIPS 2024 录用

神经信息处理系统大会&#xff08;Conference on Neural Information Processing Systems&#xff0c;NeurIPS&#xff09;是中国计算机学会&#xff08;CCF&#xff09;推荐的人工智能领域 A 类学术会议&#xff0c;其 H5 指数高达 337&#xff0c;在 Google Scholar 的 AI 类…