蓝桥杯python编程每日刷题 day 20

题目:

给定一个长度为 N 的整数序列:A1, A2, · · · , AN。现在你有一次机会,将其中连续的 K 个数修改成任意一个相同值。请你计算如何修改可以使修改后的数列的最长不下降子序列最长,请输出这个最长的长度。

最长不下降子序列是指序列中的一个子序列,子序列中的每个数不小于在它之前的数。

输入格式

输入第一行包含两个整数 N 和 K。

第二行包含 N 个整数 A1, A2, · · · , AN。

输出格式

输出一行包含一个整数表示答案。

(1)代码:

##########################输入部分

n, k = map(int, input().split())

arr = int(i) for i in input().split()

l = n

notk_max = 0

zd = 0 for i in range(l)

######################################代码部分

def bs(t,a,mid):

if t:

return mid <= a

else:

return mid >= a

def erf(t,dp,a):#找到新元素在单调栈中的位置,t为False是自左往右第一个小于a,t为True是自左往右第一个大于a

if not dp:

return -1

l,r=0,len(dp)-1

while l<r:

mid=(l+r)//2

if bs(t,a,dpmid):#缩头

l=mid+1

else:

r=mid

return -1 if bs(t,a,dpl) else l

def zhao2(): # 得出以我结尾的的最长子序列

global zd, notk_max, arr, k, l

dp = \[\]

for i in range(l - k):

wz = erf(True, dp, arri)

if wz < 0:

dp.append(arri)

zdi += len(dp)

else:

dpwz = arri

zdi += (wz + 1)

if zdi > notk_max:

notk_max = zdi

def zhao1(): # 得出以我开头的最长子序列 (不包括自己),以及k在最左侧的not_kmax

global zd, notk_max, arr, k, l

dp = \[\]

for i in range(l - 1, k, -1):

wz = erf(False, dp, arri)

if wz < 0:

dp.append(arri)

else:

dpwz = arri

#######以下为zd赋值

wz = erf(False, dp, arri - k - 1)

if wz < 0:

zdi - k - 1 = len(dp)

else:

zdi - k - 1 = wz

########以下得出k覆盖最左 最长不下降子序列长度

notk_max = len(dp)

wz = erf(False, dp, arrk)

if wz < 0:

notk_max += 1

##############################################输出部分

zhao1()

zhao2()

print(notk_max + k)

(1)解析:

1.在代码中的前两个函数为辅助判断二次搜查

def bs(t,a,mid):
if t:
return mid <= a
else:
return mid >= a

def erf(t,dp,a):#找到新元素在单调栈中的位置,t为False是自左往右第一个小于a,t为True是自左往右第一个大于a
if not dp:
return -1
l,r=0,len(dp)-1
while l<r:
mid=(l+r)//2
if bs(t,a,dpmid):#缩头
l=mid+1
else:
r=mid
return -1 if bs(t,a,dpl) else l
首先我们用t = True,来表示找到在dp这个列表中,比a大的数,首先用二分搜查,来确定比a大的属的序列在哪里,用辅助函数bs来判断,当t为True时,dpmid比a小,说明此时mid不是我们要找的序列,将l更新,直到找到我们需要的序列,最后返回l

接下来对主题函数的思想进行分析,首先将k想象为一个可以覆盖元素的滑动窗口,自左往右滑动,k每滑动一次我们就从没有被k覆盖的元素中找最长不下降子序列。而k自左往右滑动完,出现过的最长的最长不下降子序列的长度+k就是我们要的答案

案例 :1,4,2,8,5 。k=2 max=0

(1)k覆盖1,4。从剩余的2,8,5找得最长不下降子序列,2,8。或2,5。max=2

(2)k覆盖4,2。从剩余的1,8,5找得最长不下降子序列 1,8。1,5。max不变

(3)k覆盖2,8。从剩余的1,4,5找得最长不下降子序列1,4,5。max=3

(4)k覆盖8,5。.........max不变

最终max+k就是我们要的答案

2.zhao2的作用是将前(n-k)个数的最佳排序调整到notk------max,并解释一下global用法,用 global 之后的变量将不只局限函数,而是放大于整体程序,在函数中变量的变值可以在函数以外的程序中使用

def zhao2(): # 得出以我结尾的的最长子序列
global zd, notk_max, arr, k, l
dp = \[\]
for i in range(l - k):(此时l为n)
wz = erf(True, dp, arri)
if wz < 0:
dp.append(arri)
zdi += len(dp)
else:
dpwz = arri
zdi += (wz + 1)
if zdi > notk_max:
notk_max = zdi

关键操作 作用
erf(True, dp, arr[i]) 找到 arr[i] 在 dp 中的插入位置(第一个 >arr[i] 的数)。
dp.append(arr[i]) 扩展 LNDS 长度(当前数字比所有 dp 值大)。
dp[wz] = arr[i] 替换 dp 中的值,保持最小末尾(优化后续可能性)。
zd[i] 赋值 记录以 arr[i] 结尾的 LNDS 长度。

第1步:处理 arr[0] = 3

  1. 查找插入位置 :
    • erf(True, [], 3) → 空栈直接返回 -1。
  2. 更新 dp 和 zd :
    • wz = -1 → 执行 dp.append(3) → dp = [3]。
    • zd[0] = len(dp) = 1(以 3 结尾的 LNDS 长度为 1)。
  3. 更新全局最大值 :
    • notk_max = max(0, 1) = 1。
i arri dp 变化 zd notk_max
0 3 [3] [1,0,0,0,0] 1

​第2步:处理 arr[1] = 1

  1. 查找插入位置 :
    • erf(True, [3], 1) → 找第一个 >1 的数(3 在索引 0)。
    • 返回 0。
  2. 更新 dp 和 zd :
    • wz = 0 → 替换 dp[0] = 1 → dp = [1]。
    • zd[1] = wz + 1 = 1(以 1 结尾的 LNDS 长度为 1)。
  3. 全局最大值 :
    • notk_max 保持 1(未更新)。
i arri dp 变化 zd notk_max
1 1 [1] [1,1,0,0,0] 1

​第3步:处理 arr[2] = 4

  1. 查找插入位置 :
    • erf(True, [1], 4) → 找第一个 >4 的数(无,返回 -1)。
  2. 更新 dp 和 zd :
    • wz = -1 → dp.append(4) → dp = [1, 4]。
    • zd[2] = len(dp) = 2(以 4 结尾的 LNDS 为 [1, 4])。
  3. 全局最大值 :
    • notk_max = max(1, 2) = 2。
i arri dp 变化 zd notk_max
2 4 [1, 4] [1,1,2,0,0] 2

​第4步:处理 arr[3] = 2

  1. 查找插入位置 :
    • erf(True, [1, 4], 2) → 找第一个 >2 的数(4 在索引 1)。
    • 返回 1。
  2. 更新 dp 和 zd :
    • wz = 1 → 替换 dp[1] = 2 → dp = [1, 2]。
    • zd[3] = wz + 1 = 2(以 2 结尾的 LNDS 为 [1, 2])。
  3. 全局最大值 :
    • notk_max 保持 2。
i arri dp 变化 zd notk_max
3 2 [1, 2] [1,1,2,2,0] 2

​3. 最终结果

  • zd = [1, 1, 2, 2, 0]:
    • zd[0]=1:子序列 [3]
    • zd[1]=1:子序列 [1]
    • zd[2]=2:子序列 [1, 4]
    • zd[3]=2:子序列 [1, 2]
  • notk_max = 2(前 n-k=4 个元素的最长 LNDS 长度)。

3.接下来为后(n-k)个元素的最佳排序,首先对

从右向左扫描,计算以每个位置开头的 LNDS 长度

变量 作用
dp 动态维护的单调栈(反向递减),存储 ​不同长度反向 LNDS 的最大起始值
zd[i] 记录以 arr[i] 开头的 LNDS 长度(用于后续拼接)
notk_max 全局最大值,记录反向扫描后的最长 LNDS 长度(用于处理 k 在最左侧的情况)

def zhao1(): # 得出以我开头的最长子序列 (不包括自己),以及k在最左侧的not_kmax
global zd, notk_max, arr, k, l
dp = \[\]
for i in range(l - 1, k, -1):
wz = erf(False, dp, arri)
if wz < 0:
dp.append(arri)
else:
dpwz = arri
#######以下为zd赋值
wz = erf(False, dp, arri - k - 1)
if wz < 0:
zdi - k - 1 = len(dp)
else:
zdi - k - 1 = wz
########以下得出k覆盖最左 最长不下降子序列长度
notk_max = len(dp)
wz = erf(False, dp, arrk)
if wz < 0:
notk_max += 1

在wz = erf(False, dp, arri - k - 1)以上的代码用于更新dp之后的代码适用于将前(n-k)个数与后(n-k)连接起来

i - k - 1 的物理意义
  • **i**:当前反向遍历的位置(从 n-1 到 k+1)。
  • **k**:允许修改的连续元素个数。
  • **i - k - 1:
    ​
    表示前 n - k 个元素中,与当前反向位置 i 对应的"连接点"**。
    这个位置是 前 n - k 个元素的尾部 ,用于检查是否能与后 n - k 个元素的头部拼接。
​为什么需要 i - k - 1?
  • 动态拼接的核心 :
    修改 k 个元素后,前 n - k 和后 n - k 部分的子序列需要通过某个值连接。
    i - k - 1 标记了前 n - k 部分的最后一个元素,用于判断是否能与后 n - k 部分的第一个元素形成不下降序列。
​具体例子说明

输入 :arr = [3, 1, 4, 2, 8],n=5,k=1(修改 1 个元素)。

  • 前 n - k = 4 个元素 :[3, 1, 4, 2](不修改部分)。
  • 反向遍历范围 :i = 4, 3, 2(对应 arr[4]=8, arr[3]=2, arr[2]=4)。
​步骤解析:​
  1. ​**i = 4(arr[4] = 8)​**:

    • i - k - 1 = 2(即 arr[2] = 4)。
    • 作用 :检查 4(前部分的最后一个值)是否能与 8(后部分的第一个值)拼接。
      • 若 4 <= 8,则可以连接,此时 zd[2] 记录反向 LNDS 长度。
      • 本例中 4 <= 8,zd[2] 被更新为反向长度 1([8])。
  2. ​**i = 3(arr[3] = 2)​**:

    • i - k - 1 = 1(即 arr[1] = 1)。
    • 作用 :检查 1 是否能与 2 拼接。
      • 1 <= 2,可以连接,zd[1] 更新为反向长度 2([2, 8])。
  3. ​**i = 2(arr[2] = 4)​**:

    • i - k - 1 = 0(即 arr[0] = 3)。
    • 作用 :检查 3 是否能与 4 拼接。
      • 3 <= 4,可以连接,zd[0] 更新为反向长度 2([4, 8])。
​关键作用总结
场景 i - k - 1 的作用
​反向遍历时 定位前 n - k 个元素的尾部(arr[i - k - 1]),判断是否能与后 n - k 的头部连接。
​更新 zd 记录前 n - k 个元素中每个位置能向后延伸的 LNDS 长度,用于后续全局拼接。
​修改 k 的灵活性 通过 zd 和 notk_max 的配合,动态计算修改 k 个元素后的最优解。
​为什么这样设计?
  • 高效性:避免显式枚举所有可能的修改方案,通过反向扫描直接计算可能的最优连接点。
  • 正确性 :确保前 n - k 和后 n - k 部分的子序列能通过某个值(可能是修改后的 k 个元素)无缝拼接。
​最终结果的意义
  • **zd 数组**:
    • zd[i] 表示以 arr[i] 开头或结尾的 LNDS 长度。
    • 例如 zd = [2, 2, 1, 2, 0] 表示:
      • 从 3 开头的 LNDS 长度为 2([3, 4])。
      • 从 1 开头的 LNDS 长度为 2([1, 2])。
  • **notk_max + k**:
    结合正向和反向结果,允许修改 k 个元素时的最长 LNDS(如 [1, 4, 8] 长度为 3)。

​总结

i - k - 1 是算法中 ​连接前后子序列的桥梁 ,通过反向扫描动态更新 zd 数组,确保能高效计算出修改 k 个元素后的最优解。其核心思想是:
​"前 n - k 的末尾必须 ≤ 后 n - k 的开头"​ ,而 i - k - 1 正是找到这个关键连接点的索引。

4.接下来解释如何取得最大的值

首先我们已经取得了前(n-k)个数和后(n-k)个数的最佳排序,用notk_max代替两个值相加,再用其加上k即可
notk_max = len(dp)
wz = erf(False, dp, arrk)
if wz < 0:
notk_max += 1

数字示例

​输入:
  • arr = [3, 1, 4, 2, 8],n=5,k=1(修改 1 个元素)。
  • 前 n-k=4 个元素 :[3, 1, 4, 2]。
  • 后 n-k=4 个元素 :[1, 4, 2, 8](反向扫描从 i=4 到 i=1)。
​步骤解析:
​**(1) zhao2() 正向扫描后:​**
  • zd = [1, 1, 2, 2, 0](前 n-k 部分的 LNDS 长度):
    • zd[0]=1:[3]
    • zd[1]=1:[1]
    • zd[2]=2:[1,4]
    • zd[3]=2:[1,2]
​**(2) zhao1() 反向扫描时:​**
  • 初始化 :dp = [](反向单调栈),notk_max = 0。
  • 处理 i=4(arr[4]=8)​ :
    • dp = [8](反向 LNDS 长度 len(dp)=1)。
    • i-k-1=2(arr[2]=4):
      • 检查 4 <= 8(可以拼接)。
      • notk_max = max(0, zd[2] + len(dp)) = max(0, 2+1) = 3。
  • 处理 i=3(arr[3]=2)​ :
    • dp = [8, 2](反向 LNDS 长度 len(dp)=2)。
    • i-k-1=1(arr[1]=1):
      • 检查 1 <= 2(可以拼接)。
      • notk_max = max(3, zd[1] + len(dp)) = max(3, 1+2) = 3(未更新)。
  • 处理 i=2(arr[2]=4)​ :
    • dp = [8, 4](替换 2 为 4,保持递减)。
    • i-k-1=0(arr[0]=3):
      • 检查 3 <= 4(可以拼接)。
      • notk_max = max(3, zd[0] + len(dp)) = max(3, 1+2) = 3(未更新)。
  • 最终 notk_max = 3 :
    • 对应子序列 [1,4,8](前 [1] + 后 [4,8],修改 arr[0]=1)。
​**(3) 输出 notk_max + k = 3 + 1 = 4**:
  • 实际最长 LNDS 为 [1,1,4,8](修改 arr[0]=1,长度 4)。

​关键点总结

操作 数学意义 示例中的作用
zd[i](正向) 前 n-k 部分以 arr[i] 结尾的 LNDS 长度。 zd[2]=2 表示 [1,4]。
len(dp)(反向) 后 n-k 部分以 arr[i] 开头的 LNDS 长度。 dp=[8,4] 表示 [4,8]。
notk_max 更新 取 zd[i-k-1] + len(dp) 的最大值,确保前后部分能拼接。 max(3, 2+1)=3([1,4,8])。
+ k 修改的 k 个元素作为桥梁,连接前后子序列。 长度 3 + 1 = 4([1,1,4,8])。

​为什么这样能覆盖所有情况?

  • 不修改 k 的情况 :
    notk_max 已经记录了前/后部分的最长拼接(如 [1,4,8] 长度 3)。
  • 修改 k 的情况 :
    通过 + k 直接扩展长度(如修改 arr[0]=1,得到 [1,1,4,8] 长度 4)。
  • 数学保证 :
    最长 LNDS 必然由以下两种之一构成:
    1. 不修改 k 时的 notk_max。
    2. 修改 k 时的 notk_max + k(因为修改的 k 个元素可以完美连接前后部分)。
相关推荐
171320330计算机毕设编程8 小时前
计算机毕业设计 基于YOLO改进算法的车辆特征识别系统设计与实现--lw
java·大数据·python·sql·算法
姜鱼问生8 小时前
Nginx gzip 压缩:一个配置让页面加载快 3 倍
后端·算法·架构
不会就选b8 小时前
算法日常・每日刷题--<动态规划>23
算法·动态规划
GreatSQL社区9 小时前
主从复制SQL线程中断——字符集砸出的坑
java·数据库·算法
Tisfy10 小时前
LeetCode 2333.最小差值平方和:排序 + 数学计算(不用二分)
数学·算法·leetcode·题解·贪心·排序
aqiu11111110 小时前
【C++算法打怪专栏】LeetCode 217. 存在重复元素(解法总结)
c++·算法·leetcode
yi01111 小时前
DAY28: LeetCode 111. 二叉树的最小深度
人工智能·笔记·python·算法·leetcode·二叉树·递归
wuyk55512 小时前
27.LeetCode 2265. 统计值等于子树平均值的节点数 —— 后序遍历实战
stm32·单片机·嵌入式硬件·算法
xiangyun6112 小时前
C++ list 模拟实现(二):const迭代器与反向迭代器,模板的黑魔法
开发语言·数据库·c++·算法·stl·list