我最近遇到了以下面试问题,我想知道动态编程方法是否有效,或者/和是否有某种数学洞察力可以使解决方案更容易......它与ieee754的翻倍非常相似建造。
问题: 存在N个双值的向量V.其中向量的第i个索引处的值等于1/2 ^(i + 1)。例如:1 / 2,1 / 4,1 / 8,1 / 16等......
你要编写一个函数,它将一个双“r”作为输入,其中0< r< 1,并将V的索引输出到stdout,当求和时,它将给出一个最接近值'r'的值,而不是来自向量V的任何其他索引组合。
此外,索引的数量应该是最小的,如果有两个解决方案,应该首选最接近零的解决方案。
void getIndexes(std::vector<double>& V, double r)
{
....
}
int main()
{
std::vector<double> V;
// populate V...
double r = 0.3;
getIndexes(V,r);
return 0;
}
注意:似乎有一些SO'er并没有完全阅读这个问题。所以请大家注意以下几点:
解决方案,也就是总和可能大于r - 因此任何策略从r逐步减去分数,直到它达到零或接近零是错误的
有r的例子,其中有2个解,即| r-s0 | == | r-s1 |和s0&lt; s1 - 在这种情况下应选择s0,这会使问题稍微困难一些,因为背包式解决方案首先倾向于过度估计。
如果您认为这个问题很简单,那么您很可能还没有理解它。因此,再次阅读这个问题是个好主意。
EDIT(Matthieu M。): V = {1/2, 1/4, 1/8, 1/16, 1/32}
r = 0.3
,S = {1, 3}
r = 0.256652
,S = {1}
答案 0 :(得分:12)
考虑目标编号r
和一组F
分数{1/2, 1/4, ... 1/(2^N)}
。将最小分数1/(2^N)
表示为P
。
然后最佳总和将等于:
S = P * round(r/P)
也就是说,最佳总和S
将是可用的最小分数P
的一些整数倍。最大错误err = r - S
为± 1/2 * 1/(2^N)
。没有更好的解决方案是可能的,因为这需要使用小于1/(2^N)
的数字,这是集合F
中的最小数字。
由于分数F
都是P = 1/(2^N)
的两倍幂,任何 P
的整数倍可以表示为F
中的分数。要获取应使用的分数列表,请以二进制形式对整数round(r/P)
进行编码,并在1
二进制位置中读取kth
作为“包含kth
分数溶液”。
将
r = 0.3
和F
作为{1/2, 1/4, 1/8, 1/16, 1/32}
。
将整个问题乘以32。
将
r = 9.6
和F
作为{16, 8, 4, 2, 1}
。
将r
舍入到最接近的整数。
选择
r = 10
。
将10
编码为二进制整数(五位)
10 = 0b 0 1 0 1 0 ( 8 + 2 )
^ ^ ^ ^ ^
| | | | |
| | | | 1
| | | 2
| | 4
| 8
16
将每个二进制位与分数相关联。
= 0b 0 1 0 1 0 ( 1/4 + 1/16 = 0.3125 )
^ ^ ^ ^ ^
| | | | |
| | | | 1/32
| | | 1/16
| | 1/8
| 1/4
1/2
考虑通过将2**N
所涉及的所有数字相乘来转换问题,以便所有分数都成为整数。
原始问题:
考虑范围
r
中的目标号码0 < r < 1
和分数{1/2, 1/4, .... 1/(2**N)
列表。找到总和为S
的分数列表的子集,以使error = r - S
最小化。
成为以下等效问题(乘以2**N
后):
考虑
r
范围内的目标号码0 < r < 2**N
和整数{2**(N-1), 2**(N-2), ... , 4, 2, 1}
列表。找到总和为S
的整数列表的子集,以使error = r - S
最小化。
选择总和为给定数字的2的幂(尽可能小的误差)只是整数的二进制编码。因此,这个问题减少为整数的二进制编码。
r
,0 < r < 2**N
都可以转换为整数并以二进制形式表示。±0.5
的舍入误差。 (在原始问题中,最大错误为±0.5 * 1/2**N
。)0.5
=可能例外=见下文。)此函数将问题转换为整数等价,将r
四舍五入为整数,然后将整数r
的二进制表示作为整数读取,以获得所需的分数。
def conv_frac (r,N):
# Convert to equivalent integer problem.
R = r * 2**N
S = int(round(R))
# Convert integer S to N-bit binary representation (i.e. a character string
# of 1's and 0's.) Note use of [2:] to trim leading '0b' and zfill() to
# zero-pad to required length.
bin_S = bin(S)[2:].zfill(N)
nums = list()
for index, bit in enumerate(bin_S):
k = index + 1
if bit == '1':
print "%i : 1/%i or %f" % (index, 2**k, 1.0/(2**k))
nums.append(1.0/(2**k))
S = sum(nums)
e = r - S
print """
Original number `r` : %f
Number of fractions `N` : %i (smallest fraction 1/%i)
Sum of fractions `S` : %f
Error `e` : %f
""" % (r,N,2**N,S,e)
示例输出:
>>> conv_frac(0.3141,10)
1 : 1/4 or 0.250000
3 : 1/16 or 0.062500
8 : 1/512 or 0.001953
Original number `r` : 0.314100
Number of fractions `N` : 10 (smallest fraction 1/1024)
Sum of fractions `S` : 0.314453
Error `e` : -0.000353
>>> conv_frac(0.30,5)
1 : 1/4 or 0.250000
3 : 1/16 or 0.062500
Original number `r` : 0.300000
Number of fractions `N` : 5 (smallest fraction 1/32)
Sum of fractions `S` : 0.312500
Error `e` : -0.012500
0.5
问题如果r * 2**N
在0.5
中结束,则可以向上或向下舍入。也就是说,有两种可能的表示形式作为分数之和。
如果在原始问题陈述中,您希望使用最少分数的表示(即二进制表示中1
位的最小数量),只需尝试两种舍入选项并选择哪一种更经济
答案 1 :(得分:7)
也许我很蠢......
我在这里看到的唯一技巧是(1/2)^(i+1)
中i
[0..n)
的总和n
1
趋向于无穷大,(1/2)^i
。这个简单的事实证明sum (1/2)^j
总是优于j
[i+1, n)
n
,i = 0
无论r
是什么。
因此,在寻找我们的指数时,似乎我们没有太多选择。让我们从2^-(i+1)
2^-(i+1)
优于sum 2^-j
,因此我们需要 j
[i+2, N]
// The resulting vector contains at index i the sum of 2^-j for j in [i+1, N]
// and is padded with one 0 to get the same length as `v`
static std::vector<double> partialSums(std::vector<double> const& v) {
std::vector<double> result;
// When summing doubles, we need to start with the smaller ones
// because of the precision of representations...
double sum = 0;
BOOST_REVERSE_FOREACH(double d, v) {
sum += d;
result.push_back(sum);
}
result.pop_back(); // there is a +1 offset in the indexes of the result
std::reverse(result.begin(), result.end());
result.push_back(0); // pad the vector to have the same length as `v`
return result;
}
// The resulting vector contains the indexes elected
static std::vector<size_t> getIndexesImpl(std::vector<double> const& v,
std::vector<double> const& ps,
double r)
{
std::vector<size_t> indexes;
for (size_t i = 0, max = v.size(); i != max; ++i) {
if (r >= v[i]) {
r -= v[i];
indexes.push_back(i);
continue;
}
// We favor the closest to 0 in case of equality
// which is the sum of the tail as per the theorem above.
if (std::fabs(r - v[i]) < std::fabs(r - ps[i])) {
indexes.push_back(i);
return indexes;
}
}
return indexes;
}
std::vector<size_t> getIndexes(std::vector<double>& v, double r) {
std::vector<double> const ps = partialSums(v);
return getIndexesImpl(v, ps, r);
}
0.3
中的0.3:
1: 0.25
3: 0.0625
=> 0.3125
是否最接近(在平等的情况下推迟后者)可能成本高昂的唯一步骤是获得总和,但可以一次性预先计算(甚至可以预先计算)。
{{1}}
代码在ideone运行(带有一些调试输出)。请注意,对于{{1}},它会给出:
{{1}}
与其他答案略有不同。
答案 2 :(得分:4)
冒着downvotes的风险,这个问题似乎相当简单。只需从V
开始生成最大和最小的数字,依次调整每个索引,直到得到两个可能最接近的答案。然后评估哪一个是更好的答案。
这是未经测试的代码(用我不写的语言):
void getIndexes(std::vector<double>& V, double r)
{
double v_lower = 0;
double v_upper = 1.0 - 0.5**V.size();
std::vector<int> index_lower;
std::vector<int> index_upper;
if (v_upper <= r)
{
// The answer is trivial.
for (int i = 0; i < V.size(); i++)
cout << i;
return;
}
for (int i = 0; i < N; i++)
{
if (v_lower + V[i] <= r)
{
v_lower += V[i];
index_lower.push_back(i);
}
if (r <= v_upper - V[i])
v_upper -= V[i];
else
index_upper.push_back(i);
}
if (r - v_lower < v_upper - r)
printIndexes(index_lower);
else if (v_upper - r < r - v_lower)
printIndexes(index_upper);
else if (v_upper.size() < v_lower.size())
printIndexes(index_upper);
else
printIndexes(index_lower);
}
void printIndexes(std::vector<int>& ind)
{
for (int i = 0; i < ind.size(); i++)
{
cout << ind[i];
}
}
我得到了这份工作! :d
(请注意,这是一个糟糕的代码,依赖于我们知道完全 V中的内容......)
答案 3 :(得分:2)
我不知道您是否有测试用例,请尝试下面的代码。这是一种动态编程方法。
1] exp: given 1/2^i, find the largest i as exp. Eg. 1/32 returns 5.
2] max: 10^exp where exp=i.
3] create an array of size max+1 to hold all possible sums of the elements of V.
Actually the array holds the indexes, since that's what you want.
4] dynamically compute the sums (all invalids remain null)
5] the last while loop finds the nearest correct answer.
以下是代码:
public class Subset {
public static List<Integer> subsetSum(double[] V, double r) {
int exp = exponent(V);
int max = (int) Math.pow(10, exp);
//list to hold all possible sums of the elements in V
List<Integer> indexes[] = new ArrayList[max + 1];
indexes[0] = new ArrayList();//base case
//dynamically compute the sums
for (int x=0; x<V.length; x++) {
int u = (int) (max*V[x]);
for(int i=max; i>=u; i--) if(null != indexes[i-u]) {
List<Integer> tmp = new ArrayList<Integer>(indexes[i - u]);
tmp.add(x);
indexes[i] = tmp;
}
}
//find the best answer
int i = (int)(max*r);
int j=i;
while(null == indexes[i] && null == indexes[j]) {
i--;j++;
}
return indexes[i]==null || indexes[i].isEmpty()?indexes[j]:indexes[i];
}// subsetSum
private static int exponent(double[] V) {
double d = V[V.length-1];
int i = (int) (1/d);
String s = Integer.toString(i,2);
return s.length()-1;
}// summation
public static void main(String[] args) {
double[] V = {1/2.,1/4.,1/8.,1/16.,1/32.};
double r = 0.6, s=0.3,t=0.256652;
System.out.println(subsetSum(V,r));//[0, 3, 4]
System.out.println(subsetSum(V,s));//[1, 3]
System.out.println(subsetSum(V,t));//[1]
}
}// class
以下是运行代码的结果:
For 0.600000 get 0.593750 => [0, 3, 4]
For 0.300000 get 0.312500 => [1, 3]
For 0.256652 get 0.250000 => [1]
For 0.700000 get 0.687500 => [0, 2, 3]
For 0.710000 get 0.718750 => [0, 2, 3, 4]
答案 4 :(得分:2)
我首先要说的是,我确实相信这个问题是微不足道的......
(等到所有扔石头之后)
是的,我确实读过OP的编辑,说我必须重新阅读这个问题。因此,我可能会遗漏一些我看不到的东西 - 在这种情况下,请原谅我的无知,并随意指出我的错误。
我不认为这是一个动态编程问题。听起来天真的风险,为什么不在搜索索引时尝试保留r
的两个估计 - 即低估和高估。毕竟,如果r
不等于可以从V
的元素计算出来的任何总和,那么它将介于这两种总和之间。我们的目标是找到这些总和,并报告哪个更接近r
。
我把一些快速而肮脏的Python代码汇总在一起。它报告的答案对于OP提供的两个测试用例是正确的。请注意,如果return
的结构使得至少必须返回一个索引 - 即使最佳估计根本没有索引。
def estimate(V, r):
lb = 0 # under-estimation (lower-bound)
lbList = []
ub = 1 - 0.5**len(V) # over-estimation = sum of all elements of V
ubList = range(len(V))
# calculate closest under-estimation and over-estimation
for i in range(len(V)):
if r == lb + V[i]:
return (lbList + [i], lb + V[i])
elif r == ub:
return (ubList, ub)
elif r > lb + V[i]:
lb += V[i]
lbList += [i]
elif lb + V[i] < ub:
ub = lb + V[i]
ubList = lbList + [i]
return (ubList, ub) if ub - r < r - lb else (lbList, lb) if lb != 0 else ([len(V) - 1], V[len(V) - 1])
# populate V
N = 5 # number of elements
V = []
for i in range(1, N + 1):
V += [0.5**i]
# test
r = 0.484375 # this value is equidistant from both under- and over-estimation
print "r:", r
estimate = estimate(V, r)
print "Indices:", estimate[0]
print "Estimate:", estimate[1]
注意:写完答案后,我注意到this answer遵循相同的逻辑。唉!
答案 5 :(得分:1)
解决方案实现Polynomial time approximate algorithm。程序的输出与其他解决方案的输出相同。
#include <math.h>
#include <stdio.h>
#include <vector>
#include <algorithm>
#include <functional>
void populate(std::vector<double> &vec, int count)
{
double val = .5;
vec.clear();
for (int i = 0; i < count; i++) {
vec.push_back(val);
val *= .5;
}
}
void remove_values_with_large_error(const std::vector<double> &vec, std::vector<double> &res, double r, double max_error)
{
std::vector<double>::const_iterator iter;
double min_err, err;
min_err = 1.0;
for (iter = vec.begin(); iter != vec.end(); ++iter) {
err = fabs(*iter - r);
if (err < max_error) {
res.push_back(*iter);
}
min_err = std::min(err, min_err);
}
}
void find_partial_sums(const std::vector<double> &vec, std::vector<double> &res, double r)
{
std::vector<double> svec, tvec, uvec;
std::vector<double>::const_iterator iter;
int step = 0;
svec.push_back(0.);
for (iter = vec.begin(); iter != vec.end(); ++iter) {
step++;
printf("step %d, svec.size() %d\n", step, svec.size());
tvec.clear();
std::transform(svec.begin(), svec.end(), back_inserter(tvec),
std::bind2nd(std::plus<double>(), *iter));
uvec.clear();
uvec.insert(uvec.end(), svec.begin(), svec.end());
uvec.insert(uvec.end(), tvec.begin(), tvec.end());
sort(uvec.begin(), uvec.end());
uvec.erase(unique(uvec.begin(), uvec.end()), uvec.end());
svec.clear();
remove_values_with_large_error(uvec, svec, r, *iter * 4);
}
sort(svec.begin(), svec.end());
svec.erase(unique(svec.begin(), svec.end()), svec.end());
res.clear();
res.insert(res.end(), svec.begin(), svec.end());
}
double find_closest_value(const std::vector<double> &sums, double r)
{
std::vector<double>::const_iterator iter;
double min_err, res, err;
min_err = fabs(sums.front() - r);
res = sums.front();
for (iter = sums.begin(); iter != sums.end(); ++iter) {
err = fabs(*iter - r);
if (err < min_err) {
min_err = err;
res = *iter;
}
}
printf("found value %lf with err %lf\n", res, min_err);
return res;
}
void print_indexes(const std::vector<double> &vec, double value)
{
std::vector<double>::const_iterator iter;
int index = 0;
printf("indexes: [");
for (iter = vec.begin(); iter != vec.end(); ++iter, ++index) {
if (value >= *iter) {
printf("%d, ", index);
value -= *iter;
}
}
printf("]\n");
}
int main(int argc, char **argv)
{
std::vector<double> vec, sums;
double r = .7;
int n = 5;
double value;
populate(vec, n);
find_partial_sums(vec, sums, r);
value = find_closest_value(sums, r);
print_indexes(vec, value);
return 0;
}
答案 6 :(得分:0)
对矢量进行排序并搜索r可用的最近部分。存储该索引,从r中减去该值,并用r的余数重复。迭代直到达到r,或者找不到这样的索引。
示例:
0.3 - 可用的最大值为0.25。 (索引2)。剩下的现在是0.05
0.05 - 可用的最大值为0.03125 - 余数为0.01875
等
等。每一步都是排序数组中的O(logN)搜索。步数也将是O(logN)总复杂度将超过O(logN ^ 2)。
答案 7 :(得分:0)
这不是动态编程问题
输出应该是int(索引)的向量,而不是双精度的向量
这可能是从0到2的精确值,这只是概念:
A)输出零指数直到r0(r-指数值已经输出)大于1/2
B)检查r0 double和
的内部表示x(第1位移位)= - 指数; //更大的指数,最小的数字(你开始的1/2 /(x)中的x越大)
使用body检查float的小数部分的位表示:
(方向取决于小/大端)
{
if (bit is 1)
output index x;
x++;
{
if (bit is 1)
output index x;
x++;
每一步的复杂性是不变的,所以整体上是O(n),其中n是输出的大小。
答案 8 :(得分:0)
为了解释这个问题,r的二进制表示中的一位是什么(在二进制点之后)?如果你愿意,N就是'精确'。
在Cish伪代码中
for (int i=0; i<N; i++) {
if (r>V[i]) {
print(i);
r -= V[i];
}
}
你可以为r == 0添加一个额外的测试来提前终止循环。
请注意,这给出了最接近'r'的最小二进制数,即如果有两个同样“正确”的答案则更接近零。
如果第N个数字为1,则需要在获得的“二进制”数字中加“1”并检查原始“r”。 (提示:构造向量a [N],b [N]为'bits',设置'1'位而不是'print'在上面。设置b = a并进行手动添加,逐个数字地从'结束' b'直到你停止携带。转换为双倍并选择较近者。
注意a []&lt; = r&lt; = a [] + 1/2 ^ N且b [] = a [] + 1 ^ 2 ^ N.
'索引的最少数量[sic]'是红鲱鱼。