├── .gitignore ├── LICENSE ├── README.md ├── algorithmSolution ├── README.md ├── docs │ ├── README.md │ └── 动态规划 │ │ ├── 三角形最小路径和_120_minimumTotal.md │ │ ├── 乘积最大子数组_152_maxProduct.md │ │ ├── 俄罗斯套娃信封问题_354_maxEnvelopes.md │ │ ├── 动态规划.md │ │ ├── 博弈问题 │ │ └── 石子游戏_877_stoneGame.md │ │ ├── 四字键盘_651_maxA.md │ │ ├── 子序列-子串-回文 │ │ ├── 子序列问题模板.md │ │ ├── 最长上升子序列_300_lengthOfLIS.md │ │ ├── 最长公共子序列_1143_longestCommonSubsequence.md │ │ ├── 最长回文子串_5_longestPalindrome.md │ │ ├── 最长回文子序列_516_longestPalindromeSubseq.md │ │ └── 让字符串成为回文串的最少插入次数_1312_minInsertions.md │ │ ├── 戳气球_312_maxCoins.md │ │ ├── 打家劫舍问题 │ │ ├── 打家劫舍III_337_rob.md │ │ ├── 打家劫舍II_213_rob.md │ │ └── 打家劫舍_198_rob.md │ │ ├── 斐波那契数_509_fib.md │ │ ├── 正则表达式匹配_10_isMatch.md │ │ ├── 爬楼梯_70_climbStairs.md │ │ ├── 编辑距离_72_minDistance.md │ │ ├── 股票问题 │ │ ├── 买卖股票的最佳时机含手续费_714_maxProfit.md │ │ ├── 买卖股票的最佳时机III_123_maxProfit.md │ │ ├── 买卖股票的最佳时机II_122_maxProfit.md │ │ ├── 买卖股票的最佳时机IV_188_maxProfit.md │ │ ├── 买卖股票的最佳时机_121_maxProfit.md │ │ └── 最佳买卖股票时机含冷冻期_309_maxProfit.md │ │ ├── 背包问题 │ │ ├── 分割等和子集_416_canPartition.md │ │ ├── 背包问题讲解.md │ │ ├── 零钱兑换II_518_change.md │ │ └── 零钱兑换_322_coinChange.md │ │ └── 鸡蛋掉落_887_superEggDrop.md ├── leetcodeSolution │ ├── README.md │ ├── Trie字典树 │ │ ├── Trie_208.js │ │ ├── exist_79.js │ │ ├── findWords_212.js │ │ ├── minimumLengthEncoding_剑指II_65.js │ │ └── replaceWords_剑指II_63.js │ ├── floodFill算法 │ │ └── floodFill.js │ ├── union-find并查集 │ │ ├── UF算法.js │ │ ├── equationsPossible_990.js │ │ ├── findCircleNum_547.js │ │ ├── findRedundantConnection_剑指II_118.js │ │ └── numIslands_200.js │ ├── 两-三-四数之和 │ │ ├── fourSumCount_454.js │ │ ├── fourSum_18.js │ │ ├── threeSum_15.js │ │ ├── twoSum_1.js │ │ ├── twoSum_167.js │ │ └── twoSum_653.js │ ├── 二分查找 │ │ ├── MyCalendar_剑指II_58.js │ │ ├── divide_29.js │ │ ├── minArray_剑指_11.js │ │ ├── minEatingSpeed_875.js │ │ ├── peakIndexInMountainArray_剑指II_69.js │ │ ├── searchMatrix_240.js │ │ ├── searchRange_34.js │ │ ├── search_33.js │ │ ├── search_剑指_53_I.js │ │ └── shipWithinDays_1011.js │ ├── 位操作 │ │ ├── addBinary_剑指II_2.js │ │ ├── add_剑指_65.js │ │ ├── countBits_338.js │ │ ├── countPrimeSetBits_762.js │ │ ├── divide_剑指II_1.js │ │ ├── findComplement_476.js │ │ ├── findMaximumXOR_剑指II_67.js │ │ ├── findTheDifference_389.js │ │ ├── getSum_371.js │ │ ├── grayCode_89.js │ │ ├── hammingDistance_461.js │ │ ├── hammingWeight_191.js │ │ ├── hasAlternatingBits_693.js │ │ ├── isPowerOfFour_342.js │ │ ├── isPowerOfTwo_231.js │ │ ├── maxProduct_剑指II_5.js │ │ ├── missingNumber_268.js │ │ ├── reverseBits_190.js │ │ ├── singleNonDuplicate_剑指II_70.js │ │ ├── singleNumber_136.js │ │ ├── singleNumber_剑指II_4.js │ │ ├── singleNumber_剑指_56_II.js │ │ ├── singleNumbers_剑指_56_I.js │ │ ├── sumNums_剑指_64.js │ │ └── 个别位操作.js │ ├── 前缀和 │ │ ├── NumMatrix_剑指II_13.js │ │ ├── Solution_剑指II_71.js │ │ ├── constructArr_剑指_66.js │ │ ├── findMaxLength_剑指II_11.js │ │ ├── pivotIndex_724.js │ │ ├── productExceptSelf_238.js │ │ ├── subarraySum_560.js │ │ └── 网易面试题.js │ ├── 动态规划 │ │ ├── canPartition_416.js │ │ ├── change_518.js │ │ ├── climbStairs_70.js │ │ ├── coinChange_322.js │ │ ├── combinationSum4_剑指II_104.js │ │ ├── countSubstrings_剑指II_20.js │ │ ├── cuttingRope_剑指_14_I.js │ │ ├── dicesProbability_剑指_60.js │ │ ├── fib_509.js │ │ ├── fib_剑指_10_I.js │ │ ├── isInterleave_剑指II_96.js │ │ ├── isMatch_10.js │ │ ├── isMatch_44.js │ │ ├── lenLongestFibSubseq_剑指II_93.js │ │ ├── lengthOfLIS_300.js │ │ ├── longestCommonSubsequence_1143.js │ │ ├── longestPalindromeSubseq_516.js │ │ ├── longestPalindrome_5.js │ │ ├── maxA_651.js │ │ ├── maxCoinsBalloon_312.js │ │ ├── maxEnvelopes_354.js │ │ ├── maxProduct_152.js │ │ ├── maxSubArray_53.js │ │ ├── maxSubArray_剑指_42.js │ │ ├── maximalSquare_221.js │ │ ├── minCost_剑指II_91.js │ │ ├── minDistance_72.js │ │ ├── minFlipsMonoIncr_剑指II_92.js │ │ ├── minInsertions_1312.js │ │ ├── minPathSum_64.js │ │ ├── minPathSum_剑指II_99.js │ │ ├── minimumTotal_120.js │ │ ├── numDecodings_91.js │ │ ├── numSquares_279.js │ │ ├── partition_131.js │ │ ├── stoneGame_877.js │ │ ├── superEggDrop_887.js │ │ ├── translateNum_剑指_46.js │ │ ├── uniquePaths_62.js │ │ ├── uniquePaths_剑指II_98.js │ │ ├── updateMatrix_剑指II_107.js │ │ ├── zeroOneBag.js │ │ ├── 打家劫舍 │ │ │ ├── rob_198.js │ │ │ ├── rob_213.js │ │ │ └── rob_337.js │ │ └── 股票问题 │ │ │ ├── maxProfit_121.js │ │ │ ├── maxProfit_122.js │ │ │ ├── maxProfit_123.js │ │ │ ├── maxProfit_188.js │ │ │ ├── maxProfit_309.js │ │ │ └── maxProfit_714.js │ ├── 双指针 │ │ ├── deleteDuplicates_83.js │ │ ├── exchange_剑指_21.js │ │ ├── findContinuousSequence_剑指_57.js │ │ ├── findDuplicate_287.js │ │ ├── findNumberIn2DArray_offer04.js │ │ ├── findUnsortedSubarray_581.js │ │ ├── isPalindrome_剑指II_18.js │ │ ├── isSubsequence_392.js │ │ ├── linkHasCycle_141.js │ │ ├── merge_88.js │ │ ├── moveZeroes_283.js │ │ ├── nextPermutation_31.js │ │ ├── partition_86.js │ │ ├── removeDuplicates_26.js │ │ ├── removeDuplicates_80.js │ │ ├── removeElement_27.js │ │ ├── reverseString_344.js │ │ ├── sortColors_75.js │ │ ├── strStr_28.js │ │ ├── threeSumClosest_16.js │ │ ├── trap_42.js │ │ ├── twoSum_剑指_57.js │ │ ├── validPalindrome_剑指II_19.js │ │ └── 滑动窗口 │ │ │ ├── checkInclusion_567.js │ │ │ ├── findAnagrams_438.js │ │ │ ├── lengthOfLongestSubstring_3.js │ │ │ ├── maxArea_11.js │ │ │ ├── maxSlidingWindow_239.js │ │ │ ├── minSubArrayLen_剑指II_8.js │ │ │ ├── minWindow_76.js │ │ │ └── numSubarrayProductLessThanK_剑指II_9.js │ ├── 哈希表 │ │ ├── MagicDictionary_剑指II_64.js │ │ ├── RandomizedSet_380.js │ │ ├── containsDuplicate_217.js │ │ ├── findRepeatNumber_剑指_3.js │ │ ├── firstMissingPositive_41.js │ │ ├── firstUniqChar_387.js │ │ ├── firstUniqChar_剑指_50.js │ │ ├── groupAnagrams_49.js │ │ ├── intersect_350.js │ │ ├── isAnagram_242.js │ │ ├── isHappy_202.js │ │ ├── longestConsecutive_128.js │ │ ├── longestConsecutive_剑指II_119.js │ │ ├── majorityElement_剑指_39.js │ │ ├── romanToInt_13.js │ │ └── setZeroes_73.js │ ├── 图-拓扑排序 │ │ ├── calcEquation_剑指II_111.js │ │ ├── findOrder_210.js │ │ ├── findOrder_剑指II_113.js │ │ └── sequenceReconstruction_剑指II_115.js │ ├── 堆 │ │ ├── KthLargest_703.js │ │ ├── MedianFinder_剑指_41.java │ │ └── nthUglyNumber_剑指_49.js │ ├── 字符串 │ │ ├── KMP │ │ │ ├── kmp算法.js │ │ │ └── strStr_28.js │ │ ├── countAndSay_38.js │ │ ├── lengthOfLastWord_58.js │ │ ├── longestCommonPrefix_14.js │ │ ├── multiply_43.js │ │ ├── myAtoi_8.js │ │ ├── replaceSpace_剑指_5.js │ │ ├── reverseLeftWords_剑指_58_II.js │ │ ├── reverseWords_557.js │ │ ├── reverseWords_剑指_58_I.js │ │ └── strToInt_剑指_67.js │ ├── 排序 │ │ ├── containsNearbyAlmostDuplicate_剑指II_57.js │ │ ├── findMinDifference_剑指II_35.js │ │ ├── getLeastNumbers_面试题_40.js │ │ ├── isAlienSorted_剑指II_34.js │ │ ├── kSmallestPairs_剑指II_61.js │ │ ├── largestNumber_179.js │ │ ├── minNumber_剑指_45.java │ │ ├── pancakeSort_969.js │ │ ├── reconstructQueue_406.js │ │ ├── relativeSortArray_剑指II_75.js │ │ ├── reversePairs_剑指_51.js │ │ ├── sortList_148.js │ │ ├── thirdMax_414.js │ │ └── wiggleSort_324.js │ ├── 搜索 │ │ ├── 回溯 │ │ │ ├── allPathsSourceTarget_剑指II_110.js │ │ │ ├── backtrack_140.js │ │ │ ├── combine_77.js │ │ │ ├── generateParenthesis_22.js │ │ │ ├── isValidSudoku_36.js │ │ │ ├── largestValues_剑指II_515.js │ │ │ ├── letterCombinations_17.js │ │ │ ├── partition_剑指II_86.js │ │ │ ├── permutation_剑指_38.js │ │ │ ├── permuteUnique_剑指II_84.js │ │ │ ├── permute_46.js │ │ │ ├── restoreIpAddresses_剑指II_87.js │ │ │ ├── solveNQueens_51.js │ │ │ ├── solveSudoku_37.js │ │ │ ├── subsets_78.js │ │ │ └── totalNQueens_52.js │ │ ├── 广度优先搜索 │ │ │ ├── CBTInserter_剑指II_43.js │ │ │ ├── calcEquation_399.js │ │ │ ├── inorderSuccessor_剑指II_53.js │ │ │ ├── largestValues_剑指II_44.js │ │ │ └── rightSideView_剑指II_46.js │ │ └── 深度优先搜索 │ │ │ ├── NestedIterator_341.js │ │ │ ├── convertBST_剑指II_54.js │ │ │ ├── flatten_剑指II_28.js │ │ │ ├── isBipartite_剑指II_106.js │ │ │ ├── movingCount_剑指_13.js │ │ │ ├── pathSum_剑指II_50.js │ │ │ ├── solve_130.js │ │ │ └── wordBreak_139.js │ ├── 数学 │ │ ├── countDigitOne_剑指_43.js │ │ ├── countPrimes_204.js │ │ ├── findNthDigit_剑指_44.js │ │ ├── fizzBuzz_412.js │ │ ├── fractionToDecimal_166.js │ │ ├── isPowerOfThree_326.js │ │ ├── lastRemaining_剑指_62.js │ │ ├── preimageSizeFZF_793.js │ │ ├── printNumbers_剑指_17.js │ │ ├── superPow_372.js │ │ ├── titleToNumber_171.js │ │ └── trailingZeroes_172.js │ ├── 数组 │ │ ├── findDisappearedNumbers_448.js │ │ ├── findDuplicates_442.js │ │ ├── findErrorNums_645.js │ │ ├── gameOfLife_289.js │ │ ├── generate_118.js │ │ ├── isStraight_剑指_61.js │ │ ├── leastInterval_621.js │ │ ├── missingNumber_剑指_53_II.js │ │ ├── rotate_189.js │ │ └── 差分数组 │ │ │ ├── corpFlightBookings_1109.js │ │ │ └── 差分数组算法.js │ ├── 栈 │ │ ├── MinStack_155.js │ │ ├── asteroidCollision_剑指II_37.js │ │ ├── backspaceCompare_844.js │ │ ├── evalRPN_150.js │ │ ├── isValid_20.js │ │ ├── myQueue_232.js │ │ ├── removeDuplicateLetters_316.js │ │ ├── smallestSubsequence_1081.js │ │ ├── validateStackSequences_剑指_31.js │ │ ├── 单调栈 │ │ │ ├── dailyTemperatures_739.js │ │ │ ├── dailyTemperatures_剑指II_38.js │ │ │ ├── largestRectangleArea_84.js │ │ │ ├── maxChunksToSorted_768.js │ │ │ ├── nextGreaterElement_496.js │ │ │ └── nextGreaterElements_503.js │ │ └── 计算器 │ │ │ ├── calculateComplex.js │ │ │ ├── calculate_16.26.js │ │ │ ├── calculate_224.js │ │ │ └── calculate_227.js │ ├── 树 │ │ ├── BSTIterator_剑指II_55.js │ │ ├── connect_116.js │ │ ├── constructMaximumBinaryTree_654.js │ │ ├── convertBST_538.js │ │ ├── diameterOfBinaryTree_543.js │ │ ├── flatten_114.js │ │ ├── increasingBST_剑指II_52.js │ │ ├── invertTree_226.js │ │ ├── isBalanced_剑指_55_II.js │ │ ├── isSubStructure_剑指_26.js │ │ ├── isSymmetric_101.js │ │ ├── isSymmetric_剑指_28.js │ │ ├── isValidBST_98.js │ │ ├── kthLargest_剑指_54.js │ │ ├── kthSmallest_230.js │ │ ├── lowestCommonAncestor_235.js │ │ ├── lowestCommonAncestor_236.js │ │ ├── maxDepth_104.js │ │ ├── maxPathSum_124.js │ │ ├── mergeTrees_617.js │ │ ├── minDepth_111.js │ │ ├── numTrees_96.js │ │ ├── openLock_752.js │ │ ├── pathSum_437.js │ │ ├── serialize_297.js │ │ ├── treeToDoublyList_剑指_36.js │ │ ├── verifyPostorder_剑指_33.js │ │ ├── zigzagLevelOrder_103.js │ │ ├── 构造二叉树 │ │ │ ├── buildTree_105.js │ │ │ ├── buildTree_106.js │ │ │ ├── sortedArrayToBST_108.js │ │ │ └── sortedListToBST_109.js │ │ └── 树的遍历 │ │ │ ├── levelOrder_102.js │ │ │ ├── levelOrder_剑指_32_I.js │ │ │ ├── levelOrder_剑指_32_II.js │ │ │ └── levelOrder_剑指_32_III.js │ ├── 模拟 │ │ ├── generateMatrix_59.js │ │ ├── rotate_48.js │ │ ├── spiralOrder_54.js │ │ └── spiralOrder_剑指_29.js │ ├── 水塘抽样 │ │ └── getRandom_382.js │ ├── 洗牌算法 │ │ └── shuffle_384.js │ ├── 状态机 │ │ ├── isNumber_剑指_20.java │ │ └── maxSumDivThree_1262.js │ ├── 计数法 │ │ ├── longestValidParentheses_32.js │ │ └── minAddToMakeValid_921.js │ ├── 贪心算法 │ │ ├── canCompleteCircuit_134.js │ │ ├── canJump_55.js │ │ ├── cuttingRope_剑指_14_II.js │ │ ├── eraseOverlapIntervals_435.js │ │ ├── findMinArrowShots_452.js │ │ ├── increasingTriplet_334.js │ │ ├── intervalIntersection_986.js │ │ ├── jump_45.js │ │ └── merge_56.js │ ├── 递归-分治 │ │ ├── longestSubstring_395.js │ │ └── myPow_50.js │ ├── 链表 │ │ ├── addTwoNumbers_2.js │ │ ├── addTwoNumbers_剑指II_25.js │ │ ├── copyRandomList_138.js │ │ ├── deleteNode_237.js │ │ ├── deleteNode_剑指_18.js │ │ ├── detectCycle_142.js │ │ ├── getIntersectionNode_160.js │ │ ├── getKthFromEnd_剑指_22.js │ │ ├── insert_剑指II_29.js │ │ ├── isPalindrome_234.js │ │ ├── mergeTwoLists_21.js │ │ ├── merge_剑指II_77.js │ │ ├── middleNode_876.js │ │ ├── oddEvenList_328.js │ │ ├── removeNthFromEnd_19.js │ │ ├── reorderList_剑指II_26.js │ │ ├── reversePrint_剑指_6.js │ │ ├── rotateRight_61.js │ │ ├── swapPairs_24.js │ │ └── 反转链表 │ │ │ ├── reverseKGroup_25.js │ │ │ └── reverseList_206.js │ └── 队列 │ │ ├── MaxQueue_剑指_59_II.js │ │ ├── MovingAverage_剑指II_41.js │ │ └── RecentCounter_剑指II_42.js └── my91algoSolution │ ├── 10_getIntersectionNode_160.md │ ├── 11_detectCycle_142.md │ ├── 12_LRUCache_146.md │ ├── 13_maxDepth_104.md │ ├── 14_isSameTree_100.md │ ├── 15_sumNumbers_129.md │ ├── 16_findBottomLeftValue_513.md │ ├── 17_serialize_297.md │ ├── 18_verticalTraversal_987.md │ ├── 19_ twoSum_1.md │ ├── 1_plusOne_66.md │ ├── 20_topKFrequent_347.md │ ├── 21_numberOfBoomerangs_447.md │ ├── 22_lengthOfLongestSubstring_3.md │ ├── 23_findSubstring_30.md │ ├── 24_solveSudoku_37.md │ ├── 25_searchInsert_35.md │ ├── 26_searchMatrix_74.md │ ├── 27_removeDuplicates_26.md │ ├── 28_middleNode_876.md │ ├── 29_maxSatisfied_1052.md │ ├── 2_shortestToChar_821.md │ ├── 30_maxSlidingWindow_239.md │ ├── 31_树的遍历系列 │ ├── 1_preorderTraversal_144.md │ ├── 2_inorderTraversal_94.md │ ├── 3_postorderTraversal_145.md │ └── 4_levelOrder_102.md │ ├── 32_反转链表系列 │ ├── 1_reverseList_206.md │ ├── 2_reverseBetween_92.md │ └── 3_reverseKGroup_25.md │ ├── 33_位运算系列 │ └── 1_subsets_78.md │ ├── 34_动态规划系列 │ └── 1_climbStairs_70.md │ ├── 35_有效的括号 │ └── 1_isValid_20.md │ ├── 36_设计系列 │ └── 剑指09_CQueue.md │ ├── 37_前缀和系列 │ ├── 1_网易面试题.md │ └── 2_subarraySum_560.md │ ├── 38_trie_208.md │ ├── 39_MapSum_677.md │ ├── 3_CustomStack_1381.md │ ├── 40_multiSearch_面试题17.17.md │ ├── 41_findCircleNum_547.md │ ├── 42_minMalwareSpread_924.md │ ├── 43_makeConnected_1319.md │ ├── 44_Skiplist_1206.md │ ├── 45_pruneTree_814.md │ ├── 46_combinationSum_39.md │ ├── 47_combinationSum2_40.md │ ├── 48_49_strStr_28.md │ ├── 4_decodeString_394.md │ ├── 50_findKthLargest_215.md │ ├── 51_lastStoneWeight_1046.md │ ├── 52_mergeKLists_23.md │ ├── 53_frequencySort_451.md │ ├── 54_kthSmallest_378.md │ ├── 55_rearrangeBarcodes_1054.md │ ├── 56_mySqrt_69.md │ ├── 57_isBadVersion_278.md │ ├── 58_findPeakElement_162.md │ ├── 59_waysToSplit_5643.md │ ├── 5_MyQueue_232.md │ ├── 60_findMedianSortedArrays_4.md │ ├── 61_swimInWater_778.md │ ├── 62_maxVowels_1456.md │ ├── 63_new21Game_837.md │ ├── 64_findAnagrams_438.md │ ├── 65_minWindow_76.md │ ├── 66_missingNumber_268.md │ ├── 67_isUnique_面试题01.01.md │ ├── 68_readBinaryWatch_401.md │ ├── 69_totalNQueens_52.md │ ├── 6_maxChunksToSorted_768.md │ ├── 70_maxAreaOfIsland_695.md │ ├── 71_maxDistance_1162.md │ ├── 72_pathSum_113.md │ ├── 73_maxScoreWords_1255.md │ ├── 74_removeInvalidParentheses_301.md │ ├── 75_canPartition_416.md │ ├── 76_findTargetSumWays_494.md │ ├── 77_coinChange_322.md │ ├── 78_change_518.md │ ├── 79_2_ceramic.md │ ├── 79_minCostClimbingStairs_746.md │ ├── 7_swapPairs_24.md │ ├── 80_maxValue_剑指47.md │ ├── 81_countBits_338.md │ ├── 82_knightProbability_688.md │ ├── 83_subsets_78.md │ ├── 84_singleNumber_260.md │ ├── 85_findContentChildren_455.md │ ├── 86_eraseOverlapIntervals_435.md │ ├── 87_largestRectangleArea_84.md │ ├── 8_rotateRight_61.md │ ├── 9_sortedListToBST_109.md │ ├── README.md │ └── test.js ├── dataStructure ├── README.md ├── lru实现 │ ├── doubleListLru.js │ ├── hashmapLru.js │ └── singleListLru.js ├── 哈希表 │ ├── 哈希表实现.js │ ├── 哈希表实现自定义key.js │ └── 哈希表实现解决冲突.js ├── 堆-大顶堆-小顶堆实现 │ └── heap.js ├── 实现单链表.js ├── 数组.js ├── 数组实现循环队列.js ├── 数组实现栈.js ├── 数组实现队列.js ├── 栈实现浏览器的前进和后退 │ ├── SampleBrowser.js │ └── StackBasedOnLinkedList.js ├── 跳表.js ├── 链表实现循环队列.js └── 链表实现队列.js ├── docs ├── README.md └── 动态规划 │ ├── 三角形最小路径和_120_minimumTotal.md │ ├── 乘积最大子数组_152_maxProduct.md │ ├── 俄罗斯套娃信封问题_354_maxEnvelopes.md │ ├── 动态规划.md │ ├── 博弈问题 │ └── 石子游戏_877_stoneGame.md │ ├── 四字键盘_651_maxA.md │ ├── 子序列-子串-回文 │ ├── 子序列问题模板.md │ ├── 最长上升子序列_300_lengthOfLIS.md │ ├── 最长公共子序列_1143_longestCommonSubsequence.md │ ├── 最长回文子串_5_longestPalindrome.md │ ├── 最长回文子序列_516_longestPalindromeSubseq.md │ └── 让字符串成为回文串的最少插入次数_1312_minInsertions.md │ ├── 戳气球_312_maxCoins.md │ ├── 打家劫舍问题 │ ├── 打家劫舍III_337_rob.md │ ├── 打家劫舍II_213_rob.md │ └── 打家劫舍_198_rob.md │ ├── 斐波那契数_509_fib.md │ ├── 正则表达式匹配_10_isMatch.md │ ├── 爬楼梯_70_climbStairs.md │ ├── 编辑距离_72_minDistance.md │ ├── 股票问题 │ ├── 买卖股票的最佳时机含手续费_714_maxProfit.md │ ├── 买卖股票的最佳时机III_123_maxProfit.md │ ├── 买卖股票的最佳时机II_122_maxProfit.md │ ├── 买卖股票的最佳时机IV_188_maxProfit.md │ ├── 买卖股票的最佳时机_121_maxProfit.md │ └── 最佳买卖股票时机含冷冻期_309_maxProfit.md │ ├── 背包问题 │ ├── 分割等和子集_416_canPartition.md │ ├── 背包问题讲解.md │ ├── 零钱兑换II_518_change.md │ └── 零钱兑换_322_coinChange.md │ └── 鸡蛋掉落_887_superEggDrop.md ├── sort ├── README.md ├── bubbleSort.js ├── bucketSort.js ├── countingSort.js ├── directInsertSort.js ├── heapSort.js ├── mergeSort.js ├── quickSort.js ├── shellSort.js └── simpleSelectSort.js └── 二分查找 ├── README.md ├── 二分查找.js ├── 二分查找变形一.js ├── 二分查找变形三.js ├── 二分查找变形二.js ├── 二分查找变形四.js └── 实现开平方.js /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 sinkhaha 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 | # dataStructureAndAlgorithm 2 | 3 | * [leetcode详细题解](https://github.com/sinkhaha/dataStructureAndAlgorithm/tree/master/algorithmSolution) 4 | 5 | * [动态规划专题](https://github.com/sinkhaha/dataStructureAndAlgorithm/tree/master/docs) 6 | 7 | * [常见排序](https://github.com/sinkhaha/dataStructureAndAlgorithm/tree/master/sort) 8 | 9 | * [二分查找](https://github.com/sinkhaha/dataStructureAndAlgorithm/tree/master/%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE) 10 | 11 | * [个别数据结构实现](https://github.com/sinkhaha/dataStructureAndAlgorithm/tree/master/dataStructure) 12 | -------------------------------------------------------------------------------- /algorithmSolution/docs/动态规划/子序列-子串-回文/子序列问题模板.md: -------------------------------------------------------------------------------- 1 | ## 解法1:一维dp数组 2 | 3 | ```javascript 4 | let dp = new Array(n); 5 | 6 | for (let i = 0; i < n; i++) { 7 | for (let j = 0; j < i; j++) { 8 | dp[i] = 最值(dp[i], dp[j] + 1) 9 | } 10 | } 11 | 12 | ``` 13 | 如 `300. 最长上升子序列` 题 14 | >dp数组dp[i]表示:以`nums[i]`这个数为结尾的最长子序列的长度 (即从头到第`i`个元素的最长序列长度),所以所求的最终结果就是dp数组中的最大值 15 | 16 | 17 | ## 解法2: 二维dp数组 18 | ```javascript 19 | // 初始化n行n列的二维数组 20 | let dp = Array.from(Array(n), () => Array(n).fill(0)); 21 | // 做选择,遍历二维数组 22 | for (let i = 0; i < n; i++) { 23 | for (let j = 0; j < n; j++) { 24 | if (arr[i] == arr[j]) { 25 | dp[i][j] = dp[i][j] + ... 26 | } 27 | else { 28 | dp[i][j] = 最值(...) 29 | } 30 | } 31 | } 32 | ``` 33 | 如 `1143. 最长公共子序列` 题 34 | 35 | >dp数组`dp[i][j]`表示:对于 `text1[1..i]` 和 `text2[1..j]`,它们的 LCS ⻓度是`dp[i][j]`,`dp[i][j]`即为所求结果 36 | 37 | -------------------------------------------------------------------------------- /algorithmSolution/docs/动态规划/打家劫舍问题/打家劫舍II_213_rob.md: -------------------------------------------------------------------------------- 1 | ## 题目 2 | **213. 打家劫舍 II** 3 | >中等 4 | 5 | https://leetcode-cn.com/problems/house-robber-ii/ 6 | 7 | ## 解法 8 | ### 思路 9 | 和198题的区别是这里的nums是一个`首尾相连的圈`。 10 | 11 | ⾸尾房间不能同时被抢,那么只可能有三种不同情况,找出其中结果最大的选择即可: 12 | 1. 要么都不被抢; 13 | 2. 要么第⼀间房⼦被抢,最后⼀间不抢; 14 | 3. 要么最后⼀间房⼦被抢,第⼀间不抢 15 | 16 | 只要⽐较情况2和情况3就⾏了,因为房⼦⾥的钱数都是⾮负数,所以这两种情况对于房⼦的选择后的钱⽐情况1⼤。 17 | 18 | ### 代码 19 | ```javascript 20 | /** 21 | * 22 | * 23 | * @param {number[]} nums 24 | * @return {number} 25 | */ 26 | var rob = function (nums) { 27 | let n = nums.length; 28 | if (n === 0) { 29 | return 0; 30 | } 31 | if (n === 1) { 32 | return nums[0]; 33 | } 34 | 35 | // 仅计算闭区间 [start,end] 的最优结果 36 | this.dp = function (nums, start, end) { 37 | let dp_i_1 = 0; 38 | let dp_i_2 = 0; 39 | let dp_i = 0; 40 | 41 | for (let i = end; i >= start; i--) { 42 | dp_i = Math.max( 43 | dp_i_1, 44 | nums[i] + dp_i_2 45 | ); 46 | dp_i_2 = dp_i_1; 47 | dp_i_1 = dp_i; 48 | } 49 | 50 | return dp_i; 51 | } 52 | 53 | return Math.max( 54 | dp(nums, 0, n - 2), // 第1间抢,最后1间不抢 55 | dp(nums, 1, n - 1) // 第1间不抢,最后1间抢 56 | ); 57 | }; 58 | ``` 59 | -------------------------------------------------------------------------------- /algorithmSolution/docs/动态规划/股票问题/ 买卖股票的最佳时机含手续费_714_maxProfit.md: -------------------------------------------------------------------------------- 1 | ## 题目 2 | **714. 买卖股票的最佳时机含手续费** 3 | >中等 4 | 5 | https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-with-transaction-fee/ 6 | 7 | ## 解法:动态规划 8 | ### 思路 9 | 跟122、309的解法类似。 10 | 11 | 每次交易要支付手续费,只要把手续费从利润中减去即可。 12 | 13 | 状态转移方程: 14 | `dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i])` 15 | `dp[i][1] = max(dp[i-1][1], dp[i-1][0] - prices[i] - fee)` 16 | 解释:相当于买入股票的价格升高了;在第一个式子里减fee也是一样的,相当于卖出股票的价格减小了 17 | 18 | ### 代码 19 | ```javascript 20 | /** 21 | * @param {number[]} prices 22 | * @param {number} fee 23 | * @return {number} 24 | */ 25 | var maxProfit = function(prices, fee) { 26 | let n = prices.length; 27 | let dp_i_0 = 0, dp_i_1 = Number.MIN_SAFE_INTEGER; 28 | 29 | for (let i = 0; i < n; i++) { 30 | let temp = dp_i_0; 31 | dp_i_0 = Math.max(dp_i_0, dp_i_1 + prices[i]); 32 | dp_i_1 = Math.max(dp_i_1, temp - prices[i] - fee); 33 | } 34 | return dp_i_0; 35 | }; 36 | const prices = [1, 3, 2, 8, 4, 9], fee = 2; 37 | console.log(maxProfit(prices, fee)); // 8 38 | 39 | ``` 40 | ### 复杂度 41 | * 时间复杂度O(n) 42 | * 空间复杂度O(1) -------------------------------------------------------------------------------- /algorithmSolution/docs/动态规划/股票问题/最佳买卖股票时机含冷冻期_309_maxProfit.md: -------------------------------------------------------------------------------- 1 | ## 题目 2 | **309. 最佳买卖股票时机含冷冻期** 3 | >中等 4 | 5 | https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-with-cooldown/ 6 | 7 | ## 解法:动态规划 8 | ### 思路 9 | 每次 sell卖出 之后要等一天才能继续交易(跟122的动态规划解法类似) 10 | 11 | 状态转移方程: 12 | `dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i])` 13 | `dp[i][1] = max(dp[i-1][1], dp[i-2][0] - prices[i])` 14 | 第 i 天选择买入的时候,要从 i-2 的状态转移,而不是 i-1 15 | 16 | ### 代码 17 | ```javascript 18 | /** 19 | * @param {number[]} prices 20 | * @return {number} 21 | */ 22 | var maxProfit = function (prices) { 23 | let n = prices.length; 24 | let dp_i_0 = 0, dp_i_1 = Number.MIN_SAFE_INTEGER; 25 | let dp_pre_0 = 0; // 代表 dp[i-2][0] 26 | 27 | for (let i = 0; i < n; i++) { 28 | let temp = dp_i_0; 29 | dp_i_0 = Math.max(dp_i_0, dp_i_1 + prices[i]); 30 | dp_i_1 = Math.max(dp_i_1, dp_pre_0 - prices[i]); 31 | dp_pre_0 = temp; 32 | } 33 | 34 | return dp_i_0; 35 | }; 36 | ``` 37 | ### 复杂度 38 | * 时间复杂度 O(n) 39 | * 空间复杂度 O(1) 40 | -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/Trie字典树/minimumLengthEncoding_剑指II_65.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 剑指 Offer II 065. 最短的单词编码 3 | * 中等 4 | * https://leetcode.cn/problems/iSwD2y/ 5 | * 6 | * 解法:字典树 7 | */ 8 | /** 9 | * @param {string[]} words 10 | * @return {number} 11 | */ 12 | var minimumLengthEncoding = function (words) { 13 | // 前缀树 14 | let root = {}; 15 | let result = 0; 16 | 17 | // 根据字符串长度 从大到小 排序 18 | words.sort((a, b) => { 19 | return b.length - a.length; 20 | }); 21 | 22 | // 开始插入字符串,长的先插 23 | for (let word of words) { 24 | let flag = false; 25 | let cur = root; 26 | 27 | // 单词倒叙 28 | for (let i = word.length - 1; i >= 0; i--) { 29 | let s = word[i]; 30 | 31 | // 说明前面没有出现相同后缀的单词 32 | if (!cur[s]) { 33 | cur[s] = {}; 34 | flag = true; // 标记该元素不属于其他元素的后缀 35 | } 36 | cur = cur[s]; 37 | } 38 | 39 | // 有flag说明这个单词是长的单词,不是短的单词,此时计算所有长的单词加上#号这个1个字符就是所求结果 40 | if (flag) { 41 | result += (word.length + 1); 42 | } 43 | } 44 | return result; 45 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/union-find并查集/equationsPossible_990.js: -------------------------------------------------------------------------------- 1 | const UF = require('./UF算法'); 2 | 3 | /** 4 | * 990 等式方程的可满足性 5 | * 中等 6 | * https://leetcode-cn.com/problems/satisfiability-of-equality-equations/ 7 | * 8 | * @param {string[]} equations 9 | * @return {boolean} 10 | */ 11 | var equationsPossible = function (equations) { 12 | // 26个字母 13 | const uf = new UF(26); 14 | 15 | // 让相等的字母形成连通分量 16 | for (let eq of equations) { 17 | if (eq.charAt(1) == '=') { 18 | // 获取ascii码,97 是 a 19 | const x = eq.charCodeAt(0) - 97; 20 | const y = eq.charCodeAt(3) - 97; 21 | uf.union(x, y); 22 | } 23 | } 24 | 25 | // console.log(uf.parent); 26 | 27 | // 检查不等关系是否打破相等关系的连通性 28 | for (let eq of equations) { 29 | if (eq.charAt(1) == '!') { 30 | const x = eq.charCodeAt(0) - 97; 31 | const y = eq.charCodeAt(3) - 97; 32 | if (uf.connected(x, y)) { 33 | return false; 34 | } 35 | } 36 | } 37 | 38 | return true; 39 | }; 40 | 41 | const equations = ["c==c", "b==d", "x!=z"]; 42 | console.log(equationsPossible(equations)); 43 | -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/union-find并查集/findRedundantConnection_剑指II_118.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 剑指 Offer II 118. 多余的边 3 | * 中等 4 | * https://leetcode.cn/problems/7LpjUW/ 5 | * 6 | * 解法:并查集 7 | * 8 | * 时间O(nlogn) 9 | * 空间O(n) 10 | */ 11 | /** 12 | * @param {number[][]} edges 13 | * @return {number[]} 14 | */ 15 | var findRedundantConnection = function (edges) { 16 | const n = edges.length; 17 | const parent = new Array(n + 1).fill(0).map((value, index) => index); 18 | 19 | for (let i = 0; i < n; i++) { 20 | const edge = edges[i]; 21 | const node1 = edge[0], node2 = edge[1]; 22 | if (find(parent, node1) != find(parent, node2)) { 23 | union(parent, node1, node2); 24 | } else { 25 | return edge; 26 | } 27 | } 28 | 29 | return [0]; 30 | }; 31 | 32 | const union = (parent, index1, index2) => { 33 | parent[find(parent, index1)] = find(parent, index2); 34 | } 35 | 36 | const find = (parent, index) => { 37 | if (parent[index] !== index) { 38 | parent[index] = find(parent, parent[index]); 39 | } 40 | return parent[index]; 41 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/两-三-四数之和/twoSum_167.js: -------------------------------------------------------------------------------- 1 | /** 2 | * leetcode 167 两数之和--给定的数据有序 3 | * https://leetcode-cn.com/problems/two-sum-ii-input-array-is-sorted/ 4 | * 5 | * 因为是有序,可以利用双指针 6 | * 7 | * 时间复杂度O(n),n为数组长度 8 | * 空间复杂度O(1) 9 | * 10 | * @param {number[]} nums 11 | * @param {number} target 12 | * @return {number[]} 13 | */ 14 | function twoSum(nums, target) { 15 | let low = 0; 16 | let high = nums.length - 1; 17 | 18 | while (low < high) { 19 | const sum = nums[low] + nums[high]; 20 | if (sum > target) { 21 | high--; 22 | } else if (sum < target) { 23 | low++; 24 | } else { 25 | // 相等,题目要求的是输出第几个,而非数组下标 26 | return [low + 1, high + 1]; 27 | } 28 | } 29 | 30 | // 找不到 31 | return [-1, -1]; 32 | }; 33 | 34 | const nums = [2, 7, 11, 15]; 35 | const target = 18; 36 | console.log(twoSum(nums, target)); // [1, 2] 37 | -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/二分查找/minArray_剑指_11.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 剑指 Offer 11. 旋转数组的最小数字 3 | * 简单 4 | * https://leetcode.cn/problems/xuan-zhuan-shu-zu-de-zui-xiao-shu-zi-lcof/ 5 | * 6 | * 解法:二分查找 7 | * 8 | * 时间 O(logn) 9 | * 空间 O(1) 10 | */ 11 | /** 12 | * @param {number[]} numbers 13 | * @return {number} 14 | */ 15 | var minArray = function (numbers) { 16 | let low = 0; 17 | let high = numbers.length - 1; 18 | 19 | while (low < high) { 20 | const mid = Math.floor((high - low) / 2) + low; 21 | if (numbers[mid] < numbers[high]) { 22 | high = mid; 23 | } else if (numbers[mid] > numbers[high]) { 24 | low = mid + 1; 25 | } else { // 由于重复元素的存在,我们并不能确定 numbers[mid]是在最小值的左侧还是右侧,因为此时numbers[high]和numbers[mid]相等,所以可以把high去掉,无论numbers[high]是不是最小值,它都有一个替代品numbers[mid] 26 | high -= 1; 27 | } 28 | } 29 | 30 | return numbers[low]; 31 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/二分查找/peakIndexInMountainArray_剑指II_69.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 剑指 Offer II 069. 山峰数组的顶部 3 | * 简单 4 | * https://leetcode.cn/problems/B1IidL/ 5 | * 6 | * 解法:二分查找 7 | */ 8 | /** 9 | * @param {number[]} arr 10 | * @return {number} 11 | */ 12 | var peakIndexInMountainArray = function (arr) { 13 | let left = 0; 14 | let right = arr.length - 1; 15 | 16 | // 返回峰值的下标 17 | while (left <= right) { 18 | const mid = Math.floor(left + (right - left) / 2); 19 | 20 | if (arr[mid] < arr[mid + 1]) { // 递增,在右边寻找 21 | left = mid + 1; 22 | } else { 23 | right = mid - 1; 24 | } 25 | } 26 | 27 | return left; 28 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/二分查找/searchMatrix_240.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 240. 搜索二维矩阵 II 3 | * 中等 4 | * https://leetcode.cn/problems/search-a-2d-matrix-ii/ 5 | * 6 | * 解法: z字形查找 7 | * 8 | * 时间O(m+n) 9 | * 空间O(1) 10 | */ 11 | /** 12 | * @param {number[][]} matrix 13 | * @param {number} target 14 | * @return {boolean} 15 | */ 16 | var searchMatrix = function (matrix, target) { 17 | let m = matrix.length; 18 | let n = matrix[0].length; 19 | 20 | let x = 0; 21 | let y = n - 1; 22 | 23 | while (x < m && y >= 0) { 24 | if (matrix[x][y] == target) { 25 | return true; 26 | } else if (matrix[x][y] > target) { 27 | y--; 28 | } else { 29 | x++; 30 | } 31 | } 32 | 33 | return false; 34 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/位操作/addBinary_剑指II_2.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 剑指 Offer II 002. 二进制加法 3 | * 简单 4 | * https://leetcode.cn/problems/JFETK5/ 5 | * 6 | * 解法:位运算 7 | */ 8 | /** 9 | * @param {string} a 10 | * @param {string} b 11 | * @return {string} 12 | */ 13 | var addBinary = function (a, b) { 14 | // 位运算 15 | // 参考官方题解 16 | 17 | let x = parseInt(a, 2); // x保存结果 18 | let y = parseInt(b, 2); // y保存进位 19 | 20 | let answer; 21 | while (y) { 22 | answer = x ^ y; // 当前x和y的无进位结果 23 | carry = (x & y) << 1; // 当前x和y的进位 24 | 25 | x = answer; 26 | y = carry; 27 | } 28 | 29 | return x.toString(2); // 十进制转二进制 30 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/位操作/add_剑指_65.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 剑指 Offer 65. 不用加减乘除做加法 3 | * 简单 4 | * https://leetcode.cn/problems/bu-yong-jia-jian-cheng-chu-zuo-jia-fa-lcof/ 5 | * 6 | * 解法:位运算 7 | */ 8 | /** 9 | * @param {number} a 10 | * @param {number} b 11 | * @return {number} 12 | */ 13 | var add = function (a, b) { 14 | // 位运算 15 | while (b != 0) { // 当进位为0时跳出 16 | let c = (a & b) << 1; // c为进位 17 | a ^= b; // a为非进位和 18 | b = c; // b为进位 19 | } 20 | return a; 21 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/位操作/countBits_338.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 338 比特位计数 3 | * 4 | * 中等 5 | * https://leetcode-cn.com/problems/counting-bits/ 6 | */ 7 | /** 8 | * 利用: 9 | * n中1的个数 = (n & n-1)中1的个数 + 1 10 | * 11 | * 1. dp[i]数组表示:数字 i 的1的个数 12 | * 2. 转移方程:dp[i] = dp[i & (i - 1)] + 1 13 | * 14 | * i & (i - 1)实现的是去除二进制 i 的最后一个1,该数一定比 i 小 15 | * 因此dp数组里一定已经计算过 i & (i - 1) 的1的个数 16 | * 再加上去掉的这个1 17 | * 只要是大于0的数至少有一个1,等式一定成立 18 | * 19 | * 20 | * 时间复杂度:O(n) 21 | * 空间复杂度:O(n) 22 | * 23 | * @param {number} num 24 | * @return {number[]} 25 | */ 26 | var countBits = function (num) { 27 | let result = new Array(num + 1); 28 | result[0] = 0; 29 | for (let i = 1; i < num + 1; i++) { 30 | // n中1的个数 = (n & n-1)中1的个数 + 1 31 | // 例如 6中1的个数 = 4中1的个数 + 1 32 | result[i] = result[i & (i - 1)] + 1 33 | } 34 | return result; 35 | }; 36 | 37 | const num = 2; 38 | console.log(countBits(num)); // [0,1,1] 39 | 40 | -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/位操作/countPrimeSetBits_762.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 762 二进制表示中质数个计算置位 3 | * 4 | * 简单 5 | * https://leetcode-cn.com/problems/prime-number-of-set-bits-in-binary-representation/ 6 | */ 7 | /** 8 | * 也是利用 n & (n-1) 解题 9 | * 10 | * 题目给定了最大值范围(R - L 的最大值为 10000), 11 | * 这个最大值范围最多不超过20bit就可以完全表示了, 12 | * 所以可以先把20以内的质数缓存一下然后对比个数即可,不需要计算是不是质数 13 | * 14 | * @param {number} L 15 | * @param {number} R 16 | * @return {number} 17 | */ 18 | var countPrimeSetBits = function (L, R) { 19 | const mapPrime = { 20 | 2: true, 21 | 3: true, 22 | 5: true, 23 | 7: true, 24 | 11: true, 25 | 13: true, 26 | 17: true, 27 | 19: true 28 | }; 29 | 30 | this.count = function (n) { 31 | let res = 0; 32 | while (n != 0) { 33 | res++; 34 | n = n & (n - 1); 35 | } 36 | return res; 37 | } 38 | 39 | let c = 0; 40 | for (let i = L; i <= R; i++) { 41 | if (mapPrime[this.count(i)]) { 42 | c++; 43 | } 44 | } 45 | 46 | return c; 47 | }; 48 | 49 | const L = 6; 50 | const R = 10; 51 | console.log(countPrimeSetBits(6, 10)); // 4 52 | -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/位操作/findComplement_476.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 476 数字的补数 3 | * 4 | * 简单 5 | * https://leetcode-cn.com/problems/number-complement/ 6 | */ 7 | /** 8 | * 利用左移右移 和 异或 9 | * 10 | * <<左移,乘以2的b次方 >>右移,除以2的b次方 11 | * 12 | * @param {number} num 13 | * @return {number} 14 | */ 15 | var findComplement = function (num) { 16 | // num在二进制下右多少位为0 17 | let count = 0; 18 | let tmpNum = num; 19 | 20 | while (tmpNum != 0) { 21 | count += 1; 22 | tmpNum >>= 1; 23 | } 24 | 25 | // 异或运算取反 26 | return num ^ ((1 << count) - 1); 27 | }; 28 | 29 | const num = 5; 30 | console.log(findComplement(num)); 31 | -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/位操作/findMaximumXOR_剑指II_67.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 剑指 Offer II 067. 最大的异或 3 | * 中等 4 | * https://leetcode.cn/problems/ms70jA/ 5 | * 6 | * 解法:哈希表 + 位运算 7 | */ 8 | /** 9 | * 10 | * @param {*} nums 11 | * @returns 12 | */ 13 | var findMaximumXOR = function (nums) { 14 | // 哈希表法 参考官方题解 15 | 16 | const HIGH_BIT = 30; 17 | let x = 0; 18 | for (let k = HIGH_BIT; k >= 0; --k) { 19 | const seen = new Set(); 20 | // 将所有的 pre^k(a_j) 放入哈希表中 21 | for (const num of nums) { 22 | // 如果只想保留从最高位开始到第 k 个二进制位为止的部分 23 | // 只需将其右移 k 位 24 | seen.add(num >> k); 25 | } 26 | 27 | // 目前 x 包含从最高位开始到第 k+1 个二进制位为止的部分 28 | // 我们将 x 的第 k 个二进制位置为 1,即为 x = x*2+1 29 | const xNext = x * 2 + 1; 30 | let found = false; 31 | 32 | // 枚举 i 33 | for (const num of nums) { 34 | if (seen.has(xNext ^ (num >> k))) { 35 | found = true; 36 | break; 37 | } 38 | } 39 | 40 | if (found) { 41 | x = xNext; 42 | } else { 43 | // 如果没有找到满足等式的 a_i 和 a_j,那么 x 的第 k 个二进制位只能为 0 44 | // 即为 x = x*2 45 | x = xNext - 1; 46 | } 47 | } 48 | return x; 49 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/位操作/findTheDifference_389.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 389 找不同 3 | * 4 | * 简单 5 | * https://leetcode-cn.com/problems/find-the-difference/ 6 | */ 7 | /** 8 | * 利用^异或的性质,n ^ n = 0, n ^ 0 = n 9 | * 10 | * 时间复杂度O(n),n为s的长度 11 | * 12 | * @param {string} s 13 | * @param {string} t 14 | * @return {character} 15 | */ 16 | var findTheDifference = function (s, t) { 17 | if (!s) { 18 | return t; 19 | } 20 | 21 | // 因为t比s多一个字符,所以可以先默认取t最后一个字符,字符转成unicode 22 | let result = t.charCodeAt(t.length - 1); 23 | 24 | // 获取字符的unicode值 25 | for (let i = 0; i < s.length; i++) { 26 | result ^= s.charCodeAt(i); 27 | result ^= t.charCodeAt(i); 28 | } 29 | 30 | // unicode转成字符 31 | return String.fromCharCode(result); 32 | }; 33 | 34 | const s = ""; 35 | const t = "b"; 36 | console.log(findTheDifference(s, t)); 37 | -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/位操作/getSum_371.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 371. 两整数之和 3 | * 简单 4 | * https://leetcode.cn/problems/sum-of-two-integers/ 5 | * 6 | * 解法:位运算 7 | * 8 | */ 9 | /** 10 | * @param {number} a 11 | * @param {number} b 12 | * @return {number} 13 | */ 14 | var getSum = function (a, b) { 15 | // 根据规律可以得出,a + b的问题拆分成求“a和b的无进位结果”与“a和b的进位结果” 16 | // 无进位加法可用 异或运算 得出 17 | // 有进位加法可用 与运算与左移 得出 18 | // 循环此过程,直到进位为0 19 | 20 | while (b != 0) { 21 | const carry = (a & b) << 1; // 进位 22 | a = a ^ b; // 无进位和 23 | b = carry; // b存进位 24 | } 25 | 26 | return a; 27 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/位操作/grayCode_89.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 89. 格雷编码 3 | * 中等 4 | * https://leetcode.cn/problems/gray-code/ 5 | * 6 | * 解法:位运算 7 | */ 8 | /** 9 | * @param {number} n 10 | * @return {number[]} 11 | */ 12 | var grayCode = function (n) { 13 | 14 | const ret = [0]; 15 | 16 | for (let i = 1; i <= n; i++) { 17 | const m = ret.length; 18 | for (let j = m - 1; j >= 0; j--) { 19 | ret.push(ret[j] | (1 << (i - 1))); 20 | } 21 | } 22 | 23 | return ret; 24 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/位操作/hammingDistance_461.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 算法常⽤操作 n&(n-1) 3 | * 作⽤:消除数字 n 的⼆进制表⽰中的最后⼀个 1 4 | */ 5 | /** 6 | * leetcode 461. 汉明距离 7 | * 8 | * 简单 9 | * https://leetcode-cn.com/problems/hamming-distance/ 10 | * 11 | * 利用先异或,然后n&(n-1) 12 | * 13 | * 时间复杂度:O(1) 14 | * 空间复杂度:O(1) 15 | * 16 | * @param {number} x 17 | * @param {number} y 18 | * @return {number} 19 | */ 20 | var hammingDistance = function (x, y) { 21 | // 异或,相同为0,不同为1,此时含有1的个数即为不同的个数 22 | let result = x ^ y; 23 | 24 | let countOne = 0; 25 | while (result !== 0) { 26 | // (n & (n-1))可以消除n最后一个1,一直到n为0为止 27 | result = result & (result - 1); 28 | countOne++; 29 | } 30 | return countOne; 31 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/位操作/hammingWeight_191.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 191 位1的个数 3 | * 4 | * 简单 5 | * https://leetcode-cn.com/problems/number-of-1-bits/ 6 | */ 7 | /** 8 | * n & n-1 可以消掉二进制最右侧的一个1 9 | * 10 | * 时间复杂度O(1) 11 | * 空间复杂度O(n) 12 | * 13 | * @param {number} n - a positive integer 14 | * @return {number} 15 | */ 16 | var hammingWeight = function (n) { 17 | if (!n) { 18 | return 0; 19 | } 20 | 21 | let count = 0; 22 | while (n != 0) { 23 | n &= (n - 1); 24 | count++; 25 | } 26 | 27 | return count; 28 | }; 29 | 30 | const n = 00000000000000000000000000001011; 31 | console.log(hammingWeight(n)); // 3 -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/位操作/hasAlternatingBits_693.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 693 交替位二进制数 3 | * https://leetcode-cn.com/problems/binary-number-with-alternating-bits/ 4 | * 5 | * 判断一个数字的二进制是不是01交替的 6 | * 7 | * 找规律 8 | * n 01010101 (1) 9 | * 10 | * n>>1 00101010 (2) 11 | * 12 | * n + n>>1 01111111 (3) 13 | * 14 | * n + n>>1 + 1 10000000 (4) 15 | * 16 | * (3) & (4) 等于0 17 | * 18 | * 19 | * @param {number} n 20 | * @return {boolean} 21 | */ 22 | var hasAlternatingBits = function (n) { 23 | return ((n + (n >> 1) + 1) & (n + (n >> 1))) == 0; 24 | }; 25 | 26 | const n = 7; 27 | console.log(hasAlternatingBits(n)); 28 | -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/位操作/isPowerOfFour_342.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 342 4的幂 3 | * 4 | * 简单 5 | * https://leetcode-cn.com/problems/power-of-four/ 6 | */ 7 | /** 8 | * 9 | * 位运算解法 10 | * 11 | * 时间复杂度:O(1) 12 | * 空间复杂度:O(1) 13 | * 14 | * @param {*} num 15 | */ 16 | var isPowerOfFour = function (num) { 17 | // 4的幂必然也是2的幂,且1永远在奇数位上,奇数位都是1的值的16进制是0x55555555 18 | return (num > 0) && ((num & (num - 1)) == 0) && (num & 0x55555555) == num; 19 | }; 20 | 21 | console.log(isPowerOfFour(16)); -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/位操作/isPowerOfTwo_231.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 231 2的幂 3 | * https://leetcode-cn.com/problems/power-of-two/ 4 | * 5 | * 给定一个整数,编写一个函数来判断它是否是 2 的幂次方 6 | */ 7 | /** 8 | * 9 | * 如果一个数字是2的幂必然只有一位是1,其他位数都是0 10 | * 11 | * @param {number} n 12 | * @return {boolean} 13 | */ 14 | var isPowerOfTwo = function (n) { 15 | return (n > 0) && ((n & (n - 1)) == 0); 16 | }; 17 | 18 | const n = 18; 19 | console.log(isPowerOfTwo(n)); 20 | -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/位操作/maxProduct_剑指II_5.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 剑指 Offer II 005. 单词长度的最大乘积 3 | * 中等 4 | * https://leetcode.cn/problems/aseY1I/ 5 | * 6 | * 解法:位运算 7 | * 8 | * 时间O(L+n^2) 9 | * 空间O(n) 10 | */ 11 | /** 12 | * @param {string[]} words 13 | * @return {number} 14 | */ 15 | var maxProduct = function (words) { 16 | // 两层遍历,依次判断每两个单词是否有公共字符 17 | 18 | // 使用位运算判断两个单词是否有公共字符,降低时间复杂度 19 | 20 | // 知识点:位掩码 21 | 22 | let n = words.length; 23 | let masks = new Array(n).fill(0); 24 | 25 | for (let i = 0; i < n; i++) { 26 | const word = words[i]; 27 | for (let j = 0; j < word.length; j++) { 28 | // 计算掩码 29 | masks[i] |= 1 << (word[j].charCodeAt() - 'a'.charCodeAt()); 30 | } 31 | } 32 | 33 | let maxResult = 0; 34 | for (let i = 0; i < n; i++) { 35 | for (let j = i + 1; j < n; j++) { 36 | if ((masks[i] & masks[j]) == 0) { // 没有公共字母 37 | maxResult = Math.max(maxResult, words[i].length * words[j].length); 38 | } 39 | } 40 | } 41 | 42 | return maxResult; 43 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/位操作/missingNumber_268.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 268. 缺失数字 3 | * 4 | * 简单 5 | * https://leetcode-cn.com/problems/missing-number/ 6 | * 7 | * 异或运算: 8 | * 一个数和它本身做异或运算结果为 0,一个数和 0 做异或运算还是它本身 9 | * 10 | * 把所有的元素和索引做异或运算,成对儿的数字都会消为 0,就会剩下这个缺失的元素 11 | * 12 | * 时间复杂度 O(N) 13 | * 空间复杂度 O(1) 14 | * 15 | * @param {number[]} nums 16 | * @return {number[]} 17 | */ 18 | var missingNumber = function (nums) { 19 | let n = nums.length; 20 | 21 | let res = 0; 22 | // 先和新补的索引异或一下 23 | res ^= n; 24 | 25 | // 和其他的元素、索引做异或 26 | for (let i = 0; i < n; i++) { 27 | res ^= i ^ nums[i]; 28 | } 29 | 30 | return res; 31 | }; 32 | 33 | // 用等差数列解法 34 | var missingNumber2 = function (nums) { 35 | let n = nums.length; 36 | // 公式:(首项 + 末项) * 项数 / 2 37 | let expect = (0 + n) * (n + 1) / 2; 38 | 39 | let sum = 0; 40 | for (let x of nums) { 41 | sum += x; 42 | } 43 | 44 | return expect - sum; 45 | } 46 | 47 | const nums = [9, 6, 4, 2, 3, 5, 7, 0, 1]; 48 | console.log(missingNumber(nums)); // 8 49 | console.log(missingNumber2(nums)); // 8 50 | -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/位操作/reverseBits_190.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 190. 颠倒二进制位 3 | * 简单 4 | * https://leetcode.cn/problems/reverse-bits/ 5 | * 6 | * 解法:位运算 7 | */ 8 | /** 9 | * @param {number} n - a positive integer 10 | * @return {number} - a positive integer 11 | */ 12 | var reverseBits = function (n) { 13 | let ret = 0; 14 | for (let i = 0; i < 32; i++) { 15 | ret <<= 1; 16 | ret += (n & 1); 17 | n >>= 1; 18 | } 19 | return ret >>> 0; 20 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/位操作/singleNonDuplicate_剑指II_70.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 剑指 Offer II 070. 排序数组中只出现一次的数字 3 | * 中等 4 | * https://leetcode.cn/problems/skFtm2/ 5 | * 6 | * 解法:异或 7 | */ 8 | /** 9 | * @param {number[]} nums 10 | * @return {number} 11 | */ 12 | var singleNonDuplicate = function (nums) { 13 | // 异或 14 | let result = 0; 15 | for (let num of nums) { 16 | result = result ^ num; 17 | } 18 | 19 | return result; 20 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/位操作/singleNumber_136.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 136. 只出现一次的数字 3 | * 4 | * 简单 5 | * https://leetcode-cn.com/problems/single-number/ 6 | */ 7 | /** 8 | * 9 | * 利用 n^n=0,n^0=n 10 | * 11 | * 时间复杂度O(n) 12 | * 13 | * @param {number[]} nums 14 | * @return {number} 15 | */ 16 | var singleNumber = function (nums) { 17 | let result = 0; 18 | 19 | for (let num of nums) { 20 | result ^= num; 21 | } 22 | 23 | return result; 24 | }; 25 | const nums = [1, 1, 2, 3, 3, 4, 4]; 26 | console.log(singleNumber(nums)); -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/位操作/singleNumber_剑指II_4.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 剑指 Offer II 004. 只出现一次的数字 3 | * 中等 4 | * https://leetcode.cn/problems/WGki4K/ 5 | * 6 | * 解法:位运算 7 | */ 8 | /** 9 | * @param {number[]} nums 10 | * @return {number} 11 | */ 12 | var singleNumber = function (nums) { 13 | let ans = 0; 14 | for (let i = 0; i < 32; ++i) { 15 | let total = 0; 16 | for (const num of nums) { 17 | total += ((num >> i) & 1); 18 | } 19 | if (total % 3 != 0) { 20 | ans |= (1 << i); 21 | } 22 | } 23 | return ans; 24 | }; 25 | -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/位操作/singleNumber_剑指_56_II.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 剑指 Offer 56 - II. 数组中数字出现的次数 II 3 | * 中等 4 | * https://leetcode.cn/problems/shu-zu-zhong-shu-zi-chu-xian-de-ci-shu-ii-lcof/ 5 | * 6 | * 解法:位运算 7 | */ 8 | /** 9 | * @param {number[]} nums 10 | * @return {number} 11 | */ 12 | var singleNumber = function (nums) { 13 | 14 | // 因为数组中只有一个数出现了一次,那么(各个二进制位为1的个数 % 3) 便能求出这个数哪些位置为1, 最后再将其转换为十进制 15 | 16 | let counts = new Array(32).fill(0); // 32表示4个字节即32位 17 | 18 | // 统计所有数字的各二进制位的 1 的出现次数 19 | for (let num of nums) { 20 | for (let j = 0; j < 32; j++) { 21 | counts[j] += num & 1; // 与运算,都为1才为1 22 | num >>>= 1; // 第 j 位 --> 第 j + 1 位 23 | } 24 | } 25 | 26 | // 没看懂 27 | let res = 0; 28 | let m = 3; 29 | for (let i = 0; i < 32; i++) { 30 | res <<= 1; // 左移1位 31 | res |= counts[31 - i] % m; // 恢复第 i 位的值到 res 32 | } 33 | return res; 34 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/位操作/singleNumbers_剑指_56_I.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 剑指 Offer 56 - I. 数组中数字出现的次数 4 | * 中等 5 | * https://leetcode.cn/problems/shu-zu-zhong-shu-zi-chu-xian-de-ci-shu-lcof/ 6 | * 7 | * 解法:位运算 8 | * 9 | * 时间O(n) 10 | * 空间O(1) 11 | */ 12 | /** 13 | * @param {number[]} nums 14 | * @return {number[]} 15 | */ 16 | var singleNumbers = function (nums) { 17 | // 解法:异或分组 18 | // 把所有数字分成两组 19 | // (1)两个只出现一次的数字在不同的组中 20 | // (2)相同的数字会被分到相同的组中 21 | // 最后分别对两个子数组进行异或运算即可得到结果 22 | 23 | // 设最后结果所求的两个数分别是x和y 24 | // 1、对所有数字进行异或,最终得到x和y异或的结果,即为n,即n = x^y 25 | // 2、接下来对nums进行分组,先找到n中任意一个为1的位 26 | 27 | let n = 0; 28 | for (let num of nums) { // 异或 相同为0 不同为1 29 | n = n ^ num; 30 | } 31 | 32 | // 在异或结果n中找到任意一个为 1 的位 33 | let m = 1; 34 | while ((n & m) == 0) { // 循环左移 35 | m <<= 1; 36 | } 37 | 38 | // 拆分成两个数组 39 | let x = 0; 40 | let y = 0; 41 | for (let num of nums) { 42 | if ((num & m) != 0) { // 与运算,根据这一位(m),对所有的数字进行分组;都为1时,与运算才为1;nums中相同的数会被分到同一个子数组中 43 | x ^= num; 44 | } else { 45 | y ^= num; 46 | } 47 | } 48 | 49 | return [x, y]; 50 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/位操作/sumNums_剑指_64.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 剑指 Offer 64. 求1+2+…+n 3 | * 中等 4 | * https://leetcode.cn/problems/qiu-12n-lcof/ 5 | * 6 | */ 7 | /** 8 | * @param {number} n 9 | * @return {number} 10 | */ 11 | var sumNums = function (n) { 12 | if (n <= 1) { 13 | return n; 14 | } 15 | 16 | let curIndex = 1; 17 | let sum = 0; 18 | 19 | while (curIndex < n) { 20 | sum += (n + 1); 21 | curIndex++; 22 | } 23 | 24 | return sum; 25 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/位操作/个别位操作.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 个别位操作小技巧 3 | */ 4 | 5 | // 判断两个数是否异号 6 | function test1(x, y) { 7 | const f = ((x ^ y) < 0); // true 8 | console.log('是否异号', f); 9 | return f; 10 | } 11 | test1(-1, 2); 12 | 13 | // 交换两个数 14 | function test2(a, b) { 15 | a ^= b; 16 | b ^= a; 17 | a ^= b; 18 | return { a, b }; 19 | } 20 | console.log(test2(1, 2)); 21 | 22 | 23 | /** 24 | * 25 | * 利用n & (n-1) 判断⼀个数是不是 2 的指数 26 | * 27 | * ⼀个数如果是 2 的指数,那么它的⼆进制表⽰⼀定只含有⼀个 1 28 | * @param {*} n 29 | */ 30 | function isPowerOfTwo(n) { 31 | if (n <= 0) { 32 | return false; 33 | } 34 | return (n & (n - 1)) == 0; 35 | } 36 | console.log(isPowerOfTwo(1)); 37 | -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/前缀和/NumMatrix_剑指II_13.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 剑指 Offer II 013. 二维子矩阵的和 3 | * 中等 4 | * https://leetcode.cn/problems/O4NDxx/ 5 | * 6 | * 解法:一维前缀和 7 | * 8 | * 时间O(mn) 9 | * 空间O(mn) 10 | */ 11 | /** 12 | * @param {number[][]} matrix 13 | */ 14 | var NumMatrix = function (matrix) { 15 | const m = matrix.length; 16 | 17 | if (m > 0) { 18 | const n = matrix[0].length; 19 | this.sums = new Array(m).fill(0).map(() => new Array(n + 1).fill(0)); 20 | for (let i = 0; i < m; i++) { 21 | for (let j = 0; j < n; j++) { 22 | this.sums[i][j + 1] = this.sums[i][j] + matrix[i][j]; 23 | } 24 | } 25 | } 26 | }; 27 | 28 | NumMatrix.prototype.sumRegion = function (row1, col1, row2, col2) { 29 | let sum = 0; 30 | 31 | for (let i = row1; i <= row2; i++) { 32 | sum += this.sums[i][col2 + 1] - this.sums[i][col1]; 33 | } 34 | 35 | return sum; 36 | }; 37 | 38 | /** 39 | * Your NumMatrix object will be instantiated and called as such: 40 | * var obj = new NumMatrix(matrix) 41 | * var param_1 = obj.sumRegion(row1,col1,row2,col2) 42 | */ -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/前缀和/Solution_剑指II_71.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 剑指 Offer II 071. 按权重生成随机数 3 | * 中等 4 | * https://leetcode.cn/problems/cuyjEf/ 5 | * 6 | * 解法:前缀和 + 二分查找 7 | * 8 | */ 9 | var Solution = function (w) { 10 | // 前缀和数组 11 | this.pre = new Array(w.length).fill(0); 12 | this.pre[0] = w[0]; 13 | for (let i = 1; i < w.length; ++i) { 14 | this.pre[i] = this.pre[i - 1] + w[i]; 15 | } 16 | 17 | this.total = _.sum(w); 18 | }; 19 | 20 | Solution.prototype.pickIndex = function () { 21 | const x = Math.floor((Math.random() * this.total)) + 1; 22 | 23 | const binarySearch = (x) => { 24 | let low = 0; 25 | let high = this.pre.length - 1; 26 | while (low < high) { 27 | const mid = Math.floor((high - low) / 2) + low; 28 | 29 | if (this.pre[mid] < x) { 30 | low = mid + 1; 31 | } else { 32 | high = mid; 33 | } 34 | } 35 | 36 | return low; 37 | } 38 | 39 | return binarySearch(x); 40 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/前缀和/constructArr_剑指_66.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 剑指 Offer 66. 构建乘积数组 3 | * 中等 4 | * https://leetcode.cn/problems/gou-jian-cheng-ji-shu-zu-lcof/ 5 | * 6 | * 解法:前缀和 7 | */ 8 | /** 9 | * @param {number[]} a 10 | * @return {number[]} 11 | */ 12 | var constructArr = function (a) { 13 | // 方法一:时间O(n) 空间O(n) 14 | // 用两个数组L和R,L[i]表示i左侧所有数字的乘积,R[i]表示i右侧所有数字的乘积 15 | // 所以L[i] = L[i-1] * a[i-1] 其中 L[0] = 1 16 | // 所以R[i] = R[i+1] * a[i+1] 其中 R[a.length - 1] = 1 17 | 18 | // 方法二:时间O(n) 空间O(1) 19 | // 可以把方法一到R数组去掉,改成在迭代过程中用一个变量保存i右边所有数字的乘积 20 | 21 | let len = a.length; 22 | if (len == 0) { 23 | return []; 24 | } 25 | 26 | let result = new Array(len); 27 | result[0] = 1; 28 | 29 | for (let i = 1; i < len; i++) { 30 | result[i] = result[i - 1] * a[i - 1]; 31 | } 32 | 33 | let R = 1; 34 | for (let i = len - 1; i >= 0; i--) { 35 | result[i] = result[i] * R; // 于索引 i,左边的乘积为 answer[i],右边的乘积为 R 36 | R *= a[i]; // R 需要包含右边所有的乘积,所以计算下一个结果时需要将当前值乘到 R 上 37 | } 38 | 39 | return result; 40 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/前缀和/findMaxLength_剑指II_11.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 剑指 Offer II 011. 0 和 1 个数相同的子数组 3 | * 中等 4 | * https://leetcode.cn/problems/A1NYOS/ 5 | * 6 | * 解法:哈希表 + 前缀和 7 | */ 8 | /** 9 | * @param {number[]} nums 10 | * @return {number} 11 | */ 12 | var findMaxLength = function (nums) { 13 | // 0和1数量相同,等价于 1的数量减去0的数量等于0,可以把数组的0全部变成-1,这样问题就转化为“求最长的连续子数组,其元素和为0” 14 | 15 | // 参考官方题解 时间O(n) 空间O(n) 16 | let map = new Map(); // key是某个前缀和,value是该前缀和第一次出现时的下标 17 | map.set(0, -1); // 空时前缀和是0,出现的下标用-1表示 18 | 19 | let counter = 0; // 存储前缀和 20 | 21 | let result = 0; 22 | for (let i = 0; i < nums.length; i++) { 23 | // 计算前缀和 24 | if (nums[i] == 1) { 25 | counter++; 26 | } else { 27 | counter--; 28 | } 29 | 30 | if (map.has(counter)) { // 说明从index + 1 到 i 有相同数量的0和1。因为[0, index]的前缀和sum1跟[0, i]的前缀和sum2是相等的,所以sum2-sum1的值为0,即可推出[index + 1, i]区间的和是0,所以此区间有相同数量的0和1 31 | const index = map.get(counter); 32 | result = Math.max(result, i - index); 33 | } else { 34 | map.set(counter, i); // 把当前前缀和 和 下标存入map中 35 | } 36 | } 37 | 38 | return result; 39 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/前缀和/pivotIndex_724.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 724. 寻找数组的中心下标 3 | * 简单 4 | * 5 | * https://leetcode-cn.com/problems/find-pivot-index/ 6 | * 7 | * 时间O(n) 8 | * 空间O(1) 9 | * 10 | * @param {number[]} nums 11 | * @return {number} 12 | */ 13 | var pivotIndex = function (nums) { 14 | let total = nums.reduce((total, cur) => total + cur, 0); 15 | 16 | let preSum = 0; 17 | for (let i = 0; i < nums.length; i++) { 18 | // 左边之和等于右边之和 19 | if (preSum + nums[i] == total - preSum) { // 也可写成2 * preSum + nums[i] === total 20 | return i; 21 | } 22 | preSum += nums[i]; 23 | } 24 | 25 | return -1; 26 | }; 27 | -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/前缀和/productExceptSelf_238.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 238. 除自身以外数组的乘积 3 | * 中等 4 | * https://leetcode.cn/problems/product-of-array-except-self/ 5 | * 6 | * 解法:前缀和 7 | * 8 | * 时间O(N) 9 | * 空间O(1) 10 | */ 11 | /** 12 | * @param {number[]} nums 13 | * @return {number[]} 14 | */ 15 | var productExceptSelf = function (nums) { 16 | let n = nums.length; 17 | 18 | // leftArr[i]表示i左侧所有数字的乘积,leftArr[i] = leftArr[i-1] * nums[i-1] 19 | let leftArr = new Array(n); 20 | 21 | leftArr[0] = 1; // 0索引左侧没有数字,所以可以为1,任何数乘以1都不变 22 | 23 | for (let i = 1; i < n; i++) { 24 | leftArr[i] = leftArr[i - 1] * nums[i - 1]; 25 | } 26 | 27 | let r = 1; 28 | for (let i = n - 1; i >= 0; i--) { 29 | leftArr[i] = leftArr[i] * r; // 左侧 * 右侧 30 | r = r * nums[i]; 31 | } 32 | 33 | return leftArr; 34 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/前缀和/网易面试题.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 用一个长度为 150 的数组 prefix 来记录每个分数出现的次数,数组下标是分数值,数组的值就是分数出现的 次数; 3 | * 4 | * 遍历 prefix,计算前缀和,第i位表示分数不超过分数为i的同学的人数 5 | * 6 | * 时间O(n),n为scores全班所有同学的人数 7 | * 空间O(n) 8 | * 9 | * @param {array} scores 所有同学第分数 10 | * @param {number} m 第m个同学 11 | */ 12 | function wangyi(scores, m) { 13 | // 下标是分数,值是有多少人是该分数 14 | let prefix = Array(151).fill(0); 15 | 16 | scores.forEach(v => prefix[v]++); 17 | 18 | 19 | for (let i = 1; i < prefix.length; i++) { 20 | prefix[i] += prefix[i - 1]; 21 | } 22 | 23 | // 分数即下标 24 | const score = scores[m - 1]; 25 | // 分数不超过i的同学的人数/总人数 26 | const p = (prefix[score] / scores.length) * 100; 27 | 28 | return p.toFixed(6) + '%'; 29 | } 30 | 31 | const scores = [50, 60, 70]; 32 | const m = 2; 33 | console.log(wangyi(scores, m)); // 66.666667% -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/动态规划/combinationSum4_剑指II_104.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 剑指 Offer II 104. 排列的数目 3 | * 中等 4 | * https://leetcode.cn/problems/D0F0SV/ 5 | * 6 | * 解法:动态规划 7 | * 8 | * 时间O(target*n) 9 | * 空间O(target) 10 | */ 11 | /** 12 | * @param {number[]} nums 13 | * @param {number} target 14 | * @return {number} 15 | */ 16 | var combinationSum4 = function (nums, target) { 17 | // dp[x]表示选取的元素之和等于x的方案数,目标就是求dp[target] 18 | // base case: dp[0]=1,只有当不选取任何元素时,元素之和才为0,因此只有 1 种方案 19 | 20 | // 当 1 <= i <= target 时,如果存在一种排列,其中的元素之和等于i,则该排列的最后一个元素一定是数组nums 中的一个元素。假设该排列的最后一个元素是num,则一定有num <= i,对于元素之和等于i − num 的每一种排列,在最后添加 num 之后即可得到一个元素之和等于 i 的排列,因此在计算 dp[i] 时,应该计算所有的dp[i−num] 之和 21 | 22 | const dp = new Array(target + 1).fill(0); 23 | dp[0] = 1; 24 | 25 | for (let i = 1; i <= target; i++) { 26 | for (const num of nums) { 27 | if (num <= i) { 28 | dp[i] += dp[i - num]; 29 | } 30 | } 31 | } 32 | 33 | return dp[target]; 34 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/动态规划/cuttingRope_剑指_14_I.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 剑指 Offer 14- I. 剪绳子 3 | * 中等 4 | * https://leetcode.cn/problems/jian-sheng-zi-lcof/ 5 | * 6 | * 解法:动态规划 7 | * 动态规划 参考https://leetcode.cn/problems/jian-sheng-zi-lcof/solution/by-nehzil-w61p/ 8 | * 9 | * 时间O(n) 10 | * 空间O(n) 11 | */ 12 | /** 13 | * @param {number} n 14 | * @return {number} 15 | */ 16 | var cuttingRope = function (n) { 17 | // dp[i]表示把长度为i绳子剪成至少2段后,绳子的最大乘积 18 | // 当i>=2时,第1段减的长度是j,如果第2段不剪,那乘积dp[i]=j * (i - j);如果第2段继续剪,那乘积为dp[i]=j * dp[i-j],所以状态转移方程为dp[i] = max(dp[i], max(j*(i-j), j*dp[i-j])), j >= 1 19 | // base case,绳子长为0和1时,没法剪成2段,所以dp[0]=0,dp[1]=0 20 | if (n <= 1) { 21 | return 0; 22 | } 23 | 24 | const dp = Array(n + 1).fill(0); 25 | dp[2] = 1; 26 | 27 | for (let i = 3; i <= n; i++) { // i要等于n,因为绳子最长是n 28 | for (let j = 1; j < i; j++) { // 注意j < i,因为假设第1段是1,那第2段最多只能是i - 1 29 | dp[i] = Math.max(dp[i], Math.max(j * (i - j), j * dp[i - j])); 30 | } 31 | } 32 | 33 | return dp[n]; 34 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/动态规划/dicesProbability_剑指_60.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 剑指 Offer 60. n个骰子的点数 3 | * 中等 4 | * https://leetcode.cn/problems/nge-tou-zi-de-dian-shu-lcof/ 5 | * 6 | * 解法:动态规划 7 | */ 8 | /** 9 | * @param {number} n 10 | * @return {number[]} 11 | */ 12 | var dicesProbability = function (n) { 13 | // 动态规划 14 | 15 | let dp = new Array(n + 1).fill().map(() => new Array(n * 6 + 1).fill(0)), result = []; 16 | 17 | for (let i = 1; i <= 6; i++) { 18 | dp[1][i] = 1; 19 | } 20 | 21 | for (let i = 2; i <= n; i++) { 22 | for (let j = i; j <= 6 * i; j++) { 23 | for (let cur = 1; cur <= 6; cur++) { 24 | if (j <= cur) { 25 | break; 26 | } 27 | dp[i][j] += dp[i - 1][j - cur]; 28 | } 29 | } 30 | } 31 | 32 | let all = Math.pow(6, n); 33 | 34 | for (let i = n; i <= n * 6; i++) { 35 | result.push(dp[n][i] / all); 36 | } 37 | 38 | return result; 39 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/动态规划/fib_剑指_10_I.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 剑指 Offer 10- I. 斐波那契数列 3 | * 简单 4 | * https://leetcode.cn/problems/fei-bo-na-qi-shu-lie-lcof/ 5 | * 6 | * 解法:动态规划 自上而下推导 7 | * 8 | * 时间o(n) 9 | * 空间o(1) 10 | */ 11 | /** 12 | * @param {number} n 13 | * @return {number} 14 | */ 15 | var fib = function (n) { 16 | if (n == 0 || n == 1) { 17 | return n; 18 | } 19 | 20 | let left = 0; // 初始为f(0)的值 21 | let right = 1;// 初始为f(1)的值 22 | let sum = 0; 23 | 24 | for (let i = 2; i <= n; i++) { 25 | sum = (left + right) % 1000000007; 26 | 27 | left = right; 28 | right = sum; 29 | } 30 | 31 | return sum; 32 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/动态规划/isInterleave_剑指II_96.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 剑指 Offer II 096. 字符串交织 3 | * 中等 4 | * https://leetcode.cn/problems/IY6buf/ 5 | * 6 | * 解法:动态规划 7 | * 8 | * 参考题解 https://leetcode.cn/problems/IY6buf/solution/hui-su-ji-yi-hua-san-zhi-zhen-mo-ni-hui-mcmyo/ 9 | */ 10 | /** 11 | * @param {string} s1 12 | * @param {string} s2 13 | * @param {string} s3 14 | * @return {boolean} 15 | */ 16 | var isInterleave = function (s1, s2, s3) { 17 | if (!s1 && !s2 && !s3) { 18 | return true; 19 | } 20 | 21 | let n1 = s1.length; 22 | let n2 = s2.length; 23 | let n3 = s3.length; 24 | if (n1 + n2 !== n3) { 25 | return false; 26 | } 27 | 28 | const dp = new Array(n1 + 1).fill(0).map(() => new Array(n2 + 1).fill(true)); 29 | 30 | for (let i = 1; i < dp.length; i++) { 31 | dp[i][0] = dp[i - 1][0] && s3[i - 1] === s1[i - 1]; 32 | } 33 | for (let j = 1; j < dp[0].length; j++) { 34 | dp[0][j] = dp[0][j - 1] && s3[j - 1] === s2[j - 1]; 35 | } 36 | 37 | for (let i = 1; i < dp.length; i++) { 38 | for (let j = 1; j < dp[i].length; j++) { 39 | dp[i][j] = 40 | (dp[i - 1][j] && s3[i + j - 1] === s1[i - 1]) 41 | || (dp[i][j - 1] && s3[i + j - 1] === s2[j - 1]); 42 | } 43 | } 44 | 45 | return dp[n1][n2]; 46 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/动态规划/isMatch_44.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 44. 通配符匹配 3 | * 困难 4 | * https://leetcode.cn/problems/wildcard-matching/ 5 | * 6 | * 解法:动态规划 7 | */ 8 | /** 9 | * @param {string} s 10 | * @param {string} p 11 | * @return {boolean} 12 | */ 13 | var isMatch = function (s, p) { 14 | // 跟题目类似 *号的含义不同 https://leetcode.cn/problems/regular-expression-matching/ 15 | 16 | // 动态规划 参考官方题解 17 | let m = s.length; 18 | let n = p.length; 19 | 20 | // dp[i][j] 表示字符串 s 的前 i 个字符和模式 p 的前 j 个字符是否能匹配 21 | const dp = Array.from(new Array(m + 1), () => new Array(n + 1).fill(false)); 22 | dp[0][0] = true; // s和p都是空字符串,肯定可以匹配 23 | 24 | for (let i = 1; i <= n; i++) { 25 | if (p[i - 1] == '*') { 26 | dp[0][i] = true; 27 | } else { 28 | break; 29 | } 30 | } 31 | 32 | for (let i = 1; i <= m; i++) { 33 | for (let j = 1; j <= n; j++) { 34 | if (p[j - 1] == '*') { 35 | dp[i][j] = dp[i][j - 1] || dp[i - 1][j]; 36 | } else if (p[j - 1] == '?' || s[i - 1] == p[j - 1]) { 37 | dp[i][j] = dp[i - 1][j - 1]; 38 | } 39 | } 40 | } 41 | 42 | return dp[m][n]; 43 | 44 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/动态规划/lenLongestFibSubseq_剑指II_93.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 剑指 Offer II 093. 最长斐波那契数列 3 | * 中等 4 | * https://leetcode.cn/problems/Q91FMA/ 5 | * 6 | * 解法:动态规划 7 | */ 8 | /** 9 | * @param {number[]} arr 10 | * @return {number} 11 | */ 12 | var lenLongestFibSubseq = function (arr) { 13 | const n = arr.length; 14 | const map = new Map(); // key是arr的元素,value是元素在arr的下标 15 | 16 | // dp[i][j],表示最大元素是arr[i]且 次大元素是arr[j] 的斐波拉契数列时的长度 17 | // 状态转移方程:dp[i,j] = dp[j,k] + 1,条件是 arr[k] = arr[i] - arr[j] 且k < j < i 18 | 19 | const dp = new Array(n).fill(0).map(() => new Array(n).fill(0)); 20 | 21 | for (let i = 0; i < n; i++) { 22 | map.set(arr[i], i); 23 | } 24 | 25 | let ans = 0; 26 | for (let i = 1; i < n; i++) { 27 | for (let j = 0; j < i; j++) { 28 | // arr[i]-arr[j]即arr[i]跟i前面的每个元素的差值 29 | // 如果差值存在,即k>=0且k= 0 && k < j 35 | ? dp[j][k] + 1 36 | : 2; 37 | 38 | if (ans < dp[i][j]) { 39 | ans = dp[i][j]; 40 | } 41 | } 42 | } 43 | 44 | // 斐波拉契数列必须大于2,当小于等于2则返回0 45 | return ans > 2 ? ans : 0; 46 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/动态规划/maxCoinsBalloon_312.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 312 戳气球 3 | * https://leetcode-cn.com/problems/burst-balloons/ 4 | * 5 | * @param {number[]} nums 6 | * @return {number} 7 | */ 8 | var maxCoins = function (nums) { 9 | let n = nums.length; 10 | 11 | const points = new Array(n + 2); 12 | points[0] = points[n + 1] = 1; 13 | for (let i = 1; i <= n; i++) { 14 | points[i] = nums[i - 1]; 15 | } 16 | 17 | // 初始化为0 18 | const dp = new Array(n + 2); 19 | for (let i = 0; i <= n + 1; i++) { 20 | dp[i] = new Array(n + 2); 21 | for (let j = 0; j <= n + 1; j++) { 22 | dp[i][j] = 0; 23 | } 24 | } 25 | console.log(dp); 26 | 27 | // 开始状态转移,i从下往上,从左往右遍历 28 | for (let i = n; i >= 0; i--) { 29 | // j 应该从左往右 30 | for (let j = i + 1; j < n + 2; j++) { 31 | // 最后戳破的气球是哪个? 32 | for (let k = i + 1; k < j; k++) { 33 | // 择优做选择 34 | dp[i][j] = Math.max( 35 | dp[i][j], 36 | dp[i][k] + dp[k][j] + points[i] * points[j] * points[k] 37 | ); 38 | } 39 | } 40 | } 41 | 42 | return dp[0][n + 1]; 43 | }; 44 | 45 | const nums = [3, 1, 5, 8]; 46 | console.log(maxCoins(nums)); 47 | -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/动态规划/maxSubArray_53.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 53. 最大子数组和 3 | * 简单 4 | * https://leetcode.cn/problems/maximum-subarray/ 5 | * 6 | * 解法:动态规划 7 | * 8 | * 空间O(1) 9 | * 时间O(n) 10 | */ 11 | /** 12 | * @param {number[]} nums 13 | * @return {number} 14 | */ 15 | var maxSubArray = function (nums) { 16 | // dp[i]表示以i为结尾的连续子数组的最大和 17 | let pre = 0; 18 | let rst = nums[0]; 19 | 20 | for (let i = 0; i < nums.length; i++) { 21 | // 转移方程 dp[i]=max(dp[i-1]+ nums[i], nums[i])) 22 | // 可能是nums[i]加上dp[i-1]是最大值,也可能是nums[i]自己就是最大值 23 | pre = Math.max(pre + nums[i], nums[i]); 24 | rst = Math.max(pre, rst); 25 | 26 | } 27 | 28 | return rst; 29 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/动态规划/maxSubArray_剑指_42.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 剑指 Offer 42. 连续子数组的最大和 3 | * 简单 4 | * 5 | * https://leetcode.cn/problems/lian-xu-zi-shu-zu-de-zui-da-he-lcof/ 6 | * 7 | * 解法:动态规划 8 | */ 9 | /** 10 | * @param {number[]} nums 11 | * @return {number} 12 | */ 13 | var maxSubArray = function (nums) { 14 | // 动态规划 15 | // dp[i]表示以i为结尾时连续子数组的和最大值,所以dp[i]即为所求结果 16 | // 转移方程 dp[i] = max(nums[i], dp[i - 1] + nums[i]) 因为nums[i]可以考虑自己成为一段,或着和前面的dp[i-1]成为一段 17 | // dp[0] = nums[0]; 18 | // 时间O(n) 空间O(1) 19 | let pre = nums[0]; 20 | let maxRst = pre; 21 | 22 | for (let i = 1; i < nums.length; i++) { 23 | pre = Math.max(nums[i] + pre, nums[i]); 24 | maxRst = Math.max(pre, maxRst); 25 | } 26 | 27 | return maxRst; 28 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/动态规划/numDecodings_91.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 91. 解码方法 3 | * 中等 4 | * https://leetcode.cn/problems/decode-ways/ 5 | * 6 | * 解法:动态规划 7 | */ 8 | /** 9 | * @param {string} s 10 | * @return {number} 11 | */ 12 | var numDecodings = function (s) { 13 | // 动态规划 14 | 15 | // dp[i]表示第i个字符为结尾的字符串s[1...i]的编码总数 16 | // 当i可以与i-1组合编码成一个字符时,dp[i]=dp[i-2](此时s[i-1] != 0且 10 * s[i-1] + s[i] <= 26 );当i自己编码成一个字符时,dp[i]=dp[i-1] (此时s[i] != 0) 17 | // 状态转移方程: dp[i] = dp[i-1] + dp[i-2] 累加即可 18 | // dp[0] = 1 只有一个数字,一定只有一种编码 19 | 20 | const n = s.length; 21 | const dp = new Array(n + 1).fill(0); 22 | 23 | dp[0] = 1; 24 | 25 | // 26 | for (let i = 1; i <= n; i++) { 27 | if (s[i - 1] !== '0') { 28 | dp[i] += dp[i - 1]; 29 | } 30 | if (i > 1 && s[i - 2] != '0' && ((s[i - 2] - '0') * 10 + (s[i - 1] - '0') <= 26)) { 31 | dp[i] += dp[i - 2]; 32 | } 33 | } 34 | 35 | return dp[n]; 36 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/动态规划/numSquares_279.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 279. 完全平方数 3 | * 中等 4 | * https://leetcode.cn/problems/perfect-squares/ 5 | * 6 | * 解法:动态规划 7 | * 8 | * 时间O(n*sqrt(n)),sqrt 为平方根 9 | * 空间O(n) 10 | */ 11 | /** 12 | * @param {number} n 13 | * @return {number} 14 | */ 15 | var numSquares = function (n) { 16 | 17 | // dp[i]表示完全平方数和为i的最小个数(dp长度为n+1) 18 | // 初始化:dp[0] = 0; dp[i] = i,dp[i]最坏情况等于i,即i个1相加 19 | // dp方程:dp[i] = min(dp[i], dp[i-j*j]+1) 其中, j是平方数, j=1~k, k*k<= i 20 | 21 | // 完全平方数和为i的最小个数(新dp[i])=当前完全平方数和为i的最大个数(旧dp[i]) + 完全平方数和为(i - j * j )的最小个数dp[i - j * j ] + 完全平方数和为 j * j的 最小个数(由于dp[j*j]=1,所以为1) 22 | 23 | 24 | let dp = new Array(n + 1).fill(0); 25 | 26 | for (let i = 1; i <= n; i++) { 27 | dp[i] = i; 28 | for (let j = 1; i - j * j >= 0; j++) { 29 | dp[i] = Math.min(dp[i], dp[i - j * j] + 1); 30 | } 31 | } 32 | return dp[n]; 33 | 34 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/动态规划/translateNum_剑指_46.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 剑指 Offer 46. 把数字翻译成字符串 3 | * 中等 4 | * https://leetcode.cn/problems/ba-shu-zi-fan-yi-cheng-zi-fu-chuan-lcof/ 5 | * 6 | * 解法:动态规划 7 | */ 8 | /** 9 | * @param {number} num 10 | * @return {number} 11 | */ 12 | var translateNum = function (num) { 13 | // 动态规划 14 | // dp[i]表示,以i为结尾的字符串有dp[i]种翻译的方法 15 | // 2种选择 1、s[i]可以单独翻译 2、s[i-1]和s[i]可以一起翻译(前提是组合的数在 10−25 之间) 16 | 17 | // 状态转移方程 18 | // 1、当s[i]单独翻译,dp[i] = dp[i-1] 19 | // 2、当s[i]和前面的数s[i-1]组合翻译 20 | // 如果组合的数不在[10,25]之间,相当于还是得拆开来单独翻译,即dp[i] = dp[i-1] 21 | // 如果组合的数在[10,25]之间,此时dp[i] = dp[i-2] 22 | // 综上,dp[i] = dp[i-1] + dp[i-2] 23 | 24 | // base case dp[0] = dp[1] = 1 25 | 26 | 27 | let s = String(num); 28 | 29 | let pre = 1; // 即dp[0] 30 | let cur = 1; // 即dp[1] 31 | 32 | for (let i = 2; i <= s.length; i++) { 33 | const compareNum = s.substring(i - 2, i); 34 | 35 | // 计算dp[i] 36 | let c = +compareNum >= 10 && compareNum <= 25 37 | ? pre + cur 38 | : pre; 39 | 40 | cur = pre; 41 | pre = c; 42 | } 43 | 44 | return pre; 45 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/动态规划/uniquePaths_62.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 62. 不同路径 3 | * 中等 4 | * https://leetcode.cn/problems/unique-paths/ 5 | * 6 | * 解法:动态规划 7 | * 8 | * 时间O(mn) 9 | * 空间O(mn) 10 | */ 11 | /** 12 | * @param {number} m 13 | * @param {number} n 14 | * @return {number} 15 | */ 16 | var uniquePaths = function (m, n) { 17 | // 18 | // dp[i][j]表示走到(i,j)下标的位置的总路径数 19 | // case: dp[0][0]为1,因为只能向右和向下,所以dp[0][j]都为1,dp[1][0]也都为1 20 | // (i,j)位置的总路径数,可以为其左边的总路径数 + 其上边的总路径数,即状态转移方程 dp[i][j] = dp[i-1][j] + dp[i][j-1] 21 | // 所求结果即为dp[m-1][n-1] 22 | 23 | // 初始化数组 24 | let dp = []; 25 | for (let i = 0; i < m; i++) { 26 | dp[i] = []; 27 | for (let j = 0; j < n; j++) { 28 | if (i === 0) { 29 | dp[i][j] = 1; 30 | } else if (j === 0) { 31 | dp[i][j] = 1; 32 | } else { 33 | dp[i][j] = 0; 34 | } 35 | } 36 | } 37 | 38 | // 状态转移 39 | for (let i = 1; i < m; i++) { 40 | for (let j = 1; j < n; j++) { 41 | dp[i][j] = dp[i - 1][j] + dp[i][j - 1]; 42 | } 43 | } 44 | 45 | return dp[m - 1][n - 1]; 46 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/动态规划/uniquePaths_剑指II_98.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 剑指 Offer II 098. 路径的数目 3 | * 中等 4 | * https://leetcode.cn/problems/2AoeFn/ 5 | * 6 | * 解法:动态规划 7 | * 8 | * 时间O(mn) 9 | * 空间O(mn) 10 | */ 11 | /** 12 | * @param {number} m 13 | * @param {number} n 14 | * @return {number} 15 | */ 16 | var uniquePaths = function (m, n) { 17 | // f(i, j) 表示从左上角走到(i,j) 的路径数量,其中 i 和 j 的范围分别是 [0,m) 和 [0,n) 18 | 19 | // 每一步只能从向下 或 向右移动一步,如果向下走一步,那么会从 (i-1, j) 走过来;如果向右走一步,那么会从 (i, j-1) 走过来,所以状态转移方程 20 | // f(i,j) = f(i−1,j) + f(i,j−1) 21 | 22 | // 如果 i=0,那么 f(i-1,j) 并不是一个满足要求的状态,需要忽略这一项;同理,如果 j=0,那么 f(i,j-1)并不是一个满足要求的状态,需要忽略这一项 23 | 24 | // 初始条件为 f(0,0)=1,即从左上角走到左上角有一种方法 25 | 26 | // 最终的答案即为 f(m-1,n-1) 27 | 28 | const f = new Array(m).fill(0).map(() => new Array(n).fill(0)); 29 | for (let i = 0; i < m; i++) { 30 | f[i][0] = 1; 31 | } 32 | for (let j = 0; j < n; j++) { 33 | f[0][j] = 1; 34 | } 35 | for (let i = 1; i < m; i++) { 36 | for (let j = 1; j < n; j++) { 37 | f[i][j] = f[i - 1][j] + f[i][j - 1]; 38 | } 39 | } 40 | return f[m - 1][n - 1]; 41 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/动态规划/股票问题/maxProfit_309.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 309. 最佳买卖股票时机含冷冻期 3 | * 4 | * 中等 5 | * https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-with-cooldown/ 6 | */ 7 | /** 8 | * 动态规划解法 9 | * 10 | * 每次 sell 之后要等一天才能继续交易(跟122的动态规划解法类似) 11 | * 12 | * 状态转移方程 13 | * dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i]) 14 | * dp[i][1] = max(dp[i-1][1], dp[i-2][0] - prices[i]) 15 | * 第 i 天选择买入的时候,要从 i-2 的状态转移,而不是 i-1 16 | * 17 | * @param {number[]} prices 18 | * @return {number} 19 | */ 20 | var maxProfit = function (prices) { 21 | let n = prices.length; 22 | let dp_i_0 = 0, dp_i_1 = Number.MIN_SAFE_INTEGER; 23 | let dp_pre_0 = 0; // 代表 dp[i-2][0] 24 | 25 | for (let i = 0; i < n; i++) { 26 | let temp = dp_i_0; 27 | dp_i_0 = Math.max(dp_i_0, dp_i_1 + prices[i]); 28 | dp_i_1 = Math.max(dp_i_1, dp_pre_0 - prices[i]); 29 | dp_pre_0 = temp; 30 | } 31 | 32 | return dp_i_0; 33 | }; 34 | 35 | const prices = [1, 2, 3, 0, 2]; 36 | console.log(maxProfit(prices)); // 3 37 | -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/动态规划/股票问题/maxProfit_714.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 714 买卖股票的最佳时机含手续费 3 | * 4 | * 中等 5 | * https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-with-transaction-fee/ 6 | */ 7 | /** 8 | * 9 | * 动态规划解法: 10 | * 跟122、309的解法类似 11 | * 12 | * 每次交易要支付手续费,只要把手续费从利润中减去即可 13 | * 14 | * dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i]) 15 | * dp[i][1] = max(dp[i-1][1], dp[i-1][0] - prices[i] - fee) 16 | * 解释:相当于买入股票的价格升高了;在第一个式子里减fee也是一样的,相当于卖出股票的价格减小了 17 | * 18 | * @param {number[]} prices 19 | * @param {number} fee 20 | * @return {number} 21 | */ 22 | var maxProfit = function(prices, fee) { 23 | let n = prices.length; 24 | let dp_i_0 = 0, dp_i_1 = Number.MIN_SAFE_INTEGER; 25 | 26 | for (let i = 0; i < n; i++) { 27 | let temp = dp_i_0; 28 | dp_i_0 = Math.max(dp_i_0, dp_i_1 + prices[i]); 29 | dp_i_1 = Math.max(dp_i_1, temp - prices[i] - fee); 30 | } 31 | return dp_i_0; 32 | }; 33 | const prices = [1, 3, 2, 8, 4, 9], fee = 2; 34 | console.log(maxProfit(prices, fee)); // 8 35 | -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/双指针/deleteDuplicates_83.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 83 删除排序链表中的重复元素 3 | * 简单 4 | * https://leetcode-cn.com/problems/remove-duplicates-from-sorted-list/ 5 | * 6 | * 解法类似26题 7 | */ 8 | 9 | function ListNode(val) { 10 | this.val = val; 11 | this.next = null; 12 | } 13 | 14 | /** 15 | * @param {ListNode} head 16 | * @return {ListNode} 17 | */ 18 | var deleteDuplicates = function (head) { 19 | if (head == null) { 20 | return null; 21 | } 22 | 23 | let slow = head; 24 | let fast = head.next; 25 | while (fast != null) { 26 | if (fast.val != slow.val) { 27 | slow.next = fast; 28 | slow = slow.next; 29 | fast = fast.next; 30 | } else { 31 | fast = fast.next; 32 | } 33 | } 34 | 35 | // 断开与后面重复元素的连接 36 | slow.next = null; 37 | return head; 38 | }; 39 | 40 | const head = new ListNode(1); 41 | const node1 = new ListNode(1); 42 | const node2 = new ListNode(2); 43 | head.next = node1; 44 | node1.next = node2; 45 | console.log(deleteDuplicates(head)); 46 | -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/双指针/exchange_剑指_21.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 剑指 Offer 21. 调整数组顺序使奇数位于偶数前面 3 | * 简单 4 | * https://leetcode.cn/problems/diao-zheng-shu-zu-shun-xu-shi-qi-shu-wei-yu-ou-shu-qian-mian-lcof/ 5 | * 6 | * 解法:双指针 7 | */ 8 | /** 9 | * @param {number[]} nums 10 | * @return {number[]} 11 | */ 12 | var exchange = function (nums) { 13 | // 双指针,0到left指针的值都是奇数,left + 1到right之间的值都是偶数 14 | let left = 0; 15 | let right = 0; 16 | 17 | for (let i = 0; i < nums.length; i++) { 18 | // 偶数,则右指针移动 19 | if (nums[right] % 2 == 0) { 20 | right++; 21 | } else { 22 | // right是奇数,则把它跟left的值交换,然后两个指针同时向右移动 23 | let temp = nums[left]; 24 | nums[left] = nums[right]; 25 | nums[right] = temp; 26 | left++; 27 | right++; 28 | } 29 | } 30 | 31 | return nums; 32 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/双指针/findContinuousSequence_剑指_57.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 剑指 Offer 57 - II. 和为s的连续正数序列 3 | * 简单 4 | * https://leetcode.cn/problems/he-wei-sde-lian-xu-zheng-shu-xu-lie-lcof/ 5 | * 6 | * 解法:双指针 7 | */ 8 | /** 9 | * @param {number} target 10 | * @return {number[][]} 11 | */ 12 | var findContinuousSequence = function (target) { 13 | let i = 1; // 左指针 14 | let j = 2; // 右指针 15 | 16 | let sum = 3; // 元素和 17 | 18 | let res = []; 19 | 20 | while (i < j) { 21 | if (sum == target) { // 窗口内的元素和 等于目标值,则记录序列,并左指针向右移 22 | let ans = []; 23 | for (let k = i; k <= j; k++) { 24 | ans[k - i] = k; 25 | } 26 | res.push(ans); 27 | 28 | // 左指针右移 29 | sum -= i; 30 | i++; 31 | } else if (sum > target) { // 大于目标值,则左指针向右移动,缩小窗口 32 | sum -= i; 33 | i++; 34 | } else { // 小于目标值,则右指针向右移动,扩大窗口 35 | j++; 36 | sum += j; 37 | } 38 | } 39 | 40 | return res; 41 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/双指针/findDuplicate_287.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 287. 寻找重复数 3 | * 中等 4 | * https://leetcode.cn/problems/find-the-duplicate-number/ 5 | * 6 | * 解法:快慢指针 7 | * 参考 https://leetcode.cn/problems/find-the-duplicate-number/solution/287xun-zhao-zhong-fu-shu-by-kirsche/ 8 | * 9 | * 10 | * 时间O(n) 11 | * 空间O(1) 12 | */ 13 | /** 14 | * @param {number[]} nums 15 | * @return {number} 16 | */ 17 | var findDuplicate = function (nums) { 18 | let slow = 0; 19 | let fast = 0; 20 | 21 | // 快慢指针只到相遇 22 | do { 23 | slow = nums[slow]; 24 | fast = nums[nums[fast]]; 25 | } while (slow != fast) 26 | 27 | slow = 0; // 一个指针从头开始,一个不变 28 | while (slow != fast) { 29 | slow = nums[slow]; 30 | fast = nums[fast]; 31 | } 32 | 33 | return slow; 34 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/双指针/findUnsortedSubarray_581.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 581. 最短无序连续子数组 3 | * 中等 4 | * https://leetcode.cn/problems/shortest-unsorted-continuous-subarray/ 5 | * 6 | * 解法:双指针 7 | * 8 | * 时间O(nlogn) 9 | * 空间O(n) 10 | * 11 | */ 12 | /** 13 | * @param {number[]} nums 14 | * @return {number} 15 | */ 16 | var findUnsortedSubarray = function (nums) { 17 | // 把数组分成3部分子数组,nums1,nums2,nums3,要求nums1和nums3的长度之和最长,此时nums2就是最短子数组 18 | // 先把nums从小到大排序,取最长的相同的前缀为nums2,取最长的相同的后缀为nums3,这样我们就可以取到最短的nums2 19 | 20 | // 判断是否有序,从小到大 21 | const isSorted = (nums) => { 22 | for (let i = 0; i < nums.length - 1; i++) { 23 | if (nums[i] > nums[i + 1]) { 24 | return false; 25 | } 26 | } 27 | 28 | return true; 29 | } 30 | 31 | if (isSorted(nums)) { 32 | return 0; 33 | } 34 | 35 | const newNums = nums.slice(0).sort((a, b) => parseInt(a) - parseInt(b)); 36 | 37 | let left = 0; 38 | while (nums[left] == newNums[left]) { 39 | left++; 40 | } 41 | 42 | let right = newNums.length - 1; 43 | while (nums[right] == newNums[right]) { 44 | right--; 45 | } 46 | 47 | return right - left + 1; 48 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/双指针/isPalindrome_剑指II_18.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 剑指 Offer II 018. 有效的回文 3 | * 简单 4 | * https://leetcode.cn/problems/XltzEq/ 5 | * 6 | * 解法:双指针 7 | * 8 | * 时间O(n) 9 | * 空间O(1) 10 | */ 11 | /** 12 | * @param {string} s 13 | * @return {boolean} 14 | */ 15 | var isPalindrome = function (s) { 16 | // 去掉非数字或字母的字符 17 | s = s.replace(/[^0-9a-zA-Z]/g, '').toLocaleLowerCase(); 18 | 19 | let left = 0; 20 | let right = s.length - 1; 21 | 22 | while (left < right) { 23 | if (s[left] != s[right]) { 24 | return false; 25 | } 26 | left++; 27 | right--; 28 | } 29 | 30 | return true; 31 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/双指针/isSubsequence_392.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 392. 判断子序列 3 | * 4 | * 简单 5 | * https://leetcode-cn.com/problems/is-subsequence/ 6 | * 7 | * 双指针 8 | * 9 | * 时间复杂度O(n) 10 | * 空间复杂度O(1) 11 | * 12 | * @param {string} s 13 | * @param {string} t 14 | * @return {boolean} 15 | */ 16 | var isSubsequence = function (s, t) { 17 | let i = 0, j = 0; 18 | 19 | // i指针指向s,j指针指向t 20 | while (i < s.length && j < t.length) { 21 | if (s[i] == t[j]) { 22 | i++; 23 | } 24 | j++; 25 | } 26 | 27 | return i == s.length; 28 | }; 29 | 30 | const s = 'abc', t = 'ahbgdc'; 31 | console.log(isSubsequence(s, t)); // true 32 | -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/双指针/merge_88.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 88. 合并两个有序数组 3 | * https://leetcode-cn.com/problems/merge-sorted-array/ 4 | * 5 | * 双指针,从后往前 6 | * 7 | * 其他题意相关题目 8 | * 23. 合并K个升序链表mergeKLists https://leetcode-cn.com/problems/merge-k-sorted-lists/ 9 | * 21. 合并两个有序链表 mergeTwoLists 10 | * 11 | * 时间复杂度 : O(n + m) 12 | * 空间复杂度 : O(1) 13 | * 14 | * @param {number[]} nums1 15 | * @param {number} m 16 | * @param {number[]} nums2 17 | * @param {number} n 18 | * @return {void} Do not return anything, modify nums1 in-place instead. 19 | */ 20 | var merge = function (nums1, m, nums2, n) { 21 | let p1 = m - 1; // 指向nums1的尾部 22 | let p2 = n - 1; // 指向nums2的尾部 23 | let p = m + n - 1; 24 | 25 | while ((p1 >= 0) && (p2 >= 0)) { 26 | nums1[p--] = nums1[p1] > nums2[p2] 27 | ? nums1[p1--] 28 | : nums2[p2--]; 29 | } 30 | 31 | // nums2还有剩余,则需要把它替换掉nums1前面的元素 32 | if (p2 >= 0) { 33 | nums1.splice(0, p2 + 1, ...nums2.slice(0, p2 + 1)); 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/双指针/moveZeroes_283.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 283. 移动零 3 | * 简单 4 | * https://leetcode.cn/problems/move-zeroes/ 5 | * 6 | * 解法:双指针 7 | * 8 | * 时间O(n) 9 | * 空间O(1) 10 | */ 11 | /** 12 | * @param {number[]} nums 13 | * @return {void} Do not return anything, modify nums in-place instead. 14 | */ 15 | var moveZeroes = function (nums) { 16 | // 使用双指针,左指针指向当前已经处理好的序列的尾部,右指针指向待处理序列的头部 17 | // 右指针不断向右移动,每次右指针指向非零数,则将左右指针对应的数交换,同时左指针右移 18 | 19 | // 注意到以下性质: 20 | // 1. 左指针左边均为非零数 21 | // 2. 右指针左边直到左指针处均为零 22 | 23 | let left = 0 24 | let right = 0; 25 | let n = nums.length; 26 | 27 | while (right < n) { 28 | if (nums[right] != 0) { 29 | [nums[left], nums[right]] = [nums[right], nums[left]]; 30 | left++; 31 | right++; 32 | } else { 33 | right++; 34 | } 35 | } 36 | 37 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/双指针/nextPermutation_31.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 31. 下一个排列 3 | * 中等 4 | * https://leetcode.cn/problems/next-permutation/ 5 | * 6 | * 解法:双指针 7 | * 8 | * 时间O(n) 9 | * 空间O(1) 10 | */ 11 | /** 12 | * 13 | * @param {number[]} nums 14 | * @return {void} Do not return anything, modify nums in-place instead. 15 | */ 16 | var nextPermutation = function (nums) { 17 | let i = nums.length - 2; 18 | // 找出i 19 | while (i >= 0 && nums[i] >= nums[i + 1]) { 20 | i--; 21 | } 22 | 23 | // i<0则说明nums是递减排序,即为最大,直接翻转为最小排列即可 24 | if (i >= 0) { 25 | // 找出j 26 | let j = nums.length - 1; 27 | while (j >= 0 && nums[j] <= nums[i]) { 28 | j--; 29 | } 30 | 31 | // 交换i和j的数字 32 | [nums[i], nums[j]] = [nums[j], nums[i]]; 33 | } 34 | 35 | // 交换i+1后的数 36 | reverse(nums, i + 1); 37 | }; 38 | 39 | // 双指针反转序列 40 | function reverse(nums, start) { 41 | let left = start; 42 | let right = nums.length - 1; 43 | while (left < right) { 44 | [nums[left], nums[right]] = [nums[right], nums[left]]; 45 | left++; 46 | right--; 47 | } 48 | } -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/双指针/removeDuplicates_26.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 26 删除排序数组中的重复项 3 | * 4 | * 是一个有序数组 5 | * 6 | * 简单 7 | * https://leetcode-cn.com/problems/remove-duplicates-from-sorted-array/ 8 | * 9 | * 难点:要求空间复杂度O(1) 10 | * 11 | * 双指针解法:让慢指针 slow 在后面,快指针 fast 在前面 12 | * 13 | * 思路: 14 | * 快指针找到一个不重复的元素,slow 前进一步,并把fast索引的元素值赋给slow索引的元素值 15 | * 当 fast 指针遍历完nums后,nums[0..slow] 就是不重复元素 16 | * 17 | * 时间复杂度O(n) 18 | * 空间复杂度O(1) 19 | * 20 | * @param {*} nums 21 | */ 22 | function removeDuplicates(nums) { 23 | let n = nums.length; 24 | if (n <= 1) { 25 | return nums; 26 | } 27 | 28 | let slow = 0; 29 | let fast = 1; 30 | 31 | while (fast < n) { 32 | if (nums[fast] !== nums[slow]) { 33 | slow++; 34 | nums[slow] = nums[fast]; 35 | fast++; 36 | } else { 37 | fast++; 38 | } 39 | } 40 | 41 | return slow + 1; 42 | } 43 | const nums = [1, 1, 2]; 44 | console.log(removeDuplicates(nums)); // 2, 即[1,2] 45 | -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/双指针/removeDuplicates_80.js: -------------------------------------------------------------------------------- 1 | /** 2 | * leetcode 80 删除排序数组中的重复项 II 3 | * https://leetcode-cn.com/problems/remove-duplicates-from-sorted-array-ii/ 4 | * 5 | * 使得每个元素最多出现两次 6 | * 7 | * 时间复杂度O(n) 8 | * 空间复杂度O(1) 9 | * 10 | * @param {number[]} nums 11 | * @return {number} 12 | */ 13 | let removeDuplicates = function (nums) { 14 | let j = 1; // 已经更新后数组的指针 15 | 16 | const n = nums.length; 17 | if (n <= 1) { 18 | return n; 19 | } 20 | 21 | // i为遍历指针 22 | for (let i = 2; i < n; i++) { 23 | // nums[i] !== nums[j]后一个数跟前一个数不相等,即nums[i]是新的数字 24 | // nums[i] !== nums[j - 1] 这是允许有一个重复数字,即允许nums[i]等于nums[j-1];添加nums[i] 25 | if (nums[i] !== nums[j] || (nums[i] === nums[j] && nums[i] !== nums[j - 1])) { 26 | // j指针前进,把不相等的值放到j中 27 | j++; 28 | nums[j] = nums[i]; 29 | } 30 | // 相等则不做操作,继续循环移动i遍历指针 31 | } 32 | // j是从1开始计数,需要加1 33 | return j + 1; 34 | } 35 | console.log(removeDuplicates([1, 1, 1, 2, 2, 3])); 36 | -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/双指针/removeElement_27.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 27. 移除元素 3 | * 简单 4 | * https://leetcode.cn/problems/remove-element/ 5 | * 6 | * 解法:双指针 7 | * 8 | * 时间O(n) 9 | * 空间O(1) 10 | */ 11 | /** 12 | * @param {number[]} nums 13 | * @param {number} val 14 | * @return {number} 15 | */ 16 | var removeElement = function (nums, val) { 17 | let left = -1; 18 | let right = 0; 19 | 20 | // 双指针 左指针存不为目标值的数据 21 | for (let i = 0; i < nums.length; i++) { 22 | // 右指针等于目标值,则右指针跳过继续向右移动 23 | // 右指针不等于目标值,则先移动左指针,把该值存到左指针位置 24 | if (nums[right] != val) { 25 | left++; 26 | nums[left] = nums[right]; 27 | right++; 28 | } else { 29 | right++; 30 | } 31 | } 32 | 33 | return left + 1; 34 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/双指针/reverseString_344.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 344. 反转字符串 3 | * 简单 4 | * https://leetcode.cn/problems/reverse-string/ 5 | * 6 | * 解法:双指针 7 | */ 8 | /** 9 | * @param {character[]} s 10 | * @return {void} Do not return anything, modify s in-place instead. 11 | */ 12 | var reverseString = function (s) { 13 | let n = s.length; 14 | let left = 0; 15 | let right = n - 1; 16 | 17 | while (left < right) { 18 | [s[left], s[right]] = [s[right], s[left]]; 19 | left++; 20 | right--; 21 | } 22 | 23 | return s; 24 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/双指针/sortColors_75.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 75. 颜色分类 3 | * 中等 4 | * https://leetcode.cn/problems/sort-colors/ 5 | * 6 | * 解法:双指针 7 | * 时间O(n) 8 | * 空间O(1) 9 | */ 10 | /** 11 | * @param {number[]} nums 12 | * @return {void} Do not return anything, modify nums in-place instead. 13 | */ 14 | var sortColors = function (nums) { 15 | // 双指针,一次遍历 16 | let p0 = 0; 17 | let p1 = 0; 18 | let n = nums.length; 19 | 20 | for (let i = 0; i < n; i++) { 21 | if (nums[i] === 1) { 22 | // 找到1,直接交换 23 | [nums[i], nums[p1]] = [nums[p1], nums[i]]; 24 | p1++; 25 | } else if (nums[i] === 0) { 26 | // 找到0,直接交换,可能会把已经排好序的1交换到nums[i]位置 27 | [nums[i], nums[p0]] = [nums[p0], nums[i]]; 28 | 29 | // 此时会把p0后面已经排好序的1交换到nums[i]位置 30 | if (p0 < p1) { // 把被交换到后面的1重新交换到p1后面 31 | [nums[i], nums[p1]] = [nums[p1], nums[i]] 32 | } 33 | 34 | p0++; 35 | p1++; // 因为p0一定是 小于等于 p1,所以当p0向右移动了,那p1一定要向右移动。(因为1是排在0后面,所以不存在p1 < p0的情况) 36 | } 37 | 38 | } 39 | 40 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/双指针/twoSum_剑指_57.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 剑指 Offer 57. 和为s的两个数字 3 | * 简单 4 | * https://leetcode.cn/problems/he-wei-sde-liang-ge-shu-zi-lcof/ 5 | * 6 | * 解法:双指针 7 | * 8 | * 时间O(n) 9 | * 空间O(1) 10 | * 11 | */ 12 | /** 13 | * @param {number[]} nums 14 | * @param {number} target 15 | * @return {number[]} 16 | */ 17 | var twoSum = function (nums, target) { 18 | let left = 0; 19 | let right = nums.length - 1; 20 | 21 | while (left < right) { 22 | const sum = nums[left] + nums[right]; 23 | if (sum > target) { 24 | right--; 25 | } else if (sum < target) { 26 | left++; 27 | } else { 28 | return [nums[left], nums[right]]; 29 | } 30 | } 31 | 32 | return []; 33 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/双指针/validPalindrome_剑指II_19.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 剑指 Offer II 019. 最多删除一个字符得到回文 3 | * 简单 4 | * https://leetcode.cn/problems/RQku0D/ 5 | * 6 | * 解法:双指针 7 | */ 8 | /** 9 | * @param {string} s 10 | * @return {boolean} 11 | */ 12 | var validPalindrome = function (s) { 13 | // 双指针 时间O(n) 空间O(1) 14 | if (s.length == 1 || s.length == 2) { 15 | return true; 16 | } 17 | 18 | let left = 0; 19 | let right = s.length - 1; 20 | 21 | while (left <= right) { 22 | if (s[left] == s[right]) { 23 | left++; 24 | right--; 25 | } else { 26 | // 选择删掉left指向的字符 或 删掉right指向的字符 27 | return isPalindrome(s, left + 1, right) || isPalindrome(s, left, right - 1); 28 | } 29 | } 30 | 31 | return true; 32 | }; 33 | 34 | // 判断s字符串[left, right]区间是否是回文 35 | const isPalindrome = (s, left, right) => { 36 | while (left <= right) { 37 | if (s[left] == s[right]) { 38 | left++; 39 | right--; 40 | } else { 41 | return false; 42 | } 43 | } 44 | 45 | return true; 46 | } -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/双指针/滑动窗口/maxArea_11.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 11. 盛最多水的容器 3 | * 中等 4 | * https://leetcode.cn/problems/container-with-most-water/ 5 | * 6 | * 解法:双指针 分别指向首尾 7 | * S = (j - i) * Min(height[i], heigth[j]), 8 | * 9 | * 10 | * 时间 O(n) 11 | * 空间 O(1) 12 | */ 13 | /** 14 | * 15 | * @param {number[]} height 16 | * @return {number} 17 | */ 18 | var maxArea = function (height) { 19 | let i = 0; 20 | let j = height.length - 1; 21 | let res = 0; 22 | 23 | // 双指针相遇时停下 24 | while (i < j) { 25 | if (height[i] < height[j]) { 26 | res = Math.max(res, (j - i) * height[i]); 27 | i++; 28 | } else { 29 | res = Math.max(res, (j - i) * height[j]); 30 | j--; 31 | } 32 | } 33 | 34 | return res; 35 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/双指针/滑动窗口/minSubArrayLen_剑指II_8.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 剑指 Offer II 008. 和大于等于 target 的最短子数组 3 | * 中等 4 | * https://leetcode.cn/problems/2VG8Kg/ 5 | * 6 | * 解法:滑动窗口 7 | */ 8 | /** 9 | * @param {number} target 10 | * @param {number[]} nums 11 | * @return {number} 12 | */ 13 | var minSubArrayLen = function (target, nums) { 14 | // 滑动窗口 时间O(n) 空间O(1) 15 | if (nums.length == 0) { 16 | return 0; 17 | } 18 | 19 | let left = 0; 20 | let right = 0; 21 | 22 | let result = Number.MAX_VALUE; 23 | 24 | let sum = 0; // 窗口内的值 25 | 26 | while (right < nums.length) { 27 | sum += nums[right]; 28 | 29 | // 扩大窗口后,值大于目标值,则需要计算长度,并一直缩小窗口 30 | while (sum >= target) { 31 | result = Math.min(result, right - left + 1); 32 | 33 | // 缩小窗口 34 | sum -= nums[left]; 35 | left++; 36 | } 37 | 38 | right++; // 扩大窗口 39 | } 40 | 41 | return result == Number.MAX_VALUE ? 0 : result; 42 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/双指针/滑动窗口/numSubarrayProductLessThanK_剑指II_9.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 剑指 Offer II 009. 乘积小于 K 的子数组 3 | * 中等 4 | * https://leetcode.cn/problems/ZVAVXX/ 5 | * 6 | * 解法:滑动窗口 7 | */ 8 | /** 9 | * @param {number[]} nums 10 | * @param {number} k 11 | * @return {number} 12 | */ 13 | var numSubarrayProductLessThanK = function (nums, k) { 14 | let n = nums.length; 15 | if (n == 0) { 16 | return 0; 17 | } 18 | 19 | let result = 0; 20 | let prod = 1; // 乘积 21 | 22 | let left = 0; 23 | let right = 0; 24 | while (right < n) { 25 | prod *= nums[right]; 26 | 27 | while (left <= right && prod >= k) { 28 | prod /= nums[left]; // 缩小窗口 29 | left++; 30 | } 31 | 32 | // 乘积小于k,则更新结果,每个元素都可以单独作为子数组 33 | result += right - left + 1; 34 | 35 | right++; 36 | } 37 | 38 | return result; 39 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/哈希表/containsDuplicate_217.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 217. 存在重复元素 3 | * 简单 4 | * https://leetcode.cn/problems/contains-duplicate/ 5 | */ 6 | /** 7 | * @param {number[]} nums 8 | * @return {boolean} 9 | */ 10 | var containsDuplicate = function (nums) { 11 | const set = new Set(nums); 12 | if (set.size !== nums.length) { 13 | return true; 14 | } 15 | 16 | return false; 17 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/哈希表/findRepeatNumber_剑指_3.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 剑指 Offer 03. 数组中重复的数字 3 | * 简单 4 | * https://leetcode.cn/problems/shu-zu-zhong-zhong-fu-de-shu-zi-lcof/ 5 | * 6 | * 解法:哈希表 7 | */ 8 | /** 9 | * @param {number[]} nums 10 | * @return {number} 11 | */ 12 | var findRepeatNumber = function (nums) { 13 | // 时间和空间都是O(n) 14 | let set = new Set(); 15 | 16 | for (let i = 0; i < nums.length; i++) { 17 | if (set.has(nums[i])) { 18 | return nums[i]; 19 | } 20 | set.add(nums[i]); 21 | } 22 | 23 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/哈希表/firstMissingPositive_41.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 41. 缺失的第一个正数 3 | * 困难 4 | * https://leetcode.cn/problems/first-missing-positive/ 5 | * 6 | * 解法:哈希表 7 | * 8 | * 时间O(n) 9 | * 空间O(1) 10 | */ 11 | /** 12 | * @param {number[]} nums 13 | * @return {number} 14 | */ 15 | var firstMissingPositive = function (nums) { 16 | // 最小正整数在[1, N+1]中 17 | // 如果 [1, N]都出现了,那么答案是 N+1,否则是[1,N] 中没有出现的最小正整数 18 | let n = nums.length; 19 | 20 | // 把小于等于0的值变成n+1 21 | for (let i = 0; i < n; i++) { 22 | if (nums[i] <= 0) { 23 | nums[i] = n + 1; 24 | } 25 | } 26 | 27 | // 打上负号的标记 28 | for (let i = 0; i < n; i++) { 29 | let num = Math.abs(nums[i]); // 因为nums可能有重复的数字,即nums[i]可能已经是负数了,需要取绝对值 30 | if (num <= n) { 31 | nums[num - 1] = -Math.abs(nums[num - 1]); // 把num-1的位置变成负数,标示这个数已经出现在原数组中了 32 | } 33 | } 34 | 35 | 36 | // 遍历,找出没有被标记的 37 | for (let i = 0; i < n; i++) { 38 | if (nums[i] > 0) { // 说明不存在 i+1 这个数,所以nums[i]没有变成负数 39 | return i + 1; 40 | } 41 | } 42 | 43 | return n + 1; 44 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/哈希表/firstUniqChar_387.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} s 3 | * @return {number} 4 | */ 5 | /** 6 | * 387. 字符串中的第一个唯一字符 7 | * 简单 8 | * https://leetcode.cn/problems/first-unique-character-in-a-string/ 9 | * 10 | * 解法:哈希表 11 | */ 12 | /** 13 | * 14 | * @param {*} s 15 | * @returns 16 | */ 17 | var firstUniqChar = function (s) { 18 | let n = s.length; 19 | if (n == 0) { 20 | return -1; 21 | } 22 | 23 | let map = new Map(); 24 | for (let item of s) { 25 | map.set(item, map.get(item) ? map.get(item) + 1 : 1); 26 | } 27 | 28 | for (let i = 0; i < n; i++) { 29 | if (map.get(s[i]) == 1) { 30 | return i; 31 | } 32 | } 33 | 34 | return -1; 35 | 36 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/哈希表/firstUniqChar_剑指_50.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 剑指 Offer 50. 第一个只出现一次的字符 3 | * 简单 4 | * https://leetcode.cn/problems/di-yi-ge-zhi-chu-xian-yi-ci-de-zi-fu-lcof/ 5 | * 6 | * 解法:哈希表 7 | */ 8 | /** 9 | * @param {string} s 10 | * @return {character} 11 | */ 12 | var firstUniqChar = function (s) { 13 | if (!s) { 14 | return ' '; 15 | } 16 | const map = new Map(); 17 | for (let c of s) { 18 | map.set(c, map.get(c) ? map.get(c) + 1 : 1); 19 | } 20 | 21 | for (let c of s) { 22 | if (map.get(c) === 1) { 23 | return c; 24 | } 25 | } 26 | 27 | return ' '; 28 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/哈希表/isHappy_202.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 202. 快乐数 3 | * 简单 4 | * https://leetcode.cn/problems/happy-number/ 5 | * 6 | * 解法:哈希表 7 | */ 8 | /** 9 | * @param {number} n 10 | * @return {boolean} 11 | */ 12 | var isHappy = function (n) { 13 | // 最后有3种可能: 14 | // 1、最终会得到1 15 | // 2、最终会进入循环 16 | // 3、值会越来越大,最后接近无穷大(此情况永远不会发生,会转化成第1种或第2种情况) 17 | 18 | // 哈希表检测循环 19 | const seen = new Set(); 20 | while (n != 1 && !seen.has(n)) { // 哈希表不存在该数,即还没进入循环,则继续分离 21 | seen.add(n); 22 | n = getNext(n); // 得到下一个数字 23 | } 24 | 25 | // 此时说明进入了循环 或者 是1,可以判断是否是快乐数 26 | return n == 1; 27 | }; 28 | 29 | // 对n做数位分离,求平方和 30 | const getNext = (n) => { 31 | let totalNum = 0; 32 | while (n > 0) { 33 | let d = n % 10; 34 | n = Math.floor(n / 10); 35 | totalNum += d * d; 36 | } 37 | return totalNum; 38 | } -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/哈希表/longestConsecutive_128.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 128. 最长连续序列 3 | * 中等 4 | * https://leetcode.cn/problems/longest-consecutive-sequence/ 5 | * 6 | * 解法:哈希表 7 | * 8 | * 时间O(n) 9 | * 空间O(n) 10 | */ 11 | /** 12 | * @param {number[]} nums 13 | * @return {number} 14 | */ 15 | var longestConsecutive = function (nums) { 16 | // 遍历每个数,以当前数num为序列的起点,然后依次在数组找是否存在num+1, num+2, num+x ,直到num+x不存在停止,此时计算长度,比较下是否是最长 17 | let set = new Set(nums); 18 | 19 | let maxLength = 0; 20 | 21 | for (let num of nums) { 22 | // 因为是以当前num为序列的起点,所以x一定是在数组中不存在前驱数x−1 的,不然就是以x-1为起点了 23 | if (set.has(num - 1)) { 24 | continue; 25 | } 26 | 27 | let count = 1; // 当前num算一个长度 28 | while (set.has(num + 1)) { 29 | num++; 30 | count++; 31 | } 32 | 33 | maxLength = Math.max(maxLength, count); 34 | } 35 | 36 | return maxLength; 37 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/哈希表/longestConsecutive_剑指II_119.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 剑指 Offer II 119. 最长连续序列 3 | * 中等 4 | * https://leetcode.cn/problems/WhsWhI/ 5 | * 6 | * 解法:哈希表 7 | * 8 | * 时间O(n) 9 | * 空间O(n) 10 | */ 11 | /** 12 | * @param {number[]} nums 13 | * @return {number} 14 | */ 15 | var longestConsecutive = function (nums) { 16 | let num_set = new Set(); 17 | for (const num of nums) { 18 | num_set.add(num); 19 | } 20 | 21 | let longestStreak = 0; 22 | 23 | for (const num of num_set) { 24 | if (!num_set.has(num - 1)) { 25 | let currentNum = num; 26 | let currentStreak = 1; 27 | 28 | while (num_set.has(currentNum + 1)) { 29 | currentNum += 1; 30 | currentStreak += 1; 31 | } 32 | 33 | longestStreak = Math.max(longestStreak, currentStreak); 34 | } 35 | } 36 | 37 | return longestStreak; 38 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/哈希表/majorityElement_剑指_39.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 剑指 Offer 39. 数组中出现次数超过一半的数字 3 | * 简单 4 | * https://leetcode.cn/problems/shu-zu-zhong-chu-xian-ci-shu-chao-guo-yi-ban-de-shu-zi-lcof/ 5 | * 6 | * 解法:哈希表 7 | */ 8 | /** 9 | * @param {number[]} nums 10 | * @return {number} 11 | */ 12 | var majorityElement = function (nums) { 13 | const map = new Map(); 14 | 15 | for (let num of nums) { 16 | if (map.has(num)) { 17 | map.set(num, map.get(num) + 1); 18 | } else { 19 | map.set(num, 1); 20 | } 21 | } 22 | 23 | let maxNum; 24 | let maxCount; 25 | 26 | for (let key of map.keys()) { 27 | const count = map.get(key); 28 | if (!maxCount || count > maxCount) { 29 | maxNum = key; 30 | maxCount = map.get(key); 31 | } 32 | } 33 | 34 | return maxNum; 35 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/哈希表/romanToInt_13.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 13. 罗马数字转整数 3 | * 简单 4 | * https://leetcode.cn/problems/roman-to-integer/ 5 | * 6 | * 解法:哈希表 7 | */ 8 | /** 9 | * @param {string} s 10 | * @return {number} 11 | */ 12 | var romanToInt = function (s) { 13 | // 时间O(n) 空间O(1) 14 | const obj = { 15 | 'I': 1, 16 | 'V': 5, 17 | 'X': 10, 18 | 'L': 50, 19 | 'C': 100, 20 | 'D': 500, 21 | 'M': 1000 22 | }; 23 | 24 | let result = 0; 25 | // 一般是左边的大于右边的。当左边的小于右边的,需要特殊处理,直接取反即可 26 | for (let i = 0; i < s.length; i++) { 27 | const curValue = obj[s[i]]; 28 | if (i < s.length - 1 && curValue < obj[s[i + 1]]) { 29 | result -= curValue; 30 | } else { 31 | result += curValue; 32 | } 33 | } 34 | 35 | return result; 36 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/图-拓扑排序/findOrder_剑指II_113.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 剑指 Offer II 113. 课程顺序 3 | * 中等 4 | * https://leetcode.cn/problems/QA2IGt/ 5 | * 6 | * 解法:拓扑排序 7 | * 8 | * 时间O(n+m) 9 | * 空间O(n+m) 10 | */ 11 | /** 12 | * @param {number} numCourses 13 | * @param {number[][]} prerequisites 14 | * @return {number[]} 15 | */ 16 | var findOrder = function (numCourses, prerequisites) { 17 | const map = {}; 18 | const inDegrees = new Array(numCourses).fill(0); // 入度数组 19 | 20 | for (const [a, b] of prerequisites) { 21 | if (map[b]) { 22 | map[b].push(a); 23 | } else { 24 | map[b] = [a]; 25 | } 26 | inDegrees[a]++; 27 | } 28 | 29 | const queue = []; // 入度为0的数 30 | for (let i = 0; i < numCourses; i++) { 31 | if (inDegrees[i] === 0) { 32 | queue.push(i); 33 | } 34 | } 35 | 36 | const ans = []; 37 | while (queue.length) { 38 | let cur = queue.shift(); 39 | ans.push(cur); 40 | 41 | for (let next of (map[cur] || [])) { 42 | inDegrees[next]--; 43 | if (inDegrees[next] === 0) { 44 | queue.push(next); 45 | } 46 | } 47 | } 48 | 49 | return ans.length === numCourses ? ans : []; 50 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/堆/KthLargest_703.js: -------------------------------------------------------------------------------- 1 | /** 2 | * leetcode 703 数据流中的第K大元素 3 | * 简单 4 | * https://leetcode-cn.com/problems/kth-largest-element-in-a-stream/ 5 | * 6 | * 解法1:维护一个倒序的数组,然后取第k个即可 7 | * 解法2:用小顶堆(优先队列)实现,java有内置api 8 | * 9 | */ 10 | 11 | /** 12 | * 解法1 13 | * 14 | * @param {number} k 15 | * @param {number[]} nums 16 | */ 17 | var KthLargest = function(k, nums) { 18 | // 对nums倒序排序 19 | this.minHeap = nums.sort((a, b) => b - a); 20 | this.k = k; 21 | }; 22 | 23 | /** 24 | * @param {number} val 25 | * @return {number} 26 | */ 27 | KthLargest.prototype.add = function(val) { 28 | // 找到要插入值的位置进行插入 29 | let pos = this.minHeap.length; 30 | for (let i = 0; i < this.minHeap.length; i++) { 31 | if (val >= this.minHeap[i]) { 32 | pos = i; 33 | break; 34 | } 35 | } 36 | this.minHeap.splice(pos, 0, val); 37 | return this.minHeap[this.k-1]; 38 | }; 39 | 40 | /** 41 | * Your KthLargest object will be instantiated and called as such: 42 | * var obj = new KthLargest(k, nums) 43 | * var param_1 = obj.add(val) 44 | */ -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/堆/MedianFinder_剑指_41.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 剑指 Offer 41. 数据流中的中位数 3 | * 困难 4 | * https://leetcode.cn/problems/shu-ju-liu-zhong-de-zhong-wei-shu-lcof/ 5 | * 6 | * 解法:大顶堆 + 小顶堆 7 | */ 8 | class MedianFinder { 9 | Queue A, B; 10 | 11 | public MedianFinder() { 12 | A = new PriorityQueue<>((x, y) -> (y - x)); // 大顶堆,保存较小的一半,假设m个元素 13 | B = new PriorityQueue<>(); // 小顶堆,保存较大的一半,假设n个元素 14 | } 15 | 16 | public void addNum(int num) { 17 | // 原先是奇数,需向B添加一个元素。实现方法:将新元素num插入至A ,再将A堆顶元素插入至B 18 | if (B.size() != A.size()) { 19 | A.add(num); 20 | B.add(A.poll()); 21 | // 原先是偶数,需要将加到大顶堆A中。实现:将新元素num先插入至B ,再将B堆顶元素插入至A,因为要保证插入A的元素一定是大于B堆的所有元素 22 | } else { 23 | B.add(num); 24 | A.add(B.poll()); 25 | } 26 | } 27 | 28 | public double findMedian() { 29 | // 个数为奇数 则中位数为 大顶堆的堆顶元素;是偶数,则中位数为 (大顶堆+小顶堆)/2 30 | return B.size() != A.size() 31 | ? A.peek() 32 | : (B.peek() + A.peek()) / 2.0; 33 | } 34 | } 35 | 36 | /** 37 | * Your MedianFinder object will be instantiated and called as such: 38 | * MedianFinder obj = new MedianFinder(); 39 | * obj.addNum(num); 40 | * double param_2 = obj.findMedian(); 41 | */ -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/字符串/KMP/strStr_28.js: -------------------------------------------------------------------------------- 1 | const KMP = require('./kmp算法'); 2 | 3 | /** 4 | * 28. 实现 strStr() 5 | * 简单 6 | * https://leetcode-cn.com/problems/implement-strstr/ 7 | * 8 | * 题目: 9 | * 给定一个 haystack 字符串和一个 needle 字符串, 10 | * 在 haystack 字符串中找出 needle 字符串出现的第一个位置 (从0开始)。 11 | * 如果不存在,则返回  -1。 12 | * 13 | * 例子: 14 | * 输入: haystack = "hello", needle = "ll" 15 | * 输出: 2 16 | * 17 | * 解法:利用kmp字符串匹配算法 18 | * 19 | * @param {string} haystack 20 | * @param {string} needle 21 | * @return {number} 22 | */ 23 | var strStr = function (haystack, needle) { 24 | const kmp = new KMP(needle); 25 | return kmp.search(haystack); 26 | }; 27 | const haystack = 'aaaaa'; 28 | const needle = 'bba'; 29 | console.log(strStr(haystack, needle)); // -1 30 | 31 | /** 32 | * 暴力解法 33 | * 34 | * 时间复杂度 O(MN) 35 | * 空间复杂度O(1) 36 | * 37 | * @param {string} haystack 38 | * @param {string} needle 39 | * @return {number} 40 | */ 41 | var strStr2 = function (haystack, needle) { 42 | let L = needle.length; 43 | let n = haystack.length; 44 | 45 | for (let j = 0; j < n - L + 1; ++j) { 46 | const sub = haystack.substring(j, j + L); 47 | if (sub == needle) { 48 | return j; 49 | } 50 | } 51 | return -1; 52 | }; 53 | 54 | console.log(strStr2(haystack, needle)); // -1 55 | -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/字符串/countAndSay_38.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 38. 外观数列 3 | * 中等 4 | * https://leetcode.cn/problems/count-and-say/ 5 | * 6 | * 解法:字符串 7 | */ 8 | /** 9 | * @param {number} n 10 | * @return {string} 11 | */ 12 | var countAndSay = function (n) { 13 | 14 | let str = '1'; 15 | 16 | for (let i = 2; i <= n; i++) { 17 | // 对每个n求出它的序列,其实是对前一项相同字符的个数统计 18 | let start = 0; 19 | let point = 0; 20 | const sb = []; // 存放前一项相同字符的个数统计,如['11','21'] 分别表示1个1,2个1 21 | 22 | while (point < str.length) { 23 | while (point < str.length && str[point] == str[start]) { 24 | point++; 25 | } 26 | 27 | // 此时point - 1索引指向的数和start索引指向的数是相同的,放入队列中,如3个1,即'31' 28 | sb.push('' + (point - start) + str[start]); 29 | start = point; 30 | } 31 | 32 | // 前一项的序列 33 | str = sb.join(''); 34 | } 35 | 36 | return str; 37 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/字符串/lengthOfLastWord_58.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 58. 最后一个单词的长度 3 | * 简单 4 | * https://leetcode.cn/problems/length-of-last-word/ 5 | * 6 | * 解法:字符串 7 | * 时间O(n) 8 | * 空间O(1) 9 | */ 10 | /** 11 | * @param {string} s 12 | * @return {number} 13 | */ 14 | var lengthOfLastWord = function (s) { 15 | // 从后往前定位到最后单词的最后一个字符,然后往前遍历直到空格停下 16 | let count = 0; 17 | 18 | let index = s.length - 1; 19 | while (index > 0 && s[index] === ' ') { 20 | index--; 21 | } 22 | 23 | while (index >= 0 && s[index] !== ' ') { 24 | count++; 25 | index--; 26 | } 27 | 28 | return count; 29 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/字符串/longestCommonPrefix_14.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 14. 最长公共前缀 3 | * 简单 4 | * https://leetcode.cn/problems/longest-common-prefix/ 5 | * 6 | * 解法:字符串 7 | */ 8 | /** 9 | * @param {string[]} strs 10 | * @return {string} 11 | */ 12 | var longestCommonPrefix = function (strs) { 13 | if (strs.length == 0) { 14 | return ''; 15 | } 16 | 17 | // 找到两两的最长公共前缀 18 | let n = strs.length; 19 | 20 | let prefix = strs[0]; 21 | 22 | for (let i = 1; i < n; i++) { 23 | // 找到当前字符串跟之前的公共前缀prefix的公共前缀 24 | prefix = findPrefix(strs[i], prefix); 25 | if (prefix == '') { 26 | break; 27 | } 28 | } 29 | 30 | return prefix; 31 | }; 32 | 33 | // 找两个字符串的公共前缀 34 | const findPrefix = (str1, str2) => { 35 | let len1 = str1.length; 36 | let len2 = str2.length; 37 | const len = Math.min(len1, len2); 38 | 39 | let prefix = ''; 40 | let i = 0; 41 | while (i < len) { 42 | if (str1[i] == str2[i]) { 43 | i++; 44 | } else { 45 | break; 46 | } 47 | } 48 | 49 | prefix = str1.substring(0, i); 50 | 51 | return prefix; 52 | } -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/字符串/myAtoi_8.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 8. 字符串转换整数 (atoi) 3 | * 中等 4 | * 5 | * 解法:字符串 6 | */ 7 | /** 8 | * @param {string} s 9 | * @return {number} 10 | */ 11 | var myAtoi = function (s) { 12 | 13 | let str = s.trim(); // 去掉空格 14 | 15 | if (str.length == 0) { 16 | return 0; 17 | } 18 | 19 | let i = 0; 20 | let sign = 1; // 标示首位是正号 还是 负号 21 | 22 | if (str[0] == '-') { 23 | sign = -1; 24 | i = 1; 25 | } else if (str[0] == '+') { 26 | i = 1; 27 | } 28 | 29 | let res = 0; 30 | const intMax = 2147483647; 31 | const intMin = -2147483648; 32 | 33 | for (let j = i; j < str.length; j++) { 34 | if (str[j] >= 0 && str[j] <= 9 && str[j] != " ") { 35 | res = res * 10 + (+str[j] - 0); // 此时c[j]是数字字符,算出此时字符串转成数字的结果 36 | 37 | // 判断加上当前字符后是否越界 38 | if (res * sign <= intMin) { 39 | return intMin; 40 | } else if (res * sign >= intMax) { 41 | return intMax; 42 | } 43 | } else { 44 | break; 45 | } 46 | } 47 | 48 | return sign * res; 49 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/字符串/replaceSpace_剑指_5.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 剑指 Offer 05. 替换空格 3 | * 简单 4 | * https://leetcode.cn/problems/ti-huan-kong-ge-lcof/ 5 | * 6 | * 7 | */ 8 | /** 9 | * @param {string} s 10 | * @return {string} 11 | */ 12 | var replaceSpace = function (s) { 13 | let rst = ''; 14 | for (let c of s) { 15 | rst += c == ' ' 16 | ? '%20' 17 | : c; 18 | } 19 | return rst; 20 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/字符串/reverseLeftWords_剑指_58_II.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 剑指 Offer 58 - II. 左旋转字符串 3 | * 简单 4 | * https://leetcode.cn/problems/zuo-xuan-zhuan-zi-fu-chuan-lcof/ 5 | */ 6 | /** 7 | * @param {string} s 8 | * @param {number} n 9 | * @return {string} 10 | */ 11 | var reverseLeftWords = function (s, n) { 12 | if (!s || s.length == 1) { 13 | return s; 14 | } 15 | 16 | return s.substring(n, s.length) + s.substring(0, n); 17 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/字符串/reverseWords_557.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 557. 反转字符串中的单词 III 3 | * 简单 4 | * https://leetcode.cn/problems/reverse-words-in-a-string-iii/ 5 | * 6 | * 解法:字符串 7 | */ 8 | /** 9 | * @param {string} s 10 | * @return {string} 11 | */ 12 | var reverseWords = function (s) { 13 | let result = ''; 14 | 15 | let i = 0; 16 | 17 | // 遍历遇到空格则进行翻转 18 | for (let j = 0; j < s.length; j++) { 19 | if (s[j] == ' ') { 20 | result += reverse(s.substring(i, j)) + ' '; 21 | i = j + 1; 22 | } 23 | } 24 | 25 | // 最后一个空格后的单词,此时也需要翻转 26 | result += reverse(s.substring(i)); 27 | 28 | return result; 29 | }; 30 | 31 | var reverse = function (s) { 32 | let t = ''; 33 | for (let j = s.length - 1; j >= 0; j--) { 34 | t += s[j]; 35 | } 36 | return t; 37 | } -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/字符串/reverseWords_剑指_58_I.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 剑指 Offer 58 - I. 翻转单词顺序 3 | * 简单 4 | * https://leetcode.cn/problems/fan-zhuan-dan-ci-shun-xu-lcof/ 5 | * 6 | */ 7 | /** 8 | * @param {string} s 9 | * @return {string} 10 | */ 11 | var reverseWords = function (s) { 12 | if (!s) { 13 | return s; 14 | } 15 | 16 | const arr = s.trim().split(' '); 17 | 18 | let result = []; 19 | for (let item of arr) { 20 | if (!item) { 21 | continue; 22 | } 23 | result.push(item); 24 | } 25 | 26 | return result.reverse().join(' '); 27 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/字符串/strToInt_剑指_67.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 剑指 Offer 67. 把字符串转换成整数 3 | * 中等 4 | * https://leetcode.cn/problems/ba-zi-fu-chuan-zhuan-huan-cheng-zheng-shu-lcof/ 5 | * 6 | * 解法:字符串 7 | */ 8 | /** 9 | * @param {string} str 10 | * @return {number} 11 | */ 12 | var strToInt = function (str) { 13 | 14 | str = str.trim(); // 去掉空格 15 | 16 | if (str.length == 0) { 17 | return 0; 18 | } 19 | 20 | let i = 0; 21 | let sign = 1; // 标示首位是正号 还是 负号 22 | 23 | if (str[0] == '-') { 24 | sign = -1; 25 | i = 1; 26 | } else if (str[0] == '+') { 27 | i = 1; 28 | } 29 | 30 | let res = 0; 31 | const intMax = 2147483647; 32 | const intMin = -2147483648; 33 | 34 | for (let j = i; j < str.length; j++) { 35 | if (str[j] >= 0 && str[j] <= 9 && str[j] != " ") { 36 | res = res * 10 + (+str[j] - 0); // 此时c[j]是数字字符,算出此时字符串转成数字的结果 37 | 38 | // 判断加上当前字符后是否越界 39 | if (res * sign <= intMin) { 40 | return intMin; 41 | } else if (res * sign >= intMax) { 42 | return intMax; 43 | } 44 | } else { 45 | break; 46 | } 47 | } 48 | 49 | return sign * res; 50 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/排序/findMinDifference_剑指II_35.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 剑指 Offer II 035. 最小时间差 3 | * 中等 4 | * https://leetcode.cn/problems/569nqc/ 5 | * 6 | * 解法:排序 7 | */ 8 | /** 9 | * @param {string[]} timePoints 10 | * @return {number} 11 | */ 12 | var findMinDifference = function (timePoints) { 13 | // 排序 14 | // 将timePoints从小到大排序后,最小时间差必然出现在timePoints 的两个相邻时间,或者timePoints的两个首尾时间中。因此排序后遍历一遍timePoints 即可得到最小时间差 15 | 16 | timePoints.sort(); 17 | 18 | let ans = Number.MAX_VALUE; 19 | 20 | let t0Minutes = getMinutes(timePoints[0]); 21 | let preMinutes = t0Minutes; 22 | 23 | for (let i = 1; i < timePoints.length; ++i) { 24 | const minutes = getMinutes(timePoints[i]); 25 | ans = Math.min(ans, minutes - preMinutes); // 相邻时间的时间差 26 | preMinutes = minutes; 27 | } 28 | 29 | ans = Math.min(ans, t0Minutes + 1440 - preMinutes); // 首尾时间的时间差 30 | 31 | return ans; 32 | }; 33 | 34 | const getMinutes = (t) => { 35 | return ((t[0].charCodeAt() - '0'.charCodeAt()) * 10 + 36 | (t[1].charCodeAt() - '0'.charCodeAt())) * 60 + 37 | (t[3].charCodeAt() - '0'.charCodeAt()) * 10 + 38 | (t[4].charCodeAt() - '0'.charCodeAt()); 39 | } -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/排序/getLeastNumbers_面试题_40.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 面试题40. 最小的k个数 3 | * 简单 4 | * https://leetcode.cn/problems/zui-xiao-de-kge-shu-lcof/ 5 | */ 6 | /** 7 | * @param {number[]} arr 8 | * @param {number} k 9 | * @return {number[]} 10 | */ 11 | var getLeastNumbers = function (arr, k) { 12 | arr.sort((a, b) => parseInt(a) - parseInt(b)); 13 | return arr.slice(0, k); 14 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/排序/kSmallestPairs_剑指II_61.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 剑指 Offer II 061. 和最小的 k 个数对 3 | * 中等 4 | * https://leetcode.cn/problems/qn8gGX/ 5 | * 6 | * 解法:暴力 7 | */ 8 | /** 9 | * 10 | * @param {*} nums1 11 | * @param {*} nums2 12 | * @param {*} k 13 | * @returns 14 | */ 15 | var kSmallestPairs = function (nums1, nums2, k) { 16 | // 暴力解法 17 | // 遍历出所有数对,然后取前k个 18 | let arr = []; 19 | for (let i = 0; i < nums1.length; i++) { 20 | for (let j = 0; j < nums2.length; j++) { 21 | arr.push([nums1[i], nums2[j]]); 22 | } 23 | } 24 | 25 | return arr.sort((a, b) => (a[0] + a[1]) - (b[0] + b[1])).slice(0, k).map(a => [a[0], a[1]]); 26 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/排序/largestNumber_179.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 179. 最大数 3 | * https://leetcode-cn.com/problems/largest-number/ 4 | * 5 | * 给定一组非负整数 nums,重新排列它们每个数字的顺序(每个数字不可拆分) 6 | * 使之组成一个最大的整数。 7 | * 8 | * 比较 ab 与 ba的大小,按降序排列,再将数组转化为字符串 9 | * 10 | * 时间复杂度:O(nlogn) 11 | * 空间复杂度:O(n) 12 | * 13 | * @param {number[]} nums 14 | * @return {string} 15 | */ 16 | var largestNumber = function(nums) { 17 | nums.sort((a, b) => { 18 | let result1 = `${a}${b}`; 19 | let result2 = `${b}${a}`; 20 | return result2 - result1; 21 | }); 22 | 23 | return nums[0] ? nums.join('') : '0'; 24 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/排序/minNumber_剑指_45.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 剑指 Offer 45. 把数组排成最小的数 3 | * 中等 4 | * https://leetcode.cn/problems/ba-shu-zu-pai-cheng-zui-xiao-de-shu-lcof/ 5 | */ 6 | class Solution { 7 | public String minNumber(int[] nums) { 8 | String[] strs = new String[nums.length]; 9 | for(int i = 0; i < nums.length; i++) 10 | strs[i] = String.valueOf(nums[i]); 11 | 12 | quickSort(strs, 0, strs.length - 1); 13 | 14 | StringBuilder res = new StringBuilder(); 15 | for(String s : strs) 16 | res.append(s); 17 | 18 | return res.toString(); 19 | } 20 | 21 | // 快排 22 | void quickSort(String[] strs, int l, int r) { 23 | if(l >= r) 24 | return; 25 | 26 | int i = l, j = r; 27 | String tmp = strs[i]; 28 | while(i < j) { 29 | while((strs[j] + strs[l]).compareTo(strs[l] + strs[j]) >= 0 && i < j) j--; 30 | while((strs[i] + strs[l]).compareTo(strs[l] + strs[i]) <= 0 && i < j) i++; 31 | tmp = strs[i]; 32 | strs[i] = strs[j]; 33 | strs[j] = tmp; 34 | } 35 | 36 | strs[i] = strs[l]; 37 | strs[l] = tmp; 38 | 39 | quickSort(strs, l, i - 1); 40 | quickSort(strs, i + 1, r); 41 | } 42 | } -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/排序/reconstructQueue_406.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 406. 根据身高重建队列 3 | * 中等 4 | * https://leetcode.cn/problems/queue-reconstruction-by-height/ 5 | * 6 | * 解法:排序 7 | * 参考 https://leetcode.cn/problems/queue-reconstruction-by-height/solution/an-shen-gao-cong-da-dao-xiao-pai-xu-ai-ge-cha-ru-d/ 8 | * 9 | */ 10 | /** 11 | * 12 | * @param {*} people 13 | * @returns 14 | */ 15 | var reconstructQueue = function (people) { 16 | 17 | // 将people按身高从大到小排序,如果身高一样则将前面高于自己人数小的人放在前面 18 | people.sort((a, b) => a[0] === b[0] ? a[1] - b[1] : b[0] - a[0]) 19 | // 创建新数组 ans 20 | let ans = [] 21 | for (let i = 0; i < people.length; i++) { 22 | // 挨个根据前面高于自己人数插入到ans里 23 | // 因为people已按照身高排序,所以某个人被插入到ans里时,所有比他高的都已经在ans里了 24 | // 而身高比他矮的人怎样插入到ans里都不影响前面高于他的人数 25 | // 所以这样得到的数组就是符合我们要求的队列 26 | ans.splice(people[i][1], 0, people[i]) 27 | } 28 | return ans 29 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/排序/relativeSortArray_剑指II_75.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 剑指 Offer II 075. 数组相对排序 3 | * 简单 4 | * https://leetcode.cn/problems/0H97ZC/ 5 | * 6 | * 解法:排序 + 哈希表 7 | */ 8 | /** 9 | * @param {number[]} arr1 10 | * @param {number[]} arr2 11 | * @return {number[]} 12 | */ 13 | var relativeSortArray = function (arr1, arr2) { 14 | const map = new Map(); 15 | for (let num of arr1) { 16 | map.set(num, (map.get(num) || 0) + 1); 17 | } 18 | 19 | // 对arr2中出现的数据进行排序 20 | let idx = 0; 21 | for (let num of arr2) { 22 | while (map.get(num)) { 23 | arr1[idx] = num; 24 | idx++; 25 | map.set(num, map.get(num) - 1); 26 | } 27 | map.delete(num); 28 | } 29 | 30 | // 对剩下的数据排序 31 | let rst = []; 32 | map.forEach((value, key) => { 33 | rst.push(key); 34 | }) 35 | rst.sort((a, b) => a - b); 36 | 37 | for (let num of rst) { 38 | while (map.get(num)) { 39 | arr1[idx++] = num; 40 | map.set(num, map.get(num) - 1); 41 | } 42 | } 43 | return arr1; 44 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/排序/thirdMax_414.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 414. 第三大的数 3 | * 简单 4 | * https://leetcode-cn.com/problems/third-maximum-number/ 5 | * 6 | * 给你一个非空数组,返回此数组中 第三大的数 。如果不存在,则返回数组中最大的数。 7 | * 8 | * 1. 数组先去重 9 | * 2. 升序排序 10 | * 3. 若数组长度大于等于3返回数组第3位;否则返回数组第1位 11 | * 12 | * @param {number[]} nums 13 | * @return {number} 14 | */ 15 | var thirdMax = function(nums) { 16 | var arr = [...new Set(nums)].sort((a, b) => b - a); 17 | return arr.length >=3 ? arr[2] : arr[0] 18 | }; 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/排序/wiggleSort_324.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 324. 摆动排序 II 3 | * 中等 4 | * https://leetcode.cn/problems/wiggle-sort-ii/ 5 | * 6 | * 解法:桶排序 7 | * 参考 https://leetcode.cn/problems/wiggle-sort-ii/solution/bai-dong-pai-xu-ii-by-jiang-hui-4-4tyk/ 8 | * 9 | * 时间O(n) 10 | * 空间O(c) c为5001 11 | */ 12 | /** 13 | * @param {number[]} nums 14 | * @return {void} Do not return anything, modify nums in-place instead. 15 | */ 16 | var wiggleSort = function (nums) { 17 | 18 | const bucket = new Array(5001).fill(0); 19 | for (let num of nums) { 20 | bucket[num]++; 21 | } 22 | 23 | let j = 5000; 24 | // 插入较大元素到原数组中,插入到偶数位置 25 | for (let i = 1; i < nums.length; i += 2) { 26 | // 从后往前定位到 最大的元素 27 | while (bucket[j] == 0) { 28 | j--; 29 | } 30 | 31 | nums[i] = j; 32 | bucket[j]--; // 计数减1 33 | } 34 | 35 | // 插入较小元素 36 | for (let i = 0; i < nums.length; i += 2) { 37 | while (bucket[j] == 0) { 38 | j--; 39 | } 40 | 41 | nums[i] = j; 42 | bucket[j]--; 43 | } 44 | 45 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/搜索/回溯/allPathsSourceTarget_剑指II_110.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 剑指 Offer II 110. 所有路径 3 | * 中等 4 | * https://leetcode.cn/problems/bP4bmD/ 5 | * 6 | * 解法:深度优先搜索 7 | * 8 | * 时间O(n * 2^n) 9 | * 空间O(n) 10 | */ 11 | /** 12 | * @param {number[][]} graph 13 | * @return {number[][]} 14 | */ 15 | var allPathsSourceTarget = function (graph) { 16 | const dfs = (graph, x, n) => { 17 | if (x === n) { 18 | ans.push(stack.slice()); 19 | return; 20 | } 21 | for (const y of graph[x]) { 22 | stack.push(y); 23 | dfs(graph, y, n); 24 | stack.pop(); 25 | } 26 | } 27 | 28 | const stack = []; 29 | const ans = []; 30 | stack.push(0); 31 | 32 | dfs(graph, 0, graph.length - 1); 33 | 34 | return ans; 35 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/搜索/回溯/combine_77.js: -------------------------------------------------------------------------------- 1 | /** 2 | * leetcode 77 组合 3 | * https://leetcode-cn.com/problems/combinations/ 4 | * 5 | * 给定两个整数 n 和 k,返回 1 ... n 中所有可能的 k 个数的组合 6 | * 7 | * 回溯 8 | * 9 | * @param {number} n 10 | * @param {number} k 11 | * @return {number[][]} 12 | */ 13 | var combine = function(n, k) { 14 | let res = []; 15 | if (k <= 0 || n <= 0) { 16 | return res; 17 | } 18 | 19 | this.backtrack = function (n, k, start, track) { 20 | // 结束条件,长度等于要求的长度即可以存入结果集 21 | if (track.length === k) { 22 | // 不能直接res.push(track),因为该引用的值会改变,需要存拷贝后的值 23 | res.push(track.slice()); 24 | return; 25 | } 26 | 27 | for (let i = start; i <= n; i++) { 28 | // 选择 29 | track.push(i); 30 | 31 | // 回溯 32 | this.backtrack(n, k, i + 1, track); 33 | 34 | // 撤销选择 35 | track.pop(); 36 | } 37 | } 38 | 39 | // 所选的路径 40 | let track = []; 41 | // 从1开始,因为题目是要求返回1到n 42 | this.backtrack(n, k, 1, track); 43 | return res; 44 | }; 45 | 46 | const n = 4; 47 | const k = 2; 48 | console.log(combine(n, k)); 49 | -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/搜索/回溯/generateParenthesis_22.js: -------------------------------------------------------------------------------- 1 | /** 2 | * leetcode 22 括号生成 3 | * 4 | * https://leetcode-cn.com/problems/generate-parentheses/ 5 | * 6 | * @param {number} n 7 | * @return {string[]} 8 | */ 9 | var generateParenthesis = function (n) { 10 | if (n <= 0) { 11 | return []; 12 | } 13 | 14 | let res = []; 15 | 16 | // left 和 right 分别表示左括号、右括号剩下的个数 17 | this.backtrack = function (left, right, track) { 18 | // 左括号剩下的多不合法(左括号在左边,永远比右括号多) 19 | if (right < left) { 20 | return; 21 | } 22 | 23 | // 数量⼩于 0 不合法 24 | if (left < 0 || right < 0) { 25 | return; 26 | } 27 | 28 | // 当所有括号刚好⽤完时,此时得到⼀个合法的括号组合 29 | if (left == 0 && right == 0) { 30 | res.push(track.join('')); 31 | return; 32 | } 33 | 34 | // 选择左括号 35 | track.push('('); 36 | backtrack(left - 1, right, track); 37 | track.pop(); 38 | 39 | // 选择右括号 40 | track.push(')'); 41 | backtrack(left, right - 1, track); 42 | track.pop(); 43 | } 44 | 45 | let track = []; 46 | 47 | // 刚开始左括号和右括号都是n个 48 | this.backtrack(n, n, track); 49 | 50 | return res; 51 | }; 52 | 53 | const n = 3; 54 | console.log(generateParenthesis(n)); 55 | 56 | -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/搜索/回溯/largestValues_剑指II_515.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 剑指 Offer II 044. 二叉树每层的最大值 3 | * 中等 4 | * https://leetcode.cn/problems/hPov7L/ 5 | * 6 | * 解法:bfs 7 | */ 8 | /** 9 | * Definition for a binary tree node. 10 | * function TreeNode(val, left, right) { 11 | * this.val = (val===undefined ? 0 : val) 12 | * this.left = (left===undefined ? null : left) 13 | * this.right = (right===undefined ? null : right) 14 | * } 15 | */ 16 | /** 17 | * @param {TreeNode} root 18 | * @return {number[]} 19 | */ 20 | var largestValues = function (root) { 21 | // 广度优先 bfs 22 | let result = []; 23 | 24 | if (!root) { 25 | return result; 26 | } 27 | 28 | let queue = [root]; 29 | 30 | while (queue.length) { 31 | let n = queue.length; 32 | 33 | let levelMax = Number.MIN_SAFE_INTEGER; 34 | 35 | for (let i = 0; i < n; i++) { 36 | const cur = queue.shift(); 37 | 38 | levelMax = Math.max(levelMax, cur.val); 39 | 40 | if (cur.left) { 41 | queue.push(cur.left); 42 | } 43 | if (cur.right) { 44 | queue.push(cur.right); 45 | } 46 | } 47 | 48 | result.push(levelMax); 49 | } 50 | 51 | return result; 52 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/搜索/回溯/partition_剑指II_86.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 剑指 Offer II 086. 分割回文子字符串 3 | * 中等 4 | * https://leetcode.cn/problems/M99OJA/ 5 | * 6 | * 解法:回溯 + 动态规划预处理 7 | */ 8 | /** 9 | * @param {string} s 10 | * @return {string[][]} 11 | */ 12 | var partition = function (s) { 13 | // 回溯 14 | const dfs = (i) => { 15 | if (i === n) { 16 | ret.push(ans.slice()); 17 | return; 18 | } 19 | 20 | for (let j = i; j < n; ++j) { 21 | if (dp[i][j]) { // 是回文串才继续做选择 22 | ans.push(s.slice(i, j + 1)); // 选择[i,j]子串 23 | dfs(j + 1); 24 | ans.pop(); // 撤销选择 25 | } 26 | } 27 | } 28 | 29 | const n = s.length; 30 | 31 | // dp二维数组存储是否是回文串的状态 32 | // dp[i][j]表示s[i,j]是否是回文串 33 | // 状态转移方程 当i==j时,dp[i][j]=true;当s[i]==s[j]时,dp[i][j]=dp[i+1][j-1] 34 | const dp = new Array(n).fill(0).map(() => new Array(n).fill(true)); 35 | let ret = []; 36 | let ans = []; 37 | 38 | for (let i = n - 1; i >= 0; --i) { 39 | for (let j = i + 1; j < n; ++j) { 40 | dp[i][j] = (s[i] === s[j]) && dp[i + 1][j - 1]; 41 | } 42 | } 43 | 44 | dfs(0); 45 | return ret; 46 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/搜索/回溯/permute_46.js: -------------------------------------------------------------------------------- 1 | /** 2 | * leetcode 46 全排列 3 | * https://leetcode-cn.com/problems/permutations/ 4 | * 5 | * 返回一个数组的全排列 6 | * 7 | * 回溯 8 | * 9 | * @param {number[]} nums 10 | * @return {number[][]} 11 | */ 12 | var permute = function (nums) { 13 | let n = nums.length; 14 | if (n <= 1) { 15 | return [nums]; 16 | } 17 | 18 | let res = []; 19 | 20 | this.backtrack = function (nums, track) { 21 | // 选择的列表满足全排列的个数,则加入结果集 22 | if (track.length === n) { 23 | // 不能直接res.push(track),因为该引用的值会改变,需要存拷贝后的值 24 | res.push(track.slice()); 25 | return; 26 | } 27 | 28 | for (let i = 0; i < n; i++) { 29 | // 因为是全排列,该选择已经选择过的,可以跳过 30 | if (track.includes(nums[i])) { 31 | continue; 32 | } 33 | 34 | // 选择 35 | track.push(nums[i]); 36 | // 回溯 37 | this.backtrack(nums, track); 38 | // 撤回 39 | track.pop(); 40 | } 41 | } 42 | 43 | let track = []; 44 | this.backtrack(nums, track); 45 | return res; 46 | }; 47 | 48 | const nums = [1, 2, 3]; 49 | console.log(permute(nums)); 50 | -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/搜索/回溯/restoreIpAddresses_剑指II_87.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 剑指 Offer II 087. 复原 IP 4 | * 中等 5 | * https://leetcode.cn/problems/0on3uN/ 6 | * 7 | * 解法:回溯 8 | * 9 | */ 10 | /** 11 | * @param {string} s 12 | * @return {string[]} 13 | */ 14 | var restoreIpAddresses = function (s) { 15 | // IP一共有四位, 每位整数位于 0 到 255 之间组成,且不能含有前导 0 16 | 17 | const len = s.length; 18 | if (len > 12 || len < 4) { 19 | return []; 20 | } 21 | 22 | const dfs = (start, track) => { 23 | if (track.length > 4) { 24 | return; 25 | } 26 | 27 | // 遍历到末尾了,且分成了4段 28 | if (start === len && track.length === 4) { 29 | result.push(track.join('.')); 30 | return result; 31 | } 32 | 33 | let temp = ''; 34 | for (let i = start; i < len; ++i) { 35 | temp += s[i]; 36 | 37 | // 第1位置上的数字是0时,只有0才符合条件,'01'则不符合条件 38 | if (+temp >= 0 && +temp <= 255 && (temp[0] !== '0' || temp == '0')) { 39 | track.push(temp); 40 | dfs(i + 1, track); 41 | track.pop(); 42 | } 43 | } 44 | }; 45 | 46 | let result = []; 47 | let track = []; 48 | 49 | dfs(0, track); 50 | 51 | return result; 52 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/搜索/回溯/subsets_78.js: -------------------------------------------------------------------------------- 1 | /** 2 | * leetcode 78 子集 3 | * https://leetcode-cn.com/problems/subsets/ 4 | * 5 | * 给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。 6 | * 解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。 7 | * 8 | * 输入:nums = [1,2,3] 9 | * 输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]] 10 | * 11 | * 回溯算法 12 | * 13 | * @param {number[]} nums 14 | * @return {number[][]} 15 | */ 16 | var subsets = function (nums) { 17 | let res = []; 18 | 19 | this.backtrack = function(nums, start, track) { 20 | // 不能直接res.push(track),因为引用问题,所以要存拷贝后的值 21 | res.push(track.slice()); 22 | 23 | for (let i = start; i < nums.length; i++) { 24 | // 做选择 25 | track.push(nums[i]); 26 | // 回溯 27 | this.backtrack(nums, i + 1, track); 28 | // 撤回选择 29 | track.pop(); 30 | } 31 | } 32 | 33 | // 记录所做的选择 34 | const track = []; 35 | backtrack(nums, 0, track); 36 | return res; 37 | }; 38 | 39 | const nums = [1, 2, 3]; 40 | console.log(subsets(nums)); 41 | -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/搜索/广度优先搜索/inorderSuccessor_剑指II_53.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 剑指 Offer II 053. 二叉搜索树中的中序后继 3 | * 中等 4 | * https://leetcode.cn/problems/P5rCT8/ 5 | * 6 | * 解法:中序遍历(迭代) 7 | * 8 | * 时间O(n) 9 | * 空间O(n) 10 | */ 11 | /** 12 | * Definition for a binary tree node. 13 | * function TreeNode(val) { 14 | * this.val = val; 15 | * this.left = this.right = null; 16 | * } 17 | */ 18 | /** 19 | * @param {TreeNode} root 20 | * @param {TreeNode} p 21 | * @return {TreeNode} 22 | */ 23 | var inorderSuccessor = function (root, p) { 24 | // 在中序遍历的过程中维护上一个访问的节点和当前访问的节点 25 | // 如果上一个访问的节点是节点 p,则当前访问的节点即为节点 p 的中序后继 26 | 27 | const stack = []; 28 | let pre = null; 29 | let cur = root; 30 | 31 | while (stack.length || cur) { 32 | while (cur) { // 寻找最左侧的节点 33 | stack.push(cur); 34 | cur = cur.left; 35 | } 36 | cur = stack.pop(); 37 | 38 | if (pre == p) { 39 | return cur; 40 | } 41 | 42 | pre = cur; 43 | cur = cur.right; 44 | } 45 | 46 | return null; 47 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/搜索/广度优先搜索/largestValues_剑指II_44.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 剑指 Offer II 044. 二叉树每层的最大值 3 | * 中等 4 | * https://leetcode.cn/problems/hPov7L/ 5 | * 6 | * Definition for a binary tree node. 7 | * function TreeNode(val, left, right) { 8 | * this.val = (val===undefined ? 0 : val) 9 | * this.left = (left===undefined ? null : left) 10 | * this.right = (right===undefined ? null : right) 11 | * } 12 | */ 13 | /** 14 | * @param {TreeNode} root 15 | * @return {number[]} 16 | */ 17 | var largestValues = function (root) { 18 | // 广度优先 bfs 19 | let result = []; 20 | 21 | if (!root) { 22 | return result; 23 | } 24 | 25 | let queue = [root]; 26 | 27 | while (queue.length) { 28 | let n = queue.length; 29 | 30 | let levelMax = Number.MIN_SAFE_INTEGER; 31 | 32 | for (let i = 0; i < n; i++) { 33 | const cur = queue.shift(); 34 | 35 | levelMax = Math.max(levelMax, cur.val); 36 | 37 | if (cur.left) { 38 | queue.push(cur.left); 39 | } 40 | if (cur.right) { 41 | queue.push(cur.right); 42 | } 43 | } 44 | 45 | result.push(levelMax); 46 | } 47 | 48 | return result; 49 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/搜索/广度优先搜索/rightSideView_剑指II_46.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 剑指 Offer II 046. 二叉树的右侧视图 3 | * 中等 4 | * https://leetcode.cn/problems/WNC0Lk/ 5 | * 6 | * 解法:bfs 7 | */ 8 | /** 9 | * Definition for a binary tree node. 10 | * function TreeNode(val, left, right) { 11 | * this.val = (val===undefined ? 0 : val) 12 | * this.left = (left===undefined ? null : left) 13 | * this.right = (right===undefined ? null : right) 14 | * } 15 | */ 16 | /** 17 | * @param {TreeNode} root 18 | * @return {number[]} 19 | */ 20 | var rightSideView = function (root) { 21 | // bfs 22 | let result = []; 23 | if (root == null) { 24 | return result; 25 | } 26 | 27 | let queue = [root]; 28 | 29 | while (queue.length) { 30 | let n = queue.length; 31 | 32 | for (let i = 0; i < n; i++) { 33 | const cur = queue.shift(); 34 | 35 | // 每1层的最后1个节点的值 36 | if (i == n - 1) { 37 | result.push(cur.val); 38 | } 39 | 40 | if (cur.left) { 41 | queue.push(cur.left); 42 | } 43 | if (cur.right) { 44 | queue.push(cur.right); 45 | } 46 | } 47 | } 48 | 49 | return result; 50 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/搜索/深度优先搜索/convertBST_剑指II_54.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 剑指 Offer II 054. 所有大于等于节点的值之和 3 | * 中等 4 | * https://leetcode.cn/problems/w6cpku/ 5 | * 6 | * 解法:dfs + 中序遍历 7 | */ 8 | /** 9 | * Definition for a binary tree node. 10 | * function TreeNode(val, left, right) { 11 | * this.val = (val===undefined ? 0 : val) 12 | * this.left = (left===undefined ? null : left) 13 | * this.right = (right===undefined ? null : right) 14 | * } 15 | */ 16 | /** 17 | * @param {TreeNode} root 18 | * @return {TreeNode} 19 | */ 20 | var convertBST = function (root) { 21 | if (!root) { 22 | return root; 23 | } 24 | 25 | // 中序遍历是从小到大,倒序的中序遍历是从大到小 26 | 27 | let sum = 0; 28 | const dfs = (root) => { 29 | if (!root) { 30 | return; 31 | } 32 | 33 | dfs(root.right); 34 | 35 | // 中序 36 | const val = root.val; 37 | sum += val; 38 | 39 | root.val = sum; 40 | 41 | dfs(root.left); 42 | } 43 | 44 | dfs(root); 45 | 46 | return root; 47 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/搜索/深度优先搜索/movingCount_剑指_13.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 剑指 Offer 13. 机器人的运动范围 3 | * 中等 4 | * https://leetcode.cn/problems/ji-qi-ren-de-yun-dong-fan-wei-lcof/ 5 | * 6 | * 解法:dfs 7 | */ 8 | /** 9 | * @param {number} m 10 | * @param {number} n 11 | * @param {number} k 12 | * @return {number} 13 | */ 14 | var movingCount = function (m, n, k) { 15 | // 标记当前格子是否被访问过 16 | const visited = Array.from(new Array(m), () => new Array(n).fill(false)); 17 | 18 | return dfs(visited, m, n, k, 0, 0); 19 | }; 20 | 21 | /** 22 | * 23 | */ 24 | const dfs = function (visited, m, n, k, i, j) { 25 | if (i >= m || j >= n || (bitSum(i) + bitSum(j) > k) || visited[i][j]) { 26 | return 0; 27 | } 28 | 29 | visited[i][j] = true; 30 | 31 | // 当前格 + 往下走 + 往右走 32 | return 1 + dfs(visited, m, n, k, i + 1, j) + dfs(visited, m, n, k, i, j + 1); 33 | } 34 | 35 | /** 36 | * 数位之和计算 37 | */ 38 | const bitSum = function (x) { 39 | let sum = 0; 40 | while (x != 0) { 41 | sum += x % 10; 42 | x = Math.floor(x / 10); 43 | } 44 | 45 | return sum; 46 | } -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/数学/countDigitOne_剑指_43.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 剑指 Offer 43. 1~n 整数中 1 出现的次数 3 | * 困难 4 | * https://leetcode.cn/problems/1nzheng-shu-zhong-1chu-xian-de-ci-shu-lcof/ 5 | * 6 | */ 7 | /** 8 | * @param {number} n 9 | * @return {number} 10 | */ 11 | var countDigitOne = function (n) { 12 | 13 | // mulk 表示 10^k 14 | // 在下面的代码中,可以发现 k 并没有被直接使用到(都是使用 10^k) 15 | // 但为了让代码看起来更加直观,这里保留了 k 16 | let mulk = 1; 17 | let ans = 0; 18 | for (let k = 0; n >= mulk; ++k) { 19 | ans += (Math.floor(n / (mulk * 10))) * mulk + Math.min(Math.max(n % (mulk * 10) - mulk + 1, 0), mulk); 20 | mulk *= 10; 21 | } 22 | return ans; 23 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/数学/findNthDigit_剑指_44.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 剑指 Offer 44. 数字序列中某一位的数字 3 | * 中等 4 | * https://leetcode.cn/problems/shu-zi-xu-lie-zhong-mou-yi-wei-de-shu-zi-lcof/ 5 | * 6 | */ 7 | /** 8 | * @param {number} n 9 | * @return {number} 10 | */ 11 | var findNthDigit = function (n) { 12 | if (n < 10) { 13 | return n; 14 | } 15 | 16 | let digit = 1; 17 | let start = 1; 18 | let count = 9; 19 | 20 | while (n > count) { 21 | n -= count; 22 | digit += 1; 23 | start *= 10; 24 | count = digit * start * 9; 25 | } 26 | 27 | let num = start + (n - 1) / digit; 28 | return String(num).charAt((n - 1) % digit) - '0'; 29 | 30 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/数学/fizzBuzz_412.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 412. Fizz Buzz 3 | * 简单 4 | * https://leetcode.cn/problems/fizz-buzz/ 5 | * 6 | * 解法:数学 7 | * 8 | */ 9 | /** 10 | * @param {number} n 11 | * @return {string[]} 12 | */ 13 | var fizzBuzz = function (n) { 14 | let result = []; 15 | 16 | for (let i = 1; i <= n; i++) { 17 | let item; 18 | if (i % 3 == 0 && i % 5 == 0) { 19 | item = 'FizzBuzz'; 20 | } else if (i % 3 == 0) { 21 | item = 'Fizz'; 22 | } else if (i % 5 == 0) { 23 | item = 'Buzz'; 24 | } else { 25 | item = String(i); 26 | } 27 | result[i - 1] = item; 28 | } 29 | 30 | return result; 31 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/数学/isPowerOfThree_326.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 326. 3 的幂 3 | * 简单 4 | * https://leetcode.cn/problems/power-of-three/ 5 | * 6 | * 解法:试除法 7 | * 8 | * 时间O(logn) 9 | * 空间O(1) 10 | */ 11 | /** 12 | * @param {number} n 13 | * @return {boolean} 14 | */ 15 | var isPowerOfThree = function (n) { 16 | // 试除法:不断除以3,直到1,如果过程中无法被3整除,说明不是3的幂 17 | while (n != 0 && n % 3 == 0) { 18 | n = Math.floor(n / 3); 19 | } 20 | 21 | return n == 1; 22 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/数学/lastRemaining_剑指_62.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 剑指 Offer 62. 圆圈中最后剩下的数字 3 | * 简单 4 | * https://leetcode.cn/problems/yuan-quan-zhong-zui-hou-sheng-xia-de-shu-zi-lcof/ 5 | * 6 | */ 7 | /** 8 | * @param {number} n 9 | * @param {number} m 10 | * @return {number} 11 | */ 12 | var lastRemaining = function (n, m) { 13 | // 约瑟夫环 14 | let x = 0; 15 | for (let i = 2; i <= n; i++) { 16 | x = (x + m) % i; 17 | } 18 | return x; 19 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/数学/printNumbers_剑指_17.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 剑指 Offer 17. 打印从1到最大的n位数 3 | * 简单 4 | * https://leetcode.cn/problems/da-yin-cong-1dao-zui-da-de-nwei-shu-lcof/ 5 | * 6 | * 解法:数学 7 | */ 8 | /** 9 | * @param {number} n 10 | * @return {number[]} 11 | */ 12 | var printNumbers = function (n) { 13 | // 考规律 时间和空间都是O(n) 14 | // n为1时,最大位数为 10^1 - 1 = 9 15 | // n为2时,最大位数为 10^2 - 1 = 99 16 | // n为3时,最大位数为 10^3 - 1 = 999 17 | // 所以最大位数为10^n - 1 18 | 19 | const max = Math.pow(10, n) - 1; 20 | let result = new Array(max); 21 | 22 | for (let i = 0; i < max; i++) { 23 | result[i] = i + 1; 24 | } 25 | 26 | return result; 27 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/数学/titleToNumber_171.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 171. Excel 表列序号 3 | * 简单 4 | * https://leetcode.cn/problems/excel-sheet-column-number/ 5 | * 6 | * 解法:数学 7 | */ 8 | /** 9 | * @param {string} columnTitle 10 | * @return {number} 11 | */ 12 | var titleToNumber = function (columnTitle) { 13 | let ans = 0; 14 | 15 | for (let i = 0; i < columnTitle.length; i++) { 16 | let num = columnTitle[i].charCodeAt() - 'A'.charCodeAt() + 1; 17 | ans = ans * 26 + num; 18 | } 19 | 20 | return ans; 21 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/数组/findDisappearedNumbers_448.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 448. 找到所有数组中消失的数字 3 | * 4 | * 简单 5 | * https://leetcode-cn.com/problems/find-all-numbers-disappeared-in-an-array/ 6 | * 7 | * 时间复杂度O(n) 8 | * 空间复杂度O(1) 9 | * 10 | * @param {number[]} nums 11 | * @return {number[]} 12 | */ 13 | var findDisappearedNumbers = function (nums) { 14 | let n = nums.length; 15 | 16 | // nums的元素为下标,把对应元素变为负 17 | for (let i = 0; i < n; i++) { 18 | const newIndex = Math.abs(nums[i]) - 1; 19 | if (nums[newIndex] > 0) { 20 | nums[newIndex] = nums[newIndex] * -1; 21 | } 22 | } 23 | 24 | console.log(nums); 25 | 26 | // 找到不为负数的元素,说明nums中没有元素等于此下标 27 | let result = []; 28 | for (let i = 1; i <= n; i++) { 29 | if (nums[i - 1] > 0) { 30 | result.push(i); 31 | } 32 | } 33 | 34 | return result; 35 | }; 36 | const nums = [4, 3, 2, 7, 8, 2, 3, 1]; 37 | console.log(findDisappearedNumbers(nums)); // [5, 6] 38 | -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/数组/findDuplicates_442.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 442. 数组中重复的数据 3 | * 4 | * https://leetcode-cn.com/problems/find-all-duplicates-in-an-array/ 5 | * 给定一个整数数组 a,其中1 ≤ a[i] ≤ n (n为数组长度), 其中有些元素出现两次而其他元素出现一次。 6 | * 7 | * 找到所有出现两次的元素。 8 | * 9 | * 你可以不用到任何额外空间并在O(n)时间复杂度内解决这个问题吗 10 | * 11 | */ 12 | /** 13 | * 1. 因为是1到n,且有些元素只出现2次,这里可以使用符号来标记元素是否出现过 14 | * 2. 下标为 i 的元素的符号,代表着值为 i + 1 的元素是否出现过,负号是出现过,正号是没出现过 15 | * 16 | * 空间复杂度是 O(1) 17 | * 时间复杂度是 O(N) 18 | * 19 | * 解法有点类似448 20 | * 21 | * @param {number[]} nums 22 | * @return {number[]} 23 | */ 24 | var findDuplicates = function (nums) { 25 | const res = []; 26 | 27 | for (const num of nums) { 28 | const newIndex = Math.abs(num); 29 | // 该数对应的下标已经是负数,则说明已经重复了 30 | if (nums[newIndex - 1] < 0) { 31 | res.push(newIndex); 32 | // 把该数对应的下标变成负数 33 | } else { 34 | nums[newIndex - 1] *= -1; 35 | } 36 | } 37 | 38 | return res; 39 | }; 40 | 41 | const nums = [4, 3, 2, 7, 8, 2, 3, 1]; 42 | console.log(findDuplicates(nums)); // [2, 3] 43 | -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/数组/findErrorNums_645.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * 645. 错误的集合 4 | * 5 | * 简单 6 | * https://leetcode-cn.com/problems/set-mismatch/ 7 | * 8 | * 时间复杂度O(n) 9 | * 空间复杂度O(1) 10 | * 11 | * 解法类似448题 12 | * 13 | * @param {number[]} nums 14 | * @return {number[]} 15 | */ 16 | var findErrorNums = function(nums) { 17 | let n = nums.length; 18 | let result = []; 19 | 20 | // nums的元素为下标,把对应元素变为负 21 | for (let i = 0; i < n; i++) { 22 | const newIndex = Math.abs(nums[i]) - 1; 23 | if (nums[newIndex] > 0) { 24 | nums[newIndex] = nums[newIndex] * -1; 25 | } else { 26 | // 已经是负,说明该值重复 27 | result.push(Math.abs(nums[i])); 28 | } 29 | } 30 | 31 | // 正数为缺失的元素 32 | for (let i = 0; i < n; i++) { 33 | if (nums[i] > 0) { 34 | result.push(i + 1); 35 | } 36 | } 37 | return result; 38 | }; 39 | 40 | const nums = [2, 2]; 41 | console.log(findErrorNums(nums)); // [2, 1] 42 | -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/数组/generate_118.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 118. 杨辉三角 3 | * 简单 4 | * https://leetcode.cn/problems/pascals-triangle/ 5 | * 6 | * 解法:数组 7 | */ 8 | /** 9 | * @param {number} numRows 10 | * @return {number[][]} 11 | */ 12 | var generate = function (numRows) { 13 | // 杨辉三角的性质:第n行的第i个数 = 第n-1行的第i-1个数和第i个数之和 14 | const ret = []; 15 | 16 | for (let i = 0; i < numRows; i++) { 17 | // 初始化当前行,第1行1个元素,第2行2个元素,... 第n行n个元素 18 | const row = new Array(i + 1).fill(1); 19 | 20 | // 计算第i行每个元素的值 21 | for (let j = 1; j < row.length - 1; j++) { 22 | row[j] = ret[i - 1][j - 1] + ret[i - 1][j]; 23 | } 24 | 25 | ret.push(row); 26 | } 27 | 28 | return ret; 29 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/数组/isStraight_剑指_61.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 剑指 Offer 61. 扑克牌中的顺子 3 | * 简单 4 | * https://leetcode.cn/problems/bu-ke-pai-zhong-de-shun-zi-lcof/ 5 | * 6 | */ 7 | /** 8 | * @param {number[]} nums 9 | * @return {boolean} 10 | */ 11 | var isStraight = function (nums) { 12 | // 5张牌,除去大小王,(最大值 - 最小值 < 5)则表示是顺子 13 | 14 | const puke = new Set(); // 存扑克的牌号,不存大王 15 | 16 | let min = 14; // 因为数组最大值只会是13,所以此处用14表示绝对大的值 17 | let max = 0; 18 | 19 | for (let num of nums) { 20 | if (num == 0) { // 大王跳过 21 | continue; 22 | } 23 | if (puke.has(num)) { // 出现重复的牌,表示不是顺子 24 | return false; 25 | } 26 | 27 | max = Math.max(max, num); 28 | min = Math.min(min, num); 29 | 30 | puke.add(num); 31 | } 32 | 33 | return max - min < 5; 34 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/数组/leastInterval_621.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 621. 任务调度器 3 | * 中等 4 | * https://leetcode.cn/problems/task-scheduler/ 5 | * 6 | * 解法:数组 7 | * 参考 https://leetcode.cn/problems/task-scheduler/solution/python-xiang-jie-by-jalan/ 8 | * 9 | * 时间O(n) 10 | * 空间O(1) 11 | */ 12 | /** 13 | * @param {character[]} tasks 14 | * @param {number} n 15 | * @return {number} 16 | */ 17 | var leastInterval = function (tasks, n) { 18 | let len = tasks.length; 19 | 20 | if (len <= 1) { 21 | return len; 22 | } 23 | 24 | // 记录每个任务出现的次数 25 | let letterNum = {}; 26 | for (let item of tasks) { 27 | if (letterNum[item]) { 28 | letterNum[item] += 1; 29 | } else { 30 | letterNum[item] = 1; 31 | } 32 | } 33 | 34 | // 找出任务次数最多的任务次数maxCount 35 | const countSort = Object.values(letterNum).sort((a, b) => parseInt(b) - parseInt(a)); 36 | let maxCount = countSort[0]; 37 | 38 | // 至少需要的最短时间 39 | let ret = (maxCount - 1) * (n + 1); 40 | 41 | for (let letter in letterNum) { 42 | // 加上出现次数和“最大任务次数相同”的任务个数 43 | if (letterNum[letter] == maxCount) { 44 | ret++; 45 | } 46 | } 47 | 48 | return Math.max(ret, tasks.length); // 在tasks的长度和ret中取较大的一个 49 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/数组/missingNumber_剑指_53_II.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 剑指 Offer 53 - II. 0~n-1中缺失的数字 3 | * 简单 4 | * https://leetcode.cn/problems/que-shi-de-shu-zi-lcof/ 5 | * 6 | * 解法:数组 7 | */ 8 | /** 9 | * @param {number[]} nums 10 | * @return {number} 11 | */ 12 | var missingNumber = function (nums) { 13 | let n = nums.length; 14 | 15 | let arr = Array(n + 1).fill(0); // 下标即nums的数字,值为1 16 | for (let num of nums) { 17 | if (arr[num] == 0) { 18 | arr[num] = 1; 19 | } 20 | } 21 | 22 | for (let i = 0; i <= n; i++) { 23 | if (arr[i] !== 1) { 24 | return i; 25 | } 26 | } 27 | 28 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/数组/rotate_189.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 189. 轮转数组 3 | * 中等 4 | * https://leetcode.cn/problems/rotate-array/ 5 | * 6 | * 解法:数组翻转 7 | * 8 | * 时间O(n) 9 | * 空间O(1) 10 | */ 11 | /** 12 | * @param {number[]} nums 13 | * @param {number} k 14 | * @return {void} Do not return anything, modify nums in-place instead. 15 | */ 16 | var rotate = function (nums, k) { 17 | k %= nums.length; 18 | reverse(nums, 0, nums.length - 1); 19 | reverse(nums, 0, k - 1); 20 | reverse(nums, k, nums.length - 1); 21 | }; 22 | 23 | const reverse = (nums, start, end) => { 24 | while (start < end) { 25 | const temp = nums[start]; 26 | nums[start] = nums[end]; 27 | nums[end] = temp; 28 | start += 1; 29 | end -= 1; 30 | } 31 | } -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/栈/MinStack_155.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 155. 最小栈 3 | * 中等 4 | * https://leetcode.cn/problems/min-stack/ 5 | * 6 | * 解法:栈 7 | */ 8 | /** 9 | * 10 | */ 11 | var MinStack = function () { 12 | this.stack = []; 13 | this.minStack = [Infinity]; // 存栈已有数值的最小值 14 | }; 15 | 16 | /** 17 | * @param {number} val 18 | * @return {void} 19 | */ 20 | MinStack.prototype.push = function (val) { 21 | this.stack.push(val); 22 | const premin = this.minStack[this.minStack.length - 1]; 23 | // 入栈后,计算当前栈中的最小值curmin 24 | const curmin = Math.min(premin, val); 25 | this.minStack.push(curmin); 26 | }; 27 | 28 | /** 29 | * @return {void} 30 | */ 31 | MinStack.prototype.pop = function () { 32 | this.stack.pop(); 33 | this.minStack.pop(); 34 | }; 35 | 36 | /** 37 | * @return {number} 38 | */ 39 | MinStack.prototype.top = function () { 40 | return this.stack[this.stack.length - 1]; 41 | }; 42 | 43 | /** 44 | * @return {number} 45 | */ 46 | MinStack.prototype.getMin = function () { 47 | return this.minStack[this.minStack.length - 1]; 48 | }; 49 | 50 | /** 51 | * Your MinStack object will be instantiated and called as such: 52 | * var obj = new MinStack() 53 | * obj.push(val) 54 | * obj.pop() 55 | * var param_3 = obj.top() 56 | * var param_4 = obj.getMin() 57 | */ -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/栈/asteroidCollision_剑指II_37.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 剑指 Offer II 037. 小行星碰撞 3 | * 中等 4 | * https://leetcode.cn/problems/XagZNi/ 5 | * 6 | * 解法:栈 7 | */ 8 | /** 9 | * @param {number[]} asteroids 10 | * @return {number[]} 11 | */ 12 | var asteroidCollision = function (asteroids) { 13 | // 栈 14 | let stack = []; 15 | 16 | for (let num of asteroids) { 17 | // 只有当前元素小于0,且栈顶元素大于0时,才会相撞 18 | if (num < 0) { 19 | // 栈顶元素小于当前元素,栈顶元素会爆炸,所以栈顶出栈 20 | while (stack.length > 0 && stack[stack.length - 1] > 0 && stack[stack.length - 1] < Math.abs(num)) { 21 | stack.pop(); 22 | } 23 | 24 | // 栈顶跟新元素相等则两个一起爆炸,栈顶大于新元素就是新元素自己爆炸,当前元素需要爆炸,即不入栈 25 | if (stack.length > 0 && stack[stack.length - 1] >= Math.abs(num)) { 26 | // 两个元素相等,两个都爆炸 27 | if (stack[stack.length - 1] == Math.abs(num)) { 28 | stack.pop(); 29 | } 30 | // 跳过for单次循环,则不执行下面入栈的逻辑,表示当前元素爆炸 31 | continue; 32 | } 33 | 34 | stack.push(num); 35 | } else { 36 | stack.push(num); 37 | } 38 | 39 | } 40 | 41 | return stack; 42 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/栈/backspaceCompare_844.js: -------------------------------------------------------------------------------- 1 | /** 2 | * leetcode 844. 比较含退格的字符串 3 | * 简单 4 | * https://leetcode-cn.com/problems/backspace-string-compare/ 5 | * 6 | * @param {*} S 7 | * @param {*} T 8 | */ 9 | /** 10 | * 给定 S 和 T 两个字符串,当它们分别被输入到空白的文本编辑器后, 11 | * 判断二者是否相等,并返回结果。 # 代表退格字符。 12 | * 注意:如果对空文本输入退格字符,文本继续为空。 13 | * 14 | * 输入:S = "ab#c", T = "ad#c" 15 | * 输出:true 16 | * 17 | * 解法一:使用栈存储每次输入的字符 18 | * 时间复杂度O(n+m) 19 | * 空间复杂度O(n+m) 20 | * 21 | * n和m分别是S和T的长度 22 | * 23 | * @param {*} S 24 | * @param {*} T 25 | */ 26 | const backspaceCompare = function(S, T) { 27 | if (S === T) { 28 | return true; 29 | } 30 | 31 | return deal(S) === deal(T) 32 | }; 33 | 34 | /** 35 | * 使用栈存储每次输入的字符,遇到#则出栈 36 | * @param {*} 37 | */ 38 | function deal(s) { 39 | const arr = s.split(''); 40 | let result = []; 41 | 42 | arr.forEach(val => { 43 | if (val === '#') { 44 | result.pop(); 45 | } else { 46 | result.push(val); 47 | } 48 | }); 49 | return result.join(''); 50 | } 51 | 52 | console.log(backspaceCompare('dc', 'dc')); 53 | -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/栈/smallestSubsequence_1081.js: -------------------------------------------------------------------------------- 1 | /** 2 | * leetcode 1081 不同字符的最小子序列 3 | * https://leetcode-cn.com/problems/smallest-subsequence-of-distinct-characters/ 4 | * 5 | * 解法同316题 removeDuplicateLetters 6 | * 7 | * @param {string} text 8 | * @return {string} 9 | */ 10 | var smallestSubsequence = function(text) { 11 | let countMap = {}; 12 | 13 | for (let c of text) { 14 | countMap[c] = countMap[c] 15 | ? countMap[c] + 1 16 | : 1; 17 | } 18 | 19 | let result = []; 20 | for (let s of text) { 21 | // 计数减1 22 | countMap[s] = +countMap[s] - 1; 23 | 24 | if (result.includes(s)) { 25 | continue; 26 | } 27 | 28 | while (result.length && s < result.slice(-1)) { 29 | const last = result.slice(-1); 30 | if (countMap[last] === 0) { 31 | break; 32 | } 33 | result.pop(); 34 | } 35 | 36 | result.push(s); 37 | } 38 | 39 | return result.join(''); 40 | }; 41 | 42 | const text = 'cdadabcc'; 43 | console.log(smallestSubsequence(text)); // adbc 44 | -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/栈/validateStackSequences_剑指_31.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 剑指 Offer 31. 栈的压入、弹出序列 3 | * 中等 4 | * https://leetcode.cn/problems/zhan-de-ya-ru-dan-chu-xu-lie-lcof/ 5 | * 6 | * 解法:栈 7 | */ 8 | /** 9 | * @param {number[]} pushed 10 | * @param {number[]} popped 11 | * @return {boolean} 12 | */ 13 | var validateStackSequences = function (pushed, popped) { 14 | // 借助辅助栈,模拟pushed入栈和poped出栈操作 15 | // 对pushed依次入栈到辅助栈中,每入一次栈后,判断popped[i]当前元素是否和入栈的值相等, 16 | // 相等则从辅助栈中出栈,然后i++,循环结束后,如果helpStack为空,说明为true 17 | const helpStack = []; 18 | let i = 0; 19 | for (let num of pushed) { 20 | helpStack.push(num); 21 | while (helpStack.length && popped[i] === helpStack[helpStack.length - 1]) { 22 | helpStack.pop(); 23 | i += 1; 24 | } 25 | } 26 | 27 | return helpStack.length === 0; 28 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/栈/单调栈/dailyTemperatures_739.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 739. 每日温度 3 | * 中等 4 | * https://leetcode.cn/problems/daily-temperatures/ 5 | * 6 | * 解法:单调栈 7 | * 8 | * 时间O(n) 9 | * 空间O(n) 10 | */ 11 | /** 12 | * @param {number[]} temperatures 13 | * @return {number[]} 14 | */ 15 | var dailyTemperatures = function (temperatures) { 16 | let map = new Map(); // key是temperatures的下标,value是比key对应数和其下一个大的数下标差> 17 | 18 | let stack = []; // 单调递减栈,从栈底到栈顶是从大到小 19 | 20 | for (let i = 0; i < temperatures.length; i++) { 21 | while (stack.length && temperatures[i] > temperatures[stack[stack.length - 1]]) { 22 | const popIndex = stack.pop(); 23 | map.set(popIndex, i - popIndex); 24 | } 25 | stack.push(i); 26 | } 27 | 28 | return temperatures.map((_, index) => { 29 | return map.get(index) || 0; 30 | }); 31 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/栈/单调栈/dailyTemperatures_剑指II_38.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 剑指 Offer II 038. 每日温度 3 | * 中等 4 | * https://leetcode.cn/problems/iIQa4I/ 5 | * 6 | * 解法:单调递减栈 7 | * 8 | * 时间O(n) 9 | * 空间O(n) 10 | */ 11 | /** 12 | * @param {number[]} temperatures 13 | * @return {number[]} 14 | */ 15 | var dailyTemperatures = function (temperatures) { 16 | let map = new Map(); // key是temperatures的下标,value是比key对应数和其下一个大的数下标差> 17 | 18 | let stack = []; // 单调递减栈,从栈底到栈顶是从大到小 19 | 20 | for (let i = 0; i < temperatures.length; i++) { 21 | while (stack.length && temperatures[i] > temperatures[stack[stack.length - 1]]) { 22 | const popIndex = stack.pop(); 23 | map.set(popIndex, i - popIndex); 24 | } 25 | stack.push(i); 26 | } 27 | 28 | return temperatures.map((_, index) => { 29 | return map.get(index) || 0; 30 | }); 31 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/栈/单调栈/largestRectangleArea_84.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 84. 柱状图中最大的矩形 3 | * 困难 4 | * https://leetcode.cn/problems/largest-rectangle-in-histogram/ 5 | * 6 | * 解法:单调递增栈 7 | * 8 | */ 9 | /** 10 | * 11 | * @param {*} heights 12 | * @returns 13 | */ 14 | var largestRectangleArea = function (heights) { 15 | heights.push(0); 16 | const len = heights.length; 17 | 18 | let result = 0; 19 | const indexStack = []; 20 | 21 | for (let i = 0; i < len; i++) { 22 | while (indexStack.length && heights[i] <= heights[indexStack.slice(-1)]) { 23 | let h = heights[indexStack.slice(-1)]; 24 | indexStack.pop(); 25 | 26 | const j = indexStack.length 27 | ? indexStack.slice(-1) 28 | : -1; 29 | 30 | result = Math.max(result, h * (i - j - 1)); 31 | } 32 | 33 | indexStack.push(i); 34 | } 35 | 36 | return result; 37 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/栈/计算器/calculate_227.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 227. 基本计算器 II 3 | * 中等 4 | * https://leetcode.cn/problems/basic-calculator-ii/ 5 | * 6 | * 解法:栈 7 | */ 8 | /** 9 | * @param {string} s 10 | * @return {number} 11 | */ 12 | var calculate = function (s) { 13 | s = s.trim(); 14 | const stack = new Array(); 15 | 16 | let preSign = '+'; 17 | let num = 0; 18 | const n = s.length; 19 | 20 | for (let i = 0; i < n; ++i) { 21 | if (!isNaN(Number(s[i])) && s[i] !== ' ') { 22 | num = num * 10 + s[i].charCodeAt() - '0'.charCodeAt(); 23 | } 24 | if (isNaN(Number(s[i])) || i === n - 1) { 25 | switch (preSign) { 26 | case '+': 27 | stack.push(num); 28 | break; 29 | case '-': 30 | stack.push(-num); 31 | break; 32 | case '*': 33 | stack.push(stack.pop() * num); 34 | break; 35 | default: 36 | stack.push(stack.pop() / num | 0); 37 | } 38 | preSign = s[i]; 39 | num = 0; 40 | } 41 | } 42 | let ans = 0; 43 | while (stack.length) { 44 | ans += stack.pop(); 45 | } 46 | return ans; 47 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/树/connect_116.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 116 填充每个节点的下一个右侧节点指针 3 | * https://leetcode-cn.com/problems/populating-next-right-pointers-in-each-node/ 4 | * 5 | * 中等 6 | */ 7 | 8 | /** 9 | * // Definition for a Node. 10 | * function Node(val, left, right, next) { 11 | * this.val = val === undefined ? null : val; 12 | * this.left = left === undefined ? null : left; 13 | * this.right = right === undefined ? null : right; 14 | * this.next = next === undefined ? null : next; 15 | * }; 16 | */ 17 | 18 | /** 19 | * 递归 20 | * @param {Node} root 21 | * @return {Node} 22 | */ 23 | var connect = function(root) { 24 | if (root == null) { 25 | return null; 26 | } 27 | 28 | connectTwoNode(root.left, root.right); 29 | 30 | return root; 31 | }; 32 | 33 | /** 34 | * 连接两个节点 35 | */ 36 | function connectTwoNode(node1, node2) { 37 | if (node1 == null || node2 == null) { 38 | return; 39 | } 40 | 41 | // 前序遍历位置,将传入的两个节点连接 42 | node1.next = node2; 43 | 44 | // 递归 45 | // 连接相同父节点的两个子节点 46 | connectTwoNode(node1.left, node1.right); 47 | connectTwoNode(node2.left, node2.right); 48 | 49 | // 连接跨越父节点的两个子节点 50 | connectTwoNode(node1.right, node2.left); 51 | } 52 | -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/树/constructMaximumBinaryTree_654.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 654 最大二叉树 3 | * 中等 4 | * https://leetcode-cn.com/problems/maximum-binary-tree/ 5 | */ 6 | function TreeNode(val) { 7 | this.val = val; 8 | this.left = this.right = null; 9 | } 10 | 11 | /** 12 | * 递归 13 | * 14 | * 对每个根节点,只需要找到当前nums中的最大值和对应的索引 15 | * 然后递归调用左右数组构造左右子树即可 16 | * 17 | * @param {number[]} nums 18 | * @return {TreeNode} 19 | */ 20 | var constructMaximumBinaryTree = function (nums) { 21 | let n = nums.length; 22 | if (n === 0) { 23 | return null; 24 | } 25 | return buildTree(nums, 0, n - 1); 26 | }; 27 | 28 | function buildTree(nums, low, high) { 29 | if (low > high) { 30 | return null; 31 | } 32 | 33 | // 最大值的索引 34 | let index = -1; 35 | 36 | // 找出最大值 37 | let maxVal = Number.MIN_SAFE_INTEGER; 38 | for (let i = low; i <= high; i++) { 39 | if (nums[i] > maxVal) { 40 | maxVal = nums[i]; 41 | index = i; 42 | } 43 | } 44 | 45 | let tree = new TreeNode(maxVal); 46 | // 递归构造左右子树 47 | tree.left = buildTree(nums, low, index - 1); 48 | tree.right = buildTree(nums, index + 1, high); 49 | return tree; 50 | } 51 | 52 | const nums = [3, 2, 1, 6, 0, 5]; 53 | console.log(constructMaximumBinaryTree(nums)); 54 | -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/树/convertBST_538.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 538. 把二叉搜索树转换为累加树 3 | * 中等 4 | * https://leetcode.cn/problems/convert-bst-to-greater-tree/ 5 | * 6 | * 解法:反序中序遍历 7 | * 8 | * 参考 https://leetcode.cn/problems/convert-bst-to-greater-tree/solution/shou-hua-tu-jie-zhong-xu-bian-li-fan-xiang-de-by-x/ 9 | */ 10 | /** 11 | * Definition for a binary tree node. 12 | * function TreeNode(val, left, right) { 13 | * this.val = (val===undefined ? 0 : val) 14 | * this.left = (left===undefined ? null : left) 15 | * this.right = (right===undefined ? null : right) 16 | * } 17 | */ 18 | /** 19 | * @param {TreeNode} root 20 | * @return {TreeNode} 21 | */ 22 | var convertBST = function (root) { 23 | let sum = 0; 24 | 25 | const inOrder = (root) => { 26 | if (root == null) { 27 | return; 28 | } 29 | // 因为是反序中序遍历 所以先遍历右节点 30 | inOrder(root.right); 31 | 32 | sum += root.val; 33 | root.val = sum; 34 | 35 | inOrder(root.left); 36 | } 37 | 38 | inOrder(root); 39 | return root; 40 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/树/diameterOfBinaryTree_543.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 543. 二叉树的直径 3 | * 简单 4 | * https://leetcode.cn/problems/diameter-of-binary-tree/ 5 | * 6 | * 7 | * 解法:dfs 8 | * 9 | * 时间O(n) 10 | * 空间O(heigth) 11 | */ 12 | /** 13 | * Definition for a binary tree node. 14 | * function TreeNode(val, left, right) { 15 | * this.val = (val===undefined ? 0 : val) 16 | * this.left = (left===undefined ? null : left) 17 | * this.right = (right===undefined ? null : right) 18 | * } 19 | */ 20 | /** 21 | * @param {TreeNode} root 22 | * @return {number} 23 | */ 24 | var diameterOfBinaryTree = function (root) { 25 | if (!root) { 26 | return 0; 27 | } 28 | 29 | let rst = 1; // 存最长路径的所有节点数量,默认只有根节点,即为1 30 | 31 | // 计算root为根节点时,子树最长时的节点数 32 | this.depth = function (root) { 33 | if (root == null) { // 空节点即为0 34 | return 0; 35 | } 36 | let leftDepth = this.depth(root.left); 37 | let rightDepth = this.depth(root.right); 38 | 39 | rst = Math.max(rst, leftDepth + rightDepth + 1); // 遍历树的过程中计算路径的最大值 40 | 41 | return Math.max(leftDepth, rightDepth) + 1; // 以该节点为根节点时树的深度,加1是因为要算上根节点 42 | } 43 | 44 | depth(root); 45 | 46 | // 路径长度 = 路径节点数 - 1 47 | return rst - 1; 48 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/树/flatten_114.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 114 二叉树展开为链表 3 | * 中等 4 | * https://leetcode-cn.com/problems/flatten-binary-tree-to-linked-list/ 5 | */ 6 | /** 7 | * Definition for a binary tree node. 8 | * function TreeNode(val, left, right) { 9 | * this.val = (val===undefined ? 0 : val) 10 | * this.left = (left===undefined ? null : left) 11 | * this.right = (right===undefined ? null : right) 12 | * } 13 | */ 14 | /** 15 | * 16 | * 递归 17 | * 1. 将root的左子树和右子树拉平 18 | * 2. 将root的右子树接到左子树下方,然后将整个左子树作为右子树 19 | * 20 | * @param {TreeNode} root 21 | * @return {void} Do not return anything, modify root in-place instead. 22 | */ 23 | var flatten = function (root) { 24 | if (root == null) { 25 | return null; 26 | } 27 | 28 | flatten(root.left); 29 | flatten(root.right); 30 | 31 | // 后序遍历位置 32 | // 1、左右子树已经被拉平成一条链表 33 | let left = root.left; 34 | let right = root.right; 35 | 36 | // 2、将左子树作为右子树 37 | root.left = null; 38 | root.right = left; 39 | 40 | // 3、将原先的右子树接到当前右子树的末端 41 | let p = root; 42 | while (p.right != null) { 43 | p = p.right; 44 | } 45 | p.right = right; 46 | }; 47 | -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/树/increasingBST_剑指II_52.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 剑指 Offer II 052. 展平二叉搜索树 3 | * 简单 4 | * https://leetcode.cn/problems/NYBBNL/ 5 | * 6 | * 解法:中序遍历 7 | */ 8 | /** 9 | * Definition for a binary tree node. 10 | * function TreeNode(val, left, right) { 11 | * this.val = (val===undefined ? 0 : val) 12 | * this.left = (left===undefined ? null : left) 13 | * this.right = (right===undefined ? null : right) 14 | * } 15 | */ 16 | /** 17 | * @param {TreeNode} root 18 | * @return {TreeNode} 19 | */ 20 | var increasingBST = function (root) { 21 | // 先中序遍历,然后再根据中序遍历的结果组装树 22 | if (!root) { 23 | return root; 24 | } 25 | 26 | let inOrderRst = []; // 中序遍历的值 27 | const dfs = (root) => { 28 | if (!root) { 29 | return; 30 | } 31 | 32 | dfs(root.left); 33 | inOrderRst.push(root.val); 34 | dfs(root.right); 35 | } 36 | 37 | dfs(root); 38 | 39 | const dummyNode = new TreeNode(-1); 40 | let currNode = dummyNode; 41 | 42 | for (const value of inOrderRst) { 43 | currNode.right = new TreeNode(value); 44 | currNode = currNode.right; 45 | } 46 | return dummyNode.right; 47 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/树/invertTree_226.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 226. 翻转二叉树 3 | * 简单 4 | * https://leetcode-cn.com/problems/invert-binary-tree/ 5 | */ 6 | function TreeNode(val) { 7 | this.val = val; 8 | this.left = this.right = null; 9 | } 10 | 11 | /** 12 | * 翻转后和原来的数镜像对称 13 | * 14 | * 递归 15 | * 16 | * @param {TreeNode} root 17 | * @return {TreeNode} 18 | */ 19 | var invertTree = function(root) { 20 | if (root == null) { 21 | return null; 22 | } 23 | 24 | // root节交换左右子节点 25 | let temp = root.left; 26 | root.left = root.right; 27 | root.right = temp; 28 | 29 | // 递归 30 | invertTree(root.left); 31 | invertTree(root.right); 32 | 33 | return root; 34 | }; 35 | 36 | -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/树/isBalanced_剑指_55_II.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 剑指 Offer 55 - II. 平衡二叉树 3 | * 简单 4 | * https://leetcode.cn/problems/ping-heng-er-cha-shu-lcof/ 5 | * 6 | * 解法:递归 7 | */ 8 | /** 9 | * Definition for a binary tree node. 10 | * function TreeNode(val) { 11 | * this.val = val; 12 | * this.left = this.right = null; 13 | * } 14 | */ 15 | /** 16 | * @param {TreeNode} root 17 | * @return {boolean} 18 | */ 19 | var isBalanced = function (root) { 20 | // 递归 21 | if (!root) { 22 | return true; 23 | } 24 | 25 | // 当前节点的深度为1 且左右子树都平衡 26 | return Math.abs(height(root.left) - height(root.right)) <= 1 && isBalanced(root.left) && isBalanced(root.right) 27 | }; 28 | 29 | // 求树的高度 30 | const height = (root) => { 31 | if (!root) { 32 | return 0; 33 | } 34 | return Math.max(height(root.left), height(root.right)) + 1; 35 | } -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/树/isSubStructure_剑指_26.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 剑指 Offer 26. 树的子结构 3 | * 中等 4 | * https://leetcode.cn/problems/shu-de-zi-jie-gou-lcof/ 5 | * 6 | * 解法:递归 7 | */ 8 | /** 9 | * Definition for a binary tree node. 10 | * function TreeNode(val) { 11 | * this.val = val; 12 | * this.left = this.right = null; 13 | * } 14 | */ 15 | /** 16 | * @param {TreeNode} A 17 | * @param {TreeNode} B 18 | * @return {boolean} 19 | */ 20 | var isSubStructure = function (A, B) { 21 | // 递归 22 | if (!A || !B) { 23 | return false; 24 | } 25 | 26 | return isSame(A, B) || isSubStructure(A.left, B) || isSubStructure(A.right, B); 27 | }; 28 | 29 | // 判断A是否包含B 30 | const isSame = function (A, B) { 31 | if (B == null) { 32 | return true; 33 | } 34 | 35 | // A遍历完了,B还没遍历完 或 A的值不等于B的值 说明不包含 36 | if (A == null || A.val != B.val) { 37 | return false; 38 | } 39 | 40 | return isSame(A.left, B.left) && isSame(A.right, B.right); 41 | } -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/树/isSymmetric_101.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 101. 对称二叉树 3 | * 简单 4 | * https://leetcode.cn/problems/symmetric-tree/ 5 | * 6 | * 解法:递归 7 | */ 8 | /** 9 | * Definition for a binary tree node. 10 | * function TreeNode(val, left, right) { 11 | * this.val = (val===undefined ? 0 : val) 12 | * this.left = (left===undefined ? null : left) 13 | * this.right = (right===undefined ? null : right) 14 | * } 15 | */ 16 | /** 17 | * @param {TreeNode} root 18 | * @return {boolean} 19 | */ 20 | var isSymmetric = function (root) { 21 | // 递归 22 | return check(root, root); 23 | }; 24 | 25 | function check(root1, root2) { 26 | if (root1 == null && root2 == null) { 27 | return true; 28 | } 29 | 30 | if (root1 == null || root2 == null) { 31 | return false; 32 | } 33 | 34 | return root1.val == root2.val && check(root1.left, root2.right) && check(root1.right, root2.left); 35 | } -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/树/isSymmetric_剑指_28.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 剑指 Offer 28. 对称的二叉树 3 | * 简单 4 | * https://leetcode.cn/problems/dui-cheng-de-er-cha-shu-lcof/ 5 | * 6 | * 解法:递归 7 | */ 8 | /** 9 | * Definition for a binary tree node. 10 | * function TreeNode(val) { 11 | * this.val = val; 12 | * this.left = this.right = null; 13 | * } 14 | */ 15 | /** 16 | * @param {TreeNode} root 17 | * @return {boolean} 18 | */ 19 | var isSymmetric = function (root) { 20 | return check(root, root); 21 | }; 22 | 23 | const check = function (root1, root2) { 24 | // 递归 25 | if (!root1 && !root2) { 26 | return true; 27 | } 28 | if (!root1 || !root2) { 29 | return false; 30 | } 31 | 32 | // 值相等,且左子树等于右子树 33 | return root1.val === root2.val && check(root1.left, root2.right) && check(root1.right, root2.left); 34 | } -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/树/kthLargest_剑指_54.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 剑指 Offer 54. 二叉搜索树的第k大节点 3 | * 简单 4 | * https://leetcode.cn/problems/er-cha-sou-suo-shu-de-di-kda-jie-dian-lcof/ 5 | * 6 | * 解法:dfs 7 | */ 8 | /** 9 | * Definition for a binary tree node. 10 | * function TreeNode(val) { 11 | * this.val = val; 12 | * this.left = this.right = null; 13 | * } 14 | */ 15 | /** 16 | * @param {TreeNode} root 17 | * @param {number} k 18 | * @return {number} 19 | */ 20 | var kthLargest = function (root, k) { 21 | if (!root) { 22 | return null; 23 | } 24 | 25 | let result = undefined; 26 | 27 | // 反向中序遍历 就是从大到小 28 | const dfs = (root) => { 29 | if (!root) { 30 | return; 31 | } 32 | 33 | // 说明已经找到第k大的数了,不需要遍历了 34 | if (result != undefined) { 35 | return; 36 | } 37 | 38 | dfs(root.right); 39 | 40 | k -= 1; 41 | 42 | if (k == 0) { 43 | result = root.val; 44 | } 45 | 46 | dfs(root.left); 47 | } 48 | 49 | dfs(root); 50 | 51 | return result; 52 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/树/kthSmallest_230.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 230. 二叉搜索树中第K小的元素 3 | * 中等 4 | * https://leetcode.cn/problems/kth-smallest-element-in-a-bst/ 5 | * 6 | * 解法:中序遍历 7 | */ 8 | /** 9 | * Definition for a binary tree node. 10 | * function TreeNode(val, left, right) { 11 | * this.val = (val===undefined ? 0 : val) 12 | * this.left = (left===undefined ? null : left) 13 | * this.right = (right===undefined ? null : right) 14 | * } 15 | */ 16 | /** 17 | * @param {TreeNode} root 18 | * @param {number} k 19 | * @return {number} 20 | */ 21 | var kthSmallest = function (root, k) { 22 | let i = 0; 23 | let result; 24 | 25 | const dfs = (root) => { 26 | if (root == null || i == k) { 27 | return; 28 | } 29 | 30 | dfs(root.left); 31 | 32 | i += 1; 33 | 34 | if (i == k) { 35 | result = root.val; 36 | } 37 | 38 | dfs(root.right); 39 | } 40 | 41 | dfs(root); 42 | 43 | return result; 44 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/树/lowestCommonAncestor_236.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 236. 二叉树的最近公共祖先 3 | * 4 | * 中等 5 | * https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-tree/ 6 | * 7 | * 跟235题的区别是这个不是二叉搜索树 8 | */ 9 | /** 10 | * Definition for a binary tree node. 11 | * function TreeNode(val) { 12 | * this.val = val; 13 | * this.left = this.right = null; 14 | * } 15 | */ 16 | /** 17 | * 时间复杂度:O(N),其中 N 是二叉树的节点数 18 | * 空间复杂度:O(N) 19 | * 20 | * @param {TreeNode} root 21 | * @param {TreeNode} p 22 | * @param {TreeNode} q 23 | * @return {TreeNode} 24 | */ 25 | var lowestCommonAncestor = function (root, p, q) { 26 | if (root == null) { 27 | return null; 28 | } 29 | 30 | // 根为其中一个要找的节点重合,那这个就是结果 31 | if (root == p || root == q) { 32 | return root; 33 | } 34 | 35 | // 分别递归查找左子树 和 右子树 36 | let left = lowestCommonAncestor(root.left, p, q); 37 | let right = lowestCommonAncestor(root.right, p, q); 38 | 39 | // 其中一个为空,则p和q都在另一个子树中,在另一个子树中查找 40 | if (left == null) { 41 | return right; 42 | } 43 | if (right == null) { 44 | return left; 45 | } 46 | 47 | // 两边都存在p和q,说明当前节点root为最近公共祖先 48 | return root; 49 | }; 50 | -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/树/maxPathSum_124.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 124. 二叉树中的最大路径和 3 | * 4 | * 困难 5 | * https://leetcode-cn.com/problems/binary-tree-maximum-path-sum/ 6 | */ 7 | /** 8 | * Definition for a binary tree node. 9 | * function TreeNode(val) { 10 | * this.val = val; 11 | * this.left = this.right = null; 12 | * } 13 | */ 14 | /** 15 | * 递归 16 | * @param {TreeNode} root 17 | * @return {number} 18 | */ 19 | function maxPathSum(root) { 20 | let res = Number.MIN_SAFE_INTEGER; 21 | 22 | const getMax = (root) => { 23 | // 递归结束条件 24 | if (root == null) { 25 | return 0; 26 | } 27 | 28 | // 左右子树的和为负数的话,不计算,返回0 29 | const left = Math.max(0, getMax(root.left)); 30 | const right = Math.max(0, getMax(root.right)); 31 | 32 | // “根+左+右” 33 | // 节点的最大路径和取决于该节点的值与该节点的左右子节点的最大贡献值 34 | let sum = root.val + left + right; 35 | 36 | // 更新路径的最大和,因为最大和可以为不经过根节点的子树 37 | res = Math.max(res, sum); 38 | 39 | // 返回节点的最大贡献值 40 | return root.val + Math.max(left, right); 41 | } 42 | 43 | getMax(root); 44 | 45 | return res; 46 | } 47 | -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/树/mergeTrees_617.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 617. 合并二叉树 3 | * 简单 4 | * https://leetcode.cn/problems/merge-two-binary-trees/ 5 | * 6 | * 解法:深度优先搜索 7 | * 8 | */ 9 | /** 10 | * Definition for a binary tree node. 11 | * function TreeNode(val, left, right) { 12 | * this.val = (val===undefined ? 0 : val) 13 | * this.left = (left===undefined ? null : left) 14 | * this.right = (right===undefined ? null : right) 15 | * } 16 | */ 17 | /** 18 | * @param {TreeNode} root1 19 | * @param {TreeNode} root2 20 | * @return {TreeNode} 21 | */ 22 | var mergeTrees = function (root1, root2) { 23 | if (root1 == null) { 24 | return root2; 25 | } 26 | if (root2 == null) { 27 | return root1; 28 | } 29 | 30 | // 构造新的树 31 | let treeNode = {}; 32 | treeNode.val = root1.val + root2.val; 33 | treeNode.left = mergeTrees(root1.left, root2.left); 34 | treeNode.right = mergeTrees(root1.right, root2.right); 35 | 36 | return treeNode; 37 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/树/numTrees_96.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 96. 不同的二叉搜索树 3 | * 中等 4 | * https://leetcode.cn/problems/unique-binary-search-trees/ 5 | * 6 | * 解法:递归 7 | * 参考 https://leetcode.cn/problems/unique-binary-search-trees/solution/a-qiu-javadi-gui-jie-fa-by-emeraki-qi2d/ 8 | * 9 | */ 10 | /** 11 | * @param {number} n 12 | * @return {number} 13 | */ 14 | var numTrees = function (n) { 15 | // 二叉搜索树的中序遍历是递增的 16 | // 题目可以转换为求“1到n”的递增序列[1...n],分别以每个元素为二叉树根节点,能构造出多少个二叉搜索树 17 | 18 | // 以nums = [1,2,3,4,5,6,7]为例,当root根节点是5时,其左边有4个节点(1、2、3、4),右边有2个节点(6、7), 对于左边的4个节点,假设能延伸出n种二叉搜索树子树,对于右边的2个节点,假设能延伸出m种二叉搜索树子树。则以5为root节点时的二叉搜索树总数为 m*n 19 | 20 | // 没有节点或只有1个节点,则只有一个子树 21 | if (n == 0 || n == 1) { 22 | return 1; 23 | } 24 | 25 | let count = 0; 26 | for (let i = 1; i <= n; i++) { 27 | //当用i这个节点当做根节点时 28 | 29 | // 左边有多少种子树 30 | let leftNum = numTrees(i - 1); 31 | 32 | // 右边有多少种子树 33 | let rightNum = numTrees(n - i); 34 | 35 | count = count + leftNum * rightNum; 36 | } 37 | 38 | return count; 39 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/树/pathSum_437.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 437. 路径总和 III 3 | * 中等 4 | * https://leetcode.cn/problems/path-sum-iii/ 5 | * 6 | * 解法:深度优先搜索 7 | * 8 | * 时间O(N^2) 9 | * 空间O(N) 10 | * 11 | */ 12 | /** 13 | * Definition for a binary tree node. 14 | * function TreeNode(val, left, right) { 15 | * this.val = (val===undefined ? 0 : val) 16 | * this.left = (left===undefined ? null : left) 17 | * this.right = (right===undefined ? null : right) 18 | * } 19 | */ 20 | /** 21 | * @param {TreeNode} root 22 | * @param {number} targetSum 23 | * @return {number} 24 | */ 25 | var pathSum = function (root, targetSum) { 26 | if (root == null) { 27 | return 0; 28 | } 29 | 30 | let result = rootSum(root, targetSum); 31 | 32 | // 33 | result += pathSum(root.left, targetSum); 34 | result += pathSum(root.right, targetSum); 35 | 36 | return result; 37 | }; 38 | 39 | // 表示以节点 root 为起点向下且满足路径总和为 targetSum 的路径数目 40 | const rootSum = (root, targetSum) => { 41 | let ret = 0; 42 | 43 | if (root == null) { 44 | return 0; 45 | } 46 | 47 | let val = root.val; 48 | if (val == targetSum) { 49 | ret++; 50 | } 51 | 52 | ret += rootSum(root.left, targetSum - val); 53 | ret += rootSum(root.right, targetSum - val); 54 | return ret; 55 | } -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/树/treeToDoublyList_剑指_36.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 剑指 Offer 36. 二叉搜索树与双向链表 3 | * 中等 4 | * https://leetcode.cn/problems/er-cha-sou-suo-shu-yu-shuang-xiang-lian-biao-lcof/ 5 | * 6 | * 解法:中序遍历 + dfs 7 | */ 8 | /** 9 | * // Definition for a Node. 10 | * function Node(val,left,right) { 11 | * this.val = val; 12 | * this.left = left; 13 | * this.right = right; 14 | * }; 15 | */ 16 | /** 17 | * @param {Node} root 18 | * @return {Node} 19 | */ 20 | var treeToDoublyList = function (root) { 21 | if (!root) { 22 | return null; 23 | } 24 | 25 | let pre = null; 26 | let head = null; // 双向链表的头节点 27 | 28 | // 在中序遍历处做处理 29 | const dfs = (cur) => { 30 | if (!cur) { 31 | return; 32 | } 33 | 34 | // 左子树 35 | dfs(cur.left); 36 | 37 | // 构建链表 38 | 39 | if (pre != null) { 40 | pre.right = cur; 41 | cur.left = pre; 42 | } else { // pre为空,说明是此时正访问链表头节点 43 | head = cur; 44 | cur.left = pre; 45 | } 46 | 47 | pre = cur; 48 | 49 | // 右子树 50 | dfs(cur.right); 51 | } 52 | 53 | dfs(root); 54 | 55 | // 中序遍历完成后,head指向头节点, pre指向尾节点,修改 head 和 pre 的双向节点引用即可 56 | head.left = pre; 57 | pre.right = head; 58 | 59 | return head; 60 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/树/构造二叉树/sortedArrayToBST_108.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 108. 将有序数组转换为二叉搜索树 3 | * 简单 4 | * https://leetcode.cn/problems/convert-sorted-array-to-binary-search-tree/ 5 | * 6 | * 解法:dfs 7 | */ 8 | /** 9 | * Definition for a binary tree node. 10 | * function TreeNode(val, left, right) { 11 | * this.val = (val===undefined ? 0 : val) 12 | * this.left = (left===undefined ? null : left) 13 | * this.right = (right===undefined ? null : right) 14 | * } 15 | */ 16 | /** 17 | * @param {number[]} nums 18 | * @return {TreeNode} 19 | */ 20 | var sortedArrayToBST = function (nums) { 21 | // 递归 时间O(n) 空间O(logN) 22 | if (!nums || !nums.length) { 23 | return null; 24 | } 25 | 26 | // 树的中序遍历后就是升序数组,可以任选一个节点为根节点进行构造 27 | const dfs = (nums, left, right) => { 28 | if (left > right) { // 节点为空 29 | return null; 30 | } 31 | 32 | // 选中间位置左边的数字为根节点;也可以选择中间位置右边的数字为根节点,或者任意位置的数字为根节点 33 | const mid = Math.floor((left + right) / 2); 34 | const root = new TreeNode(nums[mid]); 35 | root.left = dfs(nums, left, mid - 1); 36 | root.right = dfs(nums, mid + 1, right); 37 | 38 | return root; 39 | } 40 | 41 | return dfs(nums, 0, nums.length - 1); 42 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/树/树的遍历/levelOrder_剑指_32_I.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 剑指 Offer 32 - I. 从上到下打印二叉树 3 | * 中等 4 | * https://leetcode.cn/problems/cong-shang-dao-xia-da-yin-er-cha-shu-lcof/ 5 | * 6 | * 解法:bfs 7 | */ 8 | /** 9 | * Definition for a binary tree node. 10 | * function TreeNode(val) { 11 | * this.val = val; 12 | * this.left = this.right = null; 13 | * } 14 | */ 15 | /** 16 | * @param {TreeNode} root 17 | * @return {number[]} 18 | */ 19 | var levelOrder = function (root) { 20 | // 层序遍历 21 | if (root == null) { 22 | return []; 23 | } 24 | 25 | let result = []; 26 | let queue = [root]; 27 | 28 | while (queue.length) { 29 | let curLevelResult = []; 30 | 31 | for (let i = 0; i < queue.length; i++) { 32 | const root = queue.shift(); 33 | curLevelResult.push(root.val); 34 | 35 | if (root.left) { 36 | queue.push(root.left); 37 | } 38 | if (root.right) { 39 | queue.push(root.right); 40 | } 41 | } 42 | 43 | result = result.concat(curLevelResult); 44 | } 45 | 46 | return result; 47 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/树/树的遍历/levelOrder_剑指_32_II.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 剑指 Offer 32 - II. 从上到下打印二叉树 II 3 | * 简单 4 | * https://leetcode.cn/problems/cong-shang-dao-xia-da-yin-er-cha-shu-ii-lcof/ 5 | * 6 | * 解法:bfs 7 | */ 8 | /** 9 | * Definition for a binary tree node. 10 | * function TreeNode(val) { 11 | * this.val = val; 12 | * this.left = this.right = null; 13 | * } 14 | */ 15 | /** 16 | * @param {TreeNode} root 17 | * @return {number[][]} 18 | */ 19 | var levelOrder = function (root) { 20 | // 层序遍历 21 | if (!root) { 22 | return []; 23 | } 24 | 25 | let result = []; 26 | let queue = [root]; 27 | 28 | while (queue.length) { 29 | let levelSize = queue.length; 30 | 31 | let curLevelRst = []; 32 | 33 | for (let i = 0; i < levelSize; i++) { 34 | const node = queue.shift(); 35 | curLevelRst.push(node.val); 36 | 37 | if (node.left) { 38 | queue.push(node.left); 39 | } 40 | if (node.right) { 41 | queue.push(node.right); 42 | } 43 | } 44 | 45 | result.push(curLevelRst); 46 | } 47 | 48 | return result; 49 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/模拟/generateMatrix_59.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 59. 螺旋矩阵 II 3 | * 中等 4 | * https://leetcode.cn/problems/spiral-matrix-ii/ 5 | * 6 | * 解法:矩阵 + 模拟 7 | */ 8 | /** 9 | * @param {number} n 10 | * @return {number[][]} 11 | */ 12 | var generateMatrix = function (n) { 13 | let l = 0; // 左 14 | let r = n - 1; // 右 15 | let t = 0; // 上 16 | let b = n - 1; // 下 17 | 18 | // n行n列的矩阵 19 | let matrix = Array.from(new Array(n), () => new Array(n)); 20 | 21 | let num = 1; 22 | let endNum = n * n; 23 | 24 | while (num <= endNum) { 25 | for (let i = l; i <= r; i++) { // 从左向右遍历 26 | matrix[t][i] = num++; 27 | } 28 | t++; 29 | 30 | for (let i = t; i <= b; i++) { // 从上向下遍历 31 | matrix[i][r] = num++; 32 | } 33 | r--; 34 | 35 | for (let i = r; i >= l; i--) { // 从右向左遍历 36 | matrix[b][i] = num++; 37 | } 38 | b--; 39 | 40 | for (let i = b; i >= t; i--) { // 从下向上遍历 41 | matrix[i][l] = num++; 42 | } 43 | l++; 44 | } 45 | 46 | return matrix; 47 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/模拟/rotate_48.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 48. 旋转图像 3 | * 中等 4 | * https://leetcode.cn/problems/rotate-image/ 5 | * 6 | * 参考 https://leetcode.cn/problems/rotate-image/solution/48-xuan-zhuan-tu-xiang-fu-zhu-ju-zhen-yu-jobi/ 7 | * 8 | * 时间O(N^2) 9 | * 空间O(1) 10 | */ 11 | /** 12 | * @param {number[][]} matrix 13 | * @return {void} Do not return anything, modify matrix in-place instead. 14 | */ 15 | var rotate = function (matrix) { 16 | let n = matrix.length; 17 | 18 | // 由外向内,一圈圈旋转 19 | // 一轮可以完成矩阵 4 个元素的旋转。因而,只要分别以矩阵左上角 1/4 的各元素为起始点执行以上旋转操作,即可完整实现矩阵旋转 20 | for (let i = 0; i < Math.floor(n / 2); i++) { 21 | for (let j = 0; j < Math.floor((n + 1) / 2); j++) { 22 | let tmp = matrix[i][j]; // 比如例1的 1 23 | 24 | matrix[i][j] = matrix[n - 1 - j][i]; // 比如例1的 7换到1的位置 25 | matrix[n - 1 - j][i] = matrix[n - 1 - i][n - 1 - j]; // 比如例1的 9换到7的位置 26 | matrix[n - 1 - i][n - 1 - j] = matrix[j][n - 1 - i]; // 比如例1的 3换到9的位置 27 | 28 | matrix[j][n - 1 - i] = tmp; // 比如例1的 1换到3的位置 29 | } 30 | } 31 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/模拟/spiralOrder_54.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 54. 螺旋矩阵 3 | * 中等 4 | * https://leetcode.cn/problems/spiral-matrix/ 5 | * 6 | * 解法:矩阵 + 模拟 7 | */ 8 | /** 9 | * @param {number[][]} matrix 10 | * @return {number[]} 11 | */ 12 | var spiralOrder = function (matrix) { 13 | if (matrix.length == 0) { 14 | return []; 15 | } 16 | 17 | let l = 0; // 左 18 | let r = matrix[0].length - 1; // 右 19 | let t = 0; // 上 20 | let b = matrix.length - 1; // 下 21 | 22 | let x = 0; 23 | let res = new Array((r + 1) * (b + 1)); 24 | 25 | while (true) { 26 | for (let i = l; i <= r; i++) { 27 | res[x++] = matrix[t][i]; // 从左向右遍历 28 | } 29 | if (++t > b) { 30 | break; 31 | } 32 | 33 | for (let i = t; i <= b; i++) { 34 | res[x++] = matrix[i][r]; // 从上向下遍历 35 | } 36 | if (l > --r) { 37 | break; 38 | } 39 | 40 | for (let i = r; i >= l; i--) { 41 | res[x++] = matrix[b][i]; // 从右向左遍历 42 | } 43 | if (t > --b) { 44 | break; 45 | } 46 | 47 | for (let i = b; i >= t; i--) { 48 | res[x++] = matrix[i][l]; // 从下向上遍历 49 | } 50 | if (++l > r) { 51 | break; 52 | } 53 | } 54 | 55 | return res; 56 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/计数法/longestValidParentheses_32.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 32. 最长有效括号 3 | * 困难 4 | * 5 | * https://leetcode.cn/problems/longest-valid-parentheses/ 6 | * 7 | * 解法:计数法 8 | * 参考官方题解 9 | * 10 | * 时间 O(n) 11 | * 空间 O(1) 12 | */ 13 | /** 14 | * 15 | * @param {string} s 16 | * @return {number} 17 | */ 18 | var longestValidParentheses = function (s) { 19 | let left = 0; 20 | let right = 0; 21 | 22 | let maxRst = 0; 23 | 24 | // 从左向右遍历 25 | for (let i = 0; i < s.length; i++) { 26 | if (s[i] === '(') { 27 | left++; 28 | } else { 29 | right++; 30 | } 31 | // 左右括号的个数相等,计算长度 32 | if (left === right) { 33 | maxRst = Math.max(maxRst, 2 * left); 34 | } else if (right > left) { // 右边括号多于左括号,则重新从下一个字符开始计算 35 | left = right = 0; 36 | } 37 | } 38 | 39 | left = right = 0; 40 | // 从右向左遍历 41 | for (let i = s.length - 1; i >= 0; i--) { 42 | if (s[i] === '(') { 43 | left++; 44 | } else { 45 | right++; 46 | } 47 | 48 | if (left === right) { 49 | maxRst = Math.max(maxRst, 2 * left); 50 | } else if (left > right) { 51 | left = right = 0; 52 | } 53 | } 54 | 55 | return maxRst; 56 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/计数法/minAddToMakeValid_921.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 921. 使括号有效的最少添加 3 | * 4 | * 中等 5 | * https://leetcode-cn.com/problems/minimum-add-to-make-parentheses-valid/ 6 | * 7 | * 计数法 8 | * 9 | * 思路: 10 | * 以左括号为基准,通过维护对右括号的需求数,来计算最小的插入次数 11 | * 12 | * @param {*} S 13 | */ 14 | function minAddToMakeValid(S) { 15 | // 记录左括号插入次数 16 | let leftCount = 0; 17 | // 记录右括号的需求量 18 | let needRight = 0; 19 | 20 | for (let i = 0; i < S.length; i++) { 21 | if (S[i] === '(') { 22 | // 对右括号的需求 + 1 23 | needRight++; 24 | } 25 | if (S[i] === ')') { 26 | // 对右括号的需求 - 1 27 | needRight--; 28 | 29 | // 等于-1表示右括号太多,需要插入左括号 30 | if (needRight === -1) { 31 | needRight = 0; 32 | leftCount++; 33 | } 34 | } 35 | } 36 | 37 | // 左括号插入次数 + 右括号的需求 38 | return leftCount + needRight; 39 | } 40 | const S = '))('; 41 | console.log(minAddToMakeValid(S)); // 3 42 | -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/贪心算法/canCompleteCircuit_134.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 134. 加油站 3 | * 中等 4 | * https://leetcode.cn/problems/gas-station/ 5 | * 6 | * 解法:贪心 + 数组 7 | * 8 | * 时间O(n) 9 | * 空间O(1) 10 | */ 11 | /** 12 | * @param {number[]} gas 13 | * @param {number[]} cost 14 | * @return {number} 15 | */ 16 | var canCompleteCircuit = function (gas, cost) { 17 | // 思路: 18 | // 1、首先判断总油量是否小于总油耗,如果是则肯定不能走一圈。如果否,那肯定能跑一圈 19 | // 2、循环数组,从第一个站开始,计算到下一站剩余的油量,如果油量为负了,就以这个站为起点从新计算 20 | // (如果到达某一个点为负,说明起点到这个点中间的所有站点都不能到达该点) 21 | // 22 | let n = gas.length; 23 | let gasSum = 0; 24 | let costSum = 0; 25 | 26 | for (let i = 0; i < n; i++) { 27 | gasSum += gas[i]; 28 | costSum += cost[i]; 29 | } 30 | 31 | if (gasSum < costSum) { // 总油量<总耗油量肯定不能达到 32 | return -1; 33 | } 34 | 35 | // 总油量大于等于总耗油量,一定可以达到 36 | 37 | let start = 0; // 从start索引的站点开始 38 | let curRemainGas = 0; // 当前剩余的油量 39 | 40 | for (let i = start; i < n; i++) { 41 | // 到达下一站剩余的油量,如果小于0,说明起点到下一站中间的所有站点都不能到达该点 42 | curRemainGas = curRemainGas - cost[i] + gas[i]; 43 | if (curRemainGas < 0) { 44 | start = i + 1; // 说明此时从start开始,无法到达第i+1站,则重新以i+1站为新的起点 45 | curRemainGas = 0; 46 | } 47 | } 48 | 49 | return start; 50 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/贪心算法/canJump_55.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 55. 跳跃游戏 3 | * 中等 4 | * https://leetcode-cn.com/problems/jump-game/ 5 | */ 6 | /** 7 | * 贪心算法 8 | * 9 | * 时间复杂度O(n) 10 | * 空间复杂度O(1) 11 | * 12 | * @param {number[]} nums 13 | * @return {boolean} 14 | */ 15 | var canJump = function (nums) { 16 | let n = nums.length; 17 | if (n === 0) { 18 | return false; 19 | } 20 | 21 | let farthest = 0; 22 | // 计算当前位置能跳的最远距离 23 | for (let i = 0; i < n - 1; i++) { 24 | // i + nums[i] 表示当前在i位置的距离,加上在该位置可以跳的距离 25 | farthest = Math.max(farthest, i + nums[i]); 26 | // 该位置的最远距离 <= i, 说明nums[i]可能是0,永远无法跳到最后 27 | if (farthest <= i) { 28 | return false; 29 | } 30 | } 31 | 32 | return true; 33 | }; 34 | 35 | const nums = [3, 2, 1, 0, 4]; 36 | console.log(canJump(nums)); 37 | -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/贪心算法/cuttingRope_剑指_14_II.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 剑指 Offer 14- II. 剪绳子 II 3 | * 中等 4 | * https://leetcode.cn/problems/jian-sheng-zi-ii-lcof/ 5 | * 6 | * 解法:贪心 7 | * 参考 https://leetcode.cn/problems/jian-sheng-zi-ii-lcof/solution/jian-zhi-offer-14-ii-jian-sheng-zi-iihua-r5op/ 8 | * 9 | * 时间O(n) 10 | * 空间O(1) 11 | */ 12 | /** 13 | * @param {number} n 14 | * @return {number} 15 | */ 16 | var cuttingRope = function (n) { 17 | // 贪心算法 不同于https://leetcode.cn/problems/jian-sheng-zi-lcof/ 18 | 19 | if (n == 1) { 20 | return 0; 21 | } 22 | if (n == 2) { 23 | return 1; 24 | } 25 | if (n == 3) { 26 | return 2; 27 | } 28 | 29 | let res = 1; 30 | while (n > 4) { // 分成尽可能多的长度为3的小段 31 | res = res * 3 % 1000000007; 32 | n = n - 3; 33 | } 34 | 35 | // 最后剩下的n不够3,可能是2或者1,所以还需要相乘 36 | return res * n % 1000000007; 37 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/贪心算法/findMinArrowShots_452.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 452. 用最少数量的箭引爆气球 3 | * https://leetcode-cn.com/problems/minimum-number-of-arrows-to-burst-balloons/ 4 | * 5 | * 解法类似435 6 | * 7 | * 输入: 8 | * [[10,16], [2,8], [1,6], [7,12]] 9 | * 输出: 10 | * 2 11 | */ 12 | /** 13 | * 注意:边界相同算重叠了,只需要用一只箭 14 | * 15 | * 时间复杂度: 如果忽略sort排序,时间复杂度为O(n) n为points的长度 16 | * 空间复杂度O(1) 17 | * 18 | * @param {number[][]} points 19 | * @return {number} 20 | */ 21 | var findMinArrowShots = function (points) { 22 | let n = points.length; 23 | if (n === 0) { 24 | return 0; 25 | } 26 | 27 | // 根据区间的最后一个元素排序 28 | points.sort(function (a, b) { 29 | return a[1] - b[1]; 30 | }); 31 | 32 | // 最多不重叠区间数,至少为1 33 | let count = 1; 34 | // 排序后,初始时,第一个区间的最后一个元素即为end 35 | let end = points[0][1]; 36 | for (let point of points) { 37 | let start = point[0]; 38 | // 当前区间跟前一个区间不重叠,增加计数,更新end的值(注意:边界相同算重叠了,只需要用一只箭) 39 | if (start > end) { 40 | count++; 41 | end = point[1]; 42 | } 43 | } 44 | 45 | return count; 46 | }; 47 | 48 | const points = [[10, 16], [2, 8], [1, 6], [7, 12]]; 49 | console.log(findMinArrowShots(points)); 50 | -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/贪心算法/increasingTriplet_334.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 334. 递增的三元子序列 3 | * 中等 4 | * https://leetcode.cn/problems/increasing-triplet-subsequence/ 5 | * 6 | * 解法:贪心 7 | */ 8 | /** 9 | * @param {number[]} nums 10 | * @return {boolean} 11 | */ 12 | var increasingTriplet = function (nums) { 13 | // 贪心 14 | let n = nums.length; 15 | if (n < 3) { 16 | return false; 17 | } 18 | 19 | let first = nums[0]; // 表示递增子序列的第1个数 20 | let second = Number.MAX_VALUE; // 表示递增子序列的第2个数,初始为一个最大的数 21 | 22 | for (let i = 1; i < n; i++) { 23 | const num = nums[i]; 24 | 25 | if (num > second) { // 找到一个递增的子序列 26 | return true; 27 | } else if (num > first) { // 将second更新为num 28 | second = num; 29 | } else { 30 | first = num; 31 | } 32 | } 33 | return false; 34 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/贪心算法/intervalIntersection_986.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 986. 区间列表的交集 3 | * 4 | * 中等 5 | * https://leetcode-cn.com/problems/interval-list-intersections/ 6 | * 7 | * 题目给的区间已经是排好序的 8 | * 9 | * @param {*} A 10 | * @param {*} B 11 | */ 12 | function intervalIntersection(A, B) { 13 | // 双指针 14 | let i = 0; 15 | let j = 0; 16 | 17 | let result = []; 18 | while (i < A.length && j < B.length) { 19 | let a1 = A[i][0]; 20 | let a2 = A[i][1]; 21 | 22 | let b1 = B[j][0]; 23 | let b2 = B[j][1]; 24 | 25 | // 注意点:因为两个区间不存在交集是 b1 > a2 || a1 > b2,所以两个区间存在交集是b2 >= a1 && a2 >= b1 26 | if (b2 >= a1 && a2 >= b1) { 27 | const one = Math.max(a1, b1); 28 | const two = Math.min(a2, b2); 29 | // 注意点:计算出交集 30 | result.push([one, two]); 31 | } 32 | 33 | // 注意点:i和j指针推进 34 | if (b2 < a2) { 35 | j += 1; 36 | } else { 37 | i += 1; 38 | } 39 | } 40 | return result; 41 | } 42 | 43 | const A = [[0, 2], [5, 10], [13, 23], [24, 25]]; 44 | const B = [[1, 5], [8, 12], [15, 24], [25, 26]]; 45 | console.log(intervalIntersection(A, B)); // [ [ 1, 2 ], [ 5, 5 ], [ 8, 10 ], [ 15, 23 ], [ 24, 24 ], [ 25, 25 ] ] 46 | -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/链表/deleteNode_237.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 237. 删除链表中的节点 3 | * 简单 4 | * https://leetcode.cn/problems/delete-node-in-a-linked-list/ 5 | * 6 | */ 7 | /** 8 | * Definition for singly-linked list. 9 | * function ListNode(val) { 10 | * this.val = val; 11 | * this.next = null; 12 | * } 13 | */ 14 | /** 15 | * @param {ListNode} node 16 | * @return {void} Do not return anything, modify node in-place instead. 17 | */ 18 | var deleteNode = function(node) { 19 | node.val = node.next.val; 20 | node.next = node.next.next; 21 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/链表/deleteNode_剑指_18.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 剑指 Offer 18. 删除链表的节点 3 | * 简单 4 | * https://leetcode.cn/problems/shan-chu-lian-biao-de-jie-dian-lcof/ 5 | * 6 | * 解法:链表 7 | */ 8 | /** 9 | * Definition for singly-linked list. 10 | * function ListNode(val) { 11 | * this.val = val; 12 | * this.next = null; 13 | * } 14 | */ 15 | /** 16 | * @param {ListNode} head 17 | * @param {number} val 18 | * @return {ListNode} 19 | */ 20 | var deleteNode = function (head, val) { 21 | // 双指针 时间O(n) 空间O(1) 22 | if (head.val == val) { 23 | return head.next; 24 | } 25 | 26 | let pre = head; 27 | let cur = head.next; 28 | 29 | while (cur !== null) { 30 | if (cur.val == val) { 31 | pre.next = cur.next; 32 | break; 33 | } else { 34 | pre = cur; 35 | cur = cur.next; 36 | } 37 | } 38 | 39 | return head; 40 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/链表/getKthFromEnd_剑指_22.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 剑指 Offer 22. 链表中倒数第k个节点 3 | * 简单 4 | * https://leetcode.cn/problems/lian-biao-zhong-dao-shu-di-kge-jie-dian-lcof/ 5 | * 6 | * 解法:链表 7 | */ 8 | /** 9 | * Definition for singly-linked list. 10 | * function ListNode(val) { 11 | * this.val = val; 12 | * this.next = null; 13 | * } 14 | */ 15 | /** 16 | * @param {ListNode} head 17 | * @param {number} k 18 | * @return {ListNode} 19 | */ 20 | var getKthFromEnd = function (head, k) { 21 | // 时间O(n) 空间O(1) 22 | // 当总节点数为n,输出倒数第k个节点后到链表,即找到 n - k + 1个节点即可 23 | 24 | if (!head) { 25 | return head; 26 | } 27 | 28 | // 计算总节点数 29 | let n = 0; 30 | let dummyHead = head; 31 | while (dummyHead !== null) { 32 | dummyHead = dummyHead.next; 33 | n = n + 1; 34 | } 35 | 36 | let j = n - k; 37 | let p = head; 38 | while (j > 0) { 39 | p = p.next; 40 | j--; 41 | } 42 | 43 | return p; 44 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/链表/oddEvenList_328.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 328. 奇偶链表 3 | * 中等 4 | * https://leetcode.cn/problems/odd-even-linked-list/ 5 | * 6 | * 解法:链表 7 | * 8 | * 时间O(n) 9 | * 空间O(1) 10 | */ 11 | /** 12 | * Definition for singly-linked list. 13 | * function ListNode(val, next) { 14 | * this.val = (val===undefined ? 0 : val) 15 | * this.next = (next===undefined ? null : next) 16 | * } 17 | */ 18 | /** 19 | * @param {ListNode} head 20 | * @return {ListNode} 21 | */ 22 | var oddEvenList = function (head) { 23 | if (!head || !head.next) { 24 | return head; 25 | } 26 | 27 | // 偶数的头,用于最后拼接 28 | let ouHead = head.next; 29 | 30 | let ji = head; 31 | let ou = head.next; 32 | 33 | while (ou !== null && ou.next !== null) { 34 | ji.next = ou.next; 35 | ji = ji.next; 36 | 37 | ou.next = ji.next; 38 | ou = ou.next; 39 | } 40 | 41 | // 奇链表 拼接 偶链表 42 | ji.next = ouHead; 43 | 44 | return head; 45 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/链表/reversePrint_剑指_6.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 剑指 Offer 06. 从尾到头打印链表 3 | * 简单 4 | * https://leetcode.cn/problems/cong-wei-dao-tou-da-yin-lian-biao-lcof/ 5 | * 6 | * 7 | */ 8 | /** 9 | * Definition for singly-linked list. 10 | * function ListNode(val) { 11 | * this.val = val; 12 | * this.next = null; 13 | * } 14 | */ 15 | /** 16 | * @param {ListNode} head 17 | * @return {number[]} 18 | */ 19 | var reversePrint = function (head) { 20 | let list = []; 21 | // 遍历链表,存到数组,反转数组即可 22 | // 时间和空间都是o(n) 23 | let p = head; 24 | while (p) { 25 | list.push(p.val); 26 | p = p.next; 27 | } 28 | 29 | let rst = []; 30 | for (let i = list.length - 1; i >= 0; i--) { 31 | rst.push(list[i]); 32 | } 33 | 34 | return rst; 35 | }; -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/队列/MovingAverage_剑指II_41.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 剑指 Offer II 041. 滑动窗口的平均值 3 | * 简单 4 | * https://leetcode.cn/problems/qIsx9U/ 5 | * 6 | * 解法:队列 7 | */ 8 | /** 9 | * Initialize your data structure here. 10 | * @param {number} size 11 | */ 12 | var MovingAverage = function (size) { 13 | this.list = []; 14 | this.cap = size; 15 | this.sum = 0; 16 | }; 17 | 18 | /** 19 | * @param {number} val 20 | * @return {number} 21 | */ 22 | MovingAverage.prototype.next = function (val) { 23 | if (this.list.length == this.cap) { 24 | const delVal = this.list.shift(); 25 | this.sum -= delVal; 26 | } 27 | 28 | this.list.push(val); 29 | this.sum += val; 30 | return this.sum / this.list.length; 31 | }; 32 | 33 | /** 34 | * Your MovingAverage object will be instantiated and called as such: 35 | * var obj = new MovingAverage(size) 36 | * var param_1 = obj.next(val) 37 | */ -------------------------------------------------------------------------------- /algorithmSolution/leetcodeSolution/队列/RecentCounter_剑指II_42.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 剑指 Offer II 042. 最近请求次数 3 | * 简单 4 | * https://leetcode.cn/problems/H8086Q/ 5 | * 6 | * 解法:队列 7 | */ 8 | /** 9 | * 10 | */ 11 | var RecentCounter = function () { 12 | // 队列 13 | this.queue = []; 14 | }; 15 | 16 | /** 17 | * @param {number} t 18 | * @return {number} 19 | */ 20 | RecentCounter.prototype.ping = function (t) { 21 | // 因为每次请求的时间都比之前的大,所以队列从头到尾是单调递增的 22 | this.queue.push(t); 23 | 24 | // 从队首弹出早于 t-3000 的时间的请求 25 | while (this.queue[0] < t - 3000) { 26 | this.queue.shift(); 27 | } 28 | 29 | return this.queue.length; 30 | }; 31 | 32 | /** 33 | * Your RecentCounter object will be instantiated and called as such: 34 | * var obj = new RecentCounter() 35 | * var param_1 = obj.ping(t) 36 | */ -------------------------------------------------------------------------------- /algorithmSolution/my91algoSolution/19_ twoSum_1.md: -------------------------------------------------------------------------------- 1 | ## 题目 2 | **1. 两数之和** 3 | >简单 4 | 5 | 给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。 6 | 7 | 你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。 8 | 9 | 10 | 示例: 11 | ``` 12 | 给定 nums = [2, 7, 11, 15], target = 9 13 | 14 | 因为 nums[0] + nums[1] = 2 + 7 = 9 15 | 所以返回 [0, 1] 16 | ``` 17 | >来源:力扣(LeetCode) 18 | 链接:https://leetcode-cn.com/problems/two-sum 19 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 20 | 21 | ## 思路:哈希表 22 | 维护一个哈希表,key是数字,value为下标,遍历数组,判断`(目标值-当前数)`的值是否存在哈希表里,存在则返回对应的下标,不存在则把当前数字放入哈希表 23 | 24 | ## 代码 25 | ```javascript 26 | /** 27 | * @param {number[]} nums 28 | * @param {number} target 29 | * @return {number[]} 30 | */ 31 | var twoSum = function(nums, target) { 32 | // 存 数字=>下标 键值对 33 | let hashMap = new Map(); 34 | 35 | for (let i = 0; i < nums.length; i++) { 36 | const another = target - nums[i]; 37 | const isHas = hashMap.has(another); 38 | if (isHas) { 39 | return [hashMap.get(another), i]; 40 | } 41 | hashMap.set(nums[i], i); 42 | } 43 | return []; 44 | }; 45 | ``` 46 | 47 | ## 复杂度 48 | * 时间复杂度 O(N) 49 | * 空间复杂度 O(N) -------------------------------------------------------------------------------- /algorithmSolution/my91algoSolution/32_反转链表系列/1_reverseList_206.md: -------------------------------------------------------------------------------- 1 | ### 题目 2 | **206. 反转链表** 3 | >简单 4 | 5 | 反转一个单链表。 6 | 7 | 示例: 8 | ``` 9 | 输入: 1->2->3->4->5->NULL 10 | 输出: 5->4->3->2->1->NULL 11 | ``` 12 | 进阶: 13 | 你可以迭代或递归地反转链表。你能否用两种方法解决这道题? 14 | 15 | >来源:力扣(LeetCode) 16 | 链接:https://leetcode-cn.com/problems/reverse-linked-list 17 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 18 | 19 | ### 解法:双指针 20 | #### 思路 21 | 双指针 22 | * 定义两个指针:pre在后,cur在前,一次走一步,直到cur为null,即到链表尾部 23 | * 每次让 cur 的 next 指向 pre ,完成一次局部反转 24 | * pre指向cur,cur指向cur的下一个节点,即前进一步 25 | 26 | #### 代码 27 | ```js 28 | /** 29 | * Definition for singly-linked list. 30 | * function ListNode(val) { 31 | * this.val = val; 32 | * this.next = null; 33 | * } 34 | */ 35 | /** 36 | * @param {ListNode} head 37 | * @return {ListNode} 38 | */ 39 | var reverseList = function(head) { 40 | let pre = null; 41 | let cur = head; 42 | while(cur != null) { 43 | let curNext = cur.next; 44 | cur.next = pre; 45 | pre = cur; 46 | cur = curNext; 47 | } 48 | return pre; 49 | }; 50 | ``` 51 | #### 复杂度 52 | * 时间复杂度O(n) 53 | * 空间复杂度O(1) 54 | -------------------------------------------------------------------------------- /algorithmSolution/my91algoSolution/56_mySqrt_69.md: -------------------------------------------------------------------------------- 1 | ## 题目 2 | **69. x 的平方根** 3 | >简单 4 | 5 | 实现 int sqrt(int x) 函数。 6 | 7 | 计算并返回 x 的平方根,其中 x 是非负整数。 8 | 9 | 由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去。 10 | 11 | 示例 1: 12 | ``` 13 | 输入: 4 14 | 输出: 2 15 | ``` 16 | 示例 2: 17 | ``` 18 | 输入: 8 19 | 输出: 2 20 | 说明: 8 的平方根是 2.82842..., 21 |   由于返回类型是整数,小数部分将被舍去。 22 | ``` 23 | >来源:力扣(LeetCode) 24 | 链接:https://leetcode-cn.com/problems/sqrtx 25 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 26 | 27 | ## 解法:二分查找 28 | ### 思路 29 | * 因为x平方根的整数部分k,在满足`k^2 ≤ x`的情况下,k取最大值 30 | * 所以可以二分查找`0~x`区间之间符合的k值 31 | 32 | ### 代码 33 | ```js 34 | /** 35 | * 36 | * @param {number} x 37 | * @return {number} 38 | */ 39 | var mySqrt = function(x) { 40 | let low = 0; 41 | let high = x; 42 | let result = 0; 43 | 44 | while (low <= high) { 45 | let mid = Math.floor(low + (high - low)/2); 46 | if (mid * mid <= x) { 47 | result = mid; 48 | low = mid + 1; 49 | } else { 50 | high = mid - 1; 51 | } 52 | } 53 | 54 | return result; 55 | }; 56 | ``` 57 | 58 | ### 复杂度 59 | * 时间复杂度O(logN) 60 | * 空间复杂度O(1) 61 | -------------------------------------------------------------------------------- /algorithmSolution/my91algoSolution/75_canPartition_416.md: -------------------------------------------------------------------------------- 1 | https://github.com/sinkhaha/dataStructureAndAlgorithm/blob/master/docs/%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92/%E8%83%8C%E5%8C%85%E9%97%AE%E9%A2%98/%E5%88%86%E5%89%B2%E7%AD%89%E5%92%8C%E5%AD%90%E9%9B%86_416_canPartition.md 2 | -------------------------------------------------------------------------------- /algorithmSolution/my91algoSolution/77_coinChange_322.md: -------------------------------------------------------------------------------- 1 | https://github.com/sinkhaha/dataStructureAndAlgorithm/blob/master/docs/%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92/%E8%83%8C%E5%8C%85%E9%97%AE%E9%A2%98/%E9%9B%B6%E9%92%B1%E5%85%91%E6%8D%A2_322_coinChange.md 2 | -------------------------------------------------------------------------------- /algorithmSolution/my91algoSolution/78_change_518.md: -------------------------------------------------------------------------------- 1 | https://github.com/sinkhaha/dataStructureAndAlgorithm/blob/master/docs/%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92/%E8%83%8C%E5%8C%85%E9%97%AE%E9%A2%98/%E9%9B%B6%E9%92%B1%E5%85%91%E6%8D%A2II_518_change.md 2 | -------------------------------------------------------------------------------- /algorithmSolution/my91algoSolution/79_2_ceramic.md: -------------------------------------------------------------------------------- 1 | ## 题目描述(加餐) 2 | >网易2021校招题 3 | 牛牛有一块 `2 * n` 的空白瓷砖,并且有 `1 * 2` 和 `2 * 3` 两种类型的地毯(地毯可以旋转)。现在他想满足以下条件: 4 | 5 | * 瓷砖需要被铺满 6 | * 地毯之间没有重叠 7 | * 地毯不能铺出瓷砖外 8 | 求一共有多少种铺地毯的方案,由于结果可能很大,因此你需要返回结果取模 10007。 9 | 10 | ## 解法:动态规划 11 | ### 思路 12 | 1. `dp[i]`数组 13 | 表示当 n = i 时方案的数量 14 | 15 | 2. 状态转移方程:`dp[i] = dp[i - 1] + dp[i - 2] + dp[i - 3]` 16 | >`当前方案数量` = `先铺长度为1的瓷砖再铺剩下 i - 1长度` + `先铺长度为2的瓷砖再铺剩下 i - 2长度` + `先铺长度为3的瓷砖再铺剩下 i - 3长度` 17 | 18 | ### 代码 19 | ```js 20 | /** 21 | * 22 | * @param {*} n 23 | */ 24 | var ceramic = function (n) { 25 | const dp = new Array(n + 1).fill(0); 26 | dp[0] = 1; 27 | dp[1] = 1; 28 | dp[2] = 2; 29 | 30 | for (let i = 3; i < n + 1; i++) { 31 | dp[i] = (dp[i - 1] + dp[i - 2] + dp[i - 3]) % 10007; 32 | } 33 | 34 | return dp[n] % 10007; 35 | }; 36 | ``` 37 | ### 复杂度 38 | * 时间复杂度:O(n) 39 | * 空间复杂度:O(n) 40 | -------------------------------------------------------------------------------- /algorithmSolution/my91algoSolution/81_countBits_338.md: -------------------------------------------------------------------------------- 1 | ## 题目 2 | **338. 比特位计数** 3 | 4 | > 中等 5 | 6 | 给定一个非负整数 num。对于 0 ≤ i ≤ num 范围中的每个数字 i ,计算其二进制数中的 1 的数目并将它们作为数组返回。 7 | 8 | 示例 1: 9 | ``` 10 | 输入: 2 11 | 输出: [0,1,1] 12 | ``` 13 | 示例 2: 14 | ``` 15 | 输入: 5 16 | 输出: [0,1,1,2,1,2] 17 | ``` 18 | 进阶: 19 | 20 | * 给出时间复杂度为O(n*sizeof(integer))的解答非常容易。但你可以在线性时间O(n)内用一趟扫描做到吗? 21 | * 要求算法的空间复杂度为O(n)。 22 | * 你能进一步完善解法吗?要求在C++或任何其他语言中不使用任何内置函数(如 C++ 中的 __builtin_popcount)来执行此操作。 23 | 24 | >来源:力扣(LeetCode) 25 | 链接:https://leetcode-cn.com/problems/counting-bits 26 | 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 27 | 28 | ## 解法:动态规划 29 | ### 思路 30 | 1. dp数组 31 | `dp[i]`表示数字 i 的1的个数 32 | 2. 状态转移方程 33 | >`dp[i] = dp[i & (i - 1)] + 1` 34 | 35 | `i & (i - 1)`是去除二进制 i 的最后一个1,该数一定比 i 小,因此dp数组里一定已经计算过 `i & (i - 1)` 的1的个数,再加上去掉的这个1,只要是大于0的数至少有一个1,等式一定成立 36 | 37 | 38 | ### 代码 39 | ```js 40 | /** 41 | * 42 | * 43 | * @param {number} num 44 | * @return {number[]} 45 | */ 46 | var countBits = function(num) { 47 | let dp = new Array(num + 1); 48 | 49 | dp[0] = 0; 50 | for (let i = 1; i < num + 1; i++) { 51 | // n中1的个数 = (n & n-1)中1的个数 + 1 52 | // 例如 6中1的个数 = 4中1的个数 + 1 53 | dp[i] = dp[i & (i - 1)] + 1 54 | } 55 | return dp; 56 | }; 57 | ``` 58 | ### 复杂度 59 | * 时间复杂度:O(n) 60 | * 空间复杂度:O(n) 61 | -------------------------------------------------------------------------------- /algorithmSolution/my91algoSolution/test.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sinkhaha/dataStructureAndAlgorithm/ee4575b8d1cfec295e6f0b7bfa447736b955a382/algorithmSolution/my91algoSolution/test.js -------------------------------------------------------------------------------- /dataStructure/README.md: -------------------------------------------------------------------------------- 1 | ## 数据结构 2 | 3 | -------------------------------------------------------------------------------- /dataStructure/数组实现栈.js: -------------------------------------------------------------------------------- 1 | // 数组实现栈 2 | class Stack { 3 | constructor() { 4 | this.array = []; 5 | } 6 | // 入栈 时间复杂度都是 O(1) 空间复杂度是 O(1) 7 | push(element) { 8 | this.array.push(element); 9 | return element; 10 | } 11 | // 出栈 时间复杂度都是 O(1) 空间复杂度是 O(1) 12 | pop() { 13 | return this.array.pop(); 14 | } 15 | // 是否为空 16 | isEmpty() { 17 | return this.array.length === 0; 18 | } 19 | // 获取大小 20 | getSize() { 21 | return this.array.length; 22 | } 23 | // 获取第一个元素 24 | getFirst() { 25 | return this.array[0]; 26 | } 27 | // 获取最后一个元素 28 | getLast() { 29 | return this.array[this.array.length - 1]; 30 | } 31 | // 输出数据 32 | getDatas() { 33 | let queueString = ''; 34 | for (let i = 0; i < this.array.length; i++) { 35 | queueString += this.array[i] + ' '; 36 | } 37 | return queueString; 38 | } 39 | } 40 | 41 | const stack = new Stack(); 42 | stack.push('5'); 43 | stack.push('4'); 44 | stack.push('6'); 45 | stack.push('7'); 46 | console.log(stack.getDatas()); 47 | 48 | console.log(stack.pop()); 49 | console.log(stack.getDatas()); 50 | -------------------------------------------------------------------------------- /dataStructure/数组实现队列.js: -------------------------------------------------------------------------------- 1 | // 数组实现队列 2 | class MyQueue { 3 | constructor() { 4 | this.array = []; 5 | } 6 | // 入队 7 | enQueue(element) { 8 | this.array.push(element); 9 | return element; 10 | } 11 | // 出队 12 | deQueue() { 13 | return this.array.shift(); 14 | } 15 | // 是否为空 16 | isEmpty() { 17 | return this.array.length === 0; 18 | } 19 | // 获取第一个元素 20 | getFirst() { 21 | return this.array[0]; 22 | } 23 | // 获取最后一个元素 24 | getLast() { 25 | return this.array[this.array.length - 1]; 26 | } 27 | // 输出数据 28 | getDatas() { 29 | let queueString = ''; 30 | for (let i = 0; i < this.array.length; i++) { 31 | queueString += this.array[i] + ' '; 32 | } 33 | return queueString; 34 | } 35 | } 36 | 37 | const myQueue = new MyQueue(); 38 | myQueue.enQueue('3'); 39 | myQueue.enQueue('6'); 40 | myQueue.enQueue('7'); 41 | myQueue.enQueue('5'); 42 | console.log(myQueue.getDatas()); 43 | myQueue.deQueue('6'); 44 | console.log(myQueue.getDatas()); 45 | console.log(myQueue.isEmpty()); 46 | console.log(myQueue.getFirst()); 47 | console.log(myQueue.getLast()); 48 | -------------------------------------------------------------------------------- /dataStructure/链表实现队列.js: -------------------------------------------------------------------------------- 1 | // 基于链表实现队列 2 | class Node { 3 | constructor(element) { 4 | this.element = element; 5 | this.next = null; 6 | } 7 | } 8 | 9 | class QueueBasedOnLinkedList { 10 | constructor() { 11 | // 维护两个指针 12 | this.head = null; 13 | this.tail = null; 14 | } 15 | 16 | /** 17 | * 入队 18 | */ 19 | enqueue(value) { 20 | if (this.head === null) { 21 | this.head = new Node(value); 22 | this.tail = this.head; 23 | } else { 24 | this.tail.next = new Node(value); 25 | this.tail = this.tail.next; 26 | } 27 | } 28 | 29 | /** 30 | * 出队 31 | * @returns 32 | */ 33 | dequeue() { 34 | if (this.head === null) { 35 | return -1; 36 | } 37 | const value = this.head.element; 38 | this.head = this.head.next; 39 | return value; 40 | 41 | } 42 | } 43 | 44 | // 测试 45 | const newQueue = new QueueBasedOnLinkedList(); 46 | // 插入元素 47 | newQueue.enqueue(1); 48 | newQueue.enqueue(2); 49 | newQueue.enqueue(3); 50 | 51 | // 获取元素 52 | let res = 0; 53 | console.log('-------出队dequeue------'); 54 | while (res !== -1) { 55 | res = newQueue.dequeue(); 56 | console.log(res); 57 | } -------------------------------------------------------------------------------- /docs/动态规划/子序列-子串-回文/子序列问题模板.md: -------------------------------------------------------------------------------- 1 | ## 解法1:一维dp数组 2 | 3 | ```javascript 4 | let dp = new Array(n); 5 | 6 | for (let i = 0; i < n; i++) { 7 | for (let j = 0; j < i; j++) { 8 | dp[i] = 最值(dp[i], dp[j] + 1) 9 | } 10 | } 11 | 12 | ``` 13 | 如 `300. 最长上升子序列` 题 14 | >dp数组dp[i]表示:以`nums[i]`这个数为结尾的最长子序列的长度 (即从头到第`i`个元素的最长序列长度),所以所求的最终结果就是dp数组中的最大值 15 | 16 | 17 | ## 解法2: 二维dp数组 18 | ```javascript 19 | // 初始化n行n列的二维数组 20 | let dp = Array.from(Array(n), () => Array(n).fill(0)); 21 | // 做选择,遍历二维数组 22 | for (let i = 0; i < n; i++) { 23 | for (let j = 0; j < n; j++) { 24 | if (arr[i] == arr[j]) { 25 | dp[i][j] = dp[i][j] + ... 26 | } 27 | else { 28 | dp[i][j] = 最值(...) 29 | } 30 | } 31 | } 32 | ``` 33 | 如 `1143. 最长公共子序列` 题 34 | 35 | >dp数组`dp[i][j]`表示:对于 `text1[1..i]` 和 `text2[1..j]`,它们的 LCS ⻓度是`dp[i][j]`,`dp[i][j]`即为所求结果 36 | 37 | -------------------------------------------------------------------------------- /sort/bubbleSort.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 冒泡排序 3 | * 4 | * 时间复杂度: 最好O(n)、平均O(n^2)、最差O(n^2) 5 | * 空间复杂度: O(1) 6 | * 7 | * 稳定排序/原地排序 8 | * 9 | * @param {Array} arr 10 | * @returns {Array} 11 | */ 12 | function bubbleSort(arr) { 13 | const len = arr.length; 14 | 15 | for (let i = 0; i < len; i++) { 16 | // 引入后面序列是否有序标志 17 | let isSorted = true; 18 | 19 | for (let j = 0; j < len - i; j++) { 20 | if (arr[j] > arr[j + 1]) { 21 | // 交换 22 | let temp = arr[j]; 23 | arr[j] = arr[j + 1]; 24 | arr[j + 1] = temp; 25 | // js交换两个数也可以用以下简洁语句 26 | // [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]]; 27 | 28 | // 有数据交换,说明后面序列无序 29 | isSorted = false; 30 | } 31 | } 32 | // 后面序列已是有序,则不执行后面的循环 33 | if (isSorted) { 34 | break; 35 | } 36 | } 37 | return arr; 38 | } 39 | 40 | const arr = [5, 3, 4, 2, 32, 15, 2, 45, 15, 57]; 41 | console.log(bubbleSort(arr)); 42 | 43 | -------------------------------------------------------------------------------- /sort/directInsertSort.js: -------------------------------------------------------------------------------- 1 | /** 2 | * (直接)插入排序 3 | * 4 | * 时间复杂度: 最好O(n)、平均O(n^2)、最差O(n^2) 5 | * 空间复杂度: O(1) 6 | * 7 | * 稳定排序/原地排序 8 | * 9 | * 思路: 10 | * 外层循环:遍历待比较的所有数组元素arr[i] 11 | * 内层循环:将本轮选择的元素与已经排好序的元素依次相比较,如果选择的元素比已排序的元素小,则交换,直到全部元素都比较完 12 | * 13 | * @param {Array} arr 14 | * @returns {Array} 15 | */ 16 | function directInsertSort(arr) { 17 | if (arr.length <= 1) { 18 | return arr; 19 | } 20 | 21 | // i=0时第一个元素默认是有序区,所以从下标1开始遍历 22 | for (let i = 1; i < arr.length; i++) { 23 | // 需要插入有序区的当前值 24 | let needInsertValue = arr[i]; 25 | 26 | // 在有序区"从后往前"查找当前值要插入的位置 27 | let j = i - 1; 28 | for (; j >= 0; j--) { 29 | // 要插入的元素小于前面的元素 30 | // 则把前面的元素往后移动,把位置空出来给要插入的元素 31 | if (needInsertValue < arr[j]) { 32 | arr[j + 1] = arr[j]; 33 | } else { 34 | break; 35 | } 36 | } 37 | 38 | // 把元素插入有序区 39 | arr[j + 1] = needInsertValue; 40 | } 41 | 42 | return arr; 43 | } 44 | 45 | const arr = [2, 5, 13, 4, 13, 45, 67, 34, 80, 1]; 46 | console.log(directInsertSort(arr)); 47 | -------------------------------------------------------------------------------- /sort/quickSort.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 快速排序 3 | * 4 | * 时间复杂度:最好 O(nlog2n)、平均O(nlog2n)、最坏O(n^2) 5 | * 空间复杂度:O(nlog2n) 6 | * 7 | * 不稳定排序/非原地排序 8 | * 9 | * @param {Array} arr 10 | * @param {Number} i 11 | * @param {Number} j 12 | */ 13 | function quickSort(arr, i, j) { 14 | if (i < j) { 15 | // 左右指针 16 | let left = i; 17 | let right = j; 18 | 19 | // 简单的选择数组第一个元素为基准数 20 | // 基准数的选择关系到排序的次数,可以使用三数取中法去取基准数 21 | let pivot = arr[left]; 22 | 23 | while (i < j) { 24 | // 从后往前找比基准小的数 25 | while (arr[j] >= pivot && i < j) { 26 | j--; 27 | } 28 | if (i < j) { 29 | arr[i] = arr[j]; 30 | i++; 31 | } 32 | 33 | // 从前往后找比基准大的数 34 | while (arr[i] <= pivot && i < j) { 35 | i++; 36 | } 37 | if (i < j) { 38 | arr[j] = arr[i]; 39 | j--; 40 | } 41 | } 42 | 43 | arr[i] = pivot; 44 | 45 | quickSort(arr, left, i - 1); 46 | quickSort(arr, i + 1, right); 47 | 48 | return arr; 49 | } 50 | } 51 | 52 | let arr = [2, 5, 8, 9, 3, 1, 3]; 53 | console.log(quickSort(arr, 0, arr.length - 1)); 54 | -------------------------------------------------------------------------------- /sort/shellSort.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 希尔排序 3 | * 4 | * 时间复杂度:最好O(n)、平均O(nlog2 n)、最坏O(n^2) 5 | * 空间复杂度:O(1) 6 | * 7 | * 不稳定排序/原地排序 8 | * 9 | * @param {Array} arr 10 | * @returns {Array} 11 | */ 12 | function shellSort(arr) { 13 | // 增量/步长 默认取长度的一半 14 | let step = Math.floor(arr.length / 2); 15 | 16 | while (step >= 1) { 17 | // 把距离为 step 的元素组为一个组 18 | for (let i = step; i < arr.length; i++) { 19 | let temp = arr[i]; 20 | // 对距离为 step 的元素组进行排序 21 | let j = i - step; 22 | for (; j >= 0; j -= step) { 23 | if (temp >= arr[j]) { 24 | break; 25 | } 26 | arr[j + step] = arr[j]; 27 | } 28 | arr[j + step] = temp; 29 | } 30 | 31 | // 减小步长增量为一半 32 | step = Math.floor(step / 2); 33 | } 34 | 35 | return arr; 36 | } 37 | 38 | const arr = [2, 5, 13, 4, 13, 45, 67, 34, 80, 1]; 39 | console.log(shellSort(arr)); 40 | -------------------------------------------------------------------------------- /sort/simpleSelectSort.js: -------------------------------------------------------------------------------- 1 | /** 2 | * (简单)选择排序 3 | * 4 | * 时间复杂度:最好O(n^2)、平均O(n^2)、最坏O(n^2) 5 | * 空间复杂度:O(1) 6 | * 7 | * 不稳定排序/原地排序 8 | * 9 | * 思路: 10 | * 1. 外层循环依次遍历序列中的每一个元素arr[i] 11 | * 2. 内层循环从待排序序列中剩下的 n-i 个元素,找到最小的元素arr[min], 12 | * 如果arr[i]不是最小元素,则将遍历得到的当前元素arr[i]和arr[min]交换 13 | * 3. 重复以上(1)(2)步骤直到结束 14 | * 15 | * @param {Array} arr 16 | * @returns {Array} 17 | */ 18 | function simpleSelectSort(arr) { 19 | let n = arr.length; 20 | if (n <= 1) { 21 | return arr; 22 | } 23 | 24 | // 注意边界, 因为需要在内层进行i+1后的循环,所以外层需要 数组长度-1 25 | for (let i = 0; i < n - 1; i++) { 26 | // 当前最小元素索引 27 | let minIndex = i; 28 | 29 | // 找到剩下的"无序序列"中最小元素的索引 30 | for (let j = i + 1; j < n; j++) { 31 | if (arr[j] < arr[minIndex]) { 32 | minIndex = j; 33 | } 34 | } 35 | 36 | // 交换有序区最后的一个元素和待排序区最小待元素 37 | if (minIndex !== i) { 38 | let temp = arr[i]; 39 | arr[i] = arr[minIndex] 40 | arr[minIndex] = temp; 41 | } 42 | } 43 | return arr; 44 | } 45 | 46 | const arr = [2, 13, 4, 13, 35, 67, 34, 70, 1]; 47 | console.log(simpleSelectSort(arr)); 48 | 49 | -------------------------------------------------------------------------------- /二分查找/README.md: -------------------------------------------------------------------------------- 1 | ## 查找 2 | -------------------------------------------------------------------------------- /二分查找/二分查找变形一.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 二分查找变形一:查找第一个值等于给定值的元素 3 | * @param {*} a 4 | * @param {*} target 5 | */ 6 | const binaryFindFirst = (a, target) => { 7 | if (a.length === 0) { 8 | return -1; 9 | } 10 | 11 | let low = 0; 12 | let high = a.length - 1; 13 | while (low <= high) { 14 | const mid = Math.floor(low + (high - low) / 2); 15 | 16 | if (target < a[mid]) { 17 | high = mid - 1; 18 | } else if (target > a[mid]) { 19 | low = mid + 1; 20 | } else { 21 | // 相等时,需要判断此时这个数是否是第一个等于target的值 22 | // (1)如果 mid等于0,那这个元素已经是数组的第一个元素,arr[mid]就是要找的第一个值等于给定值的元素 23 | // (2)arr[mid]的前一个元素arr[mid-1]不等于target,则arr[mid]就是要找的第一个值等于给定值的元素 24 | if (mid === 0 || a[mid - 1] !== target) { 25 | return mid; 26 | } else { 27 | // 如果a[mid]前面的一个元素 a[mid-1]也等于target 28 | // a[mid]肯定不是我们要查找的第一个值等于给定值的元素 29 | // 就更新 high=mid-1,因为要找的元素肯定出现在[low, mid-1]之间 30 | high = mid - 1; 31 | } 32 | } 33 | } 34 | return -1; 35 | } 36 | 37 | const arr = [1, 2, 3, 4, 4, 4, 4, 4, 6, 7, 8, 8, 9]; 38 | const first = binaryFindFirst(arr, 4); 39 | console.log(`查找第一个值等于给定值的元素: index=${first} value=${arr[first]}`); 40 | -------------------------------------------------------------------------------- /二分查找/二分查找变形三.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 变形三:查找第一个大于等于给定值的元素 3 | * @param {*} a 4 | * @param {*} target 5 | */ 6 | const binaryFindFistBig = (a, target) => { 7 | if (a.length === 0) { 8 | return -1; 9 | } 10 | 11 | let low = 0; 12 | let high = a.length - 1; 13 | while (low <= high) { 14 | const mid = Math.floor(low + (high - low) / 2); 15 | if (a[mid] >= target) { 16 | // 如果mid此时是数组第一个元素 或者 前一个值小于要查找的值,那此时就是要查找的第一个大于target的值了 17 | if (mid === 0 || a[mid - 1] < target) { 18 | return mid; 19 | } else { 20 | // 在[low, mid-1]找 21 | high = mid - 1; 22 | } 23 | } else { 24 | // 如果a[mid]小于要查找的值target, 那要查找的值肯定在[mid+1, high]之间,所以更新 low=mid+1 25 | low = mid + 1; 26 | } 27 | } 28 | return -1; 29 | } 30 | 31 | const arr = [1, 2, 3, 4, 4, 4, 4, 4, 6, 7, 8, 8, 9]; 32 | 33 | const firstBig = binaryFindFistBig(arr, 5); 34 | console.log(`查找第一个大于等于给定值的元素: indes=${firstBig} value=${arr[firstBig]}`); 35 | -------------------------------------------------------------------------------- /二分查找/二分查找变形二.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 二分查找变形问题二:查找最后一个等于给定值的元素 3 | * @param {*} a 4 | * @param {*} target 5 | */ 6 | const binaryFindLast = (a, target) => { 7 | if (a.length === 0) { 8 | return -1; 9 | } 10 | 11 | let low = 0; 12 | let high = a.length - 1; 13 | while (low <= high) { 14 | const mid = Math.floor(low + (high - low) / 2); 15 | 16 | if (target < a[mid]) { 17 | high = mid - 1; 18 | } else if (target > a[mid]) { 19 | low = mid + 1; 20 | } else { 21 | // 相等时,需要判断此时这个数是否是最后一个等于target的值 22 | // (1)如果 mid等于0,那这个元素已经是数组的最后一个元素,arr[mid]就是要找的最后一个值等于给定值的元素 23 | // (2)arr[mid]的后一个元素arr[mid-1]不等于target,则arr[mid]就是要找的最后一个值等于给定值的元素 24 | if (mid === a.length - 1 || a[mid + 1] !== target) { 25 | return mid; 26 | } else { 27 | // 如果不是最后一个等于给定值的元素,则在[mid+1, high]中查找 28 | low = mid + 1; 29 | } 30 | } 31 | } 32 | return -1; 33 | } 34 | 35 | const arr = [1, 2, 3, 4, 4, 4, 4, 4, 6, 7, 8, 8, 9]; 36 | const last = binaryFindLast(arr, 4); 37 | console.log(`查找最后一个相等的数: index=${last} value=${arr[last]}`); 38 | -------------------------------------------------------------------------------- /二分查找/二分查找变形四.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 变形四:查找最后一个小于等于给定值的元素 3 | * @param {*} a 4 | * @param {*} target 5 | */ 6 | const binaryFindLastSmall = (a, target) => { 7 | if (a.length === 0) { 8 | return -1; 9 | } 10 | 11 | let low = 0; 12 | let high = a.length - 1; 13 | while (low <= high) { 14 | const mid = Math.floor(low + (high - low) / 2); 15 | if (a[mid] > target) { 16 | high = mid - 1; 17 | } else { 18 | // mid是最后一个 或 mid的下一个大于目标值,说明当前值是最后一个小于等于给定值的 19 | if (mid === a.length - 1 || a[mid + 1] >= target) { 20 | return mid; 21 | } else { 22 | low = mid + 1; 23 | } 24 | } 25 | } 26 | return -1; 27 | } 28 | 29 | const arr = [1, 2, 3, 4, 4, 4, 4, 4, 6, 7, 8, 8, 9]; 30 | const lastSmall = binaryFindLastSmall(arr, 4); 31 | console.log(`查找最后一个小于等于给定值的元素: index=${lastSmall} value=${arr[lastSmall]}`); 32 | -------------------------------------------------------------------------------- /二分查找/实现开平方.js: -------------------------------------------------------------------------------- 1 | /** 2 | * leetcode 69 x 的平方根 3 | * 4 | * 计算并返回 x 的平方根,其中 x 是非负整数。 5 | * 由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去 6 | * 7 | * 简单 8 | */ 9 | /** 10 | * 利用二分查找思路 11 | * 12 | * 时间复杂度:O(logx),即为二分查找需要的次数 13 | * 空间复杂度:O(1) 14 | * 15 | * @param {*} x 16 | */ 17 | function mySqrt(x) { 18 | let low = 0; 19 | let high = x; 20 | let result = -1; 21 | 22 | while (low <= high) { 23 | const middle = Math.floor(low + (high - low) / 2); 24 | 25 | if (middle * middle <= x) { 26 | result = middle; 27 | low = middle + 1; 28 | } else { 29 | high = middle - 1; 30 | } 31 | } 32 | 33 | return result; 34 | } 35 | 36 | console.log(mySqrt(9)); 37 | --------------------------------------------------------------------------------