如何将这种递归解决方案转换为分而治之?

How to convert this recurrence solution to divide and conquer?

本文关键字:转换 分而治之 解决方案 递归      更新时间:2023-10-16

如何解决这个问题:

给定 n 个气球,索引从 0 到 n-1。每个气球都涂有 它上面的一个数字,由数组编号表示。你被要求全部爆裂 气球。如果你爆破气球 i 你会得到数字[左] * 数字[右]硬币。这里的左边和右边是 i 的相邻索引。 爆发后,左右变得相邻。如果你爆裂 角气球然后你会得到相邻的点 那些气球。如果你爆破了最后一个气球,那么你将得到 上面写的点数。 找到你能找到的最大硬币 通过明智地爆破气球来收集。

示例测试用例:

{1,2,3,4}
20
{5,7,8}
56

我已经使用递归尝试了这个解决方案,它似乎给出了正确的答案:

public static int maxCoints(List<Integer> list) {
int max = 0;
if (list.size() == 1) {
return list.get(0);
}
if(list.size() == 2) {
return Math.max(list.get(0),list.get(1))*2;
}
for (int i = 0; i < list.size(); i++) {
int left = i == 0 ? 1 : list.get(i-1);
int right = i == list.size()-1 ? 1 : list.get(i+1);
int n = left * right;
List<Integer> tmp = new ArrayList<>(list);
tmp.remove(i);
max = Math.max(max, n + maxCoints(tmp));
}
return max;
}

但是我已经尝试了这个分而治之的解决方案,但它似乎对第一个测试用例给出了错误的答案,它给出的答案是 17 而不是 20

int find(vector<int>& v, int L, int R) {
int ans = 0;
// if(L==R)    return v[L];
for (int i = L; i <= R; i++) {
int l = find(v, L, i-1);
int r = find(v, i+1, R);
int val = v[L-1]*v[R+1] + l + r;
ans = max(ans, val);
}
return ans;
}
int32_t main() {
fast_io;
ll tt;  cin >> tt;
while(tt--) {
ll n;   cin >> n;
vector<int> v(n+2,1);
for(int i=1;i<=n;i++) {
cin >> v[i];
}
cout << find(v,1,n) << "n";
}
return 0;
}

请帮我找出错误。

这似乎是对leetcode上爆裂气球问题的一个小修改,我写了编辑解决方案。

递归会起作用,但对于我们的意图和目的来说,它太慢了。递归地删除每个气球和缓存会给我们2^N状态,这是我们气球的功率集。我们想在多项式时间内解决这个问题。

分而治之绝对是正确的想法。

  • 在气球i爆裂后,我们可以将问题分为i左侧的气球(nums[0:i](和i右侧的气球(nums[i+1:](。

  • 为了找到最佳解决方案,我们在爆破每个气球后检查每个最佳解决方案。

  • 由于我们将为每个范围找到以 nums 为单位的最佳解决方案,并且我们在每个范围内爆破每个气球以找到最佳解决方案,因此我们有一个O(N^2)范围乘以每个范围的时间O(N)这是一个O(N^3)的解决方案

  • 但是,如果我们试图按照我们首先爆破气球的顺序来划分我们的问题,我们就会遇到一个问题。当气球破裂时,其他气球的邻接关系会发生变化。我们无法跟踪区间的端点与哪些气球相邻。这是您的解决方案存在问题的地方。

详细阐述最后一点:

当您执行以下操作时:

int l = find(v, L, i-1);

您实际上可能无法获得最佳解决方案。考虑到气球i - 1现在与气球i + 1相邻,在您爆破气球i后。如果随后爆破气球i - 1,气球i - 2现在与气球i + 1相邻。如果您尝试在每次气球爆发时除法,您的find必须以某种方式仍然考虑范围之外的气球[L, R]

为了解决这个问题,我们考虑将气球添加到初始空区间中,而不是爆破气球和除法。

让我们dp(i, j)表示[i, j]的最高分。对于[i + 1, j - 1]k的每个气球,我们将其添加到区间中并计算分数。添加气球后,我们总是可以将问题分为[i, k][k, j],因为左右边界是已知的。这消除了邻接问题。

一个更棘手的部分是实现"如果你爆破了最后一个气球,那么你将得到写在上面的分数。我们手动迭代我们爆破的最后一个气球,并像以前一样应用分而治之。

查看代码以获得更好的想法:


class Solution {
public int maxCoins(int[] nums) {
int n = nums.length + 2;
int[] new_nums = new int[n];
for(int i = 0; i < nums.length; i++){
new_nums[i+1] = nums[i];
}
new_nums[0] = new_nums[n - 1] = 1;
// cache the results of dp
int[][] memo = new int[n][n];
// find the maximum number of coins obtained from adding all balloons from (0, len(nums) - 1)
int ans = 0;
// manually burst the last balloon because it has special rules
for(int i = 1; i < n; ++i){
ans = Math.max(ans, new_nums[i] + dp(memo, new_nums, i, n - 1) + dp(memo, new_nums, 0, i));
}
return ans;
}
public int dp(int[][] memo, int[] nums, int left, int right) {
// no more balloons can be added
if (left + 1 == right) return 0;
// we've already seen this, return from cache
if (memo[left][right] > 0) return memo[left][right];
// add each balloon on the interval and return the maximum score
int ans = 0;
for (int i = left + 1; i < right; ++i)
ans = Math.max(ans, nums[left] * nums[right]
+ dp(memo, nums, left, i) + dp(memo, nums, i, right));
// add to the cache
memo[left][right] = ans;
return ans;
}
}

输入:

[1, 2, 3, 4]
[5, 7, 8]

输出:

20
56