【问题标题】:Find minimum cost to convert array to arithmetic progression找到将数组转换为算术级数的最小成本
【发布时间】:2015-07-31 16:24:15
【问题描述】:

我最近在一次采访中遇到了这个问题。我真的想不出一个算法来解决这个问题。

给定一个未排序的整数数组,我们必须找到可以将该数组转换为算术级数的最小成本,如果数组中的任何元素发生更改,则会产生 1 个单位的成本。此外,元素的值介于 (-inf,inf) 之间。

我有点意识到这里可以使用 DP,但我无法解方程。这些值有一些限制,但我不记得了。我只是在寻找高级伪代码。

【问题讨论】:

  • 是否允许交换,还是应该只是逐个元素的算术操作?
  • 这有点像线性回归。

标签: algorithm


【解决方案1】:

编辑 不幸的是,这是一个正确的解决方案,虽然很容易理解,但在 O(n^3) 时效率不是很高。

function costAP(arr) {
    if(arr.length < 3) { return 0; }
    var minCost = arr.length;
    for(var i = 0; i < arr.length - 1; i++) {
        for(var j = i + 1; j < arr.length; j++) {
            var delta = (arr[j] - arr[i]) / (j - i);
            var cost = 0;
            for(var k = 0; k < arr.length; k++) {
                if(k == i) { continue; }
                if((arr[k] + delta * (i - k)) != arr[i]) { cost++; }
            }
            if(cost < minCost) { minCost = cost; }
        }
    }
    return minCost;
}
  • 找出数组中每对不同索引之间的相对delta
  • 使用相对 delta 来测试使用该 delta 将整个数组转换为 AP 的成本
  • 返还最低费用

【讨论】:

  • 您对寻找现有算术级数的见解是正确的,但恐怕没有什么将它限制在一次运行中,因此相邻的差异不是很有用。例如,在 [1, 42, 3, 69, 5] 中,42 和 69 可以更改,成本仅为 2. -1 现在。
  • @j_random_hacker - 我添加了一个始终正确的解决方案
【解决方案2】:

Louis Ricci 有寻找现有最大算术级数的正确基本思想,但假设它必须出现在一次运行中,而实际上这个级数的元素可以出现在位置的任何子集中,例如:

1 42 3 69 5 1111 2222 8

只需要 4 处更改:

  42   69   1111 2222
1    3    5           8

要计算这个,请注意每个 AP 都有一个最右边的元素。我们可以假设输入向量的每个元素 i 依次是最右边的 AP 位置,并且对于每个这样的 i 考虑 i 左侧的所有位置 j,确定每个 (i, j) 组合隐含的步长,并且当这是整数(表示有效的 AP),将表示此步长的元素数量加一并在位置 i 结束——因为所有这些元素都属于同一个 AP。那么总的最大值就是最长的AP:

struct solution {
    int len;
    int pos;
    int step;
};

solution longestArithProg(vector<int> const& v) {
    solution best = { -1, 0, 0 };

    for (int i = 1; i < v.size(); ++i) {
        unordered_map<int, int> bestForStep;
        for (int j = 0; j < i; ++j) {
            int step = (v[i] - v[j]) / (i - j);
            if (step * (i - j) == v[i] - v[j]) {
                // This j gives an integer step size: record that j lies on this AP
                int len = ++bestForStep[step];
                if (len > best.len) {
                    best.len = len;
                    best.pos = i;
                    best.step = step;
                }
            }
        }
    }

    ++best.len;     // We never counted the final element in the AP
    return best;
}

上面的 C++ 代码使用 O(n^2) 时间和 O(n) 空间,因为它循环遍历每对位置 i 和 j,对每个位置执行单个哈希读取和写入。回答原来的问题:

int howManyChangesNeeded(vector<int> const& v) {
    return v.size() - longestArithProg(v).len;
}

【讨论】:

  • 这是一个更有效的答案
【解决方案3】:

这个问题有一个简单的几何解释,这表明它可以在 O(n^2) 时间内解决,并且可能无法比这更快地解决(从3SUM 减少)。假设我们的数组是[1, 2, 10, 3, 5]。我们可以把这个数组写成一个点序列

(0,1), (1,2), (2,10), (3,3), (4,5)

其中x值是数组项的索引,y值是数组项的值。现在的问题变成了找到一条通过该集合中可能的最大点数的线。转换数组的代价是不在一条线上的点数,当一条线上的点数最大化时,它最小化。

此 SO 帖子中给出了对该问题的相当明确的答案:What is the most efficient algorithm to find a straight line that goes through most points?

思路:对于集合中的每一个点P,从左到右,找到经过该点的线和P​​右边的最大点数。(我们不需要看左边的点的 P,因为它们会在更早的迭代中被捕获)。

要找到 P 右侧的 P 共线点的最大数量,对于每个这样的点 Q 计算线段 PQ 的斜率。在哈希图中计算不同的斜率。映射到最大命中数的斜率就是您要寻找的。​​p>

技术问题:您可能不想使用浮点运算来计算斜率。另一方面,如果您使用有理数,则可能必须计算最大公约数才能通过比较分子和分母来比较分数,这会将运行时间乘以 log n 的因子。相反,您应该通过测试ad == bc 是否相等来检查有理数a/bc/d 是否相等。

上面引用的 SO 帖子比 3SUM 有所减少,即这个问题是 3SUM 难的,这表明如果这个问题可以比 O(n^2) 更快地解决,那么 3SUM 也可以大大解决比 O(n^2) 快。这就是整数在 (-inf,inf) 中的条件。如果已知整数来自有界集合,则从 3SUM 的减少不是确定的。

另一个有趣的问题是,当整数在有界集合 (-N,N) 中时,维基百科中在 O(n + N log N) 时间内求解 3SUM 的想法是否可用于解决将数组转换为 AP 问题的时间比 O(n^2) 快。

【讨论】:

  • 您看起来描述的算法与我给出的算法大致相同,除了(a)我检查 P 左侧(而不是右侧)的点,以及(b)我们可以忽略非- 积分斜率(假设元素必须保持整数)所以这就是我所做的,而不是使用 FP 或有理数。此外,your 减少目前是错误的方法 - 它需要将 from 链接的直线通过最大点问题 转换为 您给出的特殊情况(即,相同的问题,但 x 坐标被限制为 1, 2, 3 ,...)。
  • @j_random_hacker,当然,关于理性斜率,你是对的!我应该考虑到这一点。我相信您对减少的方向也是正确的,尽管我相信如果我们允许具有未定义元素(对应于缺失点)的数组的更改成本为 0,这很简单。我会再考虑一下。
【解决方案4】:

给定数组a = [a_1, a_2, ..., a_n] 的未排序整数,让diffs = [a_2-a_1, a_3-a_2, ..., a_n-a_(n-1)]

找到diffs 中出现的最大值并调整a 中的任何值,使所有相邻值相差这个量。

【讨论】:

  • 关于“调整a 中的任何值必要...”的部分有点重要。您如何确定要调整多少个值?我正在考虑类似 [1, 2, 4, 6, 8, 19, 21, 23, 25, 0, 0, 0] 的情况,其中有两个或更多子序列与所需的差异值匹配。
  • 你的算法需要更多的散列:a = [1,2,3,5,7,8,9,10], diffs = [1,1,2,2,1, 1,1],maxRecurringDiff = 5(5 次出现 1)。如果从头开始调整值,则最终成本为 5,但如果从最后开始调整值,则成本为 4(更改 1、2、3、4 => 3、4、5、6)。
  • 我忽略了两个有效点。我看到@LouisRicci 发布的解决方案解决了这些问题。
【解决方案5】:

有趣的是,即使我今天在校园招聘测试中也遇到了同样的问题。在进行测试时,我意识到这种基于数组中两个后续元素之间最常见差异来更改元素的逻辑在某些情况下会失败。 例如-4,5,8,9。根据上面提出的 a2-a1,a3-a2 的逻辑,答案应该是 1,但事实并非如此。 正如您建议的 DP,我觉得它可以在修改数组成本时以及未修改时为数组成本中的每个元素考虑 2 个值并返回 2 的最小值。最终在您到达末尾时终止数组。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-02-14
    • 1970-01-01
    • 1970-01-01
    • 2020-11-25
    • 1970-01-01
    • 1970-01-01
    • 2014-10-13
    • 2017-08-26
    相关资源
    最近更新 更多