Many DP problems can be also solved by backtracking algorithm.
LC 416
Given an integer array nums, return true if you can partition the array into two subsets such that the sum of the elements in both subsets is equal or false otherwise.
privatebooleanbacktrack(int[] nums, int index, int left, int right) { if (index == nums.length && left == right) { returntrue; } elseif (index == nums.length) { returnfalse; } booleanincludeLeft= backtrack(nums, index+1, left+nums[index], right); booleanincludeRight= backtrack(nums, index+1, left, right+nums[index]); return includeLeft || includeRight; } }
Idea: Our goal is to partition the array into two subsets, naming them “left” and “right” respectively. For each number in the array, it is either in the “left” subset or “right” subset. We explore all possibilities using recursion.
Complexity: O(2^n). Thinking about a tree, each node is a backtrack function. The tree is of depth n, each node takes O(1) time.
Solution using dynamic programming:
Transform the solution to knapsack problem: achieve sum/2 using elements in an array.
classSolution { publicbooleancanPartition(int[] nums) { intsum=0; for (int num : nums) { sum += num; } if (sum % 2 == 1) returnfalse; inttarget= sum / 2; boolean[] dp = newboolean[target+1]; dp[0] = true; if (nums[0] < target) { dp[nums[0]] = true; }
for (inti=1; i < nums.length; i++) { for (intj= target; j >= nums[i]; j--) { dp[j] = dp[j] || dp[j-nums[i]]; } } return dp[target]; } }
dp[i][j] is boolean, representing whether summation j can be achieved using indexes 0, 1, …, i.
If nums[i] is used to achieve the summation j, then dp[i-1][j-nums[i]] must be true. If nums[i] is not used to achieve the summation, then dp[i-1][j] must be true.
Complexity: O(nW), where n is array size, W is array summation.
Optimize Space Complexity
Knapsack Problem
Given bag space N, what is the most valuable way to put items in the bag? One Item can be used only once.
// 2D array // DP[i][j]: most valuable combination of items for space j using items 0-i. publicstaticintsolve(int M, int N, int[] spaces, int[] values) { int[][] dp = newint[M+1][N+1];
Updation: dp[i][j] = dp[i][j-coins[i]] + dp[i-1][j], which depends on both previous row and current row but previous column.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
// solution using 1D array classSolution { publicintchange(int amount, int[] coins) { int[] dp = newint[amount+1]; for (inti=0; i < amount+1; i++) { if (i % coins[0] == 0) dp[i] = 1; }
for (inti=1; i < coins.length; i++) { for (intj= coins[i]; j <= amount; j++) { dp[j] += dp[j-coins[i]]; } } return dp[amount]; } }
At iteration i, dp[j] is the number of combinations that make up amount j, using coins with indexes 0-i.
Because solution using 2D array uses dp[i][j] = dp[i][j-coins[i]] + dp[i-1][j], so in 1D array, we update from left to right so that dp[j] uses the dp[j-coins[i]] from the same row.