【售货机买东西】

贩卖机支持10,50,100支付,一次出一瓶可乐,投钱和找零遵循使用大钱的原则,计算需投入硬币次数

时间复杂度一定不能超过10的8次方数量级,所以看到数量n的数量级非常大,便可猜测算法和可乐数量没有关系.面额的种类

把面值和张数封装成两个数组

a/x怎么向上取整--------(a+(x-1))/x

// 暴力尝试,为了验证正式方法而已
	public static int right(int m, int a, int b, int c, int x) {
		int[] qian = { 100, 50, 10 };
		int[] zhang = { c, b, a };
		int puts = 0;
		while (m != 0) {
			int cur = buy(qian, zhang, x);
			if (cur == -1) {
				return -1;
			}
			puts += cur;
			m--;
		}
		return puts;
	}

	public static int buy(int[] qian, int[] zhang, int rest) {
		int first = -1;
		for (int i = 0; i < 3; i++) {
			if (zhang[i] != 0) {
				first = i;
				break;
			}
		}
		if (first == -1) {
			return -1;
		}
		if (qian[first] >= rest) {
			zhang[first]--;
			giveRest(qian, zhang, first + 1, qian[first] - rest, 1);
			return 1;
		} else {
			zhang[first]--;
			int next = buy(qian, zhang, rest - qian[first]);
			if (next == -1) {
				return -1;
			}
			return 1 + next;
		}
	}

	// 正式的方法
	// 要买的可乐数量,m
	// 100元有a张
	// 50元有b张
	// 10元有c张
	// 可乐单价x
	public static int putTimes(int m, int a, int b, int c, int x) {
		//              0    1   2
		int[] qian = { 100, 50, 10 };
		int[] zhang = { c,  b,  a };
		// 总共需要多少次投币
		int puts = 0;
		// 之前面值的钱还剩下多少总钱数
		int preQianRest = 0;
		// 之前面值的钱还剩下多少总张数
		int preQianZhang = 0;
		for (int i = 0; i < 3 && m != 0; i++) {
			// 要用之前剩下的钱、当前面值的钱,共同买第一瓶可乐
			// 之前的面值剩下多少钱,是preQianRest
			// 之前的面值剩下多少张,是preQianZhang
			// 之所以之前的面值会剩下来,一定是剩下的钱,一直攒不出一瓶可乐的单价
			// 当前的面值付出一些钱+之前剩下的钱,此时有可能凑出一瓶可乐来
			// 那么当前面值参与搞定第一瓶可乐,需要掏出多少张呢?就是curQianFirstBuyZhang
			int curQianFirstBuyZhang = (x - preQianRest + qian[i] - 1) / qian[i];
			if (zhang[i] >= curQianFirstBuyZhang) { // 如果之前的钱和当前面值的钱,能凑出第一瓶可乐
				// 凑出来了一瓶可乐也可能存在找钱的情况,
				giveRest(qian, zhang, i + 1, (preQianRest + qian[i] * curQianFirstBuyZhang) - x, 1);
				puts += curQianFirstBuyZhang + preQianZhang;
				zhang[i] -= curQianFirstBuyZhang;
				m--;
			} else { // 如果之前的钱和当前面值的钱,不能凑出第一瓶可乐
				preQianRest += qian[i] * zhang[i];
				preQianZhang += zhang[i];
				continue;
			}
			// 凑出第一瓶可乐之后,当前的面值有可能能继续买更多的可乐
			// 以下过程就是后续的可乐怎么用当前面值的钱来买
			// 用当前面值的钱,买一瓶可乐需要几张
			int curQianBuyOneColaZhang = (x + qian[i] - 1) / qian[i];
			// 用当前面值的钱,一共可以搞定几瓶可乐
			int curQianBuyColas = Math.min(zhang[i] / curQianBuyOneColaZhang, m);
			// 用当前面值的钱,每搞定一瓶可乐,收货机会吐出多少零钱
			int oneTimeRest = qian[i] * curQianBuyOneColaZhang - x;
			// 每次买一瓶可乐,吐出的找零总钱数是oneTimeRest
			// 一共买的可乐数是curQianBuyColas,所以把零钱去提升后面几种面值的硬币数,
			// 就是giveRest的含义
			giveRest(qian, zhang, i + 1, oneTimeRest, curQianBuyColas);
			// 当前面值去搞定可乐这件事,一共投了几次币
			puts += curQianBuyOneColaZhang * curQianBuyColas;
			// 还剩下多少瓶可乐需要去搞定,继续用后面的面值搞定去吧
			m -= curQianBuyColas;
			// 当前面值可能剩下若干张,要参与到后续买可乐的过程中去,
			// 所以要更新preQianRest和preQianZhang
			zhang[i] -= curQianBuyOneColaZhang * curQianBuyColas;
			preQianRest = qian[i] * zhang[i];
			preQianZhang = zhang[i];
		}
		return m == 0 ? puts : -1;
	}

	public static void giveRest(int[] qian, int[] zhang, int i, int oneTimeRest, int times) {
		for (; i < 3; i++) {
			zhang[i] += (oneTimeRest / qian[i]) * times;
			oneTimeRest %= qian[i];
		}
	}

	public static void main(String[] args) {
		int testTime = 1000;
		int zhangMax = 10;
		int colaMax = 10;
		int priceMax = 20;
		System.out.println("如果错误会打印错误数据,否则就是正确");
		System.out.println("test begin");
		for (int i = 0; i < testTime; i++) {
			int m = (int) (Math.random() * colaMax);
			int a = (int) (Math.random() * zhangMax);
			int b = (int) (Math.random() * zhangMax);
			int c = (int) (Math.random() * zhangMax);
			int x = ((int) (Math.random() * priceMax) + 1) * 10;
			int ans1 = putTimes(m, a, b, c, x);
			int ans2 = right(m, a, b, c, x);
			if (ans1 != ans2) {
				System.out.println("int m = " + m + ";");
				System.out.println("int a = " + a + ";");
				System.out.println("int b = " + b + ";");
				System.out.println("int c = " + c + ";");
				System.out.println("int x = " + x + ";");
				break;
			}
		}
		System.out.println("test end");
	}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值