文章目录
- 链表基础
- 141. 环形链表 - 2.12 (快慢指针)
- 142. 环形链表 II - 2.12 (快慢指针)
- 876. 链表的中间结点 - 2.12(快慢指针)
- 203. 移除链表元素 - 简单 - 10/18(遍历查找)
- 707. 设计链表 - 中等 - 10/18
- 206. 反转链表 - 中等 - 10/19(双指针 | 递归)
- 92. 反转链表 II(反转链表的一部分) - 2.13(头插法)
- 25. K 个一组翻转链表 - 2.13(迭代和递归)
- 234. 回文链表 - 2.13(递归)
- 24. 两两交换链表中的节点 - 中等 - 10/19(递归)
- 19. 删除链表的倒数第 N 个结点 - 中等 - 10/19 (快慢指针)
- 面试题 02.07. 链表相交 - 简单 - 10/19(双指针 | 快慢指针)
- 剑指 Offer 06. 从尾到头打印链表(头插法 | 栈)
- 剑指 Offer 24. 反转链表(双指针)
- 剑指 Offer 25. 合并两个排序的链表(新建链表)
- 23. 合并K个升序链表 - 2.11(根据值反向构建新链表 | 最小堆)
- 剑指 Offer 52. 两个链表的第一个公共结点(双指针 | 拼接链表)
- JZ23 链表中环的入口结点(哈希表 | 快慢指针)
- 剑指 Offer 22. 链表中倒数第k个节结点(双指针)
- 剑指 Offer 35. 复杂链表的复制(map存储)
- JZ76 删除链表中重复的结点(遍历)
- JZ18 删除链表的节点(遍历)
链表基础
什么是链表,链表是一种通过指针串联在一起的线性结构,每一个节点是又两部分组成,一个是数据域一个是指针域(存放指向下一个节点的指针),最后一个节点的指针域指向null(空指针的意思)。
链接的入口点称为列表的头结点也就是head。
链表的类型:
单链表
单链表中的节点只能指向节点的下一个节点。
双链表
双链表:每一个节点有两个指针域,一个指向下一个节点,一个指向上一个节点。
双链表 既可以向前查询也可以向后查询。
循环链表
循环链表,顾名思义,就是链表首尾相连。
循环链表可以用来解决约瑟夫环问题。
链表的存储方式
数组是在内存中是连续分布的,但是链表在内存中可不是连续分布的。
链表是通过指针域的指针链接在内存中各个节点。
所以链表中的节点在内存中不是连续分布的 ,而是散乱分布在内存中的某地址上,分配机制取决于操作系统的内存管理。
链表的定义
public class ListNode{
int value;
ListNode next;
ListNode(){};
ListNode(int value) { this.value = value; }
ListNode(int value, ListNode next){
this.value = value;
this.next = next;
}
}
链表的操作:
删除节点
添加节点
性能分析
141. 环形链表 - 2.12 (快慢指针)
给你一个链表的头节点 head ,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。
如果链表中存在环 ,则返回 true 。 否则,返回 false 。
解析://快慢指针,慢指针走一步,快指针走两步
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
//快慢指针,慢指针走一步,快指针走两步
public class Solution {
public boolean hasCycle(ListNode head) {
ListNode slow = head; //慢指针
ListNode fast = head; //快指针
while(fast != null && fast.next != null){
slow = slow.next; //慢指针走一步
fast = fast.next.next; //快指针走两步
if(slow == fast) return true; //如果相等,说明有环
}
return false;
}
}
142. 环形链表 II - 2.12 (快慢指针)
给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。
不允许修改 链表。
解析:快慢指针,相遇后将慢指针指向head,然后快慢指针同时移动,相遇即为入环节点
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
//快慢指针,相遇后将慢指针指向head,然后快慢指针同时移动,相遇即为入环节点
public class Solution {
public ListNode detectCycle(ListNode head) {
ListNode slow = head; //慢指针
ListNode fast = head; //快指针
while(fast != null && fast.next != null){
slow = slow.next; //慢指针走一步
fast = fast.next.next; //快指针走两步
if(slow == fast) break; //如果相等,先跳出循环
}
//如果fast为空,则没有环
if(fast == null || fast.next == null) {
return null;
}
slow = head; //慢指针指向head
while(slow != fast){ //同时移动,快指针在环内走一圈,慢指针从头开始走,相遇时即跳出循环
slow = slow.next;
fast = fast.next;
}
return slow;
}
}
876. 链表的中间结点 - 2.12(快慢指针)
解析:快慢指针,每当慢指针 slow 前进一步,快指针 fast 就前进两步,这样,当 fast 走到链表末尾时,slow 就指向了链表中点。
/**
* 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; }
* }
*/
//快慢指针,每当慢指针 slow 前进一步,快指针 fast 就前进两步,这样,当 fast 走到链表末尾时,slow 就指向了链表中点。
class Solution {
public ListNode middleNode(ListNode head) {
// 快慢指针初始化指向 head
ListNode slow = head;
ListNode fast = head;
// 快指针走到末尾时停止
while (fast != null && fast.next != null) {
// 慢指针走一步,快指针走两步
slow = slow.next;
fast = fast.next.next;
}
// 慢指针指向中点
return slow;
}
}
203. 移除链表元素 - 简单 - 10/18(遍历查找)
203. 移除链表元素 - 简单
给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点 。
解析:设置一个虚拟头结点在进行删除操作。
/**
* 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 removeElements(ListNode head, int val) {
// 因为删除可能涉及到头节点,所以设置dummy节点,统一操作
ListNode dummy = new ListNode(0);
dummy.next = head;
ListNode h = dummy;
while(h.next != null){
if(h.next.val == val){ //如果找到val,则跳过
h.next = h.next.next;
} else {
h = h.next; //没有找到就移动到下一个
}
}
return dummy.next;
}
}
707. 设计链表 - 中等 - 10/18
设计链表的实现。您可以选择使用单链表或双链表。单链表中的节点应该具有两个属性:val 和 next。val 是当前节点的值,next 是指向下一个节点的指针/引用。如果要使用双向链表,则还需要一个属性 prev 以指示链表中的上一个节点。假设链表中的所有节点都是 0-index 的。
在链表类中实现这些功能:
get(index):获取链表中第 index 个节点的值。如果索引无效,则返回-1。
addAtHead(val):在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点。
addAtTail(val):将值为 val 的节点追加到链表的最后一个元素。
addAtIndex(index,val):在链表中的第 index 个节点之前添加值为 val 的节点。如果 index 等于链表的长度,则该节点将附加到链表的末尾。如果 index 大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。
deleteAtIndex(index):如果索引 index 有效,则删除链表中的第 index 个节点。
//单链表
class ListNode{
int val;
ListNode next;
ListNode(){}
ListNode(int val) {
this.val = val;
}
}
class MyLinkedList {
int size;
ListNode head;
public MyLinkedList() {
size = 0;
head = new ListNode(0);
}
public int get(int index) {
if(index < 0 || index >= size) return -1;
ListNode curr = head;
//因为包含一个虚拟头节点,所以查找第 index+1 个节点
for(int i=0; i<index+1; ++i){
curr = curr.next;
}
return curr.val;
}
public void addAtHead(int val) {
addAtIndex(0,val);
}
public void addAtTail(int val) {
addAtIndex(size,val);
}
public void addAtIndex(int index, int val) {
if(index > size) return;
if(index < 0) index = 0;
++size; //大小+1
ListNode pred = head;
for(int i=0; i<index; i++){
pred = pred.next;
}
//创建新的节点
ListNode toAdd = new ListNode(val);
toAdd.next = pred.next;
pred.next = toAdd;
}
public void deleteAtIndex(int index) {
if(index<0 || index>=size) return;
--size;
ListNode pred = head;
for(int i=0; i<index; i++){
pred = pred.next;
}
pred.next = pred.next.next;
}
}
/**
* Your MyLinkedList object will be instantiated and called as such:
* MyLinkedList obj = new MyLinkedList();
* int param_1 = obj.get(index);
* obj.addAtHead(val);
* obj.addAtTail(val);
* obj.addAtIndex(index,val);
* obj.deleteAtIndex(index);
*/
206. 反转链表 - 中等 - 10/19(双指针 | 递归)
解法一: 双指针法
首先定义一个cur指针,指向头结点,再定义一个pre指针,初始化为null。
然后就要开始反转了,首先要把 cur->next 节点用tmp指针保存一下,也就是保存一下这个节点。
为什么要保存一下这个节点呢,因为接下来要改变 cur->next 的指向了,将cur->next 指向pre ,此时已经反转了第一个节点了。
接下来,就是循环走如下代码逻辑了,继续移动pre和cur指针。
最后,cur 指针已经指向了null,循环结束,链表也反转完毕了。 此时我们return pre指针就可以了,pre指针就指向了新的头结点。
/**
* 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 reverseList(ListNode head) {
ListNode pred = null; //前一个结点
ListNode curr = head; //当前结点
ListNode temp = null; //临时保存结点
while(curr != null){
temp = curr.next; //保存下一个结点
curr.next = pred; //反转
pred = curr; //移动
curr = temp; //移动
}
return pred;
}
}
解法二:递归反转链表
/**
* 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 reverseList(ListNode head) {
//如果链表只有一个节点的时候反转也是它自己,直接返回即可
if(head == null || head.next == null) return head;
ListNode last = reverseList(head.next);
head.next.next = head;
head.next = null; //链表的末尾要指向 null
return last;
}
}
92. 反转链表 II(反转链表的一部分) - 2.13(头插法)
解析:
1、我们定义两个指针,分别称之为 g(guard 守卫) 和 p(point)。
我们首先根据方法的参数 m 确定 g 和 p 的位置。将 g 移动到第一个要反转的节点的前面,将 p 移动到第一个要反转的节点的位置上。我们以 m=2,n=4为例。
2、将 p 后面的元素删除,然后添加到 g 的后面。也即头插法。
3、根据 m 和 n 重复步骤(2)
4、返回 dummyHead.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 reverseBetween(ListNode head, int left, int right) {
ListNode dummy = new ListNode(-1); //虚拟头节点
dummy.next = head;
ListNode begin = dummy; //开始反转的位置
ListNode end = dummy.next; //结束反转的位置
//将指针移动到相应的位置
for(int i=0; i<left-1; i++){
begin = begin.next;
end = end.next;
}
//头插法插入节点 比如: dummy -> 1 -> 3 -> 2 -> 4 -> 5
for(int i=0; i<right-left; i++){
ListNode temp = end.next; //接收end的后一个节点
end.next = end.next.next; //删除end的后一个节点
temp.next = begin.next; //将刚删除的节点指向begin的next节点
begin.next = temp; //begin再指向 刚删除的节点
}
return dummy.next;
}
}
25. K 个一组翻转链表 - 2.13(迭代和递归)
给你一个链表,每 k 个节点一组进行翻转,请你返回翻转后的链表。
k 是一个正整数,它的值小于或等于链表的长度。
如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。
进阶:
你可以设计一个只使用常数额外空间的算法来解决此问题吗?
你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。
解析:
1、先反转以 head 开头的 k 个元素。
2、将第 k + 1 个元素作为 head 递归调用 reverseKGroup 函数。
3、将上述两个过程的结果连接起来。
/**
* 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) {
if(head == null) return head;
ListNode begin = head; //一组的开始位置
ListNode end = head; //一组的结束位置
//先将end指针移动k个单位,即为一组
for(int i=0; i<k; i++){
if(end == null) return head; //不足 k 个,不需要反转,base case
end = end.next; //移动
}
ListNode newHead = reverse(begin,end); //反转一组节点
begin.next = reverseKGroup(end, k); //递归反转后续链表 并 连接起来
return newHead;
}
//反转链表[a,b)节点
private ListNode reverse(ListNode a, ListNode b){
ListNode pre = null;
ListNode cur = a;
ListNode temp = a;
while(cur != b){
temp = cur.next;
cur.next = pre;
pre = cur;
cur = temp;
}
return pre;
}
}
234. 回文链表 - 2.13(递归)
解析:先用快慢指针找到中点,然后反转后半部分链表,比较前后链表的值是否相等,若不相等则不是回文链表,反之则是。
/**
* 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 boolean isPalindrome(ListNode head) {
ListNode slow = head;
ListNode fast = head;
//快慢指针 找 中点
while(fast != null && fast.next != null){
slow = slow.next;
fast = fast.next.next;
}
//如果fast不为空,说明为奇数,slow还要继续走一步
if(fast != null){
slow = slow.next;
}
//比较前后半部分是否对称
ListNode left = head;
ListNode right = reverse(slow);
while(right != null){
if(left.val != right.val){
return false;
}
left = left.next;
right = right.next;
}
return true;
}
//反转后半部分的链表
private ListNode reverse(ListNode head){
ListNode pre = null;
ListNode cur = head;
ListNode temp = head;
while(cur != null){
temp = cur.next;
cur.next = pre;
pre = cur;
cur = temp;
}
return pre;
}
}
24. 两两交换链表中的节点 - 中等 - 10/19(递归)
参考:三道题套路解决递归问题
给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。
你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
解析:使用递归
直接上三部曲模版:
- 找终止条件。 什么情况下递归终止?没得交换的时候,递归就终止了呗。因此当链表只剩一个节点或者没有节点的时候,自然递归就终止了。
- 找返回值。 我们希望向上一级递归返回什么信息?由于我们的目的是两两交换链表中相邻的节点,因此自然希望交换给上一级递归的是已经完成交换处理,即已经处理好的链表。
- 本级递归应该做什么。 结合第二步,看下图!由于只考虑本级递归,所以这个链表在我们眼里其实也就三个节点:head、head.next、已处理完的链表部分。而本级递归的任务也就是交换这3个节点中的前两个节点,就很easy了。
/**
* 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 swapPairs(ListNode head) {
//终止条件:链表只剩一个节点或者没节点了,没得交换了。返回的是已经处理好的链表
if(head == null || head.next == null) return head;
//声明当前head节点的下一个节点为 next
ListNode next = head.next;
//交换位置
head.next = swapPairs(next.next);
next.next = head;
//返回给上一级的是当前已经完成交换后,即处理好了的链表部分
return next;
}
}
19. 删除链表的倒数第 N 个结点 - 中等 - 10/19 (快慢指针)
给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
进阶:你能尝试使用一趟扫描实现吗?
解析:快慢双指针,快指针先移动,然后一起移动。
/**
* 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 removeNthFromEnd(ListNode head, int n) {
//加一个空的结点
ListNode dummy = new ListNode(0);
//让它指向头指针
dummy.next = head;
ListNode first = dummy;
ListNode second = dummy;
//第一个指针先移动 n 步
for (int i = 1; i <= n + 1; i++) {
first = first.next;
}
//第一个指针到达终点停止遍历
while (first != null) {
first = first.next;
second = second.next;
}
//改变指针方向,即为删除结点
second.next = second.next.next;
return dummy.next;
}
}
面试题 02.07. 链表相交 - 简单 - 10/19(双指针 | 快慢指针)
给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null 。
图示两个链表在节点 c1 开始相交:
解法一:
我们求出两个链表的长度,并求出两个链表长度的差值,然后让curA移动到,和curB 末尾对齐的位置,如图:
此时我们就可以比较curA和curB是否相同,如果不相同,同时向后移动curA和curB,如果遇到curA == curB,则找到焦点。
否则循环退出返回空指针。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
if(headA == null || headB == null) return null;
ListNode currA = headA;
ListNode currB = headB;
int lenA = 0, lenB = 0;
//求A链表的长度
while(currA != null){
++lenA;
currA = currA.next;
}
//求B链表的长度
while(currB != null){
++lenB;
currB = currB.next;
}
//重新赋值
currA = headA;
currB = headB;
//如果A比B长
if(lenA > lenB){
//让A先移动
for(int i=0; i<lenA-lenB;i++){
currA = currA.next;
}
//再一起移动,如果发现结点相等,就返回
for(int i=0; i<lenB;i++){
if(currA == currB){
return currA;
}else{
currA = currA.next;
currB = currB.next;
}
}
}else{ //如果B比A长
//让B先移动
for(int i=0; i<lenB-lenA;i++){
currB = currB.next;
}
//再一起移动,如果发现结点相等,就返回
for(int i=0; i<lenA;i++){
if(currA == currB){
return currA;
}else{
currA = currA.next;
currB = currB.next;
}
}
}
return null;
}
}
解法二:
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
//让 p1 遍历完链表 A 之后开始遍历链表 B,让 p2 遍历完链表 B 之后开始遍历链表 A,这样相当于「逻辑上」两条链表接在了一起
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode p1 = headA;
ListNode p2 = headB;
while(p1 != p2){
if(p1 == null) p1 = headB; //p1 遍历完链表 A 之后开始遍历链表 B
else p1 = p1.next;
if(p2 == null) p2 = headA; //p2 遍历完链表 B 之后开始遍历链表 A
else p2 = p2.next;
}
return p1;
}
}
剑指 Offer 06. 从尾到头打印链表(头插法 | 栈)
方法一:头插法实现
/**
* public class ListNode {
* int val;
* ListNode next = null;
*
* ListNode(int val) {
* this.val = val;
* }
* }
*
*/
import java.util.ArrayList;
public class Solution {
public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
ArrayList<Integer> list = new ArrayList<>();
ListNode temp = listNode;
while(temp!=null){
list.add(0,temp.val);
temp = temp.next;
}
return list;
}
}
方法二:利用栈,遍历链表,压栈后出栈。
/**
* public class ListNode {
* int val;
* ListNode next = null;
*
* ListNode(int val) {
* this.val = val;
* }
* }
*
*/
import java.util.ArrayList;
import java.util.Stack;
public class Solution {
public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
//用来存储链表中节点的值。
Stack<Integer> stack = new Stack<>();
while(listNode!=null){
stack.push(listNode.val);
listNode = listNode.next;
}
//存储数组
ArrayList<Integer> list = new ArrayList<>();
while(!stack.isEmpty()){
list.add(stack.pop()); //将值从栈中弹出,并添加到ArrayList中
}
return list;
}
}
方法三:先反转链表,在存储
/**
* public class ListNode {
* int val;
* ListNode next = null;
*
* ListNode(int val) {
* this.val = val;
* }
* }
*
*/
import java.util.ArrayList;
public class Solution {
public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
ArrayList<Integer> list = new ArrayList();
ListNode pre = null;
ListNode cur = listNode;
ListNode temp = cur;
//反转链表
while(cur != null){
temp = cur.next;
cur.next = pre;
pre = cur;
cur = temp;
}
//依次添加进list
while(pre != null){
list.add(pre.val);
pre = pre.next;
}
return list;
}
}
剑指 Offer 24. 反转链表(双指针)
描述
给定一个单链表的头结点pHead,长度为n,反转该链表后,返回新链表的表头。
数据范围: n\leq1000n≤1000
要求:空间复杂度 O(1)O(1) ,时间复杂度 O(n)O(n) 。
如当输入链表{1,2,3}时,
经反转后,原链表变为{3,2,1},所以对应的输出为{3,2,1}。
以上转换过程如下图所示:
反转链表:
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
public class Solution {
public ListNode ReverseList(ListNode head) {
ListNode pred = null; //前一个结点
ListNode curr = head; //当前结点
ListNode temp = null; //临时保存结点
while(curr != null){
temp = curr.next; //保存下一个结点
curr.next = pred; //反转
pred = curr; //移动
curr = temp; //移动
}
return pred;
}
}
剑指 Offer 25. 合并两个排序的链表(新建链表)
解析:新建一个链表来存储,比较两个链表的值,小的就添加到新链表中,然后移动到下一个结点再比较。
/**
* 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 mergeTwoLists(ListNode l1, ListNode l2) {
ListNode dummp = new ListNode(-1); //合并后的链表
ListNode h = dummp; //操作链表
while(l1 != null && l2 != null){
if(l1.val < l2.val){ //如果l1结点的值 小于 l2结点的值
h.next = l1; //链表指向 较小的l1;
l1 = l1.next; //l1也移动到下一个结点
}else{
h.next = l2;
l2 = l2.next;
}
h = h.next; //移动到下一个结点
}
if(l1 == null){ //如果l1为空了,就把 h指向l2
h.next = l2;
}
if(l2 == null){ //如果l2为空了,就把 h指向l1
h.next = l1;
}
return dummp.next;
}
}
23. 合并K个升序链表 - 2.11(根据值反向构建新链表 | 最小堆)
解法一:读取所有链表的节点,存入list数组中,对数组排序,然后根据数组的值反向构建单链表返回。
/**
* 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) {
//新建一个数组,用来存储所有值
List<Integer> list = new ArrayList<>();
//遍历所有结点,存入list
for(ListNode node : lists){
while(node != null){
list.add(node.val); //添加到list中
node = node.next; //下一个结点
}
}
//排序
Collections.sort(list);
//将集合转为链表
ListNode head = new ListNode(0);
ListNode h = head;
for(int i : list){
ListNode newNode = new ListNode(i); //新结点
h.next = newNode; //拼接新结点
h = h.next; //指向下一个结点
}
h.next = null; //最后一个结点为空
return head.next; //返回head
}
}
解法二:二叉堆。将k个链表都加入最小堆,然后取出堆元素加入链表中,再添加链表的下一个节点,然后移动p指针。
/**
* 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) {
if(lists.length == 0) return null;
ListNode dummp = new ListNode(-1); //虚拟头节点
ListNode p = dummp;
//优先级队列,最小堆
PriorityQueue<ListNode> pq = new PriorityQueue<>(
lists.length, (o1,o2) -> (o1.val - o2.val)
);
//将k个链表都加入最小堆
for(ListNode list : lists){
if(list != null){
pq.add(list);
}
}
//遍历最小堆
while(!pq.isEmpty()){
ListNode node = pq.poll(); //取出的节点就是最小节点
p.next = node; //添加到链表
if(node.next != null) pq.add(node.next); //添加下一个节点
p = p.next; //p不断移动
}
return dummp.next;
}
}
剑指 Offer 52. 两个链表的第一个公共结点(双指针 | 拼接链表)
解法一:使用双指针,先计算出两个链表的长度,然后让长的链表先移动“差值”,然后再一起移动,一起移动时碰到两个节点相同就返回,否则就一起移动到下一个节点。
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
public class Solution {
public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
if(pHead1 == null && pHead2 == null) return null;
ListNode curr1 = pHead1;
ListNode curr2 = pHead2;
//算出两个链表的长度
int len1=0, len2=0;
while(curr1 != null){
++len1;
curr1 = curr1.next;
}
while(curr2 != null){
++len2;
curr2 = curr2.next;
}
//重置头指针
curr1 = pHead1;
curr2 = pHead2;
//如果链表1比链表2长
if(len1 > len2){
//链表1先移动
for(int i=0; i<(len1-len2); i++){
curr1 = curr1.next;
}
//再同时移动,如果发现结点相等,就返回
for(int j=0; j<len2; j++){
if(curr1 == curr2){
return curr1;
}else{
curr1 = curr1.next;
curr2 = curr2.next;
}
}
}else{
//链表2先移动
for(int i=0; i<(len2-len1); i++){
curr2 = curr2.next;
}
//再同时移动,如果发现结点相等,就返回
for(int j=0; j<len2; j++){
if(curr2 == curr1){
return curr2;
}else{
curr1 = curr1.next;
curr2 = curr2.next;
}
}
}
return null;
}
}
解法二:让 p1 遍历完链表 A 之后开始遍历链表 B,让 p2 遍历完链表 B 之后开始遍历链表 A,这样相当于「逻辑上」两条链表接在了一起
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
//让 p1 遍历完链表 A 之后开始遍历链表 B,让 p2 遍历完链表 B 之后开始遍历链表 A,这样相当于「逻辑上」两条链表接在了一起
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode p1 = headA;
ListNode p2 = headB;
while(p1 != p2){
if(p1 == null) p1 = headB; //p1 遍历完链表 A 之后开始遍历链表 B
else p1 = p1.next;
if(p2 == null) p2 = headA; //p2 遍历完链表 B 之后开始遍历链表 A
else p2 = p2.next;
}
return p1;
}
}
JZ23 链表中环的入口结点(哈希表 | 快慢指针)
解法一:使用哈希表存储和查找,如找到有,说明存在环,直接返回就是环入口;否则,就是无环单链表,返回null。
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}
*/
import java.util.HashSet;
public class Solution {
public ListNode EntryNodeOfLoop(ListNode pHead) {
//建立哈希表:存储、查找node
HashSet<ListNode> set = new HashSet<ListNode>();
while(pHead != null){
if(set.contains(pHead)){ //如果哈希表里包含有该节点,说明有环,返回就是环入口
return pHead;
}else{
set.add(pHead); //添加节点
pHead = pHead.next; //移动到下一个节点
}
}
//无环单链表
return null;
}
}
解法二:快慢指针,相遇后将慢指针指向head,然后快慢指针同时移动,相遇即为入环节点
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
//快慢指针,相遇后将慢指针指向head,然后快慢指针同时移动,相遇即为入环节点
public class Solution {
public ListNode detectCycle(ListNode head) {
ListNode slow = head; //慢指针
ListNode fast = head; //快指针
while(fast != null && fast.next != null){
slow = slow.next; //慢指针走一步
fast = fast.next.next; //快指针走两步
if(slow == fast) break; //如果相等,先跳出循环
}
//如果fast为空,则没有环
if(fast == null || fast.next == null) {
return null;
}
slow = head; //慢指针指向head
while(slow != fast){ //同时移动,快指针在环内走一圈,慢指针从头开始走,相遇时即跳出循环
slow = slow.next;
fast = fast.next;
}
return slow;
}
}
剑指 Offer 22. 链表中倒数第k个节结点(双指针)
解法一:创建一个链表指向原来的链表,先遍历计算出链表长度,然后复位,移动到差值的部分即可返回。
import java.util.*;
/*
* public class ListNode {
* int val;
* ListNode next = null;
* public ListNode(int val) {
* this.val = val;
* }
* }
*/
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param pHead ListNode类
* @param k int整型
* @return ListNode类
*/
public ListNode FindKthToTail (ListNode pHead, int k) {
int len=0;
ListNode curr = pHead;
//计算链表长度
while(curr != null){
++len;
curr = curr.next;
}
//回到头节点
curr = pHead;
//如果链表长度小于k,直接返回空
if(len < k){
return null;
}else{
//遍历到要返回的节点
for(int i=0; i<(len-k); i++){
curr = curr.next;
}
return curr;
}
}
}
解法二:使用双指针,让后指针先移动k个位置,然后再一起移动,当后指针移动到null时,返回前指针即可。
import java.util.*;
/*
* public class ListNode {
* int val;
* ListNode next = null;
* public ListNode(int val) {
* this.val = val;
* }
* }
*/
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param pHead ListNode类
* @param k int整型
* @return ListNode类
*/
public ListNode FindKthToTail (ListNode pHead, int k) {
ListNode pred = pHead;
ListNode end = pHead;
for(int i=0; i<k; i++){
if(end == null) return null;
end = end.next;
}
while(end != null){
pred = pred.next;
end = end.next;
}
return pred;
}
}
剑指 Offer 35. 复杂链表的复制(map存储)
解析:使用map存储新旧节点,建立 “原节点 -> 新节点” 的 Map 映射,然后通过获取键值对 构建新链表的 next 和 random 指向,最后返回新链表的头节点。
/*
public class RandomListNode {
int label;
RandomListNode next = null;
RandomListNode random = null;
RandomListNode(int label) {
this.label = label;
}
}
*/
import java.util.Map;
import java.util.HashMap;
public class Solution {
public RandomListNode Clone(RandomListNode pHead) {
if(pHead == null) return null;
RandomListNode curr = pHead;
Map<RandomListNode,RandomListNode> map = new HashMap<>();
//复制各节点,并建立 “原节点 -> 新节点” 的 Map 映射
while(curr != null){
map.put(curr,new RandomListNode(curr.label));
curr = curr.next;
}
//重置
curr = pHead;
//构建新链表的 next 和 random 指向
while(curr != null){
map.get(curr).next = map.get(curr.next);
map.get(curr).random = map.get(curr.random);
curr = curr.next;
}
//返回新链表的头节点
return map.get(pHead);
}
}
JZ76 删除链表中重复的结点(遍历)
解析:比较相邻的两个节点,如果相同,就一直移动到不相同值的节点,然后返回。
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}
*/
import java.util.HashSet;
public class Solution {
public ListNode deleteDuplication(ListNode pHead) {
//空处理
if(pHead == null) return null;
//创建虚拟头节点,方便操作
ListNode dummy = new ListNode(0);
dummy.next = pHead;
ListNode cur = dummy;
while(cur.next != null && cur.next.next != null){
//如果两个值相等,就继续查找并移动到相等的值的节点
if(cur.next.val == cur.next.next.val){
int x = cur.next.next.val;
while(cur.next != null && cur.next.val == x){
//跳过重复的节点
cur.next = cur.next.next;
}
}else{
//没有重复的节点,就移动到下一个节点
cur = cur.next;
}
}
//返回链表
return dummy.next;
}
}
JZ18 删除链表的节点(遍历)
解析:创建虚拟头节点,遍历链表,找到与给出的值相等的节点,就跳过;否则就移动到下一个节点。
import java.util.*;
/*
* public class ListNode {
* int val;
* ListNode next = null;
* public ListNode(int val) {
* this.val = val;
* }
* }
*/
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param head ListNode类
* @param val int整型
* @return ListNode类
*/
public ListNode deleteNode (ListNode head, int val) {
if(head == null) return null;
//定义虚拟节点
ListNode dummy = new ListNode(-1);
dummy.next = head;
ListNode cur = dummy;
while(cur.next != null){
//如果值相等,就跳过
if(cur.next.val == val){
cur.next = cur.next.next;
}else{
//否则就移动到下一个节点
cur = cur.next;
}
}
//返回头节点
return dummy.next;
}
}