day42 代码随想录算法训练营 动态规划专题10

1 今日打卡

最长连续递增序列 674. 最长连续递增序列 - 力扣(LeetCode)

最长递增子序列 300. 最长递增子序列 - 力扣(LeetCode)

最长重复子数组 718. 最长重复子数组 - 力扣(LeetCode)

2 dp五部曲

确定dp数组及其下标的含义

状态转移方程

初始化

遍历顺序

手动模拟

3 最长连续递增序列

3.1 思路

第一步:

dp[i] 表示:以数组中第 i 个元素(nums[i])结尾的最长连续递增子序列的长度。
为什么要定义 “以nums[i]结尾”?因为连续递增的特性要求子序列必须是连续的,nums[i] 能否接在 nums[i-1] 后面,只需要比较两者的大小,而 “以i结尾” 的定义能让我们通过前一个位置的结果推导出当前位置的结果。

第二步:

核心逻辑:如果当前元素 nums[i] 大于前一个元素 nums[i-1],说明可以把 nums[i] 接在以 nums[i-1] 结尾的递增子序列后面,此时 dp[i] = dp[i-1] + 1;
如果 nums[i] <= nums[i-1],说明无法形成连续递增,以 nums[i] 结尾的最长连续递增子序列只能是它自己,因此 dp[i] 保持初始化的 1 即可。

第三步:

每个元素自身可以构成一个长度为 1 的递增子序列,因此需要将 dp 数组的所有元素初始化为 1(Arrays.fill(dp, 1));
同时初始化结果变量 res = 1,因为数组至少有一个元素,最长长度不会小于 1。

第四步:

因为 dp[i] 的值依赖于 dp[i-1],所以需要从左到右遍历数组(i 从 1 开始,到 len-1 结束);
遍历过程中,每计算出一个 dp[i],就和 res 比较,更新 res 为当前最大值。

第五步:

以示例数组 nums = [1,3,5,4,7] 为例:
初始化:dp = [1,1,1,1,1],res = 1;
i=1(nums[1]=3 > nums[0]=1):dp[1] = 1+1=2,res 更新为 2;
i=2(nums[2]=5 > nums[1]=3):dp[2] = 2+1=3,res 更新为 3;
i=3(nums[3]=4 < nums[2]=5):dp[3] 保持 1,res 仍为 3;
i=4(nums[4]=7 > nums[3]=4):dp[4] = 1+1=2,res 还是 3;
最终返回 res=3,对应最长连续递增子序列 [1,3,5]。

3.2 实现代码

import java.util.Arrays; class Solution { public int findLengthOfLCIS(int[] nums) { // 1. 处理边界情况:如果数组为空,直接返回0 if (nums == null || nums.length == 0) { return 0; } // 获取数组长度 int len = nums.length; // 2. dp数组定义:dp[i]表示以nums[i]结尾的最长连续递增子序列的长度 int[] dp = new int[len]; // 3. 初始化:每个元素自身是长度为1的子序列,所以全部填充为1 Arrays.fill(dp, 1); // 初始化结果变量,最小为1(数组至少有一个元素) int res = 1; // 4. 遍历顺序:从左到右遍历,i从1开始(因为要比较i-1) for (int i = 1; i < len; i++) { // 状态转移方程:如果当前元素大于前一个元素,说明可以接在前面的子序列后 if (nums[i] > nums[i - 1]) { dp[i] = dp[i - 1] + 1; } // 每次更新当前的最大长度 if (dp[i] > res) { res = dp[i]; } } // 5. 返回最长连续递增子序列的长度 return res; } }

4 最长递增子序列

4.1 思路

第一步:

dp[i] 表示:以数组中第 i 个元素(nums[i])结尾的最长递增子序列的长度。
这个定义和 LCIS 一致,但因为 “不要求连续”,dp[i] 不再只依赖 dp[i-1],而是需要遍历 i 之前的所有元素 j(j < i)来推导。

第二步:

核心逻辑:对于每个 i,遍历所有 j < i,如果 nums[i] > nums[j],说明 nums[i] 可以接在以 nums[j] 结尾的递增子序列后面,此时候选长度为 dp[j] + 1;
我们需要在所有满足 nums[i] > nums[j] 的 dp[j] 中找到最大值,再加 1 作为 dp[i];如果没有任何 j 满足 nums[i] > nums[j],则 dp[i] 保持初始化的 1(自身构成子序列)。
公式总结:
plaintext
dp[i] = max(dp[j] + 1) (其中 j < i 且 nums[i] > nums[j])
若没有满足条件的 j → dp[i] = 1

第三步:

每个元素自身可以构成一个长度为 1 的递增子序列,因此 dp 数组所有元素初始化为 1(Arrays.fill(dp, 1));
结果变量 res 初始化为 0,遍历过程中不断更新为 dp 数组的最大值。

第四步:

外层循环:i 从 1 到 len-1(逐个处理每个结尾元素);
内层循环:j 从 0 到 i-1(遍历 i 之前的所有元素,寻找能接在后面的最长子序列);
先通过内层循环算出 dp[i],再用 dp[i] 更新 res,确保 res 始终是当前最长长度。

第五步:

以示例数组 nums = [10,9,2,5,3,7,101,18] 为例:
初始化:dp = [1,1,1,1,1,1,1,1],res = 0;
i=1(nums [1]=9 < nums [0]=10):无满足条件的 j,dp[1]=1,res=1;
i=2(nums[2]=2 < 10/9):dp[2]=1,res=1;
i=3(nums[3]=5 > nums[2]=2):dp[3] = dp[2]+1=2,res=2;
i=4(nums[4]=3 > nums[2]=2):dp[4] = dp[2]+1=2,res 仍为 2;
i=5(nums [5]=7 > 2/5/3):最大 dp[j]=2,dp[5]=3,res=3;
i=6(nums [6]=101 > 所有前序元素):最大 dp[j]=3,dp[6]=4,res=4;
i=7(nums [7]=18 > 2/5/3/7):最大 dp[j]=3,dp[7]=4,res 仍为 4;
最终返回 res=4,对应最长递增子序列(如 [2,5,7,101] 或 [2,3,7,18])。

4.2 实现代码

import java.util.Arrays; class Solution { public int lengthOfLIS(int[] nums) { // 获取数组长度 int len = nums.length; // 边界情况:长度为1返回1 if (len == 1) { return 1; } // 1. dp数组定义:dp[i]表示以nums[i]结尾的最长递增子序列的长度 int[] dp = new int[len]; // 2. 初始化:每个元素自身是长度为1的子序列,全部填充为1 Arrays.fill(dp, 1); // 初始化结果变量,记录全局最长长度 int res = 0; // 3. 遍历顺序:外层i从1开始(逐个处理每个结尾元素) for (int i = 1; i < len; i++) { // 记录i之前能接在nums[i]前面的最长子序列长度 int max = 0; // 内层j遍历0到i-1,寻找所有nums[j] < nums[i]的情况 for (int j = 0; j < i; j++) { // 如果nums[i] > nums[j],且dp[j]是当前找到的最大值 if (nums[i] > nums[j] && dp[j] > max) { max = dp[j]; } } // 4. 状态转移:dp[i] = 能接的最长子序列长度 + 1(自身) dp[i] = max + 1; // 更新全局最长长度 if (dp[i] > res) { res = dp[i]; } } // 5. 返回最长递增子序列的长度 return res; } }

5 最长重复子数组

5.1 思路

第一步:

定义 dp[i][j]:以 nums1[i-1] 为最后一个元素、以 nums2[j-1] 为最后一个元素的最长公共连续子数组的长度。
为什么用 i-1/j-1?
若直接用 dp[i][j] 对应 nums1[i]/nums2[j],则需要单独处理 i=0 或 j=0 的边界(比如数组第一个元素匹配时);而 dp[i][j] 对应 i-1/j-1 可以让 dp[0][*] 和 dp[*][0] 都为 0(空数组与任意数组的公共子数组长度为 0),简化初始化逻辑。

第二步:

核心逻辑:子数组是连续的,只有当前元素匹配时,才能延续前一个位置的公共长度;不匹配则长度归 0。
若 nums1[i-1] == nums2[j-1]:当前元素匹配,公共子数组长度 = 前一个位置的公共长度 + 1 → dp[i][j] = dp[i-1][j-1] + 1;
若 nums1[i-1] != nums2[j-1]:当前元素不匹配,以这两个元素结尾的公共子数组中断,长度为 0 → dp[i][j] = 0(代码中未显式写这行,因为数组默认初始化值就是 0,效果等价)。

第三步:

dp 是 (len1+1) × (len2+1) 的二维数组,所有元素默认初始化为 0;
dp[0][j] = 0(nums1 取前 0 个元素,即空数组,和 nums2 前 j 个元素无公共子数组);
dp[i][0] = 0(nums2 取前 0 个元素,同理无公共子数组);
结果变量 res = 0,用于记录遍历过程中出现的最大公共子数组长度。

第四步:

因为 dp[i][j] 的值依赖于左上角的 dp[i-1][j-1],所以必须按从上到下、从左到右的顺序遍历:
外层循环:i 从 1 到 len1(遍历 nums1 的每个元素,对应 nums1[i-1]);
内层循环:j 从 1 到 len2(遍历 nums2 的每个元素,对应 nums2[j-1]);
每计算完一个 dp[i][j],就和 res 比较,更新 res 为当前最大值。

第五步:

以 nums1 = [1,2,3,2,1]、nums2 = [3,2,1,4,7] 为例:
初始化:dp 数组全为 0,res=0;
i=1(nums1 [0]=1):
j=1(nums2 [0]=3)→ 不匹配 → dp [1][1]=0,res=0;
j=2(nums2 [1]=2)→ 不匹配 → dp [1][2]=0,res=0;
j=3(nums2 [2]=1)→ 匹配 → dp [1][3] = dp [0][2]+1=1,res=1;
i=3(nums1 [2]=3):
j=1(nums2 [0]=3)→ 匹配 → dp [3][1] = dp [2][0]+1=1,res=1;
i=4(nums1 [3]=2):
j=2(nums2 [1]=2)→ 匹配 → dp [4][2] = dp [3][1]+1=2,res=2;
i=5(nums1 [4]=1):
j=3(nums2 [2]=1)→ 匹配 → dp [5][3] = dp [4][2]+1=3,res=3;
最终返回 res=3,对应最长公共子数组 [3,2,1]。

5.2 实现代码

class Solution { public int findLength(int[] nums1, int[] nums2) { // 获取两个数组的长度 int len1 = nums1.length, len2 = nums2.length; // 1. 定义dp数组:dp[i][j]表示以nums1[i-1]和nums2[j-1]结尾的最长公共子数组长度 // 维度为(len1+1)x(len2+1),默认初始值为0,无需额外初始化 int[][] dp = new int[len1 + 1][len2 + 1]; // 记录全局最长公共子数组的长度,初始为0 int res = 0; // 2. 遍历顺序:从上到下、从左到右(i从1开始对应nums1[i-1]) for(int i = 1; i <= len1; i++) { for(int j = 1; j <= len2; j++) { // 3. 状态转移方程:当前元素匹配时,延续前一个位置的长度+1 if(nums1[i-1] == nums2[j-1]) { // 修正原代码的nums[i]/nums[j]笔误 dp[i][j] = dp[i-1][j-1] + 1; } // 4. 更新全局最大值:只要当前dp[i][j]更大,就更新res if(dp[i][j] > res) { res = dp[i][j]; } } } // 5. 返回最长公共子数组的长度 return res; } }

Read more

【python】第六节anacoda+配置Jupyter notebook

先下载安装好anacoda →Advance AI with Open Source | Anaconda 打开安装好的anaconda 通过cmd打开 可以在桌面创建快捷方式 为什么分析数据大家要使用jupyter book呢 有的时候一个py文件数据量太大,并非想要全部都要让它运行,那会浪费太多的时间了,所以使用Jupyter book 1.交互模式,交互模式之下不用print打印语句,我们就可以看到结果 2.html格式直接分享,可以看到你的思考模式 3.可以用Latex插入公式 安装Jupyter book 打开终端,windows 在开始的地方输入cmd 输入pip install notebook 等待安装结束之后,输入jupyter book查看是否安装完毕 浏览器弹出一个notebook窗口,说明安装成功啦 终止jupyter notebook 不光要叉掉浏览器,还要在终端的地方按住control c,在他询问你是否关闭的时候输入 y 后台服务器就会终止了 jupyter notebook的使用 打开cmd

By Ne0inhk
【数据结构初阶】--快速排序进阶

【数据结构初阶】--快速排序进阶

🔥个人主页:@草莓熊Lotso 🎬作者简介:C++研发方向学习者 📖个人专栏: 《C语言》 《数据结构与算法》《C语言刷题集》《Leetcode刷题指南》 ⭐️人生格言:生活是默默的坚持,毅力是永久的享受。 前言: 在之前的博客中我们实现了递归版本和非递归版本的快速排序,其中递归版本中的找基准的方法我们学习了三种。但是有些特殊的情况,比如重复元素过多或者已经有序的时候,我们的时间效率就会受到影响了,这次的进阶篇中,我们会通过一些方法来优化快速排序 目录 一.三数取中和随机数选择基准 三数取中法: 随机数选择法:  两种方法的对比分析 :  二.三路划分 实现步骤:  代码实现:  三路划分和传统二路划分思路的对比:   三.自省排序 核心思想:  代码实现: 一.三数取中和随机数选择基准 三数取中法: 原理:从子数组的首元素、尾元素、中间元素中选择中位数作为基准。通过选取中间大小的值,避免极端值(如最大/最小值)作为基准,从而平衡左右子数组的划分。 核心逻辑:

By Ne0inhk

从零到一:BLDC/PMSM恒功率算法在智能家居风扇中的实战应用

智能家居风扇的BLDC/PMSM恒功率控制:从算法设计到用户体验优化 1. 引言:智能家居风扇的特殊需求 在炎热的夏季,一台安静、节能且能自动调节风速的智能风扇已经成为现代家庭的标配。传统交流电机风扇的嗡嗡声和忽高忽低的转速早已无法满足追求品质生活的用户需求。BLDC(无刷直流)和PMSM(永磁同步)电机凭借其高效率、低噪音和精准控制特性,正在重塑智能家居风扇市场。 与工业场景不同,家用风扇对电机控制提出了独特挑战: * 静音优先:卧室环境下,30分贝以下的运行噪音是基本要求 * 能效敏感:全年不间断使用使得每瓦功耗都影响电费账单 * 交互友好:需要响应手机APP、语音控制等多模式指令 * 安全可靠:长时间运行必须杜绝过热风险 恒功率算法在这其中扮演着关键角色——它不仅是保护电机和电子元件的安全阀,更是实现"智能"的基础。当用户设定"睡眠模式"时,算法需要动态平衡风量、噪音和功耗;当检测到电压波动时,又要确保转速稳定不突变。这些需求催生了专为智能家居优化的BLDC/PMSM控制方案。 2. 恒功率控制的核心原理 2.

By Ne0inhk
12306反反爬虫策略:Python网络请求优化实战

12306反反爬虫策略:Python网络请求优化实战

一、引言:12306反爬虫的严峻挑战 12306作为中国铁路售票系统,每天面临着海量的抢票请求,其反爬虫机制异常严格:IP封锁、验证码、请求频率限制、会话追踪等。要在这样的环境下实现稳定抢票,必须设计一套完善的反反爬虫策略。12306抢票项目通过CDN加速、代理IP、请求频率控制和"小黑屋"机制等技术,成功突破了12306的反爬虫防线。 二、CDN加速:突破网络瓶颈 1. 实现原理 CDN(内容分发网络)通过将资源分发到全球各地的节点,使用户可以就近获取所需内容,提高访问速度。12306项目通过筛选和使用高速CDN节点,加速与12306服务器的通信。 2. 代码实现 核心文件:d:\python-code\12306-master\init\select_ticket_info.py defcdn_certification(self):"""CDN认证与筛选&

By Ne0inhk