前端面试必备的65道算法题附答案

1,319 阅读11分钟

手写

深拷贝

function deepClone(obj = {}) {
  if (typeof obj !== "object" || obj == null) {
    return obj;
  }
  let result = {};
  if (obj instanceof Array) {
    result = [];
  }
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      result[key] = deepClone(obj[key]);
    }
  }
  return result;
}

手写 apply

Function.prototype.myapply = function (context, args) {
  //这里默认不传就是给window,也可以用es6给参数设置默认参数
  context = context || window;
  args = args ? args : [];
  //给context新增一个独一无二的属性以免覆盖原有属性
  const key = Symbol();
  context[key] = this; //this就是fn1
  //通过隐式绑定的方式调用函数
  const result = context[key](...args);
  //删除添加的属性
  delete context[key];
  //返回函数调用的返回值
  return result;
};
function fn1(a, b, c) {
  console.log("this", this);
  console.log(a, b, c);
  return "this is fn1";
}
const fn2 = fn1.myapply({ x: 100 }, []);

手写 call

Function.prototype.mycall = function (context, ...args) {
  //这里默认不传就是给window,也可以用es6给参数设置默认参数
  context = context || window;
  args = args ? args : [];
  //给context新增一个独一无二的属性以免覆盖原有属性
  const key = Symbol();
  context[key] = this;
  //通过隐式绑定的方式调用函数
  const result = context[key](...args);
  //删除添加的属性
  delete context[key];
  //返回函数调用的返回值
  return result;
};

手写 bind

//手写bind
Function.prototype.mybind = function(context, ...args){
    const fn = this// 获取绑定mybind的function
    args = args ? args : []
    // 返回一个函数
    return function(){
        return fn.apply(context, args);
    }
}

function fn1(a, b, c){
    console.log('this', this);
    console.log(a,b,c);
    return 'this is fn1';
}

const fn2 = fn1.mybind({x:100}, 10. 20. 30);
const res = fn2():
console.log(res);

手写 instanceof

function new_instance_of(leftVaule, rightVaule) {
  let rightProto = rightVaule.prototype; // 取右表达式的 prototype 值
  leftVaule = leftVaule.__proto__; // 取左表达式的__proto__值
  while (true) {
    if (leftVaule === null) {
      return false;
    }
    if (leftVaule === rightProto) {
      return true;
    }
    leftVaule = leftVaule.__proto__;
  }
}

写一个简单的 jQuery

class jQuery {
  constructor(selector) {
    const result = document.querySelectorAll(selector);
    const length = result.length;
    for (let i = 0; i < length; i++) {
      this[i] = result[i];
    }
    this.length = length;
    this.selector = selector;
  }
  get(index) {
    return this[index];
  }
  each(fn) {
    for (let i = 0; i < this.length; i++) {
      const elem = this[i];
      fn(elem);
    }
  }
  on(type, fn) {
    return this.each((elem) => {
      elem.addEventListener(type, fn, false);
    });
  }
  // 扩展很多 DOM API
}

// 插件
jQuery.prototype.dialog = function (info) {
  alert(info);
};

// “造轮子”
class myJQuery extends jQuery {
  constructor(selector) {
    super(selector);
  }
  // 扩展自己的方法
  addClass(className) {}
  style(data) {}
}

const $p = new jQuery("p");
$p.get(1);
$p.each((elem) => console.log(elem.nodeName));
$p.on("click", () => alert("clicked"));

实现一个事件绑定 Event 类

class Event {
  constructor() {
    this._cache = {}; // 为了查找迅速使用了对象
  }
  on(eventName, callback) {
    // 绑定事件
    // 如果有就放入, 没有就新建, 然后再看下是否有放入函数,没有就加入
    let fns = (this._cache[eventName] = this._cache[eventName] || []);
    if (fns.indexOf(callback === -1)) {
      // 如果事件方法没有的话就放入到字典进去
      fns.push(callback);
    }
    return this;
  }
  once(eventName, callback) {
    callback.__once = true;
    this.on(eventName, callback);
  }
  trigger(eventName, data) {
    // 触发事件
    // 看下字典里有没有这个函数名字, 有的话就触发它
    let fns = this._cache[eventName];
    if (Array.isArray(fns)) {
      // 有的话就对立面的每一个function传入参数data
      fns.forEach((fn) => {
        fn(data);
        if (fn.__once) {
          this.off(eventName, fn);
        }
      });
    }
    return this;
  }
  off(eventName, callback) {
    //解绑
    let fns = this._cache[eventName];
    if (Array.isArray(fns)) {
      if (callback) {
        let index = fns.indexOf(callback);
        if (index !== -1) {
          fns.splice(index, 1);
        }
      } else {
        fns.length = 0; // 全部清空
      }
    }
    return this;
  }
}
const event = new Event();
event.on("test", (a) => {
  console.log(a);
});
event.trigger("thet", "hello world"); // 绑定后就输出
event.off("test");
event.trigger("test", "hello world"); // 解绑后就不显示了

手写简易的 ajax

function ajax(url) {
  const p = new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.open("GET", url, true);
    xhr.onreadystatechange = function () {
      if (xhr.readyState === 4) {
        if (xhr.status === 200) {
          resolve(JSON.parse(xhr.responseText));
        } else if (xhr.status === 404 || xhr.status === 500) {
          reject(new Error("404 not found"));
        }
      }
    };
    xhr.send(null);
  });
  return p;
}

const url = "/data/test.json";
ajax(url)
  .then((res) => console.log(res))
  .catch((err) => console.error(err));

实现 promise

function Promise(fn) {
  // Promise resolve时的回调函数集
  this.cbs = [];

  // 传递给Promise处理函数的resolve
  // 这里直接往实例上挂个data
  // 然后把onResolvedCallback数组里的函数依次执行一遍就可以
  const resolve = (value) => {
    // 注意promise的then函数需要异步执行
    setTimeout(() => {
      this.data = value;
      this.cbs.forEach((cb) => cb(value));
    });
  };

  // 执行用户传入的函数
  // 并且把resolve方法交给用户执行
  fn(resolve);
}

Promise.prototype.then = function (onResolved) {
  // 这里叫做promise2
  return new Promise((resolve) => {
    this.cbs.push(() => {
      const res = onResolved(this.data);
      if (res instanceof Promise) {
        // resolve的权力被交给了user promise
        res.then(resolve);
      } else {
        // 如果是普通值 就直接resolve
        // 依次执行cbs里的函数 并且把值传递给cbs
        resolve(res);
      }
    });
  });
};

new Promise((resolve) => {
  setTimeout(() => {
    resolve(1);
  }, 500);
})
  .then((res) => {
    console.log(res);
    return new Promise((resolve) => {
      setTimeout(() => {
        resolve(2);
      }, 500);
    });
  })
  .then(console.log);

实现 Promise.all 方法

function promiseAll(promises) {
  return new Promise(function (resolve, reject) {
    if (!isArray(promises)) {
      return reject(new TypeError("arguments must be an array"));
    }
    var resolvedCounter = 0;
    var promiseNum = promises.length;
    var resolvedValues = new Array(promiseNum);
    for (var i = 0; i < promiseNum; i++) {
      (function (i) {
        Promise.resolve(promises[i]).then(
          function (value) {
            resolvedCounter++;
            resolvedValues[i] = value;
            if (resolvedCounter == promiseNum) {
              return resolve(resolvedValues);
            }
          },
          function (reason) {
            return reject(reason);
          }
        );
      })(i);
    }
  });
}

利用 promise sleep 函数实现

const sleep = (delay) => new Promise((resolve) => setTimeout(resolve, delay));

const repeatedGreetings = async () => {
  await sleep(1000);
  console.log(1);
  await sleep(1000);
  console.log(2);
  await sleep(1000);
  console.log(3);
};
repeatedGreetings();

手动控制并发请求

function multiRequest(urls = [], maxNum) {
  const len = urls.length; // 请求总数量
  const result = new Array(len).fill(false); // 根据请求数量创建一个数组来保存请求的结果
  let count = 0; // 当前完成的数量

  return new Promise((resolve, reject) => {
    while (count < maxNum) {
      // 请求maxNum个
      next();
    }
    function next() {
      let current = count++;
      if (current >= len) {
        // 处理边界条件
        // 请求全部完成就将promise置为成功状态, 然后将result作为promise值返回
        !result.includes(false) && resolve(result);
        return;
      }
      const url = urls[current];
      console.log(`开始 ${current}`, new Date().toLocaleString());
      fetch(url)
        .then((res) => {
          result[current] = res; // 保存请求结果
          console.log(`完成 ${current}`, new Date().toLocaleString());
          if (current < len) {
            // 请求没有全部完成, 就递归
            next();
          }
        })
        .catch((err) => {
          console.log(`结束 ${current}`, new Date().toLocaleString());
          result[current] = err;
          if (current < len) {
            // 请求没有全部完成, 就递归
            next();
          }
        });
    }
  });
}

实现一个发布订阅模式

class Subjects {
  constructor() {
    this.subs = [];
    this.state = 0;
  }
  addSubs(sub) {
    var isExsit = this.subs.includes(sub);
    if (isExsit) {
      return console.log("sub existed");
    }
    this.subs.push(sub);
  }
  removeSubs(sub) {
    var index = this.subs.indexOf(sub);
    if (index === -1) {
      return console.log("sub not exist");
    }
    this.subs.splice(index, 1);
  }
  notify() {
    console.log("sub update");
    this.subs.forEach((sub) => sub.update(this.state));
  }
  doSomeLogic() {
    console.log("doSomeLogic");
    this.state = Math.floor(Math.random() * 10);
    this.notify();
  }
}
class Observer {
  constructor(name) {
    this.name = name;
  }
  update(state) {
    console.log(this.name + " Recived state" + state);
  }
}
var observerA = new Observer("A");
var observerB = new Observer("B");
var subject = new Subjects();
subject.addSubs(observerA);
subject.addSubs(observerB);
subject.doSomeLogic();
subject.doSomeLogic();
subject.removeSubs(observerB);
subject.doSomeLogic();

手写防抖 debounce

const input1 = document.getElementById("input1");
function debounce(fn, delay = 500) {
  // timer 是闭包中的
  let timer = null;

  return function () {
    if (timer) {
      clearTimeout(timer);
    }
    timer = setTimeout(() => {
      fn.apply(this, arguments);
      timer = null;
    }, delay);
  };
}

input1.addEventListener(
  "keyup",
  debounce(function (e) {
    console.log(e.target);
    console.log(input1.value);
  }, 600)
);

手写节流 throttle

const div1 = document.getElementById("div1");
function throttle(fn, delay = 100) {
  let timer = null;

  return function () {
    if (timer) {
      return;
    }
    timer = setTimeout(() => {
      fn.apply(this, arguments);
      timer = null;
    }, delay);
  };
}

div1.addEventListener(
  "drag",
  throttle(function (e) {
    console.log(e.offsetX, e.offsetY);
  })
);

手写深度比较 isEqual

// 判断是否是对象或数组
function isObject(obj) {
  return typeof obj === "object" && obj !== null;
}
// 全相等(深度)
function isEqual(obj1, obj2) {
  if (!isObject(obj1) || !isObject(obj2)) {
    //处理非对象的比较

    // 值类型(注意,参与 equal 的一般不会是函数)
    return obj1 === obj2;
  }
  if (obj1 === obj2) {
    // 处理两个相同值
    return true;
  }
  // 两个都是对象或数组,而且不相等
  // 1. 先取出 obj1 和 obj2 的 keys ,比较个数
  const obj1Keys = Object.keys(obj1);
  const obj2Keys = Object.keys(obj2);
  if (obj1Keys.length !== obj2Keys.length) {
    //key 的长度不同
    return false;
  }
  // 2. 以 obj1 为基准,和 obj2 一次递归比较
  for (let key in obj1) {
    // 比较当前 key 的 val —— 递归!!!
    const res = isEqual(obj1[key], obj2[key]);
    if (!res) {
      return false;
    }
  }
  // 3. 全相等
  return true;
}

函数柯里化

//add(1)(2)(3) = 6;
//add(1, 2, 3)(4) = 10;
//add(1)(2)(3)(4)(5) = 15;
function add(...args) {
  let allArg = args;
  var _adder = function () {
    allArg.push(...arguments);
    return _adder;
  };
  _adder.toString = function () {
    return allArg.reduce(function (a, b) {
      return a + b;
    }, 0);
  };
  return _adder;
}
console.log(add(1)(2)(3).toString()); //6
console.log(add(1, 2, 3)(4).toString()); //10
console.log(add(1)(2)(3)(4)(5).toString()); //15
console.log(add(2, 6)(1).toString()); //9

解析 URL 参数为对象

function parseParam(url) {
  const paramsStr = /.+\?(.+)$/.exec(url)[1]; // 将 ? 后面的字符串取出来
  const paramsArr = paramsStr.split("&"); // 将字符串以 & 分割后存到数组中
  let paramsObj = {};
  // 将 params 存到对象中
  paramsArr.forEach((param) => {
    if (/=/.test(param)) {
      // 处理有 value 的参数
      let [key, val] = param.split("="); // 分割 key 和 value
      val = decodeURIComponent(val); // 解码
      val = /^\d+$/.test(val) ? parseFloat(val) : val; // 判断是否转为数字

      if (paramsObj.hasOwnProperty(key)) {
        // 如果对象有 key,则添加一个值
        paramsObj[key] = [].concat(paramsObj[key], val);
      } else {
        // 如果对象没有这个 key,创建 key 并设置值
        paramsObj[key] = val;
      }
    } else {
      // 处理没有 value 的参数
      paramsObj[param] = true;
    }
  });

  return paramsObj;
}

实现 36 进制

// 提供36位的表达 0-9 a-z
function getNums36() {
  var nums36 = [];
  for (var i = 0; i < 36; i++) {
    if (i >= 0 && i <= 9) {
      nums36.push(i);
    } else {
      nums36.push(String.fromCharCode(i + 87)); //将 Unicode 编码转为一个字符
    }
  }
  return nums36;
}
function scale36(n) {
  // 单独的功能函数 // 16进制数: 0-9  a-f    36进制数: 0-9  a-z
  const arr = [];
  var nums36 = getNums36();
  // 36 10
  if (!Number.isInteger(n)) {
    //浮点数判断,目前不支持小鼠
    console.warn("不支持小数转换");
    return n;
  }
  var neg = "";
  if (n < 0) {
    //对负数的处理
    neg = "-";
    n = Math.abs(n);
  }
  while (n) {
    var res = n % 36;
    console.log(res, "+++++++");
    arr.unshift(nums36[res]);
    // 进位
    n = parseInt(n / 36);
    console.log(n, "---------");
  }
  arr.unshift(neg);
  return arr.join("");
}

console.log(scale36(20)); // k

实现一个对象被 for of 遍历

let obj2 = {
  name: "XX",
  age: 20,
  job: "teacher",
  [Symbol.iterator]() {
    const self = this;
    const keys = Object.keys(self);
    let index = 0;
    return {
      next() {
        if (index < keys.length) {
          return {
            value: self[keys[index++]],
            done: false,
          };
        } else {
          return { value: undefined, done: true };
        }
      },
    };
  },
};

实现洋葱模型

let middleware = [];
middleware.push((next) => {
  console.log(1);
  next();
  console.log(1.1);
});
middleware.push((next) => {
  console.log(2);
  next();
  console.log(2.1);
});
middleware.push((next) => {
  console.log(3);
  next();
  console.log(3.1);
});
//实现compose函数
// /* 输出
// 1
// 2
// 3
// 3.1
// 2.1
// 1.1
// */
function compose(fnArr) {
  let onIndex = -1;
  let next = function () {
    onIndex++;
    if (onIndex < fnArr.length) {
      fnArr[onIndex](next);
    }
  };
  return next;
}

let fn = compose(middleware);
fn();

字符串/数组

手写数组 flatern(数组拍平)

//使用apply
function flat(arr) {
  // 验证 arr 中,还有没有深层数组 [1, 2, [3, 4]]
  const isDeep = arr.some((item) => item instanceof Array);
  if (!isDeep) {
    return arr; // 已经是 flatern [1, 2, 3, 4]
  }

  const res = Array.prototype.concat.apply([], arr);
  return flat(res); // 递归
}

const res = flat([1, 2, [3, 4, [10, 20, [100, 200]]], 5]);
console.log(res);

//使用展开运算符
function arrf(arr) {
  let res = [];
  for (let i = 0; i < arr.length; i++) {
    if (arr[i] instanceof Array) {
      res = res.concat(arrf(arr[i]));
    } else {
      res.push(arr[i]);
    }
  }
  return res;
}

冒泡排序

遍历整个数组,将数组的每一项与其后一项进行对比,如果不符合要求就交换位置,一共遍历 n 轮,n 为数组的长度。n 轮之后,数组得以完全排序。时间复杂度 O(n^2)。

function bubbleSort(arr) {
  //console.time('BubbleSort');
  // 获取数组长度,以确定循环次数。
  let len = arr.length;
  // 遍历数组len次,以确保数组被完全排序。
  for (let i = 0; i < len; i++) {
    // 遍历数组的前len-i项,忽略后面的i项(已排序部分)。
    for (let j = 0; j < len - 1 - i; j++) {
      // 将每一项与后一项进行对比,不符合要求的就换位。
      if (arr[j] > arr[j + 1]) {
        //从小到大排序
        [arr[j + 1], arr[j]] = [arr[j], arr[j + 1]];
      }
    }
  }
  //console.timeEnd('BubbleSort');
  return arr;
}

快速排序

在数组中选取一个参考点(pivot),然后对于数组中的每一项,大于 pivot 的项都放到数组右边,小于 pivot 的项都放到左边,左右两边的数组项可以构成两个新的数组(left 和 right),然后继续分别对 left 和 right 进行分解,直到数组长度为 1,最后合并(其实没有合并,因为是在原数组的基础上操作的,只是理论上的进行了数组分解)。

//递归
function quickSort(arr) {
  // 当数组长度不大于1时,返回结果,防止callstack溢出。
  if (arr.length <= 1) return arr;
  return [
    // 递归调用quickSort,通过Array.prototype.filter方法过滤小于arr[0]的值,注意去掉了arr[0]以防止出现死循环。
    ...quickSort(arr.slice(1).filter((item) => item < arr[0])),
    arr[0],
    ...quickSort(arr.slice(1).filter((item) => item >= arr[0])),
  ];
}
//非递归
const quickSort1 = (arr) => {
  if (arr.length <= 1) {
    return arr;
  }
  //取基准点
  const midIndex = Math.floor(arr.length / 2);
  //取基准点的值,splice(index,1) 则返回的是含有被删除的元素的数组。
  const valArr = arr.splice(midIndex, 1);
  const midIndexVal = valArr[0];
  const left = []; //存放比基准点小的数组
  const right = []; //存放比基准点大的数组
  //遍历数组,进行判断分配
  for (let i = 0; i < arr.length; i++) {
    if (arr[i] < midIndexVal) {
      left.push(arr[i]); //比基准点小的放在左边数组
    } else {
      right.push(arr[i]); //比基准点大的放在右边数组
    }
  }
  //递归执行以上操作,对左右两个数组进行操作,直到数组长度为 <= 1
  return quickSort1(left).concat(midIndexVal, quickSort1(right));
};
const array2 = [5, 4, 3, 2, 1];
console.log("quickSort1 ", quickSort1(array2));
// quickSort1: [1, 2, 3, 4, 5]

归并排序

排序一个数组,我们先把数组从中间分成前后两部分,然后对前后两部分分别排序,再将排好序的两部分合并在一起,这样整个数组就都有序了。

const mergeSort = (arr) => {
  //采用自上而下的递归方法
  const len = arr.length;
  if (len < 2) {
    return arr;
  }
  // length >> 1 和 Math.floor(len / 2) 等价
  let middle = Math.floor(len / 2),
    left = arr.slice(0, middle),
    right = arr.slice(middle); // 拆分为两个子数组
  return merge(mergeSort(left), mergeSort(right));
};

const merge = (left, right) => {
  const result = [];

  while (left.length && right.length) {
    // 注意: 判断的条件是小于或等于,如果只是小于,那么排序将不稳定.
    if (left[0] <= right[0]) {
      result.push(left.shift());
    } else {
      result.push(right.shift());
    }
  }

  while (left.length) result.push(left.shift());

  while (right.length) result.push(right.shift());

  return result;
};

下一个排列

var nextPermutation = function (nums) {
  // 从又往左找到第一个降序的位置
  let right = nums.length - 1;
  let flag = false;
  while (right) {
    if (nums[right] > nums[right - 1]) {
      right--;
      flag = true;
      break;
    } else {
      right--;
    }
  }
  if (!flag) {
    nums.sort((next, pre) => next - pre);
  } else {
    let sorted = nums.splice(right + 1).sort((next, pre) => next - pre);
    let move;
    for (let i = 0; i < sorted.length; i++) {
      if (sorted[i] > nums[right]) {
        move = i;
        break;
      }
    }
    let temp = sorted[move];
    sorted[move] = nums[right];
    nums[right] = temp;
    sorted.sort((next, pre) => next - pre);
    nums.push(...sorted);
  }
  nums;
};

实现[['a', 'b'], ['n', 'm'], ['0', '1']] => ['an0', 'an1, 'am0', 'am1', 'bn0', 'bn1', 'bm0', 'bm1']

function changeArr(arr) {
  // 赋值:赋值给一个新的对象,这样修改之后不会影响之前的值
  const newArr = [...arr];
  // 取值:获取数组的第一个值
  let result = newArr.shift();
  // 循环这个数组
  while (newArr.length) {
    // 取值:从这个数组中再次获取第一个值
    const other = newArr.shift();
    // 定义一个新的数组为 []
    const newResult = [];
    // 循环 result
    result.forEach((item) => {
      // 循环 other
      other.forEach((_item) => {
        // 把数据组合返回给定义的数组
        newResult.push(item + "" + _item);
      });
    });
    // 把 result 赋值给 newResult
    result = [...newResult];
  }
  return result;
}

const arr = [
  ["a", "b"],
  ["m", "n"],
  [0, 1],
];
const result = changeArr(arr);
console.log(result); // ["am0", "am1", "an0", "an1", "bm0", "bm1", "bn0", "bn1"]

const arr2 = [
  ["a", "b"],
  ["m", "n", "0"],
  [0, 1],
  ["#", "$"],
];
const result2 = changeArr(arr2);
console.log(result2);
// (24) ["am0#", "am0$", "am1#", "am1$", "an0#", "an0$", "an1#", "an1$", "a00#", "a00$", "a01#", "a01$", "bm0#", "bm0$", "bm1#", "bm1$", "bn0#", "bn0$", "bn1#", "bn1$", "b00#", "b00$", "b01#", "b01$"]

合并区间

//输入: intervals = [[1,3],[2,6],[8,10],[15,18]]
//输出: [[1,6],[8,10],[15,18]]
//解释: 区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].
var merge = function (intervals) {
  if (intervals.length <= 1) {
    return intervals;
  }

  // 先将数组按照区间最左边的大小顺序排序(升序)
  let arr = intervals.sort((a, b) => a[0] - b[0]);
  function unite(arr, i) {
    if (i == arr.length - 1) {
      return arr;
    }
    // 如果下一个区间的左区间在本区间之间,则合并一次
    if (arr[i + 1][0] <= arr[i][1]) {
      arr[i] = [arr[i][0], Math.max(arr[i][1], arr[i + 1][1])];
      // 合并之后删除冗余区间
      arr.splice(i + 1, 1);
    } else {
      // 如果没有合并,则找到下一个待合并区间
      i++;
    }
    return unite(arr, i);
  }

  return unite(arr, 0);
};

两数之和:给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。输入:nums = [2,7,11,15], target = 9 输出:[0,1]

/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number[]}
 */
var twoSum = function (nums, target) {
  for (let i = 0; i < nums.length; i++) {
    let onitem = nums[i];
    let next = nums.indexOf(target - onitem);
    if (next !== -1 && next !== i) {
      return [i, next];
    }
  }
};

三数之和:给你一个包含 n 个整数的数组  nums,判断  nums  中是否存在三个元素 a,b,c ,使得  a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。

var threeSum = function (nums) {
  // 如果元素的个数小于4,直接返回空数组
  if (nums.length < 3) {
    return [];
  }
  let res = [];
  nums.sort((num1, num2) => num1 - num2);
  for (let i = 0; i < nums.length - 2; i++) {
    if (i > 0 && nums[i] === nums[i - 1]) {
      continue;
    }
    let left = i + 1;
    let right = nums.length - 1;
    while (left < right) {
      if (nums[i] + nums[left] + nums[right] === 0) {
        if (nums[left] === nums[left + 1] && right > left + 1) {
          left++;
          continue;
        } else if (nums[right] === nums[right - 1] && right > left + 1) {
          right--;
          continue;
        } else {
          res.push([nums[i], nums[left], nums[right]]);
          left++;
        }
      } else if (nums[i] + nums[left] + nums[right] < 0) {
        left++;
      } else {
        right--;
      }
    }
  }
  return res;
};

搜索旋转排序数组

 /**
 * 二分法
 */
public int search(int[] nums, int target) {
    int len = nums.length;
    int left = 0;   // 左边界
    int right = len -1; // 右边界
    while (left <= right) {
        int mid = (right + left) / 2;
        if (nums[mid] == target) {
            return mid;
        }
        // 右半边为升序
        else if (nums[mid] < nums[right]) {
            if (nums[mid] < target && target <= nums[right]) {
                // 如果值在右半边,则丢弃左半边
                left = mid + 1;
            } else {
                // 其他情况
                right = mid - 1;
            }
        }
        // 左半边升序
        else {
            if (nums[left] <= target && target < nums[mid]) {
                // 如果值在左半边,则丢弃右半边
                right = mid - 1;
            } else {
                left = mid + 1;
            }
        }
    }
    return -1;
}

寻找两个正序数组的中位数

var findMedianSortedArrays = function (nums1, nums2) {
  let res = [];
  if (nums1.length < 1) {
    res = nums2;
  }
  if (nums2.length < 1) {
    res = nums1;
  }
  res = nums2.concat(nums1);
  res = res.sort((a, b) => a - b);
  let i1 = Math.ceil(res.length / 2);
  let i2 = Math.floor(res.length / 2);
  if (i1 === i2) {
    return (res[i1 - 1] + res[i1]) / 2;
  } else {
    return res[i2];
  }
};

全排列

var permute = function (nums) {
  const res = [];

  // 回溯
  const backtrack = (path) => {
    // 终点,当 path 的 length 和 nums 的 length 相等的时候,记录这一次的 path 并结束递归
    if (path.length === nums.length) {
      return res.push(path);
    }

    // 通过循环加递归的形式,模拟出所有的排列情况
    nums.forEach((v) => {
      // 当 path 中,包含这一次的循环的值的时候,进行回溯(中断递归)
      if (path.includes(v)) return;

      // 递归
      backtrack(path.concat(v));
    });
  };
  backtrack([]);

  return res;
};

字符串中的第一个唯一字符

var firstUniqChar = function (s) {
  for (let i in s) {
    if (s.indexOf(s[i]) == s.lastIndexOf(s[i])) {
      return i;
    }
  }
  return -1;
};

无重复字符的最长子串

function lengthOfLongestSubstring(str) {
  if (str === "") return 0;
  let start = 0,
    maxLen = 0;
  const map = new Map();
  const len = str.length;
  for (let i = 0; i < len; i++) {
    const c = str[i];
    if (map.has(c)) {
      start = Math.max(map.get(c) + 1, start);
    }
    map.set(c, i);
    maxLen = Math.max(i - start + 1, maxLen);
  }
  return maxLen;
}

最长回文子串

var longestPalindrome = function (s) {
  let n = s.length;
  if (n == 0) return ""; //字符串为空则返回空
  if (n == 1) return s; //字符串为一个字符, 显然返回自身
  let result = "";
  for (let i = 0; i < n; i++) {
    //字符串长度超过2
    for (let j = i + 1; j <= n; j++) {
      let str = s.slice(i, j); //可得到所有子串
      let f = str.split("").reverse().join(""); //对字符串利用数组方法倒序

      if (str == f) {
        //判断是否为回文
        result = str.length > result.length ? str : result;
      }
    }
  }
  return result;
};

整数反转

var reverse = function (x) {
  const symbol = String(x).split("").reverse().join("");
  let result;
  if (x >= 0) {
    result = Number(symbol);
  } else {
    result = Number(symbol.slice(-1) + symbol.slice(0, -1));
  }
  if (result < (-2) ** 31 || result > 2 ** 31 - 1) {
    result = 0;
  }
  return result;
};

退格

// 比较含有退格的字符串,"<-"代表退格键,"<"和"-"均为正常字符
// 输入:"a<-b<-", "c<-d<-",结果:true,解释:都为""
// 输入:"<-<-ab<-", "<-<-<-<-a",结果:true,解释:都为"a"
// 输入:"<-<ab<-c", "<<-<a<-<-c",结果:false,解释:"<ac" !== "c"
function isEqual(str1, str2) {
  function getResStr(str) {
    let res = [];
    for (let i = 0; i < str.length; i++) {
      if (str[i] === "<" && str[i + 1] === "-") {
        res.pop();
        i++;
      } else {
        res.push(str[i]);
      }
    }
    console.log(res.join(""));
    return res.join("");
  }
  let restBo = getResStr(str1) === getResStr(str2);
  console.log(restBo);
  return restBo;
}
isEqual("a<-b<-", "c<-d<-");
isEqual("<-<-ab<-", "<-<-<-<-a");
isEqual("<-<ab<-c", "<<-<a<-<-c");

数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合,输入:n = 3,输出:["((()))","(()())","(())()","()(())","()()()"]

function generateParenthesis(n: number): string[] {
  const result: string[] = [];
  const dfs = (path: string, count1: number, count2: number) => {
    // path为递归的字符串,count1为左括号的数量,count2为右括号的数量
    // 当左括号或右括号大于传入的n,括号生成后的岁数,那这个递归函数就不跑了。
    if (count1 > n || count2 > n) return;
    // 如果右括号的数量大于左括号的数量,也不符合题意,也不跑了。
    if (count2 > count1) return;
    // 左括号和右括号的数量都对了 那就把正确结果推出去
    if (count1 === n && count2 === n) {
      result.push(path);
      return;
    }

    //这边处理第一次传入空字符串的情况
    if (count1 === 0) {
      dfs(path + "(", count1 + 1, count2);
    } else {
      // 只有这两种结果
      dfs(path + "(", count1 + 1, count2);
      dfs(path + ")", count1, count2 + 1);
    }
  };
  dfs("", 0, 0);
  return result;
}

有效括号

/**
 * @param {string} s
 * @return {boolean}
 */
var isValid = function (s) {
  let sObj = {
    "(": ")",
    "[": "]",
    "{": "}",
  };
  let que = [];
  for (let i = 0; i < s.length; i++) {
    if (que.length === 0) {
      que.push(s[i]);
    } else if (sObj[que[que.length - 1]] === s[i]) {
      que.pop();
    } else {
      que.push(s[i]);
    }
  }
  if (que.length === 0) {
    return true;
  } else {
    return false;
  }
};

数组中出现次数超过一半的数字

/**
 * @param {number[]} nums
 * @return {number}
 */
var majorityElement = function (nums) {
  let newNums = nums.sort((a, b) => a - b);
  let length = Math.floor(newNums.length / 2);
  for (let i = 0; i < length + 1; i++) {
    if (newNums[i] === newNums[i + length]) {
      return newNums[i];
    }
  }
  return -1;
};

返回该数组中出现频率>=n 的元素列表

Array.prototype.findDup = function (count) {
  return this.reduce((re, val) => {
    let index = re.findIndex((o) => o.val === val);
    if (index >= 0) {
      re[index].count++;
    } else {
      re.push({ count: 1, val });
    }
    return re;
  }, [])
    .filter((o) => o.count >= count)
    .map((o) => o.val);
};

对象查找值,返回路径

let obj = {
    a: {
        a_1: {
            a_1_1: 'a11',
            a_1_2: 'a12'
        },
        a_2: {
            a_2_1: 'a21',
            a_2_2: 'a22'
        }
    },
    b: {
        b_1: 'b1',
        b_2: 'b2'
    },
    c: 'c'
} 
function findPath(obj, val) {
    let keys = Object.keys(obj);
    let result = [];
    for (let i = 0; i < keys.length; i++) {
        let _ = keys[i];
        if (typeof obj[_] === 'object' && obj[_] !== null) {
            let res = findPath(obj[_], val);
            if (res.length > 0) {
                result = result.concat([_], res);
            }
        } else if (obj[_] === val) {
            result = result.concat([_]);
            break;
        }
    }
    return result;
}
console.log(findPath(obj, 'a22')); // ['a', 'a_2', 'a_2_2']

链表

链表(插入,删除,反转)

//节点类
class Node {
  constructor(data) {
    this.data = data;
    this.next = null;
  }
}

class SinglyLinkedList {
  constructor(data) {
    this.head = new Node(data);
  }
  add(data) {
    let node = new Node(data);
    let current = this.head;
    while (current.next) {
      current = current.next;
    }
    current.next = node;
  }
  addAt(data, index) {
    let node = new Node(data);
    let current = this.head;
    let currentIndex = 1;
    while (currentIndex < index) {
      current = current.next;
      currentIndex++;
    }
    node.next = current.next;
    current.next = node;
  }
  removeAt(index) {
    let current = this.head;
    let currentIndex = 1;
    let pre = null;
    while (currentIndex < index) {
      pre = current;
      current = current.next;
      currentIndex++;
    }
    pre.next = current.next;
  }
  reverse() {
    let pre = this.head;
    let current = this.head.next;
    pre.next = null;
    while (current) {
      let next = current.next;
      current.next = pre;
      pre = current;
      current = next;
    }
    this.head = pre;
  }
}

判断回文链表

var isPalindrome = function (head) {
  let left = head;
  function traverse(right) {
    if (right == null) return true;
    let res = traverse(right.next);
    res = res && right.val === left.val;
    left = left.next;
    return res;
  }
  return traverse(head);
};

K 个一组翻转链表(输入:head = [1,2,3,4,5], k = 2 ; 输出:[2,1,4,3,5])

var reverseKGroup = function (head, k) {
  let a = head,
    b = head;
  for (let i = 0; i < k; i++) {
    if (b == null) return head;
    b = b.next;
  }
  const newHead = reverse(a, b);
  a.next = reverseKGroup(b, k);
  return newHead;
};
function reverse(a, b) {
  let prev = null,
    cur = a,
    nxt = a;
  while (cur != b) {
    nxt = cur.next;
    cur.next = prev;
    prev = cur;
    cur = nxt;
  }
  return prev;
}

判断环形链表

var hasCycle = function (head) {
  if (head == null || head.next == null) return false;
  let slower = head,
    faster = head;
  while (faster != null && faster.next != null) {
    slower = slower.next;
    faster = faster.next.next;
    if (slower === faster) return true;
  }
  return false;
};

判断相交链表

var getIntersectionNode = function (headA, headB) {
  let lastHeadA = null;
  let lastHeadB = null;
  let originHeadA = headA;
  let originHeadB = headB;
  if (!headA || !headB) {
    return null;
  }
  while (true) {
    if (headB == headA) {
      return headB;
    }
    if (headA && headA.next == null) {
      lastHeadA = headA;
      headA = originHeadB;
    } else {
      headA = headA.next;
    }
    if (headB && headB.next == null) {
      lastHeadB = headB;
      headB = originHeadA;
    } else {
      headB = headB.next;
    }
    if (lastHeadA && lastHeadB && lastHeadA != lastHeadB) {
      return null;
    }
  }
  return null;
};

合并两个有序链表

//输入:1->2->4, 1->3->4 输出:1->1->2->3->4->4
var mergeTwoLists = function (l1, l2) {
  if (l1 == null) return l2;
  if (l2 == null) return l1;
  if (l1.val < l2.val) {
    l1.next = mergeTwoLists(l1.next, l2);
    return l1;
  } else {
    l2.next = mergeTwoLists(l1, l2.next);
    return l2;
  }
};

两数相加

var addTwoNumbers = function (l1, l2) {
  let addOne = 0;
  let sum = new ListNode("0");
  let head = sum;
  while (addOne || l1 || l2) {
    let val1 = l1 !== null ? l1.val : 0;
    let val2 = l2 !== null ? l2.val : 0;
    let r1 = val1 + val2 + addOne;
    addOne = r1 >= 10 ? 1 : 0;
    sum.next = new ListNode(r1 % 10);
    sum = sum.next;
    if (l1) l1 = l1.next;
    if (l2) l2 = l2.next;
  }
  return head.next;
};

二叉树

二叉树前中后遍历

二叉树前中后遍历

// 二叉树的生成
function NodeTree(value){
    this.value = value;
    this.left = null;
    this.right = null;
}

let ta = new NodeTree('a');
let tb = new NodeTree('b');
let tc = new NodeTree('c');
let td = new NodeTree('d');
let te = new NodeTree('e');
let tf = new NodeTree('f');
let tg = new NodeTree('g');

ta.left = tb;
ta.right = tc;
tb.left = td;
tb.right = te;
tc.left = tf;
tc.right = tg;

// 形如上面的这个格式,ta被称为一颗二叉树。 满二叉树, 完全二叉树

// 二叉树的遍历,分为三种,前序遍历,中序遍历,后续遍历(使用回调)
/**
 * 二叉树的前序遍历
 * @param treeList
 * @returns {null}
 */
function treeFrontEach(treeList){
    if (!treeList || treeList.value === null) return null;
    console.log(treeList.value);
    treeFrontEach(treeList.left);
    treeFrontEach(treeList.right);
}

treeFrontEach(ta); 输出的结果 a b d e c f g

/**
 * 中序遍历
 * @param treeList
 * @returns {null}
 */
function treeMiddleEach(treeList){
    if (!treeList || treeList.value === null) return null;
    treeMiddleEach(treeList.left);
    console.log(treeList.value);
    treeMiddleEach(treeList.right);
}
treeMiddleEach(ta); 输出的结果 d b e a c f c g

/**
 * 后序遍历
 * @param treeList
 * @returns {null}
 */
function treeEndEach(treeList){
    if (!treeList || treeList.value === null) return null;
    treeEndEach(treeList.left);
    treeEndEach(treeList.right);
    console.log(treeList.value);
}

treeEndEach(ta); 输出结果 d e b f g c a
//不使用回调

//前序遍历
const preorderTraversal = function (root) {
  const stack = [],
    res = [];
  root && stack.push(root);
  // 使用一个栈stack,每次首先输出栈顶元素,也就是当前二叉树根节点,之后依次输出二叉树的左孩子和右孩子
  while (stack.length > 0) {
    let cur = stack.pop();
    res.push(cur.val);
    // 先入栈的元素后输出,所以先入栈当前节点右孩子,再入栈左孩子
    cur.right && stack.push(cur.right);
    cur.left && stack.push(cur.left);
  }
  return res;
};
//中序遍历
const inorderTraversal = function (root) {
  const res = [],
    stack = [];
  let node = root;
  while (stack.length > 0 || node !== null) {
    if (node) {
      //node存在
      stack.push(node); //当前节点push
      node = node.left; //找left
    } else {
      //node不存在
      node = stack.pop(); //弹出最深的left
      res.push(node.val); //res push
      node = node.right; //找right
    }
  }
  return res;
};
//后序遍历
const postorderTraversal = function (root) {
  let stack = [],
    res = [];
  root && stack.push(root);
  while (stack.length > 0) {
    let cur = stack.pop();
    res.push(cur.val);
    cur.left && stack.push(cur.left);
    cur.right && stack.push(cur.right);
  }
  return res.reverse();
};

判断对称二叉树(镜像对称)

// 用于递归的helper函数,接收参数为左节点和右节点。
const helper = (left: TreeNode | null, right: TreeNode | null) => {
  // 如果传入的左节点和右节点都不存在 也是镜像
  if (left == right) return true;
  // 如果左节点和右节点有一个的值不存在,那就不是对称的两个节点
  else if (left.val === 0 || right.val === 0) return false;
  // 最后判断并递归,左节点和右节点都存在并且值为相等,那就递归他们的子节点。
  return (
    left.val === right.val &&
    helper(left.left, right.right) &&
    helper(left.right, right.left)
  );
};
function isSymmetric(root: TreeNode | null): boolean {
  //传入的root可能为null,做下判断。
  if (root === null || root === undefined) return true;
  else {
    return helper(root.left, root.right);
  }
}

平衡二叉树

function isBalanced(root) {
  if (root === null) {
    return true;
  }
  return (
    Math.abs(tree_height(root.left) - tree_height(root.right)) <= 1 &&
    isBalanced(root.left) &&
    isBalanced(root.right)
  );
}
function tree_height(root) {
  var deep = -Infinity;
  if (root === null) {
    return -1;
  }
  deep = Math.max(deep, tree_height(root.left));
  deep = Math.max(deep, tree_height(root.right));
  return deep + 1;
}

路径总和

//通过递归方法来解决
var hasPathSum = function (root, targetSum) {
  if (!root) {
    return false;
  }
  if (root.val == targetSum && root.left == null && root.right == null) {
    return true;
  }
  let left = hasPathSum(root.left, targetSum - root.val);
  let right = hasPathSum(root.right, targetSum - root.val);
  return left || right;
};

路径总和 II

/**
 * @param {TreeNode} root
 * @param {number} targetSum
 * @return {number[][]}
 */
var pathSum = function (root, targetSum) {
  var res = [];
  var array = [];
  function doFind(root, targetSum) {
    if (root == null) return;
    targetSum = targetSum - root.val;
    array.push(root.val);
    if (root.left == null && root.right == null && targetSum == 0) {
      res.push([...array]);
    }
    doFind(root.left, targetSum);
    doFind(root.right, targetSum);
    array.pop();
  }
  doFind(root, targetSum);
  return res;
};

翻转二叉树

/**
 * Definition for a binary tree node.
 * function TreeNode(val, left, right) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.left = (left===undefined ? null : left)
 *     this.right = (right===undefined ? null : right)
 * }
 */
/**
 * @param {TreeNode} root
 * @return {TreeNode}
 */
var invertTree = function (root) {
  if (root) {
    [root.left, root.right] = [invertTree(root.right), invertTree(root.left)];
  }
  return root;
};

合并二叉树

var mergeTrees = function (t1, t2) {
  if (!t1) return t2;
  if (!t2) return t1;
  t1.val = t1.val + t2.val;
  t1.left = mergeTrees(t1.left, t2.left);
  t1.right = mergeTrees(t1.right, t2.right);
  return t1;
};

DOM 树的 DFS(深度优先遍历)

const parentDOM = document.querySelector("#container");
//非回调
function deepTravalSal(node) {
  const nodes = [];
  const stack = [];
  if (node) {
    stack.push(node);
    while (stack.length) {
      const item = stack.pop();
      const len = item.children.length;
      nodes.push(item);
      for (let i = len - 1; i >= 0; i--) {
        stack.push(item.children[i]);
      }
    }
  }
  return nodes;
}
//回调
function dfs(dom) {
  let nodeList = [];
  nodeList.push(dom);
  if (dom.children && dom.children.length) {
    for (let i = 0; i < dom.children.length; i++) {
      nodeList = nodeList.concat(dfs(dom.children[i]));
    }
  }
  return nodeList;
}
console.log(deepTravalSal(parentDOM));

DOM 树的 BFS(广度优先遍历)

const parentDOM = document.getElementById("container");
//非回调
function breathTravalSal(node) {
  const nodes = [];
  const queue = [];
  if (node) {
    queue.push(node);
    while (queue.length) {
      const item = queue.shift();
      nodes.push(item);
      for (const v of item.children) {
        queue.push(v);
      }
    }
  }
  return nodes;
}
//回调
function bfs(dom) {
  if (!(dom instanceof Array)) {
    dom = [dom];
  }
  let nodeList = [];
  let childrenArr = [];
  for (let i = 0; i < dom.length; i++) {
    nodeList.push(dom[i]);
    if (dom[i].children && dom[i].children.length) {
      childrenArr = childrenArr.concat(dom[i].children);
    }
  }
  if (childrenArr.length > 0) {
    nodeList = nodeList.concat(bfs(childrenArr));
  }
  return nodeList;
}
console.log(breathTravalSal(parentDOM));

二叉树右视图

//方法1
var rightSideView = function (root) {
  if (!root) return [];
  let arrList = [];
  DFS(root, 0, arrList);
  return arrList;
};

function DFS(root, depth, res) {
  if (root) {
    if (res.length === depth) {
      // 当数组长度等于当前 深度 时, 把当前的值加入数组
      res.push(root.val);
    }
    DFS(root.right, depth + 1, res); // 先从右边开始, 当右边没了, 再轮到左边
    DFS(root.left, depth + 1, res);
  }
}
//方法2
var rightSideView = function(root) {
    let nums = [];
    if (!root) return nums;

    let stack = [];
    let p = root;
    let maxDepth = 0;
    let currentDepth = 0;
    while(p || stack.length > 0) {
        while(p) {      //遍历节点的右分支
            currentDepth++;
            if (currentDepth > maxDepth) {    //推入节点
                maxDepth++;
                nums.push(p.val);
            }
            stack.push([p, currentDepth]);
            p = p.right;
        }

        let node = stack.pop();    //回溯
        p = node[0].left;     //对节点的左分支进行遍历
        currentDepth = node[1];  //当前深度也要回溯
    }

    return nums;
};

动态规划

爬楼梯:假设你现在正在爬楼梯,楼梯有 n 级。每次你只能爬 1 级或者 2 级,那么你有多少种方法爬到楼梯的顶部

var climbStairs = function (n) {
  if (n === 1 || n === 2) {
    return n;
  }
  // 前一个值
  let pre = 2;
  // 前一个的前一个的值
  let beforePre = 1;
  // 中间变量
  let temp = null;
  for (let index = 3; index <= n; index++) {
    temp = pre;
    pre = pre + beforePre;
    beforePre = temp;
  }
  return pre;
};

编辑距离

var minDistance = function (word1, word2) {
  let row = word1.length;
  let col = word2.length;
  //创建dp矩阵
  const dp = [];
  //为了创建二维矩阵,所用到的辅助的矩阵
  let tmp = new Array(col + 1).fill(0);
  for (let i = 0; i < row + 1; i++) {
    dp[i] = [...tmp];
  }
  // dp矩阵的第一行
  for (let j = 1; j <= col; j++) dp[0][j] = dp[0][j - 1] + 1;
  // dp矩阵的第一列
  for (let i = 1; i <= row; i++) dp[i][0] = dp[i - 1][0] + 1;
  // dp矩阵的其它元素
  for (let i = 1; i <= row; i++) {
    for (let j = 1; j <= col; j++) {
      //当前字母相等时
      if (word1[i - 1] === word2[j - 1]) {
        dp[i][j] = dp[i - 1][j - 1];
        //如果当前字母不等
      } else {
        dp[i][j] =
          Math.min(Math.min(dp[i - 1][j - 1], dp[i][j - 1]), dp[i - 1][j]) + 1;
      }
    }
  }
  return dp[row][col];
};

参考原文

买卖股票的最佳时机

var maxProfit = function (prices) {
  if (prices.length === 0 || prices.length === 1) {
    return 0;
  }
  // dp1数组存储第`i`天,持有股票的最大利润
  const dp1 = [];
  dp1[0] = -prices[0];
  // dp2数组存储第`i`天,不持有股票的最大利润
  const dp2 = [];
  dp2[0] = 0;

  for (let i = 1; i < prices.length; i++) {
    dp1[i] = Math.max(dp1[i - 1], -prices[i]);
    dp2[i] = Math.max(dp2[i - 1], prices[i] + dp1[i - 1]);
  }

  return dp2[dp2.length - 1];
};

买卖股票的最佳时机 II

/**
 * @param {number[]} prices
 * @return {number}
 */
var maxProfit = function (prices) {
  if (prices.length === 0 || prices.length === 1) {
    return 0;
  }

  const dp1 = [];
  dp1[0] = -prices[0];

  const dp2 = [];
  dp2[0] = 0;

  for (let i = 1; i < prices.length; i++) {
    dp1[i] = Math.max(dp1[i - 1], dp2[i - 1] - prices[i]);
    dp2[i] = Math.max(dp2[i - 1], prices[i] + dp1[i - 1]);
  }

  return dp2[prices.length - 1];
};

买卖股票的最佳时机 III

/**
 * @param {number[]} prices
 * @return {number}
 */
var maxProfit = function (prices) {
  if (prices.length === 0 || prices.length === 1) {
    return 0;
  }

  // 持有股票
  const dp1 = [
    [-prices[0]], // 还剩下两次交易机会
    [-prices[0]], // 还剩下一次交易机会
  ];
  // 不持有股票
  const dp2 = [
    [0], // 还剩下两次交易机会
    [0], // 还剩下一次交易机会
  ];

  for (let i = 1; i < prices.length; i++) {
    // 持有股票,还有两次交易机会
    dp1[0][i] = Math.max(dp1[0][i - 1], -prices[i]);
    // 持有股票,还有一次交易机会
    dp1[1][i] = Math.max(dp1[1][i - 1], dp2[0][i - 1] - prices[i]);
    // 不持有股票,还有两次交易机会
    dp2[0][i] = Math.max(dp2[0][i - 1], prices[i] + dp1[0][i - 1]);
    // 不持有股票,还有一次交易机会
    dp2[1][i] = Math.max(dp2[1][i - 1], prices[i] + dp1[1][i - 1]);
  }

  return dp2[1][prices.length - 1];
};

参考原文

小知识

on.png