├── 1.TwoSum ├── README.md └── solution.cpp ├── 100-SameTree ├── 100.cpp └── README.md ├── 102-Binary Tree Level Order Traversal ├── README.md └── solution.cpp ├── 103. Binary Tree Zigzag Level Order Traversal ├── README.md └── solution.cpp ├── 105-buildTree ├── README.md └── solution.cpp ├── 106-buildTree ├── README.md └── solution.cpp ├── 108-sortedArrayToBST ├── .DS_Store ├── README.md └── solution.cpp ├── 109-sortedListToBST ├── README.md └── solution.cpp ├── 11.ContainerWithMostWater ├── README.md └── solution.cpp ├── 112-hasPathSum ├── .DS_Store ├── README.md └── solution.cpp ├── 113-pathSum ├── .DS_Store ├── README.md └── solution.cpp ├── 115-DistinctSubsequences ├── README.md └── solution.cpp ├── 119-Pascal'sTriangleII ├── README.md └── Solution.cpp ├── 12 Integer to Roman ├── README.md └── solution.cpp ├── 120.Triangle ├── README.md └── solution.cpp ├── 121. Best Time to Buy and Sell Stock ├── README.md └── solution.cpp ├── 122. Best Time to Buy and Sell Stock II ├── README.md └── solution.cpp ├── 123-Best Time to Buy and Sell Stock III ├── README.md └── solution.cpp ├── 124-Binary Tree Maximum Path Sum ├── README.md └── Solution.cpp ├── 125-ValidPalindrome ├── README.md └── Solution.cpp ├── 126-WordLadderII ├── README.md └── solution.cpp ├── 128-Longest Consecutive Sequence ├── README.md └── solution.cpp ├── 129-SumRootToLeafNumbers ├── README.md ├── solution1.cpp └── solution2.cpp ├── 13 Roman to integer ├── README.md └── solution.cpp ├── 130-SurroundedRegions ├── README.md ├── solution1.cpp └── solution2.cpp ├── 132-PalindromePartitioningII ├── README.md └── solution.cpp ├── 134-GasStation ├── README.md └── solution.cpp ├── 135-Candy ├── README.md └── solution.cpp ├── 138-CopyListwithRandomPointer ├── README.md └── Solution.cpp ├── 139.WordBreak ├── README.md └── solution.cpp ├── 14.Longest Common Prefix ├── README.md └── solution.cpp ├── 140-WordBreakII ├── README.md └── solution.cpp ├── 143-ReorderList ├── README.md └── solution.cpp ├── 144-BinaryTreePreorderTraversal ├── README.md ├── solution1.cpp ├── solution2.cpp └── solution3.cpp ├── 145-postorderTraversal ├── README.md └── solution.cpp ├── 146-LRUCache ├── README.md └── solution.cpp ├── 149-MaxPointsOnALine ├── README.md └── solution.cpp ├── 15.3Sum ├── README.md └── solution.cpp ├── 151-ReverseWordsinaString ├── README.md ├── Solution.cpp └── Solution.py ├── 152.MaximumProductSubarray ├── README.md └── solution.cpp ├── 154-Find Minimum in Rotated Sorted Array II ├── README.md ├── solution1.cpp ├── solution2.cpp └── solution3.cpp ├── 155. Min Stack ├── README.md └── solution.cpp ├── 16.3SumClosest ├── README.md └── solution.cpp ├── 164-Maximum Gap ├── README.md └── solution.cpp ├── 17.LetterCombinationsofaPhoneNumber ├── README.md └── solution.md ├── 174-calculateMinimumHP ├── README.md ├── solution1.cpp └── solution2.cpp ├── 18.4Sum ├── README.md └── solution.cpp ├── 188-maxProfit ├── README.md └── solution.cpp ├── 189 Rotate Array ├── README.md └── solution.cpp ├── 198.HouseRobber ├── README.md └── solution.cpp ├── 2-addTwoNumbers ├── README.md └── solution.cpp ├── 20.ValidParentheses ├── README.md └── solution.cpp ├── 207- Course Schedule ├── README.md ├── Solution1.cpp └── Solution2.cpp ├── 210-Course Schedule II ├── README.md └── Solution.cpp ├── 213-HouseRobberII ├── README.md └── solution.cpp ├── 214-shortestPalindrome ├── README.md └── solution.cpp ├── 216-CombinationSumIII ├── README.md └── solution.cpp ├── 22.GenerateParentheses ├── README.md └── solution.cpp ├── 221-MaximalSquare ├── README.md └── solution1.cpp ├── 225.ImplementStackusingQueues ├── README.md └── solution.cpp ├── 228-Summary Ranges ├── README.md └── Solution.cpp ├── 229- Majority Element II ├── README.md └── Solution.cpp ├── 23-mergeKLists ├── README.md └── solution.cpp ├── 233-NumberofDigitOne ├── README.md └── solution.cpp ├── 238-ProductofArrayExceptSelf ├── README.md └── solution.cpp ├── 239-SlidingWindowMaximum ├── README.md └── solution.cpp ├── 25-ReverseNodesink-Group ├── README.md └── solution.cpp ├── 26. Remove Duplicates from Sorted Array ├── README.md └── solution.cpp ├── 264-UglyNumberII ├── README.md └── solution.cpp ├── 27. Remove Element ├── README.md └── solution.cpp ├── 273-Integer to English Words ├── README.md └── solution.cpp ├── 279-PerfectSquares ├── README.md └── solution.cpp ├── 282-Expression Add Operators ├── README.md └── solution.cpp ├── 283-MoveZeros ├── README.md └── solution.cpp ├── 287-findtheduplicatenumber ├── README.md └── solution.cpp ├── 289-gameoflife ├── README.md └── solution.cpp ├── 295-FindMedianFromDataStream ├── README.md └── solution.cpp ├── 297-SerializeandDeserializeBinaryTree ├── README.md └── Solution.py ├── 3-lengthOfLongestSubstring ├── README.md └── solution.cpp ├── 30-SubstringWithConcatenationOfAllWords ├── README.md └── solution.cpp ├── 300-LongestIncreasingSubsequence ├── README.md ├── solution1.cpp └── solution2.cpp ├── 303.RangeSumQuery-Immutable ├── README.md ├── solution1.cpp └── solution2.cpp ├── 309-BestTimetoBuyandSellStockwithCooldown ├── README.md └── solution.cpp ├── 310-Minimum Height Trees ├── README.md ├── Solution1.cpp └── Solution2.cpp ├── 312-Burst Balloons ├── README.md └── solution.cpp ├── 315-CountOfSmallerNumbersAfterSelf ├── README.md └── solution.cpp ├── 316-RemoveDuplicateLetters ├── README.md └── solution.cpp ├── 32-longestValidParentheses ├── README.md └── solution.cpp ├── 321-Create Maximum Number ├── README.md └── solution.cpp ├── 322-CoinChange ├── README.md └── solution.cpp ├── 327-CountOfRangeSum ├── README.md └── solution.cpp ├── 329-Longest Increasing Path in a Matrix ├── README.md └── Solution.cpp ├── 33-SearchInRotatedSortedArray ├── README.md ├── solution1.cpp └── solution2.cpp ├── 330-PatchingArray ├── README.md └── solution.cpp ├── 332-Reconstruct Itinerary ├── README.md └── Solution.cpp ├── 335-Self Crossing ├── README.md └── solution.cpp ├── 336-PalindromePairs ├── README.md ├── solution.cpp └── solution2.cpp ├── 338.CountingBits ├── README.md └── solution.cpp ├── 343-IntegerBreak ├── README.md └── solution.cpp ├── 349.Intersection of Two Arrays ├── README.md └── solution.cpp ├── 354-RussianDollEnvelopes ├── README.md └── solution.cpp ├── 357.CountNumberswithUniqueDigits ├── README.md └── solution.cpp ├── 363-MaxSumofRectangleNoLargerThanK ├── README.md └── Solution.cpp ├── 368-LargestDivisibleSubset ├── README.md └── solution.cpp ├── 375-GuessNumberHigherOrLowerII ├── README.md └── solution.cpp ├── 376-WiggleSubsequence ├── README.md └── solution.cpp ├── 380-Insert Delete GetRandom O-1 ├── README.md └── Solution.cpp ├── 381-Insert Delete GetRandom O-1- - Duplicates allowed ├── README.md └── Solution.cpp ├── 39-CombinationSum ├── README.md └── solution.cpp ├── 392-IsSubsequence ├── README.md └── solution.cpp ├── 394-Decode String ├── README.md └── Solution.cpp ├── 399-Evaluate Division ├── README.md ├── Solution1.cpp └── Solution2.cpp ├── 4.MedianofTwoSortedArrays ├── README.md └── solution.cpp ├── 401-BinaryWatch ├── README.md └── solution.cpp ├── 403. Frog Jump ├── README.md └── solution.cpp ├── 407-TrappingRainWaterII ├── README.md └── solution.cpp ├── 41-FirstMissingPositive ├── README.md └── solution.cpp ├── 410-SplitArrayLargestSum ├── README.md └── solution.cpp ├── 413-Arithmeticslices ├── README.md └── solution.cpp ├── 414.第三大数 ├── README.md └── solution.cpp ├── 417- Pacific Atlantic Water Flow ├── README.md └── Solution.cpp ├── 42-Trapping Rain Water ├── README.md └── solution.cpp ├── 420-StrongPasswordChecker ├── README.md └── solution.cpp ├── 435-Non-overlappingIntervals ├── README.md └── solution.cpp ├── 44-Wildcard Matching ├── README.md └── solution.cpp ├── 440-K-th Smallest in Lexicographical Order ├── README.md └── solution.cpp ├── 446-ArithmeticSlicesII-Subsequence ├── README.md └── solution.cpp ├── 448. Find All Numbers Disappeared in an Array ├── README.md └── solution.cpp ├── 45-JumpGameII ├── README.md ├── solution.cpp └── solution.py ├── 452-MinimumNumberofArrowstoBurstBalloons ├── README.md └── solution.cpp ├── 466-CountTheRepetitions ├── README.md └── solution.cpp ├── 472-ConcatenatedWords ├── README.md └── solution.cpp ├── 474 Ones and Zeroes ├── README.md └── solution.cpp ├── 480-SlidingWindowMedian ├── README.md └── solution.cpp ├── 483-SmallestGoodBase ├── README.md └── solution.cpp ├── 485. Max Consecutive Ones ├── README.md └── solution.cpp ├── 486-PredictTheWinner ├── README.md └── solution.cpp ├── 488-Zuma Game ├── README.md └── solution.cpp ├── 491. Increasing Subsequences ├── README.MD └── Solution.cpp ├── 493-ReversePairs ├── README.md ├── solution.cpp └── solution0.cpp ├── 494-TargetSum ├── README.md └── solution.cpp ├── 495-TeemoAttacking ├── README.md └── solution.cpp ├── 5-longestPalindrome ├── README.md └── solution.cpp ├── 502-IPO ├── README.md └── solution.cpp ├── 51- N-Queens ├── README.md └── Solution.cpp ├── 513-Find Bottom Left Tree Value ├── 513.cpp ├── 513解法一.cpp └── solution for 513.md ├── 514-Freedom Trail ├── README.md └── solution.cpp ├── 515-Find Largest Value in Each Tree Row ├── 515.cpp └── solution for 515.md ├── 516-LongestPalindromicSubsequence ├── README.md ├── solution1.cpp └── solution2.cpp ├── 517-SuperWashingMachines ├── README.md └── solution.cpp ├── 52-N-Queens II ├── README.MD └── Solution.cpp ├── 520.Detect Capital ├── README.md └── solution.cpp ├── 520.Detect Capital的副本 ├── README.md └── solution.cpp ├── 521. Longest Uncommon Subsequence I ├── README.md └── solution.cpp ├── 523-ContinuousSubarraySum ├── README.md └── solution.cpp ├── 525- Contiguous Array ├── README.md └── Solution.cpp ├── 529. Minesweeper ├── README.md └── Solution.cpp ├── 532-K-diffPairsinanArray ├── README.md └── solution.cpp ├── 535- Encode and Decode TinyURL ├── README.md └── Solution.cpp ├── 538-ConvertBSTtoGreaterTree ├── README.md └── solution.cpp ├── 541. Reverse String II ├── README.md └── solution.cpp ├── 542-01 Matrix ├── README.md └── Solution.cpp ├── 546-RemoveBoxes ├── README.md └── solution.cpp ├── 547-findCircleNum ├── README.md └── solution.cpp ├── 55-JumpGame ├── README.md └── solution.cpp ├── 551-Student Attendance Record I ├── README.md └── solution.cpp ├── 552-Student Attendance Record II ├── README.md └── solution.cpp ├── 553-Optimal Division ├── README.md └── solution.cpp ├── 554-Brick Wall ├── README.md └── solution.cpp ├── 557.Reverse Words in a String III ├── README.md └── solution.cpp ├── 557.Reverse Words in a String III的副本 ├── README.md └── solution.cpp ├── 56-MergeIntervals ├── README.md └── solution.cpp ├── 564-FindtheClosestPalindrome ├── README.md └── Solution.py ├── 57-InsertInterval ├── README.md └── solution1.cpp ├── 576-OutOfBoundaryPaths ├── README.md └── solution.cpp ├── 581-ShortestUnsortedContinuousSubarray ├── README.md └── Solution.cpp ├── 582-KillProcess ├── README.md └── Solution.cpp ├── 59-generateMatrix ├── README.md └── solution.cpp ├── 6-convert ├── README.md └── solution.cpp ├── 60-getPermutation ├── README.md └── solution.cpp ├── 61-RotateList ├── README.md └── Solution.cpp ├── 62-Unique Paths ├── README.MD └── Solution.cpp ├── 62.UniquePaths ├── README.md └── solution.cpp ├── 63-Unique Paths II ├── README.md └── Solution.cpp ├── 63.UniquePathsII ├── README.md └── solution.cpp ├── 64.MinimumPathSum ├── README.md └── solution.cpp ├── 65-Valid Number ├── README.md └── solution.cpp ├── 68-Text Justification ├── README.md └── solution.cpp ├── 7. Reverse Integer ├── README.md └── solution.cpp ├── 70.ClimbingStairs ├── README.md └── solution.cpp ├── 71.SimplifyPath ├── README.md └── solution.cpp ├── 72-Edit Distance ├── README.md └── solution.cpp ├── 73.Set_Matrix_Zeroes ├── README.md └── solution.cpp ├── 75.sort-clors ├── README.md └── solution.cpp ├── 76-Minimum Window Substring ├── README.md └── solution.cpp ├── 78.Subsets ├── README.md ├── solution1.cpp └── solution2.cpp ├── 79.Word_Search ├── README.md └── solution.cpp ├── 8-myAtoi ├── README.md └── solution.cpp ├── 81 Search in Rotated Sorted Array II ├── README.md └── solution.cpp ├── 84-LargestRectangleInHistogram ├── README.md ├── solution1.cpp ├── solution2.cpp └── solution3.cpp ├── 85-MaximalRectangle ├── README.md ├── solution1.cpp └── solution2.cpp ├── 87-ScrambleString ├── README.md └── solution.cpp ├── 89-Gray Code ├── README.MD └── Solution.cpp ├── 9-isPalindrome ├── README.md └── solution.cpp ├── 90-Subsets II ├── README.md └── Solution.cpp ├── 91.DecodeWays ├── README.md └── solution.cpp ├── 94.BinaryTreeInorderTraversal ├── README.md └── solution.cpp ├── 96-UniqueBinarySearchTrees ├── 96.cpp └── README.md ├── 97-isInterleave ├── README.md └── solution.cpp ├── 98-isValidBST ├── README.md └── solution.cpp ├── 99-recoverTree ├── README.md └── solution.cpp └── README.md /1.TwoSum/solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | vector twoSum(vector& nums, int target) { 4 | 5 | vectorans; 6 | unordered_mapmyMap; 7 | unordered_map::iterator it; 8 | 9 | int len = nums.size(); 10 | for(int i = 0; i < len; i ++) { 11 | it = myMap.find(target - nums[i]); 12 | if(it != myMap.end()) { 13 | ans.push_back(it->second); 14 | ans.push_back(i); 15 | return ans; 16 | } 17 | myMap[nums[i]] = i; 18 | } 19 | return ans; 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /100-SameTree/100.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leetcode-Tutorial/Tutorial/ef9f3bdaa3d3fcf78f5f266549cad3ee3145844f/100-SameTree/100.cpp -------------------------------------------------------------------------------- /102-Binary Tree Level Order Traversal/README.md: -------------------------------------------------------------------------------- 1 | ## 题目分析 2 | 这道题是给定一个树,要求输出的结果中,将同一层结点元素放入同一个vector中,返回这个结果即可! 3 | 4 | ### 解题思路 5 | 树的问题,我们仔细思考,总归是要解决树的遍历问题,只要完成了遍历,每个结点访问到了,那么我再对该结点进行相关操作即可。该题要求同一层的结点元素要放到一个vector中,那么我们自然就想到层次遍历的方法,利用队列的性质,每一次得到每一层的结点数量,然后将该层所有结点保存到vector即可。来一步一步实现题目的要求,例子会详细给出解释! 6 | 7 | ### 举一个例子走一遍算法: 8 | 先观察: 9 | ```cpp 10 | 3 11 | /\ 12 | 9 20 13 | /\ 14 | 15 7 15 | ``` 16 | (1) 首先我们得到了root, 就是3这个节点, 加入队列,放入当前节点列表ans, 因为只有root一个, 这一层我们结束了, 所以size为1(3出队列),输出一个加入到res即可,左右孩子(9,20)入队列,此时res为[[3]] 17 | 18 | (2)当队列不为空,继续循环,此时第二层有俩个结点,size为2,分别进行出队列,左右孩子入队列动作,9出队列,没有孩子,20出队列,15,7入队列,该层结束后,res为[[3],[9,20]] 19 | 20 | (3)如法炮制:当第三层15,7出队列完时候,没有左右孩子,队列为空,最终结果res为[[3],[9,20],[15,7]] 21 | ```cpp 22 | 算法复杂度,由于每个结点出队列入队列一次,为0(n) 23 | ``` 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /103. Binary Tree Zigzag Level Order Traversal/README.md: -------------------------------------------------------------------------------- 1 | ## 题目分析 2 | 这道题与102题非常相似,102题是给定一个树,要求输出的结果中,将同一层结点元素放入同一个vector中,返回这个结果即可!而这道题是给定一棵树,奇数行正序放入vector,偶数逆序放入vector,最终返回结果即可,差别就在于偶数行,需要逆序,那么太简单了,代码都不需要改多少,只需要判断一下是否偶数行,然后reverse一下即可 3 | 4 | ### 解题思路 5 | 树的问题,我们仔细思考,总归是要解决树的遍历问题,只要完成了遍历,每个结点访问到了,那么我再对该结点进行相关操作即可。该题要求同一层的结点元素要放到一个vector中,那么我们自然就想到层次遍历的方法,利用队列的性质,每一次得到每一层的结点数量,然后将该层所有结点保存到vector即可。来一步一步实现题目的要求,例子会详细给出解释! 6 | 7 | ### 举一个例子走一遍算法: 8 | 先观察: 9 | ```cpp 10 | 3 11 | /\ 12 | 9 20 13 | /\ 14 | 15 7 15 | ``` 16 | (1)首先我们得到了root, 就是3这个节点, 加入队列,放入当前节点列表ans, 因为只有root一个, 这一层我们结束了, 所以size为1(3出队列),输出一个加入到res即可,左右孩子(9,20)入队列,此时res为[[3]] 17 | 18 | (2)当队列不为空,继续循环,此时第二层(偶数行需要reverse)有俩个结点,size为2,分别进行出队列,左右孩子入队列动作,9出队列,没有孩子,20出队列,经过reverse之后,我们就得到了逆序15,7入队列,该层结束后,res为[[3],[20,9]] 19 | 20 | (3)如法炮制:当第三层15,7出队列完时候,没有左右孩子,队列为空,最终结果res为[[3],[20,9],[15,7]] 21 | ```cpp 22 | 算法复杂度,由于每个结点出队列入队列一次,为0(n) 23 | ``` 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /105-buildTree/README.md: -------------------------------------------------------------------------------- 1 | ## 题目分析 2 | 3 | 给出树的先序遍历和中序遍历,要求还原整棵树。 4 | 5 | ## 题解思路 6 | 7 | 非常经典的树上递归题。 8 | 9 | > 回顾一下先序遍历和中序遍历的顺序。先序遍历的递归定义是`根-左子树-右子树`,中序遍历的递归定义是`左子树-根-右子树`。 10 | 11 | 熟悉了这些以后就可以递归还原全树。传递四个参数:`l1, r1, l2, r2`,表示现在正在处理先序遍历的`[l1, r1]`和中序遍历的`[l2, r2]`。 12 | 根据定义先序遍历的`[l1]`这个节点是根节点,我们找到这个根节点在中序遍历`[l2, r2]`中的位置mid,显然此时也就知道了这个根左右子树的size(mid左边的那一段就是左子树size,右边那一段就是右子树size),于是我们成功将先序遍历序列划分成了`[root(l1)],[左子树],[右子树]`,将中序遍历序列划分成了`[左子树],[root(mid)],[右子树]`,现在问题就变成了:已经找到了root,构造它的左子树和右子树,由于我们已经知道了左右子树分别的下标边界,递归构造即可。 13 | 14 | ## 复杂度分析 15 | 16 | 递归构造后每个节点会进入一次,这里的复杂度是`O(n)`,进入每个节点中的主要开销在于*从中序遍历中寻找某一个值的位置*,可以直接循环从中序遍历中查找,复杂度是`O(n)`,总时间复杂度是`O(n^2)`;更加快速的做法是预处理把所有值和下标存入一个map映射中,需要查询时直接查找映射即可,这个做法中预处理复杂度`O(n*lgn)`,查找复杂度`O(lgn)`,递归到节点查找一次,总时间复杂度是`O(n*lgn)`。 -------------------------------------------------------------------------------- /106-buildTree/README.md: -------------------------------------------------------------------------------- 1 | ## 题目分析 2 | 3 | 给出树的中序遍历和后序遍历,要求还原整棵树。 4 | 5 | ## 题解思路 6 | 7 | 非常经典的树上递归题。和上一题完全一样的做法,只不过每次的根是后序遍历序列的尾部节点,将后序遍历序列按照`[左子树][右子树][root]`划分即可。代码也是稍作修改即可。 8 | 9 | -------------------------------------------------------------------------------- /108-sortedArrayToBST/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leetcode-Tutorial/Tutorial/ef9f3bdaa3d3fcf78f5f266549cad3ee3145844f/108-sortedArrayToBST/.DS_Store -------------------------------------------------------------------------------- /108-sortedArrayToBST/README.md: -------------------------------------------------------------------------------- 1 | ## 题目分析 2 | 3 | 给出一个升序数组,构造出一个平衡二叉树。 4 | 5 | ## 题解思路 6 | 7 | 考虑将数组`[1,n]`分成`[1,mid-1][mid][mid+1,n]`,其中`mid=[n/2]`,那么这个树肯定是平衡的,因为左右子树规模都是整个树规模的一半。那么按照这个方法递归建树即可。 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /108-sortedArrayToBST/solution.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for a binary tree node. 3 | * struct TreeNode { 4 | * int val; 5 | * TreeNode *left; 6 | * TreeNode *right; 7 | * TreeNode(int x) : val(x), left(NULL), right(NULL) {} 8 | * }; 9 | */ 10 | class Solution { 11 | public: 12 | TreeNode* solve (vector& nums, int l, int r) { 13 | TreeNode *u = NULL; 14 | int pos = (l+r+1)/2; 15 | u = new TreeNode (nums[pos]); 16 | if (pos > l) { 17 | u->left = solve (nums, l, pos-1); 18 | } 19 | if (pos < r) { 20 | u->right = solve (nums, pos+1, r); 21 | } 22 | return u; 23 | } 24 | TreeNode* sortedArrayToBST(vector& nums) { 25 | TreeNode *root = NULL; 26 | if (nums.size () == 0) return root; 27 | root = solve (nums, 0, nums.size ()-1); 28 | return root; 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /109-sortedListToBST/README.md: -------------------------------------------------------------------------------- 1 | ## 题目分析 2 | 3 | 给出一个升序链表,构造出一个平衡二叉树。 4 | 5 | ## 题解思路(1) 6 | 7 | 根据108的方法,我们显然可以通过`O(n)`的时间复杂度将链表里面的元素抓下来存到数组中,那么直接按照108的做法构建即可。这样做的时间复杂度达到了`O(n)`,但是有额外`O(n)`的空间开销,也就是我们用来存链表元素的数组。 8 | 9 | ## 题解思路(2) 10 | 11 | 考虑避免额外空间消耗的做法。对于链表`[1->2->3...->n]`,按照我们的做法要构造`[1->2->...->mid-1][mid][mid+1->mid+2...->n]`,只需要在递归的时候传递当前链表指针的引用即可。每当构造完左子树,指针便已经指向了当前子树的根,具体的: 12 | 13 | ``` 14 | function (l, r, pointer) { 15 | 计算mid=ceil((l+r)/2) 16 | 递归构造(l,mid-1,pointer) 17 | 给当前子树根节点赋值 18 | pointer=pointer->next 19 | 递归构造(mid+1,r,pointer) 20 | 返回当前子树 21 | } 22 | ``` 23 | 24 | 这样空间额外开销就没有了。容易发现时间复杂度也是`O(n)`。 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /11.ContainerWithMostWater/solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | int calArea(int left, int right, vector& height) { 4 | int realHeight = min(height[left], height[right]); 5 | int width = right - left; 6 | return width * realHeight; 7 | } 8 | int maxArea(vector& height) { 9 | 10 | int left = 0, right = height.size() - 1; 11 | int ans = calArea(left, right, height); 12 | 13 | while(left < right) { 14 | if(height[left] <= height[right]) { 15 | left ++; 16 | } else { 17 | right --; 18 | } 19 | ans = max(ans, calArea(left, right, height)); 20 | } 21 | return ans; 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /112-hasPathSum/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leetcode-Tutorial/Tutorial/ef9f3bdaa3d3fcf78f5f266549cad3ee3145844f/112-hasPathSum/.DS_Store -------------------------------------------------------------------------------- /112-hasPathSum/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## 题目分析 3 | 4 | 给出一棵二叉树,询问是够存在根到叶子的路径使得路径上节点权值和等于sum。 5 | 6 | ## 题解思路 7 | 8 | 非常简单的递归,对于某一个节点u,根到它的路径是确定的,假设跟到u经过的节点权值和是sum1,那么问题就变成了**对于u这棵子树,是够存在u到叶子的路径使得路径上节点权值和等于sum-sum1**,这样每次递归到一个子树判断是否存在路径即可。注意边界情况,也就是递归到叶子节点时,如果此时需要的权值等于这个叶子节点的权值,那么这个叶子就是路径的终点。我们假设`f(u,sum)`表示u为根的子树是否存在到叶子点权和为sum的路径,那么对于`f(u,sum)`,我们只需要递归判断`f(lson[u],sum-val[u])`和`f(rson[u],sum-val[u])`是否有一个成立即可。 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /112-hasPathSum/solution.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for a binary tree node. 3 | * struct TreeNode { 4 | * int val; 5 | * TreeNode *left; 6 | * TreeNode *right; 7 | * TreeNode(int x) : val(x), left(NULL), right(NULL) {} 8 | * }; 9 | */ 10 | class Solution { 11 | public: 12 | bool dfs (TreeNode *u, int sum) { //判断从u出发能够找到长度是sum的路径 13 | sum -= u->val; 14 | if (sum == 0 && u->left == NULL && u->right == NULL) return true; 15 | if (u->left && dfs (u->left, sum)) return 1; 16 | if (u->right && dfs (u->right, sum)) return 1; 17 | return 0; 18 | } 19 | bool hasPathSum(TreeNode* root, int sum) { 20 | if (root == NULL) return false; 21 | else return dfs (root, sum); 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /113-pathSum/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leetcode-Tutorial/Tutorial/ef9f3bdaa3d3fcf78f5f266549cad3ee3145844f/113-pathSum/.DS_Store -------------------------------------------------------------------------------- /113-pathSum/README.md: -------------------------------------------------------------------------------- 1 | ## 题目分析 2 | 3 | 给出一棵二叉树,输出所有根到叶子的路径上节点权值和等于sum的路径。 4 | 5 | ## 题解思路 6 | 7 | 在112的基础上我们只需要增加一个数组,每当进入一个节点的时候把它放在数组后面,每次要返回时就从数组后面消除它,这样数组中保存的就是根到当前节点的路径。根据112的思路,每次找到合法的叶子时,把保存路径的数组保存在答案中即可。 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /115-DistinctSubsequences/README.md: -------------------------------------------------------------------------------- 1 | ## 题目分析: 2 | 3 | 给出两个字符串s和t,问在s中可以找到几个与t相同的子序列 4 | 5 | ### 解题思路: 6 | 7 | 思路是dp,`dp[i][j]`表示在字符串`s[0..j-1]`中有多少个子序列等于字符串`t[0..i-1]`,那么对于`dp[i][j]`就有两种情况转移: 8 | 1. 如果`t[i - 1] != s[j - 1]`,则`dp[i][j] = dp[i][j - 1]`;因为`s[j - 1]`已经不能参与构成`t[0..i-1]`了。 9 | 2. 如果`t[i - 1] == s[j - 1]`,则`dp[i][j] = dp[i][j - 1] + dp[i - 1][j - 1]`;因为如果`s[j - 1]`和`t[i - 1]`相同,那么找到的符合条件的子序列就有两种,一种是以`s[j - 1]`作为末尾的,这样的情况数是`dp[i - 1][j - 1]`,另一种是不以`s[j - 1]`作为末尾的,这样的情况数是`dp[i][j - 1]`。 10 | 这样状态转移方程就出来了,剩下的是初始化,因为`s[0..j-1]`中一定能找到一个空序列,所以初始化为`dp[0][j] = 1`,其中j范围从0到n。 11 | 12 | #### 算法正确性: 13 | 14 | 举例说明:如`s = "abacbc"`,`t = "abc"`,按照上述思路,因为`s[6] == t[3]`,那么考虑`dp[3][6]`就要从两种情况转移: 15 | 1. 构成t的字符串用到了s[6],那就要计算在s[1..5]的范围内能组成t[1..2]的方案数,也就是从dp[2][5]转移过来。 16 | 2. 构成t的字符串没有用到s[6],那就要计算在s[1..5]的范围内能组成t[1..3]的方案数,也就是从dp[3][5]转移过来。 17 | 状态转移考虑的两种情况没有重复和遗漏,且初始化也符合常理,所以算法正确,时间复杂度为`O(n*m)`。 18 | 19 | -------------------------------------------------------------------------------- /115-DistinctSubsequences/solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | int numDistinct(string s, string t) { 4 | int m = t.length(), n = s.length(); 5 | vector> dp(m + 1, vector (n + 1, 0)); 6 | for (int j = 0; j <= n; j++) dp[0][j] = 1; 7 | for (int i = 1; i <= m; i++) 8 | for (int j = 1; j <= n; j++) 9 | dp[i][j] = dp[i][j - 1] + (t[i - 1] == s[j - 1] ? dp[i - 1][j - 1] : 0); 10 | return dp[m][n]; 11 | } 12 | }; -------------------------------------------------------------------------------- /119-Pascal'sTriangleII/README.md: -------------------------------------------------------------------------------- 1 | ## 题目分析: 2 | 3 | 求Pascal三角形的第k行。 4 | 5 | ### 解题思路 6 | Pascal第k行第i个数为C(k, i), 0 <= i <= k 7 | C(k, i) = C(k, i-1)*(k-i+1)/i, 0 < i <= k 8 | 顺序求得即可. -------------------------------------------------------------------------------- /119-Pascal'sTriangleII/Solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | vector getRow(int rowIndex) { 4 | int ret = 1; 5 | vector ans = {ret}; 6 | for(int i = 1; i <= rowIndex; i++){ 7 | ret = ret*(rowIndex-i+1LL)/i; 8 | ans.push_back(ret); 9 | } 10 | return ans; 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /12 Integer to Roman/README.md: -------------------------------------------------------------------------------- 1 | ## 题目描述 2 | Given an integer, convert it to a roman numeral. 3 | 4 | Input is guaranteed to be within the range from 1 to 3999 5 | 6 | ## 解题思路 7 | 8 | 我们注意到罗马数字的字母是有规律的,可以分成几组,I(1), V(5) 是一组, X(10), L(50)是一组, C(100), D(500)是一组, M(1000), d(应该是D加一个上划线,表示5000) 是一组 ……。后一组的两个数是前一组的10倍。 9 | 10 | 对于大于10的整数,我们把该整数逐位表示成罗马数字。 11 | 12 | 个位上的数字1~9的分别为: I II III IV V VI VII VIII IX 13 | 14 | 十位上的数字1~9,只要把原来个位上的I 替换成 X, V 替换成L,X替换成C,即十位上的1~9表示的是10~90. 15 | 16 | 百位、千位以此类推。。。。。。 -------------------------------------------------------------------------------- /120.Triangle/solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | int minimumTotal(vector>& triangle) { 4 | int lenr = triangle.size(); 5 | vectordp(lenr); 6 | 7 | if(lenr == 0) return 0; 8 | 9 | dp[0] = triangle[0][0]; 10 | for(int i = 1; i < lenr; i ++) { 11 | for(int j = i; j >= 0; j --) { 12 | if(j == 0) { 13 | dp[j] = dp[j] + triangle[i][j]; 14 | } else if(j == i) { 15 | dp[j] = dp[j - 1] + triangle[i][j]; 16 | } else { 17 | dp[j] = min(dp[j - 1], dp[j]) + triangle[i][j]; 18 | } 19 | } 20 | } 21 | 22 | int ans = dp[0]; 23 | for(int j = 1; j < lenr; j ++) { 24 | ans = min(ans, dp[j]); 25 | } 26 | return ans; 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /121. Best Time to Buy and Sell Stock/README.md: -------------------------------------------------------------------------------- 1 | ## 题目分析 2 | 3 | 给出股票每天的价格,先买入再卖出,问能赚的最大价钱是多少。 4 | 5 | ### 解题思路 6 | 7 | 这是一道典型的动态规划的题目。设dp为当前步骤能赚得的最大价钱,用min来维护当前最小值,则状态转移方程为dp = max(dp, prices[i]-min),即当前利润最大值为,上一步最大值和当前价格减去当前最小值中,较大的那一个。 8 | 9 | 比如说,Input: [7, 1, 5, 3, 6, 4] 这组数据,第三天是5,dp为4,mim为1,那么遍历到第四天时,新出现的值为3-1=2,更新dp为dp(4)和2中的最大值,即为4. 10 | -------------------------------------------------------------------------------- /121. Best Time to Buy and Sell Stock/solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | int maxProfit(vector& prices) { 4 | int len = prices.size(); 5 | if(len == 0) return 0; 6 | int dp = 0; 7 | int min = prices[0]; 8 | for(int i = 1; i < len; i++){ 9 | dp = max(dp, prices[i]-min); 10 | if(prices[i] < min) min = prices[i]; 11 | } 12 | return dp; 13 | } 14 | }; -------------------------------------------------------------------------------- /122. Best Time to Buy and Sell Stock II/README.md: -------------------------------------------------------------------------------- 1 | ## 题目分析 2 | 3 | 给出股票每天的价格,先买入再卖出,不限制次数,不能同时持有多个股票,问能赚的最大价钱是多少。 4 | 5 | ### 解题思路 6 | 7 | 采用贪心的策略,N天的总利润最大,就是在一段价格上升的区间的开头买入,末尾卖出,这样收益最大。因为假如第i天买入,i+2天卖出,这种情况与i天买入,i+1天卖出并买入,i+2天卖出并买入等价。也就是说,每相邻两天的价格增长量的和就是总利润。 8 | 9 | 如果两天的增长量为负(不会赚钱),那么就不进行交易。 10 | -------------------------------------------------------------------------------- /122. Best Time to Buy and Sell Stock II/solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | int maxProfit(vector &prices){ 4 | int ans = 0; 5 | for(int i = 1; i < prices.size(); i++){ 6 | int tmp = prices[i] - prices[i-1]; 7 | if(tmp > 0){ 8 | ans += tmp; 9 | } 10 | } 11 | return ans; 12 | } 13 | }; -------------------------------------------------------------------------------- /123-Best Time to Buy and Sell Stock III/solution.cpp: -------------------------------------------------------------------------------- 1 | int maxProfit(vector& prices) 2 | { 3 | int len=prices.size(); 4 | if(len==0)return 0; 5 | int dp[len+1]; 6 | dp[0]=0;//初始化边界 7 | int minm=1e9+7;//最小价格初始化为极大值 8 | for(int i=1;i<=len;i++) 9 | { 10 | dp[i]=max(dp[i-1],prices[i-1]-minm);//更新前i天交易一次的最大收益 11 | minm=min(minm,prices[i-1]);//更新最小价格 12 | } 13 | int maxm=-1,ans=dp[len];//初始化最终结果,最大价格初始化为极小值 14 | for(int i=len;i>0;i--) 15 | { 16 | ans=max(ans,dp[i]+maxm-prices[i-1]);//将最终结果与前i天的最大收益外加第i天后的最大收益比较并更新 17 | maxm=max(maxm,prices[i-1]);//更新最大价格 18 | } 19 | return ans; 20 | } 21 | -------------------------------------------------------------------------------- /124-Binary Tree Maximum Path Sum/README.md: -------------------------------------------------------------------------------- 1 | ## 题目分析: 2 | 一棵树,树上每个节点都有对应的`value`。现要求给出树上的一条路径,使得路径上所经过的节点的`value`的累加和达到最大。路径不一定要经过根节点, 3 | 4 | ### 解题思路: 5 | 整棵树上的最大值路径的可能有以下两种 6 | ![Situation_1](http://p1.bpimg.com/1949/864c8e584f7cc107.png)![Situation_2](http://p1.bpimg.com/1949/42a12bf1d45f2715.png) 7 | **定义**: 8 | + `f[x]`为从x点开始,在树上深度依次递增,并且值最大的一条路径,暂将其命名为`最大单链`,`f[x] = max(f[x->left],f[x->right],0) + value[x]` 9 | + `max_path[x]`为以x点作为路径中深度最浅的点,所能达到的路径最大值 10 | 11 | 在`Case_1`中,最大值路径是由树上的一个节点以及其左右儿子当中的一个最大单链构成,即`max_path[x] = max(f[x->left],f[x->right]) + value[x]` 12 | 在`Case_2`中,最大路径是由一个节点以及两个儿子的最大单链共同构成,即`max_path[x] = f[x->left] + f[x->right] + value[x]`     13 | 因此我们可以使用**dfs**来对整棵树进行遍历,先计算出每个子节点的最大单链的值之后,考虑上述两种情况之中,哪种情况可以令当前节点的`max_path`达到最大值。值得注意的是,每个结点的`max_path`需要初始化为相应节点的`value`以考虑单个点的情况。由于最大路径并不一定经过根节点,在过程中及时更新全局的最大值。 14 | 每个节点仅需被访问一次,因而总体的时间复杂度为`$O(n)$` 15 | -------------------------------------------------------------------------------- /124-Binary Tree Maximum Path Sum/Solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | int maxPathSum(TreeNode* root) { 4 | int ans = root->val; 5 | dfs(root,ans); 6 | return ans; 7 | } 8 | int dfs(TreeNode* cur, int& ans) 9 | { 10 | int x = (cur->left)? dfs(cur->left, ans):0;// 如果存在左儿子,则计算出左儿子的最大单链 11 | int y = (cur->right)? dfs(cur->right, ans):0;//如果存在右儿子,则计算出右儿子的最大单链 12 | int z = x + y + cur->val;// 因为若一儿子的最大单链无法造成正贡献则直接取零,相当于不取该儿子的最大单链,所以题解中两种情况可合在一个式子来表示。计算出以当前结点为深度最小的路径的最大值 13 | ans = (z>ans)? z:ans;//更新全局的最大值 14 | x = x+cur->val; 15 | y = y+cur->val; 16 | if(x0)? x:0;//返回最大单链的值,如果其为负数则无法对父节点造成正的贡献,不如不取,即直接返回0 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /125-ValidPalindrome/README.md: -------------------------------------------------------------------------------- 1 | ## 题目分析: 2 | 3 | 给定一个字符串。字母忽略大小写, 并忽略除字母, 数字外的其它字符, 判断给定字符串是否是回文串. 4 | 5 | ### 解题思路 6 | 两迭代器分别从前, 从后往中间扫, 直至两迭代器相遇.依次判断即可. -------------------------------------------------------------------------------- /125-ValidPalindrome/Solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | bool isPalindrome(string s) { 4 | if(s.empty()) return true; 5 | auto it1 = s.begin(), it2 = s.end()-1; 6 | while(it1 < it2){ 7 | while(it1 < it2 && !(isdigit(*it1)||isalpha(*it1)) ) it1++; 8 | while(it1 < it2 && !(isdigit(*it2)||isalpha(*it2)) ) it2--; 9 | if(islower(*it1)) *it1 = toupper(*it1); 10 | if(islower(*it2)) *it2 = toupper(*it2); 11 | if(*it1++ != *it2--) return false; 12 | } 13 | return true; 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /128-Longest Consecutive Sequence/README.md: -------------------------------------------------------------------------------- 1 | ## 题目分析: 2 | 给你一个未排序的数组,要在O(n)的时间复杂度内求出最长的由数组中元素组成的连续序列的长度。 3 | 4 | ### 解题思路(1): 5 | 最直观的就是排序之后扫一遍,但这种方式时间复杂度为O(nlogn),明显不符合要求。 6 | 7 | ### 解题思路(2): 8 | 假设我们碰到一个数i,要求包含这个数的连续序列的长度,我们会去向左找i-1、i-2、i-3…是否在数组中,再向右找i+1、i+2、i+3…是否在序列中,直到找到第一个不在数组中的为止,然后即可知道i所在序列的长度。因此我们可以利用hash表,先将数组中所有数存到hash表中,然后对数组中的数扫一遍,每个数依次为中心向左向右检查是否在hash表中,直到扫到第一个不在hash表中的为止。当然,如果要开始扫的时候,这个数之前已经检查过了,那就无需再扫了,避免对一个连续序列重复扫描。这里hash表用unordered_map实现,时间复杂度为O(n)+O(n*hash表扫描效率),这里hash表扫描效率可以近似看成O(1)。 9 | 10 | #### 算法正确性: 11 | 算法的关键点在于能否保证所有连续序列都正确扫描一遍。因为每个数都会在一个连续序列中,因此每个连续序列是可以保证都检查一遍的,向左向右一个一个检查也不会将长度算错,因此上述算法是正确的。 12 | 13 | 下面举一个简单例子走一遍算法帮助理解:[100,4,200,1,3,2]。
14 | 初始化:ans=0,将100、4、200、1、3、2扔到unordered_map中,是否扫描的状态记为false;
15 | 扫描第一个数100:,还未扫描,向左:99不在unordered_map中,向右:101不在unordered_map中,ans=max(ans,101-99-1)=1;
16 | 扫描第二个数4,还未扫描,向左:3、2、1在unordered_map中0不在unordered_map中,向右:5不在unordered_map中,ans=max(ans,5-0-1)=4;
17 | 扫描第三个数200,已经扫描,跳过;
18 | 扫描第四个数1,已经扫描,跳过;
19 | 扫描第五个数3,已经扫描,跳过;
20 | 扫描第六个数2,已经扫描,跳过;
21 | 最终结果为ans=4。 22 | -------------------------------------------------------------------------------- /128-Longest Consecutive Sequence/solution.cpp: -------------------------------------------------------------------------------- 1 | int longestConsecutive(vector& nums) 2 | { 3 | int ans=0; 4 | int len=nums.size(); 5 | unordered_map used;//hash表 6 | for(int i=0;ival; 19 | if(!child->left && !child->right) return root_to_current; 20 | return sum_to_leaf(child->left, root_to_current) + sum_to_leaf(child->right, root_to_current); 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /13 Roman to integer/README.md: -------------------------------------------------------------------------------- 1 | ## 题目描述 2 | 通过给定Roman数字,得到我们的Integer 3 | 4 | ## 解题思路 5 | 首先给出Roman计数规则 6 | 计数规则: 7 | 相同的数字连写,所表示的数等于这些数字相加得到的数,例如:III = 3 8 | 小的数字在大的数字右边,所表示的数等于这些数字相加得到的数,例如:VIII = 8 9 | 小的数字,限于(I、X和C)在大的数字左边,所表示的数等于大数减去小数所得的数, 例如:IV = 4 10 | 正常使用时,连续的数字重复不得超过三次 11 | 在一个数的上面画横线,表示这个数扩大1000倍(本题只考虑3999以内的数,所以用不到这条规则) 12 | 13 | 罗马数字共有7个,即I(1)、V(5)、X(10)、L(50)、C(100)、D(500)和M(1000)。按照下述的规则可以表示任意正整数。需要注意的是罗马数字中没有“0”,与进位制无关。一般认为罗马数字只用来记数,而不作演算。 14 | 15 | 重复数次:一个罗马数字重复几次,就表示这个数的几倍。 16 | 右加左减: 17 | 在较大的罗马数字的右边记上较小的罗马数字,表示大数字加小数字。 18 | 在较大的罗马数字的左边记上较小的罗马数字,表示大数字减小数字。 19 | 左减的数字有限制,仅限于I、X、C。比如45不可以写成VL,只能是XLV 20 | 但是,左减时不可跨越一个位数。比如,99不可以用IC()表示,而是用XCIX()表示。(等同于阿拉伯数字每位数字分别表示。) 21 | 左减数字必须为一位,比如8写成VIII,而非IIX。 22 | 右加数字不可连续超过三位,比如14写成XIV,而非XIIII。(见下方“数码限制”一项。) 23 | 加线乘千: 24 | 在罗马数字的上方加上一条横线或者加上下标的Ⅿ,表示将这个数乘以1000,即是原数的1000倍。 25 | 同理,如果上方有两条横线,即是原数的1000000()倍。 26 | 数码限制: 27 | 同一数码最多只能出现三次,如40不可表示为XXXX,而要表示为XL。 28 | 例外:由于IV是古罗马神话主神朱庇特(即IVPITER,古罗马字母里没有J和U)的首字,因此有时用IIII代替IV。 -------------------------------------------------------------------------------- /13 Roman to integer/solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | int romanToInt(string s) { 4 | int map[26]; 5 | map['I'-'A'] = 1; map['V'-'A'] = 5; map['X'-'A'] = 10; map['L'-'A'] = 50; 6 | map['C'-'A'] = 100; map['D'-'A'] = 500; map['M'-'A'] = 1000; 7 | int res = 0, n = s.size(); 8 | s.push_back(s[n-1]); 9 | for(int i = 0; i < n; i++) 10 | { 11 | if(map[s[i]-'A'] >= map[s[i+1]-'A']) 12 | res += map[s[i]-'A']; 13 | else res -= map[s[i]-'A']; 14 | } 15 | return res; 16 | } 17 | }; -------------------------------------------------------------------------------- /132-PalindromePartitioningII/README.md: -------------------------------------------------------------------------------- 1 | ## 题目分析 2 | 3 | 给一个字符串s,问最少切几下使得切出的子串都是回文串。比如,s=`aab`,那么只要切一下变成[`aa`,`b`]就可以。 4 | 5 | ### 解题思路 6 | 7 | 先从简单情况考虑起,如果s本身就是一个回文串,那么答案显然是0。 8 | 如果s不是回文串,那至少要切一下。考虑切的第一块,即包含第一个字母的那一块。这个时候我们可能会想到贪心取最长的一个,但贪心是不对的,考虑一个例子:`aaabaa`,如果第一个贪心的话,最远可以切到`aaa`,`baa`需要两下,`aaa`+`b`+`aa`,一共两下,但是我们可以切成`a`+`aabaa`,即只需要一下。 9 | 10 | 因此我们去枚举第一个切分的位置,我们就得到了一个回文串,和剩下的子串,对于剩下的子串又是一个相同的问题:最少要切多少下?因此,我们就得到了一个更小的子问题,也就是动态规划的标志。 11 | 12 | 令dp[i]代表[0,i]要最少切多少下才能满足条件,枚举前面切分的位置j,那么dp[i]=min(dp[i],dp[j-1]+1),当然,子串(i,j)要是一个回文串。 13 | 14 | 下面以`aaabaa`作为例子: 15 | ``` 16 | dp[0]=0.因为`a`本身就是回文串 17 | dp[1]=0,因为`aa`本身就是回文串 18 | dp[2]=0,同上 19 | dp[3]=1,因为可以我们切成`aaa`+`b`=dp[2]+1=1 20 | dp[4]=1,因为我们可以切成`aa`+`aba`=dp[1]+1=1 21 | dp[5]=1,因为我们可以切成`a`+`aabaa`=dp[0]+1=1 22 | ``` -------------------------------------------------------------------------------- /134-GasStation/README.md: -------------------------------------------------------------------------------- 1 | ## 题目分析: 2 | 3 | 现在有n个加油站组成一个环形,你有一辆油箱无限的汽车,现在从第i个到第i+1个加油站需要cost[i]的油,每个加油站有gas[i]的油,当油箱中油不足时,就无法进行前进,每到一个加油站就可以加任意数量的油,现在问是否可以从某个位置开始,绕环一周, 4 | 5 | ### 解题思路: 6 | 7 | 对于是否有解存在,有数学定理: 8 | 如果一个数组的总和非负,那么一定可以找到一个起始位置,从他开始绕数组一圈,累加和一直保持非负。 9 | 因此只要计算出经过一圈后加油与用油最后的总和是否大于0即可判断是否有解。 10 | 对于找到起始位置,可以遍历数组,假设从i开始前进,到达j的时候没油了,那么我们下一步不应该从i+1开始遍历,而应该直接从j+1开始仅需遍历。因为如果i到j的剩余油量小于0,而i显然油量大于0,那么从i+1到j就必定更小,同理,i+2,i+3也不用考虑,所以应该直接从j+1开始继续遍历,并保存之前欠缺的油量总和,若最后遍历到n,发现剩余油量大于等于之前的欠缺的油量总和,说明可以遍历一圈,反之则无解。 11 | 12 | 13 | #### 算法正确性: 14 | 15 | 对于每个位置不需要单独判断,可以证明没有开始遍历的点一定不会满足油量一直大于等于0的条件。假设从位置x起始遍历可以满足要求,若y < x且z > x,若从y开始到z没油了,那么此时就会跳过y到z之间的位置,直接从z+1开始遍历。但是,若y到z为油量和为负数,则x到z油量和也必为负数,显然矛盾。所以不会存在漏掉解的情况,算法正确。 16 | 算法复杂度为O(n)。 -------------------------------------------------------------------------------- /134-GasStation/solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | int canCompleteCircuit(vector &gas, vector &cost) { 4 | int res = 0; // 起始位置 5 | int remain = 0; // 当前剩余燃料 6 | int debt = 0; // 之前没走完还差的燃料 7 | 8 | for (int i = 0; i < gas.size(); i++) { 9 | remain += gas[i] - cost[i]; 10 | if (remain < 0) { 11 | debt += remain; 12 | remain = 0; 13 | res = i + 1; 14 | } 15 | } 16 | 17 | return remain + debt >= 0 ? res : -1; 18 | } 19 | }; -------------------------------------------------------------------------------- /135-Candy/README.md: -------------------------------------------------------------------------------- 1 | ## 题目分析 2 | 3 | n个人,每人有一个rating值,在满足 4 | * 每个人至少一个糖 5 | * 对于任意相邻的两个人,rating值高的人分到的糖也多 6 | 7 | 的情况下,最少需要多少糖。 8 | 9 | ### 解题思路 10 | 11 | 首先考虑简单情况,即单调的时候: 12 | * 对于单调递增的情况,比如[1,3,4,6,9],这个时候只要从左到右扫一下,第一个人给1个,第二个人2个,以此类推。 13 | * 对于单调递减的情况,比如[9,6,4,3,1],这个时候只要从右到左扫一下,最后一个人一个,倒数第二个人2个,以此类推。 14 | 15 | 对于比较复杂的情况,我们可以先从左到右扫一遍,类似递增的做法,更新值;然后从右到左扫一遍,类似于递减的做法,更新值。 16 | 17 | ### 例子 18 | yi以[1,3,5,3,2,4]为例子,首先从左到右扫一遍,得到[1,2,3,1,1,2],然后从右到左扫一遍,得到[1,2,3,2,1,2] -------------------------------------------------------------------------------- /135-Candy/solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution 2 | { 3 | public: 4 | int candy(vector& ratings) 5 | { 6 | int n=ratings.size(); 7 | vector c(n,1); 8 | for(int i=1;iratings[i-1]) 10 | c[i]=c[i-1]+1; 11 | for(int i=n-2;i>=0;i--) 12 | if(ratings[i]>ratings[i+1]) 13 | c[i]=max(c[i],c[i+1]+1); 14 | int ans=0; 15 | for(int i=0;i ma; 4 | RandomListNode *copyRandomList(RandomListNode *head) { 5 | if(!head) return head; 6 | auto newnode = new RandomListNode(head->label); 7 | ma[head] = newnode;//put in a hashmap 8 | newnode->next = copyRandomList(head->next);//create the list 9 | newnode->random = ma[head->random];//deepcopy random pointer 10 | return newnode; 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /139.WordBreak/solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | bool wordBreak(string s, vector& wordDict) { 4 | 5 | int len = s.length(); 6 | vectordp(len + 1); 7 | 8 | dp[0] = true; 9 | for(int i = 1; i <= len; i ++) { 10 | for(int j = i - 1; j >= 0; j --) { 11 | if(dp[j] == true && (find(wordDict.begin(), wordDict.end(), s.substr(j, i-j)) != wordDict.end())) { 12 | dp[i] = true; 13 | break; 14 | } 15 | } 16 | } 17 | return dp[len]; 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /14.Longest Common Prefix/README.md: -------------------------------------------------------------------------------- 1 | ## 题目分析 2 | 给我们一个vector,里面的元素全是string类型,让我们找到最长的公共前缀。 3 | 4 | ### 解题思路 5 | 首先我们来分析一下题目,要找到最长的公共前缀的意思,就是说,这个前缀,要在vector中的所有string串都要存在。 6 | 7 | 那么我们很容易想到先进行排序,答案最长也就是所有string串中最短的那个长度。 8 | 9 | 排序后,我们以最短的string串(第一个string)作为基准,每一个字符进行其他剩余串相同位置的判断,如果出现不在其中一个string串的时候,代表我们已经找到了最长公共前缀了 10 | 11 | #### 例子 12 | 举一个例子帮助理解算法。 13 | vector中的string分别为abc、abdc、abce 14 | 首先进行排序后 15 | 我们可以得到为 16 | abc、abce、abdc 17 | 判断第一个字符,都为a相同 18 | 进行下一步判断,全为b相同 19 | 进行下一个字符判断,c与d不同 20 | 则最终我们的最长公共前缀为ab 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /14.Longest Common Prefix/solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | string longestCommonPrefix(vector& strs) { 4 | //先挑选最短的,然后按照这个,从后往前扫描 5 | string res = ""; 6 | if(strs.size()==0) 7 | return res; 8 | sort(strs.begin(),strs.end()); 9 | int length1 = strs[0].length(); 10 | 11 | 12 | for(int i = 0;inext; 15 | if(len < 3) return ; 16 | 17 | p1 = head, len /= 2; 18 | while(len--) p1 = p1->next;//p1 is the middle of the position 19 | //reverse the second half 20 | p2 = p1->next, p1->next = NULL, p3 = p2->next; 21 | while(p3) 22 | p2->next = p1, p1 = p2, p2 = p3, p3 = p3->next; 23 | p2->next = p1; 24 | //merge 25 | p1 = head; 26 | while(p1){ 27 | p3 = p1->next, p1->next = p2, p1 = p3; 28 | if(!p2) break; 29 | p4 = p2->next, p2->next = p1, p2 = p4; 30 | } 31 | return ; 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /144-BinaryTreePreorderTraversal/solution2.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for a binary tree node. 3 | * struct TreeNode { 4 | * int val; 5 | * TreeNode *left; 6 | * TreeNode *right; 7 | * TreeNode(int x) : val(x), left(NULL), right(NULL) {} 8 | * }; 9 | */ 10 | class Solution { 11 | public: 12 | vector preorderTraversal(TreeNode* root) { 13 | //init 14 | value_list.clear(); 15 | //start preorder traversal 16 | preorder(root); 17 | return value_list; 18 | } 19 | private: 20 | void preorder(TreeNode* root){ 21 | if (!root) return; 22 | //record current node 23 | value_list.emplace_back(root->val); 24 | //visit left subtree 25 | preorder(root->left); 26 | //visit right subtree 27 | preorder(root->right); 28 | return; 29 | } 30 | //record current path 31 | vector value_list; 32 | }; 33 | -------------------------------------------------------------------------------- /144-BinaryTreePreorderTraversal/solution3.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for a binary tree node. 3 | * struct TreeNode { 4 | * int val; 5 | * TreeNode *left; 6 | * TreeNode *right; 7 | * TreeNode(int x) : val(x), left(NULL), right(NULL) {} 8 | * }; 9 | */ 10 | class Solution { 11 | public: 12 | vector preorderTraversal(TreeNode* root) { 13 | //record result 14 | vector value_list; 15 | vector stack; 16 | while(root || !stack.empty()){ 17 | while(root){ 18 | value_list.emplace_back(root->val); 19 | stack.emplace_back(root->right); 20 | root = root->left; 21 | } 22 | root = stack.back(); 23 | stack.pop_back(); 24 | } 25 | return value_list; 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /145-postorderTraversal/README.md: -------------------------------------------------------------------------------- 1 | ##题目分析 2 | 3 | 给定一棵二叉树,求出它的后序遍历序列,要求使用非递归 4 | 5 | ##题解思路 6 | 7 | 如果使用递归,那就是遍历树的经典题了。只需要知道后序遍历是根节点放在最后。直接对于每一个节点,先递归它的左孩子,再递归它的右孩子,最后再将自己放在序列末。时间空都是复杂度`O(n)`。 8 | 9 | 换成非递归也非常的简单,只需要自己手动模拟递归栈即可。按照递归的思路,我们要先遍历完某个节点的左孩子,再遍历右孩子,最后回溯,所以我们在进入某个节点时先将它置入栈顶,此时应该设置一个标记(比如标记等于1),表示下次弹出这个节点的时候表示需要向下搜索。那么当弹出某个标记为1的点时我们怎么安排入栈顺序呢?回忆一下后序遍历的次序是左右中,而栈又是先进后出故而入栈顺序应该是中右左,所以把这个标记为1的点入栈,记得入栈后将标记修改(比如修改成2),表示下次这个点出栈是要向上回溯;紧接着将它的右孩子和左孩子相继入栈(记得置标记)。当弹出一个标记为2的点时,便可以直接将值存入答案序列。 10 | 11 | 下面对于原题的样例举个例子: 12 | 我们先假设原树节点的编号等于值,按照算法的思路 13 | + 初始放入根节点,栈为:{1, 1}|(前面为栈顶,后面为栈尾,{}对中第一个键值表示节点编号,第二个表示入栈标记) 14 | + 弹出栈顶{1,1},按照中右左的顺序入栈,栈为:{2,1},{1,2}| 15 | + 弹出栈顶{2,1},按照中右左的顺序入栈,栈为:{3,1},{2,2},{1,2}| 16 | + 弹出栈顶{3,1},按照中右左的顺序入栈,栈为:{3,2},{2,2},{1,2}| 17 | + 弹出栈顶{3,2},此时标记为2,将节点3对应的val放入序列,此时序列为:{3},栈为{3,2},{1,2}| 18 | + 弹出栈顶{2,2},此时标记为2,将节点2对应的val放入序列,此时序列为:{3,2},栈为{1,2}| 19 | + 弹出栈顶{1,2},此时标记为2,将节点1对应的val放入序列,此时序列为:{3,2,1},栈为空| 20 | + 算法结束。 21 | -------------------------------------------------------------------------------- /146-LRUCache/README.md: -------------------------------------------------------------------------------- 1 | ## 题目分析: 2 | 3 | 设计一个`Least Recently Used (LRU) cache`(最近最少使用缓存)的数据结构, 支持 4 | 1.`new LRUCache(capacity)`, 初始化内存大小; 5 | 2.`get(key)`, 访问关键字为`key`的`value`值; 6 | 3.`put(key, value)`, 设置关键字为`key`的`value`值 7 | 8 | ### 解题思路(1) 9 | 维护一个以访问时间为键值, 大小为`capacity`的平衡树。那么每次替换的时候,将键值最小的替换出去即可。单次操作时间复杂度为`O(log(capacity))`. 10 | ### 解题思路(2) 11 | 想象一下双端队列。每次从队首`pop`, 从队尾`push`, 询问成功, 则将询问的元素从中间抽出调至队尾。这样, 在最坏的情况下, 我们每次需要移动大量元素。那么我们可以将双端队列改成链表的数据结构, 每次可以`O(1)`增删调整。要做到这一点, 我们需要一个额外的`hashtable`来快速访问元素在链表中的位置。单次操作时间复杂度为`O(1)`. 12 | #### 算法的正确性: 13 | 举例如下: 14 | ``` 15 | LRUCache cache = new LRUCache( 2 /* capacity */ ); 16 | cache.put(1, 1); //list: 1 17 | cache.put(2, 2); //list: 2, 1 18 | cache.get(1); //list: 1, 2; returns 1 19 | cache.put(3, 3); //list: 3, 1; evicts key 2 20 | cache.get(2); //list: 3, 1; returns -1 (not found) 21 | cache.put(4, 4); //list: 4, 3; evicts key 1 22 | cache.get(1); //list: 4, 3; returns -1 (not found) 23 | cache.get(3); //list: 3, 4; returns 3 24 | cache.get(4); //list: 4, 3; returns 4 25 | ``` 26 | -------------------------------------------------------------------------------- /149-MaxPointsOnALine/README.md: -------------------------------------------------------------------------------- 1 | ## 题目分析 2 | 3 | 给二维平面上n个点,问最大共线的点数 4 | 5 | ### 解题思路 6 | 7 | 由于两点定线,那么最简单的办法就是枚举两个端点,然后枚举第三个点,判断是否三点共线,这样的复杂度是O(n^3)。 8 | 9 | 虽然这样的复杂度可以通过,但我们可以做的更好。 10 | 11 | 我们考虑直线的另外定义,即一个点和一个方向可以定一条直线。我们可以先枚举点,那么只要方向相同,就是同一条直线了。这里的方向,最常见的度量就是斜率,我们可以维护每个斜率出现的次数,取决于怎样维护斜率,使用map的话,复杂度是O(n^2lgn)的,使用hashmap的话复杂度是O(n^2)的。 12 | 13 | 这样也可以通过,不过我们可以做的更好。 14 | 15 | 斜率的有两个问题,一个是计算精度问题,一个是斜率可能无穷大。我们考虑下斜率的定义`y/x`,如果我们把它看做分数的话,我们就可以去维护`(分母,分子)`的出现次数,当然,是化简后的分数。如果分母为0话,我们只需要存(1,0)就好了。 16 | 17 | ### 例子 18 | 19 | 点(1,1),(2,3),(3,3),(3,6),(4,4),(1,2),假设现在枚举的是点(1,1)。 20 | (2,3)对应的分数是(3-1,2-1)=(2,1) 21 | (3,3)对应的分数是(3-1,3-1)=(2,2)=(1,1) 22 | (3,6)对应的分数是(3-1,6-1)=(2,5) 23 | (2,4)对应的分数是(4-1,4-1)=(3,3)=(1,1) 24 | (1,2)对应的分数是(2-1,1-1)=(1,0) 25 | 26 | 方向为(1,1)的最多,有2个,那么答案就是3。 -------------------------------------------------------------------------------- /149-MaxPointsOnALine/solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution 2 | { 3 | public: 4 | int maxPoints(vector& points) 5 | { 6 | int n=points.size(),ans=0; 7 | if(n==0) return 0; 8 | for(int i=0;i,int> mp; 11 | int same=0,mx=0; 12 | for(int j=0;j& nums) { 4 | 5 | int len = nums.size(); 6 | vectordpmax(len); 7 | vectordpmin(len); 8 | dpmax[0] = dpmin[0] = nums[0]; 9 | 10 | int ans = nums[0]; 11 | for(int i = 1; i < len; i ++) { 12 | dpmax[i] = max(nums[i], max(dpmax[i - 1] * nums[i], dpmin[i - 1] * nums[i])); 13 | dpmin[i] = min(nums[i], min(dpmax[i - 1] * nums[i], dpmin[i - 1] * nums[i])); 14 | ans = max(ans, dpmax[i]); 15 | } 16 | 17 | return ans; 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /154-Find Minimum in Rotated Sorted Array II/README.md: -------------------------------------------------------------------------------- 1 | ## 题目分析: 2 | 一个按升序排列的数组中的元素在某个位置左右互换(如[0,1,2,4,5,6,7]变成了[4,5,6,7,0,1,2]),找出数组中的最小元素,数组中的元素可能重复。 3 | 4 | ### 解题思路(1): 5 | 最简单的方法,扫一遍数组找出最小元素,时间复杂度为O(n)。 6 | 7 | ### 解题思路(2): 8 | 只需要扫到那个互换的中心点就可以了,即当前元素比前一个元素小的时候,它就是最小的元素,否则整个序列依旧是有序的,第一个即为最小,极端情况时间复杂度仍为O(n)。 9 | 10 | ### 解题思路(3): 11 | 二分,初始时­­区间左指针left指向第一个元素,右指针right指向最后一个元素,令mid=(left+right)/2,每次将处于mid处的元素和right处的元素比较,如果相等right减1,如果right处的元素大则right=mid,如果right处的元素小则left=mid+1。最终结果即为left和right重合时所指向的元素,理想情况下时间复杂度为O(logn)。 12 | 13 | #### 算法正确性: 14 | 算法的关键点在于每次二分判断后的端点指针更新。当mid处所指元素和right处的相等时,此时我们不知道mid处所指元素是在中心点的左侧还是右侧,因此可以单纯把right处的元素丢掉而不影响结果;当right处的元素大时,说明从mid+1到right这一段是不会成为答案的,它们必然在中心点的右侧;当right处的元素小时,说明left到mid这一段不会成为答案,它们必然在中心点的左侧。因此上述算法是正确的。 15 | 16 | 下面举一个简单例子走一遍算法帮助理解:[3,4,1,3,3,3,3]。
17 | 初始时:left=0,right=6;
18 | 第一次:mid=(left+right)/2=3,mid处元素为3,right处元素为3,right=right-1=5;
19 | 第二次:mid=(left+right)/2=2,mid处元素为1,right处元素为3,right=mid=2;
20 | 第三次:mid=(left+right)/2=1,mid处元素为4,right处元素为1,left=mid+1=2;
21 | left与right重合,二分结束,最终结果为1。 22 | -------------------------------------------------------------------------------- /154-Find Minimum in Rotated Sorted Array II/solution1.cpp: -------------------------------------------------------------------------------- 1 | int findMin(vector& nums) 2 | { 3 | int minm=nums[0];//最小值初始化为第一个元素 4 | int len=nums.size(); 5 | for(int i=1;i& nums) 2 | { 3 | int len=nums.size(); 4 | for(int i=1;i& nums) 2 | { 3 | int len=nums.size(); 4 | int left=0,right=len-1;//初始化左右指针 5 | while(left 14 | 按个位数排列:第4个数组中有[104,14,4],第5个数组中有[15,5];
15 | 按十位数排列:第0个数组中有[104,4,5],第1个数组中有[14,15];
16 | 按百位数排列:第0个数组中有[4,5,14,15],第1个数组中有[104];
17 | 按千位数排列:第0个数组中有[4,5,14,15,104];
18 | 后续操作均不再发生变化,接着比较相邻两个数的差:5-4=1,14-5=9,15-14=1,104-15=89,因此最终结果为89。 19 | -------------------------------------------------------------------------------- /174-calculateMinimumHP/solution2.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | int n, m, dp[1005][1005]; //来(i,j)位置至少需要多少hp 4 | int calculateMinimumHP(vector>& dungeon) { 5 | n = dungeon.size (), m = dungeon[0].size (); 6 | for (int i = 0; i < 1005; i++) for (int j = 0; j < 1005; j++) dp[i][j] = 1e9; 7 | dp[n-1][m-1] = (dungeon[n-1][m-1] <= 0 ? -dungeon[n-1][m-1]+1 : 1); 8 | for (int i = n-1; i >= 0; i--) { 9 | for (int j = m-1; j >= 0; j--) { 10 | if (i-1 >= 0) { 11 | dp[i-1][j] = min (dp[i-1][j], max (1, dp[i][j]-dungeon[i-1][j])); 12 | } 13 | if (j-1 >= 0) { 14 | dp[i][j-1] = min (dp[i][j-1], max (1, dp[i][j]-dungeon[i][j-1])); 15 | } 16 | } 17 | } 18 | return dp[0][0]; 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /188-maxProfit/README.md: -------------------------------------------------------------------------------- 1 | ## 题目分析 2 | 3 | 个人认为题目描述不清,根据揣摩题意+根据自己提交后AC的代码应该是这样的:股票在n天中每一天都有一个价格,最多进行k次交易(买进卖出算作一次,买进后在卖出前不能再买),求能够获得的最大收益。 4 | 5 | ## 题解思路 6 | 7 | ### 解法1 8 | 9 | 收益和两个因素有关:天数和交易次数。为了方便起见我们将交易次数扩展为两倍,并且规定当第奇数次交易时必须是买进,第偶数次交易时必须是卖出。假设每天的价格是`cost[i]` 10 | 那么我们就可以按照两个因素设计状态`f[i][j]`表示**第i天已经进行了j次交易后的最大收益**。这个状态是比较简单的,可以直接通过前一天的j或者j-1的状态转移来,具体来说就是: 11 | 12 | `f[i][j]= max:` 13 | 14 | + f[i-1][j] (不交易) 15 | + f[i-1][j-1]-cost[i] (j是奇数) 16 | + f[i-1][j-1]+cost[i] (j是偶数) 17 | 18 | 容易发现,在这个解法中,时空复杂度都是`O(nk)`。 19 | 20 | ### 解法2 21 | 22 | 我们发现其实每一天的状态都只需要知道前一天的状态。故而我们可以利用**滚动数组**优化掉第一维。一个比较简单的方法是第一维只需要设置长度为2,第一维0存前一天,第一维1存今天计算的结果。每一天计算结束后把第一维1的结果存到第一维的0中。但其实这样也没必要只需要每一天计算时从后往前计算便可完全消除第一维,因为每一个j计算时都只被比他小的j影响,所以从后往前计算能够保证更新而来的值来自前一天。具体来说就是: 23 | 24 | `f[j]= max:` 25 | 26 | + f[j-1]-cost[i] (j是奇数) 27 | + f[j-1]+cost[i] (j是偶数) 28 | 29 | 那么为什么转移方程没有“不交易”这个选择呢?其实不交易已经隐含在取max中了,因为不交易的结果即dp[j]不改变,所以我们对于每个选项取max即已经考虑了“不交易”。 30 | 31 | 因为用了滚动数组所以空间复杂度为`O(k)`,时间复杂度为`O(nk)`。细心的读者也许已经发现了,经典的01背包即是使用了这样的方法优化内存。 32 | 33 | 34 | -------------------------------------------------------------------------------- /188-maxProfit/solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | int maxProfit(int k, vector& prices) { 4 | int dp[100005]; //前i天进行了j次交易的最大收益 5 | int n = prices.size (); 6 | k <<= 1; 7 | 8 | for (int i = 0; i < 100005; i++) dp[i] = -1e9; 9 | dp[0] = 0; 10 | for (int i = 1; i <= n; i++) { 11 | int val = prices[i-1]; 12 | for (int j = i; j >= 0; j--) { 13 | if (!j) continue; //这一天不做交易 14 | if (j&1) { //这一天买入股票 15 | dp[j] = max (dp[j], dp[j-1]-val); 16 | } 17 | else { //这一天卖出股票 18 | dp[j] = max (dp[j], dp[j-1]+val); 19 | } 20 | } 21 | } 22 | int ans = -1e9; 23 | for (int i = 0; i <= min (n, k); i += 2) ans = max (ans, dp[i]); 24 | return ans; 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /189 Rotate Array/README.md: -------------------------------------------------------------------------------- 1 | # 题目分析 2 | 题意要求我们将一个数组中的第三大的数进行返回。 3 | 如果数组中元素少于3个,那么返回最大的那个值。注意,相同元素只算一个。 4 | 5 | ### 解题思路(1) 6 | 记下最后一个数字,其他的数字向后移一位,最后把记下的数字放在开头,如此进行k次。这个解法空间复杂度为O(1),时间复杂度O(kn)。 7 | 8 | 时间复杂度,最坏可能到0(n*n)级别,无法忍受 9 | 10 | ### 解题思路(2) 11 | 另设置一个vector,然后逐个元素添加进去,最后将这个vector赋值给nums。添加方式为将右边的k个元素添加进去,再将左边的n-k个元素添加进去。(注意k>vector长度的情况,一定要进行取余操作~) 12 | 空间复杂度为0(n) 13 | 14 | 15 | ###解题思路(3) 16 | 右旋k个数字有个等价操作,先将整个数组翻转,就是将前k个数字翻转,将剩下的数字也翻转此时数组与右旋k个数字的结果相同。这个解法空间复杂度为O(1),时间复杂度为O(n)。代码见solution.cpp 17 | 18 | 一个例子说明一下算法3: 19 | 原数组为1,2,3,4,5 k=2 20 | 第一步先将原数组全部翻转 5,4,3,2,1 21 | 然后前k=2个数进行翻转 4,5,3,2,1 22 | 最后剩下的数进行翻转4,5,1,2,3 23 | 这样得到了我们的最终结果 -------------------------------------------------------------------------------- /189 Rotate Array/solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | void rotate_sub(vector& nums,int begin,int end) 4 | { 5 | 6 | int i; 7 | int end_temp = end; 8 | int begin_temp = begin; 9 | for(i = 0;i<(end-begin+1)/2;i++) 10 | { 11 | int temp = nums[end_temp]; 12 | nums[end_temp] = nums[begin_temp]; 13 | nums[begin_temp] = temp; 14 | begin_temp++; 15 | end_temp--; 16 | } 17 | } 18 | void rotate(vector& nums, int k) { 19 | //这是我数据结构第一个实验 20 | if(nums.size()& nums) { 4 | int len = nums.size(); 5 | if(len == 0) { 6 | return 0; 7 | } else if(len == 1) { 8 | return nums[0]; 9 | } else if(len == 2) { 10 | return max(nums[0], nums[1]); 11 | } else { 12 | vector dp; 13 | dp.push_back(nums[0]); 14 | dp.push_back(max(dp[0], nums[1])); 15 | for(int i = 2; i < len; i ++) { 16 | dp.push_back(max(dp[i - 2] + nums[i], dp[i - 1])); 17 | } 18 | return dp[len - 1]; 19 | } 20 | return 0; 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /2-addTwoNumbers/README.md: -------------------------------------------------------------------------------- 1 | ## 题意分析 2 | 3 | 给出两个链表表示两个整数,表头代表低位。返回一个两个整数相加之后的链表表示。 4 | 5 | ## 题解思路 6 | 7 | 直接从低位到高位用链表模拟即可,有几点注意事项: 8 | 9 | + 注意设置进位标记; 10 | + 注意两个整数长短不一时,在余出的长度部分相当于加0; 11 | + 不要忘记余出长度部分的进位标记,比如1+999,进位标记需要一直传递; 12 | + 注意0+0的特殊情况。 13 | 14 | -------------------------------------------------------------------------------- /20.ValidParentheses/README.md: -------------------------------------------------------------------------------- 1 | ### 题意 2 | **中文描述** 3 | 给定一个字符串,只包含 '(', ')', '{', '}', '[', ']', 判断一个输入字符串是否合法. 在合法的序列中括号是闭合的, 比如 "()" 是合法的, 但是 "(]" 不合法. 4 | 5 | **题意分析** 6 | 括号匹配问题 7 | 8 | ### 题解 9 | **算法及复杂度 (3 ms) ** 10 | 本题就是验证括号的顺序是否能保证正确匹配,通过自己简单的模拟就会发现:在括号的匹配过程中,右括号才是最重要的.每个右括号能且只能对应前边的一个左括号,因此每个右括号对应的左括号一定在前边出现,并且位置是确定的. 因此就萌生了一种模拟的思路: **遇到左括号就存起来,遇到右括号就进行匹配**. 11 | 本题为什么想到用stack进行实现?在左括号的存储过程有很多结构进行实现,主要仔细分析右括号的匹配过程.不妨举个例子 `s = "((()))"`, 先用某种方式把左括号存起来,那么存储结果是 `"((("`, 遇到第一个右括号,与前一个符号进行比对,发现是左括号(如果不是对应的左括号,就可以直接return false了,原因根据正确括号序列的定义),这样就进行了匹配,匹配成功之后,显然这一对括号已经没有作用了,因此就可以把这对括号覆盖掉或者删除掉,这里使用stack通过弹出顶部元素(即对应左括号)达到这个效果. 12 | ***时间复杂度:*** O(n). n 表示括号序列的长度,只需要一次遍历就可以完成,因此是 O(n) 的复杂度. 13 | ***代码参见同文件夹下solution.cpp*** 14 | 15 | ### 算法正确性 16 | **正确性证明** 17 | 模拟的思想,根据题目提供的方法在进行操作,提供的已知条件保证了算法的正确性. 18 | **举个例子** 19 | ``` 20 | //输入序列 21 | s = "([{)" 22 | 23 | //分析s[0],左括号,入栈st 24 | st = "(" 25 | 26 | //分析s[1],左括号,入栈st 27 | st = "([" 28 | 29 | //分析s[2],左括号,入栈st 30 | st = "([{" 31 | 32 | //分析s[3],右括号,尝试匹配st.top(),返现不匹配,返回false 33 | ``` 34 | -------------------------------------------------------------------------------- /207- Course Schedule/README.md: -------------------------------------------------------------------------------- 1 | ## 题目分析: 2 | 现有*n*门课程——从`0`标记至`n-1`,以及*m*对序列`[x,y]`来表示y是x的先修课程。现要求判断当前给出的关系是否合理,以至于能够顺利完成所有课程。 3 | 4 | ### 解题思路(1): 5 | 对于给定的序列,我们将其转换为图模型以表达出对应的关系。如对于序列`[0,1]`,我们建立一条从`1`指向`0`号点的单向边。 6 | 后思考什么样的图是非法的?应为整张图中至少存在一个环路时,整张图非法。因为若两个节点*x*与*y*存在于同一环之内,则课程*y*是*x*的先修课程,同时*x*也是*y*的先修课程。 7 | 该种图模型实质上是一张拓扑图,若其当前要求输出修习课程的序列时,下面的拓扑算法实质上更为通用。只要该拓扑图能够存在一合法的拓扑解,则所有课程能够顺利修习完成。 8 | 拓扑算法的过程如下所述: 9 | 1. 每次在图中寻找一**入度为0**节点,若当前无法找到且图中还有未被删除的节点,则说明当前图能存在环,无解; 10 | 2. 拓展从该节点出发的有向边能到达的另一节点,将其入度剪一; 11 | 3. 从图中删除该节点,若删除的节点数目已达到N则表示所有节点均已输出,存在一个合法解;否则回到过程1. 12 | 13 | 14 | ~~该种算法最坏情况下将平均对每个节点访问`$\frac{N}{2}$`次,因此时间复杂度为`O(N^2)`~~ 15 | 可以用一个队列存储待处理的入度为0的点,每次取出一个节点,删除所有出边。这样可以做到每个点与每条边均只访问一次,时间复杂度为`O(N+M)` 16 | ### 解题思路(2): 17 | 由于该题并没有要求输出这张图的拓扑序,因此仅需判断该图是否合法即可。因此可以使用`DFS`的方式来寻找该图当中是否存在环路。 18 | + 每次从任意一点未被访问过的节点出发,向下进行深搜,如果在一条深搜的路径上,可访问一个节点两次,则说明当前路径已构成一条环,则整张图无解。 19 | + 如果一个节点之前在另一个深搜过程已经被遍历过,并且没有找到环,则没有再次进行搜索的必要。原因为该节点不可能存在于任意一个环中,**否则在早先的DFS搜索过程中,其已被找出**。 20 | 21 | 该种做法只访问每个节点一次、每条边一次,因此整体时间复杂度为`O(N+M)`。 22 | -------------------------------------------------------------------------------- /207- Course Schedule/Solution1.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | bool canFinish(int numCourses, vector>& prerequisites) { 4 | vector DU(numCourses,0);//存储每个节点的入度 5 | vector used(numCourses,false);//表示每个节点是否已在该图中被删除 6 | vector Q;//入度为0的待处理节点加入队列之中 7 | vector> Link(numCourses,vector()); 8 | for(auto x:prerequisites) 9 | { 10 | ++DU[x.first];//若为一有向边的终点,则将其入度加一 11 | Link[x.second].push_back(x.first); 12 | } 13 | int cnt(0); 14 | for(int i=0; i& nums) { 4 | if(nums.size() == 0) return 0; 5 | if(nums.size() == 1) return nums[0]; 6 | 7 | int pre1 = 0, Max1 = 0; 8 | for(int i = 0; i < nums.size() - 1; ++ i) { 9 | int temp = pre1; 10 | pre1 = Max1; 11 | Max1 = max(temp + nums[i], pre1); 12 | } 13 | 14 | int pre2 = 0, Max2 = 0; 15 | for(int i = 1; i < nums.size(); ++ i) { 16 | int temp = pre2; 17 | pre2 = Max2; 18 | Max2 = max(temp + nums[i], pre2); 19 | } 20 | 21 | return max(Max1, Max2); 22 | } 23 | }; -------------------------------------------------------------------------------- /214-shortestPalindrome/README.md: -------------------------------------------------------------------------------- 1 | ##题目分析 2 | 3 | 给出一个串,能够在串前添加字母,求出一个最短的回文串。 4 | 5 | ##题解思路 6 | 7 | + 一个回文串的头和尾同时往中间走,那么经历的字符应该是完全一样的。 8 | 9 | 那么再来回顾原串,我们只能往原串的头部放字母,意味着回文串的尾我们已经确定。方便起见不妨将原串逆转一下,那么题目变成往一个串后面放最少的字母构造回文串。 10 | 11 | 假设逆转后的串是s,现在我们知道了回文串的头部,知道了回文串的头部肯定也知道了回文串的尾部,因为回文串的头和尾是一样的,所以逆转s(假设是h),那么从同时从h的尾往前走和从s的头往尾走所经历的字符必须是一样的。那么如果我们发现走了k个字母后,s的后缀和h的前缀相等了,那么我们就找到了回文中心,也就是这个相等的s的后缀串(h的前缀串)的中心,因为h是s逆转来的,相当于在相等的这段后缀串中是满足上述条件的,所以只需要把不满足条件的前k个字母放到s的后面即可。 12 | 13 | 再进一步抽象,**我们需要找到一个h的一个最长的前缀,使得它等于s的后缀**。 14 | 15 | 举个例子: 16 | 假设题目给出的串是`aacecaaa`,那么`s=aaacecaa, h=aacecaaa`,我们从同时s的下标0往下走和从h的下标7往前走。我们发现取`k=1`,接下来的s的后缀`aacecaa`和h的前缀`aacecaa`是相等的,那么我们把之前路径上的字母补充到s的后面也就是s后面补充一个a,得到`ans=aaacecaaa`。 17 | 18 | 现在我们抽象出了问题,就需要用一个方案去解决了。显然这里最好的方法就是哈希,可以从s的末尾和h的首部不断通过添加字母更新哈希值,每当更新完发现了相等的两个哈希值就说明此时s的后缀等于h的前缀,便可以更新答案。最终的复杂度也不过是`O(n)`。 19 | 20 | -------------------------------------------------------------------------------- /214-shortestPalindrome/solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | string shortestPalindrome(string s) { 4 | reverse (s.begin (), s.end ()); 5 | string h = s; 6 | reverse (h.begin (), h.end ()); 7 | unsigned long long hash1 = 0, hash2 = 0, seed = 131, bit = 1; 8 | int n = s.length (); 9 | int len = 0; //s的后缀和h的前缀的最长匹配长度 10 | for (int i = 0; i < n; i++, bit *= seed) { 11 | hash1 = hash1*seed+s[n-1-i]-'a'+1; 12 | hash2 = hash2+bit*(h[i]-'a'+1); 13 | if (hash1 != hash2) { 14 | continue; 15 | } 16 | else { 17 | len = i+1; 18 | } 19 | } 20 | for (int i = len; i < n; i++) { 21 | s += h[i]; 22 | } 23 | return s; 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /216-CombinationSumIII/README.md: -------------------------------------------------------------------------------- 1 | ## 题目分析: 2 | 3 | 从[1, 9]这9个数中选择k个数,使得k个数总和等于n,每个数只能使用一次,将所有方案都输出。 4 | 5 | ### 解题思路: 6 | 7 | 因为要输出方案,不适合用动态规划,可以考虑搜索。 8 | 利用深度优先搜索(DFS),设当前已经还需要构造的数大小为n,从1到9枚举这一轮要选择的数。 9 | 如果上一轮数字枚举到x,因为每个数只能选择一次,所以已经选择的数,就不能再次选择;对于之前没有选择的数,也不能再次选择,因为会产生重复。 10 | 举个例子:[1, 2, 3, 4, 5, 6, 7, 8, 9]中选择2个数构成7,如果第一轮选择的是2,第二轮选择5可以构成,若之后第一轮选择的是5,第二轮选择2,就会产生重复,所以每次选择的数一定要比之前选择的数都大。 11 | 12 | 13 | #### 算法正确性: 14 | 15 | 深度优先搜索其实就是一种暴力穷举,对于每种情况都会考虑到,而且枚举顺序上的要求也保证了不会产生重复,所以算法正确。枚举次数不会超过9的阶乘。 16 | -------------------------------------------------------------------------------- /216-CombinationSumIII/solution.cpp: -------------------------------------------------------------------------------- 1 | class nowution { 2 | 3 | public: 4 | void dfs(int k, int n, vector>& res, vector now) { 5 | if (now.size() == k && n == 0) { 6 | res.push_back(now); 7 | return ; 8 | } 9 | if (now.size() < k) { 10 | int st = now.empty() ? 1 : now.back() + 1; 11 | for (int i = st; i <= 9; ++i) { 12 | if (n - i < 0) return; 13 | now.push_back(i); 14 | dfs(k, n - i, res, now); 15 | now.pop_back(); 16 | } 17 | } 18 | } 19 | 20 | vector> combinationSum3(int k, int n) { 21 | vector> res; 22 | vector now; 23 | dfs(k, n, res, now); 24 | return res; 25 | } 26 | }; -------------------------------------------------------------------------------- /221-MaximalSquare/solution1.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | int maximalSquare(vector>& matrix) { 4 | int ret = 0; 5 | for(int i=0; i0&&j>0) matrix[i][j]='1'+ min(matrix[i-1][j-1]-'0', min(matrix[i-1][j]-'0', matrix[i][j-1]-'0')); 9 | ret = ret >= (matrix[i][j]-'0') ? ret : (matrix[i][j]-'0'); 10 | } 11 | } 12 | return ret*ret; 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /225.ImplementStackusingQueues/README.md: -------------------------------------------------------------------------------- 1 | ### 题意 2 | **中文描述** 3 | 用队列实现栈的以下操作: 4 | (1) push(x) 5 | (2) pop() 6 | (3) top() 7 | (4) empty() 8 | 注意: 只能使用队列的标准操作,仅有push, pop/front, size和empty. 9 | 10 | ### 题解 11 | **算法及复杂度(6 ms)** 12 | 做本题需要了解栈和队列的基本原理,以及STL中队列的基本操作. 13 | 使用队列模拟实现栈的操作.先简单分析,队列是一种“先进先出”的数据结构,栈是一种“后进先出”的数据结构,如何用先进先出实现后进先出呢?一种比较容易想到的做法就是: 把队列中新进队的元素放在最首部就跟栈一模一样了,其实如果能把新进队的元素能放在队列的首部,那就是一个栈了. 14 | 显然,不能直接把一个元素放在队列的首部,原因是数据结构定义中规定了队列的元素只能放在尾部,那么我们想要实现能够把新添加的元素放在队列的首部的操作可以通过借助一个临时队列的方式进行: ***首先把新添加的元素放在临时队列中,然后把主队列中所有元素出队然后入队到临时队列,最后把临时队列赋值给主队列*** ,之后就能实现栈的一系列功能了. 15 | ***时间复杂度:*** 这个算法的push操作花费的时间和栈内元素的数量同量级,而pop/top/empty操作的时间复杂度都是常数量级. 16 | ***代码参见本文件夹下solution.cpp*** 17 | 18 | ### 算法正确性 19 | **正确性证明** 20 | 模拟的思想,题目提供的已知条件保证了算法的正确性. 21 | -------------------------------------------------------------------------------- /228-Summary Ranges/README.md: -------------------------------------------------------------------------------- 1 | ## 题目分析: 2 | 题目中给出一个有序的整数序列,要求将相邻的两个整数合并。 3 | *如[0,1,2,4,5,7]转换为["0->2", "4->5", 7]* 4 | ### 解题思路: 5 | 由于整个序列已经经过排序,因此处理起来较为简单。使用两个指针,第一个指针指向序列当中最前方的未被合并的整数。另一个指针从该指针所在位置开始,向后进行拓展,同时保证这两个指针所指出的这段区间内的整数是连续的。当右指针无法向后进行拓展时,将该区间进行合并即可。下次再将左指针已到右指针+1的位置,如此往复。 6 | 所有的整数仅被扫描一次,因此整体时间复杂度为O(N)。 7 | -------------------------------------------------------------------------------- /228-Summary Ranges/Solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | vector summaryRanges(vector& nums) { 4 | vector ans(0,""); 5 | if(nums.size() == 0) return ans; 6 | int L,R; 7 | to_array 8 | L = R = nums[0];//初始化左右指针 9 | for(int i=1; i" + to_string(R); 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /229- Majority Element II/README.md: -------------------------------------------------------------------------------- 1 | ## 题目分析: 2 | 题目给出一个大小为N的数组,找出所有出现次数大于 [N/3] 的整数。要求空间复杂度为O(1),时间复杂度为O(N)。 3 | ## 解题思路 4 | 首先,有一个经典的问题是在一个大小为M的数组之中找出一个出现次数大于[M/2]的数字。 5 | 若该数字存在的话,其出现次数会大于所有其它数字出现的总和。因此考虑用x来记录当前可能的候选数组,cnt_x来标记下当前数字是否可能。若当前读入的数字 y = x则,cnt_x+1,否则判断 cnt_x 是否为0。若为0,则以y来替换x否则cnt_x-1。 正确性显而易见,若答案P的出现次数在一半以上,便算其被所有其它数字所减,cnt_x仍会大于0。最后检查下x的出现次数是否符合结果即可。 6 | 然后我们回到当前的题目背景中来,当前是要找出出现次数大于[N/3]的数字,其最多的可能是2个。因此考虑使用两个变量去缓存当前可能的答案候选 `num_a`与`num_b`。若当前数字与`num_a`相同,则`cnt_a+1`.与`num_b`相同则`cnt_b+1`。都不相同则两个计数量同时减一,替换规则也与之前相同。最后再扫一遍数组,检查下两个数字的出现次数是否满足要求,若满足则加入答案。 7 | -------------------------------------------------------------------------------- /23-mergeKLists/README.md: -------------------------------------------------------------------------------- 1 | ##题目分析 2 | 3 | 给定k个有序链表,求出一个合并以后的有序链表。 4 | 5 | ###题解思路(1) 6 | 7 | 先假设所有的元素个数是n。首先我们可以确定一个复杂度上界,就是把这k个链表中的所有节点保存下来排序,这样的做法复杂度主要在排序,是一个`O(nlgn)`的做法。 8 | 9 | ###题解思路(2) 10 | 11 | 接下用k个链表本身已经有序这个限制来优化下上界的算法。假设有两个来自同一个链表的元素`a,b(a productExceptSelf(vector& nums) { 5 | int n = nums.size(); 6 | int l = 1; 7 | vector res(n, 1); 8 | for(int i = 0; i < n; i++) { 9 | res[i] *= l; 10 | l *= nums[i]; 11 | } 12 | int r = 1; 13 | for (int i = n - 1; i >= 0; i--) { 14 | res[i] *= r; 15 | r *= nums[i]; 16 | } 17 | return res; 18 | } 19 | }; -------------------------------------------------------------------------------- /239-SlidingWindowMaximum/README.md: -------------------------------------------------------------------------------- 1 | ## 题目分析 2 | 3 | 给你一串数,用一个大小为k的滑动窗口从左向右走,问每一次滑动时窗口内的最大值是多少。 4 | 5 | ### 解题思路 6 | 7 | 最简单的方法就是每k个暴力找一次,这样复杂度显然是O(nk)的。 8 | 9 | 然而,想象一下,每次我们向右移动的时候,其实是有很大一部分的重合的,以前的信息我们依然可以利用。 10 | ``` 11 | i,i+1,i+2...i+k-1 12 | i+1,i+2...i+k-1,i+k 13 | ``` 14 | 考虑一下,假设窗口左端点为i时的最大值有两种情况: 15 | * 在i的位置,当我们右移的时候,我们直接i删掉了,加入了i+k,那么这时的最大值是[i+1,i+k-1]的最大值和i+k的值中较大的那个 16 | * 如果在[i+1,i+k-1]区间内,那么我们只要把之前的最大值和i+k比较一下。也就是说,这时候i位置上的值已经不重要了,因为反正它也不会成为最大值。 17 | 18 | 我们仔细考虑一下后一种情况,为什么可以直接删掉i,直接用之前的答案?如果i,j都在窗口里,而且inums[i]的话,那么i位置的数永远也不可能成为最大值了。想一想,为什么? 19 | 20 | 这样,每个窗口里有可能在以后成为最大值的点,一定是单调减的。因此,我们去维护一个当前窗口的一个单调递减的队列,这样每个数只会入队一次,出队一次,时间复杂度就被减小到了O(n)。 21 | 22 | ### 例子 23 | 24 | nums = [1,3,-1,-3,5,3,6,7], and k = 3 25 | 26 | 首先前k个的单调减为[3,-1] 27 | 接着我们插入-3,这个时候可以直接放进去,变为[3,-1,-3] 28 | 插入5,这个时候要把3,-1,-3都删掉,队列变为[5]。 29 | 接着插入3,队列变成[5,3] 30 | 插入6,队列变为[6] 31 | 插入7,队列变为[7] 32 | -------------------------------------------------------------------------------- /239-SlidingWindowMaximum/solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution 2 | { 3 | public: 4 | vector maxSlidingWindow(vector& nums, int k) 5 | { 6 | vector ans; 7 | deque que; 8 | for(int i=0;inums[que.back()]) 11 | que.pop_back(); 12 | que.push_back(i); 13 | if(i-que.front()+1>k) que.pop_front(); 14 | if(i+1>=k) ans.push_back(nums[que.front()]); 15 | } 16 | return ans; 17 | } 18 | }; 19 | -------------------------------------------------------------------------------- /25-ReverseNodesink-Group/README.md: -------------------------------------------------------------------------------- 1 | ## 题目分析: 2 | 3 | 将一个链表, 每隔k个节点进行反转。不足k个不反转。 4 | 5 | ### 解题思路 6 | 设置三个指针进行链表反转。具体操作难以描述, 请参考代码。指针比较繁琐, 注意细节。 -------------------------------------------------------------------------------- /26. Remove Duplicates from Sorted Array/README.md: -------------------------------------------------------------------------------- 1 | # 题目分析 2 | 3 | 排好序的数组,求去掉重复元素后的数组大小。要求空间复杂度为常数,不能使用额外的数组。 4 | 5 | ## 解题思路: 6 | 7 | 由于数组已经排好序,可以维护两个变量l和r,l初始为0,r从位置1开始遍历数组,如果nums[r]和nums[l]相等,那么不做处理;如果不相等,把nums[r]放到nums[l+1]上,l自增1。 8 | -------------------------------------------------------------------------------- /26. Remove Duplicates from Sorted Array/solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | int removeDuplicates(vector& nums) { 4 | int l = 0; 5 | if(nums.size() <= 1) 6 | return nums.size(); 7 | for(int r = 1; r < nums.size(); r++){ 8 | if(nums[l] != nums[r]) 9 | nums[++l] = nums[r]; 10 | } 11 | return l+1; 12 | } 13 | }; -------------------------------------------------------------------------------- /264-UglyNumberII/solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | int nthUglyNumber(int n) { 4 | if(n<=0) return 0; 5 | vector dp(n); 6 | dp[0]=1; 7 | int index_2=0, index_3=0, index_5=0; 8 | for(int i=1; i& nums, int val) { 4 | int l = 0; 5 | int len = nums.size(); 6 | for(int r = 0; r < len; r++){ 7 | if(nums[r] != val) 8 | nums[l++] = nums[r]; 9 | } 10 | return l; 11 | } 12 | }; -------------------------------------------------------------------------------- /273-Integer to English Words/README.md: -------------------------------------------------------------------------------- 1 | ## 题目分析: 2 | 将一个非负整数转换成英文表达形式,给定的数小于2^31-1。 3 | 4 | ### 解题思路: 5 | 按照英文数字表达的特点模拟即可。在英文中,数字从低到高每三位分一级,即千、百万、十亿是分界点,每级内的表达方式相同,因此可以用级内表达+分界点单位的方式处理,对于级内的数,可以分成百位、十位、个位来处理,百位后面有单位百。需要注意几点:某一级或者百位数如果是0,那么对应的单位就不能有;单词之间的空格需要控制好;数字0是有一个单词”Zero”的;如果十位和个位组成的两位数小于20,则需要一起处理。 6 | 7 | 下面举一个简单例子走一遍算法帮助理解:1234567。
8 | 给数字分级,从低到高分别为,567、234、1、0;
9 | 处理最高级,数字为0,跳过;
10 | 处理第二级,数字为1,级内百位为0,跳过;十位为0,跳过;个位为1,答案添加”One”;最后再添加” Million”;
11 | 处理第三级,数字为234,由于先前已经有非0数,因此添加空格,级内百位为2,答案添加”Two Hundred”;十位为3,由于先前已经有非0数,因此添加空格,再添加”Thirty”;个位为4,由于先前已经有非0数,因此添加空格,再添加”Four”;最后再添加” Thousand”;
12 | 处理第四级,数字为567,由于先前已经有非0数,因此添加空格,级内百位为5,答案添加”Five Hundred”;十位为6,由于先前已经有非0数,因此添加空格,再添加”Sixty”;个位为7,由于先前已经有非0数,因此添加空格,再添加”Seven”;
13 | 最终结果为”One Million Two Hundred Thirty Four Thousand Five Hundred Sixty Seven”。 14 | -------------------------------------------------------------------------------- /279-PerfectSquares/solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | int numSquares(int n) { 4 | static vector cntPerfectSquares{0};//n=0时,数量为0,充当初始值。 5 | while(cntPerfectSquares.size()<=n){//cntPerfectSquares.size()==n+1时,说明正整数n的结果已经找到。 6 | int cur = cntPerfectSquares.size();//当前的正整数 7 | int cur_count = INT_MAX;//当前正整数的结果 8 | for(int i=1; i*i<=cur; i++){//遍历所有小于或等于当前正整数的完全平方数 9 | cur_count=min(cur_count, cntPerfectSquares[cur-i*i]+1);//更新最小值 10 | } 11 | cntPerfectSquares.emplace_back(cur_count);//当前正整数的结果已经找到,记录下来 12 | } 13 | return cntPerfectSquares[n]; 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /283-MoveZeros/README.md: -------------------------------------------------------------------------------- 1 | ## 题目分析: 2 | 3 | 给出一串n个数的序列nums,要求将每个0都移动到序列末尾,但是要保持非零数字的相对顺序不变。 4 | 如:nums = [0, 1, 0, 3, 12],经过操作后,nums = [1, 3, 12, 0, 0] 5 | 要求有两点: 6 | 1. 必须在原数组操作 7 | 2. 操作数最少 8 | 9 | ### 解题思路: 10 | 11 | 从前到后遍历数组nums,将0移动到序列末尾其实就是将非零数字移动到队列开头,对于每个数字nums[i],如果之前已经找到了j-1个非零数字,那么就将nums[j]更新为nums[i]。 12 | 最后如果j < n,则将[j+1, n]位置都补零。 13 | 14 | #### 算法正确性: 15 | 16 | 对于每个位置i,可以保证之前的非零数字数目j-1 < i,所以每次更新num[j]时不用担心会影响到没有遍历到的非零数字。 17 | 算法需要遍历一次,符合操作数最少的要求。 18 | 没有开辟新数组,符合在原数组操作的要求。 19 | -------------------------------------------------------------------------------- /283-MoveZeros/solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | void moveZeroes(vector& nums) { 4 | int j = 0; 5 | for (int i = 0; i < nums.size(); i++) { 6 | if (nums[i] != 0) { 7 | nums[j++] = nums[i]; 8 | } 9 | } 10 | for (;j < nums.size(); j++) { 11 | nums[j] = 0; 12 | } 13 | } 14 | }; -------------------------------------------------------------------------------- /287-findtheduplicatenumber/solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | int findDuplicate(vector& nums) { 4 | int n = nums.size(); 5 | int l = 1, r = n; 6 | while (l < r) { 7 | int m = (l + r) >> 1, cnt = 0; 8 | for (int i = 0; i < n; i++) 9 | if (nums[i] <= m) cnt++; 10 | if (cnt > m) r = m; 11 | else l = m + 1; 12 | } 13 | return l; 14 | } 15 | }; -------------------------------------------------------------------------------- /289-gameoflife/README.md: -------------------------------------------------------------------------------- 1 | ## 题目分析: 2 | 3 | 给出一个m * n的放个,每个方格中有一个细胞,1代表存活,0代表死亡,细胞会受到上下左右以及对角八个方格的影响,每一轮影响如下: 4 | 1. 如果相邻8个细胞中只有少于两个存活,那么就该细胞就要死亡 5 | 2. 如果相邻8个细胞中有2或3个细胞存活,那么该细胞会继续活着 6 | 3. 如果相邻8个细胞中有超过3个细胞存活,那么该细胞就要死亡 7 | 4. 如果当前细胞已死,且相邻的8个细胞中有正好3个存活,那么该细胞就要复活 8 | 除了以上的条件,还需要要满足只在当前给出的二维数组中操作。并且每一轮考虑当前细胞的相邻细胞的时候,都是考虑这些细胞转化之前的状态。 9 | 10 | ### 解题思路: 11 | 12 | 题目很简单,只要按照要求进行操作即可。二重循环对每一个细胞判断周围八个细胞的状态,再改变当前细胞的状态即可。 13 | 但是这题有一个额外条件是只能在给出的二维数组中操作,不能另外开辟空间储存新状态。而且每次根据相邻的8个细胞转化之前的状态判断,那么如何保存每个细胞新的状态就是个问题。 14 | 考虑到生或死状态是由1和0表示,可以利用二进制,每次将当前细胞的旧状态保存在第0位,新状态保存在第1位,每次判断相邻8个元素时只看第0位。 15 | 更新完所有的细胞的第1位后,只要将每个细胞的值/2,也就是将第1位移到第0位,这样就完成了一轮的更新。 16 | 17 | 18 | #### 算法正确性: 19 | 20 | 算法满足题目要求,正确性显然。算法复杂度为O(n^2)。并且通过二进制满足了只改变原本数组的要求。 21 | -------------------------------------------------------------------------------- /289-gameoflife/solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | void gameOfLife(vector>& board) { 4 | int m = board.size(), n = m ? board[0].size() : 0; 5 | for (int i = 0; i < m; ++i) { 6 | for (int j = 0; j < n; ++j) { // 枚举每一个方格中的细胞 7 | int cnt = 0; // 周围细胞的存活数 8 | for (int p = max(i - 1, 0); p < min(i + 2, m); ++p) // 枚举时考虑不超过边界 9 | for (int q = max(j - 1, 0); q < min(j + 2, n); ++q) // 枚举相邻的周围8个细胞以及自己一共9个细胞 10 | cnt += board[p][q] & 1; 11 | if (cnt == 3 || cnt - board[i][j] == 3) // cnt == 3说明有2~3个存活,cnt - board[i][j] == 3说明周围8个细胞中有正好3个存活 12 | board[i][j] |= 2; // 这两种情况下该细胞下一轮一定会存活,所以将其二进制第一位设置成1 13 | } 14 | } 15 | for (int i = 0; i < m; ++i) 16 | for (int j = 0; j < n; ++j) 17 | board[i][j] >>= 1; 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /295-FindMedianFromDataStream/README.md: -------------------------------------------------------------------------------- 1 | ## 题目分析: 2 | 3 | 设计一个数据结构,支持1) 从数据流中添加一个整数到数据结构中; 2) 返回到目前为止的所有元素的中位数。 4 | 5 | ### 解题思路(1) 6 | 题号480的simple版,无删除操作。每次查询暴力排序。很遗憾,会超时。 7 | ### 解题思路(2) 8 | 维护两个堆,一个保存前一半的数,另一个保存后一半的数,我们就能轻易求得中位数。单次操作的复杂度为`O(logn)`。 9 | #### 例子: 10 | 举例如下:`addNum(1)`, 两个堆为`{}`, `{1}`; `addNum(2)`, 两个堆为`{1}`, `{2}`; `findMedian() = 1.5`, `addNum(3)`, 两个堆为`{1}`, `{2, 3}`; `findMedian() = 2`. -------------------------------------------------------------------------------- /295-FindMedianFromDataStream/solution.cpp: -------------------------------------------------------------------------------- 1 | class MedianFinder { 2 | priority_queue l; // max heap 3 | priority_queue, greater> r; // min heap 4 | public: 5 | MedianFinder() { 6 | 7 | } 8 | void addNum(int num) { 9 | l.push(num); 10 | r.push(l.top()); 11 | l.pop(); 12 | if(l.size() < r.size()) { 13 | l.push(r.top()); 14 | r.pop(); 15 | } 16 | } 17 | double findMedian() { 18 | return l.size() > r.size()? l.top(): ((long long)l.top()+r.top())*0.5; 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /297-SerializeandDeserializeBinaryTree/README.md: -------------------------------------------------------------------------------- 1 | ## 题目分析: 2 | 3 | 设计一个算法。支持1.将二叉树以某种方式转化为一字符串; 2.将字符串逆转化为二叉树 4 | 你需要保证将二叉树转化为字符串, 再将该字符串逆转化为二叉树, 得到的二叉树与原二叉树相同。 5 | 6 | ### 解题思路 7 | 我们可以将树进行先序遍历, 所得到的序列以' '顺次连接得到字符串; 逆转时将该字符串以' '分割开, 并按先序顺序构造出二叉树。时间复杂度为`O(n)`. 8 | 9 | ### 例子 10 | ``` 11 | 1 12 | / \ 13 | 2 3 14 | / \ 15 | 4 5 16 | 17 | 先序遍历, 得到序列['1', '2', 'None', 'None', '3', '4', '5'] 18 | 以' '顺次连接得到字符串"1 2 None None 3 4 5" 19 | 逆转时以' '分割得到序列['1', '2', 'None', 'None', '3', '4', '5'] 20 | 并以该序列dfs构造出二叉树. 21 | ``` -------------------------------------------------------------------------------- /3-lengthOfLongestSubstring/solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | int lengthOfLongestSubstring(string s) { 4 | bool vis[233] = {0}; int n = s.length (); 5 | int l = 0, r = 0; 6 | while (r < n && !vis[s[r]]) { 7 | vis[s[r]] = true; 8 | r++; 9 | } 10 | int ans = r-l; 11 | while (l < n) { 12 | while (r < n && !vis[s[r]]) { 13 | vis[s[r]] = true; 14 | r++; 15 | } 16 | ans = max (ans, r-l); 17 | vis[s[l]] = 0; 18 | l++; 19 | } 20 | return ans; 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /300-LongestIncreasingSubsequence/solution1.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | int lengthOfLIS(vector& nums) { 4 | if(nums.empty()) return 0; 5 | vector dp(nums.size(), 1); 6 | int res=0;//记录最大长度 7 | for(int i=0; i& nums) { 4 | vector tails(nums.size(), 0); 5 | int len=0;//记录当前最长递增子序列的长度 6 | for(const auto& num: nums){ 7 | int i=binarySearch(tails, 0, len, num);//二分搜索确定当前数字 num 在 tails 数组中的位置 8 | tails[i]=num;//更新 tails 数组 9 | if(i == len) ++len;//当前数字 num 大于 tails 数组中的所有值 10 | } 11 | return len; 12 | } 13 | private: 14 | int binarySearch(vector& tails, int start, int end, int val){ 15 | while(start < end){ 16 | int mid=start+(end-start)/2; 17 | if(tails[mid] < val) start=mid+1; 18 | else end=mid; 19 | } 20 | return start; 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /303.RangeSumQuery-Immutable/solution1.cpp: -------------------------------------------------------------------------------- 1 | class NumArray { 2 | public: 3 | NumArray(vector nums) { 4 | arr = nums; 5 | } 6 | 7 | int sumRange(int i, int j) { 8 | int sum = 0; 9 | for(int k = i; k <= j; k ++) { 10 | sum += arr[k]; 11 | } 12 | return sum; 13 | } 14 | private: 15 | //transfer the nums 16 | vector arr; 17 | }; 18 | 19 | /** 20 | * Your NumArray object will be instantiated and called as such: 21 | * NumArray obj = new NumArray(nums); 22 | * int param_1 = obj.sumRange(i,j); 23 | */ 24 | -------------------------------------------------------------------------------- /303.RangeSumQuery-Immutable/solution2.cpp: -------------------------------------------------------------------------------- 1 | class NumArray { 2 | public: 3 | NumArray(vector nums) { 4 | // nums may be empty 5 | if(nums.empty()) { 6 | return; 7 | } 8 | sums.push_back(nums[0]); 9 | int len = nums.size(); 10 | for(int i = 1; i < len; i ++) { 11 | sums.push_back(sums[i-1] + nums[i]); 12 | } 13 | } 14 | 15 | int sumRange(int i, int j) { 16 | if(i == 0) { 17 | return sums[j]; 18 | } else { 19 | return sums[j] - sums[i-1]; 20 | } 21 | } 22 | 23 | private: 24 | // record the sum of nums[0,i] into sums[i] 25 | vectorsums; 26 | }; 27 | 28 | /** 29 | * Your NumArray object will be instantiated and called as such: 30 | * NumArray obj(nums); 31 | * int param_1 = obj.sumRange(i,j); 32 | */ 33 | -------------------------------------------------------------------------------- /309-BestTimetoBuyandSellStockwithCooldown/README.md: -------------------------------------------------------------------------------- 1 | ## 题目分析: 2 | 3 | 给出一串序列表示股票每天的价格,问买卖股票最多能赚多少钱? 4 | 有两个限制条件: 5 | 1. 必须先买后卖,不能买入多次。 6 | 2. 第x天卖完股票,第x+1天不能买入股票, 需要等到x+2天才能再次买股票。 7 | 股票交易的流程是[买,卖,冷却,买,卖...] 8 | 9 | ### 解题思路: 10 | 11 | 动态规划,维护两个值,sell[i]表示第i天且手上没有股票时所获得的最大利润,buy[i]表示第i天且当前买入股票后的最大利润。 12 | 令prices[i]表示第i天股票的价格,那么状态转移可以表示为: 13 | sell[i] = max(buy[i-1] + prices[i]) 14 | buy[i] = max(sell[i-2] - prices[i]) 15 | 这里利用prev_sell以及prev_sell两个变量实现对sell和buy的降维处理。 16 | 更新顺序上经过一定的安排即可满足冷却一天的条件,具体细节看代码。 17 | 18 | #### 算法正确性: 19 | 20 | 因为这里最大利润满足最优子结构,故可以用动态规划求解,算法正确,时间复杂度为O(n)。 21 | -------------------------------------------------------------------------------- /309-BestTimetoBuyandSellStockwithCooldown/solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | int maxProfit(vector &prices) { 4 | int buy = -0x3f3f3f3f, sell = 0, prev_sell = 0, prev_buy; 5 | for (int i = 0; i < (int)prices.size(); i++) { 6 | int price = prices[i]; 7 | prev_buy = buy; 8 | buy = max(prev_sell - price, buy); 9 | prev_sell = sell; 10 | sell = max(prev_buy + price, sell); 11 | } 12 | return sell; 13 | } 14 | }; -------------------------------------------------------------------------------- /312-Burst Balloons/solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution 2 | { 3 | public: 4 | int maxCoins(vector& nums) 5 | { 6 | int n=nums.size(); 7 | int arr[n+2],dp[n+2][n+2]; 8 | arr[0]=arr[n+1]=1; 9 | for(int i=1;i<=n;i++) 10 | arr[i]=nums[i-1]; 11 | for(int l=2;l<=n+2;l++) 12 | for(int i=0;i+l-1> &nums,int L,int R,vector &ans) 5 | { 6 | if(R-L<=1) return; 7 | int mid=(L+R)/2; 8 | mergeSort(nums,L,mid,ans); 9 | mergeSort(nums,mid,R,ans); 10 | int rp=mid; 11 | for(int i=L;inums[rp].first) ++rp; 14 | ans[nums[i].second]+=rp-mid; 15 | } 16 | inplace_merge(nums.begin()+L,nums.begin()+mid,nums.begin()+R); 17 | } 18 | vector countSmaller(vector& nums) 19 | { 20 | int n=nums.size(); 21 | if(n==0) return {}; 22 | vector ans(n,0); 23 | vector> vec; 24 | for(int i=0;i c(26,0),in(26,0); 8 | for(int i=0;i& coins, int amount) { 4 | sort(coins.begin(), coins.end()); 5 | vector dp(amount+1,INT_MAX); 6 | dp[0]=0; 7 | for(int i=0; iamount) break; 11 | dp[i+num] = min(dp[i]+1,dp[i+num]); 12 | } 13 | } 14 | return dp[amount]==INT_MAX ? -1 : dp[amount]; 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /327-CountOfRangeSum/README.md: -------------------------------------------------------------------------------- 1 | ## 题目分析 2 | 3 | 给定一组数,问有多少个区间和S(i,j)(i<=j)在[lower,upper]之间。 4 | 5 | ### 解题思路 6 | 7 | 像所有关于区间和的题目一样,第一步的套路就是算个前缀和,把S(i,j)转化成`s[j]-s[i-1]`。这样题目就转化成了,给你一组数,问有多少个(i,j)使得`i=l`,`r'>=r`。为什么?因为前缀和是不递减的。这样,我们就得到了一个O(n)的算法:考虑l,随着j从0到n的增长,l一直是不递减的,也就是说l最多变化n次,增长到n,同理r也是。 10 | 11 | 但现在数可能是负数,也就是说前缀和是没有不递减这样的好性质的。重新观察下前面的算法,我们希望前缀和是有序的,那怎样才能使一个无序的变成有序的呢?显而易见:排序,但排序后位置信息就已经被打乱了,就有可能没法保证j>i了。也就是说,现在我们既想排序,又想保证j>i。那么似乎就只有一种可能了:让j的范围的最小值都比i的范围的最大值都大,那么任意一个j是大于i的,这样我们就可以对j范围内的数排序,i范围内的排序。 12 | 13 | 因此,我们把序列分为前后两块,把它们分别排序,然后让j始终在后面的序列内,i始终在前面的范围内,这样就可以应用有序时候的算法了。当然,排序前,要先求出前后两块序列内的答案,怎么做?把前面的分为两块...... 14 | 15 | -------------------------------------------------------------------------------- /327-CountOfRangeSum/solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution 2 | { 3 | public: 4 | int mergeSort(vector &sum,int L,int R,int lower,int upper) 5 | { 6 | if(R-L<=1) return 0; 7 | int mid=(L+R)/2; 8 | int count=mergeSort(sum,L,mid,lower,upper)+mergeSort(sum,mid,R,lower,upper); 9 | int lp=mid,rp=mid; 10 | for(int i=L;i& nums, int lower, int upper) 20 | { 21 | int n=nums.size()+1; 22 | vector sum(n,0); 23 | for(int i=1;i& nums, int target) { 4 | int left=0, right=nums.size()-1; 5 | while(left < right){ 6 | int mid = left + (right - left)/2; 7 | if(nums[mid] == target) return mid; 8 | if(nums[mid] > nums[right]){ //二分中点在左侧有序序列中 9 | if(target < nums[mid] && target >= nums[left]){ 10 | right = mid - 1; 11 | }else{ 12 | left = mid + 1; 13 | } 14 | }else{ //二分中点在右侧有序序列中或者整体有序序列中 15 | if(target > nums[mid] && target <= nums[right]){ 16 | left = mid + 1; 17 | }else{ 18 | right = mid -1; 19 | } 20 | } 21 | } 22 | return !nums.empty() && nums[left] == target ? left : -1; 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /33-SearchInRotatedSortedArray/solution2.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | int search(vector& nums, int target) { 4 | int left=0, right=nums.size()-1; 5 | //find the index of the smallest number 6 | while(leftnums[right]) left=mid+1; 9 | else right=mid; 10 | } 11 | int rot=left; 12 | left=0; right=nums.size()-1; 13 | while(left<=right){ 14 | int mid=left+(right-left)/2; 15 | int realmid=(mid+rot)%nums.size(); 16 | if(nums[realmid]==target) return realmid; 17 | if(nums[realmid] 4`, 故添加`4`, 则当前能得到`1 - 7`的数, 之后扫描到`7`, 发现`7 < 8`, 直接更新当前能得到的数位`1 - 14`, 故答案为`1`. -------------------------------------------------------------------------------- /330-PatchingArray/solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | int minPatches(vector& nums, int n) { 4 | int ans = 0, i = 0; 5 | long long sum = 0; 6 | while(sum < n){ 7 | if(i < nums.size()&&nums[i] <= sum+1) 8 | sum += nums[i++]; 9 | else{ 10 | ans++; 11 | sum += sum+1; 12 | } 13 | } 14 | return ans; 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /332-Reconstruct Itinerary/Solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution{ 2 | public: 3 | vector findItinerary(vector> tickets) 4 | { 5 | vector ans; 6 | Link.clear(); 7 | for(auto &tmp: tickets) 8 | Link[tmp.first].push(tmp.second); 9 | dfs("JFK",ans); 10 | return vector(ans.rbegin(),ans.rend());//将当前答案序列进行翻转以得到我们所需要的最终答案 11 | } 12 | void dfs(string Cur, vector &ans) 13 | { 14 | /* 15 | Cur中所存储的是当前所在的名称 16 | 以vector容器来存储最终的答案序列 17 | */ 18 | while(!Link[Cur].empty()) 19 | { 20 | string tmp = Link[Cur].top(); //取出当前的堆顶城市名称 21 | Link[Cur].pop(); 22 | dfs(tmp,ans); 23 | } 24 | ans.push_back(Cur);//当 当前点所能千万的点都被访问过时, 将其加入答案序列当中 25 | } 26 | private: 27 | unordered_map, greater>> Link;//以小根堆的形式来存储每个城市所能够到达的另外一个城市,\ 28 | 在堆顶的一定是当前所能够到达的字典序最小的城市名称 29 | } 30 | -------------------------------------------------------------------------------- /335-Self Crossing/solution.cpp: -------------------------------------------------------------------------------- 1 | bool isSelfCrossing(vector& x) 2 | { 3 | int len=x.size(); 4 | bool isreduce=false;//序列是否进入降序的标志 5 | for(int i=2;i=4&&x[i-4]+x[i]>=x[i-2])||(i==3&&x[i]==x[i-2]))x[i-1]=x[i-1]-x[i-3];//两个注意的地方 13 | } 14 | } 15 | else//序列已处于降序状态 16 | { 17 | if(x[i]>=x[i-2])return true;//出现非降序的元素,存在一个点经过了多次 18 | } 19 | } 20 | return false;//不存在一个点经过多次 21 | } 22 | -------------------------------------------------------------------------------- /336-PalindromePairs/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leetcode-Tutorial/Tutorial/ef9f3bdaa3d3fcf78f5f266549cad3ee3145844f/336-PalindromePairs/README.md -------------------------------------------------------------------------------- /336-PalindromePairs/solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | bool judge(string& a, string& b){ 4 | int i = 0, j = b.size()-1; 5 | while(i < a.size()&&j >= 0&&a[i] == b[j]) i++, j--; 6 | if(i < a.size()&&j >= 0) return false; 7 | if(j < 0){ 8 | j = a.size()-1; 9 | while(i < j&&a[i] == a[j]) i++, j--; 10 | return i >= j; 11 | } 12 | else{ 13 | i = 0; 14 | while(i < j&&b[i] == b[j]) i++, j--; 15 | return i >= j; 16 | } 17 | } 18 | vector> palindromePairs(vector& words) { 19 | vector> ans; 20 | for(int i = 0; i < words.size(); i++) 21 | for(int j = 0; j < words.size(); j++) 22 | if(i != j&&judge(words[i], words[j])) 23 | ans.push_back({i, j}); 24 | return ans; 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /338.CountingBits/solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | vector countBits(int num) { 4 | 5 | vectordp(num + 1, 0); 6 | dp[0] = 0; 7 | if(num == 0) return dp; 8 | dp[1] = 1; 9 | if(num == 1) return dp; 10 | 11 | int j = 2; 12 | int size = 2; 13 | while(j <= num) { 14 | for(int i = 0;i < size && j <= num; i ++, j ++) { 15 | dp[j] = dp[i] + 1; 16 | } 17 | size = j; 18 | } 19 | return dp; 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /343-IntegerBreak/README.md: -------------------------------------------------------------------------------- 1 | ## 题目分析: 2 | 3 | 给出一个数n,要求拆分成若干整数,求这些整数的乘积最大是多少? 4 | 如10 = 3 + 3 + 4, 最大为3 * 3 * 4 = 36 5 | 6 | ### 解题思路: 7 | 8 | 数学思路,由均值不等式,n个数的算数平均数大于等于几何平均数,可以得出,n拆分成相等的数乘积最大。 9 | 为了求出拆分成几个数,设n拆分成实数x,一共拆分成n/x个数。 10 | 设它们的积为f(x),则f(x) = x^(n/x),对f(x)求导,得: 11 | f′(x)=(n/(x^2)) * x^(n/x) * (1-lnx) 12 | 当x = e时取得极大值。 13 | 由于这里规定x取整,e = 2.71..,所以取3最好,再取不到3的情况下再取2。 14 | 15 | #### 算法正确性: 16 | 17 | 根据数学推导得出结论,算法正确,复杂度为O(logn)。 18 | -------------------------------------------------------------------------------- /343-IntegerBreak/solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | long long integerBreak(long long n) { 4 | if (n == 2) return 1; 5 | if (n == 3) return 2; 6 | if (n == 4) return 4; 7 | if (n == 5) return 6; 8 | if (n == 6) return 9; 9 | return 3 * integerBreak(n - 3); 10 | } 11 | }; -------------------------------------------------------------------------------- /349.Intersection of Two Arrays/README.md: -------------------------------------------------------------------------------- 1 | # 题目分析 2 | 给定俩个数组,让我们得出俩个数组交叉元素的值,返回这些元素(独特不同) 3 | 4 | ### 解题思路(1) 5 | 题意要我们找出俩个数组的共同部分,并且保存不重复的值,第一种方法我们可以将俩个数组都进行排序(当碰到数组问题,排序后一定会比 不排序有思路,好做(只要运行复杂度在排序复杂度之内即可)),那么我们可以用俩个指针来指向俩个数组,碰到相等的时候,就记录,但是注意由于结果是不重复的,那么我保存结果用集合即可,此算法时间复杂度为0(m+n),假设m,n分别为俩个数组的长度 6 | 7 | ### 解题思路(2) 8 | 当涉及到查找问题的时候,就可以想到在一个数组中固定,另一个数组排序好,然后采用二分查找的过程~结果用集合来表示(防止结果重合) 9 | 10 | ### 解题思路(3) 11 | 用hash来做,扫描第一个数组的时候,记录在hash表中,并使得该key值对应的val为1,然后扫描第二个数组,碰到大于0的就记录,并且此时立即清空,为了防止记录重复的,这里的hash功能用c++中stl中的map(实际上内部是用红黑树实现)来实现!具体建立代码。 -------------------------------------------------------------------------------- /349.Intersection of Two Arrays/solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | vector intersection(vector& nums1, vector& nums2) { 4 | vectorres; 5 | mapMap; 6 | int len1 = nums1.size(),len2 = nums2.size(),i; 7 | for(i = 0;i0) 14 | { 15 | res.push_back(nums2[i]); 16 | Map[nums2[i]] = 0; //因为它不允许重复的,那么在这里就一定要清零 17 | } 18 | } 19 | return res; 20 | } 21 | }; -------------------------------------------------------------------------------- /354-RussianDollEnvelopes/README.md: -------------------------------------------------------------------------------- 1 | ## 题目分析 2 | 3 | 给定一组套娃,每个套娃用(w,h)描述,一个能放进另一个里面当且仅当w,h都大于另一个。问最多可以几个套娃放在一起。 4 | 比如,`[[5,4],[6,4],[6,7],[2,3]]`的答案是3,因为 `[2,3] => [5,4] => [6,7]` 5 | 6 | ### 解题思路 7 | 8 | 考虑如果只有一个维度,比方说w的话,那么我们直接排序,然后扫一遍,看有多少不同的数就可以了,这样子显然是最优的。 9 | 10 | 但现在每个套娃有两个维度(w,h),简单的排序已经不可以了,对于样例来说,排序后是`[2,3],[5,4],[6,4],[6,7]`,[5,4]是不能放进[6,4]里的。不过,我们也可以发现一个现象,就是排在后面的是显然不可能放进前面里的,也就是说,最终的答案一定是在排序后的数组中依次出现的,也就相当于是排序后的一个子序列。 11 | 12 | 这样,是不是就已经很熟悉题目的模型了?这样,这道题目就成了一个经典的dp问题,即最长上升子序列问题。接下来我们就可以写一个O(n^2)的最长上升子序列,或者也可以写一个O(nlgn)的算法。 -------------------------------------------------------------------------------- /354-RussianDollEnvelopes/solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution 2 | { 3 | public: 4 | int maxEnvelopes(vector>& envelopes) 5 | { 6 | sort(envelopes.begin(),envelopes.end()); 7 | int n=envelopes.size(); 8 | if(n==0) return 0; 9 | int dp[n]; 10 | for(int i=0;ienvelopes[j].first 14 | &&envelopes[i].second>envelopes[j].second) 15 | dp[i]=max(dp[i],dp[j]+1); 16 | int ans=0; 17 | for(int i=0;i dp(11); 6 | dp[0] = 1; 7 | 8 | for(int i = 1; i <= 10; i ++) { 9 | int temp = 9; 10 | for(int j = 9; j > 10 - i; j--) { 11 | temp *= j; 12 | } 13 | dp[i] = temp + dp[i - 1]; 14 | } 15 | 16 | if(n > 10) return dp[10]; 17 | else return dp[n]; 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /363-MaxSumofRectangleNoLargerThanK/Solution.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leetcode-Tutorial/Tutorial/ef9f3bdaa3d3fcf78f5f266549cad3ee3145844f/363-MaxSumofRectangleNoLargerThanK/Solution.cpp -------------------------------------------------------------------------------- /368-LargestDivisibleSubset/README.md: -------------------------------------------------------------------------------- 1 | ## 题目分析 2 | 给定一组正整数,找出满足以下条件的最大子集: 3 | - 子集中的每一对数字`(Si,Sj)`满足:`Si % Sj == 0 或者 Sj % Si == 0` 4 | - 如果有多个答案,任何一个都可以。 5 | 6 | ```cpp 7 | Example 1: 8 | nums: [1,2,3] 9 | Result: [1,2] (of course, [1,3] will also be ok) 10 | 11 | Example 2: 12 | nums: [1,2,4,8] 13 | Result: [1,2,4,8] 14 | ``` 15 | ## 题解 16 | 假设满足上述条件的一个子集(不一定是最大子集)为`Y = [1,2,4]`,有一个新的整数`S = 8`,`S`可以加入子集`Y`的充要条件是 17 | - `S`可以整除子集`Y`中的最小整数,或者可以被子集`Y`中的最大整数除尽。 18 | - 比如`S = 8`可以被子集`Y`中的最大整数除尽。 19 | 20 | 一个递增序列`nums=[1...n]` 21 | `Count[i]`表示最大整数是`nums[n]`的满足条件的子集的大小。 22 | `Count[i+1] = max( { 1 + Count[j] | nums[n+1] % nums[j] == 0 and 0 <= j <= n } )` 23 | 计算`Count`数组可以是一个动态规划的过程,可以使用另一个数组记录子集。 24 | `parent[i]`表示最大子集中整数`nums[i]`的前一个整数,比如: 25 | - `nums=[1, 2, 4, 8]`,对应的`parent = [0, 0, 1, 2]` 26 | - 当`i == parent[i]`时,即子集中整数`nums[i]`的上一个数字是它本身,代表这是子集中的第一个数。 27 | 28 | 具体实现在`solution.cpp`中(带注释)。 29 | **时间复杂度** 30 | 由`Count 和 parent`数组的计算的时间复杂度是`O(n^2)`,空间复杂度是`O(n)`。 31 | -------------------------------------------------------------------------------- /375-GuessNumberHigherOrLowerII/solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | int getMoneyAmount(int n) { 4 | int min_sum[n + 2][n + 2]; 5 | memset(min_sum, 0, sizeof(min_sum)); 6 | // iterate the length of subarray 7 | for (int l = 1; l <= n; l++) 8 | // iterate the beginning of subarray 9 | for (int i = 1; i + l - 1 <= n; i++){ 10 | // calculate the end of subarray based on the length of subarray 11 | int j = i + l - 1; 12 | // if l == 1(i.e. i==j), it means the penalty of subarray is zero 13 | if (i == j) min_sum[i][j] = 0; 14 | else{ 15 | min_sum[i][j] = (1 << 30); 16 | // iterate the first picked number in subarray 17 | for (int k = i; k <= j; k++) 18 | min_sum[i][j] = min(min_sum[i][j], max(min_sum[i][k - 1], min_sum[k + 1][j]) + k); 19 | } 20 | } 21 | return min_sum[1][n]; 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /376-WiggleSubsequence/solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | int wiggleMaxLength(vector& nums) { 4 | if(nums.size()<2) return nums.size(); 5 | // dp[i] represents the difference of the last two numbers of wiggle subsequence of nums[0...i] 6 | vector dp(nums.size()); 7 | dp[1] = nums[1]-nums[0]; 8 | // dp[1]==0 is same as nums[1]==nums[0], so the length of wiggle subsequence of nums[0,1] is 1 9 | int res = dp[1] ? 2 : 1; 10 | for(int i=2; i M`、`vector V`,则两个容器之间的关系满足该关系——若`y = M[x]`,则`V[y]=x`。 13 | 设计出数据结构之后,考虑如何实现各种操作。 14 | ### 插入 15 | 当一个新的值被插入到集合中,先检查其是否已经在M之中,若不在则在V的尾端申请一空间,建立M与v之间的联系 16 | ### 移除 17 | 通过M查找到对应整数存储在V中的位置,将该单元清空。为维护V中存储空间连续的性质,将V尾部的单元移至被删除的单元的位置,同时维护若`y = M[x]`,则`V[y]=x`的性质。 18 | ### 随机输出 19 | 产生范围为`0~vector.size()`的随机数,再输出对应vector单元中的数字即可。 20 | -------------------------------------------------------------------------------- /39-CombinationSum/solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | vector> combinationSum(vector& candidates, int target) { 4 | sort(candidates.begin(), candidates.end()); 5 | result.clear(); 6 | helper(candidates, vector(), 0, target); 7 | return result; 8 | } 9 | private: 10 | void helper(vector& candidates, vector cur, int start, int target){ 11 | if(target==0) { 12 | result.emplace_back(); 13 | result.back().assign(cur.begin(), cur.end()); 14 | return; 15 | } 16 | for(int i=start; i> result; 24 | }; 25 | -------------------------------------------------------------------------------- /392-IsSubsequence/solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | bool isSubsequence(string s, string t) { 4 | //俩个指针都往前走 5 | if(s.length()==0) 6 | return true; //空字符串是任何字符串的子串 7 | int i = 0,j = 0; 8 | bool res = false; 9 | int s_len = s.length(),t_len = t.length(); 10 | while(j < t_len) 11 | { 12 | if(t[j] == s[i]) 13 | { 14 | i++; 15 | if(i==s_len) 16 | { 17 | res = true; 18 | break; 19 | } 20 | } 21 | j++; //无论t[j]是否等于s[i],j都要加1 22 | } 23 | return res; 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /394-Decode String/README.md: -------------------------------------------------------------------------------- 1 | ## 题目分析: 2 | 题目给出一个编码过的字符串,要求对其进行解码。 3 | 编码规则为:`k[encoding_string]`,其中*k*为*encoding_string*的重复次数,保证为正数。 4 | 如 `3[a2[bc]]`解码后变为 `abcbcabcbcabcbc`。 5 | ### 解题思路 6 | 简单的模拟类型题。 7 | 使用一个指针从头开始扫描 8 | + 若遇到的字符为字母则加入当前的临时字符串`tmp_string`当中。 9 | + 若遇到的字符为数字则说明当前遇到了压缩字符串的重复次数*k*,将其解析出来。 10 | + 若遇到的字符为`‘[’`则表示着*encoding_string*的开端,将当前的`tmp_string`使用栈存起来,重复次数k压入另一次存储次数的栈中,而后将`tmp_string`清为空字符串。 11 | + 若遇到的字符为`']'`则意味着*encoding_string*的结束,当前`tmp_string`便为*encoding_string*,取出栈顶的*k*,将`tmp_string`重复*k*次。再取出存储原字符串的栈顶元素,与当前`tmp_string`拼接。 12 | 13 | 最后的`tmp_string`便为我们所期望的答案,输出即可。 14 | -------------------------------------------------------------------------------- /399-Evaluate Division/README.md: -------------------------------------------------------------------------------- 1 | ## 题目分析: 2 | 提供*n*个`A/B=K`的等式,*A*与*B*都为字符串,*K*为浮点数。现有*M*个询问分式的值为多少,如果可以根据之前所给定的等式计算出当前的值则输出答案,否则输出`-1.0` 3 | 4 | ### 解题思路(1): 5 | 将上述给定的等式抽象为图模型。对于每个变量,我们以节点来表示,而之间的比例关系则以边权的形式来加以体现。 6 | + 若`A/B=K`,则建立一条*A*至*B*的边权为*K*的单向边,同时建立一条边权为*1/K*的反向边(**需要预先判断K是否为0**) 7 | 8 | 而对于M个询问,我们可以预处理任意两个能建立关系的结点的比例关系,则可离线地给出每个分式的值。考虑使用`Floyd`算法求出关系,表示式为`F[A/B]=F[A/C]*F[C/B]`,枚举*A*、*B*和*C*节点。 9 | 整体的时间复杂度为`O(N^3)`。 10 | ### 解题思路(2): 11 | 在解法1的基础上思考进行改进。由于这张图中任意两点的比值都是确定的,并不存在多值,因此用最短路算法求解时,实质上有许多的过程都是冗余的。考虑**集合`{A, B, C, D}`内任意两点的比例关系都存在,则可以从集合中的一个变量出发,遍历至集合内的其它变量**。即存在比例关系的两个节点实质上都存在于一个**强连通分量**内。 12 | 因此我们可以从一个确定的节点*K*出发,求出其所属的强连通分量内任意一个变量*X*与*K*的比例,即*K/X*的值,若当前要计算出集合内另一变量*Y*与*X*的比例——*Y/X*,则可通过 ` Y/X = (K/X) / (K/Y) ` 计算出。 13 | 若*Y*与*X*不属于一个强连通分量,则当前无解。 14 | 每个节点仅访问一次,因此时间复杂度为`O(N)` 15 | -------------------------------------------------------------------------------- /401-BinaryWatch/README.md: -------------------------------------------------------------------------------- 1 | ## 题目分析 2 | 有一种二进制手表,它的小时和分钟都二进制表示的。4 位 LEDs 表示小时(0-11),6 位 LEDs 表示分钟(0-59),每个 LED 代表`1`或者`0`,二进制最右边是最低位。 3 | 给定非负整数`n`,表示亮着的 LEDs 的数量,返回这种二进制手表所有可能表示的时间。 4 | ```cpp 5 | 输入: n = 1 6 | 输出: 7 | [ 8 | "1:00(0001:000000)", 9 | "2:00(0010:000000)", 10 | "4:00(0100:000000)", 11 | "8:00(1000:000000)", 12 | "0:01(0000:000001)", 13 | "0:02(0000:000010)", 14 | "0:04(0000:000100)", 15 | "0:08(0000:001000)", 16 | "0:16(0000:010000)", 17 | "0:32(0000:100000)" 18 | ] 19 | ``` 20 | 注意: 21 | - 输出时间的顺序任意。 22 | - 小时没有前导`0`。 23 | - 分钟包含两位数字,位数不足前面填`0`。 24 | 25 | ## 题解 26 | 小时有`12`个数字,分钟有`60`个数字,一共`720`种组合,每一种组合我们都可以知道它有几个 LED 是亮着的(二进制对应位为`1`)。 27 | 我们枚举`720`种组合,把那些满足条件的组合找出来即可。 28 | 具体实现在 `solution.cpp`。 -------------------------------------------------------------------------------- /401-BinaryWatch/solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | vector readBinaryWatch(int num) { 4 | vector result; 5 | for(int h=0; h<12; h++) 6 | for(int m=0; m<60; m++){ 7 | if(bitset<10>((h<<6)+m).count() == num) 8 | result.emplace_back(to_string(h) + (m<10 ? ":0":":") + to_string(m)); 9 | } 10 | return result; 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /403. Frog Jump/README.md: -------------------------------------------------------------------------------- 1 | ## 题目分析 2 | 3 | 青蛙过河,河面上一共n(2<=n<1100)个石头,初始在第1个石头上;如果上一步跳了k距离,那么下一步只能是k-1,k或者k+1,而且只能向前跳,问能否跳到最后的石头。 4 | 5 | ### 解题思路 6 | 7 | 首先容易想到一种的方法:令`f(i)` 代表是否可以到达第i个石头,那么f(n)就是问题的答案。容易看出的是,`f(1)=true`即第一个可以到达自己。考虑`f(i)`,第i个是否可达呢?然后发现推不下去了,为什么呢?因为由于步数的限制,要想知道能不能到达第i个,那么我们必须知道上一步的距离和石头。虽然这个方法失败了,但也为我们指明了方向。 8 | 9 | 所以我们**扩充**状态:令f(i,k)代表是否可以到达第i个石头,并且跳到这个石头上的步数为k,那么如果任意一个f(n,k)为true那么答案就是true。首先容易得知f(1,0)为真。其次,如果f(i,k)为真,那么f(x,k),f(x,k-1),f(x,k+1)也为真,其中x是从i跳了k(k-1或者k+1)步到达的石头的编号。 10 | 11 | 由于我们一共有O(n^2)个状态,每个状态只有3个可能的转移,如果找到坐标为x的石头编号的复杂度是k的话,那么一共的复杂度是O(k*n^2),如果使用一个map的话,那么复杂度就是O(n^2lgn),是hashmap的话就是O(n^2)。 12 | 13 | 算法的正确性蕴藏在算法的描述之中,可以使用数学归纳法规范证明,有兴趣可以试试。 14 | 15 | ### 例子 16 | 17 | 假设坐标为[0,1,3,4,5],那么f(1,0)=true `=>` f(2,1)=true `=>` f(3,2)=true `=>` f(4,1)=true,f(5,2)=true. 18 | -------------------------------------------------------------------------------- /403. Frog Jump/solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | bool dp[1110][1110]; 4 | bool canCross(vector& stones) 5 | { 6 | int n=stones.size(); 7 | dp[0][0]=true; 8 | for(int i=1;i &stones, int pos) 27 | { 28 | int idx=lower_bound(stones.begin(),stones.end(),pos)-stones.begin(); 29 | if(stones[idx]!=pos) return -1; 30 | return idx; 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /407-TrappingRainWaterII/README.md: -------------------------------------------------------------------------------- 1 | ## 题目分析: 2 | 3 | 给定一个二维矩阵表示一块矩形地面, 每个格子的数表示该格子所代表的地面的高度。问:注满水后, 该矩形地面最多能容多少体积的水? 4 | 5 | ### 解题思路(1) 6 | 对于每个格子,我们需要找到该格子最多能容纳的水的体积。如果以该体积的数值向周围进行bfs, 遇到地面高于水位的相当于阻碍, 遇到地面比水位低的格子则可访问格子。若能到达边界处, 则该格子无法容纳该体积数量的水。 7 | 我们可以对每个格子二分该各自能容纳的水的体积进行bfs, 时间复杂度为`O((mn)^2*logh)`. 8 | 9 | ### 解题思路(2) 10 | 我们优先考虑边界上地面高度较低的格子。用一个最小堆维护边界格子。每次弹出堆顶元素, 以堆顶元素为起始点进行bfs操作, 若访问到的格子高度比当前值低, 则我们可以用当前值为水位高度填充所访问的节点, 否则, 相当于遇到障碍, 则将障碍点push进最小堆。时间复杂度为`O(mn*log(n+m))`. 11 | 12 | #### 算法正确性: 13 | 以最小堆维护边界格子显然是正确的。因为最短板理论, 格子所能容纳的水的高度必然不会超过与它相联通的格子的高度(否则水就会溢出)。该算法相当于水从外往里涌, 水自然优先从地面高度低的格点往里涌入。 14 | 15 | #### 例子: 16 | ``` 17 | [1,4,3,1,3,2] 18 | [3,2,1,3,2,4] 19 | [2,3,3,2,3,1] 20 | 初始状态: 21 | [1,4,3,1,3,2] 22 | [3, , , , ,4] 23 | [2,3,3,2,3,1] 24 | 以heightmap[0][3]的1进行bfs, 遇到四周都是3, 被阻: 25 | [1,4,3,1,3,2] 26 | [3, , ,3, ,4] 27 | [2,3,3,2,3,1] 28 | ... 29 | ... 30 | 以heightmap[1][3]的3进行bfs, 遇到值为1, 2, 都较3为低, 填充: 31 | [1,4,3,1,3,2] 32 | [3,3,3,3,3,4] 33 | [2,3,3,2,3,1] 34 | 上述即为每个格子最终的水位高度。 35 | ``` -------------------------------------------------------------------------------- /41-FirstMissingPositive/README.md: -------------------------------------------------------------------------------- 1 | ## 题目分析: 2 | 3 | 给一个int型数组,问:不在该数组中的最小正整数。时间复杂度要求 `O(n)` ,空间复杂度要求为常数。 4 | 5 | ### 解题思路 6 | 常规思路就是排序,扫一遍。时间复杂度为 `O(nlogn)` 。题干要求的时间复杂度让我们想到需要类似于计数排序,但计数排序的空间复杂度并不满足题干要求。挖掘题面信息,我们发现题目要求的是正整数,int型最高位为0。因此我们可以利用好这个最高位来打标记。 7 | 我们首先可以预处理出所有正整数,然后用类似计数排序去标记该数组中的正整数,答案最大为数组中的正整数的个数 + 1,若遇到更大的正整数,直接忽略掉不做标记即可。 8 | 时间复杂度为 `O(n)` ,额外空间复杂度为 `O(1)` 。 9 | 如2, 5, -1, 1, 预处理后为2, 5, 1, 各正数为02, 05, 01, (0表示符号位), 扫到2时, 数组变为02, 15, 01; 扫到5时,越界,忽略, 数组仍为02, 15, 01; 扫到1时, 数组为12, 15, 01。最后计算答案时,从前往后扫描,发现第3个数符号位为0, 故答案为3。 10 | -------------------------------------------------------------------------------- /41-FirstMissingPositive/solution.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leetcode-Tutorial/Tutorial/ef9f3bdaa3d3fcf78f5f266549cad3ee3145844f/41-FirstMissingPositive/solution.cpp -------------------------------------------------------------------------------- /410-SplitArrayLargestSum/README.md: -------------------------------------------------------------------------------- 1 | ## 题目分析: 2 | 3 | 给出一串n个数的序列,要求将这n个数分成m个块,算出每个块中数的总和,要保证总和的最大值最小。 4 | 5 | ### 解题思路: 6 | 7 | 最小化最大值,显然是二分搜索结果判断是否可行,关键在于判断函数的设计。 8 | 判断函数可以从另一个角度考虑,假设当前枚举的最大值是limit,那么肯定每个块的最大值都不能超过这个限制。 9 | 那么从左到右扫一遍,记录当前的累加和now,如果扫到第i个数nums[i]的时候,now > sum,说明nums[i]需要放到新的块中,块的数目c加一。这样最后在保证所有块的总合都小于等于最大值的情况下,看c是否小于等于m,若c <= m,说明还可以继续二分更小的结果,否则就要二分更大的结果。 10 | 11 | #### 算法正确性: 12 | 13 | 举例说明: 14 | 数组序列为[1,3,2,1,5],要求分成3块,初始化二分范围是[5,12],f(x)为总和最大值不超过x的情况下最少分成几块。 15 | step 1:l = 5, r = 12, mid = 8, 则f(8) = 2 <= 3. 16 | step 2: l = 5, r = 8, mid = 6, 则f(6) = 2 <= 3. 17 | step 3: l = 5, r = 6, mid = 5, 则f(5) = 4 > 3. 18 | step 4:l = 6, r = 6, l == r, 所以最终结果就是6. 19 | 每次二分都需要一遍遍历,这样算法的复杂度就是O(n * logx),其中x是数的大小,n是数组长度。 20 | 21 | -------------------------------------------------------------------------------- /410-SplitArrayLargestSum/solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | using ll = long long; 4 | 5 | bool canSplit(vector& nums, int m, ll limit) { 6 | ll now = 0; 7 | int c = 1, cnt = nums.size(); 8 | for (int i = 0; i < cnt; i++) { 9 | now += nums[i]; 10 | if (now > limit) { 11 | now = nums[i]; 12 | ++c; 13 | } 14 | } 15 | return c <= m; 16 | } 17 | 18 | int splitArray(vector& nums, int m) { 19 | ll l = 0, r = 0; 20 | int cnt = nums.size(); 21 | for (int i = 0; i < cnt; i++) { 22 | l = max(l, (ll)nums[i]); 23 | r += nums[i]; 24 | } 25 | while (l < r) { 26 | ll mid = (l + r) >> 1LL; 27 | if (canSplit(nums, m, mid)) r = mid; 28 | else l = mid + 1; 29 | } 30 | return r; 31 | } 32 | }; -------------------------------------------------------------------------------- /413-Arithmeticslices/README.md: -------------------------------------------------------------------------------- 1 | ## 题目分析: 2 | 3 | 给出一个数组A,求出A中有多少个连续段是长度大于等于3的等差数列。 4 | 如:A = [1,2,3,4],那么一共有3个不同的段是等差序列:[1,2,3],[2,3,4],[1,2,3,4] 5 | 6 | ### 解题思路: 7 | 8 | 遍历数组A,假设当前找到A[i],如果发现A[i-2],A[i-1],A[i]这三个数是等差序列,这时候答案就要加1. 9 | 接下来看A[i+1],如果A[i-1],A[i],A[i+1]是等差序列,那么显然A[i-2],A[i-1],A[i],A[i+1]也是等差序列,这时答案要加2. 10 | 因为如果找到当前[i..j]这一段都是等差序列,那么[i..j+1]如果也满足等差序列,那么这时候等于答案增加了[i..j+1],[i+1..j+1],[i+2..j+1]...[j-1,j,j+1]一共j-i个等差序列. 11 | 这样保存一个当前找到的变量now,每次等差数列长度增加1时,就会增加now+1个不同的等差序列。 12 | 13 | #### 算法正确性: 14 | 15 | 可以从另一个角度验证算法的正确性,如果找到[i..j]这一段是等差序列: 16 | 以i为左端点一共有j-i-2个。 17 | 以i+1为左端点一共有j-i-3个。 18 | ... 19 | 以j-2为左端点一共有1个。 20 | 所以,那么从中一共能找到(j-i-1) * (j-2-i) / 2个等差序列,与上述算法结果一致。 21 | 整个算法需要遍历一遍数组A,复杂度O(n) 22 | 23 | -------------------------------------------------------------------------------- /413-Arithmeticslices/solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | int numberOfArithmeticSlices(vector& A) { 4 | int n = A.size(); 5 | if (n < 3) return 0; 6 | int now = 0; 7 | if (A[2] - A[1] == A[1] - A[0]) now = 1; 8 | int ans = now; 9 | for (int i = 3; i < n; ++i) { 10 | if (A[i] - A[i - 1] == A[i - 1] - A[i - 2]) ++now; 11 | else now = 0; 12 | ans += now; 13 | } 14 | return ans; 15 | } 16 | }; -------------------------------------------------------------------------------- /414.第三大数/README.md: -------------------------------------------------------------------------------- 1 | # 题目分析 2 | 题意要求我们将一个数组中的第三大的数进行返回。 3 | 如果数组中元素少于3个,那么返回最大的那个值。注意,相同元素只算一个。 4 | 5 | ### 解题思路(1) 6 | 我们很容易想到对数组进行逆序排序,然后从头到尾扫,用一个变量count=0进行统计不同的个数,最后发现如果count==3的时候,返回当前dang该值,如果扫描完后,还发现count<3,那么返回排好序数组中的第一个。即可。但是这样的时间复杂度是排序的复杂度0(nlogn),与题目中要求的0(n)不是太符合,我们必须另想办法! 7 | 8 | ### 解题思路(2) 9 | 我们发现不能对数组进行排序,因为排序中最快的算法就是0(nlogn)级别的。题目要求对数量的统计是不包括相同元素的,那么我们可以联系到set集合的特性,我们只要将元素压入到集合中,经过set特性,保证里面是不相同的。 10 | 11 | 由于set是自带排序功能,那么我们可以利用这点,在压入set的过程中,一直保持set中元素个数是小于3的,如果一当大于3了,将s.begin()set中的最小元素去除,那么这样下来我最终保留的三个元素就是我们最大的三个元素,返回s.begin()即可,如果最后元素是小于3的话,我们使用迭代器返回当前最大元素即可~由于每次都是在大于3的时候立即进行剔除,set的排序时间复杂度趋近于0(klogn)满足条件。 12 | 13 | #### 举个例子说明一下: 14 | 假如数组元素为1,2,3,3,5 15 | 第一次压入1,set中元素为1,个数为1小于等于3 16 | 第二次压入2,set中元素为1,2,个数为2小于等于3 17 | 第三次压入3,set中元素为1,2,3,个数为3小于等于3 18 | 第四次压入3,set中元素还是1,2,3,因为set中有3了 19 | 第5次压入5,set中元素为1,2,3,5,个数大于3,那么将s.begin()=1剔除 20 | 剩下2,3,5最终返回s.begin()为2,返回结果 -------------------------------------------------------------------------------- /414.第三大数/solution.cpp: -------------------------------------------------------------------------------- 1 | 2 | class Solution { 3 | public: 4 | int thirdMax(vector& nums) { 5 | set s; 6 | for(int i = 0; i < nums.size(); i++) { 7 | s.insert(nums[i]); 8 | if(s.size() > 3) 9 | s.erase(s.begin()); //剔除最小的 10 | } 11 | 12 | set ::iterator it = s.begin(); 13 | int ans = *it; 14 | if(s.size()<3) 15 | { 16 | 17 | for(it;it!=s.end();it++) //小于3了,就找最大的 18 | { 19 | ans = *it; 20 | } 21 | } 22 | return ans; 23 | } 24 | }; -------------------------------------------------------------------------------- /417- Pacific Atlantic Water Flow/README.md: -------------------------------------------------------------------------------- 1 | ## 题目分析: 2 | 给出一个大小为 `m x n`的矩阵。左边界和上边界外为太平洋,右边界与下边界为大西洋。矩阵内每个单元格的数字表示的是当前格子的高度,每个格子内都有水。 3 | 试求出所有能够流到两个海洋的格子坐标。 4 | **规定:水只能流到高度相同或者更低的格子内** 5 | ### 解题思路(1) 6 | 为了解释方便,现规定左方与上方边界格子称为**一类边界格子**;右方与下方边界格子称为**二类边界格子**。 7 | 水能流到两个海洋的格子,实质上是能以其为起点找到两条路径——一条所经过的格子高度组成一个不上升序列且终点的单元格是一类边界格子、另一条为二类边界格子。 8 | 因此最容易想到的做法就是,从矩阵中的每一个单元格出发,拓展四周高度不大于当前单元格高度的格子。如果最终能拓展到两类边界格子,则把当前单元格加入到答案容器中。 9 | 每个单元格都会作为起始结点进行拓展,拓展一次的时间复杂度为`O(M*N)`,总体时间复杂度为`O(M^2 * N^2)`。 10 | ### 解题思路(2) 11 | 换个角度思考,能否有办法在**数次**的拓展当中就能够把全部能到达两类边界格子的单元格全部找出? 12 | 考虑一开始把所有的一类边界格子染色,并加入到队列之中。依次拓展队列中每个元素,要求拓展的格子未被染色过,同时比高度不小于当前格子,而后将拓展的格子也加入到队列之中。 13 | 同理,用相同的方法处理第二类边界格子。 14 | 该方法至多访问一个单元格两次,因此时间复杂度为`O(M*N)`; 15 | -------------------------------------------------------------------------------- /42-Trapping Rain Water/solution.cpp: -------------------------------------------------------------------------------- 1 | int trap(vector& height) 2 | { 3 | int ans=0; 4 | stack s,id; 5 | int len=height.size(); 6 | for(int i=0;i=s.top())//栈不空且当前海拔不小于栈顶海拔 10 | { 11 | ans=ans+(i-id.top()-1)*(s.top()-pre);//更新答案 12 | pre=s.top();//更新上一次计算的海拔 13 | s.pop(); 14 | id.pop();//栈顶元素弹出 15 | } 16 | if(!s.empty())//退出后如果栈不空还需要再计算一次 17 | { 18 | ans=ans+(i-id.top()-1)*(height[i]-pre); 19 | pre=s.top(); 20 | } 21 | s.push(height[i]); 22 | id.push(i);//压栈 23 | } 24 | return ans; 25 | } 26 | -------------------------------------------------------------------------------- /420-StrongPasswordChecker/README.md: -------------------------------------------------------------------------------- 1 | ## 题目分析 2 | 3 | 给定一个密码,问至少要操作多少次使得其变成强密码。强密码是满足所有下面3点的密码: 4 | * 包含至少6个,至多20个字符 5 | * 至少包含数字,小写字母,大写字母各一个 6 | * 不能包含连续3个以上的相同字母。 7 | 8 | 插入,删除,修改各算一次修改。 9 | 10 | ### 解题思路 11 | 12 | 我们一个一个考虑条件: 13 | * 对于长度的限制,由于修改不能改变长度,因此如果长度小于6,就一定要添加,长度大于20,就一定要删除。 14 | * 对于字符种类的限制,一定不会使用删除;当长度<6的时候,使用添加是更好的,因为这样一下解决了两个问题。当长度>=6时候,使用修改是更好的选择,因为这样不会增加密码长度。 15 | * 对于相同字符的限制,显然不能用添加。当长度>20的时候,使用删除是更好的选择,因为这样一下解决了两个问题。当长度<=20的时候,使用修改是更好的选择,因为这样不会减小字符长度。 16 | 17 | 由上述讨论可以发现,所有条件都与长度有关系,因此我们对长度进行分类讨论。 18 | 19 | 下面我们用n代表密码原始长度,t代表类型总数。显然,我们至少需要3-t次操作才能让类型符合条件。 20 | - __长度n<6__ 如果连续字符长度为5的话,直接返回2就可以了,因为这个时候我们可以通过添加一个,修改一个,增加两个类型。其他情况直接返回max(6-n,3-t)就可以了。 21 | - __长度n>20__ 我们要先删除,把长度变成20。这个时候先删除连续的字母是更好的,因为这样一下解决了两个问题。但是先删除哪些字连续的呢?考虑下,对于一个连续长度为n的,通过修改的话需要n/3步,也就是说对于长度为3*n,3*n+1,3*n+2的都需要n次修改才能不连续。因此我们应当优先把所有的3*n变成3*(n-1)+2,3*n+1变成3*(n-1)+2,这样子又可以省下一次修改的次数。然后我们就可以按照长度满足条件的去修改。 22 | - __长度满足条件__ 这个时候只需要用修改就可以了,把连续长度为n的修改n/3次就可以,如果修改条件不足3-t次的话,补足就可以了。 23 | 24 | 这里虽然分类比较多,但代码写起来还是很清晰的,推荐看代码。 25 | -------------------------------------------------------------------------------- /435-Non-overlappingIntervals/README.md: -------------------------------------------------------------------------------- 1 | ## 题目分析: 2 | 3 | 给出n个区间[L,R],要求删除最少的区间,使得任意两个区间都没有重叠部分。 4 | 5 | ### 解题思路: 6 | 7 | 贪心思想。 8 | 按照每个节点的end从小到大排序,从第一个开始,将当前区间的end和后面一个节点的start比较,如果end <= start那么说明两个区间没有重合,将后面一个区间作为当前点,继续向后比较。如果当前区间的end和后面一个区间的start比较,end > start说明有重合,那么就需要删除后一个区间,使得答案增加1,并继续向后面比较。 9 | 算法复杂度为O(nlogn)。 10 | 11 | 12 | #### 算法正确性: 13 | 14 | 按照这样的规则排序,如果两个区间A和B有重叠,且A排在B之前,那么有两种情况: 15 | 1. 区间B包含区间A,既然存在重复,说明至少也要删除一个区间,之所以删B不删A,是因为B包含A,显然删掉B有较大可能减少更多的重叠部分。 16 | 2. 区间A和B仅重叠一部分,这种情况下,由于B的右端点大于等于A的右端点,说明B对于之后的区间重叠的可能性更大,所以应该删除B。 17 | 综上所述,改贪心策略正确。 18 | 19 | -------------------------------------------------------------------------------- /435-Non-overlappingIntervals/solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | int eraseOverlapIntervals(vector& intervals) { 4 | sort(intervals.begin(), intervals.end(), cmp); 5 | int res = 0, last = 0; 6 | for (int i = 1; i < (int)intervals.size(); i++) { 7 | if (intervals[i].start < intervals[last].end) ++res; 8 | else last = i; 9 | } 10 | return res; 11 | } 12 | 13 | static bool cmp(const Interval& x, const Interval& y) { 14 | return x.end < y.end; 15 | } 16 | }; -------------------------------------------------------------------------------- /440-K-th Smallest in Lexicographical Order/README.md: -------------------------------------------------------------------------------- 1 | ## 题目分析: 2 | 给你n和k,求出在1到n范围内字典序第k小的数,1<=k<=n<=10^9。 3 | 4 | ### 解题思路: 5 | 从高位到低位依次构造答案,构造到某一位时,根据之前的计算结果,从0开始循环计算出字典序不大于当前前缀的字符串数,如果不小于k即可确定这一位就是当前循环的结果,为了计算这个字符串数,还需设置一个flag标记,当flag为1时表示当前位之前的前缀与n的前缀相等,此时带上当前位的前缀如果和n的前缀依旧相等则需要特殊计算字符串数;flag为0时表示时表示当前位之前的前缀小于n的前缀;flag为-1时表示当前位之前的前缀大于n的前缀,此时包含当前前缀的数的位数一定小于n的位数。 6 | 7 | 下面举一个简单例子走一遍算法帮助理解:n=100,k=99。
8 | 先将n分解为[1,0,0],共3位数;
9 | 处理第一位,此时flag=1,k=90,st=1,ed=1;
10 | 令第一位为1,由于和ed相等,因此数为100、1x(x为0-9或空白),共12个,k剩余87;
11 | 令第一位为2,由于大于ed,因此数为2x(x为0-9或空白),共11个,k剩余76;
12 | 令第一位为3,由于大于ed,因此数为3x(x为0-9或空白),共11个,k剩余65;
13 | ……
14 | 令第一位为8,由于大于ed,因此数为8x(x为0-9或空白),共11个,k剩余10;
15 | 令第一位为9,由于大于ed,因此数为9x(x为0-9或空白),共11个,k剩余-1,超过需要的数,因此第一位为9,flag变为-1,k变为10,由于9本身也是一个数,为了下一层计算方便,k变为9;
16 | 处理第二位,此时flag=-1,k=9,st=0,ed=9;
17 | 令第一位为0,由于小于ed,因此数为90,共1个,k剩余8;
18 | 令第一位为1,由于小于ed,因此数为91,共1个,k剩余7;
19 | ……
20 | 令第一位为8,由于小于ed,因此数为98,共1个,k剩余0,达到需要的数,因此第二位为8,flag变为-1,k变为1,由于98本身也是一个数,为了下一层计算方便,k变为0;
21 | 最终结果为98。 22 | -------------------------------------------------------------------------------- /446-ArithmeticSlicesII-Subsequence/README.md: -------------------------------------------------------------------------------- 1 | ## 题目分析: 2 | 3 | 给定一个数组序列,可以按次序从原数组中取出部分数来组成一个等差数列。问:有多少种取法?等差数列的项数至少为3. 4 | 5 | ### 解题思路(1) 6 | 直接枚举搜索的复杂度是`O(n!)`的。 7 | ### 解题思路(2) 8 | 动态规划。我们可以用`dp[i][d]`表示以i结尾, 等差为`d`,数列长度大于`1`的数列的个数。那么我们for循环的时候,对于第i个数,枚举第j个数(`j < i`),其中`d = a[i]-a[j]`,那么我们就会对答案产生`dp[j][d]`的贡献,同时对`dp[i][d]`产生`dp[j][d]+1`的贡献。(为什么+1?因为需要加上`a[i]`和`a[j]`两个数的情况)此外,我们还需要计算等差为0的情况。这个可以通过记录每个数出现的次数,累加到贡献里做到。时间复杂度为`O(n^2)`. 9 | #### 例子: 10 | 举例如下:`[2, 4, 6, 8]` 11 | 初始化`ans = 0` 12 | 对于4,`ans += dp[0][2]`, (`ans = 0`), `dp[1][2] += dp[0][2]+1`, (`dp[1][2] = 1`); 13 | 对于6, `ans += dp[1][2]`, (`ans = 1`), `dp[2][2] += dp[1][2]+1`, (`dp[2][2] = 2`); `ans += dp[0][4]`, (`ans = 1`), `dp[2][4] += dp[0][4]+1`, (`dp[2][4] = 1`) 14 | 对于8, `ans += dp[2][2]`, (`ans = 3`), `dp[3][2] += dp[2][2]+1`, (`dp[3][2] = 3`); `ans += dp[1][4]`, (`ans = 3`), `dp[3][4] += dp[1][4]+1`, (`dp[3][4] = 1`); `ans += dp[0][6]`, (`ans = 3`), `dp[3][6] += dp[0][6]+1`, (`dp[3][6] = 1`) 15 | 故`ans = 3`. -------------------------------------------------------------------------------- /446-ArithmeticSlicesII-Subsequence/solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | int numberOfArithmeticSlices(vector& A) { 4 | if(A.empty()) return 0; 5 | unordered_map ma[A.size()]; 6 | int ans = 0; 7 | for(int i = 0; i < A.size(); i++) 8 | for(int j = i-1; j >= 0; j--){ 9 | if(A[i] == A[j]) continue ; 10 | long long d = (long long)A[i]-A[j]; 11 | if(ma[j].find(d) != ma[j].end()){ 12 | ans += ma[j][d]; 13 | ma[i][d] += ma[j][d]+1; 14 | } 15 | else ma[i][d]++; 16 | } 17 | unordered_map cal; 18 | for(auto x: A) cal[x]++; 19 | for(auto x: cal) { 20 | int n = x.second; 21 | if(n > 2) ans += (1LL< findDisappearedNumbers(vector& nums) { 4 | int len = nums.size(); 5 | for(int i = 0; i < len; i++){ 6 | int tmp = abs(nums[i])-1; 7 | if(tmp > 0){ 8 | if(nums[tmp] > 0){ 9 | nums[tmp] = -nums[tmp]; 10 | } 11 | }else{ 12 | tmp = -tmp; 13 | if(nums[tmp] > 0){ 14 | nums[tmp] = -nums[tmp]; 15 | } 16 | } 17 | } 18 | vector ans; 19 | for(int i = 0; i < len; i++){ 20 | int tmp = nums[i]; 21 | if(tmp > 0){ 22 | ans.push_back(i+1); 23 | } 24 | } 25 | return ans; 26 | } 27 | }; -------------------------------------------------------------------------------- /45-JumpGameII/README.md: -------------------------------------------------------------------------------- 1 | ## 题目分析: 2 | 3 | 给一个非负整数数组,初始位置在数组的首位置。数组中的每个数表示从该位置最多能向前跳几步。问:跳到数组末位置需要的最少步数。 4 | 5 | ### 解题思路(1) 6 | 开一维数组 `step[]` 记录跳到该位置所需要的最小步数。那么对于每个位置i,更新下标在 `i+1` ~ `i+nums[i]` 的 `step` 值。 时间复杂度是`O(n^2)`的。 7 | ### 解题思路(2) 8 | 观察到下标越大,`step` 值会更大,且对于 `i < j`,若 `i` 和 `j` 都能调至某一位置,显然从 `i` 跳不会比从 `j` 跳更差。那么我们记录当前能跳到的最远位置 `last` ,每次更新下标为 `last+1` ~ `i+nums[i]` 的 `step` 值即可。因为 `nums[]` 只扫一遍,`step[]`也只扫了一遍,时间复杂度是 `O(n)` ,空间复杂度为 `O(n)` 。 9 | #### 算法的正确性: 10 | 关键在于从 `last+1` 处开始扫和从 `i+1` 处开始扫会不会遗漏掉某些情况。在(2)中我们已经给出了说明。 11 | 如3, 0, 1, 1, 1。扫描到第一个数时,step[] = {0, 1, 1, 1}, last = 3;扫描到第二,三个数时,因为能跳及的地方在last以内,内循环直接跳过;扫描到第四个数时,step[] = {0, 1, 1, 1, 2},last = 4;故最后结果为2。 12 | -------------------------------------------------------------------------------- /45-JumpGameII/solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | int jump(vector& nums) { 4 | int sz = nums.size(); 5 | vector step(sz, 0); 6 | int last = 0; 7 | for(int i = 0; i < sz; i++){ 8 | for(int j = last+1; j < min(i+nums[i]+1, sz); j++) 9 | step[j] = step[i]+1; 10 | last = max(last, i+nums[i]); 11 | } 12 | return step[sz-1]; 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /45-JumpGameII/solution.py: -------------------------------------------------------------------------------- 1 | class Solution(object): 2 | def jump(self, nums): 3 | """ 4 | :type nums: List[int] 5 | :rtype: int 6 | """ 7 | sz = len(nums) 8 | last, step = 0, [0]*sz 9 | for i in xrange(sz): 10 | for j in xrange(last+1, min(i+nums[i]+1, sz)): 11 | step[j] = step[i]+1 12 | last = max(last, i+nums[i]) 13 | return step[sz-1] 14 | -------------------------------------------------------------------------------- /452-MinimumNumberofArrowstoBurstBalloons/README.md: -------------------------------------------------------------------------------- 1 | ## 题目分析: 2 | 3 | 给出n个区间[L,R],选择最少的点,使得每个区间都至少有一个点(不同区间内含的点可以是同一个)。 4 | 5 | ### 解题思路: 6 | 7 | 贪心思想,将n个区间先按照的右端点从小到大排序,右端点相同的情况下再按照左端点从大到小排序,然后从前往后扫一遍,保存当前选择的点,如果当前搜索到的区间的左端点要小于等于保存的点,那么跳过这个区间,否则重新选择该区间的右端点保存,并且令答案增加1。 8 | 算法复杂度为O(nlogn)。 9 | 10 | 11 | #### 算法正确性: 12 | 13 | 为了方便起见,如果区间i内已经有一个点被取到,我们称区间i被满足。 14 | 1. 首先考虑区间包含的情况,当小区间被满足时大区间一定被满足。所以我们应当优先选取小区间中的点,从而使大区间不用考虑。按照上面的方式排序后,如果出现区间包含的情况,小区间一定在大区间前面。所以此情况下我们会优先选择小区间。则此情况下,贪心策略是正确的。 15 | 2. 对于非包含的情况,说明两个区间有重叠的部分,那么这时选择前一个区间的右端点肯定比选择它之前的点覆盖更大的范围。所以该贪心策略正确。 16 | 17 | -------------------------------------------------------------------------------- /452-MinimumNumberofArrowstoBurstBalloons/solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | int findMinArrowShots(vector>& points) { 4 | int cnt = 0, now = -0x3f3f3f3f; 5 | sort(points.begin(), points.end(), cmp); 6 | for (int i = 0; i < (int)points.size(); i++){ 7 | if (now != -0x3f3f3f3f && points[i].first <= now) continue; 8 | now = points[i].second; 9 | cnt++; 10 | } 11 | return cnt; 12 | } 13 | 14 | static bool cmp(pair& x, pair& y){ 15 | return x.second == y.second ? x.first > y.first : x.second < y.second; 16 | } 17 | }; -------------------------------------------------------------------------------- /466-CountTheRepetitions/solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | int repeat[105], cnt[105]; 4 | int getMaxRepetitions(string s1, int n1, string s2, int n2) { 5 | memset(repeat, 0, sizeof(repeat)); 6 | memset(cnt, 0, sizeof(cnt)); 7 | for (int t = 1, j = 0; t <= n1; ++t) { 8 | cnt[t] = cnt[t - 1]; 9 | for (int i = 0; i < s1.size(); ++i) { 10 | if (s1[i] == s2[j]) { 11 | j = (j + 1) % s2.size(); 12 | if (j == 0) ++cnt[t]; 13 | } 14 | } 15 | for (int st = 0; st < t; ++st) { 16 | if (repeat[st] == j) { 17 | int res = cnt[st] + cnt[st + (n1 - st) % (t - st)] - cnt[st]; 18 | res += (cnt[t] - cnt[st]) * (n1 - st) / (t - st); 19 | return res / n2; 20 | } 21 | } 22 | repeat[t] = j; 23 | } 24 | return cnt[n1] / n2; 25 | } 26 | }; -------------------------------------------------------------------------------- /472-ConcatenatedWords/README.md: -------------------------------------------------------------------------------- 1 | ## 题目分析: 2 | 3 | 给出一系列单词,找到所有的合成词并且输出(不用考虑顺序),合成词就是由至少两个所给出的单词连接而成的词。 4 | 5 | ### 解题思路: 6 | 7 | 很显然合成词一定是由比自己短的词组合而成的,所以要先对单词按照长度从小到大排序,对于每个单词单独处理,我们设`dp[i]`表示该单词`[0,i]`这一段是否是合成词,很显然如果`dp[j]`是合成词,而`[j+1,i]`这一段也是出现过的单词,那么`dp[i]`也是合成词。 8 | 所以动态转移方程是: `dp[i]=dp[i]||(dp[j]&&(st.count(subtr[j + 1, i])>0))`,其中`j`是从`0`到`i-1`。其中st是C++STL的set容器,用来保存已经出现过的单词,这样可以在`O(logn)`时间查询到子串,`(st.count(subtr[j + 1, i])>0`表示`[j+1,i]`这一子串的在st集合中数目大于0。 9 | 算法复杂度是`O(n*m*logn)` 10 | 11 | #### 算法正确性: 12 | 13 | 正确性显然,但是要注意对于每个位置i,在枚举j的时候只要能判断`dp[i]=true`就要break,否则会超时。 14 | 15 | -------------------------------------------------------------------------------- /474 Ones and Zeroes/README.md: -------------------------------------------------------------------------------- 1 | ###题目分析 2 | 这道题的意思是给了m个1和n个0,还有一堆01构成的字符串,问能用这m个1和n个0能完整组成那堆字符串里的几个字符串。 3 | 4 | ####解题思路 5 | 可以转化为动态规划中的0-1背包问题,`dp[i][j][k]`代表的是前i个字符串1的个数为j0的个数为k的最多字符串个数。 6 | 7 | 那么按照0-1背包的思想就是对第i个字符串,我可以采取取也可以采取不取的做法,那么转移方程为`dp[i][j][k] = max(dp[i-1][j][k],dp[i-1][j-ones][k-zeros])+1`,又因为i只与i-1有关,那么可以变为二维数组`dp[i][j] = max(dp[i][j], dp[i – a][j – b] + 1).dp[m][n]`就是结果。 8 | 9 | 注意遍历的时候是从最右底部开始遍历,这样用到的信息就是原来的,不是现在覆盖的了. -------------------------------------------------------------------------------- /480-SlidingWindowMedian/README.md: -------------------------------------------------------------------------------- 1 | ## 题目分析: 2 | 3 | 给定一个数组nums,有一个长度为k的窗口从最左端往最右端滑。问:在滑动过程中出现的所有中位数。如果k为偶数,则中位数为居中两数的平均值。 4 | 5 | ### 解题思路(1) 6 | 暴力排序`(i, i+k)`区间的数,取其中位数。时间复杂度为`O((n-k)*klogk)`。很遗憾,会超时。 7 | ### 解题思路(2) 8 | 我们需要一个支持插入与删除,并快速求中位数的数据结构。考虑`multiset`。`multiset`不能快速求取中位数,但我们如果构造两个`multiset`,一个保存前一半的数,另一个保存后一半的数,我们就能轻易求得中位数。期间插入删除时,维护好两个`multiset`。 9 | ### 解题思路(3) 10 | 延续思路2。但我们只用一个`multiset`和一个迭代器指针。迭代器指针指向中位数。插入与删除时,适当调整迭代器指针的指向即可。为了防止迭代器失效,可以在erase前检查迭代器指的元素是否是被erase的元素,如果是,先调整指针后再erase(直接erase迭代器将造成迭代器失效)。 11 | #### 算法的正确性: 12 | 以思路2为例。`[1 3 -1 -3 5 3 6 7]`, `k = 3`。那么一开始两个multiset为`{-1}, {1, 3}`, 中位数为`1`; `{-3}, {-1, 3}`, 中位数为`-1`; `{-3}, {-1, 5}`, 中位数为`-1`; `{-3}, {3, 5}`, 中位数为`3`; `{3}, {5, 6}`, 中位数为`5`; `{3}, {6, 7}`, 中位数为`6`。 故答案为 `[1,-1,-1,3,5,6]`。 13 | -------------------------------------------------------------------------------- /483-SmallestGoodBase/README.md: -------------------------------------------------------------------------------- 1 | ## 题目分析: 2 | 3 | 给出一个数字n,要求找到一个最小的数字k(k>=2),使得n在k进制下各个数位上都是1。 4 | 5 | ### 解题思路: 6 | 7 | 看到题目给出的数字n的范围是[3, 1e18],这样可以保证在二进制情况下,数位不会超过64,其他进制的数位长度一定比二进制更小。这样的进制长度我们可以枚举,对于每个进制长度的情况下,必然只有一个进制k可以满足使得所有数位上的长度都是1。 8 | 设当前枚举的数位长度是len,那么数位长度是len且各个数位上是1的数: 9 | f[k] = k^0 + k^1 + k^2 + ... + k^(len - 1) 10 | 很显然这是一个等比序列,化成求和公式为: 11 | f[k] = (k^len - 1) / (k - 1) 12 | 这样看来,随着k的增大,tmp也会增大,这样我们就可以对k进行二分,直到求出一个满足f[k] == n的为止,那么长度是len的情况下的结果就求出来了,由于枚举len,所以最后只要计算完所有长度下的答案,保存最小值即可。 13 | 14 | #### 算法正确性: 15 | 16 | 因为f[k,len]是在len一定的情况下是随着k单调递增的,可以保证每个数位长度len下如果有结果,一定只有一个。因为单调性所以也可以满足二分搜索的条件,故算法正确。每次枚举之后二分,算法复杂度为O(64*logn),可以满足题目要求。另外要注意的一点是,这里n非常大,在等比数列求和的时候不能利用求和公式,一定会溢出。而且在求和之前应该先判断判断k^(y-1) < n是否成立,因为数量巨大所以化成对数处理,判断log(n)/log(k) < y-1是否成立,如果不成立可直接推出。 17 | 18 | -------------------------------------------------------------------------------- /485. Max Consecutive Ones/README.md: -------------------------------------------------------------------------------- 1 | # 题目分析 2 | 3 | 0 和 1 组成的数组,找出其中连续出现次数最多的 1 的长度。 4 | 5 | ## 解题思路 6 | 7 | 遍历数组,用一个变量 tmp 代表连续 1 的长度,每次加上当前位置的值,如果值没有改变说明此位置为 0,这时更新 ans,tmp 置为 0. 8 | -------------------------------------------------------------------------------- /485. Max Consecutive Ones/solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | int findMaxConsecutiveOnes(vector& nums) { 4 | int tmp = 0; 5 | int ans = 0; 6 | for(int i = 0; i < nums.size(); i++){ 7 | int old = tmp; 8 | tmp += nums[i]; 9 | ans = max(ans, tmp); 10 | if(tmp == old){ 11 | tmp = 0; 12 | } 13 | } 14 | return ans; 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /486-PredictTheWinner/solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | bool PredictTheWinner(vector& nums) { 4 | int dp[nums.size()][nums.size()]; 5 | for (int l=1; l<=nums.size(); l++) 6 | for (int i=0; i+l-1= 0; 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /488-Zuma Game/README.md: -------------------------------------------------------------------------------- 1 | ## 题目分析: 2 | 关于祖玛游戏,有一串珠子,可以向其发射新珠子,如果发射后有至少三个同色珠子相连那么这些珠子会消去,消去后形成的新串如果还有至少三个同色珠子相连那么这些珠子还会消去,现在有一串待消去的珠子,你手上有若干珠子,问至少发射几个珠子才能消去整串珠子,如果全部发射都无法消去整串珠子返回-1,一串珠子个数不超过20,手上的珠子个数不超过5,珠子颜色限定为red(R)、yellow(Y)、blue(B)、green(G)、white(W)五种。 3 | 4 | ### 解题思路: 5 | DFS。在每一层选择手上还有的一个珠子,找到串中每个位置发射,如果有消去则进行消去,形成的新串和剩余珠子进行下一层搜索。这里有两个剪枝策略,一个就是当前搜索层数如果达到当前答案的时候直接返回,还有一个就是发射的位置周围必须有同色珠子。 6 | 7 | #### 算法正确性: 8 | 算法的关键点在于发射的时候周围必须有同色珠子这一剪枝策略。可以这么考虑,如果发射的位置周围没有同色珠子,那就说明发射后形成了单个的珠子,到后面可能还要发射相同颜色的珠子对其进行消去,即使是为了形成连击,那也可以在之前把这个珠子直接发射到连击消去的同色珠子周围,这样所用的步数是一样的,不会产生更优解,因此上述算法是正确的。 9 | 10 | 下面举一个简单例子走一遍算法帮助理解:串为”WRRBBW”,手上的珠子为”RB”。
11 | - 选取珠子R,在第0、1个字符间发射,形成串”WRRRBBW”,消去后变为”WBBW”,进入下一层;
12 | - 选取珠子B,在第0、1个字符间发射,形成串”WBBBW”,消去后变为”WW”,进入下一层;
13 | - 由于没有珠子可选,返回;
14 | - 可选的珠子已选完,返回;
15 | - 选取珠子B,在第2、3个字符间发射,形成串”WRRBBBW”,消去后变为”WRRW”,进入下一层;
16 | - 选取珠子R,在第0、1个字符间发射,形成串”WRRRW”,消去后变为”WW”,进入下一层;
17 | - 由于没有珠子可选,返回;
18 | - 可选的珠子已选完,返回;
19 | - 可选的珠子已选完,返回;
20 | 最终结果为-1。 21 | 22 | -------------------------------------------------------------------------------- /491. Increasing Subsequences/README.MD: -------------------------------------------------------------------------------- 1 | ## 题目分析: 2 | 题目给出一个长度不大于15的序列,要求找出所有**不重复**的不下降子序列集合。 3 | + 不下降子序列的长度至少为2; 4 | + 序列中每个数字的数值大小范围为[-100,100]。 5 | ### 解题思路 6 | 由于题目中序列长度最大为15,这便意味着最终的答案集合中序列的个数不大于`2^15`个。 7 | 常规的寻找序列当中不下降子序列的方法自然不需要赘述,而问题在于如何保证不出现**重复**的子序列? 8 | 首先,题目若给出一个`[4, *, *, *, 4, *, *]`的序列,易证后面的那个4所能组成的不下降子序列的集合必然是前头的那个4的**真子集**,因而每次有多个相同元素可供选择时,选最前头的必然把之后的情况全部**囊括**。 9 | 因此构思出一种算法: 10 | 1. 初始化一个空集合; 11 | 2. 若当前集合为空,则从前往后,选取一个未被选取过的数字加入到集合当中,并使用HashTable标记其已经选取过; 若当前不为空,则从前往后选取一个不小于集合尾部的数字且未被HashTable标记过的数字;若找不到则结束。 12 | 3. 检查当前集合内的元素个数,若个数大于等于2则加入到答案集合; 13 | 4. 存储当前HashTable(可使用不同层次的迭代实现),后将其清空。若从当前位置往后依旧能拓展序列,则回到2步;否则恢复HashTable,并且将最后加入当前集合的数字弹出,回到第2步; 14 | 15 | 最后返回答案序列即可。 16 | -------------------------------------------------------------------------------- /491. Increasing Subsequences/Solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | vector> findSubsequences(vector& nums) { 4 | vector> ans(0, vector(0,0)); 5 | vector cur(0,0); 6 | dfs(-1, ans, cur, nums); 7 | return ans; 8 | } 9 | void dfs(int no, vector> &ans, vector &cur, vector& nums) 10 | { 11 | //使用M记录每个与当前串组合过的数字 12 | unordered_map M; 13 | int x = (no<0)? -111:nums[no]; 14 | for(int i=no+1;i=x&&M.find(nums[i]) == M.end()) 17 | { 18 | M[nums[i]] = true; 19 | cur.push_back(nums[i]); 20 | //如果当前序列的长度大于等于2则加入到答案之中 21 | if(cur.size()>=2) ans.push_back(cur); 22 | dfs(i,ans,cur,nums); 23 | //回溯 24 | cur.pop_back(); 25 | } 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /493-ReversePairs/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leetcode-Tutorial/Tutorial/ef9f3bdaa3d3fcf78f5f266549cad3ee3145844f/493-ReversePairs/README.md -------------------------------------------------------------------------------- /493-ReversePairs/solution0.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | int mergeSort(vector& nums, int l, int r){ 4 | if(l >= r) return 0; 5 | int m = (l+r) >> 1; 6 | int ans = mergeSort(nums, l, m) + mergeSort(nums, m+1, r); 7 | for(int i = l, j = m+1; i <= m; i++){ 8 | while(j <= r && nums[i] > nums[j]*2LL) j++; 9 | ans += j-1-m; 10 | } 11 | sort(nums.begin()+l, nums.begin()+r+1); 12 | return ans; 13 | } 14 | int reversePairs(vector& nums) { 15 | return mergeSort(nums, 0, nums.size()-1); 16 | } 17 | }; 18 | -------------------------------------------------------------------------------- /494-TargetSum/README.md: -------------------------------------------------------------------------------- 1 | ## 题目分析: 2 | 3 | 给出一个数组nums,要求在nums中的每个数nums[i]添加正负号,使得nums的总和等于s,问一共有多少种不同的方法 4 | 5 | ### 解题思路: 6 | 7 | 初看可能没思路,可以推导一下关系: 8 | 当一次分配符号完成后,设P是其中的正数集合,N是其中的负数绝对值集合,则有sum(P) - sum(N) = s 9 | 等式两边同时加上sum(P) + sum(N),得到2 * sum(P) = s + sum(P) + sum(N) 10 | 而sum(P) + sum(N)就是所有数绝对值的总和Sum,所以得到关系2 * sum(P) = s + Sum 11 | 这也就是说,每一种分配符号的方式都对应着一种从中选择一个若干数使得总和等于(s+Sum)/2的方式,那么我们可以直接计算从nums中选择若干数求和等于(s+Sum)/2的方案数。 12 | 转化而成的这个问题就是一个裸的01背包问题,采用动态规划求解即可。 13 | 14 | #### 算法正确性: 15 | 16 | 推导过程没有问题,转化合理,而转化后的问题也可以利用动态规划求解,可以保证算法正确性。 17 | 要注意的是特殊情况的判断,如果s < Sum那么显然不可能存在方法,根据上述推导的结果,如果s+Sum是一个奇数,也不能找到解。 18 | 采用动态规划求解01背包问题,复杂度为O(n * (s+Sum)),其中n为nums的个数。 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /494-TargetSum/solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | int dp[1005]; 4 | 5 | int findTargetSumWays(vector& nums, int s) { 6 | int n = nums.size(), sum = 0; 7 | for (int i = 0; i < n; i++) sum += nums[i]; 8 | if (sum < s || (s + sum) % 2) return 0; 9 | memset(dp, 0, sizeof(dp)); 10 | dp[0] = 1; 11 | for (int i = 0; i < n; i++) { 12 | for (int j = (s + sum) / 2; j >= nums[i]; j--) { 13 | dp[j] += dp[j - nums[i]]; 14 | } 15 | } 16 | return dp[(s + sum) / 2]; 17 | } 18 | }; -------------------------------------------------------------------------------- /495-TeemoAttacking/README.md: -------------------------------------------------------------------------------- 1 | ## 题目分析: 2 | 3 | A可以对B进行若干次攻击,每次攻击会让B进入一段时间的中毒状态,中毒的重复时间不叠加。给出A一串攻击时刻的序列(保证递增),问最后B一共会中毒多长时间。 4 | 5 | ### 解题思路: 6 | 7 | 模拟题,模拟攻击的过程。 8 | 假设上一次中毒的终止时间是T,下一次攻击发生在t,每次攻击持续x秒中毒状态,总中毒时间是ans。 9 | 首先让ans加上中毒时间x,但是这x可能会存在重复,比如这次的攻击时间t < 上一次终止时间T,那么T-t这一段时间在上次攻击的时候就已经被算过一次了,需要从ans中减掉。 10 | 11 | #### 算法正确性: 12 | 13 | 算法模拟攻击过程,排除了所有的重复时间,正确性显然。时间复杂度为O(n)。 14 | 15 | 16 | -------------------------------------------------------------------------------- /495-TeemoAttacking/solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | int findPoisonedDuration(vector& ts, int duration) { 4 | int ans = 0, now = 0; 5 | for(int i = 0; i < (int)ts.size(); i++){ 6 | ans += duration; 7 | if (ts[i] < now) ans -= now - ts[i]; 8 | now = ts[i] + duration; 9 | } 10 | return ans; 11 | } 12 | 13 | }; -------------------------------------------------------------------------------- /5-longestPalindrome/README.md: -------------------------------------------------------------------------------- 1 | ## 题意分析 2 | 3 | 求一个串的最长回文子串。 4 | 5 | ## 题解思路 6 | 7 | ### 解法一: 8 | 9 | 约定[i...j]表示下标i到j的子串。那么最显然的就是`O(n^2)`枚举i,j,判断[i...j]是否是回文串同时记录下最大长度的答案。判断[i...j]是否是回文串需要从头尾往中间走遍历一次该串,复杂度`O(n)`。故总时间复杂度是`O(n^3)`。 10 | 11 | ### 解法二: 12 | 13 | 因为每一个回文串都有回文中心,所以可以通过枚举回文中心来确定回文串。回文中心可能是任何一个下标的字符或者是任意两个相邻字符的中间,所以最多有`O(2*n)=O(n)`个回文中心,当回文中心确定以后,就可以直接从中心往两边扩展,直至两边的字符不相等即可,扩展过程最多遍历全部字符,故扩展时间复杂度是`O(n)`,总时间复杂度是`O(n^2)`。 14 | 15 | ### 解法三: 16 | 17 | 求解最长回文子串可以使用**manachar算法**,时间复杂度是O(1)。 18 | 19 | 20 | -------------------------------------------------------------------------------- /502-IPO/README.md: -------------------------------------------------------------------------------- 1 | ## 题目分析: 2 | 有若干项目,每个项目有一个完成所获得的收益和启动的资产要求,初始时有资产W,求完成不超过k个项目的情况下的最终资产的最大值。 3 | 4 | ### 解题思路: 5 | 先将项目按启动资产要求从小到大排序,然后用优先队列求解,每次求解把启动资产要求不超过当前资产的项目放入优先队列中,然后若队列空则直接结束求解,否则取出一个收益最大的项目,更新答案,当取了k个项目的时候,上述求解过程结束。 6 | 7 | 下面举一个简单例子走一遍算法帮助理解:k=2,W=0,收益为[1,2,3],资产要求为[0,1,1]。
8 | 对项目进行排序,排序结果为[{1,0},{2,1},{3,1}](数组元素表示为{收益,资产要求});
9 | 当前已有资产设为0;
10 | 第一次求解:
11 | 项目{1,0}资产要求为0,不大于当前已有资产0,放入优先队列;
12 | 项目{2,1}资产要求为1,大于当前已有资产0,不放入优先队列;
13 | 从游戏队列中取出收益最大的项目{1,0},更新当前收益为1;
14 | 第二次求解:
15 | 项目{2,1}资产要求为1,不大于当前已有资产1,放入优先队列;
16 | 项目{3,1}资产要求为1,不大于当前已有资产1,放入优先队列;
17 | 此时所有项目都已放入优先队列;
18 | 从游戏队列中取出收益最大的项目{3,1},更新当前收益为4;
19 | 最终答案为4。 20 | -------------------------------------------------------------------------------- /502-IPO/solution.cpp: -------------------------------------------------------------------------------- 1 | struct project 2 | { 3 | int profit; 4 | int capital; 5 | bool operator < (const project &a) const {return profit& Profits, vector& Capital) 12 | { 13 | int n=Profits.size(); 14 | project p[n]; 15 | for(int i=0;i q; 22 | int f=0,cnt=0; 23 | int ans=W; 24 | while(cntq; 8 | 9 | q.push(root); 10 | TreeNode* temp; 11 | while(!q.empty()) 12 | { 13 | temp = q.front(); 14 | result = temp->val; 15 | int size = q.size(); 16 | while(size-->0) //一次性处理一层的值,这个函数就是将一层的节点都处理完了,一层的个数为size 17 | { 18 | temp = q.front(); 19 | q.pop(); 20 | if(temp->left) 21 | q.push(temp->left); 22 | if(temp->right) 23 | q.push(temp->right); 24 | } 25 | } 26 | return result; 27 | } 28 | }; -------------------------------------------------------------------------------- /513-Find Bottom Left Tree Value/513解法一.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for a binary tree node. 3 | * struct TreeNode { 4 | * int val; 5 | * TreeNode *left; 6 | * TreeNode *right; 7 | * TreeNode(int x) : val(x), left(NULL), right(NULL) {} 8 | * }; 9 | */ 10 | class Solution { 11 | public: 12 | int findBottomLeftValue(TreeNode* root) { 13 | level = -1; 14 | inorderTraversal(root, 0); 15 | return val; 16 | } 17 | void inorderTraversal(TreeNode* root, int cur_level){ 18 | if(root->left){ 19 | inorderTraversal(root->left, cur_level+1); 20 | } 21 | if(cur_level>level){ 22 | level = cur_level; 23 | val = root->val; 24 | } 25 | if(root->right){ 26 | inorderTraversal(root->right,cur_level+1); 27 | } 28 | } 29 | private: 30 | int level; 31 | int val; 32 | }; -------------------------------------------------------------------------------- /513-Find Bottom Left Tree Value/solution for 513.md: -------------------------------------------------------------------------------- 1 | ## 题目分析: 2 | 3 | 题目让我们求二叉树的最左下树结点的值,也就是最后一行左数第一个值。 4 | 5 | ### 解题思路(1) 6 | 树的题目我们很容易想到递归,或者遍历来求,因为基于树的一些问法,我们都可以变换到遍历树然后到达那个结点再操作。那么我首先想的是用中序遍历来做,我们维护一个最大深度和该深度的结点值,由于中序遍历遍历的顺序是左-根-右,所以每一行最左边的结点肯定最先遍历到,那么由于是新一行,那么当前深度肯定比之前的最大深度大,所以我们可以更新最大深度为当前深度,结点值result为当前结点值(深度比当前记录的深度大的时候,我们才进行更新,那么更新完的result就有可能是最终求的结果),这样在遍历到该行其他结点时就不会更新结果result了。那么最后的结果result就是我们要求的结果。 7 | 8 | ### 解题思路(2): 9 | 这道题用层序遍历更直接一些,因为层序遍历时遍历完当前行所有结点之后才去下一行,那么我们再遍历每行第一个结点时更新结果result即可,根本不用维护最大深度了,参见代码参见solution513.cpp 10 | 11 | 12 | ### *下面举一个简单例子走一遍算法帮助理解:* 13 | 给出下面例子: 14 | 1 15 | /\ 16 | 2 3 17 | / 18 | 4 19 | (1)首先我们可以判断root=*1,不为空(我用*1代表指向1结点的指针,用1代表这个结点的值) 20 | 那么1进入队列 21 | (2)进入while(!q.empty())循环,首先赋值result=1,并得到size=1(size就是每一层的个数) 22 | (3)然后进入while(--size>0),这相当于对每一层的节点进行遍历操作.1结点出队列,然后判断1结点的左孩子与右孩子。不为空进入队列 23 | (4)size此时为0,跳出来,因为这一层就1一个结点,然后继续取出2赋值给result.这相当于进行第二层最左边的结点孩子的值给了result,依次进行下去,最终得到的结果是7 -------------------------------------------------------------------------------- /514-Freedom Trail/solution.cpp: -------------------------------------------------------------------------------- 1 | int findRotateSteps(string ring, string key) 2 | { 3 | int lenr=ring.size(),lenk=key.size(); 4 | int dp[lenk+1][lenr]; 5 | memset(dp,0x7f,sizeof(dp));//将数组所有值初始化为无穷大 6 | dp[0][0]=0;//初始化边界 7 | for(int i=1;i<=lenk;i++) 8 | { 9 | for(int j=0;j largestValues(TreeNode* root) { 5 | vectorresult; 6 | if(root == NULL) //如果树为空,就返回空vector.做任何题目的时候,都应该考虑特殊情况 7 | return result; 8 | queueq; 9 | q.push(root); 10 | TreeNode* temp; 11 | 12 | while(!q.empty()) 13 | { 14 | int size = q.size(); 15 | int maxn = INT_MIN; //来记录每一层最后的结果 16 | 17 | //一次性取出一层的个数节点来处理 18 | while(size-->0) 19 | { 20 | temp = q.front(); 21 | q.pop(); 22 | maxn = max(maxn,temp->val); //处理了当前节点了 23 | 24 | if(temp->left!=NULL) 25 | q.push(temp->left); 26 | if(temp->right!=NULL) 27 | q.push(temp->right); 28 | } 29 | result.push_back(maxn); 30 | } 31 | return result; 32 | } 33 | }; -------------------------------------------------------------------------------- /515-Find Largest Value in Each Tree Row/solution for 515.md: -------------------------------------------------------------------------------- 1 | ## 题目分析: 2 | 3 | 题目要求我们求得求出每一层的最大值,最后保存在vector中。最终返回结果。 4 | 5 | ### 解题思路 6 | 这道题与513非常相似的解题思路。对于树来说,如果是涉及到每一层的题目时,一般采取层次遍历,队列来维护。这样我们可以很容易的对每一层结点进行处理。那么这道题的核心是,我判断什么时候进入新的一层了,然后用一个变量来保存这一层的最大值,然后每一层得到一个最大值,压入vector,层数遍历完成之后,我们也就得到了result这个最终结果。 7 | 8 | 9 | ### *下面举一个简单例子走一遍算法帮助理解:* 10 | 1 11 | /\ 12 | 2 3 13 | / 14 | 4 15 | (1)首先我们可以判断root=point_1,不为空(我用point_1代表指向1结点的指针,用1代表这个结点的值) 16 | 那么1进入队列 17 | (2)进入while(!q.empty())循环,首先赋值maxn=INT-MIN,并得到size=1(size就是每一层的个数) 18 | (3)然后进入while(--size>0),这相当于对每一层的节点进行遍历操作.1结点出队列,然后判断1结点的左孩子与右孩子。不为空进入队列,这个while中,一直进行着maxn=max(maxn,temp->val),也就是遍历一遍得到最大值,压入result中。 19 | (4)size此时为0,跳出来,因为这一层就1一个结点,然后2,3进入队列,得到最大值,然后2,3结点分别出队列,然后4进入队列,得到第三层最大值,最终返回[1,3,4] -------------------------------------------------------------------------------- /516-LongestPalindromicSubsequence/solution1.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | int longestPalindromeSubseq(string s) { 4 | vector> memo(s.size(), vector(s.size(), 0)); 5 | return longestPalindromeSubseq(s, 0, s.size()-1, memo); 6 | } 7 | int longestPalindromeSubseq(string &s, int left, int right, vector> &memo){ 8 | if(left == right) return 1; 9 | if(left > right) return 0; 10 | if(memo[left][right]) return memo[left][right]; 11 | return s[left] == s[right] ? 2 + longestPalindromeSubseq(s,left+1,right-1,memo) : memo[left][right]=max(longestPalindromeSubseq(s,left,right-1,memo),longestPalindromeSubseq(s,left+1,right,memo)); 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /516-LongestPalindromicSubsequence/solution2.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | int longestPalindromeSubseq(string s) { 4 | int dp[1000][1000]; 5 | memset(dp,0,sizeof(dp)); 6 | for(int i=s.size()-1; i>=0; i--){ 7 | dp[i][i]=1; 8 | for(int j=i+1; ja`,负值就是`a->b`。这样对每个洗衣机从左往右扫一遍,依次求出每个洗衣机给了左边洗衣机一定的衣服,让其达到`avg`之后自己还剩下的衣服数。 8 | 这样,统计每个洗衣机所需要的操作数,互不影响的操作可以同时进行,所以这么多操作中的最大值就是答案。但是要注意两个相邻的洗衣机的操作数如果左边正值`x`,右边负值`y`,说明右边洗衣机要先给左边的洗衣机`x`件衣服,而且还要给它自身右边的洗衣机`y`件衣服,这两个是不能同时操作的,所以需要用两个操作数绝对值的和更新最大值。 9 | 以上算法只需要遍历两遍n个洗衣机,算法复杂度`O(n)`。 10 | 11 | #### 算法正确性: 12 | 算法的关键在于要保证任意两个洗衣机之间所传递的是单向进行的,这样才能保证最少的操作数,因为如果a传给b一部分x,然后b又传给a一部分y,那直接a传给b一共x-y个这样所用的操作数更少。而不会影响的操作都可以同时进行,会相互影响的操作只有同一台洗衣机既向左边传递,又向右边传递,这时候不能同时进行,需要分开考虑。如此一来可以保证操作数最少。 -------------------------------------------------------------------------------- /517-SuperWashingMachines/solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | int cnt[10005]; 4 | int findMinMoves(vector& machines) { 5 | int n = machines.size(); 6 | long long sum = 0; 7 | for (int i = 0; i < n; i++) 8 | sum += machines[i]; 9 | if (sum % n != 0) return -1; 10 | int avg = sum / n; 11 | for (int i = 0; i < n - 1; i++) { 12 | machines[i + 1] -= avg - machines[i]; 13 | } 14 | int ans = 0; 15 | for (int i = 0; i < n - 1; i++) { 16 | int x = avg - machines[i], y = avg - machines[i + 1]; 17 | if (x > 0 && y < 0) { 18 | ans = max(ans, abs(x) + abs(y)); 19 | ++i; 20 | } 21 | else ans = max(ans, abs(x)); 22 | } 23 | return ans; 24 | } 25 | }; -------------------------------------------------------------------------------- /52-N-Queens II/README.MD: -------------------------------------------------------------------------------- 1 | ## 题目分析: 2 | 经典的`NPC`问题——N皇后。给出一个数字`N`,要求在`N*N`大小的棋盘之中摆放`N`个皇后,并要求皇后之前彼此不能攻击到。仅需算出合法的棋盘布局数目即可。 3 | ### 解题思路: 4 | 与`#51`类似,但已不需输出每种合法的棋盘布局的具体摆放,仅需计数即可。 5 | 参照`#51`的解法二,使用与主对角线平行的直线的标记、与次对角线平行的直线的标记以及列的标记来判断当前行的棋子能够落在哪一个位置。得到一种合法的摆放情况,将计数值加一即可,整体时间复杂度不变。 6 | -------------------------------------------------------------------------------- /520.Detect Capital/README.md: -------------------------------------------------------------------------------- 1 | ### 题目分析 2 | 判断一个字母是否大小写正确:要么全是大写,要么全是小写,或者首字母大写其他小写,否则不满足题意 3 | 4 | ### 解题思路 5 | 这个题目很简单,其实用什么方法都能得到这个逻辑,只是说如何更加清楚的得到整个问题的思路。 6 | 我们可以这样做,统计整个字符串的长度,然后统计整个字符串大写和小写的个数分别是多少,如果任何一个等于长度len,那么就是合乎规则的。 7 | 如果不是,判断是否大写的个数为1个,然后其它的均为小写的情况,如果满足,那么也是可以的。 8 | 这样的话,就能得到最后的结果。详情见代码。 9 | 10 | -------------------------------------------------------------------------------- /520.Detect Capital/solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | bool detectCapitalUse(string word) { 4 | bool flag = false; 5 | int count_upper = 0; 6 | for(int i = 0;i= 'A' && word[i] <= 'Z') 9 | { 10 | count_upper++; 11 | } 12 | } 13 | if(count_upper == word.length() || count_upper == 0|| (count_upper == 1 && (word[0] >= 'A'&&word[0] <= 'Z'))) 14 | flag = true; 15 | return flag; 16 | } 17 | }; -------------------------------------------------------------------------------- /520.Detect Capital的副本/README.md: -------------------------------------------------------------------------------- 1 | ### 题目分析 2 | 判断一个字母是否大小写正确:要么全是大写,要么全是小写,或者首字母大写其他小写,否则不满足题意 3 | 4 | ### 解题思路 5 | 这个题目很简单,其实用什么方法都能得到这个逻辑,只是说如何更加清楚的得到整个问题的思路。 6 | 我们可以这样做,统计整个字符串的长度,然后统计整个字符串大写和小写的个数分别是多少,如果任何一个等于长度len,那么就是合乎规则的。 7 | 如果不是,判断是否大写的个数为1个,然后其它的均为小写的情况,如果满足,那么也是可以的。 8 | 这样的话,就能得到最后的结果。详情见代码。 9 | 10 | -------------------------------------------------------------------------------- /520.Detect Capital的副本/solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | bool detectCapitalUse(string word) { 4 | bool flag = false; 5 | int count_upper = 0; 6 | for(int i = 0;i= 'A' && word[i] <= 'Z') 9 | { 10 | count_upper++; 11 | } 12 | } 13 | if(count_upper == word.length() || count_upper == 0|| (count_upper == 1 && (word[0] >= 'A'&&word[0] <= 'Z'))) 14 | flag = true; 15 | return flag; 16 | } 17 | }; -------------------------------------------------------------------------------- /521. Longest Uncommon Subsequence I/README.md: -------------------------------------------------------------------------------- 1 | ### 题目分析 2 | 给定俩个字符串,然后返回最长的不同前缀字符串。 3 | 4 | ### 解题思路 5 | 题目很简单,换一个思路思考后,可以转换规则如下: 6 | 比较两个字符串的长度,若不相等,则返回长度的较大值,若相等则再判断两个字符串是否相同,若相同则返回-1,否则返回长度。 -------------------------------------------------------------------------------- /521. Longest Uncommon Subsequence I/solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | int findLUSlength(string a, string b) { 4 | int len_a = a.length(),len_b = b.length(); 5 | if(len_a != len_b) 6 | { 7 | return max(len_a,len_b); 8 | } 9 | else 10 | { 11 | if(a==b) //如果相等,说明没有满足要求的 12 | return -1; 13 | else 14 | return len_a; 15 | } 16 | } 17 | }; -------------------------------------------------------------------------------- /523-ContinuousSubarraySum/solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | bool checkSubarraySum(vector& nums, int k) { 4 | unordered_map modSum; 5 | modSum[0]=-1; 6 | int prefixModSum=0; 7 | for(int i=0; isecond > 1) return true; 13 | }else{ 14 | modSum[prefixModSum]=i; 15 | } 16 | } 17 | return false; 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /525- Contiguous Array/README.md: -------------------------------------------------------------------------------- 1 | ## 题目分析: 2 | 给定一个连续的0、1序列,要求找出满足0和1数目相同的子序列的最长长度。 3 | ### 解题思路(1) 4 | 使用两个指针i与j,扫描该序列。 5 | 统计i与j指针所指出的区间内部0与1的值是否相同,若相同则查看是否能更新答案。 6 | 整体的时间复杂度为`O(N^2)`。 7 | ### 解题思路(2) 8 | 考虑[0,i]区间内0比1多出x个,[0,j]区间内0比1也多出x个`(j>i)`,则[i+1,j]区间内0与1的数目必然相等。 9 | 则目前的问题变化为,针对目前的[0,j]区间,如何去找出符合条件的[0,i]区间,并且在多个满足条件的[0,i]区间当中该如何去选择? 10 | 易证,在满足条件的[0,i]区间之中,i越小则[i+1,j]区间越长。 11 | 设cnt为[0,j]区间距离0与1数量相等状态的偏移量,如0比1多出一个则`cnt = -1`,0比1少一个则`cnt = 1`。 12 | 使用`unordered_map M`去记录偏移量为x的最前方的区间[0,i]的上界i。 13 | 当使用指针j去扫描序列时,计算出当前的偏移量cnt,并去M中去找是否已经存储过偏移量为cnt的区间[0,i]。若存在则计算区间[i+1,j]的长度,并更新答案。否则在M[cnt]中插入j。 14 | 对于每次HashTable的查询以及插入操作的时间复杂度为`O(1)`,至多执行N次,因此最终的时间复杂度为`O(N)`。 15 | -------------------------------------------------------------------------------- /525- Contiguous Array/Solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | int findMaxLength(vector& nums) { 4 | int ans = 0; 5 | int x = 0;//记录01平衡偏移量 6 | M[0] = -1; 7 | int ld = nums.size(); 8 | for(int i = 0; i M; 26 | }; 27 | -------------------------------------------------------------------------------- /529. Minesweeper/README.md: -------------------------------------------------------------------------------- 1 | ## 题目分析: 2 | 经典扫雷模拟。 3 | 现给出一个二维数组,`M`表示没有被揭开的地雷、`E`表示未被揭开的空方块、`B`表示已经被打开了的空方块且周围没有地雷。如果一个空方块周围有地雷,则显示的是周围地雷的数目。`X`表示被揭开的地雷数目。 4 | 具体规则如下: 5 | + 若揭开一个埋好的地雷,则游戏结束,将其变为`X`; 6 | + 若揭开一个周围没有地雷的空方格,则将其变为`B`,并且周围所有的空方格也应都被揭开; 7 | + 若一个周围有雷的空方格被揭开,则将其变为周围地雷的数值; 8 | + 当当前的已无方格再被揭开,返回当前矩阵 9 | ### 解题思路 10 | 一道常规的模拟题,现分情况讨论: 11 | 1. 若点击的格子埋雷,则直接将其变为`X`并结束; 12 | 2. 若点击的格子为空方格则 找周围八个格子,统计地雷数目,若其周围有雷则将其更改为对应的数字,并结束;否则将周围的格子加入队列进行拓展,重复该步,直到队列为空;(**注意使用bool数组标记,以防重复处理一个空格子**) 13 | -------------------------------------------------------------------------------- /532-K-diffPairsinanArray/README.md: -------------------------------------------------------------------------------- 1 | ## 题目分析: 2 | 3 | 定义k-diff对就是一个数对(x,y),其中abs(x-y)=k,找出一串序列中有多少个这样的k-diff对。 4 | 其中一个数对(x,y)不计顺序,而且同样的数对只算一次。 5 | 6 | ### 解题思路: 7 | 8 | 利用STL的map来保存每个数的个数。 9 | 每次枚举每个序列中的数x,对于x只要知道x+k是否存在即可。对于k是否等于0分为两种情况: 10 | 1. 若k > 0,查询map中x+k的个数是否大于0,如果大于0,说明存在一个k-diff对,结果ans加1。 11 | 2. 若k = 0,查询map中x的个数是否大于1,如果大于1,说明存在一个k-diff对,结果ans加1。 12 | 注意到这里有个trick,k如果小于0,不可能存在这样的k-diff对,直接返回0。 13 | 14 | #### 算法正确性: 15 | 16 | 算法每次枚举一个k-diff对中较小的数,保证找到的k-diff对(x,y)都满足x < y,这样(x,y)和(y,x)就不会重复计算,另外对于每个数都要找到可能存在的k-diff对,也不会存在遗漏。 17 | 利用map保存每个数出现的次数,算法时间复杂度O(nlogn)。 18 | 19 | 20 | -------------------------------------------------------------------------------- /532-K-diffPairsinanArray/solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | int findPairs(vector& nums, int k) { 4 | if (k < 0) return 0; 5 | map mp; 6 | for (int i = 0; i < nums.size(); i++) 7 | ++mp[nums[i]]; 8 | int res = 0; 9 | for (auto it = mp.begin(); it != mp.end(); it++) 10 | if ((k && mp.find(it->first+k) != mp.end()) || (!k && it->second > 1)) 11 | ++res; 12 | return res; 13 | } 14 | }; -------------------------------------------------------------------------------- /535- Encode and Decode TinyURL/README.md: -------------------------------------------------------------------------------- 1 | ## 题目分析: 2 | 编写一个程序,将所给的URL编码为较短的一个TinyURL,而后再将其解码为原本的URL。 3 | 验证方式为`Decode( Encode(x) ) = x`。 4 | ## 解题思路(1) 5 | 比较捉急的做法,由验证方式思考,若`Encode(x) = x`, `Decode(x) = x`,则可以满足上述需求。即解码与编码直接返回原串即可。时间复杂度为O(1),但算是一种欺诈。 6 | ## 解题思路(2) 7 | 对于每个URL构造一个对应的TinyURL,建立一一对应的映射关系。 8 | 如构造TinyURL的格式为 `http://tinyurl.com/` + `序号`。 9 | 通过unordered_map建立正反两连接即可。 10 | -------------------------------------------------------------------------------- /535- Encode and Decode TinyURL/Solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | 4 | // Encodes a URL to a shortened URL. 5 | string encode(string longUrl) { 6 | if(Encode.find(longUrl) != Encode.end())return "http://" + Encode[longUrl]; 7 | string tmp = "tinyurl.com/" + to_string(Encode.size());//生成tinyURL 8 | Decode[tmp] = longUrl; 9 | Encode[longUrl] = tmp; 10 | return "http://" + tmp; 11 | } 12 | 13 | // Decodes a shortened URL to its original URL. 14 | string decode(string shortUrl) { 15 | shortUrl = shortUrl.substr(7, shortUrl.npos); 16 | if(Decode.find(shortUrl) != Decode.end()) return Decode[shortUrl]; 17 | } 18 | private: 19 | unordered_map Encode; 20 | unordered_map Decode; 21 | }; 22 | -------------------------------------------------------------------------------- /538-ConvertBSTtoGreaterTree/README.md: -------------------------------------------------------------------------------- 1 | ## 题目分析: 2 | 3 | 给定一个二叉排序树。将每个节点的值改为当前值与二叉排序树中所有比该节点大的值的和。 4 | 5 | ### 解题思路 6 | 二叉树的遍历。按`右子树 -> 根 -> 左子树`的顺序遍历, 并记录当前的和。复杂度`O(n)`. -------------------------------------------------------------------------------- /538-ConvertBSTtoGreaterTree/solution.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for a binary tree node. 3 | * struct TreeNode { 4 | * int val; 5 | * TreeNode *left; 6 | * TreeNode *right; 7 | * TreeNode(int x) : val(x), left(NULL), right(NULL) {} 8 | * }; 9 | */ 10 | class Solution { 11 | public: 12 | int sum = 0; 13 | TreeNode* convertBST(TreeNode* root) { 14 | if(root){ 15 | convertBST(root->right); 16 | root->val += sum; 17 | sum = root->val; 18 | convertBST(root->left); 19 | } 20 | return root; 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /541. Reverse String II/README.md: -------------------------------------------------------------------------------- 1 | ### 题目分析 2 | 输入k,和字符串,然后将字符串前2K个的k个字符反转就可以,后面不满足k个的需要将这k个反转。 3 | 4 | ### 解题思路 5 | 题目很简单,基本就是暴力模拟题目。我的做法,是每一次移动2k个距离,然后分为俩种情况判断,如果还剩余k个数,那么就反转前k个即可,如果已经不足k个了,那么就将剩余的反转即可。详情见代码。 6 | 7 | -------------------------------------------------------------------------------- /541. Reverse String II/solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | void transit(string &s, int t, int k){ 4 | int med = (k-t)/2 + t; 5 | for(int i = t; i <= med; i++) 6 | { 7 | int j = k-(i-t); 8 | char tmp; 9 | tmp = s[i]; 10 | s[i] = s[j]; 11 | s[j] = tmp; 12 | 13 | } 14 | } 15 | string reverseStr(string s, int k) { 16 | int begin = 0, end; 17 | while(begin < s.size()){ 18 | end = begin+2*k-1; 19 | if(begin+k-1 < s.size()) 20 | transit(s, begin, begin+k-1); 21 | else 22 | transit(s, begin, s.size()-1); 23 | 24 | begin += 2*k; 25 | } 26 | return s; 27 | } 28 | }; -------------------------------------------------------------------------------- /542-01 Matrix/README.md: -------------------------------------------------------------------------------- 1 | ## 题目分析: 2 | 给出一个由0与1组成的矩阵,要求得到每个单元距离自己最近的0的距离。 3 | ### 解题思路(1): 4 | 最暴力的做法便是,从每个0单元出发,遍历矩阵内每个单元,尝试将1单元的答案更新为与其的距离。假设矩阵的大小为`N*M`,则整体时间复杂度为`O(N*M*number_of_zero)`。 5 | ### 解题思路(2): 6 | 上述算法中有若干次的更新其实是不必要的。对当个的0单元而言,周围的1单元的距离依次上升。因而考虑事先将所有的0单元加入至队列Q之中,对队列Q进行X次扩展。每次拓展队列Q内单元距离为1且不曾加入过队列中的单元,将其加入队列之中,以及答案值更新为当前更新的**轮数**。易证,越晚加入队列的结点,其与最近0的距离也越大,因此不需要进行重复更新。 7 | 每个节点仅入队一次,出队一次,拓展的方向也仅有四个,因此时间复杂度为`O(N*M)`。 8 | -------------------------------------------------------------------------------- /546-RemoveBoxes/README.md: -------------------------------------------------------------------------------- 1 | ## 题目分析 2 | 3 | 给定一列数,每次可以删掉连续的相同数字,获得的价值是长度的平方,问最大价值。 4 | 5 | ### 解题思路 6 | 7 | 首先,这一题和```Burst Ballons```有很多想通之处,都是选一些数删去,问获得的最大价值。因此也很正常的去想到区间dp,令f(l,r)为删去l-r区间后的最大价值。 8 | 9 | 类比气球题,我们考虑是否要去枚举最后剩下的数字?发现这样并不好处理删去后和两边合并的情况,转移的状态也比较多。 10 | 11 | 这题和气球不一样的地方在于,气球只取决于两端的两个,这题不仅仅是两个,是两端所有连续的。因此,我们考虑修正状态,记录下两端还有多少个可以合并,那么随之而来的另一个问题就是,两端的颜色是什么呢?难道还要再枚举么? 12 | 13 | 其实不用,我们只需要考虑末尾就好了。因为末尾的只有两个状态,要么直接删掉,要么把先留着,和前面的某一段合并。因此,令f(l,r,k)为合并l,r区间,且r后边还有k个颜色和它相同的,即f(l,r,k)=max(f(l,r-1,0)+(1+k)*(1+k),f(l,i,1+k)+f(i+1,r,0))。 14 | -------------------------------------------------------------------------------- /546-RemoveBoxes/solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution 2 | { 3 | int dp[105][105][105]; 4 | int dfs(int l,int r,int k,vector &b) 5 | { 6 | if(l>r) return 0; 7 | int &d=dp[l][r][k]; 8 | if(d!=-1) return d; 9 | d=dfs(l,r-1,0,b)+(1+k)*(1+k); 10 | for(int i=l;i& boxes) 17 | { 18 | memset(dp,-1,sizeof(dp)); 19 | return dfs(0,boxes.size()-1,0,boxes); 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /547-findCircleNum/README.md: -------------------------------------------------------------------------------- 1 | ## 题目分析 2 | 3 | n个人,若a和b是直接/间接朋友,b和c是直接/间接朋友,那么a和c也是朋友。给出一个n*n的矩阵表示i和j是否是直接朋友,确定有多少个朋友网。 4 | 5 | ## 题解思路 6 | 7 | 先把问题抽象出来,每个人看成一个节点,直接朋友关系当做连接两点的无向边,那么问题就是给出一个无向图,求一共有多少个**联通分量**。 8 | 9 | 回忆一下联通分量的定义:若存在一条从点u到v的路径,那么u和v在同一联通分量中。 10 | 11 | 联通分量问题可以用**并查集**处理。并查集是一种处理集合,但是并不关心集合中元素之间关系的数据结构。 12 | 13 | 用并查集解决这个问题,简单的说就是,每次把有直接关系的两个点所在的联通分量点集合并。 14 | 15 | -------------------------------------------------------------------------------- /547-findCircleNum/solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | int fa[205]; 4 | bool vis[205]; 5 | int find (int x) { 6 | return fa[x] == x ? fa[x] : fa[x] = find (fa[x]); 7 | } 8 | int findCircleNum(vector>& M) { 9 | int n = M.size (); 10 | if (n == 0) { 11 | return 0; 12 | } 13 | for (int i = 0; i < n; i++) fa[i] = i, vis[i] = 0; 14 | for (int i = 0; i < n; i++) { 15 | for (int j = 0; j < n; j++) if (M[i][j]) { 16 | int fa1 = find (i), fa2 = find (j); 17 | if (fa1 != fa2) //如果两个点不在同一个联通分量,直接合并 18 | fa[fa1] = fa2; 19 | } 20 | } 21 | int ans = 0; 22 | for (int i = 0; i < n; i++) { 23 | int tmp = find (i); 24 | if (!vis[tmp]) ans++, vis[tmp] = 1; 25 | } 26 | return ans; 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /55-JumpGame/README.md: -------------------------------------------------------------------------------- 1 | ## 题目分析: 2 | 3 | 给出一串n个非负数的序列nums,其中nums[i]表示你当前在第i个数时最多能前进num[i]步,初始时你在第1个数,问:最后能否到第n个数? 4 | 举例说明: 5 | A = [2,3,1,1,4], return true. 6 | A = [3,2,1,0,4], return false. 7 | 8 | ### 解题思路: 9 | 10 | 根据题意可知,对于点i,在该点能到达的最远位置为i+nums[i],前提是,能到到达点i。 11 | 这样我们可以从左到右遍历,维护一个当前所能到达的最大边界reach,始终保持当前遍历的点i <= reach,这样点i都是可以到达的,再根据nums[i]+i来更新reach的大小。 12 | 若最后能遍历到点n,返回true,否则返回false。 13 | 14 | #### 算法正确性: 15 | 16 | 每次遍历都是在已经能到达的范围内,接着计算的结果可以更新边界。 17 | 保证走出的每一步都是可以到达的,故算法正确。 18 | 算法复杂度O(n)。 -------------------------------------------------------------------------------- /55-JumpGame/solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | bool canJump(vector& nums) { 4 | int now = 0; 5 | for (int reach = 0; now < nums.size() && now <= reach; now++) { 6 | reach = max(reach, now + nums[now]); 7 | } 8 | return now == nums.size(); 9 | } 10 | }; -------------------------------------------------------------------------------- /551-Student Attendance Record I/README.md: -------------------------------------------------------------------------------- 1 | ## 题目分析: 2 | 给你一个字符串代表一个学生的出勤记录,其中A代表缺勤,L代表迟到,P代表出勤,这名学生能获得奖励当且仅当出勤记录中缺勤没有超过一次或者没有连续迟到超过两次,求这名学生能否获得奖励。 3 | 4 | ### 解题思路: 5 | 对字符串从左到右扫一遍,用cntA代表扫到当前字符一共遇到了多少个A,用cntL代表扫到当前字符遇到了多少个连续的L。如果当前字符是A就把cntA加1,同时把cntL清零;如果当前字符是L就把cntL加1;如果当前字符是P就把cntL清零,如果某一时刻cntA>1或者cntL>2,那就直接返回false,如果字符串都扫完了还没有返回就返回true。 6 | 7 | 下面举一个简单的例子走一遍算法帮助理解:”LALLPL”。
8 | 初始化:cntA=0,cntL=0。
9 | 第0个字符为L,cntL变为1,继续;
10 | 第1个字符为A,cntA变为1,cntL变为0,继续;
11 | 第2个字符为L,cntL变为1,继续;
12 | 第3个字符为L,cntL变为2,继续;
13 | 第4个字符为P,cntL变为0,继续;
14 | 第5个字符为L,cntL变为1,继续;
15 | 最终结果为true。 16 | -------------------------------------------------------------------------------- /551-Student Attendance Record I/solution.cpp: -------------------------------------------------------------------------------- 1 | bool checkRecord(string s) 2 | { 3 | int len=s.size(); 4 | int cntA=0,cntL=0; 5 | for(int i=0;i1)return false;//缺勤次数超过1次 13 | break; 14 | case 'L': 15 | cntL++; 16 | if(cntL>2)return false;//连续迟到次数超过2次 17 | break; 18 | case 'P': 19 | cntL=0; 20 | break; 21 | } 22 | } 23 | return true; 24 | } 25 | -------------------------------------------------------------------------------- /553-Optimal Division/README.md: -------------------------------------------------------------------------------- 1 | ## 题目分析: 2 | 给你一串正整数,相邻正整数间做浮点除法,现在可以在形成的式子中添加括号使得最终结果最大,构造出使得最终结果最大的式子,添加的括号不能有冗余,即去掉这个括号后运算优先级没有任何改变。 3 | 4 | ### 解题思路: 5 | 想法题。考虑最后一次除法,要使整个式子结果最大,我们需要使最后一次除法的被除数尽量大,除数尽量小,由于所有数都是正整数,因此最大的被除数只能为第一个数。要让除数最小,添加一个括号把第二个数到最后一个数括起来就行了,因为除得越多,数就会越小。需要注意在构造的时候如果数的个数不超过两个不需要添加括号,如果只有一个数就不用添加任何符号。 6 | -------------------------------------------------------------------------------- /553-Optimal Division/solution.cpp: -------------------------------------------------------------------------------- 1 | string optimalDivision(vector& nums) 2 | { 3 | int len=nums.size(); 4 | string ans=""; 5 | char store[4]; 6 | for(int i=0;i=0;j--)//倒序加入答案 15 | { 16 | ans=ans+store[j]; 17 | } 18 | if(i2)//如果不超过两个数就不用添加括号 20 | { 21 | if(i==len-1)ans=ans+")";//最后一个数后面加右括号 22 | else if(i==0)ans=ans+"(";//头一个数后面加左括号 23 | } 24 | } 25 | return ans; 26 | } 27 | -------------------------------------------------------------------------------- /554-Brick Wall/README.md: -------------------------------------------------------------------------------- 1 | ## 题目分析: 2 | 在你面前有一堵长方形的墙,墙上有若干砖块,所有砖块高相同但宽不一定相同,墙上的砖块以若干行的形式给出,每行是一系列整数,表示该行砖块从左到右的宽度。现在你要画一条竖直的线(这条线不能画在矩形边缘),求这条竖直的线最少会穿过多少个砖块,如果先从一个砖块边界穿过,这个砖块就不认为是被穿过的,数据保证每行砖块的宽的和在int范围内,每行不超过10000个砖块,行数不超过10000,砖块总数不超过20000。 3 | 4 | ### 解题思路: 5 | 想法题。很明显如果有一行砖块数超过一个,画的这条线是一定要经过某个砖块边缘的,否则每一行都要穿过一个砖块,答案必然不会最优。因此我们可以把所有砖块边缘点离墙左边界的距离求出来并存起来,然后排个序,然后扫一遍看边界值出现最多的次数(墙的右边界除外),行数减去这个出现次数最多的值就是答案。 6 | 7 | 下面举一个简单例子走一遍算法帮助理解:[[2,1],[2,1],[1,2]]。
8 | 第一行:第一个砖块离左边界距离2,第二个砖块离左边界距离3;
9 | 第二行:第一个砖块离左边界距离2,第二个砖块离左边界距离3;
10 | 第三行:第一个砖块离左边界距离1,第二个砖块离左边界距离3;
11 | 将这些距离排序,得到[1,2,2,3,3,3];
12 | 检查所有非墙右边界的边界,1出现1次,2出现2次,因此最终答案为3-2=1。 13 | -------------------------------------------------------------------------------- /554-Brick Wall/solution.cpp: -------------------------------------------------------------------------------- 1 | int leastBricks(vector >& wall) 2 | { 3 | int store[20005]; 4 | int cnt=0; 5 | int lenr=wall.size(); 6 | for(int i=0;ilenr)ans=min(ans,lenr-nowcnt);//如果cnt和lenr相等说明每行都只有一个墙,否则还要更新一下最后一次统计的答案 28 | return ans; 29 | } 30 | -------------------------------------------------------------------------------- /557.Reverse Words in a String III/README.md: -------------------------------------------------------------------------------- 1 | ### 题目分析 2 | 给定一个字符串,得到它的反转串 3 | 4 | ### 解题思路 5 | 将每个单词放入栈中,当遇到空格或者最后一个字符的时候,说明当前栈内为一个完整的单词,那么就将栈内的单词按字符一个个出栈加入result字符串中。 6 | 根据flag的值判断是否是第一个单词,如果不是第一个单词就要在result的后面加一个空格~ 7 | 8 | -------------------------------------------------------------------------------- /557.Reverse Words in a String III/solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | string reverseWords(string s) { 4 | string result = ""; 5 | stack word; 6 | int flag = 0; 7 | for (int i = 0; i < s.length(); i++) { 8 | if (s[i] != ' ') 9 | word.push(s[i]); 10 | if (s[i] == ' ' || i == s.length() - 1) { 11 | if (flag == 1) result += " "; 12 | while (!word.empty()) { 13 | result += word.top(); 14 | word.pop(); 15 | flag = 1; 16 | } 17 | } 18 | } 19 | return result; 20 | } 21 | }; -------------------------------------------------------------------------------- /557.Reverse Words in a String III的副本/README.md: -------------------------------------------------------------------------------- 1 | ### 题目分析 2 | 给定一个字符串,得到它的反转串 3 | 4 | ### 解题思路 5 | 将每个单词放入栈中,当遇到空格或者最后一个字符的时候,说明当前栈内为一个完整的单词,那么就将栈内的单词按字符一个个出栈加入result字符串中。 6 | 根据flag的值判断是否是第一个单词,如果不是第一个单词就要在result的后面加一个空格~ 7 | 8 | -------------------------------------------------------------------------------- /557.Reverse Words in a String III的副本/solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | string reverseWords(string s) { 4 | string result = ""; 5 | stack word; 6 | int flag = 0; 7 | for (int i = 0; i < s.length(); i++) { 8 | if (s[i] != ' ') 9 | word.push(s[i]); 10 | if (s[i] == ' ' || i == s.length() - 1) { 11 | if (flag == 1) result += " "; 12 | while (!word.empty()) { 13 | result += word.top(); 14 | word.pop(); 15 | flag = 1; 16 | } 17 | } 18 | } 19 | return result; 20 | } 21 | }; -------------------------------------------------------------------------------- /56-MergeIntervals/README.md: -------------------------------------------------------------------------------- 1 | ## 题目分析: 2 | 3 | 给出n个区间[l,r],现要求将区间合并,有重叠的区间将合并成一个区间。 4 | 如,初始区间为:[1,3],[2,6],[8,10],[15,18] 5 | 合并之后的区间为:[1,6],[8,10],[15,18] 6 | 7 | ### 解题思路: 8 | 9 | 贪心思路,将初始区间序列ins按照左端点的从小到大排序,接着遍历ins。 10 | 一开始将第一个区间ins[0]放入结果区间序列res,接着每次遍历到一个新的区间[l,r],将其与当前合并后的最后一个区间[L,R]比较: 11 | 1. 若l <= R,说明新区间与当前最有一个区间有重叠,应该将这两个区间合并,也就需要修改当前最后一个区间为[L,max(r,R)]。 12 | 2. 若l > R,说明新区间与当前最后一个区间没有重叠,所以不需要合并,直接将新区间加入结果序列res,成为新的最后一个区间。 13 | 14 | #### 算法正确性: 15 | 16 | 在上述贪心思路中,只考虑了新区间的左端点与最后一个区间的右端点的大小比较,最后只会对最后区间的右端点进行修改,却不会修改左端点。之所以不考虑左端点,是因为初始化时已经将ins按照左端点排序,保证后遍历的左端点l >= 之前遍历过的左端点L。 17 | 算法复杂度为O(nlogn)。 -------------------------------------------------------------------------------- /56-MergeIntervals/solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | static bool cmp(const Interval &a, const Interval &b) { 4 | return a.start < b.start; 5 | } 6 | 7 | vector merge(vector& ins) { 8 | vector res; 9 | if (ins.empty()) return res; 10 | sort(ins.begin(), ins.end(), cmp); 11 | res.push_back(ins[0]); 12 | int cnt = ins.size(); 13 | for (int i = 1; i < cnt; i++) { 14 | if (ins[i].start <= res.back().end) { 15 | res.back().end = max(res.back().end, ins[i].end); 16 | } 17 | else { 18 | res.push_back(ins[i]); 19 | } 20 | } 21 | return res; 22 | } 23 | }; -------------------------------------------------------------------------------- /564-FindtheClosestPalindrome/README.md: -------------------------------------------------------------------------------- 1 | ## 题目分析: 2 | 3 | 给定一个数x, 找到一个与x相差最小的回文数.(n != x)如果有两个数与x的差的绝对值相同, 输出较小的数。 4 | 5 | ### 解题思路 6 | 我们分别考虑比x大的最小回文数和比x小的最大回文数。将x的左半段翻折到右半段显然可以得到两个回文数中的某一个。如何得到另一个数? 若当前数比x大, 将中间的数位改小, 若中间的数位已经是0, 则0变成9,并往前改低; 当前数比x大也类似; x本身是回文数的话, 分别找比它大和比它小的回文数。需要注意一些比较容易错的case: 7 | ``` 8 | 99...99 -> 100...01 9 | 100...00, 100...01 -> 99...99 10 | 1~9: 减一 11 | ``` 12 | ### 例子 13 | 以8776为例。我们翻折左半段, 得到8778(8778 > 8776). 我们将中间的7改成6, 得到8668。比较8778和8668与8776的差值, 答案为8668. 14 | -------------------------------------------------------------------------------- /576-OutOfBoundaryPaths/solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | int findPaths(int m, int n, int N, int i, int j) { 4 | if (N==0) return 0; 5 | int dp[m][n][N+1]; 6 | memset(dp, 0, sizeof(dp)); 7 | for (int l=1; l<=N; l++) 8 | for (int row=0; row nums[i+1]`, 那么该区间的左端点必然 `<= i`. 我们计算出`[i+1, nums.size())`的最小值`minnum`, 该最小值经过排序后必然需要插入到`[0, i]`区间内的某个位置。该插入的位置即是需要排序的最小长度区间的左端点。同理计算出右端点即可。时间复杂度为`O(n)`. -------------------------------------------------------------------------------- /581-ShortestUnsortedContinuousSubarray/Solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | int findUnsortedSubarray(vector& nums) { 4 | if(nums.size() <= 1) return 0; 5 | int i, j, l, r; 6 | for(i = 1; i < nums.size(); i++) 7 | if(nums[i] < nums[i-1]) break; 8 | if(i == nums.size()) return 0;// if already sorted 9 | 10 | int tmp = nums[i--]; 11 | for(j = i+1; j < nums.size(); j++) 12 | if(nums[j] < tmp) tmp = nums[j];//tmp is min in [i+1, nums.size()] 13 | while(i >= 0&&nums[i] > tmp) i--; 14 | l = i+1; 15 | 16 | for(i = nums.size()-2; i >= l; i--) 17 | if(nums[i] > nums[i+1]) break; 18 | tmp = nums[i++]; 19 | for(j = i-1; j >= l; j--) 20 | if(nums[j] > tmp) tmp = nums[j];//tmp is max in [l, i-1] 21 | while(i < nums.size()&&nums[i] < tmp) i++; 22 | r = i-1; 23 | return r-l+1; 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /582-KillProcess/README.md: -------------------------------------------------------------------------------- 1 | ## 题目分析: 2 | 3 | 有n个进程。它们组成了树状结构。每个进程有相应的父进程id。只有一个进程的父进程id是0.当kill某个进程后, 以该进程为根的子树内的所有进程都会被kill。问:kill某个进程后, 哪些进程会被kill? 4 | 5 | ### 解题思路 6 | 对所有进程, 递归查找其祖先节点的id, 直到id为0或为被kill的进程id, 并进行路径压缩。答案即为所有的父节点为被kill的进程的进程 + 被kill的进程本身。 -------------------------------------------------------------------------------- /582-KillProcess/Solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | int findf(int x, unordered_map& dict, int kill){ 4 | int f = dict[x]; 5 | if(f == 0||f == kill) return f; 6 | return dict[x] = findf(f, dict, kill); 7 | } 8 | vector killProcess(vector& pid, vector& ppid, int kill) { 9 | unordered_map dict; 10 | for(int i = 0; i < pid.size(); i++) 11 | dict[ pid[i] ] = ppid[i]; 12 | vector ans = {kill}; 13 | for(int i: pid){ 14 | findf(i, dict, kill); 15 | if(dict[i] == kill) ans.push_back(i); 16 | } 17 | return ans; 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /59-generateMatrix/README.md: -------------------------------------------------------------------------------- 1 | ## 题目分析 2 | 3 | 输出一个n*n的蛇形矩阵,例如n=3时输出 4 | ``` 5 | [ 1, 2, 3 ], 6 | [ 8, 9, 4 ], 7 | [ 7, 6, 5 ] 8 | ``` 9 | 10 | ## 题解思路 11 | 12 | 先清空矩阵,把数字从小到大填进去,那么就是一直往前走的一条线,每次这条线到尽头或者到一个填过的点就右转(初始在[0,0]位置方向向右)。那么就可以直接拿x方向的增量和y方向的增量来模拟,每次试着从上一次的增量方向前进,如果到了边界外或者到过的点,就修正方向(右转),并继续前进,直至填的数字大于n*n。 13 | 14 | 如n=2的情况,初始化: 15 | ``` 16 | [0, 0], 17 | [0, 0] 18 | 当前位置[0,0],x增量0,y增量1,填的数字是1 19 | ``` 20 | 填充,前进一步: 21 | ``` 22 | [1, 0], 23 | [0, 0] 24 | 当前位置[0,1],x增量0,y增量1,填的数字是2 25 | ``` 26 | 填充,试着前进一步,发现出了边界,修正方向为向下。重新前进一步: 27 | ``` 28 | [1, 2], 29 | [0, 0] 30 | 当前位置[1,1],x增量1,y增量0,填的数字是3 31 | ``` 32 | 填充,试着前进一步,发现出了边界,修正方向为向左。重新前进一步: 33 | ``` 34 | [1, 2], 35 | [0, 3] 36 | 当前位置[1,0],x增量0,y增量-1,填的数字是4 37 | ``` 38 | 填充,填完后填的数字变成了5,大于2*2,结束。 39 | ``` 40 | [1, 2], 41 | [4, 3] 42 | ``` 43 | 44 | -------------------------------------------------------------------------------- /6-convert/README.md: -------------------------------------------------------------------------------- 1 | ## 题意分析 2 | 3 | 给出一个串s和行数n,按照如下当时重新排列串: 4 | 5 | ``` 6 | s[0] s[2n-2] 7 | s[1] s[2n-3] s[2n-1] 8 | s[2] s[2n-4] s[2n] 9 | ... ... s[2n+1] 10 | s[n-3] s[n+1] s[2n+2] 11 | s[n-2] s[n] ... 12 | s[n-1] 13 | ``` 14 | 最后按照s[0]s[2n-2]...s[1]s[2n-3]s[2n-1]...s[2]s[2n-4]s[2n]...的顺序重新排列串。 15 | 16 | ## 题解思路 17 | 18 | 其实把排列的方式按照题意分析中的样子画出来,新串的下标排列就很显然了。按照每一行的顺序重新排列新船,假设现在是第r行第c列(我们假设这个c始终是指向某一个竖直的列): 19 | 20 | + 显然对于每一个行r,他的竖直列上的字符下标是`r+2n-2,r+4n-4,r+6n-6...`,因为排列是周期性的。假设我们求得的这个竖直字符下标是j。 21 | + 如果r不是第一行也不是最后一行,那么这个字符后面还接着一个斜方向的字符。显然第r行的字符和这行的斜方向字符距离间隔是`2n-2-2i`,所以这个斜的数字的下标是`j+2n-2-2i`。 22 | 23 | -------------------------------------------------------------------------------- /6-convert/solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | string convert(string s, int numRows) { 4 | if (numRows <= 1) return s; 5 | string ans = ""; 6 | for (int i = 0; i < numRows; i++) { //第i行 7 | for (int j = i; j < s.length (); j += 2*numRows-2) { //当前下标是j 8 | ans += s[j]; 9 | if (i == 0 || i == numRows-1) {} 10 | else if (2*numRows-2-2*i+j < s.length ()) ans += s[2*numRows-2-2*i+j]; 11 | } 12 | } 13 | return ans; 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /60-getPermutation/README.md: -------------------------------------------------------------------------------- 1 | ## 题目分析 2 | 3 | 求1,2...n的第k小字典序的排列。 4 | 5 | ## 题解思路1 6 | 7 | 首先我们需要一个标记数组记录下哪些数字已经被使用了。约定[i...j]表示排列的下标i到下标j的子数组。 8 | 从第1位到第n位依次确定应该是什么数。 9 | 10 | 对于n个不同的数字的第k小字典序排列,我们可以每一位处理。首先对于第一位,如果放的是第j大的数字(假设是x),那么所有第一位的值小于x的排列都比放x的任意排列小。第一位小于x的排列总数是`(j-1)*(n-1)!`,也就是我们**需要找到这样的一个最小的j,使得j*(n-1)!>=k**,其中的含义就是不管第一位放小于x的任意数,最终的排列序都比k要小。现在找到了这样的j之后,我们便排除了`(j-1)*(n-1)`个最小排列,那么接下来问题变成了:第一位放x的排列中,找第`k-(j-1)*(n-1)`小的排列。同时记得将x标记为“已访问” 11 | 因为数字的不可重复性,相当于我们只需要解决子问题:(n-1)个数字的第`k-(j-1)*(n-1)`小的排列。 12 | 不断减小问题规模至只剩一个数即可。 13 | 14 | ## 题解思路2 15 | 16 | C++的STL中有一个函数是std::next_purmutation(),可以直接求出某一个排列的下一个排列。于是可以直接用以下代码暴力完成。 17 | ``` 18 | class Solution { 19 | public: 20 | string getPermutation(int n, int k) { 21 | int a[11]; 22 | for (int i = 1; i <= n; i++) a[i] = i; 23 | for (int i = 1; i < k; i++) { 24 | next_permutation (a+1, a+1+n); 25 | } 26 | string ans = ""; for (int i = 1; i <= n; i++) ans += (char)(a[i]+'0'); 27 | return ans; 28 | } 29 | }; 30 | ``` 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /60-getPermutation/solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | string getPermutation(int n, int k) { 4 | int a[11], A[11]; 5 | int left = n; //还剩下多少数 6 | bool vis[11] = {0}; 7 | A[0] = 1; for (int i = 1; i <= n; i++) A[i] = A[i-1]*i; 8 | 9 | for (int i = 1; i <= n; i++) { //当前在放第i位的数 10 | for (int j = 1; j <= n; j++) { //判断应该放第j大的 11 | if (j*A[left-1] >= k) { //找剩下的数中第j大的 12 | int cnt = 0; 13 | for (int id = 1; id <= n; id++) if (!vis[id]) { 14 | cnt++; 15 | if (cnt == j) { 16 | k -= (j-1)*A[left-1]; 17 | a[i] = id, vis[id] = 1, left--; 18 | break; 19 | } 20 | } 21 | break; 22 | } 23 | } 24 | } 25 | string ans = ""; 26 | for (int i = 1; i <= n; i++) ans += (char) (a[i]+'0'); 27 | return ans; 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /61-RotateList/README.md: -------------------------------------------------------------------------------- 1 | ## 题目分析: 2 | 3 | 将一个链表向右旋转`k`位。如`1 -> 2 -> 3 -> 4`向右旋转3位则为 `2 -> 3 -> 4 -> 1`。 4 | 5 | ### 解题思路 6 | 求出链表的长度len, 则我们需要移k%len位。找到需要断开的位置重接一下即可。例子如上。 -------------------------------------------------------------------------------- /61-RotateList/Solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | ListNode* rotateRight(ListNode* head, int k) { 4 | if(!head) return head; 5 | ListNode *p = head, *q; 6 | int len = 1; 7 | while(p->next) { 8 | len++; 9 | p = p->next; 10 | } 11 | if(!(k %= len)) return head;//if k%len == 0, needn't do anything 12 | k = len-k; 13 | p->next = head;//let the last node point to head 14 | while(k--) 15 | p = p->next;//find the kth node 16 | head = p->next; 17 | p->next = NULL;//break the list 18 | return head; 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /62-Unique Paths/README.MD: -------------------------------------------------------------------------------- 1 | ## 题目分析: 2 | 现有一个大小为`M*N`的网格,问从左上角到右下角的方案数目。 3 | `M`与`N`不大于100。 4 | ### 解题思路(1): 5 | 经典的递推入门题目。乍一看题面中M与N的大小规模,默默估算下最大答案为C(200,100,远远大于Int的范围。而重新审视我们所要写的函数所返回的类型为int,这说明不必担心答案超出int的范围。 6 | 使用递推式`path[i][j] = path[i-1][j] + path[i][j-1]`即可,注意下边界的初始化。时间复杂度为`O(N*M)`。 7 | ### 解题思路(2): 8 | 由于从左上角到右下角的总步数为`N+M`,仅需从其中选出M步进行向下的运动,剩下的N步向右运动即可,因此总方案数可用组合数计算公式`C(M+N,N)`,便可计算出。时间复杂度为`O(N+M)`。因此方法有些大材小用,并不给出相应代码,读者可以自行实现。 9 | -------------------------------------------------------------------------------- /62-Unique Paths/Solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | int uniquePaths(int m, int n) { 4 | vector> cal(m, vector(n, 1));//所有单元的方案数预置为1,免去了单独初始化第一行和第一列的麻烦 5 | cal[0][0]=1; 6 | for(int i=1;i>dp(m + 1, vector(n + 1)); 5 | for(int i = 0; i <= m; i ++) dp[i][0] = 0; 6 | for(int j = 0; j <= n; j ++) dp[0][j] = 0; 7 | 8 | for(int i = 1; i <= m; i ++) { 9 | for(int j = 1; j <= n; j ++) { 10 | if(i == 1 && j == 1) { 11 | dp[i][j] = 1; 12 | } else { 13 | dp[i][j] = dp[i - 1][j] + dp[i][j - 1]; 14 | } 15 | } 16 | } 17 | return dp[m][n]; 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /63-Unique Paths II/README.md: -------------------------------------------------------------------------------- 1 | ## 题目分析: 2 | 现有一个大小为`M*N`的网格,中间有些方格不允许通过,问从左上角到右下角的方案数目。 3 | `M`与`N`不大于100。 4 | ### 解题思路: 5 | `#62`的升级版。注意处理下存在障碍的单元的方案数为0即可。不存在障碍时使用递推式`path[i][j] = path[i-1][j] + path[i][j-1]`,注意边界的初始化。时间复杂度为`O(N*M)`。 6 | -------------------------------------------------------------------------------- /63-Unique Paths II/Solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | int uniquePathsWithObstacles(vector>& obstacleGrid) { 4 | int row = obstacleGrid.size(); 5 | int col = obstacleGrid[0].size(); 6 | vector> cnt(row, vector(col, 0)); 7 | if(obstacleGrid[0][0]) return 0; 8 | cnt[0][0] = 1; 9 | for(int i=1;i>& obstacleGrid) { 4 | int m = obstacleGrid.size(), n = obstacleGrid[0].size(); 5 | vector>dp(m + 1, vector(n + 1)); 6 | for(int i = 0; i <= m; i ++) dp[i][0] = 0; 7 | for(int j = 0; j <= n; j ++) dp[0][j] = 0; 8 | 9 | for(int i = 1; i <= m; i ++) { 10 | for(int j = 1; j <= n; j ++) { 11 | if(i == 1 && j == 1) { 12 | dp[i][j] = 1; 13 | } else { 14 | dp[i][j] = dp[i - 1][j] + dp[i][j - 1]; 15 | } 16 | if(obstacleGrid[i - 1][j - 1] == 1) { 17 | dp[i][j] = 0; 18 | } 19 | } 20 | } 21 | return dp[m][n]; 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /64.MinimumPathSum/solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | int minPathSum(vector>& grid) { 4 | int m = grid.size(), n = grid[0].size(); 5 | vector>dp(m, vector(n)); 6 | dp[0][0] = grid[0][0]; 7 | for(int i = 1; i < m; i ++) dp[i][0] = dp[i - 1][0] + grid[i][0]; 8 | for(int j = 1; j < n; j ++) dp[0][j] = dp[0][j - 1] + grid[0][j]; 9 | for(int i = 1; i < m; i ++) { 10 | for(int j = 1; j < n; j ++) { 11 | dp[i][j] = min(dp[i - 1][j], dp[i][j - 1]) + grid[i][j]; 12 | } 13 | } 14 | return dp[m - 1][n - 1]; 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /65-Valid Number/solution.cpp: -------------------------------------------------------------------------------- 1 | bool isNumber(string s) 2 | { 3 | int switchcond[9][6]= 4 | { 5 | {-1,0,2,1,3,-1}, 6 | {-1,-1,2,-1,3,-1}, 7 | {-1,8,2,-1,4,5}, 8 | {-1,-1,4,-1,-1,-1}, 9 | {-1,8,4,-1,-1,5}, 10 | {-1,-1,7,6,-1,-1}, 11 | {-1,-1,7,-1,-1,-1}, 12 | {-1,8,7,-1,-1,-1}, 13 | {-1,8,-1,-1,-1,-1} 14 | };//当前状态接收不合法字符、空格、数字、正负号、小数点、指数符号所转移到的状态矩阵,-1为不合法状态 15 | int len=s.size(); 16 | int nowcond=0,next; 17 | for(int i=0;i='0'&&s[i]<='9')next=2; 21 | else if(s[i]=='+'||s[i]=='-')next=3; 22 | else if(s[i]=='.')next=4; 23 | else if(s[i]=='e')next=5; 24 | else next=0;//将所接收到的字符进行 25 | nowcond=switchcond[nowcond][next];//转移状态 26 | if(nowcond==-1)return false;//到达不合法状态 27 | } 28 | if(nowcond==2||nowcond==4||nowcond==7||nowcond==8)return true;//字符串读完到达终止状态 29 | else return false; 30 | } 31 | -------------------------------------------------------------------------------- /7. Reverse Integer/README.md: -------------------------------------------------------------------------------- 1 | # 题目分析 2 | 题目意思,要求反转一个数字,如果反转后超过32位表示,则输出0 3 | 4 | ### 解题思路(1) 5 | 这道题非常简单,可能容易错误的是,最后怎么判断是否超过了32位,我们的limit.h中的INT_MAX,INT_MIN提供了,那么我们专心考虑如何反转即可。 6 | 7 | 我们可以用字符串进行保存,然后调用反转方法,最后重新转发为数字即可。最终判断一下是否属于32位范围之内即可。 8 | 9 | ### 解题思路(2) 10 | 我们可以不用这么麻烦,先转化为字符串再做,而是直接进行反转即可,我们可以对输入的x,进行判断,如果while(x!=0),一直进行s = s*10+x%10,x/=10的操作,最终跳出循环的时候,已经实现了反转操作。 11 | 12 | #####举个例子解释一下 13 | 当输入为123时候, 14 | while(123!=0) 15 | s = 0*10+123%10 = 3 16 | x = 123/10 = 12 17 | 18 | while(12!=0) 19 | s = 3*10+12%10 = 32 20 | x = 12/10 = 1 21 | 22 | while(1!=0) 23 | s = 32*10+1%10 = 321 24 | x = 1/10 = 0 25 | 26 | 这个时候x为0,跳出循环,已经完成了转换过程!具体见solution.cpp 27 | -------------------------------------------------------------------------------- /7. Reverse Integer/solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | int reverse(int x) { 4 | long long int s = 0; //它有可能会超过int的值 5 | while(x!=0) 6 | { 7 | s = s*10+x%10; //进行反转 8 | x/=10; 9 | } 10 | if(s>INT_MAX || s= 2`. 12 | 显然到达 n - 1 的路径数并不受 n 之后的点的影响,也就是没有后效性.满足使用动态规划求解的要求. 13 | **时间复杂度:** O(n). 每个点只需要计算一次即可. 14 | **代码参见本文件夹下solution.cpp** 15 | 16 | ### 算法正确性 17 | **举个例子** 18 | ``` 19 | // 输入 n = 5 20 | 21 | // 初始化 22 | dp[0] = 1 23 | dp[1] = 1 24 | 25 | //继续求解 26 | dp[2] = dp[1] + dp[0] = 2 27 | dp[3] = dp[1] + dp[2] = 3 28 | dp[4] = dp[2] + dp[3] = 5 29 | dp[5] = dp[3] + dp[4] = 8 30 | 31 | // 返回结果 32 | return dp[5] = 8 33 | ``` 34 | -------------------------------------------------------------------------------- /70.ClimbingStairs/solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | int climbStairs(int n) { 4 | vectordp(n + 1); 5 | dp[0] = 1; 6 | dp[1] = 1; 7 | for(int i = 2; i <= n; i ++) { 8 | dp[i] = dp[i - 1] + dp[i - 2]; 9 | } 10 | return dp[n]; 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /71.SimplifyPath/README.md: -------------------------------------------------------------------------------- 1 | ### 题意 2 | **中文描述** 3 | 给定一个文件的绝对路径(Unix-style),进行简化. 4 | 5 | ### 题解 6 | **算法及复杂度 (6 ms)** 7 | 本题主要的处理对象分为: ".", "..", "/", 普通文件或目录名.其中"."的作用是保持当前的目录,".."的作用是退回上一级目录,"/"的作用的分隔符, 普通文件或目录名不需要进行特殊处理. 8 | 很容易的思路(模拟),根据"/"对所有字符串进行分割,得到不同的三类字符串: ".", "..", 普通文件或目录名.分割过程是比较容易实现的,就是简单的读取字符串,然后分割. 9 | 由于".."有回退的作用,因此可以考虑使用stack进行实现.在上一段中叙述的分割的过程中进行处理:(1)遇到普通目录名就进行压栈;(2)遇到"."就跳过不处理;(3)遇到".."就对栈进行弹出(保证栈不为空的情况下). 10 | ***存在问题的几点:*** (1)输入字符串为空字符串,则返回空字符串,而不是根目录"/";(2)输入字符串的第一个字符一定是'/',而不是任意的(leetcode的参考程序会报错);(3)存在"...", "...."这样的路径,在本题中被认为是普通的目录或文件名. 11 | ***时间复杂度:*** O(n). n 表示输入字符串的长度,只需要一次遍历就可以完成,因此是 O(n) 的复杂度. 12 | ***代码参见本文件夹下solution.cpp*** 13 | 14 | ### 算法正确性 15 | **正确性证明** 16 | 模拟的思想,根据题目提供的方法在进行操作,提供的已知条件保证了算法的正确性. 17 | **举个例子** 18 | ``` 19 | //输入序列 20 | path = "/abc/../..." 21 | 22 | //分割第一个字符串,得到"abc",入栈st 23 | st = ["abc"] 24 | 25 | //分割第二个字符串,得到:..",弹栈st 26 | st = [] 27 | 28 | //继续分割,得到"...",入栈st 29 | st = ["..."] 30 | 31 | //还原path,最后得到path 32 | ``` 33 | -------------------------------------------------------------------------------- /72-Edit Distance/solution.cpp: -------------------------------------------------------------------------------- 1 | int minDistance(string word1, string word2) 2 | { 3 | int len1=word1.size(),len2=word2.size(); 4 | int dp[len1+1][len2+1]; 5 | dp[0][0]=0;//初始化边界 6 | for(int i=1;i<=len1;i++)//初始化边界 7 | { 8 | dp[i][0]=i; 9 | } 10 | for(int i=1;i<=len2;i++)//初始化边界 11 | { 12 | dp[0][i]=i; 13 | } 14 | for(int i=1;i<=len1;i++) 15 | { 16 | for(int j=1;j<=len2;j++) 17 | { 18 | if(word1[i-1]==word2[j-1])dp[i][j]=dp[i-1][j-1];//两个字符相等 19 | else dp[i][j]=min(dp[i-1][j]+1,min(dp[i][j-1]+1,dp[i-1][j-1]+1));//两个字符不相等 20 | } 21 | } 22 | return dp[len1][len2]; 23 | } 24 | -------------------------------------------------------------------------------- /73.Set_Matrix_Zeroes/README.md: -------------------------------------------------------------------------------- 1 | # 题目分析 2 | 题目意思是给定一个矩阵,里面只包含0,或者1.要求我们将矩阵中是0的对应行列的元素全置为0 3 | 4 | ### 解题思路(1) 5 | 我们很容易想到的办法是另开一个一样大小的矩阵B,将原矩阵A复制到该矩阵B,那么对B矩阵进行扫描,碰到0,我就将原矩阵A的对应行列全置为0即可。满足要求,但是这样的空间复杂度较大,空间复杂度为0(m*n),m,n分被是行列。 6 | 7 | ### 解题思路(2) 8 | 我们可以有一个空间复杂度为0(1)的算法,我们扫描一下矩阵,把0对应的行首变为0,列头变为0,那么后面扫描处理的时候,碰到行头为0的,我就将这一行全变为0,碰到列头为0的,我就将这一列全变为0。这样我们的空间复杂度就是0(1),没有额外开空间。 9 | -------------------------------------------------------------------------------- /75.sort-clors/README.md: -------------------------------------------------------------------------------- 1 | # 题目分析 2 | 题目意思是给定数组中只有0,1,2三种元素,让我们返回新的数组,里面元素已经排好序。 3 | 4 | ### 解题思路(1) 5 | 一开始我题目没看懂,后来才发现整个数组中只有0,1,2三种数字,进行排序,如果可以用sort一句话就可以了,但是很不幸,说了不允许用sort,那么我们需要自己排序,很简单冒泡0(n2)或者快排(一般0(nlogn)),就可以了 6 | 7 | ### 解题思路(2) 8 | 因为它的所有数字只有三种,那么我们可以利用这个特性就是用0(n)的复杂度即可,扫描一遍,然后分别记录0,1,2的个数,然后在扫描一遍依次填充即可,但是这扫描了俩次,时间复杂度为0(n) 9 | 10 | ### 解题思路(3) 11 | 有什么办法只扫描一遍吗,那就是用三个指针来辅助,一个指向0位置的尾部,一个指向2的开始,一个用来扫描数组,详细见代码 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /75.sort-clors/solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | void sortColors(vector& nums) { 4 | //这里我们是不允许使用sort函数 5 | //我可以采取俩边夹的过程,碰到0从左到右,碰到2从右到左即可 6 | //然后最终完成的就是排序好的 7 | int len = nums.size(); 8 | int i_0 = 0,j_2 = len-1; 9 | for(int i = 0;i=i_0&&nums[i]==0) 16 | { 17 | swap(nums[i],nums[i_0++]); //就是切记你换过来还可能是0,所以需要循环 18 | } 19 | } 20 | } 21 | }; -------------------------------------------------------------------------------- /78.Subsets/README.md: -------------------------------------------------------------------------------- 1 | # 题目分析 2 | 给定一个数组,要求返回这个数组的所有子集! 3 | 4 | ### 解题思路(1) 5 | 这道题也就是要求求出所有的子集,可以用深度优先遍历来求解(有可能复杂度会爆,为指数级),每个数字都可能是俩种状态,一种是0,一种是1.从左子树走是0,从右子树走是1,整个搜索树结束后,所有的状态都已经遍历完。代码见solution1.cpp(代码中还有详细说明) 6 | 7 | 8 | ### 解题思路(2) 9 | 我们可以用位操作来做,n位就有2的n次方个子集,那么我只要包括这些个结果可以,如对于数组[1,2,3],可以用一个下标0和1表示是否选择该数字,0表示未选择,1表示选中,那么每一组3个0和1的组合表示一种选择,3位共有8种选择,分别是: 10 | 000 对应[] 11 | 001 对应[3] 12 | 010 对应[2] 13 | 011 对应[2,3] 14 | 100 ... 15 | 101 ... 16 | 110 ... 17 | 111 ... 18 | 那么上面为1的位表示数组中该位被选中。 0代码没有选中。(这样实现之后,所有的子集都会包括进来,并且不会少也不会多)代码见solution2.cpp(有对应注释) -------------------------------------------------------------------------------- /78.Subsets/solution1.cpp: -------------------------------------------------------------------------------- 1 | 2 | class Solution { 3 | public: 4 | void solve_subset(int level,vectortemp,vector>& res,vector& nums) 5 | { 6 | if(level==nums.size()) //到根结点就统计结果 7 | { 8 | res.push_back(temp); 9 | return; 10 | } 11 | solve_subset(level+1,temp,res,nums); //这相当于左子树,从下一层回溯到这一层的时候,相当于出来一层了 12 | temp.push_back(nums[level]); //这里是在记录结果,依次往上递增,temp是临时变量,相当于起了一个分支,二叉树过程 13 | solve_subset(level+1,temp,res,nums); //上一句和这一句加起来相当于右子树 14 | } 15 | vector> subsets(vector& nums) { 16 | //这就是返回所有的子集,用搜索试试 17 | //搜索是一定可以构造搜索树的 18 | vector> res; 19 | vectortemp; 20 | solve_subset(0,temp,res,nums); 21 | return res; 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /78.Subsets/solution2.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | vector> subsets(vector& nums) { 4 | int len = nums.size(); 5 | int i,j; 6 | int maxx = 1<> res; 8 | for(i = 0;itemp; 11 | j = i; //在想象的时候,就把它想成32位数字来考虑 12 | int count = 0; //控制我这个时候是第几位了 13 | while(j>0) 14 | { 15 | if(j&1) 16 | { 17 | temp.push_back(nums[count]); 18 | } 19 | count++; 20 | j = j>>1; 21 | } 22 | res.push_back(temp); 23 | } 24 | return res; 25 | } 26 | }; -------------------------------------------------------------------------------- /79.Word_Search/README.md: -------------------------------------------------------------------------------- 1 | # 题目分析 2 | 题意要求我们在board这个矩形中是否能够找到指定的word,如果找到,则返回true,没有找到返回false.这道题目是非常经典的回溯类型的题目, 3 | 4 | ### 解题思路(1) 5 | 采用搜索,枚举board中的一条路径,并且在枚举的过程中一边检查是否能够匹配上word,如果顺利的匹配完了所有word的字符,就说明找到了;如果枚举结束之后仍然没有找到,就说明不存在。我用一个全局变量res,来保存最后的结果,只要在搜索过程中碰到了一个结果,那么res就为true,立即退出,最坏的复杂度是0(M4^n ),其中M是矩阵字符的个数,因为每一个点,我都当做入口进入搜索了~而每一个结点都可以延伸出4个子节点出来,所以是4^n .这个复杂度是很高。 6 | 7 | 一定要注意能剪枝的地方要剪枝,如搜索到了结果,就直接跳出来,不再进行搜索了.搜索过程中,进行相应位置匹配的时候,进入下一步搜索,如果长度与word长度一样说明已经在矩阵中已经找到了一个单词。代码见solution.cpp(有详细注释) 8 | 9 | -------------------------------------------------------------------------------- /8-myAtoi/README.md: -------------------------------------------------------------------------------- 1 | ## 题意分析 2 | 3 | 实现一个字符串转数字的函数。只需要返回位于字符串前缀最长的一个数字,如`-12a`就返回`-12`。 4 | 5 | ## 题解思路 6 | 7 | 除了正常情况还有很多的corner case,下面列举一下可能的情况: 8 | 1. 忽略前缀空格; 9 | 2. 一个数字前只能有一个单元运算符(负号或者正号); 10 | 3. 读到非数字之后的余串忽略; 11 | 4. 当数字不小于`2147483647`,认为正溢出,直接返回2147483647; 12 | 5. 当数字不大于`-2147483648`,认为负溢出,直接返回`-2147483648`; 13 | 6. 最好用long long存储数据避免溢出。 14 | 15 | -------------------------------------------------------------------------------- /8-myAtoi/solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | int myAtoi(string str) { 4 | int s = 0; while (s < str.length () && str[s] == ' ') s++; 5 | if (s == str.length ()) return 0; 6 | if (!isdigit (str[s]) && str[s] != '+' && str[s] != '-') { 7 | return 0; 8 | } 9 | 10 | long long ans = 0; 11 | int flag = (str[s] == '-' ? -1 : 1); 12 | if (!isdigit (str[s])) s++; 13 | for (int i = s; i < str.length () && isdigit (str[i]); i++) { 14 | ans *= 10; 15 | ans += (str[i]-'0'); 16 | if (flag == 1 && ans >= 2147483647) return 2147483647; 17 | else if (flag == -1 && -ans <= -2147483648) return -2147483648; 18 | } 19 | ans *= flag; 20 | return (int)ans; 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /81 Search in Rotated Sorted Array II/README.md: -------------------------------------------------------------------------------- 1 | # 题目分析 2 | 题意要求我们在数组中找到特定的值,并返回。 3 | 4 | ### 解题思路(1) 5 | 最容易想到的就是线性扫描一遍数组,找到就返回,没找到就不返回,时间复杂度为0(n) 6 | ### 解题思路(2) 7 | 要想比0(n)更快、我们发现不能对数组进行排序,因为排序中最快的算法就是0(nlogn)级别的。题目我们可以找到转折的那个点比如4,5,6,7,0,1,2转折点是7,7左边都比4(最左边)小,7的右边都比4小。 8 | 9 | 二分可以找到,找到这个点之后,后面的就好办了,我们拆成了俩个有序的数组,每一个再用二分方法来查找,最后的结果时间复杂度为0(log^{n} ).下面我说一下如何二分找到那个关键点位置,当num[mid]对应的值比最左边值大的时候,转折点肯定在右边,那么将left改为mid+1,当num[mid]对应的值比最左边值小的时候,转折点肯定在左边,这时将right = mid-1,当等于的时候,left++,那么出来while循环,找到的就是转折点的位置. 10 | 11 | ###解题思路(3) 12 | 本题采用二分法实现,但是比较挠头的是边界问题,而且元素有重复,相比纯粹递增的数组难度要大得多,要解决这个问题,首先要对所有可能情况进行分类,然后对每种可能的类别进行相应的处理。 13 | 14 | 暂且不考虑nums[mid] = nums[left]的情况,本题大致可以简化为上图两种情况,可能的情况划分出来,那么解决本题就比较容易了: 15 | 16 | 当 nums[mid] = nums[left] 时,这时由于很难判断 target 会落在哪,那么只能采取 left++ 17 | 18 | 当 nums[mid] > nums[left] 时,这时可以分为两种情况,判断左半部比较简单(如果target不在左边这部分,那么我们是可以直接去掉左边这部分的) 19 | 20 | 详细实现见solution.cpp 21 | 22 | 当 nums[mid] < nums[left] 时,这时可以分为两种情况,判断右半部比较简单(如果target不在右边这部分,那么我们也是可以直接去掉右边这部分的) -------------------------------------------------------------------------------- /84-LargestRectangleInHistogram/solution1.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | int largestRectangleArea(vector& heights) { 4 | //Time Limit Exceeded 5 | int ret = 0; 6 | for(int i=0; i=0 && heights[left_index]>=heights[i]) left_index--; 10 | while(right_index=heights[i]) right_index++; 11 | ret = max(ret, heights[i]*(right_index - left_index - 1)); 12 | } 13 | return ret; 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /84-LargestRectangleInHistogram/solution2.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | int largestRectangleArea(vector& heights) { 4 | //solution 2: 9ms (beats 93.96%) 5 | if(heights.size()==0) return 0; 6 | stack index_stack; 7 | int ret = 0; 8 | for(int i=0; i= heights[i]){ 10 | int index = index_stack.top(); 11 | index_stack.pop(); 12 | ret = max(ret, heights[index]*(i-(index_stack.empty()? 0 : index_stack.top()+1))); 13 | } 14 | index_stack.push(i); 15 | } 16 | 17 | while(!index_stack.empty()){ 18 | int index = index_stack.top(); 19 | index_stack.pop(); 20 | int right = heights.size(); 21 | int left = index_stack.empty()? 0 : index_stack.top()+1; 22 | ret = max(ret, heights[index]*(right-left)); 23 | } 24 | return ret; 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /84-LargestRectangleInHistogram/solution3.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | int largestRectangleArea(vector& heights) { 4 | // solution 3: push a sentinel node back into the end of heights to make the code logic more concise. 5 | int ret = 0; 6 | heights.emplace_back(0); 7 | stack index_stack; 8 | for(int i = 0; i < heights.size(); i++) 9 | { 10 | while(!index_stack.empty() && heights[index_stack.top()] >= heights[i]) 11 | { 12 | int index = index_stack.top(); 13 | index_stack.pop(); 14 | ret = max(ret, heights[index]*(i-(index_stack.empty() ? 0 : index_stack.top()+1))); 15 | } 16 | index_stack.push(i); 17 | } 18 | return ret; 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /87-ScrambleString/README.md: -------------------------------------------------------------------------------- 1 | ## 题目分析 2 | 3 | 给定一个字符串,将其表示成二叉树之后,问是否可以通过交换节点顺序,变成另外一个二叉树。 4 | 5 | ### 解题思路 6 | 7 | 假设已经知道了这棵二叉树,我们是否可以判断其是否可以通过旋转变成另外一颗二叉树呢? 8 | 9 | 考虑两棵树根节点的两个儿子(l,r),(l',r'),显然成立的条件要么是l-l', r-r'是相似的,要么l-r',r-l'是相似的。这样我们就把问题转移到了一个更小的子问题上了,递归下去就好了。 10 | 11 | 那么问题就成了怎样找到这棵二叉树呢?对于二叉树来说,重要的是中间那个唯一的分界点,显然,我们只要去枚举中间那个分界点就可以解决这个问题了。 12 | -------------------------------------------------------------------------------- /87-ScrambleString/solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution 2 | { 3 | public: 4 | bool isScramble(string s1, string s2) 5 | { 6 | if(s1==s2) return true; 7 | if(s1.size()!=s2.size()) return false; 8 | vector count(26); 9 | for(auto v:s1) 10 | ++count[v-'a']; 11 | for(auto v:s2) 12 | --count[v-'a']; 13 | for(auto v:count) 14 | if(v) 15 | return false; 16 | for(int i=1;i 格雷码:在一组数的编码中,若任意两个相邻的代码只有一位二进制数不同,则称这种编码为格雷码(Gray Code) 4 | ### 解题思路: 5 | 简单的模拟生成题,由于格雷码要求相邻的数字有且仅有一位不同,先考虑如果当前已经有不大于`N-1`位的合法格雷码序列 6 | > 如现在已经有不大于2位的合法格雷码序列——000、001、011、010 7 | 8 | 最简单直接的方式便是将上述序列复制一遍,将其第N位全置为1。则在上述所得到的副本内部,彼此之前满足相邻数字有且仅有一位不同的要求。而后考虑将副本与原先的进行拼接,发现副本最尾端的是由原序列最尾端的数字**派生**出来。因此,将副本翻转顺序后和原序列拼接即可获得不大于`N`的合法格雷码序列。 9 | 算法过程如下: 10 | 1. 初始化序列,将0加入其中,i=1。 11 | 2. 生成不大于`i`位的序列,从原序列尾端往前依次取出一数字`x`,令`y = x | (1<N`则结束,否则回到过程2。 13 | 14 | 整体而言,生成每个格雷码仅需要一次运算,因此时间复杂度为O(2^N). 15 | -------------------------------------------------------------------------------- /89-Gray Code/Solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | vector grayCode(int n) { 4 | vector ans; 5 | ans.push_back(0); 6 | if(n==0) return ans; 7 | ans.push_back(1); 8 | int down=1; 9 | for(int T=1; T number; number.clear (); 6 | while (x) { 7 | number.push_back (x%10); 8 | x /= 10; 9 | } 10 | int n = number.size (); 11 | for (int i = 0; i < n/2; i++) { 12 | if (number[i] != number[n-i-1]) return 0; 13 | } 14 | return 1; 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /90-Subsets II/README.md: -------------------------------------------------------------------------------- 1 | ## 题目分析: 2 | 题目提供一组整数的集合,这些整数可能存在重复的现象。现要求得到原集合的各个**不重复**的子集。 3 | 4 | ### 解题思路: 5 | 设到集合中某一个整数x前,先前的元素组合而成的已满足题目要求的子集的集合为V。下面分情况讨论 6 | + 整数x第一次出现: 7 | 整数x如果第一次出现,则和集合V当中的任意一个子集组合成的新的子集集合满足彼此之间互不相同的条件(该点易证,由于原本的子集集合已至少存在一个元素不同,加上一个相同的元素之后,依旧不同)。并将新产生的子集集合并入集合V当中。 8 | + 整数x先前已出现过n(n>0)次: 9 | 若整数x先前已出现过,依旧与集合V当中的任意一个子集组合将会造成重复。假设存在一个不含整数x的集合Y,在第一次x出现时,其便已经与x组合。当x往后再次出现时,若依旧与Y组合,将与前者相重复。 10 | **正解**应为其与集合V之中,存在n个整数x的子集相组合,产生新的存在(n+1)个整数x的子集。 11 | 12 | 思路如上所述,为了优化上述的做法,我们使用`map`容器来存储整数x出现的次数,最后构造集合时,只需将整数x一起处理便可。第二种情况内的寻找含有n个整数x的子集可能较难实现。这里有个小技巧为将含有n个整数x的子集存储在`vector`连续的存储空间内,用头尾指针表示出这段位置,则可方便的取出。 13 | -------------------------------------------------------------------------------- /91.DecodeWays/solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | int numDecodings(string s) { 4 | 5 | int len = s.length(); 6 | if(len == 0 || s[0] == '0') return 0; 7 | 8 | vectordp(len + 1); 9 | dp[0] = 1;dp[1] = 1; 10 | 11 | for(int i = 2; i <= len; i ++ ){ 12 | int value = (s[i - 2] - '0') * 10 + s[i - 1] - '0'; 13 | if(value % 10 == 0) { 14 | int temp = value / 10; 15 | if(temp == 1 || temp == 2) { 16 | dp[i] = dp[i - 2]; 17 | } else { 18 | return 0; 19 | } 20 | } else { 21 | if(value < 10 || value > 26) { 22 | dp[i] = dp[i - 1]; 23 | } else { 24 | dp[i] = dp[i - 1] + dp[i - 2]; 25 | } 26 | } 27 | } 28 | return dp[len]; 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /96-UniqueBinarySearchTrees/96.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leetcode-Tutorial/Tutorial/ef9f3bdaa3d3fcf78f5f266549cad3ee3145844f/96-UniqueBinarySearchTrees/96.cpp -------------------------------------------------------------------------------- /97-isInterleave/README.md: -------------------------------------------------------------------------------- 1 | ##题目分析 2 | 3 | 给出三个串a,b,c,问c是否是串b中插入串a得到的。 4 | 比如,`a="abc"`,`b="def"`,那么`c="adebcf"`就是合法的,可以通过把`"de"`插入到`"ab"`中间,然后把`"f"`插入到`"c"`后面得到串c。 5 | 6 | ##题解分析 7 | 8 | 类似于经典的最公共子序列问题,这题也是用动态规划解决。首先状态设计就是用`DP[i][j]`表示串a下标i**以前**的串和串b下标j**以前**的串已经插入完成是否可以构成c串的前`i+j`位,这是一个布尔型的值。那么转移就很简答了:下一位是c串的`i+j`位,它只可能是a串的第`i`位或者是b串的第`j`位,于是只需要判断`c[i+j]`和`a[i]`或者`b[j]`是否相等便能转移到`DP[i+1][j]`或者`DP[i][j+1]`了。 9 | 10 | 我们还是用题目分析中的三个串作为例子: 11 | 初始化所有的DP值都为flase,`DP[0][0]=1`,那么我们可以通过它转移到`DP[1][0]=true (a[0]==c[0])`,然后接着转移到`DP[1][1]=true (b[0]==c[1])`......最终我们可以得到`DP[3][3]=true`,证明串a和b可以组成串c。 12 | 13 | 时间复杂度: 14 | 过程需要完成一个二维的DP数组,第一维长度等于串a,第二维长度等于串b,故时空复杂度都是`O(nm)`。 15 | 16 | -------------------------------------------------------------------------------- /97-isInterleave/solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | bool isInterleave(string s1, string s2, string s3) { 4 | bool dp[1005][1005]; 5 | int n = s1.length (), m = s2.length (); 6 | if (n+m != s3.length ()) return 0; 7 | if (n == 0 || m == 0) { 8 | return s1+s2 == s3; 9 | } 10 | for (int i = 0; i <= n; i++) for (int j = 0; j <= m; j++) dp[i][j] = 0; 11 | dp[0][0] = 1; 12 | for (int i = 0; i <= n; i++) { 13 | for (int j = 0; j <= m; j++) if (dp[i][j]) { 14 | if (i < n && s1[i] == s3[i+j]) dp[i+1][j] = 1; 15 | if (j < m && s2[j] == s3[i+j]) dp[i][j+1] = 1; 16 | } 17 | } 18 | return dp[n][m]; 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /98-isValidBST/README.md: -------------------------------------------------------------------------------- 1 | ## 题目分析 2 | 3 | 判断一棵树是否是合法二叉搜索树。 4 | 5 | ## 二叉树节点编号 6 | 7 | 我们可以按照先序遍历的顺序给每个节点编号。设置一个全局变量x,初始化为0,每次进入一个新的节点时x+1,这时候x便是当前节点的编号,按照搜索顺序接下来搜索当前节点的左孩子(如果存在),那么便可以知道当前节点左孩子的编号是自己的编号+1。当遍历完左子树回到自身时,因为全局变量每次新遍历一个节点都会+1,所以右孩子的编号便是x+1。 8 | 9 | 伪代码可以写作: 10 | 11 | ``` 12 | dfs (node *u) { 13 | x++; 14 | u的编号cur = x; 15 | u的左孩子编号 cur_left = cur+1; 16 | dfs (u->left) {} 17 | u的右孩子编号 cur_right = x+1; 18 | dfs (u->right) {} 19 | } 20 | ``` 21 | 22 | ## 题解思路 23 | 24 | 问题等价为,判断是否对于二叉树上每个节点都有: 25 | 26 | + 他的左子树上所有节点的值小于自身; 27 | + 他的右子树上所有节点的值大于自身。 28 | 29 | 判断第一点,只需要找到左子树上的最大值,判断是否小于自身;同样的,只需要找到右子树上的最小值,判断是否大于自身。 30 | 至此问题的关键已经抽象出来:找到树上所有节点为根的子树的最大/小值。 31 | 采取递归的策略可以得到`Min[u]=min(Min[u->left],Min[u->right],u->val)`,注意这里的下标要用节点的编号,最大值也同理。 32 | 33 | 上述思想是树上动态规划的思想,处理一类树上的问题,这类问题的一般共性是父节点的答案需要通过孩子节点确定。 34 | 35 | 36 | -------------------------------------------------------------------------------- /99-recoverTree/README.md: -------------------------------------------------------------------------------- 1 | ##题目分析 2 | 3 | 给出一个交换过两个节点val的二叉索引树,将它还原,要求空间复杂度是O(n)。 4 | 5 | ##引理 6 | 7 | 任何一棵二叉索引树都能通过中序遍历的方式产生一个升序数组。比如下面的这棵二叉树: 8 | 9 | ``` 10 | 6 11 | / \ 12 | 4 8 13 | / \ /\ 14 | 2 5 7 9 15 | \ 16 | 3 17 | ``` 18 | 它经过中序遍历就能对应数组`[23456789]` 19 | 20 | ##题解思路 21 | 22 | 通过数-数组的转化,现在问题变成了: 23 | 24 | + 在一个有序数列中交换两个数,要求还原成为原数列。 25 | 26 | 我们发现问题从二维的树降到一维的数列就一下子明朗了。我们只需从左往右找到第一个下标i使得`num[i]>num[i+1]`,再从右往左找到第一个下标j使得`num[j-1]>num[j]`,那么i,j就是原数列交换的两个下标。证明就很简单了,因为原数列升序,所以当`num[i]`和右边的某一个`num[k]`交换后,下标i的数必然比它右边的数大了,下标j也是同理。当然我们在数组中不仅要记录val值,最好再记录下树上的节点指针,这样直接交换两个指针指向节点的值就行了。 27 | 28 | 时空复杂度也是显然的,遍历了树和数组,故时间复杂度是`O(n)`,空间复杂度也是`O(n)`。 29 | 30 | 读者可以试着直接在树上做判断修正,你会发现树上改不仅有很多繁琐的情况,而且有许多的边界需要注意,这也正是我们树转化为数组的目的。 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ###简介 2 | 我们是东北大学ACM校队(NEU-ACM)的一群小伙伴,有些已经退役,有些仍在参加比赛。 3 | Leetcode是国内外计算机专业面试的必备题库,里面的题目相比于一些ACM题目而言,更具有实际上的思维训练效果。 4 | 我们发起这个pr的目的是希望尽到我们人多的优势,来编写一套Leetcode的Tutorial,尽我们的理解,希望把这个Tutorial做的比较详细,比较清晰,以便更多的人的理解,帮助更多的人。 5 | 有任何问题和对哪道题有自己更好的理解或者解题思路,非常欢迎提交Issue一起交流。 6 | --------------------------------------------------------------------------------