xxleyi / loop_invariants

记录以及整理「循环不变式」视角下的算法题解

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

leetcode 1345. 跳跃游戏 IV

xxleyi opened this issue · comments

题:

给你一个整数数组 arr ,你一开始在数组的第一个元素处(下标为 0)。

每一步,你可以从下标 i 跳到下标:

i + 1 满足:i + 1 < arr.length
i - 1 满足:i - 1 >= 0
j 满足:arr[i] == arr[j] 且 i != j
请你返回到达数组最后一个元素的下标处所需的 最少操作次数 。

注意:任何时候你都不能跳到数组外面。

示例 1:

输入:arr = [100,-23,-23,404,100,23,23,23,3,404]
输出:3
解释:那你需要跳跃 3 次,下标依次为 0 --> 4 --> 3 --> 9 。下标 9 为数组的最后一个元素的下标。
示例 2:

输入:arr = [7]
输出:0
解释:一开始就在最后一个元素处,所以你不需要跳跃。
示例 3:

输入:arr = [7,6,9,6,9,6,9,7]
输出:1
解释:你可以直接从下标 0 处跳到下标 7 处,也就是数组的最后一个元素处。
示例 4:

输入:arr = [6,1,9]
输出:2
示例 5:

输入:arr = [11,22,7,7,7,7,7,7,7,22,13]
输出:3
 
提示:

1 <= arr.length <= 5 * 10^4
-10^8 <= arr[i] <= 10^8

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/jump-game-iv
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。


解:

此题和之前的 leetcode 1306. 跳跃游戏 III 一脉相承。

首先,我们应该知道这种求最小次数,也就是最短路径的问题,应该使用「广度优先遍历」

然后,就是按照题目要求用一个队列和若干辅助变量(类似图)组织遍历次序。只要在入队时记录好当前的步数,则抵达目标时的步数就是我们的答案。

「循环不变式」一模一样,不一样的只是几个小细节。

var minJumps = function(arr) {
  // initialize graph
  let sameValue = {}

  for (let [i, e] of arr.entries()) {
    if (e in sameValue) sameValue[e].add(i)
    else sameValue[e] = new Set([i])
  }

  // initialize our target position
  let target = arr.length - 1
  // initialize loop invariant related variable queue
  let queue = Array(arr.length)
  // initialize loop invariant related variables head and tail pointer
  let head = tail = 0
  // initialize loop invariant relate variable doneSet
  let doneSet = new Set()
  // update variables to maintain loop invariant before loop
  queue[head] = [0, 0]
  doneSet.add(0)

  function enqueue(j, step) {
    queue[++tail] = [j, step]
    doneSet.add(j)
  }

  while (queue[head] != null) {
    // dequeue to maintain loop invariant
    let [i, step] = queue[head++]
    // termination 1: reach at end position
    if (i === target) return step

    // enqueue new positions to maintain loop invariant
    for (let j of sameValue[arr[i]]) {
      if (i !== j && !doneSet.has(j)) enqueue(j, step + 1)
    }
    // set of arr[i] has all been enqueued, so should be dropped to avoid repeated iteration
    sameValue[arr[i]] = new Set()

    // enqueue new positions to maintain loop invariant
    for (let j of [i - 1, i + 1]) {
      if (j >= 0 && j < arr.length && !doneSet.has(j)) enqueue(j, step + 1)
    }
  }
  
  // termination 2: should not happen
  throw Error("can not reach at end.")
};