大家好,我是想要进阶高级前端的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)
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)
解法一(遍历法):
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)
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)
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)
这道题可以通过滑动窗口(不断调节子序列的起始位置和终止位置)方法解题。
关键点有如下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;
};