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三角形最小路径和
一、题目
给定一个三角形 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
}