【问题标题】:How to determine if a sequence is bitonic?如何确定一个序列是否是双调的?
【发布时间】:2010-06-12 14:46:04
【问题描述】:

一个序列是双调的,如果它单调增加然后单调去- 折痕,或者如果它可以循环移动以单调增加,然后 单调递减。例如序列 (1, 4, 6, 8, 3, -2) , (9, 2, -4, -10, -5) 和 (1, 2, 3, 4) 是双调的,但 (1, 3, 12, 4, 2, 10) 不是 双调。

如何判断给定的序列是否是双调的?

我有以下意见。我们可以走到n/2,其中n是数组的长度,然后检查是否

(a[i] < a[i + 1]) and (a[n - i - 1] < a[n-1 - (i + 1)])

这是正确的吗?

【问题讨论】:

  • 您的情况在我看来并不正确。考虑4 3 2 1。你会检查a[0] &lt; a[1],这不是真的,所以你会报告它不是双音的,对吗?但是你可以将数组向右移动一个位置,得到1 4 3 2,它是双调的。所以你的方法行不通。您是否正在寻找比O(n^2) 更快的东西?我很确定我有一个O(n^2) 解决方案。

标签: algorithm


【解决方案1】:

双音序列:

 /\
/  \
    \/

不是双音序列:

 /\    
/  \  / (higher than start)
    \/

显然,如果方向改变超过两次,我们就不能有双音序列。
如果方向改变少于两次,我们必须有一个双音序列。

如果方向有两个变化,我们可能有一个双音序列。考虑上面的两个 ascii 图像。显然,具有两个方向变化的序列将匹配其中一个模式(允许反射)。因此,我们通过比较第一个和最后一个元素来设置初始方向。由于它们可以相同,因此我们使用不等于最后一个元素的第一个元素。

这是一个Java实现:

public static Boolean bitonic(int[] array) {
    if (array == null) return false;
    if (array.length < 4) return true;
    Boolean dir;// false is decreasing, true is increasing
    int pos = 0, switches = 0;
    while (pos < array.length) {
        if (array[pos] != array[array.length - 1])
            break;
        pos++;
    }
    if (pos == array.length) return true;
    //pos here is the first element that differs from the last
    dir = array[pos] > array[array.length - 1];
    while (pos < array.length - 1 && switches <= 2) {
        if ((array[pos + 1] != array[pos]) &&
           ((array[pos + 1] <= array[pos]) == dir)) {
            dir ^= true;
            switches++;
        }
        pos++;
    }
    return switches <= 2;
}

【讨论】:

  • 数组 [0, -1, -2] 怎么样?它会在减少时返回 true
  • @Paweł [0,-1,-2] 是双调的,因为它是单调的(参见 this question)。
【解决方案2】:
  • 向前遍历数组,到达终点时回绕(代码如下)
  • 计算您找到的拐点总数,如果num_inflection_points==2 则您的数组是双调的。
  • 它的运行时间应该是O(n)

-

这是一个 c++ 中的工作示例:

bool is_bitonic(const vector<int>& v) {
  bool was_decreasing = v.back() > v.front();
  int num_inflections = 0;
  for (int i = 0; i < v.size() && num_inflections <= 2; i++) {
    bool is_decreasing = v[i] > v[(i+1)%v.size()];
    // Check if this element and next one are an inflection.
    if (was_decreasing != is_decreasing) {
      num_inflections++;
      was_decreasing = is_decreasing;
    }
  }
  return 2 == num_inflections;
}
  • 注意事项,具体取决于您的实施:

注1:循环遍历数组的基本思路如下:

for (int i = ip_index; i < array_length; i++) {
   int index = (i + 1) % array_length;  // wraps around to beginning
   // Retrieve the value with
   DoSomethingWithValue(array[index];)
}

注意 2:循环循环 length + 1 元素可能会简化代码,这将保证您找到两个拐点。

注 3:或者,如果您总是寻找第一个从增加到减少的拐点(在搜索其他拐点之前),它可能会简化代码。这样一来,您的扫描只需要担心准确找到另一个具有相反翻转的拐点。

注意 4:对于您的示例,您不能使用 N/2,因为拐点不一定出现在数组的中点。

【讨论】:

  • 这个答案没有考虑循环移位。如果序列发生变化,可能会有两个拐点变成一个拐点。
  • @Owen S. 是的,确实如此。您找到第一个拐点,然后搜索下一个拐点,并确保只有一个拐点。重新措辞澄清。
  • +1 -- 看起来是正确的策略!虽然有点迂腐,但我相信您的代码不能根据精确定义正确处理长度 2 - 您在长度 2 中有两个拐点,但您不能将其重新包装为上升和下降。
  • @Rex Kerr - 谢谢,是的,我忽略了错误检查,长度
  • 更正:“我没有检查子序列是否单调”。
【解决方案3】:

这是一个高效且简单的 Java 实现。它只遍历数组一次以确定数组是否是双调的。它使用变量reversal 来计算数组中单调性方向反转的次数(包括循环环绕)。

变量trend可以有三个值:

  • 0,如果值相同;
  • 1,如果数组是单调递增的;
  • -1,如果数组单调递减。
public static boolean bitonic(int[] arr) {
  int reversal = 0;
  int len = arr.length;
  int trend = 0; // 0 means any, 1 means increasing, -1 means decreasing 
  for (int i= 0; i < len ; i++) {
    if(arr[i%len] < arr[(i+1)%len]){
      if (trend == 0) trend = 1;
      else if ( trend == -1) {
        reversal ++;
        trend = 1;
      }
    }
    else if(arr[i%len] > arr[(i+1)%len]){
      if (trend == 0) trend = -1;
      else if ( trend == 1) {
        reversal ++;
        trend = -1;
      }
    }
    if(reversal > 2) return false;
  }
  return true;
}

【讨论】:

    【解决方案4】:

    可以找峰值,即当a[i-1] a[i+1],那么a[i]就是局部峰值(注意换行使用模数运算符)。

    在双音序列中,只能有一个这样的峰值。找到山峰后,您可以向左走下坡(必要时绕行,再次使用模数),直到找到上坡。然后你回到山顶,向右走下坡,直到你发现另一个上坡。现在,如果一个序列是双调的,那么你将通过两边下坡来覆盖整个序列。

    顺便说一句,我误解了这个问题,还是你的第一个例子不是双音的?

    【讨论】:

    • 我也注意到了...我不认为它是双音的。
    • 我认为这个例子实际上是多个例子,不幸的是,用逗号分隔:) 也许 [1,4,6], [8, 3, -2 , 9], [2, -4, -10, -5].
    【解决方案5】:

    上升和下降之间需要恰好有两个(或者,取决于您的定义如何处理退化,正好为 0)。不要忘记检查 a[n] 和 a[0] 之间的转换。

    【讨论】:

      猜你喜欢
      • 2015-04-03
      • 1970-01-01
      • 1970-01-01
      • 2010-09-16
      • 2023-04-08
      • 2010-09-30
      • 1970-01-01
      • 1970-01-01
      • 2016-09-11
      相关资源
      最近更新 更多