Bagaimana Anda dapat menemukan semua parens tidak seimbang dalam string dalam waktu linier dengan memori konstan?

11

Saya diberi masalah berikut saat wawancara:

Memberikan string yang berisi beberapa campuran parens (bukan kurung atau kurung kurawal - hanya parens) dengan karakter alfanumerik lainnya, identifikasi semua paren yang tidak memiliki paren yang cocok.

Misalnya, dalam string ") (ab))", indeks 0 dan 5 berisi parens yang tidak memiliki paren yang cocok.

Saya mengedepankan solusi O (n) yang berfungsi menggunakan memori O (n), menggunakan stack dan melalui string sekali menambahkan parens ke stack dan menghapusnya dari stack setiap kali saya menemukan paren penutup dan bagian atas stack berisi sebuah paren pembuka.

Setelah itu, pewawancara mencatat bahwa masalahnya dapat diselesaikan dalam waktu linier dengan memori konstan (seperti, tidak ada penggunaan memori tambahan selain apa yang diambil oleh input.)

Saya bertanya bagaimana dan dia mengatakan sesuatu tentang melalui string sekali dari kiri mengidentifikasi semua parens terbuka, dan kemudian kedua kalinya dari kanan mengidentifikasi semua parens dekat .... atau mungkin sebaliknya. Saya tidak benar-benar mengerti dan tidak ingin memintanya untuk memegang saya.

Adakah yang bisa menjelaskan solusi yang dia sarankan?

temporary_user_name
sumber
1
Kami mungkin perlu klarifikasi dari Anda terlebih dahulu. Apakah parens pertama atau parens kedua dalam "(()" dianggap tidak seimbang? Apakah parens terakhir atau parens kedua ke terakhir dalam "())" dianggap tidak seimbang? Atau apakah cukup untuk mengidentifikasi seperangkat parens dengan kardinalitas minimal sehingga menghilangkannya akan membuat parens yang tersisa seimbang? Atau sesuatu yang lain? Atau apakah ini bagian dari wawancara sehingga suatu jawaban dapat mengajukan spesifikasi yang dapat dibenarkan?
John L.
Saya akan mengatakan itu tidak masalah, terserah Anda. Hapus semua set yang membuat sisanya seimbang.
temporary_user_name
5
Kemudian lepaskan semuanya; P
Veedrac
@Veedrac, tentu saja (seperti yang Anda tahu) poster lupa kata 'minimal' di "Hapus set minimal ..."
LSpice
Saya tidak "melupakannya," semata-mata, tetapi meninggalkannya karena itu tidak tampak sebagai spesifikasi penting bagi saya karena hanya ada satu set yang dapat dihapus untuk membuatnya seimbang, selain "semuanya" yang tentu saja mengalahkan tujuan latihan.
temporary_user_name

Jawaban:

17

O(1)Θ(log(n))n

Anda dapat menjaga prinsip dasar algoritma yang Anda gunakan. Anda melewatkan peluang untuk pengoptimalan memori.

menggunakan stack dan melalui string sekali menambahkan parens ke stack dan menghapusnya dari stack setiap kali saya menemukan paren penutup dan bagian atas stack berisi paren pembuka

Jadi apa isi tumpukan ini? Ini tidak akan mengandung ()(tanda kurung buka diikuti oleh tanda kurung tutup), karena setiap kali )muncul Anda pop the (bukannya mendorong ). Jadi tumpukan selalu dalam bentuk )…)(…(- sekelompok kurung penutup diikuti oleh sekelompok kurung pembuka.

Anda tidak perlu tumpukan untuk mewakili ini. Ingat saja jumlah tanda kurung tutup dan jumlah tanda kurung buka.

Jika Anda memproses string dari kiri ke kanan, menggunakan dua penghitung ini, yang Anda miliki pada akhirnya adalah jumlah kurung penutup yang tidak cocok dan jumlah kurung pembuka yang tidak cocok.

Θ(n)

Singkatnya: proses string dari kiri ke kanan. Pertahankan counter tanda kurung pembuka yang tidak cocok. Jika Anda melihat tanda kurung buka, tambahkan penghitung. Jika Anda melihat tanda kurung penutup dan penghitungnya bukan nol, turunkan penghitungnya. Jika Anda melihat tanda kurung penutup dan penghitung adalah nol, output indeks saat ini sebagai tanda kurung penutupan tidak cocok.

Nilai akhir dari penghitung adalah jumlah tanda kurung pembuka yang tidak cocok, tetapi ini tidak memberi Anda posisi mereka. Perhatikan bahwa masalahnya simetris. Untuk daftar posisi kurung pembuka yang tidak cocok, jalankan saja algoritma di arah yang berlawanan.

Latihan 1: tulis ini dalam notasi formal (matematika, kodesemu atau bahasa pemrograman favorit Anda).

Latihan 2: yakinkan diri Anda bahwa ini adalah algoritma yang sama dengan Apass.Jack , baru saja dijelaskan secara berbeda.

Gilles 'SANGAT berhenti menjadi jahat'
sumber
Oh, Gilles sangat bagus, dijelaskan dengan sangat baik. Saya mengerti sekarang. Sudah beberapa tahun sejak saya mendapat jawaban dari Anda pada salah satu pertanyaan saya.
temporary_user_name
"Jika kamu ingin melaporkan posisi tanda kurung yang tidak cocok di akhir, kamu harus mengingat posisi setiap tanda kurung." Tidak terlalu. Waktu linier tidak berarti lulus tunggal. Anda dapat melakukan lintasan kedua untuk menemukan tanda kurung di sisi yang tidak cocok dan menandainya.
Mooing Duck
Untuk langkah terakhir, Anda tidak harus menjalankannya secara terbalik, Anda cukup menandai N "terakhir" ("sebagai ketidakcocokan.
Mooing Duck
1
@ MoingDuck Itu tidak berhasil. Misalnya (().
orlp
Sementara saya sangat menyukai jawaban ini, sesuatu terus menggangguku. Sesuatu itu adalah "Saya entah bagaimana perlu mengingat posisi Dan saya pikir masalah yang saya miliki dengan itu adalah: bagaimana Anda" menampilkan indeks saat ini "tanpa mengkonsumsi memori (atau konteks yang cukup spesifik di mana output Anda dikonsumsi sedemikian rupa sehingga urutan w-dari output Anda tidak masalah)
Édouard
8

Karena kita bisa mengabaikan semua karakter alfanumerik, kita akan menganggap string hanya berisi tanda kurung mulai sekarang. Seperti dalam pertanyaan, hanya ada satu jenis kurung, "()".

Jika kita terus menghapus tanda kurung yang seimbang sampai tidak ada lagi tanda kurung yang seimbang dapat dihapus, semua tanda kurung yang tersisa harus terlihat seperti ")) ...) ((...," yang semuanya tanda kurung tidak seimbang. Pengamatan ini menunjukkan bahwa kita harus menemukan terlebih dahulu titik balik itu) , sebelum itu kami hanya memiliki kurung tutup yang tidak seimbang dan setelah itu kami memiliki kurung pembuka yang tidak seimbang saja.

Di sini adalah algoritma. Singkatnya, ini menghitung titik balik pertama. Kemudian output kurung penutup ekstra, memindai string dari awal ke kanan hingga titik balik. Secara simetris, ini menghasilkan kurung pembuka ekstra, pemindaian dari ujung ke kiri sampai titik balik.


strn

Inisialisasi turning_point=0, maximum_count=0, count=0. Untuk setiap idari 0ke n-1melakukan hal berikut.

  1. Jika str[i] = ')', tambahkan 1 ke count; jika tidak, kurangi 1.
  2. Jika count > maximum_count, atur turning_point=idan maximum_count=count.

Sekarang turning_pointadalah indeks dari titik baliknya.

Setel ulang maximum_count=0, count=0. Untuk setiap idari 0ke turning_pointmelakukan hal berikut.

  1. Jika str[i] = ')', tambahkan 1 ke count; jika tidak, kurangi 1.
  2. Jika count > maximum_countdiatur maximum_count = count. Output isebagai indeks kurung tutup tidak seimbang.

Setel ulang maximum_count=0, count=0. Untuk setiap idari n-1ke turning_point+1bawah melakukan hal berikut.

  1. Jika str[j] = '(', tambahkan 1 ke count; jika tidak, kurangi 1.
  2. Jika count > maximum_countdiatur maximum_count = count. Output isebagai indeks kurung pembuka tidak seimbang.

O(n)O(1)O(u)u


Jika kita menganalisis algoritma di atas, kita akan melihat bahwa, pada kenyataannya, kita tidak perlu menemukan dan menggunakan titik balik sama sekali. Pengamatan yang bagus bahwa semua kurung tutup tidak seimbang terjadi sebelum semua kurung buka tidak seimbang dapat diabaikan meskipun menarik.

Berikut adalah kode dalam Python .

Tekan saja "lari" untuk melihat beberapa hasil tes.


Latihan 1. Tunjukkan bahwa algoritma di atas akan menampilkan satu set tanda kurung dengan kardinalitas terkecil sehingga tanda kurung yang tersisa seimbang.

Masalah 1. Bisakah kita menggeneralisasi algoritma ke kasus ketika string berisi dua jenis tanda kurung seperti "() []"? Kita harus menentukan bagaimana mengenali dan memperlakukan situasi baru, kasus interleaving, "([)]".

John L.
sumber
Lol, latihan 1 dan masalah 1, imut. Logika dari algoritma yang Anda uraikan ternyata sangat sulit untuk divisualisasikan. Saya harus kode ini besok untuk mendapatkannya.
temporary_user_name
Sepertinya saya melewatkan penjelasan yang agak jelas tapi paling penting. Logikanya, sebenarnya, sangat sederhana. Pertama, kami mengeluarkan setiap kurung pembuka ekstra. Setelah kami melewati titik balik, kami mengeluarkan setiap kurung tutup tambahan. Selesai
John L.
Menemukan kurung pembuka yang tidak seimbang adalah salah. Yaitu jika arr Anda adalah "())", p adalah 2 dan p +1 jatuh di luar batas arr. Hanya sebuah ide - untuk menemukan kurung pembuka yang tidak seimbang, Anda dapat membalikkan arr dan menggunakan bagian dari algoritma untuk menemukan kurung penutup yang tidak seimbang (tentu saja, dengan indeks yang disesuaikan secara terbalik).
OzrenTkalcecKrznaric
p+1
Butuh sedikit bagiku untuk memahami ini, tapi aku suka, itu cukup pintar .. dan bekerja setidaknya untuk setiap kasus yang aku pikirkan
dquijada