动态编程:将数组减少为1的步数

时间:2019-06-22 11:06:09

标签: arrays algorithm dynamic-programming

我试图用动态编程表填充方法来解决一个优化问题,但是我无法在最佳子结构中解决这个问题。

这是问题所在

给出一个从种子元素开始的排序数组,您可以将数组缩小为只有一个元素,可以移动多少步。

  1. 您可以删除除种子以外的数组元素,这被视为1步,这样可以减少数组。
  2. 您可以向种子添加小于种子的数组元素。这被认为是一招。
  3. 您可以使用以前的方法添加到种子中,次数不限。
  4. 种子可以“消费”少于它的任何元素。这样可以减少数组。

例如,这是排序后的数组

3, 20, 50, 100, 400

第一个元素是种子。

第一个元素还不能使用下一个更大的元素,因此我们需要添加它。根据约束条件,我们可以在第一步中添加少于种子的任何东西。因此,假设我们添加2。

moves = 1
seed = seed + 2
5, 20, 50, 100, 400

静止种子不能消耗下一个元素,所以我们加4

moves = 2
seed = seed + 4
9, 20, 50, 100, 400

moves = 3
seed = seed + 8
17, 20, 50, 100, 400

moves = 4
seed = seed + 16
33, 20, 50, 100, 400

现在种子可以“消耗”下一个元素 这样减少数组就变成

53 (33 + 20) , 50, 100 , 400

53可以消耗50并变为103

103, 100, 400

103可以容纳100并成为203

203, 400

203是种子,不能消耗400 所以

moves = 5
seed = seed + 202
405, 400

现在它可以消耗并变成805

我们也可以简单地删除400,这将算作1步。

总移动次数= 5

但是还有另一种解决方案,即从阵列中删除所有元素(20、50、100、400),这将花费4步。

因此,在这种情况下,最小移动数为4。

尝试从动态编程的角度来思考它,我可以看到在每一步中我们都有2个选择,无论是消耗元素还是删除元素。看来,要考虑的路径总数是所有2 ^ n条路径。

但是我无法将其分解为重叠的子问题或最佳子结构,甚至无法定义递归关系。

动态编程在这里正确吗?

一些观察:

  1. 当我们必须向种子中添加一些东西时,最好的方法似乎是添加可能的最高值,即种子-1
  2. 一旦我们决定删除一个元素,这意味着后面的元素也将被删除。为什么,因为如果我们决定对当前元素多次添加种子是徒劳的,那么对于下一个更大的元素也将成立。

3 个答案:

答案 0 :(得分:1)

您可以使用(max {given array} +1)* array_size时间和内存复杂度来解决此问题。

int dp[array_size][max{given array}+1]={INF} // initially all index holds infinity
int seed=array[seed_index];
int Max=max{given array}+1;
dp[0][seed]=0; 
for(int i=1;i<=n;i++){  // Consuming or removing ith element
   for(int j=1;j<=Max;j++){ // if the current seed value is j
       //Consider the removing case
       dp[i][j]=min(dp[i][j],dp[i-1][j]+1);
       // Increasing seed value
        dp[i-1][min(Max,j+(j-1))]=min(dp[i-1][min(Max,j+(j-1))],dp[i-1][j]+1);
       // Consider the consuming case
       if(j>array[i]){
          dp[i][min(Max,j+array[i])]=min(dp[i-1][j],dp[i][min(Max,j+array[i])]);  // If j is the current seed value and you want to consume ith element then  after consuming the ith element the seed will be j+array[i] but if it is greater than Max we can take upto max . Because for consuming the maximum value we need only the Max value. 
       }
   }
}

// Now check the smallest possible value for the latest number.
result = INF;
for(int i=0;i<=Max;i++)result=min(result,dp[n][i]);

在这里dp [i] [j]表示将数组的第i个值减少至第i个值的结果,在将第i个值减少至第i个值后,种子将为j。 dp [i] [j]将保留将种子数为j的第i个值减少到1个元素的移动次数。

在消耗情况下:
 如果j是当前种子值,并且您要使用第ith个元素,那么在使用第i个元素之后,种子将为j + array [i],但是如果它大于Max,则可以取最大Max。因为要消耗最大值,我们只需要max {given array}的maximum + 1。

递归方法:

int dp[array_size][max{given array}+1]={-1} // initially all index holds -1
int Max=max{givenarray}+1;

int DP(int seed,int index){
   if(index==n+1)return 0;
   if(dp[seed][index]!=-1)return dp[seed][index];// previously calculated
   int res=inf;
   // By removing
    res=DP(seed,i+1)+1;
   //Increasing seed
   if(seed<Max)
      res=min(res,1+DP(min(Max,seed+seed-1),index));
   // By consuming
   if(seed>array[i])
      res=min(res,DP(seed+array[i],i+1));
   dp[seed][index]=res;
  return res;
}

答案 1 :(得分:1)

我不认为这是DP问题。

您可以删除最小元素a或将其消耗掉(以及其他所有内容;您的第二次观察是正确的)。

移除将进行n个动作,消耗将进行log (a/s)个动作(此动作有效地使种子加倍)。做更少的事情;如果您选择增加种子并消耗,请对下一个元素应用相同的逻辑。

答案 2 :(得分:1)

观察到目标消费之间的去除没有意义,因为给定了种子s和下一个元素; abc;为了能够接下来消费bc,我们必须先增加sa之后再增加b。但是,如果我们利用“增加”动作将s增加了那么远,那么它只能为我们节省(并且永远不会增加动作)消耗ab来消耗{ {1}}。 (我认为您得出了类似的结论。)

因此,我们从左到右遍历的决定始终是是否停止增加种子并从此点到结束删除所有内容。正如已经提到的,我们可以通过观察给定的种子c和下一个元素O(1)来计算s中任何需要增加的移动量:

a

总遍历为O(n)。