文章目录
FastSlowPointer
快慢指针概念
快慢指针中的快慢指的是移动的步长,即每次向前移动速度的快慢。 例如,可以让快指针每次沿链表向前移动2,慢指针每次向前移动1次。
经典应用
- 判断单链表是否为循环链表
思想: 当链表中存在环时,快指针与慢指针一定会在某一个结点处相遇,就像在操场跑步,快的人将会追上慢的人,即他们一定会相遇。
public class ListNode<T> {
T val;
ListNode<T> next;
ListNode(T t) {
val = t;
next = null;
}
}
public class ListUtil {
/** 形成链表 */
public static<T> ListNode<T> getList(T[] ts) {
ListNode<T> head = null;
ListNode<T> temp = null;
for (T t : ts) {
ListNode<T> tmp = new ListNode<>(t);
if (temp == null) {
temp = tmp;
head = tmp;
}
temp.next = tmp;
temp = tmp;
}
return head;
}
/** 形成指定位置的环形链表 */
public static<T> void getList(ListNode<T> head, int index) {
ListNode<T> temp = head;
while (temp.next != null) temp = temp.next;
getList(head, temp, index);
}
/** 成环 */
private static<T> void getList(ListNode<T> head, ListNode<T> end, int index) {
ListNode<T> tmp = head;
while (index-- > 0) tmp = tmp.next;
end.next = tmp;
}
/** 打印无环链表 */
public static<T> void toString(ListNode<T> head) {
ListNode<T> tmp = head;
while (tmp != null) {
System.out.print(tmp.val + " ");
tmp = tmp.next;
}
System.out.println();
}
/** 打印有环链表 */
public static<T> void toString(ListNode<T> head, ListNode<T> startLoop) {
ListNode<T> tmp = head;
int count = 0;
while (count != 2) {
if (tmp == startLoop) count++;
if (count != 2) System.out.print(tmp.val + " ");
tmp = tmp.next;
}
System.out.println();
}
}
public class FastSlowPointer<T> {
/** 环的入口节点 */
private ListNode<T> startLoop;
/** 判断是否有环 */
public boolean judge(ListNode<T> head) {
return meetNode(head) != null;
}
/** 得到相遇节点 */
public ListNode<T> meetNode(ListNode<T> head) {
ListNode<T> before = head;
ListNode<T> after = head;
do {
if (before == null || before.next == null) return null;
before = before.next.next;
after = after.next;
}while (before != after);
return after;
}
/** 判断环的入口位置 */
public int judgeStart(ListNode<T> head, ListNode<T> meet) {
ListNode<T> temp = head;
int count = 0;
while (temp != meet) {
if (temp == null || meet == null) return -1;
temp = temp.next;
meet = meet.next;
count++;
}
startLoop = temp;
return count;
}
/** 得到环的入口 */
@SuppressWarnings("unused")
public ListNode<T> judgeStartNode(ListNode<T> head, ListNode<T> meet) {
int ignore = judgeStart(head, meet);
return startLoop;
}
/** 中位数 */
public double media(ListNode<T> head) {
ListNode<T> before = head;
ListNode<T> after = head;
while (true) {
before = before.next.next;
if (before == null)
return (Double.parseDouble(String.valueOf(after.val)) +
Double.parseDouble(String.valueOf(after.next.val))) / 2;
after = after.next;
if (before.next == null) return Double.parseDouble(String.valueOf(after.val));
}
}
/** 中位数, 使用此方法时不再限制有序列表中数值的类型,该方法的正确性将由调用者来保证。*/
@SuppressWarnings("rawtypes")
public double mediaAny(ListNode head) {
ListNode before = head;
ListNode after = head;
while (true) {
before = before.next.next;
if (before == null)
return (Double.parseDouble(String.valueOf(after.val)) +
Double.parseDouble(String.valueOf(after.next.val))) / 2;
after = after.next;
if (before.next == null) return Double.parseDouble(String.valueOf(after.val));
}
}
/** 判断双链表是否相交 */
public boolean intersect(ListNode<T> before, ListNode<T> after) {
ListNode<T> be = meetNode(before);
ListNode<T> af = meetNode(after);
if (be == null && af == null) return acyclic(before, after) != null;
if (be != null && af == null) return false;
if (be == null) return false;
return dualBoolean(be, af);
}
/** 得到双链表的相交点 */
public ListNode<T> intersectMeetCode(ListNode<T> before, ListNode<T> after) {
ListNode<T> be = judgeStartNode(before, meetNode(before));
ListNode<T> af = judgeStartNode(after, meetNode(after));
if (be == null && af == null) return acyclic(before, after);
if (be != null && af == null) return null;
if (be == null) return null;
return dual(before, after, be, af);
}
/** 得到无环双链表的相交点 */
private ListNode<T> acyclic(ListNode<T> before, ListNode<T> after) {
ListNode<T> af = after;
while (af.next != null) af = af.next;
af.next = before;
ListNode<T> res = judgeStartNode(after, meetNode(after));
af.next = null;
return res;
}
/** 仅对双环链表是否相交进行判定 */
private boolean dualBoolean(ListNode<T> before, ListNode<T> after) {
if (before == after) return true;
ListNode<T> temp = before;
do {
temp = temp.next;
if (temp == after) return true;
}while (temp != before);
return false;
}
/** 得到双环链表的相交点 */
private ListNode<T> dual(ListNode<T> before, ListNode<T> after, ListNode<T> be, ListNode<T> af) {
if (be == af) return dual0(before, after, af);
ListNode<T> temp = be;
do {
temp = temp.next;
if (temp == af) return temp;
}while (temp != be);
return null;
}
/** 双环链表相交点 */
private ListNode<T> dual0(ListNode<T> before, ListNode<T> after, ListNode<T> af) {
ListNode<T> temp = af.next;
af.next = before;
ListNode<T> res = judgeStartNode(after, meetNode(after));
af.next = temp;
return res;
}
}
- 如果链表为存在环,找到环的入口点
- 判断是否存在环
- 获取快慢指针相遇的节点
- 头结点和相遇节点同时以步长1进行,相遇时得到环的入口
代码如上
思想: 假设非环部分长度为a、环的起点到相遇点长度为b、相遇点到环的起点长度为c、慢指针行走长度为s、快指针行走长度为2s、环的长度为r = b + c
当快慢指针相遇时,快指针比慢指针多出n圈环的长度,即2s - s = n * r
s = a + b
s = n * r = n * (b+c)
∴ a + b = n * r
∴ a = n * r - b = n * r - (r - c) = (n - 1) * r + c
∴ a = (n - 1) * r + c
所以,当在起点和相遇点各设置一个指针,它们每次只走一步,相遇时在环的入口处。
由此可见,该算法的主要影响因素是n的值,如何降低n的值,提高效率?
- 在有序链表中寻找中位数
中位数(Median)是指按顺序排列的一组数据中居于中间位置的数。
代码如上
思想: 快指针走2步,慢指针走1步;当链表长度为奇数时,当快指针到达尾部,则慢指针正好位于中位数处;
当链表长度为偶数时,当快指针到达尾部后变成null,此时慢指针正好处于用于计算中位数的两个数的前一个,此时慢指针不能移动,而是要计算中位数;
中位数为慢指针当前数值和下一个节点的数值的平均数。
泛型问题: 由于泛型不可判断类型,则在计算数值时,应当借由String类型完成转换,以最高的Double类型接收并完成计算 - 判断两个单链表是否相交
代码如上
思想: 分为三种情况。
①两个链表均为无环单链表。
②其中一个链表有环,另一个无环,则不相交。
③两个链表均为有环。求出两个环的入口节点,比较两个节点,若地址一致,则相交;若不一致,则以一个节点为起点,遍历一圈如果遇到另一个节点,则相交,否则不相交。
如果要求相交的节点,则第①种情况,可以将一个链表的尾部连接另一个链表的头部,形成带有环的一个单链表,求出入环点即可。
如果是第③种情况,若两个入口节点不相同,则寻找到的位于后面的入口即为交点;若入口节点相同,则以入口点为尾部,转化为第①种情况。 - 证明快慢指针相遇
假设: 链表环的入口结点为k,环的长度为n,fast步长为2、slow步长为1。
当slow到达k结点时,fast走过2k的距离,二者之间相差为x
∴x =(2k - k) % n = k % n
此时,假设slow不动,则fast再经过m个结点与slow相遇;但是slow每次向前移动一步,则需要m次移动,fast即可追上slow相遇。
∴m = n - x = n - k % n
∴当二者相遇时,slow位于k + m处,则fast也应当位于k + m处
∴k + (x + 2m) % n = k + (2n - x) % n = k + (2n - k % n) % n = k + (n + m) % n = k + m % n
∵m < n
∴m % n = m
∴k + (x + 2m) % n = k + m % n = k + m
∴fast和slow必定相遇
假设: fast步长为a、slow步长为1,其他一致
∴x =(ak - k) % n = (a - 1)k % n
∴m = n - x = n - (a - 1)k % n
∴k + (x + am) % n = k + (an - (a - 1)x) % n = k + (n + (a - 1)m) % n = k + (a - 1)m % n
∴k + (x + am) % n = k + (a - 1)m
∴a = 2
假设: fast步长为a、slow步长为b,其他一致
∴x =(ak - k) % n = (a/b - 1)k % n
∴m = n - x = n - (a/b - 1)k % n
∴k + (x + (a/b)m) % n = k + (a - 1)m % n
∴k + (x + am) % n = k + (a/b - 1)m
∴a = b^2 + b
END