There is my solution which I'm proud of ;-)

http://pastie.caboo.se/144083 (solution)
http://pastie.caboo.se/144089 (rspec)

def make_change(amount, coins=[25, 10, 5, 1])
   
    # Does the change, recursively with a kind of
    # alpha-beta pruning (whitout the beta)
    # Best default value for alpha is +Infinity as
    # alpha represents the size of the actual found
    # solution, it can only decrease with the time.
    def make_change_r(amount, coins, alpha)
        # the coin with its solution
        # that has to be the shortest one
        best_coin = nil
        solution = []
       
        # The good coin exists (win!)
        if coins.include? amount
            best_coin = amount
       
        # Only one coin stand (avoids a lot of recursion)
        elsif coins.length == 1
            coin = coins[0]
            unless coin > amount or amount % coin != 0
                # Do not construct a solution if this one
                # is bigger than the allowed one (alpha).
                size = amount/coin - 1
                if size <= alpha
                    best_coin = coin
                    solution = [best_coin] * size
                end
            end
       
        # No solution can be found (odd coins and even amount)
        elsif amount % 2 === 1 and \
                coins.select{|coin| coin % 2 != 0}.length === 0
            # pass
           
        # Alpha(-beta) pruning:
        # Do not look to this subtree because another
        # shorter solution has been found already.
        elsif alpha > 1 and
           
            coins.select{|c| c >= amount/alpha}.each do |coin|
                # only give a subset of the coins, the bigger ones
                # have been tested already.
                found = make_change_r( \
                    amount-coin, \
                    coins.select {|c| c <= coin and c <= amount-coin}, \
                    alpha-1)
               
                # Check if the solution (if there is any) is good enough
                if not found.nil? and \
                        (solution.length === 0 or solution.length >
found.length)
                   
                    best_coin = coin
                    solution = found
                    alpha = solution.length
                end
            end
        end
       
        return best_coin.nil? \
            ? best_coin \
            : [best_coin] + solution
    end
   
    # Any money or coins to give back?
    if not amount.nil? and amount > 0 and not coins.nil?
       
        # make sure the coins are ready to be used
        coins.sort!
        coins.uniq!
        coins.reverse!
   
        infinity = 1.0/0
        return make_change_r(amount, coins, infinity)
    else
        return []
    end
end

And the RSpec I used to test it:

describe "make_change" do
    it "should work with US money" do
        make_change(39).should == [25, 10, 1, 1, 1, 1]
    end
   
    it "should work with alien money" do
        make_change(14, [10, 7, 1]).should == [7, 7]
    end
   
    it "should work with non solvable solution" do
        make_change(0.5).should == nil
    end
   
    it "should now when not to give money back" do
        make_change(0).should == []
    end
   
    it "should agree with dh and Marcelo" do
        make_change(1000001, [1000000, 1]).should == [1000000, 1]
        make_change(10000001, [10000000, 1]).should == [10000000, 1]
        make_change(100000001, [100000000, 1]).should == [100000000, 1]
       
        make_change(1000001, [1000000, 2, 1]).should == [1000000, 1]
    end

    it "should not be naive (by James)" do
        make_change(24, [10, 8, 2]).should == [8, 8, 8]
       
        make_change(11, [10, 9, 2]).should == [9, 2]
    end

    it "should have a good pruning" do
        make_change(19, [5, 2, 1]).should == [5, 5, 5, 2, 2]
        make_change(39, [5, 2, 1]).should == [5, 5, 5, 5, 5, 5, 5, 2, 2]
    end
   
    it "should work with Swiss paper money" do
        money = [1000,500,200,100,50,20,10,5,2,1]
        make_change(1789, money).should == [1000,500,200,50,20,10,5,2,2]
    end
   
    it "should be nice to tho_mica_l" do
        money = [97, 89, 83, 79, 73, 71, 67, 61, 59, 53, 47, 43, 41, 37,
31, 29, 23, 19, 17, 13, 11, 7, 5, 3]
        make_change(101, money).should == [89, 7, 5]
       
        make_change(4563, money).should == [97, 97, 97, 97, 97, 97, 97,
97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97,
97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97, 97,
97, 97, 97, 89, 7, 5]
    end

    it "should fail with a combination trick (vsv)" do
        make_change(2**10-1, (1..10).map{|n| 2**n}).should == nil
        make_change(2**100-1, (1..100).map{|n| 2**n}).should == nil
    end
end

with good performances ;-)

..........

Finished in 0.095362 seconds

10 examples, 0 failures

It's basically a min-max algorithm (min only) with alpha-beta pruning
(alpha only). An example with (24, [10, 8, 2])

24, [10, 8, 2], alpha=Infinity
    24: tested coin 10 -> 14, [10, 8, 2], Infinity
       14: tested coin 10 -> 4, [2], Infinity
          4: one coin stands -> return [2, 2]
       14: tested coin 8 -> 6, [2], alpha=2 (previous solution (at this
level) is [10, 2, 2] -> length - 1)
          6: one coin stands and we need 3 coins which is bigger than
alpha, fail! -> return nil
    24: tested coin 8 -> 16, [8, 2], alpha=3 (previous solution is [10,
10, 2, 2])
       16: tested coin 8 -> 8, [8, 2], alpha=2
          8: amount is a known coin, win! -> return [8]
       16: tested coin 2 -> 14, [2], alpha=2
          14: one coin stands and we need 7 coins which is bigger than
alpha, fail! -> return nil
    24: tested coin 2 -> 22, [2], alpha=2 (previous solution is [8,8,8])
       22: one coin stands but we need 11 coins which is far bigger than
alpha, fail! -> return nil

I had a lot of fun with this one coz it was my first play with rspec and
ruby -r profile to spot the weak parts.

Cheers,

-- Yoan