动态规划: 最长重复子数组

最长重复子数组

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

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

Solution

与之前写过的最长公共子序列还是有所区别的,子数组类似于子串,都是连续的。子序列可以是不连续的(注意:可以是不连续的,连续的情况也算是子序列)。
看到求解最值问题,首先可以想到动态规划算法,因此在思考再三之后,有了下面的想法:


  • 确定dp Table的维度,因为有两个数组,可能存在两个维度上的变化,所以先使用二维数组。

  • 确定完维度,需要做的就是定义每一个状态的含义,对于本题,已经非常明确地说明需要求解最长公共子数组,那么能给出定义:dp[ i ][ j ]表示A数组索引范围为[ 0, i ],B数组索引范围为 [ 0, j ]的状态下最长公共子数组的长度 (要说为什么能够想到这个定义,笔者感觉真的就是经验之谈,动态规划的形式见得多了可能会更加自然地想到一些方案,总之就是尽量往 可能会变化的参数 上靠,比如本题,变化的量也很简单,就是单一索引的不同,那么状态转移的时候改变的也就是数组的索引)。

  • 得到了每个状态的定义,接下来要推导递推公式,也就是常说的状态转移方程,首先细看一个状态dp[ i ][ j ],如果此时 A[ i ] != B[ j ],那么对于 A[0 : i], B[0 : j]范围内肯定是没有公共子数组,将dp[ i ][ j ]置为 0,反之,若 A[ i ] == B[ j ],那么在A[0 : i], B[0 : j]范围内至少会有一个长度为1的公共子数组(刚发生比较的这两元素)。但是另一方面,若该数组前一个状态 dp[ i - 1][ j - 1]也就是索引范围在 [0 : i - 1] , [ 0 : j - 1]内可能也存在一定长度的公共子数组,因此可以将其拼接起来,总的情况就是:
    在这里插入图片描述

  • 初始化dp数组;在递推公式中了解到可能会使用dp[i - 1][ j - 1],因此在对索引0的位置需要提前维护,一种做法是先将dp[0][j]与dp[i][0]都通过手动比较进行初始化,还有一种方法是对dp数组多加一行一列,这样就防止了越界问题。

在完成以上步骤之后可以写出dp数组了,这里给出示例的dpTable。
dp table

Codes
class Solution {
public:
	int findLength(vector<int>& nums1, vector<int>& nums2) {
		int n1 = nums1.size(), n2 = nums2.size();
		vector<vector<int>> dp(n1 + 1, vector<int>(n2 + 1));
		int ans = 0;
		for (int i = 1; i <= n1; ++i) {
			for (int j = 1; j <= n2; ++j) {
				if (nums1[i - 1] == nums2[j - 1])
					dp[i][j] = dp[i - 1][j - 1] + 1;
				else
					dp[i][j] = 0;
				ans = max(dp[i][j], ans);
			}
		}
		return ans;
	}
};

在这里插入图片描述


注意:这里维护一个ans变量是为了记录所有新状态的的最大值。
问:为什么要每一次都记录?
答:因为我们在遍历填充dp Table的过程中可以看到,每一个dp状态跟其他的并不是都有直接关联的,比如当A[i] != B[j] 的情况时,dp就直接变成了0,所以单单通过某一个状态或某一行或者某一列数据是不能得到最优情况的。


状态压缩

上述做法使用了二维数组对每一个状态进行保存,观察递推公式:
dp[ i ][ j ] = dp[i - 1][ j - 1] + 1, 也就是说我们最多能够往回用到的数据也就是左上角(上面一行左边一列)
在这里插入图片描述

在上图表现为灰色部分的状态是通过上一行卡其色部分得到的,真正有用的数据仅仅是前面一行,而在之前的那些状态将会失去作用,因此可以使用 滚动数组 来节省空间。
实现代码如下:

class Solution {
public:
	int findLength(vector<int>& nums1, vector<int>& nums2) {
		int n1 = nums1.size(), n2 = nums2.size();
		vector<vector<int>> dp(2, vector<int>(n2 + 1));
		int ans = 0;
		for (int i = 0; i < n1; ++i) {
			for (int j = 1; j <= n2; ++j) {
                int t = i % 2;
				if (nums1[i] == nums2[j - 1]){
					dp[(i + 1) % 2][j] = dp[t][j - 1] + 1;
                    ans = max(ans, dp[(i + 1) % 2][j]);
                }
				else
					dp[(i + 1) % 2][j] = 0;
				
			}
		}
		return ans;
	}
};

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

nepu_bin

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值