【问题标题】:Find a number in an array在数组中查找一个数字
【发布时间】:2017-05-08 17:03:53
【问题描述】:

对于以下问题,是否有 O(n) 效率的解决方案?

您需要在数组中找到一个单元格,使其前面的所有数字都低于它,而它后面的所有数字都高于它。您应该忽略第一个和最后一个单元格。

例如,考虑以下列表:

1, 3, 2, 6, 5, 7, 9, 8, 10, 8, 11

在这种情况下,答案将是索引 5 处的数字 7

【问题讨论】:

  • 1和11呢?
  • 你试过什么? Stack Overflow 并不是一个真正让您要求人们为您寻找解决方案/编写代码的网站。
  • @shmosel,请忽略第一个和最后一个数字
  • @RushyPanchal 我可以用 O'n2 做到这一点,现在将发布我的解决方案。找不到任何 O'n 解决方案
  • 提示:保留一个变量,存储当前基于列表子集的当前满足条件的数字,如果未满足,则保留 -1,以及存储迄今为止最高数字的变量。

标签: java arrays algorithm


【解决方案1】:

是的,它确实可以O(n) 时间内完成。以下是几种方法。

第一个对于查找所有个候选单元格更有用。对数据进行一次O(n) 传递,为每个单元格设置两个额外的项目,因此O(n) 空间(可以通过以空间换时间来解决大量优化问题)。

每个单元格需要计算的两项是左侧的最大值和右侧的最小值。第一遍为所有单元格设置这些项目,最后一个没有意义(显然是伪代码):

# Set current values.

highLeftVal = cell[0]
lowRightVal = cell[cell.lastIndex]

# For running right and left through array.

rightIdx = cell.lastIndex
for each leftIdx 1 thru cell.lastIndex inclusive:
    # Left index done by loop, right one manually.

    rightIdx = rightIdx - 1

    # Store current values and update.

    highLeft[leftIdx] = highLeftVal
    if cell[leftIdx] > highLeftVal: highLeftVal = cell[leftIdx]

    lowRight[rightIdx] = lowRightVal
    if cell[rightIdx] < lowRightVal: lowRightVal = cell[rightIdx]

然后检查每个单元格(第一个和最后一个)以确保值都大于(根据您的问题,此答案假设“更高/更低”是 字面值,不是“大于/小于或等于”)左边最高,小于右边最低:

for each idx 1 thru cell.lastIndex-1 inclusive:
    if cell[idx] > highLeft[idx] and cell[idx] < lowRight[idx]
        print "Found value ", cell[idx], " at index ", idx

您可以在下面看到初始传递的结果:

highLeft:   -  1  3  3  6  6  7  9  9 10 10
cells   :   1  3  2  6  5  7  9  8 10  8 11
lowRight:   2  2  5  5  7  8  8  8  8 11  -
                           ^

唯一候选单元格,其中值相对于它上面和下面的两个值进行排序(不包含),是带有^ 标记的7


现在请记住,这是一个相对容易理解的解决方案,可以找到满足约束条件的多个项。鉴于您只需要 一个 项,就有可能获得更好的性能(尽管仍然是 O(n))。

基本思想是从左到右遍历数组,对于每个单元格,检查左侧是否较低,右侧是否较高。

第一点很容易,因为通过从左到右遍历,您可以记住遇到的最高值。第二点似乎涉及以某种方式展望未来,但您可以使用一个技巧来避免这种“时间体操”。

这个想法是保持当前单元格左侧的最高值当前答案的索引(最初设置为哨兵值)。

如果当前答案是哨兵值,则选择第一个满足“大于左边所有”的单元格作为可能的答案。

而且,只要情况仍然如此,这就是您选择的单元格。但是,一旦您在其右侧找到小于或等于它的值,它就不再有效,因此您将其丢弃并重新开始搜索。

这个搜索是从当前点开始的,而不是回到起点,因为:

  • 当前答案之后直到但不包括此(小于或等于)单元格的所有内容都高于当前答案者,否则您会已经找到一个小于或等于的单元格;和
  • 因此,该单元格必须小于或等于该范围内的每个单元格,因为它小于或等于当前答案;因此
  • 没有该范围内的单元格有效,它们全部大于或等于这个。

一旦您完成了非结束项目的处理,您的答案将是哨兵或几乎满足约束条件的单元格。

我说“几乎”是因为需要进行一次最终检查以确保最终项目大于它,因为您在遍历过程中没有对该项目执行任何检查。

因此,该野兽的伪代码类似于:

# Store max on left and start with sentinel.

maxToLeft = cell[0]
answer = -1

for checking = 1 to cell.lastIndex-1 inclusive:
    switch on answer:
        # Save if currently sentinel and item valid.
        case -1:
            if cell[checking] > maxToLeft:
                answer = checking

        # Set back to sentinel if saved answer is now invalid.
        otherwise:
            if cell[answer] >= cell[checking]:
                answer = -1

    # Ensure we have updated max on left.

    if cell[checking] > maxToLeft:
        maxToLeft = cell[checking]

# Final check against last cell.

if answer != -1:
    if cell[cell.lastIndex] <= cell[answer]:
        answer = -1

由于我的伪代码(大量)基于 Python,因此提供一个更具体的代码示例是一件相当简单的事情。首先,“找到每一种可能性”选项:

cell = [1, 3, 2, 6, 5, 7, 9, 8, 10, 8, 11]

highLeft = [0] * len(cell)
lowRight = [0] * len(cell)

highLeftVal = cell[0]
lowRightVal = cell[len(cell)-1]

rightIdx = len(cell) - 1
for leftIdx in range(1, len(cell)):
    rightIdx = rightIdx - 1

    highLeft[leftIdx] = highLeftVal
    if cell[leftIdx] > highLeftVal: highLeftVal = cell[leftIdx]

    lowRight[rightIdx] = lowRightVal
    if cell[rightIdx] < lowRightVal: lowRightVal = cell[rightIdx]

print(highLeft)
print(cell)
print(lowRight)

for idx in range(1, len(cell) - 1):
    if cell[idx] > highLeft[idx] and cell[idx] < lowRight[idx]:
        print("Found value", cell[idx], "at index", idx)

还有第二种,效率稍高一些,但只能找到一种可能性:

cell = [1, 3, 2, 6, 5, 7, 9, 8, 10, 8, 11]
maxToLeft = cell[0]
answer = -1
for checking in range(1, len(cell) - 1):
    if answer == -1:
        if cell[checking] > maxToLeft:
            answer = checking
    else:
        if cell[answer] >=cell[checking]:
            answer = -1
    if cell[checking] > maxToLeft:
        maxToLeft = cell[checking]

if answer != -1:
    if cell[len(cell] - 1] <= cell[answer]:
        answer = -1

if answer == -1:
    print ("Not found")
else:
    print("Found value", cell[answer], "at index", answer);


print(highLeft)
print(cell)
print(lowRight)

for idx in range(1, len(cell) - 1):
    if cell[idx] > highLeft[idx] and cell[idx] < lowRight[idx]:
        print("Found value", cell[idx], "at index", idx)

两者的输出(尽管后一个示例仅显示最后一行)基本上显示了伪代码的意图:

[0, 1, 3, 3, 6, 6, 7, 9, 9, 10, 10]
[1, 3, 2, 6, 5, 7, 9, 8, 10, 8, 11]
[2, 2, 5, 5, 7, 8, 8, 8, 8, 11, 0]
Found value 7 at index 5

【讨论】:

  • 取决于您对复杂的定义,@S.Pinkus。您的定义似乎涉及最小化传递,如果效率是您的 压倒一切 关注点,这是可以的,但代码似乎更难遵循(无论如何对我来说)。在没有其他更强大的要求的情况下,我倾向于更易于理解的代码:-) 无论如何,我们的答案都是 O(n),因此满足 OP 的查询,他们可以选择他们喜欢的。
  • @paxdiablo 对我来说,三个具有数据依赖关系的循环很难遵循。我总是对人们看待事物的不同方式感到惊讶,所以我猜每个人都有自己的看法。
  • 我会一口气写完。函数将是 testCandidate()acceptCandidate()removeCandidates() 等。
【解决方案2】:

创建一个额外的数组,该数组通过在源数组上从左到右进行计算。对于此数组中的任何索引 N,该值是第一个数组中 0:N-1 之间观察到的最大值。

int arr1 = new int[source.length];
int highest = MIN_INT;
for (int i = 0; i < source.length; i++) {
    arr1[i] = highest;
    if (source[i] > highest) {
        highest = source[i];
    }
}

现在创建第二个数组,它是通过从右到左扫描形成的,其中索引 N 处的任何值表示 N+1:end 之间看到的最小值

arr2 = new int[source.length];
int lowest = MAX_INT;
for (int i = (source.length-1); i <= 0; i--) {
    arr2[i] = lowest;
    if (source[i] < lowest) {
        lowest = source[i];
    }
}

现在你基本上得到了三个数组:

source: 1   3   2   6   5  7   9   8   10   8   11
arr1:   MIN 1   3   3   6  6   7   9    9  10   10
arr2:   2   2   5   5   7  8   8   8    8  11   MAX

现在您只想一起扫描所有三个数组,直到找到满足此条件的索引:

arr1[i] < source[i] < arr2[i] 

where:
     0 < i < (source.length-1)

代码:

for (int i = 1; i < (source.length-1); i++) {
    if ((arr1[i] < source[i]) && (source[i] < arr2[i])) {
        return i; // or return source[i]
    }
}

这是 O(N) 时间。

【讨论】:

  • @greybeard - 我们都在 2 分钟内提交了答案。直到我提交了我的之后,我才看到他提交的内容。它不时发生在SO上。我昨晚回来修复代码中的错误。
【解决方案3】:

这对于时间和空间复杂度和单个数组 Pass 都有 O(n)。

逻辑:

  1. 查找到现在为止找到的第一个最大值和总最大值,继续遍历数组,直到找到小于 first_max 的值。
  2. 如果您这样做了,则保留 first_max 和之前的所有元素,因为所有这些元素都比我当前找到的元素多。
  3. 现在选择一个 first_max,这样下一个值应该大于我们一直找到的 total_max。

代码:

// int[] arr = { 10, 11, 1, 2, 12, 13, 14};
int[] arr = {  1, 3, 2, 6, 5, 7, 9, 8, 10, 8, 11};
Integer firstMax = null;
Integer overallMax = null;

for (int i = 1; i < arr.length - 1; i++) {
    int currentElement = arr[i];
    if (firstMax == null) {
        if (overallMax == null) {
            firstMax = currentElement;
        } else if (overallMax != null && currentElement > overallMax) {
            firstMax = currentElement;
        }
    }
    if (overallMax == null || currentElement > overallMax) {
        overallMax = currentElement;
    }
    if (firstMax != null && currentElement < firstMax) {
        // We found a smaller element, so all max found so far is useless. Start fresh.
        firstMax = null;
    }
}

System.out.println(firstMax);

PS:根据我的分析,我觉得这应该足够了,适用于所有情况。不知道有没有漏掉什么情况。

【讨论】:

  • 这是错误的。以{ 10, 11, 1, 2, 12, 13, 14 } 为例。
  • @S.Pinkus 啊!!不错的收获。没有想到这个场景。修改了答案。您是否看到任何其他场景失败?
  • 我不确定投反对票的原因。是否有任何情况下这不起作用?谁能解释一下?
【解决方案4】:

这是一个 O(n),Python 中的一次通过解决方案。移植到 Java 很简单:

import random
A = [random.randint(1, 10) for i in range(0, 5)] # Generate list of size 5.
max_seen = A[0]
candidate = None
for i in range(1,len(A)):
  if candidate is None:
    if A[i] > max_seen:
      candidate = i
  else:
    if A[i] <= A[candidate]:
      candidate = None
  max_seen = max(max_seen, A[i])

print("Given %s " % (A,))
if candidate is not None and candidate != len(A)-1: # Ignore the last element as per spec.
  print("found: value=%d; index=%d\n" % (A[candidate], candidate))
else:
  print("not found")

您需要运行几次才能生成真正满足条件的列表。


说明

重申目标:找到数组A中元素的索引i,使得all A[j], j A[j] i => A[k] > A[i]。第一个这样的元素是一个这样的元素,所以我们只找到第一个。

给定一个索引 x,如果 x 满足上述条件,则 A[x] > A[0..x-1] 和 A[x ] 。验证给定 x 的这两个约束就足够了。注意 A[x] > A[0..x-1] A[x] > max(A[0..x-1])。所以我们保持到目前为止看到的最大值,找到第一个满足条件 1 的 x 并遍历数组验证条件 2 是否满足。如果条件 2 没有得到验证,我们知道下一个可能的候选对象超出当前索引 y,因为 A[x..y-1] > A[x] => A[y ] ,并且大于目前看到的最大值。

【讨论】:

  • +1 非常好。而且由于这只需要一次通过(和 O(1) 空间),它可以作为“在线算法”来完成;您甚至不需要提前知道有多少元素,您只需处理 Iterator 并在其 hasNext() 返回 false 时立即返回结果。
  • 请将此标记为答案,它只需要循环一次,不需要重复数组。
  • 这个解决方案很有趣,所以我在c 中写了一个实现作为另一个答案,希望你不介意;)
  • 这是最好的答案。
【解决方案5】:

我使用S.Pinkus 的答案中的算法在c 中编写了一个实现,并带有调试信息。


代码

find_mid_num.c:

/**
 * Problem:
 *  there is an array of number, find an element which is larer than elements before it, and smaller than elements after it,
 *  refer:  http://stackoverflow.com/questions/41293848
 * 
 * Solution:
 *  loop through array, remember max value of previous loopped elements, compare it to next element, to check whether the first condition is met,
 *  when found an element met the first condition, then loop elements after it to see whether second condition is met,
 *  if found, then that's it; if not found, say at position 'y' the condition is broken, then the next candidate must be after y, thus resume the loop from element after y,
 *  until found one or end of array,
 * 
 * @author Eric Wang
 * @date 2016-12-23 17:08
 */
#include <stdio.h>

// find first matched number, return its index on found, or -1 if not found,
extern int findFirstMidNum(int *arr, int len);

int findFirstMidNum(int *arr, int len) {
    int i=0, j;
    int max=arr[0];

    while(i < len) {
        printf("\n");
        if(arr[i] <= max) {
            printf("give up [%d]-th element {%d}, 1st condition not met\n", i, arr[i]);
            i++;
            continue;
        }
        max = arr[i]; // update max,

        printf("checking [%d]-th element {%d}, for 2nd condition\n", i, arr[i]);
        j = i+1;
        while(j < len) {
            if(arr[j] <= max) {
                printf("give up [%d]-th element {%d}, 2nd condition not met\n", i, arr[i]);
                break;
            }
            j++;
        }
        printf("position after 2nd check:\ti = %d, j = %d\n", i, j);

        if(j==len && j>i+1) {
            return i;
        } else {
            max = arr[j-1]; // adjust max before jump,
            i = j+1; // jump
            printf("position adjust to [%d], max adjust to value {%d}, after 2nd check\n", i, arr[j-1]);
        }
    }

    return -1;
}

int main() {
    int arr[] = {1, 3, 2, 6, 5, 7, 9, 8, 10, 8, 11};
    int len = sizeof(arr)/sizeof(arr[0]);
    printf("\n============ Input array ============\n");
    printf("size:\t%d\n", len);
    printf("elements:\t{");

    int i;
    for(i=0; i<len; i++) {
        printf("%d, ", arr[i]);
    }
    printf("}\n\n");

    printf("\n============ Running info ============\n");
    int pos = findFirstMidNum(arr, len);

    printf("\n============ Final result============\n");
    if (pos < 0) {
        printf("Element not found.\n");
    } else {
        printf("Element found at:\n\t position [%d], with value: {%d}\n", pos, arr[pos]);
    }
    printf("\n");

    return 0;
}

编译:

gcc -Wall find_mid_num.c

执行:

./a.out

运行结果:

============ Input array ============
size:   11
elements:   {1, 3, 2, 6, 5, 7, 9, 8, 10, 8, 11, }


============ Running info ============

give up [0]-th element {1}, 1st condition not met

checking [1]-th element {3}, for 2nd condition
give up [1]-th element {3}, 2nd condition not met
position after 2nd check:   i = 1, j = 2
position adjust to [3], max adjust to value {3}, after 2nd check

checking [3]-th element {6}, for 2nd condition
give up [3]-th element {6}, 2nd condition not met
position after 2nd check:   i = 3, j = 4
position adjust to [5], max adjust to value {6}, after 2nd check

checking [5]-th element {7}, for 2nd condition
position after 2nd check:   i = 5, j = 11

============ Final result============
Element found at:
     position [5], with value: {7}

TODO - 进一步改进:

  • 检查边界元素。
  • 提供多种功能来查找所有此类元素,而不仅仅是第一个。

【讨论】:

    猜你喜欢
    • 2014-02-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-10-25
    • 2014-03-01
    • 2023-03-10
    相关资源
    最近更新 更多