haskell中3个实现的最高产品

时间:2017-11-08 18:41:37

标签: algorithm haskell math functional-programming greedy

我喜欢在haskell中实现 3 问题的最高产品的算法。这是问题陈述:

  

给定一组整数,找到可以获得的最高产品   三个整数。

例如,给定[1, 2, 3, 4],算法应返回24。鉴于[-10, -10, 5, 1, 6],3的最高乘积为600 = -10*-10*6

我的尝试(第一次尝试没有否定):

sol2' a b c []     = a*b*c
sol2' a b c (x:xs) = sol2' a' b' c' xs
  where 
    a' = if (x > a) then x else a
    b' = if (x > a && a > b) then a else b
    c' = if (x > a && a > b && b > c) then b else c

sol2 li = sol2' a b c li
  where a = 0
        b = 0 
        c = 0

我使用[3, 5, 1, 2, 4, 10, 0, 4, 8, 11]测试了实现,但返回值为550,应该是880

2 个答案:

答案 0 :(得分:5)

正面数字

在寻找最高数字的意义上,你走在正确的轨道上。但问题是 a b c 并不总是有序。

确实说我们有数字[6,2,4]。然后(a,b,c)通过递归演变的方式是:

(0,0,0) -> (6,0,0) -> (2,6,0) -> (4,2,6)

但现在a=4,这意味着如果我们现在遇到3,我们将替换该值,而我们可以这样做,因为我们可以删除{ {1}}。

虽然有很多方法可以解决这个问题,但最好的方法是维护订单:确保2

所以我们可以使用:

a <= b <= c

这产生了预期的:

sol1 = sol2' (0,0,0)

sol2' (a,b,c) []     = a*b*c
sol2' t@(a,b,c) (x:xs) = sol2' f xs
  where f | x >= c = (b,c,x)
          | x >= b = (b,x,c)
          | x > a = (x,b,c)
          | otherwise = t

Intermezzo:如果存在负数,则跟踪数字

您的程序首先将Prelude> sol1 [1,2,3,4] 24 Prelude> sol1 [3, 5, 1, 2, 4, 10, 0, 4, 8, 11] 880 作为前三个值。但是,例如,如果列表仅包含负数(即(0,0,0)),我们当然希望首先跟踪这些数字。我们可以通过使用列表中的元素初始化元组来实现此目的:

[-1,-2,-3]

所以现在我们采用前三个元素,对它们进行排序,并将它们用作第一个元组。处理剩余的列表。如果import Data.List(sort) sol1 (xa:xb:xc:xs) = sol2' (a,b,c) xs where [a,b,c] = sort [xa,xb,xc] 没有给出至少包含三个元素的列表,则此函数将出错,但在这种情况下可能没有答案。我们可以使用sol1来处理函数非全部的事实。

所有号码

当然,我们也想处理负数。将两个负数相乘得到正数。因此,通过跟踪两个最小的数字,我们可以正确地进行数学运算。首先,我们将使用另一个参数Maybe来跟踪(d,e)的最小数字:

d <= e

所以现在我们获得了最大数字sol1_all = sol2_all' (0,0,0) (0,0) sol2_all' (a,b,c) (d,e) [] = -- ... sol2_all' t@(a,b,c) u@(d,e) (x:xs) = sol2_all' f g xs where f | x >= c = (b,c,x) | x >= b = (b,x,c) | x > a = (x,b,c) | otherwise = t g | x <= d = (x,d) | x <= e = (d,x) | otherwise = u 和最小数字(a,b,c)。如果(d,e)d确实是否定的,则产生大的唯一方法。现在,我们有以下可能性来考虑ea*b*c。所以我们可以把它写成:

c*d*e

但请注意,这并不总是会产生正确的结果,因为我们可以计算两个元组中的两个数字。我们可以通过正确地初始化元组来解决这个问题:

sol2_all' (a,b,c) (d,e) [] = max (a*b*c) (c*d*e)
sol2_all' t@(a,b,c) u@(d,e) (x:xs) = sol2_all' f g xs
  where f | x >= c = (b,c,x)
          | x >= b = (b,x,c)
          | x > a = (x,b,c)
          | otherwise = t
        g | x <= d = (x,d)
          | x <= e = (d,x)
          | otherwise = u

选择不同(可能等效)元素

的基本原理

我们怎么知道我们不会两次使用元素?由于我们只使用import Data.List(sort) sol1_all (xa:xb:xc:xs) = sol2_all' (a,b,c) (a,b) xs where [a,b,c] = sort [xa,xb,xc] sol2_all' (a,b,c) (d,e) [] = max (a*b*c) (c*d*e) sol2_all' t@(a,b,c) u@(d,e) (x:xs) = sol2_all' f g xs where f | x >= c = (b,c,x) | x >= b = (b,x,c) | x > a = (x,b,c) | otherwise = t g | x <= d = (x,d) | x <= e = (d,x) | otherwise = u a*b*c,因此,如果是包含三个元素的列表,则会归结为c*d*emax(a*b*c,a*b*c)ab这里是c)的结果。所以保证了独特性。由于我们只会在第一个元组中添加元素,如果它们至少大于sort且小于a,我们知道为了在两个元组中添加b ,它应该是x。在这种情况下,我们将获得元组a <= x <= b(x,b,c)。但是,由于我们在这种情况下评估(a,x)x*b*c,因此a*x*c不会在任何表达式中出现两次。

Leetcode 挑战

我向Leetcode Challenge提交了此代码的Python版本,并且已被接受:

x

答案 1 :(得分:1)

有一些更有效的解决方案,但我倾向于更直接的事情,如:

import Data.List (subsequences)
f :: (Num a, Ord a) => [a] -> a
f = maximum . map product . filter ((==3) . length) . subsequences

将函数算法视为集合上的变换序列使得它们比将命令式循环转换为递归函数更具惯用性。

请注意,如果您使用非常长的列表来执行此操作,您可以先对列表进行排序,然后选择最低的两个和最高的三个,算法仍然有效:

takeFirstLast xs = (take 2 sorted) ++ (drop (length sorted - 3) sorted)
  where sorted = sort xs

然而,我原来的方式很快就达到了大约100个左右的列表,而且很多更容易理解。我不相信牺牲速度的可读性,直到我被告知这是一个实际的要求。