题目描述

✅ 1518. 换水问题

image-20260928230446618

image-20260928230446619

题意分析

最初有若干满水瓶,喝完后变成空瓶;每 numExchange 个空瓶可以换一瓶新水,新水喝完又会留下空瓶。求整个过程最多能喝多少瓶。

兑换比例始终不变,已有的水都可以喝完,够数的空瓶也都可以继续兑换。因此按轮模拟“喝完本轮水,再兑换下一轮水”,直到无法获得新水即可。

解法:按轮模拟喝水和兑换

核心思路

[!blue]

numBottles 表示本轮还没喝的满瓶数,empty 表示上一轮兑换后留下的空瓶,answer 表示累计喝水量。先把本轮满瓶全部喝掉,让 answer 和 empty 都增加 numBottles。

此时设共有 S 个空瓶,兑换门槛为 E,可以换出 S/E 瓶水,并留下 S%E 个空瓶。商成为下一轮的满瓶数,余数必须保留,之后还能与新喝完的空瓶合并。代码先计算商,再把 empty 覆盖为余数,保证两个计算都使用兑换前的完整数量。

只要还有满瓶,喝掉就能增加答案,而且不会减少后续兑换机会;够数的空瓶马上兑换也不会比延后兑换少得水,因为比例不变。循环一直执行这些可用操作,停止时既没有满瓶,空瓶数又小于门槛,已经没有办法继续喝水,因此得到最大值。

题目保证 E >= 2。每兑换并喝掉一瓶水,空瓶数量净减少 E-1,所以兑换不可能无限继续。初始瓶数不足兑换门槛时,也会在喝完第一轮后自然结束。

解题步骤

  1. 初始化 answer = 0、empty = 0,输入的 numBottles 作为第一轮满瓶数。
  2. 只要 numBottles > 0,就把这些水计入答案,并把喝完产生的空瓶加入 empty。
  3. 令 numBottles = empty/numExchange,得到下一轮满瓶数。
  4. 令 empty %= numExchange,保留兑换后剩余的空瓶。
  5. 没有满瓶后返回累计答案。

代码实现

class Solution {
    public int numWaterBottles(int numBottles, int numExchange) {
        int answer = 0;
        // empty 含上一轮兑换后剩下的找零,不能丢弃。
        int empty = 0;

        // 有满水就一定会喝,用满瓶数作为终止条件。
        while (numBottles > 0) {
            // 两行都基于本轮的满瓶数,必须在覆盖 numBottles 之前执行。
            answer += numBottles;
            // 本轮喝完的瓶子与以前余下的空瓶共同参与兑换。
            empty += numBottles;
            // 整数除法天然表达「只能换整瓶」。
            numBottles = empty / numExchange;
            // 先算兑换再取余,顺序反了会用被截断的空瓶数去换。
            empty %= numExchange;
        }

        return answer;
    }
}
func numWaterBottles(numBottles int, numExchange int) int {
    answer := 0
    // empty 含上一轮兑换后剩下的找零,不能丢弃。
    empty := 0

    // 有满水就一定会喝,用满瓶数作为终止条件。
    for numBottles > 0 {
        // 两行都基于本轮的满瓶数,必须在覆盖 numBottles 之前执行。
        answer += numBottles
        // 本轮喝完的瓶子与以前余下的空瓶共同参与兑换。
        empty += numBottles
        // 整数除法天然表达「只能换整瓶」。
        numBottles = empty / numExchange
        // 先算兑换再取余,顺序反了会用被截断的空瓶数去换。
        empty %= numExchange
    }

    return answer
}

复杂度分析

  • 时间复杂度:$O(\log(N+1))$ 上界,N 为初始瓶数。每轮开始时余下空瓶 R < E,若本轮满瓶为 F,下一轮满瓶至多为 ceil(F/E);当 F > 1 时,下一轮满瓶数-1 <= (F-1)/E,因此这一数量按比例缩小。进入只剩一瓶的阶段后,最多再执行两轮,避免把余数影响误说成每轮满瓶都严格减半。
  • 空间复杂度:$O(1)$。只维护满瓶数、空瓶余数和累计答案。

关键点总结

[!green]

  • 本轮先喝水,再用新产生的空瓶和此前余数一起兑换。
  • 商表示下一轮的新水,余数表示仍可留到下一轮的空瓶,两者都不能遗漏。
  • 满瓶为零时,空瓶已经是小于兑换门槛的余数,循环可以直接结束。

易错点总结

[!yellow]

  • 只计算第一次兑换,忽略新水喝完后还能继续产生空瓶。
  • 丢掉每轮余数,会漏掉它与新空瓶凑成的后续兑换。
  • 先把 empty 取余再计算商,得到的空瓶数已经不足一次兑换,会提前终止。
  • 在累加答案和空瓶之前覆盖 numBottles,会把本轮喝水量错误地换成下一轮的量。

相似题目

题目 难度 关联与区别
3100. 换水问题 II 中等 本题兑换比率固定,原题每次兑换后提高比率,需要逐步维护新的兑换条件。
转载与许可
作者
链接 https://hgnulb.github.io/blog/2026/94973074
许可 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!