├── .eslintrc.js ├── .github └── workflows │ └── main.yml ├── .gitignore ├── .prettierrc.js ├── .vscode └── launch.json ├── LICENSE ├── README.md ├── docs ├── .vuepress │ ├── config.ts │ └── public │ │ ├── avatar.jpg │ │ └── favicon.ico ├── README.md └── docs │ ├── list │ ├── array │ │ ├── 1.two-sum.md │ │ ├── 1137.n-th-tribonacci-number.md │ │ ├── 15.3sum.md │ │ ├── 18.4sum.md │ │ ├── 189.rotate-array.md │ │ └── 48.rotate-image.md │ ├── backtracking │ │ ├── 1038.binarySearchTreeToGreaterSumTree.md │ │ ├── 17.letter-combinations-of-a-phone-number.md │ │ ├── 22.generateParentheses.md │ │ ├── 37.sudokuSolver.md │ │ ├── 39.combination-sum.md │ │ ├── 39.combinationSum.md │ │ ├── 40.combination-sum-ii.md │ │ ├── 46.permutations.md │ │ ├── 51.nQueens.md │ │ ├── 52.nQueensIi.md │ │ ├── 77.combinations.md │ │ ├── 78.subsets.md │ │ ├── 79.word-search.md │ │ └── 90.subsets-ii.md │ ├── binary-search │ │ ├── 153.find-minimum-in-rotated-sorted-array.md │ │ ├── 162.findPeakElement.md │ │ ├── 230.kth-smallest-element-in-a-bst.md │ │ ├── 278.first-bad-version..md │ │ ├── 33.searchInRotatedSortedArray.md │ │ ├── 34.findFirstAndLastPositionOfElementInSortedArray.md │ │ ├── 35.search-insert-position.md │ │ ├── 367.valid-perfect-square.md │ │ ├── 4.medianOfTwoSortedArrays.md │ │ ├── 69.sqrtx.md │ │ ├── 704.binary-search.md │ │ └── 74.search-a-2d-matrix.md │ ├── bit-manipulation │ │ ├── 136.singleNumber.md │ │ └── 169.majorityElement.md │ ├── breadth-first-search │ │ ├── 1091.shortest-path-in-binary-matrix.md │ │ ├── 111.minimumDepthOfBinaryTree.md │ │ ├── 1162.as-far-from-land-as-possible.md │ │ ├── 117.populating-next-right-pointers-in-each-node-ii.md │ │ ├── 127.word-ladder.md │ │ ├── 752.open-the-lock.md │ │ └── 773.sliding-puzzle.md │ ├── depth-first-search │ │ ├── 1020.number-of-enclaves.md │ │ ├── 1254.number-of-closed-islands.md │ │ ├── 130.surrounded-regions.md │ │ ├── 1905.count-sub-islands.md │ │ ├── 200.number-of-islands.md │ │ ├── 337.houseRobberIii.md │ │ ├── 547.number-of-provinces.md │ │ └── 695.max-area-of-island.md │ ├── design │ │ └── 146.lru-cache.md │ ├── divide-and-conquer │ │ └── 53.maximumSubarray.md │ ├── dynamic-programming │ │ ├── 10.regularExpressionMatching.md │ │ ├── 1143.longestCommonSubsequence.md │ │ ├── 120.triangle.md │ │ ├── 121.bestTimeToBuyAndSellStock.md │ │ ├── 123.bestTimeToBuyAndSellStockIii.md │ │ ├── 139.wordBreak.md │ │ ├── 1449.form-largest-integer-with-digits-that-add-up-to-target.md │ │ ├── 198.houseRobber.md │ │ ├── 213.houseRobberIi.md │ │ ├── 221.maximal-square.md │ │ ├── 279.perfectSquares.md │ │ ├── 300.longestIncreasingSubsequence.md │ │ ├── 309.bestTimeToBuyAndSellStockWithCooldown.md │ │ ├── 32.longestValidParentheses.md │ │ ├── 322.coinChange.md │ │ ├── 343.integer-break.md │ │ ├── 354.russian-doll-envelopes.md │ │ ├── 392.is-subsequence.md │ │ ├── 413.arithmetic-slices.md │ │ ├── 416.partitionEqualSubsetSum.md │ │ ├── 44.wildcardMatching.md │ │ ├── 474.ones-and-zeroes.md │ │ ├── 494.target-sum.md │ │ ├── 5.longestPalindromicSubstring.md │ │ ├── 516.longestPalindromicSubsequence.md │ │ ├── 518.coinChange2.md │ │ ├── 53.maximumSubarray.md │ │ ├── 583.delete-operation-for-two-strings.md │ │ ├── 62.uniquePaths.md │ │ ├── 63.uniquePathsIi.md │ │ ├── 64.minimum-path-sum.md │ │ ├── 673.number-of-longest-increasing-subsequence.md │ │ ├── 674.longest-continuous-increasing-subsequence.md │ │ ├── 70.climbingStairs.md │ │ ├── 712.minimum-ascii-delete-sum-for-two-strings.md │ │ ├── 718.maximum-length-of-repeated-subarray.md │ │ ├── 72.editDistance.md │ │ ├── 740.delete-and-earn.md │ │ ├── 836.rectangleOverlap.md │ │ ├── 85.maximalRectangle.md │ │ ├── 877.stone-game.md │ │ ├── 879.profitable-scheme.md │ │ ├── 91.decode-ways.md │ │ ├── 91.decodeWays.md │ │ ├── 918.maximum-sum-circular-subarray.md │ │ ├── 96.uniqueBinarySearchTrees.md │ │ └── 97.interleavingString.md │ ├── graph │ │ ├── 743.network-delay-time.md │ │ └── 797.all-paths-from-source-to-target.md │ ├── greedy │ │ ├── 122.bestTimeToBuyAndSellStockIi.md │ │ ├── 406.queueReconstructionByHeight.md │ │ ├── 435.non-overlapping-intervals.md │ │ ├── 45.jumpGameIi.md │ │ └── 55.jumpGame.md │ ├── hash-table │ │ ├── 136.singleNumber.md │ │ ├── 219.containsDuplicateIi.md │ │ ├── 299.bullsAndCows.md │ │ ├── 3.longestSubstringWithoutRepeatingCharacters.md │ │ ├── 349.intersectionOfTwoArrays.md │ │ ├── 36.valid-sudoku.md │ │ ├── 463.islandPerimeter.md │ │ └── 575.distributeCandies.md │ ├── heap │ │ └── 887.superEggDrop.md │ ├── linked-list │ │ ├── 2.addTwoNumbers.md │ │ ├── 203.removeLinkedListElements.md │ │ ├── 206.reverseLinkedList.md │ │ ├── 21.mergeTwoSortedLists.md │ │ ├── 237.deleteNodeInALinkedList.md │ │ ├── 24.swapNodesInPairs.md │ │ ├── 82.remove-duplicates-from-sorted-list-ii.md │ │ └── 92.reverseLinkedListIi.md │ ├── math │ │ ├── 13.romanToInteger.md │ │ ├── 172.factorialTrailingZeroes.md │ │ ├── 2222.numberOfWaysToSelectBuildings.md │ │ ├── 319.bulbSwitcher.md │ │ ├── 633.sumOfSquareNumbers.md │ │ ├── 67.addBinary.md │ │ ├── 7.reverseInteger.md │ │ ├── 856.scoreOfParentheses.md │ │ └── 9.palindromeNumber.md │ ├── sliding-window │ │ ├── 209.minimum-size-subarray-sum.md │ │ ├── 239.sliding-window-maximum.md │ │ ├── 3.longestSubstringWithoutRepeatingCharacters.md │ │ ├── 438.findAllAnagramsInAString.md │ │ ├── 567.permutationInString.md │ │ ├── 713.subarray-product-less-than-k.md │ │ └── 76.minimumWindowSubstring.md │ ├── sort │ │ ├── 1288.删除被覆盖区间.md │ │ ├── 56.合并区间.md │ │ ├── 57.插入区间.md │ │ └── 986.区间列表的交集.md │ ├── stack │ │ ├── 1081.smallestSubsequenceOfDistinctCharacters.md │ │ ├── 20.validParentheses.md │ │ ├── 225.implementStackUsingQueues.md │ │ ├── 232.implementQueueUsingStacks.md │ │ ├── 316.removeDuplicateLetters.md │ │ ├── 496.next-greater-element-i.md │ │ ├── 503.next-greater-element-ii.md │ │ └── 84.largest-rectangle-in-histogram.md │ ├── string │ │ ├── 14.longestCommonPrefix.md │ │ ├── 28.implementStrStr.md │ │ ├── 415.addStrings.md │ │ ├── 557.reverse-words-in-a-string-iii.md │ │ └── 6.zigZagConversion.md │ ├── tree │ │ ├── 100.sameTree.md │ │ ├── 101.symmetricTree.md │ │ ├── 104.maximumDepthOfBinaryTree.md │ │ ├── 110.balancedBinaryTree.md │ │ ├── 124.binaryTreeMaximumPathSum.md │ │ ├── 144.binaryTreePreorderTraversal.md │ │ ├── 145.binaryTreePostorderTraversal.md │ │ ├── 222.count-complete-tree-nodes.md │ │ ├── 226.invertBinaryTree.md │ │ ├── 230.kthSmallestElementInABst.md │ │ ├── 297.serializeAndDeserializeBinaryTree.md │ │ ├── 437.pathSumIii.md │ │ ├── 509.fibonacci-number.md │ │ ├── 538.convertBstToGreaterTree.md │ │ ├── 572.subtree-of-another-tree.md │ │ ├── 654.maximum-binary-tree.md │ │ ├── 700.search-in-a-binary-search-tree.md │ │ ├── 94.binaryTreeInorderTraversal.md │ │ ├── 95.uniqueBinarySearchTreesIi.md │ │ ├── 96.uniqueBinarySearchTrees.md │ │ ├── 98.validateBinarySearchTree.md │ │ └── 99.recoverBinarySearchTree.md │ ├── trie │ │ └── 746.min-cost-climbing-stairs.md │ └── two-pointers │ │ ├── 11.container-with-most-water.md │ │ ├── 125.validPalindrome.md │ │ ├── 141.linkedListCycle.md │ │ ├── 142.linkedListCycleIi.md │ │ ├── 167.twoSumIiInputArrayIsSorted.md │ │ ├── 19.removeNthNodeFromEndOfList.md │ │ ├── 26.removeDuplicatesFromSortedArray.md │ │ ├── 28.implementStrStr.md │ │ ├── 283.moveZeroes.md │ │ ├── 344.reverseString.md │ │ ├── 42.trappingRainWater.md │ │ ├── 75.sortColors.md │ │ ├── 844.backspace-string-compare.md │ │ ├── 88.mergeSortedArray.md │ │ └── 977.squares-of-a-sorted-array.md │ └── topic │ ├── 0.introduction.md │ ├── 1.recursive.md │ ├── 10.dynamic-programming-normal.md │ ├── 11.dynamic-programming-backpack.md │ ├── 12. dynamic-programming-subsequence.md │ ├── 13.graph.md │ ├── 14.monotonic-stack.md │ ├── 15.slide-window.md │ ├── 16.partial-sum.md │ ├── 2.tree.md │ ├── 3.binary-search-tree.md │ ├── 4.sort.md │ ├── 5.two-pointers.md │ ├── 6.binary-search.md │ ├── 7.backtrack.md │ ├── 8.depth-first-search.md │ └── 9.breadth-first-search.md ├── package.json ├── pnpm-lock.yaml ├── src ├── Unknown │ ├── 1155.掷骰子等于目标和的方法数.ts │ ├── 1160.拼写单词.ts │ ├── 1218.最长定差子序列.ts │ ├── 1288.删除被覆盖区间.js │ ├── 1319.连通网络的操作次数.ts │ ├── 1456.定长子串中元音的最大数目.ts │ ├── 1541.平衡括号字符串的最少插入次数.ts │ ├── 1626.无矛盾的最佳球队.ts │ ├── 1774.最接近目标价格的甜点成本.ts │ ├── 2008.出租车的最大盈利.ts │ ├── 2222.选择建筑的方案数.ts │ ├── 3290.最高乘法得分.ts │ ├── 384.打乱数组.ts │ ├── 815.公交路线.ts │ ├── 841.钥匙和房间.ts │ └── 844.比较含退格的字符串.ts ├── array │ ├── 1.两数之和.js │ ├── 1.两数之和.ts │ ├── 1137.第N个泰波那契数.ts │ ├── 118.杨辉三角.ts │ ├── 119.杨辉三角Ii.ts │ ├── 128.最长连续序列.ts │ ├── 15.三数之和.js │ ├── 15.三数之和.ts │ ├── 18.四数之和.ts │ ├── 189.轮转数组.ts │ ├── 209.长度最小的子数组.ts │ ├── 27.移除元素.ts │ ├── 41.缺失的第一个正数.ts │ ├── 42.接雨水.ts │ ├── 48.旋转图像.ts │ ├── 485.最大连续1的个数.ts │ ├── 56.合并区间.ts │ ├── 560.和为K的子数组.ts │ ├── 57.插入区间.ts │ ├── 643.子数组最大平均数I.ts │ ├── 713.乘积小于K的子数组.ts │ └── 75.颜色分类.ts ├── backtracking │ ├── 1038.把二叉搜索树转换为累加树.js │ ├── 17.电话号码的字母组合.ts │ ├── 22.括号生成.js │ ├── 22.括号生成.ts │ ├── 37.解数独.js │ ├── 37.解数独.ts │ ├── 39.组合总和.js │ ├── 39.组合总和.ts │ ├── 40.组合总和Ii.ts │ ├── 46.全排列.js │ ├── 46.全排列.ts │ ├── 47.全排列Ii.ts │ ├── 51.n皇后.js │ ├── 52.n皇后Ii.js │ ├── 77.组合.js │ ├── 77.组合.ts │ ├── 78.子集.js │ ├── 78.子集.ts │ ├── 79.单词搜索.ts │ └── 90.子集Ii.ts ├── binary-indexed-tree │ └── 307.区域和检索数组可修改.ts ├── binary-search │ ├── 153.寻找旋转排序数组中的最小值.ts │ ├── 162.寻找峰值.js │ ├── 162.寻找峰值.ts │ ├── 278.第一个错误的版本.ts │ ├── 33.搜索旋转排序数组.js │ ├── 33.搜索旋转排序数组.ts │ ├── 34.在排序数组中查找元素的第一个和最后一个位置.js │ ├── 34.在排序数组中查找元素的第一个和最后一个位置.ts │ ├── 35.搜索插入位置.ts │ ├── 367.有效的完全平方数.ts │ ├── 374.猜数字大小.ts │ ├── 4.寻找两个有序数组的中位数.js │ ├── 4.寻找两个正序数组的中位数.ts │ ├── 436.寻找右区间.ts │ ├── 69.x的平方根.ts │ ├── 704.二分查找.ts │ ├── 74.搜索二维矩阵.ts │ └── 912.排序数组.ts ├── bit-manipulation │ ├── 136.只出现一次的数字.js │ ├── 169.多数元素.js │ └── 201.数字范围按位与.ts ├── breadth-first-search │ ├── 102.二叉树的层序遍历.ts │ ├── 1091.二进制矩阵中的最短路径.ts │ ├── 111.二叉树的最小深度.js │ ├── 111.二叉树的最小深度.ts │ ├── 1162.地图分析.ts │ ├── 117.填充每个节点的下一个右侧节点指针Ii.ts │ ├── 127.单词接龙.ts │ ├── 515.在每个树行中找最大值.ts │ ├── 529.扫雷游戏.ts │ ├── 542.01矩阵.ts │ ├── 752.打开转盘锁.ts │ └── 773.滑动谜题.ts ├── depth-first-search │ ├── 1020.飞地的数量.ts │ ├── 112.路径总和.ts │ ├── 113.路径总和Ii.ts │ ├── 1254.统计封闭岛屿的数目.ts │ ├── 129.求根节点到叶节点数字之和.ts │ ├── 130.被围绕的区域.ts │ ├── 1905.统计子岛屿.ts │ ├── 200.岛屿数量.ts │ ├── 337.打家劫舍Iii.js │ ├── 337.打家劫舍Iii.ts │ ├── 547.省份数量.ts │ ├── 695.岛屿的最大面积.ts │ ├── 733.图像渲染.ts │ └── 904.水果成篮.ts ├── design │ └── 146.lru缓存.ts ├── divide-and-conquer │ ├── 215.数组中的第k个最大元素.ts │ ├── 4.寻找两个正序数组的中位数.ts │ └── 53.最大子序和.js ├── dynamic-programming │ ├── 10.正则表达式匹配.js │ ├── 10.正则表达式匹配.ts │ ├── 1081.不同字符的最小子序列.js │ ├── 1081.不同字符的最小子序列.ts │ ├── 1143.最长公共子序列.js │ ├── 1143.最长公共子序列.ts │ ├── 120.三角形最小路径和.js │ ├── 121.买卖股票的最佳时机.js │ ├── 121.买卖股票的最佳时机.ts │ ├── 123.买卖股票的最佳时机Iii.js │ ├── 123.买卖股票的最佳时机Iii.ts │ ├── 132.分割回文串Ii.ts │ ├── 139.单词拆分.js │ ├── 139.单词拆分.ts │ ├── 1449.数位成本和为目标值的最大数字.ts │ ├── 152.乘积最大子数组.ts │ ├── 174.地下城游戏.ts │ ├── 188.买卖股票的最佳时机Iv.ts │ ├── 198.打家劫舍.js │ ├── 198.打家劫舍.ts │ ├── 213.打家劫舍Ii.js │ ├── 213.打家劫舍Ii.ts │ ├── 221.最大正方形.ts │ ├── 279.完全平方数.js │ ├── 279.完全平方数.ts │ ├── 300.最长上升子序列.js │ ├── 300.最长递增子序列.ts │ ├── 303.区域和检索数组不可变.ts │ ├── 304.二维区域和检索矩阵不可变.ts │ ├── 309.最佳买卖股票时机含冷冻期.js │ ├── 32.最长有效括号.ts │ ├── 322.零钱兑换.js │ ├── 322.零钱兑换.ts │ ├── 343.整数拆分.ts │ ├── 354.俄罗斯套娃信封问题.ts │ ├── 357.统计各位数字都不同的数字个数.ts │ ├── 375.猜数字大小Ii.ts │ ├── 377.组合总和Ⅳ.ts │ ├── 392.判断子序列.ts │ ├── 413.等差数列划分.ts │ ├── 416.分割等和子集.js │ ├── 416.分割等和子集.ts │ ├── 44.通配符匹配.ts │ ├── 474.一和零.ts │ ├── 494.目标和.ts │ ├── 5.最长回文子串.js │ ├── 5.最长回文子串.ts │ ├── 516.最长回文子序列.js │ ├── 518.零钱兑换Ii.js │ ├── 518.零钱兑换Ii.ts │ ├── 53.最大子序和.js │ ├── 53.最大子数组和.ts │ ├── 576.出界的路径数.ts │ ├── 583.两个字符串的删除操作.ts │ ├── 62.不同路径.js │ ├── 62.不同路径.ts │ ├── 63.不同路径Ii.js │ ├── 63.不同路径Ii.ts │ ├── 64.最小路径和.ts │ ├── 673.最长递增子序列的个数.ts │ ├── 674.最长连续递增序列.ts │ ├── 70.爬楼梯.js │ ├── 70.爬楼梯.ts │ ├── 712.两个字符串的最小ascii删除和.ts │ ├── 718.最长重复子数组.ts │ ├── 72.编辑距离.js │ ├── 72.编辑距离.ts │ ├── 740.删除并获得点数.ts │ ├── 836.矩形重叠.js │ ├── 85.最大矩形.js │ ├── 87.扰乱字符串.ts │ ├── 877.石子游戏.ts │ ├── 879.盈利计划.ts │ ├── 902.最大为N的数字组合.ts │ ├── 91.解码方法.js │ ├── 91.解码方法.ts │ ├── 911.在线选举.ts │ ├── 918.环形子数组的最大和.ts │ ├── 96.不同的二叉搜索树.js │ ├── 97.交错字符串.ts │ └── 977.有序数组的平方.ts ├── graph │ ├── 207.课程表.ts │ ├── 210.课程表Ii.ts │ ├── 743.网络延迟时间.ts │ ├── 785.判断二分图.ts │ ├── 797.所有可能的路径.ts │ └── 886.可能的二分法.ts ├── greedy │ ├── 122.买卖股票的最佳时机Ii.js │ ├── 122.买卖股票的最佳时机Ii.ts │ ├── 406.根据身高重建队列.js │ ├── 435.无重叠区间.ts │ ├── 45.跳跃游戏Ii.js │ ├── 45.跳跃游戏Ii.ts │ ├── 55.跳跃游戏.js │ ├── 55.跳跃游戏.ts │ └── 621.任务调度器.ts ├── hash-table │ ├── 136.只出现一次的数字.js │ ├── 1636.按照频率将数组升序排序.ts │ ├── 219.存在重复元素Ii.js │ ├── 299.猜数字游戏.js │ ├── 3.无重复字符的最长子串.js │ ├── 349.两个数组的交集.js │ ├── 350.两个数组的交集Ii.ts │ ├── 36.有效的数独.ts │ ├── 409.最长回文串.ts │ ├── 463.岛屿的周长.js │ ├── 49.字母异位词分组.ts │ └── 575.分糖果.js ├── heap │ ├── 215.数组中的第k个最大元素.js │ ├── 215.数组中的第k个最大元素.ts │ ├── 347.前K个高频元素.ts │ ├── 887.鸡蛋掉落.js │ └── 918.环形子数组的最大和.ts ├── linked-list │ ├── 141.环形链表.ts │ ├── 142.环形链表Ii.ts │ ├── 19.删除链表的倒数第N个结点.ts │ ├── 2.两数相加.js │ ├── 203.移除链表元素.js │ ├── 206.反转链表.js │ ├── 21.合并两个有序链表.js │ ├── 21.合并两个有序链表.ts │ ├── 23.合并K个升序链表.ts │ ├── 237.删除链表中的节点.js │ ├── 24.两两交换链表中的节点.js │ ├── 82.删除排序链表中的重复元素Ii.ts │ ├── 86.分隔链表.ts │ └── 92.反转链表Ii.js ├── math │ ├── 1038.从二叉搜索树到更大和树.ts │ ├── 13.罗马数字转整数.js │ ├── 149.直线上最多的点数.ts │ ├── 172.阶乘后的零.js │ ├── 202.快乐数.ts │ ├── 319.灯泡开关.js │ ├── 633.平方数之和.js │ ├── 67.二进制求和.js │ ├── 7.整数反转.js │ ├── 856.括号的分数.js │ ├── 9.回文数.js │ ├── 9.回文数.ts │ ├── 921.使括号有效的最少添加.ts │ └── 986.区间列表的交集.js ├── ordered-map │ └── 876.链表的中间结点.ts ├── recursion │ └── 930.和相同的二元子数组.ts ├── sliding-window │ ├── 209.长度最小的子数组.ts │ ├── 239.滑动窗口最大值.ts │ ├── 3.无重复字符的最长子串.js │ ├── 3.无重复字符的最长子串.ts │ ├── 438.找到字符串中所有字母异位词.js │ ├── 438.找到字符串中所有字母异位词.ts │ ├── 567.字符串的排列.js │ ├── 567.字符串的排列.ts │ ├── 76.最小覆盖子串.js │ └── 76.最小覆盖子串.ts ├── sort │ ├── 56.合并区间.js │ ├── 56.合并区间.ts │ └── 57.插入区间.ts ├── stack │ ├── 155.最小栈.ts │ ├── 20.有效的括号.js │ ├── 20.有效的括号.ts │ ├── 224.基本计算器.ts │ ├── 225.用队列实现栈.js │ ├── 232.用栈实现队列.js │ ├── 316.去除重复字母.js │ ├── 316.去除重复字母.ts │ ├── 394.字符串解码.ts │ ├── 42.接雨水.ts │ ├── 496.下一个更大元素I.ts │ ├── 503.下一个更大元素Ii.ts │ ├── 739.每日温度.ts │ └── 84.柱状图中最大的矩形.ts ├── string │ ├── 14.最长公共前缀.js │ ├── 227.基本计算器Ii.ts │ ├── 28.实现StrStr.js │ ├── 383.赎金信.ts │ ├── 415.字符串相加.js │ ├── 5.最长回文子串.ts │ ├── 556.下一个更大元素Iii.ts │ ├── 557.反转字符串中的单词Iii.ts │ ├── 583.两个字符串的删除操作.ts │ └── 6.z字形变换.js ├── tree │ ├── 100.相同的树.js │ ├── 100.相同的树.ts │ ├── 101.对称二叉树.js │ ├── 102.二叉树的层序遍历.ts │ ├── 103.二叉树的锯齿形层序遍历.ts │ ├── 104.二叉树的最大深度.js │ ├── 105.从前序与中序遍历序列构造二叉树.ts │ ├── 106.从中序与后序遍历序列构造二叉树.ts │ ├── 107.二叉树的层序遍历Ii.ts │ ├── 108.将有序数组转换为二叉搜索树.ts │ ├── 110.平衡二叉树.js │ ├── 110.平衡二叉树.ts │ ├── 124.二叉树中的最大路径和.js │ ├── 144.二叉树的前序遍历.js │ ├── 144.二叉树的前序遍历.ts │ ├── 145.二叉树的后序遍历.js │ ├── 145.二叉树的后序遍历.ts │ ├── 199.二叉树的右视图.ts │ ├── 222.完全二叉树的节点个数.ts │ ├── 226.翻转二叉树.js │ ├── 230.二叉搜索树中第k小的元素.js │ ├── 230.二叉搜索树中第k小的元素.ts │ ├── 257.二叉树的所有路径.ts │ ├── 297.二叉树的序列化与反序列化.js │ ├── 437.路径总和Iii.js │ ├── 509.斐波那契数.ts │ ├── 538.把二叉搜索树转换为累加树.js │ ├── 538.把二叉搜索树转换为累加树.ts │ ├── 572.另一棵树的子树.ts │ ├── 654.最大二叉树.ts │ ├── 700.二叉搜索树中的搜索.ts │ ├── 889.根据前序和后序遍历构造二叉树.ts │ ├── 94.二叉树的中序遍历.js │ ├── 94.二叉树的中序遍历.ts │ ├── 95.不同的二叉搜索树Ii.js │ ├── 96.不同的二叉搜索树.js │ ├── 98.验证二叉搜索树.js │ ├── 98.验证二叉搜索树.ts │ └── 99.恢复二叉搜索树.js ├── trie │ └── 746.使用最小花费爬楼梯.ts ├── two-pointers │ ├── 11.盛最多水的容器.ts │ ├── 125.验证回文串.js │ ├── 141.环形链表.js │ ├── 141.环形链表.ts │ ├── 142.环形链表Ii.js │ ├── 142.环形链表Ii.ts │ ├── 167.两数之和Ii输入有序数组.js │ ├── 19.删除链表的倒数第n个节点.js │ ├── 26.删除排序数组中的重复项.js │ ├── 26.删除有序数组中的重复项.ts │ ├── 28.实现StrStr.js │ ├── 28.实现StrStr.ts │ ├── 283.移动零.js │ ├── 283.移动零.ts │ ├── 344.反转字符串.js │ ├── 344.反转字符串.ts │ ├── 42.接雨水.ts │ ├── 713.乘积小于k的子数组.ts │ ├── 75.颜色分类.js │ ├── 875.爱吃香蕉的珂珂.ts │ ├── 88.合并两个有序数组.js │ └── 977.有序数组的平方.ts ├── utils │ ├── list.ts │ └── tree.ts └── 专题 │ ├── 二叉堆算法.js │ └── 二叉树的序列化与反序列化.js └── tsconfig.json /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | browser: true, 5 | es6: true 6 | }, 7 | extends: [ 8 | 'alloy', 9 | 'alloy/typescript' 10 | ], 11 | rules: { 12 | 'no-var': 0, 13 | 'no-unused-vars': 0, 14 | 'max-params': 0, 15 | 'no-irregular-whitespace': 0, 16 | 'no-param-reassign': 0, 17 | '@typescript-eslint/explicit-member-accessibility': 'no-public' 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Leetcode Release CI / CD 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | build-and-deploy: 10 | runs-on: ubuntu-latest 11 | name: Leetcode Continuous Deploy 12 | 13 | strategy: 14 | matrix: 15 | node-version: [18.x] 16 | 17 | steps: 18 | - name: Actions Checkout 🛎️ 19 | uses: actions/checkout@v3 20 | 21 | - name: Use Node.js ${{ matrix.node-version }} 22 | uses: actions/setup-node@v3 23 | with: 24 | node-version: ${{ matrix.node-version }} 25 | 26 | - name: Install pnpm 27 | run: npm install -g pnpm@8 28 | 29 | - name: Install dependencies 🔧 30 | run: pnpm install 31 | 32 | - name: Build project 🔧 33 | run: pnpm build 34 | 35 | - name: Deploy to Github Pages 🚀 36 | uses: JamesIves/github-pages-deploy-action@v4 37 | with: 38 | branch: gh-pages 39 | folder: docs/.vuepress/dist 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .DS_Store 3 | docs/.vuepress/dist/ -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // 一行最多 120 字符 3 | printWidth: 120, 4 | // 使用 2 个空格缩进 5 | tabWidth: 2, 6 | // 不使用缩进符,而使用空格 7 | useTabs: false, 8 | // 行尾需要有分号 9 | semi: true, 10 | // 使用单引号 11 | singleQuote: true, 12 | // 对象的 key 仅在必要时用引号 13 | quoteProps: 'as-needed', 14 | // jsx 不使用单引号,而使用双引号 15 | jsxSingleQuote: false, 16 | // 末尾不需要逗号 17 | trailingComma: 'none', 18 | // 大括号内的首尾需要空格 19 | bracketSpacing: true, 20 | // jsx 标签的反尖括号需要换行 21 | jsxBracketSameLine: false, 22 | // 箭头函数,只有一个参数的时候,不需要括号 23 | arrowParens: 'avoid', 24 | // 每个文件格式化的范围是文件的全部内容 25 | rangeStart: 0, 26 | rangeEnd: Infinity, 27 | // 不需要写文件开头的 @prettier 28 | requirePragma: false, 29 | // 不需要自动在文件开头插入 @prettier 30 | insertPragma: false, 31 | // 使用默认的折行标准 32 | proseWrap: 'preserve', 33 | // 根据显示样式决定 html 要不要折行 34 | htmlWhitespaceSensitivity: 'css', 35 | // 换行符使用 lf 36 | endOfLine: 'lf' 37 | }; -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "ts debug", 6 | "type": "node", 7 | "request": "launch", 8 | "program": "${workspaceFolder}/${relativeFile}", 9 | "runtimeArgs": [ 10 | "--nolazy", 11 | "-r", 12 | "ts-node/register" 13 | ], 14 | "sourceMaps": true, 15 | "cwd": "${workspaceRoot}", 16 | "protocol": "inspector", 17 | "console": "externalTerminal", 18 | "internalConsoleOptions": "neverOpen" 19 | } 20 | ] 21 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 kelekexiao123 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # leetcode-in-javascript 2 | 3 | 使用 JavaScript 的 Leetcode 题解仓库 4 | 5 | 开刷最有价值的 Leetcode 题型~(附分类及题解) 6 | 7 | 题解按专题分类已整理成文档,链接传送门: [https://realduang.github.io/leetcode-in-javascript](https://realduang.github.io/leetcode-in-javascript) 8 | 9 | 如有错误,欢迎提 [issue](https://github.com/realDuang/leetcode-in-javascript/issues) 指正,不胜感激。 10 | -------------------------------------------------------------------------------- /docs/.vuepress/public/avatar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realDuang/leetcode-in-javascript/eb564a77bf82944ac9212ef427b1c26e093809a6/docs/.vuepress/public/avatar.jpg -------------------------------------------------------------------------------- /docs/.vuepress/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realDuang/leetcode-in-javascript/eb564a77bf82944ac9212ef427b1c26e093809a6/docs/.vuepress/public/favicon.ico -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | home: true 3 | actionText: 进入题库 → 4 | actionLink: /docs/topic/0.introduction.md 5 | 6 | footer: MIT LICENSE | Copyright © 2019-present by Duang 7 | --- -------------------------------------------------------------------------------- /docs/docs/list/array/1.two-sum.md: -------------------------------------------------------------------------------- 1 | # [1] 两数之和 2 | 3 | > 给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target  的那 两个 整数,并返回它们的数组下标。 4 | > 5 | > 你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。 6 | > 7 | > 你可以按任意顺序返回答案。 8 | > 9 | > 示例 1: 10 | > 11 | > 输入:nums = [2,7,11,15], target = 9 12 | > 13 | > 输出:[0,1] 14 | > 15 | > 解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。 16 | > 17 | > 示例 2: 18 | > 19 | > 输入:nums = [3,2,4], target = 6 20 | > 21 | > 输出:[1,2] 22 | > 23 | > 示例 3: 24 | > 25 | > 输入:nums = [3,3], target = 6 26 | > 27 | > 输出:[0,1] 28 | > 29 | > 提示: 30 | > 只会存在一个有效答案 31 | > 32 | > 进阶:你可以想出一个时间复杂度小于 O(n^2) 的算法吗? 33 | 34 | 这道题的解题思路是,我们遍历数组两次,第一次记录`target - 当前遍历值`的结果,第二次比对数组中是否有值与记录值相同,若存在的话,即`target=第一次遍历值+第二次遍历值`,符合题意,返回其索引即可。 35 | 36 | 这样的话,时间复杂度 O(n^2), 空间复杂度 O(1)。 37 | 38 | ```js 39 | var twoSum = function(nums, target) { 40 | for(let i = 0; i < nums.length; i++) { 41 | const rest = target - nums[i]; 42 | for(let j = i+1; j < nums.length; j++) { 43 | if(rest === nums[j]) return [i, j] 44 | } 45 | } 46 | return [] 47 | } 48 | ``` 49 | 50 | 我们可以采用空间换时间的方式来优化这道题。 51 | 52 | 建立一个 hash 表,遍历数组时存入每一次被减后的结果,然后直接拿当前值与之前匹配过的情况比较,看是否有相等的,即可得出最终结果。 53 | 54 | 这样时间复杂度降到了 O(n),空间复杂度 O(n)。 55 | 56 | ```js 57 | function twoSum(nums: number[], target: number): number[] { 58 | const hashMap: Record = {}; 59 | 60 | for (let i = 0; i < nums.length; i++) { 61 | const rest = target - nums[i]; 62 | if (hashMap[rest] !== undefined) { 63 | return [i, hashMap[rest]]; 64 | } 65 | hashMap[nums[i]] = i; 66 | } 67 | return []; 68 | } 69 | ``` 70 | -------------------------------------------------------------------------------- /docs/docs/list/array/1137.n-th-tribonacci-number.md: -------------------------------------------------------------------------------- 1 | # [1137] 第 N 个泰波那契数 2 | 3 | > 题目描述见 4 | > 5 | > https://leetcode-cn.com/problems/n-th-tribonacci-number/description/ 6 | > 7 | > 泰波那契序列 Tn 定义如下: 8 | > 9 | > T0 = 0, T1 = 1, T2 = 1, 且在 n >= 0 的条件下 Tn+3 = Tn + Tn+1 + Tn+2 10 | > 11 | > 给你整数 n,请返回第 n 个泰波那契数 Tn 的值。 12 | 13 | ```ts 14 | function tribonacci(n: number): number { 15 | if (n === 0) return 0; 16 | if (n === 1 || n === 2) return 1; 17 | 18 | let a = 0; 19 | let b = 1; 20 | let c = 1; 21 | 22 | for (let i = 3; i <= n; i++) { 23 | const sum = a + b + c; 24 | a = b; 25 | b = c; 26 | c = sum; 27 | } 28 | return c; 29 | } 30 | ``` 31 | 32 | 典型的斐波那契数列的变体,只是从条件推导参数由两个变为了三个。考察灵活变通能力,一般用来对付算法面试靠死记硬背的狠人,没什么价值。 33 | -------------------------------------------------------------------------------- /docs/docs/list/array/48.rotate-image.md: -------------------------------------------------------------------------------- 1 | # [48] 旋转图像 2 | 3 | > 给定一个 n × n 的二维矩阵 matrix 表示一个图像。请你将图像顺时针旋转 90 度。 4 | > 5 | > 你必须在 原地 旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要 使用另一个矩阵来旋转图像。 6 | > 7 | > 示例 1: 8 | > 9 | > 输入:matrix = [[1,2,3],[4,5,6],[7,8,9]] 10 | > 11 | > 输出:[[7,4,1],[8,5,2],[9,6,3]] 12 | > 13 | > 示例 2: 14 | > 15 | > 输入:matrix = [[5,1,9,11],[2,4,8,10],[13,3,6,7],[15,14,12,16]] 16 | > 17 | > 输出:[[15,13,2,5],[14,3,4,1],[12,6,8,9],[16,7,10,11]] 18 | > 19 | > 提示: 20 | > 21 | > n == matrix.length == matrix[i].length 22 | > 23 | > 1 <= n <= 20 24 | > 25 | > -1000 <= matrix[i][j] <= 1000 26 | 27 | 转置二维矩阵。难点在于找到方法,如何将原来的横行转变为纵行。 28 | 29 | 其实只要沿着左上到右下的对角线,将数组进行一次交换,即可将横行转变为纵行。但此时的翻转结果是镜像的,因此还需要再进行一次左右翻转交换即可得到最后的答案。 30 | 31 | 这道题的数学思考角度大于编程本身难度。 32 | 33 | ```ts 34 | function rotate(matrix: number[][]): void { 35 | const len = matrix.length; 36 | // 先将矩阵延左上到右下的对角线进行颠倒交换 37 | for (let i = 0; i < len; i++) { 38 | for (let j = i + 1; j < len; j++) { 39 | const temp = matrix[i][j]; 40 | matrix[i][j] = matrix[j][i]; 41 | matrix[j][i] = temp; 42 | } 43 | } 44 | // 之后将每一行的数据进行左右颠倒交换 45 | for (let i = 0; i < len; i++) { 46 | let left = 0; 47 | let right = len - 1; 48 | while (left < right) { 49 | const temp = matrix[i][left]; 50 | matrix[i][left] = matrix[i][right]; 51 | matrix[i][right] = temp; 52 | left += 1; 53 | right -= 1; 54 | } 55 | } 56 | } 57 | ``` 58 | -------------------------------------------------------------------------------- /docs/docs/list/backtracking/1038.binarySearchTreeToGreaterSumTree.md: -------------------------------------------------------------------------------- 1 | # [1038] 把二叉搜索树转换为累加树 2 | 3 | > 给出二叉 搜索 树的根节点,该树的节点值各不相同,请你将其转换为累加树(Greater Sum Tree),使每个节点 node 的新值等于原树中大于或等于 node.val 的值之和。 4 | > 5 | > 提醒一下,二叉搜索树满足下列约束条件: 6 | > 7 | > 节点的左子树仅包含键 小于 节点键的节点。 8 | > 9 | > 节点的右子树仅包含键 大于 节点键的节点。 10 | > 11 | > 左右子树也必须是二叉搜索树。 12 | > 13 | > 示例 1: 14 | > 15 | > 输入:[4,1,6,0,2,5,7,null,null,null,3,null,null,null,8] 16 | > 17 | > 输出:[30,36,21,36,35,26,15,null,null,null,33,null,null,null,8] 18 | > 19 | > 示例 2: 20 | > 21 | > 输入:root = [0,null,1] 22 | > 23 | > 输出:[1,null,1] 24 | > 25 | > 示例 3: 26 | > 27 | > 输入:root = [1,0,2] 28 | > 29 | > 输出:[3,3,2] 30 | > 31 | > 示例 4: 32 | > 33 | > 输入:root = [3,2,4,1] 34 | > 35 | > 输出:[7,9,4,10] 36 | > 37 | > 提示: 38 | > 39 | > 树中的节点数介于 1 和 100 之间。 40 | > 41 | > 每个节点的值介于 0 和 100 之间。 42 | > 43 | > 树中的所有值 互不相同 。 44 | > 45 | > 给定的树为二叉搜索树。 46 | 47 | 这道题与 538 题一模一样,因此参照该题的解法解答就好。 48 | 49 | 这道题提出了一个叫做累加树的概念。 50 | 51 | 我们可以发现,累加树的形成实际上就是将 BST 经过中序遍历后得到的从小到大排列的数据,从后向前开始累加,并在每个节点保留当前累加的值。 52 | 53 | 这就要求我们反向遍历树节点,并利用变量存储当前累加的值。 54 | 55 | 那么反向遍历树该怎么做呢?其实很简单,仍然属于中序遍历的一种,只需要将中序遍历先左后右的规矩改变成先右后左即可。接下来问题便迎刃而解了。 56 | 57 | ```js 58 | var bstToGst = function(root) { 59 | let sum = 0; 60 | function traverse(root) { 61 | if (!root) return; 62 | 63 | traverse(root.right); 64 | 65 | sum += root.val; 66 | root.val = sum; 67 | 68 | traverse(root.left); 69 | } 70 | 71 | traverse(root); 72 | return root; 73 | }; 74 | ``` 75 | -------------------------------------------------------------------------------- /docs/docs/list/backtracking/17.letter-combinations-of-a-phone-number.md: -------------------------------------------------------------------------------- 1 | # [17] 电话号码的字母组合 2 | 3 | > 给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。 4 | > 5 | > 给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。 6 | > 7 | > 示例 1: 8 | > 9 | > 输入:digits = "23" 10 | > 11 | > 输出:["ad","ae","af","bd","be","bf","cd","ce","cf"] 12 | > 13 | > 示例 2: 14 | > 15 | > 输入:digits = "" 16 | > 17 | > 输出:[] 18 | > 19 | > 示例 3: 20 | > 21 | > 输入:digits = "2" 22 | > 23 | > 输出:["a","b","c"] 24 | > 25 | > 提示: 26 | > 27 | > 0 <= digits.length <= 4 28 | > 29 | > digits[i] 是范围 ['2', '9'] 的一个数字。 30 | 31 | 这其实就是一个全排列题的变种,由于不存在最优子问题,我们直接用回溯法来解。 32 | 33 | 解题时记住回溯三要素:做选择,进行下一层递归,取消选择。 34 | 35 | 当递归进入到字符串与输入长度相等时,存入结果,结束递归。 36 | 37 | ```ts 38 | function letterCombinations(digits: string): string[] { 39 | const map: Record = { 40 | '2': ['a', 'b', 'c'], 41 | '3': ['d', 'e', 'f'], 42 | '4': ['g', 'h', 'i'], 43 | '5': ['j', 'k', 'l'], 44 | '6': ['m', 'n', 'o'], 45 | '7': ['p', 'q', 'r', 's'], 46 | '8': ['t', 'u', 'v'], 47 | '9': ['w', 'x', 'y', 'z'] 48 | }; 49 | 50 | if (digits.length === 0) return []; 51 | const res: string[] = []; 52 | const chosenArr = digits.split('').map(ch => map[ch]); 53 | backtrack('', 0); 54 | return res; 55 | 56 | function backtrack(str: string, index: number) { 57 | if (index === chosenArr.length) { 58 | res.push(str); 59 | return; 60 | } 61 | const currArr = chosenArr[index]; 62 | for (let i = 0; i < currArr.length; i++) { 63 | backtrack(str + chosenArr[index][i], index + 1); 64 | } 65 | } 66 | } 67 | ``` 68 | -------------------------------------------------------------------------------- /docs/docs/list/backtracking/46.permutations.md: -------------------------------------------------------------------------------- 1 | # [46] 全排列 2 | 3 | > 给定一个 没有重复 数字的序列,返回其所有可能的全排列。 4 | > 5 | > 示例: 6 | > 7 | > 输入: [1,2,3] 8 | > 9 | > 输出: 10 | > 11 | > [ 12 | > 13 | > ⁠ [1,2,3], 14 | > 15 | > ⁠ [1,3,2], 16 | > 17 | > ⁠ [2,1,3], 18 | > 19 | > ⁠ [2,3,1], 20 | > 21 | > ⁠ [3,1,2], 22 | > 23 | > ⁠ [3,2,1] 24 | > 25 | > ] 26 | 27 | 全排列问题,使用暴力回溯法解题,输出整个树的所有可能。 28 | 29 | 每次遍历时,控制剩余元素可选范围即可。 30 | 31 | ```ts 32 | function permute(nums: number[]): number[][] { 33 | const res: number[][] = []; 34 | const path: number[] = []; 35 | backtrack(nums); 36 | return res; 37 | 38 | function backtrack(rest: number[]) { 39 | if (rest.length === 0) { 40 | res.push([...path]); 41 | return; 42 | } 43 | rest.forEach((num, index) => { 44 | // 做选择 45 | path.push(num); 46 | // 回溯 47 | rest.splice(index, 1); 48 | backtrack(rest); 49 | // 取消选择 50 | rest.splice(index, 0, num); 51 | path.pop(); 52 | }); 53 | } 54 | } 55 | ``` 56 | -------------------------------------------------------------------------------- /docs/docs/list/backtracking/52.nQueensIi.md: -------------------------------------------------------------------------------- 1 | # [52] N皇后 II 2 | 3 | > n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。 4 | > 5 | > 给定一个整数 n,返回 n 皇后不同的解决方案的数量。 6 | > 7 | > 示例: 8 | > 9 | > 输入: 4 10 | > 11 | > 输出: 2 12 | > 13 | > 解释: 4 皇后问题存在如下两个不同的解法。 14 | > [ 15 | > 16 | > [".Q..", "...Q", "Q...", "..Q."], 17 | > 18 | > ["..Q.", "Q...", "...Q", ".Q.."] 19 | > 20 | > ] 21 | 22 | ```js 23 | var totalNQueens = function(n) { 24 | let res = 0; 25 | const map = Array(n) 26 | .fill('') 27 | .map(x => Array(n).fill('.')); 28 | backtrack(map, 0); 29 | return res; 30 | 31 | function backtrack(map, row) { 32 | if (row >= n) { 33 | res += 1; 34 | return; 35 | } 36 | 37 | for (let col = 0; col < n; col++) { 38 | if (isValid(map, row, col)) { 39 | map[row][col] = 'Q'; 40 | backtrack(map, row + 1); 41 | map[row][col] = '.'; 42 | } 43 | } 44 | } 45 | 46 | function isValid(map, row, col) { 47 | let i = row - 1; 48 | let topLeft = col - 1; 49 | let topRight = col + 1; 50 | while (i >= 0) { 51 | if (map[i][topLeft] === 'Q' || map[i][col] === 'Q' || map[i][topRight] === 'Q') return false; 52 | i--; 53 | topLeft--; 54 | topRight++; 55 | } 56 | 57 | return true; 58 | } 59 | }; 60 | ``` 61 | -------------------------------------------------------------------------------- /docs/docs/list/backtracking/77.combinations.md: -------------------------------------------------------------------------------- 1 | # [77] 组合 2 | 3 | > 给定两个整数 n 和 k,返回 1 ... n 中所有可能的 k 个数的组合。 4 | > 5 | > 示例: 6 | > 7 | > 输入: n = 4, k = 2 8 | > 9 | > 输出: 10 | > 11 | > [ 12 | > 13 | > ⁠ [2,4], 14 | > 15 | > ⁠ [3,4], 16 | > 17 | > ⁠ [2,3], 18 | > 19 | > ⁠ [1,2], 20 | > 21 | > ⁠ [1,3], 22 | > 23 | > ⁠ [1,4], 24 | > 25 | > ] 26 | 27 | 排列组合子集等一系列问题,由于没有可推导的规律,一般都是用最暴力的回溯法来解。 28 | 29 | 本题的注意点在,题目限制了子集的元素个数,因此在入栈 res 的时候需要判断一次 path 的长度。 30 | 31 | ```ts 32 | function combine(n: number, k: number): number[][] { 33 | const res: number[][] = []; 34 | const path: number[] = []; 35 | backtrack(1); 36 | return res; 37 | 38 | function backtrack(start: number) { 39 | if (path.length === k) { 40 | res.push([...path]); 41 | return; 42 | } 43 | 44 | for (let i = start; i <= n; i++) { 45 | path.push(i); 46 | backtrack(i + 1); 47 | path.pop(); 48 | } 49 | } 50 | } 51 | ``` 52 | -------------------------------------------------------------------------------- /docs/docs/list/backtracking/78.subsets.md: -------------------------------------------------------------------------------- 1 | # [78] 子集 2 | 3 | > 给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。 4 | > 5 | > 说明:解集不能包含重复的子集。 6 | > 7 | > 示例: 8 | > 9 | > 输入: nums = [1,2,3] 10 | > 11 | > 输出: 12 | > 13 | > [ 14 | > 15 | > [3], 16 | > 17 | > [1], 18 | > 19 | > [2], 20 | > 21 | > [1,2,3], 22 | > 23 | > [1,3], 24 | > 25 | > [2,3], 26 | > 27 | > [1,2], 28 | > 29 | > [] 30 | > 31 | > ] 32 | 33 | 经典回溯问题,使用前序遍历,取出每一个第一次走到的路径,入结果数组即可。 34 | 35 | ```js 36 | var subsets = function(nums) { 37 | const res = []; 38 | backtrack(nums, [], 0); 39 | return res; 40 | 41 | function backtrack(nums, path, start) { 42 | // 多叉树的前序遍历 43 | res.push([...path]); 44 | for (let i = start; i < nums.length; i++) { 45 | path.push(nums[i]); 46 | backtrack(nums, path, i + 1); 47 | path.pop(); 48 | } 49 | } 50 | }; 51 | ``` 52 | -------------------------------------------------------------------------------- /docs/docs/list/backtracking/90.subsets-ii.md: -------------------------------------------------------------------------------- 1 | # [90] 子集 II 2 | 3 | > 给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。 4 | > 5 | > 解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。 6 | > 7 | > 示例 1: 8 | > 9 | > 输入:nums = [1,2,2] 10 | > 11 | > 输出:[[],[1],[1,2],[1,2,2],[2],[2,2]] 12 | > 13 | > 示例 2: 14 | > 15 | > 输入:nums = [0] 16 | > 17 | > 输出:[[],[0]] 18 | 19 | 这题相比 `[78] 子集` 来说,增加了包含重复元素的可能,那么这就要求我们对重复元素的路径只取一次。 20 | 21 | 自然先要排序,将重复元素划分在一起。 22 | 23 | 由于进行过排序,因此在遍历时,除去第一个值一定会被执行外,若当前节点值与前一个节点相等,则可以判定直接跳过,进行剪枝。 24 | 25 | ```ts 26 | function subsetsWithDup(nums: number[]): number[][] { 27 | // 先排序 28 | nums.sort((a, b) => a - b); 29 | 30 | const res: number[][] = []; 31 | backtrack([], 0); 32 | return res; 33 | 34 | function backtrack(path: number[], start: number) { 35 | res.push([...path]); 36 | 37 | for (let i = start; i < nums.length; i++) { 38 | // 如果遇到值相同的情况,只递归第一个值,其余跳过 39 | if (i > start && nums[i] === nums[i - 1]) { 40 | continue; 41 | } 42 | // 选择该节点 43 | path.push(nums[i]); 44 | // 回溯 45 | backtrack(path, i + 1); 46 | // 撤销选择 47 | path.pop(); 48 | } 49 | } 50 | } 51 | ``` 52 | -------------------------------------------------------------------------------- /docs/docs/list/binary-search/162.findPeakElement.md: -------------------------------------------------------------------------------- 1 | # [162] 寻找峰值 2 | 3 | > 峰值元素是指其值大于左右相邻值的元素。 4 | > 5 | > 给定一个输入数组 nums,其中 nums[i] ≠ nums[i+1],找到峰值元素并返回其索引。 6 | > 7 | > 数组可能包含多个峰值,在这种情况下,返回任何一个峰值所在位置即可。 8 | > 9 | > 你可以假设 nums[-1] = nums[n] = -∞。 10 | > 11 | > 示例 1: 12 | > 13 | > 输入: nums = [1,2,3,1] 14 | > 15 | > 输出: 2 16 | > 17 | > 解释: 3 是峰值元素,你的函数应该返回其索引 2。 18 | > 19 | > 示例 2: 20 | > 21 | > 输入: nums = [1,2,1,3,5,6,4] 22 | > 23 | > 输出: 1 或 5 24 | > 25 | > 解释: 你的函数可以返回索引 1,其峰值元素为 2;或者返回索引 5, 其峰值元素为 6。 26 | > 27 | > 说明: 28 | > 29 | > 你的解法应该是 O(logN) 时间复杂度的。 30 | 31 | 这题最简单的当然是一个复杂度为O(n)的解法,不断比较当前值与后一个值的大小,直到后一个值比当前值大,则当前位置一定为一个峰值点。 32 | 33 | ```js 34 | var findPeakElement = function(nums) { 35 | for (let i = 0; i < nums.length; i++) { 36 | if (nums[i] > nums[i + 1]) return i; 37 | } 38 | // 单调递增时,即最后一个值最大 39 | return nums.length - 1; 40 | }; 41 | ``` 42 | 43 | 但是如果题目要求时间复杂度为O(lgn)的话,就只能从二分的角度下手了。 44 | 45 | 算法思路的关键在于,对于任意一点与其相邻的节点,沿着递增一侧的方向一定能够找到一个峰值。如比较`mid`与`mid+1`位置上值的大小,如果`mid`值较大,则[left, mid)一定有峰值,反之[mid+1, right)一定有峰值。 46 | 47 | ```js 48 | function findPeakElement(nums: number[]): number { 49 | let left = 0; 50 | let right = nums.length - 1; 51 | while (left < right) { 52 | const mid = Math.floor((left + right) / 2); 53 | if (nums[mid] > nums[mid + 1]) { 54 | // 说明此时一定在mid左侧有峰值,缩小右边界范围 55 | right = mid; 56 | } else { 57 | // 说明此时一定在mid右侧有峰值,缩小右边界范围 58 | left = mid + 1; 59 | } 60 | } 61 | return left; 62 | } 63 | ``` 64 | -------------------------------------------------------------------------------- /docs/docs/list/binary-search/230.kth-smallest-element-in-a-bst.md: -------------------------------------------------------------------------------- 1 | # [230] 二叉搜索树中第K小的元素 2 | 3 | > 给定一个二叉搜索树的根节点 root ,和一个整数 k ,请你设计一个算法查找其中第 k 个最小元素(从 1 开始计数)。 4 | > 5 | > 示例 1: 6 | > 7 | > 输入:root = [3,1,4,null,2], k = 1 8 | > 9 | > 输出:1 10 | > 11 | > 示例 2: 12 | > 13 | > 输入:root = [5,3,6,2,4,null,null,1], k = 3 14 | > 15 | > 输出:3 16 | > 17 | > 提示: 18 | > 19 | > 树中的节点数为 n 。 20 | > 21 | > 进阶:如果二叉搜索树经常被修改(插入/删除操作)并且你需要频繁地查找第 k 小的值,你将如何优化算法? 22 | 23 | 二叉搜索树,即 BST,它拥有两个特性: 24 | 25 | 1. 对于 BST 的每一个节点 node,左子树节点的值都比 node 的值要小,右子树节点的值都比 node 的值大。 26 | 2. 对于 BST 的每一个节点 node,它的左侧子树和右侧子树都是 BST。 27 | 28 | 从它的两个特性我们可以注意到,它与我们在快速排序完成后的树形结构是一模一样的。也就是说,二叉搜索树的中序遍历结果一定是有序的。这个特性对我们解题的思路十分有帮助。 29 | 30 | 我们需要在 BST 中找出第 K 小的元素。由于 BST 其中序遍历是有序的,因此题目就被直接简化成:输出中序遍历的第K个结果。题目就变得十分简单了。 31 | 32 | ```ts 33 | function kthSmallest(root: TreeNode | null, k: number): number { 34 | let count = 0; 35 | let res = root.val; 36 | traverse(root); 37 | return res; 38 | 39 | function traverse(node: TreeNode | null) { 40 | if (!node) return; 41 | 42 | traverse(node.left); 43 | count += 1; 44 | if (count === k) { 45 | res = node.val; 46 | return; 47 | } 48 | traverse(node.right); 49 | } 50 | } 51 | ``` 52 | -------------------------------------------------------------------------------- /docs/docs/list/binary-search/278.first-bad-version..md: -------------------------------------------------------------------------------- 1 | # [278] 第一个错误的版本 2 | 3 | > 你是产品经理,目前正在带领一个团队开发新的产品。不幸的是,你的产品的最新版本没有通过质量检测。由于每个版本都是基于之前的版本开发的,所以错误的版本之后的所有版本都是错的。 4 | > 5 | > 假设你有 n 个版本 [1, 2, ..., n],你想找出导致之后所有版本出错的第一个错误的版本。 6 | > 7 | > 你可以通过调用 bool isBadVersion(version) 接口来判断版本号 version 8 | > 9 | > 是否在单元测试中出错。实现一个函数来查找第一个错误的版本。你应该尽量减少对调用 API 的次数。 10 | > 11 | > 示例 1: 12 | > 13 | > 输入:n = 5, bad = 4 14 | > 15 | > 输出:4 16 | > 17 | > 解释: 18 | > 19 | > 调用 isBadVersion(3) -> false 20 | > 21 | > 调用 isBadVersion(5) -> true 22 | > 23 | > 调用 isBadVersion(4) -> true 24 | > 25 | > 所以,4 是第一个错误的版本。 26 | > 27 | > 示例 2: 28 | > 29 | > 输入:n = 1, bad = 1 30 | > 31 | > 输出:1 32 | 33 | 这道题是经典的二分查找中寻求左边界的题型。 34 | 35 | 这里的思路可以参考我的专题:`二分查找专题`中提到的通用思路来解,几乎没有变化。 36 | 37 | 这里的isBadVersion函数本质上其实就是一个判断 version >= bad 结果的函数。带入到公式里即可。 38 | 39 | ```ts 40 | const solution = function(isBadVersion: (version: number) => boolean) { 41 | return function(n: number): number { 42 | let left = 1; 43 | let right = n; 44 | while (left <= right) { 45 | const mid = Math.floor((left + right) / 2); 46 | if (isBadVersion(mid)) { 47 | right = mid - 1; 48 | } else { 49 | left = mid + 1; 50 | } 51 | } 52 | if (left <= n && isBadVersion(left)) { 53 | return left; 54 | } 55 | return n + 1; 56 | }; 57 | }; 58 | ``` 59 | -------------------------------------------------------------------------------- /docs/docs/list/binary-search/35.search-insert-position.md: -------------------------------------------------------------------------------- 1 | # [35] 搜索插入位置 2 | 3 | > 给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。 4 | > 5 | > 请必须使用时间复杂度为 O(log n) 的算法。 6 | > 7 | > 示例 1: 8 | > 9 | > 输入: nums = [1,3,5,6], target = 5 10 | > 11 | > 输出: 2 12 | > 13 | > 示例 2: 14 | > 15 | > 输入: nums = [1,3,5,6], target = 2 16 | > 17 | > 输出: 1 18 | > 19 | > 示例 3: 20 | > 21 | > 输入: nums = [1,3,5,6], target = 7 22 | > 23 | > 输出: 4 24 | > 25 | > 示例 4: 26 | > 27 | > 输入: nums = [1,3,5,6], target = 0 28 | > 29 | > 输出: 0 30 | > 31 | > 示例 5: 32 | > 33 | > 输入: nums = [1], target = 0 34 | > 35 | > 输出: 0 36 | > 37 | > 提示: 38 | > 39 | > nums 为无重复元素的升序排列数组 40 | 41 | 一看到使用O(logn)的解法,就知道需要用到二分查找了。 42 | 43 | 这题是经典二分算法的一个变种。区别仅在于,当最后发现没有匹配到target时,不是直接返回-1,而是返回应插入的位置。 44 | 45 | 这里需要插入的位置实际上就是循环退出时,左指针所在的位置。因此未匹配到时,返回左指针位置即可。 46 | 47 | ```ts 48 | function searchInsert(nums: number[], target: number): number { 49 | const len = nums.length; 50 | let left = 0; 51 | let right = len - 1; 52 | while (left <= right) { 53 | const mid = Math.floor((left + right) / 2); 54 | if (nums[mid] < target) { 55 | left = mid + 1; 56 | } else if (nums[mid] > target) { 57 | right = mid - 1; 58 | } else { 59 | return mid; 60 | } 61 | } 62 | return left; 63 | } 64 | ``` 65 | -------------------------------------------------------------------------------- /docs/docs/list/binary-search/367.valid-perfect-square.md: -------------------------------------------------------------------------------- 1 | # [367] 有效的完全平方数 2 | 3 | > 给定一个 正整数 num ,编写一个函数,如果 num 是一个完全平方数,则返回 true ,否则返回 false 。 4 | > 5 | > 进阶:不要 使用任何内置的库函数,如  sqrt 。 6 | > 7 | > 示例 1: 8 | > 9 | > 输入:num = 16 10 | > 11 | > 输出:true 12 | > 13 | > 示例 2: 14 | > 15 | > 输入:num = 14 16 | > 17 | > 输出:false 18 | 19 | 不使用 sqrt 库函数,最简单的思路就是从 1 * 1, 2 * 2 开始不断累加,直到等于或大于目标数。此时返回true或false即可。这样的时间复杂度为O(N)。 20 | 21 | 如果要优化效率的话,显然就只有二分查找了。对于大于1的目标数,通过不断二分,逼近平方根即可。时间复杂度为O(logN)。 22 | 23 | ```ts 24 | function isPerfectSquare(num: number): boolean { 25 | if (num === 1) return true; 26 | 27 | let left = 0, 28 | right = num; 29 | while (left <= right) { 30 | const mid = Math.floor((left + right) / 2); 31 | if (mid> mid > num) { 32 | right = mid - 1; 33 | } else if (mid> mid < num) { 34 | left = mid + 1; 35 | } else { 36 | return true; 37 | } 38 | } 39 | return false; 40 | } 41 | ``` 42 | -------------------------------------------------------------------------------- /docs/docs/list/binary-search/69.sqrtx.md: -------------------------------------------------------------------------------- 1 | # [69] x 的平方根 2 | 3 | > 给你一个非负整数 x ,计算并返回  x  的 算术平方根 。 4 | > 5 | > 由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 。 6 | > 7 | > 注意:不允许使用任何内置指数函数和算符,例如 pow(x, 0.5) 或者 x>\* 0.5 。 8 | > 9 | > 示例 1: 10 | > 11 | > 输入:x = 4 12 | > 13 | > 输出:2 14 | > 15 | > 示例 2: 16 | > 17 | > 输入:x = 8 18 | > 19 | > 输出:2 20 | > 21 | > 解释:8 的算术平方根是 2.82842..., 由于返回类型是整数,小数部分将被舍去。 22 | > 23 | > 提示: 24 | > 25 | > 0 <= x <= 2^31 - 1 26 | 27 | 这道题是一个经典的面试题了,也是最容易最典型的二分查找问题。不借助任何高深的数学知识,我们可以通过二分查找来找到 x 的平方根。 28 | 29 | 我们对 x 不断二分,直到找到一个数 mid,使得 mid 的平方小于等于 x,但 mid+1 的平方大于 x。这样我们就找到了 x 的平方根。 30 | 31 | ```ts 32 | function mySqrt(x: number): number { 33 | if (x === 0) return 0; 34 | if (x === 1) return 1; 35 | 36 | let l = 0, 37 | r = x; 38 | let ans = -1; 39 | 40 | while (l <= r) { 41 | const mid = Math.floor((l + r) / 2); 42 | if (mid * mid > x) { 43 | r = mid - 1; 44 | } else { 45 | ans = mid; 46 | l = mid + 1; 47 | } 48 | } 49 | 50 | return ans; 51 | } 52 | ``` 53 | -------------------------------------------------------------------------------- /docs/docs/list/binary-search/704.binary-search.md: -------------------------------------------------------------------------------- 1 | # [704] 二分查找 2 | 3 | > 给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target  ,写一个函数搜索 nums 中的 4 | > 5 | > target,如果目标值存在返回下标,否则返回 -1。 6 | > 7 | > 示例 1: 8 | > 9 | > 输入: nums = [-1,0,3,5,9,12], target = 9 10 | > 11 | > 输出: 4 12 | > 13 | > 解释: 9 出现在 nums 中并且下标为 4 14 | > 15 | > 示例 2: 16 | > 17 | > 输入: nums = [-1,0,3,5,9,12], target = 2 18 | > 19 | > 输出: -1 20 | > 21 | > 解释: 2 不存在 nums 中因此返回 -1 22 | > 23 | > 提示: 24 | > 25 | > 你可以假设 nums 中的所有元素是不重复的。 26 | > 27 | > n 将在 [1, 10000]之间。 28 | > 29 | > nums 的每个元素都将在 [-9999, 9999]之间。 30 | 31 | 这道题就是二分查找法的基本形态,数组有序且元素不重复。 32 | 33 | 这里的思路可以参考我的专题:`二分查找专题`中提到的通用思路来解。 34 | 35 | 由于元素不重复,因此选用全闭区间的二分查找思路即可。 36 | 37 | ```ts 38 | function search(nums: number[], target: number): number { 39 | let left = 0; 40 | let right = nums.length - 1; 41 | 42 | while (left <= right) { 43 | const mid = Math.floor((left + right) / 2); 44 | 45 | if (nums[mid] === target) return mid; 46 | if (nums[mid] > target) { 47 | right = mid - 1; 48 | } else { 49 | left = mid + 1; 50 | } 51 | } 52 | return -1; 53 | } 54 | ``` 55 | -------------------------------------------------------------------------------- /docs/docs/list/bit-manipulation/136.singleNumber.md: -------------------------------------------------------------------------------- 1 | # [136] 只出现一次的数字 2 | 3 | > 给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。 4 | > 5 | > 说明:你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗? 6 | > 7 | > 示例 1: 8 | > 9 | > 输入: [2,2,1] 10 | > 11 | > 输出: 1 12 | > 13 | > 示例 2: 14 | > 15 | > 输入: [4,1,2,1,2] 16 | > 17 | > 输出: 4 18 | 19 | 根据题目,我们很直观的想出:可以利用hash表的方式存储第一次得到的值,之后每次查询hash表看是否有相同的值,若有则删除。遍历结束后hash表中剩下的值即为所求。 20 | 21 | 这样做时间复杂度O(n),空间复杂度O(n)。 22 | 23 | ```js 24 | var singleNumber = function(nums) { 25 | const hash = {}; 26 | for (let i = 0; i < nums.length; i++) { 27 | if (hash[nums[i]] === undefined) { 28 | hash[nums[i]] = nums[i]; 29 | } else { 30 | Reflect.deleteProperty(hash, nums[i]); 31 | } 32 | } 33 | return hash[Reflect.ownKeys(hash)[0]]; 34 | }; 35 | ``` 36 | 37 | 当然也可以利用排序后的数组相邻的值若不同则可得唯一性来解,这样的时间复杂度O(nlgn),空间复杂度O(1)。这里就不多说了。 38 | 39 | 但由于题目要求时间复杂度O(n),空间复杂度O(1),因此不能使用排序算法,也不能使用hash-table。 40 | 41 | 本题使用二进制异或的性质来完成:两个数字异或 a^b 是将 a 和 b 的二进制每一位进行运算,如果同一位的数字相同则为0,不同则为1。 42 | 43 | ```js 44 | var singleNumber = function(nums) { 45 | let res = 0; 46 | for (let i = 0; i < nums.length; i++) { 47 | res = res ^ nums[i]; 48 | } 49 | return res; 50 | }; 51 | ``` 52 | -------------------------------------------------------------------------------- /docs/docs/list/bit-manipulation/169.majorityElement.md: -------------------------------------------------------------------------------- 1 | # [169] 多数元素 2 | 3 | > 给定一个大小为 n 的数组,找到其中的多数元素。多数元素是指在数组中出现次数大于 ⌊ n/2 ⌋ 的元素。 4 | > 5 | > 你可以假设数组是非空的,并且给定的数组总是存在多数元素。 6 | > 7 | > 示例 1: 8 | > 9 | > 输入: [3,2,3] 10 | > 11 | > 输出: 3 12 | > 13 | > 示例 2: 14 | > 15 | > 输入: [2,2,1,1,1,2,2] 16 | > 17 | > 输出: 2 18 | 19 | 这道题使用了Moore Voting算法,即摩尔投票算法求众数,对于求众数的问题复杂度降到了惊人的时间O(n),空间O(1)。 20 | 21 | 算法思想是:设定一个候选者,每出现相同的元素计数器+1,否则-1。当计数器为0时,说明当前元素与候选者出现次数相同,更换候选者为当前元素。遍历完数组时当前的候选者即为所求众数。 22 | 23 | ```js 24 | var majorityElement = function(nums) { 25 | let res = nums[0]; 26 | let count = 0; 27 | for (let i = 0; i < nums.length; i++) { 28 | if (count === 0) { 29 | res = nums[i]; 30 | count++; 31 | } else { 32 | res === nums[i] ? count++ : count--; 33 | } 34 | } 35 | return res; 36 | }; 37 | ``` 38 | -------------------------------------------------------------------------------- /docs/docs/list/breadth-first-search/111.minimumDepthOfBinaryTree.md: -------------------------------------------------------------------------------- 1 | # [111] 二叉树的最小深度 2 | 3 | > 给定一个二叉树,找出其最小深度。 4 | > 5 | > 最小深度是从根节点到最近叶子节点的最短路径上的节点数量。 6 | > 7 | > 说明:叶子节点是指没有子节点的节点。 8 | > 9 | > 示例: 10 | > 11 | > 给定二叉树[3,9,20,null,null,15,7], 12 | > 13 | > 返回它的最小深度 2. 14 | 15 | 这道题经典的BFS题型。从BFS的角度来考虑,只要出现某一层中的节点为叶子节点,那么直接返回当前所在层级即可。 16 | 17 | ```ts 18 | function minDepth(root: TreeNode | null): number { 19 | if (root === null) return 0; 20 | 21 | let res = 0; 22 | const queue: TreeNode[] = []; 23 | queue.unshift(root); 24 | 25 | while (queue.length > 0) { 26 | // 对于每一层,进行一次遍历,入队子节点操作 27 | res += 1; 28 | let count = queue.length; 29 | while (count) { 30 | const curr = queue.pop(); 31 | // 此时为叶子节点,直接返回结果 32 | if (!curr.left && !curr.right) { 33 | return res; 34 | } 35 | if (curr.left) { 36 | queue.unshift(curr.left); 37 | } 38 | if (curr.right) { 39 | queue.unshift(curr.right); 40 | } 41 | count -= 1; 42 | } 43 | } 44 | return res; 45 | } 46 | ``` 47 | -------------------------------------------------------------------------------- /docs/docs/list/depth-first-search/547.number-of-provinces.md: -------------------------------------------------------------------------------- 1 | # [547] 省份数量 2 | > 3 | > 有 n 个城市,其中一些彼此相连,另一些没有相连。如果城市 a 与城市 b 直接相连,且城市 b 与城市 c 直接相连,那么城市 a 与城市 c 间接相连。 4 | > 5 | > 省份 是一组直接或间接相连的城市,组内不含其他没有相连的城市。 6 | > 7 | > 给你一个 n x n 的矩阵 isConnected ,其中 isConnected[i][j] = 1 表示第 i 个城市和第 j 个城市直接相连,而 isConnected[i][j] = 0 表示二者不直接相连。 8 | > 9 | > 返回矩阵中 省份 的数量。 10 | > 11 | > 示例 1: 12 | > 13 | > 输入:isConnected = [[1,1,0],[1,1,0],[0,0,1]] 14 | > 15 | > 输出:2 16 | > 17 | > 示例 2: 18 | > 19 | > 输入:isConnected = [[1,0,0],[0,1,0],[0,0,1]] 20 | > 21 | > 输出:3 22 | 23 | 这个也是岛屿问题的一个变种,这里我们还是考虑使用 visited 数组与 DFS 来解题。 24 | 25 | 注意到,实际上`isConnected[i][j]` 与 `isConnected[j][i]` 指的是同一件事。也就是说,我们判断省份 a、b 是否联通时,只需要判断一遍即可。 26 | 27 | 因此我们的 visited 数组可以被简化为一维数组,表示各省份是否有被遍历过。 28 | 29 | ```ts 30 | function findCircleNum(isConnected: number[][]): number { 31 | let res = 0; 32 | const len = isConnected.length; 33 | 34 | const visited: boolean[] = Array(len).fill(false); 35 | 36 | for (let i = 0; i < len; i++) { 37 | if (!visited[i]) { 38 | res += 1; 39 | dfs(i); 40 | } 41 | } 42 | 43 | function dfs(i: number) { 44 | visited[i] = true; 45 | for (let j = 0; j < len; j++) { 46 | if (isConnected[i][j] === 1 && !visited[j]) { 47 | dfs(j); 48 | } 49 | } 50 | } 51 | 52 | return res; 53 | } 54 | ``` 55 | -------------------------------------------------------------------------------- /docs/docs/list/dynamic-programming/120.triangle.md: -------------------------------------------------------------------------------- 1 | # [120] 三角形最小路径和 2 | 3 | > 给定一个三角形,找出自顶向下的最小路径和。每一步只能移动到下一行中相邻的结点上。 4 | > 5 | > 例如,给定三角形: 6 | > 7 | > [ 8 | > ⁠ [2], 9 | > ⁠ [3,4], 10 | > ⁠ [6,5,7], 11 | > ⁠ [4,1,8,3] 12 | > ] 13 | > 14 | > 自顶向下的最小路径和为 11(即,2 + 3 + 5 + 1 = 11)。 15 | > 16 | > 说明:如果你可以只使用 O(n) 的额外空间(n 为三角形的总行数)来解决这个问题,那么你的算法会很加分。 17 | 18 | 状态转移方程:dp[i][j] = min(dp[i+1][j], dp[i+1][j+1]) + triangle[i][j] 19 | 20 | 注意要从下往上求极值,题目可以转化为:最后一行元素到当前元素的最小路径和。可以看出每一个点的最小路径只与下一排的相邻两个值有关。 21 | 22 | ```js 23 | var minimumTotal = function(triangle) { 24 | const len = triangle.length; 25 | const dp = new Array(len).fill(0).map(x => new Array(len).fill(0)); 26 | dp[len - 1] = triangle[len - 1]; 27 | for (let i = len - 2; i >= 0; i--) { 28 | for (let j = 0; j <= i; j++) { 29 | dp[i][j] = Math.min(dp[i + 1][j], dp[i + 1][j + 1]) + triangle[i][j]; 30 | } 31 | } 32 | return dp[0][0]; 33 | }; 34 | ``` 35 | -------------------------------------------------------------------------------- /docs/docs/list/dynamic-programming/121.bestTimeToBuyAndSellStock.md: -------------------------------------------------------------------------------- 1 | # [121] 买卖股票的最佳时机 2 | 3 | >给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。 4 | > 5 | >如果你最多只允许完成一笔交易(即买入和卖出一支股票),设计一个算法来计算你所能获取的最大利润。 6 | > 7 | >注意你不能在买入股票前卖出股票。 8 | > 9 | >示例 1: 10 | > 11 | >输入: [7,1,5,3,6,4] 12 | > 13 | >输出: 5 14 | > 15 | >解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。 16 | > 17 | >注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格。 18 | > 19 | >示例 2: 20 | > 21 | >输入: [7,6,4,3,1] 22 | > 23 | >输出: 0 24 | > 25 | >解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。 26 | 27 | 求最值的问题我们用动态规划是比较容易想到答案的,设dp[i]为第i天卖出股票时的最大利润。 28 | 29 | 由于题目要求我们只能进行一次交易,当希望在第i天卖出时,那么买入的时间点一定是在此之前的最低点才行。因此我们还需要一个变量存入在第i天以前的价格最低点min。 30 | 31 | 状态转移方程:dp[i] = nums[i] - min 32 | 33 | 得到了状态转移方程,我们很明显可以看出,dp[i]与dp[?]之间并没有直接关系,因此我们只需要一个变量存入dp[i]中的最大值就能得出答案了,从而节省了空间复杂度。 34 | 35 | ```js 36 | function maxProfit(prices: number[]): number { 37 | const len = prices.length; 38 | let res = 0; 39 | let min = prices[0]; 40 | 41 | for (let i = 1; i < len; i++) { 42 | res = Math.max(res, prices[i] - min); 43 | min = Math.min(min, prices[i]); 44 | } 45 | 46 | return res; 47 | } 48 | ``` 49 | -------------------------------------------------------------------------------- /docs/docs/list/dynamic-programming/198.houseRobber.md: -------------------------------------------------------------------------------- 1 | # [198] 打家劫舍 2 | 3 | > 你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。 4 | > 5 | > 给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额。 6 | > 7 | > 示例 1: 8 | > 9 | > 输入:[1,2,3,1] 10 | > 11 | > 输出:4 12 | > 13 | > 解释:偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。偷窃到的最高金额 = 1 + 3 = 4 。 14 | > 15 | > 示例 2: 16 | > 17 | > 输入:[2,7,9,3,1] 18 | > 19 | > 输出:12 20 | > 21 | > 解释:偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。偷窃到的最高金额 = 2 + 9 + 1 = 12 。 22 | 23 | 最简单经典的动态规划题之一。当前最优选择只有两种可能: 24 | 25 | 1. 要么拿当前这户的钱,获利为当前这户所取得的钱与上两家获取的获利相加 26 | 2. 要么不拿当前这户的钱,获利等于在上一户所取得的获利 27 | 28 | 原版动态规划如下: 29 | 30 | 状态转移方程:dp[n] = max(dp[n-2] + amount, dp[n-1]) 31 | 32 | ```js 33 | var rob = function(nums) { 34 | let dp = new Array(nums.length); 35 | dp[0] = 0; 36 | dp[1] = nums[0]; 37 | for (let i = 2; i <= nums.length; i++) { 38 | dp[i] = Math.max(dp[i - 2] + nums[i - 1], dp[i - 1]); 39 | } 40 | return dp[nums.length]; 41 | }; 42 | ``` 43 | 44 | 由于 dp[n] 的值只取决于 dp[n-1] 与 dp[n-2],因此我们只需要不断更新这两个变量,从而节省空间复杂度。 45 | 46 | ```ts 47 | function rob(nums: number[]): number { 48 | const len = nums.length; 49 | if (len === 1) return nums[0]; 50 | 51 | let interval = 0; 52 | let prev = nums[0]; 53 | // 从第二间房开始判断,当前房间是否抢: 54 | // 不抢,结果等于抢邻房间时的最大值 + 0; 55 | // 抢,结果等于隔间房间的最大值 + 当前房间金额 56 | // 状态转移方程:Sn = Math.max(Sn-1, Sn-2 + nums[n]) 57 | for (let i = 1; i < len; i++) { 58 | const curr = Math.max(interval + nums[i], prev); 59 | interval = prev; 60 | prev = curr; 61 | } 62 | return prev; 63 | } 64 | ``` 65 | -------------------------------------------------------------------------------- /docs/docs/list/dynamic-programming/213.houseRobberIi.md: -------------------------------------------------------------------------------- 1 | # [213] 打家劫舍 II 2 | 3 | > 你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都围成一圈,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。 4 | > 5 | > 给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额。 6 | > 7 | > 示例 1: 8 | > 9 | > 输入:[2,3,2] 10 | > 11 | > 输出:3 12 | > 13 | > 解释:你不能先偷窃 1 号房屋(金额 = 2),然后偷窃 3 号房屋(金额 = 2), 因为他们是相邻的。 14 | > 15 | > 示例 2: 16 | > 17 | > 输入:[1,2,3,1] 18 | > 19 | > 输出:4 20 | > 21 | > 解释:你可以先偷窃 1 号房屋(金额 = 1),然后偷窃 3 号房屋(金额 = 3)。 22 | > 23 | > 偷窃到的最高金额 = 1 + 3 = 4 。 24 | 25 | 这道题与`198. 打家劫舍`长得几乎一致,因此解法也是相同的,解题思路可以参考该题。 26 | 27 | 本题与上题唯一的一点不同在于,房屋的摆放是环形的,形成了一个环形数组。因此我们做的最优决策里,不能同时取第一号和最后一号房屋里的钱。 28 | 29 | 解决方法也很简单,我们排除掉收尾连接处可能对结果产生的影响,即分别算出取 [1, n] 房间与 [0, n-1] 房间的最优决策,再比较一下取较大的那个即能得到最优解了。 30 | 31 | ```ts 32 | function rob(nums: number[]): number { 33 | const len = nums.length; 34 | if (len === 1) return nums[0]; 35 | 36 | // 围成一圈后,代表着不能同时抢第一家与最后一家 37 | // 需要分成抢第一家与不抢第一家两种情况 38 | const rob1 = getMaxSum(0, len - 1); 39 | const notRob1 = getMaxSum(1, len); 40 | return Math.max(rob1, notRob1); 41 | 42 | function getMaxSum(start: number, end: number) { 43 | let interval = 0; 44 | let prev = 0; 45 | for (let i = start; i < end; i++) { 46 | const res = Math.max(prev, interval + nums[i]); 47 | interval = prev; 48 | prev = res; 49 | } 50 | return prev; 51 | } 52 | } 53 | ``` 54 | -------------------------------------------------------------------------------- /docs/docs/list/dynamic-programming/221.maximal-square.md: -------------------------------------------------------------------------------- 1 | # [221] 最大正方形 2 | 3 | > 在一个由 '0' 和 '1' 组成的二维矩阵内,找到只包含 '1' 的最大正方形,并返回其面积。 4 | > 5 | > 示例 1: 6 | > 7 | > 输入:matrix = 8 | > 9 | > [["1","0","1","0","0"],["1","0","1","1","1"],["1","1","1","1","1"],["1","0","0","1","0"]] 10 | > 11 | > 输出:4 12 | > 13 | > 示例 2: 14 | > 15 | > 输入:matrix = [["0","1"],["1","0"]] 16 | > 17 | > 输出:1 18 | > 19 | > 示例 3: 20 | > 21 | > 输入:matrix = [["0"]] 22 | > 23 | > 输出:0 24 | > 25 | > 提示: 26 | > 27 | > m == matrix.length 28 | > 29 | > n == matrix[i].length 30 | > 31 | > matrix[i][j] 为 '0' 或 '1' 32 | 33 | 动态规划一类的问题,求解大多不难,难的是想到重复的子问题结构。 34 | 35 | 例如本题,我们需要通过观察,找出一个由1组成的正方形是如何扩大的,其推导过程是怎样的。 36 | 37 | 我们可以发现,当 matrix[i][j] 为 1,且它的左边、上边、左上边都为正方形时,matrix[i][j] 才能够成为一个更大的正方形的右下角。而其能够组成的最大正方形为,左边、上边、左上边的正方形的最小值 + 1。 38 | 39 | 观察到这个规律,我们即可设 dp[i][j] 为以 (i, j) 为右下角时,所组成的最大正方形的边长,并最终求解。 40 | 41 | ```ts 42 | function maximalSquare(matrix: string[][]): number { 43 | const rowLen = matrix.length; 44 | const colLen = matrix[0].length; 45 | // 设 dp[i][j] 为以 (i, j) 为右下角时,所组成的最大正方形的边长 46 | const dp: number[][] = Array(rowLen + 1) 47 | .fill(0) 48 | .map(x => Array(colLen + 1).fill(0)); 49 | 50 | let res = 0; 51 | for (let i = 1; i <= rowLen; i++) { 52 | for (let j = 1; j <= colLen; j++) { 53 | // 当 matrix[i][j] 为 1,且它的左边、上边、左上边都为正方形时,matrix[i][j] 才能够成为一个更大的正方形的右下角 54 | if (matrix[i - 1][j - 1] === '1') { 55 | dp[i][j] = Math.min(dp[i][j - 1], dp[i - 1][j], dp[i - 1][j - 1]) + 1; 56 | res = Math.max(res, dp[i][j]); 57 | } 58 | } 59 | } 60 | return res> res; 61 | } 62 | ``` 63 | -------------------------------------------------------------------------------- /docs/docs/list/dynamic-programming/32.longestValidParentheses.md: -------------------------------------------------------------------------------- 1 | # [32] 最长有效括号 2 | 3 | > 给你一个只包含 '(' 和 ')' 的字符串,找出最长有效(格式正确且连续)括号子串的长度。 4 | > 5 | > 示例 1: 6 | > 7 | > 输入:s = "(()" 8 | > 9 | > 输出:2 10 | > 11 | > 解释:最长有效括号子串是 "()" 12 | > 13 | > 示例 2: 14 | > 15 | > 输入:s = ")()())" 16 | > 17 | > 输出:4 18 | > 19 | > 解释:最长有效括号子串是 "()()" 20 | > 21 | > 示例 3: 22 | > 23 | > 输入:s = "" 24 | > 25 | > 输出:0 26 | > 27 | > 提示: 28 | > 29 | > 0 <= s.length <= 3> 10^4 30 | > 31 | > s[i] 为 '(' 或 ')' 32 | 33 | 遇到括号匹配的问题,我们常用的方法是使用栈记录左括号以等待遇到右括号时获取匹配信息。 34 | 35 | 本题也是一样,先用栈记录左括号的位置,然后在遇到右括号时,计算当前位置与栈顶元素的距离,即为当前括号所匹配的范围长度。 36 | 37 | 而由于题目要求我们找到最长有效(格式正确且连续)括号子串的长度,因此,在找到匹配的括号后,我们还需要知道,匹配到的左括号之前是否还有连续的有效括号子串。 38 | 39 | 这就涉及到动态规划擅长的范围了,记录下每一次匹配到时的最长有效括号子串长度,即可解决问题。 40 | 41 | ```ts 42 | function longestValidParentheses(s: string): number { 43 | const len = s.length; 44 | if (len <= 1) return 0; 45 | 46 | const stack = []; 47 | // dp[i] 定义:以 s[i]为结尾的最长合法括号子串的长度 48 | const dp: number[] = Array(s.length + 1).fill(0); 49 | 50 | for (let i = 0; i < s.length; i++) { 51 | if (s.charAt(i) === '(') { 52 | // 匹配到左括号,压栈,且当前s[i]结尾的子串肯定不合法 53 | stack.push(i); 54 | dp[i + 1] = 0; 55 | } else { 56 | if (stack.length === 0) { 57 | // 没有匹配的左括号,不合法 58 | dp[i + 1] = 0; 59 | } else { 60 | // 最长长度为,当前位置与匹配到的左括号的距离,加上匹配到左括号前的最长子串 61 | const leftIndex = stack.pop(); 62 | dp[i + 1] = dp[leftIndex] + i - leftIndex + 1; 63 | } 64 | } 65 | } 66 | 67 | const res = Math.max(...dp); 68 | return res; 69 | } 70 | ``` 71 | -------------------------------------------------------------------------------- /docs/docs/list/dynamic-programming/583.delete-operation-for-two-strings.md: -------------------------------------------------------------------------------- 1 | # [583] 两个字符串的删除操作 2 | 3 | > 给定两个单词 word1 和 word2 ,返回使得 word1 和  word2 相同所需的最小步数。 4 | > 5 | > 每步 可以删除任意一个字符串中的一个字符。 6 | > 7 | > 示例 1: 8 | > 9 | > 输入: word1 = "sea", word2 = "eat" 10 | > 11 | > 输出: 2 12 | > 13 | > 解释: 第一步将 "sea" 变为 "ea" ,第二步将 "eat "变为 "ea" 14 | > 15 | > 示例  2: 16 | > 17 | > 输入:word1 = "leetcode", word2 = "etco" 18 | > 19 | > 输出:4 20 | > 21 | > 提示: 22 | > 23 | > 1 <= word1.length, word2.length <= 500 24 | > 25 | > word1 和 word2 只包含小写英文字母 26 | 27 | 这道题与 `[1143] 最长公共子序列` 一样,都是通过比对两字符串各字符来寻求最优解的问题。 28 | 29 | 我们只需要找出,当字符匹配时与不匹配时,最优解的变化即可得出结论。 30 | 31 | 1. 当字符相等时,说明本位置不需要做任何删除操作,结果等于去除该位置的结果。 32 | 2. 当字符不等时,说明本位置需要多进行一个删除操作,但具体是从word1取还是word2取,取决于那种删除步骤更少。 33 | 34 | ```ts 35 | function minDistance(word1: string, word2: string): number { 36 | const len1 = word1.length; 37 | const len2 = word2.length; 38 | const dp = Array(len1 + 1) 39 | .fill(0) 40 | .map(x => Array(len2 + 1).fill(0)); 41 | 42 | for (let j = 1; j <= len2; j++) { 43 | dp[0][j] = j; 44 | } 45 | for (let i = 1; i <= len1; i++) { 46 | dp[i][0] = i; 47 | for (let j = 1; j <= len2; j++) { 48 | if (word1[i - 1] === word2[j - 1]) { 49 | dp[i][j] = dp[i - 1][j - 1]; 50 | } else { 51 | dp[i][j] = Math.min(dp[i - 1][j], dp[i][j - 1]) + 1; 52 | } 53 | } 54 | } 55 | 56 | return dp[len1][len2]; 57 | } 58 | ``` 59 | -------------------------------------------------------------------------------- /docs/docs/list/dynamic-programming/673.number-of-longest-increasing-subsequence.md: -------------------------------------------------------------------------------- 1 | # [673] 最长递增子序列的个数 2 | 3 | > 给定一个未排序的整数数组 nums , 返回最长递增子序列的个数 。 4 | > 5 | > 注意 这个数列必须是 严格 递增的。 6 | > 7 | > 示例 1: 8 | > 9 | > 输入:[1,3,5,4,7] 10 | > 11 | > 输出:2 12 | > 13 | > 解释:有两个最长递增子序列,分别是 [1, 3, 4, 7] 和 [1, 3, 5, 7]。 14 | > 15 | > 示例 2: 16 | > 17 | > 输入:[2,2,2,2,2] 18 | > 19 | > 输出:5 20 | > 21 | > 解释:最长递增子序列的长度是 1,并且存在 5 个子序列的长度为 1,因此输出 5。 22 | > 23 | > 提示: 24 | > 25 | > 1 <= nums.length <= 2000 26 | > 27 | > -10^6 <= nums[i] <= 10^6 28 | 29 | 这道题是 `[300] 最长递增子序列` 的升级版,建议理解了该题后再来做这道题。 30 | 31 | 这道题不仅需要我们找出最长递增子序列,并且同时还需要输出形成组成最长子序列的可能个数。 32 | 33 | 这里我们需要建立一个 count[i] 数组,来存储 i 位置下,组成该最长子序列长度 dp[i] 时的可能个数。 34 | 35 | 当发现 dp[i] 与被判断值相等时,需要将当前与被判断位置两种情况的 count 个数相加来更新 count[i] 的值。 36 | 37 | 当 dp[i] 更新时,也需要重置当前位置的 count[i] 与被判断位置相等。 38 | 39 | ```ts 40 | function findNumberOfLIS(nums: number[]): number { 41 | let res = 1; 42 | let max = 1; 43 | const dp = Array(nums.length).fill(1); 44 | const count = Array(nums.length).fill(1); 45 | 46 | for (let i = 1; i < nums.length; i++) { 47 | for (let j = 0; j < i; j++) { 48 | if (nums[i] > nums[j]) { 49 | if (dp[i] < dp[j] + 1) { 50 | dp[i] = dp[j] + 1; 51 | count[i] = count[j]; 52 | } else if (dp[i] === dp[j] + 1) { 53 | count[i] += count[j]; 54 | } 55 | } 56 | } 57 | 58 | if (max < dp[i]) { 59 | max = dp[i]; 60 | res = count[i]; 61 | } else if (max === dp[i]) { 62 | res += count[i]; 63 | } 64 | } 65 | 66 | return res; 67 | } 68 | ``` 69 | -------------------------------------------------------------------------------- /docs/docs/list/dynamic-programming/718.maximum-length-of-repeated-subarray.md: -------------------------------------------------------------------------------- 1 | # [718] 最长重复子数组 2 | 3 | > 给两个整数数组 nums1 和 nums2 ,返回 两个数组中 公共的 、长度最长的子数组的长度 。 4 | > 5 | > 示例 1: 6 | > 7 | > 输入:nums1 = [1,2,3,2,1], nums2 = [3,2,1,4,7] 8 | > 9 | > 输出:3 10 | > 11 | > 解释:长度最长的公共子数组是 [3,2,1] 。 12 | > 13 | > 示例 2: 14 | > 15 | > 输入:nums1 = [0,0,0,0,0], nums2 = [0,0,0,0,0] 16 | > 17 | > 输出:5 18 | > 19 | > 提示: 20 | > 21 | > 1 <= nums1.length, nums2.length <= 1000 22 | > 23 | > 0 <= nums1[i], nums2[i] <= 100 24 | 25 | 这也是一个求最长子序列的系列问题。注意最长子序列与最长子数组之间的区分,最长子数组要求连成的序列是连续的,而子序列是可以跳过子数组中某些元素的。 26 | 27 | 子序列问题,通常是设 dp[i] 为以 nums[i] 结尾的子序列的最值的求值结果。而既然是求子数组,那么通常 dp 数组就仅与 dp[i-1] 相关。若是求子序列,则可能需要遍历从0到i中的dp最大值来求解。 28 | 29 | 对于本题,由于有入参有两个,因此我们应分别对 nums1 与 nums2 进行遍历。判断字符相等时,则dp[i] 应在为加入当前字符前的最长值+1。之后的问题就直接套入公式即可。 30 | 31 | ```ts 32 | function findLength(nums1: number[], nums2: number[]): number { 33 | let res = 0; 34 | // 设 dp[i][j] 为: 以nums1[i-1]结尾的子数组与 nums2[j-1] 结尾的子数组 的最长公共子数组 35 | const dp: number[][] = Array(nums1.length + 1) 36 | .fill(0) 37 | .map(x => Array(nums2.length + 1).fill(0)); 38 | 39 | for (let i = 1; i <= nums1.length; i++) { 40 | for (let j = 1; j <= nums2.length; j++) { 41 | if (nums1[i - 1] === nums2[j - 1]) { 42 | dp[i][j] = dp[i - 1][j - 1] + 1; 43 | res = Math.max(res, dp[i][j]); 44 | } 45 | } 46 | } 47 | return res; 48 | } 49 | ``` 50 | -------------------------------------------------------------------------------- /docs/docs/list/dynamic-programming/836.rectangleOverlap.md: -------------------------------------------------------------------------------- 1 | # [836] 矩形重叠 2 | 3 | > 矩形以列表 [x1, y1, x2, y2] 的形式表示,其中 (x1, y1) 为左下角的坐标,(x2, y2) 是右上角的坐标。 4 | > 5 | > 如果相交的面积为正,则称两矩形重叠。需要明确的是,只在角或边接触的两个矩形不构成重叠。 6 | > 7 | > 给出两个矩形,判断它们是否重叠并返回结果。 8 | > 9 | > 示例 1: 10 | > 11 | > 输入:rec1 = [0,0,2,2], rec2 = [1,1,3,3] 12 | > 13 | > 输出:true 14 | > 15 | > 示例 2: 16 | > 17 | > 输入:rec1 = [0,0,1,1], rec2 = [1,0,2,1] 18 | > 19 | > 输出:false 20 | > 21 | > 提示: 22 | > 23 | > 两个矩形 rec1 和 rec2 都以含有四个整数的列表的形式给出。 24 | > 25 | > 矩形中的所有坐标都处于 -10^9 和 10^9 之间。 26 | > 27 | > x 轴默认指向右,y 轴默认指向上。 28 | > 29 | > 你可以仅考虑矩形是正放的情况。 30 | 31 | 这道题我们可以通过反证的方式来思考。即若两矩形不重合,那么应满足什么条件。 32 | 33 | 当两矩形不重合时,矩形2(x3,y3,x4,y4)对于矩形1(x1,y1,x2,y2)的方位一共有上、下、左、右四种可能。分别来考虑: 34 | 35 | 1. 当矩形2在左时,即矩形2的右边坐标小于矩形1的左边坐标,即x4 <= x1 36 | 2. 当矩形2在右时,即矩形2的左边坐标大于矩形1的右边坐标,即x3 >= x2 37 | 3. 当矩形2在下时,即矩形2的上边坐标小于矩形1的下边坐标,即y4 <= y1 38 | 4. 当矩形2在上时,即矩形2的下边坐标大于矩形1的上边坐标,即y3 >= y2 39 | 40 | 结合四种情况,即可得出,两矩形不重合的条件为:x4 <= x1 || x3 >= x2 || y4 <= y1 || y3 >= y2 41 | 42 | 那么将该条件取反,即可得到两矩形重合的条件了:x4 > x1 && x3 < x2 && y4 > y1 && y3 < y2 43 | 44 | ```js 45 | var isRectangleOverlap = function(rec1, rec2) { 46 | return rec2[2] > rec1[0] && rec2[0] < rec1[2] && rec2[3] > rec1[1] && rec2[1] < rec1[3] 47 | }; 48 | ``` 49 | -------------------------------------------------------------------------------- /docs/docs/list/dynamic-programming/877.stone-game.md: -------------------------------------------------------------------------------- 1 | # [877] 石子游戏 2 | 3 | > Alice 和 Bob 用几堆石子在做游戏。一共有偶数堆石子,排成一行;每堆都有 正 整数颗石子,数目为 piles[i] 。 4 | > 5 | > 游戏以谁手中的石子最多来决出胜负。石子的 总数 是 奇数 ,所以没有平局。 6 | > 7 | > Alice 和 Bob 轮流进行,Alice 先开始 。 每回合,玩家从行的 开始 或 结束 处取走整堆石头。 8 | > 9 | > 这种情况一直持续到没有更多的石子堆为止,此时手中 石子最多 的玩家 获胜 。 10 | > 11 | > 假设 Alice 和 Bob 都发挥出最佳水平,当 Alice 赢得比赛时返回 true ,当 Bob 赢得比赛时返回 false 。 12 | > 13 | > 示例 1: 14 | > 15 | > 输入:piles = [5,3,4,5] 16 | > 17 | > 输出:true 18 | > 19 | > 解释: 20 | > 21 | > Alice 先开始,只能拿前 5 颗或后 5 颗石子 。 22 | > 23 | > 假设他取了前 5 颗,这一行就变成了 [3,4,5] 。 24 | > 25 | > 如果 Bob 拿走前 3 颗,那么剩下的是 [4,5],Alice 拿走后 5 颗赢得 10 分。 26 | > 27 | > 如果 Bob 拿走后 5 颗,那么剩下的是 [3,4],Alice 拿走后 4 颗赢得 9 分。 28 | > 29 | > 这表明,取前 5 颗石子对 Alice 来说是一个胜利的举动,所以返回 true 。 30 | > 31 | > 示例 2: 32 | > 33 | > 输入:piles = [3,7,2,3] 34 | > 35 | > 输出:true 36 | > 37 | > 提示: 38 | > 39 | > 2 <= piles.length <= 500 40 | > 41 | > piles.length 是 偶数 42 | > 43 | > 1 <= piles[i] <= 500 44 | > 45 | > sum(piles[i]) 是 奇数 46 | 47 | 博弈问题,先手必胜。 48 | 49 | 必胜法则为:首先判断数组中奇数位置的石头数和较大还是偶数位置的石头数和较大。每一次选择的两头位置必定为一奇一偶。只要保持不断对较大的那个位置的选择,则必胜。 50 | 51 | ```ts 52 | function stoneGame(piles: number[]): boolean { 53 | return true; 54 | }; 55 | ``` 56 | -------------------------------------------------------------------------------- /docs/docs/list/dynamic-programming/91.decodeWays.md: -------------------------------------------------------------------------------- 1 | # [91] 解码方法 2 | 3 | > 一条包含字母 A-Z 的消息通过以下方式进行了编码: 4 | > 5 | > 'A' -> 1 6 | > 7 | > 'B' -> 2 8 | > 9 | > ... 10 | > 11 | > 'Z' -> 26 12 | > 13 | > 给定一个只包含数字的非空字符串,请计算解码方法的总数。 14 | > 15 | > 示例 1: 16 | > 17 | > 输入: "12" 18 | > 19 | > 输出: 2 20 | > 21 | > 解释: 它可以解码为 "AB"(1 2)或者 "L"(12)。 22 | > 23 | > 示例 2: 24 | > 25 | > 输入: "226" 26 | > 27 | > 输出: 3 28 | > 29 | > 解释: 它可以解码为 "BZ" (2 26), "VF" (22 6), 或者 "BBF" (2 2 6) 。 30 | 31 | 状态转移方程: dp[n] = (1<=single<=9: dp[n - 1]) + (10<=double<=26: dp[n - 2]) 32 | 33 | 动态规划问题,从后往前倒退,若最后一个数字为1-9,那么它的情况与dp[n-1]的情况一样多,排列为每种情况在最后面加上最后一个数字即可,同理,若最后两个数字为10-26,它的情况与dp[n-2]一样多,最后的结果即为两者相加的和。 34 | 35 | ```js 36 | var numDecodings = function(s) { 37 | if (!s || s.length <= 0) return 0; 38 | const dp = new Array(s.length + 1).fill(0); 39 | dp[0] = 1; 40 | dp[1] = s[0] === "0" ? 0 : 1; 41 | for (let i = 2; i <= s.length; i++) { 42 | const single = +s.slice(i - 1, i); 43 | const double = +s.slice(i - 2, i); 44 | if (single >= 1 && single <= 9) { 45 | dp[i] += dp[i - 1]; 46 | } 47 | if (double >= 10 && double <= 26) { 48 | dp[i] += dp[i - 2]; 49 | } 50 | } 51 | return dp[s.length]; 52 | }; 53 | ``` 54 | -------------------------------------------------------------------------------- /docs/docs/list/dynamic-programming/96.uniqueBinarySearchTrees.md: -------------------------------------------------------------------------------- 1 | # [96] 不同的二叉搜索树 2 | 3 | > 给定一个整数 n,求以 1 ... n 为节点组成的二叉搜索树有多少种? 4 | > 5 | > 示例: 6 | > 7 | > 输入: 3 8 | > 9 | > 输出: 5 10 | > 11 | > 解释: 12 | > 13 | > 给定 n = 3, 一共有 5 种不同结构的二叉搜索树 14 | 15 | 这道题请与`95. 不同的二叉搜索树 II`配合食用,这道题需要求对按数字顺序1~n组成的树进行先序遍历,求所有可能的个数。那么我们可以建立一个一维数组dp,考虑当每个数字为根节点时,所有可行的方法个数。 16 | 17 | 这实际上是求卡特兰数,不知道的同学可自行百度。根节点的组成个数为左右子树的组成个数相乘。 18 | 19 | 对于每一个节点i,考虑所有左右子树分配节点个数可能的组成情况,设左子树节点个数为j,那么右子树节点个数为i-1-j,将左右子树的组成个数相乘,将所有情况相加,即可得到dp[i]的值。 20 | 21 | 状态转移方程为:dp[i] = dp[i-1] * dp[n-i]。 22 | 23 | 确定边界情况,n=0时,只有可能是空树;n=1时,也只有一种可能。 24 | 25 | ```js 26 | var numTrees = function(n) { 27 | if (n === 0 || n === 1) return 1; 28 | const dp = new Array(n + 1).fill(0); 29 | dp[0] = 1; 30 | dp[1] = 1; 31 | for (let i = 2; i <= n; i++) { 32 | for (let j = 0; j < i; j++) { 33 | dp[i] += dp[j] * dp[i - j - 1]; 34 | } 35 | } 36 | return dp[n]; 37 | }; 38 | ``` 39 | -------------------------------------------------------------------------------- /docs/docs/list/graph/797.all-paths-from-source-to-target.md: -------------------------------------------------------------------------------- 1 | # [797] 所有可能的路径 2 | 3 | > 给你一个有 n 个节点的 有向无环图(DAG),请你找出所有从节点 0 到节点 n-1 的路径并输出(不要求按特定顺序) 4 | > 5 | > graph[i] 是一个从节点 i 可以访问的所有节点的列表(即从节点 i 到节点 graph[i][j] 存在一条有向边)。 6 | > 7 | > 示例 1: 8 | > 9 | > 输入:graph = [[1,2],[3],[3],[]] 10 | > 11 | > 输出:[[0,1,3],[0,2,3]] 12 | > 13 | > 解释:有两条路径 0 -> 1 -> 3 和 0 -> 2 -> 3 14 | > 15 | > 示例 2: 16 | > 17 | > 输入:graph = [[4,3,1],[3,2,4],[3],[4],[]] 18 | > 19 | > 输出:[[0,4],[0,3,4],[0,1,3,4],[0,1,2,3,4],[0,1,4]] 20 | > 21 | > 提示: 22 | > 23 | > n == graph.length 24 | > 25 | > 2 <= n <= 15 26 | > 27 | > 0 <= graph[i][j] < n 28 | > 29 | > graph[i][j] != i(即不存在自环) 30 | > 31 | > graph[i] 中的所有元素 互不相同 32 | > 33 | > 保证输入为 有向无环图(DAG) 34 | 35 | 这道题是一道经典的有向无环图的遍历算法。 36 | 37 | 详细解题思路可以参考`图算法`专题。 38 | 39 | ```ts 40 | function allPathsSourceTarget(graph: number[][]): number[][] { 41 | // 入参 graph 直接就是邻接表了 hh 42 | 43 | // 结果数组 44 | const res: number[][] = []; 45 | // 记录已遍历过的路径 46 | const path: number[] = []; 47 | 48 | const len = graph.length; 49 | traverse(0); 50 | 51 | return res; 52 | 53 | function traverse(index: number) { 54 | // 添加当前节点到路径中 55 | path.push(index); 56 | 57 | // 遍历到达终点 58 | if (index === len - 1) { 59 | // 拷贝一份当前路径,将其添加到结果数组中 60 | res.push([...path]); 61 | // 从路径中移除当前节点 62 | path.pop(); 63 | return; 64 | } 65 | 66 | // 递归所有相邻节点 67 | for (const adjacent of graph[index]) { 68 | traverse(adjacent); 69 | } 70 | 71 | // 从路径中移除当前节点,相当于回溯法中的撤销选择 72 | path.pop(); 73 | } 74 | } 75 | ``` 76 | -------------------------------------------------------------------------------- /docs/docs/list/greedy/122.bestTimeToBuyAndSellStockIi.md: -------------------------------------------------------------------------------- 1 | # [122] 买卖股票的最佳时机 II 2 | 3 | >给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。 4 | > 5 | >设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。 6 | > 7 | >注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。 8 | > 9 | >示例 1: 10 | > 11 | >输入: [7,1,5,3,6,4] 12 | > 13 | >输出: 7 14 | > 15 | >解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。 16 | > 17 | >随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。 18 | > 19 | >示例 2: 20 | > 21 | >输入: [1,2,3,4,5] 22 | > 23 | >输出: 4 24 | > 25 | >解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。 26 | > 27 | >注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。 28 | > 29 | >示例 3: 30 | > 31 | >输入: [7,6,4,3,1] 32 | > 33 | >输出: 0 34 | > 35 | >解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。 36 | 37 | 这道题与 `[121] Best Time to Buy and Sell Stock` 不一样,它可以支持进行任意多笔交易。那么很明显,只要有上涨趋势的时间都应该进行一次买入卖出操作,因此这是一道考察贪心算法的问题。 38 | 39 | 使用一个变量记录前一天的股票价格,将其与当天价格作比较,若小于当前价格则认为有盈利空间,执行买入与卖出操作,总盈利要加上这两天的价格差。遍历完数组后返回最终的总盈利即可。 40 | 41 | ```ts 42 | function maxProfit(prices: number[]): number { 43 | const len = prices.length; 44 | 45 | let prev = prices[0]; 46 | let profit = 0; 47 | for (let i = 1; i < len; i++) { 48 | // 贪心算法,每一天只要比前一天价格高,就直接买卖,获取利润 49 | if (prices[i] > prev) { 50 | profit += prices[i] - prev; 51 | } 52 | prev = prices[i]; 53 | } 54 | 55 | return profit; 56 | } 57 | ``` 58 | -------------------------------------------------------------------------------- /docs/docs/list/greedy/406.queueReconstructionByHeight.md: -------------------------------------------------------------------------------- 1 | # [406] 根据身高重建队列 2 | 3 | > 假设有打乱顺序的一群人站成一个队列。 每个人由一个整数对(h, k)表示,其中h是这个人的身高,k是排在这个人前面且身高大于或等于h的人数。编写一个算法来重建这个队列。 4 | > 5 | > 注意:总人数少于1100人。 6 | > 7 | > 示例 8 | > 9 | > 输入: 10 | > 11 | > [[7,0], [4,4], [7,1], [5,0], [6,1], [5,2]] 12 | > 13 | > 输出: 14 | > 15 | > [[5,0], [7,0], [5,2], [6,1], [4,4], [7,1]] 16 | 17 | 这道题解法比较简单,关键是想到这个思路很难。题目需要将每个元素的第一个值height按从小到大排序,但要求元素的第二个值k表示该元素前所有height不小于当前height的元素的最小个数。 18 | 19 | 这里我们可以将数组先按height从大到小排序,若height相同,则按k从小到大排序。这样排序的目的是保证k的值一定不会大于当前的位置(如果题目有解)。这步操作便利了我们接下来的插入操作数组中间不至于存在空元素。 20 | 21 | 之后我们新建另一个数组res,遍历刚刚排序得到的数组,按K值插入到res中。因为height是从大到小排序,因此若遇到k相同的两个元素,后者的应该插入到前者之前。 22 | 23 | ```js 24 | var reconstructQueue = function(people) { 25 | people.sort((a, b) => (a[0] === b[0] ? a[1] - b[1] : b[0] - a[0])); 26 | const res = []; 27 | people.forEach(ele => { 28 | res.splice(ele[1], 0, ele); 29 | }); 30 | return res; 31 | }; 32 | ``` 33 | -------------------------------------------------------------------------------- /docs/docs/list/greedy/435.non-overlapping-intervals.md: -------------------------------------------------------------------------------- 1 | # [435] 无重叠区间 2 | 3 | > 给定一个区间的集合 intervals ,其中 intervals[i] = [starti, endi] 。返回需要移除区间的最小数量,使剩余区间互不重叠 。 4 | > 5 | > 示例 1: 6 | > 7 | > 输入:intervals = [[1,2],[2,3],[3,4],[1,3]] 8 | > 9 | > 输出:1 10 | > 11 | > 解释:移除 [1,3] 后,剩下的区间没有重叠。 12 | > 13 | > 示例 2: 14 | > 15 | > 输入:intervals = [ [1,2], [1,2], [1,2] ] 16 | > 17 | > 输出:2 18 | > 19 | > 解释:你需要移除两个 [1,2] 来使剩下的区间没有重叠。 20 | > 21 | > 示例 3: 22 | > 23 | > 输入:intervals = [ [1,2], [2,3] ] 24 | > 25 | > 输出:0 26 | > 27 | > 解释:你不需要移除任何区间,因为它们已经是无重叠的了。 28 | > 29 | > 提示: 30 | > 31 | > 1 <= intervals.length <= 10^5 32 | > 33 | > intervals[i].length == 2 34 | > 35 | > -5> 10^4 <= starti < endi <= 5> 10^4 36 | 37 | 这是一道区间调度问题。 38 | 39 | 解题步骤分为以下几点: 40 | 41 | 1. 先对区间按照结束位置从小到大排序。并先选择第一个区间,即结束位置最小的区间。 42 | 43 | 2. 向后遍历,若当前区间的起始位置小于上一步中的结束位置,则说明这两个区间相交,将该区间剔除,count++。 44 | 45 | 3. 若不相交,则说明当前区间是一个不重叠的新区间,将选择结束位置移动到当前区间的结束位置。并重复上述步骤直到遍历完成。 46 | 47 | 不难看出,在排序后,这其实是一个贪心问题。通过判断俩区间 end 与 start 的关系,就能知道区间是否相交,从而使每一步都能选择出是否需要剔除当前区间。 48 | 49 | ```ts 50 | function eraseOverlapIntervals(intervals: number[][]): number { 51 | let res = 0; 52 | // 先对 end 进行从小到大排序 53 | intervals.sort((a, b) => a[1] - b[1]); 54 | 55 | let end = intervals[0][1]; 56 | for (let i = 1; i < intervals.length; i++) { 57 | const start = intervals[i][0]; 58 | // 若该区间的起始点小于前一个区间的结束点,则说明该区间有重叠,需要被移除 59 | if (start < end) { 60 | res += 1; 61 | } else { 62 | // 否则找到了一个新的不重叠区间,将 end 指针移动过来 63 | end = intervals[i][1]; 64 | } 65 | } 66 | return res; 67 | } 68 | ``` 69 | -------------------------------------------------------------------------------- /docs/docs/list/hash-table/136.singleNumber.md: -------------------------------------------------------------------------------- 1 | # [136] 只出现一次的数字 2 | 3 | >给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。 4 | > 5 | >说明:你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗? 6 | 7 | 根据题目,我们很直观的想出:可以利用hash表的方式存储第一次得到的值,之后每次查询hash表看是否有相同的值,若有则删除。遍历结束后hash表中剩下的值即为所求。 8 | 9 | 这样做时间复杂度O(n),空间复杂度O(n)。 10 | 11 | ```js 12 | var singleNumber = function(nums) { 13 | const hash = {}; 14 | for (let i = 0; i < nums.length; i++) { 15 | if (hash[nums[i]] === undefined) { 16 | hash[nums[i]] = nums[i]; 17 | } else { 18 | Reflect.deleteProperty(hash, nums[i]); 19 | } 20 | } 21 | return hash[Reflect.ownKeys(hash)[0]]; 22 | }; 23 | ``` 24 | 25 | 当然也可以利用排序后的数组相邻的值若不同则可得唯一性来解,这样的时间复杂度O(nlgn),空间复杂度O(1)。这里就不多说了。 26 | 27 | 但由于题目要求时间复杂度O(n),空间复杂度O(1),因此不能使用排序算法,也不能使用hash-table。 28 | 29 | 本题使用二进制异或的性质来完成:两个数字异或 a^b 是将 a 和 b 的二进制每一位进行运算,如果同一位的数字相同则为0,不同则为1。 30 | 31 | ```js 32 | var singleNumber = function(nums) { 33 | let res = 0; 34 | for (let i = 0; i < nums.length; i++) { 35 | res = res ^ nums[i]; 36 | } 37 | return res; 38 | }; 39 | ``` 40 | -------------------------------------------------------------------------------- /docs/docs/list/hash-table/219.containsDuplicateIi.md: -------------------------------------------------------------------------------- 1 | # [219] 存在重复元素 II 2 | 3 | > 给定一个整数数组和一个整数 k,判断数组中是否存在两个不同的索引 i 和 j,使得 nums [i] = nums [j],并且 i 和 j 的差的 4 | > 5 | > 绝对值 至多为 k。 6 | > 7 | > 示例 1: 8 | > 9 | > 输入: nums = [1,2,3,1], k = 3 10 | > 11 | > 输出: true 12 | > 13 | > 示例 2: 14 | > 15 | > 输入: nums = [1,0,1,1], k = 1 16 | > 17 | > 输出: true 18 | > 19 | > 示例 3: 20 | > 21 | > 输入: nums = [1,2,3,1,2,3], k = 2 22 | > 23 | > 输出: false 24 | 25 | ```js 26 | var containsNearbyDuplicate = function(nums, k) { 27 | const hash = {}; 28 | for (let i = 0; i < nums.length; i++) { 29 | if (hash[nums[i]] !== undefined && i - hash[nums[i]] <= k) { 30 | return true; 31 | } 32 | hash[nums[i]] = i; 33 | } 34 | return false; 35 | }; 36 | ``` 37 | -------------------------------------------------------------------------------- /docs/docs/list/hash-table/3.longestSubstringWithoutRepeatingCharacters.md: -------------------------------------------------------------------------------- 1 | # [3] 无重复字符的最长子串 2 | 3 | >给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。 4 | > 5 | >示例 1: 6 | > 7 | >输入: "abcabcbb" 8 | > 9 | >输出: 3 10 | > 11 | >解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。 12 | > 13 | >示例 2: 14 | > 15 | >输入: "bbbbb" 16 | > 17 | >输出: 1 18 | > 19 | >解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。 20 | > 21 | >示例 3: 22 | > 23 | >输入: "pwwkew" 24 | > 25 | >输出: 3 26 | > 27 | >解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。 28 | > 29 | >请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。 30 | 31 | 这道题我们很容易想到暴力法,利用快慢指针,遍历字符串时,每次都检查当前字符是否在快慢指针之间的子串中存在,这里的快慢指针实际上形成了一个滑动窗口。 32 | 33 | 至于如何检查某个字符在子串中是否存在,我们可以直接遍历,也可以利用hash表进行优化。 34 | 35 | 1. 若不存在该字符,则直接加上,值为当前字符的位置。 36 | 2. 若存在该字符,则比对字符是否在滑动窗口内,若在,则说明当前子串存在重复字符。将慢指针移动到子串中重复字符的后一个位置以将重复字符剔除出滑动窗口,并继续向后遍历。 37 | 38 | 我们另外需要一个变量来记录遍历过程中的最长子串长度。遍历时,若当前子串长度长于记录值,则更新记录值即可。 39 | 40 | ```js 41 | var lengthOfLongestSubstring = function(s) { 42 | const len = s.length; 43 | let res = 0; 44 | const hash = {}; 45 | let start = 0; 46 | for (let i = 0; i < len; i++) { 47 | if (hash[s[i]] !== undefined && hash[s[i]] >= start) { 48 | start = hash[s[i]] + 1; 49 | } 50 | hash[s[i]] = i; 51 | if (i - start + 1 > res) { 52 | res = i - start + 1; 53 | } 54 | } 55 | return res; 56 | }; 57 | ``` 58 | -------------------------------------------------------------------------------- /docs/docs/list/hash-table/349.intersectionOfTwoArrays.md: -------------------------------------------------------------------------------- 1 | # [349] 两个数组的交集 2 | 3 | > 给定两个数组,编写一个函数来计算它们的交集。 4 | > 5 | > 示例 1: 6 | > 7 | > 输入: nums1 = [1,2,2,1], nums2 = [2,2] 8 | > 9 | > 输出: [2] 10 | > 11 | > 示例 2: 12 | > 13 | > 输入: nums1 = [4,9,5], nums2 = [9,4,9,8,4] 14 | > 15 | > 输出: [9,4] 16 | > 17 | > 说明: 18 | > 19 | > 输出结果中的每个元素一定是唯一的。 20 | > 21 | > 我们可以不考虑输出结果的顺序。 22 | 23 | ```js 24 | var intersection = function(nums1, nums2) { 25 | const set2 = new Set(nums2); 26 | return Array.from(new Set(nums1.filter(ele => set2.has(ele)))); 27 | }; 28 | ``` 29 | 30 | 利用Set的特性可以很轻松的实现数组的交集、并集、补集。 31 | 32 | 并集 33 | 34 | ```js 35 | var union = function(nums1, nums2) { 36 | return Array.from(new Set([...nums1, ...nums2])); 37 | }; 38 | ``` 39 | 40 | 补集 41 | 42 | ```js 43 | var complement = function(nums1, nums2) { 44 | const set2 = new Set(nums2); 45 | return Array.from(new Set(nums1.filter(ele => !set2.has(ele)))); 46 | }; 47 | ``` 48 | -------------------------------------------------------------------------------- /docs/docs/list/hash-table/463.islandPerimeter.md: -------------------------------------------------------------------------------- 1 | # [463] 岛屿的周长 2 | 3 | > 给定一个包含 0 和 1 的二维网格地图,其中 1 表示陆地 0 表示水域。 4 | > 5 | > 网格中的格子水平和垂直方向相连(对角线方向不相连)。整个网格被水完全包围,但其中恰好有一个岛屿(或者说,一个或多个表示陆地的格子相连组成的岛屿)。 6 | > 7 | > 岛屿中没有“湖”(“湖” 指水域在岛屿内部且不和岛屿周围的水相连)。格子是边长为 1 的正方形。网格为长方形,且宽度和高度均不超过 100。计算这个岛屿的周长。 8 | > 9 | > 示例 : 10 | > 11 | > 输入: 12 | > 13 | > [[0,1,0,0], 14 | > 15 | > ⁠[1,1,1,0], 16 | > 17 | > ⁠[0,1,0,0], 18 | > 19 | > ⁠[1,1,0,0]] 20 | > 21 | > 输出: 16 22 | 23 | 这题挺简单的,遍历二维数组每个值,分别判断其上下左右是否存在为1的值,若存在则说明该块的该条边在多边形内部,不计入该条边,若为0则将该条边长计入,遍历完成返回所有边长之和即可。 24 | 25 | ```js 26 | var islandPerimeter = function(grid) { 27 | let res = 0; 28 | const height = grid.length; 29 | const width = grid[0].length; 30 | for (let i = 0; i < height; i++) { 31 | for (let j = 0; j < width; j++) { 32 | if (grid[i][j] === 1) { 33 | let temp = 4; 34 | if (i > 0 && grid[i - 1][j] === 1) temp--; 35 | if (i < height - 1 && grid[i + 1][j] === 1) temp--; 36 | if (j > 0 && grid[i][j - 1] === 1) temp--; 37 | if (j < width - 1 && grid[i][j + 1] === 1) temp--; 38 | res += temp; 39 | } 40 | } 41 | } 42 | return res; 43 | }; 44 | ``` 45 | -------------------------------------------------------------------------------- /docs/docs/list/hash-table/575.distributeCandies.md: -------------------------------------------------------------------------------- 1 | # [575] 分糖果 2 | 3 | > 给定一个偶数长度的数组,其中不同的数字代表着不同种类的糖果,每一个数字代表一个糖果。你需要把这些糖果平均分给一个弟弟和一个妹妹。返回妹妹可以获得的最大糖果的种类数。 4 | > 5 | > 示例 1: 6 | > 7 | > 输入: candies = [1,1,2,2,3,3] 8 | > 9 | > 输出: 3 10 | > 11 | > 解析: 一共有三种种类的糖果,每一种都有两个。最优分配方案:妹妹获得[1,2,3],弟弟也获得[1,2,3]。这样使妹妹获得糖果的种类数最多。 12 | > 13 | > 示例 2 : 14 | > 15 | > 输入: candies = [1,1,2,3] 16 | > 17 | > 输出: 2 18 | > 19 | > 解析: 妹妹获得糖果[2,3],弟弟获得糖果[1,1],妹妹有两种不同的糖果,弟弟只有一种。这样使得妹妹可以获得的糖果种类数最多。 20 | > 21 | > 注意: 22 | > 23 | > 数组的长度为[2, 10,000],并且确定为偶数。 24 | > 25 | > 数组中数字的大小在范围[-100,000, 100,000]内。 26 | 27 | ```js 28 | var distributeCandies = function(candies) { 29 | const kinds = new Set(candies).size; 30 | const num = candies.length / 2; 31 | return Math.min(kinds, num); 32 | }; 33 | ``` 34 | -------------------------------------------------------------------------------- /docs/docs/list/linked-list/2.addTwoNumbers.md: -------------------------------------------------------------------------------- 1 | # [2] 两数相加 2 | 3 | >给出两个 非空 的链表用来表示两个非负的整数。其中,它们各自的位数是按照 逆序 的方式存储的,并且它们的每个节点只能存储 一位 数字。 4 | > 5 | >如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。 6 | > 7 | >您可以假设除了数字 0 之外,这两个数都不会以 0 开头。 8 | > 9 | > 示例: 10 | > 11 | > 输入:(2 -> 4 -> 3) + (5 -> 6 -> 4) 12 | > 13 | > 输出:7 -> 0 -> 8 14 | > 15 | > 原因:342 + 465 = 807 16 | 17 | 链表的相加问题,需要注意的有两点: 18 | 19 | 1. 设立头结点dummy,作为链表的起始节点,这样方便统一第一次和之后的每次的插入行为。 20 | 2. 注意进位问题,包括在有两个链表的值相加时、两链表长度不同时只有一个链表有值时、最后的运算有进位的情况都要考虑到。 21 | 22 | ```js 23 | var addTwoNumbers = function(l1, l2) { 24 | const dummy = new ListNode(null); 25 | let cur = dummy; 26 | let carry = 0; 27 | while (l1 || l2) { 28 | const val1 = l1 ? l1.val : 0; 29 | const val2 = l2 ? l2.val : 0; 30 | let answer = val1 + val2 + carry; 31 | carry = answer > 9 ? 1 : 0; 32 | cur.next = new ListNode(answer % 10); 33 | if (l1) l1 = l1.next; 34 | if (l2) l2 = l2.next; 35 | cur = cur.next; 36 | } 37 | 38 | if (carry === 1) cur.next = new ListNode(1); 39 | return dummy.next; 40 | }; 41 | 42 | function ListNode(val) { 43 | this.val = val; 44 | this.next = null; 45 | } 46 | ``` 47 | -------------------------------------------------------------------------------- /docs/docs/list/linked-list/203.removeLinkedListElements.md: -------------------------------------------------------------------------------- 1 | # [203] 移除链表元素 2 | 3 | > 删除链表中等于给定值 val 的所有节点。 4 | > 5 | > 示例: 6 | > 7 | > 输入: 1->2->6->3->4->5->6, val = 6 8 | > 9 | > 输出: 1->2->3->4->5 10 | 11 | 注意头结点也是可能被删去的节点,因此需要补充建立一个虚拟的dummy节点,next指向head 12 | 13 | ```js 14 | var removeElements = function(head, val) { 15 | const dummy = { 16 | next: head 17 | }; 18 | let cur = dummy; 19 | while (cur.next) { 20 | if (cur.next.val === val) { 21 | const newNext = cur.next.next || null; 22 | cur.next = newNext; 23 | } else { 24 | cur = cur.next; 25 | } 26 | } 27 | return dummy.next; 28 | }; 29 | 30 | function ListNode(val) { 31 | this.val = val; 32 | this.next = null; 33 | } 34 | ``` 35 | -------------------------------------------------------------------------------- /docs/docs/list/linked-list/206.reverseLinkedList.md: -------------------------------------------------------------------------------- 1 | # [206] 反转链表 2 | 3 | > 反转一个单链表。 4 | > 5 | > 示例: 6 | > 7 | > 输入: 1->2->3->4->5->NULL 8 | > 9 | > 输出: 5->4->3->2->1->NULL 10 | > 11 | > 进阶: 12 | > 13 | > 你可以迭代或递归地反转链表。你能否用两种方法解决这道题? 14 | 15 | 这道题是一道非常典型的双指针使用场景。既然为反转链表,因此只需要将每个节点的next指针指向前一个节点即可,因此我们需要两个指针分别记录当前节点与前一个节点,交换当前节点指向后,前后指针依次向前遍历即可。循环时需要注意,后一个节点是需要优先记录的,这样才能够进入下一次循环前找到下一个节点的引用。 16 | 17 | ```js 18 | var reverseList = function(head) { 19 | let pre = null; 20 | let cur = head; 21 | while (cur) { 22 | const next = cur.next; 23 | cur.next = pre; 24 | pre = cur; 25 | cur = next; 26 | } 27 | return pre; 28 | }; 29 | ``` 30 | -------------------------------------------------------------------------------- /docs/docs/list/linked-list/21.mergeTwoSortedLists.md: -------------------------------------------------------------------------------- 1 | # [21] 合并两个有序链表 2 | 3 | >将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 4 | > 5 | >示例: 6 | > 7 | >输入:1->2->4, 1->3->4 8 | > 9 | >输出:1->1->2->3->4->4 10 | 11 | 这道题实际上就是一次归并排序的思想,两个有序子数组合并为一个有序数组,只不过形式换成了链表。 12 | 13 | 非常简单,注意新链表创建头结点即可。 14 | 15 | ```js 16 | function ListNode(val) { 17 | this.val = val; 18 | this.next = null; 19 | } 20 | 21 | var mergeTwoLists = function(l1, l2) { 22 | const dummy = new ListNode(); 23 | let cur = dummy; 24 | while (l1 && l2) { 25 | const node = new ListNode(); 26 | if (l1.val < l2.val) { 27 | node.val = l1.val; 28 | l1 = l1.next; 29 | } else { 30 | node.val = l2.val; 31 | l2 = l2.next; 32 | } 33 | cur.next = node; 34 | cur = cur.next; 35 | } 36 | if (l1) { 37 | cur.next = l1; 38 | } 39 | if (l2) { 40 | cur.next = l2; 41 | } 42 | return dummy.next; 43 | }; 44 | ``` 45 | -------------------------------------------------------------------------------- /docs/docs/list/linked-list/237.deleteNodeInALinkedList.md: -------------------------------------------------------------------------------- 1 | # [237] 删除链表中的节点 2 | 3 | > 请编写一个函数,使其可以删除某个链表中给定的(非末尾)节点,你将只被给定要求被删除的节点。 4 | > 5 | > 现有一个链表 -- head = [4,5,1,9],它可以表示为: 6 | > 7 | > 示例 1: 8 | > 9 | > 输入: head = [4,5,1,9], node = 5 10 | > 11 | > 输出: [4,1,9] 12 | > 13 | > 解释: 给定你链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9. 14 | > 15 | > 示例 2: 16 | > 17 | > 输入: head = [4,5,1,9], node = 1 18 | > 19 | > 输出: [4,5,9] 20 | > 21 | > 解释: 给定你链表中值为 1 的第三个节点,那么在调用了你的函数之后,该链表应变为 4 -> 5 -> 9. 22 | > 23 | > 说明: 24 | > 25 | > 链表至少包含两个节点。 26 | > 27 | > 链表中所有节点的值都是唯一的。 28 | > 29 | > 给定的节点为非末尾节点并且一定是链表中的一个有效节点。 30 | > 31 | > 不要从你的函数中返回任何结果。 32 | 33 | 这道题出的很烂且解法没啥参考意义,题目描述与实际操作完全无关,直接将当前节点替换成下一个节点即可AC。 34 | 35 | ```js 36 | var deleteNode = function(node) { 37 | node.val = node.next.val; 38 | node.next = node.next.next 39 | }; 40 | ``` 41 | -------------------------------------------------------------------------------- /docs/docs/list/linked-list/24.swapNodesInPairs.md: -------------------------------------------------------------------------------- 1 | # [24] 两两交换链表中的节点 2 | 3 | >给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。 4 | > 5 | >你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。 6 | > 7 | >示例: 8 | > 9 | >给定 1->2->3->4, 你应该返回 2->1->4->3. 10 | 11 | 对链表交换相邻节点的问题,我们还是按照普通对链表遍历的处理即可(即先加头结点遍历直到节点判空结束)。 12 | 13 | 因为是节点交换,因此需要交换的两个节点都必须有值,因此,当cur或者next任何一个没有值就可以结束循环了。 14 | 15 | ```js 16 | var swapPairs = function(head) { 17 | const dummy = new ListNode(null); 18 | dummy.next = head; 19 | let pre = dummy; 20 | while (pre.next && pre.next.next) { 21 | const cur = pre.next; 22 | const next = cur.next; 23 | 24 | pre.next = next; 25 | cur.next = next.next; 26 | next.next = cur; 27 | 28 | pre = cur; 29 | } 30 | return dummy.next; 31 | }; 32 | 33 | function ListNode(val) { 34 | this.val = val; 35 | this.next = null; 36 | } 37 | ``` 38 | -------------------------------------------------------------------------------- /docs/docs/list/linked-list/82.remove-duplicates-from-sorted-list-ii.md: -------------------------------------------------------------------------------- 1 | # [82] 删除排序链表中的重复元素 II 2 | 3 | > 存在一个按升序排列的链表,给你这个链表的头节点 head ,请你删除链表中所有存在数字重复情况的节点,只保留原始链表中 没有重复出现 的数字。 4 | > 5 | > 返回同样按升序排列的结果链表。 6 | > 7 | > 示例 1: 8 | > 9 | > 输入:head = [1,2,3,3,4,4,5] 10 | > 11 | > 输出:[1,2,5] 12 | > 13 | > 示例 2: 14 | > 15 | > 输入:head = [1,1,1,2,3] 16 | > 17 | > 输出:[2,3] 18 | > 19 | > 提示: 20 | > 21 | > 题目数据保证链表已经按升序排列 22 | 23 | 注意点在于,对于重复元素,我们要一个不留的全部删除,因此必须保留一个每个节点的前一个节点的指针 prev,方便通过 prev.next 删除所有元素节点。 24 | 25 | ```ts 26 | function deleteDuplicates(head: ListNode | null): ListNode | null { 27 | // 如果链表长度小于等于 1,则不可能有重复元素 28 | if (!head || !head.next) return head; 29 | 30 | let dummy = new ListNode(); 31 | dummy.next = head; 32 | 33 | let prev = dummy; 34 | let curr = head; 35 | while (curr && curr.next) { 36 | if (curr.val === curr.next.val) { 37 | const sameValue = curr.val; 38 | // 如果有重复元素,则找出所有相同 val 的节点 39 | while (curr && curr.val === sameValue) { 40 | curr = curr.next; 41 | } 42 | // 删除之间的所有节点 43 | prev.next = curr; 44 | } else { 45 | // 否则,继续往下遍历 46 | prev = curr; 47 | curr = curr.next; 48 | } 49 | } 50 | return dummy.next; 51 | } 52 | ``` 53 | -------------------------------------------------------------------------------- /docs/docs/list/linked-list/92.reverseLinkedListIi.md: -------------------------------------------------------------------------------- 1 | # [92] 反转链表 II 2 | 3 | > 反转从位置 m 到 n 的链表。请使用一趟扫描完成反转。 4 | > 5 | > 说明: 6 | > 7 | > 1 ≤ m ≤ n ≤ 链表长度。 8 | > 9 | > 示例: 10 | > 11 | > 输入: 1->2->3->4->5->NULL, m = 2, n = 4 12 | > 13 | > 输出: 1->4->3->2->5->NULL 14 | 15 | 这道题与 `206.反转链表` 可以合并在一起看,实际上就是206题的加难版。 16 | 17 | 核心问题不变,就是翻转链表的问题。解题方法可以参考206题。 18 | 19 | 本题多出来的难点在于,在翻转完m~n之间的链表后,怎样将翻转后的头尾节点与原来的链表联系上。这里就是一个逻辑问题了,先记录下翻转链表之前和之后的节点`beforeStart`与`afterEnd`,翻转完后在连上头尾即可。 20 | 21 | ```js 22 | var reverseBetween = function(head, m, n) { 23 | if (m === n) return head; 24 | const dummy = new ListNode(null); 25 | dummy.next = head; 26 | 27 | let beforeStart = dummy; 28 | let cnt = 1; 29 | while (cnt < m) { 30 | beforeStart = beforeStart.next; 31 | cnt++; 32 | } 33 | let end = beforeStart; 34 | while (cnt <= n) { 35 | end = end.next; 36 | cnt++; 37 | } 38 | const start = beforeStart.next; 39 | const afterEnd = end.next; 40 | 41 | let pre = start; 42 | let cur = pre.next; 43 | while (cur !== end) { 44 | const next = cur.next; 45 | cur.next = pre; 46 | pre = cur; 47 | cur = next; 48 | } 49 | 50 | start.next = afterEnd; 51 | beforeStart.next = end; 52 | cur.next = pre; 53 | 54 | return dummy.next; 55 | }; 56 | 57 | function ListNode(val) { 58 | this.val = val; 59 | this.next = null; 60 | } 61 | ``` 62 | -------------------------------------------------------------------------------- /docs/docs/list/math/172.factorialTrailingZeroes.md: -------------------------------------------------------------------------------- 1 | # [172] 阶乘后的零 2 | 3 | > 给定一个整数 n,返回 n! 结果尾数中零的数量。 4 | > 5 | > 示例 1: 6 | > 7 | > 输入: 3 8 | > 9 | > 输出: 0 10 | > 11 | > 解释: 3! = 6, 尾数中没有零。 12 | > 13 | > 示例 2: 14 | > 15 | > 输入: 5 16 | > 17 | > 输出: 1 18 | > 19 | > 解释: 5! = 120, 尾数中有 1 个零. 20 | > 21 | > 说明: 你算法的时间复杂度应为 O(log n) 。 22 | 23 | 由于题目要求在O(lgn)的时间下解决,因此无法用O(n)的暴力求解完成。 24 | 25 | 分析后得出,个位数只有2*5能够得出末尾0,有多少对2和5就有多少个0,而任何数因数分解5的个数永远小于2的个数, 26 | 27 | 因此题目转化为:求解目标数字因数分解后共含有多少个5。 28 | 29 | f(n) = n/5 + n/5^2 + n/5^3 + n/5^4 + n/5^5 + ... + 余数 30 | 31 | ```js 32 | var trailingZeroes = function(n) { 33 | let res = 0; 34 | while (n >= 5) { 35 | n = Math.floor(n / 5); 36 | res += n; 37 | } 38 | return res; 39 | }; 40 | ``` 41 | -------------------------------------------------------------------------------- /docs/docs/list/math/319.bulbSwitcher.md: -------------------------------------------------------------------------------- 1 | # [319] 灯泡开关 2 | 3 | > 初始时有 n 个灯泡关闭。 第 1 轮,你打开所有的灯泡。 第 2 轮,每两个灯泡你关闭一次。 第 3轮,每三个灯泡切换一次开关(如果关闭则开启,如果开启则关闭)。第 i 轮,每 i 个灯泡切换一次开关。 对于第 n 轮,你只切换最后一个灯泡的开关。 4 | > 5 | > 找出 n 轮后有多少个亮着的灯泡。 6 | > 7 | > 示例: 8 | > 9 | > 输入: 3 10 | > 11 | > 输出: 1 12 | > 13 | > 解释: 14 | > 15 | > 初始时, 灯泡状态 [关闭, 关闭, 关闭]. 16 | > 17 | > 第一轮后, 灯泡状态 [开启, 开启, 开启]. 18 | > 19 | > 第二轮后, 灯泡状态 [开启, 关闭, 开启]. 20 | > 21 | > 第三轮后, 灯泡状态 [开启, 关闭, 关闭]. 22 | > 23 | > 你应该返回 1,因为只有一个灯泡还亮着。 24 | 25 | 这道题与计算机算法无关,是一道考察数学归纳能力的推理题。 26 | 27 | 我们知道,最后判断某一个灯泡为亮着的话,那么它一定经过了奇数次的开关动作。若像题目这样从1-n的间隔依次进行的话,那么题目就可以转换为,如果某个位置的灯泡亮着,那么该位置必定具有奇数个约数。 28 | 29 | 那么我们知道,具有奇数个约数的数字必为完全平方数(约数一定是成对存在的,奇数个约数说明其中有一对约数为同一个数字,即被约数为该约数的完全平方数)。那么题目就又转化为了求1-n中存在多少个完全平方数了。那么答案也就显而易见了,将该数开方向下取整即可。 30 | 31 | ```js 32 | var bulbSwitch = function(n) { 33 | return Math.floor(Math.sqrt(n)); 34 | }; 35 | ``` 36 | -------------------------------------------------------------------------------- /docs/docs/list/math/633.sumOfSquareNumbers.md: -------------------------------------------------------------------------------- 1 | # [633] 平方数之和 2 | 3 | > 给定一个非负整数 c ,你要判断是否存在两个整数 a 和 b,使得 a^2 + b^2 = c。 4 | > 5 | > 示例1: 6 | > 7 | > 输入: 5 8 | > 9 | > 输出: True 10 | > 11 | > 解释: 1 \* 1 + 2 \* 2 = 5 12 | > 13 | > 示例2: 14 | > 15 | > 输入: 3 16 | > 17 | > 输出: False 18 | 19 | ```js 20 | var judgeSquareSum = function(c) { 21 | let lp = 0; 22 | let rp = Math.floor(Math.sqrt(c)); 23 | while (lp <= rp) { 24 | const res = lp * lp + rp * rp; 25 | if (res === c) { 26 | return true; 27 | } else if (res > c) { 28 | rp--; 29 | } else { 30 | lp++; 31 | } 32 | } 33 | return false; 34 | }; 35 | ``` 36 | -------------------------------------------------------------------------------- /docs/docs/list/math/67.addBinary.md: -------------------------------------------------------------------------------- 1 | # [67] 二进制求和 2 | 3 | > 给定两个二进制字符串,返回他们的和(用二进制表示)。 4 | > 5 | > 输入为非空字符串且只包含数字 1 和 0。 6 | > 7 | > 示例 1: 8 | > 9 | > 输入: a = "11", b = "1" 10 | > 11 | > 输出: "100" 12 | > 13 | > 示例 2: 14 | > 15 | > 输入: a = "1010", b = "1011" 16 | > 17 | > 输出: "10101" 18 | 19 | 简单题就不多做解析了。大概思想就是,先补齐两个二进制数,然后从后往前遍历相加,注意二进制进位问题即可。 20 | 21 | ```js 22 | var addBinary = function(a, b) { 23 | let i = a.length - 1; 24 | let j = b.length - 1; 25 | if (i < j) a = '0'.repeat(j - i) + a; 26 | if (i > j) b = '0'.repeat(i - j) + b; 27 | 28 | let res = ''; 29 | let cnt = 0; 30 | 31 | for (k = Math.max(i, j); k >= 0; k--) { 32 | const temp = Number(a[k]) + Number(b[k]) + cnt; 33 | cnt = temp > 1 ? 1 : 0; 34 | res = (temp % 2) + res; 35 | } 36 | if (cnt === 1) res = '1' + res; 37 | return res; 38 | }; 39 | ``` 40 | -------------------------------------------------------------------------------- /docs/docs/list/math/856.scoreOfParentheses.md: -------------------------------------------------------------------------------- 1 | # [856] 括号的分数 2 | 3 | > 给定一个平衡括号字符串 S,按下述规则计算该字符串的分数: 4 | > 5 | > () 得 1 分。 6 | > 7 | > AB 得 A + B 分,其中 A 和 B 是平衡括号字符串。 8 | > 9 | > (A) 得 2 * A 分,其中 A 是平衡括号字符串。 10 | > 11 | > 示例 1: 12 | > 13 | > 输入: "()" 14 | > 15 | > 输出: 1 16 | > 17 | > 示例 2: 18 | > 19 | > 输入: "(())" 20 | > 21 | > 输出: 2 22 | > 23 | > 示例 3: 24 | > 25 | > 输入: "()()" 26 | > 27 | > 输出: 2 28 | > 29 | > 示例 4: 30 | > 31 | > 输入: "(()(()))" 32 | > 33 | > 输出: 6 34 | > 35 | > 提示: 36 | > 37 | > S 是平衡括号字符串,且只含有 ( 和 ) 。 38 | > 39 | > 2 <= S.length <= 50 40 | 41 | 涉及到括号匹配的题一般采用栈来解是比较合适的。 42 | 43 | 这道题中我们可以遍历字符串,若遇到左括号,则把之前的计算结果压入栈,并重置当前计数。遇到右括号时,就能算出与之匹配的括号内整体的分数了。 44 | 45 | 这里分两种情况:若res = 0,则说明上一个符号就是左括号,那么根据规则1,取temp值为1;若res不为0,则说明之前已经有右括号更新了res了,此时应该匹配规则3,取temp值为res * 2。再根据规则2,更新当前计算结果为栈顶值 + temp。 46 | 47 | 由于字符串最后一个字符一定是右括号,那么遍历结束时,整体得分就已经存在了res中了,因此直接返回res即可。 48 | 49 | ```js 50 | var scoreOfParentheses = function(S) { 51 | let res = 0; 52 | const stack = []; 53 | for (let ele of S) { 54 | if (ele === '(') { 55 | stack.push(res); 56 | res = 0; 57 | } else { 58 | const temp = res === 0 ? 1 : res * 2; 59 | res = stack.pop() + temp; 60 | } 61 | } 62 | return res; 63 | }; 64 | ``` 65 | -------------------------------------------------------------------------------- /docs/docs/list/sliding-window/209.minimum-size-subarray-sum.md: -------------------------------------------------------------------------------- 1 | # [209] 长度最小的子数组 2 | 3 | > 给定一个含有 n 个正整数的数组和一个正整数 target 。 4 | > 5 | > 找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl+1, ..., numsr-1, numsr],并返回其长度。如果不存在符合条件的子数组,返回 0 。 6 | > 7 | > 示例 1: 8 | > 9 | > 输入:target = 7, nums = [2,3,1,2,4,3] 10 | > 11 | > 输出:2 12 | > 13 | > 解释:子数组 [4,3] 是该条件下的长度最小的子数组。 14 | > 15 | > 示例 2: 16 | > 17 | > 输入:target = 4, nums = [1,4,4] 18 | > 19 | > 输出:1 20 | > 21 | > 示例 3: 22 | > 23 | > 输入:target = 11, nums = [1,1,1,1,1,1,1,1] 24 | > 25 | > 输出:0 26 | > 27 | > 提示: 28 | > 29 | > 进阶: 30 | > 31 | > 如果你已经实现 O(n) 时间复杂度的解法, 请尝试设计一个 O(n log(n)) 时间复杂度的解法。 32 | 33 | 这道题是求满足条件的子数组最小长度。对于求子串、子数组的最优解问题,我们首先想到是否能用滑动窗口或是动态规划来解。这道题要求子数组连续,那么其实用滑动窗口就够了。 34 | 35 | 那么就来到了经典的问题填空环节: 36 | 37 | 1. 扩充右边界后,何时能使滑动窗口内的元素满足要求。根据题意,当滑动窗口内元素总和 >= target 时,考虑开始缩小左边界。 38 | 39 | 2. 何时更新返回结果。在本题中,当滑动窗口内元素总和 >= target 时,比对记录值与当前sum结果,保存最小值。 40 | 41 | 这样一套操作下来,整个问题就没有任何难点可言了。 42 | 43 | (吐槽一句,题目是要笑死我吗,实现了O(n)的算法,为啥还要我设计一个O(nlgn)的算法……) 44 | 45 | ```ts 46 | function minSubArrayLen(target: number, nums: number[]): number { 47 | let res: number = nums.length + 1; 48 | let left = 0; 49 | let right = 0; 50 | 51 | let sum = 0; 52 | while (right < nums.length) { 53 | sum += nums[right]; 54 | right += 1; 55 | 56 | while (sum >= target) { 57 | res = Math.min(res, right - left); 58 | sum -= nums[left]; 59 | left++; 60 | } 61 | } 62 | 63 | // 不存在时返回0 64 | return res === nums.length + 1 ? 0 : res; 65 | } 66 | ``` 67 | -------------------------------------------------------------------------------- /docs/docs/list/sliding-window/3.longestSubstringWithoutRepeatingCharacters.md: -------------------------------------------------------------------------------- 1 | # [3] 无重复字符的最长子串 2 | 3 | > 给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。 4 | > 5 | > 示例 1: 6 | > 7 | > 输入: "abcabcbb" 8 | > 9 | > 输出: 3 10 | > 11 | > 解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。 12 | > 13 | > 示例 2: 14 | > 15 | > 输入: "bbbbb" 16 | > 17 | > 输出: 1 18 | > 19 | > 解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。 20 | > 21 | > 示例 3: 22 | > 23 | > 输入: "pwwkew" 24 | > 25 | > 输出: 3 26 | > 27 | > 解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。 28 | > 29 | > 请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。 30 | 31 | 这是一个简化版的滑动窗口题型。解题思路可以参考专题中关于双指针滑动窗口的相关介绍。 32 | 33 | 按照专题中的思路,这里考虑两点: 34 | 35 | 1. 扩充右边界后,何时能使滑动窗口内的元素满足要求。根据题意,当滑动窗口的hash map中出现字符个数大于1的情况时,说明窗口中字串有重复字符,此时考虑开始缩小左边界。 36 | 37 | 2. 何时更新返回结果。在本题中,当滑动窗口中所有元素个数都为1,则可以认为当前子串为无重复字符串,此时可以比对并更新结果。 38 | 39 | ```ts 40 | function lengthOfLongestSubstring(s: string): number { 41 | const window: Record = {}; 42 | let res = 0; 43 | 44 | let left = 0; 45 | let right = 0; 46 | while (right < s.length) { 47 | // 扩大右边界 48 | const ch = s[right]; 49 | right++; 50 | 51 | // 更新滑动窗口元素 52 | window[ch] = window[ch] ? window[ch] + 1 : 1; 53 | 54 | // 当滑动窗口中该字符个数大于1,此时字串不合法,需要缩小左边界直到使该字符唯一 55 | while (window[ch] > 1) { 56 | // 缩左边界 57 | const dropCh = s[left]; 58 | left++; 59 | 60 | // 更新滑动窗口元素 61 | window[dropCh] -= 1; 62 | } 63 | 64 | // 更新合法情况的结果 65 | res = Math.max(res, right - left); 66 | } 67 | return res; 68 | } 69 | ``` 70 | -------------------------------------------------------------------------------- /docs/docs/list/sliding-window/713.subarray-product-less-than-k.md: -------------------------------------------------------------------------------- 1 | # [713] 乘积小于 K 的子数组 2 | 3 | > 给定一个正整数数组 nums 和整数 k 。 4 | > 5 | > 请找出该数组内乘积小于 k 的连续的子数组的个数。 6 | > 7 | > 示例 1: 8 | > 9 | > 输入:nums = [10,5,2,6], k = 100 10 | > 11 | > 输出:8 12 | > 13 | > 解释:8 个乘积小于 100 的子数组分别为:[10], [5], [2], [6], [10,5], [5,2], [2,6], [5,2,6]。 14 | > 15 | > 需要注意的是 [10,5,2] 并不是乘积小于 100 的子数组。 16 | > 17 | > 示例 2: 18 | > 19 | > 输入:nums = [1,2,3], k = 0 20 | > 21 | > 输出:0 22 | 23 | 这道题难点在于想到复杂度小于 O(n2) 的方法来解。需要用到滑动窗口算法,此时复杂度可以做到 O(n)。 24 | 25 | 我们知道,如果一个数组中所有元素的乘积都小于 k,那么其所有连续子数组的乘积一定都小于 k。 26 | 27 | 因此,我们遍历每个元素下标作为右边界,不断缩小左边界,直到乘积满足小于 k。 28 | 29 | 此时该 left 到 right 的数组中所有连续子数组都小于 k,共有 right - left + 1 个(这是由于滑动窗口区间是左闭右闭的,如果是左闭右开区间的话是 right - left 个)。 30 | 31 | 如 [1,2,3] 乘积小于 10,则有 [1]、[1,2]、[1,2,3] 的乘积都小于 10,共 3 个。 32 | 33 | ```ts 34 | function numSubarrayProductLessThanK(nums: number[], k: number): number { 35 | if (k <= 1) return 0; 36 | 37 | let res = 0; 38 | const len = nums.length; 39 | 40 | let temp = 1; 41 | 42 | let left = 0; 43 | let right = 0; 44 | while (right < len) { 45 | temp *= nums[right]; 46 | 47 | while (temp >= k) { 48 | temp /= nums[left]; 49 | left += 1; 50 | } 51 | res += right - left + 1; 52 | right += 1; 53 | } 54 | 55 | return res; 56 | } 57 | ``` 58 | -------------------------------------------------------------------------------- /docs/docs/list/sort/1288.删除被覆盖区间.md: -------------------------------------------------------------------------------- 1 | # [1288] 删除被覆盖区间 2 | 3 | > 给你一个区间列表,请你删除列表中被其他区间所覆盖的区间。 4 | > 5 | > 只有当 c <= a 且 b <= d 时,我们才认为区间 [a,b) 被区间 [c,d) 覆盖。 6 | > 7 | > 在完成所有删除操作后,请你返回列表中剩余区间的数目。 8 | > 9 | > 示例: 10 | > 11 | > 输入:intervals = [[1,4],[3,6],[2,8]] 12 | > 13 | > 输出:2 14 | > 15 | > 解释:区间 [3,6] 被区间 [2,8] 覆盖,所以它被删除了。 16 | > 17 | > 提示:​​​​​​ 18 | > 19 | > 1 <= intervals.length <= 1000 20 | > 21 | > 0 <= intervals[i][0] < intervals[i][1] <= 10^5 22 | > 23 | > 对于所有的 i != j:intervals[i] != intervals[j] 24 | 25 | 这道题也是一道判断区间交并之间关系的题目。 26 | 27 | 解决该类型的问题需要先进行排序。但这道题稍微特殊一点,他的排序规则为:起点升序排列,终点降序排列。当两区间起始相同时,比较区间以范围较大的优先。这是为了解决类似如 [[1, 2],[1, 4]] 这样的列表中[1,2]不被识别为被覆盖区间的问题的。 28 | 29 | 接下来就比较简单了,只需要判断区间的右侧位置即可确定两个区间之间是否存在交集了。 30 | 31 | 若区间右侧位置比前一个区间右侧小的话,说明该区间已经被完全覆盖了,删除计数器+1。否则说明不能被覆盖,则更新比较区间为新区间。 32 | 33 | ```js 34 | var removeCoveredIntervals = function(intervals) { 35 | // 先排序,接下来就只用判断区间右侧位置与新区间之间的关系 36 | // 注意,当两区间起始相同时,比较区间以范围较大的优先,即:起点升序排列,终点降序排列 37 | intervals.sort((a, b) => { 38 | if (a[0] === b[0]) return b[1] - a[1]; 39 | return a[0] - b[0]; 40 | }); 41 | 42 | let res = 0; 43 | 44 | // 先初始化区间的结束位置 45 | // 因为之前的排序新区间起始位置不可能小于 left,因此左侧位置不需要比较 46 | let right = intervals[0][1]; 47 | 48 | for (let i = 1; i < intervals.length; i++) { 49 | const newRight = intervals[i][1]; 50 | if (right >= newRight) { 51 | // 新的区间完全被覆盖了,满足要求 52 | res += 1; 53 | } else { 54 | // 新区间不能被完全覆盖,则更新匹配区间位置 55 | right = newRight; 56 | } 57 | } 58 | return intervals.length - res; 59 | }; 60 | ``` 61 | -------------------------------------------------------------------------------- /docs/docs/list/sort/56.合并区间.md: -------------------------------------------------------------------------------- 1 | # [56] 合并区间 2 | 3 | > 给出一个区间的集合,请合并所有重叠的区间。 4 | > 5 | > 示例 1: 6 | > 7 | > 输入: intervals = [[1,3],[2,6],[8,10],[15,18]] 8 | > 9 | > 输出: [[1,6],[8,10],[15,18]] 10 | > 11 | > 解释: 区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6]. 12 | > 13 | > 示例  2: 14 | > 15 | > 输入: intervals = [[1,4],[4,5]] 16 | > 17 | > 输出: [[1,5]] 18 | > 19 | > 解释: 区间 [1,4] 和 [4,5] 可被视为重叠区间。 20 | > 21 | > 提示: 22 | > 23 | > intervals[i][0] <= intervals[i][1] 24 | 25 | 解区间的题型最重要的就是处理区间之间顺序关系,这能让我们思考逻辑时直接砍掉一半的逻辑节点,对于思考复杂树的难度是指数级下降的。 26 | 27 | 比如对于本题来说,我们只需要让区间的某一边有序(其实起始位置或结束位置有序都行,下面的解法对起始位置排序)。 28 | 29 | 这样一来我们对两个区间取并集时,起始位置就一定会取第一个区间的。之后只需要比较第二个区间的起始位置与第一个区间的结束位置,就能知道两个区间是否相交了。这样是新建一个区间还是扩展一个区间的选择就非常好做了。 30 | 31 | 所以解区间问题的关键点在于优先排序。 32 | 33 | ```ts 34 | function merge(intervals: number[][]): number[][] { 35 | const res: number[][] = []; 36 | intervals.sort((a, b) => a[0] - b[0]); 37 | 38 | for (const interval of intervals) { 39 | const origin = res[res.length - 1]; 40 | if (res.length <= 0 || origin[1] < interval[0]) { 41 | // 不存在交集时直接放入 42 | res.push(interval); 43 | } else { 44 | // 此时存在交集,确认是包含还是交集关系,取代原来的最后一个区间 45 | res[res.length - 1][1] = Math.max(origin[1], interval[1]); 46 | } 47 | } 48 | 49 | return res; 50 | } 51 | ``` 52 | -------------------------------------------------------------------------------- /docs/docs/list/sort/986.区间列表的交集.md: -------------------------------------------------------------------------------- 1 | # [986] 区间列表的交集 2 | 3 | > 给定两个由一些 闭区间 组成的列表,每个区间列表都是成对不相交的,并且已经排序。 4 | > 5 | > 返回这两个区间列表的交集。 6 | > 7 | > (形式上,闭区间 [a, b](其中 a <= b)表示实数 x 的集合,而 a <= x <= b。两个闭区间的交集是一组实数,要么为空集,要么为闭区间。例如,[1, 3] 和 [2, 4] 的交集为 [2, 3]。) 8 | > 9 | > 示例: 10 | > 11 | > 输入:A = [[0,2],[5,10],[13,23],[24,25]], B = [[1,5],[8,12],[15,24],[25,26]] 12 | > 13 | > 输出:[[1,2],[5,5],[8,10],[15,23],[24,24],[25,25]] 14 | > 15 | > 提示: 16 | > 17 | > 0 <= A.length < 1000 18 | > 19 | > 0 <= B.length < 1000 20 | > 21 | > 0 <= A[i].start, A[i].end, B[i].start, B[i].end < 10^9 22 | 23 | 这道题也是一道判断区间交并之间关系的题目。 24 | 25 | 之前说过,解区间问题的关键点在于优先排序,不过题目已经确定有序了,那这步就可以跳过了。 26 | 27 | 接下来是怎样判断两个区间是否存在交集。我们知道两个区间 a=[x1, y1] 与 b=[x2, y2] 不相交的的条件为 x1 > y2 或者 x2 > y1,那么判断相交即对该条件取非即可。 28 | 29 | 接下来是判断什么情况下遍历两个列表的指针需要向前进位。经过简单的实验容易看出,指针进位的规则为:右边界小的那方指针右移。因为该区间中所有位置一定已经被取完了。 30 | 31 | ```js 32 | var intervalIntersection = function(A, B) { 33 | if (A.length <= 0 || B.length <= 0) return []; 34 | 35 | const res = []; 36 | let i = 0; 37 | let j = 0; 38 | 39 | while (i < A.length && j < B.length) { 40 | // 判断两区间是否存在交集 41 | // 不存在交集的情况为:A[i][0] > B[j][1] || B[j][0] > A[i][1]。取非 42 | if (A[i][0] <= B[j][1] && B[j][0] <= A[i][1]) { 43 | // 选取交集 44 | res.push([Math.max(A[i][0], B[j][0]), Math.min(A[i][1], B[j][1])]); 45 | } 46 | // 指针进位规则:右边界小的那方进行移动 47 | if (B[j][1] < A[i][1]) { 48 | j++; 49 | } else { 50 | i++; 51 | } 52 | } 53 | 54 | return res; 55 | }; 56 | ``` 57 | -------------------------------------------------------------------------------- /docs/docs/list/stack/20.validParentheses.md: -------------------------------------------------------------------------------- 1 | # [20] 有效的括号 2 | 3 | >给定一个只包括 '(',')','{','}','\[',']' 的字符串,判断字符串是否有效。 4 | > 5 | >有效字符串需满足: 6 | > 7 | >左括号必须用相同类型的右括号闭合。 8 | > 9 | >左括号必须以正确的顺序闭合。 10 | > 11 | >注意空字符串可被认为是有效字符串。 12 | > 13 | >>示例 1: 14 | >> 15 | >>输入: "()" 16 | >> 17 | >>输出: true 18 | >> 19 | >>示例 2: 20 | >> 21 | >>输入: "()[]{}" 22 | >> 23 | >>输出: true 24 | >> 25 | >>示例 3: 26 | >> 27 | >>输入: "(]" 28 | >> 29 | >>输出: false 30 | >> 31 | >>示例 4: 32 | >> 33 | >>输入: "(\[)]" 34 | >> 35 | >>输出: false 36 | >> 37 | >>示例 5: 38 | >> 39 | >>输入: "{[]}" 40 | >> 41 | >>输出: true 42 | 43 | 这道题是一道经典的符号匹配题,涉及到成对抵消的题我们应当首先考虑使用栈来解决。在这道题里有三对符号匹配且有前后关系(即左括号在右括号之前才合法),因此我们再划分符号为左括号和右括号两部分。当然,如果你认定题目不会给你除了这几个符号之外的字符,也可以用左括号与非左括号划分,这样可以减少两个集合的使用,直接用一个map即可。 44 | 45 | 之后进行遍历,若是左括号,则直接入栈。若是右括号,将之与栈顶元素比较看是否成对,如果不成对或者栈中没有符号则直接返回false,否则将栈顶取出,进入下一次循环。 46 | 47 | 这里还有一个剪枝的小技巧:如果字符串长度为奇数,则不可能是一个有效的字符串,返回false。 48 | 49 | ```ts 50 | function isValid(s: string): boolean { 51 | if (s.length % 2 !== 0) return false; 52 | const stack = []; 53 | const leftMap: Record = { 54 | '(': ')', 55 | '[': ']', 56 | '{': '}' 57 | }; 58 | 59 | for (let i = 0; i < s.length; i++) { 60 | // 若为左括号,则入栈 61 | if (leftMap[s[i]]) { 62 | stack.push(s[i]); 63 | } else { 64 | // 若为右括号,则与栈顶元素匹配看是否为一对 65 | const temp = stack.pop(); 66 | if (leftMap[temp] !== s[i]) return false; 67 | } 68 | } 69 | if (stack.length !== 0) return false; 70 | return true; 71 | } 72 | ``` 73 | -------------------------------------------------------------------------------- /docs/docs/list/stack/225.implementStackUsingQueues.md: -------------------------------------------------------------------------------- 1 | # [225] 用队列实现栈 2 | 3 | > 使用队列实现栈的下列操作: 4 | > 5 | > push(x) -- 元素 x 入栈 6 | > 7 | > pop() -- 移除栈顶元素 8 | > 9 | > top() -- 获取栈顶元素 10 | > 11 | > empty() -- 返回栈是否为空 12 | > 13 | > 注意: 14 | > 15 | > 你只能使用队列的基本操作-- 也就是 push to back, peek/pop from front, size, 和 is empty 16 | > 17 | > 这些操作是合法的。 18 | > 19 | > 你所使用的语言也许不支持队列。 你可以使用 list 或者 deque(双端队列)来模拟一个队列 , 只要是标准的队列操作即可。 20 | > 21 | > 你可以假设所有操作都是有效的(例如, 对一个空的栈不会调用 pop 或者 top 操作)。 22 | 23 | ```js 24 | /** 25 | * Initialize your data structure here. 26 | */ 27 | var MyStack = function() { 28 | this.queue = []; 29 | this.tempQueue = []; 30 | }; 31 | 32 | /** 33 | * Push element x onto stack. 34 | * @param {number} x 35 | * @return {void} 36 | */ 37 | MyStack.prototype.push = function(x) { 38 | this.queue.push(x); 39 | }; 40 | 41 | /** 42 | * Removes the element on top of the stack and returns that element. 43 | * @return {number} 44 | */ 45 | MyStack.prototype.pop = function() { 46 | while (this.queue.length > 1) { 47 | this.tempQueue.push(this.queue.shift()); 48 | } 49 | const res = this.queue.shift(); 50 | this.queue = this.tempQueue; 51 | this.tempQueue = []; 52 | return res; 53 | }; 54 | 55 | /** 56 | * Get the top element. 57 | * @return {number} 58 | */ 59 | MyStack.prototype.top = function() { 60 | return this.queue[this.queue.length - 1]; 61 | }; 62 | 63 | /** 64 | * Returns whether the stack is empty. 65 | * @return {boolean} 66 | */ 67 | MyStack.prototype.empty = function() { 68 | return this.queue.length === 0; 69 | }; 70 | ``` 71 | -------------------------------------------------------------------------------- /docs/docs/list/stack/503.next-greater-element-ii.md: -------------------------------------------------------------------------------- 1 | # [503] 下一个更大元素 II 2 | > 3 | > 给定一个循环数组 nums ( nums[nums.length - 1] 的下一个元素是 nums[0] ),返回 nums 中每个元素的下一个更大元素 。 4 | > 5 | > 数字 x 的 下一个更大的元素 是按数组遍历顺序,这个数字之后的第一个比它更大的数,这意味着你应该循环地搜索它的下一个更大的数。如果不存在,则输出 -1。 6 | > 7 | > 示例 1: 8 | > 9 | > 输入: nums = [1,2,1] 10 | > 11 | > 输出: [2,-1,2] 12 | > 13 | > 解释: 第一个 1 的下一个更大的数是 2; 14 | > 15 | > 数字 2 找不到下一个更大的数; 16 | > 17 | > 第二个 1 的下一个最大的数需要循环搜索,结果也是 2。 18 | > 19 | > 示例 2: 20 | > 21 | > 输入: nums = [1,2,3,4,3] 22 | > 23 | > 输出: [2,3,4,-1,4] 24 | > 25 | > 提示: 26 | > 27 | > 1 <= nums.length <= 10^4 28 | > 29 | > -10^9 <= nums[i] <= 10^9 30 | 31 | ## 解析 32 | 33 | 本题与 [496. 下一个更大元素 I](496.next-greater-element-i.md) 类似,只不过数组变成了循环数组。因此,我们可以将数组复制一份,然后将其拼接到原数组的后面,这样就可以将循环数组转换为普通数组。之后,我们便可以使用单调栈算法来解决这个问题。 34 | 35 | ```ts 36 | function nextGreaterElements(nums: number[]): number[] { 37 | const len = nums.length; 38 | const res: number[] = []; 39 | const stack: number[] = []; 40 | 41 | for (let i = 2 * len - 1; i >= 0; i--) { 42 | const index = i % len; 43 | while (stack.length > 0 && stack[stack.length - 1] <= nums[index]) { 44 | stack.pop(); 45 | } 46 | 47 | res[index] = stack.length > 0 ? stack[stack.length - 1] : -1; 48 | stack.push(nums[index]); 49 | } 50 | 51 | return res; 52 | } 53 | ``` 54 | -------------------------------------------------------------------------------- /docs/docs/list/string/14.longestCommonPrefix.md: -------------------------------------------------------------------------------- 1 | # [14] 最长公共前缀 2 | 3 | >编写一个函数来查找字符串数组中的最长公共前缀。 4 | > 5 | >如果不存在公共前缀,返回空字符串 ""。 6 | > 7 | >示例 1: 8 | > 9 | >输入: ["flower","flow","flight"] 10 | > 11 | >输出: "fl" 12 | > 13 | >示例 2: 14 | > 15 | >输入: ["dog","racecar","car"] 16 | > 17 | >输出: "" 18 | > 19 | >解释: 输入不存在公共前缀。 20 | > 21 | >说明: 22 | > 23 | >所有输入只包含小写字母 a-z 。 24 | 25 | easy题,没想到啥好解决方法,直接暴力遍历查询。 26 | 27 | 这里的every是我偷懒了,直接for遍历的话注意若存在字符串不存在第i个字符的话直接就能返回了,可以剪枝掉一部分情况。 28 | 29 | ```js 30 | var longestCommonPrefix = function(strs) { 31 | if (strs.length <= 0) return ''; 32 | for (let i = 0; i < strs[0].length; i++) { 33 | const flag = strs.every(str => str[i] === strs[0][i]); 34 | if (!flag) { 35 | return strs[0].substring(0, i); 36 | } 37 | } 38 | return strs[0]; 39 | }; 40 | ``` 41 | -------------------------------------------------------------------------------- /docs/docs/list/string/415.addStrings.md: -------------------------------------------------------------------------------- 1 | # [415] 字符串相加 2 | 3 | >给定两个字符串形式的非负整数 num1 和num2 ,计算它们的和。 4 | > 5 | >注意: 6 | > 7 | >num1 和num2 的长度都小于 5100. 8 | > 9 | >num1 和num2 都只包含数字 0-9. 10 | > 11 | >num1 和num2 都不包含任何前导零。 12 | > 13 | >你不能使用任何內建 BigInteger 库, 也不能直接将输入的字符串转换为整数形式。 14 | 15 | 这道题也是简单级别的题,解题思路参考 67.addBinary 16 | 17 | ```js 18 | var addStrings = function(num1, num2) { 19 | let i = num1.length - 1; 20 | let j = num2.length - 1; 21 | if (i < j) num1 = '0'.repeat(j - i) + num1; 22 | if (i > j) num2 = '0'.repeat(i - j) + num2; 23 | 24 | let res = ''; 25 | let cnt = 0; 26 | 27 | for (let k = Math.max(i, j); k >= 0; k--) { 28 | const temp = Number(num1[k]) + Number(num2[k]) + cnt; 29 | cnt = temp > 9 ? 1 : 0; 30 | res = (temp % 10) + res; 31 | } 32 | if (cnt === 1) res = '1' + res; 33 | return res; 34 | }; 35 | ``` 36 | -------------------------------------------------------------------------------- /docs/docs/list/string/557.reverse-words-in-a-string-iii.md: -------------------------------------------------------------------------------- 1 | # [557] 反转字符串中的单词 III 2 | 3 | > 给定一个字符串,你需要反转字符串中每个单词的字符顺序,同时仍保留空格和单词的初始顺序。 4 | > 5 | > 示例: 6 | > 7 | > 输入:"Let's take LeetCode contest" 8 | > 9 | > 输出:"s'teL ekat edoCteeL tsetnoc" 10 | > 11 | > 提示: 12 | > 13 | > 在字符串中,每个单词由单个空格分隔,并且字符串中不会有任何额外的空格。 14 | 15 | 这题比较简单不多说了。以空格作为分界线,分别翻转每个单词字符串就行,若要求使用原地算法就使用首尾指针翻转即可。 16 | 17 | ```ts 18 | function reverseWords(s: string): string { 19 | const words = s.split(' '); 20 | const reverseWords = words.map(word => { 21 | return word 22 | .split('') 23 | .reverse() 24 | .join(''); 25 | }); 26 | return reverseWords.join(' '); 27 | } 28 | ``` 29 | -------------------------------------------------------------------------------- /docs/docs/list/string/6.zigZagConversion.md: -------------------------------------------------------------------------------- 1 | # [6] Z 字形变换 2 | 3 | > 将一个给定字符串根据给定的行数,以从上往下、从左到右进行 Z 字形排列。 4 | > 5 | > 比如输入字符串为 "LEETCODEISHIRING" 行数为 3 时,排列如下: 6 | > 7 | > L C  I  R\ 8 | > ETOES I I G\ 9 | > E D H N 10 | > 11 | > 之后,你的输出需要从左往右逐行读取,产生出一个新的字符串,比如:"LCIRETOESIIGEDHN"。 12 | > 13 | > 请你实现这个将字符串进行指定行数变换的函数: 14 | > 15 | > string convert(string s, int numRows); 16 | > 17 | > 示例 1: 18 | > 19 | > 输入: s = "LEETCODEISHIRING", numRows = 3\ 20 | > 输出: "LCIRETOESIIGEDHN" 21 | > 22 | > 示例 2: 23 | > 24 | > 输入: s = "LEETCODEISHIRING", numRows = 4\ 25 | > 输出: "LDREOEIIECIHNTSG" 26 | > 27 | > 解释: 28 | > 29 | > L  D  R\ 30 | > E OE I I\ 31 | > EC I H N\ 32 | > T  S  G 33 | 34 | 这道题只需要按顺序存储一遍再拼接起来就行了。注意在第一行以及最后一行的时候存储顺序需要反向。 35 | 36 | ```js 37 | var convert = function(s, numRows) { 38 | if (numRows < 2) return s; 39 | const res = new Array(numRows).fill(''); 40 | let cnt = 1; 41 | let cur = 0; 42 | for (let i = 0; i < s.length; i++) { 43 | res[cur] += s[i]; 44 | cur += cnt; 45 | if (cur === numRows - 1 || cur === 0) cnt = -cnt; 46 | } 47 | return res.join(''); 48 | }; 49 | ``` 50 | -------------------------------------------------------------------------------- /docs/docs/list/tree/101.symmetricTree.md: -------------------------------------------------------------------------------- 1 | # [101] 对称二叉树 2 | 3 | > 给定一个二叉树,检查它是否是镜像对称的。 4 | > 5 | > 例如,二叉树 [1,2,2,3,4,4,3] 是对称的。 6 | > 7 | > ⁠ ⁠ ⁠ ⁠ ⁠ 1 8 | > 9 | > ⁠ ⁠ ⁠ ⁠ ⁠ / \ 10 | > 11 | > ⁠ ⁠ ⁠ 2⁠ ⁠ ⁠ 2 12 | > 13 | > ⁠⁠ ⁠ ⁠ / \ ⁠ / \ 14 | > 15 | > 3⁠ ⁠ 4⁠ ⁠ 4⁠ ⁠ 3 16 | > 17 | > 但是下面这个 [1,2,2,null,3,null,3] 则不是镜像对称的: 18 | > 19 | > ⁠⁠ ⁠ ⁠ ⁠ ⁠ 1 20 | > 21 | > ⁠ ⁠ ⁠ ⁠ / \ 22 | > 23 | > ⁠   2   2 24 | > 25 | > ⁠     \\    \ 26 | > 27 | > ⁠     3     3 28 | > 29 | > 说明: 30 | > 31 | > 如果你可以运用递归和迭代两种方法解决这个问题,会很加分。 32 | 33 | ```js 34 | var isSymmetric = function(root) { 35 | if (!root) return true; 36 | return helper(root.left, root.right); 37 | }; 38 | 39 | function helper(left, right) { 40 | if (!left && !right) return true; 41 | if ((left && !right) || (!left && right) || left.val !== right.val) 42 | return false; 43 | return helper(left.left, right.right) && helper(left.right, right.left); 44 | } 45 | 46 | function TreeNode(val) { 47 | this.val = val; 48 | this.left = this.right = null; 49 | } 50 | ``` 51 | -------------------------------------------------------------------------------- /docs/docs/list/tree/104.maximumDepthOfBinaryTree.md: -------------------------------------------------------------------------------- 1 | # [104] 二叉树的最大深度 2 | 3 | > 给定一个二叉树,找出其最大深度。 4 | > 5 | > 二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。 6 | > 7 | > 说明: 叶子节点是指没有子节点的节点。 8 | > 9 | > 示例: 10 | > 11 | > 给定二叉树 [3,9,20,null,null,15,7], 12 | > 13 | > 返回它的最大深度 3 。 14 | 15 | ```js 16 | var maxDepth = function(root) { 17 | if (!root) return 0; 18 | let depth = 0; 19 | let queue = [root]; 20 | while (queue.length !== 0) { 21 | for (let i = queue.length; i > 0; i--) { 22 | const node = queue.shift(); 23 | if (node.left) queue.push(node.left); 24 | if (node.right) queue.push(node.right); 25 | } 26 | depth++; 27 | } 28 | return depth; 29 | }; 30 | ``` 31 | 32 | ## DFS深度优先搜索 33 | 34 | ```js 35 | var maxDepth = function(root) { 36 | if (!root) return 0; 37 | let depth = 0; 38 | let queue = [root]; 39 | while (queue.length !== 0) { 40 | for (let i = queue.length; i > 0; i--) { 41 | const node = queue.shift(); 42 | if (node.left) queue.push(node.left); 43 | if (node.right) queue.push(node.right); 44 | } 45 | depth++; 46 | } 47 | return depth; 48 | }; 49 | ``` 50 | 51 | ## BFS广度优先搜索实现 52 | 53 | 递归实现 54 | 55 | ```js 56 | var maxDepth = function(root) { 57 | if (!root) return 0; 58 | return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1; 59 | }; 60 | ``` 61 | -------------------------------------------------------------------------------- /docs/docs/list/tree/110.balancedBinaryTree.md: -------------------------------------------------------------------------------- 1 | # [110] 平衡二叉树 2 | 3 | > 给定一个二叉树,判断它是否是高度平衡的二叉树。 4 | > 5 | > 本题中,一棵高度平衡二叉树定义为: 6 | > 7 | > 一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过1。 8 | > 9 | > 示例 1: 10 | > 11 | > 给定二叉树 [3,9,20,null,null,15,7] 12 | > 13 | > 返回 true 。 14 | > 15 | > 示例 2: 16 | > 17 | > 给定二叉树 [1,2,2,3,3,null,null,4,4] 18 | > 19 | > 返回 false 。 20 | 21 | ```js 22 | /** 23 | * Definition for a binary tree node. 24 | * function TreeNode(val) { 25 | * this.val = val; 26 | * this.left = this.right = null; 27 | * } 28 | */ 29 | var isBalanced = function(root) { 30 | return Math.abs(helper(root.left) - helper(root.right)) <= 1; 31 | }; 32 | 33 | function helper(root) { 34 | if (!root) return 0; 35 | return Math.max(helper(root.left), helper(root.right)) + 1; 36 | } 37 | ``` 38 | -------------------------------------------------------------------------------- /docs/docs/list/tree/124.binaryTreeMaximumPathSum.md: -------------------------------------------------------------------------------- 1 | # [124] 二叉树中的最大路径和 2 | 3 | > 给定一个非空二叉树,返回其最大路径和。 4 | > 5 | > 本题中,路径被定义为一条从树中任意节点出发,沿父节点-子节点连接,达到任意节点的序列。该路径至少包含一个节点,且不一定经过根节点。 6 | > 7 | > 示例 1: 8 | > 9 | > 输入:[1,2,3] 10 | > 11 | > 输出:6 12 | > 13 | > 示例 2: 14 | > 15 | > 输入:[-10,9,20,null,null,15,7] 16 | > 17 | > 输出:42 18 | 19 | 这道题实际上是一个考察二叉树后序遍历逻辑的简单算法题。 20 | 21 | 我们注意到,想要获取到最大路径,则必须从下向上取值的所经过的路径都取最优即可。恰好二叉树的后序遍历就是从下向上遍历的,因此很容易想到解法。 22 | 23 | 我们最下层节点完成计算当前的最大路径和,并更新全局最优路径和,然后返回当前节点的最优路径值即可。 24 | 25 | 需要注意的是,这个最优路径值要是仍为负数的话,则需要置为0,视作舍弃包括该节点下的所有路径,从上一个节点作为起始点开始记录最优路径和。 26 | 27 | 边界条件理解清楚了后,问题就迎刃而解了。 28 | 29 | ```js 30 | var maxPathSum = function(root) { 31 | let res = -Number.MAX_VALUE; 32 | traverse(root); 33 | return res; 34 | 35 | function traverse(root) { 36 | if (!root) return 0; 37 | 38 | const left = traverse(root.left); 39 | const right = traverse(root.right); 40 | 41 | // 进行后序遍历处理 42 | 43 | // 当前最大路径和 44 | res = Math.max(res, left + right + root.val); 45 | 46 | // 当前的最大路径值,如果值为负数,则设为0,表示直接不取该节点,从上一个节点出发为更优 47 | return Math.max(0, left + root.val, right + root.val); 48 | } 49 | }; 50 | ``` 51 | -------------------------------------------------------------------------------- /docs/docs/list/tree/144.binaryTreePreorderTraversal.md: -------------------------------------------------------------------------------- 1 | # [144] 二叉树的前序遍历 2 | 3 | > 给定一个二叉树,返回它的 前序 遍历。 4 | > 5 | > 示例: 6 | > 7 | > 输入: [1,null,2,3] 8 | > 9 | > 输出: [1,2,3] 10 | > 11 | > 进阶: 递归算法很简单,你可以通过迭代算法完成吗? 12 | 13 | 非常典型的先序遍历树问题,有两种解法,递归与迭代。 14 | 15 | ## 递归法 16 | 17 | 递归写起来非常简单,遵循中-左-右的顺序即可 18 | 19 | ```ts 20 | function preorderTraversal(root: TreeNode | null): number[] { 21 | if (!root) return []; 22 | 23 | const res: Array = []; 24 | helper(root); 25 | 26 | return res; 27 | 28 | function helper(node: TreeNode | null): void { 29 | if (!node) return; 30 | 31 | res.push(node.val); 32 | helper(node.left); 33 | helper(node.right); 34 | } 35 | } 36 | ``` 37 | 38 | ## 迭代法 39 | 40 | 迭代先序遍历利用了栈的特性,直接先将根节点入栈,然后开始循环:出栈一个元素,存储节点值,若该节点有右节点,入栈,若该节点有左节点,入栈。直到栈空为止。 41 | 42 | ```ts 43 | function preorderTraversal(root: TreeNode | null): number[] { 44 | if (!root) return []; 45 | 46 | const res: Array = []; 47 | 48 | const stack: TreeNode[] = [root]; 49 | 50 | while(stack.length > 0) { 51 | const node = stack.pop(); 52 | res.push(node.val); 53 | node.right && stack.push(node.right); 54 | node.left && stack.push(node.left); 55 | } 56 | return res; 57 | } 58 | ``` 59 | 60 | > 数的前中后序遍历方法相关题目: 61 | > 62 | > [94] Binary Tree Inorder Traversal 63 | > 64 | > [144] Binary Tree Preorder Traversal 65 | > 66 | > [145] Binary Tree Postorder Traversal 67 | -------------------------------------------------------------------------------- /docs/docs/list/tree/226.invertBinaryTree.md: -------------------------------------------------------------------------------- 1 | # [226] 翻转二叉树 2 | 3 | > 翻转一棵二叉树。 4 | > 5 | > 示例: 6 | > 7 | > 输入: 8 | > 9 | > ⁠ ⁠  ⁠4 10 | > 11 | > ⁠ ⁠ /⁠ \ 12 | > 13 | >  2  7 14 | > 15 | >  ⁠/ \\  / \ 16 | > 17 | > 1 3 6 9 18 | > 19 | > 输出: 20 | > 21 | > ⁠ ⁠  ⁠4 22 | > 23 | > ⁠ ⁠ /⁠ \ 24 | > 25 | >  7  2 26 | > 27 | >  ⁠/ \\  / \ 28 | > 29 | > 9 6 3 1 30 | 31 | 树深度优先遍历(递归)与层序遍历(队列,非递归)两种方法 32 | 33 | ```js 34 | var invertTree = function(root) { 35 | if (!root) return null; 36 | invertTree(root.left); 37 | invertTree(root.right); 38 | 39 | const temp = root.left; 40 | root.left = root.right; 41 | root.right = temp; 42 | 43 | return root; 44 | }; 45 | 46 | function TreeNode(val) { 47 | this.val = val; 48 | this.left = this.right = null; 49 | } 50 | ``` 51 | 52 | ```js 53 | var invertTree = function(root) { 54 | if (!root) return null; 55 | const queue = [root]; 56 | while (queue.length !== 0) { 57 | const node = queue.shift(); 58 | const temp = node.left; 59 | node.left = node.right; 60 | node.right = temp; 61 | 62 | if (node.left) queue.push(node.left); 63 | if (node.right) queue.push(node.right); 64 | } 65 | return root; 66 | }; 67 | ``` 68 | -------------------------------------------------------------------------------- /docs/docs/list/tree/230.kthSmallestElementInABst.md: -------------------------------------------------------------------------------- 1 | # [230] 二叉搜索树中第 K 小的元素 2 | 3 | > 给定一个二叉搜索树,编写一个函数 kthSmallest 来查找其中第 k 个最小的元素。 4 | > 5 | > 说明: 6 | > 7 | > 你可以假设 k 总是有效的,1 ≤ k ≤ 二叉搜索树元素个数。 8 | > 9 | > 示例 1: 10 | > 11 | > 输入:root = [3,1,4,null,2], k = 1 12 | > 13 | > 输出:1 14 | > 15 | > 示例 2: 16 | > 17 | > 输入:root = [5,3,6,2,4,null,null,1], k = 3 18 | > 19 | > 输出:3 20 | > 21 | > 进阶: 22 | > 23 | > 如果二叉搜索树经常被修改(插入/删除操作)并且你需要频繁地查找第 k 小的值,你将如何优化 kthSmallest 函数? 24 | 25 | 这道题其实就是考察二叉树的中序遍历。 26 | 27 | 在二叉搜索树中,第 K 小的数实际上就是二叉树中序遍历后的索引位置,因此只需要在中序遍历时判断该位置是不是第 k 个即可。 28 | 29 | ```js 30 | var kthSmallest = function (root, k) { 31 | let res = null; 32 | let count = k; 33 | function traverse(root) { 34 | if (!root) return null; 35 | 36 | traverse(root.left); 37 | 38 | // 中序遍历时判断 39 | count -= 1; 40 | if (count === 0) { 41 | res = root.val; 42 | return; 43 | } 44 | 45 | traverse(root.right); 46 | } 47 | 48 | traverse(root); 49 | 50 | return res; 51 | }; 52 | ``` 53 | -------------------------------------------------------------------------------- /docs/docs/list/tree/437.pathSumIii.md: -------------------------------------------------------------------------------- 1 | # [437] 路径总和 III 2 | 3 | > 给定一个二叉树,它的每个结点都存放着一个整数值。 4 | > 5 | > 找出路径和等于给定数值的路径总数。 6 | > 7 | > 路径不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点)。 8 | > 9 | > 二叉树不超过1000个节点,且节点数值范围是 [-1000000,1000000] 的整数。 10 | > 11 | > 示例: 12 | > 13 | > root = [10,5,-3,3,2,null,11,3,-2,null,1], sum = 8 14 | > 15 | > ⁠ ⁠ ⁠ 10 16 | > 17 | > ⁠⁠ ⁠ ⁠ / \ 18 | > 19 | > ⁠⁠ ⁠ 5⁠ -3 20 | > 21 | > ⁠⁠ ⁠ / \\⁠ ⁠  \ 22 | > 23 | > ⁠ 3 2  11 24 | > 25 | >  ⁠/ \\  \ 26 | > 27 | > 3 -2  1 28 | > 29 | > 返回 3。和等于 8 的路径有: 30 | > 31 | > 1.5 -> 3 32 | > 2.5 -> 2 -> 1 33 | > 3.-3 -> 11 34 | 35 | 注意题目要求,并不一定从根节点开始计数,因此每一个节点都需要执行一遍子路径计算。 36 | 37 | ```js 38 | var pathSum = function(root, sum) { 39 | if (!root) return 0; 40 | const count = helper(root, sum); 41 | const lCount = pathSum(root.left, sum); 42 | const rCount = pathSum(root.right, sum); 43 | return count + lCount + rCount; 44 | }; 45 | 46 | function helper(root, remain) { 47 | if (!root) return 0; 48 | const newRemain = remain - root.val; 49 | const count = newRemain === 0 ? 1 : 0; 50 | const lCount = helper(root.left, newRemain); 51 | const rCount = helper(root.right, newRemain); 52 | return lCount + rCount + count; 53 | } 54 | 55 | function TreeNode(val) { 56 | this.val = val; 57 | this.left = this.right = null; 58 | } 59 | ``` 60 | -------------------------------------------------------------------------------- /docs/docs/list/tree/509.fibonacci-number.md: -------------------------------------------------------------------------------- 1 | # [509] 斐波那契数 2 | 3 | > 斐波那契数,通常用 F(n) 表示,形成的序列称为 斐波那契数列 。该数列由 0 和 1 开始,后面的每一项数字都是前面两项数字的和。也就是: 4 | > 5 | > F(0) = 0,F(1) = 1 6 | > 7 | > F(n) = F(n - 1) + F(n - 2),其中 n > 1 8 | > 9 | > 给你 n ,请计算 F(n) 。 10 | 11 | ```ts 12 | function fib(n: number): number { 13 | if (n === 0) return 0; 14 | if (n === 1) return 1; 15 | 16 | let prev = 0; 17 | let curr = 1; 18 | for (let i = 2; i <= n; i++) { 19 | const temp = curr; 20 | curr = temp + prev; 21 | prev = temp; 22 | } 23 | return curr; 24 | } 25 | ``` 26 | 27 | 动态规划入门,最基本的斐波那契数列问题。 28 | -------------------------------------------------------------------------------- /docs/docs/list/tree/538.convertBstToGreaterTree.md: -------------------------------------------------------------------------------- 1 | # [538] 把二叉搜索树转换为累加树 2 | 3 | > 给出二叉 搜索 树的根节点,该树的节点值各不相同,请你将其转换为累加树(Greater Sum Tree),使每个节点 node 的新值等于原树中大于或等于 node.val 的值之和。 4 | > 5 | > 提醒一下,二叉搜索树满足下列约束条件: 6 | > 7 | > 节点的左子树仅包含键 小于 节点键的节点。 8 | > 9 | > 节点的右子树仅包含键 大于 节点键的节点。 10 | > 11 | > 左右子树也必须是二叉搜索树。 12 | > 13 | > 示例 1: 14 | > 15 | > 输入:[4,1,6,0,2,5,7,null,null,null,3,null,null,null,8] 16 | > 17 | > 输出:[30,36,21,36,35,26,15,null,null,null,33,null,null,null,8] 18 | > 19 | > 示例 2: 20 | > 21 | > 输入:root = [0,null,1] 22 | > 23 | > 输出:[1,null,1] 24 | > 25 | > 示例 3: 26 | > 27 | > 输入:root = [1,0,2] 28 | > 29 | > 输出:[3,3,2] 30 | > 31 | > 示例 4: 32 | > 33 | > 输入:root = [3,2,4,1] 34 | > 35 | > 输出:[7,9,4,10] 36 | > 37 | > 提示: 38 | > 39 | > 树中的节点数介于 0 和 10^4^ 之间。 40 | > 41 | > 每个节点的值介于 -10^4 和 10^4 之间。 42 | > 43 | > 树中的所有值 互不相同 。 44 | > 45 | > 给定的树为二叉搜索树。 46 | 47 | 这道题提出了一个叫做累加树的概念。 48 | 49 | 我们可以发现,累加树的形成实际上就是将 BST 经过中序遍历后得到的从小到大排列的数据,从后向前开始累加,并在每个节点保留当前累加的值。 50 | 51 | 这就要求我们反向遍历树节点,并利用变量存储当前累加的值。 52 | 53 | 那么反向遍历树该怎么做呢?其实很简单,仍然属于中序遍历的一种,只需要将中序遍历先左后右的规矩改变成先右后左即可。接下来问题便迎刃而解了。 54 | 55 | ```ts 56 | function convertBST(root: TreeNode | null): TreeNode | null { 57 | let sum = 0; 58 | traverse(root); 59 | return root; 60 | 61 | function traverse(node: TreeNode | null) { 62 | if (!node) return; 63 | 64 | traverse(node.right); 65 | sum += node.val; 66 | node.val = sum; 67 | traverse(node.left); 68 | } 69 | } 70 | ``` 71 | -------------------------------------------------------------------------------- /docs/docs/list/tree/572.subtree-of-another-tree.md: -------------------------------------------------------------------------------- 1 | # [572] 另一棵树的子树 2 | 3 | > 给你两棵二叉树 root 和 subRoot 。检验 root 中是否包含和 subRoot 具有相同结构和节点值的子树。如果存在,返回 true;否则,返回 false 。 4 | > 5 | > 二叉树 tree 的一棵子树包括 tree 的某个节点和这个节点的所有后代节点。tree 也可以看做它自身的一棵子树。 6 | > 7 | > 示例 1: 8 | > 9 | > 输入:root = [3,4,5,1,2], subRoot = [4,1,2] 10 | > 11 | > 输出:true 12 | > 13 | > 示例 2: 14 | > 15 | > 输入:root = [3,4,5,1,2,null,null,null,null,0], subRoot = [4,1,2] 16 | > 17 | > 输出:false 18 | > 19 | > 提示: 20 | > 21 | > root 树上的节点数量范围是 [1, 2000] 22 | > subRoot 树上的节点数量范围是 [1, 1000] 23 | 24 | 这道题与 `[100] 相同的树` 非常相似,仅需不断地将root的子树与subRoot做相等对比即可得出结果。 25 | 26 | ```ts 27 | function isSubtree(root: TreeNode | null, subRoot: TreeNode | null): boolean { 28 | if (root === null) { 29 | return subRoot === null; 30 | } 31 | 32 | if (isSameTree(root, subRoot)) return true; 33 | 34 | return isSubtree(root.left, subRoot) || isSubtree(root.right, subRoot); 35 | 36 | function isSameTree(p: TreeNode | null, q: TreeNode | null): boolean { 37 | if (!p && !q) return true; 38 | if ((p && !q) || (!p && q)) return false; 39 | if (p.val !== q.val) return false; 40 | 41 | return isSameTree(p.left, q.left) && isSameTree(p.right, q.right); 42 | } 43 | } 44 | ``` 45 | -------------------------------------------------------------------------------- /docs/docs/list/tree/700.search-in-a-binary-search-tree.md: -------------------------------------------------------------------------------- 1 | # [700] 二叉搜索树中的搜索 2 | 3 | > 给定二叉搜索树(BST)的根节点 root 和一个整数值 val。 4 | > 5 | > 你需要在 BST 中找到节点值等于 val 的节点。 返回以该节点为根的子树。 如果节点不存在,则返回 null 。 6 | > 7 | > 示例 1: 8 | > 9 | > 输入:root = [4,2,7,1,3], val = 2 10 | > 11 | > 输出:[2,1,3] 12 | > 13 | > Example 2: 14 | > 15 | > 输入:root = [4,2,7,1,3], val = 5 16 | > 17 | > 输出:[] 18 | > 19 | > 提示: 20 | > 21 | > 数中节点数在 [1, 5000] 范围内 22 | > 23 | > 1 <= Node.val <= 10^7 24 | > 25 | > root 是二叉搜索树 26 | > 27 | > 1 <= val <= 10^7 28 | 29 | 二叉搜索树的特性就是所有的子树都有:左侧所有值都较当前节点小,右侧所有值都较当前节点大。 30 | 31 | 因此我们只需不断比较当前节点与target的值的大小,不同就在左右子树上继续判断,直到最终找不到为止。 32 | 33 | ```ts 34 | function searchBST(root: TreeNode | null, val: number): TreeNode | null { 35 | return traverse(root); 36 | 37 | function traverse(node: TreeNode | null): TreeNode | null { 38 | if (!node) return null; 39 | if (node.val > val) { 40 | return traverse(node.left); 41 | } else if (node.val < val) { 42 | return traverse(node.right); 43 | } 44 | return node; 45 | } 46 | } 47 | ``` 48 | -------------------------------------------------------------------------------- /docs/docs/list/tree/94.binaryTreeInorderTraversal.md: -------------------------------------------------------------------------------- 1 | # [94] 二叉树的中序遍历 2 | 3 | > 给定一个二叉树,返回它的 中序 遍历。 4 | > 5 | > 示例: 6 | > 7 | > 输入: [1,null,2,3] 8 | > 9 | > 输出: [1,3,2] 10 | > 11 | > 进阶: 递归算法很简单,你可以通过迭代算法完成吗? 12 | 13 | 非常典型的中序遍历树问题,有两种解法,递归与迭代。 14 | 15 | ## 递归法 16 | 17 | 递归写起来非常简单,遵循左-中-右的顺序即可。 18 | 19 | ```ts 20 | function inorderTraversal(root: TreeNode | null): number[] { 21 | if (!root) return []; 22 | const res: number[] = []; 23 | helper(root); 24 | return res; 25 | 26 | function helper(node: TreeNode | null) { 27 | if (!node) return; 28 | if (node.left) helper(node.left); 29 | res.push(node.val); 30 | if (node.right) helper(node.right); 31 | } 32 | } 33 | ``` 34 | 35 | ## 迭代法 36 | 37 | 迭代中序遍历利用了栈的特性。先将根节点入栈,指针指向根节点的左子节点,然后开始循环: 38 | 39 | 1. 指针节点所有左子节点全部依次入栈, 40 | 2. 取出栈顶节点,存储节点值, 41 | 3. 该节点若有右节点,指针指向其右节点。 42 | 43 | 直到节点栈空并且取出节点没有右子节点为止。 44 | 45 | ```ts 46 | function inorderTraversal(root: TreeNode | null): number[] { 47 | if (!root) return []; 48 | const res: number[] = []; 49 | 50 | const stack: TreeNode[] = [root]; 51 | 52 | let cur = root.left; 53 | while (cur || stack.length > 0) { 54 | // 左子节点依次入栈 55 | while (cur) { 56 | stack.push(cur); 57 | cur = cur.left; 58 | } 59 | // 读取并存储当前节点值 60 | cur = stack.pop(); 61 | res.push(cur.val); 62 | // 指针指向右子节点 63 | cur = cur.right; 64 | } 65 | 66 | return res; 67 | } 68 | ``` 69 | 70 | > 数的前中后序遍历方法相关题目: 71 | > 72 | > [94] Binary Tree Inorder Traversal 73 | > 74 | > [144] Binary Tree Preorder Traversal 75 | > 76 | > [145] Binary Tree Postorder Traversal 77 | -------------------------------------------------------------------------------- /docs/docs/list/tree/95.uniqueBinarySearchTreesIi.md: -------------------------------------------------------------------------------- 1 | # [95] 不同的二叉搜索树 II 2 | 3 | > 给定一个整数 n,生成所有由 1 ... n 为节点所组成的二叉搜索树。 4 | > 5 | > 示例: 6 | > 7 | > 输入: 3 8 | > 9 | > 输出: 10 | > 11 | > [ 12 | > 13 | > [1,null,3,2], 14 | > 15 | > [3,2,null,1], 16 | > 17 | > [3,1,null,null,2], 18 | > 19 | > [2,1,3], 20 | > 21 | > [1,null,2,null,3] 22 | > 23 | > ] 24 | 25 | 这道题请与`96. 不同的二叉搜索树`配合食用,这道题需要求对按数字顺序1~n组成的树进行先序遍历,求所有可能的组成。 26 | 27 | 因为我们知道先序遍历的二叉查找树,父节点一定会大于左子节点,小于右子节点,因此我们可以使用递归来解这个问题(暂时还没有想出使用动态规划的话用什么存入dp数组里,但思想上还是使用dp的思想减少解重复子问题次数) 28 | 29 | 首先选取当前根节点为x,则左子树节点数为(l ~ x-1),右子树节点数为(x+1, r),之后进行递归遍历即可。 30 | 31 | ```js 32 | var generateTrees = function(n) { 33 | if (n === 0) return []; 34 | const res = helper(1, n); 35 | return res; 36 | }; 37 | 38 | function TreeNode(val) { 39 | this.val = val; 40 | this.left = this.right = null; 41 | } 42 | 43 | function helper(l, r) { 44 | if (l > r) return [null]; 45 | if (l === r) return [new TreeNode(l)]; 46 | 47 | const subTree = []; 48 | for (let i = l; i <= r; i++) { 49 | const leftSubTree = helper(l, i - 1); 50 | const rightSubTree = helper(i + 1, r); 51 | 52 | for (let leftNode of leftSubTree) { 53 | for (let rightNode of rightSubTree) { 54 | const rootNode = new TreeNode(i); 55 | rootNode.left = leftNode; 56 | rootNode.right = rightNode; 57 | subTree.push(rootNode); 58 | } 59 | } 60 | } 61 | return subTree; 62 | } 63 | ``` 64 | -------------------------------------------------------------------------------- /docs/docs/list/tree/96.uniqueBinarySearchTrees.md: -------------------------------------------------------------------------------- 1 | # [96] 不同的二叉搜索树 2 | 3 | > 给定一个整数 n,求以 1 ... n 为节点组成的二叉搜索树有多少种? 4 | > 5 | > 示例: 6 | > 7 | > 输入: 3 8 | > 9 | > 输出: 5 10 | > 11 | > 解释: 12 | > 13 | > 给定 n = 3, 一共有 5 种不同结构的二叉搜索树 14 | 15 | 这道题请与`95. 不同的二叉搜索树 II`配合食用,这道题需要求对按数字顺序1~n组成的树进行先序遍历,求所有可能的个数。那么我们可以建立一个一维数组dp,考虑当每个数字为根节点时,所有可行的方法个数。 16 | 17 | 这实际上是求卡特兰数,不知道的同学可自行百度。根节点的组成个数为左右子树的组成个数相乘。 18 | 19 | 对于每一个节点i,考虑所有左右子树分配节点个数可能的组成情况,设左子树节点个数为j,那么右子树节点个数为i-1-j,将左右子树的组成个数相乘,将所有情况相加,即可得到dp[i]的值。 20 | 21 | 状态转移方程为:dp[i] = dp[i-1] * dp[n-i]。 22 | 23 | 确定边界情况,n=0时,只有可能是空树;n=1时,也只有一种可能。 24 | 25 | ```js 26 | var numTrees = function(n) { 27 | if (n === 0 || n === 1) return 1; 28 | const dp = new Array(n + 1).fill(0); 29 | dp[0] = 1; 30 | dp[1] = 1; 31 | for (let i = 2; i <= n; i++) { 32 | for (let j = 0; j < i; j++) { 33 | dp[i] += dp[j] * dp[i - j - 1]; 34 | } 35 | } 36 | return dp[n]; 37 | }; 38 | ``` 39 | -------------------------------------------------------------------------------- /docs/docs/list/tree/98.validateBinarySearchTree.md: -------------------------------------------------------------------------------- 1 | # [98] 验证二叉搜索树 2 | 3 | > 给定一个二叉树,判断其是否是一个有效的二叉搜索树。 4 | > 5 | > 假设一个二叉搜索树具有如下特征: 6 | > 7 | > 节点的左子树只包含小于当前节点的数。 8 | > 9 | > 节点的右子树只包含大于当前节点的数。 10 | > 11 | > 所有左子树和右子树自身必须也是二叉搜索树。 12 | > 13 | > 示例 1: 14 | > 15 | > 输入: 16 | > 17 | >    2 18 | > 19 | >   / \ 20 | > 21 | > 1   3 22 | > 23 | > 输出: true 24 | > 25 | > 示例 2: 26 | > 27 | > 输入: 28 | > 29 | > ⁠    5 30 | > 31 | > ⁠   / \ 32 | > 33 | >   1   4 34 | > 35 | >   / \ 36 | > 37 | > 3   6 38 | > 39 | > 输出: false 40 | > 41 | > 解释: 输入为: [5,1,4,null,null,3,6]。 42 | > 43 | > 根节点的值为 5 ,但是其右子节点值为 4 。 44 | 45 | ```js 46 | var isValidBST = function (root) { 47 | return helper(root, Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER); 48 | }; 49 | 50 | function helper(root, min, max) { 51 | if (!root) return true; 52 | 53 | if (root.val <= min || root.val >= max) return false; 54 | 55 | return helper(root.left, min, root.val) && helper(root.right, root.val, max); 56 | } 57 | ``` 58 | -------------------------------------------------------------------------------- /docs/docs/list/tree/99.recoverBinarySearchTree.md: -------------------------------------------------------------------------------- 1 | # [99] 恢复二叉搜索树 2 | 3 | > 给你二叉搜索树的根节点 root ,该树中的两个节点被错误地交换。请在不改变其结构的情况下,恢复这棵树。 4 | > 5 | > 进阶:使用 O(n) 空间复杂度的解法很容易实现。你能想出一个只使用常数空间的解决方案吗? 6 | > 7 | > 示例 1: 8 | > 9 | > 输入:root = [1,3,null,null,2] 10 | > 11 | > 输出:[3,1,null,null,2] 12 | > 13 | > 解释:3 不能是 1 左孩子,因为 3 > 1 。交换 1 和 3 使二叉搜索树有效。 14 | > 15 | > 示例 2: 16 | > 17 | > 输入:root = [3,1,4,null,null,2] 18 | > 19 | > 输出:[2,1,4,null,null,3] 20 | > 21 | > 解释:2 不能在 3 的右子树中,因为 2 < 3 。交换 2 和 3 使二叉搜索树有效。 22 | > 23 | > 提示: 24 | > 25 | > 树上节点的数目在范围 [2, 1000] 内 -2^31 26 | 27 | ```js 28 | var recoverTree = function(root) { 29 | let prev = null; 30 | let first = null; 31 | let second = null; 32 | if(!root) return null; 33 | helper(root, null) 34 | 35 | const temp = first.val; 36 | first.val = second.val; 37 | second.val = temp; 38 | return root; 39 | 40 | function helper(node) { 41 | if(!node) return; 42 | helper(node.left); 43 | if(!prev) { 44 | prev = node; 45 | } else { 46 | if(node.val < prev.val) { 47 | if(!first) first = prev; 48 | second = node; 49 | } 50 | prev = node; 51 | } 52 | helper(node.right); 53 | } 54 | }; 55 | ``` 56 | 57 | 这也是一道二叉树查找的变体题。我们需要三个指针,prev指向当前节点的上一个节点,first与second分别记录两个需要交换的节点。接下来我们只需要遍历这棵树查找即可,我们这里选择了中序遍历。 58 | 59 | 在遍历时,我们需要比较prev与当前节点的值大小,若prev更大,说明搜索树错乱,此时需要交换节点值。如果first此时不存在,将first指向prev,否则说明之前已出现更需要替换的节点,则first维持原有指向。second指向当前节点。 60 | 61 | 遍历结束后,交换first和second的值即可。 62 | -------------------------------------------------------------------------------- /docs/docs/list/trie/746.min-cost-climbing-stairs.md: -------------------------------------------------------------------------------- 1 | # [746] 使用最小花费爬楼梯 2 | > 3 | > 题目描述见 4 | > 5 | > https://leetcode-cn.com/problems/min-cost-climbing-stairs/description/ 6 | > 7 | > 数组的每个下标作为一个阶梯,第 i 个阶梯对应着一个非负数的体力花费值 cost[i](下标从 0 开始)。 8 | > 9 | > 每当你爬上一个阶梯你都要花费对应的体力值,一旦支付了相应的体力值,你就可以选择向上爬一个阶梯或者爬两个阶梯。 10 | > 11 | > 请你找出达到楼层顶部的最低花费。在开始时,你可以选择从下标为 0 或 1 的元素作为初始阶梯。 12 | > 13 | > 示例 1: 14 | > 15 | > 输入:cost = [10, 15, 20] 16 | > 17 | > 输出:15 18 | > 19 | > 解释:最低花费是从 cost[1] 开始,然后走两步即可到阶梯顶,一共花费 15 。 20 | > 21 | > 示例 2: 22 | > 23 | > 输入:cost = [1, 100, 1, 1, 1, 100, 1, 1, 100, 1] 24 | > 25 | > 输出:6 26 | > 27 | > 解释:最低花费方式是从 cost[0] 开始,逐个经过那些 1 ,跳过 cost[3] ,一共花费 6 。 28 | > 29 | > 提示: 30 | > 31 | > cost 的长度范围是 [2, 1000]。 32 | > 33 | > cost[i] 将会是一个整型数据,范围为 [0, 999] 。 34 | 35 | ```ts 36 | function minCostClimbingStairs(cost: number[]): number { 37 | if (cost.length === 0 || cost.length === 1) return 0; 38 | 39 | // 构造最后一级阶梯需要花费的体力,cost[i]花费为0 40 | cost.push(0); 41 | 42 | let n1 = cost[0]; 43 | let n2 = cost[1]; 44 | 45 | // 状态转移方程:Fn = Math.min(Fn-2 + Costn, Fn-1 + Costn) 46 | for (let i = 2; i < cost.length; i++) { 47 | const res = Math.min(n1 + cost[i], n2 + cost[i]); 48 | n1 = n2; 49 | n2 = res; 50 | } 51 | return n2; 52 | } 53 | ``` 54 | 55 | 这道题时基础的斐波那契数列的一道变体题,加入了花费的变量。但整体思路包括状态转移方程都是类似的。 56 | 57 | 我们发现,登上最后一级台阶时,该台阶上的花费是不会计入花费体力内的。于是我们可以给cost数组多假定一级台阶,作为最后一级的台阶花费。这样一来,题目就转变为:停留在该台阶上就需要花费多少体力。这样解题思路就变得线性了。 58 | 59 | 接下来仅需比对登上某一级台阶时,是选择前一级台阶加上当前台阶的花费高,还是选择前两级台阶加上当前台阶的花费高的问题了。而当前台阶花费的体力固定,因此需要做比较的状态将与经典爬楼梯(斐波那契)问题完全一致了。 60 | -------------------------------------------------------------------------------- /docs/docs/list/two-pointers/11.container-with-most-water.md: -------------------------------------------------------------------------------- 1 | # [11] 盛最多水的容器 2 | 3 | > 给你 n 个非负整数 a1,a2,...,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0) 。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。 4 | > 5 | > 说明:你不能倾斜容器。 6 | > 7 | > 示例 1: 8 | > 9 | > 输入:[1,8,6,2,5,4,8,3,7] 10 | > 11 | > 输出:49 12 | > 13 | > 解释:图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。 14 | > 15 | > 示例 2: 16 | > 17 | > 输入:height = [1,1] 18 | > 19 | > 输出:1 20 | > 21 | > 示例 3: 22 | > 23 | > 输入:height = [4,3,2,1,4] 24 | > 25 | > 输出:16 26 | > 27 | > 示例 4: 28 | > 29 | > 输入:height = [1,2,1] 30 | > 31 | > 输出:2 32 | > 33 | > 提示: 34 | > 35 | > n == height.length 36 | > 37 | > 2 <= n <= 10^5 38 | > 39 | > 0 <= height[i] <= 10^4 40 | 41 | 这道题挺有意思的,它要求我们找出来存最多水的情况,指引我们用贪心法去解。 42 | 43 | 首先,基础用例是很好求出来的,面积 = 较短边高度 * 指针距离。我们先求出距离最远的两边盛水面积。 44 | 45 | 之后移动较短边的指针,为什么呢?因为若想找到更大的`面积`,在`指针距离`缩短的情况下,只有找到一条更长的边,来增加`较短边高度`才有可能实现。 46 | 47 | 不断比对当前盛水面积与记录的盛水面积,取较大值。直到左右指针重合,搜索结束。 48 | 49 | ```ts 50 | function maxArea(height: number[]): number { 51 | let res = 0; 52 | 53 | let left = 0; 54 | let right = height.length - 1; 55 | while (left < right) { 56 | // 面积 = 短边高度 * 指针距离 57 | const temp = Math.min(height[left], height[right]) * (right - left); 58 | res = Math.max(res, temp); 59 | // 这里注意,要移动较短边的指针,才有可能找到更大值的情况 60 | if (height[left] < height[right]) { 61 | left += 1; 62 | } else { 63 | right -= 1; 64 | } 65 | } 66 | return res; 67 | } 68 | ``` 69 | -------------------------------------------------------------------------------- /docs/docs/list/two-pointers/125.validPalindrome.md: -------------------------------------------------------------------------------- 1 | # [125] 验证回文串 2 | 3 | > 给定一个字符串,验证它是否是回文串,只考虑字母和数字字符,可以忽略字母的大小写。 4 | > 5 | > 说明:本题中,我们将空字符串定义为有效的回文串。 6 | > 7 | > 示例 1: 8 | > 9 | > 输入: "A man, a plan, a canal: Panama" 10 | > 11 | > 输出: true 12 | > 13 | > 示例 2: 14 | > 15 | > 输入: "race a car" 16 | > 17 | > 输出: false 18 | 19 | 设置头尾双指针比对 20 | 21 | ```js 22 | var isPalindrome = function(s) { 23 | const str = s.toLowerCase().replace(/[^a-z0-9]/g, ''); 24 | let p1 = 0; 25 | let p2 = str.length - 1; 26 | while (p1 < p2) { 27 | if (str[p1] !== str[p2]) { 28 | return false; 29 | } 30 | p1++; 31 | p2--; 32 | } 33 | return true; 34 | }; 35 | ``` 36 | -------------------------------------------------------------------------------- /docs/docs/list/two-pointers/141.linkedListCycle.md: -------------------------------------------------------------------------------- 1 | # [141] 环形链表 2 | 3 | > 给定一个链表,判断链表中是否有环。 4 | > 5 | > 为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。 6 | > 7 | > 示例 1: 8 | > 9 | > 输入:head = [3,2,0,-4], pos = 1 10 | > 11 | > 输出:true 12 | > 13 | > 解释:链表中有一个环,其尾部连接到第二个节点。 14 | > 15 | > 示例 2: 16 | > 17 | > 输入:head = [1,2], pos = 0 18 | > 19 | > 输出:true 20 | > 21 | > 解释:链表中有一个环,其尾部连接到第一个节点。 22 | > 23 | > 示例 3: 24 | > 25 | > 输入:head = [1], pos = -1 26 | > 27 | > 输出:false 28 | > 29 | > 解释:链表中没有环。 30 | > 31 | > 进阶: 32 | > 33 | > 你能用 O(1)(即,常量)内存解决此问题吗? 34 | 35 | 这道题如果不限制空间复杂度可以参考图的遍历,额外拿出一个空间为n的哈希表,每遍历到一个值就查表当前节点是否已被记录过,若没有则记录当前节点,若有则当前节点一定为环的起始点,这样就可以很轻松地测出链表中有环了。代码很简单就不列出了。 36 | 37 | 但题目要求空间复杂度为O(1),并且也不需要知道环的起始点位置。 38 | 39 | 我们知道,避免遍历在环中死循环除了可以用哈希表记录的方式,还可以用快慢指针记录的方式。慢指针一个一个遍历,快指针两个两个遍历,只要存在环,那么快慢指针一定有机会相遇。若快指针指向了null,说明链表能被遍历完,那么一定不存在环。 40 | 41 | ```ts 42 | function hasCycle(head: ListNode | null): boolean { 43 | let quick = head; 44 | let slow = head; 45 | while (quick && quick.next) { 46 | quick = quick.next.next; 47 | slow = slow.next; 48 | if (slow === quick) { 49 | return true; 50 | } 51 | } 52 | return false; 53 | } 54 | ``` 55 | -------------------------------------------------------------------------------- /docs/docs/list/two-pointers/167.twoSumIiInputArrayIsSorted.md: -------------------------------------------------------------------------------- 1 | # [167] 两数之和 II - 输入有序数组 2 | 3 | > 给定一个已按照升序排列 的有序数组,找到两个数使得它们相加之和等于目标数。 4 | > 5 | > 函数应该返回这两个下标值 index1 和 index2,其中 index1 必须小于 index2。 6 | > 7 | > 说明: 8 | > 9 | > 返回的下标值(index1 和 index2)不是从零开始的。 10 | > 11 | > 你可以假设每个输入只对应唯一的答案,而且你不可以重复使用相同的元素。 12 | > 13 | > 示例: 14 | > 15 | > 输入: numbers = [2, 7, 11, 15], target = 9 16 | > 17 | > 输出: [1,2] 18 | > 19 | > 解释: 2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。 20 | 21 | 由于数组已经排好序,因此利用头尾指针能在O(n)的时间内解决该题,比结果大前移大数指针位置,比结果小后移小数指针位置 22 | 23 | ```js 24 | var twoSum = function(numbers, target) { 25 | const size = numbers.length; 26 | let lp = 0; 27 | let rp = size - 1; 28 | while (lp < rp) { 29 | if (numbers[lp] + numbers[rp] === target) { 30 | return [lp + 1, rp + 1]; 31 | } else if (numbers[lp] + numbers[rp] < target) { 32 | lp++; 33 | } else { 34 | rp--; 35 | } 36 | } 37 | return []; 38 | }; 39 | ``` 40 | -------------------------------------------------------------------------------- /docs/docs/list/two-pointers/19.removeNthNodeFromEndOfList.md: -------------------------------------------------------------------------------- 1 | # [19] 删除链表的倒数第N个节点 2 | 3 | > 给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。 4 | > 5 | > 示例: 6 | > 7 | > 给定一个链表: 1->2->3->4->5, 和 n = 2. 8 | > 9 | > 当删除了倒数第二个节点后,链表变为 1->2->3->5. 10 | > 11 | > 说明: 12 | > 13 | > 给定的 n 保证是有效的。 14 | > 15 | > 进阶: 16 | > 17 | > 你能尝试使用一趟扫描实现吗? 18 | 19 | 这道题用正常思维很好解决,链表最大的问题就是不知道链表长度,那么只需要一次遍历并计数即可获取,然后在第二次遍历是寻找第length - n个节点即可得到倒数第k个节点了。 20 | 21 | 题目要求一次遍历搞定,那又要祭出我们的快慢指针大法了,先让快指针走n - 1步,然后慢指针和快指针一同前进,当快指针遍历到末尾了自然慢指针就走到了length - n的位置了。 22 | 23 | 注意加上一个虚头结点再开始遍历,防止删除的元素为头结点。 24 | 25 | ```js 26 | var removeNthFromEnd = function(head, n) { 27 | const nude = new ListNode(null); 28 | nude.next = head; 29 | 30 | let slow = nude; 31 | let fast = nude; 32 | 33 | while (n--) { 34 | fast = fast.next; 35 | } 36 | 37 | while (fast.next) { 38 | slow = slow.next; 39 | fast = fast.next; 40 | } 41 | slow.next = slow.next.next; 42 | 43 | return nude.next; 44 | }; 45 | 46 | function ListNode(val) { 47 | this.val = val; 48 | this.next = null; 49 | } 50 | ``` 51 | -------------------------------------------------------------------------------- /docs/docs/list/two-pointers/26.removeDuplicatesFromSortedArray.md: -------------------------------------------------------------------------------- 1 | # [26] 删除排序数组中的重复项 2 | 3 | >给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。 4 | > 5 | >不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。 6 | > 7 | > 示例 1: 8 | > 9 | > 给定数组 nums = [1,1,2], 10 | > 11 | > 函数应该返回新的长度 2, 并且原数组 nums 的前两个元素被修改为 1, 2。 12 | > 13 | > 你不需要考虑数组中超出新长度后面的元素。 14 | > 15 | > 示例 2: 16 | > 17 | > 给定 nums = [0,0,1,1,1,2,2,3,3,4], 18 | > 19 | > 函数应该返回新的长度 5, 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4。 20 | > 21 | > 你不需要考虑数组中超出新长度后面的元素。 22 | 23 | 本来只是一个基本的数组去重动作,但是题目要求我们使用原地算法(空间复杂度O(1)),并且不需要考虑超出长度后面的元素,也就是说我们只需要保证前K个数是有序去重的即可。 24 | 25 | 那我们可以用快慢指针来解决这个问题。快指针遍历整个数组,慢指针指向当前已排好序的前K个数。最后返回的长度为慢指针索引+1。 26 | 27 | ```ts 28 | function removeDuplicates(nums: number[]): number { 29 | let left = 0; 30 | let right = 1; 31 | while (right < nums.length) { 32 | if (nums[right] === nums[left]) { 33 | right += 1; 34 | } else { 35 | left += 1; 36 | swap(left, right); 37 | right += 1; 38 | } 39 | } 40 | return left + 1; 41 | 42 | function swap(i: number, j: number) { 43 | const temp = nums[i]; 44 | nums[i] = nums[j]; 45 | nums[j] = temp; 46 | } 47 | } 48 | ``` 49 | -------------------------------------------------------------------------------- /docs/docs/list/two-pointers/283.moveZeroes.md: -------------------------------------------------------------------------------- 1 | # [283] 移动零 2 | 3 | > 给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。 4 | > 5 | > 示例: 6 | > 7 | > 输入: [0,1,0,3,12] 8 | > 9 | > 输出: [1,3,12,0,0] 10 | > 11 | > 说明: 12 | > 13 | > 必须在原数组上操作,不能拷贝额外的数组。 14 | > 15 | > 尽量减少操作次数。 16 | 17 | 因为是原地算法,因此考虑用快慢指针。 18 | 19 | 快指针不停向后遍历数组,慢指针指向数组第一个有0的位置。 20 | 21 | 若快指针指向的值不为0,则交换快慢指针的值,并将慢指针+1,指向下一个0的位置(快慢指针之间的值必然全为0) 22 | 23 | ```ts 24 | function moveZeroes(nums: number[]): void { 25 | const len = nums.length; 26 | // 指针每次指向第一个零的位置 27 | let point = 0; 28 | for (let i = 0; i < len; i++) { 29 | if (nums[i] !== 0) { 30 | const temp = nums[i]; 31 | nums[i] = nums[point]; 32 | nums[point] = temp; 33 | point += 1; 34 | } 35 | } 36 | } 37 | ``` 38 | -------------------------------------------------------------------------------- /docs/docs/list/two-pointers/344.reverseString.md: -------------------------------------------------------------------------------- 1 | # [344] 反转字符串 2 | 3 | > 编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 char[] 的形式给出。 4 | > 5 | > 不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。 6 | > 7 | > 你可以假设数组中的所有字符都是 ASCII 码表中的可打印字符。 8 | > 9 | > 示例 1: 10 | > 11 | > 输入:["h","e","l","l","o"] 12 | > 13 | > 输出:["o","l","l","e","h"] 14 | > 15 | > 示例 2: 16 | > 17 | > 输入:["H","a","n","n","a","h"] 18 | > 19 | > 输出:["h","a","n","n","a","H"] 20 | 21 | 这道题是一个普通的头尾指针算法。不断交换头尾指针所对应的值,然后依次向右向左缩窄头尾范围即可。 22 | 23 | ```js 24 | function reverseString(s: string[]): void { 25 | let left = 0; 26 | let right = s.length - 1; 27 | while (left < right) { 28 | const temp = s[left]; 29 | s[left] = s[right]; 30 | s[right] = temp; 31 | left += 1; 32 | right -= 1; 33 | } 34 | } 35 | ``` 36 | -------------------------------------------------------------------------------- /docs/docs/list/two-pointers/88.mergeSortedArray.md: -------------------------------------------------------------------------------- 1 | # [88] 合并两个有序数组 2 | 3 | > 给你两个有序整数数组 nums1 和 nums2,请你将 nums2 合并到 nums1 中,使 num1 成为一个有序数组。 4 | > 5 | > 说明: 6 | > 7 | > 初始化 nums1 和 nums2 的元素数量分别为 m 和 n 。 8 | > 9 | > 你可以假设 nums1 有足够的空间(空间大小大于或等于 m + n)来保存 nums2 中的元素。 10 | > 11 | > 示例: 12 | > 13 | > 输入: nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3 14 | > 15 | > 输出: [1,2,2,3,5,6] 16 | 17 | 原地算法,一般用双指针法比较多,从后往前插入排序 18 | 19 | ```js 20 | var merge = function(nums1, m, nums2, n) { 21 | let p1 = m - 1; 22 | let p2 = n - 1; 23 | let cur = m + n - 1; 24 | while (p1 >= 0 && p2 >= 0) { 25 | if (nums1[p1] > nums2[p2]) { 26 | nums1[cur] = nums1[p1]; 27 | p1--; 28 | } else { 29 | nums1[cur] = nums2[p2]; 30 | p2--; 31 | } 32 | cur--; 33 | } 34 | while (p2 >= 0) { 35 | nums1[cur] = nums2[p2]; 36 | p2--; 37 | cur--; 38 | } 39 | }; 40 | ``` 41 | -------------------------------------------------------------------------------- /docs/docs/topic/0.introduction.md: -------------------------------------------------------------------------------- 1 | # 前言 2 | 3 | 算法这门学问,在很多计算机系的同学看来十分的高深。因为由老教授讲出来的知识总是由数学形式展开并做推导,在我看来这无疑是在加大算法的学习门槛。 4 | 5 | 但其实,对于工程人才的我们来说,算法真没有那么阳春白雪。它更像是我们在复杂工程实践中的总结与提炼,培养计算机思维与程序设计模式的一种抽象化的提炼。 6 | 7 | 高中时打 NOIP,大学时打 ACM,所用的战术更像是在混沌中不断训练,对于能 AC 的题只能说一句,唯手熟尔。我们的竞赛更像是传统医学的思路,完全是靠灵感来解题,知其然不知其所以然。 8 | 9 | 而到了现在,很庆幸我们生在一个幸运的时代,算法的知识与思想已经深入到工程项目的方方面面。我们不再是为了纯粹的竞赛而学习算法,而是真的可以通过精深算法来拓展我们的解决思路,优化我们的代码性能,在项目中体现自己的价值。 10 | 11 | 这也是为什么近年来一些国际大厂,例如谷歌、微软,他们在面试一位候选人时更倾向去面试算法能力而非项目经历,因为成功的业务很多都可能跟时运有关,而算法能力才能体现自己的思维能力。 12 | 13 | 现代工程开发,对于前端工程师的要求早已不只是会还原样式,调试 API 的鄙视链底层的页面仔,而是一个真正需要探索性能优化,支撑起巨型项目架构的重要一环。因此逻辑的复杂度以及可钻研的深度自然不可同日而语。因此我仍认为,所有的软件开发人员都必须对算法知识有较为深刻的理解。 14 | 15 | 而作为在职的打工人来说,我们很难花费大量时间也没有必要去阅读大部头书籍来深入算法原理。我们的目标只是理解算法与数据结构的设计思路,帮助我们在需要时运用在实际的工程项目中来优化表现。相信我,在工程项目中,想到可以优化的点要比如何去优化重要得多,也难得多。 16 | 17 | 因此我决定写一个专栏,帮助更多对此感兴趣的工程人才拓展算法思维,提升发现问题的能力。 18 | 19 | 再强调一遍,本专栏旨在提炼算法的思想,拓展技术视野,提升自己的项目质量,而非囿于某一道具体的 LeetCode 题目是否能做得出来为我的写作目标。不要从页面仔转变为做题家,要担得起工程师的称谓。 20 | -------------------------------------------------------------------------------- /docs/docs/topic/1.recursive.md: -------------------------------------------------------------------------------- 1 | # 重新认识递归 2 | 3 | 正确的学习并理解递归,是整个算法体系中最基础,也是最重要的一节。 4 | 5 | 在程序设计领域,递归是指函数(或方法)直接或间接调用自身的一种操作,如下图所示。 6 | 7 | ![20220627170909](https://zakum-1252497671.cos.ap-guangzhou.myqcloud.com/20220627170909.png) 8 | 9 | 很多同学可能会觉得,递归的做法绝大部分存在于教科书中,而工程中应该摒弃这种写法。至于理由嘛,一是不好理解,人脑并不像电脑那样能够拥有无穷的栈空间,正确的代码并不容易实现出来,或者花在 debug 的时间很长。二是听说递归的运行效率较差,写出来就显得自己不专业。 10 | 11 | 其实真相与大家认为的不太一致。递归相比于我们日常生活中的逻辑推演来说的确是一个逆向思维,但从解决一个抽象问题的角度来看,递归其实才是最符合人类解题思路的方式。如果用递归的思路来看待问题,我们需要清楚的就不是从哪里开始解决问题,而只需要知道下一步该干什么就行。而这也就是算法书中所说的,**自顶向下**的方法。 12 | 13 | 递归仅需抓住两个关键点:**输入是什么**,以及**什么时候停止递归并输出**。从而避免了我们仔细的去控制嵌套过程中的各个变量的变化关系。因此,从这个角度来说,递归的写法其实更易于程序员对功能进行了解,而不必深抠细节。同时,由于无需定义中间层变量,代码量也大为精简。 14 | 15 | 另外说到执行效率低的问题。诚然,对大部分语言来说,递归都是使用的**栈机制**来实现的,对于嵌套数极多的调用来说,使用递归的方式占用的空间(或内存)会变大,另外,递归会带来额外的函数寻址及调用开销,确实在一定程度上影响运行效率。但对现代程序设计的绝大部分应用场景来说,这点多余的调用对于性能的影响可以说微不足道。 16 | 17 | 绝大部分递归反对者可能遇到的真实情况是,在使用递归时使用了不恰当的返回时机,或者相比迭代方法,少考虑了可以剪枝的情况,从而导致程序执行了多次无用的操作,导致性能下降。关于这一点就显然是程序设计者自身的水平问题了,递归可不背这个锅。 18 | 19 | ![20220627172828](https://zakum-1252497671.cos.ap-guangzhou.myqcloud.com/20220627172828.png) 20 | 21 | 总而言之,虽然递归的解题方式可能在执行效率上略有影响,但它在抽象问题或者复杂情景下,对解题思路的梳理,以及程序的可读性上都有着极大的帮助,非常适合解决抽象的算法提型。我们在之后的算法专题解析中将会明显的发现这一点。 22 | 23 | 即使抛开算法不谈,递归思维的养成,对快速了解工程中的复杂场景也是很有帮助的。比如从现在开始,以递归的思维方式,从入口处开始粗读 React 的源码,你会有事半功倍的感觉。 24 | 25 | 当然,任何问题的解决都没有银弹,递归思维也许不是万能的,但在不了解递归前,消除对递归的偏见,时刻想着还有这么一种简化问题的思维,这对成为计算机人才来说很有必要。 26 | -------------------------------------------------------------------------------- /docs/docs/topic/12. dynamic-programming-subsequence.md: -------------------------------------------------------------------------------- 1 | # 动态规划 - 子序列问题 2 | 3 | 之前说过,遇到求极值的问题我们首先可以考虑是否可以使用动态规划来解。 4 | 5 | 例如求最长子序列的系列问题。 6 | 7 | 首先最重要的一步是审题。注意最长**子序列**与最长**子数组**之间的区分,最长子数组要求连成的序列是**连续**的,而子序列是可以跳过子数组中某些元素的。 8 | 9 | 像这类问题,通常是设 `dp[i]` 为以 `nums[i]` 结尾的子序列的最值的求值结果。而既然是求子数组,那么通常 dp 数组就仅与 `dp[i-1]` 相关。若是求子序列,则 `dp[i]` 的结果可能需要遍历从 0 到 i 中的 dp 数组 ,取出其中的最大值来求解。 10 | 11 | 参考题型: 12 | 13 | 1. `[300] 最长递增子序列` 14 | 2. `[674] 最长连续递增序列` 15 | 3. `[718] 最长重复子数组` 16 | 4. `[1143] 最长公共子序列` 17 | 5. `[647] 回文子串` 18 | 6. `[516] 最长回文子序列` 19 | 7. `[53] 最大子序和` 20 | 8. `[583] 两个字符串的删除操作` 21 | 9. `[72] 编辑距离` 22 | -------------------------------------------------------------------------------- /docs/docs/topic/3.binary-search-tree.md: -------------------------------------------------------------------------------- 1 | # 二叉搜索树 2 | 3 | 二叉搜索树,即 BST (Binary Search Tree)。我们先来看看它的定义: 4 | 5 | > 二叉搜索树是一棵空树,或者是具有下列性质的二叉树: 6 | > 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 7 | > 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值。 8 | 9 | 从它的定义中,我们可以总结出它拥有的两个特性: 10 | 11 | 1. 对于 BST 的每一个节点 node,左子树节点的值都比 node 的值要小,右子树节点的值都比 node 的值大。 12 | 2. 对于 BST 的每一个节点 node,它的左侧子树和右侧子树都是 BST。 13 | 14 | 这是一个非常重要的数据结构设计。经过自平衡优化的 BST,如红黑树,AVL树,能够从增删改查各个方面全部实现 O(lgN) 级别的时间复杂度。 15 | 16 | 从它的两个特性我们可以注意到,它与我们在快速排序完成后的树形结构是一模一样的。也就是说,二叉搜索树的**中序遍历结果一定是有序的**。这个特性对我们解题的思路十分有帮助。 17 | 18 | 例如在 `[230] 二叉搜索树中第K小的元素` 中,我们需要在 BST 中找出第 K 小的元素。由于 BST 其中序遍历是有序的,因此题目就被直接简化成:输出中序遍历的第K个结果。题目就变得十分简单了。 19 | 20 | ```ts 21 | function kthSmallest(root: TreeNode | null, k: number): number { 22 | let count = 0; 23 | let res = root.val; 24 | traverse(root); 25 | return res; 26 | 27 | function traverse(node: TreeNode | null) { 28 | if (!node) return; 29 | 30 | traverse(node.left); 31 | count += 1; 32 | if (count === k) { 33 | res = node.val; 34 | return; 35 | } 36 | traverse(node.right); 37 | } 38 | } 39 | ``` 40 | -------------------------------------------------------------------------------- /docs/docs/topic/5.two-pointers.md: -------------------------------------------------------------------------------- 1 | # 双指针问题 2 | 3 | 这类题型的涉及面非常广,从原地遍历到二分查询都有着该问题的缩影。 4 | 5 | 但是这里,我将双指针的类型分为两类,首尾指针和快慢指针。可以通过这两类问题的思路来举一反三,通解这一类题型。 6 | 7 | ## 快慢指针 8 | 9 | 这一题型在链表问题中十分常用。 10 | 11 | 例如经典的判断链表是否存在环的问题:[141] 环形链表 与 [142] 环形链表 II,实际上就是通过有速度差的快慢指针是否相遇来解决的。 12 | 13 | 再例如如果要删除链表中的一个节点,那么必然需要两个指针,一个指向被删的前一个节点,一个指向后一个节点。这实际上也是快慢指针的一个用法。 14 | 15 | 相关题型可见: 16 | 17 | 1. `[141] 环形链表` 18 | 2. `[142] 环形链表 II` 19 | 20 | ## 首尾指针 21 | 22 | 首尾指针的应用范围就更广了,例如最经典的二分查找、归并排序算法,其中都是依靠着对每一个部分进行首尾指针相关的操作,来缩小解题范围,最终逼近确定值的过程。 23 | 24 | 首尾指针的题型有很多,大体可以分为两类,一类是同向双指针问题,一类是反向双指针问题。 25 | 26 | 同向双指针问题即为滑动窗口问题,指针初始化在窗口的首尾两侧。本质上就是判断一个区间内的元素是否满足某种条件,如果满足则缩小左边界,如果不满足则扩大右边界。我们会单开一个专题来讲解这一类问题。 27 | 28 | 反向双指针问题其实更符合首尾指针的定义,即指针初始化在数组的两端,然后向中间移动。这类问题一般是求最值问题,例如求最长回文子串、求最小覆盖子串等。 29 | 30 | 1. `[125] 验证回文串` 31 | 2. `[344] 反转字符串` 32 | -------------------------------------------------------------------------------- /docs/docs/topic/8.depth-first-search.md: -------------------------------------------------------------------------------- 1 | # 深度优先搜索(DFS) 2 | 3 | 在树的遍历方式中,深度优先搜索法与广度优先搜索算是最常用的手段了。这一篇来聊聊深度优先搜索。 4 | 5 | 深度优先搜索,本质上就是`回溯算法`,是一个构建并遍历决策树的过程。 6 | 7 | 既然是遍历树,那么自然就离不了树的前中后序遍历。实际上,这三种遍历法其实都是深度优先搜索的一种体现。 8 | 9 | DFS 要理解不难,难点在于如何善于利用题目特征,巧妙的进行剪枝,从而规避问题或复杂度。 10 | 11 | ## Flood Fill 系列 12 | 13 | 像经典的`岛屿系列问题`,用的就是`Flood Fill`的思路来解决的。 14 | 15 | 重点有几个: 16 | 17 | 1. 善用方向数组解决递归选择 18 | 2. 使用 visited 数组记录已经被遍历过的节点。当然,这个有时可以通过`Flood Fill`淹没掉遍历节点来节省空间复杂度。 19 | 20 | Flood Fill 解题思路框架如下,该情况下不需要记录 visited 数组,也不需要撤销选择状态: 21 | 22 | ```ts 23 | const direction = [ 24 | [-1, 0], 25 | [1, 0], 26 | [0, -1], 27 | [0, 1] 28 | ]; 29 | 30 | function backtrack(row: number, col: number) { 31 | // 越界处理 32 | if (row < 0 || col < 0 || row >= rowLen || col >= colLen) return; 33 | // 非目标节点 34 | if (grid[row][col] !== 目标值) return; 35 | 36 | // 做选择 37 | grid[row][col] = 目标值; 38 | 39 | for (const [i, j] of direction) { 40 | backtrack(row + i, col + j); 41 | } 42 | } 43 | ``` 44 | 45 | 因此,我们可以总结出解决 DFS 问题的一般步骤: 46 | 47 | ```ts 48 | function dfs(curr) { 49 | if (visited[curr] || curr 越界 || curr 不满足要求) return; 50 | 51 | // 做选择 52 | visited[curr] = true; 53 | 54 | // ... 更新目标值 55 | 56 | for (const new of 新产生的情况) { 57 | // 进入下一次递归 58 | dfs(new); 59 | } 60 | } 61 | ``` 62 | 63 | 题型参考: 64 | 65 | 1. `[733] 图像渲染` 66 | 2. `[695] 岛屿的最大面积` 67 | 3. `[200] 岛屿数量` 68 | 4. `[130] 被围绕的区域` 69 | 5. `[547] 省份数量` 70 | 6. `[1254] 统计封闭岛屿的数目` 71 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "leetcode-in-javascript", 3 | "version": "1.0.0", 4 | "description": "使用js的leetcode题解仓库", 5 | "directories": { 6 | "doc": "docs" 7 | }, 8 | "dependencies": { 9 | "vuepress": "^1.9.2" 10 | }, 11 | "devDependencies": { 12 | "@types/node": "^18.0.3", 13 | "@typescript-eslint/eslint-plugin": "^5.10.0", 14 | "@typescript-eslint/parser": "^5.10.0", 15 | "babel-eslint": "^10.1.0", 16 | "eslint": "^8.7.0", 17 | "eslint-config-alloy": "^4.4.0", 18 | "ts-node": "^10.4.0", 19 | "typescript": "^4.5.4", 20 | "vuepress-bar": "^0.4.4" 21 | }, 22 | "scripts": { 23 | "dev": "vuepress dev docs", 24 | "build": "vuepress build docs", 25 | "test": "echo \"Error: no test specified\" && exit 1" 26 | }, 27 | "repository": { 28 | "type": "git", 29 | "url": "git+https://github.com/realDuang/leetcode-in-javascript.git" 30 | }, 31 | "keywords": [ 32 | "leetcode" 33 | ], 34 | "author": "Duang", 35 | "license": "MIT", 36 | "bugs": { 37 | "url": "https://github.com/realDuang/leetcode-in-javascript/issues" 38 | }, 39 | "homepage": "https://github.com/realDuang/leetcode-in-javascript#readme" 40 | } 41 | -------------------------------------------------------------------------------- /src/Unknown/815.公交路线.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @lc app=leetcode.cn id=815 lang=typescript 3 | * 4 | * [815] 公交路线 5 | * 6 | * https://leetcode.cn/problems/bus-routes/description/ 7 | * 8 | * algorithms 9 | * Hard (46.81%) 10 | * Likes: 432 11 | * Dislikes: 0 12 | * Total Accepted: 52.2K 13 | * Total Submissions: 111.3K 14 | * Testcase Example: '[[1,2,7],[3,6,7]]\n1\n6' 15 | * 16 | * 给你一个数组 routes ,表示一系列公交线路,其中每个 routes[i] 表示一条公交线路,第 i 辆公交车将会在上面循环行驶。 17 | * 18 | * 19 | * 例如,路线 routes[0] = [1, 5, 7] 表示第 0 辆公交车会一直按序列 1 -> 5 -> 7 -> 1 -> 5 -> 7 -> 1 20 | * -> ... 这样的车站路线行驶。 21 | * 22 | * 23 | * 现在从 source 车站出发(初始时不在公交车上),要前往 target 车站。 期间仅可乘坐公交车。 24 | * 25 | * 求出 最少乘坐的公交车数量 。如果不可能到达终点车站,返回 -1 。 26 | * 27 | * 28 | * 29 | * 示例 1: 30 | * 31 | * 32 | * 输入:routes = [[1,2,7],[3,6,7]], source = 1, target = 6 33 | * 输出:2 34 | * 解释:最优策略是先乘坐第一辆公交车到达车站 7 , 然后换乘第二辆公交车到车站 6 。 35 | * 36 | * 37 | * 示例 2: 38 | * 39 | * 40 | * 输入:routes = [[7,12],[4,5,15],[6],[15,19],[9,12,13]], source = 15, target = 41 | * 12 42 | * 输出:-1 43 | * 44 | * 45 | * 46 | * 47 | * 提示: 48 | * 49 | * 50 | * 1 . 51 | * 1 52 | * routes[i] 中的所有值 互不相同 53 | * sum(routes[i].length) 54 | * 0 55 | * 0 56 | * 57 | * 58 | */ 59 | 60 | // @lc code=start 61 | function numBusesToDestination(routes: number[][], source: number, target: number): number { 62 | 63 | }; 64 | // @lc code=end 65 | 66 | -------------------------------------------------------------------------------- /src/array/1.两数之和.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @lc app=leetcode.cn id=1 lang=javascript 3 | * 4 | * [1] 两数之和 5 | * 6 | * https://leetcode-cn.com/problems/two-sum/description/ 7 | * 8 | * algorithms 9 | * Easy (46.61%) 10 | * Likes: 6319 11 | * Dislikes: 0 12 | * Total Accepted: 561.3K 13 | * Total Submissions: 1.2M 14 | * Testcase Example: '[2,7,11,15]\n9' 15 | * 16 | * 给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。 17 | * 18 | * 你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。 19 | * 20 | * 示例: 21 | * 22 | * 给定 nums = [2, 7, 11, 15], target = 9 23 | * 24 | * 因为 nums[0] + nums[1] = 2 + 7 = 9 25 | * 所以返回 [0, 1] 26 | * 27 | * 28 | */ 29 | 30 | // @lc code=start 31 | /** 32 | * @param {number[]} nums 33 | * @param {number} target 34 | * @return {number[]} 35 | */ 36 | var twoSum = function(nums, target) { 37 | const hashObj = {}; 38 | for (let i = 0; i < nums.length; i++) { 39 | const another = hashObj[target - nums[i]]; 40 | if (another !== undefined) { 41 | return [another, i]; 42 | } 43 | hashObj[nums[i]] = i; 44 | } 45 | return []; 46 | }; 47 | // @lc code=end 48 | 49 | console.log(twoSum([7, 7, 11, 15], 14)); -------------------------------------------------------------------------------- /src/array/1137.第N个泰波那契数.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @lc app=leetcode.cn id=1137 lang=typescript 3 | * 4 | * [1137] 第 N 个泰波那契数 5 | * 6 | * https://leetcode-cn.com/problems/n-th-tribonacci-number/description/ 7 | * 8 | * algorithms 9 | * Easy (60.94%) 10 | * Likes: 151 11 | * Dislikes: 0 12 | * Total Accepted: 90.8K 13 | * Total Submissions: 149K 14 | * Testcase Example: '4' 15 | * 16 | * 泰波那契序列 Tn 定义如下: 17 | * 18 | * T0 = 0, T1 = 1, T2 = 1, 且在 n >= 0 的条件下 Tn+3 = Tn + Tn+1 + Tn+2 19 | * 20 | * 给你整数 n,请返回第 n 个泰波那契数 Tn 的值。 21 | * 22 | * 23 | * 24 | * 示例 1: 25 | * 26 | * 输入:n = 4 27 | * 输出:4 28 | * 解释: 29 | * T_3 = 0 + 1 + 1 = 2 30 | * T_4 = 1 + 1 + 2 = 4 31 | * 32 | * 33 | * 示例 2: 34 | * 35 | * 输入:n = 25 36 | * 输出:1389537 37 | * 38 | * 39 | * 40 | * 41 | * 提示: 42 | * 43 | * 44 | * 0 <= n <= 37 45 | * 答案保证是一个 32 位整数,即 answer <= 2^31 - 1。 46 | * 47 | * 48 | */ 49 | 50 | // @lc code=start 51 | function tribonacci(n: number): number { 52 | if (n === 0) return 0; 53 | if (n === 1 || n === 2) return 1; 54 | 55 | let a = 0; 56 | let b = 1; 57 | let c = 1; 58 | 59 | for (let i = 3; i <= n; i++) { 60 | const sum = a + b + c; 61 | a = b; 62 | b = c; 63 | c = sum; 64 | } 65 | return c; 66 | } 67 | // @lc code=end 68 | 69 | (() => { 70 | const n = 25; 71 | console.log(tribonacci(n)); 72 | })(); 73 | -------------------------------------------------------------------------------- /src/array/118.杨辉三角.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @lc app=leetcode.cn id=118 lang=typescript 3 | * 4 | * [118] 杨辉三角 5 | * 6 | * https://leetcode.cn/problems/pascals-triangle/description/ 7 | * 8 | * algorithms 9 | * Easy (75.35%) 10 | * Likes: 841 11 | * Dislikes: 0 12 | * Total Accepted: 345.8K 13 | * Total Submissions: 459K 14 | * Testcase Example: '5' 15 | * 16 | * 给定一个非负整数 numRows,生成「杨辉三角」的前 numRows 行。 17 | * 18 | * 在「杨辉三角」中,每个数是它左上方和右上方的数的和。 19 | * 20 | * 21 | * 22 | * 23 | * 24 | * 示例 1: 25 | * 26 | * 27 | * 输入: numRows = 5 28 | * 输出: [[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]] 29 | * 30 | * 31 | * 示例 2: 32 | * 33 | * 34 | * 输入: numRows = 1 35 | * 输出: [[1]] 36 | * 37 | * 38 | * 39 | * 40 | * 提示: 41 | * 42 | * 43 | * 1 44 | * 45 | * 46 | */ 47 | 48 | // @lc code=start 49 | function generate(numRows: number): number[][] { 50 | const result: number[][] = []; 51 | for (let i = 0; i < numRows; i++) { 52 | const row = new Array(i + 1).fill(1); 53 | for (let j = 1; j < row.length - 1; j++) { 54 | row[j] = result[i - 1][j - 1] + result[i - 1][j]; 55 | } 56 | result.push(row); 57 | } 58 | return result; 59 | }; 60 | // @lc code=end 61 | 62 | (() => { 63 | console.log(generate(5)); 64 | })(); -------------------------------------------------------------------------------- /src/array/485.最大连续1的个数.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @lc app=leetcode.cn id=485 lang=typescript 3 | * 4 | * [485] 最大连续 1 的个数 5 | * 6 | * https://leetcode.cn/problems/max-consecutive-ones/description/ 7 | * 8 | * algorithms 9 | * Easy (61.39%) 10 | * Likes: 438 11 | * Dislikes: 0 12 | * Total Accepted: 248K 13 | * Total Submissions: 403.9K 14 | * Testcase Example: '[1,1,0,1,1,1]' 15 | * 16 | * 给定一个二进制数组 nums , 计算其中最大连续 1 的个数。 17 | * 18 | * 19 | * 20 | * 示例 1: 21 | * 22 | * 23 | * 输入:nums = [1,1,0,1,1,1] 24 | * 输出:3 25 | * 解释:开头的两位和最后的三位都是连续 1 ,所以最大连续 1 的个数是 3. 26 | * 27 | * 28 | * 示例 2: 29 | * 30 | * 31 | * 输入:nums = [1,0,1,1,0,1] 32 | * 输出:2 33 | * 34 | * 35 | * 36 | * 37 | * 提示: 38 | * 39 | * 40 | * 1 <= nums.length <= 10^5 41 | * nums[i] 不是 0 就是 1. 42 | * 43 | * 44 | */ 45 | 46 | // @lc code=start 47 | function findMaxConsecutiveOnes(nums: number[]): number { 48 | let res = 0; 49 | let cnt = 0; 50 | 51 | for (let i = 0; i < nums.length; i++) { 52 | if (nums[i] === 1) { 53 | cnt += 1; 54 | if (cnt > res) { 55 | res = cnt; 56 | } 57 | } else { 58 | cnt = 0; 59 | } 60 | } 61 | 62 | return res; 63 | } 64 | // @lc code=end 65 | 66 | (() => { 67 | const nums = [1, 0, 1, 1, 0, 1]; 68 | console.log(findMaxConsecutiveOnes(nums)); 69 | })(); 70 | -------------------------------------------------------------------------------- /src/array/643.子数组最大平均数I.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @lc app=leetcode.cn id=643 lang=typescript 3 | * 4 | * [643] 子数组最大平均数 I 5 | * 6 | * https://leetcode.cn/problems/maximum-average-subarray-i/description/ 7 | * 8 | * algorithms 9 | * Easy (43.62%) 10 | * Likes: 367 11 | * Dislikes: 0 12 | * Total Accepted: 165.3K 13 | * Total Submissions: 379K 14 | * Testcase Example: '[1,12,-5,-6,50,3]\n4' 15 | * 16 | * 给你一个由 n 个元素组成的整数数组 nums 和一个整数 k 。 17 | * 18 | * 请你找出平均数最大且 长度为 k 的连续子数组,并输出该最大平均数。 19 | * 20 | * 任何误差小于 10^-5 的答案都将被视为正确答案。 21 | * 22 | * 23 | * 24 | * 示例 1: 25 | * 26 | * 27 | * 输入:nums = [1,12,-5,-6,50,3], k = 4 28 | * 输出:12.75 29 | * 解释:最大平均数 (12-5-6+50)/4 = 51/4 = 12.75 30 | * 31 | * 32 | * 示例 2: 33 | * 34 | * 35 | * 输入:nums = [5], k = 1 36 | * 输出:5.00000 37 | * 38 | * 39 | * 40 | * 41 | * 提示: 42 | * 43 | * 44 | * n == nums.length 45 | * 1 <= k <= n <= 10^5 46 | * -10^4 <= nums[i] <= 10^4 47 | * 48 | * 49 | */ 50 | 51 | // @lc code=start 52 | function findMaxAverage(nums: number[], k: number): number { 53 | let l = 0; 54 | let r = k - 1; 55 | let res = Number.MIN_SAFE_INTEGER; 56 | 57 | // 前 n-1 项和 58 | let sum = nums.slice(l, r).reduce((a, b) => a + b, 0); 59 | 60 | while (r < nums.length) { 61 | // 扩大右窗口 62 | sum += nums[r]; 63 | res = Math.max(res, sum / k); 64 | // 缩小左窗口 65 | sum -= nums[l]; 66 | 67 | l++; 68 | r++; 69 | } 70 | 71 | return res; 72 | } 73 | // @lc code=end 74 | 75 | (() => { 76 | const nums = [1, 12, -5, -6, 50, 3], 77 | k = 4; 78 | console.log(findMaxAverage(nums, k)); 79 | })(); 80 | -------------------------------------------------------------------------------- /src/backtracking/22.括号生成.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @lc app=leetcode.cn id=22 lang=javascript 3 | * 4 | * [22] 括号生成 5 | * 6 | * https://leetcode-cn.com/problems/generate-parentheses/description/ 7 | * 8 | * algorithms 9 | * Medium (73.67%) 10 | * Likes: 839 11 | * Dislikes: 0 12 | * Total Accepted: 94.3K 13 | * Total Submissions: 127.9K 14 | * Testcase Example: '3' 15 | * 16 | * 给出 n 代表生成括号的对数,请你写出一个函数,使其能够生成所有可能的并且有效的括号组合。 17 | * 18 | * 例如,给出 n = 3,生成结果为: 19 | * 20 | * [ 21 | * ⁠ "((()))", 22 | * ⁠ "(()())", 23 | * ⁠ "(())()", 24 | * ⁠ "()(())", 25 | * ⁠ "()()()" 26 | * ] 27 | * 28 | * 29 | */ 30 | 31 | // @lc code=start 32 | /** 33 | * @param {number} n 34 | * @return {string[]} 35 | */ 36 | var generateParenthesis = function(n) { 37 | const res = []; 38 | backtrack('', n, n); 39 | return res; 40 | 41 | function backtrack(str, left, right) { 42 | if (left < 0 || right < 0 || left > right) return; 43 | if (left === 0) { 44 | str += ')'.repeat(right); 45 | res.push(str); 46 | return; 47 | } 48 | 49 | backtrack(str + '(', left - 1, right); 50 | backtrack(str + ')', left, right - 1); 51 | } 52 | }; 53 | // @lc code=end 54 | 55 | console.log(generateParenthesis(3)); 56 | 57 | // 这道题可以用递归来解,本质上就是一次二叉树的深度优先遍历。难点关键在于如何剪枝,减少不必要的递归次数。 58 | // 我们可以发现,若每次增加括号的过程中,若左括号的数量大于右括号,则当前递归一定是不合法的,直接剪掉。若左括号已经用完,那么将剩下的所有右括号全部加入进字符串即可。之后生成一种合法的字符串,然后加入到res数组中。 59 | -------------------------------------------------------------------------------- /src/backtracking/22.括号生成.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @lc app=leetcode.cn id=22 lang=typescript 3 | * 4 | * [22] 括号生成 5 | * 6 | * https://leetcode-cn.com/problems/generate-parentheses/description/ 7 | * 8 | * algorithms 9 | * Medium (77.36%) 10 | * Likes: 2367 11 | * Dislikes: 0 12 | * Total Accepted: 425K 13 | * Total Submissions: 549.3K 14 | * Testcase Example: '3' 15 | * 16 | * 数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。 17 | * 18 | * 19 | * 20 | * 示例 1: 21 | * 22 | * 23 | * 输入:n = 3 24 | * 输出:["((()))","(()())","(())()","()(())","()()()"] 25 | * 26 | * 27 | * 示例 2: 28 | * 29 | * 30 | * 输入:n = 1 31 | * 输出:["()"] 32 | * 33 | * 34 | * 35 | * 36 | * 提示: 37 | * 38 | * 39 | * 1 <= n <= 8 40 | * 41 | * 42 | */ 43 | 44 | // @lc code=start 45 | function generateParenthesis(n: number): string[] { 46 | const res: string[] = []; 47 | backtrack('', 0, 0); 48 | return res; 49 | 50 | function backtrack(str: string, left: number, right: number) { 51 | if (left === n && right === n) { 52 | res.push(str); 53 | } 54 | left < n && backtrack(str + '(', left + 1, right); 55 | right < left && backtrack(str + ')', left, right + 1); 56 | } 57 | } 58 | // @lc code=end 59 | 60 | (() => { 61 | const n = 3; 62 | console.log(generateParenthesis(n)); 63 | })(); 64 | -------------------------------------------------------------------------------- /src/backtracking/46.全排列.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @lc app=leetcode.cn id=46 lang=javascript 3 | * 4 | * [46] 全排列 5 | * 6 | * https://leetcode-cn.com/problems/permutations/description/ 7 | * 8 | * algorithms 9 | * Medium (74.47%) 10 | * Likes: 614 11 | * Dislikes: 0 12 | * Total Accepted: 103.2K 13 | * Total Submissions: 138K 14 | * Testcase Example: '[1,2,3]' 15 | * 16 | * 给定一个 没有重复 数字的序列,返回其所有可能的全排列。 17 | * 18 | * 示例: 19 | * 20 | * 输入: [1,2,3] 21 | * 输出: 22 | * [ 23 | * ⁠ [1,2,3], 24 | * ⁠ [1,3,2], 25 | * ⁠ [2,1,3], 26 | * ⁠ [2,3,1], 27 | * ⁠ [3,1,2], 28 | * ⁠ [3,2,1] 29 | * ] 30 | * 31 | */ 32 | 33 | // @lc code=start 34 | /** 35 | * @param {number[]} nums 36 | * @return {number[][]} 37 | */ 38 | var permute = function(nums) { 39 | const res = []; 40 | backtrack(nums, []); 41 | return res; 42 | 43 | function backtrack(rest, path) { 44 | if (rest.length === 0) { 45 | res.push(path); 46 | return; 47 | } 48 | for (let i = 0; i < rest.length; i++) { 49 | // 若选择该元素,剩余可选数减除该元素,路径增加该元素 50 | const num = rest.splice(i, 1)[0]; 51 | backtrack(rest, [...path, num]); 52 | // 否则恢复该元素后,进行其他分支递归 53 | rest.splice(i, 0, num); 54 | } 55 | } 56 | }; 57 | 58 | // @lc code=end 59 | 60 | console.log(permute([1, 2, 3])); 61 | -------------------------------------------------------------------------------- /src/backtracking/77.组合.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @lc app=leetcode.cn id=77 lang=javascript 3 | * 4 | * [77] 组合 5 | * 6 | * https://leetcode-cn.com/problems/combinations/description/ 7 | * 8 | * algorithms 9 | * Medium (73.30%) 10 | * Likes: 251 11 | * Dislikes: 0 12 | * Total Accepted: 45.5K 13 | * Total Submissions: 61.9K 14 | * Testcase Example: '4\n2' 15 | * 16 | * 给定两个整数 n 和 k,返回 1 ... n 中所有可能的 k 个数的组合。 17 | * 18 | * 示例: 19 | * 20 | * 输入: n = 4, k = 2 21 | * 输出: 22 | * [ 23 | * ⁠ [2,4], 24 | * ⁠ [3,4], 25 | * ⁠ [2,3], 26 | * ⁠ [1,2], 27 | * ⁠ [1,3], 28 | * ⁠ [1,4], 29 | * ] 30 | * 31 | */ 32 | 33 | // @lc code=start 34 | /** 35 | * @param {number} n 36 | * @param {number} k 37 | * @return {number[][]} 38 | */ 39 | var combine = function(n, k) { 40 | const res = []; 41 | backtrack(k, [], 1); 42 | return res; 43 | 44 | function backtrack(k, path, start) { 45 | if (k === 0) { 46 | res.push([...path]); 47 | return; 48 | } 49 | for (let i = start; i <= n; i++) { 50 | path.push(i); 51 | backtrack(k - 1, path, i + 1); 52 | path.pop(); 53 | } 54 | } 55 | }; 56 | // @lc code=end 57 | 58 | console.log(combine(4, 2)); 59 | -------------------------------------------------------------------------------- /src/backtracking/77.组合.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @lc app=leetcode.cn id=77 lang=typescript 3 | * 4 | * [77] 组合 5 | * 6 | * https://leetcode-cn.com/problems/combinations/description/ 7 | * 8 | * algorithms 9 | * Medium (76.97%) 10 | * Likes: 879 11 | * Dislikes: 0 12 | * Total Accepted: 288K 13 | * Total Submissions: 374.2K 14 | * Testcase Example: '4\n2' 15 | * 16 | * 给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。 17 | * 18 | * 你可以按 任何顺序 返回答案。 19 | * 20 | * 21 | * 22 | * 示例 1: 23 | * 24 | * 25 | * 输入:n = 4, k = 2 26 | * 输出: 27 | * [ 28 | * ⁠ [2,4], 29 | * ⁠ [3,4], 30 | * ⁠ [2,3], 31 | * ⁠ [1,2], 32 | * ⁠ [1,3], 33 | * ⁠ [1,4], 34 | * ] 35 | * 36 | * 示例 2: 37 | * 38 | * 39 | * 输入:n = 1, k = 1 40 | * 输出:[[1]] 41 | * 42 | * 43 | * 44 | * 提示: 45 | * 46 | * 47 | * 1 48 | * 1 49 | * 50 | * 51 | */ 52 | 53 | // @lc code=start 54 | function combine(n: number, k: number): number[][] { 55 | const res: number[][] = []; 56 | const path: number[] = []; 57 | backtrack(1); 58 | return res; 59 | 60 | function backtrack(start: number) { 61 | if (path.length === k) { 62 | res.push([...path]); 63 | return; 64 | } 65 | 66 | for (let i = start; i <= n; i++) { 67 | path.push(i); 68 | backtrack(i + 1); 69 | path.pop(); 70 | } 71 | } 72 | } 73 | // @lc code=end 74 | 75 | (() => { 76 | const n = 4, 77 | k = 2; 78 | console.log(combine(n, k)); 79 | })(); 80 | -------------------------------------------------------------------------------- /src/backtracking/78.子集.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @lc app=leetcode.cn id=78 lang=javascript 3 | * 4 | * [78] 子集 5 | * 6 | * https://leetcode-cn.com/problems/subsets/description/ 7 | * 8 | * algorithms 9 | * Medium (76.85%) 10 | * Likes: 543 11 | * Dislikes: 0 12 | * Total Accepted: 80.6K 13 | * Total Submissions: 104.4K 14 | * Testcase Example: '[1,2,3]' 15 | * 16 | * 给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。 17 | * 18 | * 说明:解集不能包含重复的子集。 19 | * 20 | * 示例: 21 | * 22 | * 输入: nums = [1,2,3] 23 | * 输出: 24 | * [ 25 | * ⁠ [3], 26 | * [1], 27 | * [2], 28 | * [1,2,3], 29 | * [1,3], 30 | * [2,3], 31 | * [1,2], 32 | * [] 33 | * ] 34 | * 35 | */ 36 | 37 | // @lc code=start 38 | /** 39 | * @param {number[]} nums 40 | * @return {number[][]} 41 | */ 42 | var subsets = function(nums) { 43 | const res = []; 44 | backtrack(nums, [], 0); 45 | return res; 46 | 47 | function backtrack(nums, path, start) { 48 | // 多叉树的前序遍历 49 | res.push([...path]); 50 | for (let i = start; i < nums.length; i++) { 51 | path.push(nums[i]); 52 | backtrack(nums, path, i + 1); 53 | path.pop(); 54 | } 55 | } 56 | }; 57 | // @lc code=end 58 | 59 | console.log(subsets([1, 2, 3])); 60 | -------------------------------------------------------------------------------- /src/backtracking/78.子集.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @lc app=leetcode.cn id=78 lang=typescript 3 | * 4 | * [78] 子集 5 | * 6 | * https://leetcode-cn.com/problems/subsets/description/ 7 | * 8 | * algorithms 9 | * Medium (80.27%) 10 | * Likes: 1455 11 | * Dislikes: 0 12 | * Total Accepted: 360.7K 13 | * Total Submissions: 449.3K 14 | * Testcase Example: '[1,2,3]' 15 | * 16 | * 给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。 17 | * 18 | * 解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。 19 | * 20 | * 21 | * 22 | * 示例 1: 23 | * 24 | * 25 | * 输入:nums = [1,2,3] 26 | * 输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]] 27 | * 28 | * 29 | * 示例 2: 30 | * 31 | * 32 | * 输入:nums = [0] 33 | * 输出:[[],[0]] 34 | * 35 | * 36 | * 37 | * 38 | * 提示: 39 | * 40 | * 41 | * 1 42 | * -10 43 | * nums 中的所有元素 互不相同 44 | * 45 | * 46 | */ 47 | 48 | // @lc code=start 49 | function subsets(nums: number[]): number[][] { 50 | const res: number[][] = []; 51 | backtrack([], 0); 52 | return res; 53 | 54 | function backtrack(path: number[], start: number) { 55 | // 类似前序遍历,将所有符合要求的节点入 path 56 | res.push([...path]); 57 | 58 | for (let i = start; i < nums.length; i++) { 59 | // 节点入栈并递归下一选择 60 | path.push(nums[i]); 61 | backtrack(path, i + 1); 62 | // 撤销选择 63 | path.pop(); 64 | } 65 | } 66 | } 67 | // @lc code=end 68 | 69 | (() => { 70 | const nums = [1, 2, 3]; 71 | console.log(subsets(nums)); 72 | })(); 73 | -------------------------------------------------------------------------------- /src/backtracking/90.子集Ii.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @lc app=leetcode.cn id=90 lang=typescript 3 | * 4 | * [90] 子集 II 5 | * 6 | * https://leetcode-cn.com/problems/subsets-ii/description/ 7 | * 8 | * algorithms 9 | * Medium (63.35%) 10 | * Likes: 727 11 | * Dislikes: 0 12 | * Total Accepted: 160.3K 13 | * Total Submissions: 253.1K 14 | * Testcase Example: '[1,2,2]' 15 | * 16 | * 给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。 17 | * 18 | * 解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。 19 | * 20 | * 21 | * 22 | * 23 | * 24 | * 示例 1: 25 | * 26 | * 27 | * 输入:nums = [1,2,2] 28 | * 输出:[[],[1],[1,2],[1,2,2],[2],[2,2]] 29 | * 30 | * 31 | * 示例 2: 32 | * 33 | * 34 | * 输入:nums = [0] 35 | * 输出:[[],[0]] 36 | * 37 | * 38 | * 39 | * 40 | * 提示: 41 | * 42 | * 43 | * 1 44 | * -10 45 | * 46 | * 47 | * 48 | * 49 | */ 50 | 51 | // @lc code=start 52 | function subsetsWithDup(nums: number[]): number[][] { 53 | // 先排序 54 | nums.sort((a, b) => a - b); 55 | 56 | const res: number[][] = []; 57 | backtrack([], 0); 58 | return res; 59 | 60 | function backtrack(path: number[], start: number) { 61 | res.push([...path]); 62 | 63 | for (let i = start; i < nums.length; i++) { 64 | // 如果遇到值相同的情况,只递归第一个值,其余跳过 65 | if (i > start && nums[i] === nums[i - 1]) { 66 | continue; 67 | } 68 | // 选择该节点 69 | path.push(nums[i]); 70 | // 回溯 71 | backtrack(path, i + 1); 72 | // 撤销选择 73 | path.pop(); 74 | } 75 | } 76 | } 77 | // @lc code=end 78 | 79 | (() => { 80 | const nums = [1, 2, 2]; 81 | console.log(subsetsWithDup(nums)); 82 | })(); 83 | -------------------------------------------------------------------------------- /src/binary-search/367.有效的完全平方数.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @lc app=leetcode.cn id=367 lang=typescript 3 | * 4 | * [367] 有效的完全平方数 5 | * 6 | * https://leetcode.cn/problems/valid-perfect-square/description/ 7 | * 8 | * algorithms 9 | * Easy (44.84%) 10 | * Likes: 386 11 | * Dislikes: 0 12 | * Total Accepted: 154.6K 13 | * Total Submissions: 344.7K 14 | * Testcase Example: '16' 15 | * 16 | * 给定一个 正整数 num ,编写一个函数,如果 num 是一个完全平方数,则返回 true ,否则返回 false 。 17 | * 18 | * 进阶:不要 使用任何内置的库函数,如  sqrt 。 19 | * 20 | * 21 | * 22 | * 示例 1: 23 | * 24 | * 25 | * 输入:num = 16 26 | * 输出:true 27 | * 28 | * 29 | * 示例 2: 30 | * 31 | * 32 | * 输入:num = 14 33 | * 输出:false 34 | * 35 | * 36 | * 37 | * 38 | * 提示: 39 | * 40 | * 41 | * 1 42 | * 43 | * 44 | */ 45 | 46 | // @lc code=start 47 | function isPerfectSquare(num: number): boolean { 48 | if (num === 1) return true; 49 | 50 | let left = 0, 51 | right = num; 52 | while (left <= right) { 53 | const mid = Math.floor((left + right) / 2); 54 | if (mid * mid > num) { 55 | right = mid - 1; 56 | } else if (mid * mid < num) { 57 | left = mid + 1; 58 | } else { 59 | return true; 60 | } 61 | } 62 | return false; 63 | } 64 | // @lc code=end 65 | 66 | (() => { 67 | const num = 16; 68 | console.log(isPerfectSquare(num)); 69 | })(); 70 | -------------------------------------------------------------------------------- /src/binary-search/69.x的平方根.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @lc app=leetcode.cn id=69 lang=typescript 3 | * 4 | * [69] x 的平方根 5 | * 6 | * https://leetcode.cn/problems/sqrtx/description/ 7 | * 8 | * algorithms 9 | * Easy (38.56%) 10 | * Likes: 1551 11 | * Dislikes: 0 12 | * Total Accepted: 916.4K 13 | * Total Submissions: 2.4M 14 | * Testcase Example: '4' 15 | * 16 | * 给你一个非负整数 x ,计算并返回 x 的 算术平方根 。 17 | * 18 | * 由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 。 19 | * 20 | * 注意:不允许使用任何内置指数函数和算符,例如 pow(x, 0.5) 或者 x ** 0.5 。 21 | * 22 | * 23 | * 24 | * 示例 1: 25 | * 26 | * 27 | * 输入:x = 4 28 | * 输出:2 29 | * 30 | * 31 | * 示例 2: 32 | * 33 | * 34 | * 输入:x = 8 35 | * 输出:2 36 | * 解释:8 的算术平方根是 2.82842..., 由于返回类型是整数,小数部分将被舍去。 37 | * 38 | * 39 | * 40 | * 41 | * 提示: 42 | * 43 | * 44 | * 0 <= x <= 2^31 - 1 45 | * 46 | * 47 | */ 48 | 49 | // @lc code=start 50 | function mySqrt(x: number): number { 51 | if (x === 0) return 0; 52 | if (x === 1) return 1; 53 | 54 | let l = 0, 55 | r = x; 56 | let ans = -1; 57 | 58 | while (l <= r) { 59 | const mid = Math.floor((l + r) / 2); 60 | if (mid * mid > x) { 61 | r = mid - 1; 62 | } else { 63 | ans = mid; 64 | l = mid + 1; 65 | } 66 | } 67 | 68 | return ans; 69 | } 70 | // @lc code=end 71 | 72 | const x = 82; 73 | console.log(mySqrt(x)); 74 | -------------------------------------------------------------------------------- /src/binary-search/704.二分查找.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @lc app=leetcode.cn id=704 lang=typescript 3 | * 4 | * [704] 二分查找 5 | * 6 | * https://leetcode-cn.com/problems/binary-search/description/ 7 | * 8 | * algorithms 9 | * Easy (55.02%) 10 | * Likes: 522 11 | * Dislikes: 0 12 | * Total Accepted: 351.1K 13 | * Total Submissions: 638.1K 14 | * Testcase Example: '[-1,0,3,5,9,12]\n9' 15 | * 16 | * 给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target  ,写一个函数搜索 nums 中的 17 | * target,如果目标值存在返回下标,否则返回 -1。 18 | * 19 | * 20 | * 示例 1: 21 | * 22 | * 输入: nums = [-1,0,3,5,9,12], target = 9 23 | * 输出: 4 24 | * 解释: 9 出现在 nums 中并且下标为 4 25 | * 26 | * 27 | * 示例 2: 28 | * 29 | * 输入: nums = [-1,0,3,5,9,12], target = 2 30 | * 输出: -1 31 | * 解释: 2 不存在 nums 中因此返回 -1 32 | * 33 | * 34 | * 35 | * 36 | * 提示: 37 | * 38 | * 39 | * 你可以假设 nums 中的所有元素是不重复的。 40 | * n 将在 [1, 10000]之间。 41 | * nums 的每个元素都将在 [-9999, 9999]之间。 42 | * 43 | * 44 | */ 45 | 46 | // @lc code=start 47 | function search(nums: number[], target: number): number { 48 | let left = 0; 49 | let right = nums.length - 1; 50 | 51 | while (left <= right) { 52 | const mid = Math.floor((left + right) / 2); 53 | 54 | if (nums[mid] === target) return mid; 55 | if (nums[mid] > target) { 56 | right = mid - 1; 57 | } else { 58 | left = mid + 1; 59 | } 60 | } 61 | return -1; 62 | } 63 | // @lc code=end 64 | 65 | (() => { 66 | const nums = [-1, 0, 3, 5, 9, 12]; 67 | const target = 0; 68 | console.log(search(nums, target)); 69 | })() 70 | -------------------------------------------------------------------------------- /src/bit-manipulation/136.只出现一次的数字.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @lc app=leetcode.cn id=136 lang=javascript 3 | * 4 | * [136] 只出现一次的数字 5 | * 6 | * https://leetcode-cn.com/problems/single-number/description/ 7 | * 8 | * algorithms 9 | * Easy (65.92%) 10 | * Likes: 1144 11 | * Dislikes: 0 12 | * Total Accepted: 171.7K 13 | * Total Submissions: 259.4K 14 | * Testcase Example: '[2,2,1]' 15 | * 16 | * 给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。 17 | * 18 | * 说明: 19 | * 20 | * 你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗? 21 | * 22 | * 示例 1: 23 | * 24 | * 输入: [2,2,1] 25 | * 输出: 1 26 | * 27 | * 28 | * 示例 2: 29 | * 30 | * 输入: [4,1,2,1,2] 31 | * 输出: 4 32 | * 33 | */ 34 | 35 | // @lc code=start 36 | /** 37 | * @param {number[]} nums 38 | * @return {number} 39 | */ 40 | var singleNumber = function(nums) { 41 | let res = 0; 42 | for (let i = 0; i < nums.length; i++) { 43 | res = res ^ nums[i]; 44 | } 45 | return res; 46 | }; 47 | // @lc code=end 48 | 49 | // 由于题目要求时间复杂度O(n),空间复杂度O(1),因此不能使用排序算法,也不能使用hash-table。 50 | // 本题使用二进制异或的性质来完成:两个数字异或 a^b 是将 a 和 b 的二进制每一位进行运算,如果同一位的数字相同则为0,不同则为1。 51 | console.log(singleNumber([4, 10, 3, 1, 2, 1, 3, 7, 4, 10, 2])); -------------------------------------------------------------------------------- /src/bit-manipulation/169.多数元素.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @lc app=leetcode.cn id=169 lang=javascript 3 | * 4 | * [169] 多数元素 5 | * 6 | * https://leetcode-cn.com/problems/majority-element/description/ 7 | * 8 | * algorithms 9 | * Easy (62.76%) 10 | * Likes: 546 11 | * Dislikes: 0 12 | * Total Accepted: 152.7K 13 | * Total Submissions: 242.9K 14 | * Testcase Example: '[3,2,3]' 15 | * 16 | * 给定一个大小为 n 的数组,找到其中的多数元素。多数元素是指在数组中出现次数大于 ⌊ n/2 ⌋ 的元素。 17 | * 18 | * 你可以假设数组是非空的,并且给定的数组总是存在多数元素。 19 | * 20 | * 21 | * 22 | * 示例 1: 23 | * 24 | * 输入: [3,2,3] 25 | * 输出: 3 26 | * 27 | * 示例 2: 28 | * 29 | * 输入: [2,2,1,1,1,2,2] 30 | * 输出: 2 31 | * 32 | * 33 | */ 34 | 35 | // @lc code=start 36 | /** 37 | * @param {number[]} nums 38 | * @return {number} 39 | */ 40 | var majorityElement = function(nums) { 41 | let res = nums[0]; 42 | let count = 0; 43 | for (let i = 0; i < nums.length; i++) { 44 | if (count === 0) { 45 | res = nums[i]; 46 | count++; 47 | } else { 48 | res === nums[i] ? count++ : count--; 49 | } 50 | } 51 | return res; 52 | }; 53 | // @lc code=end 54 | 55 | // Moore Voting 摩尔投票算法求众数,时间O(n),空间O(1) 56 | // 设定一个候选者,每出现相同的元素计数器+1否则-1。当计数器为0时,说明当前元素与候选者出现次数相同,更换候选者为当前元素。 57 | console.log(majorityElement([2, 2, 1, 1, 1, 2, 2])); 58 | -------------------------------------------------------------------------------- /src/bit-manipulation/201.数字范围按位与.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @lc app=leetcode.cn id=201 lang=typescript 3 | * 4 | * [201] 数字范围按位与 5 | * 6 | * https://leetcode-cn.com/problems/bitwise-and-of-numbers-range/description/ 7 | * 8 | * algorithms 9 | * Medium (53.07%) 10 | * Likes: 364 11 | * Dislikes: 0 12 | * Total Accepted: 56K 13 | * Total Submissions: 105.4K 14 | * Testcase Example: '5\n7' 15 | * 16 | * 给你两个整数 left 和 right ,表示区间 [left, right] ,返回此区间内所有数字 按位与 的结果(包含 left 、right 17 | * 端点)。 18 | * 19 | * 20 | * 21 | * 示例 1: 22 | * 23 | * 24 | * 输入:left = 5, right = 7 25 | * 输出:4 26 | * 27 | * 28 | * 示例 2: 29 | * 30 | * 31 | * 输入:left = 0, right = 0 32 | * 输出:0 33 | * 34 | * 35 | * 示例 3: 36 | * 37 | * 38 | * 输入:left = 1, right = 2147483647 39 | * 输出:0 40 | * 41 | * 42 | * 43 | * 44 | * 提示: 45 | * 46 | * 47 | * 0 48 | * 49 | * 50 | */ 51 | 52 | // @lc code=start 53 | function rangeBitwiseAnd(left: number, right: number): number { 54 | let shift = 0; 55 | // 找到公共前缀 56 | while (left < right) { 57 | left >>= 1; 58 | right >>= 1; 59 | ++shift; 60 | } 61 | return left << shift; 62 | } 63 | // @lc code=end 64 | 65 | (() => { 66 | const left = 5, 67 | right = 7; 68 | console.log(rangeBitwiseAnd(left, right)); 69 | })(); 70 | -------------------------------------------------------------------------------- /src/dynamic-programming/120.三角形最小路径和.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @lc app=leetcode.cn id=120 lang=javascript 3 | * 4 | * [120] 三角形最小路径和 5 | * 6 | * https://leetcode-cn.com/problems/triangle/description/ 7 | * 8 | * algorithms 9 | * Medium (64.02%) 10 | * Likes: 355 11 | * Dislikes: 0 12 | * Total Accepted: 49.7K 13 | * Total Submissions: 77.4K 14 | * Testcase Example: '[[2],[3,4],[6,5,7],[4,1,8,3]]' 15 | * 16 | * 给定一个三角形,找出自顶向下的最小路径和。每一步只能移动到下一行中相邻的结点上。 17 | * 18 | * 例如,给定三角形: 19 | * 20 | * [ 21 | * ⁠ [2], 22 | * ⁠ [3,4], 23 | * ⁠ [6,5,7], 24 | * ⁠ [4,1,8,3] 25 | * ] 26 | * 27 | * 28 | * 自顶向下的最小路径和为 11(即,2 + 3 + 5 + 1 = 11)。 29 | * 30 | * 说明: 31 | * 32 | * 如果你可以只使用 O(n) 的额外空间(n 为三角形的总行数)来解决这个问题,那么你的算法会很加分。 33 | * 34 | */ 35 | 36 | // @lc code=start 37 | /** 38 | * @param {number[][]} triangle 39 | * @return {number} 40 | */ 41 | var minimumTotal = function(triangle) { 42 | const len = triangle.length; 43 | const dp = new Array(len).fill(0).map(x => new Array(len).fill(0)); 44 | dp[len - 1] = triangle[len - 1]; 45 | for (let i = len - 2; i >= 0; i--) { 46 | for (let j = 0; j <= i; j++) { 47 | dp[i][j] = Math.min(dp[i + 1][j], dp[i + 1][j + 1]) + triangle[i][j]; 48 | } 49 | } 50 | return dp[0][0]; 51 | }; 52 | // @lc code=end 53 | 54 | // 状态转移方程:dp[i][j] = min(dp[i+1][j], dp[i+1][j+1]) + triangle[i][j] 55 | // 注意要从下往上求极值,题目可以转化为:最后一行元素到当前元素的最小路径和。可以看出每一个点的最小路径只与下一排的相邻两个值有关。 56 | const arr = [[2], [3, 4], [6, 5, 7], [4, 1, 8, 3]]; 57 | console.log(minimumTotal(arr)); 58 | -------------------------------------------------------------------------------- /src/dynamic-programming/121.买卖股票的最佳时机.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @lc app=leetcode.cn id=121 lang=javascript 3 | * 4 | * [121] 买卖股票的最佳时机 5 | * 6 | * https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock/description/ 7 | * 8 | * algorithms 9 | * Easy (53.75%) 10 | * Likes: 881 11 | * Dislikes: 0 12 | * Total Accepted: 179.5K 13 | * Total Submissions: 333.5K 14 | * Testcase Example: '[7,1,5,3,6,4]' 15 | * 16 | * 给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。 17 | * 18 | * 如果你最多只允许完成一笔交易(即买入和卖出一支股票一次),设计一个算法来计算你所能获取的最大利润。 19 | * 20 | * 注意:你不能在买入股票前卖出股票。 21 | * 22 | * 23 | * 24 | * 示例 1: 25 | * 26 | * 输入: [7,1,5,3,6,4] 27 | * 输出: 5 28 | * 解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。 29 | * ⁠ 注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格。 30 | * 31 | * 32 | * 示例 2: 33 | * 34 | * 输入: [7,6,4,3,1] 35 | * 输出: 0 36 | * 解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。 37 | * 38 | * 39 | */ 40 | 41 | // @lc code=start 42 | /** 43 | * @param {number[]} prices 44 | * @return {number} 45 | */ 46 | var maxProfit = function(prices) { 47 | let min = prices[0]; 48 | let res = 0; 49 | for (let i = 1; i < prices.length; i++) { 50 | res = Math.max(res, prices[i] - min); 51 | min = Math.min(min, prices[i]); 52 | } 53 | return res; 54 | }; 55 | // @lc code=end 56 | 57 | // 状态转移方程:dp[i] = nums[i] - min 58 | console.log(maxProfit([7, 5, 3, 6, 4])); 59 | -------------------------------------------------------------------------------- /src/dynamic-programming/121.买卖股票的最佳时机.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @lc app=leetcode.cn id=121 lang=typescript 3 | * 4 | * [121] 买卖股票的最佳时机 5 | * 6 | * https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock/description/ 7 | * 8 | * algorithms 9 | * Easy (57.66%) 10 | * Likes: 2266 11 | * Dislikes: 0 12 | * Total Accepted: 745.4K 13 | * Total Submissions: 1.3M 14 | * Testcase Example: '[7,1,5,3,6,4]' 15 | * 16 | * 给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。 17 | * 18 | * 你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。 19 | * 20 | * 返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。 21 | * 22 | * 23 | * 24 | * 示例 1: 25 | * 26 | * 27 | * 输入:[7,1,5,3,6,4] 28 | * 输出:5 29 | * 解释:在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。 30 | * ⁠ 注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。 31 | * 32 | * 33 | * 示例 2: 34 | * 35 | * 36 | * 输入:prices = [7,6,4,3,1] 37 | * 输出:0 38 | * 解释:在这种情况下, 没有交易完成, 所以最大利润为 0。 39 | * 40 | * 41 | * 42 | * 43 | * 提示: 44 | * 45 | * 46 | * 1 47 | * 0 48 | * 49 | * 50 | */ 51 | 52 | // @lc code=start 53 | function maxProfit(prices: number[]): number { 54 | const len = prices.length; 55 | let res = 0; 56 | let min = prices[0]; 57 | 58 | for (let i = 1; i < len; i++) { 59 | res = Math.max(res, prices[i] - min); 60 | min = Math.min(min, prices[i]); 61 | } 62 | 63 | return res; 64 | } 65 | // @lc code=end 66 | 67 | (() => { 68 | const prices = [7, 1, 5, 3, 6, 4]; 69 | console.log(maxProfit(prices)); 70 | })(); 71 | -------------------------------------------------------------------------------- /src/dynamic-programming/279.完全平方数.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @lc app=leetcode.cn id=279 lang=javascript 3 | * 4 | * [279] 完全平方数 5 | * 6 | * https://leetcode-cn.com/problems/perfect-squares/description/ 7 | * 8 | * algorithms 9 | * Medium (50.75%) 10 | * Likes: 188 11 | * Dislikes: 0 12 | * Total Accepted: 19.9K 13 | * Total Submissions: 39.2K 14 | * Testcase Example: '12' 15 | * 16 | * 给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, ...)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。 17 | * 18 | * 示例 1: 19 | * 20 | * 输入: n = 12 21 | * 输出: 3 22 | * 解释: 12 = 4 + 4 + 4. 23 | * 24 | * 示例 2: 25 | * 26 | * 输入: n = 13 27 | * 输出: 2 28 | * 解释: 13 = 4 + 9. 29 | * 30 | */ 31 | 32 | // @lc code=start 33 | /** 34 | * @param {number} n 35 | * @return {number} 36 | */ 37 | var numSquares = function(n) { 38 | if (n <= 0) return 0; 39 | // 四平方和定理提高算法效率,不知道该定理可以不加这两行 40 | while (n % 4 === 0) n /= 4; 41 | if (n % 8 === 7) return 4; 42 | 43 | const dp = new Array(n + 1).fill(Number.MAX_SAFE_INTEGER); 44 | dp[0] = 0; 45 | for (let i = 1; i <= n; i++) { 46 | for (let j = 1; j * j <= i; j++) { 47 | dp[i] = Math.min(dp[i], dp[i - j * j] + 1); 48 | } 49 | } 50 | return dp[n]; 51 | }; 52 | // @lc code=end 53 | 54 | // 四平方和定理:任何正整数都能表示为4个整数的平方和。 55 | // 状态转移方程:dp[i] = min(dp[i], dp[i - j * j] + 1) 56 | // 动态规划问题,可以从后往前倒推,本数字i的最小平方和组成个数为: i减去一个比该数小的平方数j^2后的数所需要的最小平方和组成个数+1(这个1指代的就是j)的所有情况的最小值 57 | console.log(numSquares(3)); 58 | -------------------------------------------------------------------------------- /src/dynamic-programming/343.整数拆分.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @lc app=leetcode.cn id=343 lang=typescript 3 | * 4 | * [343] 整数拆分 5 | * 6 | * https://leetcode-cn.com/problems/integer-break/description/ 7 | * 8 | * algorithms 9 | * Medium (61.32%) 10 | * Likes: 712 11 | * Dislikes: 0 12 | * Total Accepted: 135.1K 13 | * Total Submissions: 220.4K 14 | * Testcase Example: '2' 15 | * 16 | * 给定一个正整数 n ,将其拆分为 k 个 正整数 的和( k >= 2 ),并使这些整数的乘积最大化。 17 | * 18 | * 返回 你可以获得的最大乘积 。 19 | * 20 | * 21 | * 22 | * 示例 1: 23 | * 24 | * 25 | * 输入: n = 2 26 | * 输出: 1 27 | * 解释: 2 = 1 + 1, 1 × 1 = 1。 28 | * 29 | * 示例 2: 30 | * 31 | * 32 | * 输入: n = 10 33 | * 输出: 36 34 | * 解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36。 35 | * 36 | * 37 | * 38 | * 提示: 39 | * 40 | * 41 | * 2 <= n <= 58 42 | * 43 | * 44 | */ 45 | 46 | // @lc code=start 47 | function integerBreak(n: number): number { 48 | if (n < 4) return n - 1; 49 | 50 | const dp = Array(n + 1).fill(1); 51 | // 默认处理好基础情况 52 | // dp[1] = 1; 53 | // dp[2] = 1; 54 | for (let i = 3; i <= n; i++) { 55 | dp[i] = Math.max(2 * (i - 2), 2 * dp[i - 2], 3 * (i - 3), 3 * dp[i - 3]); 56 | } 57 | return dp[n]; 58 | } 59 | // @lc code=end 60 | 61 | (() => { 62 | const n = 10; 63 | console.log(integerBreak(n)); 64 | })(); 65 | -------------------------------------------------------------------------------- /src/dynamic-programming/70.爬楼梯.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @lc app=leetcode.cn id=70 lang=javascript 3 | * 4 | * [70] 爬楼梯 5 | * 6 | * https://leetcode-cn.com/problems/climbing-stairs/description/ 7 | * 8 | * algorithms 9 | * Easy (48.09%) 10 | * Likes: 915 11 | * Dislikes: 0 12 | * Total Accepted: 165.7K 13 | * Total Submissions: 343.7K 14 | * Testcase Example: '2' 15 | * 16 | * 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 17 | * 18 | * 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢? 19 | * 20 | * 注意:给定 n 是一个正整数。 21 | * 22 | * 示例 1: 23 | * 24 | * 输入: 2 25 | * 输出: 2 26 | * 解释: 有两种方法可以爬到楼顶。 27 | * 1. 1 阶 + 1 阶 28 | * 2. 2 阶 29 | * 30 | * 示例 2: 31 | * 32 | * 输入: 3 33 | * 输出: 3 34 | * 解释: 有三种方法可以爬到楼顶。 35 | * 1. 1 阶 + 1 阶 + 1 阶 36 | * 2. 1 阶 + 2 阶 37 | * 3. 2 阶 + 1 阶 38 | * 39 | * 40 | */ 41 | 42 | // @lc code=start 43 | /** 44 | * @param {number} n 45 | * @return {number} 46 | */ 47 | var climbStairs = function(n) { 48 | let dp1 = 1; 49 | let dp2 = 1; 50 | while (--n) { 51 | const temp = dp1 + dp2; 52 | dp2 = dp1; 53 | dp1 = temp; 54 | } 55 | return dp1; 56 | }; 57 | // @lc code=end 58 | 59 | // 递归写法 60 | // var climbStairs = function(n) { 61 | // if (n === 0 || n === 1) return 1; 62 | // return climbStairs(n - 1) + climbStairs(n - 2); 63 | // }; 64 | // 状态转移方程: dp[n] = dp[n-1] + dp[n-2] 65 | // var climbStairs = function(n) { 66 | // const dp = [1, 1]; 67 | // for (let i = 2; i <= n; i++) { 68 | // dp[i] = dp[i - 1] + dp[i - 2]; 69 | // } 70 | // return dp[n]; 71 | // }; 72 | // 由于状态转移方程只依赖n-1与n-2,因此可以只用两个变量存储中间值以节省空间复杂度 73 | 74 | console.log(climbStairs(45)); 75 | -------------------------------------------------------------------------------- /src/dynamic-programming/70.爬楼梯.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @lc app=leetcode.cn id=70 lang=typescript 3 | * 4 | * [70] 爬楼梯 5 | * 6 | * https://leetcode-cn.com/problems/climbing-stairs/description/ 7 | * 8 | * algorithms 9 | * Easy (53.20%) 10 | * Likes: 2049 11 | * Dislikes: 0 12 | * Total Accepted: 631.3K 13 | * Total Submissions: 1.2M 14 | * Testcase Example: '2' 15 | * 16 | * 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 17 | * 18 | * 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢? 19 | * 20 | * 注意:给定 n 是一个正整数。 21 | * 22 | * 示例 1: 23 | * 24 | * 输入: 2 25 | * 输出: 2 26 | * 解释: 有两种方法可以爬到楼顶。 27 | * 1. 1 阶 + 1 阶 28 | * 2. 2 阶 29 | * 30 | * 示例 2: 31 | * 32 | * 输入: 3 33 | * 输出: 3 34 | * 解释: 有三种方法可以爬到楼顶。 35 | * 1. 1 阶 + 1 阶 + 1 阶 36 | * 2. 1 阶 + 2 阶 37 | * 3. 2 阶 + 1 阶 38 | * 39 | * 40 | */ 41 | 42 | // @lc code=start 43 | function climbStairs(n: number): number { 44 | if (n === 1) return 1; 45 | if (n === 2) return 2; 46 | let prev = 1; 47 | let cur = 2; 48 | for (let i = 3; i <= n; i++) { 49 | const sum = prev + cur; 50 | prev = cur; 51 | cur = sum; 52 | } 53 | return cur; 54 | } 55 | // @lc code=end 56 | 57 | console.log(climbStairs(6)); 58 | -------------------------------------------------------------------------------- /src/dynamic-programming/836.矩形重叠.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @lc app=leetcode.cn id=836 lang=javascript 3 | * 4 | * [836] 矩形重叠 5 | * 6 | * https://leetcode-cn.com/problems/rectangle-overlap/description/ 7 | * 8 | * algorithms 9 | * Easy (45.63%) 10 | * Likes: 115 11 | * Dislikes: 0 12 | * Total Accepted: 23.6K 13 | * Total Submissions: 47.8K 14 | * Testcase Example: '[0,0,2,2]\n[1,1,3,3]' 15 | * 16 | * 矩形以列表 [x1, y1, x2, y2] 的形式表示,其中 (x1, y1) 为左下角的坐标,(x2, y2) 是右上角的坐标。 17 | * 18 | * 如果相交的面积为正,则称两矩形重叠。需要明确的是,只在角或边接触的两个矩形不构成重叠。 19 | * 20 | * 给出两个矩形,判断它们是否重叠并返回结果。 21 | * 22 | * 23 | * 24 | * 示例 1: 25 | * 26 | * 输入:rec1 = [0,0,2,2], rec2 = [1,1,3,3] 27 | * 输出:true 28 | * 29 | * 30 | * 示例 2: 31 | * 32 | * 输入:rec1 = [0,0,1,1], rec2 = [1,0,2,1] 33 | * 输出:false 34 | * 35 | * 36 | * 37 | * 38 | * 提示: 39 | * 40 | * 41 | * 两个矩形 rec1 和 rec2 都以含有四个整数的列表的形式给出。 42 | * 矩形中的所有坐标都处于 -10^9 和 10^9 之间。 43 | * x 轴默认指向右,y 轴默认指向上。 44 | * 你可以仅考虑矩形是正放的情况。 45 | * 46 | * 47 | */ 48 | 49 | // @lc code=start 50 | /** 51 | * @param {number[]} rec1 52 | * @param {number[]} rec2 53 | * @return {boolean} 54 | */ 55 | var isRectangleOverlap = function(rec1, rec2) { 56 | return rec2[2] > rec1[0] && rec2[0] < rec1[2] && rec2[3] > rec1[1] && rec2[1] < rec1[3]; 57 | }; 58 | // @lc code=end 59 | 60 | const rec1 = [0, 0, 2, 2]; 61 | const rec2 = [1, 1, 3, 3]; 62 | console.log(isRectangleOverlap(rec1, rec2)); 63 | -------------------------------------------------------------------------------- /src/dynamic-programming/877.石子游戏.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @lc app=leetcode.cn id=877 lang=typescript 3 | * 4 | * [877] 石子游戏 5 | * 6 | * https://leetcode.cn/problems/stone-game/description/ 7 | * 8 | * algorithms 9 | * Medium (76.09%) 10 | * Likes: 428 11 | * Dislikes: 0 12 | * Total Accepted: 79.7K 13 | * Total Submissions: 104.7K 14 | * Testcase Example: '[5,3,4,5]' 15 | * 16 | * Alice 和 Bob 用几堆石子在做游戏。一共有偶数堆石子,排成一行;每堆都有 正 整数颗石子,数目为 piles[i] 。 17 | * 18 | * 游戏以谁手中的石子最多来决出胜负。石子的 总数 是 奇数 ,所以没有平局。 19 | * 20 | * Alice 和 Bob 轮流进行,Alice 先开始 。 每回合,玩家从行的 开始 或 结束 处取走整堆石头。 21 | * 这种情况一直持续到没有更多的石子堆为止,此时手中 石子最多 的玩家 获胜 。 22 | * 23 | * 假设 Alice 和 Bob 都发挥出最佳水平,当 Alice 赢得比赛时返回 true ,当 Bob 赢得比赛时返回 false 。 24 | * 25 | * 26 | * 27 | * 示例 1: 28 | * 29 | * 30 | * 输入:piles = [5,3,4,5] 31 | * 输出:true 32 | * 解释: 33 | * Alice 先开始,只能拿前 5 颗或后 5 颗石子 。 34 | * 假设他取了前 5 颗,这一行就变成了 [3,4,5] 。 35 | * 如果 Bob 拿走前 3 颗,那么剩下的是 [4,5],Alice 拿走后 5 颗赢得 10 分。 36 | * 如果 Bob 拿走后 5 颗,那么剩下的是 [3,4],Alice 拿走后 4 颗赢得 9 分。 37 | * 这表明,取前 5 颗石子对 Alice 来说是一个胜利的举动,所以返回 true 。 38 | * 39 | * 40 | * 示例 2: 41 | * 42 | * 43 | * 输入:piles = [3,7,2,3] 44 | * 输出:true 45 | * 46 | * 47 | * 48 | * 49 | * 提示: 50 | * 51 | * 52 | * 2 <= piles.length <= 500 53 | * piles.length 是 偶数 54 | * 1 <= piles[i] <= 500 55 | * sum(piles[i]) 是 奇数 56 | * 57 | * 58 | */ 59 | 60 | // @lc code=start 61 | function stoneGame(piles: number[]): boolean { 62 | return true; 63 | }; 64 | // @lc code=end 65 | 66 | (()=>{ 67 | const piles = [5,3,4,5]; 68 | console.log(stoneGame(piles)) 69 | })() -------------------------------------------------------------------------------- /src/dynamic-programming/96.不同的二叉搜索树.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @lc app=leetcode.cn id=96 lang=javascript 3 | * 4 | * [96] 不同的二叉搜索树 5 | * 6 | * https://leetcode-cn.com/problems/unique-binary-search-trees/description/ 7 | * 8 | * algorithms 9 | * Medium (65.18%) 10 | * Likes: 458 11 | * Dislikes: 0 12 | * Total Accepted: 37.8K 13 | * Total Submissions: 58K 14 | * Testcase Example: '3' 15 | * 16 | * 给定一个整数 n,求以 1 ... n 为节点组成的二叉搜索树有多少种? 17 | * 18 | * 示例: 19 | * 20 | * 输入: 3 21 | * 输出: 5 22 | * 解释: 23 | * 给定 n = 3, 一共有 5 种不同结构的二叉搜索树: 24 | * 25 | * ⁠ 1 3 3 2 1 26 | * ⁠ \ / / / \ \ 27 | * ⁠ 3 2 1 1 3 2 28 | * ⁠ / / \ \ 29 | * ⁠ 2 1 2 3 30 | * 31 | */ 32 | 33 | // @lc code=start 34 | /** 35 | * @param {number} n 36 | * @return {number} 37 | */ 38 | var numTrees = function(n) { 39 | if (n === 0 || n === 1) return 1; 40 | const dp = new Array(n + 1).fill(0); 41 | dp[0] = 1; 42 | dp[1] = 1; 43 | for (let i = 2; i <= n; i++) { 44 | for (let j = 0; j < i; j++) { 45 | dp[i] += dp[j] * dp[i - j - 1]; 46 | } 47 | } 48 | return dp[n]; 49 | }; 50 | // @lc code=end 51 | 52 | // 这道题请与Unique Binary Search Trees II配合食用,这道题需要求对按数字顺序1~n组成的树进行先序遍历,求所有可能的个数。那么我们可以建立一个一维数组dp,考虑当每个数字为根节点时,所有可行的方法个数。 53 | // 这实际上是求卡特兰数,不知道的同学可自行百度。根节点的组成个数为左右子树的组成个数相乘。 54 | // 对于每一个节点i,考虑所有左右子树分配节点个数可能的组成情况,设左子树节点个数为j,那么右子树节点个数为i-1-j,将左右子树的组成个数相乘,将所有情况相加,即可得到dp[i]的值。 55 | // 状态转移方程为dp[i] = dp[i-1] * dp[n-i]。 56 | // 确定边界情况,n=0时,只有可能是空树;n=1时,也只有一种可能。 57 | console.log(numTrees(3)); 58 | -------------------------------------------------------------------------------- /src/greedy/406.根据身高重建队列.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @lc app=leetcode.cn id=406 lang=javascript 3 | * 4 | * [406] 根据身高重建队列 5 | * 6 | * https://leetcode-cn.com/problems/queue-reconstruction-by-height/description/ 7 | * 8 | * algorithms 9 | * Medium (63.50%) 10 | * Likes: 278 11 | * Dislikes: 0 12 | * Total Accepted: 22.5K 13 | * Total Submissions: 35.2K 14 | * Testcase Example: '[[7,0],[4,4],[7,1],[5,0],[6,1],[5,2]]' 15 | * 16 | * 假设有打乱顺序的一群人站成一个队列。 每个人由一个整数对(h, k)表示,其中h是这个人的身高,k是排在这个人前面且身高大于或等于h的人数。 17 | * 编写一个算法来重建这个队列。 18 | * 19 | * 注意: 20 | * 总人数少于1100人。 21 | * 22 | * 示例 23 | * 24 | * 25 | * 输入: 26 | * [[7,0], [4,4], [7,1], [5,0], [6,1], [5,2]] 27 | * 28 | * 输出: 29 | * [[5,0], [7,0], [5,2], [6,1], [4,4], [7,1]] 30 | * 31 | * 32 | */ 33 | 34 | // @lc code=start 35 | /** 36 | * @param {number[][]} people 37 | * @return {number[][]} 38 | */ 39 | var reconstructQueue = function(people) { 40 | people.sort((a, b) => (a[0] === b[0] ? a[1] - b[1] : b[0] - a[0])); 41 | const res = []; 42 | people.forEach(ele => { 43 | res.splice(ele[1], 0, ele); 44 | }); 45 | return res; 46 | }; 47 | // @lc code=end 48 | 49 | console.log( 50 | reconstructQueue([ 51 | [7, 0], 52 | [4, 4], 53 | [7, 1], 54 | [5, 0], 55 | [6, 1], 56 | [5, 2] 57 | ]) 58 | ); 59 | 60 | // 这道题解法比较简单,关键是想到这个思路很难。题目需要将每个元素的第一个值height按从小到大排序,但要求元素的第二个值k表示该元素前所有height不小于当前height的元素的最小个数。 61 | // 这里我们可以将数组先按height从大到小排序,若height相同,则按k从小到大排序。这样排序的目的是保证k的值一定不会大于当前的位置(如果题目有解)。这步操作便利了我们接下来的插入操作数组中间不至于存在空元素。 62 | // 之后我们新建另一个数组res,遍历刚刚排序得到的数组,按K值插入到res中。因为height是从大到小排序,因此若遇到k相同的两个元素,后者的应该插入到前者之前。 -------------------------------------------------------------------------------- /src/greedy/45.跳跃游戏Ii.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @lc app=leetcode.cn id=45 lang=javascript 3 | * 4 | * [45] 跳跃游戏 II 5 | * 6 | * https://leetcode-cn.com/problems/jump-game-ii/description/ 7 | * 8 | * algorithms 9 | * Hard (31.37%) 10 | * Likes: 392 11 | * Dislikes: 0 12 | * Total Accepted: 35.5K 13 | * Total Submissions: 105.9K 14 | * Testcase Example: '[2,3,1,1,4]' 15 | * 16 | * 给定一个非负整数数组,你最初位于数组的第一个位置。 17 | * 18 | * 数组中的每个元素代表你在该位置可以跳跃的最大长度。 19 | * 20 | * 你的目标是使用最少的跳跃次数到达数组的最后一个位置。 21 | * 22 | * 示例: 23 | * 24 | * 输入: [2,3,1,1,4] 25 | * 输出: 2 26 | * 解释: 跳到最后一个位置的最小跳跃数是 2。 27 | * 从下标为 0 跳到下标为 1 的位置,跳 1 步,然后跳 3 步到达数组的最后一个位置。 28 | * 29 | * 30 | * 说明: 31 | * 32 | * 假设你总是可以到达数组的最后一个位置。 33 | * 34 | */ 35 | 36 | // @lc code=start 37 | /** 38 | * @param {number[]} nums 39 | * @return {number} 40 | */ 41 | var jump = function(nums) { 42 | if (nums.length <= 1) return 0; 43 | let res = 0; 44 | let i = 0; 45 | for (let cur = 0; cur < nums.length - 1; res++) { 46 | console.log(cur); 47 | const temp = cur; 48 | while (i <= temp) { 49 | cur = Math.max(cur, i + nums[i]); 50 | i++; 51 | } 52 | if (temp === cur) return -1; 53 | } 54 | return res; 55 | }; 56 | // @lc code=end 57 | 58 | console.log(jump([2, 3, 1, 1, 4, 1])); 59 | 60 | // 这道题使用的核心方法是贪婪法。从第一个点开始,贪婪它所能到达的最远位置,并存下来,并预测这两个位置之间是否有下一跳能达到更远位置的点,如果没有则取这一跳的结果位置,如果有则取下一跳能达到更远的点,并在该位置进行下一次上述计算。直到当前位置已经到数组尾。 61 | // 这里要注意的一点是,若一次计算后,下一跳的值没有发生变化的话,说明没有任何一跳能到达数组尾,返回-1。 -------------------------------------------------------------------------------- /src/greedy/55.跳跃游戏.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @lc app=leetcode.cn id=55 lang=javascript 3 | * 4 | * [55] 跳跃游戏 5 | * 6 | * https://leetcode-cn.com/problems/jump-game/description/ 7 | * 8 | * algorithms 9 | * Medium (36.10%) 10 | * Likes: 511 11 | * Dislikes: 0 12 | * Total Accepted: 72.3K 13 | * Total Submissions: 187.9K 14 | * Testcase Example: '[2,3,1,1,4]' 15 | * 16 | * 给定一个非负整数数组,你最初位于数组的第一个位置。 17 | * 18 | * 数组中的每个元素代表你在该位置可以跳跃的最大长度。 19 | * 20 | * 判断你是否能够到达最后一个位置。 21 | * 22 | * 示例 1: 23 | * 24 | * 输入: [2,3,1,1,4] 25 | * 输出: true 26 | * 解释: 我们可以先跳 1 步,从位置 0 到达 位置 1, 然后再从位置 1 跳 3 步到达最后一个位置。 27 | * 28 | * 29 | * 示例 2: 30 | * 31 | * 输入: [3,2,1,0,4] 32 | * 输出: false 33 | * 解释: 无论怎样,你总会到达索引为 3 的位置。但该位置的最大跳跃长度是 0 , 所以你永远不可能到达最后一个位置。 34 | * 35 | * 36 | */ 37 | 38 | // @lc code=start 39 | /** 40 | * @param {number[]} nums 41 | * @return {boolean} 42 | */ 43 | var canJump = function(nums) { 44 | let furthest = 0; 45 | for (let i = 0; i < nums.length; i++) { 46 | if (i > furthest) return false; 47 | furthest = Math.max(furthest, i + nums[i]); 48 | } 49 | return true; 50 | }; 51 | // @lc code=end 52 | 53 | console.log(canJump([2, 3, 1, 1, 4])); 54 | console.log(canJump([3, 2, 1, 0, 4])); 55 | 56 | // 如果某一个作为 起跳点 的格子可以跳跃的距离是 n,那么表示后面 n 个格子都可以作为 起跳点。 57 | // 可以对每一个能作为 起跳点 的格子都尝试跳一次,把 能跳到最远的距离 不断更新。 58 | // 如果可以一直跳到最后,就成功了。 59 | -------------------------------------------------------------------------------- /src/hash-table/136.只出现一次的数字.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @lc app=leetcode.cn id=136 lang=javascript 3 | * 4 | * [136] 只出现一次的数字 5 | * 6 | * https://leetcode-cn.com/problems/single-number/description/ 7 | * 8 | * algorithms 9 | * Easy (65.92%) 10 | * Likes: 1144 11 | * Dislikes: 0 12 | * Total Accepted: 171.7K 13 | * Total Submissions: 259.4K 14 | * Testcase Example: '[2,2,1]' 15 | * 16 | * 给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。 17 | * 18 | * 说明: 19 | * 20 | * 你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗? 21 | * 22 | * 示例 1: 23 | * 24 | * 输入: [2,2,1] 25 | * 输出: 1 26 | * 27 | * 28 | * 示例 2: 29 | * 30 | * 输入: [4,1,2,1,2] 31 | * 输出: 4 32 | * 33 | */ 34 | 35 | // @lc code=start 36 | /** 37 | * @param {number[]} nums 38 | * @return {number} 39 | */ 40 | var singleNumber = function(nums) { 41 | const hash = {}; 42 | for (let i = 0; i < nums.length; i++) { 43 | if (hash[nums[i]] === undefined) { 44 | hash[nums[i]] = nums[i]; 45 | } else { 46 | Reflect.deleteProperty(hash, nums[i]); 47 | } 48 | } 49 | return hash[Reflect.ownKeys(hash)[0]]; 50 | }; 51 | // @lc code=end 52 | 53 | console.log(singleNumber([2, 2, 5, 1, 4, 3, 2, 2, 3, 5, 4])); 54 | -------------------------------------------------------------------------------- /src/hash-table/219.存在重复元素Ii.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @lc app=leetcode.cn id=219 lang=javascript 3 | * 4 | * [219] 存在重复元素 II 5 | * 6 | * https://leetcode-cn.com/problems/contains-duplicate-ii/description/ 7 | * 8 | * algorithms 9 | * Easy (37.82%) 10 | * Likes: 156 11 | * Dislikes: 0 12 | * Total Accepted: 40K 13 | * Total Submissions: 105.2K 14 | * Testcase Example: '[1,2,3,1]\n3' 15 | * 16 | * 给定一个整数数组和一个整数 k,判断数组中是否存在两个不同的索引 i 和 j,使得 nums [i] = nums [j],并且 i 和 j 的差的 17 | * 绝对值 至多为 k。 18 | * 19 | * 20 | * 21 | * 示例 1: 22 | * 23 | * 输入: nums = [1,2,3,1], k = 3 24 | * 输出: true 25 | * 26 | * 示例 2: 27 | * 28 | * 输入: nums = [1,0,1,1], k = 1 29 | * 输出: true 30 | * 31 | * 示例 3: 32 | * 33 | * 输入: nums = [1,2,3,1,2,3], k = 2 34 | * 输出: false 35 | * 36 | */ 37 | 38 | // @lc code=start 39 | /** 40 | * @param {number[]} nums 41 | * @param {number} k 42 | * @return {boolean} 43 | */ 44 | var containsNearbyDuplicate = function(nums, k) { 45 | const hash = {}; 46 | for (let i = 0; i < nums.length; i++) { 47 | if (hash[nums[i]] !== undefined && i - hash[nums[i]] <= k) { 48 | return true; 49 | } 50 | hash[nums[i]] = i; 51 | } 52 | return false; 53 | }; 54 | // @lc code=end 55 | 56 | console.log(containsNearbyDuplicate([1, 2, 3, 1, 2, 3], 2)); 57 | -------------------------------------------------------------------------------- /src/hash-table/3.无重复字符的最长子串.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @lc app=leetcode.cn id=3 lang=javascript 3 | * 4 | * [3] 无重复字符的最长子串 5 | * 6 | * https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/description/ 7 | * 8 | * algorithms 9 | * Medium (33.48%) 10 | * Likes: 3424 11 | * Dislikes: 0 12 | * Total Accepted: 418.8K 13 | * Total Submissions: 1.2M 14 | * Testcase Example: '"abcabcbb"' 15 | * 16 | * 给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。 17 | * 18 | * 示例 1: 19 | * 20 | * 输入: "abcabcbb" 21 | * 输出: 3 22 | * 解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。 23 | * 24 | * 25 | * 示例 2: 26 | * 27 | * 输入: "bbbbb" 28 | * 输出: 1 29 | * 解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。 30 | * 31 | * 32 | * 示例 3: 33 | * 34 | * 输入: "pwwkew" 35 | * 输出: 3 36 | * 解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。 37 | * 请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。 38 | * 39 | * 40 | */ 41 | 42 | // @lc code=start 43 | /** 44 | * @param {string} s 45 | * @return {number} 46 | */ 47 | var lengthOfLongestSubstring = function(s) { 48 | const len = s.length; 49 | let res = 0; 50 | let hash = {}; 51 | let start = 0; 52 | for (let i = 0; i < len; i++) { 53 | if (hash[s[i]] !== undefined && hash[s[i]] >= start) { 54 | start = hash[s[i]] + 1; 55 | } 56 | hash[s[i]] = i; 57 | if (i - start + 1 > res) { 58 | res = i - start + 1; 59 | } 60 | } 61 | return res; 62 | }; 63 | // @lc code=end 64 | 65 | console.log(lengthOfLongestSubstring('pwwkew')); -------------------------------------------------------------------------------- /src/hash-table/349.两个数组的交集.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @lc app=leetcode.cn id=349 lang=javascript 3 | * 4 | * [349] 两个数组的交集 5 | * 6 | * https://leetcode-cn.com/problems/intersection-of-two-arrays/description/ 7 | * 8 | * algorithms 9 | * Easy (68.51%) 10 | * Likes: 170 11 | * Dislikes: 0 12 | * Total Accepted: 59.7K 13 | * Total Submissions: 86.8K 14 | * Testcase Example: '[1,2,2,1]\n[2,2]' 15 | * 16 | * 给定两个数组,编写一个函数来计算它们的交集。 17 | * 18 | * 示例 1: 19 | * 20 | * 输入: nums1 = [1,2,2,1], nums2 = [2,2] 21 | * 输出: [2] 22 | * 23 | * 24 | * 示例 2: 25 | * 26 | * 输入: nums1 = [4,9,5], nums2 = [9,4,9,8,4] 27 | * 输出: [9,4] 28 | * 29 | * 说明: 30 | * 31 | * 32 | * 输出结果中的每个元素一定是唯一的。 33 | * 我们可以不考虑输出结果的顺序。 34 | * 35 | * 36 | */ 37 | 38 | // @lc code=start 39 | /** 40 | * @param {number[]} nums1 41 | * @param {number[]} nums2 42 | * @return {number[]} 43 | */ 44 | var intersection = function(nums1, nums2) { 45 | const set2 = new Set(nums2); 46 | return Array.from(new Set(nums1.filter(ele => set2.has(ele)))); 47 | }; 48 | // @lc code=end 49 | // 利用Set的特性可以很轻松的实现数组的交集、并集、补集。 50 | 51 | // 并集 52 | // var union = function(nums1, nums2) { 53 | // return Array.from(new Set([...nums1, ...nums2])); 54 | // }; 55 | 56 | // 补集 57 | // var complement = function(nums1, nums2) { 58 | // const set2 = new Set(nums2); 59 | // return Array.from(new Set(nums1.filter(ele => !set2.has(ele)))); 60 | // }; 61 | 62 | console.log(intersection([4, 4, 9, 5], [9, 4, 9, 8, 4])); 63 | // console.log(union([4, 4, 9, 5], [9, 4, 9, 8, 4])); 64 | // console.log(complement([4, 4, 9, 5], [9, 4, 9, 8, 4])); 65 | -------------------------------------------------------------------------------- /src/hash-table/409.最长回文串.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @lc app=leetcode.cn id=409 lang=typescript 3 | * 4 | * [409] 最长回文串 5 | * 6 | * https://leetcode.cn/problems/longest-palindrome/description/ 7 | * 8 | * algorithms 9 | * Easy (55.58%) 10 | * Likes: 413 11 | * Dislikes: 0 12 | * Total Accepted: 123.4K 13 | * Total Submissions: 222K 14 | * Testcase Example: '"abccccdd"' 15 | * 16 | * 给定一个包含大写字母和小写字母的字符串 s ,返回 通过这些字母构造成的 最长的回文串 。 17 | * 18 | * 在构造过程中,请注意 区分大小写 。比如 "Aa" 不能当做一个回文字符串。 19 | * 20 | * 21 | * 22 | * 示例 1: 23 | * 24 | * 25 | * 输入:s = "abccccdd" 26 | * 输出:7 27 | * 解释: 28 | * 我们可以构造的最长的回文串是"dccaccd", 它的长度是 7。 29 | * 30 | * 31 | * 示例 2: 32 | * 33 | * 34 | * 输入:s = "a" 35 | * 输入:1 36 | * 37 | * 38 | * 示例 3: 39 | * 40 | * 41 | * 输入:s = "bb" 42 | * 输入: 2 43 | * 44 | * 45 | * 46 | * 47 | * 提示: 48 | * 49 | * 50 | * 1 <= s.length <= 2000 51 | * s 只能由小写和/或大写英文字母组成 52 | * 53 | * 54 | */ 55 | 56 | // @lc code=start 57 | function longestPalindrome(s: string): number { 58 | const hash: { [key: string]: number } = {}; 59 | for (let i = 0; i < s.length; i++) { 60 | hash[s[i]] ? (hash[s[i]] += 1) : (hash[s[i]] = 1); 61 | } 62 | // 每个字符最多可以取 2 的倍数个 63 | let res = Object.values(hash).reduce((prev, curr) => prev + Math.floor(curr / 2) * 2, 0); 64 | // 回文串允许在最中间有一个不同的字符 65 | return res === s.length ? res : res + 1; 66 | } 67 | // @lc code=end 68 | 69 | (() => { 70 | const s = 'abccccdd'; 71 | console.log(longestPalindrome(s)); 72 | })(); 73 | -------------------------------------------------------------------------------- /src/hash-table/575.分糖果.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @lc app=leetcode.cn id=575 lang=javascript 3 | * 4 | * [575] 分糖果 5 | * 6 | * https://leetcode-cn.com/problems/distribute-candies/description/ 7 | * 8 | * algorithms 9 | * Easy (66.00%) 10 | * Likes: 61 11 | * Dislikes: 0 12 | * Total Accepted: 17.7K 13 | * Total Submissions: 26.8K 14 | * Testcase Example: '[1,1,2,2,3,3]' 15 | * 16 | * 17 | * 给定一个偶数长度的数组,其中不同的数字代表着不同种类的糖果,每一个数字代表一个糖果。你需要把这些糖果平均分给一个弟弟和一个妹妹。返回妹妹可以获得的最大糖果的种类数。 18 | * 19 | * 示例 1: 20 | * 21 | * 22 | * 输入: candies = [1,1,2,2,3,3] 23 | * 输出: 3 24 | * 解析: 一共有三种种类的糖果,每一种都有两个。 25 | * ⁠ 最优分配方案:妹妹获得[1,2,3],弟弟也获得[1,2,3]。这样使妹妹获得糖果的种类数最多。 26 | * 27 | * 28 | * 示例 2 : 29 | * 30 | * 31 | * 输入: candies = [1,1,2,3] 32 | * 输出: 2 33 | * 解析: 妹妹获得糖果[2,3],弟弟获得糖果[1,1],妹妹有两种不同的糖果,弟弟只有一种。这样使得妹妹可以获得的糖果种类数最多。 34 | * 35 | * 36 | * 注意: 37 | * 38 | * 39 | * 数组的长度为[2, 10,000],并且确定为偶数。 40 | * 数组中数字的大小在范围[-100,000, 100,000]内。 41 | * 42 | * 43 | * 44 | * 45 | * 46 | */ 47 | 48 | // @lc code=start 49 | /** 50 | * @param {number[]} candies 51 | * @return {number} 52 | */ 53 | var distributeCandies = function(candies) { 54 | const kinds = new Set(candies).size; 55 | const num = candies.length / 2; 56 | return Math.min(kinds, num); 57 | }; 58 | // @lc code=end 59 | 60 | console.log(distributeCandies([1, 1, 2, 3])); 61 | -------------------------------------------------------------------------------- /src/linked-list/206.反转链表.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @lc app=leetcode.cn id=206 lang=javascript 3 | * 4 | * [206] 反转链表 5 | * 6 | * https://leetcode-cn.com/problems/reverse-linked-list/description/ 7 | * 8 | * algorithms 9 | * Easy (68.20%) 10 | * Likes: 880 11 | * Dislikes: 0 12 | * Total Accepted: 207.4K 13 | * Total Submissions: 303.3K 14 | * Testcase Example: '[1,2,3,4,5]' 15 | * 16 | * 反转一个单链表。 17 | * 18 | * 示例: 19 | * 20 | * 输入: 1->2->3->4->5->NULL 21 | * 输出: 5->4->3->2->1->NULL 22 | * 23 | * 进阶: 24 | * 你可以迭代或递归地反转链表。你能否用两种方法解决这道题? 25 | * 26 | */ 27 | 28 | // @lc code=start 29 | /** 30 | * Definition for singly-linked list. 31 | * function ListNode(val) { 32 | * this.val = val; 33 | * this.next = null; 34 | * } 35 | */ 36 | /** 37 | * @param {ListNode} head 38 | * @return {ListNode} 39 | */ 40 | var reverseList = function(head) { 41 | let pre = null; 42 | let cur = head; 43 | while (cur) { 44 | const next = cur.next || null; 45 | cur.next = pre; 46 | pre = cur; 47 | cur = next; 48 | } 49 | return pre; 50 | }; 51 | // @lc code=end 52 | 53 | function ListNode(val) { 54 | this.val = val; 55 | this.next = null; 56 | } 57 | const a = new ListNode(1); 58 | const b = new ListNode(2); 59 | const c = new ListNode(3); 60 | const d = new ListNode(4); 61 | const e = new ListNode(5); 62 | a.next = b; 63 | b.next = c; 64 | c.next = d; 65 | d.next = e; 66 | console.log(reverseList(a)); 67 | -------------------------------------------------------------------------------- /src/linked-list/24.两两交换链表中的节点.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @lc app=leetcode.cn id=24 lang=javascript 3 | * 4 | * [24] 两两交换链表中的节点 5 | * 6 | * https://leetcode-cn.com/problems/swap-nodes-in-pairs/description/ 7 | * 8 | * algorithms 9 | * Medium (64.92%) 10 | * Likes: 457 11 | * Dislikes: 0 12 | * Total Accepted: 89.7K 13 | * Total Submissions: 137.9K 14 | * Testcase Example: '[1,2,3,4]' 15 | * 16 | * 给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。 17 | * 18 | * 你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。 19 | * 20 | * 21 | * 22 | * 示例: 23 | * 24 | * 给定 1->2->3->4, 你应该返回 2->1->4->3. 25 | * 26 | * 27 | */ 28 | 29 | // @lc code=start 30 | /** 31 | * Definition for singly-linked list. 32 | * function ListNode(val) { 33 | * this.val = val; 34 | * this.next = null; 35 | * } 36 | */ 37 | /** 38 | * @param {ListNode} head 39 | * @return {ListNode} 40 | */ 41 | var swapPairs = function(head) { 42 | const dummy = new ListNode(null); 43 | dummy.next = head; 44 | let pre = dummy; 45 | while (pre && pre.next && pre.next.next) { 46 | let cur = pre.next; 47 | let next = cur.next; 48 | pre.next = next; 49 | cur.next = next.next; 50 | next.next = cur; 51 | 52 | pre = cur; 53 | } 54 | return dummy.next; 55 | }; 56 | // @lc code=end 57 | 58 | function ListNode(val) { 59 | this.val = val; 60 | this.next = null; 61 | } 62 | 63 | const a = new ListNode(1); 64 | const b = new ListNode(2); 65 | const c = new ListNode(3); 66 | const d = new ListNode(4); 67 | a.next = b; 68 | b.next = c; 69 | c.next = d; 70 | console.log(a); 71 | console.log(swapPairs(a)); 72 | -------------------------------------------------------------------------------- /src/math/172.阶乘后的零.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @lc app=leetcode.cn id=172 lang=javascript 3 | * 4 | * [172] 阶乘后的零 5 | * 6 | * https://leetcode-cn.com/problems/factorial-trailing-zeroes/description/ 7 | * 8 | * algorithms 9 | * Easy (39.46%) 10 | * Likes: 149 11 | * Dislikes: 0 12 | * Total Accepted: 18.3K 13 | * Total Submissions: 46.3K 14 | * Testcase Example: '3' 15 | * 16 | * 给定一个整数 n,返回 n! 结果尾数中零的数量。 17 | * 18 | * 示例 1: 19 | * 20 | * 输入: 3 21 | * 输出: 0 22 | * 解释: 3! = 6, 尾数中没有零。 23 | * 24 | * 示例 2: 25 | * 26 | * 输入: 5 27 | * 输出: 1 28 | * 解释: 5! = 120, 尾数中有 1 个零. 29 | * 30 | * 说明: 你算法的时间复杂度应为 O(log n) 。 31 | * 32 | */ 33 | 34 | // @lc code=start 35 | /** 36 | * @param {number} n 37 | * @return {number} 38 | */ 39 | var trailingZeroes = function(n) { 40 | let res = 0; 41 | while (n >= 5) { 42 | n = Math.floor(n / 5); 43 | res += n; 44 | } 45 | return res; 46 | }; 47 | // @lc code=end 48 | 49 | // 由于题目要求在O(lgn)的时间下解决,因此无法用O(n)的暴力求解完成。 50 | // 分析后得出,个位数只有2*5能够得出末尾0,有多少对2和5就有多少个0,而任何数因数分解5的个数永远小于2的个数, 51 | // 因此题目转化为:求解目标数字因数分解后共含有多少个5。 52 | // f(n) = n/5 + n/5^2 + n/5^3 + n/5^4 + n/5^5 + ... + 余数 53 | console.log(trailingZeroes(20)); 54 | -------------------------------------------------------------------------------- /src/math/202.快乐数.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @lc app=leetcode.cn id=202 lang=typescript 3 | * 4 | * [202] 快乐数 5 | * 6 | * https://leetcode-cn.com/problems/happy-number/description/ 7 | * 8 | * algorithms 9 | * Easy (62.39%) 10 | * Likes: 821 11 | * Dislikes: 0 12 | * Total Accepted: 206K 13 | * Total Submissions: 330.1K 14 | * Testcase Example: '19' 15 | * 16 | * 编写一个算法来判断一个数 n 是不是快乐数。 17 | * 18 | * 「快乐数」 定义为: 19 | * 20 | * 21 | * 对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。 22 | * 然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。 23 | * 如果这个过程 结果为 1,那么这个数就是快乐数。 24 | * 25 | * 26 | * 如果 n 是 快乐数 就返回 true ;不是,则返回 false 。 27 | * 28 | * 29 | * 30 | * 示例 1: 31 | * 32 | * 33 | * 输入:n = 19 34 | * 输出:true 35 | * 解释: 36 | * 1^2 + 9^2 = 82 37 | * 8^2 + 2^2 = 68 38 | * 6^2 + 8^2 = 100 39 | * 1^2 + 0^2 + 0^2 = 1 40 | * 41 | * 42 | * 示例 2: 43 | * 44 | * 45 | * 输入:n = 2 46 | * 输出:false 47 | * 48 | * 49 | * 50 | * 51 | * 提示: 52 | * 53 | * 54 | * 1 <= n <= 2^31 - 1 55 | * 56 | * 57 | */ 58 | 59 | // @lc code=start 60 | function isHappy(n: number): boolean { 61 | const hash: Record = {}; 62 | 63 | let curr = String(n); 64 | while (true) { 65 | // 求平方和 66 | curr = curr 67 | .split('') 68 | .map(str => Number(str) ** 2) 69 | .reduce((prev, curr) => prev + curr) 70 | .toString(); 71 | 72 | if (curr === '1') return true; 73 | if (hash[curr]) return false; 74 | 75 | hash[curr] = 1; 76 | } 77 | } 78 | // @lc code=end 79 | 80 | (() => { 81 | const n = 2; 82 | console.log(isHappy(n)); 83 | })(); 84 | -------------------------------------------------------------------------------- /src/math/319.灯泡开关.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @lc app=leetcode.cn id=319 lang=javascript 3 | * 4 | * [319] 灯泡开关 5 | * 6 | * https://leetcode-cn.com/problems/bulb-switcher/description/ 7 | * 8 | * algorithms 9 | * Medium (44.95%) 10 | * Likes: 84 11 | * Dislikes: 0 12 | * Total Accepted: 8.2K 13 | * Total Submissions: 18.4K 14 | * Testcase Example: '3' 15 | * 16 | * 初始时有 n 个灯泡关闭。 第 1 轮,你打开所有的灯泡。 第 2 轮,每两个灯泡你关闭一次。 第 3 17 | * 轮,每三个灯泡切换一次开关(如果关闭则开启,如果开启则关闭)。第 i 轮,每 i 个灯泡切换一次开关。 对于第 n 轮,你只切换最后一个灯泡的开关。 18 | * 找出 n 轮后有多少个亮着的灯泡。 19 | * 20 | * 示例: 21 | * 22 | * 输入: 3 23 | * 输出: 1 24 | * 解释: 25 | * 初始时, 灯泡状态 [关闭, 关闭, 关闭]. 26 | * 第一轮后, 灯泡状态 [开启, 开启, 开启]. 27 | * 第二轮后, 灯泡状态 [开启, 关闭, 开启]. 28 | * 第三轮后, 灯泡状态 [开启, 关闭, 关闭]. 29 | * 30 | * 你应该返回 1,因为只有一个灯泡还亮着。 31 | * 32 | * 33 | */ 34 | 35 | // @lc code=start 36 | /** 37 | * @param {number} n 38 | * @return {number} 39 | */ 40 | var bulbSwitch = function(n) { 41 | return Math.floor(Math.sqrt(n)); 42 | }; 43 | // @lc code=end 44 | 45 | console.log(bulbSwitch(12)); 46 | -------------------------------------------------------------------------------- /src/math/633.平方数之和.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @lc app=leetcode.cn id=633 lang=javascript 3 | * 4 | * [633] 平方数之和 5 | * 6 | * https://leetcode-cn.com/problems/sum-of-square-numbers/description/ 7 | * 8 | * algorithms 9 | * Easy (33.05%) 10 | * Likes: 102 11 | * Dislikes: 0 12 | * Total Accepted: 17.7K 13 | * Total Submissions: 53.6K 14 | * Testcase Example: '5' 15 | * 16 | * 给定一个非负整数 c ,你要判断是否存在两个整数 a 和 b,使得 a^2 + b^2 = c。 17 | * 18 | * 示例1: 19 | * 20 | * 21 | * 输入: 5 22 | * 输出: True 23 | * 解释: 1 * 1 + 2 * 2 = 5 24 | * 25 | * 26 | * 27 | * 28 | * 示例2: 29 | * 30 | * 31 | * 输入: 3 32 | * 输出: False 33 | * 34 | * 35 | */ 36 | 37 | // @lc code=start 38 | /** 39 | * @param {number} c 40 | * @return {boolean} 41 | */ 42 | var judgeSquareSum = function(c) { 43 | let lp = 0; 44 | let rp = Math.floor(Math.sqrt(c)); 45 | while (lp <= rp) { 46 | const res = lp * lp + rp * rp; 47 | if (res === c) { 48 | return true; 49 | } else if (res > c) { 50 | rp--; 51 | } else { 52 | lp++; 53 | } 54 | } 55 | return false; 56 | }; 57 | // @lc code=end 58 | 59 | console.log(judgeSquareSum(2)); 60 | -------------------------------------------------------------------------------- /src/math/67.二进制求和.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @lc app=leetcode.cn id=67 lang=javascript 3 | * 4 | * [67] 二进制求和 5 | * 6 | * https://leetcode-cn.com/problems/add-binary/description/ 7 | * 8 | * algorithms 9 | * Easy (52.29%) 10 | * Likes: 336 11 | * Dislikes: 0 12 | * Total Accepted: 74.1K 13 | * Total Submissions: 141.7K 14 | * Testcase Example: '"11"\n"1"' 15 | * 16 | * 给你两个二进制字符串,返回它们的和(用二进制表示)。 17 | * 18 | * 输入为 非空 字符串且只包含数字 1 和 0。 19 | * 20 | * 21 | * 22 | * 示例 1: 23 | * 24 | * 输入: a = "11", b = "1" 25 | * 输出: "100" 26 | * 27 | * 示例 2: 28 | * 29 | * 输入: a = "1010", b = "1011" 30 | * 输出: "10101" 31 | * 32 | * 33 | * 34 | * 提示: 35 | * 36 | * 37 | * 每个字符串仅由字符 '0' 或 '1' 组成。 38 | * 1 <= a.length, b.length <= 10^4 39 | * 字符串如果不是 "0" ,就都不含前导零。 40 | * 41 | * 42 | */ 43 | 44 | // @lc code=start 45 | /** 46 | * @param {string} a 47 | * @param {string} b 48 | * @return {string} 49 | */ 50 | var addBinary = function(a, b) { 51 | let i = a.length - 1; 52 | let j = b.length - 1; 53 | if (i < j) a = '0'.repeat(j - i) + a; 54 | if (i > j) b = '0'.repeat(i - j) + b; 55 | 56 | let res = ''; 57 | let cnt = 0; 58 | 59 | for (let k = Math.max(i, j); k >= 0; k--) { 60 | const temp = Number(a[k]) + Number(b[k]) + cnt; 61 | cnt = temp > 1 ? 1 : 0; 62 | res = (temp % 2) + res; 63 | } 64 | if (cnt === 1) res = '1' + res; 65 | return res; 66 | }; 67 | // @lc code=end 68 | 69 | console.log(addBinary('1010', '1011')); 70 | 71 | // 简单题就不多做解析了。大概思想就是,先补齐两个二进制数,然后从后往前遍历相加,注意二进制进位问题即可。 72 | -------------------------------------------------------------------------------- /src/math/7.整数反转.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @lc app=leetcode.cn id=7 lang=javascript 3 | * 4 | * [7] 整数反转 5 | * 6 | * https://leetcode-cn.com/problems/reverse-integer/description/ 7 | * 8 | * algorithms 9 | * Easy (33.89%) 10 | * Likes: 1802 11 | * Dislikes: 0 12 | * Total Accepted: 319.5K 13 | * Total Submissions: 940.3K 14 | * Testcase Example: '123' 15 | * 16 | * 给出一个 32 位的有符号整数,你需要将这个整数中每位上的数字进行反转。 17 | * 18 | * 示例 1: 19 | * 20 | * 输入: 123 21 | * 输出: 321 22 | * 23 | * 24 | * 示例 2: 25 | * 26 | * 输入: -123 27 | * 输出: -321 28 | * 29 | * 30 | * 示例 3: 31 | * 32 | * 输入: 120 33 | * 输出: 21 34 | * 35 | * 36 | * 注意: 37 | * 38 | * 假设我们的环境只能存储得下 32 位的有符号整数,则其数值范围为 [−2^31,  2^31 − 1]。请根据这个假设,如果反转后整数溢出那么就返回 39 | * 0。 40 | * 41 | */ 42 | 43 | // @lc code=start 44 | /** 45 | * @param {number} x 46 | * @return {number} 47 | */ 48 | var reverse = function(x) { 49 | const INT_MAX = Math.pow(2, 31) - 1; 50 | const INT_MIN = Math.pow(-2, 31); 51 | let res = Math.abs(x) 52 | .toString() 53 | .split('') 54 | .reverse(); 55 | 56 | while (res[0] === '0') { 57 | res.shift(); 58 | } 59 | 60 | if (x < 0) { 61 | res.unshift('-'); 62 | } 63 | res = res.join(''); 64 | if (res < INT_MIN || res > INT_MAX) { 65 | return 0; 66 | } 67 | return res; 68 | }; 69 | // @lc code=end 70 | 71 | console.log(reverse(120)); 72 | -------------------------------------------------------------------------------- /src/math/9.回文数.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @lc app=leetcode.cn id=9 lang=javascript 3 | * 4 | * [9] 回文数 5 | * 6 | * https://leetcode-cn.com/problems/palindrome-number/description/ 7 | * 8 | * algorithms 9 | * Easy (57.05%) 10 | * Likes: 951 11 | * Dislikes: 0 12 | * Total Accepted: 258.5K 13 | * Total Submissions: 452.8K 14 | * Testcase Example: '121' 15 | * 16 | * 判断一个整数是否是回文数。回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。 17 | * 18 | * 示例 1: 19 | * 20 | * 输入: 121 21 | * 输出: true 22 | * 23 | * 24 | * 示例 2: 25 | * 26 | * 输入: -121 27 | * 输出: false 28 | * 解释: 从左向右读, 为 -121 。 从右向左读, 为 121- 。因此它不是一个回文数。 29 | * 30 | * 31 | * 示例 3: 32 | * 33 | * 输入: 10 34 | * 输出: false 35 | * 解释: 从右向左读, 为 01 。因此它不是一个回文数。 36 | * 37 | * 38 | * 进阶: 39 | * 40 | * 你能不将整数转为字符串来解决这个问题吗? 41 | * 42 | */ 43 | 44 | // @lc code=start 45 | /** 46 | * @param {number} x 47 | * @return {boolean} 48 | */ 49 | var isPalindrome = function(x) { 50 | if (x < 0) return false; 51 | // 判断能相除能取x首位的除数 52 | let divisor = 1; 53 | while (x / divisor >= 10) divisor *= 10; 54 | 55 | while (x > 0) { 56 | const l = Math.floor(x / divisor); 57 | const r = x % 10; 58 | if (l !== r) return false; 59 | x = ((x % divisor) - r) / 10; 60 | divisor /= 100; 61 | } 62 | return true; 63 | }; 64 | // @lc code=end 65 | 66 | console.log(isPalindrome(123454321)); 67 | -------------------------------------------------------------------------------- /src/sliding-window/3.无重复字符的最长子串.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @lc app=leetcode.cn id=3 lang=javascript 3 | * 4 | * [3] 无重复字符的最长子串 5 | * 6 | * https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/description/ 7 | * 8 | * algorithms 9 | * Medium (33.48%) 10 | * Likes: 3424 11 | * Dislikes: 0 12 | * Total Accepted: 418.8K 13 | * Total Submissions: 1.2M 14 | * Testcase Example: '"abcabcbb"' 15 | * 16 | * 给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。 17 | * 18 | * 示例 1: 19 | * 20 | * 输入: "abcabcbb" 21 | * 输出: 3 22 | * 解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。 23 | * 24 | * 25 | * 示例 2: 26 | * 27 | * 输入: "bbbbb" 28 | * 输出: 1 29 | * 解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。 30 | * 31 | * 32 | * 示例 3: 33 | * 34 | * 输入: "pwwkew" 35 | * 输出: 3 36 | * 解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。 37 | * 请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。 38 | * 39 | * 40 | */ 41 | 42 | // @lc code=start 43 | /** 44 | * @param {string} s 45 | * @return {number} 46 | */ 47 | var lengthOfLongestSubstring = function(s) { 48 | const window = {}; 49 | let res = 0; 50 | 51 | let left = 0; 52 | let right = 0; 53 | while (right < s.length) { 54 | const ch = s[right]; 55 | right++; 56 | window[ch] = window[ch] ? window[ch] + 1 : 1; 57 | while (window[ch] > 1) { 58 | const dropCh = s[left]; 59 | left++; 60 | window[dropCh]--; 61 | } 62 | res = Math.max(right - left, res); 63 | } 64 | return res; 65 | }; 66 | // @lc code=end 67 | 68 | console.log(lengthOfLongestSubstring('pwwkew')); 69 | -------------------------------------------------------------------------------- /src/string/14.最长公共前缀.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @lc app=leetcode.cn id=14 lang=javascript 3 | * 4 | * [14] 最长公共前缀 5 | * 6 | * https://leetcode-cn.com/problems/longest-common-prefix/description/ 7 | * 8 | * algorithms 9 | * Easy (34.79%) 10 | * Likes: 699 11 | * Dislikes: 0 12 | * Total Accepted: 122.3K 13 | * Total Submissions: 351.4K 14 | * Testcase Example: '["flower","flow","flight"]' 15 | * 16 | * 编写一个函数来查找字符串数组中的最长公共前缀。 17 | * 18 | * 如果不存在公共前缀,返回空字符串 ""。 19 | * 20 | * 示例 1: 21 | * 22 | * 输入: ["flower","flow","flight"] 23 | * 输出: "fl" 24 | * 25 | * 26 | * 示例 2: 27 | * 28 | * 输入: ["dog","racecar","car"] 29 | * 输出: "" 30 | * 解释: 输入不存在公共前缀。 31 | * 32 | * 33 | * 说明: 34 | * 35 | * 所有输入只包含小写字母 a-z 。 36 | * 37 | */ 38 | 39 | // @lc code=start 40 | /** 41 | * @param {string[]} strs 42 | * @return {string} 43 | */ 44 | var longestCommonPrefix = function (strs) { 45 | if (strs.length <= 0) return ''; 46 | for (let i = 0; i < strs[0].length; i++) { 47 | const flag = strs.every(str => str[i] === strs[0][i]); 48 | if (!flag) { 49 | return strs[0].substring(0, i); 50 | } 51 | } 52 | return strs[0]; 53 | }; 54 | // @lc code=end 55 | 56 | // easy题,没想到啥好解决方法,直接暴力遍历查询。 57 | console.log(longestCommonPrefix(['flower', 'flow', 'flight'])); 58 | -------------------------------------------------------------------------------- /src/string/415.字符串相加.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @lc app=leetcode.cn id=415 lang=javascript 3 | * 4 | * [415] 字符串相加 5 | * 6 | * https://leetcode-cn.com/problems/add-strings/description/ 7 | * 8 | * algorithms 9 | * Easy (47.59%) 10 | * Likes: 144 11 | * Dislikes: 0 12 | * Total Accepted: 27K 13 | * Total Submissions: 54.4K 14 | * Testcase Example: '"0"\n"0"' 15 | * 16 | * 给定两个字符串形式的非负整数 num1 和num2 ,计算它们的和。 17 | * 18 | * 注意: 19 | * 20 | * 21 | * num1 和num2 的长度都小于 5100. 22 | * num1 和num2 都只包含数字 0-9. 23 | * num1 和num2 都不包含任何前导零。 24 | * 你不能使用任何內建 BigInteger 库, 也不能直接将输入的字符串转换为整数形式。 25 | * 26 | * 27 | */ 28 | 29 | // @lc code=start 30 | /** 31 | * @param {string} num1 32 | * @param {string} num2 33 | * @return {string} 34 | */ 35 | var addStrings = function(num1, num2) { 36 | let i = num1.length - 1; 37 | let j = num2.length - 1; 38 | if (i < j) num1 = '0'.repeat(j - i) + num1; 39 | if (i > j) num2 = '0'.repeat(i - j) + num2; 40 | 41 | let res = ''; 42 | let cnt = 0; 43 | 44 | for (let k = Math.max(i, j); k >= 0; k--) { 45 | const temp = Number(num1[k]) + Number(num2[k]) + cnt; 46 | cnt = temp > 9 ? 1 : 0; 47 | res = (temp % 10) + res; 48 | } 49 | if (cnt === 1) res = '1' + res; 50 | return res; 51 | }; 52 | // @lc code=end 53 | 54 | console.log(addStrings('12345', '87654321')); 55 | 56 | // 这道题也是简单级别的题,解题思路参考 67.addBinary 57 | -------------------------------------------------------------------------------- /src/string/5.最长回文子串.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @lc app=leetcode.cn id=5 lang=typescript 3 | * 4 | * [5] 最长回文子串 5 | * 6 | * https://leetcode.cn/problems/longest-palindromic-substring/description/ 7 | * 8 | * algorithms 9 | * Medium (38.50%) 10 | * Likes: 7275 11 | * Dislikes: 0 12 | * Total Accepted: 1.8M 13 | * Total Submissions: 4.5M 14 | * Testcase Example: '"babad"' 15 | * 16 | * 给你一个字符串 s,找到 s 中最长的 回文 子串。 17 | * 18 | * 19 | * 20 | * 示例 1: 21 | * 22 | * 23 | * 输入:s = "babad" 24 | * 输出:"bab" 25 | * 解释:"aba" 同样是符合题意的答案。 26 | * 27 | * 28 | * 示例 2: 29 | * 30 | * 31 | * 输入:s = "cbbd" 32 | * 输出:"bb" 33 | * 34 | * 35 | * 36 | * 37 | * 提示: 38 | * 39 | * 40 | * 1 <= s.length <= 1000 41 | * s 仅由数字和英文字母组成 42 | * 43 | * 44 | */ 45 | 46 | // @lc code=start 47 | function longestPalindrome(s: string): string { 48 | const len = s.length; 49 | if (len === 0) return ''; 50 | 51 | function palindrome(str: string, l: number, r: number) { 52 | while (l >= 0 && r < len && s[l] === s[r]) { 53 | l -= 1; 54 | r += 1; 55 | } 56 | return str.substring(l + 1, r); 57 | } 58 | 59 | let res = ''; 60 | for (let i = 0; i < len; i++) { 61 | // 以 s[i]为中心的子串 62 | const s1 = palindrome(s, i, i); 63 | // 以 s[i] 和 s[i+1] 为中心的子串 64 | const s2 = palindrome(s, i, i + 1); 65 | 66 | res = s1.length > res.length ? s1 : res; 67 | res = s2.length > res.length ? s2 : res; 68 | } 69 | return res; 70 | } 71 | // @lc code=end 72 | 73 | const s1 = 'babad', 74 | s2 = 'cbbd'; 75 | console.log(longestPalindrome(s1)); 76 | -------------------------------------------------------------------------------- /src/string/557.反转字符串中的单词Iii.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @lc app=leetcode.cn id=557 lang=typescript 3 | * 4 | * [557] 反转字符串中的单词 III 5 | * 6 | * https://leetcode-cn.com/problems/reverse-words-in-a-string-iii/description/ 7 | * 8 | * algorithms 9 | * Easy (74.35%) 10 | * Likes: 383 11 | * Dislikes: 0 12 | * Total Accepted: 186.1K 13 | * Total Submissions: 250.3K 14 | * Testcase Example: '"Let\'s take LeetCode contest"' 15 | * 16 | * 给定一个字符串,你需要反转字符串中每个单词的字符顺序,同时仍保留空格和单词的初始顺序。 17 | * 18 | * 19 | * 20 | * 示例: 21 | * 22 | * 输入:"Let's take LeetCode contest" 23 | * 输出:"s'teL ekat edoCteeL tsetnoc" 24 | * 25 | * 26 | * 27 | * 28 | * 提示: 29 | * 30 | * 31 | * 在字符串中,每个单词由单个空格分隔,并且字符串中不会有任何额外的空格。 32 | * 33 | * 34 | */ 35 | 36 | // @lc code=start 37 | function reverseWords(s: string): string { 38 | const words = s.split(' '); 39 | const reverseWords = words.map(word => { 40 | return word 41 | .split('') 42 | .reverse() 43 | .join(''); 44 | }); 45 | return reverseWords.join(' '); 46 | } 47 | // @lc code=end 48 | 49 | (() => { 50 | const s = "Let's take LeetCode contest"; 51 | console.log(reverseWords(s)); 52 | })(); 53 | -------------------------------------------------------------------------------- /src/tree/509.斐波那契数.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @lc app=leetcode.cn id=509 lang=typescript 3 | * 4 | * [509] 斐波那契数 5 | * 6 | * https://leetcode-cn.com/problems/fibonacci-number/description/ 7 | * 8 | * algorithms 9 | * Easy (67.13%) 10 | * Likes: 361 11 | * Dislikes: 0 12 | * Total Accepted: 275.8K 13 | * Total Submissions: 410.8K 14 | * Testcase Example: '2' 15 | * 16 | * 斐波那契数,通常用 F(n) 表示,形成的序列称为 斐波那契数列 。该数列由 0 和 1 开始,后面的每一项数字都是前面两项数字的和。也就是: 17 | * 18 | * 19 | * F(0) = 0,F(1) = 1 20 | * F(n) = F(n - 1) + F(n - 2),其中 n > 1 21 | * 22 | * 23 | * 给你 n ,请计算 F(n) 。 24 | * 25 | * 26 | * 27 | * 示例 1: 28 | * 29 | * 30 | * 输入:2 31 | * 输出:1 32 | * 解释:F(2) = F(1) + F(0) = 1 + 0 = 1 33 | * 34 | * 35 | * 示例 2: 36 | * 37 | * 38 | * 输入:3 39 | * 输出:2 40 | * 解释:F(3) = F(2) + F(1) = 1 + 1 = 2 41 | * 42 | * 43 | * 示例 3: 44 | * 45 | * 46 | * 输入:4 47 | * 输出:3 48 | * 解释:F(4) = F(3) + F(2) = 2 + 1 = 3 49 | * 50 | * 51 | * 52 | * 53 | * 提示: 54 | * 55 | * 56 | * 0 57 | * 58 | * 59 | */ 60 | 61 | // @lc code=start 62 | function fib(n: number): number { 63 | if (n === 0) return 0; 64 | if (n === 1) return 1; 65 | 66 | let prev = 0; 67 | let curr = 1; 68 | for (let i = 2; i <= n; i++) { 69 | const temp = curr; 70 | curr = temp + prev; 71 | prev = temp; 72 | } 73 | return curr; 74 | } 75 | // @lc code=end 76 | 77 | console.log(fib(6)); 78 | -------------------------------------------------------------------------------- /src/tree/96.不同的二叉搜索树.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @lc app=leetcode.cn id=96 lang=javascript 3 | * 4 | * [96] 不同的二叉搜索树 5 | * 6 | * https://leetcode-cn.com/problems/unique-binary-search-trees/description/ 7 | * 8 | * algorithms 9 | * Medium (65.18%) 10 | * Likes: 458 11 | * Dislikes: 0 12 | * Total Accepted: 37.8K 13 | * Total Submissions: 58K 14 | * Testcase Example: '3' 15 | * 16 | * 给定一个整数 n,求以 1 ... n 为节点组成的二叉搜索树有多少种? 17 | * 18 | * 示例: 19 | * 20 | * 输入: 3 21 | * 输出: 5 22 | * 解释: 23 | * 给定 n = 3, 一共有 5 种不同结构的二叉搜索树: 24 | * 25 | * ⁠ 1 3 3 2 1 26 | * ⁠ \ / / / \ \ 27 | * ⁠ 3 2 1 1 3 2 28 | * ⁠ / / \ \ 29 | * ⁠ 2 1 2 3 30 | * 31 | */ 32 | 33 | // @lc code=start 34 | /** 35 | * @param {number} n 36 | * @return {number} 37 | */ 38 | var numTrees = function(n) { 39 | if (n === 0 || n === 1) return 1; 40 | const dp = new Array(n + 1).fill(0); 41 | dp[0] = 1; 42 | dp[1] = 1; 43 | for (let i = 2; i <= n; i++) { 44 | for (let j = 0; j < i; j++) { 45 | dp[i] += dp[j] * dp[i - j - 1]; 46 | } 47 | } 48 | return dp[n]; 49 | }; 50 | // @lc code=end 51 | 52 | // 这道题请与Unique Binary Search Trees II配合食用,这道题需要求对按数字顺序1~n组成的树进行先序遍历,求所有可能的个数。那么我们可以建立一个一维数组dp,考虑当每个数字为根节点时,所有可行的方法个数。 53 | // 这实际上是求卡特兰数,不知道的同学可自行百度。根节点的组成个数为左右子树的组成个数相乘。 54 | // 对于每一个节点i,考虑所有左右子树分配节点个数可能的组成情况,设左子树节点个数为j,那么右子树节点个数为i-1-j,将左右子树的组成个数相乘,将所有情况相加,即可得到dp[i]的值。 55 | // 状态转移方程为dp[i] = dp[i-1] * dp[n-i]。 56 | // 确定边界情况,n=0时,只有可能是空树;n=1时,也只有一种可能。 57 | console.log(numTrees(3)); 58 | -------------------------------------------------------------------------------- /src/two-pointers/125.验证回文串.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @lc app=leetcode.cn id=125 lang=javascript 3 | * 4 | * [125] 验证回文串 5 | * 6 | * https://leetcode-cn.com/problems/valid-palindrome/description/ 7 | * 8 | * algorithms 9 | * Easy (40.95%) 10 | * Likes: 114 11 | * Dislikes: 0 12 | * Total Accepted: 52.4K 13 | * Total Submissions: 128K 14 | * Testcase Example: '"A man, a plan, a canal: Panama"' 15 | * 16 | * 给定一个字符串,验证它是否是回文串,只考虑字母和数字字符,可以忽略字母的大小写。 17 | * 18 | * 说明:本题中,我们将空字符串定义为有效的回文串。 19 | * 20 | * 示例 1: 21 | * 22 | * 输入: "A man, a plan, a canal: Panama" 23 | * 输出: true 24 | * 25 | * 26 | * 示例 2: 27 | * 28 | * 输入: "race a car" 29 | * 输出: false 30 | * 31 | * 32 | */ 33 | 34 | // @lc code=start 35 | /** 36 | * @param {string} s 37 | * @return {boolean} 38 | */ 39 | var isPalindrome = function (s) { 40 | const str = s.toLowerCase().replace(/[^a-z0-9]/g, ''); 41 | let p1 = 0; 42 | let p2 = str.length - 1; 43 | while (p1 < p2) { 44 | if (str[p1] !== str[p2]) { 45 | return false; 46 | } 47 | p1++; 48 | p2--; 49 | } 50 | return true; 51 | }; 52 | // @lc code=end 53 | 54 | // 设置头尾双指针比对 55 | console.log(isPalindrome('race a ecar')); 56 | -------------------------------------------------------------------------------- /src/two-pointers/167.两数之和Ii输入有序数组.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @lc app=leetcode.cn id=167 lang=javascript 3 | * 4 | * [167] 两数之和 II - 输入有序数组 5 | * 6 | * https://leetcode-cn.com/problems/two-sum-ii-input-array-is-sorted/description/ 7 | * 8 | * algorithms 9 | * Easy (53.06%) 10 | * Likes: 272 11 | * Dislikes: 0 12 | * Total Accepted: 80.7K 13 | * Total Submissions: 151.6K 14 | * Testcase Example: '[2,7,11,15]\n9' 15 | * 16 | * 给定一个已按照升序排列 的有序数组,找到两个数使得它们相加之和等于目标数。 17 | * 18 | * 函数应该返回这两个下标值 index1 和 index2,其中 index1 必须小于 index2。 19 | * 20 | * 说明: 21 | * 22 | * 23 | * 返回的下标值(index1 和 index2)不是从零开始的。 24 | * 你可以假设每个输入只对应唯一的答案,而且你不可以重复使用相同的元素。 25 | * 26 | * 27 | * 示例: 28 | * 29 | * 输入: numbers = [2, 7, 11, 15], target = 9 30 | * 输出: [1,2] 31 | * 解释: 2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。 32 | * 33 | */ 34 | 35 | // @lc code=start 36 | /** 37 | * @param {number[]} numbers 38 | * @param {number} target 39 | * @return {number[]} 40 | */ 41 | var twoSum = function (numbers, target) { 42 | const size = numbers.length; 43 | let lp = 0; 44 | let rp = size - 1; 45 | while (lp < rp) { 46 | if (numbers[lp] + numbers[rp] === target) { 47 | return [lp + 1, rp + 1]; 48 | } else if (numbers[lp] + numbers[rp] < target) { 49 | lp++; 50 | } else { 51 | rp--; 52 | } 53 | } 54 | return []; 55 | }; 56 | // @lc code=end 57 | 58 | // 由于数组已经排好序,因此利用头尾指针能在O(n)的时间内解决该题,比结果大前移大数指针位置,比结果小后移小数指针位置 59 | console.log(twoSum([2, 7, 11, 15], 9)); 60 | -------------------------------------------------------------------------------- /src/two-pointers/283.移动零.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @lc app=leetcode.cn id=283 lang=javascript 3 | * 4 | * [283] 移动零 5 | * 6 | * https://leetcode-cn.com/problems/move-zeroes/description/ 7 | * 8 | * algorithms 9 | * Easy (60.06%) 10 | * Likes: 544 11 | * Dislikes: 0 12 | * Total Accepted: 128.1K 13 | * Total Submissions: 212.5K 14 | * Testcase Example: '[0,1,0,3,12]' 15 | * 16 | * 给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。 17 | * 18 | * 示例: 19 | * 20 | * 输入: [0,1,0,3,12] 21 | * 输出: [1,3,12,0,0] 22 | * 23 | * 说明: 24 | * 25 | * 26 | * 必须在原数组上操作,不能拷贝额外的数组。 27 | * 尽量减少操作次数。 28 | * 29 | * 30 | */ 31 | 32 | // @lc code=start 33 | /** 34 | * @param {number[]} nums 35 | * @return {void} Do not return anything, modify nums in-place instead. 36 | */ 37 | var moveZeroes = function (nums) { 38 | for (let i = 0, j = 0; i < nums.length; i++) { 39 | if (nums[i] !== 0) { 40 | const temp = nums[i]; 41 | nums[i] = nums[j]; 42 | nums[j] = temp; 43 | j++; 44 | } 45 | } 46 | }; 47 | // @lc code=end 48 | 49 | // 因为是原地算法,因此考虑用快慢指针。 50 | // 快指针不停向后遍历数组,慢指针指向数组第一个有0的位置。若快指针指向的值不为0,则交换快慢指针的值,并将慢指针+1,指向下一个0的位置(快慢指针之间的值必然全为0) 51 | console.log(moveZeroes([0, 1, 0, 3, 12])); 52 | -------------------------------------------------------------------------------- /src/two-pointers/344.反转字符串.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @lc app=leetcode.cn id=344 lang=javascript 3 | * 4 | * [344] 反转字符串 5 | * 6 | * https://leetcode-cn.com/problems/reverse-string/description/ 7 | * 8 | * algorithms 9 | * Easy (69.50%) 10 | * Likes: 230 11 | * Dislikes: 0 12 | * Total Accepted: 125.7K 13 | * Total Submissions: 180.2K 14 | * Testcase Example: '["h","e","l","l","o"]' 15 | * 16 | * 编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 char[] 的形式给出。 17 | * 18 | * 不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。 19 | * 20 | * 你可以假设数组中的所有字符都是 ASCII 码表中的可打印字符。 21 | * 22 | * 23 | * 24 | * 示例 1: 25 | * 26 | * 输入:["h","e","l","l","o"] 27 | * 输出:["o","l","l","e","h"] 28 | * 29 | * 30 | * 示例 2: 31 | * 32 | * 输入:["H","a","n","n","a","h"] 33 | * 输出:["h","a","n","n","a","H"] 34 | * 35 | */ 36 | 37 | // @lc code=start 38 | /** 39 | * @param {character[]} s 40 | * @return {void} Do not return anything, modify s in-place instead. 41 | */ 42 | var reverseString = function (s) { 43 | let l = 0; 44 | let r = s.length - 1; 45 | while (l < r) { 46 | const temp = s[l]; 47 | s[l] = s[r]; 48 | s[r] = temp; 49 | l++; 50 | r--; 51 | } 52 | }; 53 | // @lc code=end 54 | 55 | console.log(reverseString(['h', 'e', 'l', 'o'])); 56 | -------------------------------------------------------------------------------- /src/two-pointers/344.反转字符串.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @lc app=leetcode.cn id=344 lang=typescript 3 | * 4 | * [344] 反转字符串 5 | * 6 | * https://leetcode-cn.com/problems/reverse-string/description/ 7 | * 8 | * algorithms 9 | * Easy (77.36%) 10 | * Likes: 501 11 | * Dislikes: 0 12 | * Total Accepted: 418.2K 13 | * Total Submissions: 540K 14 | * Testcase Example: '["h","e","l","l","o"]' 15 | * 16 | * 编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出。 17 | * 18 | * 不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。 19 | * 20 | * 21 | * 22 | * 示例 1: 23 | * 24 | * 25 | * 输入:s = ["h","e","l","l","o"] 26 | * 输出:["o","l","l","e","h"] 27 | * 28 | * 29 | * 示例 2: 30 | * 31 | * 32 | * 输入:s = ["H","a","n","n","a","h"] 33 | * 输出:["h","a","n","n","a","H"] 34 | * 35 | * 36 | * 37 | * 提示: 38 | * 39 | * 40 | * 1 <= s.length <= 10^5 41 | * s[i] 都是 ASCII 码表中的可打印字符 42 | * 43 | * 44 | */ 45 | 46 | // @lc code=start 47 | /** 48 | Do not return anything, modify s in-place instead. 49 | */ 50 | function reverseString(s: string[]): void { 51 | let left = 0; 52 | let right = s.length - 1; 53 | while (left < right) { 54 | const temp = s[left]; 55 | s[left] = s[right]; 56 | s[right] = temp; 57 | left += 1; 58 | right -= 1; 59 | } 60 | } 61 | // @lc code=end 62 | 63 | (() => { 64 | const s = ['H', 'a', 'n', 'n', 'a', 'h']; 65 | reverseString(s); 66 | console.log(s); 67 | })(); 68 | -------------------------------------------------------------------------------- /src/two-pointers/713.乘积小于k的子数组.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * @lc app=leetcode.cn id=713 lang=typescript 3 | * 4 | * [713] 乘积小于K的子数组 5 | * 6 | * https://leetcode-cn.com/problems/subarray-product-less-than-k/description/ 7 | * 8 | * algorithms 9 | * Medium (43.30%) 10 | * Likes: 343 11 | * Dislikes: 0 12 | * Total Accepted: 33.7K 13 | * Total Submissions: 77.7K 14 | * Testcase Example: '[10,5,2,6]\n100' 15 | * 16 | * 给定一个正整数数组 nums和整数 k 。 17 | * 18 | * 请找出该数组内乘积小于 k 的连续的子数组的个数。 19 | * 20 | * 21 | * 22 | * 示例 1: 23 | * 24 | * 25 | * 输入: nums = [10,5,2,6], k = 100 26 | * 输出: 8 27 | * 解释: 8个乘积小于100的子数组分别为: [10], [5], [2], [6], [10,5], [5,2], [2,6], [5,2,6]。 28 | * 需要注意的是 [10,5,2] 并不是乘积小于100的子数组。 29 | * 30 | * 31 | * 示例 2: 32 | * 33 | * 34 | * 输入: nums = [1,2,3], k = 0 35 | * 输出: 0 36 | * 37 | * 38 | * 39 | * 提示: 40 | * 41 | * 42 | * 1 43 | * 1 44 | * 0 45 | * 46 | * 47 | */ 48 | 49 | // @lc code=start 50 | function numSubarrayProductLessThanK(nums: number[], k: number): number { 51 | if (k <= 1) return 0; 52 | 53 | let res = 0; 54 | const len = nums.length; 55 | 56 | let temp = 1; 57 | 58 | let left = 0; 59 | let right = 0; 60 | while (right < len) { 61 | temp *= nums[right]; 62 | 63 | while (temp >= k) { 64 | temp /= nums[left]; 65 | left += 1; 66 | } 67 | res += right - left + 1; 68 | right += 1; 69 | } 70 | 71 | return res; 72 | } 73 | // @lc code=end 74 | 75 | (() => { 76 | const nums = [10, 5, 2, 6], 77 | k = 100; 78 | console.log(numSubarrayProductLessThanK(nums, k)); 79 | })(); 80 | -------------------------------------------------------------------------------- /src/two-pointers/88.合并两个有序数组.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @lc app=leetcode.cn id=88 lang=javascript 3 | * 4 | * [88] 合并两个有序数组 5 | * 6 | * https://leetcode-cn.com/problems/merge-sorted-array/description/ 7 | * 8 | * algorithms 9 | * Easy (47.08%) 10 | * Likes: 464 11 | * Dislikes: 0 12 | * Total Accepted: 131.4K 13 | * Total Submissions: 278.3K 14 | * Testcase Example: '[1,2,3,0,0,0]\n3\n[2,5,6]\n3' 15 | * 16 | * 给你两个有序整数数组 nums1 和 nums2,请你将 nums2 合并到 nums1 中,使 num1 成为一个有序数组。 17 | * 18 | * 19 | * 20 | * 说明: 21 | * 22 | * 23 | * 初始化 nums1 和 nums2 的元素数量分别为 m 和 n 。 24 | * 你可以假设 nums1 有足够的空间(空间大小大于或等于 m + n)来保存 nums2 中的元素。 25 | * 26 | * 27 | * 28 | * 29 | * 示例: 30 | * 31 | * 输入: 32 | * nums1 = [1,2,3,0,0,0], m = 3 33 | * nums2 = [2,5,6], n = 3 34 | * 35 | * 输出: [1,2,2,3,5,6] 36 | * 37 | */ 38 | 39 | // @lc code=start 40 | /** 41 | * @param {number[]} nums1 42 | * @param {number} m 43 | * @param {number[]} nums2 44 | * @param {number} n 45 | * @return {void} Do not return anything, modify nums1 in-place instead. 46 | */ 47 | var merge = function (nums1, m, nums2, n) { 48 | let p1 = m - 1; 49 | let p2 = n - 1; 50 | let cur = m + n - 1; 51 | while (p1 >= 0 && p2 >= 0) { 52 | if (nums1[p1] > nums2[p2]) { 53 | nums1[cur] = nums1[p1]; 54 | p1--; 55 | } else { 56 | nums1[cur] = nums2[p2]; 57 | p2--; 58 | } 59 | cur--; 60 | } 61 | while (p2 >= 0) { 62 | nums1[cur] = nums2[p2]; 63 | p2--; 64 | cur--; 65 | } 66 | }; 67 | // @lc code=end 68 | 69 | // 原地算法,一般用双指针法比较多,从后往前插入排序 70 | merge([1, 2, 3, 0, 0, 0], 3, [2, 5, 6], 3); 71 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "esnext", 5 | "noImplicitAny": true, 6 | "outDir": "./dist", 7 | "sourceMap": true 8 | }, 9 | "include": [ 10 | "src/**/*.ts" 11 | ], 12 | } --------------------------------------------------------------------------------