给定数组最近的排列

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了给定数组最近的排列相关的知识,希望对你有一定的参考价值。

我有两个整数A[]B[]数组。数组B[]是固定的,我需要找到A[]的排列,其排列尺寸小于B[],排列最接近B[]。我的意思是:

for i in(0 <= i <n)abs(B [i] -A [i])最小,A[]应小于B[] lexiographically

例如:

A[]={1,3,5,6,7}

B[]={7,3,2,4,6}

所以,A[]最接近B[]的排列是

A[]={7,3,1,6,5}

我的方法

尝试A[]的所有排列,然后将其与B[]进行比较。但时间的复杂性将是(n! * n)

那么有什么方法可以优化这个吗?

编辑

n可以和10^5一样大

为了更好地了解enter image description here

答案

首先,构建一个有序的A不同元素计数图。

然后,向前遍历数组索引(0到n-1),从此映射中“撤消”元素。在每一点上,有三种可能性:

  • 如果i < n-1,并且可以选择A[i] == B[i],那么这样做并继续向前迭代。
  • 否则,如果可以选择A[i] < B[i],请为A[i] < B[i]选择最大可能值。然后选择所有后续数组索引的最大可用值。 (此时你不再需要担心维持A[i] <= B[i],因为我们已经在A[i] < B[i]的索引之后了。)返回结果。
  • 否则,我们需要回溯到可以选择A[i] < B[i]的最后一个索引,然后使用上一个项目符号中的方法。 请注意,尽管需要回溯,但这里最糟糕的情况是三次传递:一次使用第一个项目符号点中的逻辑传递,一次向后传递回溯以找到A[i] < B[i]可能的最后一个索引,然后是最终使用第二个项目符号中的逻辑进行前向传递。

由于维护有序映射的开销,这需要O(n log m)时间和O(m)额外空间,其中n是A的元素总数,m是不同元素的数量。 (由于m≤n,我们也可以将其表示为O(n log n)时间和O(n)额外空间。)


请注意,如果没有解决方案,那么回溯步骤将一直到达i == -1。如果发生这种情况,您可能希望引发异常。


编辑添加(2019-02-01):

在一个现已删除的答案中,גלעדברקן以这种方式总结了目标:

要在字典上缩小,数组必须有一个从左到右的初始可选部分,其中A[i] = B[i]以元素A[j] < B[j]结尾。为了最接近B,我们希望最大化该部分的长度,然后最大化数组的剩余部分。

因此,考虑到这个总结,另一种方法是做两个单独的循环,其中第一个循环确定初始部分的长度,第二个循环实际填充A。这相当于上述方法,但可以使代码更清晰。所以:

  1. 构建A不同元素计数的有序映射。
  2. 初始化initial_section_length := -1
  3. 遍历数组索引0到n-1,从此映射中“撤消”元素。对于每个指数: 如果可以选择A的尚未使用的元素小于B的当前元素,则将initial_section_length设置为等于当前数组索引。 (否则,不要。) 如果不可能选择一个尚未使用的A元素,它等于B的当前元素,那就突破这个循环。 (否则,继续循环。)
  4. 如果initial_section_length == -1,那么没有解决方案;举一个例外。
  5. 重复步骤#1:重新构建有序地图。
  6. 迭代从0到initial_section_length-1的数组索引,从地图中“撤回”元素。对于每个索引,选择一个尚未使用的A元素,该元素等于B的当前元素。 (第一个循环确保了这种元素的存在。)
  7. 对于数组索引initial_section_length,选择A中最大的尚未使用的元素,该元素小于B的当前元素(并从地图中“撤回”它)。 (第一个循环确保了这种元素的存在。)
  8. 迭代从initial_section_length+1到n-1的数组索引,继续从地图中“撤回”元素。对于每个索引,选择尚未使用的A的最大元素。

这种方法与基于回溯的方法具有相同的时间和空间复杂性。

另一答案

n!A[n]排列(如果有重复元素则少)。

在范围0..n!-1上使用二元搜索来确定A[]arbitrary found example)的第k个词典排列,其与B[]最接近。

也许在C ++中你可以利用std::lower_bound

另一答案

根据您的问题的评论部分中的讨论,您寻找一个完全由向量A的元素组成的数组 - 在字典顺序中 - 最接近向量B

对于这种情况,算法变得非常简单。这个想法与@ruakh的答案中已经提到的相同(虽然他的回答是指你问题的更早和更复杂的版本 - 仍然显示在OP中 - 因此更复杂):

  • 排序A.
  • 循环在B上并选择最接近B [i]的A元素。从列表中删除该元素。
  • 如果A中的元素没有小于或等于B [i],则选择最大的元素。

这是基本的实现:

#include <string>
#include <vector>
#include <algorithm>

auto get_closest_array(std::vector<int> A, std::vector<int> const& B)
{
    std::sort(std::begin(A), std::end(A), std::greater<>{});

    auto select_closest_and_remove = [&](int i)
    {
        auto it = std::find_if(std::begin(A), std::end(A), [&](auto x) { return x<=i;});
        if(it==std::end(A))
        {
            it = std::max_element(std::begin(A), std::end(A));            
        }
        auto ret = *it;
        A.erase(it);
        return ret;
    };

    std::vector<int> ret(B.size());
    for(int i=0;i<(int)B.size();++i)
    {
        ret[i] = select_closest_and_remove(B[i]);
    }
    return ret;
}

应用于OP中的问题得到:

int main()
{
    std::vector<int> A ={1,3,5,6,7};
    std::vector<int> B ={7,3,2,4,6};

    auto C = get_closest_array(A, B);

    for(auto i : C)
    {
        std::cout<<i<<" ";
    }
    std::cout<<std::endl;
}

并显示

7 3 1 6 5

这似乎是理想的结果。

以上是关于给定数组最近的排列的主要内容,如果未能解决你的问题,请参考以下文章

翻转数组

10个JavaScript代码片段,使你更加容易前端开发。

10个JavaScript代码片段,使你更加容易前端开发。

如何用java代码实现在一个已排列好的数组中找出小于等于给定x的位数下标(用二分查找做)?

Leetcode练习(Python):数组类:第75题:给定一个包含红色白色和蓝色,一共 n 个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色白色蓝色顺序排列。

给定 O(1) 或 O(log N) 时间中的源集,有没有办法在数学上生成排序排列和数组的给定索引?