返回

优化题解:四步解码 LeetCode 特殊四元组难题

后端

统计特殊四元组:LeetCode 上的一道有趣题目

探索一种有趣而有效的解决方案

在 LeetCode 上,有着数不胜数的有趣题目,它们旨在挑战我们的编程技能和算法知识。今天,我们将深入探讨其中一道引人入胜的题目:统计特殊四元组 。这道题不仅考验我们的算法设计能力,还要求我们对数组和排序等数据结构有深入的理解。

题目概述

给你一个下标从 0 开始的整数数组 nums,请返回满足以下条件的不同四元组 (a, b, c, d) 的数目:

  • a + b + c = d
  • a < b < c < d

例如,如果 nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],那么满足条件的四元组有:

  • (1, 2, 3, 6)
  • (1, 2, 3, 7)
  • (1, 2, 3, 8)
  • (1, 2, 3, 9)
  • (1, 2, 3, 10)
  • (2, 3, 4, 9)
  • (2, 3, 4, 10)
  • (2, 3, 5, 10)

因此,我们应该返回 8

解题思路

乍一看,这道题似乎有些棘手,但仔细分析后,我们可以发现一个巧妙的解决方案。问题的关键在于利用数组的有序性。我们可以依次枚举每个元素作为四元组中的 a,然后利用二分查找在剩余数组中寻找满足条件的 bcd

具体步骤如下:

  1. 枚举 a 遍历数组,依次枚举每个元素作为四元组中的 a
  2. 寻找 b 对于当前的 a,我们可以利用数组的有序性,通过二分查找找到满足条件的 b
  3. 寻找 c 对于当前的 ab,我们可以同样利用二分查找找到满足条件的 c
  4. 统计 d 对于当前的 abc,我们可以通过简单的计算得到满足条件的 d
  5. 统计结果: 重复以上步骤,对于每个 a,统计满足条件的四元组数量,并将结果累加得到最终结果。

优化算法

为了进一步优化算法的效率,我们可以对数组进行预处理,以便在二分查找时能够快速找到满足条件的元素。具体来说,我们可以对数组进行排序,并保存每个元素的索引。这样,在二分查找时,我们就可以通过索引直接找到元素,而无需遍历整个数组。

代码实现

import java.util.Arrays;

class Solution {
    public int countQuadruplets(int[] nums) {
        int n = nums.length;
        if (n < 4) {
            return 0;
        }

        // 对数组进行排序
        Arrays.sort(nums);

        // 保存每个元素的索引
        int[] indexes = new int[n];
        for (int i = 0; i < n; i++) {
            indexes[i] = i;
        }

        // 统计满足条件的四元组数量
        int count = 0;
        for (int i = 0; i < n - 3; i++) {
            // 跳过重复元素
            if (i > 0 && nums[i] == nums[i - 1]) {
                continue;
            }

            // 寻找满足条件的 b
            int left = i + 1;
            int right = n - 1;
            int target = nums[i] + nums[i + 1] + nums[i + 2];
            int indexB = binarySearch(nums, indexes, left, right, target);
            if (indexB < 0) {
                continue;
            }

            // 寻找满足条件的 c
            left = indexB + 1;
            right = n - 1;
            target = nums[i] + nums[i + 1] + nums[i + 2] + nums[indexB];
            int indexC = binarySearch(nums, indexes, left, right, target);
            if (indexC < 0) {
                continue;
            }

            // 统计满足条件的 d
            int indexD = n - 1;
            while (indexD >= 0 && nums[indexD] == nums[indexC]) {
                indexD--;
            }
            count += indexD - indexC + 1;
        }

        return count;
    }

    // 二分查找
    private int binarySearch(int[] nums, int[] indexes, int left, int right, int target) {
        while (left <= right) {
            int mid = (left + right) / 2;
            if (nums[indexes[mid]] == target) {
                return indexes[mid];
            } else if (nums[indexes[mid]] < target) {
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }

        return -1;
    }
}

常见问题解答

  1. 为什么需要对数组进行排序?

排序是为了利用数组的有序性进行二分查找。二分查找是一种高效的搜索算法,可以快速找到满足条件的元素。

  1. 为什么需要保存每个元素的索引?

保存每个元素的索引是为了在二分查找时能够快速找到元素。直接通过索引查找元素比遍历整个数组要高效得多。

  1. 在枚举 a 时,为什么需要跳过重复元素?

跳过重复元素是为了避免重复计算。如果我们不跳过重复元素,那么对于相同的 a,我们会重复计算多次。

  1. 在寻找 b 时,为什么需要设置 left = i + 1

因为 a < b,所以我们只需要在 a 之后的元素中寻找 b

  1. 在统计 d 时,为什么需要循环递减 indexD

因为 a < b < c < d,所以 d 一定是数组中最大的元素。因此,我们可以从数组的末尾开始循环递减 indexD,直到找到满足条件的 d