本文将总结Leetcode和剑指offer已有的链表相关的题目和解答,有简入难。
1、删除链表的节点
(1)、O(1)时间删除链表节点
题目:给定一个链表和一个节点指针,在O(1)时间删除该节点。
方法:狸猫换太子,用后一个节点数据覆盖要删除的节点,然后删除下一个节点。
leetcode 237. Delete Node in a Linked List(删除链表中的特定节点)
//java
public class leetcode237删除链表中的特定节点 {
public void deleteNode(ListNode node) {
node.val = node.next.val;
node.next = node.next.next;
}
public class ListNode {
int val;
ListNode next;
ListNode(int x) { val = x; }
}
}
# python
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def deleteNode(self, node):
"""
:type node: ListNode
:rtype: void Do not return anything, modify node in-place instead.
"""
node.val = node.next.val
node.next = node.next.next
(2)、删除链表中的重复节点
剑指offer 面试题18 删除链表中的重复节点 删除重复项
leetcode82. Remove Duplicates from Sorted List(删除有序链表中的重复项) 保留第一个重复项
leetcode83. Remove Duplicates from Sorted List II(删除有序链表中的重复项,重复项只保留第一次出现的节点如果链表是无序的,可以利用set())
题目:输入的单链表中有一些重复节点,请对他们进行删除
分析:根据问题1,可以进行狸猫换太子删除法如leetcode83,也可以进行传统的删除方法可以参见剑指offer18,对比理解。
# python
# 更好理解 三个指针 head一直是第0个节点不动,cur是当前节点,p是当前节点的前一节点
class Solution:
def deleteDuplication(self, pHead):
# write code here
head = ListNode(-1)
p = head
p.next = pHead
cur = pHead
while cur and cur.next:
if cur.val != cur.next.val:
p = p.next
cur = cur.next
else:
val = cur.val
while cur and cur.val == val:
cur = cur.next
p.next = cur
return head.next
// java
public class Solution {
public ListNode deleteDuplication(ListNode pHead) {
if (pHead == null || pHead.next == null) { // 只有0个或1个结点,则返回
return pHead;
}
if (pHead.val == pHead.next.val) { // 当前结点是重复结点
ListNode pNode = pHead.next;
while (pNode != null && pNode.val == pHead.val) {
// 跳过值与当前结点相同的全部结点,找到第一个与当前结点不同的结点
pNode = pNode.next;
}
return deleteDuplication(pNode); // 从第一个与当前结点不同的结点开始递归
} else { // 当前结点不是重复结点
pHead.next = deleteDuplication(pHead.next); // 保留当前结点,从下一个结点开始递归
return pHead;
}
}
}
(3) leetcode19 Remove Nth Node From End of List
题目:给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。你能尝试使用一趟扫描实现吗?
解法:采用双指针法:一个指针在前一个指针在后间隔n个节点,遍历一次即可。 删除链表中倒数第 n 个元素。原来的头节点可能被删除掉,所以需要使用辅助头节点。
(4). 找出链表中间节点:Leecode 876. Middle of the Linked List:
解法:双指针法:
# python
class ListNode:
def __init__(self, x):
self.val = x
self.next = None
class Solution:
def middleNode(self, head: ListNode) -> ListNode:
if head is None:
return None
slow = head
fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
return slow
(5) leetcode 203 Remove Linked List Elements 删除元素值为val 的节点 ,注意可能删除的是头结点
解法:https://leetcode-cn.com/problems/remove-linked-list-elements/solution/di-gui-he-die-dai-by-powcai-6/
2、链表反转
(1). 单链表反转
题目:输入一个单项链表,输出逆序反转后的链表。
方法:pre, cur, next 三指针循环一遍即可。 或者递归方法(不容易想到)
leetcode 206. Reverse Linked List (翻转一个链表)
剑指offer 面试题24 反转链表
# python
# 循环的方法
class Solution:
# 返回ListNode
def ReverseList(self, pHead):
if not pHead or not pHead.next:
return pHead
left = None
while pHead:
right = pHead.next
pHead.next = left
left = pHead
pHead = right
return left
# 递归的方法
class Solution:
# 返回ListNode
def ReverseList(self, pHead):
# write code here
if pHead==None or pHead.next == None:
return pHead
p = self.ReverseList(pHead.next)
pHead.next.next = pHead
pHead.next = None
return p
class ListNode:
def __init__(self, x):
self.val = x
self.next = None
//java
public class offer16递归反转链表 {
//用三个指针分别指向前一个,后一个和目前这个
public ListNode ReverseList(ListNode head) {
if(head==null||head.next==null) return head;
ListNode i=null;
ListNode j=head;
ListNode k;
while(j!=null){//比较简单
k=j.next;
j.next=i;
i=j;
j=k;
}
return i;
}
//用递归方法实现反转链表
public ListNode reverse(ListNode current) {
if (current == null || current.next == null) return current;
ListNode nextNode = current.next;
current.next = null;
ListNode reverseRest = reverse(nextNode);
nextNode.next = current;
return reverseRest;
}
}
(2). leetcode 24 两两交换链表中的节点
Ref: https://blog.csdn.net/xiaoling_000666/article/details/78927687
给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。
(3). leetcode 92. 反转链表 II
反转从位置 m 到 n 的链表。请使用一趟扫描完成反转。
示例:
输入: 1->2->3->4->5->NULL, m = 2, n = 4
输出: 1->4->3->2->5->NULL
3、求链表倒数第k个节点
题目:输入一个单向链表,输出该链表中倒数第k个节点。
分析: 双指针法,设置p1,p2两个指针,首先p1移动k-1个点,然后p2和p1再一起移动,直到p1到达链表尾部,p2刚好是链表的倒数第k个节点。
# python
class Solution:
def FindKthToTail(self, head, k):
# write code here
p1 = p2 = head
for i in range(k):
if p1==None:
return None
p1 = p1.next
while(p1):
p2 = p2.next
p1 = p1.next
return p2
// java
public class offer15链表中倒数第K个结点 {
//要分别考虑head和k的边界条件 本方法只需要一次遍历链表
public ListNode FindKthToTail (ListNode head,int k) {
if(head==null) return head;
if(k<=0) return null;
ListNode first=head;
ListNode second=head;
for(int i=0;i<k;i++){
if(first==null) return null;
first=first.next;
}
while(first!=null){
first=first.next;
second=second.next;
}
return second;
}
}
4、 判断单链表是否有环
leetcode141 Linked List Cycle(判断链表是否有环)
题目:输入一个单链表判断是否有环存在
方案一:用一个set集合取遍历每一个节点,如果出现重复就证明有环(注意:两个节点的值相同 != 两个节点相同)
方案二:快慢指针法,从头出发,快指针一次走两步,慢指针一次走一步,如果存在环,那么快慢指针一定在某一时刻相遇。
# python
# 方案一
class Solution1:
def hasCycle(self, head):
mapping = set()
flag = False
p = head
while p:
if p not in mapping:
mapping.add(p)
else:
flag = True
break
p = p.next
return flag
# 方案二
class Solution1:
def hasCycle(self, head):
if head is None or head.next is None:
return False
slow = head
fast = head
while fast is not None and fast.next is not None:
slow = slow.next
fast = fast.next.next
if slow == fast:
return True
return False
//java
public class leetcode141判断链表是否有环 {
public static boolean hasLoop(ListNode n){
//定义两个指针tmp1,tmp2
ListNode tmp1 = n;
ListNode tmp2 = n.next;
while(tmp2!=null){
int d1 = tmp1.val;
int d2 = tmp2.val;
if(d1 == d2)return true;//当两个指针重逢时,说明存在环,否则不存在。
tmp1 = tmp1.next; //每次迭代时,指针1走一步,指针2走两步
tmp2 = tmp2.next.next;
if(tmp2 == null)return false;//不存在环时,退出
}
return true; //如果tmp2为null,说明元素只有一个,也可以说明是存在环
}
//方法2:将每次走过的节点保存到hash表中,如果节点在hash表中,则表示存在环
public static boolean hasLoop2(ListNode n){
ListNode temp1 = n;
HashMap<ListNode,ListNode> map = new HashMap<ListNode,ListNode>();
while(n!=null){
if(map.get(temp1)!=null) {
return true;
}else {
map.put(temp1, temp1);
}
temp1 = temp1.next;
if(temp1 == null)return false;
}
return true;
}
public class ListNode {
int val;
ListNode next;
ListNode(int x) {
val = x;
}
}
}
5、找到环的入口节点
题目:输入一个单链表判断是否有环存在,如果存在环,如何找到环的入口节点。
分析:快慢指针法,从头出发,快指针faster一次走两步,慢指针slower一次走一步,如果存在环,那么快慢指针一定在某一时刻相遇。接下来,让faster回到链表的头部重新走,每次走一步,当faster,slower再次相遇的时候,则就是入口节点。
这类问题通常使用双指针的方法,即一个快指针一个慢指针。
faster = faster.next.next;
slower = slower.next;
“公理”:两指针相遇时,快指针走过的路程为慢指针的2倍。
链表有环时,有以下3种情况,右边和下边都是第一种的特例,下文以第一种为讨论对象。
1.判断是否有环
两个指针开始时均指向头节点,快指针每次跨2个节点,慢指针每次跨1个节点,如果两个指针能够相遇即存在环。
2.求环的长度
假设两个指针在b点相遇。则点b必在环中,以b点为出发点,两个指针再来一次“追逐”,慢指针走一圈,快指针走2圈后他们又在b点相遇。此时慢指针走过的路程即为环周长。
3.求环入口
根据前文的“公理”:两指针相遇时,快指针走过的路程为慢指针的2倍。可得:(快指针)a+nr+b=2(a+b)(满指针), 其中r为圆周长,n>=1 化简得:a=nr-b, 即: a=(n-1)r+r-b
这个式子的意义就是,一个慢指针slower1从链表头出发,1个慢指针slower2从b点出发,slower1走到环入口时(路程为a),slower2也刚好走到环入口(路程为(n-1)r+r-b:n-1个整圈加上r-b的路程)
# python #这个方案更容易理解
class Solution:
def EntryNodeOfLoop(self, pHead):
if pHead==None or pHead.next==None or pHead.next.next==None:
return None
slow=pHead.next
fast=pHead.next.next
while slow!=fast:
if fast.next==None or fast.next.next==None:
return None
slow=slow.next
fast=fast.next.next
fast=pHead
while slow!=fast:
slow=slow.next
fast=fast.next
return fast
# java
public class Solution {
public ListNode EntryNodeOfLoop(ListNode pHead)
{
ListNode fast=pHead;
ListNode slow=pHead;
while(fast!=null&&fast.next!=null){
fast=fast.next.next;
slow=slow.next;
if(fast==slow)
break;
}
if(fast==null||fast.next==null)
return null;
slow=pHead;
while(fast!=slow){
fast=fast.next;
slow=slow.next;
}
return slow;
}
}
6、两个链表相交的第一个公共节点
leetcode160. Intersection of Two Linked Lists(寻找两个链表的交叉点)
剑指offer 面试题52 链表的公共节点
题目:如果两个没有环的链表相交,怎么求出他们相交的第一个节点?
Ref: https://www.cnblogs.com/yqpy/p/9551004.html
方案一:将两个链表先后压入同一个栈中,遇到第一个重复的就是他们的第一个公共节点。
方案二:就是把全部结点分别压入两个栈,利用栈的特性LIFO,然后同时pop出栈,一开始两边的元素肯定是相同的,当遇到不同的元素时,肯定已经遇到了最后一个节点,那就break
方案三:就是分别从链表的头结点开始遍历,当两条链表有长度差时,先让长链表走他们的差值,当走到还剩下和短链表一样长时,两个链表同时遍历,这样就能找到第一个公共结点了
方案四:采用对齐的思想,把两个单链表互相拼接成同样长度的链表即l1+l2 和 l2+l1,然后从头用指针p1,p2分别遍历,p1=p2时找到。
#python
# 方案一:
class Solution:
def FindFirstCommonNode(self, pHead1, pHead2):
# write code here
result_set = set()
while pHead1:
result_set.add(pHead1)
pHead1 = pHead1.next
while pHead2:
if pHead2 in result_set:
return pHead2
pHead2 = pHead2.next
return None
# 方案二:
class Solution(object):
def getIntersectionNode(self, headA, headB):
stack1 = []
stack2 = []
while headA:
stack1.append(headA)
headA = headA.next
while headB:
stack2.append(headB)
headB = headB.next
node1 = None
while stack1 and stack2 and stack1[-1] is stack2[-1]:
node1 = stack1.pop()
stack2.pop()
return node1
#is比较的是两个对象的地址值,判断两个对象是否为同一个实例对象;而==比较的是对象的值是否相等,其调用了对象的__eq__()方法。
# 方案三:
def FindFirstCommonNode(self, pHead1, pHead2):
len1 = self.getChainLen(pHead1)
len2 = self.getChainLen(pHead2)
if len2 > len1:
pHead1, pHead2 = pHead2, pHead1
diff = abs(len1-len2)
while diff > 0:
pHead1 = pHead1.next
diff -= 1
while pHead1 != pHead2:
pHead1 = pHead1.next
pHead2 = pHead2.next
return pHead1
def getChainLen(self, Head):
chainLen = 0
while Head:
chainLen += 1
Head = Head.next
return chainLen
方案四:
class Solution(object):
def getIntersectionNode(self, headA, headB):
ha, hb = headA, headB
while ha != hb:
ha = ha.next if ha else headB
hb = hb.next if hb else headA
return ha
//java
# 方案一:
import java.util.HashSet;
public class Solution {
public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
HashSet<ListNode> set = new HashSet<>();
if(pHead1==null || pHead2==null) return null;
ListNode p1 = pHead1,p2 = pHead2;
while(p1!=null){
set.add(p1);
p1 = p1.next;
}
while(p2!=null){
if(set.contains(p2)){
return p2;
}
p2 = p2.next;
}
return null;
}
}
#方案四:
public class Solution {
public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
ListNode p1 = pHead1;
ListNode p2 = pHead2;
while(p1!=p2){
p1 = (p1==null) ? pHead2 : p1.next;
p2 = (p2==null) ? pHead1 : p2.next;
}
return p1;
}
}
7、合并两个有序链表
(1). 合并两个有序链表
leetcode 21 Merge Two Sorted Lists (合并两个有序链表迭代)
剑指offer 面试题25 合并两个排好序的链表–递归
题目:给定两个排好序的链表,对两个链表进行合并
分析:递归和迭代两种方法分别给出。可以用两个指针分别指向两个链表,比较每一次节点值得大小。
# python
# 方案一:非递归
class Solution:
# 返回合并后列表
def Merge(self, pHead1, pHead2):
# write code here
mergeHead = ListNode(-1)
p = mergeHead
while pHead1 and pHead2:
if pHead1.val >= pHead2.val:
mergeHead.next = pHead2
pHead2 = pHead2.next
else:
mergeHead.next = pHead1
pHead1 = pHead1.next
mergeHead = mergeHead.next
if pHead1:
mergeHead.next = pHead1
elif pHead2:
mergeHead.next = pHead2
return p.next
#方案二:递归
class Solution:
# 返回合并后列表
def Merge(self, pHead1, pHead2):
if pHead1==None and pHead2==None:
return None
if pHead1==None:
return pHead2
if pHead2==None:
return pHead1
if pHead1.val<=pHead2.val:
pHead1.next=self.Merge(pHead1.next,pHead2)
return pHead1
else:
pHead2.next=self.Merge(pHead1,pHead2.next)
return pHead2
//java
// 方案一:非递归
public class Solution {
public ListNode Merge(ListNode list1,ListNode list2) {
//新建一个头节点,用来存合并的链表。
ListNode head=new ListNode(-1);
head.next=null;
ListNode root=head;
while(list1!=null&&list2!=null){
if(list1.val<list2.val){
head.next=list1;
list1=list1.next;
}else{
head.next=list2;
list2=list2.next;
}
head=head.next
}
//把未结束的链表连接到合并后的链表尾部
if(list1!=null){
head.next=list1;
}
if(list2!=null){
head.next=list2;
}
return root.next;
}
}
# 方案二:递归
public class Solution {
public ListNode Merge(ListNode list1,ListNode list2) {
if (list1 == null) return list2;
if (list2 == null) return list1;
ListNode head = null;
if (list1.val <= list2.val){
head = list1;
head.next = Merge(list1.next, list2);
} else {
head = list2;
head.next = Merge(list1, list2.next);
}
return head;
}
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}
}
(2). Leetcode 23 Merge k Sorted Lists
8.旋转链表
(1) Leetcode 61. 旋转链表
给定一个链表,旋转链表,将链表每个节点向右移动 k 个位置,其中 k 是非负数。
示例 1:
输入: 1->2->3->4->5->NULL, k = 2
输出: 4->5->1->2->3->NULL
解释:
向右旋转 1 步: 5->1->2->3->4->NULL
向右旋转 2 步: 4->5->1->2->3->NULL
方法一:先将链表闭合成环,然后找到相应的位置断开这个环,确定新的链表头和链表尾
https://leetcode-cn.com/problems/rotate-list/solution/xuan-zhuan-lian-biao-by-leetcode/
方法二:双指针法找到倒数第k个节点,断开成两个链表,然后将后一个链表拼接在前一个链表前面。
9. 复制链表:
(1). leetcode 138. Copy List with Random Pointer offer35 复杂链表的复制
解答:https://leetcode-cn.com/problems/copy-list-with-random-pointer/solution/duo-chong-si-lu-by-powcai/
https://www.cnblogs.com/yanmk/p/9220525.html
方案一:递归法 通过了
# -*- coding:utf-8 -*-
# class RandomListNode:
# def __init__(self, x):
# self.label = x
# self.next = None
# self.random = None
class Solution:
# 返回 RandomListNode
def Clone(self, pHead):
# write code here
if pHead==None:
return None
newNode=RandomListNode(pHead.label)
newNode.next=self.Clone(pHead.next)
newNode.random=pHead.random
return newNode
其他链表题目:
430. Flatten a Multilevel Doubly Linked List
遍历一个有子链表的双向链表。尽管此题被贴上了链表的标签,但是题目中主要考察的是深度优先搜索,链表只是一种外在的形式。
817. Linked List Components
给出一个链表和一个数组G。数组G中的元素是链表所有节点 val 值的子集。如果某个节点或者某一段节点的 val 值在数组G中,那么就被看作一个“组件”。求最终求整个链表一共有多少个“组件”?这个题需要用到集合,可以借此熟悉集合的用法。
2. Add Two Numbers
给出两个非空的链表,代表着2个非负整数。求这2个非负整数的和,也使用链表表示出来。
445. Add Two Numbers II
本题和 2. Add Two Numbers 非常像,唯一不同的地方在于链表表示整数是从高位到低位表示的。计算的过程中需要使用堆栈。可以借此熟悉堆栈的用法。
1019. Next Greater Node In Linked List
给出一个链表,求每个节点后面第一个val值更大的节点。为了降低时间复杂度,这个题使用了堆栈、还将链表的遍历顺序改变了。有点像动态规划的思路了,也就是利用之前计算存储下来的结果,计算当前的问题。
725. Split Linked List in Parts
把一个链表分割为 k 个尽量相等的部分。这个题目练习了如何从一个大链表中截取一小部分的链表。
328. Odd Even Linked List
把链表的奇数节点都排在偶数节点的前面,使用双指针法。不过本题的双指针法严格意义上来说,是用了三个指针的。
147. Insertion Sort List
使用插入排序对一个链表进行排序。需要学会如何在一个有序链表中插入元素,并且保证插入元素后链表还是有序的。
141. Linked List Cycle
给定一个链表,判断该链表中是否有环。该题可以用hash集合做,也可以用双指针法做。这两种方法都很常见,建议把它们都熟练掌握。
160. Intersection of Two Linked Lists
写一个程序去寻找两个单向链表相交的节点。本题同样有两种方法,一种是集合,一种是双指针法。可以当作双指针法的题目练习。
142. Linked List Cycle II
和141. Linked List Cycle非常类似,同样是给出一个链表,判断该链表中是否有环。如果有环,就返回环形开始的那个节点。如果这个链表中没有环,那么就返回null。方法仍然是集合、双指针法。
109. Convert Sorted List to Binary Search Tree
给出一个按照递增排序的单向链表,将其转换成一个高度平衡的二叉树。这个题目也利用了快慢双指针法找到链表的中点。该方法在 141. Linked List Cycle 和 142. Linked List Cycle II中都有使用过。
234. Palindrome Linked List
判断一个链表是不是回文串,主要用到了从一个链表中间一分为二的方法,以及反转链表的方法。
148. Sort List
对链表进行归并排序。需要掌握归并排序的方法、将链表从中间一分为二的方法和合并两个有序链表的方法。
143. Reorder List
这个题目是一道综合题,涉及三种针对链表的操作:
1 . 如何从中间将链表一分为二?使用了 109. Convert Sorted List to Binary Search Tree 中介绍的快慢双指针法
2. 如何反转一个链表?使用了 206. Reverse Linked List 中反转链表的方法,即节点的next指向前一个节点
3. 如何拼接两个链表?使用了类似于 21. Merge Two Sorted Lists 和 2. Add Two Numbers 中的合并链表的方法