2024

168 阅读11分钟

基本方法

1.防抖

function debounce(fn, delay) {
    let timer = null;
    return function () {
        const that = this;
        const arg = arguments;

        clearTimeout(timer)
        timer = setTimeout(() => {
            fn.apply(that, arg);
        }, delay)
    }
}

2.节流

const trottle = (fn, delay) => {
    let timer = null;
    return function () {
        const that = this;
        const arg = arguments;
        if (!timer) {
            timer = setTimeout(() => {
                fn.apply(that, arg);
                timer = null;
            },delay)
        } 
    }
}

3.call

Function.prototype.customCall = function(thisArg, ...arguments) {
    // thisArg必须是个对象,否则thisArg.fn报错
    thisArg = thisArg ? Object(thisArg) : window;

    // this绑定到thisArg
    let fn  = this;
    thisArg.fn = fn;

    //执行
    const res = thisArg.fn(...arguments);

    delete thisArg.fn;
    return res;
}

4.apply

Function.prototype.customApply = function(thisArg, arguments) {
    thisArg = thisArg ? Object(thisArg) : window;
    let fn = this;
    thisArg.fn = fn;
    const res = arguments ? thisArg.fn(...arguments) : thisArg.fn();
    delete thisArg.fn;
    return res;
}

5.bind

Function.prototype.customBind = function(thisArg, ...args) {
    thisArg = thisArg ? Object(thisArg) : window;
    let fn = this;
    return function(...otherArgs) {
        thisArg.fn = fn;
        const res = thisArg.fn(...args, ...otherArgs); // 合并两个参数
        delete thisArg.fn;
        return res;
    }
}
a.customBind(this, ...args)(...otherArgs);

6.instanceof

function customInstanceof(s1, s2) {
    // let s1_proto = s1._proto_;
    let s1_proto = Object.getPrototypeOf(s1);
    let s2_proto = s2.prototype;
    while(true) {
        if (!s1_proto) return false;
        if (s1_proto === s2_proto) {
            return true;
        }
        s1_proto = Object.getPrototypeOf(s1_proto)
    }
}

7.promise

class customPromise {
    constructor(excutor) {
        this.state = 'pending';
        this.value = null;
        this.reason = null;

        let resolve = (value) => {
            if (this.state === 'pending') {
                this.pending = 'fulfilled';
                this.value = value;
            }
        }

        let reject = (reason) => {
            if (this.state === 'pending') {
                this.pending = 'rejected';
                this.reason = reason;
            }
        }

        try {
            excutor(resolve, reject)
        } catch (error) {
            reject(error);
        }
    }
}

8.promise.all

Promise.all = function(arr) {
    let result = [];
    let count = 0;

    return new Promise((resolve, reject) => {
        for(let i = 0; i < arr.length; i++) {
            arr[i].then((res) => {
                result[i] = res;
                count++;
                if(count === arr.length) {
                    resolve(result);
                }
            }, reject)
        }
    })
}

9.promise.race

Promise.race = function(arr) {
    return new Promise((resolve, reject) => {
        for(let i = 0; i < arr.length; i++) {
            arr[i].then(resolve, reject);
        }
    })
}

Promise.race([p1,p2,p3]).then((res) => {
    console.log('res', res);
}).catch((error) => {
    console.log('error', error);
})

10.promise异步加载图片

function loadImg(url) {
  return new Promise((resolve, reject) => {
    const img = new Image()
    img.url = url
    img.onload = () => {
      resolve(img)
    }
    img.onerror = () => {
      reject(`图片加载失败-${url}`)
    }
  })
}

// 使用
loadImg('xxx.png')
  .then((res) => {
    console.log(res)
  })
  .catch((error) => {
    console.log(error)
  })

11.Object.create

function(obj){
  // 参数必须是一个对象或 null
  if (typeof obj !== "object" && typeof obj !== "function") {
    throw new TypeError("Object prototype may only be an Object or null.");
  }
  // 创建一个空的构造函数
  function F(){}
  // 将构造函数的原型指向传入的对象
  F.prototype = obj
  // 返回一个新的实例对象,该对象的原型为传入的对象
  return new F()
}

12.手写一个new

function myNew(Con, ...arg) {
  let obj = Object.create(Con.prototype)
  let result = Con.apply(obj, arg)
  return typeof result === 'object' ? result : obj
}

13.函数柯里化

const myCurried = (fn, ...args) => {
  if (args.length < fn.length) {
    // 未接受完参数
    return (..._args) => myCurried(fn, ...args, ..._args)
  } else {
    // 接受完所有参数,直接执行
    return fn(...args)
  }
}

function add(a, b, c) {
  return a + b + c
}

const curriedAdd = myCurried(add)

console.log(curriedAdd(1)(2)(3)) // 输出 6
console.log(curriedAdd(1, 2)(3)) // 输出 6
console.log(curriedAdd(1)(2, 3)) // 输出 6

14.params参数

export default class {
  static setUrlParams(url, param, value) {
    let result;
    // 防止多次添加同一个字段
    let cleanUrl = this.removeParams(param, url);
    // 防止 url?&param=value
    cleanUrl = cleanUrl[cleanUrl.length - 1] === '?' ? cleanUrl.replace('?', '') : cleanUrl;
    if (cleanUrl.indexOf('?') !== -1) {
      const p = new RegExp('(\\?|&" + param + ")=[^&]*');
      if (p.test(cleanUrl)) {
        result = cleanUrl.replace(p, `$1=${value}`);
      } else {
        result = `${cleanUrl}&${param}=${value}`;
      }
    } else {
      result = `${cleanUrl}?${param}=${value}`;
    }
    return result;
  }

  static getParams(keyName, url) {
    const urlStr = url || window.location.href;
    // eslint-disable-next-line no-useless-escape
    const name = keyName.replace(/[\[\]]/g, '\\$&');
    const regex = new RegExp('[?&]'.concat(name, '(=([^&#]*)|&|#|$)'));
    const results = regex.exec(urlStr);

    if (!results) {
      return undefined;
    }
    if (!results[2]) {
      return undefined;
    }
    const res = decodeURIComponent(results[2].replace(/\+/g, ' '));
    if (res === 'false') {
      return false;
    }
    if (res === 'undefined') {
      return undefined;
    }
    if (res === 'null') {
      return null;
    }
    if (res === 'true') {
      return true;
    }
    return res;
  }

  static removeParams(keyName, urlStr) {
    const name = keyName.replace(/[\]]/g, '\\$&');
    const regex = new RegExp(`[?&]${name}(=([^&#]*(&|#|$))|&|#|$)`);
    return urlStr.replace(regex, (item) => {
      if (/^\?/.test(item)) {
        return '?';
      } if (/^&.+&$/.test(item)) {
        return '&';
      } if (/#$/.test(item)) {
        return '#';
      }
      return '';
    });
  }
}

15.浅拷贝

16.深拷贝

const clone = (originObj) => {
  const obj = originObj;

  if(obj === null || typeof obj !== 'object') {
    return obj;
  }

  if ('isActive' in obj) {
    throw new Error('循环引用警告')
  }

  let temp;
  if (obj instanceof Date) {
    temp = new obj.constructor(obj);
  } else {
    temp = obj.constructor();
  }

  Object.keys(obj).forEach((key) => {
    obj.isActive = null;
    temp[key] = clone(obj[key])
    delete obj.isActive;
  })
  return temp;
};

17.深度比较

function isEqual(obj1, obj2) {
  //不是对象,直接返回比较结果
  if (typeof obj1 !== 'object' || typeof obj2 !== 'object') {
    return obj1 === obj2
  }
  //都是对象,且地址相同,返回true
  if (obj2 === obj1) return true
  //是对象或数组
  let keys1 = Object.keys(obj1)
  let keys2 = Object.keys(obj2)
  //比较keys的个数,若不同,肯定不相等
  if (keys1.length !== keys2.length) return false
  for (let k of keys1) {
    //递归比较键值对
    let res = isEqual(obj1[k], obj2[k])
    if (!res) return false
  }
  return true
}

18.实现一个定时器

const countTimer = useRef<NodeJs.Timeout>(null);
// useRef<any>(null)
// let countTimer: NodeJs.Timeout | null
const [time, setTime] = useState('00:00:00');

//为什么用useRef和useState定义数据,不直接用let

const setTimeFlash = (diffTime: any) => {
  let s = Math.floor((diffTime / 1000) % 60);
  let m = Math.floor((diffTime / 1000 / 60) % 60);
  let h = Math.floor((diffTime / 1000 / 60 / 60) % 24);
  let arr = [h,m,s].map((item: number) => {
    if (item < 10) {
      item = `0${item}`
    }
    return item;
  })
  return {
    hour: arr[0],
    minute: arr[1],
    second: arr[2],
  };
}

const startCountdown = (endTime: any) => {
  let diffTime = new Date(endTime).getTime() - new Date().getTime()
  if (diffTime > 0) {
    countTimer.current = setInterval(() => {
      diffTime-=1000;
      if (diffTime <= 0) {
        clearInterval(countTimer)
      }
      setTime(setTimeFlash(diffTime));
    }, 1000)
  }
}

useEffect(() => {
  startCountdown('19:00:00');
  // 这里的作用
  return () => {
    clearInterval(countTimer);
  }
}, [])

19.使用setTimeout实现setInterval

function mySetinterval(callback, interval) {
  let timeoutId = null
  function repeat() {
    callback()
    timeoutId = setTimeout(repeat, interval)
  }

  repeat()

  return {
    cancel: () => clearTimeout(timeoutId),
  }
}

20.实现JSONP

function jsonp(url, callbackName, success) {
  const script = document.createElement('script')
  script.src = `${url}?callback=${callbackName}`
  document.body.appendChild(script)
  window['callbackName'] = (response) => {
    success(response)
    document.body.removeChild(script)
  }
}

jsonp(url, 'handleResponse', (response) => console.log(response))

// 服务端将返回响应
handleResponse({
  name: '树哥',
  age: 18,
})

21.实现ajax

function get() {
  //创建ajax实例
  let req = new XMLHTTPRequest()
  if (req) {
    //执行open 确定要访问的链接 以及同步异步
    req.open('GET', 'http://test.com/?keywords=手机', true)
    //监听请求状态
    req.onreadystatechange = function () {
      if (req.readystate === 4) {
        if (req.statue === 200) {
          console.log('success')
        } else {
          console.log('error')
        }
      }
    }
    //发送请求
    req.send()
  }
}

22.使用promise封装ajax

function get() {
  //创建ajax实例
  return new Promise((resolve, reject) => {
      let req = new XMLHTTPRequest()
      if (req) {
        //执行open 确定要访问的链接 以及同步异步
        req.open('GET', 'http://test.com/?keywords=手机', true)
        //监听请求状态
        req.onreadystatechange = function () {
          if (req.readystate === 4) {
            if (req.statue === 200) {
                reslove('success')
            } else {
                reject('error')
            }
          }
        }
        //发送请求
        req.send()
      }
  })
}

23.数组扁平化

function _flat(arr, depth) {
  if (!Array.isArray(arr) || depth <= 0) {
    return arr
  }
  return arr.reduce((prev, cur) => {
    if (Array.isArray(cur)) {
      return prev.concat(_flat(cur, depth - 1))
    } else {
      return prev.concat(cur)
    }
  }, [])
}

24.实现数组的push,map,filter

// push
Array.prototype.myPush = function (...args) {
  const length = this.length
  for (let i = 0; i < args.length; i++) {
    this[this.length + i] = args[i]
  }
  return this.length
}

// filter
Array.prototype.myFilter = function (callback) {
  const newArr = []
  for (let i = 0; i < this.length; i++) {
    if (callback(this[i], i, this)) {
      newArr.push(this[i])
    }
  }
  return newArr
}

// map
Array.prototype.myMap = function (callback) {
  const newArr = []
  for (let i = 0; i < this.length; i++) {
    newArr.push(callback(this[i], i, this))
  }
  return newArr
}

const list = [1, 2, 3, 4, 5]

console.log(list.myPush(6)) // 6
console.log(list.myFilter((i) => i > 3)) //[ 4, 5, 6 ]
console.log(list.myMap((i) => i + 1)) // [ 2, 3, 4, 5, 6, 7 ]

25.图片懒加载SDK

function lazyload() {
  let viewHeight = document.body.clientHeight //获取可视区高度
  let imgs = document.querySelectorAll('img[data-src]')
  imgs.forEach((item, index) => {
    if (item.dataset.src === '') return

    // 用于获得页面中某个元素的左,上,右和下分别相对浏览器视窗的位置
    let rect = item.getBoundingClientRect()
    if (rect.bottom >= 0 && rect.top < viewHeight) {
      item.src = item.dataset.src
      item.removeAttribute('data-src')
    }
  })
}
window.addEventListener('scroll', lazyload)
//加一个节流函数

26.实现单例模式

let cache
class A {
  // ...
}

function getInstance() {
  if (cache) return cache
  return (cache = new A())
}

const x = getInstance()
const y = getInstance()
console.log(x === y) // true

27.观察者模式

class Subject {
  constructor() {
    this.observers = []
  }

  addObserver(observer) {
    this.observers.push(observer)
  }
  removeObserver(observer) {
    this.observers = this.observers.filter((item) => item !== observer)
  }

  notify() {
    this.observers.forEach((observer) => observer.update())
  }
}

class Observer {
  constructor(data) {
    this.data = data
  }
  update() {
    console.log('data:', this.data)
  }
}

// 创建主题对象
const subject = new Subject()

// 创建两个观察者对象
const observer1 = new Observer('Hello啊,树哥!')
const observer2 = new Observer('Hello')

// 将观察者添加到主题对象中
subject.addObserver(observer1)
subject.addObserver(observer2)

// 通知观察者
subject.notify()
// data: Hello啊,树哥!
// data: Hello

28.发布订阅模式

class EventEmitter {
  constructor() {
    this.events = {}
  }

  on(eventName, callback) {
    if (!this.events[eventName]) {
      this.events[eventName] = []
    }
    this.events[eventName].push(callback)
  }

  emit(eventName, ...args) {
    const callbacks = this.events[eventName] || []
    callbacks.forEach((cb) => cb.apply(this, args))
  }

  off(eventName, callback) {
    const callbacks = this.events[eventName] || []
    const index = callbacks.indexOf(callback)
    if (index !== -1) {
      callbacks.splice(index, 1)
    }
  }

  // 只监听一次事件
  once(eventName, callback) {
    // 定义一个新函数 wrapper,它接收任意数量的参数,并在调用原始回调函数后通过 off() 方法将自己从订阅者集合中移除。
    const wrapper = (...args) => {
      callback.apply(this, args)
      this.off(eventName, wrapper)
    }
    // 传入的是封装后的 wrapper 函数
    this.on(eventName, wrapper)
  }
}

const emitter = new EventEmitter()

// 订阅 'event1' 事件
emitter.on('event1', function (data) {
  console.log(`event1 is triggered with data: ${data}`)
})

// 订阅 'event2' 事件
emitter.on('event2', function () {
  console.log('event2 is triggered')
})

// 触发 'event1' 事件
emitter.emit('event1', 'hello world')

// 移除订阅的 'event1' 事件
emitter.off('event1')

// 再次触发 'event1' 事件,但不会执行任何回调函数
emitter.emit('event1', 'foo bar')

// 再次触发 'event2' 事件
emitter.emit('event2')

const emitter = new EventEmitter()

// 只监听一次 'event1' 事件
emitter.once('event1', function (data) {
  console.log(`event1 is triggered with data: ${data}`)
})

// 触发 'event1' 事件
emitter.emit('event1', 'hello world')

// 再次触发 'event1' 事件,但不会执行任何回调函数
emitter.emit('event1', 'foo bar')

29.koa洋葱模型

function compose(middleware) {
    // 判断格式,数组形式,每一项是函数
    if(!Array.isArray(middleware)) return Promise.reject(new Error(''))
    for (const fun of middleware) {
        if(typeof fun !== 'function') {
            return Promise.reject(new Error(''))
        }
    }


    return function(ctx, next) {
        let i = -1;
        return dispatch(0)

        function dispatch(index) {
            if(index < i) return Promise.reject()
            i = index;
            
            let fn = middleware[index];
            // 最后一个
            if(index === middleware.length) fn = next;
            if(!fn) return Promise.resolve();
            
            try {
                return Promise.resolve(fn(ctx, dispatch.bind(null, index+1)));
            } catch (error) {
                return Promise.reject(error);
            }
        };
    }
}

30.设置请求字段

const http = require('http')
const server = http.createServer((req, res) => {
    if(req.url = '/') { 
        res.writeHead('200', { 'cache-control': 'max-age=10' //十秒 })
        res.setHeader('cache-control', 'max-age=10')
        res.cookie('user', 'aa');
        res.end()
    }
})
server.listen(8080, () => {})

Object.defineProperty和proxy

Object.defineProperty(obj, "name", { 
    enumerable: true, //可枚举属性,可以在 for...in 循环中被枚举 
    configurable: true, //可配置属性,可以使用 delete 运算符删除属性 
    //获取属性值的函数 
    get: function () { 
        console.log("获取,收集依赖");
        return demoBute 
    }, 
    //设置属性值的函数
    set: function (value) {
        console.log("更新,通知用户");
        demoBute = value; 
    }
})
const handler = {
    get(target, property, receiver) {
        console.log(`正在读取属性:${property}`);
        return target[property];
     },
     set(target, property, value, receiver) {
         console.log(`正在设置属性:${property},新值为:${value}`); 
         target[property] = value;
         return true;
     }
};
const proxy = new Proxy(obj, handler);

排序

1.冒泡排序

// 冒泡,时间复杂度O(n*n),空间O(1),前一个和后一个对比
function sort1(arr) {
    let len = arr.length;
    for(let i=0; i < len; i++) {
        for(let j=0; j < len-1-i; j++) {
            if (arr[j] > arr[j+1]) {
                let temp = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = temp; 
            }
        }
    }
}

2.插入排序

// 插入排序,时间复杂度o(n*n),假设第i个元素已经排好序了,
function arr3(arr) {
    for(let i=1; i< arr.length; i++) {
        let j = i-1;
        let current = arr[i];
        while(j > -1 && arr[j] > current) {
            arr[j+1] = arr[j];
            j--
        }
        arr[j+1] = current;
    }
    return arr;
}

3.选择排序

// 选择排序,时间复杂度o(n*n),找到数组中最小的,跟数组第一个交换位置,再找数组中最小的
function sort2(arr) {
    let minIndex = 0;
    for(let i=0; i<arr.length; i++) {
        minIndex = i;
        for(let j=i+1; j< arr.length; j++) {
            if (arr[j] < arr[minIndex]) {
                minIndex = j;
            }
        }
        if (i !== minIndex) {
            [arr[i], arr[minIndex]] = [arr[minIndex], arr[i]];
        }
    }
    return arr;
}

4.堆排序

function swap(arr, n, m) {
    let temp = arr[n];
    arr[n] = arr[m];
    arr[m] = temp;
  }
  
  function buildHeap(arr) {
    let len = arr.length;
    let start = Math.floor(len/2) - 1;
    for(let i = start; i >=0; i--) {
      heapify(arr, i, len)
    }
  }
  
  function heapify(arr, i, len) {
    let cur = i;
    let left = 2*i + 1;
    let right = 2*i + 2;
    if (left < len && arr[cur] < arr[left]) {
      cur = left;
    } 
    if (right < len && arr[cur] < arr[right]) {
      cur = right;
    }
    if (cur !== i) {
      // 交换
      swap(arr, cur, i);
      heapify(arr, cur, len);
    }
  }
  
  // 排序
  function sortHeap(arr) {
    buildHeap(arr);
    for(let s = arr.length - 1; s > 0; s--) {
      swap(arr, 0, s);
      heapify(arr, 0, s);
    }
    return arr;
  }
  
  console.log(sortHeap([9, 3, 5, 1, 6, 8, 4]))

算法

1.最长回文子串

var longestPalindrome = function(s) {
    if (s.length < 2) return s;
    let res = '';
    for(let i=0; i < s.length - 1; i++) {
        palindrome(i, i+1)
        palindrome(i, i)
    }
    function palindrome(m, n) {
        while(m >=0 && n < s.length && s[n] === s[m]) {
            n++;
            m--;
        }
        if ((n-m-1) > res.length) {
            res = s.slice(m + 1, n +1 -1);
        }
    }
    return res;
} //- 输入abbac,返回abba

2.括号生成

var generateParenthesis = function(n) {
   const res = []; // 输出的结果数组

    const generate = (str, left, right) => {
        if (str.length == 2 * n) {   // 字符串构建完成
            res.push(str);           // 将字符串加入res
            return;                  // 结束当前递归(结束当前搜索分支)
        }
        if (left > 0) {        // 只要左括号有剩,可以选它,继续递归做选择
            generate(str + '(', left - 1, right);
        }
        if (right > left) {    // 右括号的保有数量大于左括号的保有数量,才能选右括号
            generate(str + ')', left, right - 1);
        }
    };

    generate('', n, n); // 递归的入口,初始字符串是空字符串,初始括号数量都是n
    return res;
};

3.组合总和

var combinationSum = function(candidates, target) {
    const ans = [];
    const dfs = (target, combine, idx) => {
        if (idx === candidates.length) {
            return;
        }
        if (target === 0) {
            ans.push(combine);
            return;
        }
        // 直接跳过
        dfs(target, combine, idx + 1);
        // 选择当前数
        if (target - candidates[idx] >= 0) {
            dfs(target - candidates[idx], [...combine, candidates[idx]], idx);
        }
    }

    dfs(target, [], 0);
    return ans;
}; // 输入: candidates = [2,3,5], target = 8,输出: [[2,2,2,2],[2,3,3],[3,5]]

4.全排列

var permute = function(nums) {
    const res = [], path = [];
    backtracking(nums, nums.length, []);
    return res;
    
    function backtracking(n, k, used) {
        if(path.length === k) {
            res.push(Array.from(path));
            return;
        }
        for (let i = 0; i < k; i++ ) {
            if(used[i]) continue;
            path.push(n[i]);
            used[i] = true; // 同支
            backtracking(n, k, used);
            path.pop();
            used[i] = false;
        }
    }
}; // 输入: nums = [1,2,3],输出: [[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

5.下一个排列

var nextPermutation = function(nums) {
    let i = nums.length - 2;                   // 向左遍历,i从倒数第二开始是为了nums[i+1]要存在
    while (i >= 0 && nums[i] >= nums[i + 1]) { // 寻找第一个小于右邻居的数
        i--;
    }
    if (i >= 0) {                             // 这个数在数组中存在,从它身后挑一个数,和它换
        let j = nums.length - 1;                // 从最后一项,向左遍历
        while (j >= 0 && nums[j] <= nums[i]) {  // 寻找第一个大于 nums[i] 的数
            j--;
        }
        [nums[i], nums[j]] = [nums[j], nums[i]]; // 两数交换,实现变大
    }
    // 如果 i = -1,说明是递减排列,如 3 2 1,没有下一排列,直接翻转为最小排列:1 2 3
    let l = i + 1;           
    let r = nums.length - 1;
    while (l < r) {                            // i 右边的数进行翻转,使得变大的幅度小一些
        [nums[l], nums[r]] = [nums[r], nums[l]];
        l++;
        r--;
    }
};

6.两数相除

const MAX = 2147483647, MIN = -2147483648;
var divide = function(dividend, divisor) {
    if(dividend == MIN && divisor == -1)
        return MAX;
    let a = Math.abs(dividend), b = Math.abs(divisor), res = 0;
    for(let i=31;i>=0;i--){
        if((a>>>i)>=b){
            // 1<<31 = -2147483648,需特殊处理
            if(i==31){
                a -= MAX;
                a -= 1;
                res -= MIN;
            } else{
                a -= b<<i;
                res += 1<<i;
            }
        }
    }
    return (dividend > 0) == (divisor > 0) ? res : -res;
};

7.盛最多水的容器

var maxArea = function(height) {
    let maxRes = 0;
    let i = 0, j = height.length - 1;
    while(i < j) {
        const max = Math.min(height[i], height[j]) * (j - i)
        maxRes = maxRes > max ? maxRes : max;
        if(height[i] <= height[j]) {
            i++;
        } else {
            j--;
        }
    }
    return maxRes;
};

8.三数之和

 var threeSum = function(nums) {
    if (nums.length < 3) return [];
    let res = [];
    nums.sort((a, b) => a - b);
    for(let i=0; i < nums.length - 2; i++) {
        if (nums[i] > 0) return res;
        if (i > 0 && nums[i] === nums[i-1]) continue;
        let j = i+1;
        let k = nums.length - 1;
        while(j < k) {
            if (nums[i] + nums[j] + nums[k] === 0) {
                res.push([nums[i], nums[j], nums[k]]);
                //
                while(j < k && nums[j] === nums[j+1]) j++;
                while(j < k && nums[k] === nums[k-1]) k--; 
                j++;
                k--;
            }
            if (nums[i] + nums[j] + nums[k] > 0) {
                k--;
            }
            if (nums[i] + nums[j] + nums[k] < 0) {
                j++;
            }
        }
    }
    return res;
};

链表

1.环形链表

var hasCycle = function(head) {
    let map = new Map();
    while(head) {
        if(map.has(head)) return true;
        map.set(head, true)
        head = head.next;
    }
    return false
};

2.删除链表中的倒数第n个节点

var removeNthFromEnd = function(head, n) {
    let slow = head, fast = head;
    // 先让 fast 往后移 n 位
    while(n--) {
        fast = fast.next;
    }

    // 如果 n 和 链表中总结点个数相同,即要删除的是链表头结点时,fast 经过上一步已经到外面了
    if(!fast) {
        return head.next;
    }

    // 然后 快慢指针 一起往后遍历,当 fast 是链表最后一个结点时,此时 slow 下一个就是要删除的结点
    while(fast.next) {
        slow = slow.next;
        fast = fast.next;
    }
    slow.next = slow.next.next;

    return head;
};

3.合并两个有序链表

var mergeTwoLists = function(list1, list2) {
    let resHead = new ListNode(-1);
    let prev = resHead;
    while(list1 !== null && list2 !== null) {
        if(list1.val <= list2.val) {
            prev.next = list1;
            list1 = list1.next
        } else {
            prev.next = list2;
            list2 = list2.next;
        }
        prev = prev.next;
    }
    prev.next = list1 === null ? list2 : list1;
    return resHead.next
};

4.两两交换链表中的节点

var swapPairs = function(head) {
    const dummyHead = new ListNode(0);
    dummyHead.next = head;
    let temp = dummyHead;
    while (temp.next !== null && temp.next.next !== null) {
        const node1 = temp.next;
        const node2 = temp.next.next;
        temp.next = node2;
        node1.next = node2.next;
        node2.next = node1;
        temp = node1;
    }
    return dummyHead.next;
};

5.K个一组翻转链表

const myReverse = (head, tail) => {
    let prev = tail.next;
    let p = head;
    while (prev !== tail) {
        const nex = p.next;
        p.next = prev;
        prev = p;
        p = nex;
    }
    return [tail, head];
}
var reverseKGroup = function(head, k) {
    const hair = new ListNode(0);
    hair.next = head;
    let pre = hair;

    while (head) {
        let tail = pre;
        // 查看剩余部分长度是否大于等于 k
        for (let i = 0; i < k; ++i) {
            tail = tail.next;
            if (!tail) {
                return hair.next;
            }
        }
        const nex = tail.next;
        [head, tail] = myReverse(head, tail);
        // 把子链表重新接回原链表
        pre.next = head;
        tail.next = nex;
        pre = tail;
        head = tail.next;
    }
    return hair.next;
};

6.删除排序链表中的重复元素

var deleteDuplicates = function(head) {
    if (!head) {
        return head;
    }

    const dummy = new ListNode(0, head);

    let cur = dummy;
    while (cur.next && cur.next.next) {
        if (cur.next.val === cur.next.next.val) {
            const x = cur.next.val;
            while (cur.next && cur.next.val === x) {
                cur.next = cur.next.next;
            } 
        } else {
            cur = cur.next;
        }
    }
    return dummy.next;
};

7.旋转链表

var rotateRight = function(head, k) {
    if (k === 0 || !head || !head.next) {
        return head;
    }
    let n = 1;
    let cur = head;
    while (cur.next) {
        cur = cur.next;
        n++;
    }

    let add = n - k % n;
    if (add === n) {
        return head;
    }

    cur.next = head;
    while (add) {
        cur = cur.next;
        add--;
    }

    const ret = cur.next;
    cur.next = null;
    return ret;
};

8.反转链表

var reverseList = function(head) {
    let prev = null;
    let curr = head;
    while (curr) {
        const next = curr.next;
        curr.next = prev;
        prev = curr;
        curr = next;
    }
    return prev;
};

9.两数相加

var addTwoNumbers = function(l1, l2) {
    let head = null, tail = null;
    let carry = 0;
    while (l1 || l2) {
        const n1 = l1 ? l1.val : 0;
        const n2 = l2 ? l2.val : 0;
        const sum = n1 + n2 + carry;
        if (!head) {
            head = tail = new ListNode(sum % 10);
        } else {
            tail.next = new ListNode(sum % 10);
            tail = tail.next;
        }
        carry = Math.floor(sum / 10);
        if (l1) {
            l1 = l1.next;
        }
        if (l2) {
            l2 = l2.next;
        }
    }
    if (carry > 0) {
        tail.next = new ListNode(carry);
    }
    return head;
};

二叉树的中序遍历

var inorderTraversal = function(root) {
    const res = [];
    const inorder = (root) => {
        if (!root) {
            return;
        }
        inorder(root.left);
        res.push(root.val);
        inorder(root.right);
    }
    inorder(root);
    return res;
}; // 输入: root = [1,null,2,3],输出: [1,3,2]

二叉树的层序遍历

var levelOrder = function(root) {
    const ret = [];
    if (!root) {
        return ret;
    }

    const q = [];
    q.push(root);
    while (q.length !== 0) {
        const currentLevelSize = q.length;
        ret.push([]);
        for (let i = 1; i <= currentLevelSize; ++i) {
            const node = q.shift();
            ret[ret.length - 1].push(node.val);
            if (node.left) q.push(node.left);
            if (node.right) q.push(node.right);
        }
    }
        
    return ret;
}; // 输入: root = [3,9,20,null,null,15,7]输出: [[3],[9,20],[15,7]]

不同的二叉搜索树

var numTrees = function(n) {
    var numTrees = function(n) {
        const G = new Array(n + 1).fill(0);
        G[0] = 1;
        G[1] = 1;

        for (let i = 2; i <= n; ++i) {
            for (let j = 1; j <= i; ++j) {
                G[i] += G[j - 1] * G[i - j];
            }
        }
        return G[n];
    };
}; // 输入n=3,输出5

验证二叉搜索树

const helper = (root, lower, upper) => {
    if (root === null) {
        return true;
    }
    if (root.val <= lower || root.val >= upper) {
        return false;
    }
    return helper(root.left, lower, root.val) && helper(root.right, root.val, upper);
}
var isValidBST = function(root) {
    return helper(root, -Infinity, Infinity);
}; // 输入: root = [5,1,4,null,null,3,6],输出: false

恢复二叉搜索树

const swap = (x, y) => {
    const temp = x.val;
    x.val = y.val;
    y.val = temp;
}

var recoverTree = function(root) {
    const stack = [];
    let x = null, y = null, pred = null;

    while (stack.length || root !== null) {
      while (root !== null) {
        stack.push(root);
        root = root.left;
      }
      root = stack.pop();
      if (pred !== null && root.val < pred.val) {
        y = root;
        if (x === null) {
            x = pred;
        }
        else break;
      }
      pred = root;
      root = root.right;
    }
    swap(x, y);
};

二叉树展开为链表

var flatten = function(root) {
    const list = [];
    preorderTraversal(root, list);
    const size = list.length;
    for (let i = 1; i < size; i++) {
        const prev = list[i - 1], curr = list[i];
        prev.left = null;
        prev.right = curr;
    }
};

const preorderTraversal = (root, list) => {
    if (root != null) {
        list.push(root);
        preorderTraversal(root.left, list);
        preorderTraversal(root.right, list);
    }
}