前一阵子,看了一篇对Best time buy and sell stock的动态规划总结。作者把动态规划的本质用一句话描述:定义状态,状态数组的维数由决定状态的因素构成(多少个因素就是多少维)。动态规划是bottom - top的方法,而备忘录法 (memorization)则是top - bottom的方法。动态规划常见的降维方法是:滚动数组。
备忘录法例子:
361. Bomb Enemy
备忘录法的“砝码移动”(通过遍历中间的所有可能值再利用备忘录数据求最优解)
689. Maximum Sum of 3 Non-Overlapping Subarrays
312. Burst Balloons
321. Create Maximum Number
动态规划例子:
276. Paint Fence
198. House Robber
213. House Robber II
361. Bomb Enemy
Given a 2D grid, each cell is either a wall 'W', an enemy 'E' or empty '0' (the number zero), return the maximum enemies you can kill using one bomb.
The bomb kills all the enemies in the same row and column from the planted point until it hits the wall since the wall is too strong to be destroyed.Note that you can only put the bomb at an empty cell.
Example:
For the given grid
0 E 0 0
E 0 W E
0 E 0 0
return 3. (Placing a bomb at (1,1) kills 3 enemies)
思路:因为嵌套的for loop外循环是行,内循环是列。所以,在内循环的时候,行的备忘用一个变量即可。列的备忘还是要用一个数组。更新备忘只在前一个是‘W’或者一开始进行。备忘的结果是到下一个‘W’结束为止。每当遇到‘0’的时候,统计并更新结果。
class Solution {
public int maxKilledEnemies(char[][] grid) {
if (grid == null || grid.length == 0 || grid[0] == null || grid[0].length == 0) {
return 0;
}
int m = grid.length; int n = grid[0].length;
int res = 0;
int rowHits = 0;
int[] colHits = new int[n];
for (int i = 0; i < m; ++i) {
for (int j = 0; j < n; ++j) {
if (j == 0 || grid[i][j - 1] == 'W') {
rowHits = 0;
for (int k = j; k < n && grid[i][k] != 'W'; ++k) {
rowHits += (grid[i][k] == 'E' ? 1 : 0);
}
}
if (i == 0 || grid[i - 1][j] == 'W') {
colHits[j] = 0;
for (int k = i; k < m && grid[k][j] != 'W'; ++k) {
colHits[j] += (grid[k][j] == 'E' ? 1 : 0);
}
}
if (grid[i][j] == '0') {
res = Math.max(res, rowHits + colHits[j]);
}
}
}
return res;
}
}
689. Maximum Sum of 3 Non-Overlapping Subarrays
In a given array nums of positive integers, find three non-overlapping subarrays with maximum sum.
Each subarray will be of size k, and we want to maximize the sum of all 3*k entries.
Return the result as a list of indices representing the starting position of each interval (0-indexed). If there are multiple answers, return the lexicographically smallest one.
Example:
Input: [1,2,1,2,6,7,5,1], 2
Output: [0, 3, 5]
Explanation: Subarrays [1, 2], [2, 6], [7, 5] correspond to the starting indices [0, 3, 5].
We could have also taken [2, 1], but an answer of [1, 3, 5] would be lexicographically larger.
Note:
nums.length will be between 1 and 20000.
nums[i] will be between 1 and 65535.
k will be between 1 and floor(nums.length / 3).
思路:假设中间的区间的起点是i,那么中间的区间是[i, i + k - 1],左边的区间起点是[0, i - k],右边的区间是[i + k, n - k]. 所以,i的取值范围是:k <= i <= n - 2*k. (使得两边的区间起点都只能取一个值)。接下来便是构建备忘录看看两边以某一个index作为取值范围的边界的时候的最大和是什么。然后移动中间的砝码 ‘i’来获得最优解。需要注意的是,这道题的sum数组用的是隔板法(prefix - sum)。前开后闭。坐标代表的是第几个元素的前缀和。
class Solution {
public int[] maxSumOfThreeSubarrays(int[] nums, int k) {
int n = nums.length, maxsum = 0;
int[] sum = new int[n+1], posLeft = new int[n], posRight = new int[n], ans = new int[3];
for (int i = 0; i < n; i++) sum[i+1] = sum[i]+nums[i];
// DP for starting index of the left max sum interval
for (int i = k, tot = sum[k]-sum[0]; i < n; i++) {
if (sum[i+1]-sum[i+1-k] > tot) {
posLeft[i] = i+1-k;
tot = sum[i+1]-sum[i+1-k];
}
else
posLeft[i] = posLeft[i-1];
}
// DP for starting index of the right max sum interval
// caution: the condition is ">= tot" for right interval, and "> tot" for left interval
posRight[n-k] = n-k;
for (int i = n-k-1, tot = sum[n]-sum[n-k]; i >= 0; i--) {
if (sum[i+k]-sum[i] >= tot) {
posRight[i] = i;
tot = sum[i+k]-sum[i];
}
else
posRight[i] = posRight[i+1];
}
// test all possible middle interval
for (int i = k; i <= n-2*k; i++) {
int l = posLeft[i-1], r = posRight[i+k];
int tot = (sum[i+k]-sum[i]) + (sum[l+k]-sum[l]) + (sum[r+k]-sum[r]);
if (tot > maxsum) {
maxsum = tot;
ans[0] = l; ans[1] = i; ans[2] = r;
}
}
return ans;
}
}
312. Burst Balloons
Given n balloons, indexed from 0 to n-1. Each balloon is painted with a number on it represented by array nums. You are asked to burst all the balloons. If the you burst balloon i you will get nums[left] * nums[i] * nums[right] coins. Here left and right are adjacent indices of i. After the burst, the left and right then becomes adjacent.
Find the maximum coins you can collect by bursting the balloons wisely.
Note:
(1) You may imagine nums[-1] = nums[n] = 1. They are not real therefore you can not burst them.
(2) 0 ≤ n ≤ 500, 0 ≤ nums[i] ≤ 100
Example:
Given [3, 1, 5, 8]
Return 167
nums = [3,1,5,8] --> [3,5,8] --> [3,8] --> [8] --> []
coins = 3*1*5 + 3*5*8 + 1*3*8 + 1*8*1 = 167
状态转移方程:C[i][j] = max(C[i][j], C[i][k - 1] + nums[i - 1]*nums[k]*nums[j + 1] + C[k + 1][j])
class Solution {
public int maxCoins(int[] nums) {
// in order to unify the status transformation equation.
int n = nums.length;
int[] newNums = new int[n + 2];
for (int i = 1; i <= n; ++i) {
newNums[i] = nums[i - 1];
}
newNums[0] = 1;
newNums[n + 1] = 1;
int[][] C = new int[n + 2][n + 2];
// 以打掉的气球距离为变量,从最小的开始
for (int l = 1; l <= n; ++l) {
for (int i = 1; i <= n - l + 1; ++i) {
int j = i + l - 1;
for (int k = i; k <= j; ++k) {
C[i][j] = Math.max(C[i][j], C[i][k - 1] + newNums[i - 1]*newNums[k]*newNums[j + 1] + C[k + 1][j]);
}
}
}
return C[1][n];
}
}
321 Create Maximum Number
Given two arrays of length m and n with digits 0-9 representing two numbers. Create the maximum number of length k <= m + n from digits of the two. The relative order of the digits from the same array must be preserved. Return an array of the k digits. You should try to optimize your time and space complexity.
Example 1:nums1 = [3, 4, 6, 5]
nums2 = [9, 1, 2, 5, 8, 3]
k = 5
return [9, 8, 6, 5, 3]
Example 2:
nums1 = [6, 7]
nums2 = [6, 0, 4]
k = 5
return [6, 7, 6, 0, 4]
Example 3:
nums1 = [3, 9]
nums2 = [8, 9]
k = 3
return [9, 8, 9]
分拆成两个问题:
1)单个矩阵当中取出最大数。例如 [9, 1, 2, 5, 8, 3]取三个数且要保持原有的相对顺序,应该是9、8、5. 用贪心算法:遇到一个数,如果比现有的序列的某些数要大,则都丢掉这些更小的数。但是,需要留意的一点是,不能丢掉太多,导致不够数来凑。
2)第二个问题,从n1长度的数组和n2长度的数组当中合并一个新的n1 + n2长度的数组。道理很简单,每次抽两个位最大的。换而言之,抽数字比较大的。
接下来就是状态转换了:抽k位的数,那么就是遍历i + (k - i) = k,从第一个抽i个,第二个抽k - i个,接着试着合并和当前最大的比较。遍历i的所有可能数目即可。
class Solution {
public int[] maxNumber(int[] nums1, int[] nums2, int k) {
int[] best = new int[0];
for (int i = Math.max(0, k - nums2.length);
i <= Math.min(k, nums1.length); ++i)
best = max(best, 0,
maxNumber(maxNumber(nums1, i),
maxNumber(nums2, k - i)), 0);
return best;
}
private int[] maxNumber(int[] nums, int k) {
int[] ans = new int[k];
int j = 0;
for (int i = 0; i < nums.length; ++i) {
while (j > 0 && nums[i] > ans[j - 1]
&& nums.length - i > k - j) --j;
if (j < k)
ans[j++] = nums[i];
}
return ans;
}
private int[] maxNumber(int[] nums1, int[] nums2) {
int[] ans = new int[nums1.length + nums2.length];
int s1 = 0;
int s2 = 0;
int index = 0;
while (s1 != nums1.length || s2 != nums2.length)
ans[index++] = max(nums1, s1, nums2, s2) == nums1 ?
nums1[s1++] : nums2[s2++];
return ans;
}
private int[] max(int[] nums1, int s1, int[] nums2, int s2) {
for (int i = s1; i < nums1.length; ++i) {
int j = s2 + i - s1;
if (j >= nums2.length) return nums1;
if (nums1[i] < nums2[j]) return nums2;
if (nums1[i] > nums2[j]) return nums1;
}
return nums2;
}
}
276. Paint Fence
There is a fence with n posts, each post can be painted with one of the k colors.
You have to paint all the posts such that no more than two adjacent fence posts have the same color.
Return the total number of ways you can paint the fence.
Note:
n and k are non-negative integers.
思路:这道题的解决方案是bottom up. 选择最后两个posts,有两种情况,它们同色或者不同色。同色的组合数:k;不同色则是k * (k - 1). 基于这样的bottom去继续递推。如果下一张是不同色的话,k - 1个选择(只要不和其前一张post同色即可)。如果下一张同色的话,则只有一个选择,就是和前一张post一样的颜色。所以递推公式如下:(sameColorBefore + diffColorBefore) * (k - 1)代表下一张不同色,diffColorBefore * 1的话代表选择同色,因为如果要同色的话只能前面只有diffColorBefore。最后把再两个数加起来即可。
public int numWays(int n, int k) {
if(n == 0) return 0;
else if(n == 1) return k;
int diffColorCounts = k*(k-1);
int sameColorCounts = k;
for(int i=2; i<n; i++) {
int temp = diffColorCounts;
diffColorCounts = (diffColorCounts + sameColorCounts) * (k-1);
sameColorCounts = temp;
}
return diffColorCounts + sameColorCounts;
}
198. House Robber
You are a professional robber planning to rob houses along a street. Each house has a certain amount of money stashed, the only constraint stopping you from robbing each of them is that adjacent houses have security system connected and it will automatically contact the police if two adjacent houses were broken into on the same night.Given a list of non-negative integers representing the amount of money of each house, determine the maximum amount of money you can rob tonight without alerting the police.
思路:相邻的不能抢,则是否抢当前取决于prevOfLast + nums[i] 和 last比较,看哪个比较大来决定。
class Solution {
public int rob(int[] nums) {
return robHelper(nums, 0, nums.length - 1);
}
private int robHelper(int[] nums, int begin, int end) {
int prevOfLast = 0; int last = 0;
for (int i = begin; i <= end; ++i) {
int cur = Math.max(prevOfLast + nums[i], last);
prevOfLast = last;
last = cur;
}
return last;
}
}
213. House Robber II
Note: This is an extension of House Robber.After robbing those houses on that street, the thief has found himself a new place for his thievery so that he will not get too much attention. This time, all houses at this place are arranged in a circle. That means the first house is the neighbor of the last one. Meanwhile, the security system for these houses remain the same as for those in the previous street.
Given a list of non-negative integers representing the amount of money of each house, determine the maximum amount of money you can rob tonight without alerting the police.
思路:This problem is a little tricky at first glance. However, if you have finished the House Robber problem, this problem can simply be decomposed into two House Robber problems.
Suppose there are n houses, since house 0 and n - 1 are now neighbors, we cannot rob them together and thus the solution is now the maximum of
Rob houses 0 to n - 2;
Rob houses 1 to n - 1.
The code is as follows. Some edge cases (n < 2) are handled explicitly.
class Solution {
public int rob(int[] nums) {
if (nums == null || nums.length == 0) {
return 0;
}
if (nums.length == 1) {
return nums[0];
}
return Math.max(robHelper(nums, 0, nums.length - 2), robHelper(nums, 1, nums.length - 1));
}
private int robHelper(int[] nums, int begin, int end) {
int prevOfLast = 0; int last = 0;
for (int i = begin; i <= end; ++i) {
int cur = Math.max(prevOfLast + nums[i], last);
prevOfLast = last;
last = cur;
}
return last;
}
}