盘点面试中可能遇到的手写算法题(非力扣题型和常规手写题)

567 阅读8分钟

开篇提醒:不是手写防抖、节流,手写call、apply、bind等内容!!!!!

阿sir的一个朋友的朋友面试了字节,遇到了两个算法题,朋友的朋友面试完以后,来找朋友讨论,朋友再来找阿sir讨论。阿sir于是乎便整理了一下这些面试中容易出现的手写算法题,并且也自己手动实现了一遍,大家可以参考一下,如果有不同见解欢迎在评论区中指出,不胜感激!

话不多说,先上题目,难度不尽相同,有兴趣的朋友可以先自己尝试做一遍。

题目:

1. 判断子对象是否存在父对象中(字节原题)

注意:子对象中的属性必须存在父对象中,并且层级要对应(并非一定层级相同)

// 父对象
const father = {
    a: 1,
    b: 'name',
    c: {
        d: 1,
        e: '100',
        f: {
            g: 22,
            h: 23,
            j: 'xc',
            i: {
                c: [1,2,4, [1,2,3], {
                    name: 'xxx'
                }],
                k: 22
            },
        },
        b: {
            a: 'xxxxx',
        },
    }
}

// 子对象
const child1 = {
    a: 1
}
const child2 = {
    a: 1,
    b: 'name',
}
const child3 = {
    a: 1,
    b: 'name',
    c: 100,
}
const child4 = {
    i: {
        c: [1,2,4, [1,2,3], {
            name: 'xxx'
        }],
        k: 22
    },
}
const chil5 = {
    c: [1,2,4, [1,2,3], {
        name: 'xxx'
    }],

}
const child6 = {
    f: {
        g: 22,
        h: 23,
        j: 'xc',
        i: {
            c: [1,2,4, [1,2,3], {
                name: 'xxx'
            }],
            k: 22
        },
    },
    b: {
        a: 'xxxxx',
    },
}
isCheck(child1, father) // true
isCheck(child2, father) // true
isCheck(child3, father) // false
isCheck(child4, father) // true
isCheck(child5, father) // true
isCheck(child6, father) // true

2. 对象扁平化 (字节原题)

const obj = {
  a: 0,
  b: [1, 2, { e: 3 }, 5],
  c: {
    e: 2,
    i: 1
  },
  g: null
}

// 实现一函数能将数据转换为
// {
//   'a': 0,
//   'b[0]':1,
//   'b[1]':2,
//   'b[2].e':3,
//   'b[3]':5,
//   'c.e':2,
//   'c.i':1,
// }

3. 红绿灯问题

模拟实现红绿灯,从0开始,第3秒输出红灯,第5秒输出绿灯,第6秒输出黄灯,第9秒输出红灯...

4. 异步任务队列控制

实现一个Scheduler,满足如下要求

const scheduler = new Scheduler(2)  // 传入并发最大限制
scheduler.addTask(1, '1');   // 1s后输出'1'
scheduler.addTask(2, '2');  // 2s后输出'2'
scheduler.addTask(3, '3');  // 4s后输出'3'
scheduler.addTask(4, '4');  // 6s后输出'4'
scheduler.start();

5. 洗牌算法,如何验证这个洗牌算法可以把牌洗得足够乱

经典洗牌算法,给定一个[1,2,3,4,5,6,7,8,9,10],随机打乱

6. 实现 ob 和 watch 方法,希望当方法传入 watch 函数时会执行一次,之后每次修改 data 上的属性时,会触发对应的 console

会vue的同学应该是手拿把掐

  const data = ob({ count: 0, foo: 'test' });

  watch(() => {
      console.log('watch-count', data.count);
  });
  watch(() => {
      console.log('watch-foo', data.foo);
  });

  data.count += 1;
  console.log('showcount', data.count);
  delete data.count;
  data.foo = 'test2';

7. 构建树

其中根id为0

let arr = [
    {id: 1, name: '部门1', pid: 0},
    {id: 2, name: '部门2', pid: 1},
    {id: 3, name: '部门3', pid: 1},
    {id: 4, name: '部门4', pid: 3},
    {id: 5, name: '部门5', pid: 4},
]
输出结果
[
    {
        "id": 1,
        "name": "部门1",
        "pid": 0,
        "children": [
            {
                "id": 2,
                "name": "部门2",
                "pid": 1,
                "children": []
            },
            {
                "id": 3,
                "name": "部门3",
                "pid": 1,
                "children": [
                    // 结果 ,,,
                ]
            }
        ]
    }
]

8. vue模板替换功能实现

const data = {
    name: '222',
    age: 18,
    happy: {
        ball: '333',
        game: '33333'
    },
    arr: ['444444'],
    flag: true,
    sayHello: function (msg) {
        return msg
    }
}


let temStr = 'hello my name is {{ name }}, age is {{ age }}, my happy is {{ happy.ball }} and {{ happy.game }}, i like {{ arr[0] }}, end {{ flag ? sayHello("232") : sayHello("2323") }}'

9. 解析Url中的请求参数

随便放一个百度的url

www.baidu.com/s?ie=utf-8&…

10. 使用requestAnimation实现setInterVal

不错,大家没有看错,不再是setTimeout模拟了,setTimeout模拟已经是人人必备的一项技能了,所以我们需要掌握使用requestAnimation实现setInterVal。

11. 实现Promise.all、Promise.race、Promise.allSettled

如果对这几个方法不是很了解的童鞋,可以先看看MDN(developer.mozilla.org/zh-CN/docs/…)

解析:

以下的代码,均是sir根据自己的理解写的,当中可能会有一些不太合理的地方,欢迎各位童鞋提出其他看法,或者指出其中存在的问题。

1. 判断子对象是否存在父对象中

const father = {
    a: 1,
    b: 'sdad',
    c: {
        d: 1,
        e: '100',
        f: {
            g: 22,
            h: 23,
            j: 'xc',
            i: {
                c: [1,2,4, [1,2,3], {
                    name: 'xxx'
                }],
                k: 22
            },
        },
        b: {
            a: 'xxxxx',
        },
    }
}

const child = {
    c: [1,2,4, [1,2,3], {
        name: 'xxx'
    }],

}

const isCheck = (obj1, obj2) => {
    // 先判断obj1和obj2的类型
    if (obj1 instanceof Object && obj2 instanceof Object) { // 如果两个都是对象
        const aKeys = Object.keys(obj1);
        const bKeys = Object.keys(obj2);
        let flag = true; // 表示正常执行完while
        let currResult = true; // 记录当前层级是否匹配,默认是true
        while (aKeys.length) {
            const aKey = aKeys.pop()
            if (bKeys.includes(aKey)) { // 如果是包含关系
                //先判断类型
                if (typeof obj1[aKey] !== typeof obj2[aKey]) { // 如果两个类型不一致,那父对象当前层级一定不满足
                    flag = false;
                    break;  // 跳出当前循环
                }
                if (obj1[aKey] instanceof Object && obj2[aKey] instanceof Object) { // 如果类型是相同的,.并且都是对象
                    // 递归比对该键对应的值
                    currResult = currResult && isCheck(obj1[aKey], obj2[aKey]); // 只有当下一个层级是包含关系,返回true,
                } else { // 不都是对象,那么直接判断是否相等
                    if (obj1[aKey] !== obj2[aKey]) { // 如果该键对应的值不相同,那么说明父对象当前层级一定不满足,直接跳出(同一级对象不能拥有相同的键值)
                        flag = false
                        break;
                    }
                    // 相同不需要做任何处理
                }
            } else { // 如果不是包含关系
                flag = false;
                break
            }
        }
        if (!flag) { // flag为false表示是通过break跳出的while循环
            let result = false;
            const bObjectKeys = getObjectKeys(obj2); // 当层对象肯定不满足,找到所有值为object类型的键,每个都去比对是否相同
            for (let key of bObjectKeys) { // 只要找到一个包含关系,那么就说明是包含
                result = result || isCheck(obj1, obj2[key]);
            }
            return result
        } else { // 正常while遍历完成
            // 直接返回currResult
            return currResult
        }
    } else { // 如果两个不都是对象,那么直接返回是否相等
        return obj1 === obj2
    }
}
const getObjectKeys = (obj) => {
    return Object.keys(obj).reduce((curr, key) => {
        if (obj[key] instanceof Object) {
            curr.push(key)
        }
        return curr
    }, [])
}

console.log(isCheck(child, father))

2. 对象扁平化

const obj = {
    a: 0,
    b: [1, 2, { e: 3 }, 5, [1]],
    c: {
        e: 2,
        i: 1
    },
    g: null,
    d: {
        dsad: 'xxx',
        sds: 'vc',
        dsd: [1, 2, 3, 2, [2, 3, [3, 3, 5, {
            dsd: 'vcv',
            arr: [1, 2, 3]
        }]]]
    }
}
const result = {};
const func = (obj, str) => {
    for (let key in obj) {
        if (obj[key] === null || obj[key] === undefined) continue
        let nextStr = str.length === 0 ? `${key}` : Array.isArray(obj) ? `${str}[${key}]` : `${str}.${key}`;
        obj[key] instanceof Object ? func(obj[key], nextStr) : result[nextStr] = obj[key]
    }
}
func(obj, '')
console.log(result)

3. 红绿灯问题

// 解法1
function red() {
    console.log('red');
}
function green() {
    console.log('green')
}
function yellow() {
    console.log('yellow')
}
const task = (name, wait) => new Promise((resolve) => {
    setTimeout(() => {
        console.log(name)
        resolve();
    }, wait);
})
const loop = async () => {
    await task("红灯", 3000)
    await task("绿灯", 2000)
    await task("黄灯", 1000)
    loop();
}
loop()
// 解法2
const func = (msg, time) => { 
    const child = (resolve) => {
        setTimeout(() => {
            console.log(msg)
            resolve(child)
        }, time);
    }
    return child
}
const red = func('红灯', 3000)
const green = func('绿灯', 2000)
const yellow = func('黄灯', 1000)
const func2 = (arr) => {
    new Promise(resolve => {
        const curr = arr.shift();
        curr(resolve)
    }).then(fun => {
        arr.push(fun);
        func2(arr);
    })
}
const arr2 = [red, green, yellow]
func2(arr2)

4. 异步任务队列控制

class Scheduler {
    constructor(limit) {
        this.limit = limit
        this.number = 0
        this.queue = []
    }
    addTask(timeout, str) {
        this.queue.push([timeout, str])
    }
    start() {
        if (this.number < this.limit&&this.queue.length) {
            var [timeout, str] = this.queue.shift()
            this.number++
            setTimeout(() => {
                console.log(str)
                this.number--
                this.start()
            }, timeout * 1000);
            this.start()
        }
    }
}

const scheduler = new Scheduler(2)
scheduler.addTask(1, '1');   // 1s后输出'1'
scheduler.addTask(2, '2');  // 2s后输出'2'
scheduler.addTask(3, '3');  // 4s后输出'3'
scheduler.addTask(4, '4');  // 6s后输出'4'
scheduler.start();

5. 洗牌算法

// 简单的洗牌算法
const arr = [1,2,3,4,5,6,7,8,9,10]
arr.sort((a,b) => {
    const num = Math.floor(Math.random() * 10) + 1; // 生成1 到 10
    if(num > 5) {
        return a - b
    }else {
        return b - a
    }
})

console.log(arr)

力扣中有一道打乱数组的题目,leetcode-cn.com/problems/sh…, 上面的答案并不能通过全部的测试用例,得使用下面这种

function shuffle(nums) {
    for(let i = 0; i < nums.length; i++) {
        const j = Math.floor(Math.random() * (nums.length - i)) + i;
        [nums[i], nums[j]] = [nums[j], nums[i]]
    }
    return nums
};
// 原理很简单,就是从后面得数字中随机抽一个作为当前的数字

至于如何验证是否洗的足够乱,这个sir也没有想到啥很好的办法,目前考虑的主要是从数组中每个元素被打乱的概率是否完全相同。如果说对于初始时候是有序数组,可以考虑使用最长递增子序列来判断一下是否有规律(sir的臆想),有思路的朋友欢迎在评论区中指出。

6. 实现 ob 和 watch 方法

// 根据vue3中的实现原理,写的稍微全面一点
let globalFunction = null;
const globalWekMap = new WeakMap();
function observer(params) {
    if(typeof params !== 'object') {
        throw new Error('function obj params is must be object!')
    }
    const handler = {
        get: function(object, key, descriptor) {
            if(globalFunction) {
                if(globalWekMap.has(object)) {
                    const map = globalWekMap.get(object);
                    if(map.has(key)) {
                        const tem = map.get(key);
                        tem.push(globalFunction);
                        map.set(key, tem);
                    }else {
                        map.set(key, [globalFunction]);
                    }
                }else {
                    const map = new Map();
                    map.set(key, [globalFunction]);
                    globalWekMap.set(object, map);
                }
            }
            return Reflect.get(object, key, descriptor)
        },
        set: function(object, key, value, descriptor) {
            Reflect.set(object, key, value, descriptor)
            if(globalWekMap.has(object)) {
                const map = globalWekMap.get(object)
                if(map.has(key)) {
                    const arr = map.get(key)
                    arr.map(func => {
                        func();
                    })
                }
            }
        },
        deleteProperty(object, key, descriptor) {
            if(Reflect.has(object, key)) {
                if(globalWekMap.has(object)) {
                    const map = globalWekMap.get(object)
                    if(map.has(key)) {
                        map.delete(key)
                    }
                }
                Reflect.deleteProperty(object, key, descriptor);
            }
        }
    }
    const object = new Proxy(params, handler)
    return object;
}
function watch(fn) {
    globalFunction = fn;
    fn();
    globalFunction = null;
}

const data = observer({ count: 0, foo: 'test' });

watch(() => {
    console.log('watch-count', data.count);
});
watch(() => {
    console.log('watch-foo', data.foo);
});

data.count += 1;
console.log('showcount', data.count);
delete data.count;
data.foo = 'test2';

7. 构建树

let arr = [
    {id: 1, name: '部门1', pid: 0},
    {id: 2, name: '部门2', pid: 1},
    {id: 3, name: '部门3', pid: 1},
    {id: 4, name: '部门4', pid: 3},
    {id: 5, name: '部门5', pid: 4},
]
const result = [
    {
        "id": 1,
        "name": "部门1",
        "pid": 0,
        "children": [
            {
                "id": 2,
                "name": "部门2",
                "pid": 1,
                "children": []
            },
            {
                "id": 3,
                "name": "部门3",
                "pid": 1,
                "children": [
                    // 结果 ,,,
                ]
            }
        ]
    }
]
const map = new Map();
map.set(0, []);
const func = (arr) => {
    const result = [];
    for(let i = 0; i < arr.length; i++) {
        const obj = arr[i];
        obj.children = [];
        if(map.has(obj.pid)) {
            const child = map.get(obj.pid);
            child.push(obj);
        }
        map.set(obj.id, obj.children);
    }
    const temArr = map.get(0);
    result.push(...temArr)
    return result
}
console.log(func(arr))

8. vue模板替换功能实现

// 这里sir偷懒了,有兴趣的朋友可以尝试一下常规的解法

const data = {
    name: '222',
    age: 18,
    happy: {
        ball: '333',
        game: '33333'
    },
    arr: ['444444'],
    flag: true,
    sayHello: function (msg) {
        return msg
    }
}
let temStr = 'hello my name is {{ name }}, age is {{ age }}, my happy is {{ happy.ball }} and {{ happy.game }}, i like {{ arr[0] }}, end {{ flag ? sayHello("232") : sayHello("2323") }}'
function complie(str) {
    const reg = /{{.+?}}/g;
    const result = str.replace(reg, regStr => {
        regStr = regStr.slice(2, regStr.length - 2);
        regStr = regStr.trim();
        return geStrForTrueValue(regStr)
    })
    return result
}
function geStrForTrueValue(str) {
    with (data) {
        return eval(str);
    }
}
console.log(complie(temStr))

9. 解析Url中的请求参数

const str = 'https://www.baidu.com/s?ie=utf-8&f=8&rsv_bp=1&rsv_idx=1&tn=baidu&wd=%E6%8E%98%E9%87%91&fenlei=256&oq=%25E5%258A%259B%25E6%2589%25A3&rsv_pq=e47284980005215c&rsv_t=2e8cCZQGOTWslcWtfyhbQZh4iZECjg5OOPN6Lqdsmlqr5InmPlS6tnX2iEg&rqlang=cn&rsv_enter=0&rsv_dl=tb&rsv_btype=t&rsv_sug3=27&rsv_sug1=27&rsv_sug7=100&rsv_sug2=0&inputT=2044&rsv_sug4=2045&rsv_sug=1'

// 解法1
// 创建一个URLSearchParams实例
const urlSearchParams = new URLSearchParams(str);
// 把键值对列表转换为一个对象
const params = Object.fromEntries(urlSearchParams.entries());
console.log(params)

// 解法2
function getParams(url) {
    const res = {}
    if (url.includes('?')) {
      const str = url.split('?')[1]
      const arr = str.split('&')
      arr.forEach(item => {
        const key = item.split('=')[0]
        const val = item.split('=')[1]
        res[key] = decodeURIComponent(val) // 解码
      })
    }
    return res
  }
 console.log(getParams(str))

10. 使用requestAnimation实现setInterVal

function mySetInterVal(callback, timer) {
    let timerId = null;
    let preDate = new Date();
    const func = () => {
        const nowDate = new Date();
        if(nowDate - preDate >= timer) {
            preDate = nowDate;
            callback();
        }
        timerId = window.requestAnimationFrame(func);
    }
    func();
    return () => {
        cancelAnimationFrame(timerId);
    }
}

function test(){
    console.log('xxxxxxxxxxx')
}
const callback = mySetInterVal(test, 2000)

// callBakc() 停止定时器

11. 实现Promise.all、Promise.race、Promise.allSettled

const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise((resolve, reject) => {
    setTimeout(reject, 100, 'foo');
});
const promise4 = () => {
    return 'xxxxxxxxx'
}

Promise.MyAll = function (promises) {
    return new Promise(async (resolve, reject) => {
        const callback = [];
        for (let i = 0; i < promises.length; i++) {
            if (promises[i] instanceof Promise) {
                await promises[i].then(res => {
                    callback[i] = res;
                }).catch(err => {
                    reject(err);
                })
            } else {
                callback[i] = promises[i]
            }

        }
        resolve(callback)
    })
}
// 模拟实现
Promise.MyAll([promise1, promise2, promise3, promise4()]).then((values) => {
    console.log(values);
}).catch(err => {
    console.log(err)
});
// 原版
Promise.all([promise1, promise2, promise3, promise4()]).then((values) => {
    console.log(values);
}).catch(err => {
    console.log(err)
});

实现了Promise.all以后,在实现Promise.race和Promise.allSettled就简单很多了,sir就不再写这两个了,大家可以自行尝试一下。

结语

文中的题目或者解析可能存在某些不严谨的地方,欢迎大家在评论区提出自己的看法和建议,最后祝各位童鞋元旦快乐!