24两两交换链表中的节点
题目要求:
给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。
输入:head = [1,2,3,4]
输出:[2,1,4,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 swapPairs(ListNode head) {
//设置虚拟结点,每三个节点进行操作(其实是四个)
ListNode dummyHead = new ListNode(-1);
//这一步指向不要忘记了。
dummyHead.next = head;
//cur表示当前操作位置,往往在要操作的节点前一个位置
ListNode cur = dummyHead;
//分别保存第三个,第一个,第二个节点
ListNode temp ,firstNode,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;
//移动完之后,工作指针temp也需要向后移动,移至需要更改节点之前
cur = cur.next.next;
}
return dummyHead.next;
}
}
cur.next.next;
}
return dummyHead.next;
}
}
19 删除链表的倒数第n个结点 +203 移除链表元素
题目要求
给你一个链表,删除链表的倒数第 n
个结点,并且返回链表的头结点。
思路一
先算长度,再正向地去算该删除的第几个结点。
注意,链表的处理往往都需要一个哨兵结点来处理空链表的情况,需要自己新建一个空链表。
且在删除时,链表的操作指针一定要指向被删除结点的前一个
代码一(失败版)
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
//未设置哨兵的后果
struct ListNode* removeNthFromEnd(struct ListNode* head, int n){
//先获取整个链表长度len,然后再删除第len-n个(下标从0开始),题目要求长度在1-30以内,还不大
int len = GetLength(head);
//要找到要删除结点的前驱后继
struct ListNode* pre;//注意这里声明时要带struct
struct ListNode* del;
int j =0;
//遍历找到前驱
pre = head;
while(pre && j<len-n-1){
pre = pre->next;
j++;
}
//进行删除.分情况讨论,如果只有一个元素该返回什么
if(len == 1){
return
}
//以及可能出现要删除的地方前驱以及其后继为空的情况
if(!pre || !pre->next){
return head;
}
del =pre->next;
pre->next = del->next;
free(del);
return head;
}
//获取总长度
int GetLength(struct ListNode*head){
int i;
int len = 0;
struct ListNode* del;
del = head;
while(del){
del = del->next;
len++;
}
return len;
}
代码一(正确版)
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* removeNthFromEnd(struct ListNode* head, int n){
//先获取整个链表长度len,然后再删除第len-n个(下标从0开始),题目要求长度在1-30以内,还不大
int len = GetLength(head);
//要找到要删除结点的前驱后继
struct ListNode* pre;//注意这里声明时要带struct
struct ListNode* del;
int j ;
//创建一个哨兵结点
struct ListNode*dummy = malloc(sizeof(struct ListNode));
dummy->val = 0,dummy->next = head;
//注意有了哨兵之后,默认链表元素从1开始;找到要删除的前驱
pre = dummy;
for(j=1;j<len-n+1;j++){
pre =pre->next;
}
//找到后开始删除
pre->next = pre->next->next;
del = dummy->next;
free(dummy);
return del;
}
//获取总长度
int GetLength(struct ListNode*head){
int i;
int len = 0;
struct ListNode* del;
del = head;
while(del){
del = del->next;
len++;
}
return len;
}
思路二(双指针)
-
设计哨兵以及快慢指针,二者初始都指向哨兵,然后快指针先走n+1步,其后快慢一起走直到快指针指向了空为止。
-
此时的慢指针就会指向要删除结点的前一个位置。(本题的n是从1开始)
-
注意不能对空指针进行操作。
代码二
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* removeNthFromEnd(struct ListNode* head, int n){
//要找到要删除结点的前驱后继
struct ListNode* fast;//注意这里声明时要带struct
struct ListNode* slow;
int j ;
//创建一个哨兵结点
struct ListNode*dummy = malloc(sizeof(struct ListNode));
dummy->val = 0,dummy->next = head;
fast = dummy;slow = dummy;
//快指针先动n+1
n++;
while(n-- && fast){//注意这里的限制条件
fast = fast->next;
}
//快慢指针一起往后走,直到快指针指向null
while(fast){
fast = fast->next;
slow = slow ->next;
}
//slow的位置就是要删除的前一个位置
slow->next = slow->next->next;
struct ListNode* del = dummy->next;
free(dummy);
return del;
}
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 dummyhead = new ListNode(-1);
dummyhead.next = head;
ListNode fastNode = dummyhead;
ListNode slowNode = dummyhead;
//快指针向后走n+1步
for(int i = 0;i < n + 1;i ++){
fastNode = fastNode.next;
}
//此时快慢再一起走,当快指针为空的时候慢指针就已经走到要删除的节点之前了。
while(fastNode != null){
fastNode = fastNode.next;
slowNode= slowNode.next;
}
//进行删除操作
slowNode.next = slowNode.next.next;
return dummyhead.next;
}
}
142环形链表
题目要求:
给定一个链表的头节点 head
,返回链表开始入环的第一个节点。 如果链表无环,则返回 null
。
如果链表中有某个节点,可以通过连续跟踪 next
指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos
来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos
是 -1
,则在该链表中没有环。注意:pos
不作为参数进行传递,仅仅是为了标识链表的实际情况。
不允许修改 链表。
思路:
第一想法:
- 怎么去找这个环,链表中只有一个环吗?
困难:
- 判断环
- 如何找到入口处
正解:
-
如果无环,快慢指针走完整个链表永远不可能相遇;如果有环,在相对速度为1的前提下,二者一定会在环内相遇。因为就像操场跑圈,相对速度1,快指针会一个一个地追,最终一定会追上。
-
slow 走过的距离:x+y ; fast 走过的距离:x+y+n(y+z)* ;进圈之后!!!这里慢指针不可能在圈里面走超过一圈,因为我们设定快指针速度是其两倍,相同时间路程也是两倍,当慢指针马上走完一圈时,快指针一定都走了两圈了,早就相遇了!
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eDH9hiro-1676721314229)(C:\Users\ASUS\Pictures\Screenshots\屏幕截图_20230218_185907.png)]
-
可以根据—时间相同,速度一个1一个2,来列出二者路程的等式——2(x+y) = x+y + n(z+y)——化简后得到**:x=(n-1)(z+y)+z**
-
可以发现当n=1时,即走到相遇点后快指针又绕了一圈与慢指针相遇了。x=z,故等式里的前面那一坨就是快指针多绕了多少圈罢了
-
根据x=z,在相遇点的指针和在起始的指针一起移动,一定会在环的入口处相遇。
-
找入口就可以定义一个在相遇处的指针,一个在起始位置的指针,二者相遇的点一定是入口。
代码
/**
* 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) {
//根据思路,我们先设置快慢指针,一个速度为2一个为1
ListNode fastNode = head;
ListNode slowNode = head;
//判断是否有环
while(fastNode != null && fastNode.next != null){//因为快指针两步去走得看next
fastNode = fastNode.next.next;
slowNode = slowNode.next;
//相遇之后说明有环
if(slowNode == fastNode){
//有环之后再去找入口,设置两个指针,一个在相遇点,一个在起始位置
ListNode Node1 = fastNode;
ListNode Node2 = head;
while(Node1 != Node2){
//这时是公式里的x=z环节,速度是一样的
Node1 = Node1.next;
Node2 = Node2.next;
}
return Node1;
}
}
//如果找不到环即退出返回null,其实找到环就一定会有入口的
return null;
}
}