[LeetCode] 923. 3Sum With Multiplicity 三數之和的多種情況

2021-01-18 刷盡天下

Given an integer array A, and an integer target, return the number of tuples i,j,k  such that i<j<k and A[i]+A[j]+A[k]==target.

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:

3<=A.length<=3000

0<=A[i]<=100

0<=target<=300


這道題是之前那道 3Sum 的拓展,之前那道題是說沒有重複數字,而這道題卻有大量的重複數字,所以每個組合可能會大量重複出現,讓我們統計出所有組合的總出現次數,並對一個超大數取餘。這樣難度就比之前提升了不少,但是本質還是類似,都是使用雙指針來解題。因為有很多重複的數字,所以將相同的數字放在一起便於統計,可以對數組進行排序,然後遍歷數組,先確定一個數字 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 題目講解匯總(持續更新中...)


相關焦點

  • LeetCode-16.最接近的三數之和(3Sum Closest)
    最接近的三數之和給定一個包括 n 個整數的數組 nums 和 一個目標值 target。找出 nums 中的三個整數,使得它們的和與 target 最接近。返回這三個數的和。假定每組輸入只存在唯一答案。
  • LeetCode習題解析|2. 3Sum
    下面開始今天的LeetCode習題解析💡原題地址:https://leetcode.com/problems/3sum/題目描述:給定一個數組,找出所有的可能的三個數,使這三個數之和等於0(答案中不能有重複的三元組)
  • ​LeetCode刷題實戰15題: 三數之和
    今天和大家聊的問題叫做三數之和 ,我們先來看題面:https://leetcode.com/problems/3sum/描述給定一個整數的數組,要求尋找當中所有的a,b,c三個數的組合,使得三個數的和為0.注意,
  • 三數之和 --- leetcode
    給你一個包含 n 個整數的數組 nums,判斷 nums 中是否存在三個元素 a,b,c ,使得 a + b + c = 0 ?
  • [LeetCode] 918. Maximum Sum Circular Subarray 環形子數組的最大和
    Example 1:Input: [1,-2,3,-2]Output: 3Explanation: Subarray [3] has maximum sum 3Example 2:Input: [5,-3,5]Output: 10 Explanation: Subarray
  • leetcode-16. 3Sum Closest
    找出 nums 中的三個整數,使得它們的和與 target 最接近。返回這三個數的和。假定每組輸入只存在唯一答案。解法一 暴力解法寫三個循環遍歷,求出三個數的和和目標值進行比較,選取差值最小的即可。Javapublic class _3_Sum_Closest { public static int threeSumClosest(int[] nums,int target) {  int res=Integer.MAX_VALUE;  int sum=0;  for(int i=0;i<
  • 哈希表:解決了兩數之和,那麼能解決三數之和麼?
    ❝用哈希表解決了兩數之和,那麼三數之和呢?❞第15題. 三數之和給你一個包含 n 個整數的數組 nums,判斷 nums 中是否存在三個元素 a,b,c ,使得 a + b + c = 0 ?請你找出所有滿足條件且不重複的三元組。
  • [LeetCode] 996. Number of Squareful Arrays 平方數組的個數
    Given an array A of non-negative integers, the array is squareful if for every pair of adjacent elements, their sum
  • 兩數之和
    兩數之和給你兩個 非空 的鍊表,表示兩個非負的整數。它們每位數字都是按照 逆序 的方式存儲的,並且每個節點只能存儲 一位 數字。請你將兩個數相加,並以相同形式返回一個表示和的鍊表。你可以假設除了數字 0 之外,這兩個數都不會以 0 開頭。示例 1:輸入:l1 = [2,4,3], l2 = [5,6,4]輸出:[7,0,8]解釋:342 + 465 = 807.
  • Split Array Largest Sum
    給定一個正整數數組nums, 將其分成m個子數組(子數組至少應該有一個元素),那麼每一次分的子數組中的元素求和,會有一個最大值,建設是sum。問題是求所有的分法中,sum最小的值是多少。題目中舉了一個例子。
  • [leetcode] 15. 3Sum
    Given an array S of n integers, are there elements a, b, c in S
  • 給你代碼:leetcode隨筆
    整數的各位積和之差給你一個整數 n,請你幫忙計算並返回該整數「各位數字之積」與「各位數字之和」的差。example 1:輸入:n = 234輸出:15 解釋:各位數之積 = 2 * 3 * 4 = 24 各位數之和 = 2 + 3 + 4 = 9 結果 = 24 - 9 = 15來源:力扣(LeetCode)這題非常簡單。只要轉換一下字符串就可以做。
  • [LeetCode] 976. Largest Perimeter Triangle 最大周長的三角形
    Example 1:Input: [2,1,2]Output: 5Example 2:Input: [1,2,1]Output: 0Example 3:Input: [3,2,3,4]Output: 10
  • 一個函數秒殺 2Sum 3Sum 4Sum 問題
    對於修改後的問題,關鍵難點是現在可能有多個和為 target 的數對兒,還不能重複,比如上述例子中 [1,3] 和 [3,1] 就算重複,只能算一次。二、3Sum 問題這是力扣第 15 題「三數之和」:
  • Python識別完美數
    所有的完美數都是三角形數。例如:6=1+2+328=1+2+3+...+6+78128=1+2+3…+126+1273. 所有完美數的倒數都是調和數。例如:1/1+1/2+1/3+1/6=21/1+1/2+1/4+1/7+1/14+1/28=21/1+1/2+1/4+1/8+1/16+1/31+1/62+1/124+1/248+1/496=24. 可以表示成連續奇立方數之和。除6以外的完全數,都可以表示成連續奇立方數之和,並規律式增加。
  • 數據結構與算法:05 Leetcode同步練習(一)
    目錄題目01:兩數之和題目02:刪除排序數組中的重複項題目03:移除元素題目04:最大子序和題目05:盛最多水的容器題目06:搜索旋轉排序數組題目07:數組中的第K個最大元素題目08:除自身以外數組的乘積題目09:尋找兩個有序數組的中位數題目10:缺失的第一個正數
  • LeetCode刷題第三周【數組(簡單)】
    示例 2:輸入:3輸出:2解釋:F(3) = F(2)) + F(1)) = 1 + 1 = 2.示例 3:輸入:4輸出:3解釋:F(4) = F(3) + F(2) = 2 + 1 = 3.4.1 解題思路圖中是一個F(5)計算的遞歸樹(圖片來自leetcode官方[7])。
  • 每天一道leetcode234-回文鍊表
    正當班主任要繼續發話,只聽到角落默默想起來一個聲音:」K2」前言 2018.11.6號打卡明天的題目:https://leetcode-cn.com/problems/remove-linked-list-elements/以後明天的題目提取公布一下哈,因為有些朋友想提前做一下~題目 leetcode234-回文鍊表中文連結:https
  • leetcode16. 最接近的三數之和--每天刷一道leetcode算法系列!
    找出 nums 中的三個整數,使得它們的和與 target 最接近。返回這三個數的和。假定每組輸入只存在唯一答案。例如,給定數組 nums = [-1,2,1,-4], 和 target = 1.與 target 最接近的三個數的和為 2. (-1 + 2 + 1 = 2).
  • LeetCode數組類知識點&題型總結
    題型總結這類題目通常會給定一個數組和一個值,讓求出這個數組中兩個/三個/K個值的和等於這個給定的值target。leetcode第一題就是two-sum,對於這類題目,首先看題目要求的時間複雜度和空間複雜度是什麼,其次看有沒有限制條件,如要求不能有重複的子數組或者要求按照升序/降序排列等。