问题 在阵列中找到“乱序”的数字的最佳方法是什么?


我有一个长度为10的数组,数字为0-9。

这些数字是(大部分)按顺序排列的。然而,起始索引处的数字可以是任何数字,并且数字是按升序还是按降序排列是未知的(一旦它们达到最小/最大数字时数字会环绕 - 一旦达到9则为0,反之亦然)。

这些数字中的其中一个不是有序的(好像它被拔出并随机插回到数组中)。

例:

[4, 3, 1, 0, 9, 8, 7, 2, 6, 5]

索引7处的数字2无序。索引1和2之间的数字“间隙”是可以的,并且数字3或1都不被认为是乱序的。

找出无序数字索引的最佳方法是什么?

更多示例 - 不合适的地址用*标记:

[2, 3, *0, 4, 5, 6, 7, 8, 9, 1]
[5, 6, 7, 9, *8, 0, 1, 2, 3, 4]
[7, 6, 5, 4, 3, *8, 2, 1, 0, 9]
[0, *5, 1, 2, 3, 4, 6, 7, 8, 9]
[4, 3, *0, 2, 1, 9, 8, 7, 6, 5] 

8039
2018-05-20 02:53


起源

你只需要遍历数组并使用变量来记忆最后一个数字。如果最后一个数字和当前数字的差异不是1,则它是乱序的 - Raptor
这听起来像一个有趣的求职面试问题:-) - Michael Dautermann
听起来你想找到两件不同的东西。一个是“无序”项目,但另一个是“环绕”(否则,可能会被误认为是无序项目)。因此,扫描列表记录差异的迹象(seq [n-1] - seq [n]> 0)并查找您看到两个连续符号更改的任何情况。退化输入可能会欺骗一个天真的实现,但否则你会在线性时间内找到你的“罪魁祸首”。一个没有错误的数组将是 - , - , - ...最多只有一个变化为+,+,+ ......但是告密者是: - , - , - ,+, - ,+ ,. .. (或相反亦然)。 - Jim Dennis
问题:您说过,一旦达到最大值或最小值,数字就会回旋。但你也说过这些数字之间可能存在1+的差距。那么即使数字没有达到最大值或最小值,数字也会回旋吗?以下系列是否有效 - [1, 2, 3, 4, 5, 6, 0, 7, 8, 9]  - 注意在6系列环绕后,从0开始。 - Anurag
没有这样的保证,它们不是相互排斥的。 0和9可能并不总是在一起,因为0或9可以是从数组中删除并随机添加的数字。如果你想要更好地了解数组的外观,可以想象一个数字0-9的数组(升序/降序),它可以从任何数字0-9开始,并在0/9处“环绕” 。然后想象一下,随机索引中的一个数字已被删除,然后随机插回到数组中。 - Vadoff


答案:


要查找乱序的数字,您需要查看数组中的每个元素。因此,您必须以复杂度O(n)迭代整个数组。

当你遍历数组时,你应该

  • 计算前一个数字和当前数字之间差值的绝对值。
  • 计算当前数字和下一个数字之间差值的绝对值

如果上述差异均大于1且不等于n-1(当差值为n-1时,即阵列翻转的点),那么这就是无序的数字。


3
2018-05-20 02:58



我认为可以通过查看其他所有元素来逃避。 - n.m.
@牛米。因为任何一个元素的值与它旁边的元素无关,所以你需要查看每个元素。例如,如果数组是 {1,2,3,4000,5,6,7},你不能只看 1,3,5,7 并说一切都很好。 - Seph
这种方法会给出数字的误报,因为它们是n-2但仍然被认为是“按顺序”的。 - Vadoff
@Seph:“填充数字0-9”,没有4000。 - n.m.
{1,2,3,9,5,6,7,8} - 同样的问题即使是0-9。你只会看1,3,5,7。 - srikfreak


查看每个其他元素,并计算差异。

如果大多数差异为正,则顺序为升序。如果大多数是负面的,它会下降;它可以像其他情况一样决定,我不会进一步检查。

当然,你需要环绕并计算第(N-1)和第0个元素之间的差异,或者其他什么。

从现在开始看模数N的差异。

如果差异是2,这是没有额外或缺少元素的常规情况;忽略它。

如果差异是3,那么一个元素就会从这里的某个地方被拉出来。但我们不是在寻找它的旧地方,我们正在寻找它的新地方;也忽略这一点。

如果diff是1,那么乱序元素就在这些数字之间。

如果你有任何其他差异,那么你必须让它们中的两个相邻。乱序元素是产生这两个差异的元素。

在交换两个连续数字的情况下,任何一个都可以被认为是无序的。产生的差异将是(1,3)或(3,1)彼此相邻。这种方法选择了两个可能的答案之一。

对于有问题的阵列:

array -> 2 3 0 4 5 6 7 8 9 1(2)        
          \ / \ / \ / \ / \ /
diffs ->  -2   5   2   2   -7(=3 mod 10)
             *

在进一步的示例中,我将不会声明等式mod 10以节省空间。

array -> 5 6 7 9 8 0 1 2 3 4(5) 
          \ / \ / \ / \ / \ /
diffs ->   2   1   3   2   2
               *

array -> 7 6 5 4 3 8 2 1 0 9(7)        
          \ / \ / \ / \ / \ /
diffs ->  -2  -2  -1  -2  -3
                   *

array -> 0 5 1 2 3 4 6 7 8 9(0)        
          \ / \ / \ / \ / \ /
diffs ->   1   2   3   2   2
           *

array -> 4 3 0 2 1 9 8 7 6 5(4)       
          \ / \ / \ / \ / \ /
diffs ->  -4  -9  -3  -2  -2
             *    

更多例子:

array -> 0 2 1 3 4 5 6 7 8 9(0)        swapped adjacent elements, case 1
          \ / \ / \ / \ / \ /
diffs ->   1   3   2   2   2
           *

array -> 0 1 3 2 4 5 6 7 8 9(0)        swapped adjacent elements, case 2
          \ / \ / \ / \ / \ /
diffs ->   3   1   2   2   2
               *

array -> 0 2 3 4 5 6 7 1 8 9(0)        element removed and inserted at odd pos
          \ / \ / \ / \ / \ /
diffs ->   3   2   2   1   2
                       *

array -> 0 2 3 4 5 6 1 7 8 9(0)        element removed and inserted at even pos
          \ / \ / \ / \ / \ /
diffs ->   3   2   6   7   2
                     *

array -> 0 7 1 2 3 4 5 6 8 9(0)        element removed and inserted at odd pos
          \ / \ / \ / \ / \ /
diffs ->   1   2   2   3   2
           *            

array -> 0 1 7 2 3 4 5 6 8 9(0)        element removed and inserted at even pos
          \ / \ / \ / \ / \ /
diffs ->   7   6   2   3   2
             *

3
2018-05-20 05:43



你的算法听起来很有趣,但我很难理解它。您是否介意解释为什么它适用于问题中的示例? - Steven Wexler
@steaks:看看更新。 - n.m.
可能编辑 - "If the diff is 2, everything is fine, ignore it. If the diff is 3, the missing element should go somewhere between here, but the actual element is somewhere else, so ignore it." 另外,我认为你需要一个特殊的案例 [0,1,2,3,5,4,6,7,8,9]。 - Dukeling
@Dukeling:是的,我忘了提到2.对于3,缺少的元素 是 介于两者之间,在它被移除之前;这是你的意思吗?另外,为什么你的例子很特别?它仍然是4和5交换,任何一个都可以被选中,算法挑选4。 - n.m.
@牛米。是的,你的编辑正是我的意思。你的权利,你的代码适用于这个例子,我没有给予足够的重视。 - Dukeling


以下设计的示例没有唯一的解决方案。你需要决定在这些情况下会发生什么:

0, 1, 2, 3, 4, 5, 6, 7, 8, 9 // first item move to end

2, 1, 3, 4, 5, 6, 7, 8, 9, 0 // adjacent items swapped 

对于所有其他情况,幸运的是,所谓的“无序”项目距离将超过1 它的邻居(因为上面的#2)。

for (i = 0; i < arr.length; i++) {
    int priorIndex = (i-1) % arr.length;
    int nextIndex = (i+1) % arr.length;
    int diffToPriorValue = abs(arr[i] - arr[priorIndex]);
    int diffToNextValue = abs(arr[i] - arr[nextIndex]);
    if (diffToPriorValue > arr.length/2)
        diffToPriorValue = arr.length - diffToPriorValue; // wrap-around
    if (diffToNextValue > arr.length/2)
        diffToNextValue = arr.length - diffToNextValue; // wrap-around
    if (diffToPriorValue != 1 && diffToNextValue != 1)
        return i;
return -1;

2
2018-05-20 04:01



+1非常好的答案......我在阅读完问题后想出了同样的解决方案!我特别喜欢你提到了两个模棱两可的案例(我只想到了第二个案例)。如果有关如何处理这两种情况的更多信息,你必须做一个微小的改变来处理第一种情况和一次更具侵略性的改变,你必须确定列表的“顺序”,而不是能够使用绝对值。 - Steven Wexler


首先,我将首先定义什么是“乱序”:

Suppose we have a list of numbers A

If there exist A[i] in A,
Such that A[i-1] <= A[i] <= A[i+1], then A[i] is "in order"
Otherwise, A[i] is "out of order"

算法

FOR i: 1..SIZE(A) DO

    PRINT " "
    PRINT A[i]

    IF A[i-1] <= A[i] <= A[i+1]
    THEN
        CONTINUE
    ELSE
        PRINT "*"
        REMOVE A[i]
    END-IF

END-FOR

测试

INPUT: { 2, 3, 0, 1, 2, 3, 5, 6, 7, 8, 9, 1 }

OUTPUT: { 2, 3, 3, 5, 6, 7, 8, 9 }

CONSOLE: 2 3 0* 1* 2* 3 5 6 7 8 9 1*

2
2018-05-20 08:18



1) "in ascending or descending order", 所以就 A[i-1] <= A[i] <= A[i+1] 不行。 2)每个号码只能出现一次,所以不一定要出现 <=,它可以只是 <。 3)只有1个号码处于错误的位置。 4)您的算法不考虑环绕。 5)为 0,1,2,3,4,8,5,6,7,9,你的算法会标记 8 和 5而不仅仅是 8。 6)Eww @ all-caps。 - Dukeling
1)算法根据给定的定义工作。 2)我将“顺序”解释为asc或desc顺序。 3)<=或<的选择取决于您是否允许重复。 4)什么是环绕式? 5)给出 0,1,2,3,4,8,5,6,7,9 我的算法只会标记为8,因为当标记为8时,它将从列表中删除(忽略)。 ..感谢我对算法的兴趣,如果你讨厌全部大写,那就是我们在编写算法伪代码时使用的方式 - Khaled.K
这是环绕式的: 6,7,8,9,0,1,2,3,4,5  - 序列从中间某处开始,一旦到达结尾,它就从头开始。我已经看过(并且写过)了大量的伪代码,但从来没有使用全部大写,但是,由于没有标准,我想任何人都可以按照他们想要的方式去做。 - Dukeling
然后我想我的算法不支持环绕,好吧这不是我的标准,这是我的大学标准 - Khaled.K


这是一个类似于Khalid的解决方案。

考虑两个要素  如果他们可以看起来彼此相邻而不知道包装。所以, 9 和 0 被认为是相邻的元素。

该算法循环通过每组三个连续元素,并检查第一个和第三个元素是否相邻。如果它们相邻,那么中间值必须是乱序的。

我将给定的列表加入到自身中,从而创建一个大小为20的数组。这将处理一个特殊情况,其中数字被移动到列表的开头或结尾。

# checks if two given numbers are adjacent or not, independent of wrapping
def adjacent?(a, b)
  (a - b).abs == 1 || [a, b].sort == [0, 9]
end

# finds the misplaced number in a list
def misplaced_number(list)
  middle_index = 1
  (list + list).each_cons(3).find { |first, second, third|
    adjacent?(first, third)
  }[middle_index]
end

检查以下测试。由于含糊不清,第二次和最后一次测试失败了。

test([2, 3, 0, 4, 5, 6, 7, 8, 9, 1], 0)
test([5, 6, 7, 9, 8, 0, 1, 2, 3, 4], 8) # [FAIL: result = 9]
test([7, 6, 5, 4, 3, 8, 2, 1, 0, 9], 8)
test([0, 5, 1, 2, 3, 4, 6, 7, 8, 9], 5)
test([4, 3, 0, 2, 1, 9, 8, 7, 6, 5], 0)
test([2, 4, 5, 6, 7, 8, 9, 0, 1, 3], 2) # [FAIL: result = 3]

def test(list, expected)
  result = misplaced_number(list)
  assert result == expected_value, "Got #{result} but expected #{expected} from list #{list}"
end

2
2018-05-20 10:01





因此,在Haskell中结合srikanta和n.m.:

import Data.List (findIndex)

f s = maybe (-1) (+1) . findIndex (==1)
    $ zipWith (\a b -> abs (a - b)) s (drop 2 s ++ take 2 s)


*Main> f [2,3,0,4,5,6,7,8,9,1]
2
*Main> f [5,6,7,9,8,0,1,2,3,4]
3
...

1
2018-05-22 16:15





#include <stdio.h>

int main(void)
{
int array[10] = { 4, 3, 1, 0, 9, 8, 7, 2, 6, 5};

size_t idx;
int diff, state,this,next;

#define COUNT (sizeof array/sizeof array[0])
#define FOLD(n,i) ((i)%(n))
#define FETCH(a,i) a[FOLD(COUNT,(i))]

this = FETCH(array,COUNT-1);
next = FETCH(array,0);
diff = next - this;
state = (diff < -1 || diff >1) ? 1: 0;
for (idx = 0; idx < COUNT; idx++) {
        this = next;
        next = FETCH(array,idx+1);
        diff = next - this;
        state = (state<<1) & 3;
        state |= (diff < -1 || diff >1) ? 1: 0;
        if (state==3) putc('*', stdout);
        printf("%d ", this );
        }
putc('\n', stdout);
return 0;
}

输出:

4 3 1 0 9 8 7 *2 6 5

1
2018-05-23 00:02





int element, backdiff, forwarddiff;
boolean elementFound = false;

if(abs(a[0] - a[1]) == 2 )
    return "Out of Order Element is @ position" + (a[0] - a[1] > 0 ? 0 : 1);
for (i=1;i<n;i++){
    if(!elementFound){
        backdiff = abs(a[i-1] - a[i]);
        forwarddiff = abs(a[i+1] - a[i]);
        if( (backdiff == 1 && forwarddiff == 1) || 
            (backdiff == n-1 && forwarddiff == 1) ||
                (backdiff == 1 && forwarddiff == n-1) )
            continue;
        if(forwarddiff == 2 || backdiff == 2)
            element = abs(a[i]-(a[i-1]+a[i+1]));
            elementFound = true;

        if(forwarddiff > 2 || backdiff > 2)
            return "Out of Order Element is @ position" + (forwarddiff > 2 ? (i+1) : i);

    } else {
        if(a[i] == element)
            return "Out of Order Element is @ position" + (i);
    }   
}

0
2017-10-03 01:11



请不要只是转码;解释你的解决方案。 - Lambda Fairy