给定一个随机数数组。在数组中找到最长的递增子序列(LIS)。我知道你们中的许多人可能已经阅读了递归和动态编程(DP)解决方案。在论坛帖子中很少有O(N log N)算法的请求。
暂时,无需考虑递归和DP解决方案。让我们抽取少量样本并将解决方案扩展到大型实例。尽管乍一看可能很复杂,但是一旦我们理解了逻辑,编码就很简单。
考虑输入数组A = {2,5,3}。我将在解释过程中扩展数组。
通过观察,我们知道LIS是{2,3}或{2,5}。请注意,我只考虑严格增加顺序。
让我们再添加两个元素,例如7、11。这些元素将扩展现有序列。现在,输入数组{2,5,3,7,11}的递增序列为{2,3,7,11}和{2,5,7,11}。
此外,我们在数组中添加了另一个元素,例如8,即输入数组变为{2,5,5,3,7,11,8}。请注意,最新元素8大于任何活动序列的最小元素(将在短期内讨论活动序列)。如何将现有序列扩展为8?首先,8可以成为LIS的一部分吗?如果是,怎么办?如果我们要添加8,它应该在7之后(通过替换11)。
由于该方法是脱机的(我们所说的是脱机的?) ,因此我们不确定是否添加8将扩展该系列。假设输入数组中有9个,例如{2,5,3,7,11,11,8,7,9…}。我们可以将8替换为11,因为可能存在最佳候选者(9),可以扩展新系列{2,3,7,8}或{2,5,7,8}。
我们的观察是,假设最大序列的末尾元素为E。如果存在元素A [j](j> i),使得E A [i]
对于原始数组{2,5,3},请注意,当我们将3添加到递增序列{2,5}时,我们会遇到相同的情况。我只是创建了两个递增的序列,以使说明变得简单。 3可以代替序列{2,5}中的5,而不是两个序列。 我知道这会令人困惑,我会尽快清除它! 问题是,什么时候可以安全地添加或替换现有序列中的元素? 让我们考虑另一个样本A = {2,5,3}。假设下一个元素是1。如何扩展当前序列{2,3}或{2,5}。显然,它也不能扩展。但是,新的最小元素有可能成为LIS的开始。为了清楚起见,请考虑数组为{2,5,3,1,2,3,4,5,6}。将1设为新序列将创建最大的新序列。 观察结果是,当我们在数组中遇到新的最小元素时,它可能是开始新序列的潜在候选者。 从观察中,我们需要维护递增序列的列表。 通常,我们有一组长度可变的活动列表。我们将元素A [i]添加到这些列表中。我们以长度减少的顺序扫描列表(用于结束元素)。我们将核实所有列表的末尾元素查找其结束元素比[I](本底值)小的列表。 我们的策略由以下条件决定, 请注意,在我们构造活动列表的任何时候,都会保持以下条件。 “较小列表的结尾元素小于较大列表的结尾元素” 。 通过一个示例将很清楚,让我们以Wiki {0,8,4,12,12,2,10,6,14,1,9,5,13,3,11,7,15}为例。 设计算法需要了解以上策略。另外,确保我们保持以下条件:“较小列表的结束元素小于较大列表的结束元素”。在进一步阅读之前,请尝试其他一些示例。重要的是要了解结束元素发生了什么。 算法: 查询最长的长度是很容易的。请注意,我们仅处理末端元素。我们不需要维护所有列表。我们可以将结束元素存储在数组中。丢弃操作可以通过替换进行模拟,并且扩展列表类似于向数组中添加更多元素。 我们将使用一个辅助数组来保留结束元素。该数组的最大长度是输入的长度。在最坏的情况下,数组会分成N个大小为1的列表(请注意,这不会导致最坏的情况下的复杂性)。要丢弃一个元素,我们将在辅助数组中跟踪A [i]的ceil值(再次观察粗略工作中的末端元素),并用A [i]替换ceil值。我们通过将元素添加到辅助数组来扩展列表。我们还维护一个计数器来跟踪辅助数组的长度。 奖励:您已经部分学习了耐心排序技术🙂 这是一个谚语,“告诉我,我会忘记的。给我看,我会记得。让我参与进来,我会明白的。”因此,从一副纸牌中挑选一套西装。从经过改组的套装中找出牌中最长的递增子序列。您将永远不会忘记这种方法。 🙂 更新– 2016年7月17日:读者的回响颇为深刻,很少有网站引用此帖子,作为我为他人提供的辛勤工作感到很高兴。看来读者在发表评论之前没有做任何功课。阅读本文后,要求阅读一些示例,并请在纸上做您的工作(不要使用编辑器/编译器)。要求是帮助自己。 “知道”的专业不同于真正的理解(不尊重)。以下是我的个人经历。 最初的内容准备工作大约花了我6个小时。但是,这是一个很好的教训。我在一个小时内完成了初始代码。当我开始写内容向读者解释时,我意识到我不理解这些案例。拿了我的笔记本(我习惯于装订绑定的笔记本以跟踪我的粗略工作),几个小时后,我填写了将近15页的粗略工作。无论您在灰色示例中看到的内容是来自这些页面的。由“ Udi Manber的算法简介”一书中的注释触发的解决方案的所有思考过程,我强烈建议您实践该书。 我怀疑,许多读者可能无法理解CeilIndex(二进制搜索)背后的逻辑。我把它作为练习让读者理解它是如何工作的。在纸上浏览几个示例。我意识到我已经在另一篇文章中介绍了该算法。 更新– 2016年8月5日: 完成工作后,以下链接值得参考。我是通过最近创建的Disqus个人资料了解链接的。该链接对Wiki中提到的方法进行了解释。 http://stackoverflow.com/questions/2631726/how-to-determine-the-longest-increasing-subsequence-using-dynamic-programming 下面给出的是查找LIS长度的代码(已更新为C++ 11代码,没有C样式的数组), 输出: 复杂: 循环运行N个元素。在最坏的情况下(什么是最坏情况的输入?),我们可能最终会使用二进制搜索(log i )查询许多A [i]的ceil值。 因此,T(n) 练习: 1.设计一种算法以构造最长的递增列表。另外,使用DAG对解决方案进行建模。 2.设计一种算法,以构造所有等长最长的递增列表。 3.以上算法是在线算法吗? 4.设计一种算法来构造最长的递减列表。 输出: 1. If A[i] is smallest among all end
candidates of active lists, we will start
new active list of length 1.
2. If A[i] is largest among all end candidates of
active lists, we will clone the largest active
list, and extend it by A[i].
3. If A[i] is in between, we will find a list with
largest end element that is smaller than A[i].
Clone and extend this list by A[i]. We will discard all
other lists of same length as that of this modified list.
A[0] = 0. Case 1. There are no active lists, create one.
0.
-----------------------------------------------------------------------------
A[1] = 8. Case 2. Clone and extend.
0.
0, 8.
-----------------------------------------------------------------------------
A[2] = 4. Case 3. Clone, extend and discard.
0.
0, 4.
0, 8. Discarded
-----------------------------------------------------------------------------
A[3] = 12. Case 2. Clone and extend.
0.
0, 4.
0, 4, 12.
-----------------------------------------------------------------------------
A[4] = 2. Case 3. Clone, extend and discard.
0.
0, 2.
0, 4. Discarded.
0, 4, 12.
-----------------------------------------------------------------------------
A[5] = 10. Case 3. Clone, extend and discard.
0.
0, 2.
0, 2, 10.
0, 4, 12. Discarded.
-----------------------------------------------------------------------------
A[6] = 6. Case 3. Clone, extend and discard.
0.
0, 2.
0, 2, 6.
0, 2, 10. Discarded.
-----------------------------------------------------------------------------
A[7] = 14. Case 2. Clone and extend.
0.
0, 2.
0, 2, 6.
0, 2, 6, 14.
-----------------------------------------------------------------------------
A[8] = 1. Case 3. Clone, extend and discard.
0.
0, 1.
0, 2. Discarded.
0, 2, 6.
0, 2, 6, 14.
-----------------------------------------------------------------------------
A[9] = 9. Case 3. Clone, extend and discard.
0.
0, 1.
0, 2, 6.
0, 2, 6, 9.
0, 2, 6, 14. Discarded.
-----------------------------------------------------------------------------
A[10] = 5. Case 3. Clone, extend and discard.
0.
0, 1.
0, 1, 5.
0, 2, 6. Discarded.
0, 2, 6, 9.
-----------------------------------------------------------------------------
A[11] = 13. Case 2. Clone and extend.
0.
0, 1.
0, 1, 5.
0, 2, 6, 9.
0, 2, 6, 9, 13.
-----------------------------------------------------------------------------
A[12] = 3. Case 3. Clone, extend and discard.
0.
0, 1.
0, 1, 3.
0, 1, 5. Discarded.
0, 2, 6, 9.
0, 2, 6, 9, 13.
-----------------------------------------------------------------------------
A[13] = 11. Case 3. Clone, extend and discard.
0.
0, 1.
0, 1, 3.
0, 2, 6, 9.
0, 2, 6, 9, 11.
0, 2, 6, 9, 13. Discarded.
-----------------------------------------------------------------------------
A[14] = 7. Case 3. Clone, extend and discard.
0.
0, 1.
0, 1, 3.
0, 1, 3, 7.
0, 2, 6, 9. Discarded.
0, 2, 6, 9, 11.
----------------------------------------------------------------------------
A[15] = 15. Case 2. Clone and extend.
0.
0, 1.
0, 1, 3.
0, 1, 3, 7.
0, 2, 6, 9, 11.
0, 2, 6, 9, 11, 15. <-- LIS List
----------------------------------------------------------------------------
C++
#include
Java
// Java program to find length of longest increasing subsequence
// in O(n Log n) time
import java.io.*;
import java.util.*;
import java.lang.Math;
class LIS {
// Binary search (note boundaries in the caller)
// A[] is ceilIndex in the caller
static int CeilIndex(int A[], int l, int r, int key)
{
while (r - l > 1) {
int m = l + (r - l) / 2;
if (A[m] >= key)
r = m;
else
l = m;
}
return r;
}
static int LongestIncreasingSubsequenceLength(int A[], int size)
{
// Add boundary case, when array size is one
int[] tailTable = new int[size];
int len; // always points empty slot
tailTable[0] = A[0];
len = 1;
for (int i = 1; i < size; i++) {
if (A[i] < tailTable[0])
// new smallest value
tailTable[0] = A[i];
else if (A[i] > tailTable[len - 1])
// A[i] wants to extend largest subsequence
tailTable[len++] = A[i];
else
// A[i] wants to be current end candidate of an existing
// subsequence. It will replace ceil value in tailTable
tailTable[CeilIndex(tailTable, -1, len - 1, A[i])] = A[i];
}
return len;
}
// Driver program to test above function
public static void main(String[] args)
{
int A[] = { 2, 5, 3, 7, 11, 8, 10, 13, 6 };
int n = A.length;
System.out.println("Length of Longest Increasing Subsequence is " + LongestIncreasingSubsequenceLength(A, n));
}
}
/* This code is contributed by Devesh Agrawal*/
Python3
# Python program to find
# length of longest
# increasing subsequence
# in O(n Log n) time
# Binary search (note
# boundaries in the caller)
# A[] is ceilIndex
# in the caller
def CeilIndex(A, l, r, key):
while (r - l > 1):
m = l + (r - l)//2
if (A[m] >= key):
r = m
else:
l = m
return r
def LongestIncreasingSubsequenceLength(A, size):
# Add boundary case,
# when array size is one
tailTable = [0 for i in range(size + 1)]
len = 0 # always points empty slot
tailTable[0] = A[0]
len = 1
for i in range(1, size):
if (A[i] < tailTable[0]):
# new smallest value
tailTable[0] = A[i]
elif (A[i] > tailTable[len-1]):
# A[i] wants to extend
# largest subsequence
tailTable[len] = A[i]
len+= 1
else:
# A[i] wants to be current
# end candidate of an existing
# subsequence. It will replace
# ceil value in tailTable
tailTable[CeilIndex(tailTable, -1, len-1, A[i])] = A[i]
return len
# Driver program to
# test above function
A = [ 2, 5, 3, 7, 11, 8, 10, 13, 6 ]
n = len(A)
print("Length of Longest Increasing Subsequence is ",
LongestIncreasingSubsequenceLength(A, n))
# This code is contributed
# by Anant Agarwal.
C#
// C# program to find length of longest
// increasing subsequence in O(n Log n)
// time
using System;
class GFG {
// Binary search (note boundaries
// in the caller) A[] is ceilIndex
// in the caller
static int CeilIndex(int[] A, int l,
int r, int key)
{
while (r - l > 1) {
int m = l + (r - l) / 2;
if (A[m] >= key)
r = m;
else
l = m;
}
return r;
}
static int LongestIncreasingSubsequenceLength(
int[] A, int size)
{
// Add boundary case, when array size
// is one
int[] tailTable = new int[size];
int len; // always points empty slot
tailTable[0] = A[0];
len = 1;
for (int i = 1; i < size; i++) {
if (A[i] < tailTable[0])
// new smallest value
tailTable[0] = A[i];
else if (A[i] > tailTable[len - 1])
// A[i] wants to extend largest
// subsequence
tailTable[len++] = A[i];
else
// A[i] wants to be current end
// candidate of an existing
// subsequence. It will replace
// ceil value in tailTable
tailTable[CeilIndex(tailTable, -1,
len - 1, A[i])]
= A[i];
}
return len;
}
// Driver program to test above function
public static void Main()
{
int[] A = { 2, 5, 3, 7, 11, 8, 10, 13, 6 };
int n = A.Length;
Console.Write("Length of Longest "
+ "Increasing Subsequence is " + LongestIncreasingSubsequenceLength(A, n));
}
}
// This code is contributed by nitin mittal.
PHP
1)
{
$m = (int)($l + ($r - $l)/2);
if ($A[$m] >= $key)
$r = $m;
else
$l = $m;
}
return $r;
}
function LongestIncreasingSubsequenceLength($A, $size)
{
// Add boundary case,
// when array size is one
$tailTable = array_fill(0, ($size + 1), 0);
$len = 0; // always points empty slot
$tailTable[0] = $A[0];
$len = 1;
for($i = 1; $i < $size; $i++)
{
if ($A[$i] < $tailTable[0])
// new smallest value
$tailTable[0] = $A[$i];
else if ($A[$i] > $tailTable[$len-1])
{
// A[i] wants to extend
// largest subsequence
$tailTable[$len] = $A[$i];
$len++;
}
else
// A[i] wants to be current
// end candidate of an existing
// subsequence. It will replace
// ceil value in tailTable
$tailTable[CeilIndex($tailTable, -1, $len-1, $A[$i])] = $A[$i];
}
return $len;
}
// Driver program to
// test above function
$A = array( 2, 5, 3, 7, 11, 8, 10, 13, 6 );
$n = count($A);
print("Length of Longest Increasing Subsequence is ".
LongestIncreasingSubsequenceLength($A, $n));
// This code is contributed by chandan_jnu
?>
Length of Longest Increasing Subsequence is 6
在C++中使用lower_bound()的替代实现: #include
Length of Longest Increasing Subsequence is 6