120. Triangle
Given a triangle, find the minimum path sum from top to bottom. Each step you may move to adjacent numbers on the row below.
For example, given the following triangle
[
[2],
[3,4],
[6,5,7],
[4,1,8,3]
]The minimum path sum from top to bottom is 11 (i.e., 2 + 3 + 5 + 1 = 11).
Note:
Bonus point if you are able to do this using only O(n) extra space, where n is the total number of rows in the triangle.
题意
给定一个三角形,找到一个最小路径和从顶部到底部,每一次只能移动到下一行相邻的元素。
空间复杂度能是O(n)
思路1
自底向上。动态规划。
求解从上到下最小路径和,那么由底部将每一层到达当前位置的最小路径计算出来,计算方法底层相邻元素取小的并和当前位置元素相加,层层计算,那么最顶层就是最小路径和。
- k层中第i个位置最小路径和
minPath[k][i] = min(minPath[k+1][i]+minPath[k+1][i+1])+triangle[k][i]
代码1
class Solution {
public:
int minimumTotal(vector<vector<int>>& triangle) {
vector<vector<int>> minLen(triangle);
//用于保存每一层的最小路径和
//从倒数第二层开始判断
for (int layer = triangle.size() - 2; layer >= 0; layer--)
{
for (int i = 0; i < triangle[layer].size(); i++)
{
minLen[layer][i] = min(minLen[layer+1][i], minLen[layer+1][i + 1]) + triangle[layer][i];
}
}
return minLen[0][0];
}
};
- 代码优化,将空间复杂度降低。
- 保存当前行各个位置的最小路径和,到下一次计算可以被边计算边覆盖,节省空间。
- minPath[i] = min(minPath[i]+minPath[i+1])+triangle[k][i]
class Solution {
public:
int minimumTotal(vector<vector<int>>& triangle) {
int res = 0;
vector<int> minLen(triangle.back()); //行内最多元素为三角形最底层
for(int layer=triangle.size()-2; layer >= 0; layer--)
{
for(int i=0;i<triangle[layer].size();i++)
{
minLen[i] = min(minLen[i],minLen[i+1])+triangle[layer][i];
}
}
return minLen[0];
}
};
思路2
自顶向下,并未使用递归结构。
特除处理是处理每一行中第一个元素:没有左子树为无限大,最后一个元素:没有右子树为无限大。其模拟的结果就是,到达两侧元素的路径最小和就是上一行的第一个元素(最后一个元素)+当前元素。
非两侧的元素,就需要比较相邻的两个元素选择小的一个+当前元素。
就像一颗倒立的二叉树
空间复杂度使用的是三角形数组本身。记忆累加,各个位置存储的都是到达当前位置的最小路径和。
最终将三角形数组的最后一行排序,输出最小值,即为从顶到底的最小路径和。
代码2
- triangle[i][j] = triangle[i][j] + min(triangle[i-1][j-1],triangle[i-1][j]);
class Solution {
public:
int minimumTotal(vector<vector<int>>& triangle) {
for(int i=1;i<triangle.size();i++)
{
for(int j=0;j<triangle[i].size();j++)
{
int left = 9999999;
int right = 9999999;
if(j>0) //第一个元素没有左子树,
{
left = triangle[i-1][j-1];
}
if(j<triangle[i].size()-1) //最后一个元素没有右子树
{
right = triangle[i-1][j];
}
//非两侧元素
triangle[i][j] = triangle[i][j] + min(left,right);
}
}
sort(triangle.back().begin(), triangle.back().end());
return triangle.back()[0];
}
};
思路3
自顶向下的另外一种写发,较思路2较复杂。
其实是思路2是思路3的一种优化,将两侧元素和其余元素统一处理,不需要额外空间。
代码3
class Solution {
public:
int minimumTotal(vector<vector<int>>& triangle) {
vector<int> minLen(triangle.back().size(), triangle[0][0]);
//一开始的初始值要确定好了,是必须的等于第一个元素,默认将第一行的值作为初始值
//for循环,一开始从第二行开始,内层for循环必须使用逆序,因为在进行minLen操作,顺序会将之前的值覆盖,只有逆序将上次的结果使用之后
for (int i = 1; i < triangle.size();i++) //行
{
/*
for (int j = 0; j < triangle[i].size(); j++) //行中有多少元素
{
if (j == 0)
//直接赋值是行不通的,因为上一次的加结果并未参与到下一行加的计算中,需要+=
//minLen[j] += triangle[i-1][0]+triangle[i][j];
minLen[j] += triangle[i][j]; //将上一次的累加结果+当前元素的值,即为当前路径的值
else if (j == triangle[i].size() - 1)
minLen[j] += triangle[i][j]; //处理三角形两侧的元素
else
minLen[j] = min(minLen[j-1], minLen[j]) + triangle[i][j];
}
*/
for (int j=triangle[i].size()-1;j>=0;j--) //逆序,否则数据还未被使用,就会被覆盖
{
if (j == 0)
minLen[j] += triangle[i][j]; //第一个元素可以加直接 + minLen[j] 是因为历史值就存储在minLen[0]中,
else if (j == triangle[i].size() - 1)
minLen[j] = triangle[i][j] + minLen[j - 1]; //最后的元素必须是+minLen[j-1],这是历史存储的结果,如果是+minLen[j]那么就是一个初始值为第一行第一个元素
else
minLen[j] = triangle[i][j] + min(minLen[j - 1], minLen[j]); //更新这一行相同坐标的值,使用了上一行的minLen[j-1],[j]值
}
}
sort(minLen.begin(), minLen.end());
return minLen[0];
}
};