Cách hiệu quả để tìm kiếm một phần tử


88

Gần đây tôi đã có một cuộc phỏng vấn, nơi họ hỏi tôi một câu hỏi " tìm kiếm ".
Câu hỏi là:

Giả sử có một mảng các số nguyên (dương), trong đó mỗi phần tử là một trong hai +1hoặc được -1so sánh với các phần tử liền kề của nó.

Thí dụ:

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

Bây giờ hãy tìm kiếm 7và trả lại vị trí của nó.

Tôi đã đưa ra câu trả lời này:

Lưu trữ các giá trị trong một mảng tạm thời, sắp xếp chúng và sau đó áp dụng tìm kiếm nhị phân.

Nếu phần tử được tìm thấy, hãy trả lại vị trí của nó trong mảng tạm thời.
(Nếu số xuất hiện hai lần thì trả về số lần xuất hiện đầu tiên)

Nhưng, họ dường như không hài lòng với câu trả lời này.

Câu trả lời đúng là gì?


4
Theo như tôi biết, tìm kiếm tuyến tính là một cách tốt để định vị chỉ mục của một phần tử trong mảng. Tôi không chắc về một thuật toán tìm kiếm khác có hiệu quả trong việc định vị chỉ mục của một phần tử.
Sean Francis N. Ballais

4
Nếu số 7 được đảm bảo chỉ xuất hiện một lần hoặc nếu số 7 được trả về không quan trọng, bạn có thể cải thiện thêm một số điểm về thuật toán tuyến tính của câu trả lời của coleman.
user1942027

52
Nếu giải pháp ban đầu của bạn yêu cầu sắp xếp, thì nó tệ hơn so với tìm kiếm tuyến tính ngây thơ. Bạn dường như không nhận thức được điều đó.
cubuspl42 27/12/15

5
Sắp xếp yêu cầu O (nlogn) và tìm kiếm nhị phân là O (logn). Nếu bạn cần tìm kiếm nhiều giá trị từ mảng lớn, câu trả lời của bạn có thể tốt hơn, nhưng nếu bạn chỉ tìm kiếm một lần, thuật toán O (n) có thể tốt hơn.
jingyu9575

23
Tôi không biết tại sao không ai khác đề cập đến điều này: phương pháp của bạn không chỉ kém hiệu quả mà còn không chính xác , và điều đó còn tệ hơn nhiều so với sự kém hiệu quả đơn thuần. Yêu cầu là cho vị trí của một số nhất định trong mảng ban đầu . Phương thức của bạn trả về vị trí của số trong một mảng đã sắp xếp . Bây giờ, bạn có thể lấy lại vị trí ban đầu, bằng cách chuyển đổi mảng đơn giản thành một mảng các bộ giá trị (số, orig_pos) trước khi sắp xếp. Nhưng bạn đã không đề cập đến điều đó, vì vậy tôi đoán bạn cũng không đề cập đến nó trong cuộc phỏng vấn.
Tom Zych

Câu trả lời:


126

Bạn có thể thực hiện tìm kiếm tuyến tính với các bước thường lớn hơn 1. Quan sát quan trọng là nếu ví dụ array[i] == 4và 7 chưa xuất hiện thì ứng cử viên tiếp theo cho 7 đang ở chỉ mục i+3. Sử dụng vòng lặp while lặp đi lặp lại trực tiếp đến ứng viên khả thi tiếp theo.

Đây là một triển khai, hơi khái quát. Nó tìm lần xuất hiện đầu tiên của kmảng (tuân theo giới hạn + = 1) hoặc -1nếu nó không xảy ra:

#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;
}

đầu ra:

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

8
Chính xác những gì tôi đang nghĩ. Điều này là vậy O(N), nhưng tôi không nghĩ có cách nào nhanh hơn để làm điều đó.
shapiro yaacov 27/12/15

2
Bạn có thể làm điều đó trung bình nhanh hơn một chút với nhiều ứng cử viên hơn (ví dụ: đầu tiên và cuối cùng), sau đó chọn ứng cử viên gần mục tiêu nhất - nghĩa là nếu bạn chỉ cần tìm một lần xuất hiện duy nhất, không phải lần đầu tiên.
mkadunc

2
@mkadunc Đó là một ý kiến ​​hay. Quan sát khác là nếu các yếu tố đầu tiên và cuối cùng straddle 7 sau đó trong đó trường hợp đặc biệt bạn có thể sử dụng tìm kiếm nhị phân (nếu bạn không quan tâm đó có 7 bạn tìm thấy)
John Coleman

1
Trong trường hợp bạn cần tìm bất kỳ số 7 nào (không nhất thiết phải là cái đầu tiên), tôi đề xuất cải tiến (thiết thực) sau đây. Tạo danh sách các phần (hai số nguyên, 'bắt đầu' và 'kết thúc') và thay vì bắt đầu ở đầu mảng, hãy bắt đầu ở giữa. Theo giá trị trong ô, hãy bỏ qua phạm vi có liên quan và thêm hai phần còn lại vào danh sách các phần của bạn. Bây giờ lặp lại cho mục tiếp theo trong danh sách. Đây vẫn là 'O (n)', nhưng bạn bỏ qua hai lần phạm vi mỗi khi bạn kiểm tra một ô.
shapiro yaacov

3
@ShapiroYaacov: Kết hợp với việc kiểm tra xem khoảng từ thấp hơn đến cao hơn của các giá trị cho cả hai bên của phần có bao gồm k (7) hay không, điều này xứng đáng là một câu trả lời của riêng nó.
greybeard

35

Cách tiếp cận của bạn quá phức tạp. Bạn không cần phải kiểm tra mọi phần tử mảng. Giá trị đầu tiên là 4, vì vậy 7ít nhất 7-4 các yếu tố đi, và bạn có thể bỏ qua những.

#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;
}

Đầu ra chương trình:

Steps 4, index 11

Chỉnh sửa: được cải thiện sau nhận xét từ @Raphael Miedl và @Martin Zabel.


2
Một nitpick, if ((skip = 7 - array[i]) < 1) skip = 1;dường như làm phức tạp nó quá mức và làm nó bi quan theo ý kiến ​​của tôi. Nếu array[i] == 200bạn nhận được -193và chỉ bỏ qua 1 lần mặc dù bạn có thể bỏ qua tất cả 193. Tại sao không chỉ i += abs(7 - array[i])?
user1942027

1
Bạn nên đặt thành skipchênh lệch tuyệt đối giữa 7 và array[i].
Martin Zabel

@Raphael Miedl không, một phần tử sẽ không có 200, bạn sẽ vượt qua 7.
Weather Vane

3
@WeatherVane chúng tôi không có đảm bảo đó, chỉ có các giá trị liền kề là +1/ -1từ nhau. Vì vậy, nó chỉ có thể làarray[0] == 200 và những người khác chủ yếu -1là.
user1942027

1
@WeatherVane điều này có giả định rằng phần tử luôn được tìm thấy trong mảng, điều này có thể không đúng. -1 là lợi nhuận hợp lệ trong trường hợp đó; mà thay đổi mã bạn có khá một chút
Eugene

20

Một biến thể của tìm kiếm tuyến tính thông thường có thể là một cách tốt để thực hiện. Hãy để chúng tôi chọn một phần tử nói array[i] = 2. Bây giờ, array[i + 1]sẽ là 1 hoặc 3 (lẻ), array[i + 2]sẽ là (chỉ số nguyên dương) 2 hoặc 4 (số chẵn).

Tiếp tục như vậy, một mô hình có thể quan sát được - array[i + 2*n]sẽ chứa các số chẵn và vì vậy tất cả các chỉ số này có thể bị bỏ qua.

Ngoài ra, chúng ta có thể thấy rằng

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

vì vậy, chỉ mục i + 5nên được kiểm tra tiếp theo và một vòng lặp while có thể được sử dụng để xác định chỉ mục tiếp theo để kiểm tra, tùy thuộc vào giá trị được tìm thấy tại chỉ mục i + 5.

Mặc dù, điều này có độ phức tạp O(n)(thời gian tuyến tính về độ phức tạp tiệm cận), nó tốt hơn so với tìm kiếm tuyến tính thông thường về mặt thực tế vì tất cả các chỉ số không được truy cập.

Rõ ràng, tất cả điều này sẽ bị đảo ngược nếu array[i](điểm xuất phát của chúng tôi) là kỳ quặc.


8

Cách tiếp cận do John Coleman trình bày là những gì người phỏng vấn hy vọng, trong tất cả các xác suất.
Nếu bạn sẵn sàng làm phức tạp hơn một chút, bạn có thể tăng độ dài bỏ qua dự kiến:
Gọi giá trị mục tiêu là k . Bắt đầu với giá trị v của phần tử đầu tiên ở vị trí p và gọi hiệu kv là dv với giá trị tuyệt đối av . Để tăng tốc độ tìm kiếm phủ định, hãy xem phần tử cuối cùng là giá trị khác u ở vị trí o : nếu dv × du là âm, k có (nếu bất kỳ sự xuất hiện nào của k được chấp nhận, bạn có thể thu hẹp phạm vi chỉ mục tại đây theo cách tìm kiếm nhị phân ). Nếu av + au lớn hơn độ dài của mảng thì k bị vắng mặt. (Nếu dv × du bằng 0, v hoặc u bằng k.)
Bỏ qua giá trị chỉ số: Probe vị trí ( "bên cạnh"), nơi các chuỗi có thể quay trở lại v với k ở giữa: o = p + 2*av.
Nếu dv × du là số âm, hãy tìm k (đệ quy?) Từ p + av đến o-au;
nếu nó bằng 0, u bằng k tại o.
Nếu du bằng dv và giá trị ở giữa không phải là k, hoặc au vượt quá av
hoặc bạn không tìm thấy k từ p + av đến o-au
hãy p=o; dv=du; av=au;tiếp tục thăm dò.
(Để xem lại toàn bộ văn bản của những năm 60, hãy xem bằng Courier. "Ý nghĩ thứ 2 đầu tiên" của tôi là sử dụngo = p + 2*av - 1, loại trừ du bằng dv .)


3

BƯỚC 1

Bắt đầu với phần tử đầu tiên và kiểm tra xem nó có phải là 7. Giả sử clà chỉ số của vị trí hiện tại. Vì vậy, ban đầu c = 0,.

BƯỚC 2

Nếu nó là 7, bạn đã tìm thấy chỉ mục. Nó c. Nếu bạn đã đến cuối mảng, hãy thoát ra.

BƯỚC 3

Nếu không, thì 7 phải ở |array[c]-7|vị trí ít nhất vì bạn chỉ có thể thêm một đơn vị cho mỗi chỉ mục. Do đó, Thêm vào |array[c]-7|chỉ mục hiện tại của bạn, c, và chuyển sang BƯỚC 2 một lần nữa để kiểm tra.

Trong trường hợp xấu nhất, khi có 1 và -1 thay thế, độ phức tạp về thời gian có thể đạt đến O (n), nhưng các trường hợp trung bình sẽ được phân phối nhanh chóng.


Điều này khác với câu trả lời của John Coleman như thế nào? (Ngoài việc đề xuất |c-7|nơi |array[c]-7|có vẻ được yêu cầu.)
greybeard

Tôi vừa thấy câu trả lời của anh ấy. Tôi thừa nhận rằng ý tưởng cốt lõi là giống nhau.
Akeshwar Jha

Câu hỏi ban đầu không quy định rằng mảng bắt đầu bằng một số nhỏ hơn 7. Vì vậy, array[c]-7có thể là dương hoặc âm. Bạn cần phải áp dụng abs()nó trước khi chuyển tiếp bỏ qua.
mình

Vâng bạn đã đúng. Đó là lý do tại sao tôi đang sử dụng array[c] - 7với toán tử mô-đun , |array[c] - 7|.
Akeshwar Jha

3

Ở đây tôi đang đưa ra triển khai trong java ...

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
Mã không có tài liệu bằng một ngôn ngữ có quy ước ít nhất là bán chính thức . Điều này khác với câu trả lời của John Coleman và Akeshwar như thế nào, ngoài việc giải thích thẻ "c" một cách phóng khoáng?
greybeard

3

Đây là một giải pháp phong cách chia để trị. Với chi phí (nhiều) kế toán hơn, chúng ta có thể bỏ qua nhiều yếu tố hơn; thay vì quét từ trái sang phải, hãy kiểm tra ở giữa và bỏ qua theo cả hai hướng.

#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 câu trả lời của bạn sẽ không trả lại lần xuất hiện đầu tiên mà là bất kỳ lần xuất hiện ngẫu nhiên nào của phần tử tìm kiếm khi bạn bắt đầu từ giữa và bỏ qua từ hai bên.
Ram Patra

Việc chuyển đổi thứ tự của đệ quy được để lại như một bài tập cho người đọc.
Neal Fultz

1
neal-fultz sau đó vui lòng chỉnh sửa thông báo trong cuộc gọi phương thức printf () của bạn.
Ram Patra,

1

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);

Muốn bao gồm một giải pháp đệ quy cho vấn đề. Thưởng thức

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.