【发布时间】:2017-09-23 01:08:12
【问题描述】:
我尝试解决Hackerland Radio Transmitters programming challange。
总而言之,挑战如下:
Hackerland 是一个有 n 栋房屋的一维城市,其中每栋房屋 i 位于某个 xi em> 在 x 轴上。市长想在城市房屋的屋顶上安装无线电发射器。每个发射器都有一个范围,k,这意味着它可以将信号传输到距离≤k 个单位的所有房屋。
给定 Hackerland 的地图和 k 的值,你能找到覆盖每个房子所需的最少发射器数量吗?
我的实现如下:
package biz.tugay;
import java.util.*;
public class HackerlandRadioTransmitters {
public static int minNumOfTransmitters(int[] houseLocations, int transmitterRange) {
// Sort and remove duplicates..
houseLocations = uniqueHouseLocationsSorted(houseLocations);
int towerCount = 0;
for (int nextHouseNotCovered = 0; nextHouseNotCovered < houseLocations.length; ) {
final int towerLocation = HackerlandRadioTransmitters.findNextTowerIndex(houseLocations, nextHouseNotCovered, transmitterRange);
towerCount++;
nextHouseNotCovered = HackerlandRadioTransmitters.nextHouseNotCoveredIndex(houseLocations, towerLocation, transmitterRange);
if (nextHouseNotCovered == -1) {
break;
}
}
return towerCount;
}
public static int findNextTowerIndex(final int[] houseLocations, final int houseNotCoveredIndex, final int transmitterRange) {
final int houseLocationWeWantToCover = houseLocations[houseNotCoveredIndex];
final int farthestHouseLocationAllowed = houseLocationWeWantToCover + transmitterRange;
int towerIndex = houseNotCoveredIndex;
int loop = 0;
while (true) {
loop++;
if (towerIndex == houseLocations.length - 1) {
break;
}
if (farthestHouseLocationAllowed >= houseLocations[towerIndex + 1]) {
towerIndex++;
continue;
}
break;
}
System.out.println("findNextTowerIndex looped : " + loop);
return towerIndex;
}
public static int nextHouseNotCoveredIndex(final int[] houseLocations, final int towerIndex, final int transmitterRange) {
final int towerCoversUntil = houseLocations[towerIndex] + transmitterRange;
int notCoveredHouseIndex = towerIndex + 1;
int loop = 0;
while (notCoveredHouseIndex < houseLocations.length) {
loop++;
final int locationOfHouseBeingChecked = houseLocations[notCoveredHouseIndex];
if (locationOfHouseBeingChecked > towerCoversUntil) {
break; // Tower does not cover the house anymore, break the loop..
}
notCoveredHouseIndex++;
}
if (notCoveredHouseIndex == houseLocations.length) {
notCoveredHouseIndex = -1;
}
System.out.println("nextHouseNotCoveredIndex looped : " + loop);
return notCoveredHouseIndex;
}
public static int[] uniqueHouseLocationsSorted(final int[] houseLocations) {
Arrays.sort(houseLocations);
final HashSet<Integer> integers = new HashSet<>();
final int[] houseLocationsUnique = new int[houseLocations.length];
int innerCounter = 0;
for (int houseLocation : houseLocations) {
if (integers.contains(houseLocation)) {
continue;
}
houseLocationsUnique[innerCounter] = houseLocation;
integers.add(houseLocationsUnique[innerCounter]);
innerCounter++;
}
return Arrays.copyOf(houseLocationsUnique, innerCounter);
}
}
我很确定这个实现是正确的。但是请看函数中的细节:findNextTowerIndex和nextHouseNotCoveredIndex:他们一个一个地遍历数组!
我的一个测试如下:
static void test_01() throws FileNotFoundException {
final long start = System.currentTimeMillis();
final File file = new File("input.txt");
final Scanner scanner = new Scanner(file);
int[] houseLocations = new int[73382];
for (int counter = 0; counter < 73382; counter++) {
houseLocations[counter] = scanner.nextInt();
}
final int[] uniqueHouseLocationsSorted = HackerlandRadioTransmitters.uniqueHouseLocationsSorted(houseLocations);
final int minNumOfTransmitters = HackerlandRadioTransmitters.minNumOfTransmitters(uniqueHouseLocationsSorted, 73381);
assert minNumOfTransmitters == 1;
final long end = System.currentTimeMillis();
System.out.println("Took: " + (end - start) + " milliseconds..");
}
input.txt 可以从here 下载。 (这不是这个问题中最重要的细节,但仍然......)所以我们有一个 73382 房屋的数组,我特意设置了发射器范围,所以我的方法循环了很多:
这是在我的机器上测试的示例输出:
findNextTowerIndex looped : 38213
nextHouseNotCoveredIndex looped : 13785
Took: 359 milliseconds..
我也有这个测试,它不断言任何东西,只是保持时间:
static void test_02() throws FileNotFoundException {
final long start = System.currentTimeMillis();
for (int i = 0; i < 400; i ++) {
final File file = new File("input.txt");
final Scanner scanner = new Scanner(file);
int[] houseLocations = new int[73382];
for (int counter = 0; counter < 73382; counter++) {
houseLocations[counter] = scanner.nextInt();
}
final int[] uniqueHouseLocationsSorted = HackerlandRadioTransmitters.uniqueHouseLocationsSorted(houseLocations);
final int transmitterRange = ThreadLocalRandom.current().nextInt(1, 70000);
final int minNumOfTransmitters = HackerlandRadioTransmitters.minNumOfTransmitters(uniqueHouseLocationsSorted, transmitterRange);
}
final long end = System.currentTimeMillis();
System.out.println("Took: " + (end - start) + " milliseconds..");
}
我随机创建 400 个发射器范围,并运行程序 400 次。我将在我的机器中获得如下运行时间。
Took: 20149 milliseconds..
所以现在,我说,我为什么不使用二进制搜索而不是遍历数组,并将我的实现更改如下:
public static int findNextTowerIndex(final int[] houseLocations, final int houseNotCoveredIndex, final int transmitterRange) {
final int houseLocationWeWantToCover = houseLocations[houseNotCoveredIndex];
final int farthestHouseLocationAllowed = houseLocationWeWantToCover + transmitterRange;
int nextTowerIndex = Arrays.binarySearch(houseLocations, 0, houseLocations.length, farthestHouseLocationAllowed);
if (nextTowerIndex < 0) {
nextTowerIndex = -nextTowerIndex;
nextTowerIndex = nextTowerIndex -2;
}
return nextTowerIndex;
}
public static int nextHouseNotCoveredIndex(final int[] houseLocations, final int towerIndex, final int transmitterRange) {
final int towerCoversUntil = houseLocations[towerIndex] + transmitterRange;
int nextHouseNotCoveredIndex = Arrays.binarySearch(houseLocations, 0, houseLocations.length, towerCoversUntil);
if (-nextHouseNotCoveredIndex > houseLocations.length) {
return -1;
}
if (nextHouseNotCoveredIndex < 0) {
nextHouseNotCoveredIndex = - (nextHouseNotCoveredIndex + 1);
return nextHouseNotCoveredIndex;
}
return nextHouseNotCoveredIndex + 1;
}
我期待性能大幅提升,因为现在我最多会循环 log(N) 次,而不是 O(N).. 所以 test_01 输出:
Took: 297 milliseconds..
请记住,之前是 359 毫秒。对于 test_02:
Took: 18047 milliseconds..
所以我总是在 20 秒左右获得数组遍历实现的值,而对于二分搜索实现,我总是在 18 - 19 秒左右。
我期待使用 Arrays.binarySearch 获得更好的性能提升,但显然不是这样,这是为什么呢?我错过了什么?我是否需要一个超过 73382 的数组才能看到好处,还是无关紧要?
编辑#01
在@huck_cussler 发表评论后,我尝试将我拥有的数据集(使用随机数)翻倍和翻三倍,并尝试运行 test02(当然,将测试本身中的数组大小翻三倍......)。对于线性实现,时间是这样的:
Took: 18789 milliseconds..
Took: 34396 milliseconds..
Took: 53504 milliseconds..
对于二分搜索实现,我得到的值如下:
Took: 18644 milliseconds..
Took: 33831 milliseconds..
Took: 52886 milliseconds..
【问题讨论】:
-
以一种大小测试两个实现不会暴露不同的大哦行为。当你增加问题规模时会发生什么?这就是您应该看到线性解决方案所需的时间会增长得更快的地方。问题规模每增加一倍,线性算法所需的时间大约会增加一倍。
-
@huck_cussler 我尝试将我的输入加倍,但线性解决方案似乎并没有像你建议的那样增长得更快。当我将输入数据加倍时,我得到线性解决方案的 18789 - 34396 和 test2 的二进制搜索解决方案的 18644 - 33831 之类的值。它们似乎都像 O(N) 一样增加..
-
@huck_cussler 请看我的编辑..
-
我的第一个想法是将代码行移到启动计时器的位置。如果我正确理解了这个问题,
n是在您的测试代码中显示为73382的房屋数量。你同意吗?假设是这种情况,通过将计时器启动放在方法的开头,您将捕获运行n次的 for 循环。即使该操作相对较快,它仍然是线性的,并且会覆盖您的log(n)行为。如果将计时器开始移动到运行73382次的 for 循环之后会发生什么? ... -
... 这意味着您将不得不在外循环内部停止计时器,这使得计时更加复杂。您必须汇总 400 次迭代的运行时间,或者(我认为更好的选择)单独进行所有设置,然后只对您感兴趣的操作计时。所以,做所有你做的事情在
test_02的第 4-9 行其他地方,然后循环400次,只需执行解决问题的代码。
标签: java arrays binary-search