用 elixir 刷 LeetCode 的一些筆記

Ljzn發表於2022-02-25

動態規劃

最近在猛刷動態規劃題,正好 leetcode 中國有“學習計劃” 的功能,每天給我分配幾道題的任務。

image.png

總結出了一套做動態規劃的小模板。以“跳躍遊戲”這道題為例

image.png

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 的優化很好吧。

相關文章