LeetCode 链表相关题目汇总

文章目录

  • 剑指 Offer 06. 从尾到头打印链表
  • 剑指 Offer 18. 删除链表的节点
  • 剑指 Offer 22. 链表中倒数第k个节点
  • 剑指 Offer 24. 反转链表
  • 剑指 Offer 25. 合并两个排序的链表
  • 剑指 Offer 35. 复杂链表的复制
  • 剑指 Offer 36. 二叉搜索树与双向链表
  • 剑指 Offer 52. 两个链表的第一个公共节点
  • 234. 回文链表

主要是《剑指offer》书中的题目

剑指 Offer 06. 从尾到头打印链表

https://leetcode-cn.com/problems/cong-wei-dao-tou-da-yin-lian-biao-lcof/

思路一:栈

从链表的头节点开始,依次将每个节点压入栈内,然后依次弹出栈内的元素并存储到数组中。

时间复杂度: O ( n ) O(n) O(n):正向遍历一遍链表,然后从栈弹出全部节点,等于又反向遍历一遍链表

空间复杂度: O ( n ) O(n) O(n):额外使用一个栈存储链表的值

class Solution {
    public int[] reversePrint(ListNode head) {
        Stack<Integer> stk = new Stack<>();
        while (head != null) {
            stk.push(head.val);
            head = head.next;
        }
        int[] res = new int[stk.size()];
        for (int i = 0; i < res.length; i++) {
            res[i] = stk.pop();
        }
        return res;
    }
}

执行用时:2 ms, 在所有 Java 提交中击败了36.18%的用户

内存消耗:39.1 MB, 在所有 Java 提交中击败了57.54%的用户

思路二:递归

本质上和栈是一样的,先走至链表末端,回溯时依次将节点值加入列表 ,这样就可以实现链表值的倒序输出

时间复杂度: O ( n ) O(n) O(n):遍历链表,递归 n n n 次【没有栈的压入、弹出操作,实际时间要快了一点】

空间复杂度: O ( n ) O(n) O(n):系统递归需要使用 O ( n ) O(n) O(n) 的栈空间。

class Solution {
    int[] res = null;
    int  index = 0;

    public int[] reversePrint(ListNode head) {
        recur(head,0);
        return res;
    }

    public void recur(ListNode head, int i) {
        if (head == null) {
            res = new int[i];
            return;
        }
        recur(head.next, i + 1);
        res[index++] = head.val;
    }
}

执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户

内存消耗:39.9 MB, 在所有 Java 提交中击败了8.33%的用户

思想3:反转链表

时间复杂度依然是 O ( n ) O(n) O(n),原地转置空间复杂度可以做到 O ( 1 ) O(1) O(1),不详细写了,下面有反转链表的题目

剑指 Offer 18. 删除链表的节点

https://leetcode-cn.com/problems/shan-chu-lian-biao-de-jie-dian-lcof/

LeetCode中所有的链表题目都是不带头结点的,所以添加一个 newHead 方便删除首个结点。

时间复杂度: O ( n ) O(n) O(n)

空间复杂度: O ( 1 ) O(1) O(1)

class Solution {
    public ListNode deleteNode(ListNode head, int val) {
        ListNode newHead = new ListNode(-1);
        newHead.next = head;
        ListNode pre = newHead;
        while (head != null) {
            if (head.val == val) {
                pre.next = head.next;
                break;
            }
            pre = pre.next;
            head = head.next;
        }
        return newHead.next;
    }
}

执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户

内存消耗:37.6 MB, 在所有 Java 提交中击败了92.14%的用户

剑指 Offer 22. 链表中倒数第k个节点

https://leetcode-cn.com/problems/lian-biao-zhong-dao-shu-di-kge-jie-dian-lcof/

思路一:双指针

快慢指针同时出发,慢指针一步一个,快指针一步两个,快慢指针确定链表长度 n,倒着数第 k 个,即正着数 n-k 个

时间复杂度: O ( n ) O(n) O(n)

空间复杂度: O ( 1 ) O(1) O(1)

class Solution {
    public ListNode getKthFromEnd(ListNode head, int k) {
        ListNode slow = head, fast = head;
        int length = 0;
        while (fast != null && fast.next != null) {
            length++;
            slow = slow.next;
            fast = fast.next.next;
        }
        length = fast == null ? length * 2 : length * 2 + 1;
        length -= k;
        while (length-- > 0) 
            head = head.next;
        return head;
    }
}

执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户

内存消耗:36.3 MB, 在所有 Java 提交中击败了54.22%的用户

思路二:双指针(更简洁)

快指针先出发,让它指向正数第 k 个结点,慢指针再出发,和快指针同步后移,当快指针达到链表尾部时,慢指针就是倒数第k个结点

时间复杂度: O ( n ) O(n) O(n)

空间复杂度: O ( 1 ) O(1) O(1)

class Solution {
    public ListNode getKthFromEnd(ListNode head, int k) {
        ListNode fast = head, slow = head;
        while (k > 0) {
            fast = fast.next;
            k--;
        }
        while (fast != null) {
            fast = fast.next;
            slow = slow.next;
        }
        return slow;
    }
}

执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户

内存消耗:36.3 MB, 在所有 Java 提交中击败了34.05%的用户

剑指 Offer 24. 反转链表

https://leetcode-cn.com/problems/fan-zhuan-lian-biao-lcof/

思路一:递归

递归到链表的最后一个结点,该结点就是反转后的头结点,记作 ret,函数返回的过程中,让当前结点的下一个节点的 next 指向当前节点,让当前结点的 next 指针指向空

时间复杂度: O ( n ) O(n) O(n),其中 n 是链表的长度。需要遍历链表一次。

空间复杂度: O ( n ) O(n) O(n),递归调用的栈空间,最多为 n n n

class Solution {
    public ListNode reverseList(ListNode head) {
        if (head == null || head.next == null) return head;
        ListNode ret = reverseList(head.next);
        head.next.next = head;
        head.next = null;
        return ret;
    }
}

执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户

内存消耗:38.3 MB, 在所有 Java 提交中击败了35.71%的用户

思路二:原地转置

双指针遍历链表,将当前节点的 next 指针改为指向前一个节点。

时间复杂度: O ( n ) O(n) O(n),其中 n 是链表的长度。需要遍历链表一次。

空间复杂度: O ( 1 ) O(1) O(1)

class Solution {
    public ListNode reverseList(ListNode head) {
        ListNode temp, pre = null;
        while (head != null) {
            temp = head.next;
            head.next = pre;
            pre = head;
            head = temp;
        }
        return pre;
    }
}

执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户

内存消耗:37.7 MB, 在所有 Java 提交中击败了98.59%的用户

剑指 Offer 25. 合并两个排序的链表

https://leetcode-cn.com/problems/he-bing-liang-ge-pai-xu-de-lian-biao-lcof/

思路一:迭代归并

时间复杂度:O(n+m) ,n 和 m 分别为两个链表的长度

空间复杂度:O(1),只需要常数的空间存放若干变量。

class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        ListNode head = new ListNode(); //便于最后找到链表的头
        ListNode p = head;
        while (l1 != null && l2 != null) {
            if (l1.val <= l2.val) {
                p.next = l1;
                l1 = l1.next;
            } else {
                p.next = l2;
                l2 = l2.next;
            }
            p = p.next;
        }
        p.next = l1 != null ? l1 : l2;
        return head.next;
    }
}

执行用时:1 ms, 在所有 Java 提交中击败了98.55%的用户

内存消耗:38.8 MB, 在所有 Java 提交中击败了16.43%的用户

思路二:递归

时间复杂度:O(n+m) ,n 和 m 分别为两个链表的长度

空间复杂度:O(n + m)递归调用消耗的栈空间,n 和 m 分别为两个链表的长度。

class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        if (l1 == null) return l2;
        else if (l2 == null) return l1;
        else if (l1.val <= l2.val) {
            l1.next = mergeTwoLists(l1.next, l2);
            return l1;
        } else {
            l2.next = mergeTwoLists(l1, l2.next);
            return l2;
        }
    }
}

执行用时:1 ms, 在所有 Java 提交中击败了98.55%的用户

内存消耗:39 MB, 在所有 Java 提交中击败了5.13%的用户

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

https://leetcode-cn.com/problems/fu-za-lian-biao-de-fu-zhi-lcof/

思想一:哈希表

先遍历一遍链表,通过一个HashMap记录新旧节点的映射,再遍历一遍链表,通过旧链表当前节点的 random 指针找到对应的新链表的节点random指针要指向的地方

时间复杂度: O ( N ) O(N) O(N)

空间复杂度: O ( N ) O(N) O(N)

class Solution {
    HashMap<Node, Node> map = new HashMap(); //做新旧结点映射

    public Node copyRandomList(Node head) {
        if (head == null) return null;
        Node newhead = new Node(0);
        Node p = head, new_p = newhead;
        while (p != null) {
            Node cur = new Node(p.val);
            new_p.next = cur;
            new_p = new_p.next;
            map.put(p, cur);
            p = p.next;
        }
        p = head;
        new_p = newhead.next;
        while (p != null) {
            if (p.random != null)
                new_p.random = map.get(p.random);
            new_p = new_p.next;
            p = p.next;
        }
        return newhead.next;
    }
}

执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户

内存消耗:38.2 MB, 在所有 Java 提交中击败了43.74%的用户

我只想到了哈希表的解法,下面三种都是 官方题解

方法二:回溯

方法三:O(N)空间的迭代

方法二和三的最核心要点还是用哈希表建立新旧结点的映射,才得以实现,不详细写了

时间复杂度: O ( N ) O(N) O(N)

空间复杂度: O ( N ) O(N) O(N)

方法二:O(1)空间的迭代

遍历原来的链表并拷贝每一个节点,将拷贝节点放在原来节点的旁边,创造出一个旧节点和新节点交错的链表,然后遍历新组成的链表,这样就有办法把Random指针接上,之后就好办了

LeetCode 链表相关题目汇总_第1张图片

LeetCode 链表相关题目汇总_第2张图片

时间复杂度: O ( N ) O(N) O(N)

空间复杂度: O ( 1 ) O(1) O(1)

class Solution {
    public Node copyRandomList(Node head) {
        if (head == null)  return null;
        Node ptr = head;
        while (ptr != null) {
            Node newNode = new Node(ptr.val);
            newNode.next = ptr.next;
            ptr.next = newNode;
            ptr = newNode.next;
        }
        ptr = head;
        while (ptr != null) {
            ptr.next.random = (ptr.random != null) ? ptr.random.next : null;
            ptr = ptr.next.next;
        }
        Node ptr_old_list = head; // A->B->C
        Node ptr_new_list = head.next; // A'->B'->C'
        Node head_old = head.next;
        while (ptr_old_list != null) {
            ptr_old_list.next = ptr_old_list.next.next;
            ptr_new_list.next = (ptr_new_list.next != null) ? ptr_new_list.next.next : null;
            ptr_old_list = ptr_old_list.next;
            ptr_new_list = ptr_new_list.next;
        }
        return head_old;
    }
}

剑指 Offer 36. 二叉搜索树与双向链表

https://leetcode-cn.com/problems/er-cha-sou-suo-shu-yu-shuang-xiang-lian-biao-lcof/

二叉排序树的中序遍历是有序的,所以这必然是中序遍历的过程中串成双链表,还有第一个和最后一个结点,在全部遍历结束后再处理让它们相互对指形成循环链表

递归

时间复杂度: O ( n ) O(n) O(n),中序遍历链表

空间复杂度: O ( n ) O(n) O(n),递归栈空间

class Solution {
    Node pre = null, newHead = null;
    void inOrder(Node root) {
        if (root == null) return;
        inOrder(root.left);
        if (newHead == null)
            newHead = root;
        else
            pre.right = root;
        root.left = pre;
        pre = root;
        inOrder(root.right);
    }

    public Node treeToDoublyList(Node root) {
        if (root == null) return null;
        inOrder(root);
        pre.right = newHead;
        newHead.left = pre;
        return newHead;
    }
}

执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户

内存消耗:37.6 MB, 在所有 Java 提交中击败了88.54%的用户

非递归

时间复杂度: O ( n ) O(n) O(n),中序遍历链表

空间复杂度: O ( n ) O(n) O(n),栈空间

class Solution {
    public Node treeToDoublyList(Node root) {
        if (root == null) return null;
        Stack<Node> stack = new Stack<>();
        Node head = null, p = root, prev = null;
        while (!stack.isEmpty() || p != null) {
            while (p != null) {
                stack.push(p);
                p = p.left;
            }
            p = stack.pop();
            //记录链表首个节点
            if (head == null) {
                head = p;
            } else {
                prev.right = p;
            }
            p.left = prev;
            prev = p;
            p = p.right;
        }
        //要求循环链表,处理头节点与尾节点
        head.left = prev;
        prev.right = head;
        return head;
    }
}

剑指 Offer 52. 两个链表的第一个公共节点

https://leetcode-cn.com/problems/liang-ge-lian-biao-de-di-yi-ge-gong-gong-jie-dian-lcof/

思路一:求表长,同步遍历

分别用快慢指针法求出链表 A,B 的长度 l e n g t h A , l e n g t h B length_A,length_B lengthA,lengthB,假设 A 表较长,让 A 先走 l e n g t h A − l e n g t h B length_A-length_B lengthAlengthB ,然后两表同步遍历,如果存在公共节点,一定会相遇

不知道哪个表长,实现起来很繁琐,算了

双指针法

两个指针 P1,P2,P1先遍历表A再遍历表B,P2遍历表B遍历表A,相遇时就是公共节点,两表没有公共节点时,pA和pB会同时遍历完两个表,同时为空,不会死循环

时间复杂度: O ( m + n ) O(m+n) O(m+n),两个链表的长度

空间复杂度: O ( 1 ) O(1) O(1)

class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode pA = headA, pB = headB;
      //两表没有公共节点时,pA和pB会同时遍历完两个表,同时为空,不会死循环
        while (pA != pB) {
            pA = pA == null ? headB : pA.next;
            pB = pB == null ? headA : pB.next;
        }
        return headA;
    }
}

执行用时:1 ms, 在所有 Java 提交中击败了100.00%的用户

内存消耗:41.3 MB, 在所有 Java 提交中击败了48.48%的用户

234. 回文链表

https://leetcode-cn.com/problems/palindrome-linked-list/

思路一:双指针+栈

快慢指针,前半部分存入栈中,但有些边界问题要处理,代码看起来不整洁

时间复杂度: O ( n ) O(n) O(n),遍历一遍链表

空间复杂度: O ( n ) O(n) O(n) ,辅助栈空间

class Solution {
    public boolean isPalindrome(ListNode head) {
        if (head == null) return true;
        Stack<Integer> stk = new Stack<>();
        stk.push(head.val);
        ListNode slow = head, fast = head.next;
        while (fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;
            stk.push(slow.val);
        }
        if (fast != null) slow = slow.next;
        while (slow != null) {
            if (slow.val != stk.pop()) return false;
            slow = slow.next;
        }
        return true;
    }
}

执行用时:2 ms, 在所有 Java 提交中击败了60.25%的用户

内存消耗:42.2 MB, 在所有 Java 提交中击败了30.66%的用户

思路二:双指针+反转链表

快慢指针找到中点,反转后半部分链表

时间复杂度: O ( n ) O(n) O(n)

空间复杂度: O ( 1 ) O(1) O(1)

class Solution {
    public boolean isPalindrome(ListNode head) {
        if (head == null) return true;
        ListNode fast = head.next, slow = head;
        while (fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;
        }
        //结点个数为奇数、偶数需要分别处理
        ListNode p = fast == null ? slow : slow.next, pre = null;
        while (p != null) {
            ListNode temp = p.next;
            p.next = pre;
            pre = p;
            p = temp;
        }
        while (pre != null) {
            if (head.val != pre.val) return false
            head = head.next;
            pre = pre.next;
        }
        return true;
    }
}

执行用时:1 ms, 在所有 Java 提交中击败了99.95%的用户

内存消耗:41.1 MB, 在所有 Java 提交中击败了64.37%的用户

你可能感兴趣的:(OJ题目,链表,leetcode,java,指针,面试)