输入:N个元素的只读数组,包含从1到N的整数值(某些整数值可以出现多次!)。并且固定大小的记忆区域(10,100,1000等 - 不,取决于N)。
如果数组代表一个排列,如何告诉在O(n)?
- 到目前为止我取得了什么(答案证明这是不好): -
<击> 撞击>
<击>我知道如果条件(2)为真,我可能有一个排列。我想知道是否有办法证明条件(2)足以判断我是否有排列。到目前为止,我还没想出来......
击><击> 撞击>
答案 0 :(得分:16)
我有点怀疑有一个解决方案。你的问题似乎与几年前在数学文献中提出的问题非常接近,a summary given here ("The Duplicate Detection Problem", S. Kamal Abdali, 2003)使用循环检测 - 这个想法如下:
如果存在重复,则在1和N之间存在数字j
,以便以下将导致无限循环:
x := j;
do
{
x := a[x];
}
while (x != j);
因为排列由不同元素s 0 ,s 1 ,... s k-1 的一个或多个子集S组成其中s j = a [s j-1 ]表示1和k-1之间的所有j,s 0 = a [s < sub> k-1 ],因此所有元素都参与循环 - 其中一个副本不会成为这样一个子集的一部分。
e.g。如果数组= [2,1,4,6, 8 ,7,9,3,8]
然后在位置5处以粗体显示的元素是重复的,因为所有其他元素形成循环:{2 - &gt; 1,4-&gt; 6 - &gt; 7 - &gt; 9 - &gt; 8 - &gt; 3}。阵列[2,1,4,6,5,7,9,3,8]和[2,1,4,6,3,7,9,5,8]是有效排列(周期为{2} - &gt; 1,4-> 6-> 7-> 9-> 8-> 3,5}和{2-> 1,4-> 6-> 7-> 9 - 分别是&gt; 8 - &gt; 5 - &gt; 3}。
Abdali开始寻找重复的方法。基本上,以下算法(使用Floyd's cycle-finding algorithm)适用于您遇到的其中一个重复项:
function is_duplicate(a, N, j)
{
/* assume we've already scanned the array to make sure all elements
are integers between 1 and N */
x1 := j;
x2 := j;
do
{
x1 := a[x1];
x2 := a[x2];
x2 := a[x2];
} while (x1 != x2);
/* stops when it finds a cycle; x2 has gone around it twice,
x1 has gone around it once.
If j is part of that cycle, both will be equal to j. */
return (x1 != j);
}
困难在于我不确定你所说的问题是否与他论文中的问题相符,而且我也不确定他描述的方法是在O(N)中运行还是使用固定数量的空间。一个潜在的反例是以下数组:
[3,4,5,6,7,8,9,10 ...... N-10,N-9,N-8,N-7,N-2,N-5,N-5 ,N-3,N-5,N-1,N,1,2]
基本上是身份置换移位2,元素[N-6,N-4和N-2]被[N-2,N-5,N-5]取代。这有正确的总和(不是正确的产品,但我拒绝将产品作为一种可能的检测方法,因为用任意精度算术计算N!的空间要求是O(N),这违反了“固定存储空间”的精神要求),如果你试图寻找周期,你将获得周期{3 - > 5 - &gt; 7 - &gt; 9 - &gt; ...... N-7 - &gt; N-5 - &gt; N-1}和{4 - > 6 - &gt; 8 - &gt; ...... N-10 - &gt; N-8 - &gt; N-2 - > N - >; 2}。问题是可能有多达N个周期(身份置换有N个周期),每个周期都占用O(N)来找到重复,你必须跟踪某些已经跟踪的周期和哪些没有。我怀疑在固定的空间内可以做到这一点。但也许是。
这是一个非常严重的问题,值得在mathoverflow.net上询问(尽管大多数时候mathoverflow.net都是在stackoverflow上引用的,但是这些问题太容易了)
编辑:我做了ask on mathoverflow,那里有一些有趣的讨论。
答案 1 :(得分:10)
这在O(1)空间中是不可能的,至少使用单扫描算法。
<强>证明强>
假设您已经处理了N个元素中的N / 2个。假设序列是一个置换,那么,给定算法的状态,你应该能够找出N / 2个剩余元素的集合。如果你无法找出剩余的元素,那么可以通过重复一些旧元素来欺骗算法。
有N个选择N / 2个可能的剩余集。它们中的每一个都必须由算法的不同内部状态表示,否则你无法找出剩余的元素。但是,它需要对数空间来存储X状态,因此需要BigTheta(log(N选择N / 2))空间来存储N选择N / 2个状态。该值随N增长,因此算法的内部状态不能适合O(1)空间。
更多正式证明
你想要创建一个程序P,给定最后的N / 2个元素和线性时间常数空间算法在处理N / 2个元素后的内部状态,确定整个序列是否是一个排列1..N。此次要计划没有时间或空间限制。
假设P存在,我们可以创建一个程序Q,只采用线性时间常数空间算法的内部状态,该算法确定序列的必要最终N / 2个元素(如果它是一个置换)。 Q通过传递P每个可能的最终N / 2个元素并返回P返回true的集合来工作。
但是,因为Q有N选择N / 2个可能的输出,所以它必须至少有N个选择N / 2个可能的输入。这意味着原始算法的内部状态必须存储至少N个选择N / 2个状态,需要BigTheta(log N选择N / 2),这大于常数大小。
因此,具有时间和空间界限的原始算法如果具有恒定大小的内部状态,也无法正常工作。
[我认为这个想法可以概括,但思考并不能证明。]
<强>后果强>
BigTheta(log(N选择N / 2))等于BigTheta(N)。因此,只要使用布尔数组并在遇到它们时勾选值(可能)是空间最优的,并且时间也是最佳的,因为它需要线性时间。
答案 2 :(得分:5)
我怀疑你能否证明这一点;)
(1, 2, 4, 4, 4, 5, 7, 9, 9)
我认为更一般地说,通过按顺序处理数字无法解决这个问题。假设您按顺序处理元素,并且您在数组的中间。现在你的程序状态必须以某种方式反映你到目前为止遇到的数字。这需要至少存储O(n)位。
答案 3 :(得分:3)
由于作为N而不是M的函数给出的复杂性,这不起作用,暗示N>&gt;中号
这是我的镜头,但是对于一个有用的布隆过滤器,你需要一个大的M,此时你也可以使用简单的位切换为整数这样的东西
http://en.wikipedia.org/wiki/Bloom_filter
对于数组中的每个元素 运行k哈希函数 检查包含在bloom过滤器中 如果它在那里,你有可能以前见过这个元素 如果不是,请添加
当你完成后,你也可以按顺序将它与1..N数组的结果进行比较,因为这只会花费你另一个N.
现在,如果我没有提出足够的警告。它不是100%,甚至是关闭,因为你指定了N的复杂性,这意味着N&gt;&gt; M,所以从根本上说它不会像你指定的那样起作用。
顺便说一下,个别项目的误报率应该是 e = 2 ^( - m /(n * sqrt(2)))周围的人会让你知道M需要多大才能被接受。
答案 4 :(得分:1)
我不知道如何在O(N)中完成它,或者即使它可以在O(N)中完成。我知道如果你(使用适当的)排序和比较它可以在O(N log N)中完成。
话虽如此,有许多O(N)技术可以表明一个不是另一个的排列。
希望这有帮助。
答案 5 :(得分:1)
您可以通过计算O(n)
和sum(x_i)
模数一堆随机选择的大小为product(x_i)
的常量C,在随机O(n)
时间和常量空间中执行此操作。这基本上可以解决product(x_i)
过大的问题。
但仍然存在许多未解决的问题,例如,如果sum(x_i)=N(N+1)/2
和product(x_i)=N!
是保证排列的充分条件,那么非排列会产生误报的可能性是多少(I希望你尝试的每个C都能达到1 / C,但也许不会。)
答案 6 :(得分:0)
这是一种排列,当且仅当数组中没有重复值时,应该很容易在O(N)中检查
答案 7 :(得分:0)
根据您有多少空间,相对于N,您可以尝试使用散列和存储桶。
即,遍历整个列表,散列每个元素,并将其存储在存储桶中。您需要找到一种方法来减少哈希中的桶冲突,但这是一个已解决的问题。
如果一个元素试图进入一个包含与之相同的项目的存储桶,那么它就是一个排列。
当你只触摸每个元素一次时,这种类型的解决方案将是O(N)。
然而,问题在于空间M是否大于N.如果M> N,这个解决方案没问题,但如果M&lt; N,那么你将无法以100%的准确率解决问题。
答案 8 :(得分:0)
首先,这可能是信息理论的原因。我们可以简单地检查数组中的数字是否在O(N)时间和O(1)空间的边界内。要指定任何此类入站数字数组,需要N log N
位信息。但是要指定排列需要大约(N log N) - N
位信息(斯特林的近似值)。因此,如果我们可以在测试期间获取N
位信息,我们可能能够知道答案。这在N
时间内很简单(事实上,对于M
静态空间,我们可以非常轻松地获取每个步骤的log M
个信息,并且在特殊情况下我们可以获得log N
信息)。
另一方面,我们只能在我们的静态存储空间中存储类似M log N
位的信息,这可能远小于N
,所以它大大取决于它的形状决策面在“排列”和“不”之间。
我认为这几乎可能但不完全给出问题设置。我认为一个人“应该”使用循环技巧(如Iulian提到的链接),但是有一个尾巴的关键假设在这里失败,因为你可以索引最后一个元素带排列的数组。
答案 9 :(得分:0)
总和和产品不能保证正确答案,因为这些哈希值会发生冲突,即不同的输入可能会产生相同的结果。如果你想要一个完美的哈希,一个实际完全描述数组数字组成的单数结果,它可能如下。
想象一下,对于i
范围内的任何数字[1, N]
,您都可以生成唯一素数P(i)
(例如,P(i)
是第i个素数)。现在您需要做的就是计算数组中所有数字的所有P(i)
的乘积。该产品将完全无误地描述数组的组成,无论其中的值的排序如何。您需要做的就是预先计算“完美”值(对于置换)并将其与给定输入的结果进行比较:)
当然,这样的算法并不能立即满足发布的要求。但与此同时,它直观地过于通用:它允许您检测数组中绝对任何数值组合的排列。在您的情况下,您需要检测特定组合1, 2, ..., N
的排列。也许这可以用某种方式来简化事情......可能不会。
答案 10 :(得分:0)
好吧,这是不同的,但似乎有效!
我运行了这个测试程序(C#):
static void Main(string[] args) {
for (int j = 3; j < 100; j++) {
int x = 0;
for (int i = 1; i <= j; i++) {
x ^= i;
}
Console.WriteLine("j: " + j + "\tx: " + x + "\tj%4: " + (j % 4));
}
}
简短说明:x是单个列表的所有XOR的结果,i是特定列表中的元素,j是列表的大小。由于我所做的只是异或,所以元素的顺序无关紧要。但我正在研究在应用这种情况时正确的排列是什么样的。
如果你看一下j%4,你可以对这个值进行切换,得到类似的结果:
bool IsPermutation = false;
switch (j % 4) {
case 0:
IsPermutation = (x == j);
break;
case 1:
IsPermutation = (x == 1);
break;
case 2:
IsPermutation = (x == j + 1);
break;
case 3:
IsPermutation = (x == 0);
break;
}
现在我承认这可能需要一些微调。这不是100%,但它是一个很容易的入门方式。也许在整个XOR循环中运行一些小检查,这可能是完美的。尝试从那里开始。
答案 11 :(得分:0)
它看起来像是要求在堆栈机器中找到重复的数组。
听起来不可能知道堆栈的完整历史记录,而你提取每个数字并且对所取出的数字知之甚少。
答案 12 :(得分:0)
这是证明无法完成:
假设有一些技巧,除了最后一个细胞外,你没有发现任何重复。然后问题就减少到检查最后一个单元格是否包含重复项。
如果到目前为止您的问题状态的无结构化表示,那么对于每个单元格,您将简化为对整个先前输入执行线性搜索。通过二次时间算法很容易看出它是如何离开你的。
现在,假设通过一些聪明的数据结构,您实际上知道您希望最后看到哪个数字。那么知识肯定需要至少足够的位来存储你寻找的数字 - 也许是一个存储单元?但是有一个倒数第二个数字和倒数第二个子问题:那么你必须同样代表一组两个可能尚未被看到的数字。这肯定需要比仅对剩余数字编码更多的存储空间。通过类似论证的进展,国家的规模必须随问题的大小而增长,除非你愿意接受二次时最坏情况。
这是时空权衡。您可以拥有二次时间和常数空间,或线性时间和线性空间。你不能拥有线性时间和恒定空间。
答案 13 :(得分:0)
查看以下解决方案。它使用O(1)附加空间。 它在检查过程中改变数组,但最后将其返回到初始状态。
这个想法是:
按顺序查看数字(现在确保所有数字都在[1,n]范围内),并且对于每个数字x(例如3):
这样,我们确信所有元素都在[1,n]范围内,并且没有重复项=&gt;数组是一个排列。
int is_permutation_linear(int a[], int n) {
int i, is_permutation = 1;
// Step 1.
for (i = 0; i < n; ++i) {
if (a[i] < 1 || a[i] > n) {
return 0;
}
}
// Step 2.
for (i = 0; i < n; ++i) {
if (a[abs(a[i]) - 1] < 0) {
is_permutation = 0;
break;
}
a[i] *= -1;
}
// Step 3.
for (i = 0; i < n; ++i) {
if (a[i] < 0) {
a[i] *= -1;
}
}
return is_permutation;
}
以下是测试它的完整程序:
/*
* is_permutation_linear.c
*
* Created on: Dec 27, 2011
* Author: Anis
*/
#include <stdio.h>
int abs(int x) {
return x >= 0 ? x : -x;
}
int is_permutation_linear(int a[], int n) {
int i, is_permutation = 1;
for (i = 0; i < n; ++i) {
if (a[i] < 1 || a[i] > n) {
return 0;
}
}
for (i = 0; i < n; ++i) {
if (a[abs(a[i]) - 1] < 0) {
is_permutation = 0;
break;
}
a[abs(a[i]) - 1] *= -1;
}
for (i = 0; i < n; ++i) {
if (a[i] < 0) {
a[i] *= -1;
}
}
return is_permutation;
}
void print_array(int a[], int n) {
int i;
for (i = 0; i < n; i++) {
printf("%2d ", a[i]);
}
}
int main() {
int arrays[9][8] = { { 1, 2, 3, 4, 5, 6, 7, 8 },
{ 8, 6, 7, 2, 5, 4, 1, 3 },
{ 0, 1, 2, 3, 4, 5, 6, 7 },
{ 1, 1, 2, 3, 4, 5, 6, 7 },
{ 8, 7, 6, 5, 4, 3, 2, 1 },
{ 3, 5, 1, 6, 8, 4, 7, 2 },
{ 8, 3, 2, 1, 4, 5, 6, 7 },
{ 1, 1, 1, 1, 1, 1, 1, 1 },
{ 1, 8, 4, 2, 1, 3, 5, 6 } };
int i;
for (i = 0; i < 9; i++) {
printf("array: ");
print_array(arrays[i], 8);
printf("is %spermutation.\n",
is_permutation_linear(arrays[i], 8) ? "" : "not ");
printf("after: ");
print_array(arrays[i], 8);
printf("\n\n");
}
return 0;
}
及其输出:
array: 1 2 3 4 5 6 7 8 is permutation.
after: 1 2 3 4 5 6 7 8
array: 8 6 7 2 5 4 1 3 is permutation.
after: 8 6 7 2 5 4 1 3
array: 0 1 2 3 4 5 6 7 is not permutation.
after: 0 1 2 3 4 5 6 7
array: 1 1 2 3 4 5 6 7 is not permutation.
after: 1 1 2 3 4 5 6 7
array: 8 7 6 5 4 3 2 1 is permutation.
after: 8 7 6 5 4 3 2 1
array: 3 5 1 6 8 4 7 2 is permutation.
after: 3 5 1 6 8 4 7 2
array: 8 3 2 1 4 5 6 7 is permutation.
after: 8 3 2 1 4 5 6 7
array: 1 1 1 1 1 1 1 1 is not permutation.
after: 1 1 1 1 1 1 1 1
array: 1 8 4 2 1 3 5 6 is not permutation.
after: 1 8 4 2 1 3 5 6
答案 14 :(得分:0)
下面的Java解决方案部分回答了问题。我相信时间复杂度是O(n)。 (这种信念基于解决方案不包含嵌套循环的事实。)关于内存 - 不确定。问题首先出现在谷歌的相关请求中,因此它可能对某些人有用。
public static boolean isPermutation(int[] array) {
boolean result = true;
array = removeDuplicates(array);
int startValue = 1;
for (int i = 0; i < array.length; i++) {
if (startValue + i != array[i]){
return false;
}
}
return result;
}
public static int[] removeDuplicates(int[] input){
Arrays.sort(input);
List<Integer> result = new ArrayList<Integer>();
int current = input[0];
boolean found = false;
for (int i = 0; i < input.length; i++) {
if (current == input[i] && !found) {
found = true;
} else if (current != input[i]) {
result.add(current);
current = input[i];
found = false;
}
}
result.add(current);
int[] array = new int[result.size()];
for (int i = 0; i < array.length ; i ++){
array[i] = result.get(i);
}
return array;
}
public static void main (String ... args){
int[] input = new int[] { 4,2,3,4,1};
System.out.println(isPermutation(input));
//output true
input = new int[] { 4,2,4,1};
System.out.println(isPermutation(input));
//output false
}
答案 15 :(得分:0)
int solution(int A[], int N) {
int i,j,count=0, d=0, temp=0,max;
for(i=0;i<N-1;i++) {
for(j=0;j<N-i-1;j++) {
if(A[j]>A[j+1]) {
temp = A[j+1];
A[j+1] = A[j];
A[j] = temp;
}
}
}
max = A[N-1];
for(i=N-1;i>=0;i--) {
if(A[i]==max) {
count++;
}
else {
d++;
}
max = max-1;
}
if(d!=0) {
return 0;
}
else {
return 1;
}
}