{x}
blog image

Check if a Parentheses String Can Be Valid

A parentheses string is a non-empty string consisting only of '(' and ')'. It is valid if any of the following conditions is true:

  • It is ().
  • It can be written as AB (A concatenated with B), where A and B are valid parentheses strings.
  • It can be written as (A), where A is a valid parentheses string.

You are given a parentheses string s and a string locked, both of length n. locked is a binary string consisting only of '0's and '1's. For each index i of locked,

  • If locked[i] is '1', you cannot change s[i].
  • But if locked[i] is '0', you can change s[i] to either '(' or ')'.

Return true if you can make s a valid parentheses string. Otherwise, return false.

 

Example 1:

Input: s = "))()))", locked = "010100"
Output: true
Explanation: locked[1] == '1' and locked[3] == '1', so we cannot change s[1] or s[3].
We change s[0] and s[4] to '(' while leaving s[2] and s[5] unchanged to make s valid.

Example 2:

Input: s = "()()", locked = "0000"
Output: true
Explanation: We do not need to make any changes because s is already valid.

Example 3:

Input: s = ")", locked = "0"
Output: false
Explanation: locked permits us to change s[0]. 
Changing s[0] to either '(' or ')' will not make s valid.

Example 4:

Input: s = "(((())(((())", locked = "111111010111"
Output: false
Explanation: locked permits us to change s[6] and s[8]. 
We change s[6] and s[8] to ')' to make s valid.

 

Constraints:

  • n == s.length == locked.length
  • 1 <= n <= 105
  • s[i] is either '(' or ')'.
  • locked[i] is either '0' or '1'.

Solution Explanation

This problem asks whether a given parentheses string s can be made valid by changing at most some of its characters, based on the constraints given by a locked string. A valid parentheses string is one where every opening parenthesis has a corresponding closing parenthesis in the correct order.

The solution employs a clever greedy approach using two scans of the input string. It leverages the fact that if a string can be made valid, it must satisfy two conditions:

  1. Sufficient opening parentheses from left to right: When scanning from left to right, the number of opening parentheses (() or changeable parentheses (locked as '0') must always be greater than or equal to the number of closing parentheses (). If at any point, we encounter more closing parentheses than available opening/changeable parentheses, the string cannot be made valid.

  2. Sufficient closing parentheses from right to left: Similarly, when scanning from right to left, the number of closing parentheses () or changeable parentheses must always be greater than or equal to the number of opening parentheses (().

Algorithm:

  1. Length Check: The function first checks if the length of s is odd. If it is, it's impossible to make the string valid, so it returns false.

  2. Left-to-Right Scan: It initializes a counter x to 0. It iterates through s from left to right. For each character:

    • If it's an opening parenthesis '(' or the corresponding character in locked is '0' (meaning it's changeable), increment x.
    • If it's a closing parenthesis ')' and x is greater than 0, decrement x (we use an available opening parenthesis to match it).
    • If it's a closing parenthesis ')' and x is 0, it means there's no matching opening parenthesis, so the string cannot be made valid, and it returns false.
  3. Right-to-Left Scan: It resets x to 0 and iterates through s from right to left. This scan performs the same logic as the left-to-right scan but in reverse to ensure there are enough closing parentheses for each opening parenthesis.

  4. Return True: If both scans complete without returning false, it means the string can be made valid, so the function returns true.

Time Complexity: O(n), where n is the length of the strings s and locked. The algorithm performs two linear scans of the strings.

Space Complexity: O(1). The algorithm uses only a constant amount of extra space to store the counter x.

Code Examples (with slight improvements for clarity):

Python:

class Solution:
    def canBeValid(self, s: str, locked: str) -> bool:
        n = len(s)
        if n % 2 != 0:  #Odd length strings cannot be valid
            return False
 
        def check(direction):
            count = 0
            for i in range(n):
                idx = i if direction == 1 else n - 1 - i
                char = s[idx]
                locked_char = locked[idx]
 
                if char == '(' or locked_char == '0':
                    count +=1
                elif count > 0:
                    count -= 1
                else:
                    return False
            return True
        return check(1) and check(-1)

Java:

class Solution {
    public boolean canBeValid(String s, String locked) {
        int n = s.length();
        if (n % 2 != 0) return false;
 
        boolean check(int direction) {
            int count = 0;
            for (int i = 0; i < n; i++) {
                int idx = (direction == 1) ? i : n - 1 - i;
                char c = s.charAt(idx);
                char l = locked.charAt(idx);
 
                if (c == '(' || l == '0') count++;
                else if (count > 0) count--;
                else return false;
            }
            return true;
        }
        return check(1) && check(-1);
    }
}

The other languages (C++, Go) would have very similar implementations, following the same algorithmic steps. The improved code emphasizes readability by using a helper function check to encapsulate the left-to-right and right-to-left scans, making the main function cleaner.