Compartilhamento de tecnologia

【1.5】Solução de programação dinâmica para particionar subconjuntos de soma igual

2024-07-12

한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina

1. Problema


Você recebe um array não vazio contendo apenas números inteiros positivos. Determine se esta matriz pode ser dividida em dois subconjuntos para que a soma dos elementos dos dois subconjuntos seja igual.

2. Ideia de solução 1

Solução de programação dinâmica

Este problema não pode ser resolvido usando o algoritmo de retrocesso. Se a quantidade de dados for grande, o tempo limite será atingido. Podemos usar programação dinâmica.
Esta questão determina se a soma dos elementos das duas partes é igual quando o array é dividido em duas partes. Primeiro precisamos calcular a soma de todos os elementos do array e depois determinar se a soma é um número par: se não for um número par, significa que não pode ser dividido em duas partes completamente iguais e retornará falso diretamente.
Se for um número par, só precisamos determinar se existem alguns elementos cuja soma é igual a soma/2. Se for igual a soma/2, então o resto também deve ser igual a soma/2, o que significa que. podemos dividir o array em duas partes com elementos iguais.
Então o problema está muito claro neste momento, assumindo que soma/2 é a capacidade de uma mochila, só precisamos encontrar alguns elementos e colocá-los na mochila se a soma máxima dos elementos da mochila for igual à soma. /2, significa que podemos colocar o array Divide em duas partes iguais. Não é este o clássico problema da mochila 0-1? O problema básico da mochila na série de problemas da mochila pode ser encontrado em detalhes. Não repetirei a introdução aqui. Procuramos sua fórmula de recursão, definindo dp[i][j] para representar o valor máximo obtido ao colocar o i-ésimo item em uma mochila com capacidade j.
O valor do i-ésimo item é nums [i -1]:
Se nums [i -1]>j, significa que a capacidade da mochila não é suficiente e o i-ésimo item não pode ser colocado, portanto não podemos selecionar o i-ésimo item, então
dp[i][j]=dp[i -1][j];
Se nums [i -1]<=j, significa que o j-ésimo item pode ser colocado na mochila. Podemos optar por colocá-lo ou não, basta pegar o valor máximo. capacidade da mochila. O valor máximo sim.
dp[i][j]=dp[i-1][j-nums[i-1]]+nums[i-1]
Se você não deixar ir
dp[i][j]=dp[i -1][j];
Pegue o máximo dos dois

A fórmula recursiva final e o código são os seguintes:

  1. #include <vector>
  2. bool canPartition(std::vector<int>& nums) {
  3. // 计算数组中所有元素的和
  4. int sum = 0;
  5. for (int num : nums) {
  6. sum += num;
  7. }
  8. // 如果sum是奇数,说明数组不可能分成完全相等的两份
  9. if ((sum & 1) == 1) {
  10. return false;
  11. }
  12. // sum除以2
  13. int target = sum >> 1;
  14. int length = nums.size();
  15. std::vector<std::vector<int>> dp(length + 1, std::vector<int>(target + 1, 0));
  16. for (int i = 1; i <= length; ++i) {
  17. for (int j = 1; j <= target; ++j) {
  18. // 下面是递推公式
  19. if (j >= nums[i - 1]) {
  20. dp[i][j] = std::max(dp[i - 1][j], dp[i - 1][j - nums[i - 1]] + nums[i - 1]);
  21. } else {
  22. dp[i][j] = dp[i - 1][j];
  23. }
  24. }
  25. }
  26. // 判断背包最大是否能存放和为target的元素
  27. return dp[length][target] == target;
  28. }

Também podemos escrever assim. O array bidimensional dp é do tipo booleano dp[i][j] indica se a soma dos primeiros i elementos do array pode formar uma soma de j. 0] = verdadeiro, o que significa que os primeiros 0 elementos (ou seja, nenhum elemento) podem formar uma soma de 0.código mostrado abaixo

  1. #include <vector>
  2. bool canPartition(std::vector<int>& nums) {
  3. // 计算数组中所有元素的和
  4. int sum = 0;
  5. for (int num : nums) {
  6. sum += num;
  7. }
  8. // 如果sum是奇数,说明数组不可能分成完全相等的两份
  9. if ((sum & 1) == 1) {
  10. return false;
  11. }
  12. // sum除以2得到target
  13. int target = sum >> 1;
  14. int length = nums.size();
  15. std::vector<std::vector<bool>> dp(length + 1, std::vector<bool>(target + 1, false));
  16. // base case
  17. dp[0][0] = true;
  18. for (int i = 1; i <= length; ++i) {
  19. for (int j = 0; j <= target; ++j) {
  20. // 递推公式
  21. if (j >= nums[i - 1]) {
  22. dp[i][j] = dp[i - 1][j] || dp[i - 1][j - nums[i - 1]];
  23. } else {
  24. dp[i][j] = dp[i - 1][j];
  25. }
  26. }
  27. }
  28. // 返回能否找到和为target的子集
  29. return dp[length][target];
  30. }

Vemos que ao calcular o array bidimensional acima, o valor atual está relacionado apenas à linha acima, então podemos alterá-lo para unidimensional. Observe que o segundo loop for deve ser flashback, caso contrário o valor anterior será substituído. e o resultado estará errado, observe mais de perto.
dp[j] = (dp[j] || dp[j - nums [i - 1]]);
Você pode entender que o valor no final da mesma linha depende do anterior. Caso não seja um flashback, o valor anterior é modificado, o que causará um erro no cálculo do posterior.Vamos dar uma olhada no código

  1. #include <vector>
  2. bool canPartition(std::vector<int>& nums) {
  3. // 计算数组中所有元素的和
  4. int sum = 0;
  5. for (int num : nums) {
  6. sum += num;
  7. }
  8. // 如果sum是奇数,说明数组不可能分成完全相等的两份
  9. if ((sum & 1) == 1) {
  10. return false;
  11. }
  12. // sum除以2得到target
  13. int target = sum >> 1;
  14. int length = nums.size();
  15. std::vector<bool> dp(target + 1, false);
  16. // base case
  17. dp[0] = true;
  18. for (int i = 0; i < length; ++i) {
  19. // 注意这里j要倒序
  20. for (int j = target; j >= nums[i]; --j) {
  21. // 递推公式
  22. dp[j] = dp[j] || dp[j - nums[i]];
  23. }
  24. }
  25. // 返回能否找到和为target的子集
  26. return dp[target];
  27. }

3. Ideia de solução 2

Solução de algoritmo DFS

Cada elemento pode ser selecionado ou não. Você só precisa determinar se a soma dos elementos em todas as suas combinações possíveis é igual a soma/2. Podemos pensar nisso como uma árvore binária. elemento atual, e o nó filho direito representa a seleção do elemento atual. O nó filho indica que o elemento atual não está selecionado, conforme mostrado na figura abaixo. indica que o elemento atual não está selecionado.

Vamos primeiro dar uma olhada no código de implementação preliminar

  1. #include <vector>
  2. bool dfs(const std::vector<int>& nums, int target, int index) {
  3. // target等于0,说明存在一些元素的和等于sum/2,直接返回true
  4. if (target == 0) return true;
  5. // 如果数组元素都找完了,或者target小于0,直接返回false
  6. if (index == nums.size() || target < 0) return false;
  7. // 选择当前元素和不选择当前元素两种情况
  8. return dfs(nums, target - nums[index], index + 1) || dfs(nums, target, index + 1);
  9. }
  10. bool canPartition(std::vector<int>& nums) {
  11. // 计算数组中所有元素的和
  12. int sum = 0;
  13. for (int num : nums) sum += num;
  14. // 如果sum是奇数,说明数组不可能分成完全相等的两份
  15. if ((sum & 1) == 1) return false;
  16. // 调用dfs函数
  17. return dfs(nums, sum >> 1, 0);
  18. }

Mas, infelizmente, como a quantidade de cálculo é muito grande, a operação atingirá o tempo limite. Podemos otimizá-la e dar uma olhada no código.

  1. #include <vector>
  2. bool dfs(const std::vector<int>& nums, int index, int target, std::vector<std::vector<int>>& map) {
  3. // target等于0,说明存在一些元素的和等于sum/2,直接返回true
  4. if (target == 0) return true;
  5. // 如果数组元素都找完了,或者target小于0,直接返回false
  6. if (index >= nums.size() || target < 0) return false;
  7. // 如果此子问题已经被计算过,直接返回结果
  8. if (map[index][target] != -1) return map[index][target] == 1;
  9. // 选择当前元素
  10. bool select = dfs(nums, index + 1, target - nums[index], map);
  11. // 不选择当前元素
  12. bool unSelect = dfs(nums, index + 1, target, map);
  13. // 存储结果,并返回
  14. map[index][target] = select || unSelect ? 1 : 0;
  15. return select || unSelect;
  16. }
  17. bool canPartition(std::vector<int>& nums) {
  18. // 计算数组中所有元素的和
  19. int sum = 0;
  20. for (int num : nums) sum += num;
  21. // 如果sum是奇数,说明数组不可能分成完全相等的两份
  22. if (sum % 2 != 0) return false;
  23. // sum除以2
  24. int target = sum / 2;
  25. std::vector<std::vector<int>> map(nums.size(), std::vector<int>(target + 1, -1));
  26. return dfs(nums, 0, target, map);
  27. }

4. Ideia de solução 3

Solução de operação de bits

Operações de bits podem ser usadas aqui, mas a chave está em algumas restrições na questão, como uma matriz não vazia de números inteiros positivos, os elementos em cada matriz não excederão 100, o tamanho da matriz não excederá 200, etc. .
O princípio é muito simples. Só precisamos aplicar para um array bits[soma+1] de tamanho soma+1. Os números no array podem ser apenas 0 e 1. Podemos imaginá-lo como um bit binário muito longo, porque. int e O tipo long é muito curto, estamos usando um array aqui. Então, toda vez que um elemento da matriz é percorrido, como m, o bit binário é deslocado para a esquerda em m bits e, em seguida, é feito OR com o bit binário original. Finalmente, determine se bits[soma/2] é 1 e retorne verdadeiro se for 1.A descrição do texto não é muito direta. Tomemos o Exemplo 1 como exemplo para fazer um desenho.

Por fim, você encontrará uma regra, ou seja, desde que o resultado final da operação seja 1, ele pode ser combinado usando elementos do array. Enquanto for 0, não pode ser combinado usando elementos do array. processo acima, o código é fácil de escrever. Como só precisamos determinar se o valor do meio no bit binário é 1, só precisamos calcular o bit inferior, e o bit superior não precisa ser calculado, porque é movido para a esquerda, e o bit superior bit não terá nenhum impacto no valor médio, então podemos fazer isso aqui. Uma pequena otimização e, finalmente, vamos dar uma olhada no código.

  1. #include <vector>
  2. bool canPartition(std::vector<int>& nums) {
  3. // 计算数组中所有数字的和
  4. int sum = 0;
  5. for (int n : nums) {
  6. sum += n;
  7. }
  8. // 如果sum是奇数,直接返回false
  9. if ((sum & 1) == 1) {
  10. return false;
  11. }
  12. int len = sum / 2;
  13. // 这里bits的长度是len+1,因为我们只需要计算低位就行了,没必要计算所有的
  14. std::vector<char> bits(len + 1, 0);
  15. bits[0] = 1;
  16. for (int num : nums) {
  17. for (int j = len - num; j >= 0; j--) {
  18. bits[j + num] |= bits[j];
  19. }
  20. // 判断中位数如果是1,说明可以分成两种相等的子集,直接返回true,不需要再计算了
  21. if ((bits[len] & 1) != 0) {
  22. return true;
  23. }
  24. }
  25. return false;
  26. }