问题

给出一个三角形,找出从顶部至底部的最小路径和。每一步你只能移动到下一行的邻接数字。

例如,给出如下三角形:

[

[2],

[3,4],

[6,5,7],

[4,1,8,3]

]

从顶部至底部的最小路径和为11(即2+3+5+1=11)。

注意:

加分项-如果你能只使用O(n)的额外空间,n为三角形中的总行数。

初始思路

最直接的思路就是把路径都走一遍。即从顶点出发,分别往左中右移动(如果可能的话);然后对走到的位置继续进行同样移动,直到走到最后一行。这样就可以得到一个递归的方案,而递归的结束条件就是前面所说的走到最后一行。伪代码如下:

[最短路径长度] 查找路径(当前节点坐标,当前路径值)

如果是最后一行,返回当前路径值+当前节点值

否则

如果可以往左下走,左路径 = 当前路径值 + 查找路径(左下节点坐标,当前路径值)

如果可以往下走,下路径 = 当前路径值 + 查找路径(下节点坐标,当前路径值)

如果可以往右下走,右路径 = 当前路径值 + 查找路径(右下节点坐标,当前路径值)

找出左路径,下路径和右路径中的最小值,返回该最小值

结合范例数据仔细分析一下上面的伪代码, 可以发现其中有不少重复的步骤。如2->3->5和2->4->5后面的处理是完全相同的。回想一下我们在 [LeetCode 132] - 回文分割II(Palindrome Partitioning II) 中的做法,可以使用一个map保存已计算过的路径来应对这种重复。这里我们使用std::map<std::pair<int, int>, int>,将某点的坐标作为map的key,从key出发的最小路径作为值。

按以上思路完成代码提交后发现有些测试用例不能通过,如:

[

[-1]

[3,2]

[-3,1,-1]

]

按以上算法得出的值为-2,而期望的值为-1。-2为-1 -> 2-> -3这条路径得出的值,而-1为路径-1 -> 3 -> -3。看来题目中的邻接(英文原文adjacent)规定只能往下或者右走。修改也很简单,将代码中处理向左下走的那部分逻辑去掉即可。最终通过了Judge Small和Judge Large的代码如下:

 class Solution {
public:
int minimumTotal(std::vector<std::vector<int> > &triangle)
{
pathInfo.clear(); if(triangle.empty())
{
return ;
} return FindMinPath(triangle, , , );
} private:
int FindMinPath(std::vector<std::vector<int>>& input, int X, int Y, int currentPathValue)
{
if(X == input.size() - )
{
return currentPathValue + input[X][Y];
} auto iter = pathInfo.find(Coordinate(X, Y)); if(iter != pathInfo.end())
{
return currentPathValue + iter->second;
} //int left = currentPathValue;
int down = currentPathValue;
int right = currentPathValue;
int min = ;
bool minUpdated = false; /*
if(Y - 1 >= 0)
{
left += FindMinPath(input, X + 1, Y - 1, input[X][Y]);
min = left;
minUpdated = true;
}
*/ if(Y < input[X + ].size())
{
down += FindMinPath(input, X + , Y, input[X][Y]); if(!minUpdated || min > down)
{
min = down;
minUpdated = true;
} if(Y + < input[X + ].size())
{
right += FindMinPath(input, X + , Y + , input[X][Y]);
if(!minUpdated || min > right)
{
min = right;
}
}
} pathInfo[Coordinate(X, Y)] = min - currentPathValue; return min;
} std::map<std::pair<int, int>, int> pathInfo; typedef std::pair<int, int> Coordinate;
};

minimumTotal

获得加分的方案

在上面的方案中,我们使用了以每个点坐标为key的map来保存已计算过路径,空间复杂度达到了n^2的级别,即不计map的额外消耗需要1 + 2 + 3 +..... + n = n(n-1) / 2的空间来储存这些信息。

让我们改变一下思路,不考虑某点出发的最短路径,而考虑到达某点的最短路径。给出一个点,怎么得到到该点的最短路径?可以发现有三种情况:

  • 该点为最左边的点,即纵坐标为0。由于我们前面已经知道题目不允许往左下走,所以这种情况没得选择,最短路径就是上面的点的最短路径加当前点的值。
  • 该点为最右边的点,即纵坐标为n-1(n为该行的长度)。由于是三角形,上一行中没有纵坐标为n-1的点。这种情况最短路径只能是左上的点的最短路径加当前点的值。
  • 其他情况。有两种选择,左上的点或者上方的点,需要取其中的小者来加当前点的值。

用上面方法得出第n行的所有点的最短路径后,我们发现第n-1行即上面一行的信息已经不再需要被存储了,因为第n+1行即下一行可以完全通过第n行的信息来算得自己的最短路径值。那么我们需要的最大存储空间就为最后一行的点的个数。不难看出,该数字和行数是相等的。这就符合了加分项中空间复杂度为O(n)的要求。

根据以上算法,我们将第一行中唯一一个值直接存到以纵坐标为下标的一个数组pathInfo中。然后从第二行开始对每行中的每列进行遍历,不断更新直到最后一行最后一列即可得到一个存有最后一行中每个点的最短路径的数组。对数组pathInfo进行一次遍历找出最小值即为题目所求。在处理过程中,还需要注意一个小细节:遍历每行时,需要从最右边的列开始。因为如果从左边开始,我们更新pathInfo[0]时就把上一层的信息覆盖了,而新的pathInfo[1]还需要用到上一层的信息(它需要从上一层的0和1中选一个最小值)。

最终代码如下:

 class Solution
{
public:
int minimumTotal(std::vector<std::vector<int> > &triangle)
{
std::vector<int> pathInfo(triangle.size()); pathInfo[] = triangle[][]; for(int i = ; i < triangle.size(); ++i)
{
for(int j = i; j >= ; --j)
{
if(j == )
{
pathInfo[j] = pathInfo[j] + triangle[i][j];
}
else if(j == triangle[i].size() - )
{
pathInfo[j] = pathInfo[j - ] + triangle[i][j];
}
else
{
pathInfo[j] = pathInfo[j] < pathInfo[j - ] ? pathInfo[j] : pathInfo[j - ];
pathInfo[j] += triangle[i][j];
}
}
} int min = *pathInfo.begin();
for(auto iter = pathInfo.begin() + ; iter != pathInfo.end(); ++iter)
{
if(min > *iter)
{
min = *iter;
}
} return min;
}
};

minimumTotal_Bonus

使用了新的算法后,不但减少了空间复杂度,递归也不再需要了,过Judge Large的时间由130ms左右降到了40ms左右。

[LeetCode 120] - 三角形(Triangle)的更多相关文章

  1. Java实现 LeetCode 120 三角形最小路径和

    120. 三角形最小路径和 给定一个三角形,找出自顶向下的最小路径和.每一步只能移动到下一行中相邻的结点上. 例如,给定三角形: [ [2], [3,4], [6,5,7], [4,1,8,3] ] ...

  2. LeetCode 120. 三角形最小路径和(Triangle)

    题目描述 给定一个三角形,找出自顶向下的最小路径和.每一步只能移动到下一行中相邻的结点上. 例如,给定三角形: [ [2], [3,4], [6,5,7], [4,1,8,3] ] 自顶向下的最小路径 ...

  3. [Leetcode]120.三角形路径最小和

    ---恢复内容开始--- 题目的链接 简单的动态规划题,使用了二维dp数组就能很好的表示. 由于有边界的问题,所以这个dp数组为 dp[n+1][n+1]. dp[i][j]意思是终点为(i-1,j- ...

  4. leetcode 120. 三角形最小路径和 JAVA

    题目: 给定一个三角形,找出自顶向下的最小路径和.每一步只能移动到下一行中相邻的结点上. 例如,给定三角形: [ [2], [3,4], [6,5,7], [4,1,8,3] ] 自顶向下的最小路径和 ...

  5. LeetCode 120——三角形最小路径和

    1. 题目 2. 解答 详细解答方案可参考北京大学 MOOC 程序设计与算法(二)算法基础之动态规划部分. 从三角形倒数第二行开始,某一位置只能从左下方或者右下方移动而来,因此,我们只需要求出这两者的 ...

  6. leetcode 120. 三角形最小路径和 及 53. 最大子序和

    三角形最小路径和 问题描述 给定一个三角形,找出自顶向下的最小路径和.每一步只能移动到下一行中相邻的结点上. 例如,给定三角形: [ [2], [3,4], [6,5,7], [4,1,8,3] ] ...

  7. leetcode面试准备:Triangle

    leetcode面试准备:Triangle 1 题目 Given a triangle, find the minimum path sum from top to bottom. Each step ...

  8. 三角形(Triangle)

    三角形(Triangle) 问题 给出一个三角形,找出从顶部至底部的最小路径和.每一步你只能移动到下一行的邻接数字. 例如,给出如下三角形: [ [2], [3,4], [6,5,7], [4,1,8 ...

  9. 1. 线性DP 120. 三角形最小路径和

    经典问题: 120. 三角形最小路径和  https://leetcode-cn.com/problems/triangle/ func minimumTotal(triangle [][]int) ...

随机推荐

  1. Back to Basics: Using KVO

    One of the things I like most about Apple’s iOS SDK is the consistent and easy-to-use API they provi ...

  2. NS CKD

    NS 定义:ALB<30:高脂血症:大量蛋白尿>3.5g:浮肿 见于:肾小球肾炎.小血管炎.微血管血栓性疾病.NSAIDs引起的急性间质性肾炎.不见于肾大血管病.间质性.小管性疾病. 并发 ...

  3. [Angular 2] Handling Click Events with Subjects

    While Angular 2 usually uses event handlers to manage events and RxJS typically uses Observable.from ...

  4. ios AFNetworking 有用篇

    在寻常开发中,af是个非常好用的东西.非常喜欢.可是网上的af找了好多都不太全面,不有用.所以我今天做了一个demo.有上传下载的. 比較有用.希望大家可以用到. 去我github下载demo git ...

  5. 《C专家变成》之二

    第五章 一.对链接的思考 目标文件并不能直接执行,它首先需要载入到链接器.链接器确认main函数为初始进入点(程序开始执行的地方),把符号引用绑定到内存地址,把所有的目标文件集中在一起,再加上库文件, ...

  6. .NET注册页面代码

    using System;using System.Collections.Generic;using System.Linq;using System.Web;using System.Web.UI ...

  7. Java基础知识强化91:DateFormat类之DateFormat实现日期和字符串的相互转换

    1. DateFormat类概述: DateFormat 是日期/时间格式化子类的抽象类,它以与语言无关的方式格式化并解析日期或时间. 是抽象类,所以使用其子类SimpleDateFormat 2.  ...

  8. NYOJ-744蚂蚁的难题(一)

    这个题都说是水题,楞是没做出来,看了好多题解,感觉这个规律没看懂,后来在讨论区看到了一个题解,感觉有点懂了,写一下自己的理解 首先要明白异或的意思,简单一句话: 同0异1,既然这样,让求区间a,b 中 ...

  9. 自定义控件 TextView 歌词 Lrc

    演示 <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> < ...

  10. java使用内部类的好处及其初始化

    java使用内部类的原因 每个内部类都能独立地继承自一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响          java内部类初始化 ForeCatal ...