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.
Table of Content
[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.
#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;
}
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));
}
}
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))
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));
}
}
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.
#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;
}
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));
}
}
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))
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));
}
}
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