Maximales Subarray-Problem in Java

1. Übersicht

Das maximale Subarray-Problem besteht darin, die Reihe zusammenhängender Elemente mit der maximalen Summe in einem bestimmten Array zu finden.

Im folgenden Array hat das hervorgehobene Subarray beispielsweise die maximale Summe (6):

In diesem Tutorial werden zwei Lösungen zum Ermitteln des maximalen Subarrays in einem Array vorgestellt . Eines davon werden wir mit O (n) Zeit- und Raumkomplexität entwerfen .

2. Brute-Force-Algorithmus

Brute Force ist ein iterativer Ansatz zur Lösung eines Problems. In den meisten Fällen erfordert die Lösung eine Reihe von Iterationen über eine Datenstruktur. In den nächsten Abschnitten wenden wir diesen Ansatz an, um das maximale Subarray-Problem zu lösen.

2.1. Ansatz

Im Allgemeinen besteht die erste Lösung darin, die Summe aller möglichen Subarrays zu berechnen und die mit der maximalen Summe zurückzugeben.

Zunächst berechnen wir die Summe aller Subarrays, die bei Index 0 beginnen. In ähnlicher Weise finden wir alle Subarrays, die bei jedem Index von 0 bis n-1 beginnen, wobei n die Länge des Arrays ist:

Wir beginnen also bei Index 0 und addieren jedes Element zur laufenden Summe in der Iteration. Wir werden auch die maximale Summe verfolgen, die bisher gesehen wurde . Diese Iteration wird auf der linken Seite des obigen Bildes angezeigt.

Auf der rechten Seite des Bildes sehen wir die Iteration, die bei Index 3 beginnt . Im letzten Teil dieses Bildes haben wir das Subarray mit der maximalen Summe zwischen Index 3 und 6 .

Allerdings wird unser Algorithmus weiterhin alle Sub - Arrays auf Indizes , die zwischen dem finden 0 und n-1 .

2.2. Implementierung

Lassen Sie uns nun sehen, wie wir diese Lösung in Java implementieren können:

public int maxSubArray(int[] nums) { int n = nums.length; int maximumSubArraySum = Integer.MIN_VALUE; int start = 0; int end = 0; for (int left = 0; left < n; left++) { int runningWindowSum = 0; for (int right = left; right  maximumSubArraySum) { maximumSubArraySum = runningWindowSum; start = left; end = right; } } } logger.info("Found Maximum Subarray between {} and {}", start, end); return maximumSubArraySum; }

Wie erwartet aktualisieren wir die maximumSubArraySum, wenn die aktuelle Summe größer als die vorherige maximale Summe ist. Insbesondere aktualisieren wir dann auch den Start und das Ende , um die Indexpositionen dieses Subarrays herauszufinden .

2.3. Komplexität

Im Allgemeinen iteriert die Brute-Force-Lösung viele Male über das Array, um jede mögliche Lösung zu erhalten. Dies bedeutet, dass die von dieser Lösung benötigte Zeit quadratisch mit der Anzahl der Elemente im Array wächst. Dies ist möglicherweise kein Problem für Arrays kleiner Größe. Mit zunehmender Größe des Arrays ist diese Lösung jedoch nicht effizient.

Durch Überprüfen des Codes können wir auch feststellen, dass zwei for- Schleifen verschachtelt sind. Daher können wir schließen, dass die zeitliche Komplexität dieses Algorithmus O (n2) ist .

In den späteren Abschnitten werden wir dieses Problem in O (n) -Komplexität mithilfe dynamischer Programmierung lösen .

3. Dynamische Programmierung

Dynamische Programmierung löst ein Problem, indem es in kleinere Teilprobleme unterteilt wird. Dies ist der Lösungstechnik für Divide-and-Conquer-Algorithmen sehr ähnlich. Der Hauptunterschied besteht jedoch darin, dass die dynamische Programmierung ein Teilproblem nur einmal löst.

Es speichert dann das Ergebnis dieses Teilproblems und verwendet dieses Ergebnis später erneut, um andere verwandte Teilprobleme zu lösen. Dieser Vorgang wird als Memoisierung bezeichnet .

3.1. Kadanes Algorithmus

Der Kadane-Algorithmus ist eine beliebte Lösung für das maximale Subarray-Problem. Diese Lösung basiert auf dynamischer Programmierung.

Die wichtigste Herausforderung bei der Lösung eines dynamischen Programmierproblems besteht darin, die optimalen Teilprobleme zu finden .

3.2. Ansatz

Lassen Sie uns dieses Problem anders verstehen:

Im obigen Bild nehmen wir an, dass das maximale Subarray an der letzten Indexposition endet. Daher beträgt die maximale Summe der Subarrays:

maximumSubArraySum = max_so_far + arr[n-1]

max_so_far ist die maximale Summe eines Subarrays, die am Index n-2 endet . Dies ist auch im obigen Bild dargestellt.

Jetzt können wir diese Annahme auf jeden Index im Array anwenden. Zum Beispiel kann die maximale Subarray-Summe, die bei n-2 endet, wie folgt berechnet werden:

maximumSubArraySum[n-2] = max_so_far[n-3] + arr[n-2]

Daraus können wir schließen:

maximumSubArraySum[i] = maximumSubArraySum[i-1] + arr[i]

Da jedes Element im Array ein spezielles Subarray der Größe eins ist, müssen wir auch prüfen, ob ein Element größer als die maximale Summe selbst ist:

maximumSubArraySum[i] = Max(arr[i], maximumSubArraySum[i-1] + arr[i])

Wenn wir uns diese Gleichungen ansehen, können wir sehen, dass wir die maximale Subarray-Summe an jedem Index des Arrays finden müssen. Daher haben wir unser Problem in n Teilprobleme unterteilt. Wir können die maximale Summe an jedem Index finden, indem wir das Array nur einmal iterieren:

Das hervorgehobene Element zeigt das aktuelle Element in der Iteration. Bei jedem Index wenden wir die zuvor abgeleitete Gleichung an, um einen Wert für max_ending_here zu berechnen . Dies hilft uns zu identifizieren, ob wir das aktuelle Element in das Subarray aufnehmen oder ein neues Subarray ab diesem Index starten sollen .

Another variable, max_so_far is used to store the maximum subarray sum found during the iteration. Once we iterate over the last index, max_so_far will store the sum of the maximum subarray.

3.3. Implementation

Again, let's see how we can now implement the Kadane algorithm in Java, following the above approach:

public int maxSubArraySum(int[] arr) {       int size = arr.length;     int start = 0;     int end = 0;       int maxSoFar = 0, maxEndingHere = 0;       for (int i = 0; i  maxEndingHere + arr[i]) {             start = i;             maxEndingHere = arr[i];         } else             maxEndingHere = maxEndingHere + arr[i];           if (maxSoFar < maxEndingHere) {             maxSoFar = maxEndingHere;             end = i;         }     } logger.info("Found Maximum Subarray between {} and {}", start, end);     return maxSoFar; }

Here, we updated the start and end to find the maximum subarray indices.

3.4. Complexity

Since we only need to iterate the array once, the time complexity of this algorithm is O(n).

So as we can see, the time taken by this solution grows linearly with the number of elements in the array. Consequently, it is more efficient than the brute force approach we discussed in the last section.

4. Conclusion

In diesem kurzen Tutorial haben wir zwei Möglichkeiten beschrieben, um das maximale Subarray-Problem zu lösen.

Zuerst untersuchten wir einen Brute-Force-Ansatz und stellten fest, dass diese iterative Lösung zu einer quadratischen Zeit führte. Später diskutierten wir dann den Kadane-Algorithmus und verwendeten dynamische Programmierung, um das Problem in linearer Zeit zu lösen.

Wie immer ist der vollständige Quellcode des Artikels auf GitHub verfügbar.