Leetcode solution 772: Basic Calculator III

Posted baozitraining

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Leetcode solution 772: Basic Calculator III相关的知识,希望对你有一定的参考价值。

 Problem Statement 

 

Implement a basic calculator to evaluate a simple expression string.

The expression string may contain open ( and closing parentheses ), the plus + or minus sign -non-negative integers and empty spaces .

The expression string contains only non-negative integers, +-*/ operators , open ( and closing parentheses ) and empty spaces . The integer division should truncate toward zero.

You may assume that the given expression is always valid. All intermediate results will be in the range of [-2147483648, 2147483647].

Some examples:

"1 + 1" = 2
" 6-4 / 2 " = 4
"2*(5+5*2)/3+(6/2+8)" = 21
"(2+6* 3+5- (3*14/7+2)*5)+3"=-12

 

Note: Do not use the eval built-in library function.

Problem link

 

Video Tutorial

You can find the detailed video tutorial here

 

Thought Process

This is an extension problem to Basic Calculator and Basic Calculator II. The differences are this time it really looks like a real calculator where it can do "+-*/" and with brackets.

 

It‘s still the same thought process with exactly the same idea before: having two stacks,  one stack for the operator and the other for the operands, and with exactly the same calculating preferences before: calculate * and / before + and -. When seeing right bracket, continue popping till you see the left bracket.

 

This works fine assuming all the integers are non-negative, which they are supposed to be based on the problem description, and that‘s what most of the existing online leetcode solutions did as well.

 

As of 08/18/2019, this doesn‘t seem to be the case because leetcode decides to include "non-negative" integer test cases. See below case "-1+4*3/3/3"

 

技术图片

An "non-negative" integer case

 

 

Since we are practicing interviews, so let‘s also address the non-negative integer case as well

 

A few caveats

  • (New) Handle negative integer starting case:
    "-1 + 2" or "-(2+3)*4"
  • (New) Handle negative integer in between expression case:
    "1 - (-7)"
  • (New) Calculate whenever you can calculate since division order matters
    15 / 3 / 5 should be 1, but it won‘t work 3 / 5 = 0,  then 15/0 invalid
  • Pay attention to the order when popping out operands and calculate, the order matters. 
  • The number might not be just single digit, so need to read the char and convert the numbers

Solutions

 

Two stacks for non-negative integer solution

Keep two stacks, operator and operands as explained in the above "Thought Process"

 

  1 public int calculate(String s) 
  2         if (s == null || s.length() == 0) 
  3             throw new IllegalArgumentException("invalid input");
  4         
  5 
  6         int i = 0;
  7 
  8         Stack<Long> operands = new Stack<>();
  9         Stack<Character> operators = new Stack<>();
 10         StringBuilder number = new StringBuilder(); // deal with non single digit numbers
 11 
 12         while (i < s.length()) 
 13             char c = s.charAt(i);
 14 
 15             if (c == ‘ ‘) 
 16                 i++;
 17                 continue;
 18             
 19 
 20             if (Character.isDigit(c)) 
 21                 number.append(c);
 22              else if (this.isValidOperator(c)) 
 23                 if (number.length() != 0) 
 24                     operands.push(Long.parseLong(number.toString()));
 25                     number = new StringBuilder();
 26                 
 27 
 28                 // Basically based on different priority of operators
 29                 if (operators.isEmpty()) 
 30                     // it says it only contains non-negative integer, but now we have "-1 + 2", "-(2+3)*4"
 31                     operators.push(c);
 32                  else if (!operators.isEmpty() && (c == ‘*‘ || c == ‘/‘) && (operators.peek() == ‘+‘ || operators.peek() == ‘-‘)) 
 33                     // do nothing, keep pushing because */ has higher priority than +-
 34                     operators.push(c);
 35                  else if (!operators.isEmpty() && (c == ‘+‘ || c == ‘-‘) && (operators.peek() == ‘*‘ || operators.peek() == ‘/‘)) 
 36                     // calculate all previous expressions till hit the left bracket
 37                     while (!operators.isEmpty() && operators.peek() != ‘(‘) 
 38                         operands.push(this.calculateValue(operands, operators.pop()));
 39                     
 40 
 41                     operators.push(c);
 42                  else if (c == ‘(‘) 
 43                     operators.push(c);
 44                  else if (c == ‘)‘) 
 45                     if (number.length() != 0) 
 46                         operands.push(Long.parseLong(number.toString()));
 47                         number = new StringBuilder();
 48                     
 49 
 50                     while (!operators.isEmpty() && operators.peek() != ‘(‘) 
 51                         char op = operators.pop();
 52                         operands.push(this.calculateValue(operands, op));
 53 
 54                     
 55                     operators.pop(); // pop out the left (
 56 
 57                  else 
 58                     // for normal +- with previous +- || */ with previous */ case just calculate 1 step ahead
 59                     // but because 15 / 3 / 5 should be 1, but it won‘t work 3 / 5 = 0, so we have to calculate 
 60                     // every time we can calculate and get result 
 61 
 62                     if (!operators.isEmpty() && operators.peek() != ‘(‘) 
 63                         operands.push(this.calculateValue(operands, operators.pop()));
 64                     
 65 
 66                     operators.push(c);
 67 
 68 
 69                 
 70             
 71             i++;
 72         
 73 
 74         if (number.length() != 0) 
 75             operands.push(Long.parseLong(number.toString()));
 76         
 77         // for "3+2*2" case that‘s why we need a while loop
 78         while (!operators.isEmpty()) 
 79             operands.push(this.calculateValue(operands, operators.pop()));
 80         
 81 
 82         return (int)operands.pop().longValue();
 83     
 84 
 85     private boolean isValidOperator(char op) 
 86         if (op == ‘+‘ || op == ‘-‘ || op == ‘*‘ || op == ‘/‘ || op == ‘(‘ || op == ‘)‘) 
 87             return true;
 88         
 89         return false;
 90     
 91 
 92     private long calculateValue(Stack<Long> operands, char op) 
 93         long o2 = operands.pop();
 94         long o1 = operands.pop();
 95 
 96         if (op == ‘+‘) 
 97             return o1 + o2;
 98          else if (op == ‘-‘) 
 99             return o1 - o2;
100          else if (op == ‘*‘) 
101             return o1 * o2;
102          else if (op == ‘/‘) 
103             return o1 / o2;
104          else 
105             throw new IllegalArgumentException("invalid op! " + op);
106         
107     

 

 

 

Time Complexity: O(N), N is the length of the string

Space Complexity: O(N), extra stack is needed

 

Two stacks for negative integer solution

Essentially how do address the below two situations given the current code structure

  • (New) Handle negative integer starting case. I simply just check if the first char in trimmed string is "-", and push a -1 into operands and "*" into operator. (e.g.,  -(a+b) = -1 * (a+b) and -a = -1 * a)
    "-1 + 2" or "-(2+3)*4"
  • (New) Handle negative integer in between expression case. This is a bit hacky because a  "-" in the middle of the expression could mean two things: a normal minus sign or a negative sigh denoting negative integer. Luckily "1 - - 2" would not be a valid case which means there should always be a left bracket before the negative sign for negative integer. What I did was using isNegativeNumberFollowingLeftBracket to find those cases and put a -1 and * into the operator and operands respectively
    "1 - (-7)"

 

  1 public int calculate(String s) 
  2         if (s == null || s.length() == 0) 
  3             throw new IllegalArgumentException("invalid input");
  4         
  5         
  6         s = s.trim();
  7         int i = 0;
  8 
  9         Stack<Long> operands = new Stack<>();
 10         Stack<Character> operators = new Stack<>();
 11         StringBuilder number = new StringBuilder(); // deal with non single digit numbers
 12 
 13         while (i < s.length()) 
 14             char c = s.charAt(i);
 15 
 16             if (c == ‘ ‘) 
 17                 i++;
 18                 continue;
 19             
 20 
 21             if (Character.isDigit(c)) 
 22                 number.append(c);
 23              else if (this.isValidOperator(c)) 
 24                 if (number.length() != 0) 
 25                     operands.push(Long.parseLong(number.toString()));
 26                     number = new StringBuilder();
 27                 
 28 
 29                 // Basically based on different priority of operators
 30                 if (operators.isEmpty()) 
 31                     // it says it only contains non-negative integer, but now we have "-1 + 2", "-(2+3)*4"
 32                     // this is to deal with the starting negative case
 33                     if (c == ‘-‘ && i == 0) 
 34                         operands.push(-1L);
 35                         operators.push(‘*‘);
 36                      else 
 37                         operators.push(c);
 38                     
 39                  else if (!operators.isEmpty() && (c == ‘*‘ || c == ‘/‘) && (operators.peek() == ‘+‘ || operators.peek() == ‘-‘)) 
 40                     // do nothing, keep pushing because */ has higher priority than +-
 41                     operators.push(c);
 42                  else if (!operators.isEmpty() && (c == ‘+‘ || c == ‘-‘) && (operators.peek() == ‘*‘ || operators.peek() == ‘/‘)) 
 43                     // calculate all previous expressions till hit the left bracket
 44                     while (!operators.isEmpty() && operators.peek() != ‘(‘) 
 45                         operands.push(this.calculateValue(operands, operators.pop()));
 46                     
 47 
 48                     operators.push(c);
 49                  else if (c == ‘(‘) 
 50                     operators.push(c);
 51                     // this is to deal with 1 * (-7+2) case
 52                     if (this.isNegativeNumberFollowingLeftBracket(s, i + 1)) 
 53                         operands.push(-1L);
 54                         operators.push(‘*‘);
 55                         while (i < s.length()) 
 56                             if (s.charAt(i) == ‘-‘) 
 57                                 // i++;  // skip this ‘-‘, later we i++ on line 129
 58                                 break;
 59                             
 60                             i++;
 61                         
 62                     
 63                  else if (c == ‘)‘) 
 64                     if (number.length() != 0) 
 65                         operands.push(Long.parseLong(number.toString()));
 66                         number = new StringBuilder();
 67                     
 68 
 69                     while (!operators.isEmpty() && operators.peek() != ‘(‘) 
 70                         char op = operators.pop();
 71                         operands.push(this.calculateValue(operands, op));
 72 
 73                     
 74                     operators.pop(); // pop out the left (
 75 
 76                  else 
 77                     // for normal +- with previous +- || */ with previous */ case just calculate 1 step ahead
 78                     // but because 15 / 3 / 5 should be 1, but it won‘t work 3 / 5 = 0, so we have to calculate
 79                     // every time we can calculate and get result
 80                     if (!operators.isEmpty() && operators.peek() != ‘(‘) 
 81                         operands.push(this.calculateValue(operands, operators.pop()));
 82                     
 83                     operators.push(c);
 84                 
 85             
 86             i++;
 87         
 88 
 89         if (number.length() != 0) 
 90             operands.push(Long.parseLong(number.toString()));
 91         
 92         // for "3+2*2" case that‘s why we need a while loop
 93         while (!operators.isEmpty()) 
 94             operands.push(this.calculateValue(operands, operators.pop()));
 95         
 96 
 97         return (int)operands.pop().longValue();
 98     
 99 
100     // given the current index(this would normally be the index after the ‘(‘, find out if there is
101     // a negative integer following the ‘(‘
102     private boolean isNegativeNumberFollowingLeftBracket(String s, int index) 
103         while (index < s.length()) 
104             char c = s.charAt(index);
105             if (c == ‘ ‘) 
106                 index++;
107                 continue;
108             
109             if (c == ‘-‘) 
110                 return this.isDigitFollowing(s, index + 1);
111              else 
112                 return false;
113             
114         
115         return false;
116     
117 
118     // given the current index, find out if it‘s a digit following it.
119     private boolean isDigitFollowing(String s, int index) 
120         while (index < s.length()) 
121             char c = s.charAt(index);
122             if (c == ‘ ‘) 
123                 index++;
124                 continue;
125             
126             if (Character.isDigit(c)) 
127                 return true;
128             
129             return false;
130         
131         return false;
132     
133 
134 
135     private boolean isValidOperator(char op) 
136         if (op == ‘+‘ || op == ‘-‘ || op == ‘*‘ || op == ‘/‘ || op == ‘(‘ || op == ‘)‘) 
137             return true;
138         
139         return false;
140     
141 
142     private long calculateValue(Stack<Long> operands, char op) 
143         long o2 = operands.pop();
144         long o1 = operands.pop();
145 
146         if (op == ‘+‘) 
147             return o1 + o2;
148          else if (op == ‘-‘) 
149             return o1 - o2;
150          else if (op == ‘*‘) 
151             return o1 * o2;
152          else if (op == ‘/‘) 
153             return o1 / o2;
154          else 
155             throw new IllegalArgumentException("invalid op! " + op);
156         
157     

 

 

Time Complexity: O(N), N is the length of the string

Space Complexity: O(N), extra stack is needed

 

References

以上是关于Leetcode solution 772: Basic Calculator III的主要内容,如果未能解决你的问题,请参考以下文章

Leetcode 772. Basic Calculator III

LeetCode338. Counting Bits (2 solutions)

Personal Leetcode solution(Python) 21-40

LeetCode Solution-141

LeetCode Solution-125

LeetCode Solution-90