欢迎来到老胡的算法解题思路,本文章主要使用的语言为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; }
}
二、链表题型
2.1、修改链表结点
例题:力扣82.删除排序链表中的重复元素||
分析:题目分析
这类题型涉及到链表结点的修改,本题主要删除链表中重复出现的结点,针对本题,由于本题给定条件是已经排序好的链表,因此重复元素出现的位置也是连续的,我们可以采用一次遍历的方法来解题。
分析:解题模板
while (条件) {
if () {
// 删除结点
node.next = node.next.next;
}
} else {
// 指向下一个新的且在链表中没有重复出现的结点
node = node.next;
}
解题:完整代码
class Solution {
public ListNode deleteDuplicates(ListNode head) {
// 给给定的链表设置头指针
ListNode listNode = new ListNode(0, head);
// 给该链表设置移动指针
ListNode node = listNode;
while (node.next != null && node.next.next != null) {
if (node.next.val == node.next.next.val) {
int count = node.next.val;
while (node.next != null && node.next.val == count) {
// 删除结点
node.next = node.next.next;
}
} else {
// 指向下一个新的且在链表中没有重复出现的结点
node = node.next;
}
}
return listNode.next;
}
}
2.2、合并有序链表
例题:力扣23.合并k个升序链表
分析:题目分析
合并 k 个有序链表的逻辑类似合并两个有序链表,难点在于,如何快速得到 k 个节点中的最⼩节点?这⾥我们就要⽤到 优先级队列 这种数据结构,把链表数值放入优先队列,然后在通过读取出来的值创建新发符合要求的链表即可。
解题:完整代码
class Solution {
ListNode mergeKLists(ListNode[] lists) {
if (lists.length == 0){
return null;
}
// 虚拟头结点指针和头节点
ListNode p = new ListNode();
ListNode dummy = new ListNode(-1,p);
// 创建优先队列
PriorityQueue<Integer> pq = new PriorityQueue<>();
// 将 k 个链表的值加入优先队列
for (ListNode head : lists) {
while(head!=null){
pq.add(head.val);
head = head.next;
}
}
while (!pq.isEmpty()) {
// 获取最⼩值,创建新链表
ListNode node = new ListNode();
node.val = pq.poll();
p.next = node;
p=p.next;
}
return dummy.next.next;
}
}
2.3、寻找单链表结点
例题:力扣1019.链表中的下一个更大节点
分析:题目分析
针对这类题目,我们只需要给两个指针,对两个指针指向的结点的值进行比较,进行两次遍历,满足条件跳出循环,最后在把结果转换为题目所需要的类型即可。
解题:完整代码
class Solution {
public int[] nextLargerNodes(ListNode head) {
int count = 0;
//头结点
ListNode listNode = new ListNode(Integer.MIN_VALUE,head);
//虚拟指针
ListNode node = head;
// 两层遍历
while(head!=null){
count++;
node = head;
while(node.next!=null){
//找到值,跳出循环
// 到达边界,也跳出循环,最后一个值交给循环外处理
if(node.next.val>head.val){
head.val=node.next.val;
break;
}
node = node.next;
}
//1.处理二层循环找不到最大值
//2.处理一层循环的最后一位数
if(node.next == null){
head.val = 0;
}
head = head.next;
}
//将结果转换为数组
int[] ans = new int[count];
for(int i = 0;listNode.next!=null;i++){
ans[i] = listNode.next.val;
listNode = listNode.next;
}
return ans;
}
}
2.4、链表相交问题
例题:力扣142.环形链表||
分析:题目分析
本题是比较典型的环形链表,我们可以借助哈希表遍历链表中的每个节点,并将它记录下来;一旦遇到了遍历过的节点,就可以判定链表中存在环。从而通过头指针指出位置并输出。
解题:完整代码
public class Solution {
public ListNode detectCycle(ListNode head) {
//构建哈希表
Map<ListNode,Integer> map = new HashMap<>();
int count = 0;
//头指针
ListNode node = head;
while(head!=null){
// 判断哈希表是是否含有同一个结点
if(map.get(head)==null){
map.put(head,count);
count++;
head = head.next;
}else{
// 跳出循环,并给出循环处的索引
count = map.get(head);
break;
}
}
// 移动头指针,寻找循环结点
for(int i = 0;i<count;i++){
node = node.next;
}
return node;
}
}
2.5、链表反转
例题:力扣206.反转链表
分析:题目分析
这类题主要作为面试考题,主要考察基础知识的掌握,在创建链表时,主要有两种方法,头插法和尾插法,针对这种题型,只需要一次遍历后用尾插法的方式并加入节点值。
解题:完整代码
class Solution {
public ListNode reverseList(ListNode head) {
ListNode listNode = new ListNode();
ListNode ans = listNode;
while(head!=null){
ListNode node = new ListNode();
node.val = head.val;
node.next = listNode.next;
listNode.next = node;
head = head.next;
}
return ans.next;
}
}
三、总结
使用链表结构可以克服数组需要预先知道数据大小的缺点,链表结构可以充分利用计算机内存空间,实现灵活的内存动态管理,同时链表允许插入和移除表上任意位置上的 节点 ,但是不允许 随机存取 。缺点是链表失去了数组随机读取的优点,并且由于增加了结点的指针域,空间开销比较大。
补充说明:链表还存在很多没有总结到的典型题型,后期总结后继续补上