1 链表
插入的时候,单链表/双链表(先接后面)
往后移,前面的先移动,往前移动,后面的先移动。
与数组不同,我们无法在常量时间内访问单链表中的随机元素。
 如果我们想要获得第 i 个元素,我们必须从头结点逐个遍历。
我们按索引来访问元素平均要花费 O(N) 时间,其中 N 是链表的长度。
尽管它在通过索引访问数据时(与数组相比)具有如此糟糕的性能。
插入和删除操作,将了解到链表的好处。
- 在 O(1) 时间复杂度中将新结点插入到链表中,这非常高效。 
- 删除结点的时间复杂度将是 O(N)。空间复杂度为 O(1),因为我们只需要常量空间来存储指针。 
2 链表中的双指针
2.1 循环链表
给定一个链表,判断链表中是否有环
在链表中使用两个速度不同的指针时会遇到的情况:
如果没有环,快指针将停在链表的末尾。
如果有环,快指针最终将与慢指针相遇。
这两个指针的适当速度应该是多少?
一个安全的选择是每次移动慢指针一步,而移动快指针两步。每一次迭代,快速指针将额外移动一步。如果环的长度为 M,经过 M 次迭代后,快指针肯定会多绕环一周,并赶上慢指针。
public boolean hasCycle(ListNode head) {
    if (head == null || head.next == null) {
        return false;
    }
    ListNode slow = head;
    ListNode fast = head.next;
    while (slow != fast) {
        if (fast == null || fast.next == null) {
            return false;
        }
        slow = slow.next;
        fast = fast.next.next;
    }
    return true;
}方法2,哈希表
我们遍历所有结点并在哈希表中存储每个结点的引用(或内存地址)。如果当前结点为空结点 null(即已检测到链表尾部的下一个结点),那么我们已经遍历完整个链表,并且该链表不是环形链表。如果当前结点的引用已经存在于哈希表中,那么返回 true(即该链表为环形链表)
public boolean hasCycle(ListNode head) {
    Set<ListNode> nodesSeen = new HashSet<>();
    while (head != null) {
        if (nodesSeen.contains(head)) {
            return true;
        } else {
            nodesSeen.add(head);
        }
        head = head.next;
    }
    return false;
}2.2 循环链表2
给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
- 快指针1次走2步,慢指针1次走1步。所以快指针总是走了慢指针两倍的路。
- 回顾一下阶段1的过程,设头节点到入环点的路途为 n, 那么慢指针走了入环路途的一半(n/2)时,快指针就到达入环点了(走完n了)。
- 慢指针再继续走完剩下的一般入环路途(剩下的n/2),到达入环点时,快指针已经在环内又走了一个 n 那么远的路了。
- 为了方便理解,这里先讨论环很大,大于n的情况(其他情况后文补充)。此时,慢指针正处于入环点,快指针距离入环点的距离为n。环内路,可以用此时快指针的位置分割为两段,前面的 n 部分,和后面的 b 部分。
- 此时开始继续快慢指针跑圈,因为已经在环内了,他们其实就是在一条nbnbnbnbnbnbnb(无尽nb路)上跑步。
- 慢指针从入环处开始跑b步,距离入环处就剩下了n。此时,快指针则是从距离入环处n步远的位置开始跑了2b步,距离入环处也是剩下了n。他们相遇了,并且距离入环处的距离就是n,n就是头节点到入环点的距离阿!!! 后面的不用说了吧。
环很小的情况,其实跟环很大是一样的,比如你可以理解为将多个小环的循环铺开,虚拟扩展成一个大环来理解。

假设 节点到入环口长为 L,环长为C
这里简单讨论C>L的情况, 即一个大环(slow在走到入环口处,fast还没有遍历一遍环)
- slow到达入口处时,fast 在环 L%C 的位置(slow走L,fast走2L)
- 设 slow 继续行进 t 长 时,两者相遇, 此时有方程: (L%C+2t)%C = t%C
- 则 有 L%C + 2t = t+ nC —> L%C+2t = t+nC
- C>L, 取n=1,得 t = C - L%C, 相遇,L%C=L, 即 目前两者处在环 C-L处,再走 L 长便可到达 入环处
当C<L,即小环时,n的取值较大,即两者绕环次数多一些,结果还是不变。
public ListNode detectCycle(ListNode head) {
    if(head == null)
        return null;
    // 找到相遇点
    ListNode p1 = getIntersect(head);
    ListNode p2 = head;
    if(p1 == null)
        return null;
    // 头节点到入环处的距离 和 相遇点行走到 入环处的距离 相等
    while(p1 != p2){
        p1 = p1.next;
        p2 = p2.next;
    }
    return p1;
}3 模板
3.1 解决链表中的双指针
// 判断是否有环
while (slow != null && fast != null && fast.next != null) {
    slow = slow.next;           // move slow pointer one step each time
    fast = fast.next.next;      // move fast pointer two steps each time
    if (slow == fast) {         // change this condition to fit specific problem
        return true;
    }
}
// 寻找链表的 第一次相遇节点
private ListNode getIntersect(ListNode head) {
    ListNode slow = head, fast = head;
    while (fast!=null && fast.next!=null){
        slow = slow.next;
        fast = fast.next.next;
        if(slow == fast)
            return slow;
    }
    return null;
}3.2 列表反转
private ListNode reverseList(ListNode head) {
    ListNode prev = null;
    ListNode curr = head;
    while (curr != null) {
        ListNode nextTemp = curr.next;
        curr.next = prev;
        prev = curr;
        curr = nextTemp;
    }
    return prev;
}4 要善用哑变量,特别是在删除的时候
删除链表中等于给定值 val 的所有节点。
public ListNode removeElements(ListNode head, int val) {
    // 巧用 哑变量
    /*
        如果头或者前 n 个 都是要删除的变量, 这样操作比较麻烦
         */
    ListNode dummy = new ListNode(-1);
    dummy.next =  head;
    ListNode cur = dummy;
    while(cur.next!=null){
        if(cur.next.val == val){
            cur.next =  cur.next.next;
        }
        else
            cur = cur.next;
    }
    return dummy.next;
}5 递归的总结
删除链表中等于给定值 val 的所有节点。
class solution {
    public ListNode removeElements(ListNode head, int val) {
        /*
        和19题 有异曲同工之妙
        1. 递归的结束条件时什么【走到最深处】
        2. 子序列去递归【做什么】
        3. 递归的返值是什么【向上返回】
         */
        if( head == null)
            return null;
        head.next = removeElements(head.next, val);
        if(head.val == val)
            return head.next;
        return head;
        //return head->val == val ? head->next : head;
    }
} 
                     
                     
                        
                        