Counting with str2.split(letter).length takes linear time on the size of the string, so it’s not efficient to repeat it for each distinct character in the input (even if that still results in a linear overall runtime, strictly speaking, since Unicode is limited in size). Here’s an accurate implementation of the solution Mike Brant described, since your rewrite that creates a map still does the slow count:
const getCounts = iterable => {
const result = new Map();
for (const x of iterable) {
result.set(x, (result.get(x) ?? 0) + 1);
}
return result;
};
const scramble = (available, target) => {
const remaining = getCounts(available);
for (const c of target) {
const count = remaining.get(c);
if (!count) {
return false;
}
remaining.set(c, count - 1);
}
return true;
};
An alternative solution with decent performance is to compare sorted versions of the strings:
const scrambleSorted = (available, target) => {
let i = -1;
for (const c of target) {
i = available.indexOf(c, i + 1);
if (i === -1) {
return false;
}
}
return true;
};
const scramble = (available, target) =>
scrambleSorted([...available].sort(), [...target].sort());
(This would be the one with constant auxiliary space if you were allowed to mutate the input, but JavaScript doesn’t have mutable strings.)
Also, note that strings are iterables of codepoints, but string functions like split, which predate iterables, operate on UTF-16 code units. So anything that mixes iteration (e.g. new Set(str), [...s], Array.from(s), for (c of s)) won’t work with split-based counting.
Or, when the number of distinct characters is small and even contiguous, you can use an array:
const scramble = (available, target) => {
if (target.length > available.length) {
return false;
}
const remaining = Array(26).fill(0);
for (let i = 0; i < available.length; i++) {
const c = available.charCodeAt(i) - 97;
remaining[c]++;
}
for (let i = 0; i < target.length; i++) {
const c = target.charCodeAt(i) - 97;
if (--remaining[c] === -1) {
return false;
}
}
return true;
};