Haskell:从列表中找到2个加起来为目标总和的数字

时间:2017-07-17 01:10:06

标签: haskell dynamic

我的目标:给出一个数字列表,找到与目标数字相加的第一对数字的索引。这是旧的谷歌代码堵塞挑战中给出的问题的一部分。 https://code.google.com/codejam/contest/351101/dashboard#s=p0

λ: check 14 [2,4,6,8,10]
(3,4)

NB从1

索引

我的解决方案是创建一个地图,然后迭代数字列表;在每次迭代中,检查地图中是否存在补偿(即目标 - 当前值)。如果是的话,我从地图返回补充的当前索引和索引。如果它不存在,则使用插入到map和项目列表其余部分的当前索引进行递归。

第一次尝试:

import qualified Data.HashMap as M

check :: Int -> [Int] -> (Int, Int)
check target items = go M.empty (zip [1..] items)
  where
    go m ((i,v):vs) = case M.lookup (target - v) m of
      Just x  -> (x,i)
      Nothing -> go (M.insert v i m) vs
  1. 这是我算法的正确实现吗?它适用于我的测试用例,但我觉得用Haskell编写它是不正确的。

  2. HashMap是否使用正确的数据结构?或者像Vector这样的东西会更好吗?

  3. 另外,这实际上是一个很好的算法吗?

2 个答案:

答案 0 :(得分:1)

看起来没问题。 go部分的事实有点令人不安,但问题陈述确实说应该存在解决方案。

当密钥为IntMap时,

Int应该是首选。然后Map用于其他小尺寸的键。 HashMap用于较大的键(例如String)。

不可变Vector不正确,因为更新它太贵了。可以使用可变MVector来解决问题,因为输入的值非常小(<= 2000),但这比Haskell中的当前解决方案更加尴尬。

答案 1 :(得分:1)

另一种不需要映射的方法(尽管仍然渐近地等同于OP的版本)是从两端遍历排序列表,根据当前总和向左或向右移动“指针”:

import Data.List (sort)

check t xs =
  let sxs = sort xs
  in go sxs (reverse sxs)
  where
    go ls@(l:ls') rs@(r:rs') | l < r = 
      case compare (l + r) t of
        LT -> go ls' rs
        EQ -> Just (l,r)
        GT -> go ls rs'
    go _ _ = Nothing

为简单起见,假设列表中的数字都是不同的。

更新:错过了我们追求的是索引,而不是价值。

UPDATE2:并处理非独特的案例:

check t xs =
  let sxs = sort $ zip xs [1..]
  in go sxs (reverse sxs)
  where
    go ls@((l,li):ls') rs@((r,ri):rs') | li /= ri = 
      case compare (l + r) t of
        LT -> go ls' rs
        EQ -> Just (li,ri)
        GT -> go ls rs'
    go _ _ = Nothing

或者我们可以找到所有这些:

checkAll t xs =
  let sxs = sort $ zip xs [1..]
  in go sxs (reverse sxs) []
  where
    go ls@((l,li):ls') rs@((r,ri):rs') ms | li /= ri = 
      case compare (l + r) t of
        LT -> go ls' rs ms
        EQ -> go ls' rs ((li,ri):ms)
        GT -> go ls rs' ms
    go _ _ ms = ms