Given a string s, check if it is possible to construct the given string s by performing any of the below operations any number of times. In each step, we can:
- Add any character at the end of the string.
- or, append the string to the string itself.
Examples:
Input: s = "aaaaaaaa"
Output: 4
Explanation: Build "a" by adding 'a', then "aa" by adding 'a' again, then double "aa" to get "aaaa", then double "aaaa" to get "aaaaaaaa". Total 4 operations.Input: s = "abcabca"
Output: 5
Explanation: Build "abc" by adding 'a', 'b' and 'c' one by one, then double "abc" to get "abcabc", then add 'a' to get "abcabca". Total 5 operations.
Table of Content
[Naive Approach] Using Recursion - O(2 ^ n) Time and O(n) Space
The idea is to recursively find the minimum operations needed to build a prefix of length i. At each step, we do either of the following two
- Extend the prefix of length i-1 by adding one character.
- Double the prefix of length i/2 if it matches the second half.
We take whichever option costs fewer operations, recursing all the way down to the empty prefix.
Step By step Implementation:
- Start the recursion at i = n, the full length of s.
- At i = 0, return 0 (base case).
- Try adding one character to the prefix of length i-1.
- If i is even and the first half matches the second half, also try doubling the prefix of length i/2.
- Return the smaller of these two costs, plus 1.
#include <bits/stdc++.h>
using namespace std;
int minStepsRec(string& s, int i) {
if (i == 0) return 0;
// Option 1: add a single character
int res = minStepsRec(s, i - 1) + 1;
// Option 2: double the first half if it matches the second half
if (i % 2 == 0) {
int half = i / 2;
if (s.substr(0, half) == s.substr(half, half))
res = min(res, minStepsRec(s, half) + 1);
}
return res;
}
int minSteps(string& s) {
return minStepsRec(s, s.size());
}
int main() {
string s = "aaaaaaaa";
cout << minSteps(s) << endl;
return 0;
}
class GfG {
static int minStepsRec(String s, int i) {
if (i == 0) return 0;
// Option 1: add a single character
int res = minStepsRec(s, i - 1) + 1;
// Option 2: double the first half if it matches the second half
if (i % 2 == 0) {
int half = i / 2;
if (s.substring(0, half).equals(s.substring(half, i)))
res = Math.min(res, minStepsRec(s, half) + 1);
}
return res;
}
static int minSteps(String s) {
return minStepsRec(s, s.length());
}
public static void main(String[] args) {
String s = "aaaaaaaa";
System.out.println(minSteps(s));
}
}
def minStepsRec(s, i):
if i == 0:
return 0
# Option 1: add a single character
res = minStepsRec(s, i - 1) + 1
# Option 2: double the first half if it matches the second half
if i % 2 == 0:
half = i // 2
if s[0:half] == s[half:i]:
res = min(res, minStepsRec(s, half) + 1)
return res
def minSteps(s):
return minStepsRec(s, len(s))
if __name__ == "__main__":
s = "aaaaaaaa"
print(minSteps(s))
using System;
class GfG {
static int minStepsRec(string s, int i) {
if (i == 0) return 0;
// Option 1: add a single character
int res = minStepsRec(s, i - 1) + 1;
// Option 2: double the first half if it matches the second half
if (i % 2 == 0) {
int half = i / 2;
if (s.Substring(0, half) == s.Substring(half, half))
res = Math.Min(res, minStepsRec(s, half) + 1);
}
return res;
}
static int minSteps(string s) {
return minStepsRec(s, s.Length);
}
static void Main() {
string s = "aaaaaaaa";
Console.WriteLine(minSteps(s));
}
}
function minStepsRec(s, i) {
if (i === 0) return 0;
// Option 1: add a single character
let res = minStepsRec(s, i - 1) + 1;
// Option 2: double the first half if it matches the second half
if (i % 2 === 0) {
const half = i / 2;
if (s.substring(0, half) === s.substring(half, i))
res = Math.min(res, minStepsRec(s, half) + 1);
}
return res;
}
function minSteps(s) {
return minStepsRec(s, s.length);
}
// Driver code
const s = "aaaaaaaa";
console.log(minSteps(s));
Output
4
[Better Approach] Using Dynamic Programming - O(n ^ 2) Time and O(n) Space
The idea is to solve the problem for every prefix length one at a time, starting from the smallest and working up to n. We store the answer for each length in dp[i], so by the time we reach a bigger prefix, the answers for the smaller prefixes it depends on are already sitting in the array. This way, instead of recomputing dp[i-1] or dp[i/2] again and again like in recursion, we just look the value up directly.
Step By Step Implementation:
- Create a dp array of size n+1, with dp[0] = 0 (the empty string needs no operations).
- Iterate i from 1 to n, computing dp[i] in order.
- Set dp[i] = dp[i-1] + 1 as the cost of adding one character.
- If i is even and the first half of the prefix equals the second half, update dp[i] to the smaller of its current value and dp[half] + 1.
- Return dp[n] as the final answer.
#include <iostream>
#include <vector>
#include <climits>
using namespace std;
int minSteps(string& s) {
int n = s.size();
// dp[i] = minimum operations to build prefix of length i
vector<int> dp(n + 1, INT_MAX);
dp[0] = 0;
for (int i = 1; i <= n; i++) {
// Option 1: add a single character
dp[i] = dp[i - 1] + 1;
// Option 2: double the first half if it matches the second half
if (i % 2 == 0) {
int half = i / 2;
if (s.substr(0, half) == s.substr(half, half))
dp[i] = min(dp[i], dp[half] + 1);
}
}
return dp[n];
}
int main() {
string s = "aaaaaaaa";
cout << minSteps(s) << endl;
return 0;
}
import java.util.Arrays;
class GfG {
static int minSteps(String s) {
int n = s.length();
// dp[i] = minimum operations to build prefix of length i
int[] dp = new int[n + 1];
Arrays.fill(dp, Integer.MAX_VALUE);
dp[0] = 0;
for (int i = 1; i <= n; i++) {
// Option 1: add a single character
dp[i] = dp[i - 1] + 1;
// Option 2: double the first half if it matches the second half
if (i % 2 == 0) {
int half = i / 2;
if (s.substring(0, half).equals(s.substring(half, i)))
dp[i] = Math.min(dp[i], dp[half] + 1);
}
}
return dp[n];
}
public static void main(String[] args) {
String s = "aaaaaaaa";
System.out.println(minSteps(s));
}
}
def minSteps(s):
n = len(s)
# dp[i] = minimum operations to build prefix of length i
dp = [float('inf')] * (n + 1)
dp[0] = 0
for i in range(1, n + 1):
# Option 1: add a single character
dp[i] = dp[i - 1] + 1
# Option 2: double the first half if it matches the second half
if i % 2 == 0:
half = i // 2
if s[0:half] == s[half:i]:
dp[i] = min(dp[i], dp[half] + 1)
return dp[n]
if __name__ == "__main__":
s = "aaaaaaaa"
print(minSteps(s))
using System;
class GfG {
static int minSteps(string s) {
int n = s.Length;
// dp[i] = minimum operations to build prefix of length i
int[] dp = new int[n + 1];
Array.Fill(dp, int.MaxValue);
dp[0] = 0;
for (int i = 1; i <= n; i++) {
// Option 1: add a single character
dp[i] = dp[i - 1] + 1;
// Option 2: double the first half if it matches the second half
if (i % 2 == 0) {
int half = i / 2;
if (s.Substring(0, half) == s.Substring(half, half))
dp[i] = Math.Min(dp[i], dp[half] + 1);
}
}
return dp[n];
}
static void Main() {
string s = "aaaaaaaa";
Console.WriteLine(minSteps(s));
}
}
function minSteps(s) {
const n = s.length;
// dp[i] = minimum operations to build prefix of length i
const dp = new Array(n + 1).fill(Infinity);
dp[0] = 0;
for (let i = 1; i <= n; i++) {
// Option 1: add a single character
dp[i] = dp[i - 1] + 1;
// Option 2: double the first half if it matches the second half
if (i % 2 === 0) {
const half = i / 2;
if (s.substring(0, half) === s.substring(half, i))
dp[i] = Math.min(dp[i], dp[half] + 1);
}
}
return dp[n];
}
// Driver code
const s = "aaaaaaaa";
console.log(minSteps(s));
Output
4
[Expected Approach] Using DP with Rolling Hash - O(n) Time and O(n) Space
The bottleneck in the previous approach is the substring comparison, which can take O(n) time per check, leading to O(n2) overall. We speed this up using polynomial rolling hashing - by precomputing prefix hashes, any substring's hash can be retrieved in O(1), turning each comparison into a constant time operation.
Step By Step Implementation:
- Precompute prefixHash[i] and power[i] so that the hash of any substring s[l..r) can be computed in O(1) using the formula prefixHash[r] - prefixHash[l] * power[r-l].
- Replace the direct substring comparison with a hash comparison - if the hashes of the first half and second half match, treat them as equal.
- Use a safe modulus (10^9+7) so intermediate products never overflow 64-bit integers.
#include <iostream>
#include <vector>
#include <climits>
using namespace std;
int minSteps(string& s) {
int n = s.size();
const long long MOD = 1000000007;
const long long BASE = 131;
// Precompute prefix hashes and powers of BASE
vector<long long> prefixHash(n + 1, 0), power(n + 1, 1);
for (int i = 0; i < n; i++) {
prefixHash[i + 1] = (prefixHash[i] * BASE + s[i]) % MOD;
power[i + 1] = (power[i] * BASE) % MOD;
}
// Returns hash of substring s[l..r)
auto getHash = [&](int l, int r) {
return ((prefixHash[r] - (prefixHash[l] * power[r - l]) % MOD) % MOD + MOD) % MOD;
};
// dp[i] = minimum operations to build prefix of length i
vector<int> dp(n + 1, INT_MAX);
dp[0] = 0;
for (int i = 1; i <= n; i++) {
// Option 1: add a single character
dp[i] = dp[i - 1] + 1;
// Option 2: double the first half if its hash matches the second half
if (i % 2 == 0) {
int half = i / 2;
if (getHash(0, half) == getHash(half, i))
dp[i] = min(dp[i], dp[half] + 1);
}
}
return dp[n];
}
int main() {
string s = "aaaaaaaa";
cout << minSteps(s) << endl;
return 0;
}
import java.util.Arrays;
class GfG {
static int minSteps(String s) {
int n = s.length();
final long MOD = 1000000007;
final long BASE = 131;
// Precompute prefix hashes and powers of BASE
long[] prefixHash = new long[n + 1];
long[] power = new long[n + 1];
power[0] = 1;
for (int i = 0; i < n; i++) {
prefixHash[i + 1] = (prefixHash[i] * BASE + s.charAt(i)) % MOD;
power[i + 1] = (power[i] * BASE) % MOD;
}
// dp[i] = minimum operations to build prefix of length i
int[] dp = new int[n + 1];
Arrays.fill(dp, Integer.MAX_VALUE);
dp[0] = 0;
for (int i = 1; i <= n; i++) {
// Option 1: add a single character
dp[i] = dp[i - 1] + 1;
// Option 2: double the first half if its hash matches the second half
if (i % 2 == 0) {
int half = i / 2;
long hash1 = ((prefixHash[half] - (prefixHash[0] * power[half]) % MOD) % MOD + MOD) % MOD;
long hash2 = ((prefixHash[i] - (prefixHash[half] * power[i - half]) % MOD) % MOD + MOD) % MOD;
if (hash1 == hash2)
dp[i] = Math.min(dp[i], dp[half] + 1);
}
}
return dp[n];
}
public static void main(String[] args) {
String s = "aaaaaaaa";
System.out.println(minSteps(s));
}
}
def minSteps(s):
n = len(s)
MOD = 1000000007
BASE = 131
# Precompute prefix hashes and powers of BASE
prefixHash = [0] * (n + 1)
power = [1] * (n + 1)
for i in range(n):
prefixHash[i + 1] = (prefixHash[i] * BASE + ord(s[i])) % MOD
power[i + 1] = (power[i] * BASE) % MOD
# Returns hash of substring s[l:r]
def getHash(l, r):
return (prefixHash[r] - (prefixHash[l] * power[r - l]) % MOD + MOD) % MOD
# dp[i] = minimum operations to build prefix of length i
dp = [float('inf')] * (n + 1)
dp[0] = 0
for i in range(1, n + 1):
# Option 1: add a single character
dp[i] = dp[i - 1] + 1
# Option 2: double the first half if its hash matches the second half
if i % 2 == 0:
half = i // 2
if getHash(0, half) == getHash(half, i):
dp[i] = min(dp[i], dp[half] + 1)
return dp[n]
if __name__ == "__main__":
s = "aaaaaaaa"
print(minSteps(s))
using System;
class GfG {
static int minSteps(string s) {
int n = s.Length;
const long MOD = 1000000007;
const long BASE = 131;
// Precompute prefix hashes and powers of BASE
long[] prefixHash = new long[n + 1];
long[] power = new long[n + 1];
power[0] = 1;
for (int i = 0; i < n; i++) {
prefixHash[i + 1] = (prefixHash[i] * BASE + s[i]) % MOD;
power[i + 1] = (power[i] * BASE) % MOD;
}
// Returns hash of substring s[l..r)
long GetHash(int l, int r) {
return ((prefixHash[r] - (prefixHash[l] * power[r - l]) % MOD) % MOD + MOD) % MOD;
}
// dp[i] = minimum operations to build prefix of length i
int[] dp = new int[n + 1];
Array.Fill(dp, int.MaxValue);
dp[0] = 0;
for (int i = 1; i <= n; i++) {
// Option 1: add a single character
dp[i] = dp[i - 1] + 1;
// Option 2: double the first half if its hash matches the second half
if (i % 2 == 0) {
int half = i / 2;
if (GetHash(0, half) == GetHash(half, i))
dp[i] = Math.Min(dp[i], dp[half] + 1);
}
}
return dp[n];
}
static void Main() {
string s = "aaaaaaaa";
Console.WriteLine(minSteps(s));
}
}
function minSteps(s) {
const n = s.length;
const MOD = 1000000007n;
const BASE = 131n;
// Precompute prefix hashes and powers of BASE using BigInt
const prefixHash = new Array(n + 1).fill(0n);
const power = new Array(n + 1).fill(1n);
for (let i = 0; i < n; i++) {
prefixHash[i + 1] = (prefixHash[i] * BASE + BigInt(s.charCodeAt(i))) % MOD;
power[i + 1] = (power[i] * BASE) % MOD;
}
// Returns hash of substring s[l..r)
const getHash = (l, r) => {
return ((prefixHash[r] - (prefixHash[l] * power[r - l]) % MOD) % MOD + MOD) % MOD;
};
// dp[i] = minimum operations to build prefix of length i
const dp = new Array(n + 1).fill(Infinity);
dp[0] = 0;
for (let i = 1; i <= n; i++) {
// Option 1: add a single character
dp[i] = dp[i - 1] + 1;
// Option 2: double the first half if its hash matches the second half
if (i % 2 === 0) {
const half = i / 2;
if (getHash(0, half) === getHash(half, i))
dp[i] = Math.min(dp[i], dp[half] + 1);
}
}
return dp[n];
}
// Driver code
const s = "aaaaaaaa";
console.log(minSteps(s));
Output
4