BUG: df.replace with numeric values and str to_replace
- [x] closes #34789
- [x] tests added / passed
- [x] passes `black pandas`
- [x] passes `git diff upstream/master -u -- "*.py" | flake8 --diff`
- [x] whatsnew entry

We also avoid copies by not calling `self.as_array` and instead moving the mask-finding to the block level.
Looks like we have both a doctest and prose in missing_data.rst saying the current behavior (which this PR calls a bug) is intentional:

```
        Note that when replacing multiple ``bool`` or ``datetime64`` objects,
        the data types in the `to_replace` parameter must match the data
        type of the value being replaced:

        >>> df = pd.DataFrame({'A': [True, False, True], 'B': [False, True, False]})
        >>> df.replace({'a string': 'new value', True: False})  # raises
        Traceback (most recent call last):
            ...
        TypeError: Cannot compare types 'ndarray(dtype=bool)' and 'str'

        This raises a ``TypeError`` because one of the ``dict`` keys is not of
        the correct type for replacement.
```

Under this PR, the example in the doctest returns

```
       A      B
0  False  False
1  False  False
2  False  False
```

which strike me as better behavior. I don't think the documented behavior is desirable, and I read it as more of a "hey this is a limitation of the replace implementation".

So I'm OK with changing behavior here as a "bugfix with behavior changing implications". very nice @jbrockmendel +1 on adding array_algos and *using* them from the blocks