Bagaimana cara mencari nomor dalam array 2d yang diurutkan dari kiri ke kanan dan dari atas ke bawah?


90

Saya baru-baru ini diberi pertanyaan wawancara ini dan saya ingin tahu apa solusi yang baik untuk itu.

Katakanlah saya diberi array 2d di mana semua angka dalam array berada dalam urutan meningkat dari kiri ke kanan dan dari atas ke bawah.

Apa cara terbaik untuk mencari dan menentukan apakah nomor target ada dalam larik?

Sekarang, kecenderungan pertama saya adalah menggunakan pencarian biner karena data saya diurutkan. Saya dapat menentukan apakah suatu angka berada dalam satu baris dalam waktu O (log N). Namun, 2 arah itulah yang membuat saya menjauh.

Solusi lain yang saya pikir mungkin berhasil adalah memulai di suatu tempat di tengah. Jika nilai tengahnya kurang dari target saya, maka saya bisa yakin itu berada di bagian persegi kiri matriks dari tengah. Saya kemudian bergerak secara diagonal dan memeriksa lagi, mengurangi ukuran persegi yang berpotensi menjadi sasaran target sampai saya mengasah nomor target.

Apakah ada yang punya ide bagus untuk memecahkan masalah ini?

Contoh larik:

Diurutkan dari kiri ke kanan, dari atas ke bawah.

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

Pertanyaan sederhana: mungkin itu yang Anda dapat memiliki tetangga dengan nilai yang sama: [[1 1][1 1]]?
Matthieu M.

Jawaban:


115

Inilah pendekatan sederhana:

  1. Mulai dari pojok kiri bawah.
  2. Jika target kurang dari nilai itu, itu harus di atas kita, jadi naik satu .
  3. Jika tidak, kita tahu bahwa target tidak boleh ada di kolom itu, jadi pindah ke kanan .
  4. Goto 2.

Untuk sebuah NxMarray, ini berjalan di O(N+M). Saya pikir akan sulit untuk berbuat lebih baik. :)


Edit: Banyak diskusi bagus. Saya berbicara tentang kasus umum di atas; jelas, jika Natau M kecil, Anda dapat menggunakan pendekatan pencarian biner untuk melakukan ini pada sesuatu yang mendekati waktu logaritmik.

Berikut beberapa detailnya, bagi yang penasaran:

Sejarah

Algoritma sederhana ini disebut Pencarian Saddleback . Sudah ada untuk sementara waktu, dan saat itu optimal N == M. Beberapa referensi:

Namun, kapan N < M, intuisi menyarankan bahwa pencarian biner seharusnya dapat bekerja lebih baik daripada O(N+M): Misalnya, ketika N == 1, pencarian biner murni akan berjalan dalam waktu logaritmik daripada waktu linier.

Terikat kasus terburuk

Richard Bird meneliti intuisi ini bahwa pencarian biner dapat meningkatkan algoritma Saddleback dalam makalah tahun 2006:

Menggunakan teknik percakapan yang agak tidak biasa, Bird menunjukkan kepada kita bahwa untuk N <= M, masalah ini memiliki batas bawah Ω(N * log(M/N)). Batasan ini masuk akal, karena memberi kita kinerja linier kapan N == Mdan kinerja logaritmik kapan N == 1.

Algoritma untuk array persegi panjang

Salah satu pendekatan yang menggunakan pencarian biner baris demi baris terlihat seperti ini:

  1. Mulailah dengan array persegi panjang di mana N < M. Misalkan Nadalah baris dan Mkolom.
  2. Lakukan pencarian biner di baris tengah untuk value. Jika kita menemukannya, kita sudah selesai.
  3. Jika tidak, kami telah menemukan pasangan angka yang berdekatan sdan g, di mana s < value < g.
  4. Persegi panjang angka di atas dan di kiri skurang dari value, jadi kita bisa menghilangkannya.
  5. Persegi panjang di bawah dan di kanan glebih besar dari value, jadi kita bisa menghilangkannya.
  6. Lanjutkan ke langkah (2) untuk masing-masing dari dua persegi panjang yang tersisa.

Dalam hal kompleksitas kasus terburuk, algoritme ini log(M)berfungsi untuk menghilangkan setengah dari solusi yang mungkin, dan kemudian secara rekursif memanggil dirinya dua kali pada dua masalah yang lebih kecil. Kita memang harus mengulang versi yang lebih kecil dari log(M)pekerjaan itu untuk setiap baris, tetapi jika jumlah barisnya kecil dibandingkan dengan jumlah kolom, maka kemampuan untuk menghilangkan semua kolom tersebut dalam waktu logaritmik mulai menjadi bermanfaat .

Ini memberi algoritme kompleksitas T(N,M) = log(M) + 2 * T(M/2, N/2), yang ditunjukkan oleh Bird O(N * log(M/N)).

Pendekatan lain yang diposting oleh Craig Gidney menjelaskan algoritme yang mirip dengan pendekatan di atas: ini memeriksa baris pada satu waktu menggunakan ukuran langkah M/N. Analisisnya menunjukkan bahwa ini juga menghasilkan O(N * log(M/N))kinerja.

Perbandingan Kinerja

Analisis Big-O semuanya baik dan bagus, tetapi seberapa baik pendekatan ini bekerja dalam praktik? Bagan di bawah ini memeriksa empat algoritme untuk larik yang semakin "persegi":

kinerja algoritma vs kuadrat

(Algoritme "naif" hanya menelusuri setiap elemen larik. Algoritme "rekursif" dijelaskan di atas. Algoritme "hibrid" adalah implementasi algoritme Gidney . Untuk setiap ukuran larik, kinerja diukur dengan mengatur waktu setiap algoritme pada set tetap dari 1.000.000 array yang dihasilkan secara acak.)

Beberapa poin penting:

  • Seperti yang diharapkan, algoritme "penelusuran biner" menawarkan kinerja terbaik pada larik persegi panjang dan algoritme Saddleback bekerja paling baik pada larik persegi.
  • Algoritme Saddleback berkinerja lebih buruk daripada algoritme "naif" untuk larik 1-hari, mungkin karena algoritme ini melakukan banyak perbandingan pada setiap item.
  • Kinerja hit yang diambil oleh algoritme "penelusuran biner" pada larik persegi mungkin karena overhead dalam menjalankan penelusuran biner berulang.

Ringkasan

Penggunaan pencarian biner yang cerdas dapat memberikan O(N * log(M/N)kinerja untuk array persegi panjang dan persegi. The O(N + M)"Saddleback" algoritma jauh lebih sederhana, tetapi menderita dari penurunan kinerja sebagai array menjadi semakin persegi panjang.


6
terapkan pencarian biner ke jalan diagonal dan Anda mendapatkan O (logN) atau O (logM) mana saja yang lebih tinggi.
Anurag

4
@Anurag - Saya rasa kerumitannya tidak berjalan sebaik itu. Pencarian biner akan memberi Anda tempat yang baik untuk memulai, tetapi Anda harus berjalan sepanjang satu dimensi atau yang lain, dan dalam kasus terburuk, Anda masih dapat memulai di satu sudut dan berakhir di sudut lainnya.
Jeffrey L Whitledge

1
Jika N = 1 dan M = 1000000 i dapat melakukan lebih baik daripada O (N + M), Jadi solusi lain adalah menerapkan pencarian biner di setiap baris yang menghasilkan O (N * log (M)) di mana N <M jika ini menghasilkan konstanta yang lebih kecil.
Luka Rahne

1
Saya melakukan beberapa tes menggunakan metode Anda dan metode pencarian biner dan memposting hasilnya DI SINI . Tampaknya metode zigzag adalah yang terbaik, kecuali jika saya gagal menghasilkan kondisi kasus terburuk untuk kedua metode dengan benar.
The111

1
Penggunaan referensi yang bagus! Namun ketika M==Nkita menginginkan O(N)kompleksitas, bukan O(N*log(N/N))karena yang terakhir adalah nol. Batas tajam "terpadu" yang benar adalah O(N*(log(M/N)+1))kapan N<=M.
hardmath

35

Masalah ini membutuhkan Θ(b lg(t))waktu, dimana b = min(w,h)dan t=b/max(w,h). Saya membahas solusinya di posting blog ini .

Batas bawah

Musuh dapat memaksa algoritme untuk membuat Ω(b lg(t))kueri, dengan membatasi dirinya pada diagonal utama:

Musuh menggunakan diagonal utama

Legenda: sel putih adalah item yang lebih kecil, sel abu-abu adalah item yang lebih besar, sel kuning adalah item yang lebih kecil atau sama, dan sel oranye adalah item yang lebih besar atau sama. Musuh memaksa solusi menjadi sel kuning atau oranye mana pun yang kueri algoritmanya terakhir.

Perhatikan bahwa ada bdaftar ukuran yang diurutkan secara independen t, yang memerlukan Ω(b lg(t))kueri untuk dihilangkan sepenuhnya.

Algoritma

  1. (Asumsikan tanpa kehilangan keumuman bahwa w >= h)
  2. Bandingkan item target dengan sel tdi kiri pojok kanan atas area yang valid
    • Jika item sel cocok, kembalikan posisi saat ini.
    • Jika item sel kurang dari item target, hilangkan tsel yang tersisa di baris dengan pencarian biner. Jika item yang cocok ditemukan saat melakukan ini, kembali dengan posisinya.
    • Jika tidak, item sel lebih dari item target, menghilangkan tkolom pendek.
  3. Jika tidak ada area valid tersisa, kembalikan kegagalan
  4. Lanjutkan langkah 2

Menemukan sebuah item:

Menemukan sebuah item

Menentukan item tidak ada:

Menentukan item tidak ada

Legenda: sel putih adalah item yang lebih kecil, sel abu-abu adalah item yang lebih besar, dan sel hijau adalah item yang sama.

Analisis

Ada b*tkolom pendek untuk dihilangkan. Ada bbaris panjang yang harus dihilangkan. Menghilangkan pertengkaran yang panjang membutuhkan O(lg(t))waktu. Menghilangkan tkolom pendek membutuhkan O(1)waktu.

Dalam kasus terburuk kita harus menghilangkan setiap kolom dan setiap baris, memakan waktu O(lg(t)*b + b*t*1/t) = O(b lg(t)).

Perhatikan bahwa saya mengasumsikan lgklem ke hasil di atas 1 (yaitu lg(x) = log_2(max(2,x))). Itu sebabnya ketika w=h, artinya t=1, kita mendapatkan batas yang diharapkan O(b lg(1)) = O(b) = O(w+h).

Kode

public static Tuple<int, int> TryFindItemInSortedMatrix<T>(this IReadOnlyList<IReadOnlyList<T>> grid, T item, IComparer<T> comparer = null) {
    if (grid == null) throw new ArgumentNullException("grid");
    comparer = comparer ?? Comparer<T>.Default;

    // check size
    var width = grid.Count;
    if (width == 0) return null;
    var height = grid[0].Count;
    if (height < width) {
        var result = grid.LazyTranspose().TryFindItemInSortedMatrix(item, comparer);
        if (result == null) return null;
        return Tuple.Create(result.Item2, result.Item1);
    }

    // search
    var minCol = 0;
    var maxRow = height - 1;
    var t = height / width;
    while (minCol < width && maxRow >= 0) {
        // query the item in the minimum column, t above the maximum row
        var luckyRow = Math.Max(maxRow - t, 0);
        var cmpItemVsLucky = comparer.Compare(item, grid[minCol][luckyRow]);
        if (cmpItemVsLucky == 0) return Tuple.Create(minCol, luckyRow);

        // did we eliminate t rows from the bottom?
        if (cmpItemVsLucky < 0) {
            maxRow = luckyRow - 1;
            continue;
        }

        // we eliminated most of the current minimum column
        // spend lg(t) time eliminating rest of column
        var minRowInCol = luckyRow + 1;
        var maxRowInCol = maxRow;
        while (minRowInCol <= maxRowInCol) {
            var mid = minRowInCol + (maxRowInCol - minRowInCol + 1) / 2;
            var cmpItemVsMid = comparer.Compare(item, grid[minCol][mid]);
            if (cmpItemVsMid == 0) return Tuple.Create(minCol, mid);
            if (cmpItemVsMid > 0) {
                minRowInCol = mid + 1;
            } else {
                maxRowInCol = mid - 1;
                maxRow = mid - 1;
            }
        }

        minCol += 1;
    }

    return null;
}

1
Menarik dan mungkin sebagian di atas kepalaku. Saya tidak akrab dengan gaya analisis kompleksitas "musuh" ini. Apakah musuh sebenarnya entah bagaimana secara dinamis mengubah larik saat Anda menelusuri, atau dia hanya nama yang diberikan untuk nasib buruk yang Anda temui dalam penelusuran kasus terburuk?
The111

2
@ The111 Nasib buruk setara dengan seseorang yang memilih jalan buruk yang tidak melanggar hal-hal yang terlihat sejauh ini, jadi kedua definisi tersebut bekerja sama. Saya sebenarnya kesulitan menemukan tautan yang menjelaskan teknik ini secara khusus sehubungan dengan kompleksitas komputasi ... Saya pikir ini adalah ide yang jauh lebih terkenal.
Craig Gidney

Karena log (1) = 0, estimasi kompleksitas harus diberikan sebagai O(b*(lg(t)+1))pengganti O(b*lg(t)). Tulisan yang bagus, khususnya. untuk menarik perhatian pada "teknik musuh" dalam menunjukkan ikatan "kasus terburuk".
hardmath

@hardmath saya sebutkan itu dalam jawaban. Saya menjelaskannya sedikit.
Craig Gidney

17

Saya akan menggunakan strategi bagi-dan-taklukkan untuk masalah ini, mirip dengan yang Anda sarankan, tetapi detailnya sedikit berbeda.

Ini akan menjadi pencarian rekursif pada subrange matriks.

Di setiap langkah, pilih elemen di tengah rentang. Jika nilai yang ditemukan adalah apa yang Anda cari, maka Anda sudah selesai.

Sebaliknya, jika nilai yang ditemukan lebih kecil dari nilai yang Anda cari, maka Anda tahu bahwa nilai tersebut tidak berada di kuadran atas dan di kiri posisi Anda saat ini. Jadi secara rekursif mencari dua subrange: semuanya (eksklusif) di bawah posisi saat ini, dan semua (eksklusif) di sebelah kanan yang berada di atau di atas posisi saat ini.

Jika tidak, (nilai yang ditemukan lebih besar dari nilai yang Anda cari) Anda tahu bahwa nilai tersebut tidak berada di kuadran di bawah dan di sebelah kanan posisi Anda saat ini. Jadi secara rekursif mencari dua subrange: semuanya (eksklusif) di kiri posisi saat ini, dan semua (eksklusif) di atas posisi saat ini yang ada di kolom saat ini atau kolom di sebelah kanan.

Dan ba-da-bing, Anda menemukannya.

Perhatikan bahwa setiap panggilan rekursif hanya menangani subrentang saat ini saja, bukan (misalnya) SEMUA baris di atas posisi saat ini. Hanya yang ada di subrentang saat ini.

Berikut beberapa pseudocode untuk Anda:

bool numberSearch(int[][] arr, int value, int minX, int maxX, int minY, int maxY)

if (minX == maxX and minY == maxY and arr[minX,minY] != value)
    return false
if (arr[minX,minY] > value) return false;  // Early exits if the value can't be in 
if (arr[maxX,maxY] < value) return false;  // this subrange at all.
int nextX = (minX + maxX) / 2
int nextY = (minY + maxY) / 2
if (arr[nextX,nextY] == value)
{
    print nextX,nextY
    return true
}
else if (arr[nextX,nextY] < value)
{
    if (numberSearch(arr, value, minX, maxX, nextY + 1, maxY))
        return true
    return numberSearch(arr, value, nextX + 1, maxX, minY, nextY)
}
else
{
    if (numberSearch(arr, value, minX, nextX - 1, minY, maxY))
        return true
    reutrn numberSearch(arr, value, nextX, maxX, minY, nextY)
}

+1: Ini adalah strategi O (log (N)), dan dengan demikian order yang bagus akan didapatkan.
Rex Kerr

3
@Rex Kerr - Sepertinya O (log (N)), karena itulah pencarian biner normal, bagaimanapun, perhatikan bahwa ada kemungkinan dua panggilan rekursif di setiap level. Ini berarti jauh lebih buruk daripada logaritmik biasa. Saya tidak percaya kasus yang lebih buruk lebih baik daripada O (M + N) karena, secara potensial, setiap baris atau setiap kolom harus dicari. Saya akan menebak bahwa algoritma ini dapat mengalahkan kasus terburuk untuk banyak nilai. Dan bagian terbaiknya adalah itu dapat dilumpuhkan, karena ke sanalah perangkat keras diarahkan belakangan ini.
Jeffrey L Whitledge

1
@JLW: Ini adalah O (log (N)) - tapi sebenarnya O (log_ (4/3) (N ^ 2)) atau sesuatu seperti itu. Lihat jawaban Svante di bawah ini. Jawaban Anda sebenarnya sama (jika yang Anda maksud adalah rekursif seperti yang saya pikir Anda lakukan).
Rex Kerr

1
@Svante - Subarray tidak tumpang tindih. Pada opsi pertama, mereka tidak memiliki elemen y yang sama. Pada opsi kedua, mereka tidak memiliki elemen x yang sama.
Jeffrey L Whitledge

1
Saya tidak yakin apakah ini logaritmik. Saya menghitung kompleksitas menggunakan perkiraan relasi pengulangan T (0) = 1, T (A) = T (A / 2) + T (A / 4) + 1, di mana A adalah area pencarian, dan berakhir dengan T ( A) = O (Fib (lg (A))), yang kira-kira O (A ^ 0,7) dan lebih buruk dari O (n + m) yaitu O (A ^ 0,5). Mungkin saya membuat kesalahan bodoh, tetapi sepertinya algoritme ini membuang banyak waktu untuk menjatuhkan cabang yang tidak membuahkan hasil.
Craig Gidney

6

Dua jawaban utama yang diberikan sejauh ini tampaknya adalah O(log N)"metode ZigZag" dan O(N+M)metode Pencarian Biner. Saya pikir saya akan melakukan beberapa pengujian yang membandingkan dua metode dengan beberapa pengaturan yang berbeda. Berikut detailnya:

Array adalah N x N persegi di setiap pengujian, dengan N bervariasi dari 125 hingga 8000 (tumpukan JVM terbesar saya dapat menangani). Untuk setiap ukuran array, saya memilih tempat acak dalam array untuk meletakkan satu 2. Saya kemudian meletakkan di 3mana saja mungkin (ke kanan dan di bawah 2) dan kemudian mengisi sisa array dengan1. Beberapa pemberi komentar sebelumnya tampaknya berpikir jenis pengaturan ini akan menghasilkan run time kasus terburuk untuk kedua algoritme. Untuk setiap ukuran array, saya memilih 100 lokasi acak yang berbeda untuk 2 (target pencarian) dan menjalankan pengujian. Saya mencatat waktu pengoperasian rata-rata dan waktu pengoperasian kasus terburuk untuk setiap algoritme. Karena itu terjadi terlalu cepat untuk mendapatkan pembacaan ms yang baik di Java, dan karena saya tidak mempercayai nanoTime () Java, saya mengulangi setiap pengujian 1000 kali hanya untuk menambahkan faktor bias yang seragam setiap saat. Berikut hasilnya:

masukkan deskripsi gambar di sini

ZigZag mengalahkan biner di setiap pengujian untuk waktu rata-rata dan kasus terburuk, namun, semuanya berada dalam urutan besarnya satu sama lain lebih atau kurang.

Ini kode Java:

public class SearchSortedArray2D {

    static boolean findZigZag(int[][] a, int t) {
        int i = 0;
        int j = a.length - 1;
        while (i <= a.length - 1 && j >= 0) {
            if (a[i][j] == t) return true;
            else if (a[i][j] < t) i++;
            else j--;
        }
        return false;
    }

    static boolean findBinarySearch(int[][] a, int t) {
        return findBinarySearch(a, t, 0, 0, a.length - 1, a.length - 1);
    }

    static boolean findBinarySearch(int[][] a, int t,
            int r1, int c1, int r2, int c2) {
        if (r1 > r2 || c1 > c2) return false; 
        if (r1 == r2 && c1 == c2 && a[r1][c1] != t) return false;
        if (a[r1][c1] > t) return false;
        if (a[r2][c2] < t) return false;

        int rm = (r1 + r2) / 2;
        int cm = (c1 + c2) / 2;
        if (a[rm][cm] == t) return true;
        else if (a[rm][cm] > t) {
            boolean b1 = findBinarySearch(a, t, r1, c1, r2, cm - 1);
            boolean b2 = findBinarySearch(a, t, r1, cm, rm - 1, c2);
            return (b1 || b2);
        } else {
            boolean b1 = findBinarySearch(a, t, r1, cm + 1, rm, c2);
            boolean b2 = findBinarySearch(a, t, rm + 1, c1, r2, c2);
            return (b1 || b2);
        }
    }

    static void randomizeArray(int[][] a, int N) {
        int ri = (int) (Math.random() * N);
        int rj = (int) (Math.random() * N);
        a[ri][rj] = 2;
        for (int i = 0; i < N; i++) {
            for (int j = 0; j < N; j++) {
                if (i == ri && j == rj) continue;
                else if (i > ri || j > rj) a[i][j] = 3;
                else a[i][j] = 1;
            }
        }
    }

    public static void main(String[] args) {

        int N = 8000;
        int[][] a = new int[N][N];
        int randoms = 100;
        int repeats = 1000;

        long start, end, duration;
        long zigMin = Integer.MAX_VALUE, zigMax = Integer.MIN_VALUE;
        long binMin = Integer.MAX_VALUE, binMax = Integer.MIN_VALUE;
        long zigSum = 0, zigAvg;
        long binSum = 0, binAvg;

        for (int k = 0; k < randoms; k++) {
            randomizeArray(a, N);

            start = System.currentTimeMillis();
            for (int i = 0; i < repeats; i++) findZigZag(a, 2);
            end = System.currentTimeMillis();
            duration = end - start;
            zigSum += duration;
            zigMin = Math.min(zigMin, duration);
            zigMax = Math.max(zigMax, duration);

            start = System.currentTimeMillis();
            for (int i = 0; i < repeats; i++) findBinarySearch(a, 2);
            end = System.currentTimeMillis();
            duration = end - start;
            binSum += duration;
            binMin = Math.min(binMin, duration);
            binMax = Math.max(binMax, duration);
        }
        zigAvg = zigSum / randoms;
        binAvg = binSum / randoms;

        System.out.println(findZigZag(a, 2) ?
                "Found via zigzag method. " : "ERROR. ");
        //System.out.println("min search time: " + zigMin + "ms");
        System.out.println("max search time: " + zigMax + "ms");
        System.out.println("avg search time: " + zigAvg + "ms");

        System.out.println();

        System.out.println(findBinarySearch(a, 2) ?
                "Found via binary search method. " : "ERROR. ");
        //System.out.println("min search time: " + binMin + "ms");
        System.out.println("max search time: " + binMax + "ms");
        System.out.println("avg search time: " + binAvg + "ms");
    }
}

1
+1 Hore, data. :) Mungkin juga menarik untuk melihat bagaimana kedua pendekatan ini berjalan pada array NxM, karena pencarian biner sepertinya secara intuitif menjadi lebih berguna jika kita semakin mendekati kasus 1-dimensi.
Nate Kohl

5

Ini adalah bukti singkat batas bawah masalah.

Anda tidak dapat melakukannya dengan lebih baik daripada waktu linier (dalam hal dimensi array, bukan jumlah elemen). Dalam larik di bawah ini, setiap elemen yang ditandai sebagai *dapat berupa 5 atau 6 (terlepas dari yang lain). Jadi, jika nilai target Anda adalah 6 (atau 5), algoritme perlu memeriksa semuanya.

1 2 3 4 *
2 3 4 * 7
3 4 * 7 8
4 * 7 8 9
* 7 8 9 10

Tentu saja ini meluas ke array yang lebih besar juga. Artinya jawaban ini sudah optimal.

Pembaruan: Seperti yang ditunjukkan oleh Jeffrey L Whitledge, ini hanya optimal sebagai batas bawah asimtotik pada waktu berjalan vs ukuran data masukan (diperlakukan sebagai variabel tunggal). Waktu berjalan yang diperlakukan sebagai fungsi dua variabel pada kedua dimensi array dapat ditingkatkan.


Anda belum menunjukkan bahwa jawaban itu optimal. Pertimbangkan, misalnya, larik sepuluh-melintasi dan satu-juta ke bawah di mana baris kelima berisi semua nilai lebih tinggi dari nilai target. Dalam hal ini algoritma yang diusulkan akan melakukan pencarian linier hingga 999.995 nilai sebelum mendekati target. Algoritme yang bercabang dua seperti milik saya hanya akan mencari 18 nilai sebelum mendekati target. Dan kinerjanya (secara asimtotik) tidak lebih buruk dari algoritme yang diusulkan di semua kasus lainnya.
Jeffrey L Whitledge

@Jeffrey: Ini adalah batas bawah masalah untuk kasus pesimis. Anda dapat mengoptimalkan input yang baik, tetapi ada input yang tidak dapat Anda lakukan lebih baik daripada linier.
Rafał Dowgird

Ya, memang ada masukan di mana Anda tidak dapat melakukan lebih baik daripada linier. Dalam hal ini algoritme saya melakukan pencarian linier itu. Tetapi ada masukan lain di mana Anda bisa melakukan jauh lebih baik daripada linier. Dengan demikian solusi yang diusulkan tidak optimal, karena selalu melakukan pencarian linier.
Jeffrey L Whitledge

Ini menunjukkan bahwa algoritma harus mengambil waktu BigOmega (min (n, m)), bukan BigOmega (n + m). Itulah mengapa Anda dapat melakukan jauh lebih baik ketika satu dimensi jauh lebih kecil. Misalnya, jika Anda tahu hanya akan ada 1 baris, Anda bisa menyelesaikan soal dalam waktu logaritmik. Saya pikir algoritma yang optimal akan memakan waktu O (min (n + m, n lg m, m lg n)).
Craig Gidney

Memperbarui jawaban yang sesuai.
Rafał Dowgird

4

Saya pikir Inilah jawabannya dan berfungsi untuk semua jenis matriks yang diurutkan

bool findNum(int arr[][ARR_MAX],int xmin, int xmax, int ymin,int ymax,int key)
{
    if (xmin > xmax || ymin > ymax || xmax < xmin || ymax < ymin) return false;
    if ((xmin == xmax) && (ymin == ymax) && (arr[xmin][ymin] != key)) return false;
    if (arr[xmin][ymin] > key || arr[xmax][ymax] < key) return false;
    if (arr[xmin][ymin] == key || arr[xmax][ymax] == key) return true;

    int xnew = (xmin + xmax)/2;
    int ynew = (ymin + ymax)/2;

    if (arr[xnew][ynew] == key) return true;
    if (arr[xnew][ynew] < key)
    {
        if (findNum(arr,xnew+1,xmax,ymin,ymax,key))
            return true;
        return (findNum(arr,xmin,xmax,ynew+1,ymax,key));
    } else {
        if (findNum(arr,xmin,xnew-1,ymin,ymax,key))
            return true;
        return (findNum(arr,xmin,xmax,ymin,ynew-1,key));
    }
}

1

Pertanyaan menarik. Pertimbangkan ide ini - buat satu batas di mana semua angkanya lebih besar dari target Anda dan yang lain di mana semua angkanya kurang dari target Anda. Jika ada yang tertinggal di antara keduanya, itu target Anda.

Jika saya mencari 3 dalam contoh Anda, saya membaca di baris pertama sampai saya mencapai 4, lalu mencari angka terdekat terkecil (termasuk diagonal) lebih besar dari 3:

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

Sekarang saya melakukan hal yang sama untuk angka-angka yang kurang dari 3:

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

Sekarang saya bertanya, apakah ada yang di dalam dua batasan itu? Jika ya, pasti 3. Jika tidak, maka tidak ada 3. Urutkan tidak langsung karena saya tidak benar-benar menemukan nomornya, saya hanya menyimpulkan bahwa itu pasti ada. Ini memiliki bonus tambahan untuk menghitung SEMUA 3.

Saya mencoba ini pada beberapa contoh dan tampaknya berfungsi dengan baik.


A down voting with no comment? Saya pikir ini adalah O (N ^ 1/2) karena kinerja kasus terburuk memerlukan pemeriksaan diagonal. Setidaknya tunjukkan saya contoh balasan di mana metode ini tidak berhasil!
Grembo

+1: solusi bagus ... kreatif, dan bagus karena menemukan semua solusi.
Tony Delroy

1

Pencarian biner melalui diagonal array adalah pilihan terbaik. Kita dapat mengetahui apakah elemen tersebut kurang dari atau sama dengan elemen di diagonal.


0

A. Lakukan pencarian biner pada baris di mana nomor target mungkin berada.

B.Buatlah menjadi grafik: Carilah angkanya dengan selalu mengambil simpul tetangga terkecil yang belum dikunjungi dan mundur ketika nomor yang terlalu besar ditemukan


0

Pencarian biner akan menjadi pendekatan terbaik, imo. Mulai dari 1/2 x, 1/2 y akan dipotong menjadi dua. IE persegi 5x5 akan menjadi seperti x == 2 / y == 3. Saya membulatkan satu nilai ke bawah dan satu nilai ke atas ke zona yang lebih baik ke arah nilai yang ditargetkan.

Untuk kejelasan, iterasi berikutnya akan memberi Anda sesuatu seperti x == 1 / y == 2 ATAU x == 3 / y == 5


0

Untuk memulainya, mari kita asumsikan kita menggunakan persegi.

1 2 3
2 3 4
3 4 5

1. Mencari kotak

Saya akan menggunakan pencarian biner di diagonal. Tujuannya adalah menemukan angka yang lebih kecil yang tidak lebih rendah dari angka target.

Katakanlah saya mencari 4misalnya, maka saya akan menemukan 5di (2,2).

Kemudian, saya yakin bahwa jika 4ada di tabel, itu ada di posisi baik (x,2)atau (2,x)dengan xmasuk [0,2]. Nah, itu hanya 2 pencarian biner.

Kompleksitasnya tidak menakutkan: O(log(N))(3 pencarian biner pada rentang panjang N)

2. Mencari pendekatan yang naif dan persegi panjang

Tentu saja, ini menjadi sedikit lebih rumit ketika Ndan Mberbeda (dengan persegi panjang), pertimbangkan kasus yang merosot ini:

1  2  3  4  5  6  7  8
2  3  4  5  6  7  8  9
10 11 12 13 14 15 16 17

Dan katakanlah saya mencari 9... Pendekatan diagonal masih bagus, tetapi definisi perubahan diagonal. Di sini diagonal saya [1, (5 or 6), 17]. Katakanlah saya mengambil [1,5,17], maka saya tahu bahwa jika 9ada di tabel itu ada di sub bagian:

            5  6  7  8
            6  7  8  9
10 11 12 13 14 15 16

Ini memberi kita 2 persegi panjang:

5 6 7 8    10 11 12 13 14 15 16
6 7 8 9

Jadi kita bisa mengulang! mungkin dimulai dengan yang memiliki lebih sedikit elemen (meskipun dalam hal ini membunuh kita).

Saya harus menunjukkan bahwa jika salah satu dimensi lebih kecil dari 3, kita tidak dapat menerapkan metode diagonal dan harus menggunakan pencarian biner. Ini artinya:

  • Terapkan pencarian biner pada 10 11 12 13 14 15 16, tidak ditemukan
  • Terapkan pencarian biner pada 5 6 7 8, tidak ditemukan
  • Terapkan pencarian biner pada 6 7 8 9, tidak ditemukan

Ini rumit karena untuk mendapatkan kinerja yang baik Anda mungkin ingin membedakan antara beberapa kasing, tergantung pada bentuk umum ....

3. Pencarian persegi panjang, pendekatan brutal

Akan jauh lebih mudah jika kita berurusan dengan persegi ... jadi mari kita persegi.

1  2  3  4  5  6  7  8
2  3  4  5  6  7  8  9
10 11 12 13 14 15 16 17
17 .  .  .  .  .  .  17
.                    .
.                    .
.                    .
17 .  .  .  .  .  .  17

Kami sekarang memiliki persegi.

Tentu saja, kami mungkin TIDAK akan benar-benar membuat baris tersebut, kami dapat dengan mudah meniru mereka.

def get(x,y):
  if x < N and y < M: return table[x][y]
  else: return table[N-1][M-1]            # the max

sehingga berperilaku seperti persegi tanpa menempati lebih banyak memori (dengan mengorbankan kecepatan, mungkin, tergantung pada cache ... oh well: p)


0

EDIT:

Saya salah paham dengan pertanyaan itu. Seperti yang ditunjukkan oleh komentar, ini hanya berfungsi dalam kasus yang lebih terbatas.

Dalam bahasa seperti C yang menyimpan data dalam urutan baris-mayor, perlakukan saja sebagai larik 1D berukuran n * m dan gunakan penelusuran biner.


Ya, mengapa membuatnya lebih kompleks dari yang seharusnya.
erikkallen

Array tidak diurutkan, sehingga tidak ada pencarian bin yang dapat diterapkan padanya
Miollnyr

1
Ini hanya akan berfungsi jika elemen terakhir dari setiap baris lebih tinggi dari elemen pertama pada baris berikutnya, yang merupakan persyaratan yang jauh lebih ketat daripada masalah yang diajukan.
Jeffrey L Whitledge

Terima kasih, saya telah mengedit jawaban saya. Tidak membaca dengan cukup cermat, terutama contoh larik.
Hugh Brackett

0

Saya memiliki Solusi Divide & Conquer rekursif. Ide Dasar untuk satu langkah adalah: Kita tahu bahwa Kiri-Atas (LU) terkecil dan kanan-bawah (RB) adalah no terbesar, jadi No (N) yang diberikan harus: N> = LU dan N <= RB

IF N == LU dan N == RB :::: Element Found dan Abort mengembalikan posisi / Index Jika N> = LU dan N <= RB = FALSE, No tidak ada dan batalkan. Jika N> = LU dan N <= RB = TRUE, Bagilah larik 2D dalam 4 bagian yang sama dari larik 2D masing-masing secara logis .. Dan kemudian terapkan langkah algo yang sama ke keempat sub-larik.

Algo saya Benar Saya telah menerapkan di PC teman saya. Kompleksitas: masing-masing 4 perbandingan dapat digunakan untuk menyimpulkan tidak total elemen menjadi seperempat pada kasus terburuknya .. Jadi kompleksitas saya menjadi 1 + 4 x lg (n) + 4 Tapi benar-benar berharap ini akan bekerja pada O (n)

Saya pikir ada yang salah di suatu tempat dalam penghitungan Kompleksitas saya, harap perbaiki jika demikian ..


0

Solusi optimal adalah mulai dari sudut kiri atas, yang memiliki nilai minimal. Pindah secara diagonal ke bawah ke kanan sampai Anda mencapai elemen yang nilainya> = nilai elemen yang diberikan. Jika nilai elemen sama dengan elemen yang diberikan, kembalikan ditemukan sebagai benar.

Jika tidak, dari sini kita dapat melanjutkan dengan dua cara.

Strategi 1:

  1. Pindah ke atas di kolom dan cari elemen yang diberikan hingga kita mencapai akhir. Jika ditemukan, pengembalian ditemukan sebagai benar
  2. Pindah ke kiri di baris dan cari elemen yang diberikan hingga kita mencapai akhir. Jika ditemukan, pengembalian ditemukan sebagai benar
  3. kembali ditemukan sebagai salah

Strategi 2: Misalkan i menunjukkan indeks baris dan j menunjukkan indeks kolom dari elemen diagonal tempat kita berhenti. (Di sini, kami memiliki i = j, BTW). Misalkan k = 1.

  • Ulangi langkah-langkah di bawah ini sampai ik> = 0
    1. Cari jika a [ik] [j] sama dengan elemen yang diberikan. jika ya, kembali ditemukan sebagai benar.
    2. Cari jika a [i] [jk] sama dengan elemen yang diberikan. jika ya, kembali ditemukan sebagai benar.
    3. Kenaikan k

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


0
public boolean searchSortedMatrix(int arr[][] , int key , int minX , int maxX , int minY , int maxY){

    // base case for recursion
    if(minX > maxX || minY > maxY)
        return false ;
    // early fails
    // array not properly intialized
    if(arr==null || arr.length==0)
        return false ;
    // arr[0][0]> key return false
    if(arr[minX][minY]>key)
        return false ;
    // arr[maxX][maxY]<key return false
    if(arr[maxX][maxY]<key)
        return false ;
    //int temp1 = minX ;
    //int temp2 = minY ;
    int midX = (minX+maxX)/2 ;
    //if(temp1==midX){midX+=1 ;}
    int midY = (minY+maxY)/2 ;
    //if(temp2==midY){midY+=1 ;}


    // arr[midX][midY] = key ? then value found
    if(arr[midX][midY] == key)
        return true ;
    // alas ! i have to keep looking

    // arr[midX][midY] < key ? search right quad and bottom matrix ;
    if(arr[midX][midY] < key){
        if( searchSortedMatrix(arr ,key , minX,maxX , midY+1 , maxY))
            return true ;
        // search bottom half of matrix
        if( searchSortedMatrix(arr ,key , midX+1,maxX , minY , maxY))
            return true ;
    }
    // arr[midX][midY] > key ? search left quad matrix ;
    else {
         return(searchSortedMatrix(arr , key , minX,midX-1,minY,midY-1));
    }
    return false ;

}

0

Saya sarankan, simpan semua karakter dalam file 2D list. kemudian temukan indeks elemen yang diperlukan jika ada dalam daftar.

Jika tidak ada, cetak pesan yang sesuai, jika tidak, cetak baris dan kolom sebagai:

row = (index/total_columns) dan column = (index%total_columns -1)

Ini hanya akan menghasilkan waktu pencarian biner dalam daftar.

Harap sarankan koreksi apa pun. :)


0

Jika solusi O (M log (N)) baik-baik saja untuk array MxN -

template <size_t n>
struct MN * get(int a[][n], int k, int M, int N){
  struct MN *result = new MN;
  result->m = -1;
  result->n = -1;

  /* Do a binary search on each row since rows (and columns too) are sorted. */
  for(int i = 0; i < M; i++){
    int lo = 0; int hi = N - 1;
    while(lo <= hi){
      int mid = lo + (hi-lo)/2;
      if(k < a[i][mid]) hi = mid - 1;
      else if (k > a[i][mid]) lo = mid + 1;
      else{
        result->m = i;
        result->n = mid;
        return result;
      }
    }
  }
  return result;
}

Bekerja demo C ++.

Tolong beri tahu saya jika ini tidak akan berhasil atau jika ada bug.


0

Saya telah menanyakan pertanyaan ini dalam wawancara selama lebih dari satu dekade dan saya pikir hanya ada satu orang yang dapat menghasilkan algoritma yang optimal.

Solusi saya selalu:

  1. Pencarian biner di tengah diagonal, yang merupakan diagonal mengalir ke bawah dan ke kanan, berisi item di (rows.count/2, columns.count/2).

  2. Jika nomor target ditemukan, kembalikan true.

  3. Jika tidak, dua angka ( udan v) akan ditemukan sehingga ulebih kecil dari target, vlebih besar dari target, dan vsatu kanan dan satu turun dari u.

  4. Mencari sub-matriks secara berulang di kanan udan atas dan sub-matriks vdi bawah udan kiri v.

Saya percaya ini adalah peningkatan yang ketat atas algoritma yang diberikan oleh Nate di sini , karena mencari diagonal sering kali memungkinkan pengurangan lebih dari setengah ruang pencarian (jika matriksnya mendekati persegi), sedangkan mencari baris atau kolom selalu menghasilkan eliminasi dari tepat setengah.

Berikut kode di Swift (mungkin tidak terlalu Swifty):

import Cocoa

class Solution {
    func searchMatrix(_ matrix: [[Int]], _ target: Int) -> Bool {
        if (matrix.isEmpty || matrix[0].isEmpty) {
            return false
        }

        return _searchMatrix(matrix, 0..<matrix.count, 0..<matrix[0].count, target)
    }

    func _searchMatrix(_ matrix: [[Int]], _ rows: Range<Int>, _ columns: Range<Int>, _ target: Int) -> Bool {
        if (rows.count == 0 || columns.count == 0) {
            return false
        }
        if (rows.count == 1) {
            return _binarySearch(matrix, rows.lowerBound, columns, target, true)
        }
        if (columns.count == 1) {
            return _binarySearch(matrix, columns.lowerBound, rows, target, false)
        }

        var lowerInflection = (-1, -1)
        var upperInflection = (Int.max, Int.max)
        var currentRows = rows
        var currentColumns = columns
        while (currentRows.count > 0 && currentColumns.count > 0 && upperInflection.0 > lowerInflection.0+1) {
            let rowMidpoint = (currentRows.upperBound + currentRows.lowerBound) / 2
            let columnMidpoint = (currentColumns.upperBound + currentColumns.lowerBound) / 2
            let value = matrix[rowMidpoint][columnMidpoint]
            if (value == target) {
                return true
            }

            if (value > target) {
                upperInflection = (rowMidpoint, columnMidpoint)
                currentRows = currentRows.lowerBound..<rowMidpoint
                currentColumns = currentColumns.lowerBound..<columnMidpoint
            } else {
                lowerInflection = (rowMidpoint, columnMidpoint)
                currentRows = rowMidpoint+1..<currentRows.upperBound
                currentColumns = columnMidpoint+1..<currentColumns.upperBound
            }
        }
        if (lowerInflection.0 == -1) {
            lowerInflection = (upperInflection.0-1, upperInflection.1-1)
        } else if (upperInflection.0 == Int.max) {
            upperInflection = (lowerInflection.0+1, lowerInflection.1+1)
        }

        return _searchMatrix(matrix, rows.lowerBound..<lowerInflection.0+1, upperInflection.1..<columns.upperBound, target) || _searchMatrix(matrix, upperInflection.0..<rows.upperBound, columns.lowerBound..<lowerInflection.1+1, target)
    }

    func _binarySearch(_ matrix: [[Int]], _ rowOrColumn: Int, _ range: Range<Int>, _ target: Int, _ searchRow : Bool) -> Bool {
        if (range.isEmpty) {
            return false
        }

        let midpoint = (range.upperBound + range.lowerBound) / 2
        let value = (searchRow ? matrix[rowOrColumn][midpoint] : matrix[midpoint][rowOrColumn])
        if (value == target) {
            return true
        }

        if (value > target) {
            return _binarySearch(matrix, rowOrColumn, range.lowerBound..<midpoint, target, searchRow)
        } else {
            return _binarySearch(matrix, rowOrColumn, midpoint+1..<range.upperBound, target, searchRow)
        }
    }
}

-1

Diberikan matriks persegi sebagai berikut:

[abc]
[def]
[ijk]

Kita tahu bahwa a <c, d <f, i <k. Yang tidak kami ketahui adalah apakah d <c atau d> c, dll. Kami memiliki jaminan hanya dalam 1-dimensi.

Melihat elemen akhir (c, f, k), kita dapat melakukan semacam filter: apakah N <c? pencarian (): berikutnya (). Jadi, kami memiliki n iterasi di atas baris, dengan setiap baris mengambil O (log (n)) untuk pencarian biner atau O (1) jika disaring.

Izinkan saya memberikan CONTOH dimana N = j,

1) Periksa baris 1. j <c? (tidak, lanjutkan)

2) Periksa baris 2. j <f? (ya, pencarian bin tidak mendapat apa-apa)

3) Periksa baris 3. j <k? (ya, pencarian bin menemukannya)

Coba lagi dengan N = q,

1) Periksa baris 1. q <c? (tidak, lanjutkan)

2) Periksa baris 2. q <f? (tidak, lanjutkan)

3) Periksa baris 3. q <k? (tidak, lanjutkan)

Mungkin ada solusi yang lebih baik di luar sana tetapi ini mudah dijelaskan .. :)


Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.