本文已参与「新人创作礼」活动,一起开启掘金创作之路。
题目链接:817.链表组件
题目描述【medium】
解题思路
1.初见
本题的题意是找出给定链表中有多少个子链表,子链表:不存在给定列表num中元素的连续链表,由此可知,本题是找到目标链表中所有的拆分点,拆分为≥1个子链表。
读题时观察到【提示】内容,目标链表的长度为N,元素值不唯一且∈[0, N),即链表节点的值包含0~N-1中所有值当且仅当一次,num数组为[0, N-1)的子集,因此想到了如下思路:链表值列表与num的差集即为拆分点集合,遍历链表记录链表长度N以及链表节点值对应的链表位置,如第一个元素为Node:4,记录数值4对应位置为0,获取拆分点集合值对应链表中的位置列表,依次遍历其中连续区间累加产生的子链表数量。
代码-1.初见
/**
* 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; }
* }
*/
import java.util.ArrayList;
import java.util.Collections;
class Solution {
public int numComponents(ListNode head, int[] nums) {
int count = 0;
ListNode ptr = head;
List<Integer> nodeList = new ArrayList<>();
while (head != null) {
count++;
head = head.next;
}
int[] value = new int[count];
count = 0;
while (ptr != null) {
value[ptr.val] = count;
count++;
ptr = ptr.next;
}
int[] arr = new int[count];
for (int tmp : nums) {
arr[tmp] = 1;
}
List<Integer> unlinkList = new ArrayList<>();
for (int i = 0; i < count; i++) {
if (arr[i] == 0) {
unlinkList.add(value[i]);
}
}
if (unlinkList.size() == 0) {
return 1;
}
// 计算组件数
Collections.sort(unlinkList);
int lo = 0;
int hi = count - 1;
int st = 0;
int end = st + 1;
int total = 0;
while (end < unlinkList.size()) {
while (end < unlinkList.size() && (unlinkList.get(end) - unlinkList.get(end - 1) == 1)) {
end++;
}
if (end == unlinkList.size()) {
break;
}
total += unlinkList.get(st) != lo ? 2 : 1;
st = end;
end = st + 1;
lo = unlinkList.get(st);
}
total += unlinkList.get(st) > lo ? 1 : 0;
total += unlinkList.get(end - 1) < hi ? 1 : 0;
return total;
}
}
- 时间复杂度:O(N)【拆分点数量少】 ~ O(NlogN)【拆分点数量为N最差情况排序】
- 空间复杂度:O(N)
2.探索
上述思路中存在的局限点:
- 依赖
[0, n-1)每个数值必须且仅存在一个这个特性,增加了本题的限定; - 并未用到链表本身的性质;
- 计算子链表数时通过遍历拆分点集合连续子区间进行计算;
- 当题目限定进一步放宽时,如链表中节点为不同的值而没有限定数值范围时无法使用;
进一步思考上述局限点,计算子链表数量时需要找到每一个子链表,子链表的定义为:不存在给定列表num中元素的连续链表,即链表节点为num中元素即为拆分点,有如下思路:依次遍历链表节点,双指针,当前位置元素为num而下一个元素不为num元素时即为一个子链表。
代码-2.探索
class Solution {
public int numComponents(ListNode head, int[] num) {
// 记录num数组
Set<Integer> numSet = new HashSet();
for (int item: num) numSet.add(item);
ListNode cur = head;
int ans = 0;
while (cur != null) {
if (numSet.contains(cur.val)
&& (cur.next == null || !numSet.contains(cur.next.val))) {
ans++;
}
cur = cur.next;
}
return ans;
}
}
- 时间复杂度:O(N)
- 空间复杂度:O(N)
3.归途
本题最终是属于存在性问题,【1.初见】中使用方式是利用元素值的特殊属性,利用空间换时间方式,快速定位元素位置;【2.探索】中忽略本题中关于数值的限制,从链表结构本身出发,分析子链表的形成场景,进一步利用HashSet的优势,contains()方法底层使用HashMap#get(key)实现时间复杂度仅为O(1)。