1995. 统计特殊四元组 :「枚举」&「哈希表」&「多维背包」

题目描述

这是 LeetCode 上的 1995. 统计特殊四元组 ,难度为 简单

Tag : 「枚举」、「哈希表」、「背包 DP」

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

nums[a] + nums[b] + nums[c] == nums[d],且 a < b < c < d

示例 1:

输入:nums = [1,2,3,6]

输出:1

解释:满足要求的唯一一个四元组是 (0, 1, 2, 3) 因为 1 + 2 + 3 == 6 。
复制代码

示例 2:

输入:nums = [3,3,6,4,5]

输出:0

解释:[3,3,6,4,5] 中不存在满足要求的四元组。
复制代码

示例 3:

输入:nums = [1,1,1,3,5]

输出:4

解释:满足要求的 4 个四元组如下:
- (0, 1, 2, 3): 1 + 1 + 1 == 3
- (0, 1, 3, 4): 1 + 1 + 3 == 5
- (0, 2, 3, 4): 1 + 1 + 3 == 5
- (1, 2, 3, 4): 1 + 1 + 3 == 5
复制代码

提示:

  • 4 < = n u m s . l e n g t h < = 50 4 <= nums.length <= 50
  • 1 < = n u m s [ i ] < = 100 1 <= nums[i] <= 100

朴素解法

利用数据范围只有 50 50 ,可直接根据题意进行模拟。

代码:

class Solution {
    public int countQuadruplets(int[] nums) {
        int n = nums.length, ans = 0;
        for (int a = 0; a < n; a++) {
            for (int b = a + 1; b < n; b++) {
                for (int c = b + 1; c < n; c++) {
                    for (int d = c + 1; d < n; d++) {
                        if (nums[a] + nums[b] + nums[c] == nums[d]) ans++;
                    }
                }
            }
        }
        return ans;
    }
}
复制代码
  • 时间复杂度: O ( n 4 ) O(n^4)
  • 空间复杂度: O ( 1 ) O(1)

哈希表

利用等式关系 n u m s [ a ] + n u m s [ b ] + n u m s [ c ] = n u m s [ d ] nums[a] + nums[b] + nums[c] = nums[d] ,可以调整枚举 c c 的顺序为「逆序」,每次 c c 往左移动一个单位, d d 的可取下标范围增加一个(即 c + 1 c + 1 位置),使用数组代替哈希表对 n u m s [ d ] nums[d] 的个数进行统计,可使复杂度下降到 O ( n 3 ) O(n^3)

代码:

class Solution {
    public int countQuadruplets(int[] nums) {
        int n = nums.length, ans = 0;
        int[] cnt = new int[10010];
        for (int c = n - 2; c >= 2; c--) {
            cnt[nums[c + 1]]++;
            for (int a = 0; a < n; a++) {
                for (int b = a + 1; b < c; b++) {
                    ans += cnt[nums[a] + nums[b] + nums[c]];
                }
            }
        }
        return ans;
    }
}
复制代码
  • 时间复杂度: O ( n 3 ) O(n^3)
  • 空间复杂度: O ( C ) O(C)

哈希表

更进一步,根据等式关系进行移项可得: n u m s [ a ] + n u m s [ b ] = n u m s [ d ] n u m s [ c ] nums[a] + nums[b] = nums[d] - nums[c] ,其中各下标满足 a < b < c < d a < b < c < d

我们可在「逆序」枚举 b b 时,将新产生的 c c (即 b + 1 b + 1 位置)所能产生的新 n u m s [ d ] n u m s [ c ] nums[d] - nums[c] 的值存入哈希表(即 从 [ b + 2 , n ) [b + 2, n) 范围内枚举 d d ),最后通过枚举 a a 来统计答案。

一些细节:由于 n u m s [ d ] n u m s [ c ] nums[d] - nums[c] 可能为负,在使用数组代替哈希表时,可利用 1 < = n u m s [ i ] < = 100 1 <= nums[i] <= 100 做一个值偏移。

代码:

class Solution {
    public int countQuadruplets(int[] nums) {
        int n = nums.length, ans = 0;
        int[] cnt = new int[10010];
        for (int b = n - 3; b >= 1; b--) {
            for (int d = b + 2; d < n; d++) cnt[nums[d] - nums[b + 1] + 200]++;
            for (int a = 0; a < b; a++) ans += cnt[nums[a] + nums[b] + 200];
        }
        return ans;
    }
}
复制代码
  • 时间复杂度: O ( n 2 ) O(n^2)
  • 空间复杂度: O ( C ) O(C)

多维背包

利用等式关系 n u m s [ a ] + n u m s [ b ] + n u m s [ c ] = n u m s [ d ] nums[a] + nums[b] + nums[c] = nums[d] ,具有明确的「数值」和「个数」关系,可将问题抽象为组合优化问题求方案数。

限制组合个数的维度有两个,均为「恰好」限制,转换为「二维费用背包问题求方案数」问题。

定义 f [ i ] [ j ] [ k ] f[i][j][k] 为考虑前 i i 个物品(下标从 1 1 开始),凑成数值恰好 j j ,使用个数恰好为 k k 的方案数。

最终答案为 i = 3 n 1 ( f [ i ] [ n u m s [ i ] ] [ 3 ] ) \sum_{i = 3}^{n - 1}(f[i][nums[i]][3]) ,起始状态 f [ 0 ] [ 0 ] [ 0 ] = 1 f[0][0][0] = 1 代表不考虑任何物品时,所用个数为 0 0 ,凑成数值为 0 0 的方案数为 1 1

不失一般性考虑 f [ i ] [ j ] [ k ] f[i][j][k] 该如何转移,根据 n u m s [ i 1 ] nums[i - 1] 是否参与组合进行分情况讨论:

  • n u m s [ i 1 ] nums[i - 1] 不参与组成,此时有: f [ i 1 ] [ j ] [ k ] f[i - 1][j][k] ;
  • n u m s [ i 1 ] nums[i - 1] 参与组成,此时有: f [ i 1 ] [ j t ] [ k 1 ] f[i - 1][j - t][k - 1] ;

最终 f [ i ] [ j ] [ k ] f[i][j][k] 为上述两种情况之和,最终统计 i = 3 n 1 ( f [ i ] [ n u m s [ i ] ] [ 3 ] ) \sum_{i = 3}^{n - 1}(f[i][nums[i]][3]) 即是答案。

利用 f [ i ] [ j ] [ k ] f[i][j][k] 仅依赖于 f [ i 1 ] [ j ] [ k ] f[i - 1][j][k] j k 维度值更小的 f [ i 1 ] [ X ] [ X ] f[i - 1][X][X] ,可进行维度优化,并在转移过程中统计答案。

代码(维度优化见 P 2 P2 ):

class Solution {
    public int countQuadruplets(int[] nums) {
        int n = nums.length;
        int[][][] f = new int[n + 1][110][4];
        f[0][0][0] = 1;
        for (int i = 1; i <= n; i++) {
            int t = nums[i - 1];
            for (int j = 0; j < 110; j++) {
                for (int k = 0; k < 4; k++) {
                    f[i][j][k] += f[i - 1][j][k];
                    if (j - t >= 0 && k - 1 >= 0) f[i][j][k] += f[i - 1][j - t][k - 1];
                }
            }
        }
        int ans = 0;
        for (int i = 3; i < n; i++) ans += f[i][nums[i]][3];
        return ans;
    }
}
复制代码
class Solution {
    public int countQuadruplets(int[] nums) {
        int n = nums.length, ans = 0;
        int[][] f = new int[110][4];
        f[0][0] = 1;
        for (int i = 1; i <= n; i++) {
            int t = nums[i - 1];
            ans += f[t][3];
            for (int j = 109; j >= 0; j--) {
                for (int k = 3; k >= 0; k--) {
                    if (j - t >= 0 && k - 1 >= 0) f[j][k] += f[j - t][k - 1];
                }
            }
        }
        return ans;
    }
}
复制代码
  • 时间复杂度: O ( n 110 4 ) O(n * 110 * 4)
  • 空间复杂度: O ( n 110 4 ) O(n * 110 * 4)

最后

这是我们「刷穿 LeetCode」系列文章的第 No.1995 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。

在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。

为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:github.com/SharingSour…

在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。

猜你喜欢

转载自juejin.im/post/7046948637435723812