给定未排序的正整数数组,找到最长子数组的长度,其中排序后的元素是连续的。你能想到一个O(n)解决方案吗?
示例:
{10,5,3,1,4,2,8,7},答案是5.
{4,5,1,5,7,6,8,4,1},答案是5。
对于第一个例子,子阵列{5,3,1,4,2}在排序时可以形成连续序列1,2,3,4,5,这是最长的。
对于第二个例子,子阵列{5,7,6,8,4}是结果子阵列。
我可以想到一个方法,对于每个子阵列,检查(最大值 - 最小值+ 1)是否等于该子阵列的长度,如果为真,那么它是一个连续的子阵列。花费最长的。但它是O(n ^ 2)并且不能处理重复。
有人可以提供更好的方法吗?
答案 0 :(得分:2)
解决O(n)中没有重复的原始问题的算法。也许,它可以帮助某人开发处理重复的O(n)解决方案。
输入:[a1,a2,a3,...]
将原始数组映射为第一个元素为值的对,第二个为数组的索引。
数组:[[a1,i1],[a2,i2],[a3,i3],...]
使用一些O(n)算法(例如Counting Sort)对这个对数组进行排序,以便按值整数排序。 我们得到另一个数组:
数组:[[a3,i3],[a2,i2],[a1,i1],...]
其中a3,a2,a1,...按排序顺序。
通过已排序的数组
运行循环在线性时间内,我们可以检测连续的数字组a3,a2,a1。连续组定义是下一个值= prev值+ 1。 在该扫描期间,保持当前组大小( n ),索引的最小值( min )和当前索引总和( actualSum )。< / p>
在连续组内的每一步,我们可以估计指数的总和,因为它们创建了第一个元素 min ,步骤 1 的算术级数,以及到目前为止看到的组的大小<强>名词即可。 该总和估计可以使用算术级数公式在O(1)时间内完成:
估计sum =(a1 + an)* n / 2;
估计sum =(min + min +(n - 1))* n / 2;
估计sum = min * n + n *(n - 1)/ 2;
如果在某个循环步骤内连续组估计和等于实际总和,则看到目前为止连续组满足条件。将 n 保存为当前最大结果,或在当前最大值和 n 之间选择最大值。
如果在值元素上我们停止看到连续组,则重置所有值并执行相同操作。
答案 1 :(得分:1)
UPD2:以下解决方案是针对不需要子阵列连续的问题。我误解了问题陈述。不要删除这个,因为有人可能会根据我的想法来解决实际问题。
以下是我的想法:
创建字典实例(实现为哈希表,在正常情况下给出O(1))。键是整数,值是整数的散列集(也是O(1)) - var D = new Dictionary<int, HashSet<int>>
。
遍历数组A
并为索引为n
的每个整数i
执行:
n-1
中是否包含密钥n+1
和D
。
D.Add(n, new HashSet<int>)
n-1
,D.Add(n, D[n-1])
D[n-1].UnionWith(D[n+1]); D[n+1] = D[n] = D[n-1];
D[n].Add(n)
现在浏览D
中的每个密钥,找到长度最大的哈希集(查找长度为O(1))。最长的答案就是答案。
根据我的理解,最坏情况复杂度将为O(n * log(n)),这仅仅是因为UnionWith
操作。我不知道如何计算平均复杂度,但它应该接近O(n)。如果我错了,请纠正我。
UPD:要说代码,这里是C#中的测试实现,它在两个OP的示例中都给出了正确的结果:
var A = new int[] {4, 5, 1, 5, 7, 6, 8, 4, 1};
var D = new Dictionary<int, HashSet<int>>();
foreach(int n in A)
{
if(D.ContainsKey(n-1) && D.ContainsKey(n+1))
{
D[n-1].UnionWith(D[n+1]);
D[n+1] = D[n] = D[n-1];
}
else if(D.ContainsKey(n-1))
{
D[n] = D[n-1];
}
else if(D.ContainsKey(n+1))
{
D[n] = D[n+1];
}
else if(!D.ContainsKey(n))
{
D.Add(n, new HashSet<int>());
}
D[n].Add(n);
}
int result = int.MinValue;
foreach(HashSet<int> H in D.Values)
{
if(H.Count > result)
{
result = H.Count;
}
}
Console.WriteLine(result);
答案 2 :(得分:1)
在数学集定义中查看数组 S :
S = U j = 0 k ( I j )< / p>
I j 是不相交的整数段。您可以设计一个特定的间隔树(基于红黑树或您喜欢的自平衡树:))将数组存储在此数学定义中。节点和树结构应如下所示:
struct node {
int d, u;
int count;
struct node *n_left, *n_right;
}
这里,d是整数段的较小界限,u是上界。添加count
以处理数组中可能存在的重复:当尝试在树中插入已存在的元素时,我们将增加它所在节点的count
值找到了。
struct root {
struct node *root;
}
树只存储不相交的节点,因此,插入比传统的红黑树插入要复杂一些。插入间隔时,必须扫描已存在间隔的潜在溢出。在你的情况下,因为你只会插入单例,所以不应该增加太多的开销。
给定三个节点P,L和R,L是P的左子,R是P的右子。然后,你必须强制执行L.u&lt; P.d和P.u&lt; R.d(对于每个节点,d&lt; = u,当然)。
插入整数段[x,y]时,必须找到“重叠”段,也就是说,满足以下不等式之一的区间[u,d]:
y&gt; = d - 1
OR
x&lt; = u + 1
如果插入的间隔是单个x
,那么您最多只能找到2个重叠间隔节点N1和N2,例如N1.d == x + 1
和N2.u == x - 1
。然后你必须合并两个间隔并更新计数,这样你就可以得到{3},N3.d = N2.d
和N3.u = N1.u
的N3。由于N3.count = N1.count + N2.count + 1
和N1.d
之间的差值是两个细分不相交的最小增量,因此必须具有以下之一:
因此,在最坏的情况下插入仍然会在N2.u
。
从这里开始,我无法弄清楚如何处理初始序列中的顺序,但这里的结果可能很有趣:如果输入数组定义了完美整数段,那么树只有一个节点。
答案 3 :(得分:1)
这需要对数据进行两次传递。首先创建一个哈希映射,将int映射到bools。我更新了我的算法,不使用STL中的map,我很乐意在内部使用排序。该算法使用散列,可以轻松更新任何最大或最小组合,甚至可能是整数可以获得的所有可能值。
#include <iostream>
using namespace std;
const int MINIMUM = 0;
const int MAXIMUM = 100;
const unsigned int ARRAY_SIZE = MAXIMUM - MINIMUM;
int main() {
bool* hashOfIntegers = new bool[ARRAY_SIZE];
//const int someArrayOfIntegers[] = {10, 9, 8, 6, 5, 3, 1, 4, 2, 8, 7};
//const int someArrayOfIntegers[] = {10, 6, 5, 3, 1, 4, 2, 8, 7};
const int someArrayOfIntegers[] = {-2, -3, 8, 6, 12, 14, 4, 0, 16, 18, 20};
const int SIZE_OF_ARRAY = 11;
//Initialize hashOfIntegers values to false, probably unnecessary but good practice.
for(unsigned int i = 0; i < ARRAY_SIZE; i++) {
hashOfIntegers[i] = false;
}
//Chage appropriate values to true.
for(int i = 0; i < SIZE_OF_ARRAY; i++) {
//We subtract the MINIMUM value to normalize the MINIMUM value to a zero index for negative numbers.
hashOfIntegers[someArrayOfIntegers[i] - MINIMUM] = true;
}
int sequence = 0;
int maxSequence = 0;
//Find the maximum sequence in the values
for(unsigned int i = 0; i < ARRAY_SIZE; i++) {
if(hashOfIntegers[i]) sequence++;
else sequence = 0;
if(sequence > maxSequence) maxSequence = sequence;
}
cout << "MAX SEQUENCE: " << maxSequence << endl;
return 0;
}
基本思想是将哈希映射用作存储桶排序,这样您只需要对数据进行两次传递。该算法是O(2n),其又是O(n)
答案 4 :(得分:0)
不要抱有希望,这只是部分答案。
我非常有信心O(n)
无法解决问题。不幸的是,我无法证明这一点。
如果有一种方法可以在不到O(n^2)
的时间内解决问题,我怀疑解决方案是基于以下策略:
O(n)
(或者O(n log n)
)是否存在存在连续子数组,因为您使用至少i
元素进行描述。让我们称之为谓词E(i)
。i
保留的最大E(i)
。此算法的总运行时间为O(n log n)
(或O(n log^2 n)
)。
这是我能够提出将问题简化为另一个问题的唯一方法,这个问题至少比原始公式更简单。但是,我找不到在E(i)
以内计算O(n^2)
的方法,所以我可能完全不在...
答案 5 :(得分:0)
这是另一种思考问题的方法:假设你有一个仅由1和0组成的数组,你想要找到最长的连续1s运行。这可以通过行程编码1来在线性时间内完成(忽略0)。为了将您的原始问题转换为这个新的游程长度编码问题,您计算一个新数组b [i] =(a [i]&lt; a [i + 1])。这不必明确地完成,您可以隐式地实现它,以实现具有恒定内存要求和线性复杂度的算法。
答案 6 :(得分:-1)
第一个是时间O(nlog(n))
和O(n)
空格,第二个是时间O(n)
,空间O(n)
,第三个是O(n)
in时间和O(1)
在太空中。
构建binary search tree
然后遍历in order
保留2个指针,一个用于最大子集的开始,一个用于结束。
迭代树时保持max_size
值。
这是O(n*log(n))
时间和空间的复杂性。
您始终可以在线性时间内使用counting sort对设置的数字进行排序
并遍历数组,这意味着O(n)
时间和空间
复杂性。
假设没有溢出或大整数数据类型。假设数组是一个数学集(没有重复值)。你可以在O(1)
内存中执行此操作:
O(n)
时间复杂度。