요소를 효율적으로 검색하는 방법


88

최근에 인터뷰를했는데 그들이 " 검색 "질문을했습니다.
질문은 :

각 요소가 인접한 요소 중 하나 +1이거나 -1비교 되는 (양수) 정수 배열이 있다고 가정 합니다.

예:

array = [4,5,6,5,4,3,2,3,4,5,6,7,8];

이제 7위치를 검색 하고 반환합니다.

나는이 대답을했다 :

값을 임시 배열에 저장하고 정렬 한 다음 이진 검색을 적용합니다.

요소가 발견되면 임시 배열에서 해당 위치를 반환합니다.
(번호가 두 번 발생하면 첫 번째 발생을 반환)

그러나 그들은이 답변에 만족하지 않는 것 같습니다.

정답은 무엇입니까?


4
내가 아는 한, 선형 검색은 배열에서 요소의 인덱스를 찾는 좋은 방법입니다. 요소의 인덱스를 찾는 데 효율적인 다른 검색 알고리즘이 아직 확실하지 않습니다.
Sean Francis N. Ballais 2015

4
7이 한 번만 나타나는 것이 보장되거나 7이 반환되는 것이 중요하지 않은 경우 coleman의 대답의 선형 알고리즘에서 더 개선 할 수 있습니다.
user1942027

52
원래 솔루션에 정렬이 필요한 경우 순진한 선형 검색보다 나쁩니다. 당신은 그것을 모르는 것 같습니다.
cubuspl42

5
정렬에는 O (nlogn)가 필요하고 이진 검색은 O (logn)입니다. 큰 배열에서 많은 값을 검색해야하는 경우 답변이 더 좋을 수 있지만 한 번만 검색하면 O (n) 알고리즘이 더 좋을 수 있습니다.
jingyu9575

23
왜 다른 사람이 이것을 언급하지 않았는지 모르겠습니다. 귀하의 방법은 비효율적 일뿐만 아니라 부정확 했으며 단순한 비 효율성보다 훨씬 더 나쁩니다. 요구 사항은 원래 배열에서 주어진 숫자의 위치에 대한 것 입니다. 메서드는 정렬 된 배열에서 숫자의 위치를 ​​반환합니다 . 이제 정렬하기 전에 단순 배열을 튜플 배열 (number, orig_pos)로 변환하여 원래 위치를 검색 할 있습니다. 그러나 당신은 그것을 언급하지 않았으므로 인터뷰에서도 언급하지 않은 것 같습니다.
Tom Zych 2015

답변:


125

1보다 큰 단계로 선형 검색을 수행 할 수 있습니다. 중요한 관찰은 eg array[i] == 4와 7이 아직 나타나지 않은 경우 7에 대한 다음 후보가 index에 있다는 것 i+3입니다. 다음 실행 가능한 후보로 반복적으로 직접 이동하는 while 루프를 사용하십시오.

다음은 약간 일반화 된 구현입니다. k배열에서 첫 번째 발생 (+ = 1 제한에 따라) 또는 -1발생하지 않는 경우를 찾습니다 .

#include <stdio.h>
#include <stdlib.h>

int first_occurence(int k, int array[], int n);

int main(void){
    int a[] = {4,3,2,3,2,3,4,5,4,5,6,7,8,7,8};
    printf("7 first occurs at index %d\n",first_occurence(7,a,15));
    printf("but 9 first \"occurs\" at index %d\n",first_occurence(9,a,15));
    return 0;
}

int first_occurence(int k, int array[], int n){
    int i = 0;
    while(i < n){
        if(array[i] == k) return i;
        i += abs(k-array[i]);
    }
    return -1;
}

산출:

7 first occurs at index 11
but 9 first "occurs" at index -1

8
정확히 내가 생각했던 것. 이것은 O(N)이지만 더 빠른 방법은 없다고 생각합니다.
shapiro yaacov 2015

2
더 많은 후보 (예 : 첫 번째 및 마지막)를 사용하여 평균적으로 조금 더 빠르게 수행 한 다음 대상에 가장 가까운 후보로 진행할 수 있습니다. 즉, 첫 번째 항목이 아니라 단일 항목 만 찾아야하는 경우입니다.
mkadunc 2015

2
@mkadunc 좋은 생각입니다. 또 다른 관찰은 첫 번째 요소와 마지막 요소가 7 개에 걸쳐있는 경우이 특수한 경우에 이진 검색을 사용할 수 있다는 것입니다 (어떤 7 개를 찾았는지 상관하지 않으면)
John Coleman

1
7 개 (첫 번째는 아님)를 찾아야하는 경우 다음과 같은 (실용적인) 개선을 제안합니다. 섹션 목록 (두 개의 정수, 'start'및 'end')을 만들고 배열의 시작 부분에서 시작하는 대신 중간에서 시작합니다. 셀의 값에 따라 관련 범위를 무시하고 섹션 목록에 남은 두 섹션을 추가하십시오. 이제 목록의 다음 항목에 대해 반복하십시오. 이것은 여전히 ​​'O (n)'이지만 셀을 확인할 때마다 범위의 두 배를 무시합니다.
shapiro yaacov 2015

3
@ShapiroYaacov : 섹션의 양쪽에 대한 값 중 낮은 값에서 높은 값까지의 간격에 k (7)이 포함되어 있는지 확인하는 것과 결합하여 자체 답을 얻을 가치가 있습니다.
greybeard

35

접근 방식이 너무 복잡합니다. 모든 배열 요소를 검사 할 필요는 없습니다. 첫 번째 값은 4그래서 7입니다 적어도 7-4 멀리 요소, 당신은 사람들을 건너 뛸 수 있습니다.

#include <stdio.h>
#include <stdlib.h>

int main (void)
{
    int array[] = {4,5,6,5,4,3,2,3,4,5,6,7,8};
    int len = sizeof array / sizeof array[0];
    int i = 0;
    int steps = 0;
    while (i < len && array[i] != 7) {
        i += abs(7 - array[i]);
        steps++;
    }

    printf("Steps %d, index %d\n", steps, i);
    return 0;
}

프로그램 출력 :

Steps 4, index 11

편집 : @Raphael Miedl 및 @Martin Zabel의 주석 이후 개선되었습니다.


2
if ((skip = 7 - array[i]) < 1) skip = 1;멍청이는 그것을 지나치게 복잡하게 만들고 내 의견으로는 비관적 인 것 같습니다. 경우 array[i] == 200당신은 얻을 -193당신은 왜 그냥 모든 193을 건너 뛸 수에도 불구하고 단지 1로 모든 시간을 건너 뛸 i += abs(7 - array[i])?
user1942027

1
skip7과 사이의 절대 차이로 설정 해야합니다 array[i].
Martin Zabel 2015

@Raphael Miedl 아니, 요소는 없을 것입니다 200, 당신은 통과했을 것 7입니다.
Weather Vane 2015

3
@WeatherVane 우리는 그 보장이 없으며 인접한 값이 서로 +1/ -1로부터 있다는 것 입니다. 그래서 그것은 단지 될 수 array[0] == 200있고 다른 것들은 대부분 -1의 것입니다.
user1942027 dec.

1
@WeatherVane 이것은 요소가 항상 배열에서 발견된다는 가정을 가지고 있습니다. 이 경우 -1은 유효한 반환입니다. 코드를 상당히 변경합니다
Eugene

20

기존 선형 검색의 변형이 좋은 방법이 될 수 있습니다. 요소를 선택합시다 array[i] = 2. 이제 array[i + 1]1 또는 3 (홀수), array[i + 2](양의 정수만) 2 또는 4 (짝수)가됩니다.

이렇게 계속하면 패턴이 관찰 가능합니다. array[i + 2*n]짝수를 보유하므로 이러한 모든 인덱스를 무시할 수 있습니다.

또한 우리는

array[i + 3] = 1 or 3 or 5
array[i + 5] = 1 or 3 or 5 or 7

따라서 index i + 5에서 찾은 값에 따라 다음 인덱스를 확인하고 while 루프를 사용하여 확인할 다음 인덱스를 결정할 수 있습니다 i + 5.

이것은 복잡성 O(n)(점근 적 복잡성 측면에서 선형 시간) 이 있지만 모든 인덱스를 방문하지 않기 때문에 실제적인 측면에서 일반 선형 검색보다 낫습니다.

분명히, array[i](우리의 시작 지점)이 이상 하다면이 모든 것이 반전 될 것 입니다.


8

John Coleman이 제시 한 접근 방식은 모든 가능성에서 면접관이 바라던 것입니다.
좀 더 복잡하게하려면 예상 건너 뛰기 길이를 늘릴 수 있습니다 .
목표 값 k를 호출합니다 . 위치 p 에서 첫 번째 요소의 값 v 로 시작하고 절대 값 av로 차이 kv dv 를 호출합니다 . 부정적인 검색 속도를 높이려면 위치 o 에서 다른 값 u 로 마지막 요소를 들여다보십시오.: dv × du가 음수이면 k가 존재합니다 (k의 발생이 허용되는 경우 여기에서 이진 검색 방식으로 인덱스 범위를 좁힐 수 있습니다). av + au가 배열의 길이보다 크면 k가 없습니다. (dvxdu가 0이면 v 또는 u는 k와 같습니다.)
인덱스 유효성 생략 : 시퀀스가 ​​중간에 k를 사용하여 v로 돌아갈 수있는 ( "next") 위치를 조사합니다 o = p + 2*av..
dv × du가 음수이면 p + av에서 o-au까지 k (재귀 적으로?)를 찾으십시오.
0이면 u는 o에서 k와 같습니다.
뒤이 DV 같고 중간에있는 값은 케이되지 않았거나 AU는 유명을 초과하는 경우
또는 실패 , + AV에 오 프 페이지에서 K를 찾을
수 있도록 p=o; dv=du; av=au;하고 프로빙 유지합니다.
('60 년대 텍스트에 대한 전체 플래시백을 보려면 Courier로보기. 내 "첫 번째 두 번째 생각"은o = p + 2*av - 1, du가 dv와 같지 않음.)


4

1 단계

첫 번째 요소로 시작하여 7인지 확인합니다 c. 현재 위치의 인덱스 라고 가정 해 보겠습니다 . 따라서 처음에는 c = 0.

2 단계

7이면 인덱스를 찾았습니다. 그것은이다 c. 배열의 끝에 도달하면 중단하십시오.

3 단계

그렇지 않은 경우 |array[c]-7|인덱스 당 하나의 단위 만 추가 할 수 있으므로 7은 최소한 떨어져 있어야합니다 . 따라서 |array[c]-7|현재 색인 c에 추가 하고 2 단계로 다시 이동하여 확인하십시오.

최악의 경우, 대체 1과 -1이있을 때 시간 복잡도는 O (n)에 도달 할 수 있지만 평균 사례는 빠르게 전달됩니다.


이것은 John Coleman의 답변과 어떻게 다릅니 까? (별도로 제안에서 |c-7|어디 |array[c]-7|를 요구 보인다.)
수염이 희끗 희끗 한

방금 그의 대답을 봤어요. 핵심 아이디어가 동일하다는 것을 인정합니다.
Akeshwar Jha 2015

원래 질문은 배열이 7보다 작은 숫자로 시작한다고 규정하지 않습니다. 따라서 array[c]-7양수 또는 음수가 될 수 있습니다. abs()앞으로 건너 뛰기 전에 신청해야합니다 .
arielf 2015

네 말이 맞아. 그래서 array[c] - 7모듈러스 연산자 인 |array[c] - 7|.
Akeshwar Jha 2015

4

여기에 자바 구현을 제공하고 있습니다 ...

public static void main(String[] args) 
{       
    int arr[]={4,5,6,5,4,3,2,3,4,5,6,7,8};
    int pos=searchArray(arr,7);

    if(pos==-1)
        System.out.println("not found");
    else
        System.out.println("position="+pos);            
}

public static int searchArray(int[] array,int value)
{
    int i=0;
    int strtValue=0;
    int pos=-1;

    while(i<array.length)
    {
        strtValue=array[i];

        if(strtValue<value)
        {
            i+=value-strtValue;
        }
        else if (strtValue==value)
        {
            pos=i;
            break;
        }
        else
        {
            i=i+(strtValue-value);
        }       
    }

    return pos;
}

2
적어도 반 공식적인 규칙을 가진 언어로 된 문서화되지 않은 코드 . 태그 "c"를 자유롭게 해석하는 것 외에는 John Coleman 및 Akeshwar의 답변과 어떻게 다른가요?
greybeard

3

다음은 분할 및 정복 스타일 솔루션입니다. (훨씬) 더 많은 부기를 희생시키면서 더 많은 요소를 건너 뛸 수 있습니다. 왼쪽에서 오른쪽으로 스캔하는 대신 중간에서 테스트하고 양방향 으로 건너 뜁니다 .

#include <stdio.h>                                                               
#include <math.h>                                                                

int could_contain(int k, int left, int right, int width);                        
int find(int k, int array[], int lower, int upper);   

int main(void){                                                                  
    int a[] = {4,3,2,3,2,3,4,5,4,5,6,7,8,7,8};                                   
    printf("7 first occurs at index %d\n",find(7,a,0,14));                       
    printf("but 9 first \"occurs\" at index %d\n",find(9,a,0,14));               
    return 0;                                                                    
}                                                                                

int could_contain(int k, int left, int right, int width){                        
  return (width >= 0) &&                                                         
         (left <= k && k <= right) ||                                            
         (right <= k && k <= left) ||                                            
         (abs(k - left) + abs(k - right) < width);                               
}                                                                                

int find(int k, int array[], int lower, int upper){                              
  //printf("%d\t%d\n", lower, upper);                                            

  if( !could_contain(k, array[lower], array[upper], upper - lower )) return -1;  

  int mid = (upper + lower) / 2;                                                 

  if(array[mid] == k) return mid;                                                

  lower = find(k, array, lower + abs(k - array[lower]), mid - abs(k - array[mid]));
  if(lower >= 0 ) return lower;                                                    

  upper = find(k, array, mid + abs(k - array[mid]), upper - abs(k - array[upper]));
  if(upper >= 0 ) return upper;                                                  

  return -1;                                                                     

}

neal-fultz 귀하의 대답은 첫 번째 발생을 반환하지 않지만 중간에서 시작하여 양쪽에서 건너 뛸 때 검색 요소의 임의 발생을 반환합니다.
Ram Patra 2015

재귀 순서를 바꾸는 것은 독자에게 연습으로 남겨 둡니다.
Neal Fultz 2015

1
neal-fultz 그런 다음 printf () 메서드 호출에서 메시지를 편집하십시오.
Ram Patra 2015

2

const findMeAnElementsFunkyArray = (arr, ele, i) => {
  const elementAtCurrentIndex = arr[i];

  const differenceBetweenEleAndEleAtIndex = Math.abs(
    ele - elementAtCurrentIndex
  );

  const hop = i + differenceBetweenEleAndEleAtIndex;

  if (i >= arr.length) {
    return;
  }
  if (arr[i] === ele) {
    return i;
  }

  const result = findMeAnElementsFunkyArray(arr, ele, hop);

  return result;
};

const array = [4,5,6,5,4,3,2,3,4,5,6,7,8];

const answer = findMeAnElementsFunkyArray(array, 7, 0);

console.log(answer);

문제에 대한 재귀 솔루션을 포함하고 싶었습니다. 즐겨

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.