每日leetcode——224.基本計算器

Ethan發表於2022-03-09

題目

給你一個字串表示式 s ,請你實現一個基本計算器來計算並返回它的值。

輸入:s = "1 + 1"
輸出:2

輸入:s = " 2-1 + 2 "
輸出:3

輸入:s = "(1+(4+5+2)-3)+(6+8)"
輸出:23

s 由數字、'+'、'-'、'('、')'、和 ' ' 組成
s 表示一個有效的表示式
'+' 不能用作一元運算(例如, "+1" 和 "+(2 + 3)" 無效)
'-' 可以用作一元運算(即 "-1" 和 "-(2 + 3)" 是有效的)
輸入中不存在兩個連續的操作符
每個數字和執行的計算將適合於一個有符號的 32位 整數

思路

計算器類的題目,224、227、772,只需要一個思路:逆波蘭表示式+棧。
a+b,逆波蘭表示式:a,b,+

例如:s = '2+(35/4+7(2+3))/4'

定義兩個棧:
stack棧 用來存放數字以外的符號:運算子、括號
res棧 用來存放數字,以及pop出來的運算子,形成逆波蘭表示式

給出一個加減乘除全都有的思路:
思路規則:
數字,放入res
符號,放入stack
噹噹前符號的優先順序,<=stack棧頂的符號優先順序時,需要將stack棧頂的符號pop出去,放入res中,再將當前符號放入stack中
當遇到 )時,將stack中最後一個( 之後的所有運算子都pop出去,放入res中

構建逆波蘭表示式的過程:

2+(3*5/4+7*(2+3))/4

當前字元 2,壓入res
stack: []
res: ['2']

當前字元 +,壓入stack
stack: ['+']
res: ['2']

當前字元是 (,壓入stack
stack: ['+', '(']
res: ['2']

當前字元 3,壓入res
stack: ['+', '(']
res: ['2', '3']

當前字元 *,壓入stack
stack: ['+', '(', '*']
res: ['2', '3']

當前字元 5,壓入res
stack: ['+', '(', '*']
res: ['2', '3', '5']

當前字元 /,優先順序<=stack棧頂的*, 將stack棧頂的* pop到res中,再將當前的/壓入stack
stack: ['+', '(', '/']
res: ['2', '3', '5', '*']

當前字元 4,壓入res
stack: ['+', '(', '/']
res: ['2', '3', '5', '*', '4']

當前字元 +,優先順序<=stack棧頂的/, 將stack棧頂的/ pop到res中,再將當前的+壓入stack
stack: ['+', '(', '+']
res: ['2', '3', '5', '*', '4', '/']

當前字元 7,壓入res
stack: ['+', '(', '+']
res: ['2', '3', '5', '*', '4', '/', '7']

當前字元 *,壓入stack
stack: ['+', '(', '+', '*']
res: ['2', '3', '5', '*', '4', '/', '7']

當前字元是 (,壓入stack
stack: ['+', '(', '+', '*', '(']
res: ['2', '3', '5', '*', '4', '/', '7']

當前字元 2,壓入res
stack: ['+', '(', '+', '*', '(']
res: ['2', '3', '5', '*', '4', '/', '7', '2']

當前字元 +,壓入stack
stack: ['+', '(', '+', '*', '(', '+']
res: ['2', '3', '5', '*', '4', '/', '7', '2']

當前字元 3,壓入res
stack: ['+', '(', '+', '*', '(', '+']
res: ['2', '3', '5', '*', '4', '/', '7', '2', '3']

當前字元 ),將stack中最後一個 ( 之後的運算子都pop到res中
stack: ['+', '(', '+', '*']
res: ['2', '3', '5', '*', '4', '/', '7', '2', '3', '+']

當前字元 ),將stack中最後一個 ( 之後的運算子都pop到res中
stack: ['+']
res: ['2', '3', '5', '*', '4', '/', '7', '2', '3', '+', '*', '+']

當前字元 /,壓入stack
stack: ['+', '/']
res: ['2', '3', '5', '*', '4', '/', '7', '2', '3', '+', '*', '+']

當前字元 4,壓入res
stack: ['+', '/']
res: ['2', '3', '5', '*', '4', '/', '7', '2', '3', '+', '*', '+', '4']

stack中剩餘的操作符,後進先出的pop到res中
stack: []
res: ['2', '3', '5', '*', '4', '/', '7', '2', '3', '+', '*', '+', '4', '/', '+']

逆波蘭表示式計算過程:

字元: 2
入棧: [2]

字元: 3
入棧: [2, 3]

字元: 5
入棧: [2, 3, 5]

字元: *
取出棧頂後兩位進行相應的運算,結果入棧: [2, 15]

字元: 4
入棧: [2, 15, 4]

字元: /
取出棧頂後兩位進行相應的運算,結果入棧: [2, 3]

字元: 7
入棧: [2, 3, 7]

字元: 2
入棧: [2, 3, 7, 2]

字元: 3
入棧: [2, 3, 7, 2, 3]

字元: +
取出棧頂後兩位進行相應的運算,結果入棧: [2, 3, 7, 5]

字元: *
取出棧頂後兩位進行相應的運算,結果入棧: [2, 3, 35]

字元: +
取出棧頂後兩位進行相應的運算,結果入棧: [2, 38]

字元: 4
入棧: [2, 38, 4]

字元: /
取出棧頂後兩位進行相應的運算,結果入棧: [2, 9]

字元: +
取出棧頂後兩位進行相應的運算,結果入棧: [11]

python程式碼:

def calculate(s: str) -> int:
    if s.strip().isdigit():
            return int(s)
    n = len(s)
    stack = [] # 用來存運算子和括號的棧
    res = [] # 用來存數字的棧
    dic = { # 操作符優先順序字典
        '+':1,
        '-':1,
        '*':2,
        '/':2,
        '%':2,
        '^':3
    }
    for i in range(n):
        # 列印日誌
        underline = '\033[4m'
        end = '\033[0m'
        print(s[0:i]+underline + s[i] + end+s[i+1:])
        if s[i]==' ':
            continue
        # 1. 遇到數字,直接壓入res棧
        if s[i].isdigit():
            # ‘11’這種,前一位也是數字的,需要進行處理
            if i!=0 and s[i-1].isdigit():
                print('當前字元 %s,前一個字元也是數字 %s,特殊處理'%(s[i],s[i-1]))
                res.append(str(int(res.pop())*10+int(s[i])))
            else:
                print('當前字元 %s,壓入res'%(s[i]))
                res.append(s[i])
        else:
            # 2. 遇到右括號,將stack棧頂到最近的左括號之間的運算子,後進先出的pop到res中
            if s[i]==')':
                print('當前字元 %s,將stack中最後一個 ( 之後的運算子都pop到res中'%(s[i]))
                while True:
                    if stack[-1]=='(':
                        stack.pop()
                        break
                    else:
                        res.append(stack.pop())
            # 3. 遇到左括號,直接壓入stack
            elif s[i]=='(':
                print('當前字元是 (,壓入stack')
                stack.append('(')
            # 4. '-2+1' 和 '1-(-2+1)'這兩邊界情況,將-2當作0-2處理,在res中也壓入0
            elif s[i]=='-' and (i==0 or s[i-1]=='(') :
                print('當前字元是 -,前面是(或著沒有字元,當作0-處理,res中壓入0,stack壓入-')
                res.append('0')
                stack.append('-')
            else:
                print('當前字元 %s'%s[i],end=',')
                # 5. 遇到運算子,壓入stack棧
                # 如果當前運算子優先順序 <= stack棧頂運算子優先順序,
                # 則將stack棧定的運算子pop到res棧中,再壓入當前運算子到stack
                if len(stack) and stack[-1] in dic and dic[s[i]]<=dic[stack[-1]]:
                    print('優先順序<=stack棧頂的%s, 將stack棧頂的%s pop到res中,再將當前的%s壓入stack'%(stack[-1],stack[-1],s[i]))
                    res.append(stack.pop())
                    stack.append(s[i])
                else:
                    print('壓入stack')
                    # 否則直接壓入stack棧中
                    stack.append(s[i])
        print('stack:',stack)
        print('res:',res)
        print('\n')
                    
    # 最後將stack中剩餘的操作符,後進先出的pop到res中
    for _ in range(len(stack)):
        res.append(stack.pop())
#     print('stack中剩餘的操作符,後進先出的pop到res中')
#     print('stack:',stack)
#     print('res:',res)
    
    # res就是最終的逆波蘭表示式,開始計算res的值
    # 再新建一個空棧_res,遍歷逆波蘭表示式,將數字壓入到_res中
    # 當遍歷到運算子時,_res棧中最後兩個數字就是參與該運算的數字
    # 注意:要注意數字的順序,a,b,/ => a/b, a,b,^= a**b
    _res = []
    for ch in res:
#         print('字元:',ch)
        if ch.isdigit():
            _res.append(int(ch))
#             print('入棧:',_res)
        else:
            b = _res.pop()
            a = _res.pop()
            if ch=='+':
                _res.append(a+b)
            elif ch=='-':
                _res.append(a-b)
            elif ch=='*':
                _res.append(a*b)
            elif ch=='/':
                _res.append(a//b)
            elif ch=='%':
                _res.append(a%b)
            elif ch=='^':
                _res.append(a**b)
#             print('取出棧頂後兩位進行相應的運算,結果入棧:',_res)
#         print('\n')

    return _res

無print清爽版:

def calculate(s: str) -> int:
    if s.strip().isdigit():
            return int(s)
    n = len(s)
    stack = []
    res = []
    dic = {
        '+':1,
        '-':1,
        '*':2,
        '/':2,
        '%':2,
        '^':3
    }
    for i in range(n):
        if s[i]==' ':
            continue
        if s[i].isdigit():
            if i!=0 and s[i-1].isdigit():
                res.append(str(int(res.pop())*10+int(s[i])))
            else:
                res.append(s[i])
        else:
            if s[i]==')':
                while True:
                    if stack[-1]=='(':
                        stack.pop()
                        break
                    else:
                        res.append(stack.pop())
            elif s[i]=='(':
                stack.append('(')
            elif s[i]=='-' and (i==0 or s[i-1]=='(') :
                res.append('0')
                stack.append('-')
            else:
                if len(stack) and stack[-1] in dic and dic[s[i]]<=dic[stack[-1]]:
                    res.append(stack.pop())
                    stack.append(s[i])
                else:
                    stack.append(s[i])

    for _ in range(len(stack)):
        res.append(stack.pop())

    _res = []
    for ch in res:
        if ch.isdigit():
            _res.append(int(ch))
        else:
            b = _res.pop()
            a = _res.pop()
            if ch=='+':
                _res.append(a+b)
            elif ch=='-':
                _res.append(a-b)
            elif ch=='*':
                _res.append(a*b)
            elif ch=='/':
                _res.append(a//b)
            elif ch=='%':
                _res.append(a%b)
            elif ch=='^':
                _res.append(a**b)

    return _res

邊界情況總結:

  • '123456', ' 123456',s直接就是一串數字字串,程式碼開頭s.strip().isdigit()判斷一下直接返回即可。
  • '-2+1',1-(-2)',在字串開頭或者(後,接一個符號,按0-2處理
  • '1+111',多位數的判斷,res.append(str(int(res.pop())*10+int(s[i])))

相關文章