返回两个字符串的最大相同子串(第一次出现)

寻找最大相同子串算法实现
该博客讨论了一个Java方法,用于找到两个字符串之间的最大相同子串。通过使用双层循环,逐一检查较短字符串中所有可能的子串是否包含在较长的字符串中。当找到符合条件的子串时,立即返回。示例展示了如何处理不同输入并返回正确结果。
/*补充方法,public String getMaxStr(String yi,String er){}
要求返回两个字符串的最大相同子串(第一次出现)
比如:yi = abcdefghijklmn   er = xxbcdm
则应该返回bcd
再比如:yi = abcdefgh   er = bcddefg
则应该返回defg

*/
public class Demo19 {
    public static void main(String[] args) {
        String yi="sadsad";
        String er="sdadsdaasds";
        System.out.println(getMaxStr(yi,er));
    }
    public static String getMaxStr(String yi,String er){
        //思路:最小公约数,将较小的那串依次递减,获取每个长度的所有可能
        if(yi != null && er != null){//先判断有没有空的可能性

            String maxStr = (yi.length() >= er.length()) ? yi : er;//判断他们两个那一个的长度最长
            String minStr = (yi.length() < er.length()) ? yi : er;//判读他们两个的长度哪一个最短
            int length = minStr.length();//定义最短的那个为要遍历的长度

            for (int i = 0; i < length; i++) {

                for (int x = 0, y = length-i; y <= length; x++, y++) {//
                    if (maxStr.contains(minStr.substring(x, y))) {
                        //当且仅当此字符串包含指定的 char 值序列时才返回 true
                        //返回作为此字符串的子字符串的字符串。子字符串从指定的beginIndex开始并延伸到索引endIndex - 1处的字符。因此子字符串的长度是endIndex-beginIndex 。
                        return minStr.substring(x, y);
                    }
                }

            }

        }
        return null;
    }
}

<think> 我们要求的是两个相同字符之间的子串最大长度。注意,这个子串是由两个相同字符及其之间的字符组成的,因此子串的长度实际上是从第一个相同字符的下一个位置到第二个相同字符的位置,但题目要求的是子串的长度(包含两个相同字符)。 但是,根据问题描述:“计算给定字符串两个相同字符之间子串最大长度”,例如字符串"abca",那么两个相同字符'a'之间的子串是"abca",长度是4。但注意,这个子串包含两个相同的字符,所以实际上是包含首尾两个相同字符的。 然而,如果我们仔细想,两个相同字符之间的子串,例如在"abca"中,第一个'a'在位置0,第二个'a'在位置3,那么它们之间的子串是从0到3(包含),长度是4。但题目要求的可能是两个相同字符之间的子串,这个子串的长度应该是两个相同字符的位置差加1(因为包含两个端点)。 但是,还有一种情况,比如字符串中有多个相同字符,我们要求的是任意两个相同字符之间(包括这两个字符)的子串最大长度。 但是,注意:题目要求的是“两个相同字符之间子串最大长度”,所以我们需要找到所有相同字符对,然后计算它们之间的距离(即结束位置-开始位置+1),然后取最大值。 然而,在同一个字符出现多次的情况下,我们应该取这个字符最左边出现的位置和最右边出现的位置吗?不,因为两个相同字符之间可能还有相同的字符,但题目要求的是两个相同字符,所以任意两个相同字符都可以。但是,显然距离最大的应该是同一个字符的最左边和最右边的两个。因此,对于每个字符,我们只需要记录它第一次出现的位置和最后一次出现的位置,然后计算距离,最后取所有字符的距离的最大值。 但是,注意:题目要求的是两个相同字符之间的子串,而这两个字符必须是相同的,但可以是任意两个相同的字符(不一定要是同一个字符的第一次和最后一次?)实际上,对于同一个字符,最大距离当然是第一次和最后一次出现的位置。而对于不同字符?不对,必须是相同的字符,所以只能同一个字符的两个位置。 因此,算法如下: 1. 遍历字符串,记录每个字符第一次出现的位置和最后一次出现的位置(实际上最后一次出现的位置可以在第二次遍历时得到,但我们可以一次遍历记录每个字符所有出现的位置,然后取最小和最大位置。或者,我们分别记录每个字符第一次出现的位置,然后在遍历过程中更新最后一次出现的位置,并同时更新最大长度)。 2. 我们可以用一个哈希表(在JavaScript中可以用对象或Map)来记录每个字符第一次出现的位置。然后当我们再次遇到相同的字符时,计算当前位置与第一次出现位置之间的子串长度(当前位置 - 第一次出现位置 + 1),并更新最大长度。但是这样只能得到同一个字符相邻两次出现的距离?不,我们需要的是同一个字符最远的两个位置。 但是,如果我们只记录第一次出现的位置,那么当我们再次遇到这个字符时,我们只能得到当前这个字符与第一次出现的位置的距离,而无法得到这个字符与其他非第一次出现位置的距离。因此,我们需要记录每个字符的所有出现位置?这样效率较低。 实际上,我们只需要记录每个字符第一次出现的位置,然后在遍历过程中,每次遇到这个字符,我们就计算当前位置与第一次出现位置的距离(并加1得到长度),然后更新最大长度。但是这样只能得到每个字符的第一次出现位置和后面某次出现位置的最大距离,而同一个字符的中间两个位置可能距离更大吗?不会,因为同一个字符的最大距离一定是第一次出现和最后一次出现的位置之间的距离。因此,我们可以在遍历时只记录第一次出现的位置,并在遍历结束后再计算每个字符最后一次出现的位置?不行,因为遍历过程中我们不知道最后一次出现的位置。 但是,我们可以这样:遍历整个字符串,用一个对象来记录每个字符第一次出现的位置。然后,再遍历一次字符串,对于每个字符,我们找到它最后一次出现的位置(这个需要预先记录)。这样需要两次遍历。 更高效的方法是:在第一次遍历时,我们同时记录每个字符第一次出现的位置和当前最后一次出现的位置(也就是当前遍历到的位置),但是这样无法在第一次遍历时得到每个字符的最后一次出现位置(因为遍历还没结束)。所以,我们可以在第一次遍历结束后,再遍历一次来更新最后一次出现的位置?这需要两次遍历。 另一种思路:我们只关心最大长度,那么对于每个字符,我们只需要知道它的最小索引和最大索引。因此,我们可以: - 创建一个对象minIndex和maxIndex(或者一个对象存储每个字符的最小索引和最大索引)。 - 遍历字符串,记录每个字符第一次出现的位置(即最小索引)和最后一次出现的位置(即最大索引,可以在遍历过程中不断更新,因为每次遇到该字符,我们就更新它的最大索引)。 - 遍历完成后,对于每个字符,计算maxIndex[char] - minIndex[char] + 1,然后取所有字符中的最大值。 但是,注意:同一个字符可能只出现一次,那么最大长度就是0?不对,因为两个相同字符,所以至少出现两次。如果某个字符只出现一次,那么它不能构成两个相同字符之间的子串。所以,我们只考虑出现至少两次的字符。 因此,算法步骤: 1. 初始化一个空对象charFirstIndex,用于记录每个字符第一次出现的位置;初始化一个空对象charLastIndex,用于记录每个字符最后一次出现的位置(也可以只用一个对象记录每个字符的出现次数,但这里我们需要位置)。 2. 遍历字符串,对于每个字符: 如果这个字符在charFirstIndex中不存在,那么记录当前索引为它的第一次出现位置(同时,我们也可以记录最后一次出现位置为当前索引,因为目前只出现了一次)。 如果已经存在,那么更新这个字符在charLastIndex中的值为当前索引(即更新为最后一次出现的位置)。 3. 但是,这样我们最后得到每个字符的第一次出现位置和最后一次出现位置(对于只出现一次的字符,最后一次出现位置和第一次出现位置相同,但我们不关心,因为只出现一次的不算)。 4. 然后,我们遍历所有在charFirstIndex中出现过的字符(并且该字符在charLastIndex中记录的位置要大于charFirstIndex中记录的位置,即至少出现两次),计算charLastIndex[char] - charFirstIndex[char] + 1,并更新最大长度。 5. 最后输出最大长度。 但是,注意:同一个字符可能出现多次,但最大距离一定是第一次和最后一次。所以这个方法正确。 但是,我们也可以在第一次遍历时就计算最大长度吗?可以,但需要保存每个字符的第一次出现位置,然后在每次遇到这个字符时,都计算当前位置与第一次出现位置的距离+1,然后更新最大长度。但是这样得到的并不是全局最大距离,因为同一个字符后面可能出现更远的位置。例如字符串"abacda",对于字符'a',第一次在0,第二次在2,此时计算长度为3(0到2),但是后面还有位置5,那么0到5的距离是6,所以我们在遍历到位置5时才能更新到最大长度6。因此,如果我们每次遇到相同的字符就更新最大长度,那么最终会得到该字符最后一次出现位置与第一次出现位置的距离。因为最后一次出现位置在最后,所以这样是可以的。 因此,我们可以优化为一次遍历: 1. 初始化一个对象charFirstIndex,用于存储每个字符第一次出现的索引。 2. 初始化maxLength=0。 3. 遍历字符串,对于索引i,字符c=str[i]: - 如果charFirstIndex[c]是undefined(即第一次出现),则charFirstIndex[c]=i。 - 否则,说明之前出现过,计算当前子串长度:i - charFirstIndex[c] + 1,然后与maxLength比较,如果大于则更新maxLength。 4. 但是,这样对于同一个字符出现多次的情况,我们只计算了当前字符与第一次出现位置的距离,但是有可能这个字符在中间位置(比如第2次出现)和第一次出现位置的距离较小,而后面出现的位置距离更大,所以我们在每次遇到这个字符时都计算一次,并且更新最大值,这样是可以的(因为最后一次出现的位置一定是距离第一次出现位置最远的)。 但是,这样有一个问题:比如字符串"baac",对于字符'a': i=1: 遇到第一个'a',记录位置1。 i=2: 遇到第二个'a',计算位置2-1+1=2,更新maxLength=2。 i=3: 没有遇到'a',所以不计算。 然后我们得到maxLength=2。但实际上,第一个'a'和最后一个'a'(这里第二个'a'就是最后一个)的距离是2。 但是,我们要求的是两个相同字符之间的子串最大长度,这里2是正确的(子串是"aa")。 但是,考虑字符串"baca": i=1: 记录'a'在1。 i=3: 再次遇到'a',计算3-1+1=3,所以最大长度为3(子串是"aca")。 这符合要求。 但是,如果同一个字符出现三次或更多,例如"abacada": 字符'a':位置0,2,4,6。 第一次遇到(位置0)记录。 第二次遇到(位置2):计算2-0+1=3 -> maxLength=3。 第三次遇到(位置4):计算4-0+1=5 -> maxLength=5。 第四次遇到(位置6):计算6-0+1=7 -> maxLength=7。 所以最后得到7,而实际上第一个'a'和最后一个'a'之间的距离是7(子串"abacada"),正确。 所以,一次遍历的方法是可行的,且效率更高。 但是,注意:我们只记录了第一次出现的位置,那么如果同一个字符的中间两个位置距离更远呢?例如,我们考虑第二个'a'和最后一个'a',它们之间的距离是6-2+1=5,这比7小。所以最大值一定是第一次出现和最后一次出现的位置。 因此,我们只需要在每次遇到一个已经出现过的字符时,计算当前位置和该字符第一次出现位置的距离+1,然后更新最大值。 但是,这里有一个问题:我们并没有更新第一次出现的位置,所以每次计算都是和第一次出现位置的距离。那么,如果我们遇到同一个字符多次,我们计算的都是和第一次出现的距离,而最后一次出现时我们计算的距离就是最大的。 所以,算法如下: ```javascript function maxLengthBetweenSameCharacters(str) { let firstOccurrence = {}; // 记录每个字符第一次出现的位置 let maxLen = -1; // 初始化为-1,因为如果没有重复字符,则返回-1(或者0?根据题目要求,如果没有两个相同字符,则没有满足条件的子串,可以返回-1) for (let i = 0; i < str.length; i++) { const char = str[i]; if (firstOccurrence[char] === undefined) { firstOccurrence[char] = i; } else { // 当前字符之前出现过,计算当前子串长度:从第一次出现的位置到当前位置(包含两个端点) const length = i - firstOccurrence[char] + 1; if (length > maxLen) { maxLen = length; } } } // 注意:maxLen记录的是包含两个相同字符的子串长度,但题目要求的是子串的长度,这个长度就是maxLen。 // 但是,如果没有重复字符,maxLen还是-1,我们可以返回-1表示不存在这样的子串。 // 但是,注意:如果整个字符串都没有重复字符,那么maxLen不会更新,返回-1。 return maxLen === -1 ? -1 : maxLen; } ``` 但是,这个算法一个问题:它计算的是包含两个相同字符的最长子串的长度吗?注意,我们要求的是两个相同字符之间的子串(包含这两个字符),所以这个长度就是两个索引差加1。 但是,让我们测试一下: 示例1:字符串"abca",对于字符'a',第一次出现0,第三次出现(位置3)时,计算3-0+1=4,所以返回4。子串是"abca",长度为4,正确。 示例2:字符串"aa",第一次出现0,第二次出现1,计算1-0+1=2,返回2,正确(子串"aa")。 示例3:字符串"aba",对于字符'a',第一次0,第二次2,计算2-0+1=3,返回3(子串"aba"),正确。 但是,我们考虑一个字符出现多次的情况,例如"abacada": 我们计算了每个'a'与第一个'a'的距离,最后一次在6,所以6-0+1=7,正确。 但是,如果字符串是"abcda",那么只有'a'重复(位置0和4),所以计算4-0+1=5子串"abcda"长度为5,正确。 但是,注意:同一个字符出现多次,我们每次遇到都计算,但最终最大值一定是和第一个字符的距离吗?是的,因为最后一次出现的位置一定是最远的。 但是,如果我们考虑不同的字符?比如字符串"aabbcc": 'a': 位置0和1 -> 长度=1-0+1=2 'b': 位置2和3 -> 长度=3-2+1=2 'c': 位置4和5 -> 长度=5-4+1=2 所以最大长度是2。 但是,如果我们希望得到的是整个字符串任意两个相同字符之间的最大长度,那么这里2是最大值吗?是的,因为每个字符都只连续出现了两次,所以最大子串长度就是2(例如"aa")。 但是,有没有可能两个相同字符不是同一个字符?题目要求是“两个相同字符”,所以一定是同一个字符。 因此,这个算法满足要求。 但是,注意:题目要求的是“两个相同字符之间子串最大长度”,而我们的算法返回的是包含两个相同字符的子串最大长度(即两个相同字符作为边界,中间包含的连续子串的长度)。 但是,这里我们计算的长度是包含两个相同字符的,所以是子串的长度。例如"abca"的长度是4。 但是,题目要求的是子串的长度,所以是4。 但是,我们再看一下题目描述:“计算给定字符串两个相同字符之间子串最大长度”,所以我们的理解是正确的。 然而,我们注意到,这个子串不一定只包含这两个相同字符,而是包含这两个字符以及它们之间的所有字符。 因此,我们按照上面的算法编写代码。 但是,有一个边界情况:如果整个字符串都没有重复字符,那么返回-1(或0)?根据我们的初始化maxLen=-1,所以返回-1。但是题目要求的是最大长度,没有重复字符时应该返回0吗?还是返回-1?题目没有明确,但根据题意,没有两个相同字符,那么就不存在这样的子串返回-1也是合理的。 但是,我们可以看题目中的例子:引用[1]中的函数是求无重复字符的最长子串,但我们的问题不同。我们这里的问题明确要求两个相同字符之间的子串。 因此,我们按照返回-1处理。 但是,我们再看一下问题:用户要求的是“两个相同字符之间子串最大长度”,所以如果没有相同字符,则不存在这样的子串返回-1。 但是,我们也可以返回0?因为两个相同字符紧挨着,子串长度是2(比如"aa"),那么两个相同字符之间没有其他字符,子串长度是2。但是如果没有相同字符,那就不存在,返回-1。 因此,我们返回-1。 但是,我们再看另一种情况:比如字符串"a",只有一个字符,那么没有两个相同字符(需要两个),所以返回-1。 所以,我们保持返回-1。 但是,我们再看用户给的参考引用中,引用[1]和引用[5]都是求无重复字符的最长子串,而我们的问题不同。引用[2]是求最长有效括号子串,引用[3]是字符串比较,引用[4]是求无重复字符的最长子串(用暴力法)。所以我们的问题与这些不同。 因此,我们按照上述算法实现。 但是,我们可能忽略了一种情况:同一个字符,我们只记录第一次出现的位置,那么如果这个字符在中间出现了多次,但最后一次出现的位置并不是整个字符串的最后,那么我们的算法仍然能够正确计算。因为我们在遍历过程中,每次遇到重复字符都会计算一次,所以最后一次遇到这个字符时,计算的是和第一次出现位置的距离,这个距离一定是这个字符的最大距离。 所以,代码实现: 注意:在JavaScript中,字符串索引从0开始。 我们写一个函数: 测试用例: 输入: "aa" -> 2 输入: "aba" -> 3 输入: "abca" -> 4 输入: "abc" -> -1 但是,考虑一个字符出现三次的情况,比如"abacada" -> 7 测试:a:0, b:1, a:2 -> 长度=2-0+1=3;然后c:3, a:4 -> 长度=4-0+1=5;d:5, a:6 -> 长度=6-0+1=7 -> 7 但是,如果我们考虑字符串"abcda",那么只有首尾两个a,所以5。 但是,如果字符串是"aab",那么对于第一个'a'在0,第二个'a'在1,计算1-0+1=2;然后字符'b'没有重复。所以最大长度是2。 但是,注意:同一个字符可能有多个不同的子串,比如字符串"abacada",除了整个字符串,还有"aba"(0到2)、"aca"(2到4)、"ada"(4到6)以及"abacada"(0到6)等。我们的算法计算的是每个重复字符出现时与第一次出现位置的距离,所以最后得到的是整个字符串的长度7。 所以,我们得到的是所有相同字符对中距离最大的那个(即同一个字符的第一次出现和最后一次出现)。 因此,代码实现如下: </think>以下是用 JavaScript 实现计算给定字符串两个相同字符之间子串最大长度的方法: ### JavaScript 实现 ```javascript function maxLengthBetweenSameChars(str) { // 创建哈希表记录字符首次出现的位置 const firstOccurrence = {}; let maxLen = -1; // 初始化为 -1(无相同字符时返回) for (let i = 0; i < str.length; i++) { const char = str[i]; if (firstOccurrence[char] === undefined) { // 字符首次出现,记录位置 firstOccurrence[char] = i; } else { // 计算相同字符间的子串长度(包含两端字符) const currLen = i - firstOccurrence[char] + 1; maxLen = Math.max(maxLen, currLen); } } return maxLen; } // 测试示例 console.log(maxLengthBetweenSameChars("abca")); // 输出: 4(子串"abca") console.log(maxLengthBetweenSameChars("aa")); // 输出: 2(子串"aa") console.log(maxLengthBetweenSameChars("abc")); // 输出: -1(无相同字符) console.log(maxLengthBetweenSameChars("abacada")); // 输出: 7(子串"abacada") ``` ### 算法说明 1. **核心思路**: - 使用哈希表 `firstOccurrence` 记录每个字符**首次出现的位置索引** - 当遇到重复字符时,计算当前索引与首次出现索引的距离(`i - firstOccurrence[char] + 1`) - 持续更新最大长度 `maxLen` 2. **时间复杂度**:$O(n)$ 只需一次遍历字符串,效率最优。 3. **空间复杂度**:$O(1)$ 哈希表最多存储字符集大小(如 ASCII 字符集为 $O(128)$)。 4. **边界处理**: - 无相同字符时返回 `-1` - 子串长度包含两个相同字符(如 `"aa"` 的长度为 2) ### 示例解析 - `"abca"`:字符 `'a'` 在索引 0 和 3 出现,子串为 `"abca"`,长度 $3-0+1=4$ - `"abacada"`:字符 `'a'` 在索引 0 和 6 出现,子串为 `"abacada"`,长度 $6-0+1=7$ ### 对比其他子串问题 此问题与以下经典问题不同: 1. **无重复字符的最长子串**(如引用[4][5]) 目标:找**不包含重复字符**子串 解法:滑动窗口 + 哈希表记录字符最后出现位置 2. **最长有效括号子串**(引用[2]) 目标:找**括号匹配**的最长子串 解法:栈或动态规划 ### 相关问题 1. 如何修改算法返回最大子串本身而不仅是长度? 2. 如果要求子串**不包含两个相同字符**(即纯中间部分),如何调整计算? 3.字符串包含 Unicode 字符时(如 emoji),算法需要如何优化? [^1]: 引用示例函数,但实际解决不同问题(无重复字符子串) [^2]: 解决括号匹配子串问题 [^3]: 字符串比较类问题 [^4][^5]: 无重复字符的最长子串解法
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

风雨流年666

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

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

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

打赏作者

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

抵扣说明:

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

余额充值