├── .gitignore ├── src ├── public │ ├── logo.png │ ├── logo-128.png │ ├── logo-256.png │ ├── logo-512.png │ └── logo-64.png ├── index.md └── docs │ └── prerequisite-knowledge.md ├── package.json ├── Sword-Pointing-Offer ├── 190.ReverseBits.js ├── 226.InvertBinaryTree.js ├── 172.FactorialTrailingZeroes.js ├── 392.IsSubsequence.js ├── 222.CountCompleteTreeNodes.js ├── 201.BitwiseAndNumbersRange.js ├── 219.ContainsDuplicateII.js ├── 228.SummaryRanges.js ├── 155.MinStack.js ├── 191.NumberOf1Bits.js ├── 383.RansomNote.js ├── 162.FindPeakElement.js ├── 230.KthSmallestElementInBST.js ├── 221.MaximalSquare.js ├── 202.HappyNumber.js ├── 167.TwoSumII.js ├── 173.BinarySearchTreeIterator.js ├── 199.BinaryTreeRightSideView.js ├── 530.MinimumAbsoluteDifferenceinBST.js ├── 152.MaximumProductSubarray.js ├── 290.WordPattern.js ├── 289.GameofLife.js ├── 637.AverageofLevelsinBinaryTree.js ├── 151.ReverseWordsString.js ├── 236.LowestCommonAncestorBinaryTree.js ├── 188.BestTimetoBuyandSellStockIV.js ├── 452.MinimumNumberofArrowstoBurstBalloons.js ├── 380.InsertDeleteGetRandom.js ├── 153.FindMinimuminRotatedSortedArray.js ├── 274.H-Index.js ├── 918.MaximumSumCircularSubarray.js ├── 169.MajorityElement.js ├── 208.ImplementTrie(PrefixTree).js ├── 200.NumberofIslands.js ├── 198.HouseRobber.js ├── 209.MinimumSizeSubarraySum.js ├── 211.DesignAddSearchWordsDataStructure.js ├── 427.ConstructQuadTree.js ├── 189.RotateArray.js ├── 242.ValidAnagram.js ├── 433.MinimumGeneticMutation.js └── 909.SnakesAndLadders.js ├── General-Questions-Part1 ├── 09.isPalindromes.js ├── 13.romanToInteger.js ├── 12.integerToRoman.js ├── 14.longestCommonPrefix.js ├── 06.converStringToNGlyph.js ├── 26.removeDuplicatesInArray.js ├── 38.countAndSay.js ├── 21.mergeTwoSortedList.js ├── 35.searchInsertionLocation.js ├── 22.generateParenthesis.js ├── 07.integerInversion.js ├── 50.pow.js ├── 20.isValidBrackets.js ├── 16.threeNumberSumClosest.js ├── 17.phoneNumberAlphabet.js ├── 48.rotateImage.js ├── 39.combinedSum.js ├── 10.regularMatch.js ├── 33.searchRotatedSortedArray.js ├── 43.stringMultiplication.js ├── 46.permutations.js ├── 44.wildcardMatching.js ├── 11.holdsMostWater.js ├── 08.stringToInteger.js └── 36.validSudoku.js ├── General-Questions-Part2 ├── 100.SameTree.js ├── 83.RemoveDuplicatesSortedList.js ├── 58.LengthOfLastWord.js ├── 78.Subsets.js ├── 66.PlusOne.js ├── 61.RotateList.js ├── 67.AddBinary.js ├── 90.SubsetsII.js ├── 98.ValidateBinarySearchTree.js ├── 55.JumpGame.js ├── 73.SetMatrixZeroes.js ├── 64.MinimumPathSum.js ├── 74.Search2DMatrix.js ├── 77.Combinations.js ├── 89.GrayCode.js ├── 93.RestoreIPAddresses.js ├── 71.SimplifyPath.js ├── 82.RemoveDuplicatesSortedListII.js ├── 80.RemoveDuplicatesSortedArrayII.js ├── 86.PartitionList.js ├── 68.TextJustification.js ├── 92.ReverseLinkedListII.js ├── 59.SpiralMatrixII.js ├── 54.SpiralMatrix.js ├── 94.BinaryTreeInorderTraversal.js ├── 88.MergeSortedArray.js ├── 56.MergeIntervals.js └── 60.PermutationSequence.js ├── General-Questions-Part3 ├── 118.Pascal'sTriangle.js ├── 123.BestTimetoBuyandSellStockIII.js ├── 101.SymmetricTree.js ├── 144.BinaryTreePreorderTraversal.js ├── 125.ValidPalindrome.js ├── 120.Triangle.js ├── 104.MaximumDepthofBinaryTree.js ├── 150.EvaluateReversePolishNotation.js ├── 141.LinkedListCycle.js ├── 129.SumRoottoLeafNumbers.js ├── 127.WordLadder.js ├── 111.MinimumDepthOfBinaryTree.js ├── 114.FlattenBinaryTreetoLinkedList.js ├── 147.InsertionSortList.js ├── 122.BestTimetoBuyandSellStockII.js ├── 108.ConvertSortedArraytoBinarySearchTree.js ├── 143.ReorderList.js ├── 110.BalancedBinaryTree.js ├── 139.WordBreak.js ├── 116.PopulatingNextRightPointersinEachNode.js ├── 137.SingleNumberII.js ├── 117.PopulatingNextRightPointersinEachNodeII.js ├── 113.PathSumII.js ├── 112.PathSum.js ├── 145.BinaryTreePostorderTraversal.js ├── 132.PalindromePartitioningII.js ├── 128.LongestConsecutiveSequence.js ├── 136.SingleNumber.js ├── 142.LinkedListCycleII.js ├── 121.BestTimetoBuyandSellStock.js ├── 119.Pascal'sTriangleII.js ├── 134.GasStation.js ├── 103.BinaryTreeZigzagLevelOrderTraversal.js ├── 130.SurroundedRegions.js ├── 115.DistinctSubsequences.js └── 149.MaxPointsonaLine.js ├── LICENSE ├── .vitepress └── theme │ ├── custom.css │ ├── index.mts │ └── components │ └── ReloadPrompt.vue ├── .github └── workflows │ └── main.yml └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | cache 3 | dev-dist 4 | 5 | package-lock.json 6 | .github -------------------------------------------------------------------------------- /src/public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JunLiangWangX/JS-Algorithm/HEAD/src/public/logo.png -------------------------------------------------------------------------------- /src/public/logo-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JunLiangWangX/JS-Algorithm/HEAD/src/public/logo-128.png -------------------------------------------------------------------------------- /src/public/logo-256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JunLiangWangX/JS-Algorithm/HEAD/src/public/logo-256.png -------------------------------------------------------------------------------- /src/public/logo-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JunLiangWangX/JS-Algorithm/HEAD/src/public/logo-512.png -------------------------------------------------------------------------------- /src/public/logo-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JunLiangWangX/JS-Algorithm/HEAD/src/public/logo-64.png -------------------------------------------------------------------------------- /src/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | # https://vitepress.dev/reference/default-theme-home-page 3 | layout: home 4 | title: 首页 5 | hero: 6 | name: "JS每日一算法" 7 | text: "" 8 | tagline: 每天学习一个JS算法 9 | actions: 10 | - theme: brand 11 | text: 开始阅读 12 | link: /docs/prerequisite-knowledge 13 | - theme: alt 14 | text: 访问Github 15 | link: https://github.com/JunLiangWangX/JS-Algorithm/ 16 | 17 | --- 18 | 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "@vite-pwa/vitepress": "^0.2.0", 4 | "flexsearch": "^0.7.34", 5 | "vitepress": "^1.0.0-beta.3", 6 | "vitepress-plugin-search": "^1.0.4-alpha.22" 7 | }, 8 | "scripts": { 9 | "dev": "vitepress dev", 10 | "docs:build": "vitepress build", 11 | "docs:preview": "vitepress preview" 12 | }, 13 | "dependencies": { 14 | "cross-spawn": "^7.0.3", 15 | "vite-plugin-pwa": "^0.16.3", 16 | "vitepress-plugin-comment-with-giscus": "^1.1.11" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Sword-Pointing-Offer/190.ReverseBits.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 颠倒给定的 32 位无符号整数的二进制位。 3 | * @Author: JunLiangWang 4 | * @Date: 2024-02-23 09:31:21 5 | * @LastEditors: JunLiangWang 6 | * @LastEditTime: 2024-02-23 09:32:53 7 | */ 8 | 9 | 10 | /** 11 | * @description: 逐位颠倒 TC:O(logn) SC:O(1) 12 | * @param {*} n 给定32位无符号整数 13 | * @return {*} 14 | */ 15 | function reverseBits(n) { 16 | let rev = 0; 17 | for (let i = 0; i < 32 && n > 0; ++i) { 18 | rev |= (n & 1) << (31 - i); 19 | n >>>= 1; 20 | } 21 | return rev >>> 0; 22 | }; -------------------------------------------------------------------------------- /Sword-Pointing-Offer/226.InvertBinaryTree.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 你一棵二叉树的根节点 root ,翻转这棵二叉树,并返回其根节点。 3 | * @Author: JunLiangWang 4 | * @Date: 2023-12-08 09:21:49 5 | * @LastEditors: JunLiangWang 6 | * @LastEditTime: 2023-12-08 09:25:07 7 | */ 8 | 9 | 10 | /** 11 | * @description: 深度优先 TC:O(n) SC:O(n) 12 | * @author: JunLiangWang 13 | * @param {*} root 给定树根节点 14 | * @return {*} 15 | */ 16 | function dfs(root) { 17 | /** 18 | * 本方案使用深度优先的方式遍历树的节点, 19 | * 然后交换遍历的左右节点即可 20 | */ 21 | if (!root) return root 22 | let left = dfs(root.left), 23 | right = dfs(root.right) 24 | root.left = right 25 | root.right = left 26 | return root 27 | } -------------------------------------------------------------------------------- /Sword-Pointing-Offer/172.FactorialTrailingZeroes.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 给定一个整数 n ,返回 n! 结果中尾随零的数量。 3 | * @Author: JunLiangWang 4 | * @Date: 2024-02-28 09:31:46 5 | * @LastEditors: JunLiangWang 6 | * @LastEditTime: 2024-02-28 09:42:03 7 | */ 8 | 9 | 10 | /** 11 | * @description: 数学方法 TC:O(logn) SC:O(1) 12 | * @param {*} n 给定整数n 13 | */ 14 | function mathMethod(n){ 15 | /** 16 | * 本题题意为阶乘后的结果的结尾有几个连续的0 17 | * 其实也就是能被10整除多少次。换句话说n的因子 18 | * 中有多少个数相乘能得10,对于其因子而言只有 19 | * 2*5的倍数,以及10*1的倍数能够得10,因此我们 20 | * 不断对n/5即可得到阶乘结果得结尾有几个连续的0 21 | */ 22 | let count=0; 23 | while(n!=0){ 24 | n=Math.floor(n/5) 25 | count+=n 26 | } 27 | return count 28 | } -------------------------------------------------------------------------------- /General-Questions-Part1/09.isPalindromes.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 给你一个整数x,如果x是一个回文整数,返回true;否则返回false。 3 | * @Author: JunLiangWang 4 | * @Date: 2023-03-01 09:22:51 5 | * @LastEditors: JunLiangWang 6 | * @LastEditTime: 2023-03-01 09:29:05 7 | */ 8 | 9 | 10 | /** 11 | * @description: 翻转法 TC:O(n) SC:O(1) 12 | * @author: JunLiangWang 13 | * @param {*} x 14 | * @return {*} 15 | */ 16 | function overTurn(x) 17 | { 18 | // 利用回文数左右对称的原理,即将回文数翻转后仍等于未翻转时数值 19 | 20 | // 转换为字符串 21 | let xString=x.toString() 22 | // 翻转字符串存放变量 23 | let overTurnString='' 24 | // 翻转字符串 25 | for(let item of xString)overTurnString=item+overTurnString 26 | // 如果翻转后仍等于翻转前则为回文数,否则不为。 27 | return xString==overTurnString 28 | } -------------------------------------------------------------------------------- /General-Questions-Part2/100.SameTree.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 给你两棵二叉树的根节点 p 和 q ,编写一个函数来检验这两棵树是否相同。 3 | 如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。 4 | * @Author: JunLiangWang 5 | * @Date: 2023-08-07 09:02:50 6 | * @LastEditors: JunLiangWang 7 | * @LastEditTime: 2023-08-07 09:10:03 8 | */ 9 | 10 | 11 | /** 12 | * @description: 深度优先 TC:O(n) SC:O(n) 13 | * @author: JunLiangWang 14 | * @param {*} p 给定树头节点 15 | * @param {*} q 给定树头节点 16 | * @return {*} 17 | */ 18 | function dfs(p,q){ 19 | /** 20 | * 该方案采用深度优先的方式 21 | */ 22 | if(q==null&&p==null)return true 23 | else if(q==null||p==null)return false 24 | else if(q.val!=p.val)return false 25 | else return dfs(q.left,p.left)&&dfs(q.right,p.right) 26 | } -------------------------------------------------------------------------------- /General-Questions-Part3/118.Pascal'sTriangle.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 给定一个非负整数 numRows,生成「杨辉三角」的前 numRows 行。 3 | * @Author: JunLiangWang 4 | * @Date: 2023-09-01 09:37:52 5 | * @LastEditors: JunLiangWang 6 | * @LastEditTime: 2023-09-01 09:42:11 7 | */ 8 | 9 | 10 | /** 11 | * @description: 迭代法 TC:O(n^2) SC:O(n^2) 12 | * @author: JunLiangWang 13 | * @param {*} numRows 给定一个非负整数 14 | * @return {*} 15 | */ 16 | function iteration(numRows){ 17 | /** 18 | * 该方案使用迭代法,两次遍历生成杨辉三角形 19 | */ 20 | let outArray=[] 21 | for(let i=0;i=0;i--) 26 | { 27 | const value=mark[s[i]] 28 | // 当为降序规则,则为加法 29 | if(value>=last)number+=value 30 | // 当违反了降序规则,则为减法 31 | else number-=value 32 | last=value 33 | } 34 | return number 35 | } -------------------------------------------------------------------------------- /General-Questions-Part3/123.BestTimetoBuyandSellStockIII.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。 3 | 设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。 4 | * @Author: JunLiangWang 5 | * @Date: 2023-09-11 14:14:48 6 | * @LastEditors: JunLiangWang 7 | * @LastEditTime: 2023-09-11 14:14:54 8 | */ 9 | 10 | 11 | /** 12 | * @description: 动态规划 TC:O(n) SC:O(1) 13 | * @author: JunLiangWang 14 | * @param {*} prices 给定数组 15 | * @return {*} 16 | */ 17 | function dp(prices){ 18 | let firstBuy=prices[0], 19 | secondBuy=-prices[0], 20 | firstProfit=0, 21 | secondProfit=0; 22 | for(let i=1;i'+nums[i-1]); 29 | start=i 30 | } 31 | } 32 | if(start==nums.length-1)out.push(nums[start]+'') 33 | else out.push(nums[start]+'->'+nums[nums.length-1]); 34 | return out 35 | } -------------------------------------------------------------------------------- /General-Questions-Part1/12.integerToRoman.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 整数转罗马数字 3 | * @Author: JunLiangWang 4 | * @Date: 2023-03-08 09:10:28 5 | * @LastEditors: JunLiangWang 6 | * @LastEditTime: 2023-03-08 09:25:04 7 | */ 8 | 9 | /** 10 | * @description: 贪心 TC:O(n) SC:O(1) 11 | * @author: JunLiangWang 12 | * @param {*} num 输入的数字 13 | * @return {*} 14 | */ 15 | function greedy(num){ 16 | // 此处根据mark[n][1]降序排列,为满足后续贪心法则 17 | const mark=[['M',1000],['CM',900],['D',500],['CD',400],['C',100],['XC',90],['L',50],['XL',40], 18 | ['X',10],['IX',9],['V',5],['IV',4],['I',1]]; 19 | let number=num 20 | let roman='' 21 | // 此处贪心法则,从最大的进制值遍历 22 | for([symbol,key] of mark) 23 | { 24 | // 当number大于了当前进制值,由于使用了贪心法则(从最大的进制值开始遍历)则证明 25 | // 此进制值为number能满足的最大进制 26 | while(number>=key) 27 | { 28 | // 不断减去当前满足的最大进制,直到小于当前进制为止 29 | number-=key 30 | // 加上当前进制的roman字符 31 | roman+=symbol 32 | } 33 | } 34 | return roman 35 | } -------------------------------------------------------------------------------- /General-Questions-Part3/144.BinaryTreePreorderTraversal.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 给你二叉树的根节点 root ,返回它节点值的 前序 遍历。 3 | * @Author: JunLiangWang 4 | * @Date: 2023-10-23 10:26:44 5 | * @LastEditors: JunLiangWang 6 | * @LastEditTime: 2023-10-23 10:29:21 7 | */ 8 | 9 | 10 | /** 11 | * @description: 深度优先 TC:O(n) SC:O(n) 12 | * @author: JunLiangWang 13 | * @param {*} root 给定树的头节点 14 | * @return {*} 15 | */ 16 | function dfs(root) { 17 | /** 18 | * 本方案使用深度优先的方式,前序遍历树的节点 19 | */ 20 | 21 | // 记录树的节点 22 | let outArray = [] 23 | /** 24 | * @description: 递归实现深度优先 25 | * @author: JunLiangWang 26 | * @param {*} root 子树根节点 27 | * @return {*} 28 | */ 29 | function recursion(root) { 30 | // 如果根节点为空,直接return 31 | if (!root) return 32 | // 向数组添加根节点 33 | outArray.push(root.val) 34 | // 递归遍历左子树 35 | recursion(root.left) 36 | // 递归遍历右子树 37 | recursion(root.right) 38 | } 39 | // 执行递归 40 | recursion(root) 41 | // 返回结果 42 | return outArray 43 | } -------------------------------------------------------------------------------- /Sword-Pointing-Offer/155.MinStack.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 设计一个支持 push ,pop ,top 操作, 3 | 并能在常数时间内检索到最小元素的栈。 4 | * @Author: JunLiangWang 5 | * @Date: 2023-11-06 09:25:38 6 | * @LastEditors: JunLiangWang 7 | * @LastEditTime: 2023-11-06 09:35:12 8 | */ 9 | 10 | /** 11 | * 本方案采用栈的方法,每当向栈中push元 12 | * 素时,一并加入当前元素中的最小元素, 13 | * 即: { 14 | * val: 当前值, 15 | * min: min(当前值, 栈顶的.min) 16 | * } 17 | * 18 | * 那么栈中的每个元素都对应了相应的最小值 19 | */ 20 | 21 | var MinStack = function () { 22 | this.stack = [] 23 | }; 24 | MinStack.prototype.push = function (val) { 25 | let length = this.stack.length 26 | this.stack.push({ 27 | val: val, 28 | min: length == 0 ? val : Math.min(val, this.stack[length - 1].min) 29 | }) 30 | }; 31 | MinStack.prototype.pop = function () { 32 | this.stack.pop(); 33 | }; 34 | MinStack.prototype.top = function () { 35 | return this.stack[this.stack.length - 1].val; 36 | }; 37 | MinStack.prototype.getMin = function () { 38 | return this.stack[this.stack.length - 1].min; 39 | }; -------------------------------------------------------------------------------- /General-Questions-Part2/83.RemoveDuplicatesSortedList.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 给定一个已排序的链表的头 head , 删除所有重复的元素,使每个元素只出现一次 。返回 已排序的链表 。 3 | * @Author: JunLiangWang 4 | * @Date: 2023-07-07 09:06:30 5 | * @LastEditors: JunLiangWang 6 | * @LastEditTime: 2023-07-07 09:11:48 7 | */ 8 | 9 | 10 | 11 | /** 12 | * @description: 迭代法 TC:O(n) SC:O(1) 13 | * @author: JunLiangWang 14 | * @param {*} head 给定链表头节点 15 | * @return {*} 16 | */ 17 | function iterate(head) { 18 | /** 19 | * 由于给定的链表是排好序的,因此重复的元素在链表中出现的位置是连续的, 20 | * 因此我们只需要对链表进行一次遍历,就可以删除重复的元素。 21 | */ 22 | 23 | // 如果链表为空直接返回 24 | if (!head) return head 25 | 26 | // 记录头节点,方便后续返回 27 | const HEAD = head 28 | 29 | // 如果当前节点不存在下一个节点, 30 | // 证明以遍历完成所有节点 31 | while (head.next) { 32 | // 如果当前节点的值等于下一个节点的值 33 | if (head.val == head.next.val) 34 | // 则删除下一个节点,具体为将当前节点 35 | // 的下一个节点赋值为下一个节点的下一个节点 36 | head.next = head.next.next 37 | // 不等于,则将当前节点后移一位 38 | else 39 | head = head.next 40 | } 41 | return HEAD; 42 | } -------------------------------------------------------------------------------- /General-Questions-Part3/125.ValidPalindrome.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 给你一个字符串 s,移除所有非字母数字字符之后, 3 | 如果它是 回文串 ,返回 true ;否则,返回 false 。 4 | * @Author: JunLiangWang 5 | * @Date: 2023-09-13 09:27:52 6 | * @LastEditors: JunLiangWang 7 | * @LastEditTime: 2023-09-13 09:32:22 8 | */ 9 | 10 | 11 | /** 12 | * @description: 双指针 TC:O(n) SC:O(1) 13 | * @author: JunLiangWang 14 | * @param {*} s 给定字符串 15 | * @return {*} 16 | */ 17 | function doublePoint(s) { 18 | /** 19 | * 该方案使用双指针的方式,先将字符串转换为小写, 20 | * 然后使用正则排除0-9 a-z以外的字符,最后通过 21 | * 双指针,first指向首个字符,last指向最后一个字 22 | * 符,两指针不断向中间靠拢,如果所指字符不一样证 23 | * 明不是回文字符串,如果遍历完成所指字符全一样, 24 | * 则证明该字符串是回文串。 25 | */ 26 | 27 | // 先将字符串转换为小写,然后使用正则排除0-9 a-z以外的字符 28 | s = s.toLowerCase().replace(/[^0-9a-z]/g, ''); 29 | // 通过双指针,first指向首个字符,last指向最后一个字 30 | // 符,两指针不断向中间靠拢,如果所指字符不一样证 31 | // 明不是回文字符串,直接返回false 32 | for (let first = 0, last = s.length - 1; first < last; first++, last--) 33 | if (s[first] != s[last]) return false 34 | // 如果所指字符全一样,则证明该字符串是回文串,返回true 35 | return true; 36 | } -------------------------------------------------------------------------------- /General-Questions-Part2/58.LengthOfLastWord.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 给你一个字符串 s,由若干单词组成,单词前后用一些空格字符隔开。返回字符串中4 3 | 最后一个 单词的长度。单词 是指仅由字母组成、不包含任何空格字符的最大子字符串。 4 | * @Author: JunLiangWang 5 | * @Date: 2023-06-02 10:12:53 6 | * @LastEditors: JunLiangWang 7 | * @LastEditTime: 2023-06-02 10:20:38 8 | */ 9 | 10 | 11 | /** 12 | * @description: 反向遍历 TC:O(n) SC:O(1) 13 | * @author: JunLiangWang 14 | * @param {*} s 给定字符串 15 | * @return {*} 16 | */ 17 | function reverseTraversal(s){ 18 | /** 19 | * 该方案利用反向遍历的方式,定义一个count遍历记录单词长度, 20 | * 然后反向遍历给定字符串s,当字符不等于空格时,count增加1, 21 | * 当字符等于空格时并且count不等于0时(count不等于0是为了防 22 | * 止多个空格的情况)证明已找到最后一个单词长度,直接返回即可, 23 | * 最后循环外再返回0(为了解决当给定字符串全为空格时的情况) 24 | */ 25 | 26 | // 定义记录单词长度遍历 27 | let count=0; 28 | // 反向遍历给定字符串 29 | for(let i=s.length-1;i>=0;i--){ 30 | // 当字符不等于空格时,此时为字母,count增加1 31 | if(s[i]!=' ')count++; 32 | // 当字符等于空格时并且count不等于0时(count不等于0是为了防 33 | // 止多个空格的情况)证明已找到最后一个单词长度,直接返回即可 34 | else if(count!=0)return count; 35 | } 36 | // 返回0(为了解决当给定字符串全为空格时的情况) 37 | return 0; 38 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Wang JunLiang 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 | -------------------------------------------------------------------------------- /Sword-Pointing-Offer/191.NumberOf1Bits.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 编写一个函数,输入是一个无符号整数(以二进制串的形式),返回其二进制 3 | 表达式中数字位数为 '1' 的个数(也被称为汉明重量)。 4 | * @Author: JunLiangWang 5 | * @Date: 2024-02-26 09:47:12 6 | * @LastEditors: JunLiangWang 7 | * @LastEditTime: 2024-02-26 09:55:28 8 | */ 9 | 10 | 11 | /** 12 | * @description: 转换为二进制字符串 TC:O(n) SC:O(1) 13 | * @param {*} n 给定无符号整数 14 | * @return {*} 15 | */ 16 | function convertBinaryString(n){ 17 | /** 18 | * 本方案将n转换为2进制字符串, 19 | * 然后遍历字符计算1的数量即可 20 | */ 21 | let count=0,binaryString=n.toString(2); 22 | for(let i=0;imaxDeep)maxDeep=deep 38 | // 继续递归左子树,层级+1 39 | recursion(root.left,deep+1) 40 | // 继续递归右子树,层级+1 41 | recursion(root.right,deep+1) 42 | } 43 | // 执行递归 44 | recursion(root,1) 45 | // 返回结果 46 | return maxDeep 47 | } -------------------------------------------------------------------------------- /General-Questions-Part1/14.longestCommonPrefix.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 查找字符串数组中的最长公共前缀,如果不存在公共前缀,返回空字符串。 3 | * @Author: JunLiangWang 4 | * @Date: 2023-03-10 09:46:28 5 | * @LastEditors: JunLiangWang 6 | * @LastEditTime: 2023-03-10 10:03:51 7 | */ 8 | 9 | 10 | /** 11 | * @description: 纵向比较法 TC:O(n^2) SC:O(1) 12 | * @author: JunLiangWang 13 | * @param {*} strs 输入的字符串数组 14 | * @return {*} 15 | */ 16 | function longitudinalComparison(strs) 17 | { 18 | // 当前需要对比的字符索引 19 | let index=0 20 | // 已比对的相同的前缀字符串 21 | let commonPrefix='' 22 | // strs[0]当前需要对比的字符不为undefined则继续循环,否则则证明当前字符不构成共有前缀字符串,跳出循环直接返回结果 23 | while(strs[0][index]) 24 | { 25 | // 当前需要比对的前缀字符 26 | let currentCharacter=strs[0][index] 27 | // 遍历字符串开始比对 28 | for(let i=1;i=0;i--){ 29 | // 给元素+1 30 | let sum=digits[i]+1; 31 | // 将加一后的元素对10取余后重新赋值给元素 32 | // 之所以对10取余是防止进位的情况 33 | digits[i]=sum%10; 34 | // 当加一对10取余后仍等于加一时的值,证明 35 | // 无进位的情况,直接返回即可 36 | if(digits[i]===sum)return digits; 37 | } 38 | // 遍历完所有元素并未跳出,证明还需要进一位 39 | // 在数组开头添加一个1元素 40 | digits.splice(0,0,1); 41 | // 返回结果 42 | return digits; 43 | } -------------------------------------------------------------------------------- /Sword-Pointing-Offer/162.FindPeakElement.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 给你一个整数数组 nums,找到峰值元素并返回其索引。 3 | 数组可能包含多个峰值,在这种情况下,返回 任何一个 4 | 峰值 所在位置即可。 5 | * @Author: JunLiangWang 6 | * @Date: 2024-01-17 09:12:30 7 | * @LastEditors: JunLiangWang 8 | * @LastEditTime: 2024-01-17 09:15:37 9 | */ 10 | 11 | 12 | /** 13 | * @description: 二分 TC:O(logn) SC:O(logn) 14 | * @author: JunLiangWang 15 | * @param {*} nums 给定数组 16 | * @return {*} 17 | */ 18 | function binarySearch(nums){ 19 | /** 20 | * 题中说了数组相邻元素互不相等,因此我仅需要 21 | * 找到数组最大值,即可找到峰值元素。 22 | */ 23 | 24 | /** 25 | * @description: 递归+二分 26 | * @author: JunLiangWang 27 | * @param {*} start 开始索引 28 | * @param {*} end 结束索引 29 | * @return {*} 30 | */ 31 | function recursion(start,end){ 32 | // 如果无法继续二分,返回当前索引 33 | if(start==end)return start 34 | // 获得中间索引 35 | let middle=Math.floor((start+end)/2), 36 | // 继续递归 37 | left= recursion(start,middle), 38 | right=recursion(middle+1,end) 39 | // 比较两索引的值大小,返回值较大的索引 40 | return nums[left]>nums[right]?left:right 41 | } 42 | // 执行递归,返回结果 43 | return recursion(0,nums.length-1) 44 | } -------------------------------------------------------------------------------- /General-Questions-Part1/26.removeDuplicatesInArray.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 删除有序数组中的重复项 3 | * @Author: JunLiangWang 4 | * @Date: 2023-04-03 10:38:26 5 | * @LastEditors: JunLiangWang 6 | * @LastEditTime: 2023-04-03 10:55:20 7 | */ 8 | 9 | 10 | /** 11 | * @description: 双指针 TC:O(n) SC:O(1) 12 | * @author: JunLiangWang 13 | * @param {*} nums 输入数组 14 | * @return {*} 15 | */ 16 | function doublePoint(nums){ 17 | /** 18 | * 该方案利用双指针的方式,定义两个指针(low初值0,fast初值1),当两指针所指元素相同时,将fast指针继 19 | * 续向右移动直至遇到不同元素时(此时low与fast则会拉开一个区间,该区间的元素则为需要删除的相同的元 20 | * 素),将low指针向右移动一位,并将其赋值为fast指针所指元素(此时0到low指针所在区间的元素则是不重 21 | * 复的元素),当fast指针超出数组范围则终止循环,此时low位置+1则为去重后数组长度。 22 | */ 23 | 24 | // 如果数组长度不大于1,无需去重直接返回即可 25 | if(nums.length<=1)return nums.length; 26 | // 定义两个指针 27 | let fastPoint=1,lowPoint=0; 28 | // 当fast指针超出数组范围则终止循环 29 | while(fastPoint1){ 42 | node=node.next; 43 | k++; 44 | } 45 | // 将头节点保存 46 | head=node.next 47 | // 将循环链表断开 48 | node.next=null; 49 | // 返回头节点 50 | return head; 51 | } 52 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Deploy 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | branches: 8 | - main 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v3 15 | with: 16 | fetch-depth: 0 17 | - name: Use Node.js 18 | uses: actions/setup-node@v3 19 | with: 20 | node-version: 18.x 21 | - run: npm i 22 | - run: npm run docs:build --if-present 23 | 24 | - if: ${{ github.event_name == 'push' && success() }} 25 | name: Publish HTML to GitHub Pages 26 | uses: peaceiris/actions-gh-pages@v3 27 | with: 28 | github_token: ${{ secrets.SECRET }} 29 | publish_dir: ./public 30 | enable_jekyll: false 31 | 32 | - if: ${{ github.event_name == 'push' && success() }} 33 | name: deploy file to server 34 | uses: appleboy/scp-action@v0.1.7 35 | with: 36 | host: ${{ secrets.SERVER_IP }} 37 | username: 'root' 38 | key: ${{ secrets.SSH_PRIVATE_KEY }} 39 | source: './public/*' 40 | target: "/home/www/htdocs/wangjunliang/JS-Algorithm/" 41 | strip_components: 1 42 | -------------------------------------------------------------------------------- /General-Questions-Part1/38.countAndSay.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 给定一个正整数 n ,输出外观数列的第 n 项。 3 | * @Author: JunLiangWang 4 | * @Date: 2023-04-24 09:46:54 5 | * @LastEditors: JunLiangWang 6 | * @LastEditTime: 2023-04-24 10:01:25 7 | */ 8 | 9 | 10 | /** 11 | * @description: 迭代法 TC:O(n^2) SC:O(n) 12 | * @author: JunLiangWang 13 | * @param {*} n 获取第n项外观数列 14 | * @return {*} 15 | */ 16 | function iteration(n){ 17 | /** 18 | * 该方案使用迭代模拟外观数列生成的过程 19 | */ 20 | 21 | // 初始化[外观数列]为1 22 | let str='1'; 23 | // 循环次数n-1次,由于str初始化为[外观数列]的第一项,所以 24 | // 此处循环n-1次获得第n项[外观数列] 25 | while(--n>0) 26 | { 27 | // 统计相邻的相同数字出现次数,初始化为1 28 | // 因为每个字符至少出现1次 29 | let count=1, 30 | // 当前项[外观数列] 31 | tempStr=''; 32 | // 遍历上次[外观数列]以生成本次外观数列 33 | for(let i=0;i new Array(columns + 1).fill(0)); 27 | for (let i = 1; i <= rows; i++) { 28 | for (let j = 1; j <= columns; j++) { 29 | if (matrix[i - 1][j - 1] == 1) { 30 | dp[i][j] = Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]) + 1; 31 | maxSide = Math.max(maxSide, dp[i][j]); 32 | } 33 | } 34 | } 35 | return maxSide * maxSide; 36 | } -------------------------------------------------------------------------------- /General-Questions-Part3/141.LinkedListCycle.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 给你一个链表的头节点 head ,判断链表中是否有环。 3 | * @Author: JunLiangWang 4 | * @Date: 2023-10-18 09:19:17 5 | * @LastEditors: JunLiangWang 6 | * @LastEditTime: 2023-10-18 09:26:16 7 | */ 8 | 9 | 10 | /** 11 | * @description: 哈希表 TC:O(n) SC:O(n) 12 | * @author: JunLiangWang 13 | * @param {*} head 给定链表头节点 14 | * @return {*} 15 | */ 16 | function hashMap(head){ 17 | /** 18 | * 本方案使用hashMap的方式,遍历链表节点并在 19 | * hashMap记录该节点,当遍历节点时查询hashMap 20 | * 中是否存在该节点,如果存在,则证明有环。能 21 | * 够遍历完成链表,证明无环 22 | */ 23 | let map=new Map(); 24 | while(head){ 25 | if(map.get(head))return true 26 | map.set(head,true) 27 | head=head.next; 28 | } 29 | return false; 30 | } 31 | 32 | 33 | /** 34 | * @description: 双指针 TC:O(n) SC:O(1) 35 | * @author: JunLiangWang 36 | * @param {*} head 给定链表头节点 37 | * @return {*} 38 | */ 39 | function doublePoint(head){ 40 | /** 41 | * 本方案采用双指针的方式,定义两指针slow,fast。 42 | * slow每次移动一个节点,fast每次移动两个节点,如 43 | * 果链表存在环,则在某时刻slow与fast指针将重合, 44 | * 如何不存在环,则能正常遍历完成链表 45 | */ 46 | if(!head)return false 47 | let slow=head,fast=head.next 48 | while(fast&&fast.next){ 49 | if(slow==fast)return true 50 | slow=slow.next; 51 | fast=fast.next.next; 52 | } 53 | return false 54 | } -------------------------------------------------------------------------------- /Sword-Pointing-Offer/202.HappyNumber.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 编写一个算法来判断一个数 n 是不是快乐数。 3 | * @Author: JunLiangWang 4 | * @Date: 2023-11-23 09:25:46 5 | * @LastEditors: JunLiangWang 6 | * @LastEditTime: 2023-11-23 09:38:01 7 | */ 8 | 9 | 10 | 11 | /** 12 | * @description: 快慢指针 TC:O(logn) SC:O(1) 13 | * @author: JunLiangWang 14 | * @param {*} n 给定整数 15 | * @return {*} 16 | */ 17 | function fastSlowPoint(n){ 18 | /** 19 | * 本题使用快慢指针的方式,如果n不是一个快乐数, 20 | * 意味着无限循环也始终变不到 1。那么其整个过程 21 | * 不就形成了一个环形链表吗?那么该题则可以使用 22 | * 快慢指针判断是否环形链表。定义两指针slow,fast。 23 | * slow每次移动一个节点,fast每次移动两个节点,如 24 | * 果链表存在环,则在某时刻slow与fast指针将重合, 25 | * 如何不存在环,则能正常遍历完成链表 26 | */ 27 | 28 | /** 29 | * @description: 获得下一个数,也就是下一个指针 30 | * @author: JunLiangWang 31 | * @param {*} n 给定整数 32 | * @return {*} 33 | */ 34 | function getNextNumber(n){ 35 | // 按照题中快乐数的规则, 36 | // 计算下一个数 37 | let sum=0; 38 | while(n>0){ 39 | sum+=(Math.pow(n%10,2)) 40 | n=Math.trunc(n/10) 41 | } 42 | return sum 43 | } 44 | // 利用快慢指针判断是否循环列表 45 | let slow=n,fast=getNextNumber(n); 46 | while(fast!=1&&slow!=fast){ 47 | slow=getNextNumber(slow) 48 | fast=getNextNumber(getNextNumber(fast)) 49 | } 50 | // 返回结果 51 | return fast==1 52 | } -------------------------------------------------------------------------------- /General-Questions-Part3/129.SumRoottoLeafNumbers.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 给你一个二叉树的根节点 root ,树中每个节点都存放有一个 0 到 9 之间的数字。 3 | 每条从根节点到叶节点的路径都代表一个数字:例如,从根节点到叶节点的路径 4 | 1 -> 2 -> 3 表示数字 123 。计算从根节点到叶节点生成的 所有数字之和 。 5 | * @Author: JunLiangWang 6 | * @Date: 2023-09-21 10:53:30 7 | * @LastEditors: JunLiangWang 8 | * @LastEditTime: 2023-09-21 11:07:44 9 | */ 10 | 11 | 12 | /** 13 | * @description: 深度遍历 TC:O(n) SC:O(n) 14 | * @author: JunLiangWang 15 | * @param {*} root 给定根节点 16 | * @return {*} 17 | */ 18 | function dfs(root){ 19 | /** 20 | * 这题还是比较简单的,直接深度遍历就ok,递归参数除了子树根节点以外 21 | * 再加上一个路径即可,我记录路径的方式是采用字符串,最后到达叶子节 22 | * 点再将字符串转换为数字计算路径和 23 | */ 24 | 25 | /** 26 | * @description: 利用递归回溯实现深度遍历 27 | * @author: JunLiangWang 28 | * @param {*} root 当前子树根节点 29 | * @param {*} path 到底当前节点的路径 30 | * @return {*} 31 | */ 32 | function recursionBacktracking(root,path){ 33 | // 如果节点为空直接返回0 34 | if(!root){ 35 | return 0 36 | } 37 | // 如果为叶子节点则返回路径 38 | if(!root.left&&!root.right){ 39 | return (path+root.val)*1 40 | } 41 | // 返回路径和 42 | return recursionBacktracking(root.left,path+root.val)+recursionBacktracking(root.right,path+root.val) 43 | } 44 | // 执行递归返回结果 45 | return recursionBacktracking(root,'') 46 | } -------------------------------------------------------------------------------- /General-Questions-Part2/67.AddBinary.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 给你两个二进制字符串 a 和 b ,以二进制字符串的形式返回它们的和。 3 | * @Author: JunLiangWang 4 | * @Date: 2023-06-13 08:59:17 5 | * @LastEditors: JunLiangWang 6 | * @LastEditTime: 2023-06-13 09:19:44 7 | */ 8 | 9 | 10 | 11 | /** 12 | * @description: 双指针 TC:O(n) SC:O(1) 13 | * @author: JunLiangWang 14 | * @param {*} a 给二进制字符串1 15 | * @param {*} b 给二进制字符串2 16 | * @return {*} 17 | */ 18 | function doublePoints(a,b){ 19 | /** 20 | * 该方案利用双指针的方式,定义两个指针分别指向a,b两数组最后一个 21 | * 元素,定义一个变量记录其是否有进位情况,然后从后向前遍历两数组 22 | * 元素,将两元素值(如果存在)与进位变量相加,当前位置的结果则等于 23 | * 相加后的和对2取余,是否存在进位则等于相加后的和除以2然后向下取 24 | * 整。当两指针都超出了数组索引范围此时结束循环。最后再判断是否存 25 | * 在进位,有则在结果前加一个'1'。 26 | */ 27 | 28 | // 定义两指针 29 | let point1=a.length-1, 30 | point2=b.length-1, 31 | // 是否存在进位 32 | carry=0, 33 | // 结果 34 | out=''; 35 | // 从后到前遍历两数组元素,当两指针都超出了数组索引结束循环 36 | while(a[point1]||b[point2]){ 37 | // 将两元素值(如果存在)与进位变量相加 38 | if(a[point1])carry+=a[point1]*1; 39 | if(b[point2])carry+=b[point2]*1; 40 | // 当前位置的结果则等于相加后的和对2取余 41 | out=carry%2+out; 42 | // 否存在进位则等于相加后的和除以2然后向下取整 43 | carry=Math.floor(carry/2); 44 | point1--; 45 | point2--; 46 | } 47 | // 最后如果还存在进位,则需要在结果前加一个'1'。 48 | if(carry!=0)out='1'+out; 49 | // 返回结果 50 | return out; 51 | } -------------------------------------------------------------------------------- /General-Questions-Part3/127.WordLadder.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 给你两个单词 beginWord 和 endWord 和一个字典 wordList , 3 | 返回 从 beginWord 到 endWord 的 最短转换序列 中的 单词数 4 | 目 。如果不存在这样的转换序列,返回 0 。 5 | * @Author: JunLiangWang 6 | * @Date: 2023-09-19 11:04:04 7 | * @LastEditors: JunLiangWang 8 | * @LastEditTime: 2023-09-19 11:05:29 9 | */ 10 | 11 | 12 | /** 13 | * @description: 广度优先 TC:O(n) SC:O(n) 14 | * @author: JunLiangWang 15 | * @param {*} beginWord 开始单词 16 | * @param {*} endWord 结束单词 17 | * @param {*} wordList 单词列表 18 | * @return {*} 19 | */ 20 | function bfs(beginWord, endWord, wordList){ 21 | const wordSet = new Set(wordList); 22 | const queue = []; 23 | queue.push([beginWord, 1]); 24 | 25 | while (queue.length) { 26 | const [word, level] = queue.shift(); // 当前出列的单词 27 | if (word == endWord) { 28 | return level; 29 | } 30 | for (let i = 0; i < word.length; i++) { // 遍历当前单词的所有字符 31 | for (let c = 97; c <= 122; c++) { // 对应26个字母 32 | const newWord = word.slice(0, i) + String.fromCharCode(c) + word.slice(i + 1); // 形成新词 33 | if (wordSet.has(newWord)) { // 单词表里有这个新词 34 | queue.push([newWord, level + 1]); // 作为下一层的词入列 35 | wordSet.delete(newWord); // 避免该词重复入列 36 | } 37 | } 38 | } 39 | } 40 | return 0; // bfs结束,始终没有遇到终点 41 | } -------------------------------------------------------------------------------- /General-Questions-Part3/111.MinimumDepthOfBinaryTree.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 给定一个二叉树,找出其最小深度。最小深度是从根节点到最近叶子节点的最短路径上的节点数量。 3 | * @Author: JunLiangWang 4 | * @Date: 2023-08-23 09:06:16 5 | * @LastEditors: JunLiangWang 6 | * @LastEditTime: 2023-08-23 09:12:46 7 | */ 8 | 9 | 10 | 11 | /** 12 | * @description: 广度优先 TC:O(n) SC:O(n) 13 | * @author: JunLiangWang 14 | * @param {*} root 给定根节点 15 | * @return {*} 16 | */ 17 | function bfs(root){ 18 | /** 19 | * 叶子节点是指没有子节点的节点。因此我们仅需要使用广度优先遍历 20 | * 树,并记录层级,找到第一个无子节点的节点,然后返回层级即可。 21 | */ 22 | 23 | // 如果根节点为空,直接返回0 24 | if(!root)return 0; 25 | 26 | // 定义广度优先需要的队列 27 | let quene=[root], 28 | // 记录层级 29 | level=1; 30 | // 如果队列为空,证明已遍历完成树中所有节点 31 | while(quene.length>0){ 32 | // 记录当前队列长度,也是本次层级的 33 | // 所有节点数量,由于出队会动态影响 34 | // 到队列的长度,因此此处记录,就可 35 | // 以知道本次层级的节点是否全部出队 36 | let size=quene.length; 37 | // 遍历当前层级的所有节点 38 | while(size-->0){ 39 | // 出队 40 | let node=quene.shift(); 41 | // 如果遇到叶子节点则直接返回记录的层级 42 | if(!node.left&&!node.right)return level; 43 | // 否则将出队节点不为空的左右节点再次入队 44 | if(node.left) quene.push(node.left); 45 | if(node.right)quene.push(node.right); 46 | } 47 | // 更新层级 48 | level++; 49 | } 50 | // 返回层级 51 | return level; 52 | } -------------------------------------------------------------------------------- /Sword-Pointing-Offer/167.TwoSumII.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 给你一个整数数组 numbers ,该数组已按 非递减顺序排列 , 3 | 请你从数组中找出满足相加之和等于目标数 target 的两个数。 4 | * @Author: JunLiangWang 5 | * @Date: 2023-11-15 09:17:06 6 | * @LastEditors: JunLiangWang 7 | * @LastEditTime: 2023-11-15 09:25:08 8 | */ 9 | 10 | 11 | /** 12 | * @description: 双指针 TC:O(n) SC:O(1) 13 | * @author: JunLiangWang 14 | * @param {*} numbers 给定数组 15 | * @param {*} target 给定目标值 16 | * @return {*} 17 | */ 18 | function doublePoint(numbers, target){ 19 | /** 20 | * 本题给定数组为有序数组,且是找到两个不同元素的和 21 | * 为目标值,因此使用双指针的方式就非常合适。 22 | * 23 | * 定义左右两指针,指向数组左右两端,当两指针所指元素 24 | * 和等于目标值直接返回指针索引即可;当两指针所指元素 25 | * 小于目标值,由于数组是升序的,因此仅将左指针向右移 26 | * 动一位即可增大两元素和;当两指针所指元素大于目标值 27 | * ,由于数组是升序,因此仅当将右指针向左移动一位即可 28 | * 降低两元素和。 29 | */ 30 | 31 | //定义左右两指针,指向数组左右两端 32 | let left=0,right=numbers.length-1; 33 | // 当左指针超出右指针,证明数组遍历完成 34 | while(lefttarget) right--; 43 | // 小于目标值,由于数组是升序的, 44 | // 因此仅将左指针向右移动一位即 45 | // 可增大两元素和 46 | else left++; 47 | } 48 | 49 | return null; 50 | } -------------------------------------------------------------------------------- /Sword-Pointing-Offer/173.BinarySearchTreeIterator.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 实现一个二叉搜索树迭代器类BSTIterator ,表示一个 3 | 按中序遍历二叉搜索树(BST)的迭代器 4 | * @Author: JunLiangWang 5 | * @Date: 2023-12-09 09:20:34 6 | * @LastEditors: JunLiangWang 7 | * @LastEditTime: 2023-12-09 09:28:19 8 | */ 9 | 10 | 11 | /** 12 | * 本方案使用深度优先的方式,首先使用递归实现 13 | * 深度优先中序遍历,然后使用数组记录遍历的树 14 | * 的节点,最后定义一个索引,表示next()函数已 15 | * 经调用到的数组索引位置,当索引超出数组大小 16 | * 即hasNext()函数为false 17 | */ 18 | 19 | var BSTIterator = function (root) { 20 | // 记录中序遍历的节点 21 | this.searchList = [] 22 | // 记录next()函数已经调用到的数组的索引 23 | this.index = 0; 24 | let that = this 25 | /** 26 | * 递归回溯实现深度优先中序遍历 27 | */ 28 | function dfs(root) { 29 | if (!root) return 30 | dfs(root.left) 31 | // 记录遍历的节点 32 | that.searchList.push(root.val) 33 | dfs(root.right) 34 | } 35 | // 执行深度优先中心遍历 36 | dfs(root) 37 | }; 38 | 39 | BSTIterator.prototype.next = function () { 40 | // 当索引未超过数组大小 41 | if (this.index < this.searchList.length) { 42 | // 索引+1 43 | this.index++; 44 | // 返回元素 45 | return this.searchList[this.index - 1]; 46 | } 47 | return null 48 | }; 49 | 50 | BSTIterator.prototype.hasNext = function () { 51 | // 当索引未超过数组大小,证明还有下一个元素,返回true 52 | if (this.index < this.searchList.length) return true 53 | // 否则,返回false 54 | else return false 55 | }; -------------------------------------------------------------------------------- /General-Questions-Part2/90.SubsetsII.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。 3 | 解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。 4 | * @Author: JunLiangWang 5 | * @Date: 2023-07-21 10:43:29 6 | * @LastEditors: JunLiangWang 7 | * @LastEditTime: 2023-07-21 10:46:41 8 | */ 9 | 10 | 11 | /** 12 | * @description: 递归回溯 TC:O(2^n) SC:O(n^2) 13 | * @author: JunLiangWang 14 | * @param {*} nums 给定数组 15 | * @return {*} 16 | */ 17 | function recursionBackTracking(nums){ 18 | /** 19 | * 该题与 78.子集类似,可以使用递归回溯枚举所有组合 20 | * 但由于该题存在重复元素,因此我们需要对数组先进行 21 | * 排序,然后递归过程去重。 22 | */ 23 | 24 | // 对数组进行排序 25 | nums.sort(); 26 | // 输出数组 27 | let outArray=[]; 28 | /** 29 | * @description: 递归 30 | * @author: JunLiangWang 31 | * @param {*} index 当前索引 32 | * @param {*} selectNum 当前组合 33 | * @return {*} 34 | */ 35 | function recursion(index,selectNum){ 36 | // 向输出数组添加当前组合 37 | outArray.push(selectNum); 38 | // 从当前索引遍历继续递归,直到超出nums长度 39 | for(let i=index;iselectNode.next.val){ 33 | let nextNode=selectNode.next.next 34 | selectNode.next.next=compareNode.next 35 | compareNode.next=selectNode.next 36 | selectNode.next=nextNode 37 | isChange=true; 38 | break; 39 | } 40 | compareNode=compareNode.next 41 | } 42 | if(!isChange)selectNode=selectNode.next 43 | } 44 | return HEAD.next 45 | } -------------------------------------------------------------------------------- /Sword-Pointing-Offer/530.MinimumAbsoluteDifferenceinBST.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 给你一个二叉搜索树的根节点 root ,返回 树中任意两不同节点值之间的最小差值 。 3 | 差值是一个正数,其数值等于两值之差的绝对值。 4 | * @Author: JunLiangWang 5 | * @Date: 2023-12-15 09:37:05 6 | * @LastEditors: JunLiangWang 7 | * @LastEditTime: 2023-12-15 09:43:48 8 | */ 9 | 10 | 11 | /** 12 | * @description: 深度优先 TC:O(N) SC:O(n) 13 | * @author: JunLiangWang 14 | * @param {*} root 给定树的根节点 15 | * @return {*} 16 | */ 17 | function dfs(root){ 18 | /** 19 | * 本方案使用深度优先中序遍历二叉树的节点,因为 20 | * 中序遍历二叉搜索树其节点值是升序的,因此我们 21 | * 通过不断比较上一个节点与当前节点的差,即可获 22 | * 得不同节点之间的最小差值 23 | */ 24 | 25 | // 记录上一个节点 26 | let lastValue=0, 27 | // 记录最小差值 28 | min=Number.MAX_VALUE, 29 | // 是否第一次遍历 30 | isFirst=true; 31 | /** 32 | * @description: 递归回溯实现深度优先中序遍历 33 | * @author: JunLiangWang 34 | * @param {*} root 子树根节点 35 | * @return {*} 36 | */ 37 | function recursion(root){ 38 | // 根节点为空,直接return 39 | if(!root)return 40 | // 递归左子树 41 | recursion(root.left) 42 | // 如果不是第一个节点,则获得两节点差值,并与以往差值 43 | // 比较取最小值 44 | if(!isFirst) min=Math.min(min,root.val-lastValue) 45 | // 更新上个节点值为当前节点值 46 | lastValue=root.val 47 | isFirst=false 48 | // 递归右子树 49 | recursion(root.right) 50 | } 51 | // 执行递归 52 | recursion(root) 53 | // 返回结果 54 | return min; 55 | } -------------------------------------------------------------------------------- /General-Questions-Part1/21.mergeTwoSortedList.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description:将两个升序链表合并为一个新的升序链表并返回。 3 | * @Author: JunLiangWang 4 | * @Date: 2023-03-18 15:05:33 5 | * @LastEditors: JunLiangWang 6 | * @LastEditTime: 2023-03-18 15:31:11 7 | */ 8 | 9 | 10 | /** 11 | * @description: 递归法 TC:O(m+n) SC:O(m+n) 12 | * @author: JunLiangWang 13 | * @param {*} list1 有序链表1 14 | * @param {*} list2 有序链表2 15 | * @return {*} 16 | */ 17 | function recursion(list1,list2) 18 | { 19 | if(!list1)return list2 20 | if(!list2)return list1 21 | if(list1.val=root.val)return false; 40 | // 更新上一个节点值为当前节点值 41 | lastValue=root.val 42 | // 继续递归右子树,看其是否为二叉搜索树 43 | let rightTreeIsBST=recursion(root.right) 44 | // 如果左右子树都为二叉搜索树,则证明该节点作为根的子树为二叉搜索树 45 | return leftTreeIsBST&rightTreeIsBST 46 | } 47 | 48 | // 执行递归 49 | return recursion(root) 50 | } 51 | -------------------------------------------------------------------------------- /General-Questions-Part3/122.BestTimetoBuyandSellStockII.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 你一个整数数组 prices ,其中 prices[i] 表示某支股票第 i 天的价格。 3 | 在每一天,你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持 4 | 有 一股 股票。你也可以先购买,然后在 同一天 出售。返回 你能获得的最 5 | 大利润。 6 | * @Author: JunLiangWang 7 | * @Date: 2023-09-08 09:31:14 8 | * @LastEditors: JunLiangWang 9 | * @LastEditTime: 2023-09-08 09:43:51 10 | */ 11 | 12 | 13 | /** 14 | * @description: 贪心算法 TC:O(n) SC:O(1) 15 | * @author: JunLiangWang 16 | * @param {*} prices 给定数组 17 | * @return {*} 18 | */ 19 | function greedy(prices) { 20 | /** 21 | * 该方案使用贪心算法,我们可以根据第二天 22 | * 涨跌情况来决定今天是否购入股票,如果第 23 | * 二天跌了,今天就不购入,如果第二天涨了 24 | * 今天则购入。 25 | * 26 | * 我们假设购入了第一天的股票,如果第二天涨 27 | * 了则记录利润,然后再次购入第二天的股票。 28 | * 如果跌了,我们则只购入第二天的股票,上述 29 | * 假设失败。以此类推,直至最后一天。 30 | * 31 | */ 32 | 33 | // 如果天数少于两天无法盈利,直接返回0 34 | if (prices.length < 2) return 0 35 | // 我们假设购入第一天股票 36 | let buy = prices[0], 37 | // 记录利润 38 | profit = 0; 39 | // 从第二天开始遍历直到最后一天 40 | for (let i = 1; i < prices.length; i++) { 41 | // 如果明天的股票涨了,假设成立 42 | if (prices[i] > buy) { 43 | // 计算利润 44 | profit += (prices[i] - buy) 45 | } 46 | // 如果明天的股票跌了,假设失败 47 | // 我们不需要进行任何操作 48 | 49 | // 将购入股票刷新为明天的股票,继续遍历 50 | buy = prices[i] 51 | } 52 | // 返回利润 53 | return profit; 54 | } -------------------------------------------------------------------------------- /General-Questions-Part1/35.searchInsertionLocation.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 给定一个升序数组和一个目标值,在数组中找到目标值,并返回其索引。 3 | 如果目标值不存在于数组中,返回它将会被按顺序插入的位置。 4 | * @Author: JunLiangWang 5 | * @Date: 2023-04-20 09:06:22 6 | * @LastEditors: JunLiangWang 7 | * @LastEditTime: 2023-04-20 09:17:33 8 | */ 9 | 10 | 11 | /** 12 | * @description: 二分查找 TC:O(logn) SC:O(1) 13 | * @author: JunLiangWang 14 | * @param {*} nums 输入数组 15 | * @param {*} target 目标值 16 | * @return {*} 17 | */ 18 | function binarySearch(nums,target){ 19 | // 初始化左指针为0,右指针为nums最后一个元素 20 | let left=0,right=nums.length-1,middle=0; 21 | // 当left>right证明比对完成数组元素,跳出循环 22 | while(left<=right) 23 | { 24 | // 计算中间指针位置 25 | middle=Math.floor((left+right)/2); 26 | // 如果middle所指值等于target,直接返回其位置 27 | if(nums[middle]==target)return middle; 28 | // 如果middle所指值大于target,由于数组为升序, 29 | // nums[middle]>target,证明区间[middle,right] 30 | // 元素都大于target,因此舍去,继续在[left,middle-1] 31 | // 区间二分查找 32 | if(nums[middle]>target) right=middle-1; 33 | // 反之nums[middle]target,则在middle插入target即可 42 | return nums[middle]>target?middle:middle+1; 43 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JS-Algorithm 2 | Learn a JS algorithm every day.(每天学习一个JS算法) 3 | 4 | 5 | 6 | 7 | ## Prerequisite Knowledge(前置知识) 8 | 9 | | 题目编号(topic number) | 文件名(file name) | 描述 | description | 10 | | ---------------------- | ------------------------------------------------------------ | ---------------------------- | ------------------------------------------------------------ | 11 | | 1~50 | [General-Questions-Part1](https://github.com/JunLiangWangX/js-algorithm/tree/main/General-Questions-Part1) | 普通题:题1到题50归档集合 | GeneralQuestion:Question 1 to Question 50 archive collection | 12 | | 51~100 | [General-Questions-Part2](https://github.com/JunLiangWangX/js-algorithm/tree/main/General-Questions-Part2) | 普通题:题51到题100归档集合 | GeneralQuestion:Question 51 to Question 100 archive collection | 13 | | 101~150 | [General-Questions-Part3](https://github.com/JunLiangWangX/js-algorithm/tree/main/General-Questions-Part3) | 普通题:题101到题150归档集合 | GeneralQuestion:Question 101 to Question 150 archive collection | 14 | | Sword Pointing Offer | [Sword-Pointing-Offer](https://github.com/JunLiangWangX/js-algorithm/tree/main/Sword-Pointing-Offer) | 剑指offer系列 | Sword pointing offer series | 15 | 16 | 17 | 18 | ## Advanced knowledge(进阶知识) 19 | 20 | [Click to View](https://wangjunliang.com/JS-Algorithm) -------------------------------------------------------------------------------- /Sword-Pointing-Offer/152.MaximumProductSubarray.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 给你一个整数数组 nums ,请你找出数组中乘积最大的非空连续子数组 3 | (该子数组中至少包含一个数字),并返回该子数组所对应的乘积。 4 | * @Author: JunLiangWang 5 | * @Date: 2023-11-02 10:49:52 6 | * @LastEditors: JunLiangWang 7 | * @LastEditTime: 2023-11-02 10:53:40 8 | */ 9 | 10 | 11 | /** 12 | * @description: 暴力破解 TC:O(n^2) SC:O(1) 13 | * @author: JunLiangWang 14 | * @param {*} nums 给定数组 15 | * @return {*} 16 | */ 17 | function bruteForce(nums){ 18 | /** 19 | * 本方案使用暴力破解的方式,两次遍历数组元素 20 | * 比较其乘积获取最大乘积 21 | */ 22 | let maxProduct=nums[0] 23 | for(let i=0;is与s->pattern的映射关系,最终遍历时 22 | * 不断判断两者映射是否相同即可 23 | */ 24 | let sIndex = 0, pToSMap = new Map(), sToPMap = new Map(); 25 | // 遍历字符串pattern 26 | for (let i = 0; i < pattern.length; i++) { 27 | // 遍历字符串s,找到空格分割的单词 28 | let str = '' 29 | for (; sIndex < s.length; sIndex++) { 30 | if (s[sIndex] !== ' ') str += s[sIndex] 31 | else if (str != '') break; 32 | } 33 | // 如果单词为空并且字符串pattern还未遍历完成, 34 | // 证明两者无法映射,直接返回false 35 | if (str == '') return false 36 | // 拿到各自映射的字符 37 | let pToSStr = pToSMap.get(pattern[i]), 38 | sToPStr = sToPMap.get(str); 39 | // 判断是否相等,不相等证明两者无法映射,直接返回false 40 | if ((pToSStr && pToSStr != str) || (sToPStr && sToPStr != pattern[i])) return false 41 | pToSMap.set(pattern[i], str) 42 | sToPMap.set(str, pattern[i]) 43 | } 44 | // 返回结果 45 | return sIndex == s.length 46 | } -------------------------------------------------------------------------------- /General-Questions-Part2/55.JumpGame.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 给定一个非负整数数组 nums ,你最初位于数组的 第一个下标 。 3 | 数组中的每个元素代表你在该位置可以跳跃的最大长度。 4 | 判断你是否能够到达最后一个下标。 5 | * @Author: JunLiangWang 6 | * @Date: 2023-05-30 09:35:33 7 | * @LastEditors: JunLiangWang 8 | * @LastEditTime: 2023-05-30 09:48:32 9 | */ 10 | 11 | 12 | /** 13 | * @description: 贪心 TC:O(n) SC:O(1) 14 | * @author: JunLiangWang 15 | * @param {*} nums 给定数组 16 | * @return {*} 17 | */ 18 | function greedy(nums){ 19 | /** 20 | * 该题与跳跃游戏II相似,跳跃游戏II是记录步数,而该题则是 21 | * 判断能否到达,因此该题同样可以使用贪心算法。 22 | * 23 | * 我们遍历数组元素,然后获得当前元素能跳转到的最大索引值 24 | * (当前元素能抵达的索引nums[i]+i与之前记录的能抵达的最大 25 | * 索引值maxJumpIndex的最大值)并赋值给maxJumpIndex,当maxJumpIndex 26 | * 大于等于了数组最大索引,证明能抵达终点;否则,如果maxJumpIndex 27 | * 小于了当前元素索引,证明已无法抵达当前索引值,也就是说无法 28 | * 抵达终点。 29 | */ 30 | 31 | 32 | // 记录最大能抵达的索引 33 | let maxJumpIndex=0; 34 | // 遍历数组元素 35 | for(let i=0;i=nums.length-1)return true; 44 | } 45 | // 否则证明已无法抵达当前索引值,也就是说无法抵达终点,返回结果false。 46 | else return false; 47 | } 48 | } -------------------------------------------------------------------------------- /General-Questions-Part1/22.generateParenthesis.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 请你设计一个函数,生成n有效的括号组合。 3 | * @Author: JunLiangWang 4 | * @Date: 2023-03-27 10:10:36 5 | * @LastEditors: JunLiangWang 6 | * @LastEditTime: 2023-03-27 10:38:06 7 | */ 8 | 9 | 10 | /** 11 | * @description: 深度优先+剪枝算法 TC:O(2^n) SC:O(n) 12 | * @author: JunLiangWang 13 | * @param {*} n 输入括号对数 14 | * @return {*} 15 | */ 16 | function DFSAndPruningWay(n){ 17 | // 该方案是由深度优先遍历+剪枝的方法实现,利用深度优先算法不断向字符串追加 18 | // "("以及")",直至左或右括号数量超出范围,或左右括号数量等于n。当左括号数 19 | // 量大于右括号数量时,此时不可能形成有效括号组合因此直接舍去(剪枝),例:)、 20 | // ())、(()))等。 21 | 22 | // 定义输出数组 23 | let outArray=[] 24 | /** 25 | * @description: 定义深度优先算法 26 | * @author: JunLiangWang 27 | * @param {*} str 当前左右括号组合 28 | * @param {*} left 当前左括号数量 29 | * @param {*} right 当前右括号数量 30 | * @return {*} 31 | */ 32 | function DFSandPruning(str,left,right) 33 | { 34 | // 当左或右括号数量超出范围,以及当左括号数量大于右括号数量时, 35 | // 此时无论如何追究括号都不可能再形成有效括号组合因此直接舍去(剪枝) 36 | if(left>n||right>n||left>right)return ; 37 | // 当左右括号数量等于规定范围,证明此时括号数量达到范围,且组合有效,则直接向输出数组添加结果 38 | if(left==n&&right==n) 39 | { 40 | outArray.push(str) 41 | return ; 42 | } 43 | // 遍历左节点 44 | DFSandPruning(str+")",left+1,right) 45 | // 遍历右节点 46 | DFSandPruning(str+"(",left,right+1) 47 | } 48 | //调用深度优先+剪枝算法 49 | DFSandPruning("",0,0) 50 | // 返回结果 51 | return outArray 52 | } -------------------------------------------------------------------------------- /General-Questions-Part3/108.ConvertSortedArraytoBinarySearchTree.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 给你一个整数数组 nums ,其中元素已经按 升序 排列,请你将其转换为一棵 3 | 高度平衡 二叉搜索树。高度平衡 二叉树是一棵满足「每个节点的左右两个子 4 | 树的高度差的绝对值不超过 1 」的二叉树。 5 | * @Author: JunLiangWang 6 | * @Date: 2023-08-18 08:43:35 7 | * @LastEditors: JunLiangWang 8 | * @LastEditTime: 2023-08-18 09:17:00 9 | */ 10 | 11 | 12 | 13 | /** 14 | * @description: 递归回溯+二分 TC:O(n) SC:O(1) 15 | * @author: JunLiangWang 16 | * @param {*} nums 给定升序数组 17 | * @return {*} 18 | */ 19 | function recursionBackTrackingAndBinary(nums){ 20 | 21 | /** 22 | * 该方案使用递归回溯+二分的方式,对于一棵二叉搜索 23 | * 树而言,其中序遍历则为单调递增的数组,因此我们可 24 | * 以将给定数组nums看作为该树中序遍历的结果,但是我 25 | * 们如何确定其根节点呢?题中说是该树一个高度平衡的 26 | * 二叉树因此我们仅需要取数组的中间值作为根即可构造 27 | * 一棵高度平衡的二叉树 28 | * 29 | */ 30 | 31 | /** 32 | * @description: 递归 33 | * @author: JunLiangWang 34 | * @param {*} start 开始索引 35 | * @param {*} end 结束索引 36 | * @return {*} 37 | */ 38 | function recursion(start,end){ 39 | // 如果开始索引大于结束索引证明遍历完成 40 | if(start>end)return null 41 | // 取得[start,end]的中间值,作为根元素 42 | let center=Math.floor((end+start)/2) 43 | // 构造一棵树,其根节点为中间值,左节点为[start,center-1]区间 44 | // 继续递归的结果,右节点为[center+1,end]区间继续递归的结果 45 | return new TreeNode(nums[center],recursion(start,center-1), recursion(center+1,end)) 46 | } 47 | // 执行递归,返回结果 48 | return recursion(0,nums.length-1) 49 | } -------------------------------------------------------------------------------- /General-Questions-Part3/143.ReorderList.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 重排链表 3 | * @Author: JunLiangWang 4 | * @Date: 2023-10-20 10:04:29 5 | * @LastEditors: JunLiangWang 6 | * @LastEditTime: 2023-10-20 10:17:26 7 | */ 8 | 9 | 10 | /** 11 | * @description: 递归回溯 TC:O(n) SC:O(1) 12 | * @author: JunLiangWang 13 | * @param {*} head 给定链表头节点 14 | * @return {*} 15 | */ 16 | function recursionBacktracking(head) { 17 | /** 18 | * 本方案采用递归回溯的方式,先使用递归 19 | * 遍历链表,直至最后一个节点,然后利用 20 | * 回溯来改变链表顺序 21 | */ 22 | 23 | // 临时头节点 24 | let tempNode = head, 25 | // 是否完成顺序变更 26 | isSuccess = false 27 | 28 | /** 29 | * @description: 递归回溯 30 | * @author: JunLiangWang 31 | * @param {*} node 当前节点 32 | * @return {*} 33 | */ 34 | function recursion(node) { 35 | // 如果节点为空,直接return 36 | if (!node) return 37 | // 继续递归 38 | recursion(node.next) 39 | // 直到到达链表最后一个节点,开始回溯 40 | 41 | // 如果链表已经完成了顺序变更,直接return 42 | if (isSuccess) return 43 | // 当临时头节点等于当前节点 或者 其下一个节点 44 | // 等于当前节点,证明链表顺序重新排列完成 45 | if (tempNode == node || tempNode.next == node) { 46 | node.next = null 47 | isSuccess = true 48 | return 49 | } 50 | // 变更节点顺序 51 | 52 | node.next = tempNode.next; 53 | tempNode.next = node; 54 | tempNode = node.next; 55 | } 56 | // 执行递归回溯 57 | recursion(head) 58 | // 返回结果 59 | return head 60 | } 61 | -------------------------------------------------------------------------------- /Sword-Pointing-Offer/289.GameofLife.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 生命游戏 3 | * @Author: JunLiangWang 4 | * @Date: 2023-11-17 09:51:50 5 | * @LastEditors: JunLiangWang 6 | * @LastEditTime: 2023-11-17 09:59:14 7 | */ 8 | 9 | 10 | /** 11 | * @description: 额外的状态 TC:O(n^2) SC:O(1) 12 | * @author: JunLiangWang 13 | * @param {*} board 给定二维数组 14 | * @return {*} 15 | */ 16 | function additionalStates(board){ 17 | /** 18 | * 本题较为简单,直接再定义一个矩阵,然后 19 | * 遍历根据规则生成即可,但想要使用原地算 20 | * 法则需要定义两个额外的状态,让后续数组 21 | * 知道该值的原始值,我们将0转变为1的值更 22 | * 改为2,将1转变为0的值更改为3,后面我们 23 | * 遍历时则把2看作0,把3看作1即可,最后再 24 | * 遍历数组,把2改为1,把3改为0即可。 25 | */ 26 | for(let i=0;i=board.length||l<0||l>=board[i].length|| 33 | board[k][l]==2||board[k][l]==0)continue; 34 | sum+=1 35 | } 36 | } 37 | if(board[i][j]==0&&sum==3)board[i][j]=2; 38 | if(board[i][j]==1&&(sum<2||sum>3))board[i][j]=3; 39 | } 40 | } 41 | for(let i=0;i1||!result){ 43 | result=false; 44 | return 0; 45 | } 46 | // 否则证明两树高度差小于等于1,该子树为一棵平衡二叉树 47 | // 返回其最大子树高度+1 48 | return Math.max(leftNodeCount,rightNodeCount)+1; 49 | } 50 | // 执行递归 51 | recursion(root) 52 | // 返回结果 53 | return result; 54 | } -------------------------------------------------------------------------------- /General-Questions-Part3/139.WordBreak.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 给你一个字符串 s 和一个字符串列表 wordDict 作为字典。 3 | 请你判断是否可以利用字典中出现的单词拼接出 s 。 4 | * @Author: JunLiangWang 5 | * @Date: 2023-10-14 09:27:57 6 | * @LastEditors: JunLiangWang 7 | * @LastEditTime: 2023-10-14 09:38:39 8 | */ 9 | 10 | 11 | /** 12 | * @description: 动态规划 TC:O(n^2) SC:O(n) 13 | * @author: JunLiangWang 14 | * @param {*} s 给定字符串 15 | * @param {*} wordDict 给定单词数组 16 | * @return {*} 17 | */ 18 | function DP(s, wordDict){ 19 | /** 20 | * 本方案使用动态规划的方式,对于字符串S而言其能否 21 | * 由wordDict单词组成可以将其问题的规模缩小,即为: 22 | * 对于字符串S[0至i]而言,能否由wordDict单词组成。 23 | * 24 | * 如何判断字符串S[0至i]是否由wordDict组成呢?我们 25 | * 可以将其切分成两个部分,s[0至j]与s[j至i],s[0至j] 26 | * 我们之前是判断过的,因此可以直接得出答案,最终我们 27 | * 仅需再次判断s[j至i]是否能由wordDict组成即可,最后 28 | * 我们可以得出转移方程: 29 | * 30 | * dp[i]=dp[j]&&check(s[j至i]),dp[i]表示s[0至i]能否被 31 | * wordDict单词组成 32 | * 33 | * 根据转移方程我们即可写出代码 34 | */ 35 | 36 | // 定义DP数组 37 | let dpArray=new Array(s.length+1).fill(false); 38 | // 初始化0为true 39 | dpArray[0]=true; 40 | 41 | // 遍历字符串 42 | for(let i=0;i<=s.length;i++){ 43 | // 选择切割位置 44 | for(let j=0;j a[0] - b[0]) 37 | // 记录当前交集的end位置 38 | let end = points[0][1], 39 | // 记录当前有多少无交集元素,由于从1开始的,因此初始化为1 40 | count = 1; 41 | // 从1遍历数组元素 42 | for (let i = 1; i < points.length; i++) { 43 | // 如果当前交集的结束位置,超过了后一个元素的开始位置 44 | // 证明两者还存在交集,此时更新两者交集的结束位置 45 | if (end >= points[i][0]) end = Math.min(points[i][1], end) 46 | // 反之则没有交集 47 | else { 48 | // count+1 49 | count++; 50 | // 将当前交集结束位置更新为当前元素的结束位置 51 | end = points[i][1] 52 | } 53 | } 54 | // 返回结果 55 | return count; 56 | } -------------------------------------------------------------------------------- /General-Questions-Part2/64.MinimumPathSum.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 给定一个包含非负整数的 m x n 网格 grid ,请找出一条从左上角 3 | 到右下角的路径,使得路径上的数字总和为最小。说明:每次只能向 4 | 下或者向右移动一步。 5 | * @Author: JunLiangWang 6 | * @Date: 2023-06-09 08:49:43 7 | * @LastEditors: JunLiangWang 8 | * @LastEditTime: 2023-06-09 09:07:54 9 | */ 10 | 11 | 12 | 13 | /** 14 | * @description: 动态规划 TC:O(n^2) SC:O(n^2) 15 | * @author: JunLiangWang 16 | * @param {*} grid 给定m*n的网格 17 | * @return {*} 18 | */ 19 | function dp(grid){ 20 | 21 | /** 22 | * 该方案使用动态规划的方式,定义一个m*n的矩阵(DPArray),其中矩阵的 23 | * 某一个元素,例如DPArray[i][j]表示到达i行j列(记作[i,j])的最小和, 24 | * 已知每次只能向下/向右移动一步,因此到达[i,j]的最小和就等于[i,j] 25 | * 的当前值加上(到达上一格[i-1,j]的最小和与到达左一格[i,j-1]的最小和 26 | * 的最小值),即: 27 | * DPArray[i][j]=grid[i][j]+Min(DPArray[i-1][j],DPArray[i][j-1]) 28 | * 最终DPArray最后一个元素则为达到终点的最小和。 29 | * 30 | * 后面的代码中我之所以是定义(m+1)*(n+1)的矩阵(DPArray),是因为考虑到 31 | * 能够方便获取到DPArray[i-1][j]与DPArray[i][j-1]的值。 32 | * 33 | */ 34 | 35 | // 定义一个(m+1)*(n+1)的矩阵 36 | let m=grid.length,n=grid[0].length, 37 | DPArray=new Array(m+1).fill(0).map(()=>new Array(n+1).fill(Infinity)); 38 | // 赋值[0,1]元素为0, 39 | DPArray[0][1]=0; 40 | 41 | // 从(1至m,1至n)遍历数组 42 | for(let i=1;i<=m;i++){ 43 | for(let j=1;j<=n;j++){ 44 | // 到达[i,j]的最小和就等于[i,j]的当前值加上(到达上一格[i-1,j]的 45 | // 最小和与到达左一格[i,j-1]的最小和的最小值) 46 | DPArray[i][j]=grid[i-1][j-1]+Math.min(DPArray[i-1][j],DPArray[i][j-1]); 47 | } 48 | } 49 | 50 | // 返回结果 51 | return DPArray[m][n]; 52 | } -------------------------------------------------------------------------------- /Sword-Pointing-Offer/380.InsertDeleteGetRandom.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 实现RandomizedSet 类: 3 | 1.RandomizedSet() 初始化 RandomizedSet 对象 4 | 2.bool insert(int val) 当元素 val 不存在时,向集合中插入该项,并返回 true ;否则,返回 false 。 5 | 3.bool remove(int val) 当元素 val 存在时,从集合中移除该项,并返回 true ;否则,返回 false 。 6 | 4.int getRandom() 随机返回现有集合中的一项(测试用例保证调用此方法时集合中至少存在一个元素)。每个元素应该有 相同的概率 被返回。 7 | 你必须实现类的所有函数,并满足每个函数的 平均 时间复杂度为 O(1) 。 8 | * @Author: JunLiangWang 9 | * @Date: 2023-11-10 10:13:45 10 | * @LastEditors: JunLiangWang 11 | * @LastEditTime: 2023-11-10 10:18:44 12 | */ 13 | 14 | /** 15 | * 本方案使用map+array的方式,利用map实现插入/删除,并且 16 | * 满足O(1)的时间复杂度。而getRandom则利用数组,在插入元 17 | * 素时,同样在数组中插入相应的元素,并在map中记录插入的 18 | * 元素的索引。删除元素时,将数组尾部元素移动到删除的元素 19 | * 的位置,并改变该元素在map中的记录的索引,然后删除数组与 20 | * map中的元素 21 | */ 22 | var RandomizedSet = function () { 23 | this.list = []; 24 | this.map = new Map(); 25 | }; 26 | RandomizedSet.prototype.insert = function (val) { 27 | if (this.map.has(val)) return false 28 | this.map.set(val, this.list.length) 29 | this.list.push(val) 30 | return true 31 | }; 32 | RandomizedSet.prototype.remove = function (val) { 33 | let index = this.map.get(val) 34 | if (index == undefined) return false 35 | this.list[index] = this.list[this.list.length - 1] 36 | this.map.set(this.list[index], index); 37 | this.list.pop(); 38 | this.map.delete(val) 39 | return true 40 | }; 41 | RandomizedSet.prototype.getRandom = function () { 42 | return this.list[Math.floor(Math.random() * this.list.length)]; 43 | }; -------------------------------------------------------------------------------- /General-Questions-Part1/07.integerInversion.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 给你一个32位的有符号整数x ,返回将x中的数字部分反转后的结果。如果反转后整数超过32位的有 3 | 符号整数的范围 [−2^31,  2^31 − 1],就返回0。 4 | * @Author: JunLiangWang 5 | * @Date: 2023-02-27 09:48:38 6 | * @LastEditors: JunLiangWang 7 | * @LastEditTime: 2023-02-27 10:07:43 8 | */ 9 | 10 | 11 | /** 12 | * @description: 利用字符串进行翻转 TC:O(n) SC:O(1) 13 | * @author: JunLiangWang 14 | * @param {*} x 给定数值 15 | * @return {*} 16 | */ 17 | function stringInversion(x) 18 | { 19 | // 判断是否大于等于零 20 | let isGreateZero=x>=0 21 | // 数值范围[-2^31,2^31-1],由于上方已判断正负,此处剔除了负号,后续好作比较 22 | let maxString=isGreateZero?'2147483647':'2147483648' 23 | // 如果大于等于零则全部转换为字符串,如果小于零,则转换字符串后剔除负号 24 | let xString=isGreateZero?x.toString():x.toString().substring(1) 25 | 26 | // 如果x长度大于maxString的长度,无论如何转换都会超出数值范围,因此直接返回0 27 | if(xString.length>maxString.length)return 0 28 | let isCompare=false 29 | // 当x长度等于maxString的长度时,翻转时才需要比较 30 | if(xString.length==maxString.length)isCompare=true 31 | let reverseString='' 32 | for(let i=xString.length-1;i>=0;i--) 33 | { 34 | let currentChart=xString[i] 35 | // 是否仍需要比较 36 | if(isCompare) 37 | { 38 | // 如果当前字符数值大于最大范围的数值,证明已超出数值范围,直接返回0 39 | if(currentChart*1>maxString[maxString.length-i-1])return 0 40 | // 如果当前字符小于最大范围的数值,证明翻转后数值仍小于数值范围,后续无需再进行比较 41 | if(currentChart*1 0 ? n : -n, 26 | result = 1; 27 | while (exponent-- > 0) { 28 | result *= x; 29 | } 30 | return n > 0 ? result : 1 / result; 31 | } 32 | 33 | 34 | /** 35 | * @description: 快速幂 TC:O(logn) SC:O(1) 36 | * @author: JunLiangWang 37 | * @param {*} x 给定幂运算指数 38 | * @param {*} n 给定幂运算底数 39 | * @return {*} 40 | */ 41 | function fastPower(x, n) { 42 | /** 43 | * 本方案采用快速幂,上述暴力破解算法是一个x逼近x^n,我们可以 44 | * 考虑采用成倍递增的方式逼近x^n,即x -> x^2 -> x^4 -> x^8 45 | */ 46 | 47 | // 指数为负数转为正数处理 48 | let exponent = n > 0 ? n : -n, 49 | //  初始化结果为1 50 | result = 1; 51 | // 当指数小于等于0,证明 52 | while (exponent > 0) { 53 | // 如果指数二进制表示的最低位为1,那么需要计入结果统计 54 | if (exponent % 2 === 1) result *= x 55 | // 成倍递增逼近 56 | x *= x 57 | // 舍弃指数二进制表示的最低位,这样我们每次只要判断最低位即可 58 | exponent = Math.floor(exponent / 2) 59 | } 60 | // 返回结果 61 | return n > 0 ? result : 1 / result; 62 | } -------------------------------------------------------------------------------- /General-Questions-Part2/74.Search2DMatrix.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 编写一个高效的算法来判断 m x n 升序矩阵中,是否存在一个目标值。 3 | * @Author: JunLiangWang 4 | * @Date: 2023-06-21 09:02:22 5 | * @LastEditors: JunLiangWang 6 | * @LastEditTime: 2023-06-21 09:17:42 7 | */ 8 | 9 | 10 | 11 | /** 12 | * @description: 二分法 TC:O(logn) SC:O(1) 13 | * @author: JunLiangWang 14 | * @param {*} matrix 给定矩阵 15 | * @param {*} target 给定目标值 16 | * @return {*} 17 | */ 18 | function binary(matrix, target) { 19 | /** 20 | * 本方案使用二分法,二维矩阵(m行,n列)可看作一个一维矩 21 | * 阵 ,其长度为 m*n,当我们选择一维矩阵的某个元素的下 22 | * 标时(例如index),其在二维矩阵的行索引为index/n,列 23 | * 索引为index%n,并且二维矩阵是升序的,因此本题即可看 24 | * 作为在升序的一维数组中寻找目标值,即可使用二分法。 25 | */ 26 | 27 | let m = matrix.length, 28 | n = matrix[0].length, 29 | // 初始化左指针为首个元素 30 | left = 0, 31 | // 初始化右指针为最后一个元素 32 | right = m * n - 1; 33 | // 当左指针超过了右指针,证明遍历完成 34 | while (left <= right) { 35 | // 取得中间值 36 | let middle = Math.floor((left + right) / 2); 37 | // 将一维数组的索引转为二维数组索引 38 | let row=Math.floor(middle/n),col=middle%n; 39 | // 如果矩阵元素等于目标值,直接返回 40 | if(matrix[row][col]===target)return true; 41 | // 如果当前元素大于目标值,由于数组为升序, 42 | // 证明后续区间[middle,right]的元素都是 43 | // 大于目标值的,因此直接舍弃,在[left,middle-1] 44 | // 区间继续寻找 45 | else if(matrix[row][col]>target)right=middle-1; 46 | // 如果当前元素小于目标值,由于数组为升序, 47 | // 证明前面区间[left,middle]的元素都是 48 | // 小于目标值的,因此直接舍弃,在[middle+1,right] 49 | // 区间继续寻找 50 | else left=middle+1; 51 | } 52 | // 遍历完成依旧没找到目标值,直接返回false 53 | return false; 54 | } -------------------------------------------------------------------------------- /General-Questions-Part2/77.Combinations.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。 3 | 你可以按 任何顺序 返回答案。 4 | * @Author: JunLiangWang 5 | * @Date: 2023-06-29 09:06:49 6 | * @LastEditors: JunLiangWang 7 | * @LastEditTime: 2023-06-29 09:26:02 8 | */ 9 | 10 | 11 | 12 | /** 13 | * @description: 递归+减枝 TC:O(2^n) SC:O(1) 14 | * @author: JunLiangWang 15 | * @param {*} n 给定范围[1,n] 16 | * @param {*} k 给定k个数 17 | * @return {*} 18 | */ 19 | function recursionPruning(n, k) { 20 | /** 21 | * 该方案使用递归+减枝枚举所有组合 22 | */ 23 | 24 | // 输出数组 25 | let outArray=[]; 26 | 27 | /** 28 | * @description: 递归回溯枚举所有组合 29 | * @author: JunLiangWang 30 | * @param {*} currentNum 当前选中的数 31 | * @param {*} selectNum 已选中的数字数组 32 | * @return {*} 33 | */ 34 | function recursionBackTracking(currentNum, selectNum){ 35 | // 当已选中的数的长度等于k,满足要求 36 | if (selectNum.length == k) { 37 | // 向输出数组添加该数组 38 | outArray.push(selectNum); 39 | // 直接返回 40 | return; 41 | } 42 | // 否则长度不等于k,继续递归 43 | 44 | // 由于currentNum之前都是已选中的数, 45 | // 因此直接从currentNum遍历到n继续递归 46 | for (let i = currentNum; i <= n; i++) { 47 | // 减枝:当已选择的数加上后续的数长度不能 48 | // 大于等于k,后续无论如何追加都已 49 | // 无法满足要求,因此要继续递归应当 50 | // 满足已选择的数加上后续的数长度大于等于k 51 | if(selectNum.length+n-i+1>=k) 52 | recursionBackTracking(i + 1, [...selectNum, i]); 53 | } 54 | } 55 | // 执行递归 56 | recursionBackTracking(1,[]); 57 | // 返回结果 58 | return outArray; 59 | } -------------------------------------------------------------------------------- /General-Questions-Part1/20.isValidBrackets.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @description: 给定一个只包括 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否有效。 3 | * @author: JunLiangWang 4 | * @return {*} 5 | */ 6 | 7 | 8 | /** 9 | * @description: 栈 TC:O(n) SC:O(n) 10 | * @author: JunLiangWang 11 | * @param {*} s 输入字符串 12 | * @return {*} 13 | */ 14 | function stack(s) 15 | { 16 | // 该方案利用栈先进后出特点,遍历字符串,当遇到左括号,则入栈一个对应的右括 17 | // 号;当遇到右括号,则比对栈顶右括号是否相同,如若不同说明括号对应顺序不同 18 | // ,则可直接返回false,如果相同则将该右括号出栈。遍历完成如果栈为空则证明 19 | // 括号一一匹配,返回ture;反之返回false。 20 | 21 | // 判断s长度是否为单数,如果为单数直接返回false 22 | if(Math.floor(s.length/2)!=s.length/2)return false 23 | // 构造一个左括号对应的右括号的Map 24 | let leftCorrespondingRight=new Map(); 25 | leftCorrespondingRight.set('[',']') 26 | leftCorrespondingRight.set('(',')') 27 | leftCorrespondingRight.set('{','}') 28 | // 使用数组模拟栈 29 | let stackArray=[] 30 | // 遍历字符串 31 | for(let i=0;i0){ 38 | 39 | // 记录当前队列长度,也是本次层级的 40 | // 所有节点数量,由于出队会动态影响 41 | // 到队列的长度,因此此处记录,就可 42 | // 以知道本次层级的节点是否全部出队 43 | let size=quene.length; 44 | 45 | // 遍历当前层级的所有节点 46 | while(size-->0){ 47 | 48 | // 出队 49 | let node=quene.shift(); 50 | // 如果当前队列不为空证明有右节点 51 | // 则将其next指向右节点 52 | if(size>0)node.next=quene[0]; 53 | // 将出队节点不为空的左右节点再次入队 54 | if(node.left)quene.push(node.left); 55 | if(node.right)quene.push(node.right); 56 | } 57 | } 58 | // 返回结果 59 | return root 60 | } -------------------------------------------------------------------------------- /Sword-Pointing-Offer/153.FindMinimuminRotatedSortedArray.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 给你一个元素值 互不相同 的数组 nums ,它原来是一个升序排列的数组, 3 | 并按上述情形进行了多次旋转。请你找出并返回数组中的 最小元素 。 4 | * @Author: JunLiangWang 5 | * @Date: 2023-11-03 15:09:18 6 | * @LastEditors: JunLiangWang 7 | * @LastEditTime: 2023-11-03 15:29:11 8 | */ 9 | 10 | 11 | /** 12 | * @description: 二分 TC:O(logn) SC:O(1) 13 | * @author: JunLiangWang 14 | * @param {*} nums 给定数组 15 | * @return {*} 16 | */ 17 | function binarySearch(nums){ 18 | /** 19 | * 该方案使用二分查找的方式,其中的关键则为将局部有序的数组一分为二, 20 | * 其中一个一定是有序的,另一个一定又是局部有序的。 21 | * 22 | * 因此我们利用该特性,不断将数组一分为二找到有序的部分,它最左边则是 23 | * 该部分最小值;然后再将局部有序的数组继续一分为二,又有一部分数组是 24 | * 有序的如此往复直到完成。 25 | * 26 | */ 27 | 28 | // 初始化左指针为0 29 | let left=0, 30 | // 右指针为数组最后一个元素 31 | right=nums.length-1, 32 | // 最小值为第一个元素 33 | min=nums[0]; 34 | // 当左指针超过右指针,证明遍历完成,跳出循环 35 | while(left<=right){ 36 | // 寻找中间值 37 | let middle=Math.floor((left+right)/2) 38 | // 找到有顺序的一半数组(无论如何分割数组,总有一半是全为升序的) 39 | // 如果nums[left]<=nums[middle]证明[left,middle]该区间全为升序 40 | if(nums[left]<=nums[middle]){ 41 | // 它最左边则是该部分最小值 42 | min=Math.min(nums[left],min) 43 | // 继续遍历[middle+1,right]这部分局部有序的数组 44 | left=middle+1; 45 | } 46 | // 否则[middle,right]区间全为升序 47 | else 48 | { 49 | // 它最左边则是该部分最小值 50 | min=Math.min(nums[middle],min) 51 | // 继续遍历[left,middle-1]这部分局部有序的数组 52 | right=middle-1; 53 | } 54 | } 55 | // 返回最小值 56 | return min; 57 | } -------------------------------------------------------------------------------- /General-Questions-Part2/89.GrayCode.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 给你一个整数 n ,返回任一有效的 n 位格雷码序列 。 3 | * @Author: JunLiangWang 4 | * @Date: 2023-07-20 09:59:53 5 | * @LastEditors: JunLiangWang 6 | * @LastEditTime: 2023-07-20 10:21:25 7 | */ 8 | 9 | 10 | /** 11 | * @description: 归纳法 TC:O(2^n) SC:O(n) 12 | * @author: JunLiangWang 13 | * @param {*} n 给定整数n 14 | * @return {*} 15 | */ 16 | function inductionMethod(n){ 17 | /** 18 | * 该方案使用归纳法,给定以下格雷码序列: 19 | * 20 | * n= 0 1 2 3 4 21 | * 0 0 0 0 0 22 | * 1 1 1 1 23 | * 11 11 11 24 | * 10 10 10 25 | * 110 110 26 | * 111 111 27 | * 101 101 28 | * 100 100 29 | * ... 30 | * 我们可以找到规律,对于格雷序列n,它的前半部分 31 | * 是等于n-1的格雷序列,而它的后半部分等于前半部 32 | * 分倒序,在最高位补1。 33 | * 根据该规律,我们遍历模拟该过程即可 34 | */ 35 | let outArray=[0]; 36 | for(let i=1;i<=n;i++){ 37 | let len=outArray.length; 38 | for(let j=len-1;j>=0;j--){ 39 | outArray.push(outArray[j]|(1<<(i-1))); 40 | } 41 | } 42 | return outArray; 43 | } 44 | 45 | 46 | /** 47 | * @description: 数学公式法 TC:O(2^n) SC:O(n) 48 | * @author: JunLiangWang 49 | * @param {*} n 给定整数n 50 | * @return {*} 51 | */ 52 | function mathMethod(n){ 53 | /** 54 | * 该方案使用数学公式,求第i位格雷码序列有以下公式: 55 | * G(i)= (i>>1)^i ^为位异或 56 | */ 57 | let outArray=[]; 58 | for(let i=0;i>1)^i); 59 | return outArray; 60 | } -------------------------------------------------------------------------------- /General-Questions-Part3/137.SingleNumberII.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 给你一个 非空 整数数组 nums ,除了某个元素只出现一次以外, 3 | 其余每个元素均出现三次。找出那个只出现了一次的元素。 4 | * @Author: JunLiangWang 5 | * @Date: 2023-10-11 09:05:20 6 | * @LastEditors: JunLiangWang 7 | * @LastEditTime: 2023-10-12 15:17:00 8 | */ 9 | 10 | 11 | /** 12 | * @description: 哈希表 TC:O(n) SC:O(n) 13 | * @author: JunLiangWang 14 | * @param {*} nums 给定数组 15 | * @return {*} 16 | */ 17 | function hashMap(nums) { 18 | /** 19 | * 该方案采用hashMap的方式,首先遍历一次数组 20 | * 并利用hashMap记录数组元素出现的次数(key为 21 | * 数组元素,value为出现的次数),然后再遍历一 22 | * 次数组,找出hashMap中value为1的元素,则为 23 | * 只出现了一次的元素 24 | */ 25 | // 记录数组元素出现的次数(key为数组元素,value为出现的次数) 26 | let hashMap = new Map(); 27 | // 遍历数组元素 28 | nums.forEach((value) => { 29 | // 获得该元素已出现的次数 30 | let count = hashMap.get(value) 31 | // 更新hashMap中该元素出现的次数为count+1 32 | hashMap.set(value, count == undefined ? 1 : ++count) 33 | }) 34 | // 遍历数组元素 35 | for (let value of nums) { 36 | // 找出找出hashMap中value为1的元素,则为 37 | // 只出现了一次的元素 38 | if (hashMap.get(value) == 1) return value 39 | } 40 | } 41 | 42 | 43 | /** 44 | * @description: 真值表 TC:O(n) SC:O(1) 45 | * @author: JunLiangWang 46 | * @param {*} nums 给定数组 47 | * @return {*} 48 | */ 49 | function truthTable(nums){ 50 | /** 51 | * 该方案利用计算真值表的方式实现, 52 | * 需要一定的电子信息的基础,通过 53 | * 给定输入/输出设计真值表,从而得 54 | * 出逻辑表达式,满足该真值表,下述 55 | * 公式则从真值表得出。 56 | */ 57 | let a = 0, b = 0; 58 | for (const num of nums) { 59 | b = ~a & (b ^ num); 60 | a = ~b & (a ^ num); 61 | } 62 | return b; 63 | } -------------------------------------------------------------------------------- /General-Questions-Part3/117.PopulatingNextRightPointersinEachNodeII.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 给定一个二叉树 ,填充它的每个 next 指针,让这个指针指向其下一个右侧节点。 3 | 如果找不到下一个右侧节点,则将 next 指针设置为 NULL。 4 | * @Author: JunLiangWang 5 | * @Date: 2023-08-30 10:33:29 6 | * @LastEditors: JunLiangWang 7 | * @LastEditTime: 2023-08-31 09:15:13 8 | */ 9 | 10 | 11 | /** 12 | * @description: 广度优先 TC:O(n) SC:O(n) 13 | * @author: JunLiangWang 14 | * @param {*} root 给定树的根节点 15 | * @return {*} 16 | */ 17 | function bfs(root){ 18 | /** 19 | * 本题与上一题一样, 只不过一个是完美二叉树,一个是二叉树 20 | * 该方案使用广度优先算法, 该题本质上还是树的层级遍历 21 | * 我们只需要在广度优先算法的基础上,将出队的节点的next 22 | * 指向当前队列中的首节点(因为出队节点的下一个节点即是它 23 | * 右边的节点),如果队列中无节点,则不做任何处理 24 | * 25 | * 广度优先算法依靠一个队列,先将根节点入队,然后循环 26 | * 不断将队列中的节点出队,然后将出队节点不为空的左右 27 | * 节点再次入队。如此往复直到队列为空 28 | */ 29 | 30 | // 如果根为空,直接返回 31 | if(!root)return root; 32 | 33 | // 定义队列,将根节点入队 34 | let quene=[root] 35 | 36 | // 当队列不为空则继续遍历 37 | while(quene.length>0){ 38 | 39 | // 记录当前队列长度,也是本次层级的 40 | // 所有节点数量,由于出队会动态影响 41 | // 到队列的长度,因此此处记录,就可 42 | // 以知道本次层级的节点是否全部出队 43 | let size=quene.length; 44 | 45 | // 遍历当前层级的所有节点 46 | while(size-->0){ 47 | 48 | // 出队 49 | let node=quene.shift(); 50 | // 如果当前队列不为空证明有右节点 51 | // 则将其next指向右节点 52 | if(size>0)node.next=quene[0]; 53 | // 将出队节点不为空的左右节点再次入队 54 | if(node.left)quene.push(node.left); 55 | if(node.right)quene.push(node.right); 56 | } 57 | } 58 | // 返回结果 59 | return root 60 | } -------------------------------------------------------------------------------- /Sword-Pointing-Offer/274.H-Index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 给你一个整数数组 citations ,其中 citations[i] 表示研究者的 3 | 第 i 篇论文被引用的次数。计算并返回该研究者的 h 指数。 4 | * @Author: JunLiangWang 5 | * @Date: 2023-11-09 09:37:27 6 | * @LastEditors: JunLiangWang 7 | * @LastEditTime: 2023-11-09 09:52:20 8 | */ 9 | 10 | 11 | 12 | /** 13 | * @description: 排序法 TC:O(nlogn) SC:O(1) 14 | * @author: JunLiangWang 15 | * @param {*} citations 给定数组 16 | * @return {*} 17 | */ 18 | function sort(citations){ 19 | /** 20 | * 本方案使用排序的方式,先对给定数组citations 21 | * 进行降序排序,然后遍历数组,直到当其索引+1大 22 | * 于它的被引次数,则该索引即为他的h指数 23 | */ 24 | 25 | // 对数组进行降序排序 26 | citations.sort((a,b)=>b-a) 27 | 28 | let i=0; 29 | while(i+1<=citations[i]&&i<=citations.length)i++ 30 | 31 | return i 32 | } 33 | 34 | 35 | /** 36 | * @description: 计数排序 TC:O(n) SC:O(n) 37 | * @author: JunLiangWang 38 | * @param {*} citations 给定数组 39 | * @return {*} 40 | */ 41 | function countSort(citations){ 42 | /** 43 | * 本方案使用计数排序的方式,对于N篇文章而言 44 | * 其引用次数可能为[0,1,2,3....,>=n],我们 45 | * 可以根据此定义一个数组,其长度为n+1,索引 46 | * [0至n-1]分别记录引用次数0至(n-1)的文章, 47 | * 索引n则记录引用次数>=n的文章,根据上述规 48 | * 则遍历数组记录。 49 | * 50 | * 最后我们从后向前遍历数组,当文章的篇数 51 | * 大于等于文章的引用时即为h指数 52 | */ 53 | let len=citations.length, 54 | recordArray=new Array(len+1).fill(0); 55 | 56 | for(let i=0;i=len)recordArray[len]++ 58 | else recordArray[citations[i]]++ 59 | } 60 | 61 | let total=0 62 | for(let i=len;i>=0;i--){ 63 | total+=recordArray[i] 64 | if(total>=i)return i 65 | } 66 | 67 | return 0 68 | } -------------------------------------------------------------------------------- /General-Questions-Part2/93.RestoreIPAddresses.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 给定一个只包含数字的字符串 s ,用以表示一个 IP 地址,返回所有可能的有效 IP 地址, 3 | 这些地址可以通过在 s 中插入 '.' 来形成。 4 | * @Author: JunLiangWang 5 | * @Date: 2023-07-27 09:15:56 6 | * @LastEditors: JunLiangWang 7 | * @LastEditTime: 2023-07-27 09:32:53 8 | */ 9 | 10 | /** 11 | * @description: 递归回溯 TC:O(2^n) SC:O(n) 12 | * @author: JunLiangWang 13 | * @param {*} s 给定字符串S 14 | * @return {*} 15 | */ 16 | function recursionBackTracking(s) 17 | { 18 | /** 19 | * 该方案使用递归回溯,对字符串进行1到3个字符分隔,并进行搜索, 20 | * 最后筛选出满足要求的作为答案。 21 | */ 22 | 23 | // 输出数组 24 | let outArray=[] 25 | /** 26 | * @description: 递归 27 | * @author: JunLiangWang 28 | * @param {*} index 当前字符串位置 29 | * @param {*} selectStr 已经选择的子串数组 30 | * @return {*} 31 | */ 32 | function recursion(index,selectStr){ 33 | // 如果已经选择了4个子串 34 | if(selectStr.length==4){ 35 | // 如果刚好将字符串分割为4个子串,证明能复原IP地址,添加结果 36 | if(index==s.length)outArray.push(selectStr.join('.')); 37 | // 如果分割后还存在字符,证明无法复原,舍去 38 | else return; 39 | } 40 | // 如果当前索引已经超出数组范围,则直接return 41 | if(index>=s.length)return 42 | 43 | // 三种切割长度截取字符串 44 | for(let i=1;i<=3;i++){ 45 | // 获得三种切割长度的字符串 46 | let preString=s.slice(index,index+i) 47 | // 判断是否满足IP单个片段的要求 48 | if((i>1&&preString[0]!='0'&&preString*1<256)||i==1){ 49 | // 满足则继续递归 50 | recursion(index+i,[...selectStr,preString]) 51 | } 52 | } 53 | } 54 | // 执行递归 55 | recursion(0,[],0) 56 | // 返回结果 57 | return outArray 58 | } -------------------------------------------------------------------------------- /General-Questions-Part3/113.PathSumII.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 给你二叉树的根节点 root 和一个整数目标和 targetSum ,找出 3 | 所有 从根节点到叶子节点 路径总和等于给定目标和的路径。 4 | * @Author: JunLiangWang 5 | * @Date: 2023-08-25 09:14:04 6 | * @LastEditors: JunLiangWang 7 | * @LastEditTime: 2023-08-25 09:21:43 8 | */ 9 | 10 | 11 | /** 12 | * @description: 深度优先 TC:O(n) SC:O(n) 13 | * @author: JunLiangWang 14 | * @param {*} root 给定树的根节点 15 | * @param {*} targetSum 给定目标值 16 | * @return {*} 17 | */ 18 | function dfs(root,targetSum){ 19 | /** 20 | * 本方案使用深度优先的方式,该题与112.路径总和类 21 | * 型, 不过112题只是判断是否存在一条路径和等于给 22 | * 定目标值,而该题是找出所有等于目标的的路径和, 23 | * 其实方法上并由没什么差别,我们同样利用深度遍历 24 | * 树的节点并记录到达该节点的路径和,然后当遇到叶 25 | * 子节点则比较路径和是否与目标值相等,相等则记录 26 | * 路径,然后继续遍历其他节点,直到遍历完成所有节 27 | * 点。 28 | */ 29 | 30 | // 记录满足条件的路径 31 | let outArray=[]; 32 | /** 33 | * @description: 递归实现深度优先遍历 34 | * @author: JunLiangWang 35 | * @param {*} root 当前根节点 36 | * @param {*} path 达到当前节点之前的路径 37 | * @param {*} sum 达到当前节点之前的路径和 38 | * @return {*} 39 | */ 40 | function recursion(root,path,sum){ 41 | // 如果节点为空,直接返回false 42 | if(!root) return sum 43 | // 计算到达当前节点的路径和 44 | let tempSum=sum+root.val, 45 | // 得出达到当前节点的路径 46 | tempPath=[...path,root.val]; 47 | // 当前为叶子节点且其路径和等于目标值,则满足要求 48 | if(tempSum===targetSum&&!root.left&&!root.right)outArray.push(tempPath) 49 | // 继续深度遍历其他左右节点 50 | recursion(root.left,tempPath,tempSum); 51 | recursion(root.right,tempPath,tempSum); 52 | } 53 | // 执行递归 54 | recursion(root,[],0) 55 | // 返回结果 56 | return outArray; 57 | } -------------------------------------------------------------------------------- /General-Questions-Part1/16.threeNumberSumClosest.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 给你一个整数数组和一个目标值。请你从数组中选出三个整数,使它们的和与目标值最接近。 3 | * @Author: JunLiangWang 4 | * @Date: 2023-03-13 09:21:34 5 | * @LastEditors: JunLiangWang 6 | * @LastEditTime: 2023-03-13 09:44:07 7 | */ 8 | 9 | 10 | /** 11 | * @description: 双指针 TC:O(n^2) SC:O(1) 12 | * @author: JunLiangWang 13 | * @param {*} nums 输入的数组 14 | * @param {*} target 输入的目标值 15 | * @return {*} 16 | */ 17 | function doublePoint(nums,target) 18 | { 19 | // 先对数组排序 20 | nums.sort((a,b)=>a-b) 21 | // 初始化最接近target为前三个元素和 22 | let threeNumberSum=nums[0]+nums[1]+nums[2] 23 | // 遍历nums所有元素,确定第一个数 24 | for(let firstIndex=0;firstIndex0)left++ 43 | // 反之右指针向前即可 44 | else right-- 45 | // 当发现更小的距离,则赋值 46 | if(Math.abs(tempDistance) { 48 | // 计算数组最大子数组和 49 | sumMaxElement = Math.max(sumMaxElement + val, val) 50 | max = Math.max(max, sumMaxElement) 51 | // 计算数组最小子数组和 52 | sumMinElement = Math.min(sumMinElement + val, val) 53 | min = Math.min(min, sumMinElement) 54 | sum += val 55 | }) 56 | // 如果数组最大子数组和小于等于0,证明数组总和也小于0 57 | // 因此直接返回max,否则比较 58 | return max > 0 ? Math.max(max, sum - min) : max 59 | } -------------------------------------------------------------------------------- /General-Questions-Part3/112.PathSum.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 给你二叉树的根节点 root 和一个表示目标和的整数 targetSum 。判断该树中是否存在 根节点到叶子节点 的路径, 3 | 这条路径上所有节点值相加等于目标和 targetSum 。如果存在,返回 true ;否则,返回 false 。 4 | * @Author: JunLiangWang 5 | * @Date: 2023-08-24 09:14:33 6 | * @LastEditors: JunLiangWang 7 | * @LastEditTime: 2023-08-24 09:20:24 8 | */ 9 | 10 | 11 | /** 12 | * @description: 深度优先 TC:O(n) SC:O(1) 13 | * @author: JunLiangWang 14 | * @param {*} root 给定树的根节点 15 | * @return {*} 16 | */ 17 | /** 18 | * @description: 深度优先 TC:O(n) SC:O(1) 19 | * @author: JunLiangWang 20 | * @param {*} root 给定树的根节点 21 | * @param {*} targetSum 给定目标和整数 22 | * @return {*} 23 | */ 24 | function dfs(root,targetSum){ 25 | /** 26 | * 本方案使用深度优先的方式,利用深度遍历树的节点 27 | * 并记录到达该节点的路径和,然后当遇到叶子节点则 28 | * 比较路径和是否与目标值相等,相等则满足题意返回 29 | * true,否则继续遍历其他节点,直到遍历完成所有节 30 | * 点。 31 | */ 32 | 33 | // 如果节点为空,直接返回false 34 | if(!root)return false 35 | // 记录结果,初值为fasle 36 | let result=false; 37 | 38 | /** 39 | * @description: 利用递归实现深度遍历 40 | * @author: JunLiangWang 41 | * @param {*} root 当前根节点 42 | * @param {*} val 到达根节点的路径和 43 | * @return {*} 44 | */ 45 | function recursion(root,val){ 46 | // 如果节点为空或已找到目标值,则直接返回val 47 | if(!root||result){ 48 | return val 49 | } 50 | // 计算到达该节点的路径和 51 | let sum=val+root.val; 52 | // 当前为叶子节点且其路径和等于目标值,则满足要求 53 | if(!root.left&&!root.right&&sum==targetSum)result=true 54 | // 深度遍历左右节点 55 | recursion(root.left,sum) 56 | recursion(root.right,sum) 57 | } 58 | // 执行递归 59 | recursion(root,0) 60 | // 返回结果 61 | return result 62 | } -------------------------------------------------------------------------------- /Sword-Pointing-Offer/169.MajorityElement.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 给定一个大小为 n 的数组 nums ,返回其中的多数元素。 3 | 多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。 4 | * @Author: JunLiangWang 5 | * @Date: 2023-11-07 09:48:34 6 | * @LastEditors: JunLiangWang 7 | * @LastEditTime: 2023-11-07 09:56:27 8 | */ 9 | 10 | 11 | /** 12 | * @description: 排序法 TC:O(nlogn) SC:O(1) 13 | * @author: JunLiangWang 14 | * @param {*} nums 给定数组 15 | * @return {*} 16 | */ 17 | function sort(nums){ 18 | /** 19 | * 本方案使用排序法,先对数组进行排序, 20 | * 然后计算每个元素的数量,最终大于2/n 21 | * 即为最多的元素 22 | */ 23 | 24 | // 数组长度小于等于2,直接返回任一元素 25 | if(nums.length<=2)return nums[0] 26 | // 对数组进行排序 27 | nums.sort() 28 | // 计数 29 | let count=1; 30 | // 从第二个元素开始遍历 31 | for(let i=1;i=nums.length/2)return nums[i] 39 | } 40 | // 如果与前一个元素不同,则是新的元素,重新计数 41 | else count=1; 42 | } 43 | return null 44 | } 45 | 46 | 47 | /** 48 | * @description: 摩尔投票法 TC:O(n) SC:O(1) 49 | * @author: JunLiangWang 50 | * @param {*} nums 给定数组 51 | * @return {*} 52 | */ 53 | function BoyerMoore(nums){ 54 | /** 55 | * 该方案使用摩尔投票法,本题描述的多数元素 56 | * 是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。 57 | * 也就是说,当我删除数组中任意两个不同的元素 58 | * 这个数组的多数元素还是不会变得,因此利用这 59 | * 个特性,我们可以很巧妙的写出算法 60 | */ 61 | 62 | let candidate=0,count=0; 63 | for(let num of nums){ 64 | if(count==0)candidate=num 65 | if(candidate==num)count++ 66 | else count--; 67 | } 68 | return candidate 69 | } -------------------------------------------------------------------------------- /Sword-Pointing-Offer/208.ImplementTrie(PrefixTree).js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 实现前缀树 3 | * @Author: JunLiangWang 4 | * @Date: 2024-01-09 10:08:51 5 | * @LastEditors: JunLiangWang 6 | * @LastEditTime: 2024-01-09 10:17:03 7 | */ 8 | 9 | /** 10 | * 字典前缀树,大家可以百度一下看看什么是前缀树, 11 | * 这个整体实现起来也比较简单。 12 | */ 13 | var Trie = function () { 14 | this.tree = { 15 | 16 | }; 17 | }; 18 | 19 | /** 20 | * @description: 插入 21 | * @author: JunLiangWang 22 | * @param {*} word 23 | * @return {*} 24 | */ 25 | Trie.prototype.insert = function (word) { 26 | function recursion(node, index) { 27 | if (node[word[index]] == undefined) node[word[index]] = {} 28 | if (index == word.length - 1) { 29 | node[word[index]].end = true 30 | return 31 | } 32 | recursion(node[word[index]], index + 1) 33 | } 34 | recursion(this.tree, 0) 35 | }; 36 | 37 | /** 38 | * @description:搜索固定单词 39 | * @author: JunLiangWang 40 | * @param {*} word 41 | * @return {*} 42 | */ 43 | Trie.prototype.search = function (word) { 44 | function recursion(node, index) { 45 | if (index == word.length) return node.end != undefined 46 | return node[word[index]] != undefined && recursion(node[word[index]], index + 1) 47 | } 48 | return recursion(this.tree, 0) 49 | }; 50 | 51 | /** 52 | * @description: 查找前缀 53 | * @author: JunLiangWang 54 | * @param {*} prefix 55 | * @return {*} 56 | */ 57 | Trie.prototype.startsWith = function (prefix) { 58 | function recursion(node, index) { 59 | if (index == prefix.length) return true 60 | return node[prefix[index]] != undefined && recursion(node[prefix[index]], index + 1) 61 | } 62 | return recursion(this.tree, 0) 63 | }; -------------------------------------------------------------------------------- /General-Questions-Part1/17.phoneNumberAlphabet.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 电话号码的字母组合,给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。 3 | * @Author: JunLiangWang 4 | * @Date: 2023-03-14 10:38:32 5 | * @LastEditors: JunLiangWang 6 | * @LastEditTime: 2023-03-14 11:09:51 7 | */ 8 | 9 | 10 | /* 11 | * @Description: 递归回溯 TC:O(n^3) SC:(n) 12 | * @Author: JunLiangWang 13 | * @Date: 2023-03-14 10:38:32 14 | * @LastEditors: JunLiangWang 15 | * @LastEditTime: 2023-03-14 11:00:14 16 | */ 17 | function recursion(digits) { 18 | // 如果没有数字直接返回 19 | if(digits.length==0)return [] 20 | // 电话号码对应的字母表示(mark[number-1],例子:电话号码2,对应mark[2-1]=['a', 'b', 'c']) 21 | const mark = [ 22 | [''], 23 | ['a', 'b', 'c'], 24 | ['d', 'e', 'f'], 25 | ['g', 'h', 'i'], 26 | ['j', 'k', 'l'], 27 | ['m', 'n', 'o'], 28 | ['p', 'q', 'r', 's'], 29 | ['t', 'u', 'v'], 30 | ['w', 'x', 'y', 'z'] 31 | ] 32 | // 记录电话号码字母组合数组 33 | var recordArray=[] 34 | /** 35 | * @description: 回溯函数 36 | * @author: JunLiangWang 37 | * @param {*} cuString 上一次字母组合字符串 38 | * @param {*} index 当前遍历到的digits索引 39 | * @return {*} 40 | */ 41 | var backtrack=function(cuString,index){ 42 | // 如果遍历完成digits所有字符,则向记录数组添加上一次字母组合的字符串 43 | if(index==digits.length)recordArray.push(cuString) 44 | // 如果没有,则找出当前电话号码对应的字母进行遍历(mark[number-1], 45 | // 例子:电话号码2,对应mark[2-1]=['a', 'b', 'c']),并继续调用 46 | // 回溯函数(参数1:上一次字母组合的字符串+这次对应的字母,参数2:索引+1) 47 | // 直到遍历完成digits为止 48 | else mark[digits[index]-1].map((word)=>backtrack(cuString+word,index+1)) 49 | } 50 | // 调用回溯函数 51 | backtrack('',0) 52 | // 返回结果 53 | return recordArray 54 | } -------------------------------------------------------------------------------- /Sword-Pointing-Offer/200.NumberofIslands.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 给你一个由 '1'(陆地)和 '0'(水)组成的的二维网格,请你计算网格中岛屿的数量。 3 | 岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。 4 | 此外,你可以假设该网格的四条边均被水包围。 5 | * @Author: JunLiangWang 6 | * @Date: 2023-12-19 11:18:52 7 | * @LastEditors: JunLiangWang 8 | * @LastEditTime: 2023-12-19 11:25:04 9 | */ 10 | 11 | 12 | /** 13 | * @description: 递归 TC:O(nm) SC:O(nm) 14 | * @author: JunLiangWang 15 | * @param {*} grid 给定二维数组 16 | * @return {*} 17 | */ 18 | function recursion(grid){ 19 | /** 20 | * 本方案采用递归的方式,首先遍历数组元素 21 | * 当遇到1时,将该元素赋值为0,当该元素上 22 | * 下左右四个方向有元素为1时,则继续递归 23 | * 递归结束证明该岛屿所有板块找到,数量+1 24 | * 继续遍历 25 | */ 26 | 27 | // 记录岛屿数量 28 | let count=0; 29 | /** 30 | * @description: 递归 31 | * @author: JunLiangWang 32 | * @param {*} i x轴 33 | * @param {*} j y轴 34 | * @return {*} 35 | */ 36 | function recursionMethod(i,j){ 37 | // 将该元素置为0 38 | grid[i][j]=0; 39 | // 当该元素上下左右四个方向有元素 40 | // 为1时,则继续递归递归结束证明该 41 | // 岛屿所有板块找到 42 | if(i+1=0&&grid[i-1][j]==1) recursionMethod(i-1,j) 44 | if(j+1=0&&grid[i][j-1]==1)recursionMethod(i,j-1) 46 | } 47 | // 遍历数组元素 48 | for(let i=0;i0){ 64 | let peek=stack.pop() 65 | outArray.push(peek.val) 66 | if(peek.left)stack.push(peek.left) 67 | if(peek.right)stack.push(peek.right) 68 | } 69 | // 将结果翻转则是后序遍历 70 | return outArray.reverse(); 71 | } -------------------------------------------------------------------------------- /General-Questions-Part3/132.PalindromePartitioningII.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是回文。 3 | 返回符合要求的 最少分割次数 。 4 | * @Author: JunLiangWang 5 | * @Date: 2023-10-08 09:51:24 6 | * @LastEditors: JunLiangWang 7 | * @LastEditTime: 2023-10-08 10:02:42 8 | */ 9 | 10 | 11 | /** 12 | * @description: 动态规划 TC:O(n^2) SC:O(n^2) 13 | * @author: JunLiangWang 14 | * @param {*} s 给定字符串 15 | * @return {*} 16 | */ 17 | function dp(s) { 18 | /** 19 | * 本方案使用两次动态规划的方法,首先利用动态规划找出字符串 20 | * s[i至j]是否为回文子串(0<=i,j 26 | new Array(size).fill(true)) 27 | // 从后向前进行DP,这是因为对于子串s[i至j],如果 28 | // s[i]==s[j]的情况下,其是否回文串是由其子串 29 | // s[i+1至j-1]是否为回文串决定的 30 | for (let i = size - 1; i >= 0; i--) { 31 | for (let j = i + 1; j < size; j++) { 32 | StrDPArray[i][j] = (s[i] == s[j] && StrDPArray[i + 1][j - 1]) 33 | } 34 | } 35 | // 最小分割数的DP数组 36 | let CountDPArray = new Array(size); 37 | // 将子串最小分割数置为字符数,即为分割为单个字符 38 | for (let i = 0; i < size; i++)CountDPArray[i] = [i] 39 | // 遍历子串 40 | for (let i = 1; i < size; i++) { 41 | // 如果子串为回文串,直接置为0,无需分割 42 | if (StrDPArray[0][i]) { 43 | CountDPArray[i] = 0 44 | } 45 | // 否则 46 | else { 47 | // 从0分割子串,找出最小分割数 48 | for (let j = 0; j < i; j++) { 49 | if (StrDPArray[j + 1][i]) { 50 | CountDPArray[i] = Math.min(CountDPArray[i], CountDPArray[j] + 1); 51 | } 52 | } 53 | } 54 | } 55 | // 返回结果 56 | return CountDPArray[size - 1] 57 | } -------------------------------------------------------------------------------- /General-Questions-Part3/128.LongestConsecutiveSequence.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 给定一个未排序的整数数组 nums ,找出数字连续的 3 | 最长序列(不要求序列元素在原数组中连续)的长度。 4 | 请你设计并实现时间复杂度为 O(n) 的算法解决此问 5 | 题。 6 | * @Author: JunLiangWang 7 | * @Date: 2023-09-20 10:51:53 8 | * @LastEditors: JunLiangWang 9 | * @LastEditTime: 2023-09-20 11:23:23 10 | */ 11 | 12 | 13 | 14 | /** 15 | * @description: 哈希表+动态规划 TC:O(n) SC:O(n) 16 | * @author: JunLiangWang 17 | * @param {*} nums 给定未排序数组 18 | * @return {*} 19 | */ 20 | function hashMapAndDP(nums){ 21 | /** 22 | * 该方案使用哈希表+动态规划,对于数组中某个元素 23 | * num,它的最长序列应该为其左邻居num-1的最长序 24 | * 列与右邻居num+1的最长序列的和+1即: 25 | * 26 | * MaxLen(num)=MaxLen(num-1)+1+MaxLen(num+1) 27 | * 28 | * 我们可以通过哈希表将某元素的最长序列存储起来, 29 | * key为num,value为当前元素的最长序列 30 | * 31 | * 我们通过遍历数组,在哈希表中查询是否存在该元素的 32 | * 左右邻居以获取其邻居的最长序列,如果没有则置为0, 33 | * 然后通过上述公式计算结果并将结果保存至哈希表中。 34 | * 35 | * 我们如何更新哈希表中序列的其他值?其实我们只需要更 36 | * 新当前元素最长序列的首个元素与最后一个元素的值即可, 37 | * 比如以下示例: 38 | * 39 | * [1,2,3,4,5] 我们计算出元素3的最长序列为5,我们无需去 40 | * 更新2和4元素的值,因为他们各自左右元素都已出现,后续 41 | * 元素不会再依赖其来计算,因此我们仅需要更新1和5元素的 42 | * 值即可 43 | * 44 | * 上述公式计算某个元素的最 45 | * 长序列,并用哈希表存起来, 46 | * 47 | */ 48 | let map=new Map(),maxCount=0; 49 | for(let i=0;i { 29 | // 获得该元素已出现的次数 30 | let count = hashMap.get(value) 31 | // 更新hashMap中该元素出现的次数为count+1 32 | hashMap.set(value, count == undefined ? 1 : ++count) 33 | }) 34 | // 遍历数组元素 35 | for (let value of nums) { 36 | // 找出找出hashMap中value为1的元素,则为 37 | // 只出现了一次的元素 38 | if (hashMap.get(value) == 1) return value 39 | } 40 | } 41 | 42 | 43 | /** 44 | * @description: 位运算 TC:O(n) SC:O(1) 45 | * @author: JunLiangWang 46 | * @param {*} nums 给定数组 47 | * @return {*} 48 | */ 49 | function bitOperations(nums){ 50 | /** 51 | * 本方案使用位运算的方式,异或运算有以下三个性质: 52 | * 1.任何数和0做异或运算,结果仍然是原来的数,即 a⊕0=a 53 | * 2.任何数和其自身做异或运算,结果是0,即 a⊕a=0 54 | * 3.异或运算满足交换律和结合律,即 a⊕b⊕a=b⊕a⊕a=b⊕(a⊕a)=b⊕0=b 55 | * 56 | * 因此该题我们可通过上述异或运算得出: 57 | * (a1⊕a1)⊕(a2⊕a2)⊕.....⊕(am⊕am)⊕an=an 58 | * 59 | * 因此我们可通过异或运算解决该题 60 | */ 61 | let single=0; 62 | nums.forEach((value)=>{ 63 | single^=value 64 | }) 65 | return single 66 | } -------------------------------------------------------------------------------- /General-Questions-Part2/82.RemoveDuplicatesSortedListII.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 给定一个已排序的链表的头 head , 删除原始链表中所有重复数字的节点, 3 | 只留下不同的数字 。返回 已排序的链表 。 4 | * @Author: JunLiangWang 5 | * @Date: 2023-07-06 10:14:56 6 | * @LastEditors: JunLiangWang 7 | * @LastEditTime: 2023-07-06 11:42:04 8 | */ 9 | 10 | 11 | /** 12 | * @description: 迭代法 TC:O(n) SC:O(1) 13 | * @author: JunLiangWang 14 | * @param {*} head 给定链表头节点 15 | * @return {*} 16 | */ 17 | function iterate(head) { 18 | /** 19 | * 由于给定的链表是排好序的,因此重复的元 20 | * 素在链表中出现的位置是连续的,因此我们 21 | * 只需要对链表进行一次遍历,就可以删除重 22 | * 复的元素。由于链表的头节点可能会被删除, 23 | * 因此我们需要额外使用一个哑节点(HEAD)指 24 | * 向链表的头节点。 25 | */ 26 | 27 | // 添加哑节点 28 | const HEAD = new ListNode(null, head); 29 | // 定义上一个节点(已经确定不重复的节点) 30 | let lastNode = HEAD; 31 | // head为当前节点,当当前节点不存在 32 | // 证明链表无节点,此时结束遍历,或 33 | // 其下一个节点不存在证明已遍历完成 34 | // 所有节点,且当前节点是不重复的, 35 | // 此时结束遍历 36 | while (head && head.next) { 37 | // 当前节点与下一个节点不重复 38 | if (head.val != head.next.val) { 39 | 40 | // 当前节点已经确认不重复,将 41 | // 上一个节点(已经确定不重复 42 | // 的节点)向后移动一位 43 | 44 | // 注意head始终是等于last.next的 45 | // 因此当head不重复时,我们操作 46 | // lastNode即可 47 | lastNode = lastNode.next; 48 | } 49 | // 当前节点与下一个节点重复 50 | else { 51 | // 遍历找到与当前节点不重复的点 52 | while (head.next && head.val == head.next.val) 53 | head = head.next; 54 | // 将 上一个节点(已经确定不重复 55 | // 的节点)的下一个节点赋值为不重 56 | // 复的节点 57 | lastNode.next = head.next; 58 | } 59 | // 将当前节点后移一位 60 | head = lastNode.next 61 | } 62 | // 返回结果 63 | return HEAD.next 64 | } 65 | -------------------------------------------------------------------------------- /General-Questions-Part3/142.LinkedListCycleII.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 3 | 如果链表无环,则返回 null。 4 | * @Author: JunLiangWang 5 | * @Date: 2023-10-19 09:27:57 6 | * @LastEditors: JunLiangWang 7 | * @LastEditTime: 2023-10-19 09:33:42 8 | */ 9 | 10 | /** 11 | * @description: 哈希表 TC:O(n) SC:O(n) 12 | * @author: JunLiangWang 13 | * @param {*} head 给定链表头节点 14 | * @return {*} 15 | */ 16 | function hashMap(head){ 17 | /** 18 | * 本方案使用hashMap的方式,遍历链表节点并在 19 | * hashMap记录该节点,当遍历节点时查询hashMap 20 | * 中是否存在该节点,如果存在,则证明有环且该节 21 | * 点即为入环的第一个节点,直接返回该节点即可。 22 | * 能够遍历完成链表,证明无环,返回null即可 23 | */ 24 | let map=new Map() 25 | while(head){ 26 | if(map.get(head))return head 27 | map.set(head,true) 28 | head=head.next; 29 | } 30 | return null 31 | } 32 | 33 | 34 | /** 35 | * @description: 双指针 TC:O(n) SC:O(1) 36 | * @author: JunLiangWang 37 | * @param {*} head 给定链表头节点 38 | * @return {*} 39 | */ 40 | function doublePoint(head){ 41 | /** 42 | * 本方案采用双指针的方式,定义两指针slow,fast。 43 | * slow每次移动一个节点,fast每次移动两个节点,如 44 | * 果链表存在环,则在某时刻slow与fast指针将重合, 45 | * 如何不存在环,则能正常遍历完成链表 46 | * 47 | * 如果有环的情况下,我们如果确定入环节点呢?答案 48 | * 就是slow与fast指针相遇时,我们再额外使用一个指 49 | * 针ptr。起始,它指向链表头部;随后,它和slow每 50 | * 次向后移动一个位置。最终,它们会在入环点相遇。 51 | * 52 | * 你可以通过slow=2fast,然后通过设环外与环内变量 53 | * 得出该结论 54 | */ 55 | if(!head)return null 56 | let slow=head,fast=head 57 | while(fast&&fast.next) 58 | { 59 | slow=slow.next; 60 | fast=fast.next.next 61 | if(slow==fast){ 62 | while(slow!==head){ 63 | slow=slow.next 64 | head=head.next 65 | } 66 | return head 67 | } 68 | } 69 | return null 70 | } -------------------------------------------------------------------------------- /General-Questions-Part3/121.BestTimetoBuyandSellStock.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。 3 | 你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设 4 | 计一个算法来计算你所能获取的最大利润。 5 | * @Author: JunLiangWang 6 | * @Date: 2023-09-07 09:40:57 7 | * @LastEditors: JunLiangWang 8 | * @LastEditTime: 2023-09-07 09:56:05 9 | */ 10 | 11 | /** 12 | * @description: 暴力破解 TC:O(n^2) SC:O(1) 13 | * @author: JunLiangWang 14 | * @param {*} prices 给定数组 15 | * @return {*} 16 | */ 17 | function bruteForce(prices){ 18 | /** 19 | * 该方案使用暴力破解法,利用嵌套遍历数组所有元素 20 | * 以及与其之后元素的差值,最大的差值即为最大利润 21 | */ 22 | 23 | // 记录最大利润 24 | let max=0 25 | // 遍历所有元素 26 | for(let i=0;imax)max=DValue 35 | } 36 | } 37 | // 返回结果 38 | return max 39 | } 40 | 41 | 42 | /** 43 | * @description: 一次遍历 TC:O(n) SC:O(1) 44 | * @author: JunLiangWang 45 | * @param {*} prices 给定数组 46 | * @return {*} 47 | */ 48 | function traverseOnce(prices){ 49 | /** 50 | * 我们可对上述暴力破解进行进一步优化,使用一次遍历 51 | * 即可得出答案,我们只需要定义一个变量,在遍历过程 52 | * 中不断记录最小值,然后以此来计算利润差即可 53 | */ 54 | 55 | if(prices.length<2)return 0 56 | // 初始化最小值为首个元素 57 | let minPrices=prices[0], 58 | // 初始化最大利润为0 59 | maxProfit=0; 60 | // 从第二个元素开始遍历数组 61 | for(let i=1;i h(ReloadPrompt) 46 | }) 47 | } 48 | } -------------------------------------------------------------------------------- /General-Questions-Part3/119.Pascal'sTriangleII.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 给定一个非负索引 rowIndex,返回「杨辉三角」的第 rowIndex 行。 3 | * @Author: JunLiangWang 4 | * @Date: 2023-09-05 11:08:27 5 | * @LastEditors: JunLiangWang 6 | * @LastEditTime: 2023-09-05 11:19:33 7 | */ 8 | 9 | 10 | /** 11 | * @description: 模拟法 TC:O(n^2) SC:O(n^2) 12 | * @author: JunLiangWang 13 | * @param {*} rowIndex 给定杨辉三角形行索引 14 | * @return {*} 15 | */ 16 | function simulation(rowIndex){ 17 | /** 18 | * 本方案利用迭代模拟杨辉三角形生成过程 19 | * 然后取出最后一行即可 20 | */ 21 | let outArray=[]; 22 | for(let i=0;i<=rowIndex;i++){ 23 | let tempArray=new Array(i+1).fill(1); 24 | for(let j=1;j=1;j--) 48 | outArray[j]=outArray[j]+outArray[j-1] 49 | } 50 | return outArray 51 | } 52 | 53 | 54 | 55 | /** 56 | * @description: 数学方法 TC:O(n) SC:O(n) 57 | * @author: JunLiangWang 58 | * @param {*} rowIndex 给定杨辉三角形行索引 59 | * @return {*} 60 | */ 61 | function Math(rowIndex){ 62 | /** 63 | * 对于杨辉三角形i行i列有公式 64 | * f[i][j]=f[i][j-1]*(rowsCount-j)/j 65 | */ 66 | let outArray=[1]; 67 | for(let i=1;i<=rowIndex;i++){ 68 | outArray.push(outArray[i - 1]* (rowIndex - i + 1) / i); 69 | } 70 | return outArray 71 | } -------------------------------------------------------------------------------- /General-Questions-Part3/134.GasStation.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 在一条环路上有 n 个加油站,其中第 i 个加油站有汽油 gas[i] 升。 3 | 你有一辆油箱容量无限的的汽车,从第 i 个加油站开往第 i+1 个加油 4 | 站需要消耗汽油 cost[i] 升。你从其中的一个加油站出发,开始时油箱 5 | 为空。给定两个整数数组 gas 和 cost ,如果你可以按顺序绕环路行驶 6 | 一周,则返回出发时加油站的编号,否则返回 -1 。如果存在解,则 保 7 | 证 它是 唯一 的。 8 | * @Author: JunLiangWang 9 | * @Date: 2023-10-10 09:43:12 10 | * @LastEditors: JunLiangWang 11 | * @LastEditTime: 2023-10-10 09:56:11 12 | */ 13 | 14 | 15 | /** 16 | * @description: 一次遍历 TC:O(n) SC:O(1) 17 | * @author: JunLiangWang 18 | * @param {*} gas 给定整数数组,表示走到该点能加的油 19 | * @param {*} cost 给定整数数组,表示走到该点需要耗费的油 20 | * @return {*} 21 | */ 22 | function onceIteration(gas, cost){ 23 | /** 24 | * 本方案采用一次遍历的方式,首先我们如何判断能否按顺序绕环路 25 | * 行驶一周呢?其实当gas的总和大于等于cost的总和我们肯定是能 26 | * 够按顺序绕环路一周的,即:Sum(gas)>=Sum(cost) 27 | * 28 | * 如果可以按顺序绕环路行驶一周那么我们如何确定起点呢? 29 | * 其实也很简单,我们假定起点为0,并定义一个变量diff记 30 | * 录走过的节点gas与cost的差值的和,如果diff小于0,证明 31 | * 该节点以及之前的节点都无法作为起点,此时将起点更新为下 32 | * 一个节点,直至遍历结束。 33 | * 34 | */ 35 | // 总加油数 36 | let allGas=0, 37 | // 总耗油数 38 | allCost=0, 39 | // 走过的节点gas与cost的差值的和 40 | diff=0, 41 | // 起点初始为0 42 | index=0; 43 | for(let i=0;i=allCost)return index 62 | // 否则不能返回-1 63 | else return -1; 64 | } -------------------------------------------------------------------------------- /Sword-Pointing-Offer/198.HouseRobber.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 给定一个代表每个房屋存放金额的非负整数数组,计算你不触动警报装置的情况下(如果两间相邻 3 | 的房屋在同一晚上被小偷闯入,系统会自动报警) ,一夜之内能够偷窃到的最高金额。 4 | * @Author: JunLiangWang 5 | * @Date: 2024-02-29 10:18:25 6 | * @LastEditors: JunLiangWang 7 | * @LastEditTime: 2024-02-29 10:36:20 8 | */ 9 | 10 | 11 | /** 12 | * @description: 动态规划 TC:O(n^2) SC:O(n) 13 | * @param {*} nums 给定非负数组 14 | */ 15 | function dp(nums){ 16 | /** 17 | * 本方案使用动态规划,对于数n而言,其 18 | * 最大组合为: 19 | * 数n+Max(数n-2最大组合数,数n-3最大组合数....数0最大组合数) 20 | * 例如: 21 | * 数组: 3 2 8 9 7 1 22 | * 最大组合数: 3 2 11 12 18 13 23 | * 以此为状态方程编写代码即可 24 | */ 25 | 26 | // 如何数组长度为1,直接返回唯一元素即可 27 | if(nums.length==1)return nums[0] 28 | // 遍历数组元素 29 | for(let i=0;i=0;j--)max=Math.max(max,nums[j]) 34 | // 计算当前元素最大组合数 35 | nums[i]+=max 36 | } 37 | // 返回值 38 | return Math.max(nums[nums.length-1],nums[nums.length-2]) 39 | } 40 | 41 | /** 42 | * @description: 动态规划优化 TC:O(n) SC:O(1) 43 | * @param {*} nums 给定非负数组 44 | */ 45 | function dpOptimization(nums){ 46 | /** 47 | * 上述DP使用了数组存储结果,并且每次都会重复遍历 48 | * 获取之前元素的最大组合数,为此我们可以使用滚动 49 | * 数组进行优化,在每个时刻只需要存储前两个元素的 50 | * 最大组合数即可。 51 | */ 52 | 53 | // 如何数组长度为1,直接返回唯一元素即可 54 | if(nums.length==1)return nums[0] 55 | // 获取前两个元素的最大组合数 56 | let first=nums[0],second=Math.max(nums[0], nums[1]); 57 | // 遍历数组 58 | for(let i=2;i= target) { 51 | if (right - left < end - start) { 52 | start = left; 53 | end = right; 54 | } 55 | sum -= nums[left]; 56 | left++; 57 | } 58 | // 如果sum小于目标值,并且右指针超出了数组范围, 59 | // 证明我们已遍历完成,此时返回结果即可 60 | else { 61 | return end == nums.length + 1 ? 0 : end - start 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /General-Questions-Part1/48.rotateImage.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 给定一个n × n的二维矩阵请你将该矩阵顺时针旋转 90 度(仅允许O(1)的空间复杂度)。 3 | * @Author: JunLiangWang 4 | * @Date: 2023-05-11 17:31:13 5 | * @LastEditors: JunLiangWang 6 | * @LastEditTime: 2023-05-11 22:01:51 7 | */ 8 | 9 | 10 | 11 | /** 12 | * @description: 对角线+水平翻转 TC:O(n^2) SC:O(1) 13 | * @author: JunLiangWang 14 | * @param {*} matrix 给定矩阵 15 | * @return {*} 16 | */ 17 | function diagonalHorizontalRotation(matrix) 18 | { 19 | /** 20 | * 顺时针旋转90°可通过对角线翻转+水平翻转实现, 21 | * 顺手拿下身边的物体模拟即可理解。 22 | */ 23 | 24 | // 遍历矩阵行 25 | for(let i=0;i0) 43 | { 44 | // 记录当前队列长度,也是本次层级的 45 | // 所有节点数量,由于出队会动态影响 46 | // 到队列的长度,因此此处记录,就可 47 | // 以知道本次层级的节点是否全部出队 48 | let size=quene.length 49 | // 添加本次层级的数组 50 | out.push([]) 51 | // 遍历当前层级的所有节点 52 | while(size-->0){ 53 | // 出队 54 | let node=quene.shift(); 55 | 56 | // 判断方向,true:从左向右,即为尾插 57 | // false:从右向左即为头插 58 | if(dir) 59 | out[out.length-1].push(node.val); 60 | else 61 | out[out.length-1].unshift(node.val) 62 | // 将出队节点不为空的左右节点再次入队 63 | if(node.left)quene.push(node.left) 64 | if(node.right)quene.push(node.right) 65 | } 66 | // 翻转方向 67 | dir=!dir 68 | } 69 | // 返回结果 70 | return out 71 | } -------------------------------------------------------------------------------- /General-Questions-Part2/86.PartitionList.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 给你一个链表的头节点 head 和一个特定值 x ,请你对链表进行分隔,使得所有 小于 x 的节点都出现在 3 | 大于或等于 x 的节点之前。你应当 保留 两个分区中每个节点的初始相对位置。 4 | * @Author: JunLiangWang 5 | * @Date: 2023-07-12 09:28:23 6 | * @LastEditors: JunLiangWang 7 | * @LastEditTime: 2023-07-12 09:40:59 8 | */ 9 | 10 | 11 | /** 12 | * @description: 双指针 TC:O(n) SC:O(1) 13 | * @author: JunLiangWang 14 | * @param {*} head 给定链表头节点 15 | * @param {*} x 给定特定值X 16 | * @return {*} 17 | */ 18 | function doublePoint(head, x) { 19 | /** 20 | * 该方案使用双指针的方式,先遍历链表获得链表的长度, 21 | * 并将lastNode指针指向链表最后一个节点。因为首个节 22 | * 点有可能被移动到尾部,因此我们需要向链表头部添加 23 | * 一个哨兵节点。然后在使用一个指针currentNode指向 24 | * 头节点,利用该指针逐个遍历链表节点,当节点值大于 25 | * 给定值x,则将该节点移动到lastNode指针所指的节点 26 | * 的下一个节点,然后将lastNode指针指向下一个节点, 27 | * 该节点则被移动到了尾部。 28 | */ 29 | 30 | //向链表头部添加一个哨兵节点,因为首个节点有可能被移动到尾部 31 | const HEAD = new ListNode(0, head); 32 | // 指向最后一个节点的指针 33 | let lastNode = HEAD, 34 | // 指向当前节点的指针 35 | currentNode = HEAD, 36 | // 链表长度 37 | length = 0; 38 | 39 | // 遍历链表获得链表的长度,并将lastNode 40 | // 指针指向链表最后一个节点 41 | while (lastNode.next) { 42 | lastNode = lastNode.next; 43 | length++; 44 | } 45 | // 遍历链表 46 | while (length > 0) { 47 | // 获得当前指针的下一个节点 48 | let nextNode = currentNode.next 49 | // 如果该节点值大于给定值x,需要移动该节点到链表尾部 50 | // 如果此时该节点等于链表尾部节点则无需移动 51 | if (nextNode.val >= x && nextNode != lastNode) { 52 | currentNode.next = nextNode.next; 53 | nextNode.next = null; 54 | lastNode.next = nextNode; 55 | lastNode = lastNode.next; 56 | } 57 | // 否则继续遍历下一个节点 58 | else { 59 | currentNode = currentNode.next; 60 | } 61 | length--; 62 | } 63 | // 返回结果 64 | return HEAD.next; 65 | } -------------------------------------------------------------------------------- /General-Questions-Part1/39.combinedSum.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 给你一个无重复元素的整数数组candidates和一个目标整数target, 3 | 找出candidates中可以使数字和为目标数target的所有不同组合。 4 | candidates中的同一个数字可以无限制重复被选取。 5 | * @Author: JunLiangWang 6 | * @Date: 2023-04-26 08:51:18 7 | * @LastEditors: JunLiangWang 8 | * @LastEditTime: 2023-04-26 09:30:10 9 | */ 10 | 11 | 12 | /** 13 | * @description: 深度遍历+剪枝 TC:O(2^n) SC:O(n) 14 | * @author: JunLiangWang 15 | * @param {*} candidates 16 | * @param {*} target 17 | * @return {*} 18 | */ 19 | function dfs(candidates, target){ 20 | 21 | /** 22 | * 此题使用深度优先+剪枝的方案,仔细体会递归过程便可理解该题 23 | */ 24 | 25 | // 定义输出数组 26 | let outArray=[]; 27 | // 排序 28 | candidates.sort((a,b)=>a-b); 29 | /** 30 | * @description: 递归遍历,表示当前在candidates数组的第 31 | * index位,还剩targetValue要组合 32 | * @author: JunLiangWang 33 | * @param {*} targetValue 当前目标值 34 | * @param {*} combined 已组合的值 35 | * @param {*} index 当前candidates索引 36 | * @return {*} 37 | */ 38 | function recursion(targetValue,combined,index){ 39 | // 如果当前索引超出给定数组范围,直接return 40 | if(index>=candidates.length)return ; 41 | // 如果targetValue等于零,证明当前组合的和 42 | // 为target,将当前组合(combined)加入输出数组 43 | if(targetValue==0){ 44 | outArray.push(combined); 45 | return ; 46 | } 47 | // 如果当前目标值减去差值小于0,由于candidates为升序 48 | // 因此后续元素与当前差值依旧会小于0,因此舍去(剪枝) 49 | let diff=targetValue-candidates[index]; 50 | if(diff>=0) 51 | { 52 | // 跳到下一个值 53 | recursion(targetValue,combined,index+1); 54 | // 减去当前值,继续递归本次元素 55 | recursion(diff,[...combined,candidates[index]],index); 56 | } 57 | } 58 | 59 | // 执行递归 60 | recursion(target,[],0) 61 | // 返回结果 62 | return outArray; 63 | } -------------------------------------------------------------------------------- /General-Questions-Part3/130.SurroundedRegions.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 给你一个 m x n 的矩阵 board ,由若干字符 'X' 和 'O' , 3 | 找到所有被 'X' 围绕的区域,并将这些区域里所有的 'O' 用 4 | 'X' 填充。 5 | * @Author: JunLiangWang 6 | * @Date: 2023-09-26 09:12:50 7 | * @LastEditors: JunLiangWang 8 | * @LastEditTime: 2023-09-26 09:27:27 9 | */ 10 | 11 | 12 | /** 13 | * @description: 深度优先 TC:O(n) SC:O(n) 14 | * @author: JunLiangWang 15 | * @param {*} board 16 | * @return {*} 17 | */ 18 | function dfs(board){ 19 | /** 20 | * 该方案使用深度优先的方式,不被包围的元素则肯定 21 | * 是4条最外的边中的'O'的延伸,因此我们仅需要遍历 22 | * 最外层的边,然后找到为'O'的元素,利用递归向上/ 23 | * 下/左/右不断延申找到所有为'O'的元素即可,然后 24 | * 他们标记为'A'表示未被包围的'O',最终遍历数组, 25 | * 将'A'元素改为'O',将'O'元素改为'X'即可 26 | */ 27 | 28 | /** 29 | * @description: 递归实现深度优先遍历 30 | * @author: JunLiangWang 31 | * @param {*} x 横坐标索引 32 | * @param {*} y 纵坐标索引 33 | * @return {*} 34 | */ 35 | function recursion(x,y){ 36 | // 当超出矩阵索引/当前元素不为'O'结束递归 37 | if(x<0||x>=board[0].length||y<0||y>=board.length||board[y][x]!='O')return 38 | // 将为'O'的元素置为'A' 39 | board[y][x]='A' 40 | // 继续向上/下/左/右递归 41 | recursion(x+1,y) 42 | recursion(x-1,y) 43 | recursion(x,y+1) 44 | recursion(x,y-1) 45 | } 46 | // 遍历上下两条边,找到为'O'的元素 47 | for(let i=0;inew Array(p.length+1).fill(false)) 21 | // 由于当两字符为空时(长度为0),因也判断为匹配,所以重置[0][0]位置为true,其余为false 22 | DPArray[0][0]=true 23 | /** 24 | * @description: 判断是否匹配,当i2等于0时,证明还未匹配字符串S,此时无论匹配P字符串中的任何字符 25 | * 应当全为false;当i2大于0时p[j2-1](j2从1开始,因此匹配字符串需要-1)等于'.'或 26 | * S[i2-1](同理)时判断两字符为匹配 27 | **/ 28 | let isMatch=function(i2,j2){ 29 | return i2!=0&&(p[j2-1]=='.'||p[j2-1]==s[i2-1]) 30 | } 31 | // 遍历字符串S,由于P字符串中'*'号的存在,因此当字符串为空时仍存在匹配成功的例子, 32 | // 因此需要从0开始匹配(即从空字符串开始匹配) 33 | for(let i=0;i<=s.length;i++) 34 | { 35 | // 遍历字符串P 36 | for(let j=1;j<=p.length;j++) 37 | { 38 | // 当p[j-1]处为*时 39 | if(p[j-1]=='*') 40 | { 41 | // 赋值为*号匹配0个的情况,如(ca*)a*匹配零个的情况,则此处值应为c处的值 42 | DPArray[i][j]=DPArray[i][j-2] 43 | // 当*号字符前一个字符能够匹配当前S中的字符,则取(*号匹配0个的情况)||(前一个字符的匹配情况) 44 | if(isMatch(i,j-1))DPArray[i][j]=DPArray[i][j]||DPArray[i-1][j] 45 | } 46 | // 当p[j-1]处不为*时 47 | else 48 | { 49 | // 此时判断两字符是否匹配,如果匹配则赋值为上一字符是否匹配 50 | if(isMatch(i,j))DPArray[i][j]=DPArray[i-1][j-1] 51 | } 52 | } 53 | } 54 | return DPArray[s.length][p.length] 55 | } -------------------------------------------------------------------------------- /Sword-Pointing-Offer/427.ConstructQuadTree.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 给你一个 n * n 矩阵 grid ,矩阵由若干 0 和 1 组成。请你用四叉树表示该矩阵 grid 。 3 | * @Author: JunLiangWang 4 | * @Date: 2024-01-15 10:18:13 5 | * @LastEditors: JunLiangWang 6 | * @LastEditTime: 2024-01-15 10:32:43 7 | */ 8 | 9 | 10 | /** 11 | * @description: 递归回溯 TC:O(n^2) SC:O(n^2) 12 | * @author: JunLiangWang 13 | * @param {*} grid 给定矩阵 14 | * @return {*} 15 | */ 16 | function recursionBacktracking(grid) { 17 | /** 18 | * 本方案使用递归回溯的方式,将矩阵不断行列递归2分, 19 | * 直到长度为1,然后回溯过程将4个矩阵合并为1个,构 20 | * 造节点。 21 | */ 22 | 23 | /** 24 | * @description: 递归 25 | * @author: JunLiangWang 26 | * @param {*} startRow 开始行索引 27 | * @param {*} startCol 开始列索引 28 | * @param {*} length 矩阵长度 29 | * @return {*} 30 | */ 31 | function recursion(startRow, startCol, length) { 32 | // 直到长度为1,构造节点返回 33 | if (length == 1) return new Node(grid[startRow][startCol], true); 34 | // 2分行列 35 | let cuLength = length / 2, 36 | // 继续递归,获得左右上/左右下的矩阵节点 37 | nodeList = [recursion(startRow, startCol, cuLength), 38 | recursion(startRow, startCol + cuLength, cuLength), 39 | recursion(startRow + cuLength, startCol, cuLength), 40 | recursion(startRow + cuLength, startCol + cuLength, cuLength)] 41 | // 根据规则,合并4个矩阵,构造节点 42 | for (let i = 0; i < 4; i++) { 43 | // 如果子矩阵ifLeaf为false,或者其值不相等,则需要将其作为子节点 44 | if (!nodeList[i].isLeaf || (i != 0 && nodeList[i].val != nodeList[i - 1].val)) { 45 | return new Node(grid[startRow][startCol], false, nodeList[0], 46 | nodeList[1], nodeList[2], nodeList[3]) 47 | } 48 | } 49 | // 如果子矩阵ifLeaf为true,且其值全等,则需要子节点全为null,其isLeaf也为true 50 | return new Node(grid[startRow][startCol], true); 51 | } 52 | 53 | // 执行递归返回节点 54 | return recursion(0, 0, grid.length) 55 | } 56 | -------------------------------------------------------------------------------- /General-Questions-Part1/33.searchRotatedSortedArray.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 给你旋转后的数组nums和一个整数target,如果nums中存在这个目标值target,则返回它的下标,否则返回-1。 3 | * @Author: JunLiangWang 4 | * @Date: 2023-04-18 10:37:43 5 | * @LastEditors: JunLiangWang 6 | * @LastEditTime: 2023-04-18 10:57:20 7 | */ 8 | 9 | 10 | 11 | /** 12 | * @description: 二分查找 TC:O(logn) SC:O(1) 13 | * @author: JunLiangWang 14 | * @param {*} nums 输入数组 15 | * @param {*} target 目标值 16 | * @return {*} 17 | */ 18 | function binarySearch(nums,target){ 19 | /** 20 | * 该方案使用二分查找的方式,其中的关键则为将局部有序的数组一分为二,其中一定有一个是有序的, 21 | * 另一个可能是有序,也能是部分有序。此时有序部分用二分法查找,如果目标值未在其范围。则将无 22 | * 序部分再一分为二,其中一个一定有序,另一个可能有序,可能无序。就这样循环,直至遍历完数组。 23 | */ 24 | 25 | // 初始化左指针为0,右指针为数组最后一个元素 26 | let left=0,right=nums.length-1; 27 | // 当左指针超过右指针,证明遍历完成,跳出循环 28 | while(left<=right) 29 | { 30 | // 寻找中间值 31 | let middle=Math.floor((left+right)/2); 32 | // 如果中间值等于目标值,直接返回索引 33 | if(nums[middle]==target)return middle; 34 | 35 | // 找到有顺序的一半数组(无论如何分割数组,总有一半是全为升序的) 36 | // 如果nums[left]<=nums[middle]证明[left,middle]该区间全为升序 37 | if(nums[left]<=nums[middle]) 38 | { 39 | // 如果目标值不在该区间值的范围内,则去掉区间[left,middle]从 40 | // [middle+1,right]区间继续遍历 41 | if(targetnums[middle])left=(middle+1); 42 | // 否则目标值在区间[left,middle]中,则去掉[middle,right]区间 43 | // 从[left,middle-1]继续遍历 44 | else right=(middle-1); 45 | } 46 | // 否则[middle,right]区间全为升序 47 | else 48 | { 49 | // 如果目标值不在该区间值的范围内,则去掉区间[middle,right]从 50 | // [left,middle-1]区间继续遍历 51 | if(targetnums[right])right=(middle-1); 52 | // 否则目标值在区间[middle,right]中,则去掉[left,middle]区间 53 | // 从[middle+1,right]继续遍历 54 | else left=(middle+1); 55 | } 56 | } 57 | // 遍历完数组元素后仍未找到目标值,直接返回-1 58 | return -1; 59 | } -------------------------------------------------------------------------------- /General-Questions-Part2/68.TextJustification.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 给定一个单词数组 words 和一个长度 maxWidth ,重新排版单词,使其成为每行恰好有 maxWidth 个字符,且左右两端对齐的文本。 3 | * @Author: JunLiangWang 4 | * @Date: 2023-06-14 08:50:27 5 | * @LastEditors: JunLiangWang 6 | * @LastEditTime: 2023-06-14 08:53:23 7 | */ 8 | 9 | 10 | 11 | /** 12 | * @description: 模拟法 TC:O(n) SC:O(1) 13 | * @author: JunLiangWang 14 | * @param {*} words 15 | * @param {*} maxWidth 16 | * @return {*} 17 | */ 18 | function simulation(words, maxWidth) { 19 | /** 20 | * 作为困难题,该题并未太大价值,题解是从参考答案中copy的 21 | */ 22 | 23 | const ans = []; 24 | let right = 0, n = words.length; 25 | while (true) { 26 | const left = right; // 当前行的第一个单词在 words 的位置 27 | let sumLen = 0; // 统计这一行单词长度之和 28 | while (right < n && sumLen + words[right].length + right - left <= maxWidth) { 29 | sumLen += words[right].length; 30 | right++; 31 | } 32 | 33 | // 当前行是最后一行:单词左对齐,且单词之间应只有一个空格,在行末填充剩余空格 34 | if (right === n) { 35 | const s = words.slice(left).join(' '); 36 | ans.push(s + blank(maxWidth - s.length)); 37 | break; 38 | } 39 | const numWords = right - left; 40 | const numSpaces = maxWidth - sumLen; 41 | 42 | // 当前行只有一个单词:该单词左对齐,在行末填充空格 43 | if (numWords === 1) { 44 | ans.push(words[left] + blank(numSpaces)); 45 | continue; 46 | } 47 | 48 | // 当前行不只一个单词 49 | const avgSpaces = Math.floor(numSpaces / (numWords - 1)); 50 | const extraSpaces = numSpaces % (numWords - 1); 51 | const s1 = words.slice(left, left + extraSpaces + 1).join(blank(avgSpaces + 1)); // 拼接额外加一个空格的单词 52 | const s2 = words.slice(left + extraSpaces + 1, right).join(blank(avgSpaces)); // 拼接其余单词 53 | ans.push(s1 + blank(avgSpaces) + s2); 54 | } 55 | return ans; 56 | 57 | function blank(n) { 58 | return new Array(n).fill(' ').join(''); 59 | } 60 | } -------------------------------------------------------------------------------- /General-Questions-Part3/115.DistinctSubsequences.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 给你两个字符串 s 和 t ,统计并返回在 s 的 子序列 中 t 出现的个数。 3 | * @Author: JunLiangWang 4 | * @Date: 2023-08-29 09:53:05 5 | * @LastEditors: JunLiangWang 6 | * @LastEditTime: 2023-08-29 10:11:51 7 | */ 8 | 9 | 10 | /** 11 | * @description: 动态规划 TC:O(n^2) SC:O(n^2) 12 | * @author: JunLiangWang 13 | * @param {*} s 给定字符串s 14 | * @param {*} t 给定字符串t 15 | * @return {*} 16 | */ 17 | function dp(s, t) { 18 | /** 19 | * 本方案使用动态规划的方式,首先定义动态规划数组DPArray 20 | * 行索引代表子串t索引,列索引代码母串s索引,DPArray[i][j] 21 | * 则表示字符串t[0至i]的子串在字符串s[0至j]的子串中出现的 22 | * 次数。 23 | * 24 | * 当t[i]不等于s[j]时,字符串t[0至i]在s[0至j]出现的 25 | * 次数则等于其在s[0至j-1]出现的次数,即有: 26 | * t[i]!=s[j],DPArray[i][j]=DPArray[i][j-1] 27 | * 28 | * 当t[i]等于s[j]时,字符串t[0至i]在s[0至j]出现的 29 | * 次数则等于其在s[0至j-1]出现的次数加上t[0至i-1] 30 | * 在s[0至j-1]出现的次数,即有: 31 | * t[i]==s[j],DPArray[i][j]=DPArray[i][j-1]+DPArray[i-1][j-1] 32 | */ 33 | 34 | // 如果子串长度大于母串直接返回0 35 | if (s.length < t.length) return 0; 36 | // 定义DP数组,行列+1是因为方便后续[j-1]或[i-1]处理 37 | let DPArray = new Array(t.length + 1).fill(0).map(() => new Array(s.length + 1).fill(0)) 38 | // 将第0行所有列赋值为1 39 | for (let i = 0; i <= s.length; i++)DPArray[0][i] = 1; 40 | // 遍历字符串t 41 | for (let i = 1; i <= t.length; i++) { 42 | // 遍历字符串s 43 | for (let j = 1; j <= s.length; j++) { 44 | // 无论是否相等都需要加上t[0至i]在 45 | // s[0至j-1]出现的次数 46 | DPArray[i][j] = DPArray[i][j - 1] 47 | // 当t[i]等于s[j]时,字符串t[0至i]在s[0至j]出现的 48 | // 次数则等于其在s[0至j-1]出现的次数加上t[0至i-1] 49 | // 在s[0至j-1]出现的次数 50 | if (s[j - 1] == t[i - 1]) { 51 | DPArray[i][j] += DPArray[i - 1][j - 1] 52 | } 53 | } 54 | } 55 | // 题中说已做了最大整数的限制,但提交时发现未做限制导致答案错误 56 | // 因此此处判断是否超出了最大整数限制,超出返回-1 57 | return DPArray[t.length][s.length] <= Math.pow(2, 31) - 1 ? DPArray[t.length][s.length] : -1 58 | } -------------------------------------------------------------------------------- /General-Questions-Part2/92.ReverseLinkedListII.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 给你单链表的头指针 head 和两个整数 left 和 right ,其中 left <= right 。 3 | 请你反转从位置 left 到位置 right 的链表节点,返回 反转后的链表 。 4 | * @Author: JunLiangWang 5 | * @Date: 2023-07-25 09:21:51 6 | * @LastEditors: JunLiangWang 7 | * @LastEditTime: 2023-07-25 09:36:45 8 | */ 9 | 10 | 11 | 12 | /** 13 | * @description: 头插法 TC:O(n) SC:O(1) 14 | * @author: JunLiangWang 15 | * @param {*} head 给定链表头节点 16 | * @param {*} left 给定翻转开始节点的位置(从1开始) 17 | * @param {*} right 给定翻转结束节点的位置(从1开始) 18 | * @return {*} 19 | */ 20 | function headInsertion(head, left, right) { 21 | /** 22 | * 该方案使用头插法,由于头节点也可能被翻转,因此我们一 23 | * 开始需要向头节点前插入一个虚拟节点。然后遍历链表达到 24 | * 开始翻转的节点的上一个节点,然后新建一个链表,不断使 25 | * 用头插,将原链表中需要翻转的节点不断头插到新链表,然后 26 | * 从原链表中删除该节点,直到到达翻转结束节点。此时新链表 27 | * 则为翻转后的链表,将新链表插入回原链表即可完成翻转操作 28 | */ 29 | 30 | // 如果翻转开始/结束位置一样,证明无需翻转,直接返回原链表 31 | if (left == right) return head 32 | // 由于头节点也可能被翻转,因此我们一开始需要向头节点前插 33 | // 入一个虚拟节点 34 | const HEAD = new ListNode(0, head) 35 | let preNode = HEAD, 36 | // 需要翻转的节点数量,加1是因为假设:right=2,left=1 37 | // 需要翻转的节点数量为2,而right-left=1,此时需要加1 38 | count = right - left +1; 39 | // 遍历原链表节点,找到开始翻转的节点的上一个节点 40 | while (--left > 0)preNode = preNode.next 41 | // 新链表,记录翻转后的节点 42 | let reverseNode = new ListNode(0), 43 | // 此时首个需要翻转的节点,在翻转后是最后一个节点,需要 44 | // 将其保存起来,或许更改它的next,使其接入原链表 45 | lastNode = preNode.next; 46 | 47 | // 遍历需要翻转的节点, 不断使用头插,将原链表中需要翻转 48 | // 的节点不断头插到新链表,然后从原链表中删除该节点,直 49 | // 到到达翻转结束节点。 50 | while (count--) { 51 | let currentNode = preNode.next; 52 | preNode.next = preNode.next.next 53 | currentNode.next = reverseNode.next; 54 | reverseNode.next = currentNode 55 | } 56 | // 翻转完成所有节点,需要将新链表插入回原链表 57 | 58 | // 将新链表中最后一个节点的next指向,原链表当前节点的next 59 | lastNode.next = preNode.next 60 | // 将原链表当前节点的next指向新链表 61 | preNode.next = reverseNode.next 62 | // 返回结果 63 | return HEAD.next 64 | } 65 | -------------------------------------------------------------------------------- /General-Questions-Part2/59.SpiralMatrixII.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 给你一个正整数 n ,生成一个包含 1 到 n^2 所有元素, 3 | 且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix 。 4 | * @Author: JunLiangWang 5 | * @Date: 2023-06-03 23:19:02 6 | * @LastEditors: JunLiangWang 7 | * @LastEditTime: 2023-06-03 23:26:17 8 | */ 9 | 10 | 11 | /** 12 | * @description: 模拟法 TC:O(n^2) SC:O(n^2) 13 | * @author: JunLiangWang 14 | * @param {*} n 给定整数数 15 | * @return {*} 16 | */ 17 | function simulation(n){ 18 | 19 | /** 20 | * 该题与螺旋矩阵I相同,同样可以利用模拟法的方式, 21 | * 利用循环遍历模拟矩阵顺时针螺旋顺序输出元素 22 | */ 23 | 24 | // 定义方向,0:向右,1:向下,2:向左,3:向上 25 | let dir = 0, 26 | // 当前元素所在行 27 | row = 0, 28 | // 当前元素所在列,从-1开始为了方便出来开始+1的情况 29 | col = -1, 30 | // 已经遍历了多少圈,每遍历一圈,row/col的最大值则会 31 | // 减一 32 | spinCount = 0, 33 | // 定义矩阵 34 | matrix=new Array(n).fill(0).map(()=>new Array(n)); 35 | // 遍历矩阵所有元素 36 | for(let i=1;i<=n*n;i++){ 37 | // 选择方向 38 | switch (dir) { 39 | // 向右 40 | case 0: 41 | col++; 42 | // 当达到右边界,方向变为向下 43 | if (col === n - spinCount - 1) dir++; 44 | break; 45 | // 向下 46 | case 1: 47 | row++; 48 | // 当达到下边界,方向变为向左 49 | if (row === n - spinCount - 1) dir++; 50 | break; 51 | // 向左 52 | case 2: 53 | col--; 54 | // 当达到左边界,方向变为向上 55 | if (col === spinCount) dir++; 56 | break; 57 | // 向上 58 | case 3: 59 | row--; 60 | // 当达到上边界,方向变为向右,并且 61 | // 圈数+1 62 | if (row === spinCount + 1) { 63 | dir = 0; 64 | spinCount++; 65 | } 66 | break; 67 | } 68 | // 将当前元素添加到输出数组 69 | matrix[row][col]=i 70 | } 71 | // 输出 72 | return matrix; 73 | } -------------------------------------------------------------------------------- /Sword-Pointing-Offer/189.RotateArray.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 给定一个整数数组 nums,将数组中的元素向右轮转 k 个位置, 3 | 其中 k 是非负数。 4 | * @Author: JunLiangWang 5 | * @Date: 2023-11-08 09:28:48 6 | * @LastEditors: JunLiangWang 7 | * @LastEditTime: 2023-11-08 09:45:19 8 | */ 9 | 10 | 11 | 12 | /** 13 | * @description: 使用数组 TC:O(n) SC:O(n) 14 | * @author: JunLiangWang 15 | * @param {*} nums 给定数组 16 | * @param {*} k 轮转的位数 17 | * @return {*} 18 | */ 19 | function array(nums, k){ 20 | /** 21 | * 本方案使用额外的数组,首先对使用数组长度对k 22 | * 进行取余,获得轮转恢复原样N次后,仍需要移动 23 | * 多少位。然后定义一个新的数组并将原数组赋值给 24 | * 它。最后遍历改变数组元素即可 25 | */ 26 | let index=k%nums.length; 27 | if(index==0)return ; 28 | let newNums=[...nums] 29 | for(let i=0;i=nums.length)index=0 33 | } 34 | } 35 | 36 | 37 | /** 38 | * @description: 翻转 TC:O(n) SC:O(1) 39 | * @author: JunLiangWang 40 | * @param {*} nums 给定数组 41 | * @param {*} k 轮转的位数 42 | * @return {*} 43 | */ 44 | function reverseArray(nums,k){ 45 | /** 46 | * 本方案使用翻转数组的方案,与上同理首先 47 | * 对使用数组长度对k进行取余 ,获得轮转恢 48 | * 复原样N次后,仍需要移动多少位。然后我们 49 | * 看如下示例: 50 | * nums=[1,2,3,4,5,6,7],k=3 51 | * 52 | * 对数组0到n-1进行翻转: 53 | * nums=[7,6,5,4,3,2,1] 54 | * 对数组0到k-1进行翻转: 55 | * nums=[5,6,7,4,3,2,1] 56 | * 对数组k到n-1进行翻转: 57 | * nums=[5,6,7,1,2,3,4] 58 | * 59 | * 此时我们就获得了答案,因此仅需对数组进行 60 | * 如下翻转操作即可得到答案 61 | * reverse(0,n-1) 62 | * reverse(0,k-1) 63 | * reverse(k,n-1) 64 | */ 65 | let index=k%nums.length; 66 | if(index==0)return ; 67 | function reverse(start,end){ 68 | while(start0?carryNumber+result:result; 67 | } -------------------------------------------------------------------------------- /General-Questions-Part1/46.permutations.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。 3 | * @Author: JunLiangWang 4 | * @Date: 2023-05-07 13:59:46 5 | * @LastEditors: JunLiangWang 6 | * @LastEditTime: 2023-05-07 14:28:39 7 | */ 8 | 9 | 10 | /** 11 | * @description: 递归回溯 TC:O(2^n) SC:O(n) 12 | * @author: JunLiangWang 13 | * @param {*} nums 给定数组 14 | * @return {*} 15 | */ 16 | function recursionBacktracking(nums){ 17 | /** 18 | * 该方案使用递归回溯的方式,递归参数为:unusedNums以及usedNums,unusedNums 19 | * 表示当前未被使用到的数(即未加入到排列的数,已加入到排列的数unusedNums[i] 20 | * 将会赋值为null),usedNums表示已加入到排列的数。我们在递归函数中遍历unusedNums 21 | * 当其元素不等于null时,证明该数未加入到排列,此时我们将其加入到排列并标识其为已被 22 | * 使用(unusedNums[i]=null),然后将二者作为新的参数继续递归,直到已加入到排列的数的 23 | * 数量等于给定数组元素的数量则证明所有数排列完成,此时添加结果,停止递归。 24 | */ 25 | 26 | // 定义输出数组 27 | let outArray=[]; 28 | /** 29 | * @description: 利用递归遍历未被使用的数,并将当前未被使用的数标识为已使用 30 | * 并将该数加入当前排列继续递归,直到排列元素数量等于给定数组 31 | * 元素数量,证明排列完成停止递归 32 | * @author: JunLiangWang 33 | * @param {*} unusedNums 未被使用的数 34 | * @param {*} usedNums 已被使用的数(当前排列) 35 | * @return {*} 36 | */ 37 | function recursion(unusedNums,usedNums){ 38 | // 如已使用的数的数量等于给定数组的元素的数量 39 | // 证明排列完成 40 | if(usedNums.length==nums.length) 41 | { 42 | // 向输出数组添加该排列 43 | outArray.push(usedNums); 44 | // 返回 45 | return ; 46 | } 47 | // 否则,遍历未使用的数 48 | for(let i=0;i 0) { 36 | // 选择方向 37 | switch (dir) { 38 | // 向右 39 | case 0: 40 | col++; 41 | // 当达到右边界,方向变为向下 42 | if (col === matrix[0].length - spinCount - 1) dir++; 43 | break; 44 | // 向下 45 | case 1: 46 | row++; 47 | // 当达到下边界,方向变为向左 48 | if (row === matrix.length - spinCount - 1) dir++; 49 | break; 50 | // 向左 51 | case 2: 52 | col--; 53 | // 当达到左边界,方向变为向上 54 | if (col === spinCount) dir++; 55 | break; 56 | // 向上 57 | case 3: 58 | row--; 59 | // 当达到上边界,方向变为向右,并且 60 | // 圈数+1 61 | if (row === spinCount + 1) { 62 | dir = 0; 63 | spinCount++; 64 | } 65 | break; 66 | } 67 | // 将当前元素添加到输出数组 68 | outArray.push(matrix[row][col]); 69 | } 70 | // 输出 71 | return outArray; 72 | } -------------------------------------------------------------------------------- /General-Questions-Part2/94.BinaryTreeInorderTraversal.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 给定一个二叉树的根节点 root ,返回 它的 中序 遍历 。 3 | * @Author: JunLiangWang 4 | * @Date: 2023-07-28 10:29:40 5 | * @LastEditors: JunLiangWang 6 | * @LastEditTime: 2023-07-28 10:57:17 7 | */ 8 | 9 | 10 | /** 11 | * @description: 递归回溯 TC:O(n) SC:O(n) 12 | * @author: JunLiangWang 13 | * @param {*} root 给定树的头节点 14 | * @return {*} 15 | */ 16 | function recursionBackTracking(root) { 17 | /** 18 | * 该方案利用递归回溯,对树进行中序遍历(左根右) 19 | */ 20 | 21 | // 如果树为空,直接返回[] 22 | if (!root) return [] 23 | // 记录中序遍历结果 24 | let outArray = [] 25 | /** 26 | * @description: 递归中序遍历 27 | * @author: JunLiangWang 28 | * @param {*} node 当前节点 29 | * @return {*} 30 | */ 31 | function recursion(node) { 32 | // 如果存在左节点继续递归,直到没有左节点 33 | if (node.left) recursion(node.left) 34 | // 记录最左的节点,然后依次回溯记录 35 | outArray.push(node.val) 36 | // 最后存在右节点继续递归 37 | if (node.right) recursion(node.right) 38 | } 39 | 40 | // 执行递归 41 | recursion(root) 42 | // 返回结果 43 | return outArray 44 | } 45 | 46 | 47 | /** 48 | * @description: 迭代法 TC:O(n) SC:O(n) 49 | * @author: JunLiangWang 50 | * @param {*} root 给定树的头节点 51 | * @return {*} 52 | */ 53 | function iteration(root) { 54 | /** 55 | * 递归回溯本质上是利用函数调用机制维护了一个栈,因此 56 | * 我们可以自己定义一个栈,然后使用迭代的方式模拟这个 57 | * 过程,而不需要使用递归 58 | */ 59 | // 定义一个栈 60 | let stack = [], 61 | // 记录中序遍历结果 62 | outArray = []; 63 | 64 | // 当头节点为空并且栈中并为节点,结束遍历 65 | while (root || stack.length) { 66 | // 不断遍历左节点,并将其添加到栈中, 67 | // 直到为树中最左节点 68 | while (root) { 69 | stack.push(root) 70 | root = root.left 71 | } 72 | // 此时栈顶为最左节点,中序遍历其 73 | // 此时应该出栈,并记录它的值 74 | 75 | // 将当前最左节点出栈 76 | let lastNode = stack.pop() 77 | // 记录它的值 78 | outArray.push(lastNode.val) 79 | // 遍历它的右节点 80 | root = lastNode.right 81 | } 82 | 83 | // 返回结果 84 | return outArray; 85 | } 86 | -------------------------------------------------------------------------------- /General-Questions-Part1/44.wildcardMatching.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 给你一个输入字符串 (s) 和一个字符模式 (p) ,请你实现一个支持 '?' 和 '*' 匹配规则的通配符匹配: 3 | 1.'?' 可以匹配任何单个字符。 4 | 2.'*' 可以匹配任意字符序列(包括空字符序列)。 5 | 判定匹配成功的充要条件是:字符模式必须能够 完全匹配 输入字符串(而不是部分匹配)。 6 | * @Author: JunLiangWang 7 | * @Date: 2023-05-05 22:22:07 8 | * @LastEditors: JunLiangWang 9 | * @LastEditTime: 2023-05-05 23:05:54 10 | */ 11 | 12 | 13 | /** 14 | * @description: 动态规划 TC:O(n^2) SC:O(n^2) 15 | * @author: JunLiangWang 16 | * @param {*} s 原串 17 | * @param {*} p 匹配字符串 18 | * @return {*} 19 | */ 20 | function dp(s,p) 21 | { 22 | /** 23 | * 该方案使用动态规划的方式,构造一个[p.length+1,s.length+1]的二维矩阵, 24 | * 行从1开始对应匹配字符串字符索引,列从1开始对应原串字符索引,之所以行 25 | * 列都加了1个长度,是因为0行/0列可代表匹配字符串/原串为空的情况, 也 26 | * 方便后续处理,该矩阵[i,j]的状态代表:匹配字符从0到i-1处能否与原串字 27 | * 符从0到j-1匹配,匹配成功为1,失败为0。如果矩阵最后一个状态为1,则证明 28 | * 匹配字符串与原串匹配成功。 29 | */ 30 | 31 | // 如果原串等于匹配字符串,直接返回true 32 | if(s===p)return true; 33 | // 定义dp矩阵(行从1开始对应匹配字符串字符索引,列从1开始对应原串字符索引) 34 | // 之所以行列都加了1个长度,是因为0行/0列可代表匹配字符串/原串为空的情况, 35 | // 也方便后续处理 36 | let dpArray=new Array(p.length+1).fill(0).map(()=>new Array(s.length+1).fill(0)); 37 | // 当两者都为空字符串时,此时两字符匹配,因此初始化[0][0]位置为1 38 | dpArray[0][0]=1; 39 | // 遍历匹配字符串每个字符 40 | for(let i=1;i<=p.length;i++) 41 | { 42 | // 当匹配字符为"*"时,由于"*"可匹配零个字符,因此其 43 | // ([i][0])位置状态应为上一匹配字符0列([i-1][0])位置状态 44 | if(p[i-1]==="*")dpArray[i][0]=dpArray[i-1][0]; 45 | // 遍历原串每个字符 46 | for(let j=1;j<=s.length;j++) 47 | { 48 | // 如果当前匹配字符等于"*",当"*"匹配0个字符时,其状态应等于上一个匹配字符 49 | // 匹配原串当前字符的状态即dpArray[i-1][j],当"*"匹配n个字符时,其状态应 50 | // 等于当前匹配字符匹配原串上一字符的状态即dpArray[i][j-1],因此此处状态 51 | // 为两者或的结果 52 | if(p[i-1]==="*")dpArray[i][j]=dpArray[i-1][j]||dpArray[i][j-1]; 53 | // 当前匹配字符匹配原串成功,当前状态则等于上一个匹配字符/原串字符匹配的情况 54 | else if(p[i-1]==="?"||p[i-1]===s[j-1])dpArray[i][j]=dpArray[i-1][j-1]; 55 | } 56 | } 57 | // 如果最后一个字符匹配成功,则证明匹配字符串能匹配原串 58 | return dpArray[p.length][s.length]==1; 59 | } -------------------------------------------------------------------------------- /General-Questions-Part2/88.MergeSortedArray.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元素数目。 3 | 请你 合并 nums2 到 nums1 中,使合并后的数组同样按 非递减顺序 排列。 4 | * @Author: JunLiangWang 5 | * @Date: 2023-07-19 09:06:51 6 | * @LastEditors: JunLiangWang 7 | * @LastEditTime: 2023-07-19 09:24:09 8 | */ 9 | 10 | /** 11 | * @description: 双指针 TC:O(n) SC:O(1) 12 | * @author: JunLiangWang 13 | * @param {*} nums1 给定非递减顺序排列的整数数组nums1 14 | * @param {*} m nums1数组长度 15 | * @param {*} nums2 给定非递减顺序排列的整数数组nums2 16 | * @param {*} n nums2数组长度 17 | * @return {*} 18 | */ 19 | function doublePoint(nums1, m, nums2, n) { 20 | /** 21 | * 该方案使用双指针的方式,nums1.length是等于m+n的,因此 22 | * 我们可以利用nums1[m+1:nums1.length-1]这段空闲的空间,来 23 | * 放排好序的元素,由于是从尾部开始的,因此我们不能再从前 24 | * 向后升序比较元素,而是从后向前降序比较元素。 25 | * 26 | * 我们定义两个指针firstPoint指向nums1的最后一个元素即: 27 | * m-1,secondPoint指向nums2的最后一个元素即:n-1。然后 28 | * 我们将两指针所指的较大的元素放入最后nums1最后的位置 29 | * (即nums1[firstPoint+seoncdPoint+1])然后将相应的数 30 | * 组元素前移一位,直到firstPoint或secondPoint任一超 31 | * 出数组范围,然后再将未超出数组范围的指针元素逐个放入 32 | * nums1中即可 33 | */ 34 | 35 | // 定义指针指向nums1的最后一个元素 36 | let firstPoint = m - 1, 37 | // 定义指针指向nums2的最后一个元素 38 | secondPoint = n - 1; 39 | // firstPoint或secondPoint任一超 40 | // 出数组范围则结束遍历 41 | while (firstPoint >= 0 && secondPoint >= 0) { 42 | // 将两指针所指的较大的元素放入最后nums1最后的位置 43 | // (即nums1[firstPoint+seoncdPoint+1])然后将相应的数 44 | // 组元素前移一位 45 | if (nums1[firstPoint] > nums2[secondPoint]) { 46 | nums1[firstPoint + secondPoint + 1] = nums1[firstPoint]; 47 | firstPoint--; 48 | } 49 | else { 50 | nums1[firstPoint + secondPoint + 1] = nums2[secondPoint]; 51 | secondPoint--; 52 | } 53 | } 54 | // 如果secondPoint未超出数组范围, 55 | // 则将剩余元素逐个放入nums1中 56 | if (secondPoint >= 0) { 57 | for (let i = 0; i <= secondPoint; i++)nums1[i] = nums2[i]; 58 | } 59 | 60 | // 如果firstPoint未超过数组范围, 61 | // 由于firstPoint所指元素本身就在 62 | // nums1中且是升序的,因此无需操作 63 | } -------------------------------------------------------------------------------- /Sword-Pointing-Offer/242.ValidAnagram.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。 3 | * @Author: JunLiangWang 4 | * @Date: 2023-11-22 09:08:29 5 | * @LastEditors: JunLiangWang 6 | * @LastEditTime: 2023-11-22 09:17:48 7 | */ 8 | 9 | 10 | /** 11 | * @description: 数组 TC:O(n) SC:O(1) 12 | * @author: JunLiangWang 13 | * @param {*} s 给定字符串s 14 | * @param {*} t 给定字符串t 15 | * @return {*} 16 | */ 17 | function array(s, t) { 18 | /** 19 | * 本题使用数组的方式,由于字符串中仅 20 | * 存在小写字母,因此我们可以通过构建 21 | * 一个26位的数组,利用数组记录字符串 22 | * 中a-z的字符数量即可 23 | */ 24 | // 如果两者长度不一致,肯定不为字母异位词 25 | if (s.length != t.length) return false 26 | // 定义一个26位的数组记录单词数量 27 | let wordList = new Array(26).fill(0) 28 | // 遍历字符串 29 | for (let i = 0; i < s.length; i++) { 30 | // 记录单词数量 31 | wordList[s.charCodeAt(i) - 97]++; 32 | wordList[t.charCodeAt(i) - 97]--; 33 | } 34 | // 遍历数组,如果某个单词数量不为0,则 35 | // 证明两字符串中该单词数量不一样 36 | for (let i = 0; i < 26; i++)if (wordList[i] != 0) return false 37 | return true 38 | } 39 | 40 | 41 | 42 | /** 43 | * @description: 哈希表 TC:O(n) SC:O(n) 44 | * @author: JunLiangWang 45 | * @param {*} s 给定字符串s 46 | * @param {*} t 给定字符串t 47 | * @return {*} 48 | */ 49 | function hashMap(s, t) { 50 | /** 51 | * 本题使用哈希表的方式,与上述使用 52 | * 数组的方式思路一样,利用哈希表记 53 | * 录字符串中单词的数量 54 | */ 55 | 56 | // 如果两者长度不一致,肯定不为字母异位词 57 | if (s.length != t.length) return false 58 | // 定义哈希表 59 | let map = new Map 60 | // 遍历字符串s,在hashMap中记录单词 61 | for (let i = 0; i < s.length; i++) { 62 | let count = map.get(s[i]) 63 | map.set(s[i], count == undefined ? 1 : count + 1) 64 | } 65 | // 遍历字符串t 66 | for (let i = 0; i < t.length; i++) { 67 | let count = map.get(t[i]) 68 | // 如果hashMap中单词数量为0,或没有 69 | // 这个单词,证明两字符单词数量不一致 70 | if (count == 0 || count == undefined) return false 71 | map.set(t[i], count - 1) 72 | } 73 | // 遍历hashMap,如果存在数量不为0的单词 74 | // 证明两字符单词数量不一致 75 | for (let key in map) if (map.get(key) != 0) return false 76 | 77 | return true 78 | } 79 | -------------------------------------------------------------------------------- /General-Questions-Part1/11.holdsMostWater.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。 3 | 找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。返回容器可以储存的最大水量。 4 | * @Author: JunLiangWang 5 | * @Date: 2023-03-07 10:10:39 6 | * @LastEditors: JunLiangWang 7 | * @LastEditTime: 2023-03-08 08:53:59 8 | */ 9 | 10 | /** 11 | * @description: 暴力破解方法 TC:O(n^2) SC:O(1) 12 | * @author: JunLiangWang 13 | * @param {*} height 输入的高度数组 14 | * @return {*} 15 | */ 16 | function bruteForce(height) 17 | { 18 | let maxAreaValue=0 19 | // 从0开始遍历数组,i为左边界 20 | for(let i=0;imaxAreaValue)maxAreaValue=tempArea 29 | } 30 | } 31 | return maxAreaValue 32 | } 33 | 34 | /** 35 | * @description: 双指针 TC:O(n) SC:O(1) 36 | * @author: JunLiangWang 37 | * @param {*} height 输入的高度数组 38 | * @return {*} 39 | */ 40 | function doublePoint(height){ 41 | // 左边界指针 42 | let left=0 43 | // 右边界指针 44 | let right=height.length-1 45 | // 最大面积 46 | let maxArea=0 47 | // 中止循环条件:当左右边界指针重合或左边界超出右边界 48 | while(right>left) 49 | { 50 | // 计算当前面积:(right-left)为x轴长,min(height[right],height[left])为y轴高 51 | let tempArea=(right-left)*Math.min(height[right],height[left]) 52 | // 当前面积大于记录的最大值,则更改最大值 53 | if(tempArea>maxArea)maxArea=tempArea 54 | // 当右边界值更高,则使其左边界向中心移动 55 | // 当左边界值更高,则使其右边界向中心移动 56 | // 直至不满足循环 57 | if(height[right]>height[left])left++ 58 | else right-- 59 | } 60 | return maxArea 61 | /** 62 | * 为什么双指针的做法是正确的? 63 | * 双指针代表的是可以作为容器边界的所有位置的范围。在一开始,双指针指向数组的左右边界, 64 | * 表示数组中所有的位置都可以作为容器的边界,因为我们还没有进行过任何尝试。在这之后, 65 | * 我们每次将对应的数字较小的那个指针往另一个指的方向移动一个位置,就表示我们认为这 66 | * 个指针不可能再作为容器的边界了。 67 | * 68 | * 当某一边界确定时,则它为短期内的极大值,即使后续遇见更大值,其前一极大值与当前边界值 69 | * 已组成了最高的高 70 | */ 71 | } -------------------------------------------------------------------------------- /General-Questions-Part1/08.stringToInteger.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 将字符串转换成一个 32 位有符号整数,并满足以下要求: 3 | 1.读入字符串并丢弃无用的前导空格(' ') 4 | 2.检查下一个字符(假设还未到字符末尾)为正还是负号,读取该字符(如果有)。 5 | 确定最终结果是负数还是正数。 如果两者都不存在,则假定结果为正。 6 | 3.读入下一个字符,直到到达下一个非数字字符或到达输入的结尾。字符串的其余 7 | 部分将被忽略。 8 | 4.将前面步骤读入的这些数字转换为整数(即,"123" -> 123, "0032" -> 32)。 9 | 如果没有读入数字,则整数为 0 。必要时更改符号(从步骤 2 开始)。 10 | 5.如果整数数超过 32 位有符号整数范围 [−231,  231 − 1] ,需要截断这个整数, 11 | 使其保持在这个范围内。具体来说,小于 −231 的整数应该被固定为 −231 ,大于 12 | 231 − 1 的整数应该被固定为 231 − 1 。 13 | 6.返回整数作为最终结果。 14 | * @Author: JunLiangWang 15 | * @Date: 2023-02-28 10:22:40 16 | * @LastEditors: JunLiangWang 17 | * @LastEditTime: 2023-02-28 10:44:20 18 | */ 19 | 20 | 21 | /** 22 | * @description: 一般方法 TC:O(n) SC:O(1) 23 | * @author: JunLiangWang 24 | * @param {*} s 输入字符串 25 | * @return {*} 26 | */ 27 | function myAtoi(s){ 28 | // 需要匹配的标记 29 | const mark=['0','1','2','3','4','5','6','7','8','9',' ','-','+'] 30 | // 是否开始匹配,默认为未开始匹配 31 | let isMatched=false 32 | // count总数,默认为0;plusOrMinus是否为负数,默认为正 33 | let count=0,plusOrMinus=1 34 | 35 | for(let i=0;i=10)return count*plusOrMinus 41 | // 如果不满足上述条件,当匹配到' '字符直接跳过 42 | if(markIndex==10) continue 43 | // 如果不满足上述条件,则进入匹配阶段 44 | isMatched=true 45 | // 当匹配到正负号时,改变正负号变量 46 | if(markIndex>=11)plusOrMinus=markIndex==11?-1:1 47 | else 48 | { 49 | // 当匹配到数值 50 | const currentCount=s[i]*1 51 | const tempCount=count*plusOrMinus 52 | // 判断是否超出数值范围 53 | if(tempCount>214748364||(tempCount==214748364&¤tCount>7)) 54 | return 2147483647 55 | else if(tempCount<-214748364||(tempCount==-214748364&¤tCount>8)) 56 | return -2147483648 57 | // 未超出则加入数值 58 | count=count*10+currentCount 59 | } 60 | } 61 | return count*plusOrMinus 62 | } -------------------------------------------------------------------------------- /General-Questions-Part3/149.MaxPointsonaLine.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 给你一个数组 points ,其中 points[i] = [xi, yi] 3 | 表示 X-Y 平面上的一个点。求最多有多少个点在同一条 4 | 直线上。 5 | * @Author: JunLiangWang 6 | * @Date: 2023-10-28 10:26:45 7 | * @LastEditors: JunLiangWang 8 | * @LastEditTime: 2023-10-28 10:43:08 9 | */ 10 | 11 | 12 | /** 13 | * @description: 暴力破解 TC:O(n^3) SC:O(1) 14 | * @author: JunLiangWang 15 | * @param {*} points 给定数组 16 | * @return {*} 17 | */ 18 | function bruteForce(points){ 19 | /** 20 | * 本方案采用暴力破解得方式,一开始我的想法是遍历数组元素 21 | * 两两组成一条直线,然后根据直线求出斜率与截距,得出直线 22 | * 方程,最终再次遍历数组元素,将(x,y)带入直线方程中,如果 23 | * 满足则证明该点存在直线中。 24 | * 25 | * 但我在其中遇到了如下问题: 26 | * 0.斜率公式为:(y2-y1)/(x2-x1),(x2-x1)可能等于0 27 | * 1.先求出斜率可能存在精度不够得问题,例如斜率为1/3,此时 28 | * 存储的数据则为:0.33333333333,此时点为(3,1),代入时 29 | * 结果为0.999999999,不等于1了 30 | * 31 | * 解决思路: 32 | * 我们先不求解两点直线的斜率/截距,而是在再次遍历数组 33 | * 找存在于直线的元素时,将3个点分别构成两条直线,如果 34 | * 两直线斜率相等,则两直线在一条直线上,否则不在一条直 35 | * 线上,此时公式则为 36 | * 37 | * 求直线1斜率:(y2-y1)/(x2-x1) 38 | * 求直线2斜率:(y3-y2)/(x3-x2) 39 | * 40 | * 两者斜率相等才存在于一条直线上: 41 | * (y2-y1)/(x2-x1)==(y3-y2)/(x3-x2) 42 | * 变换一下:(y2-y1)*(x3-x2)==(y3-y2)*(x2-x1) 43 | * 44 | * 这里巧妙的把除法变成了乘法,规避了精度问题以及 45 | * x2-x1==0或x3-x2==0的问题。 46 | * 47 | * 48 | */ 49 | if(points.length<3)return points.length 50 | let maxCount=2 51 | for(let i=0;i[[0,8],[2,3],[3,6]]。然后第一个指针 22 | * (firstPoint)初始化指向首个元素(记作first),第二个指针(secondPoint) 23 | * 初始化指向第二个元素(记作second),如果first与second产生了交集(即: 24 | * 当first的第二个元素大于等于了second的第一个元素,first[1]>=second[0], 25 | * 此时两元素则有交集),由于我们数组是按照元素中第一个元素升序排序的,因 26 | * 此无论如何first[0]则为最小的,所以合并后的元素为[first[0],Max(first[1],second[1])], 27 | * 然后我们将firstPoint所指的元素更改为合并后的元素。当first与second并无交集, 28 | * 我们则将firstPoint指向下一个元素,并将该元素赋值为secondPoint所指元素。 29 | * 最后secondPoint继续指向下一个元素,重复上述操作,直到遍历完成所有元素。 30 | * 31 | */ 32 | 33 | // 根据元素中的第一个元素对数组进行升序排序 34 | // 例如:[[3,6],[0,8],[2,3]]==>[[0,8],[2,3],[3,6]] 35 | intervals.sort((a, b) => a[0] - b[0]) 36 | 37 | // 初始化第一个指针指向首个元素 38 | let firstPoint = 0; 39 | // 初始化第二个指针指向第二个元素 40 | for (let secondPoint = 1; secondPoint < intervals.length; secondPoint++) { 41 | // 如果两指针所指元素产生了交集 42 | if (intervals[firstPoint][1] >= intervals[secondPoint][0]) { 43 | // 则需要将两素合并,并把firstPoint所指的元素更改为合并后的元素由于 44 | // 我们数组是按照元素中第一个元素升序排序的,因此无论如何intervals[firstPoint][0] 45 | // 都是最小的,所以合并后的元素为 46 | // [intervals[firstPoint][0],Max(intervals[firstPoint][1],intervals[secondPoint][1])] 47 | intervals[firstPoint] = [intervals[firstPoint][0], 48 | Math.max(intervals[firstPoint][1], intervals[secondPoint][1]) 49 | ] 50 | // 当两指针所指元素并无交集,我们则将firstPoint指向下一个元素,并将该元素赋值为secondPoint所指元素。 51 | } else { 52 | firstPoint++; 53 | intervals[firstPoint] = intervals[secondPoint]; 54 | } 55 | } 56 | // 此时经过合并后数组长度变更为firstPoint+1 57 | intervals.length = firstPoint + 1; 58 | // 返回结果 59 | return intervals; 60 | } -------------------------------------------------------------------------------- /.vitepress/theme/components/ReloadPrompt.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 65 | 66 | -------------------------------------------------------------------------------- /General-Questions-Part2/60.PermutationSequence.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 给出集合 [1,2,3,...,n],其所有元素共有 n! 种排列。按照排列升序返回第 k 个排列。 3 | * @Author: JunLiangWang 4 | * @Date: 2023-06-05 08:56:10 5 | * @LastEditors: JunLiangWang 6 | * @LastEditTime: 2023-06-05 10:33:24 7 | */ 8 | 9 | 10 | 11 | /** 12 | * @description: 逆康托展开 TC:O(n) SC:O(n) 13 | * @author: JunLiangWang 14 | * @param {*} n 生成 n!种排列 15 | * @param {*} k 返回第 k 个排列 16 | * @return {*} 17 | */ 18 | function inverseCantorExpansion(n, k) { 19 | /** 20 | * 本方案使用逆康托展开的方法,试想当n=3时,产生如下序列: 21 | * 22 | * 1."123" 23 | * 2."132" 24 | * 3."213" 25 | * 4."231" 26 | * 5."312" 27 | * 6."321" 28 | * 29 | * 我们可以观察发现生成的序列的第一列为1,1,2,2,3,3 30 | * 从1到3两个一组,开始我们剩余未选择的字符为1,2,3,因此 31 | * 我们可以通过(k-1)/2得到未选择的字符的索引,假设k=1或2,此 32 | * 时计算出索引为0,第一个字符得出为1,剩余未选择的字符为2,3; 33 | * 假设k=3或4,此时计算出索引为1,第一个字符得出为2,剩余未选择 34 | * 的字符为1,3。上述公式中的2我们不难发现其实是n-1的阶乘,如果我们 35 | * 一开始就把k减去1,此处公式为k/(n-1的阶乘),设其结果为R1 36 | * 37 | * 第二个字符呢?此时我们发现第二个字符的可以通过k%(n-1的阶乘)/(n-2的阶乘) 38 | * 得到未选择的字符的索引,比如k=1,计算得出索引为0,则得到未选择字符中的2, 39 | * k=3,计算得出索引为1,则得到未选择字符中的1。 40 | * 41 | * 第三个字符则以此类推公式为k%(n-1的阶乘)%(n-2的阶乘)/(n-3的阶乘) 42 | * 43 | * 这种方式则被称为逆逆康托展开,我们定义一个字符串记录剩余未被选择的字符, 44 | * 然后利用迭代模拟逆康托展开过程,不断选择/删除剩余未被选择的字符,即可 45 | * 获得答案 46 | * 47 | * 48 | */ 49 | // 记录剩余未被选择的字符,初值为[1,2,3......,n] 50 | let record = '', 51 | // 记录当前阶乘,初值为n-1的阶乘 52 | factorial = 1, 53 | // 记录输出数组 54 | outString = ''; 55 | // 遍历生成record/factorial初值 56 | for (let i = 1; i <= n; i++) { 57 | factorial *= i; 58 | record += i 59 | } 60 | // k从1开始,需要将其减1,使之从0开始,方便选择record字符 61 | k--; 62 | // 迭代模拟逆康托展开过程 63 | while (n >= 1) { 64 | // 将当前阶乘除以n,以此不断获得n-1的阶乘,n-2的阶乘.....1的阶乘 65 | factorial = factorial / n; 66 | // 通过公式k/(n-i的阶乘),i从1到n-1,计算获得索引 67 | let index = Math.floor(k / factorial); 68 | // 从剩余未被选择的字符串中选择字符并记录 69 | outString += record[index]; 70 | // 该字符已被选择,从剩余未被选择的字符串中删去 71 | record = record.replace(record[index], '') 72 | // 将k赋值为k%(n-i的阶乘),i从1到n-1,得到下一次k值 73 | k = k % factorial; 74 | n--; 75 | } 76 | // 返回结果 77 | return outString; 78 | } -------------------------------------------------------------------------------- /Sword-Pointing-Offer/909.SnakesAndLadders.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Description: 给定一个n*n的矩阵棋盘,方格按从 1 到 n^2 编号,排列顺序从左下角[n-1,0]开始,蛇形排列。 3 | 玩家从1号方格出发,每次能够移动1到6格,当遇到的格子的值不为-1,则可以跳转到[格子的值]号 4 | 格子,请问需要多少次能将棋移动到n^2号格子 5 | * @Author: JunLiangWang 6 | * @Date: 2024-01-04 21:17:01 7 | * @LastEditors: JunLiangWang 8 | * @LastEditTime: 2024-01-04 21:30:03 9 | */ 10 | 11 | 12 | /** 13 | * @description: 广度优先 TC:O(n^2) SC:O(n^2) 14 | * @author: JunLiangWang 15 | * @param {*} board 给定矩阵 16 | * @return {*} 17 | */ 18 | function bfs(board) { 19 | 20 | /** 21 | * 本题题意: 22 | * 给定一个n*n的矩阵棋盘,方格按从 1 到 n^2 编号,排列顺序从左下角[n-1,0]开始,蛇形排列。 23 | * 如下: 24 | * 7 8 9 25 | * 6 5 4 26 | * 1 2 3 27 | * 玩家从1号方格出发,每次能够移动1到6格,当遇到的格子的值不为-1,则可以跳转到[格子的值]号 28 | * 格子,请问需要多少次能将棋移动到n^2号格子 29 | * 30 | * 本题其实就是寻找最短路径,最短路径可用dfs和bfs,由于dfs需要遍历全部路径才能找出,因此 31 | * 该题使用bfs比较合适 32 | */ 33 | 34 | // 总长 35 | let length = board.length * board.length, 36 | // 记录已经走过的格子 37 | recordArray = new Array(board.length).fill(0).map(() => new Array(board.length).fill(false)) 38 | // 队列 39 | let quene = [1], 40 | // 已经移动了多少次 41 | count = 0 42 | while (quene.length) { 43 | // 该次队列长度 44 | let size = quene.length; 45 | // 移动次数+1 46 | count++; 47 | // 将该次队列所有元素出队 48 | while (size--) { 49 | // 出队 50 | let index = quene.shift(); 51 | // 可移动1到6次格子 52 | for (let i = index + 1; i <= index + 6 && i <= length; i++) { 53 | // 根据索引计算出在矩阵的几行列 54 | let r = -Math.floor((i - 1) / board.length), 55 | c = (r % 2 == 0 ? (i - 1) % board.length : board.length - 1 - (i - 1) % board.length) 56 | r += (board.length - 1) 57 | // 如果已经走过,直接跳过 58 | if (recordArray[r][c]) continue; 59 | // 把该格子标记为已经走过 60 | recordArray[r][c] = true; 61 | // 如果当前格子值不为-1,则跳到相应的格子 62 | let next = board[r][c] == -1 ? i : board[r][c] 63 | // 如果格子等于终点,直接返回次数 64 | if (next == length) return count; 65 | // 入队 66 | quene.push(next) 67 | } 68 | } 69 | } 70 | // 如果无法到达终点,返回-1 71 | return -1 72 | } --------------------------------------------------------------------------------