面试题: 检查链表是否存在环,并计算环的长度以及环的起点位置

 

/**
     * 检查链表是否存在环,并计算环的长度以及环的起点
     *
     * 判断一个链表是否存在环有一个简单的方法,就是使用一个快指针、和一个慢指针,快指针每次走两步,慢指针每次走一步,
     * 则如果有环,它们最后必然会相遇的。
     * 本题的难点在于要找出环的起点。其实也不难,与判断是否有环类似,用两个步长分别为1和2的指针遍历链表,直到两者相遇,此时慢指针走过的长度就是环的长度。
     * 另外相遇后把其中指针重新设定为起始点,让两个指针以步长1再走一遍链表,相遇点就是环的起始点。
     *
     * 证明也很简单,证明如下:
     *
     * 我们注意到第一次相遇时
     *
     * 慢指针走过的路程S1 = 非环部分长度 + m * 环长 + 弧A长 (m:环的圈数)
     *
     * 快指针走过的路程S2 = 非环部分长度 + n * 环长 + 弧A长 (n:环的圈数)
     *
     *
     * S1 * 2 = S2,可得:非环部分长度 + 弧A长 = (n-2m) * 环长
     * m,n为整数  非环部分长度 + 弧A长最小值为环长的一倍
     * 让指针A回到起始点后,走过一个非环部分长度,指针B走过了相等的长度,也就是n-2m * 环长 - 弧A长,正好回到环的开头。
     * 
     */

    /**
	 * 链表结构
	 */
	public static class ListNode {
		// next指针
		ListNode next;
		// value值
		int value;

		// 构造方法
		public ListNode(int val) {
			next = null;
			value = val;
		}
	}

	/**
	 * 检查链表是否存在环,并计算环的长度以及环的起点
	 *
	 * 判断一个链表是否存在环有一个简单的方法,就是使用一个快指针、和一个慢指针,快指针每次走两步,慢指针每次走一步,
	 * 则如果有环,它们最后必然会相遇的。
	 * 本题的难点在于要找出环的起点。其实也不难,与判断是否有环类似,用两个步长分别为1和2的指针遍历链表,直到两者相遇,此时慢指针走过的长度就是环的长度。
	 * 另外相遇后把其中指针重新设定为起始点,让两个指针以步长1再走一遍链表,相遇点就是环的起始点。
	 *
	 * 证明也很简单,证明如下:
	 *
	 * 我们注意到第一次相遇时
	 *
	 * 慢指针走过的路程S1 = 非环部分长度 + m * 环长 + 弧A长 (m:环的圈数)
	 *
	 * 快指针走过的路程S2 = 非环部分长度 + n * 环长 + 弧A长 (n:环的圈数)
	 *
	 *
	 * S1 * 2 = S2,可得 非环部分长度 = (n-2m) * 环长 - 弧A长
	 *
	 * 让指针A回到起始点后,走过一个非环部分长度,指针B走过了相等的长度,也就是n-2m * 环长 - 弧A长,正好回到环的开头。
	 * @param args
	 */
	public static void main(String[] args) {
		ListNode head = new ListNode(0);
		ListNode node1 = new ListNode(1);
		ListNode node2 = new ListNode(2);
		ListNode node3 = new ListNode(3);
		ListNode node4 = new ListNode(4);
		ListNode node5 = new ListNode(2);
		ListNode node6 = new ListNode(3);
		ListNode node7 = new ListNode(4);
		head.next = node1;
		node1.next = node2;
		node2.next = node3;
		node3.next = node4;
		node4.next = node5;
		node5.next = node6;
		node6.next = node7;
		// 指向node4
		node7.next = node4;

		// 环是否存在
		boolean circle = false;

		// 使用快慢指针
		ListNode faster = head.next.next;
		ListNode slower = head.next;
		while (faster != null && slower != null) {
			if (faster.equals(slower)) {
				System.out.println("circle is exist");
				circle = true;
				break;
			}

			// 防止没有环到最后了
			if (faster.next == null) {
				System.out.println("circle is not exist");
				break;
			}

			faster = faster.next.next;
			slower = slower.next;
		}

		// 求环的长度:
		// 当两个相遇时,慢指针继续向前移动,faster指针不动,
		// 慢指针移动移动长度就是环的长度
		if (circle) {
			// 当存在环,
			int count = 0;
			slower = slower.next;
			while (slower != null) {
				count ++;
				if (faster.equals(slower)) {
					System.out.println("circle length:" + count);
					break;
				}
				slower = slower.next;
			}
		}

		// 求环的开始节点
		// 慢指针继续回到初始点,让两个指针以步长1再走一遍链表,相遇点就是环的起始点
		if (circle) {
			// 当存在环,
			slower = head;
			while (slower != null) {
				if (faster.equals(slower)) {
					System.out.println("circle start Node:" + slower.value);
					break;
				}
				slower = slower.next;
				faster = faster.next;
			}
		}
	}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值