leetcode
leetcode 1751 ~ 1800
重复 K 次的最长子序列

重复 K 次的最长子序列

难度:

标签:

题目描述

给你一个长度为 n 的字符串 s ,和一个整数 k 。请你找出字符串 s重复 k 次的 最长子序列

子序列 是由其他字符串删除某些(或不删除)字符派生而来的一个字符串。

如果 seq * ks 的一个子序列,其中 seq * k 表示一个由 seq 串联 k 次构造的字符串,那么就称 seq 是字符串 s 中一个 重复 k 的子序列。

  • 举个例子,"bba" 是字符串 "bababcba" 中的一个重复 2 次的子序列,因为字符串 "bbabba" 是由 "bba" 串联 2 次构造的,而 "bbabba" 是字符串 "bababcba" 的一个子序列。

返回字符串 s重复 k 次的最长子序列  。如果存在多个满足的子序列,则返回 字典序最大 的那个。如果不存在这样的子序列,返回一个 字符串。

 

示例 1:

example 1

输入:s = "letsleetcode", k = 2
输出:"let"
解释:存在两个最长子序列重复 2 次:let" 和 "ete" 。
"let" 是其中字典序最大的一个。

示例 2:

输入:s = "bb", k = 2
输出:"b"
解释:重复 2 次的最长子序列是 "b" 。

示例 3:

输入:s = "ab", k = 2
输出:""
解释:不存在重复 2 次的最长子序列。返回空字符串。

 

提示:

  • n == s.length
  • 2 <= k <= 2000
  • 2 <= n < k * 8
  • s 由小写英文字母组成

代码结果

运行时间: 1392 ms, 内存: 18.2 MB


/*
 * 思路:
 * 1. 使用回溯法生成所有可能的子序列,生成器函数使用流式编程。
 * 2. 对每一个子序列,检查其重复k次后是否仍为原字符串的子序列。
 * 3. 保存最长的符合条件的子序列,并在出现更长或者字典序更大的子序列时更新结果。
 */
import java.util.*;
import java.util.stream.*;

public class LongestRepeatingSubsequenceStream {
    public String longestSubsequenceRepeatedK(String s, int k) {
        String result = "";
        for (int len = 1; len <= s.length() / k; len++) {
            Set<String> subsequences = generateSubsequences(s, len).collect(Collectors.toSet());
            for (String subseq : subsequences) {
                if (isKTimesRepeatedSubsequence(s, subseq, k)) {
                    if (subseq.length() > result.length() || (subseq.length() == result.length() && subseq.compareTo(result) > 0)) {
                        result = subseq;
                    }
                }
            }
        }
        return result;
    }

    private Stream<String> generateSubsequences(String s, int len) {
        List<String> result = new ArrayList<>();
        generate(s, 0, len, new StringBuilder(), result);
        return result.stream();
    }

    private void generate(String s, int index, int len, StringBuilder current, List<String> result) {
        if (current.length() == len) {
            result.add(current.toString());
            return;
        }
        if (index >= s.length()) return;
        generate(s, index + 1, len, current, result);
        current.append(s.charAt(index));
        generate(s, index + 1, len, current, result);
        current.deleteCharAt(current.length() - 1);
    }

    private boolean isKTimesRepeatedSubsequence(String s, String subseq, int k) {
        int count = 0, j = 0;
        for (int i = 0; i < s.length(); i++) {
            if (s.charAt(i) == subseq.charAt(j)) {
                j++;
                if (j == subseq.length()) {
                    j = 0;
                    count++;
                    if (count == k) return true;
                }
            }
        }
        return false;
    }

    public static void main(String[] args) {
        LongestRepeatingSubsequenceStream lrs = new LongestRepeatingSubsequenceStream();
        System.out.println(lrs.longestSubsequenceRepeatedK("letsleetcode", 2)); // 输出: "let"
        System.out.println(lrs.longestSubsequenceRepeatedK("bb", 2)); // 输出: "b"
        System.out.println(lrs.longestSubsequenceRepeatedK("ab", 2)); // 输出: ""
    }
}

解释

方法:

此题解的思路首先是将字符串s转换为字符的整数表示,便于后续操作。然后构建一个next数组,用于快速定位字符在字符串中的下一个出现位置,这是通过逆序遍历字符串s并记录每个字符最后出现位置来实现的。接着,利用字符频数统计,确定哪些字符的出现次数至少是k的倍数,这些字符可能构成最终答案的一部分。题解尝试所有可能的组合(长度从1到7),使用排列尝试构建子序列,检查是否能在字符串s中重复出现k次。这一步使用next数组进行快速验证。最后,返回字典序最大的有效子序列。

时间复杂度:

O(n + 7^7 * km)

空间复杂度:

O(n + 7^7)

代码细节讲解

🦆
为什么在构建next数组时选择逆序遍历字符串s?这种做法与正序遍历有何不同?
在构建next数组时选择逆序遍历字符串s是为了方便地记录每个字符最近一次出现的位置。逆序遍历可以确保在遍历到某个字符时,直接使用已记录的信息更新next数组。如果使用正序遍历,则需要额外记录每个字符接下来的出现位置,这会使得逻辑更为复杂并可能增加错误发生的概率。
🦆
在题解中,字符的整数表示从0至25是如何帮助优化算法的?是否有其他方法可以达到相同的效果?
字符的整数表示(从0至25)通过简化字符处理帮助优化算法,使得字符可以直接作为数组索引使用,从而提高访问效率和减少内存使用。其他方法,如直接使用字符作为字典的键,虽然可行,但通常会比直接使用整数索引慢,并且在内存使用上也不如数组索引高效。
🦆
题解中提到使用next数组进行快速验证子序列是否可重复出现k次,具体是如何实现的?能否详细解释其逻辑和步骤?
使用next数组进行快速验证的逻辑是通过next数组快速定位字符串中字符的下一个出现位置。具体步骤是:通过next数组,从当前位置开始查找子序列中的下一个字符。如果找到,继续向后查找;如果找不到,表示子序列不能在字符串中重复出现k次。这种方法大大减少了查找每个可能的子序列出现位置的时间复杂度,从而提高了整体算法的效率。
🦆
为什么题解尝试的子序列长度限制在7以内?这是否意味着超过7个字符的子序列是不可能存在的?
子序列长度限制在7以内主要是出于性能考虑。考虑到排列的数量随着长度增加而急剧增加,长度大于7的排列会导致计算量过大,从而影响算法效率。这并不意味着超过7个字符的子序列不可能存在,但在实际应用中,限制长度可以在接受的时间内得到结果。

相关问题