岛湖算法

时间:2014-05-09 10:13:40

标签: python algorithm complexity-theory

摘要

假设你有一个多山的二维岛屿。由于天气多雨,岛上的所有山谷都被水完全填满,以至于再加水会导致湖泊溢出。如果溢流进入另一个湖泊,它也会溢出。最终,水将从岛上流出。考虑到岛屿的形状,你如何找出形成的湖泊?

详情

读取一系列数字(每个数字代表二维连通图中一个点的高度),计算并输出可能在岛屿山谷中形成的所有湖泊表面的高度。

例如,输入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])

此代码找到所有部分湖泊,通过仅在最深的山谷中填充水来制作,但如下图所示,我可以有一个由其他湖泊组成的湖泊。

Example

如果输入高于4 3 7 1 3 2 3 5 6 2 1 3 3 2 3,则应打印4 6 3 3但我的程序输出:

4 3 3 2 3

如何修复我的代码以允许它找到较大的山谷,例如那些山峰较小的山谷?

4 个答案:

答案 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++;
        }
    }
}