编程在生活中的小应用

编程在生活中的小应用


经常在一家快餐店吃饭,店里的消费方式是微信小程序扫码充值然后消费。

  • 可充值金额有: 30元、50元、100元
  • 饭菜金额有: 一荤一素 19元、一荤两素 21元、两荤一素25元。(当然还有其它的,但是我只在这三个里选一)

今天我打完饭菜准备付钱,发现余额只剩 7块。 这时候我脑袋里蹦出一个问题,要是我突然不想在这吃了,我该如何用最少的充值消费次数把钱用完。大概想了一下

应该是充 50元,然后吃三顿 一荤一素19元。 7 + 50 - 19 - 19 -19 == 0

那要是我剩下的是其它金额呢,或者说以后我换了一家店 充值和消费金额都不同呢。我该如何用一个通用模型来解决这类问题。

听着挺扯淡的! 但是这在数学上应该是一个线性规划问题,要想明白还不简单。

这里我想的是如何通过编程来快速解决。那最容易想到的应该就是 dfs,直接模拟各种情况,然后返回成功的情况,直接coding。

public class EatingProblem {

    public static void main(String[] args) {
        new EatingProblem().minOperation(new int[]{10, 30, 50}, new int[]{19, 21, 25}, 7);
    }

    /**
     * 充值金额数组
     */
    int[] rechargeArr;
    /**
     * 消费金额数组
     */
    int[] consumeArr;

    /**
     * 最少次操作
     *
     * @param rechargeArr 充值金额数组
     * @param consumeArr  消费金额数组
     * @param balance     余额
     */
    private void minOperation(int[] rechargeArr, int[] consumeArr, int balance) {
        this.rechargeArr = rechargeArr;
        this.consumeArr = consumeArr;
        dfs(balance, new StringBuilder());
    }

    /**
     * @param balance 余额
     * @param sb      记录操作步骤的字符串
     */
    private void dfs(int balance, StringBuilder sb) {
        if (balance < 0) {
            return;
        } else if (balance == 0) {
            String last = String.format("--消费: %d, ^^^^余额: 0", balance);
            System.out.println(sb + last);
            return;
        }

        //充值
        for (int cuReVal : rechargeArr) {
            balance += cuReVal;
            //当前操作步骤,用字符串记录
            String cuStr = String.format("--充值: %d, 余额: %d ", cuReVal, balance);
            sb.append(cuStr);
            dfs(balance, sb);
            sb.delete(sb.length() - cuStr.length(), sb.length());
            balance -= cuReVal;
        }

        //消费
        for (int cuConsVal : consumeArr) {
            balance -= cuConsVal;
            //当前操作步骤,用字符串记录
            String cuStr = String.format("--消费: %d, 余额: %d ", cuConsVal, balance);
            sb.append(cuStr);
            dfs(balance, sb);
            sb.delete(sb.length() - cuStr.length(), sb.length());
            balance += cuConsVal;
        }
    }
}   

于是乎吧,我就写了一个 dfs。虽然总感觉怪怪的,先跑一下吧!

E x c e p t i o n i n t h r e a d " m a i n " j a v a . l a n g . S t a c k O v e r f l o w E r r o r . . . \color{#FF3030}{Exception in thread "main" java.lang.StackOverflowError...} Exceptioninthread"main"java.lang.StackOverflowError...

直接爆栈了,因为除了余额(balance), 没有其它终止条件了,比如充值那就可以一直加一直加。

所以得加一个中止条件,结合我这题的目的,我准备加一个次数限制,规定多少次操作内消费完,大于这个次数直接中止。改进代码:

public class EatingProblem {

    public static void main(String[] args) {
        new EatingProblem().minOperation(new int[]{10, 30, 50}, new int[]{19, 21, 25}, 7, 5);
    }

    /**
     * 充值金额数组
     */
    int[] rechargeArr;
    /**
     * 消费金额数组
     */
    int[] consumeArr;

    /**
     * 限制操作次数
     */
    int limitTimes;

    /**
     * 最少次操作
     *
     * @param rechargeArr 充值金额数组
     * @param consumeArr  消费金额数组
     * @param balance     余额
     * @param limitTimes  限制操作次数
     */
    private void minOperation(int[] rechargeArr, int[] consumeArr, int balance, int limitTimes) {
        this.limitTimes = limitTimes;
        this.rechargeArr = rechargeArr;
        this.consumeArr = consumeArr;
        dfs(balance, new StringBuilder(), 0);
    }


    /**
     * @param balance 余额
     * @param sb      记录操作步骤的字符串
     * @param times   实际操作次数
     */
    private void dfs(int balance, StringBuilder sb, int times) {
        if (times >= limitTimes) {
            return;
        }
        if (balance < 0) {
            return;
        } else if (balance == 0) {
            String last = String.format("--消费: %d, ^^^^余额: 0", balance);
            System.out.println(sb + last);
            return;
        }

        //充值
        for (int cuReVal : rechargeArr) {
            balance += cuReVal;
            //当前操作步骤,用字符串记录
            String cuStr = String.format("--充值: %d, 余额: %d ", cuReVal, balance);
            sb.append(cuStr);
            dfs(balance, sb, times + 1);
            sb.delete(sb.length() - cuStr.length(), sb.length());
            balance -= cuReVal;
        }

        //消费
        for (int cuConsVal : consumeArr) {
            balance -= cuConsVal;
            //当前操作步骤,用字符串记录
            String cuStr = String.format("--消费: %d, 余额: %d ", cuConsVal, balance);
            sb.append(cuStr);
            dfs(balance, sb, times + 1);
            sb.delete(sb.length() - cuStr.length(), sb.length());
            balance += cuConsVal;
        }
    }
}

这次看着没问题了,我把限制次数从1一直设置到5,终于有了结果,也就是最短4次操作后能将余额化为0。运行结果是:

–充值: 50, 余额: 57 --消费: 19, 余额: 38 --消费: 19, 余额: 19 --消费: 19, 余额: 0 --消费: 0, ^^^^余额: 0

符合预期,和我上面人脑相出的答案一样。

如果把限制次数往上加,比如设置为6,看看结果:

--充值: 10, 余额: 17 --充值: 50, 余额: 67 --消费: 21, 余额: 46 --消费: 21, 余额: 25 --消费: 25, 余额: 0 --消费: 0, ^^^^余额: 0
--充值: 10, 余额: 17 --充值: 50, 余额: 67 --消费: 21, 余额: 46 --消费: 25, 余额: 21 --消费: 21, 余额: 0 --消费: 0, ^^^^余额: 0
--充值: 10, 余额: 17 --充值: 50, 余额: 67 --消费: 25, 余额: 42 --消费: 21, 余额: 21 --消费: 21, 余额: 0 --消费: 0, ^^^^余额: 0
--充值: 30, 余额: 37 --充值: 30, 余额: 67 --消费: 21, 余额: 46 --消费: 21, 余额: 25 --消费: 25, 余额: 0 --消费: 0, ^^^^余额: 0
--充值: 30, 余额: 37 --充值: 30, 余额: 67 --消费: 21, 余额: 46 --消费: 25, 余额: 21 --消费: 21, 余额: 0 --消费: 0, ^^^^余额: 0
--充值: 30, 余额: 37 --充值: 30, 余额: 67 --消费: 25, 余额: 42 --消费: 21, 余额: 21 --消费: 21, 余额: 0 --消费: 0, ^^^^余额: 0
--充值: 30, 余额: 37 --消费: 21, 余额: 16 --充值: 30, 余额: 46 --消费: 21, 余额: 25 --消费: 25, 余额: 0 --消费: 0, ^^^^余额: 0
--充值: 30, 余额: 37 --消费: 21, 余额: 16 --充值: 30, 余额: 46 --消费: 25, 余额: 21 --消费: 21, 余额: 0 --消费: 0, ^^^^余额: 0
--充值: 30, 余额: 37 --消费: 25, 余额: 12 --充值: 30, 余额: 42 --消费: 21, 余额: 21 --消费: 21, 余额: 0 --消费: 0, ^^^^余额: 0
--充值: 50, 余额: 57 --充值: 10, 余额: 67 --消费: 21, 余额: 46 --消费: 21, 余额: 25 --消费: 25, 余额: 0 --消费: 0, ^^^^余额: 0
--充值: 50, 余额: 57 --充值: 10, 余额: 67 --消费: 21, 余额: 46 --消费: 25, 余额: 21 --消费: 21, 余额: 0 --消费: 0, ^^^^余额: 0
--充值: 50, 余额: 57 --充值: 10, 余额: 67 --消费: 25, 余额: 42 --消费: 21, 余额: 21 --消费: 21, 余额: 0 --消费: 0, ^^^^余额: 0
--充值: 50, 余额: 57 --消费: 19, 余额: 38 --消费: 19, 余额: 19 --消费: 19, 余额: 0 --消费: 0, ^^^^余额: 0
--充值: 50, 余额: 57 --消费: 21, 余额: 36 --充值: 10, 余额: 46 --消费: 21, 余额: 25 --消费: 25, 余额: 0 --消费: 0, ^^^^余额: 0
--充值: 50, 余额: 57 --消费: 21, 余额: 36 --充值: 10, 余额: 46 --消费: 25, 余额: 21 --消费: 21, 余额: 0 --消费: 0, ^^^^余额: 0
--充值: 50, 余额: 57 --消费: 21, 余额: 36 --消费: 21, 余额: 15 --充值: 10, 余额: 25 --消费: 25, 余额: 0 --消费: 0, ^^^^余额: 0
--充值: 50, 余额: 57 --消费: 21, 余额: 36 --消费: 25, 余额: 11 --充值: 10, 余额: 21 --消费: 21, 余额: 0 --消费: 0, ^^^^余额: 0
--充值: 50, 余额: 57 --消费: 25, 余额: 32 --充值: 10, 余额: 42 --消费: 21, 余额: 21 --消费: 21, 余额: 0 --消费: 0, ^^^^余额: 0
--充值: 50, 余额: 57 --消费: 25, 余额: 32 --消费: 21, 余额: 11 --充值: 10, 余额: 21 --消费: 21, 余额: 0 --消费: 0, ^^^^余额: 0

那操作方式又多很多了,当然还可以将限制条件设置为最多充值不超过多少钱等。以及衍生到一些其它类似的线性规划问题。

你可能感兴趣的:(java,算法,深度优先,算法)