2024-07-12
한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina
Vous recevez un tableau nums non vide contenant uniquement des entiers positifs. Veuillez déterminer si ce tableau peut être divisé en deux sous-ensembles afin que la somme des éléments des deux sous-ensembles soit égale.
Solution de programmation dynamique
Ce problème ne peut pas être résolu à l'aide de l'algorithme de retour en arrière. Si la quantité de données est importante, le délai expirera. Nous pouvons utiliser la programmation dynamique.
Cette question détermine si la somme des éléments des deux parties est égale lorsque le tableau est divisé en deux parties. Nous devons d'abord calculer la somme de tous les éléments du tableau, puis déterminer si la somme est un nombre pair : si ce n'est pas un nombre pair, cela signifie qu'il ne peut pas être divisé en deux parties complètement égales, et il renverra faux directement.
S'il s'agit d'un nombre pair, il suffit de déterminer s'il existe des éléments dont la somme est égale à somme/2, alors le reste doit également être égal à somme/2, ce qui signifie que. nous pouvons diviser le tableau en deux parties avec des éléments égaux.
Le problème est alors très clair à ce stade. En supposant que somme/2 soit la capacité d’un sac à dos, il suffit de trouver certains éléments et de les mettre dans le sac à dos si la somme maximale des éléments dans le sac à dos est égale à la somme. /2, cela signifie que nous pouvons mettre le tableau Divide en deux parties égales. N'est-ce pas le problème classique du sac à dos 0-1 ? Le problème de base du sac à dos dans la série des problèmes de sac à dos peut être trouvé en détail. Je ne répéterai pas l'introduction ici. Nous recherchons sa formule de récursion, définissant dp[i][j] pour représenter la valeur maximale obtenue en mettant le i-ème élément dans un sac à dos de capacité j.
La valeur du i-ième élément est nums [i -1] :
Si nums [i -1]>j, cela signifie que la capacité du sac à dos n'est pas suffisante et que le i-ème élément ne peut pas être mis dedans, nous ne pouvons donc pas sélectionner le i-ème élément, alors
dp[i][j]=dp[i -1][j];
Si nums [i -1]<=j, cela signifie que le jième élément peut être mis dans le sac à dos. On peut choisir de le mettre ou non, il suffit de prendre la valeur maximale. Si on le met, il occupera une partie du. capacité du sac à dos. La valeur maximale oui.
dp[i][j]=dp[i-1][j-nums[i-1]]+nums[i-1]
Si tu ne lâches pas
dp[i][j]=dp[i -1][j];
Prendre le maximum des deux
La formule récursive finale et le code sont les suivants :
- #include <vector>
-
- bool canPartition(std::vector<int>& nums) {
- // 计算数组中所有元素的和
- int sum = 0;
- for (int num : nums) {
- sum += num;
- }
- // 如果sum是奇数,说明数组不可能分成完全相等的两份
- if ((sum & 1) == 1) {
- return false;
- }
- // sum除以2
- int target = sum >> 1;
- int length = nums.size();
- std::vector<std::vector<int>> dp(length + 1, std::vector<int>(target + 1, 0));
- for (int i = 1; i <= length; ++i) {
- for (int j = 1; j <= target; ++j) {
- // 下面是递推公式
- if (j >= nums[i - 1]) {
- dp[i][j] = std::max(dp[i - 1][j], dp[i - 1][j - nums[i - 1]] + nums[i - 1]);
- } else {
- dp[i][j] = dp[i - 1][j];
- }
- }
- }
- // 判断背包最大是否能存放和为target的元素
- return dp[length][target] == target;
- }
On peut aussi écrire ainsi : Le tableau bidimensionnel dp est de type booléen. dp[i][j] indique si la somme des i premiers éléments du tableau peut former une somme de j. 0]=true, ce qui signifie que les 0 premiers éléments (c'est-à-dire aucun élément) peuvent former une somme de 0.code affiché comme ci-dessous
- #include <vector>
-
- bool canPartition(std::vector<int>& nums) {
- // 计算数组中所有元素的和
- int sum = 0;
- for (int num : nums) {
- sum += num;
- }
- // 如果sum是奇数,说明数组不可能分成完全相等的两份
- if ((sum & 1) == 1) {
- return false;
- }
- // sum除以2得到target
- int target = sum >> 1;
- int length = nums.size();
- std::vector<std::vector<bool>> dp(length + 1, std::vector<bool>(target + 1, false));
- // base case
- dp[0][0] = true;
- for (int i = 1; i <= length; ++i) {
- for (int j = 0; j <= target; ++j) {
- // 递推公式
- if (j >= nums[i - 1]) {
- dp[i][j] = dp[i - 1][j] || dp[i - 1][j - nums[i - 1]];
- } else {
- dp[i][j] = dp[i - 1][j];
- }
- }
- }
- // 返回能否找到和为target的子集
- return dp[length][target];
- }
Nous voyons que lors du calcul du tableau bidimensionnel ci-dessus, la valeur actuelle n'est liée qu'à la ligne ci-dessus, nous pouvons donc la changer en unidimensionnelle. Notez que la deuxième boucle for doit être flashback, sinon la valeur précédente sera écrasée. et le résultat sera faux, regardez de plus près.
dp[j] = (dp[j] || dp[j - nombres [i - 1]]);
Vous pouvez comprendre que la valeur à la fin d'une même ligne dépend de la précédente. S'il ne s'agit pas d'un flashback, la valeur précédente est modifiée, ce qui entraînera une erreur dans le calcul de la suivante.Jetons un coup d'œil au code
- #include <vector>
-
- bool canPartition(std::vector<int>& nums) {
- // 计算数组中所有元素的和
- int sum = 0;
- for (int num : nums) {
- sum += num;
- }
- // 如果sum是奇数,说明数组不可能分成完全相等的两份
- if ((sum & 1) == 1) {
- return false;
- }
- // sum除以2得到target
- int target = sum >> 1;
- int length = nums.size();
- std::vector<bool> dp(target + 1, false);
- // base case
- dp[0] = true;
- for (int i = 0; i < length; ++i) {
- // 注意这里j要倒序
- for (int j = target; j >= nums[i]; --j) {
- // 递推公式
- dp[j] = dp[j] || dp[j - nums[i]];
- }
- }
- // 返回能否找到和为target的子集
- return dp[target];
- }
Solution d'algorithme DFS
Chaque élément peut être sélectionné ou non. Il suffit de déterminer si la somme des éléments dans toutes ses combinaisons possibles est égale à somme/2. On peut le considérer comme un arbre binaire. Le nœud enfant de gauche représente la sélection des éléments. L'élément actuel et le nœud enfant de droite représentent la sélection de l'élément actuel. Le nœud enfant indique que l'élément actuel n'est pas sélectionné, comme le montre la figure ci-dessous. Le nœud orange indique que l'élément actuel est sélectionné et le nœud bleu. indique que l'élément actuel n'est pas sélectionné.
Jetons d'abord un coup d'œil au code d'implémentation préliminaire
- #include <vector>
-
- bool dfs(const std::vector<int>& nums, int target, int index) {
- // target等于0,说明存在一些元素的和等于sum/2,直接返回true
- if (target == 0) return true;
- // 如果数组元素都找完了,或者target小于0,直接返回false
- if (index == nums.size() || target < 0) return false;
- // 选择当前元素和不选择当前元素两种情况
- return dfs(nums, target - nums[index], index + 1) || dfs(nums, target, index + 1);
- }
-
- bool canPartition(std::vector<int>& nums) {
- // 计算数组中所有元素的和
- int sum = 0;
- for (int num : nums) sum += num;
- // 如果sum是奇数,说明数组不可能分成完全相等的两份
- if ((sum & 1) == 1) return false;
- // 调用dfs函数
- return dfs(nums, sum >> 1, 0);
- }
Mais malheureusement, parce que la quantité de calcul est trop importante, cela entraînera un délai d'attente de l'opération. Nous pouvons l'optimiser et examiner le code.
- #include <vector>
-
- bool dfs(const std::vector<int>& nums, int index, int target, std::vector<std::vector<int>>& map) {
- // target等于0,说明存在一些元素的和等于sum/2,直接返回true
- if (target == 0) return true;
- // 如果数组元素都找完了,或者target小于0,直接返回false
- if (index >= nums.size() || target < 0) return false;
- // 如果此子问题已经被计算过,直接返回结果
- if (map[index][target] != -1) return map[index][target] == 1;
- // 选择当前元素
- bool select = dfs(nums, index + 1, target - nums[index], map);
- // 不选择当前元素
- bool unSelect = dfs(nums, index + 1, target, map);
- // 存储结果,并返回
- map[index][target] = select || unSelect ? 1 : 0;
- return select || unSelect;
- }
-
- bool canPartition(std::vector<int>& nums) {
- // 计算数组中所有元素的和
- int sum = 0;
- for (int num : nums) sum += num;
- // 如果sum是奇数,说明数组不可能分成完全相等的两份
- if (sum % 2 != 0) return false;
- // sum除以2
- int target = sum / 2;
- std::vector<std::vector<int>> map(nums.size(), std::vector<int>(target + 1, -1));
- return dfs(nums, 0, target, map);
- }
Solution d'opération de bits
Les opérations sur les bits peuvent être utilisées ici, mais la clé réside dans certaines restrictions de la question, comme un tableau non vide d'entiers positifs, les éléments de chaque tableau ne dépasseront pas 100, la taille du tableau ne dépassera pas 200, etc. .
Le principe est très simple. Il suffit de demander un tableau bits[somme+1] de taille somme+1. Les nombres dans le tableau ne peuvent être que 0 et 1. On peut l'imaginer comme un bit binaire très long, car. int et Le type long est trop court, nous utilisons ici un tableau. Ensuite, chaque fois qu'un élément du tableau est parcouru, tel que m, le bit binaire est décalé vers la gauche de m bits, puis soumis à un OU avec le bit binaire d'origine. Enfin, déterminez si bits[sum/2] vaut 1 et renvoyez true s'il vaut 1.La description textuelle n’est pas très simple. Prenons l’exemple 1 comme exemple pour dessiner une image.
Enfin, vous trouverez une règle, c'est-à-dire que tant que le résultat final de l'opération est 1, il peut être combiné en utilisant les éléments du tableau. Tant qu'il est 0, il ne peut pas être combiné en utilisant les éléments du tableau. processus ci-dessus, le code est facile à écrire. Parce que nous avons seulement besoin de déterminer si la valeur moyenne du bit binaire est 1, nous avons seulement besoin de calculer le bit bas, et le bit haut n'a pas besoin d'être calculé du tout, car il est déplacé vers la gauche et le bit haut bit n'aura aucun impact sur la valeur moyenne, nous pouvons donc le faire ici. Un peu d'optimisation, et enfin regardons le code.
- #include <vector>
-
- bool canPartition(std::vector<int>& nums) {
- // 计算数组中所有数字的和
- int sum = 0;
- for (int n : nums) {
- sum += n;
- }
- // 如果sum是奇数,直接返回false
- if ((sum & 1) == 1) {
- return false;
- }
- int len = sum / 2;
- // 这里bits的长度是len+1,因为我们只需要计算低位就行了,没必要计算所有的
- std::vector<char> bits(len + 1, 0);
- bits[0] = 1;
- for (int num : nums) {
- for (int j = len - num; j >= 0; j--) {
- bits[j + num] |= bits[j];
- }
- // 判断中位数如果是1,说明可以分成两种相等的子集,直接返回true,不需要再计算了
- if ((bits[len] & 1) != 0) {
- return true;
- }
- }
- return false;
- }