前端刷题路-Day22|刷题打卡

204 阅读1分钟

掘金团队号上线,助你 Offer 临门! 点击 查看详情

三角形最小路径和(题号120)

题目

给定一个三角形 triangle ,找出自顶向下的最小路径和。

每一步只能移动到下一行中相邻的结点上。相邻的结点 在这里指的是 下标 与 上一层结点下标 相同或者等于 上一层结点下标 +1 的两个结点。也就是说,如果正位于当前行的下标 i ,那么下一步可以移动到下一行的下标 ii + 1

示例 1:

输入:triangle = [[2],[3,4],[6,5,7],[4,1,8,3]]
输出:11
解释:如下面简图所示:
   2  
  3 4  
 6 5 7  
4 1 8 3  
自顶向下的最小路径和为 11(即,2 + 3 + 5 + 1 = 11)。  

示例 2:

输入:triangle = [[-10]]  
输出:-10  

提示:

  • 1 <= triangle.length <= 200
  • triangle[0].length == 1
  • triangle[i].length == triangle[i - 1].length + 1
  • -104 <= triangle[i][j] <= 104

进阶:

你可以只使用 O(n) 的额外空间(n 为三角形的总行数)来解决这个问题吗?

链接

leetcode-cn.com/problems/tr…

解释

这题可是真的费劲,想了一天都没有什么解决方案,最后一经提点才发现原来就是简单的动态规划,只是我存储数据存储错了位置,才导致花了那么久的时间。

其实思路已经对了,就是不知道怎么存储数据,一步之差。

自己的答案(简单递归)

var minimumTotal = function(triangle) {
  if (triangle.length === 1) return triangle[0]
  var res = []
  findNum(0, 0, triangle[0][0])
  function findNum(i, j, num) {
    if (i + 1 === triangle.length ) return res.push(num)
    findNum(i + 1, j, num + triangle[i + 1][j])
    findNum(i + 1, j + 1, num + triangle[i + 1][j + 1])
  }
  return res.sort((a, b) => a - b)[0]
};

确实很简单,但递归次数太多,会直接超时,无法采用

自己的答案(正序循环+记忆化)

var minimumTotal = function(triangle) {
  if (triangle.length === 1) return triangle[0]
  var board = []
  var res = 0
  for (let i = 0; i < triangle.length; i++) {
    var tmp = new Array(triangle[i].length)
    for (let j = 0; j < triangle[i].length; j++) {
      if (board.length === 0) {
        tmp[j] = [triangle[i][j]]
      } else {
        if (board[j]) {
          for (let k = 0; k < board[j].length; k++) {
            if (!tmp[j]) tmp[j] = []
            if (i === triangle.length - 1) {
              res = res === 0 ? triangle[i][j] + board[j][k] : triangle[i][j] + board[j][k] < res ? triangle[i][j] + board[j][k] : res
            } else {
              tmp[j].push(triangle[i][j] + board[j][k])
            }
          } 
        }
        if (board[j - 1]) {
          for (let k = 0; k < board[j - 1].length; k++) {
            if (!tmp[j]) tmp[j] = []
            if (i === triangle.length - 1) {
              res = res === 0 ? triangle[i][j] + board[j - 1][k] : triangle[i][j] + board[j - 1][k] < res ? triangle[i][j] + board[j - 1][k] : res
            } else {
              tmp[j].push(triangle[i][j] + board[j - 1][k])
            }
          }
        }
      }
    }
    board = tmp
  }
};

其实这种方法已经比较接近正确答案了,因为已经想到了当前列的数据与下一列对应数据相加。不过没有想到可以直接取下一列数据中比较小的那个值,导致GG。

而且这种方法设计的逻辑过于复杂,不仅仅代码看上去很多,同时推荐当前列与下一列的关系时也十分复杂,数学不好的笔者越想越晕,简直不能自已。

自己的答案(倒序循环+记忆化)

var minimumTotal = function(triangle) {
  if (triangle.length === 1) return triangle[0]
  var res = 0
  var board = []
      row = triangle.length
  for (let i = 0; i < row; i++) {
    var index = row - i - 1
        tmp = []
    for (let j = 0; j < triangle[index].length; j++) {
      if (index === row - 1) {
        tmp[j] = [triangle[index][j]]
      } else {
        console.log(board)
        for (let k = 0; k < board[j].length; k++) {
          if (!tmp[j]) tmp[j] = []
          tmp[j].push(triangle[index][j] + board[j][k])
        }
        for (let k = 0; k < board[j + 1].length; k++) {
          tmp[j].push(triangle[index][j] + board[j + 1][k])
        }
      }
    }
    board = tmp
  }
  console.log(board)
};

正序不行就倒序吧,但是记忆方法是和顺序的记忆方法一样,导致即使倒序了也不好使,同样GG。

自己的答案(动态规划+替代原数组)

var minimumTotal = function(triangle) {
  if (triangle.length === 1) return triangle[0]
  var board = []
      row = triangle.length
  for (let i = 0; i < row; i++) {
    var index = row - i - 1
        tmp = []
    for (let j = 0; j < triangle[index].length; j++) {
      tmp[j] = (Math.min(board[j + 1], board[j]) || 0) + triangle[index][j]
    }
    board = tmp
  }
  return board[0]
};

这是经过提点之后的答案了,其实当听到可以直接取两个数中比较小的那个时就知道方法了,之后一顿操作猛如虎,写出了这段有点丑陋但胜在好用的代码。

这里其实有点冗余了,为了避免二维数组,而使用tmp不断替代board,这样做无疑浪费了一些读写性能,也占用了一些空间。于是试了试二维数组,发现性能相差无几。

自己的答案(动态规划+数组降维)

var minimumTotal = function(triangle) {
  var board = triangle[triangle.length - 1]
  for (let i = triangle.length - 2; i > -1 ; i--) {
    for (let j = 0; j < triangle[i].length; j++) {
      board[j] = Math.min(board[j + 1], board[j]) + triangle[i][j]
    }
  }
  return board[0]
};

这里就给了一个一维数组,因为每次只能加i或者i-1的元素,所以不用担心会覆盖掉board中仍需使用的元素。

这样每次只需要取下层中可以使用的元素中的最小值,再和当前元素相加,直接替换到board中的数据即可,那么到最后board的第一个元素就是答案。

只是这样做需要在一开始给board赋值为triangle的最下层元素,每次修改board中的数据会越来越少,但这些多余的数据并没有被去掉,感觉有些占用空间。

自己的答案(动态规划+修改原数组)

既然👆这种方法会占用一些没必要的空间,那直接在triangle上修改不就好了?

事实证明的确可以,代码量进一步减少:

var minimumTotal = function(triangle) {
  for (let i = triangle.length - 2; i > -1 ; i--) {
    for (let j = 0; j < triangle[i].length; j++) {
      triangle[i][j] += Math.min(triangle[i + 1][j + 1], triangle[i + 1][j])
    }
  }
  return triangle[0][0]
};

但这种做法会修改原数组的值,有些情况下并不推荐使用。

上面基本就是这题我的全部思考过程了,过程比较久,但在点破记忆化的时候就已经是优化代码了,可以看到,从一开始的二十多行代码浓缩到最后的6行,果然是学无止境啊

更好的方法



PS:想查看往期文章和题目可以点击下面的链接:

这里是按照日期分类的👇

前端刷题路-目录(日期分类)

经过有些朋友的提醒,感觉也应该按照题型分类
这里是按照题型分类的👇

前端刷题路-目录(题型分类)

有兴趣的也可以看看我的个人主页👇

Here is RZ