Leetcode-动态规划典型题

LC053.Maximum Subarray最大子数组和

一、题目

给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

子数组 是数组中的一个连续部分。

 
示例 1:

输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。
示例 2:

输入:nums = [1]
输出:1
示例 3:

输入:nums = [5,4,-1,7,8]
输出:23
 
提示:

1 <= nums.length <= 105
-104 <= nums[i] <= 104
 

进阶:如果你已经实现复杂度为 O(n) 的解法,尝试使用更为精妙的 分治法 求解。

二、实现方法

方法一:滑动窗口/动态规划更改数组

时间复杂度O(n),空间复杂度O(1)
func maxSubArray(nums []int) (res int) {
	res=nums[0]
	if len(nums)==1{
		return
	}
	for i:=1;i<len(nums);i++{
		if nums[i-1]+nums[i]>nums[i]{
			nums[i]+=nums[i-1]
		}
		if res<nums[i]{
			res=nums[i]
		}
	}
	return
}

方法二:分治-线段树

时间复杂度O(nlogn),空间复杂度O(logn)
将问题分解为最大子序和都在左边/都在右边/跨中间
func maxSubArray2(nums []int) int {
	return get(nums, 0, len(nums) - 1).mSum;
}

type Status struct {
	/*
		lSum 表示 [l,r] 内以 l 为左端点的最大子段和
		rSum 表示 [l,r] 内以 r 为右端点的最大子段和
		mSum 表示 [l,r] 内的最大子段和
		iSum 表示 [l,r] 的区间和
	*/
	lSum, rSum, mSum, iSum int
}

// 合并
func pushUp(l, r Status) Status {
	// 合并后左边的最大子段和=max(左侧最大子段和,左侧区间全部的和+右侧以 l 为左端点的最大子段和)
	lSum := max(l.lSum, l.iSum + r.lSum)
	// 合并后右边的最大子段和=max(右侧最大子段和,右侧区间全部的和+左侧以 r 为右端点的最大子段和)
	rSum := max(r.rSum, r.iSum + l.rSum)
	// 合并跨中间的最大子段和=max(max(左右测区间的最大子段和),左侧以 r 为右端点的最大子段和+右侧以 l 为左端点的最大子段和)
	mSum := max(max(l.mSum, r.mSum), l.rSum + r.lSum)
	// 区间和:左+右
	iSum := l.iSum + r.iSum
	return Status{lSum, rSum, mSum, iSum}
}

func get(nums []int, l, r int) Status {
	if (l == r) {
		return Status{nums[l], nums[l], nums[l], nums[l]}
	}
	m := (l + r) >> 1
	// 分治,将跨中间的分解为左边和右边,中间的跟着左边
	lSub := get(nums, l, m)
	rSub := get(nums, m + 1, r)
	return pushUp(lSub, rSub)
}

func max(x, y int) int {
	if x > y {
		return x
	}
	return y
}

LC062.UniquePaths不同路径

一、题目

一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。

机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。

问总共有多少条不同的路径?
示例 1:
输入:m = 3, n = 7
输出:28

示例 2:

输入:m = 3, n = 2
输出:3
示例 3:

输入:m = 7, n = 3
输出:28
示例 4:

输入:m = 3, n = 3
输出:6

二、实现方法

方法一:动态规划

思路:

// dp[i][j]含义为到点(i,j)可能的路径数
// 方程为dp[i][j]=dp[i-1][j]+dp[i][j-1]
// 终点为dp[m-1][n-1]
// 空间复杂度O(mn),时间复杂度O(mn)
func uniquePaths(m int, n int) int {
	dp:=make([][]int,m)
	for i,_:=range dp{
		dp[i]=make([]int,n)
		dp[i][0]=1
	}
	for i:=0;i<n;i++{
		dp[0][i]=1
	}
	for i:=1;i<m;i++{
		for j:=1;j<n;j++{
			// 当前点的路径总数=上一个节点的+左一个节点的
			dp[i][j]=dp[i-1][j]+dp[i][j-1]
		}
	}

	return dp[m-1][n-1]
}

LC070.ClimbingStairs爬楼梯

一、题目

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。

每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?

 

示例 1:

输入:n = 2
输出:2
解释:有两种方法可以爬到楼顶。
1. 1 阶 + 1 阶
2. 2 阶
示例 2:

输入:n = 3
输出:3
解释:有三种方法可以爬到楼顶。
1. 1 阶 + 1 阶 + 1 阶
2. 1 阶 + 2 阶
3. 2 阶 + 1 阶

二、实现方法

方法一:动态规划

// dp[i]含义为,台阶为i时有dp[i]种方法到楼顶
// 方程为dp[i]=dp[i-1]+dp[i-2]
// 结束条件:dp[n]
// 时间复杂度O(n),空间复杂度O(n)
func ClimbStairs(n int) int {
	if n<2{
		return n
	}
	dp:=make([]int,n+1)
	dp[0]=1
	dp[1]=1

	for i:=2;i<=n;i++{
		dp[i]=dp[i-1]+dp[i-2]
	}
	return dp[n]
}

方法二:斐波那契

// 根据dp[i]=dp[i-1]+dp[i-2],发现可以用斐波那契算
// 时间复杂度O(n),空间复杂度O(1)
func ClimbStairs2(n int) int {
	if n<2{
		return n
	}
	n1,n2:=1,1

	for i:=2;i<=n;i++{
		n1,n2=n2,n1+n2
	}
	return n2
}

 LC096.Unique Binary Search Trees不同的二叉搜索数

一、题目

给你一个整数 n ,求恰由 n 个节点组成且节点值从 1 到 n 互不相同的 二叉搜索树 有多少种?返回满足题意的二叉搜索树的种数。

 

示例 1:


输入:n = 3
输出:5
示例 2:

输入:n = 1
输出:1
 

提示:

1 <= n <= 19

二、实现方法

// 二叉搜索树:它或者是一棵空树,或者是具有下列性质的二叉树:
// 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
// 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
// 它的左、右子树也分别为二叉排序树。

方法一:动态规划

// dp[i]含义:由i个节点可以组成dp[i]个二叉搜索数
// 思路:固定中间的值,把左右的可能性次数相乘
// dp[3]=dp[0]*dp[2](固定1,左边0个节点,右边2个节点)+dp[1]*dp[1](固定2,左边1个节点,右边1个节点)+dp[2]*dp[0](固定2,左边2个节点,右边0个节点)
// 动态方程 两层循环:dp[i]+=dp[j]*dp[i-j-1]
// 结束输出 dp[n]
// 时间复杂度O(n^2),空间复杂度O(n)
func numTrees(n int) int {
	if n<=2{
		return n
	}
	dp:=make([]int,n+1)
	dp[0]=1
	dp[1]=1
	for i:=2;i<=n;i++{
		for j:=0;j<i;j++{
			dp[i]+=dp[j]*dp[i-j-1]
		}
	}
	return dp[n]
}

LC120.Triangle三角形最小路径和

一、题目

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 为三角形的总行数)来解决这个问题吗?

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/triangle
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

 方法一:动态规划

// 二维dp
// 时间复杂度O(n^2),空间复杂度O(n^2)
func minimumTotal(triangle [][]int) int {
	m,n:=len(triangle),len(triangle[len(triangle)-1])
	if m==1{
		return triangle[0][0]
	}
	dp:=make([][]int,m)
	for i:=0;i<m;i++{
		dp[i]=make([]int,n)
	}
	dp[0][0]=triangle[0][0]
	for i:=1;i<m;i++{
		dp[i][0]=dp[i-1][0]+triangle[i][0]
		for j:=1;j<i;j++{
			dp[i][j]=min(dp[i-1][j],dp[i-1][j-1])+triangle[i][j]
		}
		dp[i][i]=dp[i-1][i-1]+triangle[i][i]
	}
	res:=math.MaxInt64
	for _,v:=range dp[n-1]{
		if v<res{
			res=v
		}
	}
	return res
}

方法二:动态规划 +原地修改

// 原数组上改
// 时间复杂度O(n^2),空间复杂度O(1)
func minimumTotal2(triangle [][]int) int {
	n:=len(triangle)
	if n==1{
		return triangle[0][0]
	}
	for i:=1;i<n;i++{
		triangle[i][0]+=triangle[i-1][0]
		for j:=1;j<i;j++{
			triangle[i][j]+=min(triangle[i-1][j],triangle[i-1][j-1])
		}
		triangle[i][i]+=triangle[i-1][i-1]
	}
	res:=math.MaxInt64
	for _,v:=range triangle[n-1]{
		if v<res{
			res=v
		}
	}
	return res
}

 方法三:动态规划 +一维数组

// 一维 需要倒着来,因为左边的值已经被修改,会导致错误答案
// 时间复杂度O(n^2),空间复杂度O(n)
func minimumTotal3(triangle [][]int) int {
	m:=len(triangle)
	if m==1{
		return triangle[0][0]
	}
	dp:=make([]int,m)
	dp[0]=triangle[0][0]
	for i:=1;i<m;i++{
		dp[i]=dp[i-1]+triangle[i][i]
		for j:=i-1;j>0;j--{
			// 注意这里下标是j
			dp[j]=min(dp[j],dp[j-1])+triangle[i][j]
		}
		dp[0]+=triangle[i][0]
	}
	res:=math.MaxInt64
	for _,v:=range dp{
		if v<res{
			res=v
		}
	}
	return res
}

func min(a,b int)int{
	if a<b{
		return a
	}
	return b
}

 LC121买卖股票的最佳时机.Best Time to Buy and Sell Stock买卖股票的最佳时机

一、题目

121. 买卖股票的最佳时机
给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。

你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。

返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。

 

示例 1:

输入:[7,1,5,3,6,4]
输出:5
解释:在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
     注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。
示例 2:

输入:prices = [7,6,4,3,1]
输出:0
解释:在这种情况下, 没有交易完成, 所以最大利润为 0。
 

提示:

1 <= prices.length <= 105
0 <= prices[i] <= 104

 方法一:动态规划 +一维数组

// dp代表在当前天的最高利润
// 需要定义一个最小成本cost
// 动态转移方程为dp[i]=max(dp[i-1],prices[i]-cost)
// 时间复杂度O(n),空间复杂度O(n)
func maxProfit(prices []int) int {
	cost:=prices[0]
	n:=len(prices)
	dp:=make([]int,n)
	dp[0]=0
	for i:=1;i<n;i++{
		if prices[i]<cost{
			cost=prices[i]
		}
		dp[i]=max(dp[i-1],prices[i]-cost)
	}
	return dp[n-1]
}
func max(a,b int)int{
	if a>b{
		return a
	}
	return b
}

 方法二:动态规划优化

// 优化,因为只用到了dp[i-1],所以可以只用一个值去记录然后更新
// 时间复杂度O(n),空间复杂度O(1)
func maxProfit2(prices []int) int {
	cost:=prices[0]
	n:=len(prices)
	dp:=0
	for i:=1;i<n;i++{
		if prices[i]<cost{
			cost=prices[i]
		}
		dp=max(dp,prices[i]-cost)
	}
	return dp
}

func max(a,b int)int{
	if a>b{
		return a
	}
	return b
}

 LC279.PerfectSquares

一、题目

给你一个整数 n ,返回 和为 n 的完全平方数的最少数量 。

完全平方数 是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,1、4、9 和 16 都是完全平方数,而 3 和 11 不是。

示例 1:

输入:n = 12
输出:3 
解释:12 = 4 + 4 + 4
示例 2:

输入:n = 13
输出:2
解释:13 = 4 + 9
 
提示:

1 <= n <= 104

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/perfect-squares
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

 方法一:动态规划

func numSquares(n int) int {
	// 申请n+1个内存空间
	dp:=make([]int,n+1)
	// 初始值为0
	dp[0]=0
	for i:=1;i<=n;i++{
		minnum:=math.MaxInt64
		for j:=1;j*j<=i;j++{
			// 从1开始,即dp[i-1]
			// if i==4 ,j=2,dp[0],这就是dp[0]初始化为0的意义
			minnum=min(minnum,dp[i-j*j])
		}
		// 在这里统一加一
		dp[i]=minnum+1
	}
	return dp[n]
}

func min(a,b int) int {
	if a<b{
		return a
	}
	return b
}

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值