代码随想录算法训练营

代码随想录算法训练营Day52 动态规划| 300.最长递增子序列 674. 最长连续递增序列 718. 最长重复子数组

300.最长递增子序列

题目链接:300.最长递增子序列

给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。

子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。

示例 1:

  • 输入:nums = [10,9,2,5,3,7,101,18]
  • 输出:4
  • 解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。

总体思路

首先通过本题大家要明确什么是子序列,“子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序”。

本题也是代码随想录中子序列问题的第一题,如果没接触过这种题目的话,本题还是很难的,甚至想暴力去搜索也不知道怎么搜。 子序列问题是动态规划解决的经典问题,当前下标i的递增子序列长度,其实和i之前的下表j的子序列长度有关系,那又是什么样的关系呢。

用动规五部曲来详细分析一波:

  1. dp[i]的定义

    本题中,正确定义dp数组的含义十分重要。

    dp[i]表示i之前包括i的以nums[i]结尾的最长递增子序列的长度

    为什么一定表示 “以nums[i]结尾的最长递增子序” ,因为我们在 做 递增比较的时候,如果比较 nums[j] 和 nums[i] 的大小,那么两个递增子序列一定分别以nums[j]为结尾 和 nums[i]为结尾, 要不然这个比较就没有意义了,不是尾部元素的比较那么 如何算递增呢。
  2. 状态转移方程

    位置i的最长升序子序列等于j从0到i-1各个位置的最长升序子序列 + 1 的最大值。

    所以:if (nums[i] > nums[j]) dp[i] = max(dp[i], dp[j] + 1);

    注意这里不是要dp[i] 与 dp[j] + 1进行比较,而是我们要取dp[j] + 1的最大值
  3. dp[i]的初始化

    每一个i,对应的dp[i](即最长递增子序列)起始大小至少都是1.
  4. 确定遍历顺序

    dp[i] 是有0到i-1各个位置的最长递增子序列 推导而来,那么遍历i一定是从前向后遍历。

    j其实就是遍历0到i-1,那么是从前到后,还是从后到前遍历都无所谓,只要吧 0 到 i-1 的元素都遍历了就行了。 所以默认习惯 从前向后遍历。

    遍历i的循环在外层,遍历j则在内层,代码如下:
for (int i = 1; i < nums.size(); i++) {
for (int j = 0; j < i; j++) {
if (nums[i] > nums[j]) dp[i] = max(dp[i], dp[j] + 1);
}
if (dp[i] > result) result = dp[i]; // 取长的子序列
}
  1. 举例推导dp数组

    输入:[0,1,0,3,2],dp数组的变化如下:



    如果代码写出来,但一直AC不了,那么就把dp数组打印出来,看看对不对!

    以上五部分析完毕,C++代码如下:
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
if (nums.size() <= 1) return nums.size();
vector<int> dp(nums.size(), 1);
int result = 0;
for (int i = 1; i < nums.size(); i++) {
for (int j = 0; j < i; j++) {
if (nums[i] > nums[j]) dp[i] = max(dp[i], dp[j] + 1);
}
if (dp[i] > result) result = dp[i]; // 取长的子序列
}
return result;
}
};

674. 最长连续递增序列

题目链接:674. 最长连续递增序列

给定一个未经排序的整数数组,找到最长且 连续递增的子序列,并返回该序列的长度。

连续递增的子序列 可以由两个下标 l 和 r(l < r)确定,如果对于每个 l <= i < r,都有 nums[i] < nums[i + 1] ,那么子序列 [nums[l], nums[l + 1], ..., nums[r - 1], nums[r]] 就是连续递增子序列。

示例 1:

  • 输入:nums = [1,3,5,4,7]
  • 输出:3
  • 解释:最长连续递增序列是 [1,3,5], 长度为3。尽管 [1,3,5,7] 也是升序的子序列, 但它不是连续的,因为 5 和 7 在原数组里被 4 隔开。

总体思路

本题对[[#300.最长递增子序列]]最大的区别在于“连续”。

本题要求的是最长连续递增序列

动规五部曲分析如下:

  1. 确定dp数组(dp table)以及下标的含义

    dp[i]:以下标i为结尾的连续递增的子序列长度为dp[i]

    注意这里的定义,一定是以下标i为结尾,并不是说一定以下标0为起始位置。
  2. 确定递推公式

    如果 nums[i] > nums[i - 1],那么以 i 为结尾的连续递增的子序列长度 一定等于 以i - 1为结尾的连续递增的子序列长度 + 1 。

    即:dp[i] = dp[i - 1] + 1;

    注意这里就体现出和[[#300.最长递增子序列]]的区别!

    因为本题要求连续递增子序列,所以就只要比较nums[i]与nums[i - 1],而不用去比较nums[j]与nums[i] (j是在0到i之间遍历)。

    既然不用j了,那么也不用两层for循环,本题一层for循环就行,比较nums[i] 和 nums[i - 1]。

    这里大家要好好体会一下!
  3. dp数组如何初始化

    以下标i为结尾的连续递增的子序列长度最少也应该是1,即就是nums[i]这一个元素。

    所以dp[i]应该初始1;
  4. 确定遍历顺序

    从递推公式上可以看出, dp[i + 1]依赖dp[i],所以一定是从前向后遍历。

    本文在确定递推公式的时候也说明了为什么本题只需要一层for循环,代码如下:
for (int i = 1; i < nums.size(); i++) {
if (nums[i] > nums[i - 1]) { // 连续记录
dp[i] = dp[i - 1] + 1;
}
}
  1. 举例推导dp数组



    注意这里要取dp[i]里的最大值,所以dp[2]才是结果!
class Solution {
public:
int findLengthOfLCIS(vector<int>& nums) {
if (nums.size() == 0) return 0;
int result = 1;
vector<int> dp(nums.size() ,1);
for (int i = 1; i < nums.size(); i++) {
if (nums[i] > nums[i - 1]) { // 连续记录
dp[i] = dp[i - 1] + 1;
}
if (dp[i] > result) result = dp[i];
}
return result;
}
};

718. 最长重复子数组

题目链接:718. 最长重复子数组

给两个整数数组 A 和 B ,返回两个数组中公共的、长度最长的子数组的长度。

示例:

输入:

  • A: [1,2,3,2,1]
  • B: [3,2,1,4,7]
  • 输出:3
  • 解释:长度最长的公共子数组是 [3, 2, 1] 。

    提示:
  • 1 <= len(A), len(B) <= 1000
  • 0 <= A[i], B[i] < 100

总体思路

注意题目中说的子数组,其实就是连续子序列。

要求两个数组中最长重复子数组,如果是暴力的解法 只需要先两层for循环确定两个数组起始位置,然后再来一个循环可以是for或者while,来从两个起始位置开始比较,取得重复子数组的长度。

本题其实是动规解决的经典题目,我们只要想到 用二维数组可以记录两个字符串的所有比较情况,这样就比较好推 递推公式了。 动规五部曲分析如下:

  1. 确定dp数组(dp table)以及下标的含义

    dp[i][j] :以下标i - 1为结尾的A,和以下标j - 1为结尾的B,最长重复子数组长度为dp[i][j]。 (特别注意: “以下标i - 1为结尾的A” 标明一定是 以A[i-1]为结尾的字符串 )

    此时细心的同学应该发现,那dp[0][0]是什么含义呢?总不能是以下标-1为结尾的A数组吧。

    其实dp[i][j]的定义也就决定着,我们在遍历dp[i][j]的时候i 和 j都要从1开始。

    那有同学问了,我就定义dp[i][j]为 以下标i为结尾的A,和以下标j 为结尾的B,最长重复子数组长度。不行么?

    行倒是行! 但实现起来就麻烦一点,需要单独处理初始化部分,在本题解下面的拓展内容里,我给出了 第二种 dp数组的定义方式所对应的代码和讲解,大家比较一下就了解了。
  2. 确定递推公式

    根据dp[i][j]的定义,dp[i][j]的状态只能由dp[i - 1][j - 1]推导出来。

    即当A[i - 1] 和B[j - 1]相等的时候,`dp[i][j] = dp[i - 1][j - 1] + 1;

    根据递推公式可以看出,遍历i 和 j 要从1开始!
  3. dp数组如何初始化

    根据dp[i][j]的定义,dp[i][0]dp[0][j]其实都是没有意义的!

    dp[i][0] dp[0][j]要初始值,因为 为了方便递归公式dp[i][j] = dp[i - 1][j - 1] + 1; 所以dp[i][0]dp[0][j]初始化为0。 举个例子A[0]如果和B[0]相同的话,dp[1][1] = dp[0][0] + 1,只有dp[0][0]`初始为0,正好符合递推公式逐步累加起来。
  4. 确定遍历顺序

    外层for循环遍历A,内层for循环遍历B。

    那又有同学问了,外层for循环遍历B,内层for循环遍历A。不行么?

    也行,一样的,我这里就用外层for循环遍历A,内层for循环遍历B了。

    同时题目要求长度最长的子数组的长度。所以在遍历的时候顺便把dp[i][j]的最大值记录下来
for (int i = 1; i <= nums1.size(); i++) {
for (int j = 1; j <= nums2.size(); j++) {
if (nums1[i - 1] == nums2[j - 1]) {
dp[i][j] = dp[i - 1][j - 1] + 1;
}
if (dp[i][j] > result) result = dp[i][j];
}
}
  1. 举例推导dp数组

    拿示例1中,A: [1,2,3,2,1],B: [3,2,1,4,7]为例,画一个dp数组的状态变化,如下:

// 版本一
class Solution {
public:
int findLength(vector<int>& nums1, vector<int>& nums2) {
vector<vector<int>> dp (nums1.size() + 1, vector<int>(nums2.size() + 1, 0));
int result = 0;
for (int i = 1; i <= nums1.size(); i++) {
for (int j = 1; j <= nums2.size(); j++) {
if (nums1[i - 1] == nums2[j - 1]) {
dp[i][j] = dp[i - 1][j - 1] + 1;
}
if (dp[i][j] > result) result = dp[i][j];
}
}
return result;
}
};

代码随想录算法训练营Day52 动态规划的更多相关文章

  1. 代码随想录算法训练营day01 | leetcode 704/27

    前言   考研结束半个月了,自己也简单休整了一波,估了一下分,应该能进复试,但还是感觉不够托底.不管怎样,要把代码能力和八股捡起来了,正好看到卡哥有这个算法训练营,遂果断参加,为机试和日后求职打下一个 ...

  2. 代码随想录算法训练营day02 | leetcode 977/209/59

    leetcode 977   分析1.0:   要求对平方后的int排序,而给定数组中元素可正可负,一开始有思维误区,觉得最小值一定在0左右徘徊,但数据可能并不包含0:遂继续思考,发现元素分布有三种情 ...

  3. 代码随想录算法训练营day22 | leetcode 235. 二叉搜索树的最近公共祖先 ● 701.二叉搜索树中的插入操作 ● 450.删除二叉搜索树中的节点

    LeetCode 235. 二叉搜索树的最近公共祖先 分析1.0  二叉搜索树根节点元素值大小介于子树之间,所以只要找到第一个介于他俩之间的节点就行 class Solution { public T ...

  4. 代码随想录算法训练营day17 | leetcode ● 110.平衡二叉树 ● 257. 二叉树的所有路径 ● 404.左叶子之和

    LeetCode 110.平衡二叉树 分析1.0 求左子树高度和右子树高度,若高度差>1,则返回false,所以我递归了两遍 class Solution { public boolean is ...

  5. 代码随想录算法训练营day13

    基础知识 二叉树基础知识 二叉树多考察完全二叉树.满二叉树,可以分为链式存储和数组存储,父子兄弟访问方式也有所不同,遍历也分为了前中后序遍历和层次遍历 Java定义 public class Tree ...

  6. 代码随想录算法训练营day12 | leetcode 239. 滑动窗口最大值 347.前 K 个高频元素

    基础知识 ArrayDeque deque = new ArrayDeque(); /* offerFirst(E e) 在数组前面添加元素,并返回是否添加成功 offerLast(E e) 在数组后 ...

  7. 代码随想录算法训练营day10 | leetcode 232.用栈实现队列 225. 用队列实现栈

    基础知识 使用ArrayDeque 实现栈和队列 stack push pop peek isEmpty() size() queue offer poll peek isEmpty() size() ...

  8. 代码随想录算法训练营day06 | leetcode 242、349 、202、1

    基础知识 哈希 常见的结构(不要忘记数组) 数组 set (集合) map(映射) 注意 哈希冲突 哈希函数 LeetCode 242 分析1.0 HashMap<Character, Inte ...

  9. 代码随想录算法训练营day03 | LeetCode 203/707/206

    基础知识 数据结构初始化 // 链表节点定义 public class ListNode { // 结点的值 int val; // 下一个结点 ListNode next; // 节点的构造函数(无 ...

  10. 代码随想录算法训练营day24 | leetcode 77. 组合

    基础知识 回溯法解决的问题都可以抽象为树形结构,集合的大小就构成了树的宽度,递归的深度构成的树的深度 void backtracking(参数) { if (终止条件) { 存放结果; return; ...

随机推荐

  1. 11.1/2 鼠标显示问题(harib08a)11.2 实现画面外的支持(harib08b)

    ps:能力有限,若有错误及纰漏欢迎指正.交流 11.1 鼠标显示问题(harib08a) 存在问题: ​ 在harib07d中鼠标移动到最右侧后就不能再往右移了 解决办法: 将 if (mx > ...

  2. kafka的原理及集群部署详解

    kafka原理详解 消息队列概述 消息队列分类 点对点 组成:消息队列(Queue).发送者(Sender).接收者(Receiver) 特点:一个生产者生产的消息只能被一个接受者接收,消息一旦被消费 ...

  3. 搭建良好编写体验的webgl编程环境 vscode+vit

    因为webgl代码是以字符串的形式嵌入在javascript代码中,这对于我们编写webgl代码的体验不友好,本文介绍如何搭建友好webgl编程环境: 需要安装的vscode插件 WebGL GLSL ...

  4. 教你如何用纯css代码实现太极阴阳鱼动画效果

    今天看到一个有意思的效果,闲来无事做一个: 把2d静态的太极图改成了3d,阴极和阳极分到了两个平面里实现旋转效果,这个好实现,重点是实现它的透明效果,平面太极图显示出两极是用另加的块元素挡住底面的颜色 ...

  5. 把一个列表拆成N个子列表的四种方法

    编程的方法往往不止一种,比如怎么把一个Python种的列表拆成N个子列表,我们可以很容易找到N种方法,也许这就是编程的魅力所在. 一.列表表达式法 这种方法最为简洁,不过可读性差一些 这个方法中,即使 ...

  6. ChatGPT 通识入门

    最近网络上对于Chat GPT的讨论热潮不断地膨胀,一个势必给整个人类社会带来新变革的科技和工具产生了.这个新的工具能够识别自然语言并能够理解上下文的语境,并能够具备人类思维的模型. 但是ChatGP ...

  7. 中英文拼写检测纠正开源项目使用入门 word-checker 1.1.0

    项目简介 word-checker 本项目用于单词拼写检查.支持英文单词拼写检测,和中文拼写检测. 特性说明 可以迅速判断当前单词是否拼写错误 可以返回最佳匹配结果 可以返回纠正匹配列表,支持指定返回 ...

  8. Java BIO,NIO,AIO

    一丶IO模型&Java IO Unix为程序员提供了以下5种基本的io模型: blocking io: 阻塞io nonblocking io: 非阻塞io I/O multiplexing: ...

  9. 方差分析3——正交表与正交实验设计(R语言)

    正交试验设计(orthogonal design简称正交设计(orthoplan),是利用正交表(orthogonal table)科学地安排与分析多因素试验的方法,是最常用的试验设计之一.正交表是一 ...

  10. day68:Vue:类值操作/style样式操作&v-for&filer/computed/watch&生命周期钩子函数&axios

    目录 1.类值操作 :class 2.style操作样式 :style 3:示例:选项卡 @click+:class 4.v-for示例:循环商品显示 5.过滤器:filter 6.计算属性:comp ...