类似题目:
53.最大子序和
70. 爬楼梯
120. 三角形最小路径和
给定一个整数数组 nums
,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
示例:
输入: [-2,1,-3,4,-1,2,1,-5,4],
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
进阶:
如果你已经实现复杂度为 O(n) 的解法,尝试使用更为精妙的分治法求解。
实现思路:
递推式:动态规划:s(n)=Math.max(s(n-1)+nums[n],nums[n])
实现:
class Solution {
public int maxSubArray(int[] nums) {
int sum=0;
int maxsum=nums[0];
for(int num:nums){
sum=Math.max(num,sum+num);
maxsum = Math.max(sum,maxsum);
}
return maxsum;
}
}
实现思路:
二分法:不断将区间划分为左、中、右!中间直接计算,左与右递归处理。
实现:
class Solution {
public int maxSubArray(int[] nums) {
int res;
if(nums.length==0) return 0;
res = maxSub(nums,0,nums.length-1);
return res;
}
public int maxSub(int[] nums, int left, int right){
if(left>right) return Integer.MIN_VALUE;
if(left==right) return nums[left];
int mid=(left+right)/2;
int l=maxSub(nums,left,mid-1);//求左半边最大子序和
int r=maxSub(nums,mid+1,right);//求右半边最大子序和
int t=nums[mid];
int max_num=nums[mid];
for(int i=mid-1;i>=left;i--)//整合左半部分
{
t+=nums[i];
max_num=Math.max(max_num,t);
}
t=max_num;
for(int i=mid+1;i<=right;i++)//整合右半部分
{
t+=nums[i];
max_num=Math.max(max_num,t);
}
return Math.max(Math.max(r,l),max_num);
}
}
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
注意:给定 n 是一个正整数。
示例 1:
输入: 2
输出: 2
解释: 有两种方法可以爬到楼顶。
1. 1 阶 + 1 阶
2. 2 阶
示例 2:
输入: 3
输出: 3
解释: 有三种方法可以爬到楼顶。
1. 1 阶 + 1 阶 + 1 阶
2. 1 阶 + 2 阶
3. 2 阶 + 1 阶
实现思路:
直接递归:因为有众多重复计算,所以通不过!
实现:
class Solution {
public int climbStairs(int n) {
if(n==1||n==0){
return 1;
}
return climbStairs(n-1)+climbStairs(n-2);
}
}
实现思路:
递归加缓存memo,由于每次计算都有许多重复计算,比如f4=f3+f2,f5=f4+f3,f4与f3被多次计算,如果能够在第一次的时候保存下来,后面直接访问,岂不是效率会高。
实现:
class Solution {
public int climbStairs(int n) {
int[] memo = new int[n+1];
return fibStair(memo,n);
}
public int fibStair(int[] memo,int n){
if(n==1||n==0){
memo[n]=1;
}else if(memo[n]==0){
memo[n]=fibStair(memo,n-1)+fibStair(memo,n-2);
}
return memo[n];
}
}
实现思路:
动态规划:自底向上。
实现:
class Solution {
public int climbStairs(int n) {
int[] memo=new int[n+1];
memo[0]=memo[1]=1;
for (int i=2;i<=n;i++){
memo[i]=memo[i-1]+memo[i-2];
}
return memo[n];
}
};
递归:自顶向下。
实现:
class Solution {
public int minPathSum(int[][] grid) {
int m = grid.length;
int n = grid[0].length;
int res = fibPath(grid, m - 1, n - 1);
return res;
}
public int fibPath(int[][] grid, int i, int j) {
//左上角
if (i == 0 && j == 0) {
return grid[i][j];
}
//第一行
if (i==0&&j == 1) {
return grid[i][j] + grid[0][0];
} else if (j==0&&i == 1) { //第一列
return grid[0][0] + grid[i][j];
}
//递归第一列
if (i > 0 && j == 0) {
return fibPath(grid, i - 1, j) + grid[i][j];
} else if (j > 0 && i == 0) { //递归第一行
return fibPath(grid, i, j - 1) + grid[i][j];
}
//递归中间部分
return Math.min(fibPath(grid, i, j - 1), fibPath(grid, i - 1, j)) + grid[i][j];
}
}
实现思路:
递归+记忆化搜索法:自顶向下。新开一个存储空间即可。
实现:
class Solution {
public int minPathSum(int[][] grid) {
int m = grid.length;
int n = grid[0].length;
int[][] memo = new int[m][n];
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
memo[i][j] = -1;
}
}
memo[0][0] = grid[0][0];
return fibPath(memo, grid, m - 1, n - 1);
}
public int fibPath(int[][] memo, int[][] grid, int i, int j) {
if (i == 0 && j == 0) {
return grid[i][j];
}
if (memo[i][j] != -1) { //记忆搜索存储条件
return memo[i][j];
}
if (i==0&&j == 1) {
memo[i][j] = grid[i][j] + grid[0][0];
} else if (j==0&&i == 1) {
memo[i][j] = grid[0][0] + grid[i][j];
}
if (i > 0 && j > 0) {
memo[i][j] = Math.min(fibPath(memo, grid, i, j - 1), fibPath(memo, grid, i - 1, j)) + grid[i][j];
} else if (i > 0 && j == 0) {
memo[i][j] = fibPath(memo, grid, i - 1, j) + grid[i][j];
} else if (j > 0 && i == 0) {
memo[i][j] = fibPath(memo, grid, i, j - 1) + grid[i][j];
}
return memo[i][j];
}
}
实现思路:
动态规划:自底向上。
实现:
开辟二维数组存储:
class Solution {
public int minPathSum(int[][] grid) {
int m = grid.length;
int n = grid[0].length;
int[][] memo = new int[m][n];
memo[0][0] = grid[0][0];
int i,j;
for(i=1;i<n;i++){
memo[0][i]=memo[0][i-1]+grid[0][i];
}
for(i=1;i<m;i++){
memo[i][0]=memo[i-1][0]+grid[i][0];
}
for(i=1;i<m;i++){
for(j=1;j<n;j++){
memo[i][j]=Math.min(memo[i][j-1],memo[i-1][j])+grid[i][j];
}
}
return memo[m-1][n-1];
}
}
开辟一维数组存储:
1 3 1
1 5 1
4 2 1
-----------
1 4 5
2 7 6
6 8 7
实际上用一维数组把巧妙的保存了当前(i,j)位置对应的上一次(i-1,j)与更新后的(i,j-1)的值。
class Solution {
public int minPathSum(int[][] grid) {
int m = grid.length;
int n = grid[0].length;
int[] memo = new int[n];
memo[0] = grid[0][0];
int i,j;
for(i=1;i<n;i++){
memo[i]=memo[i-1]+grid[0][i];
}
for(i=1;i<m;i++){
for(j=0;j<n;j++){
if(j==0){
memo[j]+=grid[i][j]; //巧妙的对边界进行相加!
}else{
memo[j]=Math.min(memo[j-1],memo[j])+grid[i][j];
}
}
}
return memo[n-1];
}
}
给定一个三角形,找出自顶向下的最小路径和。每一步只能移动到下一行中相邻的结点上。
例如,给定三角形:
[
[2],
[3,4],
[6,5,7],
[4,1,8,3]
]
自顶向下的最小路径和为 11
(即,2 + 3 + 5 + 1 = 11)。
说明:
如果你可以只使用 O(n) 的额外空间(n 为三角形的总行数)来解决这个问题,那么你的算法会很加分。
实现思路:
递归
实现:
class Solution {
public int minimumTotal(List<List<Integer>> triangle) {
return fibTri(triangle,0,0,0);
}
public int fibTri(List<List<Integer>> triangle,int row,int col,int min_sum){
if(row>=triangle.size()){
return min_sum;
}
min_sum+=triangle.get(row).get(col);
return Math.min(fibTri(triangle,row+1,col,min_sum),fibTri(triangle,row+1,col+1,min_sum));
}
}
记忆化搜索+递归
class Solution {
public int minimumTotal(List<List<Integer>> triangle) {
return helper(triangle, 0, 0, new HashSet<>());
}
public int helper(List<List<Integer>> triangle, int x, int y, Set<String> visited) {
//递归终止条件
if (x == triangle.size()) {
return 0;
}
//使用记忆化搜索的条件
if (visited.contains(x + " " + y)) {
return triangle.get(x).get(y);
}
int left = helper(triangle, x + 1, y, visited);
int right = helper(triangle, x + 1, y + 1, visited);
int sum = Math.min(left, right) + triangle.get(x).get(y);
triangle.get(x).set(y, sum);
visited.add(x + " " + y);
return sum;
}
}
使用了二维数组空间的动态规划!
直接为tmp多分配一行一列数据,那么就可以不用事先对tmp赋值。
class Solution {
public int minimumTotal(List<List<Integer>> triangle) {
int n = triangle.size();
int[][] tmp = new int[n+1][n+1];
for(int i=n-1;i>=0;i--){
for(int j=0;j<=i;j++){
tmp[i][j]=Math.min(tmp[i+1][j],tmp[i+1][j+1])+triangle.get(i).get(j);
}
}
return tmp[0][0];
}
}
使用了一维数组空间的动态规划!
每次使用一个数组来存储上一次的运算结果。自底向上动态规划。
class Solution {
public int minimumTotal(List<List<Integer>> triangle) {
int rows = triangle.size();
//定义一维数组存放最近一行数据。
int[] arr = new int[rows];
//初始化最底层
for(int i=0;i<rows;i++){
arr[i]=triangle.get(rows-1).get(i); //将最后一行数据转存到arr数组。
}
for(int i=rows-2;i>=0;i--){
for(int j=0;j<=i;j++){ //刚好是第几行就有几列数据
arr[j]=Math.min(arr[j],arr[j+1])+triangle.get(i).get(j);
}
}
return arr[0];
}
}
上述第一个循环其实可以省去,怎么省去?
直接为arr多分配一列数据空间,保证不溢出。
class Solution {
public int minimumTotal(List<List<Integer>> triangle) {
int rows = triangle.size();
//定义一维数组存放最近一行数据。
int[] arr = new int[rows+1];
for(int i=rows-1;i>=0;i--){
for(int j=0;j<=i;j++){ //刚好是第几行就有几列数据
arr[j]=Math.min(arr[j],arr[j+1])+triangle.get(i).get(j);
}
}
return arr[0];
}
}
不用自定义空间的两种动态规划!
实现思路:
自底向上动态规划。每次挑选当前对应的下一行的左边与右边元素中最小元素,然后依次原地累加,直到最后累加到第一行就是最后结果。
实现:
class Solution {
public int minimumTotal(List<List<Integer>> triangle) {
for(int i=triangle.size()-2;i>=0;i--){
for(int j=0;j<triangle.get(i).size();j++){
triangle.get(i).set(j,triangle.get(i).get(j)+Math.min(triangle.get(i+1).get(j),triangle.get(i+1).get(j+1)));
}
}
return triangle.get(0).get(0);
}
}
实现思路:
自顶向下动态规划。分为三种情况,左边界、中间、右边界。
class Solution {
public int minimumTotal(List<List<Integer>> triangle) {
for (int i = 1; i < triangle.size(); i++) {
for (int j = 0; j < triangle.get(i).size(); j++) {
if (j == 0)
//当前行(i,j)与上一行(i-1,j)相关
//将当前行(i,j)对应的值设置为当前行(i,j)的值加上上一行(i-1,j)对应的值。
triangle.get(i).set(j, triangle.get(i-1).get(j) + triangle.get(i).get(j));
else if (j == triangle.get(i).size()-1)
//当前行(i,j)与上一行(i-1,j-1)相关
//将当前行(i,j)对应的值设置为当前行(i,j)的值加上上一行(i-1,j-1)对应的值。
triangle.get(i).set(j, triangle.get(i-1).get(j-1) + triangle.get(i).get(j));
else
//中间部分与上一行的(i-1,j)、(i-1,j-1)相关。
triangle.get(i).set(j, Math.min(triangle.get(i-1).get(j-1), triangle.get(i-1).get(j))
+ triangle.get(i).get(j));
}
}
return Collections.min(triangle.get(triangle.size()-1)); //取出最后一行的最小值即可。
}
}