每天一道手写/算法题(日更)

531 阅读15分钟

大家好,我是想要进阶高级前端的betterwlf。最近发现自己在手写和算法方面有点菜,就想通过每天一道算法或者手写题,提升自己,同时记录学习的过程。

46.版本号排序(5月13日)

/**
 * 版本排序
 * @param {版本数组} arr 
 */
function sortVersion(arr) {
    arr.sort((v1, v2) => {
        const version1 = v1.split('.');
        const version2 = v2.split('.');
        const len = Math.max(version1.length, version2.length);

        while (version1.length < len) {
            version1.push('0');
        }

        while (version2.length < len) {
            version2.push('0');
        }

        for (let i = 0; i < len; i++) {
            const num1 = parseInt(version1[i]) || 0;
            const num2 = parseInt(version2[i]) || 0;
            if (num1 > num2) {
                return 1;
            } else if (num1 == num2) {
                return 0;
            } else {
                return -1;
            }
        }
    });
    return arr;
}

var arr = ['0.1.1', '2.3.3', '0.302.1', '4.2', '4.3.5', '4.3.4.5'];
console.log(sortVersion(arr));

45.版本比较(5月12日)

/**
 * 版本比较
 * @param {版本一} v1 
 * @param {版本二} v2 
 */
function compareVersion(v1 = "", v2 = "") {
    if (v1 == v2) {
        return 0;
    }
    const version1 = v1.split('.');
    const version2 = v2.split('.');
    const len = Math.max(version1.length, version2.length);

    while (version1.length < len) {
        version1.push('0');
    }

    while (version2.length < len) {
        version2.push('0');
    }

    for (let i = 0; i < len; i++) {
        const num1 = parseInt(version1[i]) || 0;
        const num2 = parseInt(version2[i]) || 0;
        if (num1 > num2) {
            return 1;
        } else {
            return -1;
        }
    }
    return 0;
}

console.log(compareVersion('0.12.14', '1.2.2'));

44.手写EventBus(5月11日)

 class EventBus {
    constructor() {
        this.events = {};
        this.onceEvents = {};
    }
    on(type, fn) {
        const events = this.events;
        if (events[type] == null) {
            events[type] = []; // 初始化key的fn数组
        }
        events[type].push({ fn});
    }
    once(type, fn) {
        const onceEvents = this.events;
        if (onceEvents[type] == null) {
            onceEvents[type] = []; // 初始化key的fn数组
        }
        onceEvents[type].push({ fn});
    }
    off(type, fn) {
        if (!fn) {
            // 解绑所有事件
            this.events[type] = [];
            this.onceEvents[type] = [];
        } else {
            // 解绑单个fn
            const fnList = this.events[type];
            const onceFnList = this.onceEvents[type];
            if (fnList) {
                this.events[type] = fnList.filter(item => item.fn != fn);
            }
            if (onceFnList) {
                this.onceEvents[type] = onceFnList.filter(item => item.fn != fn);
            }
        }
    }
     emit(type, ...args) {
        const fnList = this.events[type];
        const onceFnList = this.onceEvents[type];
         if (fnList) {
            fnList.forEach(item => item.fn(...args));
        }
        if (onceFnList) {
            onceFnList.forEach(item => item.fn(...args));

            // once执行一次就删除
            this.onceEvents[type] = [];
        }
    }
}

const e = new EventBus();
function fn1(a, b) {
    console.log('fn1', a, b);
}
function fn2(a, b) {
    console.log('fn2', a, b);
}
function fn3(a, b) {
    console.log('fn3', a, b);
}

e.on('key1', fn1);
e.on('key1', fn2);
e.once('key1', fn3);
e.on('xxxxxx', fn3);

e.emit('key1', 10, 20);  // 触发 fn1 fn2 fn3

e.off('key1', fn1);

e.emit('key1', 100, 200); // 触发 fn2

43.手写apply(5月10日)

Function.prototype.myApply = function (context, args) {
    if (!context || context === null) {
        context = window;
    }

    // 创建唯一的key值,防止被覆盖
    let fn = Symbol();
    // this就是当前函数
    context[fn] = this;
    // 执行函数并返回结果,相当于把自身作为context的方法进行调用
    const res = context[fn](...args);
    delete context[fn];
    return res;
}

42.手写call(5月9日)

Function.prototype.myCall = function (context, ...args) {
    if (!context || context === null) {
        context = window;
    }

    // 创建唯一的key值,防止覆盖
    let fn = Symbol();
    // this就是当前的函数
    context[fn] = this;
    // 执行函数并返回结果,相当于把自身作为传入的context的方法进行了调用
    const res = context[fn](...args);
    // 清理fn,防止污染
    delete context[fn];
    return res;
}

41.手写bind(5月8日)

Function.prototype.myBind = function (context, ...args) {
    // context是bind传入的this
    // args是bind传入的各个参数
    if (!context || context == null) {
        context = window;
    }

    // 创建唯一的key值,作为构造的context内部方法名
    let fn = Symbol();
    context[fn] = this;
    // 当前函数本身
    let _this = this;
    console.log(_this);
    const result = function (...innerArgs) {
        // 如果将bind绑定之后的函数当作构造函数,通过new操作符使用,则不绑定传入的this,而是将this指向实例化出来的对象
        // 此时new操作符作用 this指向result实例对象 而result又继承自传入的_this 根据原型链知识可以得出
        // this.__proto__ === result.prototype
        // this.__proto__.__proto__===result.prototype.__proto__===_this.prototype
        if (this instanceof _this) {
            this[fn] = _this;
            this[fn](...[...args, ...innerArgs]);
        } else {
            context[fn](...[...args, ...innerArgs]);
        }
    }
    result.prototype = Object.create(this.prototype);
    return result;
}

41.柯里化(5月7日)

题目描述:柯里化(Currying),又称部分求值(Partial Evaluation),是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。核心思想是把多参数传入的函数拆成单参数(或部分)函数,内部再返回调用下一个单参数(或部分)函数,依次处理剩余的参数。

function currying(fn, ...args) {
    const length = fn.length; // 传入函数的参数的长度,比如add的参数有三个
    let allArgs = [...args];
    function calc(...newArgs) {
        // 积累参数
        allArgs = [...allArgs, ...newArgs];
        if (allArgs.length === length) {
            return fn(...allArgs);
        } else {
            // 参数不够,返回函数
            return calc;
        }
    }
    return calc;
}

40.实现LazyMan(5月6日)

LazyMan(“Hank”)
输出: 
Hi! This is Hank! 

LazyMan(“Hank”).sleep(10).eat(“dinner”)输出 
Hi! This is Hank! 
//等待10秒.. 
Wake up after 10 
Eat dinner~ 

LazyMan(“Hank”).eat(“dinner”).eat(“supper”)输出
Hi This is Hank! 
Eat dinner~ 
Eat supper~ 

LazyMan(“Hank”).eat(“supper”).sleepFirst(5)输出 
//等待5秒 
Wake up after 5 
Hi This is Hank! 
Eat supper

实现代码:

class _LazyMan {
    constructor(name) {
        this.name = name;
        this.tasks = [];
        const task = () => {
            console.info(`Hi! This is ${name}`);
            this.next();
        }
        this.tasks.push(task);
        setTimeout(() => {
            // 把this.next()方法调用栈清空后执行    
            this.next();
        }, 0);
    }
    next() {
        const task = this.tasks.shift(); // 取出当前tasks的第一个任务
        if (task) task();
    }
    sleep(time) {
        this._sleepWrapper(time, false);
        return this; // 实现链式调用
    }
    sleepFirst(time) {
        this._sleepWrapper(time, true);
        return this;
    }
    _sleepWrapper(timer, first) {
        const task = () => {
            setTimeout(() => {
                console.info(`Wake up after ${timer}`);
                this.next();
            }, this.timer * 1000);
        };
        if (first) {
            this.tasks.unshift(task); //放到任务队列顶部
        } else {
            this.tasks.push(task); // 放到任务队列尾部
        }
    }
    eat(name) {
        const task = () => {
            console.info(`Eat ${name}`);
            this.next();
        }
        this.tasks.push(task);
        return this;
    }
}

function LazyMan(name) {
    return new _LazyMan(name);
}

39.前K个高频单词(5月5日 leetcode692和leetcode347解法相同)

给定一个单词列表 words 和一个整数 k ,返回前 k 个出现次数最多的单词。

返回的答案应该按单词出现频率由高到低排序。如果不同的单词有相同出现频率, 按字典顺序 排序。

class MinHeap {
    constructor(data = []) {
        this.data = data;
        this.comparator = (a, b) => a[1] - b[1];
        this.heapify();
    }
    heapify() {
        if (this.size() < 2) return;
        // 将每个元素插入,往上冒到合适位置
        for (let i = 0; i < this.size(); i++) {
            this.bubbleUp(i);
        }
    }
    // 获得堆顶元素
    peek() {
        if (this.size() === 0) return null;
        return this.data[0];
    }
    // 插入元素
    offer(value) {
        this.data.push(value);
        // 在最后的位置插入且向上冒泡
        this.bubbleUp(this.size() - 1);
    }
    // 移除顶堆元素
    poll() {
        if (this.size() === 0) {
            return null;
        }
        const result = this.data[0];
        const last = this.data.pop();
        if (this.size() !== 0) {
            // 最末尾元素放到堆顶
            this.data[0] = last;
            // 向下调整直至放到合适位置
            this.bubbleDown(0);
        }
        return result;
    }
    bubbleUp(index) {
        while (index > 0) {
            // 获得父节点索引
            const parentIndex = (index - 1) >> 1;
            if (this.comparator(this.data[index], this.data[parentIndex]) < 0) {
                // 交换位置往上冒
                this.swap(index, parentIndex);
                index = parentIndex;
            } else {
                break;
            }
        }
    }
    bubbleDown(index) {
        const last = this.size() - 1;
        while (true) {
            // 获得要调整的节点的左子节点和右字节点的索引
            const left = 2 * index + 1;
            const right = 2 * index + 2;
            let min = index;
            if (left <= last && this.comparator(this.data[left], this.data[min]) < 0) {
                min = left;
            }
            if (right <= last && this.comparator(this.data[right], this.data[min]) < 0) {
                min = right;
            }
            // 交换
            if (index != min) {
                this.swap(index, min);
                index = min;
            } else {
                break;
            }
        }
    }
    // 交换元素
    swap(i, j) {
        [this.data[i], this.data[j]] = [this.data[j], this.data[i]];
    }
    // 获取堆大小
    size() {
        return this.data.length;
    }
}

var topKFrequent = function (words, k) {
    const map = new Map();

    // 统计频次
    for (const word of words) {
        map.set(word, (map.get(word) || 0) + 1);
    }

    const minHeap = new MinHeap();
    for (entry of map.entries()) {
        if (minHeap.size() < k) {
            minHeap.offer(entry);
        } else if (entry[1] > minHeap.peek()[1]) {
            minHeap.poll();
            minHeap.offer(entry);
        }
    }

    const result = [];
    for (let a of minHeap.data) {
        result.push(a[0]);
    }
    return result;
}

38.数组中的第K个最大元素(4月30日 leetcode215)

给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。

请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。

思路:将大顶堆下沉k-1次,得到的就是第k大的元素

class MinHeap {
    constructor(data = []) {
        this.data = data;
        this.comparator = (a, b) => a - b;
        this.heapify();
    }
    heapify() {
        if (this.size() < 2) return;
        // 将每个元素插入,往上冒到合适位置
        for (let i = 0; i < this.size(); i++) {
            this.bubbleUp(i);
        }
    }
    // 获得堆顶元素
    peek() {
        if (this.size() === 0) return null;
        return this.data[0];
    }
    // 插入元素
    offer(value) {
        this.data.push(value);
        // 在最后的位置插入且向上冒泡
        this.bubbleUp(this.size() - 1);
    }
    // 移除顶堆元素
    poll() {
        if (this.size() === 0) {
            return null;
        }
        const result = this.data[0];
        const last = this.data.pop();
        if (this.size() !== 0) {
            // 最末尾元素放到堆顶
            this.data[0] = last;
            // 向下调整直至放到合适位置
            this.bubbleDown(0);
        }
        return result;
    }
    bubbleUp(index) {
        while (index > 0) {
            // 获得父节点索引
            const parentIndex = (index - 1) >> 1;
            if (this.comparator(this.data[index], this.data[parentIndex]) < 0) {
                // 交换位置往上冒
                this.swap(index, parentIndex);
                index = parentIndex;
            } else {
                break;
            }
        }
    }
    bubbleDown(index) {
        const last = this.size() - 1;
        while (true) {
            // 获得要调整的节点的左子节点和右字节点的索引
            const left = 2 * index + 1;
            const right = 2 * index + 2;
            let min = index;
            if (left <= last && this.comparator(this.data[left], this.data[min]) < 0) {
                min = left;
            }
            if (right <= last && this.comparator(this.data[right], this.data[min]) < 0) {
                min = right;
            }
            // 交换
            if (index != min) {
                this.swap(index, min);
                index = min;
            } else {
                break;
            }
        }
    }
    // 交换元素
    swap(i, j) {
        [this.data[i], this.data[j]] = [this.data[j], this.data[i]];
    }
    // 获取堆大小
    size() {
        return this.data.length;
    }
}

var findKthLargest = function (nums, k) {
    const minHeap = new MinHeap();
    for (const n of nums) {
        // 将数组元素依次插入堆中
        minHeap.offer(n);
        // 如果堆大小超过k,将堆顶元素去掉
        if (minHeap.size() > k) {
            minHeap.poll();
        }
    }
    return minHeap.peek();
}

let num = [3, 2, 1, 5, 6, 4], k = 2;
console.log(findKthLargest(num, k));

37.堆排序(4月29日)

时间复杂度:O(nlogn)

class Heap {
    constructor(arr) {
        this.data = [...arr];
        this.size = this.data.length;
    }
    buildHeap() {
        // 最后一个节点的位置=数组长度-1
        const lastNode = this.size - 1;
        // 最后一个节点的父节点
        const parentNode = Math.floor((lastNode - 1) / 2);
        // 从最后一个节点的父节点开始进行heapify操作
        for (let i = parentNode; i >= 0; i--) {
            this.heapify(i);
        }
    }
    heapSort() {
        // 构建堆
        this.buildHeap();
        // 从最后一个节点出发
        for (let i = this.size - 1; i >= 0; i--) {
            // 交换根节点和最后一个节点的位置
            this.swap(this.data, 0, i);
            this.size--;
            // 重新调整堆
            this.heapify(0);
        }
    }
    heapify(i) {
        if (i >= this.size) {
            return;
        }

        // 左节点(索引)
        let left = 2 * i + 1;
        // 右节点(索引)
        let right = 2 * i + 2;
        /**
         * 1. 找到左子树和右子树位置后,必须确保它小于树的总节点树
         * 2. 已知当前节点与它的左子树与右子树的位置,找到最大值
         */
        // 假设最大值的位置为i
        let max = i;
        // 父节点和左节点left做比较,取最大值
        if (left < this.size && this.data[left] > this.data[max]) {
            max = left;
        }
        // 右节点和最大值比较
        if (right < this.size && this.data[right] > this.data[max]) {
            max = right;
        }
        // 进行比较后,如果最大值的位置不是刚开始设置的i,则将最大值和当节点进行位置交换
        if (max != i) {
            // 交换位置
            this.swap(this.data, i, max);
            // 递归调用
            return this.heapify(max);
        }
    }
    swap(arr, i, max) {
        [arr[max], arr[i]] = [arr[i], arr[max]];
    }
}

const arr = [15, 12, 8, 2, 5, 2, 3, 4, 7];
const fun = new Heap(arr);
fun.buildHeap(); // 形成最大堆的结构
fun.heapSort();// 通过排序,生成一个升序的数组
console.log(fun.data) // [2, 2, 3, 4, 5, 7, 8, 12, 15]

36.快速排序(4月28日)

时间复杂度:O(n^2)

原理:数组中指定一个元素作为标尺,比它大的放到该元素后面,比它小的放到该元素前面,如此重复直至全部正序排列

基础版:

function quicksort(arr) {
    // 缓存数组的长度
    const len = arr.length;

    if (len <= 1) {
        return arr;
    }
    // 初始化标尺元素,左边元素和右边元素
    let flag = arr[0],
        left = [],
        right = [];
    // 把剩余的元素遍历下,比标尺元素小的放左边,比标尺元素大的放右边
    for (let i = 1; i < len; i++) {
        let tmp = arr[i];
        if (tmp < flag) {
            left.push(tmp);
        } else {
            right.push(tmp);
        }
    }
    return quicksort(left).concat(flag,quicksort(right));
}

进阶版(in-place):

function quickSort(arr, left = 0, right = arr.length - 1) {
    // 定义递归边界,如果数组只有一个元素,则没有排序的必要
    if (arr.length > 1) {
        // lineIndex表示下一次划分左右数组的索引位置
        const lineIndex = partition(arr, left, right);
        // 如果左边子数组的长度不小于1,则递归快排这个子数组
        if (left < lineIndex - 1) {
            quickSort(arr, left, lineIndex - 1);
        }
        // 如果右子数组的长度不小于1,则递归快排这个子数组
        if (lineIndex < right) {
            quickSort(arr, lineIndex, right);
        }
    }
    return arr;
}
// 以基准值为中心,划分左右子数组
function partition(arr, left, right) {
    // 基准值默认取中间位置的元素
    let pivotValue = arr[Math.floor(left + (right-left)/2)];
    let i = left;
    let j = right;
    // 当左右指针不越界时,循环执行以下逻辑
    while (i <= j) {
        // 左指针所指元素若小于基准值,则右移动左指针
        while (arr[i] < pivotValue) {
            i++;
        }
        // 右指针所指元素大于基准值,则左移右指针
        while (arr[j] > pivotValue) {
            j--;
        }

        // 若i<=j,则意味着基准值左边存在较大元素或右边存在较小元素,交换两个元素确保左右两侧有序
        if (i <= j) {
            swap(arr, i, j);
            i++;
            j--;
        }
    }
    // 返回左指针索引作为下一次划分左右子数组的依据
    return i;
}

function swap(arr, i, j) {
    [arr[i], arr[j]] = [arr[j], arr[i]];
}

let arr = [5, 8, 1, 6, 10, 4, 1, 2, 3];
console.log(quickSort(arr));

35.归并排序(4月27日)

时间复杂度:O(nlogn)

操作步骤:

1)将需要被排序的数组从中间分割为两半,然后再将分割出来的每个子数组各分割为两半,重复以上操作,直到单个子数组只有一个元素为止;
2)从粒度最小的子数组开始,两两合并、确保每次合并出来的数组都是有序的;
3)当数组被合并至原有的规模时,就得到了一个完全排序的数组

function mergeSort(arr) {
    // 缓存数组的长度
    const len = arr.length;

    // 处理边界情况
    if (len <= 1) {
        return arr;
    }
    // 计算分割点
    const mid = Math.floor(len / 2);
    // 递归分割左子数组,然后合并为有序数组
    const leftArr = mergeSort(arr.slice(0, mid));
    // 递归分割右子数组,然后合并为有序数组
    const rightArr = mergeSort(arr.slice(mid, len));
    // 合并左右两个有序数组
    arr = mergeArr(leftArr, rightArr);
    // 返回合并后的结果
    return arr;
}

function mergeArr(arr1, arr2) {
    let i = 0, j = 0;

    // 初始化结果数组
    const res = [];
    // 缓存arr1的长度
    const len1 = arr1.length;
    // 缓存arr2的长度
    const len2 = arr2.length;
    // 合并两个子数组
    while (i < len1 && j < len2) {
        if (arr1[i] < arr2[j]) {
            res.push(arr1[i]);
            i++;
        } else {
            res.push(arr2[j]);
            j++;
        }
    }
    // 若其中一个子数组首先被合并完全,则直接拼接另一个子数组的剩余部分
    if (i < len1) {
        return res.concat(arr1.slice(i));
    } else {
        return res.concat(arr2.slice(j));
    }
}

34.希尔排序(4月26日)

原理:把数组按下标的一定增量进行分组,对每组使用插入排序算法进行排序。随着增量的逐渐减少。每组包含的元素越来越多,当增量减到1的时候,进行最后一次直接插入排序,排序结束

function shellSort(arr) {
    // 缓存数组长度
    const len = arr.length;
    // 外层循环确定增量的大小,每次增量的大小减半
    for (let gap = parseInt(len >> 1); gap >0; gap = parseInt(gap >> 1)) {
        // 对每个分组使用插入排序,相当于将插入排序的1换成了n
        for (let i = gap; i < len; i++) {
            let tmp = arr[i];
            for (j = i; j >= gap && arr[j - gap] > tmp; j -= gap) {
                arr[j] = arr[j - gap];
            }
            arr[j] = tmp;
        }
    }
    return arr;
}
let arr = [1, 8, 5, 6, 9, 10, 2, 7, 4, 3];
console.log(shellSort(arr))

33.冒泡排序(4月26日)

function bubbleSort(arr) {
    // 缓存数组长度
    const len = arr.length;
    // 外层循环用于控制从头到尾的比较+交换到底进行了多少轮
    for (let i = 0; i < len; i++) {
        // 内层循环用于完成每一轮循环过程中的重复比较+交换
        for (let j = 0; j < len - 1; j++) {
            // 如果相邻前面的数大于后面的数
            if (arr[j] < arr[j - 1]) {
                // 交换
                [arr[j - 1], arr[j]] = [arr[j], arr[j - 1]];
            }
        }
    }
    return arr;
}

32.插入排序(4月26日)

原理:将未排序数据,对已排序数据序列从后向前扫描,找到对应的位置并插入。

具体操作:

1)从第一个元素开始,该元素可以被认为已经被排序

2)取出下一个元素,在已经排好序的序列中从后往前扫描

3)如果已排序元素大于新元素,将新元素向前移动一个位置;

4)重复步骤三,直到找到已排序的元素小雨或等于新元素。就不再向前扫描。新元素插入当前位置;

5)重复步骤2-4

说明:插入排序有条件限制,可以提前结束循环,但是选择排序不行。如果序列是有序的,那么插入排序的时间复杂度是O(n)

function insertSort(arr) {
    const len = arr.length;
    for (let i = 1; i < len; i++) {
        let tmp = arr[i],
            j;// j 保存元素tmp插入的位置
        for (j = i; j > 0 && arr[j - 1] > tmp; j--) {
            arr[j] = arr[j - 1];
        }
        arr[j] = tmp;
    }
    return arr;
}

31.数组去重(4月25日)

function unique(arr) {
    return [...new Set(arr)];
}

let arr = [1, 4, 5, 5, 5, 7, 8];
console.log(unique(arr)); // [1,4,5,7,8]

30.选择排序(4月24日)

原理:从头到尾扫描序列,找出最小的元素,和第一个元素交换,接着从剩下的元素中找到最小的元素,和第二个元素交换,重复以上操作,直至得到一个有序序列为止。

时间复杂度:O(n^2)

升序(从小到大)

function selectSort(arr) {
    const len = arr.length;
    let minIndex;
    for (let i = 0; i < len - 1; i++) {
        // 寻找(i,n)区间里的最小值
        minIndex = i;
        for (let j = i + 1; j < len; j++) {
            if (arr[j] < arr[minIndex]) {
                minIndex = j;
            }
        }
        if (minIndex !== 1) {
            [arr[i], arr[minIndex]] = [arr[minIndex], arr[i]];
        }
    }
    return arr;
}
let arr = [10, 3, 8, 1, 5, 4, 8];
console.log(selectSort(arr))

降序(从大到小)

function selectSort(arr) {
    const len = arr.length;
    let maxIndex;
    for (let i = 0; i < len - 1; i++) {
        // 寻找(i,n)区间里的大值
        maxIndex = i;
        for (let j = i + 1; j < len; j++) {
            if (arr[j] > arr[maxIndex]) {
                maxIndex = j;
            }
        }
        if (maxIndex !== 1) {
            [arr[i], arr[maxIndex]] = [arr[maxIndex], arr[i]];
        }
    }
    return arr;
}
let arr = [1, 8, 5, 6, 9, 10];
console.log(selectSort(arr))

29. 节流(4月23日)

function throttle(fn, delay) {
    let flag = true;
    return () => {
        if (!flag) return;
        flag = false;
        timer = setTimeout(() => {
            fn();
            flag = true;
        }, delay);
    }
}

const div = document.getElementById('iDiv');
div.addEventListener('drag', throttle(() => {
    console.log('1111');
}, 1000));

28. 防抖(4月23日)

function debounce(fn, delay = 200) {
    let timer;
    return function () {
        const args = arguments;
        if (timer) {
            clearTimeout(timer);
        }
        timer = setTimeout(() => {
            // 透传this和参数,改变this指向为调用debounce所指的对象
            fn.apply(this, args);
        }, delay);
    }
}

const ipt = document.getElementById('input');
ipt.addEventListener('keyup', debounce(() => {
    console.log('发起搜索', ipt.value);
}));

27. 用XMLHttpRequest实现Ajax(4月22日)

const getJSON = function (url) {
    return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest();
        // false表示同步,true表示异步
        xhr.open('GET', url, false);
        // 设置请求头
        xhr.setRequestHeader('Content-Type', 'application/json');
        xhr.onreadystatechange = function () {
            if (xhr.readyState !== 4) return;
            if (xhr.status === 200 || xhr.status === 304) {
                resolve(xhr.responseText);
            } else {
                reject(new Error(xhr.responseText));
            }
        };
        xhr.send();
    });
}

26. Array.prototype.includes(4月21日)

题目:实现Array.prototype.includes

Number.isNaN = function (param) {
    if (typeof param === 'number') {
        return isNaN(param);
    }
    return false;
}

function ToIntegerOrInfinity(argument) {
    var num = Number(argument);
    // +0 和 -0
    if ((Number.isNaN(num)) || num == 0) {
        return 0;
    }
    if (num === Infinity || num == -Infinity) {
        return num;
    }
    var inter = Math.floor(Math.abs(num));
    if (num < 0) {
        inter = -inter;
    }
    return inter;
}

Array.prototype.includes = function (item, fromIndex) {
    // call,apply调用,严格模式下,this不会被Object包装
    if (this == null) {
        throw new TypeError('无效的this');
    }

    var O = Object(this);
    var len = O.length >> 0;
    if (len <= 0) {
        return false;
    }

    var n = ToIntegerOrInfinity(fromIndex);
    if (fromIndex === undefined) {
        n = 0;
    }
    if (n === +Infinity) {
        return false;
    }

    if (n === -Infinity) {
        n = 0;
    }

    var k = n > 0 ? n : len + n;
    if (k < 0) {
        k = 0;
    }

    const isNaN = Number.isNaN(item);
    for (let i = 0; i < len; i++) {
        if (O[i] === item) {
            return true;
        } else if (isNaN && Number.isNaN(O[i])) {
            return true;
        }
    }
    return false;
}

const arr = ['a', 'b', 'c'];
console.log(arr.includes('c', -100));
console.log(arr.includes('c', -1));
console.log(arr.includes('a', -1));

25. Array.prototype.entries 和 Array.prototype.keys(4月20日)

题目:实现Array.prototype.entries和Array.prototype.keys

// 如果要想一个对象可以被遍历,需要有Symbol.iterator属性
Array.prototype[Symbol.iterator] = function () {
    const O = Object(this);
    let index = 0;
    const length = O.length;

    function next() {
        if (index < length) {
            return { value: O[index++], done: false };
        }
        return { value: undefined, done: true };
    }

    return {
        next
    }
}

// 实现Array.prototype.entries
Array.prototype.entries = function () {
    const O = Object(this);
    const length = O.length;
    var entries = [];

    for (var i = 0; i < length; i++) {
        entries.push([i, O[i]]);
    }

    const itr = this[Symbol.iterator].bind(entries)();
    return {
        next: itr.next,
        [Symbol.iterator]() { return itr }
    }
}

// 实现Array.prototype.keys
Array.prototype.keys = function () {
    const O = Object(this);
    const length = O.length;
    var keys = [];

    for (var i = 0; i < length; i++) {
        keys.push([i, O[i]]);
    }

    const itr = this[Symbol.iterator].bind(keys)();
    return {
        next: itr.next,
        [Symbol.iterator]() { return itr }
    }
}

const arr = [1, 2, 3];
var iter = arr.entries();

console.log(iter.next());
console.log(iter.next());
console.log(iter.next());

for (let [k, v] of arr.entries()) {
    console.log(`k:${k}`, `v:${v}`);
}

var iter1 = arr.keys();

console.log(iter1.next());
console.log(iter1.next());
console.log(iter1.next());

for (let [k, v] of arr.keys()) {
    console.log(`k:${k}`, `v:${v}`);
}

24. 逆波兰表达式求值(4月19日 leetcode150)

根据 逆波兰表示法,求表达式的值。

有效的算符包括 +、-、*、/ 。每个运算对象可以是整数,也可以是另一个逆波兰表达式。

注意 两个整数之间的除法只保留整数部分。

可以保证给定的逆波兰表达式总是有效的。换句话说,表达式总会得出有效数值且不存在除数为 0 的情况。

    // 定义栈结构
    let stack = [];
    for (const token of tokens) {
        if (isNaN(Number(token))) {
            const n2 = stack.pop();
            const n1 = stack.pop();
            switch (token) {
                case '+':
                    stack.push(n1 + n2);
                    break;
                case '-':
                    stack.push(n1 - n2);
                    break;
                case '*':
                    stack.push(n1 * n2);
                    break;
                case '/':
                    stack.push(n1 / n2 | 0);
                    break;
            }
        } else {
            stack.push(Number(token));
        }
    }
    return stack.pop();// 因没有遇到运算符而在栈中

23. 删除字符串中的所有相邻重复项(4月18日 leetcode1047)

给出由小写字母组成的字符串 S,重复项删除操作会选择两个相邻且相同的字母,并删除它们。

在 S 上反复执行重复项删除操作,直到无法继续删除。

在完成所有重复项删除操作后返回最终的字符串。答案保证唯一。

示例:

  • 输入:"abbaca"
  • 输出:"ca"
  • 解释:例如,在 "abbaca" 中,我们可以删除 "bb" 由于两字母相邻且相同,这是此时唯一可以执行删除操作的重复项。之后我们得到字符串 "aaca",其中又只有 "aa" 可以执行重复项删除操作,所以最后的字符串为 "ca"。
var removeDuplicates = function (s) {
    // 初始化栈结构
    const stack = [];
    // 循环字符串
    for (let i = 0; i < s.length; i++) {
        // 如果栈不为空,且栈顶元素和字符串值相同,元素出栈
        if (stack.length > 0 && stack[stack.length - 1] === s[i]) {
            stack.pop();
        } else {
            // 如果栈为空,元素入栈
            stack.push(s[i]);
        }
    }
    // 输出字符串
    return stack.join('');
}

22. compose(4月18日)

实现一个 compose 函数

// 用法如下:
function fn1(x) {
    return x + 1;
  }
  function fn2(x) {
    return x + 2;
  }
  function fn3(x) {
    return x + 3;
  }
  function fn4(x) {
    return x + 4;
  }
  const a = compose(fn1, fn2, fn3, fn4);
  console.log(a(1)); // 1+4+3+2+1=11

实现代码如下:

function compose(...fn) {
    if (!fn.length) return (v) => v;
    if (fn.length === 1) return fn[0];
    return fn.reduce(
        (pre, cur) =>
            (...args) =>
                pre(cur(...args))
    )
}

21. 有效括号(4月17日 leetcode 20)

给定一个只包括 '(',')','{','}','[',']' 的字符串,判断字符串是否有效。

有效字符串需满足:

  • 左括号必须用相同类型的右括号闭合。
  • 左括号必须以正确的顺序闭合。
  • 注意空字符串可被认为是有效字符串。

示例 1:

  • 输入: "()"
  • 输出: true
var isValid = function(s) {
    const stack = [],
        map = {
            "(": ")",
            "[": "]",
            "{": "}"
        };
    for (let x of s) {
        if (x in map) {
            stack.push(x);
            continue;
        };
        if (map[stack.pop()] !== x) {
            return false;
        }
    }
    return stack.length === 0;
};

20. 用队列实现栈(4月16日 leetcode225)

请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(push、top、pop 和 empty)。

实现 MyStack 类:

void push(int x) 将元素 x 压入栈顶。 int pop() 移除并返回栈顶元素。 int top() 返回栈顶元素。 boolean empty() 如果栈是空的,返回 true ;否则,返回 false 。  

注意:

你只能使用队列的基本操作 —— 也就是 push to back、peek/pop from front、size 和 is empty 这些操作。 你所使用的语言也许不支持队列。 你可以使用 list (列表)或者 deque(双端队列)来模拟一个队列 , 只要是标准的队列操作即可。

解法1(一个队列实现):

var MyStack = function() {
    this.queue = [];
};

/** 
 * @param {number} x
 * @return {void}
 */
MyStack.prototype.push = function (x) {
    // 元素入队
    this.queue.push(x);
};

/**
 * @return {number}
 */
MyStack.prototype.pop = function () {
    // 缓存队列的长度
    let size = this.queue.length;
    // 当队列不为空
    while (size-- > 1) {
        // 队头元素出队加到队列的尾部
        this.queue.push(this.queue.shift());
    }
    // 队列元素出队,模拟栈操作
    return this.queue.shift();
};

/**
 * @return {number}
 */
MyStack.prototype.top = function () {
    // 借用pop方法,避免重复代码
    let x = this.pop();
    // 将出队的元素,重新入队
    this.queue.push(x);
    return x;
};

/**
 * @return {boolean}
 */
MyStack.prototype.empty = function() {
    return !this.queue.length;
};

解法2(两个队列实现):

var MyStack = function () {
    // 初始化两个队列
    this.queue1 = [];
    this.queue2 = [];
};

MyStack.prototype.push = function (x) {
    // 将元素入队到queue1
    this.queue1.push(x);
};

MyStack.prototype.pop = function () {
    // 当queue1队列为空时,交换queue1和queue2
    if (!this.queue1.length) {
        [this.queue1, this.queue2] = [this.queue2, this.queue1];
    }

    while (this.queue1.length > 1) {
        // 将queue1出队push到queue2中
        this.queue2.push(this.queue1.shift());
    }
    // queue1的元素出队
    return this.queue1.shift();
};

MyStack.prototype.top = function () {
    // 调用pop方法,出队的元素重新入队
    const x = this.pop();
    this.queue1.push(x);
    return x;
};

MyStack.prototype.empty = function() {
    return !this.queue1.length && !this.queue2.length;
};

19. 用栈实现队列(leetcode232 4月15日)

请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(push、pop、peek、empty):

实现 MyQueue 类:

void push(int x) 将元素 x 推到队列的末尾 int pop() 从队列的开头移除并返回元素 int peek() 返回队列开头的元素 boolean empty() 如果队列为空,返回 true ;否则,返回 false 说明:

你 只能 使用标准的栈操作 —— 也就是只有 push to top, peek/pop from top, size, 和 is empty 操作是合法的。 你所使用的语言也许不支持栈。你可以使用 list 或者 deque(双端队列)来模拟一个栈,只要是标准的栈操作即可。

var MyQueue = function () {
    // 初始化两个栈
    this.stackIn = [];
    this.stackOut = [];
};

/** 
 * @param {number} x
 * @return {void}
 */
MyQueue.prototype.push = function (x) {
    // 直接使用数组的push方法
    this.stackIn.push(x);
};

/**
 * @return {number}
 */
MyQueue.prototype.pop = function () {
    // 如果stackOut是空的,需要将stackIn的元素转移过来
    if (this.stackOut.length == 0) {
        // 当stackIn不为空时,出栈
        while (this.stackIn.length) {
            // 将stackIn出栈的元素push到stackOut中
            this.stackOut.push(this.stackIn.pop());
        }
    }
    // 从stackOut中出栈元素,实现先进先出
    return this.stackOut.pop();
};

/**
 * @return {number}
 */
MyQueue.prototype.peek = function () {
    // peek和pop的功能类似,只不过是没有将定位的值出栈
    const x = this.pop();
    // 因为pop弹出了元素,再添加回去
    this.stackOut.push(x);
    return x;
};

/**
 * @return {boolean}
 */
MyQueue.prototype.empty = function () {
    // 若stack1和stack2均为空,那么队列为空
    return !this.stackIn.length && !this.stackOut.length;
};

18. 完全二叉树的节点个数(4月14日 leetcode222)

给你一棵 完全二叉树 的根节点 root ,求出该树的节点个数。

完全二叉树 的定义如下:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层,则该层包含 1~ 2h 个节点。

var countNodes = function (root) {
    // 定义结果变量
    let count = 0;
    // 如果根节点不存在,返回0
    if(!root){
        return count;
    }
    // 将根结点入队
    let queue=[root];
    while (queue.length) {
        // 缓存当前层级的结点数
        let len = queue.length;
        // 记录循环的结点数
        count = count + len;
        for (let i = 0; i < len; i++){
            // 获取队头元素,并将队头元素出队
            let top = queue.shift();
            // 如果队头元素有左孩子,左孩子入队
            if (top.left) {
                queue.push(top.left);
            }
            // 如果队头元素有右孩子,右孩子入队
            if (top.right) {
                queue.push(top.right);
            }
        }
    }
    return count;
}

17. N叉树的最大深度(4月13日 leetcode559)

截屏2022-04-16 下午6.23.44.png

var maxDepth = function (root) {
    // 定义结果变量
    let count = 0;
    // 如果根节点不存在,返回0
    if(!root){
        return count;
    }
    // 将根结点入队
    let queue=[root];
    while (queue.length) {
        // 缓存当前层级的结点数
        let len = queue.length;
        count++;
        for (let i = 0; i < len; i++){
            // 获取队头元素,并将队头元素出队
            let top = queue.shift();
            // 获取队头元素的子元素
            for (let item of top.children) {
                // 如果队头元素有子元素
                if (item) {
                    // 将子元素入队
                    queue.push(item);
                }
            }
        }
    }
    return count;
}

16. 手写深拷贝(4月12日)

function isObject(val) {
    return val !== null && typeof val === 'object';
}

function deepClone(obj, hash = new WeakMap()) {
    if (!isObject(obj)) return obj;
    if (hash.has(obj)) {
        return hash.get(obj);
    }

    let target = Array.isArray(obj) ? [] : {};
    hash.set(obj, target);
    Reflect.ownKeys(obj).forEach((item) => {
        if (!isObject(obj[item])) {
            target[item, deepClone(obj[item], hash)];
        } else {
            target[item] = obj[item];
        }
    });
    return target;
}

var obj1 = {
    a: 1,
    b: { a: 2 }
}
var obj2 = deepClone(obj1);
console.log(obj1);

15. 实现instanceof(4月8日)

function myInstanceof(left, right) {
    while (true) {
        if (left == null) {
            return false;
        }
        if (left.__proto__ == right.protoype) {
            return true;
        }
        left = left.__proto__;
    }
}

14. 对称二叉树(4月7日 leetcode101)

给你一个二叉树的根节点 root , 检查它是否轴对称。

var isSymmetric = function(root) {
    const compareNode = function (left, right) {
        if (left == null && right != null || left !== null && right == null) {
            // 如果左子树为空,右子树不为空或者左子树不为空,右子树为空,返回false
            return false;
        } else if (left == null && right == null) {
            // 如果左子树为空,右子树也为空,返回true
            return true;
        } else if (left.val !== right.val) {
            // 如果左孩子的值不等于右孩子的值,返回false
            return false;
        }

        // 对比左子树的左孩子和右子树的右孩子
        let outSide = compareNode(left.left, right.right);
        // 对比左子树的右孩子和右子树的左孩子
        let inSide = compareNode(left.right, right.left);
        return outSide && inSide;
    }
    if (root == null) {
        return true;
    }
    return compareNode(root.left, root.right);
};

13. 翻转二叉树(4月6日 leetcode226)

截屏2022-04-06 下午9.47.44.png

解法一(遍历法):

var invertTree = (root) => {
    // 交换结点函数
    const invertNode = function (left, right) {
        let temp = left;
        left = right;
        right = temp;
        // 交换结点后重新给root赋值
        root.left = left;
        root.right = right;
    }
    // 如果根结点不存在,直接返回
    if (root == null) {
        return root;
    }

    // 交换根结点的左右孩子
    invertNode(root.left, root.right);
    // 如果左孩子存在,交换左孩子的左右结点
    if (root.left) {
        invertTree(root.left);
    }
    // 如果右孩子存在,交换右孩子的左右结点
    if (root.right) {
        invertTree(root.right);
    }
    return root;
}

解法二(迭代法):

const inverTree = (root) => {
    // 如果根结点不存在,直接返回
    if (root == null) {
        return root;
    }

    // 初始化队列
    const queue = [];
    // 根结点入队
    queue.push(root);
    // 如果队列不为空,执行操作
    while (queue.length) {
        // 获取队头元素
        const cur = queue[0];
        // 交换左右子树
        [cur.left, cur.right] = [cur.right, cur.left];
        // 如果左孩子存在,左孩子入队
        if (cur.left) {
            queue.push(cur.left);
        }
        // 如果右孩子存在,右孩子入队
        if (cur.right) {
            queue.push(cur.right);
        }
        // 队头元素出队   
        queue.shift();
    }
    return root;
}

12. 二叉树的最小深度(4月5日 leetcode111)

给定一个二叉树,找出其最小深度。

最小深度是从根节点到最近叶子节点的最短路径上的节点数量。

说明: 叶子节点是指没有子节点的节点。

获取最小深度最大的关键是:叶子节点没有左右孩子

var minDepth = function (root) {
    // 定义二叉树的深度
    let curLevel = 0;
    // 如果根结点为空,直接返回
    if (root === null) return curLevel;

    // 定义队列
    const queue = [];
    // 根结点入队
    queue.push(root);

    // 如果队列不为空,循环操作
    while (queue.length) {
        // 每循环一次,二叉树的深度加一
        curLevel++;
        // 缓存队列长度
        let len = queue.length;
        for (let i = 0; i < len; i++) {
            // 获取队头元素
            const top = queue.shift();
            // 如果左孩子和右孩子都为空,返回最小深度
            if (top.left == null && top.right == null) {
                return curLevel;
            }
            // 如果当前结点有左孩子,左孩子入队
            if (top.left) {
                queue.push(top.left);
            }
            // 如果当前结点有右孩子,右孩子入队
            if (top.right) {
                queue.push(top.right);
            }
        }
    }
    return curLevel;
};

11. 二叉树的最大深度(4月5日 leetcode104)

image.png

var maxDepth = function (root) {
    // 定义二叉树的深度
    let curLevel = 0;
    // 如果根结点为空,直接返回
    if (root === null) return curLevel;

    // 定义队列
    const queue = [];
    // 根结点入队
    queue.push(root);

    // 如果队列不为空,循环操作
    while (queue.length) {
        // 每循环一次,二叉树的深度加一
        curLevel++;
        // 缓存队列长度
        let len = queue.length;
        for (let i = 0; i < len; i++) {
            // 获取队头元素
            const top = queue.shift();
            // 如果当前结点有左孩子,左孩子入队
            if (top.left) {
                queue.push(top.left);
            }
            // 如果当前结点有右孩子,右孩子入队
            if (top.right) {
                queue.push(top.right);
            }
        }
    }
    return curLevel;
};

10. 填充每个节点的下一个右侧节点指针(4月4日 leetcode 116)

image.png

var connect = function(root) {
    // 如果根结点为空,直接返回
    if (root === null) return root;
    
    // 定义队列
    const queue = [];
    // 根结点入队
    queue.push(root);
    // 如果队列不为空,循环操作
    while (queue.length) {
        // 缓存队列长度
        let len = queue.length;
        for (let i = 0; i < len; i++) {
            // 获取队头元素
            const top = queue.shift();
            // 给中间结点元素赋next指针
            if(i<len-1){
                top.next=queue[0];
            }
            // 如果当前结点有左孩子,左孩子入队
            if(top.left){
                queue.push(top.left);
            }
            // 如果当前结点有右孩子,右孩子入队
            if(top.right){
                queue.push(top.right);
            }
        }
    }
    return root;
};

9. 在每个树行中找最大值(4月3日 leetcode515)

给定一棵二叉树的根节点 root ,请找出该二叉树中每一层的最大值。

var largestValues = function (root) {
    // 定义结果数组
    let res = [];

    // 如果根节点为空,直接返回
    if (!root) {
        return res;
    }
    // 定义队列
    const queue = [];
    // 根结点入队
    queue.push(root);
    // 如果队列不为空,循环操作
    while (queue.length) {
        // 初始化最大值
        let max = queue[0].val;
        // 缓存队列长度
        let len = queue.length;
        for (let i = 0; i < len; i++) {
            // 获取队头元素
            const top = queue[0];
            // max和队头元素比较大小,如果队头元素比max大,重新给max赋值
            max = max > top.val ? max : top.val;
            // 如果当前结点有左孩子,左孩子入队
            if(top.left){
                queue.push(top.left);
            }
            // 如果当前结点有右孩子,右孩子入队
            if(top.right){
                queue.push(top.right);
            }
            // 队头元素出队
            queue.shift();
        }
        res.push(max);
    }
    return res;
};

8. 二叉树的右视图(4月2日 leetcode199)

给定一个二叉树的 根节点 root,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。

var rightSideView = function (root) {
    // 定义结果数组
    let res = [];

    // 如果根结点不存在,直接返回
    if (!root) {
        return res;
    }
    // 定义队列
    const queue = [];
    // 根结点入队
    queue.push(root);
    // 当队列不为空时,循环操作
    while (queue.length) {
        // 缓存队列的长度
        let len = queue.length;
        // 定义当前循环层
        let cur = [];
        // 循环队列
        for (let i = 0; i < len; i++) {
            // 获取队头元素,将队头元素入队
            const top = queue[0];
            // 将队头元素的值从数组尾部插入到当前层数组中
            cur.push(top.val);
            // 如果当前结点有左孩子,将左孩子入队
            if (top.left) {
                queue.push(top.left);
            }
            // 如果当前结点有右孩子,将右孩子入队
            if (top.right) {
                queue.push(top.right);
            }
            // 队头元素出队
            queue.shift();
        }
        // 将当前层数组最后一个元素插入到结果数组尾部
        res.push(cur[cur.length - 1]);
    }
    return res;
};

7. 二叉树的锯齿形层序遍历(4月1日 leetcode 103)

给你二叉树的根节点 root ,返回其节点值的 锯齿形层序遍历 。(即先从左往右,再从右往左进行下一层遍历,以此类推,层与层之间交替进行)。

和3月31日题目的类型相似,重点在打印顺序的控制上

var zigzagLevelOrder = function (root) {
    // 定义结果数组
    let res = [];

    // 如果根节点不存在,直接返回
    if (!root) {
        return res;
    }

    // 初始化队列queue
    const queue = [];
    // 控制当前层数是顺序还是逆序 打印
    let flag = 1;
    // 入口坐标入队
    queue.push(root);
    // 如果队列不为空,遍历队列
    while (queue.length) {
        // 缓存队列长度
        let len = queue.length;
        // 定义当前层数组
        let curLevel = [];
        for (let i = 0; i < len; i++) {
            // 取出队头元素
            const top = queue[0];
            if (flag > 0) {
                curLevel.push(top.val);
            } else {
                curLevel.unshift(top.val);
            }

            // 如果左子树存在,左子树入队
            if (top.left) {
                queue.push(top.left);
            }
            // 如果右子树存在,右子树入队
            if (top.right) {
                queue.push(top.right);
            }
            // 弹出队头元素   
            queue.shift();
        }
        res.push(curLevel);
        flag = -flag;
    }
    return res;
}

6. 二叉树的层序遍历(3月31日 leetcode102)

给你二叉树的根节点 root ,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。

var levelOrder = function (root) {
    // 定义结果数组
    let res = [];

    // 如果根节点不存在,直接返回
    if (!root) {
        return res;
    }

    // 初始化队列queue
    const queue = [];
    // 入口坐标入队
    queue.push(root);
    // 如果队列不为空,遍历队列
    while (queue.length) {
        debugger
        // 缓存队列长度
        let len = queue.length;
        // 定义当前层数组
        let curLevel = [];
        for (let i = 0; i < len; i++) {
            // 取出队头元素
            const top = queue[0];
            curLevel.push(top.val);
            // 如果左子树存在,左子树入队
            if (top.left) {
                queue.push(top.left);
            }
            // 如果右子树存在,右子树入队
            if (top.right) {
                queue.push(top.right);
            }  
            // 弹出队头元素   
            queue.shift();       
        }
        res.push(curLevel);
    }
    return res;
}

5. 模拟new操作符(3月30日)

function myNew(fn, ...args) {
    // 创建新对象,并继承构造方法的prototype属性,为了把obj挂在原型链上<=>obj.__proto__=Foo.prototype
    let obj = Object.create(fn.prototype);
    // 执行构造函数,并为其绑定新this,让构造函数能进行this.name=name之类的操作
    let res = fn.call(obj, ...args);
    // 如果构造函数返回一个对象,就返回该对象;否则返回新创建的对象
    if (res && (typeof res === 'object' || typeof res === 'function')) {
        return res;
    }
    return obj;
}

4. 二叉树的前序遍历(3月29日 leetcode144)

给你二叉树的根节点 root ,返回它节点值的 前序 遍历。

解法一(递归):

var preorderTraversal = function (root) {
    let res = [];
    const dfs = function (root) {
        if (root == null) return;
        // 先序遍历从父节点开始
        res.push(root.val);
        // 递归左子树
        dfs(root.left);
        // 递归右子树
        dfs(root.right);
    }
    dfs(root);
    return res;
}

解法二(迭代):

var preorderTraversal = function (root) {
    // 定义结果数组
    let res = []; 

    // 如果root为空,直接返回
    if (root == null) {
        return res;
    }

    // 将根节点入栈
    const stack = [root]; 

    // 若栈不为空,重复出栈、入栈操作
    while (stack.length) {
        // 将栈顶节点标记为当前结点
        let cur = stack.pop();
        // 当前结点就是当前子树的根结点,把这个结点值放到结果数组的尾部
        cur.val ? res.push(cur.val) : '';
        // 如果当前子树有右孩子,将右孩子入栈
        cur.right && stack.push(cur.right);
        // 如果当前子树有左孩子,将左孩子入栈
        cur.left && stack.push(cur.left);
    }
    // 返回结果数组
    return res;
}

3. 二叉树的中序遍历(3月29日 leetcode94)

给你一棵二叉树的根节点 root ,返回其节点值的 中序遍历

解法一(递归):

var preorderTraversal = function (root) {
    let res = [];
    const dfs = function (root) {
        if (root == null) return;
        // 中序遍历先遍历左子树
        dfs(root.left);
        // 遍历父节点
        res.push(root.val);
        // 遍历右子树
        dfs(root.right);
    }
    dfs(root);
    return res;
}

解法二(迭代):

var inorderTraversal = function (root) {
    // 定义结果数组
    let res = [];
    // 初始化栈结构
    const stack = [];
    // 用cur结点充当游标
    let cur = root;

    // 若cur不为空或栈不为空时,重复以下逻辑
    while (cur || stack.length) {
        // 这个while是为了把寻找左叶子结点过程中,途径的所有结点都记录下来
        while (cur) {
            // 将途经的结点入栈
            stack.push(cur);
            // 继续搜索当前结点的左孩子
            cur = cur.left;
        }
        // 取出栈顶元素
        cur = stack.pop();
        // 将栈顶元素入栈
        res.push(cur.val);
        // 读取cur结点的右孩子
        cur = cur.right;
    }
    // 返回结果数组
    return res;
}

2. 二叉树的后序遍历(3月29日 leetcode145)

给你一棵二叉树的根节点 root ,返回其节点值的 后序遍历

解法一(递归):

var postorderTraversal = function (root) {
    let res = [];
    const dfs = function (root) {
        if (root == null) return;
        // 后序遍历先遍历左子树
        dfs(root.left);
        // 遍历右子树
        dfs(root.right);
        // 遍历根节点
        res.push(root.val);
    }
    dfs(root);
    return res;
}

解法二(迭代):

var postorderTraversal = function(root) {
    // 定义结果数组
    let res = [];

    // 如果根节点为空,直接返回
    if (!root) {
        return res;
    }

    // 将根结点入栈
    const stack = [root];
    // 若栈不为空,重复出栈、入栈操作
    while (stack.length) {
        // 将栈顶结点标记为当前结点
        let cur = stack.pop();
        // 当前结点就是当前子树的根结点,把这个结点放到结果数组的头部
        res.unshift(cur.val);

        // 如果当前子树根结点有左孩子,将左孩子入栈
        if (cur.left) {
            stack.push(cur.left);
        }
        // 如果当前子树根结点有右孩子,将右孩子入栈
        if (cur.right) {
            stack.push(cur.right);
        }
    }
    // 返回结果数组
    return res;
};

1. 长度最小的子数组(3月28日 leetcode209)

1.png

这道题可以通过滑动窗口(不断调节子序列的起始位置和终止位置)方法解题。

关键点有如下3点:

  • 窗口:满足和>=s的长度的最小的连续子数组
  • 窗口起始位置如何移动:设置为数组的起始位置。如果当前窗口数据之和大于s,窗口向前移动(该缩小了)
  • 窗口的结束位置如何移动:窗口的结束位置就是遍历数组的指针

解题:

var minSubArrayLen = function (target, nums) {
    const len = nums.length; // 数组长度
    let l = r = 0; // 窗口起始位置和结束位置
    let sum = 0; // 窗口数值之和
    let res = len + 1; // 初始化结果数组的长度为len+1,是为了判断“不存在符合条件的子数组,返回0”的情况
    // 通过窗口结束位置遍历数组
    while (r < len) {
        // 求和
        sum += nums[r];
        // 当窗口数字之和大于目标值时
        while (sum >= target) {
            // 获取符合条件的子数组长度
            let subLen = r - l + 1;
            // 如果子数组长度小于res的长度,给res赋值
            if (subLen < res) {
                res = subLen;
            }
            // 窗口起始位置向右移动
            sum -= nums[l++];
        }
        // 窗口结束位置向右移动
        r++;
    }
    // 如果res没有被赋值,说明没有匹配的子数组,返回0
    return res > len ? 0 : res;
};