我遇到过这个问题:
有n + 1个装货码头。放置方框1-> n的排列 第一个有一个叉子可以将一个盒子移动到一个空盒子 一次位置。 提供一种算法,以最小的方式对框进行排序 移动次数。
我的算法(伪代码)(基于1的索引)
(1)遍历查找最大框的数组。将其移至最后一个插槽(n + 1)。递增计数器1。
(2)然后从开始重新开始到limit = n_th slot,找到max box并将其交换到结尾。增加计数器3(因为交换需要3次移动)。
更新:Saruva Sahu在下面提出了一个非常好的解决方案,我优化了一点以避免“交换”。
public static void moveToPos(int[] nums) {
int tempLoc = nums.length - 1;
final int n = nums.length - 2;
// boxes --> loc
Map<Integer, Integer> tracking = new HashMap<>();
for (int i = 0; i < n; ++i) {
// if the target place is empty
if (nums[i] == tempLoc) {
// then move it there
nums[tempLoc] = nums[i];
// new temp loc
tempLoc = i;
}
else if(nums[i] != i) {
// move current box at target out of the way
nums[tempLoc] = nums[nums[i]];
if (tempLoc != nums[nums[i]]){
// nums[i] is not at the right place yet
// so keep track of it for later
tracking.put(nums[nums[i]], tempLoc);
}
nums[nums[i]] = nums[i];
tempLoc = i;
}
}
// move the unstelled to its right place
while (!tracking.isEmpty()) {
// where is the box that is supposed to be at tempLoc
int loc = tracking.remove(tempLoc);
nums[tempLoc] = nums[loc];
// make the emtpy spot the new temp loc
tempLoc = loc;
}
}
对此更好的算法是什么?
答案 0 :(得分:4)
找到任何不合适的方框。将它移动到最后一个码头。然后找到应该位于第一个框'原始位置的框并将其移动到那里。然后找到此位置的方框,依此类推。最后,您将第一个框移动到其目标位置。然后,重新开始直到所有方框都处于正确的位置。这将只触摸任何一个框,除了一个周期的第一个框,它将被触摸两次。
答案 1 :(得分:2)
有一种非常好的技术可以解决这个问题。让我们说我们有这个顺序的盒子。
[5] 4 2 3 1
将第一个方框与第五个方框交换(这是第一个方框的值):
[1] 4 2 3 5
现在第一个盒子在正确的位置。转到第二位。
1 [4] 2 3 5
交换第二个方框,第四个方框(这是第二个方框的值)得到:
1 [3] 2 4 5
现在再次检查第二个盒子是否处于正确位置。 交换第二个方框,第三个方框(这是第二个方框的值)得到:
1 [2] 3 4 5
现在再次检查第二个盒子是否处于正确位置。如果没有移动到下一个索引。 重复上述步骤直到第n个框。
1 2 [3] 4 5
1 2 3 [4] 5
1 2 3 4 [5]
那就是它。这些方块将被分类。
要点注意事项:
<强>更新强>: Nico建议的算法给出最小移动次数,如下所示:
5 4 2 3 1 [ ] : Start positions
.. 4 2 3 1 [5] : 1st move
1 4 2 3 .. [5] : 2nd move
1 4 2 3 5 [ ] : 3rd move
1 .. 2 3 5 [4] : 4th move
1 2 .. 3 5 [4] : 5th move
1 2 3 .. 5 [4] : 6th move
1 2 3 4 5 [ ] : 7th move
总共7次移动,代价是时间复杂度更高O(n ^ 2)。
另一方面,我建议的算法保证最低时间复杂度O(n),但不是最小移动次数,如下所示:
[5] 4 2 3 1 -> [1] 4 2 3 5 : 3 moves to swap 5 and 1
1 [4] 2 3 5 -> 1 [3] 2 4 5 : 3 moves to swap 4 and 3
1 [3] 2 4 5 -> 1 [2] 3 4 5 : 3 moves to swap 3 and 2
在O(n)的较低时间复杂度下总计9次移动。
答案 2 :(得分:1)
没有交换的示例C代码,使用[0]作为空槽,并假设[1]到[n]具有随机顺序的值1到n。每次移动到[1]到[n]都会将元素移动到它的最终位置。该算法遵循&#34;周期&#34;在排列中。
int i,j,k;
// scan a[] from 1 to n
for(i = 1; i <= n; i++){
if(i != a[i]){
// if element out of place, move to a[0]
a[0] = a[i];
// follow the cycle to put elements in place
j = i;
do{
// find element that belongs in a[j]
for(k = i; j != a[k]; k++);
// move that element to a[j]
a[j] = a[k];
j = k;
}while(j != a[0]);
// move a[0] to a[j] to complete rotate of cycle
a[j] = a[0];
}
}
作为一个更通用的例子,索引数组I []根据值A []的数组排序,然后A []和I []根据I []中的排序索引重新排序,每次写入A []都会在其中放置一个元素的排序位置。这是一个C ++示例(使用lambda compare)。
int A[8] = {7,5,0,6,4,2,3,1};
size_t I[8];
size_t i, j, k;
int ta;
// create array of indices to A[]
for(i = 0; i < sizeof(A)/sizeof(A[0]); i++)
I[i] = i;
// sort array of indices according to A[]
std::sort(I, I+sizeof(A)/sizeof(A[0]),
[&A](size_t i, size_t j){return A[i] < A[j];});
// reorder A[] and I[] according to I[]
for(i = 0; i < sizeof(A)/sizeof(A[0]); i++){
if(i != I[i]){
ta = A[i];
k = i;
while(i != (j = I[k])){
A[k] = A[j];
I[k] = k;
k = j;
}
A[k] = ta;
I[k] = k;
}
}
答案 3 :(得分:0)
IMO,最有效的算法是(如果“移动”意味着“交换”,并允许移动到任何位置):
假设数组的长度为n
。计算数组的longest increasing subsequence,假设最长增长子序列的长度为s
,则最小移动为n - s
。
即
[2, 3, 5, 4, 1]
,n
为5,s
为3([2, 3, 5]
),因此答案为5 - 3 = 2
[5, 4, 2, 3, 1]
,n
为5,s
为2([2, 3]
),因此答案为5 - 2 = 3