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 原题链接和其他优选题解。

相关文章
|
存储 安全 算法
网络安全与信息安全:漏洞、加密与意识
在当今数字化时代,网络安全与信息安全成为人们关注的焦点。本文将深入探讨网络安全漏洞、加密技术和安全意识等方面的知识,帮助读者更好地了解网络安全的重要性,以及如何应对潜在的安全威胁。
|
7月前
|
Java API 开发者
微服务——SpringBoot使用归纳——Spring Boot使用slf4j进行日志记录——slf4j 介绍
在软件开发中,`System.out.println()`常被用于打印信息,但大量使用会增加资源消耗。实际项目推荐使用slf4j结合logback输出日志,效率更高。Slf4j(Simple Logging Facade for Java)是一个日志门面,允许开发者通过统一方式记录日志,无需关心具体日志系统。它支持灵活切换日志实现(如log4j或logback),且具备简洁占位符和日志级别判断等优势。阿里巴巴《Java开发手册》强制要求使用slf4j,以保证日志处理方式的统一性和维护性。使用时只需通过`LoggerFactory`创建日志实例即可。
472 0
|
10月前
|
人工智能 异构计算
DisPose:清华北大等多所高校联合推出基于人物图像增强视频生成技术,实现对人物动画的准确控制和一致性
DisPose是由北京大学、中国科学技术大学、清华大学和香港科技大学联合推出的增强人物图像控制动画质量的技术。该技术通过从骨骼姿态和参考图像中提取控制信号,生成密集运动场,并保持对不同体型的泛化能力,显著提升了人物图像动画的质量和一致性。
256 14
DisPose:清华北大等多所高校联合推出基于人物图像增强视频生成技术,实现对人物动画的准确控制和一致性
|
存储 分布式计算 Hadoop
|
架构师
高级架构师考试的过关率是多少
【5月更文挑战第2天】高级架构师考试的过关率是多少
693 0
|
缓存 负载均衡 Dubbo
Dubbo服务集群容错原理(重要)
该文章主要介绍了Dubbo服务集群容错的原理,包括集群容错技术的概念、Dubbo中使用的集群容错技术种类及其原理。
|
算法 安全 数据库
真实世界的密码学(一)(4)
真实世界的密码学(一)
404 0
报错信息 "ResultCode:403" 表示后端服务器返回的错误代码是403
报错信息 "ResultCode:403" 表示后端服务器返回的错误代码是403
718 1