趁着闲暇之时整理一些题目出来,一起看看吧。
1、计算字符串中出现的所有目标字符的下标。比如输入:‘my name is dorsey’和’m’,输出:[0, 5]
看到这道题,可能第一反应就是利用indexOf。如:
const getIndexOfString0 = (str, target) => {
let res = [], i = str.indexOf(target);
while( i > -1 ) {
res.push(i);
i = str.indexOf(target, i + target.length);
}
return res;
}
console.log( getIndexOfString0('my name is dorsey', 'm') );
当然也可以通过最原始的方式,即遍历取值比对。
const getIndexOfString = (str, target) => {
let res = [];
for(let i = 0; i < str.length; i ++) {
if(str[i] === target) {
res.push(i);
}
}
return res;
}
getIndexOfString('my name is dorsey', 'm');
2、不使用排序,求一个乱序数组的最大和,比如输入 [1, 2, 3, 4, 5, 6, 7] 输出 7 + 6 = 13
可能看到这个,首次的思路就是遍历数组取出最大的数,再移除最大的那个数,并将剩余的数组再一次的遍历取大者,就像下面这样:
const largestSum1 = arr => {
let max = -Infinity,
secondMax = -Infinity;
arr.forEach(item => max = max > item ? max : item);
arr.splice(arr.indexOf(max), 1);
arr.forEach(item => secondMax = secondMax > item ? secondMax : item);
return `${max} + ${secondMax} = ${max + secondMax}`;
}
但这个时候面试官可能还想考察的是你在做大小比对时的灵活运用,而这里刚好是可以利用Math对象的功能,就像下面这样。
const largestSum0 = arr => {
let max = Math.max.apply(-Infinity, arr);
arr.splice(arr.indexOf(max), 1);
let secondMax = Math.max.apply(-Infinity, arr);
return `${max} + ${secondMax} = ${max + secondMax}`;
}
console.log(largestSum0([1, 2, 3, 4, 5, 6, 7]));
假如此时max函数是Array.prototype中的一个方法,是不是应用起来挺方便的?但实际上并没有,所以需要做一层映射,很巧的就在于apply的第二个参数刚好是一个数组,当然,将数组解构变成Math.max.call(-Infinity, …arr)也是可以的。在参数较少时,用call的性能会更好一些。
3、对一个只会出现英文字母且不重复的字符串,求其缺失的字母,忽略大小写。比如字符串:‘abcdefghijkopqrstxyz’,就缺少了l、m、n跟u、v、w,输出此时的缺失字母。
看到这个可能第一反应会事先构建一个完整从a~z的字符串数组,并遍历数组,查看是否存在于字符串中,这里我们虽然也是这样做,但却是通过一种更加巧妙的方式构建我们的字符串数组,并利用filter这个强大的过滤利器来完成我们的功能。
其实,这里呢,字母或者是chat型的字符,无论是js也好,其他语言也好,都有一样的一个字典,也可能是枚举类,它们都遵循一样的unicode编码。而我们想要的这个从a-z的编码刚好是97 ~ 122,看看下图:
而这时我们就会想,既然有这样的对应关系,那我创建一个99到122的数组并通过这样的对应关系再映射回来,不就是一个完整的a~z了吗?看看:
那再将原本的数组遍历,换成filter的过滤,这样这道题对于我们来说就简单多了,也优雅多了。看看代码:
const getLostLetter = str => {
let arr = Array.from({length: 26}, (item, index) => String.fromCharCode(index + 97));
str = str.toLowerCase();
return arr.filter(item => str.indexOf(item) === -1);
}
getLostLetter('Abcdefgijklopqrstxyz');
4、任务管理
假如现在有这样的一个情况:
一个任务队列中,有任务优先级为高中低三种任务,按高中低的优先级在数组中排序。后续插入的任务也有高中低,需要将后续插入的任务放到对应的级别末尾,并且不能混乱到该任务队列。
队列的结构可能是这样的:
let arr = [
{
rank: '高',
taskID: '6b8ca06e'
},
{
rank: '高',
taskID: '7dac2d24'
},
{
rank: '中',
taskID: '668a451a'
},
{
rank: '低',
taskID: '6ad40d38'
}
]
假如此时再来一条任务,优先级为高,其实这时候要做的就是将这条任务插入到高优先级的最末尾。那怎么知道最末尾在哪,完整的数组在插入后是如何重新续上的呢?一起看看。
const taskManage = (arr, newTask) => {
let rank = ['高', '中', '低'];
let rankIndex = rank.indexOf(newTask.rank);
let newArr = arr.filter(item => {
for(let i = 0; i <= rankIndex; i ++) {
if(item.rank === rank[i]) {
return item.rank;
}
}
});
arr = [...newArr, newTask, ...arr.splice(newArr.length)];
return arr;
}
return taskManage(arr, {
rank: '高',
taskID: '128a451a'
});
5、移动 0。一个数组里面有若干个 0,将这些 0 移至数组的开头,并保持其他值的相对位置不变。
比如下面这样的数组:[1, 0, 2, 7, 0, 8, 0, ‘12’],最终需要输出:[0, 0, 0, 1, 2, 7, 8, ‘12’]。
其中一个很简单也很暴力的做法就是,既然有0,和其他,那将数组做两次过滤,一次过滤出0来,一次过滤出其他的,由于filter并不会改变数组值的相对位置,刚好符合要求,比如下面这样:
const moveZero0 = arr => arr.filter(item => item === 0).concat(arr.filter(item => item !== 0));
console.log( moveZero0([1, 0, 2, 7, 0, 8, 0, '12']) );
这第二种方式,其实就是规规矩矩的拿0放开头,像这样:
const moveZero = arr => {
for(let i = 0; i < arr.length; i ++) {
if(arr[i] === 0) {
arr.unshift(...arr.splice(i, 1));
}
}
return arr;
}
moveZero([1, 0, 2, 7, 0, 8, 0, '12']);
每天跳出环境,学点新东西,一年后你会发现不一样的你。