XingYun blog
  • JS基础

    • 图解js原型链
    • JS Event Loop
    • 对象的底层数据结构
    • 让你的JavaScript代码简单又高效
    • 函数参数按值传递
    • 判断数据类型
    • 浮点数精度问题和解决办法
    • 常用方法snippet
    • 实现Promise
    • 防抖和节流
    • 巧用sort排序
  • CSS && HTML

    • CSS也需要性能优化
    • class命名规范
    • em、px、rem、vh、vw 区别
    • CSS揭秘阅读笔记
  • 浏览器

    • 浏览器是如何渲染页面的
    • 重排和重绘
    • BOM浏览器对象模型
    • DOM事件
    • 浏览器存储
  • 数据结构

    • JS实现链表
    • JS实现栈与栈应用
    • JS实现常见排序
    • 哈夫曼编码
    • MD5算法
  • vue原理浅析

    • Vue虚拟dom与Diff算法
    • 前端打包文件的缓存机制
    • vue数组为什么不是响应式
    • v-for为什么不能用index做key
  • 前端工程化

    • 浏览器是如何渲染页面的
    • 前端打包需要gzip压缩吗
    • 前端打包文件的缓存机制
    • webpack loader和plugin
  • 轮子&&组件库

    • 实现水波浪进度球
  • 文字转语音mp3文件
  • 文件上传前后端实现
  • moment.js给定时间获取自然月、周的时间轴
  • 实现文件上传功能
  • 批量下载照片
  • leaflet改变坐标原点
  • 网络

    • 有了MAC地址 为什么还需要IP地址
    • 为什么IP地址老是变
    • 我们为什么需要IPV6
    • TCP与UDP
  • 计算机组成原理

    • ASCII、Unicode、UTF-8和UTF-16
  • VSCode

    • VSCode图片预览插件 Image preview
    • rsync:linux间的高效传输工具

XingYun

冲!
  • JS基础

    • 图解js原型链
    • JS Event Loop
    • 对象的底层数据结构
    • 让你的JavaScript代码简单又高效
    • 函数参数按值传递
    • 判断数据类型
    • 浮点数精度问题和解决办法
    • 常用方法snippet
    • 实现Promise
    • 防抖和节流
    • 巧用sort排序
  • CSS && HTML

    • CSS也需要性能优化
    • class命名规范
    • em、px、rem、vh、vw 区别
    • CSS揭秘阅读笔记
  • 浏览器

    • 浏览器是如何渲染页面的
    • 重排和重绘
    • BOM浏览器对象模型
    • DOM事件
    • 浏览器存储
  • 数据结构

    • JS实现链表
    • JS实现栈与栈应用
    • JS实现常见排序
    • 哈夫曼编码
    • MD5算法
  • vue原理浅析

    • Vue虚拟dom与Diff算法
    • 前端打包文件的缓存机制
    • vue数组为什么不是响应式
    • v-for为什么不能用index做key
  • 前端工程化

    • 浏览器是如何渲染页面的
    • 前端打包需要gzip压缩吗
    • 前端打包文件的缓存机制
    • webpack loader和plugin
  • 轮子&&组件库

    • 实现水波浪进度球
  • 文字转语音mp3文件
  • 文件上传前后端实现
  • moment.js给定时间获取自然月、周的时间轴
  • 实现文件上传功能
  • 批量下载照片
  • leaflet改变坐标原点
  • 网络

    • 有了MAC地址 为什么还需要IP地址
    • 为什么IP地址老是变
    • 我们为什么需要IPV6
    • TCP与UDP
  • 计算机组成原理

    • ASCII、Unicode、UTF-8和UTF-16
  • VSCode

    • VSCode图片预览插件 Image preview
    • rsync:linux间的高效传输工具
  • 算法图解阅读笔记
  • MD5算法
  • JS实现常见排序
  • 邓俊辉算法与数据结构
  • JS实现队列
  • JS实现树的几种遍历方式
  • 二叉树相关
  • 哈希算法
  • 进制转换与js实现
  • JS实现链表
  • JS实现栈与栈应用
  • 算法与数据结构
XingYun
2022-09-13
目录

leetcode-main

记录我的 leetcode 薄弱题目

# 39. 组合总和

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
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 96. 不同的二叉搜索树

/**
 * @param {number} n
 * @return {number}
 */
var numTrees = function (n) {
  const dp = new Array(n + 1).fill(0)
  dp[0] = 1
  dp[1] = 1

  for (let i = 2; i <= n; ++i) {
    for (let j = 1; j <= i; ++j) {
      dp[i] += dp[j - 1] * dp[i - j]
    }
  }

  return dp[n]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 72. 编辑距离

var minDistance = function (word1, word2) {
  let m = word1.length
  let n = word2.length
  let dp = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0))
  // 初始化
  for (let i = 1; i <= m; i++) {
    dp[i][0] = i
  }
  for (let j = 1; j <= n; j++) {
    dp[0][j] = j
  }

  for (let i = 1; i <= m; i++) {
    for (let j = 1; j <= n; j++) {
      if (word1[i - 1] === word2[j - 1]) {
        // 最后一位字符一样,不需要任何操作
        dp[i][j] = dp[i - 1][j - 1]
      } else {
        // 插入、删除、替换
        dp[i][j] = Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]) + 1
      }
    }
  }
  return dp[m][n]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# 85. 最大矩形

/**
 * @param {character[][]} matrix
 * @return {number}
 */
var maximalRectangle = function (matrix) {
  const m = matrix.length
  if (m === 0) {
    return 0
  }
  const n = matrix[0].length
  const left = new Array(m).fill(0).map(() => new Array(n).fill(0))

  for (let i = 0; i < m; i++) {
    for (let j = 0; j < n; j++) {
      if (matrix[i][j] === '1') {
        left[i][j] = (j === 0 ? 0 : left[i][j - 1]) + 1
      }
    }
  }

  let ret = 0
  for (let i = 0; i < m; i++) {
    for (let j = 0; j < n; j++) {
      if (matrix[i][j] === '0') {
        continue
      }
      let width = left[i][j]
      let area = width
      for (let k = i - 1; k >= 0; k--) {
        width = Math.min(width, left[k][j])
        area = Math.max(area, (i - k + 1) * width)
      }
      ret = Math.max(ret, area)
    }
  }
  return ret
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

# 338. 比特位计数

var countBits = function (n) {
  const bits = new Array(n + 1).fill(0)
  let highBit = 0
  for (let i = 1; i <= n; i++) {
    if ((i & (i - 1)) == 0) {
      highBit = i
    }
    bits[i] = bits[i - highBit] + 1
  }
  return bits
}
1
2
3
4
5
6
7
8
9
10
11

# 312. 戳气球

/**
 * @param {number[]} nums
 * @return {number}
 */
var maxCoins = function (nums) {
  let n = nums.length
  // 添加两侧的虚拟气球
  let points = new Array(n + 2)
  points[0] = points[n + 1] = 1
  for (let i = 1; i <= n; i++) {
    points[i] = nums[i - 1]
  }
  // base case 已经都被初始化为 0
  let dp = new Array(n + 2).fill(0).map(() => new Array(n + 2).fill(0))
  // 开始状态转移
  // i 应该从下往上
  for (let i = n; i >= 0; i--) {
    // j 应该从左往右
    for (let j = i + 1; j < n + 2; j++) {
      // 最后戳破的气球是哪个?
      for (let k = i + 1; k < j; k++) {
        // 择优做选择
        dp[i][j] = Math.max(
          dp[i][j],
          dp[i][k] + dp[k][j] + points[i] * points[j] * points[k]
        )
      }
    }
  }
  return dp[0][n + 1]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

# 416. 分割等和子集

https://leetcode.cn/problems/partition-equal-subset-sum/solution/fen-ge-deng-he-zi-ji-by-leetcode-solution/

/**
 * @param {number[]} nums
 * @return {boolean}
 */
var canPartition = function (nums) {
  const n = nums.length
  if (n < 2) {
    return false
  }
  let sum = 0,
    maxNum = 0
  for (const num of nums) {
    sum += num
    maxNum = maxNum > num ? maxNum : num
  }
  if (sum & 1) {
    return false
  }
  const target = Math.floor(sum / 2)
  if (maxNum > target) {
    return false
  }
  const dp = new Array(target + 1).fill(false)
  dp[0] = true
  for (const num of nums) {
    for (let j = target; j >= num; --j) {
      dp[j] |= dp[j - num]
    }
  }
  return dp[target]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

# 621. 任务调度器

var leastInterval = function (tasks, n) {
  if (n < 1) return tasks.length
  let map = new Map()
  for (let task of tasks) {
    map.set(task, map.has(task) ? map.get(task) + 1 : 1)
  }
  let counts = Array.from(map)
    .map((item) => item[1])
    .sort((a, b) => b - a)
  let maxCount = 0
  for (let count of counts) {
    if (count === counts[0]) maxCount++
    else break
  }
  let sum = (counts[0] - 1) * (n + 1) + maxCount
  return sum > tasks.length ? sum : tasks.length
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 32. 最长有效括号

  • 用栈模拟一遍,将所有无法匹配的括号的位置全部置 false
  • 例如: "()(()"的 mark 为[true, true, false, true, true]
  • 再例如: ")()((())"的 mark 为[false, true, true, false, true, true, true, true]
  • 经过这样的处理后, 此题就变成了寻找最长的连续的 true 的长度
/**
 * @param {string} s
 * @return {number}
 */
var longestValidParentheses = function (s) {
  if (s.length < 2) return 0
  let arr = new Array(s.length).fill(true)
  let notMatch = isValid(s)
  for (let i = 0; i < notMatch.length; i++) {
    arr[notMatch[i]] = false
  }
  let ans = 0
  let i = 0
  while (i < s.length) {
    if (arr[i]) {
      let j = i + 1
      while (arr[j]) {
        j++
      }
      ans = Math.max(ans, j - i)
      i = j
    } else {
      i++
    }
  }
  return ans
}

var isValid = function (s) {
  let stack = []
  let res = []
  for (let i = 0; i < s.length; i++) {
    if (s[i] === '(') {
      stack.push(i)
    } else {
      if (stack.length === 0) {
        res.push(i)
      } else {
        stack.pop()
      }
    }
  }
  while (stack.length) {
    res.push(stack.pop())
  }
  return res
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

# 89. 格雷编码

var grayCode = function (n) {
  const ret = [0]
  for (let i = 1; i <= n; i++) {
    const m = ret.length
    for (let j = m - 1; j >= 0; j--) {
      ret.push(ret[j] | (1 << (i - 1)))
    }
  }
  return ret
}
1
2
3
4
5
6
7
8
9
10

# 16. 最接近的三数之和

/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number}
 */
var threeSumClosest = function (nums, target) {
  // 升序排序
  nums.sort((a, b) => a - b)
  // 初始化一个最小值
  let min = Infinity
  const len = nums.length
  for (let i = 0; i < len - 2; i++) {
    // 定义左右指针
    let left = i + 1,
      right = len - 1
    while (left < right) {
      // 当前三数之和
      const sum = nums[i] + nums[left] + nums[right]
      // 如果当前和更接近,更新最小值
      if (Math.abs(sum - target) < Math.abs(min - target)) {
        min = sum
      }
      // 根据sum和target的关系,移动指针
      if (sum < target) {
        left++
      } else if (sum > target) {
        right--
      } else {
        // sum和target相等,直接返回sum,肯定是最小的了
        return sum
      }
    }
  }
  // 遍历结束,返回最接近的和
  return min
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

# 12. 整数转罗马数字

/**
 * @param {number} num
 * @return {string}
 */
var intToRoman = function (num) {
  const valueSymbols = [
    [1000, 'M'],
    [900, 'CM'],
    [500, 'D'],
    [400, 'CD'],
    [100, 'C'],
    [90, 'XC'],
    [50, 'L'],
    [40, 'XL'],
    [10, 'X'],
    [9, 'IX'],
    [5, 'V'],
    [4, 'IV'],
    [1, 'I']
  ]
  const roman = []
  for (const [value, symbol] of valueSymbols) {
    while (num >= value) {
      num -= value
      roman.push(symbol)
    }
    if (num == 0) {
      break
    }
  }
  return roman.join('')
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

# 71. 简化路径

重点是利用栈和 根据 / 切割

var simplifyPath = function (path) {
  const names = path.split('/') // 所有路径都是以 / 分割
  const stack = []
  for (const name of names) {
    if (name === '..') {
      if (stack.length) {
        stack.pop() // 遇到 .. pop上一个
      }
    } else if (name.length && name !== '.') {
      stack.push(name) // 如果等于 . 直接跳过  如果是其它直接入栈
    }
  }
  return '/' + stack.join('/')
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 95. 不同的二叉搜索树 II

/**
 * @param {number} n
 * @return {TreeNode[]}
 */
var generateTrees = function (n) {
  let nums = []
  for (let i = 1; i <= n; i++) {
    nums.push(i)
  }
  return handle(nums)
}

var handle = function (nums) {
  if (nums.length === 0) return [null] // 如果nums长度为0 直接返回null
  let allNodes = []
  for (let i = 0; i < nums.length; i++) {
    let leftNodes = handle(nums.slice(0, i)) // 返回左树列表
    let rightNodes = handle(nums.slice(i + 1)) // 返回右树列表
    // 如果左右树都为空 则循环赋值时也会刚好生成一个节点 左右为null
    for (let j = 0; j < leftNodes.length; j++) {
      for (let k = 0; k < rightNodes.length; k++) {
        let node = new TreeNode(nums[i])
        node.right = rightNodes[k]
        node.left = leftNodes[j]
        allNodes.push(node)
      }
    }
  }
  return allNodes
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

# 65. 有效数字

var isNumber = function (s) {
  // blank 表示空格
  // sign 表示正负号
  // digit 表示数字
  // . 就是小数点
  // e 就是 e
  const graph = {
    0: { blank: 0, sign: 1, '.': 2, digit: 6 },
    1: { digit: 6, '.': 2 },
    2: { digit: 3 },
    3: { digit: 3, e: 4, E: 4 },
    4: { digit: 5, sign: 7 },
    5: { digit: 5 },
    6: { digit: 6, '.': 3, e: 4, E: 4 },
    7: { digit: 5 }
  }

  let state = 0
  let char
  s = s.trim()
  for (char of s) {
    if (char >= '0' && char <= '9') {
      char = 'digit'
    } else if (char == ' ') {
      char = 'blank'
    } else if (char == '+' || char == '-') {
      char = 'sign'
    }
    state = graph[state][char]
    if (state == undefined) {
      return false
    }
  }
  // 3 是小数,5是科学计数法,6是整数
  return state == 3 || state == 5 || state == 6
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

# 97. 交错字符串

var isInterleave = function (s1, s2, s3) {
  const m = s1.length + 1,
    n = s2.length + 1
  if (s3.length !== m + n - 2) return false
  const dp = []
  for (let i = 0; i < m; ++i) {
    const temp = new Array(n)
    dp.push(temp)
  }
  dp[0][0] = true
  for (let i = 1; i < m; ++i) {
    dp[i][0] = dp[i - 1][0] && s1[i - 1] === s3[i - 1]
  }
  for (let j = 1; j < n; ++j) {
    dp[0][j] = dp[0][j - 1] && s2[j - 1] === s3[j - 1]
  }
  for (let i = 1; i < m; ++i) {
    for (let j = 1; j < n; ++j) {
      dp[i][j] =
        (dp[i - 1][j] && s1[i - 1] === s3[i + j - 1]) ||
        (dp[i][j - 1] && s2[j - 1] === s3[i + j - 1])
    }
  }
  return dp[m - 1][n - 1]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# 115. 不同的子序列

var numDistinct = function (s, t) {
  const m = s.length
  const n = t.length
  if (m < n) return 0
  const dp = new Array(m + 1).fill(0).map(() => new Array(n + 1).fill(0))
  for (let i = 0; i <= m; i++) {
    dp[i][n] = 1
  }
  for (let i = m - 1; i >= 0; i--) {
    for (let j = n - 1; j >= 0; j--) {
      if (s[i] == t[j]) {
        dp[i][j] = dp[i + 1][j + 1] + dp[i + 1][j]
      } else {
        dp[i][j] = dp[i + 1][j]
      }
    }
  }
  return dp[0][0]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 123. 买卖股票的最佳时机 III

/**
       对于任意一天考虑四个变量:
       fstBuy: 在该天第一次买入股票可获得的最大收益 
       fstSell: 在该天第一次卖出股票可获得的最大收益
       secBuy: 在该天第二次买入股票可获得的最大收益
       secSell: 在该天第二次卖出股票可获得的最大收益
       分别对四个变量进行相应的更新, 最后secSell就是最大
       收益值(secSell >= fstSell)
       **/

var maxProfit = function (prices) {
  const n = prices.length
  let fstBuy = -prices[0],
    secBuy = -prices[0]
  let fstSell = 0,
    secSell = 0
  for (let i = 1; i < n; i++) {
    fstBuy = Math.max(fstBuy, -prices[i])
    fstSell = Math.max(fstSell, fstBuy + prices[i])
    secBuy = Math.max(secBuy, fstSell - prices[i])
    secSell = Math.max(secSell, secBuy + prices[i])
  }
  return secSell
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 201. 数字范围按位与

var rangeBitwiseAnd = function (m, n) {
  let shift = 0
  // 找到公共前缀
  while (m < n) {
    m >>= 1
    n >>= 1
    ++shift
  }
  return m << shift
}
1
2
3
4
5
6
7
8
9
10

# 203. 移除链表元素

var removeElements = function (head, val) {
  if (head === null) {
    return head
  }
  head.next = removeElements(head.next, val)
  return head.val === val ? head.next : head
}
1
2
3
4
5
6
7

# 209. 长度最小的子数组

/**
 * @param {number} target
 * @param {number[]} nums
 * @return {number}
 */
var minSubArrayLen = function (target, nums) {
  // 求出总和
  let allSums = nums.reduce((pre, cur) => {
    return pre + cur
  })
  if (allSums < target) return 0 //总和比目标小直接return
  let len = nums.length
  let ans = len // 最长子序列为len
  let currentSum = allSums // 当前总和
  for (let i = 0; i < len; i++) {
    if (nums[i] >= target) return 1
    // 从最后一个数开始 一个一个减去当前总和 直到当前总和小于target
    let j = len - 1
    let sum = currentSum
    while (sum >= target) {
      sum -= nums[j]
      j--
    }
    ans = Math.min(ans, j - i + 2) // 取较小结果
    currentSum -= nums[i]
    if (currentSum < target) break
  }
  return ans
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

# 222. 完全二叉树的节点个数

/**
 * @param {TreeNode} root
 * @return {number}
 */
var countNodes = function (root) {
  if (!root) return 0
  let count = 1
  count += countNodes(root.left)
  count += countNodes(root.right)
  return count
}
1
2
3
4
5
6
7
8
9
10
11

# 241. 为运算表达式设计优先级

不断分割直到是数字字符串,然后返回包含该数字的数组,然后对左右两侧的递归结果进行组合

var diffWaysToCompute = function (expression) {
  let results = []
  // 如果为纯数字 直接返回
  if (!isNaN(expression - 0)) {
    results.push(expression - 0)
    return results
  }

  for (let i = 0; i < expression.length; i++) {
    // 遇到操作符
    if (isNaN(expression[i])) {
      const operator = expression[i]
      // 分治法 分别计算操作符左右子串的结果集
      const left = diffWaysToCompute(expression.slice(0, i))
      const right = diffWaysToCompute(expression.slice(i + 1))
      // 组合左右子集的结果
      for (const l of left) {
        for (const r of right) {
          if (operator === '+') {
            results.push(l + r)
          } else if (operator === '-') {
            results.push(l - r)
          } else {
            results.push(l * r)
          }
        }
      }
    }
  }
  return results
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

# 260. 只出现一次的数字 III

const singleNumber = (nums) => {
  let temp = 0
  const len = nums.length
  for (let i = 0; i < len; i++) {
    temp = temp ^ nums[i]
  }
  // 此时temp是两个不同的数异或的结果
  // 寻找k,k是temp最低位为1、其余位是0的二进制数
  let k = 1
  while ((temp & k) === 0) k = k << 1
  let [num1, num2] = [0, 0]
  for (let i = 0; i < len; i++) {
    // 分组,目的是将两个不同的数分开
    if (nums[i] & k) {
      num1 = num1 ^ nums[i]
    } else {
      num2 = num2 ^ nums[i]
    }
  }
  return [num1, num2]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 263. 丑数

/**
 * @param {number} n
 * @return {boolean}
 */
var isUgly = function (n) {
  while (n % 5 === 0) {
    n = n / 5
  }
  while (n % 3 === 0) {
    n = n / 3
  }
  while (n % 2 === 0) {
    n = n / 2
  }
  return n == 1
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 264. 丑数 II

/**
 * @param {number} n
 * @return {number}
 */
var nthUglyNumber = function (n) {
  if (n <= 5) return n
  let ans = [1, 2, 3, 4, 5]
  let i = 6
  while (ans.length < n) {
    let num = i
    while (num % 5 === 0) {
      num = num / 5
    }
    while (num % 3 === 0) {
      num = num / 3
    }
    while (num % 2 === 0) {
      num = num / 2
    }
    if (num == 1) {
      ans.push(i)
    }
    i++
  }
  return ans[ans.length - 1]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# 316. 去除重复字母

/**
 * @param {string} s
 * @return {string}
 */
var removeDuplicateLetters = function (s) {
  // 字典序,换成数字更好理解一点 a:1,b:2,c:3, d:4 那么:acdb => 1342 dcab => 4312
  //  4312 > 1342 所以 dcab > acdb 也可以想象在英语词典里,acdb会比dcab出现得更早,所以acdb的字典序更小
  let stack = []
  let set = new Set() //记录是否为新字符
  for (let i = 0; i < s.length; i++) {
    if (stack.length === 0) {
      stack.push(s[i])
      set.add(s[i])
      continue
    }
    // 遇到一个新字符 如果比栈顶小 并且在新字符后面还有和栈顶一样的 就把栈顶的字符抛弃了
    while (
      !set.has(s[i]) &&
      s[i] < stack[stack.length - 1] && // JS字母比较大小会自动转换
      s.slice(i + 1).includes(stack[stack.length - 1])
    ) {
      let letter = stack.pop()
      set.delete(letter)
    }
    if (!set.has(s[i])) {
      stack.push(s[i])
      set.add(s[i])
    }
  }
  return stack.join('')
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

# 306. 累加数

var isAdditiveNumber = function (num) {
  const add = (l, r) => {
    let temp = parseInt(l) + parseInt(r)
    return temp.toString()
  }
  //只要确定前两个数,就可以判断了,那么就遍历所有的可能性
  if (num.length < 3) return false
  for (let i = 1; i < num.length; ++i) {
    if (num[0] == 0) {
      if (i > 1) break
    }
    for (let j = i + 1; j < num.length; ++j) {
      let l = num.slice(0, i)
      if (num[i] == 0) {
        if (j > i + 1) break
      }
      let r = num.slice(i, j)
      //从j开始验证
      let now = j
      while (now < num.length) {
        let temp = add(l, r)
        let k = 0
        for (k = 0; k < temp.length; ++k) {
          if (temp[k] != num[now + k]) break
        }
        if (k >= temp.length) {
          l = r
          r = temp
          now += temp.length
        } else break
      }
      if (now >= num.length) return true
    }
  }
  return false
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

# 368. 最大整除子集

var largestDivisibleSubset = function (nums) {
  const len = nums.length
  nums.sort((a, b) => a - b)

  // 第 1 步:动态规划找出最大子集的个数、最大子集中的最大整数
  const dp = new Array(len).fill(1)
  let maxSize = 1
  let maxVal = dp[0]
  for (let i = 1; i < len; i++) {
    for (let j = 0; j < i; j++) {
      // 题目中说「没有重复元素」很重要
      if (nums[i] % nums[j] === 0) {
        dp[i] = Math.max(dp[i], dp[j] + 1)
      }
    }

    if (dp[i] > maxSize) {
      maxSize = dp[i]
      maxVal = nums[i]
    }
  }

  // 第 2 步:倒推获得最大子集
  const res = []
  if (maxSize === 1) {
    res.push(nums[0])
    return res
  }

  for (let i = len - 1; i >= 0 && maxSize > 0; i--) {
    if (dp[i] === maxSize && maxVal % nums[i] === 0) {
      res.push(nums[i])
      maxVal = nums[i]
      maxSize--
    }
  }
  return res
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

# 376. 摆动序列

var wiggleMaxLength = function (nums) {
  const n = nums.length
  if (n < 2) return n
  const up = new Array(n).fill(0)
  const down = new Array(n).fill(0)
  up[0] = down[0] = 1
  for (let i = 1; i < n; i++) {
    if (nums[i] > nums[i - 1]) {
      up[i] = Math.max(up[i - 1], down[i - 1] + 1)
      down[i] = down[i - 1]
    } else if (nums[i] < nums[i - 1]) {
      up[i] = up[i - 1]
      down[i] = Math.max(up[i - 1] + 1, down[i - 1])
    } else {
      up[i] = up[i - 1]
      down[i] = down[i - 1]
    }
  }
  return Math.max(up[n - 1], down[n - 1])
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 319. 灯泡开关

/**
 * @param {number} n
 * @return {number}
 */
var bulbSwitch = function (n) {
  let i = 1
  let cnt = 0
  let ans = 0
  while (cnt < n) {
    cnt += i
    i += 2
    if (cnt <= n) ans++
  }
  return ans
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 377. 组合总和 Ⅳ

var combinationSum4 = function (nums, target) {
  const dp = new Array(target + 1).fill(0)
  dp[0] = 1
  for (let i = 1; i <= target; i++) {
    for (const num of nums) {
      if (num <= i) {
        dp[i] += dp[i - num]
      }
    }
  }
  return dp[target]
}
1
2
3
4
5
6
7
8
9
10
11
12

# 336. 回文对

/**
 * @param {string[]} words
 * @return {number[][]}
 */
var palindromePairs = function (words) {
  // 用Map存每个字符串的翻转
  const map = new Map()
  // 用Set存回文对的索引,以避免重复
  const set = new Set()
  // map的key为字符串的翻转,value为字符串的索引
  words.forEach((word, i) => {
    map.set(word.split('').reverse().join(''), i)
  })
  for (let i = 0; i < words.length; i++) {
    const word = words[i]
    for (let j = 0; j <= word.length; j++) {
      // 将字符串分为左右两个字串
      const left = word.slice(0, j),
        right = word.slice(j)
      // 如果左字符串是回文的
      if (isPalindrom(left)) {
        // 如果map里存有右字符串且索引不是i(题目写着要不同的索引对)
        if (map.has(right) && map.get(right) !== i) {
          // 满足前面条件的字符串为  右字符串的翻转 + 本身是回文的左字符串 + 右字符串
          // 这是一个回文字符串,其索引对为[map.get(right),i]
          // 因为数组没办法去重,所以用字符串表示并存入set
          const temp = `${map.get(right)},${i}`
          set.add(temp)
        }
      }
      // 如果右字符串是回文的
      if (isPalindrom(right)) {
        // 如果map里存有左字符串且索引不是i(题目写着要不同的索引对)
        if (map.has(left) && map.get(left) !== i) {
          // 满足前面条件的字符串为  左字符串 + 本身是回文的右字符串 + 左字符串的翻转
          const temp = `${i},${map.get(left)}`
          set.add(temp)
        }
      }
    }
  }
  // 将set中表示索引对的字符串转化为数组存到ans里
  const ans = [...set].map((v) => v.split(','))
  return ans
}
// 判断是否是回文字符串
function isPalindrom(str) {
  let i = 0,
    j = str.length - 1
  while (i < j) {
    if (str[i] !== str[j]) {
      return false
    }
    i++
    j--
  }
  return true
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58

# 386. 字典序排数

var lexicalOrder = function (n) {
  const ret = []
  let number = 1
  for (let i = 0; i < n; i++) {
    ret.push(number)
    if (number * 10 <= n) {
      number *= 10
    } else {
      while (number % 10 === 9 || number + 1 > n) {
        number = Math.floor(number / 10)
      }
      number++
    }
  }
  return ret
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 390. 消除游戏

/**
 * @param {number} n
 * @return {number}
 */
var lastRemaining = function (n) {
  let a1 = 1
  let k = 0,
    cnt = n,
    step = 1
  while (cnt > 1) {
    if (k % 2 === 0) {
      // 正向
      a1 = a1 + step
    } else {
      // 反向
      a1 = cnt % 2 === 0 ? a1 : a1 + step
    }
    k++
    cnt = cnt >> 1
    step = step << 1
  }
  return a1
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 400. 第 N 位数字

var findNthDigit = function (n) {
  let d = 1
  let count = 9
  while (n > d * count) {
    n -= d * count
    d++
    count *= 10
  }
  const index = n - 1
  const start = Math.floor(Math.pow(10, d - 1))
  const num = start + Math.floor(index / d)
  const digitIndex = index % d
  const digit =
    Math.floor(num / Math.floor(Math.pow(10, d - digitIndex - 1))) % 10
  return digit
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 410. 分割数组的最大值

/**
 * @param {number[]} nums
 * @param {number} m
 * @return {number}
 */
var splitArray = function (nums, m) {
  let len = nums.length,
    sumList = Array(len + 1).fill(0),
    dp = Array.from({ length: len + 1 }, () =>
      Array(m + 1).fill(Number.MAX_VALUE)
    )

  // 逐位增加,反面后面根据区间求区间和
  for (let i = 0; i < len; i++) {
    sumList[i + 1] = sumList[i] + nums[i]
  }

  // 默认值
  dp[0][0] = 0

  for (let i = 1; i <= len; i++) {
    for (let j = 1; j <= Math.min(m, i); j++) {
      // 前i个数分成j段
      for (let x = j - 1; x < i; x++) {
        // x最后一段的起点
        // perv本轮分割完成 分段中最大的和
        let prev = Math.max(dp[x][j - 1], sumList[i] - sumList[x])
        // 该分割情况下最大分段和的最小值
        dp[i][j] = Math.min(prev, dp[i][j])
      }
    }
  }

  return dp[len][m]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

# 407. 接雨水 II

/**
 * @param {number[][]} heightMap
 * @return {number}
 */
var trapRainWater = function (heightMap) {
  let m = heightMap.length,
    n = heightMap[0].length

  if (m <= 2 || n <= 2) return 0

  // mask表示已经遍历过的位置, smallHeap表示遍历到的但还未扩展探索其周围的元素
  // smallHeap是一个小堆,其中每个元素都是一个数组,即[元素值, i, j], rainSum表示统计出的雨水。
  // 这里还是广度优先遍历(BFS),smallHeap相当于传统BFS算法中的队列。
  let mask = Array(m),
    smallHeap = [],
    rainSum = 0,
    direct = [
      [-1, 0],
      [1, 0],
      [0, -1],
      [0, 1]
    ]
  for (let i = 0; i < m; i++) {
    mask[i] = Array(n).fill(0)
  }

  // 由于能积累雨水的地势肯定比周围的低,因此需要将heightMap的四条边push进小堆smallHeap中。
  for (let i = 0; i < n; i++) {
    pushSmallHeap(smallHeap, [heightMap[0][i], 0, i])
    pushSmallHeap(smallHeap, [heightMap[m - 1][i], m - 1, i])
    mask[0][i] = 1
    mask[m - 1][i] = 1
  }
  for (let j = 1; j < m - 1; j++) {
    pushSmallHeap(smallHeap, [heightMap[j][0], j, 0])
    pushSmallHeap(smallHeap, [heightMap[j][n - 1], j, n - 1])
    mask[j][0] = 1
    mask[j][n - 1] = 1
  }

  // 广度优先搜索。由于smallHeap是小堆,它是当前已探索但周围未开始探索的位置中最低的地方,
  // 因此能够接住雨水的地方一定在它的周围
  while (smallHeap.length > 0) {
    let node = popSmallHeap(smallHeap)

    for (let i = 0; i < direct.length; i++) {
      let nx = node[1] + direct[i][0]
      let ny = node[2] + direct[i][1]

      if (nx >= 0 && nx < m && ny >= 0 && ny < n && mask[nx][ny] == 0) {
        if (heightMap[nx][ny] < node[0]) {
          rainSum += node[0] - heightMap[nx][ny]
          pushSmallHeap(smallHeap, [node[0], nx, ny])
        } else {
          pushSmallHeap(smallHeap, [heightMap[nx][ny], nx, ny])
        }
        mask[nx][ny] = 1
      }
    }
  }

  return rainSum

  // 向小堆中插入元素, heap中的每个元素都是一个数组,即[元素值, i, j]。
  function pushSmallHeap(heap, item) {
    heap.push(item)
    // t表示当前元素下标,p表示t元素的父元素下标
    let t = heap.length - 1,
      p,
      tempt
    while (t > 0) {
      p = (t - 1 - ((t - 1) % 2)) / 2
      if (heap[p][0] > heap[t][0]) {
        tempt = heap[p]
        heap[p] = heap[t]
        heap[t] = tempt
        t = p
      } else {
        break
      }
    }
  }
  // 从小堆中取出堆顶元素
  function popSmallHeap(heap) {
    if (heap.length === 0) return null

    let rn = heap[0]

    heap[0] = heap[heap.length - 1]
    heap.length--

    // t表示当前元素下标,lc表示当前元素左子元素下标,c表示当前元素值最小的子元素的下标
    let t = 0,
      lc = 1,
      c,
      tempt
    while (lc < heap.length) {
      c = lc
      // 寻找当前元素值最小的子元素的下标
      if (lc + 1 < heap.length && heap[lc][0] > heap[lc + 1][0]) {
        c = lc + 1
      }

      if (heap[t][0] > heap[c][0]) {
        tempt = heap[t]
        heap[t] = heap[c]
        heap[c] = tempt
        t = c
        lc = 2 * t + 1
      } else {
        break
      }
    }

    return rn
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117

# 417. 太平洋大西洋水流问题

const dirs = [
  [-1, 0],
  [1, 0],
  [0, -1],
  [0, 1]
]
var pacificAtlantic = function (heights) {
  m = heights.length
  n = heights[0].length
  const pacific = new Array(m).fill(0).map(() => new Array(n).fill(0))
  const atlantic = new Array(m).fill(0).map(() => new Array(n).fill(0))

  const dfs = (row, col, ocean) => {
    if (ocean[row][col]) {
      return
    }
    ocean[row][col] = true
    for (const dir of dirs) {
      const newRow = row + dir[0],
        newCol = col + dir[1]
      if (
        newRow >= 0 &&
        newRow < m &&
        newCol >= 0 &&
        newCol < n &&
        heights[newRow][newCol] >= heights[row][col]
      ) {
        dfs(newRow, newCol, ocean)
      }
    }
  }

  for (let i = 0; i < m; i++) {
    dfs(i, 0, pacific)
  }
  for (let j = 1; j < n; j++) {
    dfs(0, j, pacific)
  }
  for (let i = 0; i < m; i++) {
    dfs(i, n - 1, atlantic)
  }
  for (let j = 0; j < n - 1; j++) {
    dfs(m - 1, j, atlantic)
  }
  const result = []
  for (let i = 0; i < m; i++) {
    for (let j = 0; j < n; j++) {
      if (pacific[i][j] && atlantic[i][j]) {
        const cell = []
        cell.push(i)
        cell.push(j)
        result.push(cell)
      }
    }
  }
  return result
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57

# 424. 替换后的最长重复字符

var characterReplacement = function (s, k) {
  // 用来记录滑动窗口内字母出现的次数
  let map = new Array(26).fill(0)
  let left = 0,
    right = 0,
    max = 0
  while (right < s.length) {
    // 一个字母进入窗口,在map中将次数加一,并且更新最大字母重复次数
    let char = s[right]
    let index = char.charCodeAt() - 'A'.charCodeAt()
    map[index]++
    max = Math.max(max, map[index])
    // 判断当前窗口的字符串是否符合规则,
    // 如果当前窗口长度减去最大字母出现次数 的值 大于最大替换次数 K
    // 则不符合规则 所以整个窗口往左移动,left++ 且要将 map中记录的值减去
    if (right - left + 1 - max > k) {
      map[s[left].charCodeAt() - 'A'.charCodeAt()]--
      left++
    }
    right++
  }
  return right - left
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 450. 删除二叉搜索树中的节点

var deleteNode = function (root, key) {
  if (!root) {
    return null
  }
  if (root.val > key) {
    root.left = deleteNode(root.left, key)
    return root
  }
  if (root.val < key) {
    root.right = deleteNode(root.right, key)
    return root
  }
  if (root.val === key) {
    if (!root.left && !root.right) {
      return null
    }
    if (!root.right) {
      return root.left
    }
    if (!root.left) {
      return root.right
    }
    let successor = root.right
    while (successor.left) {
      successor = successor.left
    }
    root.right = deleteNode(root.right, successor.val)
    successor.right = root.right
    successor.left = root.left
    return successor
  }
  return root
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

# 449. 序列化和反序列化二叉搜索树

var serialize = function (root) {
  const list = []

  const postOrder = (root, list) => {
    if (!root) {
      return
    }
    postOrder(root.left, list)
    postOrder(root.right, list)
    list.push(root.val)
  }

  postOrder(root, list)
  const str = list.join(',')
  return str
}

var deserialize = function (data) {
  if (data.length === 0) {
    return null
  }
  let arr = data.split(',')
  const length = arr.length
  const stack = []
  for (let i = 0; i < length; i++) {
    stack.push(parseInt(arr[i]))
  }

  const construct = (lower, upper, stack) => {
    if (
      stack.length === 0 ||
      stack[stack.length - 1] < lower ||
      stack[stack.length - 1] > upper
    ) {
      return null
    }
    const val = stack.pop()
    const root = new TreeNode(val)
    root.right = construct(val, upper, stack)
    root.left = construct(lower, val, stack)
    return root
  }

  return construct(-Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER, stack)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

# 453. 最小操作次数使数组元素相等

var minMoves = function (nums) {
  const minNum = Math.min(...nums)
  let res = 0
  for (const num of nums) {
    res += num - minNum
  }
  return res
}
1
2
3
4
5
6
7
8

# 516. 最长回文子序列

/**
 * @param {string} s
 * @return {number}
 */
var longestPalindromeSubseq = function (s) {
  const n = s.length
  // 用 dp[i][j] 表示字符串 s 的下标范围 [i,j] 内的最长回文子序列的长度
  const dp = new Array(n).fill(0).map(() => new Array(n).fill(0))
  for (let i = n - 1; i >= 0; i--) {
    dp[i][i] = 1
    const c1 = s[i]
    for (let j = i + 1; j < n; j++) {
      const c2 = s[j]
      // 如果头尾字符相同
      if (c1 === c2) {
        dp[i][j] = dp[i + 1][j - 1] + 2
      } else {
        // 不同则取最长
        dp[i][j] = Math.max(dp[i + 1][j], dp[i][j - 1])
      }
    }
  }
  return dp[0][n - 1]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 542. 01 矩阵

/**
 * @param {number[][]} mat
 * @return {number[][]}
 */
var updateMatrix = function (mat) {
  let m = mat.length,
    n = mat[0].length
  // 目标结果
  let dp = new Array(m)
    .fill(0)
    .map(() => new Array(n).fill(Number.MAX_SAFE_INTEGER))
  // 如果 (i, j) 的元素为 0,那么距离为 0
  for (let i = 0; i < m; i++)
    for (let j = 0; j < n; j++) if (mat[i][j] == 0) dp[i][j] = 0

  // 只有 水平向右移动 和 竖直向下移动,递归的顺序是从左到右,从上到下
  for (let i = 0; i < m; i++) {
    for (let j = 0; j < n; j++) {
      // 水平向左 是由同行左侧的元素递推算出来的
      if (i - 1 >= 0) dp[i][j] = Math.min(dp[i][j], dp[i - 1][j] + 1)
      // 垂直向下,是由同列上行的元素递推算出来的
      if (j - 1 >= 0) dp[i][j] = Math.min(dp[i][j], dp[i][j - 1] + 1)
    }
  }
  // 只有 水平向左移动 和 竖直向上移动,递归的顺序是从右到左,从下到上
  for (let i = m - 1; i >= 0; i--) {
    for (let j = n - 1; j >= 0; j--) {
      // 水平向右 是由同行右侧的元素递推算出来的
      if (i + 1 < m) dp[i][j] = Math.min(dp[i][j], dp[i + 1][j] + 1)
      // 垂直向下,是由同列下行的元素递推算出来的
      if (j + 1 < n) dp[i][j] = Math.min(dp[i][j], dp[i][j + 1] + 1)
    }
  }
  return dp
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

# 696. 计数二进制子串

var countBinarySubstrings = function (s) {
  const counts = []
  let ptr = 0,
    n = s.length
  while (ptr < n) {
    const c = s.charAt(ptr)
    let count = 0
    while (ptr < n && s.charAt(ptr) === c) {
      ++ptr
      ++count
    }
    counts.push(count)
  }
  let ans = 0
  for (let i = 1; i < counts.length; ++i) {
    ans += Math.min(counts[i], counts[i - 1])
  }
  return ans
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 698. 划分为 k 个相等的子集

var canPartitionKSubsets = function (nums, k) {
  var sum = 0
  for (var i = nums.length - 1; i >= 0; i--) {
    sum += nums[i]
  }
  nums.sort((a, b) => b - a)
  let sums = new Array(k).fill(0)
  return backtrace(nums, sums, 0, k, sum / k)
}
function backtrace(nums, sums, i, k, average) {
  if (i === nums.length) return true
  for (let j = 0; j < k; j++) {
    if (sums[j] < average && nums[i] + sums[j] <= average) {
      // 尝试求解
      sums[j] += nums[i]
      // 求解成功 返回true
      if (backtrace(nums, sums, i + 1, k, average)) {
        return true
      }
      // 求解失败 回溯
      sums[j] -= nums[i]
    }
  }
  return false
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# 763. 划分字母区间

var partitionLabels = function (s) {
  const last = new Array(26)
  const length = s.length
  const codePointA = 'a'.codePointAt(0)
  for (let i = 0; i < length; i++) {
    last[s.codePointAt(i) - codePointA] = i
  }
  const partition = []
  let start = 0,
    end = 0
  for (let i = 0; i < length; i++) {
    end = Math.max(end, last[s.codePointAt(i) - codePointA])
    if (i == end) {
      partition.push(end - start + 1)
      start = end + 1
    }
  }
  return partition
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 797. 所有可能的路径

/**
 * @param {number[][]} graph
 * @return {number[][]}
 */
var allPathsSourceTarget = function (graph) {
  let len = graph.length
  let ans = []
  // 深度优先搜索
  const dfs = (idx, temp) => {
    // 如果搜索到最后一个节点 记录结果后return
    if (idx === len - 1) {
      ans.push(temp)
      return
    }
    for (let i = 0; i < graph[idx].length; i++) {
      // 将当前值加入路径
      dfs(graph[idx][i], [...temp, graph[idx][i]])
    }
  }
  // 从0开始 初始值为[0]
  dfs(0, [0])
  return ans
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 877. 石子游戏

/**
 * @param {number[]} piles
 * @return {boolean}
 */
var stoneGame = function (piles) {
  const length = piles.length
  const dp = new Array(length).fill(0).map(() => new Array(length).fill(0))
  for (let i = 0; i < length; i++) {
    dp[i][i] = piles[i]
  }
  for (let i = length - 2; i >= 0; i--) {
    for (let j = i + 1; j < length; j++) {
      dp[i][j] = Math.max(piles[i] - dp[i + 1][j], piles[j] - dp[i][j - 1])
    }
  }
  return dp[0][length - 1] > 0
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 890. 查找和替换模式

var findAndReplacePattern = function (words, pattern) {
  const ans = []
  for (const word of words) {
    if (match(word, pattern) && match(pattern, word)) {
      ans.push(word)
    }
  }
  return ans
}

const match = (word, pattern) => {
  const map = new Map()
  for (let i = 0; i < word.length; ++i) {
    const x = word[i],
      y = pattern[i]
    if (!map.has(x)) {
      map.set(x, y)
    } else if (map.get(x) !== y) {
      // word 中的同一字母必须映射到 pattern 中的同一字母上
      return false
    }
  }
  return true
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 904. 水果成篮

var totalFruit = function (fruits) {
  let left = 0
  // 记录出现的水果的频次
  let map = new Map()
  let ans = 0
  for (let right = 0; right < fruits.length; right++) {
    // 频次+1
    map.set(
      fruits[right],
      map.get(fruits[right]) ? map.get(fruits[right]) + 1 : 1
    )
    while (map.size > 2) {
      map.set(fruits[left], map.get(fruits[left]) - 1)
      // 如果频次为0 就删除
      if (map.get(fruits[left]) === 0) {
        map.delete(fruits[left])
      }
      left++
    }
    ans = Math.max(ans, right - left + 1)
  }
  return ans
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 863 二叉树中所有距离为 K 的结点

/**
 * Definition for a binary tree node.
 * function TreeNode(val) {
 *     this.val = val;
 *     this.left = this.right = null;
 * }
 */
/**
 * @param {TreeNode} root
 * @param {TreeNode} target
 * @param {number} k
 * @return {number[]}
 */
var distanceK = function (root, target, k) {
  let paths = []
  handle(root, paths)
  const ans = new Set() // 利用set去重
  let targetVal = target.val
  console.log(paths)
  // 寻找path列表的target
  for (let i = 0; i < paths.length; i++) {
    let idx = paths[i].findIndex((i) => targetVal === i)
    if (idx > -1) {
      // 判断距离target的k距离的模板 如果符合就加入ans
      if (idx - k > -1) ans.add(paths[i][idx - k])
      if (idx + k < paths[i].length) ans.add(paths[i][idx + k])
    }
  }
  return [...ans]
}

var handle = function (node, paths) {
  // 寻找每棵子树的路径
  searchPaths(node, paths)
  if (node.left) handle(node.left, paths)
  if (node.right) handle(node.right, paths)
}

var searchPaths = function (root, paths) {
  // 先搜索出左右路径 然后拼接起来
  let left = []
  let right = []
  search(root.left, [], left)
  search(root.right, [], right)
  if (left.length === 0) left.push([])
  if (right.length === 0) right.push([])
  for (let i = 0; i < left.length; i++) {
    let l = [...left[i]].reverse()
    for (let j = 0; j < right.length; j++) {
      paths.push([...l, root.val, ...right[j]])
    }
  }
}

// 找出单侧树的路径
var search = function (node, temp, paths) {
  if (!node) return
  if (!node.left && !node.right) {
    paths.push([...temp, node.val])
    return
  }
  temp.push(node.val)
  search(node.left, [...temp], paths)
  search(node.right, [...temp], paths)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65

# 979. 在二叉树中分配硬币

# 1025. 除数博弈

如果 N 是奇数,因为奇数的所有因数都是奇数,因此 N 进行一次 N-x 的操作结果一定是偶数,所以如果 a 拿到了一个奇数,那么轮到 b 的时候,b 拿到的肯定是偶数,这个时候 b 只要进行 -1, 还给 a 一个奇数,那么这样子 b 就会一直拿到偶数,到最后 b 一定会拿到最小偶数 2,a 就输了。

所以如果游戏开始时 Alice 拿到 N 为奇数,那么她必输,也就是 false。如果拿到 N 为偶数,她只用 -1,让 bob 拿到奇数,最后 bob 必输,结果就是 true。

var divisorGame = function (N) {
  return N % 2 === 0
}
1
2
3

# 1026. 节点与其祖先之间的最大差值

# 1071. 字符串的最大公因子

需要知道一个性质:如果 str1 和 str2 拼接后等于 str2 和 str1 拼接起来的字符串(注意拼接顺序不同),那么一定存在符合条件的字符串 X。

题解 (opens new window)

var gcdOfStrings = function (str1, str2) {
  const gcd = (a, b) => {
    if (b === 0) return a
    return gcd(b, a % b)
  }

  if (str1 + str2 !== str2 + str1) return ''

  return str1.substring(0, gcd(str1.length, str2.length))
}
1
2
3
4
5
6
7
8
9
10

# 1143. 最长公共子序列

var longestCommonSubsequence = function (text1, text2) {
  const m = text1.length,
    n = text2.length
  const dp = new Array(m + 1).fill(0).map(() => new Array(n + 1).fill(0))
  for (let i = 1; i <= m; i++) {
    const c1 = text1[i - 1]
    for (let j = 1; j <= n; j++) {
      const c2 = text2[j - 1]
      if (c1 === c2) {
        dp[i][j] = dp[i - 1][j - 1] + 1
      } else {
        dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1])
      }
    }
  }
  return dp[m][n]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 1414. 和为 K 的最少斐波那契数字数目

var findMinFibonacciNumbers = function (k) {
  const f = [1]
  let a = 1,
    b = 1
  while (a + b <= k) {
    let c = a + b
    f.push(c)
    a = b
    b = c
  }
  let ans = 0
  for (let i = f.length - 1; i >= 0 && k > 0; i--) {
    const num = f[i]
    if (k >= num) {
      k -= num
      ans++
    }
  }
  return ans
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 1641. 统计字典序元音字符串的数目

/**
 * @param {number} n
 * @return {number}
 */
var countVowelStrings = function (n) {
  a = e = i = o = u = 1
  for (let k = 1; k < n; k++) {
    a = a + e + i + o + u
    e = e + i + o + u
    i = i + o + u
    o = o + u
  }
  return a + e + i + o + u
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 1641. 统计字典序元音字符串的数目

/**
 * @param {number} n
 * @return {number}
 */
var countVowelStrings = function (n) {
  a = e = i = o = u = 1
  for (let k = 1; k < n; k++) {
    a = a + e + i + o + u
    e = e + i + o + u
    i = i + o + u
    o = o + u
  }
  return a + e + i + o + u
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 1605. 给定行和列的和求可行矩阵

/**
 * @param {number[]} rowSum
 * @param {number[]} colSum
 * @return {number[][]}
 */
var restoreMatrix = function (rowSum, colSum) {
  const [m, n] = [rowSum.length, colSum.length]
  const matrix = Array(m)
    .fill(0)
    .map(() => Array(n).fill(0))
  for (let i = 0; i < m; i++) {
    for (let j = 0; j < n; j++) {
      matrix[i][j] = Math.min(rowSum[i], colSum[j])
      rowSum[i] -= matrix[i][j]
      colSum[j] -= matrix[i][j]
    }
  }
  return matrix
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 1734. 解码异或后的排列

var decode = function (encoded) {
  const n = encoded.length + 1
  let total = 0
  for (let i = 1; i <= n; i++) {
    total ^= i
  }
  let odd = 0
  for (let i = 1; i < n - 1; i += 2) {
    odd ^= encoded[i]
  }
  const perm = new Array(n).fill(0)
  perm[0] = total ^ odd
  for (let i = 0; i < n - 1; i++) {
    perm[i + 1] = perm[i] ^ encoded[i]
  }
  return perm
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
上次更新: 2023/04/05, 09:41:10
最近更新
01
JavaScript-test
07-20
02
二维码的原理
07-20
03
利用ChatGPT优化代码
07-20
更多文章>
Theme by Vdoing | Copyright © 2021-2023 XingYun | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式