Mutating counts without cleanup
Wrong move: Zero-count keys stay in map and break distinct/count constraints.
Usually fails on: Window/map size checks are consistently off by one.
Fix: Delete keys when count reaches zero.
Break down a hard problem into reliable checkpoints, edge-case handling, and complexity trade-offs.
Given an expression such as expression = "e + 8 - a + 5" and an evaluation map such as {"e": 1} (given in terms of evalvars = ["e"] and evalints = [1]), return a list of tokens representing the simplified expression, such as ["-1*a","14"]
"2x" or "-x".Expressions are evaluated in the usual order: brackets first, then multiplication, then addition and subtraction.
expression = "1 + 2 * 3" has an answer of ["7"].The format of the output is as follows:
"b*a*c", only "a*b*c"."a*a*b*c" has degree 4.["-2*a*a*a", "3*a*a*b", "3*b*b", "4*a", "5*c", "-6"].0 are not included.
"0" has an output of [].Note: You may assume that the given expression is always valid. All intermediate results will be in the range of [-231, 231 - 1].
Example 1:
Input: expression = "e + 8 - a + 5", evalvars = ["e"], evalints = [1] Output: ["-1*a","14"]
Example 2:
Input: expression = "e - 8 + temperature - pressure", evalvars = ["e", "temperature"], evalints = [1, 12] Output: ["-1*pressure","5"]
Example 3:
Input: expression = "(e + 8) * (e - 8)", evalvars = [], evalints = [] Output: ["1*e*e","-64"]
Constraints:
1 <= expression.length <= 250expression consists of lowercase English letters, digits, '+', '-', '*', '(', ')', ' '.expression does not contain any leading or trailing spaces.expression are separated by a single space.0 <= evalvars.length <= 1001 <= evalvars[i].length <= 20evalvars[i] consists of lowercase English letters.evalints.length == evalvars.length-100 <= evalints[i] <= 100Problem summary: Given an expression such as expression = "e + 8 - a + 5" and an evaluation map such as {"e": 1} (given in terms of evalvars = ["e"] and evalints = [1]), return a list of tokens representing the simplified expression, such as ["-1*a","14"] An expression alternates chunks and symbols, with a space separating each chunk and symbol. A chunk is either an expression in parentheses, a variable, or a non-negative integer. A variable is a string of lowercase letters (not including digits.) Note that variables can be multiple letters, and note that variables never have a leading coefficient or unary operator like "2x" or "-x". Expressions are evaluated in the usual order: brackets first, then multiplication, then addition and subtraction. For example, expression = "1 + 2 * 3" has an answer of ["7"]. The format of the output is as follows: For each term of free variables with a non-zero
Start with the most direct exhaustive search. That gives a correctness anchor before optimizing.
Pattern signal: Hash Map · Math · Stack
"e + 8 - a + 5" ["e"] [1]
"e - 8 + temperature - pressure" ["e", "temperature"] [1, 12]
"(e + 8) * (e - 8)" [] []
parse-lisp-expression)basic-calculator-iii)Source-backed implementations are provided below for direct study and interview prep.
// Accepted solution for LeetCode #770: Basic Calculator IV
class Poly {
public Poly add(Poly o) {
for (final String term : o.terms.keySet())
terms.merge(term, o.terms.get(term), Integer::sum);
return this;
}
public Poly minus(Poly o) {
for (final String term : o.terms.keySet())
terms.merge(term, -o.terms.get(term), Integer::sum);
return this;
}
public Poly mult(Poly o) {
Poly res = new Poly();
for (final String a : terms.keySet())
for (final String b : o.terms.keySet())
res.terms.merge(merge(a, b), terms.get(a) * o.terms.get(b), Integer::sum);
return res;
}
// @Override
// Public String toString() {
// StringBuilder sb = new StringBuilder();
// sb.append("{");
// for (final String term : terms.keySet())
// sb.append(term).append(": ").append(terms.get(term)).append(", ");
// sb.append("}");
// return sb.toString();
// }
public List<String> toList() {
List<String> res = new ArrayList<>();
List<String> keys = new ArrayList<>(terms.keySet());
Collections.sort(keys, new Comparator<String>() {
@Override
public int compare(final String a, final String b) {
// the minimum degree is the last
if (a.equals("1"))
return 1;
if (b.equals("1"))
return -1;
String[] as = a.split("\\*");
String[] bs = b.split("\\*");
// the maximum degree is the first
// Break ties by their lexicographic orders.
return as.length == bs.length ? a.compareTo(b) : bs.length - as.length;
}
});
for (final String key : keys)
if (terms.get(key) != 0)
res.add(concat(key));
return res;
}
public Poly() {}
public Poly(final String term, int coef) {
terms.put(term, coef);
}
private Map<String, Integer> terms = new HashMap<>();
// e.g. merge("a*b", "a*c") -> "a*a*b*c"
private static String merge(final String a, final String b) {
if (a.equals("1"))
return b;
if (b.equals("1"))
return a;
StringBuilder sb = new StringBuilder();
String[] A = a.split("\\*");
String[] B = b.split("\\*");
int i = 0; // A's index
int j = 0; // B's index
while (i < A.length && j < B.length)
if (A[i].compareTo(B[j]) < 0)
sb.append("*").append(A[i++]);
else
sb.append("*").append(B[j++]);
while (i < A.length)
sb.append("*").append(A[i++]);
while (j < B.length)
sb.append("*").append(B[j++]);
return sb.substring(1).toString();
}
private String concat(final String term) {
if (term.equals("1"))
return String.valueOf(terms.get(term));
return new StringBuilder().append(terms.get(term)).append('*').append(term).toString();
}
}
class Solution {
public List<String> basicCalculatorIV(String expression, String[] evalvars, int[] evalints) {
List<String> tokens = getTokens(expression);
Map<String, Integer> evalMap = new HashMap<>();
for (int i = 0; i < evalvars.length; ++i)
evalMap.put(evalvars[i], evalints[i]);
for (int i = 0; i < tokens.size(); ++i)
if (evalMap.containsKey(tokens.get(i)))
tokens.set(i, String.valueOf(evalMap.get(tokens.get(i))));
List<String> postfix = infixToPostfix(tokens);
return evaluate(postfix).toList();
}
private List<String> getTokens(final String s) {
List<String> tokens = new ArrayList<>();
int i = 0;
for (int j = 0; j < s.length(); ++j)
if (s.charAt(j) == ' ') {
if (i < j)
tokens.add(s.substring(i, j));
i = j + 1;
} else if ("()+-*".contains(s.substring(j, j + 1))) {
if (i < j)
tokens.add(s.substring(i, j));
tokens.add(s.substring(j, j + 1));
i = j + 1;
}
if (i < s.length())
tokens.add(s.substring(i));
return tokens;
}
private boolean isOperator(final String token) {
return token.equals("+") || token.equals("-") || token.equals("*");
}
private boolean precedes(final String prevOp, final String currOp) {
if (prevOp.equals("("))
return false;
return prevOp.equals("*") || currOp.equals("+") || currOp.equals("-");
}
private List<String> infixToPostfix(List<String> tokens) {
List<String> postfix = new ArrayList<>();
Deque<String> ops = new ArrayDeque<>();
for (final String token : tokens)
if (token.equals("(")) {
ops.push(token);
} else if (token.equals(")")) {
while (!ops.peek().equals("("))
postfix.add(ops.pop());
ops.pop();
} else if (isOperator(token)) {
while (!ops.isEmpty() && precedes(ops.peek(), token))
postfix.add(ops.pop());
ops.push(token);
} else { // isOperand(token)
postfix.add(token);
}
while (!ops.isEmpty())
postfix.add(ops.pop());
return postfix;
}
private Poly evaluate(List<String> postfix) {
LinkedList<Poly> polys = new LinkedList<>();
for (final String token : postfix)
if (isOperator(token)) {
final Poly b = polys.removeLast();
final Poly a = polys.removeLast();
if (token.equals("+"))
polys.add(a.add(b));
else if (token.equals("-"))
polys.add(a.minus(b));
else // token == "*"
polys.add(a.mult(b));
} else if (token.charAt(0) == '-' || token.chars().allMatch(c -> Character.isDigit(c))) {
polys.add(new Poly("1", Integer.parseInt(token)));
} else {
polys.add(new Poly(token, 1));
}
return polys.getFirst();
}
}
// Accepted solution for LeetCode #770: Basic Calculator IV
// Auto-generated Go example from java.
func exampleSolution() {
}
// Reference (java):
// // Accepted solution for LeetCode #770: Basic Calculator IV
// class Poly {
// public Poly add(Poly o) {
// for (final String term : o.terms.keySet())
// terms.merge(term, o.terms.get(term), Integer::sum);
// return this;
// }
//
// public Poly minus(Poly o) {
// for (final String term : o.terms.keySet())
// terms.merge(term, -o.terms.get(term), Integer::sum);
// return this;
// }
//
// public Poly mult(Poly o) {
// Poly res = new Poly();
// for (final String a : terms.keySet())
// for (final String b : o.terms.keySet())
// res.terms.merge(merge(a, b), terms.get(a) * o.terms.get(b), Integer::sum);
// return res;
// }
//
// // @Override
// // Public String toString() {
// // StringBuilder sb = new StringBuilder();
// // sb.append("{");
// // for (final String term : terms.keySet())
// // sb.append(term).append(": ").append(terms.get(term)).append(", ");
// // sb.append("}");
// // return sb.toString();
// // }
//
// public List<String> toList() {
// List<String> res = new ArrayList<>();
// List<String> keys = new ArrayList<>(terms.keySet());
// Collections.sort(keys, new Comparator<String>() {
// @Override
// public int compare(final String a, final String b) {
// // the minimum degree is the last
// if (a.equals("1"))
// return 1;
// if (b.equals("1"))
// return -1;
// String[] as = a.split("\\*");
// String[] bs = b.split("\\*");
// // the maximum degree is the first
// // Break ties by their lexicographic orders.
// return as.length == bs.length ? a.compareTo(b) : bs.length - as.length;
// }
// });
// for (final String key : keys)
// if (terms.get(key) != 0)
// res.add(concat(key));
// return res;
// }
//
// public Poly() {}
// public Poly(final String term, int coef) {
// terms.put(term, coef);
// }
//
// private Map<String, Integer> terms = new HashMap<>();
//
// // e.g. merge("a*b", "a*c") -> "a*a*b*c"
// private static String merge(final String a, final String b) {
// if (a.equals("1"))
// return b;
// if (b.equals("1"))
// return a;
// StringBuilder sb = new StringBuilder();
// String[] A = a.split("\\*");
// String[] B = b.split("\\*");
// int i = 0; // A's index
// int j = 0; // B's index
// while (i < A.length && j < B.length)
// if (A[i].compareTo(B[j]) < 0)
// sb.append("*").append(A[i++]);
// else
// sb.append("*").append(B[j++]);
// while (i < A.length)
// sb.append("*").append(A[i++]);
// while (j < B.length)
// sb.append("*").append(B[j++]);
// return sb.substring(1).toString();
// }
//
// private String concat(final String term) {
// if (term.equals("1"))
// return String.valueOf(terms.get(term));
// return new StringBuilder().append(terms.get(term)).append('*').append(term).toString();
// }
// }
//
// class Solution {
// public List<String> basicCalculatorIV(String expression, String[] evalvars, int[] evalints) {
// List<String> tokens = getTokens(expression);
// Map<String, Integer> evalMap = new HashMap<>();
//
// for (int i = 0; i < evalvars.length; ++i)
// evalMap.put(evalvars[i], evalints[i]);
//
// for (int i = 0; i < tokens.size(); ++i)
// if (evalMap.containsKey(tokens.get(i)))
// tokens.set(i, String.valueOf(evalMap.get(tokens.get(i))));
//
// List<String> postfix = infixToPostfix(tokens);
// return evaluate(postfix).toList();
// }
//
// private List<String> getTokens(final String s) {
// List<String> tokens = new ArrayList<>();
// int i = 0;
// for (int j = 0; j < s.length(); ++j)
// if (s.charAt(j) == ' ') {
// if (i < j)
// tokens.add(s.substring(i, j));
// i = j + 1;
// } else if ("()+-*".contains(s.substring(j, j + 1))) {
// if (i < j)
// tokens.add(s.substring(i, j));
// tokens.add(s.substring(j, j + 1));
// i = j + 1;
// }
// if (i < s.length())
// tokens.add(s.substring(i));
// return tokens;
// }
//
// private boolean isOperator(final String token) {
// return token.equals("+") || token.equals("-") || token.equals("*");
// }
//
// private boolean precedes(final String prevOp, final String currOp) {
// if (prevOp.equals("("))
// return false;
// return prevOp.equals("*") || currOp.equals("+") || currOp.equals("-");
// }
//
// private List<String> infixToPostfix(List<String> tokens) {
// List<String> postfix = new ArrayList<>();
// Deque<String> ops = new ArrayDeque<>();
//
// for (final String token : tokens)
// if (token.equals("(")) {
// ops.push(token);
// } else if (token.equals(")")) {
// while (!ops.peek().equals("("))
// postfix.add(ops.pop());
// ops.pop();
// } else if (isOperator(token)) {
// while (!ops.isEmpty() && precedes(ops.peek(), token))
// postfix.add(ops.pop());
// ops.push(token);
// } else { // isOperand(token)
// postfix.add(token);
// }
//
// while (!ops.isEmpty())
// postfix.add(ops.pop());
//
// return postfix;
// }
//
// private Poly evaluate(List<String> postfix) {
// LinkedList<Poly> polys = new LinkedList<>();
// for (final String token : postfix)
// if (isOperator(token)) {
// final Poly b = polys.removeLast();
// final Poly a = polys.removeLast();
// if (token.equals("+"))
// polys.add(a.add(b));
// else if (token.equals("-"))
// polys.add(a.minus(b));
// else // token == "*"
// polys.add(a.mult(b));
// } else if (token.charAt(0) == '-' || token.chars().allMatch(c -> Character.isDigit(c))) {
// polys.add(new Poly("1", Integer.parseInt(token)));
// } else {
// polys.add(new Poly(token, 1));
// }
// return polys.getFirst();
// }
// }
# Accepted solution for LeetCode #770: Basic Calculator IV
class Poly:
def __init__(self, term: str = None, coef: int = None):
if term and coef:
self.terms = collections.Counter({term: coef})
else:
self.terms = collections.Counter()
def __add__(self, other):
for term, coef in other.terms.items():
self.terms[term] += coef
return self
def __sub__(self, other):
for term, coef in other.terms.items():
self.terms[term] -= coef
return self
def __mul__(self, other):
res = Poly()
for a, aCoef in self.terms.items():
for b, bCoef in other.terms.items():
res.terms[self._merge(a, b)] += aCoef * bCoef
return res
# Def __str__(self):
# res = []
# for term, coef in self.terms.items():
# res.append(term + ': ' + str(coef))
# return '{' + ', '.join(res) + '}'
def toList(self) -> list[str]:
for term in list(self.terms.keys()):
if not self.terms[term]:
del self.terms[term]
def cmp(term: str) -> tuple:
# the minimum degree is the last
if term == '1':
return (0,)
var = term.split('*')
# the maximum degree is the first
# Break ties by their lexicographic orders.
return (-len(var), term)
def concat(term: str) -> str:
if term == '1':
return str(self.terms[term])
return str(self.terms[term]) + '*' + term
terms = list(self.terms.keys())
terms.sort(key=cmp)
return [concat(term) for term in terms]
def _merge(self, a: str, b: str) -> str:
if a == '1':
return b
if b == '1':
return a
res = []
A = a.split('*')
B = b.split('*')
i = 0 # A's index
j = 0 # B's index
while i < len(A) and j < len(B):
if A[i] < B[j]:
res.append(A[i])
i += 1
else:
res.append(B[j])
j += 1
return '*'.join(res + A[i:] + B[j:])
class Solution:
def basicCalculatorIV(
self,
expression: str,
evalvars: list[str],
evalints: list[int],
) -> list[str]:
tokens = list(self._getTokens(expression))
evalMap = {a: b for a, b in zip(evalvars, evalints)}
for i, token in enumerate(tokens):
if token in evalMap:
tokens[i] = str(evalMap[token])
postfix = self._infixToPostfix(tokens)
return self._evaluate(postfix).toList()
def _getTokens(self, s: str) -> Iterator[str]:
i = 0
for j, c in enumerate(s):
if c == ' ':
if i < j:
yield s[i:j]
i = j + 1
elif c in '()+-*':
if i < j:
yield s[i:j]
yield c
i = j + 1
if i < len(s):
yield s[i:]
def _infixToPostfix(self, tokens: list[str]) -> list[str]:
postfix = []
ops = []
def precedes(prevOp: str, currOp: str) -> bool:
if prevOp == '(':
return False
return prevOp == '*' or currOp in '+-'
for token in tokens:
if token == '(':
ops.append(token)
elif token == ')':
while ops[-1] != '(':
postfix.append(ops.pop())
ops.pop()
elif token in '+-*': # isOperator(token)
while ops and precedes(ops[-1], token):
postfix.append(ops.pop())
ops.append(token)
else: # isOperand(token)
postfix.append(token)
return postfix + ops[::-1]
def _evaluate(self, postfix: list[str]) -> Poly:
polys: list[Poly] = []
for token in postfix:
if token in '+-*':
b = polys.pop()
a = polys.pop()
if token == '+':
polys.append(a + b)
elif token == '-':
polys.append(a - b)
else: # token == '*'
polys.append(a * b)
elif token.lstrip('-').isnumeric():
polys.append(Poly("1", int(token)))
else:
polys.append(Poly(token, 1))
return polys[0]
// Accepted solution for LeetCode #770: Basic Calculator IV
struct Solution;
use std::cmp::Reverse;
use std::collections::BTreeMap;
use std::collections::HashMap;
use std::iter::Peekable;
use std::ops::Add;
use std::ops::Mul;
use std::ops::Neg;
use std::ops::Sub;
use std::slice::Iter;
use std::str::Chars;
#[derive(Eq, PartialEq, Debug, Clone)]
struct Term {
co: i32,
va: Vec<String>,
}
impl Term {
fn new(co: i32, va: Vec<String>) -> Self {
Term { co, va }
}
}
impl Neg for Term {
type Output = Term;
fn neg(self) -> Self::Output {
Term::new(-self.co, self.va)
}
}
impl Add for Term {
type Output = Term;
fn add(self, rhs: Term) -> Self::Output {
if self.va != rhs.va {
panic!();
}
Term::new(self.co + rhs.co, self.va)
}
}
impl Sub for Term {
type Output = Term;
fn sub(self, rhs: Term) -> Self::Output {
if self.va != rhs.va {
panic!();
}
Term::new(self.co - rhs.co, self.va)
}
}
impl Mul for Term {
type Output = Term;
fn mul(self, rhs: Term) -> Self::Output {
let co = self.co * rhs.co;
let mut va = vec![];
let mut left_va = self.va;
let mut right_va = rhs.va;
va.append(&mut left_va);
va.append(&mut right_va);
va.sort_unstable();
Term::new(co, va)
}
}
struct Expr {
terms: Vec<Term>,
}
impl Expr {
fn new(terms: Vec<Term>) -> Self {
Expr { terms }
}
}
impl Add for Expr {
type Output = Expr;
fn add(self, rhs: Expr) -> Self::Output {
let mut terms = vec![];
let mut left_terms = self.terms;
let mut right_terms = rhs.terms;
terms.append(&mut left_terms);
terms.append(&mut right_terms);
Expr::new(terms)
}
}
impl Sub for Expr {
type Output = Expr;
fn sub(self, rhs: Expr) -> Self::Output {
let mut terms = vec![];
let mut left_terms = self.terms;
let mut right_terms = rhs.terms.into_iter().map(|t| -t).collect();
terms.append(&mut left_terms);
terms.append(&mut right_terms);
Expr::new(terms)
}
}
impl Mul for Expr {
type Output = Expr;
fn mul(self, rhs: Expr) -> Self::Output {
let mut terms = vec![];
let left_terms = self.terms;
let right_terms = rhs.terms;
for i in 0..left_terms.len() {
for j in 0..right_terms.len() {
terms.push(left_terms[i].clone() * right_terms[j].clone());
}
}
Expr::new(terms)
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
enum Tok {
Num(i32),
Op(char),
Var(String),
}
impl Tok {
fn eval(self, lhs: Expr, rhs: Expr) -> Option<Expr> {
match self {
Op('+') => Some(lhs + rhs),
Op('-') => Some(lhs - rhs),
Op('*') => Some(lhs * rhs),
_ => None,
}
}
}
use Tok::*;
impl Solution {
fn basic_calculator_iv(
expression: String,
evalvars: Vec<String>,
evalints: Vec<i32>,
) -> Vec<String> {
let mut mapping: HashMap<String, i32> = HashMap::new();
let n = evalints.len();
for i in 0..n {
mapping.insert(evalvars[i].to_string(), evalints[i]);
}
let mut it = expression.chars().peekable();
let tokens = Self::parse_tokens(&mut it, mapping);
let mut it = tokens.iter().peekable();
let expr = Self::parse_expr(&mut it).unwrap();
let mut terms = expr.terms;
terms.sort_by_key(|t| {
let mut va = t.va.to_vec();
va.sort_unstable();
(Reverse(t.va.len()), va)
});
let mut groups: BTreeMap<(Reverse<usize>, Vec<String>), i32> = BTreeMap::new();
for term in terms {
*groups
.entry((Reverse(term.va.len()), term.va.to_vec()))
.or_default() += term.co;
}
let mut res = vec![];
for ((_, va), co) in groups {
if co == 0 {
continue;
}
let mut s = co.to_string();
if !va.is_empty() {
for x in va {
s.push('*');
s.push_str(&x);
}
}
res.push(s);
}
res
}
fn parse_tokens(it: &mut Peekable<Chars>, mapping: HashMap<String, i32>) -> Vec<Tok> {
let mut res: Vec<Tok> = vec![];
while let Some(c) = it.next() {
match c {
'0'..='9' => {
let mut x: i32 = (c as u8 - b'0') as i32;
while let Some(c) = it.peek() {
if c.is_numeric() {
x *= 10;
x += (it.next().unwrap() as u8 - b'0') as i32;
} else {
break;
}
}
res.push(Tok::Num(x));
}
'a'..='z' => {
let mut s = "".to_string();
s.push(c);
while let Some(c) = it.peek() {
if c.is_alphabetic() {
s.push(it.next().unwrap());
} else {
break;
}
}
if let Some(&x) = mapping.get(&s) {
res.push(Tok::Num(x));
} else {
res.push(Tok::Var(s));
}
}
'+' | '-' | '*' | '/' | '(' | ')' => {
res.push(Tok::Op(c));
}
_ => {}
}
}
res
}
fn parse_expr(it: &mut Peekable<Iter<Tok>>) -> Option<Expr> {
let mut lhs = Self::parse_factor(it)?;
while let Some(&tok) = it.peek() {
if let Op('+') | Op('-') = tok {
let op = it.next().unwrap().clone();
let rhs = Self::parse_factor(it)?;
lhs = op.eval(lhs, rhs)?;
} else {
break;
}
}
Some(lhs)
}
fn parse_factor(it: &mut Peekable<Iter<Tok>>) -> Option<Expr> {
let mut lhs = Self::parse_terminal(it)?;
while let Some(&tok) = it.peek() {
if let Op('*') | Op('/') = tok {
let op = it.next().unwrap().clone();
let rhs = Self::parse_terminal(it)?;
lhs = op.eval(lhs, rhs)?;
} else {
break;
}
}
Some(lhs)
}
fn parse_terminal(it: &mut Peekable<Iter<Tok>>) -> Option<Expr> {
if let Some(Op('(')) = it.peek() {
it.next();
let expr = Self::parse_expr(it);
it.next();
expr
} else {
Self::parse_var(it)
}
}
fn parse_var(it: &mut Peekable<Iter<Tok>>) -> Option<Expr> {
match it.next() {
Some(&Tok::Num(x)) => Some(Expr::new(vec![Term::new(x, vec![])])),
Some(Tok::Var(s)) => Some(Expr::new(vec![Term::new(1, vec![s.to_string()])])),
_ => None,
}
}
}
#[test]
fn test() {
let expression = "e + 8 - a + 5".to_string();
let evalvars = vec_string!["e"];
let evalints = vec![1];
let res = vec_string!["-1*a", "14"];
assert_eq!(
Solution::basic_calculator_iv(expression, evalvars, evalints),
res
);
let expression = "e - 8 + temperature - pressure".to_string();
let evalvars = vec_string!["e", "temperature"];
let evalints = vec![1, 12];
let res = vec_string!["-1*pressure", "5"];
assert_eq!(
Solution::basic_calculator_iv(expression, evalvars, evalints),
res
);
let expression = "(e + 8) * (e - 8)".to_string();
let evalvars = vec_string![];
let evalints = vec![];
let res = vec_string!["1*e*e", "-64"];
assert_eq!(
Solution::basic_calculator_iv(expression, evalvars, evalints),
res
);
let expression = "7 - 7".to_string();
let evalvars = vec_string![];
let evalints = vec![];
let res = vec_string![];
assert_eq!(
Solution::basic_calculator_iv(expression, evalvars, evalints),
res
);
let expression = "a * b * c + b * a * c * 4".to_string();
let evalvars = vec_string![];
let evalints = vec![];
let res = vec_string!["5*a*b*c"];
assert_eq!(
Solution::basic_calculator_iv(expression, evalvars, evalints),
res
);
let expression = "((a - b) * (b - c) + (c - a)) * ((a - b) + (b - c) * (c - a))".to_string();
let evalvars = vec_string![];
let evalints = vec![];
let res = vec_string![
"-1*a*a*b*b",
"2*a*a*b*c",
"-1*a*a*c*c",
"1*a*b*b*b",
"-1*a*b*b*c",
"-1*a*b*c*c",
"1*a*c*c*c",
"-1*b*b*b*c",
"2*b*b*c*c",
"-1*b*c*c*c",
"2*a*a*b",
"-2*a*a*c",
"-2*a*b*b",
"2*a*c*c",
"1*b*b*b",
"-1*b*b*c",
"1*b*c*c",
"-1*c*c*c",
"-1*a*a",
"1*a*b",
"1*a*c",
"-1*b*c"
];
assert_eq!(
Solution::basic_calculator_iv(expression, evalvars, evalints),
res
);
}
// Accepted solution for LeetCode #770: Basic Calculator IV
// Auto-generated TypeScript example from java.
function exampleSolution(): void {
}
// Reference (java):
// // Accepted solution for LeetCode #770: Basic Calculator IV
// class Poly {
// public Poly add(Poly o) {
// for (final String term : o.terms.keySet())
// terms.merge(term, o.terms.get(term), Integer::sum);
// return this;
// }
//
// public Poly minus(Poly o) {
// for (final String term : o.terms.keySet())
// terms.merge(term, -o.terms.get(term), Integer::sum);
// return this;
// }
//
// public Poly mult(Poly o) {
// Poly res = new Poly();
// for (final String a : terms.keySet())
// for (final String b : o.terms.keySet())
// res.terms.merge(merge(a, b), terms.get(a) * o.terms.get(b), Integer::sum);
// return res;
// }
//
// // @Override
// // Public String toString() {
// // StringBuilder sb = new StringBuilder();
// // sb.append("{");
// // for (final String term : terms.keySet())
// // sb.append(term).append(": ").append(terms.get(term)).append(", ");
// // sb.append("}");
// // return sb.toString();
// // }
//
// public List<String> toList() {
// List<String> res = new ArrayList<>();
// List<String> keys = new ArrayList<>(terms.keySet());
// Collections.sort(keys, new Comparator<String>() {
// @Override
// public int compare(final String a, final String b) {
// // the minimum degree is the last
// if (a.equals("1"))
// return 1;
// if (b.equals("1"))
// return -1;
// String[] as = a.split("\\*");
// String[] bs = b.split("\\*");
// // the maximum degree is the first
// // Break ties by their lexicographic orders.
// return as.length == bs.length ? a.compareTo(b) : bs.length - as.length;
// }
// });
// for (final String key : keys)
// if (terms.get(key) != 0)
// res.add(concat(key));
// return res;
// }
//
// public Poly() {}
// public Poly(final String term, int coef) {
// terms.put(term, coef);
// }
//
// private Map<String, Integer> terms = new HashMap<>();
//
// // e.g. merge("a*b", "a*c") -> "a*a*b*c"
// private static String merge(final String a, final String b) {
// if (a.equals("1"))
// return b;
// if (b.equals("1"))
// return a;
// StringBuilder sb = new StringBuilder();
// String[] A = a.split("\\*");
// String[] B = b.split("\\*");
// int i = 0; // A's index
// int j = 0; // B's index
// while (i < A.length && j < B.length)
// if (A[i].compareTo(B[j]) < 0)
// sb.append("*").append(A[i++]);
// else
// sb.append("*").append(B[j++]);
// while (i < A.length)
// sb.append("*").append(A[i++]);
// while (j < B.length)
// sb.append("*").append(B[j++]);
// return sb.substring(1).toString();
// }
//
// private String concat(final String term) {
// if (term.equals("1"))
// return String.valueOf(terms.get(term));
// return new StringBuilder().append(terms.get(term)).append('*').append(term).toString();
// }
// }
//
// class Solution {
// public List<String> basicCalculatorIV(String expression, String[] evalvars, int[] evalints) {
// List<String> tokens = getTokens(expression);
// Map<String, Integer> evalMap = new HashMap<>();
//
// for (int i = 0; i < evalvars.length; ++i)
// evalMap.put(evalvars[i], evalints[i]);
//
// for (int i = 0; i < tokens.size(); ++i)
// if (evalMap.containsKey(tokens.get(i)))
// tokens.set(i, String.valueOf(evalMap.get(tokens.get(i))));
//
// List<String> postfix = infixToPostfix(tokens);
// return evaluate(postfix).toList();
// }
//
// private List<String> getTokens(final String s) {
// List<String> tokens = new ArrayList<>();
// int i = 0;
// for (int j = 0; j < s.length(); ++j)
// if (s.charAt(j) == ' ') {
// if (i < j)
// tokens.add(s.substring(i, j));
// i = j + 1;
// } else if ("()+-*".contains(s.substring(j, j + 1))) {
// if (i < j)
// tokens.add(s.substring(i, j));
// tokens.add(s.substring(j, j + 1));
// i = j + 1;
// }
// if (i < s.length())
// tokens.add(s.substring(i));
// return tokens;
// }
//
// private boolean isOperator(final String token) {
// return token.equals("+") || token.equals("-") || token.equals("*");
// }
//
// private boolean precedes(final String prevOp, final String currOp) {
// if (prevOp.equals("("))
// return false;
// return prevOp.equals("*") || currOp.equals("+") || currOp.equals("-");
// }
//
// private List<String> infixToPostfix(List<String> tokens) {
// List<String> postfix = new ArrayList<>();
// Deque<String> ops = new ArrayDeque<>();
//
// for (final String token : tokens)
// if (token.equals("(")) {
// ops.push(token);
// } else if (token.equals(")")) {
// while (!ops.peek().equals("("))
// postfix.add(ops.pop());
// ops.pop();
// } else if (isOperator(token)) {
// while (!ops.isEmpty() && precedes(ops.peek(), token))
// postfix.add(ops.pop());
// ops.push(token);
// } else { // isOperand(token)
// postfix.add(token);
// }
//
// while (!ops.isEmpty())
// postfix.add(ops.pop());
//
// return postfix;
// }
//
// private Poly evaluate(List<String> postfix) {
// LinkedList<Poly> polys = new LinkedList<>();
// for (final String token : postfix)
// if (isOperator(token)) {
// final Poly b = polys.removeLast();
// final Poly a = polys.removeLast();
// if (token.equals("+"))
// polys.add(a.add(b));
// else if (token.equals("-"))
// polys.add(a.minus(b));
// else // token == "*"
// polys.add(a.mult(b));
// } else if (token.charAt(0) == '-' || token.chars().allMatch(c -> Character.isDigit(c))) {
// polys.add(new Poly("1", Integer.parseInt(token)));
// } else {
// polys.add(new Poly(token, 1));
// }
// return polys.getFirst();
// }
// }
Use this to step through a reusable interview workflow for this problem.
For each element, scan left (or right) to find the next greater/smaller element. The inner scan can visit up to n elements per outer iteration, giving O(n²) total comparisons. No extra space needed beyond loop variables.
Each element is pushed onto the stack at most once and popped at most once, giving 2n total operations = O(n). The stack itself holds at most n elements in the worst case. The key insight: amortized O(1) per element despite the inner while-loop.
Review these before coding to avoid predictable interview regressions.
Wrong move: Zero-count keys stay in map and break distinct/count constraints.
Usually fails on: Window/map size checks are consistently off by one.
Fix: Delete keys when count reaches zero.
Wrong move: Temporary multiplications exceed integer bounds.
Usually fails on: Large inputs wrap around unexpectedly.
Fix: Use wider types, modular arithmetic, or rearranged operations.
Wrong move: Pushing without popping stale elements invalidates next-greater/next-smaller logic.
Usually fails on: Indices point to blocked elements and outputs shift.
Fix: Pop while invariant is violated before pushing current element.