在乘积等于给定数的数组中查找k个元素

时间:2019-05-04 16:46:24

标签: algorithm

给出a个非负整数和两个整数nk的数组m,找到k的{​​{1}}个元素,其乘积等于a(返回其索引)。输入保证可以解决。

因此,蛮力算法将检查所有可能的组合,即O(n!/(k!(nk)!))性能,但是时间限制表明存在O(n log n)解决方案,我正在努力找到。

3 个答案:

答案 0 :(得分:3)

如评论中所述,这可以通过动态编程来解决。

有两种方法可以进行动态编程。自上而下,自下而上。权衡是自上而下更容易实现。但是自下而上通常可以表现更好。由于您正在努力寻找解决方案,因此我将自上而下进行解释。

要自上而下,您需要编写一个递归算法,然后编写memoize。备注意味着如果必须计算结果,则将其保存在缓存中。下次您不进行计算时,只需返回缓存的值。因此,您采用的功能是这样的:

def foo(bar, baz):
    # do recursive stuff
    return answer

,然后将其转换为此:

cached_foo = {}
def foo (bar, baz):
    if (bar, baz) not in cached_foo:
        # Do recursive stuff
        cached_foo[(bar, baz)] = answer
    return cached_foo[(bar, baz)]

在实践中可能会有复杂的情况,但这始终是普遍的想法。

在这种情况下,递归算法的核心是:

def reachable_factors(a, m, i, j):
    # Returns all factors of m that can be reached, and how to reach
    # them with j of the first i terms of a
    pass

此算法应该很慢。但是一旦您记住它,它将很快。


由于已经发布了另一种解决方案,所以这里是Python。

def exact_factorization(a, m, k):
    cache = {}
    def reachable_factors(i, j):
        # This will be all of the ways to get to a factor of m
        # using j of the first i elements of a
        if (i, j) not in cache:
            # This is the recursive calculation
            answer = {}
            if i < j:
                # We cannot use more than i of the first i elements.
                pass
            elif 0 == j:
                # The empty product is 1
                answer = {1: None}
            else:
                # First, find all of the ways of not using this element.
                for (fact, path) in reachable_factors(i-1, j).iteritems():
                    answer[fact] = path

                # Note the potential off by one error.  The i'th
                # element is at i-1
                i_th = a[i-1]

                # Next,find all of the ways of using this element
                for (fact, path) in reachable_factors(i-1, j-1).iteritems():
                    if 0 == m % (fact * i_th):
                        answer[fact * i_th] = [i-1, path]

            cache[(i, j)] = answer
        return cache[(i, j)]

    reachable = reachable_factors(len(a), k)

    # The answer is now in reachable[m], but as a nested list in reverse
    # order.  We want to extract it in a better format.
    path = reachable[m]
    final_answer = []
    while path is not None:
        final_answer.append(path[0])
        path = path[1]
    return [x for x in reversed(final_answer)]

print(exact_factorization(
    [1, 2, 3, 2, 1, 4, 12], 12, 4
))

这是自下而上的方法。请注意,它的性能与自上而下相同,但是所需的内存更少。它还避免了Python愚蠢的递归限制。

def exact_factorization(a, m, k):
    partial_answers = [{1: None}]
    for _ in range(k):
        partial_answers.append({})

    for i in range(len(a)):
        x = a[i]
        for j in range(k, 0, -1):
            these_answers = partial_answers[j]
            for fact, path in partial_answers[j-1].iteritems():
                if 0 == m % (x * fact):
                    these_answers[x * fact] = [i, path]

    reachable = partial_answers[k]

    if m not in reachable:
        return None

    # The answer is now in reachable[m], but as a nested list in reverse
    # order.  We want to extract it in a better format.
    path = reachable[m]
    final_answer = []
    while path is not None:
        final_answer.append(path[0])
        path = path[1]
    return [x for x in reversed(final_answer)]

答案 1 :(得分:0)

将输入数组中的数字映射到其频率。对于m的除数,开始递归尝试以构建大小为k的子集,前提是子集中的元素存在于地图中。对递归进行排序,以使子集中的元素排序。继续尝试将累加器乘积除以m的下一个更高的除数(如果频率允许,则用相同的除数),当下一个除法产生的除数小于当前除数时,请切断递归。

这是一个不处理频率的JavasScript示例(如chqrlie所指出的,重复的因素带来了问题,但我们可以通过仅尝试不同大小的组来处理它们):

function f(A, m, k){
  let map = {}
  for (let a of A)
    map[a] = map[a] ? map[a] + 1: 1
  
  let ds = Object.keys(map).map(x => Number(x)).sort((a, b) => a - b)
 
  function g(es, i, p){
    if (es.length == k - 1 && map[p])
      return [es.concat(p)]
    if (i == ds.length ||
        p / ds[i] < ds[i] ||
        es.length + ds.length - i < k)
      return []
    
    let result = g(es, i + 1, p)

    if (!(p % ds[i]))
      result = result.concat(
        g(es.slice().concat(ds[i]), i + 1, p / ds[i]))
        
     return result
  }

  return g([], 0, m)
}

console.log(JSON.stringify(f([15, 2, 3, 5, 30, 1], 30, 3)))
console.log(JSON.stringify(f([15, 2, 3, 5, 30, 1], 30, 2)))

let a = [1, 2]
for (let i=0; i<500000; i++)
  a.push(30)
a.push(5,6,7,8,9,10,11,12,13,14,15,16)
for (let i=0; i<500000; i++)
  a.push(15)
a.push(3,4)

let m = 1*2*3*4*5*6*7*8*9*10*11*12
console.log(JSON.stringify(f(a, m, 12)))

答案 2 :(得分:0)

测试所有组合的算法复杂性完全爆发,但是强行实施可以非常有效地修剪测试用例:我正在使用递归函数来测试数组中第i个数是否是除数m的值,然后从下一个元素开始在列表的其余部分上递归,以找到k-1的{​​{1}}除数,否则从下一个元素进行迭代。

m

输出:

#include <stdio.h>
#include <stdlib.h>

int found(int n, const int set[], int flags[], int i, int k, int m) {
    if (k <= 0)
        return m == 1;
    if (i >= n)
        return 0;
    if (set[i] == 0) {  // must special case 0
        if (m == 0) {
            if (i + k <= n) {
                while (k-- > 0)
                    flags[i++] = 1;
                return 1;
            }
            return 0;
        }
    } else if (m % set[i] == 0) {
        if (found(n, set, flags, i + 1, k - 1, m / set[i])) {
            flags[i] = 1;
            return 1;
        }
    }
    return found(n, set, flags, i + 1, k, m);
}

// get a number from the command line or from stdin
int getnum(int *index, char *argv[]) {
    int value;
    if (argv[*index]) {
        value = strtol(argv[(*index)++], NULL, 0);
        printf("%d ", value);
        return value;
    }
    if (scanf("%d", &value) != 1) {
        printf("invalid input\n");
        exit(1);
    }
    return value;
}

int main(int argc, char *argv[]) {
    int n, m, k, arg_i = 1;

    printf("enter n, k and m: ");
    n = getnum(&arg_i, argv);
    k = getnum(&arg_i, argv);
    m = getnum(&arg_i, argv);
    printf("\n");
    int set[n];
    int flags[n];
    printf("enter %d numbers: ", n);
    for (int i = 0; i < n; i++) {
        set[i] = getnum(&arg_i, argv);
        flags[i] = 0;
    }
    printf("\n");
    if (found(n, set, flags, 0, k, m)) {
        for (int i = 0, j = k; i < n; i++) {
            if (flags[i])
                printf("%d %c ", set[i], --j > 0 ? '*' : '=');
        }
        printf("%d\n", m);
    } else {
        printf("no solution\n");
    }
    return 0;
}

如果存在解决方案,我怀疑在大多数情况下会在线性时间内找到它。病理情况是,如果数组包含许多chqrlie$ ./findfactors 20 5 720 {0..19} enter n, k and m: 20 5 720 enter 20 numbers: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 1 * 2 * 3 * 8 * 15 = 720 的除数,尤其是值m,并且解决方案在数组末尾涉及很大因素,或者根本不涉及任何解决方案。然后,复杂度确实爆炸为 O(2 N

病理案例:

1