Secret Cipher

Last Updated : 31 Jul, 2026

Geek wants to send a secret message to his friend Keeg. Instead of sending the original message directly, he encrypts it by inserting the character '*'.

Keeg decodes the message as follows:

  • Traverse the encoded string from left to right and initialize the original string as empty.
  • Whenever a normal character appears, append it to the current original string.
  • Whenever '*' is encountered, remove it and append all characters before it to the end of the current original string.
  • Repeat until no '*' remains.

Given the original string s, find the lexicographically smallest encrypted string that decodes to s.

Examples:

Input: s = "ababcababcd"
Output: ab*c*d
Explanation: We can encrypt the string in following way : "ababcababcd" -> "ababc*d" -> "ab*c*d".

Input: s = "zzzzzzz"
Output: z*z*z
Explanation: The string can be encrypted in 2 ways: "z*z*z" and "z**zzz". Out of the two "z*z*z" is smaller in length.

Try It Yourself
redirect icon

[Naive Approach] Brute Force Approach - O(n ^ 2) Time and O(n) Space

The idea is to process the string from right to left. We traverse from right to left to ensure that we get smallest result. We mainly try to cover largest repeating prefix first.

For every prefix, check whether it can be divided into two identical halves by comparing corresponding characters. If both halves are equal, then replace the second half with * and continue processing only the first half. Otherwise, keep the current character.

  • Traverse the string from right to left, considering each prefix s[0...i].
  • If the current prefix has an odd length, append the current character to the answer.
  • Otherwise, compare the first half and second half of the prefix character by character.
  • If both halves are identical, append '*' to the answer and continue processing only the first half.
  • Otherwise, append the current character to the answer.
  • Finally, reverse the constructed string and prepend the first character to obtain the compressed string.
C++
#include <bits/stdc++.h>
using namespace std;

string compress(string& s)
{
    int n = s.size();

    stack<char> st;

    // Traverse the prefixes from right to left
    for (int i = n - 1; i > 0; i--)
    {
        int len = i + 1;

        // Odd-length prefixes cannot be divided into two equal halves
        if (len % 2 == 1)
        {
            st.push(s[i]);
            continue;
        }

        int half = len / 2;
        bool same = true;

        // Compare both halves character by character
        for (int j = 0; j < half; j++)
        {
            if (s[j] != s[j + half])
            {
                same = false;
                break;
            }
        }

        if (same)
        {
            // Replace the second half by '*'
            st.push('*');

            // Continue processing only the first half
            i = half;
        }
        else
        {
            st.push(s[i]);
        }
    }

    string ans;

    // The first character is always present
    ans.push_back(s[0]);

    // Reverse the constructed answer
    while (!st.empty())
    {
        ans.push_back(st.top());
        st.pop();
    }

    return ans;
}

int main()
{
    string s = "ababcababcd";
    cout << compress(s) << endl;

    return 0;
}
Java
import java.util.*;

class GFG {

    static String compress(String s)
    {
        int n = s.length();

        Stack<Character> st = new Stack<>();

        // Traverse the prefixes from right to left
        for (int i = n - 1; i > 0; i--) {

            int len = i + 1;

            // Odd-length prefixes cannot be divided into
            // two equal halves
            if (len % 2 == 1) {
                st.push(s.charAt(i));
                continue;
            }

            int half = len / 2;
            boolean same = true;

            // Compare both halves character by character
            for (int j = 0; j < half; j++) {
                if (s.charAt(j) != s.charAt(j + half)) {
                    same = false;
                    break;
                }
            }

            if (same) {

                // Replace the second half by '*'
                st.push('*');

                // Continue processing only the first half
                i = half;
            }
            else {
                st.push(s.charAt(i));
            }
        }

        StringBuilder ans = new StringBuilder();

        // The first character is always present
        ans.append(s.charAt(0));

        // Reverse the constructed answer
        while (!st.isEmpty()) {
            ans.append(st.pop());
        }

        return ans.toString();
    }

    public static void main(String[] args)
    {
        String s = "ababcababcd";

        System.out.println(compress(s));
    }
}
Python
def compress(s):
    n = len(s)

    stack = []

    # Traverse the prefixes from right to left
    i = n - 1
    while i > 0:

        length = i + 1

        # Odd-length prefixes cannot be divided into two equal halves
        if length % 2 == 1:
            stack.append(s[i])
            i -= 1
            continue

        half = length // 2
        same = True

        # Compare both halves character by character
        for j in range(half):
            if s[j] != s[j + half]:
                same = False
                break

        if same:

            # Replace the second half by '*'
            stack.append('*')

            # Continue processing only the first half
            i = half - 1
        else:
            stack.append(s[i])
            i -= 1

    # The first character is always present
    ans = [s[0]]

    # Reverse the constructed answer
    while stack:
        ans.append(stack.pop())

    return "".join(ans)


if __name__ == "__main__":

    s = "ababcababcd"

    print(compress(s))
C#
using System;
using System.Collections.Generic;

class GFG {
    static string compress(string s)
    {
        int n = s.Length;

        Stack<char> st = new Stack<char>();

        // Traverse the prefixes from right to left
        for (int i = n - 1; i > 0; i--) {
            int len = i + 1;

            // Odd-length prefixes cannot be divided into
            // two equal halves
            if (len % 2 == 1) {
                st.Push(s[i]);
                continue;
            }

            int half = len / 2;
            bool same = true;

            // Compare both halves character by character
            for (int j = 0; j < half; j++) {
                if (s[j] != s[j + half]) {
                    same = false;
                    break;
                }
            }

            if (same) {
                // Replace the second half by '*'
                st.Push('*');

                // Continue processing only the first half
                i = half;
            }
            else {
                st.Push(s[i]);
            }
        }

        string ans = s[0].ToString();

        // Reverse the constructed answer
        while (st.Count > 0) {
            ans += st.Pop();
        }

        return ans;
    }

    static void Main()
    {
        string s = "ababcababcd";

        Console.WriteLine(compress(s));
    }
}
JavaScript
function compress(s)
{
    const n = s.length;

    const stack = [];

    // Traverse the prefixes from right to left
    for (let i = n - 1; i > 0; i--) {

        const len = i + 1;

        // Odd-length prefixes cannot be divided into two
        // equal halves
        if (len % 2 === 1) {
            stack.push(s[i]);
            continue;
        }

        const half = Math.floor(len / 2);
        let same = true;

        // Compare both halves character by character
        for (let j = 0; j < half; j++) {
            if (s[j] !== s[j + half]) {
                same = false;
                break;
            }
        }

        if (same) {

            // Replace the second half by '*'
            stack.push("*");

            // Continue processing only the first half
            i = half;
        }
        else {
            stack.push(s[i]);
        }
    }

    // The first character is always present
    let ans = s[0];

    // Reverse the constructed answer
    while (stack.length > 0) {
        ans += stack.pop();
    }

    return ans;
}

// Driver Code
const s = "ababcababcd";

console.log(compress(s));

Output
ab*c*d

[Expected Approach] Using KMP Algorithm - O(n) Time and O(n) Space

Instead of comparing both halves of every prefix explicitly, the idea is to use the LPS (Longest Prefix Suffix) array from the KMP algorithm to identify the repeating pattern of each prefix. If a prefix consists of an even number of repetitions of its smallest repeating block, it can be divided into two identical halves, allowing us to replace the second half with *.

  • Compute the LPS array for the given string using the KMP preprocessing algorithm.
  • Traverse the string from right to left, considering each prefix s[0...i].
  • Skip odd-length prefixes since they cannot be divided into two equal halves.
  • For every even-length prefix, use its LPS value to determine the smallest repeating block and check whether the prefix can be split into two identical halves.
  • If it can, append '*' to the answer and continue processing only the first half; otherwise, append the current character.
  • Reverse the constructed string and prepend the first character to obtain the final compressed string.
C++
#include <bits/stdc++.h>
using namespace std;

// Computes the LPS (Longest Prefix Suffix) array
void computeLPS(string &s, vector<int> &lps)
{
    int n = s.size();

    lps[0] = 0;

    for (int i = 1; i < n; i++)
    {
        int len = lps[i - 1];

        // Find the longest prefix which is also a suffix
        while (len > 0 && s[i] != s[len])
        {
            len = lps[len - 1];
        }

        if (s[i] == s[len])
        {
            len++;
        }

        lps[i] = len;
    }
}

string compress(string& s)
{
    int n = s.size();

    // Stores the LPS value for every prefix
    vector<int> lps(n);

    computeLPS(s, lps);

    stack<char> st;

    // Traverse the prefixes from right to left
    for (int i = n - 1; i > 0; i--)
    {
        int len = i + 1;

        // Odd-length prefixes cannot be divided into two equal halves
        if (len % 2 == 1)
        {
            st.push(s[i]);
            continue;
        }

        int longestPrefixSuffix = lps[i];
        bool canCompress = false;

        // Length of the smallest repeating block
        int blockLength = len - longestPrefixSuffix;

        // Check if the prefix is made up of an even number
        // of repetitions of its smallest repeating block
        if (longestPrefixSuffix * 2 >= len && len % blockLength == 0 && (len / blockLength) % 2 == 0)
        {
            canCompress = true;
        }

        if (canCompress)
        {
            // Replace the second half with '*'
            st.push('*');

            // Continue processing only the first half
            i = (len / 2);
        }
        else
        {
            st.push(s[i]);
        }
    }

    string ans;

    // The first character is always present
    ans.push_back(s[0]);

    // Reverse the constructed answer
    while (!st.empty())
    {
        ans.push_back(st.top());
        st.pop();
    }

    return ans;
}

int main()
{
    string s = "ababcababcd";
    cout << compress(s) << endl;

    return 0;
}
Java
import java.util.*;

class GFG {

    // Computes the LPS (Longest Prefix Suffix) array
    static void computeLPS(String s, int[] lps)
    {
        int n = s.length();

        lps[0] = 0;

        for (int i = 1; i < n; i++) {

            int len = lps[i - 1];

            // Find the longest prefix which is also a
            // suffix
            while (len > 0
                   && s.charAt(i) != s.charAt(len)) {
                len = lps[len - 1];
            }

            if (s.charAt(i) == s.charAt(len)) {
                len++;
            }

            lps[i] = len;
        }
    }

    static String compress(String s)
    {
        int n = s.length();

        // Stores the LPS value for every prefix
        int[] lps = new int[n];

        computeLPS(s, lps);

        Stack<Character> st = new Stack<>();

        // Traverse the prefixes from right to left
        for (int i = n - 1; i > 0; i--) {

            int len = i + 1;

            // Odd-length prefixes cannot be divided into
            // two equal halves
            if (len % 2 == 1) {
                st.push(s.charAt(i));
                continue;
            }

            int longestPrefixSuffix = lps[i];
            boolean canCompress = false;

            // Length of the smallest repeating block
            int blockLength = len - longestPrefixSuffix;

            // Check if the prefix is made up of an even
            // number of repetitions of its smallest
            // repeating block
            if (longestPrefixSuffix * 2 >= len
                && len % blockLength == 0
                && (len / blockLength) % 2 == 0) {

                canCompress = true;
            }

            if (canCompress) {

                // Replace the second half with '*'
                st.push('*');

                // Continue processing only the first half
                i = len / 2;
            }
            else {
                st.push(s.charAt(i));
            }
        }

        StringBuilder ans = new StringBuilder();

        // The first character is always present
        ans.append(s.charAt(0));

        // Reverse the constructed answer
        while (!st.isEmpty()) {
            ans.append(st.pop());
        }

        return ans.toString();
    }

    public static void main(String[] args)
    {
        String s = "ababcababcd";
        System.out.println(compress(s));
    }
}
Python
def compute_lps(s):
    """Computes the LPS (Longest Prefix Suffix) array."""

    n = len(s)
    lps = [0] * n

    for i in range(1, n):

        length = lps[i - 1]

        # Find the longest prefix which is also a suffix
        while length > 0 and s[i] != s[length]:
            length = lps[length - 1]

        if s[i] == s[length]:
            length += 1

        lps[i] = length

    return lps


def compress(s):

    n = len(s)

    # Stores the LPS value for every prefix
    lps = compute_lps(s)

    stack = []

    # Traverse the prefixes from right to left
    i = n - 1
    while i > 0:

        length = i + 1

        # Odd-length prefixes cannot be divided into two equal halves
        if length % 2 == 1:
            stack.append(s[i])
            i -= 1
            continue

        longest_prefix_suffix = lps[i]
        can_compress = False

        # Length of the smallest repeating block
        block_length = length - longest_prefix_suffix

        # Check if the prefix is made up of an even number
        # of repetitions of its smallest repeating block
        if (longest_prefix_suffix * 2 >= length and
                length % block_length == 0 and
                (length // block_length) % 2 == 0):

            can_compress = True

        if can_compress:

            # Replace the second half with '*'
            stack.append('*')

            # Continue processing only the first half
            i = (length // 2) - 1
        else:
            stack.append(s[i])
            i -= 1

    ans = [s[0]]

    # Reverse the constructed answer
    while stack:
        ans.append(stack.pop())

    return "".join(ans)


if __name__ == "__main__":

    s = "ababcababcd"

    print(compress(s))
C#
using System;
using System.Collections.Generic;

class GFG {
    
    // Computes the LPS (Longest Prefix Suffix) array
    static void ComputeLPS(string s, int[] lps)
    {
        int n = s.Length;

        lps[0] = 0;

        for (int i = 1; i < n; i++) {
            int len = lps[i - 1];

            // Find the longest prefix which is also a
            // suffix
            while (len > 0 && s[i] != s[len]) {
                len = lps[len - 1];
            }

            if (s[i] == s[len]) {
                len++;
            }

            lps[i] = len;
        }
    }

    static string compress(string s)
    {
        int n = s.Length;

        // Stores the LPS value for every prefix
        int[] lps = new int[n];

        ComputeLPS(s, lps);

        Stack<char> st = new Stack<char>();

        // Traverse the prefixes from right to left
        for (int i = n - 1; i > 0; i--) {
            int len = i + 1;

            // Odd-length prefixes cannot be divided into
            // two equal halves
            if (len % 2 == 1) {
                st.Push(s[i]);
                continue;
            }

            int longestPrefixSuffix = lps[i];
            bool canCompress = false;

            // Length of the smallest repeating block
            int blockLength = len - longestPrefixSuffix;

            // Check if the prefix is made up of an even
            // number of repetitions of its smallest
            // repeating block
            if (longestPrefixSuffix * 2 >= len
                && len % blockLength == 0
                && (len / blockLength) % 2 == 0) {
                canCompress = true;
            }

            if (canCompress) {
                // Replace the second half with '*'
                st.Push('*');

                // Continue processing only the first half
                i = len / 2;
            }
            else {
                st.Push(s[i]);
            }
        }

        string ans = s[0].ToString();

        // Reverse the constructed answer
        while (st.Count > 0) {
            ans += st.Pop();
        }

        return ans;
    }

    static void Main()
    {
        string s = "ababcababcd";
        Console.WriteLine(compress(s));
    }
}
JavaScript
function computeLPS(s)
{
    const n = s.length;
    const lps = new Array(n).fill(0);

    for (let i = 1; i < n; i++) {

        let len = lps[i - 1];

        // Find the longest prefix which is also a suffix
        while (len > 0 && s[i] !== s[len]) {
            len = lps[len - 1];
        }

        if (s[i] === s[len]) {
            len++;
        }

        lps[i] = len;
    }

    return lps;
}

function compress(s)
{
    const n = s.length;

    // Stores the LPS value for every prefix
    const lps = computeLPS(s);

    const stack = [];

    // Traverse the prefixes from right to left
    for (let i = n - 1; i > 0; i--) {

        const len = i + 1;

        // Odd-length prefixes cannot be divided into two
        // equal halves
        if (len % 2 === 1) {
            stack.push(s[i]);
            continue;
        }

        const longestPrefixSuffix = lps[i];
        let canCompress = false;

        // Length of the smallest repeating block
        const blockLength = len - longestPrefixSuffix;

        // Check if the prefix is made up of an even number
        // of repetitions of its smallest repeating block
        if (longestPrefixSuffix * 2 >= len
            && len % blockLength === 0
            && Math.floor(len / blockLength) % 2 === 0) {

            canCompress = true;
        }

        if (canCompress) {

            // Replace the second half with '*'
            stack.push("*");

            // Continue processing only the first half
            i = Math.floor(len / 2);
        }
        else {
            stack.push(s[i]);
        }
    }

    let ans = s[0];

    // Reverse the constructed answer
    while (stack.length > 0) {
        ans += stack.pop();
    }

    return ans;
}

// Driver Code
const s = "ababcababcd";

console.log(compress(s));

Output
ab*c*d
Comment