题解 | 24点游戏算法
24点游戏算法
https://www.nowcoder.com/practice/fbc417f314f745b1978fc751a54ac8cb
package main import ( "errors" "fmt" "regexp" "strconv" "strings" ) var operators = []string{"+", "-", "*", "/"} var digitRegex = regexp.MustCompile(`\d+`) func main() { var a, b, c, d int _, _ = fmt.Scan(&a, &b, &c, &d) permList := getPermutation([]int{a, b, c, d}) for _, perm := range permList { formulas := getAllFormulaStr(perm) for _, formula := range formulas { calcResult, err := calculateFormula(formula) if err != nil { continue } if calcResult == 24 || (calcResult > 23.999 && calcResult < 24.0001) { fmt.Println(true) return } } } fmt.Println(false) } func getPermutation(nums []int) [][]int { permList := make([][]int, 0) recorded := make(map[string]bool) permutationHelper(nums, 0, &permList, recorded) return permList } func permutationHelper(nums []int, from int, permList *[][]int, recorded map[string]bool) { if from == len(nums)-1 { numStr := parseToStr(nums) if _, ok := recorded[numStr]; ok { return } recorded[numStr] = true numsCopy := make([]int, len(nums)) copy(numsCopy, nums) *permList = append(*permList, numsCopy) return } for i := from; i < len(nums); i++ { nums[i], nums[from] = nums[from], nums[i] permutationHelper(nums, from+1, permList, recorded) nums[i], nums[from] = nums[from], nums[i] } } func parseToStr(nums []int) string { str := "" for i, num := range nums { str += strconv.Itoa(num) if i < len(nums) { str += "," } } return str } func getAllFormulaStr(nums []int) []string { results := make([]string, 0) //一个排列穷举出所有的运算符和括号,组成一个数组 //无括号公式 results = append(results, FormulaWithoutBracket(nums)...) //增加括号 FormulaWithBracket(&results) return results } func FormulaWithoutBracket(nums []int) []string { results := make([]string, 0) for _, operator := range operators { unFinished := make([]string, 0) formulaHelper(nums, 0, operator, &unFinished, &results) } return results } func formulaHelper(nums []int, numIndex int, operator string, unfinishedPart *[]string, finishedPart *[]string) { if numIndex >= len(nums) { *finishedPart = append(*finishedPart, strings.Join(*unfinishedPart, "")) return } offset := 1 firstStr := strconv.Itoa(nums[numIndex]) *unfinishedPart = append(*unfinishedPart, firstStr) if numIndex < len(nums)-1 { *unfinishedPart = append(*unfinishedPart, operator) offset++ } if numIndex < len(nums)-2 { for _, nextOp := range operators { formulaHelper(nums, numIndex+1, nextOp, unfinishedPart, finishedPart) } } else { formulaHelper(nums, numIndex+1, "", unfinishedPart, finishedPart) } *unfinishedPart = (*unfinishedPart)[0 : len(*unfinishedPart)-offset] } func FormulaWithBracket(srcFormulas *[]string) { srcLength := len(*srcFormulas) destFormulas := make([]string, 0) for i := 0; i < srcLength; i++ { brkFormulas := make([]string, 0) addBracketFormulas((*srcFormulas)[i], &brkFormulas) destFormulas = append(destFormulas, brkFormulas...) } *srcFormulas = destFormulas } // FormulaInfo 公式信息 type FormulaInfo struct { NumParts []int Symbols []byte headSymbol byte results []string left, right *FormulaInfo } func (f *FormulaInfo) SplitAndMerge() { if len(f.NumParts) == 1 { numStr := strconv.Itoa(f.NumParts[0]) f.results = append(f.results, numStr) return } for i := 1; i < len(f.NumParts); i++ { f.executeSplitAndMerge(i) } } func (f *FormulaInfo) executeSplitAndMerge(i int) { f.left = &FormulaInfo{ NumParts: f.NumParts[0:i], Symbols: f.Symbols[0 : i-1], results: make([]string, 0), } f.right = &FormulaInfo{ NumParts: f.NumParts[i:], Symbols: f.Symbols[i:], headSymbol: f.Symbols[i-1], results: make([]string, 0), } f.left.SplitAndMerge() f.right.SplitAndMerge() f.results = append(f.results, mergeTwoPartFormula(f.left.results, f.right.results, f.right.headSymbol)...) } func mergeTwoPartFormula(leftParts, rightParts []string, op byte) []string { if rightParts == nil { return leftParts } if leftParts == nil { return rightParts } resultParts := make([]string, 0) for _, left := range leftParts { for _, right := range rightParts { leftIsDigit, rightIsDigit := digitRegex.ReplaceAllString(left, "") == "", digitRegex.ReplaceAllString(right, "") == "" if leftIsDigit && rightIsDigit { resultParts = append(resultParts, left+string(op)+right) continue } if !leftIsDigit { resultParts = append(resultParts, "("+left+")"+string(op)+right) } if !rightIsDigit { resultParts = append(resultParts, left+string(op)+"("+right+")") } if !leftIsDigit && !rightIsDigit { resultParts = append(resultParts, "("+left+")"+string(op)+"("+right+")") } } } return resultParts } func addBracketFormulas(formulaStr string, results *[]string) { //先将字符串分为 数字切片和符号切片 numParts := make([]int, 0) symbols := make([]byte, 0) numStr := "" for _, fc := range formulaStr { switch byte(fc) { case '+', '-', '*', '/': if len(numStr) > 0 { num, _ := strconv.Atoi(numStr) numParts = append(numParts, num) numStr = "" } symbols = append(symbols, byte(fc)) default: numStr += string(fc) } } if len(numStr) > 0 { num, _ := strconv.Atoi(numStr) numParts = append(numParts, num) } formulaInfo := &FormulaInfo{ NumParts: numParts, Symbols: symbols, results: make([]string, 0), } formulaInfo.SplitAndMerge() *results = append(*results, formulaInfo.results...) } func calculateFormula(formula string) (float64, error) { //解析和计算式子 rootElement := ParseCalculateElement(formula) if rootElement == nil { return 0, errors.New("no root element") } return rootElement.Calculate() } func ParseCalculateElement(formula string) CalculateElement { stack := Stack[CalculateElement]{} //连续的右括号数量统计 continuousRightBrkCnt := 0 for _, c := range formula { var curElement CalculateElement bc := byte(c) if bc != ')' { if continuousRightBrkCnt&1 > 0 && stack.Size() > 1 { //连续的右括号数量为奇数,需要多出一次栈,以保证栈顶是正确的运算符元素 这样a op (b op c) op //...这个场景在HandleElementStackWhenMetOperator 就可以得到正确的处理 //但是stack的size==1时不能这么处理,因为只有(a op b) op ... 这种场景是这样的 stack.Pop() } continuousRightBrkCnt = 0 } switch bc { case '(': curElement = NewOperateCalculateElement(' ') if !stack.IsEmpty() { if lastElement, ok := stack.Peek().(*BinaryOperateCalculateElement); ok { lastElement.AddSubElement(curElement, lastElement.UnInitialed()) } } stack.Push(curElement) case '+', '-', '*', '/': HandleElementStackWhenMetOperator(&stack, bc) case ')': // 需要处理一下 就出栈吧 // 当 ((a op b) op c) op d if !stack.IsEmpty() { stack.Pop() } continuousRightBrkCnt++ default: //数字0~9 numVal, _ := strconv.ParseFloat(string(c), 64) curElement = NewSingleValueElementWithFloat(numVal) if !stack.IsEmpty() { if lastElement, ok := stack.Peek().(*BinaryOperateCalculateElement); ok { //如果栈里面是一个操作符运算元素,需要检查运算符是否完成初始化, //未完成运算符初始化就是符号左边的值,已完成就是右边的值 lastElement.AddSubElement(curElement, lastElement.UnInitialed()) stack.Push(curElement) } else { //说明栈里面就是一个数字值,isLeft为甚么都不重要了,但是该元素不需要再入栈 stack.Peek().AddSubElement(curElement, true) } } else { stack.Push(curElement) } } } if stack.IsEmpty() { return nil } for stack.Size() > 1 { stack.Pop() } return stack.Pop() } // HandleElementStackWhenMetOperator 有如下几种场景 // op 代表(+,-,*,/) // a op ... // // 这种场景下 栈中只有一个数字元素 将数字元素出栈 new一个操作符元素,设置上操作符再入栈 // // a op (b op... // (a op ... // // 这两种场景下 栈中有一个数字元素 和一个未设置操作符的操作符运算元素 将数字元素出栈,出栈后的栈顶设置操作符 // // a op b op ... // a op b op c op ... // 这个场景下 栈中有一个数字元素 和一个完整的操作符运算元素,将数字元素和操作符元素都出栈 // new一个操作符运算元素,操作符为当前操作符, // 左值为刚刚出栈的操作符运算元素 // (a op b) op ... // 这种场景下栈顶就是一个完整的操作符运算元素,直接将该元素出栈,然后创建一个运算符元素,将刚刚出栈的运算符元素设置为左值 // a op (b op c) op ... // 这种场景下栈顶是(b op c)这个操作符运算元素,需要找到a op (b op c)这个整体运算元素,并将其作为新创建的运算符元素的左值,这时需要给运算符元素一个parent指针 // (a op (b op c)) op ... // 这种场景和(a op b) op ...相同 // a op (b op c op ...) func HandleElementStackWhenMetOperator(stack *Stack[CalculateElement], operator byte) { var leftElement CalculateElement leftElement = stack.Pop() if opElement, ok := leftElement.(*BinaryOperateCalculateElement); ok { if opElement.UnInitialed() { // a op (b op... // (a op ... opElement.FinishInitial(operator) stack.Push(opElement) } else { // (a op b) op ... //a op (b op c) op ... curElement := NewOperateCalculateElement(operator) curElement.AddSubElement(opElement, true) stack.Push(curElement) } return } if stack.IsEmpty() { //匹配 a op ... curElement := NewOperateCalculateElement(operator) curElement.AddSubElement(leftElement, true) stack.Push(curElement) } else { topElement, ok := stack.Peek().(*BinaryOperateCalculateElement) if ok { if topElement.UnInitialed() { //... op (a op ... topElement.FinishInitial(operator) } else if topElement.Accomplished() { // a op b op ... leftElement = topElement stack.Pop() curElement := NewOperateCalculateElement(operator) curElement.AddSubElement(leftElement, true) stack.Push(curElement) } } } } // CalculateElement 运算单元接口 type CalculateElement interface { AddSubElement(subElement CalculateElement, isLeft bool) Calculate() (float64, error) } // BinaryOperateCalculateElement 二元运算符计算单元 type BinaryOperateCalculateElement struct { operator byte leftAndRight [2]CalculateElement parent *BinaryOperateCalculateElement } func NewOperateCalculateElement(operator byte) *BinaryOperateCalculateElement { return &BinaryOperateCalculateElement{ operator: operator, } } func (b *BinaryOperateCalculateElement) UnInitialed() bool { return b.operator == ' ' } func (b *BinaryOperateCalculateElement) FinishInitial(operator byte) { b.operator = operator } func (b *BinaryOperateCalculateElement) AddSubElement(subElement CalculateElement, isLeft bool) { var parentOfSub, subOperateElement *BinaryOperateCalculateElement var ok bool subOperateElement, ok = subElement.(*BinaryOperateCalculateElement) if ok { parentOfSub = subOperateElement.parent } if isLeft { if parentOfSub != nil { parentOfSub.AddSubElement(b, false) } b.leftAndRight[0] = subElement } else { b.leftAndRight[1] = subElement } if subOperateElement != nil { subOperateElement.parent = b } } func (b *BinaryOperateCalculateElement) Accomplished() bool { return !b.UnInitialed() && b.leftAndRight[0] != nil && b.leftAndRight[1] != nil } func (b *BinaryOperateCalculateElement) Calculate() (float64, error) { if b.leftAndRight[0] == nil || b.leftAndRight[1] == nil { return 0, errors.New("uncompleted calculate element") } leftResult, err := b.leftAndRight[0].Calculate() if err != nil { return 0, err } rightResult, err := b.leftAndRight[1].Calculate() if err != nil { return 0, err } switch b.operator { case '+': return leftResult + rightResult, nil case '-': return leftResult - rightResult, nil case '*': return leftResult * rightResult, nil case '/': if rightResult == 0 { return 0, errors.New("divide 0") } return leftResult / rightResult, nil } return 0, errors.New("invalid operator") } type SingleValueCalculateElement struct { value float64 } func NewSingleValueElementWithFloat(val float64) *SingleValueCalculateElement { return &SingleValueCalculateElement{value: val} } func (s *SingleValueCalculateElement) AddSubElement(c CalculateElement, _ bool) { if valueEle, ok := c.(*SingleValueCalculateElement); ok { s.value = s.value*10 + valueEle.value } } func (s *SingleValueCalculateElement) Calculate() (float64, error) { return s.value, nil } type Stack[T any] struct { elements []T top int } func (s *Stack[T]) IsEmpty() bool { return len(s.elements) == 0 } func (s *Stack[T]) Push(t T) { if s.elements == nil { s.elements = make([]T, 0) } s.elements = append(s.elements, t) s.top = len(s.elements) } func (s *Stack[T]) Peek() T { if s.IsEmpty() { panic(errors.New("couldn't peek in empty stack")) } return s.elements[s.top-1] } func (s *Stack[T]) Pop() T { if s.IsEmpty() { panic(errors.New("couldn't pop in empty stack")) } topElement := s.elements[s.top-1] s.elements = s.elements[:s.top-1] s.top-- return topElement } func (s *Stack[T]) Size() int { return len(s.elements) }