最小化两个子集之和的绝对差

data structurec++programming

为了最小化两个子集之和的绝对差,我们将一个向量划分为两个子集,即将向量的元素划分为两个较小的向量,使得原始向量的每个元素属于两个较小向量中的一个,并且这两个较小向量是不相交的。

例如,假设向量 v = {1, 2, 3, 4, 5},那么将 v 划分为两个子集的一种可能方法是 S1 = {1, 3, 4} 和 S2 = {2, 5},其中 v 的每个元素属于 S1 或 S2,S1 和 S2 的交集为空集。

本文的目标是对列表进行划分,使得子集和的绝对差最小。

问题描述

当前任务是将一个整数列表划分为两个子集 S1 和 S2,使得 S1 元素和与 S2 元素和的差尽可能小。

示例

输入

{1, 2, 7}

输出

4

解释

绝对差最小的两个划分是 {1, 2} 和​​ {7}。它们的子集和分别为 3 和 7,绝对差为 4。

输入

{10, 20, 15, 5, 25}

输出

5

解释

绝对差最小的两个划分是 {10, 20, 5} 和 {15, 25}。它们的子集和分别为 35 和 40,绝对差为 5。

输入

{1, 1, 1, 1, 1}

输出

0

解释

绝对差最小的两个划分是 {1, 1, 1} 和 {1, 1, 1}。它们的子集和均为 3,绝对差为 0。

解决方案

此方法的底层逻辑是使用动态规划从给定的整数集合中找到一个子集,其和尽可能接近集合总和的一半。具体方法是创建一个二维布尔数组,其中 dp[i][j] 表示是否存在前 i 个元素的和为 j 的子集。然后,我们检查所有不超过集合总和一半的值,是否存在一个和为 j 的子集。最后,我们计算总和与最接近总和一半的子集和的两倍之间的最小绝对差。

给定程序的解决方法包括以下步骤:

  • 计算输入数组中所有元素的和。

  • 创建一个大小为 (n+1)x(range+1) 的二维布尔向量,并将其所有值初始化为 false,其中 n 是输入数组的大小,range 是其所有元素的和。

  • 对于从 0 到 n 的所有 i,将 dp[i][0] 的值设置为 true。

  • 循环遍历二维布尔向量的行和列,并将 dp[i][j] 的值设置为 dp[i-1][j] 和 dp[i][j] 的逻辑或dp[i-1][j-arr[i-1]],其中 arr 是输入数组,i 是当前行,j 是当前列。

  • 创建一个向量来存储子集和的有效值,即 dp[n][j] 为真的 j 的值。

  • 循环遍历范围的一半,并将子集和的所有有效值添加到有效向量中。

  • 循环遍历有效向量,并将 mini 的值设置为 mini 和 (range - (2 * valid[i])) 的最小值,其中 mini 初始化为整数的最大值。

  • 返回 mini 的值,即输入数组两个子集分区之间的最小绝对差。

算法

  • 函数minDifference()

    • 初始化 range = 0

    • i 从 0 到 n - 1

      range = range + arr[i]

    • 初始化 dp 矩阵

    • i 从 0 到 n

      设置 dp[i][0] = true

    • i 从 1 到 n

      j 从 1 到 range

      if arr[i - 1] <= j

      dp[i][j] = dp[i - 1][j] || dp[i - 1][j - arr[i - 1]];

      else

      dp[i][j] = dp[i - 1][j];

    • 初始化布尔向量 valid

    • i 从 0 到 range / 2

      if (dp[n][i] == true)

      valid.push_back(i);

    • 初始化 mini = INT_MAX;

    • i 从 0 到 valid.size() - 1

      mini = min (mini, range - (2 * valid[i]))

    • 返回 mini

  • 函数main()

    • 初始化整数向量 a

    • 初始化 n = a.size();

    • 函数调用 minDifference(a, n);

    • 打印结果

示例:C++ 程序

以下 C++ 程序使用动态规划方法,求数组中两个子集和之间的最小绝对差。函数 minDifference() 计算列表中所有元素的总和。然后初始化并填充 dp 矩阵。创建一个包含所有可能子集和的向量 valid,并返回结果。

// C++ 程序,用于返回数组中两个子集分区之间的最小绝对差
#include <bits/stdc++.h>
using namespace std;
// 用于计算子集和最小差的函数
int minDifference(vector<int> arr, int n) {
    // 计算数组的范围
    int range = 0;
    for (int i = 0; i < n; i++) {
        range += arr[i];
    }
    // 初始化dp矩阵
    vector<vector<bool>> dp(n + 1, vector<bool>(range + 1, 0));
    for (int i = 0; i <= n; i++) {
        dp[i][0] = true;
    }
    // 填充dp矩阵
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= range; j++) {
            if (arr[i - 1] <= j) {
                dp[i][j] = dp[i - 1][j] || dp[i - 1][j - arr[i - 1]];
            } else {
                dp[i][j] = dp[i - 1][j];
            }
        }
    }
    // 找到有效的子集和
    vector<int> valid;
    for (int i = 0; i <= range / 2; i++) {
        if (dp[n][i] == true) {
            valid.push_back(i);
        }
    }
    // 计算最小绝对差
    int mini = INT_MAX;
    for (int i = 0; i < valid.size(); i++) {
        mini = min(mini, range - (2 * valid[i]));
    }
    return mini;
}
// 驱动代码
int main() {
    // 输入数组
    vector<int> a = {1, 2, 7};
    int n = a.size();
    // 计算最小子集差异
    int result = minDifference(a, n);
    cout << "最小子集差异为: " << result << endl;
    return 0;
}

输出

最小子集差异为: 4

时间和空间复杂度分析

时间复杂度:O(n2)

  • 程序使用嵌套循环填充dp矩阵,因此dp填充步骤的时间复杂度为O(n * range)。

  • 程序使用循环查找有效子集和,因此子集和检查步骤的时间复杂度为O(range/2)。

  • 程序使用循环计算最小绝对差,因此此步骤的时间复杂度为O(valid.size())。

  • 因此,程序的整体时间复杂度为O(n * range + range/2 + valid.size())。

空间复杂度:O(n2)

  • 该程序使用一个大小为 (n+1)x(range+1) 的二维布尔数组 dp,因此 dp 矩阵的空间复杂度为 O(n * range)。

  • 该程序使用一个向量 valid 来存储有效子集的和,因此有效向量的空间复杂度为 O(range/2)。

  • 因此,该程序的整体空间复杂度为 O(n * range)。

结论

本文讨论了动态规划方法,用于求数组两个划分子集之间的绝对最小差值。借助合适的示例,问题描述清晰。本文还详细讨论了求解方法、算法、C++ 程序代码以及时间和空间复杂度。


相关文章