我的目标:给出一个数字列表,找到与目标数字相加的第一对数字的索引。这是旧的谷歌代码堵塞挑战中给出的问题的一部分。 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
这是我算法的正确实现吗?它适用于我的测试用例,但我觉得用Haskell编写它是不正确的。
HashMap是否使用正确的数据结构?或者像Vector这样的东西会更好吗?
另外,这实际上是一个很好的算法吗?
答案 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