Given a string containing only three types of characters: '(', ')' and '*', write a function to check whether this string is valid. We define the validity of a string by these rules:
- Any left parenthesis
'('
must have a corresponding right parenthesis')'
. - Any right parenthesis
')'
must have a corresponding left parenthesis'('
. - Left parenthesis
'('
must go before the corresponding right parenthesis')'
. '*'
could be treated as a single right parenthesis')'
or a single left parenthesis'('
or an empty string.- An empty string is also valid.
Example 1:
Input: "()"
Output: True
Example 2:
Input: "(*)"
Output: True
Example 3:
Input: "(*))"
Output: True
Note:
- The string size will be in the range [1, 100].
One way of solving this is to simulate a run through of s.
1. each time we see a (, push it to a stack;
2. if we see a *, add to the end of a deque;
3. if we see a ), we first try to use the last appearing ( to match. If there is no (, we then try to use the first appearing * to match. If we can do neither, return false.
4. after going through the entire s, we've matched all ) with ( or *. Now we need to match all remaining unmatched (. To achieve this, we must have enough * that each of these matching * is to the right of its matching (. For example, ***(((, even if we have enough *, but due to the relative position, we can't match. On the other hand, we can match (*(*(*
This requires comparing the stack top and the dqeue end one by one.
The runtime is O(N) and space is O(N).
class Solution { public boolean checkValidString(String s) { Stack<Integer> stack = new Stack<>(); ArrayDeque<Integer> wild = new ArrayDeque<>(); for(int i = 0; i < s.length(); i++) { if(s.charAt(i) == '(') { stack.push(i); } else if(s.charAt(i) == ')') { if(stack.size() > 0) { stack.pop(); } else if(wild.size() > 0) { wild.pollFirst(); } else { return false; } } else { wild.addLast(i); } } while(stack.size() > 0) { if(wild.size() == 0 || wild.pollLast() < stack.pop()) { return false; } } return true; } }
Can we solve this problem with only O(1) space? The answer is yes.
If we keep a difference count between ( and ), because we have * that can be used as (, ), or empty space, this diff count has a range of values, depending on how * is used. Let's denote this range as [minDiff, maxDiff] where minDiff represents every * is interpreted as ) and maxDiff represents every * is interpreted as (.
if (, minDiff++, maxDiff++;
if ), minDiff--, maxDiff--;
if *, maxDiff++, minDiff--;
If at any point, maxDiff is < 0, it is impossible to do a match because we don't have enough ( or * to match ).
If at any point, minDiff is < 0, we need to reset it to 0. Why? Because it is useless to have unmatched ) to the left of ( or * as we can not do a match like )( or )* anyway. So resetting to 0 represents that we ignore impossible cases like this.
Check minDiff after going through s to make sure it is 0. If not, we know we do not have enough ) or * to match all (.
O(N) runtime, O(1) space
class Solution { public boolean checkValidString(String s) { int minDiff = 0, maxDiff = 0; for(int i = 0; i < s.length(); i++) { switch(s.charAt(i)) { case '(': maxDiff++; minDiff++; break; case ')': maxDiff--; minDiff--; break; case '*': maxDiff++; minDiff--; break; } if(maxDiff < 0) { return false; } minDiff = Math.max(0, minDiff); } return minDiff == 0; } }