Make the biggest and smallest numbers
Haskell, 162 bytes



l=length
((m,f)%n)s|n&gt;=l s=[]|n&gt;0,(p,c:r)&lt;-span(/=m(f$take(n+1)s))s=c:((m,id)%(n-l p)$r)|1&gt;0=s
(x#y)z=[p%z$show=&lt;&lt;[x..y]|p&lt;-[(maximum,id),(minimum,filter(&gt;'0'))]]


Try it online!

Uses the algorithm described by jafe. Might be shorter to use a less efficient method, but this was more fun to write :)

The % operation takes 4 arguments (actually 3, but whatever): m which is a function that selects the "optimal" member from a list (either maximum or minimum depending on what we want); f which is a "filter" function; n the number of digits left to drop; and s the string. First we check whether n is equal to the number of digits left in the string (I used &gt;= for safety) and drop the rest of s if so. Otherwise we check if we still need to drop digits (n&gt;0), then we use span to break our string into three pieces: p the digits to drop, c the optimal reachable digit, and r the remaining string. We do this by passing span a predicate that checks for equality against our optimal digit. To find that digit we take the first n+1 digits of the string, filter it, then pass it to our "chooser" function. Now we just yield our optimal digit and recur, subtracting the length of p (the number of digits dropped) from n. Notice that we do not pass our filtering function to the recursive call, and, instead, we replace it with id. That's because the filter is only there to avoid selecting leading 0s in the minimum case which is only relevant on the first iteration. After that we no longer need any filter.

% is really only a helper function for # which is our "real" function, taking x, y, and z. We use a list comprehension just to avoid a bit of repetition, iterating over our function tuples and passing them to % along with z and the concatenated string. That string is created using the magic monad operator (=&lt;&lt;) which, in this context, works like concatMap.