给定一组随机数。在数组中查找最长递增子序列(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, 3, 7, 11, 8}。请注意,最新的元素 8 大于任何活动序列的最小元素(稍后将讨论有关活动序列的内容)。我们如何用 8 扩展现有序列?首先,8可以成为LIS的一部分吗?如果是,如何?如果我们要添加 8,它应该在 7 之后(通过替换 11)。
由于该方法是离线的(我们所说的离线是什么意思?) ,我们不确定添加 8 是否会扩展系列。假设输入数组中有 9 个,比如 {2, 5, 3, 7, 11, 8, 7, 9 …}。我们可以将 11 替换为 8,因为存在可以扩展新系列 {2, 3, 7, 8} 或 {2, 5, 7, 8} 的潜在最佳候选 (9)。
我们的观察是,假设最大序列的结束元素是 E。如果存在元素 A[j] (j > i) 使得 E < A,我们可以将当前元素 A[i] 添加(替换)到现有序列[i] < A[j] 或 (E > A[i] < A[j] – 用于替换)。在上面的例子中,E = 11,A[i] = 8 和 A[j] = 9。
对于我们的原始数组 {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](本底值)小的列表。
我们的策略由以下条件决定,
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.
请注意,在我们构建活动列表期间的任何情况下,都会保持以下条件。
“较小列表的结尾元素小于较大列表的结尾元素” 。
举个例子就清楚了,让我们以维基{0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15}为例。
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
----------------------------------------------------------------------------
需要理解上述策略才能设计算法。另外,确保我们保持了条件,“较小列表的结尾元素小于较大列表的结尾元素”。在进一步阅读之前,请尝试一些其他示例。了解结束元素发生了什么很重要。
算法:
查询最长的长度相当容易。请注意,我们仅处理结束元素。我们不需要维护所有列表。我们可以将结束元素存储在数组中。丢弃操作可以用替换来模拟,扩展列表类似于向数组添加更多元素。
我们将使用一个辅助数组来保留结束元素。该数组的最大长度是输入的长度。在最坏的情况下,数组分为 N 个大小为 1 的列表(请注意,它不会导致最坏情况的复杂性)。为了丢弃一个元素,我们将在辅助数组中追踪 A[i] 的 ceil 值(再次观察你粗略工作中的结束元素),并将 ceil 值替换为 A[i]。我们通过向辅助数组添加元素来扩展列表。我们还维护一个计数器来跟踪辅助数组的长度。
奖励:你已经部分学习了耐心排序技术🙂
有一句谚语说:“告诉我,我会忘记的。给我看,我会记住的。让我参与,我就会明白。”所以,从一副纸牌中挑选一套西装。从洗好的花色中找出最长的递增子序列。你永远不会忘记这种方法。 🙂
更新 – 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 样式数组),
C++
#include
#include
// Binary search (note boundaries in the caller)
int CeilIndex(std::vector& v, int l, int r, int key)
{
while (r - l > 1) {
int m = l + (r - l) / 2;
if (v[m] >= key)
r = m;
else
l = m;
}
return r;
}
int LongestIncreasingSubsequenceLength(std::vector& v)
{
if (v.size() == 0)
return 0;
std::vector tail(v.size(), 0);
int length = 1; // always points empty slot in tail
tail[0] = v[0];
for (size_t i = 1; i < v.size(); i++) {
// new smallest value
if (v[i] < tail[0])
tail[0] = v[i];
// v[i] extends largest subsequence
else if (v[i] > tail[length - 1])
tail[length++] = v[i];
// v[i] will become end candidate of an existing
// subsequence or Throw away larger elements in all
// LIS, to make room for upcoming greater elements
// than v[i] (and also, v[i] would have already
// appeared in one of LIS, identify the location
// and replace it)
else
tail[CeilIndex(tail, -1, length - 1, v[i])] = v[i];
}
return length;
}
int main()
{
std::vector v{ 2, 5, 3, 7, 11, 8, 10, 13, 6 };
std::cout << "Length of Longest Increasing Subsequence is "
<< LongestIncreasingSubsequenceLength(v) << '\n';
return 0;
}
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
?>
CPP
#include
using namespace std;
int LongestIncreasingSubsequenceLength(std::vector& v)
{
if (v.size() == 0) // boundry case
return 0;
std::vector tail(v.size(), 0);
int length = 1; // always points empty slot in tail
tail[0] = v[0];
for (int i = 1; i < v.size(); i++) {
// Do binary search for the element in
// the range from begin to begin + length
auto b = tail.begin(), e = tail.begin() + length;
auto it = lower_bound(b, e, v[i]);
// If not present change the tail element to v[i]
if (it == tail.begin() + length)
tail[length++] = v[i];
else
*it = v[i];
}
return length;
}
int main()
{
std::vector v{ 2, 5, 3, 7, 11, 8, 10, 13, 6 };
std::cout
<< "Length of Longest Increasing Subsequence is "
<< LongestIncreasingSubsequenceLength(v);
return 0;
}
Python3
from bisect import bisect_left
def LongestIncreasingSubsequenceLength(v):
if len(v) == 0: # boundry case
return 0
tail = [0 for i in range(len(v) + 1)]
length = 1 # always points empty slot in tail
tail[0] = v[0]
for i in range(1, len(v)):
if v[i] > tail[length-1]:
# v[i] extends the largest subsequence
tail[length] = v[i]
length += 1
else:
# v[i] will extend a subsequence and discard older subsequence
# find the largest value just smaller than v[i] in tail
# to find that value do binary search for the v[i] in
# the range from begin to 0 + length
# bisect function either returns index where element is found
# or the appropriate index at which element should be placed
# finally replace the existing subsequene with new end value
tail[bisect_left(tail, v[i], 0, length-1)] = v[i]
return length
# Driver program to test above function
v = [2, 5, 3, 7, 11, 8, 10, 13, 6]
print("Length of Longest Increasing Subsequence is ",
LongestIncreasingSubsequenceLength(v))
# This code is contributed by Serjeel Ranjan
Java
import java.io.*;
import java.lang.Math;
import java.util.*;
class LIS {
static int LongestIncreasingSubsequenceLength(int v[])
{
if (v.length == 0) // boundry case
return 0;
int[] tail = new int[v.length];
int length = 1; // always points empty slot in tail
tail[0] = v[0];
for (int i = 1; i < v.length; i++) {
if (v[i] > tail[length - 1]) {
// v[i] extends the largest subsequence
tail[length++] = v[i];
}
else {
// v[i] will extend a subsequence and
// discard older subsequence
// find the largest value just smaller than
// v[i] in tail
// to find that value do binary search for
// the v[i] in the range from begin to 0 +
// length
int idx = Arrays.binarySearch(
tail, 0, length - 1, v[i]);
// binarySearch in java returns negative
// value if searched element is not found in
// array
// this negative value stores the
// appropriate place where the element is
// supposed to be stored
if (idx < 0)
idx = -1 * idx - 1;
// replacing the existing subsequene with
// new end value
tail[idx] = v[i];
}
}
return length;
}
// Driver program to test above function
public static void main(String[] args)
{
int v[] = { 2, 5, 3, 7, 11, 8, 10, 13, 6 };
System.out.println(
"Length of Longest Increasing Subsequence is "
+ LongestIncreasingSubsequenceLength(v));
}
}
/* This code is contributed by Serjeel Ranjan */
输出:
Length of Longest Increasing Subsequence is 6
复杂:
循环运行 N 个元素。在最坏的情况下(最坏情况的输入是什么?),我们最终可能会使用二分搜索(log i )来查询许多 A[i] 的 ceil 值。
因此,T(n) < O( log N! ) = O(N log N)。分析确保上下界也是 O( N log N )。复杂度是 THETA (N log N)。
练习:
1. 设计一个算法来构造最长递增列表。此外,使用 DAG 为您的解决方案建模。
2. 设计一个算法来构造所有长度相等的递增列表。
3.以上算法是在线算法吗?
4. 设计一个算法来构造最长递减列表。
下面给出了使用其内置二进制搜索函数在各种语言中的替代实现:
CPP
#include
using namespace std;
int LongestIncreasingSubsequenceLength(std::vector& v)
{
if (v.size() == 0) // boundry case
return 0;
std::vector tail(v.size(), 0);
int length = 1; // always points empty slot in tail
tail[0] = v[0];
for (int i = 1; i < v.size(); i++) {
// Do binary search for the element in
// the range from begin to begin + length
auto b = tail.begin(), e = tail.begin() + length;
auto it = lower_bound(b, e, v[i]);
// If not present change the tail element to v[i]
if (it == tail.begin() + length)
tail[length++] = v[i];
else
*it = v[i];
}
return length;
}
int main()
{
std::vector v{ 2, 5, 3, 7, 11, 8, 10, 13, 6 };
std::cout
<< "Length of Longest Increasing Subsequence is "
<< LongestIncreasingSubsequenceLength(v);
return 0;
}
蟒蛇3
from bisect import bisect_left
def LongestIncreasingSubsequenceLength(v):
if len(v) == 0: # boundry case
return 0
tail = [0 for i in range(len(v) + 1)]
length = 1 # always points empty slot in tail
tail[0] = v[0]
for i in range(1, len(v)):
if v[i] > tail[length-1]:
# v[i] extends the largest subsequence
tail[length] = v[i]
length += 1
else:
# v[i] will extend a subsequence and discard older subsequence
# find the largest value just smaller than v[i] in tail
# to find that value do binary search for the v[i] in
# the range from begin to 0 + length
# bisect function either returns index where element is found
# or the appropriate index at which element should be placed
# finally replace the existing subsequene with new end value
tail[bisect_left(tail, v[i], 0, length-1)] = v[i]
return length
# Driver program to test above function
v = [2, 5, 3, 7, 11, 8, 10, 13, 6]
print("Length of Longest Increasing Subsequence is ",
LongestIncreasingSubsequenceLength(v))
# This code is contributed by Serjeel Ranjan
Java
import java.io.*;
import java.lang.Math;
import java.util.*;
class LIS {
static int LongestIncreasingSubsequenceLength(int v[])
{
if (v.length == 0) // boundry case
return 0;
int[] tail = new int[v.length];
int length = 1; // always points empty slot in tail
tail[0] = v[0];
for (int i = 1; i < v.length; i++) {
if (v[i] > tail[length - 1]) {
// v[i] extends the largest subsequence
tail[length++] = v[i];
}
else {
// v[i] will extend a subsequence and
// discard older subsequence
// find the largest value just smaller than
// v[i] in tail
// to find that value do binary search for
// the v[i] in the range from begin to 0 +
// length
int idx = Arrays.binarySearch(
tail, 0, length - 1, v[i]);
// binarySearch in java returns negative
// value if searched element is not found in
// array
// this negative value stores the
// appropriate place where the element is
// supposed to be stored
if (idx < 0)
idx = -1 * idx - 1;
// replacing the existing subsequene with
// new end value
tail[idx] = v[i];
}
}
return length;
}
// Driver program to test above function
public static void main(String[] args)
{
int v[] = { 2, 5, 3, 7, 11, 8, 10, 13, 6 };
System.out.println(
"Length of Longest Increasing Subsequence is "
+ LongestIncreasingSubsequenceLength(v));
}
}
/* This code is contributed by Serjeel Ranjan */
输出:
Length of Longest Increasing Subsequence is 6
如果您希望与专家一起参加现场课程,请参阅DSA 现场工作专业课程和学生竞争性编程现场课程。