与给定数组的最近排列

时间:2019-01-26 06:25:35

标签: c++ algorithm

问题

我有两个整数数组A[]B[]。数组B[]是固定的,我需要找到A[]的排列,该排列在地理学上小于B[],并且排列最接近B[]。我的意思是:

  

对于(0 <= 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

3 个答案:

答案 0 :(得分:5)

首先,为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 )多余的空间,其中<​​em> n 是A m distinct 元素的数量。 (由于 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尚未使用的元素{em>等于 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中尚未使用的最大元素。

此方法与基于回溯的方法具有相同的时间和空间复杂度。

答案 1 :(得分:2)

n!A[n]个置换(如果有重复元素,则更少)。

使用范围为0..n!-1的二进制搜索来确定A[]arbitrary found example)的第k个字典排列,该排列最接近B[]的下一个。

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

答案 2 :(得分:0)

基于对问题的评论部分中的讨论,您寻求一个数组,该数组完全由向量A的元素组成,按字典顺序排列,该元素最接近向量B

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

  • 排序A
  • 在B上循环,然后选择A中最接近B [i]的元素。从列表中删除该元素。
  • 如果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

这似乎是理想的结果。