Leetcode——最长重复数组(最长公共子串) / 最长重复子串
Posted Yawn,
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Leetcode——最长重复数组(最长公共子串) / 最长重复子串相关的知识,希望对你有一定的参考价值。
1. 最长重复数组
(1)暴力
跟最长公共子串一个解法,没有变化
class Solution {
public int findLength(int[] nums1, int[] nums2) {
if (nums1 == null || nums2 == null) {
return 0;
}
int l1 = nums1.length;
int l2 = nums2.length;
int res = 0;
for (int i = 0; i < l1; i++) {
for (int j = 0; j < l2; j++) {
int m = i;
int n = j;
int len = 0;
while (m < l1 && n < l2 && nums1[m] == nums2[n]) {
len++;
m++;
n++;
}
res = Math.max(res, len);
}
}
return res;
}
}
(2)DP
用一个二维数组dp[i][j]表示 第一个字符串前 i 个字符(0,i - 1) 和 第二个字符串前 j 个字符(0,j - 1) 组成的最长公共字符串的长度。
那么我们在计算dp[i][j]的时候,我们首先要判断s1.charAt(i)是否等于s2.charAt(j):
- 如果不相等,说明当前字符无法构成公共子串,所以dp[i][j]=0。
- 如果相等,说明可以构成公共子串,我们还要加上他们前一个字符构成的最长公共子串长度,也就是dp[i-1][j-1]。所以我们很容易找到递推公式
class Solution {
public int findLength(int[] nums1, int[] nums2) {
if (nums1 == null || nums2 == null || nums1.length == 0 || nums2.length == 0)
return 0;
int max = 0;
// +1是为了防止边界条件判断 和 减少初始化当 i 或 j 等于 0 时,初始化子串就是为0,初始化数组也是为0
int[][] dp = new int[nums1.length + 1][nums2.length + 1];
for (int i = 1; i <= nums1.length; i++) {
for (int j = 1; j <= nums2.length; j++) {
if (nums1[i - 1] == nums2[j - 1])
dp[i][j] = dp[i - 1][j - 1] + 1;
else
dp[i][j] = 0;
//最大值不一定出现在数组的最后一个位置,所以要用一个临时变量记录下来。
max = Math.max(max, dp[i][j]);
}
}
return max;
}
}
(3)DP优化
class Solution {
public int findLength(int[] nums1, int[] nums2) {
if (nums1 == null || nums2 == null || nums1.length == 0 || nums2.length == 0)
return 0;
int max = 0;
// +1是为了防止边界条件判断 和 减少初始化当 i 或 j 等于 0 时,初始化子串就是为0,初始化数组也是为0
int[] dp = new int[nums2.length + 1];
for (int i = 1; i <= nums1.length; i++) {
//使用的倒序的方式,这是因为dp数组后面的值会依赖前面的值,而前面的值不依赖后面的值,所以后面的值先修改对前面的没影响,但前面的值修改会对后面的值有影响,所以这里要使用倒序的方式。
for (int j = nums2.length; j >= 1 ; j--) {
if (nums1[i - 1] == nums2[j - 1])
dp[j] = dp[j - 1] + 1;
else
dp[j] = 0;
//最大值不一定出现在数组的最后一个位置,所以要用一个临时变量记录下来。
max = Math.max(max, dp[j]);
}
}
return max;
}
}
2. 最长重复子串
(0)暴力(IndexOf)
lastIndexOf判断最后位置是否为当前位置
class Solution {
public int longestRepeatingSubstring(String S) {
int max = 0;
for (int i = 0; i < S.length(); i++) {
for (int j = i + 1; j<=S.length(); j++) {
if (S.lastIndexOf(S.substring(i,j)) != i)
max = Math.max(max, j - i);
}
}
return max;
}
}
从第一个位置开始找有重复的最长子串,记录max,以后的子串字节从len>max开始找,时间复杂度不行,但是内存100% 代码也简单
class Solution {
public int longestRepeatingSubstring(String S) {
int max = 0;
for(int i = 0; i < S.length(); i++){
for(int j = i + max + 1; j < S.length(); j++){
String value = S.substring(i,j);
if(S.substring(i+1).contains(value)){
max = j - i;
}
}
}
return max;
}
}
(1)暴力(Hash)
- 首先要有重复的最长子串,那么应该从S的length - 1中去找,因为只有这样才有重复的串,重复的意思是>=2个;
- 我们先预设长度是len(length - 1)的情况下找重复子串,如果有?返回这个这个子串的长度即可
- 缩小(–len)的方式找下去,直到找到存在重复子串,返回其长度即可;
class Solution {
public int longestRepeatingSubstring(String S) {
int length = S.length();
int len = length - 1;
while (len > 0) {
int maxTimes = 1;
Map<String, Integer> timer = new HashMap<>();
for (int i = 0; i <= length - len; i++) {
String substring = S.substring(i, i + len);
int times = timer.getOrDefault(substring, 0) + 1;
timer.put(substring, times);
if (times > maxTimes) {
return substring.length();
}
}
len--;
}
return 0;
}
}
(2)滑动窗口
- 这种连续的子串,子数组问题比较符合“滑动窗口”的应用场景:滑动窗口就是通过增加right指针持续寻找满足一定条件的对象,一旦发现条件不满足,立马从left指针开始减少对象范围重新比较满足性
- 本题中只要持续比较子串是否在left+1的索引后面的字符串中能否找到子串就可以
class Solution {
public int longestRepeatingSubstring(String S) {
int left = 0;
int right = 0;
int index = -1;
int maxLength = 0;
StringBuilder stringBuilder = new StringBuilder();
while (right < S.length()) {
char cAdd = S.charAt(right);
right++;
stringBuilder.append(cAdd);
index = S.indexOf(stringBuilder.toString(), left + 1);
if (index != -1) {
continue;
}
while (index == -1) {
maxLength = Math.max(maxLength, right - left);
left++;
stringBuilder.deleteCharAt(0);
index = S.indexOf(stringBuilder.toString(), left + 1);
}
}
return maxLength - 1;
}
}
(3)二分
- 如果暴力求解,至少要枚举2个指针 => 至少是On平方解法,因为要枚举边界。而且,要枚举所有长度,长度区间是[0, n] => 所以是O n三次方复杂度。
- 为了降低复杂度,可以二分搜索长度,减少搜索次数。搜索次数最多也就logN
- 对于每个长度,就用枚举法,枚举[0, input.length - midLen]区间,所有长度为midLen子串,这里配合HashSet,记录已经访问过的子串,如果后面出现相同的子串,就返回true,否则返回false。
- 二分搜索,采用求满足条件的最大值的模版
搜索范围: left = mid, right = mid - 1;
mid计算: int mid = (left + right + 1) >>> 1;
// 不加入1会死循环,因为最后剩2个元素时,[0, 1] 会导致 0 + 1 /2 == 0; 即left == mid == 0,循环无数次,left 和 mid一直都是0
class Solution {
public int longestRepeatingSubstring(String S) { // 如果暴力搜,需要遍历所有长度,二分法,可以只搜logN个长度。
int max = 0;
int left = 0, right = S.length() - 1;
while (left < right) {
int midLen = (left + right + 1) / 2; // 二分搜索中,从有序数组中,求满足条件的最大值,这要+1 ,不然会死循环。
if (isLongest(S, midLen)) {
left = midLen; // 保底,且逼近最大值
} else {
right = midLen - 1; // 二分搜索中,求满足条件的最大值标准模板
}
}
return left;
}
private boolean isLongest(String s, int midLen) {
Set<String> seen = new HashSet<>();
for (int i = 0; i <= s.length() - midLen; i++) { // len都是比区间多1的,相当于i <= subStr.length - 1; 其中subStr就是减去midLen长度的长度
String subStr = s.substring(i, i + midLen);
if (seen.contains(subStr)) {
return true;
}
seen.add(subStr);
}
return false;
}
}
以上是关于Leetcode——最长重复数组(最长公共子串) / 最长重复子串的主要内容,如果未能解决你的问题,请参考以下文章