Ruby 算法-最长摆动子序列

Ruby 算法-最长摆动子序列,ruby,algorithm,recursion,Ruby,Algorithm,Recursion,算法: 一个数字序列称为摆动序列,如果 在连续的数字之间,严格地在正数和正数之间交替 消极的第一个差异(如果存在)可能是正的 或者是否定的。少于两个元素的序列通常是一个 摆动序列 例如,[1,7,4,9,2,5]是一个摆动序列,因为 差异6,-3,5,-7,3交替为正和负。在里面 相反,[1,4,7,2,5]和[1,7,4,5,5]不是摆动序列 首先是因为它的前两个差异是正的,第二个是负的 因为它的最后一个差是零 给定一个整数序列,返回最长整数的长度 作为摆动序列的子序列。子序列通过以下方式获得

算法:

一个数字序列称为摆动序列,如果 在连续的数字之间,严格地在正数和正数之间交替 消极的第一个差异(如果存在)可能是正的 或者是否定的。少于两个元素的序列通常是一个 摆动序列

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

给定一个整数序列,返回最长整数的长度 作为摆动序列的子序列。子序列通过以下方式获得: 最终删除一定数量的元素,并且从 原始序列,将其余元素保留在原始序列中 秩序

示例:

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
我的解决方案:

def wiggle_max_length(nums)
    [   build_seq(nums, 0, 0, true, -1.0/0.0), 
        build_seq(nums, 0, 0, false, 1.0/0.0)
    ].max
end

def build_seq(nums, index, len, wiggle_up, prev)
    return len if index >= nums.length 
    if wiggle_up && nums[index] - prev > 0 || !wiggle_up && nums[index] - prev < 0
        build_seq(nums, index + 1, len + 1, !wiggle_up, nums[index])
    else
        build_seq(nums, index + 1, len, wiggle_up, prev)
    end
end
这适用于较小的输入,例如[1,1,1,3,2,4,1,6,3,10,8]和所有样本输入,但对于很难调试的非常大的输入,它失败了,例如:

3.3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 0 0 0 0 0 0 0 0 0 0 0 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 23,52,38,74,15]


应该有输出:67,但我的soln输出57。有人知道这里出了什么问题吗?

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

一个最好的子序列是:1100 6 7 4 5 2 3,但来自该算法的两个build_seq调用将生成以下序列:

1 100 99 1.
编辑:稍微修改过的贪婪方法确实有效-请参阅,谢谢Peter de Rivaz。

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

一个最好的子序列是:1100 6 7 4 5 2 3,但来自该算法的两个build_seq调用将生成以下序列:

1 100 99 1.
编辑:稍微修改的贪婪方法确实有效-请参见,谢谢Peter de Rivaz。

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

然后:

这给出了一个关于^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)

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

然后:

这给出了一个关于^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)

动态规划可用于获得最优解

注意:我在看到@peterderarivaz提到的。当动态规划在2上运行时,本文提出了一种优于贪婪算法的方法5,它也比动态规划解决方案更容易编码。我添加了实现该方法的第二个答案

代码

例子

最长摆动的长度为6,由指数0、1、2、3、5和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值如下所示

[33, 53, 12, 64, 41, 45, 21, 97, 35, 47, 39, 93, 40, 46, 42, 95, 51, 68, 9,
 84, 34, 64, 6, 26, 3, 43, 30, 60, 3, 68, 9, 97, 19, 27, 4, 96, 37, 78, 43,
 64, 4, 65, 30, 84, 18, 50, 1, 40, 32, 76, 57, 63, 53, 57, 42, 80, 9, 41, 30,
 79, 18, 97, 23, 52, 38, 74, 15]

[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]
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)>
解释


我本来打算对算法及其实现进行解释,但后来了解到有一种更好的方法,请参见我在回答开头的说明,我决定不这样做,但当然很乐意回答任何问题。除其他外,我的说明中的链接解释了如何在此处使用动态规划。

动态规划可用于获得最佳解决方案

注意:我在看到@peterderarivaz提到的。当动态规划在2上运行时,本文提出了一种优于贪婪算法的方法5,它也比动态规划解决方案更容易编码。我添加了实现该方法的第二个答案

代码

例子

最长摆动的长度为6,由指数0、1、2、3、5和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值如下所示: 接着

解释


我本来打算对算法及其实现进行解释,但后来了解到有一种更好的方法,请参见我在回答开头的说明,我决定不这样做,但当然很乐意回答任何问题。我的笔记中的链接解释了动态编程在这里的应用。

在对@quertyman的回答的评论中,@Peterdererivaz提供了一个链接,该链接考虑了解决最长摆动子序列问题的各种方法。我已经实现了方法5,它的时间复杂度为On

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

代码

范例

解释

步骤如下

[33, 53, 12, 64, 41, 45, 21, 97, 35, 47, 39, 93, 40, 46, 42, 95, 51, 68, 9,
 84, 34, 64, 6, 26, 3, 43, 30, 60, 3, 68, 9, 97, 19, 27, 4, 96, 37, 78, 43,
 64, 4, 65, 30, 84, 18, 50, 1, 40, 32, 76, 57, 63, 53, 57, 42, 80, 9, 41, 30,
 79, 18, 97, 23, 52, 38, 74, 15]

[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]
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)>
继续,除去每组连续相等的元素中的一个以外的所有元素

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]

在对@quertyman的回答的评论中,@PeterdeRivaz提供了一个链接,链接到一个考虑解决最长摆动子序列问题的各种方法的应用程序。我已经实现了方法5,它的时间复杂度为On

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

代码

范例

解释

步骤如下

[33, 53, 12, 64, 41, 45, 21, 97, 35, 47, 39, 93, 40, 46, 42, 95, 51, 68, 9,
 84, 34, 64, 6, 26, 3, 43, 30, 60, 3, 68, 9, 97, 19, 27, 4, 96, 37, 78, 43,
 64, 4, 65, 30, 84, 18, 50, 1, 40, 32, 76, 57, 63, 53, 57, 42, 80, 9, 41, 30,
 79, 18, 97, 23, 52, 38, 74, 15]

[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]
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)>
继续,除去每组连续相等的元素中的一个以外的所有元素

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]

什么是预期的时间复杂度?不确定,但不是我关心的atm-现在我在寻找准确度Yi:预期的时间复杂度是什么?不确定,但不是我关心的atm-现在我在寻找准确度Yi:这无论如何都不是答案,应该作为评论。@mudasobwa为什么不?问题似乎是,有人知道这里出了什么问题吗?i、 使用给定的算法。好吧,的确,也许。值得注意的是,一个稍微不同的贪婪算法确实有效:如果我对我的备忘录添加有其他问题,我应该重新发布吗?这无论如何都不是答案,应该作为注释。@mudasobwa为什么不?问题似乎是,有人知道这里出了什么问题吗?i、 用给定的算法。好吧,的确,也许。也许值得注意的是,一个稍微不同的贪婪算法确实有效:如果我对我的备忘录添加有其他问题,我应该重新发布吗?