目录
前言
碎碎念:猴 鸡 猪 鼠 白色银色,黑色蓝色
本系列《绝境求生》记录转码算法筑基过程,以代码随想录为纲学习,leetcode_hot_100练手,在此记录思考过程,方便过后复现。内容比较粗糙仅便于笔者厘清思路,复盘总结。
此篇后,改变了文章结构
提示:以下是本篇文章正文内容
动态规划
递归+缓存。 空间换时间。 算过的记下来,下次用到的时候直接找,不用再算
一、300. 最长递增子序列LIS
1、题目描述
给你一个整数数组
nums,找到其中最长严格递增子序列的长度。关键定义:
- 子序列:由数组派生而来,删除(或不删除)数组中的元素但不改变剩余元素的相对顺序(元素可不连续);
- 严格递增:子序列中后一个元素必须大于前一个元素(如
[2,3,7]是递增,[2,2,7]不是)。示例
示例 1:输入
nums = [10,9,2,5,3,7,101,18]→ 输出4
- 解释:最长严格递增子序列是
[2,3,7,101](或[2,5,7,101]/[2,3,7,18]),长度为 4。示例 2:输入
nums = [0,1,0,3,2,3]→ 输出4
- 解释:最长子序列是
[0,1,3,3](错误,严格递增应为[0,1,2,3])→ 正确最长是[0,1,3,3]不符合严格递增,实际是[0,1,3]或[0,1,2,3],长度 4。示例 3:输入
nums = [7,7,7,7,7]→ 输出1
- 解释:严格递增要求元素必须更大,因此只能选一个 7,长度为 1。
提示
1 <= nums.length <= 2500-10^4 <= nums[i] <= 10^4
2、简单理解?
以某个元素为结尾的最长递增子序列
3、暴力法
3.1、能不能用图示意?
# 初始化dp数组(长度8,每个元素初始为1) dp = [1, 1, 1, 1, 1, 1, 1, 1] # 逐i计算dp[i] i=0(nums[0]=10):无j<0 → dp[0]=1 i=1(nums[1]=9):j=0(10>9)→ dp[1]=1 i=2(nums[2]=2):j=0(10>2)、j=1(9>2) → dp[2]=1 i=3(nums[3]=5):j=0(10>5)、j=1(9>5)、j=2(2<5) → dp[3] = dp[2]+1=2 i=4(nums[4]=3):j=0(10>3)、j=1(9>3)、j=2(2<3→dp=2)、j=3(5>3) → dp[4]=2 i=5(nums[5]=7):j=0(10>7)、j=1(9>7)、j=2(2<7→dp=2)、j=3(5<7→dp=3)、j=4(3<7→dp=3) → dp[5]=3 i=6(nums[6]=101):j=0-5均<101,最大dp[j]=3 → dp[6]=4 i=7(nums[7]=18):j=0-5最大dp[j]=3(j=5的7<18),j=6(101>18) → dp[7]=4 # 最终dp数组:[1,1,1,2,2,3,4,4] → 最大值为4
3.2、初始化条件?
dp[i]初试化为1,因为每一个元素自身是长度为1的子序列
3.3、边界条件?
如果长度为1 直接返回1
3.4、代码逻辑?
状态定义dp[i ]表示 以num[i]为最长严格递增子序列长度
dp数组下标代表nums里每个数的位置。 对应的值代表子序列长度
状态转移方程:对于每个i, 遍历所有j<i:
若nums[j]<nums[i] 满足严格递增,则dp[i]=max(dp[i],dp[j]+1)在j的最长子序列后加nums[i]
若nums[j]>nums[i]不满足递增 则跳过
返回所有位置的最长递增子序列
3.5、之前见过但没注意到的?
返回所有位置的最长递增子序列
- 最终结果是
max(dp)而非dp[-1]:最长子序列不一定以最后一个元素结尾(比如示例中 dp [7]=4,dp [6]=4,最大值是 4);
3.6、疑惑点/新知识 ?
初始化为0,初始化为无穷大 float('inf'),初始化为bool,初始化为1,初始化为空
3.7、python 代码
def solution(nums):
n=len(nums) #感觉很经典
if n==1:
return 1
dp=[1]*(n)
#遍历每个元素i
for i in range(1,n):
for j in range(i):
if nums[j]<nums[i]:
# 状态转移:取当前dp[i]和dp[j]+1的最大值
dp[i]=max(dp[i],dp[j]+1)
return max(dp)
一、152乘积最大子数组
1、题目描述
给你一个整数数组
nums,请你找出数组中乘积最大的非空连续子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。关键定义:
- 子数组:数组中连续的一段元素(区别于子序列,子序列可不连续);
- 乘积特性:负数 × 负数 = 正数,负数 × 正数 = 负数,0 会重置乘积为 0,这是解题的核心难点。
示例
示例 1:输入
nums = [2,3,-2,4]→ 输出6
- 解释:连续子数组
[2,3]的乘积是 6,是最大的([2,3,-2,4]乘积 =-48,[-2,4]乘积 =-8)。示例 2:输入
nums = [-2,0,-1]→ 输出0
- 解释:最大乘积是 0(子数组
[-2,0]或[0]或[0,-1],[-1]乘积 =-1 更小)。示例 3:输入
nums = [-2,3,-4]→ 输出24
- 解释:连续子数组
[-2,3,-4]的乘积 =(-2)×3×(-4)=24,是最大的(负负得正让最小值反转为最大值)。提示
1 <= nums.length <= 2 * 10^4-10 <= nums[i] <= 10nums的任何前缀或后缀的乘积都不会超过2^31 - 1
2、简单理解?
负数的存在会反转乘积的大小关系(当前的负数最小值乘与一个负数可能会反转成最大值)
3、暴力法
3.1、能不能用图示意?
以 nums = [2,3,-2,4] 为例 枚举所有子数组: i=0(起始为2): j=0 → 乘积=2;j=1→2×3=6;j=2→6×(-2)=-12;j=3→-12×4=-48 → 最大值6 i=1(起始为3): j=1→3;j=2→3×(-2)=-6;j=3→-6×4=-24 → 最大值3 i=2(起始为-2): j=2→-2;j=3→-2×4=-8 → 最大值-2 i=3(起始为4): j=3→4 → 最大值4 所有子数组乘积的最大值:6
3.2、初始化条件?
初始化最大乘积为负无穷,确保能被更小的数更新。 因为最大乘积可能是负数,所以初试不为0。
3.3、边界条件?
剪枝 数组长度为1时返回第一个元素
3.4、代码逻辑?
暴力枚举所有可能的子数组
外层循环:遍历所有的起始位置
内层循环,从起始位置到末尾,计算连续子数组的乘积
current_prod 累计乘积
max_prod=max(max_prod, current_prod ) 更新全局最大的乘积
3.5、之前见过但没注意到的?
双指针一般是要满足单调规律才用。 子内部混乱,无序,随机是不适用的
跟最长子序列的区别是,最长子序列可以跳数,最大乘积子数组必须是连续的
3.6、疑惑点/新知识 ?
3.7、python 代码
class Solution:
def maxProduct(self, nums: List[int]) -> int:
n = len(nums)
# 边界条件:数组长度为1时,直接返回自身
if n == 1:
return nums[0]
# 初始化最大乘积为负无穷(确保能被更小的数更新)
max_prod = float('-inf')
# 外层循环:枚举子数组的起始位置i
for i in range(n):
# 初始化当前子数组的乘积为1(每次起始位置重置)
current_prod = 1
# 内层循环:枚举子数组的结束位置j(从i到n-1)
for j in range(i, n):
# 计算当前子数组nums[i..j]的乘积
current_prod *= nums[j]
# 更新最大乘积
if current_prod > max_prod:
max_prod = current_prod
return max_prod
4、优化法
-
状态定义:
dp_max[i]:以nums[i]结尾的连续子数组的最大乘积;dp_min[i]:以nums[i]结尾的连续子数组的最小乘积(应对负数反转的情况)。
-
状态转移方程:对于每个
i,需比较三个值:nums[i]本身、dp_max[i-1]×nums[i]、dp_min[i-1]×nums[i]:dp_max[i] = max(nums[i], dp_max[i-1] * nums[i], dp_min[i-1] * nums[i]);dp_min[i] = min(nums[i], dp_max[i-1] * nums[i], dp_min[i-1] * nums[i])。
4.1、能不能用图示意?
以 nums = [-2,3,-4] 为例 初始化: cur_max = -2(dp_max[0]),cur_min = -2(dp_min[0]),result = -2 遍历i=1(nums[1]=3): 临时保存cur_max(避免更新时覆盖):temp_max = -2 cur_max = max(3, temp_max×3=-6, cur_min×3=-6) → 3 cur_min = min(3, temp_max×3=-6, cur_min×3=-6) → -6 result = max(-2, 3) → 3 遍历i=2(nums[2]=-4): temp_max = 3 cur_max = max(-4, temp_max×(-4)=-12, cur_min×(-4)=24) → 24 cur_min = min(-4, temp_max×(-4)=-12, cur_min×(-4)=24) → -12 result = max(3, 24) → 24 最终result=24
4.2、初始化条件?
dp_max[0]=dp_min[0]=nums[0] 第一个元素的最大最小值都是自身
4.3、边界条件?
剪枝 target=0 or 1 的情况
4.4、代码逻辑?
索引i 表示第几个元素,dp_max[i]和dp_min[i]的值是以i 为结尾的最大、最小数组乘积值
其实 dp_max[i] 和 dp_min[i] 仅需前一个值,可以用变量 cur_max、cur_min 代替数组,空间复杂度从 O (n) 降到 O (1)。
-
temp_max = cur_max:必须先保存当前的 cur_max!因为更新 cur_min 时需要原始的 cur_max,若先更新 cur_max,会导致 cur_min 计算错误(比如 nums [i] 是负数时,cur_min 需要用更新前的 cur_max×nums [i]); -
cur_max = max(nums[i], temp_max * nums[i], cur_min * nums[i]):- 选
nums[i]:表示重新开始一个子数组(比如前一个乘积是 0 或负数,放弃前面的子数组); - 选
temp_max * nums[i]:前一个最大乘积 × 当前元素(默认当前元素为正); - 选
cur_min * nums[i]:前一个最小乘积 × 当前元素(默认当前元素为负,负负得正);
- 选
-
cur_min = min(...):逻辑同 cur_max,取三者最小值,为后续负数反转做准备; -
result = max(result, cur_max):每次更新全局最大值(以当前元素结尾的最大乘积可能是全局最大)。 -
无需处理 0 的特殊情况:当 nums [i]=0 时,cur_max 和 cur_min 都会被重置为 0(max (0, ...)=0,min (0, ...)=0),符合逻辑。
4.5、之前见过但没注意到的?
4.6、疑惑点/新知识 ?
第一次见到用两个动态方程的解决问题场景。 同时维护了dp_max和dp_min
和的问题无序维护最小值,但是乘积问题必须维护,运算特性!!!!!!
4.7、python 代码
from typing import List
class Solution:
def maxProduct(self, nums: List[int]) -> int:
n = len(nums)
# 边界条件:数组长度为1时,直接返回自身
if n == 1:
return nums[0]
# 步骤1:初始化
# cur_max:以当前元素结尾的子数组的最大乘积(对应dp_max[i])
# cur_min:以当前元素结尾的子数组的最小乘积(对应dp_min[i])
cur_max = nums[0]
cur_min = nums[0]
# result:全局最大乘积(初始为第一个元素,后续逐步更新)
result = nums[0]
# 步骤2:遍历数组(从第二个元素开始)
for i in range(1, n):
# 关键:临时保存cur_max,因为更新cur_min时需要原始的cur_max
temp_max = cur_max
# 步骤3:更新cur_max和cur_min
# cur_max = max(当前元素本身, 前一个最大×当前元素, 前一个最小×当前元素)
cur_max = max(nums[i], temp_max * nums[i], cur_min * nums[i])
# cur_min = min(当前元素本身, 前一个最大×当前元素, 前一个最小×当前元素)
cur_min = min(nums[i], temp_max * nums[i], cur_min * nums[i])
# 步骤4:更新全局最大乘积
if cur_max > result:
result = cur_max
# 步骤5:返回全局最大乘积
return result
偏神太多:伤官,偏财,七杀,羊刃,伤官配印,己土身旺,日坐七杀,杀印相生