Search code examples
stringalgorithmbrackets

Flipping algorithm


I have a string s containing different types of brackets : () and [] . How can I balance a string of this type with the minimum possible number of reversals ? I can replace any bracket with any other one.

For example : Cost for [)(] is 2, it becomes [()]. Cost for [](( is 1, it becomes []() . [(]) is not balanced.

A more complex example : )[)([)())] can be turned to ([])[(())] in 4 changes, but can also be turned to [()(()())] in 3 steps, which is the least number of modifications to make it balanced.

How can I solve the problem ?


Solution

  • First approach I came with is O(n^3) dynamic programming.

    Let match(i, j) be the number of replaces you have to make in order to make s[i] and s[j] as () or []. So match(i, j) can be either 0, 1 or 2.

    Consider dp[i][j] = the minimum cost to balance the subsequence from i to j in your brackets array. Now you will define dp[i][i + 1] as:

    dp[i][i + 1] = match(i, i + 1)
    

    Now the general rule is that we take the overall minimum between dp[i + 1][j - 1] + match(i, j) and min(dp[i][j], dp[i][p] + dp[p + 1][j]) for any i < p < j. Obviously, the result will be held in dp[1][n]. There is a C++ solution (I'll also upload a python program in about 15 minutes when I'll be done with it - not so strong with python :P).

    #include <iostream>
    #include <string>
    using namespace std;
    
    int dp[100][100];
    string s;
    int n;
    
    int match(char a, char b) {
        if (a == '(' && b == ')') {
            return 0;
        }
        if (a == '[' && b == ']') {
            return 0;
        }
        if ((a == ')' || a == ']') && (b == '(' || b == '[')) {
            return 2;
        }
        return 1;
    }
    
    int main() {
        cin >> s;
        n = s.length();
        s = " " + s;
        for (int i = 0; i <= n; ++i) {
            for (int j = 0; j <= n; ++j) {
                dp[i][j] = 0x3f3f3f3f;
            }
        }    
    
        for (int i = 1; i < n; ++i) {
            dp[i][i + 1] = match(s[i], s[i + 1]);
        }
    
        for (int k = 3; k <= n; k += 2) {
            for (int i = 1; i + k <= n; ++i) {
                int j = i + k;
                dp[i][j] = min(dp[i][j], dp[i + 1][j - 1] + match(s[i], s[j]));
                for (int p = i + 1; p <= j; p += 2) {
                    dp[i][j] = min(dp[i][j], dp[i][p] + dp[p + 1][j]);
                }
            }
        }
        cout << dp[1][n] << '\n';
        /*for (int i = 1; i <= n; ++i) {
            for (int j = 1; j <= n; ++j) {
                cout << dp[i][j] << ' ';
            }
            cout << '\n';
        }*/
        return 0;
    }
    

    Edit:

    Here you go Python :)

    s = input()
    n = len(s)
    inf = 0x3f3f3f3f
    
    def match(x, y):
        if x == '(' and y == ')':
            return 0
        if x == '[' and y == ']':
            return 0
        if (x == ')' or x == ']') and (y == '(' or y == '['):
            return 2
        return 1
    
    # dp[i][j] = min. cost for balancing a[i], a[i + 1], ..., a[j]
    dp = [[inf for j in range(n)] for i in range(n)]
    
    for i in range(n - 1):
        dp[i][i + 1] = match(s[i], s[i + 1])
    
    for k in range(3, n, 2):
        i = 0
        while i + k < n:
            j = i + k
            dp[i][j] = min(dp[i][j], dp[i + 1][j - 1] + match(s[i], s[j]))
            for p in range(i + 1, j, 2):
                dp[i][j] = min(dp[i][j], dp[i][p] + dp[p + 1][j])
            i += 1
    
    print(dp[0][n - 1])
    #for i in range(n):
    #    for j in range(n):
    #        print(dp[i][j], end = ' ')
    #    print()