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

news2024/11/16 6:35:10

刷题

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 。

示例 2:

  • 输入:nums = [0,1,0,3,2,3]

  • 输出:4

示例 3:

  • 输入:nums = [7,7,7,7,7,7,7]

  • 输出:1

提示:

  • 1 <= nums.length <= 2500

  • -10^4 <= nums[i] <= 104

思路及实现

子序列问题是动态规划解决的经典问题,当前下标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]; // 取长的子序列
}

5.举例推导dp数组

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

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

以上五部分析完毕,代码如下:

class Solution {
    public int lengthOfLIS(int[] nums) {
        int[] dp = new int[nums.length];
        int res = 0;
        Arrays.fill(dp, 1);
        for (int i = 1; i < dp.length; i++) {
            for (int j = 0; j < i; j++) {
                if (nums[i] > nums[j]) {
                    dp[i] = Math.max(dp[i], dp[j] + 1);
                }
                res = Math.max(res, dp[i]);
            }
        }
        return res;
    }
}

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 隔开。

示例 2:

  • 输入:nums = [2,2,2,2,2]

  • 输出:1

  • 解释:最长连续递增序列是 [2], 长度为1。

提示:

  • 0 <= nums.length <= 10^4

  • -10^9 <= nums[i] <= 10^9

思路及实现

动规五部曲分析如下:

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;
    }
}

5.举例推导dp数组

已输入nums = [1,3,5,4,7]为例,dp数组状态如下:

注意这里要取dp[i]里的最大值,所以dp[2]才是结果!

以上分析完毕,代码如下:

public static int findLengthOfLCIS(int[] nums) {
        int[] dp = new int[nums.length];
        for (int i = 0; i < dp.length; i++) {
            dp[i] = 1;
        }
        int res = 1;
    //可以注意到,這邊的 i 是從 0 開始,所以會出現和卡哥的C++ code有差異的地方,在一些地方會看到有 i + 1 的偏移。
        for (int i = 0; i < nums.length - 1; i++) {
            if (nums[i + 1] > nums[i]) {
                dp[i + 1] = dp[i] + 1;
            }
            res = res > dp[i + 1] ? res : dp[i + 1];
        }
        return res;
    }

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开始。

那有同学问了,我就定义dpi为 以下标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了。

同时题目要求长度最长的子数组的长度。所以在遍历的时候顺便把dpi的最大值记录下来。

代码如下:

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];
    }
}

5.举例推导dp数组

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

以上五部曲分析完毕,代码如下:

// 版本一
class Solution {
    public int findLength(int[] nums1, int[] nums2) {
        int result = 0;
        int[][] dp = new int[nums1.length + 1][nums2.length + 1];
        
        for (int i = 1; i < nums1.length + 1; i++) {
            for (int j = 1; j < nums2.length + 1; j++) {
                if (nums1[i - 1] == nums2[j - 1]) {
                    dp[i][j] = dp[i - 1][j - 1] + 1;
                    result = Math.max(result, dp[i][j]);
                }
            }
        }
        
        return result;
    }
}
​
// 版本二: 滚动数组
class Solution {
    public int findLength(int[] nums1, int[] nums2) {
        int[] dp = new int[nums2.length + 1];
        int result = 0;
​
        for (int i = 1; i <= nums1.length; i++) {
            for (int j = nums2.length; j > 0; j--) {
                if (nums1[i - 1] == nums2[j - 1]) {
                    dp[j] = dp[j - 1] + 1;
                } else {
                    dp[j] = 0;
                }
                result = Math.max(result, dp[j]);
            }
        }
        return result;
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1315257.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

vue脚手架安装及使用

准备工作 安装node安装cnpm cnpm是npm的“廉价平替” 提高安装速度 npm install -g cnpm --registryhttps://registry.npm.taobao.org 安装脚手架 安装Vue脚手架 cnpm install -g vue/cli 用vue脚手架创建vue项目 找好创建项目的位置 创建项目 vue create test (test为项…

磁盘及文件系统(上)

这次博客我们将重点理解Ext2文件系统。 首先我们要理解什么是文件系统。 在之前我们一直理解的文件都是一个被打开的文件&#xff0c;而os为了能够管理这样的文件创建了struct_file这样的结构体对象在内核中描述被打开的文件&#xff0c;这个结构体对象中包含了被打开文件的基…

【PostgreSQL】从零开始:(一)初识PostgreSQL

从零开始:&#xff08;一&#xff09;初识PostgreSQL PostgreSQL数据库介绍为什么使用 PostgreSQL&#xff1f;那么多最终用户,云厂商为什么要贡献核心代码&#xff1f;基于PostgreSQL底层开发的好处&#xff1a;为什么要学习PostgreSQL&#xff1f;截止本文发布之日&#xff0…

鸿蒙系统(HarmonyOS)之方舟框架(ArkUI)介绍

鸿蒙开发官网&#xff1a;HarmonyOS应用开发官网 - 华为HarmonyOS打造全场景新服务 方舟开发框架&#xff08;简称&#xff1a;ArkUI&#xff09;&#xff0c;是一套构建HarmonyOS应用界面的UI开发框架&#xff0c;它提供了极简的UI语法与包括UI组件、动画机制、事件交互等在内…

鸿蒙app获取文本控件按钮控件_修改控件名称_按钮触发事件_提示信息显示

鸿蒙app获取文本控件按钮控件_修改控件名称_按钮触发事件_ 点击启动&#xff1a;提示信息显示 package com.example.myapplication.slice;import com.example.myapplication.ResourceTable; import ohos.aafwk.ability.AbilitySlice; import ohos.aafwk.content.Intent; impor…

linux 文本信息查询grep;控制命令执行和管道操作符号

1、grep grep "keyword" /path/to/logfile获取查询结果最后一行 grep "runs/detect/train" test4.log | tail -n 12、linux控制命令执行和管道操作符号 &、|、; 和 &&、》、>、< ##例子&#xff1b;wandb disabled && yolo …

synchronized关键字的使用和原理

synchronized关键字的使用和原理 synchronized&#xff1a;对象锁&#xff0c;保证了临界区内代码的原子性&#xff0c;采用互斥的方式让同一时刻至多只有一个线程能持有对象锁&#xff0c;其它线程获取这个对象锁时会阻塞&#xff0c;保证拥有锁的线程可以安全的执行临界区内…

vue-实现高德地图-省级行政区地块显示+悬浮显示+标签显示

<template><div><div id"container" /><div click"showFn">显示</div><div click"removeFn">移除</div></div> </template><script> import AMapLoader from amap/amap-jsapi-load…

SpringBoot - application.yml 多环境切换解决方案

问题描述 这个问题玩过 SpringCloud 的小伙伴估计会想到用 bootstrap.yml 来解决这个问题。但是如果说为了解决这个问题引入了一堆的 SpringCloud Jar&#xff0c;就感觉杀鸡用牛刀。 于是今天我们想只有 application.yml 自己就可以解决这个问题&#xff0c;如何搞定&#x…

真正可行的vue3迁移到nuxt3方法(本人亲测,完全避坑)

终于到了总结经验的时候了&#xff0c;这绝对是全网唯一、完全真正可行的干货。 在我看来&#xff0c;知识就是要拿来分享的&#xff0c;分享给他人也是在提高自己。我绝对不会搞什么订阅或者vip专栏来搞钱坑害各位&#xff0c; 因为我在csdn写文章最主要的目的是为了记录和总…

Chrome限制第三方Cookie:未来无法再追踪你看过哪些敏感的“色色”内容了

当我们在浏览网络的时候&#xff0c;常听到「Cookie」这个词&#xff0c;但许多人可能不太清楚它到底是什么。最近&#xff0c;Google 宣布了一项重要更新&#xff0c;Google Chrome 将减少对第三方cookie 的支持&#xff0c;以提高用户隐私保护。 下面我会解释一下这个改变对…

华为数通——路由冗余和备份

注&#xff1a;当一条路由的出接口down时&#xff0c;该路由会自动失效。 要求&#xff1a;数据优先走千兆链路。 R1 [ ]ip route-static 172.16.1.0 24 12.1.1.2 目的地址 掩码 下一条 [ ]ip route-static 172.16.1.0 24 21.1.1.2 preference 50 目的地址 …

ETLCloud的应用策略——实时数据处理是关键

一、ETLCloud是什么&#xff1f; ETLCloud又称数据集成&#xff08;DataOps&#xff09;&#xff0c;是RestCloud旗下的一款数据仓库管理工具&#xff0c;通过自动化数据转换和集成来实现企业内部和外部数据的无缝对接&#xff0c;从而帮助企业快速获取准确的数据信息&#xff…

vue3+leaflet天地图开发

<script setup> import { onMounted, onUnmounted, ref } from "vue"; // todo 项目使用请放开 leaflet 引入 // import L from leaflet;const emit defineEmits(["mapLoad"]);var markers ref([]); const mapRef ref(); const marker ref(); co…

Spring对JUnit4和junit5的支持

Junit4支持 第一步&#xff1a;准备工作&#xff1a; 引入JUnit4的依赖&#xff0c;Spring对JUnit支持的依赖还是&#xff1a;spring-test&#xff0c;如下&#xff1a; <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://ma…

Cadence SPB17.4 -Allegro - 做Logo封装及添加中文丝印

Cadence SPB17.4 -Allegro - 做Logo封装及添加中文丝印 Chapter1 Cadence SPB17.4 -Allegro - 做Logo封装Chapter2 Allegro添加中文字体的简单有效方法Chapter3 Allegro添加Logo方法方法一方法二 链接&#xff1a;https://pan.baidu.com/s/1eUgUOjOfNam3rqZyQOov_g 提取码&…

Java技术栈 —— Log4j 2、Logpack、SLF4j日志框架介绍

Log4j 2、Logpack、SLF4j日志框架介绍 Log4j 2、Logpack、SLF4j日志框架&#xff0c;及其区别1.1 Log4j 21.1.1 日志级别1.1.2 日志输出目标位置1.1.3 日志刷新机制1.1.4 结构化打印日志1.1.5 异步打印日志1.1.6 在Cloud云环境汇集日志信息 1.2 LogPack1.3 SLF4j1.4 区别 Log4j…

tcp连接全过程各种状态详解

文章目录 TCP的一些重要特性tcp连接全过程各种状态参考资料 TCP的一些重要特性 TCP是一种可靠、面向连接、全双工、流控制、拥塞控制、有序传输、无差错传输、无重复传输、无丢失传输等特点的协议。为了实现这些特点&#xff0c;TCP必须对上层应用程序发送的数据进行分段、重组…

玩转大数据18:大规模数据处理与分布式任务调度

引言 在数字化时代&#xff0c;数据成为了一种宝贵的资源&#xff0c;对于企业和组织来说&#xff0c;如何有效地处理和分析这些数据成为了关键的竞争力。大规模数据处理与分布式任务调度作为大数据处理的核心技术&#xff0c;为解决这一问题提供了有效的解决方案。 随着数据…

Python如何匹配库的版本

目录 1. 匹配库的版本 2. Python中pip&#xff0c;库&#xff0c;编译环境的问题回答总结 2.1 虚拟环境 2.2 pip&#xff0c;安装库&#xff0c;版本 1. 匹配库的版本 &#xff08;别的库的版本冲突同理&#xff09; 在搭建pyansys环境的时候&#xff0c;安装grpcio-tools…