陈利人 面试题 给定两个字符串s和t(len(s)>len(t)),t可能是s的一个子序列。求所有s的子序列集合中,有多少个子序列等于t。

本文介绍了一种通过构建虚拟树的方法来计算一个字符串作为另一个字符串子序列出现次数的算法。该算法通过递归地匹配较短字符串的字符,并在较长字符串中寻找匹配项来构建路径,最终统计匹配完整字符串的路径数量。

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

原题

  给定两个字符串s和t(len(s)>len(t)),t可能是s的一个子序列。求所有s的子序列集合中,有多少个子序列等于t。例如s="abbbcb",t="abc",结果为3,即在s的所有子序列集合中,有3个子序列为t。


分析(自己的解法)

   拿例子说吧我自己建树,这个树在程序里是虚拟的。不过可以帮助我们更好的理解并解决问题。请看下面

s="abbbcb",t="abc"


我们首先拿t的第一个字符去在s中匹配,匹配所有的

这时候我们s中的第一个就匹配成功,然后以它作为根,建“树”,

接着我们会拿t中的第二个去在s刚才那个字符之后的区间里去匹配,然后匹配成功的作为刚才那个字符的孩子,接着依次类推t中的所有的字符去匹配s中的特定区间。。。。

最后我们得到一个森林,也可能是一棵树。

然后我们找到所有叶子为t中最后一个字符的总的数目,即为答案所求。

原理:每一条从根a到叶子c的路径上的字符顺序组成的字符串都是t的子串,真子串不是我们想要的。例如路径"ad"不是我们想要的,“adc”本身才是我们想要的,所以只要值域为t最后一个字符的叶子,它的数目即为答案。


实验数据






代码

#include<iostream>
#include<string>
using namespace std;


void getString(string &s,string &t);
void MakeTree(const string &s,size_t n,const string &t,size_t m);

int result = 0;

int main()
{
	string s;
	string t;

	getString(s,t);
	MakeTree(s,0,t,0);
	cout<<result<<endl;
	system("pause");
	return 0;
}

void getString(string &s,string &t)
{
	cout<<"please input string s and t"<<endl;
	cin>>s;
	cin>>t;
}

void MakeTree(const string &s,size_t n,const string &t,size_t m)
{
	if( n < s.size() && m < s.size())
		for(size_t i = n; i < s.size(); i++)
		{
			if(s.at(i) == t.at(m))
			{
				if((m + 1) == t.size())
					++result;
				else
					MakeTree(s, i + 1, t, m + 1);				
			}
		}	
}




<think>嗯,用户让我用Java写一个找到最长回文子串的程序。这个问题我之前可能碰到过,不过得仔细想想怎么处理。首先,回文就是正读反读都一样的字符串,比如"aba"或者"abba"。最长回文子串应该是在给定字符串中最长的这样的子串。 用户可能是在准备面试题,或者在做算法练习。这个问题在LeetCode上也有,可能用户需要详细的解释不同的解决方法。常见的解法有暴力法、动态规划中心扩展法。暴力法时间复杂度太高,可能不太适用,尤其是当字符串很长的时候。动态规划的话,需要二维数组,空间复杂度是O()可能也不是最优的。中心扩展法应该是比较高效的方法,时间复杂度O(),空间O(1),不过具体实现可能需要注意一些细节。 用户的需不仅仅是代码,可能还需要解释思路,以及不同方法的优缺点。比如,动态规划的思路是,如果子串的两端字符相同,且中间的子串也是回文,那么整个子串就是回文。而中心扩展法则是从每个字符或每两个字符中间开始向两边扩展,直到不能形成回文为止。 我应该先介绍中心扩展法,因为效率更高,也更容易理解。然后给出代码示例,逐步解释每一步的作用。比如,遍历每个字符,作为中心点,然后分奇偶两种情况扩展。比如,对于"babad",中心点可能是单个字符,也可能两个相同的字符中间。 另外,需要注意字符串长度为0或1的特殊情况处理。在代码中,初始化最长回文子串的时候,可以设为空,或者第一个字符,视情况而定。在扩展的时候,要确保左右指针不越界,且字符相等。 然后,动态规划的方法可以作为另一种解法,但需要解释其时间空间复杂度,以及适用场景。可能用户需要比较不同方法的优劣,所以应该都提到,但重点放在中心扩展法上。 还有可能的错误点,比如如何处理偶数长度的回文,或者在遍历时如何更新最长子串的起始结束位置。例如,每次找到更长的回文时,更新最长长度起始位置,最后用substring方法提取。 另外,测试用例也很重要,比如输入"babad"应该输出"bab"或"aba",输入"cbbd"应该输出"bb"。在代码中需要确保这些情况都能正确处理。 可能用户还希望看到代码的注释,以及每一步的具体作用,这样更容易理解。所以代码中应该包含必要的注释,解释关键步骤。 最后,总结一下两种方法的优缺点,帮助用户根据不同的需选择合适的方法。比如,如果内存有限,中心扩展法更好;如果字符串较短,动态规划可能更容易实现。</think>--- ### **问题分析** 找到字符串中最长的回文子串是经典的算法问题,回文子串的定义是:正读反读相同的字符串(例如 `"aba"` 或 `"abba"`)。需要高效、正确地解决此问题。 --- ### **解决方案** #### **方法 1:中心扩展法(推荐)** **核心思想**:以每个字符为中心,向两侧扩展,尝试找到最长的回文子串。需处理奇偶长度两种情况(例如 `"aba"` `"abba"`)。 ##### **Java 代码实现** ```java public class LongestPalindrome { public static String longestPalindrome(String s) { if (s == null || s.length() < 1) return ""; int start = 0, end = 0; for (int i = 0; i < s.length(); i++) { // 处理奇长度回文(如 "aba") int len1 = expandAroundCenter(s, i, i); // 处理偶长度回文(如 "abba") int len2 = expandAroundCenter(s, i, i + 1); int maxLen = Math.max(len1, len2); // 更新最长回文的起止位置 if (maxLen > end - start) { start = i - (maxLen - 1) / 2; end = i + maxLen / 2; } } return s.substring(start, end + 1); } // 向两侧扩展,返回当前中心的最长回文长度 private static int expandAroundCenter(String s, int left, int right) { while (left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)) { left--; right++; } return right - left - 1; // 实际长度为 (right-1) - (left+1) + 1 = right - left -1 } public static void main(String[] args) { System.out.println(longestPalindrome("babad")); // 输出 "bab" 或 "aba" System.out.println(longestPalindrome("cbbd")); // 输出 "bb" } } ``` ##### **代码解析** 1. **时间复杂度**:$O(n^2)$,遍历每个字符作为中心($O(n)$),每次扩展最多 $O(n)$。 2. **空间复杂度**:$O(1)$,仅使用常数级额外空间。 3. **关键逻辑**: - `expandAroundCenter` 函数向两侧扩展,直到字符不匹配。 - 同时处理奇偶长度的回文,确保不漏掉任何情况。 --- #### **方法 2:动态规划** **核心思想**:利用二维数组 `dp[i][j]` 记录子串 `s[i...j]` 是否为回文,逐步扩展子串长度。 ##### **Java 代码实现** ```java public class LongestPalindromeDP { public static String longestPalindrome(String s) { if (s == null || s.length() < 2) return s; int n = s.length(); boolean[][] dp = new boolean[n][n]; int maxLen = 1; int start = 0; // 初始化:所有长度为1的子串都是回文 for (int i = 0; i < n; i++) { dp[i][i] = true; } // 按子串长度遍历 for (int L = 2; L <= n; L++) { for (int i = 0; i <= n - L; i++) { int j = i + L - 1; if (s.charAt(i) == s.charAt(j)) { // 长度为2时直接判断,否则依赖内部子串 if (L == 2 || dp[i + 1][j - 1]) { dp[i][j] = true; if (L > maxLen) { maxLen = L; start = i; } } } } } return s.substring(start, start + maxLen); } public static void main(String[] args) { System.out.println(longestPalindrome("babad")); // 输出 "bab" System.out.println(longestPalindrome("cbbd")); // 输出 "bb" } } ``` ##### **代码解析** 1. **时间复杂度**:$O(n^2)$,双层循环遍历所有子串。 2. **空间复杂度**:$O(n^2)$,需存储二维数组。 3. **关键逻辑**: - `dp[i][j] = true` 表示 `s[i...j]` 是回文。 - 状态转移:若两端字符相同且内部子串是回文,则当前子串是回文。 --- ### **方法对比** | 方法 | 时间复杂度 | 空间复杂度 | 适用场景 | |----------------|------------|------------|-----------------------| | **中心扩展法** | $O(n^2)$ | $O(1)$ | 内存敏感或字符串较长 | | **动态规划** | $O(n^2)$ | $O(n^2)$ | 需要记录所有子串信息 | --- ### **边界条件与测试用例** 1. **空字符串**:`""` → 返回 `""`。 2. **单字符**:`"a"` → 返回 `"a"`。 3. **全相同字符**:`"aaaaa"` → 返回自身。 4. **偶长度回文**:`"abba"` → 正确识别。 --- ### **总结** - **中心扩展法** 是更优选择,尤其适合长字符串。 - **动态规划** 逻辑直观,但空间占用较高。 - 根据实际需选择方法,优先考虑时间空间复杂度。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值