Размещение отдельных предметов в мешках для максимизации прибыли

Задача о рюкзаке — это комбинаторная оптимизационная задача. Чтобы решить эту проблему, мы можем следовать следующему:

Общая стратегия:

  1. Найти базовый случай
  2. Найти рекуррентное соотношение
  3. Повторное использование вычисленных значений
  4. Напишите восходящее итеративное решение

Имея веса w и прибыль p для N товаров, нужно найти подмножество этих предметы, которые принесут максимальную прибыль, но их совокупная сумма не должна превышать общую вместимость рюкзака C.

Profits = [20, 5, 10, 40, 15, 25]
Weights = [1, 2, 3, 8, 7, 4]
Capacity = 10

Мы можем решить эту проблему снизу вверх. Сначала мы решаем меньшие подзадачи, а затем решаем более крупные подзадачи, используя уже решенные проблемы. Следующий восходящий подход вычисляет dp[i][j] для каждого 1 <= i <= n и 0 <= j <= W, что является максимальной прибылью, которая может быть достигнута с весом, меньшим или равным j, и с использованием предметов до первых i предметов.

Подход здесь заключается в создании матрицы со столбцами C (общая вместимость) и строками n (количество элементов). В этой матрице каждая ячейка представляет максимальную прибыль путем выбора количества элементов в этой строке и общего веса в этом столбце.

Из приведенной выше таблицы видно, что существуют некоторые естественные базовые случаи. Если мы говорим, что общий вес равен нулю, то мы не можем выбрать ни один элемент, поэтому 1-й столбец матрицы заполнен нулевой прибылью. Точно так же, если мы не выбираем какой-либо элемент, у нас нет прибыли, поэтому в первой строке устанавливается нулевая прибыль. Для других ячеек матрицы мы используем график решений и заполняем прибыль. Код для той же логики закодирован ниже:

def knapsack(p, w, c):
    n = len(p)
    dp = [[0 for _ in range(c + 1)] for _ in range(n + 1)]
    for i in range(1, len(p) + 1):
        for j in range(c + 1):
            if w[i - 1] > j:
                dp[i][j] = dp[i - 1][j]
            else:
                dp[i][j] = max(dp[i - 1][j], \
                dp[i - 1][j - w[i - 1]] + p[i - 1])

Оптимизация пространства по приведенной выше логике:

def knapsack(p, w, c):
    n = len(p)
    dp = [0 for i in range(c+1)]
    
    for i in range(1, n+1):
        for j in range(c, 0, -1):
            if w[i-1] <= j:
                dp[j] = max(dp[j], dp[j-w[i-1]]+p[i-1])
    
    return dp[c]

Сумма подмножества

Учитывая массив и цель, возвращайте true, если существует подмножество, сумма которого равна данной цели.

nums = [2, 3, 7, 8, 10]
target = 11
OP: True

Реализация кода

def isSubsetSum(nums, target):
    n = len(nums)
    dp = [[False for _ in range(target + 1)] for _ in range(n+1)]
    
    for i in range(n):
        dp[i][0] = True
    
    for i in range(1, n+1):
        for j in range(1, target+1):
            if nums[i-1] <= j:
                dp[i][j] = dp[i-1][j-nums[i-1]] or dp[i-1][j]
            else:
                dp[i][j] = dp[i-1][j]
    
    return dp[n][target]

Количество подмножеств с заданной суммой

Учитывая массив и цель, возвращают количество подмножеств, сумма которых равна данной цели.

nums = [2, 3, 5, 6, 8, 10]
target = 10
OP = 3 {10}, {2, 8}, {2, 3, 5}

Реализация кода

def subsetCount(nums, target):
    n = len(nums)
    dp = [ [0]*(target+1) for _ in range(n+1)]
    for i in range(target+1):
        dp[0][i] = 0
    for i in range(n+1):
        dp[i][0] = 1
    for i in range(1, n+1):
        for j in range(1, target+1):
            if nums[i-1] <= j:
                dp[i][j] = dp[i-1][j] + dp[i-1][j-nums[i-1]]
            else:
                dp[i][j] = dp[i-1][j]
        
    return dp[n][target]

Раздел равной суммы

Для заданного массива проверьте, можно ли его разделить на два массива так, чтобы сумма отдельных массивов была равна.

nums = [ 11, 5, 1, 5]
OP = True {11} {1, 5, 5}

Реализация кода

def isSubsetSum(nums, target):
    n = len(nums)
    dp = [[False for _ in range(target + 1)] for _ in range(n+1)]
    
    for i in range(n):
        dp[i][0] = True
    
    for i in range(1, n+1):
        for j in range(1, target+1):
            if nums[i-1] <= j:
                dp[i][j] = dp[i-1][j-nums[i-1]] or dp[i-1][j]
            else:
                dp[i][j] = dp[i-1][j]
    
    return dp[n][target]
def canPartition(nums):
    tsum = sum(nums)
    if tsum%2 != 0:
        return False
    else:
        return isSubsetSum(nums, tsum//2)

Подсчет суммы подмножества с заданной разностью

Пусть суммы двух подмножеств равны s1 и s2.

s1 - s2 = target(given difference)
s1 + s2 = sum(nums)
2s1 = target + sum(nums)
s1 = (target + sum(nums))/2

Теперь проблема сводится к нахождению количества подмножеств с новой целью (цель+сумма(числа)/2.

Реализация кода

def subsetCount(nums, target):
    n = len(nums)
    dp = [ [0]*(target+1) for _ in range(n+1)]
    for i in range(target+1):
        dp[0][i] = 0
    for i in range(n+1):
        dp[i][0] = 1
    for i in range(1, n+1):
        for j in range( target+1):
            if nums[i-1] <= j:
                dp[i][j] = dp[i-1][j] + dp[i-1][j-nums[i-1]]
            else:
                dp[i][j] = dp[i-1][j]
        
    return dp[n][target]
    
def subsetWithGivenDiff(nums, target):
    tsum = sum(nums)
    if (abs(target) > tsum or (tsum + target) % 2 != 0):
        return 0
    s1 = (tsum + target)//2
    ans = subsetCount(nums, s1)
    return ans

Целевая сумма

Вам дан массив целых чисел nums и целое число target. Вы хотите построить выражение из чисел, добавив один из символов '+' и '-' перед каждым целым числом в числах, а затем соединив все целые числа. Возвращает количество различных выражений, которые вы можете построить, которые оцениваются как target.

Input: nums = [1,1,1,1,1], target = 3
Output: 5
Explanation: There are 5 ways to assign symbols to make the sum of nums be target 3.
-1 + 1 + 1 + 1 + 1 = 3
+1 - 1 + 1 + 1 + 1 = 3
+1 + 1 - 1 + 1 + 1 = 3
+1 + 1 + 1 - 1 + 1 = 3
+1 + 1 + 1 + 1 - 1 = 3

Поскольку у нас есть только два варианта «+» или «-», мы можем рассматривать это как разделение на два подмножества и затем получение их различий. например +1 +1 +1 -1 -1 = 3. Это можно записать как (1+1+1)-(1+1). Мы вычисляем разницу суммы этих двух наборов (1, 1, 1) → с положительными знаками, сумма = 3 и (1, 1) → с отрицательными знаками, сумма = 2. Таким образом, разность 1 становится целью.

Реализация кода

def subsetCount(nums, target):
    n = len(nums)
    dp = [ [0]*(target+1) for _ in range(n+1)]
    for i in range(target+1):
        dp[0][i] = 0
    for i in range(n+1):
        dp[i][0] = 1
    for i in range(1, n+1):
        for j in range( target+1):
            if nums[i-1] <= j:
                dp[i][j] = dp[i-1][j] + dp[i-1][j-nums[i-1]]
            else:
                dp[i][j] = dp[i-1][j]
        
    return dp[n][target]
    
def findTargetSumWays(nums, target):
    tsum = sum(nums)
    if (abs(target) > tsum or (tsum + target) % 2 != 0):
        return 0
    s1 = (tsum + target)//2
    ans = subsetCount(nums, s1)
    return ans

Удачного кодирования!

Дополнительные материалы на PlainEnglish.io. Подпишитесь на нашу бесплатную еженедельную рассылку новостей. Подпишитесь на нас в Twitter и LinkedIn. Посетите наш Community Discord и присоединитесь к нашему Коллективу талантов.