每周一道算法题002:四则运算

问题:

求位于1000~9999,满足如下条件的数: 各位数字间加入四则运算符,也可省略,使得按四则运算计算的结果为原数字的各位数逆序排列。

例如:351 → 3×51 = 153

思路:

遍历1000-9999所有的数字,切分并组合运算符,拼成四则运算的算式然后计算。
4位数中最大的数是9999,他可以被拆成999+9=1008,逆排后不可能等于原数,减法和除法不可能计算出比原数更大的数,所以只需要考虑乘法。

php提供了eval函数,可以很方便的计算,但是需要对数字进行处理,因为08开头的数字会被当作8进制,需要清洗一下。

解答:

php

<?php
$ops = array("*", "");
$opsLen = count($ops);
for ($i = 1000; $i < 9999; $i++) {
    $num = strval($i);
    for ($j = 0; $j < $opsLen; $j++) {
        for ($k = 0; $k < $opsLen; $k++) {
            for ($l = 0; $l < $opsLen; $l++) {
                $str = $num[3] . $ops[$j] . $num[2] . $ops[$k] . $num[1] . $ops[$l] . $num[0];
                $len = strlen($str);
                if ($len > 4) {
                    $str = formatNum($str, $len, $ops);
                    $result = eval(‘return ‘ . $str . ‘;‘);
                    if ($i == $result) {
                        echo $num . "=" . $str . "\n";
                    }
                }
            }
        }
    }
}

// 格式化数字,以08开头的数字会被php当成8进制处理,必须清洗
function formatNum($str, $len, $ops)
{
    $stack = array();
    $n = ‘‘;
    for ($i = 0; $i < $len; $i++) {
        if (!in_array($str[$i], $ops)) {
            $n .= $str[$i];
        } else {
            $stack[] = intval($n);
            $n = ‘‘;
            $stack[] = $str[$i];
        }

        // 没有操作符了,就把最后一段数字扔进栈中
        if ($i == $len - 1) {
            $stack[] = intval($n);
        }
    }
    return join($stack, ‘‘);
}

结果为:

1395=5*9*31

golang

golang没有提供eval方法,需要自己实现一个四则运算计算函数。并且需要先将中缀表达式先转为后缀表达式(逆波兰)才能方便程序处理。

参考文档:https://wuyin.io/2018/02/04/calculate-math-statement-by-go-stack/

要实现逆波兰,我们需要用到栈,之前的文章里已经实现了,可以直接拿来用
https://blog.51cto.com/ustb80/2419816

main.go

package main

import (
    "./stack"
    "fmt"
    "strconv"
    "strings"
    "unicode"
)

func main() {
    //s := "1+2*3+(4*5+6)*7"
    ////s = "22+24*(12-5)*45+16/4"

    //s := "899*9"
    //r := transCalculation(s)
    //fmt.Println(s, "->", r)
    //
    //result := calculate(r)
    //fmt.Println(result)

    findNum()
}

func findNum() {
    ops := []string{"*", ""}
    opsLen := len(ops)
    for i := 1000; i < 9999; i++ {
        num := strconv.Itoa(i)
        for j := 0; j < opsLen; j++ {
            for k := 0; k < opsLen; k++ {
                for l := 0; l < opsLen; l++ {
                    str := fmt.Sprintf("%s%s%s%s%s%s%s", string(num[3]), ops[j], string(num[2]), ops[k], string(num[1]), ops[l], string(num[0]))
                    expLen := len(str)
                    if expLen > 4 {
                        postExp := transCalculation(str)
                        result := calculate(postExp)
                        if i == result {
                            fmt.Println(str, "->", postExp, " -> ", result)
                        }
                    }
                }
            }
        }
    }
}

// 计算后缀表达式的值
// 遇到数字就压栈,遇到运算符就取出两个栈顶元素计算并再压栈
func calculate(exp string) int {

    slice := strings.Split(exp, " ")
    expLen := len(slice)
    s := stack.New()
    for i := 0; i < expLen; i++ {
        char := slice[i]

        if digit, err := strconv.Atoi(char); err == nil {
            s.Push(digit)
        } else {
            s1, _ := strconv.Atoi(fmt.Sprintf("%v", s.Pop()))
            s2, _ := strconv.Atoi(fmt.Sprintf("%v", s.Pop()))
            switch char {
            case "+":
                s.Push(strconv.Itoa(s1 + s2))
            case "-":
                s.Push(strconv.Itoa(s1 - s2))
            case "*":
                s.Push(strconv.Itoa(s1 * s2))
            case "/":
                if s2 != 0 {
                    s.Push(strconv.Itoa(s1 / s2))
                }
            }
        }
        //fmt.Println("peek = ",s.Peek())
    }
    result, _ := strconv.Atoi(fmt.Sprintf("%v", s.Peek()))
    return result
}

// 将中缀表达式转为后缀表达式(逆波兰)
func transCalculation(exp string) string {
    opStack := stack.New() // 开启一个运算符堆栈
    expLen := len(exp)     // 表达式长度
    result := ""           // 存放结果

    // 遍历表达式,
    // 遇到数字直接输出
    // 遇到运算符则判断:
    // 1.栈顶运算符优先级更低则入栈,更高或相等则直接输出
    // 2.栈为空、栈顶是 ( 直接入栈
    // 3.运算符是 ) 则将栈顶运算符全部弹出,直到遇见 (
    // 中缀表达式遍历完毕,运算符栈不为空则全部弹出,依次追加到输出
    for i := 0; i < expLen; i++ {
        char := string(exp[i])
        switch char {
        case "":
            continue

        // 数字直接追加至结果
        case "0", "1", "2", "3", "4", "5", "6", "7", "8", "9":
            // 处理连续数字
            j := i
            digit := ""
            for ; j < expLen && unicode.IsDigit(rune(exp[j])); j++ {
                digit += string(exp[j])
            }
            result += " " + digit
            i = j - 1 // 变更i的值,因为j++多加了一次,需要减掉

        // 左小括号直接入栈
        case "(":
            opStack.Push("(")

        // 右小括号处理
        case ")":
            for opStack.Len() > 0 {
                preNode := fmt.Sprintf("%v", opStack.Peek())

                // 判断栈顶是不是左括号,如果是,就出栈,结束
                if preNode == "(" {
                    opStack.Pop()
                    break
                }
                result += " " + preNode
                opStack.Pop()
            }
        default:
            for opStack.Len() > 0 {
                top := fmt.Sprintf("%v", opStack.Peek())

                // 如果当前运算符的优先级高于栈顶,则将当前的入栈
                // 否则栈顶出栈,当前入栈
                if isOpPrior(char, top) {
                    break
                }
                result += " " + top
                opStack.Pop()
            }
            opStack.Push(char)
        }
    }

    for opStack.Len() > 0 {
        result += fmt.Sprintf(" %v", opStack.Pop())
    }
    return result
}

// 判断运符符优先级
// 如果比栈顶的优先级高,返回true
// 如果是左小括号,返回true
func isOpPrior(op string, top string) bool {
    switch top {
    case "+", "-":
        if op == "*" || op == "/" {
            return true
        }
    case "(":
        return true
    }
    return false
}

结果:

5*9*31 ->  5 9 * 31 *  ->  1395

原文地址:https://blog.51cto.com/ustb80/2420288

时间: 2024-10-28 14:57:57

每周一道算法题002:四则运算的相关文章

每周一道算法题005:切木棒

问题: 假设要把长度为n厘米的木棒切分为1厘米长的小段,但是1根木棒只能由1人切分,当木棒被切分为3段后,可以同时由3个人分别切分木棒.求最多有m个人时,最少要切分几次. 譬如n=8,m=3时如下图所示,切分4次就可以了. 求当n=20,m=3时的最少切分次数.求当n=100,m=5时的最少切分次数. 思路: 这道题最难的不是算法,而是理解题意.木棒刚开始只有1根,题目规定"1根木棒只能由1人切分",此时由1人切分后,变成2根:2根木棒再做切分,也要满足"1根木棒只能由1人切

每周一道算法题003:翻牌

问题: 有100张写着数字1-100的牌,并按顺序排列.最开始所有牌都是背面朝上放置.某人从第2张牌开始,隔1张牌翻牌.然后第2, 4, 6, -,100张牌就会变成正面朝上.接下来,另一个人从第3张牌开始,隔2张牌翻牌(原本背面朝上的,翻转成正面朝上:原本正面朝上的,翻转成背面朝上).再接下来,又有一个人从第4张牌开始,隔3张牌翻牌.像这样,从第n张牌开始,每隔n-1张牌翻牌,直到没有可翻动的牌为止.求当所有牌不再变动时,所有背面朝上的牌的数字. 思路: 这道题思路有很多种: 思路1设i为轮次

每周一道算法题001:回文数

题目: 找出大于10的最小的2进制,8进制,10进制都是回文数的最小的数.回文数指的是正读和反读都是一样的数,例如:33,10001,123454321... 思路: 先转换进制,然后统一处理成字符串进行比较 解答: PHP function execute(){ $x = 11; while (1) { if ($x == strrev($x) && decbin($x) == strrev(decbin($x)) && decoct($x) == strrev(deco

每周一道算法题006:抽签组合

问题: 有如下3支队伍,每个队伍都有2名队员.team1:A,B;team2:C,D;team3:E,F; 现在每个队出1个人,组成一个队去探险,请列出所有的组队方式. 思路: 这就是一个组合的问题,每个队里挑一人,那么总共应该有222=8种组合方式.如果暴力求解,那就是三层循环嵌套.但如果问题扩展一下,变成10个队,每个队10人,就无法暴力求解了,至少代码是没有扩展性的. 有如下一种思路: 循环所有的队伍第一次取出A,B两名队员,存起来:第二次取出C,D两名队员,与前一轮存下的队员进行交叉组合

每周一道算法题:兑换零钱

问题: 已知可兑换的零钱种类有1元,5元,10元,20元4种,现在有100块钱要换成零钱且总数量少于15张,有几种换法?分别是什么? 思路: 已知有[1,5,10,20]这样的一个可选数据集S,现在要从中取出n个数,每个数的张数为a,使得a1xn1+a2xn2+...aixni = 100. 最大的面额是20,总共需要100/20=5张,这是最少的张数,所以循环的下限是5,上限题目已经限定了,是15. 也就是说,从S这个数据集中,取5-15个数,使得他们的和为100,数是可以重复的. 先考虑最简

每周一道算法题009:找二进制对称的日期

题目: 把年月日表示为YYYYMMDD这样的8位整数,然后把这个整数转换成二进制数并且逆序排列,再把得到的二进制数转换成十进制数,求与原日期一致的日期.求得的日期要在上一次东京奥运会(1964年10月10日)到下一次东京奥运会(预定举办日期为2020年7月24日)之间. 思路: 从起始时间开始逐天累加,对每一天进行进制转换并反转,然后比较,如果相同就输出,不同就继续,直至到达结束时间. 解答: php: function findDate($begin, $end) { $beginDate =

每周一道算法题010:扫地机器人路径统计

问题: 假设有一款不会反复清扫同一个地方的机器人,它只能前后左右移动.举个例子,如果第1次向后移动,那么连续移动3次时,就会有以下9种情况(图6).又因为第1次移动可以是前后左右4种情况,所以移动3次时全部路径有9×4=36种. 求这个机器人移动12次时,有多少种移动路径? 思路: 尝试用递归和非递归两种办法来解. 递归思路:从起点开始,在各方向移动1步,如果移动后的点不在当前的路径中,就加入到当前路径中,并进行下一次移动,当移到到指定的N步时,退出,并计数加1,视为找到一条路径. 非递归思路:

每周一道算法题011:最长公共子串

问题: 求以下几组单词的最长公共子串的长度1.fish和fosh2.fish和hish3.fish和vista 思路: 可以用表格法,横纵坐标分别是两个单词,如果字符相同,就用左上角的数字加1,最后取表格中的最大值. 解答: php: <?php // 找出两个单词的最长公共子串 function findLongestSubString($word1, $word2) { $len1 = strlen($word1); $len2 = strlen($word2); $cell = array

一天一道算法题---6.26---二分查找

感谢微信平台---一天一道算法题----每天多一点进步-- 好累啊  现在在用win7自带的输入法 打起来真麻烦 快点把这2天的搞完就重装了 还是直接来源于----〉 待字闺中 分析 给定一个数组A,其中有一个位置被称为Magic Index,含义是:如果i是Magic Index,则A[i] = i.假设A中的元素递增有序.且不重复,请给出方法,找到这个Magic Index.更进一步,当A中允许有重复的元素,该怎么办呢? 没有重复元素的情况 一些同学在遇到这个题目的时候,往往会觉得比较简单.