Q:

Como atualizar a RecyclerView de forma simples e eficiente?

Das formas que conheço para atualizar uma RecyclerView a mais eficiente é recorrer aos métodos do adapter  

notifyItemMoved
notifyItemRangeChanged
notifyItemRangeInserted
notifyItemRangeRemoved

Eles, ao contrário de notifyDataSetChanged(), apenas provocam o refresh dos itens visíveis a que se referem(aqueles que foram movidos/alterados/inseridos/removidos), sendo, por isso, mais eficiente.
O seu uso é simples quando as alterações são feitas "à vez", basta chamar o método correspondente à alteração feita.
A dificuldade surge quando várias alterações são feitas de uma só vez, por exemplo, quando se recebe uma nova lista, actualizada, proveniente de uma chamada a um serviço, o que me leva a utilizar notifyDataSetChanged().  
Neste último caso, como posso fazer a atualização recorrendo aos métodos notifyItemXXX()?

A:

Na revisão 24.2.0 da Support Library, uma das actualizações foi a adição da classe DiffUtil.
Ela possibilita calcular a diferença entre duas colecções e obter um objecto do tipo DiffUtil.DiffResult que contém uma lista de operações de actualização para serem aplicadas a um RecyclerView.Adapter.  
As operações são aplicadas recorrendo ao método DiffResult#dispatchUpdatesTo() que internamente usa os métodos notifyItemXXX() para notificar o adapter.
DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(callback); 
diffResult.dispatchUpdatesTo(adapter);

DiffUtil precisa obter algumas informações sobre a colecção antiga e nova, como o tamanho e como comparar itens. Essas informações são obtidas usando o DiffUtil.Callback passado ao método DiffUtil.calculateDiff().
A implementação do callback depende do tipo que a colecção guarda.
Exemplo de implementação:  
public class ProdutoDiffCallback extends DiffUtil.Callback{

    List<Produto> oldProdutos;
    List<Produto> newProdutos;

    public ProdutoDiffCallback(List<Produto> newProdutos, List<Produto> oldProdutos) {
        this.newProdutos = newProdutos;
        this.oldProdutos = oldProdutos;
    }

    @Override
    public int getOldListSize() {
        return oldProdutos.size();
    }

    @Override
    public int getNewListSize() {
        return newProdutos.size();
    }

    @Override
    public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
        return oldProdutos.get(oldItemPosition).id == newProdutos.get(newItemPosition).id;
    }

    @Override
    public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
        return oldProdutos.get(oldItemPosition).equals(newProdutos.get(newItemPosition));
    }

}

A actualização do RecyclerView pode ser implementada, num método do Adapter, da seguinte forma:  
public void updateList(ArrayList<Produto> newProdutos) {
    DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new ProdutoDiffCallback(this.Produtos, newProduto));
    this.Produtos = newProdutos
    diffResult.dispatchUpdatesTo(this);
}

