(1)解决链表环的问题还是简单,那么怎么出现链表环的呢?
我暂时只知道JDK一个,Java1.7以及之前的HashMap,为了解决hash冲突,所以数组(table)那使用了单链表(Entry有一个next成员),但是插入链表的方法是头插法,就是插入链表头,高并发下就会造成一个链表环的问题,具体形成可以另外查询。
void addEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex]; // 先暂存index的e
table[bucketIndex] = new Entry<K,V>(hash, key, value, e); // 就是这里,直接构造方法放入e
if (size++ >= threshold)
resize(2 * table.length);
}
static class Entry<K,V> implements Map.Entry<K,V> {
final K key;
V value;
Entry<K,V> next;
final int hash;
Entry(int h, K k, V v, Entry<K,V> n) {//构造方法这里是直接next赋值放入的e,是头插法
value = v;
next = n;
key = k;
hash = h;
}
...................
后来Java8就改了,使用尾插法,而且超过8个元素,链表改成红黑树,高并发下至少不会死循环,不过会查出null。
/**
* The bin count threshold for using a tree rather than list for a
* bin. Bins are converted to trees when adding an element to a
* bin with at least this many nodes. The value must be greater
* than 2 and should be at least 8 to mesh with assumptions in
* tree removal about conversion back to plain bins upon
* shrinkage.
*/
static final int TREEIFY_THRESHOLD = 8;
(2)问题 I 检验是否存在链表环
难度easy
通常解决方法也简单,就是用快慢指针(fast & slow pointer)。
输入输出样例:
Input: head = [3,2,0,-4], pos = 1 // pos是最后一个节点next指向的数组位置,如果pos = -1,那么就没有环,pos只是当作说明,不会真正输入。
Output: true // true表示验证有链表环。
我自己代码:
public class Solution {
public boolean hasCycle(ListNode head) {
if(head == null || head.next == null) return false;
ListNode runner01 = head; // 慢
ListNode runner02 = head; // 快
while(runner02.next != null && runner02.next.next != null) {
runner01 = runner01.next;
runner02 = runner02.next.next;
if(runner01.val == runner02.val) return true; // 重复就存在链表环
}
return false;
}
}
(3)问题 II 如果存在链表环,返回链表环的开始节点
难度easy
输入输出样例:
Input: head = [3,2,0,-4], pos = 1
Output: tail connects to node index 1 // 发现链表环开始位置为index = 1
我自己代码:
public class Solution {
public ListNode detectCycle(ListNode head) {
if(head == null || head.next == null) return null;
ListNode runner01 = head; // 慢
ListNode runner02 = head; // 快
while(true) {
// 如果快指针发现null,就代表没有链表环
if(runner02.next == null || runner02.next.next == null) return null;
runner01 = runner01.next;
runner02 = runner02.next.next;
if(runner01 == runner02) break; // 发现链表环
}
runner02 = head; // 后续步骤是发现链表环,让快指针重新跑回开头
while(runner01 != runner02) {
runner01 = runner01.next;
runner02 = runner02.next; // 一步一步验证
}
return runner02;
}
}
找到链表环开始位置的原理:
快慢指针从S开始移动,在S''相遇,可以得出
设快指针(fast)路程X1,慢指针(slow)路程X2得
X1 = m(B + C) + A + B // 假设 快(fast) 跑了m圈
X2 = n(B + C) + A + B // 假设 慢(slow) 跑了n圈
(1)X1 = 2 * X2 // 快 是 慢 2倍
(2)m(B + C) + A + B = 2 * (n(B + C) + A + B) // X1 和 X2 代入(1)
然后(2)相消得
A + B = (m - 2n)(B + C)
A = (m - 2n - 1)(B + C) + B + C - B // 提取一个B + C出来
A = (m - 2n - 1)(B + C) + C
而B + C是一个圆周的长度(周长),跑一圈回到开始,
所以可以理解为,慢(slow)从S''跑了 C 与重新在起点快(fast) 跑了 A 之后相遇,
所以相遇点刚好是环的开始位置