我在一次采访中问了以下问题:
系统会为您提供大小为n的数组
coin
,其中coin[i]
表示位置i
处的硬币数量。 “移动”包括将一定数量的硬币从位置i
移动到位置i+1
或i-1
。重新分配硬币以使每个仓位上恰好有一个硬币的最小移动次数是多少?您可以保证硬币的数量与分配硬币的插槽数量相同。
例如,给定输入
[3, 0, 0, 0, 3, 0, 1]
输出为
[1, 1, 1, 1, 1, 1, 1]
需要四步动作:
解决此问题的有效方法是什么?
答案 0 :(得分:5)
您只需扫描内部边界并计算需要转移的边界。此计数是最小移动次数。
找到最低要求的Python代码:
def nb_moves(game):
N=len(game)-1 # number of inside borders.
sum = -1
for i in range(N):
sum += game[i]
if i == sum : N -= 1 # no transfer needed between cells i and i+1 .
return N
的确,让N
为该计数。 N
显然是移动次数的最小值。而且,可以达到以下最低要求:
单元可以分为零和块,而N
总共位于边界内。
没有单元格必须由双方填充,因为最后一个数字 该单元格中的硬币数量将>1。
如果存在必须由双方清空的单元格,则首先执行此操作: 分两步处理两个边界,将块切割成两个平衡块,并将像元设置为1。
其余的块是单调的,可以从左到右或从右到左求解。
通过这种方式,每个内部边界仅被交叉一次。
在@templatetypedef的帖子中非常详细地说明了一种实现。
一些尝试:
In [275]: solve([3, 0, 0, 0, 3, 0, 1])
Out[275]: 4
In [276]: solve([2,2,0,0])
Out[276]: 3
In [277]: solve([1,2,0,1])
Out[277]: 1
答案 1 :(得分:4)
有一个很好的观察结果,我们可以在O(n)的时间内解决这个问题。诀窍是看一下数组最左边的元素。如果发生以下三种情况:
最左边的值是1。在这种情况下,我们永远不会想要移动此硬币,也不需要在此处移动任何硬币。因此,我们可以想象将这个元素从数组中切除并解决剩余的子问题。例如,数组[1, 0, 0, 3]
将转换为数组[0, 0, 3]
。
最左边的值大于1。在这种情况下,我们知道必须将一个硬币向右移动,才能将除一个硬币以外的所有硬币移出该位置。因此,我们可以进行该移动,在最左侧留下1,然后在上述情况下通过逻辑忽略该元素。例如,[3, 2, 0, 0, 0]
会变成[1, 4, 0, 0, 0]
,然后我们将1删除以获得[4, 0, 0, 0]
。
最左边的值为0 。在这种情况下,我们将需要进行一些移动来覆盖此插槽。然后的问题是,我们采取了哪些行动。我们需要在这里进行的观察是,覆盖此插槽的最有效方法是找到第一个插槽k,其中位置k或之前的硬币总数至少为k,然后从中选择足够的硬币堆到最左边的位置并进行k-1个移动,将这些硬币送出。要知道为什么,首先要注意的是,没有最佳策略会涉及从位置k的右侧到位置k发送任何硬币以填充最左边的插槽中的间隙,因为如果这样做,我们将有太多的硬币最初的k个插槽必须转回去。因此,这意味着我们知道任何最佳解决方案都必须使用前k个插槽中的硬币来填补空白。现在,想象有比将硬币从k堆向左移动更好的解决方案。这意味着我们试图使用少于k-1个硬币来覆盖前k-1个插槽,然后留下一个孔,然后只能通过将桩k向左移动来填补该孔。换句话说,任何解决方案都涉及将桩k向左移动,因此首先向左移动k永远不会是次优的。
这是上述想法的实现,在时间O(n)内运行:
int moves = 0;
for (int i = 0; i < n; i++) {
/* Case 1 requires no action. */
/* Case 2: leftmost pile has more than one coin. */
if (coins[i] > 1) {
coins[i + 1] += coins[i] - 1;
/* No need to edit coins[i]; we won't touch it again. */
}
/* Case 3: Find a pile and shift it back. */
else if (coins[i] == 0) {
/* Total up coins until a free spot is found. */
int k = 1;
int zeroes = 1;
int cumTotal = coins[i + k];
while (cumTotal <= k) {
k++;
if (coins[k] == 0) zeroes++;
cumTotal += coins[i + k];
}
/* Remove from that pile enough coins to cover all the zeroes encountered. */
coins[k] -= zeroes;
moves += k;
/* Continue our scan after this position. */
i = k;
}
}
return moves;
答案 2 :(得分:1)
考虑一次只能移动一个硬币:
这个问题的简单解决方案是遍历所有0
个硬币的位置,并从每个位置进行迭代,从左到右进行迭代,以找到一个硬币以上的位置,并跟踪从该硬币中带出硬币所需的全部移动位置到初始位置。
int minimumCoinMoves(vector<int>&coins) {
int n = coins.size();
int moves = 0;
for (int i = 0; i < n; ++i) {
if (coins[i] == 0) {
for (int j = 0; j < n; ++j) {
if (coins[j] > 1) { // fill up ith place with coin in jth place
coins[i] = 1;
coins[j]--;
moves += abs(j - i); // total moves from jth to ith place
break;
}
}
}
}
return moves;
}
如果我们假设我们有n
个硬币,那么这种方法将使我们花费O(n * n)
的时间复杂度。在n
的值很大的情况下,这种复杂性可能不可行。我们可以通过简单地修改上述解决方案来跟踪其他向量中带有多余硬币的位置,并从左向右检索O(n)
个硬币从所有位置的位置移动的硬币,从而将我们的复杂性降低到0
。 br />
int minimumCoinMoves(vector<int>&coins) {
int n = coins.size();
int moves = 0;
vector<int>extraCoinIndices;
for (int i = 0; i < n; ++i) {
if (coins[i] > 1) {
extraCoinIndices.push_back(i);
}
}
int ptr = 0;
for (int i = 0; i < n; ++i) {
if (coins[i] == 0) {
moves += abs(extraCoinIndices[ptr] - i);
coins[i] = 1;
coins[ extraCoinIndices[ptr] ]--;
if (coins[ extraCoinIndices[ptr] ] == 1) ptr++;
}
}
return moves;
}
编辑:
考虑一次可以移动任意数量的硬币:
如果我们认为可以从某个位置移动任意数量的硬币,则可以进行以下更改以在O(n)
中进行解决。
int minimumCoinWithAnyNumberMoves(vector<int>&coins) {
int n = coins.size();
int moves = 0;
int farZero = -1;
int extraCoins = 0;
for (int i = 0; i < n; ++i) {
if (coins[i] == 0) {
if (extraCoins > 0) {
extraCoins--;
moves++;
continue;
}
farZero = (farZero == -1) ? i : farZero;
} else {
if (farZero == -1) {
if (extraCoins > 0) moves++;
extraCoins += (coins[i] - 1);
continue;
}
assert(extraCoins == 0);
int totZeros = i - farZero;
moves += totZeros;
if (coins[i] <= totZeros) {
farZero = farZero + coins[i];
} else {
farZero = -1;
extraCoins += (coins[i] - totZeros - 1);
}
}
}
return moves;
}