豆包训练营第三课 | 豆包MarsCode AI 刷题

98 阅读5分钟

小M在研究字符串时发现了一个有趣的现象:某些字符串是由一个较短的子串反复拼接而成的。如果能够找到这个最短的子串,便可以很好地还原字符串的结构。你的任务是给定一个字符串,判断它是否是由某个子串反复拼接而成的。如果是,输出该最短的子串;否则,输出空字符串""

例如:当输入字符串为 abababab 时,它可以由子串 ab 反复拼接而成,因此输出 ab;而如果输入 ab,则该字符串不能通过子串的重复拼接得到,因此输出空字符串。

代码:

#include <iostream>
#include <string>

std::string solution(const std::string &inp) {
    int n = inp.size();
    
    // 遍历所有可能的子串长度
    for (int len = 1; len <= n / 2; ++len) {
        // 检查当前长度的子串是否可以重复得到输入字符串
        if (n % len == 0) {
            std::string candidate = inp.substr(0, len);
            std::string repeated;
            
            // 重复子串,直到长度等于输入字符串的长度
            for (int i = 0; i < n / len; ++i) {
                repeated += candidate;
            }
            
            // 如果重复后的字符串等于输入字符串,返回该子串
            if (repeated == inp) {
                return candidate;
            }
        }
    }
    
    // 如果没有找到合适的子串,返回空字符串
    return "";
}

int main() {
    // 添加你的测试用例
    std::cout << (solution("abcabcabcabc") == "abc") << std::endl;
    std::cout << (solution("aaa") == "a") << std::endl;
    std::cout << (solution("abababab") == "ab") << std::endl;
    std::cout << (solution("ab") == "") << std::endl;
    std::cout << (solution("abcdabcdabcdabcd") == "abcd") << std::endl;
    std::cout << (solution("b") == "") << std::endl;
    
    return 0;
}

问题分析

问题理解

这个问题要求我们判断一个给定的字符串是否可以由某个子串重复拼接而成,并且如果可以,输出这个最短的子串;否则,输出空字符串。这个问题可以看作是一个字符串模式匹配问题,其中我们需要找到一个最小的重复单元,使得这个单元重复若干次后可以完全覆盖输入的字符串。

数据结构选择

在解决这个问题时,我们主要使用字符串操作。字符串是一种常见的数据结构,提供了许多内置的方法来处理字符串操作,例如获取子串、拼接字符串等。因此,我们不需要引入额外的数据结构,直接使用字符串类型即可。

算法步骤

  1. 遍历所有可能的子串长度

    • 我们需要遍历所有可能的子串长度,从1到字符串长度的一半。这是因为一个子串的长度不可能超过字符串长度的一半(否则无法重复拼接成原字符串)。
    • 对于每个可能的子串长度,我们需要检查是否可以通过重复该子串得到输入字符串。
  2. 检查子串是否可以重复得到输入字符串

    • 对于每个子串长度,我们首先检查输入字符串的长度是否是该子串长度的整数倍。如果不是,那么这个子串不可能通过重复得到输入字符串。
    • 如果输入字符串的长度是该子串长度的整数倍,我们提取该长度的子串,并将其重复若干次,直到长度等于输入字符串的长度。
    • 然后,我们比较重复后的字符串是否等于输入字符串。如果相等,说明这个子串可以通过重复得到输入字符串,我们返回该子串。
  3. 返回结果

    • 如果在遍历所有可能的子串长度后,没有找到合适的子串,说明输入字符串不能通过任何子串的重复拼接得到,我们返回空字符串。

时间复杂度分析

  • 遍历子串长度:我们需要遍历从1到字符串长度的一半的所有可能的子串长度,这需要O(n/2)次操作,其中n是输入字符串的长度。
  • 检查子串:对于每个子串长度,我们需要提取子串并重复拼接,这需要O(n)次操作。
  • 比较字符串:最后,我们需要比较两个字符串是否相等,这需要O(n)次操作。

综合来看,算法的时间复杂度为O(n^2),其中n是输入字符串的长度。虽然这个时间复杂度不是最优的,但对于大多数实际应用场景来说,这个复杂度是可以接受的。

空间复杂度分析

  • 子串存储:我们需要存储提取的子串和重复拼接后的字符串,这需要O(n)的额外空间。
  • 其他变量:除了字符串存储外,我们只需要常数级别的额外空间。

因此,算法的空间复杂度为O(n)。

优化思路

虽然当前的算法已经可以解决问题,但我们可以考虑一些优化思路来进一步提高效率:

  1. KMP算法:KMP算法是一种高效的字符串匹配算法,可以在O(n)时间内完成字符串匹配。我们可以利用KMP算法的思想来优化子串匹配的过程。
  2. 预处理:我们可以预处理输入字符串,计算出每个位置的最长前缀和后缀匹配长度,从而减少重复拼接和比较的次数。

总结

通过上述分析,我们可以看到,这个问题本质上是一个字符串模式匹配问题,通过遍历所有可能的子串长度并检查是否可以通过重复得到输入字符串,我们可以找到最短的重复子串。虽然当前的算法时间复杂度为O(n^2),但对于大多数实际应用场景来说,这个复杂度是可以接受的。如果需要进一步优化,可以考虑使用KMP算法等更高效的字符串匹配算法。

详细解释

  1. 头文件包含

    cpp

    #include

    #include

    • 我们包含了<iostream><string>头文件。<iostream>用于输入输出操作,<string>用于字符串操作。
  2. 函数定义

    cpp

    std::string solution(const std::string &inp) {

    • 我们定义了一个名为solution的函数,该函数接受一个常量引用inp作为输入,并返回一个字符串。const std::string &inp表示输入字符串是常量引用,这样可以避免不必要的拷贝,提高效率。
  3. 获取字符串长度

    cpp

    int n = inp.size();

    • 我们使用inp.size()获取输入字符串的长度,并将其存储在变量n中。
  4. 遍历所有可能的子串长度

    cpp

    for (int len = 1; len <= n / 2; ++len) {

    • 我们使用一个for循环遍历所有可能的子串长度,从1到n / 2。这是因为一个子串的长度不可能超过字符串长度的一半(否则无法重复拼接成原字符串)。
  5. 检查子串是否可以重复得到输入字符串

    cpp

    if (n % len == 0) {

    • 对于每个子串长度len,我们首先检查输入字符串的长度n是否是len的整数倍。如果不是,那么这个子串不可能通过重复得到输入字符串。
  6. 提取子串

    cpp

    std::string candidate = inp.substr(0, len);

    • 如果n % len == 0,我们提取长度为len的子串,并将其存储在变量candidate中。inp.substr(0, len)表示从输入字符串的第0个位置开始,提取长度为len的子串。
  7. 重复子串

    cpp

    std::string repeated;

    for (int i = 0; i < n / len; ++i) {

    repeated += candidate;

    }

    • 我们定义一个空字符串repeated,然后使用一个for循环将candidate重复拼接n / len次,直到repeated的长度等于输入字符串的长度。
  8. 比较字符串

    cpp

    if (repeated == inp) {

    return candidate;

    }

    • 我们比较repeated和输入字符串inp是否相等。如果相等,说明这个子串可以通过重复得到输入字符串,我们返回该子串。
  9. 返回空字符串

    cpp

    return "";

    • 如果在遍历所有可能的子串长度后,没有找到合适的子串,说明输入字符串不能通过任何子串的重复拼接得到,我们返回空字符串。
  10. 主函数

cpp
*   
*   
*   

int main() {

// 添加你的测试用例

std::cout << (solution("abcabcabcabc") == "abc") << std::endl;

std::cout << (solution("aaa") == "a") << std::endl;

std::cout << (solution("abababab") == "ab") << std::endl;

std::cout << (solution("ab") == "") << std::endl;

std::cout << (solution("abcdabcdabcdabcd") == "abcd") << std::endl;

std::cout << (solution("b") == "") << std::endl;

return 0;

}

*   在`main`函数中,我们添加了一些测试用例,并使用`std::cout`输出结果。每个测试用例调用`solution`函数,并检查返回值是否与预期结果相等。

总结

通过上述代码解释,我们可以看到,这个问题的核心在于遍历所有可能的子串长度,并检查是否可以通过重复该子串得到输入字符串。虽然当前的算法时间复杂度为O(n^2),但对于大多数实际应用场景来说,这个复杂度是可以接受的。如果需要进一步优化,可以考虑使用KMP算法等更高效的字符串匹配算法。

总结与反思

问题总结

在这个问题中,我们需要判断一个给定的字符串是否可以由某个子串重复拼接而成,并且如果可以,输出这个最短的子串;否则,输出空字符串。这个问题可以看作是一个字符串模式匹配问题,其中我们需要找到一个最小的重复单元,使得这个单元重复若干次后可以完全覆盖输入的字符串。

解题思路总结

  1. 理解问题

    • 我们需要找到一个最短的子串,使得这个子串重复若干次后可以得到输入的字符串。
    • 这个问题可以看作是一个字符串模式匹配问题,其中我们需要找到一个最小的重复单元。
  2. 数据结构选择

    • 我们主要使用字符串操作。字符串是一种常见的数据结构,提供了许多内置的方法来处理字符串操作,例如获取子串、拼接字符串等。
    • 因此,我们不需要引入额外的数据结构,直接使用字符串类型即可。
  3. 算法步骤

    • 遍历所有可能的子串长度:从1到字符串长度的一半。这是因为一个子串的长度不可能超过字符串长度的一半(否则无法重复拼接成原字符串)。
    • 检查子串是否可以重复得到输入字符串
      • 对于每个子串长度,我们首先检查输入字符串的长度是否是该子串长度的整数倍。如果不是,那么这个子串不可能通过重复得到输入字符串。
      • 如果输入字符串的长度是该子串长度的整数倍,我们提取该长度的子串,并将其重复若干次,直到长度等于输入字符串的长度。
      • 然后,我们比较重复后的字符串是否等于输入字符串。如果相等,说明这个子串可以通过重复得到输入字符串,我们返回该子串。
    • 返回结果:如果在遍历所有可能的子串长度后,没有找到合适的子串,说明输入字符串不能通过任何子串的重复拼接得到,我们返回空字符串。

代码实现总结

  1. 头文件包含

    • 我们包含了<iostream><string>头文件。<iostream>用于输入输出操作,<string>用于字符串操作。
  2. 函数定义

    • 我们定义了一个名为solution的函数,该函数接受一个常量引用inp作为输入,并返回一个字符串。const std::string &inp表示输入字符串是常量引用,这样可以避免不必要的拷贝,提高效率。
  3. 获取字符串长度

    • 我们使用inp.size()获取输入字符串的长度,并将其存储在变量n中。
  4. 遍历所有可能的子串长度

    • 我们使用一个for循环遍历所有可能的子串长度,从1到n / 2。这是因为一个子串的长度不可能超过字符串长度的一半(否则无法重复拼接成原字符串)。
  5. 检查子串是否可以重复得到输入字符串

    • 对于每个子串长度len,我们首先检查输入字符串的长度n是否是len的整数倍。如果不是,那么这个子串不可能通过重复得到输入字符串。
    • 如果n % len == 0,我们提取长度为len的子串,并将其存储在变量candidate中。inp.substr(0, len)表示从输入字符串的第0个位置开始,提取长度为len的子串。
    • 我们定义一个空字符串repeated,然后使用一个for循环将candidate重复拼接n / len次,直到repeated的长度等于输入字符串的长度。
    • 我们比较repeated和输入字符串inp是否相等。如果相等,说明这个子串可以通过重复得到输入字符串,我们返回该子串。
  6. 返回空字符串

    • 如果在遍历所有可能的子串长度后,没有找到合适的子串,说明输入字符串不能通过任何子串的重复拼接得到,我们返回空字符串。
  7. 主函数

    • main函数中,我们添加了一些测试用例,并使用std::cout输出结果。每个测试用例调用solution函数,并检查返回值是否与预期结果相等。

反思与优化

  1. 时间复杂度分析

    • 当前算法的时间复杂度为O(n^2),其中n是输入字符串的长度。虽然这个时间复杂度不是最优的,但对于大多数实际应用场景来说,这个复杂度是可以接受的。
    • 我们可以考虑一些优化思路来进一步提高效率:
      • KMP算法:KMP算法是一种高效的字符串匹配算法,可以在O(n)时间内完成字符串匹配。我们可以利用KMP算法的思想来优化子串匹配的过程。
      • 预处理:我们可以预处理输入字符串,计算出每个位置的最长前缀和后缀匹配长度,从而减少重复拼接和比较的次数。
  2. 空间复杂度分析

    • 当前算法的空间复杂度为O(n),其中n是输入字符串的长度。我们需要存储提取的子串和重复拼接后的字符串,这需要O(n)的额外空间。
    • 我们可以考虑一些优化思路来减少空间复杂度:
      • 原地操作:我们可以尝试在原地进行字符串操作,避免创建额外的字符串变量。
  3. 代码可读性与维护性

    • 当前代码结构清晰,逻辑明确,易于理解和维护。
    • 我们可以进一步提高代码的可读性,例如添加更多的注释,使用更具描述性的变量名等。
  4. 边界条件处理

    • 当前代码已经考虑了边界条件,例如输入字符串长度为1的情况。
    • 我们可以进一步完善边界条件处理,例如处理空字符串的情况。

总结

通过上述总结与反思,我们可以看到,这个问题本质上是一个字符串模式匹配问题,通过遍历所有可能的子串长度并检查是否可以通过重复得到输入字符串,我们可以找到最短的重复子串。虽然当前的算法时间复杂度为O(n^2),但对于大多数实际应用场景来说,这个复杂度是可以接受的。如果需要进一步优化,可以考虑使用KMP算法等更高效的字符串匹配算法。

在实际开发中,我们还需要考虑代码的可读性、维护性以及边界条件的处理。通过不断的优化和改进,我们可以提高代码的效率和质量,使其更适用于各种实际应用场景。