目录
1.1为什么slow走一步,fast走两步,他们一定会在环里面相遇?会不会永远追不上?请证明
1.2 slow走一步,fast走3步?走4步?走n步行不行?请证明
2.给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 NULL
*(再做)3.给定一个链表,每个节点包含一个额外增加的随机指针,该指针可以指向链表中的任何节点或空节点。
5.删除链表中的重复的结点,重复的结点不保留,返回链表头指针
1.给定一个链表,判断链表中是否有环。
快慢指针,即慢指针一次走一步,快指针一次走两步,两个指针从链表其实位置开始运行,如果链表 带环则一定会在环中相遇,否则快指针率先走到链表的末尾
bool hasCycle(struct ListNode *head) {
struct ListNode*slow = head,*fast = head;
while(fast && fast->next)
{
slow = slow->next;
fast = fast->next->next;
if(slow == fast)
{
return true;
}
}
return false;
}
面试的时候可能出现的扩展问题:
1.1为什么slow走一步,fast走两步,他们一定会在环里面相遇?会不会永远追不上?请证明
假设slow进环的时候,fast和slow的距离是N。追击的过程中,fast往前走两步,slow走一步,他们每走一次,他们之间的距离缩小1。二者之间的距离缩小到0的时候相遇
1.2 slow走一步,fast走3步?走4步?走n步行不行?请证明
fast走三步:假设slow进环的时候,fast和slow的距离是N。追击的过程中,fast往前走三步,slow走一步,他们每走一次,他们之间的距离缩小2。N为偶数时可以相遇,N为奇数时差距变为-1,即C-1(C是环的长度,如果C-1也恰好是奇数,那么就永远追不上)
总结,如果slow进环时,slow和fast的差距N是奇数,且环的长度是偶数,那么他们两个在环里面一直打圈,追不上;(下图即为追不上的情况)
1.3求环的入口点
假设起点到入口点的距离是L,假设相遇点到入口点的距离是X。假设环的大小是C
慢指针走的路程是:L+X(不可能为L+X+N*C,因为快慢指针的差距没有环大,slow进环以后在一圈之内fast就会追上slow,所以slow走的距离就是L+X)
快指针走的路程是:L+X+C
错误推论:
2(L+X) = L+C 即 L+X = C 即L = C-X
如果L很长,环很小,那么slow指针进环时,假设快指针已经转了N圈了
2(L+X) = L + N*C + X
L = N*C - X
结论:一个指针从相遇点走,一个指针从起点走,他们会在入口相遇
2.给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 NULL
struct ListNode *detectCycle(struct ListNode *head) {
struct ListNode*slow = head,*fast = head;
while(fast && fast->next)
{
slow = slow->next;
fast = fast->next->next;
if(slow == fast)
{
//相遇
struct ListNode*meet = fast;
//通过推论证明,一个指针从head走
//一个指针从meet走,他们会在入口点相遇
while(meet != head)
{
meet = meet->next;
head = head->next;
}
return meet;
}
}
return NULL;
}
思路二:假设slow和fast在环中某处相遇,把环从相遇点断开,转换成相交问题求交点
*(再做)3.给定一个链表,每个节点包含一个额外增加的随机指针,该指针可以指向链表中的任何节点或空节点。
要求返回这个链表的深度拷贝。
难点:拷贝结点的random不好处理,如果直接处理效率太低,太复杂
1. 拷贝以后,新链表需要确定每个random,需要去原有链表中去查找,是o(N),每个random都要处理就是o(N*N),效率就下来了
2.如果链表中存在结点的值相同,那么就更复杂了。列如有多个结点的值是7,那么原链表random指向7的结点,那么还要确定是第几个值为7的结点
1. 把拷贝的结点链接在原结点后面,建立链接关系
//1.拷贝结点挂在原结点的后面,建立对应关系
struct Node* cur = head;
while(cur)
{
struct Node*next = cur->next;
struct Node* copy = (struct Node*)malloc(sizeof(struct Node));
copy ->val = cur->val;
cur->next = copy;
copy->next = next;
copy = next;
}
2.处理拷结点的random
//2.处理copy结点的random
cur = head;
while(cur)
{
struct Node*copy = cur->next;
if(cur->random == NULL)
{
copy->random = NULL;
}
else
{
copy->random = cur->random->next;//copy->random等于cur->random的拷贝结点
}
cur = copy->next;
}
3.把拷贝结点解下来,链接在一起
//3.拷贝结点取下来链接到一起,恢复原链表
cur = head;//每次cur从头开始走
struct Node*copyHead,*copyTail;
copyHead = copyTail = (struct Node*)malloc(sizeof(struct Node));//建立哨兵结点
while(cur)
{
struct Node*copy = cur->next;
struct Node*next = copy->next;
//尾插
copyTail->next = copy;
copyTail = copyTail->next;
cur->next = next;
cur = next;
}
struct Node*guard = copyHead;
copyHead = copyHead->next;
free(guard);
return head;
struct Node* copyRandomList(struct Node* head) {
if(head == NULL)
return NULL;
//1.拷贝结点挂在原结点的后面,建立对应关系
struct Node* cur = head;
while(cur)
{
struct Node*next = cur->next;
struct Node* copy = (struct Node*)malloc(sizeof(struct Node));
copy ->val = cur->val;
cur->next = copy;
copy->next = next;
copy = next;
}
//2.处理copy结点的random
cur = head;
while(cur)
{
struct Node*copy = cur->next;
if(cur->random == NULL)
{
copy->random = NULL;
}
else
{
copy->random = cur->random->next;//copy->random等于cur->random的拷贝结点
}
cur = copy->next;
}
//3.拷贝结点取下来链接到一起,恢复原链表
cur = head;//每次cur从头开始走
struct Node* copyHead,*copyTail;
copyHead = copyTail = (struct Node*)malloc(sizeof(struct Node));//建立哨兵结点
while(cur)
{
struct Node*copy = cur->next;
struct Node*next = copy->next;
//尾插
copyTail->next = copy;
copyTail = copyTail->next;
cur->next = next;
cur = next;
}
struct Node*guard = copyHead;
copyHead = copyHead->next;
free(guard);
return copyHead;
}
4.对链表进行插入排序
struct ListNode* insertionSortList(struct ListNode* head){
if(head == NULL || head->next == NULL)
return head;
//1.初始条件
struct ListNode* sortHead = head;
struct ListNode*cur = head->next;
sortHead->next = NULL;
while(cur)//2.终止条件
{
//3、迭代条件
struct ListNode*next = cur->next;
//将cur结点插入到前面的有序区间
struct ListNode*p = NULL,*c = sortHead;
while(c)
{
if(cur->val < c->val)
{
break;
}
else
{
p = c;
c = c->next;
}
}
if(p == NULL)
{
cur->next = c;
sortHead = cur;
}
else
{
p->next = cur;
cur->next = c;
}
cur = next;
}
return sortHead;
}
5.删除链表中的重复的结点,重复的结点不保留,返回链表头指针
光有cur和next指针不行,还需添加一个prev前驱指针
下面进行针对性分析:头尾都有连续数据相同的特殊场景
此时prev是空指针,无法指向cur,应添加一个头指针,或者将头指针指向cur的位置(下面采取第二种方法)
class Solution {
public:
/**
*
* @param head ListNode类
* @return ListNode类
*/
ListNode* deleteDuplication(ListNode* phead) {
// write code here
if (pHead == NULL || pHead->next == NULL)
return pHead;
//起始条件
struct ListNode* prev = NULL, *cur = pHead, * next = pHead->next;
while (next)
{
if (cur->val == next->val)
{
//删除重复
while (next && cur->val == next->val)//next往后走,找到和cur不相等的位置
{
next = next->next;
}
//删掉cur到next之间的结点
while (cur != next)
{
struct ListNode* del = cur;
cur = cur->next;
free(del);
}
if (prev == NULL)\
{
pHead = cur;
}
else
{
prev->next = cur;
}
if (next)
next = next->next;
}
else
{
prev = cur;
cur = next;
next = next->next;
}
}
return pHead;
}
};