├── .DS_Store ├── .gitignore ├── Frequency.md ├── README.md ├── c++ └── datastructure │ └── array │ └── p48_rotate_image.cpp ├── pic ├── .DS_Store ├── p11.png ├── p802.png └── sort.png └── src ├── Main.java ├── MainClass.java ├── bisearch ├── README.md ├── gfunction │ ├── P1011CapacityToShipPackages.java │ ├── P162FindPeakElement.java │ ├── P287FindDuplicateNumber.java │ └── P875SpeedEatingBananas.java ├── kthsmallest │ ├── P378KthSmallestInSortedMatrix.java │ ├── P668KthSmallestInMultiplicationTable.java │ ├── P719KthSmallestPairDistance.java │ └── P786KthSmallestPrimeFraction.java ├── rotated │ ├── P153MinimumInRotatedSortedArray.java │ ├── P154MinimumInRotatedSortedArray2.java │ ├── P33SearchRotatedSortedArray.java │ └── P81SearchRotatedSortedArray2.java ├── twodim │ └── P74Search2DMatrix.java └── usebound │ ├── P34FindArrayElementPosition.java │ ├── P35SearchInsertPosition.java │ ├── P4MedianTwoSortedArrays.java │ ├── P69SqrtX.java │ ├── P704BinarySearch.java │ └── P981TimeBasedKeyValueStore.java ├── contest ├── Ali731.java ├── BiWeeklyContest10.java ├── FallContest2019.java ├── OtherProblem.java ├── Tencent823.java ├── WeeklyContest157.java └── WeeklyContest159.java ├── datastructure ├── P355DesignTwitter.java ├── P820ShortEncodingOfWords.java ├── advanced │ ├── BitSet.java │ ├── BloomFilter.java │ ├── ConsistentHash.java │ ├── P1206DesignSkiplist.java │ └── P208PrefixTreeCalledTrie.java ├── array │ ├── P118PascalTriangle.java │ ├── P189RotateArray.java │ ├── P289LifeGame.java │ ├── P31NextPermutation.java │ ├── P48RotateImage.java │ ├── P54SpiralMatrix.java │ ├── P560SubarraySumEqualsK.java │ ├── P59SpiralMatrix2.java │ ├── P66AddOne.java │ ├── P73SetMatrixZeroes.java │ ├── P80RemoveDup2.java │ ├── P945MakeArrayUnique.java │ └── README.md ├── hash │ ├── P1160WordCharacters.java │ ├── P146LRUCache.java │ ├── P187RepeatedDNASequences.java │ ├── P1TwoSum.java │ ├── P242ValidAnagram.java │ ├── P3LongestStringWithoutRepeating.java │ ├── P41FirstMissingPositive.java │ ├── P460LFUCache.java │ ├── P49GroupAnagrams.java │ ├── P692TopKFrequencyWord.java │ └── README.md ├── heap │ ├── Heap.java │ ├── P215KthLargestElementInArray.java │ ├── P295FindMedianFromDataStream.java │ ├── P347TopKFrequentElements.java │ ├── P407TrappingRainWater2.java │ ├── PriorityQueueUsage.java │ └── README.md ├── monotonicqueue │ ├── MaxInQueue.java │ ├── P239SlidingWindowMaximum.java │ ├── P862ShortestSubarrayWithSumAtLeastK.java │ └── README.md ├── monotonicstack │ ├── P42TrappingRainWater.java │ ├── P496NextGreaterElement1.java │ ├── P503NextGreaterElement2.java │ ├── P739DailyTemperatures.java │ ├── P84LargestRectangleInHistogram.java │ ├── P85MaximalRectangle.java │ ├── P907SumOfSubarrayMinimums.java │ └── README.md ├── stack │ ├── P150PolishNotation.java │ ├── P155MinStack.java │ ├── P20ValidParentheses.java │ ├── P225ImplementStackUsingQueues.java │ ├── P32LongestValidParentheses.java │ ├── README.md │ └── StackPushPopOrder.java └── string │ ├── P125ValidPalindrome.java │ ├── P151ReverseWordsinString.java │ ├── P58LastWord.java │ └── P6ZigZagConversion.java ├── divideconquer ├── InversionCountArray.java ├── MergeSortArray.java ├── P148SortList.java ├── P215KthLargestElementInArray.java ├── P21MergeTwoSortedLists.java ├── P23MergeKSortedLists.java ├── P241DifferentWaysToAddParentheses.java ├── P315CountOfSmallerNumbersAfterSelf.java └── README.md ├── dp ├── chessboard │ ├── P120Triangle.java │ ├── P221MaximalSquare.java │ ├── P576OutOfBoundaryPaths.java │ ├── P62UniquePaths.java │ ├── P63UniquePaths2.java │ ├── P64MinimumPathSum.java │ ├── P688KnightProbabilityInChessboard.java │ ├── P935KnightDialer.java │ └── README.md ├── game │ ├── Lintcode395CoinsLine.java │ ├── P292NimGame.java │ ├── P464CanIWin.java │ ├── P486PredictTheWinner.java │ ├── P877StoneGame.java │ ├── P887SuperEggDrop.java │ └── README.md ├── knapsack │ ├── P1155NumberOfDiceRollsWithTargetSum.java │ ├── P139WordBreak.java │ ├── P322CoinChange.java │ ├── P377CombinationSum4.java │ ├── P416PartitionEqualSubsetSum.java │ ├── P494TargetSum.java │ ├── P518CoinChange2.java │ ├── P813LargestSumOfAverages.java │ └── README.md ├── partition │ ├── P132PalindromePartitioning2.java │ ├── P312BurstBalloons.java │ ├── P375GuessNumberHigherOrLower2.java │ ├── P87ScrambleString.java │ └── README.md ├── seq │ ├── FrogJump2.java │ ├── P139WordBreak.java │ ├── P140WordBreak2.java │ ├── P303RangeSumQueryImmutable.java │ ├── P32LongestValidParentheses.java │ ├── P70ClimbingStairs.java │ ├── P746MinCostClimbingStairs.java │ ├── P91DecodeWays.java │ ├── README.md │ ├── mulstates │ │ ├── P198HouseRobber.java │ │ ├── P213HouseRobber2.java │ │ ├── P337HouseRobber3.java │ │ ├── P740DeleteAndEarn.java │ │ ├── P790DominoandTrominoTiling.java │ │ └── P801MinimumSwapsToMakeSequencesIncreasing.java │ ├── stock │ │ ├── P121BuyAndSellStock.java │ │ ├── P122BuyAndSellStock2.java │ │ ├── P123BuyAndSellStock3.java │ │ ├── P188BuyAndSellStock4.java │ │ ├── P309StockWithCooldown.java │ │ ├── P714StockWithTransactionFee.java │ │ └── README.md │ └── sub_seq │ │ ├── P152MaximumProductSubarray.java │ │ ├── P300LongestIncreasingSubsequence.java │ │ ├── P53MaximumSubarray.java │ │ ├── P5LongestPalindromicSubstring.java │ │ ├── P673NumberOfLongestIncreasingSubsequence.java │ │ └── README.md └── twoseq │ ├── P10RegularExpressionMatching.java │ ├── P1143LongestCommonSubsequence.java │ ├── P115DistinctSubsequences.java │ ├── P44WildcardMatching.java │ ├── P583DeleteOperationforTwoStrings.java │ ├── P712MinimumASCIIDeleteSumForTwoStrings.java │ ├── P718MaximumLengthOfRepeatedSubarray.java │ ├── P72EditDistance.java │ ├── P97InterleavingString.java │ └── README.md ├── graph ├── MinimumSpanTree.java ├── MinimusSpanTree2.java ├── P207CourseSchedule.java ├── P210CourseSchedule2.java ├── P399EvaluateDivision.java ├── P684RedundantConnection.java ├── P721AccountsMerge.java ├── P743NetworkDelayTime.java ├── P785IsGraphBipartite.java ├── P802FindEventualSafeStates.java ├── P827MakingALargeIsland.java ├── README.md ├── ShortestPath.java ├── dfs │ ├── P130SurroundedRegions.java │ ├── P133CloneGraph.java │ ├── P200NumberOfIslands.java │ ├── P695MaxAreaOfIsland.java │ ├── P733FloodFill.java │ └── P841KeysAndRooms.java └── unionfind │ ├── DisjointSet.java │ ├── P547FriendCircles.java │ └── P990SatisfiabilityOfEqualityEquations.java ├── greedy ├── P134GasStation.java ├── P135Candy.java ├── P435NonOverlappingIntervals.java ├── P45JumpGame2.java ├── P55JumpGame.java └── README.md ├── input.txt ├── list ├── fastslow │ ├── P141LinkedListCycle.java │ ├── P142LinkedListCycle2.java │ ├── P160IntersectionTwoList.java │ └── P234PalindromeLinkedList.java ├── manip │ ├── P147InsertionSortList.java │ ├── P2AddTwoNumbers.java │ ├── P445AddTwoNumbers2.java │ └── P707DesignLinkedList.java ├── merge │ ├── P148SortList.java │ ├── P21MergeTwoSortedLists.java │ └── P23MergeKSortedLists.java ├── remove │ ├── P19RemoveNthNodeFromEnd.java │ ├── P203RemoveListElements.java │ ├── P82RemoveDuplicatesFromSortedList2.java │ └── P83RemoveDuplicatesFromSortedList.java ├── rotate │ ├── P206ReverseList.java │ ├── P24SwapNodesinPairs.java │ ├── P25KGroupReverse.java │ ├── P61RotateList.java │ └── P92ReverseList2.java └── split │ ├── P138RandomPointerListCopy.java │ ├── P328OddEvenNode.java │ └── P86PartitionList.java ├── math ├── README.md ├── bit │ ├── P136_137_260SingleNumber1_2_3.java │ ├── P190ReverseBits.java │ ├── P191NumberOf1Bits.java │ ├── P201BitwiseANDOfNumbersRange.java │ ├── P231PowerOfTwo.java │ └── P268MissingNumber.java ├── convert │ ├── P13RomanToInteger.java │ ├── P166FractionToRecurringDecimal.java │ ├── P29DivideTwoIntegers.java │ ├── P43MultiplyStrings.java │ ├── P60PermutationSequence.java │ ├── P7ReverseInteger.java │ ├── P8StringToInteger.java │ └── Subtraction36Base.java ├── geometry │ ├── P149MaxPointsOnLine.java │ ├── P223RectangleArea.java │ └── P892SurfaceAreaOf3DShapes.java ├── numbertheory │ ├── GCD_LCM.java │ ├── P1071GCDString.java │ ├── P204CountPrimes.java │ └── P914DeckOfCards.java ├── other │ ├── P169MajorityElement.java │ ├── P171ExcelSheetColumnNumber.java │ ├── P229MajorityElement2.java │ ├── P365WaterJugProblem.java │ ├── P50Pow.java │ ├── P78Subsets.java │ ├── P793PreimageSizeFactorialZeroesFunction.java │ └── P89GrayCode.java ├── sampling │ ├── P382LinkedListRandomNode.java │ ├── P398RandomPickIndex.java │ ├── P470Rand10UsingRand7.java │ ├── P478GenerateRandomPointInCircle.java │ ├── P519RandomFlipMatrix.java │ ├── P528RandomPickWithWeight.java │ ├── P710RandomPickWithBlacklist.java │ └── Shuffle.java └── special │ ├── P172FactorialTrailingZeroes.java │ ├── P202HappyNumber.java │ ├── P263UglyNumber.java │ ├── P264UglyNumber2.java │ ├── P279PerfectSquares.java │ ├── P65ValidNumber.java │ └── P9PalindromeNumber.java ├── search ├── README.md ├── backtracking │ ├── P212WordSearch2.java │ ├── P301RemoveInvalidParentheses.java │ ├── P37SudokuSolver.java │ ├── P51NQueens.java │ ├── P79WordSearch.java │ ├── P93RestoreIPAddresses.java │ └── README.md ├── bfs │ ├── P1162FarLand.java │ ├── P127WordLadder.java │ ├── P433MinimumGeneticMutation.java │ ├── P505TheMaze2.java │ ├── P542Matrix01.java │ ├── P675CutOffTreesforGolfEvent.java │ ├── P752OpenTheLock.java │ ├── P934ShortestBridge.java │ └── P994RottingOrange.java ├── combination │ ├── P17LetterCombinations.java │ ├── P216CombinationSum3.java │ ├── P22GenerateParenthesesCombination.java │ ├── P39CombinationSum.java │ ├── P40CombinationSum2.java │ └── P77Combination.java ├── partition │ ├── P131PalindromePartitioning.java │ └── P698PartitionToKEqualSubsets.java ├── permutation │ ├── P46Permutations.java │ ├── P47Permutations2.java │ ├── P784LetterCasePermutation.java │ └── P996SquarefulArraysPermutation.java └── subset │ ├── P78Subsets.java │ ├── P842SplitArrayIntoFibonacciSequence.java │ └── P90Subsets2.java ├── sort ├── P1122RelativeSortArray.java ├── P164MaximumGap.java ├── P179LargestNumber.java ├── P220ContainsDuplicate3.java ├── P242ValidAnagram.java ├── P274HIndex.java ├── P324WiggleSort2.java ├── P350IntersectionOfTwoArrays2.java ├── P386LexicographicalNumbers.java ├── P435NonOverlappingIntervals.java ├── P56MergeIntervals.java ├── P57InsertInterval.java ├── P969PancakeSorting.java ├── README.md └── SortAlgorithms.java ├── tree ├── P508MostFrequentSubtreeSum.java ├── P979DistributeCoinsInBinaryTree.java ├── README.md ├── bst │ ├── P1038BSTToGreaterSumTree.java │ ├── P108ConvertSortedArrayToBalancedBST.java │ ├── P109ConvertSortedListToBST.java │ ├── P230KthSmallestElementInBST.java │ ├── P450DeleteNodeInBST.java │ ├── P501FindModeInBST.java │ ├── P530MinimumAbsoluteDifferenceInBST.java │ ├── P700SearchInBST.java │ ├── P701InsertIntoBST.java │ ├── P95UniqueBST2.java │ ├── P96UniqueBST.java │ ├── P98ValidateBST.java │ └── P99RecoverBST.java ├── depth │ ├── P104MaxDepthBinaryTree.java │ ├── P110BalanceBinaryTree.java │ ├── P111MinDepthBinaryTree.java │ └── P559MaxDepthNnaryTree.java ├── level │ ├── P102BinaryTreeLevelOrderTraversal.java │ ├── P103ZigzagLevelOrderTraversal.java │ ├── P107BinaryTreeLevelOrderTraversal2.java │ ├── P116PopulatingNextPointers.java │ └── P429NaryTreeLevelOrderTraversal.java ├── path │ ├── P112PathSum.java │ ├── P113PathSum2.java │ ├── P124BinaryTreeMaximumPathSum.java │ ├── P129SumRootToLeafNumbers.java │ ├── P235LowestCommonAncestorOfBST.java │ ├── P236LowestCommonAncestorOfBinaryTree.java │ ├── P257BinaryTreeAllPaths.java │ ├── P437PathSum3.java │ ├── P543DiameterPathOfBinaryTree.java │ ├── P666PathSum4.java │ └── P687LongestUnivaluePath.java ├── pruning │ ├── P669TrimBinarySearchTree.java │ └── P814BinaryTreePruning.java ├── serialize │ └── P297SerializeAndDeserializeBinaryTree.java ├── structure │ ├── P100SameTree.java │ ├── P101SymmetricTree.java │ ├── P226InvertBinaryTree.java │ ├── P572SubtreeOfAnotherTree.java │ └── P872LeafSimilarTrees.java └── traversal │ ├── P105ConstructFromPreorderInorder.java │ ├── P106ConstructFromInorderPostorder.java │ ├── P114FlattenTree.java │ ├── P144BinaryTreePreorderTraversal.java │ ├── P145BinaryTreePostorderTraversal.java │ ├── P199BinaryTreeRightSideView.java │ ├── P337HouseRobber3.java │ ├── P426ConvertBST2BiList.java │ ├── P589NaryTreePreorderTraversal.java │ ├── P590NaryTreePostorderTraversal.java │ ├── P889ConstructFromPreorderPostorder.java │ └── P94BinaryTreeInorderTraversal.java ├── twopointer ├── fastslow │ ├── P141LinkedListCycle.java │ ├── P142LinkedListCycle2.java │ ├── P160IntersectionTwoList.java │ └── P234PalindromeLinkedList.java ├── seq │ ├── LCP18BreakfastNumber.java │ ├── P11ContainerWithMostWater.java │ ├── P15ThreeSum.java │ ├── P167TwoSum2.java │ ├── P16ThreeSumClosest.java │ ├── P18FourSum.java │ ├── P240Search2DMatrix2.java │ ├── P26RemoveDuplicatesfromSortedArray.java │ ├── P27RemoveElement.java │ ├── P283MoveZeroes.java │ ├── P28StrStr.java │ ├── P32LongestValidParentheses.java │ ├── P3LongestSubstringWithoutRepeatingCharacters.java │ ├── P42TrappingRainWater.java │ ├── P75SortColors.java │ └── P88MergeSortedArray.java └── slidewindow │ ├── P209MinimumSizeSubarraySum.java │ ├── P76MinimumWindowSubstring.java │ └── PP1052GrumpyBookstoreOwner.java └── util ├── IOUtil.java ├── ListNode.java ├── ListUtil.java ├── TreeNode.java └── TreeUtil.java /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caipengbo/LeetCode/ccd4626f668c706841039eb52ded86583940e101/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | .classpath 3 | .project 4 | .settings/ 5 | bin/ 6 | test/ 7 | src/Contest.java 8 | .DS_Store 9 | -------------------------------------------------------------------------------- /c++/datastructure/array/p48_rotate_image.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Myth on 11/26/2020. 3 | // 4 | #include 5 | #include 6 | 7 | using namespace std; 8 | 9 | class P48 { 10 | public: 11 | void rotate(vector> &matrix) { 12 | int n = matrix.size(); 13 | // 矩阵转置 14 | for (int i = 0; i < n; ++i) { 15 | for (int j = i; j < n; ++j) { 16 | swap(matrix[i][j], matrix[j][i]); 17 | } 18 | } 19 | // 矩阵左右对称 沿中心对称轴 20 | for (int i = 0; i < n; ++i) { 21 | for (int j = 0; j < n / 2; j++) { 22 | swap(matrix[i][j], matrix[i][n - 1 - j]); 23 | } 24 | } 25 | } 26 | }; 27 | 28 | int main() { 29 | P48 p48; 30 | vector> matrix = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}; 31 | p48.rotate(matrix); 32 | int n = matrix.size(); 33 | for (int i = 0; i < n; ++i) { 34 | for (int j = 0; j < n; ++j) { 35 | cout << matrix[i][j] << " "; 36 | } 37 | cout << endl; 38 | } 39 | } -------------------------------------------------------------------------------- /pic/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caipengbo/LeetCode/ccd4626f668c706841039eb52ded86583940e101/pic/.DS_Store -------------------------------------------------------------------------------- /pic/p11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caipengbo/LeetCode/ccd4626f668c706841039eb52ded86583940e101/pic/p11.png -------------------------------------------------------------------------------- /pic/p802.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caipengbo/LeetCode/ccd4626f668c706841039eb52ded86583940e101/pic/p802.png -------------------------------------------------------------------------------- /pic/sort.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caipengbo/LeetCode/ccd4626f668c706841039eb52ded86583940e101/pic/sort.png -------------------------------------------------------------------------------- /src/MainClass.java: -------------------------------------------------------------------------------- 1 | import util.IOUtil; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.IOException; 5 | 6 | import static util.IOUtil.stringToIntegerArray; 7 | 8 | /** 9 | * Title: 10 | * Desc: 11 | * Created by Myth on 7/16/2019 12 | */ 13 | public class MainClass { 14 | public static void main(String[] args) throws IOException { 15 | BufferedReader in = IOUtil.getBufferReader(); 16 | String line; 17 | while ((line = in.readLine()) != null) { 18 | System.out.println("Hello"); 19 | int[] candidates = stringToIntegerArray(line); 20 | line = in.readLine(); 21 | int target = Integer.parseInt(line); 22 | System.out.print(target); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/bisearch/gfunction/P162FindPeakElement.java: -------------------------------------------------------------------------------- 1 | package bisearch.gfunction; 2 | 3 | /** 4 | * Title: 162. 寻找峰值(找局部最大值和852题一模一样) 5 | * Desc: 峰值元素是指其值大于左右相邻值的元素。 6 | * 7 | * 给定一个输入数组 nums,其中 nums[i] ≠ nums[i+1],找到峰值元素并返回其索引。 8 | * 9 | * 数组可能包含多个峰值,在这种情况下,返回任何一个峰值所在位置即可。 10 | * 11 | * 你可以假设 nums[-1] = nums[n] = -∞。 12 | * 13 | * Created by Myth on 9/1/2019 14 | */ 15 | public class P162FindPeakElement { 16 | public int findPeakElement(int[] nums) { 17 | if (nums.length == 0) return -1; 18 | if (nums.length == 1) return 0; 19 | int l = 0, r = nums.length - 1; 20 | int m; 21 | while (l < r) { 22 | m = l + (r - l) / 2; 23 | if (nums[m] > nums[m+1]) r = m; 24 | else l = m + 1; 25 | } 26 | return l; 27 | } 28 | 29 | public static void main(String[] args) { 30 | P162FindPeakElement p162 = new P162FindPeakElement(); 31 | int[] nums1 = {1, 2, 3, 1}; 32 | int[] nums2 = {1, 2, 1, 3, 5, 6, 4}; 33 | System.out.println(p162.findPeakElement(nums1)); 34 | System.out.println(p162.findPeakElement(nums2)); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/bisearch/gfunction/P287FindDuplicateNumber.java: -------------------------------------------------------------------------------- 1 | package bisearch.gfunction; 2 | 3 | /** 4 | * Title: 287. 寻找重复数 5 | * Desc: 给定一个包含 n + 1 个整数的数组 nums,其数字都在 1 到 n 之间(包括 1 和 n),可知至少存在一个重复的整数。 6 | * 假设只有一个重复的整数,找出这个重复的数。 7 | * 说明: 8 | * 不能更改原数组(假设数组是只读的)。 9 | * 只能使用额外的 O(1) 的空间。 10 | * 时间复杂度小于 O(n2) 。 11 | * 数组中只有一个重复的数字,但它可能不止重复出现一次。 12 | * 13 | * Created by Myth on 8/28/2019 14 | */ 15 | public class P287FindDuplicateNumber { 16 | // 从1 - n 二分缩短区间,然后统计数组中小于mid的数目,如果大于mid,说明[1,mid]有重复 17 | public int findDuplicate(int[] nums) { 18 | int lo = 1, hi = nums.length; 19 | int mid, count; 20 | while (lo < hi) { 21 | mid = lo + (hi - lo) / 2; 22 | count = 0; 23 | for (int i = 0; i < nums.length; i++) { 24 | if (nums[i] <= mid) count++; 25 | } 26 | if (count > mid) hi = mid; 27 | else lo = mid + 1; 28 | } 29 | return lo; 30 | } 31 | 32 | public static void main(String[] args) { 33 | P287FindDuplicateNumber p287 = new P287FindDuplicateNumber(); 34 | int[] nums1 = {1,3,4,2,2}; 35 | int[] nums2 = {3,1,3,4,2}; 36 | System.out.println(p287.findDuplicate(nums1)); 37 | System.out.println(p287.findDuplicate(nums2)); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/bisearch/gfunction/P875SpeedEatingBananas.java: -------------------------------------------------------------------------------- 1 | package bisearch.gfunction; 2 | 3 | 4 | /** 5 | * Title: 875. 在 H 小时内吃掉所有香蕉的最小速度 K 6 | * Desc: 珂珂喜欢吃香蕉。这里有 N 堆香蕉,第 i 堆中有 piles[i] 根香蕉。警卫已经离开了,将在 H 小时后回来。 7 | * 珂珂可以决定她吃香蕉的速度 K (单位:根/小时)。每个小时,她将会选择一堆香蕉,从中吃掉 K 根。 8 | * 如果这堆香蕉少于 K 根,她将吃掉这堆的所有香蕉,然后这一小时内不会再吃更多的香蕉。珂珂喜欢慢慢吃,但仍然想在警卫回来前吃掉所有的香蕉。 9 | * 10 | * 返回她可以在 H 小时内吃掉所有香蕉的最小速度 K(K 为整数)。 11 | * https://leetcode-cn.com/problems/koko-eating-bananas/ 12 | * Created by Myth on 8/30/2019 13 | */ 14 | public class P875SpeedEatingBananas { 15 | // 16 | public int minEatingSpeed(int[] piles, int H) { 17 | int lo = 1, hi = piles[0]; 18 | for (int i = 0; i < piles.length; i++) { 19 | if (hi < piles[i]) hi = piles[i]; 20 | } 21 | int mid, count; 22 | while (lo < hi) { 23 | mid = lo + (hi - lo) / 2; 24 | count = 0; 25 | for (int i = 0; i < piles.length; i++) { 26 | if (piles[i] % mid == 0) count += piles[i] / mid; 27 | else count += piles[i] / mid + 1; 28 | } 29 | if (count <= H) hi = mid; 30 | else lo = mid + 1; 31 | } 32 | return lo; 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/bisearch/kthsmallest/P668KthSmallestInMultiplicationTable.java: -------------------------------------------------------------------------------- 1 | package bisearch.kthsmallest; 2 | 3 | /** 4 | * Title: 668. 乘法表中第k小的数 5 | * Desc: https://leetcode-cn.com/problems/kth-smallest-number-in-multiplication-table/ 6 | * 7 | * 和 P378 一样 8 | * Created by Myth on 8/28/2019 9 | */ 10 | public class P668KthSmallestInMultiplicationTable { 11 | public int findKthNumber(int m, int n, int k) { 12 | int lo = 1, hi = m * n + 1; 13 | int mid, count; 14 | while (lo < hi) { 15 | mid = lo + (hi - lo) / 2; 16 | count = 0; 17 | for (int i = 1; i <= m; i++) { 18 | count += (mid/i > n ? n : mid/i); 19 | } 20 | if (count >= k) hi = mid; 21 | else lo = mid + 1; 22 | } 23 | return lo; 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/bisearch/rotated/P153MinimumInRotatedSortedArray.java: -------------------------------------------------------------------------------- 1 | package bisearch.rotated; 2 | 3 | /** 4 | * Title: 153. 寻找旋转排序数组中的最小值 5 | * Desc: 假设按照升序排序的数组在预先未知的某个点上进行了旋转。 6 | * ( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。 7 | * 请找出其中最小的元素。你可以假设数组中不存在重复元素。 8 | * Created by Myth on 8/30/2019 9 | */ 10 | public class P153MinimumInRotatedSortedArray { 11 | // 旋转点就是最小值 12 | public int findMin(int[] nums) { 13 | int lo = 0, hi = nums.length - 1; 14 | int mid; 15 | while (lo < hi) { 16 | mid = lo + (hi - lo) / 2; 17 | if (nums[mid] < nums[hi]) hi = mid; 18 | else lo = mid + 1; 19 | } 20 | return nums[lo]; // 得到的是 分界点 p, 左侧是有序的(0, p-1)有序, 右侧(p, len-1)也是有序的 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/bisearch/rotated/P154MinimumInRotatedSortedArray2.java: -------------------------------------------------------------------------------- 1 | package bisearch.rotated; 2 | 3 | /** 4 | * Title: 153. 寻找旋转排序数组中的最小值 II 5 | * Desc: 153题的拓展问题 6 | * Created by Myth on 8/30/2019 7 | */ 8 | public class P154MinimumInRotatedSortedArray2 { 9 | // 旋转点就是最小值 10 | public int findMin(int[] nums) { 11 | int lo = 0, hi = nums.length - 1; 12 | int mid; 13 | while (lo < hi) { 14 | mid = lo + (hi - lo) / 2; 15 | if (nums[mid] < nums[hi]) hi = mid; 16 | else if (nums[mid] > nums[hi]) lo = mid + 1; 17 | else hi--; 18 | } 19 | return nums[lo]; // 得到的是 分界点 p, 左侧是有序的(0, p-1)有序, 右侧(p, len-1)也是有序的 20 | } 21 | 22 | public static void main(String[] args) { 23 | int[] nums1 = {1,2,3}; 24 | int[] nums2 = {1,1,0,1}; 25 | int[] nums3 = {1,0,1,1,1}; 26 | int[] nums4 = {1,1,1,1}; 27 | P154MinimumInRotatedSortedArray2 p154 = new P154MinimumInRotatedSortedArray2(); 28 | System.out.println(p154.findMin(nums1)); 29 | System.out.println(p154.findMin(nums2)); 30 | System.out.println(p154.findMin(nums3)); 31 | System.out.println(p154.findMin(nums4)); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/bisearch/twodim/P74Search2DMatrix.java: -------------------------------------------------------------------------------- 1 | package bisearch.twodim; 2 | 3 | /** 4 | * Title: 74. 搜索二维矩阵 (双指针包内240题 矩阵每行每列都是升序) 5 | * Desc: 编写一个高效的算法来判断 m x n 矩阵中,是否存在一个目标值。该矩阵具有如下特性: 6 | * - 每行中的整数从左到右按升序排列。 7 | * - 每行的第一个整数大于前一行的最后一个整数。 8 | * 9 | * Created by Myth on 8/25/2019 10 | */ 11 | public class P74Search2DMatrix { 12 | // 难点: 如何将 2d -> 1d : 得到 位置的数字 然后转换成 坐标就可以了 13 | public boolean searchMatrix(int[][] matrix, int target) { 14 | if (matrix.length == 0 || matrix[0].length == 0) return false; 15 | int m = matrix.length, n = matrix[0].length; 16 | int l = 0, r = m * n; 17 | int mid, i, j; 18 | while (l < r) { 19 | mid = l + (r - l) / 2; 20 | i = mid / n; 21 | j = mid % n; 22 | if (matrix[i][j] < target) l = mid + 1; 23 | else r = mid; 24 | } 25 | if (l >= m * n) return false; 26 | i = l / n; 27 | j = l % n; 28 | return matrix[i][j] == target; 29 | } 30 | 31 | public static void main(String[] args) { 32 | P74Search2DMatrix p74 = new P74Search2DMatrix(); 33 | int[][] mat1 = {{1}}; 34 | int[][] mat2 = {{}}; 35 | int[][] mat3 = {{1}, {2}, {3}, {4}}; 36 | int[][] mat4 = {{1, 2, 3, 4, 5}}; 37 | int[][] mat5 = {{1,3,5,7},{10,11,16,20},{23,30,34,50}}; 38 | // System.out.println(p74.searchMatrix(mat4, 3)); 39 | System.out.println(p74.searchMatrix(mat5, 35)); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/bisearch/usebound/P35SearchInsertPosition.java: -------------------------------------------------------------------------------- 1 | package bisearch.usebound; 2 | 3 | /** 4 | * Title: 35. 搜索插入位置 5 | * Desc: 给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。 6 | * 你可以假设数组中无重复元素。 7 | * Created by Myth on 8/1/2019 8 | */ 9 | // 典型的二分查找 10 | public class P35SearchInsertPosition { 11 | // 找到 12 | public int searchInsert(int[] nums, int target) { 13 | int l = 0, r = nums.length; 14 | int m; 15 | while (l < r) { 16 | // m = l + (r - l) / 2; 17 | m = l + ((r - l) >> 1); 18 | // 注意此处为何不是 <= ? 19 | if (nums[m] < target) l = m + 1; // 右侧 20 | else r = m; // 左侧 21 | } 22 | return l; 23 | } 24 | 25 | public static void main(String[] args) { 26 | P35SearchInsertPosition p35 = new P35SearchInsertPosition(); 27 | int[] nums = {1,3,5,6}; 28 | int target1 = 5; 29 | int target2 = 2; 30 | int target3 = 7; 31 | int target4 = 0; 32 | // System.out.println(p35.searchInsert(nums, target1)); 33 | // System.out.println(p35.searchInsert(nums, target2)); 34 | System.out.println(p35.searchInsert(nums, target3)); 35 | // System.out.println(p35.searchInsert(nums, target4)); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/bisearch/usebound/P69SqrtX.java: -------------------------------------------------------------------------------- 1 | package bisearch.usebound; 2 | 3 | /** 4 | * Title: 69. x 的平方根 5 | * Desc: 实现 int sqrt(int x) 函数。 6 | * 计算并返回 x 的平方根,其中 x 是非负整数。 7 | * 由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去。 8 | * 注意: 9 | * 整数的溢出 需要强制转化成 Long 10 | * Created by Myth on 8/24/2019 11 | */ 12 | public class P69SqrtX { 13 | public int mySqrt(int x) { 14 | int l = 0; 15 | int r = x / 2 + 1; 16 | int m; 17 | while (l < r) { 18 | m = l + (r - l) / 2; 19 | // System.out.println(m); 20 | if ((long)m*m <= x) l = m + 1; 21 | else r = m; 22 | } 23 | // System.out.println(l); 24 | if ((long)l * l > x) return l - 1; 25 | else return l; 26 | } 27 | 28 | public static void main(String[] args) { 29 | P69SqrtX p69 = new P69SqrtX(); 30 | // System.out.println(p69.mySqrt(1)); 31 | // System.out.println(p69.mySqrt(2)); 32 | // System.out.println(p69.mySqrt(3)); 33 | // System.out.println(p69.mySqrt(4)); 34 | // System.out.println(p69.mySqrt(8)); 35 | // System.out.println(p69.mySqrt(9)); 36 | // System.out.println(p69.mySqrt(100)); 37 | // System.out.println(p69.mySqrt(101)); 38 | System.out.println(p69.mySqrt(2147395599)); // 46339 39 | System.out.println(p69.mySqrt(2147395600)); // 46340 40 | System.out.println(p69.mySqrt(2147483647)); // 46340 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/contest/OtherProblem.java: -------------------------------------------------------------------------------- 1 | package contest; 2 | 3 | /** 4 | * Title: 一些有意思的题目(个人手写、收藏) 5 | * Desc: 6 | * Created by Myth-Lab on 10/15/2019 7 | */ 8 | public class OtherProblem { 9 | // 给出高山的坐标,求第一个能看到最高山峰的坐标(斜率,视野) 10 | // 测试数据 double[][] mountain = {{535, 347.9}, {1513, 444.4}, {2404.1, 690.3}, {3005.2, 437.1},{3762, 393.7}, {4602.6, 832.5}}; Ans: 2404.1 11 | public double firstHigh(double[][] mountain) { 12 | //找到最高 13 | int maxHighX = 0; 14 | for (int i = 0; i < mountain.length; i++) { 15 | if (mountain[maxHighX][1] < mountain[i][1]) maxHighX = i; 16 | } 17 | if (maxHighX == 0) return mountain[0][1]; 18 | double delta = Float.MAX_VALUE; 19 | int ret = 0; 20 | for (int i = maxHighX-1; i >= 0; i--) { 21 | double temp = (mountain[maxHighX][1] - mountain[i][1]) / (mountain[maxHighX][0] - mountain[i][0]); 22 | if (temp < delta) { 23 | delta = temp; 24 | ret = i; 25 | } 26 | } 27 | return mountain[ret][0]; 28 | } 29 | 30 | public static void main(String[] args) { 31 | 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/contest/Tencent823.java: -------------------------------------------------------------------------------- 1 | package contest; 2 | 3 | 4 | /** 5 | * Title: 腾讯2020年8月23日笔试题 6 | * Desc: 7 | * Created by Myth-MBP on 24/08/2020 in VSCode 8 | */ 9 | 10 | public class Tencent823 { 11 | 12 | // 宽为1高度为arr[i]的木板,使用长宽都为1的刷子进行粉刷,可以竖着、横着刷,但是必须保证每次刷子都在木板上,不在木板上被认为是多次 13 | // arr = {2,1,2} 横着第一行刷1次,第二行刷2次 14 | 15 | 16 | } -------------------------------------------------------------------------------- /src/datastructure/array/P118PascalTriangle.java: -------------------------------------------------------------------------------- 1 | package datastructure.array; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Arrays; 5 | import java.util.List; 6 | 7 | 8 | /** 9 | * Title: 118. 杨辉三角 119. 杨辉三角 第K行 10 | * Desc: 生成杨辉三角 11 | * Created by Myth-MBP on 31/08/2020 in VSCode 12 | */ 13 | 14 | public class P118PascalTriangle { 15 | public List> generate(int numRows) { 16 | List> ret = new ArrayList<>(); 17 | ret.add(new ArrayList(Arrays.asList(1))); 18 | for (int i = 1; i < numRows; i++) { 19 | List last = ret.get(i-1); 20 | List cur = new ArrayList<>(i+1); 21 | for (int j = 0; j < last.size(); j++) { 22 | int a = j == 0 ? 0 : last.get(j-1); 23 | cur.set(j, a + last.get(j)); 24 | } 25 | ret.add(cur); 26 | } 27 | return ret; 28 | } 29 | // 获得第K行 30 | public List getRow(int k) { 31 | List res = new ArrayList<>(k + 1); 32 | for (int i = 0; i <= k; i++) { 33 | res.add(1); 34 | for (int j = i - 1; j > 0; j--) { 35 | // 当前+前一个 36 | res.set(j, res.get(j) + res.get(j - 1)); 37 | } 38 | } 39 | return res; 40 | } 41 | 42 | } -------------------------------------------------------------------------------- /src/datastructure/array/P189RotateArray.java: -------------------------------------------------------------------------------- 1 | package datastructure.array; 2 | 3 | 4 | /** 5 | * Title: 189. 旋转数组 6 | * Desc: 给定一个数组,将数组中的元素向右移动 k 个位置,其中 k 是非负数。 7 | * Created by Myth-MBP on 07/07/2020 in VSCode 8 | */ 9 | public class P189RotateArray { 10 | // 原地算法 11 | public void rotate(int[] nums, int k) { 12 | int n = nums.length; 13 | if (k >= n) k = k % n; 14 | if (k == 0) return; 15 | reverse(nums, 0, n-k-1); 16 | reverse(nums, n-k, n-1); 17 | reverse(nums, 0, n); 18 | } 19 | private void reverse(int[] nums, int i, int j) { 20 | while (i < j) { 21 | swap(nums, i, j); 22 | i++; 23 | j--; 24 | } 25 | } 26 | private void swap(int[] nums, int i, int j) { 27 | if (i >= j) return; 28 | int temp = nums[i]; 29 | nums[i] = nums[j]; 30 | nums[j] = temp; 31 | } 32 | 33 | 34 | } -------------------------------------------------------------------------------- /src/datastructure/array/P48RotateImage.java: -------------------------------------------------------------------------------- 1 | package datastructure.array; 2 | 3 | 4 | /** 5 | * Title: 48. 旋转图像 6 | * Desc: 给定一个 n × n 的二维矩阵表示一个图像。原地 将图像顺时针旋转 90 度。 7 | * Created by Myth-MBP on 13/05/2020 in VSCode 8 | */ 9 | 10 | public class P48RotateImage { 11 | // 使用两次旋转 12 | // 1. 右上角 左下角旋转 2. 然后左右旋转 13 | public void rotate(int[][] matrix) { 14 | int n = matrix.length; 15 | // 右上角 左下角旋转 16 | for (int i = 0; i < n; i++) { 17 | for (int j = 0; j < i; j++) { 18 | swap(matrix, i, j, j, i); 19 | } 20 | } 21 | // 左右旋转 22 | for (int i = 0; i < n; i++) { 23 | for (int j = 0; j < n/2; j++) { 24 | swap(matrix, i, j, i, n-1-j); 25 | } 26 | } 27 | 28 | for (int i = 0; i < n; i++) { 29 | for (int j = 0; j < n; j++) { 30 | System.out.print(matrix[i][j] + " "); 31 | } 32 | System.out.println(); 33 | } 34 | 35 | } 36 | private void swap(int[][] matrix, int i1, int j1, int i2, int j2) { 37 | int temp = matrix[i1][j1]; 38 | matrix[i1][j1] = matrix[i2][j2]; 39 | matrix[i2][j2] = temp; 40 | } 41 | 42 | public static void main(String[] args) { 43 | int[][] matrix = {{1,2,3,4},{4,5,6,7}, {7,8,9,10}, {11,12,13,14}}; 44 | P48RotateImage p48 = new P48RotateImage(); 45 | p48.rotate(matrix); 46 | StringBuilder sb = new StringBuilder(); 47 | } 48 | 49 | } -------------------------------------------------------------------------------- /src/datastructure/array/P59SpiralMatrix2.java: -------------------------------------------------------------------------------- 1 | package datastructure.array; 2 | 3 | /** 4 | * Title: 59. 打印矩阵2(和54题类似) 5 | * Desc: 给定一个正整数 n,生成一个包含 1 到 n2 所有元素,且元素按顺时针顺序螺旋排列的正方形矩阵。 6 | * Created by Myth-PC on 05/02/2020 in VSCode 7 | */ 8 | public class P59SpiralMatrix2 { 9 | public int[][] generateMatrix(int n) { 10 | int[][] array = new int[n][n]; 11 | int left = 0, right = n-1, top = 0, bottom = n-1; 12 | n = n * n; 13 | int i, j, num = 1; 14 | // 思考此处为什么不用像54题那样,在循环内部进行边界判断?? 15 | // 因为本题永远都是正方形矩阵,四个边界都是对称的,不会出现54那样不对称的情况(例如54题,3*4的矩阵不进行边界判断就会出问题) 16 | while (num <= n) { 17 | for (j = left; j <= right; j++) array[top][j] = num++; 18 | for (i = ++top; i <= bottom; i++) array[i][right] = num++; 19 | for (j = --right; j >= left; j--) array[bottom][j] = num++; 20 | for (i = --bottom; i >= top; i--) array[i][left] = num++; 21 | left++; 22 | } 23 | return array; 24 | } 25 | } -------------------------------------------------------------------------------- /src/datastructure/array/P66AddOne.java: -------------------------------------------------------------------------------- 1 | package datastructure.array; 2 | 3 | 4 | /** 5 | * Title: 66. 加1 6 | * Desc: 给定一个由整数组成的非空数组所表示的非负整数,在该数的基础上加一。 7 | 最高位数字存放在数组的首位, 数组中每个元素只存储单个数字。 8 | 你可以假设除了整数 0 之外,这个整数不会以零开头。 9 | 10 | 输入: [1,2,3] 11 | 输出: [1,2,4] 12 | 解释: 输入数组表示数字 123。 13 | * Created by Myth-MBP on 07/07/2020 in VSCode 14 | */ 15 | public class P66AddOne { 16 | 17 | public int[] plusOne(int[] digits) { 18 | int n = digits.length; 19 | if (n == 0) { 20 | return digits; 21 | } 22 | int carry = 1; 23 | for (int i = n-1; i >= 0; i--) { 24 | int temp = digits[i] + carry; 25 | digits[i] = temp % 10; 26 | carry = temp / 10; 27 | if (carry == 0) { 28 | break; 29 | } 30 | } 31 | // 说明位数增加 有进位,后面肯定是0啊 32 | if (carry != 0) { 33 | int[] newDigits = new int[n+1]; 34 | System.arraycopy(digits, 0, newDigits, 1, n); 35 | newDigits[0] = carry; 36 | digits = newDigits; 37 | } 38 | return digits; 39 | } 40 | // 修改版 41 | public int[] plusOne2(int[] digits) { 42 | for (int i = digits.length - 1; i >= 0; i--) { 43 | digits[i]++; 44 | digits[i] = digits[i] % 10; 45 | if (digits[i] != 0) return digits; 46 | } 47 | digits = new int[digits.length + 1]; 48 | digits[0] = 1; 49 | return digits; 50 | } 51 | 52 | } -------------------------------------------------------------------------------- /src/datastructure/array/P80RemoveDup2.java: -------------------------------------------------------------------------------- 1 | package datastructure.array; 2 | 3 | 4 | 5 | /** 6 | * Title: 80. 删除重复元素2 7 | * Desc: 排序数组,使得每个元素最多出现两次 8 | * Created by Myth-MBP on 10/08/2020 in VSCode 9 | */ 10 | public class P80RemoveDup2 { 11 | public int removeDuplicates(int[] nums) { 12 | int i = 0; 13 | for (int n : nums) { 14 | if (i < 2 || n > nums[i-2]) nums[i++] = n; 15 | } 16 | return i; 17 | } 18 | } -------------------------------------------------------------------------------- /src/datastructure/array/README.md: -------------------------------------------------------------------------------- 1 | # 数组 2 | 3 | ## 经典、巧妙的题 4 | 5 | ### 多次旋转 6 | - 48. 旋转图像 7 | - 189. 旋转数组 8 | 9 | ### 控制边界 10 | - 54. 螺旋打印矩阵 11 | - 59. 螺旋矩阵 12 | 13 | ### 原地修改,状态记录 14 | - 73. 矩阵置零 15 | - 289. 生命游戏 16 | 17 | 31. 下一个排列 18 | -------------------------------------------------------------------------------- /src/datastructure/hash/P187RepeatedDNASequences.java: -------------------------------------------------------------------------------- 1 | package datastructure.hash; 2 | 3 | import java.util.HashMap; 4 | import java.util.LinkedList; 5 | import java.util.List; 6 | 7 | /** 8 | * Title: 187. 重复的DNA序列 9 | * Desc: 所有 DNA 都由一系列缩写为 A,C,G 和 T 的核苷酸组成, 10 | * 例如:“ACGAATTCCG”。在研究 DNA 时,识别 DNA 中的重复序列有时会对研究非常有帮助。 11 | * 编写一个函数来查找目标子串,目标子串的长度为 10,且在 DNA 字符串 s 中出现次数超过一次。 12 | 13 | * 示例: 14 | * 输入:s = "AAAAACCCCCAAAAACCCCCCAAAAAGGGTTT" 15 | * 输出:["AAAAACCCCC", "CCCCCAAAAA"] 16 | * 17 | * Created by Myth-Lab on 11/7/2019 18 | */ 19 | public class P187RepeatedDNASequences { 20 | 21 | public List findRepeatedDnaSequences1(String s) { 22 | HashMap map = new HashMap<>(); 23 | // 滑动窗口 + Hash 24 | for (int i = 0; i+10 <= s.length(); i++) { 25 | String seq = s.substring(i, i+10); 26 | map.put(seq, map.getOrDefault(seq, 0)+1); 27 | } 28 | List ret = new LinkedList<>(); 29 | for (String seq : map.keySet()) { 30 | if (map.get(seq) > 1) ret.add(seq); 31 | } 32 | return ret; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/datastructure/hash/P1TwoSum.java: -------------------------------------------------------------------------------- 1 | package datastructure.hash; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | /** 6 | * Title: 1. 两数之和 7 | * Desc: 给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。 8 | 9 | 你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。 10 | * Created by Myth-MBP on 05/07/2020 in VSCode 11 | */ 12 | public class P1TwoSum { 13 | // 注意返回的是下标 14 | public int[] twoSum(int[] nums, int target) { 15 | int n = nums.length; 16 | Map map = new HashMap<>(n); 17 | for (int i = 0; i < n; i++) { 18 | int key = target-nums[i]; 19 | if (map.containsKey(key)) { 20 | return new int[]{map.get(key), i}; 21 | } 22 | map.put(nums[i], i); 23 | } 24 | return null; 25 | } 26 | } -------------------------------------------------------------------------------- /src/datastructure/hash/P242ValidAnagram.java: -------------------------------------------------------------------------------- 1 | package datastructure.hash; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | /** 7 | * Title: 242. 有效的字母异位词 8 | * Desc: 给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。 9 | * Created by Myth-Lab on 10/18/2019 10 | */ 11 | public class P242ValidAnagram { 12 | // 方法1:排序返回相等的串 13 | // 方法2:使用数组计数(ASCII码)只包含小写字母 14 | public boolean isAnagram2(String s, String t) { 15 | if (s.length() != t.length()) { 16 | return false; 17 | } 18 | int[] counter = new int[26]; 19 | for (int i = 0; i < s.length(); i++) { 20 | counter[s.charAt(i) - 'a']++; 21 | counter[t.charAt(i) - 'a']--; 22 | } 23 | for (int count : counter) { 24 | if (count != 0) { 25 | return false; 26 | } 27 | } 28 | return true; 29 | } 30 | // 方法3:Unicode 时候 31 | public boolean isAnagram3(String s, String t) { 32 | if(t.length() != s.length()) return false; 33 | Map map = new HashMap<>(); 34 | for (int i = 0; i < s.length(); i++) { 35 | map.put(s.charAt(i), map.getOrDefault(s.charAt(i), 0) + 1); 36 | } 37 | for (int i = 0; i < t.length(); i++) { 38 | if(!map.containsKey(t.charAt(i))) return false; 39 | int val = map.get(t.charAt(i)) - 1; 40 | if (val <= 0) map.remove(t.charAt(i)); 41 | else map.put(t.charAt(i), val); 42 | } 43 | return map.size() == 0; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/datastructure/hash/P3LongestStringWithoutRepeating.java: -------------------------------------------------------------------------------- 1 | package datastructure.hash; 2 | 3 | /** 4 | * Title: 3. 无重复字符的最长子串 5 | * Desc: 双指针、滑动窗口 + Hash 6 | * Created by Myth on 12/27/2019 in VSCode 7 | */ 8 | 9 | public class P3LongestStringWithoutRepeating { 10 | public int lengthOfLongestSubstring(String s) { 11 | int[] dic = new int[256]; 12 | int p = 0, q = 0, n = s.length(); 13 | int max = 0; 14 | while (p <= q && p < n) { 15 | if (q < n && dic[s.charAt(q)] == 0) { 16 | dic[s.charAt(q)]++; 17 | q++; 18 | } else { 19 | dic[s.charAt(p)]--; 20 | p++; 21 | } 22 | max = Math.max(max, q-p); 23 | } 24 | return max; 25 | } 26 | public static void main(String[] args) { 27 | P3LongestStringWithoutRepeating p3 = new P3LongestStringWithoutRepeating(); 28 | System.out.println(p3.lengthOfLongestSubstring("ababac")); 29 | } 30 | } -------------------------------------------------------------------------------- /src/datastructure/hash/P692TopKFrequencyWord.java: -------------------------------------------------------------------------------- 1 | package datastructure.hash; 2 | 3 | import java.util.*; 4 | 5 | 6 | 7 | /** 8 | * Title: 692. 前K个高频单词 9 | * Desc: 给一非空的单词列表,返回前 k 个出现次数最多的单词。返回的答案应该按单词出现频率由高到低排序。如果不同的单词有相同出现频率,按字母顺序排序。 10 | * Created by Myth-MBP on 27/08/2020 in VSCode 11 | */ 12 | 13 | public class P692TopKFrequencyWord { 14 | 15 | public static void main(String[] args) { 16 | 17 | } 18 | 19 | class Node { 20 | String name; 21 | int freq = 0; 22 | Node(String name, int freq) { 23 | this.name = name; 24 | this.freq = freq; 25 | } 26 | } 27 | 28 | public List topKFrequent(String[] words, int k) { 29 | Map map = new HashMap<>(); 30 | for (String word : words) { 31 | Node node = map.getOrDefault(word, new Node(word, 0)); 32 | node.freq += 1; 33 | map.put(word, node); 34 | } 35 | 36 | PriorityQueue maxHeap = new PriorityQueue<>((o1, o2) -> (o1.freq == o2.freq ? o1.name.compareTo(o2.name) : o2.freq - o1.freq)); 37 | for (String key : map.keySet()){ 38 | maxHeap.add(map.get(key)); 39 | } 40 | List ret = new ArrayList<>(); 41 | 42 | while (!maxHeap.isEmpty() && k > 0) { 43 | k--; 44 | ret.add(maxHeap.poll().name); 45 | } 46 | return ret; 47 | } 48 | 49 | } -------------------------------------------------------------------------------- /src/datastructure/hash/README.md: -------------------------------------------------------------------------------- 1 | # Hash 2 | 3 | ## hash的实现 4 | - 使用256数组 26数组 5 | - HashMap 6 | 7 | ## Cache 8 | 9 | Hash是无序的,所以一般需要一个双向链表来记录顺序,cache 经常使用到Hash + DoubleList 10 | 11 | LinkedHashMap的实现就是一个LRU,其内部就是Hash+双指针 12 | 13 | ## 经典题目 14 | - 1. TwoSum问题 15 | - 41. 确定第一个缺失的正数 16 | - 49. 242. 异位词(类似于桶排序) 17 | - 146. LRU 18 | - 460. LFU -------------------------------------------------------------------------------- /src/datastructure/heap/P215KthLargestElementInArray.java: -------------------------------------------------------------------------------- 1 | package datastructure.heap; 2 | 3 | import java.util.PriorityQueue; 4 | 5 | /** 6 | * Title: 215. 数组中的第K个最大元素 7 | * Desc: 本解法使用分治,使用堆;使用快排的partition函数的解法见 divideconquer包, 8 | * Created by Myth on 01/16/2020 in VSCode 9 | */ 10 | 11 | public class P215KthLargestElementInArray { 12 | public int findKthLargest(int[] nums, int k) { 13 | // 小顶堆:会收集最大的K个数,然后堆顶是最小的(即最大的K个数里面最小的,第K大的) 14 | PriorityQueue heap = new PriorityQueue<>(k, (o1, o2)->o1.compareTo(o2)); 15 | for (int i = 0; i < nums.length; i++) { 16 | if (i < k) heap.add(nums[i]); 17 | else if (nums[i] > heap.peek()) { 18 | heap.poll(); 19 | heap.add(nums[i]); 20 | } 21 | } 22 | 23 | while (!heap.isEmpty()) { 24 | System.out.print(heap.poll() + " "); 25 | } 26 | // return heap.peek(); 27 | return 0; 28 | } 29 | public static void main(String[] args) { 30 | P215KthLargestElementInArray p215 = new P215KthLargestElementInArray(); 31 | int[] nums = {3,2,1,5,6,4}; 32 | int[] nums2 = {3,2,3,1,2,4,5,5,6}; 33 | p215.findKthLargest(nums2, 4); 34 | } 35 | } -------------------------------------------------------------------------------- /src/datastructure/heap/P295FindMedianFromDataStream.java: -------------------------------------------------------------------------------- 1 | package datastructure.heap; 2 | 3 | import java.util.PriorityQueue; 4 | 5 | /** 6 | * Title: 295. 数据流中的中位数 7 | * Desc: 中位数是排序后中间的数 8 | * Created by Myth-PC on 14/02/2020 in VSCode 9 | */ 10 | public class P295FindMedianFromDataStream { 11 | // 分成两半,左边的最大值,右边的最小值 12 | PriorityQueue maxHeap = new PriorityQueue<>((o1, o2) -> o2-o1); 13 | PriorityQueue minHeap = new PriorityQueue<>(); 14 | public P295FindMedianFromDataStream() { 15 | 16 | } 17 | // max里数目多一个,如果数目不相等的时候,max的最大值就是中位数 18 | public void addNum(int num) { 19 | maxHeap.add(num); 20 | minHeap.add(maxHeap.poll()); 21 | if (minHeap.size() > maxHeap.size()) maxHeap.add(minHeap.poll()); 22 | } 23 | 24 | public double findMedian() { 25 | if (maxHeap.size() == 0) return 0.0; // 为空 26 | 27 | if (maxHeap.size() == minHeap.size()) return (maxHeap.peek()+minHeap.peek()) / 2.0; 28 | else return (double)maxHeap.peek(); 29 | } 30 | 31 | } -------------------------------------------------------------------------------- /src/datastructure/heap/README.md: -------------------------------------------------------------------------------- 1 | # 堆 2 | 3 | - 堆排序 4 | - 第K大的元素(小顶堆 最大的K个数里面最小的=第K大的数) 5 | - 数据流中的中位数 6 | - 前K个高频元素 -------------------------------------------------------------------------------- /src/datastructure/monotonicqueue/MaxInQueue.java: -------------------------------------------------------------------------------- 1 | package datastructure.monotonicqueue; 2 | 3 | import java.util.Deque; 4 | import java.util.LinkedList; 5 | import java.util.Queue; 6 | 7 | 8 | /** 9 | * Title: 队列的最大值 10 | * Desc: 11 | * Created by Myth-MBP on 09/07/2020 in VSCode 12 | */ 13 | 14 | public class MaxInQueue { 15 | /** 16 | * 求队列的最大值,实现 max push_back pop_front 函数。 17 | 思路:设置两个队列,一个普通队列Queue保存数据,一个Deque保存最大值(单调递减栈)。 18 | Push的时候若新元素大于Deque则弹出Deque的最后一个元素,添加新元素,否则的话直接添加新元素。 19 | Pop的时候,如果Deque队首等于要pop的Queue队首元素,那么Deque也pop掉该元素,否则不用pop Deque。 20 | 21 | > Warning: 本题出现了一个重大的错误,由于使用Deque,元素是Integer,使用了 == 去比较了(Integer有缓存池,小于255的才会==,否则是不相等的),导致出现了错误!!! 22 | */ 23 | Queue queue = new LinkedList<>(); 24 | Deque deque = new LinkedList<>(); // 存储最大值 25 | public int max_value() { 26 | if (deque.isEmpty()) return -1; 27 | return deque.peek(); 28 | } 29 | 30 | public void push_back(int value) { 31 | while (!deque.isEmpty() && value > deque.getLast()) deque.removeLast(); 32 | deque.add(value); 33 | queue.add(value); 34 | } 35 | 36 | public int pop_front() { 37 | if (queue.isEmpty()) return -1; 38 | // 此处千万不要使用==去判断 39 | if (deque.peek().equals(queue.peek())) deque.removeFirst(); 40 | return queue.remove(); 41 | } 42 | } -------------------------------------------------------------------------------- /src/datastructure/monotonicqueue/P239SlidingWindowMaximum.java: -------------------------------------------------------------------------------- 1 | package datastructure.monotonicqueue; 2 | 3 | import java.util.ArrayDeque; 4 | import java.util.Arrays; 5 | import java.util.Deque; 6 | 7 | 8 | /** 9 | * Title: 239. 滑动窗口最大值(双指针单元也有代码) 10 | * Desc: 给定一个数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。 11 | * 返回滑动窗口中的最大值。 12 | * Created by Myth-Lab on 10/15/2019 13 | */ 14 | public class P239SlidingWindowMaximum { 15 | public int[] maxSlidingWindow(int[] nums, int k) { 16 | int n = nums.length; 17 | if (n == 0 || k == 0 || k > n) return new int[0]; 18 | // 单调递减栈 19 | Deque deque = new ArrayDeque<>(); // index 20 | int[] ret = new int[n-k+1]; 21 | for (int i = 0; i < n; i++) { 22 | while (!deque.isEmpty() && i - deque.peekFirst() >= k) deque.removeFirst(); // 去头 23 | while (!deque.isEmpty() && nums[deque.peekLast()] < nums[i]) deque.removeLast(); // 循环去尾 保持单调递减特性 24 | deque.addLast(i); 25 | // 注意下标 26 | if (i >= k-1) ret[i-k+1] = nums[deque.peekFirst()]; 27 | } 28 | return ret; 29 | } 30 | 31 | public static void main(String[] args) { 32 | P239SlidingWindowMaximum p239 = new P239SlidingWindowMaximum(); 33 | int[] nums = {1,3,-1,-3,5,3,6,7}; 34 | System.out.println(Arrays.toString(p239.maxSlidingWindow(nums, 3))); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/datastructure/monotonicstack/P503NextGreaterElement2.java: -------------------------------------------------------------------------------- 1 | package datastructure.monotonicstack; 2 | 3 | import java.util.Arrays; 4 | import java.util.Stack; 5 | 6 | /** 7 | * Title: 503. 下一个更大元素 II 8 | * Desc: 给定一个循环数组(最后一个元素的下一个元素是数组的第一个元素),输出每个元素的下一个更大元素。 9 | * 数字 x 的下一个更大的元素是按数组遍历顺序,这个数字之后的第一个比它更大的数,这意味着你应该循环地搜索它的下一个更大的数。如果不存在,则输出 -1。 10 | * Created by Myth on 01/09/2020 in VSCode 11 | */ 12 | 13 | public class P503NextGreaterElement2 { 14 | // 2n 15 | // 循环数组 16 | public int[] nextGreaterElements(int[] nums) { 17 | int n = nums.length; 18 | int[] ret = new int[n]; 19 | Arrays.fill(ret, -1); 20 | Stack stack = new Stack<>(); // 递减栈, 存放下标 21 | int i = 0, j; 22 | // 遍历两遍数组即可 23 | while (i < 2*n) { 24 | j = (i >= n ? i-n : i); 25 | if (stack.empty() || nums[stack.peek()] >= nums[j]) { 26 | stack.push(j); 27 | i++; 28 | } else { 29 | ret[stack.pop()] = nums[j]; 30 | } 31 | } 32 | return ret; 33 | } 34 | // 简洁的写法 35 | public int[] nextGreaterElements2(int[] A) { 36 | int n = A.length, res[] = new int[n]; 37 | Arrays.fill(res, -1); 38 | Stack stack = new Stack<>(); 39 | for (int i = 0; i < n * 2; i++) { 40 | while (!stack.isEmpty() && A[stack.peek()] < A[i % n]) 41 | res[stack.pop()] = A[i % n]; 42 | stack.push(i % n); 43 | } 44 | return res; 45 | } 46 | } -------------------------------------------------------------------------------- /src/datastructure/monotonicstack/P739DailyTemperatures.java: -------------------------------------------------------------------------------- 1 | package datastructure.monotonicstack; 2 | 3 | import java.util.Stack; 4 | 5 | /** 6 | * Title: 739. 每日温度 7 | * Desc: 请根据每日 气温 列表,重新生成一个列表。对应位置的输出为:要想观测到更高的气温,至少需要等待的天数。如果气温在这之后都不会升高,请在该位置用 0 来代替。 8 | 9 | 例如,给定一个列表 temperatures = [73, 74, 75, 71, 69, 72, 76, 73],你的输出应该是 [1, 1, 4, 2, 1, 1, 0, 0]。 10 | 11 | 提示:气温 列表长度的范围是 [1, 30000]。每个气温的值的均为华氏度,都是在 [30, 100] 范围内的整数。 12 | * Created by Myth on 01/10/2020 in VSCode 13 | */ 14 | 15 | public class P739DailyTemperatures { 16 | public int[] dailyTemperatures(int[] T) { 17 | Stack stack = new Stack<>(); 18 | int n = T.length, j; 19 | int[] ret = new int[n]; 20 | for (int i = 0; i < n; i++) { 21 | while(!stack.empty() && T[stack.peek()] < T[i]) { 22 | j = stack.pop(); 23 | ret[j] = i-j; 24 | } 25 | stack.push(i); 26 | } 27 | return ret; 28 | } 29 | } -------------------------------------------------------------------------------- /src/datastructure/monotonicstack/README.md: -------------------------------------------------------------------------------- 1 | # 单调栈 2 | 3 | ## 可以解决的问题 4 | 一般的问题都是:在一个序列中,可以慢慢增加(减少),在突然减少(增加)的时候,可以做一些操作 5 | 6 | 以递增栈为例: 7 | - 下一个更小的元素 Next Less Element(pop时进行操作) 8 | - 前一个更小的元素 Previous Less Element(push时进行操作) 9 | 10 | > 1. push当前元素的时候肯定是当前元素比当前栈顶的元素更大(栈顶元素肯定就是当前元素前一个更小的元素) 11 | > 2. pop的时候肯定是当前元素小于栈顶元素,那么当前元素肯定是栈顶元素的下一个更小值 12 | 13 | ```Java 14 | pop时候(操作), 15 | cur 是 peek的下一个小的元素 16 | // 错误说法:cur的前一个最大是peek(错误:因为会重复更新cur): 这是一个循环前面的都是比cur大的,第84题就是利用这个 17 | 18 | // 注意:有的进不去pop循环,说明他没有next less element,可以通过赋初始值进行处理 19 | 20 | push 的时候(操作): 21 | cur的前一个小的元素 是 peek 22 | // 错误说法:peek的下一个大的是cur(错误,peek的下一个大的数不一定是cur, 当前的peek后面有可能有下降的数,被pop出栈了, 没有被记录下来) 23 | // 注意:stack为空时,要进行判断 24 | ``` 25 | 26 | > 递减栈是下一个(前一个)更大的元素 27 | 28 | ## 模版 29 | 单调递增栈 30 | ```Java 31 | Stack<> stack; // 存放下标或者元素 Key1 32 | 33 | for (int i = 0; i < n; i++) { 34 | right[i] = n-i; // 某些元素不能进到pop循环,所以赋初值 Key4 35 | while (!stack.empty() && A[stack.peek()] > A[i]) { 36 | int peek = stack.pop(); 37 | right[peek] = i - peek; // pop时操作 Next Less Element Key2 38 | } 39 | // 栈为空时需要特殊赋值 Key5 40 | left[i] = (stack.empty() ? i+1 : i - stack.peek()); // push时操作,Previous Less Element Key3 41 | stack.push(i); 42 | } 43 | ``` 44 | 递增求更小、递减求更大 45 | 46 | ## 经典题目 47 | 48 | - 42. 接雨水 49 | - 84. 柱形图问题 50 | - 496. 503. 下一个更大的元素 51 | - 739. 每日温度(入门题) -------------------------------------------------------------------------------- /src/datastructure/stack/P150PolishNotation.java: -------------------------------------------------------------------------------- 1 | package datastructure.stack; 2 | 3 | import java.util.Collections; 4 | import java.util.Stack; 5 | 6 | /** 7 | * Title: 150. 逆波兰表达式求值 8 | * Desc: 根据 逆波兰表示法,求表达式的值。有效的运算符包括 +, -, *, /。 9 | * 每个运算对象可以是整数,也可以是另一个逆波兰表达式。 10 | * Created by Myth-MBP on 06/07/2020 in VSCode 11 | */ 12 | public class P150PolishNotation { 13 | 14 | public static int evalRPN(String[] tokens) { 15 | Stack numStack = new Stack<>(); 16 | Integer op1, op2; 17 | for (String s : tokens) { 18 | switch (s) { 19 | case "+": 20 | op2 = numStack.pop(); 21 | op1 = numStack.pop(); 22 | numStack.push(op1 + op2); 23 | break; 24 | case "-": 25 | op2 = numStack.pop(); 26 | op1 = numStack.pop(); 27 | numStack.push(op1 - op2); 28 | break; 29 | case "*": 30 | op2 = numStack.pop(); 31 | op1 = numStack.pop(); 32 | numStack.push(op1 * op2); 33 | break; 34 | case "/": 35 | op2 = numStack.pop(); 36 | op1 = numStack.pop(); 37 | numStack.push(op1 / op2); 38 | break; 39 | default: 40 | numStack.push(Integer.valueOf(s)); 41 | break; 42 | } 43 | } 44 | String s = "aaaa"; 45 | return numStack.pop(); 46 | } 47 | } -------------------------------------------------------------------------------- /src/datastructure/stack/P155MinStack.java: -------------------------------------------------------------------------------- 1 | package datastructure.stack; 2 | 3 | import java.util.Stack; 4 | 5 | /** 6 | * Title: 155. 最小栈(可以求最小值的栈) 7 | * Desc: 设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。 8 | push(x) —— 将元素 x 推入栈中。 9 | pop() —— 删除栈顶的元素。 10 | top() —— 获取栈顶元素。 11 | getMin() —— 检索栈中的最小元素。 12 | * Created by Myth-PC on 05/02/2020 in VSCode 13 | */ 14 | public class P155MinStack { 15 | 16 | private Stack dataStack = new Stack<>(); 17 | // 使用一个minStack存储最小值 18 | private Stack minStack = new Stack<>(); 19 | 20 | public void push(int x) { 21 | if (minStack.empty() || x <= minStack.peek()) { 22 | minStack.push(x); 23 | } else { 24 | // 当前最小值仍然是最小值 25 | minStack.push(minStack.peek()); 26 | } 27 | dataStack.push(x); 28 | } 29 | 30 | public void pop() { 31 | dataStack.pop(); 32 | minStack.pop(); 33 | } 34 | 35 | public int top() { 36 | return dataStack.peek(); 37 | } 38 | 39 | public int getMin() { 40 | return minStack.peek(); 41 | } 42 | } -------------------------------------------------------------------------------- /src/datastructure/stack/P20ValidParentheses.java: -------------------------------------------------------------------------------- 1 | package datastructure.stack; 2 | 3 | import java.util.Stack; 4 | 5 | /** 6 | * Title: 20. 有效的括号 7 | * Desc: ( ) [ ] { } 8 | 给定一个只包括 '(',')','{','}','[',']' 的字符串,判断字符串是否有效。 9 | 有效字符串需满足: 10 | 1. 左括号必须用相同类型的右括号闭合。 11 | 2. 左括号必须以正确的顺序闭合。 12 | 3. 注意空字符串可被认为是有效字符串。 13 | * Created by Myth-MBP on 15/03/2020 in VSCode 14 | */ 15 | 16 | public class P20ValidParentheses { 17 | 18 | public boolean isValid(String s) { 19 | if (s == null) return false; 20 | int n = s.length(); 21 | // if (n == 0) return true; 22 | Stack stack = new Stack<>(); 23 | for (int i = 0; i < n; i++) { 24 | char ch = s.charAt(i); 25 | if (ch == ')' || ch == ']' || ch == '}') { 26 | if (stack.isEmpty()) return false; 27 | char peek = stack.pop(); 28 | if (!((peek == '(' && ch == ')') || (peek == '[' && ch == ']') || (peek == '{' && ch == '}'))) return false; 29 | } else if (ch == '(' || ch == '[' || ch == '{') { 30 | stack.push(ch); 31 | } else { 32 | return false; 33 | } 34 | } 35 | return stack.isEmpty(); 36 | } 37 | } -------------------------------------------------------------------------------- /src/datastructure/stack/P32LongestValidParentheses.java: -------------------------------------------------------------------------------- 1 | package datastructure.stack; 2 | 3 | import java.util.Stack; 4 | 5 | /** 6 | * Title: 32. 最长有效括号 7 | * Desc: 给定一个只包含 '(' 和 ')' 的字符串,找出最长的包含有效括号的 子串的长度。 8 | * Created by Myth on 01/06/2020 in VSCode 9 | */ 10 | 11 | public class P32LongestValidParentheses { 12 | // 难点:括号有效 13 | // 使用栈记录下标 14 | public int longestValidParentheses(String s) { 15 | Stack stack = new Stack<>(); 16 | int max = 0; 17 | stack.push(-1); 18 | 19 | for (int i = 0; i < s.length(); i++) { 20 | if (s.charAt(i) == '(') { 21 | stack.push(i); 22 | } else { 23 | stack.pop(); 24 | if (!stack.empty()) { 25 | max = Math.max(max, i-stack.peek()); 26 | } else { 27 | stack.push(i); 28 | } 29 | } 30 | } 31 | return max; 32 | } 33 | 34 | 35 | public static void main(String[] args) { 36 | P32LongestValidParentheses p32 = new P32LongestValidParentheses(); 37 | System.out.println(p32.longestValidParentheses("")); 38 | System.out.println(p32.longestValidParentheses("(")); 39 | System.out.println(p32.longestValidParentheses("()")); 40 | System.out.println(p32.longestValidParentheses(")(")); 41 | System.out.println(p32.longestValidParentheses(")()())")); 42 | } 43 | } -------------------------------------------------------------------------------- /src/datastructure/stack/README.md: -------------------------------------------------------------------------------- 1 | # 栈 2 | 3 | - 20. 有效的括号 4 | - 32. 最长的有效括号 5 | - 150. 逆波兰表达式求值 6 | - 155. 含有最小值的栈 7 | - 225. 用队列实现栈 8 | - 栈的压入弹出序列 -------------------------------------------------------------------------------- /src/datastructure/stack/StackPushPopOrder.java: -------------------------------------------------------------------------------- 1 | package datastructure.stack; 2 | 3 | import java.util.Stack; 4 | 5 | /** 6 | * Title: 剑指offer31: 7 | * Desc: 输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序。 8 | * 假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列, 9 | * 但4,3,5,1,2就不可能是该压栈序列的弹出序列。(注意:这两个序列的长度是相等的) 10 | * Created by Myth-MBP on 09/07/2020 11 | * in VSCode 12 | */ 13 | public class StackPushPopOrder { 14 | // 创建一个辅助栈,模拟入栈和出栈的顺序,如果最后辅助栈为空,那么弹出栈是压入栈的序列 15 | public boolean isPopOrder(int [] pushA,int [] popA) { 16 | java.util.Stack stack = new Stack<>(); 17 | int i = 0, j = 0; 18 | while (i < pushA.length) { 19 | stack.push(pushA[i++]); 20 | while (!stack.empty() && stack.peek() == popA[j]) { 21 | stack.pop(); 22 | j++; 23 | } 24 | } 25 | return stack.empty(); 26 | } 27 | } -------------------------------------------------------------------------------- /src/datastructure/string/P125ValidPalindrome.java: -------------------------------------------------------------------------------- 1 | package datastructure.string; 2 | 3 | /** 4 | * Title: 125. 验证回文串 5 | * Desc: 给定一个字符串,验证它是否是回文串,只考虑字母和数字字符,可以忽略字母的大小写。 6 | 说明:本题中,我们将空字符串定义为有效的回文串。 7 | 8 | 输入: "A man, a plan, a canal: Panama" 9 | 输出: true 10 | * Created by Myth-MBP on 06/07/2020 in VSCode 11 | */ 12 | public class P125ValidPalindrome { 13 | // 重点:Character的使用 14 | public static boolean isPalindrome(String s) { 15 | int i = 0, j = s.length()-1; 16 | while(i < j) { 17 | if (!Character.isLetter(s.charAt(i)) && !Character.isDigit(s.charAt(i))) { 18 | i++; 19 | continue; 20 | } 21 | if (!Character.isLetter(s.charAt(j)) && !Character.isDigit(s.charAt(j))) { 22 | j--; 23 | continue; 24 | } 25 | if (Character.toLowerCase(s.charAt(i)) != Character.toLowerCase(s.charAt(j))) { 26 | return false; 27 | } 28 | i++; 29 | j--; 30 | } 31 | return true; 32 | } 33 | public static void main(String[] args) { 34 | System.out.println(P125ValidPalindrome.isPalindrome("0P")); 35 | } 36 | } -------------------------------------------------------------------------------- /src/datastructure/string/P58LastWord.java: -------------------------------------------------------------------------------- 1 | package datastructure.string; 2 | 3 | 4 | /** 5 | * Title: 6 | * Desc: 给定一个仅包含大小写字母和空格 ' ' 的字符串 s,返回其最后一个单词的长度。 7 | * 如果字符串从左向右滚动显示,那么最后一个单词就是最后出现的单词。 8 | * 如果不存在最后一个单词,请返回 0 。 9 | * Created by Myth-MBP on 13/05/2020 in VSCode 10 | */ 11 | 12 | public class P58LastWord { 13 | // 从前往后,太麻烦了 14 | public int lengthOfLastWord2(String s) { 15 | StringBuilder sb = new StringBuilder(); 16 | String last = ""; 17 | int len = s.length(); 18 | for (int i = 0; i < len; i++) { 19 | if (s.charAt(i) != ' ') { 20 | sb.append(s.charAt(i)); 21 | } else { 22 | if (sb.length() != 0) last = sb.toString(); 23 | sb.delete(0, sb.length()); 24 | } 25 | } 26 | if (sb.length() != 0) last = sb.toString(); 27 | return last.length(); 28 | } 29 | // 从后往前 30 | public int lengthOfLastWord(String s) { 31 | int len = s.length(); 32 | int end = len-1; 33 | StringBuilder sb = new StringBuilder(); 34 | // 去除前导空格 35 | for (int i = len-1; i >= 0; i--) { 36 | if (s.charAt(i) != ' ') { 37 | end = i; 38 | break; 39 | } 40 | } 41 | for (int i = end; i >= 0; i--) { 42 | if (s.charAt(i) == ' ') { 43 | break; 44 | } 45 | sb.append(s.charAt(i)); 46 | } 47 | return sb.length(); 48 | } 49 | } -------------------------------------------------------------------------------- /src/divideconquer/P215KthLargestElementInArray.java: -------------------------------------------------------------------------------- 1 | package divideconquer; 2 | 3 | /** 4 | * Title: 215. 数组中的第K个最大元素 5 | * Desc: 使用堆的方法见datastructure.heap包,本解法使用分治,快排的partition函数 6 | * Created by Myth on 01/16/2020 in VSCode 7 | */ 8 | 9 | public class P215KthLargestElementInArray { 10 | // 大到小排列的 第 k-1 位置 11 | public int findKthLargest(int[] nums, int k) { 12 | // 注意这个nums数组已经变化了 13 | return nums[find(nums, 0, nums.length-1, k-1)]; 14 | } 15 | public int find(int[] nums, int l, int r, int k) { 16 | int pivot = partition(nums, l, r); 17 | // System.out.print(pivot + " -- " + k + "---"); 18 | // System.out.println(Arrays.toString(nums)); 19 | int i = 0; 20 | if (pivot == k) i = pivot; 21 | if (pivot > k) i = find(nums, l, pivot-1, k); 22 | if (pivot < k) i = find(nums, pivot+1, r, k); 23 | return i; 24 | } 25 | 26 | public int partition(int[] nums, int l, int r) { 27 | if (l > r) return -1; 28 | int pivot = nums[l]; 29 | while (l < r) { 30 | while (l < r && nums[r] <= pivot) r--; 31 | nums[r] = nums[l]; 32 | while (l < r && nums[l] >= pivot) l++; 33 | nums[l] = nums[r]; 34 | } 35 | nums[l] = pivot; 36 | return l; 37 | } 38 | 39 | public static void main(String[] args) { 40 | P215KthLargestElementInArray p215 = new P215KthLargestElementInArray(); 41 | int[] nums = {3,2,1,5,6,4}; 42 | int[] nums2 = {3,2,3,1,2,4,5,5,6}; 43 | p215.findKthLargest(nums2, 4); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/divideconquer/P21MergeTwoSortedLists.java: -------------------------------------------------------------------------------- 1 | package divideconquer; 2 | 3 | import util.ListNode; 4 | 5 | /** 6 | * Title: 21. 合并两个有序链表(循环写法和递归写法) 7 | * Desc: 将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 8 | * Created by Myth on 9/8/2019 9 | */ 10 | public class P21MergeTwoSortedLists { 11 | public ListNode mergeTwoLists(ListNode l1, ListNode l2) { 12 | ListNode head = new ListNode(-1); 13 | ListNode p = head; 14 | while(l1 != null && l2 != null) { 15 | if (l1.val <= l2.val) { 16 | p.next = l1; 17 | l1 = l1.next; 18 | } else { 19 | p.next = l2; 20 | l2 = l2.next; 21 | } 22 | p = p.next; 23 | } 24 | // 直接接到 剩余的链表即可 25 | if (l1 != null) p.next = l1; 26 | if (l2 != null) p.next = l2; 27 | return head.next; 28 | } 29 | public ListNode mergeTwoListsRecursive(ListNode l1, ListNode l2) { 30 | if (l1 == null) return l2; 31 | if (l2 == null) return l1; 32 | if (l1.val <= l2.val) { 33 | l1.next = mergeTwoListsRecursive(l1.next, l2); 34 | return l1; 35 | } else { 36 | l2.next = mergeTwoListsRecursive(l1, l2.next); 37 | return l2; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/divideconquer/README.md: -------------------------------------------------------------------------------- 1 | # 分治 2 | 3 | ## 经典题目 4 | 5 | - 归并排序(数组、链表) 6 | - 逆序对的个数 7 | - 315. 计算右侧小于当前元素的个数 8 | - 第K个最大元素(快排的Partition函数) 9 | - 二路归并和多路归并 -------------------------------------------------------------------------------- /src/dp/chessboard/P221MaximalSquare.java: -------------------------------------------------------------------------------- 1 | package dp.chessboard; 2 | 3 | /** 4 | * Title: 221. 最大正方形(见第85题) 5 | * Desc: 在一个由 0 和 1 组成的二维矩阵内,找到只包含 1 的最大正方形,并返回其面积。 6 | * Created by Myth on 12/07/2019 in VSCode 7 | */ 8 | 9 | public class P221MaximalSquare { 10 | public int maximalSquare(char[][] matrix) { 11 | int m = matrix.length; 12 | if (m == 0) return 0; 13 | int n = matrix[0].length; 14 | // dp中存储的是边长 15 | int[][] dp = new int[m][n]; 16 | int max = 0; 17 | // 注意初始化 18 | for (int i = 0; i < m; i++) { 19 | if (matrix[i][0] == '1') dp[i][0] = 1; 20 | max = Math.max(max, dp[i][0]); 21 | } 22 | for (int i = 0; i < n; i++) { 23 | if (matrix[0][i] == '1') dp[0][i] = 1; 24 | max = Math.max(max, dp[0][i]); 25 | } 26 | for (int i = 1; i < m; i++) { 27 | for (int j = 1; j < n; j++) { 28 | if (matrix[i][j] == '1') dp[i][j] = Math.min(dp[i-1][j-1], Math.min(dp[i-1][j], dp[i][j-1])) + 1; 29 | max = Math.max(max, dp[i][j]); 30 | } 31 | } 32 | 33 | return max*max; 34 | } 35 | } -------------------------------------------------------------------------------- /src/dp/chessboard/P62UniquePaths.java: -------------------------------------------------------------------------------- 1 | package dp.chessboard; 2 | 3 | /** 4 | * Title: 62. 不同路径(二维动态规划) 5 | * Desc: 一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。 6 | 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。 7 | 问总共有多少条不同的路径? 8 | * Created by Myth on 12/05/2019 in VSCode 9 | */ 10 | 11 | public class P62UniquePaths { 12 | public int uniquePaths(int m, int n) { 13 | int[][] dp = new int[m][n]; 14 | if (m < 1 || n < 1) return 0; 15 | for (int i = 0; i < m; i++) { 16 | dp[i][0] = 1; 17 | } 18 | for (int i = 0; i < n; i++) { 19 | dp[0][i] = 1; 20 | } 21 | 22 | for (int i = 1; i < m; i++) { // 行 23 | for (int j = 1; j < n; j++) { // 列 24 | dp[i][j] = dp[i-1][j] + dp[i][j-1]; 25 | } 26 | } 27 | return dp[m-1][n-1]; 28 | } 29 | } -------------------------------------------------------------------------------- /src/dp/chessboard/P63UniquePaths2.java: -------------------------------------------------------------------------------- 1 | package dp.chessboard; 2 | 3 | /** 4 | * Title: 63. 不同路径 II 5 | * Desc: 一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。 6 | 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。 7 | 现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径? 8 | * Created by Myth on 12/05/2019 in VSCode 9 | */ 10 | 11 | public class P63UniquePaths2 { 12 | public int uniquePathsWithObstacles(int[][] obstacleGrid) { 13 | // m > 0, n > 0 14 | int m = obstacleGrid.length, n = obstacleGrid[0].length; 15 | if (m < 1 || n < 1) return 0; 16 | int[][] dp = new int[m][n]; 17 | if(obstacleGrid[0][0] != 1) dp[0][0] = 1; 18 | for (int i = 1; i < m; i++) { 19 | if (obstacleGrid[i][0] != 1 && dp[i-1][0] != 0) dp[i][0] = 1; 20 | } 21 | for (int i = 1; i < n; i++) { 22 | if (obstacleGrid[0][i] != 1 && dp[0][i-1] != 0) dp[0][i] = 1; 23 | } 24 | 25 | for (int i = 1; i < m; i++) { // 行 26 | for (int j = 1; j < n; j++) { // 列 27 | if (obstacleGrid[i][j] != 1) { 28 | dp[i][j] = dp[i-1][j] + dp[i][j-1]; 29 | } else { 30 | dp[i][j] = 0; 31 | } 32 | } 33 | } 34 | return dp[m-1][n-1]; 35 | } 36 | } -------------------------------------------------------------------------------- /src/dp/chessboard/P64MinimumPathSum.java: -------------------------------------------------------------------------------- 1 | package dp.chessboard; 2 | 3 | /** 4 | * Title: 64. 最小路径和 5 | * Desc: 给定一个包含非负整数的 m x n 网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。 6 | 说明:每次只能向下或者向右移动一步。 7 | * Created by Myth on 12/06/2019 in VSCode 8 | */ 9 | 10 | public class P64MinimumPathSum { 11 | public int minPathSum(int[][] grid) { 12 | // m > 0, n > 0 13 | int m = grid.length, n = grid[0].length; 14 | if (m < 1 || n < 1) return 0; 15 | int[][] dp = new int[m][n]; 16 | dp[0][0] = grid[0][0]; 17 | for (int i = 1; i < m; i++) { 18 | dp[i][0] = dp[i-1][0] + grid[i][0]; 19 | } 20 | for (int i = 1; i < n; i++) { 21 | dp[0][i] = dp[0][i-1] + grid[0][i]; 22 | } 23 | 24 | for (int i = 1; i < m; i++) { // 行 25 | for (int j = 1; j < n; j++) { // 列 26 | dp[i][j] = Math.min(dp[i-1][j], dp[i][j-1]) + grid[i][j]; 27 | } 28 | } 29 | return dp[m-1][n-1]; 30 | } 31 | } -------------------------------------------------------------------------------- /src/dp/chessboard/P935KnightDialer.java: -------------------------------------------------------------------------------- 1 | package dp.chessboard; 2 | 3 | import java.util.Arrays; 4 | 5 | /** 6 | * Title: 935. 骑士拨号器 7 | * Desc: 8 | * Created by Myth on 12/10/2019 in VSCode 9 | */ 10 | 11 | public class P935KnightDialer { 12 | public int knightDialer(int N) { 13 | if (N <= 0) return 0; 14 | if (N == 1) return 10; 15 | long[][] dp = new long[N][10]; 16 | for (int j = 0; j < 10; j++) { 17 | dp[0][j] = 1; 18 | } 19 | for (int i = 1; i < N; i++) { 20 | dp[i][0] = (dp[i-1][4] + dp[i-1][6]) % 1000000007; 21 | dp[i][1] = (dp[i-1][6] + dp[i-1][8]) % 1000000007; 22 | dp[i][2] = (dp[i-1][7] + dp[i-1][9]) % 1000000007; 23 | dp[i][3] = (dp[i-1][4] + dp[i-1][8]) % 1000000007; 24 | dp[i][4] = (dp[i-1][3] + dp[i-1][9] + dp[i-1][0]) % 1000000007; 25 | dp[i][5] = 0; 26 | dp[i][6] = (dp[i-1][1] + dp[i-1][7] + dp[i-1][0]) % 1000000007; 27 | dp[i][7] = (dp[i-1][2] + dp[i-1][6]) % 1000000007; 28 | dp[i][8] = (dp[i-1][1] + dp[i-1][3]) % 1000000007; 29 | dp[i][9] = (dp[i-1][2] + dp[i-1][4]) % 1000000007; 30 | } 31 | long sum = 0; 32 | for (int j = 0; j < 10; j++) { 33 | sum = (sum + dp[N-1][j]) % 1000000007; 34 | } 35 | return (int)sum; 36 | } 37 | public static void main(String[] args) { 38 | P935KnightDialer p935 = new P935KnightDialer(); 39 | System.out.println(p935.knightDialer(50)); // 267287516 40 | } 41 | 42 | } -------------------------------------------------------------------------------- /src/dp/chessboard/README.md: -------------------------------------------------------------------------------- 1 | # 棋盘二维DP 2 | 3 | - 机器人行走棋盘,问走法 4 | - 如果棋盘有障碍物,问有多少走法 5 | - 棋盘上最大的正方形(1组成的) -------------------------------------------------------------------------------- /src/dp/game/Lintcode395CoinsLine.java: -------------------------------------------------------------------------------- 1 | package dp.game; 2 | 3 | /** 4 | * Title: Lintcode395题:硬币排成线 5 | * Desc: https://www.lintcode.com/problem/coins-in-a-line-ii/description 6 | 有 n 个不同价值的硬币排成一条线。两个参赛者轮流从 左边 依次拿走 1 或 2 个硬币,直到没有硬币为止。计算两个人分别拿到的硬币总价值,价值高的人获胜。 7 | 8 | 请判定 先手玩家 必胜还是必败? 9 | 10 | 若必胜, 返回 true, 否则返回 false 11 | * Created by Myth on 12/31/2019 in VSCode 12 | */ 13 | 14 | public class Lintcode395CoinsLine { 15 | public boolean firstWillWin(int[] values) { 16 | int n = values.length; 17 | // 最后的几种状态 18 | if (n == 0) return false; 19 | if (n <= 2) return true; 20 | int[] dp = new int[n]; 21 | dp[n-1] = values[n-1]; 22 | dp[n-2] = values[n-2] + values[n-1]; 23 | dp[n-3] = values[n-3] + values[n-2] - values[n-1]; 24 | for (int i = n-4; i >= 0; i--) { // 从后往前 25 | dp[i] = Math.max(values[i]-dp[i+1], values[i]+values[i+1]-dp[i+2]); // 注意此处的 -dp[i+1]和-dp[i+2] 26 | } 27 | return dp[0] > 0; 28 | } 29 | } -------------------------------------------------------------------------------- /src/dp/game/P292NimGame.java: -------------------------------------------------------------------------------- 1 | package dp.game; 2 | 3 | /** 4 | * Title: 292. Nim游戏(Bash博弈) 5 | * Desc: 本题是 取1-3颗石子 6 | * Created by Myth on 01/01/2020 in VSCode 7 | */ 8 | 9 | public class P292NimGame { 10 | 11 | public boolean canWinNim(int n) { 12 | if (n <= 3) return true; 13 | boolean[] dp = new boolean[n]; 14 | // 最后的几种状态 15 | dp[n-1] = true; 16 | dp[n-2] = true; 17 | dp[n-3] = true; 18 | 19 | for (int i = n-4; i >= 0; i--) { // 从后往前 20 | dp[i] = !dp[i+1] || !dp[i+2] || !dp[i+3]; // 此处的取反和Lintcode395中的 -dp道理一样,对手和自己的角色互换了 21 | } 22 | 23 | return dp[0]; 24 | } 25 | // 状态压缩 Test n = 26 | public boolean canWinNim2(int n) { 27 | if (n <= 3) return true; 28 | boolean[] dp = new boolean[4]; 29 | dp[1] = true; 30 | dp[2] = true; 31 | dp[3] = true; 32 | 33 | for (int i = n-4; i >= 0; i--) { 34 | dp[0] = !dp[1] || !dp[2] || !dp[3]; // 推广到 每次最多取 n 次 35 | dp[3] = dp[2]; 36 | dp[2] = dp[1]; 37 | dp[1] = dp[0]; 38 | } 39 | 40 | return dp[0]; 41 | } 42 | 43 | 44 | } -------------------------------------------------------------------------------- /src/dp/game/P486PredictTheWinner.java: -------------------------------------------------------------------------------- 1 | package dp.game; 2 | 3 | /** 4 | * Title: 486. 预测赢家 5 | * Desc: Lintcode395的进阶版,395是从一端取,本题是可以从两端取 6 | * Created by Myth on 01/02/2020 in VSCode 7 | */ 8 | 9 | public class P486PredictTheWinner { 10 | public boolean PredictTheWinner(int[] nums) { 11 | int n = nums.length; 12 | if (n == 0) return true; 13 | int[][] dp = new int[n][n]; // dp[i][j] 代表子序列 nums[i...j] 先手与对手的差值 14 | // 初始值:j-i 分别为 0 1 2 时的值 15 | for (int i = 0; i < n; i++) { 16 | dp[i][i] = nums[i]; 17 | if (i+1 < n) { 18 | dp[i][i+1] = Math.abs(nums[i+1]-nums[i]); 19 | } 20 | if (i+2 < n) { 21 | dp[i][i+2] = Math.max(nums[i]-Math.abs(nums[i+1]-nums[i+2]), nums[i+2]-Math.abs(nums[i+1]-nums[i])); 22 | } 23 | } 24 | 25 | for (int i = n-3; i >= 0; i--) { // 根据转移方程确定遍历的顺序和边界 26 | for (int j = i+3; j < n; j++) { 27 | dp[i][j] = Math.max(nums[i] - dp[i+1][j], nums[j] - dp[i][j-1]); 28 | } 29 | } 30 | return dp[0][n-1] >= 0; 31 | } 32 | public static void main(String[] args) { 33 | P486PredictTheWinner p486 = new P486PredictTheWinner(); 34 | int[] nums = {1, 5}; 35 | System.out.println(p486.PredictTheWinner(nums)); 36 | } 37 | } -------------------------------------------------------------------------------- /src/dp/game/P877StoneGame.java: -------------------------------------------------------------------------------- 1 | package dp.game; 2 | 3 | /** 4 | * Title: 877. 和486题一模一样!!! 5 | * Desc: 6 | * Created by Myth on 01/02/2020 in VSCode 7 | */ 8 | 9 | public class P877StoneGame { 10 | public boolean stoneGame(int[] piles) { 11 | int n = piles.length; 12 | if (n == 0) return true; 13 | int[][] dp = new int[n][n]; // dp[i][j] 代表子序列 nums[i...j] 先手与对手的差值 14 | // 初始值:j-i 分别为 0 1 2 时的值 15 | for (int i = 0; i < n; i++) { 16 | dp[i][i] = piles[i]; 17 | if (i+1 < n) { 18 | dp[i][i+1] = Math.abs(piles[i+1]-piles[i]); 19 | } 20 | if (i+2 < n) { 21 | dp[i][i+2] = Math.max(piles[i]-Math.abs(piles[i+1]-piles[i+2]), piles[i+2]-Math.abs(piles[i+1]-piles[i])); 22 | } 23 | } 24 | 25 | for (int i = n-3; i >= 0; i--) { // 根据转移方程确定遍历的顺序和边界 26 | for (int j = i+3; j < n; j++) { 27 | dp[i][j] = Math.max(piles[i] - dp[i+1][j], piles[j] - dp[i][j-1]); 28 | } 29 | } 30 | return dp[0][n-1] > 0; 31 | } 32 | 33 | } -------------------------------------------------------------------------------- /src/dp/game/README.md: -------------------------------------------------------------------------------- 1 | # 博弈论DP 2 | 3 | - 掌握这种从后往前,dp记录差值、或者胜负(递推公式-dp)的思想 4 | 5 | ## 巴什博弈 6 | 两个顶尖聪明的人在玩游戏,有一堆n个石子,每次每个人能取[1,m]个石子,不能拿的人输,请问先手与后手谁必败? 7 | 8 | 我们分类讨论一下这个问题: 9 | 10 | 当n≤m时,这时先手的人可以一次取走所有的; 11 | 12 | 当n=m+1时,这时先手无论取走多少个,后手的人都能取走剩下所有的; 13 | 14 | 当n=k∗(m+1)时,对于每m+1个石子,先手取i个,后手一定能将剩下的(m+1−i)个都取走,因此后手必胜; 15 | 16 | 当n=k∗(m+1)+x(0 < x < m+1)时,先手可以先取x个,之后的局势就回到了上一种情况,无论后手取多少个,先手都能取走m+1个中剩下的,因此先手必胜。 17 | 18 | 结论: 19 | 通过上面的分析可以得出结论:当n能整除m+1时先手必败,否则先手必胜。 20 | 21 | ## Nim博弈 22 | 23 | 两个顶尖聪明的人在玩游戏,有n堆石子,第i堆有ai个,每人每次能从一堆石子中取任意多个石子但不能不取,不能拿的人输,请问先手与后手谁必胜? 24 | 25 | 我们从简单的情况入手: 26 | 27 | 当n=1时,显然先手取走这一堆就能获胜; 28 | 29 | 当n=2且a1!=a2时,我们假设a1>a2,先手可以先在第一堆取走a1−a2个,下一次无论后手取走多少个,先手都可以在另一堆取走同样多个,因此先手必胜; 30 | 31 | 当n=2且a1==a2时,先手无论取走多少个,后手都能在另一堆取走同样多个,因此后手必胜。 32 | 33 | 当3≤n时呢? 34 | 35 | 问题变得繁琐起来,这时就要一个有规律性的结论。 36 | 37 | 结论: 38 | 当`a1^a2^…………^an=0`时先手必败,反之先手必胜。 39 | 40 | 为什么这个结论是对的? 41 | 42 | 假设当前异或和为0,这时先手在某一堆取走了若干个,局势变成了`a1^a2^…………^a′i^…………^an=k`,也就是`a1^a2^…………^a′i^…………an^k=0`。 43 | 44 | 假设k的最高位1在第x位,那么一定有一个数aj, aj的第xx位为1。 45 | 46 | 那么我们只要能将aj变成aj^k,就能使异或和又等于0,后手一直这样取,最后一定会是`a1=a2=……=an=0`(此处最重要),异或和是0,这时先手无法取了,先手就一定必败了。 47 | 48 | 那么能否每次都将aj变成aj^k呢 ? 49 | 50 | 答案是可以的,因为aj比k多出的高位异或后不变,第x位异或后为0,无论低位是什么,在第x位变0后整个数都会变小。 51 | 52 | 也就是说aj^k一定小于aj,那么后手每次只要在第j堆取走`aj−(aj^k)`个石子就好了。 -------------------------------------------------------------------------------- /src/dp/knapsack/P1155NumberOfDiceRollsWithTargetSum.java: -------------------------------------------------------------------------------- 1 | package dp.knapsack; 2 | 3 | 4 | /** 5 | * Title: 1155. 掷骰子的N种方法 6 | * Desc: 剑指offer第60题, bei 7 | * Created by Myth on 12/27/2019 in VSCode 8 | */ 9 | 10 | public class P1155NumberOfDiceRollsWithTargetSum { 11 | public int numRollsToTarget(int d, int f, int target) { 12 | int[] dp = new int[target+1]; 13 | for (int i = 1; i <= f && i <= target; i++) { 14 | dp[i] = 1; 15 | } 16 | for (int i = 1; i < d; i++) { 17 | for (int j = target; j > 0; j--) { 18 | int sum = 0; 19 | for (int k = 1; k <= f && k <= j; k++) { 20 | sum = (sum + dp[j-k]) % 1000000007; 21 | } 22 | dp[j] = sum; 23 | } 24 | } 25 | return dp[target]; 26 | } 27 | public static void main(String[] args) { 28 | P1155NumberOfDiceRollsWithTargetSum p1155 = new P1155NumberOfDiceRollsWithTargetSum(); 29 | 30 | System.out.println(p1155.numRollsToTarget(6, 30, 50)); 31 | } 32 | } -------------------------------------------------------------------------------- /src/dp/knapsack/P139WordBreak.java: -------------------------------------------------------------------------------- 1 | package dp.knapsack; 2 | 3 | import java.util.HashSet; 4 | import java.util.List; 5 | import java.util.Set; 6 | 7 | /** 8 | * Title: 139. 单词拆分(完全背包问题) 在dp.seq包下还有一个 9 | * Desc: 10 | * 给定一个非空字符串 s 和一个包含非空单词的列表 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。 11 | 说明: 12 | 拆分时可以重复使用字典中的单词 13 | 你可以假设字典中没有重复的单词 14 | * Created by Myth on 12/19/2019 in VSCode 15 | */ 16 | 17 | public class P139WordBreak { 18 | // 在上面代码的基础上改成 DP(状态是前缀) 19 | public boolean wordBreak2(String s, List wordDict) { 20 | boolean[] dp = new boolean[s.length()+1]; // s.sub(0, i)是否符合要求 21 | dp[0] = true; 22 | Set set = new HashSet<>(wordDict); 23 | for (int i = 1; i <= s.length(); i++) { // s.sub(0, i) 24 | for (int j = 0; j < i; j++) { 25 | if (dp[j] && set.contains(s.substring(j, i))) { // 前部分符合 && 后部分符合 26 | dp[i] = true; // 当前子串符合 27 | break; 28 | } 29 | } 30 | } 31 | return dp[s.length()]; 32 | } 33 | } -------------------------------------------------------------------------------- /src/dp/knapsack/P494TargetSum.java: -------------------------------------------------------------------------------- 1 | package dp.knapsack; 2 | 3 | 4 | // 01背包(要搞清数组中存的东西) 5 | /* 6 | 给定一个非负整数数组,a1, a2, ..., an, 和一个目标数,S。现在你有两个符号 + 和 -。对于数组中的任意一个整数,你都可以从 + 或 -中选择一个符号添加在前面。 7 | 返回可以使最终数组和为目标数 S 的所有添加符号的方法数。 8 | */ 9 | class P494TargetSum { 10 | public int findTargetSumWays(int[] nums, int S) { 11 | int sum = 0, target; 12 | for (int num : nums) { 13 | sum += num; 14 | } 15 | // if (sum == S) return 1; 16 | if (sum < S || (sum + S) % 2 != 0) return 0; 17 | // 转化成 01 背包问题 18 | target = (sum + S) / 2; 19 | int[] dp = new int[target+1]; 20 | dp[0] = 1; 21 | for (int num : nums) { 22 | for (int i = target; i >= num; i--) { // 从后往前 23 | dp[i] += dp[i-num]; 24 | } 25 | } 26 | return dp[target]; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/dp/knapsack/P518CoinChange2.java: -------------------------------------------------------------------------------- 1 | package dp.knapsack; 2 | 3 | import java.util.Arrays; 4 | 5 | /** 6 | * Title: 518. 零钱兑换2(完全背包的零钱兑换)—— 有多少种可能 7 | * Desc: 给定不同面额的硬币和一个总金额。写出函数来计算可以凑成总金额的硬币组合数。 8 | * Created by Myth on 12/19/2019 in VSCode 9 | */ 10 | 11 | public class P518CoinChange2 { 12 | public int change(int amount, int[] coins) { 13 | // if (amount == 0) return 0; 14 | int[] dp = new int[amount+1]; 15 | dp[0] = 1; 16 | for (int coin : coins) { 17 | for (int i = 0; i <= amount; i++) { 18 | if(i >= coin) dp[i] += dp[i-coin]; 19 | } 20 | } 21 | return dp[amount]; 22 | } 23 | public static void main(String[] args) { 24 | P518CoinChange2 p518 = new P518CoinChange2(); 25 | int[] coins = {1, 2, 5}; 26 | int[] coins2 = {10}; 27 | System.out.println(p518.change(10, coins2)); 28 | } 29 | } -------------------------------------------------------------------------------- /src/dp/knapsack/P813LargestSumOfAverages.java: -------------------------------------------------------------------------------- 1 | package dp.knapsack; 2 | 3 | /** 4 | * Title: 813. 最大平均值和的分组 5 | * Desc: 6 | * Created by Myth on 12/17/2019 in VSCode 7 | */ 8 | 9 | public class P813LargestSumOfAverages { 10 | public double largestSumOfAverages(int[] A, int K) { 11 | int n = A.length; 12 | // 开辟n+1的目的是让下标对应关系更清晰 13 | double[][] dp = new double[K+1][n+1]; 14 | double[] sum = new double[n+1]; 15 | for (int i = 1; i <= n; i++) { 16 | sum[i] = sum[i-1] + (double)A[i-1]; 17 | dp[1][i] = sum[i] / (double)(i); 18 | } 19 | for (int i = 2; i <= K; i++) { 20 | for (int j = 1; j <= n; j++) { 21 | for (int p = i-1; p < j; p++) { 22 | dp[i][j] = Math.max(dp[i][j], dp[i-1][p] + ((sum[j]-sum[p])/(j-p))); 23 | } 24 | } 25 | } 26 | return dp[K][n]; 27 | } 28 | public static void main(String[] args) { 29 | P813LargestSumOfAverages p813 = new P813LargestSumOfAverages(); 30 | int[] A = {9, 1, 2, 3, 9}; 31 | System.out.println(p813.largestSumOfAverages(A, 3)); 32 | } 33 | } -------------------------------------------------------------------------------- /src/dp/knapsack/README.md: -------------------------------------------------------------------------------- 1 | # 背包问题 2 | 最原始的01背包问题 3 | 有一些物品每个只有一件,w[i]是重量,v[i]是价值,给一个可以装W的包,问可以装的最大价值是多少? 4 | 5 | 解决思路: 6 | 1. 使用二维DP, 背包容量作为横轴,每一个元素的下标竖轴(计算的时候要使用w[i]) 7 | 2. 数组(矩阵)中存储的是:价值(当前状态下可以装的物品的最大价值) 8 | 3. 赋值的过程,从上到下,从右到左,从上到下代表 前i个物品 9 | 10 | 例子:背包W=4,货物w为[1,1,2,2] 相应价值[1,2,4,5] 11 | 12 | # 01背包 13 | 14 | 滚动数组,从后往前更新 15 | ```Java 16 | // n个商品,背包W, w商品重量数组 v价值数组 17 | void knapsack01(int n, int W, int[] w, int[] v) { 18 | dp[W+1] 19 | // int[] count = new int[W+1] 20 | for (int i = 0; i < n; i++) { 21 | for (int j = W; j >= w[i]; j--) { 22 | // if (dp[j] <= dp[j-w[i]]+v) count[j] = count[j-w[j]]+1 23 | // 限制条件,如果有多个限制条件,多几个循环 24 | dp[j] = max(dp[j], dp[j-w[i]]+v) 25 | } 26 | } 27 | return dp[W] // max(dp[N]) 28 | } 29 | ``` 30 | # 完全背包 31 | 32 | 滚动数组,从前往后更新 33 | ```Java 34 | void knapsackComplete(w, v) { 35 | dp[W+1] 36 | // int[] count = new int[W+1] 37 | for (int i = 0; i < n; i++) { 38 | // 注意此处 39 | for (int j = w[i]; j <= W; j++) { 40 | // if (dp[j] <= dp[j-w[i]]+v) count[j] = count[j-w[j]]+1 41 | // 限制条件,如果有多个限制条件,多几个循环 42 | dp[j] = max(dp[j], dp[j-w[i]]+v) 43 | } 44 | } 45 | return dp[W] // max(dp[N]) 46 | } 47 | ``` -------------------------------------------------------------------------------- /src/dp/partition/P87ScrambleString.java: -------------------------------------------------------------------------------- 1 | package dp.partition; 2 | 3 | /** 4 | * Title: 87. 扰乱字符串(划分型DP Hard) 5 | * Desc: 6 | * Created by Myth on 12/22/2019 in VSCode 7 | */ 8 | 9 | public class P87ScrambleString { 10 | // S1(分成S11、S12)和S2(分成S21、S22)是 Scramble 11 | // S11和S21是Scramble && S12和S22是Scramble 12 | // 或者 S11和S22是Scramble && S12和S21是Scramble 13 | // 这个思路可以用递归(记忆化搜索) 14 | public boolean isScramble(String s1, String s2) { 15 | if (s1.length() != s2.length()) return false; 16 | int n = s1.length(); 17 | if (n == 0) return true; 18 | // dp[i][j][k]:s1[i..i+k) 和 s2[j...j+k) 是否为scramble 19 | boolean[][][] dp = new boolean[n][n][n+1]; 20 | for(int i = 0; i < n; i++) { 21 | for(int j = 0; j < n; j++) { 22 | dp[i][j][1] = s1.charAt(i) == s2.charAt(j); 23 | } 24 | } 25 | for(int len = 2; len <= n; len++) { 26 | for(int i = 0; i < n-len+1; i++) { 27 | for(int j = 0; j < n-len+1; j++) { 28 | for(int k = 1; k < len; k++) { // 划分 29 | dp[i][j][len] |= (dp[i][j][k] && dp[i+k][j+k][len-k]) || (dp[i][j+len-k][k] && dp[i+k][j][len-k]); 30 | } 31 | } 32 | } 33 | } 34 | return dp[0][0][n]; 35 | } 36 | } -------------------------------------------------------------------------------- /src/dp/partition/README.md: -------------------------------------------------------------------------------- 1 | 划分类DP比记忆化搜索更难写一些,因为边界不容易控制 2 | 3 | 边界控制可以考虑多开辟一行(或一列)空间,避免条件判断 4 | 5 | -------------------------------------------------------------------------------- /src/dp/seq/FrogJump2.java: -------------------------------------------------------------------------------- 1 | package dp.seq; 2 | 3 | import java.util.Arrays; 4 | 5 | /** 6 | * Title: 跳台阶 7 | * Desc: A Frog jumps K steps at most. It costs `A[i]` to stays at `i`. 8 | * Return the min cost to get to `N-1` from `0` 9 | * Created by Myth on 01/14/2020 in VSCode 10 | */ 11 | 12 | public class FrogJump2 { 13 | 14 | public int jump(int[] costs, int K) { 15 | int n = costs.length; 16 | int[] dp = new int[n]; 17 | Arrays.fill(dp, Integer.MAX_VALUE); 18 | for (int i = 0; i <= K && i < n; i++) { 19 | dp[i] = costs[i]; 20 | } 21 | 22 | for (int i = K+1; i < n; i++) { 23 | for (int j = 1; j <= K; j++) { 24 | dp[i] = Math.min(dp[i], dp[i-j]+costs[i]); 25 | } 26 | } 27 | System.out.println(Arrays.toString(dp)); 28 | return dp[n-1]; 29 | 30 | } 31 | public static void main(String[] args) { 32 | int[] costs = {0, 3, 2, 7, 1, 4}; 33 | FrogJump2 frogJump2 = new FrogJump2(); 34 | frogJump2.jump(costs, 2); 35 | } 36 | 37 | } -------------------------------------------------------------------------------- /src/dp/seq/P303RangeSumQueryImmutable.java: -------------------------------------------------------------------------------- 1 | package dp.seq; 2 | 3 | /** 4 | * Title: 303. 区域和检索 - 数组不可变 5 | * Desc: 会多次调用 sumRange 方法。 6 | * 给定一个整数数组  nums,求出数组从索引 i 到 j  (i ≤ j) 范围内元素的总和,包含 i,  j 两点。 7 | 示例: 8 | 给定 nums = [-2, 0, 3, -5, 2, -1],求和函数为 sumRange() 9 | 10 | sumRange(0, 2) -> 1 11 | sumRange(2, 5) -> -1 12 | sumRange(0, 5) -> -3 13 | 14 | * Created by Myth-Lab on 11/20/2019 15 | */ 16 | public class P303RangeSumQueryImmutable { 17 | // 会多次调用sumRange,所以进行一个记忆化存储 18 | class NumArray { 19 | int[] numsSum; 20 | public NumArray(int[] nums) { 21 | numsSum = new int[nums.length]; 22 | if (nums.length == 0) return; 23 | int sum = 0; 24 | numsSum[0] = nums[0]; 25 | for (int i = 1; i < nums.length; i++) { 26 | numsSum[i] = numsSum[i-1] + nums[i]; 27 | } 28 | } 29 | // range(i,j) = range(j)-range(i-1) 30 | public int sumRange(int i, int j) { 31 | if (i == 0) return numsSum[j]; 32 | return numsSum[j]-numsSum[i-1]; 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/dp/seq/P32LongestValidParentheses.java: -------------------------------------------------------------------------------- 1 | package dp.seq; 2 | 3 | import java.util.Stack; 4 | 5 | /** 6 | * Title: 32. 最长有效括号(DP 解法) 7 | * Desc: 给定一个只包含 '(' 和 ')' 的字符串,找出最长的包含有效括号的 子串的长度。 8 | * Created by Myth on 01/06/2020 in VSCode 9 | */ 10 | 11 | public class P32LongestValidParentheses { 12 | // i-1和i为"()"情况 dp[i] = dp[i-2]+2 13 | // i-1和i为 "))" 要看i-1位置的")" 匹配了多少, 且要看 i-dp[i-1]-1位置是否为 "(" 则 dp[i] = dp[i-1] + 2+ dp[i-dp[i-1]-1-1] 14 | public int longestValidParentheses(String s) { 15 | int maxans = 0; 16 | int dp[] = new int[s.length()]; 17 | for (int i = 1; i < s.length(); i++) { 18 | if (s.charAt(i) == ')') { 19 | if (s.charAt(i - 1) == '(') { 20 | dp[i] = (i >= 2 ? dp[i - 2] : 0) + 2; 21 | } else if (i - dp[i - 1] > 0 && s.charAt(i - dp[i - 1] - 1) == '(') { 22 | dp[i] = dp[i - 1] + ((i - dp[i - 1]) >= 2 ? dp[i - dp[i - 1] - 2] : 0) + 2; 23 | } 24 | maxans = Math.max(maxans, dp[i]); 25 | } 26 | } 27 | return maxans; 28 | } 29 | 30 | 31 | public static void main(String[] args) { 32 | P32LongestValidParentheses p32 = new P32LongestValidParentheses(); 33 | System.out.println(p32.longestValidParentheses("")); 34 | System.out.println(p32.longestValidParentheses("(")); 35 | System.out.println(p32.longestValidParentheses("()")); 36 | System.out.println(p32.longestValidParentheses(")(")); 37 | System.out.println(p32.longestValidParentheses(")()())")); 38 | } 39 | } -------------------------------------------------------------------------------- /src/dp/seq/P746MinCostClimbingStairs.java: -------------------------------------------------------------------------------- 1 | package dp.seq; 2 | 3 | /** 4 | * Title: 746. 使用最小花费爬楼梯(70题的进阶版) 5 | * Desc: 数组的每个索引做为一个阶梯,第 i个阶梯对应着一个非负数的体力花费值 cost[i](索引从0开始)。 6 | * 每当你爬上一个阶梯你都要花费对应的体力花费值,然后你可以选择继续爬一个阶梯或者爬两个阶梯。 7 | * 您需要找到达到楼层顶部的最低花费。在开始时,你可以选择从索引为 0 或 1 的元素作为初始阶梯。 8 | * Created by Myth-Lab on 11/14/2019 9 | */ 10 | public class P746MinCostClimbingStairs { 11 | public int minCostClimbingStairs(int[] cost) { 12 | int n = cost.length; 13 | int[] dp = new int[n+1]; 14 | dp[0] = cost[0]; 15 | dp[1] = cost[1]; 16 | 17 | for (int i = 2; i < n; i++) { 18 | dp[i] = Math.min(dp[i-1], dp[i-2]) + cost[i]; 19 | } 20 | dp[n] = Math.min(dp[n-1], dp[n-2]); 21 | return dp[n]; 22 | } 23 | public int minCostClimbingStairs2(int[] cost) { 24 | int n = cost.length; 25 | int dp0 = cost[0], dp1 = cost[1], temp; 26 | 27 | for (int i = 2; i < n; i++) { 28 | temp = dp0; 29 | dp0 = dp1; 30 | dp1 = Math.min(temp, dp0) + cost[i]; 31 | } 32 | return Math.min(dp0, dp1); 33 | } 34 | 35 | public static void main(String[] args) { 36 | P746MinCostClimbingStairs p746 = new P746MinCostClimbingStairs(); 37 | int[] cost = {1, 100, 1, 1, 1, 100, 1, 1, 100, 1}; 38 | int[] cost2 = {10, 15, 20}; 39 | System.out.println(p746.minCostClimbingStairs2(cost2)); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/dp/seq/P91DecodeWays.java: -------------------------------------------------------------------------------- 1 | package dp.seq; 2 | 3 | /** 4 | * Title: 91. 解码方法(剑指offer46题) 5 | * Desc: 一条包含字母 A-Z 的消息通过以下方式进行了编码: 6 | 'A' -> 1 7 | 'B' -> 2 8 | ... 9 | 'Z' -> 26 10 | 给定一个只包含数字的非空字符串,请计算解码方法的总数。 11 | 12 | * Created by Myth on 12/25/2019 in VSCode 13 | */ 14 | 15 | public class P91DecodeWays { 16 | public int numDecodings(String s) { 17 | int n = s.length(); 18 | if (n == 0) return 0; 19 | int[] dp = new int[n+1]; 20 | dp[0] = 1; 21 | if (s.charAt(0) == '0') return 0; 22 | dp[1] = 1; 23 | // 注意处理 0 24 | for (int i = 2; i <= n; i++) { 25 | if (s.charAt(i-1)-'0' == 0) { 26 | if (s.charAt(i-2)-'0' == 0) return 0; // 不能有两个连续的0 27 | if ((s.charAt(i-2)-'0')*10 <= 20) dp[i] = dp[i-2]; 28 | else dp[i] = 0; // 可省略 29 | } else if (s.charAt(i-2)-'0' == 0 || (s.charAt(i-2)-'0')*10+(s.charAt(i-1)-'0') > 26) { 30 | dp[i] = dp[i-1]; 31 | } else { 32 | dp[i] = dp[i-1] + dp[i-2]; 33 | } 34 | } 35 | // System.out.println(Arrays.toString(dp) ); 36 | return dp[n]; 37 | } 38 | public static void main(String[] args) { 39 | P91DecodeWays p91 = new P91DecodeWays(); 40 | System.out.println(p91.numDecodings("30")); 41 | } 42 | } -------------------------------------------------------------------------------- /src/dp/seq/README.md: -------------------------------------------------------------------------------- 1 | # 单序列类DP 2 | 3 | ## 子序列/子串问题 4 | 子序列问题一般是转化成双序列问题(二维)来解决的,只不过习惯于使用滚动数组进行状态压缩而已 5 | 6 | ## 经典股票题 7 | 其实股票题目是一个**多状态的DP**问题 8 | 9 | ## 多状态的DP 10 | - 经典的**打家劫舍**题 11 | - 铺地板问题 12 | 13 | ## 未分类 14 | 经典题 15 | - 爬楼梯 16 | - 单词拆分 17 | - 跳跃(最多K步,跳到终点的最小cost) 18 | - 不同情况,递推公式不同:91. 编码方式(剑指offer第46题) 19 | -------------------------------------------------------------------------------- /src/dp/seq/mulstates/P213HouseRobber2.java: -------------------------------------------------------------------------------- 1 | package dp.seq.mulstates; 2 | 3 | import java.util.Arrays; 4 | 5 | /** 6 | * Title: 213. 打家劫舍 II 7 | * Desc: 和198的区别在于本题是首尾相连的 8 | * Created by Myth-Lab on 11/22/2019 9 | */ 10 | public class P213HouseRobber2 { 11 | // 分成两个问题:不偷第一个,不偷最后一个 12 | // 难点:如何转化成两部分? nums(0:] nums[:n-1) 13 | // 和第198题一样,本次使用滚动数组 14 | public int robOne(int[] nums) { 15 | int n = nums.length; 16 | if (n == 0) return 0; 17 | if (n == 1) return nums[0]; 18 | int pre = 0, cur = nums[0], temp; 19 | for (int i = 1; i < n; i++) { 20 | temp = pre; 21 | pre = cur; 22 | cur = Math.max(temp+nums[i], cur); 23 | } 24 | return cur; 25 | } 26 | public int rob(int[] nums) { 27 | int n = nums.length; 28 | if (n == 0) return 0; 29 | if (n == 1) return nums[0]; 30 | return Math.max(robOne(Arrays.copyOfRange(nums, 1, n)), robOne(Arrays.copyOfRange(nums, 0, n-1))); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/dp/seq/mulstates/P740DeleteAndEarn.java: -------------------------------------------------------------------------------- 1 | package dp.seq.mulstates; 2 | 3 | /** 4 | * Title: 740. 删除与获得点数(难点:如何变成打家劫舍问题) 5 | * Desc: 打家劫舍(选择i,就不能选择相邻的)的变体 6 | * Created by Myth on 12/29/2019 in VSCode 7 | */ 8 | 9 | public class P740DeleteAndEarn { 10 | // 通过计数变成 198题打家劫舍问题 11 | public int deleteAndEarn(int[] nums) { 12 | // 构建新数组T = O(n); S = O(max) 13 | int max = 0; 14 | for (int i = 0; i < nums.length; i++) { 15 | max = Math.max(max, nums[i]); 16 | } 17 | if (max == 0) return 0; 18 | int[] arr = new int[max+1]; 19 | int[] dp = new int[max+1]; 20 | for (int i = 0; i < nums.length; i++) { 21 | arr[nums[i]]++; 22 | } 23 | dp[0] = 0; 24 | dp[1] = arr[1]; 25 | for (int i = 2; i <= max; i++) { 26 | dp[i] = Math.max(dp[i-1], dp[i-2] + arr[i]*i); 27 | } 28 | return dp[max]; 29 | } 30 | 31 | } -------------------------------------------------------------------------------- /src/dp/seq/stock/P121BuyAndSellStock.java: -------------------------------------------------------------------------------- 1 | package dp.seq.stock; 2 | 3 | /** 4 | * Title: 121.买卖股票的最佳时机 5 | * Desc: (剑指offer 63题) 6 | * Created by Myth-Lab on 11/20/2019 7 | */ 8 | public class P121BuyAndSellStock { 9 | // 保存前面的profit, 保存minVal 10 | public int maxProfit(int[] prices) { 11 | int profit = 0, minVal = Integer.MAX_VALUE; 12 | for (int i = 0; i < prices.length; i++) { 13 | if (prices[i] <= minVal) minVal = prices[i]; 14 | else profit = Math.max(profit, prices[i]-minVal); 15 | } 16 | return profit; 17 | } 18 | // TODO 多状态类的转移方程 19 | public int maxProfit2(int[] prices) { 20 | return 0; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/dp/seq/stock/P188BuyAndSellStock4.java: -------------------------------------------------------------------------------- 1 | package dp.seq.stock; 2 | 3 | 4 | /** 5 | * Title: 188. 买卖股票的最佳时机 IV 6 | * Desc: 设计一个算法来计算你所能获取的最大利润。你最多可以完成 k 笔交易。 7 | * Created by Myth-MBP on 04/07/2020 in VSCode 8 | */ 9 | 10 | public class P188BuyAndSellStock4 { 11 | int maxProfit(int max_k, int[] prices) { 12 | int n = prices.length; 13 | if (max_k > n / 2) 14 | return maxProfit_k_inf(prices); 15 | 16 | int[][][] dp = new int[n][max_k + 1][2]; 17 | for (int i = 0; i < n; i++) 18 | for (int k = max_k; k >= 1; k--) { 19 | if (i - 1 == -1) { 20 | dp[i][k][0] = 0; 21 | dp[i][k][1] = -prices[i]; 22 | continue; 23 | } 24 | dp[i][k][0] = Math.max(dp[i-1][k][0], dp[i-1][k][1] + prices[i]); 25 | dp[i][k][1] = Math.max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i]); 26 | } 27 | return dp[n - 1][max_k][0]; 28 | } 29 | int maxProfit_k_inf(int[] prices) { 30 | int n = prices.length; 31 | int dp_i_0 = 0, dp_i_1 = Integer.MIN_VALUE; 32 | for (int i = 0; i < n; i++) { 33 | int temp = dp_i_0; 34 | dp_i_0 = Math.max(dp_i_0, dp_i_1 + prices[i]); 35 | dp_i_1 = Math.max(dp_i_1, temp - prices[i]); 36 | } 37 | return dp_i_0; 38 | } 39 | } -------------------------------------------------------------------------------- /src/dp/seq/stock/P309StockWithCooldown.java: -------------------------------------------------------------------------------- 1 | package dp.seq.stock; 2 | 3 | /** 4 | * Title: 309. 最佳买卖股票时机含冷冻期 5 | * Desc: 给定一个整数数组,其中第 i 个元素代表了第 i 天的股票价格 。​ 6 | * 7 | * 设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票): 8 | * 9 | * 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。 10 | * 卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。 11 | * 12 | * Created by Myth-Lab on 11/22/2019 13 | */ 14 | public class P309StockWithCooldown { 15 | // TODO 股票6题: 多状态讲解 16 | // https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-with-cooldown/solution/yi-ge-fang-fa-tuan-mie-6-dao-gu-piao-wen-ti-by-lab/ 17 | // https://leetcode.com/problems/best-time-to-buy-and-sell-stock-with-transaction-fee/discuss/108870/Most-consistent-ways-of-dealing-with-the-series-of-stock-problems 18 | // 多状态转移方程 19 | // dp[i][0] (第i天没持股票时的利润) = Math.max(dp[i-1][0], dp[i-1][1]+prices[i]) 卖出股票 利润++ 20 | // dp[i][1] (第i天持有股票时的利润) = Math.max(dp[i-1][1], dp[i-2][0]-prices[i]) 买入股票利润-- 21 | // TODO:Study这个滚动数组 22 | public int maxProfit(int[] prices) { 23 | int noStock = 0, haveStock = Integer.MIN_VALUE; 24 | int noStockPre = 0, temp; 25 | for (int i = 0; i < prices.length; i++) { 26 | temp = noStock; 27 | noStock = Math.max(noStock, haveStock+prices[i]); 28 | haveStock = Math.max(haveStock, noStockPre-prices[i]); 29 | noStockPre = temp; 30 | 31 | } 32 | return noStock; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/dp/seq/stock/P714StockWithTransactionFee.java: -------------------------------------------------------------------------------- 1 | package dp.seq.stock; 2 | 3 | 4 | /** 5 | * Title: 714. 714. 买卖股票的最佳时机含手续费 6 | * Desc: 你可以无限次地完成交易,但是你每笔交易都需要付手续费。如果你已经购买了一个股票,在卖出它之前你就不能再继续购买股票了。 7 | 返回获得利润的最大值。 8 | 输入: prices = [1, 3, 2, 8, 4, 9], fee = 2 9 | 输出: 8 10 | 解释: 能够达到的最大利润: 11 | 在此处买入 prices[0] = 1 12 | 在此处卖出 prices[3] = 8 13 | 在此处买入 prices[4] = 4 14 | 在此处卖出 prices[5] = 9 15 | 总利润: ((8 - 1) - 2) + ((9 - 4) - 2) = 8. 16 | * Created by Myth-MBP on 04/07/2020 in VSCode 17 | */ 18 | 19 | public class P714StockWithTransactionFee { 20 | int maxProfit(int[] prices, int fee) { 21 | int n = prices.length; 22 | int dp_i_0 = 0, dp_i_1 = Integer.MIN_VALUE; 23 | for (int i = 0; i < n; i++) { 24 | int temp = dp_i_0; 25 | dp_i_0 = Math.max(dp_i_0, dp_i_1 + prices[i]); 26 | dp_i_1 = Math.max(dp_i_1, temp - prices[i] - fee); 27 | } 28 | return dp_i_0; 29 | } 30 | } -------------------------------------------------------------------------------- /src/dp/seq/sub_seq/P53MaximumSubarray.java: -------------------------------------------------------------------------------- 1 | package dp.seq.sub_seq; 2 | 3 | /** 4 | * Title: 53. 最大子序和(剑指offer42) 5 | * Desc: 给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。 6 | * Created by Myth-Lab on 11/20/2019 7 | */ 8 | public class P53MaximumSubarray { 9 | // 本题也是一个动态规划问题,其中curSum就保存了之前的状态 10 | public int maxSubArray(int[] nums) { 11 | // 注意初值 12 | int maxSum = Integer.MIN_VALUE; 13 | int curSum = 0; 14 | for (int i = 0; i < nums.length; i++) { 15 | if (curSum + nums[i] < nums[i]) { 16 | curSum = nums[i]; 17 | } else { 18 | curSum = curSum + nums[i]; 19 | } 20 | maxSum = Math.max(maxSum, curSum); 21 | } 22 | return maxSum; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/dp/seq/sub_seq/README.md: -------------------------------------------------------------------------------- 1 | ## DP 子串子序列的题目总结 2 | 这几个题目都是比较经典的,需要注意的是,子串(序列)类,一般是个二维DP,只不过有时候状态压缩,变成一维的了 3 | 4 | 子串一般是双指针,子序列一般是DP填表 5 | 6 | 超经典的是:**最长子序列**,最长公共子串(见双序列章节) 7 | 8 | 一般是按列填表,表内容一般是长度、布尔值 -------------------------------------------------------------------------------- /src/dp/twoseq/P115DistinctSubsequences.java: -------------------------------------------------------------------------------- 1 | package dp.twoseq; 2 | 3 | /** 4 | * Title: 115. 不同的子序列 5 | * Desc: 给定一个字符串 S 和一个字符串 T,计算在 S 的子序列中 T 出现的个数。 6 | * 重点是S中以哪个字符结尾!! 7 | * Created by Myth-Lab on 12/2/2019 8 | */ 9 | public class P115DistinctSubsequences { 10 | public int numDistinct(String s, String t) { 11 | int n = s.length(), m = t.length(); 12 | if (m > n) return 0; 13 | int[][] dp = new int[n+1][m+1]; 14 | for (int i = 0; i <= n; i++) { 15 | dp[i][0] = 1; 16 | } 17 | for (int i = 1; i <= n; i++) { 18 | for (int j = 1; j <= m && j <= i; j++) { 19 | if (s.charAt(i-1) != t.charAt(j-1)) { 20 | dp[i][j] = dp[i-1][j]; 21 | } else { 22 | // 不以S的当前字符为结尾 + 以S的当前字符结尾 23 | dp[i][j] = dp[i-1][j] + dp[i-1][j-1]; 24 | } 25 | } 26 | } 27 | return dp[n][m]; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/dp/twoseq/P44WildcardMatching.java: -------------------------------------------------------------------------------- 1 | package dp.twoseq; 2 | 3 | /** 4 | * Title: 44. 通配符匹配 5 | * Desc: 给定一个字符串 (s) 和一个字符模式 (p) ,实现一个支持 '?' 和 '*' 的通配符匹配。 6 | * 7 | * '?' 可以匹配任何单个字符。 8 | * '*' 可以匹配任意字符串(包括空字符串)。 9 | * 10 | * Created by Myth-Lab on 11/29/2019 11 | */ 12 | public class P44WildcardMatching { 13 | 14 | public boolean isMatch(String s, String p) { 15 | int n = s.length(), m = p.length(); 16 | boolean[][] dp = new boolean[n+1][m+1]; 17 | dp[0][0] = true; 18 | // init dp[0][] dp[][0] 19 | for (int i = 1; i <= m; i++) { 20 | if (dp[0][i-1] && p.charAt(i-1) == '*') dp[0][i] = true; 21 | } 22 | for (int i = 0; i < n; i++) { // s[0:i] 23 | for (int j = 0; j < m; j++) { // p[0:j] 24 | if (s.charAt(i) == p.charAt(j) || p.charAt(j) == '?') dp[i+1][j+1] = dp[i][j]; 25 | else if (p.charAt(j) == '*') { 26 | dp[i+1][j+1] = (dp[i][j] || dp[i+1][j] || dp[i][j+1]); // 匹配单个串,或者匹配多个串 27 | } 28 | } 29 | } 30 | return dp[n][m]; 31 | } 32 | 33 | public static void main(String[] args) { 34 | P44WildcardMatching p44 = new P44WildcardMatching(); 35 | System.out.println(p44.isMatch("aa", "a")); 36 | System.out.println(p44.isMatch("aa", "*")); 37 | System.out.println(p44.isMatch("ca", "?a")); 38 | System.out.println(p44.isMatch("adceb", "a*c?b")); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/dp/twoseq/P583DeleteOperationforTwoStrings.java: -------------------------------------------------------------------------------- 1 | package dp.twoseq; 2 | 3 | /** 4 | * Title: 583. 两个字符串的删除操作 5 | * Desc: 给定两个单词 word1 和 word2,找到使得 word1 和 word2 相同所需的最小步数,每步可以删除任意一个字符串中的一个字符。 6 | * Created by Myth-Lab on 12/4/2019 7 | */ 8 | public class P583DeleteOperationforTwoStrings { 9 | public int minDistance(String word1, String word2) { 10 | int n = word1.length(), m = word2.length(); 11 | int[][] dp = new int[n+1][m+1]; 12 | for (int i = 0; i <= n; i++) { 13 | dp[i][0] = i; 14 | } 15 | for (int i = 0; i <= m; i++) { 16 | dp[0][i] = i; 17 | } 18 | 19 | for (int i = 1; i <= n; i++) { 20 | for (int j = 1; j <= m; j++) { 21 | if(word1.charAt(i-1) == word2.charAt(j-1)) dp[i][j] = dp[i-1][j-1]; 22 | else dp[i][j] = Math.min(dp[i-1][j], dp[i][j-1]) + 1; 23 | } 24 | } 25 | // for (int i = 0; i <= n; i++) { 26 | // for (int j = 0; j <= m; j++) { 27 | // System.out.print(dp[i][j] + " "); 28 | // } 29 | // System.out.println(); 30 | // } 31 | return dp[n][m]; 32 | } 33 | 34 | public static void main(String[] args) { 35 | P583DeleteOperationforTwoStrings p583 = new P583DeleteOperationforTwoStrings(); 36 | p583.minDistance("sea", "eat"); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/dp/twoseq/P712MinimumASCIIDeleteSumForTwoStrings.java: -------------------------------------------------------------------------------- 1 | package dp.twoseq; 2 | 3 | /** 4 | * Title: 712. 两个字符串的最小ASCII删除和 5 | * Desc: 给定两个字符串s1, s2,找到使两个字符串相等所需删除字符的ASCII值的最小和。所有字符串中的字符ASCII值在[97, 122]之间。 6 | * Created by Myth-Lab on 12/4/2019 7 | */ 8 | public class P712MinimumASCIIDeleteSumForTwoStrings { 9 | public int minimumDeleteSum(String s1, String s2) { 10 | int n = s1.length(), m = s2.length(); 11 | int[][] dp = new int[n+1][m+1]; 12 | for (int i = 1; i <= n; i++) { 13 | dp[i][0] = dp[i-1][0] + s1.charAt(i-1); 14 | } 15 | for (int i = 1; i <= m; i++) { 16 | dp[0][i] = dp[0][i-1] + s2.charAt(i-1); 17 | } 18 | 19 | for (int i = 1; i <= n; i++) { 20 | for (int j = 1; j <= m; j++) { 21 | if(s1.charAt(i-1) == s2.charAt(j-1)) dp[i][j] = dp[i-1][j-1]; 22 | else dp[i][j] = Math.min(dp[i - 1][j] + s1.charAt(i - 1), dp[i][j - 1] + s2.charAt(j - 1)); 23 | } 24 | } 25 | // for (int i = 0; i <= n; i++) { 26 | // for (int j = 0; j <= m; j++) { 27 | // System.out.print(dp[i][j] + " "); 28 | // } 29 | // System.out.println(); 30 | // } 31 | return dp[n][m]; 32 | } 33 | 34 | public static void main(String[] args) { 35 | P712MinimumASCIIDeleteSumForTwoStrings p712 = new P712MinimumASCIIDeleteSumForTwoStrings(); 36 | p712.minimumDeleteSum("delete", "leet"); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/dp/twoseq/P718MaximumLengthOfRepeatedSubarray.java: -------------------------------------------------------------------------------- 1 | package dp.twoseq; 2 | 3 | /** 4 | * Title: 718. 最长重复子数组(最长公共子序列1143题) 5 | * Desc: 给两个整数数组 A 和 B ,返回两个数组中公共的、长度最长的子数组的长度。(注意区分子串与子序列) 6 | * Created by Myth-Lab on 12/3/2019 7 | */ 8 | public class P718MaximumLengthOfRepeatedSubarray { 9 | 10 | public int findLength(int[] A, int[] B) { 11 | int n = A.length, m = B.length; 12 | int[][] dp = new int[n+1][m+1]; 13 | int result = 0; // 勿忘此处 14 | for (int i = 1; i <= n; i++) { 15 | for (int j = 1; j <= m; j++) { 16 | if(A[i-1] == B[j-1]) { 17 | dp[i][j] = dp[i-1][j-1] + 1; 18 | result = Math.max(result, dp[i][j]); 19 | } 20 | 21 | } 22 | } 23 | return dp[n][m]; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/dp/twoseq/P72EditDistance.java: -------------------------------------------------------------------------------- 1 | package dp.twoseq; 2 | 3 | /** 4 | * Title: 72. 编辑距离 5 | * Desc: 给你两个单词 word1 和 word2,请你计算出将 word1 转换成 word2 所使用的最少操作数 。 6 | 7 | 你可以对一个单词进行如下三种操作: 8 | 9 | 插入一个字符 10 | 删除一个字符 11 | 替换一个字符 12 | 13 | * 画出二维表格更便于理解 14 | * 15 | 输入:word1 = "intention", word2 = "execution" 16 | 输出:5 17 | 解释: 18 | intention -> inention (删除 't') 19 | inention -> enention (将 'i' 替换为 'e') 20 | enention -> exention (将 'n' 替换为 'x') 21 | exention -> exection (将 'n' 替换为 'c') 22 | exection -> execution (插入 'u') 23 | 24 | * Created by Myth-Lab on 11/27/2019 25 | */ 26 | public class P72EditDistance { 27 | // 28 | public int minDistance(String word1, String word2) { 29 | int n = word1.length(), m = word2.length(), i, j; 30 | int[][] dp = new int[n+1][m+1]; 31 | for (i = 0; i <= n; i++) { 32 | dp[i][0] = i; 33 | } 34 | for (j = 0; j <= m; j++) { 35 | dp[0][j] = j; 36 | } 37 | for (i = 1; i <= n; i++) { 38 | for (j = 1; j <= m; j++) { 39 | if (word1.charAt(i-1) == word2.charAt(j-1)) dp[i][j] = dp[i-1][j-1]; 40 | else dp[i][j] = Math.min(dp[i-1][j-1], Math.min(dp[i-1][j], dp[i][j-1])) + 1; 41 | } 42 | } 43 | return dp[n][m]; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/dp/twoseq/P97InterleavingString.java: -------------------------------------------------------------------------------- 1 | package dp.twoseq; 2 | 3 | /** 4 | * Title: 97. 交错字符串 5 | * Desc: 给定三个字符串 s1, s2, s3, 验证 s3 是否是由 s1 和 s2 交错组成的。 6 | * Created by Myth-Lab on 12/1/2019 7 | */ 8 | public class P97InterleavingString { 9 | public boolean isInterleave(String s1, String s2, String s3) { 10 | int n = s1.length(), m = s2.length(), l = s3.length(); 11 | if (n + m != l) return false; 12 | boolean[][] dp = new boolean[n+1][m+1]; 13 | dp[0][0] = true; 14 | for (int i = 1; i <= m; i++) { 15 | dp[0][i] = (dp[0][i-1] && s2.charAt(i-1) == s3.charAt(i-1)); 16 | } 17 | for (int i = 1; i <= n; i++) { 18 | dp[i][0] = (dp[i-1][0] && s1.charAt(i-1) == s3.charAt(i-1)); 19 | } 20 | // 填表 21 | for (int i = 1; i <= n; i++) { 22 | for (int j = 1; j <= m; j++) { 23 | dp[i][j] = (s1.charAt(i-1) == s3.charAt(i+j-1) && dp[i-1][j]) || (s2.charAt(j-1) == s3.charAt(i+j-1) && dp[i][j-1]); 24 | } 25 | } 26 | return dp[n][m]; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/dp/twoseq/README.md: -------------------------------------------------------------------------------- 1 | # 双序列DP 2 | 3 | - 正则表达式匹配 4 | - 通配符匹配 5 | - 编辑距离 6 | - 最长公共子序列 -------------------------------------------------------------------------------- /src/graph/P684RedundantConnection.java: -------------------------------------------------------------------------------- 1 | package graph; 2 | 3 | /** 4 | * Title: 684. 冗余连接 5 | * Desc: 无向图,去除一个环上的边,使得图变成树 6 | * 如果有多个答案,则返回二维数组中最后出现的边。 7 | * Created by Myth-Lab on 10/27/2019 8 | */ 9 | public class P684RedundantConnection { 10 | // 并查集 11 | // 如何确保是最后的点 12 | public int find(int i, int[] disjoint) { 13 | while (disjoint[i] != i) { 14 | i = find(disjoint[i], disjoint); 15 | } 16 | return i; 17 | } 18 | public boolean union(int i, int j, int[] disjoint) { 19 | int iParent = find(i, disjoint); 20 | int jParent = find(j, disjoint); 21 | if (iParent != jParent) { 22 | disjoint[iParent] = jParent; 23 | return true; 24 | } 25 | return false; 26 | } 27 | public int[] findRedundantConnection(int[][] edges) { 28 | // 输入的二维数组大小在 3 到 1000。 29 | int n = edges.length, m = edges[0].length; 30 | int[] disjoint = new int[n+1]; 31 | for (int i = 1; i <= n; i++) { 32 | disjoint[i] = i; // 每个结点的父结点都是他自己 33 | } 34 | // 如何确保是最后出现的边 35 | for (int i = 0; i < n; i++) { 36 | int[]uv = edges[i]; 37 | if (!union(uv[0], uv[1], disjoint)) return uv; 38 | } 39 | return new int[0]; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/graph/P785IsGraphBipartite.java: -------------------------------------------------------------------------------- 1 | package graph; 2 | 3 | /** 4 | * Title: 785. 判断二分图 5 | * Desc: 二部图:节点分成两部分,每一条表都是分别来自两个集合;完全二部图,两个集合中的所有点两两都有边 6 | * Created by Myth-Lab on 10/28/2019 7 | */ 8 | public class P785IsGraphBipartite { 9 | // 染色法:相邻的节点未被染色,就染成不一样的颜色,如果被染色,判断是否是相同颜色(相同就跳过,不相同就证明不能分成二分图) 10 | public boolean isBipartite(int[][] graph) { 11 | if (graph == null || graph.length == 0) return false; 12 | int v = graph.length; 13 | int[] colors = new int[v]; // 0未被染色, 1黑 2白 14 | // 要考虑非连通图 15 | for (int i = 0; i < v; i++) { 16 | if (!dfs(graph, i, colors, 0)) return false; 17 | } 18 | return true; 19 | } 20 | private boolean dfs(int[][] graph, int i, int[] colors, int lastColor) { 21 | // 注意,被染色的就不要继续染色了(因为这是自底向上的,被染色的点,其相连的节点肯定被染色了) 22 | // 如果继续对被染色的节点染色,就会导致死循环 23 | if (colors[i] != 0) return colors[i] != lastColor; 24 | colors[i] = lastColor == 1 ? 2 : 1; 25 | for (int j = 0; j < graph[i].length; j++) { 26 | if (!dfs(graph, graph[i][j], colors, colors[i])) return false; 27 | } 28 | return true; 29 | } 30 | 31 | public static void main(String[] args) { 32 | int[][] graph = {{},{3},{},{1},{}}; 33 | P785IsGraphBipartite p785 = new P785IsGraphBipartite(); 34 | System.out.println(p785.isBipartite(graph)); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/graph/README.md: -------------------------------------------------------------------------------- 1 | # 图 2 | 3 | ## 图的表示 4 | 5 | - 邻接矩阵 6 | - 邻接表 7 | 8 | ## 图的遍历 9 | 10 | ### DFS 11 | 12 | - 递归 13 | - 染色(记录、判断状态): 14 | 1. 直接在图上改变图的值 15 | 2. 开辟一个额外的数组(Flag)记录:未被、访问当前搜索树访问过、其他搜索树访问过(注意判断是否满足访问条件) 16 | 17 | ### BFS 18 | 19 | ### 判断联通 20 | Flood BFS DFS 21 | 22 | ## 拓扑排序 23 | 24 | 判断是否有环 25 | 26 | ## 最短路径 27 | 单源最短路径 28 | - Dijkstra算法(单源,无负权)(朴素、堆优化版) 29 | - Bellman-Ford算法(单源,可以判断负权回路)(队列优化:SPFA) 30 | - Floyd算法(多源,无负权) 31 | 32 | ## 最小生成树 33 | Kruskal 34 | Prim 35 | ## 并查集 36 | 并查集是搞连通性问题的,而连通性问题一般都可以通过DFS,BFS去解决,当然并查集的效率会更高些 37 | 38 | 并查集本质上就是一个染色的过程 39 | 40 | -------------------------------------------------------------------------------- /src/graph/dfs/P841KeysAndRooms.java: -------------------------------------------------------------------------------- 1 | package graph.dfs; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * Title: 841. 钥匙和房间 7 | * Desc: 有 N 个房间,开始时你位于 0 号房间。 8 | * 每个房间有不同的号码:0,1,2,...,N-1,并且房间里可能有一些钥匙能使你进入下一个房间。 9 | * 在形式上,对于每个房间 i 都有一个钥匙列表 rooms[i],每个钥匙 rooms[i][j] 由 [0,1,...,N-1] 中的一个整数表示,其中 N = rooms.length。 10 | * 钥匙 rooms[i][j] = v 可以打开编号为 v 的房间。最初,除 0 号房间外的其余所有房间都被锁住。 11 | * 你可以自由地在房间之间来回走动。如果能进入每个房间返回 true,否则返回 false。 12 | * Created by Myth-Lab on 10/23/2019 13 | */ 14 | public class P841KeysAndRooms { 15 | public boolean canVisitAllRooms(List> rooms) { 16 | boolean[] visited = new boolean[rooms.size()]; 17 | visited[0] = true; 18 | dfs(rooms, 0, visited); 19 | for (boolean visit : visited) { 20 | if (!visit) { 21 | return false; 22 | } 23 | } 24 | return true; 25 | } 26 | private void dfs(List> rooms, int i, boolean[] visited) { 27 | for (Integer key : rooms.get(i)) { 28 | if (!visited[key]) { 29 | visited[key] = true; 30 | dfs(rooms, key, visited); 31 | } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/greedy/P435NonOverlappingIntervals.java: -------------------------------------------------------------------------------- 1 | package greedy; 2 | 3 | import java.util.Arrays; 4 | 5 | /** 6 | * Title: 435. 无重叠区间 7 | * Desc: 给定一个区间的集合,找到需要移除区间的最小数量,使剩余区间互不重叠。 注意: 8 | * 可以认为区间的终点总是大于它的起点。 区间 [1,2] 和 [2,3] 的边界相互“接触”,但没有相互重叠。 9 | * Created by Myth on 9/25/2019 10 | */ 11 | public class P435NonOverlappingIntervals { 12 | 13 | // case1 ---prev------ =====cur==== cur.start >= last.end 不删除,更新 last end为当前 end 14 | // case2 =======cur======== cur.start < last.end删除,不更新end 15 | // ----prev----- 16 | // case3 ------prev----- 17 | // =====cur======= 18 | public int eraseOverlapIntervals(int[][] intervals) { 19 | if (intervals.length == 0) { 20 | return 0; 21 | } 22 | // 按照终点排序 23 | Arrays.sort(intervals, (a, b) -> (a[1] - b[1])); 24 | int end = intervals[0][1]; 25 | int count = 1; 26 | // 由于我们事先排了序,不难发现所有与 x 相交的区间必然会与 x 的 end 相交; 27 | // 如果一个区间不想与 x 的 end 相交,它的 start 必须要大于(或等于)x 的 end(x就是上一个区间的end) 28 | for (int i = 1; i < intervals.length; i++) { 29 | if (intervals[i][0] >= end) { 30 | end = intervals[i][1]; // 当前区间是不需要删除的,就更新一下end, 记一下数 31 | count++; 32 | } 33 | } 34 | return intervals.length - count; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/greedy/P45JumpGame2.java: -------------------------------------------------------------------------------- 1 | package greedy; 2 | 3 | /** 4 | * Title: 45. 跳跃游戏2 5 | * Desc: 给定一个非负整数数组,你最初位于数组的第一个位置。 6 | 数组中的每个元素代表你在该位置可以跳跃的最大长度。 7 | 你的目标是使用最少的跳跃次数到达数组的最后一个位置。 8 | * Created by Myth-PC on 22/01/2020 in VSCode 9 | */ 10 | public class P45JumpGame2 { 11 | // 假设你总是可以到达数组的最后一个位置 12 | public int jump(int[] nums) { 13 | int n = nums.length, k = 0, cur = 0, next; 14 | // 求每个范围的最值 15 | while (cur < n-1) { // next >= n-1 说明下一步可以达到数组尾部,注意此处 16 | next = nums[cur] + cur; 17 | for (int i = cur; i <= next && i < n; i++) { 18 | // 可以将当前的范围扩大,就跳到那个扩的最大的位置 19 | if (i == n-1 || nums[i]+i > nums[cur]+cur) { 20 | cur = i; 21 | } 22 | } 23 | // 不确定是否可以到达边界 24 | // if (cur <= oldCur) return -1; 25 | k++; 26 | } 27 | return k; 28 | } 29 | public static void main(String[] args) { 30 | P45JumpGame2 p45 = new P45JumpGame2(); 31 | int[] nums0 = {2,3,1,1,4}; 32 | int[] nums1 = {2,1}; 33 | int[] nums2 = {1,1,1,1,1}; 34 | System.out.println(p45.jump(nums0)); 35 | System.out.println(p45.jump(nums1)); 36 | System.out.println(p45.jump(nums2)); 37 | } 38 | } -------------------------------------------------------------------------------- /src/greedy/P55JumpGame.java: -------------------------------------------------------------------------------- 1 | package greedy; 2 | 3 | /** 4 | * Title: 55. 跳跃游戏(贪心入门题目) 5 | * Desc: 给定一个非负整数数组,你最初位于数组的第一个位置。 6 | * 数组中的每个元素代表你在该位置可以跳跃的最大长度(所以可以跳比这个小的跳数)。 7 | * 判断你是否能够到达最后一个位置。(剑指offer 青蛙跳台阶问题) 8 | * Created by Myth-Lab on 10/15/2019 9 | */ 10 | public class P55JumpGame { 11 | // 记下来可以跳的范围,不断去更新这个范围(索引),如果范围比 当前位置还要大,说明跳不过去 12 | public boolean canJump(int[] nums) { 13 | int d = 0; 14 | for (int i = 0; i < nums.length; i++) { 15 | if (i > d) return false; 16 | d = Math.max(d, nums[i]+i); 17 | } 18 | return true; 19 | } 20 | // 错误的写法 21 | // [0,2,3] 22 | public boolean canJump2(int[] nums) { 23 | if (nums == null || nums.length == 0) return false; 24 | int n = nums.length, maxDistance = nums[0]; 25 | for (int i = 0; i < n; i++) { 26 | if (maxDistance >= n-1) return true; 27 | maxDistance = Math.max(maxDistance, i+nums[i]); 28 | } 29 | return false; 30 | } 31 | 32 | public static void main(String[] args) { 33 | P55JumpGame p55 = new P55JumpGame(); 34 | 35 | } 36 | // TODO 如何跳最快,方式最优,DP 45题 37 | } 38 | -------------------------------------------------------------------------------- /src/greedy/README.md: -------------------------------------------------------------------------------- 1 | # 贪心算法 2 | 3 | 贪心算法是计算每个小步骤最优的策略,然后 4 | 5 | ## 经典题 6 | 7 | 55. 跳跃游戏(能否跳到终点) 8 | 45. 跳跃游戏2(如何最快跳到终点) 9 | 134. 加油站(找到可以绕环路一周的加油站位置) 10 | 135. 分发糖果(相邻的人,评分高的获得更多糖果) -------------------------------------------------------------------------------- /src/input.txt: -------------------------------------------------------------------------------- 1 | [10,1,2,7,6,1,5] 2 | 8 -------------------------------------------------------------------------------- /src/list/fastslow/P141LinkedListCycle.java: -------------------------------------------------------------------------------- 1 | package list.fastslow; 2 | 3 | import util.ListNode; 4 | 5 | /** 6 | * Title: 141. 环形链表 7 | * Desc: 给一个链表判断链表是否有环,你能用 O(1)(即,常量)内存解决此问题吗? 8 | * Created by Myth on 9/7/2019 9 | */ 10 | public class P141LinkedListCycle { 11 | // 使用哈希表,将每个指针地址存入,然后判断,空间复杂度 O(n) 12 | // 快慢指针 类似于两人跑步(慢指针每次1步,快指针每次2步),那么 环形部分/1 = 循环迭代的次数 13 | public boolean hasCycle(ListNode head) { 14 | ListNode slow = head, fast = head; 15 | while (fast != null && fast.next != null) { 16 | slow = slow.next; 17 | fast = fast.next; 18 | fast = fast.next; 19 | if (slow == fast) return true; 20 | } 21 | return false; 22 | } 23 | 24 | public static void main(String[] args) { 25 | ListNode node4 = new ListNode(-4); 26 | ListNode node3 = new ListNode(0, node4); 27 | ListNode node2 = new ListNode(2, node3); 28 | ListNode node1 = new ListNode(3, node2); 29 | node4.next = node2; 30 | P141LinkedListCycle p141 = new P141LinkedListCycle(); 31 | System.out.println(p141.hasCycle(node1)); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/list/merge/P21MergeTwoSortedLists.java: -------------------------------------------------------------------------------- 1 | package list.merge; 2 | 3 | import util.ListNode; 4 | 5 | /** 6 | * Title: 21. 合并两个有序链表(循环写法和递归写法) 7 | * Desc: 将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 8 | * Created by Myth on 9/8/2019 9 | */ 10 | public class P21MergeTwoSortedLists { 11 | public ListNode mergeTwoLists(ListNode l1, ListNode l2) { 12 | ListNode head = new ListNode(-1); 13 | ListNode p = head; 14 | while(l1 != null && l2 != null) { 15 | if (l1.val <= l2.val) { 16 | p.next = l1; 17 | l1 = l1.next; 18 | } else { 19 | p.next = l2; 20 | l2 = l2.next; 21 | } 22 | p = p.next; 23 | } 24 | // 直接接到 剩余的链表即可 25 | if (l1 != null) p.next = l1; 26 | if (l2 != null) p.next = l2; 27 | 28 | return head.next; 29 | } 30 | public ListNode mergeTwoListsRecursive(ListNode l1, ListNode l2) { 31 | if (l1 == null) return l2; 32 | if (l2 == null) return l1; 33 | if (l1.val <= l2.val) { 34 | l1.next = mergeTwoListsRecursive(l1.next, l2); 35 | return l1; 36 | } else { 37 | l2.next = mergeTwoListsRecursive(l1, l2.next); 38 | return l2; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/list/remove/P19RemoveNthNodeFromEnd.java: -------------------------------------------------------------------------------- 1 | package list.remove; 2 | 3 | import util.ListNode; 4 | 5 | /** 6 | * Title: 19. 删除链表的倒数第N个节点 7 | * Desc: 给定一个链表,删除链表的倒数第 n 个节点(n保证有效, 不会等于0 哦),并且返回链表的头结点。使用一趟扫描 8 | * Created by Myth on 9/11/2019 9 | */ 10 | public class P19RemoveNthNodeFromEnd { 11 | 12 | // 简化:使用dummy,不用pre指针,直接slow指向要删除的节点的前面位置,使用 n 递减计数 13 | public ListNode removeNthFromEnd2(ListNode head, int n) { 14 | if (head == null) return null; 15 | ListNode dummy = new ListNode(-1); 16 | dummy.next = head; 17 | ListNode fast = dummy, slow = dummy; 18 | while (fast.next != null) { 19 | if (n <= 0) slow = slow.next; 20 | fast = fast.next; 21 | n--; 22 | } 23 | slow.next = slow.next.next; 24 | return dummy.next; 25 | } 26 | 27 | public ListNode removeNthFromEnd(ListNode head, int n) { 28 | if (head == null) return null; 29 | int count = 0; 30 | ListNode fast = head, slow = head, pre = head; 31 | while (fast != null) { 32 | count++; 33 | if (count > n) { 34 | pre = slow; 35 | slow = slow.next; 36 | } 37 | fast = fast.next; 38 | } 39 | if (slow == head) { 40 | head = head.next; 41 | } else { 42 | pre.next = slow.next; 43 | slow.next = null; 44 | } 45 | return head; 46 | } 47 | 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/list/remove/P203RemoveListElements.java: -------------------------------------------------------------------------------- 1 | package list.remove; 2 | 3 | import util.ListNode; 4 | 5 | /** 6 | * Title: 203. 移除链表元素 7 | * Desc: 删除链表中等于给定值 val 的所有节点。 8 | * Created by Myth-Lab on 10/9/2019 9 | */ 10 | public class P203RemoveListElements { 11 | public ListNode removeElements(ListNode head, int val) { 12 | if(head == null) return null; 13 | int temp = (val == -1) ? -2 : -1; 14 | ListNode dummy = new ListNode(temp); 15 | dummy.next = head; 16 | ListNode p = dummy; 17 | while(p.next != null) { 18 | if(p.next.val == val) { 19 | p.next = p.next.next; 20 | } else { 21 | p = p.next; 22 | } 23 | } 24 | return dummy.next; 25 | } 26 | } -------------------------------------------------------------------------------- /src/list/remove/P83RemoveDuplicatesFromSortedList.java: -------------------------------------------------------------------------------- 1 | package list.remove; 2 | 3 | import util.ListNode; 4 | 5 | /** 6 | * Title: 83. 删除排序链表中的重复元素 7 | * Desc: 给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。 8 | * Created by Myth-Lab on 10/9/2019 9 | */ 10 | public class P83RemoveDuplicatesFromSortedList { 11 | // 重复元素出现一次 12 | public ListNode deleteDuplicates(ListNode head) { 13 | if(head == null) return null; 14 | ListNode dummy = new ListNode(-1); 15 | dummy.next = head; 16 | ListNode p = head; 17 | while(p != null && p.next != null) { 18 | if(p.val == p.next.val) { 19 | p.next = p.next.next; 20 | } else { 21 | p = p.next; 22 | } 23 | } 24 | return dummy.next; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/list/rotate/P206ReverseList.java: -------------------------------------------------------------------------------- 1 | package list.rotate; 2 | 3 | import util.ListNode; 4 | import util.ListUtil; 5 | 6 | /** 7 | * Title: 206. 反转链表(使用迭代 和 递归) 8 | * Desc: 反转一个单链表 9 | * Created by Myth on 9/6/2019 10 | */ 11 | public class P206ReverseList { 12 | // 迭代(循环版本) 13 | public ListNode reverseList(ListNode head) { 14 | ListNode front = null, cur = head, back; 15 | while (cur != null) { 16 | back = cur.next; 17 | cur.next = front; 18 | front = cur; 19 | cur = back; 20 | } 21 | return front; 22 | } 23 | // 递归版本 24 | public ListNode reverseList2(ListNode head) { 25 | if (head == null || head.next == null) return head; 26 | ListNode p = reverseList(head.next); 27 | head.next.next = head; 28 | head.next = null; 29 | return p; 30 | } 31 | 32 | public static void main(String[] args) { 33 | P206ReverseList p206 = new P206ReverseList(); 34 | ListUtil.prettyPrintLinkedList(p206.reverseList(ListUtil.stringToListNode("[1]"))); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/list/rotate/P61RotateList.java: -------------------------------------------------------------------------------- 1 | package list.rotate; 2 | 3 | import util.ListNode; 4 | import util.ListUtil; 5 | 6 | /** 7 | * Title: 61. 旋转链表 8 | * Desc: 给定一个链表,旋转链表,将链表每个节点向右移动 k 个位置,其中 k 是非负数。 9 | * Created by Myth-PC on 10/02/2020 in VSCode 10 | */ 11 | public class P61RotateList { 12 | // 快指针先走k-1步, 若快指针先为null说明链表长度小于k, 则快指针走 k%n 13 | // 最终慢指针为新头结点,快指针.next = head 14 | public ListNode rotateRight(ListNode head, int k) { 15 | if (head == null) return null; 16 | ListNode slow = head, fast = head, ret = null; 17 | int i = 0; 18 | for (i = 0; i < k; i++) { 19 | if (fast == null) break; 20 | fast = fast.next; 21 | } 22 | // k == 链表长度 23 | if (i == k && fast == null) return head; 24 | if (i < k) { 25 | k = k % i; 26 | fast = head; 27 | for (i = 0; i < k; i++) { 28 | fast = fast.next; 29 | } 30 | } 31 | while (fast.next != null) { 32 | slow = slow.next; 33 | fast = fast.next; 34 | } 35 | fast.next = head; 36 | ret = slow.next; 37 | slow.next = null; 38 | return ret; 39 | } 40 | public static void main(String[] args) { 41 | ListNode head = ListUtil.stringToListNode("[1,2,3,4,5]"); 42 | P61RotateList p61 = new P61RotateList(); 43 | ListUtil.prettyPrintLinkedList(p61.rotateRight(head, 7)); 44 | 45 | } 46 | } -------------------------------------------------------------------------------- /src/list/split/P328OddEvenNode.java: -------------------------------------------------------------------------------- 1 | package list.split; 2 | 3 | import util.ListNode; 4 | import util.ListUtil; 5 | 6 | /** 7 | * Title: 328. 奇偶链表(链表的拆分) 8 | * Desc: 给定一个单链表,把所有的奇数节点和偶数节点分别排在一起。请注意,这里的奇数节点和偶数节点指的是节点编号的奇偶性,而不是节点的值的奇偶性。 9 | * 10 | * 请尝试使用原地算法完成。你的算法的空间复杂度应为 O(1),时间复杂度应为 O(nodes),nodes 为节点总数。 11 | * 12 | * 应当保持奇数节点和偶数节点的相对顺序。 13 | * 链表的第一个节点视为奇数节点,第二个节点视为偶数节点,以此类推。 14 | * Created by Myth on 9/11/2019 15 | */ 16 | public class P328OddEvenNode { 17 | public static ListNode oddEvenList(ListNode head) { 18 | if (head == null || head.next == null || head.next.next == null) return head; 19 | ListNode p = head, q = head.next; 20 | ListNode evenHead = head.next; 21 | while (p.next != null && p.next.next != null) { // 判断条件是 p.next,也就是说,是 到 链表的最后一个元素,这个时候需要进行封尾操作 22 | p.next = p.next.next; 23 | q.next = q.next.next; 24 | p = p.next; 25 | q = p.next; 26 | } 27 | p.next = evenHead; // 封尾操作(该题比较特殊,如果是单纯的将一个链表拆成一个,需要进行封尾) 28 | // 因为 p.next == null 跳出循环, p 29 | return head; 30 | } 31 | 32 | public static void main(String[] args) { 33 | ListNode head = ListUtil.stringToListNode("[1,2,3,4,5,6,7,8]"); 34 | ListUtil.prettyPrintLinkedList(P328OddEvenNode.oddEvenList(head)); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/list/split/P86PartitionList.java: -------------------------------------------------------------------------------- 1 | package list.split; 2 | 3 | import util.ListNode; 4 | 5 | 6 | /** 7 | * Title: 86. 分隔链表 8 | * Desc: 给定一个链表和一个特定值 x,对链表进行分隔,使得所有小于 x 的节点都在大于或等于 x 的节点之前。 9 | * 你应当保留两个分区中每个节点的初始相对位置。 10 | * Created by Myth-MBP on 21/08/2020 in VSCode 11 | */ 12 | 13 | public class P86PartitionList { 14 | public ListNode partition(ListNode head, int x) { 15 | ListNode dummy1 = new ListNode(-1); 16 | ListNode dummy2 = new ListNode(-1); 17 | ListNode p = dummy1, q = dummy2, cur = head; 18 | while (cur != null) { 19 | if (cur.val < x) { 20 | p.next = cur; 21 | p = p.next; 22 | } else { 23 | q.next = cur; 24 | q = q.next; 25 | } 26 | cur = cur.next; 27 | } 28 | p.next = dummy2.next; 29 | q.next = null; 30 | return dummy1.next; 31 | } 32 | } -------------------------------------------------------------------------------- /src/math/bit/P190ReverseBits.java: -------------------------------------------------------------------------------- 1 | package math.bit; 2 | 3 | /** 4 | * Title: 190. 颠倒二进制位 5 | * Desc: 颠倒给定的 32 位无符号整数的二进制位。 6 | * Created by Myth-Lab on 10/30/2019 7 | */ 8 | public class P190ReverseBits { 9 | // you need treat n as an unsigned value 10 | public int reverseBits(int n) { 11 | int mask = 1; // mask左移32次 12 | int ret = 0, last; 13 | for (int i = 0; i < 32; i++) { 14 | last = (mask & n) == 0 ? 0 : 1; 15 | ret = (ret << 1) + last; 16 | mask = (mask << 1); 17 | } 18 | return ret; 19 | } 20 | public int reverseBits2(int n) { 21 | int ret = 0; 22 | for (int i = 0; i < 32; i++) { 23 | ret = (ret << 1) + (1 & n); 24 | n = (n >> 1); 25 | } 26 | return ret; 27 | } 28 | 29 | public static void main(String[] args) { 30 | P190ReverseBits p190 = new P190ReverseBits(); 31 | System.out.println(p190.reverseBits(5)); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/math/bit/P191NumberOf1Bits.java: -------------------------------------------------------------------------------- 1 | package math.bit; 2 | 3 | /** 4 | * Title: 191. 位1的个数 5 | * Desc: 编写一个函数,输入是一个无符号整数,返回其二进制表达式中数字位数为 ‘1’ 的个数(也被称为汉明重量)。 6 | * Created by Myth-Lab on 11/7/2019 7 | */ 8 | public class P191NumberOf1Bits { 9 | public int hammingWeight(int n) { 10 | int cnt = 0; 11 | while (n != 0) { 12 | cnt += (n&1); 13 | n >>>= 1; // >>> 最高位补 0 >> 最高位补1 14 | } 15 | return cnt; 16 | } 17 | // P231题 18 | // 数字-1与该数字相与,会将n二进制位的最后一个1变为0 19 | public int hammingWeight2(int n) { 20 | int cnt = 0; 21 | while (n != 0) { 22 | n &= (n-1); 23 | cnt++; 24 | } 25 | return cnt; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/math/bit/P201BitwiseANDOfNumbersRange.java: -------------------------------------------------------------------------------- 1 | package math.bit; 2 | 3 | /** 4 | * Title: 201. 数字范围按位与 5 | * Desc: 给定范围 [m, n],其中 0 <= m <= n <= 2147483647,返回此范围内所有数字的按位与(包含 m, n 两端点)。 6 | * Created by Myth-Lab on 11/7/2019 7 | */ 8 | public class P201BitwiseANDOfNumbersRange { 9 | // 判断哪些位没出现过0(以下代码超时) 10 | public int rangeBitwiseAndErr(int m, int n) { 11 | int ret = 0, mask; 12 | boolean flag; 13 | for (int i = 0; i < 32; i++) { 14 | mask = (1<>= 1; 32 | n >>= 1; 33 | i++; 34 | } 35 | return m << i; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/math/bit/P231PowerOfTwo.java: -------------------------------------------------------------------------------- 1 | package math.bit; 2 | 3 | /** 4 | * Title: 231. 2的幂 5 | * Desc: 给定一个整数,编写一个函数来判断它是否是 2 的幂次方。 6 | * Created by Myth-Lab on 11/7/2019 7 | */ 8 | public class P231PowerOfTwo { 9 | // 判断是否仅有一位为1: n减1 & n 会把末尾的1变成0 10 | public boolean isPowerOfTwo(int n) { 11 | if (n <= 0) return false; 12 | return (((n-1)&n) == 0); 13 | } 14 | 15 | public static void main(String[] args) { 16 | System.out.println(Integer.MIN_VALUE); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/math/bit/P268MissingNumber.java: -------------------------------------------------------------------------------- 1 | package math.bit; 2 | 3 | /** 4 | * Title: 268. 缺失的数字 5 | * Desc: 给定一个包含 0, 1, 2, ..., n 中 n 个数的序列,找出 0 .. n 中没有出现在序列中的那个数。 6 | * Created by Myth-Lab on 10/30/2019 7 | */ 8 | public class P268MissingNumber { 9 | public int missingNumber(int[] nums) { 10 | boolean[] counts = new boolean[nums.length+1]; 11 | for (int num : nums) { 12 | counts[num] = true; 13 | } 14 | for (int i = 0; i < counts.length; i++) { 15 | if (!counts[i]) return i; 16 | } 17 | return 0; 18 | } 19 | // 下标 1和1对应,说明异或的时候会等于0,落单的那个会等于最后返回 20 | // n+1个数字中挑选n个,未缺失的数在 [0..n]和数组中各出现一次,因此异或后得到 0。 21 | // 而缺失的数字只在 [0..n]中出现了一次,在数组中没有出现,因此最终的异或结果即为这个缺失的数字。 22 | public int missingNumber2(int[] nums) { 23 | int ret = nums.length; // 注意初值 24 | 25 | for (int i = 0; i < nums.length; i++) { 26 | ret = (nums[i] ^ i ^ ret); 27 | } 28 | return ret; 29 | } 30 | 31 | public static void main(String[] args) { 32 | System.out.println(999^999); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/math/convert/P13RomanToInteger.java: -------------------------------------------------------------------------------- 1 | package math.convert; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | /** 7 | * Title: 13. 罗马数字转整数 8 | * Desc: 9 | * Created by Myth-Lab on 10/31/2019 10 | */ 11 | public class P13RomanToInteger { 12 | public int romanToInt(String s) { 13 | // 处理特殊情况 14 | Map lookup = new HashMap() {{ 15 | put("IV", 4); 16 | put("IX", 9); 17 | put("XL", 40); 18 | put("XC", 90); 19 | put("CD", 400); 20 | put("CM", 900); 21 | put("I", 1); 22 | put("V", 5); 23 | put("X", 10); 24 | put("L", 50); 25 | put("C", 100); 26 | put("D", 500); 27 | put("M", 1000); 28 | }}; 29 | int ret = 0, len = s.length(); 30 | for (int i = 0; i < len; i++) { 31 | if (i+1 < len && lookup.containsKey(s.substring(i, i+2))) { 32 | ret += lookup.get(s.substring(i, i+2)); 33 | i = i + 1; 34 | } else { 35 | ret += lookup.get(s.substring(i, i+1)); 36 | } 37 | } 38 | return ret; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/math/convert/P60PermutationSequence.java: -------------------------------------------------------------------------------- 1 | package math.convert; 2 | 3 | import java.util.ArrayList; 4 | 5 | /** 6 | * Title: 60. 第k个排列 7 | * Desc: 给出集合 [1,2,3,…,n],其所有元素共有 n! 种排列。 给定 n 和 k,返回第 k 个排列。 8 | * 9 | * 说明:给定 n 的范围是 [1, 9]。 给定 k 的范围是[1, n!]。 从1开始的,所以要减去1 10 | * Created by Myth-MBP on06/08/2020 in VSCode 11 | */ 12 | public class P60PermutationSequence { 13 | public String getPermutation(int n, int k) { 14 | if(n == 1){ 15 | return "1"; 16 | } 17 | // 保存阶乘 18 | int[] fac = new int[n]; 19 | fac[0] = 1; 20 | for (int i = 1; i < n; i++) { 21 | fac[i] = i * fac[i-1]; 22 | } 23 | StringBuilder sb = new StringBuilder(); 24 | ArrayList list = new ArrayList<>(n); 25 | for (int i = 0; i < n; i++) { 26 | list.add(i+1); 27 | } 28 | k--; 29 | while (k != 0) { 30 | int i = k / fac[list.size()-1]; 31 | k = k % fac[list.size()-1]; 32 | sb.append(list.get(i)); 33 | list.remove(i); 34 | } 35 | for (Integer integer : list) { 36 | sb.append(integer); 37 | } 38 | return sb.toString(); 39 | 40 | } 41 | } -------------------------------------------------------------------------------- /src/math/convert/P7ReverseInteger.java: -------------------------------------------------------------------------------- 1 | package math.convert; 2 | 3 | /** 4 | * Title: 7. 整数反转 5 | * Desc: 假设我们的环境只能存储得下 32 位的有符号整数,则其数值范围为 [−2^31, 2^31 − 1]。请根据这个假设,如果反转后整数溢出那么就返回 0。 6 | * Created by Myth-Lab on 10/30/2019 7 | */ 8 | public class P7ReverseInteger { 9 | // 求最后一位,然后每次*10 10 | // 难点:如何判断溢出 11 | public int reverse(int x) { 12 | int symbol; 13 | if (x > 0) { 14 | symbol = 1; 15 | } else { 16 | symbol = -1; 17 | x = -x; 18 | } 19 | int ret = 0; 20 | while (x != 0) { 21 | int last = x % 10; 22 | x = x / 10; 23 | // 错误的地方:正负的最后一位是不一样的,一个7 一个8,并且也没必要识别符号 24 | if (ret > Integer.MAX_VALUE/10 || (ret == Integer.MAX_VALUE/10 && last > 7)) return 0; 25 | ret = ret * 10 + last; 26 | } 27 | return symbol * ret; 28 | } 29 | public int reverse2(int x) { 30 | int ret = 0; 31 | while (x != 0) { 32 | int last = x % 10; 33 | x = x / 10; 34 | if (ret > Integer.MAX_VALUE/10 || (ret == Integer.MAX_VALUE/10 && last > 7)) return 0; 35 | if (ret < Integer.MIN_VALUE/10 || (ret == Integer.MIN_VALUE/10 && last < -8)) return 0; 36 | ret = ret * 10 + last; 37 | } 38 | return ret; 39 | } 40 | 41 | public static void main(String[] args) { 42 | 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/math/geometry/P223RectangleArea.java: -------------------------------------------------------------------------------- 1 | package math.geometry; 2 | 3 | /** 4 | * Title: 223. 矩形面积 5 | * Desc: 在二维平面上计算出两个由直线构成的矩形重叠后形成的总面积。 6 | * 每个矩形由其左下顶点和右上顶点坐标表示,如图所示。 7 | * Created by Myth-Lab on 11/1/2019 8 | */ 9 | public class P223RectangleArea { 10 | // 求 s1-e1 和 s2-e2的重叠长度 11 | private int getOverlap(int s1, int e1, int s2, int e2) { 12 | if (s1 >= e2 || s2 >= e1) return 0; // 不交叉 13 | return Math.max(e1, e2) - Math.min(s1, s2); 14 | } 15 | public int computeArea(int A, int B, int C, int D, int E, int F, int G, int H) { 16 | // 重叠面积 = AC和EG的重叠 * BD和FH重叠部分 17 | // 容斥原理 : 面积和 - 重叠面积 18 | System.out.println(getOverlap(A, C, E, G)); 19 | System.out.println(getOverlap(B, D, F, H)); 20 | return (C-A)*(D-B) + (G-E)*(H-F) - getOverlap(A, C, E, G) * getOverlap(B, D, F, H); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/math/geometry/P892SurfaceAreaOf3DShapes.java: -------------------------------------------------------------------------------- 1 | package math.geometry; 2 | 3 | /** 4 | * Title: 892. 三维形体的表面积 5 | * Desc: 在 N * N 的网格上,我们放置一些 1 * 1 * 1  的立方体。 6 | 每个值 v = grid[i][j] 表示 v 个正方体叠放在对应单元格 (i, j) 上。 7 | 请你返回最终形体的表面积。 8 | * Created by Myth-MBP on 25/03/2020 in VSCode 9 | */ 10 | 11 | public class P892SurfaceAreaOf3DShapes { 12 | // 本题不能用侧视图的做法去做,因为有挖空的 13 | // 思路:所有所有表面积-重叠的表面积 14 | public int surfaceArea(int[][] grid) { 15 | if (grid == null || grid.length == 0) return 0; 16 | int n = grid.length, m = grid[0].length; 17 | int area = 0; 18 | // 一个柱子一个柱子的去看 19 | for (int i = 0; i < n; i++) { 20 | for (int j = 0; j < m; j++) { 21 | if (grid[i][j] == 0) continue; // 跳过高度为0的 22 | area += grid[i][j] * 4 + 2; // 每个柱子的表面积为 四个侧面+2个上下面 23 | // 重叠的部分:只数 左边和上边 较小的高度 * 2(乘以2就是加上右边和下面) 24 | if (i > 0) area -= Math.min(grid[i][j], grid[i-1][j]) * 2; 25 | if (j > 0) area -= Math.min(grid[i][j], grid[i][j-1]) * 2; 26 | } 27 | } 28 | return area; 29 | } 30 | } -------------------------------------------------------------------------------- /src/math/numbertheory/P1071GCDString.java: -------------------------------------------------------------------------------- 1 | package math.numbertheory; 2 | 3 | /** 4 | * Title: 1071. 字符串的最大公因子 5 | * Desc: 对于字符串 S 和 T,只有在 S = T + ... + T(T 与自身连接 1 次或多次)时,我们才认定 “T 能除尽 S”。 6 | * 返回最长字符串 X,要求满足 X 能除尽 str1 且 X 能除尽 str2。 7 | * Created by Myth-MBP on 13/06/2020 in VSCode 8 | */ 9 | class P1071GCDString { 10 | public String gcdOfStrings(String str1, String str2) { 11 | // 假设str1是N个x,str2是M个x,那么str1+str2肯定是等于str2+str1的。 12 | if (!(str1 + str2).equals(str2 + str1)) { 13 | return ""; 14 | } 15 | // 辗转相除法求gcd。 16 | return str1.substring(0, gcd(str1.length(), str2.length())); 17 | } 18 | 19 | private int gcd(int a, int b) { 20 | return b == 0? a: gcd(b, a % b); 21 | } 22 | } -------------------------------------------------------------------------------- /src/math/other/P169MajorityElement.java: -------------------------------------------------------------------------------- 1 | package math.other; 2 | 3 | import java.util.HashMap; 4 | 5 | /** 6 | * Title: 169. 求众数 (229 找1/3的数) 7 | * Desc: 输入的数组一定存在众数 8 | * Created by Myth-Lab on 11/6/2019 9 | */ 10 | public class P169MajorityElement { 11 | public int majorityElement(int[] nums) { 12 | HashMap map = new HashMap<>(); 13 | for (int num : nums) { 14 | map.put(num, map.getOrDefault(num, 0)+1); 15 | } 16 | for (Integer key : map.keySet()) { 17 | if (map.get(key) > nums.length/2) return key; 18 | } 19 | return 0; 20 | } 21 | // Boyer-Moore投票算法(已知一定存在众数) 22 | public int majorityElement2(int[] nums) { 23 | int major = nums[0]; 24 | int count = 0; 25 | for (int i = 0; i < nums.length; i++) { 26 | if (nums[i] == major) count++; 27 | else { 28 | count--; 29 | if (count <= 0) { 30 | major = nums[i+1]; 31 | count = 0; 32 | } 33 | } 34 | } 35 | return major; 36 | } 37 | // 精简写法 38 | public int majorityElement21(int[] nums) { 39 | int count = 0; 40 | Integer candidate = null; 41 | for (int num : nums) { 42 | if (count == 0) { 43 | candidate = num; 44 | } 45 | count += (num == candidate) ? 1 : -1; 46 | } 47 | 48 | return candidate; 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/math/other/P171ExcelSheetColumnNumber.java: -------------------------------------------------------------------------------- 1 | package math.other; 2 | 3 | /** 4 | * Title: 171. Excel表列序号 5 | * Desc: 6 | * Created by Myth-Lab on 11/3/2019 7 | */ 8 | public class P171ExcelSheetColumnNumber { 9 | public int titleToNumber(String s) { 10 | int sum = 0; 11 | for (int i=s.length()-1; i >= 0; i--) { 12 | sum += Math.pow(26, s.length()-1-i) * (s.charAt(i)-'A'+1); 13 | } 14 | return sum; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/math/other/P365WaterJugProblem.java: -------------------------------------------------------------------------------- 1 | package math.other; 2 | 3 | /** 4 | * Title: 365. 水壶盛水问题 5 | * Desc: 给定容量为 X 和 Y 的两个壶, 盛出来 Z 的水,无限多的水,如何盛???最终要用一个水壶或两个水壶把水盛出来 6 | * Created by Myth-MBP on 21/03/2020 in VSCode 7 | */ 8 | 9 | public class P365WaterJugProblem { 10 | // GCD, 贝祖定理 11 | /* 12 | 存在以下六种状态(注意不能半满) 当前(a,b)盛满 (x,y): 13 | 把 X 壶灌满;(a,b) -> (x, b) 14 | 把 Y 壶灌满;(a,b) -> (a,y) 15 | 把 X 壶倒空;(a,b) -> (0,b) 16 | 把 Y 壶倒空; (a,b) -> (a,0) 17 | 把 X 壶的水灌进 Y 壶,直至灌满或倒空;(a,b) -> (a-(y-b),y) 或者 (0, a+b) 18 | 把 Y 壶的水灌进 X 壶,直至灌满或倒空 (a,b) -> (x,b-(x-a)) 或者 (a+b, 0) 19 | */ 20 | // z 是 x 和 y 的 gcd的倍数就行() 21 | public boolean canMeasureWater(int x, int y, int z) { 22 | if (x + y < z) return false; 23 | if (x == 0 || y == 0) return (z == 0 || x+y == z); 24 | return z % gcd(x, y) == 0; 25 | } 26 | private int gcd(int a, int b) { 27 | return b != 0 ? gcd(b, b%a) : a; 28 | } 29 | } -------------------------------------------------------------------------------- /src/math/other/P50Pow.java: -------------------------------------------------------------------------------- 1 | package math.other; 2 | 3 | /** 4 | * Title: 50. Pow(x,n) 5 | * Desc: 实现 pow(x, n) ,即计算 x 的 n 次幂函数。 6 | * Created by Myth-Lab on 10/31/2019 7 | */ 8 | public class P50Pow { 9 | // 二分,注意N正负 —— 快速幂算法 10 | public double myPow(double x, int n) { 11 | // 边界一定要注意!! 12 | if (x == 1.0 || n == 0) return 1; 13 | if (x == -1.0) return ((n&1)==0) ? 1 : -1; 14 | if (n == (1<<31)) return 0; 15 | if (n > 0) return pow(x, n); 16 | else return 1.0/pow(x, -n); 17 | } 18 | // 递归会导致栈溢出 19 | private double pow(double x, int posN) { 20 | if (posN == 0) return 1.0; 21 | if (posN == 1) return x; 22 | double temp = 0; 23 | if ((posN&1) == 0) { 24 | temp = pow(x, posN/2); 25 | return temp*temp; 26 | } else { 27 | temp = pow(x, (posN-1)/2); 28 | return temp*temp*x; 29 | } 30 | } 31 | // 如何转化成 循环? 32 | // 指数每次减半,底数每次增加一倍,指数是奇数的时候最终结果要乘一个(落单的)当前底数 33 | private double powIter(double x, int posN) { 34 | double ret = 1.0; 35 | while (posN > 0) { 36 | if ((posN&1)==0) { // 偶数 37 | posN = (posN >> 2); 38 | x = x * x; // 底数每次增加 39 | } else { 40 | ret *= x; 41 | posN = ((posN - 1) >> 2); 42 | x = x * x; 43 | } 44 | } 45 | return ret; 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/math/other/P78Subsets.java: -------------------------------------------------------------------------------- 1 | package math.other; 2 | 3 | import java.util.LinkedList; 4 | import java.util.List; 5 | 6 | /** 7 | * Title: 78. 子集(回溯算法章节有回溯解法) 8 | * Desc: 给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。说明:解集不能包含重复的子集。 9 | * Created by Myth-Lab on 11/6/2019 10 | */ 11 | public class P78Subsets { 12 | // 当前是位运算解法 13 | // 0 —— 1 << nums.length 的二进制正好可以覆盖 数组的 选取 14 | public List> subsets(int[] nums) { 15 | int n = 1 << nums.length; 16 | List> ret = new LinkedList<>(); 17 | for (int i = 0; i < n; i++) { 18 | List sub = new LinkedList<>(); 19 | for (int j = 0; j < nums.length; j++) { 20 | if (((1< K) { 22 | r = m; 23 | } else if (k < K) { 24 | l = m + 1; 25 | } else return 5; 26 | } 27 | return 0; 28 | } 29 | public long countTrailingZeroes(long n) { 30 | long ret = 0; 31 | while (n >= 5) { 32 | ret += n / 5; 33 | n /= 5; 34 | } 35 | return ret; 36 | } 37 | 38 | public static void main(String[] args) { 39 | int a = 1000000000; 40 | long b = 5 * (a+1); // 溢出之后才转换long 41 | long c = 5L * (a+1); 42 | System.out.println(b); 43 | System.out.println(c); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/math/other/P89GrayCode.java: -------------------------------------------------------------------------------- 1 | package math.other; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | /** 7 | * Title: 89. 格雷码 8 | * Desc: 格雷编码是一个二进制数字系统,在该系统中,两个连续的数值仅有一个位数的差异。 9 | * 10 | * 给定一个代表编码总位数的非负整数 n,打印其格雷编码序列。即使有多个不同答案,你也只需要返回其中一种。 11 | * 12 | * 格雷编码序列必须以 0 开头。 13 | * 14 | * Created by Myth-MBP on 12/07/2020 in VSCode 15 | */ 16 | public class P89GrayCode { 17 | // 格雷序列只要求一位不同,所以格雷序列是收尾相应的,也叫反射码 18 | // 所以给一个格雷序列,下一个格雷序列的构造方法是: 19 | // 以前的序列前面补0 U 以前的序列倒序,前面补1 20 | public List grayCode(int n) { 21 | List list = new ArrayList<>(); 22 | list.add(0); 23 | int head = 0; 24 | for (int i = 0; i < n; i++) { 25 | head = (1 << i); // 头部补0 26 | for (int j = list.size()-1; j >=0; j--) { 27 | list.add(head + list.get(j)); 28 | } 29 | } 30 | return list; 31 | } 32 | } -------------------------------------------------------------------------------- /src/math/sampling/P382LinkedListRandomNode.java: -------------------------------------------------------------------------------- 1 | package math.sampling; 2 | 3 | import util.ListNode; 4 | 5 | import java.util.Random; 6 | 7 | /** 8 | * Title: 382. 链表随机节点(Reservoir Sampling) 9 | * Desc: 定一个单链表,随机选择链表的一个节点,并返回相应的节点值。保证每个节点被选的概率一样。 10 | * 11 | * 进阶: 12 | * 如果链表十分大且长度未知,如何解决这个问题?你能否使用常数级空间复杂度实现? 13 | * 14 | * 蓄水池抽样的特点就是stream不知道有多长,要求计算每一次的概率 15 | * Created by Myth-Lab on 11/8/2019 16 | */ 17 | public class P382LinkedListRandomNode { 18 | private ListNode head; 19 | P382LinkedListRandomNode(ListNode head){ 20 | this.head = head; 21 | } 22 | public int getRandom() { 23 | ListNode p = head; 24 | int reservoir = p.val; 25 | p = p.next; 26 | int i = 1; 27 | Random random = new Random(); 28 | while (p != null) { 29 | int rand = random.nextInt((i++)+1); 30 | if (rand == 0) reservoir = p.val; 31 | p = p.next; 32 | } 33 | return reservoir; 34 | } 35 | 36 | } -------------------------------------------------------------------------------- /src/math/sampling/P398RandomPickIndex.java: -------------------------------------------------------------------------------- 1 | package math.sampling; 2 | 3 | import java.util.Random; 4 | 5 | /** 6 | * Title: 398. 随机数索引 7 | * Desc: 给定一个可能含有重复元素的整数数组,要求随机输出给定的数字的索引。 您可以假设给定的数字一定存在于数组中。 8 | * 注意: 数组大小可能非常大。 使用太多额外空间的解决方案将不会通过测试。 9 | * 10 | * Created by Myth-Lab on 11/8/2019 11 | */ 12 | public class P398RandomPickIndex { 13 | private int[] nums; 14 | private Random random; 15 | P398RandomPickIndex(int[] nums) { 16 | this.nums = nums; 17 | random = new Random(); 18 | } 19 | // 蓄水池抽样问题,不知道前方有多少符合要求的下标,取随机数,然后随机数 40); 36 | return i % 10 + 1; 37 | } 38 | // 拒绝采样:构造均匀分布,采样符合要求的,本题符合要求的就是可以映射到1-10的 39 | } 40 | -------------------------------------------------------------------------------- /src/math/sampling/P478GenerateRandomPointInCircle.java: -------------------------------------------------------------------------------- 1 | package math.sampling; 2 | 3 | import java.util.Random; 4 | 5 | /** 6 | * Title: 478. 在圆内随机生成点 7 | * Desc: 蒙特卡洛拒绝采样 8 | * Created by Myth-Lab on 11/9/2019 9 | */ 10 | public class P478GenerateRandomPointInCircle { 11 | private double r; 12 | private double x; 13 | private double y; 14 | private Random random; 15 | public P478GenerateRandomPointInCircle(double radius, double x_center, double y_center) { 16 | r = radius; 17 | x = x_center; 18 | y = y_center; 19 | random = new Random(); 20 | } 21 | public double[] randPoint() { 22 | // [x-r, x+r] [y-r, y+r] 23 | double simpleX = random.nextDouble() * (2.0 * r) + (x - r); 24 | double simpleY = random.nextDouble() * (2.0 * r) + (y - r); 25 | if ((simpleX-x)*(simpleX-x)+(simpleY-y)*(simpleY-y) <= r * r) return new double[]{simpleX, simpleY}; 26 | else return randPoint(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/math/sampling/P519RandomFlipMatrix.java: -------------------------------------------------------------------------------- 1 | package math.sampling; 2 | 3 | import java.util.HashMap; 4 | import java.util.Random; 5 | 6 | /** 7 | * Title: 519. 随机翻转矩阵(随机抽样问题的改编版) 8 | * Desc: 题中给出一个 n 行 n 列的二维矩阵 (n_rows,n_cols),且所有值被初始化为 0。 9 | * 要求编写一个 flip 函数,均匀随机的将矩阵中的 0 变为 1,并返回该值的位置下标 [row_id,col_id];同样编写一个 reset 函数,将所有的值都重新置为 0。 10 | * 尽量最少调用随机函数 Math.random(),并且优化时间和空间复杂度。 11 | * 12 | * Created by Myth-Lab on 11/9/2019 13 | */ 14 | public class P519RandomFlipMatrix { 15 | 16 | private int row, col, n; 17 | private HashMap posMap; 18 | private Random random; 19 | public P519RandomFlipMatrix(int n_rows, int n_cols) { 20 | row = n_rows; 21 | col = n_cols; 22 | n = row * col; 23 | posMap = new HashMap<>(); 24 | random = new Random(); 25 | } 26 | // 本题可以使用一个HashMap,来交换位置,本质上还是将坐标划分为两部分(已经变成1的和未变成1的) 27 | // 可以画图查看如何实现交换的!!! 28 | public int[] flip() { 29 | if (n < 0) return null; 30 | //随机选择一个下标 31 | int r = random.nextInt(n--); // 位置 32 | int x = posMap.getOrDefault(r, r); // 查看是否存在交换关系 // 实际内容 33 | // 原下标与尾部下标交换(使用map记录origin->tail交换关系) 34 | posMap.put(r, posMap.getOrDefault(n, n)); 35 | return new int[]{x/col, x%col}; 36 | } 37 | 38 | public void reset() { 39 | posMap = new HashMap<>(); 40 | n = row * col; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/math/sampling/P710RandomPickWithBlacklist.java: -------------------------------------------------------------------------------- 1 | package math.sampling; 2 | 3 | import java.util.HashMap; 4 | import java.util.Random; 5 | 6 | /** 7 | * Title: 710. 黑名单中的随机数 8 | * Desc: 给定一个包含 [0,n ) 中独特的整数的黑名单 B,写一个函数从 [ 0,n ) 中返回一个不在 B 中的随机整数。 9 | * 10 | * 对它进行优化使其尽量少调用系统方法 Math.random() 。 11 | * 12 | * Created by Myth-Lab on 11/11/2019 13 | */ 14 | public class P710RandomPickWithBlacklist { 15 | private Random random; 16 | private HashMap map; 17 | int line; 18 | public P710RandomPickWithBlacklist(int N, int[] blacklist) { 19 | random = new Random(); 20 | line = N - blacklist.length; 21 | map = new HashMap<>(); 22 | for (int b : blacklist) { 23 | map.put(b, b); 24 | } 25 | N = N - 1; 26 | for (int b : blacklist) { 27 | if (b < line) { 28 | while (map.containsKey(N)) N--; // 找到白名单的末尾 29 | map.put(b, N--); 30 | } 31 | } 32 | } 33 | 34 | public int pick() { 35 | int rand = random.nextInt(line); 36 | return map.getOrDefault(rand, rand); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/math/sampling/Shuffle.java: -------------------------------------------------------------------------------- 1 | package math.sampling; 2 | 3 | import java.util.Arrays; 4 | import java.util.Random; 5 | 6 | /** 7 | * Title: Knuth洗牌算法 8 | * Desc: 9 | * Created by Myth-PC on 26/01/2020 in VSCode 10 | */ 11 | public class Shuffle { 12 | private void swap(int[] arr, int i, int j) { 13 | int temp = arr[i]; 14 | arr[i] = arr[j]; 15 | arr[j] = temp; 16 | } 17 | public void shuffle(int[] arr) { 18 | int n = arr.length; 19 | Random random = new Random(); 20 | for (int i = n-1; i >= 0; i--) { 21 | swap(arr, i, random.nextInt(i+1)); // [0, i] 包括i 22 | } 23 | } 24 | public static void main(String[] args) { 25 | Shuffle shuffle = new Shuffle(); 26 | int[] arr = {0,1,2,3,4,5}; 27 | shuffle.shuffle(arr); 28 | System.out.println(Arrays.toString(arr)); 29 | } 30 | } -------------------------------------------------------------------------------- /src/math/special/P172FactorialTrailingZeroes.java: -------------------------------------------------------------------------------- 1 | package math.special; 2 | 3 | /** 4 | * Title: 172. 阶乘后的零(进阶793题) 5 | * Desc: 给定一个整数 n,返回 n! 结果尾数中零的数量。 6 | * Created by Myth-Lab on 11/3/2019 7 | */ 8 | public class P172FactorialTrailingZeroes { 9 | // 难点:思维转换 10 | // 为什么会产生0, 有10: 2*5, 所有有多少<2,5>对,就有多少0。2出现的次数肯定比5多,所以只算5出现的次数就行,但是注意25 和 25 * 25均出现不只一次5 11 | public int trailingZeroes(int n) { 12 | int ret = 0; 13 | while (n >= 5) { 14 | ret += n / 5; 15 | n /= 5; 16 | } 17 | return ret; 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/math/special/P202HappyNumber.java: -------------------------------------------------------------------------------- 1 | package math.special; 2 | 3 | import java.util.HashSet; 4 | 5 | /** 6 | * Title: 202. 快乐数 7 | * Desc: 编写一个算法来判断一个数 n 是不是快乐数。 8 | 「快乐数」定义为:对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和,然后重复这个过程直到这个数变为 1, 9 | 也可能是 无限循环 但始终变不到 1。如果 可以变为  1,那么这个数就是快乐数。 10 | 如果 n 是快乐数就返回 True ;不是,则返回 False 。 11 | * Created by Myth-Lab on 11/4/2019 12 | */ 13 | public class P202HappyNumber { 14 | public boolean isHappy(int n) { 15 | HashSet set = new HashSet<>(); 16 | // 肯定存在循环 17 | while (!set.contains(n)) { 18 | if (n == 1) return true; 19 | set.add(n); 20 | n = repalce(n); 21 | } 22 | return false; 23 | } 24 | private int repalce(int n) { 25 | int sum = 0, last; 26 | while (n != 0) { 27 | last = n % 10; 28 | sum += last * last; 29 | n = n / 10; 30 | } 31 | return sum; 32 | } 33 | // 使用快慢指针的思想,循环意味着出现了环!!!,那么只需要判断,这个环是不是由1组成的就可以啦! 34 | public boolean isHappy2(int n) { 35 | int slow = n, fast= n; 36 | // 肯定存在循环 37 | do { 38 | slow = repalce(slow); 39 | fast = repalce(fast); 40 | fast = repalce(fast); 41 | } while (slow != fast); 42 | return fast == 1; 43 | } 44 | 45 | public static void main(String[] args) { 46 | P202HappyNumber p202 = new P202HappyNumber(); 47 | System.out.println(p202.isHappy2(2)); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/math/special/P263UglyNumber.java: -------------------------------------------------------------------------------- 1 | package math.special; 2 | 3 | /** 4 | * Title: 263. 丑数 5 | * Desc: 编写一个程序判断给定的数是否为丑数。丑数就是只包含质因数 2, 3, 5 的正整数。 6 | * Created by Myth-Lab on 11/4/2019 7 | */ 8 | public class P263UglyNumber { 9 | // 分解因式 10 | public boolean isUgly(int num) { 11 | if (num == 0) return false; 12 | while (num != 1) { 13 | if (num % 2 == 0) { 14 | num /= 2; 15 | } else if (num % 3 == 0) { 16 | num /= 3; 17 | } else if (num % 5 == 0) { 18 | num /= 5; 19 | } else return false; 20 | } 21 | return true; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/math/special/P9PalindromeNumber.java: -------------------------------------------------------------------------------- 1 | package math.special; 2 | 3 | /** 4 | * Title: 9. 判断回文数 5 | * Desc: 判断一个数是不是回文数,不将数字转换成字符串! 6 | * Created by Myth-MBP on 13/03/2020 in VSCode 7 | */ 8 | 9 | public class P9PalindromeNumber { 10 | // 如何把数字翻过来??? 11 | public boolean isPalindrome(int x) { 12 | if (x < 0) return false; 13 | int reversed = 0, origin = x; 14 | while (x != 0) { 15 | reversed = reversed * 10 + (x % 10); // 把数字翻过来 16 | x /= 10; 17 | } 18 | return reversed == origin; 19 | } 20 | } -------------------------------------------------------------------------------- /src/search/backtracking/README.md: -------------------------------------------------------------------------------- 1 | # 回溯 2 | 3 | 回溯就是有规律的遍历,改变状态,然后再该回来状态(回溯) 4 | 5 | **模版** 6 | 参数:原始参数(题目必备的参数)、start(开始位置), cur(当前答案), ans(保存的所有答案) 7 | 8 | Java中注意如果将当前答案添加到最终ans list时,要new一个新的对象 9 | ```Java 10 | // 找到所有方案 11 | void findSolutions(n, other params) { 12 | if (found a solution) { // 找到一个答案 13 | // solutionsFound = solutionsFound + 1; 14 | addSolution(); // 将当前solution添加到solution list中 15 | // if (solutionsFound >= solutionTarget) : 16 | // System.exit(0); 17 | return 18 | } 19 | // 有的写不成循环,要分多个情况进行讨论;本质上循环就是多个情况,只不过写起来比较简单而已 20 | for (val = first to last) { // or start(开始位置参数) to last 21 | if (! isValid(val, n)) continue; // 剪枝 22 | applyValue(val, n); // 改变参数 23 | findSolutions(n+1, other params); 24 | removeValue(val, n); // 取消 改变参数 25 | } 26 | } 27 | ``` 28 | 29 | > 还有一种方案是:将每一步的剪枝移动到循环的前部,让其 return false(见79题),具体采取哪种方案视情况而定 30 | 31 | ```Java 32 | // 一个方案是否存在 33 | boolean findSolutions(n, other params) { 34 | if (found a solution) { 35 | displaySolution(); 36 | return true; 37 | } 38 | 39 | for (val = first to last) { // or start(开始位置参数) to last 40 | if ( !isValid(val, n)) continue; 41 | applyValue(val, n); 42 | if (findSolutions(n+1, other params)) return true; 43 | removeValue(val, n); 44 | } 45 | return false; 46 | } 47 | ``` -------------------------------------------------------------------------------- /src/search/combination/P22GenerateParenthesesCombination.java: -------------------------------------------------------------------------------- 1 | package search.combination; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | /** 7 | * Title: 22. 括号生成 8 | * Desc: 给出 n 代表生成括号的对数,请你写出一个函数,使其能够生成所有可能的并且有效的括号组合。 9 | * https://leetcode-cn.com/problems/generate-parentheses/ 10 | * Created by Myth on 7/23/2019 11 | */ 12 | public class P22GenerateParenthesesCombination { 13 | // 重点,根据括号的数目来判断是否可以放置 14 | // 回溯(不带循环的回溯,因为每种选择不太一样,无法使用循环语句写出) 15 | // 结束:串的长度=2*n 16 | // 限制条件:左括号数目小于n时都可以放; 右括号数目小于左括号数目时才可以放 17 | private void backtracking(int n, int open, int close, String cur, List ans) { 18 | if (cur.length() == 2*n) { 19 | // 不用new String,因为String每次会产生新的对象 20 | ans.add(cur); 21 | return; 22 | } 23 | if (open < n) { 24 | // 注意 cur+"(" 会产生新的对象 25 | backtracking(n, open+1, close, cur+"(", ans); 26 | } 27 | 28 | if (close < open) { 29 | backtracking(n, open, close+1, cur+")", ans); 30 | } 31 | } 32 | public List generateParenthesis(int n) { 33 | List ans = new ArrayList<>(); 34 | backtracking(n, 0, 0, "", ans); 35 | return ans; 36 | } 37 | 38 | public static void main(String[] args) { 39 | System.out.println(new P22GenerateParenthesesCombination().generateParenthesis(2)); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/search/combination/P77Combination.java: -------------------------------------------------------------------------------- 1 | package search.combination; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | /** 7 | * Title: 77. 组合 8 | * Desc: 给定两个整数 n 和 k,返回 1 ... n 中所有可能的 k 个数的组合。 9 | * Created by Myth on 7/17/2019 10 | */ 11 | public class P77Combination { 12 | private void backtracking(int n, int k, int start, List cur, List> ans) { 13 | if (k == 0) { 14 | ans.add(new ArrayList<>(cur)); 15 | return; 16 | } 17 | for (int i = start; i <= n; i++) { 18 | cur.add(i); 19 | backtracking(n, k-1, i+1, cur, ans); 20 | cur.remove(cur.size()-1); 21 | } 22 | } 23 | public List> combine(int n, int k) { 24 | List> ans = new ArrayList<>(); 25 | if (k <=0 || n <= 0) return ans; 26 | List cur = new ArrayList<>(); 27 | backtracking(n, k, 1, cur, ans); 28 | return ans; 29 | } 30 | public static void main(String[] args) { 31 | P77Combination p77 = new P77Combination(); 32 | System.out.println(p77.combine(4, 2)); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/search/partition/P698PartitionToKEqualSubsets.java: -------------------------------------------------------------------------------- 1 | package search.partition; 2 | 3 | /** 4 | * Title: 698. 划分K(k >=1 )个相等的子集 5 | * Desc: 给定一个整数数组 nums(nums[i] > 0) 和一个正整数 k,找出是否有可能把这个数组分成 k 个非空子集,其总和都相等。 6 | * 输入: nums = [4, 3, 2, 3, 5, 2, 1], k = 4 7 | * 输出: True 8 | * 说明: 有可能将其分成 4 个子集(5),(1,4),(2,3),(2,3)等于总和。 9 | * Created by Myth on 7/20/2019 10 | */ 11 | public class P698PartitionToKEqualSubsets { 12 | private boolean backtracking(int[] nums, int k, int target, int cur, int start, boolean[] used) { 13 | if (k == 0) return true; 14 | if (cur == target) { 15 | // 构建下一个集合 16 | return backtracking(nums, k-1, target, 0, 0, used); 17 | } 18 | for (int i = start; i < nums.length; i++) { 19 | if (!used[i] && cur+nums[i] <= target) { 20 | used[i] = true; 21 | if (backtracking(nums, k, target, cur+nums[i], i+1, used)) return true; 22 | used[i] = false; 23 | } 24 | } 25 | return false; 26 | } 27 | 28 | public boolean canPartitionKSubsets(int[] nums, int k) { 29 | int sum = 0, maxNum = 0; 30 | for (int i = 0; i < nums.length; i++) { 31 | sum += nums[i]; 32 | if (maxNum < nums[i]) maxNum = nums[i]; 33 | } 34 | if (sum % k != 0 || maxNum > sum/k) return false; 35 | boolean[] used = new boolean[nums.length]; 36 | return backtracking(nums, k, sum/k, 0, 0, used); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/search/permutation/P784LetterCasePermutation.java: -------------------------------------------------------------------------------- 1 | package search.permutation; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | /** 7 | * Title: 784 字母大小写全排列(这个题更像一个组合问题) 8 | * Desc: 给定一个字符串S,通过将字符串S中的每个字母转变大小写,我们可以获得一个新的字符串。返回所有可能得到的字符串集合。 9 | * Created by Myth on 7/19/2019 10 | */ 11 | public class P784LetterCasePermutation { 12 | private void backtracking(String S, int start, char[] cur, List ans) { 13 | ans.add(new String(cur)); 14 | for (int i = start; i < S.length(); i++) { 15 | if (S.charAt(i) >= 'a' && S.charAt(i) <= 'z') { 16 | cur[i] = (char) (cur[i] - 'a' + 'A'); 17 | backtracking(S, i+1, cur, ans); 18 | cur[i] = (char) (cur[i] - 'A' + 'a'); 19 | } 20 | if (S.charAt(i) >= 'A' && S.charAt(i) <= 'Z') { 21 | cur[i] = (char) (cur[i] - 'A' + 'a'); 22 | backtracking(S, i+1, cur, ans); 23 | cur[i] = (char) (cur[i] - 'a' + 'A'); 24 | } 25 | } 26 | } 27 | public List letterCasePermutation(String S) { 28 | List ans = new ArrayList<>(); 29 | backtracking(S, 0, S.toCharArray(), ans); 30 | return ans; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/search/subset/P78Subsets.java: -------------------------------------------------------------------------------- 1 | package search.subset; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | /** 7 | * Title: 78. 子集 8 | * Desc: 给定一组 不含重复元素 的整数数组 nums,返回该数组所有可能的子集(幂集)。 9 | * 说明:解集不能包含重复的子集。 10 | * Created by Myth on 7/17/2019 11 | */ 12 | public class P78Subsets { 13 | private void backtracking(int[] nums, int start, List cur, List> ans) { 14 | ans.add(new ArrayList<>(cur)); 15 | for (int i = start; i < nums.length; i++) { 16 | cur.add(nums[i]); 17 | backtracking(nums, i+1, cur, ans); 18 | cur.remove(cur.size()-1); 19 | } 20 | } 21 | public List> subsets(int[] nums) { 22 | List> ans = new ArrayList<>(); 23 | List cur = new ArrayList<>(); 24 | backtracking(nums, 0, cur, ans); 25 | return ans; 26 | } 27 | 28 | public static void main(String[] args) { 29 | P78Subsets p78 = new P78Subsets(); 30 | int[] arr1 = {1, 2, 3}; 31 | System.out.println(p78.subsets(arr1)); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/search/subset/P90Subsets2.java: -------------------------------------------------------------------------------- 1 | package search.subset; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Arrays; 5 | import java.util.List; 6 | 7 | /** 8 | * Title: 90. 子集 2 9 | * Desc: 在 78 题的基础上,nums是可能包含重复元素的整数数组 10 | * Created by Myth on 7/17/2019 11 | */ 12 | public class P90Subsets2 { 13 | private void backtracking(int[] nums, int start, List cur, List> ans) { 14 | ans.add(new ArrayList<>(cur)); 15 | for (int i = start; i < nums.length; i++) { 16 | // 和上一个数字相等就跳过去, 注意 i > start 17 | if (i > start && nums[i] == nums[i-1]) continue; 18 | cur.add(nums[i]); 19 | backtracking(nums, i+1, cur, ans); 20 | cur.remove(cur.size()-1); 21 | } 22 | } 23 | public List> subsetsWithDup(int[] nums) { 24 | List> ans = new ArrayList<>(); 25 | List cur = new ArrayList<>(); 26 | Arrays.sort(nums); 27 | backtracking(nums, 0, cur, ans); 28 | return ans; 29 | } 30 | public static void main(String[] args) { 31 | P90Subsets2 p90 = new P90Subsets2(); 32 | int[] arr1 = {1, 2, 2}; 33 | System.out.println(p90.subsetsWithDup(arr1)); // [[], [1], [1, 2], [1, 2, 2], [2], [2, 2]] 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/sort/P179LargestNumber.java: -------------------------------------------------------------------------------- 1 | package sort; 2 | 3 | import java.util.*; 4 | 5 | /** 6 | * Title: 179. 最大数(能组成的最大整数) 7 | * Desc: 给定一组非负整数,重新排列它们的顺序使之组成一个最大的整数。 8 | * Created by Myth-Lab on 10/17/2019 9 | */ 10 | public class P179LargestNumber { 11 | public String largestNumber(int[] nums) { 12 | if (nums == null || nums.length < 1) return "0"; 13 | String[] strs = new String[nums.length]; 14 | for (int i = 0; i < nums.length; i++) { 15 | strs[i] = String.valueOf(nums[i]); 16 | } 17 | // 如何写规则 18 | Arrays.sort(strs, new Comparator() { 19 | @Override 20 | public int compare(String o1, String o2) { 21 | // 代表o2+o1会排在前面 o2+o1 > o1+o2 时返回大于0 的数 22 | return (o2+o1).compareTo(o1+o2); 23 | } 24 | }); 25 | if ("0".equals(strs[0])) return "0"; 26 | StringBuilder stringBuilder = new StringBuilder(); 27 | for (String numString : strs) { 28 | stringBuilder.append(numString); 29 | } 30 | return stringBuilder.toString(); 31 | } 32 | 33 | public static void main(String[] args) { 34 | P179LargestNumber p179 = new P179LargestNumber(); 35 | int[] nums = {0, 0}; 36 | System.out.println(p179.largestNumber(nums)); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/sort/P242ValidAnagram.java: -------------------------------------------------------------------------------- 1 | package sort; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | /** 7 | * Title: 242. 有效的字母异位词 8 | * Desc: 给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。 9 | * Created by Myth-Lab on 10/18/2019 10 | */ 11 | public class P242ValidAnagram { 12 | // 方法1:排序返回相等的串(这也是最快最简单的方式) 13 | // 方法2:使用数组计数(ASCII码)只包含小写字母 14 | public boolean isAnagram2(String s, String t) { 15 | if (s.length() != t.length()) { 16 | return false; 17 | } 18 | int[] counter = new int[26]; 19 | for (int i = 0; i < s.length(); i++) { 20 | counter[s.charAt(i) - 'a']++; 21 | counter[t.charAt(i) - 'a']--; 22 | } 23 | for (int count : counter) { 24 | if (count != 0) { 25 | return false; 26 | } 27 | } 28 | return true; 29 | } 30 | // 方法3:Unicode 时候 31 | public boolean isAnagram3(String s, String t) { 32 | if(t.length() != s.length()) return false; 33 | Map map = new HashMap<>(); 34 | for (int i = 0; i < s.length(); i++) { 35 | map.put(s.charAt(i), map.getOrDefault(s.charAt(i), 0) + 1); 36 | } 37 | for (int i = 0; i < t.length(); i++) { 38 | if(!map.containsKey(t.charAt(i))) return false; 39 | int val = map.get(t.charAt(i)) - 1; 40 | if (val <= 0) map.remove(t.charAt(i)); 41 | else map.put(t.charAt(i), val); 42 | } 43 | return map.size() == 0; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/sort/P324WiggleSort2.java: -------------------------------------------------------------------------------- 1 | package sort; 2 | 3 | import java.util.Arrays; 4 | 5 | /** 6 | * Title: 324. 摆动排序 7 | * Desc: 给定一个无序的数组 nums,将它重新排列成 nums[0] < nums[1] > nums[2] < nums[3]... 的顺序。 8 | * (隐藏条件:输入序列在排序后最多只有序列长度一半(n/2)的相邻的数连续相等。) 9 | * Created by Myth-Lab on 10/18/2019 10 | */ 11 | public class P324WiggleSort2 { 12 | // 先排序, 分成两部分 13 | // 穿插放到一个数组里面 14 | // TestCase : [4,5,5,6] 15 | public void wiggleSort(int[] nums) { 16 | // nums长度肯定符合要求 17 | int n = nums.length; 18 | Arrays.sort(nums); // 小 -> 大 19 | // [0,mid) [mid, n) 20 | int mid = (n%2==0) ? n/2 : n/2+1; 21 | int[] copyNums = Arrays.copyOf(nums, n); 22 | // 偶数位置 23 | int odd = 0; 24 | for (int i = mid-1; i >= 0; i--) { // 前半部分倒着 25 | nums[odd] = copyNums[i]; 26 | odd += 2; 27 | } 28 | int even = 1; 29 | for (int i = n-1; i >= mid; i--) { // 右半边也倒着 30 | nums[even] = copyNums[i]; 31 | even += 2; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/sort/P350IntersectionOfTwoArrays2.java: -------------------------------------------------------------------------------- 1 | package sort; 2 | 3 | import java.util.Arrays; 4 | import java.util.LinkedList; 5 | import java.util.List; 6 | 7 | /** 8 | * Title: 350. 两数组的交集 2 9 | * Desc: 给定两个数组,编写一个函数来计算它们的交集。 10 | * 输出结果中每个元素出现的次数,应与元素在两个数组中出现的次数一致。我们可以不考虑输出结果的顺序。 11 | * Created by Myth-PC on 2019-10-19 12 | */ 13 | public class P350IntersectionOfTwoArrays2 { 14 | public int[] intersect(int[] nums1, int[] nums2) { 15 | Arrays.sort(nums1); 16 | Arrays.sort(nums2); 17 | // 排好序就用双指针 18 | List list = new LinkedList<>(); 19 | int i = 0, j = 0; 20 | while (i < nums1.length && j < nums2.length) { 21 | if (nums1[i] == nums2[j]) { 22 | list.add(nums1[i]); 23 | i++; 24 | j++; 25 | } 26 | else if (nums1[i] > nums2[j]) j++; 27 | else i++; 28 | } 29 | int[] ret = new int[list.size()]; 30 | for (int k = 0; k < list.size(); k++) { 31 | ret[k] = list.get(k); 32 | } 33 | return ret; 34 | } 35 | // 没拍好序就用Hash,将数目较小的数组存入Hash 36 | 37 | // 内存较小 —— 外排序 38 | // 对应进阶问题三,如果内存十分小,不足以将数组全部载入内存, 39 | // 那么必然也不能使用哈希这类费空间的算法,只能选用空间复杂度最小的算法,即解法一。 40 | // 但是解法一中需要改造,一般说排序算法都是针对于内部排序, 41 | // 一旦涉及到跟磁盘打交道(外部排序),则需要特殊的考虑。 42 | // 归并排序是天然适合外部排序的算法,可以将分割后的子数组写到单个文件中, 43 | // 归并时将小文件合并为更大的文件。当两个数组均排序完成生成两个大文件后, 44 | // 即可使用双指针遍历两个文件,如此可以使空间复杂度最低。 45 | 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/sort/P386LexicographicalNumbers.java: -------------------------------------------------------------------------------- 1 | package sort; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Comparator; 5 | import java.util.List; 6 | import java.util.TreeSet; 7 | 8 | /** 9 | * Title: 386. 字典序排数(179一样) 10 | * Desc: 给定一个整数 n, 返回从 1 到 n 的字典顺序。 11 | * 给定 n =1 3,返回 [1,10,11,12,13,2,3,4,5,6,7,8,9] 。 12 | * 请尽可能的优化算法的时间复杂度和空间复杂度。 输入的数据 n 小于等于 5,000,000。 13 | * Created by Myth-Lab on 10/18/2019 14 | */ 15 | public class P386LexicographicalNumbers { 16 | // 使用自定义 排序规则(太慢了,) 17 | public List lexicalOrder(int n) { 18 | TreeSet set = new TreeSet<>(new Comparator() { 19 | @Override 20 | public int compare(Integer o1, Integer o2) { 21 | return o1.toString().compareTo(o2.toString()); 22 | } 23 | }); 24 | for (int i = 1; i <= n; i++) { 25 | set.add(i); 26 | } 27 | return new ArrayList<>(set); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/sort/P435NonOverlappingIntervals.java: -------------------------------------------------------------------------------- 1 | package sort; 2 | 3 | import java.util.Arrays; 4 | 5 | /** 6 | * Title: 435. 无重叠区间 (greedy章节也有此类型题目) 7 | * Desc: 给定一个区间的集合,找到需要移除区间的最小数量,使剩余区间互不重叠。 注意: 8 | * 可以认为区间的终点总是大于它的起点。 区间 [1,2] 和 [2,3] 的边界相互“接触”,但没有相互重叠。 9 | * Created by Myth on 9/25/2019 10 | */ 11 | public class P435NonOverlappingIntervals { 12 | 13 | // case1 ---prev------ =====cur==== cur.start >= last.end 不删除,更新 last end为当前 end 14 | // case2 =======cur======== cur.start < last.end删除,不更新end 15 | // ----prev----- 16 | // case3 ------prev----- 17 | // =====cur======= 18 | public int eraseOverlapIntervals(int[][] intervals) { 19 | if (intervals.length == 0) { 20 | return 0; 21 | } 22 | // 按照终点排序 23 | Arrays.sort(intervals, (a, b) -> (a[1] - b[1])); 24 | int end = intervals[0][1]; 25 | int count = 1; 26 | // 由于我们事先排了序,不难发现所有与 x 相交的区间必然会与 x 的 end 相交; 27 | // 如果一个区间不想与 x 的 end 相交,它的 start 必须要大于(或等于)x 的 end(x就是上一个区间的end) 28 | for (int i = 1; i < intervals.length; i++) { 29 | if (intervals[i][0] >= end) { 30 | end = intervals[i][1]; // 当前区间是不需要删除的,就更新一下end, 记一下数 31 | count++; 32 | } 33 | } 34 | return intervals.length - count; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/sort/README.md: -------------------------------------------------------------------------------- 1 | # 排序 2 | ![](../../pic/sort.png) 3 | - 重要的排序算法 4 | - 快排思想 - partition函数 5 | - 归并排序 6 | - 堆排序(详细见datastructure.heap包) 7 | - 区间合并、排序(56、57) 8 | - 桶排序(164) 9 | - 煎饼排序(969) 10 | - 计数排序(1122) : 给定了一个范围,可以去计数 -------------------------------------------------------------------------------- /src/tree/P979DistributeCoinsInBinaryTree.java: -------------------------------------------------------------------------------- 1 | package tree; 2 | 3 | import util.TreeNode; 4 | 5 | /** 6 | * Title: 979. 在二叉树中分配硬币 7 | * Desc: N 枚硬币运到 N 个节点上 8 | * Created by Myth-PC on 2019-10-02 9 | */ 10 | public class P979DistributeCoinsInBinaryTree { 11 | // 自底向上(后序遍历) 12 | private int dis = 0; 13 | public int distributeCoins(TreeNode root) { 14 | dis = 0; 15 | require(root); 16 | return dis; 17 | } 18 | // 当前节点及其子树所需的硬币数目(val+left+right-1) 19 | private int require(TreeNode root) { 20 | if (root == null) return 0; 21 | int left = require(root.left); 22 | int right = require(root.right); 23 | // 每个子树需要与父节点交换的数目 24 | dis += Math.abs(left) + Math.abs(right); 25 | return root.val+left+right-1; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/tree/bst/P1038BSTToGreaterSumTree.java: -------------------------------------------------------------------------------- 1 | package tree.bst; 2 | 3 | import util.TreeNode; 4 | 5 | /** 6 | * Title: 1038. 从二叉搜索树到更大的和树 7 | * Desc: 给出二叉搜索树的根节点,该二叉树的节点值各不相同,修改二叉树, 8 | * 使每个节点 node 的新值等于原树中大于或等于 node.val 的值之和。 9 | * Created by Myth-PC on 2019-10-06 10 | */ 11 | public class P1038BSTToGreaterSumTree { 12 | // 本题依然是中序遍历,不一样的是先遍历右子树 13 | private TreeNode pre = null; 14 | public TreeNode bstToGst(TreeNode root) { 15 | pre = null; 16 | inorderDFS(root); 17 | return root; 18 | } 19 | private void inorderDFS(TreeNode root) { 20 | if (root == null) return; 21 | inorderDFS(root.right); 22 | if (pre != null) { 23 | root.val = root.val + pre.val; 24 | } 25 | pre = root; 26 | inorderDFS(root.left); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/tree/bst/P108ConvertSortedArrayToBalancedBST.java: -------------------------------------------------------------------------------- 1 | package tree.bst; 2 | 3 | import util.TreeNode; 4 | 5 | /** 6 | * Title: 108. 将有序数组转换为二叉搜索树 7 | * Desc: 将一个按照升序排列的有序数组,转换为一棵高度平衡二叉搜索树。 8 | * 本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1。 9 | * Created by Myth-PC on 2019-10-05 10 | */ 11 | public class P108ConvertSortedArrayToBalancedBST { 12 | // 树尽量平衡 —— 要求数组尽量划分均等(二分)(使用下标进行划分,注意数目的奇偶) 13 | // 0 1 2 3 4 5 14 | public TreeNode sortedArrayToBST(int[] nums) { 15 | TreeNode root = helper(nums, 0, nums.length-1); 16 | return root; 17 | } 18 | private TreeNode helper(int[] nums, int i, int j) { 19 | if (i < 0 || j >= nums.length || i > j) return null; 20 | int mid = (i+j)/2; 21 | TreeNode node = new TreeNode(nums[mid]); 22 | node.left = helper(nums, i, mid-1); 23 | node.right = helper(nums, mid+1, j); 24 | return node; 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/tree/bst/P230KthSmallestElementInBST.java: -------------------------------------------------------------------------------- 1 | package tree.bst; 2 | 3 | import util.TreeNode; 4 | 5 | /** 6 | * Title: 230. 二叉搜索树中第K小的元素 7 | * Desc: 8 | * Created by Myth-PC on 2019-10-04 9 | */ 10 | public class P230KthSmallestElementInBST { 11 | // BST的重要性质是中序遍历有序,所以计数等于K时候退出 12 | private int count = 0; 13 | public int kthSmallest(TreeNode root, int k) { 14 | count = 0; 15 | TreeNode kth = find(root, k); 16 | if (kth != null) return kth.val; 17 | return -1; 18 | } 19 | private TreeNode find(TreeNode root, int k) { 20 | if (root == null) return null; 21 | TreeNode left = find(root.left, k); 22 | if (left != null) return left; 23 | count++; 24 | if (count == k) return root; 25 | return find(root.right, k); 26 | } 27 | // 改良后的写法 28 | private int count2 = 0; 29 | private int ret = 0; 30 | public int kthSmallest2(TreeNode root, int k) { 31 | count2 = 0; 32 | ret = -1; 33 | find(root, k); 34 | return ret; 35 | } 36 | private void find2(TreeNode root, int k) { 37 | if (root.left != null) find(root.left, k); 38 | count++; 39 | if (count == k) { 40 | ret = root.val; 41 | return; 42 | } 43 | if (root.right != null) find(root.right, k); 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/tree/bst/P700SearchInBST.java: -------------------------------------------------------------------------------- 1 | package tree.bst; 2 | 3 | import util.TreeNode; 4 | 5 | /** 6 | * Title: 700. 二叉搜索树中的搜索 7 | * Desc: 8 | * Created by Myth on 10/3/2019 9 | */ 10 | public class P700SearchInBST { 11 | public TreeNode searchBST(TreeNode root, int val) { 12 | if(root == null) return null; 13 | if (root.val == val) return root; 14 | if (root.val < val) return searchBST(root.left, val); 15 | return searchBST(root.right, val); 16 | } 17 | // 迭代版本也很简单 18 | } 19 | -------------------------------------------------------------------------------- /src/tree/bst/P701InsertIntoBST.java: -------------------------------------------------------------------------------- 1 | package tree.bst; 2 | 3 | import util.TreeNode; 4 | import util.TreeUtil; 5 | 6 | /** 7 | * Title: 701. 二叉搜索树中的插入操作 8 | * Desc: 9 | * Created by Myth on 10/3/2019 10 | */ 11 | public class P701InsertIntoBST { 12 | public TreeNode insertIntoBST(TreeNode root, int val) { 13 | TreeNode cur = root; 14 | TreeNode pre; 15 | while (true) { 16 | pre = cur; 17 | if (cur.val > val) { 18 | cur = cur.left; 19 | if (cur == null) { 20 | pre.left = new TreeNode(val); 21 | break; 22 | } 23 | } 24 | else { 25 | cur = cur.right; 26 | if (cur == null) { 27 | pre.right = new TreeNode(val); 28 | break; 29 | } 30 | } 31 | } 32 | return root; 33 | } 34 | // 递归版本 35 | public TreeNode insertIntoBSTRecursive(TreeNode root, int val) { 36 | if (root == null) return new TreeNode(val); 37 | if (val > root.val) root.right = insertIntoBSTRecursive(root.right, val); 38 | else root.left = insertIntoBSTRecursive(root.left, val); 39 | return root; 40 | } 41 | public static void main(String[] args) { 42 | P701InsertIntoBST p701 = new P701InsertIntoBST(); 43 | TreeNode root = TreeUtil.stringToTreeNode("[4,2,7,1,3,6]"); 44 | root = p701.insertIntoBST(root,5); 45 | TreeUtil.prettyPrintTree(root); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/tree/bst/P96UniqueBST.java: -------------------------------------------------------------------------------- 1 | package tree.bst; 2 | 3 | 4 | /** 5 | * Title: 96. 不同的二叉搜索树 6 | * Desc: 给定一个整数 n,求以 1 ... n 为节点组成的二叉搜索树有多少种? 7 | * Created by Myth-MBP on 12/07/2020 in VSCode 8 | */ 9 | public class P96UniqueBST { 10 | // 二叉树的个数和序列内容无关,只和数列长度有关 11 | // G(n) = f(i, n) i = 0 -> n-1 // 代表以i为根,n代表长度 12 | // f(i,n) = G(i-1) + G(n-i) 13 | public int numTrees(int n) { 14 | int[] G = new int[n + 1]; 15 | G[0] = 1; 16 | G[1] = 1; 17 | 18 | for (int i = 2; i <= n; ++i) { 19 | for (int j = 1; j <= i; ++j) { 20 | G[i] += G[j - 1] * G[i - j]; 21 | } 22 | } 23 | return G[n]; 24 | } 25 | } -------------------------------------------------------------------------------- /src/tree/depth/P104MaxDepthBinaryTree.java: -------------------------------------------------------------------------------- 1 | package tree.depth; 2 | 3 | import util.TreeNode; 4 | 5 | /** 6 | * Title: 104. 二叉树的最大深度 7 | * Desc: 给定一个二叉树,找出其最大深度。二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。 8 | * 说明: 叶子节点是指没有子节点的节点。 9 | * Created by Myth-PC on 2019-09-18 10 | */ 11 | public class P104MaxDepthBinaryTree { 12 | // 递归思路:二叉树的最大深度 = max(左子树深度,右子树深度) 13 | public int maxDepth(TreeNode root) { 14 | if (root == null) return 0; 15 | int left = maxDepth(root.left); 16 | int right = maxDepth(root.right); 17 | return Math.max(left, right)+1; 18 | } 19 | // 递归 借助栈 变成迭代 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/tree/depth/P110BalanceBinaryTree.java: -------------------------------------------------------------------------------- 1 | package tree.depth; 2 | 3 | import util.TreeNode; 4 | 5 | /** 6 | * Title: 110. 平衡二叉树 7 | * Desc: 判断树是否是高度平衡的二叉树(左右子树的高度不相差1) 8 | * Created by Myth-PC on 2019-09-18 9 | */ 10 | public class P110BalanceBinaryTree { 11 | // 在104的思路基础上修改:先计算树的高度,如果发现当前的节点高度差大于1了 12 | // 那么就直接返回-1 13 | private int recursive(TreeNode root) { 14 | if (root == null) return 0; 15 | int left = recursive(root.left); 16 | if (left == -1) return -1; 17 | int right = recursive(root.right); 18 | if (right == -1) return -1; 19 | if (Math.abs(left-right)>1) return -1; 20 | return Math.max(left, right) + 1; 21 | } 22 | 23 | public boolean isBalanced(TreeNode root) { 24 | return recursive(root) != -1; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/tree/depth/P111MinDepthBinaryTree.java: -------------------------------------------------------------------------------- 1 | package tree.depth; 2 | 3 | import util.TreeNode; 4 | 5 | /** 6 | * Title: 111. 二叉树的最小深度 7 | * Desc: 8 | * Created by Myth-PC on 2019-09-18 9 | */ 10 | public class P111MinDepthBinaryTree { 11 | // 难点:当二叉树退化成单侧时 12 | public int minDepth(TreeNode root) { 13 | if (root == null) return 0; 14 | int left = minDepth(root.left); 15 | int right = minDepth(root.right); 16 | // 下面两句是和Max Depth不一样的地方 17 | if (root.left == null) return right+1; 18 | if (root.right == null) return left+1; 19 | return Math.min(left, right)+1; 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/tree/depth/P559MaxDepthNnaryTree.java: -------------------------------------------------------------------------------- 1 | package tree.depth; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * Title: 559. N叉树的最大深度 7 | * Desc: 给定一个 N 叉树,找到其最大深度。最大深度是指从根节点到最远叶子节点的最长路径上的节点总数。 8 | * Created by Myth on 9/19/2019 9 | */ 10 | public class P559MaxDepthNnaryTree { 11 | class Node { 12 | public int val; 13 | public List children; 14 | 15 | public Node() {} 16 | 17 | public Node(int _val, List _children) { 18 | val = _val; 19 | children = _children; 20 | } 21 | } 22 | public int maxDepth(Node root) { 23 | if (root == null) return 0; 24 | int max = 0, cur; 25 | for (Node node : root.children) { 26 | cur = maxDepth(node); 27 | if (max < cur) max = cur; 28 | } 29 | return max+1; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/tree/level/P107BinaryTreeLevelOrderTraversal2.java: -------------------------------------------------------------------------------- 1 | package tree.level; 2 | 3 | import util.TreeNode; 4 | 5 | import java.util.ArrayList; 6 | import java.util.LinkedList; 7 | import java.util.List; 8 | import java.util.Queue; 9 | 10 | /** 11 | * Title: 107. 二叉树的层次遍历 II 12 | * Desc: 给定一个二叉树,返回其节点值自底向上的层次遍历。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历) 13 | * 和102题一模一样,只不过本题需要将(层次)结果翻转一下,使用链表的头插,实现结果的翻转 14 | * Created by Myth on 9/20/2019 15 | */ 16 | public class P107BinaryTreeLevelOrderTraversal2 { 17 | public List> levelOrderBottom(TreeNode root) { 18 | LinkedList> ret = new LinkedList<>(); 19 | if (root == null) return ret; 20 | Queue queue = new LinkedList<>(); 21 | queue.add(root); 22 | while (!queue.isEmpty()) { 23 | int nodeCount = queue.size(); // Key 24 | List level = new ArrayList<>(nodeCount); 25 | for (int i = 0; i < nodeCount; i++) { 26 | TreeNode node = queue.poll(); 27 | level.add(node.val); 28 | if (node.left != null) queue.add(node.left); 29 | if (node.right != null) queue.add(node.right); 30 | } 31 | ret.addFirst(level); 32 | } 33 | return ret; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/tree/path/P112PathSum.java: -------------------------------------------------------------------------------- 1 | package tree.path; 2 | 3 | import util.TreeNode; 4 | 5 | public class P112PathSum { 6 | public boolean hasPathSum(TreeNode root, int sum) { 7 | if (root == null) return false; 8 | // if (root.left == null && root.right == null && root.val == sum) return true; 9 | if (root.left == null && root.right == null) return root.val == sum; 10 | return hasPathSum(root.left, sum-root.val) || hasPathSum (root.right, sum-root.val); 11 | } 12 | } -------------------------------------------------------------------------------- /src/tree/path/P113PathSum2.java: -------------------------------------------------------------------------------- 1 | package tree.path; 2 | 3 | import util.TreeNode; 4 | 5 | import java.util.ArrayList; 6 | import java.util.LinkedList; 7 | import java.util.List; 8 | 9 | public class P113PathSum2 { 10 | // 难点:如何保存结果,回溯法!!! 11 | private void findPath(TreeNode root, int sum, LinkedList path, List> res) { 12 | if (root == null) return; 13 | path.add(root.val); 14 | if (root.left == null && root.right == null) { 15 | // 注意必须new一个新的list 16 | if (root.val == sum) res.add(new LinkedList<>(path)); 17 | } 18 | findPath(root.left, sum-root.val, path, res); 19 | findPath(root.right, sum-root.val, path, res); 20 | path.removeLast(); 21 | } 22 | public List> pathSum(TreeNode root, int sum) { 23 | LinkedList path = new LinkedList(); 24 | List> res = new ArrayList<>(); 25 | findPath(root, sum, path, res); 26 | return res; 27 | } 28 | } -------------------------------------------------------------------------------- /src/tree/path/P129SumRootToLeafNumbers.java: -------------------------------------------------------------------------------- 1 | package tree.path; 2 | 3 | import util.TreeNode; 4 | 5 | /** 6 | * Title: 129. 求根到叶子节点数字之和 7 | * Desc: 8 | * Created by Myth on 9/25/2019 9 | */ 10 | public class P129SumRootToLeafNumbers { 11 | private int sum; 12 | public int sumNumbers(TreeNode root) { 13 | sum = 0; 14 | helper(root, 0); 15 | return sum; 16 | } 17 | private void helper(TreeNode root, int pre) { 18 | if (root == null) return; 19 | pre = pre * 10 + root.val; 20 | if (root.left == null && root.right == null) { 21 | sum += pre; 22 | return; 23 | } 24 | helper(root.left, pre); 25 | helper(root.right, pre); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/tree/path/P235LowestCommonAncestorOfBST.java: -------------------------------------------------------------------------------- 1 | package tree.path; 2 | 3 | import util.TreeNode; 4 | 5 | /** 6 | * Title: 235. 二叉搜索树的最近公共祖先 7 | * Desc: 给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。 8 | * 9 | * 百度百科中最近公共祖先的定义为: 10 | * “对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x, 11 | * 满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。” 12 | * 13 | * Created by Myth on 9/29/2019 14 | */ 15 | public class P235LowestCommonAncestorOfBST { 16 | // 该题求BST的最近公共祖先,难度显著降低,BST有明显的特点 17 | // 遍历树,如果p,q都在左(右)子树,那么就从左(右)子树进行递归,否则就找到了LCA 18 | public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { 19 | if (root == null) return null; 20 | if (p.val < root.val && q.val < root.val) return lowestCommonAncestor(root.left, p, q); 21 | if (p.val > root.val && q.val > root.val) return lowestCommonAncestor(root.right, p, q); 22 | return root; 23 | } 24 | // 迭代写法:循环找到一个节点(分割点),使得该节点将p 和 q分开 25 | public TreeNode lowestCommonAncestor2(TreeNode root, TreeNode p, TreeNode q) { 26 | TreeNode cur = root; 27 | while (cur != null) { 28 | if (p.val < cur.val && q.val < cur.val) cur = cur.left; 29 | else if (p.val > cur.val && q.val > cur.val) cur = cur.right; 30 | else return cur; 31 | } 32 | return cur; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/tree/path/P257BinaryTreeAllPaths.java: -------------------------------------------------------------------------------- 1 | package tree.path; 2 | 3 | import util.TreeNode; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | /** 9 | * Title: 257. 二叉树的所有路径 10 | * Desc: 给定一个二叉树,返回所有从根节点到叶子节点的路径。 11 | * Created by Myth on 9/29/2019 12 | */ 13 | public class P257BinaryTreeAllPaths { 14 | public List binaryTreePaths(TreeNode root) { 15 | List res = new ArrayList<>(); 16 | addPath(root, "", res); 17 | return res; 18 | } 19 | private void addPath(TreeNode root, String curPath, List res) { 20 | if (root == null) return; 21 | if (root.left == null && root.right == null) { 22 | curPath += Integer.toString(root.val); 23 | res.add(curPath); 24 | return; 25 | } 26 | curPath += Integer.toString(root.val) + "->"; 27 | addPath(root.left, curPath, res); 28 | addPath(root.right, curPath, res); 29 | } 30 | } -------------------------------------------------------------------------------- /src/tree/path/P543DiameterPathOfBinaryTree.java: -------------------------------------------------------------------------------- 1 | package tree.path; 2 | 3 | import util.TreeNode; 4 | 5 | /** 6 | * Title: 543. 二叉树的直径 7 | * Desc: 给定一棵二叉树,你需要计算它的直径长度。一棵二叉树的直径长度是任意两个结点路径长度中的最大值。这条路径可能穿过根结点。 8 | * 注意:两结点之间的路径长度是以它们之间边的数目表示 9 | * Created by Myth on 9/28/2019 10 | */ 11 | public class P543DiameterPathOfBinaryTree { 12 | // 和 124、687 类型相似 13 | private int maxSum; 14 | public int diameterOfBinaryTree(TreeNode root) { 15 | maxSum = 0; 16 | helper(root); 17 | return maxSum; 18 | } 19 | // 求单侧的路径长度 20 | public int helper(TreeNode root) { 21 | if (root == null) return -1; 22 | if (root.left == null && root.right == null) return 0; 23 | int left = helper(root.left)+1; 24 | int right = helper(root.right)+1; 25 | maxSum = Math.max(maxSum, left+right); 26 | return Math.max(left, right); 27 | } 28 | 29 | public static void main(String[] args) { 30 | P543DiameterPathOfBinaryTree p543 = new P543DiameterPathOfBinaryTree(); 31 | TreeNode node5 = new TreeNode(5); 32 | TreeNode node4 = new TreeNode(4); 33 | TreeNode node2 = new TreeNode(2, node4, node5); 34 | TreeNode node3 = new TreeNode(3); 35 | TreeNode node1 = new TreeNode(1, node2, node3); 36 | System.out.println(p543.helper(node1)); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/tree/path/P687LongestUnivaluePath.java: -------------------------------------------------------------------------------- 1 | package tree.path; 2 | 3 | import util.TreeNode; 4 | import util.TreeUtil; 5 | 6 | /** 7 | * Title: 687. 最长同值路径(本题是任意两个节点的路径)和543基本写法一样 8 | * Desc: 给定一个二叉树,找到最长的路径,这个路径中的每个节点具有相同值。 9 | * 注意:这条路径可以经过也可以不经过根节点。两个节点之间的路径长度由它们之间的边数表示。 10 | * Created by Myth on 9/24/2019 11 | */ 12 | public class P687LongestUnivaluePath { 13 | // 这种路径是 1.左子树最长同值路径(单箭头路径) 2. 右子树最长同值路径(单箭头路径) 的 最大值 14 | private int longest = 0; 15 | public int longestUnivaluePath(TreeNode root) { 16 | longest = 0; 17 | arrowPath(root); 18 | return longest; 19 | } 20 | private int arrowPath(TreeNode root) { 21 | if (root == null) return 0; 22 | int left = arrowPath(root.left); 23 | int right = arrowPath(root.right); 24 | int arrowLeft = 0, arrowRight = 0; 25 | if (root.left != null && root.left.val == root.val) arrowLeft = left + 1; 26 | if (root.right != null && root.right.val == root.val) arrowRight = right + 1; 27 | // 更新最终结果是双向的 28 | longest = Math.max(longest, arrowLeft + arrowRight); 29 | // 返回的是单向的 30 | return Math.max(arrowLeft, arrowRight); 31 | } 32 | 33 | public static void main(String[] args) { 34 | TreeNode root = TreeUtil.stringToTreeNode("[1,4,5,4,4,5]"); 35 | P687LongestUnivaluePath p687 = new P687LongestUnivaluePath(); 36 | System.out.println(p687.longestUnivaluePath(root)); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/tree/pruning/P669TrimBinarySearchTree.java: -------------------------------------------------------------------------------- 1 | package tree.pruning; 2 | 3 | import util.TreeNode; 4 | 5 | /** 6 | * Title: 669. 修剪二叉搜索树 7 | * Desc: 给定一个二叉搜索树,同时给定最小边界L 和最大边界 R。 8 | * 通过修剪二叉搜索树,使得所有节点的值在[L, R]中 (R>=L) 。你可能需要改变树的根节点,所以结果应当返回修剪好的二叉搜索树的新的根节点。 9 | * Created by Myth-MBP on 04/06/2020 in VSCode 10 | */ 11 | 12 | class P669TrimBinarySearchTree { 13 | // 重点在于如何剪枝,如何调整节点 14 | // 参考思路:< L , 只保留二叉树的右子树 15 | // > R, 只保留二叉树的左子树 16 | public TreeNode trimBST(TreeNode root, int L, int R) { 17 | if (root == null) return null; 18 | // 调整节点(难点) 19 | // < L, 只保留二叉树的右子树(结果肯定在右边) 20 | if (root.val < L) return trimBST(root.right, L, R); 21 | 22 | // > R, 只保留二叉树的左子树(结果肯定在左边) 23 | if (root.val > R) return trimBST(root.left, L, R); 24 | 25 | root.left = trimBST(root.left, L, R); 26 | root.right = trimBST(root.right, L, R); 27 | return root; 28 | } 29 | } -------------------------------------------------------------------------------- /src/tree/pruning/P814BinaryTreePruning.java: -------------------------------------------------------------------------------- 1 | package tree.pruning; 2 | 3 | import util.TreeNode; 4 | 5 | /** 6 | * Title: 814. 二叉树剪枝 7 | * Desc: 给定二叉树根结点 root ,此外树的每个结点的值要么是 0,要么是 1。 8 | * 返回移除了所有不包含 1 的子树的原二叉树。(节点 X 的子树为 X 本身,以及所有 X 的后代。) 9 | * Created by Myth-MBP on 04/06/2020 in VSCode 10 | */ 11 | 12 | public class P814BinaryTreePruning { 13 | // 判断是否含有1 14 | private boolean hasOne(TreeNode root) { 15 | if (root == null) return false; 16 | if (root.val == 1) return true; 17 | return hasOne(root.left) || hasOne(root.right); 18 | } 19 | public TreeNode pruneTree(TreeNode root) { 20 | if (!hasOne(root)) return null; 21 | root.left = pruneTree(root.left); 22 | root.right = pruneTree(root.right); 23 | return root; 24 | } 25 | // 后续遍历写法 26 | public TreeNode pruneTree2(TreeNode root) { 27 | if (root == null) return null; 28 | root.left = pruneTree(root.left); 29 | root.right = pruneTree(root.right); 30 | if (root.val == 0 && root.left == null && root.right == null) 31 | return null; 32 | return root; 33 | } 34 | 35 | } -------------------------------------------------------------------------------- /src/tree/structure/P100SameTree.java: -------------------------------------------------------------------------------- 1 | package tree.structure; 2 | 3 | import util.TreeNode; 4 | 5 | /** 6 | * Title: 100. 相同的树 7 | * Desc: 给定两个二叉树,编写一个函数来检验它们是否相同。如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。 8 | * Created by Myth on 9/18/2019 9 | */ 10 | public class P100SameTree { 11 | 12 | public boolean isSameTree(TreeNode p, TreeNode q) { 13 | if (p == null && q == null) return true; 14 | if (p != null && q != null && p.val != q.val) return false; 15 | if ((p != null && q == null) || (p == null && q != null)) return false; 16 | boolean left = isSameTree(p.left, q.left); 17 | boolean right = isSameTree(p.right, q.right); 18 | return left && right; 19 | } 20 | // 简化判断条件 21 | public boolean isSameTree2(TreeNode p, TreeNode q) { 22 | // if (p == null && q == null) return true; // 同时为空 23 | // if (p == null || q == null) return false; // 不同时为空 24 | if (p == null || q == null) return p == q; // 更精简的写法 25 | if (p.val != q.val) return false; // 都不为空 26 | return isSameTree(p.left, q.left) && isSameTree(p.right, q.right); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/tree/structure/P226InvertBinaryTree.java: -------------------------------------------------------------------------------- 1 | package tree.structure; 2 | 3 | import util.TreeNode; 4 | 5 | /** 6 | * Title: 226. 反转二叉树 7 | * Desc: 8 | * Created by Myth-PC on 05/02/2020 in VSCode 9 | */ 10 | public class P226InvertBinaryTree { 11 | // 这种写法不太好,以为返回值显得很多余 12 | public TreeNode invertTree(TreeNode root) { 13 | if (root == null) return null; 14 | TreeNode temp = root.left; 15 | root.left = root.right; 16 | root.right = temp; 17 | invertTree(root.left); 18 | invertTree(root.right); 19 | return root; 20 | } 21 | public void invertTree2(TreeNode root) { 22 | if (root == null) return; 23 | // 前序遍历(可改成中序或者后序遍历) 24 | TreeNode temp = root.left; 25 | root.left = root.right; 26 | root.right = temp; 27 | invertTree2(root.left); 28 | invertTree2(root.right); 29 | } 30 | // 实际上这是一个简洁版的中序遍历 31 | public TreeNode invertTree3(TreeNode root) { 32 | if (root == null) return null; 33 | TreeNode temp = root.left; 34 | root.left = invertTree(root.right); 35 | root.right = invertTree(temp); 36 | return root; 37 | } 38 | public TreeNode invertTree4(TreeNode root) { 39 | if (root == null) return null; 40 | TreeNode left = invertTree(root.left); 41 | TreeNode right = invertTree(root.right); 42 | root.left = right; 43 | root.right = left; 44 | return root; 45 | } 46 | 47 | } -------------------------------------------------------------------------------- /src/tree/structure/P872LeafSimilarTrees.java: -------------------------------------------------------------------------------- 1 | package tree.structure; 2 | 3 | import util.TreeNode; 4 | 5 | import java.util.LinkedList; 6 | import java.util.List; 7 | 8 | 9 | /** 10 | * Title: 872. 叶子相似的树 11 | * Desc: 请考虑一颗二叉树上所有的叶子,这些叶子的值按从左到右的顺序排列形成一个 叶值序列 。 12 | 13 | 如果有两颗二叉树的叶值序列是相同,那么我们就认为它们是 叶相似 的。 14 | 如果给定的两个头结点分别为 root1 和 root2 的树是叶相似的,则返回 true;否则返回 false 。 15 | 16 | * Created by Myth-MBP on 27/08/2020 in VSCode 17 | */ 18 | 19 | public class P872LeafSimilarTrees { 20 | private void allLeaf(TreeNode root, List res) { 21 | if(root == null) return; 22 | if(root.left == null && root.right == null) { 23 | res.add(root.val); 24 | return; 25 | } 26 | allLeaf(root.left, res); 27 | allLeaf(root.right, res); 28 | } 29 | public boolean leafSimilar(TreeNode root1, TreeNode root2) { 30 | List res1 = new LinkedList<>(); 31 | List res2 = new LinkedList<>(); 32 | allLeaf(root1, res1); 33 | allLeaf(root2, res2); 34 | return res1.equals(res2); 35 | } 36 | } -------------------------------------------------------------------------------- /src/tree/traversal/P105ConstructFromPreorderInorder.java: -------------------------------------------------------------------------------- 1 | package tree.traversal; 2 | 3 | import util.TreeNode; 4 | 5 | /** 6 | * Title: 105. 从前序与中序遍历序列构造二叉树 7 | * Desc: 根据一棵树的前序遍历与中序遍历构造二叉树。 8 | * 注意: 9 | * 你可以假设树中没有重复的元素。 10 | * Created by Myth on 9/17/2019 11 | */ 12 | public class P105ConstructFromPreorderInorder { 13 | // 难点:如何在数组上分区域 14 | // 方案:递归的在子数组上进行操作 15 | public TreeNode buildTree(int[] preorder, int[] inorder) { 16 | return helper(0, 0, inorder.length-1, preorder, inorder); 17 | } 18 | public TreeNode helper(int preStart, int inStart, int inEnd, int[] preorder, int[] inorder) { 19 | if (preStart >= preorder.length || inStart > inEnd) return null; 20 | // 当前根节点 21 | TreeNode root = new TreeNode(preorder[preStart]); 22 | // 中序遍历中找到当前的 根节点,划分左右区域(为了加速可以使用 HashMap,O(1)时间找到inIndex) 23 | int inIndex = 0; 24 | for (int i = inStart; i <= inEnd; i++) { 25 | if (inorder[i] == root.val) { 26 | inIndex = i; 27 | break; 28 | } 29 | } 30 | // 划分区域 31 | root.left = helper(preStart+1, inStart, inIndex-1, preorder, inorder); 32 | root.right = helper(preStart+inIndex-inStart+1, inIndex+1, inEnd, preorder, inorder); 33 | return root; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/tree/traversal/P106ConstructFromInorderPostorder.java: -------------------------------------------------------------------------------- 1 | package tree.traversal; 2 | 3 | import util.TreeNode; 4 | 5 | /** 6 | * Title: 106. 从中序与后序遍历序列构造二叉树 7 | * Desc: 在105的基础(思路)之上修改 8 | * Created by Myth on 9/17/2019 9 | */ 10 | public class P106ConstructFromInorderPostorder { 11 | public TreeNode buildTree(int[] inorder, int[] postorder) { 12 | return helper(postorder.length-1, 0, inorder.length-1, postorder, inorder); 13 | } 14 | public TreeNode helper(int postStart, int inStart, int inEnd, int[] postorder, int[] inorder) { 15 | if (postStart < 0 || inStart > inEnd) return null; 16 | // 当前根节点 17 | TreeNode root = new TreeNode(postorder[postStart]); 18 | // 后序遍历中从后往前找到当前根节点,划分左右区域(为了加速可以使用 HashMap,O(1)时间找到inIndex) 19 | int inIndex = 0; 20 | for (int i = inStart; i <= inEnd; i++) { 21 | if (inorder[i] == root.val) { 22 | inIndex = i; 23 | break; 24 | } 25 | } 26 | // 划分区域(难点) 27 | root.left = helper(postStart-(inEnd-inIndex+1), inStart, inIndex-1, postorder, inorder); 28 | root.right = helper(postStart-1, inIndex+1, inEnd, postorder, inorder); 29 | return root; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/tree/traversal/P114FlattenTree.java: -------------------------------------------------------------------------------- 1 | package tree.traversal; 2 | 3 | import util.TreeNode; 4 | 5 | 6 | /** 7 | * Title: 114. 二叉树展开为链表 8 | * Desc: 给定一个二叉树,原地将它展开为一个单链表。 9 | * Created by Myth-MBP on 10 | * 12/07/2020 in VSCode 11 | */ 12 | public class P114FlattenTree { 13 | TreeNode pre = null; 14 | // 前序 倒序 --> 右 左 root 15 | public void flatten(TreeNode root) { 16 | if (root == null) return; 17 | 18 | flatten(root.right); 19 | flatten(root.left); 20 | 21 | root.left = null; 22 | root.right = pre; 23 | pre = root; 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /src/tree/traversal/P145BinaryTreePostorderTraversal.java: -------------------------------------------------------------------------------- 1 | package tree.traversal; 2 | 3 | import util.TreeNode; 4 | 5 | import java.util.ArrayList; 6 | import java.util.LinkedList; 7 | import java.util.List; 8 | import java.util.Stack; 9 | 10 | /** 11 | * Title: 145. 二叉树的后序遍历 12 | * Desc: 后序遍历的迭代算法和递归算法 13 | * Created by Myth on 9/14/2019 14 | */ 15 | public class P145BinaryTreePostorderTraversal { 16 | // 递归 17 | public void postorder(TreeNode root, List ret) { 18 | if (root == null) return; 19 | postorder(root.left, ret); 20 | postorder(root.right, ret); 21 | ret.add(root.val); 22 | } 23 | public List postorderTraversal(TreeNode root) { 24 | List ret = new ArrayList<>(); 25 | postorder(root, ret); 26 | return ret; 27 | } 28 | // 非递归算法(前序遍历的顺序是 根-左-右,后序遍历的顺序是 左-右-根) 29 | // 那么 前序稍微修改一下 变成 根-右-左,然后把结果倒序,就变成 左-右-根 了 30 | public List postorderIterative(TreeNode root) { 31 | // 使用LinkedList的头插,让结果倒序 32 | LinkedList ret = new LinkedList<>(); 33 | Stack stack = new Stack<>(); 34 | TreeNode cur = root; 35 | stack.add(cur); 36 | while (!stack.empty()) { 37 | cur = stack.pop(); 38 | if (cur != null) { 39 | ret.addFirst(cur.val); // 头插法,让结果倒序 40 | stack.add(cur.left); 41 | stack.add(cur.right); 42 | } 43 | } 44 | return ret; 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/tree/traversal/P199BinaryTreeRightSideView.java: -------------------------------------------------------------------------------- 1 | package tree.traversal; 2 | 3 | import java.util.*; 4 | 5 | import util.TreeNode; 6 | 7 | /** 8 | * Title: 199. 二叉树的 右视图 9 | * Desc: 从右看,能看到的序列 10 | * Created by Myth-MBP on 13/07/2020 in VSCode 11 | */ 12 | public class P199BinaryTreeRightSideView { 13 | // 1. BFS : 每一层最右边的元素 14 | 15 | // 2. DFS: 前序遍历,先访问右子树,然后再访问左子树 16 | 17 | public List rightSideView(TreeNode root) { 18 | List ret = new ArrayList<>(); 19 | dfs(root, 0, ret); 20 | return ret; 21 | } 22 | private void dfs(TreeNode root, int level, List ret) { 23 | if (root == null) { 24 | return; 25 | } 26 | if (level == ret.size()){ 27 | ret.add(root.val); 28 | } 29 | level++; 30 | dfs(root.right, level, ret); 31 | dfs(root.left, level, ret); 32 | } 33 | } -------------------------------------------------------------------------------- /src/tree/traversal/P426ConvertBST2BiList.java: -------------------------------------------------------------------------------- 1 | package tree.traversal; 2 | 3 | import util.TreeNode; 4 | 5 | /** 6 | * Title: 426. 将二叉搜索树转化为排序的双向链表 7 | * Desc: 剑指offer 36题 8 | * Created by Myth-PC on 09/02/2020 in VSCode 9 | */ 10 | public class P426ConvertBST2BiList { 11 | TreeNode pre = null; 12 | public TreeNode convert(TreeNode root) { 13 | if (root == null) return null; 14 | this.pre = null; 15 | convertHelper(root); 16 | TreeNode p = root; 17 | while(p.left != null) p = p.left; 18 | return p; 19 | } 20 | // 中序遍历 记录前一个访问的节点 21 | // 使用全局变量保存pre,在convertHelper中传pre的值不行 22 | public void convertHelper(TreeNode cur) { 23 | if (cur == null) return; 24 | convertHelper(cur.left); 25 | 26 | cur.left = this.pre; 27 | if (pre != null) this.pre.right = cur; 28 | this.pre = cur; 29 | 30 | convertHelper(cur.right); 31 | } 32 | } -------------------------------------------------------------------------------- /src/tree/traversal/P589NaryTreePreorderTraversal.java: -------------------------------------------------------------------------------- 1 | package tree.traversal; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.Stack; 6 | 7 | /** 8 | * Title: 589. N叉树的前序遍历 9 | * Desc: 10 | * Created by Myth on 9/14/2019 11 | */ 12 | public class P589NaryTreePreorderTraversal { 13 | class Node { 14 | public int val; 15 | public List children; 16 | 17 | public Node() {} 18 | 19 | public Node(int _val, List _children) { 20 | val = _val; 21 | children = _children; 22 | } 23 | } 24 | public void preorder(Node root, List ret) { 25 | if (root == null) return; 26 | ret.add(root.val); 27 | for(Node child : root.children) { 28 | preorder(child, ret); 29 | } 30 | } 31 | public List preorder(Node root) { 32 | List ret = new ArrayList<>(); 33 | preorder(root, ret); 34 | return ret; 35 | } 36 | public List preorderInterative(Node root) { 37 | List ret = new ArrayList<>(); 38 | if (root == null) return null; 39 | Stack stack = new Stack<>(); 40 | Node cur = root; 41 | stack.add(cur); 42 | while (!stack.empty()) { 43 | cur = stack.pop(); 44 | ret.add(cur.val); 45 | for (int i = cur.children.size() - 1; i >= 0; i--) { 46 | stack.add(cur.children.get(i)); 47 | } 48 | } 49 | return ret; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/tree/traversal/P590NaryTreePostorderTraversal.java: -------------------------------------------------------------------------------- 1 | package tree.traversal; 2 | 3 | import java.util.LinkedList; 4 | import java.util.List; 5 | import java.util.Stack; 6 | 7 | /** 8 | * Title: 590. N叉树的后序遍历 9 | * Desc: https://leetcode-cn.com/problems/n-ary-tree-postorder-traversal/ 10 | * Created by Myth on 9/14/2019 11 | */ 12 | public class P590NaryTreePostorderTraversal { 13 | class Node { 14 | public int val; 15 | public List children; 16 | 17 | public Node() {} 18 | 19 | public Node(int _val,List _children) { 20 | val = _val; 21 | children = _children; 22 | } 23 | } 24 | // 迭代版本 25 | public List postorder(Node root) { 26 | LinkedList ret = new LinkedList<>(); 27 | Stack stack = new Stack<>(); 28 | Node cur = root; 29 | stack.add(cur); 30 | while (!stack.empty()) { 31 | cur = stack.pop(); 32 | if (cur != null) { 33 | ret.addFirst(cur.val); 34 | for (Node node : cur.children) { 35 | stack.add(node); 36 | } 37 | } 38 | } 39 | return ret; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/tree/traversal/P889ConstructFromPreorderPostorder.java: -------------------------------------------------------------------------------- 1 | package tree.traversal; 2 | 3 | import util.TreeNode; 4 | 5 | /** 6 | * Title: 889. 根据前序和后序遍历构造二叉树 7 | * Desc: 返回与给定的前序和后序遍历匹配的任何二叉树。pre 和 post 遍历中的值是不同的正整数。 8 | * Created by Myth on 9/17/2019 9 | */ 10 | public class P889ConstructFromPreorderPostorder { 11 | int preIndex = 0, posIndex = 0; 12 | public TreeNode constructFromPrePost(int[] pre, int[] post) { 13 | TreeNode root = new TreeNode(pre[preIndex++]); 14 | if (root.val != post[posIndex]) 15 | root.left = constructFromPrePost(pre, post); 16 | if (root.val != post[posIndex]) 17 | root.right = constructFromPrePost(pre, post); 18 | posIndex++; 19 | return root; 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/twopointer/fastslow/P141LinkedListCycle.java: -------------------------------------------------------------------------------- 1 | package twopointer.fastslow; 2 | 3 | import util.ListNode; 4 | 5 | /** 6 | * Title: 141. 环形链表 7 | * Desc: 给一个链表判断链表是否有环,你能用 O(1)(即,常量)内存解决此问题吗? 8 | * Created by Myth on 9/7/2019 9 | */ 10 | public class P141LinkedListCycle { 11 | // 使用哈希表,将每个指针地址存入,然后判断,空间复杂度 O(n) 12 | // 快慢指针 类似于两人跑步(慢指针每次1步,快指针每次2步),那么 环形部分/1 = 循环迭代的次数 13 | public boolean hasCycle(ListNode head) { 14 | ListNode slow = head, fast = head; 15 | while (fast != null && fast.next != null) { 16 | slow = slow.next; 17 | fast = fast.next; 18 | fast = fast.next; 19 | if (slow == fast) return true; 20 | } 21 | return false; 22 | } 23 | 24 | public static void main(String[] args) { 25 | ListNode node4 = new ListNode(-4); 26 | ListNode node3 = new ListNode(0, node4); 27 | ListNode node2 = new ListNode(2, node3); 28 | ListNode node1 = new ListNode(3, node2); 29 | node4.next = node2; 30 | P141LinkedListCycle p141 = new P141LinkedListCycle(); 31 | System.out.println(p141.hasCycle(node1)); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/twopointer/seq/LCP18BreakfastNumber.java: -------------------------------------------------------------------------------- 1 | package twopointer.seq; 2 | 3 | import java.util.Arrays; 4 | 5 | 6 | 7 | /** 8 | * Title: LCP 18 早餐组合 9 | * Desc: 小扣在秋日市集选择了一家早餐摊位,一维整型数组 staple 中记录了每种主食的价格,一维整型数组 drinks 中记录了每种饮料的价格。 10 | 小扣的计划选择一份主食和一款饮料,且花费不超过 x 元。请返回小扣共有多少种购买方案。 11 | 12 | 注意:答案需要以 1e9 + 7 (1000000007) 为底取模,如:计算初始结果为:1000000008,请返回 1 13 | * Created by Myth on 09/12/2020 in VSCode 14 | */ 15 | 16 | public class LCP18BreakfastNumber { 17 | public int breakfastNumber(int[] staple, int[] drinks, int x) { 18 | Arrays.sort(staple); 19 | Arrays.sort(drinks); 20 | int cnt=0; 21 | int m=staple.length,n=drinks.length; 22 | int i=0,j=n-1; 23 | while(i=0){ 24 | if(staple[i]+drinks[j]<=x){ 25 | cnt=(cnt+j+1)%1000000007; 26 | i++; 27 | }else{ 28 | j--; 29 | } 30 | } 31 | return cnt%1000000007; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/twopointer/seq/P167TwoSum2.java: -------------------------------------------------------------------------------- 1 | package twopointer.seq; 2 | 3 | import java.util.*; 4 | 5 | /** 6 | * Title: 167. 两数之和 II - 输入有序数组(1. 两数之和) 7 | * Desc: 有序数组,求两数之和等于target的下标 8 | * Created by Myth-MBP on 31/08/2020 in VSCode 9 | */ 10 | 11 | public class P167TwoSum2 { 12 | 13 | 14 | public int[] twoSum(int[] numbers, int target) { 15 | int i = 0, j = numbers.length-1; 16 | while (i < j) { 17 | if (numbers[i] + numbers[j] > target) { 18 | j--; 19 | } else if (numbers[i] + numbers[j] < target) { 20 | i++; 21 | } else { 22 | break; 23 | } 24 | } 25 | int[] ret = new int[2]; 26 | ret[0] = i+1; // 本题要求下标从1开始 27 | ret[1] = j+1; 28 | return ret; 29 | } 30 | // 进阶:求对数,重复的不算 31 | public int twoSum2(int[] numbers, int target) { 32 | int i = 0, j = numbers.length-1; 33 | // int count = 0; 34 | Set set = new HashSet<>(); 35 | while (i < j) { 36 | if (numbers[i] + numbers[j] > target) { 37 | j--; 38 | } else if (numbers[i] + numbers[j] < target) { 39 | i++; 40 | } else { 41 | String key = Integer.toString(numbers[i]) + "-" + Integer.toString(numbers[j]); 42 | if (!set.contains(key)) { 43 | set.add(key); 44 | // count++; 45 | } 46 | i++; 47 | j--; 48 | } 49 | } 50 | return set.size(); 51 | } 52 | 53 | } -------------------------------------------------------------------------------- /src/twopointer/seq/P16ThreeSumClosest.java: -------------------------------------------------------------------------------- 1 | package twopointer.seq; 2 | 3 | import java.util.Arrays; 4 | 5 | /** 6 | * Title: 16. 最接近的三数之和 7 | * Desc: 给定一个包括 n 个整数的数组 nums 和 一个目标值 target。找出 nums 中的三个整数,使得它们的和与 target 最接近。返回这三个数的和。 8 | * 假定每组输入只存在唯一答案。 9 | * Created by Myth-Lab on 10/8/2019 10 | */ 11 | public class P16ThreeSumClosest { 12 | // 本题假定只有 一个答案,所以不用去重了 13 | // 和第15题思路一模一样 14 | public int threeSumClosest(int[] nums, int target) { 15 | // nums.length > 3 16 | Arrays.sort(nums); 17 | int ret = nums[0]+nums[1]+nums[2]; 18 | for (int i = 0; i < nums.length-2; i++) { 19 | int j = i+1, k = nums.length-1, sum; 20 | while (j < k) { 21 | sum = nums[i] + nums[j] + nums[k]; 22 | if (Math.abs(sum-target) < Math.abs(ret-target)) { 23 | ret = sum; 24 | } 25 | if (sum == target) { 26 | return target; 27 | } else if (sum > target) { 28 | k--; 29 | } else { 30 | j++; 31 | } 32 | } 33 | } 34 | return ret; 35 | } 36 | 37 | public static void main(String[] args) { 38 | P16ThreeSumClosest p16 = new P16ThreeSumClosest(); 39 | int[] nums = {-3,-2,-5,3,-4}; 40 | System.out.println(p16.threeSumClosest(nums, -1)); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/twopointer/seq/P240Search2DMatrix2.java: -------------------------------------------------------------------------------- 1 | package twopointer.seq; 2 | 3 | /** 4 | * Title: 240. 搜索二维矩阵 II (74题二分法每行升序,下一行第一个大于本行最后一个数) 5 | * Desc: 编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target。 6 | 该矩阵具有以下特性: 7 | 每行的元素从左到右升序排列。 8 | 每列的元素从上到下升序排列。 9 | * Created by Myth-PC on 26/01/2020 in VSCode 10 | */ 11 | public class P240Search2DMatrix2 { 12 | public boolean searchMatrix(int[][] matrix, int target) { 13 | if (matrix.length == 0 || matrix[0].length == 0) return false; 14 | int m = matrix.length, n = matrix[0].length, row = 0, col = n-1; 15 | while (row < m && col >= 0) { 16 | if (matrix[row][col] < target) row++; 17 | else if (matrix[row][col] > target) col--; 18 | else return true; 19 | } 20 | return false; 21 | } 22 | } -------------------------------------------------------------------------------- /src/twopointer/seq/P26RemoveDuplicatesfromSortedArray.java: -------------------------------------------------------------------------------- 1 | package twopointer.seq; 2 | 3 | /** 4 | * Title: 26. 删除排序数组的重复项 5 | * Desc: 不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。不需要考虑数组中超出新长度后面的元素。 6 | * Created by Myth-PC on 2019-10-07 7 | */ 8 | public class P26RemoveDuplicatesfromSortedArray { 9 | // 双指针,一个指针i指向非重复部分的尾部,一个指针遍历数组 10 | public int removeDuplicates(int[] nums) { 11 | int i = 0, j = 0; 12 | while (j < nums.length) { 13 | while (j+1 < nums.length && nums[j] == nums[j+1]) j++; 14 | nums[i++] = nums[j++]; 15 | } 16 | return i; 17 | } 18 | public int removeDuplicates2(int[] nums) { 19 | int i = 0, len = nums.length; 20 | for (int j = 0; j < len; j++) { 21 | if (j+1 < len && nums[j] != nums[j+1]) { 22 | nums[i++] = nums[j]; 23 | } 24 | } 25 | if (len > 0) nums[i++] = nums[len-1]; 26 | return i; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/twopointer/seq/P283MoveZeroes.java: -------------------------------------------------------------------------------- 1 | package twopointer.seq; 2 | 3 | /** 4 | * Title: 283. 移动零 5 | * Desc: 给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。 6 | * 必须在原数组上操作,不能拷贝额外的数组。 7 | * 尽量减少操作次数。 8 | * Created by Myth-Lab on 10/15/2019 9 | */ 10 | public class P283MoveZeroes { 11 | // 优秀代码 ========= 12 | private void swap(int[] nums, int i, int j) { 13 | int temp = nums[i]; 14 | nums[i] = nums[j]; 15 | nums[j] = temp; 16 | } 17 | public void moveZeroes3(int[] nums) { 18 | int zeroPos = 0; 19 | for (int i = 0; i < nums.length; i++) { 20 | if (nums[i] != 0) { 21 | swap(nums, zeroPos++, i); 22 | } 23 | } 24 | } 25 | 26 | 27 | // 我的代码,复杂!!!! 28 | public void moveZeroes(int[] nums) { 29 | int p = 0, q = 0; 30 | while(q < nums.length) { 31 | while(q < nums.length && nums[q] == 0 ) q++; 32 | if(p == q) { 33 | p++; 34 | q++; 35 | } else if(q < nums.length) { 36 | nums[p++] = nums[q]; 37 | nums[q++] = 0; 38 | } 39 | } 40 | } 41 | 42 | public void moveZeroes2(int[] nums) { 43 | int zeroPos = 0; 44 | for (int i = 0; i < nums.length; i++) { 45 | if (nums[i] != 0) { 46 | nums[zeroPos++] = nums[i]; 47 | } 48 | } 49 | for (int i = zeroPos; i < nums.length; i++) { 50 | if (nums[i] != 0) nums[i] = 0; 51 | } 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/twopointer/seq/P75SortColors.java: -------------------------------------------------------------------------------- 1 | package twopointer.seq; 2 | 3 | /** 4 | * Title: 75. 颜色分类(三色分类) 5 | * Desc: 给定一个包含红色、白色和蓝色,一共 n 个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。 6 | * 7 | * 此题中,我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。 8 | * !!!使用一趟扫描 9 | * Created by Myth-Lab on 10/13/2019 10 | */ 11 | public class P75SortColors { 12 | private void swap(int[] nums, int i, int j) { 13 | if (nums[i] == nums[j]) return; 14 | int temp = nums[i]; 15 | nums[i] = nums[j]; 16 | nums[j] = temp; 17 | } 18 | public void sortColors(int[] nums) { 19 | int p0 = 0, p2 = nums.length-1; 20 | int cur = 0; 21 | while (cur <= p2) { 22 | if (nums[cur] == 0) swap(nums, cur++, p0++); 23 | else if (nums[cur] == 2) swap(nums, cur, p2--); 24 | else cur++; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/twopointer/seq/P88MergeSortedArray.java: -------------------------------------------------------------------------------- 1 | package twopointer.seq; 2 | 3 | /** 4 | * Title: 88. 合并两个有序数组 5 | * Desc: 给定两个有序整数数组 nums1 和 nums2,将 nums2 合并到 nums1 中,使得 num1 成为一个有序数组。 6 | * 初始化 nums1 和 nums2 的元素数量分别为 m 和 n。 7 | * 你可以假设 nums1 有足够的空间(空间大小大于或等于 m + n)来保存 nums2 中的元素。 8 | * 9 | * Created by Myth-Lab on 10/14/2019 10 | */ 11 | public class P88MergeSortedArray { 12 | public void merge(int[] nums1, int m, int[] nums2, int n) { 13 | int i = m-1, j = n-1, k = m+n-1; 14 | while (i >= 0 && j >= 0) { 15 | if (nums1[i] < nums2[j]) { 16 | nums1[k--] = nums2[j--]; 17 | } else { 18 | nums1[k--] = nums1[i--]; 19 | } 20 | } 21 | while (j >= 0) { 22 | nums1[k--] = nums2[j--]; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/twopointer/slidewindow/P209MinimumSizeSubarraySum.java: -------------------------------------------------------------------------------- 1 | package twopointer.slidewindow; 2 | 3 | /** 4 | * Title: 209. 长度最小的子数组(双指针滑动窗口) 5 | * Desc: 给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的连续子数组。如果不存在符合条件的连续子数组,返回 0。 6 | * Created by Myth on 01/13/2020 in VSCode 7 | */ 8 | 9 | public class P209MinimumSizeSubarraySum { 10 | public int minSubArrayLen(int s, int[] nums) { 11 | int n = nums.length, res = n + 1; 12 | int i = 0, j; 13 | for (j = 0; j < n; j++) { 14 | s -= nums[j]; 15 | while (s <= 0) { 16 | res = Math.min(res, j-i+1); 17 | s += nums[i++]; 18 | } 19 | } 20 | return res % (n + 1); // res == n+1 说明不存在,返回0 21 | } 22 | } -------------------------------------------------------------------------------- /src/twopointer/slidewindow/PP1052GrumpyBookstoreOwner.java: -------------------------------------------------------------------------------- 1 | package twopointer.slidewindow; 2 | 3 | 4 | /** 5 | * Title: 1052. 爱生气的书店老板 6 | * Desc: 书店老板有一家店打算试营业 customers.length 分钟。每分钟都有一些顾客(customers[i])会进入书店,所有这些顾客都会在那一分钟结束后离开。 7 | * 在某些时候,书店老板会生气。 如果书店老板在第 i 分钟生气,那么 grumpy[i] = 1,否则 grumpy[i] = 0。 8 | * 当书店老板生气时,那一分钟的顾客就会不满意,不生气则他们是满意的。 9 | * 书店老板知道一个秘密技巧,能抑制自己的情绪,可以让自己连续 X 分钟不生气,但却只能使用一次。 10 | * 请你返回这一天营业下来,最多有多少客户能够感到满意的数量。 11 | https://leetcode-cn.com/problems/grumpy-bookstore-owner/ 12 | * Created by Myth-MBP on 30/05/2020 in VSCode 13 | */ 14 | public class PP1052GrumpyBookstoreOwner { 15 | // TODO 16 | public int maxSatisfied(int[] customers, int[] grumpy, int X) { 17 | return -1; 18 | } 19 | } -------------------------------------------------------------------------------- /src/util/ListNode.java: -------------------------------------------------------------------------------- 1 | package util; 2 | 3 | /** 4 | * Title: Singly-Linked List 5 | * Desc: Definition for singly-linked list. 6 | * Created by Myth on 5/12/2019 7 | */ 8 | public class ListNode { 9 | public int val; 10 | public ListNode next; 11 | public ListNode(int x) { 12 | val = x; 13 | } 14 | public ListNode(int x, ListNode nextNode) { 15 | val = x; 16 | next = nextNode; 17 | } 18 | } -------------------------------------------------------------------------------- /src/util/TreeNode.java: -------------------------------------------------------------------------------- 1 | package util; 2 | 3 | /** 4 | * Title: 5 | * Desc: 6 | * Created by Myth on 5/12/2019 7 | */ 8 | public class TreeNode { 9 | public int val; 10 | public TreeNode left; 11 | public TreeNode right; 12 | public TreeNode(int x) { 13 | val = x; 14 | } 15 | public TreeNode(int x, TreeNode left, TreeNode right) { 16 | val = x; 17 | this.left = left; 18 | this.right = right; 19 | } 20 | } --------------------------------------------------------------------------------