⭐️代码随想录⭐️
数组篇:
二分查找
移除数组
有序数组的平方
长度最小的数组
螺旋矩阵
链表篇:
链表移除
设计链表
反转链表
交换链表中的节点
文章目录
- 19. 删除链表的倒数第 N 个结点
- 思路
- 代码
- 面试题 02.07. 链表相交
- 思路
- 代码
- 142. 环形链表 II
- 思路
- 判断链表有环
- 确定环的入口
- 代码
19. 删除链表的倒数第 N 个结点
题目LeetCode19. 删除链表的倒数第 N 个结点
思路
这道题的逻辑比较清晰
1. 先找到倒数第n+1个节点
2. 删除倒数第n个节点
为什么要找倒数第n+1个节点而不是倒数第n个节点呢?因为删除第n个节点时我们需要知道该节点的前一个节点在哪里,修改前一个节点的指针域来实现删除该节点,所以关键在于如何寻找倒数第n+1个节点
可以通过快慢指针来寻找倒数第n+1个节点,让快指针始终在慢指针前面n+1个长度,这样当快指针走到链表结尾
时,慢指针指向的就是倒数第n+1个节点
思考:本题是否需要和之前的提题目一样构造虚拟头节点呢?
使用虚拟头节点会更加方便,因为这题有可能会将头节点删除,这样不能够直接返回head,而使用虚拟头节点可以将真正的头节点和其他的节点统一处理,最后只需要返回dummHead->next即可
代码
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* removeNthFromEnd(struct ListNode* head, int n){
typedef struct ListNode ListNode;
//构造虚拟头节点
ListNode* dummyHead = (ListNode*)malloc(sizeof(ListNode));
dummyHead->next = head;
ListNode* fast = dummyHead;
ListNode* slow = dummyHead;
//快指针先走n + 1步
int cnt = 0;
while (cnt < n + 1)
{
//判断删除的节点是否合法
if (fast == NULL) return head;
fast = fast->next;
cnt++;
}
//快慢指针同时移动,直到快指针遍历完
while (fast)
{
slow = slow->next;
fast = fast->next;
}
//slow是待删除节点的下一个节点
//删除节点
ListNode* tmp = slow->next;
slow->next = tmp->next;
free(tmp);
//返回新的头节点
return dummyHead->next;
}
面试题 02.07. 链表相交
题目LeetCode面试题 02.07. 链表相交
思路
首先明白一点:链表相交指的是两个链表从某一个节点开始往后所有的节点在物理空间
上的地址是一样的,而不是节点的val域一样
如果两链表相交
,那么相交的部分最多的情况就是从较短的链表头节点开始相交,所以先让长链表遍历到一个位置使该位置往后的节点数和短链表节点数相同,从该位置开始判断链表节点的空间是否一样,继续判断两链表后一个节点的位置是否一样
如果链表不相交
,那么直到curA和curB遍历完后两个指针的值仍然不相交(物理空间不同)
代码
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
typedef struct ListNode ListNode;
int lenA = 0;
int lenB = 0;
ListNode* curA = headA;
ListNode* curB = headB;
//求A链表的节点数
while (curA)
{
lenA++;
curA = curA->next;
}
//求B链表节点数
while (curB)
{
lenB++;
curB = curB->next;
}
curA = headA;
curB = headB;
//求两链表长度差
int gap = lenA - lenB;
//对齐链表末尾
if (gap > 0) while (gap--) curA = curA->next;
else
{
gap *= -1;‘//注意gap为负数的情况
while (gap--) curB = curB->next;
}
//寻找相同节点
while (curA)
{
//找到相交的第一个节点了,后续的节点一定相交
if (curA == curB) return curA;
//两链表的该节点不相交,继续向后遍历
else
{
curA = curA->next;
curB = curB->next;
}
}
return NULL;
}
142. 环形链表 II
题目LeetCode142. 环形链表 II
思路
本题的两个关键问题
- 如何判断链表有环?
- 如果有环,如何确定环的入口?
判断链表有环
- 如果链表为空或者链表只有一个节点,那么链表一定无环
- 如果链表有多个节点,可以通过
快慢指针
判断链表是否有环
定义快指针fast、满指针slow,快指针一次走2个节点,慢指针一次走一个节点,如果经过若干步、快慢指针的值相等(指向同一个节点),那么链表一定有环
理论上来说如果一个链表有环,只需要保证快慢指针的速度差为1个节点,最终快慢指针的值一定会相等,所以这里快指针一次走3个节点、慢指针一次走2个节点也是可以的,但是这样的话可能会导致一次性越过链表尾节点,需要更多的条件来判断,所以这里使用快指针一次走2节点、慢指针一次走1节点是最合适的
确定环的入口
在确定链表一定有环之后,接下来就该确定环的入口了。
假设从头结点到环形入口节点的节点数为x。 环形入口节点到 fast指针与slow指针相遇节点节点数为y。从相遇节点 再到环形入口节点节点数为 z。 如图所示:
慢指针经过的节点数为x+y
,快指针经历的节点数为x+n(y+z)+y
,其中n大于等于1因为快指针一定是在环内部走过了一圈才和慢指针相遇,由快指针的速度是慢指针的两倍,所以在相同的时间内有等式
2(x+y)=x+n(y+z)+y
,化简可得x=(n-1)(y+z)+z
,n大于等于1即可,所以当n=1时(快指针在环内走一圈后和慢指针相遇) 有x=z
,这说明了如果快指针在环内走一圈后和慢指针相遇那么x=z
,因此我们可以定义index1指向相遇点,index2指向头节点,index1和index2每次走1个节点直到index1和index2的值相等,此时index1指向的就是环形入口节点
当n>1时,x的值一定比z大,所以index1从相遇点需要多转几圈才会和index2重合,但是总归会相遇,所以我们不需要更改代码,在相遇前仍然让index1和index2每次跨过一个节点
代码
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode *detectCycle(struct ListNode *head) {
typedef struct ListNode ListNode;
//链表为空或者 只有一个节点一定无环
if ( head == NULL || head->next == NULL) return NULL;
ListNode* fast = head;
ListNode* slow = head;
//寻找快慢指针相遇点
ListNode* index1;
while (fast && fast->next)
{
fast = fast->next->next;
slow = slow->next;
if (fast == slow)
{
index1 = fast;
break;
}
}
//判断链表是否有环
if (fast != slow) return NULL;
ListNode* index2 = head;
int x = 0;//入口到头节点的距离
int y = 0;//相遇点到入口的距离
//找到环的入口点
while (index1 != index2)
{
index1 = index1->next;
index2 = index2->next;
}
return index1;
}