链表


采用指针访问元素,而且指针只向一个方向移动。

  • 巧妙的构造虚拟头结点,可以使遍历处理逻辑更加统一;
  • 灵活使用递归(递归深度可能会导致超时和栈溢出);
  • 链表区间逆序。第 92 题。
  • 链表寻找中间节点。第 876 题(快慢指针)。链表寻找倒数第 n 个节点。第 19 题(双指针)。
  • 合并 K 个有序链表。第 21 题,第 23 题(分治算法)。
  • 链表归类。第 86 题-分隔链表,第 328 题-奇偶链表。
  • 链表排序,时间复杂度要求 O ( n × l o g n ) O(n \times log n) O(n×logn), 空间复杂度 O ( 1 ) O(1) O(1)。只有一种做法,归并排序,自顶向上递归调用,第 148 题。
  • 判断链表是否存在环,如果有环,输出环的交叉点的下标;判断 2 个链表是否有交叉点,如果有
    交叉点,输出交叉点。第 141 题(哈希表快慢指针),第 142 题(哈希表快慢指针 a = c + ( n − 1 ) ( b + c ) a=c+(n−1)(b+c) a=c+(n1)(b+c) ),第 160 题。

合并两个有序链表(第21题)

leetcode 第21题(简单)

方法一:递归

public class Solution {
   
   

    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
   
   
        // l1 为空 返回 l2
        if (l1 == null) {
   
   
            return l2;
        // l2 为空 返回 l1
        } else if (l2 == null) {
   
   
            return l1;
        // 链表中保留 val 值小的节点,递归 之后的部分
        } else if (l1.val < l2.val){
   
   
            l1.next = mergeTwoLists (l1.next, l2);
            return l1;
        } else {
   
   
            l2.next = mergeTwoLists(l1, l2.next);
            return l2;
        }
    }

    public static void main(String[] args) {
   
   
        // l1: 1 -> 2 -> 4
        ListNode l1 = new ListNode(1, new ListNode(2, new ListNode(4, null)));
        // l2: 1 -> 3 -> 4
        ListNode l2 = new ListNode(1, new ListNode(3, new ListNode(4, null)));

        ListNode result = new Solution().mergeTwoLists(l1, l2);

        ListNode ptr = result;

        while (ptr != null) {
   
   
            System.out.print(ptr.val + " -> ");
            ptr = ptr.next;
        }
        System.out.println("null");
    }
}

class ListNode {
   
   
    int val;
    ListNode next;

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

复杂度分析:

  • 时间复杂度: O ( n + m ) O(n + m) O(n+m),其中 n 和 m 分别为两个链表的长度。因为每次调用递归都会去掉 l1l2的头节点(直到至少一个链表为空),mergeTwoList至多只会递归调用每个节点一次。因此,时间复杂度取决于合并后的链表长度,即 O ( n + m ) O(n+m) O(n+m)
  • 空间复杂度: O ( n + m ) O(n+m) O(n+m),其中 n 和 m 分别为两个链表的长度。递归调用 mergeTwoLists函数时需要消耗栈空间(占空间的大小取决于递归调用的深度)。结束递归调用时 mergeTwoLists函数最多调用 n+m次,因此空间复杂度为 O ( n + m ) O(n+m) O(n+m)

方法二: 迭代

l1l2都不为空链表时,判断 哪个链表的头节点的值更小,将较小值的节点添加到结果里,当一个节点被添加到结果里之后,将对应链表中的节点向后移动一位。

class Solution {
   
   
    public ListNode mergeTwoLists (ListNode a, ListNode b) {
   
   
        // 1、创建虚拟头节点 dummyHead,保存合并之后的链表
        // 声明指针 tail 来记录下一个插入元素的在哪个节点之后挂载新的节点
        ListNode dummyHead = new ListNode(0, null), tail = dummyHead;

        // 5、循环终止条件,当有一个链表为空时,结束循环
        while (a != null && b != null) {
   
   
            if (a.val < b.val) {
   
   
                // 2、将值较小的节点拼接到尾部节点tail 之后;
                tail.next = a;
                // 3、同时将 对应指针后移;
                a = a.next;
            } else {
   
   
                tail.next = b;
                b = b.next;
            }
            // 4、尾部指针后移
            tail = tail.next;
        }
        // 6、跳出循环后,至多有一个链表非空,将非空的链表拼接到尾部节点
        tail.next = a == null ? b : a;
        return dummyHead.next;
    }
}

复杂度分析:

  • 时间复杂度: O ( n + m ) O(n + m) O(n+m),其中 n 和 m 分别为两个链表的长度。因为每次循环迭代只有 l1l2中的一个元素会被放进合并链表中,因此 while循环次数不会超过两个链表的长度之和,其他操作时间复杂度都是常数级别,因此总的时间复杂度为 O ( n + m ) O(n+m) O(n+m)
  • 空间复杂度: O ( 1 ) O(1) O(1)。只需要常数的空间存放若干指针变量。

合并 k 个有序链表(第23题)

LeetCode 第23题 (困难)

给定一个链表数组,每个链表都已经按照升序排列,将所有链表合并到一个升序链表中,返回合并后的链表;

方法一:顺序合并

最朴素的方法是,用 一个变量 ans来维护合并后的链表,第i 次循环,把第 i 个链表和 ans合并;

class Solution{
   
   
    public ListNode mergerKLists(ListNode[] lists) {
   
   
        ListNode ans = null;
        for (int i = 0; i < lists.length; ++i) {
   
   
            ans = mergeTwoLists(ans, lists[i]);
        }
        return ans;
    }
}

复杂度分析:

  • 时间复杂度:假设每个链表长度为 n,在第一次合并后,ans的长度为 n;第二次合并后, ans的长度为 2 x n;第i次合并后, ans的长度为 i x n。第 i 次合并的时间代价是 O ( n + ( i − 1 ) × n ) = O ( i × n ) O(n+(i-1)\times n) = O(i \times n) O(n+(i1)×n)=O(i×n),那么总的时间代价为 O ( ∑ i = 1 k ( i × n ) ) = O ( ( 1 + k ) k 2 × n ) = O ( k 2 n ) O(\sum_{i=1}^{k}(i\times n)) = O(\frac{(1+k)k}{2} \times n) = O(k^2 n) O(i=1k(i×n))=O(2(1+k)k×n)=O(k2n),故渐进时间复杂度为 O ( K 2 n ) O(K^2 n) O(K2n)
  • 空间复杂度: 没有用到 与 k 和 n 规模相关的辅助空间,故渐进空间复杂度为 O(1)。

*方法二: 分治合并

  • 将 k 个链表配对,并将同一对中的链表合并;
  • 第一轮合并以后,k 个链表被合并成 k 2 \frac{k}{2} 2k 个链表,平均长度 为 2 n k \frac{2n}{k} k2n, 然后是 k 4 \frac{k}{4} 4k 个链表, k 8 \frac{k}{8} 8k 个链表等等;
  • 重复这一过程,直到得到最终的有序链表;
class Solution {
   
   
    public ListNode mergeKLists (ListNode[] lists) {
   
   
        return merge(lists, 0, lists.length - 1)
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值