Minimum Operations to Construct a String

Last Updated : 24 Jul, 2026

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.

Try It Yourself
redirect icon

[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.
C++
#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;
}
Java
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));
    }
}
Python
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))
C#
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));
    }
}
JavaScript
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.
C++
#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;
}
Java
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));
    }
}
Python
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))
C#
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));
    }
}
JavaScript
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.
C++
#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;
}
Java
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));
    }
}
Python
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))
C#
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));
    }
}
JavaScript
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
Comment