├── Array ├── [E]118.Pascal's Triangle.js ├── [E]121.Best Time to Buy and Sell Stock.js ├── [E]122.Best Time to Buy and Sell Stock II.js ├── [E]167.Two Sum II - Input array is sorted.js ├── [E]169.Majority Element.js ├── [E]189.Rotate Array.js ├── [E]217.Contains Duplicate.js ├── [E]219.Contains Duplicate II.js ├── [E]26.Remove Duplicates from Sorted Array.js ├── [E]268.Missing Number.js ├── [E]27.Remove Element.js ├── [E]283.Move Zeroes.js ├── [E]414.Third Maximum Number.js ├── [E]448.Find All Numbers Disappeared in an Array.js ├── [E]485.Max Consecutive Ones.js ├── [E]53.Maximum Subarray.js ├── [E]532.K-diff Pairs in an Array.js ├── [E]561.Array Partition I.js ├── [E]566.Reshape the Matrix.js ├── [E]581.Shortest Unsorted Continuous Subarray.js ├── [E]605.Can Place Flowers.js ├── [E]628.Maximum Product of Three Numbers.js ├── [E]643.Maximum Average Subarray I.js ├── [E]66.Plus One.js ├── [E]661.Image Smoother.js ├── [E]665.Non-decreasing Array.js ├── [E]674.Longest Continuous Increasing Subsequence.js ├── [E]717.1-bit and 2-bit Characters.js ├── [E]724.Find Pivot Index.js ├── [E]746.Min Cost Climbing Stairs.js ├── [E]747.Largest Number At Least Twice of Others.js ├── [E]766.Toeplitz Matrix.js ├── [E]88.Merge Sorted Array.js ├── [H]123.Best Time to Buy and Sell Stock III.js ├── [H]128.Longest Consecutive Sequence.js ├── [H]164.Maximum Gap.js ├── [H]188.Best Time to Buy and Sell Stock IV.js ├── [H]239.Sliding Window Maximum.js ├── [H]41.First Missing Positive.js ├── [H]42.Trapping Rain Water.js ├── [H]45.Jump Game II.js ├── [H]57.Insert Interval.js ├── [M]11.Container With Most Water.js ├── [M]134.Gas Station.js ├── [M]152.Maximum Product Subarray.js ├── [M]209.Minimum Size Subarray Sum.js ├── [M]228.Summary Ranges.js ├── [M]238.Product of Array Except Self.js ├── [M]274.H-Index.js ├── [M]275.H-Index II.js ├── [M]287.Find the Duplicate Number.js ├── [M]289.Game of Life.js ├── [M]299.Bulls and Cows.js ├── [M]309.Best Time to Buy and Sell Stock with Cooldown.js ├── [M]324.Wiggle Sort II.js ├── [M]334.Increasing Triplet Subsequence.js ├── [M]376.Wiggle Subsequence.js ├── [M]442.Find All Duplicates in an Array.js ├── [M]495.Teemo Attacking.js ├── [M]55.Jump Game.js ├── [M]56.Merge Intervals.js ├── [M]565.Array Nesting.js ├── [M]718.Maximum Length of Repeated Subarray.js ├── [M]75.Sort Colors.js ├── [M]769.Max Chunks To Make Sorted.js └── [M]80.Remove Duplicates from Sorted Array II.js ├── Backtracking ├── [M]216.Combination Sum III.js ├── [M]39.Combination Sum.js ├── [M]40.Combination Sum II.js ├── [M]46.Permutations.js ├── [M]47.Permutations II.js ├── [M]77.Combinations.js ├── [M]78.Subsets.js └── [M]90.Subsets II.js ├── Binary Search ├── [E]278.First Bad Version.js ├── [E]35.Search Insert Position.js ├── [H]315.Count of Smaller Nunmbers After Self.js ├── [M]162.Find Peak Element.js ├── [M]33.Search in Rotated Sorted Array.js ├── [M]34.Find First and Last Position of Element in Sorted Array.js └── [M]81.Search in Rotated Sorted Array II.js ├── Dynamic Programming ├── [E]198.House Robber.js ├── [E]70.Climbing Stairs.js ├── [M]120.Triangle.js ├── [M]139.Word Break.js ├── [M]213.House Robber II.js ├── [M]221.Maximal Square.js ├── [M]279.Perfect Squares.js ├── [M]322.Coin Change.js ├── [M]62.Unique Paths.js ├── [M]63.Unique Paths II.js ├── [M]64.Minimum Path Sum.js └── [M]91.Decode Ways.js ├── LinkedList ├── [E]141.Linked List Cycle.js ├── [E]160.Intersection of Two Linked Lists.js ├── [E]203.Remove Linked List Elements.js ├── [E]206.Reverse Linked List.js ├── [E]21.Merge Two Sorted Lists.js ├── [E]234.Palindrome Linked List.js ├── [E]237.Delete Node in a Linked List.js ├── [E]83.Remove Duplicates from Sorted List.js ├── [H]23.Merge k Sorted Lists.js ├── [H]25.Reverse Nodes in k-Group.js ├── [M]142.Linked List Cycle II.js ├── [M]143.Reorder List.js ├── [M]147.Insertion Sort List.js ├── [M]148.Sort List.js ├── [M]19.Remove Nth Node From End of List.js ├── [M]2.Add Two Numbers.js ├── [M]24.Swap Nodes in Pairs.js ├── [M]328.Odd Even Linked List.js ├── [M]61.Rotate List.js ├── [M]817.Linked List Components.js ├── [M]82.Remove Duplicates from Sorted List II.js ├── [M]86.Partition List.js └── [M]92.Reverse Linked List II.js ├── README.md ├── Stack ├── [E]155.Min Stack.js ├── [E]225.Implement Stack using Queues.js ├── [E]232.Implement Queue using Stacks.js ├── [E]682.Baseball Game.js ├── [H]224.Basic Calculator.js ├── [H]84.Largest Rectangle in Histogram.js ├── [M]150.Evaluate Reverse Polish Notation.js ├── [M]215.Kth Largest Element in an Array.js ├── [M]227.Basic Calculator II.js ├── [M]332.Reconstruct Ltinerary.js ├── [M]341.Flatten Nested list Iterator.js ├── [M]347.Top K Frequent Elements.js ├── [M]388.Longest Absolute File Path.js ├── [M]394.Decode String.js ├── [M]402.Remove K Digits.js ├── [M]503.Next Greater Element II.js ├── [M]71.Simplify Path.js └── [M]739.Daily Temperatures.js ├── String ├── [E]125.Valid Palindrome.js ├── [E]13.Roman to Integer.js ├── [E]14.Longest Common Prefix.js ├── [E]168.Excel Sheet Column Title.js ├── [E]171.Excel Sheet Column Number.js ├── [E]20.Valid Parentheses.js ├── [E]205.Isomorphic Strings.js ├── [E]242.Valid Anagram.js ├── [E]28.Implement strStr().js ├── [E]290.Word Pattern.js ├── [E]344.Reverse String.js ├── [E]345.Reverse Vowels of a String.js ├── [E]38.Count and Say.js ├── [E]383.Ransom Note.js ├── [E]387.First Unique Character in a String.js ├── [E]434.Number of Segments in a String.js ├── [E]459.Repeated Substring Pattern.js ├── [E]520.Detect Capital.js ├── [E]541.Reverse String II.js ├── [E]557.Reverse Words in a String III.js ├── [E]58.Length of Last Word.js ├── [E]606.Construct String from Binary Tree.js ├── [E]657.Robot Return to Origin.js ├── [E]67.Add Binary.js ├── [E]680.Valid Palindrome II.js ├── [E]686.Repeated String Match.js ├── [E]696.Count Binary Substrings.js ├── [E]788.Rotated Digits.js ├── [E]804.Unique Morse Code Words.js ├── [E]819.Most Common Word.js ├── [E]9.Palindrome Number.js ├── [H]115.Distinct Subsequences.js ├── [H]132.Palindrome Partitioning II.js ├── [H]30.Substring with Concatenation of All Words.js ├── [H]301.Remove Invaild Parentheses.js ├── [H]316.Remove Duplicate Letters.js ├── [H]32.Longest Valid Parentheses.js ├── [H]336.Palindrome Pairs.js ├── [H]76.Minimum Window Substring.js ├── [M]12.Integer to Roman.js ├── [M]131.Palindrome Partitioning.js ├── [M]151.Reverse Words in a String.js ├── [M]17.Letter Combinations of a Phone Number.js ├── [M]22.Generate parentheses.js ├── [M]241.Different Ways to Add Parentheses.js ├── [M]3.Longest Substring Without Repeating Characters.js ├── [M]392.Is Subsequence.js ├── [M]395. Longest Substring with At Least K Repeating Characters.js ├── [M]49.Group Anagrams.js ├── [M]5.Longest Palindromic Substring.js ├── [M]537.Complex Number Multiplication.js ├── [M]647.Palindromic Substrings.js └── [M]791.Custom Sort String.js └── Tree ├── [E]100.Same Tree.js ├── [E]101.Symmetric Tree.js ├── [E]104.Maximum Depth of Binary Tree.js ├── [E]107.Binary Tree Level Order Traversal II.js ├── [E]108.Convert Sorted Array to Binary Search Tree.js ├── [E]110.Balanced Binary Tree.js ├── [E]111.Minimum Depth of Binary Tree.js ├── [E]112.Path Sum.js ├── [E]226.Invert Binary Tree.js ├── [E]235.Lowest Common Ancestor of a Binary Search Tree.js ├── [E]257.Binary Tree Paths.js ├── [E]404.Sum of Left Leaves.js ├── [E]437.Path Sum III.js ├── [E]501.Find Mode In Binary Search Tree.js ├── [E]538.Convert BST to Greater Tree.js ├── [E]543.Diameter of Binary Tree.js ├── [E]563.Binary Tree Tilt.js ├── [E]572.Subtree of Another Tree.js ├── [E]617.Merge Two Binary Trees.js ├── [E]637.Average of Levels in Binary Tree.js ├── [E]653.Two Sum IV - Input is a BST.js ├── [E]669.Trim a Binary Search Tree.js ├── [E]671.Second Minimum Node In a Binary Tree.js ├── [E]687.Longest Univalue Path.js ├── [H]124.Binary Tree Maximum Path Sum.js ├── [H]145.Binary Tree Postorder Traversal.js ├── [H]297.Serialize and Deserialize Binary Tree.js ├── [H]99.Recover Binary Search Tree.js ├── [M]102.Binary Tree Level Order Traversal.js ├── [M]103.Binary Tree Zigzag Level Order Traversal.js ├── [M]109.Convert Sorted List to Binary Search Tree.js ├── [M]113.Path Sum II.js ├── [M]116.Populating Next Right Pointers in Each Node.js ├── [M]129.Sum Root to Leaf Numbers.js ├── [M]144.Binary Tree Preorder Traversal.js ├── [M]173.Binary Search Tree Iterator.js ├── [M]199.Binary Tree Right Side View.js ├── [M]230.Kth Smallest Element in a BST.js ├── [M]236.Lowest Common Ancestor of a Binary Tree.js ├── [M]337.House Robber III.js ├── [M]94.Binary Tree Inorder Traversal.js ├── [M]96.Unique Binary Search Trees.js └── [M]98.Validate Binary Search Tree.js /Array/[E]118.Pascal's Triangle.js: -------------------------------------------------------------------------------- 1 | var generate = function(numRows) { 2 | if (numRows <= 0) return []; 3 | let res = Array.from({length: numRows}).map(item => []); 4 | for (let i = 0; i < numRows; i++) { 5 | res[i][0] = 1; 6 | if (i === 0) continue; 7 | for (let j = 1; j < i; j++) { 8 | res[i][j] = res[i - 1][j] + res[i - 1][j - 1]; 9 | } 10 | res[i].push(1); 11 | } 12 | return res; 13 | }; 14 | 15 | /** 16 | * 按题意翻译为代码即可,注意边界处理 17 | */ -------------------------------------------------------------------------------- /Array/[E]121.Best Time to Buy and Sell Stock.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} prices 3 | * @return {number} 4 | */ 5 | var maxProfit = function(prices) { 6 | let min = Number.MAX_VALUE; 7 | let res = 0; 8 | for (let i = 0; i < prices.length; i++) { 9 | min = Math.min(min, prices[i]); 10 | res = Math.max(prices[i] - min, res); 11 | } 12 | return res; 13 | }; 14 | 15 | /** 16 | * 用一个变量记录遍历过程中的最小值并与当前值求差,再用一个变量记录这个差的最大值即可 17 | */ -------------------------------------------------------------------------------- /Array/[E]122.Best Time to Buy and Sell Stock II.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} prices 3 | * @return {number} 4 | */ 5 | var maxProfit = function(prices) { 6 | let res = 0; 7 | for (let i = 1; i < prices.length; i++) { 8 | if (prices[i] - prices[i - 1] > 0) { 9 | res += prices[i] - prices[i - 1]; 10 | } 11 | } 12 | return res; 13 | }; 14 | 15 | /** 16 | * 121的延伸,在其基础上可以买卖多次,因此只要后一天卖出比前一天买进高就可以算进利润。 17 | */ -------------------------------------------------------------------------------- /Array/[E]167.Two Sum II - Input array is sorted.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} numbers 3 | * @param {number} target 4 | * @return {number[]} 5 | */ 6 | var twoSum = function(numbers, target) { 7 | let l = 0, r = numbers.length - 1; 8 | while (l < r) { 9 | let sum = numbers[l] + numbers[r]; 10 | if (sum == target) return [l + 1, r + 1]; 11 | else if (sum < target) ++l; 12 | else --r; 13 | } 14 | return []; 15 | }; 16 | 17 | /** 18 | * 经典的开山题two sum,头尾指针解决 19 | */ -------------------------------------------------------------------------------- /Array/[E]169.Majority Element.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} nums 3 | * @return {number} 4 | */ 5 | var majorityElement = function(nums) { 6 | let obj = {}; 7 | for (let i = 0; i < nums.length; i++) { 8 | obj[nums[i]] = obj[nums[i]] ? ++obj[nums[i]] : 1; 9 | } 10 | for (let item of Object.keys(obj)) { 11 | if (obj[item] >= nums.length / 2) { 12 | return Number(item) 13 | } 14 | } 15 | }; 16 | 17 | /** 18 | * 常规的map计数解决 19 | */ -------------------------------------------------------------------------------- /Array/[E]189.Rotate Array.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} nums 3 | * @param {number} k 4 | * @return {void} Do not return anything, modify nums in-place instead. 5 | */ 6 | var rotate = function(nums, k) { 7 | k = k % nums.length; 8 | for (let i = 0; i < k; i++) { 9 | nums.unshift(nums.pop()); 10 | } 11 | }; 12 | 13 | /** 14 | * 基础题,注意旋转次数超过数组长度时取余即可 15 | */ -------------------------------------------------------------------------------- /Array/[E]217.Contains Duplicate.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} nums 3 | * @return {boolean} 4 | */ 5 | var containsDuplicate = function(nums) { 6 | let map = {}; 7 | for (let i = 0; i < nums.length; i++) { 8 | if (map[nums[i]]) return true; 9 | map[nums[i]] = true; 10 | } 11 | return false; 12 | }; 13 | 14 | /** 15 | * hashmap记录是否出现即可 16 | */ -------------------------------------------------------------------------------- /Array/[E]219.Contains Duplicate II.js: -------------------------------------------------------------------------------- 1 | var containsNearbyDuplicate = function(nums, k) { 2 | let map = new Map(); 3 | for (let i = 0; i < nums.length; i++) { 4 | if (map.has(nums[i]) && Math.abs(map.get(nums[i]) - i) <= k) { 5 | return true; 6 | } else { 7 | map.set(nums[i], i); 8 | } 9 | } 10 | return false; 11 | }; 12 | 13 | /** 14 | * 求给定距离内是否有重复值,遍历时用map记录值和对应index,遇到重复值时判断index距离并更新index 15 | */ -------------------------------------------------------------------------------- /Array/[E]26.Remove Duplicates from Sorted Array.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} nums 3 | * @return {number} 4 | */ 5 | var removeDuplicates = function(nums) { 6 | let cur = 0; 7 | let pre = 0; 8 | while (cur < nums.length) { 9 | if (nums[cur] === nums[pre]) { 10 | cur++; 11 | } else { 12 | nums[++pre] = nums[cur++]; 13 | } 14 | } 15 | return pre + 1; 16 | }; 17 | 18 | /** 19 | * 两个指针,一个用来向前遍历,一个用来记录已替换非重复元素的位置 20 | */ -------------------------------------------------------------------------------- /Array/[E]268.Missing Number.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} nums 3 | * @return {number} 4 | */ 5 | var missingNumber = function(nums) { 6 | let sum = 0, n = nums.length; 7 | for (let a of nums) { 8 | sum += a; 9 | } 10 | return n * (n + 1) / 2 - sum; 11 | }; 12 | 13 | /** 14 | * 等差数列求和减去sum即可得丢失的数 15 | */ -------------------------------------------------------------------------------- /Array/[E]27.Remove Element.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * @param {number[]} nums 4 | * @param {number} val 5 | * @return {number} 6 | */ 7 | var removeElement = function(nums, val) { 8 | let res = 0; 9 | for (let i = 0; i < nums.length; i++) { 10 | if (nums[i] !== val) { 11 | nums[res++] = nums[i]; 12 | } 13 | } 14 | return res; 15 | }; 16 | 17 | /** 18 | * 基础数组题,遍历计数即可。 19 | */ -------------------------------------------------------------------------------- /Array/[E]283.Move Zeroes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} nums 3 | * @return {void} Do not return anything, modify nums in-place instead. 4 | */ 5 | var moveZeroes = function(nums) { 6 | let point = 0; 7 | for (let i = 0; i < nums.length; i++) { 8 | if (nums[i]) { 9 | swap(nums, i, point++); 10 | } 11 | } 12 | }; 13 | 14 | function swap(arr, i, j) { 15 | let temp = arr[i]; 16 | arr[i] = arr[j]; 17 | arr[j] = temp; 18 | } 19 | 20 | /** 21 | * 简单的排序题,用个指针记录非零位置遍历交换即可。 22 | */ -------------------------------------------------------------------------------- /Array/[E]414.Third Maximum Number.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} nums 3 | * @return {number} 4 | */ 5 | var thirdMax = function(nums) { 6 | let first = Number.MIN_SAFE_INTEGER; 7 | let second = Number.MIN_SAFE_INTEGER; 8 | let third = Number.MIN_SAFE_INTEGER; 9 | for (let i = 0; i < nums.length; i++) { 10 | if (nums[i] > third) { 11 | first = second; 12 | second = third; 13 | third = nums[i]; 14 | } else if (nums[i] > second && nums[i] < third) { 15 | first = second; 16 | second = nums[i]; 17 | } else if (nums[i] > first && nums[i] < second) { 18 | first = nums[i]; 19 | } 20 | } 21 | return first === Number.MIN_SAFE_INTEGER ? third : first; 22 | }; 23 | 24 | 25 | /** 26 | * 求第三大的数,那么用三个变量分别保存三个数,遍历比较并按条件错位赋值 27 | */ -------------------------------------------------------------------------------- /Array/[E]448.Find All Numbers Disappeared in an Array.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} nums 3 | * @return {number[]} 4 | */ 5 | var findDisappearedNumbers = function(nums) { 6 | let res = []; 7 | for (let i = 0; i < nums.length; i++) { 8 | let idx = Math.abs(nums[i]) - 1; 9 | nums[idx] = (nums[idx] > 0) ? -nums[idx] : nums[idx]; 10 | } 11 | for (let i = 0; i < nums.length; ++i) { 12 | if (nums[i] > 0) { 13 | res.push(i + 1); 14 | } 15 | } 16 | return res; 17 | }; 18 | 19 | /** 20 | * 看到数组元素限制1到n的题并且对时间空间复杂度有要求的,要立刻想到三种经典思路 21 | * 1.正负标记 22 | * 2.置换到对应下标标记 23 | * 3.对应下标位置元素累加长度标记 24 | * 这里选择1解决 25 | */ 26 | -------------------------------------------------------------------------------- /Array/[E]485.Max Consecutive Ones.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} nums 3 | * @return {number} 4 | */ 5 | var findMaxConsecutiveOnes = function(nums) { 6 | return Math.max(...nums.join("").split('0').map(item => item.length)) 7 | }; 8 | 9 | /** 10 | * 以0分割比较长度即可,内置方法解决 11 | */ -------------------------------------------------------------------------------- /Array/[E]53.Maximum Subarray.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} nums 3 | * @return {number} 4 | */ 5 | var maxSubArray = function(nums) { 6 | let res = Number.MIN_SAFE_INTEGER; 7 | let curSum = Number.MIN_SAFE_INTEGER; 8 | for (let i = 0; i < nums.length; i++) { 9 | curSum = Math.max(nums[i], nums[i] + curSum); 10 | res = Math.max(res, curSum); 11 | } 12 | return res; 13 | }; 14 | 15 | /** 16 | * 简单题,思路也很简单,可以看做是动态规划,dp[i]表示以i为结尾的总和最大子数组, 17 | * 那么dp[i + 1] = max(dp[i], dp[i] + nums[i]),节省空间用变量保存dp[i]即可 18 | */ -------------------------------------------------------------------------------- /Array/[E]532.K-diff Pairs in an Array.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} nums 3 | * @param {number} k 4 | * @return {number} 5 | */ 6 | var findPairs = function(nums, k) { 7 | let res = 0; 8 | let j = 0; 9 | nums.sort((a, b) => a - b); 10 | for (let i = 0; i < nums.length; i++) { 11 | j = Math.max(j, i + 1); 12 | if (j >= nums.length || nums[i] === nums[i - 1]) continue; 13 | while(nums[j] - nums[i] < k) { 14 | j++; 15 | } 16 | if (nums[j] - nums[i] === k) { 17 | res++; 18 | } 19 | } 20 | return res; 21 | }; 22 | 23 | /** 24 | * 先排序,再用双指针,第一个指针i记录当前元素,第二个指针往前移并将所指元素和当前元素求差,小于k则继续 25 | * 前移,等于k则结果加1。 26 | */ -------------------------------------------------------------------------------- /Array/[E]561.Array Partition I.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} nums 3 | * @return {number} 4 | */ 5 | var arrayPairSum = function(nums) { 6 | nums.sort((a, b) => a - b); 7 | return nums.reduce((a, b, index) => {return index % 2 === 0 ? a + b : a}, 0) 8 | }; 9 | 10 | /** 11 | * 要最大化每两个值中最小值之和,应该让两个值尽可能相近,排序取奇数位即可 12 | */ -------------------------------------------------------------------------------- /Array/[E]566.Reshape the Matrix.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[][]} nums 3 | * @param {number} r 4 | * @param {number} c 5 | * @return {number[][]} 6 | */ 7 | var matrixReshape = function(nums, r, c) { 8 | let flat = []; 9 | nums.forEach(arr => { 10 | flat = flat.concat(arr); 11 | }); 12 | let total = nums.length * nums[0].length; 13 | let newArr = []; 14 | if (r * c !== total) return nums; 15 | for (let i = 0; i < r; i++) { 16 | let arr = []; 17 | for (let j = 0; j < c; j++) { 18 | arr.push(flat.shift()); 19 | } 20 | newArr.push(arr); 21 | } 22 | return newArr; 23 | }; 24 | 25 | /** 26 | * 重塑矩阵。先打平再按照重塑的行列数遍历分配 27 | */ -------------------------------------------------------------------------------- /Array/[E]581.Shortest Unsorted Continuous Subarray.js: -------------------------------------------------------------------------------- 1 | var findUnsortedSubarray = function(nums) { 2 | let newArr = Array.from(nums).sort((a, b) => a - b); 3 | let min = 0; 4 | let max = 0; 5 | for (let i = 0; i < nums.length; i++) { 6 | if (nums[i] !== newArr[i]) { 7 | min = i; 8 | break; 9 | } 10 | } 11 | for (let i = nums.length - 1; i >= 0; i--) { 12 | if (nums[i] !== newArr[i]) { 13 | max = i; 14 | break; 15 | } 16 | } 17 | 18 | return max - min ? max - min + 1 : 0; 19 | }; 20 | 21 | /** 22 | * 排好序后和原数组分别从首尾比较,遇到不同值就是要被排序的最小范围 23 | */ -------------------------------------------------------------------------------- /Array/[E]605.Can Place Flowers.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} flowerbed 3 | * @param {number} n 4 | * @return {boolean} 5 | */ 6 | var canPlaceFlowers = function(flowerbed, n) { 7 | if (!n) return true; 8 | for (let i = 0; i < flowerbed.length; i++) { 9 | if (!flowerbed[i - 1] && !flowerbed[i] && !flowerbed[i + 1]) { 10 | flowerbed[i] = 1; 11 | n--; 12 | } 13 | if (!n) return true; 14 | } 15 | return false; 16 | }; 17 | 18 | /** 19 | * 种花不能相邻,那么遍历时对0的左右同时判断就可以了 20 | */ -------------------------------------------------------------------------------- /Array/[E]628.Maximum Product of Three Numbers.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} nums 3 | * @return {number} 4 | */ 5 | var maximumProduct = function(nums) { 6 | let arr = nums.sort((a, b) => a - b); 7 | let end = arr.length - 1; 8 | return Math.max(arr[0] * arr[1] * arr[2], arr[0] * arr[1] * arr[end], arr[0] * arr[end - 1] * arr[end - 2], arr[end] * arr[end - 1] * arr[end - 2]) 9 | }; 10 | 11 | 12 | /** 13 | * 求其中三个元素的最大乘积,需要考虑正负,那么排序后取头尾元素分情况讨论 14 | */ -------------------------------------------------------------------------------- /Array/[E]643.Maximum Average Subarray I.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} nums 3 | * @param {number} k 4 | * @return {number} 5 | */ 6 | var findMaxAverage = function(nums, k) { 7 | let max = 0; 8 | let cnt = 0; 9 | for (let i = 0; i < nums.length; i++) { 10 | if (i < k) { 11 | cnt += nums[i]; 12 | max = cnt / (i + 1); 13 | } else { 14 | cnt = cnt + nums[i] - nums[i - k]; 15 | max = Math.max(cnt/k, max); 16 | } 17 | } 18 | return max; 19 | }; 20 | 21 | /** 22 | * 求连续子数组的最大平均值,遍历计数并计算,当达到指定数量时,减去头部值再计算 23 | */ -------------------------------------------------------------------------------- /Array/[E]66.Plus One.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} digits 3 | * @return {number[]} 4 | */ 5 | var plusOne = function(digits) { 6 | let i = digits.length - 1; 7 | while (digits[i] + 1 > 9) { 8 | digits[i] = 0; 9 | i--; 10 | } 11 | if (i < 0) { 12 | digits.unshift(1); 13 | } else { 14 | digits[i] += 1; 15 | } 16 | return digits; 17 | }; 18 | 19 | /** 20 | * 数组的形式去加1,注意处理进位即可 21 | */ -------------------------------------------------------------------------------- /Array/[E]661.Image Smoother.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[][]} M 3 | * @return {number[][]} 4 | */ 5 | function isValid(x, y, M) { 6 | return x >= 0 && y >= 0 && x < M.length && y < M[0].length 7 | } 8 | 9 | function getAver(M, i, j) { 10 | let sum = 0; 11 | let count = 0; 12 | for (let val1 of [1, 0, -1]) { 13 | for (let val2 of [1, 0, -1]) { 14 | if (isValid(i + val1, j + val2, M)) { 15 | count++; 16 | sum += M[i + val1][j + val2]; 17 | } 18 | } 19 | } 20 | return Math.floor(sum / count); 21 | } 22 | 23 | var imageSmoother = function(M) { 24 | let res = Array.from({length: M.length}).map(item => []); 25 | for (let i = 0; i < M.length; i++) { 26 | for (let j = 0; j < M[0].length; j++) { 27 | res[i][j] = getAver(M, i, j); 28 | } 29 | } 30 | return res; 31 | }; 32 | 33 | /** 34 | * 图片光滑算法,遍历每个像素点,用题干给定逻辑求解 35 | */ 36 | -------------------------------------------------------------------------------- /Array/[E]665.Non-decreasing Array.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} nums 3 | * @return {boolean} 4 | */ 5 | var checkPossibility = function(nums) { 6 | let flag = false; 7 | for (let i = 1; i < nums.length; i++) { 8 | if (nums[i] - nums[i - 1] < 0) { 9 | if (!flag) { 10 | if (i == 1 || nums[i] >= nums[i - 2]) { 11 | nums[i - 1] = nums[i]; 12 | } else { 13 | nums[i] = nums[i - 1]; 14 | } 15 | flag = true; 16 | } else { 17 | return false; 18 | } 19 | } 20 | } 21 | return true; 22 | }; 23 | 24 | /** 25 | * 允许修改一次的非增数组检测,那么用一个标志位标记并遍历检测,遇到不符合的位置时 26 | * 修改相应位置的值使条件成立并让当前位置的值尽可能小 27 | */ -------------------------------------------------------------------------------- /Array/[E]674.Longest Continuous Increasing Subsequence.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} nums 3 | * @return {number} 4 | */ 5 | var findLengthOfLCIS = function(nums) { 6 | let res = 0; 7 | let tep = 0; 8 | nums.reduce((a, b) => { 9 | if (b > a) { 10 | tep++; 11 | res = Math.max(tep, res); 12 | } else { 13 | tep = 1; 14 | } 15 | return b; 16 | }, 0); 17 | return res; 18 | }; 19 | 20 | /** 21 | * 求最长的连续子序列,每次和前一个元素判断大小即可,不递增时重新计数 22 | */ -------------------------------------------------------------------------------- /Array/[E]717.1-bit and 2-bit Characters.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} bits 3 | * @return {boolean} 4 | */ 5 | var isOneBitCharacter = function(bits) { 6 | let i = 0; 7 | while(i < bits.length - 1) { 8 | if (bits[i] === 1) { 9 | i += 2; 10 | } else { 11 | i++; 12 | } 13 | } 14 | return i === bits.length - 1 && bits[i] === 0 15 | }; 16 | 17 | /** 18 | * 这题的关键在于遍历时遇到1需要计数两位,注意结束遍历的条件 19 | */ -------------------------------------------------------------------------------- /Array/[E]724.Find Pivot Index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} nums 3 | * @return {number} 4 | */ 5 | var pivotIndex = function(nums) { 6 | let sum = nums.reduce((a, b) => a + b, 0); 7 | let total = 0; 8 | for (let [index, val] of nums.entries()) { 9 | if (sum - val === 2 * total) return index; 10 | total += val; 11 | } 12 | return -1; 13 | }; 14 | 15 | /** 16 | * 先求总和,再遍历依次累加,用总和减去每个元素和当前累加值比较 17 | */ -------------------------------------------------------------------------------- /Array/[E]746.Min Cost Climbing Stairs.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} cost 3 | * @return {number} 4 | */ 5 | var minCostClimbingStairs = function(cost) { 6 | let dp = []; 7 | dp[0] = 0; 8 | dp[1] = 0; 9 | for (let i = 2; i < cost.length + 1; i++) { 10 | dp[i] = Math.min(dp[i - 2] + cost[i - 2], dp[i - 1] + cost[i - 1]); 11 | } 12 | return dp[cost.length]; 13 | }; 14 | 15 | /** 16 | * 动态规划,dp[i]表示到i位置的最小花费,由递推式得结果 17 | */ -------------------------------------------------------------------------------- /Array/[E]747.Largest Number At Least Twice of Others.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} nums 3 | * @return {number} 4 | */ 5 | var dominantIndex = function(nums) { 6 | let max = Math.max(...nums); 7 | return nums.filter(item => item !== max).find(item => item * 2 > max) ? -1 : nums.findIndex(item => item === max); 8 | }; 9 | 10 | /** 11 | * 如果最大数大于其他数的两倍就找出这个数。数组内置方法解决 12 | */ -------------------------------------------------------------------------------- /Array/[E]766.Toeplitz Matrix.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[][]} matrix 3 | * @return {boolean} 4 | */ 5 | var isToeplitzMatrix = function(matrix) { 6 | for (let i = 0; i < matrix.length - 1; i++) { 7 | for (let j = 0; j < matrix[i].length - 1; j++) { 8 | if (matrix[i][j] != matrix[i + 1][j + 1]) return false; 9 | } 10 | } 11 | return true; 12 | }; 13 | 14 | /** 15 | * 遍历矩阵和其右下方的值比较是否相等即可 16 | */ 17 | 18 | -------------------------------------------------------------------------------- /Array/[E]88.Merge Sorted Array.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} nums1 3 | * @param {number} m 4 | * @param {number[]} nums2 5 | * @param {number} n 6 | * @return {void} Do not return anything, modify nums1 in-place instead. 7 | */ 8 | var merge = function(nums1, m, nums2, n) { 9 | let count = m + n - 1; 10 | m--; 11 | n--; 12 | while (m >=0 && n >= 0) { 13 | nums1[count--] = nums1[m] > nums2[n] ? nums1[m--] : nums2[n--]; 14 | } 15 | while (n >= 0) nums1[count--] = nums2[n--]; 16 | }; 17 | 18 | /** 19 | * 由于合并数组可以确定长度等于num1,那么可以从num1末尾向前填充,两个指针分别指示两个数组的当前比较位按大小依次填入即可 20 | */ -------------------------------------------------------------------------------- /Array/[H]123.Best Time to Buy and Sell Stock III.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} prices 3 | * @return {number} 4 | */ 5 | var maxProfit = function(prices) { 6 | if (prices.length < 2) return 0; 7 | let n = prices.length; 8 | let dp1 = Array.from({length: n}, _ => 0); 9 | let dp2 = Array.from(dp1); 10 | for (let i = 1, min = prices[0]; i < n; i++) { 11 | min = Math.min(min, prices[i]); 12 | dp1[i] = Math.max(dp1[i - 1], prices[i] - min); 13 | } 14 | for (let i = n - 2, max = prices[n - 1]; i >= 0; i--) { 15 | max = Math.max(max, prices[i]); 16 | dp2[i] = Math.max(dp2[i], max - prices[i]); 17 | } 18 | let res = 0; 19 | for (let i = 0; i < n; i++) { 20 | res = Math.max(res, dp1[i] + dp2[i]); 21 | } 22 | return res; 23 | }; 24 | 25 | /** 26 | * 121题基础上又一变种,多增加了一次交易次数且同时只能持有一股。 27 | * 既然如此把这题拆解成两个121题即可,遍历取交易拆分点,用两个动态规划数组分别 28 | * 保存拆分后每次交易的最大值,最后相加取max即可。 29 | */ -------------------------------------------------------------------------------- /Array/[H]128.Longest Consecutive Sequence.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} nums 3 | * @return {number} 4 | */ 5 | var longestConsecutive = function(nums) { 6 | let res = 0; 7 | let m = {}; 8 | for (let num of nums) { 9 | if (!m[num]) { 10 | let left = m[num - 1] || 0; 11 | let right = m[num + 1] || 0; 12 | let sum = left + right + 1; 13 | m[num] = sum; 14 | res = Math.max(res, sum); 15 | m[num - left] = sum; 16 | m[num + right] = sum; 17 | } 18 | } 19 | return res; 20 | }; 21 | 22 | /** 23 | * 求最长的连续子序列。同样是线性复杂度内的问题,先考虑开辟额外空间。这里用hashmap记录每个元素和 24 | * 其连续长度,在遍历过程中动态更新并取最大值res。遍历完成后返回res即可。 25 | */ -------------------------------------------------------------------------------- /Array/[H]164.Maximum Gap.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} nums 3 | * @return {number} 4 | */ 5 | var maximumGap = function(nums) { 6 | if (!nums.length) return 0; 7 | let mx = Math.max(...nums); 8 | let mn = Math.min(...nums); 9 | let size = Math.floor((mx - mn) / nums.length + 1); 10 | let bucketNums = Math.floor((mx - mn) / size) + 1; 11 | let bucketMin = Array.from({length: bucketNums}, _ => Number.MAX_SAFE_INTEGER); 12 | let bucketMax = Array.from(bucketMin, _ => Number.MIN_SAFE_INTEGER); 13 | let log = new Set(); 14 | for (let num of nums) { 15 | let idx = Math.floor((num - mn) / size); 16 | bucketMin[idx] = Math.min(bucketMin[idx], num); 17 | bucketMax[idx] = Math.max(bucketMax[idx], num); 18 | log.add(idx); 19 | } 20 | let pre = 0; 21 | let res = 0; 22 | for (let i = 1; i < bucketNums; i++) { 23 | if (!log.has(i)) continue; 24 | res = Math.max(res, bucketMin[i] - bucketMax[pre]); 25 | pre = i; 26 | } 27 | return res; 28 | }; 29 | 30 | /** 31 | * 线性复杂度内求未排序数组排序后的相邻元素最大间隔,最容易想到的思路还是排序后计算,线性复杂度排序有桶排序和基数排序, 32 | * 这里用桶排序。考虑一下如何分桶,最好的结果就是最大相邻元素不在同一桶内,就可以遍历桶然后取桶中极值计算。顺着这个思路, 33 | * 分桶就可以用nums中的最大值和最小值取差再除以nums长度加1保证上述条件,再在每个桶内不用排序,只需维护极值即可,同时用Set 34 | * 记录有元素的桶。最后遍历有元素的桶计算相邻桶的最小值与最大值之差取最大值即可。 35 | */ -------------------------------------------------------------------------------- /Array/[H]188.Best Time to Buy and Sell Stock IV.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number} k 3 | * @param {number[]} prices 4 | * @return {number} 5 | */ 6 | var maxProfit = function(k, prices) { 7 | let n = prices.length; 8 | if (!n) return 0; 9 | if (k >= n) return helper(prices); 10 | let dpl = Array.from({length: n},_ => Array.from({length: k + 1}).fill(0)); 11 | let dpg = Array.from({length: n},_ => Array.from({length: k + 1}).fill(0)); 12 | for (let i = 1; i < n; i++) { 13 | let diff = prices[i] - prices[i - 1]; 14 | for (let j = 1; j <= k; j++) { 15 | dpl[i][j] = Math.max(dpg[i - 1][j - 1] + Math.max(diff, 0), dpl[i - 1][j] + diff); 16 | dpg[i][j] = Math.max(dpg[i - 1][j], dpl[i][j]); 17 | } 18 | } 19 | return dpg[n - 1][k]; 20 | }; 21 | 22 | function helper(prices) { 23 | let res = 0; 24 | for (let i = 1; i < prices.length; i++) { 25 | if (prices[i] - prices[i - 1] > 0) { 26 | res += prices[i] - prices[i - 1]; 27 | } 28 | } 29 | return res; 30 | } 31 | 32 | /** 33 | * 123题基础上的通解算法,最多2次交易变成k次交易,就不能用123的取巧解法了。初始化两个动态规划二维数组,dpl[i][j] 34 | * 表示第i天进行了第j次交易的最大盈利,dpg[i][j]表示第i天最多j次交易的最大盈利。由此可得两个状态转移方程求解。 35 | * 当k大于数组长度时算法可以转化为122题解。 36 | */ -------------------------------------------------------------------------------- /Array/[H]239.Sliding Window Maximum.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} nums 3 | * @param {number} k 4 | * @return {number[]} 5 | */ 6 | var maxSlidingWindow = function(nums, k) { 7 | let res = []; 8 | let q = []; 9 | for (let i = 0; i < nums.length; i++) { 10 | if (q.length && q[0] === i - k) q.shift(); 11 | while (q.length && nums[q[q.length - 1]] < nums[i]) q.pop(); 12 | q.push(i); 13 | if (i >= k - 1) res.push(nums[q[0]]); 14 | } 15 | return res; 16 | }; 17 | 18 | /** 19 | * 滑动窗口,这题关键在于维护一个递减的双向队列,首位元素表示窗口内的最大idx,同时保持注意最大数量内 20 | * 队列的出入即可。 21 | */ -------------------------------------------------------------------------------- /Array/[H]41.First Missing Positive.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} nums 3 | * @return {number} 4 | */ 5 | 6 | var firstMissingPositive = function(nums) { 7 | for (let i = 0; i < nums.length; i++) { 8 | while (nums[i] > 0 && nums[i] <= nums.length && nums[nums[i] - 1] !== nums[i]) { 9 | swap(nums, i, nums[i] - 1) 10 | } 11 | } 12 | for (let i = 0; i < nums.length; i++) { 13 | if (nums[i] !== i + 1) return i + 1; 14 | } 15 | return nums.length + 1; 16 | }; 17 | 18 | function swap (nums, a, b) { 19 | let temp = nums[a]; 20 | nums[a] = nums[b]; 21 | nums[b] = temp; 22 | } 23 | 24 | /** 25 | * 这一题要求在O(n)时间和O(1)空间内完成,数组类型不能开辟额外空间的题看到应该很快想到如下三种思路 26 | * 1.数组下标标记法 27 | * 将nums[i]置换到相等的下标位置上去,利用下标记录更多的信息 28 | * 29 | * 2.正负号标记法 30 | * 除了利用下标信息之外,还可以利用正负号信息, 将nums[i]对应nums[nums[i] - 1]上的数的正负做文章也可以记录更多信息 31 | * 32 | * 3.数组长度累加法 33 | * 下标和正负之外,可以通过该位置的数值本身记录信息,将nums[i]对应的nums[nums[i] - 1]上累加数组长度记录更多信息 34 | * 35 | * 以上3种方法根据数组题的具体要求做适当选择即可,这一题用的是1解法,不存在的第一个正数就是下标和值不对应的第一个下标,都对应上了则是数组长度加1。 36 | */ 37 | 38 | -------------------------------------------------------------------------------- /Array/[H]42.Trapping Rain Water.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} height 3 | * @return {number} 4 | */ 5 | var trap = function(height) { 6 | let n = height.length - 1; 7 | let left = 0, right = n, level = 0, res = 0; 8 | while (left < right) { 9 | let lower = height[height[left] < height[right] ? left++ : right--]; 10 | level = Math.max(level, lower); 11 | res += level - lower; 12 | } 13 | return res; 14 | }; 15 | 16 | /** 17 | * 这一题有点像11题,同样是求水容量,依然需要维护两个指针,所处高度较小的指针往内移动, 18 | * 如果移动后idx所处高度低于移动前,则高度差代表移动后idx能存储的水量,如果移动后idx不低于 19 | * 移动前,则此处可以作为新边缘继续往下推。最终重合时算得总存储量。 20 | */ -------------------------------------------------------------------------------- /Array/[H]45.Jump Game II.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} nums 3 | * @return {number} 4 | */ 5 | var jump = function(nums) { 6 | let dp = Array.from({length: nums.length}, _ => Number.MAX_SAFE_INTEGER); 7 | dp[0] = 0; 8 | for (let i = 1; i < nums.length; i++) { 9 | for (let j = 0; j <= i; j++) { 10 | if (j + nums[j] >= i) { 11 | dp[i] = dp[j] + 1; 12 | break; 13 | } 14 | } 15 | } 16 | return dp[nums.length - 1]; 17 | }; 18 | 19 | /** 20 | * 55题的延伸,在算出是否能跳到末尾的基础上还要算出跳到末尾的最小次数,因为有多种方法跳到末尾, 21 | * 比较容易想到的办法是用动态规划dp[i]表示跳到i的最小次数。因为dp[i]肯定是递增数组,所以当 22 | * dp[j](j <== i)能到达dp[i]时就能获取最小的dp[i],可以break跳出当前循环的后续检测。 23 | */ 24 | 25 | var jump = function(nums) { 26 | let res = 0; 27 | let last = 0; 28 | let cur = 0; 29 | for (let i = 0; i < nums.length - 1; i++) { 30 | cur = Math.max(cur, i + nums[i]); 31 | if (i === last) { 32 | last = cur; 33 | res++; 34 | if (cur >= nums.length - 1) break; 35 | } 36 | } 37 | return res; 38 | }; 39 | 40 | /** 41 | * 还有一种网上大神的解法是用两个指针动态更新当前跳能达到的最远位置cur和当前跳之后能达到的最远位置last, 42 | * 这种方法有点像贪心,不过考虑了更深的一层。 43 | */ 44 | 45 | 46 | -------------------------------------------------------------------------------- /Array/[H]57.Insert Interval.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for an interval. 3 | * function Interval(start, end) { 4 | * this.start = start; 5 | * this.end = end; 6 | * } 7 | */ 8 | /** 9 | * @param {Interval[]} intervals 10 | * @param {Interval} newInterval 11 | * @return {Interval[]} 12 | */ 13 | var insert = function(intervals, newInterval) { 14 | let res = []; 15 | let n = intervals.length; 16 | let cur = 0; 17 | while (cur < n && intervals[cur].end < newInterval.start) { 18 | res.push(intervals[cur++]); 19 | } 20 | while (cur < n && intervals[cur].start <= newInterval.end) { 21 | newInterval.start = Math.min(newInterval.start, intervals[cur].start); 22 | newInterval.end = Math.max(newInterval.end, intervals[cur].end); 23 | cur++; 24 | } 25 | res.push(newInterval); 26 | while (cur < n) { 27 | res.push(intervals[cur++]); 28 | } 29 | return res; 30 | }; 31 | 32 | /** 33 | * 这题思路比较常规,用一个指针遍历数组,对每种间隔分情况讨论即可,非重叠元素直接推入res,重叠元素算好新间隔再推入res, 34 | * 最后把剩下的原数组非重叠部分推入res返回 35 | */ -------------------------------------------------------------------------------- /Array/[M]11.Container With Most Water.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} height 3 | * @return {number} 4 | */ 5 | var maxArea = function(height) { 6 | let res = 0; 7 | let n = height.length; 8 | for (let i = 0, j = n - 1; i <= j; height[i] < height[j] ? i++ : j--) { 9 | res = Math.max(res, Math.min(height[i], height[j]) * (j - i)); 10 | } 11 | return res; 12 | }; 13 | 14 | /** 15 | * 这道题拿到手应该立刻能想到两层循环暴力解决,但应该有更好的遍历方案。因为装水量取决于最短的板, 16 | * 所以可以定义两个指针从左右两端向中间移动,移动端取决于更短的板。 17 | */ -------------------------------------------------------------------------------- /Array/[M]134.Gas Station.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} gas 3 | * @param {number[]} cost 4 | * @return {number} 5 | */ 6 | var canCompleteCircuit = function(gas, cost) { 7 | let start = 0; 8 | let sum = 0; 9 | let total = 0; 10 | for (let i = 0; i < gas.length; i++) { 11 | sum += gas[i] - cost[i]; 12 | total += gas[i] - cost[i]; 13 | if (sum < 0) { 14 | start = i + 1; 15 | sum = 0; 16 | } 17 | } 18 | return total < 0 ? -1 : start; 19 | }; 20 | 21 | /** 22 | * 这题最初拿到手最容易想到的是两层遍历算sum,小于0则跳出当前循环,很显然这样O(n^2)时间复杂度 23 | * 过高会TLE,这里要优化的关键点在于找出一个规律: 24 | * 起点之后的sum是不可能小于0的,换句话说如果从index为0开始遍历累加,当小于0时,前面的所有 25 | * index都不能作为起点 26 | */ -------------------------------------------------------------------------------- /Array/[M]152.Maximum Product Subarray.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} nums 3 | * @return {number} 4 | */ 5 | var maxProduct = function(nums) { 6 | let res = nums[0]; 7 | let f = Array.from({length: nums.length}, _ => 0); 8 | let g = Array.from(f); 9 | f[0] = nums[0]; 10 | g[0] = nums[0]; 11 | for (let i = 1; i < nums.length; i++) { 12 | f[i] = Math.max(f[i - 1] * nums[i], g[i - 1] * nums[i], nums[i]); 13 | g[i] = Math.min(f[i - 1] * nums[i], g[i - 1] * nums[i], nums[i]); 14 | res = Math.max(res, f[i]); 15 | } 16 | return res; 17 | }; 18 | 19 | /** 20 | * 这题求最大乘积子数组,由于有负数的存在,最大乘积可能由负数乘以负数,分两种情况讨论。这里用 21 | * 动态规划数组f[i]和g[i]分别表示以i为结尾的最大子数组和最小子数组,那么可能两个状态转移方程, 22 | * 最大子数组就是f中最大的元素。 23 | */ -------------------------------------------------------------------------------- /Array/[M]209.Minimum Size Subarray Sum.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number} s 3 | * @param {number[]} nums 4 | * @return {number} 5 | */ 6 | var minSubArrayLen = function(s, nums) { 7 | let res = Number.MAX_SAFE_INTEGER; 8 | let left = 0; 9 | let sum = 0; 10 | for (let i = 0; i < nums.length; i++) { 11 | sum += nums[i]; 12 | while (sum >= s && left <= i) { 13 | res = Math.min(i - left + 1, res); 14 | sum -= nums[left++]; 15 | } 16 | } 17 | return res === Number.MAX_SAFE_INTEGER ? 0 : res; 18 | }; 19 | 20 | /** 21 | * 类似于53题,由最大连续总和变为大于s的最短连续总和,思路比较简单, 22 | * 维护一个数组头指针,当达到条件时计算最小长度并将头指针向右移,遍历完成 23 | * 得结果 24 | */ -------------------------------------------------------------------------------- /Array/[M]228.Summary Ranges.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} nums 3 | * @return {string[]} 4 | */ 5 | var summaryRanges = function(nums) { 6 | if (!nums.length) return []; 7 | let res = [`${nums[0]}`]; 8 | let start = nums[0]; 9 | for (let i = 1; i < nums.length; i++) { 10 | if (nums[i] - nums[i - 1] === 1) { 11 | res[res.length - 1] = `${start}->${nums[i]}`; 12 | } else { 13 | start = nums[i]; 14 | res.push(`${nums[i]}`); 15 | } 16 | } 17 | return res; 18 | }; 19 | 20 | /** 21 | * 这一题顺着题目思路转化为代码即可,分两种情况对数组末尾处理。 22 | */ -------------------------------------------------------------------------------- /Array/[M]238.Product of Array Except Self.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} nums 3 | * @return {number[]} 4 | */ 5 | var productExceptSelf = function(nums) { 6 | let res = [1]; 7 | for (let i = 1; i < nums.length; i++) { 8 | res[i] = res[i - 1] * nums[i - 1]; 9 | } 10 | let right = 1; 11 | for (let i = nums.length - 1;i >= 0; i--) { 12 | res[i] *= right; 13 | right *= nums[i]; 14 | } 15 | return res; 16 | }; 17 | 18 | /** 19 | * 这题规定不能用除法,那么常规思路求总乘积后除以自身不可行,就反过来思考,乘以除了自己之外 20 | * 的所有数,从两个方向遍历累乘即可。 21 | */ -------------------------------------------------------------------------------- /Array/[M]274.H-Index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} citations 3 | * @return {number} 4 | */ 5 | var hIndex = function(citations) { 6 | citations.sort((a, b) => b - a); 7 | for (let i = 0; i < citations.length; i++) { 8 | if (i >= citations[i]) return i; 9 | } 10 | return citations.length; 11 | }; 12 | 13 | /** 14 | * 排序后一次遍历即可 15 | */ -------------------------------------------------------------------------------- /Array/[M]275.H-Index II.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} citations 3 | * @return {number} 4 | */ 5 | var hIndex = function(citations) { 6 | let left = 0; 7 | let right = citations.length - 1; 8 | while (left <= right) { 9 | let mid = left + Math.floor((right - left) / 2); 10 | if (citations[mid] < citations.length - mid) { 11 | left = mid + 1; 12 | } else { 13 | right = mid - 1; 14 | } 15 | } 16 | return citations.length - left; 17 | }; 18 | 19 | /** 20 | * 和274完全一样,不过对时间复杂度有O(logn)要求,对这种时间复杂度第一时间想到二分查找解决 21 | */ -------------------------------------------------------------------------------- /Array/[M]287.Find the Duplicate Number.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} nums 3 | * @return {number} 4 | */ 5 | var findDuplicate = function(nums) { 6 | let left = 0; 7 | let right = nums.length - 1; 8 | while (left <= right) { 9 | let middle = left + Math.floor(right - left); 10 | let cnt = 0; 11 | for (let num of nums) { 12 | if (num <= middle) { 13 | cnt++; 14 | } 15 | } 16 | if (cnt <= middle) { 17 | left = middle + 1; 18 | } else { 19 | right = middle - 1; 20 | } 21 | } 22 | return left; 23 | }; 24 | 25 | /** 26 | * 这题限制了常数空间复杂度以及不能修改数组,常规查重方法就都不能用了。但时间复杂度要求不高, 27 | * 就可以考虑用二分搜索了。每一次搜索中统计位于middle一边的num数量,重复数肯定在更大的一边。 28 | * 时间复杂度为O(nlogn) 29 | */ 30 | 31 | /** 32 | * @param {number[]} nums 33 | * @return {number} 34 | */ 35 | var findDuplicate = function(nums) { 36 | let slow = 0, fast = 0, finder = 0; 37 | while (true) { 38 | slow = nums[slow]; 39 | fast = nums[nums[fast]]; 40 | if (slow === fast) break; 41 | } 42 | while (true) { 43 | slow = nums[slow]; 44 | finder = nums[finder]; 45 | if (slow === finder) break; 46 | } 47 | return finder; 48 | }; 49 | 50 | /** 51 | * 在网上还看到一种更骚的解法,用到了快慢指针,因为有重复数,和数值1到n的限制,通过idx到数值的映射是一定 52 | * 可以组成一个p型环的,并且idx为0的位置不在环内,环的入口就是重复值。那么可以先通过快慢指针多次映射找到 53 | * 位于环内的某个idx相遇,这个idx有个特点,就是再走慢指针走到入口的步数也会回到入口,这样可以再建立一个查 54 | * 找器从0找起,相遇的位置就是环入口,也就是结果了。 55 | */ -------------------------------------------------------------------------------- /Array/[M]289.Game of Life.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[][]} board 3 | * @return {void} Do not return anything, modify board in-place instead. 4 | */ 5 | var gameOfLife = function(board) { 6 | let m = board.length; 7 | let n = m ? board[0].length : 0; 8 | let dx = [-1, -1, -1, 0, 1, 1, 1, 0]; 9 | let dy = [-1, 0, 1, 1, 1, 0, -1, -1]; 10 | for (let i = 0; i < m; i++) { 11 | for (let j = 0; j < n; j++) { 12 | let cnt = 0; 13 | for (let k = 0; k < 8; k++) { 14 | let x = i + dx[k], y = j + dy[k]; 15 | if (x >= 0 && x < m && y >= 0 && y < n && (board[x][y] === 1 || board[x][y] === 2)) { 16 | cnt++; 17 | } 18 | } 19 | if (board[i][j] && (cnt < 2 || cnt > 3)) board[i][j] = 2; 20 | if (!board[i][j] && cnt === 3) board[i][j] = 3; 21 | } 22 | } 23 | for (let i = 0; i < m; i++) { 24 | for (let j = 0; j < n; j++) { 25 | board[i][j] %= 2; 26 | } 27 | } 28 | }; 29 | 30 | /** 31 | * 这题的逻辑比较简单,难点在于变换对于每个位置是同时进行的,而遍历是有时间先后顺序的,又不能开辟新数组。参考了网络上的 32 | * 解法,思路在于创建状态机来表示先后状态,0为死到死,1为活到活,2为活到死,3为死到活,这样在遍历过程中同一个位置就能记录 33 | * 改变前后的信息了,最终再翻译为改变后信息即可。 34 | */ -------------------------------------------------------------------------------- /Array/[M]299.Bulls and Cows.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} secret 3 | * @param {string} guess 4 | * @return {string} 5 | */ 6 | var getHint = function(secret, guess) { 7 | let map = {}; 8 | let bulls = 0; 9 | let cows = 0; 10 | for (let i = 0; i <= 9; i++) { 11 | map[i] = 0; 12 | } 13 | for (let i = 0; i < secret.length; i++) { 14 | if (secret[i] === guess[i]) { 15 | bulls++; 16 | } else { 17 | if (map[secret[i]]++ < 0) cows++; 18 | if (map[guess[i]]-- > 0) cows++; 19 | } 20 | } 21 | return `${bulls}A${cows}B`; 22 | }; 23 | 24 | /** 25 | * 这题关键在于遍历过程中,cows对应的guess数和secret数的保存,并和当前遍历值的比较。很容易想到用hashmap记录数量。 26 | * secret数加1,guess数减1。 27 | * 如果当前遍历的secret数小于0,说明有记录的guess数未被命中,此时被当前secret命中,cows加1。 28 | * 如果当前遍历的guess数大于0,说明有记录的secret数未被命中,此时被当前guess命中,cows加1。 29 | */ 30 | 31 | -------------------------------------------------------------------------------- /Array/[M]309.Best Time to Buy and Sell Stock with Cooldown.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} prices 3 | * @return {number} 4 | */ 5 | var maxProfit = function(prices) { 6 | let n = prices.length; 7 | if (n < 2) return 0; 8 | let buy = Array.from({length: n}, _ => 0); 9 | let sell = Array.from(buy); 10 | buy[0] = -prices[0]; 11 | buy[1] = Math.max(-prices[1], - prices[0]); 12 | sell[0] = 0; 13 | sell[1] = Math.max(prices[1] - prices[0], 0); 14 | for (let i = 2; i < n; i++) { 15 | sell[i] = Math.max(buy[i - 1] + prices[i], sell[i - 1]); 16 | buy[i] = Math.max(sell[i - 2] - prices[i], buy[i - 1]); 17 | } 18 | return sell[n - 1]; 19 | }; 20 | 21 | /** 22 | * 122题的基础上增加了个冷却期的概念。初始化两个动态规划数组,buy[i]表示i天前手持股票的最大利润, 23 | * sell[i]表示i天前卖出股票的最大利润,两个状态转移方程求解 24 | */ -------------------------------------------------------------------------------- /Array/[M]324.Wiggle Sort II.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} nums 3 | * @return {void} Do not return anything, modify nums in-place instead. 4 | */ 5 | var wiggleSort = function(nums) { 6 | let temp = Array.from(nums); 7 | let n = temp.length, mid = Math.floor((n + 1) / 2), j = n; 8 | temp.sort((a, b) => a - b); 9 | for (let i = 0; i < n; i++) { 10 | nums[i] = i % 2 === 1 ? temp[--j] : temp[--mid]; 11 | } 12 | }; 13 | 14 | /** 15 | * 将一个乱序数组重排为摇摆数组,那么先排成有序数组,再从中间拆开,后一半一定比前一半大,依次填充即可 16 | */ -------------------------------------------------------------------------------- /Array/[M]334.Increasing Triplet Subsequence.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} nums 3 | * @return {boolean} 4 | */ 5 | var increasingTriplet = function(nums) { 6 | let m1 = Number.MAX_SAFE_INTEGER, m2 = Number.MAX_SAFE_INTEGER; 7 | for (let i = 0; i < nums.length; i++) { 8 | if (m1 >= nums[i]) { 9 | m1 = nums[i]; 10 | } else if (m2 >= nums[i]) { 11 | m2 = nums[i]; 12 | } else { 13 | return true; 14 | } 15 | } 16 | return false; 17 | }; 18 | 19 | /** 20 | * 这种题拿到手比较容易想到的解法是动态规划,dp[i]代表i之前不大于nums[i]的个数,两层遍历 21 | * 即可解决。不过由于要求线性时间复杂度就要考虑更特殊的解法。这里用两个变量m1和m2分别记录遍历 22 | * 过程中最小和第二小的数。那么只要有一个数大于m1和m2,则可以返回true。 23 | */ -------------------------------------------------------------------------------- /Array/[M]376.Wiggle Subsequence.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} nums 3 | * @return {number} 4 | */ 5 | var wiggleMaxLength = function(nums) { 6 | if (!nums.length) return 0; 7 | let posi = 1, nega = 1; 8 | for (let i = 1; i < nums.length; i++) { 9 | if (nums[i] - nums[i - 1] > 0) { 10 | posi = nega + 1; 11 | } 12 | if (nums[i] - nums[i - 1] < 0) { 13 | nega = posi + 1; 14 | } 15 | } 16 | return Math.max(posi, nega); 17 | }; 18 | 19 | /** 20 | * 找到最长的摇摆子序列,分两种情况,以正数或负数结尾,最容易想到的思路是动态规划,f[i] 21 | * 和g[i]表示以i结尾并且是正数,负数的最长子数组,可以得到状态转移方程并取两个数组中的最大值求解。 22 | * 但实际上没必要保存每个位置的最长子数组,因为越往后长度肯定是越来越大的,那么只需要用两个变量保存 23 | * 当前遍历的位置i的最大长度即可,遍历完成即可取得结果。 24 | */ -------------------------------------------------------------------------------- /Array/[M]442.Find All Duplicates in an Array.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} nums 3 | * @return {number[]} 4 | */ 5 | var findDuplicates = function(nums) { 6 | let res = []; 7 | for (let i = 0; i < nums.length; i++) { 8 | let idx = Math.abs(nums[i]) - 1; 9 | if (nums[idx] < 0) { 10 | res.push(idx + 1); 11 | } else { 12 | nums[idx] = -nums[idx]; 13 | } 14 | } 15 | return res; 16 | }; 17 | 18 | /** 19 | * 1到n的经典题型,三种思路,这里选择正负标记 20 | */ -------------------------------------------------------------------------------- /Array/[M]495.Teemo Attacking.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} timeSeries 3 | * @param {number} duration 4 | * @return {number} 5 | */ 6 | var findPoisonedDuration = function(timeSeries, duration) { 7 | if (!timeSeries.length) return 0; 8 | let res = duration; 9 | for (let i = 1; i < timeSeries.length; i++) { 10 | res += Math.min(timeSeries[i] - timeSeries[i - 1], duration) 11 | } 12 | return res; 13 | }; 14 | 15 | /** 16 | * 计算提莫的攻击带毒回合。最后一次攻击持续duration回合,设置为初始值。遍历时从第2次开始和上一次 17 | * 攻击比较,持续时间为duration和两次攻击间隔更小的部分 18 | */ -------------------------------------------------------------------------------- /Array/[M]55.Jump Game.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} nums 3 | * @return {boolean} 4 | */ 5 | var canJump = function(nums) { 6 | let max = 0; 7 | for (let i = 0; i < nums.length; i++) { 8 | if (i > max || max >= nums.length - 1) break; 9 | max = Math.max(i + nums[i], max); 10 | } 11 | return max >= nums.length - 1; 12 | }; 13 | 14 | /** 15 | * 贪心算法解决,遍历过程中维护一个当前能达到的最远距离max, 16 | * 终止条件为max到达终点或者未到达终点时遍历到max。 17 | */ -------------------------------------------------------------------------------- /Array/[M]56.Merge Intervals.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for an interval. 3 | * function Interval(start, end) { 4 | * this.start = start; 5 | * this.end = end; 6 | * } 7 | */ 8 | /** 9 | * @param {Interval[]} intervals 10 | * @return {Interval[]} 11 | */ 12 | var merge = function(intervals) { 13 | let res = []; 14 | let cur = 0; 15 | let n = intervals.length - 1; 16 | intervals.sort((a, b) => a.start - b.start); 17 | while (cur <= n) { 18 | if (cur === n || intervals[cur + 1].start > intervals[cur].end) { 19 | res.push(intervals[cur++]); 20 | } else { 21 | intervals[cur + 1].start = Math.min(intervals[cur].start, intervals[cur + 1].start); 22 | intervals[cur + 1].end = Math.max(intervals[cur].end, intervals[cur + 1].end); 23 | cur++; 24 | } 25 | } 26 | return res; 27 | }; 28 | 29 | /** 30 | * 和57题类似,分两种情况,一种无重叠,直接推入res,一种有重叠,则合并后继续后续循环。 31 | */ -------------------------------------------------------------------------------- /Array/[M]565.Array Nesting.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} nums 3 | * @return {number} 4 | */ 5 | var arrayNesting = function(nums) { 6 | const linkArr = []; 7 | for (let i = 0; i < nums.length; i++) { 8 | if (linkArr.every(item => !item.includes(nums[i]))) { 9 | let link = []; 10 | let val = nums[i]; 11 | while(!link.includes(val)) { 12 | link.push(val); 13 | val = nums[val]; 14 | } 15 | linkArr.push(link); 16 | } 17 | } 18 | return Math.max(...linkArr.map(item => item.length)) 19 | }; 20 | 21 | /** 22 | * 值和位置的映射关系,求最长的环。那么遍历时对每个值求环,注意过滤已经成环的值。当遍历完成时取最长的环即可 23 | */ -------------------------------------------------------------------------------- /Array/[M]718.Maximum Length of Repeated Subarray.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} A 3 | * @param {number[]} B 4 | * @return {number} 5 | */ 6 | var findLength = function(A, B) { 7 | let res = 0; 8 | let dp = Array.from({length: A.length + 1}); 9 | for (let i = 0; i < dp.length; i++) { 10 | dp[i] = new Array(B.length + 1).fill(0); 11 | } 12 | for (let i = 0; i < A.length; i++) { 13 | for (let j = 0; j < B.length; j++) { 14 | if (A[i] === B[j]) { 15 | dp[i + 1][j + 1] = dp[i][j] + 1; 16 | } 17 | res = Math.max(res, dp[i + 1][j + 1]); 18 | } 19 | } 20 | return res; 21 | }; 22 | 23 | /** 24 | * 求最长重复子数组。比较型的极值问题优先考虑动态规划,dp[i][j]表示A的前i个元素和B的前j个 25 | * 元素的最长重复子数组长度 26 | */ -------------------------------------------------------------------------------- /Array/[M]75.Sort Colors.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} nums 3 | * @return {void} Do not return anything, modify nums in-place instead. 4 | */ 5 | var sortColors = function(nums) { 6 | let red = 0; 7 | let blue = nums.length - 1; 8 | for (let i = 0; i <= blue; i++) { 9 | if (nums[i] === 0) { 10 | swap(nums, i, red++); 11 | } 12 | if (nums[i] === 2) { 13 | swap(nums, i--, blue--); 14 | } 15 | } 16 | }; 17 | 18 | function swap(nums, i, j) { 19 | let temp = nums[i]; 20 | nums[i] = nums[j]; 21 | nums[j] = temp; 22 | } 23 | 24 | /** 25 | * 排序题,遍历一次将对应颜色元素交换到特定区域即可,红色到头部,蓝色到尾部,白色保持中间不变,分别用指针记录。 26 | */ -------------------------------------------------------------------------------- /Array/[M]769.Max Chunks To Make Sorted.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} arr 3 | * @return {number} 4 | */ 5 | let maxChunksToSorted = arr => { 6 | let res = 0; 7 | let idx = -1; 8 | arr.forEach((n, i) => { 9 | if (n >= i) { 10 | idx = Math.max(idx, n) 11 | } 12 | 13 | if (i == idx) { 14 | res++; 15 | idx = -1; 16 | } 17 | }); 18 | return res; 19 | }; 20 | 21 | /** 22 | * 由于限定了数组范围在0到n - 1之间,那么排好序之后每个位置的元素不应大于index。 23 | * 因此遍历时维护一个当前最大值,当这个最大值等于当前index时就可以划分区间了 24 | */ -------------------------------------------------------------------------------- /Array/[M]80.Remove Duplicates from Sorted Array II.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} nums 3 | * @return {number} 4 | */ 5 | var removeDuplicates = function (nums) { 6 | let pre = 0; 7 | let cur = 1; 8 | let count = 1; 9 | while (cur < nums.length) { 10 | if (nums[pre] === nums[cur] && count === 0) { 11 | cur++; 12 | } else { 13 | if (nums[pre] === nums[cur]) { 14 | count--; 15 | } else { 16 | count = 1; 17 | } 18 | nums[++pre] = nums[cur++]; 19 | } 20 | } 21 | return pre + 1; 22 | }; 23 | 24 | /** 25 | * 这题是27题的变种,相比其有了重复数量的条件放宽,于是在前者两个指针的基础上,还要增加一个变量计数重复数量。无非是多加一个判断条件,主要思路还是相同的。 26 | */ -------------------------------------------------------------------------------- /Backtracking/[M]216.Combination Sum III.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number} k 3 | * @param {number} n 4 | * @return {number[][]} 5 | */ 6 | var combinationSum3 = function(k, n) { 7 | let res = []; 8 | let out = []; 9 | helper(k, n, 1, out, res); 10 | return res; 11 | }; 12 | 13 | function helper(k, n, level, out, res) { 14 | if (n < 0) return; 15 | if (n === 0 && out.length === k){ 16 | res.push(Array.from(out)) 17 | } 18 | for (let i = level; i <= 9; i++) { 19 | out.push(i); 20 | helper(k, n - i, i + 1, out, res); 21 | out.pop(); 22 | } 23 | } 24 | 25 | /** 26 | * 数字给定1到9,限定了个数,同样是基础回溯解,获得解的条件增加一个长度判断即可 27 | */ 28 | -------------------------------------------------------------------------------- /Backtracking/[M]39.Combination Sum.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} candidates 3 | * @param {number} target 4 | * @return {number[][]} 5 | */ 6 | var combinationSum = function(candidates, target) { 7 | let res = []; 8 | let out = []; 9 | candidates.sort((a, b) => a - b); 10 | helper(res, out, candidates, target, 0); 11 | return res; 12 | }; 13 | 14 | function helper(res, out, candidates, target, start) { 15 | if (target < 0) return; 16 | if (target === 0) { 17 | res.push(Array.from(out)); 18 | } else { 19 | for (let i = start; i < candidates.length; i++) { 20 | out.push(candidates[i]); 21 | helper(res, out, candidates, target - candidates[i], i); 22 | out.pop(); 23 | } 24 | } 25 | } 26 | 27 | /** 28 | * 基础回溯,每次推入一个值时目标数减去该值,当目标值为0则获得一个解,小于0就跳出 29 | */ -------------------------------------------------------------------------------- /Backtracking/[M]40.Combination Sum II.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} candidates 3 | * @param {number} target 4 | * @return {number[][]} 5 | */ 6 | var combinationSum2 = function(candidates, target) { 7 | let res = []; 8 | let out = []; 9 | candidates.sort((a, b) => a - b); 10 | helper(res, out, candidates, target, 0); 11 | return res; 12 | }; 13 | 14 | function helper(res, out, candidates, target, start) { 15 | if (target < 0) return; 16 | if (target === 0) { 17 | res.push(Array.from(out)); 18 | } else { 19 | for (let i = start; i < candidates.length; i++) { 20 | out.push(candidates[i]); 21 | helper(res, out, candidates, target - candidates[i], i + 1); 22 | out.pop(); 23 | while (i + 1 < candidates.length && candidates[i] === candidates[i + 1]) { 24 | i++; 25 | } 26 | } 27 | } 28 | } 29 | 30 | /** 31 | * 39的延伸,增加了重复值,在回溯时注意跳过 32 | */ -------------------------------------------------------------------------------- /Backtracking/[M]46.Permutations.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} nums 3 | * @return {number[][]} 4 | */ 5 | var permute = function(nums) { 6 | let res = []; 7 | let swap = function(nums, i, idx) { 8 | if (i !== idx) { 9 | let temp = nums[i]; 10 | nums[i] = nums[idx]; 11 | nums[idx] = temp; 12 | } 13 | } 14 | let helper = function(idx) { 15 | for (let i = idx; i < nums.length; i++) { 16 | swap(nums, i, idx); 17 | if (idx + 1 < nums.length - 1) { 18 | helper(idx + 1); 19 | } else { 20 | res.push(Array.from(nums)); 21 | } 22 | swap(nums, i, idx); 23 | } 24 | } 25 | helper(0); 26 | return res; 27 | }; 28 | 29 | /** 30 | * 经典的数组全排列问题,回溯解,每次调用记录头部位置,并将后续位置循环和头部交换即可 31 | */ -------------------------------------------------------------------------------- /Backtracking/[M]47.Permutations II.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} nums 3 | * @return {number[][]} 4 | */ 5 | var permuteUnique = function(nums) { 6 | let res = []; 7 | helper(nums, 0, res); 8 | return res; 9 | }; 10 | 11 | function helper(nums, start, res) { 12 | if (start === nums.length - 1) { 13 | res.push(Array.from(nums)); 14 | return; 15 | } 16 | let set = new Set(); 17 | for (let i = start; i < nums.length; i++) { 18 | if (set.has(nums[i])) continue; 19 | swap(nums, start, i); 20 | set.add(nums[start]); 21 | helper(nums, start + 1, res); 22 | swap(nums, start, i); 23 | } 24 | } 25 | 26 | function swap(nums, start, i) { 27 | let temp = nums[start]; 28 | nums[start] = nums[i]; 29 | nums[i] = temp; 30 | } 31 | 32 | /** 33 | * 有重复值的全排列,用set记录一次调用中已交换过的值,跳过重复即可 34 | */ 35 | -------------------------------------------------------------------------------- /Backtracking/[M]77.Combinations.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number} n 3 | * @param {number} k 4 | * @return {number[][]} 5 | */ 6 | var combine = function(n, k) { 7 | let res = []; 8 | let out = []; 9 | helper(res, out, 1, k, n); 10 | return res; 11 | }; 12 | 13 | function helper(res, out, pos, k, n) { 14 | if (out.length === k) { 15 | res.push(Array.from(out)); 16 | return; 17 | } 18 | for (let i = pos; i <= n; i++) { 19 | out.push(i); 20 | helper(res, out, i + 1, k, n); 21 | out.pop(); 22 | } 23 | } 24 | 25 | /** 26 | * 基础回溯,设置好跳出条件即可 27 | */ -------------------------------------------------------------------------------- /Backtracking/[M]78.Subsets.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} nums 3 | * @return {number[][]} 4 | */ 5 | var subsets = function(nums) { 6 | let out = []; 7 | let res = []; 8 | nums.sort((a, b) => a - b); 9 | helper(nums, 0, res, out); 10 | return res; 11 | }; 12 | 13 | function helper(nums, pos, res, out) { 14 | res.push(Array.from(out)); 15 | for (let i = pos; i < nums.length; i++) { 16 | out.push(nums[i]); 17 | helper(nums, i + 1, res, out); 18 | out.pop(); 19 | while (i + 1 < nums.length && nums[i] === nums[i + 1]) { 20 | i++; 21 | } 22 | } 23 | } 24 | 25 | /**基础回溯,求数组元素的所有组合 */ 26 | 27 | -------------------------------------------------------------------------------- /Backtracking/[M]90.Subsets II.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} nums 3 | * @return {number[][]} 4 | */ 5 | /** 6 | * @param {number[]} nums 7 | * @return {number[][]} 8 | */ 9 | var subsetsWithDup = function(nums) { 10 | let out = []; 11 | let res = []; 12 | nums.sort((a, b) => a - b); 13 | helper(nums, 0, res, out); 14 | return res; 15 | }; 16 | 17 | function helper(nums, pos, res, out) { 18 | res.push(Array.from(out)); 19 | for (let i = pos; i < nums.length; i++) { 20 | out.push(nums[i]); 21 | helper(nums, i + 1, res, out); 22 | out.pop(); 23 | while (i + 1 < nums.length && nums[i] === nums[i + 1]) { 24 | i++; 25 | } 26 | } 27 | } 28 | 29 | /** 30 | * 78的延伸,多了重复元素,那么只需要在回溯时跳过重复元素即可 31 | */ 32 | -------------------------------------------------------------------------------- /Binary Search/[E]278.First Bad Version.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for isBadVersion() 3 | * 4 | * @param {integer} version number 5 | * @return {boolean} whether the version is bad 6 | * isBadVersion = function(version) { 7 | * ... 8 | * }; 9 | */ 10 | 11 | /** 12 | * @param {function} isBadVersion() 13 | * @return {function} 14 | */ 15 | var solution = function(isBadVersion) { 16 | /** 17 | * @param {integer} n Total versions 18 | * @return {integer} The first bad version 19 | */ 20 | return function(n) { 21 | let arr = Array.from(n, (_, index) => index + 1); 22 | let left = 0; 23 | let right = n - 1; 24 | while (left <= right) { 25 | let mid = left + Math.floor((right - left) / 2); 26 | if (!isBadVersion(mid)) { 27 | left = mid + 1; 28 | } else { 29 | right = mid - 1; 30 | } 31 | } 32 | return left; 33 | }; 34 | }; 35 | 36 | /** 37 | * 找出坏的一个产品,最基本的二分 38 | */ -------------------------------------------------------------------------------- /Binary Search/[E]35.Search Insert Position.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} nums 3 | * @param {number} target 4 | * @return {number} 5 | */ 6 | var searchInsert = function(nums, target) { 7 | let left = 0; 8 | let right = nums.length - 1; 9 | while (left <= right) { 10 | let mid = left + Math.floor((right - left) / 2); 11 | if (nums[mid] < target) { 12 | left = mid + 1; 13 | } else { 14 | right = mid - 1; 15 | } 16 | } 17 | return left; 18 | }; 19 | 20 | /** 21 | * 找出合适的插入位置,基础二分 22 | */ -------------------------------------------------------------------------------- /Binary Search/[H]315.Count of Smaller Nunmbers After Self.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} nums 3 | * @return {number[]} 4 | */ 5 | var countSmaller = function(nums) { 6 | let res = []; 7 | let newArr = []; 8 | for (let i = nums.length - 1; i >= 0; i--) { 9 | let left = 0; 10 | let right = res.length - 1; 11 | while (left <= right) { 12 | let mid = left + Math.floor((right - left) / 2); 13 | if (res[mid] < nums[i]) { 14 | left = mid + 1; 15 | } else { 16 | right = mid - 1; 17 | } 18 | } 19 | newArr.unshift(left); 20 | res.splice(left, 0, nums[i]); 21 | } 22 | return newArr; 23 | }; 24 | 25 | /** 26 | * 从右往左插入排序到一个新有序数组,每次计算其index即为原数组后面小于当前元素的 27 | * 数量,插入排序用二分实现 28 | */ -------------------------------------------------------------------------------- /Binary Search/[M]162.Find Peak Element.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} nums 3 | * @return {number} 4 | */ 5 | var findPeakElement = function(nums) { 6 | let left = 0; 7 | let right = nums.length - 1; 8 | while (left <= right) { 9 | let mid = left + Math.floor((right - left) / 2); 10 | if (nums[mid] < nums[mid + 1]) { 11 | left = mid + 1; 12 | } else { 13 | right = mid - 1; 14 | } 15 | } 16 | return left; 17 | }; 18 | 19 | /** 20 | * 二分搜索分界条件和前一个值比大小即可 21 | */ -------------------------------------------------------------------------------- /Binary Search/[M]33.Search in Rotated Sorted Array.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} nums 3 | * @param {number} target 4 | * @return {number} 5 | */ 6 | var search = function(nums, target) { 7 | let left = 0; 8 | let right = nums.length - 1; 9 | while (left <= right) { 10 | let mid = left + Math.floor((right - left) / 2); 11 | if (nums[mid] === target) return mid; 12 | if (nums[mid] < nums[right]) { 13 | if (nums[mid] < target && nums[right] >= target) { 14 | left = mid + 1; 15 | } else { 16 | right = mid - 1; 17 | } 18 | } else { 19 | if (nums[left] <= target && nums[mid] > target) { 20 | right = mid - 1; 21 | } else { 22 | left = mid + 1; 23 | } 24 | } 25 | } 26 | return -1; 27 | }; 28 | 29 | /** 30 | * 这题用二分的关键在于找到正确的左右边界,如果中间数小于右边界,则右半段有序,反之左半段有序。 31 | * 在有序半段里判断目标是否在其中来决定保留哪半边。 32 | */ -------------------------------------------------------------------------------- /Binary Search/[M]34.Find First and Last Position of Element in Sorted Array.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} nums 3 | * @param {number} target 4 | * @return {number[]} 5 | */ 6 | var searchRange = function(nums, target) { 7 | let left = 0; 8 | let right = nums.length - 1; 9 | while (left <= right) { 10 | let mid = left + Math.floor((right - left) / 2); 11 | if (nums[mid] === target) { 12 | return helper(mid, nums); 13 | } 14 | if (nums[mid] < target) { 15 | left = mid + 1; 16 | } else { 17 | right = mid - 1; 18 | } 19 | } 20 | return [-1, -1]; 21 | }; 22 | 23 | function helper(mid, nums) { 24 | let right = mid; 25 | let left = mid; 26 | while (nums[right + 1] === nums[mid]) { 27 | right++; 28 | } 29 | while (nums[left - 1] === nums[mid]) { 30 | left--; 31 | } 32 | return [left, right]; 33 | } 34 | 35 | /** 36 | * 二分找到第一个相等的数后,左右拓展 37 | */ -------------------------------------------------------------------------------- /Binary Search/[M]81.Search in Rotated Sorted Array II.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} nums 3 | * @param {number} target 4 | * @return {boolean} 5 | */ 6 | /** 7 | * @param {number[]} nums 8 | * @param {number} target 9 | * @return {number} 10 | */ 11 | var search = function(nums, target) { 12 | let left = 0; 13 | let right = nums.length - 1; 14 | while (left <= right) { 15 | let mid = left + Math.floor((right - left) / 2); 16 | if (nums[mid] === target) return true; 17 | if (nums[mid] < nums[right]) { 18 | if (nums[mid] < target && nums[right] >= target) { 19 | left = mid + 1; 20 | } else { 21 | right = mid - 1; 22 | } 23 | } else if (nums[mid] > nums[right]) { 24 | if (nums[left] <= target && nums[mid] > target) { 25 | right = mid - 1; 26 | } else { 27 | left = mid + 1; 28 | } 29 | } else { 30 | right--; 31 | } 32 | } 33 | return false; 34 | }; 35 | 36 | /** 37 | * 33的延伸,之前由于没有重复值和最右边比较大小可以确认半边,这次有重复值。解决方法也简单, 38 | * 最右向左移,将重复值舍弃即可 39 | */ -------------------------------------------------------------------------------- /Dynamic Programming/[E]198.House Robber.js: -------------------------------------------------------------------------------- 1 | var rob = function(nums) { 2 | if (nums.length <= 1) { 3 | return nums[0] || 0; 4 | } 5 | let dp = [nums[0], Math.max(nums[0], nums[1])]; 6 | for (let i = 2; i < nums.length; i++) { 7 | dp[i] = Math.max(dp[i - 1], dp[i - 2] + nums[i]) 8 | } 9 | return dp[dp.length - 1] 10 | }; 11 | 12 | /** 13 | * 不能相邻的最大值,可得状态转移方程dp[i] = Math.max(dp[i - 1], dp[i - 2] + nums[i]) 14 | */ -------------------------------------------------------------------------------- /Dynamic Programming/[E]70.Climbing Stairs.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number} n 3 | * @return {number} 4 | */ 5 | var climbStairs = function(n) { 6 | let res = [1, 1]; 7 | for (let i = 2; i <= n; i++) { 8 | res[i] = res[i - 1] + res[i - 2]; 9 | } 10 | return res[n]; 11 | }; 12 | 13 | /** 14 | * dp[i]爬到i阶的种类,dp[i] = dp[i - 1] + dp[i - 2]递推式可得 15 | */ -------------------------------------------------------------------------------- /Dynamic Programming/[M]120.Triangle.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[][]} triangle 3 | * @return {number} 4 | */ 5 | var minimumTotal = function(triangle) { 6 | for (let i = triangle.length - 2; i >= 0; i--) { 7 | for (let j = 0; j <= i; j++) { 8 | triangle[i][j] += Math.min(triangle[i + 1][j], triangle[i + 1][j + 1]); 9 | } 10 | } 11 | return triangle[0][0]; 12 | }; 13 | 14 | /** 15 | * dp[i][j]表示i层第i个元素到底端最短路径,由递推式 dp[i][j] += Math.min(dp[i + 1][j], dp[i + 1][j + 1]) 16 | * 得到顶层值即为结果 17 | */ -------------------------------------------------------------------------------- /Dynamic Programming/[M]139.Word Break.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} s 3 | * @param {string[]} wordDict 4 | * @return {boolean} 5 | */ 6 | var wordBreak = function(s, wordDict) { 7 | let dp = Array.from({length: s.length + 1}, _ => false); 8 | dp[0] = true; 9 | for (let i = 0; i < s.length + 1; i++) { 10 | for (let j = 0; j < i; j++) { 11 | if (dp[j] && wordDict.includes(s.slice(j, i))) { 12 | dp[i] = true; 13 | break; 14 | } 15 | } 16 | } 17 | console.log(dp) 18 | return dp[s.length]; 19 | }; 20 | 21 | /** 22 | * 目标字符串能否被数组中元素组合而成,dp[i]表示字符串到i位置之前能被组合,两层循环求解dp[i], 23 | * 找到就跳出。 24 | */ -------------------------------------------------------------------------------- /Dynamic Programming/[M]213.House Robber II.js: -------------------------------------------------------------------------------- 1 | var rob = function(nums) { 2 | if (nums.length === 0) return 0; 3 | if (nums.length === 1) return nums[0]; 4 | return Math.max(helper(nums, 0, nums.length - 1), helper(nums, 1, nums.length)) 5 | }; 6 | 7 | function helper(nums, left, right) { 8 | if (right - left <= 1) return nums[left]; 9 | let dp = Array.from({length: right}, _ => 0); 10 | dp[left] = nums[left]; 11 | dp[left + 1] = Math.max(nums[left], nums[left + 1]); 12 | for (let i = left + 2; i < right; i++) { 13 | dp[i] = Math.max(nums[i] + dp[i - 2], dp[i - 1]); 14 | } 15 | return dp.pop(); 16 | } 17 | 18 | /** 19 | * 198延伸,房子成环了,那么关键就拆成两个不成环的子问题再按198题求解 20 | */ -------------------------------------------------------------------------------- /Dynamic Programming/[M]221.Maximal Square.js: -------------------------------------------------------------------------------- 1 | var maximalSquare = function(matrix) { 2 | if (!matrix.length || !matrix[0].length) return 0; 3 | let m = matrix.length, n = matrix[0].length, res = 0; 4 | let dp = Array.from({length: m}, _ => Array.from({length: n}, _ => 0)); 5 | for (let i = 0; i < m; i++) { 6 | for (let j = 0; j < n; j++) { 7 | if (i === 0 || j === 0) { 8 | dp[i][j] = matrix[i][j] - 0; 9 | } else if (matrix[i][j] === '1') { 10 | dp[i][j] = Math.min(dp[i - 1][j - 1], dp[i][j - 1], dp[i - 1][j]) + 1; 11 | } 12 | res = Math.max(res, dp[i][j]); 13 | } 14 | } 15 | return res * res; 16 | }; 17 | 18 | /** 19 | * dp[i][j]表示以该点为右下角的正方形的最大边长,可得状态转移方程 20 | */ -------------------------------------------------------------------------------- /Dynamic Programming/[M]279.Perfect Squares.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number} n 3 | * @return {number} 4 | */ 5 | var numSquares = function(n) { 6 | let dp = Array.from({length: n + 1}, _ => Number.MAX_SAFE_INTEGER); 7 | dp[0] = 0; 8 | for (let i = 0; i <= n; i++) { 9 | for (let j = 1; i + j * j <= n; j++) { 10 | dp[i + j * j] = Math.min(dp[i + j * j], dp[i] + 1); 11 | } 12 | } 13 | return dp[n]; 14 | }; 15 | 16 | /** 17 | * dp[i]表示i的完全平方数,两层循环对n以下的所有值动态更新完全平方数,循环完毕得解 18 | */ -------------------------------------------------------------------------------- /Dynamic Programming/[M]322.Coin Change.js: -------------------------------------------------------------------------------- 1 | var coinChange = function(coins, amount) { 2 | let dp = Array.from({length: amount + 1}, _ => amount + 1); 3 | dp[0] = 0; 4 | for (let i = 1; i <= amount; i++) { 5 | for (let j = 0; j < coins.length; j++) { 6 | if (coins[j] <= i) { 7 | dp[i] = Math.min(dp[i], dp[i - coins[j]] + 1); 8 | } 9 | } 10 | } 11 | return dp[amount] > amount ? -1 : dp[amount]; 12 | }; 13 | 14 | /** 15 | * 类似279求完全平方数,两层循环更新dp[amount]为解,依赖于dp[i](i < amount) 16 | */ -------------------------------------------------------------------------------- /Dynamic Programming/[M]62.Unique Paths.js: -------------------------------------------------------------------------------- 1 | 2 | var uniquePaths = function(m, n) { 3 | let dp = Array.from({length: m}, _ => []); 4 | for (let i = 0; i < m; i++) { 5 | dp[i][0] = 1; 6 | } 7 | for (let j = 0; j < n; j++) { 8 | dp[0][j] = 1; 9 | } 10 | for (let i = 1; i < m; i++) { 11 | for (let j = 1; j < n; j++) { 12 | dp[i][j] = dp[i - 1][j] + dp[i][j - 1]; 13 | } 14 | } 15 | return dp[m - 1][n - 1] 16 | }; 17 | 18 | /** 19 | * 边缘每个点只有一种路径,由边缘往内部递推,dp[i][j] = dp[i - 1][j] + dp[i][j - 1] 20 | */ -------------------------------------------------------------------------------- /Dynamic Programming/[M]63.Unique Paths II.js: -------------------------------------------------------------------------------- 1 | var uniquePathsWithObstacles = function(obstacleGrid) { 2 | const numberOfRows = obstacleGrid.length; 3 | const numberOfCols = obstacleGrid[0].length; 4 | const skeleton = new Array(numberOfRows + 1).fill(new Array(numberOfCols + 1).fill(0)) 5 | const numberOfPaths = JSON.parse(JSON.stringify(skeleton)); 6 | 7 | numberOfPaths[0][1] = 1; 8 | 9 | obstacleGrid.forEach( (row, rowIdx) => { 10 | row.forEach( (element, colIdx) => { 11 | if(element !== 1) { 12 | const pathsFromTop = numberOfPaths[rowIdx][colIdx + 1]; 13 | const pathsFromLeft = numberOfPaths[rowIdx + 1][colIdx]; 14 | numberOfPaths[rowIdx + 1][colIdx + 1] = pathsFromTop + pathsFromLeft; 15 | } 16 | }); 17 | }); 18 | 19 | return numberOfPaths[numberOfRows][numberOfCols]; 20 | }; 21 | 22 | /** 23 | * 62题的延伸,新增了障碍,注意有障碍的地方跳过dp递推即可 24 | */ -------------------------------------------------------------------------------- /Dynamic Programming/[M]64.Minimum Path Sum.js: -------------------------------------------------------------------------------- 1 | function minPathSum(grid) { 2 | var height = grid.length; 3 | var width = grid[0].length; 4 | 5 | for (var i = 0; i < height; i++) { 6 | for (var j = 0; j < width; j++) { 7 | if (i !== 0 && j !== 0) grid[i][j] += Math.min(grid[i - 1][j], grid[i][j - 1]); 8 | else if (i !== 0) grid[i][j] += grid[i - 1][j]; 9 | else if (j !== 0) grid[i][j] += grid[i][j - 1]; 10 | } 11 | } 12 | return grid[height - 1][width - 1]; 13 | } 14 | 15 | /** 16 | * 求左上到右下的最短路径,dp[i][j]表示到i行j列的最短路径可得状态转移方程 17 | */ -------------------------------------------------------------------------------- /Dynamic Programming/[M]91.Decode Ways.js: -------------------------------------------------------------------------------- 1 | function numDecodings(s) { 2 | if (s.length === 0) return 0; 3 | 4 | const N = s.length; 5 | const dp = Array(N+1).fill(0); 6 | 7 | dp[0] = 1; 8 | dp[1] = s[0] === '0' ? 0 : 1; 9 | 10 | for (let i = 2; i <= N; i++) { 11 | if (s[i-1] !== '0') { 12 | dp[i] += dp[i-1]; 13 | } 14 | if (s[i-2] === '1' || s[i-2] === '2' && s[i-1] <= '6') { 15 | dp[i] += dp[i-2]; 16 | } 17 | } 18 | 19 | return dp[N]; 20 | } 21 | 22 | /** 23 | * 这题求状态转移方程关键分两种情况,如果dp[i]对应数字不为0,那么可以对这个数字解码,加上dp[i-1] 24 | * 的所有情况,如果对应数字和前一个数字能组成1到26,那么可以和前一个数字组成解码,加上dp[i-2] 25 | */ -------------------------------------------------------------------------------- /LinkedList/[E]141.Linked List Cycle.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for singly-linked list. 3 | * function ListNode(val) { 4 | * this.val = val; 5 | * this.next = null; 6 | * } 7 | */ 8 | 9 | /** 10 | * @param {ListNode} head 11 | * @return {boolean} 12 | */ 13 | var hasCycle = function(head) { 14 | if (!head) return false; 15 | let fast = head; 16 | let slow = head; 17 | while(1) { 18 | if (!fast.next || !fast.next.next) return false; 19 | fast = fast.next.next; 20 | slow = slow.next; 21 | if (fast === slow || fast.next === slow) return true; 22 | } 23 | }; 24 | 25 | /** 26 | * 判断链表是否成环,环题最先想到快慢指针,如果成环的话,快指针必定会追上慢指针。 27 | */ -------------------------------------------------------------------------------- /LinkedList/[E]160.Intersection of Two Linked Lists.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for singly-linked list. 3 | * function ListNode(val) { 4 | * this.val = val; 5 | * this.next = null; 6 | * } 7 | */ 8 | 9 | /** 10 | * @param {ListNode} headA 11 | * @param {ListNode} headB 12 | * @return {ListNode} 13 | */ 14 | var getIntersectionNode = function(headA, headB) { 15 | if (!headA || !headB) return null; 16 | let a = headA; 17 | let b = headB; 18 | while (a !== b) { 19 | a = a ? a.next : headB; 20 | b = b ? b.next : headA; 21 | } 22 | return a; 23 | }; 24 | 25 | /** 26 | * 虽然是简单题,但思路很巧妙。判断两个链表是否相交,那么让两个链表的末尾接上相对的首部,用两个指针 27 | * 遍历,如果相交,必然在相交点相遇,否则必然在空节点相遇。 28 | */ -------------------------------------------------------------------------------- /LinkedList/[E]203.Remove Linked List Elements.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for singly-linked list. 3 | * function ListNode(val) { 4 | * this.val = val; 5 | * this.next = null; 6 | * } 7 | */ 8 | /** 9 | * @param {ListNode} head 10 | * @param {number} val 11 | * @return {ListNode} 12 | */ 13 | var removeElements = function(head, val) { 14 | let dummy = { 15 | next: head 16 | } 17 | let pre = dummy; 18 | while (pre.next) { 19 | if (pre.next.val === val) { 20 | let cnt = pre.next; 21 | pre.next = cnt.next; 22 | cnt.next = null; 23 | delete t; 24 | } else { 25 | pre = pre.next; 26 | } 27 | } 28 | return dummy.next; 29 | }; 30 | 31 | /** 32 | * 移除指定值节点,那么还是需要两个指针,当后一指针命中移除值时,使用前一指针执行 33 | * 移除操作 34 | */ -------------------------------------------------------------------------------- /LinkedList/[E]206.Reverse Linked List.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for singly-linked list. 3 | * function ListNode(val) { 4 | * this.val = val; 5 | * this.next = null; 6 | * } 7 | */ 8 | /** 9 | * @param {ListNode} head 10 | * @return {ListNode} 11 | */ 12 | var reverseList = function(head) { 13 | let pre = null; 14 | while (head) { 15 | let tmp = head.next; 16 | head.next = pre; 17 | pre = head; 18 | head = tmp; 19 | } 20 | return pre; 21 | }; 22 | 23 | /** 24 | * 经典的基础反转链表题 25 | */ -------------------------------------------------------------------------------- /LinkedList/[E]21.Merge Two Sorted Lists.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for singly-linked list. 3 | * function ListNode(val) { 4 | * this.val = val; 5 | * this.next = null; 6 | * } 7 | */ 8 | /** 9 | * @param {ListNode} l1 10 | * @param {ListNode} l2 11 | * @return {ListNode} 12 | */ 13 | var mergeTwoLists = function(l1, l2) { 14 | if (!l1) return l2; 15 | if (!l2) return l1; 16 | if (l1.val > l2.val) { 17 | let temp = l2; 18 | l2.next = mergeTwoLists(l1, l2.next); 19 | return temp; 20 | } else { 21 | let temp = l1; 22 | l1.next = mergeTwoLists(l1.next, l2); 23 | return temp; 24 | } 25 | }; 26 | 27 | /** 28 | * 合并排序链表,比较常规的题,两个指针管理,遍历和递归都很容易理解,这里选择递归 29 | */ -------------------------------------------------------------------------------- /LinkedList/[E]234.Palindrome Linked List.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for singly-linked list. 3 | * function ListNode(val) { 4 | * this.val = val; 5 | * this.next = null; 6 | * } 7 | */ 8 | /** 9 | * @param {ListNode} head 10 | * @return {boolean} 11 | */ 12 | var isPalindrome = function(head) { 13 | if (!head || !head.next) return true; 14 | let slow = head; 15 | let fast = head; 16 | let stack = [head.val]; 17 | while (fast.next && fast.next.next) { 18 | slow = slow.next; 19 | fast = fast.next.next; 20 | stack.push(slow.val); 21 | } 22 | if (!fast.next) { 23 | stack.pop(); 24 | } 25 | while (slow.next) { 26 | slow = slow.next; 27 | if (slow.val !== stack.pop()) return false; 28 | } 29 | return true; 30 | }; 31 | 32 | /** 33 | * 判断是否是回文链表,由于链表不能类似数组一样索引,判断回文需要先找到中间节点。 34 | * 快慢指针同时前进,快指针走到末尾时,慢指针到中间。同时用一个栈存储前段值,再让慢 35 | * 指针遍历后段,并和栈内元素比较即可。 36 | */ -------------------------------------------------------------------------------- /LinkedList/[E]237.Delete Node in a Linked List.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for singly-linked list. 3 | * function ListNode(val) { 4 | * this.val = val; 5 | * this.next = null; 6 | * } 7 | */ 8 | /** 9 | * @param {ListNode} node 10 | * @return {void} Do not return anything, modify node in-place instead. 11 | */ 12 | var deleteNode = function(node) { 13 | node.val = node.next.val; 14 | node.next = node.next.next; 15 | }; 16 | 17 | /** 18 | * 由于只给了需要删除的节点,那么不能通过常规修改指针的形式删除,只能更新当前 19 | * 节点值为下一节点值,再删除下一节点。 20 | */ -------------------------------------------------------------------------------- /LinkedList/[E]83.Remove Duplicates from Sorted List.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for singly-linked list. 3 | * function ListNode(val) { 4 | * this.val = val; 5 | * this.next = null; 6 | * } 7 | */ 8 | /** 9 | * @param {ListNode} head 10 | * @return {ListNode} 11 | */ 12 | 13 | var deleteDuplicates = function(head) { 14 | if (!head) return null; 15 | let pre = head; 16 | let cur = head.next; 17 | while (cur !== null) { 18 | if (cur.val === pre.val) { 19 | pre.next = cur.next; 20 | } else { 21 | pre = cur; 22 | } 23 | cur = cur.next; 24 | } 25 | return head; 26 | }; 27 | 28 | /** 29 | * 移除排序后的重复节点,两个指针先后记录节点再比较即可。 30 | */ -------------------------------------------------------------------------------- /LinkedList/[H]23.Merge k Sorted Lists.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for singly-linked list. 3 | * function ListNode(val) { 4 | * this.val = val; 5 | * this.next = null; 6 | * } 7 | */ 8 | /** 9 | * @param {ListNode[]} lists 10 | * @return {ListNode} 11 | */ 12 | var mergeKLists = function(lists) { 13 | if (!lists.length) return null; 14 | let n = lists.length; 15 | while (n > 1) { 16 | let k = Math.floor((n + 1) / 2); 17 | for (let i = 0; i < Math.floor(n / 2); i++) { 18 | lists[i] = helper(lists[i], lists[i + k]); 19 | } 20 | n = k; 21 | } 22 | return lists[0]; 23 | }; 24 | 25 | function helper(l1, l2) { 26 | let dummy = { 27 | next: null 28 | }; 29 | let cur = dummy; 30 | while (l1 && l2) { 31 | if (l1.val < l2.val) { 32 | cur.next = l1; 33 | l1 = l1.next; 34 | } else { 35 | cur.next = l2; 36 | l2 = l2.next; 37 | } 38 | cur = cur.next; 39 | } 40 | if (l1) cur.next = l1; 41 | if (l2) cur.next = l2; 42 | return dummy.next; 43 | } 44 | 45 | /** 46 | * 这题要实现通用的合并k条链表方法,那么其实还是要一条条合并,可以用分治 47 | * 法减小时间复杂度。 48 | */ -------------------------------------------------------------------------------- /LinkedList/[H]25.Reverse Nodes in k-Group.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Definition for singly-linked list. 4 | * function ListNode(val) { 5 | * this.val = val; 6 | * this.next = null; 7 | * } 8 | */ 9 | /** 10 | * @param {ListNode} head 11 | * @param {number} k 12 | * @return {ListNode} 13 | */ 14 | var reverseKGroup = function(head, k) { 15 | if (!head || k === 1) return head; 16 | let dummy = { 17 | next: head 18 | }; 19 | let pre = dummy; 20 | let cur = head; 21 | let i = 0; 22 | while (cur) { 23 | i++; 24 | if (i % k === 0) { 25 | pre = reverse(pre, cur.next); 26 | cur = pre.next; 27 | } else { 28 | cur = cur.next; 29 | } 30 | } 31 | return dummy.next; 32 | 33 | }; 34 | 35 | function reverse(pre, next) { 36 | let last = pre.next; 37 | let cur = last.next; 38 | while (cur !== next) { 39 | last.next = cur.next; 40 | cur.next = pre.next; 41 | pre.next = cur; 42 | cur = last.next; 43 | } 44 | return last; 45 | } 46 | 47 | /** 48 | * 按k组分类反转链表,思路并不复杂,用一个变量记录当前遍历位置,在满足条件 49 | * 的起始位开始翻转,并在此之前找出终止位,一次翻转完毕后,终止位又变为起始位。 50 | * 遍历结束后即可得结果。 51 | */ -------------------------------------------------------------------------------- /LinkedList/[M]142.Linked List Cycle II.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for singly-linked list. 3 | * function ListNode(val) { 4 | * this.val = val; 5 | * this.next = null; 6 | * } 7 | */ 8 | /** 9 | * @param {ListNode} head 10 | * @return {ListNode} 11 | */ 12 | var detectCycle = function(head) { 13 | let slow = head; 14 | let fast = head; 15 | while (fast && fast.next) { 16 | slow = slow.next; 17 | fast = fast.next.next; 18 | if (slow === fast) break; 19 | } 20 | if (!fast || !fast.next) return null; 21 | slow = head; 22 | while (slow !== fast) { 23 | slow = slow.next; 24 | fast = fast.next; 25 | } 26 | return fast; 27 | }; 28 | 29 | /** 30 | * 这也是很经典的一道链表环题,找出链表的环起始处。 31 | * 用快慢指针分别遍历,当两者相遇时跳出循环。这时 32 | * 再将慢指针移到起点,和相遇处以共同速度遍历,再相遇即 33 | * 为环的起点。 34 | */ -------------------------------------------------------------------------------- /LinkedList/[M]143.Reorder List.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for singly-linked list. 3 | * function ListNode(val) { 4 | * this.val = val; 5 | * this.next = null; 6 | * } 7 | */ 8 | /** 9 | * @param {ListNode} head 10 | * @return {void} Do not return anything, modify head in-place instead. 11 | */ 12 | var reorderList = function(head) { 13 | if (!head || !head.next || !head.next.next) return; 14 | let fast = head; 15 | let slow = head; 16 | while (fast.next && fast.next.next) { 17 | slow = slow.next; 18 | fast = fast.next.next; 19 | } 20 | let mid = slow.next; 21 | slow.next = null; 22 | let last = mid; 23 | let pre = null; 24 | while (last) { 25 | let next = last.next; 26 | last.next = pre; 27 | pre = last; 28 | last = next; 29 | } 30 | while (head && pre) { 31 | let next = head.next; 32 | head.next = pre; 33 | pre = pre.next; 34 | head.next.next = next; 35 | head = next; 36 | } 37 | }; 38 | 39 | /**重排链表,这题其实可以看做三个小题的组合,一是快慢指针将其从中间断开, 40 | * 二是翻转断开后第二条链表,三是合并链表,都是之前做过的题 41 | */ -------------------------------------------------------------------------------- /LinkedList/[M]147.Insertion Sort List.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for singly-linked list. 3 | * function ListNode(val) { 4 | * this.val = val; 5 | * this.next = null; 6 | * } 7 | */ 8 | /** 9 | * @param {ListNode} head 10 | * @return {ListNode} 11 | */ 12 | var insertionSortList = function(head) { 13 | let dummy = { 14 | next: null 15 | }; 16 | let cur; 17 | while (head) { 18 | let t = head.next; 19 | cur = dummy; 20 | while (cur.next && cur.next.val <= head.val) { 21 | cur = cur.next; 22 | } 23 | head.next = cur.next; 24 | cur.next = head; 25 | head = t; 26 | } 27 | return dummy.next; 28 | }; 29 | 30 | /** 31 | * 插入排序的链表实现,比较容易,新建一条链表,遍历原链表每次取出头节点 32 | * 和新链表遍历比较插入即可 33 | */ -------------------------------------------------------------------------------- /LinkedList/[M]148.Sort List.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for singly-linked list. 3 | * function ListNode(val) { 4 | * this.val = val; 5 | * this.next = null; 6 | * } 7 | */ 8 | /** 9 | * @param {ListNode} head 10 | * @return {ListNode} 11 | */ 12 | var sortList = function(head) { 13 | if (!head || !head.next) return head; 14 | let slow = head; 15 | let fast = head; 16 | let pre = head; 17 | while (fast && fast.next) { 18 | pre = slow; 19 | slow = slow.next; 20 | fast = fast.next.next; 21 | } 22 | pre.next = null; 23 | return merge(sortList(head), sortList(slow)); 24 | }; 25 | 26 | function merge(l1, l2) { 27 | let dummy = { 28 | next: null 29 | }; 30 | let cur = dummy; 31 | while (l1 && l2) { 32 | if (l1.val < l2.val) { 33 | cur.next = l1; 34 | l1 = l1.next; 35 | } else { 36 | cur.next = l2; 37 | l2 = l2.next; 38 | } 39 | cur = cur.next; 40 | } 41 | if (l1) { 42 | cur.next = l1; 43 | } 44 | if (l2) { 45 | cur.next = l2; 46 | } 47 | return dummy.next; 48 | } 49 | 50 | /** 51 | * 排序链表,还要求O(nlogn)复杂度,那么首先考虑归并排序,链表形式的归并也比较容易实现。 52 | */ -------------------------------------------------------------------------------- /LinkedList/[M]19.Remove Nth Node From End of List.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for singly-linked list. 3 | * function ListNode(val) { 4 | * this.val = val; 5 | * this.next = null; 6 | * } 7 | */ 8 | /** 9 | * @param {ListNode} head 10 | * @param {number} n 11 | * @return {ListNode} 12 | */ 13 | var removeNthFromEnd = function(head, n) { 14 | if (!head.next) return null; 15 | let pre = head; 16 | let cur = head; 17 | for (let i = 0; i < n; i++) { 18 | cur = cur.next; 19 | } 20 | if (!cur) return head.next; 21 | while (cur.next) { 22 | cur = cur.next; 23 | pre = pre.next; 24 | } 25 | pre.next = pre.next.next; 26 | return head; 27 | }; 28 | 29 | /** 30 | * 移除倒数第n个节点,那么需要两个指针找到倒数第n个节点的前一节点,先让一个节点走n步, 31 | * 再让两个节点一起走,前一节点走到末尾时,后一节点即到倒数第n个节点的前一节点。注意边界 32 | * 条件处理。 33 | */ 34 | -------------------------------------------------------------------------------- /LinkedList/[M]2.Add Two Numbers.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for singly-linked list. 3 | * function ListNode(val) { 4 | * this.val = val; 5 | * this.next = null; 6 | * } 7 | */ 8 | /** 9 | * @param {ListNode} l1 10 | * @param {ListNode} l2 11 | * @return {ListNode} 12 | */ 13 | var addTwoNumbers = function(l1, l2) { 14 | let dummy = { 15 | next: null 16 | }; 17 | let cur = dummy; 18 | let left = l1; 19 | let right = l2; 20 | let lift = 0; 21 | 22 | while (left || right) { 23 | let leftVal = left ? left.val : 0; 24 | let rightVal = right ? right.val : 0; 25 | cur.next = new ListNode((leftVal + rightVal + lift) % 10); 26 | cur = cur.next; 27 | lift = Math.floor((leftVal + rightVal + lift) / 10); 28 | left = left ? left.next : null; 29 | right = right ? right.next : null; 30 | } 31 | if (lift) { 32 | cur.next = new ListNode(lift); 33 | } 34 | return dummy.next; 35 | }; 36 | 37 | /** 38 | * 2题解,两个指针分别管理左右两边当前参与计算值,注意处理进位问题 39 | */ -------------------------------------------------------------------------------- /LinkedList/[M]24.Swap Nodes in Pairs.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for singly-linked list. 3 | * function ListNode(val) { 4 | * this.val = val; 5 | * this.next = null; 6 | * } 7 | */ 8 | /** 9 | * @param {ListNode} head 10 | * @return {ListNode} 11 | */ 12 | var swapPairs = function(head) { 13 | if (!head || !head.next) return head; 14 | let cnt = head.next; 15 | head.next = swapPairs(head.next.next); 16 | cnt.next = head; 17 | return cnt; 18 | }; 19 | /** 20 | * 成对的交换节点,这种题首先想到递归,每两个节点一组,头节点指向下一组交换后的头结点, 21 | * 头结点的下一节点指向头结点即完成交换。 22 | */ -------------------------------------------------------------------------------- /LinkedList/[M]328.Odd Even Linked List.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for singly-linked list. 3 | * function ListNode(val) { 4 | * this.val = val; 5 | * this.next = null; 6 | * } 7 | */ 8 | /** 9 | * @param {ListNode} head 10 | * @return {ListNode} 11 | */ 12 | var oddEvenList = function(head) { 13 | if (!head || !head.next) return head; 14 | let odd = head; 15 | let even = head.next; 16 | while (even && even.next) { 17 | let tmp = odd.next; 18 | odd.next = even.next; 19 | even.next = odd.next.next; 20 | odd.next.next = tmp; 21 | even = even.next; 22 | odd = odd.next; 23 | } 24 | return head; 25 | }; 26 | 27 | /** 28 | * 分离奇偶节点,那么需要两个指针分别管理,每次交换后让两个指针分别指向当前奇节点末尾和未交换偶节点首部。 29 | */ -------------------------------------------------------------------------------- /LinkedList/[M]61.Rotate List.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for singly-linked list. 3 | * function ListNode(val) { 4 | * this.val = val; 5 | * this.next = null; 6 | * } 7 | */ 8 | /** 9 | * @param {ListNode} head 10 | * @param {number} k 11 | * @return {ListNode} 12 | */ 13 | var rotateRight = function(head, k) { 14 | if (!head) return null; 15 | let n = 1; 16 | let cur = head; 17 | while (cur.next) { 18 | n++; 19 | cur = cur.next; 20 | } 21 | cur.next = head; 22 | let m = n - k % n; 23 | for (let i = 0; i < m; i++) { 24 | cur = cur.next; 25 | } 26 | let newHead = cur.next; 27 | cur.next = null; 28 | return newHead; 29 | }; 30 | 31 | /** 32 | * 旋转链表题,首先可以考虑先将链表结成环,然后找出旋转点,从其中断开即可 33 | */ -------------------------------------------------------------------------------- /LinkedList/[M]817.Linked List Components.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for singly-linked list. 3 | * function ListNode(val) { 4 | * this.val = val; 5 | * this.next = null; 6 | * } 7 | */ 8 | /** 9 | * @param {ListNode} head 10 | * @param {number[]} G 11 | * @return {number} 12 | */ 13 | var numComponents = function(head, G) { 14 | if (!head) return 0; 15 | let res = 0; 16 | let flag = false; 17 | while (head) { 18 | if (G.includes(head.val)) { 19 | if (!flag) { 20 | res++; 21 | flag = true; 22 | } 23 | } else { 24 | flag = false; 25 | } 26 | head = head.next; 27 | } 28 | return res; 29 | }; 30 | 31 | /** 32 | * 检测数组G中的元素能组成链表的几部分,那么用一个标记变量flag代表处于一部分的计算中。 33 | * 遍历链表,当G有链表元素时标记设置为true,直到G中没有链表元素时标记设置为false,完成一段计算 34 | */ -------------------------------------------------------------------------------- /LinkedList/[M]82.Remove Duplicates from Sorted List II.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for singly-linked list. 3 | * function ListNode(val) { 4 | * this.val = val; 5 | * this.next = null; 6 | * } 7 | */ 8 | /** 9 | * @param {ListNode} head 10 | * @return {ListNode} 11 | */ 12 | var deleteDuplicates = function(head) { 13 | if (!head || !head.next) return head; 14 | let dummy = { 15 | next: head 16 | }; 17 | let pre = dummy; 18 | while (pre.next) { 19 | let cur = pre.next; 20 | while (cur.next && cur.next.val === cur.val) { 21 | cur = cur.next; 22 | } 23 | if (cur !== pre.next) { 24 | pre.next = cur.next; 25 | } else { 26 | pre = pre.next; 27 | } 28 | } 29 | return dummy.next; 30 | }; 31 | 32 | /** 33 | * 83的变种,需要移除所有重复过的节点,主要思路还是用两个指针来记录比较,多考虑一种情况即可。 34 | */ -------------------------------------------------------------------------------- /LinkedList/[M]86.Partition List.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for singly-linked list. 3 | * function ListNode(val) { 4 | * this.val = val; 5 | * this.next = null; 6 | * } 7 | */ 8 | /** 9 | * @param {ListNode} head 10 | * @param {number} x 11 | * @return {ListNode} 12 | */ 13 | var partition = function(head, x) { 14 | if (!head) return head; 15 | let dummy = { 16 | next: head 17 | }; 18 | let newDummy = { 19 | next: null 20 | }; 21 | let cur = dummy; 22 | let p = newDummy; 23 | while (cur.next) { 24 | if (cur.next.val < x) { 25 | p.next = cur.next; 26 | p = p.next; 27 | cur.next = cur.next.next; 28 | p.next = null; 29 | } else { 30 | cur = cur.next; 31 | } 32 | } 33 | p.next = dummy.next; 34 | return newDummy.next; 35 | }; 36 | 37 | /** 38 | * 这题要求局部排序链表,大于指定值的节点顺序不变,小于的要移到其前面。那么只要 39 | * 找到第一个大于或等于指定值的节点,再遍历链表,将小于其的节点置于该节点前即可。 40 | */ -------------------------------------------------------------------------------- /LinkedList/[M]92.Reverse Linked List II.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for singly-linked list. 3 | * function ListNode(val) { 4 | * this.val = val; 5 | * this.next = null; 6 | * } 7 | */ 8 | /** 9 | * @param {ListNode} head 10 | * @param {number} m 11 | * @param {number} n 12 | * @return {ListNode} 13 | */ 14 | var reverseBetween = function(head, m, n) { 15 | let dummy = {next: head}; 16 | let start = dummy; 17 | while (--m) { 18 | start = start.next; 19 | n--; 20 | } 21 | let cur = start.next; 22 | while (--n) { 23 | let tmp = cur.next; 24 | cur.next = tmp.next; 25 | tmp.next = start.next; 26 | start.next = tmp; 27 | } 28 | return dummy.next; 29 | }; 30 | 31 | /** 32 | * 翻转部分链表,那么原理和翻转链表是一样的,找好起始和结束位置即可。 33 | */ -------------------------------------------------------------------------------- /Stack/[E]155.Min Stack.js: -------------------------------------------------------------------------------- 1 | /** 2 | * initialize your data structure here. 3 | */ 4 | var MinStack = function() { 5 | this.arr = []; 6 | }; 7 | 8 | MinStack.createNew = function() { 9 | return new MinStack(); 10 | } 11 | 12 | /** 13 | * @param {number} x 14 | * @return {void} 15 | */ 16 | MinStack.prototype.push = function(x) { 17 | this.arr.push(x); 18 | }; 19 | 20 | /** 21 | * @return {void} 22 | */ 23 | MinStack.prototype.pop = function() { 24 | this.arr.pop(); 25 | }; 26 | 27 | /** 28 | * @return {number} 29 | */ 30 | MinStack.prototype.top = function() { 31 | return this.arr[this.arr.length - 1]; 32 | }; 33 | 34 | /** 35 | * @return {number} 36 | */ 37 | MinStack.prototype.getMin = function() { 38 | return Math.min(...this.arr); 39 | }; 40 | 41 | /** 42 | * Your MinStack object will be instantiated and called as such: 43 | * var obj = Object.create(MinStack).createNew() 44 | * obj.push(x) 45 | * obj.pop() 46 | * var param_3 = obj.top() 47 | * var param_4 = obj.getMin() 48 | */ 49 | 50 | /** 51 | * 简单题,构造出求数组最小值方法,用math内置函数解决 52 | */ -------------------------------------------------------------------------------- /Stack/[E]225.Implement Stack using Queues.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Initialize your data structure here. 3 | */ 4 | var MyStack = function() { 5 | this.arr = []; 6 | }; 7 | 8 | MyStack.createNew = function() { 9 | return new MyStack(); 10 | } 11 | 12 | /** 13 | * Push element x onto stack. 14 | * @param {number} x 15 | * @return {void} 16 | */ 17 | MyStack.prototype.push = function(x) { 18 | this.arr.unshift(x); 19 | for (let i = this.arr.length - 1; i > 0;i--) { 20 | this.arr.unshift(this.arr.pop()); 21 | } 22 | }; 23 | 24 | /** 25 | * Removes the element on top of the stack and returns that element. 26 | * @return {number} 27 | */ 28 | MyStack.prototype.pop = function() { 29 | return this.arr.pop(); 30 | }; 31 | 32 | /** 33 | * Get the top element. 34 | * @return {number} 35 | */ 36 | MyStack.prototype.top = function() { 37 | return this.arr[this.arr.length - 1] 38 | }; 39 | 40 | /** 41 | * Returns whether the stack is empty. 42 | * @return {boolean} 43 | */ 44 | MyStack.prototype.empty = function() { 45 | return !this.arr.length 46 | }; 47 | 48 | /** 49 | * Your MyStack object will be instantiated and called as such: 50 | * var obj = Object.create(MyStack).createNew() 51 | * obj.push(x) 52 | * var param_2 = obj.pop() 53 | * var param_3 = obj.top() 54 | * var param_4 = obj.empty() 55 | */ 56 | 57 | /** 58 | * 和232相反,用队列实现栈,同样修改push方法,但这题不需要额外开辟数组,队列剩余元素出队再入队即可 59 | */ -------------------------------------------------------------------------------- /Stack/[E]232.Implement Queue using Stacks.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Initialize your data structure here. 3 | */ 4 | var MyQueue = function() { 5 | this.store = []; 6 | this.temp = []; 7 | }; 8 | 9 | /** 10 | * Push element x to the back of queue. 11 | * @param {number} x 12 | * @return {void} 13 | */ 14 | MyQueue.prototype.push = function(x) { 15 | while (this.store.length) { 16 | this.temp.push(this.store.pop()); 17 | } 18 | this.store.push(x); 19 | while (this.temp.length) { 20 | this.store.push(this.temp.pop()); 21 | } 22 | }; 23 | 24 | /** 25 | * Removes the element from in front of queue and returns that element. 26 | * @return {number} 27 | */ 28 | MyQueue.prototype.pop = function() { 29 | return this.store.pop(); 30 | }; 31 | 32 | /** 33 | * Get the front element. 34 | * @return {number} 35 | */ 36 | MyQueue.prototype.peek = function() { 37 | return this.store[this.store.length - 1]; 38 | }; 39 | 40 | /** 41 | * Returns whether the queue is empty. 42 | * @return {boolean} 43 | */ 44 | MyQueue.prototype.empty = function() { 45 | return this.store.length === 0; 46 | }; 47 | 48 | /** 49 | * Your MyQueue object will be instantiated and called as such: 50 | * var obj = Object.create(MyQueue).createNew() 51 | * obj.push(x) 52 | * var param_2 = obj.pop() 53 | * var param_3 = obj.peek() 54 | * var param_4 = obj.empty() 55 | */ 56 | 57 | /** 58 | * 用栈实现队列,需要一个辅助栈在push临时存储所有元素,保证push时的元素能顺利保存到栈底, 59 | * 再将辅助栈内的元素pop回来。 60 | */ -------------------------------------------------------------------------------- /Stack/[E]682.Baseball Game.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string[]} ops 3 | * @return {number} 4 | */ 5 | var calPoints = function(ops) { 6 | let total = []; 7 | ops.forEach((item, index) => { 8 | if (item === 'C') { 9 | total.pop(); 10 | } 11 | if (item === 'D') { 12 | total.push(total[total.length - 1] * 2); 13 | } 14 | if (item === '+' ) { 15 | total.push(total[total.length - 2] + total[total.length - 1]); 16 | } 17 | if (!Number.isNaN(+item)) { 18 | total.push(+item); 19 | } 20 | }) 21 | return total.reduce((a, b) => a + b); 22 | }; 23 | 24 | /** 25 | * 使用栈按题意规则遍历积分,遍历完成用reduce求和 26 | */ -------------------------------------------------------------------------------- /Stack/[H]224.Basic Calculator.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} s 3 | * @return {number} 4 | */ 5 | var calculate = function(s) { 6 | let res = 0; 7 | let sign = 1; 8 | let n = s.length; 9 | let stack = []; 10 | for (let i = 0; i < n; i++) { 11 | let c = s[i]; 12 | if (c >= 0) { 13 | let num = ''; 14 | while (i < n && s[i] >= 0) { 15 | num += s[i++]; 16 | } 17 | 18 | res += sign * num; 19 | i--; 20 | } else if (c === '+') { 21 | sign = 1; 22 | } else if (c === '-') { 23 | sign = -1; 24 | } else if (c === '(') { 25 | stack.push(res); 26 | stack.push(sign); 27 | res = 0; 28 | sign = 1; 29 | } else if (c === ')') { 30 | res *= stack.pop(); 31 | res += stack.pop(); 32 | } 33 | 34 | } 35 | return res; 36 | }; 37 | 38 | /** 39 | * 实现计算器,写过计算器相关业务的同学应该能第一时间想到栈,这题比较简单, 40 | * 只需要实现加减法和括号,就没有优先级问题,遍历遇到数字可以立刻加到总数中, 41 | * 遇到左括号就可以用栈先保存当前总数和当前运算的符号,再对括号内的数字进行 42 | * 总数计算,遇到右括号就可以根据栈中符号和总数得到新的总数。 43 | */ -------------------------------------------------------------------------------- /Stack/[H]84.Largest Rectangle in Histogram.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} heights 3 | * @return {number} 4 | */ 5 | var largestRectangleArea = function(heights) { 6 | let res = 0; 7 | let stack = []; 8 | heights.push(0); 9 | for (let i = 0; i < heights.length; i++) { 10 | if (!stack.length || heights[stack[stack.length - 1]] < heights[i]) { 11 | stack.push(i); 12 | } else { 13 | let cur = stack.pop(); 14 | res = Math.max(res, heights[cur] * (stack.length ? (i - stack[stack.length - 1] - 1) : i)); 15 | i--; 16 | } 17 | } 18 | return res; 19 | }; 20 | 21 | /** 22 | * 只能想到暴力解法,但肯定是TLE的,查阅了大神的答案,用到了递增栈减少计算次数,因为当递增栈遇到一个小于 23 | * 栈顶的元素时,这个元素与递增栈内围成的面积肯定不如栈内第一个比他大的元素围成的面积大,那么就可以弹栈直到 24 | * 第一个比当前元素大的位置,并计算弹栈过程中围成的面积参与极值比较,再将当前元素推入递增栈继续循环,这样就 25 | * 可以减少多余的计算量。 26 | * 27 | */ -------------------------------------------------------------------------------- /Stack/[M]150.Evaluate Reverse Polish Notation.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string[]} tokens 3 | * @return {number} 4 | */ 5 | var evalRPN = function(tokens) { 6 | let stack = []; 7 | for (let i = 0; i < tokens.length; i++) { 8 | if (Number.isInteger(+tokens[i])) { 9 | stack.push(tokens[i]); 10 | } else { 11 | let temp2 = stack.pop(); 12 | let temp1 = stack.pop(); 13 | switch(tokens[i]) 14 | { 15 | case '+': 16 | stack.push(+temp1 + +temp2); 17 | break; 18 | case '-': 19 | stack.push(+temp1 - +temp2); 20 | break; 21 | case '*': 22 | stack.push(+temp1 * +temp2); 23 | break; 24 | case '/': 25 | stack.push(~~(+temp1 / +temp2)); 26 | break; 27 | } 28 | } 29 | } 30 | return +stack[0]; 31 | }; 32 | 33 | /** 34 | * 用栈记录运算数,当遇到运算符号时弹出两个元素计算再入栈 35 | */ -------------------------------------------------------------------------------- /Stack/[M]215.Kth Largest Element in an Array.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} nums 3 | * @param {number} k 4 | * @return {number} 5 | */ 6 | var findKthLargest = function(nums, k) { 7 | let left = 0; 8 | let right = nums.length - 1; 9 | while (true) { 10 | let pos = helper(nums, left, right); 11 | if (pos === k - 1) { 12 | return nums[pos]; 13 | } else if (pos > k - 1) { 14 | right = pos - 1; 15 | } else { 16 | left = pos + 1; 17 | } 18 | 19 | } 20 | }; 21 | 22 | function helper (nums, left, right) { 23 | let pivot = nums[left]; 24 | let l = left + 1; 25 | let r = right; 26 | 27 | while (l <= r) { 28 | if (nums[l] < pivot && nums[r] > pivot) { 29 | swap(nums, l++, r--); 30 | } 31 | if (nums[l] >= pivot) l++; 32 | if (nums[r] <= pivot) r--; 33 | } 34 | swap(nums, left, r); 35 | return r; 36 | } 37 | 38 | function swap(nums, a, b) { 39 | let temp = nums[a]; 40 | nums[a] = nums[b]; 41 | nums[b] = temp; 42 | } 43 | 44 | /** 45 | * 找到数组中第k大的数,最简单的直接用内置方法排序取k-1索引就好了。但显然这题目的 46 | * 还是要手动实现排序,这里用了快速排序。选取一个中枢点,遍历数组,比其大的放左边,小的放右边。 47 | * 完成后检查中枢点索引,如果大于k-1,说明目标点在左边,继续对左边元素做快排,反之对右边元素快排。 48 | * 当中枢点索引正好等于k-1时得到结果。 49 | */ -------------------------------------------------------------------------------- /Stack/[M]227.Basic Calculator II.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} s 3 | * @return {number} 4 | */ 5 | function calculate(s) { 6 | s = s.replace(/\s/g, ''); 7 | 8 | let stack = []; 9 | let num = 0; 10 | let sign = '+'; 11 | 12 | for (let i = 0; i < s.length; i++) { 13 | const c = s[i]; 14 | 15 | if (/\d/.test(c)) { 16 | num = num * 10 + +c; 17 | } 18 | 19 | if ((/\D/.test(c)) || i === s.length - 1) { 20 | if (sign === '-') stack.push(-num); 21 | if (sign === '+') stack.push(num); 22 | if (sign === '*') stack.push(stack.pop() * num); 23 | if (sign === '/') stack.push(~~(stack.pop() / num)); 24 | 25 | sign = c; 26 | num = 0; 27 | } 28 | } 29 | 30 | return stack.reduce((a, b) => a + b); 31 | } 32 | 33 | /** 34 | * 和224同样实现计算器,区别在于去掉了括号,新增了乘除,那么就有运算优先级的问题, 35 | * 遇到数字不能直接计算到总数,需要考虑之前的符号,如果是加减就先保存到栈中,如果是 36 | * 乘除就是高优先级,可以直接计算。遍历完成后,栈内只剩下加减的元素,再reduce计算即可。 37 | */ -------------------------------------------------------------------------------- /Stack/[M]332.Reconstruct Ltinerary.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string[][]} tickets 3 | * @return {string[]} 4 | */ 5 | var findItinerary = function(tickets) { 6 | let count = 0; 7 | let map = tickets.reduce((map, item) => { 8 | if (map.get(item[0])) { 9 | let val = map.get(item[0]); 10 | val.push({to: item[1], visited: false}); 11 | map.set(item[0], val); 12 | } else { 13 | map.set(item[0], [{to: item[1], visited: false}]); 14 | } 15 | return map; 16 | }, new Map()); 17 | for (let item of map) { 18 | map.set(item[0], item[1].sort((a, b) => { 19 | let i = 0; 20 | while (a.to.charCodeAt(i) - b.to.charCodeAt(i) === 0) { 21 | i++; 22 | } 23 | return a.to.charCodeAt(i) - b.to.charCodeAt(i); 24 | })); 25 | } 26 | 27 | return (function dfs(from, path) { 28 | if (count === tickets.length) return path; 29 | let cur = map.get(from); 30 | if (!cur) return false; 31 | let res; 32 | for (let i = 0; i < cur.length; i++) { 33 | if (cur[i].visited) continue; 34 | cur[i].visited = true; 35 | count++; 36 | path.push(cur[i].to); 37 | res = dfs(cur[i].to, path); 38 | if (res) return res; 39 | cur[i].visited = false; 40 | count--; 41 | path.pop(); 42 | } 43 | return false; 44 | })('JFK', ['JFK']); 45 | }; 46 | 47 | /** 48 | * 要头尾相连重组数组,思路不复杂就是繁琐,第一步做起点到终点的映射,并用一个visited变量记录是否 49 | * 到达过,当起点有多个终点时用数组记录并排序。 50 | * 第二步从起点开始深度遍历找终点并记录到path数组,遍历到的元素也更改visited变量为true,同时用count 51 | * 记录遍历数量,当count等于原数组长度时则完成重组。 52 | */ -------------------------------------------------------------------------------- /Stack/[M]341.Flatten Nested list Iterator.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * // This is the interface that allows for creating nested lists. 4 | * // You should not implement it, or speculate about its implementation 5 | * function NestedInteger() { 6 | * 7 | * Return true if this NestedInteger holds a single integer, rather than a nested list. 8 | * @return {boolean} 9 | * this.isInteger = function() { 10 | * ... 11 | * }; 12 | * 13 | * Return the single integer that this NestedInteger holds, if it holds a single integer 14 | * Return null if this NestedInteger holds a nested list 15 | * @return {integer} 16 | * this.getInteger = function() { 17 | * ... 18 | * }; 19 | * 20 | * Return the nested list that this NestedInteger holds, if it holds a nested list 21 | * Return null if this NestedInteger holds a single integer 22 | * @return {NestedInteger[]} 23 | * this.getList = function() { 24 | * ... 25 | * }; 26 | * }; 27 | */ 28 | /** 29 | * @constructor 30 | * @param {NestedInteger[]} nestedList 31 | */ 32 | var NestedIterator = function(nestedList) { 33 | this.stack = Array.from(nestedList).reverse(); 34 | }; 35 | 36 | 37 | /** 38 | * @this NestedIterator 39 | * @returns {boolean} 40 | */ 41 | NestedIterator.prototype.hasNext = function() { 42 | while(this.stack.length) { 43 | let cnt = this.stack[this.stack.length - 1]; 44 | if (cnt.isInteger()) return true; 45 | this.stack.pop(); 46 | for (let i = cnt.getList().length - 1; i >= 0; i--) { 47 | this.stack.push(cnt.getList()[i]); 48 | } 49 | } 50 | return false; 51 | }; 52 | 53 | /** 54 | * @this NestedIterator 55 | * @returns {integer} 56 | */ 57 | NestedIterator.prototype.next = function() { 58 | return this.stack.pop().getInteger(); 59 | }; 60 | 61 | /** 62 | * Your NestedIterator will be called like this: 63 | * var i = new NestedIterator(nestedList), a = []; 64 | * while (i.hasNext()) a.push(i.next()); 65 | */ 66 | 67 | /** 68 | * 用迭代器打平数组,那么每次检测是否有下一个元素时,遇到嵌套数组就展开,注意展开的方向即可。 69 | */ -------------------------------------------------------------------------------- /Stack/[M]347.Top K Frequent Elements.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} nums 3 | * @param {number} k 4 | * @return {number[]} 5 | */ 6 | var topKFrequent = function(nums, k) { 7 | const map = new Map(); 8 | for (let item of nums) { 9 | map.has(item) ? map.set(item, map.get(item) + 1) : map.set(item, 1); 10 | } 11 | let arr = Array.from(map); 12 | arr.sort((a, b) => b[1] - a[1]); 13 | return arr.slice(0, k).map(item => item[0]); 14 | }; 15 | 16 | /** 17 | * 返回前k频繁的数,直接想到map记录出现次数,再根据出现次数排序截取k位即可。 18 | */ -------------------------------------------------------------------------------- /Stack/[M]388.Longest Absolute File Path.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} input 3 | * @return {number} 4 | */ 5 | var lengthLongestPath = function(input) { 6 | let map = new Map(); 7 | map.set(0, 0); 8 | let res = 0; 9 | let level = 0; 10 | for (let i = 0; i < input.length; i++) { 11 | let start = i; 12 | while (i < input.length && input[i] !== '\n' && input[i] !== '\t') { 13 | i++; 14 | } 15 | if (input[i] === '\n' || i >= input.length) { 16 | let cnt = input.slice(start, i); 17 | if (cnt.includes('.')) { 18 | res = Math.max(map.get(level) + cnt.length, res); 19 | } else { 20 | level++; 21 | map.set(level, map.get(level - 1) + cnt.length + 1); 22 | } 23 | level = 0; 24 | } else { 25 | level++; 26 | } 27 | } 28 | return res; 29 | }; 30 | 31 | /** 32 | * 用map记录每层深度,遍历原字符串,当遇到文件时,根据当前层级深度更新最大深度。 33 | * 当遇到文件夹时,层级加一,并更新上层深度。 34 | */ -------------------------------------------------------------------------------- /Stack/[M]394.Decode String.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} s 3 | * @return {string} 4 | */ 5 | var decodeString = function(s) { 6 | let res = ''; 7 | let stack = []; 8 | let num = ''; 9 | for (let i = 0; i < s.length; i++) { 10 | if (!stack.length && /[a-z]/i.test(s[i])) { 11 | res += s[i]; 12 | continue; 13 | } 14 | if (!stack.length && Number.isInteger(+s[i])) { 15 | num += s[i]; 16 | continue; 17 | } 18 | if (s[i] === '[') { 19 | stack.push(i + 1); 20 | continue; 21 | } 22 | if (s[i] === ']') { 23 | if (stack.length === 1) { 24 | res += decodeString(s.slice(stack[0], i)).repeat(+num); 25 | num = ''; 26 | } 27 | stack.pop(); 28 | } 29 | } 30 | return res; 31 | }; 32 | 33 | /** 34 | * 嵌套型的解码题,第一时间想到用递归解,遍历原字符串,用num记录每次递归编码串的重复次数, 35 | * 遇到左括号时用栈记录起始位置,遇到右括号就知道编码长度,可以递归解码了。 36 | */ -------------------------------------------------------------------------------- /Stack/[M]402.Remove K Digits.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} num 3 | * @param {number} k 4 | * @return {string} 5 | */ 6 | 7 | var removeKdigits = function(num, k) { 8 | let stack = [], numDigits = num.length; 9 | for (let i = 0; i < numDigits; i++) { 10 | while(k > 0 && stack.length && stack[stack.length - 1] > num[i]) { 11 | stack.pop(); 12 | k--; 13 | } 14 | stack.push(num[i]); 15 | } 16 | stack = k > 0 ? stack.slice(0, -k) : stack; 17 | return stack.join('').replace(/^0+/, '') || '0'; 18 | }; 19 | 20 | /** 21 | * 移除k个字符让给定数字尽可能小,则需要保证前面的位数尽可能小,递减栈解决。如果遍历完成已经是最佳 22 | * 排序,还有k次没有使用,则移除栈尾的k个数字。 23 | */ -------------------------------------------------------------------------------- /Stack/[M]503.Next Greater Element II.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} nums 3 | * @return {number[]} 4 | */ 5 | var nextGreaterElements = function(nums) { 6 | let n = nums.length; 7 | let res = Array.from({length: n}, (_) => -1); 8 | let stack = []; 9 | for (let i = 0; i < 2 * n; i++) { 10 | let num = nums[i % n]; 11 | while(stack.length && nums[stack[stack.length - 1]] < num) { 12 | res[stack[stack.length - 1]] = num; 13 | stack.pop(); 14 | } 15 | if (i < n) stack.push(i); 16 | } 17 | return res; 18 | }; 19 | 20 | 21 | /** 22 | * 找到后面比当前元素大的第一个元素。和后面元素比较,除了暴力破解外,最先考虑递减栈。这题由于 23 | * 考虑循环,遍历长度要翻倍,取值要长度的余数。遇到比栈顶大的元素时就可以得栈顶对应元素位置的所求。 24 | * 对于遍历到长度超过原数组的部分,只需要提供比较数值而不需要重复求解,就不压入递减栈。 25 | */ -------------------------------------------------------------------------------- /Stack/[M]71.Simplify Path.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} path 3 | * @return {string} 4 | */ 5 | var simplifyPath = function(path) { 6 | let arr = path.split('/').filter(item => item); 7 | let stack = []; 8 | for (let i = 0; i < arr.length; i++) { 9 | if (/[a-z]/i.test(arr[i])) { 10 | stack.push(arr[i]); 11 | } 12 | if (arr[i] === '...') { 13 | stack.push(arr[i]); 14 | } 15 | if (arr[i] === '..') { 16 | stack.pop(); 17 | } 18 | } 19 | return '/' + stack.join('/'); 20 | }; 21 | 22 | /** 23 | * 转化文件路径,需要特殊处理的路径就是..,用栈记录当前深度,遇到..时出栈 24 | */ -------------------------------------------------------------------------------- /Stack/[M]739.Daily Temperatures.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number[]} temperatures 3 | * @return {number[]} 4 | */ 5 | var dailyTemperatures = function(temperatures) { 6 | let n = temperatures.length; 7 | let res = Array.from({length: n}, _ => 0); 8 | let stack = []; 9 | for (let i = 0; i < n; ++i) { 10 | while (stack.length && temperatures[i] > temperatures[stack[stack.length - 1]]) { 11 | let idx = stack.pop(); 12 | res[idx] = i - idx; 13 | } 14 | stack.push(i); 15 | } 16 | return res; 17 | }; 18 | 19 | /** 20 | * 求每个位置元素距离其前方比他大的元素的位置距离,递减栈解决,遍历遇到第一个比栈顶大的元素时 21 | * 就可以算出栈顶位置的元素所需结果。 22 | */ -------------------------------------------------------------------------------- /String/[E]125.Valid Palindrome.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} s 3 | * @return {boolean} 4 | */ 5 | var isPalindrome = function(s) { 6 | s = s.toLowerCase().split('').filter(item => /[a-z0-9]/i.test(item)); 7 | return s.join('') === s.reverse().join('') 8 | }; 9 | 10 | /** 11 | * 验证回文串,直接偷懒用js内置方法加正则解了。常规思路应该是头尾指针遍历并过滤非法字符。 12 | */ -------------------------------------------------------------------------------- /String/[E]13.Roman to Integer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} s 3 | * @return {number} 4 | */ 5 | var romanToInt = function(s) { 6 | let res = 0; 7 | const map = { 8 | 'I': 1, 9 | 'V': 5, 10 | 'X': 10, 11 | 'L': 50, 12 | 'C': 100, 13 | 'D': 500, 14 | 'M': 1000 15 | }; 16 | for (let i = 0; i < s.length; i++) { 17 | let val = map[s[i]]; 18 | if (i === s.length - 1 || map[s[i + 1]] <= map[s[i]]) { 19 | res += val; 20 | } else { 21 | res -= val; 22 | } 23 | } 24 | return res; 25 | }; 26 | 27 | /** 28 | * 简单题,罗马字符和数值建立映射,遍历后根据每个罗马字符的相对位置决定增或减 29 | */ -------------------------------------------------------------------------------- /String/[E]14.Longest Common Prefix.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string[]} strs 3 | * @return {string} 4 | */ 5 | var longestCommonPrefix = function(strs) { 6 | if (!strs.length) return ''; 7 | let res = ''; 8 | for (let i = 0; i < strs[0].length; i++) { 9 | let s = strs[0][i]; 10 | for (let j = 1; j < strs.length; j++) { 11 | if (i >= strs[j].length || strs[j][i] !== s) { 12 | return res; 13 | } 14 | } 15 | res += s; 16 | console.log(res) 17 | } 18 | return res; 19 | }; 20 | 21 | /** 22 | * 简单题,按照题意转化为代码即可,取第一个元素遍历检测并拓展common,当超过第一个元素长度或者有不相等时, 23 | * 返回当前common 24 | */ -------------------------------------------------------------------------------- /String/[E]168.Excel Sheet Column Title.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number} n 3 | * @return {string} 4 | */ 5 | 6 | var convertToTitle = function (n) { 7 | return n === 0 ? '' : convertToTitle(Math.floor((n - 1) / 26)) + String.fromCharCode(--n % 26 + 0x41); 8 | }; 9 | 10 | /** 11 | * 简单常规的进制转化类型题,递归一行解决。 12 | */ -------------------------------------------------------------------------------- /String/[E]171.Excel Sheet Column Number.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} s 3 | * @return {number} 4 | */ 5 | var titleToNumber = function(s) { 6 | let total = 0; 7 | for (let i = s.length - 1; i >= 0; i--) { 8 | total += Math.pow(26, s.length - 1 - i) * (s.charCodeAt(i) - 0x40); 9 | } 10 | return total; 11 | }; 12 | 13 | /** 14 | * 168的翻转,简单进制转化 15 | */ -------------------------------------------------------------------------------- /String/[E]20.Valid Parentheses.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} s 3 | * @return {boolean} 4 | */ 5 | var isValid = function(s) { 6 | let stack = []; 7 | for (let i = 0; i < s.length; i++) { 8 | if (s[i] === '(' || s[i] === '[' || s[i] === '{') { 9 | stack.push(s[i]); 10 | } else { 11 | if (!stack.length) return false; 12 | if (s[i] === ')' && stack[stack.length - 1] !== '(') return false; 13 | if (s[i] === ']' && stack[stack.length - 1] !== '[') return false; 14 | if (s[i] === '}' && stack[stack.length - 1] !== '{') return false; 15 | stack.pop(); 16 | } 17 | } 18 | return !stack.length; 19 | }; 20 | 21 | /** 22 | * 判断是否为有效括号,括号题首先考虑用栈解,这题常规思路,按题意遍历检查即可 23 | */ -------------------------------------------------------------------------------- /String/[E]205.Isomorphic Strings.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} s 3 | * @param {string} t 4 | * @return {boolean} 5 | */ 6 | var isIsomorphic = function(s, t) { 7 | if (s.length !== t.length) return false; 8 | let map = new Map(); 9 | let set = new Set(); 10 | for (let i = 0; i < s.length; i++) { 11 | if (!map.has(s[i]) && !set.has(t[i])) { 12 | map.set(s[i], t[i]); 13 | set.add(t[i]); 14 | } 15 | if (!map.has(s[i]) && set.has(t[i])) return false; 16 | if (map.get(s[i]) !== t[i]) { 17 | return false; 18 | } 19 | } 20 | return true; 21 | }; 22 | 23 | /** 24 | * 用map做映射记录,用set记录映射过的值,按题意转化为代码即可 25 | */ -------------------------------------------------------------------------------- /String/[E]242.Valid Anagram.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} s 3 | * @param {string} t 4 | * @return {boolean} 5 | */ 6 | var isAnagram = function(s, t) { 7 | if (s.length !== t.length) return false; 8 | let map = s.split('').reduce((map, item) => map.has(item) ? map.set(item, map.get(item) + 1) : map.set(item, 1),new Map()); 9 | for (let i = 0; i < t.length; i++) { 10 | if (map.get(t[i]) >= 1) { 11 | map.set(t[i], map.get(t[i]) - 1); 12 | } else return false; 13 | } 14 | return true; 15 | }; 16 | 17 | /** 18 | * 和383类似,用map记录即可 19 | */ -------------------------------------------------------------------------------- /String/[E]28.Implement strStr().js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} haystack 3 | * @param {string} needle 4 | * @return {number} 5 | */ 6 | var strStr = function(haystack, needle) { 7 | let exp = new RegExp(needle); 8 | return haystack.search(exp); 9 | }; 10 | 11 | /** 12 | * 简单题,两层遍历搞定,或者直接用正则。 13 | */ -------------------------------------------------------------------------------- /String/[E]290.Word Pattern.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} pattern 3 | * @param {string} str 4 | * @return {boolean} 5 | */ 6 | var wordPattern = function(pattern, str) { 7 | let strArr = str.split(' '); 8 | if (pattern.length !== strArr.length) return false; 9 | let map = new Map(); 10 | let set = new Set(); 11 | for (let i = 0; i < pattern.length; i++) { 12 | if (!map.has(pattern[i]) && !set.has(strArr[i])) { 13 | map.set(pattern[i], strArr[i]); 14 | set.add(strArr[i]); 15 | continue; 16 | } 17 | if (!map.has(pattern[i]) && set.has(strArr[i])) return false; 18 | if (map.get(pattern[i]) !== strArr[i]) return false; 19 | } 20 | return true; 21 | }; 22 | 23 | /** 24 | * 和205思路完全一样,map记录映射,set记录映射过的值。 25 | */ -------------------------------------------------------------------------------- /String/[E]344.Reverse String.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} s 3 | * @return {string} 4 | */ 5 | var reverseString = function(s) { 6 | let p = s.length - 1; 7 | let res = ''; 8 | while(p >= 0) { 9 | res += s[p--]; 10 | } 11 | return res; 12 | }; 13 | 14 | /** 15 | * 常规翻转字符串 16 | */ -------------------------------------------------------------------------------- /String/[E]345.Reverse Vowels of a String.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} s 3 | * @return {string} 4 | */ 5 | function check(s) { 6 | return /[aeiou]/i.test(s); 7 | } 8 | var reverseVowels = function(s) { 9 | s = s.split(""); 10 | let left = 0; 11 | let right = s.length - 1; 12 | while(left < right) { 13 | while (!check(s[left])) { 14 | left++; 15 | } 16 | while(!check(s[right])) { 17 | right--; 18 | } 19 | if (left < right) { 20 | let temp = s[right]; 21 | s[right] = s[left]; 22 | s[left] = temp; 23 | right--; 24 | left++; 25 | } 26 | } 27 | return s.join(""); 28 | }; 29 | 30 | /** 31 | * 翻转元音字母,从左右初始化两个指针遍历,记录元音位置交换直到相遇即可 32 | */ -------------------------------------------------------------------------------- /String/[E]38.Count and Say.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number} n 3 | * @return {string} 4 | */ 5 | var countAndSay = function(n) { 6 | if (n === 1) return '1'; 7 | let c = countAndSay(n - 1); 8 | let res = ''; 9 | for (let i = 0; i < c.length; i++) { 10 | let cnt = 1; 11 | while (c[i] === c[i + 1] && i < c.length) { 12 | cnt++; 13 | i++; 14 | } 15 | res += cnt + c[i]; 16 | } 17 | return res; 18 | }; 19 | 20 | /** 21 | * 由于对于n的每次变化都是基于n - 1的,适合递归来解,找出一组变化规律转化为代码即可。 22 | */ -------------------------------------------------------------------------------- /String/[E]383.Ransom Note.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} ransomNote 3 | * @param {string} magazine 4 | * @return {boolean} 5 | */ 6 | var canConstruct = function(ransomNote, magazine) { 7 | const map = magazine.split('').reduce((map, item) => map.has(item) ? map.set(item, map.get(item) + 1) : map.set(item, 1),new Map()); 8 | for (let val of ransomNote) { 9 | map.set(val, (map.get(val) || 0) - 1); 10 | if (map.get(val) < 0) return false; 11 | } 12 | return true; 13 | }; 14 | 15 | /**简单题,用map做映射记录,按题意转化为代码即可 */ -------------------------------------------------------------------------------- /String/[E]387.First Unique Character in a String.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} s 3 | * @return {number} 4 | */ 5 | 6 | var firstUniqChar = function(s) { 7 | let map = s.split('').reduce((map, item) => map.has(item) ? map.set(item, map.get(item) + 1) : map.set(item, 1), new Map()); 8 | for (let i = 0; i < s.length; i++) { 9 | if (map.get(s[i]) === 1) return i; 10 | } 11 | return -1; 12 | }; 13 | 14 | /** 15 | * 简单题,map记录元素出现次数,找到第一个出现次数等于1的元素位置即可。 16 | */ -------------------------------------------------------------------------------- /String/[E]434.Number of Segments in a String.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} s 3 | * @return {number} 4 | */ 5 | var countSegments = function(s) { 6 | return s.match(/\S+/g) ? s.match(/\S+/g).length : 0; 7 | }; 8 | 9 | /** 10 | * 简单的正则匹配计数解决 11 | */ -------------------------------------------------------------------------------- /String/[E]459.Repeated Substring Pattern.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} s 3 | * @return {boolean} 4 | */ 5 | var repeatedSubstringPattern = function(s) { 6 | let n = s.length; 7 | for (let i = Math.floor(n / 2); i > 0; i--) { 8 | if (n % i === 0) { 9 | let c = n / i; 10 | let str = ''; 11 | for (let j = 0; j < c; j++) { 12 | str += s.slice(0, i); 13 | } 14 | if (str === s) return true; 15 | } 16 | } 17 | return false; 18 | }; 19 | 20 | /** 21 | * 从中间截开,当能被整除时,得最小重复次数c,最大重复长度i,以前i位重复c次得字符串和原字符串比较,相等则 22 | * 返回结果,不相等则减小最大重复长度循环比较。 23 | */ -------------------------------------------------------------------------------- /String/[E]520.Detect Capital.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} word 3 | * @return {boolean} 4 | */ 5 | var detectCapitalUse = function(word) { 6 | let a = word[0]; 7 | let b = word.slice(1); 8 | if (/[A-Z]/.test(a)) { 9 | return !/[A-Z]/.test(b) || !/[a-z]/.test(b) 10 | } else { 11 | return !/[A-Z]/.test(b) 12 | } 13 | }; 14 | 15 | /** 16 | * 检测大写字母是否符合规范,那么考虑正则,按逻辑翻译即可 17 | */ -------------------------------------------------------------------------------- /String/[E]541.Reverse String II.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} s 3 | * @param {number} k 4 | * @return {string} 5 | */ 6 | function reverse(s) { 7 | return s.split("").reverse().join("") 8 | } 9 | function helper(s) { 10 | return reverse(s.slice(0, s.length/2)) + s.slice(s.length / 2); 11 | } 12 | var reverseStr = function(s, k) { 13 | if (s.length < k) { 14 | return reverse(s); 15 | } 16 | if (s.length < 2 * k) { 17 | return reverse(s.slice(0, k)) + s.slice(k); 18 | } 19 | return helper(s.slice(0, 2 * k)) + reverseStr(s.slice(2 * k), k); 20 | }; 21 | 22 | /** 23 | * 翻转字符串的核心实现不变,按题意分类讨论分割范围翻转即可 24 | */ -------------------------------------------------------------------------------- /String/[E]557.Reverse Words in a String III.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} s 3 | * @return {string} 4 | */ 5 | var reverseWords = function(s) { 6 | return s.split(' ').map(str => str.split('').reverse().join('')).join(' ') 7 | }; 8 | 9 | /** 10 | * 内置方法解决 11 | */ -------------------------------------------------------------------------------- /String/[E]58.Length of Last Word.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} s 3 | * @return {number} 4 | */ 5 | var lengthOfLastWord = function(s) { 6 | if (!s.length) return 0; 7 | let right = s.length - 1; 8 | let res = 0; 9 | while (right >= 0 && s[right] === ' ') { 10 | right--; 11 | } 12 | while (right >= 0 && s[right] !== ' ') { 13 | right--; 14 | res++; 15 | } 16 | return res; 17 | }; 18 | 19 | /** 20 | * 简单题,求最后一个单词的长度,那么从后往前遍历即可,第一轮循环移除空格,第二轮计算长度。 21 | */ -------------------------------------------------------------------------------- /String/[E]606.Construct String from Binary Tree.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for a binary tree node. 3 | * function TreeNode(val) { 4 | * this.val = val; 5 | * this.left = this.right = null; 6 | * } 7 | */ 8 | /** 9 | * @param {TreeNode} t 10 | * @return {string} 11 | */ 12 | var tree2str = function(t) { 13 | if (!t) return ''; 14 | let ans = t.val; 15 | if (t.left) { 16 | ans += `(${tree2str(t.left)})`; 17 | } else { 18 | if (t.right) { 19 | ans += `()`; 20 | } 21 | } 22 | if (t.right) { 23 | ans += `(${tree2str(t.right)})`; 24 | } 25 | return ans.toString(); 26 | }; 27 | 28 | /** 29 | * 二叉树转字符串表示,常规深度遍历处理 30 | */ -------------------------------------------------------------------------------- /String/[E]657.Robot Return to Origin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} moves 3 | * @return {boolean} 4 | */ 5 | var judgeCircle = function(moves) { 6 | let pos = {x: 0, y: 0}; 7 | for (let i = 0; i < moves.length; i++) { 8 | if (moves[i] === 'U') pos.y += 1; 9 | if (moves[i] === 'D') pos.y -= 1; 10 | if (moves[i] === 'L') pos.x -= 1; 11 | if (moves[i] === 'R') pos.x += 1; 12 | } 13 | return pos.x === 0 && pos.y === 0 14 | }; 15 | 16 | /** 17 | * 按题意翻译判断是否回到原点即可 18 | */ -------------------------------------------------------------------------------- /String/[E]67.Add Binary.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} a 3 | * @param {string} b 4 | * @return {string} 5 | */ 6 | var addBinary = function(a, b) { 7 | if (a.length < b.length) { 8 | [a, b] = [b, a] 9 | } 10 | let etr = 0; 11 | let c = ''; 12 | for (let i = 1; i <= a.length; i++) { 13 | let fin; 14 | let res = +a[a.length - i] + (+b[b.length - i] || 0) + etr; 15 | if (res > 1) { 16 | fin = res - 2; 17 | etr = 1; 18 | } else { 19 | fin = res; 20 | etr = 0; 21 | } 22 | c = fin + c; 23 | } 24 | if (etr === 1) c = 1 + c; 25 | return c; 26 | }; 27 | 28 | /** 29 | * 实现二进制计算,逻辑不复杂就是繁琐,注意进位即可 30 | */ -------------------------------------------------------------------------------- /String/[E]680.Valid Palindrome II.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} s 3 | * @return {boolean} 4 | */ 5 | var validPalindrome = function(s) { 6 | let left = 0, right = s.length - 1; 7 | while (left < right) { 8 | if (s[left] !== s[right]) { 9 | return isValid(s, left, right - 1) || isValid(s, left + 1, right) 10 | } 11 | ++left; 12 | --right; 13 | } 14 | return true; 15 | }; 16 | 17 | function isValid(s, left, right) { 18 | while(left < right) { 19 | if (s[left] !== s[right]) return false; 20 | ++left; 21 | --right; 22 | } 23 | return true; 24 | } 25 | 26 | /** 27 | * 这题在判断回文的基础增加了一个错误字符的可操作空间,那么在碰到不匹配回文时,跳过任意一个继续判断,只要其中一个 28 | * 满足回文就可 29 | */ -------------------------------------------------------------------------------- /String/[E]686.Repeated String Match.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} A 3 | * @param {string} B 4 | * @return {number} 5 | */ 6 | var repeatedStringMatch = function(A, B) { 7 | let init = A; 8 | for (let i = 1; i < Math.ceil(B.length / init.length) + 2; i++) { 9 | if (A.includes(B)) return i; 10 | A += init; 11 | } 12 | return -1; 13 | }; 14 | 15 | /** 16 | * 求A重复几次能包含B,那么最少两次,最多的时候长度超过B再加一次保证A的头尾和尾头组合都有。循环检查即可。 17 | */ -------------------------------------------------------------------------------- /String/[E]696.Count Binary Substrings.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} s 3 | * @return {number} 4 | */ 5 | var countBinarySubstrings = function(s) { 6 | let prevRunLength = 0, curRunLength = 1, res = 0; 7 | for (let i=1;i= curRunLength) res++; 14 | } 15 | return res; 16 | } 17 | 18 | /** 19 | * 这题的关键在于分两个变量记录0和1出现的连续次数,当其中一个数字连续出现n次后, 20 | * 另一个数字连续出现m次,结果就可以加m。当不连续时,就要重置统计。 21 | */ 22 | -------------------------------------------------------------------------------- /String/[E]788.Rotated Digits.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number} N 3 | * @return {number} 4 | */ 5 | function helper(num) { 6 | return /[2569]/.test(num) && !/[347]/.test(num) 7 | } 8 | 9 | var rotatedDigits = function(N) { 10 | let res = 0; 11 | for (let i = 0;i <= N; i++) { 12 | if (helper(i)) res++; 13 | } 14 | return res; 15 | }; 16 | 17 | /** 18 | * 写个帮助函数判断旋转数,遍历计数即可 19 | */ -------------------------------------------------------------------------------- /String/[E]804.Unique Morse Code Words.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string[]} words 3 | * @return {number} 4 | */ 5 | var uniqueMorseRepresentations = function(words) { 6 | const code =[".-","-...","-.-.","-..",".","..-.","--.","....","..",".---","-.-",".-..","--","-.","---",".--.","--.-",".-.","...","-","..-","...-",".--","-..-","-.--","--.."]; 7 | let set = new Set(); 8 | for (let i = 0; i < words.length; i++) { 9 | let w = words[i].split("").map(item => code[item.charCodeAt(0) - 0x61]).join(""); 10 | if (!set.has(w)) { 11 | set.add(w); 12 | } 13 | } 14 | return set.size; 15 | }; 16 | 17 | /** 18 | * 摩尔斯电码设置为数组映射后对每个单词转码,用set的不重复特性记录次数 19 | */ -------------------------------------------------------------------------------- /String/[E]819.Most Common Word.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} paragraph 3 | * @param {string[]} banned 4 | * @return {string} 5 | */ 6 | var mostCommonWord = function(paragraph, banned) { 7 | let arr = paragraph.toLowerCase().replace(/['?!,;.]/g, ' ').split(/\s+/); 8 | let map = arr.reduce((map, item) => { 9 | if (!banned.includes(item)) { 10 | map.set(item, map.get(item) + 1 || 1); 11 | } 12 | return map; 13 | },new Map()); 14 | return Array.from(map.entries()).sort((a, b) => b[1] - a[1])[0][0]; 15 | }; 16 | 17 | /** 18 | * 除杂后hashmap统计各单词出现数量,排序后输出数量最多的 19 | */ -------------------------------------------------------------------------------- /String/[E]9.Palindrome Number.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number} x 3 | * @return {boolean} 4 | */ 5 | var isPalindrome = function(x) { 6 | x = x.toString(); 7 | for (let i = 0; i < Math.floor(x.length / 2); i++) { 8 | if (x[i] !== x[x.length - i - 1]) return false; 9 | } 10 | return true; 11 | }; 12 | 13 | /** 14 | * 验证回文数字,转化为字符串求解。如果不转化的话也就是繁琐的进制转换问题,验证回文逻辑不变。 15 | */ -------------------------------------------------------------------------------- /String/[H]115.Distinct Subsequences.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} s 3 | * @param {string} t 4 | * @return {number} 5 | */ 6 | var numDistinct = function(s, t) { 7 | let dp = Array.from({length: t.length + 1}, (_, idx) => { 8 | if (idx === 0) { 9 | return Array.from({length: s.length + 1}, _ => 1); 10 | } else { 11 | return Array.from({length: s.length + 1}, _ => 0); 12 | } 13 | }); 14 | for (let i = 1; i <= t.length; i++) { 15 | for (let j = 1; j <= s.length; j++) { 16 | dp[i][j] = dp[i][j - 1] + (t[i - 1] === s[j - 1] ? dp[i - 1][j - 1] : 0); 17 | } 18 | } 19 | return dp[t.length][s.length]; 20 | }; 21 | 22 | /** 23 | * 找出给定目标子串的构造数量,动态规划解决,动规数组dp[i][j]表示目标字符串的前i位子串被原字符串的 24 | * 前j位子串构造出的数量。由状态转移方程求得最终状态得解。 25 | */ -------------------------------------------------------------------------------- /String/[H]132.Palindrome Partitioning II.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} s 3 | * @return {number} 4 | */ 5 | var minCut = function(s) { 6 | let len = s.length; 7 | let dp1 = Array.from({length: len + 1}, (item, index) => len - index - 1); 8 | let dp2 = Array.from({length: len}, _ => Array.from({length: len}, _ => false)); 9 | 10 | for (let i = len - 1; i >= 0; i--) { 11 | for (let j = i; j < len; j++) { 12 | if (s[i] === s[j] && (j - i <= 1 || dp2[i + 1][j - 1])) { 13 | dp2[i][j] = true; 14 | dp1[i] = Math.min(dp1[i], dp1[j + 1] + 1); 15 | } 16 | } 17 | } 18 | return dp1[0]; 19 | }; 20 | 21 | /** 22 | * 和131类似,区别在于不用求出所有情况,只用求出最少的分割法。那么考虑用动态规划,新建两个动规 23 | * 数组,dp1[i]表示以i为起点的子字符串的切割为回文的最小切割次数。dp2[i][j]表示i到j的子字符串 24 | * 是否为回文。从后往前双层遍历,根据各自的状态转移方程求解。 25 | */ -------------------------------------------------------------------------------- /String/[H]30.Substring with Concatenation of All Words.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} s 3 | * @param {string[]} words 4 | * @return {number[]} 5 | */ 6 | var findSubstring = function(s, words) { 7 | let res = []; 8 | if (!s.length || !words.length) return res; 9 | let m = words[0].length; 10 | let n = words.length; 11 | let m1 = words.reduce((map, item) => map.set(item, (map.get(item) || 0) + 1), new Map()); 12 | for (let i = 0; i <= s.length - m * n; i++) { 13 | let map = new Map(); 14 | let j; 15 | for (j = 0; j < n; j++) { 16 | let t = s.substr(i + j * m, m); 17 | if (!m1.has(t)) break; 18 | if ((map.get(t) || 0) < m1.get(t)) { 19 | map.set(t, (map.get(t) || 0) + 1); 20 | } else { 21 | break; 22 | } 23 | } 24 | if (j === n) { 25 | res.push(i); 26 | } 27 | } 28 | return res; 29 | }; 30 | 31 | /** 32 | * 为words建立单词和数量间的映射,再遍历字符串以每个字符串为起点切割words单个单词长度的字符串, 33 | * 并建立个hashmap记录,和words的hashmap按题意比对,满足题意则记录当前遍历的起点位置,遍历完成得到结果。 34 | */ -------------------------------------------------------------------------------- /String/[H]301.Remove Invaild Parentheses.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} s 3 | * @return {string[]} 4 | */ 5 | var removeInvalidParentheses = function(s) { 6 | let res = []; 7 | let visited = new Set([s]); 8 | let q = [s]; 9 | let found = false; 10 | while (q.length) { 11 | let cnt = q[0]; 12 | q.shift(); 13 | if (isValid(cnt)) { 14 | res.push(cnt); 15 | found = true; 16 | } 17 | if (found) continue; 18 | for (let i = 0; i < cnt.length; i++) { 19 | if (cnt[i] !== '(' && cnt[i] !== ')') continue; 20 | let str = cnt.slice(0, i) + cnt.slice(i + 1); 21 | if (!visited.has(str)) { 22 | q.push(str); 23 | visited.add(str); 24 | } 25 | } 26 | } 27 | return res; 28 | }; 29 | 30 | function isValid(str) { 31 | let cnt = 0; 32 | for (let i = 0; i < str.length; i++) { 33 | if (str[i] === '(') { 34 | cnt++; 35 | } else if (str[i] === ')' && --cnt < 0) { 36 | return false; 37 | } 38 | } 39 | return cnt === 0; 40 | } 41 | 42 | /** 43 | * 要求移除尽量少的子元素保证括号字符串有效,广度遍历,用一个set记录遍历 44 | * 过的子串,从头遍历一个个移除并验证有效性,如果遇到有效子串,用found变量 45 | * 记录并跳出。 46 | */ -------------------------------------------------------------------------------- /String/[H]316.Remove Duplicate Letters.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} s 3 | * @return {string} 4 | */ 5 | var removeDuplicateLetters = function(s) { 6 | let map = s.split('').reduce((map, item) => map.has(item) ? map.set(item, map.get(item) + 1) : map.set(item, 1),new Map()); 7 | let stack = [0]; 8 | for (let i = 0; i < s.length; i++) { 9 | let top = stack[stack.length - 1]; 10 | map.set(s[i], map.get(s[i]) - 1); 11 | if (stack.includes(s[i])) continue; 12 | while (top > s[i] && map.get(top) > 0) { 13 | stack.pop(); 14 | top = stack[stack.length - 1]; 15 | } 16 | stack.push(s[i]); 17 | } 18 | return stack.slice(1).join(''); 19 | }; 20 | 21 | /** 22 | * 这题要求移除重复字母并尽可能按照字典序的顺序保留非重复字母,那么要保证每次选中的非重复字母字典序尽可能靠前, 23 | * 可以维护一个近似递增栈,对每个字母和栈顶比较,大于栈顶则可以排进去,如果小于栈顶说明栈顶元素应该尽可能的排在当前 24 | * 元素后面,如果后面还有栈顶元素就可以把栈顶元素弹出留到后续排,最终遍历完成,栈内也就是最终顺序了,这里栈中初始 25 | * 添加了一个0元素是为了循环时初次比较的方便。 26 | */ -------------------------------------------------------------------------------- /String/[H]32.Longest Valid Parentheses.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} s 3 | * @return {number} 4 | */ 5 | var longestValidParentheses = function(s) { 6 | let res = 0; 7 | for (let i = 1; i < s.length; i++) { 8 | let total = 0; 9 | for (let j = i; j >= 0; j--) { 10 | if (s[j] === ')') { 11 | total++; 12 | } else { 13 | total--; 14 | } 15 | if (!total) res = Math.max(res, i - j + 1); 16 | if (total < 0) break; 17 | } 18 | } 19 | return res; 20 | }; 21 | 22 | /** 23 | * 比较容易想到的做法是两层遍历,在以i为结尾的子串中寻找极值。 24 | */ 25 | 26 | /** 27 | * @param {string} s 28 | * @return {number} 29 | */ 30 | var longestValidParentheses = function(s) { 31 | let stack = []; 32 | let start = 0; 33 | let res = 0; 34 | for (let i = 0; i < s.length; i++) { 35 | if (s[i] === '(') { 36 | stack.push(i); 37 | } else if (s[i] === ')') { 38 | if (!stack.length) { 39 | start = i + 1; 40 | } else { 41 | stack.pop(); 42 | res = stack.length ? Math.max(res, i - stack[stack.length - 1]) : Math.max(res, i - start + 1); 43 | } 44 | } 45 | } 46 | return res; 47 | }; 48 | 49 | /** 50 | * 还有一种用栈辅助的方法,也是括号题常用的手段。用一个变量start记录有效子串的起始位置。 51 | * 当遇到)且栈不为空时就有一个有效子串存在了,同时更新res。 52 | */ -------------------------------------------------------------------------------- /String/[H]336.Palindrome Pairs.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string[]} words 3 | * @return {number[][]} 4 | */ 5 | var palindromePairs = function(words) { 6 | let res = []; 7 | let map = words.reduce((map, item, index) => map.set(item, index), new Map()); 8 | let set = new Set(Array.from(words,item => item.length)); 9 | for (let i = 0; i < words.length; i++) { 10 | let t = words[i].split('').reverse().join(''); 11 | let len = t.length; 12 | if (map.has(t) && map.get(t) !== i) { 13 | res.push([i, map.get(t)]); 14 | } 15 | for (let d of set) { 16 | if (d < len && check(t.slice(0, len - d)) && map.has(t.slice(len - d))) { 17 | res.push([i, map.get(t.slice(len - d))]); 18 | } 19 | if (d < len && check(t.slice(d - len)) && map.has(t.slice(0, d))) { 20 | res.push([map.get(t.slice(0, d)), i]); 21 | } 22 | } 23 | } 24 | return res; 25 | }; 26 | 27 | function check(s) { 28 | let left = 0; 29 | let right = s.length - 1; 30 | while (left < right) { 31 | if (s[left++] !== s[right--]) { 32 | return false; 33 | } 34 | } 35 | return true; 36 | } 37 | 38 | /** 39 | * 用个map记录单词和其位置的映射,再用个set记录所有长度。遍历单词,分两种情况。 40 | * 1.两个单词长度一样:如果单词反序后在map中存在且位置和当前单词不同,则可得结果。 41 | * 2.两个单词长度不一:遍历所有长度,选出长度小于当前单词的,进行下一步比较。 42 | * 截取当前单词为两部分,如果需要满足条件,前部分必须为回文,并且后部分反序等于当前选出单词。 43 | */ -------------------------------------------------------------------------------- /String/[H]76.Minimum Window Substring.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} s 3 | * @param {string} t 4 | * @return {string} 5 | */ 6 | var minWindow = function(s, t) { 7 | if (s.length < t.length) return ''; 8 | let shash = new Map(); 9 | let thash = new Map(); 10 | let res = Number.MAX_SAFE_INTEGER; 11 | let minStr = ''; 12 | 13 | for (let i = 0; i < t.length; i++) { 14 | thash.set(t[i], (thash.get(t[i]) || 0) + 1); 15 | } 16 | 17 | let right = 0; 18 | 19 | for (let left = 0; left < s.length; left++) { 20 | while (right < s.length && !isValid(shash, thash)) { 21 | shash.set(s[right], (shash.get(s[right]) || 0) + 1); 22 | if (right < s.length) { 23 | right++; 24 | } else { 25 | break; 26 | } 27 | } 28 | if (isValid(shash, thash)) { 29 | if (res > right - left){ 30 | res = right - left; 31 | minStr = s.slice(left, right); 32 | } 33 | } 34 | shash.set(s[left], shash.get(s[left]) - 1); 35 | } 36 | return minStr; 37 | }; 38 | 39 | function isValid(shash, thash) { 40 | for (let key of thash.keys()) { 41 | if (thash.get(key) > (shash.get(key) || 0)) { 42 | return false; 43 | } 44 | } 45 | return true; 46 | } 47 | 48 | /** 49 | * 求s中包含t的最小子字符串,一般遇到这种极值子字符串题,应该有两种思路,动态规划和滑动窗口,这题 50 | * 不太好找合适的动态规划数组,用滑动窗口动态计算。建立两个hashmap,shash保存当前窗口的字母和数量映射关系, 51 | * thash保存目标包含字符串的字母数量映射关系,遍历每个left起点并将right向右移,isValid判断当前窗口是否 52 | * 包含目标子字符串,包含的话则对结果进行一次最小值比较。注意每次更改left起点也要同时更新shash。遍历完成则 53 | * 可得最小值。 54 | */ -------------------------------------------------------------------------------- /String/[M]12.Integer to Roman.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number} num 3 | * @return {string} 4 | */ 5 | var intToRoman = function(num) { 6 | let res = ''; 7 | const roman = ['M', 'D', 'C', 'L', 'X', 'V', 'I']; 8 | const value = [1000, 500, 100, 50, 10, 5, 1]; 9 | 10 | for (let n = 0; n < 7; n += 2) { 11 | let x = Math.floor(num / value[n]); 12 | if (x < 4) { 13 | for (let i = 1; i <= x; i++) { 14 | res += roman[n]; 15 | } 16 | } else if (x == 4) { 17 | res = res + roman[n] + roman[n - 1]; 18 | } else if (x > 4 && x < 9) { 19 | res += roman[n - 1]; 20 | for (let i = 6; i <= x; i++) { 21 | res += roman[n]; 22 | } 23 | } else if (x === 9) { 24 | res = res + roman[n] + roman[n - 2]; 25 | } 26 | num %= value[n]; 27 | } 28 | return res; 29 | }; 30 | 31 | /** 32 | * 13题的反转,逻辑不复杂就是繁琐,每3位一组拆分数字,根据具体大小映射到字母上按罗马字规则排序。 33 | */ -------------------------------------------------------------------------------- /String/[M]131.Palindrome Partitioning.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} s 3 | * @return {string[][]} 4 | */ 5 | var partition = function(s) { 6 | let res = []; 7 | dfs(s, 0, [], res); 8 | return res; 9 | }; 10 | 11 | function dfs(s, start, out, res) { 12 | if (start === s.length) { 13 | res.push(Array.from(out)); 14 | return; 15 | } 16 | for (let i = start; i < s.length; i++) { 17 | if (isValid(s, start, i)) { 18 | out.push(s.slice(start, i + 1)); 19 | dfs(s, i + 1, out, res); 20 | out.pop(); 21 | } 22 | } 23 | } 24 | function isValid(s, start, end) { 25 | while (start < end) { 26 | if (s[start++] !== s[end--]) return false; 27 | } 28 | return true; 29 | } 30 | 31 | /** 32 | * 拆分为回文字符串数组,由于要求所有情况,免不了所有情况都要遍历到,用深度遍历实现,需要用一个参数记录每次开始遍历的位置。 33 | * 当该参数记录到末尾时,则可输出这次的结果。 34 | */ -------------------------------------------------------------------------------- /String/[M]151.Reverse Words in a String.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} str 3 | * @returns {string} 4 | */ 5 | var reverseWords = function(str) { 6 | let tmp = ''; 7 | let res = ''; 8 | let p = str.length - 1; 9 | while (p >= 0) { 10 | if (str[p] !== ' ') { 11 | tmp = str[p] + tmp; 12 | } else { 13 | if (tmp.length) { 14 | res += `${tmp} `; 15 | tmp = ''; 16 | } 17 | } 18 | p--; 19 | } 20 | res += tmp; 21 | return res.trim(); 22 | }; 23 | 24 | /** 25 | * 翻转单词,思路和翻转字符串也相同,从后遍历记录并用个变量记录当前单词即可 26 | */ -------------------------------------------------------------------------------- /String/[M]17.Letter Combinations of a Phone Number.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} digits 3 | * @return {string[]} 4 | */ 5 | var letterCombinations = function(digits) { 6 | if (!digits.length) return []; 7 | let res = []; 8 | let dict = ["", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"]; 9 | helper(digits, dict, 0, '', res); 10 | return res; 11 | }; 12 | 13 | function helper(digits, dict, level, out, res) { 14 | if (level === digits.length) { 15 | res.push(out); 16 | return; 17 | } 18 | let str = dict[digits[level]]; 19 | for (let i = 0; i < str.length; i++) { 20 | helper(digits, dict, level + 1, out + str[i], res); 21 | } 22 | } 23 | 24 | /** 25 | * 电话盘数字映射到字母组合,递归实现,根据给定数字个数记录层数,决定何时返回最终结果 26 | */ -------------------------------------------------------------------------------- /String/[M]22.Generate parentheses.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number} n 3 | * @return {string[]} 4 | */ 5 | var generateParenthesis = function(n) { 6 | let res = []; 7 | helper(n, n, '', res); 8 | return res; 9 | }; 10 | 11 | function helper(left, right, out, res) { 12 | if (left > right) return; 13 | if (!left && !right) { 14 | res.push(out); 15 | } else { 16 | if (left > 0) helper(left - 1, right, out + '(', res); 17 | if (right > 0) helper(left, right - 1, out + ')', res); 18 | } 19 | } 20 | 21 | /** 22 | * 记录所有能生成的括号组合,dfs解决,靠记录剩余括号数量决定跳出时机。 23 | */ -------------------------------------------------------------------------------- /String/[M]241.Different Ways to Add Parentheses.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} input 3 | * @return {number[]} 4 | */ 5 | var diffWaysToCompute = function(input) { 6 | let res = []; 7 | for (let i = 0; i < input.length; i++) { 8 | if (input[i] === '+' || input[i] === '-' || input[i] === '*') { 9 | let left = diffWaysToCompute(input.slice(0, i)); 10 | let right = diffWaysToCompute(input.slice(i + 1)); 11 | for (let j = 0; j < left.length; j++) { 12 | for (let k = 0; k < right.length; k++) { 13 | if (input[i] === '+') res.push(left[j] + right[k]); 14 | if (input[i] === '-') res.push(left[j] - right[k]); 15 | if (input[i] === '*') res.push(left[j] * right[k]); 16 | } 17 | } 18 | } 19 | } 20 | if (!res.length) res.push(+input); 21 | return res; 22 | }; 23 | 24 | /** 25 | * 不同的插入括号的形式计算结果,以运算符为单位分治解决 26 | */ -------------------------------------------------------------------------------- /String/[M]3.Longest Substring Without Repeating Characters.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} s 3 | * @return {number} 4 | */ 5 | var lengthOfLongestSubstring = function(s) { 6 | let res = 0; 7 | let stack = []; 8 | for (let i = 0; i < s.length; i++) { 9 | if (!stack.includes(s[i])) { 10 | stack.push(s[i]) 11 | } else { 12 | while (stack[0] !== s[i]) { 13 | stack.shift(); 14 | } 15 | stack.push(s[i]); 16 | stack.shift(); 17 | } 18 | res = Math.max(res, stack.length); 19 | } 20 | return res; 21 | }; 22 | 23 | /** 24 | * 求最长非重复子字符串,用栈保存非重复字符,当遇到重复字符时将其前面的字符全部推出, 25 | * 遍历求得最长栈即可。 26 | */ -------------------------------------------------------------------------------- /String/[M]392.Is Subsequence.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} s 3 | * @param {string} t 4 | * @return {boolean} 5 | */ 6 | var isSubsequence = function(s, t) { 7 | if (!s.length) return true; 8 | let idx = t.indexOf(s[0]); 9 | if (idx !== -1) { 10 | return isSubsequence(s.slice(1), t.slice(idx + 1)); 11 | } else { 12 | return false; 13 | } 14 | }; 15 | 16 | /** 17 | * 求子串是否存在,递归即可 18 | */ -------------------------------------------------------------------------------- /String/[M]395. Longest Substring with At Least K Repeating Characters.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} s 3 | * @param {number} k 4 | * @return {number} 5 | */ 6 | var longestSubstring = function(s, k) { 7 | if (!s.length) return 0; 8 | let map = new Map(); 9 | for (let i = 0; i < s.length; i++) { 10 | map.set(s[i], (map.get(s[i]) || 0) + 1); 11 | } 12 | for (let item of map) { 13 | if (item[1] < k) { 14 | let sArr = s.split(item[0]); 15 | return Math.max(...sArr.map(item => longestSubstring(item, k))); 16 | } 17 | } 18 | return s.length; 19 | }; 20 | 21 | /** 22 | * 分治法,用hashmap统计各字符出现次数,不达标的字符肯定不在子字符串中,则可以以这些字符为间隔 23 | * 拆分原字符串递归求解。 24 | */ 25 | -------------------------------------------------------------------------------- /String/[M]49.Group Anagrams.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string[]} strs 3 | * @return {string[][]} 4 | */ 5 | var groupAnagrams = function(strs) { 6 | let map = new Map(); 7 | let res = []; 8 | for (let i = 0; i < strs.length; i++) { 9 | let temp = strs[i]; 10 | let key = strs[i].split('').sort().join(''); 11 | if (map.has(key)) { 12 | let value = map.get(key); 13 | value.push(temp); 14 | map.set(key, value); 15 | } else { 16 | map.set(key, [temp]); 17 | } 18 | } 19 | for (let item of map.values()) { 20 | res.push(item); 21 | } 22 | return res; 23 | }; 24 | 25 | /** 26 | * 相同字母组成的分类题,本质还是映射问题,用排序后的单词作为key遍历分组记录到value即可。 27 | */ -------------------------------------------------------------------------------- /String/[M]5.Longest Palindromic Substring.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} s 3 | * @return {string} 4 | */ 5 | var longestPalindrome = function(s) { 6 | let dp = Array.from({length: s.length}, _ => []); 7 | let len = 0, left = 0, right = 0; 8 | for (let i = 0; i < s.length; i++) { 9 | for (let j = 0; j < i; j++) { 10 | dp[j][i] = s[i] === s[j] && (i - j < 2 || dp[j + 1][i - 1]); 11 | if (dp[j][i] && len < i - j + 1) { 12 | len = i - j + 1; 13 | left = j; 14 | right = i; 15 | } 16 | } 17 | dp[i][i] = 1; 18 | } 19 | return s.slice(left, right + 1); 20 | }; 21 | 22 | /** 23 | * 求最长回文字符串,极值问题多考虑贪心和动态规划,这题显然不适合贪心。新建动规二维数组为dp[i][j] 24 | * 表示i到j的子字符串为回文,按状态转移方程求解。 25 | */ -------------------------------------------------------------------------------- /String/[M]537.Complex Number Multiplication.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} a 3 | * @param {string} b 4 | * @return {string} 5 | */ 6 | var complexNumberMultiply = function(a, b) { 7 | let p1 = a.split('+'); 8 | let p2 = b.split('+'); 9 | let a1 = +p1[0]; 10 | let b1 = +p2[0]; 11 | let a2 = +p1[1].slice(0, -1); 12 | let b2 = +p2[1].slice(0, -1); 13 | let r1 = a1 * b1 - a2 * b2; 14 | let r2 = a1 * b2 + a2 * b1; 15 | return r1.toString() + '+' + r2.toString() + 'i'; 16 | } 17 | 18 | /** 19 | * 按题意拆分字符串计算,注意数字和字符串转化 20 | */ -------------------------------------------------------------------------------- /String/[M]647.Palindromic Substrings.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} s 3 | * @return {number} 4 | */ 5 | var countSubstrings = function(s) { 6 | if (!s.length) { 7 | return 0; 8 | } 9 | let res = 0; 10 | let helper = function(i, j) { 11 | while (i >= 0 & j < s.length && s[i] === s[j]) { 12 | i--; 13 | j++; 14 | res++; 15 | } 16 | } 17 | for (let i = 0; i < s.length; i++) { 18 | helper(i, i); 19 | helper(i, i + 1); 20 | } 21 | return res; 22 | }; 23 | 24 | /** 25 | * 求子回文字符串。遍历每个字母,并以字母为中心向两边扩散寻找回文,每移动一步,左右相等的话结果就加1。 26 | * 分奇偶两种情况计算,奇数中间为i,偶数中间为i和i+1。 27 | */ -------------------------------------------------------------------------------- /String/[M]791.Custom Sort String.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {string} S 3 | * @param {string} T 4 | * @return {string} 5 | */ 6 | var customSortString = function(S, T) { 7 | T = T.split(''); 8 | T.sort((a, b) => { 9 | return S.indexOf(a) - S.indexOf(b); 10 | }); 11 | return T.join(''); 12 | }; 13 | 14 | /** 15 | * 内置sort根据S确定排序规则 16 | */ -------------------------------------------------------------------------------- /Tree/[E]100.Same Tree.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for a binary tree node. 3 | * function TreeNode(val) { 4 | * this.val = val; 5 | * this.left = this.right = null; 6 | * } 7 | */ 8 | /** 9 | * @param {TreeNode} p 10 | * @param {TreeNode} q 11 | * @return {boolean} 12 | */ 13 | var isSameTree = function(p, q) { 14 | if (p === null && q === null) return true; 15 | if (p === null || q === null) return false; 16 | return p.val === q.val && isSameTree(p.left, q.left) && isSameTree(p.right, q.right) 17 | }; 18 | 19 | /** 20 | * 递归判断左右子节点是否相等 21 | */ -------------------------------------------------------------------------------- /Tree/[E]101.Symmetric Tree.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for a binary tree node. 3 | * function TreeNode(val) { 4 | * this.val = val; 5 | * this.left = this.right = null; 6 | * } 7 | */ 8 | /** 9 | * @param {TreeNode} root 10 | * @return {boolean} 11 | */ 12 | var isSymmetric = function(root) { 13 | if (!root) return true; 14 | return helper(root.left, root.right); 15 | }; 16 | 17 | function helper(left, right) { 18 | if (!left && !right) return true; 19 | if (left && !right || !left && right || left.val !== right.val) return false; 20 | return helper(left.left, right.right) && helper(left.right, right.left); 21 | } 22 | 23 | /** 24 | * 递归判断左右节点的子节点是否对称 25 | */ -------------------------------------------------------------------------------- /Tree/[E]104.Maximum Depth of Binary Tree.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for a binary tree node. 3 | * function TreeNode(val) { 4 | * this.val = val; 5 | * this.left = this.right = null; 6 | * } 7 | */ 8 | /** 9 | * @param {TreeNode} root 10 | * @return {number} 11 | */ 12 | var maxDepth = function(root) { 13 | return helper(root, 0); 14 | }; 15 | 16 | function helper(node, level) { 17 | if (!node) return level; 18 | level += 1; 19 | return Math.max(helper(node.left, level), helper(node.right, level)); 20 | } 21 | 22 | /** 23 | * 基础题,求树的最大深度,后序DFS解决 24 | */ -------------------------------------------------------------------------------- /Tree/[E]107.Binary Tree Level Order Traversal II.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for a binary tree node. 3 | * function TreeNode(val) { 4 | * this.val = val; 5 | * this.left = this.right = null; 6 | * } 7 | */ 8 | /** 9 | * @param {TreeNode} root 10 | * @return {number[][]} 11 | */ 12 | 13 | var levelOrderBottom = function(root) { 14 | let res = []; 15 | if (root === null) return res; 16 | let que = []; 17 | que.push(root); 18 | while(que.length) { 19 | let level = []; 20 | let length = que.length; 21 | for (let i = 0; i < length; i++) { 22 | let node = que.shift(); 23 | level.push(node.val); 24 | if (node.left) que.push(node.left); 25 | if (node.right) que.push(node.right); 26 | } 27 | res.unshift(level); 28 | } 29 | return res; 30 | }; 31 | 32 | /** 33 | * 基础的层序遍历解决 34 | */ -------------------------------------------------------------------------------- /Tree/[E]108.Convert Sorted Array to Binary Search Tree.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for a binary tree node. 3 | * function TreeNode(val) { 4 | * this.val = val; 5 | * this.left = this.right = null; 6 | * } 7 | */ 8 | /** 9 | * @param {number[]} nums 10 | * @return {TreeNode} 11 | */ 12 | function helper(nums, l, r) { 13 | if (l > r) return null; 14 | let m = Math.floor((l + r) / 2); 15 | let root = new TreeNode(nums[m]); 16 | root.left = helper(nums, l, m - 1); 17 | root.right = helper(nums, m + 1, r); 18 | return root; 19 | } 20 | var sortedArrayToBST = function(nums) { 21 | if (nums.length === 0) return null; 22 | return helper(nums, 0, nums.length - 1); 23 | }; 24 | 25 | /** 26 | * 排序数组转化为二叉搜索树,根节点取数组中点,左右节点递归数组两边即可 27 | */ -------------------------------------------------------------------------------- /Tree/[E]110.Balanced Binary Tree.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for a binary tree node. 3 | * function TreeNode(val) { 4 | * this.val = val; 5 | * this.left = this.right = null; 6 | * } 7 | */ 8 | /** 9 | * @param {TreeNode} root 10 | * @return {boolean} 11 | */ 12 | function check(node) { 13 | if (!node) return 0; 14 | let left = check(node.left); 15 | if (left === -1) return -1; 16 | let right = check(node.right); 17 | if (right === -1) return -1; 18 | if (Math.abs(left - right) > 1) return -1; 19 | return 1 + Math.max(left, right); 20 | } 21 | var isBalanced = function(root) { 22 | if (check(root) === -1) return false; 23 | return true; 24 | }; 25 | 26 | /** 27 | * 检查二叉平衡树,关键在于后序回溯出每个节点的深度判断 28 | */ -------------------------------------------------------------------------------- /Tree/[E]111.Minimum Depth of Binary Tree.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for a binary tree node. 3 | * function TreeNode(val) { 4 | * this.val = val; 5 | * this.left = this.right = null; 6 | * } 7 | */ 8 | /** 9 | * @param {TreeNode} root 10 | * @return {number} 11 | */ 12 | var minDepth = function(root) { 13 | if (!root) return 0; 14 | if (!root.left && !root.right) return 1; 15 | if (!root.left) return minDepth(root.right) + 1; 16 | if (!root.right) return minDepth(root.left) + 1; 17 | return 1 + Math.min(minDepth(root.left), minDepth(root.right)); 18 | }; 19 | 20 | /** 21 | * 基础题,求树的最小深度,DFS实现 22 | */ -------------------------------------------------------------------------------- /Tree/[E]112.Path Sum.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for a binary tree node. 3 | * function TreeNode(val) { 4 | * this.val = val; 5 | * this.left = this.right = null; 6 | * } 7 | */ 8 | /** 9 | * @param {TreeNode} root 10 | * @param {number} sum 11 | * @return {boolean} 12 | */ 13 | var hasPathSum = function(root, sum) { 14 | if (!root) return false; 15 | if (!root.left && !root.right) { 16 | return sum === root.val; 17 | } 18 | return hasPathSum(root.left, sum - root.val) || hasPathSum(root.right, sum - root.val); 19 | }; 20 | 21 | /** 22 | * 递归时动态更新sum比较即可 23 | */ -------------------------------------------------------------------------------- /Tree/[E]226.Invert Binary Tree.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for a binary tree node. 3 | * function TreeNode(val) { 4 | * this.val = val; 5 | * this.left = this.right = null; 6 | * } 7 | */ 8 | /** 9 | * @param {TreeNode} root 10 | * @return {TreeNode} 11 | */ 12 | var invertTree = function(root) { 13 | if (!root) return null; 14 | let temp = root.left; 15 | root.left = invertTree(root.right); 16 | root.right = invertTree(temp); 17 | return root; 18 | }; 19 | 20 | /** 21 | * 出名的翻转二叉树 22 | */ -------------------------------------------------------------------------------- /Tree/[E]235.Lowest Common Ancestor of a Binary Search Tree.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for a binary tree node. 3 | * function TreeNode(val) { 4 | * this.val = val; 5 | * this.left = this.right = null; 6 | * } 7 | */ 8 | /** 9 | * @param {TreeNode} root 10 | * @param {TreeNode} p 11 | * @param {TreeNode} q 12 | * @return {TreeNode} 13 | */ 14 | var lowestCommonAncestor = function(root, p, q) { 15 | if (!root) return null; 16 | if (root.val > Math.max(p.val, q.val)) { 17 | return lowestCommonAncestor(root.left, p, q); 18 | } 19 | if (root.val < Math.min(p.val, q.val)) { 20 | return lowestCommonAncestor(root.right, p, q); 21 | } 22 | return root.val; 23 | }; 24 | 25 | /** 26 | * 找到两个节点的公共祖先,DFS解决,对每个节点和目标节点比较大小,决定DFS的方向, 27 | * 当当前节点值大于目标值之一并小于另一个目标值时即为所求 28 | */ -------------------------------------------------------------------------------- /Tree/[E]257.Binary Tree Paths.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for a binary tree node. 3 | * function TreeNode(val) { 4 | * this.val = val; 5 | * this.left = this.right = null; 6 | * } 7 | */ 8 | /** 9 | * @param {TreeNode} root 10 | * @return {string[]} 11 | */ 12 | var binaryTreePaths = function(root) { 13 | let res = []; 14 | if (root) { 15 | helper(root, '', res); 16 | } 17 | return res; 18 | }; 19 | 20 | function helper(node, out, res) { 21 | if (!node.left && !node.right) { 22 | res.push(out + node.val); 23 | } 24 | if (node.left) { 25 | helper(node.left, out + node.val + '->' , res); 26 | } 27 | if (node.right) { 28 | helper(node.right, out + node.val + '->' , res); 29 | } 30 | } 31 | 32 | /** 33 | * 递归的同时记录当前路径,没有子节点时记录结果 34 | */ -------------------------------------------------------------------------------- /Tree/[E]404.Sum of Left Leaves.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for a binary tree node. 3 | * function TreeNode(val) { 4 | * this.val = val; 5 | * this.left = this.right = null; 6 | * } 7 | */ 8 | /** 9 | * @param {TreeNode} root 10 | * @return {number} 11 | */ 12 | var sumOfLeftLeaves = function(root) { 13 | if (!root) return 0; 14 | let res = 0; 15 | let helper = function(node, bool) { 16 | if (!node) return; 17 | if (bool && !node.left && !node.right) { 18 | res += node.val; 19 | } 20 | helper(node.left, true); 21 | helper(node.right, false); 22 | } 23 | helper(root.left, true); 24 | helper(root.right, false); 25 | return res; 26 | }; 27 | 28 | /** 29 | * 只记录左节点的值,那么遍历时用个布尔值标记左右即可,当没有子节点且满足左时记录 30 | */ -------------------------------------------------------------------------------- /Tree/[E]437.Path Sum III.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for a binary tree node. 3 | * function TreeNode(val) { 4 | * this.val = val; 5 | * this.left = this.right = null; 6 | * } 7 | */ 8 | /** 9 | * @param {TreeNode} root 10 | * @param {number} sum 11 | * @return {number} 12 | */ 13 | function helper(node, sum) { 14 | let res = 0; 15 | if (!node) return res; 16 | if (node.val === sum) res++; 17 | res += helper(node.left, sum - node.val); 18 | res += helper(node.right, sum - node.val); 19 | return res; 20 | } 21 | var pathSum = function(root, sum) { 22 | if (!root) return 0; 23 | return helper(root, sum) + pathSum(root.left, sum) + pathSum(root.right, sum); 24 | }; 25 | 26 | /** 27 | * 分别针对每个节点都求解,求解用常规后序遍历 28 | */ -------------------------------------------------------------------------------- /Tree/[E]501.Find Mode In Binary Search Tree.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for a binary tree node. 3 | * function TreeNode(val) { 4 | * this.val = val; 5 | * this.left = this.right = null; 6 | * } 7 | */ 8 | /** 9 | * @param {TreeNode} root 10 | * @return {number[]} 11 | */ 12 | var findMode = function(root) { 13 | let res = []; 14 | let cnt = 1; 15 | let mx = 0; 16 | let pre = null; 17 | let search = function(node) { 18 | if (!node) return; 19 | search(node.left); 20 | if (pre) { 21 | cnt = (node.val === pre.val) ? cnt + 1 : 1; 22 | } 23 | if (cnt >= mx) { 24 | if (cnt > mx) res.length = 0; 25 | res.push(node.val); 26 | mx = cnt; 27 | } 28 | pre = node; 29 | search(node.right); 30 | } 31 | search(root); 32 | return res; 33 | }; 34 | 35 | /** 36 | * 对二分搜索树中序遍历,因为相等的元素肯定在一起,所以用两个变量分别 37 | * 记录当前节点重复数量和先一个节点,每次相等时cnt加1,否则重置,再和 38 | * 最大值比较 39 | */ -------------------------------------------------------------------------------- /Tree/[E]538.Convert BST to Greater Tree.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for a binary tree node. 3 | * function TreeNode(val) { 4 | * this.val = val; 5 | * this.left = this.right = null; 6 | * } 7 | */ 8 | /** 9 | * @param {TreeNode} root 10 | * @return {TreeNode} 11 | */ 12 | var convertBST = function(root) { 13 | var sum = 0; 14 | function traverse (root) { 15 | if (root === null) return; 16 | traverse(root.right); 17 | root.val = root.val + sum; 18 | sum = root.val; 19 | traverse(root.left); 20 | } 21 | traverse(root); 22 | return root; 23 | }; 24 | 25 | /** 26 | * 由于需要累加比自己节点大的节点,那么遍历顺序需要从大到小的顺序右根左并用一个变量记录当前累加 27 | */ -------------------------------------------------------------------------------- /Tree/[E]543.Diameter of Binary Tree.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for a binary tree node. 3 | * function TreeNode(val) { 4 | * this.val = val; 5 | * this.left = this.right = null; 6 | * } 7 | */ 8 | /** 9 | * @param {TreeNode} root 10 | * @return {number} 11 | */ 12 | var diameterOfBinaryTree = function(root) { 13 | let res = 1; 14 | function helper(node) { 15 | if (node === null) return 0; 16 | let left = helper(node.left); 17 | let right = helper(node.right); 18 | res = Math.max(res, left + right + 1); 19 | return Math.max(left, right) + 1; 20 | } 21 | helper(root); 22 | return res - 1; 23 | }; 24 | 25 | /** 26 | * 常规后序遍历更新最值,并返回当前深度 27 | */ -------------------------------------------------------------------------------- /Tree/[E]563.Binary Tree Tilt.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for a binary tree node. 3 | * function TreeNode(val) { 4 | * this.val = val; 5 | * this.left = this.right = null; 6 | * } 7 | */ 8 | /** 9 | * @param {TreeNode} root 10 | * @return {number} 11 | */ 12 | var findTilt = function(root) { 13 | let res = 0; 14 | (function postorder(node) { 15 | if (!node) return 0; 16 | let leftSum = postorder(node.left, res); 17 | let rightSum = postorder(node.right, res); 18 | res += Math.abs(leftSum - rightSum); 19 | return leftSum + rightSum + node.val; 20 | })(root); 21 | return res; 22 | }; 23 | 24 | /** 25 | * 涉及到子树累加值的计算,后序遍历 26 | */ -------------------------------------------------------------------------------- /Tree/[E]572.Subtree of Another Tree.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for a binary tree node. 3 | * function TreeNode(val) { 4 | * this.val = val; 5 | * this.left = this.right = null; 6 | * } 7 | */ 8 | /** 9 | * @param {TreeNode} s 10 | * @param {TreeNode} t 11 | * @return {boolean} 12 | */ 13 | function isSame(s, t) { 14 | if (!s && !t) return true; 15 | if (!s || !t) return false; 16 | if (s.val !== t.val) return false; 17 | return isSame(s.left, t.left) && isSame(s.right, t.right); 18 | } 19 | 20 | var isSubtree = function(s, t) { 21 | if (!s) return false; 22 | if (isSame(s, t)) return true; 23 | return isSubtree(s.left, t) || isSubtree(s.right, t); 24 | }; 25 | 26 | /** 27 | * 首先用递归写一个判断相等树的帮助函数,再对原树的所有子树递归判断是否和目标树相等 28 | */ -------------------------------------------------------------------------------- /Tree/[E]617.Merge Two Binary Trees.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for a binary tree node. 3 | * function TreeNode(val) { 4 | * this.val = val; 5 | * this.left = this.right = null; 6 | * } 7 | */ 8 | /** 9 | * @param {TreeNode} t1 10 | * @param {TreeNode} t2 11 | * @return {TreeNode} 12 | */ 13 | var mergeTrees = function(t1, t2) { 14 | if (t1 !== null || t2 !== null) { 15 | let tmp = new TreeNode(0); 16 | if(null !== t1) tmp.val += t1.val; 17 | if(null !== t2) tmp.val += t2.val; 18 | tmp.left = mergeTrees((t1 !== null) ? t1.left : null, (t2 !== null) ? t2.left : null); 19 | tmp.right = mergeTrees((t1 !== null) ? t1.right : null, (t2 !== null) ? t2.right : null); 20 | return tmp; 21 | } else return null; 22 | }; 23 | 24 | /** 25 | * 合并两棵树的值,前序遍历处理值,子节点递归处理 26 | */ -------------------------------------------------------------------------------- /Tree/[E]637.Average of Levels in Binary Tree.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for a binary tree node. 3 | * function TreeNode(val) { 4 | * this.val = val; 5 | * this.left = this.right = null; 6 | * } 7 | */ 8 | /** 9 | * @param {TreeNode} root 10 | * @return {number[]} 11 | */ 12 | var averageOfLevels = function(root) { 13 | let res = []; 14 | let quene = []; 15 | quene.push(root); 16 | while(quene.length > 0) { 17 | let sum = 0; 18 | let count = 0; 19 | let temp = []; 20 | while(quene.length > 0) { 21 | let n = quene.shift(); 22 | sum += n.val; 23 | count++; 24 | if (n.left !== null) { 25 | temp.push(n.left); 26 | } 27 | if (n.right !== null) { 28 | temp.push(n.right); 29 | } 30 | } 31 | quene = temp; 32 | res.push(sum/count); 33 | } 34 | return res; 35 | }; 36 | 37 | /** 38 | * 求每层平均数,层序遍历解决 39 | */ -------------------------------------------------------------------------------- /Tree/[E]653.Two Sum IV - Input is a BST.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for a binary tree node. 3 | * function TreeNode(val) { 4 | * this.val = val; 5 | * this.left = this.right = null; 6 | * } 7 | */ 8 | /** 9 | * @param {TreeNode} root 10 | * @param {number} k 11 | * @return {boolean} 12 | */ 13 | var findTarget = function(root, k) { 14 | let arr = []; 15 | let stack = []; 16 | if (root) { 17 | stack.push(root); 18 | } 19 | while (stack.length !== 0) { 20 | root = stack.pop(); 21 | if (arr.indexOf(root.val) !== -1) { 22 | return true; 23 | } else { 24 | arr.push(k - root.val); 25 | } 26 | if (root.right) stack.push(root.right); 27 | if (root.left) stack.push(root.left); 28 | } 29 | return false; 30 | }; 31 | 32 | /** 33 | * 树类型的two sum,遍历时用个数组记录每个值求和得到目标值的另一值,同时在该数组中查询是否存在 34 | */ -------------------------------------------------------------------------------- /Tree/[E]669.Trim a Binary Search Tree.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for a binary tree node. 3 | * function TreeNode(val) { 4 | * this.val = val; 5 | * this.left = this.right = null; 6 | * } 7 | */ 8 | /** 9 | * @param {TreeNode} root 10 | * @param {number} L 11 | * @param {number} R 12 | * @return {TreeNode} 13 | */ 14 | var trimBST = function(root, L, R) { 15 | if (root == null) return root; 16 | if (root.val > R) return trimBST(root.left, L, R); 17 | if (root.val < L) return trimBST(root.right, L, R); 18 | root.left = trimBST(root.left, L, R); 19 | root.right = trimBST(root.right, L, R); 20 | return root; 21 | }; 22 | 23 | /** 24 | * 根据二叉树的特性,根节点大于R时抛弃右子树,小于L时抛弃左子树,递归修剪 25 | */ -------------------------------------------------------------------------------- /Tree/[E]671.Second Minimum Node In a Binary Tree.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for a binary tree node. 3 | * function TreeNode(val) { 4 | * this.val = val; 5 | * this.left = this.right = null; 6 | * } 7 | */ 8 | /** 9 | * @param {TreeNode} root 10 | * @return {number} 11 | */ 12 | function helper(node, val) { 13 | if (!node) return Number.MAX_VALUE; 14 | if (node.val !== val) return node.val; 15 | return Math.min(helper(node.left, val), helper(node.right, val)) ; 16 | 17 | } 18 | var findSecondMinimumValue = function(root) { 19 | let res = helper(root, root.val); 20 | return res === Number.MAX_VALUE ? -1 : res; 21 | }; 22 | 23 | /** 24 | * 返回第二小的值,根节点一定是最小的,目的就是找到除根节点外最小的数,则递归 25 | * 获取左右子树最小的节点即可 26 | */ -------------------------------------------------------------------------------- /Tree/[E]687.Longest Univalue Path.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for a binary tree node. 3 | * function TreeNode(val) { 4 | * this.val = val; 5 | * this.left = this.right = null; 6 | * } 7 | */ 8 | /** 9 | * @param {TreeNode} root 10 | * @return {number} 11 | */ 12 | var longestUnivaluePath = function(root) { 13 | if (!root) return 0; 14 | let sub = Math.max(longestUnivaluePath(root.left), longestUnivaluePath(root.right)); 15 | return Math.max(sub, helper(root.left, root.val) + helper(root.right, root.val)); 16 | }; 17 | 18 | function helper(node, parent) { 19 | if (!node || node.val !== parent) return 0; 20 | return 1 + Math.max(helper(node.left, node.val), helper(node.right, node.val)) 21 | } 22 | 23 | /** 24 | * 分三种情况讨论,一种是左子树的最长题解,一种是右子树的最长题解,一种是以根节点为目标值的跨越左右子树的最长题解 25 | */ -------------------------------------------------------------------------------- /Tree/[H]124.Binary Tree Maximum Path Sum.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for a binary tree node. 3 | * function TreeNode(val) { 4 | * this.val = val; 5 | * this.left = this.right = null; 6 | * } 7 | */ 8 | /** 9 | * @param {TreeNode} root 10 | * @return {number} 11 | */ 12 | var maxPathSum = function(root) { 13 | let res = Number.MIN_SAFE_INTEGER; 14 | (function helper(node) { 15 | if (!node) return 0; 16 | let left = Math.max(helper(node.left), 0); 17 | let right = Math.max(helper(node.right), 0); 18 | res = Math.max(res, left + right + node.val); 19 | return Math.max(left, right) + node.val; 20 | })(root); 21 | return res; 22 | }; 23 | 24 | /** 25 | * 关键在于后序回溯出每个节点满足题意的最大值,最大值应满足当前节点值加上左右分支更大的一边, 26 | * 当最大值为负数时可以舍弃。 27 | */ -------------------------------------------------------------------------------- /Tree/[H]145.Binary Tree Postorder Traversal.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for a binary tree node. 3 | * function TreeNode(val) { 4 | * this.val = val; 5 | * this.left = this.right = null; 6 | * } 7 | */ 8 | /** 9 | * @param {TreeNode} root 10 | * @return {number[]} 11 | */ 12 | var postorderTraversal = function(root) { 13 | let res = []; 14 | let stack = []; 15 | let p = root; 16 | while (p || stack.length) { 17 | if (p) { 18 | stack.push(p); 19 | res.unshift(p.val); 20 | p = p.right; 21 | } else { 22 | let t = stack.pop(); 23 | p = t.left; 24 | } 25 | } 26 | return res; 27 | }; 28 | 29 | /** 30 | * 基础后序遍历,因为后序是左右根,可以先用根右左的顺序遍历再翻转 31 | */ -------------------------------------------------------------------------------- /Tree/[H]297.Serialize and Deserialize Binary Tree.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for a binary tree node. 3 | * function TreeNode(val) { 4 | * this.val = val; 5 | * this.left = this.right = null; 6 | * } 7 | */ 8 | 9 | /** 10 | * Encodes a tree to a single string. 11 | * 12 | * @param {TreeNode} root 13 | * @return {string} 14 | */ 15 | var serialize = function(root) { 16 | let string = ''; 17 | 18 | (function buildString(node) { 19 | if (!node) { 20 | string += 'e '; 21 | } else { 22 | string += node.val + ' '; 23 | buildString(node.left); 24 | buildString(node.right); 25 | } 26 | })(root); 27 | 28 | return string; 29 | }; 30 | 31 | /** 32 | * Decodes your encoded data to tree. 33 | * 34 | * @param {string} data 35 | * @return {TreeNode} 36 | */ 37 | var deserialize = function(data) { 38 | let nodes = data.split(' '); 39 | 40 | return (function buildTree() { 41 | let val = nodes.shift(); 42 | 43 | if (val === 'e') { 44 | return null; 45 | } else { 46 | let node = new TreeNode(Number(val)); 47 | node.left = buildTree(); 48 | node.right = buildTree(); 49 | return node; 50 | } 51 | })(); 52 | }; 53 | 54 | /** 55 | * Your functions will be called as such: 56 | * deserialize(serialize(root)); 57 | */ 58 | 59 | /** 60 | * 二叉树的序列与反序列化,那么按一种遍历方式转化为一种可反转回来的表示方法即可。 61 | * 这里选择字符串形式记录的先序遍历。 62 | */ -------------------------------------------------------------------------------- /Tree/[H]99.Recover Binary Search Tree.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for a binary tree node. 3 | * function TreeNode(val) { 4 | * this.val = val; 5 | * this.left = this.right = null; 6 | * } 7 | */ 8 | /** 9 | * @param {TreeNode} root 10 | * @return {void} Do not return anything, modify root in-place instead. 11 | */ 12 | var recoverTree = function(root) { 13 | let list = []; 14 | let val = []; 15 | helper(root, list, val); 16 | val.sort((a, b) => a - b); 17 | for (let i = 0; i < list.length; i++) { 18 | list[i].val = val[i]; 19 | } 20 | }; 21 | 22 | function helper(root, list, val) { 23 | if (!root) return; 24 | helper(root.left, list, val); 25 | list.push(root); 26 | val.push(root.val); 27 | helper(root.right, list, val); 28 | } 29 | 30 | /** 31 | * 修复有问题的二叉搜索树,可以用中序遍历获取当前节点值排序,再人为排序获取正确二叉树的顺序, 32 | * 两者比较修复错误位置 33 | */ -------------------------------------------------------------------------------- /Tree/[M]102.Binary Tree Level Order Traversal.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for a binary tree node. 3 | * function TreeNode(val) { 4 | * this.val = val; 5 | * this.left = this.right = null; 6 | * } 7 | */ 8 | /** 9 | * @param {TreeNode} root 10 | * @return {number[][]} 11 | */ 12 | var levelOrder = function(root) { 13 | let res = []; 14 | if (!root) return res; 15 | let q = [root]; 16 | while (q.length) { 17 | let level = []; 18 | let n = q.length; 19 | for (let i = 0; i < n; i++) { 20 | let node = q.shift(); 21 | level.push(node.val); 22 | if (node.left) { 23 | q.push(node.left); 24 | } 25 | if (node.right) { 26 | q.push(node.right); 27 | } 28 | } 29 | res.push(level); 30 | } 31 | return res; 32 | }; 33 | 34 | /** 35 | * 层序遍历,适合用BFS处理 36 | */ -------------------------------------------------------------------------------- /Tree/[M]103.Binary Tree Zigzag Level Order Traversal.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for a binary tree node. 3 | * function TreeNode(val) { 4 | * this.val = val; 5 | * this.left = this.right = null; 6 | * } 7 | */ 8 | /** 9 | * @param {TreeNode} root 10 | * @return {number[][]} 11 | */ 12 | var zigzagLevelOrder = function(root) { 13 | let res = []; 14 | if (!root) return res; 15 | let q = [root]; 16 | let bool = true; 17 | while (q.length) { 18 | let n = q.length; 19 | let cnt = []; 20 | for (let i = 0; i < n; i++) { 21 | let cur = q.shift(); 22 | if (cur.left) { 23 | q.push(cur.left); 24 | } 25 | if (cur.right) { 26 | q.push(cur.right); 27 | } 28 | cnt.push(cur.val); 29 | } 30 | res.push(bool ? cnt : cnt.reverse()); 31 | bool = !bool; 32 | } 33 | return res; 34 | }; 35 | 36 | /** 37 | * 层序遍历上稍微做点处理,用个布尔值记录奇偶,决定保存顺序 38 | */ -------------------------------------------------------------------------------- /Tree/[M]109.Convert Sorted List to Binary Search Tree.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for singly-linked list. 3 | * function ListNode(val) { 4 | * this.val = val; 5 | * this.next = null; 6 | * } 7 | */ 8 | /** 9 | * Definition for a binary tree node. 10 | * function TreeNode(val) { 11 | * this.val = val; 12 | * this.left = this.right = null; 13 | * } 14 | */ 15 | /** 16 | * @param {ListNode} head 17 | * @return {TreeNode} 18 | */ 19 | var sortedListToBST = function(head) { 20 | if (!head) return null; 21 | if (!head.next) return new TreeNode(head.val); 22 | let slow = head; 23 | let fast = head; 24 | let last = slow; 25 | while (fast.next && fast.next.next) { 26 | last = slow; 27 | slow = slow.next; 28 | fast = fast.next.next; 29 | } 30 | fast = slow.next; 31 | last.next = null; 32 | let cur = new TreeNode(slow.val); 33 | if (head !== slow) { 34 | cur.left = sortedListToBST(head); 35 | } 36 | cur.right = sortedListToBST(fast); 37 | return cur; 38 | }; 39 | 40 | /** 41 | * 108的follow up,排序数组变成排序链表,那么关键还是取中点,链表的中点用快慢指针解决 42 | */ -------------------------------------------------------------------------------- /Tree/[M]113.Path Sum II.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for a binary tree node. 3 | * function TreeNode(val) { 4 | * this.val = val; 5 | * this.left = this.right = null; 6 | * } 7 | */ 8 | /** 9 | * @param {TreeNode} root 10 | * @param {number} sum 11 | * @return {number[][]} 12 | */ 13 | var pathSum = function(root, sum) { 14 | let res = []; 15 | let out = []; 16 | helper(root, sum, out, res); 17 | return res; 18 | }; 19 | 20 | function helper(node, sum, out, res) { 21 | if (!node) return; 22 | out.push(node.val); 23 | if (sum === node.val && !node.left && !node.right) { 24 | res.push(Array.from(out)); 25 | } 26 | helper(node.left, sum - node.val, out, res); 27 | helper(node.right, sum - node.val, out, res); 28 | out.pop(); 29 | } 30 | 31 | /** 32 | * 和112思路一样,区别在于需要记录满足条件的路径,那么可以用一个out数组动态记录 33 | */ -------------------------------------------------------------------------------- /Tree/[M]116.Populating Next Right Pointers in Each Node.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for binary tree with next pointer. 3 | * function TreeLinkNode(val) { 4 | * this.val = val; 5 | * this.left = this.right = this.next = null; 6 | * } 7 | */ 8 | 9 | /** 10 | * @param {TreeLinkNode} root 11 | * @return {void} Do not return anything, modify tree in-place instead. 12 | */ 13 | var connect = function(root) { 14 | if (!root) return; 15 | let q = [root]; 16 | while (q.length) { 17 | let n = q.length; 18 | for (let i = 0; i < n; i++) { 19 | let cur = q.shift(); 20 | cur.next = (i === n - 1) ? null : q[0]; 21 | if (cur.left) { 22 | q.push(cur.left); 23 | } 24 | if (cur.right) { 25 | q.push(cur.right); 26 | } 27 | } 28 | } 29 | }; 30 | 31 | /** 32 | * 层序遍历给每层加上指向下一个节点的指针即可 33 | */ -------------------------------------------------------------------------------- /Tree/[M]129.Sum Root to Leaf Numbers.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for a binary tree node. 3 | * function TreeNode(val) { 4 | * this.val = val; 5 | * this.left = this.right = null; 6 | * } 7 | */ 8 | /** 9 | * @param {TreeNode} root 10 | * @return {number} 11 | */ 12 | var sumNumbers = function(root) { 13 | let res = []; 14 | let out = []; 15 | helper(root, res, out); 16 | return res.reduce((a, b) => +a + +b, 0); 17 | }; 18 | 19 | function helper(node, res, out) { 20 | if (!node) return; 21 | out.push(node.val); 22 | if (!node.left && !node.right) { 23 | res.push(out.join('')); 24 | } 25 | helper(node.left, res, out); 26 | helper(node.right, res, out); 27 | out.pop(); 28 | } 29 | 30 | /** 31 | * 记录根到叶形成数字的总数,那么需要用一个out数组记录每次遍历到根时形成的数字, 32 | * 并存到res中,遍历完成累加res获取结果 33 | */ -------------------------------------------------------------------------------- /Tree/[M]144.Binary Tree Preorder Traversal.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for a binary tree node. 3 | * function TreeNode(val) { 4 | * this.val = val; 5 | * this.left = this.right = null; 6 | * } 7 | */ 8 | /** 9 | * @param {TreeNode} root 10 | * @return {number[]} 11 | */ 12 | var preorderTraversal = function(root) { 13 | let res = []; 14 | let stack = []; 15 | let p = root; 16 | while (stack.length || p) { 17 | if (p) { 18 | stack.push(p); 19 | res.push(p.val); 20 | p = p.left; 21 | } else { 22 | t = stack.pop(); 23 | p = t.right; 24 | } 25 | } 26 | return res; 27 | }; 28 | 29 | /** 30 | * 基础前序遍历,不用递归的实现方式 31 | */ -------------------------------------------------------------------------------- /Tree/[M]173.Binary Search Tree Iterator.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for binary tree 3 | * function TreeNode(val) { 4 | * this.val = val; 5 | * this.left = this.right = null; 6 | * } 7 | */ 8 | 9 | /** 10 | * @constructor 11 | * @param {TreeNode} root - root of the binary search tree 12 | */ 13 | var BSTIterator = function(root) { 14 | this.stack = []; 15 | while (root) { 16 | this.stack.push(root); 17 | root = root.left; 18 | } 19 | }; 20 | 21 | 22 | /** 23 | * @this BSTIterator 24 | * @returns {boolean} - whether we have a next smallest number 25 | */ 26 | BSTIterator.prototype.hasNext = function() { 27 | return Boolean(this.stack.length); 28 | }; 29 | 30 | /** 31 | * @this BSTIterator 32 | * @returns {number} - the next smallest number 33 | */ 34 | BSTIterator.prototype.next = function() { 35 | let n = this.stack.pop(); 36 | let res = n.val; 37 | if (n.right) { 38 | n = n.right; 39 | while (n) { 40 | this.stack.push(n); 41 | n = n.left; 42 | } 43 | } 44 | return res; 45 | }; 46 | 47 | /** 48 | * Your BSTIterator will be called like this: 49 | * var i = new BSTIterator(root), a = []; 50 | * while (i.hasNext()) a.push(i.next()); 51 | */ 52 | 53 | /** 54 | * 迭代器实现二叉搜索树的遍历,关键在于实现一个指针始终指向当前最小节点,用栈维护遍历顺序 55 | */ -------------------------------------------------------------------------------- /Tree/[M]199.Binary Tree Right Side View.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for a binary tree node. 3 | * function TreeNode(val) { 4 | * this.val = val; 5 | * this.left = this.right = null; 6 | * } 7 | */ 8 | /** 9 | * @param {TreeNode} root 10 | * @return {number[]} 11 | */ 12 | var rightSideView = function(root) { 13 | if (!root) return []; 14 | let res = []; 15 | let q = [root]; 16 | while (q.length) { 17 | let n = q.length; 18 | res.push(q[n - 1].val); 19 | for (let i = 0; i < n; i++) { 20 | let cur = q.shift(); 21 | if (cur.left) { 22 | q.push(cur.left); 23 | } 24 | if (cur.right) { 25 | q.push(cur.right); 26 | } 27 | } 28 | } 29 | return res; 30 | }; 31 | 32 | /** 33 | * 层序遍历记录每层的最后一个值即可 34 | */ -------------------------------------------------------------------------------- /Tree/[M]230.Kth Smallest Element in a BST.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for a binary tree node. 3 | * function TreeNode(val) { 4 | * this.val = val; 5 | * this.left = this.right = null; 6 | * } 7 | */ 8 | /** 9 | * @param {TreeNode} root 10 | * @param {number} k 11 | * @return {number} 12 | */ 13 | var kthSmallest = function(root, k) { 14 | return (function helper(root) { 15 | if (!root) return -1; 16 | let val = helper(root.left); 17 | if (k === 0) return val; 18 | if (--k === 0) return root.val; 19 | return helper(root.right); 20 | })(root) 21 | }; 22 | 23 | /** 24 | * 求二叉搜索树中第k大的数,那么中序按从小到大顺序遍历即可,用参数k计数 25 | */ -------------------------------------------------------------------------------- /Tree/[M]236.Lowest Common Ancestor of a Binary Tree.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for a binary tree node. 3 | * function TreeNode(val) { 4 | * this.val = val; 5 | * this.left = this.right = null; 6 | * } 7 | */ 8 | /** 9 | * @param {TreeNode} root 10 | * @param {TreeNode} p 11 | * @param {TreeNode} q 12 | * @return {TreeNode} 13 | */ 14 | var lowestCommonAncestor = function(root, p, q) { 15 | if (!root || p === root || q === root) return root; 16 | let left = lowestCommonAncestor(root.left, p, q); 17 | if (left && left !== p && left !== q) return left; 18 | let right = lowestCommonAncestor(root.right, p, q); 19 | if (right && right !== p && right !== q) return right; 20 | if (left && right) return root; 21 | return left ? left : right; 22 | }; 23 | 24 | /** 25 | * 235的follow up,不再限定二叉搜索树,那么需要用后序遍历对每个左右节点查找,当左右都能找到 26 | * 目标节点时,该节点就为所求。 27 | */ -------------------------------------------------------------------------------- /Tree/[M]337.House Robber III.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for a binary tree node. 3 | * function TreeNode(val) { 4 | * this.val = val; 5 | * this.left = this.right = null; 6 | * } 7 | */ 8 | /** 9 | * @param {TreeNode} root 10 | * @return {number} 11 | */ 12 | var rob = function(root) { 13 | let map = new Map(); 14 | return helper(root, map); 15 | }; 16 | 17 | function helper(node, map) { 18 | if (!node) return 0; 19 | if (map.has(node)) { 20 | return map.get(node); 21 | } 22 | let val = 0; 23 | if (node.left) { 24 | val += helper(node.left.left, map) + helper(node.left.right, map); 25 | } 26 | if (node.right) { 27 | val += helper(node.right.left, map) + helper(node.right.right, map); 28 | } 29 | val = Math.max(val + node.val, helper(node.left, map) + helper(node.right ,map)); 30 | map.set(node, val); 31 | return val; 32 | } 33 | 34 | /** 35 | * 本质上是求二叉树上不相邻的节点最大和,那么递归时对每个节点都要考虑取该节点和不取该节点两种情况, 36 | * 取其中最大值,防止递归时的重复计算可以新建一个map保存重复路径。 37 | */ -------------------------------------------------------------------------------- /Tree/[M]94.Binary Tree Inorder Traversal.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for a binary tree node. 3 | * function TreeNode(val) { 4 | * this.val = val; 5 | * this.left = this.right = null; 6 | * } 7 | */ 8 | /** 9 | * @param {TreeNode} root 10 | * @return {number[]} 11 | */ 12 | var inorderTraversal = function(root) { 13 | let res = []; 14 | let stack = []; 15 | let p = root; 16 | while (p || stack.length) { 17 | if (p) { 18 | stack.push(p); 19 | p = p.left; 20 | } else { 21 | let t = stack.pop(); 22 | res.push(t.val); 23 | p = t.right; 24 | } 25 | } 26 | return res; 27 | }; 28 | 29 | /** 30 | * 基础中序遍历,非递归实现 31 | */ -------------------------------------------------------------------------------- /Tree/[M]96.Unique Binary Search Trees.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param {number} n 3 | * @return {number} 4 | */ 5 | var numTrees = function(n) { 6 | let dp = Array.from({length: n + 1}, _ => 0); 7 | dp[0] = 1; 8 | dp[1] = 1; 9 | for (let i = 2; i <= n; i++) { 10 | for (let j = 0; j < i; j++) { 11 | dp[i] += dp[j] * dp[i - j - 1]; 12 | } 13 | } 14 | return dp[n]; 15 | }; 16 | 17 | /** 18 | * 求给定节点数的二叉搜索树种类用整体法考虑,每多一个节点增加一层为根节点,左右都是新的二叉搜索树。 19 | * 根节点可以为节点值不大于i的n种情况,对根节点为j + 1时,左边搜索树数量为dp[j],右边则为dp[i - j - 1]。 20 | */ -------------------------------------------------------------------------------- /Tree/[M]98.Validate Binary Search Tree.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for a binary tree node. 3 | * function TreeNode(val) { 4 | * this.val = val; 5 | * this.left = this.right = null; 6 | * } 7 | */ 8 | /** 9 | * @param {TreeNode} root 10 | * @return {boolean} 11 | */ 12 | var isValidBST = function(root) { 13 | return helper(root, Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER); 14 | }; 15 | 16 | function helper(node, min, max) { 17 | if (!node) return true; 18 | if (node.val <= min || node.val >= max) return false; 19 | return helper(node.left, min, node.val) && helper(node.right, node.val, max); 20 | } 21 | 22 | /** 23 | * 验证二叉搜索树,DFS按二叉搜索树性质检测即可 24 | */ --------------------------------------------------------------------------------