快慢指针经典应用

文章目录

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值