题解 | 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)
}

全部评论

相关推荐

点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务