Əsas məzmuna keçin

Knapsack Problem (Çanta Problemi)

Problem Təsviri

Müəyyən tutumu olan bir çantanız var. Hər birinin dəyəri və çəkisi olan əşyalar verilmişdir. Çantanın tutumunu aşmadan maksimum dəyəri əldə edəcək əşyaları seçin.

0/1 Knapsack: Hər əşyadan ya götürürsünüz, ya da götürmürsünüz (hissə-hissə olmur).

Məsələn:

  • Tutum: 50
  • Əşyalar:
    • value : 60, çəki: 10
    • value: 100, çəki: 20
    • value: 120, çəki: 30
  • Maksimum dəyər: 220 (əşya 2 və 3)

Recurrence Relation:

dp[i][w] = max(
dp[i-1][w], // don't take item i
dp[i-1][w-weight[i]] + value[i] // take item i
)

Müxtəlif Yanaşmalar

1. Sadə Rekursiya (Naive Approach)

Koda bax
public class KnapsackNaive {

public static int knapsack(int[] weights, int[] values, int capacity) {
return knapsackHelper(weights, values, capacity, weights.length);
}

private static int knapsackHelper(int[] weights, int[] values,
int capacity, int n) {
// Base cases
if (n == 0 || capacity == 0) {
return 0;
}

// If weight of nth item is more than capacity, can't include it
if (weights[n - 1] > capacity) {
return knapsackHelper(weights, values, capacity, n - 1);
}

// Return maximum of:
// 1. nth item included
// 2. not included
int include = values[n - 1] +
knapsackHelper(weights, values,
capacity - weights[n - 1], n - 1);
int exclude = knapsackHelper(weights, values, capacity, n - 1);

return Math.max(include, exclude);
}

public static void main(String[] args) {
System.out.println("0/1 Knapsack (Naive Recursion):");

int[] weights = {10, 20, 30};
int[] values = {60, 100, 120};
int capacity = 50;

System.out.println("Çəkilər: [10, 20, 30]");
System.out.println("Dəyərlər: [60, 100, 120]");
System.out.println("Tutum: " + capacity);
System.out.println("Maksimum dəyər: " + knapsack(weights, values, capacity));

// Performance test
int[] weights2 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int[] values2 = {10, 20, 30, 40, 50, 60, 70, 80, 90, 100};

long startTime = System.currentTimeMillis();
int result = knapsack(weights2, values2, 20);
long endTime = System.currentTimeMillis();

System.out.println("\n10 əşya, tutum 20");
System.out.println("Maksimum dəyər: " + result);
System.out.println("Vaxt: " + (endTime - startTime) + " ms");
}
}

Mürəkkəblik:

  • Time Complexity: O(2^n) - Eksponensial
  • Space Complexity: O(n) - Rekursiya stack-i

Problemlər:

  • Çox yavaş çox əşya üçün
  • Eyni alt-problemlər dəfələrlə hesablanır

2. Memoization (Top-Down DP)

Koda bax
import java.util.HashMap;
import java.util.Map;

public class KnapsackMemoization {

// Using 2D array for memoization
public static int knapsack(int[] weights, int[] values, int capacity) {
int n = weights.length;
int[][] memo = new int[n + 1][capacity + 1];

// Initialize with -1 (not computed)
for (int i = 0; i <= n; i++) {
for (int j = 0; j <= capacity; j++) {
memo[i][j] = -1;
}
}

return knapsackHelper(weights, values, capacity, n, memo);
}

private static int knapsackHelper(int[] weights, int[] values,
int capacity, int n, int[][] memo) {
// Base cases
if (n == 0 || capacity == 0) {
return 0;
}

// Check if already computed
if (memo[n][capacity] != -1) {
return memo[n][capacity];
}

// If weight exceeds capacity, can't include
if (weights[n - 1] > capacity) {
memo[n][capacity] = knapsackHelper(weights, values,
capacity, n - 1, memo);
} else {
// Max of include or exclude
int include = values[n - 1] +
knapsackHelper(weights, values,
capacity - weights[n - 1], n - 1, memo);
int exclude = knapsackHelper(weights, values, capacity, n - 1, memo);

memo[n][capacity] = Math.max(include, exclude);
}

return memo[n][capacity];
}

// Alternative: Using HashMap with string key
private static Map<String, Integer> memoMap = new HashMap<>();

public static int knapsackMap(int[] weights, int[] values, int capacity) {
memoMap.clear();
return knapsackHelperMap(weights, values, capacity, weights.length);
}

private static int knapsackHelperMap(int[] weights, int[] values,
int capacity, int n) {
if (n == 0 || capacity == 0) return 0;

String key = n + "," + capacity;
if (memoMap.containsKey(key)) {
return memoMap.get(key);
}

int result;
if (weights[n - 1] > capacity) {
result = knapsackHelperMap(weights, values, capacity, n - 1);
} else {
int include = values[n - 1] +
knapsackHelperMap(weights, values,
capacity - weights[n - 1], n - 1);
int exclude = knapsackHelperMap(weights, values, capacity, n - 1);
result = Math.max(include, exclude);
}

memoMap.put(key, result);
return result;
}

public static void main(String[] args) {
System.out.println("0/1 Knapsack (Memoization):");

int[] weights = {10, 20, 30};
int[] values = {60, 100, 120};
int capacity = 50;

System.out.println("Çəkilər: [10, 20, 30]");
System.out.println("Dəyərlər: [60, 100, 120]");
System.out.println("Tutum: " + capacity);
System.out.println("Maksimum dəyər: " + knapsack(weights, values, capacity));

// Performance test
int[] weights2 = new int[100];
int[] values2 = new int[100];
for (int i = 0; i < 100; i++) {
weights2[i] = i + 1;
values2[i] = (i + 1) * 10;
}

long startTime = System.currentTimeMillis();
int result = knapsack(weights2, values2, 500);
long endTime = System.currentTimeMillis();

System.out.println("\n100 əşya, tutum 500");
System.out.println("Maksimum dəyər: " + result);
System.out.println("Vaxt: " + (endTime - startTime) + " ms");
}
}

Mürəkkəblik:

  • Time Complexity: O(n * W) - n: əşya sayı, W: tutum
  • Space Complexity: O(n * W) - Memo table və rekursiya stack-i

3. Tabulation (Bottom-Up DP)

Koda bax
public class KnapsackTabulation {

public static int knapsack(int[] weights, int[] values, int capacity) {
int n = weights.length;

// Create DP table
int[][] dp = new int[n + 1][capacity + 1];

// Base case: dp[0][w] = 0 and dp[i][0] = 0 (already initialized)

// Fill table bottom-up
for (int i = 1; i <= n; i++) {
for (int w = 1; w <= capacity; w++) {
// If current item can fit
if (weights[i - 1] <= w) {
// Max of include or exclude
dp[i][w] = Math.max(
dp[i - 1][w], // exclude
dp[i - 1][w - weights[i - 1]] + values[i - 1] // include
);
} else {
// Can't include, take previous value
dp[i][w] = dp[i - 1][w];
}
}
}

return dp[n][capacity];
}

// Method to print DP table
public static void printDPTable(int[] weights, int[] values, int capacity) {
int n = weights.length;
int[][] dp = new int[n + 1][capacity + 1];

for (int i = 1; i <= n; i++) {
for (int w = 1; w <= capacity; w++) {
if (weights[i - 1] <= w) {
dp[i][w] = Math.max(
dp[i - 1][w],
dp[i - 1][w - weights[i - 1]] + values[i - 1]
);
} else {
dp[i][w] = dp[i - 1][w];
}
}
}

System.out.println("\nDP Table:");
System.out.print("W\\I\t");
for (int i = 0; i <= n; i++) {
System.out.print(i + "\t");
}
System.out.println();

for (int w = 0; w <= capacity; w++) {
System.out.print(w + "\t");
for (int i = 0; i <= n; i++) {
System.out.print(dp[i][w] + "\t");
}
System.out.println();
}
}

public static void main(String[] args) {
System.out.println("0/1 Knapsack (Tabulation):");

int[] weights = {10, 20, 30};
int[] values = {60, 100, 120};
int capacity = 50;

System.out.println("Çəkilər: [10, 20, 30]");
System.out.println("Dəyərlər: [60, 100, 120]");
System.out.println("Tutum: " + capacity);
System.out.println("Maksimum dəyər: " + knapsack(weights, values, capacity));

printDPTable(weights, values, capacity);

// Performance test
long startTime = System.currentTimeMillis();
int result = knapsack(weights, values, capacity);
long endTime = System.currentTimeMillis();

System.out.println("\nVaxt: " + (endTime - startTime) + " ms");
}
}

Mürəkkəblik:

  • Time Complexity: O(n * W)
  • Space Complexity: O(n * W) - DP table üçün

4. Space Optimized (O(W) Space)

Koda bax
public class KnapsackOptimized {

// Using two 1D arrays
public static int knapsack(int[] weights, int[] values, int capacity) {
int n = weights.length;

// Use only two rows
int[] prev = new int[capacity + 1];
int[] curr = new int[capacity + 1];

for (int i = 1; i <= n; i++) {
for (int w = 1; w <= capacity; w++) {
if (weights[i - 1] <= w) {
curr[w] = Math.max(
prev[w],
prev[w - weights[i - 1]] + values[i - 1]
);
} else {
curr[w] = prev[w];
}
}

// Swap arrays
int[] temp = prev;
prev = curr;
curr = temp;
}

return prev[capacity];
}

// Ultra optimized: single array (traverse backwards)
public static int knapsackSingleArray(int[] weights, int[] values, int capacity) {
int n = weights.length;
int[] dp = new int[capacity + 1];

for (int i = 0; i < n; i++) {
// Traverse backwards to avoid using updated values
for (int w = capacity; w >= weights[i]; w--) {
dp[w] = Math.max(
dp[w],
dp[w - weights[i]] + values[i]
);
}
}

return dp[capacity];
}

public static void main(String[] args) {
System.out.println("0/1 Knapsack (Space Optimized):");

int[] weights = {10, 20, 30};
int[] values = {60, 100, 120};
int capacity = 50;

System.out.println("Çəkilər: [10, 20, 30]");
System.out.println("Dəyərlər: [60, 100, 120]");
System.out.println("Tutum: " + capacity);
System.out.println("Maksimum dəyər: " + knapsack(weights, values, capacity));
System.out.println("Single array: " + knapsackSingleArray(weights, values, capacity));

// Performance test with large input
int[] largeWeights = new int[1000];
int[] largeValues = new int[1000];
for (int i = 0; i < 1000; i++) {
largeWeights[i] = (i % 50) + 1;
largeValues[i] = (i % 100) + 1;
}

long startTime = System.currentTimeMillis();
int result = knapsackSingleArray(largeWeights, largeValues, 500);
long endTime = System.currentTimeMillis();

System.out.println("\n1000 əşya, tutum 500");
System.out.println("Maksimum dəyər: " + result);
System.out.println("Vaxt: " + (endTime - startTime) + " ms");
}
}

Mürəkkəblik:

  • Time Complexity: O(n * W)
  • Space Complexity: O(W) - Yalnız bir array

5. Item Tracking (Hansı Əşyalar Seçildi)

Koda bax
import java.util.ArrayList;
import java.util.List;

public class KnapsackWithItems {

public static class Result {
int maxValue;
List<Integer> items;
int totalWeight;

Result(int maxValue, List<Integer> items, int totalWeight) {
this.maxValue = maxValue;
this.items = items;
this.totalWeight = totalWeight;
}
}

public static Result knapsackWithItems(int[] weights, int[] values, int capacity) {
int n = weights.length;
int[][] dp = new int[n + 1][capacity + 1];

// Fill DP table
for (int i = 1; i <= n; i++) {
for (int w = 1; w <= capacity; w++) {
if (weights[i - 1] <= w) {
dp[i][w] = Math.max(
dp[i - 1][w],
dp[i - 1][w - weights[i - 1]] + values[i - 1]
);
} else {
dp[i][w] = dp[i - 1][w];
}
}
}

// Backtrack to find items
List<Integer> selectedItems = new ArrayList<>();
int w = capacity;
int totalWeight = 0;

for (int i = n; i > 0 && w > 0; i--) {
// If value comes from including item i
if (dp[i][w] != dp[i - 1][w]) {
selectedItems.add(i - 1); // Add item index
totalWeight += weights[i - 1];
w -= weights[i - 1];
}
}

return new Result(dp[n][capacity], selectedItems, totalWeight);
}

public static void main(String[] args) {
System.out.println("0/1 Knapsack with Item Tracking:");

int[] weights = {10, 20, 30};
int[] values = {60, 100, 120};
int capacity = 50;

System.out.println("Çəkilər: [10, 20, 30]");
System.out.println("Dəyərlər: [60, 100, 120]");
System.out.println("Tutum: " + capacity);

Result result = knapsackWithItems(weights, values, capacity);

System.out.println("\nMaksimum dəyər: " + result.maxValue);
System.out.println("Seçilmiş əşyalar (index): " + result.items);
System.out.println("Ümumi çəki: " + result.totalWeight);

System.out.println("\nÆşya təfərrüatları:");
for (int idx : result.items) {
System.out.println("Əşya " + idx + ": çəki=" +
weights[idx] + ", dəyər=" + values[idx]);
}
}
}

Mürəkkəblik:

  • Time Complexity: O(n * W)
  • Space Complexity: O(n * W) - Backtracking üçün tam DP table lazımdır

Variantlar

Koda bax
public class KnapsackVariants {

// 1. Unbounded Knapsack (hər əşyadan istənilən sayda götürə bilərsiniz)
public static int unboundedKnapsack(int[] weights, int[] values, int capacity) {
int[] dp = new int[capacity + 1];

for (int w = 1; w <= capacity; w++) {
for (int i = 0; i < weights.length; i++) {
if (weights[i] <= w) {
dp[w] = Math.max(dp[w], dp[w - weights[i]] + values[i]);
}
}
}

return dp[capacity];
}

// 2. Fractional Knapsack (hissə-hissə götürə bilərsiniz) - Greedy
public static double fractionalKnapsack(int[] weights, int[] values, int capacity) {
int n = weights.length;

// Create array of items with value/weight ratio
Item[] items = new Item[n];
for (int i = 0; i < n; i++) {
items[i] = new Item(weights[i], values[i], i);
}

// Sort by value/weight ratio in descending order
java.util.Arrays.sort(items, (a, b) ->
Double.compare(b.ratio, a.ratio));

double totalValue = 0;
int remainingCapacity = capacity;

for (Item item : items) {
if (item.weight <= remainingCapacity) {
// Take whole item
totalValue += item.value;
remainingCapacity -= item.weight;
} else {
// Take fraction of item
totalValue += item.value * ((double) remainingCapacity / item.weight);
break;
}
}

return totalValue;
}

static class Item {
int weight, value, index;
double ratio;

Item(int weight, int value, int index) {
this.weight = weight;
this.value = value;
this.index = index;
this.ratio = (double) value / weight;
}
}

// 3. Count Subsets with Given Sum
public static int countSubsets(int[] arr, int sum) {
int n = arr.length;
int[][] dp = new int[n + 1][sum + 1];

// Base case: empty subset has sum 0
for (int i = 0; i <= n; i++) {
dp[i][0] = 1;
}

for (int i = 1; i <= n; i++) {
for (int s = 1; s <= sum; s++) {
dp[i][s] = dp[i - 1][s]; // exclude

if (arr[i - 1] <= s) {
dp[i][s] += dp[i - 1][s - arr[i - 1]]; // include
}
}
}

return dp[n][sum];
}

// 4. Partition Equal Subset Sum
public static boolean canPartition(int[] nums) {
int sum = 0;
for (int num : nums) {
sum += num;
}

// If sum is odd, can't partition equally
if (sum % 2 != 0) {
return false;
}

int target = sum / 2;
boolean[] dp = new boolean[target + 1];
dp[0] = true;

for (int num : nums) {
for (int s = target; s >= num; s--) {
dp[s] = dp[s] || dp[s - num];
}
}

return dp[target];
}

// 5. Target Sum (LeetCode 494)
public static int findTargetSumWays(int[] nums, int target) {
int sum = 0;
for (int num : nums) {
sum += num;
}

// Check if solution is possible
if (Math.abs(target) > sum || (sum + target) % 2 != 0) {
return 0;
}

int subsetSum = (sum + target) / 2;
return countSubsets(nums, subsetSum);
}

public static void main(String[] args) {
System.out.println("Knapsack Variants:\n");

int[] weights = {10, 20, 30};
int[] values = {60, 100, 120};
int capacity = 50;

System.out.println("1. Unbounded Knapsack:");
System.out.println("Maksimum dəyər: " +
unboundedKnapsack(weights, values, capacity));

System.out.println("\n2. Fractional Knapsack:");
System.out.println("Maksimum dəyər: " +
fractionalKnapsack(weights, values, capacity));

System.out.println("\n3. Count Subsets with Sum:");
int[] arr = {1, 2, 3, 3};
System.out.println("Array: [1, 2, 3, 3], Sum: 6");
System.out.println("Count: " + countSubsets(arr, 6));

System.out.println("\n4. Partition Equal Subset Sum:");
int[] nums = {1, 5, 11, 5};
System.out.println("Array: [1, 5, 11, 5]");
System.out.println("Can partition: " + canPartition(nums));

System.out.println("\n5. Target Sum:");
int[] nums2 = {1, 1, 1, 1, 1};
System.out.println("Array: [1, 1, 1, 1, 1], Target: 3");
System.out.println("Ways: " + findTargetSumWays(nums2, 3));
}
}

Performance Müqayisəsi

Koda bax
public class KnapsackComparison {

public static void comparePerformance() {
int[][] testCases = {
{10, 5}, // 10 items, capacity 5
{20, 10}, // 20 items, capacity 10
{50, 25}, // 50 items, capacity 25
{100, 50}, // 100 items, capacity 50
{200, 100} // 200 items, capacity 100
};

System.out.println("Performance Müqayisəsi:");
System.out.println("Items\tCap\tNaive\t\tMemo\t\tTabulation\tOptimized");

for (int[] test : testCases) {
int n = test[0];
int capacity = test[1];

int[] weights = new int[n];
int[] values = new int[n];
for (int i = 0; i < n; i++) {
weights[i] = (i % 10) + 1;
values[i] = (i % 20) + 1;
}

System.out.print(n + "\t" + capacity + "\t");

// Naive (only for small n)
if (n <= 20) {
long start = System.nanoTime();
KnapsackNaive.knapsack(weights, values, capacity);
long end = System.nanoTime();
System.out.print((end - start) / 1000 + "μs\t\t");
} else {
System.out.print("Too slow\t");
}

// Memoization
long start = System.nanoTime();
KnapsackMemoization.knapsack(weights, values, capacity);
long end = System.nanoTime();
System.out.print((end - start) / 1000 + "μs\t\t");

// Tabulation
start = System.nanoTime();
KnapsackTabulation.knapsack(weights, values, capacity);
end = System.nanoTime();
System.out.print((end - start) / 1000 + "μs\t\t");

// Optimized
start = System.nanoTime();
KnapsackOptimized.knapsackSingleArray(weights, values, capacity);
end = System.nanoTime();
System.out.print((end - start) / 1000 + "μs");

System.out.println();
}
}

public static void main(String[] args) {
comparePerformance();
}
}

Müsahibə Sualları

1. Əsas 0/1 Knapsack

Sual: Maksimum dəyəri əldə etmək üçün hansı əşyaları seçməlisiniz? Cavab: DP ilə O(n*W) həll

2. Unbounded Knapsack

Sual: Hər əşyadan istənilən sayda götürə bilərsinizsə? Cavab: Coin Change-ə oxşar, lakin maksimizasiya problemi

3. Subset Sum Problem

Sual: Verilmiş cəmə bərabər alt-çoxluq tapın. LeetCode: Problem 416 - Partition Equal Subset Sum

4. Target Sum

Sual: +/- əlavə edərək target sum əldə edin. LeetCode: Problem 494 - Target Sum

5. Fractional Knapsack

Sual: Hissə-hissə götürə bilərsinizsə? Cavab: Greedy algorithm (value/weight ratio-ya görə sırala)

LeetCode Problemləri

  1. 416. Partition Equal Subset Sum - https://leetcode.com/problems/partition-equal-subset-sum/ (0/1 Knapsack variantı)

  2. 494. Target Sum - https://leetcode.com/problems/target-sum/ (Subset sum problemi)

  3. 474. Ones and Zeroes - https://leetcode.com/problems/ones-and-zeroes/ (2D Knapsack)

  4. 1049. Last Stone Weight II - https://leetcode.com/problems/last-stone-weight-ii/ (Partition problemi)

  5. 518. Coin Change 2 - https://leetcode.com/problems/coin-change-2/ (Unbounded Knapsack)

Real-World Tətbiqləri

  1. Resource Allocation: Məhdud büdcə ilə layihə seçimi
  2. Portfolio Optimization: İnvestisiya seçimi
  3. Cargo Loading: Yük maşınına optimal yükləmə
  4. Budget Management: Xərclərin optimallaşdırılması
  5. Memory Management: Cache-də hansı məlumatların saxlanması
  6. Task Scheduling: CPU və ya GPU task seçimi

Optimizasiya Məsləhətləri

  1. Space optimization: O(W) space mümkündür
  2. Backward traversal: Single array üçün geriyə doğru traverse
  3. Early termination: Maksimum dəyər əldə edilibsə
  4. Value/Weight sorting: Bəzi hallarda performansı yaxşılaşdırır
  5. Branch and Bound: Daha böyük instance-lər üçün

Fərqlər

0/1 vs Unbounded Knapsack

  • 0/1: Hər əşyadan max 1 dəfə
  • Unbounded: Hər əşyadan istənilən sayda

0/1 vs Fractional Knapsack

  • 0/1: Tam götürmək və ya heç götürməmək (DP)
  • Fractional: Hissə-hissə götürə bilərsiniz (Greedy)

Əlaqəli Problemlər

  • Subset Sum
  • Partition Equal Subset Sum
  • Target Sum
  • Coin Change
  • Rod Cutting
  • Minimum Subset Sum Difference
  • Count of Subset Sum