给出a
个非负整数和两个整数n
和k
的数组m
,找到k
的{{1}}个元素,其乘积等于a
(返回其索引)。输入保证可以解决。
因此,蛮力算法将检查所有可能的组合,即O(n!/(k!(nk)!))性能,但是时间限制表明存在O(n log n)解决方案,我正在努力找到。
答案 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