跟着左神学算法:P5 链表

102 阅读1分钟

P5 链表

哈希表和有序表

-数据结构操作时间复杂度
哈希表HashSet、HashMapO(1)
有序表TreeSet、TreeMapO(logN)

单向链表和双向链表

// 单向链表
Class Node{
    int v;
    Node next;
}
// 双向链表
Class Node2{
    int v;
    Node2 pre;
    Node2 next;
}

反转单向链表

public Node reserveList(Node node) {
    if(node == null || node.next == null) {
        return node;
    }
    Node pre = null;
    Node current = node;
    Node next = null;
    while(current != null) {
        next = current.next;
        current.next = pre;
        pre = current;
        current = next;
    }
    return pre;
}

打印两个有序链表的公共部分

public void printCommon(Node n1, Node n2) {
    Node p1 = n1;
    Node p2 = n2;
    while (p1 != null && p2 != null) {
        if(p1.v == p2.v) {
            System.out.println(p1.v);
            p1 = p1.next;
            p2 = p2.next;
        } else if(p1.v < p2.v) {
            p1 = p1.next;
        } else {
            p2 = p2.next;
        }
    }
}

链表解题技巧

快慢指针和额外数据结构

链表笔试技巧

尽可能优化时间复杂度,空间复杂度过了就行

链表面试技巧

保证时间复杂度,尽可能优化空间复杂度

判断一个链表是否是回文

可以实现的方案:

  1. 把链表全部压栈,然后出栈比较
  2. 快慢指针,等到快指针到链表结束的时候,慢指针开始压栈,然后出栈比较
  3. 快慢指针,然后慢指针修改链表方向,再从两侧开始往中间比较,比较的过程中还原链表
    // 方案1
    public boolean isPalindrome1(Node node) {
        if (node == null || node.next == null) {
            return true;
        }
        Stack<Node> stack = new Stack<>();
        Node p = node;
        while (p != null) {
            stack.push(p);
            p = p.next;
        }
        p = node;
        boolean res = true;
        while (!stack.isEmpty()) {
            if (stack.pop().v != p.v) {
                res = false;
                break;
            }
            p = p.next;
        }
        return res;
    }
    // 方案2
    public boolean isPalindrome2(Node node) {
        if (node == null || node.next == null) {
            return true;
        }
        Node fast = node;
        Node slow = node;
        // 压栈的位置
        Node push = null;

        while (fast != null) {
            if (fast.next == null) {
                push = slow;
                break;
            }
            fast = fast.next.next;
            slow = slow.next;
        }
        // 根据链表单双处理一下入栈位置
        push = push == null ? slow : push;
        Stack<Node> stack = new Stack<>();
        while (push != null) {
            stack.push(push);
            push = push.next;
        }

        boolean res = true;
        while (!stack.isEmpty()) {
            if (stack.pop().v != node.v) {
                res = false;
                break;
            }
            node = node.next;
        }
        return res;
    }
    public boolean isPalindrome3(Node node) {
        if (node == null || node.next == null) {
            return true;
        }

        Node fast = node;
        Node slow = node;
        // 压栈的位置
        Node push = null;
        // slow 前一个节点
        Node pre = null;
        while (fast != null) {
            // 奇数个节点
            if (fast.next == null) {
                push = slow.next;
                pre = slow;
                break;
            }
            pre = slow;
            fast = fast.next.next;
            slow = slow.next;
        }

        // 偶数个节点下设置反转位置
        if (push == null) {
            push = slow;
        }
        // 把 push 位置链表反向
        Node current = push;
        Node next = null;
        while (current != null) {
            next = current.next;
            current.next = pre;
            pre = current;
            current = next;
        }
        // 最后一个节点的位置
        Node tail = pre;
        Node head = node;
        // 恢复链表的指针
        Node used = null;
        Node usedNext = null;
        boolean res = true;
        // 如果链表有环说明链表没恢复
        while (push.next != null && push.next.next == push) {
            if (head.v != tail.v) {
                res = false;
                break;
            }
            head = head.next;
            // 恢复链表
            used = tail;
            tail = tail.next;
            used.next = usedNext;
            usedNext = used;
        }
        if (push.next != null && push.next.next == push) {
            used = tail;
            tail = tail.next;
            head = head.next;
            used.next = usedNext;
            usedNext = used;
        }
        return res;
    }

把单向链表按照某值划分成左边小、中间相等、右边大的形式

额外要求:

  1. 保证排序稳定性
  2. 时间复杂度O(N), 空间复杂度O(1)

可以实现的方案:

  1. 把 Node 放进数组,做 partition
  2. 把链表拆分成 3 个链表,再连接起来
    // 方案2
    public Node sortByK(Node node, int k) {
        if (node == null || node.next == null) {
            return node;
        }
        Node lessKHead = null;
        Node lessKTail = null;
        Node equalKHead = null;
        Node equalKTail = null;
        Node moreKHead = null;
        Node moreKTail = null;

        while (node != null) {
            if (node.v == k) {
                if (equalKHead == null) {
                    equalKHead = node;
                } else {
                    equalKTail.next = node;
                }
                equalKTail = node;
            } else if (node.v < k) {
                if (lessKHead == null) {
                    lessKHead = node;
                } else {
                    lessKTail.next = node;
                }
                lessKTail = node;
            } else {
                if (moreKHead == null) {
                    moreKHead = node;
                } else {
                    moreKTail.next = node;
                }
                moreKTail = node;
            }
            node = node.next;
        }
        Node head = null;
        Node tail = null;
        if (lessKHead != null) {
            head = lessKHead;
            tail = lessKTail;
        }
        if (equalKHead != null) {
            if (head == null) {
                head = equalKHead;
            } else {
                tail.next = equalKHead;
            }
            tail = equalKTail;
        }
        if (moreKHead != null) {
            if (head == null) {
                head = moreKHead;
            } else {
                tail.next = moreKHead;
            }
            tail = moreKTail;
        }
        return head;
    }

复制含有随机指针节点的链表

链表结构如下:

class RNode{
    int v;
    RNode next;
    RNode rand;
    public RNode(int v, RNode next, RNode rand) {
        this.v = v;
        this.next = next;
        this.rand = rand;
    }
}

rand 指针可能指向链表中任意一个节点,也可能指向 null ,给定一个由RNode节点构成的无环单链表 head,实现链表的复制。
额外要求:时间复杂度 O(N),空间复杂度 O(1)

可以实现的方案:

  1. 哈希表,空间复杂度 O(N)
  2. 把复制节点先放在原来链表后面,然后完成 rand 指针复制
    public RNode copyList(RNode head) {
        if (head == null) {
            return null;
        }
        RNode p = head;
        while (p != null) {
            RNode copy = new RNode(p.v, p.next, null);
            p.next = copy;
            p = p.next.next;
        }
        p = head;
        while (p != null) {
            p.next.rand = (p.rand == null) ? null : p.rand.next;
            p = p.next.next;
        }
        RNode res = head.next;
        p = head;
        RNode copy = p.next;
        while (copy != null) {
            p.next = copy.next;
            p = p.next;
            copy.next = p == null ? null : p.next;
            copy = copy.next;
        }
        return res;
    }

两个单链表相交的问题

给定两个单链表(可能有环),头节点是 head1,head2,实现一个函数,如果两个链表相交,返回相交的第一个节点,否则返回 null
额外要求:如果两个链表长度之和是 N,时间复杂度 O(N),空间复杂度 O(1)

解决方案:

  1. 先检查两个链表,如果两个链表都是无环的,那么遍历两个链表,长度记为 l1 和 l2,较长的链表从头节点开始移动至两个链表剩余长度相同,然后两个链表一边遍历一边比较
  2. 如果两个链表都是有环的,如果入环节点相同,那么就是相交的,可以把环去除用 1 实现;如果不同,只需要看两个节点是不是再一个环上(如果一个节点在两个换上,那它有两个 next 节点)
  3. 如果其中一个有环,不相交
    // 快慢指针看相遇
    // 从 head 再来一个慢指针, 两个慢指针一起移动到相遇节点,就是第一个入环节点
    public Node firstRingNode(Node head) {
        if (head == null || head.next == null || head.next.next == null) {
            return null;
        }
        Node fast = head.next.next;
        Node slow = head.next;
        while (fast != slow) {
            if (fast.next == null || fast.next.next == null) {
                return null;
            }
            fast = fast.next.next;
            slow = slow.next;
        }
        Node node = head;
        while (node != slow) {
            node = node.next;
            slow = slow.next;
        }
        return node;
    }

    public Node firstIntersectNode(Node head1, Node head2) {
        if (head1 == null || head2 == null) {
            return null;
        }

        Node node1 = firstRingNode(head1);
        Node node2 = firstRingNode(head2);
        if (node1 == null && node2 == null) {
            return noLoop(head1, head2, null, null);
        }
        if (node1 != null && node2 != null) {
            return bothLoop(head1, head2, node1, node2);
        }

        return null;
    }

    // 1.处理无环情况
    // 2.处理环是公有的情况
    public Node noLoop(Node head1, Node head2, Node tail1, Node tail2) {
        Node node1 = head1;
        Node node2 = head2;
        int l1 = 1;
        int l2 = 1;
        while (node1.next != tail1) {
            l1++;
            node1 = node1.next;
        }
        while (node2.next != tail2) {
            l2++;
            node2 = node2.next;
        }
        if (node1 != node2) {
            return null;
        }
        node1 = head1;
        node2 = head2;
        while (l1 > l2) {
            node1 = node1.next;
            l1--;
        }
        while (l1 < l2) {
            node2 = node2.next;
            l2--;
        }
        while (node1 != node2) {
            node1 = node1.next;
            node2 = node2.next;
        }
        return node1;
    }

    // 两个环的情况
    public Node bothLoop(Node head1, Node head2, Node loop1, Node loop2) {
        if (loop1 == loop2) {
            return noLoop(head1, head2, loop1, loop2);
        } else {
            Node p = loop1.next;
            while (p != loop1) {
                if (p == loop2) {
                    return loop2;
                }
            }
        }
        return null;
    }