现有一个有一个长度为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!");
}
}
}
通过多次验证,我们发现只要总油量大于总路程,我们始终能找到合适的起始点能够遍历完整个跑道,只有当总油量少于总路程的时候,才找不到合适的起始点。