Several bases but not twice the same digit Input

A non-empty array of positive integers.

Task

Convert each integer to either binary, octal, decimal or hexadecimal in such a way that each digit (0 to F) is used at most once.

Output

The list of bases that were used to solve the puzzle.

Detailed example

The expected output for [ 16, 17 ] is [ octal, decimal ].

Here is why:


We can't simply use decimal for both numbers, because they both contains a 1.
16 cannot be converted to binary, because its representation in this base (10000) contains several 0's.
17 cannot be converted to binary either, because its representation in this base (10001) contains several 0's and  several 1's.
17 cannot be converted to hexadecimal, because its representation in this base (11) consists of two 1's.
Let's consider all remaining possibilities:

               +---------+---------+--------+
               | oct(16) | dec(16) | hex(16)|
               | = 20    | = 16    | = 10   |
+--------------+---------+---------+--------+
| oct(17) = 21 | 20,21   | 16,21   | 10,21  |
| dec(17) = 17 | 20,17   | 16,17   | 10,17  |
+--------------+---------+---------+--------+


The only possible solution is to convert 16 in octal (20) and to keep 17 in decimal (17). This way, the digits 0, 1, 2 and 7 are used exactly once.


Clarifications and rules


The input is guaranteed to lead to a unique solution. Your code is not supposed to support arrays that give several solutions or no solution at all.
You may output the bases in any reasonable format, such as [ "bin","oct","dec","hex" ], [ 'b','o','d','h' ], "BODH", [ 2,8,10,16 ], [ 0,1,2,3 ] etc. But it should be clearly explained in your answer.
The order of the bases in the output must match the order of the input integers.
If that helps, you may assume that the input is sorted from lowest to highest, or from highest to lowest.
This is code-golf, so the shortest answer in bytes wins!


Test cases

You do not have to output the conversion results listed below. They are purely informational.

Input                                  | Output          | Conversion result
---------------------------------------+-----------------+------------------------
[ 119 ]                                | O               | 167
[ 170 ]                                | D               | 170
[ 64222 ]                              | H               | FADE
[ 16, 17 ]                             | O/D             | 20/17
[ 14, 64, 96 ]                         | H/H/D           | E/40/96
[ 34, 37, 94 ]                         | O/D/H           | 42/37/5E
[ 2, 68, 82 ]                          | B/D/H           | 10/68/52
[ 22, 43, 96 ]                         | O/O/O           | 26/53/140
[ 3639, 19086, 57162 ]                 | H/D/H           | E37/19086/DF4A
[ 190, 229, 771 ]                      | O/H/O           | 276/E5/1403
[ 2, 44, 69, 99 ]                      | B/H/H/H         | 10/2C/45/63
[ 75, 207, 218, 357, 385 ]             | H/H/H/D/O       | 4B/CF/DA/357/601
[ 12, 28, 46, 78, 154, 188, 222, 240 ] | D/O/O/D/H/H/H/H | 12/34/56/78/9A/BC/DE/F0


The raw input list is available here.
JavaScript (Node.js),192,155,154,152,151,145,136,113,99,92 90 bytes

thanks to @Arnauld for reminding me that i can return [0,1,2,3] -&gt; which is [2,8,10,16] saves 8 bytes, and for the brilliant idea (which help to reduce by 23+ bytes)
thanks to @Kevin Cruijssen for reducing by 1 bytes


f=([c,...a],t=&quot;&quot;)=&gt;c?[1,4,5,8].find(b=&gt;T=!/(.).*\1/.test(n=t+c.toString(b*2))&amp;&amp;f(a,n))+T:a

Try it online!
Explanation:
[c,...a] - @Arnauld trick for taking one item at a time
c?***:&quot; &quot; -&gt;if c is undefined we managed to get to the end result- [] - if i would put &quot;&quot; than the find would not considered that legit. ([]+5=&quot;5&quot; JS FTW)
[1,4,5,8].find every time we find the correct base (the output will be of this array (1,4,5,8)-&gt;(2,8,10,16) its legit.
now how the find works -&gt; if it finds something it returns the element (1-8)
and than i add the result of the inner solution. if it doesnt find then it returns undefined + T is now false -&gt; NaN which in the parent call will be considered false
!/(.).*\1/.test(n=t+b) determine if the string has duplicates, if so:
f(a,n)) just go the the next number(a is now array.slice(1)) with the new string(n)
we assign result to T(temp) of the result because find stops when it finds and so we know that the last result will be f() which is result B