One of the handiest commands in emacs is
repeat-complex-command (shortcut: M-x ESC ESC). It opens in the minibuffer the lisp formulation of the last command you ran, for example:
Redo: (query-replace "j" "k" nil nil nil)
That way, if you just want to redo that thing you did, hit return. If you want to slightly modify it -- change "k" to "m", for example -- just edit the command before pressing return. Not very interesting in the "k" to "m" case, but if you run
query-replace-regexpand then realize you got one tiny thing wrong in a huge regular expression, well, you can see that would be useful.
Sometimes the thing that comes up in the minibuffer isn't something worth repeating:
Redo: (find-file-other-window "~/.emacs" 1)
In that case, just use the minibuffer history (M-p, M-n) to get to the command you're looking for.
Gotcha: I scratched my head for a few minutes today wondering why nothing happened when I re-ran a
replace-string. If your replace command is restricted to a region, the bounds of that region are present in the lisp command. So when you try to redo it, it redoes it for your old region -- not the current one. Best case for that is a no-op; worst case is changing a region you didn't want to. Be careful when redoing any command that might apply only to the region.