每日一题:817.链表组件

125 阅读2分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

题目链接:817.链表组件

题目描述【medium】

image.png

解题思路

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)。

道阻且长,诸位共勉!