给出一个列表,例如
[1, 45, 1, 99, 3, 5, 95, 1, 5, 97, 3, 99, 87]
从该列表中返回最多为100的对。当项目被消耗时,不应重新评估它。在上面的例子中,输出将是:
[{1,99}, {1, 99}, {3,97}, {5,95}]
不假设列表被排序,因为重复对应该起作用。
方法的优点/缺点很好理解(BigO复杂性,空间/时间)。
答案 0 :(得分:4)
你可以使用list comprehension和guard来做。
find_pairs(List) ->
[{X, Y} || X <- List,
Y <- List,
X+Y =:= 100].
这种方法当然具有n^2
的复杂性,但几乎任何其他方法都是如此。最后,您必须采用每个元素(n
)并相互验证(并且* n
)。您可以像another answer中建议的那样引入一些优化,但仍然会留下大的O n^2
。所以我认为没有意义。
如果这种复杂性会给我带来一些问题,事实上我必须进行优化,我会尝试减少第二次*n
。由于第二部分只是值查找(对于给定X
的eny,您在其余值中寻找100 -X
。你可以尝试在gb_tree中查找。创建这样的树需要一些n log n
,但这只做一次。所有的观察都会带你log n
。所以最终这种方法会有n log n
复杂性。
在其他语言中,而不是使用gb_tree,只需对列表进行排序,并进行二进制搜索以进行值查找(同样位O为n log n
)。但必须记住,在Erlang列表中不是数组。它们是“链表”,查找列表中的一个值不是常量,但可能具有n
复杂度。这个n
会对我们的算法产生影响,这可能会让我们n * n long n
比初始n^2
更糟糕。
编辑。使用我的方法,在配对来自[1, 99, 99]
而不是[{1, 99}]
的值时,我会返回[{1,99},{1,99},{99,1},{99,1}]
。
我真的需要仔细阅读问题。谢谢史蒂夫指出这一点。我仍然想留下我最初的答案,因为它清楚地显示了算法的复杂性,这是我在答案中集中注意力的。
那就是说我想为任何可能的混淆道歉,并提供有效的解决方案:
find_pairs(List) ->
find_pairs(List, _Pairs = [], _Sum = 100).
find_pairs([], Pairs, _Sum) ->
Pairs;
find_pairs([First | Tail], Pairs, Sum) ->
case pop(Tail, Sum - First) of
{Second, Rest} ->
find_pairs(Rest,
[{First, Second} | Pairs],
Sum);
not_found ->
find_pairs(Tail,
Pairs,
Sum)
end.
pop(List, Value) ->
pop(List, Value, []).
pop([], _Value, _Processed) ->
not_found;
pop([Value | Tail], Value, Processed) ->
{Value, Processed ++ Tail};
pop([Different | Tail], Value, Processed) ->
pop(Tail, Value, [Different|Processed]).
再次关于这种算法的复杂性。 find_pairs
只是通过列表,pop
也是如此,所以它似乎是n^2
。事实证明,它并非如此简单。还有一个附加功能++
,由于链接列表的性质可能会n
复杂化。所以最后,根据输入,我们可以体验n*(2*n)
。仍然在BigO中它是n^2
,但值得注意的是,在算法中添加更多工作(或代码行)并不能保证提高性能。
还有一个简单的解决方法。 ++
具有左侧元素的复杂性。因此,在pop
中连接两个列表,而不是将Processed
添加到Tail
,可以将Tail
添加到Processed
。这样,当我们在Value
位置(以及k
次调用之后)找到k
时,我们在连接期间只需执行n - k
次额外工作。这可以保证pop
的工作时间超过n
。我们回到直接n^2
寻找整个算法,(而不依赖于数据顺序)。
答案 1 :(得分:2)
在shell中,因为它使用递归匿名函数,它只适用于R17,但在具有早期erlang版本的模块中可以正常
1> L = [1, 45, 1, 99, 3, 5, 95, 1, 5, 97, 3, 99, 87].
2> F= fun F([],R) -> R;
2> F([H|T],R) -> Rest = lists:dropwhile(fun(X) -> X+H /= 100 end,T),
2> case Rest of
2> [] -> F(T,R);
2> [Found|_] -> F(lists:delete(Found,T),[{H,Found}|R])
2> end
2> end.
#Fun<erl_eval.36.90072148>
3> F(L,[]).
[{5,95},{3,97},{1,99},{1,99}]
4>
如果我必须自己完成,我会完全重现我会做的事情:
第一个实现是在使其工作精神,我做了一个更快的,首先排序列表。以下模块实现了2个解决方案以及一些测试和评估性能的功能(在2种极端情况下,新解决方案对于长列表来说要快得多:没有解决方案或每个术语都属于一对)。在我的电脑上,s2比s1快2500多倍,随机列表包含100000个元素。
-module (sum).
-compile([export_all]).
s1(L,S) -> s1(L,S,[]).
s1([],_S,R) -> R;
s1([H|T],S,R) ->
Rest = lists:dropwhile(fun(X) -> X+H /= S end,T),
case Rest of
[] -> s1(T,S,R);
[Found|_] -> s1(lists:delete(Found,T),S,[{H,Found}|R])
end.
s2(L,S) ->
Linc = lists:sort(L),
Ldec = lists:reverse(Linc),
s2(Linc,Ldec,S,[]).
s2(Linc,Ldec,_S,R) when Linc == [] ; Ldec == [] ; hd(Linc) > hd(Ldec) -> R;
s2([H,H|Linc],[H,H|Ldec],S,R) when S == 2*H -> s2(Linc,Ldec,S,[{H,H}|R]);
s2([H1|Linc],[H2|Ldec],S,R) when S == H1+H2, H1/=H2 -> s2(Linc,Ldec,S,[{H1,H2}|R]);
s2([H|Linc],Ldec,S,R) when H + hd(Ldec) < S -> s2(Linc,Ldec,S,R);
s2(Linc,[_H|Ldec],S,R) -> s2(Linc,Ldec,S,R).
%% Test and performance
compare(S1,S2) ->
S = normalize(S1),
S = normalize(S2).
normalize(S) -> lists:sort([{min(X,Y),max(X,Y)} || {X,Y} <- S]).
shuffle(P) when is_list(P) ->
Max = length(P)*10000,
{_,R}= lists:unzip(lists:keysort(1,[{random:uniform(Max),X} || X <- P])),
R.
test1(S) -> % every term is part of a solution pair
random:seed(erlang:now()),
L = shuffle(lists:seq(1,S)),
test(L,S+1).
test2(S) -> % no solution
random:seed(erlang:now()),
L = shuffle(lists:seq(1,S)),
test(L,2*S).
test3(S) -> % random
random:seed(erlang:now()),
L = [random:uniform(2*S) || _ <- lists:seq(1,S)],
test(L,S).
test(L,S) ->
{T1,S1} = timer:tc(sum,s1,[L,S]),
{T2,S2} = timer:tc(sum,s2,[L,S]),
compare(S1,S2),
{T1,T2,S1}.
答案 2 :(得分:0)
我不是那种流利的算法和他们的表现,但这是我已经想到的。我的一般想法是将列表拆分为两个子列表,其中元素&gt; = 50和&lt; 50.至少这可以确保您不必在每个列表中查找对,而只需在它们之间查找:
-module(test).
-export([add_to_100/1]).
add_to_100([]) -> [];
add_to_100(List) ->
{L1, L2} = split(List, [], []),
find_match(L1, L2).
%% Do matching between 2 lists
find_match([], _) -> [];
find_match(_, []) -> [];
find_match([H1|T1], L2) ->
case match_element(H1, L2, []) of
{{A, B}, Left} -> [{A, B} | find_match(T1, Left)];
{{}, Left} -> find_match(T1, Left)
end.
%% Match every element with list of integers.
%% Return match pair and exclude matched element from list of integers.
match_element(_, [], Rest) -> {{}, Rest};
match_element(E, [H|T], Rest) when E + H == 100 -> {{E,H}, Rest ++ T};
match_element(E, [H|T], Rest) -> match_element(E, T, [H|Rest]).
%% Split input list into two sublists - L1 < 50 and L2 >= 50
split([], L1, L2) -> {L1, L2};
split([H|T], L1, L2) when H < 50 -> split(T, [H|L1], L2);
split([H|T], L1, L2) -> split(T, L1, [H|L2]).
示例:
85> c(test).
{ok,test}
86> test:add_to_100([1, 45, 1, 99, 3, 5, 95, 1, 5, 97, 3, 99, 87]).
[{3,97},{5,95},{1,99},{1,99}]
87> test:add_to_100([1, 45, 1, 99, 3, 5, 95, 1, 5, 97, -50, 3, 99, 87, 100, 0, 150]).
[{0,100},{3,97},{-50,150},{5,95},{1,99},{1,99}]
实现之后,我意识到它不处理对{50,50} - 无论如何你可以将它作为特殊情况添加到这个算法中。即使这个解决方案不完整,也应该让你深入了解Erlang中的模式匹配,尾递归和列表操作,在解决这个问题时你肯定需要它。