a-z*_*a-z 4 c++ algorithm optimization greedy
c是一个给定的n整数数组; 问题是要找到一个增加的n整数数组,a (a[i] <= a[i+1])以便最小化这个总和:
abs(a[0]+c[0]) + abs(a[1]+c[1]) + ... + abs(a[n-1]+c[n-1])
// abs(x) = absolute value of x
Run Code Online (Sandbox Code Playgroud)
最优a存在只出现在整数中,c因此我们可以使用DP来解决它O(n^2):
dp[i][j]: a[i] >= j'th integer
Run Code Online (Sandbox Code Playgroud)
但可能应该有一个更快的解决方案O(n lg n).
更新:我添加了解决方案,最大限度地减少了绝对值之和.在这篇文章的最后,如果有人感兴趣的话,其他最小化平方和的解决方案仍在这里.
我从算法开始,该算法仅适用于非负整数数组.然后它将扩展到任何整数(甚至扩展到非整数对象).
这是一个贪婪的算法.它使用整数的按位表示.从每个数组元素的最高位开始(暂时忽略其他位).找到最大的前缀,最大化零/零平衡.现在清除所有数组值,属于前缀并且具有零最高有效位(这些值的所有位都为零).对于后缀中具有非零最高有效位的所有数组值,将所有其他位设置为"1".将此算法递归地应用于前缀和后缀,使用下一位作为"最重要".
这会将原始数组拆分为多个段.您可以找到每个段的中位数,并使用此中位数填充输出数组.或者,只需在处理前缀时在输出数组中设置相应的位,并在处理后缀时将它们保留为零.
所有这一切都有效,因为最小化绝对值之和需要找到子阵列的中位数,并且在找到这个中值时,你可以非常近似地比较值,总是只使用整个数组的一个最高有效位并下降到其他稍后,对于子阵列.
这是C++ 11代码片段,它解释了详细信息:
//g++ -std=c++0x
#include <iostream>
#include <vector>
#include <iomanip>
using namespace std;
typedef vector<unsigned> arr_t;
typedef arr_t::iterator arr_it;
void nonincreasing(arr_it array, arr_it arrayEnd, arr_it out, int bits)
{
if (bits != -1)
{
int balance = 0;
int largestBalance = -1;
arr_it prefixEnd = array;
for (arr_it i = array; i != arrayEnd; ++i)
{
int d = ((*i >> bits) & 1)? 1: -1;
balance += d;
if (balance > largestBalance)
{
balance = largestBalance;
prefixEnd = i + 1;
}
}
for (arr_it i = array; i != prefixEnd; ++i)
{
*(out + (i - array)) += (1 << bits);
if (!((*i >> bits) & 1))
{
*i = 0;
}
}
nonincreasing(array, prefixEnd, out, bits - 1);
for (arr_it i = prefixEnd; i != arrayEnd; ++i)
{
if ((*i >> bits) & 1)
{
*i = (1 << bits) - 1;
}
}
nonincreasing(prefixEnd, arrayEnd, out + (prefixEnd - array), bits - 1);
}
}
void printArray(const arr_t& array)
{
for (auto val: array)
cout << setw(2) << val << ' ';
cout << endl;
}
int main()
{
arr_t array({12,10,10,17,6,3,9});
arr_t out(array.size());
printArray(array);
nonincreasing(begin(array), end(array), begin(out), 5);
printArray(out);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
要处理任何整数,而不仅仅是积极的,有两种选择:
以下是该算法的高级描述.复杂性是O(N).
从搜索一个子阵列开始,从c []的开头开始,并且具有尽可能大的平均值.然后在[]中用这个平均值填充相同长度的子阵列(舍入到最接近的整数并取反).然后从[]和c []中删除这个子数组(换句话说,假设[]的开头和c []按子阵列的长度向前移动)并递归地将此算法应用于[]和c的其余部分[].
该算法最有趣的部分是搜索最大的子阵列.使用来自c []的累积元素总和填充临时数组b []:b[0] = c[0], b[1] = b[0] + c[1], ...现在,您可以使用以下方法确定c []中任何时间间隔的平均值:(b[i+m] - b[i]) / m.巧合的是,完全相同的公式(其值的最大化)确定了从b [i]到曲线的切线,由b []描述.因此,您可以使用任何Convex外壳算法立即找到此算法所需的所有最大值(以及子阵列边界).凸壳算法通常使用二维点并具有超线性复杂度.但在这种情况下,点已经在一个维度中排序,因此Graham扫描或Monotone Chain算法在O(N)时间内完成任务,这也决定了整个算法的复杂性.
