Mach dich fit für Coding-Interviews: Datenstrukturen in Python | Alvin Wan | Skillshare
Drawer
Suchen

Playback-Geschwindigkeit


  • 0.5x
  • 1x (normal)
  • 1.25x
  • 1.5x
  • 2x

Mach dich fit für Coding-Interviews: Datenstrukturen in Python

teacher avatar Alvin Wan, Research Scientist

Schau dir diesen Kurs und Tausende anderer Kurse an

Erhalte unbegrenzten Zugang zu allen Kursen
Lerne von Branchenführern, Ikonen und erfahrenen Experten
Wähle aus einer Vielzahl von Themen, wie Illustration, Design, Fotografie, Animation und mehr

Schau dir diesen Kurs und Tausende anderer Kurse an

Erhalte unbegrenzten Zugang zu allen Kursen
Lerne von Branchenführern, Ikonen und erfahrenen Experten
Wähle aus einer Vielzahl von Themen, wie Illustration, Design, Fotografie, Animation und mehr

Einheiten dieses Kurses

    • 1.

      Bereit? Setze ein. Geh schon.

      1:41

    • 2.

      Warum du Datenstrukturen brauchst

      6:15

    • 3.

      Was ist eine „gute“ Datenstruktur?

      5:31

    • 4.

      Aufträge der Growth

      12:00

    • 5.

      Sequenzen: Stack vs. Warteschlange

      6:32

    • 6.

      Sequences

      39:09

    • 7.

      Listen: Array vs. verlinkte Liste

      7:22

    • 8.

      Lists Practice

      27:38

    • 9.

      Karten: Hashmaps

      6:22

    • 10.

      Mappings

      29:51

    • 11.

      Bäume: Breadth vs. Tiefe

      9:14

    • 12.

      Trees

      25:39

    • 13.

      Schlussbemerkung

      1:32

  • --
  • Anfänger-Niveau
  • Fortgeschrittenes Niveau
  • Fortgeschrittenes Niveau
  • Jedes Niveau

Von der Community generiert

Das Niveau wird anhand der mehrheitlichen Meinung der Teilnehmer:innen bestimmt, die diesen Kurs bewertet haben. Bis das Feedback von mindestens 5 Teilnehmer:innen eingegangen ist, wird die Empfehlung der Kursleiter:innen angezeigt.

630

Teilnehmer:innen

1

Projekte

Über diesen Kurs

Keine Ahnung, wie man sich für die Codierung von Interviews vorbereiten kann? Die Antwort liegt darin, die praktischen Vorteile eines alters mit der Informatik 101-Themas zu verstehen: Datenstrukturen.

Dieser Kurs behandelt ein Muss für jeden Programmierer – Datenstrukturen. Wir behandeln mehrere Konzepte und Themen:

  • Wie man mithilfe von Aufträgen der Wachstumsanalyse Algorithmus „Effizienz“ analysiert und versteht
  • Häufig verwendete Datenstrukturen: Stacks, Warteschlangen, verlinkte Listen, Bäume, hash
  • Analyse von Algorithmen der Datenstruktur: Einfügen, Löschen, Suchen und Zugriff
  • Praktische Auswirkungen von Stärken und Schwächen von Datenstrukturen
  • 28 ausführliche Fragen der Praxis im Interview-Stil
  • 20 Tipps des Interviews

Der Kurs ist sehr interaktiv, denn wir werden zusammen programmieren. Am Ende dieses Kurses erhältst du das Know-how von Datenstrukturen, lernst die Grundlagen und verstehst praktische Folgen. Am wichtigsten ist, dass du unter deinem Gürtel die Praxis im Interviewstil hast.

Bist du interessiert am kreativen Programmieren? Wirf einen Blick auf meinen Kurs VR101 (AFrame).

Interessierst du dich für Datenwissenschaft oder maschinelles Lernen? Sieh dir meine Kurse in Coding 101 (Python), OOP 101 (Python), SQL 101 (Datenbankdesign), Daten 101 (Analytics) oder Computer Vision 101 (Applied ML) an.

Triff deine:n Kursleiter:in

Teacher Profile Image

Alvin Wan

Research Scientist

Top Teacher

Hi, I'm Alvin. I was formerly a computer science lecturer at UC Berkeley, where I served on various course staffs for 5 years. I'm now a research scientist at a large tech company, working on cutting edge AI. I've got courses to get you started -- not just to teach the basics, but also to get you excited to learn more. For more, see my Guide to Coding or YouTube.

Welcoming Guest Teacher Derek! I was formerly an instructor for the largest computer science course at UC Berkeley, where I taught for several years and won the Distinguished GSI (graduate student instructor) award. I am now a software engineer working on experimentation platforms at a large tech company. 4.45 / 5.00 average rating (943 reviews) at UC Berkeley. For more, see my Skillshare or Webs... Vollständiges Profil ansehen

Level: Intermediate

Kursbewertung

Erwartungen erfüllt?
    Voll und ganz!
  • 0%
  • Ja
  • 0%
  • Teils teils
  • 0%
  • Eher nicht
  • 0%

Warum lohnt sich eine Mitgliedschaft bei Skillshare?

Nimm an prämierten Skillshare Original-Kursen teil

Jeder Kurs setzt sich aus kurzen Einheiten und praktischen Übungsprojekten zusammen

Mit deiner Mitgliedschaft unterstützt du die Kursleiter:innen auf Skillshare

Lerne von überall aus

Ob auf dem Weg zur Arbeit, zur Uni oder im Flieger - streame oder lade Kurse herunter mit der Skillshare-App und lerne, wo auch immer du möchtest.

Transkripte

1. Bereit? Setze ein. Geh schon.: [MUSIK] Das Programmieren von Interviews ist wirklich einschüchternd. Es gibt schwierige Fragen, knifflige Rätsel und sogar unlösbare Probleme. Es gibt Angriffspläne für jedes Szenario, aber wir werden uns auf das offensichtlichste konzentrieren wenn die Frage einfach ist. Wo es keine Kurvenbälle gibt, keine Tricks. Ich will sichergehen, dass du das Interview bestehst. Hallo! Ich bin Alvin, ein Forscher in der Industrie. Im vergangenen Frühjahr erhielt ich Angebote von Meta, Apple, Amazon, Tesla und anderen. In den letzten Jahren habe ich auch technische Angebote von Google, NVIDIA, Facebook und Lyft erhalten . Die Erfahrung, die ich aus diesen Interviews gesammelt habe , war von unschätzbarem Wert Ich habe mich wirklich hingesetzt, um zu verstehen, welche Teile meine Interviews erfolgreich gemacht haben? Was sind die Grundlagen und die Tricks? Dieser Kurs ist ein Schritt in wichtige Richtungen, ein Schritt vorwärts bei der Vorbereitung eines Vorstellungsgesprächs und ein Schritt vorwärts in der Programmierbeherrschung. Um beides zu erreichen, lernen wir Datenstrukturen kennen. Datenstrukturen sind eine Möglichkeit, Daten zu organisieren. In diesem Kurs sehen Sie Beispiele, wie sie funktionieren und wie Sie sie verwenden. Am Ende des Kurses verfügen Sie über zwei Fähigkeiten, eine für jedes Ziel. Sie können Datenstrukturen auswählen, die für die Codierung von Interviews verwendet werden sollen . Das ist eine große Sache. Anstatt effiziente Algorithmen zu erfinden, nur für Call-In-Umfragen bereit effizienter Algorithmus. Sie werden auch in der Lage sein, diese Datenstrukturen zu implementieren und Implementierungen nach Bedarf zu kombinieren und zu mischen . Das ist auch ein Eins. Implementierungen effektiv kennen, erhalten Sie im Interview einen Startcode, einen freien Vorsprung. Ich hoffe du bist begeistert. Ich weiß, dass ich es bin. Lass uns anfangen. 2. Warum du Datenstrukturen brauchst: Lassen Sie mich erklären, warum Sie insbesondere Datenstrukturen benötigen, warum dieser Kurs über Datenstrukturen für Ihr Wachstum als Programmierer so wichtig ist Ihr Wachstum als Programmierer so wichtig und wie Sie diesen Kurs effektiv nutzen können. Wir werden auf drei wichtige Diskussionspunkte eingehen: Erstens diskutieren wir die Vorteile der Beherrschung dieses Themas, zweitens besprechen wir spezifische Lernziele im Kurs, um Ihre Beherrschung zu beschleunigen, und drittens stellen wir eine Übung , die Ihnen hilft, nach Abschluss des Kurses die Meisterschaft zu erlangen. Hier ist unser erstes Gesprächsthema, warum Sie sich darum kümmern sollten, warum ich diesen Kurs unterrichte, welche Vorteile Sie daraus ziehen werden. Der größte Vorteil dieses Kurses besteht darin, besser auf Interviews vorbereitet zu sein. Es gibt zwei Arten von technischen Interviews: Erstens gibt es Codierungsfragen, sind spezifische Probleme wie, wie kehrt man eine Zeichenfolge um? Wie entfernt man alle doppelten Zahlen? Zweitens gibt es Fragen zum Systemdesign. Dies sind umfassendere Fragen wie Wie würdest du einen Clone von Airbnb erstellen? Im Grunde genommen, wie man Infrastruktur und Software für ein ganzes Produkt entwirft. Beide Interviews können herausfordernd sein, aber Datenstrukturen helfen Ihnen bei diesen beiden technischen Interviews enorm , und so geht's. Hier sind zwei spezifische Vorteile der Kenntnis von Datenstrukturen. Vorteil Nummer 1 ist für Ihr erstes technisches Interview, das Programmieren von Interviews. Ein erheblicher Teil, wahrscheinlich mehr als ein Drittel der Codierungsfragen, bezieht sich auf Datenstrukturen. Wenn Sie Ihre Datenstrukturen kennen, können Sie in Ihren Interviews oft Algorithmen anstelle von Design-Algorithmen anwenden . Das ist riesig. Es ist nicht nötig, Ideen von Grund auf neu zu entwickeln. Wenn Sie wissen, dass Datenstruktur A genau diese Anforderungen erfüllt, sagen Sie einfach: „Ich weiß, dass HashMaps ideal für diesen Anwendungsfall sind Hier erfahren Sie, warum und wie ich es verwenden würde.“ Das ist eine Killer-Antwort auf ein Interview Vorteil Nummer 2 ist für Ihr Systemdesign-Interview, Sie Systeme für Ihre spezifische Anwendung optimieren können , sowohl in Ihren Interviews als auch in der Praxis. Ich spreche nicht von einem 10 Prozent oder 25 Prozent schnelleren System, ich spreche von einem System , das um Größenordnungen effizienter arbeitet , indem die richtige Datenbank, Server oder Serverlose Infrastruktur und mehr. Sie würden sagen: „Wir brauchen gezielte azyklische Graphen. Graphdatenbanken sind speziell für diese Art von Beziehung optimiert , und hier ist der Grund dafür.“ Das ist eine gut informierte und überzeugende Designentscheidung. Vorteil Nummer 3 liegt außerhalb des Interviewprozesses. Mit einem Verständnis der Datenstrukturen werden Sie in der Lage sein, fortgeschrittenere Themen über Ihre Interviews hinaus anzugehen . In der Informatik vermitteln Ihnen diese Themen im Allgemeinen neue Fähigkeiten wie das Erstellen eines Spiels. Eines der beliebtesten Tools zur Spieleerstellung, Unity, verwendet ein sogenanntes datenorientiertes Programmierparadigma. Wie der Name schon sagt, ist das Verständnis Ihrer Datenstrukturen ziemlich wichtig. Zusammenfassend sind hier drei Vorteile für das Verständnis von Datenstrukturen aufgeführt. Lage zu sein, bei Codierungsinterviews anstelle von Design von Grund auf neu anzuwenden , zu optimieren der Lage zu sein, für die Anwendung in Interviews mit Systemdesign und fortgeschrittenere Themen anzugehen neue Türen zu öffnen, die über reine Interviews hinausgehen. In diesem Kurs konzentrieren wir uns auf Vorteil Nummer 1. In jeder zweiten Lektion üben Sie die Anwendung von Datenstrukturen zur Codierung von Interviewfragen. Um diese Vorteile nutzen zu können, müssen Sie sich in diesem Kurs auf zwei Lernziele konzentrieren . Ziel Nummer 1 ist es, zu wissen, wie jede Datenstruktur funktioniert, und die Implementierung jeder Datenstruktur zu üben. Dies ist wichtig, da Sie später möglicherweise Teile dieser Datenstrukturen implementieren oder Datenstrukturen für verschiedene Zwecke kombinieren müssen. Genau wie in meinen anderen Klassen empfehle ich Ihnen dringend, neben mir zu programmieren, dieses Video auf der einen Seite Ihres Bildschirms und Ihren bevorzugten Code-Editor auf der anderen Seite zu platzieren Bildschirms und Ihren bevorzugten . Kopieren Sie meinen Code genau und Sie müssen sich nicht beeilen. Halten Sie das Video an, wann immer Sie müssen. Wichtiger ist, dass Sie das Muskelgedächtnis aufbauen. Ziel Nummer 2 ist es, zu wissen, wie und wann die einzelnen Datenstrukturen verwendet werden müssen. Denken Sie insbesondere an die Vor - und Nachteile jeder Datenstruktur. In der nächsten Lektion stellen wir Ihnen spezifische Rubrikenelemente vor, anhand derer Sie die einzelnen Datenstrukturen beurteilen. Vor- und Nachteile sind nicht verwaschen, sie sind sehr präzise und klar definiert. Zusammenfassend lässt sich sagen, dass Ihre beiden wichtigsten Lernziele darin bestehen, zu wissen, wie Datenstrukturen funktionieren, indem Sie gemeinsam mit mir Implementierungen und Programmieren üben . Zweitens, wissen Sie, wie und wann Daten verwendet werden Strukturen, indem wir uns an die Vor- und Nachteile erinnern, die wir besprechen Unser letztes Gesprächsthema hier ist, wie Sie Ihr Verständnis der Datenstruktur über diesen Kurs hinaus festigen können. Klar, es ist zum Üben, aber es ist nicht wirklich das, was man erwartet. Wir müssen den Spieß umdrehen, so tun, als wären Sie der Interviewer, sagen, dass Sie jetzt die Fragen zur Datenstruktur schreiben. Ihre Aufgabe ist es, das Entwerfen von Datenstrukturfragen zu üben und dies drei Tage lang täglich zu tun. Ziel ist es, dass Sie nach einer guten Nachtruhe nur ein paar Mal über Datenstrukturen nachdenken . Tun Sie dies während Ihrer Ausfallzeit, während des Pendelns, beim Zähneputzen oder während Sie in der Schlange stehen. Fügen Sie dies zu Ihrer Liste „Mach das , wenn mir langweilig ist“ hinzu. Je mehr Sie dies tun, desto mehr Fragen stellen Sie sich, desto besser beherrschen Sie Datenstrukturen. Zusammenfassend haben wir die Vorteile der Kenntnis von Datenstrukturen, einer besseren Bereitschaft für Vorstellungsgespräche und ein Sprungbrett zu fortgeschrittenen Themen erörtert die Vorteile der Kenntnis von Datenstrukturen, besseren Bereitschaft für Vorstellungsgespräche und ein Sprungbrett zu fortgeschrittenen Themen Wir haben Ihre Lernziele besprochen, wissen, wie man Datenstrukturen implementiert und wie man Datenstrukturen benutzt, wir haben endlich eine Möglichkeit besprochen , Ihr Wissen zu vertiefen nach dem Kurs drei Tage lang Entwerfen Sie nach dem Kurs drei Tage lang jeden Tag eine Datenstrukturfrage. Ich ermutige Sie auch, Ihre Frage im Diskussionsbereich zu posten , mein Fehler. Ich meine das wirklich, wirklich ernst. Es ist unglaublich lohnend für mich, die Fragen zu sehen, die Sie stellen, entweder eine Frage, die Sie gestellt haben oder eine Frage zum Kurs, also laden Sie bitte hoch. Darauf freue ich mich. Das war's für den Überblick. Eine Kopie dieser Folien und weiterer Ressourcen finden Sie auf der Kurswebsite. Wir haben den Überblick abgeschlossen und als Nächstes werden wir darüber sprechen, wie Datenstrukturen verglichen werden können. 3. Was ist eine „gute“ Datenstruktur?: Was ist eine gute Datenstruktur? Dies ist für uns von entscheidender Bedeutung, da eines unserer Lernziele darin besteht, zu wissen, wie und wann jede Datenstruktur verwendet werden muss. Aber noch bevor wir diese Frage stellen, müssen wir uns zuerst fragen, was überhaupt eine Datenstruktur ist? Wir können mit der einfachen Definition beginnen. Eine Datenstruktur ist einfach eine Sammlung von Daten. Das klingt einfach, also lassen Sie uns das jetzt ein wenig näher erläutern. Lassen Sie uns diese Definition erweitern, indem einige Erwartungen an eine Datenstruktur hinzufügen. Insbesondere sollten wir in der Lage sein, die enthaltenen Daten zu ändern und darauf zuzugreifen . Eine Datenstruktur ist eine Sammlung von Daten, die geändert werden können und auf die zugegriffen werden kann. Um die Datenstruktur zu ändern, sollten wir in der Lage sein, Daten einzufügen und zu entfernen. Um auf die Daten zugreifen zu können, sollten wir in der Lage sein, Daten zu suchen und abzurufen. Dies sind die vier Aktionen, die jede Datenstruktur unterstützen muss. Jede dieser Aktionen kann je nach Datenstruktur sehr langsam oder sehr schnell sein. Dies bringt uns zu unserem nächsten Abschnitt Wie quantifizieren wir sehr schnell oder sehr langsam? Wie effizient oder ineffizient ist jede dieser Aktionen? Mit anderen Worten, wie bewerten wir Effizienz? Um die Effizienz eines Algorithmus zu bewerten, zählen wir die Anzahl der Operationen, die ein Algorithmus zur Ausführung benötigt. Nehmen wir zum Beispiel an, wir haben diese Liste von Früchten und sagen, wir suchen nach Avocado. Um die Liste zu durchsuchen, müssen wir die Liste nacheinander durchqueren bis wir den gesuchten Artikel gefunden haben. In diesem Fall haben wir drei „Operationen“ durchgeführt, um nach Avocado zu suchen. Wenn Avocado jedoch am Ende steht oder wenn sie überhaupt nicht in der Liste enthalten ist, müsste Ihr Suchalgorithmus alle Elemente in der Liste durchqueren. Bei einer Liste mit n Elementen nimmt die Suche nach einem Wert bis zu n Operationen in Anspruch. Jetzt ist die Definition von Operation vage. Vielleicht ist ein Speicherzugriff eine Operation, vielleicht ist das Überprüfen der String-Gleichheit eine andere Operation. Nehmen wir an, die Anzahl der Operationen für ein einzelnes Element benötigt werden, ist c. Dann werden für die Suche cn-Operationen ausgeführt. Diese Konstante c ist für uns jedoch beim Vergleich von Algorithmen uninteressant. Wir sagen einfach, dass der Algorithmus O von n Operationen ausführt, bei denen die O-Notation alle Konstanten verbirgt. Wir nennen das O of n Statistik, die Laufzeit oder Rechenkomplexität. Hier ist eine andere Möglichkeit zu sehen , was O of n-Komplexität bedeutet. Was wir wissen ist, dass mit zunehmender Liste die Anzahl der Operationen, wie Anzahl der Operationen, auch immer Sie sie zählen, linear wächst. Dies bedeutet, dass die Rechenkomplexität unserer Algorithmen oben und unten durch lineare Funktionen begrenzt werden kann . Jeder Algorithmus, dessen Komplexität innerhalb dieser Grenzen liegt, ist O von n. Infolgedessen weist die Suche in Ihrer Liste O von n Rechenkomplexität auf. Wir wissen jetzt, dass für eine Liste, die auch als Array bezeichnet wird, O von n mal eine Suche benötigt wird. Um genau zu sein, suchen wir nach einem Wert wenn wir nicht wissen, wo sich ein bestimmter Wert in der Liste befindet. Nehmen wir jedoch an, wir wollen genau den ith-Artikel holen. Wir wissen, dass wir das i-te Element wollen und nicht nach einem Wert suchen müssen. Dieser Abruf benötigt eine konstante Zeit oder O von einem, weil wir einfach auf das i-te Element der Liste zugreifen , ohne andere Werte zu betrachten. Insbesondere ändert sich der Aufwand, der zum Abrufen eines Elements erforderlich ist, nicht, wenn die Listenlänge zunimmt . Das ist großartig. Einen Artikel zu holen ist billig. Um die Effizienz zu maximieren, bedeutet dies, dass wir ein Array verwenden sollten, wenn wir häufiger mehr Daten abrufen müssen , als wir nach Daten suchen. Dies ist ein Beispiel für ein praktisches Mitnehmen, und wir werden viele davon im Kurs sehen. Jede dieser Entscheidungen zielt darauf ab , die Effizienz zu maximieren. Wir sollten unsere Definition einer Datenstruktur ändern. Eine Datenstruktur ist eine Sammlung von Daten , die effizient geändert und abgerufen werden können. Basierend auf jeder Anwendung müssen wir Datenstrukturen auswählen , um diese Effizienz sicherzustellen. werden wir die Komplexität einiger Datenstrukturen analysieren diesem Kurs werden wir die Komplexität einiger Datenstrukturen analysieren. Bei der Durchführung dieser Analysen werden wir auf mehrere gängige Wachstumsordnungen stoßen. Wachstumsordnungen sind das O von etwas, das wir zuvor gesehen haben. Wir haben bereits zwei Beispiele für Wachstumsaufträge gesehen. Konstantes zeitliches Wachstum O von eins und lineares Wachstum O von n. Dies sind sechs gängige Wachstumsordnungen , die hier auf dieser Folie dargestellt werden. Aber für die Datenstrukturen und Algorithmen, die wir behandeln, werden wir nur vier Wachstumsordnungen sehen, konstante O von einer, logarithmisches O von log n, lineares O von n und O von n im Quadrat. Wir werden später sehen, wie O of log n aussieht , aber denken Sie vorerst daran, dass Sie ein neues Tool zur Analyse der algorithmischen Komplexität haben und dass diese vier die häufigsten Wachstumsordnungen sind, die Sie finden werden. Eine Kopie dieser Folien und weiterer Ressourcen finden Sie auf der Kurswebsite. Jetzt haben Sie ein neues Tool und Anweisungen zur Wachstumsanalyse, denen Sie die Rechenkomplexität für jeden Algorithmus bewerten können . Noch wichtiger ist, dass Sie Datenstrukturen für verschiedene Anwendungen auswählen können Datenstrukturen für verschiedene Anwendungen je nachdem, wie langsam oder wie schnell eine Datenstruktur zum Einfügen, Löschen, Abrufen und Suchen ist . Im nächsten Abschnitt werden wir die Analyse von Wachstumsaufträgen üben . 4. Aufträge der Growth: Willkommen bei einigen Datenstrukturpraktiken. In dieser Lektion werden wir Datenstrukturen bewerten. Mit anderen Worten, wir bewerten Algorithmen und Datenstrukturen mithilfe eines Systems, das als Orders of Growth bezeichnet wird. Darüber haben wir in der vorherigen Lektion gesprochen. Die Big O-Notation zeigt uns, wie schnell oder wie langsam ein Algorithmus basierend auf seiner Eingabegröße ausgeführt wird. Wir werden die Interviewtaktiken vorerst beiseite legen, zumindest bestimmte Taktiken interviewen. Wir werden sicherlich Tipps zum Thema Vorstellungsgespräche haben, aber wir müssen uns zuerst auf die Grundlagen konzentrieren. Navigieren Sie zunächst zu dieser URL, alvinwan.com/data structures101/oog. Sobald Sie diese Seite sehen, forken Sie das Projekt, um Ihre eigene editierbare Kopie zu erhalten. Klicken Sie dazu oben links auf den Projektnamen, um ein Dropdown zu erhalten. Klicken Sie dann auf die Ellipse oben rechts, um ein weiteres Dropdown zu erhalten. Klicken Sie abschließend auf die Schaltfläche „Fork“. Dann schlage ich vor, Ihre Skillshare- und Repl.it-Fenster nebeneinander zu platzieren , wie hier gezeigt. Sie sollten dann auf der rechten Seite ein Fenster wie meines sehen . Bevor wir uns mit den Problemen befassen, wollen wir uns die Wachstumsreihenfolgen ansehen , über die wir zuvor gesprochen haben. Es gibt vier relevante Wachstumsordnungen. Zur Erinnerung, was ich hier schreibe, ist eine Funktion von n, wobei n die Größe der Eingabe ist. Diese Wachstumsreihenfolgen zeigen uns, um wie viel langsamer die Funktion ausgeführt wird , wenn wir die Eingabegröße n erhöhen. Daher möchten wir, dass die Laufzeiten langsamer wachsen , wenn die Eingabe an Größe zunimmt. Es ist gut, diese Liste der Laufzeiten nach oben zu verschieben. Je langsamer die Reihenfolge des Wachstums ist, desto effizienter und bevorzugter ist der Algorithmus oder die Datenstruktur. Ein Tipp, den ich für Sie habe, ist, diese Laufzeiten auswendig zu kennen Dies sind die vier Kernlaufzeiten , die Sie für jedes einzelne Interview kennen müssen. Die erste O (1) oder konstante Zeit bedeutet, dass unsere Funktion unabhängig von der Eingabegröße immer die gleiche Zeit benötigt. Zweitens wird O (log n) später tatsächlich darüber sprechen. Es ist etwas komplizierter, diese Laufzeit zu erklären. dritte O (n) oder lineare Laufzeit bedeutet, dass sich auch unsere Laufzeit verdoppelt, wenn sich unsere Eingabegröße verdoppelt. Wenn sich unsere Eingabegröße verdreifacht, verdreifacht sich auch unsere Laufzeit. Mit anderen Worten, unsere Funktionslaufzeit wächst tatsächlich linear in Bezug auf n. O (n) quadriert bedeutet hier quadratisch. Wenn wir die Eingabegröße verdoppeln, unsere Funktionslaufzeit oder die Laufzeit vervierfacht sich unsere Funktionslaufzeit oder die Laufzeit des Algorithmus vervierfacht sich. Wenn sich unsere Eingabegröße verdreifacht wächst unsere Laufzeit tatsächlich um das Neunfache, und so weiter und so fort. Lassen Sie uns jetzt direkt in die Übungen eintauchen. Auf der rechten Seite habe ich unsere erste Übung. Wie hoch ist die Laufzeitkomplexität für Folgendes? Hier haben wir nur eine for-Schleife für i im Bereich n. Denken Sie daran, dass der Bereich von n Ihnen eine eingebbare Anzahl von n verschiedenen Elementen bietet. Was ist die Laufzeitkomplexität dieser Funktion? Pausiere das Video hier und probiere es aus und hier ist die Antwort. Diese Funktion wiederholt erneut einmal über jedes einzelne Element in diesem iterierbaren Element von n Elementen. Mit anderen Worten, es iteriert einmal für jeweils n Elemente, es wird n-mal iteriert. Die Laufzeit für diese Funktion oder diesen Algorithmus ist O (n). [LACHEN] Ich habe vergessen, es dir zu zeigen. Hier ist die Liste der vier verschiedenen Laufzeitoptionen, die Sie haben. Ich behalte die hier, während wir diese Übungen durchmachen. Gehen wir nun zur zweiten Übung über. Was ist die Laufzeitkomplexität dieser Funktion, wiederum als Funktion von n. Hier haben wir zwei, für Schleifen. Jeder von ihnen wiederholt bis zu n. Eine andere Möglichkeit, dies zu sehen, wäre zu fragen, wie oft die Druckfunktion aufgerufen wurde? Pausiere das Video hier und versuche es und hier ist die Antwort. Hier wiederholen wir es in wenigen Zeiten. Dann wiederholen wir für jedes einzelne i auch über j n mal. Das ergibt insgesamt n mal n Läufe. Dieser Algorithmus ist O (n) quadriert. Gehen wir zur nächsten Übung über. Wie hoch ist die Laufzeitkomplexität dieser Funktion als Funktion von n? Hier beginnen wir mit n. Dann haben wir in dieser While-Schleife für jeden einzelnen Schritt diesen Wert. Was ist die Laufzeitkomplexität dieser Funktion? Sie können einen Eliminierungsprozess verwenden, um herauszufinden, wie die Laufzeit aussieht. Pausiere das Video hier und probiere es aus und hier ist die Antwort. Die Antwort auf diese Funktion, die wir durch Eliminierungsprozess ableiten können , lautet O (logn). So könnte dieser Eliminierungsprozess funktionieren. Hier sehen wir, dass die Funktion definitiv nicht konstant ist. Es dauert definitiv etwas länger, wenn n zunimmt. Es ist definitiv nicht O (1). Es ist jedoch nicht so langsam wie O (n), weil O (n) einmal iterieren würde. Es würde nacheinander über alle Elemente iterieren, bis zu n. Das tun wir jedoch nicht. Wir halbieren den Wert jedes Mal. Wir springen nur ein bisschen herum. Es ist nicht so langsam wie O (n) und es ist nicht so schnell wie O (1). Die einzige Laufzeit dazwischen ist O (log n). Wir werden das später genauer machen, um zu erklären, wie O (logn) zustande kommt. Aber jetzt sprechen wir über eine visuelle Erklärung. Wie sieht logarithmisches Wachstum aus und was bedeutet logarithmisches Wachstum? logarithmische Wachstum ist wirklich sehr langsam. Um Ihnen eine Vorstellung davon zu geben, wie langsam die Log-Funktion ist. Denken Sie daran, n ist die Eingabegröße, der Wert von log n ist die Laufzeit. Angenommen, unsere Eingabe hat Größe zwei, dann hat die Laufzeit Einheit Eins-Laufzeit. Nehmen wir an, unser Input verdoppelt die Größe vier, die Laufzeit erhöht sich um eins. Wenn sich unser Input erneut auf Größe Acht verdoppelt, erhöht sich die Laufzeit um eins. Wenn sich Ihre Eingabe erneut auf Größe 16 verdoppelt, erhöht sich die Laufzeit immer noch nur um eins. Beachten Sie, dass sich unsere Eingabegröße weiter verdoppeln muss , um die Laufzeit um eins zu erhöhen. Das bedeutet, dass unser Algorithmus ziemlich effizient ist. Optisch sieht das so aus. Wir zeichnen die Eingabegröße entlang der X-Achse, wir zeichnen die Laufzeit entlang der Y-Achse und plotten dann alle Punkte von zuvor. Beachten Sie, dass unsere Funktion ziemlich langsam wächst. Tatsächlich ist hier ein größeres Diagramm mit mehr Werten von x. Sie können sehen, dass unser Diagramm sehr schnell flach wird. Mit anderen Worten, logarithmische Funktionen wachsen sehr langsam. Behalte das im Hinterkopf. Kehren wir zu unserer Funktion zurück. Wir können jetzt sehen, dass i hier dies oder i oder n, sagen wir, n muss sich verdoppeln, um die Anzahl der Iterationen der While-Schleife um eins zu erhöhen . Wenn n zwei ist, die While-Schleife einmal wiederholt. Wenn n gleich vier ist, dies zweimal wiederholt. Wenn n acht ist, wiederholt sich dies dreimal und so weiter und so fort. Dieser Wert n muss jedes Mal verdoppelt werden, um die Laufzeit um nur eins zu erhöhen. Das ist logarithmisches Wachstum. Die andere Möglichkeit, dies zu sehen, besteht darin, dass unsere logarithmische Funktion die Eingabegröße in jedem Schritt halbiert . Wie Sie hier sehen können, halbieren wir jeden einzelnen Schritt, also halbieren wir unsere Arbeitsbelastung jedem einzelnen Schritt. Das ist der Tipp. Logarithmische Funktionen halbieren die Eingabegröße in jedem einzelnen Schritt. Hier ist eine Zusammenfassung der drei Beispiele, die wir gerade durchgesehen haben, und ihrer Laufzeiten. Das war's für die „grundlegenden Beispiele“. Ich meine nicht einfach, da diese einfach sind. Diese Beispiele sind ziemlich verwirrend, besonders wenn Sie sie zum ersten Mal sehen. Ich sage grundlegend, um zu bedeuten, dass alle anderen Beispiele eine Modifikation dieser Grundstruktur sein werden . Um die Laufzeiten in der Zukunft herauszufinden, gibt es zwei Schritte. Beginnen Sie damit, sich zu merken, wie diese „grundlegenden Beispiele“ aussehen. Zweitens, sobald Sie diese Beispiele kennen, können Sie sich die verschiedenen möglichen Änderungen merken . Das ist unser Tipp. Kommen wir nun zu diesen Änderungen, diesen kniffligen Beispielen. Dies sind wiederum Modifikationen der obigen Beispiele , um entweder verwirrend auszusehen oder die Laufzeit zu ändern. Hier sind die vier Laufzeiten zuvor. Gehen wir nun zu den Beispielen über, den kniffligen Beispielen. Wie oft wird print in der folgenden Funktion aufgerufen? Hier haben wir eine verschachtelte Schleife. Dann haben wir diese if-Bedingung, wenn i gleich j ist. Beachten Sie diese if-Anweisung und wie wirkt sie sich auf die Komplexität aus, wenn überhaupt? Pausieren Sie das Video hier und probieren Sie es erneut Wie oft wird Print aufgerufen? Hier ist die Antwort. Print wird nur n-mal aufgerufen. Dies liegt daran, dass unsere innere For-Schleife nur einmal ausgeführt wird. Wenn i gleich Null ist, dann gilt diese if-Bedingung nur, wenn j ebenfalls gleich Null ist. Wenn i gleich eins ist, gilt wiederum diese if-Bedingung nur, wenn j auch gleich eins ist. Das bedeutet, dass für jede einzelne äußere Schleife die innere Schleife nur einmal läuft, das sind also n mal eine Iteration, das ist eine Summe von O (n). Gehen wir nun zu unserem nächsten kniffligen Beispiel über. Wie oft wird print als Funktion von n aufgerufen? Hier haben wir eine geschachtelte for-Schleife, aber beachten Sie, dass die innere für Schleife nur bis i zählt . Wie wirkt sich das auf die Laufzeit aus, wenn überhaupt? Pausiere das Video hier und probiere es aus. Hier ist jetzt die Antwort. Wie sich herausstellt, scheinen wir, obwohl wir nur bis i iterieren, eine schnellere Laufzeitkomplexität zu haben. Es stellt sich heraus, dass wir das nicht tun Das ist immer noch O (n) quadriert. Hier ist der Grund dafür. Ich muss es erklären, indem ich dir ein Bild zeige. Nehmen wir an, wir haben eine normale for-Schleife auf der linken Seite. Dann gehen wir für jeden einzelnen Wert von i j n mal durch. Wir machen das, weil i gleich eins ist, wir iterieren durch alle Werte von j, i ist gleich zwei alle Werte von j und so weiter und so fort. Mach weiter so. Das bedeutet, dass wir insgesamt n Quadrat verschiedener Zeiten drucken. Nehmen wir an, wir drucken i j. Nehmen wir an, wir haben diese neue for-Schleife in der wir nur bis i iterieren. Was das bedeutet, ist hier in dieser ersten Zeile, wenn i gleich eins ist, wir führe j nur einmal aus. Wenn i in der zweiten Reihe gleich zwei ist, führen wir j nur zweimal aus. Wenn i gleich drei ist, führen wir j nur dreimal aus, und so weiter und so fort. Hier haben wir anstelle von n quadrierten Gesamtzeiten, die wir print nennen, 1/2 mal n quadrierte Male, die wir print nennen. 1/2 von n im Quadrat liegt jedoch immer noch in der Größenordnung von n im Quadrat, weshalb diese Funktion in Bezug auf die algorithmische Komplexität immer noch O von n im Quadrat ist . Damit ist dieses knifflige Beispiel abgeschlossen. Wenn Sie Fragen haben oder immer noch verwirrt sind, können Sie gerne eine Frage in den Diskussionsbereich stellen. Dies sind die vier verschiedenen Themen, die wir behandelt haben. Wachstumsordnungen haben wir einige grundlegende Beispiele wiedergefunden, logarithmisches Wachstum und einige knifflige Beispiele sowie wie sie diese grundlegenden Beispiele für ein kniffligeres Beispiel modifiziert haben . Eine Kopie dieser Folien und den fertigen Code für diese Lektion Sie auf der Kurswebsite. Damit ist diese Lektion zur Bewertung von Datenstrukturen abgeschlossen. 5. Sequenzen: Stack vs. Warteschlange: Ein gängiges Format für Daten ist eine Sequenz, und insbesondere gibt es zwei gängige Arten von Sequenzen. Es gibt Stapel, wie ein Stapel Kleidung oder ein Stapel Steine. Es gibt auch Warteschlangen wie eine Reihe von Personen oder einen Autotunnel. Sobald wir sowohl Stacks als auch Warteschlangen analysiert haben, besprechen wir ihre Vor- und Nachteile. An erster Stelle steht ein Stapel. Genau wie bei einem Stapel Steine fügst du Gegenstände oben auf den Stapel. Um dann einen Stein oder einen Gegenstand zu entfernen, entfernst du ihn auch von der Oberseite des Stapels. Wir fassen dies als last in, first out zusammen; das letzte Element, das Sie hinzufügen, ist das erste, das Sie entfernen. Der Einfügevorgang gemäß dieser Logik ist dann einfach. Wir nehmen den Wert, den wir schaffen wollen, wir schieben diesen Wert auf den Stack und das war's. Es dauert eine Konstante oder O von einmal, um einen neuen Wert in den Stapel einzufügen einen neuen Wert in den Stapel da der Aufwand, einfach ein Element einzufügen , gleich ist, egal wie lang der Stapel ist. Beachten Sie, dass wir per Definition nur Werte oben im Stapel einfügen können. Der Löschvorgang ist ebenfalls einfach. Wir nehmen den letzten hier in Grau angezeigten Wert und geben den Wert ein. Dies erfordert auch eine Konstante oder ein O von einem Mal. Da die Länge der Liste die Anzahl der Operationen nicht ändert , wird das Löschen geknetet. Zusammengenommen wissen wir jetzt, dass Stapel konstantes Einfügen und Löschen ermöglichen, sodass Änderungen billig sind. Aber was ist mit dem Zugang? Wie teuer ist die Suche im Stack nach einem Wert? Leider ziemlich langsam. Nehmen wir an, wir suchen nach dem Wert 8. Wir öffnen einen Gegenstand, überprüfen seinen Wert. Ist 7 gleich acht? Ist dies nicht der Fall, also geben wir einen anderen Wert ein und überprüfen dann seinen Wert. Ist 2 gleich 8? Es ist nicht so, dass wir noch einmal auftauchen und dann seinen Wert überprüfen. Ist 8 gleich? Das ist es absolut, also ist unsere Suche abgeschlossen. Befinden sich jedoch n Items im Stapel, müssen wir bis zu n Prüfungen durchführen. Dies bedeutet, dass die Suche linear oder O von n Zeit dauert. Das Abrufen eines bestimmten Elements ist ähnlich. Angenommen, wir wollen das zweite Element mit dem Wert 8 holen. Wir knallen einen Gegenstand, das ist ein Gegenstand, wir öffnen einen anderen Gegenstand, das sind zwei Gegenstände, dann legen wir einen anderen Gegenstand auf, das sind drei Gegenstände. Wir haben endlich den Wert erreicht, den wir wollen. Wenn sich nach wie vor n Gegenstände auf dem Stapel befinden, benötigen wir möglicherweise bis zu n Pops. Dadurch wird Fetch O nicht rechtzeitig ausgeschaltet. Zusammenfassend können wir in konstanter Zeit in einen Stapel einfügen und daraus löschen. Die Änderung ist effizient. Suchen und Abrufen benötigen jedoch lineare Zeit. Der Zugang ist ineffizient. Aber was wäre, wenn wir statt eines Steinstapels eine Reihe von Leuten hätten? Die erste Person , die sich der Linie anschließt sollte die erste sein, die sie verlässt. Dies wird als Warteschlange bezeichnet. Dieses Mal fügen wir einen Wert am Ende der Warteschlange ein und löschen einen Wert von vorne in der Warteschlange. Wir nennen dies eine First-In-, First-Out-Sequenz oder eine Warteschlange. Wie bereits erwähnt, ist das Einfügen einfach. Wir erwägen einen Wert, den wir hinzufügen müssen, und fügen diesen Artikel dann in die Warteschlange ein. Unabhängig von der Länge der Liste wird die Einfachheit des Einfügens nicht beeinträchtigt. Dies ist ein O einer Operation. Das Löschen ist ähnlich. Wir betrachten einen Wert, der in Grau dargestellt entfernt werden soll, und entfernen diesen Wert dann aus der Warteschlange. Dies ist auch ein O einer Operation. Zusammenfassend lässt sich sagen, dass Einfügen und Löschen für eine Warteschlange also beide O einer Zeit sind. Wieder einmal ist die Suche jedoch ziemlich langsam. Angenommen, wir durchsuchen die Warteschlange nach dem Wert 8, wieder entfernen wir die Warteschlange, dann überprüfen Sie, ob 7 gleich 8 ist? Es ist nicht so, dass wir uns wieder aus der Warteschlange entfernen und dann überprüfen Ist 2 gleich 8? Ist es nicht. Wir entfernen die Warteschlange und überprüfen. Ist 8 gleich 8? Ist es. Unsere Suche ist abgeschlossen. Wenn sich jedoch n Elemente in der Warteschlange befinden, benötigen wir möglicherweise bis zu n Überprüfungen. Dies macht Suchen und Warteschlangen zu einem O-of-n-Algorithmus. Fetch ist wieder ähnlich. Angenommen, wir wollen den dritten Gegenstand mit dem Wert 8 holen. Wir entfernen einen Gegenstand aus der Warteschlange, das ist 1, wir entfernen einen anderen Gegenstand, das ist 2, und wir entfernen einen anderen Gegenstand aus der Warteschlange , um schließlich den Wert des dritten Gegenstands abzurufen, der acht ist. Für eine Warteschlange mit n Elementen benötigen wir n Dequeues, also ist das Abrufen O von n. Zusammenfassend lässt sich sagen, eine Warteschlange effizient geändert werden kann, aber der Zugriff ineffizient ist , genau wie bei Stacks. Lassen Sie uns nun die Vor- und Nachteile von Stacks und Warteschlangen analysieren. Ganz links haben wir die vier Operationen, die uns wichtig sind: Einfügen, Löschen, Suchen und Abrufen. In der 2. Spalte haben wir die Laufzeiten für einen Stack und in der 3. Spalte haben wir die Laufzeiten für eine Warteschlange. Leider sind die Laufzeiten selbst nicht besonders interessant, daher müssen wir wissen, welche wir basierend auf der Anwendung verwenden sollen und wie die einzelnen Datenstrukturen definiert wurden. Ein Stack ist beispielsweise ideal, Änderungen zu verfolgen, wie das Rückgängigmachen eines Vorgangs und anschließende Wiederholen dieses Vorgangs. Dies ist ein Stapel, da die letzte hinzugefügte Änderung die erste ist, die Sie rückgängig machen. Eine Warteschlange ist ideal für die sequentielle Verarbeitung, z. B. Bearbeitung von Autos in einer Linie an einer Mautbrücke oder das Herunterladen von Anfragen auf einen Server. Denken Sie an diese Folie, da sie die gesamte Lektion zusammenfasst. Eine Kopie dieser Folien und weiterer Ressourcen finden Sie auf der Kurswebsite. Lassen Sie uns nun diese Datenstrukturen in Aktion sehen. In der nächsten Lektion werden wir sowohl die Verwendung als auch die Implementierung dieser Datenstrukturen üben und unsere beiden Lernziele für diesen Kurs angehen. 6. Sequences: Willkommen zu einer weiteren Übungsstunde. In dieser Lektion behandeln wir Sequenzen, also Stapel und Warteschlangen. Genau wie zuvor werden wir wichtige Interviewtaktiken beiseite legen , wir müssen diese Grundlagen zuerst beherrschen. Beachten Sie, dass diese Fragen ziemlich schwierig sind. Das ist keine coole Anwendung. Dies sind Grundlagen , die Sie für Interviews benötigen , und es ist technisch herausforderndes Material. Navigieren Sie also wie zuvor zu dieser URL, alvinwan.com/datastructures101/sequences. Sobald Sie diese Seite sehen, forken Sie das Projekt, um Ihre eigene editierbare Kopie zu erhalten. Klicken Sie dazu oben links auf den Projektnamen , um ein Dropdown zu erhalten, klicken Sie auf die Ellipse, um ein weiteres Dropdown zu erhalten, und klicken Sie schließlich auf die Schaltfläche Fork. Dann schlage ich vor, Ihre Skillshare- und Replit-Bearbeitungsfenster nebeneinander zu platzieren , wie hier gezeigt. Wir werden über Palmen in drei verschiedenen Kategorien sprechen. Wir werden zunächst über Implementierungsprobleme sprechen. Dies sind Probleme , die Kernfunktionen für eine Datenstruktur implementieren . Zweitens werden wir einige Funktionen implementieren und die richtige Datenstruktur für die Verwendung auswählen , das ist der entscheidende Teil. Wir werden lernen, wie man die Datenstrukturen anhand ihrer Vor- und Nachteile auswählt . Im dritten Fall werden wir Datenstrukturen möglicherweise zu einer neuen Datenstruktur oder einer neuen Anwendung kombinieren möglicherweise zu einer neuen Datenstruktur oder einer neuen , bei der wir mehrere Datenstrukturen für jede ihrer Stärken unter einen Hut bringen müssen . Unabhängig von der Kategorie der Probleme hier einen Profi-Tipp, den Sie immer beachten sollten. In der Tat werden wir diesen Profi-Tipp für die gesamte Lektion und alle folgenden Übungslektionen befolgen. Wir werden alle unsere Fragen zum Lösen von Rätseln auf diese Weise lösen. Sie sollten auch alle Ihre Interviews zum Lösen von Rätseln auf diese Weise führen. Zuerst entwerfen wir den Algorithmus ohne Codierung. Entwerfen Sie den Algorithmus mündlich oder visuell mit Ihrem Interviewer. Auf diese Weise kann Ihr Interviewer Sie und Ihr Denken zur richtigen Antwort führen . Zweitens melden Sie die Laufzeit und die Speicherkomplexität. Das ist wichtig. Zunächst, um Verständnis zu demonstrieren, aber auch um zu sehen, ob Ihr Interviewer eine effizientere Lösung wünscht. Drittens, und schließlich, dann programmieren Sie. Wir strukturieren Ihre Praxis auf diese Weise. Entwerfe den Algorithmus, dann gehe ich die Lösung durch, berichte die Komplexität und dann gehe ich die Lösung und codiere schließlich den Algorithmus, und dann gehen wir noch einmal die Lösung durch. Die Idee ist, dass Sie, selbst wenn Sie einen Teil dieses dreistufigen Prozesses durcheinander bringen , die anderen Schritte üben können. Hier ist unsere allererste Frage. Wir werden Warteschlangen implementieren, aber Stacks verwenden. Bevor Sie jedoch beginnen, lassen Sie mich über einen Tipp sprechen. Denken Sie daran, dass ein Stack als letztes an erster Stelle steht. Das heißt, es ist wie ein Stapel Steine oder ein Stapel Kisten. Der letzte Gegenstand, den du auf diesen Stapel legst , wird der erste sein, den du abnimmst, das ist Stapel. Jetzt ist Warteschlange wie eine Linie, sagen wir mal eine Warteschlange von Autos. Das heißt First-In, First-Out. Das erste Auto, das sich anstellte, ist das erste Auto , das diese Warteschlange verlässt. Behalte das im Hinterkopf. Das, was ich hier geschrieben habe, eine Warteschlange ist First-in-First-Out, ein Stack ist Last Out, First-In. Zugegeben, ich erinnere mich nie an diesen First-In, First-Out, Last-Out, First-In. Woran ich mich stattdessen erinnere, ist so ziemlich genau dass ich mit dem Stapel arbeite, wie ein Stapel Steine, Stapel Kleidung, ein Stapel Kisten, und dann denke ich an eine Warteschlange nur als eine Linie. Wenn Sie das im Hinterkopf behalten, zeigt Ihnen das, in welche Richtung Sie sich bewegen. Die Übung hier besteht erneut darin, eine Warteschlange mithilfe von Stacks zu implementieren. Wenn du in deinem ripl.it nach unten scrollst, siehst du das genau hier. Das müssen Sie implementieren. Also pausiere das Video hier und denke zuerst über den Algorithmus nach. Wie werden Sie das konzeptionell machen ? Also versuch es mal. Hier ist die Antwort. Also werden wir Folgendes tun. Angenommen, wir haben diese Liste hier in Schwarz. Wir haben 9, 8, 2, 7. Neun ist unser erster Artikel. Unser letzter Punkt ist sieben. Um eine Warteschlange zu emulieren, müssen wir das erste Element entfernen, das neun ist, mit dem roten Pfeil genau dort. Unser Ziel, hier rot dargestellt, ist es, den Wert neun zu entfernen und den verbleibenden Stapel 8, 2, 7 zurückzugeben oder zu behalten. Zu wissen, dass das unser Ziel ist, das Einzige, was wir mit einem Stapel tun können, ist das Einzige, was wir mit einem Stapel tun können, vom letzten Gegenstand zu entfernen. Wir können nur Pop Seven. Nun, das ist okay. Das können wir weiter machen. Wir werden sieben, wir machen zwei, wir machen acht und schließlich werden wir neun. Da haben wir's. Wir können jetzt neun zurückgeben. Jetzt können wir uns von einem Stapel entfernen. Wir haben jedoch alle Gegenstände verloren , die zuvor veröffentlicht wurden, alle 8, 2 und 7. Wir brauchen also einen weiteren Stapel , um sie alle aufzunehmen. Also hier, was wir tun werden , ist sieben herauszuholen, aber sie auf den anderen Stapel zu schieben. Dann gehen wir zwei, acht raus, und dann kommen wir endlich neun zurück. Das ist großartig. Wir sind fast da. Wir brauchen jedoch 8, 2, 7 in unserem Stack. Im Moment haben wir die umgekehrten 7, 2, 8. Was wir also tun werden , ist vom zweiten Stapel zu springen und ihn wieder auf den ursprünglichen Stapel zu schieben . Also werden wir das zurückdrängen und los geht's. Wir haben beide Ziele. Wir können neun zurückgeben, als wäre es eine Warteschlange, und wir können einen verbleibenden Stapel 8, 2, 7 so behalten , wie wir wollten. Das scheint eine clevere Lösung zu sein. Ich würde mir wahrscheinlich sagen, dass ich diese Lösung auf keinen Fall finden kann. Wenn Sie dasselbe denken, bin ich ganz bei Ihnen und ich bin hier, um Ihnen zu sagen , dass das kein Problem ist. Es besteht eigentlich keine Notwendigkeit , diese Lösung zu finden. Verstehen Sie es stattdessen gründlich. in Ihren Interviews oder bei den Jobs einfach an die Taktiken, Erinnern Sie sich in Ihren Interviews oder bei den Jobs einfach an die Taktiken, die Ihnen zur Verfügung stehen Konzentrieren Sie also Ihre mentale Energie darauf, die Lösung zu verstehen. Wenn etwas keinen Sinn ergibt, fragen Sie einfach im Diskussionsbereich nach. wissen, dass dies der Algorithmus ist, fahren wir mit dem zweiten Schritt fort. Was ist die algorithmische Komplexität? Was ist die Laufzeitkomplexität und wie hoch ist die Speicherkomplexität? Pausiere das Video hier und versuche es herauszufinden . Hier ist die Antwort. Die algorithmische Komplexität oder Laufzeitkomplexität ist hier O (n). Wir müssen grundsätzlich zwei n Operationen durchführen, das ist O (n) -Zeit. Wir müssen jeden einzelnen Gegenstand in den zweiten Stapel legen, und dann müssen wir jeden einzelnen Gegenstand vom zweiten Stapel zurück in den ersten stapeln. Das ist O (n) für die Laufzeitkomplexität, für die Speicherkomplexität, es ist auch O (n) , einfach weil wir diesen anderen Stack behalten mussten und dieser andere Stapel bis zu n verschiedene Elemente benötigte. Wir haben also O (n) Laufzeitkomplexität, O (n) Speicherkomplexität. Versuchen wir nun, dies in Code zu implementieren. Halten Sie das Video hier erneut an und probieren Sie es zuerst selbst aus. Lassen Sie uns jetzt diese Lösung implementieren. Wir beginnen damit, einen Konstruktor zu schreiben. Dieser Konstruktor nimmt ein iterierbares auf. Hier können wir also sehen, dass dieser Konstruktor ein iterierbares Objekt annimmt, das eine Liste von nur einem Element ist. Aus dem Iterablen bauen wir einen Stack. [NOISE] Um nun [NOISE] in die Warteschlange zu stellen, drücken wir einfach auf den Stack. [NOISE] Ich möchte jedoch die Warteschlange entfernen oder vom Stack entfernen , hier kommt die spezielle Logik ins Spiel. Das ist der gesamte Algorithmus, über den wir hier auf der linken Seite gesprochen haben . Wir beginnen damit, den ursprünglichen Stapel in einen neuen Stapel zu leeren . Hier haben wir den neuen Stack. Dann, während wir Gegenstände im alten Stapel haben, werden wir sie auf den neuen Stapel schieben und wir werden sie vom alten verschieben. Jetzt, wo wir all diese Gegenstände herausgebracht haben, werden wir jetzt endlich den letzten Artikel herausnehmen. Dies wird uns den Wert geben , den wir tatsächlich zurückgeben müssen. Leeren wir nun den neuen Stapel wieder in den ursprünglichen Stapel. Denken Sie daran, dass dies der letzte Schritt war , bei dem wir alles umgekehrt hatten. Wir müssen sie alle zurück in den ursprünglichen Stapel schieben. Jetzt müssen wir, während der Stapel Gegenstände enthält, sie wieder auf den ursprünglichen Stapel schieben. Schließlich geben wir den Wert zurück. Los geht's. Wir haben unseren dreistufigen Algorithmus von früher implementiert. Lassen Sie uns nun prüfen, ob diese Funktion unsere Doctests besteht oder nicht . Sie dazu auf Ausführen, die grüne Schaltfläche ganz oben in Ihrer Datei. Hier können wir sehen, dass wir in allen anderen Funktionen Fehler hatten , aber nicht in dieser. Denken Sie daran, unsere Funktion hier ist Queue via Stack, was in dieser Liste fehlt. Wir sind startklar. Wir haben unsere Doctests für diese Funktion bestanden. Gehen wir jetzt zur nächsten Frage über. In unserer nächsten Frage werden wir das Gegenteil tun. Wir werden einen Stack mithilfe von Warteschlangen implementieren. Auch hier haben wir eine Warteschlange mit Stacks implementiert. Bei unserem nächsten Problem werden wir das Gegenteil tun. Nehmen Sie sich eine Sekunde Zeit, um das Video anzuhalten und zu überlegen welchen Algorithmus Sie dafür erstellen würden. Hier ist eine Lösung. Dies ist dem letzten Problem sehr ähnlich. Wie zuvor werden wir eine zweite Warteschlange instanziieren. Wir nehmen dann jeden Gegenstand in unserer ersten Warteschlange und schieben ihn in die zweite Warteschlange. Sobald wir beim letzten Element angekommen sind, wird dieses Listenelement genau wie ein Stapel zurückgeben. Das ist so ziemlich alles. Nachdem wir den Algorithmus haben, wollen wir über die Speicher- und Laufzeitkomplexität des Algorithmus nachdenken . Pausiere das Video erneut. Hier ist eine konzeptionelle Lösung. Hier wollen wir einen Stack implementieren, wir haben jedoch nur eine Warteschlange. Erinnern Sie sich daran, dass eine Warteschlange nur von Anfang an aus der Warteschlange entfernt werden kann , während wir, um einen Stack zu emulieren, in der Lage sein müssen, vom Ende her zu springen. Wir haben dieses Dilemma, aber wir werden es auf die gleiche oder ähnliche Weise lösen wie zuvor. Wir erstellen eine zweite Warteschlange. Dann werden wir neun aus der Warteschlange entfernen und sie in die zweite Warteschlange stellen. Dann entfernen wir acht und fügen eine zweite Warteschlange hinzu, dann entfernen wir zwei aus der Warteschlange und fügen sie der zweiten Warteschlange hinzu. Schließlich werden wir sieben aus der Warteschlange entfernen und sie zurückgeben. Hier haben wir es tatsächlich geschafft, „Pop Seven“ zu erreichen. Hier ist etwas Neues, anders als zuvor ist unsere zweite Warteschlange bereits in der richtigen Reihenfolge. Denken Sie daran, dass unsere Warteschlange vor 9, 8, 2, 7 war und jetzt 9, 8, 2. Dies ist die genaue und korrekte Reihenfolge. Wir müssen es nicht wie zuvor wieder in die ursprüngliche Warteschlange verschieben . Zu wissen, dass wir jetzt genau wissen können , wie wir diesen Algorithmus implementieren werden. Bevor wir das tun, wollen wir jedoch die Laufzeit und Speicherkomplexität der Funktion melden . Pausiere das Video hier und versuche es herauszufinden. Jetzt ist hier die Lösung. Die Laufzeit und Speicherkomplexität ist o von n, genau wie zuvor. die Laufzeitkomplexität zu gewährleisten, müssen wir jedes einzelne Element in der Liste Warteschlange entfernen und es in die zweite Warteschlange einreihen, also ist es o of n. Aus Gründen der Speicherkomplexität müssen wir nun die zweite Warteschlange beibehalten. Die zweite Warteschlange umfasst im Grunde genommen keinen Speicher bis zu einem Element. Da hast du es. Sowohl die Laufzeit als auch die Speicherkomplexität sind beide o of n. Nun wir endlich zum dritten Schritt über. Versuchen wir, diesen Algorithmus zu codieren. Pausiere das Video hier und versuche es zu codieren. Lassen Sie uns nun über die Lösung sprechen. Lassen Sie uns den Stack mithilfe von Warteschlangen implementieren. Genau wie zuvor werden wir einen Konstruktor erstellen. Dieser Konstruktor wird eine Iterable benötigen. Das Iterable wird der Anfangswert unserer Warteschlangen sein. Dann lasst uns Push für diesen Stack implementieren. Dies ist nur ein Warteschleifen- und Warteschleifenanruf. Die ganze Magie wird jetzt in der Pop-Methode geschehen. Bei dieser Pop-Methode leeren wir zunächst n minus ein Element in der ursprünglichen Warteschlange in die neue Warteschlange. Um das zu tun, erstellen wir eine Warteschlange. Das ist unsere neue Warteschlange. Während unsere alte Warteschlange mindestens einen Gegenstand hat, werden wir ihn weiter entfernen. Hier wird es tatsächlich aus der alten Warteschlange entfernt und in die neue Warteschlange eingefügt. Nachdem wir fast alle aus der Warteschlange entfernt haben, entfernen wir nun das letzte Element aus der alten Warteschlange. Hoppla, das ist Queue, Dequeue. Dieser letzte Punkt wäre zum Beispiel sieben. Dann würden wir diesen Wert zurückgeben. Wie wir bereits gesagt haben, enthält unsere neue Warteschlange alle Elemente, die wir benötigen, in der richtigen Reihenfolge. Wir ersetzen einfach unsere alte Warteschlange durch unsere neue. Jetzt können wir einen Wert zurückgeben. Lass uns weitermachen und versuchen, alle unsere Tests durchzuführen und zu sehen, was passiert. Da haben wir's. Wir können sehen, dass unser Stack via Queue nicht mehr in der Liste der Fehler steht. Tada, wir haben diese Tests bestanden. Gehen wir nun zum nächsten Problem über. Das nächste Problem ist, dass wir eine Datenstruktur verwenden. Ich werde Ihnen nicht sagen, welches Sie verwenden sollen, und es liegt an Ihnen, zu entscheiden, welches. Wir werden entweder Stapel oder Warteschlangen verwenden. Insbesondere um alle Kombinationen der Buchstaben a und b zu drucken. Wir werden alle Kombinationen der Länge k ausdrucken . Hier ist ein Beispiel, print_baba von 2 gibt uns aa, ab, bb. Wenn wir nun print baba von 3 nennen, dann gibt uns das alle drei Buchstaben bestehenden „Wörter“, die die Buchstaben a und b haben. Auch hier sind alle möglichen Kombinationen möglich. Beachten Sie, dass es keine Wiederholungen gibt. Beachten Sie, dass jeder einzelne Wert hier die Länge drei hat. das wissen, lassen Sie uns noch einmal den dreistufigen Prozess durchlaufen , über den wir zuvor gesprochen haben. Lassen Sie uns im ersten Schritt den Algorithmus entwerfen. Pausieren Sie das Video hier und versuchen Sie, den Algorithmus zu entwerfen. Auch hier sollten Sie entweder einen Stack oder eine Warteschlange verwenden. Lassen Sie uns nun konzeptionell über die Lösung sprechen. Hier in dieser speziellen Lösung verwenden wir eine Warteschlange. Ich werde die Warteschlange hier zuerst mit einer einfachen Liste kennzeichnen hier zuerst mit einer einfachen Liste , um zu visualisieren, was vor sich geht. Nehmen wir an, ich habe den Buchstaben A hier drin. Was wir tun werden, ist zuerst den Buchstaben a aus der Warteschlange zu entfernen. Jetzt haben wir eine und wir werden die einzigen zwei möglichen Möglichkeiten, den Buchstaben a zu verlängern, in die Warteschlange stellen. Um von einer Ein-Buchstaben-Sequenz die einzigen zwei möglichen Möglichkeiten, den Buchstaben a zu verlängern, in die Warteschlange stellen. aller möglichen As auszugehen und b steht für alle möglichen Zwei-Buchstaben-Sequenzen von a und b. Wir fügen aa und ab hinzu. Dies berücksichtigt alle möglichen Möglichkeiten eine A- und B-Sequenz von diesem Ausgangspunkt aus zu erstellen. Jetzt können wir das etwas umfangreicher machen. Wir beginnen mit der leeren Zeichenfolge. Dann entfernen wir diesen leeren String aus der Warteschlange. Dann fügen wir die einzigen zwei möglichen Möglichkeiten hinzu, dies zu erweitern, nämlich das Hinzufügen eines a am Ende oder das Hinzufügen eines B am Ende. Dann werden wir aus der Warteschlange entfernt und dann werden wir ein Plus a plus b hinzufügen, also aa ab. Dann machen wir das nochmal. Wir werden den ersten Punkt hier aus der Warteschlange entfernen, der b sein wird. Dann erweitern wir mit ba und bb. Wir machen das weiter. Jetzt haben wir aa, wir entfernen es aus der Warteschlange, und dann fügen wir aa plus aa plus b hinzu, also aaa, aab. Wir machen das weiter. Indem wir zum richtigen Zeitpunkt anhalten, stellen wir sicher, dass wir tatsächlich alle möglichen k-Längensequenzen von As und Bs abgedeckt haben. wissen, dass dies der Algorithmus ist, sprechen wir nun über die Speicher- und Laufzeitkomplexität. Pausieren Sie das Video hier und prüfen Sie, ob Sie sich diese beiden Komplexitäten einfallen lassen können. Lassen Sie uns über die Rechen- und Speicherkomplexität sprechen. Die Rechen- und Speicherkomplexität ist in diesem speziellen Szenario tatsächlich kompliziert, aber wir werden sie trotzdem besprechen. Angesichts dessen, was Sie bisher gewusst haben, kann es sich dieses vielleicht nicht einfallen lassen, aber das ist völlig in Ordnung. Auch hier ist es Ihr Ziel, die Lösung zu verstehen , die ich jetzt vorstelle. Lassen Sie uns darüber sprechen, wie oft es dauert, oder lassen Sie uns zuerst darüber sprechen, wie viele Ergebnisse es hier gibt. Alle möglichen drei Längenfolgen von A's und B's. Hier haben wir zwei Optionen für jeden einzelnen Buchstaben, a oder b mal 2 mal 2. Das heißt, es gibt hier zwei Möglichkeiten von drei verschiedenen Optionen. Wenn wir alle möglichen Sequenzen der Länge k wollen, geben wir zwei bis k verschiedene Antworten aus. Um nun zwei bis k verschiedene Antworten zu erzeugen , mussten wir tatsächlich auch alle beiden Längen- und Ein-Length-Lösungen generieren . In Wirklichkeit müssen wir zwei bis drei verschiedene Optionen generieren. Wir mussten zwei zu den Zwei durchgehen, und dann auch zwei zu dem einen. Das stimmt im Allgemeinen. Wenn wir alle vier Längensequenzen erzeugen wollten, hätten wir zwei zu der einen zwei zu den zwei die drei und zwei zu den vier verschiedenen Kombinationen und so weiter und so fort generiert drei und zwei zu . In Wirklichkeit bedeutet das, dass dies tatsächlich eine Summe von bis zu, sagen wir mal j, Werten ist tatsächlich eine Summe von bis zu, sagen wir mal j, , bei denen höchstens j gleich k ist . Hier haben wir diese fiese Zusammenfassung einer Reihe von Begriffen. Zum Glück für uns kümmert sich die Größenordnungsanalyse jedoch nur um den Begriff der größten Größenordnung. Hier können wir das einfach als o von 2 auf n zusammenfassen , oder ich denke k, da wir k verwenden. Da wir wissen, dass dieser Algorithmus ziemlich langsam ist, ist dies die Rechenkomplexität. Zum Glück für uns ist die Speicherkomplexität leider auch die numerische Komplexität dieselbe. O von 2 zu k. Das ist ein ziemlich langsamer Algorithmus. Es ist auch ein ziemlich teurer Algorithmus, aber wir lassen das, dass es ein hübscher, komplexer Algorithmus ist. Aber zumindest angesichts dessen, was wir bisher gesagt haben, ist dies eine Möglichkeit, dies zu tun, und wir werden es vorerst so machen. Machen Sie weiter und pausieren Sie das Video hier und versuchen Sie, es zu codieren. Jetzt ist hier eine Lösung. Um loszulegen, erstellen wir eine Warteschlange. Genau wie wir zuvor besprochen haben, werden wir die leere Zeichenfolge hinzufügen . Diese leere Zeichenfolge alle möglichen Sätze von Sequenzen der Länge Null. Daraus generieren wir alle möglichen Sätze von Sequenzen einer Länge usw. Während sich etwas in der Warteschlange befindet, werden wir immer wieder generieren. Lass uns weitermachen und die erste Zeichenfolge öffnen oder aus der Warteschlange entfernen. Wenn unser Wort hier genau die Länge k hat, dann drucken wir dieses Wort einfach aus und schon kann es losgehen. Wenn das Wort jedoch noch nicht die Länge k hat, werden wir es erweitern. Wir werden es mit dem Buchstaben a erweitern und wir werden es mit dem Buchstaben b erweitern. Los geht's. Das ist es für unsere Funktion. Dies gibt uns alle möglichen Kalign-Sequenzen da dies sicherstellt, dass wir mit allen Möglichkeiten a und b erweitern , und dies stellt sicher, dass alle Kalign-Sequenzen erfasst werden, sobald sie entstehen. Wenn es etwas längeres als eine Kalign-Sequenz gibt, dann geht es nirgendwohin. Wir werden nicht weiter damit arbeiten. Tatsächlich können Sie tatsächlich beweisen, dass es keine Länge mehr als k geben wird , aber das ist nicht besonders wichtig. Der Punkt ist, dass diese Funktion uns das geben sollte, was wir wollen. Lass uns weitermachen und es jetzt versuchen. oben auf die grüne Schaltfläche „Ausführen“ und Sie sollten sehen, dass print_baba aus dieser Liste der Fehler verschwindet . Da haben wir's. print_BABA ist jetzt aus der Liste der Fehler verschwunden. Wir haben auch diesen Test bestanden. Gehen wir zur nächsten Frage über. In dieser nächsten Frage verwenden wir nun Stapel oder Warteschlangen, um Treppen zu drucken. Drucken Sie insbesondere die Anzahl der Möglichkeiten aus, wie wir k Treppen hinaufsteigen können, wenn Sie jeweils ein oder zwei Schritte ausführen können. Wenn Sie eine Treppe haben, können Sie sie nur in eine Richtung hinaufsteigen. Wenn Sie zwei Treppen haben, können Sie sie in zwei Richtungen hinaufsteigen, entweder Sie machen jeweils einen Schritt oder Sie machen beide Schritte gleichzeitig. Wenn es drei Stufen gibt, dann kannst du entweder 1,1,1 steigen, oder du kannst zwei Stufen und dann eine Stufe hinaufsteigen, oder du kannst eine Stufe und dann zwei Stufen hinaufsteigen, also ist es 1,1,1; 1,2 oder 2,1. Es gibt drei Möglichkeiten , drei Stufen zu erklimmen. Diese Funktion soll die Anzahl der Möglichkeiten berechnen, die zum Treppensteigen erforderlich sind, wenn Sie ein oder zwei Schritte ausführen können . Bevor Sie den Algorithmus in Betracht ziehen, finden Sie hier einen Hinweis. Nun, Sie können das Video hier pausieren , wenn Sie den Hinweis nicht möchten, aber der Hinweis ist der folgende; vor unseren Aktionen ohne Zitat für jedes einzelne Wort, noch nicht die Länge k hatte, wir hatten zwei verschiedene Aktionen. Sie können a oder b hinzufügen. Hier haben Sie etwas Ähnliches. Wenn Sie noch nicht alle k Treppen hinaufgestiegen sind, haben Sie zwei verschiedene Aktionen. Sie können eine Stufe oder zwei Stufen hinaufsteigen. Wenn Sie das wissen und wissen , dass wir die Warteschlange zuvor verwendet um alle möglichen Möglichkeiten zum Aufbau eines Wortes darzustellen, können Sie diese Warteschlange jetzt auch verwenden, um alle möglichen Wege zum Treppensteigen aufzubauen . Pausieren Sie das Video hier und prüfen Sie, ob Sie den Algorithmus herausfinden können. Jetzt ist hier eine Lösung. Die Lösung wird die vorherige Lösung tatsächlich ziemlich genau widerspiegeln. Was wir tun werden, ist für jede einzelne Treppe, sagen wir, wir haben jetzt eine Warteschlange, unser Ziel wird es sein, jeder einzelne Gegenstand in der Warteschlange wird ein anderer Weg sein , dies zu erreichen bestimmte Anzahl von Schritten. Hier ist eine Möglichkeit, es auszudrücken. Nehmen wir an, wir haben Null. Das heißt, dies ist eine Möglichkeit, die Treppe auf Null zu bringen. Dann werden wir diese 0 aus der Warteschlange entfernen. Dann fügen wir der Warteschlange zwei Elemente hinzu. Sie können entweder einen Schritt oder zwei Schritte machen. Wir nehmen 0 plus 1 und 0 plus 2. Dann entfernen Sie den ersten Gegenstand aus der Warteschlange. Dann stellen wir uns wieder in die Warteschlange. Wir sagen, es gibt zwei Möglichkeiten Sie können entweder einen Schritt machen, Sie können 1 plus 1 machen, was 2 ist, oder Sie können zwei Schritte machen, das sind drei. Dann entfernen wir uns wieder aus der Warteschlange. Wir werden die beiden aus der Warteschlange entfernen. Dann sagen wir, hier sind zwei. Ab diesem Zeitpunkt können Sie ein oder zwei Schritte ausführen. Sie können 2 plus 1 oder drei oder 2 plus 2 nehmen, was 4 ist, und dann so weiter und so fort. Du kannst das weiter machen. Was im Grunde passieren wird, ist, wenn Sie zählen , wie oft k in dieser Warteschlange vorkommt, das zeigt Ihnen, wie viele Möglichkeiten es gibt um zu dieser Anzahl von Treppen zu gelangen. Das war's für die konzeptionelle Lösung des Algorithmus. Wir werden tatsächlich die Speicher- und Laufzeitkomplexität für dieses spezielle Problem überspringen die Speicher- und Laufzeitkomplexität , nur weil es ziemlich schwierig zu berechnen ist. Es gibt eine Möglichkeit, dies zu berechnen. Wir werden es vorerst überspringen. Ich werde eine schriftliche Version der Lösung in die endgültige Lösungsdatei aufnehmen . In der Zwischenzeit wollen wir mit der eigentlichen Codierung dieser Funktion fortfahren . Halten Sie das Video hier erneut an und versuchen Sie, die Lösung zu codieren. Lassen Sie uns jetzt die Lösung programmieren. Wir beginnen damit, hier eine Warteschlange mit nur null Elementen zu erstellen . Diese Warteschlange hat jetzt keine Elemente. Wir werden auch initialisieren, dass die Anzahl der Möglichkeiten, wie wir bis zu k Treppen hinaufsteigen können, Null ist. Während wir etwas in der Warteschlange haben, also während es Wege die Treppe hinauf gibt, werden wir uns in eine Richtung die Treppe hinauf aus der Warteschlange entfernen. Dadurch wird eine Richtung die Treppe hinauf aus der Warteschlange entfernt. Wenn wir es dann auf k Schritte geschafft haben, erhöhen wir die Anzahl der möglichen Wege, um bis zu k Schritte zu erreichen, um eins. Wenn wir es noch nicht bis zu k Stufen geschafft haben, also die Treppe weniger als k ist, dann werden wir überlegen, wie wir dorthin gelangen können. Wir werden entweder plus 1 Treppe in Betracht ziehen oder zwei Schritte in Betracht ziehen. Dann geben wir schließlich die Gesamtzahl der Möglichkeiten zurück , die benötigt wurden, um bis zu k Schritte zu erreichen. Jetzt, wo du das hast, lass uns weitermachen und auf „Ausführen“ klicken. Da haben wir's. Wir können jetzt sehen, dass print_stairs nicht mehr in unserer Liste der Fehler enthalten ist. Wir haben alle Testfälle bestanden. Bevor wir zur nächsten Frage übergehen, wollte ich diesen Tipp ansprechen. Der Tipp ist, das Kernproblem zu lösen und dann die Randfälle zu betrachten. Wir haben hier einige der Tipps für Randfälle übersprungen und Sie können sie sehen, wenn Sie zu den Folien zurückkehren, aber die Grundidee ist, sich erneut auf das Kernproblem zu konzentrieren, auf das Kernproblem zu konzentrieren bevor Sie sich mit Edge befasst haben Koffer. Das ist momentan nicht besonders relevant, aber wenn wir Probleme lösen, werden wir immer mehr dieser Randfälle sehen, und manchmal können sie eine Ablenkung sein. Konzentrieren Sie sich erneut darauf, zuerst das Kernproblem zu lösen. Hier werden wir jetzt Stapel und Warteschlangen verwenden , um Klammern zu überprüfen. Eigentlich ist dies das erste Problem, bei dem wir eine Reihe zufälliger Randfälle sehen werden . Hier ist das Problem. Scrollen Sie in Ihrer Datei ein wenig nach unten und Sie werden sehen, dass unser Ziel darin besteht, zu überprüfen, ob die angegebene Zeichenfolge übereinstimmende Klammern enthält. [GELÄCHTER] Ich habe dir hier eigentlich schon einen Hinweis gegeben. Du solltest einen Stack verwenden, aber lass uns reingehen und hier nach unten scrollen und sehen, was das bedeutet. Passende Klammern bedeuten, dass für jede einzelne Startklammer Sie für jede einzelne Startklammer eine entsprechende schließende Klammer haben. Es ist jedoch nicht nur, ob es existiert, es muss in der richtigen Reihenfolge sein. Sehen Sie zum Beispiel, hier haben Sie eine Startklammer. Eigentlich fehlt hier nur eine Endklammer, aber hier ist ein Fall, in dem Sie ungültige Klammern haben. Hier haben Sie Endklammer und dann beginnen Sie mit Klammern. Diese sind nicht ordnungsgemäß geschlossen. Das bedeutet, dass vor jeder einzelnen geschlossenen Klammer Sie vor jeder einzelnen geschlossenen Klammer eine entsprechende Startklammer benötigen. Das ist es, was es bedeutet, zusammenzupassen. Dies ist tatsächlich ein sehr häufiges Problem , das Sie sowohl als Übung als auch möglicherweise sogar im Interview selbst sehen werden. Das ist gut zu wissen, gut zu verstehen, was die Prämisse des Problems ist. Auch hier möchten wir überprüfen, ob die bereitgestellte Zeichenfolge übereinstimmende Klammern enthält. Auch hier ist der Hinweis, einen Stack zu verwenden. Halten Sie das Video hier an und prüfen Sie, ob Sie den Algorithmus finden können. Lassen Sie uns jetzt über die Lösung sprechen. Unsere Lösung besteht, wie wir bereits im Tipp oder im Hinweis erwähnt haben , darin, tatsächlich einen Stack zu verwenden. Nehmen wir an, wir haben diesen Stack genau hier. Dieser Stapel wird darstellen, wie viele Startklammern oder wie tief wir in Klammern stehen. Nehmen wir an, wir iterieren durch die Zeichenfolge und wir haben diese ersten Klammern gesehen, wir werden hier eine Startklammer hinzufügen. Dann sehen wir jetzt eine enge Klammer. Jedes Mal, wenn wir enge Klammern sehen, springen wir vom Stapel ab. Wir sind vom Stapel abgekommen, haben eine Ebene höher entfernt, wir haben diese Gruppe entfernt. Jetzt, wo wir eine weitere Startklammer sehen, fügen wir wieder hinzu, wir sehen eine weitere Startklammer wir wieder hinzufügen werden, und dann sehen wir hier eine geschlossene Klammer, also lass uns mach weiter und entferne das, und dann sehen wir eine weitere Startklammer, also fügen wir das wieder hinzu. Endlich werden wir das wieder schließen. Jetzt können wir ganz am Ende der Funktion sehen , dass wir immer noch eine nicht geschlossene Klammer haben , nämlich diese. Diese Gruppe ist nicht geschlossen. Das bedeutet, dass dies kein übereinstimmender Satz von Klammern ist, wir können false zurückgeben. Im Wesentlichen stellt dieser Stapel tatsächlich dar, wie tief wir uns in diesem verschachtelten Satz von Klammern befinden. Immer wenn du vorbeikommst, bedeutet das, dass wir wieder auf einem Level sind. Wir haben einen verschachtelten Satz Klammern losgeworden. Wenn wir nicht den ganzen Weg wieder hoch und aus den Klammern herausgekommen sind, stimmt es nicht überein. Wenn Sie gleichzeitig so etwas sehen, das mit einer engen Klammer beginnt, ist der Stapel ganz am Anfang der Zeichenfolge leer, sodass nichts zu knallen ist . Dies wird uns sofort sagen, dass dies wiederum ein ungültiger Satz von Klammern ist. So werden wir das konzeptionell machen. Lassen Sie uns nun über die Laufzeitkomplexität und die Speicherkomplexität sprechen . Pausiere das Video hier und versuche es herauszufinden. Hier ist die Antwort. Sowohl für die Laufzeit als auch für die Speicherkomplexität sind beide linear. Während wir hier iterieren , wir im Grunde nur einmal über die Zeichenkette iterieren . Jeder einzelne Gegenstand ist nur einmal zu sehen. Zweitens, um die lineare Speicherkomplexität zu verstehen, stellen Sie sich vor, Sie hätten eine Zeichenfolge, die so aussieht, dann wäre unser Stack einfach all diese Startklammern. Wir haben bis zu N verschiedene Elemente in unserem Stack, das heißt, unsere Speicherkomplexität ist O von N. Gehen wir nun zum dritten Schritt über, fangen wir an, diesen Algorithmus zu codieren. Pausiere das Video hier und versuche es zu codieren. Lass uns reingehen und diesen Algorithmus jetzt programmieren. Hier erstellen wir zunächst einen Stapel, und für jeden einzelnen Buchstaben in der Zeichenfolge überprüfen wir, ob der Buchstabe mit und für jeden einzelnen Buchstaben in der Zeichenfolge überprüfen wir, ob der Buchstabe einem neuen Satz von Klammern beginnt, wir auf den Stapel drücken. Andernfalls können wir davon ausgehen , dass der Buchstabe eine schließende Klammer ist. Das habe ich gerade vorhin geschrieben. Grundsätzlich sollten Sie in einem Interview fragen, ob die Zeichenfolge andere Zeichen wie Zahlen oder Buchstaben und was auch immer enthalten könnte . Nur der Einfachheit halber gehen wir davon aus, dass die Zeichenfolge nur Klammern enthält. Wenn es sich hier nicht um eine öffnende Klammer handelt, ist es eine schließende Klammer. Für jede einzelne schließende Klammer springen wir vom Stapel ab. Wenn der Stack nichts mehr übrig hat, geben wir true zurück. Wenn der Stapel noch etwas übrig hat, bedeutet das, dass wir nicht alle unsere Klammern geschlossen haben, also sollten wir false zurückgeben. Nun, da ich das weiß, ist hier tatsächlich ein Tipp, den ich zuvor oder in den Folien von früher erwähnt zuvor oder in den Folien habe, den ich aber nicht erwähnt habe. Betrachten Sie hier eine leere Sequenz. Was passiert, wenn dein Stack tatsächlich leer ist? Gibt es eine Stelle in Ihrem Code an der Ihr Code kaputt gehen würde? In diesem speziellen Fall ja, stack.pop. Hier überprüfen wir nicht, ob der Stack bereits leer ist, also lass uns das tun. Wenn der Stapel schon leer ist, was machen wir hier? Wenn der Stapel bereits leer ist und wir versuchen, eine öffnende Klammer zu entfernen, bedeutet dies, dass wir eine schließende Klammer gesehen haben, bevor wir die entsprechenden ersten Klammern gesehen haben. das wissen, bedeutet dies wiederum, die Zeichenfolge ungültig ist, also sollten wir false zurückgeben. Das war's wirklich für die Edge Cases. Ungültige Eingaben gelten hier nicht, sodass dieses Problem abgeschlossen ist. Lassen Sie uns nachsehen, ob es alle unsere Tests bestanden hat. den Code ausführen, werden Sie feststellen , dass is_valid_parentheses nicht in dieser Liste von Fehlern enthalten ist . Daher haben wir diese Funktionstests bestanden. Gehen wir zum nächsten Problem über. Hier ist der Tipp, berücksichtige eigentlich fremde Charaktere. Das war hier oben, der Tipp, in einem Interview fragen , ob die Zeichenfolge andere Zeichen für dieses spezielle Problem enthalten könnte . Jetzt solltest du es selbst versuchen. Verwenden Sie Stapel oder Warteschlangen , um zu überprüfen, ob übereinstimmende Klammern und übereinstimmende Klammern vorhanden sind . Hier haben wir Klammern , die sich öffnen und schließen, und wir haben auch Klammern , die sich öffnen und schließen. Dies ist eine gültige Gruppierung, da jedes einzelne Klammerpaar perfekt übereinstimmt. Aber es gibt noch eine zusätzliche Herausforderung. Beachten Sie hier, dass, wenn Sie nur eckige Klammern berücksichtigen, diese korrekt übereinstimmen. Wenn Sie nur die Klammern berücksichtigen, werden sie auch übereinstimmen. Sie werden jedoch feststellen, dass sich diese eckigen Klammern und Klammern überschneiden , was eine ungültige Gruppierung darstellt. In diesem Wissen versuchen wir, einen Algorithmus für dieses Problem zu entwerfen. Halten Sie das Video hier an und versuchen herauszufinden, wie Sie Ihre Datenstrukturen, Stacks oder Warteschlangen verwenden würden , um dieses Problem zu lösen. Auch hier können Sie Ihr vorheriges Problem als Inspiration verwenden. Pausiere das Video hier und probiere es aus. Hier ist eine Lösung. Genau wie zuvor werden wir einen Stack verwenden, außer dass der Stapel diesmal entweder Startklammern oder Startklammern enthält . Es wird immer grundsätzlich die Startinterpunktion enthalten . wissen, dass wir, wann immer wir wollen , eine schließende Klammer sehen, Wenn wir wissen, dass wir, wann immer wir wollen, eine schließende Klammer sehen, versuchen wir, vom Stapel abzuspringen. In diesem Fall ziehen wir diese eckige Klammer ab. Wir sehen derzeit eine schließende Klammer. Schließende Klammern und diese öffnende Klammer stimmen nicht überein, was bedeutet, dass es sich um eine ungültige Gruppierung handelt. Das würde in diesem speziellen Fall der Fall sein. Du würdest auf dem Stapel so etwas sehen. Sie würden eine eckige Klammer sehen, Sie würden eine Klammer sehen, und wenn Sie dann wegspringen, sehen Sie diese öffnenden Klammern im Vergleich zu dieser schließenden Klammer. Wenn diese beiden also nicht übereinstimmen, ist sie jetzt ungültig. Ansonsten sieht der Algorithmus ziemlich genauso aus wie zuvor. Jedes Mal, wenn Sie eine öffnende Interpunktion sehen, fügen Sie sie dem Stapel hinzu, wenn Sie eine schließende Interpunktion sehen, versuchen Sie, sie zu entfernen, und stellen Sie sicher, dass Sie die richtige Eröffnungsinterpunktion verwenden. Das war's für den Algorithmus. Lassen Sie uns nun erneut überlegen, was die Laufzeit und Speicherkomplexität ist. Pausieren Sie das Video hier und sehen Sie, ob Sie es herausfinden können. Nun, hier ist die Antwort. Sowohl für die Laufzeit als auch für die Speicherkomplexität haben wir O von N. Genau wie zuvor wiederholen wir aus Gründen der Rechenkomplexität einfach einmal über jedes einzelne Zeichen, also ist das O von N. Für die Speicherkomplexität wir könnten wieder eine Zeichenfolge mit nur einem Haufen öffnender Interpunktion wie dieser haben. Wenn Sie eine Reihe von öffnenden Interpunktionen wie diese haben, dann ist Ihr Stapel so lang wie Ihre Eingabezeichenfolge. Hier ist auch unsere Speicherkomplexität O von N. Nachdem wir diese ersten beiden Schritte bestanden haben, versuchen wir nun den dritten Schritt. Versuchen wir, diesen Algorithmus zu codieren. Pausiere das Video hier und sieh nach, ob du programmieren kannst. Versuchen wir nun, diese Funktion zu codieren. 7. Listen: Array vs. verlinkte Liste: Unsere erste Kategorie ist für Artikellisten. Wir analysieren zwei Datenstrukturen, insbesondere Arrays und verknüpfte Listen. Wir werden dann auf der Grundlage unserer Analyse ihre Vor - und Nachteile behandeln . Lassen Sie uns damit beginnen, unsere Array-Analyse abzuschließen. Ein Array ist einfach eine Liste von Elementen. Im Speicher werden die Array-Elemente direkt nebeneinander platziert. Zuvor haben wir festgestellt, dass das Durchsuchen eines Arrays O von n dauert, da wir die gesamte Liste ein Element nach dem anderen durchqueren müssen , um den Wert zu finden. Wir fanden auch heraus, dass das Abrufen O von 1 konstanter Zeit benötigt. Beenden wir nun diese Analyse und sehen, wie lange es dauert, ein Element in das Array einzufügen. Angenommen, Sie haben dieses Array von drei Gegenständen. Wir visualisieren jetzt einen Teil Ihres Gedächtnisses in Ihrem Computer. Wir möchten ein neues Element mit dem Wert vier einfügen. Theoretisch könnten wir einfach so einen Platz für vier Personen zuweisen . Möglicherweise hat Ihr Computer diesen Speicherplatz jedoch bereits anderen Daten zugewiesen. Infolgedessen weist die Einfügeoperation einen neuen Speicherblock für Ihre neue Länge für das Array zu, dann kopiert der Algorithmus jedes Element nacheinander. Schließlich wird der Wert des neuen Artikels aktualisiert. Bei einem Array mit n Elementen werden beim Einfügen n Kopien erstellt. Insertion ist somit ein O einer Operation. Zusammenfassend nimmt das Einfügen lineare Zeit in Anspruch. Mal sehen, wie lange das Löschen dauert. Angenommen, Sie haben ein Array von sechs Elementen, Sie möchten das dritte Element löschen, die Nummer 2 hier. Um dies zu tun, verschiebt der Löschalgorithmus alle Zahlen danach nach oben. Es kopiert 3 vorwärts, kopiert 4 vorwärts, kopiert 7 vorwärts und gibt dann den letzten Platz frei. Infolgedessen benötigt das Löschen für ein Array der Länge n n Kopien. Dadurch wird O von n gelöscht. Damit ist unsere Tabelle der Laufzeitkomplexitäten für das Array vervollständigt. Sie werden sofort feststellen, dass das Array sehr effizient zum Abrufen, aber bei allem anderen ineffizient ist. Unser Array ist insbesondere für Änderungen ineffizient weil wir alle Daten im Speicher zusammenhängend halten müssen. Mit anderen Worten, alle Gegenstände müssen so direkt nebeneinander liegen. Aber das können wir ändern. Platzieren wir jedes Element in einer Liste wo immer wir es wollen. Um alle Elemente zu einer Liste zu verbinden, müssen wir Links hinzufügen. Jeder dieser Links wird auch als Zeiger bezeichnet. Jeder Zeiger zeigt auf eine Position für das nächste Element in der Liste. Wir müssen auch eine Markierung hinzufügen , die anzeigt, dass die Liste beendet wurde. Dies nennen wir aufgrund all der Links, die wir eingefügt haben, eine verknüpfte Liste . Diese verlinkte Liste enthält einige wirklich nette Eigenschaften. Analysieren wir nun diese verlinkte Liste. Nehmen wir an, wir wollen einen neuen Wert 3 am Ende der Liste einfügen , dann vergeben wir einfach zwei neue Stellen und stellen sicher, dass 2 Punkte auf 3 gesetzt werden. Unabhängig davon, wie viele Elemente sich in der verknüpften Liste befinden, haben wir einen konstanten Arbeitsaufwand vor uns, um ein neues Element einzufügen, zwei Leerzeichen zuzuweisen und einen Zeiger zu ändern. Infolgedessen ist das Einfügen für verknüpfte Listen O von 1 konstanter Zeit. Die Geschichte ist ähnlich, auch wenn wir einen Wert in die Mitte der Liste einfügen möchten , zwei neue Leerzeichen für den neuen Wert und einen Zeiger zuweisen dann auf den vorherigen Wert 8 bis 3 zeigen auch wenn wir einen Wert in die Mitte der Liste einfügen möchten, zwei neue Leerzeichen für den neuen Wert und einen Zeiger zuweisen und dann auf den vorherigen Wert 8 bis 3 zeigen verweise den neuen Wert 3 auf 2. Da haben wir es. Das Einfügen ist abgeschlossen. Unabhängig von der Länge der verknüpften Liste müssen wir nur zwei Leerzeichen zuweisen und zwei Zeiger ändern. Dies ist immer noch eine konstante Zeit O von 1. Zusammenfassend lässt sich sagen, dass eine verknüpfte Liste eine konstante Zeiteinfügung aufweist. Sehen wir uns jetzt an, wie das Entfernen oder Löschen funktioniert. Angenommen, wir wollen 3, den letzten Artikel, löschen. Das ist einfach. Wir kehren einfach um, was wir für das Einfügen getan haben. Löschen Sie den vorletzten Zeiger. Jetzt ist der Punkt 3 nicht mehr Teil der verknüpften Liste. Um das letzte Element zu löschen, benötigen Sie nur eine Änderung mit einem Zeiger. Dies ist eine konstante Zeit oder O von 1. Das Löschen eines Elements in der Mitte der Liste ist ebenfalls einfach. Angenommen, Sie möchten 3, das dritte Element, löschen, ändern Sie den Zeiger für acht, um 3 zu überspringen. Jetzt ist 3 tatsächlich nicht mehr Teil der verknüpften Liste, und das war's. Um ein Element zu löschen, egal wo es sich befindet, ändern wir einfach einen Zeiger. Dies ist wieder eine konstante Zeit oder O von 1. Zusammenfassend lässt sich sagen, dass eine verknüpfte Liste eine konstante Zeit zum Einfügen und Löschen hat. Sehen wir uns nun an, wie lange der Zugriff auf die verlinkte Liste dauert. Nehmen wir an, wir suchen nach dem Wert 4. Wir müssen die gesamte verknüpfte Liste nacheinander durchqueren , bis wir 4 finden. Zuerst überprüfen wir den ersten Artikel. Ist das 4? 9 ist nicht 4, also greife auf das nächste Element zu. Ist das 4? 8 ist nicht 4, greifen Sie also auf das nächste Element zu. Ist das 4? 3 ist nicht 4 , als nächstes ist 2 nicht 4. Für eine verknüpfte Liste der Länge n benötigen wir also bis zu n Prüfungen. Durchsuchen einer verknüpften Liste dauert O von n Zeit. Zusammenfassend lässt sich sagen, dass eine verknüpfte Liste konstante Zeit für das Einfügen und Löschen hat , aber eine lineare Suchzeit. Leider ist das Abrufen von Elementen aus einer verknüpften Liste auch ziemlich langsam. Nehmen wir an, wir möchten das dritte Element in der verknüpften Liste überprüfen oder abrufen. Es gibt keine Möglichkeit zu wissen, wo das dritte Element im Speicher befindet also müssen wir von vorne beginnen. Wir greifen auf den ersten Gegenstand und folgen dann seinem Zeiger zum nächsten Element. Greifen Sie auf das nächste Element und folgen Sie dann dem Zeiger zum nächsten Element. Greifen Sie schließlich auf diesen Artikel zu. Da dies das dritte Element ist, geben Sie seinen Wert zurück. Für eine verknüpfte Liste der Länge n müssen wir bis zu n Knoten durchqueren. Das Abrufen des Ith-Elements aus einer verknüpften Liste dauert nicht lange. Zusammenfassend haben wir nun Analyse der verknüpften Liste abgeschlossen. Das Ändern der Liste, das Einfügen oder Löschen ist effizient. Der Zugriff auf die Liste, das Suchen oder Abrufen ist ineffizient. Nachdem wir nun sowohl Arrays als auch verknüpfte Listen analysiert haben, wollen wir sehen, wann welche verwendet werden sollen. Dies ist der wichtigste Teil dieser Lektion. Auch wenn Sie alles vergessen, was ich zuvor gesagt habe, bewahren Sie die nächste Folie in Ihrer Gesäßtasche auf. Das ist dein Essen zum Mitnehmen. Hier ist eine Zusammenfassung der Komplexität der Laufzeit. Auf der linken Seite haben wir die vier Operationen, die wir analysiert haben. In der 2. Spalte haben wir Array-Laufzeitkomplexitäten von früher. Auf der rechten Seite haben wir die Komplexität der Laufzeit der Liste verlinkt. Bei einem Array ist die Änderung langsam, aber das Abrufen ist schnell. Dies bedeutet, dass Arrays ideal für viele zufällige Zugriffe auf einen festen Datensatz sind. Für eine verknüpfte Liste ist der Zugriff langsam, aber die Änderung erfolgt schnell. Dies bedeutet, dass verknüpfte Listen aufgrund ihrer dynamischen Größe vorteilhaft sind , um sich ständig ändernde Daten aufzunehmen. Das war's. Dies ist die Zusammenfassungsfolie für diese Lektion. Eine Kopie dieser Folien und weiterer Ressourcen finden Sie auf der Kurswebsite. Lassen Sie uns nun diese Datenstrukturen in Aktion sehen. In der nächsten Lektion werden wir sowohl die Verwendung als auch die Implementierung dieser Datenstrukturen üben und unsere beiden Lernziele für diesen Kurs angehen. 8. Lists Practice: Willkommen zu einer weiteren Übungsstunde. Hier gehen wir zu den Übungslisten. Insbesondere werden wir hauptsächlich verlinkte Listen behandeln. Navigieren Sie wie zuvor zu dieser URL alvinwan.com/datastructures101/lists. Sobald Sie diese Seite sehen, forken Sie das Projekt, um Ihre eigene editierbare Kopie zu erhalten. Klicken Sie dazu oben links auf den Projektnamen , um ein Dropdown zu erhalten, klicken Sie auf die Ellipse, um ein weiteres Dropdown zu erhalten, und klicken Sie schließlich auf die Schaltfläche „Fork“. Dann schlage ich vor, Ihren Skillshare zu platzieren und darauf Fenster nebeneinander zu kräuseln , wie hier gezeigt. Hier sind die verschiedenen Unterthemen, die ich durchgehen möchte. Wir beginnen mit der Aufforderung. Dies ist der Code, Ihr Interviewer Ihnen zur Verfügung stellt. Anschließend werden wir über drei Kategorien von Eingabeaufforderungen sprechen : Implementierung, Verwendung und anschließende Kombination verschiedener Datenstrukturen. Zu Beginn sollten Sie auf der rechten Seite den Code sehen, den Ihr Interviewer Ihnen wahrscheinlich zur Verfügung stellen würde. Hier hättest du einen Stapel, du hättest eine Warteschlange. Nun, ich denke, Stack und Queue würden Sie wahrscheinlich nicht versorgen, aber Sie könnten sie sicherlich verwenden, wenn Sie müssten. Dies ist der Code , den Sie erhalten würden, im Grunde eine Implementierung einer verknüpften Liste. Sie haben etwas, das wie folgt aussieht. Sie hätten den Wert für den Link und Sie hätten auch eine Verbindung zum nächsten Link. Dies hier ist ein Dienstprogramm, das ich bereitgestellt habe , damit Sie Ihre verknüpfte Liste tatsächlich visualisieren können . Dies kann bereitgestellt werden oder nicht. Wenn dies nicht der Fall wäre, könnten Sie diese Implementierung auch verwenden. Fangen wir jetzt mit unserer allerersten Übung an. Nun werden wir für alle unsere Übungen, einschließlich dieser, dieselben drei Schritte ausführen , über die wir zuvor gesprochen haben. Auch dies ist sehr wichtig, um zu beachten und zu üben. Zuerst entwerfen wir den Algorithmus ohne Code mündlich oder visuell. Zweitens berichten wir über die Laufzeit und die Speicherkomplexität. Drittens fangen wir an zu programmieren. Hier wird eine verknüpfte Liste angehängt. Was wir tun werden, ist ein Element an die verknüpfte Liste anzuhängen. Also hier haben wir, sagen wir mal, eine verknüpfte Liste mit 1, 2 und 3. Wir wollen den Wert vier anhängen. Nachdem wir es angehängt haben, haben wir eine verknüpfte Liste, die 1, 2, 3, 4 ist. Halten Sie nun das Video hier an, um festzustellen , welchen Algorithmus Sie verwenden. Konzeptionell, wie die Schritte aussehen werden. Hier ist die Antwort. Konzeptionell werden wir wieder tun, dass wir nur einen Zeiger auf den Anfang der verknüpften Liste haben . Wir folgen diesem Zeiger genau wie hier auf der linken Seite mit diesem schwarzen Linkspfeil . Wir beginnen mit diesem Zeiger, navigieren dann zum nächsten Link und machen weiter , bis Sie das Ende unserer verknüpften Liste erreicht haben . Dann erstellen wir ganz am Ende unserer verlinkten Liste einfach einen neuen Link und hängen ihn an die Liste an. Das ist konzeptionell das, was wir tun werden. Wir gehen zum Ende der verknüpften Liste und fügen dann einen neuen Link hinzu. Wissen, dass wir jetzt wieder den zweiten Schritt in unserem dreistufigen Prozess in Betracht ziehen müssen. Wie hoch ist die Speicher- und Laufzeitkomplexität dieses Append-Algorithmus? Pausiere das Video hier und sieh nach, ob du es herausfinden kannst. Jetzt ist hier die Lösung. Dieser Algorithmus ist linear in Rechenkomplexität, einfach weil Sie die gesamte verknüpfte Liste durchlaufen müssen, die bis zur Länge N reichen kann. Das ist O of N-Rechenkomplexität. Für die Speicherkomplexität haben wir O von 1 Speicherkomplexität, einfach weil wir keinen zusätzlichen Speicher außer Speicher für das aktuelle Element und die vorhandene verknüpfte Liste verwendet Speicher für das aktuelle Element und die haben. Wir haben eigentlich keine neue Datenstruktur erstellt. Wir haben nur diesen Link , der O von 1 kostet. das wissen, lassen Sie uns jetzt den Algorithmus codieren. Halten Sie das Video hier erneut und prüfen Sie, ob Sie es codieren können. Jetzt ist hier eine Lösung. Hier gehen wir zum Ende der Liste. Hier sagen wir, während link.next nicht keiner ist. Das heißt, solange wir einen anderen Link in unserer aktuellen Liste haben , werden wir weiter durchqueren, bis wir am Ende angelangt sind. Dann endlich, ganz am Ende der Liste, also wird dieser Link jetzt nach dieser While-Schleife sein, das letzte Element, also wird es 3. Jetzt haben wir 3, wir schreiben link.next ist gleich unserem neuen Link , der einen Wert 4 enthält. das wissen, lassen Sie uns jetzt unsere Tests durchführen. Wir können jetzt sehen, dass Append in dieser Liste der Fehler fehlt , also haben wir unseren angehängten Test bestanden. Gehen wir nun zur nächsten Frage über. Bevor wir nun zur nächsten Frage übergehen, eigentlich auch für die nächste Frage, sollten Sie sich merken, wie Sie eine verknüpfte Liste durchqueren. Es wird immer fast dasselbe sein. Sie werden eine Zeitschleife haben, Sie werden überprüfen, ob Sie nicht auf ein Objekt vom Typ None zugreifen, dann greifen Sie einfach auf den nächsten Link zu. So durchquerst du so ziemlich immer eine verknüpfte Liste. Denken Sie daran, denn das nächste Problem wird etwas Ähnliches verwenden. Scrollen Sie in Ihrer Datei nach unten und Sie werden sehen, dass hier die Frage zum Einfügen ist. Unser Ziel ist es, nach dem bereitgestellten Index I einzufügen . Nehmen wir an, Sie hatten diese verlinkte Liste 1, 2, 3 und wir wollen hier nach Index 0 einen Link einfügen und wir möchten den Wert 4 einfügen. Hier fügen wir nach Index 0 den Wert 4 ein. Hier haben wir 1, 4, 2, 3. Wenn Sie auf die linke Seite schauen, haben wir uns vorgestellt, wie das aussehen wird , um Ihnen ein wenig zu helfen. [GELÄCHTER] Machen Sie weiter und halten Sie das Video hier an und prüfen Sie, ob Sie feststellen können der Algorithmus aussehen würde. Nun, hier ist die Antwort. Was wir tun werden, ist zuerst die Liste zu durchqueren bis wir den richtigen Link zum Ändern finden. Nehmen wir in diesem Fall an, wir fügen H nach 9 ein, genauso wie wir 4 nach 1 einfügen. Hier navigieren wir zu 9. Sobald wir den Wert 9 erreicht haben , erstellen wir einen neuen Link. Wir verknüpfen 9 mit 8 neu und dann werden wir 8 mit 2 neu verknüpfen. Es gibt drei Schritte. Navigieren Sie zum richtigen Element, den aktuellen Link erneut, und verwenden Sie dann den aktuellen Link, um eine Verbindung mit dem nächsten Link herzustellen. Das ist der Algorithmus. Nun, da Sie wissen, dass dies der Algorithmus ist, pausieren Sie das Video hier und finden Sie heraus, wie hoch die Laufzeit und Speicherkomplexität ist. Die Speicher- und Laufzeitkomplexität hier. Rechenkomplexität wird linear sein. Möglicherweise müssen wir bis zum Ende der Liste gehen, um etwas einzufügen. Speicherkomplexität ist konstant, weil das einzige, was wir erstellen , diese Verbindung selbst ist, was O von 1 konstanten Speicherkosten entspricht. wir das wissen, gehen wir nun zum Code über. Pausiere das Video hier und versuche zu programmieren. Jetzt ist hier eine Lösung. Genau wie zuvor beginnen wir damit, tatsächlich mit einer While-Schleife zu navigieren. Anstatt eine While-Schleife zu verwenden, verwenden wir eine for-Schleife, weil wir genau wissen , wie viele Schritte Sie ausführen werden. Für Link in Bereich i ist link gleich link.next. Ich habe hier einen Unterstrich verwendet, um zu kennzeichnen, dass wir diese Variable nicht verwenden werden . Alles was uns wichtig ist, dass wir im Grunde genommen mal auf.next.next zugreifen.next bis Sie zum richtigen Element kommen. Sobald Sie dort sind, weisen wir ein paar Dinge neu zu. Wir weisen zunächst das nächste Attribut des aktuellen Werts dem neuen Wert neu zu. Aber dann müssen wir diesen neuen Link auch mit dem Rest der verknüpften Liste verbinden . Wir werden dies verwenden, um dem ursprünglichen Link.next zu verbinden. Auch in diesem Beispiel auf der linken Seite zeigte 9 ursprünglich auf 2. Wir gehen jetzt zu Punkt 9 bis 8. Das ist genau hier. Link.next ist gleich unserem neuen Objekt. Dann gehen wir zu Punkt 8 bis 2, und das ist genau hier im zweiten Teil. Wir haben den ursprünglichen link.next und wir ersetzen ihn durch den neuen link.next. Das war's. Dies ist unsere Insert-Funktion. Fahren Sie fort und führen Sie Ihre Tests erneut aus , indem Sie oben auf die grüne Schaltfläche „Ausführen“ klicken. Jetzt können wir sehen, dass es einen Fehler weniger gibt. Wir haben nicht in unsere Liste der Fehler eingefügt, wir haben die Tests erfolgreich bestanden. Gehen wir zum nächsten Problem über. Hier ist ein Tipp, den wir für dieses und für zukünftige Probleme hatten , immer auf keine Werte zu überprüfen. Ein häufiger Fehler , den Sie bei Problemen mit verknüpften Listen erhalten, ist, dass Sie beispielsweise in diesem Link erhalten. Als nächstes erhalten Sie einen Link, der ein NoneType-Objekt ist. Next ist kein Attribut eines NoneType-Objekts. Das ist sehr verbreitet. Sie beim Debuggen und noch vor dem Debuggen, noch bevor Sie den Code ausführen während Sie mit dem Interviewer sprechen , sicher, dass Sie Ihren Code durchgehen und nach Orten suchen, an denen Es gibt möglicherweise keine Werte und stellen Sie sicher, dass Sie nicht.next oder.value für einen NoneType-Wert aufrufen. In dieser speziellen Funktion hier in Zeile 83 und 84 ist es durchaus möglich , dass ich ungültig war. Nehmen wir an, es waren 100. Dann würden wir irgendwann den NoneType-Wert erreichen und wir hatten versucht, den nächsten aufzurufen. Um diese Funktion robuster zu machen, müssten Sie das überprüfen. müsstest du berücksichtigen. In dieser besonders einfachen Implementierung haben wir das nicht getan, sondern nur etwas, das Sie immer im Hinterkopf behalten Überprüfen Sie in Ihren Interviews und jedem Gedicht, das Sie schreiben, immer auf keine Werte . Gehen wir nun zum nächsten Problem über. Unser Ziel ist es nun, einen Stack mithilfe einer verknüpften Liste zu implementieren. Hier haben wir eine verknüpfte Liste, wir haben eins, zwei. Unser Ziel ist es, dies so behandeln zu können , als wäre es ein Stack. Wenn wir darauf Dot Pop nennen, würde es uns den letzten Punkt geben, nämlich zwei. Wenn wir Push aufrufen, drücken wir hier 2, drücken 3, und wenn wir knallen, erhalten wir den letzten Gegenstand zurück, den wir geschoben haben, nämlich drei. Wenn du wieder auftauchst, bekommst du den vorletzten Gegenstand, den du drückst, nämlich zwei. Unser Ziel ist es, einen Stack zu implementieren , aber eine verknüpfte Liste im Backend zu verwenden. Halten Sie das Video hier an und sehen ob Sie herausfinden können, ob der Algorithmus, in diesem Fall der Algorithmus, lautet, wie werden Sie pushen und wie werden Sie platzen? Pausiere das Video hier. Hier ist die Antwort. Konzeptionell gesehen ist dies dem Anfang ziemlich ähnlich. Wenn wir hier pushen sagen, werden wir so ziemlich die Append-Funktion schreiben , über die wir zuvor gesprochen haben. Können wir sagen Pop hier, wir schreiben eine neue Funktion, löschen. Wir haben noch keine Löschfunktion geschrieben, aber sie wird so ziemlich wie jede andere Funktion sein , die aus einer Linkliste löscht. Jetzt, da wir den Algorithmus kennen, pausieren Sie das Video hier und prüfen Sie, ob Sie die Rechen- und Speicherkomplexität herausfinden können . Die Lösung für diesen Teil, Laufzeit und Speicherkomplexität wird tatsächlich die Laufzeitkomplexität linear sein. Wir gehen bis zum Ende der verknüpften Liste und fügen sie dann hinzu. Im Pops-Fall gehen wir bis zum Ende der verknüpften Liste, um sie daraus zu entfernen. Nun, da man das weiß, ist die Speicherkomplexität konstant. Die Speicherkomplexität, alles, was Sie tun, ist eine neue Verbindung herzustellen , und das ist O von einem Speicherpreis. Du erstellst keine ganz neue Liste. Sie erstellen keine weitere Datenstruktur , in der Elemente gespeichert werden. Du erstellst nur einen Link. Die Speicherkosten sind O von eins. Pausiere jetzt das Video hier und versuche es zu codieren. Schreiben wir jetzt den Code auf. Also hier werden wir Push so ziemlich wie das zuvor neu implementierte Append implementieren . Wir definieren zuerst einen lokalen Variablenlink, unsere aktuelle verknüpfte Liste ist , und dann, während link.next nicht keiner ist. Während wir also etwas haben, das verlinkt ist, lasst uns sie durchgehen. [LÄRM] Dann werden wir ganz am Ende einen neuen Link hinzufügen. Das ist so ziemlich alles für die Push-Funktion-Push-Methode. Gehen wir jetzt zum nächsten über. Um etwas zu entfernen, setzen wir diesen lokalen Variablenlink erneut diesen lokalen Variablenlink auf unseren Anfang mit der verknüpften Liste. Dann, während wir eins vom Ende entfernt sind, also nicht wirklich den allerletzten Link wollen, wollen wir den vorletzten Link , weil das unser neuer Schwanz sein wird . Wir werden wieder durchqueren. Dann werden wir an dieser Stelle zuerst den Wert des allerletzten Artikels bevor wir ihn tatsächlich entfernen. Hier wollten wir den Wert des allerletzten Links ermitteln. Als nächstes werden wir es trennen. Jetzt deutet unser vorletzter Link auf nichts hin. Dies ist jetzt unser brandneuer letzter Link, und dann geben wir endlich den Wert zurück. So löschen wir tatsächlich ganz am Ende der verlinkten Liste. Lassen Sie uns weitermachen und auf die grüne Schaltfläche ganz oben klicken, um unsere Datei auszuführen und unsere Tests zu überprüfen . Da haben wir's. Wir können jetzt sehen, dass unser Stack via Linked List tatsächlich weg ist. Es gibt keine Fehler mehr in unserer Liste, daher haben wir diese Tests bestanden. Gehen wir zum nächsten Problem über. Bevor ich auf dieses Problem eingehe, haben wir noch einen Tipp. Sie möchten ganz einfach eine Ihrer eigenen Fragen stellen einfach eine dieser Fragen mischen und abgleichen. Sie haben über drei verschiedene Datenstrukturen, verknüpfte Listen, Stacks und Warteschlangen gesprochen . Um eine neue Frage zu erstellen, implementieren Sie einfach, wählen Sie eine aus und wählen Sie dann eine andere aus. Jede dieser Kombinationen gibt Ihnen ein brandneues Problem , das Sie ausprobieren können. Solange Sie dieselben Tests bestehen können , die Sie für einen Stack geschrieben haben , außer Ihre brandneue Datenstruktur zu verwenden , können Sie loslegen. Das gilt für alles andere. Wenn Sie Tests für die Warteschlange haben, stellen Sie einfach sicher, dass Ihre brandneue Datenstruktur diese Tests bestehen kann, und dann wurden Tests verlinkt Liste usw. Kombinieren Sie jede dieser Kombinationen um ein brandneues Problem zu erhalten. Lassen Sie uns nun die Zyklusentfernung für eine verknüpfte Liste implementieren. Suchen und entfernen Sie insbesondere einen Zyklus in einer verknüpften Liste, falls dieser vorhanden ist. Im Moment können Sie der Einfachheit halber davon ausgehen, dass alle Linkwerte unterschiedlich sind. Dies ist sehr wichtig da die erste Frage, die Sie berücksichtigen werden, wenn Sie den Zyklus Suchen und Entfernen sehen , lautet: Wie werden Sie einen Zyklus erkennen? Eine Möglichkeit, einen Zyklus zu erkennen, wäre, wenn ich diesen Wert schon einmal gesehen habe , dann weiß ich, dass ich einen Zyklus erreicht habe. Ich schätze, um es klarzustellen. Angenommen, Sie bewegen sich entlang dieser verknüpften Liste von links nach rechts. Wenn Sie im weiteren Verlauf plötzlich zweimal drei sehen, wissen Sie, dass Sie einen Zyklus erreicht haben. Aber das können Sie nur sagen, wenn Sie wissen, dass alle Linkwerte unterschiedlich sind. diesem Grund ist die Unterscheidung von Linkwerten eine sehr wichtige Frage für dieses Problem. Ich schrieb, dass hier alle Werte einzigartig sind. Das wird eine Ihrer ersten Fragen an Ihren Interviewer sein. Ihre zweite Frage, und das ist eher eine fortgeschrittene Frage. Sie möchten fragen, gibt es eine maximale Zykluslänge? Das hat mit der Implementierung zu tun, denn wenn Sie alle Werte verfolgen möchten , die Sie bisher gesehen haben, und dann sagen möchten: „In Ordnung, habe ich diesen Wert schon gesehen?“ Dann könnten Sie möglicherweise nie einen Zyklus sehen. Diese gesamte verknüpfte Liste könnte eine Million lang sein und dass es nirgendwo einen Zyklus gibt, dann haben Sie es im Grunde genommen in diesem riesigen Laden mit einer Million Artikeln aufbewahrt , um zu sehen, ob eine dieser Millionen Artikel werden wieder angezeigt. Dies ist eine gute Frage , ob es eine maximale Zykluslänge gibt? Denn auf diese Weise müssten Sie nicht alle Millionen Artikel behalten , wenn Sie wissen, dass die maximale Zykluslänge nur fünf beträgt. Dies sind gute Fragen, die Sie bei diesem Problem beachten sollten. Während Sie diese Probleme weiterentwickeln und üben, werden Sie verstehen, welche Fragen Sie stellen müssen. Wenn Sie also vorerst denken, dass dies aus heiterem Himmel verrückte Fragen sind , können Sie niemals darüber nachdenken, das ist okay. So wie Sie immer mehr davon sehen, werden Sie sich mit den Fragen vertraut machen , die Sie stellen müssen. Lassen Sie uns vorerst sehen, ob Sie sich einen Algorithmus vorstellen können. Ich habe Teile davon weggegeben aber pausiere das Video hier und sehe, ob du eine Möglichkeit finden kannst , Zyklen zu entfernen , und hier ist die Antwort. Im Grunde werden Sie eine Sammlung aller Werte führen , die Sie bereits gesehen haben. Wenn Sie den Wert bereits gesehen haben, wissen Sie, dass dies ein Zyklus ist und Sie können den Zyklus unterbrechen indem Sie link.next einfach entsprechend neu zuweisen. Wenn man das weiß, ist das jetzt der Algorithmus. Wie hoch ist die Laufzeit und Speicherkomplexität? Pausiere das Video hier. Für die Speicher- und Laufzeitkomplexität , insbesondere für die Rechenkomplexität haben Sie O von N. Wir werden die gesamte Liste nacheinander durchgehen . Wir müssen das tun, um herauszufinden, ob es einen Zyklus gibt oder nicht. Die zweite Frage ist, was bedeutet die Speicherkomplexität? Aus Gründen der Speicherkomplexität werden wir erneut eine Sammlung all der verschiedenen Werte führen , die wir bisher gesehen haben. Das ist eine O of N-Speicherkomplexität. O von N Speicherkomplexität und O von N Rechenkomplexität. das wissen, versuchen wir jetzt, es zu codieren. Pausiere das Video hier. Lassen Sie uns jetzt weitermachen und das programmieren. Hier beginnen wir mit Schaffung einer Reihe von Werten. Wenn Sie dies also noch nicht gesehen haben, ist ein Set eine Datenstruktur , mit der Sie in konstanter Zeit nach der Mitgliedschaft suchen können . Das heißt, Sie können so etwas in einer Szene machen, und das, egal wie lang Ihr Set ist, wird immer in konstanter Zeit ausgeführt. Das ist ein Set. Wir werden das Set verwenden, um alle Werte zu verfolgen , die Sie bisher gesehen haben. Es gibt zwar noch etwas anderes in unserer verknüpften Liste, wir werden überprüfen, ob der nächste Wert in unserer Szenenwerteliste steht. Wenn ja, brechen wir die Verbindung mit dem nächsten Link ab. Nochmals, wenn der nächste Link in unserer Liste ist, habe ich Werte gesehen, wir werden diesen Link brechen und wir werden hier enden. Wir gehen davon aus, dass es in unserer Liste nur einen Zyklus gibt. Auch das wäre eine gute Frage, die Sie Ihrem Interviewer stellen sollten. Wird es mehr als einen Zyklus geben? In dieser speziellen Frage werden wir so tun, als gäbe es nur einen Zyklus. seen.add und dann haben wir den Wert genau hier verknüpft. Was wir hier tun, ist, dass wir sagen, in Ordnung, wir haben darüber nachgedacht, dass es hier keinen Zyklus gibt. Da es keinen Zyklus gibt, fügen wir den aktuellen Linkwert zu unserer Liste der Sehwerte hinzu. Schließlich ist dies der Standard für eine verknüpfte Listendurchquerung Wir werden nur auf den nächsten Punkt zugreifen und es wird weitergehen. Das war's. Lassen Sie uns diese Funktion ausführen und sicherstellen, dass unsere Tests erfolgreich sind. Los geht's, wir können sehen, dass der Entfernungszyklus nicht mehr in unserer Liste der fehlgeschlagenen Funktionen enthalten ist. Gehen wir zur nächsten Frage über. Für die nächste Frage, und auch für diese, ist ein Tipp, immer Ihre verknüpften Listen zu zeichnen und nach defekten Links zu suchen. Was ich mit Zeichnen meine sind im Grunde die Illustrationen , die ich zuvor hier hatte. Wenn es eine Mischung sein muss, ziehen Sie diese heraus, um zu verstehen, ob Sie alle auf die richtige Weise verknüpfen. Ich bin ziemlich schnell durch einige dieser Fragen gegangen , weil ich die Lösung bereits geschrieben habe und die Lösungen kenne. Aber wenn Sie ein brandneues Problem haben, ist die Wahrscheinlichkeit, dass Sie einen falschen Link erstellen, im Grunde sehr hoch. Das ist völlig in Ordnung, solange du es herausziehst. Wenn Sie es herausziehen, können Sie die richtigen Links für die Verbindung ausarbeiten . Das ist einer meiner Tipps hier. Zeichne deine verknüpfte Liste und suche nach defekten Links. Nun, zu diesem speziellen Problem werden wir es nur konzeptionell diskutieren. Ich habe tatsächlich eine voll funktionsfähige Lösung im Code für Sie geschrieben , aber weil sie ziemlich komplex ist, werden wir nur algorithmisch darüber sprechen. Für diese Frage ist es unser Ziel, ein neues Dateiobjekt zu verwenden oder zu erstellen indem wir die Datenstrukturen verwenden , die wir bereits gesehen haben. Unser Dateiobjekt wird mit anderen Dateien zusammengeführt. [GELÄCHTER] Ich habe Ihnen gerade einen Teil der Beschreibung der Lösung gezeigt . Ihr Ziel ist es jedoch, ein Dateiobjekt zu erstellen , das offiziell mit anderen Dateien zusammengeführt werden kann. Die Idee ist, dass es viele dieser Dateien gibt. Das bedeutet, dass Sie nicht alle Daten in eine riesige Datei kopieren möchten . Wenn Sie das ganze Kopieren machen, wird es sehr lange dauern. Wie können wir eine Reihe von Dateien kombinieren, ohne sie zu kopieren? Wenn Sie den Hinweis nicht möchten, pausieren Sie das Video hier. Aber ein Hinweis, den ich für Sie habe, ist, dass wir nicht kopieren wollen. Anstatt zu kopieren, speichern wir einen Zeiger auf die nächste Datei. Wir haben Datei 1 und dann sagen wir, aus Datei 1 wissen wir, was die nächste Datei ist Datei 2. Alles was Sie tun werden, ist eine Verbindung oder einen Zeiger zu speichern . Das sollte furchtbar nach einer der Datenstrukturen klingen , die wir gerade behandelt haben und die ich verwendet habe. Halten Sie nun das Video hier an und prüfen Sie, ob Sie den Algorithmus herausfinden können. Jetzt ist hier die Lösung. Im Grunde verwenden wir eine verlinkte Liste. Jede einzelne Datei wird ein Link sein. Jede Datei zeigt dann auf die nächste Datei und sagt, dass dies der nächste zu durchquerende Dateiwert ist. So werden Sie diese riesige zusammengeführte Datei speichern . Aber das ist noch nicht alles, es gibt noch ein bisschen mehr , weil wir diese Zusammenführungsfunktion unterstützen müssen. Zu wissen, wie wir sie lagern, ist großartig. Wir müssen keine Kopien anfertigen, aber wie führt man eigentlich eine neue Datei zusammen? Nun, die neue Datei, genau wie Sie sie zu jeder anderen verknüpften Liste hinzufügen würden. Sie gehen so ziemlich bis zum Ende der verknüpften Liste und fügen die neue Datei hinzu. Das ist so ziemlich alles. Für dieses Problem oder diese Übung die einzige konzeptionelle Schwierigkeit darin , zu erkennen, dass Sie eine verknüpfte Liste verwenden müssen. Das war wirklich das Zeigerproblem. Jetzt, da wir das Zeigerproblem behandelt haben, werden wir weitermachen, nur weil ich dafür eine schriftliche Lösung habe und es zeilenweise Anmerkungen für das gibt , was jeder ist und wie es funktioniert aber die Details sind nicht besonders wichtig. Gehen wir zur nächsten Frage über. Nochmals, ist kein anderes Schiff hier. Verknüpfte Listen, die wir für Sammlungen dynamischer Größe verwenden. In diesem speziellen Fall ist dieser Satz von Dateien ein Satz von Sammlungen mit dynamischer Größe. Wir wissen nicht, wie groß es ist, wir könnten es ein paar Mal ergänzen und wir könnten es sogar ein paar Mal entfernen. Verknüpfte Listen eignen sich hervorragend für diese Sammlungen, da Sie keine Daten kopieren müssen, wenn Sie ein Objekt hinzufügen oder entfernen möchten. Immer wenn Sie einen Link hinzufügen oder entfernen möchten , ist es konstante Zeit. Jetzt eignen sich Arrays jedoch perfekt für den Direktzugriff. Wir haben schon ein wenig darüber gesprochen , aber im Grunde bedeutet das , dass Sie genau wissen, wo es sich im Speicher befindet, wenn Sie auf das ith-Element in einem Array zugreifen möchten . Du gehst direkt zu diesem Teil in der Erinnerung. Wenn Sie jedoch für eine verknüpfte Liste das i-te Element so haben möchten , wie Sie es zuvor gesehen haben, müssen Sie die gesamte Liste durchqueren, also nicht ganz so effizient. Das ist der Tipp. Verknüpfte Listen sind für Sammlungen mit dynamischer Größe vorgesehen, Arrays sind für den Direktzugriff vorgesehen. Für unsere letzte Frage hier ist es Ihr Ziel, Datenstrukturen zu kombinieren, um ein Palindrom in einer verknüpften Liste zu finden. Ein Palindrom ist ein Wort, das genauso rückwärts gezüchtet wird wie Sportarten wie Rennwagen. Wenn du den Rennwagen umdrehst, bekommst du immer noch einen Rennwagen. Hier wird Ihr Wort als verknüpfte Liste dargestellt. Hier haben wir einen Link , der r enthält, und der auf den nächsten Link zeigt, der a enthält, auf den nächsten Link zeigt, der c hat, und dann auf e zeigt, auf c zeigt, auf a zeigt, so weiter und so fort. Unsere verlinkte Liste steht für das Wort Rennwagen. Wir wollen überprüfen, ob es sich bei dieser verlinkten Liste um ein Palindrom handelt. Überlegen Sie, welche anderen Datenstrukturen Sie verwenden können, um in dieser verknüpften Liste ebenfalls ein Palindrom zu sehen. Pausiere das Video hier und sieh nach, ob du es herausfinden kannst. Die konzeptionelle Lösung für diese ziemlich herausfordernde Aufgabe ist dass Sie sowohl einen Stack als auch eine Warteschlange wollen. Während Sie diese verknüpfte Liste durchlaufen, fügen Sie sie sowohl dem Stapel als auch der Warteschlange hinzu. Wenn Sie dann mit der Wartung der gesamten verknüpften Liste fertig sind, einfach die Schrägstrich-Dekeue, entfernen Sie sie einfach aus jeder Stapelwarteschlange und überprüfen Sie dann , ob jede dieser Listen gleich ist. Das ist großartig, weil der Stapel vom Ende entfernt wird, die Warteschlange vom Anfang sodass Sie sich im Grunde genommen rückwärts und vorwärts gleichzeitig entlang der Saite bewegen rückwärts und vorwärts gleichzeitig entlang der Saite gleichzeitig entlang der aus dem Stapel und der Warteschlange entfernen. Das ist perfekt für uns, denn in einem Palindrom besteht eine Möglichkeit, zu überprüfen, ob es sich um ein Palindrom handelt, darin, zu überprüfen ob der erste Charakter ist, dass Sie der letzte sind. Beim zweiten geht man zum vorletzten und so weiter und so fort. aus dem Stapel und der Warteschlange entfernen, erhalten Sie das. Lass uns gleich hier innehalten. Mal sehen, das ist der Algorithmus. Wie hoch ist die Laufzeit und Speicherkomplexität? Hier ist die Antwort. Sowohl für die Laufzeit als auch für Speicherkomplexität haben wir lineare Komplexität. Denn die Speicherkomplexität ist linear, weil wir einen Stack und eine Warteschlange speichern, die jeweils bis zur Länge n haben könnten, und wir haben auch lineare Rechenkomplexität, weil wir iterieren über jeden dieser Buchstaben. Tatsächlich wiederholen wir zweimal, aber es spielt keine Rolle, es ist o of n. Jetzt wir den Algorithmus und die Komplexität kennen, versuchen wir , ihn zu codieren. Pausiere das Video hier und versuche es zu codieren. Lassen Sie uns nun die Lösung behandeln. Wir beginnen damit, sowohl den Stack als auch eine Warteschlange zu initialisieren . Nachdem wir diese beiden Datenstrukturen haben, wir die verknüpfte Liste durch, wie gesagt, und für jeden einzelnen Link werden wir sie zu diesen beiden Datenstrukturen hinzufügen. Hier werde ich den Stack hinzufügen. Ich werde den Stapel pushen und dann werde ich mich auch in die Warteschlange stellen. Dann gehe ich zum nächsten Link. Wie ich bereits sagte, ist dieser Traversal-Code so ziemlich immer derselbe. Nachdem wir das abgeschlossen haben, gehen wir nun diese beiden Datenstrukturen, den Stack und die Warteschlange, durch und stellen sicher , dass sie für jedes einzelne Element, das wir entfernen, genau gleich sind. Während sich etwas im Stapel befindet, gehen vom Stapel und entfernen Sie die Warteschlange aus der Warteschlange. Solange sie übereinstimmen, können wir loslegen. Wenn sie zu keinem Zeitpunkt übereinstimmen, geben wir false zurück. Wenn nichts falsch zurückgibt, alle überein und wir können true zurückgeben. Das war's. Lassen Sie uns jetzt weitermachen und das bewerten. ganz oben auf die grüne Schaltfläche Ausführen und überprüfen Sie, ob alle Ihre Tests bestanden haben. Hier können wir sehen, dass unsere Funktion Palindrome Link ist nirgendwo in der Liste der Fehler zu finden. Wir haben diesen Test erfolgreich bestanden. Diese drei verbleibenden Funktionen sind Bonusfunktionen. Ich habe auch vollständig ausgearbeitete Lösungen für sie und Erklärungen in den Code aufgenommen , sodass Sie diese auch in Ihrer Freizeit ausprobieren können. Wenn Sie Fragen haben, können Sie diese gerne im Diskussionsbereich posten. Das war's für diese Lektion. Gehen Sie weiter zu den Folien, für den vollständig ausgearbeiteten Code und Lösungsart, um die Kurswebsite zu besuchen. Das war's für Listen, sowohl Arrays als auch verknüpfte Listen. 9. Karten: Hashmaps: In dieser Lektion stellen wir eine Datenstruktur für Mappings vor. Ein Mapping ist das, wonach es sich anhört, ein Mapping von einem Element zum anderen. Hier ordnen wir Katze der Nummer fünf zu. Nehmen wir in diesem Fall an, wir kartieren das Tier dem Alter. Tier ist das, was wir den Schlüssel in der Kartierung nennen, und Alter ist das, was wir den Wert in der Kartierung nennen. Wir können auch zusätzliche Schlüsselwertpaare im Mapping haben. Beachten Sie, dass diese Zuordnung keinen Ordnungssinn hat, jedes Schlüsselwertpaar ist vollständig voneinander getrennt. Die Datenstruktur, die dieses Mapping speichert , wird HashMap genannt. Wir erklären, wie eine HashMap funktioniert, diskutieren ihre Vor- und Nachteile und besprechen dann kurz ein Bonuskonzept. Deshalb wird diese Datenstruktur als Hash-Map bezeichnet. Im Kern verwendet eine HashMap eine Funktion, als Hash-Funktion bezeichnet wird. Diese Hash-Funktion nimmt einen Wert mit beliebiger Länge auf, wie cat, und transformiert diese Eingabe dann , um eine Ausgabe mit fester Größe wie zwei zu erzeugen. Diese Hash-Funktion ist wichtig, und so wie diese Ausgabe auch, schauen wir uns an, wie beide verwendet werden. Angenommen, wir wollen einige Schlüssel und Werte in eine Hash-Tabelle einfügen . Auf der rechten Seite haben wir eine Hash-Tabelle, in der Null die Speicheradresse des ersten Steckplatzes ist, eine die Speicheradresse des zweiten Steckplatzes und so weiter und so fort. Wir wollen die Schlüsselkatze dem Wert fünf zuordnen, dazu hashen wir den Wert cat. Dies gibt uns die Speicheradresse 2, also in Position zwei den Wert fünf setzen. Wir wiederholen dies für weitere Daten, um Hund auf drei zuzuordnen, Hash Dog. Dies gibt uns die Speicheradresse 3, also platzieren Sie in Position drei den Wert drei. Wir wiederholen dies noch einmal, um Schwein auf vier, Hash Pig, zuzuordnen. Dies gibt uns die Speicheradresse Null, also in der Position Null den Wert vier setzen. Dieser Prozess benötigt nur eine Konstante oder O von einem Mal für jedes Schlüsselwertpaar, das wir einfügen. Um aus der HashMap zu löschen, wir ähnliche Schritte durch. Um cat aus der HashMap zu löschen, zuerst hash cat, um zwei zu erhalten, dann an der Position zwei den Wert entfernen. Diese Operation ist ebenfalls konstant oder O von einer Zeit. Zusammenfassend lässt sich sagen, dass das Einfügen und Löschen für eine HashMap beide O einer Zeit sind. Sehr schnell. Nehmen wir an, wir wollen jetzt die HashMap durchsuchen, deren Wert cat entspricht. Fast derselbe Vorgang, hashen Sie die Katze, um ihre Speicheradresse zu erhalten, die zwei ist. Sie dann an Position zwei Nehmen Sie dann an Position zwei den Wert fünf und geben Sie ihn zurück. Als Ergebnis sagen wir, dass die Suche nach einer HashMap die Konstante O benötigt, wiederum O von einmal. Zusammenfassend lässt sich sagen, dass das Durchsuchen einer HashMap Konstante O von einer Zeit benötigt. Bei einer HashMap sind Suchen und Abrufen gleich, daher überspringen wir die separate Analyse des Abrufs. Lassen Sie uns nun die Vor- und Nachteile dieser HashMaps verstehen. Zusammenfassend haben HashMaps einige sehr beeindruckende Laufzeiten, konstante Zeit, Änderung und Zugriff. Aufgrund der Funktionsweise von HashMaps sind sie ideal für alle Daten mit eindeutigen Kennungen. Per Definition sind HashMaps nicht für Auftragsdaten ausgelegt. Wie bereits erwähnt, jedes dieser Schlüsselwertpaare vollständig getrennt. Es gibt keinen Sinn für Ordnung, es gibt keine Beziehung zwischen getrennten Schlüsselwertpaaren. Wir haben bereits die ersten beiden Abschnitte besprochen, was eine HashMap ist und wofür sie ideal ist. Wir werden den Bonusbereich besprechen nachdem wir den Kerninhalt abgeschlossen haben. Eine Kopie dieser Folien und weiterer Ressourcen finden Sie auf der Kurswebsite. Damit ist unsere Kerndiskussion zu HashMaps abgeschlossen. Wenn Sie mit diesem Detaillierungsgrad zufrieden sind, können Sie mit der kommenden Lektion mit HashMap-Praxis fortfahren . Ansonsten besprechen wir jetzt den Bonus-Abschnitt. Insbesondere haben wir einen Vorbehalt für HashMaps besprochen , der Hash-Kollisionen genannt wird. Wir haben zuvor gesagt , dass HashMaps eine konstante Zeit für Änderungen und Zugriff sind . Dies ist jedoch nicht immer der Fall. Laufzeit hängt tatsächlich davon ab, wie eine HashMap implementiert wurde und hier ist der Grund dafür. Manchmal tritt dieses Phänomen auf, wenn es zuvor erwähnt wurde, eine Hash-Kollision. Angenommen, wir möchten zwei Schlüsselwertpaare einfügen, Katzen-Maps für fünf und Hundekarten für drei. Nach wie vor hashen wir Katze, um zwei zu bekommen. Auf Position zwei setzen wir dann den Wert fünf, das ist wie zuvor. Um nun unser zweites Schlüsselwertpaar einzufügen, haben wir einen Hash-Dog, um zwei zu erhalten, an Position zwei haben wir jetzt ein Problem. Wir können die vorhandenen Daten nicht überschreiben, daher haben wir zwei Möglichkeiten. Die erste Option heißt offene Adressierung. Bei der offenen Adressierung finden wir einfach einen anderen Slot in der HashMap Wenn dieser nächste Slot leer ist, platzieren Sie Ihren neuen Wert dort. Eine Möglichkeit, einen leeren Slot zu finden, besteht darin, einfach die Slots zu durchqueren, bis Sie einen leeren finden, und dort Ihren Wert zu platzieren. Es gibt auch Strategien für die Slot-Suche, die wir hier nicht eingehen werden. Eine andere völlig andere Technik zur Behandlung von Hash-Kollisionen heißt Chaining. Bei dieser Technik behandeln wir jeden Slot als eine eigene Datenstruktur. Zum Beispiel könnten wir jeden Slot als verknüpfte Liste behandeln Um dann in Position zwei einzufügen, fügen wir einfach drei in die verknüpfte Liste ein. Wir können jeden Slot auch als HashMap behandeln, jede Datenstruktur, die wir machen, jede mit ihren eigenen Kompromissen erneut. Zusammenfassend haben wir eine Hash-Kollision definiert und auch zwei Möglichkeiten gesehen, mit einer Hash-Kollision umzugehen. Offene Adressierung oder wir finden andere leere Slots zur Verwendung oder Verkettung, bei denen wir jeden Slot als seine eigene Datenstruktur behandeln . Damit ist unser Bonusabschnitt hier über Hash-Kollisionen Lesen Sie unbedingt unsere nächste Lektion zum Üben mit HashMaps. 10. Mappings: Willkommen bei der Praxis der Mappings-Datenstruktur. Auch hier werden wir weitere Übungen üben und einige Interviewtipps zur Mapping-Datenstruktur durchgehen . Insbesondere werden diese Datenstrukturen auch Hashmaps genannt. Also werden wir diese Namen oft verwenden, Hashmaps. Navigiere wie zuvor zu dieser URL alvinwan.com/datastructures101/mappings. Das bringt dich zu repl.it. Ich weiß, dass wir jetzt mehrmals über diese Anweisungen gesprochen haben . Ich werde es noch einmal durchgehen, nur für den Fall, dass Sie, sobald Sie diese Seite sehen, das Projekt forken, um Ihre eigene bearbeitbare Kopie zu erhalten. Klicken Sie oben links auf den Projektnamen , um ein Dropdown aufzurufen. Klicken Sie auf die Ellipse, um ein weiteres Dropdown zu erhalten, und klicken Sie dann auf die Schaltfläche „Fork“. Dann schlage ich vor, Ihren Skillshare und die repl.it-Fenster nebeneinander zu platzieren , wie hier gezeigt. Sie sollten dann etwas sehen, das so aussieht, wie Sie es auf der rechten Seite haben. Lassen Sie uns jetzt über die Arten von Übungen sprechen , die Sie sehen werden. Sie haben die Übungen zur Implementierung, Verwendung und Kombination. Wenn Sie nach unten scrollen, sehen Sie, dass unsere allererste Übung hier darin besteht, einen Stack zu implementieren. Wir werden das in drei Schritten machen, genau wie wir es in allen anderen Lektionen getan haben. Wir werden zuerst den Algorithmus ohne Code entwerfen . Wir werden die Laufzeit und Speicherkomplexität melden. Dann werden wir den Algorithmus tatsächlich codieren. Wenden wir diese drei Schritte auf den allerersten Algorithmus an, oder unsere erste Übung hier. Implementieren Sie einen Stack, der alle Werte im Stack auf nur drei Vorkommen begrenzt . Wenn ein Wert verschoben wird , der mehr als drei Vorkommen aufweist, ignorieren Sie diesen Wert einfach. In diesem speziellen Fall haben wir hier ein Beispiel. Wir initialisieren zuerst einen Stack und dann drücken wir den Wert ein,10 mal. Allerdings werden nur die ersten drei Werteinheiten tatsächlich zum Stack hinzugefügt. Alles andere wird ignoriert. Sie können dann andere Werte als einen pushen und es wird normal zum Stack hinzugefügt. Mach weiter und probiere das aus. Entwerfen Sie zunächst den Algorithmus , der diesen Stack tatsächlich mit einem Limit implementiert. Pausieren Sie das Video hier und versuchen Sie, einen Algorithmus herauszufinden. Hier ist die algorithmische Lösung. Was wir tun werden , ist zuerst die Baseline-Stack-Implementierung zu verwenden. Sie können hier also sehen, dass meine Capped Stack-Klasse tatsächlich von der Basis-Stack-Klasse erbt. diesem Wissen werden wir Beschränkungen auferlegen, wann Sie tatsächlich auf einen Stack pushen können , und das war's. Dazu behalten wir eine HashMap ordnen alle Werte ihren Zählungen im Stapel zu. Jedes Mal, wenn Sie pushen, erhöhen Sie diesen Wert. Wenn dieser Wert das Limit überschreitet, ignorieren wir den Wert einfach und Pop wird dann sicherstellen, und Pop wird dann sicherstellen dass diese Anzahl verringert wird. In Push überprüfen Sie die Anzahl, fügen sie hinzu, wenn die Zählung in Ordnung ist, und erhöhen dann die Anzahl. Im Pop dekrementierst du die Anzahl. Das ist also der Algorithmus. Lassen Sie uns nun die Laufzeit und die Speicherkomplexität diskutieren. Pausieren Sie das Video hier und sehen Sie, ob Sie es herausfinden können. Für die Laufzeitkomplexität ist es unabhängig von der Komplexität des ursprünglichen Algorithmus. In diesem speziellen Fall ist Push linear und Pop ist die Art, wie wir es implementiert haben, auch linear. Sowohl Push als auch Pop sind also lineare Operationen. Nun zur eigentlichen Speicherkomplexität, also wie sieht das aus? Nun, wir müssen hier einen neuen Wert schaffen. Es ist nicht genau o von n. Wir haben für jeden neuen Wert einen Wert zur HashMap hinzugefügt, aber das sind nicht so viele wie die Anzahl der Gesamtwerte, die wir hinzugefügt haben. Wir haben nur die Anzahl der eindeutigen Werte in unserer HashMap. Wir müssen einen neuen Brief erfinden. Anstelle von o von n sagen wir zum Beispiel, dass es o von k ist, und wir definieren k als die Anzahl der eindeutigen Werte, die dem Stack hinzugefügt wurden. Wenn du das tatsächlich in einem Interview hättest, würdest du so etwas sagen. Wir müssen tatsächlich eine neue Variable definieren, k, wobei k die Anzahl der eindeutigen Werte ist, und unser Algorithmus, unsere Speicherkomplexität für unseren Algorithmus ist o von. Das war's wegen der Komplexität. Gehen wir nun zum Code über. Also pausiere das Video hier und versuche zu programmieren. Im Konstruktor werden wir einen neuen Zähler initialisieren. Aus Sammlungen importieren, default, dict. Dies ist eine andere Art von Wörterbuch. Dieses Wörterbuch initialisiert diesen Wert mit dieser Funktion, wenn Sie auf einen Schlüssel zugreifen, der keinen aktuellen Wert hat. In diesem speziellen Fall ist 0 für diesen Zähler, wenn wir sagen, wir greifen auf Zähler 0 zu, derzeit kein gültiger Schlüssel für dieses Wörterbuch, also wird es mit dieser int-Funktion initialisiert. Die Standardeinstellung für die int-Funktion ist Null. Standardmäßig gibt mir der Selbstzähler von 0 0 0. In der Tat, Selbstzähler von allem, wird mir auch 0 geben. So funktioniert dieses Standardwörterbuch, und dadurch sieht ein Teil des Codes nur ein bisschen einfacher aus. Jetzt, wo ich das habe, implementieren wir jetzt die Push-Funktion. Wie wir bereits gesagt haben, überprüfen wir zunächst , ob die aktuelle Anzahl unter dem Limit liegt. Wenn es unter dem Limit liegt, fahren wir fort. Wenn es unter dem Limit liegt, erhöhen wir die Anzahl tatsächlich und schieben sie dann tatsächlich auf den Stapel. Im Pop verringern wir einfach die Anzahl. Hier werden wir zuerst den Wert kennenlernen. Hier springen wir vom Stapel, und sobald wir diesen Wert platzen, wird die Anzahl für diesen Wert verringert, und dann geben wir den Wert zurück. Mal sehen, ob unsere Cap-Stack-Tests erfolgreich sind. Ganz oben drücken Sie den grünen Knopf und los geht's, Cap Snack ist jetzt nicht mehr in der Liste der Fehler, also haben wir diesen Test bestanden. Gehen wir zur nächsten Frage über. Ein Tipp ist, dass Hashmaps ideal für Daten mit eindeutigen IDs sind. In diesem speziellen Fall wissen wir, dass jeder einzelne Wert oder die Prämisse des Problems darin besteht, dass wir festlegen , ob wir durch den Wert auf den Stapel pushen sollen oder nicht . Der Wert selbst ist also die eindeutige ID. Da wir diese eindeutige ID haben, waren Hashmaps perfekt für dieses Problem. Hashmaps sind jedoch am schlechtesten für geordnete Daten. Wenn es eine Beziehung zwischen Ihren Daten gibt, nehmen wir an, ich habe eine Folge von Zahlen, eine bis zu 10, und ich muss diese Reihenfolge kennen. Oder eine andere Art, es auszudrücken, sagen wir, ich habe 10 Werte und füge sie in eine Datenstruktur ein. Wenn ich mich an die Reihenfolge erinnern muss, in der ich es eingefügt habe, sind Hashmaps nicht die zu verwendende Datenstruktur, da Hashmaps die gesamte Reihenfolge vergessen. Sie führen lediglich eine Zuordnung von Schlüsseln zu Werten durch. Sie erinnern sich an nichts darüber in welcher Reihenfolge Sie die Schlüssel einsetzen. Das ist also wichtig. Hashmaps sind ideal für Daten mit eindeutigen IDs, aber sie eignen sich nicht so gut für Daten, bei denen die Reihenfolge wichtig ist. für unsere nächste Frage nach unten, Scrollen Sie für unsere nächste Frage nach unten, um die nächste Frage zu sehen. Gibt ein Histogramm aller eindeutigen Buchstaben und ihrer Anzahl von Vorkommen zurück. Ich schätze, das hätte zuerst gehen sollen. Dies ist eine Teilmenge des vorherigen Problems. Sie Halten Sie das Video hier und prüfen Sie, ob Sie den Algorithmus herausfinden können. Die Lösung besteht konzeptionell darin, dass wir eine HashMap als Zähler initialisieren, und jedes Mal, wenn wir über die Zeichenfolge iterieren, und für jeden einzelnen neuen Wert greifen wir auf unseren Zähler zu. Wenn wir diesen Wert bereits gesehen haben, werden wir der Anzahl einen hinzufügen. Wenn wir diesen Wert noch nicht gesehen haben, initialisieren wir die Zählung auf eins. Das ist also der Algorithmus. Wie hoch ist die Laufzeit und Speicherkomplexität? Pausiere das Video hier. Hier ist die Antwort. Die Laufzeitkomplexität ist linear. Wir müssen über jeden einzelnen Buchstaben in der Zeichenfolge iterieren . Daran führt kein Weg vorbei. Die zweite, Speicherkomplexität. Auch hier müssen wir eine brandneue Variable erfinden. Variable ist die Anzahl der eindeutigen Werte, sagen wir k. Dieser Algorithmus ist o von k, wobei k die Anzahl der eindeutigen Werte ist, die in der Zeichenkette enthalten sind. Das war's für die Combine-Serie, versuchen wir jetzt, es zu codieren. Pausiere das Video hier und probiere den Code aus. Lassen Sie uns nun die Lösung im Code behandeln. Hier erstellen wir einen Zähler. Dieser Zähler wird ein Wörterbuch sein. In der Tat werde ich das Standardwörterbuch von früher verwenden. Auf diese Weise wird das Wörterbuch auf Null initialisiert. Hier haben wir aus Sammlungen, Import, Defaultdict. Dann erhöhen wir hier für jeden einzelnen Buchstaben in der Zeichenfolge die entsprechende Anzahl. Hier erhöhen wir die entsprechende Anzahl und geben schließlich den Zähler zurück. Jetzt möchte ich auch die Implementierung ohne dieses Defaultdict zeigen . Nehmen wir an, Sie haben das defaultdict nicht verwendet. Nehmen wir an, Sie erstellen einfach ein normales Wörterbuch. Dann würden wir hier überprüfen, ob der Brief in der Theke ist. Wenn wir diesen Brief schon einmal gesehen haben, erhöhen wir ihn einfach um eins. Wenn wir das noch nie gesehen haben, initialisieren wir es auf eins. Gegenbuchstabe ist gleich Eins. Lassen Sie uns nun weitermachen und dies ausführen, um zu überprüfen, ob unique jetzt ein bestandener Test ist. Da haben wir's. Unique hat jetzt alle Tests bestanden. Wir haben auch diese Übung abgeschlossen. Gehen wir nun zur nächsten Übung über. Ein Tipp ist, sicherzustellen, dass Ihr Schlüssel vorhanden ist, bevor Sie darauf zugreifen. Das haben wir genau hier gemacht. In Zeile 103 haben wir überprüft, ob der Brief im Zähler war. Mit anderen Worten, wenn wir diesen Brief schon einmal gesehen hätten und es bereits einen gültigen Schlüssel in unserer Zuordnung gäbe. Wenn wir das wissen, gehen wir zu der Frage über, die danach kommt. Verwenden Sie unsere Datenstruktur, um den häufigsten Wert in einer Liste ganzer Zahlen zu finden . Ich habe ganze Zahlen angegeben , um das ein bisschen einfacher zu machen. Es gibt jedoch mehrere Fragen , die Sie Ihrem Interviewer stellen sollten. Die erste Frage wäre, wenn sie nicht die ganzen Zahlen angegeben hätten, würden sie es höchstwahrscheinlich nicht tun. Sie würden höchstwahrscheinlich Zahlen sagen. Dann würden Sie fragen wollen, kann es negative Werte geben? Kann es Dezimalwerte geben? Was ist die Reichweite? Was sind die möglichen Werte, die ich möglicherweise sehe? Die zweite Frage , die Sie stellen möchten, ist: Was ist, wenn es ein Unentschieden gibt? Dass die Frage selbst nicht genau definiert ist , wenn es einen Gleichstand gibt. Im Falle eines Gleichstands in dieser Problembeschreibung melden Sie nun eine der Zahlen, die für die größte Häufigkeit gebunden sind. Das wird normalerweise passieren. Aber das sind gute Fragen, die Sie stellen sollten und es gibt Ihnen einige Brownie-Punkte in Ihrem Interview. In dieser speziellen Frage haben wir eine Liste von Zahlen, und ich möchte herausfinden, was die häufigste ist, pausieren Sie das Video hier und sehen Sie, ob Sie den Algorithmus herausfinden können . Hier ist die Lösung. Wir werden etwas sehr Ähnliches wie zuvor tun. Wir werden eine HashMap verwenden , um die Anzahl für jeden einzelnen Wert in unserer Liste zu speichern . Sobald Sie diesen Zähler haben, verwenden Sie den Zähler, um herauszufinden , welcher der beste war. Das wäre eine Möglichkeit, das zu tun. Eine andere Möglichkeit , dies zu tun , für die keine zwei Datendurchläufe erforderlich , besteht darin sind, besteht darin, beim Hinzufügen zum Zähler eine Variable beizubehalten, die den häufigsten Wert darstellt. Wenn Sie einen Wert sehen, der plötzlich mehr Instanzen hat , ersetzen Sie diese Variable. Sie werden das in nur einer Sekunde in Aktion sehen. Konzeptionell besteht die Hauptidee jedoch darin, einen Zähler aller eindeutigen Werte beizubehalten einen Zähler aller eindeutigen Werte und diesen Zähler dann zu verwenden. Nachdem wir den Algorithmus kennen, pausieren Sie das Video hier und prüfen Sie, ob Sie die Laufzeit und die Speicherkomplexität herausfinden können . Hier ist die Antwort. Genau wie zuvor haben wir genau die gleiche Laufzeitspeicherkomplexität und die vorherigen Funktionen. Komplexität der Laufzeit wird linear sein. Sie müssen jedes einzelne Element in der Liste durchgehen . Daran führt kein Weg vorbei. Der zweite ist O von k, wobei k die Anzahl der eindeutigen Werte ist. Ich schätze, das wird jetzt ein Motiv. Aber zumindest für diese Übungen ist O of k das richtige Mal, also behalte das im Hinterkopf. Anzahl der eindeutigen Werte ist linear. Nachdem wir die Komplexität behandelt haben, haben wir den Algorithmus behandelt, das Video hier pausieren und den Code ausprobieren. Hier ist die Lösung. Schreiben wir das jetzt auf. Wir beginnen mit counter und counter wird ein leeres Wörterbuch sein. Der größte Wert wird keiner sein, sagen wir mal. Für den Wert in Zahlen prüfen wir, ob sich der Wert im Zähler befindet. Wenn wir diesen Wert schon einmal gesehen haben, werde ich das eigentlich ein wenig ändern. Wenn der Wert nicht im Zähler ist, initialisieren wir ihn auf Null. Unabhängig davon, was der Wert ist, erhöhen wir um eins. Wenn der Zähler größer als der größte Wert ist, also wenn die aktuelle Anzahl größer als die größte Anzahl , werden wir neu zuweisen und dann den größten Wert zurückgeben. Bevor Sie diese Funktion ausführen, sollten Sie etwas beachten, das eine der Taktiken ist, die ich in einem zukünftigen Kurs ansprechen werde, darin besteht, dass in einem zukünftigen Kurs ansprechen Sie Ihren Code immer virtuell ausführen sollten. Damit meine ich, führe einfach den Code in deinem Kopf aus, als wärst du der Interpreter. Wenn Sie vor dem Ausführen des Codes Fehler feststellen, ist dies normalerweise ein weiterer Grenzpunkt für Sie. In diesem speziellen Fall würden wir hier tatsächlich einen Fehler wegen des größten Zählers sehen . Am größten ist hier keiner und keiner existiert noch nicht im Counter. Dies geht auf unseren vorherigen Tipp zurück. Stellen Sie immer sicher, dass der Schlüssel vorhanden ist, bevor Sie versuchen, darauf zuzugreifen. In diesem speziellen Fall füge ich hier den None-Schlüssel hinzu und setze ihn auf einen negativen. Auf diese Weise ist jeder andere Wert mit einer Zählung größer als dieser. in der allerersten Iteration Ersetzen Sie in der allerersten Iteration den größten Wert durch den aktuellen Wert. Das war's für diese Funktion. Versuchen wir nun, die Tests auszuführen und sicherzustellen, dass die häufigsten Tests nicht mehr als fehlgeschlagener Test angezeigt werden. Klicken Sie ganz oben auf die grüne Schaltfläche „Ausführen“ . Da hast du es. Sie können jetzt sehen, dass am häufigsten kein fehlgeschlagener Test mehr auftritt. Hier wollen wir leere Eingaben betrachten. Es gibt hier einen Fall, in dem möglicherweise etwas Seltsames vor sich geht, wenn Sie eine leere Liste in die häufigste Liste übergeben würden, dann würden Sie einfach keine zurückbekommen, was ein vernünftiger Standard ist. Sie sollten jedoch nur sicherstellen , dass Ihre Funktion nicht unterbrochen wird , wenn Sie leere Eingaben erhalten. In diesem Fall wird unsere Funktion nicht unterbrochen. Es gibt keine zurück. Solange wir also deklarieren , dass die Funktion bei leerer Eingabe keine zurückgibt, ist das vollkommen gültig. Für unsere nächste Frage. Dieser ist konzeptionell ziemlich herausfordernd. Wir werden eine Datenstruktur oder eine beliebige Datenstruktur verwenden , um eine Reihe von Reisen zu sortieren. Eine Reihe von Schritten ist als Quelle und Ziel formatiert. Sie können sich diese als Quell- und Zielstädte vorstellen. Sobald wir das haben, wollen wir den vollständigen Pfad berechnen. Wir wollen die reale Startposition und die reale oder endgültige Endposition finden . Sie können vorerst davon ausgehen, dass es nur einen Pfad gibt und dass es einen vollständigen Pfad gibt. Das bedeutet, dass es keine Lücken gibt. Ende niemals an einer zufälligen Position in der Mitte mit einer Reihe von Schritten, die nichts miteinander zu tun haben, und es gibt keine Zyklen. Du wirst niemals von eins auf zwei zurück zu eins gehen. Wenn Sie von eins auf zwei gehen, werden Sie immer mit drei, vier usw. fortfahren . Ihr Ziel ist es, die Reihenfolge der Schritte vom ersten bis zum letzten zu drucken . Hier haben wir ein paar Schritte. Wir gingen von drei auf zwei, eins auf drei und zwei auf fünf. Ihr Ziel ist es herauszufinden, wie die richtige Abfolge von Schritten ist. Hier ist die richtige Reihenfolge eins bis drei, dann drei bis zwei, dann zwei bis fünf. Halten Sie das Video hier an, sehen Sie, welche Datenstrukturen Sie verwenden möchten und wie Sie sie verwenden würden. Hier ist die Lösung. Konzeptionell werden wir zuerst herausfinden, was die ultimative Ausgangsposition ist. Um das zu tun, werden wir eine Reihe von Hashmaps erstellen. Diese Hashmaps werden vom Ziel zur Quelle zugeordnet. Dann starten wir von jedem beliebigen Ziel aus. Von diesem Ziel aus überprüfen wir, ist die Quelle in der HashMap? Ist die aktuelle Quelle ein weiteres Steps-Ziel? Wenn ja, dann gehen wir zu diesem Schritt Ziel und Quelle, und Sie machen das weiter. Nehmen wir zum Beispiel an, wir beginnen bei zwei und sagen dann, ist drei in unserem Mapping? Es ist, drei sind in unserer Kartierung. Schauen wir uns die entsprechende Quelle an. Erstens, ist einer in unserer Kartierung? Nein, es steht nicht in unserer Kartierung. Das bedeutet also, dass man die ultimative Ausgangsposition ist . Das werden wir tun. Wir erstellen ein Mapping vom Ziel zur Quelle.. Dann werden wir es weiter durchqueren, bis wir zur ultimativen Quelle kommen. Sobald Sie dort angekommen sind, ist das der erste Schritt. Wir haben die ultimative Quelle. Wir müssen diese Reihe von Schritten jetzt tatsächlich ausdrucken. Wir müssen rückwärts gehen, wir müssen von der Quelle ausgehen und weiter durchqueren. Wir fangen bei eins an, wir gehen zu drei, dann schauen wir nach drei. Wir sehen drei, dann gehen wir zu zwei. Wir schauen nach zwei, zwei sind da. Dann machen wir das weiter. Sie bewegen sich weiter in Vorwärtsrichtung. Sie benötigen eine Reihe von Hashmaps, die Ihnen die umgekehrte Richtung geben. Dann müssen Sie HashMap einrichten, die Ihnen die Vorwärtsrichtung gibt. machen wir. Ich werde zwei Hashmaps erstellen. Eine HashMap verlinkt immer vom Ziel zur Quelle. Man verlinkt immer von Quelle zu Ziel, das ist also unser Algorithmus. Was ist nun die Laufzeit und Speicherkomplexität? Pausiere das Video hier. Hier ist die Antwort. Unsere Speicherkomplexität ist linear in der Anzahl der Schritte, da wir sie für jeden einzelnen Schritt zu diesen beiden Hashmaps oder beiden Wörterbüchern hinzufügen . Nun zu unserer Laufzeitkomplexität, dasselbe. Die Anzahl der Schritte ist linear, da wir mindestens einmal auf jeden einzelnen dieser Schritte zugreifen müssen, und zum Glück für uns nur einmal. wir das wissen, schreiben wir jetzt die Funktion, den Code, auf. Pausieren Sie das Video hier und probieren Sie den Code aus. Lassen Sie uns nun die Lösung behandeln. Hier werden wir zwei Hashmaps oder zwei Wörterbücher initialisieren . Einer wird vorwärts sein, einer wird rückwärts sein. Für jedes einzelne Paar von Quelle und Ziel in unseren Schritten werden wir beide füllen. Das Forward-Mapping wird von der Quelle zum Ziel gehen. Die Rückwärtszuordnung erfolgt vom Ziel zur Quelle. Nachdem wir diese beiden Mappings aufgefüllt haben, gehen wir von jeder beliebigen Quelle zurück zur ultimativen Quelle. Die Quelle ist zwar rückwärts, und das ist wohl ein bisschen unklar, aber hier werde ich es nur klarstellen. Hier haben wir, dass source gleich dem ersten Schritt ist, und dann wird es die Quelle des ersten Schritts sein. Während die Quelle rückwärts ist, werden wir weiter durchqueren. Sobald diese While-Loop abgeschlossen ist, befindet sich Source nicht mehr im Backward Mapping, das bedeutet, dass Source die ultimative Quelle ist, der erste Ort, an den wir jemals gegangen sind. Solange sich source dann im Forward Dictionary oder im Forward Mapping befindet, drucken wir sie aus. Hier werden wir f-Strings in Python verwenden. Sie können dies ausdrucken, wie Sie möchten und dann gehen wir diesen Weg. Solange sich unsere Quelle im Forward-Mapping befindet, greifen wir im Forward-Mapping für sein Ziel auf dieses Element zu. Das ist so ziemlich alles. Lassen Sie uns nun diese Funktion ausführen und sicherstellen, dass wir diese Funktion übergeben, um den Pfad aus Tests zu erhalten. Los geht's, wir haben nur einen Satz fehlgeschlagener Tests, die sich in einer anderen Funktion befinden, also haben wir diese Übung erfolgreich bestanden. Lassen Sie uns nicht über diese Herausforderungsfrage sprechen. Diese Frage ist auch ziemlich schwierig, also werden wir nur algorithmisch darüber sprechen, wir werden sie nicht wirklich codieren. Auch hier habe ich Lösungen für Sie geschrieben, aber wir werden hier einfach nicht über den Code sprechen. Ich habe jede einzelne Zeile in den Lösungen kommentiert , damit Sie sie durchgehen und verstehen können sie durchgehen und verstehen Der wichtigste Teil hier ist jedoch der Algorithmus. Für dieses Problem werden wir ein Wörterbuch mit einer maximalen Größe erstellen , das als am wenigsten verwendetes Bargeld fungiert . In diesem Wörterbuch löschen Sie jedes Mal, wenn Sie einen Schlüssel einfügen , aktualisieren oder darauf zugreifen, der als Verwendung dieses Schlüssels gilt, und wenn das Wörterbuch mehr Schlüssel als die maximale Größe hat , werden Sie den zuletzt zuletzt verwendeten löschen verwendeter Schlüssel. Sehen wir uns ein Beispiel dafür an. Nehmen wir an, ich habe dieses neue Wörterbuch und füge den Wert 1 mit dem Schlüssel a ein, also schauen wir uns hier nur die Schlüssel an, die Werte spielen keine große Rolle. Wir haben einen Wert für Schlüssel a eingefügt und dann fügen wir einen Wert für Schlüssel b ein. An dieser Stelle ist b der zuletzt verwendete Wert und 1 ist der am wenigsten verwendete Wert. Allerdings greife ich hier auf a. Jetzt wird a der zuletzt verwendete Wert , hier haben wir ihn getauscht. Einer entspricht a, also ist a der zuletzt verwendete und der andere Wert wird am wenigsten verwendet. Jetzt weisen wir dem Schlüssel c einen neuen Wert zu und Sie werden feststellen, dass b zu diesem Zeitpunkt hier der am wenigsten verwendete Wert war, also wird b gelöscht und jetzt haben wir nur noch a und c übrig. In diesem speziellen Wörterbuch betrug die maximale Größe zwei. So haben wir tatsächlich gesehen, wie einer der Schlüssel fallen gelassen wurde. Denken Sie daran, dass jedes Mal, wenn Sie auf einen Wert für einen Schlüssel zugreifen, ihn aktualisieren oder einfügen , der als Zugriff auf diesen Wert gilt oder diesen Wert verwendet, und jedes Mal, wenn Sie einfügen, wenn Sie die maximale Größe überschreiten, Sie in letzter Zeit am wenigsten verlieren verwendeter Wert. all dies wissen, denken wir nun darüber nach, wie wir das algorithmisch gestalten können. Pausieren Sie das Video hier und prüfen Sie, ob Sie einen Algorithmus entwickeln können. Hier ist die Lösung. Um diesen Cache tatsächlich zu implementieren, werden wir tatsächlich eine doppelt verknüpfte Liste führen . Bevor wir über eine verknüpfte Liste sprechen, behält eine verknüpfte Liste einen Link zum nächsten Link bei, sodass Sie ungefähr so aussehen würden. Sie hätten eine, die mit zwei verknüpft ist, Links zu drei. Eine doppelt verknüpfte Liste enthält jedoch tatsächlich beide Richtungen, drei wissen, dass ihr vorheriger Wert zwei ist, zwei wissen, dass ihr vorheriger Wert eins ist. Der Grund, warum Sie diese doppelt verknüpfte Liste wünschen, liegt darin , dass wir möchten, dass die verknüpfte Liste die Reihenfolge zwischen all unseren Daten verfolgt , und diese Bestellung zeigt uns, dass die zuletzt verwendete und die am wenigsten verwendeten Werte sind. Glücklicherweise ist es mit einer verknüpften Liste sehr einfach, einen Wert einzufügen und jeweils einen Wert zu entfernen. Beides sind konstante Zeitoperationen. Beachten Sie, dass dies für ein Array nicht zutrifft. Wenn Sie in einem Array umziehen möchten, sagen wir drei. Nehmen wir an, wir hatten das. Nehmen wir an, wir hatten 1, 2, 3, 4, 5 und nehmen wir an, dass fünf jetzt der zuletzt verwendete Wert ist. Ihre neue Liste müsste 5, 1, 2, 3, 4 sein. Bei einer doppelt verknüpften Liste ist dies ein konstanter Zeitvorgang. Alles, was Sie tun, ist die Verbindungen neu zuzuweisen , wie wir zuvor besprochen haben. Für ein Array müssten Sie jedoch die Position von 1-2 kopieren, 2-3, 3-4, 4-5, und dann fünf an den Anfang kopieren. Sie müssten n Kopien, n Operationen machen , um diese Liste neu zu mischen. Bei einer doppelt verknüpften Liste müssen Sie jedoch nichts davon tun. Es ist eine konstante Zeitoperation. diesem Grund möchten wir, dass eine doppelt verknüpfte Liste mit unserem Mapping kombiniert wird, oder eine HashMap, um diesen am wenigsten verwendeten Cache zu implementieren. wir wissen, dass wir eine doppelt verknüpfte Liste wollen, wie werden wir sie tatsächlich verwenden? Lassen Sie uns über die beiden Funktionen sprechen, die wir tatsächlich benötigen. Wir haben zwei verschiedene Funktionen. Ich werde diese Funktionen nicht vollständig konkretisieren , aber ich werde hier etwas Pseudocode hinzufügen, nur damit Sie wissen, wie die allgemeine Struktur des Codes aussehen würde. Hier hätten wir die Möglichkeit, einen Gegenstand zu bekommen , und wir hätten auch eine andere Funktion hier auch eine andere Funktion, die uns die Möglichkeit geben würde, einen Gegenstand festzulegen. Die Grundidee hier ist, dass wir unter diesen beiden Funktionen den Schlüssel aktualisieren, wir würden etwas sagen wie den neuesten Schlüssel erstellen und dann dasselbe hier. Wir werden auch den neuesten Schlüssel erstellen, und das ist so ziemlich alles für die High-Level-Logik. Das meiste Wesentliche wäre hier passiert. In der Funktion make last last last hier würden Sie tatsächlich die gesamte Neuzuweisung vornehmen, die erforderlich ist, um sowohl ein Element aus der verknüpften Liste zu entfernen als auch es dann ganz am Anfang wieder einzufügen ist es. Dies ist die konzeptionelle Lösung für dieses Problem. Sie würden eine doppelt verknüpfte Liste und auch eine Map verwenden. Das scheint ziemlich komplex zu sein, ich werde nicht codieren, zumindest nicht hier in der Lektion. Ich habe die Lösungen bereits geschrieben und die Lösungen werden Ihnen mit vollständig kommentiertem Code zur Verfügung stehen . Der wichtigste Teil dieser Frage war jedoch dieser Frage war die Verwendung der doppelt verknüpften Liste. wird es einige Fragen wie diese geben, bei denen Sie sich eine Ihren Interviews wird es einige Fragen wie diese geben, bei denen Sie sich eine neuartige Kombination verschiedener Datenstrukturen einfallen lassen neuartige Kombination verschiedener Datenstrukturen einfallen verschiedener Datenstrukturen um Ihre Ziele zu erreichen. In diesem speziellen Fall wollten wir einen Cache, der am wenigsten verwendet wurde, und wir möchten, dass er sehr effizient Verwendung dieser doppelt verknüpften Liste ermöglicht es uns, diese Funktion effizient zu halten. Das bringt uns zu unserem zweiten Teil der Frage. Wie hoch ist die Laufzeit und Speicherkomplexität unseres Algorithmus? Insbesondere, wie hoch ist die Laufzeit Ihrer Speicherkomplexität beim Einfügen und anschließenden Zugriff auf einen Wert? Pausiere das Video hier, und hier ist die Lösung. Den Wert zu bekommen ist ziemlich einfach. Den Wert selbst zu bekommen, nur auf einen Wert in einer HashMap zuzugreifen , ist alles eins, es ist konstante Zeit. Die eigentliche Frage ist jedoch, wie lange dauert es, den neuesten Wert zu aktualisieren? Denn das beinhaltet das Entfernen aus und das erneute Einfügen in eine doppelt verknüpfte Liste. Nun, glücklicherweise ist das Einfügen und Löschen aus einer verknüpften Liste eine konstante Zeit. Angesichts dessen wissen wir, dass dieser Algorithmus, diese Datenstruktur eine konstante Zugriffszeit ist . Das ist großartig. Wir haben die Effizienz einer HashMap beibehalten und was ist nun mit der Zuweisung? Was aktualisiert diese HashMap? Nun, zum Glück ist das auch immer noch konstante Zeit. Es ist konstante Zeit, eine HashMap selbst und erneut zu aktualisieren, um dieses Schlüssel- und Wertepaar zum neuesten zu machen. Diese Operation selbst ist wieder konstante Zeit, genau wie wir bereits erwähnt haben. Aufgrund dieser konstanten Zeitoperation bedeutet das Erstellen von etwas Neuem nur das Einfügen und Entfernen aus einer doppelt verknüpften Liste. Um dies zu sagen, haben wir uns eine schöne Kombination von Datenstrukturen ausgedacht eine schöne Kombination von , um Zeit, Einfügen und Zugriff für eine Datenstruktur konstant zu halten . Das ist die effizienteste, die Sie bekommen können. Ich denke, die Erkenntnis hier ist, dass die Kenntnis der richtigen Datenstrukturen wirklich einen Unterschied in der Größenordnung bedeuten kann , einen Unterschied in der Größenordnung zwischen einer effizienten Implementierung. und ineffizient. In diesem Fall hat uns die Verwendung einer doppelt verknüpften Liste eine Effizienz verschafft, und dies ist eine nützliche Fähigkeit für Interviews, vielleicht weniger für die Arbeit, aber wenn Sie ein riesiges Produktionssystem hatten, wenn Sie ein systemkritische Anwendung , die ineffiziente Datenstrukturen verwendete, dann hätten Sie die Möglichkeit, massive geschäftliche Auswirkungen zu erzielen. Das war's für diese Übung. Um auf all diese groben Folien zu allen Lösungen zugreifen zu können, sollten Sie auf die Kurswebsite zugreifen. Damit ist die Übungsstunde HashMap-Slash-Mappings abgeschlossen. 11. Bäume: Breadth vs. Tiefe: In dieser Lektion besprechen wir eine Möglichkeit, Daten hierarchisch zu speichern. Eine Datenstruktur, die als Baum bezeichnet wird. Wir erklären, wie ein Baum funktioniert, wie man einen Baum durchquert, wie man einen Baum organisiert, was O (long) time bedeutet. Abschließend werden wir die Vor- und Nachteile dieser neuen Datenstruktur diskutieren . Fangen wir damit an, was ein Baum ist. Ein Baum besteht aus einem Wurzelknoten. Dieser Stammknoten hat zwei untergeordnete Knoten. Jeder dieser Knoten hat dann jeweils zwei weitere untergeordnete Knoten. Diese Struktur nennen wir einen Baum. Da jeder Knoten zwei Kinder hat, nennen wir ihn einen Binärbaum. Angenommen, Sie möchten nach einem Wert suchen oder wir möchten diesen Baum ändern, wir müssen wissen, wie man einen Baum durchquert. Hier sind zwei verschiedene Möglichkeiten, dies zu erreichen. Die erste Methode heißt Breadth First Traversal, kurz BFS. In BFS durchqueren wir die Knoten Ebene von links nach rechts. Zuerst greifen wir auf drei zu. Wir haben das erste Level abgeschlossen, also lasst uns zum nächsten Level übergehen. Dann greifen wir auf 7 zu, dann auf 2, wir haben das zweite Level beendet. Gehen wir also zum letzten Level über. Wir greifen auf Null, Eins, Neun und Acht zu. Wir haben das letzte Level beendet, also sind wir fertig mit BFS. Unsere zweite Art, diesen Baum zu durchqueren heißt Depth-First Traversal, kurz DFS. Hier greifen wir vom Root-Knoten aus zu, drei, wir greifen auch auf sieben zu. Wir gehen weiter tiefer, also greifen wir auf zwei zu. Jetzt, wo wir in eine Sackgasse geraten sind, gehen wir zurück zu sieben und erforschen so tief wie möglich. Also greifen wir auf einen zu. Wieder sind wir in einer Sackgasse. Also gehen wir zurück zu sieben, gehen zurück zu drei und erforschen erneut so tief wie möglich. Wir greifen auf zwei dann auf neun zu. Wir sind in einer Sackgasse. Gehen Sie also zurück zu zwei und greifen Sie schließlich auf acht zu. Und das sind die beiden Möglichkeiten, einen Baum zu durchqueren, Breite zuerst oder die Tiefe zuerst. Wir erklären nun eine Möglichkeit, Daten in einem Baum zu organisieren. Dies wird als binärer Suchbaum bezeichnet. Beachten Sie, dass wir die Werte neu angeordnet haben. Insbesondere haben wir sie so angeordnet, dass das linke Kind immer kleiner ist als das Elternteil. Zum Beispiel ist drei hier weniger als fünf und zwei ist weniger als drei. Wir haben diese Werte auch so angeordnet, dass das richtige Kind immer größer ist als das übergeordnete Element. Zum Beispiel ist acht größer als fünf. Das rechte Kind ist also immer größer und das linke Kind ist immer weniger. Diese geordnete Struktur ermöglicht es uns, Daten sehr effizient zu verwalten. Nehmen wir an, wir möchten Daten in den Baum einfügen. Hier wollen wir sechs einfügen. Beginnen Sie dazu mit dem Stammknoten fünf. Von hier aus suchen wir iterativ nach einem Zuhause für sechs Personen. Da sechs größer als fünf ist, wissen wir, dass sechs zur Rechten gehören müssen. Jetzt haben wir den Wert Acht. Da sechs weniger als acht sind, wissen wir, dass sechs zur Linken gehören müssen. Es gibt hier keinen Knoten, also ist das jetzt Six's Zuhause. Damit ist der Einfüge-Algorithmus abgeschlossen. Nehmen wir an, die Tiefe des Baums ist d, dann nimmt dieser Algorithmus bis zu d Operationen auf. Infolgedessen benötigt das Einfügen O von d Zeit, um ausgeführt zu werden. Zusammenfassend dauert das Einfügen O (d) -Zeit. Sehen wir uns nun an, wie das Löschen funktioniert. Angenommen, wir löschen sechs rot angezeigte. Nun, das ist ziemlich einfach. Löschen Sie einfach diesen Knoten und den Zeiger von seinem übergeordneten Knoten. Nehmen wir jedoch an, wir haben aus der Mitte des Baumes gelöscht. Insbesondere wollen wir fünf streichen. Lass uns versuchen, was wir letztes Mal gemacht haben, lass uns fünf fallen. Jetzt gibt es jedoch ein Loch im Baum. Wir können jedes Kind benutzen, um die Lücke zu füllen , drei oder acht. Hier verwenden wir das richtige Kind acht. Also schieb acht nach oben. Jetzt gibt es jedoch eine Lücke , in der früher acht waren. Um konsistent zu sein, verwenden wir das rechte Kind von Acht, neun. Schieben Sie erneut neun nach oben. Jetzt gibt es ein Ganzes, in dem früher neun waren, aber das ist okay. Es gibt keine Kinder mehr. Wir können also einfach den zusätzlichen Zeiger löschen. Genau wie beim vorherigen Algorithmus. Dieser Algorithmus nimmt bis zu d Operationen auf. Das Löschen nimmt also O. d Zeit in Anspruch. Um das Einfügen und Löschen für einen binären Suchbaum zusammenzufassen , benötigen beide O of d-Zeit. Unser Ziel ist es, nach dem Wert sechs zu suchen. Wir beginnen mit dem Wurzelknoten fünf, wie beim Einfügen vergleichen wir unseren Sollwert sechs mit dem aktuellen Knoten fünf. Sechs ist größer als fünf, also gehen wir nach rechts. Jetzt greifen wir auf acht zu. Wir vergleichen, sechs ist weniger als acht, also gehen wir nach links. Jetzt greifen wir auf sechs zu, sechs ist der Wert, den wir suchen. Also sind wir mit der Suche fertig. Dieser Algorithmus kann bis zu d Operationen aufnehmen. Die Suche ist also O von d. Zusammenfassend weist ein binärer Suchbaum eine O of d-Änderung und einen Zugriff auf. Beachten Sie, dass es in einem Baum keinen direkten Abruf des achten Knotens gibt, da es keine globale Reihenfolge der Knoten gibt und Sie nicht wissen, wo sich die Knoten im Speicher befinden. Daher gibt es keine Fetch-Laufzeitanalyse. Damit ist unsere Analyse von Bäumen abgeschlossen. Wir haben jedoch noch einen zu tun. Wir möchten diese Laufzeiten übersetzen. Alle unsere Laufzeiten werden als Funktion der Baumtiefe ausgedrückt oder d. Visualisiert, hier ist ein Baum und hier ist die Tiefe des Baumes. In diesem Fall haben wir eine Tiefe von vier. Wir möchten diese Laufzeiten in Form von n, der Anzahl der Knoten, ausdrücken , in diesem Fall gibt es 15 Knoten. Dies liegt daran, dass die Laufzeiten für alle unsere vorherigen Datenstrukturen in n, der Anzahl der Elemente, ausgedrückt wurden . Daher sollten wir unsere neuen Laufzeiten auch in n ausdrücken , um sicherzustellen, dass diese Laufzeiten vergleichbar sind. Hier ist derselbe Baum. Versuchen wir, die Beziehung zwischen d und n zu verstehen . Betrachten Sie die erste Schicht. Dies ist Depth 1 mit einem Knoten. Betrachten Sie die ersten beiden Schichten. Dies ist Depth 2 mit drei Knoten. Die ersten drei Schichten, das ist Depth 3 mit sieben Knoten. Betrachten Sie alle Ebenen. Dies ist Depth 4 mit 15 Knoten. Es kann schwierig sein, ein Muster zu erkennen. Anstatt also die Anzahl der Knoten zu zählen, zähle ich die Anzahl der Knoten plus eins. Wenn Sie sich die Zahlen in Rot ansehen , sehen Sie ein Muster? Wie Sie vielleicht bemerkt haben, verdoppelt sich die Anzahl der Rottöne und die Anzahl der Knoten ungefähr. Als Ergebnis können wir sagen, dass n ungefähr zwei Potenzen von d ist . Jetzt haben wir eine mathematische Beziehung zwischen d und n. Da wir d in unsere Laufzeitkomplexitäten einbinden wollen , lass uns nach d lösen. Um nach d zu lösen, schreiben wir zuerst die Gleichung wenden dann Logarithmen auf beide Seiten an. Denken Sie daran, dass wir mit Protokollen den Exponenten d als Koeffizienten außerhalb des Logs bewegen können . Dann ignorieren wir einfach die Konstante log zwei. Jetzt bekommen wir, dass log von n ungefähr d ist . Wir können jetzt einstecken. Zuvor hatten wir diese Laufzeiten in Form von d. Das Einstecken von d ist gleich logn. Wir erhalten jetzt O of log n für alle Änderungs- und Zugriffslaufzeiten. Lassen Sie uns nun anhand dieser Analyse die Vor- und Nachteile von binären Suchbäumen diskutieren . Binäre Suchbäume haben in einigen beeindruckende Laufzeiten, nur O (logn). In der Praxis liegt O of log n sehr nahe an o einer konstanten Zeit. Binäre Suchbäume sind von ideal für Zufallssequenzen. Je gleichmäßiger verteilt, desto besser. Binäre Suchbäume sind jedoch die schlechtesten für sequenzielle Daten, und hier ist der Grund dafür. Nehmen wir an, ich habe eine Folge von Zahlen von 0-10. Zuerst erstellen wir den Wurzelknoten 0, dann haben wir eins beantwortet, dann zwei, dann drei, dann vier, dann fünf. Die Liste geht weiter, und wie Sie sehen können, haben wir im Grunde eine gerade Linie anstelle eines gefüllten binären Suchbaums. Dies bedeutet, dass die Tiefe jetzt nicht mehr log n ist, sondern dass die Tiefe genau der Anzahl der Knoten entspricht. Das heißt, alle unsere Algorithmen benötigen jetzt O von n oder linearer Zeit. Zusammenfassend lässt sich sagen, dass binäre Suchbäume für sequenzielle Daten wirklich ineffizient sind. Zusammenfassend haben wir besprochen, was ein Baum ist, Breite zuerst versus Tiefe zuerst durchlaufen, was ein binärer Suchbaum ist, was das O der Log-n-Zeit bedeutet und einige Vor- und Nachteile von binären Suchbäumen. Eine Kopie dieser Folien und Ressourcen Sie auf der Kurswebsite. Lassen Sie uns nun diese Datenstrukturen in Aktion sehen. In der nächsten Lektion werden wir die Verwendung und Implementierung dieser Datenstrukturen üben Verwendung und Implementierung dieser Datenstrukturen unsere beiden Lernziele für diesen Kurs angehen. 12. Trees: Willkommen zur abschließenden Übungsstunde für diesen Kurs. In dieser Lektion werden wir mit Bäumen üben, insbesondere mit der Suche nach Breite und Tiefe zuerst. Lass uns weitermachen und hier loslegen. Navigiere wie zuvor zu alvinwan.com/datastructures101/trees. Unter diesem Link sehen Sie so etwas. Leiten Sie das Projekt weiter, um Ihre eigene Kopie zu erhalten, klicken Sie auf den Projektnamen, klicken Sie auf die Ellipse und dann auf Fork. Ich schlage vor, Ihr Skillshare- und Antwortfenster nebeneinander zu platzieren , damit Sie beim Codieren mitprogrammieren können. Sie sollten dann einen Bildschirm wie diesen sehen, nach unten scrollen und Sie gelangen zur ersten Übung hier, die bfs oder breadth-First-Suche genannt wird. Ein Tipp, bevor wir beginnen, ist, sich zu merken wie man die Breath-First - und Depth-First-Suche durchführt, hier abgekürzt als bfs und dfs, weil es immer dasselbe sein wird. In diesem speziellen Fall verwenden wir keine Technik, die Rekursion genannt wird. Also ohne Rekursion sieht der Weg, bfs und dfs zu machen, immer so aus. diesem Wissen versuchen wir nun, die Suche nach Breite an erster Stelle zu implementieren. So sieht eine Suche nach der Breite zuerst durch einen Baum aus. Hier haben wir einen Baum, 1, 2, 3, 4. Lassen Sie mich gehen und veranschaulichen, wie das aussieht. Hier hättest du eins, und dann hättest du hier ein Kind , das zwei wären, und dann hättest du ein weiteres Kind genau dort, das wären drei. Dann hast du auch einen Baum wie diesen. So sieht der Baum aus. Du hättest im Grunde eine der Wurzeln, es ist das linke Kind wird zwei sein, und dann werden das linke Kind von zwei drei sein. Dann ist dies das richtige Kind von einem. So sieht der Baum aus. Zu wissen, dass dieser Baum so aussieht, hier sieht die Suche in der Breite zuerst aus. Die erste Breite gibt uns zuerst eine und dann zwei und dann vier und dann drei. Es ist Traversieren und es wird auch Level Order Traversal genannt, also bewegen wir uns hier im Grunde genommen die Stufen auf einmal nach unten, 1, 2, 4, 3. Wenn wir hier unten zum Beispiel fünf gehabt hätten, dann hätten wir 1, 2, 4, 3, 5 und so weiter und so fort. So sieht das aus. Wissen, dass dies jetzt eine Durchquerung der Breite an erster Stelle ist, ist hier ein Hinweis. Pausieren Sie das Video jetzt und versuchen Sie es selbst, wenn Sie den Hinweis nicht möchten. Der Hinweis ist, dass Sie eine der Datenstrukturen verwenden müssen eine der Datenstrukturen verwenden , über die wir zuvor gesprochen haben. Insbesondere müssen Sie dazu einen Stack oder eine Warteschlange verwenden . Machen Sie weiter und pausieren Sie das Video hier und versuchen Sie, den Algorithmus für eine Durchquerung der Breite zuerst zu entwerfen . Jetzt ist hier die Lösung. Die Lösung wird die Verwendung einer Warteschlange beinhalten. Was wir tun werden, ist in dieser Warteschlange, wir beginnen mit dem Hinzufügen des Roots. Sobald wir die Wurzel hinzugefügt haben, nehmen wir die Wurzel und stellen alle untergeordneten Elemente für diesen Stamm in die Warteschlange ein. Sobald wir das getan haben, entfernen wir uns aus der Warteschlange. Dequeuing wird uns hier den allerersten Punkt geben. Dann stellen wir all seine Kinder in die Warteschlange. Das werden drei sein und was auch immer ist ein Kind von zwei Jahren. Tatsächlich besteht eine Möglichkeit, dies zu tun , darin , eine Visualisierung einzubeziehen. Hier haben wir einen, wir werden einen aus der Warteschlange entfernen und wir werden alle seine Kinder zwei und vier hinzufügen. Dann werden wir zwei aus der Warteschlange entfernen und dann die Kinder in die Warteschlange stellen, die nur drei sind. Dann sagen wir mal, vielleicht gibt es noch einen, sagen wir mal, es sind fünf. Hier hätten wir drei und fünf. Dann würden wir endlich unsere nehmen. Dann vier stellten wir alle seine Kinder in die Warteschlange, was nichts ist und so weiter. Die Idee ist, dass Sie feststellen werden, dass die Reihenfolge des Dequeuings 1, 2, 4 war. Danach werden es drei und fünf sein, was genau das ist, was bfs ist. Die Warteschlange gibt uns genau eine Baumdurchquerung der Breite an erster Stelle. das wissen, wollen wir nun sehen, wie die Laufzeit in Bezug auf die Speicherkomplexität ist Pausiere das Video hier. Hier ist die Lösung. Die Laufzeit und Speicherkomplexität werden beide o of n sein. Sie denken vielleicht, was ist n hier? Nun, n ist die Anzahl der Knoten. Das ist wichtig. Vorher hatten wir diesen Fall, in dem wir eine Variable selbst definiert haben. Wir definieren eine Variable als eine Anzahl von eindeutigen Werten, die eingegeben werden. In diesem Fall ist diese Variable n standardmäßig für einen Baum ist diese Variable n die Anzahl der Knoten im Baum. Sie könnten es auch neu definieren, Sie könnten das n als die Höhe des Baums neu definieren oder Sie können es als etwas anderes definieren , aber normalerweise ist n die Anzahl der Knoten im Baum. wissen, dass Sie, wenn Sie Ihre Laufzeitspeicherkomplexität für einen Baum melden, in Bezug auf die Anzahl der Knoten angeben sollten, was für uns o von n sein wird, weil Sie darüber gehen müssen Jeder einzelne Knoten, das ist ein Breath-First-Traversal und zweitens müssen Sie all diese Knoten zu Ihrer Warteschlange hinzufügen. Irgendwann, tut mir leid, dass ich ein bisschen gelogen habe, wird Ihre Warteschlange niemals die maximale Breite des Baumes überschreiten. Wir werden später sehen, was Breite bedeutet, aber wir werden vorerst nur sagen, dass o von n die Speicherkomplexität ist. Wir können später mit dieser Nuance spielen, ich erkläre warum, es gibt eine bessere Antwort. Halten Sie das Video vorerst an und versuchen Sie , es zu codieren. Schreiben wir die Lösung auf. Hier definieren wir eine Warteschlange und der erste Wert der Warteschlange wird tatsächlich die Wurzel sein. Während etwas in der Warteschlange ist, werden wir aus der Warteschlange entfernt. Dann werden wir es erweitern, also stellen wir uns tatsächlich in die Warteschlange. Für jeden Zweig in den Zweigen des Baumes wir uns in die Warteschlange, also stellen Sie den Zweig in die Warteschlange. Dann drucken wir endlich den aktuellen Wert. Dies wird uns eine Durchquerung der Breite an erster Stelle geben. Lassen Sie uns nun überprüfen, ob dies unsere Tests besteht. Klicken Sie auf die grüne Schaltfläche Ausführen ganz oben und Sie können jetzt sehen , dass bfs nicht mehr in unserer Liste der fehlgeschlagenen Funktionen enthalten ist. Sie haben diesen Test erfolgreich bestanden. Gehen wir zur nächsten Frage über. Bevor wir zur nächsten Frage übergehen, ist ein Tipp, dass Sie immer neu verwenden sollten, warten Sie. Einer der Tipps ist , dass Sie immer eine Warteschlange für die Breite zuerst verwenden sollten . Das haben Sie hier als Beispiel gesehen. Du hast gesehen, wie man es benutzt. Behalte das im Hinterkopf. So wird es so ziemlich immer sein, wie Sie bfs ohne Rekursion implementieren. Wir werden in einem zukünftigen Kurs über Rekursion sprechen. diesem Wissen implementieren wir jetzt die Tiefensuche. Die Tiefensuche sieht wie folgt aus. Nehmen wir an, wir hatten diesen Baum und dieser Baum hat so ziemlich dieselbe Struktur. Es ist eigentlich eine etwas andere Struktur. Lassen Sie mich das für Sie herausarbeiten. Hier haben wir die Wurzel 1. Das linke Kind wird 2 und das linke Kind von 2 wird 3 sein. Dann sind keine anderen Kinder hier. Das richtige Kind von 1 wird 4 Jahre alt, genau hier. Dann wird das linke Kind von 4 5 sein . Hier werden wir zuschlagen. Eine Sache zu beachten ist, dass ich immer wieder sage, dass linkes und rechtes Kind darin Baumprobleme normalerweise um Ihren Binärbaum herum aufgebaut sind , wo Sie nur ein linkes und ein rechtes Kind haben. So wie wir hier einen Baum implementiert haben, können wir unbegrenzt viele Zweige haben. Du bist nicht auf Links und Rechts beschränkt. Technisch gesehen ist 5 kein linkes Kind von 4, 5 ist der einzige Zweig von 4. Ich habe gerade aus Gewohnheit links und rechts gesagt, aber das ist nicht wirklich wichtig. Der Punkt ist, dass der Baum ungefähr so aussieht. Wir haben 1 an der Wurzel , die Kinder sind 2 und 4 und dann ist das einzige Kind von 2 3, das einzige Kind von 4 ist 5. Wenn Sie das wissen, durchqueren Sie zuerst die Tiefe oder gehen Sie zuerst einen dieser Pfade entlang. So wie wir es implementiert haben, wird 1 zuerst diesen Weg hier hinuntergehen 4 und dann gehen wir zu 5 hinunter, bevor wir den ganzen Weg wieder hochkommen und andere Wege erkunden. So wird die Tiefendurchquerung zuerst aussehen. Es wird bis zu einem Blatt erforscht. Wiederum ist ein Blatt ein Knoten im Baum, der keine Kinder, keine Zweige hat. Es wird zuerst den ganzen Weg nach unten erkunden und dann wieder hochkommen und anfangen , andere Wege zu erkunden. So sieht eine Tiefendurchquerung aus. Du siehst hier unten wird es 1, 4, 5 gehen , und dann geht es wieder hoch und erforscht 2 und dann 3. Das ist eine Tiefendurchquerung. Lassen Sie uns versuchen, einen Algorithmus zu entwickeln. Pausiere das Video hier. Hier ist die Lösung. Für die Durchquerung der Tiefe zuerst haben wir zuvor eine Warteschlange für die Durchquerung der Breite zuerst verwendet. Für die Tiefendurchquerung verwenden wir einen Stack. Der Stack wird auf sehr ähnliche Weise funktionieren. Wir werden weiterhin Gegenstände zum Stapel hinzufügen und wir werden immer wieder vom Stapel springen. So wird es aussehen. Wenn ich mit 1 anfange, gehen wir zu Pop 1 und dann fügen wir die Kinder 2 und 4 hinzu. Jetzt, wo wir 2 und 4 haben, werden wir den letzten Gegenstand veröffentlichen, der 4 ist. Dann fügen wir seine untergeordneten Elemente hinzu, das sind 5. Dann gehen wir zu Pop 5. Sie können sehen, wohin das führt. Bisher haben wir 1, 4, 5 durchgemacht. Sobald wir mit 5 fertig sind, gehen wir zu Pop 2. Dies gibt uns unsere erste Tiefendurchquerung mit einem Stapel. wissen, dass dies der Algorithmus ist, sprechen wir über die Speicher - und Laufzeitkomplexität. Wie hoch ist die Laufzeit der Speicherkomplexität? Pausiere das Video hier und hier ist die Antwort. Die Speicherkomplexität wird hier linear in der Anzahl der Knoten sein. Für die Speicherkomplexität werden wir die Speicherkomplexität verdauen. Für die Rechenkomplexität werden wir eine lineare Rechenkomplexität haben, da wir durch all diese Knoten iterieren müssen. Dieselbe Einschränkung, die ich zuvor für die Speicherkomplexität erwähnt habe , ist eigentlich nicht linear in der Anzahl der Knoten, aber wir werden später mehr darüber sprechen. Lassen Sie uns nun versuchen, diese Funktion zu codieren. Pausiere das Video hier und probiere es aus. Lass uns weitermachen und diese Funktion programmieren. Hier in unserer Tiefen-First-Durchquerung beginnen wir mit der Initialisierung eines Stacks. In diesem Stack wird sich unser Root-Node befinden. Dann werden wir es wiederholen. Solange wir etwas im Stapel haben, springen wir dann vom Stapel. Dann für jeden einzelnen Zweig unseres aktuellen Knotens. Für Zweige in aktuellen Top-Branchen schieben wir diesen Zweig dann auf unseren Stapel und drucken dann unseren aktuellen Wert. Das ist so ziemlich alles für unsere Tiefendurchquerung. Lass uns weitermachen und überprüfen, ob das funktioniert. Drücken Sie ganz oben auf die grüne Schaltfläche „Ausführen“. Nun, dfs ist nicht mehr in unserer Fehlerliste , daher haben wir die DFS-Tests erfolgreich bestanden. Der Tipp hier ist, dass Sie einen Stapel für die Tiefendurchquerung verwenden sollten . Gehen wir nun zur nächsten Frage über. für jeden einzelnen Knoten in der Struktur Fügen Sie für jeden einzelnen Knoten in der Struktur ein neues Attribut namens parent hinzu, das auf seinen übergeordneten Knoten verweist. Hier werden wir bfs oder dfs für die übergeordnete Zuweisung verwenden. Dieses Attribut parent zeigt auf das übergeordnete Element des Knotens in der Struktur. Lass uns weitermachen und den Baum noch einmal zeichnen. Nehmen wir an, wir haben diesen Baum genau hier. Wir haben hier 1, und das einzige Kind ist 2 und 2 hat zwei Kinder , 3 und 4. Es wird ungefähr so aussehen und es gibt keine anderen Zweige, nur diese Leute. Jetzt, wo du das hast, werden wir die Eltern tatsächlich bevölkern. Wir werden dies so implementieren, dass jeder einzelne Baum hier auf sein übergeordnetes Element zurückzeigt. 2 zeigt also auf 1, 3 zeigt auf 2, 4 zeigt auf 2. das wissen, lassen Sie uns darüber nachdenken und einen Algorithmus dafür entwerfen . Pausiere das Video hier. Lassen Sie uns über die Lösung sprechen. Für diese Frage spielt es eigentlich keine Rolle, ob Sie bfs oder dfs verwenden. In beiden Fällen können Sie dafür sorgen, dass dies funktioniert. Im Grunde wird der Algorithmus sein, wir werden einige Traversal durchführen und wenn Sie oben schauen, haben wir tatsächlich über alle Kinder oder alle Zweige für ihren aktuellen Status iteriert alle Kinder oder alle Knoten. Während Sie diese Iteration durchführen, aktualisieren Sie diesen Zweig so, dass aktualisieren Sie diesen Zweig auf den aktuellen Knoten verweist, der der übergeordnete Knoten ist. Es spielt keine Rolle , welche Traversal Sie verwenden. Das ist der Algorithmus und in der Tat ist das so ziemlich die Codelösung. Überlegen Sie sich jetzt, was ist die Speicher- und Laufzeitkomplexität? Pausiere das Video hier. Genau wie zuvor, da wir DFS und BFS effektiv verwenden, die Laufzeit und die Rechenkomplexität dieselbe wie zuvor. Rechenkomplexität ist O von N und Speicherkomplexität, sagen wir vorerst, ist im Grunde O von N. Wenn wir das wissen, wollen wir jetzt weitermachen und den Code ausprobieren. Pausiere das Video hier und probiere es zuerst aus. Lassen Sie uns nun die Lösung durchgehen. Ich werde ein bisschen schummeln. Ich werde eigentlich nur von der vorherigen Funktion kopieren und einfügen, die bereits DFS ausgeführt hat. Lass uns weitermachen und das unten einfügen. Ich werde diese Druckanweisung entfernen und dann werde ich branch.parent gleich current zuweisen. Das war's. Lassen Sie uns überprüfen, ob diese Funktion korrekt ist. Wir drücken ganz oben auf den grünen „Run“ -Button . Da haben wir's. Wir können jetzt sehen, dass bevölkerte Eltern nicht mehr in der Liste der fehlgeschlagenen Tests stehen. Wir haben alle diese Tests erfolgreich bestanden. Wie ich bereits sagte, BFS, DFS, solange Sie sich diese Implementierung merken, werden Sie das sehr oft sehen. Zumindest ohne Rekursion wird es so aussehen, also stellen Sie sicher, dass Sie diese Implementierung kennen . Tipp von früher, berücksichtige immer auch leere Eingaben. In diesem speziellen Fall, wenn unser Baum leer wäre, würde unsere Funktion fehlschlagen , denn hier werden wir platzen , wir werden aktuell, aktuell wird keiner sein und dann gehen wir um zu versuchen, auf.branches davon zuzugreifen. Auch hier sollten Sie bei all Ihren Interviews und bei all Ihren Fragen überlegen, was Sie mit leeren Eingaben tun würden. In diesem speziellen Fall hätten wir sagen sollen, wenn Baum keiner ist, dann tun wir einfach nichts, kehren einfach zurück. Das ist wichtig im Hinterkopf zu behalten. Gehen wir zur nächsten Frage über. Hier verwenden wir BFS oder DFS, um den Maximalwert in einem extrem breiten, aber flachen Baum zu berechnen . Lassen Sie uns das Folgende in Betracht ziehen. Überlegen Sie, was dies für Ihre Wahl der Durchquerung bedeutet. Was ist die maximale Raumkomplexität der Breite-First-Suche oder der Tiefensuche? Geht schon weiter. Dies hängt tatsächlich mit der Nuance zusammen, über die wir zuvor gesprochen haben. Pausieren Sie das Video hier und überlegen Sie zuerst, bevor Sie den Algorithmus in Betracht ziehen können. Eigentlich, wie Sie den Algorithmus in Betracht ziehen. [LÄRM] Hier ist die nächste Frage. Wir werden den maximalen Wert von einem extrem breiten, aber flachen Baum erhalten. Der wichtige Teil dieser Frage besteht darin, zu überlegen, was ein extrem breiter, aber flacher Baum mit Ihrem Algorithmus macht. Warum ist das wichtig? Was bedeutet das für Ihre Wahl der Durchquerung? Wir haben vorhin über eine subtile Nuance über die Raum- oder Gedächtniskomplexität Ihrer Art der Durchquerung gesprochen . Diese Frage geht wirklich darauf ein, um zu verstehen, was diese Nuance für Ihren Algorithmus bedeutet. Pausieren Sie das Video hier und sehen Sie, ob Sie das herausfinden können. Was ist der Unterschied zwischen extrem breiten, aber flachen Baum und einem tiefen Baum, aber einem sehr schmalen Baum für BFS oder DFS? Pausiere das Video hier. Hier ist die Antwort. Für einen extrem breiten, aber flachen Baum sollten Sie DFS verwenden. Der Grund dafür ist, dass BFS oder Breath-First-Traversal, Platzkomplexität oder die maximale Anzahl von Elementen in der Warteschlange oder dem Stapel oder einer anderen Sammlung, die Sie verwenden, die Breite der Baum. Während für die Tiefensuche oder DFS der maximale Speicherplatz, den wir verwenden, der Tiefe des Baums entspricht. Wenn ich einen extrem flachen Baum habe, sollten Sie, dass DFS Ihren Platzverbrauch minimiert. Denn wenn Sie einen sehr flachen Baum haben, müssen Sie eine sehr kurze Sammlung berücksichtigen. wir das wissen, entwerfen wir den Algorithmus so , dass er den Maximalwert erhält. Pausieren Sie das Video hier und versuchen Sie, den Algorithmus zu entwerfen. Hier ist die Lösung. Für den Algorithmus nehmen wir einfach den Maximalwert an , wenn Sie einen Knoten durchqueren. Jedes Mal, wenn Sie einen Knoten durchqueren, vergleichen Sie den Wert dieses Knotens mit einem global größten Wert. Wenn Ihr aktueller Wert größer ist, ersetzen Sie ihn einfach. Dies ist dem Beispiel für die maximale Frequenz sehr ähnlich Beispiel für die maximale Frequenz das wir zuvor gesprochen haben. In diesem Fall betrachten wir den Maximalwert. Nachdem wir uns den Algorithmus angesehen haben, haben wir uns die Art des Durchlaufens angesehen. Halten Sie das Video hier an und prüfen Sie, ob Sie die Speicher- und Laufzeitkomplexität ermitteln können . Hier ist die Antwort. Was die Laufzeitkomplexität betrifft, ist es wiederum linear in der Anzahl der Knoten, es ist O von N. Für die Speicherkomplexität haben wir bereits darüber gesprochen, weshalb wir DFS für dieses Problem ausgewählt haben. Ihre Raumkomplexität oder Speicherkomplexität wird die Tiefe des Baums sein. Das ist nicht gerade O von N. Es ist auch, zumindest vorerst, nicht klar, wie tief der Baum genau sein würde. Wir weisen ihr einfach eine neue Variable zu. Wir sagen, D ist die Tiefe des Baums, also ist unser Algorithmus o von d. Jetzt versuchen wir es zu codieren. Pausiere das Video hier. Jetzt, da wir eine DFS-Implementierung haben, werde ich wieder faul sein. Ich kopiere es einfach und füge es ein. Scrollen wir hier zurück und dann kopieren wir diese DFS-Implementierung, und dann scrollen wir wieder nach unten und fügen sie ein. Jetzt, da wir diese DFS-Implementierung haben, werden wir die Druckanweisung tatsächlich entfernen. Wie bereits erwähnt, werden wir für jeden einzelnen Knoten , den wir in Betracht ziehen, den Maximalwert aktualisieren. Nehmen wir an, der Höchstwert am Anfang ist nur der Roots-Wert. Wenn wir dann jeden einzelnen Knoten untersuchen, nehmen wir das Maximum zwischen dem aktuellen Wert und dem größten Wert. Schließlich geben wir den Maximalwert zurück. Versuchen wir, diese Funktion auszuführen, und stellen Sie sicher, dass sie alle Tests besteht. Fahren Sie fort und klicken Sie ganz oben auf die grüne Schaltfläche „Ausführen“ . Da haben wir's. Wir können jetzt sehen, dass get maximum nicht mehr in der Liste der fehlgeschlagenen Tests steht. Gehen wir nun zur nächsten Frage über. Wir werden Datenstrukturen kombinieren, um die Baumbreite zu berechnen. Mal sehen, wie groß die Baumbreite ist. Die Breite des Baums ist die maximale Anzahl von Knoten in einer bestimmten Tiefe oder einer bestimmten Ebene. Lass uns diesen Baum nochmal kritzeln. Hier haben wir eins und eines hat ein Kind, das sind nur zwei, und zwei haben zwei Kinder, das sind drei und vier. Hier haben wir drei, und dann haben wir vier. So sieht dieser Baum aus. Die Breite dieses Baums beträgt dann zwei , da diese Ebene zwei Elemente enthält. Wenn wir jetzt noch einen hier hätten, sagen wir, wir hätten fünf, und dann hätten wir noch einen, sechs, sagen wir, dann wäre die Breite dieses Baumes drei, weil Sie höchstens drei haben auf einer einzigen Ebene. das wissen, lasst uns weitermachen und aktualisieren. Lassen Sie uns den Algorithmus besprechen. Machen Sie weiter und halten Sie das Video hier an und sehen Sie, wie Sie die Breite dieses Baums berechnen würden . Hier ist eine Lösung. Zum Glück haben wir einen Traversalmodus, der es für uns viel einfacher macht , die Tiefe zu berechnen, die Level Order Traversal oder BFS sein wird. Es gibt zwei Möglichkeiten , dies zu tun. Zum einen können Sie eine andere Datenstruktur verwenden , um zu verfolgen wie viele Knoten sich in einer bestimmten Tiefe befinden. Das zweite ist das modifizierte BFS, sodass Sie jeweils ein Level durchqueren und im Grunde verfolgen , wann Sie das nächste Level beginnen . Das sind Ihre beiden Optionen. Das sind zwei verschiedene Algorithmen. Wir werden die erste implementieren, bei der wir eine andere Datenstruktur verwenden, wenn Sie ein Mapping verwenden. Nachdem Sie darüber gesprochen haben, sprechen wir über die Laufzeit und die Speicherkomplexität. Pausieren Sie das Video hier und sehen Sie, ob Sie es herausfinden können. Hier ist die Antwort. Aus Gründen der Laufzeitkomplexität werden beide Algorithmen, unabhängig davon, welchen Sie ausgewählt haben, unabhängig davon, welchen Sie ausgewählt haben, O von N sein, wobei n die Anzahl der Knoten ist. Beide Berechnungskomplexitäten sind linear in der Anzahl der Knoten. Wenn Sie sich nun dafür entscheiden, mehrere Datenstrukturen zu verfolgen, bei denen Sie ein Mapping von der Tiefe bis zur Anzahl beibehalten , wird dies eine zusätzliche Raumkomplexität von O of D in der Tiefe sein . Sie werden linear in der Tiefe sein, weil Sie für jede einzelne Tiefe einen Wert speichern. Wenn Sie jedoch den Algorithmus verwenden, bei dem Sie BFS an Ort und Stelle ändern, wäre Ihr Algorithmus O der Breite. Beide Algorithmen haben mindestens O Breite, denn die Grundlinie ist genau so, wie das ursprüngliche BFS funktioniert. Der Algorithmus, der zusätzlich eine HashMap verwendet, um dieses Mapping von Tiefe zu Zählung zu speichern, wird jedoch zusätzlich eine HashMap verwendet, um dieses Mapping von Tiefe zu Zählung zu speichern , wird O der Breite plus O der Tiefe sein. Wir werden die weniger effiziente implementieren, was rückwärts erscheinen würde, aber ich habe die effiziente bereits in der Lösung implementiert , sodass Sie das überprüfen können, wenn Sie möchten die effizientere Umsetzung. Aber im Moment werde ich die weniger effiziente implementieren. Machen Sie weiter und pausieren Sie das Video hier und prüfen Sie, ob Sie den Code selbst erstellen können. Hier ist unsere Lösung. Lass uns weitermachen und schreiben. Wir werden hier unsere bevorzugte Datenstruktur verwenden. Wir verwenden ein Standardwörterbuch. Wir beginnen mit der Initialisierung einer Warteschlange. Jedes einzelne Element in dieser Warteschlange wird zusätzlich zum Knoten selbst eine Tiefe des aktuellen Knotens sein Knoten selbst eine Tiefe des aktuellen Knotens , da wir die Tiefe verfolgen müssen, um zu wissen, welche Anzahl in unserem Zähler ist. von Breiten, die tatsächlich aktualisiert werden sollen. Hier ist unser Zähler, Breiten, und er wird alle Breiten auf Null initialisieren. Während wir etwas in der Warteschlange haben, werden wir die Warteschlange entfernen und dann das Widths-Wörterbuch aktualisieren, indem wir diesen Wert um eins erhöhen. Schließlich werden wir für jeden Zweig die um eins inkrementierte Tiefe und den Knoten selbst in die Warteschlange stellen . Jedes Mal, wenn Sie vom Elternteil zum Kind wechseln, erhöhen wir die Tiefe um eins. Schließlich geben wir die maximale Breite zurück, die wir sehen. Dies gibt uns die maximale Breite und alle Breiten , die wir hochgezählt haben. Lassen Sie uns das jetzt ausführen und sicherstellen, dass alle unsere Tests bestanden wurden. Da haben wir's. Mangelnde Ausgabe bedeutet, dass alle unsere Doc-Tests bestanden haben. Du hast endlich alle Übungen für diese Lektion abgeschlossen . Für all diese Folien, Komplettlösungen und weitere Übungsübungen besuchen Sie unbedingt diese Website. Das ist es für unsere Praxis für Bäume, Breite versus Tiefe versus Durchquerung. Sie haben hier eine Menge Probleme gesehen. Sie haben auch in früheren Übungsstunden eine Reihe von Problemen gesehen . Ich hoffe, dass diese sehr nützlich waren. Gehen wir nun zu unserer allerletzten Lektion , in der wir das alles abschließen. 13. Schlussbemerkung: Herzlichen Glückwunsch zum Abschluss des Kurses. Wir haben eine Menge Themen behandelt, Wachstumsreihenfolgen, Stapel und Warteschlangen, verknüpfte Listen, Hashmaps und Bäume. Das war ein ziemlich schwieriger Kurs mit einer Menge Inhalt und einigen wirklich brutalen Fragen. Du hast 20 Übungen in der Tiefe abgeschlossen, 20 Interviewtipps und übst die dreistufige Methode, um immer wieder Rätsel zu lösen . Das war eine Menge Inhalt und die Zusammenfassungsfolien sind großartige Werkzeuge zum Auswendiglernen. Die wichtigste Erkenntnis ist jedoch der dreistufige Prozess. Interviews angeht, ist das der Schlüssel. Hier ist der dreistufige Prozess einmal: Entwerfen Sie Algorithmen ohne Code, meldet Laufzeit- und Speicherkomplexitäten und codieren Sie schließlich den Algorithmus. Um Ihr Verständnis für drei Tage nach dem Kurs wirklich zu festigen , entwerfen Sie jeden Tag eine Datenstrukturfrage. Ich ermutige Sie auch, Ihre Frage im Diskussionsbereich zu stellen. Sie brauchen nur etwas Schlaf zwischendurch, wenn Sie über Datenstrukturen nachdenken. Es muss nicht die beste Arbeit deines Lebens sein. Jede Frage wird beantwortet und im Diskussionsbereich veröffentlicht . Ich freue mich wirklich darauf, sie zu sehen und das war's. Fantastische Arbeit. Dies ist der Beginn Ihrer Interviewvorbereitung und Sie stehen auf einem starken Fundament. meinem Skillshare finden Sie weitere Kurse zu Data Science, maschinellem Lernen und anderen wichtigen Programmierkonzepten. Für mehr Interviewvorbereitung folgen Sie mir auf Skillshare, um weitere Updates zu erhalten, da wir häufiger verwendete Programmierrätsel und Interviews mit spezifischen Tricks behandeln häufiger verwendete Programmierrätsel werden. Nochmals herzlichen Glückwunsch, dass Sie es bis zum Ende des Kurses und bis zum nächsten Mal geschafft haben.