确定数组是否包含n ... n + m的算法?

时间:2008-10-07 03:19:02

标签: arrays algorithm big-o puzzle

我在Reddit上看到了这个问题,并没有提出正面的解决方案,我认为这将是一个完美的问题。这是关于面试问题的一个主题:

  

编写一个采用大小为m的int数组的方法,如果数组由数字n ... n + m-1组成,则返回(True / False),该范围内的所有数字以及该范围内的数字。不保证数组已排序。 (例如,{2,3,4}将返回true。{1,3,1}将返回false,{1,2,4}将返回false。

     

我遇到的问题是我的采访者一直要求我优化(更快的O(n),更少的内存等),他声称你可以在一次使用数组中使用恒定的记忆量。从未想过那个。

除了您的解决方案外,请说明他们是否认为该阵列包含唯一的项目。同时指出你的解决方案是否假设序列从1开始。(我已经略微修改了这个问题以允许它变为2,3,4 ......)

编辑:我现在认为,在处理重复的空间算法中不存在线性时间和常数。任何人都可以验证这个吗?

重复问题归结为测试以查看数组是否在O(n)时间,O(1)空间中包含重复项。如果可以这样做,您可以先测试,如果没有重复,则运行算法。那么你可以在O(n)时间O(1)空间中测试欺骗吗?

39 个答案:

答案 0 :(得分:18)

根据假设,不允许少于一个且没有重复的数字,对此有一个简单的求和标识 - 从1m的数字总和,以{{1为增量是1。然后,您可以对数组求和并使用此标识。

您可以查看上述保证是否存在欺骗,加上保证没有数字大于m或小于n(可在(m * (m + 1)) / 2中查看)

伪代码中的想法:
  0)从N = 0开始   1)取列表中的第N个元素   2)如果列表已经排序,如果它不在正确的位置,请检查它应该在哪里   3)如果它应该已经有相同数字的地方,你有一个欺骗 - 返回TRUE
  4)否则,交换数字(将第一个数字放在正确的位置)   5)使用您刚刚交换的号码,它是否在正确的位置?
  6)如果不是,请返回第二步   7)否则,从步骤1开始,N = N + 1.如果这将超过列表的末尾,则表示没有欺骗。

而且,是的,它在O(N)中运行,尽管它可能看起来像O(N)

每个人都注意(从评论中收集的东西)

此解决方案在您可以修改数组的假设下工作,然后使用就地Radix排序(速度达到O(N ^ 2))。

其他的解决方案已被提出,但我不确定是否有任何证据。有一堆可能有用的总和,但是大多数会遇到表示总和所需的位数的爆炸,这将违反恒定的额外空间保证。我也不知道他们中的任何一个是否能够为给定的一组数字产生一个不同的数字。我认为一个正方形可能有效,它有一个已知的计算公式(见Wolfram's

新的洞察力(好吧,更多的思考无助于解决但有趣且我要去睡觉):

因此,有人提到可能使用sum +平方和。没有人知道这是否有效,并且我意识到当(x + y)=(n + m)时它只会成为一个问题,例如事实2 + 2 = 1 + 3. Squares也有这个问题归功于Pythagorean triples(所以3 ^ 2 + 4 ^ 2 + 25 ^ 2 == 5 ^ 2 + 7 ^ 2 + 24 ^ 2,并且平方和不起作用)。如果我们使用Fermat's last theorem,我们知道n ^ 3不会发生这种情况。但我们也不知道是否没有x + y + z = n(除非我们这样做而且我不知道)。因此,不能保证这一点也不会破坏 - 如果我们继续沿着这条路走下去,我们就会很快耗尽。

然而,在我的欢乐中,我忘了你会注意到你可以打破方格的总和,但这样做会创建一个无效的正常总和。我不认为你可以做到这两点,但是,正如已经指出的那样,我们没有任何证据。


我必须说,找到反例有时比证明事情容易得多!考虑以下序列,所有序列的总和为28,平方和为140:

O(N)

我找不到任何长度为6或更短的例子。如果你想要一个具有正确的最小值和最大值的例子,试试这个长度为8的那个:

[1, 2, 3, 4, 5, 6, 7]
[1, 1, 4, 5, 5, 6, 6] 
[2, 2, 3, 3, 4, 7, 7]

更简单的方法(修改hazzen的想法):

长度为m的整数数组包含从n到n + m-1的所有数字,恰好是iff

  • 每个数组元素都在n和n + m-1之间
  • 没有重复

(原因:在给定的整数范围内只有m个值,因此如果数组在此范围内包含m个唯一值,则它必须包含每个值一次)

如果你被允许修改数组,你可以使用hazzen的算法思想的修改版本在列表的一次通过中检查(不需要进行任何求和):

  • 对于从0到m-1的所有数组索引
    1. 如果array [i]< n或array [i]> = n + m =>返回错误(“超出范围的值”)
    2. 计算j = array [i] - n(这是排序数组中数组[i]的从0开始的位置,其值从n到n + m-1)
    3. 虽然j不等于i
      1. 如果list [i]等于list [j] =>返回错误(“重复发现”)
      2. 使用列表[j]
      3. 交换列表[i]
      4. 重新计算j = array [i] - n
  • 返回

我不确定原始数组的修改是否计入O(1)允许的最大额外空间,但如果没有,则应该是原始海报所需的解决方案。

答案 1 :(得分:5)

通过使用a[i] % a.length代替a[i],您可以将问题减少到需要确定您有0a.length - 1的数字。

我们认为这个观察是理所当然的,并尝试检查数组是否包含[0,m)。

找到第一个不在正确位置的节点,例如

0 1 2 3 7 5 6 8 4 ;     the original dataset (after the renaming we discussed)
        ^
        `---this is position 4 and the 7 shouldn't be here

将该数字交换到 所在的位置。即将78交换:

0 1 2 3 8 5 6 7 4 ; 
        |     `--------- 7 is in the right place.
        `--------------- this is now the 'current' position

现在我们重复一遍。再看看我们目前的立场,我们会问:

“这是正确的号码吗?”

  • 如果没有,我们将其交换到正确的位置。
  • 如果它在正确的位置,我们向右移动并再次执行此操作。

再次遵循此规则,我们得到:

0 1 2 3 4 5 6 7 8 ;     4 and 8 were just swapped

这将逐步从左到右正确建立列表,每个数字最多移动一次,因此这是O(n)。

如果存在欺骗行为,我们会立即注意到它是否会尝试在列表中交换号码backwards

答案 2 :(得分:2)

任何一次通过算法都需要Omega(n)位存储。

相反,假设存在使用o(n)位的一次通过算法。因为它只进行一次传递,所以它必须总结o(n)空间中的前n / 2个值。由于存在从S = {1,...,n}得出的C(n,n / 2)= 2 ^ Theta(n)个可能的n / 2个值集,因此存在两个不同的集合A和B的n / 2个值使得两者之后的存储器状态相同。如果A'= S \ A是补充A的“正确”值集,则算法无法正确回答输入

A A' - 是

B A' - 没有

因为它无法区分第一种情况和第二种情况。

Q.E.D。

答案 3 :(得分:2)

为什么其他解决方案使用每个值的总和?我认为这是有风险的,因为当你将O(n)项加在一个数字中时,你在技术上使用的不仅仅是O(1)空间。

更简单的方法:

步骤1,弄清楚是否有重复项。我不确定这是否可能在O(1)空间。无论如何,如果有重复,则返回false。

第2步,遍历列表,跟踪最低最高项。

步骤3,(最高 - 最低)是否等于m?如果是,请返回true。

答案 4 :(得分:1)

如果我错了,请投反对票,但我认为我们可以确定是否有重复或不使用差异。因为我们事先知道平均值(n +(m-1)/ 2或类似的东西)我们可以总结差值的数字和平方来表示总和是否与方程匹配(mn + m(m-1) )/ 2)方差为(0 + 1 + 4 + ... +(m-1)^ 2)/ m。如果方差不匹配,则可能我们有重复。

编辑:方差应该是(0 + 1 + 4 + ... + [(m-1)/ 2] ^ 2)* 2 / m,因为一半的元素小于平均值,另一半大于平均值。

如果存在重复,则上述等式中的术语将与正确的序列不同,即使另一个副本完全取消了均值的变化。因此,只有当sum和variance都与我们可以预先计算的desrired值匹配时,函数才返回true。

答案 5 :(得分:1)

假设您只知道数组的长度,并且允许修改数组,则可以在O(1)空间和O(n)时间内完成。

该过程有两个简单的步骤。 1.“模数排序”数组。 [5,3,2,4] => [4,5,2,3](O(2n)) 2.检查每个值的邻居是否高于其自身(模数)(O(n))

所有告诉你最多需要通过阵列3次。

模数排序是“棘手”的部分,但目标很简单。获取数组中的每个值并将其存储在自己的地址(模数长度)。这需要一次通过数组,在每个位置上循环“驱逐”其值,方法是将其交换到正确的位置并移动到目的地的值。如果您移动的值与您刚刚驱逐的值一致,则您有一个副本并且可以提前退出。 最坏的情况是,O(2n)。

检查是通过数组的单次传递,检查每个值与它的下一个最高邻居。总是O(n)。

组合算法为O(n)+ O(2n)= O(3n)= O(n)

我的解决方案中的伪代码:

foreach(values[]) 
  while(values[i] not congruent to i)
    to-be-evicted = values[i]
    evict(values[i])   // swap to its 'proper' location
    if(values[i]%length == to-be-evicted%length)
      return false;  // a 'duplicate' arrived when we evicted that number
  end while
end foreach
foreach(values[])
  if((values[i]+1)%length != values[i+1]%length)
    return false
end foreach

我在下面包含了java代码概念证明,它并不漂亮,但是它通过了我为它做的所有单元测试。我将它们称为“StraightArray”,因为它们对应于直线的扑克牌(忽略套装的连续序列)。

public class StraightArray {    
    static int evict(int[] a, int i) {
        int t = a[i];
        a[i] = a[t%a.length];
        a[t%a.length] = t;
        return t;
    }
    static boolean isStraight(int[] values) {
        for(int i = 0; i < values.length; i++) {
            while(values[i]%values.length != i) {
                int evicted = evict(values, i);
                if(evicted%values.length == values[i]%values.length) {
                    return false;
                }
            }
        }
        for(int i = 0; i < values.length-1; i++) {
            int n = (values[i]%values.length)+1;
            int m = values[(i+1)]%values.length;
            if(n != m) {
                return false;
            }
        }
        return true;
    }
}

答案 6 :(得分:1)

这是O(n)

中的工作解决方案

这是使用Hazzen建议的伪代码加上我自己的一些想法。它也适用于负数,不需要任何平方和的东西。

function testArray($nums, $n, $m) {
    // check the sum. PHP offers this array_sum() method, but it's
    // trivial to write your own. O(n) here.
    if (array_sum($nums) != ($m * ($m + 2 * $n - 1) / 2)) {
        return false;    // checksum failed.
    }
    for ($i = 0; $i < $m; ++$i) {
        // check if the number is in the proper range
        if ($nums[$i] < $n || $nums[$i] >= $n + $m) {
            return false;  // value out of range.
        }

        while (($shouldBe = $nums[$i] - $n) != $i) {
            if ($nums[$shouldBe] == $nums[$i]) {
                return false;    // duplicate
            }
            $temp = $nums[$i];
            $nums[$i] = $nums[$shouldBe];
            $nums[$shouldBe] = $temp;
        }
    }
    return true;    // huzzah!
}

var_dump(testArray(array(1, 2, 3, 4, 5), 1, 5));  // true
var_dump(testArray(array(5, 4, 3, 2, 1), 1, 5));  // true
var_dump(testArray(array(6, 4, 3, 2, 0), 1, 5));  // false - out of range
var_dump(testArray(array(5, 5, 3, 2, 1), 1, 5));  // false - checksum fail
var_dump(testArray(array(5, 4, 3, 2, 5), 1, 5));  // false - dupe
var_dump(testArray(array(-2, -1, 0, 1, 2), -2, 5)); // true

答案 7 :(得分:1)

一段时间后,我听到了一位为电话公司工作的人非常聪明的排序算法。他们不得不对大量的电话号码进行排序。在经历了一系列不同的排序策略后,他们终于找到了一个非常优雅的解决方案:他们只是创建了一个位数组,并将偏移量作为电话号码处理到位数组中。然后他们通过一次通过扫描他们的数据库,将每个数字的位更改为1.之后,他们扫过位数组一次,为位置设置为高的条目吐出电话号码。

沿着这些方向,我相信你可以使用数组本身的数据作为元数据结构来查找重复项。最糟糕的情况是,你可以有一个单独的数组,但我很确定你可以使用输入数组,如果你不介意进行一些交换。

我暂时不考虑n参数,b / c只会让事情变得混乱 - 添加索引偏移很容易。

考虑:

for i = 0 to m
  if (a[a[i]]==a[i]) return false; // we have a duplicate
  while (a[a[i]] > a[i]) swapArrayIndexes(a[i], i)
  sum = sum + a[i]
next

if sum = (n+m-1)*m return true else return false

这不是O(n) - 可能更接近于O(n Log n) - 但它确实提供了恒定的空间,并且可能为问题提供不同的攻击向量。

如果我们想要O(n),那么使用一个字节数组和一些位操作将提供复制检查,使用额外的n / 32字节内存(假设当然是32位整数)。

编辑:通过将总和检查添加到循环内部,可以进一步改进上述算法,并检查:

if sum > (n+m-1)*m return false

这样会很快失败。

答案 8 :(得分:1)

答案 9 :(得分:1)

boolean determineContinuousArray(int *arr, int len)
{
    // Suppose the array is like below:
    //int arr[10] = {7,11,14,9,8,100,12,5,13,6};
    //int len = sizeof(arr)/sizeof(int);

    int n = arr[0];

    int *result = new int[len];
    for(int i=0; i< len; i++)
            result[i] = -1;
    for (int i=0; i < len; i++)
    {
            int cur = arr[i];
            int hold ;
            if ( arr[i] < n){
                    n = arr[i];
            }
            while(true){
                    if ( cur - n >= len){
                            cout << "array index out of range: meaning this is not a valid array" << endl;
                            return false;
                    }
                    else if ( result[cur - n] != cur){
                            hold = result[cur - n];
                            result[cur - n] = cur;
                            if (hold == -1) break;
                            cur = hold;

                    }else{
                            cout << "found duplicate number " << cur << endl;
                            return false;
                    }

            }
    }
    cout << "this is a valid array" << endl;
    for(int j=0 ; j< len; j++)
            cout << result[j] << "," ;
    cout << endl;
    return true;
}

答案 10 :(得分:0)

所以有一种算法需要O(n ^ 2),不需要修改输入数组并占用恒定的空间。

首先,假设您知道nm。这是一个线性操作,因此不会增加任何额外的复杂性。接下来,假设存在一个等于n的元素和一个等于n+m-1的元素,其余元素都在[n, n+m)中。鉴于此,我们可以将问题减少为在[0, m)中使用包含元素的数组。

现在,既然我们知道元素受数组大小的限制,我们可以将每个元素视为一个节点,并且只有一个链接指向另一个元素;换句话说,该阵列描述了有向图。在该有向图中,如果没有重复元素,则每个节点属于一个循环,即,在m或更少的步骤中可以从自身到达节点。如果存在重复元素,则存在一个根本无法从其自身访问的节点。

因此,要检测到这一点,您可以从头到尾遍历整个数组,并确定每个元素是否以<=m步骤返回自身。如果在<=m步骤中无法访问任何元素,那么您有一个副本并且可以返回false。否则,当您访问完所有元素后,您可以返回true:

for (int start_index= 0; start_index<m; ++start_index)
{
    int steps= 1;
    int current_element_index= arr[start_index];
    while (steps<m+1 && current_element_index!=start_index)
    {
        current_element_index= arr[current_element_index];
        ++steps;
    }

    if (steps>m)
    {
        return false;
    }
}

return true;

您可以通过存储其他信息来优化此项:

  1. 记录每个元素的循环长度总和,除非循环访问该元素之前的元素,称之为sum_of_steps
  2. 对于每个元素,只有步骤m-sum_of_steps节点输出。如果你没有返回起始元素并且你没有在起始元素之前访问元素,那么你找到了一个包含重复元素的循环,并且可以返回false
  3. 这仍然是O(n ^ 2),例如{1, 2, 3, 0, 5, 6, 7, 4},但速度要快一点。

答案 11 :(得分:0)

糟糕!我陷入了一个重复的问题,并没有在这里看到已经相同的解决方案。我以为我终于做了一些原创的东西!这是一个我稍微高兴的历史档案:


嗯,我不确定这个算法是否满足所有条件。事实上,我甚至没有验证它是否适用于我尝试过的几个测试用例。即使我的算法确实存在问题,希望我的方法能够引发一些解决方案。

据我所知,这个算法在常量内存中工作并扫描数组三次。如果这不是原始问题的一部分,或许还有一个额外的好处就是它适用于整个整数范围。

我不是一个伪代码人,我真的认为代码可能比单词更有意义。这是我在PHP中编写的一个实现。注意评论。

function is_permutation($ints) {

  /* Gather some meta-data. These scans can
     be done simultaneously */
  $lowest = min($ints);
  $length = count($ints);

  $max_index = $length - 1;

  $sort_run_count = 0;

  /* I do not have any proof that running this sort twice
     will always completely sort the array (of course only
     intentionally happening if the array is a permutation) */

  while ($sort_run_count < 2) {

    for ($i = 0; $i < $length; ++$i) {

      $dest_index = $ints[$i] - $lowest;

      if ($i == $dest_index) {
        continue;
      }

      if ($dest_index > $max_index) {
        return false;
      }

      if ($ints[$i] == $ints[$dest_index]) {
        return false;
      }

      $temp = $ints[$dest_index];
      $ints[$dest_index] = $ints[$i];
      $ints[$i] = $temp;

    }

    ++$sort_run_count;

  }

  return true;

}

答案 12 :(得分:0)

我喜欢Greg Hewgill关于Radix排序的想法。要查找重复项,可以在给定此数组中值的约束的情况下在O(N)时间内进行排序。

对于恢复列表原始排序的就地O(1)空间O(N)时间,您不必对该数字进行实际交换;你可以用旗帜标记它:

//Java: assumes all numbers in arr > 1
boolean checkArrayConsecutiveRange(int[] arr) {

// find min/max
int min = arr[0]; int max = arr[0]
for (int i=1; i<arr.length; i++) {
    min = (arr[i] < min ? arr[i] : min);
    max = (arr[i] > max ? arr[i] : max);
}
if (max-min != arr.length) return false;

// flag and check
boolean ret = true;
for (int i=0; i<arr.length; i++) {
    int targetI = Math.abs(arr[i])-min;
    if (arr[targetI] < 0) {
        ret = false; 
        break;
    }
    arr[targetI] = -arr[targetI];
}
for (int i=0; i<arr.length; i++) {
    arr[i] = Math.abs(arr[i]);
}

return ret;
}

将标志存储在给定数组中是一种欺骗,并不能很好地进行并行化。我仍然试图想办法在没有触及O(N)时间和O(log N)空间的数组的情况下这样做。检查总和和最小二乘的总和(arr [i] - arr.length / 2.0)^ 2感觉它可能有效。我们知道的一个没有重复的0 ... m数组的特征是它是均匀分布的;我们应该检查一下。

现在,只要我能证明它。

我想注意上面涉及阶乘的解决方案需要O(N)空间来存储阶乘本身。 N! &GT; 2 ^ N,需要N个字节来存储。

答案 13 :(得分:0)

def test(a, n, m):
    seen = [False] * m
    for x in a:
        if x < n or x >= n+m:
            return False
        if seen[x-n]:
            return False
        seen[x-n] = True
    return False not in seen

print test([2, 3, 1], 1, 3)
print test([1, 3, 1], 1, 3)
print test([1, 2, 4], 1, 3)

请注意,这只会使一次通过第一个数组,而不考虑not in中涉及的线性搜索。 :)

我也可以使用python set,但我选择了直接的解决方案,其中set的性能特征不需要考虑。

更新:Smashery指出我误解了“恒定的内存量”,这个解决方案实际上并没有解决问题。

答案 14 :(得分:0)

任何连续数组[n,n + 1,...,n + m-1]都可以使用模运算符映射到'基数'区间[0,1,...,m]。对于区间中的每个i,基本区间中只有一个i%m,反之亦然。

任何连续数组的“span”m(最大值 - 最小值+ 1)等于它的大小。

使用这些事实,您可以创建一个相同大小的“遇到”布尔数组,其中包含最初的所有错误,并且在访问输入数组时,将其相关的“遇到”元素设置为true。

该算法在空间中为O(n),在时间上为O(n),并检查重复。

def contiguous( values )
    #initialization
    encountered = Array.new( values.size, false )
    min, max = nil, nil
    visited = 0

    values.each do |v|

        index = v % encountered.size

        if( encountered[ index ] )
            return "duplicates"; 
        end

        encountered[ index ] = true
        min = v if min == nil or v < min
        max = v if max == nil or v > max 
        visited += 1
    end

    if ( max - min + 1 != values.size ) or visited != values.size
        return "hole"
    else
        return "contiguous"
    end

end

tests = [ 
[ false, [ 2,4,5,6 ] ], 
[ false, [ 10,11,13,14 ] ] , 
[ true , [ 20,21,22,23 ] ] , 
[ true , [ 19,20,21,22,23 ] ] ,
[ true , [ 20,21,22,23,24 ] ] ,
[ false, [ 20,21,22,23,24+5 ] ] ,
[ false, [ 2,2,3,4,5 ] ]
]

tests.each do |t|
    result = contiguous( t[1] )
    if( t[0] != ( result == "contiguous" ) )
        puts "Failed Test : " + t[1].to_s + " returned " + result
    end
end

答案 15 :(得分:0)

我建议如下:

选择有限质数数P_1,P_2,...,P_K,并计算每个P_i的输入序列中元素的出现次数(减去最小值)。有效序列的模式是已知的。

例如,对于17个元素的序列,模2,我们必须有简档:[9 8],模3:[6 6 5],模5:[4 4 3 3 3]等。

使用几个碱基组合测试,我们获得了越来越精确的概率测试。由于条目以整数大小为界,因此存在提供精确测试的有限基数。这类似于概率伪素性测试。

S_i is an int array of size P_i, initially filled with 0, i=1..K
M is the length of the input sequence
Mn = INT_MAX
Mx = INT_MIN

for x in the input sequence:
  for i in 1..K: S_i[x % P_i]++  // count occurrences mod Pi
  Mn = min(Mn,x)  // update min
  Mx = max(Mx,x)  // and max

if Mx-Mn != M-1: return False  // Check bounds

for i in 1..K:
  // Check profile mod P_i
  Q = M / P_i
  R = M % P_i
  Check S_i[(Mn+j) % P_i] is Q+1 for j=0..R-1 and Q for j=R..P_i-1
  if this test fails, return False

return True

答案 16 :(得分:0)

m个连续数的乘积可被m整除! [m factorial]


所以在一次通过中你可以计算m个数的乘积,也可以计算m!看看产品是否模数为m!在传球结束时为零

我可能会遗漏一些东西,但这就是我想到的......

在python中

类似的东西

my_list1 = [9,5,8,7,6]

my_list2 = [3,5,4,7]

def continuous(my_list):

count = 0
prod = fact = 1
for num in my_list:
    prod *= num
    count +=1 
    fact *= count
if not prod % fact: 
    return 1   
else:   
    return 0 

打印连续(my_list1)

打印连续(my_list2)


HotPotato~ $ python m_consecutive.py

1

0

答案 17 :(得分:0)

一个数组包含N个数字,并且您要确定是否有两个 数字总和为给定数字K.例如,如果输入为8,4,1,6和K为10, 答案是肯定的(4和6)。一个数字可以使用两次。请执行下列操作。 一个。给出一个O(N2)算法来解决这个问题。 湾给出O(N log N)算法来解决这个问题。 (提示:首先对项目进行排序。 这样做之后,您可以在线性时间内解决问题。) C。对两种解决方案进行编码并比较算法的运行时间。 4。

答案 18 :(得分:0)

如果数组未排序,则“nickf”dows的答案不起作用 var_dump(testArray(array(5,3,1,2,4),1,5)); //给出“重复”!!!!

此外,计算和([n ... n + m-1])的公式看起来不正确.... 正确的公式是(m(m + 1)/ 2-n(n-1)/ 2)

答案 19 :(得分:0)

这是O(N)时间和O(1)额外空间的解决方案,用于查找重复项: -

public static boolean check_range(int arr[],int n,int m) {

        for(int i=0;i<m;i++) {
            arr[i] = arr[i] - n;
            if(arr[i]>=m)
                return(false);
        }

        System.out.println("In range");

        int j=0;
        while(j<m) {
            System.out.println(j);
            if(arr[j]<m) {

                if(arr[arr[j]]<m) {

                    int t = arr[arr[j]];
                    arr[arr[j]] = arr[j] + m;
                    arr[j] = t;
                    if(j==arr[j]) {

                        arr[j] = arr[j] + m;
                        j++;
                    }

                }

                else return(false);

            }

            else j++;

        }

说明: -

  
      
  1. 通过arr [i] = arr [i] - n将数字带到范围(0,m-1),如果超出范围则返回false。
  2.   
  3. 每次检查是否arr [arr [i]]未被占用,其值是否小于m
  4.   
  5. 如果是这样交换(arr [i],arr [arr [i]])和arr [arr [i]] = arr [arr [i]] + m表示它被占用
  6.   
  7. 如果arr [j] = j并且只需添加m并增加j
  8.   
  9. 如果arr [arr [j]]&gt; = m表示它已被占用,因此当前值重复,因此返回false。
  10.   
  11. 如果arr [j]&gt; = m则跳过
  12.   

答案 20 :(得分:0)

如果数字为正数,则有一个简单的解决方案,可以一次性完成 O(1) 空间:

int max = arr[0];
int min = arr[0];
for (int i = 0; i < n; i++) {
    int x = abs(arr[i]);
    int y = x % n;
    if (arr[y] < 0)
        return false;
    arr[y] = -arr[y];
    if (x > max)
        max = x;
    else if (x < min)
        min = x;
}
return max - min == n - 1;

答案 21 :(得分:0)

在Python中:

def ispermutation(iterable, m, n):
    """Whether iterable and the range [n, n+m) have the same elements.

       pre-condition: there are no duplicates in the iterable
    """ 
    for i, elem in enumerate(iterable):
        if not n <= elem < n+m:
            return False

    return i == m-1

print(ispermutation([1, 42], 2, 1)    == False)
print(ispermutation(range(10), 10, 0) == True)
print(ispermutation((2, 1, 3), 3, 1)  == True)
print(ispermutation((2, 1, 3), 3, 0)  == False)
print(ispermutation((2, 1, 3), 4, 1)  == False)
print(ispermutation((2, 1, 3), 2, 1)  == False)

时间为O(m),空间为O(1)。 会考虑重复项。

替代解决方案:

def ispermutation(iterable, m, n): 
    """Same as above.

    pre-condition: assert(len(list(iterable)) == m)
    """
    return all(n <= elem < n+m for elem in iterable)

答案 22 :(得分:0)

Kent Fredric's Ruby solution

的C版本

(便于测试)

反例(C版):{8,33,27,30,9,2,35,7,26,32,2,23,0,13,1,6,31,3,28 ,4,5,18,12,2,9,14,11,21,19,22,15,20,24,11,10,16,25}。这里n = 0,m = 35。此序列未命中34并且有两个2

时空中的O(m)和空间解中的O(1)。

超出范围的值很容易在O(n)中及时在空间中检测到O(1),因此测试集中在范围内(意味着所有值都在有效范围[n, n+m))序列。否则 {1, 34}是一个反例(对于C版,sizeof(int)== 4,数字的标准二进制表示)。

C和Ruby版本的主要区别:   由于有限的sizeof(int),<<运算符将在C中旋转值,   但在Ruby中,数字会增长以适应结果,例如,

Ruby:1 << 100 # -> 1267650600228229401496703205376

C:int n = 100; 1 << n // -> 16

在Ruby中:check_index ^= 1 << i;相当于check_index.setbit(i)。可以在C ++中实现相同的效果:vector<bool> v(m); v[i] = true;

bool isperm_fredric(int m; int a[m], int m, int n)
{
  /**
     O(m) in time (single pass), O(1) in space,
     no restriction on n,
     ?overflow?
     a[] may be readonly
   */
  int check_index = 0;
  int check_value = 0;

  int min = a[0];
  for (int i = 0; i < m; ++i) {

    check_index ^= 1 << i;
    check_value ^= 1 << (a[i] - n); //

    if (a[i] < min)
      min = a[i];
  }
  check_index <<= min - n; // min and n may differ e.g., 
                           //  {1, 1}: min=1, but n may be 0.
  return check_index == check_value;
}

根据以下代码测试上述函数的值:

bool *seen_isperm_trusted  = NULL;
bool isperm_trusted(int m; int a[m], int m, int n)
{
  /** O(m) in time, O(m) in space */

  for (int i = 0; i < m; ++i) // could be memset(s_i_t, 0, m*sizeof(*s_i_t));
    seen_isperm_trusted[i] = false;

  for (int i = 0; i < m; ++i) {

    if (a[i] < n or a[i] >= n + m)
      return false; // out of range

    if (seen_isperm_trusted[a[i]-n])
      return false; // duplicates
    else
      seen_isperm_trusted[a[i]-n] = true;
  }

  return true; // a[] is a permutation of the range: [n, n+m)
}

使用以下命令生成输入数组:

void backtrack(int m; int a[m], int m, int nitems)
{
  /** generate all permutations with repetition for the range [0, m) */
  if (nitems == m) {
    (void)test_array(a, nitems, 0); // {0, 0}, {0, 1}, {1, 0}, {1, 1}
  }
  else for (int i = 0; i < m; ++i) {
      a[nitems] = i;
      backtrack(a, m, nitems + 1);
    }
}

答案 23 :(得分:0)

我当前的最佳选择

def uniqueSet( array )
  check_index = 0; 
  check_value = 0; 
  min = array[0];
  array.each_with_index{ |value,index|
         check_index = check_index ^ ( 1 << index );
         check_value = check_value ^ ( 1 << value );
         min = value if value < min
  } 
  check_index =  check_index  << min;
  return check_index == check_value; 
end

O(n)和空间O(1)

我写了一个脚本来暴力组合,可能会失败,但没有找到任何。 如果你有一个违反这个功能的阵列,请告诉我。 :)


@ J.F。塞巴斯蒂安

它不是真正的哈希算法。从技术上讲,它是一个“看到”值的高效打包布尔数组。

ci = 0, cv = 0
[5,4,3]{ 
  i = 0 
  v = 5 
  1 << 0 == 000001
  1 << 5 == 100000
  0 ^ 000001  = 000001
  0 ^ 100000  = 100000

  i = 1
  v = 4 
  1 << 1 == 000010
  1 << 4 == 010000
  000001 ^ 000010  = 000011
  100000 ^ 010000  = 110000 

  i = 2
  v = 3 
  1 << 2 == 000100
  1 << 3 == 001000
  000011 ^ 000100  = 000111
  110000 ^ 001000  = 111000 
}
min = 3 
000111 << 3 == 111000
111000 === 111000

这一点主要是为了“假冒”大多数问题案例,人们使用重复这样做。在这个系统中,XOR惩罚你使用相同的值两次并假设你做了0次。

这里的警告当然是:

  1. 输入数组长度和最大数组值均受$x
  2. ( 1 << $x > 0 )的最大值限制
  3. 最终效果取决于您的基础系统如何实现以下功能:

    1. 向右移动1位n。
    2. xor 2个寄存器。 (其中'寄存器'可能,取决于实现,跨越几个寄存器)
    3. 修改 注意,上述陈述似乎令人困惑。假设一个完美的机器,其中“整数”是一个无限精度的寄存器,它仍然可以在O(1)时间内执行^ b。

      但是如果没有这些假设,就必须开始询问简单数学的算法复杂性。

      • 1 == 1有多复杂?,每次都应该是O(1)吗?
      • 2 ^ 32 == 2 ^ 32怎么样?
      • O(1)? 2 ^ 33 == 2 ^ 33?现在您已经了解了寄存器大小和底层实现的问题。
      • 幸运的是,XOR和==可以并行完成,因此如果假定无限精度和设计用于处理无限精度的机器,则可以安全地假设XOR和==无论其值如何都需要恒定时间(因为它的无限宽度,它将具有无限的0填充。显然这不存在。但是,将000000更改为000100并不会增加内存使用量。
      • 然而在某些机器上,(1 <&lt; 32)&lt;&lt; 1 消耗更多内存,但多少是不确定的。

答案 24 :(得分:0)

注意:此评论基于问题的原始文本(自此以来已经更正)

如果问题是完全如上所述(并且它不仅仅是一个错字),对于大小为n的数组,如果数组由数字组成,则函数应该返回(True / False) 1 ... n + 1,

...那么答案总是假的,因为所有数字1 ... n + 1的数组的大小为n + 1而不是n。因此问题可以在O(1)中回答。 :)

答案 25 :(得分:0)

  

为什么其他解决方案使用每个值的总和?我认为这是有风险的,因为当你将O(n)项加在一个数字中时,你在技术上使用的不仅仅是O(1)空间。

O(1)表示恒定的空间,它不会改变n的数量。只要它是一个常数,它是1还是2变量并不重要。你为什么说它超过O(1)空间?如果你通过在一个临时变量中累计来计算n个数的总和,那么无论如何你都会使用正好一个变量。

评论答案,因为系统不允许我写评论。

更新(回复评论):在这个答案中,我的意思是O(1)空格,只要省略“空格”或“时间”。引用的文本是早期答案的一部分,这是对此的回复。

答案 26 :(得分:0)

如果你想知道数字[n ... n + m - 1]的总和,只需使用这个等式。

var sum = m * (m + 2 * n - 1) / 2;

即使n是小数,也适用于任何正数或负数。

答案 27 :(得分:0)

鉴于此 -

  

编写一个采用大小为m的 int数组的方法...

我认为可以得出结论,m的上限等于最大 int 的值(典型值为2 ^ 32)。 换句话说,即使m未被指定为int,数组也不能有重复这一事实意味着不能超过32位可以形成的值的数量,这反过来又是暗示m也限制为int。

如果这样的结论是可以接受的,那么我建议使用(2 ^ 33 + 2)* 4字节= 34,359,738,376字节= 34.4GB的固定空间来处理所有可能的情况。 (不计算输入数组及其循环所需的空间)。

当然,为了优化,我首先考虑m,并仅分配所需的实际数量,(2m + 2)* 4字节。

如果这对于O(1)空间约束 - 对于所述问题是可接受的 - 那么让我继续进行算法提议......:)

假设:m int的数组,正数或负数,不大于4个字节可容纳的数组。处理重复。第一个值可以是任何有效的int。如上限制m。

首先,创建一个长度为2m-1, ary 的int数组,并提供三个int变量: left diff 正确。请注意,使2m + 2 ...

第二个,从输入数组中取出第一个值并将其复制到新数组中的位置m-1。初始化三个变量。

  • 设置 ary [m-1] - nthVal // n = 0
  • 设置 = 差异 = 正确 = 0

Third ,遍历输入数组中的其余值,并为每次迭代执行以下操作:

  • 设置差异 = nthVal - ary [m-1]
  • if(差异&gt; m-1 + || 差异&lt; 1-m + )return false //越界
  • if( ary [m-1 + diff ]!= null)return false // duplicate
  • 设置 ary [m-1 + 差异] = nthVal
  • if( diff &gt; left left = diff //约束左边界 LI>
  • if( diff &lt; right right = diff //约束右边界更远 LI>

我决定把它放在代码中,它起作用了。

以下是使用C#的工作示例:

public class Program
{
    static bool puzzle(int[] inAry)
    {
        var m = inAry.Count();
        var outAry = new int?[2 * m - 1];
        int diff = 0;
        int left = 0;
        int right = 0;
        outAry[m - 1] = inAry[0];
        for (var i = 1; i < m; i += 1)
        {
            diff = inAry[i] - inAry[0];
            if (diff > m - 1 + right || diff < 1 - m + left) return false;
            if (outAry[m - 1 + diff] != null) return false;
            outAry[m - 1 + diff] = inAry[i];
            if (diff > left) left = diff;
            if (diff < right) right = diff;
        }
        return true;
    }

    static void Main(string[] args)
    {
        var inAry = new int[3]{ 2, 3, 4 };
        Console.WriteLine(puzzle(inAry));
        inAry = new int[13] { -3, 5, -1, -2, 9, 8, 2, 3, 0, 6, 4, 7, 1 };
        Console.WriteLine(puzzle(inAry));
        inAry = new int[3] { 21, 31, 41 };
        Console.WriteLine(puzzle(inAry));
        Console.ReadLine();
    }

}

答案 28 :(得分:0)

ciphwn没错。这与统计数据有关。从统计学的角度来看,问题在于数字序列是否形成离散的均匀分布。离散均匀分布是有限的一组可能值的所有值同等可能的地方。幸运的是,有一些有用的公式可以确定离散集是否一致。首先,确定集合的平均值(a..b)是(a + b)/ 2,方差是(n.n-1)/ 12。接下来,确定给定集的方差:

variance = sum [i=1..n] (f(i)-mean).(f(i)-mean)/n

然后与预期的方差进行比较。这将需要对数据进行两次传递,一次确定均值并再次计算方差。

参考文献:

答案 29 :(得分:0)

XOR algorithm的反例。

(不能将其作为评论发布)

@popopome

对于a = {0, 2, 7, 5,},它返回true(表示a是范围[0, 4)的排列),但在这种情况下它必须返回falsea显然不是[0, 4))的常规。

另一个反例:{0, 0, 1, 3, 5, 6, 6} - 所有值都在范围内,但有重复。

我可能错误地实现了popopome的想法(或测试),因此这里是代码:

bool isperm_popopome(int m; int a[m], int m, int  n)
{
  /** O(m) in time (single pass), O(1) in space,
      no restrictions on n,
      no overflow,
      a[] may be readonly
  */
  int even_xor = 0;
  int odd_xor  = 0;

  for (int i = 0; i < m; ++i)
    {
      if (a[i] % 2 == 0) // is even
        even_xor ^= a[i];
      else
        odd_xor ^= a[i];

      const int b = i + n;
      if (b % 2 == 0)    // is even
        even_xor ^= b;
      else
        odd_xor ^= b;
    }

  return (even_xor == 0) && (odd_xor == 0);
}

答案 30 :(得分:0)

b3's pseudo-code

的C版本

(以避免误解伪代码)

反例:{1, 1, 2, 4, 6, 7, 7}

int pow_minus_one(int power)
{
  return (power % 2 == 0) ? 1 : -1;
}

int ceil_half(int n)
{
  return n / 2 + (n % 2);
}

bool isperm_b3_3(int m; int a[m], int m, int n)
{
  /**
     O(m) in time (single pass), O(1) in space,
     doesn't use n
     possible overflow in sum
     a[] may be readonly
   */
  int altsum = 0;
  int mina = INT_MAX;
  int maxa = INT_MIN;

  for (int i = 0; i < m; ++i)
    {
      const int v = a[i] - n + 1; // [n, n+m-1] -> [1, m] to deal with n=0
      if (mina > v)
        mina = v;
      if (maxa < v)
        maxa = v;

      altsum += pow_minus_one(v) * v;
    }
  return ((maxa-mina == m-1)
          and ((pow_minus_one(mina + m-1) * ceil_half(mina + m-1)
                - pow_minus_one(mina-1) * ceil_half(mina-1)) == altsum));
}

答案 31 :(得分:-1)

如何分别使用偶数和奇数的XOR。 考虑位级别而不是整数值本身。

bool is_same(const int* a, const int* b, int len)
{
    int even_xor = 0; 
    int odd_xor = 0;

    for(int i=0;i<len;++i)
    {
        if(a[i] & 0x01) odd_xor ^= a[i];
        else even_xor ^= a[i];

        if(b[i] & 0x01) odd_xor ^= b[i];
        else even_xor ^= b[i];
    }

    return (even_xor == 0) && (odd_xor == 0);
}

答案 32 :(得分:-1)

我不认为我在原来的帖子里解释过自己(在实线下面)。例如,对于[1 2 3 4 5]的输入,算法计算总和:

-1 + 2 - 3 + 4 - 5 

应该等于

-1^5 * ceil(5/2)

下面的伪代码显示了如何检查未从1开始的向量。 该算法处理输入向量未排序和/或包含重复项的情况。


以下算法通过计算向量元素的交替总和来解决问题:

-1 + 2 - 3 + 4 - 5 + .... + m = (-1)^m * ceil(m/2)

其中 ceil 向上舍入到最接近的整数。换句话说,从运行总计中减去奇数,并将偶数添加到其中。

function test(data, m)
    altSum = 0
    n = Inf
    mCheck = -Inf
    for ii = 1:m
    {
        if data(ii) < n
            n = data(ii)
        if data(ii) > mCheck
            mCheck = data(ii)
        altSum = altSum + (-1)^data(ii) * data(ii)
    }
    if ((mCheck-n+1!=m) || (-1)^(n+m-1) * ceil((n+m-1)/2) - ((-1)^(n-1) * ceil((n-1)/2)) != altSum
        return false
    else
        return true

答案 33 :(得分:-1)

时间线性,int m

的空间解是常数

通过利用符号位实现恒定空间。如果int小于m,则可以对任何可变INT_MAX范围执行此操作,即当输入范围[n, n+m)可以转移到[1, m+1)范围时{ {1}}不是积极的。在实践中,如果输入是可变的,前提条件几乎总是正确的。

n

答案 34 :(得分:-1)

Fail := False;
Sum1 := 0;
Sum2 := 0;
TSum1 := 0;
TSum2 := 0;

For i := 1 to m do
  Begin
    TSum1 := TSum1 + i;
    TSum2 := TSum2 + i * i;
    Item := Array[i] - n;
    If (Item < 0) or (Item >= m) then 
      Fail := True
    Else 
      Begin
        Sum1 := Sum1 + Item;
        Sum2 := Sum2 + Item * Item;
      End;
  End;
Fail := Fail Or (Sum1 <> TSum1) or (Sum2 <> TSum2);

累了,没有编译器,但我认为这给了O(m)运行时间,不能被愚弄。

答案 35 :(得分:-1)

如果这是一个拼写错误,问题是所有数字都在1 ... n范围内,那么:

def try_arr(arr):
    n = len(arr)
    return (not any(x<1 or x>n for x in arr)) and sum(arr)==n*(n+1)/2

$ print try_arr([1,2,3])
True

$ print try_arr([1,3,1])
False

$ print try_arr([1,2,4])
False

注意:

  • 我使用的是原始版本中数字从1开始的定义。可以修改确定的代码,以便从另一个数字开始。

  • 如果已知数组(n)的大小,您可以修改它以从例如输入文件流式传输数据,并且几乎不使用内存(sum()中的1个临时变量和当前的1个变量从流中取出的项目)

  • any()是python 2.5中的新功能(但是你有其他方法可以在早期版本的python中表达同样的东西)

  • 它使用O(n)时间O(1)空间。 (更新:我写的它确实说明了重复,但显然这不是真的,正如这里对另一个答案的评论所证明的那样)。

答案 36 :(得分:-1)

我认为这个问题归结为确保

(maximum - minimum + 1) == array_size

这显然可以在O(N)时间和O(1)空间中完成,如下所示:

int check_range(int input[], int N){
    int max = -INFINITY, min = INFINITY, i;
    for(i=0; i<N; i++){
        if(input[i] < min) min=input[i];
        if(input[i] > max) max=input[i];
    }
    return (max - min + 1) == N;
}

请注意,此方法会考虑重复的可能性。 请报告解决方案中的任何差异。

答案 37 :(得分:-1)

似乎我们可以通过将所有数字n ... n + m相乘来检查重复项,然后将该值与序列的预期乘积进行比较,没有重复 m!/(n-1 )!(请注意,这假设序列无法通过预期的总和测试预期的产品测试。)

因此添加到hazzen's pseudo-code,我们有:

is_range(int[] nums, int n, int m) {
  sum_to_m := (m * (m + 1)) / 2
  expected_sum := sum_to_m - (n * (n - 1)) / 2
  real_sum := sum(nums)
  expected_product := m! / (n - 1)!
  real_product := product(nums)
  return ((real_sum == expected_sum) && (expected_product == real_product))


编辑:这是我的Java解决方案,使用平方和来检查重复项。它还通过将序列移动到1来处理任何范围(包括负数)。

// low must be less than high
public boolean isSequence(int[] nums, int low, int high) {
    int shift = 1 - low;
    low += shift;
    high += shift;

    int sum = 0;
    int sumSquares = 0;
    for (int i = 0; i < nums.length; i++) {
        int num = nums[i] + shift;

        if (num < low)
            return false;
        else if (num > high)
            return false;

        sum += num;
        sumSquares += num * num;
    }

    int expectedSum = (high * (high + 1)) / 2;

    if (sum != expectedSum)
        return false;

    int expectedSumSquares = high * (high + 1) * (2 * high + 1) / 6;

    if (sumSquares != expectedSumSquares)
        return false;

    return true;
}

答案 38 :(得分:-2)

我认为你根本不需要使用总和。只需检查最小值和最大值并检查欺骗。检查欺骗是更难的部分,因为你事先不知道n,所以你不能一次性排序。要解决此问题,请放松(编辑:目标)数组上的条件。不需要对其进行排序,而是对排序序列进行循环移位,以使数组变为[k,k + 1,...,n + m-2,n + m-1,n,n +某些k为1,...,k-2,k-1]。

根据上述条件,您可以假设a [0]已经在正确的位置,然后元素d的正确位置是(d-a[0]) mod m,假设从零开始的数组索引。例如,[4,?,?,?]可以预期[4,5,6,7]或[4,1,2,3]或[4,5,6,3]或[4,5, 2,3]。

然后只扫描一次数组,将每个元素放在计算位置,更新最小值和最大值并检查碰撞。如果没有冲突且max-min = m,则满足条件,否则为假。