0
点赞
收藏
分享

微信扫一扫

力扣每日练习-java版(七)链表

小_北_爸 2022-02-18 阅读 47

力扣每日练习-java版(七)

21. 合并两个有序链表

https://leetcode-cn.com/problems/merge-two-sorted-lists/
在这里插入图片描述

思路

方法一:递归
如果 l1 或者 l2 一开始就是空链表 ,那么没有任何操作需要合并,所以我们只需要返回非空链表。否则,我们要判断 l1 和 l2 哪一个链表的头节点的值更小,然后递归地决定下一个添加到结果里的节点。如果两个链表有一个为空,递归结束。
方法二:虚拟头结点+双指针

代码

方法一:

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;
        }
    }
}

方法二:

ListNode mergeTwoLists(ListNode l1, ListNode l2) {
    // 虚拟头结点
    ListNode dummy = new ListNode(-1), p = dummy;
    ListNode p1 = l1, p2 = l2; 
    while (p1 != null && p2 != null) {
        // 比较 p1 和 p2 两个指针
        // 将值较小的的节点接到 p 指针
        if (p1.val > p2.val) {
            p.next = p2;
            p2 = p2.next;
        } else {
            p.next = p1;
            p1 = p1.next;
        }
        // p 指针不断前进
        p = p.next;
    }
    if (p1 != null) {
        p.next = p1;
    }
    if (p2 != null) {
        p.next = p2;
    }
    return dummy.next;
}

时空复杂度

方法一:
在这里插入图片描述
方法二:
在这里插入图片描述

备注

1.「虚拟头结点」- dummy 节点,相当于是个占位符,可以避免处理空指针的情况,降低代码的复杂性。

23. 合并K个升序链表

https://leetcode-cn.com/problems/merge-k-sorted-lists/
在这里插入图片描述

思路

优先级队列(二叉堆)可以构建出k个数中的最小节点。先把所有链表按头结点值升序排序,依次构建二叉堆,堆顶元素就是当前队列中的最小值。

代码

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode mergeKLists(ListNode[] lists) {
        if (lists.length == 0) return null;
        //虚拟头结点
        ListNode dummy = new ListNode(-1);
        ListNode p = dummy;
        //优先队列,最小二叉堆
        PriorityQueue<ListNode> pq = new PriorityQueue<>(lists.length,(a,b)->{
            return a.val-b.val;
        });
        //先将k个链表的头结点入队,构建二叉堆
        for(ListNode list:lists){
            if(list!=null){
                pq.add(list);
            }
        }
        //获取最小节点接到结果链表上,指针不断前进,实现排序
        while(!pq.isEmpty()){
            ListNode node = pq.poll();
            p.next = node;
            if(node.next != null){
                pq.add(node.next);
            }
            p = p.next;
        }
        return dummy.next;
    }
}

时空复杂度

1.时间复杂度
优先队列 pq 中的元素个数最多是 k,所以一次 poll 或者 add 方法的时间复杂度是 O(logk);所有的链表节点都会被加入和弹出 pq,所以算法整体的时间复杂度是 O(Nlogk),其中 k 是链表的条数,N 是这些链表的节点总数。
2.空间复杂度
这里用了优先队列,优先队列中的元素不超过 k 个,故渐进空间复杂度为 O(k)。​

备注

1.普通队列用双向链表实现
Queue q = new LinkedList<>();
2.优先队列(最大/最小二叉堆)
PriorityQueue pq = new PriorityQueue<>();
还可以增加比较器(指定队列大小,指定比较器,实现升序/降序)
PriorityQueue pq = new PriorityQueue<>(lists.length,(a,b)->{
return a.val-b-val;
});
3.以后遇到k个数的最小值-》联想到用优先队列。
4.相关题目:
力扣第 19 题「删除链表的倒数第 N 个结点」

141. 环形链表(简单)

https://leetcode-cn.com/problems/reverse-prefix-of-word/

思路

方法一:
存入集合,判断是否存过
方法二:
快慢指针,如果成环,快指针总会追上慢指针相遇。

代码

方法一:

public class Solution {
    public boolean hasCycle(ListNode head) {
        Set<ListNode> seen = new HashSet<ListNode>();
        while (head != null) {
            if (!seen.add(head)) {
                return true;
            }
            head = head.next;
        }
        return false;
    }
}

方法二:

public class Solution {
    public boolean hasCycle(ListNode head) {
        if(head==null||head.next==null) return false;
        ListNode a = head;
        ListNode b = head.next;
        while (a != b) {
            if (b==null||b.next==null) {
                return false;
            }
            a = a.next;//a每次走一步
            b = b.next.next;//b每次走两步,如果有环,b总会追上a
        }
        return true;
    }
}

时空复杂度

1.时间复杂度
方法一:
O(n),其中 N 为链表中节点的数目。我们恰好需要访问链表中的每一个节点。
方法二:
O(n)。
2.空间复杂度
​方法一:
O(n) ,其中 N 为链表中节点的数目。我们需要将链表中的每个节点都保存在哈希表当中。
方法二:
O(1)

备注

  1. 成环=》快慢指针

142. 环形链表 II(中等)

https://leetcode-cn.com/problems/linked-list-cycle-ii/
在这里插入图片描述

思路

方法一:
存入集合,判断是否存过
方法二:
1.设置快慢指针,快指针速度为慢指针的2倍
2.找出相遇点
3.在head处和相遇点同时释放相同速度且速度为1的指针,两指针必会在环入口处相遇

代码

方法一:

public class Solution {
    public ListNode detectCycle(ListNode head) {
        ListNode pos = head;
        Set<ListNode> visited = new HashSet<ListNode>();
        while (pos != null) {
            if (visited.contains(pos)) {
                return pos;
            } else {
                visited.add(pos);
            }
            pos = pos.next;
        }
        return null;
    }
}

方法二:

public class Solution {
    public ListNode detectCycle(ListNode head) {
        ListNode fast, slow;
        fast = slow = head;
        while (fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;
            if (fast == slow) break;
        }
        // 上面的代码类似 hasCycle 函数
        if (fast == null || fast.next == null) {
            // fast 遇到空指针说明没有环
            return null;
        }

        // 重新指向头结点
        slow = head;
        // 快慢指针同步前进,相交点就是环起点
        while (slow != fast) {
            fast = fast.next;
            slow = slow.next;
        }
        return slow;
    }
}

时空复杂度

1.时间复杂度
方法一:
O(n),其中 N 为链表中节点的数目。我们恰好需要访问链表中的每一个节点。
方法二:
O(n)。
2.空间复杂度
​方法一:
O(n) ,其中 N 为链表中节点的数目。我们需要将链表中的每个节点都保存在哈希表当中。
方法二:
O(1)

备注

1.对快慢指针的理解
因为快指针每次比慢指针多走一步,所以快指针一定先入环,入了环之后就一直在环内来回走了,所以慢指针和快指针一定是在环内相遇
可以想象成慢指针不动,快指针一步一步的接近慢指针。
如果快指针一直走到null也没遇到慢指针,说明没环。
如果有环,怎么判断环的入口?
只需重新释放两个指针,一个从head释放,一个从相遇点释放,速度相同,两个指针必会在环入口处相遇。

876. 链表的中间结点(简单)

https://leetcode-cn.com/problems/middle-of-the-linked-list/
在这里插入图片描述

思路

因为不知道链表的长度,所以无法快速定位中间的位置。=》快慢指针
让两个指针 slow 和 fast 分别指向链表头结点 head。
每当慢指针 slow 前进一步,快指针 fast 就前进两步,这样,当 fast 走到链表末尾时,slow 就指向了链表中点。

代码

class Solution {
    public ListNode middleNode(ListNode head) {
        // 快慢指针初始化指向 head
        ListNode slow = head, fast = head;
        // 快指针走到末尾时停止
        while (fast != null && fast.next != null) {
            // 慢指针走一步,快指针走两步
            slow = slow.next;
            fast = fast.next.next;
        }
        // 慢指针指向中点
        return slow;
    }
}

时空复杂度

1.时间复杂度
O(N),其中 N是给定链表的结点数目。
2.空间复杂度
O(1),只需要常数空间存放 slow 和 fast 两个指针。​

备注

其他思路:
1.先遍历整个链表拿到链表长度,定位中间节点位置。
再重新遍历到中心节点位置,返回中心节点。
复杂度跟上面方法一样。
2.数组
链表的缺点在于不能通过下标访问对应的元素。因此我们可以考虑对链表进行遍历,同时将遍历到的元素依次放入数组 A 中。如果我们遍历到了 N 个元素,那么链表以及数组的长度也为 N,对应的中间节点即为 A[N/2]。
3.类似题目:160. 相交链表(简单)

举报

相关推荐

0 条评论