链表是一种数据结构,它由一系列的节点组成,每个节点包含一个数据元素和一个指向下一个节点的指针。链表中的第一个节点叫做头节点,最后一个节点叫做尾节点。链表中的节点不一定是连续存储的,它们可以在内存中的任意位置。
链表的优点是插入和删除操作相对于数组来说更高效,因为只需要修改节点的指针,而无需移动其他节点。缺点是访问元素的效率相对较低,需要遍历整个链表才能找到目标元素。
链表有多种类型,常见的有单链表、双链表和循环链表。单链表每个节点只有一个指针指向下一个节点,双链表每个节点有两个指针,分别指向前一个节点和后一个节点,循环链表的尾节点指向头节点,形成一个闭环。
链表在许多编程问题中都有应用,比如实现栈、队列、图、树等数据结构,以及解决一些算法问题,如反转链表、合并链表等。
反转链表
public ListNode reverse(ListNode l) {
ListNode pre = null;
ListNode cur = l;
while (cur != null) {
// 暂存下一个节点
ListNode next = cur.next;
// 下一个节点转向
cur.next = pre;
// 前一个移动到当前
pre = cur;
// 当前移动到下一个
cur = next;
}
return pre;
}
反转区间链表
private ListNode reverse(ListNode a, ListNode b) {
ListNode pre = null;
ListNode cur = a;
while (cur != b) {
// 取出下一个节点暂存
ListNode next = cur.next;
// 下一个节点转向
cur.next = pre;
// 前一个移动到当前
pre = cur;
// 当前移动到下一个节点
cur = next;
}
return pre;
}
2两数相加
两数相加
题目要求:
给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。
请你将两个数相加,并以相同形式返回一个表示和的链表。
解题思路:
逆序存储,直接遍历链表就是从个位开始的,符合我们计算加法的习惯顺序。如果是正序存储,可能需要 翻转链表 或者使用栈来辅助。
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
ListNode p1 = l1;
ListNode p2 = l2;
ListNode dummy = new ListNode(-1);
ListNode p = dummy;
int carry = 0;
while (p1 != null || p2 != null || carry > 0) {
int val = carry;
if (p1 != null) {
val += p1.val;
p1 = p1.next;
}
if (p2 != null) {
val += p2.val;
p2 = p2.next;
}
// 处理进位
carry = val / 10;
val = val % 10;
p.next = new ListNode(val);
p = p.next;
}
return dummy.next;
}
21. 合并两个有序链表
合并两个有序链表
题目要求:
输⼊两个升序链表,将它们合并为⼀个新的升序链表并返回。
解题思路:
「虚拟头结点」技巧,也就是 dummy 节点
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
ListNode dummy = new ListNode(-1);
ListNode p = dummy;
ListNode p1 = l1;
ListNode p2 = l2;
// 都不为空比较大小
while (p1 != null && p2 != null) {
if (p1.val > p2.val) {
p.next = p2;
p2 = p2.next;
} else {
p.next = p1;
p1 = p1.next;
}
p = p.next;
}
// p2剩余的放到p后面
if (p1 == null) {
p.next = p2;
}
// p1剩余的放到p后面
if (p2 == null) {
p.next = p1;
}
return dummy.next;
}
141. 环形链表
环形链表
题目要求:
给你一个链表的头节点 head ,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。
如果链表中存在环 ,则返回 true 。 否则,返回 false 。
解题思路:
经典题目,要使用双指针技巧中的快慢指针,每当慢指针 slow 前进⼀步,快指针 fast 就前进两步。
如果 fast 最终遇到空指针,说明链表中没有环;如果 fast 最终和 slow 相遇,那肯定是 fast 超过了slow ⼀圈,说明链表中含有环。
public boolean hasCycle(ListNode head) {
if (head == null || head.next == null) {
return false;
}
ListNode slow = head;
ListNode fast = head;
while (fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
// 快慢指针相遇
if (fast == slow) {
return true;
}
}
return false;
}
19. 删除链表的倒数第 N 个结点
删除链表的倒数第 N 个结点
题目要求:
给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
解题思路:
第⼀步,我们先让⼀个指针 p1 指向链表的头节点 head,然后走 k 步。
第⼆步,用⼀个指针 p2 指向链表头节点 head。
第三步,让 p1 和 p2 同时向前走,p1 走到链表末尾的空指针时走了 n - k 步,p2 也走了 n - k 步,也就是链表的倒数第 k 个节点。
ublic ListNode removeNthFromEnd(ListNode head, int n) {
if (head == null || head.next == null) {
return null;
}
ListNode fast = head;
ListNode slow = head;
// 让fast先走n步
for (int i = 0; i < n; i++) {
fast = fast.next;
}
// 如果fast指针到达末尾,说明需要删除的是头节点(走完了整个链表)
if (fast == null) {
return head.next;
}
while (fast != null) {
fast = fast.next;
slow = slow.next;
}
// 删除第n个节点,即将slow节点的next指向它的下下个节点
slow.next = slow.next.next;
return head;
}