用 elixir 刷 LeetCode 的一些笔记

动态规划

最近在猛刷动态规划题,正好 leetcode 中国有“学习计划” 的功能,每天给我分配几道题的任务。

用 elixir 刷 LeetCode 的一些笔记_第1张图片

总结出了一套做动态规划的小模板。以“跳跃游戏”这道题为例

用 elixir 刷 LeetCode 的一些笔记_第2张图片

defmodule Solution do
  @spec can_jump(nums :: [integer]) :: boolean
  def can_jump([x]), do: true
  def can_jump([h | _] = nums) do

    # 首先构造初始状态,由于 List 的访问时间是 O(n) 的,我们先将其转换为 Map
    state = %{0 => h, nums: nums |> Enum.with_index() |> Enum.into(%{}, fn {v, k} -> {k, v} end)}

    # 用一个 Agent 来保存状态,因为 elixir 里面一般不使用全局变量
    Agent.start(fn -> state end, name: __MODULE__)

    r = dp(length(nums) - 1)

    # 结束后需要停止 Agent,否则状态会保存到下一个测试
    Agent.stop(__MODULE__)
    !!r
  end

  # dp 主逻辑
  defp dp(i) do
    find_and_store(i, fn %{nums: nums} -> nums[i] end, fn step -> 
      case dp(i - 1) do
        false -> false
        j ->
          if j >= i do
            max(j, i + step)
          else
            false
          end
      end
    end)
  end

  # 通用函数,计算并存储最新状态。fget 函数是在 Agent 里执行,再返回结果。
  # fdp 是状态转换的函数,其输入是 fget 的结果。
  defp find_and_store(i, fget, fdp) do
    case Agent.get(__MODULE__, fn m -> m[i] end) do
      nil -> 
        x = Agent.get(__MODULE__, fget) |> fdp.()
        Agent.update(__MODULE__, fn m -> Map.put(m, i, x) end)
        x

      x ->
        x
    end
  end
end

大家可能会说,你这样乱递归,不会爆栈吗?其实我也是战战兢兢的,生怕不用尾递归会导致爆栈,但目前还没有遇到这种情况。可能是 beam 的优化很好吧。

你可能感兴趣的:(leetcode)