将二进制字符串分割成K个子集,使0和1出现次数的乘积之和最小


二进制字符串是由一系列二进制数字(也称为比特)组成的,这些数字要么是0,要么是1。这是一种使用只有两个数字的编码数据的方法,适用于计算机应用程序,其中数据存储和处理使用只能识别两种状态的电子电路。在计算机编程中,二进制字符串经常用于以电子电路易于处理的方式表示数据,例如数字、文本和图像。

方法

方法1. 动态规划

方法2. 贪婪算法

方法1:动态规划

为了解决这个问题,我们可以使用动态规划。令dp[i][j]为将二进制字符串的前i个字符分成j个子集的最小乘积和。通过测试所有可能的分割点k(1 ≤ k < i),然后计算dp[i][j],我们可以根据dp[k][j-1] + cost[k+1][i]来确定dp[i][j]的值,其中cost[k+1][i]是将从k+1到i的子串分割的代价。代价由子串中0和1的数量的乘积确定。

语法

下面提供动态规划方法的语法:

  • 创建一个名为dp的二维表,其维度为K+1乘以n+1,其中K是子集的数量,n是二进制字符串的长度。当二进制字符串的前j个字符被分成i个子集时,dp[i][j]表示0和1的乘积之和的最小值。

  • 初始化边界条件:

    • dp[1][j]是二进制字符串前j个字符中所有0和1实例的乘积和。

    • 对于每个i,dp[i][i]为0(因为当分割长度为i的字符串时,只有一个子集)。

  • 使用递归关系查找每个i(从2到K)和每个j(从i+1到n)的dp[i][j]:dp[i][j] = min(dp[i-1][k] + cost[k+1][j],其中cost[k+1][j]是从k+1到j的子串中所有0和1的乘积。可以通过迭代所有可能的k值(从i-2到j-2)来找到dp[i][j]的最小值。

  • 当二进制字符串被分成K个子集时,dp[K][n]给出0和1乘积和的最小值。

算法

步骤1 - 创建一个 (K+1)*(n+1) 维的数组来存储二进制字符串的分割结果。

步骤2 - 将边界条件初始化为 dp[0][0] = 0, i > 0 时 dp[i][0] = 无穷大, j > 0 时 dp[0][j] = 无穷大。

步骤3 - 计算每个 i (从 1 到 n) 和每个 j (从 1 到 K) 的 dp[i][j]:

  • 将所有此类代价的最小值作为 dp[i][j]。

  • 对于每个 k (从 0 到 i-1),计算将二进制字符串从 k 到 i-1 分割成 j-1 个子集的代价,并加上该子串中 0 和 1 个数的乘积。

步骤4 - 返回 dp[n][K] 作为将二进制字符串分割成 K 个子集的最小代价。

示例1

第一步,我们将二进制字符串的所有可能的子串相加,并将结果存储在sum数组中。然后,在将二进制字符串分成k个子集到第i个字符后,将最小乘积和存储在dp数组中。

#include <iostream>		
#include <cstring>
#include <climits>
using namespace std;

const int MAXN = 105;
const int INF = INT_MAX;

int dp[MAXN][MAXN], sum[MAXN][MAXN];

int main() {
   string s = "1001011101"; 
   int K = 3; 
   int n = s.length();
   memset(dp, 0, sizeof(dp));
   memset(sum, 0, sizeof(sum));
   for (int i = 1; i <= n; i++) {
      sum[i][i] = s[i - 1] - '0';
      for (int j = i + 1; j <= n; j++) {
         sum[i][j] = sum[i][j - 1] + s[j - 1] - '0';
      }
   }
   for (int k = 1; k <= K; k++) {
      for (int i = 1; i <= n; i++) {
         if (k == 1) {  
            dp[i][k] = sum[1][i] * (sum[1][n] - sum[i][n]);
         } else {
           dp[i][k] = INF;
            for (int j = 1; j < i; j++) {
              dp[i][k] = min(dp[i][k], dp[j][k - 1] + sum[j + 1][i] * (sum[1][n] - sum[i][n]));
            }
         }
      }
   }
   cout << "Minimum sum of products: " << dp[n][K] << endl;
   return 0;
}

输出

Minimum sum of products: -2147483624

方法2:贪婪算法

我们可以首先将二进制字符串分成K个相等的部分。然后可以计算每个部分的代价,并通过交换相邻部分来尝试减少总代价。我们可以交换相邻部分,直到没有更多可行的交换或没有更多代价减少。

语法

  • 给定二进制字符串 s 和整数 K

  • 二进制字符串 s 的长度

int n = s.length(); 
  • cut[i] = s 的前 i 位中 1 的数量

vector<int> cut(a+1);
for (int D = 0; D < a; D++) {
   cut[D+1] = cut[D] + (s[D] == '1');
}
vector<int> ft(a+1, 1e9); 
ft[0] = 0;
for (int R = 1; k <= R; R++) {
   for (int D = a; D >= R; D--) {
      for (int j = R-1; j < R; j++) {
         dp[D] = min(ft[D], ft[j] + (cut[D] - cut[j]) * (cut[j] - cut[R-1]));
      }
   }
}
  • 将 s 分割成 K 个子集后,0 和 1 的乘积的最小和

int ans = ft[a]; 

贪婪算法

步骤1 - 计算二进制字符串中 0 和 1 的数量。

步骤2 - 用 1 的数量减去 0 的数量。

步骤3 - 将二进制字符串分成 K 个相等的部分。

步骤4 - 计算每个部分中 0 和 1 的数量。

步骤5 - 对于每个部分,用 1 的数量减去 0 的数量。

步骤6 - 将所有部分的乘积相加得到总乘积。

步骤7 - 如果总乘积小于步骤2中计算的结果,则返回这些部分作为解决方案。否则,转到步骤8。

步骤8 - 通过合并具有最小 0 和 1 乘积和的相邻部分,直到得到 K 个部分。

示例2

然后,通过遍历每个字符并将它们添加到当前子集(假设 0 和 1 的总冗余数不为负),将字符串划分为 K 个子集。

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;
vector<string> splitBinaryString(string s, int k) {
   vector<int> zeros(s.length()), ones(s.length());
   int zeroCount = 0, oneCount = 0;
   for (int i = s.length() - 1; i >= 0; i--) {
      if (s[i] == '0') zeroCount++;
      else oneCount++;
      zeros[i] = zeroCount;
      ones[i] = oneCount;
   }
   vector<string> subsets(k);
   for (int i = 0; i < k; i++) {
      subsets[i] = "";
      int start = i, end = s.length() - k + i + 1;
      for (int j = start; j < end; j++) {
         int zeroOccurrences = zeros[j] - zeros[start];
         int oneOccurrences = ones[j] - ones[start];
         if (zeroOccurrences * oneOccurrences < 0) break;
         subsets[i] += s[j];
      }
   }
   return subsets;
}
int sumOfProducts(vector<string> subsets) {
   int sum = 0;
   for (string subset : subsets) {
      int zeroCount = count(subset.begin(), subset.end(), '0');
      int oneCount = subset.length() - zeroCount;
      sum += zeroCount * oneCount;
   }
   return sum;
}
int main() {
   string s = "1010110010";
   int k = 3;
   vector<string> subsets = splitBinaryString(s, k);
   int result = sumOfProducts(subsets);
   cout << "Minimum sum of products of occurrences of 0 and 1: " << result << endl;
   return 0;
}

输出

Minimum sum of products of occurrences of 0 and 1: 48

结论

动态规划可以用来找到将二进制字符串分成K个子集时0和1乘积的最小和。通过跟踪每个子集大小和每个起始位置的最小代价,我们可以有效地找到整个字符串的最小代价。动态规划方法的时间复杂度为O(K * n^2),其中n是二进制字符串的长度。

更新于:2023年7月31日

浏览量:113

开启你的职业生涯

完成课程获得认证

开始学习
广告