#64.最小路径和
这是一个经典的动态规划题,类似于走楼梯问题。
1.定义状态:
本题目是让求从左上角到右下角最短路径,且只能向下或向右。那么我们可以缩小范围,假设与起点相邻的下面节点和右面节点是更小范围的矩形的左上角。那么求这个大矩形的状态(最短路径)就是选择向右和向下中路径相对较短的。所以最小状态就是每个节点到右下角节点的最短距离。
2.确定临界状态:
由题意,我们知道在最下面行和最右列的节点只能有一种选择。所以有式子:
dp[dp.size-1][IndexOfCol]+=dp[dp.size-1][IndexOfCol+1],
dp[IndexOfRow][dp[0].size-1]+=dp[IndexOfRow+1][dp[0].size-1] 0<=IndexOfRow<dp.size 0<=IndexOfCol<dp[0].size
不过如果路建立数组记录每个状态,空间复杂度就会达到O(n^2),显然开销是巨大的。我们可以直接在原数组上修改
grid[IndexOfRow][grid[0].length-1]+=grid[IndexOfRow+1][grid[0].length-1];
grid[grid.length-1][IndexOfCol]+=gird[grid.length-1][IndexOfCol+1]; 0<=IndexOfRow<dp.size 0<=IndexOfCol<dp[0].size
3.状态转移方程
grid[i][j]=min(grid[i+1][j],grid[i][j+1])(i<=0<grid.length-1,j<=0<grid[0].length-1)
4.代码实现:
class Solution {
public int minPathSum(int[][] grid) {
for(int i=grid.length-1;i>=0;i--)
{
for(int j=grid.length-1;j>=0;j--)
{
if(i==grid.length-1&&j!=grid[0].length-1)
{
grid[i][j]+=grid[i][j + 1];
}
else if(j==grid[0].length-1&&i!=grid.length-1)
{
grid[i][j]+=grid[i + 1][j];
}
else if(j!=grid[0].length-1&&i!=grid.length-1)
{
grid[i][j]+= Math.min(grid[i + 1][j],grid[i][j + 1]);
}
}
}
return grid[0][0];
}
}
时间复杂度:O(mn)
空间复杂度:O(1)
#70.爬楼梯
经典的动态规划题
1.定义状态
我们可以把问题分解成最小的问题,即计算1-n(n为楼层数)中到每层楼梯的方法有多少种。由于上楼是层层递进的,而由题目可知上楼无非是从该层往下数的第二层爬两个台阶,或者从该层往下数的第一层爬一个台阶(前提是该楼层的层数大于2)。这样通过递推就可以求出从起点到n层爬楼梯的方法数了。
2.临界状态
有两个特例,即第一层是无法用前两层的方法数相加。从起点到第一层只有一种方法。
3.状态转义方程
dp[i]=dp[i-1]+dp[i-2](2<=i<=n)
4.代码实现
class Solution {
public int climbStairs(int n) {
int []dp=new int[n+1];//定义数组记录到每层的方法数dp[n]就是起点到第n层的方法数
if(n<2)
{
return 1;
}
dp[1]=1;
dp[2]=2;
for(int i=3;i<=n;i++)
{
dp[i]=dp[i-1]+dp[i-2];
}
return dp[n];
}
}
这样的空间复杂度未免有点高,可以定义两个变量代替dp[i-1],dp[i-2]
class Solution {
public int climbStairs(int n) {
if(n<2)
{
return 1;
}
int oldpre=1;
int pre=1;
int ans=0;
for(int i=2;i<=n;i++)
{
ans=oldpre+pre;
oldpre=pre;
pre=ans;
}
return ans;
}
}
时间复杂度:O(n)
空间复杂度:O(1)
看状态转移方程其实可以发现这道题就是求斐波那契数列,斐波那契数列的具体定义(体现了动态规划中的递推思想):
如果设F(n)为该数列的第n项(n∈N*),那么这句话可以写成如下形式:
F(n)=F(n-1)+F(n-2)
有关斐波那契数列的问题在这里就不做过多的阐述了。
91.解码方法
这道题在leetcode上的通过率只有20%,看来还是有挑战性的。
1.定义状态
老套路,把大问题分解为可解的小问题。题目要求算出解码的方法数,可以递推求每个字符结尾的子字符串的解码方法,就得到了整个字符串的解码方法有多少种。
2.临界状态
这个题比较繁琐的一点要考虑各种状态。由于A-Z编码是对应1-26的,因此还要考虑0的情况。如果是0,还要判断前一位以及这个0的下标,为0就说明没法解码,直接返回0。下标1或者更大的情况还要看前一位的数字。是1或2,如果下标为1且前面的数字是1或2,说明字符串可以编码为J或T。以该数字为结束的子字符串的编码方法就为1,如果下标更大只需在满足前面数字的为1或2时,令记录该处的值改为下标为i-2处的值。其他情况都为直接返回0(字符串无法编码)。
如果不是0的话也有很多种情况。如果假设s[i]为当前下标处的数字,如果s[i]>0且s[i]<7且s[i-1]=1或者s[i-1]=2,说明编码的方式有很多种,dp[i]=dp[i-1]+dp[i-2]。否则dp[i]=dp[i-1]
3.定义状态方程
dp[i]=dp[i-1]+dp[i-2] (s[i]==1||s[i]==2&&s[i]>0&&s[i]<7)
4.代码实现
class Solution {
public int numDecodings(String s) {
int []dp=new int[n];
for(int i=0;i<s.length();i++)
{
if(s[i]=='0')
{
if(i==0)
{
return 0;
}
else if(i==1)
{
if(s[i-1]=='1'||s[i-1]=='2')
{
dp[i]=1;
}
else
{
return 0;
}
}
else
{
if(s[i-1]=='1'||s[i-1]=='2')
{
dp[i]=dp[i-2];
}
else
{
return 0;
}
}
}
else
{
if(i==0)
{
dp[i]=1;
}
else if(i==1)
{
if(s[i-1]=='1'||(s[i-1]=='2'&&s[i]>'0'&&s[i]<'7'))
{
dp[i]=2;
}
else
{
dp[i]=dp[i-1];
}
}
else
{
if(s[i-1]=='1'||(s[i-1]=='2'&&s[i]>'0'&&s[i]<'7'))
{
dp[i]=dp[i-1]+dp[i-2];
}
else
{
dp[i]=dp[i-1];
}
}
}
}
return dp[s.length()-1];
}
}
时间复杂度:O(N)
空间复杂度:O(N)