Leetcode(1-1) 链表

42 阅读4分钟

一、链表的访问问题

1. 环形链表

image.png
// 解题重点: 使用快慢指针,将问题转换为追击问题
public class Solution {
    public boolean hasCycle(ListNode head) {
        if (head == null) {
            return false;
        }
        ListNode slow = head;
        ListNode quick = head;
        while (quick != null && quick.next != null) {
            slow = slow.next;
            quick = quick.next.next;
            if (slow == quick) {
                return true;
            }
        }
        return false;
    }
}

2. 环形链表II

image.png image.png
定理: 任意时间快指针的路程都是慢指针路程的2倍
分析: 快慢指针相遇时的路程关系
2 * (a + b) = a + b + n*(b+c)
a = c + (n-1) *(b+c)
所以: 从相遇点和head位置出发的两个节点,将来会在入环处相遇
public class Solution {
    public ListNode detectCycle(ListNode head) {
        if (head == null) {
            return null;
        }
        ListNode slow = head;
        ListNode quick = head;
        while (quick != null && quick.next != null) {
            slow = slow.next;
            quick = quick.next.next;
            if (slow == quick) {
            // 相遇之后,slow和head结点一起走,会在环入口相遇
                while (slow != head) {
                    slow = slow.next;
                    head = head.next;
                }
                return slow;
            }
        }
        return null;
    }
}

3. 快乐数

image.png
什么情况下能够判断他不是一个快乐数呢,总不能一直循环吧。
其实很简单,一张图就可以说明,就是说这个遍历成环了
解题重点: 将快乐数问题映射成链表是否有环问题
image.png
class Solution {
    // 这里只能用快慢指针,否则会超时 
    public boolean isHappy(int n) {
        int slow = n;
        int fast = n;
        while( fast != 1 && getNext(fast) != 1){
            slow = getNext(slow);
            fast = getNext(getNext(fast));
            if(slow == fast){
                return false;
            }
        }

        return true;
    }

    public int getNext(int n){
        int sum = 0;
        while(n!=0){
            int mod = n%10;
            sum += mod*mod;
            n = n/10;
        }
        return sum;
    }
}

二、链表的节点删除问题

4. 删除链表的倒数第 N 个结点

image.png
删除链表的倒数第N个结点 = 删除链表的倒数第N-1个结点的下一个结点
问题: 如果只有一个头结点呢?删除后如何返回null.
方案: 通常都会增加一个虚拟头结点来实现
class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode virHead = new ListNode();
        virHead.next = head;
        ListNode p = virHead;
        ListNode q = head;
        while (n != 0 && q != null) {
            n--;
            q = q.next;
        }
        while (q != null) {
            p = p.next;
            q = q.next;
        }
        p.next = p.next.next;
        return virHead.next;
    }
}

5. 删除排序链表中的重复元素

image.png
class Solution {
    public ListNode deleteDuplicates(ListNode head) {
        ListNode p = head;
        ListNode q = head;
        while(q!=null){
            while(q != null && p.val == q.val){
                q = q.next;
            }
            p.next = q;
            p=q;
        }
        return head;
    }
}

6. 删除排序链表中的重复元素 II

image.png
解题重点:删除重复的元素,可能头结点也会被删除,需要引入虚拟结点
引入临时变量: 记录重复值然后依次删除
class Solution {
    public ListNode deleteDuplicates(ListNode head) {
        ListNode virHead = new ListNode(-1);
        virHead.next = head;
        ListNode cur = virHead;
        while (cur.next != null && cur.next.next != null) {
            if (cur.next.val == cur.next.next.val) {
                int val = cur.next.val;
                while (cur.next != null && cur.next.val == val) {
                    cur.next = cur.next.next;
                }
            } else {
                cur = cur.next;
            }
        }
        return virHead.next;
    }
}

三、链表的反转问题

7. 反转链表

image.png
class Solution {
    // 1. 头插法:组装一个新链表出来
    public ListNode reverseList(ListNode head) {
        ListNode virHead = new ListNode();
        virHead.next = null;
        while(head != null){
            // 这样就可以单独拿出来head
            ListNode p = head.next;
            ListNode temp = virHead.next;
            virHead.next = head;
            head.next = temp;
            // head重新放入原链表中进行索引
            head = p;
        }
        return virHead.next;
    }
    
    // 2. 在原链表上直接反转
    public ListNode reverseList(ListNode head) {
        if (head == null) {
            return null;
        }
        ListNode p = head.next;
        head.next = null;
        while (p != null) {
            ListNode temp = p.next;
            p.next = head;
            head = p;
            p = temp;
        }
        return head;
    }
}

8. 反转链表II

image.png
class Solution {
    public ListNode reverseBetween(ListNode head, int left, int right) {
        ListNode virHead = new ListNode();
        virHead.next = head;
        ListNode pre = virHead;
        // 走到left-1位置
        int num = 0;
        while (num < left - 1) {
            num++;
            pre = pre.next;
        }
        ListNode cur = pre.next;
        ListNode tmp;
        for (int i = 0; i < right - left; i++) {
            tmp = cur.next;
            cur.next = tmp.next;
            tmp.next = pre.next;
            pre.next = tmp;
        }
        return virHead.next;
    }
}

9. K个一组翻转链表

image.png
class Solution {
    public ListNode reverseKGroup(ListNode head, int k) {
        ListNode virHead = new ListNode();
        virHead.next = head;
        ListNode right = virHead;
        while (right != null) {
            ListNode pre = right;
            ListNode left = right.next;
            for (int i = 0; i < k; i++) {
                if (right != null) {
                    right = right.next;
                }
            }
            // 把left位置保存下来,处理完[left ,right]之后,left就到了right的位置,用于下一个[left ,right]翻转的虚拟头结点
            // temp就是下一个[left ,right]的虚拟头结点
            ListNode temp = new ListNode(0);
            temp.next = left;
            if (right != null) {
                // 最关键的:把中间[left ,right]给截出来,前后关联在一起,然后头插法把[left ,right]给逐个插入
                pre.next = right.next;
                for (int i = 0; i < k; i++) {
                    ListNode leftNext = left.next;
                    left.next = pre.next;
                    pre.next = left;
                    left = leftNext;
                }
                // 把right放到翻转后left的位置
                right = temp.next;
            }
        }
        return virHead.next;
    }
}

10. 旋转链表

image.png
class Solution {
    public ListNode rotateRight(ListNode head, int k) {
        if (head == null || head.next == null) {
            return head;
        }
        ListNode cur = head;
        int len = 1;
        while (cur.next != null) {
            len++;
            cur = cur.next;
        }
        // 首位相连
        cur.next = head;
        ListNode pre = cur;
        cur = head;
        // 容易出错: 向右移动并不是成环后一直next,K次,而是len-k次.
        int div = len - k % len;
        for (int i = 0; i < div; i++) {
            cur = cur.next;
            pre = pre.next;
        }
        pre.next = null;
        return cur;
    }
}

11. 两两交换链表中的节点

image.png
class Solution {
    public ListNode swapPairs(ListNode head) {
        if (head == null || head.next == null) {
            return head;
        }
        ListNode virHead = new ListNode();
        virHead.next = head;
        ListNode p = virHead.next;
        ListNode q = p.next;
        ListNode pre = virHead;
        while (p != null && q != null) {
            ListNode next = q.next;
            pre.next = q;
            q.next = p;
            p.next = next;
            // 准备交换下一阶段数据: 注意:p,q已经交换位置了
            // 如果没有元素或者只剩下一个结点就没必要交换了
            if (p.next != null && p.next.next != null) {
                pre = p;
                p = pre.next;
                q = p.next;
            } else {
                return virHead.next;
            }
        }
        return virHead.next;
    }
}

四、链表习题

12. 分隔链表

image.png
class Solution {
    public ListNode[] splitListToParts(ListNode head, int k) {
        int sum = 0;
        ListNode temp = head;
        while (temp != null) {
            sum++;
            temp = temp.next;
        }
        // 每份的各数: n
        // 前div份数量为 n+1
        int n = sum / k, div = sum % k;
        ListNode[] res = new ListNode[k];
        ListNode cur = head;
        for (int i = 0; i < k && cur != null; i++) {
            ListNode subHead = cur;
            int size = n + (i < div ? 1 : 0);
            for (int j = 1; j < size; j++) {
                cur = cur.next;
            }
            ListNode next = cur.next;
            cur.next = null;
            cur = next;
            res[i] = subHead;
        }
        return res;
    }
}

13. 复制带随机指针的链表

image.png image.png image.png image.png
class Solution {
    public Node copyRandomList(Node head) {
        if (head == null) {
            return null;
        }
        for (Node node = head; node != null; node = node.next.next) {
            Node newNode = new Node(node.val);
            newNode.next = node.next;
            node.next = newNode;
        }
        for (Node node = head; node != null; node = node.next.next) {
            Node newNode = node.next;
            if (node.random != null) {
                newNode.random = node.random.next;
            }
        }
        Node headNew = head.next;
        for (Node node = head; node != null; node = node.next) {
            Node nodeNew = node.next;
            node.next = nodeNew.next;
            nodeNew.next = (nodeNew.next != null) ? nodeNew.next.next : null;
        }
        return headNew;
    }
}

14. 链表中的下一个更大节点

image.png
class Solution {
    public int[] nextLargerNodes(ListNode head) {
        List<Integer> list = new ArrayList<>();
        ListNode slow = head;
        while (slow != null) {
            ListNode quick = slow.next;
            while (quick != null) {
                if (quick.val > slow.val) {
                    list.add(quick.val);
                    break;
                }else {
                    quick=quick.next;
                }
            }
            if (quick == null) {
                list.add(0);
            }
            slow = slow.next;
        }
        int[] res = new int[list.size()];
        for (int i = 0; i < list.size(); i++) {
            res[i] = list.get(i);
        }
        return res;
    }
}