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
-
416. Partition Equal Subset Sum - https://leetcode.com/problems/partition-equal-subset-sum/ (0/1 Knapsack variantı)
-
494. Target Sum - https://leetcode.com/problems/target-sum/ (Subset sum problemi)
-
474. Ones and Zeroes - https://leetcode.com/problems/ones-and-zeroes/ (2D Knapsack)
-
1049. Last Stone Weight II - https://leetcode.com/problems/last-stone-weight-ii/ (Partition problemi)
-
518. Coin Change 2 - https://leetcode.com/problems/coin-change-2/ (Unbounded Knapsack)
Real-World Tətbiqləri
- Resource Allocation: Məhdud büdcə ilə layihə seçimi
- Portfolio Optimization: İnvestisiya seçimi
- Cargo Loading: Yük maşınına optimal yükləmə
- Budget Management: Xərclərin optimallaşdırılması
- Memory Management: Cache-də hansı məlumatların saxlanması
- Task Scheduling: CPU və ya GPU task seçimi
Optimizasiya Məsləhətləri
- Space optimization: O(W) space mümkündür
- Backward traversal: Single array üçün geriyə doğru traverse
- Early termination: Maksimum dəyər əldə edilibsə
- Value/Weight sorting: Bəzi hallarda performansı yaxşılaşdırır
- 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