-
快慢指针
-
字面意思就是步长一个快一个慢的两个指针, 最开始引申出快慢指针应该是用来解决链表中的环路问题。
假如一个有环路的链表,以步长为2和步长为1的两个指针,一直走下去一定会有相遇的时刻。
假如上面这个有环路的链表,试试就发现,两个指针会有相遇的时候。这时候fast走了2X步,则slow走了X步,且他们相遇了。这说明fast多走了X步,X是整数倍的环路,假如环路周长为C,说明 X%C=0。
我们要找到的是环路的入口,现在快慢指针相遇的地方不一定是入口。我们假设入口位置是 Y 步的位置。
现在slow指针以及走了 X 步,X 步包含从 0-Y 的部分是在环外,而X-Y的部分是在环内。由于X%C=0,所以slow指针再走Y步则能到达入口位置。而我们这时选一个新指针从头开始走,也是走Y步即可到达入口位置,所以这两个指针相遇的位置即为环路入口位置。
-
-
例题1:寻找环形列表的环的入口节点(Leetcode#142)
这题目描述有点乱七八糟,反正就是一个链表,给你头结点head,若有环路则找出入口处节点指针返回,如果没有则返回NULL。
使用快慢指针来解决这个题目:/**最常见的链表结构 * Definition for singly-linked list. * struct ListNode { * int val; * ListNode *next; * ListNode(int x) : val(x), next(NULL) {} * }; */ class Solution { public: ListNode *detectCycle(ListNode *head) { if(head == nullptr) return nullptr; ListNode* fast = head, slow = head; //先定义slow、fast两个指针指向head do{ //快慢指针一直往后跑,直到两个相等; if(fast->next) //如果快指针跑到没地方跑了,则无环路,返回NULL退出。 { fast = fast->next; if(fast->next) fast = fast->next; else return NULL; }else return NULL; if(slow->next) slow = slow->next; } while(fast != slow); ListNode* finder = head; //这里finder指针从头开始,直到和slow相遇。 //注意:上面是do...while,这里可不能直接复制,这里有可能一开始就是入口! while(finder != slow){ slow = slow->next; finder = finder->next; } //相遇时finder则就是入口位置 return finder; } };
-
例题2:寻找重复数(Leetcode#287)
这道题一上来看起来与链表环路没关系啊?可是仔细分析:数组长度1+n,数字在1-n之间,假如我们把位置 i 的数字 nums[i] 当做链表下一个位置,那么如果有重复数字则一定会形成环路。环路的入口就是这个数字。
假如{1,3,3,2,4}这个例子,重复的“3”就是环路的入口,在存储“3”的两个1,2位置其实意义表示的是同一个节点,指向了3号位置。所以快慢指针相遇的时候,slow指向了第一个3,fast指向了第二个3,这不是bug。class Solution { public: int findDuplicate(vector<int>& nums) { int slow = 0; int fast = 0; do { slow = nums[slow]; fast = nums[fast]; fast = nums[fast]; } while(slow != fast); int finder = 0; while(slow != finder) { slow = nums[slow]; finder = nums[finder]; } return finder; } };