"Three pitchers with water" problem
Some comments on your code:


def initialize max, current, target. The community consensus seems to be to put always parenthesis on method definitions. I'd definitely do it.
return @current == @max. It's not idiomatic to put explicits return, the last expression of a block/method is returned.
MAX_STEPS = 12. Isn't this kind of cheating? 
raise 'max.length must equal init.length' if max.length != init.length. I wouldn't lose a second validating inputs. If the input data is wrong it's the caller's problem.
[Lots of imperative code which, frankly, I don't understand] When you are dealing with mathematics-related problems (some would argue all computation is a mathematics-related problem) you'll get better algorithms with functional programming.


I'd write it with a depth-first algorithm in functional style. Note that I've created two generic abstractions, Enumerable#map_detect (Facets has it) and Array#merge, so the main algorithm remains as clean and declarative as possible. Granted, it may be difficult to grasp at first if you have only experience with imperative programming, ask if you don't understand something (also, reviews of the review welcomed):

require 'facets/enumerable/map_detect'

class Array
  # [:a, :b, :c, :d].merge_from_hash(1 =&gt; :bb, 3 =&gt; :dd) #=&gt; [:a, :bb, :c, :dd]
  def merge_from_hash(hash)
    map.with_index { |x, idx| hash.fetch(idx, x) }
  end
end

class PitchersProblem &lt; Struct.new(:total)
  def solve(current, final, solution = [])
    if current == final
      solution + [current]
    else
      (candidates(current) - solution).map_detect do |candidate|
        solve(candidate, final, solution + [current])
      end
    end
  end

  def candidates(current)
    (0...total.size).to_a.permutation(2).map do |i1, i2|
      free2 = total[i2] - current[i2]
      if current[i1] &gt; 0 &amp;&amp; free2 &gt; 0
        moved = [current[i1], free2].min
        current.merge_from_hash(i1 =&gt; current[i1] - moved, i2 =&gt; current[i2] + moved)
      end
    end.compact
  end
end

p PitchersProblem.new([10, 7, 3]).solve([10, 0, 0], [5, 5, 0])
#=&gt; [[10, 0, 0], [3, 7, 0], [0, 7, 3], [7, 0, 3], [7, 3, 0], 
#    [4, 3, 3],  [4, 6, 0], [1, 6, 3], [1, 7, 2], [8, 0, 2], 
#    [8, 2, 0],  [5, 2, 3], [5, 5, 0]]


Notes on my code:


solve can be read as: "If the current state is the final, add it to the path solution and we are done. Otherwise, for each permutation of the pitchers, move water from one to the other if possible, and try to find a solution recursively (unless that state is already in our solution path)".
This algorith returns the first solution it gets (size 13), which is not the optimal (size 10?), that's because it's a depth-first algorithm, you can transform it into a breadth-first algorithm to get the shortest solution first.
Ideally solution should be an insertion-ordered data-structure with O(1) lookup (because of solution.include?(new_current)), something like an OrderedSet. An array has O(n) lookup, but anyway, let's leave it just to keep it simple.