找到三个整数,使它们的余弦值之和成为最大值

Finding three integers such that their sum of cosine values become max

有三个整数 xyz(每个都 >= 1)和给定的上限整数 n < 10^6。此外,n = x + y + zoutput = cos(x) + cos(y) + cos(z).

练习是最大化output

我为此写了一个简单的脚本,但是时间复杂度是O(n^3)。有什么办法可以简化这个吗?

from math import cos

n = 50
x = 1
y = 1
z = 1

total = cos(x) + cos(y) + cos(z)

for x in xrange(n):
    for y in xrange(n):
        for z in xrange(n):
            if x + y + z == n:
                temp = cos(x) + cos(y) + cos(z)
                if temp > total: total = temp

print round(total, 9) 

这纯粹是一个基本的三角函数问题。当所有余弦的每个值为 1 时,方程的最大值将是。在 cos(n) 中,其中 n 是任何数字,对于由 n = 2 * pi * k 的集合形成的所有值,其中 k >= 0 且 k 为整数;您的余弦值将为 1。您的 x、y、z 值属于此集合,这些值的排列将为您提供所需的值。 另外,不要忘记检查集合中的 n 是否为整数以减少样本 space。

正如 Jean-François Fabre 在评论中指出的那样,您可以应用很多技巧来提高性能,但首先

  • 注意到 ab 的值决定了 c
  • 的值
  • 注意到三个变量中至少有一个 WLOG a 小于或等于 N/3,
  • 使用 bc 中剩余的对称性将 b 限制在 a(N - a)//2 + 1
  • 之间
  • 预先计算 cos 的所有相关值,并尽量避免快速连续查找相同的值,
  • cos(a) 的给定值永远不会导致新的最大值时,修剪外循环以提前停止,
  • 使用 Numba 对代码进行 JIT 编译并免费获得一些性能(N = 500 的大约 400 倍),

然后对于 N = 1000000:

的其他暴力解决方案相对较快地终止
import numpy as np
from numba import jit

@jit
def maximize(N):
    cos = np.cos(np.arange(N))
    m = -3
    for a in range(1, N//3 + 1):
        cosa = cos[a]
        if m - 2 > cosa:
            continue
        for b in range(a, (N - a)//2 + 1):
            c = N - a - b
            res = cosa + cos[b] + cos[c]
            if res > m:
                m = res
                bestabc = (a, b, c)
    return m, bestabc

maximize(1000000)  # (2.9787165245899025, (159775, 263768, 576457))

值得注意的是,上面利用的对称性只适用于愿意忽略数值问题导致浮点数的加法通常不可交换的事实;即 cos(a) + cos(b) 不必与 cos(b) + cos(a) 相同。不过,您很可能不会为此担心。

理想情况下,您只想计算每个可能的组合一次。忽略 cos 的几何属性,并将其视为从数字到数字的简单映射(例如,将其用作随机 属性,正如@Jean 在他的第二条评论中提到的那样)。
首先,您必须意识到,在选择 2 个号码后,会给出第三个号码。您可以选择 'smart' 以避免重复选择:

from math import cos
import time
import numpy as np
from numba import jit



def calc(n):
    x = 1
    y = 1
    z = 1
    total = cos(x) + cos(y) + cos(z)
    for x in range(n, int((n/3 - 1)),-1): #I only want to pick X from n-2 to  n/3 -1 , after that we will repeat.
        cosx = cos(x)
        for y in range(max(int(((n-x)/2))-1,1),min(int(n-x),int(n/3))): #I would only pick number that will not be choosen for the z
                z = n-x-y #Infer the z, taking the rest in account
                temp = cosx + cos(y) + cos(z)
                if temp > total: total = temp
    return total

tic = time.clock()
total = calc(10000)
print(time.clock()-tic)

print (total)

将占用 1.3467099999999999(在我的机器上)。
正如@fuglede 提到的,值得使用 numba 进行进一步优化。

编辑: 保存所有先前计算的 cos 值实际上比重新计算它们更昂贵,当您访问 np 数组时,您不是简单地访问内存中的一个点,而是使用 ndarray 函数。使用 python 内置 cos 实际上更快:

import numpy as np

from math import cos
import time
import timeit

cos_arr = np.cos(np.arange(10000000))
tic = time.time()

def calc1():
    total = 0
    for j in range(100):
        for i in range(10000000):
            total += cos_arr[i]

def calc2():
    total = 0
    for j in range(100):
        for i in range(10000000):
            total += cos(i)

time1 = timeit.Timer(calc1).timeit(number=1)

time2 = timeit.Timer(calc2).timeit(number=1)
print(time1)
print(time2)

输出:

127.9849290860002
108.21062094399986

如果我将数组创建移动到计时器内,它会更慢。

完全不需要计算3 x n^3余弦值。

我们可以假设 x ≤ y ≤ z。因此 x 可以是 1 到 n/3 范围内的任意整数。 y 可以是 x 到 (n - x) / 2 范围内的任何整数。并且 z 必须等于 n - x - y。仅此一项就可以将您尝试的三元组 (x, y, z) 的数量从 n^3 减少到大约 n^2 / 6。

接下来假设您找到了三个数字的总和为 2.749。然后您尝试使用余弦 (x) = 0.748 的 x。涉及这个 x 的任何总和都不能超过 2.748,所以你可以直接拒绝 x。一旦找到一个合适的和,就可以拒绝 x 的多个值。

为了使其更有效,您将值 x 从 cosine(x) 的最高值到最低值排序,因为这使您更有可能找到一个高总数,从而允许您删除更多值。

并且计算 cos(x) 很慢,因此您将值存储到 table 中。

所以:

Set c[i] = cos (i) for 1 ≤ i ≤ n. 
Set x[i] = integers 1 to n/3, sorted in descending order by value of c[i]. 
Set (bestx, besty, bestz) = (1, 1, n-2) and total = c[bestx] + c [besty] + c [bestz].

for x = elements of array x where c[x] + 2 ≥ bestTotal
    for y = x to (n-x)/2
        z = n - x - y
        total = c[x] + c[]y] + c[z]
        if total > bestTotal
            (bestx, besty, bestz) = (x, y, z)
            bestTotal = total

你可以通过一点数学来改进这一点。如果 y + z 的和是常数,就像这里 y + z = n - x,cos(y) + cos (z) 的和是有限的。令P为最接近(n-x)/2pi的整数,令d=(n-x)-P*2pi,则cos(y)+cos(z)的最大可能和为2*cos(d/2).

所以对于每个x,1≤x≤n/3,我们计算这个值d和cos(x)+2*cos(d/2),将这些值存储为最大总和可以用一些 x 来实现,对 x 进行排序,使这些值按降序排列,并忽略那些可实现的总数小于迄今为止最佳总数的 x。

如果n很大(比如十亿),那么可以用欧几里德算法快速找到所有接近2k*pi + d的整数y,但是会有点复杂。

for x in 1 to n/3
    let s = n - x
    let P = s / 2pi, rounded to the nearest integer
    let d = (s - P * 2pi) / 2
    let maxSum [x] = cos(x) + 2*cos(d)

Set x[i] = integers 1 to n/3, sorted in descending order by value of maxSum[i]. 
Set (bestx, besty, bestz) = (1, 1, n-2)
Set bestTotal = c[bestx] + c [besty] + c [bestz].

for x = elements of array x where maxSum[x] ≥ bestTotal
    for y = x to (n-x)/2
        z = n - x - y
        total = c[x] + c[]y] + c[z]
        if total > bestTotal
            (bestx, besty, bestz) = (x, y, z)
            bestTotal = total

PS。我实际上尝试了这个 N 的一些值大约 1 亿。事实证明,我可以对数组进行排序以首先尝试 x 的最有希望的值,这需要很长时间,但通常 x 的第一个值是唯一被尝试的值。或者我可以使用 x = 1、2、3 等,这意味着将尝试 x 的几十个值,这比排序更快。

无需计算余弦即可回答此问题。只需跟踪函数的三个(如果允许 n=0 则为两个)最小值 f(n) = abs(2pi*n-round(2pi*n)) n 从 1 到 N,其中 N 是您的搜索上限。

余弦在 2*pi 的倍数处为 1,因此我们在搜索范围内搜索最接近整数的两个或三个倍数。

还没有 运行 程序来执行此操作,但在任何编程语言中都应该很容易。我会用 Mathematica。