编程导航算法通关村第一关 | 环与双向链表

65 阅读3分钟

如何确定链表中有环

哈希

判断是否有环,最容易的方法是使用Hash,遍历的时候将元素放入到map中,如果有环一定会发生碰撞。发生碰撞的位置也就是入口的位置

public class Solution {
    public ListNode detectCycle(ListNode head) {
        ListNode pos = head;
        Set<ListNode> visited = new HashSet<ListNode>();
        while (pos != null) {
            if (visited.contains(pos)) {
                return pos;
            } else {
                visited.add(pos);
            }
            pos = pos.next;
        }
        return null;
    }
}

快慢指针

为什么两个快慢指针一定会相遇

确定是否有环,最有效的方法就是双指针,一个快指针(一次走两步),一个慢指针(一次走一步)。如果快的能到达表尾就不会有环,否则如果存在环,则慢指针一定会在某个位置与快指针相遇。这就像在操场长跑,一个人快一个人慢,只要时间够,快的一定能在某个时候再次追上慢的人(也就是所谓的套圈)。 这里很多人可能会有疑问,因为两者每次走的距离不一样,会不会快的人在追上慢的时候跳过去了导致两者不会相遇呢?

不会!如下图所示,当fast快要追上slow的时候,fast一定距离slow还有一个空格,或者两个空格,不会有其他情况。

  • 假如有一个空格,如上图情况1所示,fast和slow下一步都到了3号位置,因此就相遇了。
  • 假如有两个空格,如上图情况2所示,fast下一步到达3,而slow下一步到达4,这就变成了情况1了,因此只要有环,一定会相遇。

使用双指针思想寻找是否存在环的方法:

public boolean hasCycle(ListNode head) {
    if(head==null || head.next==null){
        return false; 
    }
    ListNode fast=head;
    ListNode slow=head;
    while(fast!=null && fast.next!=null){
        fast=fast.next.next;
        slow=slow.next;
        if(fast==slow)
            return true;
    }
    return false;
}

确定环的入口

当双指针相遇时,将 fast 指针重新放到 head,slow 位置保持不变,fast 和 slow 以相同的速度移动后再次相遇的位置便是环的入口

public class Solution {
    public ListNode detectCycle(ListNode head) {
        if(head == null) {
            return null;
        }
        ListNode slow = head, fast = head;
        while(fast != null) {
            slow = slow.next;
            if(fast.next != null) {
                fast = fast.next.next;
            }else {
                return null;
            }
            if(fast == slow) {
                ListNode prt = head;
                while(prt != slow) {
                    prt = prt.next;
                    slow = slow.next;
                }
                return prt;
            }
        }
        return null;
    }
}

双向链表

基本概念

双向链表节点:

class DoubleNode {
    public int data;    //数据域
    public DoubleNode next;    //指向下一个结点
    public DoubleNode prev;    // 指向上一个节点
    public DoubleNode(int data) {
        this.data = data;
    }
    //打印结点的数据域
    public void displayNode() {
        System.out.print("{" + data + "} ");
    }
}

双向链表构造:

public class DoublyLinkList {
    private DoubleNode first;
    private DoubleNode last;
    public DoublyLinkList() {
        first = null;
        last = first;
    }
    //从头部开始打印
    public void displayForward() {
        System.out.print("List(first--->last): ");
        DoubleNode current = first;
        while (current != null) {
            current.displayNode();
            current = current.next;
        }
        System.out.println();
    }

    //从尾部开始演绎
    public void displayBackward() {
        System.out.print("List(last--->first): ");
        DoubleNode current = last;
        while (current != null) {
            current.displayNode();
            current = current.prev;
        }
        System.out.println();
    }
}

插入元素

与单向链表一样,有三种插入方法:头插法、尾插法和中间插入

头插法

// 头插法
public void insertFirst(int data) {
    DoubleNode newDoubleNode = new DoubleNode(data);
    if (first == null) {
        last = newDoubleNode;
    }else {
        first.prev = newDoubleNode;
    }
    newDoubleNode.next = first;
    first = newDoubleNode;
}

尾插法

// 尾插法
public void insertLast(int data) {
    DoubleNode newDoubleNode = new DoubleNode(data);
    if (first == null) {
        first = newDoubleNode;
    }else {
        newDoubleNode.prev = last;
        last.next = newDoubleNode;
    }
    last = newDoubleNode;
}

中间插入

// 中间插入
public void insertAfter(int key, int data) {
    DoubleNode newDoubleNode = new DoubleNode(data);
    DoubleNode current = first;
    while ((current != null) && (current.data != key)) {
        current = current.next;
    }
    if (current == null) {
        if (first == null) {
            first = newDoubleNode;
            last = newDoubleNode;
        } else {
            last.next = newDoubleNode;
            newDoubleNode.prev = first;
            last = newDoubleNode;
        }
    } else {
        if (current == last) {
            newDoubleNode.next = null;
            last = newDoubleNode;
        }else {
            newDoubleNode.next = current.next;
            current.next.prev = newDoubleNode;
        }
        current.next = newDoubleNode;
        newDoubleNode.next = current;
    }
}

删除元素

删除首元素

// 删除首元素
public DoubleNode deleteFirst() {
    DoubleNode temp = first;
    if (first.next == null) {
        last = null;
    } else {
        first.next.prev = null;
    }
    first = first.next;
    return temp;
}

删除尾元素

// 删除尾元素
public DoubleNode deleteLast() {
    DoubleNode temp = last;
    if (first.next == null) {
        first = null;
    }else {
        last.prev.next = null;
    }
    last = last.prev;
    return temp;
}

删除中间节点

public DoubleNode deleteKey(int key) {
    DoubleNode current = first;
    //遍历链表寻找该值所在的结点
    while (current != null && current.data != key) {        
        current = current.next;
    }
    //若当前结点指向null则返回null,
    if (current == null) {                        
        return null;                       
    } else {
        //如果current是第一个结点
        if (current == first) {
            //则将first指向它,将该结点的previous指向null,其余不变
            first = current.next;            
            current.next.prev = null;
        } else if (current == last) { 
            //如果current是最后一个结点
            last = current.prev;        
            current.prev.next = null;   
        } else {
            //当前结点的上一个结点的next域应指向当前的下一个结点
            current.prev.next = current.next;
            //当前结点的下一个结点的previous域应指向当前结点的上一个结点
            current.next.prev = current.prev;    
        }
    }
    return current;        //返回
}