State Compression is a technique used to optimize the space complexity of dynamic programming solutions by reducing the dimensionality of the state space or storing only the necessary states.
It's particularly useful when the solution only depends on a small subset of the previously computed states.
State Compression is useful when:
Here's a space-optimized solution for computing the nth Fibonacci number:
Here's a space-optimized solution for the 0/1 knapsack problem:
Here's a space-optimized solution for finding the longest common subsequence:
Learn how to optimize space usage by storing only the necessary states for computation.
State Compression is a technique used to optimize the space complexity of dynamic programming solutions by reducing the dimensionality of the state space or storing only the necessary states.
It's particularly useful when the solution only depends on a small subset of the previously computed states, allowing us to discard older states that are no longer needed.
Key Insight: Many DP problems that seem to require O(n²) or O(n³) space can actually be solved with O(n) or even O(1) space by recognizing that the current state often depends only on a few previous states.
When the current state only depends on a few previous states, making it unnecessary to store the entire state history.
When dealing with large input sizes that would cause memory issues with the standard DP approach.
When working with multi-dimensional DP problems that can be reduced to fewer dimensions by carefully analyzing the state transitions.
Use a small number of arrays (often just two) to represent the current and previous states, swapping them after each iteration.
Reduce the dimensionality of the DP table (e.g., 2D to 1D) by reusing the same array for multiple states.
Encode multiple states into a single value (e.g., using bit manipulation) to reduce the number of dimensions needed.
Keep only a window of the most recent states, discarding older states that are no longer needed for future computations.
Here's a space-optimized solution for computing the nth Fibonacci number:
1234567891011121314151617181920212223242526function fibonacci(n) { // Handle base cases if (n <= 1) return n; // Initialize variables for the previous two Fibonacci numbers let prev2 = 0; // F(0) let prev1 = 1; // F(1) let current; // Compute Fibonacci numbers iteratively for (let i = 2; i <= n; i++) { // F(i) = F(i-1) + F(i-2) current = prev1 + prev2; // Update variables for the next iteration prev2 = prev1; prev1 = current; } // Return the nth Fibonacci number return prev1;} // Example usage:console.log(fibonacci(10)); // Output: 55console.log(fibonacci(40)); // Output: 102334155
How it works: Instead of storing all Fibonacci numbers in an array, we only keep track of the two most recent numbers (prev1 and prev2). This reduces the space complexity from O(n) to O(1) while maintaining the O(n) time complexity.
Here's a space-optimized solution for the 0/1 knapsack problem:
12345678910111213141516171819202122232425function knapsack(weights, values, capacity) { const n = weights.length; // Create a 1D array instead of 2D // dp[w] represents the maximum value that can be obtained // with a knapsack capacity of w const dp = Array(capacity + 1).fill(0); // Fill the array in a bottom-up manner for (let i = 0; i < n; i++) { // Important: iterate from right to left to avoid using the same item multiple times for (let w = capacity; w >= weights[i]; w--) { dp[w] = Math.max(dp[w], dp[w - weights[i]] + values[i]); } } // Return the maximum value return dp[capacity];} // Example usage:const weights = [2, 3, 4, 5];const values = [3, 4, 5, 6];const capacity = 8;console.log(knapsack(weights, values, capacity)); // Output: 10 (items with values 4 and 6)
How it works: Instead of using a 2D array dp[i][w], we use a 1D array dp[w] and iterate from right to left when considering each item. This ensures that we don't use the same item multiple times. This reduces the space complexity from O(n*W) to O(W) while maintaining the O(n*W) time complexity.
Here's a space-optimized solution for finding the longest common subsequence:
12345678910111213141516171819202122232425262728293031323334function longestCommonSubsequence(text1, text2) { const m = text1.length; const n = text2.length; // Ensure text1 is the shorter string to minimize space if (m > n) { return longestCommonSubsequence(text2, text1); } // Create two 1D arrays instead of a 2D array let prev = Array(m + 1).fill(0); let curr = Array(m + 1).fill(0); // Fill the arrays in a bottom-up manner for (let j = 1; j <= n; j++) { for (let i = 1; i <= m; i++) { if (text1[i - 1] === text2[j - 1]) { curr[i] = prev[i - 1] + 1; } else { curr[i] = Math.max(prev[i], curr[i - 1]); } } // Swap arrays for the next iteration [prev, curr] = [curr, prev]; } // Return the length of LCS (result is in prev after the last swap) return prev[m];} // Example usage:console.log(longestCommonSubsequence("abcde", "ace")); // Output: 3 (LCS is "ace")console.log(longestCommonSubsequence("abc", "abc")); // Output: 3 (LCS is "abc")console.log(longestCommonSubsequence("abc", "def")); // Output: 0 (no common subsequence)
How it works: Instead of using a 2D array dp[i][j], we use two 1D arrays (prev and curr) and swap them after each iteration. This reduces the space complexity from O(m*n) to O(min(m,n)) while maintaining the O(m*n) time complexity.
State Compression is a technique used to optimize the space complexity of dynamic programming solutions by reducing the dimensionality of the state space or storing only the necessary states.
It's particularly useful when the solution only depends on a small subset of the previously computed states.
State Compression is useful when:
Here's a space-optimized solution for computing the nth Fibonacci number:
Here's a space-optimized solution for the 0/1 knapsack problem:
Here's a space-optimized solution for finding the longest common subsequence:
Learn how to optimize space usage by storing only the necessary states for computation.
State Compression is a technique used to optimize the space complexity of dynamic programming solutions by reducing the dimensionality of the state space or storing only the necessary states.
It's particularly useful when the solution only depends on a small subset of the previously computed states, allowing us to discard older states that are no longer needed.
Key Insight: Many DP problems that seem to require O(n²) or O(n³) space can actually be solved with O(n) or even O(1) space by recognizing that the current state often depends only on a few previous states.
When the current state only depends on a few previous states, making it unnecessary to store the entire state history.
When dealing with large input sizes that would cause memory issues with the standard DP approach.
When working with multi-dimensional DP problems that can be reduced to fewer dimensions by carefully analyzing the state transitions.
Use a small number of arrays (often just two) to represent the current and previous states, swapping them after each iteration.
Reduce the dimensionality of the DP table (e.g., 2D to 1D) by reusing the same array for multiple states.
Encode multiple states into a single value (e.g., using bit manipulation) to reduce the number of dimensions needed.
Keep only a window of the most recent states, discarding older states that are no longer needed for future computations.
Here's a space-optimized solution for computing the nth Fibonacci number:
1234567891011121314151617181920212223242526function fibonacci(n) { // Handle base cases if (n <= 1) return n; // Initialize variables for the previous two Fibonacci numbers let prev2 = 0; // F(0) let prev1 = 1; // F(1) let current; // Compute Fibonacci numbers iteratively for (let i = 2; i <= n; i++) { // F(i) = F(i-1) + F(i-2) current = prev1 + prev2; // Update variables for the next iteration prev2 = prev1; prev1 = current; } // Return the nth Fibonacci number return prev1;} // Example usage:console.log(fibonacci(10)); // Output: 55console.log(fibonacci(40)); // Output: 102334155
How it works: Instead of storing all Fibonacci numbers in an array, we only keep track of the two most recent numbers (prev1 and prev2). This reduces the space complexity from O(n) to O(1) while maintaining the O(n) time complexity.
Here's a space-optimized solution for the 0/1 knapsack problem:
12345678910111213141516171819202122232425function knapsack(weights, values, capacity) { const n = weights.length; // Create a 1D array instead of 2D // dp[w] represents the maximum value that can be obtained // with a knapsack capacity of w const dp = Array(capacity + 1).fill(0); // Fill the array in a bottom-up manner for (let i = 0; i < n; i++) { // Important: iterate from right to left to avoid using the same item multiple times for (let w = capacity; w >= weights[i]; w--) { dp[w] = Math.max(dp[w], dp[w - weights[i]] + values[i]); } } // Return the maximum value return dp[capacity];} // Example usage:const weights = [2, 3, 4, 5];const values = [3, 4, 5, 6];const capacity = 8;console.log(knapsack(weights, values, capacity)); // Output: 10 (items with values 4 and 6)
How it works: Instead of using a 2D array dp[i][w], we use a 1D array dp[w] and iterate from right to left when considering each item. This ensures that we don't use the same item multiple times. This reduces the space complexity from O(n*W) to O(W) while maintaining the O(n*W) time complexity.
Here's a space-optimized solution for finding the longest common subsequence:
12345678910111213141516171819202122232425262728293031323334function longestCommonSubsequence(text1, text2) { const m = text1.length; const n = text2.length; // Ensure text1 is the shorter string to minimize space if (m > n) { return longestCommonSubsequence(text2, text1); } // Create two 1D arrays instead of a 2D array let prev = Array(m + 1).fill(0); let curr = Array(m + 1).fill(0); // Fill the arrays in a bottom-up manner for (let j = 1; j <= n; j++) { for (let i = 1; i <= m; i++) { if (text1[i - 1] === text2[j - 1]) { curr[i] = prev[i - 1] + 1; } else { curr[i] = Math.max(prev[i], curr[i - 1]); } } // Swap arrays for the next iteration [prev, curr] = [curr, prev]; } // Return the length of LCS (result is in prev after the last swap) return prev[m];} // Example usage:console.log(longestCommonSubsequence("abcde", "ace")); // Output: 3 (LCS is "ace")console.log(longestCommonSubsequence("abc", "abc")); // Output: 3 (LCS is "abc")console.log(longestCommonSubsequence("abc", "def")); // Output: 0 (no common subsequence)
How it works: Instead of using a 2D array dp[i][j], we use two 1D arrays (prev and curr) and swap them after each iteration. This reduces the space complexity from O(m*n) to O(min(m,n)) while maintaining the O(m*n) time complexity.