查找包含200000+个元素的2个数组元素的最小乘积的最快方法


13

我有一个数组a[n]。该号码n由我们输入。我需要找到的最小的产品a[i]a[j]如果:

1) abs(i - j) > k

2)a[i] * a[j]最小化

这是我的解决方案(非常幼稚):

#include <iostream>
using namespace std;
#define ll long long
int main() {
    ll n,k; cin >> n >> k;

    ll a[n]; for(ll i=0;i<n;i++) cin >> a[i];

    ll mn; bool first = true;

    for(ll i=0;i<n;i++) {
        for(ll j=0;j<n;j++) {
            if(i!=j)
            if(abs(i-j) > k) {
                if(first) {
                    mn = a[i]*a[j];
                    first = false;
                } else if(a[i]*a[j] < mn) mn = a[i]*a[j];
            }
        }
    }
    cout << mn << endl;
}

但是我想知道是否有更快的方法来找到具有距离的最小产品?


7
为什么不#include <bits / stdc ++。h>?和C ++仅通过编译器扩展提供可变长度数组。为什么不使用std::vector?@Scheff-排序将破坏原始的“距离”关系。
David C. Rankin

3
至少if (i!=j) if (abs(i - j) > k)可以取消检查。只需从i + k + 1开始内部循环for (ll j = i + k + 1; j < n; ++j)first如果mn事先用初始化,也可以取消的检查mn = a[0] * a[k + 1];。(也许k应该先检查一下n以防弹。)但它仍然是O(N²)。这必须更快地完成...
Scheff

2
@PaulMcKenzie请在前十个查询中显示一个不少于两个有用命中的查询,以最小化具有索引距离(或最大)的乘积
灰胡子

1
@PaulMcKenzie“可能有数百个(如果不是数千个)URL链接显示此问题的答案。” -请至少分享其中三个网址。
גלעדברקן

2
这个问题是从哪里来的?听起来不像是凭空制造的东西。如果它来自那些“在线法官”站点之一,我不会感到惊讶。如果是这样,那么在那些站点上可能就解决问题(甚至不是完整的解决方案)进行了长时间的讨论。
PaulMcKenzie

Answers:


12

假设至少有一对满足条件的元素并且其中两个元素的乘法没有溢出,则可以在Theta(n-k)时间和Theta(1)空间最坏和最好的情况下执行此操作,如下所示:

auto back_max = a[0];
auto back_min = a[0];
auto best = a[0]*a[k+1];

for(std::size_t i=1; i<n-(k+1); ++i) {
    back_max = std::max(back_max, a[i]);
    back_min = std::min(back_min, a[i]);
    best = std::min(best, std::min(a[i+k+1]*back_max, a[i+k+1]*back_min));
}

return best;

就时间和空间的渐近最坏情况复杂度而言,这是最佳的,因为最佳乘积可能至少a[0]n-(k+1)距离中的任何元素有关k+1,因此,n-(k+1)解决该问题的任何算法都至少需要读取整数。


该算法的原理如下:

最佳乘积使用的两个元素a,假设它们是a[r]a[s]。在不失一般性的前提下,我们可以假设s > r由于乘积是可交换的。

由于限制,abs(s-r) > k这意味着s >= k+1。现在s可能每个索引都满足此条件,因此我们遍历这些索引。那是i所示代码中的迭代,但是k+1为了方便起见,它被移动了(没关系)。对于每次迭代,我们需要找到涉及i+k+1最大索引的最优产品,并将其与先前的最佳猜测进行比较。

由于距离要求,可能i+k+1与之配对的所有索引都小于或等于所有索引i。我们也需要对所有这些进行迭代,但是这是不必要的,因为固定的a[i+k+1]*a[j]over 的最小值等于产品的单调性(考虑最小值和最大值over的最小值,考虑了这两种可能单调性的两个可能方向的符号或等效形式。)jimin(a[i+k+1]*max(a[j]), a[i+k+1]*min(a[j]))a[j]a[i+k+1]

由于a[j]我们在此处进行优化的值的集合是just {a[0], ..., a[i]}a[i]在的每次迭代中仅增加一个元素(),因此i,如果变量大于或小于先前的最佳值,我们可以通过更新变量来跟踪max(a[j])和跟踪min(a[j])单个变量a[i]。这是通过back_maxback_min在代码示例中完成的。

迭代(i=0)的第一步在循环中被跳过,而是作为变量的初始化执行。


3
@greybeard我不需要保留它们,因为具有最佳产品的唯一可能的选择a[i+k+1]是最小和最大。
胡桃

您能解释一下为什么该算法在您的答案中起作用吗?
MinaHany

6

不确定最快

对于没有i <j-k的简单问题,最小乘积是来自两个最小和最大元素的对的乘积。

因此,(以下内容太复杂了,请参见胡桃木的答案
(•如果
  k≤n,则阻挠•将minProduct初始化为a [0] * a [k + 1])

  • 以{}和{a [ j ] | 开头的两个动态minmax数据结构 upToIbeyondIplusK ķĴ }
  • 对于从0到n - k -1的 每个i
    • upToI上添加一个[ i ]
    • beyondIplusK删除a [ i + k ]

    • min(upToI)×min(beyondIplusK),min(upToI)×max(beyondIplusK),
      max(upToI)×min(beyondIplusK)和max(upToI)×max(beyondIplusK)之间检查新的最小积

这应该是最快的,至少在复杂度方面。现在是O(n)时间和存储时间。
smttsp

原始解决方案的复杂度为O(N ** 2),您如何估算解决方案的复杂度?
lenik

O(nlogn)时间,O(n)空间(适用于适当的minmax实现)
灰胡子

@老人。为什么需要n * logn时间。为什么就不能保持一个包含4 * N阵列minUptomaxUptominBeyondmaxBeyond(你可以在两个迭代创建)?然后,在第三次迭代中,对于每个索引,找到最小可能的乘法。
smttsp

(@smttsp这将是在方向上的步骤的替代核桃的溶液。)

4

对于“最小量级”

找到2个“最小幅度”元素,然后(找到两个零或搜索整个数组之后),将它们相乘。

对于没有abs(i - j) > k部分的“最低价值”

有3种可能性:

  • 两个最高(最小数量级)负数

  • 两个最低(最小数量级)的非负数

  • 最低(最大数量)负数和最高(最大数量)非负数

您可以搜索所有6个值并找出产品,最后一个是最好的。

然而; 一旦看到零,就知道您不需要再了解前两种可能性。当您看到一个负数和一个非负数时,您就知道只在乎第三种可能性。

这导致了具有3种状态的有限状态机-“关心所有3种可能性”,“除非看到负数,否则答案为零”和“仅关心最后一种可能性”。这可以实现为3个循环的集合,其中goto(有限状态机的)状态改变时,其中2个循环跳到另一个循环的()中间。

具体来说,它看起来可能有点模糊(未经测试):

   // It could be any possibility

   for(ll i=0;i<n;i++) {
       if(a[i] >= 0) {
            if(a[i] < lowestNonNegative1) {
                lowestNonNegative2 = lowestNonNegative1;
                lowestNonNegative1 = a[i];
            }
            if(lowestNonNegative2 == 0) {
                goto state2;
            }
       } else {
            if(a[i] > highestNegative1) {
                highestNegative2 = highestNegative1;
                highestNegative1= a[i];
            }
            if(lowestNonNegative1 < LONG_MAX) {
                goto state3;
            }
       }
   }
   if(lowestNonNegative2 * lowestNonNegative1 < highestNegative2 * highestNegative1) {
       cout << lowestNonNegative2 * lowestNonNegative1;
   } else {
       cout << highestNegative2 * highestNegative1;
   }
   return;

   // It will be zero, or a negative and a non-negative

   for(ll i=0;i<n;i++) {
state2:
       if(a[i] < 0) {
           goto state3;
       }
   }
   cout << "0";
   return;

   // It will be a negative and a non-negative

   for(ll i=0;i<n;i++) {
state3:
       if(a[i] < lowestNegative) {
           lowestNegative = a[i];
       } else if(a[i] > highestNonNegative) {
           highestNonNegative = a[i];
       }
    }
    cout << lowestNegative * highestNonNegative;
    return;

对于abs(i - j) > k零件的“最低价值”

在这种情况下,您仍然有3种可能性;并可以使其与“具有有限状态机的3个循环”相同的方法工作,但它太乱/太难看了。对于这种情况,更好的选择可能是对阵列进行预扫描以确定是否有零,以及它们是负数还是正数。因此,在进行预扫描后,您可以知道答案为零,也可以选择仅针对特定可能性设计的循环。


1
这在哪里解释指数差异的下界k
灰胡子

1
@greybeard:不需要(我错过了那部分)-需要修改代码以考虑到这一点。
布伦丹

为什么需要两个零?
TrentP

@TrentP:啊-你是对的。一个零就足以知道答案是0还是负数。
布伦丹
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.