让我们从基础开始,并尽可能优化:
#!/usr/bin/env bash
read N
sum=0
for ((i=1;i<N;++i)); do
if ((i%3 == 0 )) || (( i%5 == 0 )); then
(( sum += i ))
fi
done
echo $sum
在上面的代码中,我们运行循环
N次,最少进行
N次比较和最多
2N次求和(i和sum)。我们可以通过使用步长为3和5的多个循环来加快速度,但是我们必须注意避免重复计数:
#!/usr/bin/env bash
read N
sum=0
for ((i=N-N%3;i>=3;i-=3)); do (( sum+=i )); done
for ((i=N-N%5;i>=5;i-=5)); do (( i%3 == 0 )) && continue; ((sum+=i)); done
echo $sum
我们现在最多有2N/3 + 2N/5 = 16N/15个求和和N/5个比较。这已经快得多了。我们可以通过添加一个额外的循环,步长为3*5,来减去重复计数,进一步优化它。
#!/usr/bin/env bash
read N
sum=0
for ((i=N-N%3 ; i>=3 ; i-=3 )); do ((sum+=i)); done
for ((i=N-N%5 ; i>=5 ; i-=5 )); do ((sum+=i)); done
for ((i=N-N%15; i>=15; i-=15)); do ((sum-=i)); done
echo $sum
这将使我们最大限度地使用
2(N/3 + N/5 + N/15) = 17N/15 的加法和零次比较。虽然这是最优的,但我们仍然每个循环周期调用一个算术表达式。我们可以将其吸收到for循环中:
#!/usr/bin/env bash
read N
sum=0
for ((i=N-N%3 ; i>=3 ; sum+=i, i-=3 )); do :; done
for ((i=N-N%5 ; i>=5 ; sum+=i, i-=5 )); do :; done
for ((i=N-N%15; i>=15; sum-=i, i-=15)); do :; done
echo $sum
最后,最简单的方法是使用算术级数公式,消除所有循环。请记住,bash使用整数算术(即m = p*(m/p) + m%p
),可以这样写:
#!/usr/bin/env bash
read N
(( sum = ( (3 + N-N%3) * (N/3) + (5 + N-N%5) * (N/5) - (15 + N-N%15) * (N/15) ) / 2 ))
echo $sum
后者是最快的方式(除了数字小于15),因为它不调用任何外部二进制文件,如bc
或awk
,并且可以在没有任何循环的情况下执行任务。
awk
没有足够的整数精度,所以我不能直接使用它;我可以做的最好的事情就是用awk
预处理输入并将其管道传输到bc
;然而,这也被认为是“太慢”了。我即将得出结论,使用 bash + 标准工具无法满足约束条件。 - Fravadona