Bagaimana menemukan elemen tengah dari daftar tertaut dalam satu pass?

12

Salah satu pertanyaan paling populer dari struktur data dan algoritma, sebagian besar ditanyakan pada wawancara teleponi.

Gilles 'SANGAT berhenti menjadi jahat'
sumber
5
Jika jawaban yang disajikan dalam utas ini adalah apa yang diharapkan pewawancara, maka pertanyaan ini tidak menguji kemampuan teknis, tetapi seberapa baik kandidat dapat menghindar seperti pengacara.
G. Bach
2
Ini adalah pertanyaan wawancara yang mengerikan karena sangat bergantung pada istilah " lulus " yang tidak jelas, ambigu, subjektif. Hampir semua jawaban yang baik untuk pertanyaan ini melibatkan penyalahgunaan definisi sehingga Anda dapat mengabaikannya secara efektif.
RBarryYoung
2
Nah, pertanyaan itu menimbulkan banyak diskusi di sini. Itu berarti itu adalah pertanyaan wawancara yang baik dalam satu hal: itu mulai Anda pikirkan.
Hendrik
3
Saya sangat ingin menjawab "baca semua elemen ke dalam vektor, lalu akses elemen pada ukuran posisi () / 2"
Cort Ammon
1
@ HendrikJan Sebenarnya, saya pikir itu adalah pertanyaan wawancara yang sangat mengerikan. Pertama, itu cenderung mengarah pada argumen tentang apa sebenarnya arti "dalam satu jalur", daripada diskusi yang produktif. Kedua, seorang kandidat dapat menemukan jawaban "benar" dan kemudian menolaknya karena mereka pikir itu melanggar kriteria "dalam satu jalur". Ketiga, karena ini adalah pertanyaan yang terkenal, ini adalah ujian yang lebih baik dari "Apakah Anda tahu pertanyaan wawancara populer?" dari "Apakah Anda cocok untuk pekerjaan ini?" Salah satu dari mereka harus cukup untuk menenggelamkan ini sebagai pertanyaan; ketiganya secara bersamaan adalah bencana.
David Richerby

Jawaban:

24

Dengan menipu, dan melakukan dua operan sekaligus, secara paralel. Tapi saya tidak tahu apakah perekrut akan menyukai ini.

Dapat dilakukan pada daftar tertaut tunggal, dengan trik yang bagus. Dua petunjuk perjalanan melintasi daftar, satu dengan kecepatan ganda. Ketika yang cepat mencapai akhir, yang lain setengah jalan.

Hendrik Jan
sumber
1
Ya, tidak jelas apakah ini hanya satu pass. Pertanyaannya tidak jelas tentang hal itu.
David Richerby
2
Ngomong-ngomong, ini terkait dengan teka-teki di mana Anda memiliki dua lilin, masing-masing satu jam terbakar, dan Anda diminta mengukur 45 menit.
Hendrik
2
Apakah ini benar-benar berbeda dari iterasi daftar, penghitungan elemen, dan iterasi kedua kalinya ke titik setengah jalan? Yang berbeda adalah ketika Anda mengulangi setengah tambahan. Seperti @RBarryYoung menyebutkan jawaban serupa lainnya, itu sebenarnya bukan satu pass, itu pass setengah.
2
Jika daftar panjang, memindahkan kedua pointer "secara paralel" akan menyebabkan lebih sedikit kesalahan cache daripada iterasi dari awal untuk kedua kalinya.
zwol
2
Ini menggunakan prinsip yang sama dengan algoritma kura - kura dan kelinci untuk deteksi siklus.
Joshua Taylor
7

Jika ini bukan daftar yang ditautkan dua kali lipat, Anda bisa saja menghitung dan menggunakan daftar, tetapi itu membutuhkan penggandaan memori Anda dalam kasus terburuk, dan itu tidak akan berfungsi jika daftar itu terlalu besar untuk disimpan dalam memori.

Solusi sederhana, hampir konyol, hanya meningkatkan simpul tengah setiap dua node

function middle(start) {
    var middle = start
    var nextnode = start
    var do_increment = false;
    while (nextnode.next != null) {
        if (do_increment) {
             middle = middle.next;
        }
        do_increment = !do_increment;
        nextnode = nextnode.next;
    }
    return middle;
}
00500005
sumber
Pilihan kedua Anda adalah jawaban yang benar (tentu saja IMHO).
Matthew Crumley
1
Ini sebenarnya membuat 1 1/2 melewati daftar yang ditautkan.
RBarryYoung
6

Menguraikan jawaban Hendrik

Jika ini daftar yang ditautkan dua kali lipat, beralihlah dari kedua ujungnya

function middle(start, end) {
  do_advance_start = false;
  while(start !== end && start && end) {
     if (do_advance_start) {
        start = start.next
     }
     else {
        end = end.prev
     }
     do_advance_start = !do_advance_start
  }
  return (start === end) ? start : null;
}

Diberikan [1, 2, 3] => 2

1, 3
1, 2
2, 2

Diberikan [1, 2] => 1

1, 2
1, 1

Diberikan [1] => 1

Diberikan [] => null

00500005
sumber
Bagaimana ini efisien? Anda juga mengulangi n kali dan bukan n / 2.
Karan Khanna
3

Buat struktur dengan pointer yang mampu menunjuk ke node dari daftar tertaut dan dengan variabel integer yang menjaga jumlah jumlah node dalam daftar.

struct LL{
    struct node *ptr;
    int         count;
}start;

start.ptrstart.count=1
start.count

start.count

Prateek
sumber
-2

Buat array dinamis, di mana setiap elemen array adalah pointer ke setiap node dalam daftar dalam urutan melintasi, mulai dari awal. Buat integer, diinisialisasi ke 1, yang melacak berapa banyak node yang telah Anda kunjungi (kenaikan yang setiap kali Anda pergi ke node baru). Ketika Anda sampai di akhir, Anda tahu seberapa besar daftar itu, dan Anda memiliki array pointer ke setiap node. Akhirnya, bagi ukuran daftar dengan 2 (dan kurangi 1 untuk pengindeksan berbasis 0) dan ambil pointer yang disimpan dalam indeks array; jika ukuran daftar aneh, Anda dapat memilih elemen mana yang akan dikembalikan (saya masih akan mengembalikan yang pertama).

Berikut adalah beberapa kode Java yang mendapatkan titik (meskipun gagasan array dinamis akan menjadi agak miring). Saya akan memberikan C / C ++ tetapi saya sangat berkarat di daerah itu.

public Node getMiddleNode(List<Node> nodes){

    int size = 1;
    //add code to dynamically increase size if at capacity after adding
    Node[] pointers = new Node[10];

    for (int i = 0; i < nodes.size(); i++){
        //remember to dynamically allocate more space if needed
        pointers[i] = nodes.get(i);
        size++;
    }

    return pointers[(size - 1)/2];

}
Andonaeus
sumber
-3

Apakah rekursi dianggap lebih dari satu pass?

Lintasi daftar sampai akhir, melewati bilangan bulat dengan referensi. Buat salinan lokal dari nilai itu di setiap level untuk referensi di kemudian hari, dan tambahkan penghitungan referensi ke panggilan berikutnya.

Pada simpul terakhir, bagi hitungan dengan dua dan potong / lantai () hasilnya (jika Anda ingin simpul pertama menjadi "tengah" ketika hanya ada dua elemen) atau bulatkan (jika Anda ingin simpul kedua menjadi Tengah"). Gunakan indeks berbasis nol atau satu dengan tepat.

Unwinding, cocokkan hitungan ref dengan salinan lokal (yang merupakan nomor node). Jika sama, kembalikan simpul itu; lain kembalikan node yang dikembalikan dari panggilan rekursif.
.

Ada cara lain untuk melakukan ini; beberapa dari mereka mungkin kurang rumit (saya pikir saya melihat seseorang mengatakan membacanya menjadi sebuah array dan menggunakan panjang array untuk menentukan tengah - pujian). Tapi sejujurnya, tidak ada jawaban yang bagus, karena itu pertanyaan wawancara yang bodoh. Nomor satu, yang masih menggunakan daftar tertaut ( opini pendukung ); Dua, menemukan simpul tengah adalah latihan akademis yang sewenang-wenang tanpa nilai dalam skenario kehidupan nyata; Tiga, jika saya benar-benar perlu tahu simpul tengah, daftar tertaut saya akan memperlihatkan jumlah simpul. Ini lebih mudah untuk mempertahankan properti itu daripada membuang waktu melintasi seluruh daftar setiap kali saya ingin simpul tengah. Dan akhirnya, empat, setiap pewawancara akan menyukai atau menolak jawaban yang berbeda - apa yang menurut pewawancara licin, yang lain akan menyebut konyol.

Saya hampir selalu menjawab pertanyaan wawancara dengan lebih banyak pertanyaan. Jika saya mendapatkan pertanyaan seperti ini (saya tidak pernah punya), saya akan bertanya (1) Apa yang Anda simpan di daftar tertaut ini, dan apakah ada struktur yang lebih tepat untuk secara efisien mengakses node tengah jika memang benar-benar diperlukan untuk melakukannya ; (2) Apa kendala saya? Saya dapat membuatnya lebih cepat jika ingatan bukan masalah (mis. Jawaban array), tetapi jika pewawancara berpikir bahwa meningkatkan ingatan itu sia-sia, saya akan kena celaka. (3) Bahasa apa yang akan saya kembangkan? Hampir setiap bahasa modern yang saya tahu memiliki kelas bawaan untuk menangani daftar tertaut yang membuat melintasi daftar itu tidak perlu - mengapa menemukan kembali sesuatu yang telah disesuaikan untuk efisiensi oleh pengembang bahasa?

James K
sumber
6
Anda tidak tahu siapa yang menurunkan penilaian, jadi kesimpulan Anda bahwa satu orang meremehkan segala sesuatu mungkin atau mungkin tidak benar tetapi tentu saja tidak memiliki dasar dalam fakta yang tersedia untuk Anda. Tapi saya menurunkan jawaban Anda karena kami sedang mencari penjelasan, bukan tumpukan kode.
David Richerby
Ini mungkin asumsi, tetapi ketika saya melihat satu momen dan kurang dari 30 detik kemudian setiap pos memiliki -1, itu bukan hal yang tidak masuk akal. Bahkan jika itu 5 atau 6 orang yang berbeda, tidak satu pun dari mereka meninggalkan komentar mengapa Tapi terima kasih untuk setidaknya menyatakan alasannya. Saya tidak mengerti mengapa penjelasan bertele-tele lebih baik daripada kode - ya, itu untuk wawancara lewat telepon, tapi saya tidak memberi OP jawaban kalengan untuk memuntahkan, saya menunjukkan kepadanya cara untuk melakukannya. Ya, saya pikir downvoting posting karena memiliki kode tidak pantas, tapi terima kasih untuk setidaknya mengatakan mengapa Anda melakukannya.
James K
Poin wajar - tidak terpikir oleh saya bahwa Anda bisa mengetahui waktu pemberian suara (memiliki halaman dimuat saat mereka terjadi adalah, saya pikir, satu-satunya cara pengguna biasa seperti kita bisa mengetahuinya). Kami memiliki beberapa posting meta tentang mengapa kami mencoba menghindari kode aktual di sini: (1) (2) .
David Richerby
Terima kasih atas tautan meta. Saya memang membaca FAQ, tetapi saya tidak melihat apa pun di sana - bukan karena saya akan memperhatikan sesuatu seperti itu, kemungkinan besar, bukan sesuatu yang saya harapkan. Tapi aku juga tidak bisa menemukan apa-apa ketika aku memeriksa kembali setelah mengalami kerusakan. Saya mungkin masih menghadapinya, tetapi saya memang melihatnya. Alasan dalam posting meta masuk akal; terima kasih atas tanggapannya.
James K
-5

Dengan menggunakan 2 pointer. Tambahkan satu di setiap iterasi dan lainnya di setiap iterasi kedua. Ketika 1 pointer menunjuk ke akhir daftar yang ditautkan, pergi 2 pointer akan menunjuk ke mode tengah dari daftar yang terhubung.

b dharuni
sumber
Ini hanya menggandakan jawaban Hendrick . Tolong jangan menjawab kecuali Anda memiliki sesuatu yang baru untuk dikatakan.
David Richerby