力扣每日练习-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)
备注
- 成环=》快慢指针
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. 相交链表(简单)