给定一个具有 n 个值的数组 A
,令 A 的 X
是一个数组,该数组在索引 i 中保存大于 A[i]
且位于其右侧的元素数在原始数组 A
中。
例如,如果 A 是:[10,12,8,17,3,24,19]
,那么 X(A) 是:[4,3,3,2,2,0,0]
如何在 O(n log(n))
时间和 O(n)
空间复杂度中解决这个问题?
我可以通过使用循环在 O(n^2)
时间和 O(1)
空间中轻松解决这个问题,并且对于每个元素,计算右侧有多少元素比它大,但我没有成功满足这些要求。
我正在考虑使用快速排序,最坏的情况是可以在 O(n log(n))
中完成,但我不知道排序数组在这里有什么帮助。
注意:关于快速排序,算法需要进行一些调整以确保 O(n log(n)) 在最坏情况下而不是平均情况下。
答案 0 :(得分:13)
问题陈述的快速总结:给定一个包含 A
个整数的数组 N
,构造一个数组 X
使得对于每个 i
,X[i] =
A
中索引大于 i
且也大于 A[i]
的元素数。
解决这个问题的一种方法是使用二叉搜索树。首先从最后一个元素迭代到第一个元素,在迭代时将每个元素添加到集合中。每次我们在一个元素 e
处,使用二叉搜索树的 find()
操作来查找当前树中有多少元素大于 e
。
也许您的第一个想法是使用 std::multiset
(不是 std::set
,因为我们可能有重复的元素!),这是一个自平衡二叉搜索树,提供 O(logN)
插入和 O(logN)
元素查找。这似乎适用于该算法,但实际上不会。原因是当你调用 std::multiset::find()
时,它会返回一个迭代器到集合中的元素。查找集合中有多少元素实际上大于需要 O(N)
时间,因为找到从迭代器到集合末尾的距离需要重复递增。< /p>
为了解决这个问题,我们使用了一个“索引多重集”,它是一个稍微修改过的二叉搜索树,这样我们可以在O(logN)
的多重集中找到一个元素的index仍然支持 O(logN)
插入的时间。这是我演示此数据结构的代码:
#include <iostream>
#include <vector>
#include <ext/pb_ds/assoc_container.hpp>
using namespace std;
using namespace __gnu_pbds;
// I know this is kind of messy, but it's the general way to get a C++ indexed
// multiset without using an external library
typedef tree <int, null_type, less_equal <int>, rb_tree_tag,
tree_order_statistics_node_update> indexed_set;
int main()
{
int A_size;
cin >> A_size;
vector <int> A(A_size);
for(int i = 0; i < A_size; ++i){
cin >> A[i];
}
// Input Done
indexed_set nums;
vector <int> X(A_size);
for(int i = A_size - 1; i >= 0; --i){
// order_of_key returns the first index that A[i] would be at in a sorted list
// with the same elements as nums.
X[i] = nums.size() - nums.order_of_key(A[i]);
nums.insert(A[i]);
}
for(int item : X){
cout << item << " ";
}
cout << "\n";
return 0;
}
因此,总体而言,总体策略是
nums
以查看有多少元素大于当前元素。 (O(logN)
)O(logN)
)
显然,该算法的总时间复杂度为O(NlogN)
,空间复杂度为O(N)
。对这种方法的观察和见解的快速总结:
INSIGHT:如果我们从最后一个元素迭代到第一个元素(不是第一个到最后一个元素),索引集将只包含在任何给定迭代中当前元素右侧的元素,这正是我们想要什么。这节省了我们的时间,因为如果我们从左到右迭代,我们不需要担心在开始时插入所有元素然后一个一个地删除它们。
观察:std::set
对于该算法中的二叉搜索树是不够的,因为尽管它提供了O(logN)
查找一个元素,计算元素< em>position 在集合中需要 O(N)
时间的最坏情况。然而,索引集在 O(logN)
时间内提供了这种“定位”操作,以及插入。
答案 1 :(得分:5)
Telescope 首先提到(在评论中)您可以使用二叉树来实现这一点。但是,您也可以使用以下替代方法来实现:
插入树的时间复杂度将是 N 次。因此,O(n log(n))
。并且空间复杂度自然会O(N)
。
可视化:
A : [10,12,8,17,3,24,19];
X(A) [? ,? ,? ,? ,? ,? ,?]
右树节点大小:S [?,?,?,?,?,?,?]
插入 19:
因此右子树中没有元素:
插入 24:
插入 3:
插入 17:
插入 8:
随着节点 8 的插入,执行旋转以平衡树。
在旋转过程中,大小也会更新,即节点 8 的大小从 0 变为 1,节点 3 的大小从 2 变为 0。: - S [? ,? ,1 ,0 ,0 ,0 ,1]
插入 12:
12 小于根(即 19)并且根的大小为 1,因此有 2 个元素大于 12 根及其右子树。让我们向左走,12 比根大(i.e., 8),让我们将节点 8 的大小从 1 增加到 2,然后转到右子树。 12 也小于根(即 17),所以到目前为止 12 小于三个元素。让我们往左边走。
大小为 12 = 0
X(A) [? ,3 ,3 ,2 ,2 ,0 ,0]
S [? ,0 ,0 ,0 ,2 ,0 ,1]
随着节点 12 的插入,执行双旋转以平衡树。
在旋转过程中,尺寸也会更新 - S [? ,0 ,1 ,2 ,0 ,0 ,1]
插入 10:
一种可能的 C 实现(AVL 代码改编自 source):
#include<stdio.h>
#include<stdlib.h>
struct Node{
int key;
struct Node *left;
struct Node *right;
int height;
int size;
};
int height(struct Node *N){
return (N == NULL) ? 0 : N->height;
}
int sizeRightTree(struct Node *N){
return (N == NULL || N -> right == NULL) ? 0 : N->right->height;
}
int max(int a, int b){
return (a > b) ? a : b;
}
struct Node* newNode(int key){
struct Node* node = (struct Node*) malloc(sizeof(struct Node));
node->key = key;
node->left = NULL;
node->right = NULL;
node->height = 1;
node->size = 0;
return(node);
}
struct Node *rightRotate(struct Node *y) {
struct Node *x = y->left;
struct Node *T2 = x->right;
x->right = y;
y->left = T2;
y->height = max(height(y->left), height(y->right))+1;
x->height = max(height(x->left), height(x->right))+1;
y->size = sizeRightTree(y);
x->size = sizeRightTree(x);
return x;
}
struct Node *leftRotate(struct Node *x){
struct Node *y = x->right;
struct Node *T2 = y->left;
y->left = x;
x->right = T2;
x->height = max(height(x->left), height(x->right))+1;
y->height = max(height(y->left), height(y->right))+1;
y->size = sizeRightTree(y);
x->size = sizeRightTree(x);
return y;
}
int getBalance(struct Node *N){
return (N == NULL) ? 0 : height(N->left) - height(N->right);
}
struct Node* insert(struct Node* node, int key, int *size){
if (node == NULL)
return(newNode(key));
if (key < node->key){
*size = *size + node->size + 1;
node->left = insert(node->left, key, size);
}
else if (key > node->key){
node->size++;
node->right = insert(node->right, key, size);
}
else
return node;
node->height = 1 + max(height(node->left), height(node->right));
int balance = getBalance(node);
if (balance > 1 && key < node->left->key)
return rightRotate(node);
if (balance < -1 && key > node->right->key)
return leftRotate(node);
if (balance > 1 && key > node->left->key){
node->left = leftRotate(node->left);
return rightRotate(node);
}
if (balance < -1 && key < node->right->key){
node->right = rightRotate(node->right);
return leftRotate(node);
}
return node;
}
int main()
{
int arraySize = 7;
struct Node *root = NULL;
int A[7] = {10,12,8,17,3,24,19};
int X[7] ={0};
for(int i = arraySize - 1; i >= 0; i--)
root = insert(root, A[i], &X[i]);
for(int i = 0; i < arraySize; i++)
printf("%d ", X[i]);
printf("\n");
return 0;
}
输出:
4 3 3 2 2 0 0
答案 2 :(得分:0)
类似于合并排序,在处理范围的右侧和处理左侧之前插入计数,例如:
#include <algorithm>
#include <functional>
void count_greater_on_right( int* a, int* x, int begin, int end )
{
if( end - begin <= 2 )
{
if( end - begin == 2 && a[begin] < a[begin+1] )
{
x[begin]+=1; // specific
std::swap( a[begin], a[begin+1] );
}
return;
}
int middle =(begin+end+1)/2;
count_greater_on_right( a, x, middle, end );
// specific
{
for( int i=begin; i!=middle; ++i )
{
x[i]+=std::lower_bound( &a[middle], &a[end], a[i], std::greater<int>() )-&a[middle];
}
}
count_greater_on_right( a, x, begin, middle );
std::inplace_merge( &a[begin], &a[middle], &a[end], std::greater<int>() );
}
代码,特定于任务,注释为 // specific; 排序的相反顺序使它稍微简单恕我直言; 更新“a”,因此如果您需要原始序列,请创建副本。
答案 3 :(得分:0)
如果将数组深入到子范围然后对这些子范围进行排序,问题就可以解决。让我们详细看看,
给定数组 = [10, 12, 8, 17, 3, 24, 19]
现在在长度为 4
的子范围内划分数组,并对这些子范围进行排序,如下所示,
子范围排序数组
.................... ...............
| 8 | 10 | 12 | 17 | | 3 | 19 | 24 |
.................... ...............
2 0 1 3 4 6 5 => index
让我们取子范围排序数组的第一个条目,即 8
并尝试找到大于 8
的正确元素的数量
正如您在上面看到的数字 8
属于第一个子范围,并且因为子范围已排序,子范围中的元素按升序排列,但不按索引顺序排列。这意味着在当前子范围内,我们必须将元素 8
右侧所有元素的索引与元素 8
8
的索引是 2
但 10
有 index = 0
,这意味着 10
在输入数组中 8
的左边,< br/>
12
的索引也小于 8
的索引,这意味着 12
在输入数组中位于 8
的左侧,
17
的索引是 3
大于 8
的索引,这意味着 17
在输入数组中位于 8
的右侧,可以认为更大元素,
将8
的索引与当前子范围的所有右边元素的索引、右边更大的元素count = 1
进行比较后,我们来看下一个范围,
在8
的子范围之后,事情完全变了,现在我们知道这个子范围在子范围元素8
所属的右侧,这意味着我们不必比较索引8
的元素或这个范围,都在元素 8
的右边,我们只需要找出有多少大于 8
,
现在我们将右子范围的第一个元素与 8
进行比较,正如您在上面看到的,第一个元素是 3
,它小于 8
但如果右子的第一个元素如果范围大于当前元素,那么我们可以直接将 count 增加到右子范围中存在的元素数。
因为第一个元素 3
小于 8
,我们在右子范围内找到 8
的上限,即 19
和 {{1} 中的所有元素} 在右子范围内大于 19
,所以有两个元素 8
并且由于这个计数增加了 19, 24
并成为 two
最后有 count = 3
个右元素大于元素 3
。
以类似的方式可以为所有元素找到更大的正确元素的数量,结果数组将是,
x(A) = 8
结论是,通过将输入数组在排序的子范围内划分,可以通过以下步骤找到右侧更大的元素,
[4, 3, 3, 2, 2, 0, 0]
输出:
#include <iostream>
#include <vector>
#include <iterator>
#include <algorithm>
using std::cout;
std::vector<std::pair<int, std::size_t>> arrayOfSortedSubRange(std::size_t subRangeSize,
const std::vector<int>& numArr){
std::vector<std::pair<int, std::size_t>> res;
res.reserve(numArr.size());
for(std::size_t i = 0, numArrSize = numArr.size(); i < numArrSize; ++i){
res.emplace_back(numArr[i], i);
}
for(std::vector<std::pair<int, std::size_t>>::iterator it = res.begin(), endIt = res.end(); endIt != it;){
std::vector<std::pair<int, std::size_t>>::iterator rangeEndIt = it + std::min<std::ptrdiff_t>(endIt - it,
subRangeSize);
std::sort(it, rangeEndIt, [](const std::pair<int, std::size_t>& a, const std::pair<int, std::size_t>& b){
return a.first < b.first;});
it = rangeEndIt;
}
return res;
}
std::size_t rightGreterElmentCountOfNumber(int num, std::vector<std::pair<int, std::size_t>>::const_iterator rightSubRangeIt,
std::vector<std::pair<int, std::size_t>>::const_iterator endIt){
std::size_t count = 0;
std::vector<std::pair<int, std::size_t>>::const_iterator subRangEndIt = rightSubRangeIt +
std::min<std::ptrdiff_t>(endIt - rightSubRangeIt, 4);
while(endIt != rightSubRangeIt){
if(rightSubRangeIt->first > num){
count += subRangEndIt - rightSubRangeIt;
}
else{
count += subRangEndIt -
std::upper_bound(rightSubRangeIt, subRangEndIt, num, [](int num,
const std::pair<int, std::size_t>& element){ return num < element.first;});
}
rightSubRangeIt = subRangEndIt;
subRangEndIt += std::min<std::ptrdiff_t>(endIt - subRangEndIt, 4);
}
return count;
}
std::vector<std::size_t> rightGreaterElementCountForLessThanFiveNumbers(const std::vector<int>& numArr){
std::vector<std::size_t> res(numArr.size(), 0);
std::vector<std::size_t>::iterator resIt = res.begin();
for(std::vector<int>::const_iterator it = numArr.cbegin(), lastIt = it + (numArr.size() - 1); lastIt != it;
++it, ++resIt){
*resIt = std::count_if(it + 1, numArr.cend(), [num = *it](int rightNum){return rightNum > num;});
}
return res;
}
std::vector<std::size_t> rightGreaterElementCount(const std::vector<int>& numArr){
if(numArr.size() < 5){
return rightGreaterElementCountForLessThanFiveNumbers(numArr);
}
std::vector<std::size_t> resArr(numArr.size(), 0);
std::vector<std::pair<int, std::size_t>> subRangeSortedArr = arrayOfSortedSubRange(4, numArr);
for(std::vector<std::pair<int, std::size_t>>::const_iterator it = subRangeSortedArr.cbegin(),
endIt = subRangeSortedArr.cend(); endIt != it;){
std::vector<std::pair<int, std::size_t>>::const_iterator rightNextSubRangeIt = it + std::min<std::ptrdiff_t>(
endIt - it, 4);
for(std::vector<std::pair<int, std::size_t>>::const_iterator eleIt = it; rightNextSubRangeIt != eleIt; ++eleIt){
std::size_t count = std::count_if(eleIt, rightNextSubRangeIt, [index = eleIt->second](
const std::pair<int, std::size_t>& element){ return index < element.second;});
if(endIt != rightNextSubRangeIt){
count += rightGreterElmentCountOfNumber(eleIt->first, rightNextSubRangeIt, endIt);
}
resArr[eleIt->second] = count;
}
it += std::min<std::ptrdiff_t>(endIt - it, 4);
}
return resArr;
}
int main(){
std::vector<std::size_t> res = rightGreaterElementCount({10, 12, 8, 17, 3, 24, 19});
cout<< "[10, 12, 8, 17, 3, 24, 19] => [";
std::copy(res.cbegin(), res.cbegin() + (res.size() - 1), std::ostream_iterator<std::size_t>(cout, ", "));
cout<< res.back()<< "]\n";
}