求两个字符串的最长公共子串

本文介绍了三种方法求解两个字符串的最大公共子串:滑动比对解法、二维数组矩阵解法和预处理map解法。滑动比对解法通过字符串固定和滑动实现,时间复杂度为O((m+n)n);二维数组矩阵解法利用对角线递增数列找出最长公共子串,时间复杂度为O(nm);预处理map解法通过构建字符出现位置的map,减少不必要的比对,时间复杂度受字符出现频率影响。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

两个字符串分别为"abccade"和"dgcadde"
输出结果为cad

滑动比对解法

输入字符串1和2
字符串1固定,字符串2滑动
两层for循环,外层控制字符串2的滑动,内层控制滑动后重叠部分字符串的比对
思路就是这样,具体的实现可以直接看代码

package main

import (
	"bytes"
	"fmt"
)

func getMaxSubStr2(str1, str2 string) string {
	len1 := len(str1)
	len2 := len(str2)
	var buf bytes.Buffer
	maxLen := 0     //最大公共子串长度
	maxLenEnd1 := 0 //最大公共子串在str1中的结束位置下标
	for i := 0; i < len1+len2; i++ {
		s1begin := 0   //比较时字符串1的开始下标
		s2begin := 0   //比较时字符串2的开始下标
		tmpMaxLne := 0 //记录本次循环当前得到的最长公共子串
		if i < len1 {
			s1begin = len1 - i
		} else {
			s2begin = i - len1
		}
		j := 0
		for j = 0; (s1begin+j < len1) && (s2begin+j < len2); j++ {
			//判断字符串是否相同
			if str1[s1begin+j] == str2[s2begin+j] {
				//相同则子串长度+1
				tmpMaxLne++
			} else {
				//不相同则进行结算,判断 本次获取的公共子串长度 是否 比之前记录的最大长度要长
				if tmpMaxLne > maxLen {
					//本次获取的长度成为最大长度
					maxLen = tmpMaxLne
					//将公共子串终结的下标记录下来
					maxLenEnd1 = s1begin + j
				} else {
					//没比之前的最大长度长,清零重来
					tmpMaxLne = 0
				}
			}
		}
	}
	//从终结位置的下标逆推公共子串的开始位置
	for i := maxLenEnd1 - maxLen; i < maxLenEnd1; i++ {
		buf.WriteByte(str1[i])
	}
	return buf.String()
}

func main() {
	str1 := "abccade"
	str2 := "dgcadde"
	fmt.Println(getMaxSubStr2(str1, str2))
}

此解法时间复杂度O((m+n)n),空间复杂度O(1)
因为没有复制输入的字符串,直接使用原字符串比对,这是空间复杂度最小的解法

二维数组矩阵解法

构建一个n*m的二维int数组,数组的一维二维下标分别对应字符串1和字符串2的字符下标,然后双层循环遍历两个字符串,将每个字符互相比对,比对结果记录在二维数组中,两个字符如果不相同则结果为0,两个字符如果相同则结果为【二维数组两下标-1位置数字+1】如

if str1[1] == str2[3] {
    arr[1][3] = arr[1-1][3-1] + 1
}

这样可以做到一边判断是否相等一边记录该公共子串的长度
实际我们把二维数组画成矩阵就会发现如果有公共子串出现,他在矩阵中体现出来的是斜向下对角线出现的一个递增数列。
![image.png](https://img-blog.csdnimg.cn/img_convert/9819820598ef93cf829132736705e2a0.png#align=left&display=inline&height=117&margin=[object Object]&name=image.png&originHeight=234&originWidth=329&size=10692&status=done&style=none&width=164.5)
这样当我们把这个矩阵计算完毕,就可以得出本题的答案了。
此算法时间复杂度为O(nm);因为构建了一个nm的map,所以空间复杂度为O(n*m)

预处理map解法

先遍历字符串2,用map[string][]int的结构记录各字符及其下标【字符可能多次出现所以value是数组】
然后遍历字符串1,这时我们通过前面构建的map可以直接获取到对应字符在字符串2中出现的下标位置,我们只要从这个位置开始往后判断即可,如str1[0] == str2[3]这个结果我们可以通过map直接获取,然后直接判断str1[0+1]是否等于str2[3+1]以此类推
此方案需要额外构建map,但是可以通过map来跳过一些比对

package main

import (
	"bytes"
	"fmt"
)

func makeMap(str1, str2 string) string {
	len1 := len(str1)
	len2 := len(str2)
	maxLen := 0     //最大公共子串长度
	maxLenEnd1 := 0 //最大公共子串在str1中的结束位置下标
	tmpMaxLen := 0  //记录本次循环当前得到的最长公共子串
	var buf bytes.Buffer
	//将字符串2预处理成map
	var map2 = make(map[string][]int, len2)
	for k, v := range str2 {
		//这里处理成字符串是为了方便打印出来看着更直观
		strv := fmt.Sprintf("%c", v)
		map2[strv] = append(map2[strv], k)
	}
	fmt.Println(map2)

	//遍历字符串1
	for i := 0; i < len1; i++ {
		strv := fmt.Sprintf("%c", str1[i])
		//如果当前字符存在于字符串2中
		if _, ok := map2[strv]; ok {
			//遍历此字符在字符串2中出现的所有下标位置
			for _, relIndex := range map2[strv] {
				//从这个下标开始往后遍历
				for j := 1; j < len2-relIndex; j++ {
					//判断字符串是否相同
					if i+j < len1 && str2[relIndex+j] == str1[i+j] {
						//相同则子串长度+1
						tmpMaxLen++
					} else {
						//不相同则进行结算,判断 本次获取的公共子串长度 是否 比之前记录的最大长度要长
						if tmpMaxLen > maxLen {
							//本次获取的长度成为最大长度
							maxLen = tmpMaxLen
							//将公共子串终结的下标记录下来
							maxLenEnd1 = i + j
						} else {
							//没比之前的最大长度长,清零重来
							tmpMaxLen = 0
						}
						//结算完代表本子串的匹配已经结束,不需要再接着往后遍历了,跳出遍历循环
						break
					}
				}
			}
		}
	}

	//从终结位置的下标逆推公共子串的开始位置
	for i := maxLenEnd1 - maxLen; i < maxLenEnd1; i++ {
		buf.WriteByte(str1[i])
	}
	return buf.String()
}

func main() {
	str1 := "abccade"
	str2 := "dgcadde"
	fmt.Println(makeMap(str1, str2))
}

此方案的时间复杂度受字符的出现频率影响,空间复杂度为O(n)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值