双周赛第69场:LeetCode:5961.链表最大孪生和

158 阅读3分钟
  • 本文已参与「新人创作礼」活动, 一起开启掘金创作之路

  • 双周赛第69场:LeetCode:5961.链表最大孪生和

    • 时间:2022-01-08
    • 力扣难度:Medium
    • 个人难度:Medium-
    • 数据结构:链表
    • 算法:快慢指针、双指针

双周赛第69场:LeetCode:5961.链表最大孪生和

1. 题目描述

  • 题目:原题链接

    • 在一个大小为 n 且 n 为 偶数的链表中,对于 0 <= i <= (n / 2) - 1 的 i ,第 i 个节点的孪生节点为第 n-1-i 个节点
    • 比方说,n = 4 那么节点 0 是节点 3 的孪生节点,节点 1 是节点 2 的孪生节点。这是长度为 n = 4 的链表中所有的孪生节点。
    • 孪生和:定义为一个节点和它孪生节点两者值之和。
    • 给你一个长度为偶数的链表的头节点 head ,请你返回链表的 最大孪生和 。
  • 输入输出规范

    • 输入:链表头节点head
    • 输出:最大的孪生和
  • 输入输出示例

    • 输入:head = [5,4,2,1]
    • 输出:6

2. 方法一:暴力模拟

  • 思路

    • 根据题意可知,孪生和实际上就是根据链表中点两侧对称的节点的值的和,且输入的链表长度一定为偶数,即可以通过确定的下标来计算每一个孪生和
    • 但是链表无法同时获取不同指定下标的元素,所以可以将其值储存到数组、列表等结构中
    • 首先,循环遍历一次链表,将其中的值取出来存放到一个List中
    • 然后,遍历该List,只需要遍历到该List的一半即可,循环中动态维护最大的孪生和
  • 复杂度分析:n是字符串的长度

    • 时间复杂度:O(n),遍历链表O(n),遍历半个ListO(n/2)
    • 空间复杂度:O(n)
  • 题解:直接模拟

    public int pairSum(ListNode head) {
        if(head == null) return 0;
        List<Integer> valList = new ArrayList<>();
        valList.add(head.val);
        while(head.next != null) {
            head = head.next;
            valList.add(head.val);
        }
        int n = valList.size();
        int maxTwinSum = 0;
        for(int i = 0; i < n/2; i++) {
            maxTwinSum = Math.max(maxTwinSum, valList.get(i) + valList.get(n - i - 1));
        }
        return maxTwinSum;
    }
    

3. 方法二:双指针 & 快慢指针

  • 思路:寻找链表中点、后半链表反转、求孪生和

    • 方法一的暴力模拟使用了额外的List储存链表的节点值,浪费空间

    • 为了避免空间浪费,可以仅在链表上完成求解孪生和操作,由于孪生元素分别为第 i 个元素和第 n-1-i 个元素,链表无法直接根据下标获取元素

    • 所以我们对链表进行一些修改,具体而言,分为以下三步

      • 需要通过快慢指针获取链表中点
      • 在中点位置将链表断开,由于是单向链表,无法从后向前获取元素,所以需要反转后半部分链表
      • 通过双指针的方式,同时遍历前半、后半链表,计算孪生和并找到其最大值
  • 复杂度分析:n是字符串的长度

    • 时间复杂度:O(n),寻找链表中点O(n/2),后半个链表反转O(n/2),求孪生和O(n/2)
    • 空间复杂度:O(1),无需额外空间
  • 题解:快慢指针+双指针

    public int pairSum(ListNode head) {
     if (head == null) return 0;
     ListNode fast = head;
     ListNode slow = head;
     // 1. 寻找链表中点
     while (fast != null && fast.next != null) {
         fast = fast.next.next;
         slow = slow.next;
     }
    ​
     // 2. 反转后半链表
     ListNode cur = slow;
     ListNode prev = null;
     while (cur != null) {
         ListNode temp = cur.next;
         cur.next = prev;
         prev = cur;
         cur = temp;
     }
    ​
     // 3. 计算孪生和
     int maxTwinSum = 0;
     while(head != null && prev != null) {
         maxTwinSum = Math.max(maxTwinSum, head.val + prev.val);
         head = head.next;
         prev = prev.next;
     }
     return maxTwinSum;
    }
    
  • 思考:时间复杂度

    • 虽然根据分析可得,两种方式的时间复杂度都是O(n),但是实际上在LeetCode平台提交测试的结果却有较大差异
    • 以Java为例,使用了List的暴力法约为10ms,而使用快慢指针的第二种方法只需要4ms,注意参考的日期是2022-01-09,不同时间可能后台的测试用例不同
    • 复杂度是同一级别,但是结果缺相差较大,个人分析可能是对于List方式而言,List在逐个添加链表元素时,如果链表长度较大(本题为0<n<10^5),就需要对List不断进行扩容,从而耗费更多的时间
    • 如果大家觉得有其他的因素导致该现象,欢迎评论区分享~ ~ ~

最后

新人LeetCoder,发布的题解有些会参考其他大佬的思路(参考资料的链接会放在最下面),欢迎大家关注我 ~ ~ ~ 如果本文有所帮助的话,希望大家可以给个三连「点赞」&「收藏」&「关注」 ~ ~ ~ 也希望大家有空的时候光临我的「个人博客」。