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——最长重复数组(最长公共子串) / 最长重复子串的主要内容,如果未能解决你的问题,请参考以下文章

#leetcode刷题之路3-无重复字符的最长子串

Leetcode——最长公共子序列 / 最长公共子串

子序列与子串问题

子序列与子串问题

Leetcode Practice -- 字符串

子序列与子串问题