Ikuti langkah-langkah ini untuk menyelesaikan masalah temu bual Pengaturcaraan Dinamik

Walaupun mempunyai pengalaman yang signifikan dalam membina produk perisian, banyak jurutera merasa gelisah memikirkan menjalani temu ramah pengkodan yang memfokuskan pada algoritma. Saya telah menemubual ratusan jurutera di Refdash, Google, dan pada permulaan saya telah menjadi sebahagian, dan beberapa soalan yang paling biasa yang membuat jurutera tidak selesa adalah yang melibatkan Pengaturcaraan Dinamik (DP).

Banyak syarikat teknologi suka bertanya soalan DP dalam wawancara mereka. Walaupun kita dapat membahaskan sama ada mereka berkesan dalam menilai kemampuan seseorang untuk melakukan peranan dalam bidang kejuruteraan, DP terus menjadi bidang yang mendorong para jurutera dalam perjalanan untuk mencari pekerjaan yang mereka cintai.

Pengaturcaraan Dinamik - Ramalan dan Bersedia

Salah satu sebab mengapa saya secara peribadi percaya bahawa soalan DP mungkin bukan kaedah terbaik untuk menguji kemampuan kejuruteraan adalah kerana soalan itu dapat diramalkan dan mudah dicocokkan. Mereka membolehkan kita menyaring lebih banyak kesediaan berbanding kemampuan kejuruteraan.

Soalan-soalan ini biasanya kelihatan cukup rumit di luar, dan mungkin memberi anda gambaran bahawa seseorang yang menyelesaikannya sangat pandai dalam algoritma. Begitu juga, orang yang mungkin tidak dapat mengatasi beberapa konsep DP yang memutar minda mungkin kelihatan lemah dalam pengetahuan mereka tentang algoritma.

Kenyataannya berbeza, dan faktor terbesar dalam prestasi mereka adalah kesediaan. Oleh itu, mari kita pastikan semua orang bersedia untuknya. Sekali dan untuk semua.

7 Langkah untuk menyelesaikan masalah Pengaturcaraan Dinamik

Di sisa catatan ini, saya akan membahas resipi yang boleh anda ikuti untuk mengetahui apakah masalahnya adalah "masalah DP", dan juga untuk mencari jalan keluar untuk masalah tersebut. Secara khusus, saya akan melalui langkah-langkah berikut:

  1. Bagaimana mengenali masalah DP
  2. Kenal pasti pemboleh ubah masalah
  3. Jelaskan hubungan berulang dengan jelas
  4. Kenal pasti kes asas
  5. Tentukan sama ada anda mahu melaksanakannya secara berulang atau berulang
  6. Tambahkan penghafalan
  7. Tentukan kerumitan masa

Masalah DP Contoh

Untuk tujuan mempunyai contoh untuk abstraksi yang akan saya buat, izinkan saya memperkenalkan contoh masalah. Di setiap bahagian, saya akan merujuk kepada masalahnya, tetapi anda juga boleh membaca bahagiannya secara bebas daripada masalahnya.

Pernyataan masalah:

Dalam masalah ini, kami menggunakan bola melompat yang gila, cuba berhenti, sambil mengelakkan lonjakan di sepanjang jalan.

Inilah peraturannya:

1) Anda diberi landasan rata dengan banyak pancang di dalamnya. Landasan dilambangkan oleh susunan boolean yang menunjukkan jika titik tertentu (diskrit) jelas dari lonjakan. Betul untuk jelas dan Salah kerana tidak jelas.

Contoh perwakilan array:

2) Anda diberi kelajuan permulaan S. S adalah bilangan bulat bukan negatif pada titik tertentu, dan ini menunjukkan berapa banyak anda akan bergerak maju dengan lompatan seterusnya.

3) Setiap kali anda mendarat di tempat, anda dapat menyesuaikan kelajuan anda hingga 1 unit sebelum lompatan berikutnya.

4) Anda mahu berhenti dengan selamat di mana sahaja di sepanjang landasan (tidak perlu berada di hujung barisan). Anda berhenti apabila kelajuan anda menjadi 0. Namun, jika anda berada di lonjakan pada bila-bila masa, bola melantun gila anda meletup dan permainannya berakhir.

Output fungsi anda mestilah boolean yang menunjukkan sama ada kita boleh berhenti dengan selamat di mana sahaja di sepanjang landasan.

Langkah 1: Cara mengenali masalah Pengaturcaraan Dinamik

Pertama, mari kita jelaskan bahawa DP pada dasarnya hanyalah teknik pengoptimuman. DP adalah kaedah untuk menyelesaikan masalah dengan memecahnya menjadi kumpulan sub-masalah yang lebih mudah, menyelesaikan setiap sub-masalah itu sekali sahaja, dan menyimpan penyelesaiannya. Pada masa berikutnya masalah yang sama berlaku, bukannya mengira semula penyelesaiannya, anda hanya mencari penyelesaian yang telah dikira sebelumnya. Ini menjimatkan masa pengiraan dengan perbelanjaan perbelanjaan (mudah-mudahan) sederhana di ruang simpanan.

Menyedari bahawa masalah dapat diselesaikan dengan menggunakan DP adalah langkah pertama dan yang paling sukar untuk menyelesaikannya. Apa yang ingin anda tanyakan kepada diri sendiri adalah apakah penyelesaian masalah anda dapat dinyatakan sebagai fungsi penyelesaian untuk masalah yang lebih kecil yang serupa.

Sekiranya terdapat masalah contoh kita, jika diberi titik di landasan, kecepatan, dan landasan di depan, kita dapat menentukan tempat di mana kita berpotensi melompat seterusnya. Selanjutnya, nampaknya sama ada kita boleh berhenti dari titik semasa dengan kelajuan semasa hanya bergantung pada apakah kita boleh berhenti dari titik yang kita pilih untuk pergi ke titik seterusnya.

Itu adalah perkara yang baik, kerana dengan bergerak maju, kita mempersingkat landasan di depan dan menjadikan masalah kita semakin kecil. Kita seharusnya dapat mengulangi proses ini sepanjang jalan sehingga kita sampai pada tahap yang jelas adakah kita boleh berhenti.

Mengenali masalah Pengaturcaraan Dinamik selalunya merupakan langkah paling sukar untuk menyelesaikannya. Bolehkah penyelesaian masalah dinyatakan sebagai fungsi penyelesaian untuk masalah kecil yang serupa?

Langkah 2: Kenalpasti pemboleh ubah masalah

Sekarang kami telah membuktikan bahawa terdapat beberapa struktur rekursif antara sub-masalah kami. Seterusnya, kita perlu menyatakan masalah dari segi parameter fungsi dan melihat parameter mana yang berubah.

Biasanya dalam temu bual, anda akan mempunyai satu atau dua parameter yang berubah, tetapi secara teknikal ini mungkin nombor apa pun. Contoh klasik masalah parameter yang berubah satu adalah "tentukan nombor Fibonacci ke-n". Contoh seperti itu untuk masalah parameter yang berubah-ubah adalah "Hitung jarak edit antara rentetan". Sekiranya anda tidak biasa dengan masalah ini, jangan risau.

Cara untuk menentukan bilangan parameter yang berubah adalah dengan menyenaraikan contoh beberapa sub masalah dan membandingkan parameternya. Mengira bilangan parameter yang berubah adalah berharga untuk menentukan bilangan sub masalah yang harus kita selesaikan. Ini juga penting dalam membantu kita mengukuhkan pemahaman mengenai hubungan berulang dari langkah 1.

Dalam contoh kami, dua parameter yang boleh berubah untuk setiap masalah adalah:

  1. Kedudukan array (P)
  2. Kelajuan (S)

Seseorang boleh mengatakan bahawa landasan di depan juga berubah, tetapi itu akan berlebihan memandangkan seluruh landasan pacu yang tidak berubah dan posisi (P) sudah memiliki maklumat tersebut.

Sekarang, dengan 2 parameter yang berubah ini dan parameter statik yang lain, kami mempunyai penerangan lengkap mengenai sub-masalah kami.

Kenal pasti parameter yang berubah dan tentukan bilangan sub masalah.

Langkah 3: Nyatakan hubungan berulang dengan jelas

Ini adalah langkah penting yang perlu dilalui oleh ramai orang untuk memasuki pengekodan. Menyatakan hubungan berulang sebisa mungkin akan mengukuhkan pemahaman masalah anda dan menjadikan segala yang lain menjadi lebih mudah.

Sebaik sahaja anda mengetahui bahawa hubungan berulang ada dan anda menentukan masalah dari segi parameter, ini harus menjadi langkah semula jadi. Bagaimana masalah saling berkaitan? Dengan kata lain, mari kita anggap bahawa anda telah mengira sub-masalah. Bagaimana anda mengira masalah utama?

Inilah cara kita memikirkannya dalam masalah sampel kita:

Kerana anda dapat menyesuaikan kecepatan anda hingga 1 sebelum melompat ke posisi berikutnya, hanya ada 3 kemungkinan kecepatan, dan oleh itu 3 tempat di mana kita bisa berada di sebelah.

Secara lebih formal, jika kelajuan kita adalah S, kedudukan P, kita dapat pergi dari (S, P) ke:

  1. (S, P + S) ; # jika kita tidak mengubah kelajuan
  2. (S - 1, P + S - 1) ; # jika kita menukar kelajuan dengan -1
  3. (S + 1, P + S + 1) ; # jika kita menukar kelajuan dengan +1

Sekiranya kita dapat mencari jalan untuk berhenti di mana-mana sub masalah di atas, maka kita juga boleh berhenti dari (S, P). Ini kerana kita boleh beralih dari (S, P) ke salah satu daripada tiga pilihan di atas.

Ini biasanya merupakan tahap pemahaman yang baik mengenai masalah ini (penjelasan bahasa Inggeris biasa), tetapi kadang-kadang anda mungkin ingin menyatakan hubungannya secara matematik. Mari kita panggil fungsi yang kita cuba mengira canStop. Kemudian:

canStop (S, P) = canStop (S, P + S) || canStop (S - 1, P + S - 1) || bolehMenghentikan (S + 1, P + S + 1)

Woohoo, nampaknya kita mempunyai hubungan berulang!

Hubungan berulang: Dengan andaian anda telah mengira sub-masalah, bagaimana anda akan mengira masalah utama?

Langkah 4: Kenal pasti kes asas

Kes asas adalah sub masalah yang tidak bergantung pada masalah lain. Untuk mencari sub-masalah seperti itu, biasanya anda ingin mencuba beberapa contoh, melihat bagaimana masalah anda menjadi lebih kecil, dan mengenal pasti pada tahap mana ia tidak dapat dipermudahkan lagi.

Alasan masalah tidak dapat disederhanakan lebih jauh adalah bahawa salah satu parameter akan menjadi nilai yang tidak mungkin dilakukan kerana kekangan masalah.

Dalam masalah contoh kami, kami mempunyai dua parameter yang berubah, S dan P. Mari kita fikirkan apa kemungkinan nilai S dan P yang mungkin tidak sah:

  1. P harus berada dalam batas landasan yang diberikan
  2. P tidak boleh sedemikian rupa sehingga landasan [P] salah kerana itu bermaksud bahawa kita berdiri di atas lonjakan
  3. S tidak boleh negatif, dan S == 0 menunjukkan bahawa kita sudah selesai

Kadang-kadang agak sukar untuk menukar pernyataan yang kita buat mengenai parameter menjadi kes asas yang dapat diprogramkan. Ini kerana, selain menyenaraikan pernyataan jika anda ingin membuat kod anda kelihatan ringkas dan tidak memeriksa keadaan yang tidak perlu, anda juga perlu memikirkan syarat mana yang mungkin.

Dalam contoh kami:

  1. P <0 || P> = panjang landasan sepertinya perkara yang betul untuk dilakukan. Alternatifnya ialah dengan mempertimbangkan m aking P == akhir landasan sebagai kes asas. Namun, ada kemungkinan masalah berpecah menjadi subproblem yang melangkaui hujung landasan, jadi kita benar-benar perlu memeriksa ketaksamaan.
  2. Ini nampak jelas. Kita hanya boleh memeriksa sama ada landasan [P] salah .
  3. Sama seperti # 1, kita hanya dapat memeriksa S <0 dan S == 0. Namun, di sini kita dapat memberi alasan bahawa mustahil untuk S menjadi <0 kerana S menurun paling banyak 1, jadi ia harus melalui S == 0 kes sebelumnya. Ther efore S == 0 adalah kes asas yang mencukupi untuk parameter S.

Langkah 5: Tentukan sama ada anda mahu melaksanakannya secara berulang atau berulang

Cara kita membincangkan langkah-langkah sejauh ini mungkin membuat anda berfikir bahawa kita harus melaksanakan masalahnya secara berulang. Walau bagaimanapun, semua perkara yang telah kita bincangkan setakat ini benar-benar agnostik sama ada anda memutuskan untuk melaksanakan masalah itu secara berulang atau berulang. Dalam kedua pendekatan tersebut, anda harus menentukan hubungan berulang dan kes asasnya.

Untuk memutuskan sama ada pergi secara berulang atau berulang, anda ingin memikirkan dengan baik mengenai pertukaran .

Masalah tumpukan limpahan biasanya merupakan pemecah urus niaga dan alasan mengapa anda tidak mahu melakukan pengulangan dalam sistem pengeluaran (backend). Walau bagaimanapun, untuk tujuan wawancara, selagi anda menyebutkan pertukaran, anda biasanya harus baik-baik saja dengan salah satu pelaksanaannya. Anda harus merasa selesa melaksanakan kedua-duanya.

Dalam masalah tertentu, saya melaksanakan kedua-dua versi. Inilah kod python untuk itu:

Penyelesaian rekursif: (coretan kod asal boleh didapati di sini)

Penyelesaian berulang: (coretan kod asal boleh didapati di sini)

Langkah 6: Tambahkan penghafalan

Menghafal adalah teknik yang berkait rapat dengan DP. Ini digunakan untuk menyimpan hasil panggilan fungsi mahal dan mengembalikan hasil cache apabila input yang sama terjadi lagi.

Mengapa kita menambahkan penghafalan pada pengulangan kita? Kami menemui sub-masalah yang sama, tanpa penghafalan, dihitung berulang kali. Pengulangan tersebut sering menyebabkan kerumitan masa eksponensial.

Dalam penyelesaian rekursif, menambahkan penghafalan harus terasa mudah. Mari lihat mengapa. Ingat bahawa penghafalan hanyalah cache hasil fungsi. Ada kalanya anda ingin menyimpang dari definisi ini untuk menghilangkan beberapa pengoptimuman kecil, tetapi menganggap memoisasi sebagai hasil fungsi cache adalah cara paling intuitif untuk melaksanakannya.

Ini bermaksud bahawa anda harus:

  1. Simpan hasil fungsi anda ke dalam memori anda sebelum setiap pernyataan pengembalian
  2. Cari memori untuk hasil fungsi sebelum anda mula melakukan pengiraan lain

Berikut adalah kod dari atas dengan penghafalan tambahan (garis tambahan diserlahkan): (coretan kod asal boleh didapati di sini)

Untuk menggambarkan keberkesanan penghafalan dan pendekatan yang berbeza, mari lakukan beberapa ujian pantas. Saya akan menekankan ujian ketiga-tiga kaedah yang telah kita lihat setakat ini. Inilah persediaannya:

  1. Saya membuat landasan panjang 1000 dengan lonjakan di tempat rawak (saya memilih kemungkinan ada lonjakan di tempat tertentu sehingga 20%)
  2. initSpeed ​​= 30
  3. Saya menjalankan semua fungsi 10 kali dan mengukur purata masa pelaksanaan

Berikut adalah hasilnya (dalam beberapa saat):

Anda dapat melihat bahawa pendekatan rekursif murni memerlukan sekitar 500x lebih banyak masa daripada pendekatan berulang dan sekitar 1300x lebih banyak masa daripada pendekatan rekursif dengan penghafalan. Perhatikan bahawa perbezaan ini akan berkembang dengan cepat dengan panjang landasan. Saya mendorong anda untuk mencuba menjalankannya sendiri.

Langkah 7: Tentukan kerumitan masa

Terdapat beberapa peraturan mudah yang dapat menjadikan kerumitan waktu pengkomputeran masalah pengaturcaraan dinamik menjadi lebih mudah. Berikut adalah dua langkah yang perlu anda lakukan:

  1. Hitung bilangan keadaan - ini akan bergantung pada jumlah parameter yang berubah dalam masalah anda
  2. Fikirkan kerja yang dilakukan di setiap negeri. Dengan kata lain, jika semua keadaan kecuali satu keadaan telah dihitung, berapa banyak kerja yang perlu anda lakukan untuk mengira keadaan terakhir itu?

Dalam masalah contoh kami, bilangan negeri adalah | P | * | S |, di mana

  • P adalah set semua kedudukan (| P | menunjukkan bilangan elemen dalam P)
  • S adalah set semua kelajuan

Kerja yang dilakukan setiap negeri adalah O (1) dalam masalah ini kerana, memandangkan semua keadaan lain, kita hanya perlu melihat 3 sub masalah untuk menentukan keadaan yang dihasilkan.

Seperti yang kita nyatakan dalam kod sebelumnya, | S | dibatasi oleh panjang landasan (| P |), jadi kita dapat mengatakan bahawa jumlah keadaan adalah | P | ² dan kerana kerja yang dilakukan setiap negeri adalah O (1), maka kerumitan total waktu adalah O (| P | ²).

Walau bagaimanapun, nampaknya | S | dapat dibatasi lebih jauh, kerana jika benar-benar | P |, sangat jelas bahawa berhenti tidak mungkin dilakukan kerana anda harus melonjak panjang keseluruhan landasan pada pergerakan pertama.

Oleh itu, mari kita lihat bagaimana kita boleh mengikat | S | Mari kita sebut kelajuan maksimum S. Andaikan kita bermula dari kedudukan 0. Seberapa cepat kita boleh berhenti jika kita cuba berhenti secepat mungkin dan jika kita mengabaikan potensi lonjakan?

Pada lelaran pertama, kita harus sampai ke titik paling sedikit (S-1), dengan menyesuaikan kelajuan kita pada sifar dengan -1. Dari sana kita sekurang-kurangnya akan melangkah (S-2) ke depan, dan seterusnya.

Untuk landasan panjang L , yang berikut harus dipegang:

=> (S-1) + (S-2) + (S-3) +…. + 1 <L

=> S * (S-1) / 2 <L

=> S² - S - 2L <0

Sekiranya anda menemui punca fungsi di atas, mereka akan menjadi:

r1 = 1/2 + sqrt (1/4 + 2L) dan r2 = 1/2 - sqrt (1/4 + 2L)

Kita boleh menulis ketaksamaan kita sebagai:

(S - r1) * (S - r2) < ; 0

Memandangkan bahawa S - r2> 0 untuk S> 0 dan L> 0, kami memerlukan yang berikut:

S - 1/2 - sqrt (1/4 + 2L) < ; 0

=> S <1/2 + sqrt (1/4 + 2L)

Itulah kelajuan maksimum yang mungkin kita miliki di landasan panjang L. Sekiranya kita mempunyai kelajuan lebih tinggi dari itu, kita tidak dapat berhenti walaupun secara teorinya, tanpa mengira kedudukan lonjakan.

Ini bermaksud bahawa kerumitan masa hanya bergantung pada panjang landasan L dalam bentuk berikut:

O (L * sqrt (L)) yang lebih baik daripada O (L²)

O (L * sqrt (L)) adalah batas atas pada kerumitan masa

Hebat, anda berjaya! :)

7 langkah yang kami lalui harus memberi anda kerangka untuk menyelesaikan masalah pengaturcaraan dinamik secara sistematik. Saya sangat mengesyorkan mengamalkan pendekatan ini pada beberapa masalah untuk menyempurnakan pendekatan anda.

Berikut adalah beberapa langkah seterusnya yang boleh anda ambil

  1. Panjangkan masalah sampel dengan berusaha mencari jalan menuju titik berhenti. Kami menyelesaikan masalah yang memberitahu anda sama ada anda boleh berhenti, tetapi bagaimana jika anda ingin mengetahui juga langkah-langkah yang harus diambil untuk berhenti akhirnya di sepanjang landasan? Bagaimana anda mengubah pelaksanaan yang ada untuk melakukannya?
  2. Sekiranya anda ingin memantapkan pemahaman anda tentang penghafalan, dan memahami bahawa itu hanyalah cache hasil fungsi, anda harus membaca tentang penghias dalam Python atau konsep serupa dalam bahasa lain. Fikirkan bagaimana mereka membolehkan anda melaksanakan penghafalan secara umum untuk fungsi yang ingin anda hafal.
  3. Selesaikan lebih banyak masalah DP dengan mengikuti langkah-langkah yang kami lalui. Anda selalu dapat menemui sebilangan besar mereka dalam talian (mis. LeetCode atau GeeksForGeeks). Semasa anda berlatih, ingatlah satu perkara: belajar idea, jangan belajar masalah. Jumlah idea jauh lebih kecil dan ini adalah ruang yang lebih mudah untuk ditaklukkan yang juga akan memberi anda lebih baik.

Apabila anda merasa berjaya menawan idea-idea ini, periksa Refdash di mana anda ditemu ramah oleh jurutera kanan dan dapatkan maklum balas terperinci mengenai pengekodan, algoritma, dan reka bentuk sistem anda.

Asalnya diterbitkan di blog Refdash. Refdash adalah platform temu ramah yang membantu jurutera menemu ramah tanpa nama dengan jurutera berpengalaman dari syarikat terkemuka seperti Google, Facebook, atau Palantir dan mendapatkan maklum balas terperinci. Refdash juga membantu jurutera mencari peluang pekerjaan yang luar biasa berdasarkan kemahiran dan minat mereka.