Right now, the protocol doesn't allow requesting or sending partial blocks, but they are working on it. That will allow even lighter clients.
* Well, you need rollback history too.
If you had such a lighter client that could request partial blocks, it seems that it would be able to verify the existence of a transaction input (e.g., by checking the Merkle branch and making sure the hashes work) but is there any way that it could verify that the input hadn't already been spent (i.e., in some block that the client doesn't have a copy of)?
Nope. As you point out, it doesn't have access to that information, so by definition, it can't say anything about it. For that, you need something else.
I personally prefer having full nodes that provide that service (probably for a fee) to users. I had a lengthy chat on IRC the other day about his proposal to have a fraud alert system, where allegedly honest nodes would shout if a transaction was a double spend, but I wasn't a big fan. You could also extend the protocol to allow nodes to request data from a peer's UTXO (unspent transaction output) set, and have your client ask several peers just to be sure.
I'm sure there are other ways too.