Facebook Pixel

2014. Longest Subsequence Repeated k Times

HardGreedyStringBacktrackingCountingEnumeration
Leetcode Link

Problem Description

You are given a string s of length n and an integer k. Your task is to find the longest subsequence that can be repeated k times in string s.

A subsequence is a string that can be derived from another string by deleting some or no characters without changing the order of the remaining characters.

A subsequence seq is considered repeated k times in string s if seq * k (which means concatenating seq with itself k times) is a subsequence of s.

For example, consider the string "bababcba" and the subsequence "bba":

  • If we concatenate "bba" 2 times, we get "bbabba"
  • We can find "bbabba" as a subsequence in "bababcba" by selecting characters at positions: b(0)b(2)ab(3,4)ba(6,7)
  • Therefore, "bba" is repeated 2 times in the string

The goal is to return the longest subsequence that can be repeated k times. If there are multiple subsequences of the same maximum length, return the lexicographically largest one. If no such subsequence exists, return an empty string.

The solution uses BFS to explore all possible subsequences. It first identifies characters that appear at least k times in the string (since any character in the repeated subsequence must appear at least k times). Then it builds subsequences incrementally, checking each candidate to see if it can be repeated k times within the original string. The check function verifies whether a given subsequence can be found k times by using a two-pointer approach to match characters sequentially through the string.

Flowchart Walkthrough

First, let's pin down the algorithm using the Flowchart. Here's a step-by-step walkthrough:

Is it a graph?

  • No: The problem involves finding subsequences in a string, not nodes and edges as in graph problems.

Need to solve for kth smallest/largest?

  • No: We're looking for the longest/lexicographically largest subsequence that can be repeated k times, not the kth smallest or largest element.

Involves Linked Lists?

  • No: The problem works with strings and subsequences, not linked list data structures.

Does the problem have small constraints?

  • Yes: Looking at the problem, we need to explore all possible subsequences and check if they can be repeated k times. The fact that we're looking for subsequences (not substrings) and need to verify each one suggests the search space is manageable. Additionally, the solution uses BFS to enumerate all possible subsequences, which is feasible only with small constraints.

Brute force / Backtracking?

  • Yes: The problem requires exploring all possible subsequences systematically. We build subsequences character by character, checking at each step whether the current subsequence can be repeated k times in the original string. This is a classic backtracking/brute force pattern where we:
    1. Start with an empty subsequence
    2. Try adding each valid character
    3. Check if the new subsequence is valid (can be repeated k times)
    4. Continue building longer subsequences or backtrack

Conclusion: The flowchart correctly identifies this as a backtracking/brute force problem. The solution implements this through BFS, where we systematically generate and test all possible subsequences, keeping track of the longest valid one that can be repeated k times.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

When we need to find the longest subsequence that can be repeated k times, we face two key challenges: generating all possible subsequences and efficiently checking if each one can be repeated k times.

The first insight is that any character appearing in our answer subsequence must appear at least k times in the original string. Why? Because if we repeat the subsequence k times, each character in it will appear k times in the concatenated result. So we can immediately filter out characters that don't meet this frequency requirement.

The second insight is about how to systematically explore all possible subsequences. Since we want the longest one (and lexicographically largest if there are ties), we can use a level-by-level exploration approach. We start with an empty string and gradually build longer subsequences by appending one character at a time. This is where BFS shines - it naturally explores all subsequences of length 1, then length 2, and so on.

Why BFS instead of DFS? BFS guarantees that when we find a valid subsequence of length n, we've already explored all subsequences of length n-1. This means the last valid subsequence we find at each level is guaranteed to be one of the longest. Since we append characters in order from our filtered character set, we also ensure lexicographic ordering within each length.

The verification step is straightforward but crucial. For each candidate subsequence, we need to check if we can find it k times in the original string. We do this by scanning through the string with two pointers - one for the original string and one for our subsequence pattern. Each time we complete matching the pattern, we decrement k. If we can match the pattern k times before reaching the end of the string, the subsequence is valid.

This approach elegantly combines the exhaustive search needed for finding all subsequences with the efficiency of pruning invalid candidates early, making it practical despite the potentially large search space.

Learn more about Greedy and Backtracking patterns.

Solution Approach

The solution implements a BFS-based approach to systematically explore all possible subsequences and find the longest one that can be repeated k times.

Step 1: Character Filtering

First, we count the frequency of each character in the string using a Counter. Then we filter out characters that appear less than k times, storing valid characters in a list cs:

cnt = Counter(s)
cs = [c for c in ascii_lowercase if cnt[c] >= k]

This preprocessing step ensures we only consider characters that could possibly be part of a subsequence repeated k times.

Step 2: BFS Initialization

We initialize a queue with an empty string and set up our answer variable:

q = deque([""])
ans = ""

The empty string serves as our starting point for building subsequences.

Step 3: Level-by-Level Exploration

We process the queue using BFS, where each level represents subsequences of a particular length:

while q:
    cur = q.popleft()
    for c in cs:
        nxt = cur + c
        if check(nxt, k):
            ans = nxt
            q.append(nxt)

For each subsequence cur from the queue, we try appending each valid character c to create a new subsequence nxt. If nxt can be repeated k times (verified by the check function), we update our answer and add nxt to the queue for further extension.

Step 4: Verification Function

The check function uses a two-pointer approach to verify if a subsequence t can be found k times in the string s:

def check(t: str, k: int) -> bool:
    i = 0
    for c in s:
        if c == t[i]:
            i += 1
            if i == len(t):
                k -= 1
                if k == 0:
                    return True
                i = 0
    return False

The function maintains pointer i for the current position in pattern t. As we scan through s, whenever we find a matching character, we advance i. When we complete matching the entire pattern (i == len(t)), we decrement k and reset i to start matching the pattern again. If we successfully match the pattern k times, we return True.

Why This Works:

  • BFS ensures we explore subsequences in increasing order of length, so the last valid subsequence we find is among the longest
  • By iterating through characters in cs (which contains characters in alphabetical order), we naturally handle the lexicographic requirement
  • The two-pointer verification is efficient, running in O(n) time for each check
  • The overall approach guarantees finding the optimal solution by exhaustively checking all valid possibilities

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through the solution with s = "bababcba" and k = 2.

Step 1: Character Filtering First, count character frequencies:

  • 'a': 3 times
  • 'b': 4 times
  • 'c': 1 time

Since we need characters that appear at least k=2 times, we filter to get cs = ['a', 'b'].

Step 2: BFS Exploration

We start with queue = [""] and begin exploring:

Level 0: Process ""

  • Try appending 'a': Check if "a" can repeat 2 times
    • Scan: b[a]babcba → found 1st 'a' at index 1
    • Continue: bab[a]bcba → found 2nd 'a' at index 3
    • ✓ Valid! Update ans = "a", add to queue
  • Try appending 'b': Check if "b" can repeat 2 times
    • Scan: [b]ababcba → found 1st 'b' at index 0
    • Continue: ba[b]abcba → found 2nd 'b' at index 2
    • ✓ Valid! Update ans = "b", add to queue

Queue now: ["a", "b"]

Level 1: Process "a"

  • Try appending 'a': Check if "aa" can repeat 2 times (need "aaaa")
    • Scan for 4 'a's total, but only have 3 'a's in string
    • ✗ Invalid, skip
  • Try appending 'b': Check if "ab" can repeat 2 times (need "abab")
    • Scan: b[a]babcba → found 1st 'a' at index 1
    • Continue: ba[b]abcba → found 1st 'b' at index 2
    • Continue: bab[a]bcba → found 2nd 'a' at index 3
    • Continue: baba[b]cba → found 2nd 'b' at index 4
    • ✓ Valid! Update ans = "ab", add to queue

Level 1 (continued): Process "b"

  • Try appending 'a': Check if "ba" can repeat 2 times (need "baba")
    • Scan: [b]ababcba → found 1st 'b' at index 0
    • Continue: b[a]babcba → found 1st 'a' at index 1
    • Continue: ba[b]abcba → found 2nd 'b' at index 2
    • Continue: bab[a]bcba → found 2nd 'a' at index 3
    • ✓ Valid! Update ans = "ba", add to queue
  • Try appending 'b': Check if "bb" can repeat 2 times (need "bbbb")
    • Need 4 'b's in order, but can only find: b(0), b(2), b(4), b(6)
    • ✓ Valid! Update ans = "bb", add to queue

Queue now: ["ab", "ba", "bb"]

Level 2: Process "ab", "ba", "bb"

For "ab":

  • Try "aba": Check if can repeat 2 times (need "abaaba")
    • After checking: ✗ Cannot find this pattern twice
  • Try "abb": Check if can repeat 2 times (need "abbabb")
    • After checking: ✗ Cannot find this pattern twice

For "ba":

  • Try "baa": ✗ Invalid
  • Try "bab": ✗ Invalid

For "bb":

  • Try "bba": Check if can repeat 2 times (need "bbabba")
    • Scan: [b]ababcba → 1st 'b'
    • Continue: ba[b]abcba → 2nd 'b'
    • Continue: bab[a]bcba → 1st 'a'
    • Continue: baba[b]cba → 3rd 'b'
    • Continue: babab[b]a → 4th 'b'
    • Continue: bababcb[a] → 2nd 'a'
    • ✓ Valid! Update ans = "bba"
  • Try "bbb": ✗ Invalid

The process continues, but no subsequence longer than 3 characters can be repeated twice.

Final Answer: "bba" - the longest subsequence that can be repeated 2 times in "bababcba".

Solution Implementation

1from collections import Counter, deque
2from string import ascii_lowercase
3
4class Solution:
5    def longestSubsequenceRepeatedK(self, s: str, k: int) -> str:
6        def is_valid_subsequence(pattern: str, required_count: int) -> bool:
7            """
8            Check if the pattern can be found as a subsequence in string s
9            at least required_count times.
10            """
11            pattern_index = 0
12            for char in s:
13                if char == pattern[pattern_index]:
14                    pattern_index += 1
15                    # If we've matched the entire pattern once
16                    if pattern_index == len(pattern):
17                        required_count -= 1
18                        # If we've found the pattern k times, return True
19                        if required_count == 0:
20                            return True
21                        # Reset to find the pattern again
22                        pattern_index = 0
23            return False
24
25        # Count frequency of each character in the string
26        char_frequency = Counter(s)
27      
28        # Only consider characters that appear at least k times
29        # (necessary condition for being in a subsequence repeated k times)
30        valid_chars = [char for char in ascii_lowercase if char_frequency[char] >= k]
31      
32        # BFS queue to build subsequences incrementally
33        queue = deque([""])
34        result = ""
35      
36        # BFS to find the longest valid subsequence
37        while queue:
38            current_subsequence = queue.popleft()
39          
40            # Try extending the current subsequence with each valid character
41            for char in valid_chars:
42                next_subsequence = current_subsequence + char
43              
44                # Check if this new subsequence can be repeated k times
45                if is_valid_subsequence(next_subsequence, k):
46                    # Update result (BFS guarantees we find longest first)
47                    result = next_subsequence
48                    # Add to queue for further extension
49                    queue.append(next_subsequence)
50      
51        return result
52
1class Solution {
2    private char[] sourceArray;
3
4    /**
5     * Finds the longest subsequence that can be repeated k times in string s
6     * @param s the input string
7     * @param k the number of times the subsequence should repeat
8     * @return the lexicographically largest valid subsequence
9     */
10    public String longestSubsequenceRepeatedK(String s, int k) {
11        this.sourceArray = s.toCharArray();
12      
13        // Count frequency of each character in the string
14        int[] charFrequency = new int[26];
15        for (char ch : this.sourceArray) {
16            charFrequency[ch - 'a']++;
17        }
18
19        // Collect characters that appear at least k times (potential candidates)
20        List<Character> validCharacters = new ArrayList<>();
21        for (char ch = 'a'; ch <= 'z'; ++ch) {
22            if (charFrequency[ch - 'a'] >= k) {
23                validCharacters.add(ch);
24            }
25        }
26      
27        // BFS to find the longest valid subsequence
28        Deque<String> queue = new ArrayDeque<>();
29        queue.offer("");
30        String result = "";
31      
32        while (!queue.isEmpty()) {
33            String currentString = queue.poll();
34          
35            // Try appending each valid character to current string
36            for (char ch : validCharacters) {
37                String nextString = currentString + ch;
38              
39                // Check if the new string can be repeated k times as subsequence
40                if (isValidSubsequence(nextString, k)) {
41                    result = nextString;  // Update result (BFS ensures longest)
42                    queue.offer(nextString);
43                }
44            }
45        }
46      
47        return result;
48    }
49
50    /**
51     * Checks if pattern can be found k times as a subsequence in sourceArray
52     * @param pattern the pattern to search for
53     * @param k the number of times pattern should appear
54     * @return true if pattern appears at least k times as subsequence
55     */
56    private boolean isValidSubsequence(String pattern, int k) {
57        int patternIndex = 0;
58      
59        for (char ch : sourceArray) {
60            // Match current character with pattern
61            if (ch == pattern.charAt(patternIndex)) {
62                patternIndex++;
63              
64                // Complete pattern match found
65                if (patternIndex == pattern.length()) {
66                    k--;
67                    if (k == 0) {
68                        return true;  // Found k occurrences
69                    }
70                    patternIndex = 0;  // Reset to find next occurrence
71                }
72            }
73        }
74      
75        return false;
76    }
77}
78
1class Solution {
2public:
3    string longestSubsequenceRepeatedK(string s, int k) {
4        // Lambda function to check if string pattern appears k times as subsequence in s
5        auto isValidSubsequence = [&](const string& pattern, int k) -> bool {
6            int patternIndex = 0;
7          
8            // Iterate through the original string
9            for (char ch : s) {
10                // If current character matches the pattern character
11                if (ch == pattern[patternIndex]) {
12                    patternIndex++;
13                  
14                    // If we've matched the entire pattern once
15                    if (patternIndex == pattern.size()) {
16                        k--;  // Decrement the required repetition count
17                        if (k == 0) {
18                            return true;  // Found k repetitions
19                        }
20                        patternIndex = 0;  // Reset to find next repetition
21                    }
22                }
23            }
24            return false;  // Couldn't find k repetitions
25        };
26      
27        // Count frequency of each character in the string
28        int charFrequency[26] = {};
29        for (char ch : s) {
30            charFrequency[ch - 'a']++;
31        }
32      
33        // Collect characters that appear at least k times (potential candidates)
34        vector<char> validChars;
35        for (char ch = 'a'; ch <= 'z'; ++ch) {
36            if (charFrequency[ch - 'a'] >= k) {
37                validChars.push_back(ch);
38            }
39        }
40      
41        // BFS to build subsequences incrementally
42        queue<string> bfsQueue;
43        bfsQueue.push("");  // Start with empty string
44        string result;
45      
46        // Process queue level by level
47        while (!bfsQueue.empty()) {
48            string currentPattern = bfsQueue.front();
49            bfsQueue.pop();
50          
51            // Try extending current pattern with each valid character
52            for (char ch : validChars) {
53                string nextPattern = currentPattern + ch;
54              
55                // Check if the new pattern can be repeated k times in s
56                if (isValidSubsequence(nextPattern, k)) {
57                    result = nextPattern;  // Update result (BFS ensures longest valid)
58                    bfsQueue.push(nextPattern);  // Add to queue for further extension
59                }
60            }
61        }
62      
63        return result;
64    }
65};
66
1/**
2 * Finds the longest subsequence that can be repeated k times in string s
3 * @param s - The input string
4 * @param k - The number of times the subsequence should repeat
5 * @returns The longest valid subsequence as a string
6 */
7function longestSubsequenceRepeatedK(s: string, k: number): string {
8    /**
9     * Checks if a given pattern can be found as a subsequence k times in string s
10     * @param pattern - The pattern to search for
11     * @param requiredCount - Number of times the pattern should appear
12     * @returns True if pattern appears as subsequence at least requiredCount times
13     */
14    const checkPatternExists = (pattern: string, requiredCount: number): boolean => {
15        let patternIndex = 0;
16      
17        // Iterate through each character in the original string
18        for (const char of s) {
19            // If current character matches the pattern character we're looking for
20            if (char === pattern[patternIndex]) {
21                patternIndex++;
22              
23                // If we've matched the entire pattern once
24                if (patternIndex === pattern.length) {
25                    requiredCount--;
26                  
27                    // If we've found the pattern k times, return true
28                    if (requiredCount === 0) {
29                        return true;
30                    }
31                  
32                    // Reset to search for the pattern again
33                    patternIndex = 0;
34                }
35            }
36        }
37      
38        return false;
39    };
40
41    // Count frequency of each character (26 lowercase letters)
42    const charFrequency = new Array(26).fill(0);
43    for (const char of s) {
44        charFrequency[char.charCodeAt(0) - 97]++;
45    }
46
47    // Collect characters that appear at least k times
48    const validCharacters: string[] = [];
49    for (let i = 0; i < 26; i++) {
50        if (charFrequency[i] >= k) {
51            validCharacters.push(String.fromCharCode(97 + i));
52        }
53    }
54
55    // BFS queue to explore all possible subsequences
56    const queue: string[] = [''];
57    let longestValidSubsequence = '';
58  
59    // BFS to find the longest valid subsequence
60    while (queue.length > 0) {
61        const currentSubsequence = queue.shift()!;
62      
63        // Try extending current subsequence with each valid character
64        for (const char of validCharacters) {
65            const nextSubsequence = currentSubsequence + char;
66          
67            // If the new subsequence can be repeated k times
68            if (checkPatternExists(nextSubsequence, k)) {
69                // Update the answer (BFS guarantees we find longest first)
70                longestValidSubsequence = nextSubsequence;
71                queue.push(nextSubsequence);
72            }
73        }
74    }
75
76    return longestValidSubsequence;
77}
78

Time and Space Complexity

Time Complexity: O(n * m * 2^m) where n is the length of string s and m is the number of distinct characters that appear at least k times in s.

  • The algorithm uses BFS to explore all possible subsequences built from characters that appear at least k times
  • In the worst case, we generate all possible strings from m characters, which gives us O(2^m) possible strings (each character can be included or not, multiple times)
  • For each generated string of length l, the check function takes O(n) time to verify if it's a valid subsequence repeated k times
  • The maximum length of any valid subsequence is bounded by n/k, but more tightly by m * (n/k) considering character frequencies
  • The total number of strings generated is approximately m + m^2 + m^3 + ... + m^(n/k) ≈ O(m^(n/k)) in the worst case, but practically bounded by valid subsequences
  • Since we're doing BFS level by level and only keeping valid subsequences, the effective complexity is O(n * m * 2^m)

Space Complexity: O(m * 2^m)

  • The queue q can contain at most all valid subsequences at any point
  • In the worst case, this could be all possible strings formed from m characters
  • Each string has maximum length n/k, but the number of strings dominates the space complexity
  • The counter and character list cs take O(26) = O(1) space for lowercase letters
  • Therefore, the overall space complexity is O(m * 2^m) for storing all possible valid subsequences in the queue

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Incorrect Character Frequency Filtering

A common mistake is assuming that if a character appears exactly k times, it can be part of the repeated subsequence. However, this is only a necessary condition, not sufficient.

Pitfall Example:

# Incorrect assumption
valid_chars = [char for char in s if s.count(char) == k]  # Wrong!

Why it's wrong: A character that appears k times can only appear once in the subsequence pattern (not multiple times). If a character appears 2k times, it could appear twice in the pattern.

Correct Approach:

# A character must appear AT LEAST k times to be in the pattern
valid_chars = [char for char in ascii_lowercase if char_frequency[char] >= k]

2. Not Handling Lexicographic Order Properly

The problem asks for the lexicographically largest subsequence among those with maximum length. A common pitfall is trying to find this by sorting or by complex comparisons.

Pitfall Example:

# Trying to track all max-length subsequences and then sort
max_length_subsequences = []
# ... collect all subsequences of max length
return max(max_length_subsequences)  # Inefficient and complex

Why BFS naturally handles this: Since we iterate through valid_chars in alphabetical order and BFS explores level by level, the last valid subsequence we find at each length is automatically the lexicographically largest for that length.

3. Greedy Subsequence Verification

A critical mistake is using a greedy approach that doesn't properly reset when matching the pattern multiple times.

Pitfall Example:

def incorrect_check(pattern: str, k: int) -> bool:
    matches = 0
    pattern_idx = 0
    for char in s:
        if pattern_idx < len(pattern) and char == pattern[pattern_idx]:
            pattern_idx += 1
            if pattern_idx == len(pattern):
                matches += 1
                # Forgot to reset pattern_idx!
    return matches >= k

Correct Implementation:

def is_valid_subsequence(pattern: str, required_count: int) -> bool:
    pattern_index = 0
    for char in s:
        if char == pattern[pattern_index]:
            pattern_index += 1
            if pattern_index == len(pattern):
                required_count -= 1
                if required_count == 0:
                    return True
                pattern_index = 0  # Critical: Reset to match pattern again
    return False

4. Memory/Time Limit Issues with Large Inputs

BFS can explore an exponential number of subsequences. Without proper pruning, this can lead to TLE or MLE.

Pitfall Example:

# Adding all possible subsequences without checking validity first
for char in valid_chars:
    next_subsequence = current + char
    queue.append(next_subsequence)  # Adding without validation

Optimized Approach:

# Only add to queue if it's a valid k-repeated subsequence
for char in valid_chars:
    next_subsequence = current + char
    if is_valid_subsequence(next_subsequence, k):
        queue.append(next_subsequence)  # Prune invalid branches early

5. Edge Case: Empty String Result

Don't forget that the result could be an empty string if no valid subsequence exists.

Pitfall Example:

# Assuming there's always a valid answer
return result[0]  # Could cause IndexError if result is empty

Correct Handling:

# Initialize with empty string and return as-is if no valid subsequence found
result = ""
# ... BFS logic
return result  # Correctly returns "" if no valid subsequence exists
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which of the following is a min heap?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More