Gegeben n
Ganzzahlen, gibt es eine O(n)
o O(n log n)
Algorithmus, der den maximalen Wert eines mathematischen Ausdrucks berechnen kann, der durch Einfügen der Operatoren erhalten werden kann -
, +
, *
und Klammern zwischen den angegebenen Zahlen? Nehmen Sie nur binäre Varianten der Operatoren an, also kein unäres Minus, außer vor dem ersten Element, falls erforderlich.
Zum Beispiel, wenn -3 -4 5
können wir den folgenden Ausdruck bilden (-3) * (-4) * 5
, dessen Wert 60
und maximal möglich.
Hintergrund:
Ich bin vor einiger Zeit beim Studium genetischer Algorithmen über dieses Problem gestolpert und habe gelernt, dass es ziemlich einfach mit einem klassischen genetischen Algorithmus gelöst werden kann. Dieser läuft jedoch langsam, und er ist nur in der Theorie einfach, da der Code in der Praxis ziemlich hässlich wird (Auswertung des Ausdrucks, Überprüfung der korrekten Platzierung von Klammern usw.). Außerdem ist nicht garantiert, dass wir das absolute Maximum finden.
All diese Unzulänglichkeiten genetischer Algorithmen brachten mich dazu, mich zu fragen: Gibt es, da wir uns nicht um Divisionen kümmern müssen, eine Möglichkeit, dies mit einem klassischeren Ansatz, wie dynamischer Programmierung oder einer gierigen Strategie, effizient zu tun?
Aktualisierung:
Hier ist ein F#-Programm, das die von @Keith Randall vorgeschlagene DV-Lösung zusammen mit meiner Verbesserung implementiert, die ich in einem Kommentar zu seinem Beitrag geschrieben habe. Es ist sehr ineffizient, aber ich behaupte, dass es polynomiell ist und kubische Komplexität hat. Es läuft in ein paar Sekunden für ~50-Element-Arrays. Es wäre wahrscheinlich schneller, wenn es vollständig imperativ geschrieben wäre, da wahrscheinlich viel Zeit für den Aufbau und das Durchlaufen von Listen verschwendet wird.
open System
open System.IO
open System.Collections.Generic
let Solve (arr : int array) =
let memo = new Dictionary<int * int * int, int>()
let rec Inner st dr last =
if st = dr then
arr.[st]
else
if memo.ContainsKey(st, dr, last) then
memo.Item(st, dr, last)
else
match last with
| 0 -> memo.Add((st, dr, last),
[
for i in [st .. dr - 1] do
for j in 0 .. 2 do
for k in 0 .. 2 do
yield (Inner st i j) * (Inner (i + 1) dr k)
] |> List.max)
memo.Item(st, dr, last)
| 1 -> memo.Add((st, dr, last),
[
for i in [st .. dr - 1] do
for j in 0 .. 2 do
for k in 0 .. 2 do
yield (Inner st i j) + (Inner (i + 1) dr k)
] |> List.max)
memo.Item(st, dr, last)
| 2 -> memo.Add((st, dr, last),
[
for i in [st .. dr - 1] do
for j in 0 .. 2 do
for k in 0 .. 2 do
yield (Inner st i j) - (Inner (i + 1) dr k)
] |> List.max)
memo.Item(st, dr, last)
let noFirst = [ for i in 0 .. 2 do yield Inner 0 (arr.Length - 1) i ] |> List.max
arr.[0] <- -1 * arr.[0]
memo.Clear()
let yesFirst = [ for i in 0 .. 2 do yield Inner 0 (arr.Length - 1) i ] |> List.max
[noFirst; yesFirst] |> List.max
let _ =
printfn "%d" <| Solve [|-10; 10; -10|]
printfn "%d" <| Solve [|2; -2; -1|]
printfn "%d" <| Solve [|-5; -3; -2; 0; 1; -1; -1; 6|]
printfn "%d" <| Solve [|-5; -3; -2; 0; 1; -1; -1; 6; -5; -3; -2; 0; 1; -1; -1; 6; -5; -3; -2; 0; 1; -1; -1; 6; -5; -3; -2; 0; 1; -1; -1; 6; -5; -3; -2; 0; 1; -1; -1; 6; -5; -3; -2; 0; 1; -1; -1; 6;|]
Ergebnisse:
1000
6
540
2147376354
Das letzte ist höchstwahrscheinlich ein Fehler aufgrund eines Überlaufs, ich versuche nur zu zeigen, dass ein relativ großer Test zu schnell läuft, um exponentiell zu sein.