题解正文
题目描述
问题分析
此题给出一个n乘n矩阵,矩阵中值可以是0/1/-1。
要求我们找出从(0,0)出发,到(n-1,n-1),然后回到(0,0)的路径,要求往程只能向右向下,而返程只能向左向上走,并且路径没有经过值为-1的位置。
然后求出符合上述要求的路径中,所经过的所有位置值加和最大的路径,将其经过的各个位置值加和,作为答案返回。
思路分析
这周本来先做了leetcode的Dungeon Game这个题,标签是hard,但是感觉并不很难,所有继续往下做直到做到这个题,我一看题目觉得这两个题目不就是同一个题目吗,只不过Dungeon Game只需要针对单程求解问题,而Cherry Pickup这道题需要先求出最大的往程节点值之和,然后将往程经过的路径上的值设为0,然后求出最大的返程节点值之和,将两个最大加起来就是答案。但是这个做法遇到了一个问题,遇到下面的例子会出现问题:
1 1 1 1 0 0 0
0 0 0 1 0 0 0
0 0 0 1 0 0 1
1 0 0 1 0 0 0
0 0 0 1 0 0 0
0 0 0 1 0 0 0
0 0 0 1 1 1 1
在这个例子中,按照我们原本的算法,会得出如下往程和返程(加粗表示):
1 1 1 1 0 0 0
0 0 0 1 0 0 0
0 0 0 1 0 0 1
1 0 0 1 0 0 0
0 0 0 1 0 0 0
0 0 0 1 0 0 0
0 0 0 1 1 1 1
或者
1 1 1 1 0 0 0
0 0 0 1 0 0 0
0 0 0 1 0 0 1
1 0 0 1 0 0 0
0 0 0 1 0 0 0
0 0 0 1 0 0 0
0 0 0 1 1 1 1
还有其它选择方法,但是往程都一样,只是返程所选择的路径会经过(2,6)和(0,3)其中一个,因此往返经过路径的节点值加起来是13+1 = 14(注意重叠的点只能算一次加法)
但是实际上最优路径如下:
1 1 1 1 0 0 0
0 0 0 1 0 0 0
0 0 0 1 0 0 1
1 0 0 1 0 0 0
0 0 0 1 0 0 0
0 0 0 1 0 0 0
0 0 0 1 1 1 1
当然还有其它走法,总之都可以经过(2,6)和(0,3)这两个点,而不是其中一个,所以往返经过路径的节点值加起来是8+7 = 14(注意重叠的点只能算一次加法)
相比最优解我们的答案不正确,这是因为分为子问题取最优然后合并子问题,并不等价于整个问题取最优。所以我们需要从整体考虑,而不能够分往返程求解然后合并。
其实稍加思考我们就知道,往返路径合起来也可以看成是,两条单程路径放在一起,所以问题变成求解两条往程路经过径节点值求和最大是多少,如果换一个角度分析问题,问题可以进一步变成:两个人同时以v=1的速度从(0,0)去往(n-1,n-1),经过t=2n-1之后,两个人将同时到达终点,求出他们路经节点值之和最大值,如果两人都经过了某个相同的节点,那么该节点值只能被加一次。
要解决上述问题,我们需要更加详细的分析两个人的行进过程:假设在出发后的t时刻,第一个人所在位置横坐标x1,那么此人坐标为(x1,t-x1),同理假设第二个人所在位置横坐标x2,那么此人坐标为(x2,t-x2),那么我们可以使用元组(t,x1,x2)来唯一标识一个进行过程状态,用maxSum(t,x1,x2)来表示当前状态下他们已经经过节点的节点值之和(同一个节点只能加一次),而maxSum数组的计算方法就是本题求解的核心。
假设现在在t时刻,我们目前已经知道了t时刻之前所有的maxSum(i,j,k)了(i<t,x1<n,x2<n),我们要求解maxSum(t,x1<n,x2<n)这个数组,我们可以遍历每个maxSum(t,x1,x2),这个数值对应的状态用(t,x1,x2)的前一个状态可以有四种可能:(t-1,x1,x2)、(t-1,x1-1,x2)、(t-1,x1,x2-1)、(t-1,x1-1,x2-1),即他们可以且仅可以从这四个状态经过一个时间单位到达(t,x1,x2),其它状态是不能经过一个时间单位就到达(t,x1,x2)状态的,如下图所示:
所以我们求解maxSum(t,x1,x2)的时候,求出maxSum(t-1,x1,x2)、maxSum(t-1,x1-1,x2)、maxSum(t-1,x1,x2-1)、maxSum(t-1,x1-1,x2-1)的最大值,再加上(x1,t-x1),(x2,t-x2)这两个节点本身的数值即可(如果两个节点重合则只需要加一次),但是如果(x1,t-x1),(x2,t-x2)中有一个节点值为-1那么这个走法不可行(因为这个节点不能走),需要设置maxSum(t,x1,x2)为-1,或者maxSum(t-1,x1,x2)、maxSum(t-1,x1-1,x2)、maxSum(t-1,x1,x2-1)、maxSum(t-1,x1-1,x2-1)的最大值为-1,那么这个走法也不可行(因为没有任何一个可行状态能够经过一个时间单位到达改状态)
最后,当我们完成对maxSum(t,x1<n,x2<n)这个数组的遍历之后,我们再继续完成对maxSum(t+1,x1<n,x2<n)、maxSum(t+1,x1<n,x2<n)等数组的求解,直到我们完成对整个maxSum(t<2n-1,x1<n,x2<n)数组的遍历,我们知道本问题答案就是经过时间2n-1之后,两个人都到达(n-1,n-1)这一点,即x1 == x2 == n-1,此时maxSum的值,然后在maxSum[2n-2][n-1][n-1]这个对应位置找到本问题的答案。
最后我们还可以对于正确解法做一下优化:本来我们需要的空间复杂度是n立方,也就是需要一个maxSum(t<2n-1,x1<n,x2<n)三维数组,但是其实我们只使用maxSum(x1<n,x2<n)也能完成任务,这需要我们在对maxSum(t,x1<n,x2<n)这个数组的遍历的时候,先算x1,x2比较大的状态,然后算x1,x2比较小的状态。因为maxSum(i,x1,x2)在求解的时候取决于maxSum(i-1,x<=x1,x’<=x2),我们使用反向求解能够保证修改maxSum(x<=x1,x’<=x2)之后,maxSum(x<=x1,x’<=x2)中的值不再被使用,从而安全完成问题求解,之所以不会在修改之后使用,是因为,需要用到maxSum(x<=x1,x’<=x2)的那些状态的x1,x2下标更大,而具有更大x1,x2下标的状态在反向求解之中已经被先行求解了。
最终通过这种做法大大优化了空间复杂度。更多细节见代码实现
算法实现
初始化数组maxCherries为全-1;
设置maxCherries[0][0]为0;
遍历t∈(0,2n-1),每一轮求解一次数组maxCherries(x1<n,x2<n),即求解经过时间t,两人所能到达的全部状态对应的节点值之和:
遍历x1∈(0,n-1),每一轮固定x1求解数组maxCherries(x1,x2<n),即第一个人经过t走到的位置,求解第二个人所能达到的全部状态对应的节点值之和:
遍历x2∈(0,n-1),每一次求解经过时间t之后,第一个人到达x1,第二个人到达x2时对应的路径上节点值之和,即maxCherries(x1,x2):
如果(x1,t-x1),(x2,t-x2)中有一个节点值为-1:
设置maxCherries(t,x1,x2)为-1;
跳出该次循环,x2++,进入下一次循环;
求解maxCherries(t-1,x1,x2)、maxCherries(t-1,x1-1,x2)、maxCherries(t-1,x1,x2-1)、maxCherries(t-1,x1-1,x2-1)的最大值,记为temp;
如果temp为-1:
设置maxCherries(t,x1,x2)为-1;
跳出该次循环,x2++,进入下一次循环;
否则:
如果两个节点(x1,t-x1),(x2,t-x2)重合则:
设置maxCherries(t,x1,x2)为temp+矩阵在(x1,t-x1)的数值+矩阵在(x2,t-x2)的数值;
否则:
设置maxCherries(t,x1,x2)为temp+矩阵在(x1,t-x1)的数值;
返回maxCherries[n-1][n-1]和0中的最大值作为本题答案;
复杂度分析
时间复杂度:主要部分就是对于数组maxSum(x1<n,x2<n)的动态规划求解,这个求解需要2n-1轮,每一轮需要nn(常数)的时间复杂度,所以该算法总共的时间复杂度是
O
(
n
3
)
O(n^3)
O(n3),其它过程没有超过该过程复杂度的,所以最终复杂度就是n立方。
空间复杂度:通过优化你,我们最终仅仅使用了数组maxSum(x1<n,x2<n)就完成了问题求解,所以空间复杂度是
O
(
n
2
)
O(n^2)
O(n2),其他都是循环遍历用到的临时变量,三层循环需要平方个临时变量,因此总共的空间复杂度还是
O
(
n
2
)
O(n^2)
O(n2)
代码实现&结果分析:
代码实现:
class Solution {
public:
int cherryPickup(vector<vector<int>>& nums) {
int n = nums.size();
vector<vector<int>> maxCherries(n, vector<int>(n, -1));
maxCherries[0][0] = nums[0][0];
for (int t = 1; t <= 2*n-1; t++) {
for (int x1 = min(t, n-1); x1 >= max(0, t-(n-1)); --x1) {
for (int x2 = min(t, n-1); x2 >= max(0, t-(n-1)); --x2) {
if (nums[x1][t-x1] == -1 || nums[x2][t-x2] == -1) {
maxCherries[x1][x2] = -1;
continue;
}
if (x1 > 0) {
maxCherries[x1][x2] = max(maxCherries[x1][x2], maxCherries[x1-1][x2]);
}
if (x2 > 0) {
maxCherries[x1][x2] = max(maxCherries[x1][x2], maxCherries[x1][x2-1]);
}
if (x1 > 0 && x2 > 0) {
maxCherries[x1][x2] = max(maxCherries[x1][x2], maxCherries[x1-1][x2-1]);
}
if (maxCherries[x1][x2] == -1) {
continue;
}
maxCherries[x1][x2] = maxCherries[x1][x2] + nums[x1][t-x1] + (x1 == x2 ? 0 : nums[x2][t-x2]);
}
}
}
return max(0, maxCherries[n-1][n-1]);
}
};
提交结果:从结果看来,经过优化后的算法无论从时间还是空间复杂度看都是相当好了。