Monty Hall simulation with any number of doors
I would have used an helper function in the first version so that your monty_hall function is:

def monty_hall(amount, doors=3):
    if doors &lt; 3:
        raise ValueError(f'doors must be greater than three, not {doors}')

    return sum(player_wins_a_car(doors) for _ in range(amount))


This also applies to the second version but removes some of the speed optimizations you made there; so not sure if it’s worth keeping.

However, in the first version, I would use list-comprehensions to reduce a bit the memory needed. It would also exhibit the actions more like a real player would do. Since creating the set from range will already iterate over each elements, using the list-comp won't be much more costy:

import random

def player_wins_a_car(doors):
    player_choice = random.randrange(doors)
    car_placement = random.randrange(doors)
    shown_door = random.choice([door for door in range(doors)
                                if door not in (player_choice, car_placement)])
    final_choice = random.choice([door for door in range(doors)
                                  if door not in (player_choice, shown_door)])

    return final_choice == car_placement


For the second version, if you were to keep two functions that could lead to more explanatory stuff like:

def player_wins_a_car(doors):
    # Choose a door that hide a car. Car is never behind door 0,
    # if it were behind door 0, consider door 0 and 1 to be
    # swapped so that host always open door 0.
    car_placement = random.randrange(doors) or 1
    # Consider an initial choice of door `doors - 1` and host
    # showing door 0 unconditionaly. Always choosing a random
    # door out of these two.
    final_choice = random.randrange(1, doors-1)

    return car_placement == final_choice


I’m using or here as the only value triggering max would be 0, not sure which one performs better.