分治问题
分治问题由“分”(divide)和“治”(conquer)两部分组成,通过把原问题分为子问题,再将子问题进行处理合并,从而实现对原问题的求解。
另外,自上而下的分治可以和memoization 结合,避免重复遍历相同的子问题。如果方便推导,也可以换用自下而上的动态规划方法求解。
241. DifferentWays to Add Parentheses (Medium)
问题说明
给你一个由数字和运算符组成的字符串 expression ,按不同优先级组合数字和运算符,计算并返回所有可能组合的结果。你可以按任意顺序 返回答案。
输入输出样例
示例 1:
输入:expression = “2-1-1”
输出:[0,2]
解释:
((2-1)-1) = 0
(2-(1-1)) = 2
示例 2:
输入:
expression = "2*3-4*5"
输出:[-34,-14,-10,-10,10]
解释:
(2*(3-(4*5))) = -34
((2*3)-(4*5)) = -14
((2*(3-4))*5) = -10
(2*((3-4)*5)) = -10
(((2*3)-4)*5) = 10
思路
对于一个表达式,可以将其按照符号分成两个部分。对两个部分分别求解值,之后再将两部分求解的值进行加减乘的操作。注意边界条件:如果一个表达式中没有符号,则直接将其加到结果中。
代码
class Solution {
public List<Integer> diffWaysToCompute(String expression) {
LinkedList<Integer> result = new LinkedList<>();
for(int i=0;i<expression.length();i++){
char c = expression.charAt(i);
if(c=='+'||c=='-'||c=='*'){
List<Integer> left_result = diffWaysToCompute(expression.substring(0,i)); //[0,i)
List<Integer> right_result = diffWaysToCompute(expression.substring(i+1));//[i+1,length)
for(int left:left_result){
for(int right:right_result){
if(c == '+')result.add(left+right);
if(c == '*')result.add(left*right);
if(c == '-')result.add(left-right);
}
}
}
}
//如果expression中只有数字
if(result.isEmpty())result.add(Integer.valueOf(expression));
return result;
}
}
932. Beautiful Array (Medium)
问题描述
对于某些固定的 N,如果数组 A 是整数 1, 2, …, N 组成的排列,使得:
对于每个 i < j,都不存在 k 满足 i < k < j 使得 A[k] * 2 = A[i] + A[j]。
那么数组 A 是漂亮数组。
给定 N,返回任意漂亮数组 A(保证存在一个)。
输入输出样例
示例 1:
输入:4
输出:[2,1,4,3]
示例 2:
输入:5
输出:[3,1,2,5,4]
思路
根据题意只要不满足A[k] * 2 = A[i] + A[j]
即为漂亮数组,也就是说对于【1-n】可能存在很多种。这里我们只要找到每个n的其中一种即可。要不满足上述等式,只需要A[i]和A[j]一个为偶数一个为奇数即可。
对于n个数,从1开始,奇数有(n+1)/2个,偶数有n/2个
前(n+1)/2
个数组成的漂亮数组为left
前n/2个
数组成的漂亮数组为right
left*2-1
依旧是漂亮数组(包括1 3 5 7…)奇漂亮数组
right*2
也依旧是漂亮数组(包括2 4 6 8…)偶漂亮数组
将left*2-1
和right*2
左右拼在一起(奇漂亮数组与偶漂亮数组合并是漂亮数组),正好包含了n个数,且是漂亮数组。
代码
class Solution {
public int[] beautifulArray(int n) {
if(n==1) return new int[]{1};
int[] left = beautifulArray((n+1)/2);
int[] right = beautifulArray(n/2);
int[] result = new int[n];
int k = 0;
for(int c:left){
result[k++]=c*2-1;
}
for(int c:right){
result[k++]=c*2;
}
return result;
}
}
312. Burst Balloons (Hard)
问题描述
有 n 个气球,编号为0 到 n - 1,每个气球上都标有一个数字,这些数字存在数组 nums 中。
现在要求你戳破所有的气球。戳破第 i 个气球,你可以获得 nums[i - 1] * nums[i] * nums[i + 1] 枚硬币。 这里的 i - 1 和 i + 1 代表和 i 相邻的两个气球的序号。如果 i - 1或 i + 1 超出了数组的边界,那么就当它是一个数字为 1 的气球。
求所能获得硬币的最大数量。
输入输出样例
示例 1:
输入:nums = [3,1,5,8]
输出: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
示例 2:
输入:nums = [1,5]
输出:10
思路
借鉴LeetCode@狗大王
这个区间的气球长这样:
假设这个区间是个开区间,最左边索引 i,最右边索引 j,这里说 “开区间” 的意思是只能戳爆 i 和 j 之间的气球。
DP思路是这样的,就先别管前面是怎么戳的,只要管这个区间最后一个被戳破的是哪个气球,这最后一个被戳爆的气球就是 k。假设最后一个被戳爆的气球是粉色的,k 就是粉色气球的索引:
由于是最后一个被戳破,开区间首尾只剩 i 和 j 了。所以DP的状态转移方程是只和 i 和 j 位置的数字有关。
假设 dp[i][j]
表示开区间 (i,j)
内你能拿到的最多金币,在 (i,j)
开区间得到的金币可以由dp[i][k]
和dp[k][j]
进行转移,此时戳破k得到的金币数量为:
dp[i][k]+val[i] * val[k] * val[j]+dp[k][j]
在 (i,j)
开区间可以选的 k 是有多个的,从可选的k中选择最大的值来更新dp[i][j]
。
代码
class Solution {
public int maxCoins(int[] nums) {
int n = nums.length;
// 创建一个辅助数组,并在首尾各添加1,方便处理边界情况
int[] temp = new int[n+2];
temp[0] = 1;
temp[n+1] = 1;
for(int i=0; i<n; i++){
temp[i+1] = nums[i];
}
int m = n+2; //表示扩充后数组长度
int[][] dp = new int[m][m];
// len表示开区间长度
for(int len=2; len<m; len++){ //遍历每一个区间长度
// i表示开区间左端点
for(int i=0; i<m-len; i++){ //区间向右滑动
range_best(i,i+len,temp,dp); //在当前区间中找到获得最多金币
}
}
return dp[0][m-1];
}
//在(i,j)开区间中获得的最多的金币
private void range_best(int i,int j,int[]nums,int[][]dp){
int max = 0;
for(int k=i+1;k<j;k++){ //k是区间(i,j)开区间内
int left = dp[i][k];
int right = dp[k][j];
max = Math.max(max,left+right+nums[i]*nums[k]*nums[j]);
}
dp[i][j]= max;
}
}