Leetcode 刷题笔记(刷题中...)

280 阅读9分钟

简单难度

231 2 的幂

  • 题目描述
给定一个整数,编写一个函数来判断它是否是 2 的幂次方。

示例 1:
输入:1
输出:true
解释:20 = 1

示例 2:
输入:16
输出:true
解释:24 = 16

示例 3:
输入:218
输出:false
  • 方法一:log
var isPowerOfTwo = function (n) {
  if (n <= 0) return false;
  return (Math.log10(n) / Math.log10(2)) % 1 === 0;
};
  • 方法二:位运算
var isPowerOfTwo = function (n) {
  if (n <= 0) return false;
  return (n & (n - 1)) === 0;
};
  • 方法三:mod
var isPowerOfTwo = function (n) {
  if (n < 1) return false;
  while (n % 2 === 0) {
    n /= 2;
  }
  return n === 1;
};

191. 位 1 的个数

  • 题目描述
编写一个函数,输入是一个无符号整数(以二进制串的形式),返回其二进制表达式中数字位数为 '1' 的个数(也被称为汉明重量)。

提示:

请注意,在某些语言(如 Java)中,没有无符号整数类型。在这种情况下,输入和输出都将被指定为有符号整数类型,并且不应影响您的实现,因为无论整数是有符号的还是无符号的,其内部的二进制表示形式都是相同的。
在 Java 中,编译器使用二进制补码记法来表示有符号整数。因此,在上面的 示例 3 中,输入表示有符号整数 -3。

进阶:

如果多次调用这个函数,你将如何优化你的算法?

示例 1:
输入:00000000000000000000000000001011
输出:3
解释:输入的二进制串 00000000000000000000000000001011 中,共有三位为 '1'。

示例 2:
输入:00000000000000000000000010000000
输出:1
解释:输入的二进制串 00000000000000000000000010000000 中,共有一位为 '1'。

示例 3:
输入:11111111111111111111111111111101
输出:31
解释:输入的二进制串 11111111111111111111111111111101 中,共有 31 位为 '1'。

提示:
输入必须是长度为 32 的 二进制串 。
  • 方法一:位运算 I
var hammingWeight = function (n) {
  let res = 0;
  while (n) {
    if (n & 1) res++;
    n = (n / 2) | 0;
  }
  return res;
};
  • 方法二:位运算 II
var hammingWeight = function (n) {
  let res = 0;
  while (n) {
    res++;
    n = n & (n - 1);
  }
  return res;
};
  • 方法三:正则
var hammingWeight = function (n) {
  return n.toString(2).replace(/0/g, "").length;
};

69. x 的平方根

  • 题目描述
实现 int sqrt(int x) 函数。
计算并返回 x 的平方根,其中 x 是非负整数。
由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去。

示例 1:

输入:4
输出:2
示例 2:

输入:8
输出:2
说明:8 的平方根是 2.82842...,
     由于返回类型是整数,小数部分将被舍去。
  • 方法一:二分法
var mySqrt = function (x) {
  if (x === 0 || x === 1) return x;
  let l = 1;
  let r = x;
  let res;
  while (l <= r) {
    let m = (l + r) >> 1;
    if (m === x / m) return m;
    else if (m > x / m) r = m - 1;
    else {
      l = m + 1;
      res = m;
    }
  }
  return res;
};
  • 方法二:牛顿迭代法
var mySqrt = function (x) {
  if (x === 0 || x === 1) return x;
  let r = x;
  while (r * r > x) {
    r = ((r + x / r) / 2) | 0;
  }
  return r;
};

111. 二叉树的最小深度

  • 题目描述
给定一个二叉树,找出其最小深度。
最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
说明:叶子节点是指没有子节点的节点。

示例 1:

输入:root = [3,9,20,null,null,15,7]
输出:2
示例 2:

输入:root = [2,null,3,null,4,null,5,null,6]
输出:5
  • 方法一:BFS
/**
 * @param {TreeNode} root
 * @return {number}
 */
var minDepth = function (root) {
  if (root === null) return 0;
  let level = 0;
  let list = [root];
  while (list.length) {
    let len = list.length;
    level++;
    for (let i = 0; i < len; i++) {
      let node = list.shift();
      if (node) {
        if (!node.left && !node.right) return level;
        node.left && list.push(node.left);
        node.right && list.push(node.right);
      }
    }
  }
  return level;
};
  • 方法二:DFS
/**
 * @param {TreeNode} root
 * @return {number}
 */
var minDepth = function (root) {
  if (root === null) return 0;
  if (!root.left) return 1 + minDepth(root.right);
  if (!root.right) return 1 + minDepth(root.left);
  return 1 + Math.min(minDepth(root.left), minDepth(root.right));
};

104. 二叉树的最大深度

  • 题目描述
给定一个二叉树,找出其最大深度。

二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。

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

示例:
给定二叉树 [3,9,20,null,null,15,7],

    3
   / \
  9  20
    /  \
   15   7
返回它的最大深度 3
  • 方法一:BFS
/**
 * @param {TreeNode} root
 * @return {number}
 */
var maxDepth = function (root) {
  if (root === null) return 0;
  let level = 0;
  let list = [root];
  while (list.length) {
    let len = list.length;
    for (let i = 0; i < len; i++) {
      let node = list.shift();
      if (node) {
        node.left && list.push(node.left);
        node.right && list.push(node.right);
      }
    }
    level++;
  }
  return level;
};
  • 方法二:DFS
/**
 * @param {TreeNode} root
 * @return {number}
 */
var maxDepth = function (root) {
  if (root === null) return 0;
  return 1 + Math.max(maxDepth(root.left), maxDepth(root.right));
};

121. 买卖股票的最佳时机

  • 题目描述
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。

如果你最多只允许完成一笔交易(即买入和卖出一支股票一次),设计一个算法来计算你所能获取的最大利润。

注意:你不能在买入股票前卖出股票。
  • 方法:贪心算法
/**
 * @param {number[]} prices
 * @return {number}
 */
var maxProfit = function (prices) {
  let [benefit, min] = [0, prices[0]];
  for (let i = 1, len = prices.length; i < len; i++) {
    min = Math.min(min, prices[i]);
    benefit = Math.max(benefit, prices[i] - min);
  }
  return benefit;
};

122. 买卖股票的最佳时机 II

  • 题目描述
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

示例 1:

输入:[7,1,5,3,6,4]
输出:7
解释:在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出,这笔交易所能获得利润 = 5-1 = 4 。
     随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,这笔交易所能获得利润 = 6-3 = 3 。

示例 2:

输入:[1,2,3,4,5]
输出:4
解释:在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出,这笔交易所能获得利润 = 5-1 = 4 。
     注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。
     因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。
示例 3:

输入:[7,6,4,3,1]
输出:0
解释:在这种情况下,没有交易完成,所以最大利润为 0。
  • 方法:贪心算法
/**
 * @param {number[]} prices
 * @return {number}
 */
var maxProfit = function (prices) {
  let benefit = 0;
  for (let i = 1, len = prices.length; i < len; i++) {
    benefit += Math.max(prices[i] - prices[i - 1], 0);
  }
  return benefit;
};

169. 多数元素

  • 题目描述
给定一个大小为 n 的数组,找到其中的多数元素。多数元素是指在数组中出现次数大于 ⌊ n/2 ⌋ 的元素。

你可以假设数组是非空的,并且给定的数组总是存在多数元素。

示例 1:

输入:[3,2,3]
输出:3
示例 2:

输入:[2,2,1,1,1,2,2]
输出:2
  • 方法一:排序
/**
 * @param {number[]} nums
 * @return {number}
 */
var majorityElement = function (nums) {
  nums.sort((a, b) => a - b);
  let mid = ((nums.length - 1) / 2) | 0;
  return nums[mid];
};
  • 方法二:Hashmap
/**
 * @param {number[]} nums
 * @return {number}
 */
var majorityElement = function (nums) {
  let map = new Map();
  let len = nums.length;
  let target = len / 2;

  for (let i = 0; i < len; i++) {
    if (!map.has(nums[i])) map.set(nums[i], 0);
    let count = map.get(nums[i]) + 1;
    map.set(nums[i], count);
    if (count > target) return nums[i];
  }
};
  • 方法三:摩尔投票法
var majorityElement = function (nums) {
  // 摩尔投票法:
  let result = [1, nums[0]];
  for (let i = 1, len = nums.length; i < len; i++) {
    if (nums[i] === result[1]) result[0] += 1;
    else {
      result[0] -= 1;
      if (!result[0]) result[1] = nums[i + 1];
    }
  }
  return result[1];
};

242. 有效的字母异位词

  • 题目描述
给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。

示例 1:

输入:s = "anagram", t = "nagaram"
输出:true
示例 2:

输入:s = "rat", t = "car"
输出:false
  • 方法一:排序
/**
 * @param {string} s
 * @param {string} t
 * @return {boolean}
 */
var isAnagram = function (s, t) {
  return Array.from(s).sort().join("") === Array.from(t).sort().join("");
};
  • 方法二:Hashmap
var isAnagram = function (s, t) {
  let map = new Map();

  for (let i = 0, sLen = s.length; i < sLen; i++) {
    if (!map.has(s[i])) map.set(s[i], 0);
    map.set(s[i], map.get(s[i]) + 1);
  }

  for (let j = 0, tLen = t.length; j < tLen; j++) {
    if (!map.has(t[j])) return false;
    map.set(t[j], map.get(t[j]) - 1);
    if (map.get(t[j]) === 0) map.delete(t[j]);
  }

  return !map.size;
};
  • 方法三:ASCII 和比较
/**
 * @param {string} s
 * @param {string} t
 * @return {boolean}
 */
var isAnagram = function (s, t) {
  let sum = 0;
  for (let i = 0, sLen = s.length; i < sLen; i++) {
    sum += s[i].codePointAt() ** 0.5;
  }
  for (let j = 0, tLen = t.length; j < tLen; j++) {
    sum -= t[j].codePointAt() ** 0.5;
  }
  // 当两个串的 ASCII 差小于 1e-5 时认为它们相等
  return Math.abs(sum) < 1e-5;
};

剑指 Offer 25. 合并两个排序的链表

  • 题目描述
输入两个递增排序的链表,合并这两个链表并使新链表中的节点仍然是递增排序的。

示例 1:

输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4
  • 方法:迭代
var mergeTwoLists = function (l1, l2) {
  let root = new ListNode(-1);
  let cur = root;
  while (l1 && l2) {
    if (l1.val < l2.val) {
      cur.next = l1;
      l1 = l1.next;
    } else {
      cur.next = l2;
      l2 = l2.next;
    }
    cur = cur.next;
  }
  cur.next = l1 ? l1 : l2;
  return root.next;
};

88. 合并两个有序数组

  • 题目描述
给你两个有序整数数组 nums1 和 nums2,请你将 nums2 合并到 nums1 中,使 nums1 成为一个有序数组。
 
说明:

初始化 nums1 和 nums2 的元素数量分别为 m 和 n 。
你可以假设 nums1 有足够的空间(空间大小大于或等于 m + n)来保存 nums2 中的元素。
 

示例:

输入:
nums1 = [1,2,3,0,0,0], m = 3
nums2 = [2,5,6],       n = 3

输出:[1,2,2,3,5,6]
  • 方法:从后往前比较
/**
 * @param {number[]} nums1
 * @param {number} m
 * @param {number[]} nums2
 * @param {number} n
 * @return {void} Do not return anything, modify nums1 in-place instead.
 */
var merge = function (nums1, m, nums2, n) {
  let p = m-- + n-- - 1;

  while (m >= 0 && n >= 0) {
    nums1[p--] = nums1[m] < nums2[n] ? nums2[n--] : nums1[m--];
  }

  while (n >= 0) {
    nums1[p--] = nums2[m--];
  }
};

剑指 Offer 09. 用两个栈实现队列

  • 题目描述
用两个栈实现一个队列。队列的声明如下,请实现它的两个函数 appendTail 和 deleteHead ,分别完成在队列尾部插入整数和在队列头部删除整数的功能。
(若队列中没有元素,deleteHead 操作返回 -1 )

示例 1:

输入:
["CQueue","appendTail","deleteHead","deleteHead"]
[[],[3],[],[]]
输出:[null,null,3,-1]
示例 2:

输入:
["CQueue","deleteHead","appendTail","appendTail","deleteHead","deleteHead"]
[[],[],[5],[2],[],[]]
输出:[null,-1,null,null,5,2]
  • 方法:双栈模拟队列
var CQueue = function () {
  this.iptStack = []; // 输入栈
  this.optStack = []; // 输出栈
};

/**
 * @param {number} value
 * @return {void}
 */
CQueue.prototype.appendTail = function (value) {
  this.iptStack.push(value);
};

/**
 * @return {number}
 */
CQueue.prototype.deleteHead = function () {
  let iptStack = this.iptStack;
  let optStack = this.optStack;
  if (!iptStack.length && !optStack.length) {
    return -1;
  }
  if (!optStack.length) {
    while (iptStack.length) {
      optStack.push(iptStack.pop());
    }
  }
  return optStack.pop();
};

20. 有效的括号

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

有效字符串需满足:

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

示例 1:

输入:"()"
输出:true
示例 2:

输入:"()[]{}"
输出:true
示例 3:

输入:"(]"
输出:false
示例 4:

输入:"([)]"
输出:false
示例 5:

输入:"{[]}"
输出:true
  • 方法:堆栈
var isValid = function (s) {
  let map = new Map([
    ["}", "{"],
    [")", "("],
    ["]", "["],
  ]);
  let stack = [];
  for (let i = 0, len = s.length; i < len; i++) {
    if (!map.has(s[i])) {
      stack.push(s[i]);
    } else {
      if (stack.pop() !== map.get(s[i])) {
        return false;
      }
    }
  }
  return !stack.length;
};

剑指 Offer 57. 和为 s 的两个数字

  • 题目描述
输入一个递增排序的数组和一个数字 s,在数组中查找两个数,使得它们的和正好是 s。如果有多对数字的和等于 s,则输出任意一对即可。

示例 1:

输入:nums = [2,7,11,15], target = 9
输出:[2,7] 或者 [7,2]
示例 2:

输入:nums = [10,26,30,31,47,60], target = 40
输出:[10,30] 或者 [30,10]
  • 方法一:Hashmap
/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number[]}
 */
var twoSum = function (nums, target) {
  let set = new Set();
  for (let i = 0, len = nums.length; i < len; i++) {
    let y = target - nums[i];
    if (set.has(y)) {
      return [nums[i], y];
    }
    set.add(nums[i]);
  }
};
  • 方法二:双指针
var twoSum = function (nums, target) {
  let l = 0;
  let r = nums.length - 1;

  while (l < r) {
    let sum = nums[l] + nums[r];
    if (sum < target) l++;
    else if (sum > target) r--;
    else return [nums[l], nums[r]];
  }
};

206. 反转链表

  • 题目描述
反转一个单链表。

示例:

输入:1->2->3->4->5->NULL
输出:5->4->3->2->1->NULL
  • 方法:迭代
var reverseList = function (head) {
  let pre = null;
  let cur = head;
  let tmp;
  while (cur) {
    tmp = cur.next;
    cur.next = pre;
    pre = cur;
    cur = tmp;
  }
  return pre;
};

141. 环形链表

  • 题目描述
给定一个链表,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。
为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。
如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。
如果链表中存在环,则返回 true 。 否则,返回 false 。

进阶:
你能用 O(1)(即,常量)内存解决此问题吗?

示例 1:

输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。

示例 2:

输入:head = [1,2], pos = 0
输出:true
解释:链表中有一个环,其尾部连接到第一个节点。

示例 3:

输入:head = [1], pos = -1
输出:false
解释:链表中没有环。
  • 方法一:Hashmap
var hasCycle = function (head) {
  let set = new Set();
  let p = head;

  while (p) {
    if (set.has(p)) {
      return true;
    }
    set.add(p);
    p = p.next;
  }
  return false;
};
  • 方法二:快慢指针
var hasCycle = function (head) {
  let slow = head;
  let fast = head;

  while (slow && fast && fast.next) {
    slow = slow.next;
    fast = fast.next.next;
    if (slow === fast) {
      return true;
    }
  }

  return false;
};

中等难度

62. 不同路径

  • 题目描述
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。
问总共有多少条不同的路径?
例如,上图是一个 7 x 3 的网格。有多少可能的路径?

示例  1:
输入:m = 3, n = 2
输出:3
解释:
从左上角开始,总共有 3 条路径可以到达右下角。

1. 向右 -> 向右 -> 向下
2. 向右 -> 向下 -> 向右
3. 向下 -> 向右 -> 向右

示例  2:
输入:m = 7, n = 3
输出:28
  • 方法一:DP
var uniquePaths = function (m, n) {
  // a[i][j] = a[i][j - 1] + a[i - 1][j]
  let dp = new Array(m);
  for (let i = 0; i < m; i++) {
    dp[i] = new Array(n);
    for (let j = 0; j < n; j++) {
      if (i === 0 && j === 0) dp[i][j] = 1;
      else if (i === 0) dp[i][j] = dp[i][j - 1];
      else if (j === 0) dp[i][j] = dp[i - 1][j];
      else dp[i][j] = dp[i][j - 1] + dp[i - 1][j];
    }
  }
  return dp[m - 1][n - 1];
};
  • DP + 滚动数组
var uniquePaths = function (m, n) {
  // a[i][j] = a[i][j - 1] + a[i - 1][j]
  let dp = new Array(n);
  dp.fill(0);
  dp[0] = 1;
  for (let i = 0; i < m; i++) {
    for (let j = 0; j < n; j++) {
      if (j >= 1) dp[j] += dp[j - 1];
    }
  }
  return dp[n - 1];
};

63. 不同路径 II

  • 题目描述
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。
现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径?

示例 1:
输入:obstacleGrid = [[0,0,0],[0,1,0],[0,0,0]]
输出:2
解释:
3x3 网格的正中间有一个障碍物。
从左上角到右下角一共有 2 条不同的路径:
1. 向右 -> 向右 -> 向下 -> 向下
2. 向下 -> 向下 -> 向右 -> 向右

示例 2:
输入:obstacleGrid = [[0,1],[0,0]]
输出:1

m == obstacleGrid.length
n == obstacleGrid[i].length
1 <= m, n <= 100
obstacleGrid[i][j] 为 0 或 1
  • 方法一:DP
var uniquePathsWithObstacles = function (obstacleGrid) {
  let m = obstacleGrid.length;
  let n = obstacleGrid[0].length;
  let dp = new Array(m);
  for (let i = 0; i < m; i++) {
    dp[i] = new Array(n);
    for (let j = 0; j < n; j++) {
      if (obstacleGrid[i][j] === 1) dp[i][j] = 0;
      else if (i === 0 && j === 0) dp[i][j] = 1;
      else if (i === 0) dp[i][j] = dp[i][j - 1];
      else if (j === 0) dp[i][j] = dp[i - 1][j];
      else dp[i][j] = dp[i][j - 1] + dp[i - 1][j];
    }
  }
  return dp[m - 1][n - 1];
};
  • 方法二 DP + 滚动数组
var uniquePathsWithObstacles = function (obstacleGrid) {
  let m = obstacleGrid.length;
  let n = obstacleGrid[0].length;
  let dp = new Array(n);
  dp.fill(0);
  dp[0] = 1;
  for (let i = 0; i < m; i++) {
    for (let j = 0; j < n; j++) {
      if (obstacleGrid[i][j] === 1) dp[j] = 0;
      else if (j >= 1 && obstacleGrid[i][j - 1] === 0) dp[j] += dp[j - 1];
    }
  }
  return dp[n - 1];
};

338. 比特位计数

  • 题目描述
给定一个非负整数 num。对于 0i ≤ num 范围中的每个数字 i ,计算其二进制数中的 1 的数目并将它们作为数组返回。

示例 1:
输入:2
输出:[0,1,1]

示例 2:
输入:5
输出:[0,1,1,2,1,2]

进阶:
给出时间复杂度为 O(n*sizeof(integer)) 的解答非常容易。但你可以在线性时间 O(n) 内用一趟扫描做到吗?
要求算法的空间复杂度为 O(n)。
你能进一步完善解法吗?要求在 C++ 或任何其他语言中不使用任何内置函数(如 C++ 中的 __builtin_popcount)来执行此操作。
  • 方法
var countBits = function (num) {
  let res = new Array(num + 1);
  res.fill(0);
  for (let i = 1; i <= num; i++) {
    res[i] = res[i & (i - 1)] + 1;
  }
  return res;
};

208. 实现 Trie (前缀树)

  • 题目描述
实现一个 Trie (前缀树),包含 insert, search, 和 startsWith 这三个操作。

示例:

Trie trie = new Trie();

trie.insert("apple");
trie.search("apple");   // 返回 true
trie.search("app");     // 返回 false
trie.startsWith("app"); // 返回 true
trie.insert("app");
trie.search("app");     // 返回 true
说明:

你可以假设所有的输入都是由小写字母 a-z 构成的。
保证所有输入均为非空字符串。
  • 方法
/**
 * Initialize your data structure here.
 */
var Trie = function () {
  this.preTree = {};
  this.end = "#";
};

/**
 * Inserts a word into the trie.
 * @param {string} word
 * @return {void}
 */
Trie.prototype.insert = function (word) {
  let preTree = this.preTree;
  for (let i = 0, len = word.length; i < len; i++) {
    let w = word[i];
    if (!preTree[w]) preTree[w] = {};
    preTree = preTree[w];
  }
  preTree[this.end] = this.end;
};

/**
 * Returns if the word is in the trie.
 * @param {string} word
 * @return {boolean}
 */
Trie.prototype.search = function (word) {
  let preTree = this.preTree;
  for (let i = 0, len = word.length; i < len; i++) {
    let w = word[i];
    if (!preTree[w]) return false;
    preTree = preTree[w];
  }
  return this.end in preTree;
};

/**
 * Returns if there is any word in the trie that starts with the given prefix.
 * @param {string} prefix
 * @return {boolean}
 */
Trie.prototype.startsWith = function (prefix) {
  let preTree = this.preTree;
  for (let i = 0, len = prefix.length; i < len; i++) {
    let w = prefix[i];
    if (!preTree[w]) return false;
    preTree = preTree[w];
  }
  return true;
};

/**
 * Your Trie object will be instantiated and called as such:
 * var obj = new Trie()
 * obj.insert(word)
 * var param_2 = obj.search(word)
 * var param_3 = obj.startsWith(prefix)
 */

264. 丑数 II

  • 题目描述
编写一个程序,找出第 n 个丑数。

丑数就是质因数只包含 2, 3, 5 的正整数。

示例:

输入:n = 10
输出:12
解释:1, 2, 3, 4, 5, 6, 8, 9, 10, 12 是前 10 个丑数。
说明:  

1 是丑数。
n 不超过 1690。
  • 方法:迭代
var nthUglyNumber = function (n) {
  let r = new Array(n + 1);
  r[0] = 0;
  r[1] = 1;
  let p2 = 1;
  let p3 = 1;
  let p5 = 1;
  for (let i = 2; i <= n; i++) {
    r[i] = Math.min(r[p2] * 2, r[p3] * 3, r[p5] * 5);
    if (r[i] === r[p2] * 2) p2++;
    if (r[i] === r[p3] * 3) p3++;
    if (r[i] === r[p5] * 5) p5++;
  }
  return r[n];
};

36. 有效的数独

  • 题目描述
判断一个 9x9 的数独是否有效。只需要根据以下规则,验证已经填入的数字是否有效即可。

数字 1-9 在每一行只能出现一次。
数字 1-9 在每一列只能出现一次。
数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。

上图是一个部分填充的有效的数独。

数独部分空格内已填入了数字,空白格用 '.' 表示。

示例 1:

输入:
[  ["5","3",".",".","7",".",".",".","."],
  ["6",".",".","1","9","5",".",".","."],
  [".","9","8",".",".",".",".","6","."],
  ["8",".",".",".","6",".",".",".","3"],
  ["4",".",".","8",".","3",".",".","1"],
  ["7",".",".",".","2",".",".",".","6"],
  [".","6",".",".",".",".","2","8","."],
  [".",".",".","4","1","9",".",".","5"],
  [".",".",".",".","8",".",".","7","9"]
]
输出:true
示例 2:

输入:
[  ["8","3",".",".","7",".",".",".","."],
  ["6",".",".","1","9","5",".",".","."],
  [".","9","8",".",".",".",".","6","."],
  ["8",".",".",".","6",".",".",".","3"],
  ["4",".",".","8",".","3",".",".","1"],
  ["7",".",".",".","2",".",".",".","6"],
  [".","6",".",".",".",".","2","8","."],
  [".",".",".","4","1","9",".",".","5"],
  [".",".",".",".","8",".",".","7","9"]
]
输出:false
解释:除了第一行的第一个数字从 5 改为 8 以外,空格内其他数字均与 示例 1 相同。
     但由于位于左上角的 3x3 宫内有两个 8 存在,因此这个数独是无效的。
说明:

一个有效的数独(部分已被填充)不一定是可解的。
只需要根据以上规则,验证已经填入的数字是否有效即可。
给定数独序列只包含数字 1-9 和字符 '.' 。
给定数独永远是 9x9 形式的。
  • 方法一:DFS
/**
 * @param {character[][]} board
 * @return {boolean}
 */
var isValidSudoku = function (board) {
  function check(board, row, col, c) {
    for (let i = 0; i < 9; i++) {
      if (i != col && board[row][i] == c) return false;
      if (i != row && board[i][col] == c) return false;
    }
    let br = (row / 3) | 0;
    let bc = (col / 3) | 0;
    for (let i = 3 * br; i < 3 * br + 3; i++) {
      for (let j = 3 * bc; j < 3 * bc + 3; j++) {
        if (!(i == row && j == col) && board[i][j] == c) return false;
      }
    }
    return true;
  }

  for (let i = 0; i < 9; i++) {
    for (let j = 0; j < 9; j++) {
      let c = board[i][j];
      if (c == ".") continue;
      if (!check(board, i, j, c)) return false;
    }
  }
  return true;
};
  • 方法二:Map
/**
 * @param {character[][]} board
 * @return {boolean}
 */
var isValidSudoku = function (board) {
  let row = createArray(9, 9);
  let col = createArray(9, 9);
  let block = createArray(9, 9);

  for (let i = 0; i < 9; i++) {
    for (let j = 0; j < 9; j++) {
      if (board[i][j] === ".") continue;
      let num = board[i][j] - 1;
      let b = ((i / 3) | 0) * 3 + ((j / 3) | 0);
      if (row[i][num] || col[j][num] || block[b][num]) return false;
      else {
        row[i][num] = 1;
        col[j][num] = 1;
        block[b][num] = 1;
      }
    }
  }

  return true;
};

function createArray(m, n) {
  let res = new Array(m);
  for (let i = 0; i < m; i++) {
    res[i] = new Array(n);
    for (let j = 0; j < n; j++) {
      res[i][j] = 0;
    }
  }
  return res;
}

328. 奇偶链表

  • 题目描述
给定一个单链表,把所有的奇数节点和偶数节点分别排在一起。请注意,这里的奇数节点和偶数节点指的是节点编号的奇偶性,而不是节点的值的奇偶性。

请尝试使用原地算法完成。你的算法的空间复杂度应为 O(1),时间复杂度应为 O(nodes),nodes 为节点总数。

示例 1:

输入:1->2->3->4->5->NULL
输出:1->3->5->2->4->NULL
示例 2:

输入:2->1->3->5->6->4->7->NULL
输出:2->3->6->7->1->5->4->NULL
说明:

应当保持奇数节点和偶数节点的相对顺序。
链表的第一个节点视为奇数节点,第二个节点视为偶数节点,以此类推。
  • 方法:奇偶指针
/**
 * @param {ListNode} head
 * @return {ListNode}
 */
var oddEvenList = function (head) {
  if (head === null) return head;
  let odd = head;
  let even = head.next;
  let evenHead = even;

  while (odd.next && even.next) {
    odd.next = even.next;
    even.next = odd.next.next;
    odd = odd.next;
    even = even.next;
  }
  odd.next = evenHead;
  return head;
};

102. 二叉树的层序遍历

  • 题目描述
给你一个二叉树,请你返回其按 层序遍历 得到的节点值。 (即逐层地,从左到右访问所有节点)。

示例:

二叉树:[3,9,20,null,null,15,7],

    3
   / \
  9  20
    /  \
   15   7
返回其层次遍历结果:

[
  [3],
  [9,20],
  [15,7]
]
  • 方法一:迭代
/**
 * @param {TreeNode} root
 * @return {number[][]}
 */
var levelOrder = function (root) {
  let result = [];
  let list = root === null ? [] : [root];
  let level = 0;

  while (list.length) {
    let len = list.length;
    let tmp = [];
    result[level] = [];
    for (let i = 0; i < len; i++) {
      let node = list[i];
      if (node) {
        result[level].push(node.val);
        node.left && tmp.push(node.left);
        node.right && tmp.push(node.right);
      }
    }
    list = tmp;
    level++;
  }
  return result;
};
  • 方法二:递归
/**
 * @param {TreeNode} root
 * @return {number[][]}
 */
var levelOrder = function (root) {
  let result = [];
  function helper(root, level) {
    if (root === null) return result;
    if (!result[level]) result[level] = [];
    result[level].push(root.val);
    helper(root.left, level + 1);
    helper(root.right, level + 1);
  }
  helper(root, 0);
  return result;
};

50. Pow(x, n)

  • 题目描述
实现 pow(x, n) ,即计算 x  n 次幂函数。

示例 1:

输入:2.00000, 10
输出:1024.00000
示例 2:

输入:2.10000, 3
输出:9.26100
示例 3:

输入:2.00000, -2
输出:0.25000
解释:2-2 = 1/22 = 1/4 = 0.25
  • 方法一:递归
/**
 * @param {number} x
 * @param {number} n
 * @return {number}
 */
var myPow = function (x, n) {
  if (n === 0) return 1;
  if (n < 0) return 1 / myPow(x, -n);
  if (n & 1) return x * myPow(x, n - 1);
  return myPow(x * x, n / 2);
};
  • 方法二:迭代
/**
 * @param {number} x
 * @param {number} n
 * @return {number}
 */
var myPow = function (x, n) {
  if (n < 0) {
    x = 1 / x;
    n *= -1;
  }
  let pow = 1;
  while (n) {
    if (n & 1) pow *= x;
    x *= x;
    n = n / 2;
  }
  return pow;
};

236. 二叉树的最近公共祖先

  • 题目描述
给定一个二叉树,找到该树中两个指定节点的最近公共祖先。

百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 pq,最近公共祖先表示为一个结点 x,满足 xpq 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”

例如,给定如下二叉树:root = [3,5,1,6,2,0,8,null,null,7,4]

示例 1:

输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出:3
解释:节点 5 和节点 1 的最近公共祖先是节点 3。

示例 2:

输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4
输出:5
解释:节点 5 和节点 4 的最近公共祖先是节点 5。因为根据定义最近公共祖先节点可以为节点本身
  • 方法:递归
/**
 * @param {TreeNode} root
 * @param {TreeNode} p
 * @param {TreeNode} q
 * @return {TreeNode}
 */
var lowestCommonAncestor = function (root, p, q) {
  if (root === null) return root;
  if (root === p || root === q) return root;
  let l = lowestCommonAncestor(root.left, p, q);
  let r = lowestCommonAncestor(root.right, p, q);
  if (l && r) return root;
  return l || r;
};

235. 二叉搜索树的最近公共祖先

  • 题目描述
给定一个二叉搜索树,找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 pq,最近公共祖先表示为一个结点 x,
满足 xpq 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”

例如,给定如下二叉搜索树:root = [6,2,8,0,4,7,9,null,null,3,5]

示例 1:

输入:root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8
输出:6
解释:节点 2 和节点 8 的最近公共祖先是 6。

示例 2:

输入:root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 4
输出:2
解释:节点 2 和节点 4 的最近公共祖先是 2, 因为根据定义最近公共祖先节点可以为节点本身。
  • 方法:递归
/**
 * @param {TreeNode} root
 * @param {TreeNode} p
 * @param {TreeNode} q
 * @return {TreeNode}
 */
var lowestCommonAncestor = function (root, p, q) {
  if (root === null) return root;
  if (p.val < root.val && q.val < root.val) {
    return lowestCommonAncestor(root.left, p, q);
  }
  if (p.val > root.val && q.val > root.val) {
    return lowestCommonAncestor(root.right, p, q);
  }
  return root;
};
  • 方法二:迭代
var lowestCommonAncestor = function (root, p, q) {
  while (root) {
    if (p.val < root.val && root.val > q.val) root = root.left;
    else if (p.val > root.val && root.val < q.val) root = root.right;
    else return root;
  }
};

98. 验证二叉搜索树

  • 题目描述
给定一个二叉树,判断其是否是一个有效的二叉搜索树。

假设一个二叉搜索树具有如下特征:

节点的左子树只包含小于当前节点的数。
节点的右子树只包含大于当前节点的数。
所有左子树和右子树自身必须也是二叉搜索树。

示例 1:

输入:
    2
   / \
  1   3
输出:true

示例 2:

输入:
    5
   / \
  1   4
     / \
    3   6
输出:false
解释:输入为:[5,1,4,null,null,3,6]。
     根节点的值为 5 ,但是其右子节点值为 4
  • 方法一:递归 + 中序遍历
/**
 * Definition for a binary tree node.
 * function TreeNode(val) {
 *     this.val = val;
 *     this.left = this.right = null;
 * }
 */

/**
 * @param {TreeNode} root
 * @return {boolean}
 */
var isValidBST = function (root) {
  let pre = null;
  function helper(root) {
    if (root === null) return true;
    if (!helper(root.left)) return false;
    if (pre && pre.val >= root.val) return false;
    pre = root;
    return helper(root.right);
  }
  return helper(root);
};
  • 方法二:设置最小、最大值
var isValidBST = function (root) {
  function helper(root, min, max) {
    if (root === null) return true;
    if (min != null && min >= root.val) return false;
    if (max != null && max <= root.val) return false;
    return (
      helper(root.left, min, root.val) && helper(root.right, root.val, max)
    );
  }

  return helper(root);
};

18. 四数之和

  • 题目描述
给定一个包含 n 个整数的数组 nums 和一个目标值 target,判断 nums 中是否存在四个元素 ab,c 和 d ,
使得 a + b + c + d 的值与 target 相等?找出所有满足条件且不重复的四元组。

注意:

答案中不可以包含重复的四元组。

示例:

给定数组 nums = [1, 0, -1, 0, -2, 2],和 target = 0。

满足要求的四元组集合为:
[  [-1,  0, 0, 1],
  [-2, -1, 1, 2],
  [-2,  0, 0, 2]
]
  • 方法:将四数之和转换为三数之和
/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number[][]}
 */
var fourSum = function (nums, target) {
  if (nums.length < 4) return [];
  let result = [];
  nums.sort((a, b) => a - b);
  for (let i = 0, len = nums.length; i < len; i++) {
    if (i > 0 && nums[i] === nums[i - 1]) continue;
    let new_target = target - nums[i];
    for (let j = i + 1; j < len; j++) {
      if (j > i + 1 && nums[j] === nums[j - 1]) continue;
      let l = j + 1;
      let r = len - 1;
      while (l < r) {
        let sum = nums[j] + nums[l] + nums[r];
        if (sum < new_target) {
          l += 1;
        } else if (sum > new_target) {
          r -= 1;
        } else {
          result.push([nums[i], nums[j], nums[l], nums[r]]);
          while (nums[l] === nums[l + 1]) l += 1;
          while (nums[r] === nums[r - 1]) r -= 1;
          l += 1;
          r -= 1;
        }
      }
    }
  }
  return result;
};

15. 三数之和

  • 题目描述
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 ab,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。

注意:答案中不可以包含重复的三元组。

示例:

给定数组 nums = [-1, 0, 1, 2, -1, -4],

满足要求的三元组集合为:
[  [-1, 0, 1],
  [-1, -1, 2]
]
  • 方法:双指针
/**
 * @param {number[]} nums
 * @return {number[][]}
 */
var threeSum = function (nums) {
  if (nums.length < 3) return [];
  let result = [];
  nums.sort((a, b) => a - b);
  for (let i = 0, len = nums.length; i < len; i++) {
    if (i > 0 && nums[i] === nums[i - 1]) continue;
    let l = i + 1;
    let r = len - 1;
    while (l < r) {
      let sum = nums[i] + nums[l] + nums[r];
      if (sum > 0) r -= 1;
      else if (sum < 0) l += 1;
      else {
        result.push([nums[i], nums[l], nums[r]]);
        while (nums[l] === nums[l + 1]) l += 1;
        while (nums[r] === nums[r - 1]) r -= 1;
        l += 1;
        r -= 1;
      }
    }
  }
  return result;
};

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

  • 题目描述
给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。

你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。

示例 1:

输入:head = [1,2,3,4]
输出:[2,1,4,3]
示例 2:

输入:head = []
输出:[]
示例 3:

输入:head = [1]
输出:[1]

方法:迭代 + 多指针

/**
 * @param {ListNode} head
 * @return {ListNode}
 */
var swapPairs = function (head) {
  if (head === null) {
    return head;
  }
  let result = head;
  let pre = null;
  let slow = head;
  let fast = head.next;

  while (fast) {
    let tmp = fast.next;
    fast.next = slow;
    slow.next = tmp;
    if (pre === null) {
      pre = slow;
      result = fast;
    } else {
      pre.next = fast;
      pre = slow;
    }
    if (tmp === null) break;
    slow = tmp;
    fast = tmp.next;
  }

  return result;
};

剑指 Offer 35. 复杂链表的复制

  • 题目描述
请实现 copyRandomList 函数,复制一个复杂链表。
在复杂链表中,每个节点除了有一个 next 指针指向下一个节点,还有一个 random 指针指向链表中的任意节点或者 null。

示例 1:

输入:head = [[7,null],[13,0],[11,4],[10,2],[1,0]]
输出:[[7,null],[13,0],[11,4],[10,2],[1,0]]

示例 2:

输入:head = [[1,1],[2,1]]
输出:[[1,1],[2,1]]

示例 3:

输入:head = [[3,null],[3,0],[3,null]]
输出:[[3,null],[3,0],[3,null]]

示例 4:

输入:head = []
输出:[]
解释:给定的链表为空(空指针),因此返回 null。
  • 方法一:迭代
/**
 * // Definition for a Node.
 * function Node(val, next, random) {
 *    this.val = val;
 *    this.next = next;
 *    this.random = random;
 * };
 */

/**
 * @param {Node} head
 * @return {Node}
 */
var copyRandomList = function (head) {
  if (head === null) {
    return null;
  }
  let root = new Node(-1, null, null);
  let pre = root;
  let map = new WeakMap();
  let cur = head;
  while (cur) {
    let node = new Node(cur.val, null, null);
    pre.next = node;
    map.set(cur, node);
    pre = node;
    cur = cur.next;
  }
  let troot = root.next;
  cur = head;
  while (cur) {
    troot.random = map.has(cur.random) ? map.get(cur.random) : null;
    cur = cur.next;
    troot = troot.next;
  }
  return root.next;
};
  • 方法二:递归
var copyRandomList = function (head) {
  function helper(root, map = new WeakMap()) {
    if (root === null) return root;
    if (map.has(root)) return map.get(root);
    let node = new Node(root.val);
    map.set(root, node);
    node.next = helper(root.next, map);
    node.random = helper(root.random, map);
    return node;
  }

  return helper(head);
};

困难难度

37. 解数独

  • 题目描述
编写一个程序,通过填充空格来解决数独问题。

一个数独的解法需遵循如下规则:
数字 1-9 在每一行只能出现一次。
数字 1-9 在每一列只能出现一次。
数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。
空白格用 '.' 表示。

一个数独。
答案被标成红色。

提示:

给定的数独序列只包含数字 1-9 和字符 '.' 。
你可以假设给定的数独只有唯一解。
给定数独永远是 9x9 形式的。

部分数独 最终结果

  • 方法
/**
 * @param {character[][]} board
 * @return {void} Do not return anything, modify board in-place instead.
 */
var solveSudoku = function (board) {
  if (!board || !board.length) return;
  dfs(board);
};

function dfs(board) {
  for (let i = 0; i < 9; i++)
    for (let j = 0; j < 9; j++) {
      if (board[i][j] == ".") {
        for (let c = 1; c <= 9; c++) {
          if (isValid(board, i, j, c)) {
            board[i][j] = c + "";
            if (dfs(board)) return true;
            else board[i][j] = ".";
          }
        }
        return false;
      }
    }
  return true;
}

function isValid(board, row, col, c) {
  for (let i = 0; i < 9; i++) {
    if (i != row && board[i][col] == c) return false;
    if (i != col && board[row][i] == c) return false;
  }
  let br = (row / 3) | 0;
  let bc = (col / 3) | 0;
  for (let i = 3 * br; i < 3 * br + 3; i++) {
    for (let j = 3 * bc; j < 3 * bc + 3; j++) {
      if (!(i == row && j == col) && board[i][j] == c) return false;
    }
  }
  return true;
}

剑指 Offer 37. 序列化二叉树

  • 题目描述
请实现两个函数,分别用来序列化和反序列化二叉树。

示例: 

你可以将以下二叉树:

    1
   / \
  2   3
     / \
    4   5

序列化为 "[1,2,3,null,null,4,5]"
  • 方法
/**
 * Definition for a binary tree node.
 * function TreeNode(val) {
 *     this.val = val;
 *     this.left = this.right = null;
 * }
 */

/**
 * Encodes a tree to a single string.
 *
 * @param {TreeNode} root
 * @return {string}
 */
var serialize = function (root) {
  if (root === null) return "";
  let queue = [];
  let tree = [root];
  while (tree.length) {
    let len = tree.length;
    for (let i = 0; i < len; i++) {
      let node = tree.shift();
      if (!node) {
        queue.push("#");
        continue;
      }
      queue.push(node.val);
      tree.push(node.left);
      tree.push(node.right);
    }
  }
  return queue.join(" ");
};

/**
 * Decodes your encoded data to tree.
 *
 * @param {string} data
 * @return {TreeNode}
 */
var deserialize = function (data) {
  if (!data) return null;
  let arr = data.split(/\s/g);
  let root = new TreeNode(arr[0]);
  let queue = [root];
  let i = 1;
  while (queue.length) {
    let node = queue.shift();
    if (!node) continue;
    node.left = arr[i] === "#" ? null : new TreeNode(arr[i]);
    node.right = arr[i + 1] === "#" ? null : new TreeNode(arr[i + 1]);
    i += 2;
    queue.push(node.left);
    queue.push(node.right);
  }
  return root;
};

/**
 * Your functions will be called as such:
 * deserialize(serialize(root));
 */

51. N 皇后

  • 题目描述
n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。

上图为 8 皇后问题的一种解法。
给定一个整数 n,返回所有不同的 n 皇后问题的解决方案。
每一种解法包含一个明确的 n 皇后问题的棋子放置方案,该方案中 'Q''.' 分别代表了皇后和空位。

示例:

输入:4
输出:[
[".Q..",  // 解法 1
  "...Q",
  "Q...",
  "..Q."],

["..Q.",  // 解法 2
  "Q...",
  "...Q",
  ".Q.."]
]
解释:4 皇后问题存在两个不同的解法。
  • 方法:DFS
/**
 * @param {number} n
 * @return {string[][]}
 */
var solveNQueens = function (n) {
  if (n < 1) return [];
  let res = [];
  let cols = new Set();
  let sum = new Set();
  let sub = new Set();

  function dfs(row, save) {
    if (row >= n) {
      res.push(save);
      return;
    }
    for (let col = 0; col < n; col++) {
      if (!cols.has(col) && !sum.has(row + col) && !sub.has(row - col)) {
        cols.add(col);
        sum.add(row + col);
        sub.add(row - col);
        dfs(row + 1, [...save, col]);
        cols.delete(col);
        sum.delete(row + col);
        sub.delete(row - col);
      }
    }
  }

  function format(res) {
    return res.map((item) => {
      let r = [];
      item.forEach((index) => {
        let a = new Array(n);
        a.fill(".");
        a[index] = "Q";
        r.push(a.join(""));
      });
      return r;
    });
  }

  dfs(0, []);
  return format(res);
};
  • 位运算
function Nqueen(n) {
  // write code here
  let count = 0;
  function dfs(row, cols, sum, sub) {
    if (row >= n) {
      count += 1;
      return;
    }
    // 获取某一行的所有空位 1
    let bits = ~(cols | sum | sub) & ((1 << n) - 1);
    while (bits) {
      // 获取最后一个 1
      let p = -bits & bits;
      dfs(row + 1, cols | p, (sum | p) << 1, (sub | p) >> 1);
      // 删除最后一个 1
      bits = bits & (bits - 1);
    }
  }
  dfs(0, 0, 0, 0);
  return count;
}

剑指 Offer 59 - I. 滑动窗口的最大值

  • 题目描述
给定一个数组 nums 和滑动窗口的大小 k,请找出所有滑动窗口里的最大值。

示例:

输入:nums = [1,3,-1,-3,5,3,6,7], 和 k = 3
输出:[3,3,5,5,6,7]
解释:

  滑动窗口的位置                最大值
---------------               -----
[1  3  -1] -3  5  3  6  7       3
 1 [3  -1  -3] 5  3  6  7       3
 1  3 [-1  -3  5] 3  6  7       5
 1  3  -1 [-3  5  3] 6  7       5
 1  3  -1  -3 [5  3  6] 7       6
 1  3  -1  -3  5 [3  6  7]      7
  • 方法:双向队列(队列头永远保存最大元素索引)
/**
 * @param {number[]} nums
 * @param {number} k
 * @return {number[]}
 */
var maxSlidingWindow = function (nums, k) {
  let [r, windows] = [[], []];
  if (!nums.length) return r;
  for (let i = 0, len = nums.length; i < len; i++) {
    if (i >= k && windows[0] <= i - k) windows.shift();
    let size = windows.length - 1;
    while (windows.length && nums[windows[size--]] < nums[i]) windows.pop();
    windows.push(i);
    if (i >= k - 1) r.push(nums[windows[0]]);
  }
  return r;
};