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

简介: 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 <= nums.length <= 504<=nums.length<=50
  • 1 <= nums[i] <= 1001<=nums[i]<=100


朴素解法



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


代码:


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(n4)
  • 空间复杂度:O(1)O(1)


哈希表



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


代码:


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(n3)
  • 空间复杂度:O(C)O(C)


哈希表



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


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


一些细节:由于 nums[d] - nums[c]nums[d]nums[c] 可能为负,在使用数组代替哈希表时,可利用 1 <= nums[i] <= 1001<=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(n2)
  • 空间复杂度:O(C)O(C)


多维背包



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


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


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


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


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


  • nums[i - 1]nums[i1] 不参与组成,此时有:f[i - 1][j][k]f[i1][j][k];
  • nums[i - 1]nums[i1] 参与组成,此时有:f[i - 1][j - t][k - 1]f[i1][jt][k1];


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


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


代码(维度优化见 P2P2):


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(n1104)
  • 空间复杂度:O(n * 110 * 4)O(n1104)


最后



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


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


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


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

相关文章
|
2月前
DAY-2 | 哈希表、指针与区间划分:字符种数统计问题
```markdown ## 题干 [牛客网链接](https://www.nowcoder.com/practice/eb94f6a5b2ba49c6ac72d40b5ce95f50) ## 题解 1. **查表法(哈希表)**:利用数组标记出现过的 ASCII 值小于127的字符,首次出现计数,重复则忽略。 2. **指针与区间划分(回头法)**:遍历字符串,对每个字符检查其前所有字符是否重复,重复则不计数。 ## 方法总结 - 哈希表在去重问题中非常实用,可多做相关练习。 - 使用`continue`时注意避免死循环,确保循环变量会改变。 - 多回顾此类问题以巩固理解。 ```
29 2
|
2月前
|
对象存储
统计数组中的重复数据的数量
这篇文章总结了5种统计数组中重复数据数量的方法。方法1和4使用for循环和对象存储计数;方法2和5利用`reduce`函数,其中方法5是最简写形式;方法3是特定场景下的应用,针对特定值计数。所有方法最终都返回一个对象,键为数组元素,值为出现次数。
|
2月前
|
算法 程序员 测试技术
【数据结构-哈希表 一】【原地哈希】:缺失的第一个正整数
【数据结构-哈希表 一】【原地哈希】:缺失的第一个正整数
43 0
|
9月前
|
人工智能 BI 索引
【Leetcode -598.范围求和Ⅱ -599.两个列表的最小索引总和】
【Leetcode -598.范围求和Ⅱ -599.两个列表的最小索引总和】
30 0
|
2月前
【每日一题Day303】统计点对的数目 | 哈希表+双指针
【每日一题Day303】统计点对的数目 | 哈希表+双指针
28 0
|
2月前
查询多维数组最大值或者最小值
查询多维数组最大值或者最小值
32 0
|
数据挖掘
白话Elasticsearch43-深入聚合数据分析之案例实战__排序:按每种颜色的平均销售额升序排序
白话Elasticsearch43-深入聚合数据分析之案例实战__排序:按每种颜色的平均销售额升序排序
67 0
给定一个数值,计算最合适的行列数量的代码
给定一个数值,计算最合适的行列数量的代码
80 0