给定一组整数,任务是将其分成两组 S1 和 S2,使得它们和之间的绝对差最小。
如果有一个包含 n 个元素的集合 S,那么如果我们假设 Subset1 有 m 个元素,那么 Subset2 必须有 nm 个元素,并且 abs(sum(Subset1) – sum(Subset2)) 的值应该是最小的。
例子:
Input: arr[] = {1, 6, 11, 5}
Output: 1
Explanation:
Subset1 = {1, 5, 6}, sum of Subset1 = 12
Subset2 = {11}, sum of Subset2 = 11
这个问题主要是对动态规划的扩展|设置 18(分区问题)。
递归解
递归方法是从数组的所有值中生成所有可能的总和,并检查哪个解决方案是最佳解决方案。
为了生成和,我们要么将第 i 个项目包含在集合 1 中,要么不包含,即包含在集合 2 中。
C++
// A Recursive C++ program to solve minimum sum partition
// problem.
#include
using namespace std;
// Function to find the minimum sum
int findMinRec(int arr[], int i, int sumCalculated,
int sumTotal)
{
// If we have reached last element. Sum of one
// subset is sumCalculated, sum of other subset is
// sumTotal-sumCalculated. Return absolute difference
// of two sums.
if (i == 0)
return abs((sumTotal - sumCalculated)
- sumCalculated);
// For every item arr[i], we have two choices
// (1) We do not include it first set
// (2) We include it in first set
// We return minimum of two choices
return min(
findMinRec(arr, i - 1, sumCalculated + arr[i - 1],
sumTotal),
findMinRec(arr, i - 1, sumCalculated, sumTotal));
}
// Returns minimum possible difference between sums
// of two subsets
int findMin(int arr[], int n)
{
// Compute total sum of elements
int sumTotal = 0;
for (int i = 0; i < n; i++)
sumTotal += arr[i];
// Compute result using recursive function
return findMinRec(arr, n, 0, sumTotal);
}
// Driver program to test above function
int main()
{
int arr[] = { 3, 1, 4, 2, 2, 1 };
int n = sizeof(arr) / sizeof(arr[0]);
cout << "The minimum difference between two sets is "
<< findMin(arr, n);
return 0;
}
Java
// JAVA code to partition a set into two subsets
// such that the difference of subset sums
// is minimum
import java.util.*;
class GFG {
// Function to find the minimum sum
public static int findMinRec(int arr[], int i,
int sumCalculated,
int sumTotal)
{
// If we have reached last element.
// Sum of one subset is sumCalculated,
// sum of other subset is sumTotal-
// sumCalculated. Return absolute
// difference of two sums.
if (i == 0)
return Math.abs((sumTotal - sumCalculated)
- sumCalculated);
// For every item arr[i], we have two choices
// (1) We do not include it first set
// (2) We include it in first set
// We return minimum of two choices
return Math.min(
findMinRec(arr, i - 1,
sumCalculated + arr[i - 1],
sumTotal),
findMinRec(arr, i - 1, sumCalculated,
sumTotal));
}
// Returns minimum possible difference between
// sums of two subsets
public static int findMin(int arr[], int n)
{
// Compute total sum of elements
int sumTotal = 0;
for (int i = 0; i < n; i++)
sumTotal += arr[i];
// Compute result using recursive function
return findMinRec(arr, n, 0, sumTotal);
}
/* Driver program to test above function */
public static void main(String[] args)
{
int arr[] = { 3, 1, 4, 2, 2, 1 };
int n = arr.length;
System.out.print("The minimum difference"
+ " between two sets is "
+ findMin(arr, n));
}
}
// This code is contributed by Arnav Kr. Mandal.
Python3
# Python3 program for the
# above approach
# A Recursive C program to
# solve minimum sum partition
# problem.
# Function to find the minimum sum
def findMinRec(arr, i, sumCalculated,
sumTotal):
# If we have reached last element.
# Sum of one subset is sumCalculated,
# sum of other subset is sumTotal-
# sumCalculated. Return absolute
# difference of two sums.
if (i == 0):
return abs((sumTotal - sumCalculated) -
sumCalculated)
# For every item arr[i], we have two choices
# (1) We do not include it first set
# (2) We include it in first set
# We return minimum of two choices
return min(findMinRec(arr, i - 1,
sumCalculated+arr[i - 1],
sumTotal),
findMinRec(arr, i - 1,
sumCalculated, sumTotal))
# Returns minimum possible
# difference between sums
# of two subsets
def findMin(arr, n):
# Compute total sum
# of elements
sumTotal = 0
for i in range(n):
sumTotal += arr[i]
# Compute result using
# recursive function
return findMinRec(arr, n,
0, sumTotal)
# Driver code
if __name__ == "__main__":
arr = [3, 1, 4, 2, 2, 1]
n = len(arr)
print("The minimum difference " +
"between two sets is ",
findMin(arr, n))
# This code is contributed by Chitranayal
C#
// C# code to partition a set into two subsets
// such that the difference of subset sums
// is minimum
using System;
class GFG {
// Function to find the minimum sum
public static int findMinRec(int[] arr, int i,
int sumCalculated,
int sumTotal)
{
// If we have reached last element.
// Sum of one subset is sumCalculated,
// sum of other subset is sumTotal-
// sumCalculated. Return absolute
// difference of two sums.
if (i == 0)
return Math.Abs((sumTotal - sumCalculated)
- sumCalculated);
// For every item arr[i], we have two choices
// (1) We do not include it first set
// (2) We include it in first set
// We return minimum of two choices
return Math.Min(
findMinRec(arr, i - 1,
sumCalculated + arr[i - 1],
sumTotal),
findMinRec(arr, i - 1, sumCalculated,
sumTotal));
}
// Returns minimum possible difference between
// sums of two subsets
public static int findMin(int[] arr, int n)
{
// Compute total sum of elements
int sumTotal = 0;
for (int i = 0; i < n; i++)
sumTotal += arr[i];
// Compute result using recursive function
return findMinRec(arr, n, 0, sumTotal);
}
/* Driver program to test above function */
public static void Main()
{
int[] arr = { 3, 1, 4, 2, 2, 1 };
int n = arr.Length;
Console.Write("The minimum difference"
+ " between two sets is "
+ findMin(arr, n));
}
}
// This code is contributed by nitin mittal.
Javascript
C++
// A Recursive C program to solve minimum sum partition
// problem.
#include
using namespace std;
// Returns the minimum value of the difference of the two
// sets.
int findMin(int arr[], int n)
{
// Calculate sum of all elements
int sum = 0;
for (int i = 0; i < n; i++)
sum += arr[i];
// Create an array to store results of subproblems
bool dp[n + 1][sum + 1];
// Initialize first column as true. 0 sum is possible
// with all elements.
for (int i = 0; i <= n; i++)
dp[i][0] = true;
// Initialize top row, except dp[0][0], as false. With
// 0 elements, no other sum except 0 is possible
for (int i = 1; i <= sum; i++)
dp[0][i] = false;
// Fill the partition table in bottom up manner
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= sum; j++) {
// If i'th element is excluded
dp[i][j] = dp[i - 1][j];
// If i'th element is included
if (arr[i - 1] <= j)
dp[i][j] |= dp[i - 1][j - arr[i - 1]];
}
}
// Initialize difference of two sums.
int diff = INT_MAX;
// Find the largest j such that dp[n][j]
// is true where j loops from sum/2 t0 0
for (int j = sum / 2; j >= 0; j--) {
// Find the
if (dp[n][j] == true) {
diff = sum - 2 * j;
break;
}
}
return diff;
}
// Driver program to test above function
int main()
{
int arr[] = { 3, 1, 4, 2, 2, 1 };
int n = sizeof(arr) / sizeof(arr[0]);
cout << "The minimum difference between 2 sets is "
<< findMin(arr, n);
return 0;
}
Java
// A Recursive java program to solve
// minimum sum partition problem.
import java.io.*;
class GFG {
// Returns the minimum value of
// the difference of the two sets.
static int findMin(int arr[], int n)
{
// Calculate sum of all elements
int sum = 0;
for (int i = 0; i < n; i++)
sum += arr[i];
// Create an array to store
// results of subproblems
boolean dp[][] = new boolean[n + 1][sum + 1];
// Initialize first column as true.
// 0 sum is possible with all elements.
for (int i = 0; i <= n; i++)
dp[i][0] = true;
// Initialize top row, except dp[0][0],
// as false. With 0 elements, no other
// sum except 0 is possible
for (int i = 1; i <= sum; i++)
dp[0][i] = false;
// Fill the partition table
// in bottom up manner
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= sum; j++) {
// If i'th element is excluded
dp[i][j] = dp[i - 1][j];
// If i'th element is included
if (arr[i - 1] <= j)
dp[i][j] |= dp[i - 1][j - arr[i - 1]];
}
}
// Initialize difference of two sums.
int diff = Integer.MAX_VALUE;
// Find the largest j such that dp[n][j]
// is true where j loops from sum/2 t0 0
for (int j = sum / 2; j >= 0; j--) {
// Find the
if (dp[n][j] == true) {
diff = sum - 2 * j;
break;
}
}
return diff;
}
// Driver program
public static void main(String[] args)
{
int arr[] = { 3, 1, 4, 2, 2, 1 };
int n = arr.length;
System.out.println(
"The minimum difference between 2 sets is "
+ findMin(arr, n));
}
}
// This code is contributed by vt_m
Python3
# A Recursive Python3 program to solve
# minimum sum partition problem.
import sys
# Returns the minimum value of the
# difference of the two sets.
def findMin(a, n):
su = 0
# Calculate sum of all elements
su = sum(a)
# Create an 2d list to store
# results of subproblems
dp = [[0 for i in range(su + 1)]
for j in range(n + 1)]
# Initialize first column as true.
# 0 sum is possible
# with all elements.
for i in range(n + 1):
dp[i][0] = True
# Initialize top row, except dp[0][0],
# as false. With 0 elements, no other
# sum except 0 is possible
for j in range(1, su + 1):
dp[0][j] = False
# Fill the partition table in
# bottom up manner
for i in range(1, n + 1):
for j in range(1, su + 1):
# If i'th element is excluded
dp[i][j] = dp[i - 1][j]
# If i'th element is included
if a[i - 1] <= j:
dp[i][j] |= dp[i - 1][j - a[i - 1]]
# Initialize difference
# of two sums.
diff = sys.maxsize
# Find the largest j such that dp[n][j]
# is true where j loops from sum/2 t0 0
for j in range(su // 2, -1, -1):
if dp[n][j] == True:
diff = su - (2 * j)
break
return diff
# Driver code
a = [3, 1, 4, 2, 2, 1]
n = len(a)
print("The minimum difference between "
"2 sets is ", findMin(a, n))
# This code is contributed by Tokir Manva
C#
// A Recursive C# program to solve
// minimum sum partition problem.
using System;
class GFG {
// Returns the minimum value of
// the difference of the two sets.
static int findMin(int[] arr, int n)
{
// Calculate sum of all elements
int sum = 0;
for (int i = 0; i < n; i++)
sum += arr[i];
// Create an array to store
// results of subproblems
bool[, ] dp = new bool[n + 1, sum + 1];
// Initialize first column as true.
// 0 sum is possible with all elements.
for (int i = 0; i <= n; i++)
dp[i, 0] = true;
// Initialize top row, except dp[0,0],
// as false. With 0 elements, no other
// sum except 0 is possible
for (int i = 1; i <= sum; i++)
dp[0, i] = false;
// Fill the partition table
// in bottom up manner
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= sum; j++) {
// If i'th element is excluded
dp[i, j] = dp[i - 1, j];
// If i'th element is included
if (arr[i - 1] <= j)
dp[i, j] |= dp[i - 1, j - arr[i - 1]];
}
}
// Initialize difference of two sums.
int diff = int.MaxValue;
// Find the largest j such that dp[n,j]
// is true where j loops from sum/2 t0 0
for (int j = sum / 2; j >= 0; j--) {
// Find the
if (dp[n, j] == true) {
diff = sum - 2 * j;
break;
}
}
return diff;
}
// Driver code
public static void Main(String[] args)
{
int[] arr = { 3, 1, 4, 2, 2, 1 };
int n = arr.Length;
Console.WriteLine("The minimum difference "
+ "between 2 sets is "
+ findMin(arr, n));
}
}
// This code is contributed by Rajput-Ji
Javascript
C++
#include
using namespace std;
int minDifference(int arr[], int n)
{
int sum = 0;
for (int i = 0; i < n; i++)
sum += arr[i];
int y = sum / 2 + 1;
// dp[i] gives whether is it possible to get i as sum of
// elements dd is helper variable
// we use dd to ignoring duplicates
bool dp[y], dd[y];
// Initialising dp and dd
for (int i = 0; i < y; i++) {
dp[i] = dd[i] = false;
}
// sum = 0 is possible
dd[0] = true;
for (int i = 0; i < n; i++) {
// updating dd[k] as true if k can be formed using
// elements from 1 to i+1
for (int j = 0; j + arr[i] < y; j++) {
if (dp[j])
dd[j + arr[i]] = true;
}
// updating dd
for (int j = 0; j < y; j++) {
if (dd[j])
dp[j] = true;
dd[j] = false; // reset dd
}
}
// checking the number from sum/2 to 1 which is possible
// to get as sum
for (int i = y - 1; i >= 0; i--) {
if (dp[i])
return (sum - 2 * i);
// since i is possible to form then another number
// is sum-i
// so mindifference is sum-i-i
}
}
int main()
{
int arr[] = { 1, 6, 11, 5 };
int n = sizeof(arr) / sizeof(arr[0]);
cout << "The Minimum difference of 2 sets is "
<< minDifference(arr, n) << '\n';
return 0;
}
输出:
The minimum difference between two sets is 1
时间复杂度:
All the sums can be generated by either
(1) including that element in set 1.
(2) without including that element in set 1.
So possible combinations are :-
arr[0] (1 or 2) -> 2 values
arr[1] (1 or 2) -> 2 values
.
.
.
arr[n] (2 or 2) -> 2 values
So time complexity will be 2*2*..... *2 (For n times),
that is O(2^n).
动态规划
当元素的总和不太大时,可以使用动态规划来解决该问题。我们可以创建一个二维数组 dp[n+1][sum+1],其中 n 是给定集合中元素的数量,sum 是所有元素的总和。我们可以以自下而上的方式构建解决方案。
The task is to divide the set into two parts.
We will consider the following factors for dividing it.
Let
dp[n+1][sum+1] = {1 if some subset from 1st to i'th has a sum
equal to j
0 otherwise}
i ranges from {1..n}
j ranges from {0..(sum of all elements)}
So
dp[n+1][sum+1] will be 1 if
1) The sum j is achieved including i'th item
2) The sum j is achieved excluding i'th item.
Let sum of all the elements be S.
To find Minimum sum difference, w have to find j such
that Min{sum - j*2 : dp[n][j] == 1 }
where j varies from 0 to sum/2
The idea is, sum of S1 is j and it should be closest
to sum/2, i.e., 2*j should be closest to sum.
下面是上面代码的实现。
C++
// A Recursive C program to solve minimum sum partition
// problem.
#include
using namespace std;
// Returns the minimum value of the difference of the two
// sets.
int findMin(int arr[], int n)
{
// Calculate sum of all elements
int sum = 0;
for (int i = 0; i < n; i++)
sum += arr[i];
// Create an array to store results of subproblems
bool dp[n + 1][sum + 1];
// Initialize first column as true. 0 sum is possible
// with all elements.
for (int i = 0; i <= n; i++)
dp[i][0] = true;
// Initialize top row, except dp[0][0], as false. With
// 0 elements, no other sum except 0 is possible
for (int i = 1; i <= sum; i++)
dp[0][i] = false;
// Fill the partition table in bottom up manner
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= sum; j++) {
// If i'th element is excluded
dp[i][j] = dp[i - 1][j];
// If i'th element is included
if (arr[i - 1] <= j)
dp[i][j] |= dp[i - 1][j - arr[i - 1]];
}
}
// Initialize difference of two sums.
int diff = INT_MAX;
// Find the largest j such that dp[n][j]
// is true where j loops from sum/2 t0 0
for (int j = sum / 2; j >= 0; j--) {
// Find the
if (dp[n][j] == true) {
diff = sum - 2 * j;
break;
}
}
return diff;
}
// Driver program to test above function
int main()
{
int arr[] = { 3, 1, 4, 2, 2, 1 };
int n = sizeof(arr) / sizeof(arr[0]);
cout << "The minimum difference between 2 sets is "
<< findMin(arr, n);
return 0;
}
Java
// A Recursive java program to solve
// minimum sum partition problem.
import java.io.*;
class GFG {
// Returns the minimum value of
// the difference of the two sets.
static int findMin(int arr[], int n)
{
// Calculate sum of all elements
int sum = 0;
for (int i = 0; i < n; i++)
sum += arr[i];
// Create an array to store
// results of subproblems
boolean dp[][] = new boolean[n + 1][sum + 1];
// Initialize first column as true.
// 0 sum is possible with all elements.
for (int i = 0; i <= n; i++)
dp[i][0] = true;
// Initialize top row, except dp[0][0],
// as false. With 0 elements, no other
// sum except 0 is possible
for (int i = 1; i <= sum; i++)
dp[0][i] = false;
// Fill the partition table
// in bottom up manner
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= sum; j++) {
// If i'th element is excluded
dp[i][j] = dp[i - 1][j];
// If i'th element is included
if (arr[i - 1] <= j)
dp[i][j] |= dp[i - 1][j - arr[i - 1]];
}
}
// Initialize difference of two sums.
int diff = Integer.MAX_VALUE;
// Find the largest j such that dp[n][j]
// is true where j loops from sum/2 t0 0
for (int j = sum / 2; j >= 0; j--) {
// Find the
if (dp[n][j] == true) {
diff = sum - 2 * j;
break;
}
}
return diff;
}
// Driver program
public static void main(String[] args)
{
int arr[] = { 3, 1, 4, 2, 2, 1 };
int n = arr.length;
System.out.println(
"The minimum difference between 2 sets is "
+ findMin(arr, n));
}
}
// This code is contributed by vt_m
蟒蛇3
# A Recursive Python3 program to solve
# minimum sum partition problem.
import sys
# Returns the minimum value of the
# difference of the two sets.
def findMin(a, n):
su = 0
# Calculate sum of all elements
su = sum(a)
# Create an 2d list to store
# results of subproblems
dp = [[0 for i in range(su + 1)]
for j in range(n + 1)]
# Initialize first column as true.
# 0 sum is possible
# with all elements.
for i in range(n + 1):
dp[i][0] = True
# Initialize top row, except dp[0][0],
# as false. With 0 elements, no other
# sum except 0 is possible
for j in range(1, su + 1):
dp[0][j] = False
# Fill the partition table in
# bottom up manner
for i in range(1, n + 1):
for j in range(1, su + 1):
# If i'th element is excluded
dp[i][j] = dp[i - 1][j]
# If i'th element is included
if a[i - 1] <= j:
dp[i][j] |= dp[i - 1][j - a[i - 1]]
# Initialize difference
# of two sums.
diff = sys.maxsize
# Find the largest j such that dp[n][j]
# is true where j loops from sum/2 t0 0
for j in range(su // 2, -1, -1):
if dp[n][j] == True:
diff = su - (2 * j)
break
return diff
# Driver code
a = [3, 1, 4, 2, 2, 1]
n = len(a)
print("The minimum difference between "
"2 sets is ", findMin(a, n))
# This code is contributed by Tokir Manva
C#
// A Recursive C# program to solve
// minimum sum partition problem.
using System;
class GFG {
// Returns the minimum value of
// the difference of the two sets.
static int findMin(int[] arr, int n)
{
// Calculate sum of all elements
int sum = 0;
for (int i = 0; i < n; i++)
sum += arr[i];
// Create an array to store
// results of subproblems
bool[, ] dp = new bool[n + 1, sum + 1];
// Initialize first column as true.
// 0 sum is possible with all elements.
for (int i = 0; i <= n; i++)
dp[i, 0] = true;
// Initialize top row, except dp[0,0],
// as false. With 0 elements, no other
// sum except 0 is possible
for (int i = 1; i <= sum; i++)
dp[0, i] = false;
// Fill the partition table
// in bottom up manner
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= sum; j++) {
// If i'th element is excluded
dp[i, j] = dp[i - 1, j];
// If i'th element is included
if (arr[i - 1] <= j)
dp[i, j] |= dp[i - 1, j - arr[i - 1]];
}
}
// Initialize difference of two sums.
int diff = int.MaxValue;
// Find the largest j such that dp[n,j]
// is true where j loops from sum/2 t0 0
for (int j = sum / 2; j >= 0; j--) {
// Find the
if (dp[n, j] == true) {
diff = sum - 2 * j;
break;
}
}
return diff;
}
// Driver code
public static void Main(String[] args)
{
int[] arr = { 3, 1, 4, 2, 2, 1 };
int n = arr.Length;
Console.WriteLine("The minimum difference "
+ "between 2 sets is "
+ findMin(arr, n));
}
}
// This code is contributed by Rajput-Ji
Javascript
输出:
The minimum difference between 2 sets is 1
时间复杂度= O(n*sum) 其中 n 是元素的数量,sum 是所有元素的总和。
空间复杂度较低的动态规划:
我们可以使用一维数组 dp[sum/2+1] 来解决这个问题,而不是使用二维数组。
假设集合 1 的元素总和是 x 而集合 2 的元素总和将是 sm-x(sm 是 arr 的所有元素的总和)。
所以我们必须最小化 abs(sm-2*x)。
因此,为了最小化两个集合之间的差异,我们需要知道一个小于 sum/2 的数字(sum 是数组中所有元素的总和),并且可以通过将数组中的元素相加来生成。
C++
#include
using namespace std;
int minDifference(int arr[], int n)
{
int sum = 0;
for (int i = 0; i < n; i++)
sum += arr[i];
int y = sum / 2 + 1;
// dp[i] gives whether is it possible to get i as sum of
// elements dd is helper variable
// we use dd to ignoring duplicates
bool dp[y], dd[y];
// Initialising dp and dd
for (int i = 0; i < y; i++) {
dp[i] = dd[i] = false;
}
// sum = 0 is possible
dd[0] = true;
for (int i = 0; i < n; i++) {
// updating dd[k] as true if k can be formed using
// elements from 1 to i+1
for (int j = 0; j + arr[i] < y; j++) {
if (dp[j])
dd[j + arr[i]] = true;
}
// updating dd
for (int j = 0; j < y; j++) {
if (dd[j])
dp[j] = true;
dd[j] = false; // reset dd
}
}
// checking the number from sum/2 to 1 which is possible
// to get as sum
for (int i = y - 1; i >= 0; i--) {
if (dp[i])
return (sum - 2 * i);
// since i is possible to form then another number
// is sum-i
// so mindifference is sum-i-i
}
}
int main()
{
int arr[] = { 1, 6, 11, 5 };
int n = sizeof(arr) / sizeof(arr[0]);
cout << "The Minimum difference of 2 sets is "
<< minDifference(arr, n) << '\n';
return 0;
}
The Minimum difference of 2 sets is 1
时间复杂度: O(n*sum)
辅助空间: O(sum)
如果您希望与专家一起参加现场课程,请参阅DSA 现场工作专业课程和学生竞争性编程现场课程。