LeetCode 24. 两两交换链表中的节点
题目链接🔗:LeetCode 24. 两两交换链表中的节点
思路
- 创建一个结点cur,cur的下一个结点指向要交换的两个结点的第一个结点
- 创建firstnode和secondnode分别用来存储要交换的两个结点的第一个结点和第二个结点
- 创建一个temp结点指向下一次要交换的两个结点的第一个结点
- 步骤一:让cur的下一个结点指向secondnode
- 步骤二:让secondnode的下一个结点指向firstnode
- 步骤三:让firstnode的下一个结点指向temp
代码实现
class ListNode{
int val;
ListNode next;
public ListNode() {
}
public ListNode(int val) {
this.val = val;
}
public ListNode(int val, ListNode next) {
this.val = val;
this.next = next;
}
}
public ListNode swapPairs(ListNode head) {
ListNode temp; //temp用来保存下两个结点中的第一个结点
ListNode ahead = new ListNode(-1);
ahead.next = head;
ListNode cur = ahead;
ListNode firstnode; // 临时节点,保存两个节点之中的第一个节点
ListNode secondnode; // 临时节点,保存两个节点之中的第二个节点
while (cur.next != null && cur.next.next != null) {
temp = cur.next.next.next;
firstnode = cur.next;
secondnode = cur.next.next;
cur.next = secondnode;
secondnode.next = firstnode;
firstnode.next = temp;
cur = firstnode; //cur移动,准备下一轮交换
}
return ahead.next;
}
时间复杂度:O(n)
使用递归实现:
public ListNode swapPairs(ListNode head) {
if (head == null || head.next == null) {
return head;
}
ListNode next = head.next;
//递归
ListNode newNode = swapPairs1(next.next);
//进行交换
next.next = head;
head.next = newNode;
return next;
}
LeetCode 19.删除链表的倒数第 N 个节点
题目链接🔗:LeetCode 19.删除链表的倒数第 N 个节点
思路
整体思路是:定义fast指针和slow指针,初始值为虚拟头结点(指向head的前一个结点),fast首先走 n+1 步,然后fast和slow同时移动,直到fast指向末尾,此时slow就会指向要删除结点的前一个结点,如下图:
那么会什么会这样呢?下面是推导步骤
- 如果我们用sum表示链表的长度,设一个指针一开始指向虚拟头结点,那么让此指针移动到要删除结点的前一个结点,那么指针需要向前移动 sum-n 步
- 采用快慢指针的方法实现,首先需要创建一个虚拟的头结点,让fast和slow的下一个结点都指向head
- 首先让fast向前移动 m 步,那么距离fast移动到链表末尾的后一位还需要 sum+1-m 步
- 要是slow移动至要删除元素的前一位
- 那么就需要 sum+1-m = sum-n
- 得到 m=n+1
代码实现
class ListNode{
int val;
ListNode next;
public ListNode() {
}
public ListNode(int val) {
this.val = val;
}
public ListNode(int val, ListNode next) {
this.val = val;
this.next = next;
}
}
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode ahead = new ListNode(0);
ahead.next = head;
ListNode fast = ahead;
ListNode slow = ahead;
//先将fast向前移动n+1步
for (int i = 0; i < n + 1; i++) {
fast = fast.next;
}
//同时移动fast和slow
while(fast != null){
fast = fast.next;
slow = slow.next;
}
//删除slow的下一个结点
slow.next = slow.next.next;
return ahead.next;
}
LeetCode 面试题 02.07. 链表相交
题目链接🔗:面试题 02.07. 链表相交
思路
刚拿到题目是第一种想法就是双层循环,然后逐个比较结点是否相同(注意这里比较的不是结点内的值是否相同,而是比较结点的地址)
其实也可以用双指针的解法:
- 目前curA指向链表A的头结点,curB指向链表B的头结点
- 先求出两个链表的长度,然后求出两个链表长度的差值,然后让链表A与链表B末尾对齐,curA移动到与curB对齐的位置
- 此时开始就可以比较curA与curB是否相同,如果不相同,同时向后移动curA和curB,如果遇到curA == curB,则找到交点,否则返回null
代码实现
直接方法
class ListNode{
int val;
ListNode next;
public ListNode() {
}
public ListNode(int val) {
this.val = val;
}
public ListNode(int val, ListNode next) {
this.val = val;
this.next = next;
}
}
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode temp;
while(headA != null){
temp = headB;
while (temp != null){
if (headA == temp){
return headA;
}
temp = temp.next;
}
headA = headA.next;
}
return null;
}
时间复杂度:O(mn)
双指针法
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
int countA = 0;
int countB = 0;
ListNode curA = headA;
ListNode curB = headB;
//求链表A的长度
while ( curA != null){
countA++;
curA = curA.next;
}
//求链表B的长度
while ( curB != null){
countB++;
curB = curB.next;
}
int n = Math.abs(countA - countB);
curA = headA;
curB = headB;
if (countA > countB){
//移动curA
for (int i = 0; i < n; i++) {
curA = curA.next;
}
}else if (countB > countA){
//移动curB
for (int i = 0; i < n; i++) {
curB = curB.next;
}
}
//比较
while (curA != null){
if (curA == curB){
return curA;
}
curA = curA.next;
curB = curB.next;
}
return null;
}
时间复杂度:O(m+n)
LeetCode 142.环形链表 II
题目链接🔗:LeetCode 142.环形链表 II
思路
本题主要有两个关键点
- 判断链表是否有环
- 怎么找到这个环的入口
判断链表是否有环
可以使用快慢指针法,快指针以每次移动两步的速度前进,慢指针以每次移动一步的速度前进,如果链表没有环两个指针不可能相遇,如果两个指针相遇表示链表一定有环
但是你可能还会想到快指针会不会存在跳过慢指针的情况?
这种情况是不存在的,因为快指针相对于慢指针的速度是一步,就是快指针以每次一步的速度接近慢指针,因此如果有环两个指针一定会相遇并且是在环中相遇
找到这个环的入口
假设从头结点到环形入口节点 的节点数为x, 环形入口节点到 fast指针与slow指针相遇节点 节点数为y,从相遇节点 再到环形入口节点节点数为 z, 如图所示:
- 相遇时慢指针需要走 x+y
- 快指针需要走 x+y+n(y+z) (n表示相遇时快指针在环中转了多少圈)
- 因为fast指针是一步走两个节点,slow指针一步走一个节点, 所以 fast指针走过的节点数 = slow指针走过的节点数 * 2
- 因此存在 (x + y) * 2 = x + y + n (y + z) (这里的n一定是大于等于一的,因为 fast指针至少要多走一圈才能相遇slow指针)
- x = n (y + z) - y
- x = (n - 1) (y + z) + z
- 从式子中可以得到以下结论:头结点出发一个指针,从相遇节点 也出发一个指针,这两个指针每次只走一个节点, 那么当这两个指针相遇的时候就是 环形入口的节点
代码实现
public ListNode detectCycle(ListNode head) {
ListNode fast = head;
ListNode slow = head;
while (fast.next != null){
fast = fast.next.next;
slow = slow.next;
if (fast == slow){
ListNode temp = head;
while (temp != slow){
temp = temp.next;
slow = slow.next;
}
return temp;
}
}
return null;
}