关于链表的常见操作和相关题目

229 阅读3分钟

一、数据结构

public class ListNode{
    int val;
    ListNode next;

    public ListNode(int val){
        this.val = val;
    }
}

当然,可以在这个基础上构建双向链表、循环链表等,这些都比较简单,本文不再赘述。

二、关于本地测试的小技巧

在刷题的时候,经常遇到需要调试代码的情况,而在本地手动创建链表又比较麻烦,所以在这里提供一个输入一个数组返回链表的函数createListNode(int[] a)(由调用者保证参数的正确性)。

public  ListNode createListNode(int[] a){
    ListNode head = new ListNode(a[0]);
    ListNode tail = head;
    for(int i=1;i<a.length;i++){
        tail.next = new ListNode(a[i]);
        tail = tail.next;
    }
    return head;
}

三、常见操作

1、反转链表

反转链表.drawio.png 最好自己动手画一遍,就可以理清楚整个流程。

public  ListNode reverseListNode(ListNode head){
        ListNode cur,nxt;
        ListNode pre = null;
        cur = nxt = head;
        while(cur != null){
            nxt = cur.next;
            cur.next = pre;
            pre = cur;
            cur = nxt;
        }
        return pre;
    }

2、找中点

一般利用快慢指针,当快指针到达终点时,慢指针指向中点。 以下分别讨论当节点数为奇数和偶数时的情况:

中点1.drawio.png

中点2.drawio.png

思考:为什么要构建一个虚拟头结点dummy,slow和fast都从dummy开始?

1、可以解决当节点数只有两个的特殊情况,如果没有dummy:

image.png 2、如果没有dummy,节点数为偶数时,slow指向的是中点的后一位:

image.png

ListNode dummy,slow,fast;
dummy = new ListNode(-1);
dummy.next = head;
slow = fast = pre;
while(fast != null && fast.next != null){
    slow = slow.next;
    fast = fast.next.next;
}

四、一些题目

剑指 Offer 35. 复杂链表的复制

// 方法一:利用哈希表
// 时间复杂度O(N) 空间复杂度O(N)
class Solution {
    public Node copyRandomList(Node head) {
        if(head == null)
            return head;
        // 构建哈希表 存储<原链表节点,新链表节点>
        Map<Node,Node> map = new HashMap<>();
        Node cur = head;
        while(cur!=null){
            Node temp = new Node(cur.val);
            map.put(cur,temp);
            cur = cur.next;
        }
        // 复制原链表的next指针和random指针
        cur = head;
        while(cur!=null){
            map.get(cur).next = map.get(cur.next);
            map.get(cur).random = map.get(cur.random);
            cur = cur.next;
        }
        // 返回新链表的头结点
        return map.get(head);
    }
}

// 方法二:Node1->newNode1->Node2->newNode2...
// 1、在原始链表的基础上加入新的链表
        Node cur = head;
        while(cur!=null){
            Node temp = new Node(cur.val);
            temp.next = cur.next;
            cur.next = temp;
            cur = temp.next;
        }
        // 2、复制原始链表的random指针
        cur = head;
        while(cur != null){
            // 需要判空 不然会导致本来random指向空的现在指向了一个节点
            if(cur.random!=null){
                // 新的节点的random域指向的是旧节点的random的下一个节点
                cur.next.random = cur.random.next;
            }
            cur = cur.next.next;
        }
        // 3、把链表分割为两个
        Node pre = head;
        Node newHead = head.next;
        Node newN = head.next;
        while(newN.next!=null){
            pre.next = pre.next.next;
            newN.next = newN.next.next;
            pre = pre.next;
            newN = newN.next;
        }
        // 注意需要手动把原始链表的尾结点改为null
        pre.next = null;
        return newHead;

剑指 Offer II 023. 两个链表的第一个重合节点

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode pA = headA;
        ListNode pB = headB;

        while(pA!=pB){
            // pA在链表A上遍历
            if(pA!=null){
                pA = pA.next;
            }else{
            // 遍历完链表A转到链表B继续遍历
                pA = headB;
            }

            // pB在链表B上遍历
            if(pB != null){
                pB = pB.next;
            }else {
                pB = headA;
            }
        }
        return pA;
    }
}

剑指 Offer II 025. 链表中的两数相加

class Solution {
    public  ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        // 思路:对l1 l2进行翻转 从地位开始相加,进位传递
        ListNode head1 = reverseListNode(l1);
        ListNode head2 = reverseListNode(l2);
        // 记录进位
        int carry = 0;
        // 辅助头结点
        ListNode head = new ListNode(-1);
        ListNode recor = head;
        while(head1 != null || head2 != null){
            int sum = 0;
            if(head1!=null&&head2 != null){
                sum = head1.val + head2.val;
                head1 = head1.next;
                head2 = head2.next;
            }else if(head1 != null){
                sum = head1.val;
                head1 = head1.next;
            }else if(head2 != null){
                sum = head2.val;
                head2 = head2.next;
            }
            // 是否有进位
            if(carry == 1){
                sum += 1;
                // 使用了进位之后复零
                carry = 0;
            }
            if(sum>=10){
                carry = 1;
                sum = sum%10;
            }
            head.next = new ListNode(sum);
            head = head.next;
        }
        if(carry == 1){
            head.next = new ListNode(1);
        }
        // 翻转链表
        ListNode res = reverseListNode(recor.next);
        return res;
    }

    public  ListNode reverseListNode(ListNode head){
        ListNode cur,nxt;
        ListNode pre = null;
        cur = nxt = head;
        while(cur != null){
            nxt = cur.next;
            cur.next = pre;
            pre = cur;
            cur = nxt;
        }
        return pre;
    }
}

剑指 Offer II 026. 重排链表

public void reorderList(ListNode head) { 
        ListNode slow,fast,pre;
        pre = new ListNode(-1);
        pre.next = head;
        slow = fast = pre;
        while(fast != null && fast.next != null){
            slow = slow.next;
            fast = fast.next.next;
        }
        // 把链表从中点分成两部分
        ListNode half = slow.next;
        slow.next = null;
        ListNode reverseHalf = reverseList(half);
        ListNode nxt1, nxt2, cur;
        cur = pre.next;
        while(reverseHalf != null){
            nxt1 = cur.next;
            cur.next = reverseHalf;
            nxt2 = reverseHalf.next;
            reverseHalf.next = nxt1;
            reverseHalf = nxt2;
            cur = nxt1;
        }
    }

    public ListNode reverseList(ListNode head){
        ListNode cur, nxt;
        ListNode pre = null;
        cur = nxt = head;
        while(cur != null){
            nxt = cur.next;
            cur.next = pre;
            pre = cur;
            cur = nxt;
        }
        return pre;
    }

剑指 Offer II 027. 回文链表

class Solution {
    public boolean isPalindrome(ListNode head) {
        ListNode pre,slow,fast;
        pre = new ListNode(-1);
        pre.next = head;
        slow = fast = pre;
        while(fast != null && fast.next != null){
            slow = slow.next;
            fast = fast.next.next;
        }
        ListNode half = slow.next;
        slow.next = null;
        ListNode reverseHalf = reverseList(half);
        ListNode cur = pre.next;
        while(reverseHalf != null){
            if(cur.val != reverseHalf.val){
                return false;
            }
            reverseHalf = reverseHalf.next;
            cur = cur.next;
        }
        return true;
    }

    public ListNode reverseList(ListNode head){
        ListNode cur,nxt;
        ListNode pre = null;
        cur = nxt = head;
        while(cur != null){
            nxt = cur.next;
            cur.next = pre;
            pre = cur;
            cur = nxt;
        }
        return pre;
    }
}

剑指 Offer II 031. 最近最少使用缓存

class LRUCache {
    Map<Integer,Node> map = new HashMap<>();
    LinkedList<Node> list = new LinkedList<>();
    int capacity = 0;

    public LRUCache(int capacity) {
        this.capacity = capacity;
    }
    
    public int get(int key) {
        if(!map.containsKey(key)){
            return -1;
        }
        int value = map.get(key).val;
        // 重新插入,更新位置
        this.put(key,value);
        return value;
    }
    
    public void put(int key, int value) {
        Node newNode = new Node(key,value);
        // 如果这个key已经存在,覆盖map并更新在list中的位置
        if(map.containsKey(key)){
            // 删掉原来的
            Node old = map.get(key);
            list.remove(old);
        }else{
            // 需要考虑容量问题
            if(list.size() == capacity){
                // 从表尾删除
                Node last = list.getLast();
                list.removeLast();
                // 删除在map中的映射关系
                map.remove(last.key);
            }

        }
        // 加到表头
        list.addFirst(newNode);
        // 覆盖map
        map.put(key,newNode);

    }
}

class Node{
    int key,val;
    Node next;

    public Node(int key, int val){
        this.key = key;
        this.val = val;
    }
}

剑指 Offer II 077. 链表排序

// 方法一:全部丢到小顶堆里面 时间复杂度O(nlogn) 空间复杂度O(n)
class Solution {
    public ListNode sortList(ListNode head) {
        PriorityQueue<ListNode> heap = new PriorityQueue<ListNode>((a,b)->a.val-b.val);
        while(head != null){
            heap.add(head);
            head = head.next;
        }
        ListNode newH = new ListNode(-1);
        ListNode pre = newH;
        while(!heap.isEmpty()){
            newH.next = heap.poll();
            newH = newH.next;
        }
        newH.next = null;
        return pre.next;
    }
}
// 方法二:归并排序 时间复杂度O(nlogn) 空间复杂度O(1)
class Solution {
    // 归并排序
    public ListNode sortList(ListNode head) {
        // base case:只有一个元素或者为空则认为是有序的
        if(head == null || head.next == null){
            return head;
        }
        // 二分
        ListNode lastHead = split(head);
        // 分解
        lastHead = sortList(lastHead);
        head = sortList(head);
        // 合并
        return mergeList(head,lastHead);   
    }

    public ListNode mergeList(ListNode head1, ListNode head2){
        ListNode dummy = new ListNode(-1);
        ListNode cur = dummy;
        while(head1 != null && head2 != null){
            if(head1.val <= head2.val){
                cur.next = head1;
                head1 = head1.next;
            }else{
                cur.next = head2;
                head2 = head2.next;
            }
            cur = cur.next;
        }
        cur.next = head1==null?head2:head1;
        return dummy.next;
    }

    public ListNode split(ListNode head){
        ListNode pre,slow,fast;
        pre = new ListNode(-1);
        pre.next = head;
        slow = fast = pre;
        while(fast != null && fast.next != null){
            slow = slow.next;
            fast = fast.next.next;
        }
        ListNode half = slow.next;
        slow.next = null;
        return half;
    }
}

剑指 Offer II 078. 合并排序链表

class Solution {
    public ListNode mergeKLists(ListNode[] lists) {
        PriorityQueue<ListNode> heap = new PriorityQueue<ListNode>((a,b)->(a.val-b.val));
        // 把全部头结点加进去
        for(ListNode node:lists){
            if(node != null){
                heap.add(node);
            }
        }
        ListNode dummy = new ListNode(-1);
        ListNode cur = dummy;
        // 维护一个最大为k(数组长度)的小顶堆 
        // 每次从堆顶取出一个元素,再将这个元素的下一个节点加入堆
        while(!heap.isEmpty()){
            ListNode tmp = heap.poll();
            if(tmp.next != null){
                heap.add(tmp.next);
            }
            cur.next = tmp;
            cur = cur.next;
        }
        cur.next = null;
        return dummy.next;
    }
}