构建分数面试挑战

时间:2012-05-07 06:57:11

标签: c++ algorithm dynamic-programming

我最近遇到了以下面试问题,我想知道动态编程方法是否有效,或者/和是否有某种数学洞察力可以使解决方案更容易......它与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并没有完全阅读这个问题。所以请大家注意以下几点:

  1. 解决方案,也就是总和可能大于r - 因此任何策略从r逐步减去分数,直到它达到零或接近零是错误的

  2. 有r的例子,其中有2个解,即| r-s0 | == | r-s1 |和s0&lt; s1 - 在这种情况下应选择s0,这会使问题稍微困难一些,因为背包式解决方案首先倾向于过度估计。

  3. 如果您认为这个问题很简单,那么您很可能还没有理解它。因此,再次阅读这个问题是个好主意。

  4. EDIT(Matthieu M。): V = {1/2, 1/4, 1/8, 1/16, 1/32}

    的2个示例
    • r = 0.3S = {1, 3}
    • r = 0.256652S = {1}

9 个答案:

答案 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.3F作为{1/2, 1/4, 1/8, 1/16, 1/32}

  1. 将整个问题乘以32。

      

    r = 9.6F作为{16, 8, 4, 2, 1}

  2. r舍入到最接近的整数。

      

    选择r = 10

  3. 10编码为二进制整数(五位)

    10 = 0b 0 1 0 1 0    ( 8 + 2 )
            ^ ^ ^ ^ ^
            | | | | |
            | | | | 1
            | | | 2
            | | 4
            | 8
            16
    
  4. 将每个二进制位与分数相关联。

       = 0b 0 1 0 1 0    ( 1/4 + 1/16 = 0.3125 )
            ^ ^ ^ ^ ^
            | | | | |
            | | | | 1/32
            | | | 1/16
            | | 1/8
            | 1/4
            1/2
    
  5. 证明

    考虑通过将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的幂(尽可能小的误差)只是整数的二进制编码。因此,这个问题减少为整数的二进制编码。

    • 解决方案的存在:任何正浮点数r0 < r < 2**N都可以转换为整数并以二进制形式表示。
    • 最优性:解的整数版本中的最大误差是±0.5的舍入误差。 (在原始问题中,最大错误为±0.5 * 1/2**N。)
    • 唯一性:对于任何正(浮点)数,都有唯一的整数表示,因此是唯一的二进制表示。 (0.5 =可能例外=见下文。)

    实施(Python)

    此函数将问题转换为整数等价,将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**N0.5中结束,则可以向上或向下舍入。也就是说,有两种可能的表示形式作为分数之和。

    如果在原始问题陈述中,您希望使用最少分数的表示(即二进制表示中1位的最小数量),只需尝试两种舍入选项并选择哪一种更经济

答案 1 :(得分:7)

也许我很蠢......

我在这里看到的唯一技巧是(1/2)^(i+1)i [0..n)的总和n 1趋向于无穷大,(1/2)^i。这个简单的事实证明sum (1/2)^j总是优于j [i+1, n) ni = 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]'是红鲱鱼。