一天一大 leet(地下城游戏)难度:困难-Day20200712

img

题目:

一些恶魔抓住了公主(P)并将她关在了地下城的右下角。地下城是由 M x N 个房间组成的二维网格。我们英勇的骑士(K)最初被安置在左上角的房间里,他必须穿过地下城并通过对抗恶魔来拯救公主。

骑士的初始健康点数为一个正整数。如果他的健康点数在某一时刻降至 0 或以下,他会立即死亡。

有些房间由恶魔守卫,因此骑士在进入这些房间时会失去健康点数(若房间里的值为负整数,则表示骑士将损失健康点数);其他房间要么是空的(房间里的值为 0),要么包含增加骑士健康点数的魔法球(若房间里的值为正整数,则表示骑士将增加健康点数)。

为了尽快到达公主,骑士决定每次只向右或向下移动一步。

编写一个函数来计算确保骑士能够拯救到公主所需的最低初始健康点数。

例如,考虑到如下布局的地下城,如果骑士遵循最佳路径 右 -> 右 -> 下 -> 下,则骑士的初始健康点数至少为 7。

-2(k)-33
-5-101
1030-5§
说明
  • 骑士的健康点数没有上限。
  • 任何房间都可能对骑士的健康点数造成威胁,也可能增加骑士的健康点数,包括骑士进入的左上角房间以及公主被监禁的右下角房间。

抛砖引玉

img

看完题目想到了做过的机器人那题:不同路径
不同路径是障碍,而本题是记录每个路过节点的值


思路

  • 最低初始需要到终点前是没有多余的值即1
  • 现在问题变成了知道结束值1,推到起始值了
  • 逻辑反转,从结束值没到一个单元格减去本单元格的值直到推导到起点
  • 到达一个点只可能从左侧或者上面进入则公式为:
    cur[i - 1][j]-dungeon[i][j] cur[i][j-1]-dungeon[i][j]
  • 可能有不同的路径到达终点前值都为1,只取最小值

实现

  • 因为迭代过程中每一个[i][j]的变化都会生成一个新的路线那么默认矩阵中每个节点的值都为 1,代表一种可能
/**
 * @param {number[][]} dungeon
 * @return {number}
 */
var calculateMinimumHP = function (dungeon) {
  let m = dungeon.length,
      n = dungeon[0] ? dungeon[0].length : 0,
      cur = Array(m+1);
  // 初始化与dungeon对应数组,记录到达某个单元格是剩余数量
  for(let i = 0;i<m+1;i++){
      cur[i]= Array(n+1).fill(Number.MAX_VALUE)
  }
  // 终点前默认剩余1
  cur[m][n - 1] = cur[m - 1][n] = 1;
  for (let i = m-1; i >= 0; i--) {
      for (let j = n-1; j >= 0; j--) {
        // 本单元格入口剩余的最小值
        let itemMin = Math.min(cur[i + 1][j], cur[i][j + 1]);
        cur[i][j] = Math.max(itemMin - dungeon[i][j], 1);
      }
  }
  return cur[0][0]
}

存储对象cur降维

/**
 * @param {number[][]} dungeon
 * @return {number}
 */
var calculateMinimumHP = function (dungeon) {
  let m = dungeon.length,
      n = dungeon[0] ? dungeon[0].length : 0,
      cur = new Array(n + 1).fill(Number.MAX_VALUE);
  cur[n - 1] = 1;
  for (let i = m - 1; i >= 0; --i) {
    for (let j = n - 1; j >= 0; --j) {
      let itemMin = Math.min(cur[j], cur[j + 1]);
      cur[j] = Math.max(1, itemMin - dungeon[i][j]);
    }
  }
  return cur[0];
};

其他解法

  • 使用递归完成双层循环的路线选择
  • 上面通过Math.min之间选择了单元格入口方向
  • 下面通过更直观的方式去检查每个入口的剩余值
  • 使用递归每次都需要查询入口值,理论上就可以省略存储结果的对象cur
    但是,每次都查询就会有效率问题,索引参考上面思路依旧使用cur记录已经查询到的结果
/**
 * @param {number[][]} dungeon
 * @return {number}
 */
var calculateMinimumHP = function (dungeon) {
  let m = dungeon.length,
      n = dungeon[0] ? dungeon[0].length : 0,
      cur = new Array(m);

  // 初始化,每一项都为0,代表还没记录
  for (let i = 0; i < m; i++) {
    cur[i] = Array(n).fill(0);
  }

  function getMin(dungeon, i, j){

     // 递归的出口
    if (i == m - 1 && j == n - 1) {
      return dungeon[i][j] > 0 ? 1 : 1 - dungeon[i][j];
    }

    // 如果备忘录中有,就直接返回它
    if (cur[i][j] > 0) return cur[i][j]; 

    let down = Number.MAX_VALUE, 
        right = Number.MAX_VALUE;

    // 走下方的点,需要带着的最小安全血量
    if (i < m - 1) down = getMin(dungeon, i + 1, j);
    // 走右方的点,需要带着的最小安全血
    if (j < n - 1) right = getMin(dungeon, i, j + 1);

    if (down < right) {
      if (down - dungeon[i][j] <= 0) {
        cur[i][j] = 1;
      } else {
        cur[i][j] = down - dungeon[i][j];
      }
    } else {
      if (right - dungeon[i][j] <= 0) {
        cur[i][j] = 1;
      } else {
        cur[i][j] = right - dungeon[i][j];
      }
    }
    return cur[i][j];
  };

  return getMin(dungeon, 0, 0, cur);
};

博客: 小书童博客

公号: 坑人的小书童

坑人的小书童

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
探险家小扣的行动轨迹,都将保存在记录仪中。expeditions[i] 表示小扣第 i 次探险记录,用一个字符串数组表示。其中的每个「营地」由大小写字母组成,通过子串 -> 连接。例:"Leet->code->Campsite",表示到访了 "Leet"、"code"、"Campsite" 三个营地。expeditions[0] 包含了初始小扣已知的所有营地;对于之后的第 i 次探险(即 expeditions[i] 且 i > 0),如果记录中包含了之前均没出现的营地,则表示小扣 新发现 的营地。 请你找出小扣发现新营地最多且索引最小的那次探险,并返回对应的记录索引。如果所有探险记录都没有发现新的营地,返回 -1。注意: 大小写不同的营地视为不同的营地; 营地的名称长度均大于 0。用python实现。给你几个例子:示例 1: 输入:expeditions = ["leet->code","leet->code->Campsite->Leet","leet->code->leet->courier"] 输出:1 解释: 初始已知的所有营地为 "leet" 和 "code" 第 1 次,到访了 "leet"、"code"、"Campsite"、"Leet",新发现营地 2 处:"Campsite"、"Leet" 第 2 次,到访了 "leet"、"code"、"courier",新发现营地 1 处:"courier" 第 1 次探险发现的新营地数量最多,因此返回 1。示例 2: 输入:expeditions = ["Alice->Dex","","Dex"] 输出:-1 解释: 初始已知的所有营地为 "Alice" 和 "Dex" 第 1 次,未到访任何营地; 第 2 次,到访了 "Dex",未新发现营地; 因为两次探险均未发现新的营地,返回 -1
04-23
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值