摘要
假设你有一个多山的二维岛屿。由于天气多雨,岛上的所有山谷都被水完全填满,以至于再加水会导致湖泊溢出。如果溢流进入另一个湖泊,它也会溢出。最终,水将从岛上流出。考虑到岛屿的形状,你如何找出形成的湖泊?
详情
读取一系列数字(每个数字代表二维连通图中一个点的高度),计算并输出可能在岛屿山谷中形成的所有湖泊表面的高度。
例如,输入4 3 7 1 3 2 3 5 6 2 1 3 3 2 3
(如下图所示)的输出4 6 3 3
的时间复杂度最多为O(n log n)
。
算法会是什么样子?可以用线性复杂度来完成吗?
这是我到目前为止的代码:
import sys
def island_lakes():
lst=[]
lst1=[0]*3
x=[int(i) for i in sys.stdin.readline().split()]
lst.extend(x)
print(lst)
for x in range(len(lst)-1):
if x>0:
lst1[0]=lst[x-1]
lst1[1]=lst[x]
lst1[2]=lst[x+1]
if lst1[1]<lst1[0] and lst1[1]<lst1[2]:
if lst1[0]>lst1[2]:
print(lst1[2])
else:
print(lst1[0])
此代码找到所有部分湖泊,通过仅在最深的山谷中填充水来制作,但如下图所示,我可以有一个由其他湖泊组成的湖泊。
如果输入高于4 3 7 1 3 2 3 5 6 2 1 3 3 2 3
,则应打印4 6 3 3
但我的程序输出:
4 3 3 2 3
如何修复我的代码以允许它找到较大的山谷,例如那些山峰较小的山谷?
答案 0 :(得分:6)
为了找到可能形成的湖泊的最大海拔高度,您需要找到所有高峰或高于其周围点的点。稍微修改一下代码,我们可以在一次迭代中轻松获得峰值:
lst = [0] + lst + [0] # Add zeros to either side to avoid having to deal with boundary issues
peaks = []
for x in range(1, len(lst)-1):
if lst[x-1] =< lst[x] >= lst[x+1]: # "x =< y >= z" is the same as "x =< y and y >= z"
peaks.append(lst[x])
对于4 3 7 1 3 2 3 5 6 2 1 3 3 2 3
的示例,峰值为
[4, 7, 3, 6, 3, 3, 3]
现在,我们需要合并湖泊。我们找到哪些湖泊可以合并的方法是遍历峰值列表,跟踪到目前为止的最高峰值,并且对于每个峰值,我们删除任何先前的峰值,这些峰值低于它,到目前为止最高峰值。但是,这不需要任何预见信息,因此我们可以在与首先找到峰值的for
循环相同的循环中执行此操作:
highest_so_far = 0
for x in range(1, len(lst)-1):
if lst[x-1] =< lst[x] >= lst[x+1]: # "x =< y >= z" is the same as "x =< y and y >= z"
while peaks and highest_so_far > peaks[-1] < lst[x]:
peaks.pop()
if lst[x] > highest_so_far:
highest_so_far = lst[x]
peaks.append(lst[x])
这会将我们示例中的峰值减少到
[4, 7, 6, 3, 3, 3]
现在,为了找到所有湖泊,我们只需浏览列表并输出每对的较低位置。然而,有一个皱纹 - 在3系列中,我们怎么知道它是平坦的地面还是分隔相等高度的山峰的湖泊?我们必须知道这些点是否彼此相邻。这可以通过将位置信息与每个峰值一起包括来实现 -
peaks.append((lst[x], x))
然后,当我们浏览最终的峰值列表时,我们检查两者中的较低者,如果它们相等,我们检查它们是否相邻(如果它们是没有湖泊的话)。
为了让我不为您编写所有代码,我将留给您修改循环以使用包含元组的peaks
并编写确定基于峰值的湖泊。
至于时间复杂度,我相信这是在线性时间内运行的。显然,我们只运行一次列表,但中间有while
循环。在每次迭代时,while循环将检查至少一个峰值,但如果它检查多个峰值,则是因为至少有一个峰值已被删除。因此,超过每次迭代检查一次,不会多次检查峰值,最多会给所需时间线性增加。
答案 1 :(得分:5)
O(n)解决方案:
从左到右。记住第一个峰值,找到一个更高的峰值(或一个相同的高度),然后在它们之间绘制一个湖泊,而不是记住这个更高的峰值并重复该过程。 然后从右到左做同样的事情。就这么简单。 (代码未经测试)
def island_lakes():
lst=[]
x=[int(i) for i in sys.stdin.readline().split()]
lst.extend(x)
print(lst)
cur_height = lst[0]
valley_found = false
for i in range(1, len(lst)):
if lst[i] >= cur_height and valley_found:
print(cur_height)
valley_found = false
else:
valley_found = true
if lst[i] >= cur_height:
cur_height = lst[i]
cur_height = lst[-1]
valley_found = false
for i in range(len(lst)-2, -1, -1):
if lst[i] >= cur_height and valley_found:
print(cur_height)
valley_found = false
else:
valley_found = true
if lst[i] >= cur_height:
cur_height = lst[i]
答案 2 :(得分:1)
问了一个类似的问题,当然,直到后来才提出这个问题。对于此问题的变体,我的解决方案很容易修改。 要点:
有一些额外的簿记,以确保最终列表的顺序是正确的(从左到右),并且它可以解决平坦点(高原)的峰值
列表中的每个元素只触摸一次,因此这是O(n)。
def lakeLevels(island):
llist = [] # list of levels from the left side.
rlist = [] # list of levels from the right side.
lpeak = 0
for i in range(1, len(island)):
if island[i] < island[lpeak]:
break
else:
lpeak = i
rpeak = len(island)-1
for i in range(len(island)-2, 0, -1):
if island[i] < island[rpeak]:
break
else:
rpeak = i
while lpeak < rpeak:
if island[lpeak] < island[rpeak]:
i = lpeak+1
# Following if check handles plateaus.
if island[i] < island[lpeak]:
llist.append(island[lpeak])
while island[i] < island[lpeak]:
i += 1
lpeak = i
else:
i = rpeak-1
# Following if check handles plateaus.
if island[i] < island[rpeak]:
rlist.insert(0,island[rpeak]) # Insert from the
while island[i] < island[rpeak]:
i -= 1
rpeak = i
return llist + rlist
答案 3 :(得分:0)
我也有问题的解决方案,我还在代码中添加了一些注释:
public static void main(String[] args) throws Exception {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String[] tokens = br.readLine().split(" ");
int[] arr = new int[tokens.length];
for (int i = 0; i < tokens.length; i++) {
arr[i] = Integer.parseInt(tokens[i]);
}
Stack<Integer> stack = new Stack<Integer>();
// biggestRight[i] stores the index of the element which is the greatest from the ones to the right of i
int[] biggestRight = new int[tokens.length];
// toRight[i] stores the first index to the right where the element is greater or equal to arr[i]
int[] toRight = new int[tokens.length];
int biggestIndex = -1;
for (int i = arr.length - 1; i >= 0; i--) {
biggestRight[i] = biggestIndex;
if (biggestIndex == -1 || (biggestIndex != -1 && arr[i] >= arr[biggestIndex])) {
biggestIndex = i;
}
}
for (int i = arr.length - 1; i >= 0; i--) {
while (!stack.isEmpty() && arr[stack.peek()] < arr[i]) {
stack.pop();
}
if (stack.isEmpty()) {
toRight[i] = -1;
} else {
toRight[i] = stack.peek();
}
stack.push(i);
}
System.out.println(Arrays.toString(biggestRight));
System.out.println(Arrays.toString(toRight));
/**
* Iterate from left to right
* When we are at arr[i]:
* (1) if toRight[i] != -1 -> this means that there is a possible valley starting at position i (we need to check the width of it)
* (2) if toRight[i] == -1 -> this means that we are at a peak and so we search for the biggest element to the right of i, because they constitute a valley
* (3) otherwise just move to the right by one
*/
int i = 0;
while (i < arr.length) {
if (toRight[i] != -1 && toRight[i] > i + 1) {
System.out.println(Math.min(arr[toRight[i]], arr[i]));
i = toRight[i];
} else if (toRight[i] == -1 && biggestRight[i] != -1 && biggestRight[i] > i + 1) {
System.out.println(Math.min(arr[biggestRight[i]], arr[i]));
i = biggestRight[i];
} else {
i++;
}
}
}