моя контактная информация
Почтамезофия@protonmail.com
2024-07-12
한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina
Вам дан непустой массив nums, содержащий только положительные целые числа. Оцените, можно ли этот массив разделить на два подмножества так, чтобы сумма элементов двух подмножеств была равна.
Решение для динамического программирования
Эту проблему невозможно решить с помощью алгоритма обратного отслеживания. Если объем данных велик, время ожидания истекает. Мы можем использовать динамическое программирование.
Этот вопрос определяет, равна ли сумма элементов двух частей, когда массив разделен на две части. Сначала нам нужно вычислить сумму всех элементов массива, а затем определить, является ли сумма четным числом: если это не четное число, значит, ее нельзя разделить на две совершенно равные части, и она вернется ложь напрямую.
Если это четное число, нам нужно только определить, есть ли элементы, сумма которых равна сумме/2. Если оно равно сумме/2, то остальные также должны быть равны сумме/2, а это означает, что. мы можем разделить массив на две части с равными элементами.
Тогда проблема на данный момент очень ясна. Если предположить, что sum/2 — это вместимость рюкзака, нам нужно только найти некоторые элементы и поместить их в рюкзак, если максимальная сумма элементов в рюкзаке равна sum. /2, это означает, что мы можем разделить массив на две равные части. Разве это не классическая проблема с рюкзаком при счёте 0:1? Подробности основной задачи о рюкзаке из серии задач о рюкзаке можно прочитать ниже. Я не буду здесь повторять введение. Мы ищем его формулу рекурсии и определяем dp[i][j] как максимальное значение, полученное при помещении i-го предмета в рюкзак вместимостью j.
Значение i-го элемента — nums [i -1]:
Если nums [i -1]>j, это означает, что вместимости рюкзака недостаточно и i-й предмет нельзя положить, поэтому мы не можем выбрать i-й предмет, тогда
dp[i][j]=dp[i -1][j];
Если nums [i -1]<=j, это означает, что j-й предмет можно положить в рюкзак. Мы можем выбрать, положить его или нет, просто возьмем максимальное значение. Если положим, он займет часть. вместимость рюкзака Максимальное значение да.
dp[i][j]=dp[i-1][j-числа[i-1]]+числа[i-1]
Если ты не отпустишь
dp[i][j]=dp[i -1][j];
Возьмите максимум из двух
Окончательная рекурсивная формула и код выглядят следующим образом:
- #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;
- }
Мы также можем написать так: Двумерный массив dp имеет логический тип. dp[i][j] указывает, может ли сумма первых i элементов массива образовывать сумму j. 0]=true, что означает, что первые 0 элементов (то есть ни одного элемента) не могут образовывать сумму, равную 0.код показан ниже
- #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];
- }
Мы видим, что при вычислении приведенного выше двумерного массива текущее значение связано только с указанной выше строкой, поэтому мы можем изменить его на одномерное. Обратите внимание, что второй цикл for должен быть ретроспективным, иначе предыдущее значение будет перезаписано. и результат будет неверным, присмотритесь.
dp[j] = (dp[j] || dp[j - числа [i - 1]]);
Вы можете понять, что значение в конце той же строки зависит от предыдущего. Если это не флешбек, то предыдущее значение модифицируется, что приведет к ошибке в расчете последующего.Давайте посмотрим на код
- #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];
- }
Решение алгоритма DFS
Каждый элемент может быть выбран или нет. Вам нужно только определить, равна ли сумма элементов во всех возможных комбинациях сумме/2. Мы можем думать об этом как о двоичном дереве. Левый дочерний узел представляет собой выбор. текущий элемент, а правый дочерний узел представляет выбор текущего элемента. Дочерний узел указывает, что текущий элемент не выбран, как показано на рисунке ниже. Оранжевый узел указывает, что текущий элемент выбран, а синий узел. указывает, что текущий элемент не выбран.
Давайте сначала посмотрим на предварительный код реализации.
- #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);
- }
Но, к сожалению, поскольку объем вычислений слишком велик, это приведет к тайм-ауту операции. Мы можем оптимизировать ее и взглянуть на код.
- #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);
- }
Решение для битовых операций
Здесь можно использовать битовые операции, но ключ кроется в некоторых ограничениях вопроса, таких как непустой массив целых положительных чисел, элементы в каждом массиве не будут превышать 100, размер массива не будет превышать 200 и т.д. .
Принцип очень прост: нам нужно применить только массив bits[sum+1] размера sum+1. Числа в массиве могут быть только 0 и 1. Мы можем представить его как очень длинный двоичный бит, потому что. int и тип long слишком короткий, здесь мы используем массив. Затем каждый раз, когда просматривается элемент массива, например m, двоичный бит сдвигается влево на m бит, а затем выполняется операция ИЛИ с исходным двоичным битом. Наконец, определите, равно ли bits[sum/2] 1, и верните true, если оно равно 1.Текстовое описание не очень простое. Давайте возьмем Пример 1 в качестве примера, чтобы нарисовать картинку.
Наконец, вы обнаружите правило: пока конечный результат операции равен 1, его можно комбинировать с использованием элементов массива. Пока он равен 0, его нельзя комбинировать с использованием элементов массива. выше процесса, код легко написать. Поскольку нам нужно только определить, равно ли 1 среднее значение двоичного бита, нам нужно вычислить только младший бит, а старший бит вообще не нужно вычислять, потому что он перемещается влево, а старший бит bit не окажет никакого влияния на среднее значение, поэтому мы можем сделать это здесь. Небольшая оптимизация и, наконец, давайте посмотрим на код.
- #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;
- }