油桶问题

现有一个有一个长度为n千米的环形跑道,跑道上随机摆放了k个油桶(油桶的油量不定),将跑道随机的分为k段,先需要找到一个合适的起点,能遍历完整条跑道,汽车耗油率为1个单位/千米。
我们可以将上述问题用如下的示意图表示,图中黄色圆圈代表随机分布在环形跑道上的k个油桶,车辆的初始油量为0,选择一个起点开始,则将油桶的油加入到车的总油量中。注意隐含条件,如果车出发时总油量不大于等于下一段要行驶的距离,说明无法遍历完整个跑道。
在这里插入图片描述

很自然的我们想到直接遍历方法,外循环选择起点,内循环负责检验起点是否合法,合法则返回。这种方法虽然可行,但是时间复杂度很高,最坏为O(n^2),我们能不能在一次遍历的情况下弄清楚这个问题呢?答案是肯定的。
在这里插入图片描述

如上图所示,研究某次出现不能继续行驶的情况,对于从i出发,到j就不能继续行驶的情况下,若中间存在一点m,能够使得从m出发可以越过j,那么必然有到达j点时总油量可以继续行驶,则反推,m点的初始油量一定大于从i开始行驶到m所剩下的油量,这显然矛盾,因为从m出发的话,m的初始油量一定是0。
所以我们得出结论:出发点到发生断点的位置不需要重复遍历,直接选择从断点的下一个位置开始即可。这样我们就能通过遍历一遍获得结果,时间复杂度为o(n),空间复杂度只需要记录每次遍历的总油量和下一段路程的差值,所以空间复杂度为o(1)。
以下,我们采用代码来模拟这个过程:

package Testforsummer;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Random;
import java.util.Set;

public class Bucket_Test {
	/**
	 * 将num 随机分成k份,模拟产生距离数组和油桶数组
	 * @param num
	 * @param k
	 * @return
	 */
	public int[] getRandomArr(int num, int k) {
		
		int[] res = new int[k];
		Random rand = new Random();
		Set<Integer> set = new HashSet<>();
		for(int i=0;i<k-1;i++) {
			res[i] = rand.nextInt(num);
			if(set.contains(res[i])) {
				i--;
				continue;
			}
			set.add(res[i]);
		}
		res[k-1] = num;
		Arrays.sort(res);
		int[] b = new int[k];
		b[0] = res[0];
		for(int i=1;i<k;i++) {
			b[i] = res[i] - res[i-1];
		}
		return b;
	}
	
	/**
	 * 找到起始索引i,使得从i开始,可以使得能跑完一圈
	 * buckets[i]表示第i桶油的容量, distance[i]表示在较上一次的距离
	 * 题目保证sum(buckets) > sum(distance),即总油量大于路程,油一个单位对应距离一个单位
	 * 不存在合适的索引,返回-1
	 * @param buckets
	 * @param distance
	 * @return
	 */
	public int getStart(int[] buckets, int[] distance) {
		if(buckets.length != distance.length) {
			System.out.println("Input invalid!");
			System.exit(1);
		}
		int sum = 0;
		int n = buckets.length;
		int start = 0;//起点
		int steps = 0;//从起点走的步数
		int eat = 0;
		while(start < n && steps <= n) {
			int i = start + steps + 1;//i是下一步索引
			if(i >= n) {
				i %= n;
			}
			int last = i - 1;
			if(last < 0) {
				last = n-1;
			}
			sum += buckets[last];
			eat = distance[last];
			if(sum < eat) {
				start += steps + 1;
				sum = 0;
				eat = 0;
				steps = 0;
//				System.out.println("断点索引  = " + (start - 1));
				continue;
			}
			steps++;
			sum -= eat;
		}
		if(start >= n) {
			return -1;
		}
		return start;
	}
	
	/**
	 * 测试函数,随机生成测试用例进行测试
	 * @param args
	 */
	public static void main(String[] args) {
		Bucket_Test test = new Bucket_Test();

		boolean flag = false;
		for(int i=0;i<10000;i++) {
			Random rand = new Random();
			int k = rand.nextInt(20) + 1;
			int dis = rand.nextInt(100) + 21;
			int buck = rand.nextInt(200) + 21;
			int[] distance = test.getRandomArr(dis, k);
			int[] buckets = test.getRandomArr(buck, k);
			int res = test.getStart(buckets, distance);
			if(res == -1 && buck > dis) {
				flag = true;
				System.out.println("distance = " + Arrays.toString(distance));
				System.out.println("buckets =  " + Arrays.toString(buckets));
				break;
			}
		}
		if(!flag) {
			System.out.println("Nice!");
		}
	}

}

通过多次验证,我们发现只要总油量大于总路程,我们始终能找到合适的起始点能够遍历完整个跑道,只有当总油量少于总路程的时候,才找不到合适的起始点。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值