给出一个数组,比如[4,2,1,3,5],基于这个数组,我们有一个新的数组,每个数字显示左边的元素数量比它本身大,这是[0,1,2,1,0]。现在编写一个给定输入[0,1,2,1,0]的函数,返回原始数组。数组的范围是1~n(n是数组的大小,如果排序,你可以假设原始数组中的所有数字都是连续的)
现在要恢复原始数组,我尝试了一种方法来解决问题,方法是从端到端遍历数组范围,如下所示:
我的方法:
假设范围是1~5,如果排序,原始数组将是[1,2,3,4,5]。从最后到乞讨迭代,
所以前5,没有元素可以大于5,所以它的最大数量大于0,那么4将有1作为其最大数量的更大元素,3到2等。存储密钥-value成对对象。
现在从后到前迭代输入,
仅仅将输入的每个元素作为值从地图映射到其键是不够的。以最佳性能处理此问题的直观方法是什么? (如果可能,避免使用O(n ^ 2)。)
答案 0 :(得分:1)
最初从数字1到n创建一个AVL树。
从后方开始,即从第n个指数开始(考虑基于1的指数)。
现在算法的高级大纲级别应如下所示:
1. At any ith index, say the number in array(not the originial array) is j
2. Search the number which is at (i-j)th position in your AVL tree(This can be done in O(logn) time. Comment if you need more explanation on this)
3. The element in the AVL tree is your required element. Delete that element from AVL tree.(O(logn))
因此总复杂度为O(nlogn)。
<强>操作实例强>
最初,树将包含所有5个元素。
从索引5开始(基于1的索引)。元素为0,即i = 5,j = 0。所以第五大元素是5。
现在树包含四个元素1,2,3和4. i = 4,j = 1。所以4-1 i..e第三大元素,在这种情况下是3。
i = 3,j = 2。 (3-2)rd最大元素是1,因为树包含(1,2,4)。
等等。
使用树查找第i个最大数字
我们可以通过在根节点处存储左子树中的节点数来实现这一点。因此,考虑一棵树,具有元素1,2,3,4和5以及树结构如下:
4(3)
/ \
3(1) 5(0)
/ \
1(0) 2(0)
在root中,数字4是值,圆括号中的数字具有左子树中的节点数。
在构建(插入和删除)树时,我们可以保持计数。
现在,要找到第i个节点,假设我们想要在给定树中假设第3个节点。我们从根开始,它说它有比左边小3个元素,所以我们向左移动。现在根,即3有一个较小的左元素,小于3(第i个元素),所以我们向右移动。减去1(左计数)+1(根本身)3。现在根是2我们想要第1个元素,左计数是0.因此,以2为根的子树的第1个元素是2。
基本伪代码如下:
while(true){
count = root->leftCount;
if((count+1)<i){
//move to right
i-=(count+1);
root = root->right;
}
else if(i==(count+1)){
//root is the ith node
break;
} else{
//move to the levft
root=root->left
}
}
答案 1 :(得分:1)
您可以使用Array#reduceRight
并使用该值作为负索引来生成原始数组。
var array = [1, 2, 3, 4, 5],
countLeft = [0, 1, 2, 1, 0],
result = countLeft.reduceRight(function (r, a) {
return array.splice(array.length - 1 - a, 1).concat(r);
}, []);
console.log(result);
ES6和反向基阵列的较短版本。
var array = [5, 4, 3, 2, 1],
countLeft = [0, 1, 2, 1, 0],
indices = array.map((_, i) => i),
result = [];
countLeft.forEach(a => {
result.unshift(array[indices[a]]);
indices = indices.filter((_, i) => i !== a);
});
console.log(result);
最后一个复杂度在O(n *(n-1)/ 2)和O(n)之间的提案。
此版本使用一个惰性数组,每次迭代都会逐渐减少长度。最后,偏移数组的元素为零。
var array = [5, 4, 3, 2, 1],
countLeft = [0, 1, 2, 1, 0],
result = [],
length = array.length;
countLeft.forEach((offset => (offset.length = countLeft.length, a => {
var i = offset[a] || 0;
result.unshift(array[i + a]);
offset.length--;
while (i < offset.length) {
offset[i] = (offset[i] || 0) + 1;
i++;
}
}))([]));
console.log(result);
线性版,受proposal
Oriol的启发
var array = [1, 2, 3, 4, 5],
countLeft = [0, 1, 2, 1, 0],
swap = [],
i = 0,
l,
temp;
while (i < countLeft.length) {
l = countLeft[i];
while (l) {
swap.push(i + l - countLeft[i]);
l--;
}
i++;
}
i = swap.length;
while (i--) {
temp = array[swap[i]];
array[swap[i]] = array[swap[i] - 1];
array[swap[i] - 1] = temp;
}
console.log(array);
答案 2 :(得分:0)
这是一个可能的解决方案。有关此方法的简要说明,请参阅内联注释。
var a = [0,1,2,1,0],
n, b = [], res = [];
// build b = [5,4,3,2,1]
// we use this array to keep track of values to be pushed in res[],
// sorted in descending order
for(n = a.length; n > 0; n--) {
b.push(n);
}
// for each element of a, starting from the end:
// find correct value in b and remove it from b
while(a.length) {
res.push(b.splice(a.pop(), 1)[0]);
}
res = res.reverse();
console.log(res);
输出:
[4, 2, 1, 3, 5]
答案 3 :(得分:0)
我提出了一种基于自定义排序的方法,基于mergesort:
与mergesort的区别在于合并部分。如果我们选择右边部分的j
- 元素而不是左边的i
- 元素,它将推进一些元素,因此其反转次数必须减少该数量。 / p>
与mergesort类似,复杂性为O(n log n)
function undoInversions(inversions) {
function reorder(arr, from=0, to=arr.length) {
// Based on a stable decreasing mergesort
if(from >= to) return []; // Unusual base case
if(to === from + 1) return [arr[from]]; // Base case
var m = Math.floor((from + to)/2);
var l = reorder(arr, from, m), // Left recursive call
r = reorder(arr, m, to), // Right recursive call
ret = [], i=0, j=0;
while(i < l.length && j < r.length) { // Merge
if(r[j].value - l.length + i >= l[i].value) {
r[j].value -= l.length - i; // Reduce number of inversions
ret.push(r[j++]);
} else {
ret.push(l[i++]);
}
}
while(i < l.length) ret.push(l[i++]); // Merge remaining, if any
while(j < r.length) ret.push(r[j++]); // Merge remaining, if any
return ret;
}
var array = new Array(inversions.length);
reorder(inversions.map(function(inv, idx) {
return {value: inv, originalIndex: idx}; // Keep track of indices
})).forEach(function(obj, idx) {
if(obj.value !== 0) throw 'Invalid input';
array[obj.originalIndex] = idx + 1; // Invert the permutation
});
return array;
}
console.log(JSON.stringify(undoInversions([0,1,2,1,0])));
以下是一个了解其工作原理的示例:
[0,1,2,1,0] ~ [4,2,1,3,5]
⤩ ⤧
[0,0,2,1,0] ~ [2,4,1,3,5]
⤩ ⤧
[0,1,0,1,0] ~ [2,1,4,3,5]
⤩ ⤧
[0,0,0,1,0] ~ [1,2,4,3,5]
⤩ ⤧
[0,0,0,0,0] ——→ [1,2,3,4,5]
也就是说,每个反转数组对应于一个排列。我们应用排列σ
,将输入转换为反转数组[0,0,0,0,0]
,这对应于排列[1,2,3,4,5]
。由于我们会跟踪原始索引,现在我们只需要将σ⁻¹
应用于[1,2,3,4,5]
,以便获得与输入相对应的排列。