Given an integer array `A`, and an integer `target`, return the number of tuples `i, j, k`  such that `i As the answer can be very large, return it modulo 10^9 + 7.

Example 1:

Input: A = [1,1,2,2,3,3,4,4,5,5], target = 8
Output: 20
Explanation:
Enumerating by the values (A[i], A[j], A[k]):
(1, 2, 5) occurs 8 times;
(1, 3, 4) occurs 8 times;
(2, 2, 4) occurs 2 times;
(2, 3, 3) occurs 2 times.

Example 2:

Input: A = [1,1,2,2,2,2], target = 5
Output: 12
Explanation:
A[i] = 1, A[j] = A[k] = 2 occurs 12 times:
We choose one 1 from [1,1] in 2 ways,
and two 2s from [2,2,2,2] in 6 ways.

Note:

  1. 3 <= A.length <= 3000
  2. 0 <= A[i] <= 100
  3. 0 <= target <= 300

这道题是之前那道 [3Sum](http://www.cnblogs.com/grandyang/p/4481576.html) 的拓展,之前那道题是说没有重复数字,而这道题却有大量的重复数字,所以每个组合可能会大量重复出现,让我们统计出所有组合的总出现次数,并对一个超大数取余。这样难度就比之前提升了不少,但是本质还是类似,都是使用双指针来解题。因为有很多重复的数字,所以将相同的数字放在一起便于统计,可以对数组进行排序,然后遍历数组,先确定一个数字 A[i],则只需要找另外两个数字,使得其和为 sum = target-A[i]。然后使用两个指针j和k分别初始化为 i+1 和 n-1,若 A[j]+A[k] 小于 sum,则将j自增1;若 A[j]+A[k] 大于 sum,则将k自减1;若 A[j]+A[k] 正好等于 sum,则此时需要统计重复数字的个数,假设跟 A[j] 相同的数字有 left 个,跟 A[k] 相同的数字有 right 个。若 A[j] 不等于 A[k],那么直接用 left 乘以 right 就是出现次数了,但如果 A[j] 等于 A[k],则相当于在 left+right 中选两个数字的不同选法,就是初高中的排列组合的知识了。完事之后j要加上 left,k要减去 right,最终别忘了 res 要对超大数取余,参见代码如下:


解法一:

class Solution {
public:
int threeSumMulti(vector<int>& A, int target) {
long res = 0, n = A.size(), M = 1e9 + 7;
sort(A.begin(), A.end());
for (int i = 0; i < n - 2; ++i) {
int sum = target - A[i];
int j = i + 1, k = n - 1;
while (j < k) {
if (A[j] + A[k] < sum) {
++j;
} else if (A[j] + A[k] > sum) {
--k;
} else {
int left = 1, right = 1;
while (j + left < k && A[j + left] == A[j]) ++left;
while (j + left <= k - right && A[k - right] == A[k]) ++right;
res += A[j] == A[k] ? (k - j + 1) * (k - j) / 2 : left * right;
j += left;
k -= right;
}
}
}
return res % M;
}
};

我们也可以换一种思路来解题,首先使用一个 HashMap 统计出每一个数字和其出现次数之间的映射,注意这里次数最好设定为长整型 long,因为之后做乘法的时候有可能会超过整型最大值,然后遍历 HashMap 中所有的任意的两个数字组合i和j,则第三个数字k可由 target-i-j 计算得到,若k不在 HashMap 中,则说明该组合不存在,直接跳过。若 i,j,k 三个数字相等,相当于在 numCnt[i] 中任选3个数字的方法,还是排列组合的知识;若i和j相等,但不等于k,则是在 numCnt[i] 中任选2个数字的方法个数再乘以 numCnt[k];若三个数字都不相同,则直接用这三个数字 numCnt[i],numCnt[j],numCnt[k] 相乘即可,最终别忘了 res 要对超大数取余,参见代码如下:


解法二:

class Solution {
public:
int threeSumMulti(vector<int>& A, int target) {
long res = 0, M = 1e9 + 7;
unordered_map<int, long> numCnt;
for (int num : A) ++numCnt[num];
for (auto a : numCnt) {
for (auto b : numCnt) {
int i = a.first, j = b.first, k = target - i - j;
if (!numCnt.count(k)) continue;
if (i == j && j == k) {
res += numCnt[i] * (numCnt[i] - 1) * (numCnt[i] - 2) / 6;
} else if (i == j && j != k) {
res += numCnt[i] * (numCnt[i] - 1) / 2 * numCnt[k];
} else if (i < j && j < k) {
res += numCnt[i] * numCnt[j] * numCnt[k];
}
}
}
return res % M;
}
};

接下来看一种更加简洁的解法,还是使用一个 HashMap 建立数字跟其出现次数之间的映射,但是这里并不是建立原数组每个数字跟其出现次数之间的映射,而是建立数组中任意两个数字之和的出现次数的映射,该数字之和出现了几次,就说明有多少个不同的两个数组合。那么此时遍历每个数字 A[i],将 numCnt[target-A[i]] 加入结果中,因为和为 target-A[i] 的每个双数组合跟 A[i] 一起都可以组成符合题意的三数组合。此时由于新加入了数字 A[i],还要从0遍历到i,将每个数字加上 A[i] 形成新的双数组合,将其和的映射值加1,这种思路真是碉堡了,参见代码如下:


解法三:

class Solution {
public:
int threeSumMulti(vector<int>& A, int target) {
int res = 0, n = A.size(), M = 1e9 + 7;
unordered_map<int, int> numCnt;
for (int i = 0; i < n; ++i) {
res = (res + numCnt[target - A[i]]) % M;
for (int j = 0; j < i; ++j) {
int sum = A[i] + A[j];
++numCnt[sum];
}
}
return res;
}
};

我们也可以使用动态规划 Dynmaic Programming 来解这道题,使用一个三维的 dp 数组,其中 dp[i][j][k] 表示在范围 [0, i] 内的子数组使用k个数字能组成和为j的组合个数,则 dp[n][target][3] 即为所求,将 dp[i][0][0] 初始化为1。接下来推导状态转移方程,要新加入一个数字 A[i] 的话,那么不管这个数字能否组成新的组合,之前的所有情况这里都是继承的,即 dp[i][j][k] 至少应该加上 dp[i-1][j][k],然后再来看 A[i] 是否能构成新的数字,但假如 j 是小于 A[i] 的,那么 A[i] 是无法组成和为j的三数之和的,只有当 j 大于等于 A[i] 时才有可能,那么就还应该加上 dp[i-1][j-A[i]][k-1] 的所有情况,它们可以跟 A[i] 组成和为j的三数组合(注意代码中由于i是从1开始的,所以是 A[i-1]),参见代码如下:


解法四:

class Solution {
public:
int threeSumMulti(vector<int>& A, int target) {
int n = A.size(), M = 1e9 + 7;
vector<vector<vector<int>>> dp(n + 1, vector<vector<int>>(target + 1, vector<int>(4)));
for (int i = 0; i <= n; ++i) dp[i][0][0] = 1;
for (int i = 1; i <= n; ++i) {
for (int j = 0; j <= target; ++j) {
for (int k = 1; k <= 3; ++k) {
dp[i][j][k] = (dp[i][j][k] + dp[i - 1][j][k]) % M;
if (j >= A[i - 1]) {
dp[i][j][k] = (dp[i][j][k] + dp[i - 1][j - A[i - 1]][k - 1]) % M;
}
}
}
}
return dp[n][target][3];
}
};

我们还可以优化一下空间复杂度,去掉i这一维度,因为i这维度只跟 i-1 相关,可以采用逐层覆盖的方法,但一定要注意的是,这样的中间两个 for 循环应该是从后往前遍历,不然会累加出错误的值,参见代码如下:


解法五:

class Solution {
public:
int threeSumMulti(vector<int>& A, int target) {
int n = A.size(), M = 1e9 + 7;
vector<vector<int>> dp(target + 1, vector<int>(4));
dp[0][0] = 1;
for (int i = 1; i <= n; ++i) {
for (int j = target; j >= A[i - 1]; --j) {
for (int k = 3; k >= 1; --k) {
dp[j][k] = (dp[j][k] + dp[j - A[i - 1]][k - 1]) % M;
}
}
}
return dp[target][3];
}
};

Github 同步地址:

https://github.com/grandyang/leetcode/issues/923

类似题目:

3Sum Smaller

3Sum Closest

3Sum

参考资料:

https://leetcode.com/problems/3sum-with-multiplicity/

https://leetcode.com/problems/3sum-with-multiplicity/discuss/181098/Java-O(n2)-code-Sort-and-Match.

https://leetcode.com/problems/3sum-with-multiplicity/discuss/181128/10-lines-Super-Super-Easy-Java-Solution

https://leetcode.com/problems/3sum-with-multiplicity/discuss/181131/C%2B%2BJavaPython-O(N-%2B-101-*-101)

https://leetcode.com/problems/3sum-with-multiplicity/discuss/181125/Knapsack-O(n-*-target)-or-Straightforward-O(n2)

[LeetCode All in One 题目讲解汇总(持续更新中...)](https://www.cnblogs.com/grandyang/p/4606334.html)

[LeetCode] 923. 3Sum With Multiplicity 三数之和的多种情况的更多相关文章

  1. [LeetCode] 16. 3Sum Closest 最近三数之和

    Given an array nums of n integers and an integer target, find three integers in nums such that the s ...

  2. LeetCode 259. 3Sum Smaller (三数之和较小值) $

    Given an array of n integers nums and a target, find the number of index triplets i, j, k with 0 < ...

  3. LeetCode:最接近的三数之和【16】

    LeetCode:最接近的三数之和[16] 题目描述 给定一个包括 n 个整数的数组 nums 和 一个目标值 target.找出 nums 中的三个整数,使得它们的和与 target 最接近.返回这 ...

  4. [LeetCode] 3Sum Closest 最近三数之和

    Given an array S of n integers, find three integers in S such that the sum is closest to a given num ...

  5. Java实现 LeetCode 16 最接近的三数之和

    16. 最接近的三数之和 给定一个包括 n 个整数的数组 nums 和 一个目标值 target.找出 nums 中的三个整数,使得它们的和与 target 最接近.返回这三个数的和.假定每组输入只存 ...

  6. leetcode第15题:三数之和

    给定一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?找出所有满足条件且不重复的三元组. 注意:答案中不可以包含重复的三元组. ...

  7. LeetCode(15):三数之和

    Medium! 题目描述: 给定一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?找出所有满足条件且不重复的三元组. 注意:答 ...

  8. LeetCode 16. 最接近的三数之和(3Sum Closest)

    题目描述 给定一个包括 n 个整数的数组 nums 和 一个目标值 target.找出 nums 中的三个整数,使得它们的和与 target 最接近.返回这三个数的和.假定每组输入只存在唯一答案. 例 ...

  9. [Swift]LeetCode923.三数之和的多种可能 | 3Sum With Multiplicity

    Given an integer array A, and an integer target, return the number of tuples i, j, k  such that i &l ...

随机推荐

  1. jQuery 源码分析(五) map函数 $.map和$.fn.map函数 详解

    $.map() 函数用于使用指定函数处理数组中的每个元素(或对象的每个属性),并将处理结果封装为新的数组返回,该函数有三个参数,如下: elems Array/Object类型 指定的需要处理的数组或 ...

  2. Kubernetes RBAC授权普通用户对命名空间访问权限

    Kubernetes RBAC授权普通用户对命名空间访问权限 官方文档:https://www.cnblogs.com/xiangsikai/p/11413970.html kind: Role ap ...

  3. F#周报2019年第24期

    新闻 ML.NET 1.1发布与模型构建器升级 .NET Core 3.0预览版6发布 尝试新的System.Text.Json API F#调用Infer.NET 匿名记录类型文档 了解FableC ...

  4. 转 tty 设备读写

    转自https://feng-qi.github.io/2017/05/04/how-to-read-write-to-tty-device/ <p>这是 StackExchange 上的 ...

  5. WPF 基础总结(学习建议)

    举个简单得例子, 类似造房子, 当然实际上可能非常细, 对应的如下所示: 在此之前, 需要了解的是. WPF项目是怎么启动的 Xaml的结构是怎么样组成, 命名控件定义引用的方法. 知道了如何在Xam ...

  6. 一款对Postman支持较好的接口文档生成工具

    最近要编写接口文档给测试和前端看,通过网上查阅资料,也认识了很多款接口文档生成工具,比如易文档.ApiPost.ShowDoc.YApi.EoLinker.DOClever.apizza等,通过对这几 ...

  7. shell编程基本语法和变量

    一.编写shell脚本基本格式 拿最简单的hello word举例 .#!/bin/bash echo 'hello world' .#!/bin/bash:告诉计算机,使用bash解释器来执行代码 ...

  8. JAVA8新特性--集合流操作Stream

    原文链接:https://blog.csdn.net/bluuusea/article/details/79967039 Stream类全路径为:java.util.stream.Stream 对St ...

  9. 动态样式语言less初识

    资料参考与http://lesscss.cn/ Bootstrap--(less)下载安装配置 http://lesscss.cn Less使用两种方法 (1)在客户端使用LESS--学习 编写 x. ...

  10. linux时间校对

    在虚拟机中查看安装的linux时间,总是不对,于是查阅资料,有很多解决办法,我个人比较倾向于用ntpdate的方式更新时间. 感觉比较好用的一个方法: 输入date查看时间,发现不对,相差甚远!(当前 ...