In der Serie "Named Problems" stelle ich dir klassische, namentlich bekannte Algorithmus-Probleme vor, die seit Jahrzehnten in Ausbildung, Studium und Interviews verwendet werden. Anhand verständlicher Problemstellungen wie FizzBuzz, Türme von Hanoi oder dem Sieb des Eratosthenes lernst du hier, algorithmisch zu denken, Lösungsstrategien zu entwickeln und diese sauber in Java umzusetzen.
Das Monty-Hall-Problem ist ein bekanntes Wahrscheinlichkeitsrätsel aus einer Spielshow: Du wählst eine von drei Türen, hinter einer ist ein Gewinn, hinter zwei ist nichts.
Berühmt ist es, weil es sich “falsch” anfühlt: Viele denken, nach dem Öffnen einer falschen Tür sei es 50/50. Tatsächlich ist Wechseln statistisch besser.
In diesem Artikel lernst du zwei Dinge: erstens die Idee dahinter (ohne Formelkram), und zweitens, wie du das Ganze mit Java per Simulation greifbar machst.
Am Ende kannst du das Problem erklären, typische Denkfehler vermeiden und eine kleine Simulation schreiben, die das Ergebnis eindeutig zeigt.
Problemstellung
Du bist in einer Spielshow. Es gibt drei Türen:
- Hinter einer Tür ist ein Gewinn.
- Hinter zwei Türen ist kein Gewinn.
- Du wählst zuerst eine Tür (ohne zu wissen, was dahinter ist).
- Der Moderator kennt die Lösung und öffnet dann eine andere Tür mit “kein Gewinn”.
- Du darfst danach bleiben oder wechseln.
Die Frage ist: Solltest du wechseln?
Mini-Beispiel (eine mögliche Runde):
- Gewinn liegt hinter Tür 2.
- Du wählst Tür 1.
- Moderator öffnet Tür 3 (kein Gewinn).
- Du wechselst auf Tür 2 und gewinnst.
Die Idee dahinter
Der Knackpunkt ist: Der Moderator öffnet nicht zufällig irgendeine Tür. Er vermeidet den Gewinn und öffnet immer eine Niete.
Beim ersten Tipp hast du:
- Mit Wahrscheinlichkeit 1/3 direkt den Gewinn getroffen.
- Mit Wahrscheinlichkeit 2/3 eine Niete getroffen.
Und jetzt kommt der Trick:
- Wenn du anfangs richtig lagst (1/3), dann ist Wechseln schlecht (du würdest wegwechseln).
- Wenn du anfangs falsch lagst (2/3), dann “schiebt” der Moderator die komplette Restwahrscheinlichkeit auf die eine übrige geschlossene Tür.
Merke: Bleiben gewinnt im Schnitt etwa 1/3, Wechseln etwa 2/3.
Schritt für Schritt zur Lösung
- Modelliere die Türen: Wir nummerieren sie als
0,1,2. - Platziere den Gewinn zufällig: Eine Zahl von
0bis2ist die Gewinntür. - Der Spieler wählt zufällig: Ebenfalls
0bis2. - Der Moderator öffnet eine Niete: Er wählt eine Tür, die weder die Spielertür noch die Gewinntür ist.
- Entscheidung: Entweder bleibt der Spieler oder wechselt auf die einzig übrige geschlossene Tür.
- Auswerten: Gewonnen, wenn die finale Wahl gleich Gewinntür ist.
- Wiederholen: Viele Runden spielen (z.B.
100000) und Gewinnquoten vergleichen.
Java-Implementierung (Variante A: straightforward)
import java.util.Random;
public class MontyHallStraightforward {
private static final int DOORS = 3;
public static void main(String[] args) {
int rounds = 100_000;
double stayRate = simulate(rounds, false);
double switchRate = simulate(rounds, true);
System.out.println("Rounds: " + rounds);
System.out.println("Stay win rate: " + formatPercent(stayRate));
System.out.println("Switch win rate: " + formatPercent(switchRate));
}
private static double simulate(int rounds, boolean shouldSwitch) {
Random random = new Random();
int wins = 0;
for (int i = 0; i < rounds; i++) {
int prizeDoor = random.nextInt(DOORS);
int firstPick = random.nextInt(DOORS);
int openedByHost = openDoorByHost(random, prizeDoor, firstPick);
int finalPick = firstPick;
if (shouldSwitch) {
finalPick = remainingClosedDoor(firstPick, openedByHost);
}
if (finalPick == prizeDoor) {
wins++;
}
}
return (double) wins / rounds;
}
private static int openDoorByHost(Random random, int prizeDoor, int firstPick) {
int[] candidates = new int[DOORS - 1];
int idx = 0;
for (int door = 0; door < DOORS; door++) {
if (door != firstPick && door != prizeDoor) {
candidates[idx++] = door;
}
}
if (idx == 1) {
return candidates[0];
}
return candidates[random.nextInt(idx)];
}
private static int remainingClosedDoor(int firstPick, int openedByHost) {
for (int door = 0; door < DOORS; door++) {
if (door != firstPick && door != openedByHost) {
return door;
}
}
throw new IllegalStateException("No remaining door found");
}
private static String formatPercent(double rate) {
return String.format("%.2f%%", rate * 100.0);
}
}
Diese Variante ist bewusst direkt: eine Schleife, pro Runde alles neu auslosen, dann gewinnen zählen. Wichtig ist die Methode openDoorByHost(...): Der Moderator darf weder die erste Wahl noch die Gewinntür öffnen. Das ist der ganze Zauber der Aufgabenstellung.
Mit remainingClosedDoor(...) machen wir das Wechseln eindeutig: Bei drei Türen bleibt genau eine Tür übrig. Das ist leicht zu lesen und schwer zu verwechseln.
Java-Implementierung (Variante B: Alternative oder Verbesserung)
import java.util.SplittableRandom;
public class MontyHallIdiomatic {
private static final int DOORS = 3;
public static void main(String[] args) {
int rounds = 200_000;
Result stay = runExperiment(rounds, Strategy.STAY);
Result swch = runExperiment(rounds, Strategy.SWITCH);
System.out.println("Rounds: " + rounds);
System.out.println("Stay wins: " + stay.wins() + " (" + percent(stay.winRate()) + ")");
System.out.println("Switch wins: " + swch.wins() + " (" + percent(swch.winRate()) + ")");
}
enum Strategy {
STAY, SWITCH
}
record Result(int wins, int rounds) {
double winRate() {
return (double) wins / rounds;
}
}
private static Result runExperiment(int rounds, Strategy strategy) {
SplittableRandom random = new SplittableRandom();
int wins = 0;
for (int i = 0; i < rounds; i++) {
if (playOneRound(random, strategy)) {
wins++;
}
}
return new Result(wins, rounds);
}
private static boolean playOneRound(SplittableRandom random, Strategy strategy) {
int prizeDoor = random.nextInt(DOORS);
int firstPick = random.nextInt(DOORS);
int openedByHost = pickHostDoor(random, prizeDoor, firstPick);
int finalPick = (strategy == Strategy.SWITCH)
? otherClosedDoor(firstPick, openedByHost)
: firstPick;
return finalPick == prizeDoor;
}
private static int pickHostDoor(SplittableRandom random, int prizeDoor, int firstPick) {
int count = 0;
int a = -1;
int b = -1;
for (int door = 0; door < DOORS; door++) {
if (door == firstPick) {
continue;
}
if (door == prizeDoor) {
continue;
}
if (count == 0) {
a = door;
} else {
b = door;
}
count++;
}
if (count == 1) {
return a;
}
return random.nextBoolean() ? a : b;
}
private static int otherClosedDoor(int firstPick, int openedByHost) {
return 3 - firstPick - openedByHost;
}
private static String percent(double rate) {
return String.format("%.2f%%", rate * 100.0);
}
}
Hier ist die Struktur stärker in kleine Bausteine getrennt: playOneRound(...) ist eine saubere “eine Runde”-Funktion, und die Strategie ist als enum klar benannt. Das ist angenehm, wenn du später erweitern willst.
Die Methode otherClosedDoor(...) nutzt einen kleinen Trick: Bei den Türen 0, 1, 2 ist die Summe 0+1+2 = 3. Wenn zwei Türen bekannt sind, ist die dritte einfach 3 - a - b. Kurz, lesbar und ohne Schleife.
Beispiel: Eingabe und Ausgabe
Beispiel 1: Du lässt das Programm laufen mit rounds = 100000.
Mögliche Ausgabe:
Stay win rate: 33.19%Switch win rate: 66.81%
Beispiel 2: Du erhöhst auf rounds = 1000000.
Moegliche Ausgabe:
Stay win rate: 33.34%Switch win rate: 66.66%
Beispiel 3: Du nutzt Variante B mit rounds = 200000.
Mögliche Ausgabe:
Stay wins: 66815 (33.41%)Switch wins: 133185 (66.59%)
Typische Fehler
- Moderator öffnet zufällig irgendeine Tür: Wenn er aus Versehen die Gewinntür öffnen könnte, ist es nicht mehr das Monty-Hall-Problem.
- Falsche Kandidatenliste für die Moderator-Tür: Du musst sowohl die erste Wahl als auch die Gewinntür ausschließen.
- Wechseln auf eine bereits geöffnete Tür: Klingt trivial, passiert aber, wenn “übrige Tür” nicht sauber berechnet wird.
- Off-by-one bei
nextInt:nextInt(3)liefert0bis2. Nicht bis3. - Zu wenige Runden: Bei
rounds = 30sieht alles nach Zufall aus. Nimm lieber100000oder mehr. - Ergebnis falsch interpretiert: Eine einzelne Ausgabe ist keine Garantie. Wichtig ist, dass sich die Quoten bei mehr Runden stabilisieren.
- Begriffe vermischen: “erste Wahl”, “geöffnete Tür”, “finale Wahl” sauber unterscheiden, sonst baust du unbemerkt Logikfehler ein.
Fazit
Du hast gesehen, warum das Monty-Hall-Problem so verwirrend wirkt: Der Moderator liefert Information, ohne es direkt so aussehen zu lassen. Genau dadurch bleibt die Wahrscheinlichkeit nicht bei “50/50”, sondern Wechseln landet langfristig bei etwa 2/3. Mit der Simulation in Java kannst du das Ergebnis selbst nachmessen, statt es nur zu glauben. Nebenbei übst du saubere Methoden, klare Parameter und Auswertungen.
