掘金团队号上线,助你 Offer 临门! 点击 查看详情
三角形最小路径和(题号120)
题目
给定一个三角形 triangle
,找出自顶向下的最小路径和。
每一步只能移动到下一行中相邻的结点上。相邻的结点 在这里指的是 下标 与 上一层结点下标 相同或者等于 上一层结点下标 +1 的两个结点。也就是说,如果正位于当前行的下标 i
,那么下一步可以移动到下一行的下标 i
或 i + 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
为三角形的总行数)来解决这个问题吗?
链接
解释
这题可是真的费劲,想了一天都没有什么解决方案,最后一经提点才发现原来就是简单的动态规划,只是我存储数据存储错了位置,才导致花了那么久的时间。
其实思路已经对了,就是不知道怎么存储数据,一步之差。
自己的答案(简单递归)
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:想查看往期文章和题目可以点击下面的链接:
这里是按照日期分类的👇
经过有些朋友的提醒,感觉也应该按照题型分类
这里是按照题型分类的👇
有兴趣的也可以看看我的个人主页👇