写在前边的话
今天是链表练习的第二天,链表相关的知识点已经在昨天总结过了,今天继续链表相关算法的训练。链表的相关知识点。
24. 两两交换链表中的节点
题目连接
题目难度
中等
看到题目的第一想法
看到题目以后会想到使用添加虚拟头节点来进行解题,但是交换的过程没有梳理的特别清晰。
看完代码随想录之后的总结
解题思路
- 添加虚拟头节点。
- 梳理两两交换的过程,注意这里需要用到两个tmp中间量保存数据,交换的过程看看卡哥画的图形,简单明了。
- 添加虚拟头节点算法的复杂度: 时间复杂度:O(n),空间复杂度:O(1)。
- 卡哥还写了递归算法,我就暂时没看了,后边再刷题的时候再看吧。
文章讲解
视频讲解
代码编写
python
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def swapPairs(self, head: Optional[ListNode]) -> Optional[ListNode]:
dummy = ListNode(next=head)
cur = dummy
while cur and cur.next and cur.next.next:
tmp1 = cur.next.next.next
tmp = cur.next
cur.next = cur.next.next
cur.next.next = tmp
tmp.next = tmp1
cur = cur.next.next #注意cur位置的变更
return dummy.next
注意:cur位置的变更没有很好的确定,由于设置了虚拟头节点,因此实际的cur位置应该是要交换数据之前的一个节点,刚开始没想好,写成了要交换节点的第一个节点,导致代码出错。
Java
/**
* 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) {
ListNode dummy = new ListNode(0, head);
ListNode cur = dummy;
ListNode tmp = null;
ListNode tmp1 = null;
while(cur != null && cur.next != null && cur.next.next != null){
tmp = cur.next.next.next;
tmp1 = cur.next;
cur.next = cur.next.next;
cur.next.next = tmp1;
tmp1.next = tmp;
cur = cur.next.next;
}
return dummy.next;
}
}
19.删除链表的倒数第N个节点
题目连接
题目难度
中等
看到题目的第一想法
看到题目的第一想法是,先遍历一遍链表拿到链表的总长度,然后再遍历执行删除的操作。
python代码实现
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]:
cur = head
count = 0
while(cur):
cur = cur.next
count += 1
index = count - n # 循环到要删除节点的前一个节点
dummy = ListNode(next = head)
cur = dummy
for i in range(index):
cur = cur.next
cur.next = cur.next.next
return dummy.next
这样的写法,前后依次进行了两次循环, 算法的时间复杂度为O(2n),空间复杂度是O(1)。
看完代码随想录之后的总结
解题思路
1. 使用双指针,快慢指针。
2. 注意点:1)要使用虚拟头节点,这样如果实际删除的是头节点也会比较方便;2)slow指针指向的应该是要删除节点的前一个节点,这样比较容易进行删除操作,所以如果要删除倒数第n个节点的话fast要走n+1步,这样等到fast指向null的时候,slow就指向了要删除节点的前一个节点。
3. 时间复杂度O(n),空间复杂度O(1)。双指针法就对链表进行了一次遍历,执行会比较快。
文章讲解
视频讲解
代码随想录19.删除链表的倒数第N个节点视频讲解
代码编写
python
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]:
dummy = ListNode(next=head)
slow = dummy
fast = dummy
for i in range(n+1):
fast = fast.next
while(fast):
fast = fast.next
slow = slow.next
slow.next = slow.next.next
return dummy.next
java
/**
* 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, head);
ListNode slow = dummy;
ListNode fast = dummy;
for(int i=0; i < n+1; i++){
fast = fast.next;
}
while(fast != null){
fast = fast.next;
slow = slow.next;
}
slow.next = slow.next.next;
return dummy.next;
}
}
面试题 02.07. 链表相交
题目连接
题目难度
简单
看到题目的第一想法
第一次看到这个题目的时候,其实没想到什么好的解决办法。
看完代码随想录之后的总结
解题思路
1. 关键是利用链表的长度差,同时注意相交节点不仅是值的相同地址也要相同,所以使用指针。
2. 步骤:
1)找到两个链表的长度差值
2)让长度长的链表移动到,和长度短的链表对齐的位置(因为要是两个链表存在相交的话,假如相交开始的节点是A节点,那么从A节点开始它们后边的节点也是相交的,因此要先末尾对齐。)
3)比较此时两个链表的指针是否相同,如果不相同就指针后移,直到找到相同的指针,如果到链表末尾也没找到的话,那么这两个链表就不相交返回空指针。
3. 算法时间复杂度O(m+n),空间复杂度O(1)。
文章讲解
代码编写
python
# Definition for singly-linked list.
# class ListNode(object):
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution(object):
def getIntersectionNode(self, headA, headB):
"""
:type head1, head1: ListNode
:rtype: ListNode
"""
curA = headA
curB = headB
lenA = 0
lenB = 0
while(curA):
curA = curA.next
lenA += 1
while(curB):
curB = curB.next
lenB += 1
# 判断长链表,使得最终长链表是B链表
curA = headA
curB = headB
if lenA > lenB:
lenA, lenB = lenB, lenA
curA, curB = curB, curA
# 遍历B到与A末尾对齐的位置,此时B要先移动的步数是 lenB - lenA
for i in range(lenB - lenA):
curB = curB.next
# 到这里A B末尾已经对齐了,接下来就要开始比较指针是否相等了,直到两个链表的末尾
while(curA):
if curA == curB:
return curA
else:
curA = curA.next
curB = curB.next
# 上边的遍历没找到相同的指针则最后返回空指针
return None
java
/**
* 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) {
ListNode curA = headA;
ListNode curB = headB;
int lenA = 0;
int lenB = 0;
while(curA != null){
curA = curA.next;
lenA++;
}
while(curB != null){
curB = curB.next;
lenB++;
}
curA = headA;
curB = headB;
if(lenA > lenB){
int tmp = lenB;
lenB = lenA;
lenA = tmp;
ListNode tmpNode = curB;
curB = curA;
curA = tmpNode;
}
int gap = lenB - lenA;
while(gap-- > 0){
curB = curB.next;
}
while(curA != null){
if(curA == curB){
return curA;
}else{
curA = curA.next;
curB = curB.next;
}
}
return null;
}
}
142.环形链表II
题目连接
题目难度
中等
看到题目的第一想法
起初看到这个题目的时候,想的是一边遍历节点一边利用键值对存储链表中的节点,然后判断遍历过程中的节点有没有出现过来判断有没有环,然而却忽略了一个问题,链表可能有相同值的节点存在,并且这种写法的空间复杂度会比较高,写完测试没通过发现了问题。还得用指针呀,判断指针相同,因此也更加意识到链表不仅包含数据还包含指针的性质。
看完代码随想录之后的总结
解题思路
1. 快慢指针
2. 步骤:
1)设置快慢指针,均从头开始遍历链表节点,直到两个指针相遇
2)然后重新从head和相遇指针遍历链表,直到两个指针相遇,相遇的节点即为所求节点。
3. 算法时间复杂度O(n)(快慢指针相遇前,指针走的次数小于链表长度,快慢指针相遇后,两个index指针走的次数也小于链表长度,总体为走的次数小于 2n),空间复杂度O(1)。
文章讲解
视频讲解
代码随想录142. 环形链表||
代码编写
python
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def detectCycle(self, head: Optional[ListNode]) -> Optional[ListNode]:
slow = head
fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if slow == fast: # 如果存在环则找到快慢指针会相遇找到相遇的指针
slow = head # 慢指针回到链表头
while slow != fast: # 遍历节点直到同一个节点
slow = slow.next
fast = fast.next
return slow
return None
java
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
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){
slow = head;
while(slow != fast){
slow = slow.next;
fast = fast.next;
}
return slow;
}
}
return null;
}
}
今日总结
今天是链表算法练习的第二天,做起来感觉熟悉了很多,对于链表这种数据结构也有了更深的理解。对于虚拟头节点的使用也更加的熟练,一般链表的增删改或者涉及到交换的话我们一般会考虑虚拟头节点的使用,这样避免了头节点情况的单独讨论;意识到在解决链表相关问题的时候一般都会用到指针;同时学习了关于删除节点倒数第N个节点、链表相交以及环形链表判断的新的算法思路。总之是收获满满,相信以后也是会更加熟练的!