算法 - 最长的摆动子序列

时间:2017-08-05 03:32:23

标签: ruby algorithm recursion

算法:

  

如果存在差异,则将一系列数字称为摆动序列   在连续数字之间严格地在正数和   负。第一个差异(如果存在)可能是积极的   或否定的。具有少于两个元素的序列通常是a   摆动序列。

     

例如,[1,7,4,9,2,5]是一个摆动序列,因为   差异(6,-3,5,-7,3)交替为正和负。在   相比之下,[1,4,7,2,5]和[1,7,4,5,5]不是摆动序列,   首先是因为它的前两个差异是积极的,第二个是差异   因为它的最后一个差异是零。

     

给定一系列整数,返回最长的长度   子序列是一个摆动序列。通过以下方式获得子序列   从中删除一些元素(最终也是零)   原始序列,将剩余的元素留在原始元素中   顺序。

示例:

_JAVA_OPTIONS

我的解决方案:

Input: [1,7,4,9,2,5]
Output: 6
The entire sequence is a wiggle sequence.

Input: [1,17,5,10,13,15,10,5,16,8]
Output: 7
There are several subsequences that achieve this length. One is [1,17,10,13,10,16,8].

Input: [1,2,3,4,5,6,7,8,9]
Output: 2

这适用于较小的输入(例如[1,1,1,3,2,4,1,6,3,10,8]和所有样本输入,但是对于非常大的输入(其中更难调试),如:

  

[33,53,12,64,50,41,45,21,97,35,47,92,39,0,93,55,40,46,69,42,6,95,51, 68,72,9,32,84,34,64,6,2,26,98,3,43,30,60,3,68,82,9,97,19,27,98,99,4, 30,96,37,9,78,43,64,4,65,30,84,90,87,64,18,50,60,1,40,32,48,50,76,100,57,29, 63,53,46,57,93,98,42,80,82,9,41,55,69,84,82,79,30,79,18,97,67,23,52,38,74, 15]

应该有输出:67但是我的soln输出57.有谁知道这里有什么问题?

4 个答案:

答案 0 :(得分:3)

尝试的方法是一个贪婪的解决方案(因为它总是使用当前元素,如果它满足摆动条件),但这并不总是有效。 我将尝试使用这个更简单的反例来说明这一点:1 100 99 6 7 4 5 2 3

一个最佳子序列是:1 100 6 7 4 5 2 3,但算法中的两个build_seq调用将生成以下序列:

  • 1 100 99
  • 1

编辑:稍加修改的贪婪方法确实有效 - 请参阅this link,谢谢Peter de Rivaz。

答案 1 :(得分:1)

动态编程可用于获得最佳解决方案。

注意:我在看到@PeterdeRivaz提到的article之前写了这个。虽然动态编程(O(n 2 ))有效,但本文提出了一种优越的(O(n))“贪婪”算法(“方法#5”),它比编码更容易。动态编程解决方案。我添加了第二个实现该方法的答案。

<强>代码

def longest_wiggle(arr)
  best = [{ pos_diff: { length: 0, prev_ndx: nil },
            neg_diff: { length: 0, prev_ndx: nil } }]
  (1..arr.size-1).each do |i|
    calc_best(arr, i, :pos_diff, best)
    calc_best(arr, i, :neg_diff, best)
  end
  unpack_best(best)
end

def calc_best(arr, i, diff, best)
  curr = arr[i]
  prev_indices = (0..i-1).select { |j|
    (diff==:pos_diff) ? (arr[j] < curr) : (arr[j] > curr) }
  best[i] = {} if best.size == i
  best[i][diff] =
    if prev_indices.empty?
      { length: 0, prev_ndx: nil }
    else
      prev_diff = previous_diff(diff)
      j = prev_indices.max_by { |j| best[j][prev_diff][:length] }
      { length: (1 + best[j][prev_diff][:length]), prev_ndx: j }
    end
end

def previous_diff(diff)
  diff==:pos_diff ? :neg_diff : :pos_diff·
end

def unpack_best(best)
  last_idx, last_diff =
    best.size.times.to_a.product([:pos_diff, :neg_diff]).
         max_by { |i,diff| best[i][diff][:length] }
  return [0, []] if best[last_idx][last_diff][:length].zero?
  best_path = []
  loop do
    best_path.unshift(last_idx)
    prev_index = best[last_idx][last_diff][:prev_ndx]
    break if prev_index.nil?
    last_idx = prev_index·
    last_diff = previous_diff(last_diff)
  end
  best_path
end

<强>实施例

longest_wiggle([1, 4, 2, 6, 8, 3, 2, 5])
  #=> [0, 1, 2, 3, 5, 7]]

最长摆动的长度为6,由指数01235和{{ 1}},即7

第二个例子使用问题中给出的较大数组。

[1, 4, 2, 6, 3, 5]

如上所述,最大的摆动由arr = [33, 53, 12, 64, 50, 41, 45, 21, 97, 35, 47, 92, 39, 0, 93, 55, 40, 46, 69, 42, 6, 95, 51, 68, 72, 9, 32, 84, 34, 64, 6, 2, 26, 98, 3, 43, 30, 60, 3, 68, 82, 9, 97, 19, 27, 98, 99, 4, 30, 96, 37, 9, 78, 43, 64, 4, 65, 30, 84, 90, 87, 64, 18, 50, 60, 1, 40, 32, 48, 50, 76, 100, 57, 29, arr.size 63, 53, 46, 57, 93, 98, 42, 80, 82, 9, 41, 55, 69, 84, 82, 79, 30, 79, 18, 97, 67, 23, 52, 38, 74, 15] #=> 100 longest_wiggle(arr).size #=> 67 longest_wiggle(arr) #=> [0, 1, 2, 3, 5, 6, 7, 8, 9, 10, 12, 14, 16, 17, 19, 21, 22, 23, 25, # 27, 28, 29, 30, 32, 34, 35, 36, 37, 38, 39, 41, 42, 43, 44, 47, 49, 50, # 52, 53, 54, 55, 56, 57, 58, 62, 63, 65, 66, 67, 70, 72, 74, 75, 77, 80, # 81, 83, 84, 90, 91, 92, 93, 95, 96, 97, 98, 99] 的67个元素组成。解决方案时间基本上是即时的。

这些指数的arr值如下。

arr

<强>解释

我本来打算提供一个算法及其实现的解释,但从那时起我就学到了一种优越的方法(参见我在答案开头的注释),我决定不这样做,但当然是很乐意回答任何问题。我的笔记中的链接解释了如何在这里使用动态编程。

答案 2 :(得分:0)

设Wp [i]是从元素i开始的最长的摆动序列,其中第一个差异是正的。设Wn [i]是相同的,但第一个差异是负的。

然后:

Wp[k] = max(1+Wn[k'] for k<k'<n, where A[k'] > A[k]) (or 1 if no such k' exists)
Wn[k] = max(1+Wp[k'] for k<k'<n, where A[k'] < A[k]) (or 1 if no such k' exists)

这给出了一个O(n ^ 2)动态编程解决方案,这里是伪代码

Wp = [1, 1, ..., 1] -- length n
Wn = [1, 1, ..., 1] -- length n
for k = n-1, n-2, ..., 0
   for k' = k+1, k+2, ..., n-1
       if A[k'] > A[k]
           Wp[k] = max(Wp[k], Wn[k']+1)
       else if A[k'] < A[k]
           Wn[k] = max(Wn[k], Wp[k']+1)
result = max(max(Wp[i], Wn[i]) for i = 0, 1, ..., n-1)

答案 3 :(得分:0)

在对@quertyman的回答的评论中,@ PeterdeRivaz提供了article的链接,该链接考虑了解决“最长的摆动子序列”问题的各种方法。我实施了“方法#5”,其时间复杂度为O(n)。

算法既简单又快速。第一步是从每对相等的连续元素中删除一个元素,并继续这样做直到没有相等的连续元素。例如,[1,2,2,2,3,4,4]将转换为[1,2,3,4]。最长的摆动子序列包括结果数组的第一个和最后一个元素a,以及a[i]0 < i < a.size-1的每个元素a[i-1] < a[i] > a[i+1]a[i-1] > a[i] > a[i+1]。换句话说,它包括第一个和最后一个元素以及所有峰和谷底。这些元素在下图中是A,D,E,G,H,I(取自上述文章,经许可)。

enter image description here

<强>代码

def longest_wiggle(arr)
  arr.each_cons(2).
      reject { |a,b| a==b }.
      map(&:first).
      push(arr.last).
      each_cons(3).
      select { |triple| [triple.min, triple.max].include? triple[1] }.
      map { |_,n,_| n }.
      unshift(arr.first).
      push(arr.last)
end

示例

arr = [33, 53, 12, 64, 50, 41, 45, 21, 97, 35, 47, 92, 39, 0, 93, 55, 40,
       46, 69, 42, 6, 95, 51, 68, 72, 9, 32, 84, 34, 64, 6, 2, 26, 98, 3,
       43, 30, 60, 3, 68, 82, 9, 97, 19, 27, 98, 99, 4, 30, 96, 37, 9, 78,
       43, 64, 4, 65, 30, 84, 90, 87, 64, 18, 50, 60, 1, 40, 32, 48, 50, 76,
       100, 57, 29, 63, 53, 46, 57, 93, 98, 42, 80, 82, 9, 41, 55, 69, 84,
       82, 79, 30, 79, 18, 97, 67, 23, 52, 38, 74, 15]    

a = longest_wiggle(arr)
  #=> [33, 53, 12, 64, 41, 45, 21, 97, 35, 92, 0, 93, 40, 69, 6, 95, 51, 72,
  #    9, 84, 34, 64, 2, 98, 3, 43, 30, 60, 3, 82, 9, 97, 19, 99, 4, 96, 9,
  #    78, 43, 64, 4, 65, 30, 90, 18, 60, 1, 40, 32, 100, 29, 63, 46, 98, 42,
  #    82, 9, 84, 30, 79, 18, 97, 23, 52, 38, 74, 15]
a.size
  #=> 67

<强>解释

步骤如下。

arr = [3, 4, 4, 5, 2, 3, 7, 4]

enum1 = arr.each_cons(2)
  #=> #<Enumerator: [3, 4, 4, 5, 2, 3, 7, 4]:each_cons(2)>

我们可以看到这个枚举器通过将它转换为数组而生成的元素。

enum1.to_a
  #=> [[3, 4], [4, 4], [4, 5], [5, 2], [2, 3], [3, 7], [7, 4]]

继续,删除每组连续相等元素中的一个。

d = enum1.reject { |a,b| a==b }
  #=> [[3, 4], [4, 5], [5, 2], [2, 3], [3, 7], [7, 4]]
e = d.map(&:first)
  #=> [3, 4, 5, 2, 3, 7]

添加最后一个元素。

f = e.push(arr.last)
  #=> [3, 4, 5, 2, 3, 7, 4]

接下来,找到山峰和谷底。

enum2 = f.each_cons(3)
  #=> #<Enumerator: [3, 4, 5, 2, 3, 7, 4]:each_cons(3)>
enum2.to_a
  #=> [[3, 4, 5], [4, 5, 2], [5, 2, 3], [2, 3, 7], [3, 7, 4]]
g = enum2.select { |triple| [triple.min, triple.max].include? triple[1] }
  #=> [[4, 5, 2], [5, 2, 3], [3, 7, 4]]
h = g.map { |_,n,_| n }
  #=> [5, 2, 7]

最后,添加arr的第一个和最后一个值。

i = h.unshift(arr.first)
  #=> [3, 5, 2, 7]
i.push(arr.last)
  #=> [3, 5, 2, 7, 4]