LeetCode
  • Introduction
  • Design
    • 348. Design Tic-Tac-Toe
    • 534. Design TinyURL
    • 535. Encode and Decode TinyURL
    • 346. Moving Average from Data Stream
    • 281. Zigzag Iterator
    • 381. Insert Delete GetRandom O(1) - Duplicates allowed
    • 432. All O`one Data Structure
    • 341. Flatten Nested List Iterator
    • 642. Design Search Autocomplete System
    • 170. Two Sum III - Data structure design
    • 622. Design Circular Queue
    • 295. Find Median from Data Stream
  • OOD
    • 146.LRU Cache(Amazon)
  • Sort
    • Bucket Sort
      • 791. Custom Sort String
      • 347. Top K Frequent Elements
      • 1057. Campus bikes
    • 280. Wiggle Sort
    • 75. Sort Colors
  • Binary Search
    • 278. First Bad Version
    • 162. Find Peak Element in O(logn)
    • LintCode 183. Wood Cut
    • 33. Search in Rotated Sorted Array
    • 81. Search in Rotated Sorted Array II
    • 153. Find Minimum in Rotated Sorted Array
    • 154. Find Minimum in Rotated Sorted Array II
    • 34. Find First and Last Position of Element in Sorted Array
    • Count number of occurrences (or frequency) in a sorted array
    • 378. Kth Smallest Element in a Sorted Matrix
    • 240. Search a 2D Matrix II
    • 658. Find K Closest Elements
    • 410. Split Array Largest Sum
  • Data Structure
    • Arrays
      • Two Pointer
        • 11. Container With Most Water
      • 283. Move Zeros
      • 26. Remove Duplicates from Sorted Array
      • 88. Merge Sorted Array
      • 683. K Empty Slots
      • 4. Median of Two Sorted Arrays
      • Interval
        • 56. Merge Interval
        • 57. Insert Interval
      • 54. Spiral Matrix
      • 48. Rotate Image(Amazon, MicroSoft, Apple)
      • 448. Find All Numbers Disappeared in an Array
      • 228. Summary Ranges
      • 289. Game of Life
      • 298. Product of Array Except Self
      • 825. Friends of Appropriate Ages
    • Stack
      • Postfix to Infix
      • 155. Min Stack
      • 716. Max Stack
      • 388. Longest Absolute File Path
      • 316. Remove Duplicate Letters
      • 636. Exclusive Time of Functions
      • Calculator Series
        • 227. Basic Calculator
        • 772. Basic Calculator III
    • Trie
      • 208. Implement Trie (Prefix Tree)
      • 211. Add and Search Word - Data structure design
      • 336. Palindrome Pairs
      • 212. Word Search II
    • Heap
      • 218. The Skyline Problem
      • 23. Merge k Sorted Lists
      • 252. Meeting Rooms
      • 253. Meeting Rooms II
      • 215. Kth Largest Element in an Array
      • 692. Top K Frequent Words
      • 373. Find K Pairs with Smallest Sums
    • Deque
      • 239. Sliding Window Maximum
    • Map
      • 128. Longest Consecutive Sequence
      • 350. Intersection of Two Arrays II
      • 760. Find Anagram Mappings
    • Segment Tree / Binary Indexed Tree
      • 308. Range Sum Query 2D - Mutable
      • 218. The Skyline Problem
      • 315. Count of Smaller Numbers After Self
      • 308. Range Sum Query 2D - Mutable
      • 493. Reverse Pairs
    • Linked List
      • 19. Remove Nth Node From End of List
      • 24. Swap Nodes in Pairs
      • 160. Intersection of Two Linked Lists
      • 138. Copy List with Random Pointer
      • 143. Reorder List
      • 445. Add Two Numbers II
    • Tree
      • BST
        • 109. Convert Sorted List to Binary Search Tree
        • 285. Inorder Successor in BST
        • Convert Binary Search Tree (BST) to Sorted Doubly-Linked List
        • Construct Binary Tree from Inorder and Postorder Traversal
      • 101. Symmetric Tree
      • 105. Construct Binary Tree from Preorder and Inorder Traversal
      • 106. Construct Binary Tree from Inorder and Postorder Traversal
      • 110. Balanced Binary Tree
      • 129. Sum Root to Leaf Numbers
      • 199. Binary Tree Right Side View
      • 257. Binary Tree Paths
      • 543. Diameter of Binary Tree
      • Sum Root to Leaf Numbers
    • Hash Table
      • 325. Maximum Size Subarray Sum Equals k
      • 314. Binary Tree Vertical Order Traversal
    • Union-Find
      • 721. Accounts Merge
      • 305. Number of Islands II
      • 323. Number of Connected Components in an Undirected Graph
      • 261. Graph Valid Tree
      • 947. Most Stones Removed with Same Row or Column
    • Linked List
      • 138. Copy List with Random Pointer
      • 21. Merge Two Sorted Lists
      • 23. Merge K Sorted Lists
      • 206. Reverse Linked List
      • 237. Delete Node in a Linked List
      • 141. Linked List Cycle
      • 142. Linked List Cycle II
      • 148. Sort List
      • 708. Insert into a Cyclic Sorted List
  • Graph
    • BFS/DFS
      • Topological Sort
        • 207. Course Schedule
        • 210. Course Schedule II
        • 133. Clone Graph
        • 675. Cut Off Trees for Golf Event
        • 269. Alien Dictionary
      • BackTracking
        • 39. Combination Sum
        • 40. Combination Sum II
        • 79. Word Search
        • 212. Word Search II
        • 282. Expression Add Operators
      • Memoization
        • 329. Longest Increasing Path in a Matrix
      • 733. Flood Fill
      • 279. Perfect Squares
      • 200. Number of Islands (Amazon)
      • 694. Number of Distinct Islands
      • 711. Number of Distinct Islands II
      • 98. Validate Binary Search Tree
      • 785. Is Graph Bipartite?
      • 491. Increasing Subsequences
      • 51. N-Queens
      • 52. N-Queens II
      • 286. Walls and Gates
      • 111. Minimum Depth of Binary Tree
      • 753. Craking the Safe
    • 399. Evaluate Division
    • 127. Word Ladder
    • 126. Word Ladder II
    • 339. Nested List Weight Sum
  • Traversal
    • 297. Serialize and Deserialize Binary Tree
    • 236. Lowest Common Ancestor of a Binary Tree
    • 235. Lowest Common Ancestor of a Binary Search Tree
    • 144. Binary Tree Preorder Traversal
    • 255. Verify Preorder Sequence in Binary Search Tree
    • 94. Binary Tree Inorder Traversal
    • 145. Binary Tree Post Order Traversal
    • 102. Level Order Traversal
    • 103. Binary Tree Zipzag Level Order Traversal
    • 107. Binary Tree Level Order Traversal
    • 156. Binary Upside Down
    • 173. Binary Search Tree Iterator
    • 99. Recover Binary Search Tree
    • Morris Traversal
    • 498. Diagonal Traverse
    • Flatten
      • Flatten Binary Tree to Linked List
  • Math
    • Bit Manipulation
      • 231. Power of Two
      • 393. UTF-8 Validation
      • 371. Sum of Two Integers
    • 463. Island Perimeter
    • 50. Pow(x,n)
    • 326. Power of Three
    • 67. Add Binary
    • 29. Divide Two Integers
    • 360. Sort Transformed Array
    • 247. Strobogrammatic Number II
    • 277. Find the Celebrity
    • 246. Strobogrammatic Number
    • 415. Add Strings
    • 398. Reservoir Sampling
    • 43. Multiply Strings
    • 311. Sparse Matrix Multiplication
    • 8. String to Integer (Atoi)
    • 7. Reverse Integer
    • 829. Consecutive Numbers Sum
    • MiniMax
      • Guess the Word
  • Sum
    • 1. Two Sum
    • 167. Two Sum II - Input array is sorted
    • 15. 3Sum
    • 16. 3Sum Closest
    • 18. 4Sum
    • 454. 4Sum II
    • Path sum
      • 124. Binary Tree Maximum Path Sum
      • 113. Path Sum II
      • 437. Path Sum III
    • 209. Minimum Size Subarray Sum
    • 862. Shortest Subarray with Sum at Least K
    • 560. Subarray Sum Equals K
    • 523. Continuous Subarray Sum
  • Parentheses
    • 20. Valid Parentheses
    • 32. Longest Valid Parentheses
    • 301. Remvoe Invalid Parenthesis
    • 678. Valid Parenthesis String
  • String
    • Substring Search
      • 3. Longest Substring Without Repeating Characters
      • 438. Find All Anagrams in a String
      • 76. Minimum Window Substring
      • 30. Substring with Concatenation of All Words
      • 159. Longest Substring with At Most Two Distinct Characters
      • 340. Longest Substring with At Most K Distinct Characters
      • 395. Longest Substring with At Least K Repeating Characters
    • 12. Integer to Roman
    • 13. Roman to Integer
    • 44. Wildcard Matching
    • 242. Valid Anagram
    • 49. Group Anagrams
    • 657. Judge Route Circle
    • 482. License Key Formatting
    • 681. Next Closet Time
    • 387. First Unique Character in a String
    • 426. Convert Binary Search Tree to Sorted Doubly Linked List
    • 273. Integer to English Words
    • 157. Read N Characters Given Read4
    • 158. Read N Characters Given Read4 II - Call multiple times
    • 480. Valid Word Abbreviation
    • 409. Longest Palindrome
    • 680. Valid Palindrome II
    • 125. Valid Palindrome
    • 468. Validate IP Address
    • 161. One Edit Distance
    • 38. Count and Say
    • 1096. Brace Expansion II
  • Greedy
    • 630. Course Schedule III
    • 621. Task Scheduler
    • 358. Rearrange String k Distance Apart
    • 406. Queue Reconstruction by Height
    • 316. Remove Duplicate Letters
    • 767. Reorganize String
  • Dynamic Programming
    • Palindromic Substring
      • 5. longest Palindromic Substring
    • Subarray
      • 53. Maximum Subarray
      • 152. Maximum Product Subarray
    • Backpack problem
      • Backpack I
      • Backpack II
      • Backpack III
      • Backpack IV
      • Backpack V
      • Backpack VI aka: Combination Sum IV
      • Coin Change 2
    • 70. Climbing Stairs
    • 10. Regular Expression Matching
    • 91. Decode Ways
    • 338. Counting Bits
    • 121. Best Time to Buy and Sell Stock
    • 122. Best Time to Buy and Sell Stock II
    • 123. Best Time to Buy and Sell Stock III
    • 139. Word Break
    • 140. Word Break II
    • 198. House Robber
    • 72. Edit Distance
    • 221. Maximal Square
    • 84. Largest Rectangle in Histogram
    • 312. Burst Balloons
    • 304. Range Sum Query 2D - Immutable
    • 518. Coin Change 2
    • Longest Arithmetic Progression
    • 975. Odd Even Jump
    • 1066. Campus Bikes II
  • JingChi.ai
    • Word Composition
    • Sliding Window Median
    • 537. Erect the Fence (Convex Hull Problem)
    • LintCode 558: Sliding Window Matrix Maximum
    • orienteering game
    • 扫雷
  • Twitter
    • Identifying Triangle
    • Last and Second-Last
    • 300. Longest increasing subsequence
    • Twin String
    • 647. Number of palindromic substring
    • Wildcard Matching
  • Akuna
    • C++ Intern
    • V1
    • V2
    • Cut the Sticks
    • Quant Dev
    • Postfix_to_infix
    • Drone Delivery
    • phone
  • LintCode Contest
    • Ask For Cooling Time
  • SQL
    • 176. Second Highest Salary
    • 597. Friend Requests I: Overall Acceptance Rate
  • FB 19
    • Convert Binary Search Tree (BST) to Sorted Doubly-Linked List
    • 3Sum
    • Minimum Window Substring
    • Count number of occurrences (or frequency) in a sorted array
    • Count NO2
    • Valid Palindrome
    • Merge k Sorted Lists
    • Kth Largest Element in an Array
    • Move Zeros
    • Remove Invalid Parenthesis
    • friends
    • Integer to English
    • Islands
    • Valid Palindrome
    • sec
      • Longest Increasing Path in a Matrix
      • Product of Array Except Self
      • Binary Tree Vertical Order Traversal
      • Add Binary
      • Valid Parentheses
      • sup
        • 128. Longest Consecutive Sequence
        • Combination Sum II
        • Add and Search Word - Data structure design
        • feb
  • Google 20
    • 410. Split Array Largest Sum
  • 818. Race Card
Powered by GitBook
On this page

Was this helpful?

  1. Data Structure
  2. Stack

Calculator Series

Previous636. Exclusive Time of FunctionsNext227. Basic Calculator

Last updated 5 years ago

Was this helpful?

So far, we have encountered the following series of calculator problems:

Though each of them may be solved using different methodologies, in this post I'd like to sort out the complexities and develop one "generic" solution to help us approach these problems.

Note that this post is NOT intended to deal with general calculator problems that involve complicated operands/operators. The analyses below are limited to the scope as specified by the problem descriptions of the above four problems.

I -- Definitions and terminology

In this section I will spell out some definitions to facilitate the explanation.

  • Expression: An expression is a string whose value is to be calculated. Every expression must be alternating betweenoperands andoperators.

  • Operand: An operand is a "generalized value" that can be the target of an operator. It must be one of the following three types --number,variable,subexpression.

  • Number: A number consists of digits only. The value of an operand of this type is given by the literal value of the number.

  • Variable: A variable consists of lowercase letters only. It can be either a free variable whose value is unknown, or a bound variable whose value is mapped to some number (can be looked up). Here we define the value of an operand of free variable type is given by the variable itself, while that of bound variable type is given by the value of the number to which the variable is mapped.

  • Subexpression: A subexpression is a valid expression enclosed in parentheses (which implies a recursive definition back toExpression). The value of an operand of this type is given by the calculated value of the subexpression itself.

  • Operator: An operator is some prescribed action to be taken on the target operands. It must be one of the following four types:+,-,*,/, corresponding to addition, subtraction, multiplication and integer division, respectively. Operators have precedences, where+and-havelevel oneprecedence, while*and/havelevel twoprecedence (the higher the level is, the higher the precedence is).

II -- Rules for calculations

In this section, I will specify the general rules for carrying out the actual evaluations of the expression.

Separation rule:

  • We separate the calculations into two different levels corresponding to the two precedence levels.

  • For each level of calculation, we maintain two pieces of information: thepartial result_and the_operator in effect.

  • For level one, the partial result starts from0and the initial operator in effect is+; for level two, the partial result starts from1and the initial operator in effect is*.

  • We will usel1ando1to denote respectively the partial result and the operator in effect for level one;l2ando2for level two. The operators have the following mapping: o1 == 1means+;o1 == -1means-; o2 == 1means*;o2 == -1means/. By default we havel1 = 0,o1 = 1, andl2 = 1,o2 = 1.

Precedence rule:

  • Each operand in the expression will be associated with a precedence of level two by default, meaning they can only take part in calculations of precedence level two, not level one.

  • The operand can be any of the aforementioned types (number, variable or subexpression), and will be evaluated together withl2under the action prescribed byo2.

Demotion rule:

  • The partial resultl2of precedence level two can be demoted to level one. Upon demotion,l2becomes the operand for precedence level one and will be evaluated together withl1under the action prescribed byo1.

  • The demotion happens when either a level one operator (i.e.,+or-) is hit or the end of the expression is reached. After demotion,l2ando2will be reset for following calculations.

III -- Algorithm implementations

In this section, I will lay out the general structure of the algorithm using pseudo-codes. From sectionI, we know there are at most five different types of structs contained in the expression: number, variable, subexpression, level one operators, level two operators. We will check each of them and proceed accordingly.

public int calculate(String s) {
    int l1 = 0, o1 = 1; // Initialization of level one
    int l2 = 1, o2 = 1; // Initialization of level two

    for (int i = 0; i < s.length(); i++) {
        char c = s.charAt(i);

        if (c is a digit) {

           --> we have an operand of type number, so find its value "num"
           --> then evaluate at level two: l2 = (o2 == 1 ? l2 * num : l2 / num);

        } else if (c is a lowercase letter) {

           --> we have an operand of type variable, so find its name "var"
           --> then look up the variable mapping table to find its value "num";
           --> lastly evaluate at level two: l2 = (o2 == 1 ? l2 * num : l2 / num);

        } else if (c is an opening parenthesis) {

           --> we have an operand of type subexpression, so find its string representation
           --> then recursively call the "calculate" function to find its value "num";
           --> lastly evaluate at level two: l2 = (o2 == 1 ? l2 * num : l2 / num);

        } else if (c is a level two operator) {

            --> o2 needs to be updated: o2 = (c == '*' ? 1 : -1);

        } else if (c is a level one operator) {

            --> demotion happens here: l1 = l1 + o1 * l2;
            --> o1 needs to be updated: o1 = (c == '+' ? 1 : -1);
            --> l2, o2 need to be reset: l2 = 1, o2 = 1;

       }

       return (l1 + o1 * l2); // end of expression reached, so demotion happens again
}

IV -- List of solutions

It is now straightforward to adapt the algorithm in sectionIIIto tackle each of the calculator problems. Note that not all the checks are needed for a particular version of the calculator problems. More specifically,

  • Basic Calculator I does not have variables and level two operators;

  • Basic Calculator II does not contain variables as well as subexpressions;

  • Basic Calculator III does not have variables;

  • Basic Calculator IV is the most general form but its level two operators do not include division (/).

In this section, I will list two solutions for Basic Calculator III (recursive and iterative), and one solution for Basic Calculator IV (recursive).

Basic Calculator III: Solutions for this version pretty much follow the general structure in sectionIII, except that we do not need to check for variables since the input expression does not contain any.

  • Recursive solution:O(n^2)time,O(n)space

public int basicCalculatorIII(String s) {
    int l1 = 0, o1 = 1;
    int l2 = 1, o2 = 1;

    for (int i = 0; i < s.length(); i++) {
        char c = s.charAt(i);

        if (Character.isDigit(c)) {
            int num = c - '0';

            while (i + 1 < s.length() && Character.isDigit(s.charAt(i + 1))) {
                num = num * 10 + (s.charAt(++i) - '0');
            }

            l2 = (o2 == 1 ? l2 * num : l2 / num);

        } else if (c == '(') {
            int j = i;

            for (int cnt = 0; i < s.length(); i++) {
                if (s.charAt(i) == '(') cnt++;
                if (s.charAt(i) == ')') cnt--;
                if (cnt == 0) break;
            }

            int num = basicCalculatorIII(s.substring(j + 1, i));

            l2 = (o2 == 1 ? l2 * num : l2 / num);

        } else if (c == '*' || c == '/') {
            o2 = (c == '*' ? 1 : -1);

        } else if (c == '+' || c == '-') {
            l1 = l1 + o1 * l2;
            o1 = (c == '+' ? 1 : -1);

            l2 = 1; o2 = 1;
        }
    }

    return (l1 + o1 * l2);
}
  • Iterative solution:O(n)time,O(n)space

public int basicCalculatorIII(String s) {
    int l1 = 0, o1 = 1;
    int l2 = 1, o2 = 1;

    Deque<Integer> stack = new ArrayDeque<>(); // stack to simulate recursion

    for (int i = 0; i < s.length(); i++) {
        char c = s.charAt(i);

        if (Character.isDigit(c)) {
            int num = c - '0';

            while (i + 1 < s.length() && Character.isDigit(s.charAt(i + 1))) {
                num = num * 10 + (s.charAt(++i) - '0');
            }

            l2 = (o2 == 1 ? l2 * num : l2 / num);

        } else if (c == '(') {
            // First preserve current calculation status
            stack.offerFirst(l1); stack.offerFirst(o1);
            stack.offerFirst(l2); stack.offerFirst(o2);

            // Then reset it for next calculation
            l1 = 0; o1 = 1;
            l2 = 1; o2 = 1;

        } else if (c == ')') {
            // First preserve the result of current calculation
            int num = l1 + o1 * l2;

            // Then restore previous calculation status
            o2 = stack.poll(); l2 = stack.poll();
            o1 = stack.poll(); l1 = stack.poll();

            // Previous calculation status is now in effect
            l2 = (o2 == 1 ? l2 * num : l2 / num);

        } else if (c == '*' || c == '/') {
            o2 = (c == '*' ? 1 : -1);

        } else if (c == '+' || c == '-') {
            l1 = l1 + o1 * l2;
            o1 = (c == '+' ? 1 : -1);

            l2 = 1; o2 = 1;
        }
    }

    return (l1 + o1 * l2);
}

Basic Calculator IV: Solutions for this version, however, require some extra effort apart from the general structure in sectionIII. Due to the presence of variables (free variables, to be exact), the partial results for each level of calculations may not be pure numbers, but instead expressions (simplified, of course). So we have to come up with some structure to represent these partial results.

Though multiple options exist as to designing this structure, we do want it to support operations like addition, subtraction and multiplication with relative ease. Here I represent each expression as a collection of mappings from terms to coefficients, where each term is just a list of free variables sorted in lexicographic order. Some quick examples:

  1. "2 * a * b - d * c": this expression contains two terms, where the first term is["a", "b"]and the second is["c", "d"]. The former has a coefficient of2while the latter has a coefficient of-1. So we represent the expression as two mappings:["a", "b"] --> 2and["c", "d"] --> -1.

  2. "4": this expression contains a single term, where the term haszerofree variables and thus will be written as[]. The coefficient is4so we have the mapping[] --> 4. More generally, for any expression formed only by a pure numbernum, we have[] --> num.

See below for a detailed definition of theTermandExpressionclasses.

TheTermclass will support operations for comparing two terms according to their degrees as well as for generating a customized string representation.

TheExpressionclass will support operations for adding additional terms to the existing mapping.

Addition of two expressions is done by adding all mappings in the second expression to those of the first one, and if possible, combine the coefficients of duplicate terms.

Subtraction of two expressions is implemented by first negating the coefficients of terms in the second expression and then applying addition (and thus can be combined with addition).

Multiplication of two expressions is done by collecting terms formed by merging every pair of terms from the two expressions (as well as their coefficients).

Lastly to conform to the format of the output, we have a dedicated function to convert the mappings in the result expression to a list of strings, where each string consists of the coefficient and the term connected by the*operator. Note that terms with0coefficient are ignored and terms without free variables contains numbers only.

As to complexity analysis, the nominal runtime complexity of this solution is similar to the recursive one for Basic Calculator III --O(n^2). It is also possible to use stacks to simulate the recursion process and cut the nominal time complexity down toO(n)(I will leave this as an exercise for you).

Here I used the word "nominal" because the above analyses assumed that the addition, subtraction and multiplication operations of two expressions take constant time, as is the case when the expressions are pure numbers. Apparently this assumption no longer stands true, since the number of terms may grow exponentially (think about this expression(a + b) * (c + d) * (e + f) * ...). Nevertheless, this solution should work against average/general test cases.

private static class Term implements Comparable<Term> {
    List<String> vars;
    static final Term C = new Term(Arrays.asList()); // this is the term for pure numbers

    Term(List<String> vars) {
        this.vars = vars;
    }

    public int hashCode() {
        return vars.hashCode();
    }

    public boolean equals(Object obj) {
        if (this == obj) return true;

        if (!(obj instanceof Term)) return false;

        Term other = (Term)obj;

        return this.vars.equals(other.vars);
    }

    public String toString() {
        return String.join("*", vars);
    }

    public int compareTo(Term other) {
        if (this.vars.size() != other.vars.size()) {
            return Integer.compare(other.vars.size(), this.vars.size());
        } else {
            for (int i = 0; i < this.vars.size(); i++) {
                int cmp = this.vars.get(i).compareTo(other.vars.get(i));
                if (cmp != 0) return cmp;
            }
        }

        return 0;
    }
}

private static class Expression {
    Map<Term, Integer> terms;

    Expression(Term term, int coeff) {
        terms = new HashMap<>();
        terms.put(term, coeff);
    }

    void addTerm(Term term, int coeff) {
        terms.put(term, coeff + terms.getOrDefault(term, 0));
    }
}

private Term merge(Term term1, Term term2) {
    List<String> vars = new ArrayList<>();

    vars.addAll(term1.vars);
    vars.addAll(term2.vars);
    Collections.sort(vars);

    return new Term(vars);
}

private Expression add(Expression expression1, Expression expression2, int sign) {
    for (Map.Entry<Term, Integer> e : expression2.terms.entrySet()) {
        expression1.addTerm(e.getKey(), sign * e.getValue());
    }

    return expression1;
}

private Expression mult(Expression expression1, Expression expression2) {
    Expression res = new Expression(Term.C, 0);

    for (Map.Entry<Term, Integer> e1 : expression1.terms.entrySet()) {
        for (Map.Entry<Term, Integer> e2 : expression2.terms.entrySet()) {
            res.addTerm(merge(e1.getKey(), e2.getKey()), e1.getValue() * e2.getValue());
        }
    }

    return res;
}

private Expression calculate(String s, Map<String, Integer> map) {
    Expression l1 = new Expression(Term.C, 0);
    int o1 = 1;

    Expression l2 = new Expression(Term.C, 1); 
    // we don't need 'o2' because the precedence level two operators contain '*' only

    for (int i = 0; i < s.length(); i++) {
        char c = s.charAt(i);

        if (Character.isDigit(c)) {  // this is a number
            int num = c - '0';

            while (i + 1 < s.length() && Character.isDigit(s.charAt(i + 1))) {
                num = num * 10 + (s.charAt(++i) - '0');
            }

            l2 = mult(l2, new Expression(Term.C, num));

        } else if (Character.isLowerCase(c)) { // this is a variable
            int j = i;

            while (i + 1 < s.length() && Character.isLowerCase(s.charAt(i + 1))) i++;

            String var = s.substring(j, i + 1);
            Term term = map.containsKey(var) ? Term.C : new Term(Arrays.asList(var));
            int num = map.getOrDefault(var, 1);

            l2 = mult(l2, new Expression(term, num));

        } else if (c == '(') { // this is a subexpression
            int j = i;

            for (int cnt = 0; i < s.length(); i++) {
                if (s.charAt(i) == '(') cnt++;
                if (s.charAt(i) == ')') cnt--;
                if (cnt == 0) break;
            }

            l2 = mult(l2, calculate(s.substring(j + 1, i), map));

        } else if (c == '+' || c == '-') { // level one operators
            l1 = add(l1, l2, o1);
            o1 = (c == '+' ? 1 : -1);

            l2 = new Expression(Term.C, 1);
        }
    }

    return add(l1, l2, o1);
}

private List<String> format(Expression expression) {
    List<Term> terms = new ArrayList<>(expression.terms.keySet());

    Collections.sort(terms);

    List<String> res = new ArrayList<>(terms.size());

    for (Term term : terms) {
        int coeff = expression.terms.get(term);

        if (coeff == 0) continue;

        res.add(coeff + (term.equals(Term.C) ? "" : "*" + term.toString()));
    }

    return res;
}

public List<String> basicCalculatorIV(String expression, String[] evalvars, int[] evalints) {
    Map<String, Integer> map = new HashMap<>();

    for (int i = 0; i < evalvars.length; i++) {
        map.put(evalvars[i], evalints[i]);
    }

    return format(calculate(expression, map));
}
224. Basic Calculator
227. Basic Calculator II
772. Basic Calculator III
770. Basic Calculator IV