From 5dce9076cd26555c5f3713a1b5db53e7571dfae6 Mon Sep 17 00:00:00 2001 From: algorithmzuo Date: Wed, 27 Jul 2022 12:47:27 +0800 Subject: [PATCH] first commit --- .../class01/Code01_SelectionSort.java | 115 ++++ .../class01/Code02_BubbleSort.java | 110 ++++ .../class01/Code03_InsertionSort.java | 116 ++++ 体系学习班/class01/Code04_BSExist.java | 65 +++ .../class01/Code05_BSNearLeft.java | 75 +++ .../class01/Code05_BSNearRight.java | 75 +++ 体系学习班/class01/Code06_BSAwesome.java | 76 +++ 体系学习班/class02/Code01_Swap.java | 71 +++ .../class02/Code02_EvenTimesOddTimes.java | 83 +++ 体系学习班/class02/Code03_KM.java | 159 ++++++ .../class03/Code01_ReverseList.java | 221 ++++++++ .../class03/Code02_DeleteGivenValue.java | 38 ++ ...Code03_DoubleEndsQueueToStackAndQueue.java | 183 +++++++ 体系学习班/class03/Code04_RingArray.java | 50 ++ .../class03/Code05_GetMinStack.java | 105 ++++ .../Code06_TwoStacksImplementQueue.java | 60 ++ .../Code07_TwoQueueImplementStack.java | 90 +++ 体系学习班/class03/Code08_GetMax.java | 24 + .../class03/HashMapAndSortedMap.java | 128 +++++ 体系学习班/class04/Code01_MergeSort.java | 147 +++++ 体系学习班/class04/Code02_SmallSum.java | 137 +++++ .../class04/Code03_ReversePair.java | 130 +++++ .../class04/Code04_BiggerThanRightTwice.java | 135 +++++ .../class05/Code01_CountOfRangeSum.java | 63 +++ .../class05/Code02_PartitionAndQuickSort.java | 197 +++++++ ...de03_QuickSortRecursiveAndUnrecursive.java | 195 +++++++ .../Code04_DoubleLinkedListQuickSort.java | 300 ++++++++++ .../class06/Code01_Comparator.java | 168 ++++++ 体系学习班/class06/Code02_Heap.java | 192 +++++++ 体系学习班/class06/Code03_HeapSort.java | 158 ++++++ .../Code04_SortArrayDistanceLessK.java | 127 +++++ 体系学习班/class07/Code01_CoverMax.java | 155 ++++++ .../class07/Code02_EveryStepShowBoss.java | 303 +++++++++++ 体系学习班/class07/HeapGreater.java | 112 ++++ 体系学习班/class07/Inner.java | 9 + 体系学习班/class08/Code01_TrieTree.java | 299 ++++++++++ 体系学习班/class08/Code02_TrieTree.java | 306 +++++++++++ 体系学习班/class08/Code03_CountSort.java | 111 ++++ 体系学习班/class08/Code04_RadixSort.java | 148 +++++ .../class09/Code01_LinkedListMid.java | 162 ++++++ .../class09/Code02_IsPalindromeList.java | 204 +++++++ .../class09/Code03_SmallerEqualBigger.java | 138 +++++ .../class09/Code04_CopyListWithRandom.java | 79 +++ .../Code01_FindFirstIntersectNode.java | 170 ++++++ .../class10/Code02_RecursiveTraversalBT.java | 72 +++ .../Code03_UnRecursiveTraversalBT.java | 118 ++++ .../class11/Code01_LevelTraversalBT.java | 49 ++ .../Code02_SerializeAndReconstructTree.java | 264 +++++++++ .../Code03_EncodeNaryTreeToBinaryTree.java | 86 +++ .../class11/Code04_PrintBinaryTree.java | 74 +++ .../class11/Code05_TreeMaxWidth.java | 114 ++++ .../class11/Code06_SuccessorNode.java | 86 +++ .../class11/Code07_PaperFolding.java | 28 + 体系学习班/class12/Code01_IsCBT.java | 149 +++++ 体系学习班/class12/Code02_IsBST.java | 125 +++++ .../class12/Code03_IsBalanced.java | 102 ++++ 体系学习班/class12/Code04_IsFull.java | 109 ++++ .../class12/Code05_MaxSubBSTSize.java | 157 ++++++ .../class12/Code06_MaxDistance.java | 180 ++++++ 体系学习班/class13/Code01_IsCBT.java | 120 ++++ .../class13/Code02_MaxSubBSTHead.java | 136 +++++ .../class13/Code03_lowestAncestor.java | 141 +++++ 体系学习班/class13/Code04_MaxHappy.java | 116 ++++ .../class13/Code05_LowestLexicography.java | 116 ++++ 体系学习班/class14/Code01_Light.java | 105 ++++ .../class14/Code02_LessMoneySplitGold.java | 79 +++ .../class14/Code03_BestArrange.java | 111 ++++ 体系学习班/class14/Code04_IPO.java | 58 ++ 体系学习班/class14/Code05_UnionFind.java | 70 +++ .../class15/Code01_FriendCircles.java | 78 +++ .../class15/Code02_NumberOfIslands.java | 317 +++++++++++ .../class15/Code03_NumberOfIslandsII.java | 165 ++++++ 体系学习班/class16/Code01_BFS.java | 30 + 体系学习班/class16/Code02_DFS.java | 34 ++ .../class16/Code03_TopologicalOrderBFS.java | 53 ++ .../class16/Code03_TopologicalOrderDFS1.java | 70 +++ .../class16/Code03_TopologicalOrderDFS2.java | 76 +++ .../class16/Code03_TopologySort.java | 36 ++ 体系学习班/class16/Code04_Kruskal.java | 101 ++++ 体系学习班/class16/Code05_Prim.java | 94 ++++ 体系学习班/class16/Code06_Dijkstra.java | 160 ++++++ .../class16/Code06_NetworkDelayTime.java | 151 ++++++ 体系学习班/class16/Edge.java | 14 + 体系学习班/class16/Graph.java | 14 + 体系学习班/class16/GraphGenerator.java | 37 ++ 体系学习班/class16/Node.java | 20 + 体系学习班/class17/Code01_Dijkstra.java | 162 ++++++ 体系学习班/class17/Code02_Hanoi.java | 139 +++++ .../class17/Code03_PrintAllSubsquences.java | 74 +++ .../class17/Code04_PrintAllPermutations.java | 110 ++++ .../Code05_ReverseStackUsingRecursive.java | 44 ++ 体系学习班/class17/Edge.java | 14 + 体系学习班/class17/Graph.java | 14 + 体系学习班/class17/Node.java | 20 + 体系学习班/class18/Code01_RobotWalk.java | 94 ++++ .../class18/Code02_CardsInLine.java | 116 ++++ 体系学习班/class19/Code01_Knapsack.java | 63 +++ .../class19/Code02_ConvertToLetterString.java | 123 +++++ .../class19/Code03_StickersToSpellWord.java | 151 ++++++ .../Code04_LongestCommonSubsequence.java | 124 +++++ .../class20/Code01_PalindromeSubsequence.java | 121 +++++ 体系学习班/class20/Code02_HorseJump.java | 109 ++++ 体系学习班/class20/Code03_Coffee.java | 204 +++++++ .../class21/Code01_MinPathSum.java | 79 +++ .../Code02_CoinsWayEveryPaperDifferent.java | 77 +++ .../class21/Code03_CoinsWayNoLimit.java | 108 ++++ .../Code04_CoinsWaySameValueSamePapper.java | 148 +++++ 体系学习班/class21/Code05_BobDie.java | 58 ++ .../class22/Code01_KillMonster.java | 100 ++++ .../class22/Code02_MinCoinsNoLimit.java | 121 +++++ .../class22/Code03_SplitNumber.java | 85 +++ .../class23/Code01_SplitSumClosed.java | 94 ++++ .../Code02_SplitSumClosedSizeHalf.java | 243 +++++++++ 体系学习班/class23/Code03_NQueens.java | 89 +++ .../class24/Code01_SlidingWindowMaxArray.java | 99 ++++ .../class24/Code02_AllLessNumSubArray.java | 109 ++++ .../class24/Code03_GasStation.java | 53 ++ .../class24/Code04_MinCoinsOnePaper.java | 250 +++++++++ .../class25/Code01_MonotonousStack.java | 165 ++++++ .../Code01_MonotonousStackForNowcoder.java | 71 +++ .../class25/Code02_AllTimesMinToMax.java | 98 ++++ .../Code03_LargestRectangleInHistogram.java | 58 ++ .../class25/Code04_MaximalRectangle.java | 48 ++ .../Code05_CountSubmatricesWithAllOnes.java | 81 +++ .../class26/Code01_SumOfSubarrayMinimums.java | 156 ++++++ .../class26/Code02_FibonacciProblem.java | 189 +++++++ .../Code03_ZeroLeftOneStringNumber.java | 113 ++++ 体系学习班/class27/Code01_KMP.java | 75 +++ 体系学习班/class27/Code02_TreeEqual.java | 172 ++++++ .../class27/Code03_IsRotation.java | 64 +++ 体系学习班/class28/Code01_Manacher.java | 90 +++ .../class28/Code02_AddShortestEnd.java | 54 ++ .../class29/Code01_FindMinKth.java | 175 ++++++ 体系学习班/class29/Code02_MaxTopK.java | 223 ++++++++ .../class29/Code03_ReservoirSampling.java | 94 ++++ .../class30/Code01_MorrisTraversal.java | 230 ++++++++ 体系学习班/class30/Code02_MinDepth.java | 88 +++ .../class31/Code01_SegmentTree.java | 245 +++++++++ .../class31/Code02_FallingSquares.java | 109 ++++ 体系学习班/class32/Code01_IndexTree.java | 83 +++ .../class32/Code02_IndexTree2D.java | 57 ++ 体系学习班/class32/Code03_AC1.java | 105 ++++ 体系学习班/class32/Code04_AC2.java | 125 +++++ 体系学习班/class33/Hash.java | 49 ++ 体系学习班/class34/ReadMe.java | 2 + .../class35/Code01_AVLTreeMap.java | 257 +++++++++ .../class36/Code01_SizeBalancedTreeMap.java | 360 ++++++++++++ .../class36/Code02_SkipListMap.java | 248 +++++++++ .../class37/Code01_CountofRangeSum.java | 223 ++++++++ .../class37/Code02_SlidingWindowMedian.java | 228 ++++++++ .../Code03_AddRemoveGetIndexGreat.java | 259 +++++++++ .../Code04_QueueReconstructionByHeight.java | 265 +++++++++ 体系学习班/class37/Compare.java | 408 ++++++++++++++ .../class38/Code01_AppleMinBags.java | 41 ++ 体系学习班/class38/Code02_EatGrass.java | 57 ++ 体系学习班/class38/Code03_MSumToN.java | 44 ++ .../class38/Code04_MoneyProblem.java | 169 ++++++ .../class39/Code01_SubsquenceMaxModM.java | 141 +++++ .../class39/Code02_SnacksWays.java | 79 +++ .../class39/Code02_SnacksWaysMain1.java | 126 +++++ .../class39/Code02_SnacksWaysMain2.java | 135 +++++ 体系学习班/class39/Code03_10Ways.java | 92 ++++ .../class39/Code04_DifferentBTNum.java | 66 +++ 体系学习班/class39/IsSum.java | 180 ++++++ ...ngestSumSubArrayLengthInPositiveArray.java | 91 ++++ .../Code02_LongestSumSubArrayLength.java | 92 ++++ .../Code03_LongestLessSumSubArrayLength.java | 103 ++++ ...de04_AvgLessEqualValueLongestSubarray.java | 152 ++++++ .../Code05_PrintMatrixSpiralOrder.java | 53 ++ .../class40/Code06_RotateMatrix.java | 44 ++ .../class40/Code07_ZigZagPrintMatrix.java | 42 ++ 体系学习班/class40/Code08_PrintStar.java | 46 ++ .../class41/Code01_BestSplitForAll.java | 72 +++ .../Code02_BestSplitForEveryPosition.java | 130 +++++ .../class41/Code03_StoneMerge.java | 117 ++++ .../class41/Code04_SplitArrayLargestSum.java | 193 +++++++ .../class42/Code01_PostOfficeProblem.java | 115 ++++ .../Code02_ThrowChessPiecesProblem.java | 166 ++++++ 体系学习班/class43/Code01_CanIWin.java | 116 ++++ 体系学习班/class43/Code02_TSP.java | 295 ++++++++++ .../class43/Code03_PavingTile.java | 215 ++++++++ ...1_LastSubstringInLexicographicalOrder.java | 136 +++++ 体系学习班/class44/DC3.java | 168 ++++++ 体系学习班/class44/DC3_Algorithm.pdf | Bin 0 -> 192716 bytes ...e01_InsertS2MakeMostAlphabeticalOrder.java | 261 +++++++++ .../class45/Code02_CreateMaximumNumber.java | 250 +++++++++ ...LongestCommonSubstringConquerByHeight.java | 286 ++++++++++ .../Code04_LongestRepeatingSubstring.java | 196 +++++++ .../class46/Code01_BurstBalloons.java | 107 ++++ .../class46/Code02_RemoveBoxes.java | 81 +++ .../Code03_DeleteAdjacentSameCharacter.java | 176 ++++++ .../class46/Code04_MaxSumLengthNoMore.java | 100 ++++ .../class46/Code05_HuffmanTree.java | 214 ++++++++ .../class47/Code01_StrangePrinter.java | 78 +++ .../class47/Code02_RestoreWays.java | 246 +++++++++ .../class47/Code03_DinicAlgorithm.java | 139 +++++ .../class01/Code01_CordCoverMaxPoint.java | 87 +++ .../class01/Code02_CountFiles.java | 40 ++ .../class01/Code03_Near2Power.java | 22 + .../class01/Code04_MinSwapStep.java | 74 +++ .../class01/Code05_LongestIncreasingPath.java | 54 ++ 大厂刷题班/class01/Code06_AOE.java | 304 +++++++++++ 大厂刷题班/class01/Code07_TargetSum.java | 127 +++++ .../class02/Code01_ChooseWork.java | 48 ++ 大厂刷题班/class02/Code02_Cola.java | 151 ++++++ .../Code03_ReceiveAndPrintOrderLine.java | 89 +++ 大厂刷题班/class02/Code04_Drive.java | 116 ++++ 大厂刷题班/class02/Code05_SetAll.java | 48 ++ .../class02/Code06_MinLengthForSort.java | 30 + ...stSubstringWithoutRepeatingCharacters.java | 27 + .../class03/Code02_HowManyTypes.java | 82 +++ .../Code03_Largest1BorderedSquare.java | 59 ++ .../class03/Code04_MaxPairNumber.java | 126 +++++ .../class03/Code05_BoatsToSavePeople.java | 74 +++ .../class03/Code06_ClosestSubsequenceSum.java | 46 ++ .../class03/Code07_FreedomTrail.java | 62 +++ .../class03/Code08_DistanceKNodes.java | 105 ++++ .../class04/Code01_QueryHobby.java | 115 ++++ .../class04/Code02_SubArrayMaxSum.java | 35 ++ .../class04/Code03_SubMatrixMaxSum.java | 75 +++ .../Code04_SubArrayMaxSumFollowUp.java | 59 ++ .../class04/Code05_CandyProblem.java | 145 +++++ 大厂刷题班/class04/Code06_MakeNo.java | 59 ++ .../class04/Code07_InterleavingString.java | 53 ++ .../class04/Code08_TheSkylineProblem.java | 73 +++ ...BinarySearchTreeFromPreorderTraversal.java | 115 ++++ .../Code02_LeftRightSameTreeNumber.java | 95 ++++ 大厂刷题班/class05/Code03_EditCost.java | 89 +++ .../class05/Code04_DeleteMinCost.java | 258 +++++++++ 大厂刷题班/class05/Hash.java | 49 ++ 大厂刷题班/class06/Code01_MaxXOR.java | 130 +++++ ...ode02_MaximumXorOfTwoNumbersInAnArray.java | 50 ++ ...de03_MaximumXorWithAnElementFromArray.java | 67 +++ .../class06/Code04_MostXorZero.java | 122 +++++ 大厂刷题班/class06/Code05_Nim.java | 18 + .../class07/Code01_MaxAndValue.java | 102 ++++ .../class07/Code02_MinCameraCover.java | 113 ++++ 大厂刷题班/class07/Code03_MaxGap.java | 46 ++ .../class07/Code04_Power2Diffs.java | 97 ++++ .../class07/Code05_WorldBreak.java | 237 ++++++++ .../class07/Code06_SplitStringMaxValue.java | 137 +++++ .../class08/Code01_ExpressionCompute.java | 71 +++ .../Code02_ContainerWithMostWater.java | 32 ++ .../class08/Code03_FindWordInMatrix.java | 159 ++++++ 大厂刷题班/class08/Code04_SnakeGame.java | 188 +++++++ .../class09/Code01_LightProblem.java | 384 +++++++++++++ .../Code02_RemoveInvalidParentheses.java | 64 +++ 大厂刷题班/class09/Code03_LIS.java | 55 ++ .../class09/Code04_EnvelopesProblem.java | 60 ++ 大厂刷题班/class09/Code05_IsStepSum.java | 58 ++ 大厂刷题班/class10/Code01_JumpGame.java | 23 + 大厂刷题班/class10/Code02_TopK.java | 154 ++++++ .../class10/Code03_KInversePairs.java | 43 ++ .../class10/Code04_BSTtoDoubleLinkedList.java | 57 ++ .../class10/Code05_BooleanEvaluation.java | 156 ++++++ ...InsertionStepsToMakeAStringPalindrome.java | 173 ++++++ .../Code02_PalindromePartitioningII.java | 207 +++++++ .../class12/Code01_ContainAllCharExactly.java | 125 +++++ .../class12/Code03_FindKthMinNumber.java | 103 ++++ .../class12/Code03_LongestConsecutive.java | 65 +++ .../Code04_RegularExpressionMatch.java | 135 +++++ .../class13/Code01_NCardsABWin.java | 169 ++++++ .../class13/Code02_SuperWashingMachines.java | 34 ++ .../class13/Code03_ScrambleString.java | 186 +++++++ .../class13/Code04_BricksFallingWhenHit.java | 149 +++++ .../class14/Code01_Parentheses.java | 97 ++++ .../Code02_MaxSubArraySumLessOrEqualK.java | 28 + .../Code03_BiggestBSTTopologyInTree.java | 65 +++ .../Code04_CompleteTreeNodeNumber.java | 45 ++ .../Code05_RecoverBinarySearchTree.java | 511 ++++++++++++++++++ .../class14/Code06_MissingNumber.java | 29 + .../Code01_BestTimeToBuyAndSellStock.java | 21 + .../Code02_BestTimeToBuyAndSellStockII.java | 17 + .../Code03_BestTimeToBuyAndSellStockIII.java | 23 + .../Code04_BestTimeToBuyAndSellStockIV.java | 65 +++ ...BestTimeToBuyAndSellStockWithCooldown.java | 101 ++++ ...meToBuyAndSellStockWithTransactionFee.java | 27 + 大厂刷题班/class16/Code01_IsSum.java | 206 +++++++ .../class16/Code02_SmallestUnFormedSum.java | 123 +++++ .../class16/Code03_MinPatches.java | 82 +++ .../class16/Code04_MergeRecord.java | 220 ++++++++ .../class16/Code05_JosephusProblem.java | 120 ++++ .../class17/Code01_FindNumInSortedMatrix.java | 34 ++ ...de02_KthSmallestElementInSortedMatrix.java | 110 ++++ .../class17/Code03_PalindromePairs.java | 104 ++++ .../class17/Code04_DistinctSubseq.java | 101 ++++ .../class17/Code05_DistinctSubseqValue.java | 50 ++ .../class18/Code01_HanoiProblem.java | 97 ++++ .../class18/Code02_ShortestBridge.java | 96 ++++ .../class18/Code03_CherryPickup.java | 70 +++ .../class18/Code04_TopKSumCrossTwoArrays.java | 93 ++++ 大厂刷题班/class19/Code01_LRUCache.java | 142 +++++ 大厂刷题班/class19/Code02_LFUCache.java | 204 +++++++ 大厂刷题班/class19/Code03_OneNumber.java | 83 +++ ...allestRangeCoveringElementsfromKLists.java | 58 ++ .../class19/Code05_CardsProblem.java | 166 ++++++ .../Code01_PreAndInArrayToPosArray.java | 233 ++++++++ ...02_LargestComponentSizebyCommonFactor.java | 109 ++++ .../class20/Code03_ShuffleProblem.java | 154 ++++++ .../class20/Code04_PalindromeWays.java | 91 ++++ .../class21/TreeChainPartition.java | 416 ++++++++++++++ ..._MaximumSumof3NonOverlappingSubarrays.java | 102 ++++ .../class22/Code02_TrappingRainWater.java | 28 + .../class22/Code03_TrappingRainWaterII.java | 76 +++ .../class22/Code04_VisibleMountains.java | 194 +++++++ .../class22/Code05_TallestBillboard.java | 38 ++ ...Code01_LCATarjanAndTreeChainPartition.java | 352 ++++++++++++ .../Code02_MaxABSBetweenLeftAndRight.java | 63 +++ .../Code03_LongestIntegratedLength.java | 106 ++++ .../class23/Code04_FindKMajority.java | 113 ++++ .../Code05_MinimumCostToMergeStones.java | 159 ++++++ .../class24/Code01_Split4Parts.java | 89 +++ .../class24/Code02_KthMinPair.java | 180 ++++++ .../class24/Code03_NotContains4.java | 144 +++++ 大厂刷题班/class24/Code04_Painting.java | 75 +++ .../class24/Code05_MinWindowLength.java | 80 +++ ...Code06_RemoveDuplicateLettersLessLexi.java | 74 +++ 大厂刷题班/class25/Code01_IPToCIDR.java | 90 +++ 大厂刷题班/class25/Code02_3Sum.java | 70 +++ .../class25/Code03_MaxPointsOnALine.java | 68 +++ .../class25/Code04_GasStation.java | 104 ++++ 大厂刷题班/class26/Code01_MinRange.java | 119 ++++ .../class26/Code02_WordSearchII.java | 122 +++++ .../Code03_ExpressionAddOperators.java | 65 +++ .../class26/Code04_WordLadderII.java | 100 ++++ 大厂刷题班/class27/Code01_PickBands.java | 221 ++++++++ 大厂刷题班/class27/Code02_MinPeople.java | 61 +++ .../class27/Problem_0001_TwoSum.java | 19 + .../class27/Problem_0007_ReverseInteger.java | 21 + 大厂刷题班/class27/说明 | 12 + .../class28/Problem_0008_StringToInteger.java | 80 +++ .../class28/Problem_0012_IntegerToRoman.java | 20 + .../class28/Problem_0013_RomanToInteger.java | 45 ++ .../Problem_0014_LongestCommonPrefix.java | 28 + ...0017_LetterCombinationsOfAPhoneNumber.java | 43 ++ ...oblem_0019_RemoveNthNodeFromEndofList.java | 33 ++ .../Problem_0020_ValidParentheses.java | 30 + .../Problem_0022_GenerateParentheses.java | 71 +++ ..._0026_RemoveDuplicatesFromSortedArray.java | 21 + ...AndLastPositionOfElementInSortedArray.java | 33 ++ .../class28/Problem_0036_ValidSudoku.java | 26 + .../class28/Problem_0037_SudokuSolver.java | 61 +++ .../class28/Problem_0038_CountAndSay.java | 33 ++ .../class28/Problem_0049_GroupAnagrams.java | 52 ++ 大厂刷题班/class28/说明 | 33 ++ ...oblem_0033_SearchInRotatedSortedArray.java | 67 +++ .../class29/Problem_0050_PowXN.java | 39 ++ .../class29/Problem_0056_MergeIntervals.java | 30 + .../class29/Problem_0062_UniquePaths.java | 30 + .../class29/Problem_0066_PlusOne.java | 19 + .../class29/Problem_0069_SqrtX.java | 30 + .../class29/Problem_0073_SetMatrixZeroes.java | 79 +++ 大厂刷题班/class29/说明 | 20 + .../class30/Problem_0079_WordSearch.java | 41 ++ .../Problem_0088_MergeSortedArray.java | 22 + .../class30/Problem_0091_DecodeWays.java | 87 +++ ...Problem_0098_ValidateBinarySearchTree.java | 42 ++ .../class30/Problem_0101_SymmetricTree.java | 30 + ...3_BinaryTreeZigzagLevelOrderTraversal.java | 52 ++ ..._ConvertSortedArrayToBinarySearchTree.java | 33 ++ ...PopulatingNextRightPointersInEachNode.java | 77 +++ .../class30/Problem_0118_PascalTriangle.java | 23 + .../Problem_0119_PascalTriangleII.java | 19 + ...Problem_0124_BinaryTreeMaximumPathSum.java | 206 +++++++ .../class30/Problem_0639_DecodeWaysII.java | 161 ++++++ 大厂刷题班/class30/说明 | 26 + .../class31/Problem_0125_ValidPalindrome.java | 52 ++ .../class31/Problem_0127_WordLadder.java | 124 +++++ .../Problem_0130_SurroundedRegions.java | 115 ++++ .../class31/Problem_0139_WordBreak.java | 97 ++++ .../class31/Problem_0140_WordBreakII.java | 104 ++++ .../class31/Problem_0148_SortList.java | 227 ++++++++ ...em_0150_EvaluateReversePolishNotation.java | 40 ++ 大厂刷题班/class31/说明 | 21 + .../Problem_0152_MaximumProductSubarray.java | 45 ++ .../class32/Problem_0163_MissingRanges.java | 35 ++ ...oblem_0166_FractionToRecurringDecimal.java | 46 ++ .../Problem_0171_ExcelSheetColumnNumber.java | 15 + .../Problem_0172_FactorialTrailingZeroes.java | 14 + .../class32/Problem_0189_RotateArray.java | 60 ++ .../class32/Problem_0190_ReverseBits.java | 48 ++ .../class32/Problem_0191_NumberOf1Bits.java | 26 + .../class32/Problem_0202_HappyNumber.java | 59 ++ .../class32/Problem_0204_CountPrimes.java | 30 + 大厂刷题班/class32/SequenceM.java | 162 ++++++ 大厂刷题班/class32/说明 | 25 + .../class33/Problem_0207_CourseSchedule.java | 113 ++++ .../Problem_0210_CourseScheduleII.java | 71 +++ .../class33/Problem_0213_HouseRobberII.java | 50 ++ .../Problem_0237_DeleteNodeInLinkedList.java | 15 + ...Problem_0238_ProductOfArrayExceptSelf.java | 24 + .../class33/Problem_0242_ValidAnagram.java | 23 + .../class33/Problem_0251_Flatten2DVector.java | 53 ++ .../class33/Problem_0269_AlienDictionary.java | 65 +++ .../Problem_0277_FindTheCelebrity.java | 38 ++ .../class33/Problem_0279_PerfectSquares.java | 70 +++ .../class33/Problem_0283_MoveZeroes.java | 20 + 大厂刷题班/class33/说明 | 32 ++ .../Problem_0287_FindTheDuplicateNumber.java | 23 + .../class34/Problem_0289_GameOfLife.java | 43 ++ ...Problem_0295_FindMedianFromDataStream.java | 46 ++ ...m_0315_CountOfSmallerNumbersAfterSelf.java | 69 +++ .../class34/Problem_0324_WiggleSortII.java | 186 +++++++ .../class34/Problem_0326_PowerOfThree.java | 14 + .../Problem_0328_OddEvenLinkedList.java | 43 ++ ...ubstringWithAtMostKDistinctCharacters.java | 29 + ...roblem_0341_FlattenNestedListIterator.java | 110 ++++ .../class34/Problem_0348_DesignTicTacToe.java | 47 ++ .../Problem_0380_InsertDeleteGetRandom.java | 52 ++ .../class34/Problem_0384_ShuffleAnArray.java | 34 ++ 大厂刷题班/class34/说明 | 30 + 大厂刷题班/class35/Code01_StringKth.java | 96 ++++ .../class35/Code02_MagicStone.java | 49 ++ .../class35/Code03_WatchMovieMaxTime.java | 136 +++++ 大厂刷题班/class35/Code04_WalkToEnd.java | 53 ++ .../class35/Code05_CircleCandy.java | 58 ++ .../Problem_0347_TopKFrequentElements.java | 54 ++ ...stringWithAtLeastKRepeatingCharacters.java | 110 ++++ .../class35/Problem_0412_FizzBuzz.java | 24 + .../class35/Problem_0454_4SumII.java | 32 ++ ..._NumberOfLongestIncreasingSubsequence.java | 90 +++ .../Problem_0687_LongestUnivaluePath.java | 62 +++ 大厂刷题班/class35/说明 | 21 + .../class36/Code01_ReverseInvertString.java | 108 ++++ .../class36/Code02_Ratio01Split.java | 68 +++ .../class36/Code03_MatchCount.java | 66 +++ .../Code04_ComputeExpressionValue.java | 58 ++ .../class36/Code05_Query3Problems.java | 132 +++++ .../class36/Code06_NodeWeight.java | 48 ++ .../class36/Code07_PickAddMax.java | 80 +++ .../class36/Code08_MinBoatEvenNumbers.java | 98 ++++ .../class36/Code09_MaxKLenSequence.java | 87 +++ .../class36/Code10_StoneGameIV.java | 63 +++ 大厂刷题班/class36/Code11_BusRoutes.java | 59 ++ 大厂刷题班/class36/说明 | 12 + .../class37/Code01_ArrangeProject.java | 50 ++ .../class37/Code02_GameForEveryStepWin.java | 111 ++++ ...em_0114_FlattenBinaryTreeToLinkedList.java | 93 ++++ .../class37/Problem_0221_MaximalSquare.java | 40 ++ .../Problem_0226_InvertBinaryTree.java | 21 + .../class37/Problem_0337_HouseRobberIII.java | 37 ++ .../class37/Problem_0394_DecodeString.java | 50 ++ ...blem_0406_QueueReconstructionByHeight.java | 264 +++++++++ .../class37/Problem_0437_PathSumIII.java | 44 ++ 大厂刷题班/class37/说明 | 30 + .../class38/Code01_FillGapMinStep.java | 103 ++++ 大厂刷题班/class38/Code02_GreatWall.java | 183 +++++++ ...Problem_0438_FindAllAnagramsInAString.java | 58 ++ ...48_FindAllNumbersDisappearedInAnArray.java | 42 ++ .../Problem_0617_MergeTwoBinaryTrees.java | 31 ++ .../class38/Problem_0621_TaskScheduler.java | 33 ++ .../Problem_0647_PalindromicSubstrings.java | 49 ++ .../Problem_0739_DailyTemperatures.java | 34 ++ .../class38/Problem_0763_PartitionLabels.java | 28 + 大厂刷题班/class38/说明 | 25 + .../class39/Code01_01AddValue.java | 46 ++ .../class39/Code02_ValidSequence.java | 113 ++++ .../Code03_SequenceKDifferentKinds.java | 63 +++ .../class39/Code04_JumpGameOnMatrix.java | 239 ++++++++ .../class39/Code05_0123Disappear.java | 82 +++ 大厂刷题班/class40/Code01_SplitTo01.java | 195 +++++++ 大厂刷题班/class40/Code02_Mod3Max.java | 247 +++++++++ .../class40/Code03_MaxMeetingScore.java | 109 ++++ .../class40/Code04_LetASorted.java | 54 ++ 大厂刷题班/class40/Code05_AllSame.java | 135 +++++ .../class41/Code01_MinSwapTimes.java | 88 +++ .../class41/Code02_PoemProblem.java | 381 +++++++++++++ .../class41/Code03_MagicGoToAim.java | 71 +++ .../class41/Problem_0031_NextPermutation.java | 44 ++ .../class42/Problem_0265_PaintHouseII.java | 56 ++ ...m_0272_ClosestBinarySearchTreeValueII.java | 109 ++++ .../Problem_0273_IntegerToEnglishWords.java | 78 +++ .../Problem_0296_BestMeetingPoint.java | 48 ++ .../class42/Problem_0335_SelfCrossing.java | 28 + .../class43/Code01_SumNoPositiveMinCost.java | 197 +++++++ .../class43/Code02_MinCostToYeahArray.java | 304 +++++++++++ ...Problem_0248_StrobogrammaticNumberIII.java | 216 ++++++++ ...0317_ShortestDistanceFromAllBuildings.java | 237 ++++++++ ..._0992_SubarraysWithKDifferentIntegers.java | 71 +++ .../class45/Code01_SplitBuildingBlock.java | 87 +++ .../class45/Problem_0291_WordPatternII.java | 62 +++ .../class45/Problem_0403_FrogJump.java | 40 ++ ...yIntoTwoArraysToMinimizeSumDifference.java | 58 ++ ...m_0363_MaxSumOfRectangleNoLargerThanK.java | 77 +++ .../Problem_0391_PerfectRectangle.java | 59 ++ ...em_0411_MinimumUniqueWordAbbreviation.java | 174 ++++++ .../class46/Problem_0425_WordSquares.java | 87 +++ .../class47/Code01_DynamicSegmentTree.java | 135 +++++ .../class47/Code02_DynamicSegmentTree.java | 198 +++++++ ...m_0315_CountOfSmallerNumbersAfterSelf.java | 94 ++++ ...em_0358_RearrangeStringKDistanceApart.java | 64 +++ ..._0428_SerializeAndDeserializeNaryTree.java | 103 ++++ .../Problem_0465_OptimalAccountBalancing.java | 149 +++++ .../class47/Problem_0475_Heaters.java | 113 ++++ .../class48/Code01_MinKthPairMinusABS.java | 122 +++++ .../Problem_0472_ConcatenatedWords.java | 124 +++++ .../Problem_0483_SmallestGoodBase.java | 33 ++ .../class48/Problem_0499_TheMazeIII.java | 87 +++ .../Problem_0377_CombinationSumIV.java | 64 +++ ...440_KthSmallestInLexicographicalOrder.java | 88 +++ ...em_0446_ArithmeticSlicesIISubsequence.java | 30 + .../Problem_0489_RobotRoomCleaner.java | 57 ++ .../Problem_0527_WordAbbreviation.java | 48 ++ .../Problem_0548_SplitArrayEithEqualSum.java | 45 ++ ...Problem_0564_FindTheClosestPalindrome.java | 72 +++ .../Problem_0568_MaximumVacationDays.java | 50 ++ .../class50/Problem_0587_ErectTheFence.java | 55 ++ ...Problem_0588_DesignInMemoryFileSystem.java | 107 ++++ ...egativeIntegersWithoutConsecutiveOnes.java | 80 +++ 大厂刷题班/class51/LCP_0003_Robot.java | 144 +++++ .../Problem_0630_CourseScheduleIII.java | 33 ++ ...m_0642_DesignSearchAutocompleteSystem.java | 134 +++++ .../Problem_0875_KokoEatingBananas.java | 34 ++ .../class51/Problem_1035_UncrossedLines.java | 86 +++ .../class52/Problem_0656_CoinPath.java | 67 +++ .../class52/Problem_0683_KEmptySlots.java | 75 +++ .../Problem_1488_AvoidFloodInTheCity.java | 81 +++ .../Code01_RetainTree.java | 89 +++ .../Code02_GuessNumberHigherOrLowerII.java | 107 ++++ .../Code03_StartToEndBinaryOneTarget.java | 337 ++++++++++++ .../Code01_XtoYMinDistance.java | 171 ++++++ .../Code02_4KeysKeyboard.java | 38 ++ .../Code03_RedundantConnectionII.java | 102 ++++ .../Code01_FindAllPeopleWithSecret.java | 102 ++++ .../Code02_AwayFromBlackHole.java | 122 +++++ .../class_2021_12_2_week/Code03_MagicSum.java | 301 +++++++++++ ...4_LowestCommonAncestorOfABinaryTreeIV.java | 57 ++ .../class_2021_12_2_week/Code05_Colors.java | 325 +++++++++++ .../Code01_RightMoveInBinaryTree.java | 74 +++ .../Code02_BinaryNegate.java | 25 + .../Code03_OneCountsInKSystem.java | 58 ++ .../Code04_CutOffTreesForGolfEvent.java | 79 +++ .../Code05_MinContinuousFragment.java | 162 ++++++ .../Code01_FiveNodesListNumbers.java | 58 ++ .../Code02_MergeArea.java | 143 +++++ .../Code03_HowManyObtuseAngles.java | 42 ++ .../Code04_MaximumNumberOfVisiblePoints.java | 43 ++ .../Code05_SplitApples.java | 123 +++++ .../Code01_LoudAndRich.java | 68 +++ .../Code02_DoAllJobs.java | 129 +++++ .../Code03_WaysToBuildWall.java | 64 +++ .../Code01_ABDisappear.java | 131 +++++ .../Code02_CatAndMouse.java | 233 ++++++++ ...romPerformingMultiplicationOperations.java | 58 ++ ...eFromLeftUpToRightDownWalk4Directions.java | 119 ++++ .../Problem_0913_CatAndMouse.java | 93 ++++ .../Code01_StringCounts.java | 13 + .../class_2022_01_2_week/Code02_BrickAll.java | 49 ++ ...ringNumberConvertBinaryAndHexadecimal.java | 119 ++++ ...umOperationsToMakeTheArrayKIncreasing.java | 49 ++ .../Code05_MagicTowSubarrayMakeMaxSum.java | 129 +++++ .../class_2022_01_2_week/Code06_QuietSum.java | 290 ++++++++++ .../Code01_AStarAlgorithm.java | 195 +++++++ .../Code02_EscapeALargeMaze.java | 74 +++ ...ode03_ShortestSubarrayWithSumAtLeastK.java | 74 +++ .../Code01_BuyThingsAboutCollocation.java | 172 ++++++ .../Code02_SplitToMArraysMinScore.java | 178 ++++++ .../Code03_RandomPickWithBlacklist.java | 52 ++ .../Code04_BattleshipsInABoard.java | 21 + .../class_2022_02_2_week/Code01_24Game.java | 97 ++++ .../Code02_DesignBitset.java | 95 ++++ .../Code03_FindKthSmallestPairDistance.java | 64 +++ .../Code04_ReachingPoints.java | 38 ++ .../Code05_RecoverTheOriginalArray.java | 48 ++ .../Code01_CheapestFlightsWithinKStops.java | 75 +++ ...de02_MinimumNumberOfDaysToEatNOranges.java | 35 ++ .../Code03_RobotBoundedInCircle.java | 40 ++ .../Code04_MaxTeamNumber.java | 94 ++++ .../Code05_StoneGameIX.java | 16 + .../Code01_SplitSameNumberWays.java | 36 ++ .../Code02_NearBiggerNoSameNeighbour.java | 69 +++ .../Code03_PartitionArrayForMaximumSum.java | 53 ++ .../Code04_NumberOfDescendingTriples.java | 134 +++++ .../Code05_GroupsOfStrings.java | 195 +++++++ .../Code01_StronglyConnectedComponents.java | 105 ++++ .../Code02_NetworkOfSchools.java | 148 +++++ .../Code03_PopularCows.java | 150 +++++ .../Code04_IgniteMinBombs.java | 328 +++++++++++ .../Code01_MeetingCheck.java | 202 +++++++ .../Code02_StringCheck.java | 65 +++ .../class_2022_03_2_week/Code03_AiFill.java | 112 ++++ .../Code04_SameTeams.java | 62 +++ .../Code05_NumberOfDivisibleByM.java | 69 +++ .../Code06_JobMinDays.java | 100 ++++ .../Code07_MinWaitingTime.java | 95 ++++ ...ode08_TimeNSpace1LowestCommonAncestor.java | 111 ++++ .../Code01_LongestUncontinuousSet.java | 100 ++++ .../class_2022_03_3_week/Code02_CutDouFu.java | 54 ++ .../Code03_MaxSumOnReverseArray.java | 105 ++++ .../Code04_ArrangeAddGetMax.java | 150 +++++ .../class_2022_03_3_week/Code05_EatFish.java | 106 ++++ .../Code06_FinancialProduct.java | 51 ++ .../Code07_CoopDevelop.java | 51 ++ .../Code01_ArrangeJob.java | 66 +++ .../Code02_BuyGoodsHaveDiscount.java | 89 +++ .../Code03_MinTowNumberSumABS.java | 159 ++++++ .../Code04_JumpToTargets.java | 45 ++ .../Code05_HowManyWaysFromBottomToTop.java | 81 +++ .../Code06_LongestContinuousTrees.java | 21 + .../Code07_IrregularSudoku.java | 127 +++++ .../class_2022_03_4_week/Code08_EggXtoY.java | 82 +++ .../Code01_KMAlgorithm.java | 166 ++++++ .../Code02_ToAllSpace.java | 213 ++++++++ .../Code03_MaximumAndSumOfArray.java | 90 +++ .../Code04_KillAllSameTime.java | 18 + .../Code01_FourNumbersMinusOne.java | 58 ++ .../Code02_MaxOrSmallestSubarray.java | 116 ++++ .../Code03_ArrangeMeetingPosCancelPre.java | 219 ++++++++ .../Code04_MaxScoreMoveInBoard.java | 88 +++ ...de05_PickKnumbersNearTowNumberMaxDiff.java | 57 ++ .../Code06_TopMinSubsquenceSum.java | 114 ++++ .../Code07_TopMaxSubsquenceSum.java | 124 +++++ .../Code01_SumOfValuesAboutPrimes.java | 114 ++++ ...de02_MinDistanceFromLeftUpToRightDown.java | 125 +++++ .../Code03_MaxSumDividedBy7.java | 82 +++ .../Code04_AllJobFinishTime.java | 48 ++ ...Code05_TowLongestSubarraySame01Number.java | 95 ++++ .../Code06_PerfectPairNumber.java | 53 ++ .../Code07_MaxMoneyMostMin.java | 236 ++++++++ .../Code01_MaxOneNumbers.java | 79 +++ .../class_2022_04_3_week/Code02_RMQ.java | 118 ++++ .../Code03_ValidSortedArrayWays.java | 125 +++++ .../Code04_SumEvenSubNumber.java | 84 +++ .../Code05_ModKSubstringNumbers.java | 83 +++ .../Code01_JumMinSameValue.java | 79 +++ .../Code02_WhoWin21Balls.java | 160 ++++++ .../Code03_FindDuplicateOnlyOne.java | 114 ++++ .../Code04_SumOfQuadraticSum.java | 113 ++++ ...eStringNoLessKLenNoOverlapingMaxParts.java | 131 +++++ .../Code01_TwoObjectMaxValue.java | 155 ++++++ .../Code02_ModifyOneNumberModXWays.java | 84 +++ .../Code03_SortedSubsequenceMaxSum.java | 162 ++++++ .../Code04_OneEdgeMagicMinPathSum.java | 173 ++++++ .../Code05_RedAndWhiteSquares.java | 211 ++++++++ .../Code01_MaxNumberUnderLimit.java | 188 +++++++ .../Code02_RemoveNumbersNotIncreasingAll.java | 162 ++++++ .../Code03_NumberOfCannon.java | 97 ++++ .../Code04_MinJumpUsePre.java | 154 ++++++ .../Code01_SomeDPFromVT.java | 246 +++++++++ .../Code02_MinSetForEveryRange.java | 48 ++ ...easingSubarrayCanDeleteContinuousPart.java | 221 ++++++++ .../Code04_ABCSameNumber.java | 169 ++++++ .../Code01_WhereWillTheBallFall.java | 46 ++ ...02_UniqueSubstringsInWraparoundString.java | 34 ++ .../Code03_NumberOfAtoms.java | 104 ++++ .../Code04_SubstringWithLargestVariance.java | 98 ++++ ..._MostStonesRemovedWithSameRowOrColumn.java | 87 +++ .../class_2022_06_2_week/Code02_Solution.HEIC | Bin 0 -> 2358360 bytes .../Code02_SumOfTotalStrengthOfWizards.java | 53 ++ ...e03_NumberOfDifferentSubsequencesGCDs.java | 60 ++ .../Code04_ConsecutiveNumbersSum.java | 101 ++++ .../Code01_MaxChunksToMakeSortedII.java | 42 ++ .../Code02_SellingPiecesOfWood.java | 151 ++++++ .../Code03_RangeModule1.java | 196 +++++++ .../Code03_RangeModule2.java | 112 ++++ .../Code04_StarNumber.java | 87 +++ .../Code01_MinimumWindowSubsequence.java | 267 +++++++++ .../Code02_StackNotSplit.java | 177 ++++++ .../Code03_MaxAnimalNumber.java | 117 ++++ ...ode04_MinimizeMaxDistanceToGasStation.java | 45 ++ .../Code01_WindPrevent.java | 149 +++++ ...de02_MinimumScoreAfterRemovalsOnATree.java | 136 +++++ .../Code03_NumberOfPeopleAwareOfASecret.java | 52 ++ .../Code01_DistinctSubseqValue.java | 68 +++ .../Code02_WaysSubsqenceXToY.java | 207 +++++++ .../Code03_SwimInRisingWater.java | 155 ++++++ .../Code04_EmployeeFreeTime.java | 57 ++ .../Code05_LineSweepAlgorithm1.java | 129 +++++ .../Code05_LineSweepAlgorithm2.java | 98 ++++ .../Code01_SetIntersectionSizeAtLeastTwo.java | 57 ++ .../Code02_ValidParenthesisString.java | 119 ++++ .../Code03_TopKFrequentElements.java | 68 +++ .../Code04_SpecialBinaryString.java | 59 ++ .../Code01_WaysWiggle.java | 146 +++++ .../Code02_SidingPuzzle1.java | 103 ++++ .../Code02_SidingPuzzle2.java | 120 ++++ .../Code03_TheNumberOfGoodSubsets.java | 49 ++ .../Code04_MatchsticksToSquare.java | 40 ++ .../class01/Code01_PrintBinary.java | 65 +++ .../class01/Code02_SumOfFactorial.java | 37 ++ 算法新手班/class01/Code03_Sort.java | 79 +++ .../class01/Code04_SelectionSort.java | 114 ++++ .../class01/Code05_BubbleSort.java | 107 ++++ .../class01/Code06_InsertionSort.java | 111 ++++ 算法新手班/class02/Code01_PreSum.java | 42 ++ .../class02/Code02_RandToRand.java | 231 ++++++++ 算法新手班/class02/Code03_Comp.java | 90 +++ .../Code03_EqualProbabilityRandom.java | 67 +++ 算法新手班/class03/Code01_BSExist.java | 64 +++ .../class03/Code02_BSNearLeft.java | 78 +++ .../class03/Code03_BSNearRight.java | 75 +++ 算法新手班/class03/Code04_BSAwesome.java | 90 +++ .../class03/Code05_HashMapTreeMap.java | 93 ++++ .../class04/Code01_ReverseList.java | 223 ++++++++ .../Code02_LinkedListToQueueAndStack.java | 222 ++++++++ .../Code03_DoubleLinkedListToDeque.java | 189 +++++++ .../class04/Code04_ReverseNodesInKGroup.java | 59 ++ .../class04/Code05_AddTwoNumbers.java | 62 +++ .../Code06_MergeTwoSortedLinkedList.java | 34 ++ 算法新手班/class05/Code01_BitMap1.java | 61 +++ 算法新手班/class05/Code02_BitMap2.java | 60 ++ .../class05/Code03_BitAddMinusMultiDiv.java | 70 +++ .../class06/Code01_MergeKSortedLists.java | 52 ++ 算法新手班/class06/Code02_SameTree.java | 23 + .../class06/Code03_SymmetricTree.java | 26 + .../Code04_MaximumDepthOfBinaryTree.java | 20 + ...ryTreeFromPreorderAndInorderTraversal.java | 72 +++ 算法新手班/class06/ShowComparator.java | 106 ++++ 算法新手班/class06/ShowComparator2.java | 78 +++ .../class06/TraversalBinaryTree.java | 72 +++ ...ode01_BinaryTreeLevelOrderTraversalII.java | 45 ++ .../class07/Code02_BalancedBinaryTree.java | 42 ++ 算法新手班/class07/Code03_PathSum.java | 60 ++ 算法新手班/class07/Code04_PathSumII.java | 58 ++ .../class07/Code05_IsBinarySearchTree.java | 85 +++ 算法新手班/class08/Code01_GetMax.java | 43 ++ 算法新手班/class08/Code02_MergeSort.java | 183 +++++++ .../class08/Code03_PartitionAndQuickSort.java | 231 ++++++++ 718 files changed, 73447 insertions(+) create mode 100644 体系学习班/class01/Code01_SelectionSort.java create mode 100644 体系学习班/class01/Code02_BubbleSort.java create mode 100644 体系学习班/class01/Code03_InsertionSort.java create mode 100644 体系学习班/class01/Code04_BSExist.java create mode 100644 体系学习班/class01/Code05_BSNearLeft.java create mode 100644 体系学习班/class01/Code05_BSNearRight.java create mode 100644 体系学习班/class01/Code06_BSAwesome.java create mode 100644 体系学习班/class02/Code01_Swap.java create mode 100644 体系学习班/class02/Code02_EvenTimesOddTimes.java create mode 100644 体系学习班/class02/Code03_KM.java create mode 100644 体系学习班/class03/Code01_ReverseList.java create mode 100644 体系学习班/class03/Code02_DeleteGivenValue.java create mode 100644 体系学习班/class03/Code03_DoubleEndsQueueToStackAndQueue.java create mode 100644 体系学习班/class03/Code04_RingArray.java create mode 100644 体系学习班/class03/Code05_GetMinStack.java create mode 100644 体系学习班/class03/Code06_TwoStacksImplementQueue.java create mode 100644 体系学习班/class03/Code07_TwoQueueImplementStack.java create mode 100644 体系学习班/class03/Code08_GetMax.java create mode 100644 体系学习班/class03/HashMapAndSortedMap.java create mode 100644 体系学习班/class04/Code01_MergeSort.java create mode 100644 体系学习班/class04/Code02_SmallSum.java create mode 100644 体系学习班/class04/Code03_ReversePair.java create mode 100644 体系学习班/class04/Code04_BiggerThanRightTwice.java create mode 100644 体系学习班/class05/Code01_CountOfRangeSum.java create mode 100644 体系学习班/class05/Code02_PartitionAndQuickSort.java create mode 100644 体系学习班/class05/Code03_QuickSortRecursiveAndUnrecursive.java create mode 100644 体系学习班/class05/Code04_DoubleLinkedListQuickSort.java create mode 100644 体系学习班/class06/Code01_Comparator.java create mode 100644 体系学习班/class06/Code02_Heap.java create mode 100644 体系学习班/class06/Code03_HeapSort.java create mode 100644 体系学习班/class06/Code04_SortArrayDistanceLessK.java create mode 100644 体系学习班/class07/Code01_CoverMax.java create mode 100644 体系学习班/class07/Code02_EveryStepShowBoss.java create mode 100644 体系学习班/class07/HeapGreater.java create mode 100644 体系学习班/class07/Inner.java create mode 100644 体系学习班/class08/Code01_TrieTree.java create mode 100644 体系学习班/class08/Code02_TrieTree.java create mode 100644 体系学习班/class08/Code03_CountSort.java create mode 100644 体系学习班/class08/Code04_RadixSort.java create mode 100644 体系学习班/class09/Code01_LinkedListMid.java create mode 100644 体系学习班/class09/Code02_IsPalindromeList.java create mode 100644 体系学习班/class09/Code03_SmallerEqualBigger.java create mode 100644 体系学习班/class09/Code04_CopyListWithRandom.java create mode 100644 体系学习班/class10/Code01_FindFirstIntersectNode.java create mode 100644 体系学习班/class10/Code02_RecursiveTraversalBT.java create mode 100644 体系学习班/class10/Code03_UnRecursiveTraversalBT.java create mode 100644 体系学习班/class11/Code01_LevelTraversalBT.java create mode 100644 体系学习班/class11/Code02_SerializeAndReconstructTree.java create mode 100644 体系学习班/class11/Code03_EncodeNaryTreeToBinaryTree.java create mode 100644 体系学习班/class11/Code04_PrintBinaryTree.java create mode 100644 体系学习班/class11/Code05_TreeMaxWidth.java create mode 100644 体系学习班/class11/Code06_SuccessorNode.java create mode 100644 体系学习班/class11/Code07_PaperFolding.java create mode 100644 体系学习班/class12/Code01_IsCBT.java create mode 100644 体系学习班/class12/Code02_IsBST.java create mode 100644 体系学习班/class12/Code03_IsBalanced.java create mode 100644 体系学习班/class12/Code04_IsFull.java create mode 100644 体系学习班/class12/Code05_MaxSubBSTSize.java create mode 100644 体系学习班/class12/Code06_MaxDistance.java create mode 100644 体系学习班/class13/Code01_IsCBT.java create mode 100644 体系学习班/class13/Code02_MaxSubBSTHead.java create mode 100644 体系学习班/class13/Code03_lowestAncestor.java create mode 100644 体系学习班/class13/Code04_MaxHappy.java create mode 100644 体系学习班/class13/Code05_LowestLexicography.java create mode 100644 体系学习班/class14/Code01_Light.java create mode 100644 体系学习班/class14/Code02_LessMoneySplitGold.java create mode 100644 体系学习班/class14/Code03_BestArrange.java create mode 100644 体系学习班/class14/Code04_IPO.java create mode 100644 体系学习班/class14/Code05_UnionFind.java create mode 100644 体系学习班/class15/Code01_FriendCircles.java create mode 100644 体系学习班/class15/Code02_NumberOfIslands.java create mode 100644 体系学习班/class15/Code03_NumberOfIslandsII.java create mode 100644 体系学习班/class16/Code01_BFS.java create mode 100644 体系学习班/class16/Code02_DFS.java create mode 100644 体系学习班/class16/Code03_TopologicalOrderBFS.java create mode 100644 体系学习班/class16/Code03_TopologicalOrderDFS1.java create mode 100644 体系学习班/class16/Code03_TopologicalOrderDFS2.java create mode 100644 体系学习班/class16/Code03_TopologySort.java create mode 100644 体系学习班/class16/Code04_Kruskal.java create mode 100644 体系学习班/class16/Code05_Prim.java create mode 100644 体系学习班/class16/Code06_Dijkstra.java create mode 100644 体系学习班/class16/Code06_NetworkDelayTime.java create mode 100644 体系学习班/class16/Edge.java create mode 100644 体系学习班/class16/Graph.java create mode 100644 体系学习班/class16/GraphGenerator.java create mode 100644 体系学习班/class16/Node.java create mode 100644 体系学习班/class17/Code01_Dijkstra.java create mode 100644 体系学习班/class17/Code02_Hanoi.java create mode 100644 体系学习班/class17/Code03_PrintAllSubsquences.java create mode 100644 体系学习班/class17/Code04_PrintAllPermutations.java create mode 100644 体系学习班/class17/Code05_ReverseStackUsingRecursive.java create mode 100644 体系学习班/class17/Edge.java create mode 100644 体系学习班/class17/Graph.java create mode 100644 体系学习班/class17/Node.java create mode 100644 体系学习班/class18/Code01_RobotWalk.java create mode 100644 体系学习班/class18/Code02_CardsInLine.java create mode 100644 体系学习班/class19/Code01_Knapsack.java create mode 100644 体系学习班/class19/Code02_ConvertToLetterString.java create mode 100644 体系学习班/class19/Code03_StickersToSpellWord.java create mode 100644 体系学习班/class19/Code04_LongestCommonSubsequence.java create mode 100644 体系学习班/class20/Code01_PalindromeSubsequence.java create mode 100644 体系学习班/class20/Code02_HorseJump.java create mode 100644 体系学习班/class20/Code03_Coffee.java create mode 100644 体系学习班/class21/Code01_MinPathSum.java create mode 100644 体系学习班/class21/Code02_CoinsWayEveryPaperDifferent.java create mode 100644 体系学习班/class21/Code03_CoinsWayNoLimit.java create mode 100644 体系学习班/class21/Code04_CoinsWaySameValueSamePapper.java create mode 100644 体系学习班/class21/Code05_BobDie.java create mode 100644 体系学习班/class22/Code01_KillMonster.java create mode 100644 体系学习班/class22/Code02_MinCoinsNoLimit.java create mode 100644 体系学习班/class22/Code03_SplitNumber.java create mode 100644 体系学习班/class23/Code01_SplitSumClosed.java create mode 100644 体系学习班/class23/Code02_SplitSumClosedSizeHalf.java create mode 100644 体系学习班/class23/Code03_NQueens.java create mode 100644 体系学习班/class24/Code01_SlidingWindowMaxArray.java create mode 100644 体系学习班/class24/Code02_AllLessNumSubArray.java create mode 100644 体系学习班/class24/Code03_GasStation.java create mode 100644 体系学习班/class24/Code04_MinCoinsOnePaper.java create mode 100644 体系学习班/class25/Code01_MonotonousStack.java create mode 100644 体系学习班/class25/Code01_MonotonousStackForNowcoder.java create mode 100644 体系学习班/class25/Code02_AllTimesMinToMax.java create mode 100644 体系学习班/class25/Code03_LargestRectangleInHistogram.java create mode 100644 体系学习班/class25/Code04_MaximalRectangle.java create mode 100644 体系学习班/class25/Code05_CountSubmatricesWithAllOnes.java create mode 100644 体系学习班/class26/Code01_SumOfSubarrayMinimums.java create mode 100644 体系学习班/class26/Code02_FibonacciProblem.java create mode 100644 体系学习班/class26/Code03_ZeroLeftOneStringNumber.java create mode 100644 体系学习班/class27/Code01_KMP.java create mode 100644 体系学习班/class27/Code02_TreeEqual.java create mode 100644 体系学习班/class27/Code03_IsRotation.java create mode 100644 体系学习班/class28/Code01_Manacher.java create mode 100644 体系学习班/class28/Code02_AddShortestEnd.java create mode 100644 体系学习班/class29/Code01_FindMinKth.java create mode 100644 体系学习班/class29/Code02_MaxTopK.java create mode 100644 体系学习班/class29/Code03_ReservoirSampling.java create mode 100644 体系学习班/class30/Code01_MorrisTraversal.java create mode 100644 体系学习班/class30/Code02_MinDepth.java create mode 100644 体系学习班/class31/Code01_SegmentTree.java create mode 100644 体系学习班/class31/Code02_FallingSquares.java create mode 100644 体系学习班/class32/Code01_IndexTree.java create mode 100644 体系学习班/class32/Code02_IndexTree2D.java create mode 100644 体系学习班/class32/Code03_AC1.java create mode 100644 体系学习班/class32/Code04_AC2.java create mode 100644 体系学习班/class33/Hash.java create mode 100644 体系学习班/class34/ReadMe.java create mode 100644 体系学习班/class35/Code01_AVLTreeMap.java create mode 100644 体系学习班/class36/Code01_SizeBalancedTreeMap.java create mode 100644 体系学习班/class36/Code02_SkipListMap.java create mode 100644 体系学习班/class37/Code01_CountofRangeSum.java create mode 100644 体系学习班/class37/Code02_SlidingWindowMedian.java create mode 100644 体系学习班/class37/Code03_AddRemoveGetIndexGreat.java create mode 100644 体系学习班/class37/Code04_QueueReconstructionByHeight.java create mode 100644 体系学习班/class37/Compare.java create mode 100644 体系学习班/class38/Code01_AppleMinBags.java create mode 100644 体系学习班/class38/Code02_EatGrass.java create mode 100644 体系学习班/class38/Code03_MSumToN.java create mode 100644 体系学习班/class38/Code04_MoneyProblem.java create mode 100644 体系学习班/class39/Code01_SubsquenceMaxModM.java create mode 100644 体系学习班/class39/Code02_SnacksWays.java create mode 100644 体系学习班/class39/Code02_SnacksWaysMain1.java create mode 100644 体系学习班/class39/Code02_SnacksWaysMain2.java create mode 100644 体系学习班/class39/Code03_10Ways.java create mode 100644 体系学习班/class39/Code04_DifferentBTNum.java create mode 100644 体系学习班/class39/IsSum.java create mode 100644 体系学习班/class40/Code01_LongestSumSubArrayLengthInPositiveArray.java create mode 100644 体系学习班/class40/Code02_LongestSumSubArrayLength.java create mode 100644 体系学习班/class40/Code03_LongestLessSumSubArrayLength.java create mode 100644 体系学习班/class40/Code04_AvgLessEqualValueLongestSubarray.java create mode 100644 体系学习班/class40/Code05_PrintMatrixSpiralOrder.java create mode 100644 体系学习班/class40/Code06_RotateMatrix.java create mode 100644 体系学习班/class40/Code07_ZigZagPrintMatrix.java create mode 100644 体系学习班/class40/Code08_PrintStar.java create mode 100644 体系学习班/class41/Code01_BestSplitForAll.java create mode 100644 体系学习班/class41/Code02_BestSplitForEveryPosition.java create mode 100644 体系学习班/class41/Code03_StoneMerge.java create mode 100644 体系学习班/class41/Code04_SplitArrayLargestSum.java create mode 100644 体系学习班/class42/Code01_PostOfficeProblem.java create mode 100644 体系学习班/class42/Code02_ThrowChessPiecesProblem.java create mode 100644 体系学习班/class43/Code01_CanIWin.java create mode 100644 体系学习班/class43/Code02_TSP.java create mode 100644 体系学习班/class43/Code03_PavingTile.java create mode 100644 体系学习班/class44/Code01_LastSubstringInLexicographicalOrder.java create mode 100644 体系学习班/class44/DC3.java create mode 100644 体系学习班/class44/DC3_Algorithm.pdf create mode 100644 体系学习班/class45/Code01_InsertS2MakeMostAlphabeticalOrder.java create mode 100644 体系学习班/class45/Code02_CreateMaximumNumber.java create mode 100644 体系学习班/class45/Code03_LongestCommonSubstringConquerByHeight.java create mode 100644 体系学习班/class45/Code04_LongestRepeatingSubstring.java create mode 100644 体系学习班/class46/Code01_BurstBalloons.java create mode 100644 体系学习班/class46/Code02_RemoveBoxes.java create mode 100644 体系学习班/class46/Code03_DeleteAdjacentSameCharacter.java create mode 100644 体系学习班/class46/Code04_MaxSumLengthNoMore.java create mode 100644 体系学习班/class46/Code05_HuffmanTree.java create mode 100644 体系学习班/class47/Code01_StrangePrinter.java create mode 100644 体系学习班/class47/Code02_RestoreWays.java create mode 100644 体系学习班/class47/Code03_DinicAlgorithm.java create mode 100644 大厂刷题班/class01/Code01_CordCoverMaxPoint.java create mode 100644 大厂刷题班/class01/Code02_CountFiles.java create mode 100644 大厂刷题班/class01/Code03_Near2Power.java create mode 100644 大厂刷题班/class01/Code04_MinSwapStep.java create mode 100644 大厂刷题班/class01/Code05_LongestIncreasingPath.java create mode 100644 大厂刷题班/class01/Code06_AOE.java create mode 100644 大厂刷题班/class01/Code07_TargetSum.java create mode 100644 大厂刷题班/class02/Code01_ChooseWork.java create mode 100644 大厂刷题班/class02/Code02_Cola.java create mode 100644 大厂刷题班/class02/Code03_ReceiveAndPrintOrderLine.java create mode 100644 大厂刷题班/class02/Code04_Drive.java create mode 100644 大厂刷题班/class02/Code05_SetAll.java create mode 100644 大厂刷题班/class02/Code06_MinLengthForSort.java create mode 100644 大厂刷题班/class03/Code01_LongestSubstringWithoutRepeatingCharacters.java create mode 100644 大厂刷题班/class03/Code02_HowManyTypes.java create mode 100644 大厂刷题班/class03/Code03_Largest1BorderedSquare.java create mode 100644 大厂刷题班/class03/Code04_MaxPairNumber.java create mode 100644 大厂刷题班/class03/Code05_BoatsToSavePeople.java create mode 100644 大厂刷题班/class03/Code06_ClosestSubsequenceSum.java create mode 100644 大厂刷题班/class03/Code07_FreedomTrail.java create mode 100644 大厂刷题班/class03/Code08_DistanceKNodes.java create mode 100644 大厂刷题班/class04/Code01_QueryHobby.java create mode 100644 大厂刷题班/class04/Code02_SubArrayMaxSum.java create mode 100644 大厂刷题班/class04/Code03_SubMatrixMaxSum.java create mode 100644 大厂刷题班/class04/Code04_SubArrayMaxSumFollowUp.java create mode 100644 大厂刷题班/class04/Code05_CandyProblem.java create mode 100644 大厂刷题班/class04/Code06_MakeNo.java create mode 100644 大厂刷题班/class04/Code07_InterleavingString.java create mode 100644 大厂刷题班/class04/Code08_TheSkylineProblem.java create mode 100644 大厂刷题班/class05/Code01_ConstructBinarySearchTreeFromPreorderTraversal.java create mode 100644 大厂刷题班/class05/Code02_LeftRightSameTreeNumber.java create mode 100644 大厂刷题班/class05/Code03_EditCost.java create mode 100644 大厂刷题班/class05/Code04_DeleteMinCost.java create mode 100644 大厂刷题班/class05/Hash.java create mode 100644 大厂刷题班/class06/Code01_MaxXOR.java create mode 100644 大厂刷题班/class06/Code02_MaximumXorOfTwoNumbersInAnArray.java create mode 100644 大厂刷题班/class06/Code03_MaximumXorWithAnElementFromArray.java create mode 100644 大厂刷题班/class06/Code04_MostXorZero.java create mode 100644 大厂刷题班/class06/Code05_Nim.java create mode 100644 大厂刷题班/class07/Code01_MaxAndValue.java create mode 100644 大厂刷题班/class07/Code02_MinCameraCover.java create mode 100644 大厂刷题班/class07/Code03_MaxGap.java create mode 100644 大厂刷题班/class07/Code04_Power2Diffs.java create mode 100644 大厂刷题班/class07/Code05_WorldBreak.java create mode 100644 大厂刷题班/class07/Code06_SplitStringMaxValue.java create mode 100644 大厂刷题班/class08/Code01_ExpressionCompute.java create mode 100644 大厂刷题班/class08/Code02_ContainerWithMostWater.java create mode 100644 大厂刷题班/class08/Code03_FindWordInMatrix.java create mode 100644 大厂刷题班/class08/Code04_SnakeGame.java create mode 100644 大厂刷题班/class09/Code01_LightProblem.java create mode 100644 大厂刷题班/class09/Code02_RemoveInvalidParentheses.java create mode 100644 大厂刷题班/class09/Code03_LIS.java create mode 100644 大厂刷题班/class09/Code04_EnvelopesProblem.java create mode 100644 大厂刷题班/class09/Code05_IsStepSum.java create mode 100644 大厂刷题班/class10/Code01_JumpGame.java create mode 100644 大厂刷题班/class10/Code02_TopK.java create mode 100644 大厂刷题班/class10/Code03_KInversePairs.java create mode 100644 大厂刷题班/class10/Code04_BSTtoDoubleLinkedList.java create mode 100644 大厂刷题班/class10/Code05_BooleanEvaluation.java create mode 100644 大厂刷题班/class11/Code01_MinimumInsertionStepsToMakeAStringPalindrome.java create mode 100644 大厂刷题班/class11/Code02_PalindromePartitioningII.java create mode 100644 大厂刷题班/class12/Code01_ContainAllCharExactly.java create mode 100644 大厂刷题班/class12/Code03_FindKthMinNumber.java create mode 100644 大厂刷题班/class12/Code03_LongestConsecutive.java create mode 100644 大厂刷题班/class12/Code04_RegularExpressionMatch.java create mode 100644 大厂刷题班/class13/Code01_NCardsABWin.java create mode 100644 大厂刷题班/class13/Code02_SuperWashingMachines.java create mode 100644 大厂刷题班/class13/Code03_ScrambleString.java create mode 100644 大厂刷题班/class13/Code04_BricksFallingWhenHit.java create mode 100644 大厂刷题班/class14/Code01_Parentheses.java create mode 100644 大厂刷题班/class14/Code02_MaxSubArraySumLessOrEqualK.java create mode 100644 大厂刷题班/class14/Code03_BiggestBSTTopologyInTree.java create mode 100644 大厂刷题班/class14/Code04_CompleteTreeNodeNumber.java create mode 100644 大厂刷题班/class14/Code05_RecoverBinarySearchTree.java create mode 100644 大厂刷题班/class14/Code06_MissingNumber.java create mode 100644 大厂刷题班/class15/Code01_BestTimeToBuyAndSellStock.java create mode 100644 大厂刷题班/class15/Code02_BestTimeToBuyAndSellStockII.java create mode 100644 大厂刷题班/class15/Code03_BestTimeToBuyAndSellStockIII.java create mode 100644 大厂刷题班/class15/Code04_BestTimeToBuyAndSellStockIV.java create mode 100644 大厂刷题班/class15/Code05_BestTimeToBuyAndSellStockWithCooldown.java create mode 100644 大厂刷题班/class15/Code06_BestTimeToBuyAndSellStockWithTransactionFee.java create mode 100644 大厂刷题班/class16/Code01_IsSum.java create mode 100644 大厂刷题班/class16/Code02_SmallestUnFormedSum.java create mode 100644 大厂刷题班/class16/Code03_MinPatches.java create mode 100644 大厂刷题班/class16/Code04_MergeRecord.java create mode 100644 大厂刷题班/class16/Code05_JosephusProblem.java create mode 100644 大厂刷题班/class17/Code01_FindNumInSortedMatrix.java create mode 100644 大厂刷题班/class17/Code02_KthSmallestElementInSortedMatrix.java create mode 100644 大厂刷题班/class17/Code03_PalindromePairs.java create mode 100644 大厂刷题班/class17/Code04_DistinctSubseq.java create mode 100644 大厂刷题班/class17/Code05_DistinctSubseqValue.java create mode 100644 大厂刷题班/class18/Code01_HanoiProblem.java create mode 100644 大厂刷题班/class18/Code02_ShortestBridge.java create mode 100644 大厂刷题班/class18/Code03_CherryPickup.java create mode 100644 大厂刷题班/class18/Code04_TopKSumCrossTwoArrays.java create mode 100644 大厂刷题班/class19/Code01_LRUCache.java create mode 100644 大厂刷题班/class19/Code02_LFUCache.java create mode 100644 大厂刷题班/class19/Code03_OneNumber.java create mode 100644 大厂刷题班/class19/Code04_SmallestRangeCoveringElementsfromKLists.java create mode 100644 大厂刷题班/class19/Code05_CardsProblem.java create mode 100644 大厂刷题班/class20/Code01_PreAndInArrayToPosArray.java create mode 100644 大厂刷题班/class20/Code02_LargestComponentSizebyCommonFactor.java create mode 100644 大厂刷题班/class20/Code03_ShuffleProblem.java create mode 100644 大厂刷题班/class20/Code04_PalindromeWays.java create mode 100644 大厂刷题班/class21/TreeChainPartition.java create mode 100644 大厂刷题班/class22/Code01_MaximumSumof3NonOverlappingSubarrays.java create mode 100644 大厂刷题班/class22/Code02_TrappingRainWater.java create mode 100644 大厂刷题班/class22/Code03_TrappingRainWaterII.java create mode 100644 大厂刷题班/class22/Code04_VisibleMountains.java create mode 100644 大厂刷题班/class22/Code05_TallestBillboard.java create mode 100644 大厂刷题班/class23/Code01_LCATarjanAndTreeChainPartition.java create mode 100644 大厂刷题班/class23/Code02_MaxABSBetweenLeftAndRight.java create mode 100644 大厂刷题班/class23/Code03_LongestIntegratedLength.java create mode 100644 大厂刷题班/class23/Code04_FindKMajority.java create mode 100644 大厂刷题班/class23/Code05_MinimumCostToMergeStones.java create mode 100644 大厂刷题班/class24/Code01_Split4Parts.java create mode 100644 大厂刷题班/class24/Code02_KthMinPair.java create mode 100644 大厂刷题班/class24/Code03_NotContains4.java create mode 100644 大厂刷题班/class24/Code04_Painting.java create mode 100644 大厂刷题班/class24/Code05_MinWindowLength.java create mode 100644 大厂刷题班/class24/Code06_RemoveDuplicateLettersLessLexi.java create mode 100644 大厂刷题班/class25/Code01_IPToCIDR.java create mode 100644 大厂刷题班/class25/Code02_3Sum.java create mode 100644 大厂刷题班/class25/Code03_MaxPointsOnALine.java create mode 100644 大厂刷题班/class25/Code04_GasStation.java create mode 100644 大厂刷题班/class26/Code01_MinRange.java create mode 100644 大厂刷题班/class26/Code02_WordSearchII.java create mode 100644 大厂刷题班/class26/Code03_ExpressionAddOperators.java create mode 100644 大厂刷题班/class26/Code04_WordLadderII.java create mode 100644 大厂刷题班/class27/Code01_PickBands.java create mode 100644 大厂刷题班/class27/Code02_MinPeople.java create mode 100644 大厂刷题班/class27/Problem_0001_TwoSum.java create mode 100644 大厂刷题班/class27/Problem_0007_ReverseInteger.java create mode 100644 大厂刷题班/class27/说明 create mode 100644 大厂刷题班/class28/Problem_0008_StringToInteger.java create mode 100644 大厂刷题班/class28/Problem_0012_IntegerToRoman.java create mode 100644 大厂刷题班/class28/Problem_0013_RomanToInteger.java create mode 100644 大厂刷题班/class28/Problem_0014_LongestCommonPrefix.java create mode 100644 大厂刷题班/class28/Problem_0017_LetterCombinationsOfAPhoneNumber.java create mode 100644 大厂刷题班/class28/Problem_0019_RemoveNthNodeFromEndofList.java create mode 100644 大厂刷题班/class28/Problem_0020_ValidParentheses.java create mode 100644 大厂刷题班/class28/Problem_0022_GenerateParentheses.java create mode 100644 大厂刷题班/class28/Problem_0026_RemoveDuplicatesFromSortedArray.java create mode 100644 大厂刷题班/class28/Problem_0034_FindFirstAndLastPositionOfElementInSortedArray.java create mode 100644 大厂刷题班/class28/Problem_0036_ValidSudoku.java create mode 100644 大厂刷题班/class28/Problem_0037_SudokuSolver.java create mode 100644 大厂刷题班/class28/Problem_0038_CountAndSay.java create mode 100644 大厂刷题班/class28/Problem_0049_GroupAnagrams.java create mode 100644 大厂刷题班/class28/说明 create mode 100644 大厂刷题班/class29/Problem_0033_SearchInRotatedSortedArray.java create mode 100644 大厂刷题班/class29/Problem_0050_PowXN.java create mode 100644 大厂刷题班/class29/Problem_0056_MergeIntervals.java create mode 100644 大厂刷题班/class29/Problem_0062_UniquePaths.java create mode 100644 大厂刷题班/class29/Problem_0066_PlusOne.java create mode 100644 大厂刷题班/class29/Problem_0069_SqrtX.java create mode 100644 大厂刷题班/class29/Problem_0073_SetMatrixZeroes.java create mode 100644 大厂刷题班/class29/说明 create mode 100644 大厂刷题班/class30/Problem_0079_WordSearch.java create mode 100644 大厂刷题班/class30/Problem_0088_MergeSortedArray.java create mode 100644 大厂刷题班/class30/Problem_0091_DecodeWays.java create mode 100644 大厂刷题班/class30/Problem_0098_ValidateBinarySearchTree.java create mode 100644 大厂刷题班/class30/Problem_0101_SymmetricTree.java create mode 100644 大厂刷题班/class30/Problem_0103_BinaryTreeZigzagLevelOrderTraversal.java create mode 100644 大厂刷题班/class30/Problem_0108_ConvertSortedArrayToBinarySearchTree.java create mode 100644 大厂刷题班/class30/Problem_0116_PopulatingNextRightPointersInEachNode.java create mode 100644 大厂刷题班/class30/Problem_0118_PascalTriangle.java create mode 100644 大厂刷题班/class30/Problem_0119_PascalTriangleII.java create mode 100644 大厂刷题班/class30/Problem_0124_BinaryTreeMaximumPathSum.java create mode 100644 大厂刷题班/class30/Problem_0639_DecodeWaysII.java create mode 100644 大厂刷题班/class30/说明 create mode 100644 大厂刷题班/class31/Problem_0125_ValidPalindrome.java create mode 100644 大厂刷题班/class31/Problem_0127_WordLadder.java create mode 100644 大厂刷题班/class31/Problem_0130_SurroundedRegions.java create mode 100644 大厂刷题班/class31/Problem_0139_WordBreak.java create mode 100644 大厂刷题班/class31/Problem_0140_WordBreakII.java create mode 100644 大厂刷题班/class31/Problem_0148_SortList.java create mode 100644 大厂刷题班/class31/Problem_0150_EvaluateReversePolishNotation.java create mode 100644 大厂刷题班/class31/说明 create mode 100644 大厂刷题班/class32/Problem_0152_MaximumProductSubarray.java create mode 100644 大厂刷题班/class32/Problem_0163_MissingRanges.java create mode 100644 大厂刷题班/class32/Problem_0166_FractionToRecurringDecimal.java create mode 100644 大厂刷题班/class32/Problem_0171_ExcelSheetColumnNumber.java create mode 100644 大厂刷题班/class32/Problem_0172_FactorialTrailingZeroes.java create mode 100644 大厂刷题班/class32/Problem_0189_RotateArray.java create mode 100644 大厂刷题班/class32/Problem_0190_ReverseBits.java create mode 100644 大厂刷题班/class32/Problem_0191_NumberOf1Bits.java create mode 100644 大厂刷题班/class32/Problem_0202_HappyNumber.java create mode 100644 大厂刷题班/class32/Problem_0204_CountPrimes.java create mode 100644 大厂刷题班/class32/SequenceM.java create mode 100644 大厂刷题班/class32/说明 create mode 100644 大厂刷题班/class33/Problem_0207_CourseSchedule.java create mode 100644 大厂刷题班/class33/Problem_0210_CourseScheduleII.java create mode 100644 大厂刷题班/class33/Problem_0213_HouseRobberII.java create mode 100644 大厂刷题班/class33/Problem_0237_DeleteNodeInLinkedList.java create mode 100644 大厂刷题班/class33/Problem_0238_ProductOfArrayExceptSelf.java create mode 100644 大厂刷题班/class33/Problem_0242_ValidAnagram.java create mode 100644 大厂刷题班/class33/Problem_0251_Flatten2DVector.java create mode 100644 大厂刷题班/class33/Problem_0269_AlienDictionary.java create mode 100644 大厂刷题班/class33/Problem_0277_FindTheCelebrity.java create mode 100644 大厂刷题班/class33/Problem_0279_PerfectSquares.java create mode 100644 大厂刷题班/class33/Problem_0283_MoveZeroes.java create mode 100644 大厂刷题班/class33/说明 create mode 100644 大厂刷题班/class34/Problem_0287_FindTheDuplicateNumber.java create mode 100644 大厂刷题班/class34/Problem_0289_GameOfLife.java create mode 100644 大厂刷题班/class34/Problem_0295_FindMedianFromDataStream.java create mode 100644 大厂刷题班/class34/Problem_0315_CountOfSmallerNumbersAfterSelf.java create mode 100644 大厂刷题班/class34/Problem_0324_WiggleSortII.java create mode 100644 大厂刷题班/class34/Problem_0326_PowerOfThree.java create mode 100644 大厂刷题班/class34/Problem_0328_OddEvenLinkedList.java create mode 100644 大厂刷题班/class34/Problem_0340_LongestSubstringWithAtMostKDistinctCharacters.java create mode 100644 大厂刷题班/class34/Problem_0341_FlattenNestedListIterator.java create mode 100644 大厂刷题班/class34/Problem_0348_DesignTicTacToe.java create mode 100644 大厂刷题班/class34/Problem_0380_InsertDeleteGetRandom.java create mode 100644 大厂刷题班/class34/Problem_0384_ShuffleAnArray.java create mode 100644 大厂刷题班/class34/说明 create mode 100644 大厂刷题班/class35/Code01_StringKth.java create mode 100644 大厂刷题班/class35/Code02_MagicStone.java create mode 100644 大厂刷题班/class35/Code03_WatchMovieMaxTime.java create mode 100644 大厂刷题班/class35/Code04_WalkToEnd.java create mode 100644 大厂刷题班/class35/Code05_CircleCandy.java create mode 100644 大厂刷题班/class35/Problem_0347_TopKFrequentElements.java create mode 100644 大厂刷题班/class35/Problem_0395_LongestSubstringWithAtLeastKRepeatingCharacters.java create mode 100644 大厂刷题班/class35/Problem_0412_FizzBuzz.java create mode 100644 大厂刷题班/class35/Problem_0454_4SumII.java create mode 100644 大厂刷题班/class35/Problem_0673_NumberOfLongestIncreasingSubsequence.java create mode 100644 大厂刷题班/class35/Problem_0687_LongestUnivaluePath.java create mode 100644 大厂刷题班/class35/说明 create mode 100644 大厂刷题班/class36/Code01_ReverseInvertString.java create mode 100644 大厂刷题班/class36/Code02_Ratio01Split.java create mode 100644 大厂刷题班/class36/Code03_MatchCount.java create mode 100644 大厂刷题班/class36/Code04_ComputeExpressionValue.java create mode 100644 大厂刷题班/class36/Code05_Query3Problems.java create mode 100644 大厂刷题班/class36/Code06_NodeWeight.java create mode 100644 大厂刷题班/class36/Code07_PickAddMax.java create mode 100644 大厂刷题班/class36/Code08_MinBoatEvenNumbers.java create mode 100644 大厂刷题班/class36/Code09_MaxKLenSequence.java create mode 100644 大厂刷题班/class36/Code10_StoneGameIV.java create mode 100644 大厂刷题班/class36/Code11_BusRoutes.java create mode 100644 大厂刷题班/class36/说明 create mode 100644 大厂刷题班/class37/Code01_ArrangeProject.java create mode 100644 大厂刷题班/class37/Code02_GameForEveryStepWin.java create mode 100644 大厂刷题班/class37/Problem_0114_FlattenBinaryTreeToLinkedList.java create mode 100644 大厂刷题班/class37/Problem_0221_MaximalSquare.java create mode 100644 大厂刷题班/class37/Problem_0226_InvertBinaryTree.java create mode 100644 大厂刷题班/class37/Problem_0337_HouseRobberIII.java create mode 100644 大厂刷题班/class37/Problem_0394_DecodeString.java create mode 100644 大厂刷题班/class37/Problem_0406_QueueReconstructionByHeight.java create mode 100644 大厂刷题班/class37/Problem_0437_PathSumIII.java create mode 100644 大厂刷题班/class37/说明 create mode 100644 大厂刷题班/class38/Code01_FillGapMinStep.java create mode 100644 大厂刷题班/class38/Code02_GreatWall.java create mode 100644 大厂刷题班/class38/Problem_0438_FindAllAnagramsInAString.java create mode 100644 大厂刷题班/class38/Problem_0448_FindAllNumbersDisappearedInAnArray.java create mode 100644 大厂刷题班/class38/Problem_0617_MergeTwoBinaryTrees.java create mode 100644 大厂刷题班/class38/Problem_0621_TaskScheduler.java create mode 100644 大厂刷题班/class38/Problem_0647_PalindromicSubstrings.java create mode 100644 大厂刷题班/class38/Problem_0739_DailyTemperatures.java create mode 100644 大厂刷题班/class38/Problem_0763_PartitionLabels.java create mode 100644 大厂刷题班/class38/说明 create mode 100644 大厂刷题班/class39/Code01_01AddValue.java create mode 100644 大厂刷题班/class39/Code02_ValidSequence.java create mode 100644 大厂刷题班/class39/Code03_SequenceKDifferentKinds.java create mode 100644 大厂刷题班/class39/Code04_JumpGameOnMatrix.java create mode 100644 大厂刷题班/class39/Code05_0123Disappear.java create mode 100644 大厂刷题班/class40/Code01_SplitTo01.java create mode 100644 大厂刷题班/class40/Code02_Mod3Max.java create mode 100644 大厂刷题班/class40/Code03_MaxMeetingScore.java create mode 100644 大厂刷题班/class40/Code04_LetASorted.java create mode 100644 大厂刷题班/class40/Code05_AllSame.java create mode 100644 大厂刷题班/class41/Code01_MinSwapTimes.java create mode 100644 大厂刷题班/class41/Code02_PoemProblem.java create mode 100644 大厂刷题班/class41/Code03_MagicGoToAim.java create mode 100644 大厂刷题班/class41/Problem_0031_NextPermutation.java create mode 100644 大厂刷题班/class42/Problem_0265_PaintHouseII.java create mode 100644 大厂刷题班/class42/Problem_0272_ClosestBinarySearchTreeValueII.java create mode 100644 大厂刷题班/class42/Problem_0273_IntegerToEnglishWords.java create mode 100644 大厂刷题班/class42/Problem_0296_BestMeetingPoint.java create mode 100644 大厂刷题班/class42/Problem_0335_SelfCrossing.java create mode 100644 大厂刷题班/class43/Code01_SumNoPositiveMinCost.java create mode 100644 大厂刷题班/class43/Code02_MinCostToYeahArray.java create mode 100644 大厂刷题班/class44/Problem_0248_StrobogrammaticNumberIII.java create mode 100644 大厂刷题班/class44/Problem_0317_ShortestDistanceFromAllBuildings.java create mode 100644 大厂刷题班/class44/Problem_0992_SubarraysWithKDifferentIntegers.java create mode 100644 大厂刷题班/class45/Code01_SplitBuildingBlock.java create mode 100644 大厂刷题班/class45/Problem_0291_WordPatternII.java create mode 100644 大厂刷题班/class45/Problem_0403_FrogJump.java create mode 100644 大厂刷题班/class45/Problem_2035_PartitionArrayIntoTwoArraysToMinimizeSumDifference.java create mode 100644 大厂刷题班/class46/Problem_0363_MaxSumOfRectangleNoLargerThanK.java create mode 100644 大厂刷题班/class46/Problem_0391_PerfectRectangle.java create mode 100644 大厂刷题班/class46/Problem_0411_MinimumUniqueWordAbbreviation.java create mode 100644 大厂刷题班/class46/Problem_0425_WordSquares.java create mode 100644 大厂刷题班/class47/Code01_DynamicSegmentTree.java create mode 100644 大厂刷题班/class47/Code02_DynamicSegmentTree.java create mode 100644 大厂刷题班/class47/Problem_0315_CountOfSmallerNumbersAfterSelf.java create mode 100644 大厂刷题班/class47/Problem_0358_RearrangeStringKDistanceApart.java create mode 100644 大厂刷题班/class47/Problem_0428_SerializeAndDeserializeNaryTree.java create mode 100644 大厂刷题班/class47/Problem_0465_OptimalAccountBalancing.java create mode 100644 大厂刷题班/class47/Problem_0475_Heaters.java create mode 100644 大厂刷题班/class48/Code01_MinKthPairMinusABS.java create mode 100644 大厂刷题班/class48/Problem_0472_ConcatenatedWords.java create mode 100644 大厂刷题班/class48/Problem_0483_SmallestGoodBase.java create mode 100644 大厂刷题班/class48/Problem_0499_TheMazeIII.java create mode 100644 大厂刷题班/class49/Problem_0377_CombinationSumIV.java create mode 100644 大厂刷题班/class49/Problem_0440_KthSmallestInLexicographicalOrder.java create mode 100644 大厂刷题班/class49/Problem_0446_ArithmeticSlicesIISubsequence.java create mode 100644 大厂刷题班/class49/Problem_0489_RobotRoomCleaner.java create mode 100644 大厂刷题班/class49/Problem_0527_WordAbbreviation.java create mode 100644 大厂刷题班/class49/Problem_0548_SplitArrayEithEqualSum.java create mode 100644 大厂刷题班/class49/Problem_0564_FindTheClosestPalindrome.java create mode 100644 大厂刷题班/class50/Problem_0568_MaximumVacationDays.java create mode 100644 大厂刷题班/class50/Problem_0587_ErectTheFence.java create mode 100644 大厂刷题班/class50/Problem_0588_DesignInMemoryFileSystem.java create mode 100644 大厂刷题班/class50/Problem_0600_NonnegativeIntegersWithoutConsecutiveOnes.java create mode 100644 大厂刷题班/class51/LCP_0003_Robot.java create mode 100644 大厂刷题班/class51/Problem_0630_CourseScheduleIII.java create mode 100644 大厂刷题班/class51/Problem_0642_DesignSearchAutocompleteSystem.java create mode 100644 大厂刷题班/class51/Problem_0875_KokoEatingBananas.java create mode 100644 大厂刷题班/class51/Problem_1035_UncrossedLines.java create mode 100644 大厂刷题班/class52/Problem_0656_CoinPath.java create mode 100644 大厂刷题班/class52/Problem_0683_KEmptySlots.java create mode 100644 大厂刷题班/class52/Problem_1488_AvoidFloodInTheCity.java create mode 100644 算法周更班/class_2021_11_4_week/Code01_RetainTree.java create mode 100644 算法周更班/class_2021_11_4_week/Code02_GuessNumberHigherOrLowerII.java create mode 100644 算法周更班/class_2021_11_4_week/Code03_StartToEndBinaryOneTarget.java create mode 100644 算法周更班/class_2021_12_1_week/Code01_XtoYMinDistance.java create mode 100644 算法周更班/class_2021_12_1_week/Code02_4KeysKeyboard.java create mode 100644 算法周更班/class_2021_12_1_week/Code03_RedundantConnectionII.java create mode 100644 算法周更班/class_2021_12_2_week/Code01_FindAllPeopleWithSecret.java create mode 100644 算法周更班/class_2021_12_2_week/Code02_AwayFromBlackHole.java create mode 100644 算法周更班/class_2021_12_2_week/Code03_MagicSum.java create mode 100644 算法周更班/class_2021_12_2_week/Code04_LowestCommonAncestorOfABinaryTreeIV.java create mode 100644 算法周更班/class_2021_12_2_week/Code05_Colors.java create mode 100644 算法周更班/class_2021_12_3_week/Code01_RightMoveInBinaryTree.java create mode 100644 算法周更班/class_2021_12_3_week/Code02_BinaryNegate.java create mode 100644 算法周更班/class_2021_12_3_week/Code03_OneCountsInKSystem.java create mode 100644 算法周更班/class_2021_12_3_week/Code04_CutOffTreesForGolfEvent.java create mode 100644 算法周更班/class_2021_12_3_week/Code05_MinContinuousFragment.java create mode 100644 算法周更班/class_2021_12_4_week/Code01_FiveNodesListNumbers.java create mode 100644 算法周更班/class_2021_12_4_week/Code02_MergeArea.java create mode 100644 算法周更班/class_2021_12_4_week/Code03_HowManyObtuseAngles.java create mode 100644 算法周更班/class_2021_12_4_week/Code04_MaximumNumberOfVisiblePoints.java create mode 100644 算法周更班/class_2021_12_4_week/Code05_SplitApples.java create mode 100644 算法周更班/class_2021_12_5_week/Code01_LoudAndRich.java create mode 100644 算法周更班/class_2021_12_5_week/Code02_DoAllJobs.java create mode 100644 算法周更班/class_2021_12_5_week/Code03_WaysToBuildWall.java create mode 100644 算法周更班/class_2022_01_1_week/Code01_ABDisappear.java create mode 100644 算法周更班/class_2022_01_1_week/Code02_CatAndMouse.java create mode 100644 算法周更班/class_2022_01_1_week/Code03_MaximumScoreFromPerformingMultiplicationOperations.java create mode 100644 算法周更班/class_2022_01_1_week/Code04_MinDistanceFromLeftUpToRightDownWalk4Directions.java create mode 100644 算法周更班/class_2022_01_1_week/Problem_0913_CatAndMouse.java create mode 100644 算法周更班/class_2022_01_2_week/Code01_StringCounts.java create mode 100644 算法周更班/class_2022_01_2_week/Code02_BrickAll.java create mode 100644 算法周更班/class_2022_01_2_week/Code03_StringNumberConvertBinaryAndHexadecimal.java create mode 100644 算法周更班/class_2022_01_2_week/Code04_MinimumOperationsToMakeTheArrayKIncreasing.java create mode 100644 算法周更班/class_2022_01_2_week/Code05_MagicTowSubarrayMakeMaxSum.java create mode 100644 算法周更班/class_2022_01_2_week/Code06_QuietSum.java create mode 100644 算法周更班/class_2022_01_3_week/Code01_AStarAlgorithm.java create mode 100644 算法周更班/class_2022_01_3_week/Code02_EscapeALargeMaze.java create mode 100644 算法周更班/class_2022_01_3_week/Code03_ShortestSubarrayWithSumAtLeastK.java create mode 100644 算法周更班/class_2022_01_4_week/Code01_BuyThingsAboutCollocation.java create mode 100644 算法周更班/class_2022_01_4_week/Code02_SplitToMArraysMinScore.java create mode 100644 算法周更班/class_2022_01_4_week/Code03_RandomPickWithBlacklist.java create mode 100644 算法周更班/class_2022_01_4_week/Code04_BattleshipsInABoard.java create mode 100644 算法周更班/class_2022_02_2_week/Code01_24Game.java create mode 100644 算法周更班/class_2022_02_2_week/Code02_DesignBitset.java create mode 100644 算法周更班/class_2022_02_2_week/Code03_FindKthSmallestPairDistance.java create mode 100644 算法周更班/class_2022_02_2_week/Code04_ReachingPoints.java create mode 100644 算法周更班/class_2022_02_2_week/Code05_RecoverTheOriginalArray.java create mode 100644 算法周更班/class_2022_02_3_week/Code01_CheapestFlightsWithinKStops.java create mode 100644 算法周更班/class_2022_02_3_week/Code02_MinimumNumberOfDaysToEatNOranges.java create mode 100644 算法周更班/class_2022_02_3_week/Code03_RobotBoundedInCircle.java create mode 100644 算法周更班/class_2022_02_3_week/Code04_MaxTeamNumber.java create mode 100644 算法周更班/class_2022_02_3_week/Code05_StoneGameIX.java create mode 100644 算法周更班/class_2022_02_4_week/Code01_SplitSameNumberWays.java create mode 100644 算法周更班/class_2022_02_4_week/Code02_NearBiggerNoSameNeighbour.java create mode 100644 算法周更班/class_2022_02_4_week/Code03_PartitionArrayForMaximumSum.java create mode 100644 算法周更班/class_2022_02_4_week/Code04_NumberOfDescendingTriples.java create mode 100644 算法周更班/class_2022_02_4_week/Code05_GroupsOfStrings.java create mode 100644 算法周更班/class_2022_03_1_week/Code01_StronglyConnectedComponents.java create mode 100644 算法周更班/class_2022_03_1_week/Code02_NetworkOfSchools.java create mode 100644 算法周更班/class_2022_03_1_week/Code03_PopularCows.java create mode 100644 算法周更班/class_2022_03_1_week/Code04_IgniteMinBombs.java create mode 100644 算法周更班/class_2022_03_2_week/Code01_MeetingCheck.java create mode 100644 算法周更班/class_2022_03_2_week/Code02_StringCheck.java create mode 100644 算法周更班/class_2022_03_2_week/Code03_AiFill.java create mode 100644 算法周更班/class_2022_03_2_week/Code04_SameTeams.java create mode 100644 算法周更班/class_2022_03_2_week/Code05_NumberOfDivisibleByM.java create mode 100644 算法周更班/class_2022_03_2_week/Code06_JobMinDays.java create mode 100644 算法周更班/class_2022_03_2_week/Code07_MinWaitingTime.java create mode 100644 算法周更班/class_2022_03_2_week/Code08_TimeNSpace1LowestCommonAncestor.java create mode 100644 算法周更班/class_2022_03_3_week/Code01_LongestUncontinuousSet.java create mode 100644 算法周更班/class_2022_03_3_week/Code02_CutDouFu.java create mode 100644 算法周更班/class_2022_03_3_week/Code03_MaxSumOnReverseArray.java create mode 100644 算法周更班/class_2022_03_3_week/Code04_ArrangeAddGetMax.java create mode 100644 算法周更班/class_2022_03_3_week/Code05_EatFish.java create mode 100644 算法周更班/class_2022_03_3_week/Code06_FinancialProduct.java create mode 100644 算法周更班/class_2022_03_3_week/Code07_CoopDevelop.java create mode 100644 算法周更班/class_2022_03_4_week/Code01_ArrangeJob.java create mode 100644 算法周更班/class_2022_03_4_week/Code02_BuyGoodsHaveDiscount.java create mode 100644 算法周更班/class_2022_03_4_week/Code03_MinTowNumberSumABS.java create mode 100644 算法周更班/class_2022_03_4_week/Code04_JumpToTargets.java create mode 100644 算法周更班/class_2022_03_4_week/Code05_HowManyWaysFromBottomToTop.java create mode 100644 算法周更班/class_2022_03_4_week/Code06_LongestContinuousTrees.java create mode 100644 算法周更班/class_2022_03_4_week/Code07_IrregularSudoku.java create mode 100644 算法周更班/class_2022_03_4_week/Code08_EggXtoY.java create mode 100644 算法周更班/class_2022_03_5_week/Code01_KMAlgorithm.java create mode 100644 算法周更班/class_2022_03_5_week/Code02_ToAllSpace.java create mode 100644 算法周更班/class_2022_03_5_week/Code03_MaximumAndSumOfArray.java create mode 100644 算法周更班/class_2022_03_5_week/Code04_KillAllSameTime.java create mode 100644 算法周更班/class_2022_04_1_week/Code01_FourNumbersMinusOne.java create mode 100644 算法周更班/class_2022_04_1_week/Code02_MaxOrSmallestSubarray.java create mode 100644 算法周更班/class_2022_04_1_week/Code03_ArrangeMeetingPosCancelPre.java create mode 100644 算法周更班/class_2022_04_1_week/Code04_MaxScoreMoveInBoard.java create mode 100644 算法周更班/class_2022_04_1_week/Code05_PickKnumbersNearTowNumberMaxDiff.java create mode 100644 算法周更班/class_2022_04_1_week/Code06_TopMinSubsquenceSum.java create mode 100644 算法周更班/class_2022_04_1_week/Code07_TopMaxSubsquenceSum.java create mode 100644 算法周更班/class_2022_04_2_week/Code01_SumOfValuesAboutPrimes.java create mode 100644 算法周更班/class_2022_04_2_week/Code02_MinDistanceFromLeftUpToRightDown.java create mode 100644 算法周更班/class_2022_04_2_week/Code03_MaxSumDividedBy7.java create mode 100644 算法周更班/class_2022_04_2_week/Code04_AllJobFinishTime.java create mode 100644 算法周更班/class_2022_04_2_week/Code05_TowLongestSubarraySame01Number.java create mode 100644 算法周更班/class_2022_04_2_week/Code06_PerfectPairNumber.java create mode 100644 算法周更班/class_2022_04_2_week/Code07_MaxMoneyMostMin.java create mode 100644 算法周更班/class_2022_04_3_week/Code01_MaxOneNumbers.java create mode 100644 算法周更班/class_2022_04_3_week/Code02_RMQ.java create mode 100644 算法周更班/class_2022_04_3_week/Code03_ValidSortedArrayWays.java create mode 100644 算法周更班/class_2022_04_3_week/Code04_SumEvenSubNumber.java create mode 100644 算法周更班/class_2022_04_3_week/Code05_ModKSubstringNumbers.java create mode 100644 算法周更班/class_2022_05_1_week/Code01_JumMinSameValue.java create mode 100644 算法周更班/class_2022_05_1_week/Code02_WhoWin21Balls.java create mode 100644 算法周更班/class_2022_05_1_week/Code03_FindDuplicateOnlyOne.java create mode 100644 算法周更班/class_2022_05_1_week/Code04_SumOfQuadraticSum.java create mode 100644 算法周更班/class_2022_05_1_week/Code05_PalindromeStringNoLessKLenNoOverlapingMaxParts.java create mode 100644 算法周更班/class_2022_05_2_week/Code01_TwoObjectMaxValue.java create mode 100644 算法周更班/class_2022_05_2_week/Code02_ModifyOneNumberModXWays.java create mode 100644 算法周更班/class_2022_05_2_week/Code03_SortedSubsequenceMaxSum.java create mode 100644 算法周更班/class_2022_05_2_week/Code04_OneEdgeMagicMinPathSum.java create mode 100644 算法周更班/class_2022_05_2_week/Code05_RedAndWhiteSquares.java create mode 100644 算法周更班/class_2022_05_3_week/Code01_MaxNumberUnderLimit.java create mode 100644 算法周更班/class_2022_05_3_week/Code02_RemoveNumbersNotIncreasingAll.java create mode 100644 算法周更班/class_2022_05_3_week/Code03_NumberOfCannon.java create mode 100644 算法周更班/class_2022_05_3_week/Code04_MinJumpUsePre.java create mode 100644 算法周更班/class_2022_05_4_week/Code01_SomeDPFromVT.java create mode 100644 算法周更班/class_2022_05_4_week/Code02_MinSetForEveryRange.java create mode 100644 算法周更班/class_2022_05_4_week/Code03_MaxIncreasingSubarrayCanDeleteContinuousPart.java create mode 100644 算法周更班/class_2022_05_4_week/Code04_ABCSameNumber.java create mode 100644 算法周更班/class_2022_06_1_week/Code01_WhereWillTheBallFall.java create mode 100644 算法周更班/class_2022_06_1_week/Code02_UniqueSubstringsInWraparoundString.java create mode 100644 算法周更班/class_2022_06_1_week/Code03_NumberOfAtoms.java create mode 100644 算法周更班/class_2022_06_1_week/Code04_SubstringWithLargestVariance.java create mode 100644 算法周更班/class_2022_06_2_week/Code01_MostStonesRemovedWithSameRowOrColumn.java create mode 100644 算法周更班/class_2022_06_2_week/Code02_Solution.HEIC create mode 100644 算法周更班/class_2022_06_2_week/Code02_SumOfTotalStrengthOfWizards.java create mode 100644 算法周更班/class_2022_06_2_week/Code03_NumberOfDifferentSubsequencesGCDs.java create mode 100644 算法周更班/class_2022_06_2_week/Code04_ConsecutiveNumbersSum.java create mode 100644 算法周更班/class_2022_06_3_week/Code01_MaxChunksToMakeSortedII.java create mode 100644 算法周更班/class_2022_06_3_week/Code02_SellingPiecesOfWood.java create mode 100644 算法周更班/class_2022_06_3_week/Code03_RangeModule1.java create mode 100644 算法周更班/class_2022_06_3_week/Code03_RangeModule2.java create mode 100644 算法周更班/class_2022_06_3_week/Code04_StarNumber.java create mode 100644 算法周更班/class_2022_06_4_week/Code01_MinimumWindowSubsequence.java create mode 100644 算法周更班/class_2022_06_4_week/Code02_StackNotSplit.java create mode 100644 算法周更班/class_2022_06_4_week/Code03_MaxAnimalNumber.java create mode 100644 算法周更班/class_2022_06_4_week/Code04_MinimizeMaxDistanceToGasStation.java create mode 100644 算法周更班/class_2022_07_1_week/Code01_WindPrevent.java create mode 100644 算法周更班/class_2022_07_1_week/Code02_MinimumScoreAfterRemovalsOnATree.java create mode 100644 算法周更班/class_2022_07_1_week/Code03_NumberOfPeopleAwareOfASecret.java create mode 100644 算法周更班/class_2022_07_2_week/Code01_DistinctSubseqValue.java create mode 100644 算法周更班/class_2022_07_2_week/Code02_WaysSubsqenceXToY.java create mode 100644 算法周更班/class_2022_07_2_week/Code03_SwimInRisingWater.java create mode 100644 算法周更班/class_2022_07_2_week/Code04_EmployeeFreeTime.java create mode 100644 算法周更班/class_2022_07_2_week/Code05_LineSweepAlgorithm1.java create mode 100644 算法周更班/class_2022_07_2_week/Code05_LineSweepAlgorithm2.java create mode 100644 算法周更班/class_2022_07_3_week/Code01_SetIntersectionSizeAtLeastTwo.java create mode 100644 算法周更班/class_2022_07_3_week/Code02_ValidParenthesisString.java create mode 100644 算法周更班/class_2022_07_3_week/Code03_TopKFrequentElements.java create mode 100644 算法周更班/class_2022_07_3_week/Code04_SpecialBinaryString.java create mode 100644 算法周更班/class_2022_07_4_week/Code01_WaysWiggle.java create mode 100644 算法周更班/class_2022_07_4_week/Code02_SidingPuzzle1.java create mode 100644 算法周更班/class_2022_07_4_week/Code02_SidingPuzzle2.java create mode 100644 算法周更班/class_2022_07_4_week/Code03_TheNumberOfGoodSubsets.java create mode 100644 算法周更班/class_2022_07_4_week/Code04_MatchsticksToSquare.java create mode 100644 算法新手班/class01/Code01_PrintBinary.java create mode 100644 算法新手班/class01/Code02_SumOfFactorial.java create mode 100644 算法新手班/class01/Code03_Sort.java create mode 100644 算法新手班/class01/Code04_SelectionSort.java create mode 100644 算法新手班/class01/Code05_BubbleSort.java create mode 100644 算法新手班/class01/Code06_InsertionSort.java create mode 100644 算法新手班/class02/Code01_PreSum.java create mode 100644 算法新手班/class02/Code02_RandToRand.java create mode 100644 算法新手班/class02/Code03_Comp.java create mode 100644 算法新手班/class02/Code03_EqualProbabilityRandom.java create mode 100644 算法新手班/class03/Code01_BSExist.java create mode 100644 算法新手班/class03/Code02_BSNearLeft.java create mode 100644 算法新手班/class03/Code03_BSNearRight.java create mode 100644 算法新手班/class03/Code04_BSAwesome.java create mode 100644 算法新手班/class03/Code05_HashMapTreeMap.java create mode 100644 算法新手班/class04/Code01_ReverseList.java create mode 100644 算法新手班/class04/Code02_LinkedListToQueueAndStack.java create mode 100644 算法新手班/class04/Code03_DoubleLinkedListToDeque.java create mode 100644 算法新手班/class04/Code04_ReverseNodesInKGroup.java create mode 100644 算法新手班/class04/Code05_AddTwoNumbers.java create mode 100644 算法新手班/class04/Code06_MergeTwoSortedLinkedList.java create mode 100644 算法新手班/class05/Code01_BitMap1.java create mode 100644 算法新手班/class05/Code02_BitMap2.java create mode 100644 算法新手班/class05/Code03_BitAddMinusMultiDiv.java create mode 100644 算法新手班/class06/Code01_MergeKSortedLists.java create mode 100644 算法新手班/class06/Code02_SameTree.java create mode 100644 算法新手班/class06/Code03_SymmetricTree.java create mode 100644 算法新手班/class06/Code04_MaximumDepthOfBinaryTree.java create mode 100644 算法新手班/class06/Code05_ConstructBinaryTreeFromPreorderAndInorderTraversal.java create mode 100644 算法新手班/class06/ShowComparator.java create mode 100644 算法新手班/class06/ShowComparator2.java create mode 100644 算法新手班/class06/TraversalBinaryTree.java create mode 100644 算法新手班/class07/Code01_BinaryTreeLevelOrderTraversalII.java create mode 100644 算法新手班/class07/Code02_BalancedBinaryTree.java create mode 100644 算法新手班/class07/Code03_PathSum.java create mode 100644 算法新手班/class07/Code04_PathSumII.java create mode 100644 算法新手班/class07/Code05_IsBinarySearchTree.java create mode 100644 算法新手班/class08/Code01_GetMax.java create mode 100644 算法新手班/class08/Code02_MergeSort.java create mode 100644 算法新手班/class08/Code03_PartitionAndQuickSort.java diff --git a/体系学习班/class01/Code01_SelectionSort.java b/体系学习班/class01/Code01_SelectionSort.java new file mode 100644 index 0000000..c9b5738 --- /dev/null +++ b/体系学习班/class01/Code01_SelectionSort.java @@ -0,0 +1,115 @@ +package class01; + +import java.util.Arrays; + +public class Code01_SelectionSort { + + public static void selectionSort(int[] arr) { + if (arr == null || arr.length < 2) { + return; + } + // 0 ~ N-1 找到最小值,在哪,放到0位置上 + // 1 ~ n-1 找到最小值,在哪,放到1 位置上 + // 2 ~ n-1 找到最小值,在哪,放到2 位置上 + for (int i = 0; i < arr.length - 1; i++) { + int minIndex = i; + for (int j = i + 1; j < arr.length; j++) { // i ~ N-1 上找最小值的下标 + minIndex = arr[j] < arr[minIndex] ? j : minIndex; + } + swap(arr, i, minIndex); + } + } + + public static void swap(int[] arr, int i, int j) { + int tmp = arr[i]; + arr[i] = arr[j]; + arr[j] = tmp; + } + + // for test + public static void comparator(int[] arr) { + Arrays.sort(arr); + } + + // for test + public static int[] generateRandomArray(int maxSize, int maxValue) { + // Math.random() [0,1) + // Math.random() * N [0,N) + // (int)(Math.random() * N) [0, N-1] + int[] arr = new int[(int) ((maxSize + 1) * Math.random())]; + for (int i = 0; i < arr.length; i++) { + // [-? , +?] + arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random()); + } + return arr; + } + + // for test + public static int[] copyArray(int[] arr) { + if (arr == null) { + return null; + } + int[] res = new int[arr.length]; + for (int i = 0; i < arr.length; i++) { + res[i] = arr[i]; + } + return res; + } + + // for test + public static boolean isEqual(int[] arr1, int[] arr2) { + if ((arr1 == null && arr2 != null) || (arr1 != null && arr2 == null)) { + return false; + } + if (arr1 == null && arr2 == null) { + return true; + } + if (arr1.length != arr2.length) { + return false; + } + for (int i = 0; i < arr1.length; i++) { + if (arr1[i] != arr2[i]) { + return false; + } + } + return true; + } + + // for test + public static void printArray(int[] arr) { + if (arr == null) { + return; + } + for (int i = 0; i < arr.length; i++) { + System.out.print(arr[i] + " "); + } + System.out.println(); + } + + // for test + public static void main(String[] args) { + int testTime = 500000; + int maxSize = 100; + int maxValue = 100; + boolean succeed = true; + for (int i = 0; i < testTime; i++) { + int[] arr1 = generateRandomArray(maxSize, maxValue); + int[] arr2 = copyArray(arr1); + selectionSort(arr1); + comparator(arr2); + if (!isEqual(arr1, arr2)) { + succeed = false; + printArray(arr1); + printArray(arr2); + break; + } + } + System.out.println(succeed ? "Nice!" : "Fucking fucked!"); + + int[] arr = generateRandomArray(maxSize, maxValue); + printArray(arr); + selectionSort(arr); + printArray(arr); + } + +} diff --git a/体系学习班/class01/Code02_BubbleSort.java b/体系学习班/class01/Code02_BubbleSort.java new file mode 100644 index 0000000..14b6e47 --- /dev/null +++ b/体系学习班/class01/Code02_BubbleSort.java @@ -0,0 +1,110 @@ +package class01; + +import java.util.Arrays; + +public class Code02_BubbleSort { + + public static void bubbleSort(int[] arr) { + if (arr == null || arr.length < 2) { + return; + } + // 0 ~ N-1 + // 0 ~ N-2 + // 0 ~ N-3 + for (int e = arr.length - 1; e > 0; e--) { // 0 ~ e + for (int i = 0; i < e; i++) { + if (arr[i] > arr[i + 1]) { + swap(arr, i, i + 1); + } + } + } + } + + // 交换arr的i和j位置上的值 + public static void swap(int[] arr, int i, int j) { + arr[i] = arr[i] ^ arr[j]; + arr[j] = arr[i] ^ arr[j]; + arr[i] = arr[i] ^ arr[j]; + } + + // for test + public static void comparator(int[] arr) { + Arrays.sort(arr); + } + + // for test + public static int[] generateRandomArray(int maxSize, int maxValue) { + int[] arr = new int[(int) ((maxSize + 1) * Math.random())]; + for (int i = 0; i < arr.length; i++) { + arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random()); + } + return arr; + } + + // for test + public static int[] copyArray(int[] arr) { + if (arr == null) { + return null; + } + int[] res = new int[arr.length]; + for (int i = 0; i < arr.length; i++) { + res[i] = arr[i]; + } + return res; + } + + // for test + public static boolean isEqual(int[] arr1, int[] arr2) { + if ((arr1 == null && arr2 != null) || (arr1 != null && arr2 == null)) { + return false; + } + if (arr1 == null && arr2 == null) { + return true; + } + if (arr1.length != arr2.length) { + return false; + } + for (int i = 0; i < arr1.length; i++) { + if (arr1[i] != arr2[i]) { + return false; + } + } + return true; + } + + // for test + public static void printArray(int[] arr) { + if (arr == null) { + return; + } + for (int i = 0; i < arr.length; i++) { + System.out.print(arr[i] + " "); + } + System.out.println(); + } + + // for test + public static void main(String[] args) { + int testTime = 500000; + int maxSize = 100; + int maxValue = 100; + boolean succeed = true; + for (int i = 0; i < testTime; i++) { + int[] arr1 = generateRandomArray(maxSize, maxValue); + int[] arr2 = copyArray(arr1); + bubbleSort(arr1); + comparator(arr2); + if (!isEqual(arr1, arr2)) { + succeed = false; + break; + } + } + System.out.println(succeed ? "Nice!" : "Fucking fucked!"); + + int[] arr = generateRandomArray(maxSize, maxValue); + printArray(arr); + bubbleSort(arr); + printArray(arr); + } + +} diff --git a/体系学习班/class01/Code03_InsertionSort.java b/体系学习班/class01/Code03_InsertionSort.java new file mode 100644 index 0000000..7283369 --- /dev/null +++ b/体系学习班/class01/Code03_InsertionSort.java @@ -0,0 +1,116 @@ +package class01; + +import java.util.Arrays; + +public class Code03_InsertionSort { + + public static void insertionSort(int[] arr) { + if (arr == null || arr.length < 2) { + return; + } + // 不只1个数 + for (int i = 1; i < arr.length; i++) { // 0 ~ i 做到有序 + for (int j = i - 1; j >= 0 && arr[j] > arr[j + 1]; j--) { + swap(arr, j, j + 1); + } + } + } + + // i和j是一个位置的话,会出错 + public static void swap(int[] arr, int i, int j) { + arr[i] = arr[i] ^ arr[j]; + arr[j] = arr[i] ^ arr[j]; + arr[i] = arr[i] ^ arr[j]; + } + + // for test + public static void comparator(int[] arr) { + Arrays.sort(arr); + } + + // for test + public static int[] generateRandomArray(int maxSize, int maxValue) { + // Math.random() -> [0,1) 所有的小数,等概率返回一个 + // Math.random() * N -> [0,N) 所有小数,等概率返回一个 + // (int)(Math.random() * N) -> [0,N-1] 所有的整数,等概率返回一个 + int[] arr = new int[(int) ((maxSize + 1) * Math.random())]; // 长度随机 + for (int i = 0; i < arr.length; i++) { + arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random()); + } + return arr; + } + + // for test + public static int[] copyArray(int[] arr) { + if (arr == null) { + return null; + } + int[] res = new int[arr.length]; + for (int i = 0; i < arr.length; i++) { + res[i] = arr[i]; + } + return res; + } + + // for test + public static boolean isEqual(int[] arr1, int[] arr2) { + if ((arr1 == null && arr2 != null) || (arr1 != null && arr2 == null)) { + return false; + } + if (arr1 == null && arr2 == null) { + return true; + } + if (arr1.length != arr2.length) { + return false; + } + for (int i = 0; i < arr1.length; i++) { + if (arr1[i] != arr2[i]) { + return false; + } + } + return true; + } + + // for test + public static void printArray(int[] arr) { + if (arr == null) { + return; + } + for (int i = 0; i < arr.length; i++) { + System.out.print(arr[i] + " "); + } + System.out.println(); + } + + // for test + public static void main(String[] args) { + int testTime = 500000; + int maxSize = 100; // 随机数组的长度0~100 + int maxValue = 100;// 值:-100~100 + boolean succeed = true; + for (int i = 0; i < testTime; i++) { + int[] arr = generateRandomArray(maxSize, maxValue); + int[] arr1 = copyArray(arr); + int[] arr2 = copyArray(arr); + insertionSort(arr1); + comparator(arr2); + if (!isEqual(arr1, arr2)) { + // 打印arr1 + // 打印arr2 + succeed = false; + for (int j = 0; j < arr.length; j++) { + System.out.print(arr[j] + " "); + } + System.out.println(); + break; + } + } + System.out.println(succeed ? "Nice!" : "Fucking fucked!"); + + int[] arr = generateRandomArray(maxSize, maxValue); + printArray(arr); + insertionSort(arr); + printArray(arr); + } + +} diff --git a/体系学习班/class01/Code04_BSExist.java b/体系学习班/class01/Code04_BSExist.java new file mode 100644 index 0000000..d89be00 --- /dev/null +++ b/体系学习班/class01/Code04_BSExist.java @@ -0,0 +1,65 @@ +package class01; + +import java.util.Arrays; + +public class Code04_BSExist { + + public static boolean exist(int[] sortedArr, int num) { + if (sortedArr == null || sortedArr.length == 0) { + return false; + } + int L = 0; + int R = sortedArr.length - 1; + int mid = 0; + // L..R + while (L < R) { // L..R 至少两个数的时候 + mid = L + ((R - L) >> 1); + if (sortedArr[mid] == num) { + return true; + } else if (sortedArr[mid] > num) { + R = mid - 1; + } else { + L = mid + 1; + } + } + return sortedArr[L] == num; + } + + // for test + public static boolean test(int[] sortedArr, int num) { + for(int cur : sortedArr) { + if(cur == num) { + return true; + } + } + return false; + } + + + // for test + public static int[] generateRandomArray(int maxSize, int maxValue) { + int[] arr = new int[(int) ((maxSize + 1) * Math.random())]; + for (int i = 0; i < arr.length; i++) { + arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random()); + } + return arr; + } + + public static void main(String[] args) { + int testTime = 500000; + int maxSize = 10; + int maxValue = 100; + boolean succeed = true; + for (int i = 0; i < testTime; i++) { + int[] arr = generateRandomArray(maxSize, maxValue); + Arrays.sort(arr); + int value = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random()); + if (test(arr, value) != exist(arr, value)) { + succeed = false; + break; + } + } + System.out.println(succeed ? "Nice!" : "Fucking fucked!"); + } + +} diff --git a/体系学习班/class01/Code05_BSNearLeft.java b/体系学习班/class01/Code05_BSNearLeft.java new file mode 100644 index 0000000..eaf5804 --- /dev/null +++ b/体系学习班/class01/Code05_BSNearLeft.java @@ -0,0 +1,75 @@ +package class01; + +import java.util.Arrays; + +public class Code05_BSNearLeft { + + // 在arr上,找满足>=value的最左位置 + public static int nearestIndex(int[] arr, int value) { + int L = 0; + int R = arr.length - 1; + int index = -1; // 记录最左的对号 + while (L <= R) { // 至少一个数的时候 + int mid = L + ((R - L) >> 1); + if (arr[mid] >= value) { + index = mid; + R = mid - 1; + } else { + L = mid + 1; + } + } + return index; + } + + // for test + public static int test(int[] arr, int value) { + for (int i = 0; i < arr.length; i++) { + if (arr[i] >= value) { + return i; + } + } + return -1; + } + + // for test + public static int[] generateRandomArray(int maxSize, int maxValue) { + int[] arr = new int[(int) ((maxSize + 1) * Math.random())]; + for (int i = 0; i < arr.length; i++) { + arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random()); + } + return arr; + } + + // for test + public static void printArray(int[] arr) { + if (arr == null) { + return; + } + for (int i = 0; i < arr.length; i++) { + System.out.print(arr[i] + " "); + } + System.out.println(); + } + + public static void main(String[] args) { + int testTime = 500000; + int maxSize = 10; + int maxValue = 100; + boolean succeed = true; + for (int i = 0; i < testTime; i++) { + int[] arr = generateRandomArray(maxSize, maxValue); + Arrays.sort(arr); + int value = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random()); + if (test(arr, value) != nearestIndex(arr, value)) { + printArray(arr); + System.out.println(value); + System.out.println(test(arr, value)); + System.out.println(nearestIndex(arr, value)); + succeed = false; + break; + } + } + System.out.println(succeed ? "Nice!" : "Fucking fucked!"); + } + +} diff --git a/体系学习班/class01/Code05_BSNearRight.java b/体系学习班/class01/Code05_BSNearRight.java new file mode 100644 index 0000000..f3b6f25 --- /dev/null +++ b/体系学习班/class01/Code05_BSNearRight.java @@ -0,0 +1,75 @@ +package class01; + +import java.util.Arrays; + +public class Code05_BSNearRight { + + // 在arr上,找满足<=value的最右位置 + public static int nearestIndex(int[] arr, int value) { + int L = 0; + int R = arr.length - 1; + int index = -1; // 记录最右的对号 + while (L <= R) { + int mid = L + ((R - L) >> 1); + if (arr[mid] <= value) { + index = mid; + L = mid + 1; + } else { + R = mid - 1; + } + } + return index; + } + + // for test + public static int test(int[] arr, int value) { + for (int i = arr.length - 1; i >= 0; i--) { + if (arr[i] <= value) { + return i; + } + } + return -1; + } + + // for test + public static int[] generateRandomArray(int maxSize, int maxValue) { + int[] arr = new int[(int) ((maxSize + 1) * Math.random())]; + for (int i = 0; i < arr.length; i++) { + arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random()); + } + return arr; + } + + // for test + public static void printArray(int[] arr) { + if (arr == null) { + return; + } + for (int i = 0; i < arr.length; i++) { + System.out.print(arr[i] + " "); + } + System.out.println(); + } + + public static void main(String[] args) { + int testTime = 500000; + int maxSize = 10; + int maxValue = 100; + boolean succeed = true; + for (int i = 0; i < testTime; i++) { + int[] arr = generateRandomArray(maxSize, maxValue); + Arrays.sort(arr); + int value = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random()); + if (test(arr, value) != nearestIndex(arr, value)) { + printArray(arr); + System.out.println(value); + System.out.println(test(arr, value)); + System.out.println(nearestIndex(arr, value)); + succeed = false; + break; + } + } + System.out.println(succeed ? "Nice!" : "Fucking fucked!"); + } + +} diff --git a/体系学习班/class01/Code06_BSAwesome.java b/体系学习班/class01/Code06_BSAwesome.java new file mode 100644 index 0000000..c167629 --- /dev/null +++ b/体系学习班/class01/Code06_BSAwesome.java @@ -0,0 +1,76 @@ +package class01; + +public class Code06_BSAwesome { + + // 课上的代码 + public static int getLessIndex(int[] arr) { + if (arr == null || arr.length == 0) { + return -1; + } + if (arr.length == 1 || arr[0] < arr[1]) { + return 0; + } + if (arr[arr.length - 1] < arr[arr.length - 2]) { + return arr.length - 1; + } + int left = 1; + int right = arr.length - 2; + int mid = 0; + while (left < right) { + mid = (left + right) / 2; + if (arr[mid] > arr[mid - 1]) { + right = mid - 1; + } else if (arr[mid] > arr[mid + 1]) { + left = mid + 1; + } else { + return mid; + } + } + return left; + } + + // 验证得到的结果,是不是局部最小 + public static boolean isRight(int[] arr, int index) { + if (arr.length <= 1) { + return true; + } + if (index == 0) { + return arr[index] < arr[index + 1]; + } + if (index == arr.length - 1) { + return arr[index] < arr[index - 1]; + } + return arr[index] < arr[index - 1] && arr[index] < arr[index + 1]; + } + + // 为了测试 + // 生成相邻不相等的数组 + public static int[] generateRandomArray(int maxSize, int maxValue) { + int[] arr = new int[(int) (Math.random() * maxSize) + 1]; + arr[0] = (int) (Math.random() * maxValue) - (int) (Math.random() * maxValue); + for (int i = 1; i < arr.length; i++) { + do { + arr[i] = (int) (Math.random() * maxValue) - (int) (Math.random() * maxValue); + } while (arr[i] == arr[i - 1]); + } + return arr; + } + + // 为了测试 + public static void main(String[] args) { + int testTime = 500000; + int maxSize = 30; + int maxValue = 100; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int[] arr = generateRandomArray(maxSize, maxValue); + int ans = getLessIndex(arr); + if (!isRight(arr, ans)) { + System.out.println("出错了!"); + break; + } + } + System.out.println("测试结束"); + } + +} diff --git a/体系学习班/class02/Code01_Swap.java b/体系学习班/class02/Code01_Swap.java new file mode 100644 index 0000000..c625614 --- /dev/null +++ b/体系学习班/class02/Code01_Swap.java @@ -0,0 +1,71 @@ +package class02; + +public class Code01_Swap { + + public static void main(String[] args) { + + + + + + + int a = 16; + int b = 603; + + System.out.println(a); + System.out.println(b); + + + a = a ^ b; + b = a ^ b; + a = a ^ b; + + + System.out.println(a); + System.out.println(b); + + + + + int[] arr = {3,1,100}; + + int i = 0; + int j = 0; + + arr[i] = arr[i] ^ arr[j]; + arr[j] = arr[i] ^ arr[j]; + arr[i] = arr[i] ^ arr[j]; + + System.out.println(arr[i] + " , " + arr[j]); + + + + + + + + + + System.out.println(arr[0]); + System.out.println(arr[2]); + + swap(arr, 0, 0); + + System.out.println(arr[0]); + System.out.println(arr[2]); + + + + } + + + public static void swap (int[] arr, int i, int j) { + // arr[0] = arr[0] ^ arr[0]; + arr[i] = arr[i] ^ arr[j]; + arr[j] = arr[i] ^ arr[j]; + arr[i] = arr[i] ^ arr[j]; + } + + + +} diff --git a/体系学习班/class02/Code02_EvenTimesOddTimes.java b/体系学习班/class02/Code02_EvenTimesOddTimes.java new file mode 100644 index 0000000..13ac877 --- /dev/null +++ b/体系学习班/class02/Code02_EvenTimesOddTimes.java @@ -0,0 +1,83 @@ +package class02; + +public class Code02_EvenTimesOddTimes { + + // arr中,只有一种数,出现奇数次 + public static void printOddTimesNum1(int[] arr) { + int eor = 0; + for (int i = 0; i < arr.length; i++) { + eor ^= arr[i]; + } + System.out.println(eor); + } + + // arr中,有两种数,出现奇数次 + public static void printOddTimesNum2(int[] arr) { + int eor = 0; + for (int i = 0; i < arr.length; i++) { + eor ^= arr[i]; + } + // a 和 b是两种数 + // eor != 0 + // eor最右侧的1,提取出来 + // eor : 00110010110111000 + // rightOne :00000000000001000 + int rightOne = eor & (-eor); // 提取出最右的1 + + + int onlyOne = 0; // eor' + for (int i = 0 ; i < arr.length;i++) { + // arr[1] = 111100011110000 + // rightOne= 000000000010000 + if ((arr[i] & rightOne) != 0) { + onlyOne ^= arr[i]; + } + } + System.out.println(onlyOne + " " + (eor ^ onlyOne)); + } + + + public static int bit1counts(int N) { + int count = 0; + + // 011011010000 + // 000000010000 1 + + // 011011000000 + // + + + + while(N != 0) { + int rightOne = N & ((~N) + 1); + count++; + N ^= rightOne; + // N -= rightOne + } + + + return count; + + } + + + public static void main(String[] args) { + int a = 5; + int b = 7; + + a = a ^ b; + b = a ^ b; + a = a ^ b; + + System.out.println(a); + System.out.println(b); + + int[] arr1 = { 3, 3, 2, 3, 1, 1, 1, 3, 1, 1, 1 }; + printOddTimesNum1(arr1); + + int[] arr2 = { 4, 3, 4, 2, 2, 2, 4, 1, 1, 1, 3, 3, 1, 1, 1, 4, 2, 2 }; + printOddTimesNum2(arr2); + + } + +} diff --git a/体系学习班/class02/Code03_KM.java b/体系学习班/class02/Code03_KM.java new file mode 100644 index 0000000..a530445 --- /dev/null +++ b/体系学习班/class02/Code03_KM.java @@ -0,0 +1,159 @@ +package class02; + +import java.util.HashMap; +import java.util.HashSet; + +// 输入一定能够保证,数组中所有的数都出现了M次,只有一种数出现了K次 +// 1 <= K < M +// 返回这种数 +public class Code03_KM { + + public static int test(int[] arr, int k, int m) { + HashMap map = new HashMap<>(); + for (int num : arr) { + if (map.containsKey(num)) { + map.put(num, map.get(num) + 1); + } else { + map.put(num, 1); + } + } + int ans = 0; + for (int num : map.keySet()) { + if (map.get(num) == k) { + ans = num; + break; + } + } + return ans; + } + + public static HashMap map = new HashMap<>(); + + // 请保证arr中,只有一种数出现了K次,其他数都出现了M次 + public static int onlyKTimes(int[] arr, int k, int m) { + if (map.size() == 0) { + mapCreater(map); + } + int[] t = new int[32]; + // t[0] 0位置的1出现了几个 + // t[i] i位置的1出现了几个 + for (int num : arr) { + while (num != 0) { + int rightOne = num & (-num); + t[map.get(rightOne)]++; + num ^= rightOne; + } + } + int ans = 0; + // 如果这个出现了K次的数,就是0 + // 那么下面代码中的 : ans |= (1 << i); + // 就不会发生 + // 那么ans就会一直维持0,最后返回0,也是对的! + for (int i = 0; i < 32; i++) { + if (t[i] % m != 0) { + ans |= (1 << i); + } + } + return ans; + } + + public static void mapCreater(HashMap map) { + int value = 1; + for (int i = 0; i < 32; i++) { + map.put(value, i); + value <<= 1; + } + } + + // 更简洁的写法 + public static int km(int[] arr, int k, int m) { + int[] help = new int[32]; + for (int num : arr) { + for (int i = 0; i < 32; i++) { + help[i] += (num >> i) & 1; + } + } + int ans = 0; + for (int i = 0; i < 32; i++) { + help[i] %= m; + if (help[i] != 0) { + ans |= 1 << i; + } + } + return ans; + } + + // 为了测试 + public static int[] randomArray(int maxKinds, int range, int k, int m) { + int ktimeNum = randomNumber(range); + // 真命天子出现的次数 + int times = k; + // 2 + int numKinds = (int) (Math.random() * maxKinds) + 2; + // k * 1 + (numKinds - 1) * m + int[] arr = new int[times + (numKinds - 1) * m]; + int index = 0; + for (; index < times; index++) { + arr[index] = ktimeNum; + } + numKinds--; + HashSet set = new HashSet<>(); + set.add(ktimeNum); + while (numKinds != 0) { + int curNum = 0; + do { + curNum = randomNumber(range); + } while (set.contains(curNum)); + set.add(curNum); + numKinds--; + for (int i = 0; i < m; i++) { + arr[index++] = curNum; + } + } + // arr 填好了 + for (int i = 0; i < arr.length; i++) { + // i 位置的数,我想随机和j位置的数做交换 + int j = (int) (Math.random() * arr.length);// 0 ~ N-1 + int tmp = arr[i]; + arr[i] = arr[j]; + arr[j] = tmp; + } + return arr; + } + + // 为了测试 + // [-range, +range] + public static int randomNumber(int range) { + return (int) (Math.random() * (range + 1)) - (int) (Math.random() * (range + 1)); + } + + // 为了测试 + public static void main(String[] args) { + int kinds = 5; + int range = 30; + int testTime = 100000; + int max = 9; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int a = (int) (Math.random() * max) + 1; // a 1 ~ 9 + int b = (int) (Math.random() * max) + 1; // b 1 ~ 9 + int k = Math.min(a, b); + int m = Math.max(a, b); + // k < m + if (k == m) { + m++; + } + int[] arr = randomArray(kinds, range, k, m); + int ans1 = test(arr, k, m); + int ans2 = onlyKTimes(arr, k, m); + int ans3 = km(arr, k, m); + if (ans1 != ans2 || ans1 != ans3) { + System.out.println(ans1); + System.out.println(ans3); + System.out.println("出错了!"); + } + } + System.out.println("测试结束"); + } + +} diff --git a/体系学习班/class03/Code01_ReverseList.java b/体系学习班/class03/Code01_ReverseList.java new file mode 100644 index 0000000..4b0318a --- /dev/null +++ b/体系学习班/class03/Code01_ReverseList.java @@ -0,0 +1,221 @@ +package class03; + +import java.util.ArrayList; +import java.util.List; + +public class Code01_ReverseList { + + public static class Node { + public int value; + public Node next; + + public Node(int data) { + value = data; + } + } + + public static class DoubleNode { + public int value; + public DoubleNode last; + public DoubleNode next; + + public DoubleNode(int data) { + value = data; + } + } + + // head + // a -> b -> c -> null + // c -> b -> a -> null + public static Node reverseLinkedList(Node head) { + Node pre = null; + Node next = null; + while (head != null) { + next = head.next; + head.next = pre; + pre = head; + head = next; + } + return pre; + } + + public static DoubleNode reverseDoubleList(DoubleNode head) { + DoubleNode pre = null; + DoubleNode next = null; + while (head != null) { + next = head.next; + head.next = pre; + head.last = next; + pre = head; + head = next; + } + return pre; + } + + public static Node testReverseLinkedList(Node head) { + if (head == null) { + return null; + } + ArrayList list = new ArrayList<>(); + while (head != null) { + list.add(head); + head = head.next; + } + list.get(0).next = null; + int N = list.size(); + for (int i = 1; i < N; i++) { + list.get(i).next = list.get(i - 1); + } + return list.get(N - 1); + } + + public static DoubleNode testReverseDoubleList(DoubleNode head) { + if (head == null) { + return null; + } + ArrayList list = new ArrayList<>(); + while (head != null) { + list.add(head); + head = head.next; + } + list.get(0).next = null; + DoubleNode pre = list.get(0); + int N = list.size(); + for (int i = 1; i < N; i++) { + DoubleNode cur = list.get(i); + cur.last = null; + cur.next = pre; + pre.last = cur; + pre = cur; + } + return list.get(N - 1); + } + + // for test + public static Node generateRandomLinkedList(int len, int value) { + int size = (int) (Math.random() * (len + 1)); + if (size == 0) { + return null; + } + size--; + Node head = new Node((int) (Math.random() * (value + 1))); + Node pre = head; + while (size != 0) { + Node cur = new Node((int) (Math.random() * (value + 1))); + pre.next = cur; + pre = cur; + size--; + } + return head; + } + + // for test + public static DoubleNode generateRandomDoubleList(int len, int value) { + int size = (int) (Math.random() * (len + 1)); + if (size == 0) { + return null; + } + size--; + DoubleNode head = new DoubleNode((int) (Math.random() * (value + 1))); + DoubleNode pre = head; + while (size != 0) { + DoubleNode cur = new DoubleNode((int) (Math.random() * (value + 1))); + pre.next = cur; + cur.last = pre; + pre = cur; + size--; + } + return head; + } + + // for test + public static List getLinkedListOriginOrder(Node head) { + List ans = new ArrayList<>(); + while (head != null) { + ans.add(head.value); + head = head.next; + } + return ans; + } + + // for test + public static boolean checkLinkedListReverse(List origin, Node head) { + for (int i = origin.size() - 1; i >= 0; i--) { + if (!origin.get(i).equals(head.value)) { + return false; + } + head = head.next; + } + return true; + } + + // for test + public static List getDoubleListOriginOrder(DoubleNode head) { + List ans = new ArrayList<>(); + while (head != null) { + ans.add(head.value); + head = head.next; + } + return ans; + } + + // for test + public static boolean checkDoubleListReverse(List origin, DoubleNode head) { + DoubleNode end = null; + for (int i = origin.size() - 1; i >= 0; i--) { + if (!origin.get(i).equals(head.value)) { + return false; + } + end = head; + head = head.next; + } + for (int i = 0; i < origin.size(); i++) { + if (!origin.get(i).equals(end.value)) { + return false; + } + end = end.last; + } + return true; + } + + // for test + public static void main(String[] args) { + int len = 50; + int value = 100; + int testTime = 100000; + System.out.println("test begin!"); + for (int i = 0; i < testTime; i++) { + Node node1 = generateRandomLinkedList(len, value); + List list1 = getLinkedListOriginOrder(node1); + node1 = reverseLinkedList(node1); + if (!checkLinkedListReverse(list1, node1)) { + System.out.println("Oops1!"); + } + + Node node2 = generateRandomLinkedList(len, value); + List list2 = getLinkedListOriginOrder(node2); + node2 = testReverseLinkedList(node2); + if (!checkLinkedListReverse(list2, node2)) { + System.out.println("Oops2!"); + } + + DoubleNode node3 = generateRandomDoubleList(len, value); + List list3 = getDoubleListOriginOrder(node3); + node3 = reverseDoubleList(node3); + if (!checkDoubleListReverse(list3, node3)) { + System.out.println("Oops3!"); + } + + DoubleNode node4 = generateRandomDoubleList(len, value); + List list4 = getDoubleListOriginOrder(node4); + node4 = reverseDoubleList(node4); + if (!checkDoubleListReverse(list4, node4)) { + System.out.println("Oops4!"); + } + + } + System.out.println("test finish!"); + + } + +} \ No newline at end of file diff --git a/体系学习班/class03/Code02_DeleteGivenValue.java b/体系学习班/class03/Code02_DeleteGivenValue.java new file mode 100644 index 0000000..647047a --- /dev/null +++ b/体系学习班/class03/Code02_DeleteGivenValue.java @@ -0,0 +1,38 @@ +package class03; + +public class Code02_DeleteGivenValue { + + public static class Node { + public int value; + public Node next; + + public Node(int data) { + this.value = data; + } + } + + // head = removeValue(head, 2); + public static Node removeValue(Node head, int num) { + // head来到第一个不需要删的位置 + while (head != null) { + if (head.value != num) { + break; + } + head = head.next; + } + // 1 ) head == null + // 2 ) head != null + Node pre = head; + Node cur = head; + while (cur != null) { + if (cur.value == num) { + pre.next = cur.next; + } else { + pre = cur; + } + cur = cur.next; + } + return head; + } + +} diff --git a/体系学习班/class03/Code03_DoubleEndsQueueToStackAndQueue.java b/体系学习班/class03/Code03_DoubleEndsQueueToStackAndQueue.java new file mode 100644 index 0000000..e9b30a9 --- /dev/null +++ b/体系学习班/class03/Code03_DoubleEndsQueueToStackAndQueue.java @@ -0,0 +1,183 @@ +package class03; + +import java.util.LinkedList; +import java.util.Queue; +import java.util.Stack; + +public class Code03_DoubleEndsQueueToStackAndQueue { + + public static class Node { + public T value; + public Node last; + public Node next; + + public Node(T data) { + value = data; + } + } + + public static class DoubleEndsQueue { + public Node head; + public Node tail; + + public void addFromHead(T value) { + Node cur = new Node(value); + if (head == null) { + head = cur; + tail = cur; + } else { + cur.next = head; + head.last = cur; + head = cur; + } + } + + public void addFromBottom(T value) { + Node cur = new Node(value); + if (head == null) { + head = cur; + tail = cur; + } else { + cur.last = tail; + tail.next = cur; + tail = cur; + } + } + + public T popFromHead() { + if (head == null) { + return null; + } + Node cur = head; + if (head == tail) { + head = null; + tail = null; + } else { + head = head.next; + cur.next = null; + head.last = null; + } + return cur.value; + } + + public T popFromBottom() { + if (head == null) { + return null; + } + Node cur = tail; + if (head == tail) { + head = null; + tail = null; + } else { + tail = tail.last; + tail.next = null; + cur.last = null; + } + return cur.value; + } + + public boolean isEmpty() { + return head == null; + } + + } + + public static class MyStack { + private DoubleEndsQueue queue; + + public MyStack() { + queue = new DoubleEndsQueue(); + } + + public void push(T value) { + queue.addFromHead(value); + } + + public T pop() { + return queue.popFromHead(); + } + + public boolean isEmpty() { + return queue.isEmpty(); + } + + } + + public static class MyQueue { + private DoubleEndsQueue queue; + + public MyQueue() { + queue = new DoubleEndsQueue(); + } + + public void push(T value) { + queue.addFromHead(value); + } + + public T poll() { + return queue.popFromBottom(); + } + + public boolean isEmpty() { + return queue.isEmpty(); + } + + } + + public static boolean isEqual(Integer o1, Integer o2) { + if (o1 == null && o2 != null) { + return false; + } + if (o1 != null && o2 == null) { + return false; + } + if (o1 == null && o2 == null) { + return true; + } + return o1.equals(o2); + } + + public static void main(String[] args) { + int oneTestDataNum = 100; + int value = 10000; + int testTimes = 100000; + for (int i = 0; i < testTimes; i++) { + MyStack myStack = new MyStack<>(); + MyQueue myQueue = new MyQueue<>(); + Stack stack = new Stack<>(); + Queue queue = new LinkedList<>(); + for (int j = 0; j < oneTestDataNum; j++) { + int nums = (int) (Math.random() * value); + if (stack.isEmpty()) { + myStack.push(nums); + stack.push(nums); + } else { + if (Math.random() < 0.5) { + myStack.push(nums); + stack.push(nums); + } else { + if (!isEqual(myStack.pop(), stack.pop())) { + System.out.println("oops!"); + } + } + } + int numq = (int) (Math.random() * value); + if (queue.isEmpty()) { + myQueue.push(numq); + queue.offer(numq); + } else { + if (Math.random() < 0.5) { + myQueue.push(numq); + queue.offer(numq); + } else { + if (!isEqual(myQueue.poll(), queue.poll())) { + System.out.println("oops!"); + } + } + } + } + } + System.out.println("finish!"); + } + +} diff --git a/体系学习班/class03/Code04_RingArray.java b/体系学习班/class03/Code04_RingArray.java new file mode 100644 index 0000000..cad73b4 --- /dev/null +++ b/体系学习班/class03/Code04_RingArray.java @@ -0,0 +1,50 @@ +package class03; + +public class Code04_RingArray { + + public static class MyQueue { + private int[] arr; + private int pushi;// end + private int polli;// begin + private int size; + private final int limit; + + public MyQueue(int limit) { + arr = new int[limit]; + pushi = 0; + polli = 0; + size = 0; + this.limit = limit; + } + + public void push(int value) { + if (size == limit) { + throw new RuntimeException("队列满了,不能再加了"); + } + size++; + arr[pushi] = value; + pushi = nextIndex(pushi); + } + + public int pop() { + if (size == 0) { + throw new RuntimeException("队列空了,不能再拿了"); + } + size--; + int ans = arr[polli]; + polli = nextIndex(polli); + return ans; + } + + public boolean isEmpty() { + return size == 0; + } + + // 如果现在的下标是i,返回下一个位置 + private int nextIndex(int i) { + return i < limit - 1 ? i + 1 : 0; + } + + } + +} diff --git a/体系学习班/class03/Code05_GetMinStack.java b/体系学习班/class03/Code05_GetMinStack.java new file mode 100644 index 0000000..91b20ae --- /dev/null +++ b/体系学习班/class03/Code05_GetMinStack.java @@ -0,0 +1,105 @@ +package class03; + +import java.util.Stack; + +public class Code05_GetMinStack { + + public static class MyStack1 { + private Stack stackData; + private Stack stackMin; + + public MyStack1() { + this.stackData = new Stack(); + this.stackMin = new Stack(); + } + + public void push(int newNum) { + if (this.stackMin.isEmpty()) { + this.stackMin.push(newNum); + } else if (newNum <= this.getmin()) { + this.stackMin.push(newNum); + } + this.stackData.push(newNum); + } + + public int pop() { + if (this.stackData.isEmpty()) { + throw new RuntimeException("Your stack is empty."); + } + int value = this.stackData.pop(); + if (value == this.getmin()) { + this.stackMin.pop(); + } + return value; + } + + public int getmin() { + if (this.stackMin.isEmpty()) { + throw new RuntimeException("Your stack is empty."); + } + return this.stackMin.peek(); + } + } + + public static class MyStack2 { + private Stack stackData; + private Stack stackMin; + + public MyStack2() { + this.stackData = new Stack(); + this.stackMin = new Stack(); + } + + public void push(int newNum) { + if (this.stackMin.isEmpty()) { + this.stackMin.push(newNum); + } else if (newNum < this.getmin()) { + this.stackMin.push(newNum); + } else { + int newMin = this.stackMin.peek(); + this.stackMin.push(newMin); + } + this.stackData.push(newNum); + } + + public int pop() { + if (this.stackData.isEmpty()) { + throw new RuntimeException("Your stack is empty."); + } + this.stackMin.pop(); + return this.stackData.pop(); + } + + public int getmin() { + if (this.stackMin.isEmpty()) { + throw new RuntimeException("Your stack is empty."); + } + return this.stackMin.peek(); + } + } + + public static void main(String[] args) { + MyStack1 stack1 = new MyStack1(); + stack1.push(3); + System.out.println(stack1.getmin()); + stack1.push(4); + System.out.println(stack1.getmin()); + stack1.push(1); + System.out.println(stack1.getmin()); + System.out.println(stack1.pop()); + System.out.println(stack1.getmin()); + + System.out.println("============="); + + MyStack1 stack2 = new MyStack1(); + stack2.push(3); + System.out.println(stack2.getmin()); + stack2.push(4); + System.out.println(stack2.getmin()); + stack2.push(1); + System.out.println(stack2.getmin()); + System.out.println(stack2.pop()); + System.out.println(stack2.getmin()); + } + +} diff --git a/体系学习班/class03/Code06_TwoStacksImplementQueue.java b/体系学习班/class03/Code06_TwoStacksImplementQueue.java new file mode 100644 index 0000000..483d7a7 --- /dev/null +++ b/体系学习班/class03/Code06_TwoStacksImplementQueue.java @@ -0,0 +1,60 @@ +package class03; + +import java.util.Stack; + +public class Code06_TwoStacksImplementQueue { + + public static class TwoStacksQueue { + public Stack stackPush; + public Stack stackPop; + + public TwoStacksQueue() { + stackPush = new Stack(); + stackPop = new Stack(); + } + + // push栈向pop栈倒入数据 + private void pushToPop() { + if (stackPop.empty()) { + while (!stackPush.empty()) { + stackPop.push(stackPush.pop()); + } + } + } + + public void add(int pushInt) { + stackPush.push(pushInt); + pushToPop(); + } + + public int poll() { + if (stackPop.empty() && stackPush.empty()) { + throw new RuntimeException("Queue is empty!"); + } + pushToPop(); + return stackPop.pop(); + } + + public int peek() { + if (stackPop.empty() && stackPush.empty()) { + throw new RuntimeException("Queue is empty!"); + } + pushToPop(); + return stackPop.peek(); + } + } + + public static void main(String[] args) { + TwoStacksQueue test = new TwoStacksQueue(); + test.add(1); + test.add(2); + test.add(3); + System.out.println(test.peek()); + System.out.println(test.poll()); + System.out.println(test.peek()); + System.out.println(test.poll()); + System.out.println(test.peek()); + System.out.println(test.poll()); + } + +} diff --git a/体系学习班/class03/Code07_TwoQueueImplementStack.java b/体系学习班/class03/Code07_TwoQueueImplementStack.java new file mode 100644 index 0000000..a2a3c54 --- /dev/null +++ b/体系学习班/class03/Code07_TwoQueueImplementStack.java @@ -0,0 +1,90 @@ +package class03; + +import java.util.LinkedList; +import java.util.Queue; +import java.util.Stack; + +public class Code07_TwoQueueImplementStack { + + public static class TwoQueueStack { + public Queue queue; + public Queue help; + + public TwoQueueStack() { + queue = new LinkedList<>(); + help = new LinkedList<>(); + } + + public void push(T value) { + queue.offer(value); + } + + public T poll() { + while (queue.size() > 1) { + help.offer(queue.poll()); + } + T ans = queue.poll(); + Queue tmp = queue; + queue = help; + help = tmp; + return ans; + } + + public T peek() { + while (queue.size() > 1) { + help.offer(queue.poll()); + } + T ans = queue.poll(); + help.offer(ans); + Queue tmp = queue; + queue = help; + help = tmp; + return ans; + } + + public boolean isEmpty() { + return queue.isEmpty(); + } + + } + + public static void main(String[] args) { + System.out.println("test begin"); + TwoQueueStack myStack = new TwoQueueStack<>(); + Stack test = new Stack<>(); + int testTime = 1000000; + int max = 1000000; + for (int i = 0; i < testTime; i++) { + if (myStack.isEmpty()) { + if (!test.isEmpty()) { + System.out.println("Oops"); + } + int num = (int) (Math.random() * max); + myStack.push(num); + test.push(num); + } else { + if (Math.random() < 0.25) { + int num = (int) (Math.random() * max); + myStack.push(num); + test.push(num); + } else if (Math.random() < 0.5) { + if (!myStack.peek().equals(test.peek())) { + System.out.println("Oops"); + } + } else if (Math.random() < 0.75) { + if (!myStack.poll().equals(test.pop())) { + System.out.println("Oops"); + } + } else { + if (myStack.isEmpty() != test.isEmpty()) { + System.out.println("Oops"); + } + } + } + } + + System.out.println("test finish!"); + + } + +} diff --git a/体系学习班/class03/Code08_GetMax.java b/体系学习班/class03/Code08_GetMax.java new file mode 100644 index 0000000..81661d8 --- /dev/null +++ b/体系学习班/class03/Code08_GetMax.java @@ -0,0 +1,24 @@ +package class03; + +public class Code08_GetMax { + + // 求arr中的最大值 + public static int getMax(int[] arr) { + return process(arr, 0, arr.length - 1); + } + + // arr[L..R]范围上求最大值 L ... R N + public static int process(int[] arr, int L, int R) { + // arr[L..R]范围上只有一个数,直接返回,base case + if (L == R) { + return arr[L]; + } + // L...R 不只一个数 + // mid = (L + R) / 2 + int mid = L + ((R - L) >> 1); // 中点 1 + int leftMax = process(arr, L, mid); + int rightMax = process(arr, mid + 1, R); + return Math.max(leftMax, rightMax); + } + +} diff --git a/体系学习班/class03/HashMapAndSortedMap.java b/体系学习班/class03/HashMapAndSortedMap.java new file mode 100644 index 0000000..bd54a06 --- /dev/null +++ b/体系学习班/class03/HashMapAndSortedMap.java @@ -0,0 +1,128 @@ +package class03; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.TreeMap; + +public class HashMapAndSortedMap { + + public static class Node { + public int value; + + public Node(int v) { + value = v; + } + } + + public static class Zuo { + public int value; + + public Zuo(int v) { + value = v; + } + } + + public static void main(String[] args) { + + HashMap test = new HashMap<>(); + Integer a = 19000000; + Integer b = 19000000; + System.out.println(a == b); + + test.put(a, "我是3"); + System.out.println(test.containsKey(b)); + + Zuo z1 = new Zuo(1); + Zuo z2 = new Zuo(1); + HashMap test2 = new HashMap<>(); + test2.put(z1, "我是z1"); + System.out.println(test2.containsKey(z2)); + + // UnSortedMap + HashMap map = new HashMap<>(); + map.put(1000000, "我是1000000"); + map.put(2, "我是2"); + map.put(3, "我是3"); + map.put(4, "我是4"); + map.put(5, "我是5"); + map.put(6, "我是6"); + map.put(1000000, "我是1000001"); + + System.out.println(map.containsKey(1)); + System.out.println(map.containsKey(10)); + + System.out.println(map.get(4)); + System.out.println(map.get(10)); + + map.put(4, "他是4"); + System.out.println(map.get(4)); + + map.remove(4); + System.out.println(map.get(4)); + + // key + HashSet set = new HashSet<>(); + set.add("abc"); + set.contains("abc"); + set.remove("abc"); + + // 哈希表,增、删、改、查,在使用时,O(1) + + System.out.println("====================="); + + Integer c = 100000; + Integer d = 100000; + System.out.println(c.equals(d)); + + Integer e = 127; // - 128 ~ 127 + Integer f = 127; + System.out.println(e == f); + + HashMap map2 = new HashMap<>(); + Node node1 = new Node(1); + Node node2 = node1; + map2.put(node1, "我是node1"); + map2.put(node2, "我是node1"); + System.out.println(map2.size()); + + System.out.println("======================"); + + // TreeMap 有序表:接口名 + // 红黑树、avl、sb树、跳表 + // O(logN) + System.out.println("有序表测试开始"); + TreeMap treeMap = new TreeMap<>(); + + treeMap.put(3, "我是3"); + treeMap.put(4, "我是4"); + treeMap.put(8, "我是8"); + treeMap.put(5, "我是5"); + treeMap.put(7, "我是7"); + treeMap.put(1, "我是1"); + treeMap.put(2, "我是2"); + + System.out.println(treeMap.containsKey(1)); + System.out.println(treeMap.containsKey(10)); + + System.out.println(treeMap.get(4)); + System.out.println(treeMap.get(10)); + + treeMap.put(4, "他是4"); + System.out.println(treeMap.get(4)); + + // treeMap.remove(4); + System.out.println(treeMap.get(4)); + + System.out.println("新鲜:"); + + System.out.println(treeMap.firstKey()); + System.out.println(treeMap.lastKey()); + // <= 4 + System.out.println(treeMap.floorKey(4)); + // >= 4 + System.out.println(treeMap.ceilingKey(4)); + // O(logN) + + } + +} diff --git a/体系学习班/class04/Code01_MergeSort.java b/体系学习班/class04/Code01_MergeSort.java new file mode 100644 index 0000000..c0ddb6b --- /dev/null +++ b/体系学习班/class04/Code01_MergeSort.java @@ -0,0 +1,147 @@ +package class04; + +public class Code01_MergeSort { + + // 递归方法实现 + public static void mergeSort1(int[] arr) { + if (arr == null || arr.length < 2) { + return; + } + process(arr, 0, arr.length - 1); + } + + // 请把arr[L..R]排有序 + // l...r N + // T(N) = 2 * T(N / 2) + O(N) + // O(N * logN) + public static void process(int[] arr, int L, int R) { + if (L == R) { // base case + return; + } + int mid = L + ((R - L) >> 1); + process(arr, L, mid); + process(arr, mid + 1, R); + merge(arr, L, mid, R); + } + + public static void merge(int[] arr, int L, int M, int R) { + int[] help = new int[R - L + 1]; + int i = 0; + int p1 = L; + int p2 = M + 1; + while (p1 <= M && p2 <= R) { + help[i++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++]; + } + // 要么p1越界了,要么p2越界了 + while (p1 <= M) { + help[i++] = arr[p1++]; + } + while (p2 <= R) { + help[i++] = arr[p2++]; + } + for (i = 0; i < help.length; i++) { + arr[L + i] = help[i]; + } + } + + // 非递归方法实现 + public static void mergeSort2(int[] arr) { + if (arr == null || arr.length < 2) { + return; + } + int N = arr.length; + // 步长 + int mergeSize = 1; + while (mergeSize < N) { // log N + // 当前左组的,第一个位置 + int L = 0; + while (L < N) { + if (mergeSize >= N - L) { + break; + } + int M = L + mergeSize - 1; + int R = M + Math.min(mergeSize, N - M - 1); + merge(arr, L, M, R); + L = R + 1; + } + // 防止溢出 + if (mergeSize > N / 2) { + break; + } + mergeSize <<= 1; + } + } + + // for test + public static int[] generateRandomArray(int maxSize, int maxValue) { + int[] arr = new int[(int) ((maxSize + 1) * Math.random())]; + for (int i = 0; i < arr.length; i++) { + arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random()); + } + return arr; + } + + // for test + public static int[] copyArray(int[] arr) { + if (arr == null) { + return null; + } + int[] res = new int[arr.length]; + for (int i = 0; i < arr.length; i++) { + res[i] = arr[i]; + } + return res; + } + + // for test + public static boolean isEqual(int[] arr1, int[] arr2) { + if ((arr1 == null && arr2 != null) || (arr1 != null && arr2 == null)) { + return false; + } + if (arr1 == null && arr2 == null) { + return true; + } + if (arr1.length != arr2.length) { + return false; + } + for (int i = 0; i < arr1.length; i++) { + if (arr1[i] != arr2[i]) { + return false; + } + } + return true; + } + + // for test + public static void printArray(int[] arr) { + if (arr == null) { + return; + } + for (int i = 0; i < arr.length; i++) { + System.out.print(arr[i] + " "); + } + System.out.println(); + } + + // for test + public static void main(String[] args) { + int testTime = 500000; + int maxSize = 100; + int maxValue = 100; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int[] arr1 = generateRandomArray(maxSize, maxValue); + int[] arr2 = copyArray(arr1); + mergeSort1(arr1); + mergeSort2(arr2); + if (!isEqual(arr1, arr2)) { + System.out.println("出错了!"); + printArray(arr1); + printArray(arr2); + break; + } + } + System.out.println("测试结束"); + } + +} diff --git a/体系学习班/class04/Code02_SmallSum.java b/体系学习班/class04/Code02_SmallSum.java new file mode 100644 index 0000000..1167a64 --- /dev/null +++ b/体系学习班/class04/Code02_SmallSum.java @@ -0,0 +1,137 @@ +package class04; + +public class Code02_SmallSum { + + public static int smallSum(int[] arr) { + if (arr == null || arr.length < 2) { + return 0; + } + return process(arr, 0, arr.length - 1); + } + + // arr[L..R]既要排好序,也要求小和返回 + // 所有merge时,产生的小和,累加 + // 左 排序 merge + // 右 排序 merge + // merge + public static int process(int[] arr, int l, int r) { + if (l == r) { + return 0; + } + // l < r + int mid = l + ((r - l) >> 1); + return + process(arr, l, mid) + + + process(arr, mid + 1, r) + + + merge(arr, l, mid, r); + } + + public static int merge(int[] arr, int L, int m, int r) { + int[] help = new int[r - L + 1]; + int i = 0; + int p1 = L; + int p2 = m + 1; + int res = 0; + while (p1 <= m && p2 <= r) { + res += arr[p1] < arr[p2] ? (r - p2 + 1) * arr[p1] : 0; + help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++]; + } + while (p1 <= m) { + help[i++] = arr[p1++]; + } + while (p2 <= r) { + help[i++] = arr[p2++]; + } + for (i = 0; i < help.length; i++) { + arr[L + i] = help[i]; + } + return res; + } + + // for test + public static int comparator(int[] arr) { + if (arr == null || arr.length < 2) { + return 0; + } + int res = 0; + for (int i = 1; i < arr.length; i++) { + for (int j = 0; j < i; j++) { + res += arr[j] < arr[i] ? arr[j] : 0; + } + } + return res; + } + + // for test + public static int[] generateRandomArray(int maxSize, int maxValue) { + int[] arr = new int[(int) ((maxSize + 1) * Math.random())]; + for (int i = 0; i < arr.length; i++) { + arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random()); + } + return arr; + } + + // for test + public static int[] copyArray(int[] arr) { + if (arr == null) { + return null; + } + int[] res = new int[arr.length]; + for (int i = 0; i < arr.length; i++) { + res[i] = arr[i]; + } + return res; + } + + // for test + public static boolean isEqual(int[] arr1, int[] arr2) { + if ((arr1 == null && arr2 != null) || (arr1 != null && arr2 == null)) { + return false; + } + if (arr1 == null && arr2 == null) { + return true; + } + if (arr1.length != arr2.length) { + return false; + } + for (int i = 0; i < arr1.length; i++) { + if (arr1[i] != arr2[i]) { + return false; + } + } + return true; + } + + // for test + public static void printArray(int[] arr) { + if (arr == null) { + return; + } + for (int i = 0; i < arr.length; i++) { + System.out.print(arr[i] + " "); + } + System.out.println(); + } + + // for test + public static void main(String[] args) { + int testTime = 500000; + int maxSize = 100; + int maxValue = 100; + boolean succeed = true; + for (int i = 0; i < testTime; i++) { + int[] arr1 = generateRandomArray(maxSize, maxValue); + int[] arr2 = copyArray(arr1); + if (smallSum(arr1) != comparator(arr2)) { + succeed = false; + printArray(arr1); + printArray(arr2); + break; + } + } + System.out.println(succeed ? "Nice!" : "Fucking fucked!"); + } + +} diff --git a/体系学习班/class04/Code03_ReversePair.java b/体系学习班/class04/Code03_ReversePair.java new file mode 100644 index 0000000..0e6e531 --- /dev/null +++ b/体系学习班/class04/Code03_ReversePair.java @@ -0,0 +1,130 @@ +package class04; + +public class Code03_ReversePair { + + public static int reverPairNumber(int[] arr) { + if (arr == null || arr.length < 2) { + return 0; + } + return process(arr, 0, arr.length - 1); + } + + // arr[L..R]既要排好序,也要求逆序对数量返回 + // 所有merge时,产生的逆序对数量,累加,返回 + // 左 排序 merge并产生逆序对数量 + // 右 排序 merge并产生逆序对数量 + public static int process(int[] arr, int l, int r) { + if (l == r) { + return 0; + } + // l < r + int mid = l + ((r - l) >> 1); + return process(arr, l, mid) + process(arr, mid + 1, r) + merge(arr, l, mid, r); + } + + public static int merge(int[] arr, int L, int m, int r) { + int[] help = new int[r - L + 1]; + int i = help.length - 1; + int p1 = m; + int p2 = r; + int res = 0; + while (p1 >= L && p2 > m) { + res += arr[p1] > arr[p2] ? (p2 - m) : 0; + help[i--] = arr[p1] > arr[p2] ? arr[p1--] : arr[p2--]; + } + while (p1 >= L) { + help[i--] = arr[p1--]; + } + while (p2 > m) { + help[i--] = arr[p2--]; + } + for (i = 0; i < help.length; i++) { + arr[L + i] = help[i]; + } + return res; + } + + // for test + public static int comparator(int[] arr) { + int ans = 0; + for (int i = 0; i < arr.length; i++) { + for (int j = i + 1; j < arr.length; j++) { + if (arr[i] > arr[j]) { + ans++; + } + } + } + return ans; + } + + // for test + public static int[] generateRandomArray(int maxSize, int maxValue) { + int[] arr = new int[(int) ((maxSize + 1) * Math.random())]; + for (int i = 0; i < arr.length; i++) { + arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random()); + } + return arr; + } + + // for test + public static int[] copyArray(int[] arr) { + if (arr == null) { + return null; + } + int[] res = new int[arr.length]; + for (int i = 0; i < arr.length; i++) { + res[i] = arr[i]; + } + return res; + } + + // for test + public static boolean isEqual(int[] arr1, int[] arr2) { + if ((arr1 == null && arr2 != null) || (arr1 != null && arr2 == null)) { + return false; + } + if (arr1 == null && arr2 == null) { + return true; + } + if (arr1.length != arr2.length) { + return false; + } + for (int i = 0; i < arr1.length; i++) { + if (arr1[i] != arr2[i]) { + return false; + } + } + return true; + } + + // for test + public static void printArray(int[] arr) { + if (arr == null) { + return; + } + for (int i = 0; i < arr.length; i++) { + System.out.print(arr[i] + " "); + } + System.out.println(); + } + + // for test + public static void main(String[] args) { + int testTime = 500000; + int maxSize = 100; + int maxValue = 100; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int[] arr1 = generateRandomArray(maxSize, maxValue); + int[] arr2 = copyArray(arr1); + if (reverPairNumber(arr1) != comparator(arr2)) { + System.out.println("Oops!"); + printArray(arr1); + printArray(arr2); + break; + } + } + System.out.println("测试结束"); + } + +} diff --git a/体系学习班/class04/Code04_BiggerThanRightTwice.java b/体系学习班/class04/Code04_BiggerThanRightTwice.java new file mode 100644 index 0000000..7eb4f7e --- /dev/null +++ b/体系学习班/class04/Code04_BiggerThanRightTwice.java @@ -0,0 +1,135 @@ +package class04; + +// 本题测试链接 : https://leetcode.com/problems/reverse-pairs/ +public class Code04_BiggerThanRightTwice { + + public static int reversePairs(int[] arr) { + if (arr == null || arr.length < 2) { + return 0; + } + return process(arr, 0, arr.length - 1); + } + + public static int process(int[] arr, int l, int r) { + if (l == r) { + return 0; + } + // l < r + int mid = l + ((r - l) >> 1); + return process(arr, l, mid) + process(arr, mid + 1, r) + merge(arr, l, mid, r); + } + + public static int merge(int[] arr, int L, int m, int r) { + // [L....M] [M+1....R] + int ans = 0; + // 目前囊括进来的数,是从[M+1, windowR) + int windowR = m + 1; + for (int i = L; i <= m; i++) { + while (windowR <= r && (long) arr[i] > (long) arr[windowR] * 2) { + windowR++; + } + ans += windowR - m - 1; + } + int[] help = new int[r - L + 1]; + int i = 0; + int p1 = L; + int p2 = m + 1; + while (p1 <= m && p2 <= r) { + help[i++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++]; + } + while (p1 <= m) { + help[i++] = arr[p1++]; + } + while (p2 <= r) { + help[i++] = arr[p2++]; + } + for (i = 0; i < help.length; i++) { + arr[L + i] = help[i]; + } + return ans; + } + + // for test + public static int comparator(int[] arr) { + int ans = 0; + for (int i = 0; i < arr.length; i++) { + for (int j = i + 1; j < arr.length; j++) { + if (arr[i] > (arr[j] << 1)) { + ans++; + } + } + } + return ans; + } + + // for test + public static int[] generateRandomArray(int maxSize, int maxValue) { + int[] arr = new int[(int) ((maxSize + 1) * Math.random())]; + for (int i = 0; i < arr.length; i++) { + arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) ((maxValue + 1) * Math.random()); + } + return arr; + } + + // for test + public static int[] copyArray(int[] arr) { + if (arr == null) { + return null; + } + int[] res = new int[arr.length]; + for (int i = 0; i < arr.length; i++) { + res[i] = arr[i]; + } + return res; + } + + // for test + public static boolean isEqual(int[] arr1, int[] arr2) { + if ((arr1 == null && arr2 != null) || (arr1 != null && arr2 == null)) { + return false; + } + if (arr1 == null && arr2 == null) { + return true; + } + if (arr1.length != arr2.length) { + return false; + } + for (int i = 0; i < arr1.length; i++) { + if (arr1[i] != arr2[i]) { + return false; + } + } + return true; + } + + // for test + public static void printArray(int[] arr) { + if (arr == null) { + return; + } + for (int i = 0; i < arr.length; i++) { + System.out.print(arr[i] + " "); + } + System.out.println(); + } + + // for test + public static void main(String[] args) { + int testTime = 500000; + int maxSize = 100; + int maxValue = 100; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int[] arr1 = generateRandomArray(maxSize, maxValue); + int[] arr2 = copyArray(arr1); + if (reversePairs(arr1) != comparator(arr2)) { + System.out.println("Oops!"); + printArray(arr1); + printArray(arr2); + break; + } + } + System.out.println("测试结束"); + } + +} diff --git a/体系学习班/class05/Code01_CountOfRangeSum.java b/体系学习班/class05/Code01_CountOfRangeSum.java new file mode 100644 index 0000000..559a7d8 --- /dev/null +++ b/体系学习班/class05/Code01_CountOfRangeSum.java @@ -0,0 +1,63 @@ +package class05; + +// 这道题直接在leetcode测评: +// https://leetcode.com/problems/count-of-range-sum/ +public class Code01_CountOfRangeSum { + + public static int countRangeSum(int[] nums, int lower, int upper) { + if (nums == null || nums.length == 0) { + return 0; + } + long[] sum = new long[nums.length]; + sum[0] = nums[0]; + for (int i = 1; i < nums.length; i++) { + sum[i] = sum[i - 1] + nums[i]; + } + return process(sum, 0, sum.length - 1, lower, upper); + } + + public static int process(long[] sum, int L, int R, int lower, int upper) { + if (L == R) { + return sum[L] >= lower && sum[L] <= upper ? 1 : 0; + } + int M = L + ((R - L) >> 1); + return process(sum, L, M, lower, upper) + process(sum, M + 1, R, lower, upper) + + merge(sum, L, M, R, lower, upper); + } + + public static int merge(long[] arr, int L, int M, int R, int lower, int upper) { + int ans = 0; + int windowL = L; + int windowR = L; + // [windowL, windowR) + for (int i = M + 1; i <= R; i++) { + long min = arr[i] - upper; + long max = arr[i] - lower; + while (windowR <= M && arr[windowR] <= max) { + windowR++; + } + while (windowL <= M && arr[windowL] < min) { + windowL++; + } + ans += windowR - windowL; + } + long[] help = new long[R - L + 1]; + int i = 0; + int p1 = L; + int p2 = M + 1; + while (p1 <= M && p2 <= R) { + help[i++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++]; + } + while (p1 <= M) { + help[i++] = arr[p1++]; + } + while (p2 <= R) { + help[i++] = arr[p2++]; + } + for (i = 0; i < help.length; i++) { + arr[L + i] = help[i]; + } + return ans; + } + +} diff --git a/体系学习班/class05/Code02_PartitionAndQuickSort.java b/体系学习班/class05/Code02_PartitionAndQuickSort.java new file mode 100644 index 0000000..e8a9d7a --- /dev/null +++ b/体系学习班/class05/Code02_PartitionAndQuickSort.java @@ -0,0 +1,197 @@ +package class05; + +public class Code02_PartitionAndQuickSort { + + public static void swap(int[] arr, int i, int j) { + int tmp = arr[i]; + arr[i] = arr[j]; + arr[j] = tmp; + } + + // arr[L..R]上,以arr[R]位置的数做划分值 + // <= X > X + // <= X X + public static int partition(int[] arr, int L, int R) { + if (L > R) { + return -1; + } + if (L == R) { + return L; + } + int lessEqual = L - 1; + int index = L; + while (index < R) { + if (arr[index] <= arr[R]) { + swap(arr, index, ++lessEqual); + } + index++; + } + swap(arr, ++lessEqual, R); + return lessEqual; + } + + // arr[L...R] 玩荷兰国旗问题的划分,以arr[R]做划分值 + // arr[R] + public static int[] netherlandsFlag(int[] arr, int L, int R) { + if (L > R) { // L...R L>R + return new int[] { -1, -1 }; + } + if (L == R) { + return new int[] { L, R }; + } + int less = L - 1; // < 区 右边界 + int more = R; // > 区 左边界 + int index = L; + while (index < more) { // 当前位置,不能和 >区的左边界撞上 + if (arr[index] == arr[R]) { + index++; + } else if (arr[index] < arr[R]) { +// swap(arr, less + 1, index); +// less++; +// index++; + swap(arr, index++, ++less); + } else { // > + swap(arr, index, --more); + } + } + swap(arr, more, R); // <[R] =[R] >[R] + return new int[] { less + 1, more }; + } + + public static void quickSort1(int[] arr) { + if (arr == null || arr.length < 2) { + return; + } + process1(arr, 0, arr.length - 1); + } + + public static void process1(int[] arr, int L, int R) { + if (L >= R) { + return; + } + // L..R partition arr[R] [ <=arr[R] arr[R] >arr[R] ] + int M = partition(arr, L, R); + process1(arr, L, M - 1); + process1(arr, M + 1, R); + } + + + + + + + public static void quickSort2(int[] arr) { + if (arr == null || arr.length < 2) { + return; + } + process2(arr, 0, arr.length - 1); + } + + // arr[L...R] 排有序,快排2.0方式 + public static void process2(int[] arr, int L, int R) { + if (L >= R) { + return; + } + // [ equalArea[0] , equalArea[0]] + int[] equalArea = netherlandsFlag(arr, L, R); + process2(arr, L, equalArea[0] - 1); + process2(arr, equalArea[1] + 1, R); + } + + + + + + + + public static void quickSort3(int[] arr) { + if (arr == null || arr.length < 2) { + return; + } + process3(arr, 0, arr.length - 1); + } + + public static void process3(int[] arr, int L, int R) { + if (L >= R) { + return; + } + swap(arr, L + (int) (Math.random() * (R - L + 1)), R); + int[] equalArea = netherlandsFlag(arr, L, R); + process3(arr, L, equalArea[0] - 1); + process3(arr, equalArea[1] + 1, R); + } + + // for test + public static int[] generateRandomArray(int maxSize, int maxValue) { + int[] arr = new int[(int) ((maxSize + 1) * Math.random())]; + for (int i = 0; i < arr.length; i++) { + arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random()); + } + return arr; + } + + // for test + public static int[] copyArray(int[] arr) { + if (arr == null) { + return null; + } + int[] res = new int[arr.length]; + for (int i = 0; i < arr.length; i++) { + res[i] = arr[i]; + } + return res; + } + + // for test + public static boolean isEqual(int[] arr1, int[] arr2) { + if ((arr1 == null && arr2 != null) || (arr1 != null && arr2 == null)) { + return false; + } + if (arr1 == null && arr2 == null) { + return true; + } + if (arr1.length != arr2.length) { + return false; + } + for (int i = 0; i < arr1.length; i++) { + if (arr1[i] != arr2[i]) { + return false; + } + } + return true; + } + + // for test + public static void printArray(int[] arr) { + if (arr == null) { + return; + } + for (int i = 0; i < arr.length; i++) { + System.out.print(arr[i] + " "); + } + System.out.println(); + } + + // for test + public static void main(String[] args) { + int testTime = 500000; + int maxSize = 100; + int maxValue = 100; + boolean succeed = true; + for (int i = 0; i < testTime; i++) { + int[] arr1 = generateRandomArray(maxSize, maxValue); + int[] arr2 = copyArray(arr1); + int[] arr3 = copyArray(arr1); + quickSort1(arr1); + quickSort2(arr2); + quickSort3(arr3); + if (!isEqual(arr1, arr2) || !isEqual(arr2, arr3)) { + succeed = false; + break; + } + } + System.out.println(succeed ? "Nice!" : "Oops!"); + + } + +} diff --git a/体系学习班/class05/Code03_QuickSortRecursiveAndUnrecursive.java b/体系学习班/class05/Code03_QuickSortRecursiveAndUnrecursive.java new file mode 100644 index 0000000..57a7657 --- /dev/null +++ b/体系学习班/class05/Code03_QuickSortRecursiveAndUnrecursive.java @@ -0,0 +1,195 @@ +package class05; + +import java.util.LinkedList; +import java.util.Queue; +import java.util.Stack; + +public class Code03_QuickSortRecursiveAndUnrecursive { + + // 荷兰国旗问题 + public static int[] netherlandsFlag(int[] arr, int L, int R) { + if (L > R) { + return new int[] { -1, -1 }; + } + if (L == R) { + return new int[] { L, R }; + } + int less = L - 1; + int more = R; + int index = L; + while (index < more) { + if (arr[index] == arr[R]) { + index++; + } else if (arr[index] < arr[R]) { + swap(arr, index++, ++less); + } else { + swap(arr, index, --more); + } + } + swap(arr, more, R); + return new int[] { less + 1, more }; + } + + public static void swap(int[] arr, int i, int j) { + int tmp = arr[i]; + arr[i] = arr[j]; + arr[j] = tmp; + } + + // 快排递归版本 + public static void quickSort1(int[] arr) { + if (arr == null || arr.length < 2) { + return; + } + process(arr, 0, arr.length - 1); + } + + public static void process(int[] arr, int L, int R) { + if (L >= R) { + return; + } + swap(arr, L + (int) (Math.random() * (R - L + 1)), R); + int[] equalArea = netherlandsFlag(arr, L, R); + process(arr, L, equalArea[0] - 1); + process(arr, equalArea[1] + 1, R); + } + + // 快排非递归版本需要的辅助类 + // 要处理的是什么范围上的排序 + public static class Op { + public int l; + public int r; + + public Op(int left, int right) { + l = left; + r = right; + } + } + + // 快排3.0 非递归版本 用栈来执行 + public static void quickSort2(int[] arr) { + if (arr == null || arr.length < 2) { + return; + } + int N = arr.length; + swap(arr, (int) (Math.random() * N), N - 1); + int[] equalArea = netherlandsFlag(arr, 0, N - 1); + int el = equalArea[0]; + int er = equalArea[1]; + Stack stack = new Stack<>(); + stack.push(new Op(0, el - 1)); + stack.push(new Op(er + 1, N - 1)); + while (!stack.isEmpty()) { + Op op = stack.pop(); // op.l ... op.r + if (op.l < op.r) { + swap(arr, op.l + (int) (Math.random() * (op.r - op.l + 1)), op.r); + equalArea = netherlandsFlag(arr, op.l, op.r); + el = equalArea[0]; + er = equalArea[1]; + stack.push(new Op(op.l, el - 1)); + stack.push(new Op(er + 1, op.r)); + } + } + } + + // 快排3.0 非递归版本 用队列来执行 + public static void quickSort3(int[] arr) { + if (arr == null || arr.length < 2) { + return; + } + int N = arr.length; + swap(arr, (int) (Math.random() * N), N - 1); + int[] equalArea = netherlandsFlag(arr, 0, N - 1); + int el = equalArea[0]; + int er = equalArea[1]; + Queue queue = new LinkedList<>(); + queue.offer(new Op(0, el - 1)); + queue.offer(new Op(er + 1, N - 1)); + while (!queue.isEmpty()) { + Op op = queue.poll(); + if (op.l < op.r) { + swap(arr, op.l + (int) (Math.random() * (op.r - op.l + 1)), op.r); + equalArea = netherlandsFlag(arr, op.l, op.r); + el = equalArea[0]; + er = equalArea[1]; + queue.offer(new Op(op.l, el - 1)); + queue.offer(new Op(er + 1, op.r)); + } + } + } + + // 生成随机数组(用于测试) + public static int[] generateRandomArray(int maxSize, int maxValue) { + int[] arr = new int[(int) ((maxSize + 1) * Math.random())]; + for (int i = 0; i < arr.length; i++) { + arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random()); + } + return arr; + } + + // 拷贝数组(用于测试) + public static int[] copyArray(int[] arr) { + if (arr == null) { + return null; + } + int[] res = new int[arr.length]; + for (int i = 0; i < arr.length; i++) { + res[i] = arr[i]; + } + return res; + } + + // 对比两个数组(用于测试) + public static boolean isEqual(int[] arr1, int[] arr2) { + if ((arr1 == null && arr2 != null) || (arr1 != null && arr2 == null)) { + return false; + } + if (arr1 == null && arr2 == null) { + return true; + } + if (arr1.length != arr2.length) { + return false; + } + for (int i = 0; i < arr1.length; i++) { + if (arr1[i] != arr2[i]) { + return false; + } + } + return true; + } + + // 打印数组(用于测试) + public static void printArray(int[] arr) { + if (arr == null) { + return; + } + for (int i = 0; i < arr.length; i++) { + System.out.print(arr[i] + " "); + } + System.out.println(); + } + + // 跑大样本随机测试(对数器) + public static void main(String[] args) { + int testTime = 500000; + int maxSize = 100; + int maxValue = 100; + boolean succeed = true; + System.out.println("test begin"); + for (int i = 0; i < testTime; i++) { + int[] arr1 = generateRandomArray(maxSize, maxValue); + int[] arr2 = copyArray(arr1); + int[] arr3 = copyArray(arr1); + quickSort1(arr1); + quickSort2(arr2); + quickSort3(arr3); + if (!isEqual(arr1, arr2) || !isEqual(arr1, arr3)) { + succeed = false; + break; + } + } + System.out.println("test end"); + System.out.println("测试" + testTime + "组是否全部通过:" + (succeed ? "是" : "否")); + } + +} diff --git a/体系学习班/class05/Code04_DoubleLinkedListQuickSort.java b/体系学习班/class05/Code04_DoubleLinkedListQuickSort.java new file mode 100644 index 0000000..a3014dc --- /dev/null +++ b/体系学习班/class05/Code04_DoubleLinkedListQuickSort.java @@ -0,0 +1,300 @@ +package class05; + +import java.util.ArrayList; +import java.util.Comparator; + +// 双向链表的随机快速排序 +// 课上没有讲,因为这是群里同学问的问题 +// 作为补充放在这,有需要的同学可以看看 +// 和课上讲的数组的经典快速排序在算法上没有区别 +// 但是coding需要更小心 +public class Code04_DoubleLinkedListQuickSort { + + public static class Node { + public int value; + public Node last; + public Node next; + + public Node(int v) { + value = v; + } + } + + public static Node quickSort(Node h) { + if (h == null) { + return null; + } + int N = 0; + Node c = h; + Node e = null; + while (c != null) { + N++; + e = c; + c = c.next; + } + return process(h, e, N).h; + } + + public static class HeadTail { + public Node h; + public Node t; + + public HeadTail(Node head, Node tail) { + h = head; + t = tail; + } + } + + // L...R是一个双向链表的头和尾, + // L的last指针指向null,R的next指针指向null + // 也就是说L的左边没有,R的右边也没节点 + // 就是一个正常的双向链表,一共有N个节点 + // 将这一段用随机快排的方式排好序 + // 返回排好序之后的双向链表的头和尾(HeadTail) + public static HeadTail process(Node L, Node R, int N) { + if (L == null) { + return null; + } + if (L == R) { + return new HeadTail(L, R); + } + // L..R上不只一个节点 + // 随机得到一个随机下标 + int randomIndex = (int) (Math.random() * N); + // 根据随机下标得到随机节点 + Node randomNode = L; + while (randomIndex-- != 0) { + randomNode = randomNode.next; + } + // 把随机节点从原来的环境里分离出来 + // 比如 a(L) -> b -> c -> d(R), 如果randomNode = c,那么调整之后 + // a(L) -> b -> d(R), c会被挖出来,randomNode = c + if (randomNode == L || randomNode == R) { + if (randomNode == L) { + L = randomNode.next; + L.last = null; + } else { + randomNode.last.next = null; + } + } else { // randomNode一定是中间的节点 + randomNode.last.next = randomNode.next; + randomNode.next.last = randomNode.last; + } + randomNode.last = null; + randomNode.next = null; + Info info = partition(L, randomNode); + // randomNode的部分去排序 + HeadTail rht = process(info.rh, info.rt, info.rs); + // 左部分排好序、右部分排好序 + // 把它们串在一起 + if (lht != null) { + lht.t.next = info.eh; + info.eh.last = lht.t; + } + if (rht != null) { + info.et.next = rht.h; + rht.h.last = info.et; + } + // 返回排好序之后总的头和总的尾 + Node h = lht != null ? lht.h : info.eh; + Node t = rht != null ? rht.t : info.et; + return new HeadTail(h, t); + } + + public static class Info { + public Node lh; + public Node lt; + public int ls; + public Node rh; + public Node rt; + public int rs; + public Node eh; + public Node et; + + public Info(Node lH, Node lT, int lS, Node rH, Node rT, int rS, Node eH, Node eT) { + lh = lH; + lt = lT; + ls = lS; + rh = rH; + rt = rT; + rs = rS; + eh = eH; + et = eT; + } + } + + // (L....一直到空),是一个双向链表 + // pivot是一个不在(L....一直到空)的独立节点,它作为划分值 + // 根据荷兰国旗问题的划分方式,把(L....一直到空)划分成: + // pivot 三个部分,然后把pivot融进=pivot的部分 + // 比如 4(L)->6->7->1->5->0->9->null pivot=5(这个5和链表中的5,是不同的节点) + // 调整完成后: + // 4->1->0 小于的部分 + // 5->5 等于的部分 + // 6->7->9 大于的部分 + // 三个部分是断开的 + // 然后返回Info: + // 小于部分的头、尾、节点个数 : lh,lt,ls + // 大于部分的头、尾、节点个数 : rh,rt,rs + // 等于部分的头、尾 : eh,et + public static Info partition(Node L, Node pivot) { + Node lh = null; + Node lt = null; + int ls = 0; + Node rh = null; + Node rt = null; + int rs = 0; + Node eh = pivot; + Node et = pivot; + Node tmp = null; + while (L != null) { + tmp = L.next; + L.next = null; + L.last = null; + if (L.value < pivot.value) { + ls++; + if (lh == null) { + lh = L; + lt = L; + } else { + lt.next = L; + L.last = lt; + lt = L; + } + } else if (L.value > pivot.value) { + rs++; + if (rh == null) { + rh = L; + rt = L; + } else { + rt.next = L; + L.last = rt; + rt = L; + } + } else { + et.next = L; + L.last = et; + et = L; + } + L = tmp; + } + return new Info(lh, lt, ls, rh, rt, rs, eh, et); + } + + // 为了测试 + public static class NodeComp implements Comparator { + + @Override + public int compare(Node o1, Node o2) { + return o1.value - o2.value; + } + + } + + // 为了测试 + public static Node sort(Node head) { + if (head == null) { + return null; + } + ArrayList arr = new ArrayList<>(); + while (head != null) { + arr.add(head); + head = head.next; + } + arr.sort(new NodeComp()); + Node h = arr.get(0); + h.last = null; + Node p = h; + for (int i = 1; i < arr.size(); i++) { + Node c = arr.get(i); + p.next = c; + c.last = p; + c.next = null; + p = c; + } + return h; + } + + // 为了测试 + public static Node generateRandomDoubleLinkedList(int n, int v) { + if (n == 0) { + return null; + } + Node[] arr = new Node[n]; + for (int i = 0; i < n; i++) { + arr[i] = new Node((int) (Math.random() * v)); + } + Node head = arr[0]; + Node pre = head; + for (int i = 1; i < n; i++) { + pre.next = arr[i]; + arr[i].last = pre; + pre = arr[i]; + } + return head; + } + + // 为了测试 + public static Node cloneDoubleLinkedList(Node head) { + if (head == null) { + return null; + } + Node h = new Node(head.value); + Node p = h; + head = head.next; + while (head != null) { + Node c = new Node(head.value); + p.next = c; + c.last = p; + p = c; + head = head.next; + } + return h; + } + + // 为了测试 + public static boolean equal(Node h1, Node h2) { + return doubleLinkedListToString(h1).equals(doubleLinkedListToString(h2)); + } + + // 为了测试 + public static String doubleLinkedListToString(Node head) { + Node cur = head; + Node end = null; + StringBuilder builder = new StringBuilder(); + while (cur != null) { + builder.append(cur.value + " "); + end = cur; + cur = cur.next; + } + builder.append("| "); + while (end != null) { + builder.append(end.value + " "); + end = end.last; + } + return builder.toString(); + } + + // 为了测试 + public static void main(String[] args) { + int N = 500; + int V = 500; + int testTime = 10000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int size = (int) (Math.random() * N); + Node head1 = generateRandomDoubleLinkedList(size, V); + Node head2 = cloneDoubleLinkedList(head1); + Node sort1 = quickSort(head1); + Node sort2 = sort(head2); + if (!equal(sort1, sort2)) { + System.out.println("出错了!"); + break; + } + } + System.out.println("测试结束"); + } + +} diff --git a/体系学习班/class06/Code01_Comparator.java b/体系学习班/class06/Code01_Comparator.java new file mode 100644 index 0000000..af23b64 --- /dev/null +++ b/体系学习班/class06/Code01_Comparator.java @@ -0,0 +1,168 @@ +package class06; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.TreeMap; + +public class Code01_Comparator { + + public static class Student { + public String name; + public int id; + public int age; + + public Student(String name, int id, int age) { + this.name = name; + this.id = id; + this.age = age; + } + } + + // 任何比较器: + // compare方法里,遵循一个统一的规范: + // 返回负数的时候,认为第一个参数应该排在前面 + // 返回正数的时候,认为第二个参数应该排在前面 + // 返回0的时候,认为无所谓谁放前面 + public static class IdShengAgeJiangOrder implements Comparator { + + // 根据id从小到大,但是如果id一样,按照年龄从大到小 + @Override + public int compare(Student o1, Student o2) { + return o1.id != o2.id ? (o1.id - o2.id) : (o2.age - o1.age); + } + + } + + public static class IdAscendingComparator implements Comparator { + + // 返回负数的时候,第一个参数排在前面 + // 返回正数的时候,第二个参数排在前面 + // 返回0的时候,谁在前面无所谓 + @Override + public int compare(Student o1, Student o2) { + return o1.id - o2.id; + } + + } + + public static class IdDescendingComparator implements Comparator { + + @Override + public int compare(Student o1, Student o2) { + return o2.id - o1.id; + } + + } + + // 先按照id排序,id小的,放前面; + // id一样,age大的,前面; + public static class IdInAgeDe implements Comparator { + + @Override + public int compare(Student o1, Student o2) { + return o1.id != o2.id ? o1.id - o2.id : (o2.age - o1.age); + } + + } + + public static void printStudents(Student[] students) { + for (Student student : students) { + System.out.println("Name : " + student.name + ", Id : " + student.id + ", Age : " + student.age); + } + } + + public static void printArray(Integer[] arr) { + if (arr == null) { + return; + } + for (int i = 0; i < arr.length; i++) { + System.out.print(arr[i] + " "); + } + System.out.println(); + } + + public static class MyComp implements Comparator { + + @Override + public int compare(Integer o1, Integer o2) { + return o2 - o1; + } + + } + + public static class AComp implements Comparator { + + // 如果返回负数,认为第一个参数应该拍在前面 + // 如果返回正数,认为第二个参数应该拍在前面 + // 如果返回0,认为谁放前面都行 + @Override + public int compare(Integer arg0, Integer arg1) { + + return arg1 - arg0; + +// return 0; + } + + } + + public static void main(String[] args) { + + Integer[] arr = { 5, 4, 3, 2, 7, 9, 1, 0 }; + + Arrays.sort(arr, new AComp()); + + for (int i = 0; i < arr.length; i++) { + System.out.println(arr[i]); + } + + System.out.println("==========================="); + + Student student1 = new Student("A", 4, 40); + Student student2 = new Student("B", 4, 21); + Student student3 = new Student("C", 3, 12); + Student student4 = new Student("D", 3, 62); + Student student5 = new Student("E", 3, 42); + // D E C A B + + Student[] students = new Student[] { student1, student2, student3, student4, student5 }; + System.out.println("第一条打印"); + + Arrays.sort(students, new IdShengAgeJiangOrder()); + for (int i = 0; i < students.length; i++) { + Student s = students[i]; + System.out.println(s.name + "," + s.id + "," + s.age); + } + + System.out.println("第二条打印"); + ArrayList studentList = new ArrayList<>(); + studentList.add(student1); + studentList.add(student2); + studentList.add(student3); + studentList.add(student4); + studentList.add(student5); + studentList.sort(new IdShengAgeJiangOrder()); + for (int i = 0; i < studentList.size(); i++) { + Student s = studentList.get(i); + System.out.println(s.name + "," + s.id + "," + s.age); + } + // N * logN + System.out.println("第三条打印"); + student1 = new Student("A", 4, 40); + student2 = new Student("B", 4, 21); + student3 = new Student("C", 4, 12); + student4 = new Student("D", 4, 62); + student5 = new Student("E", 4, 42); + TreeMap treeMap = new TreeMap<>((a, b) -> (a.id - b.id)); + treeMap.put(student1, "我是学生1,我的名字叫A"); + treeMap.put(student2, "我是学生2,我的名字叫B"); + treeMap.put(student3, "我是学生3,我的名字叫C"); + treeMap.put(student4, "我是学生4,我的名字叫D"); + treeMap.put(student5, "我是学生5,我的名字叫E"); + for (Student s : treeMap.keySet()) { + System.out.println(s.name + "," + s.id + "," + s.age); + } + + } + +} diff --git a/体系学习班/class06/Code02_Heap.java b/体系学习班/class06/Code02_Heap.java new file mode 100644 index 0000000..28140a1 --- /dev/null +++ b/体系学习班/class06/Code02_Heap.java @@ -0,0 +1,192 @@ +package class06; + +import java.util.Comparator; +import java.util.PriorityQueue; + +public class Code02_Heap { + + public static class MyMaxHeap { + private int[] heap; + private final int limit; + private int heapSize; + + public MyMaxHeap(int limit) { + heap = new int[limit]; + this.limit = limit; + heapSize = 0; + } + + public boolean isEmpty() { + return heapSize == 0; + } + + public boolean isFull() { + return heapSize == limit; + } + + public void push(int value) { + if (heapSize == limit) { + throw new RuntimeException("heap is full"); + } + heap[heapSize] = value; + // value heapSize + heapInsert(heap, heapSize++); + } + + // 用户此时,让你返回最大值,并且在大根堆中,把最大值删掉 + // 剩下的数,依然保持大根堆组织 + public int pop() { + int ans = heap[0]; + swap(heap, 0, --heapSize); + heapify(heap, 0, heapSize); + return ans; + } + + // 新加进来的数,现在停在了index位置,请依次往上移动, + // 移动到0位置,或者干不掉自己的父亲了,停! + private void heapInsert(int[] arr, int index) { + // [index] [index-1]/2 + // index == 0 + while (arr[index] > arr[(index - 1) / 2]) { + swap(arr, index, (index - 1) / 2); + index = (index - 1) / 2; + } + } + + // 从index位置,往下看,不断的下沉 + // 停:较大的孩子都不再比index位置的数大;已经没孩子了 + private void heapify(int[] arr, int index, int heapSize) { + int left = index * 2 + 1; + while (left < heapSize) { // 如果有左孩子,有没有右孩子,可能有可能没有! + // 把较大孩子的下标,给largest + int largest = left + 1 < heapSize && arr[left + 1] > arr[left] ? left + 1 : left; + largest = arr[largest] > arr[index] ? largest : index; + if (largest == index) { + break; + } + // index和较大孩子,要互换 + swap(arr, largest, index); + index = largest; + left = index * 2 + 1; + } + } + + private void swap(int[] arr, int i, int j) { + int tmp = arr[i]; + arr[i] = arr[j]; + arr[j] = tmp; + } + + } + + public static class RightMaxHeap { + private int[] arr; + private final int limit; + private int size; + + public RightMaxHeap(int limit) { + arr = new int[limit]; + this.limit = limit; + size = 0; + } + + public boolean isEmpty() { + return size == 0; + } + + public boolean isFull() { + return size == limit; + } + + public void push(int value) { + if (size == limit) { + throw new RuntimeException("heap is full"); + } + arr[size++] = value; + } + + public int pop() { + int maxIndex = 0; + for (int i = 1; i < size; i++) { + if (arr[i] > arr[maxIndex]) { + maxIndex = i; + } + } + int ans = arr[maxIndex]; + arr[maxIndex] = arr[--size]; + return ans; + } + + } + + public static class MyComparator implements Comparator { + + @Override + public int compare(Integer o1, Integer o2) { + return o2 - o1; + } + + } + + public static void main(String[] args) { + + // 小根堆 + PriorityQueue heap = new PriorityQueue<>(new MyComparator()); + heap.add(5); + heap.add(5); + heap.add(5); + heap.add(3); + // 5 , 3 + System.out.println(heap.peek()); + heap.add(7); + heap.add(0); + heap.add(7); + heap.add(0); + heap.add(7); + heap.add(0); + System.out.println(heap.peek()); + while (!heap.isEmpty()) { + System.out.println(heap.poll()); + } + + int value = 1000; + int limit = 100; + int testTimes = 1000000; + for (int i = 0; i < testTimes; i++) { + int curLimit = (int) (Math.random() * limit) + 1; + MyMaxHeap my = new MyMaxHeap(curLimit); + RightMaxHeap test = new RightMaxHeap(curLimit); + int curOpTimes = (int) (Math.random() * limit); + for (int j = 0; j < curOpTimes; j++) { + if (my.isEmpty() != test.isEmpty()) { + System.out.println("Oops!"); + } + if (my.isFull() != test.isFull()) { + System.out.println("Oops!"); + } + if (my.isEmpty()) { + int curValue = (int) (Math.random() * value); + my.push(curValue); + test.push(curValue); + } else if (my.isFull()) { + if (my.pop() != test.pop()) { + System.out.println("Oops!"); + } + } else { + if (Math.random() < 0.5) { + int curValue = (int) (Math.random() * value); + my.push(curValue); + test.push(curValue); + } else { + if (my.pop() != test.pop()) { + System.out.println("Oops!"); + } + } + } + } + } + System.out.println("finish!"); + + } + +} diff --git a/体系学习班/class06/Code03_HeapSort.java b/体系学习班/class06/Code03_HeapSort.java new file mode 100644 index 0000000..49e743a --- /dev/null +++ b/体系学习班/class06/Code03_HeapSort.java @@ -0,0 +1,158 @@ +package class06; + +import java.util.Arrays; +import java.util.PriorityQueue; + +public class Code03_HeapSort { + + // 堆排序额外空间复杂度O(1) + public static void heapSort(int[] arr) { + if (arr == null || arr.length < 2) { + return; + } + // O(N*logN) +// for (int i = 0; i < arr.length; i++) { // O(N) +// heapInsert(arr, i); // O(logN) +// } + // O(N) + for (int i = arr.length - 1; i >= 0; i--) { + heapify(arr, i, arr.length); + } + int heapSize = arr.length; + swap(arr, 0, --heapSize); + // O(N*logN) + while (heapSize > 0) { // O(N) + heapify(arr, 0, heapSize); // O(logN) + swap(arr, 0, --heapSize); // O(1) + } + } + + // arr[index]刚来的数,往上 + public static void heapInsert(int[] arr, int index) { + while (arr[index] > arr[(index - 1) / 2]) { + swap(arr, index, (index - 1) / 2); + index = (index - 1) / 2; + } + } + + // arr[index]位置的数,能否往下移动 + public static void heapify(int[] arr, int index, int heapSize) { + int left = index * 2 + 1; // 左孩子的下标 + while (left < heapSize) { // 下方还有孩子的时候 + // 两个孩子中,谁的值大,把下标给largest + // 1)只有左孩子,left -> largest + // 2) 同时有左孩子和右孩子,右孩子的值<= 左孩子的值,left -> largest + // 3) 同时有左孩子和右孩子并且右孩子的值> 左孩子的值, right -> largest + int largest = left + 1 < heapSize && arr[left + 1] > arr[left] ? left + 1 : left; + // 父和较大的孩子之间,谁的值大,把下标给largest + largest = arr[largest] > arr[index] ? largest : index; + if (largest == index) { + break; + } + swap(arr, largest, index); + index = largest; + left = index * 2 + 1; + } + } + + public static void swap(int[] arr, int i, int j) { + int tmp = arr[i]; + arr[i] = arr[j]; + arr[j] = tmp; + } + + // for test + public static void comparator(int[] arr) { + Arrays.sort(arr); + } + + // for test + public static int[] generateRandomArray(int maxSize, int maxValue) { + int[] arr = new int[(int) ((maxSize + 1) * Math.random())]; + for (int i = 0; i < arr.length; i++) { + arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random()); + } + return arr; + } + + // for test + public static int[] copyArray(int[] arr) { + if (arr == null) { + return null; + } + int[] res = new int[arr.length]; + for (int i = 0; i < arr.length; i++) { + res[i] = arr[i]; + } + return res; + } + + // for test + public static boolean isEqual(int[] arr1, int[] arr2) { + if ((arr1 == null && arr2 != null) || (arr1 != null && arr2 == null)) { + return false; + } + if (arr1 == null && arr2 == null) { + return true; + } + if (arr1.length != arr2.length) { + return false; + } + for (int i = 0; i < arr1.length; i++) { + if (arr1[i] != arr2[i]) { + return false; + } + } + return true; + } + + // for test + public static void printArray(int[] arr) { + if (arr == null) { + return; + } + for (int i = 0; i < arr.length; i++) { + System.out.print(arr[i] + " "); + } + System.out.println(); + } + + // for test + public static void main(String[] args) { + + // 默认小根堆 + PriorityQueue heap = new PriorityQueue<>(); + heap.add(6); + heap.add(8); + heap.add(0); + heap.add(2); + heap.add(9); + heap.add(1); + + while (!heap.isEmpty()) { + System.out.println(heap.poll()); + } + + int testTime = 500000; + int maxSize = 100; + int maxValue = 100; + boolean succeed = true; + for (int i = 0; i < testTime; i++) { + int[] arr1 = generateRandomArray(maxSize, maxValue); + int[] arr2 = copyArray(arr1); + heapSort(arr1); + comparator(arr2); + if (!isEqual(arr1, arr2)) { + succeed = false; + break; + } + } + System.out.println(succeed ? "Nice!" : "Fucking fucked!"); + + int[] arr = generateRandomArray(maxSize, maxValue); + printArray(arr); + heapSort(arr); + printArray(arr); + } + +} diff --git a/体系学习班/class06/Code04_SortArrayDistanceLessK.java b/体系学习班/class06/Code04_SortArrayDistanceLessK.java new file mode 100644 index 0000000..0d1b1ea --- /dev/null +++ b/体系学习班/class06/Code04_SortArrayDistanceLessK.java @@ -0,0 +1,127 @@ +package class06; + +import java.util.Arrays; +import java.util.PriorityQueue; + +public class Code04_SortArrayDistanceLessK { + + public static void sortedArrDistanceLessK(int[] arr, int k) { + if (k == 0) { + return; + } + // 默认小根堆 + PriorityQueue heap = new PriorityQueue<>(); + int index = 0; + // 0...K-1 + for (; index <= Math.min(arr.length - 1, k - 1); index++) { + heap.add(arr[index]); + } + int i = 0; + for (; index < arr.length; i++, index++) { + heap.add(arr[index]); + arr[i] = heap.poll(); + } + while (!heap.isEmpty()) { + arr[i++] = heap.poll(); + } + } + + // for test + public static void comparator(int[] arr, int k) { + Arrays.sort(arr); + } + + // for test + public static int[] randomArrayNoMoveMoreK(int maxSize, int maxValue, int K) { + int[] arr = new int[(int) ((maxSize + 1) * Math.random())]; + for (int i = 0; i < arr.length; i++) { + arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random()); + } + // 先排个序 + Arrays.sort(arr); + // 然后开始随意交换,但是保证每个数距离不超过K + // swap[i] == true, 表示i位置已经参与过交换 + // swap[i] == false, 表示i位置没有参与过交换 + boolean[] isSwap = new boolean[arr.length]; + for (int i = 0; i < arr.length; i++) { + int j = Math.min(i + (int) (Math.random() * (K + 1)), arr.length - 1); + if (!isSwap[i] && !isSwap[j]) { + isSwap[i] = true; + isSwap[j] = true; + int tmp = arr[i]; + arr[i] = arr[j]; + arr[j] = tmp; + } + } + return arr; + } + + // for test + public static int[] copyArray(int[] arr) { + if (arr == null) { + return null; + } + int[] res = new int[arr.length]; + for (int i = 0; i < arr.length; i++) { + res[i] = arr[i]; + } + return res; + } + + // for test + public static boolean isEqual(int[] arr1, int[] arr2) { + if ((arr1 == null && arr2 != null) || (arr1 != null && arr2 == null)) { + return false; + } + if (arr1 == null && arr2 == null) { + return true; + } + if (arr1.length != arr2.length) { + return false; + } + for (int i = 0; i < arr1.length; i++) { + if (arr1[i] != arr2[i]) { + return false; + } + } + return true; + } + + // for test + public static void printArray(int[] arr) { + if (arr == null) { + return; + } + for (int i = 0; i < arr.length; i++) { + System.out.print(arr[i] + " "); + } + System.out.println(); + } + + // for test + public static void main(String[] args) { + System.out.println("test begin"); + int testTime = 500000; + int maxSize = 100; + int maxValue = 100; + boolean succeed = true; + for (int i = 0; i < testTime; i++) { + int k = (int) (Math.random() * maxSize) + 1; + int[] arr = randomArrayNoMoveMoreK(maxSize, maxValue, k); + int[] arr1 = copyArray(arr); + int[] arr2 = copyArray(arr); + sortedArrDistanceLessK(arr1, k); + comparator(arr2, k); + if (!isEqual(arr1, arr2)) { + succeed = false; + System.out.println("K : " + k); + printArray(arr); + printArray(arr1); + printArray(arr2); + break; + } + } + System.out.println(succeed ? "Nice!" : "Fucking fucked!"); + } + +} \ No newline at end of file diff --git a/体系学习班/class07/Code01_CoverMax.java b/体系学习班/class07/Code01_CoverMax.java new file mode 100644 index 0000000..6260f97 --- /dev/null +++ b/体系学习班/class07/Code01_CoverMax.java @@ -0,0 +1,155 @@ +package class07; + +import java.util.Arrays; +import java.util.Comparator; +import java.util.PriorityQueue; + +public class Code01_CoverMax { + + public static int maxCover1(int[][] lines) { + int min = Integer.MAX_VALUE; + int max = Integer.MIN_VALUE; + for (int i = 0; i < lines.length; i++) { + min = Math.min(min, lines[i][0]); + max = Math.max(max, lines[i][1]); + } + int cover = 0; + for (double p = min + 0.5; p < max; p += 1) { + int cur = 0; + for (int i = 0; i < lines.length; i++) { + if (lines[i][0] < p && lines[i][1] > p) { + cur++; + } + } + cover = Math.max(cover, cur); + } + return cover; + } + + public static int maxCover2(int[][] m) { + Line[] lines = new Line[m.length]; + for (int i = 0; i < m.length; i++) { + lines[i] = new Line(m[i][0], m[i][1]); + } + Arrays.sort(lines, new StartComparator()); + // 小根堆,每一条线段的结尾数值,使用默认的 + PriorityQueue heap = new PriorityQueue<>(); + int max = 0; + for (int i = 0; i < lines.length; i++) { + // lines[i] -> cur 在黑盒中,把<=cur.start 东西都弹出 + while (!heap.isEmpty() && heap.peek() <= lines[i].start) { + heap.poll(); + } + heap.add(lines[i].end); + max = Math.max(max, heap.size()); + } + return max; + } + + public static class Line { + public int start; + public int end; + + public Line(int s, int e) { + start = s; + end = e; + } + } + + public static class EndComparator implements Comparator { + + @Override + public int compare(Line o1, Line o2) { + return o1.end - o2.end; + } + + } + + // 和maxCover2过程是一样的 + // 只是代码更短 + // 不使用类定义的写法 + public static int maxCover3(int[][] m) { + // m是二维数组,可以认为m内部是一个一个的一维数组 + // 每一个一维数组就是一个对象,也就是线段 + // 如下的code,就是根据每一个线段的开始位置排序 + // 比如, m = { {5,7}, {1,4}, {2,6} } 跑完如下的code之后变成:{ {1,4}, {2,6}, {5,7} } + Arrays.sort(m, (a, b) -> (a[0] - b[0])); + // 准备好小根堆,和课堂的说法一样 + PriorityQueue heap = new PriorityQueue<>(); + int max = 0; + for (int[] line : m) { + while (!heap.isEmpty() && heap.peek() <= line[0]) { + heap.poll(); + } + heap.add(line[1]); + max = Math.max(max, heap.size()); + } + return max; + } + + // for test + public static int[][] generateLines(int N, int L, int R) { + int size = (int) (Math.random() * N) + 1; + int[][] ans = new int[size][2]; + for (int i = 0; i < size; i++) { + int a = L + (int) (Math.random() * (R - L + 1)); + int b = L + (int) (Math.random() * (R - L + 1)); + if (a == b) { + b = a + 1; + } + ans[i][0] = Math.min(a, b); + ans[i][1] = Math.max(a, b); + } + return ans; + } + + public static class StartComparator implements Comparator { + + @Override + public int compare(Line o1, Line o2) { + return o1.start - o2.start; + } + + } + + public static void main(String[] args) { + + Line l1 = new Line(4, 9); + Line l2 = new Line(1, 4); + Line l3 = new Line(7, 15); + Line l4 = new Line(2, 4); + Line l5 = new Line(4, 6); + Line l6 = new Line(3, 7); + + // 底层堆结构,heap + PriorityQueue heap = new PriorityQueue<>(new StartComparator()); + heap.add(l1); + heap.add(l2); + heap.add(l3); + heap.add(l4); + heap.add(l5); + heap.add(l6); + + while (!heap.isEmpty()) { + Line cur = heap.poll(); + System.out.println(cur.start + "," + cur.end); + } + + System.out.println("test begin"); + int N = 100; + int L = 0; + int R = 200; + int testTimes = 200000; + for (int i = 0; i < testTimes; i++) { + int[][] lines = generateLines(N, L, R); + int ans1 = maxCover1(lines); + int ans2 = maxCover2(lines); + int ans3 = maxCover3(lines); + if (ans1 != ans2 || ans1 != ans3) { + System.out.println("Oops!"); + } + } + System.out.println("test end"); + } + +} diff --git a/体系学习班/class07/Code02_EveryStepShowBoss.java b/体系学习班/class07/Code02_EveryStepShowBoss.java new file mode 100644 index 0000000..a4c123b --- /dev/null +++ b/体系学习班/class07/Code02_EveryStepShowBoss.java @@ -0,0 +1,303 @@ +package class07; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; + +public class Code02_EveryStepShowBoss { + + public static class Customer { + public int id; + public int buy; + public int enterTime; + + public Customer(int v, int b, int o) { + id = v; + buy = b; + enterTime = 0; + } + } + + public static class CandidateComparator implements Comparator { + + @Override + public int compare(Customer o1, Customer o2) { + return o1.buy != o2.buy ? (o2.buy - o1.buy) : (o1.enterTime - o2.enterTime); + } + + } + + public static class DaddyComparator implements Comparator { + + @Override + public int compare(Customer o1, Customer o2) { + return o1.buy != o2.buy ? (o1.buy - o2.buy) : (o1.enterTime - o2.enterTime); + } + + } + + public static class WhosYourDaddy { + private HashMap customers; + private HeapGreater candHeap; + private HeapGreater daddyHeap; + private final int daddyLimit; + + public WhosYourDaddy(int limit) { + customers = new HashMap(); + candHeap = new HeapGreater<>(new CandidateComparator()); + daddyHeap = new HeapGreater<>(new DaddyComparator()); + daddyLimit = limit; + } + + // 当前处理i号事件,arr[i] -> id, buyOrRefund + public void operate(int time, int id, boolean buyOrRefund) { + if (!buyOrRefund && !customers.containsKey(id)) { + return; + } + if (!customers.containsKey(id)) { + customers.put(id, new Customer(id, 0, 0)); + } + Customer c = customers.get(id); + if (buyOrRefund) { + c.buy++; + } else { + c.buy--; + } + if (c.buy == 0) { + customers.remove(id); + } + if (!candHeap.contains(c) && !daddyHeap.contains(c)) { + if (daddyHeap.size() < daddyLimit) { + c.enterTime = time; + daddyHeap.push(c); + } else { + c.enterTime = time; + candHeap.push(c); + } + } else if (candHeap.contains(c)) { + if (c.buy == 0) { + candHeap.remove(c); + } else { + candHeap.resign(c); + } + } else { + if (c.buy == 0) { + daddyHeap.remove(c); + } else { + daddyHeap.resign(c); + } + } + daddyMove(time); + } + + public List getDaddies() { + List customers = daddyHeap.getAllElements(); + List ans = new ArrayList<>(); + for (Customer c : customers) { + ans.add(c.id); + } + return ans; + } + + private void daddyMove(int time) { + if (candHeap.isEmpty()) { + return; + } + if (daddyHeap.size() < daddyLimit) { + Customer p = candHeap.pop(); + p.enterTime = time; + daddyHeap.push(p); + } else { + if (candHeap.peek().buy > daddyHeap.peek().buy) { + Customer oldDaddy = daddyHeap.pop(); + Customer newDaddy = candHeap.pop(); + oldDaddy.enterTime = time; + newDaddy.enterTime = time; + daddyHeap.push(newDaddy); + candHeap.push(oldDaddy); + } + } + } + + } + + public static List> topK(int[] arr, boolean[] op, int k) { + List> ans = new ArrayList<>(); + WhosYourDaddy whoDaddies = new WhosYourDaddy(k); + for (int i = 0; i < arr.length; i++) { + whoDaddies.operate(i, arr[i], op[i]); + ans.add(whoDaddies.getDaddies()); + } + return ans; + } + + // 干完所有的事,模拟,不优化 + public static List> compare(int[] arr, boolean[] op, int k) { + HashMap map = new HashMap<>(); + ArrayList cands = new ArrayList<>(); + ArrayList daddy = new ArrayList<>(); + List> ans = new ArrayList<>(); + for (int i = 0; i < arr.length; i++) { + int id = arr[i]; + boolean buyOrRefund = op[i]; + if (!buyOrRefund && !map.containsKey(id)) { + ans.add(getCurAns(daddy)); + continue; + } + // 没有发生:用户购买数为0并且又退货了 + // 用户之前购买数是0,此时买货事件 + // 用户之前购买数>0, 此时买货 + // 用户之前购买数>0, 此时退货 + if (!map.containsKey(id)) { + map.put(id, new Customer(id, 0, 0)); + } + // 买、卖 + Customer c = map.get(id); + if (buyOrRefund) { + c.buy++; + } else { + c.buy--; + } + if (c.buy == 0) { + map.remove(id); + } + // c + // 下面做 + if (!cands.contains(c) && !daddy.contains(c)) { + if (daddy.size() < k) { + c.enterTime = i; + daddy.add(c); + } else { + c.enterTime = i; + cands.add(c); + } + } + cleanZeroBuy(cands); + cleanZeroBuy(daddy); + cands.sort(new CandidateComparator()); + daddy.sort(new DaddyComparator()); + move(cands, daddy, k, i); + ans.add(getCurAns(daddy)); + } + return ans; + } + + public static void move(ArrayList cands, ArrayList daddy, int k, int time) { + if (cands.isEmpty()) { + return; + } + // 候选区不为空 + if (daddy.size() < k) { + Customer c = cands.get(0); + c.enterTime = time; + daddy.add(c); + cands.remove(0); + } else { // 等奖区满了,候选区有东西 + if (cands.get(0).buy > daddy.get(0).buy) { + Customer oldDaddy = daddy.get(0); + daddy.remove(0); + Customer newDaddy = cands.get(0); + cands.remove(0); + newDaddy.enterTime = time; + oldDaddy.enterTime = time; + daddy.add(newDaddy); + cands.add(oldDaddy); + } + } + } + + public static void cleanZeroBuy(ArrayList arr) { + List noZero = new ArrayList(); + for (Customer c : arr) { + if (c.buy != 0) { + noZero.add(c); + } + } + arr.clear(); + for (Customer c : noZero) { + arr.add(c); + } + } + + public static List getCurAns(ArrayList daddy) { + List ans = new ArrayList<>(); + for (Customer c : daddy) { + ans.add(c.id); + } + return ans; + } + + // 为了测试 + public static class Data { + public int[] arr; + public boolean[] op; + + public Data(int[] a, boolean[] o) { + arr = a; + op = o; + } + } + + // 为了测试 + public static Data randomData(int maxValue, int maxLen) { + int len = (int) (Math.random() * maxLen) + 1; + int[] arr = new int[len]; + boolean[] op = new boolean[len]; + for (int i = 0; i < len; i++) { + arr[i] = (int) (Math.random() * maxValue); + op[i] = Math.random() < 0.5 ? true : false; + } + return new Data(arr, op); + } + + // 为了测试 + public static boolean sameAnswer(List> ans1, List> ans2) { + if (ans1.size() != ans2.size()) { + return false; + } + for (int i = 0; i < ans1.size(); i++) { + List cur1 = ans1.get(i); + List cur2 = ans2.get(i); + if (cur1.size() != cur2.size()) { + return false; + } + cur1.sort((a, b) -> a - b); + cur2.sort((a, b) -> a - b); + for (int j = 0; j < cur1.size(); j++) { + if (!cur1.get(j).equals(cur2.get(j))) { + return false; + } + } + } + return true; + } + + public static void main(String[] args) { + int maxValue = 10; + int maxLen = 100; + int maxK = 6; + int testTimes = 100000; + System.out.println("测试开始"); + for (int i = 0; i < testTimes; i++) { + Data testData = randomData(maxValue, maxLen); + int k = (int) (Math.random() * maxK) + 1; + int[] arr = testData.arr; + boolean[] op = testData.op; + List> ans1 = topK(arr, op, k); + List> ans2 = compare(arr, op, k); + if (!sameAnswer(ans1, ans2)) { + for (int j = 0; j < arr.length; j++) { + System.out.println(arr[j] + " , " + op[j]); + } + System.out.println(k); + System.out.println(ans1); + System.out.println(ans2); + System.out.println("出错了!"); + break; + } + } + System.out.println("测试结束"); + } + +} diff --git a/体系学习班/class07/HeapGreater.java b/体系学习班/class07/HeapGreater.java new file mode 100644 index 0000000..e58da47 --- /dev/null +++ b/体系学习班/class07/HeapGreater.java @@ -0,0 +1,112 @@ +package class07; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; + +/* + * T一定要是非基础类型,有基础类型需求包一层 + */ +public class HeapGreater { + + private ArrayList heap; + private HashMap indexMap; + private int heapSize; + private Comparator comp; + + public HeapGreater(Comparator c) { + heap = new ArrayList<>(); + indexMap = new HashMap<>(); + heapSize = 0; + comp = c; + } + + public boolean isEmpty() { + return heapSize == 0; + } + + public int size() { + return heapSize; + } + + public boolean contains(T obj) { + return indexMap.containsKey(obj); + } + + public T peek() { + return heap.get(0); + } + + public void push(T obj) { + heap.add(obj); + indexMap.put(obj, heapSize); + heapInsert(heapSize++); + } + + public T pop() { + T ans = heap.get(0); + swap(0, heapSize - 1); + indexMap.remove(ans); + heap.remove(--heapSize); + heapify(0); + return ans; + } + + public void remove(T obj) { + T replace = heap.get(heapSize - 1); + int index = indexMap.get(obj); + indexMap.remove(obj); + heap.remove(--heapSize); + if (obj != replace) { + heap.set(index, replace); + indexMap.put(replace, index); + resign(replace); + } + } + + public void resign(T obj) { + heapInsert(indexMap.get(obj)); + heapify(indexMap.get(obj)); + } + + // 请返回堆上的所有元素 + public List getAllElements() { + List ans = new ArrayList<>(); + for (T c : heap) { + ans.add(c); + } + return ans; + } + + private void heapInsert(int index) { + while (comp.compare(heap.get(index), heap.get((index - 1) / 2)) < 0) { + swap(index, (index - 1) / 2); + index = (index - 1) / 2; + } + } + + private void heapify(int index) { + int left = index * 2 + 1; + while (left < heapSize) { + int best = left + 1 < heapSize && comp.compare(heap.get(left + 1), heap.get(left)) < 0 ? (left + 1) : left; + best = comp.compare(heap.get(best), heap.get(index)) < 0 ? best : index; + if (best == index) { + break; + } + swap(best, index); + index = best; + left = index * 2 + 1; + } + } + + private void swap(int i, int j) { + T o1 = heap.get(i); + T o2 = heap.get(j); + heap.set(i, o2); + heap.set(j, o1); + indexMap.put(o2, i); + indexMap.put(o1, j); + } + +} diff --git a/体系学习班/class07/Inner.java b/体系学习班/class07/Inner.java new file mode 100644 index 0000000..d3d5326 --- /dev/null +++ b/体系学习班/class07/Inner.java @@ -0,0 +1,9 @@ +package class07; + +public class Inner { + public T value; + + public Inner(T v) { + value = v; + } +} diff --git a/体系学习班/class08/Code01_TrieTree.java b/体系学习班/class08/Code01_TrieTree.java new file mode 100644 index 0000000..f7f9cf1 --- /dev/null +++ b/体系学习班/class08/Code01_TrieTree.java @@ -0,0 +1,299 @@ +package class08; + +import java.util.HashMap; + +// 该程序的对数器跑不过,你能发现bug在哪吗? +public class Code01_TrieTree { + + // 前缀树节点类型 + public static class Node1 { + public int pass; + public int end; + public Node1[] nexts; + + public Node1() { + pass = 0; + end = 0; + nexts = new Node1[26]; + } + } + + public static class Trie1 { + private Node1 root; + + public Trie1() { + root = new Node1(); + } + + public void insert(String word) { + if (word == null) { + return; + } + char[] chs = word.toCharArray(); + Node1 node = root; + node.pass++; + int index = 0; + for (int i = 0; i < chs.length; i++) { // 从左往右遍历字符 + index = chs[i] - 'a'; // 由字符,对应成走向哪条路 + if (node.nexts[index] == null) { + node.nexts[index] = new Node1(); + } + node = node.nexts[index]; + node.pass++; + } + node.end++; + } + + public void delete(String word) { + if (search(word) != 0) { + char[] chs = word.toCharArray(); + Node1 node = root; + node.pass--; + int index = 0; + for (int i = 0; i < chs.length; i++) { + index = chs[i] - 'a'; + if (--node.nexts[index].pass == 0) { + node.nexts[index] = null; + return; + } + node = node.nexts[index]; + } + node.end--; + } + } + + // word这个单词之前加入过几次 + public int search(String word) { + if (word == null) { + return 0; + } + char[] chs = word.toCharArray(); + Node1 node = root; + int index = 0; + for (int i = 0; i < chs.length; i++) { + index = chs[i] - 'a'; + if (node.nexts[index] == null) { + return 0; + } + node = node.nexts[index]; + } + return node.end; + } + + // 所有加入的字符串中,有几个是以pre这个字符串作为前缀的 + public int prefixNumber(String pre) { + if (pre == null) { + return 0; + } + char[] chs = pre.toCharArray(); + Node1 node = root; + int index = 0; + for (int i = 0; i < chs.length; i++) { + index = chs[i] - 'a'; + if (node.nexts[index] == null) { + return 0; + } + node = node.nexts[index]; + } + return node.pass; + } + } + + public static class Node2 { + public int pass; + public int end; + public HashMap nexts; + + public Node2() { + pass = 0; + end = 0; + nexts = new HashMap<>(); + } + } + + public static class Trie2 { + private Node2 root; + + public Trie2() { + root = new Node2(); + } + + public void insert(String word) { + if (word == null) { + return; + } + char[] chs = word.toCharArray(); + Node2 node = root; + node.pass++; + int index = 0; + for (int i = 0; i < chs.length; i++) { + index = (int) chs[i]; + if (!node.nexts.containsKey(index)) { + node.nexts.put(index, new Node2()); + } + node = node.nexts.get(index); + node.pass++; + } + node.end++; + } + + public void delete(String word) { + if (search(word) != 0) { + char[] chs = word.toCharArray(); + Node2 node = root; + node.pass--; + int index = 0; + for (int i = 0; i < chs.length; i++) { + index = (int) chs[i]; + if (--node.nexts.get(index).pass == 0) { + node.nexts.remove(index); + return; + } + node = node.nexts.get(index); + } + node.end--; + } + } + + // word这个单词之前加入过几次 + public int search(String word) { + if (word == null) { + return 0; + } + char[] chs = word.toCharArray(); + Node2 node = root; + int index = 0; + for (int i = 0; i < chs.length; i++) { + index = (int) chs[i]; + if (!node.nexts.containsKey(index)) { + return 0; + } + node = node.nexts.get(index); + } + return node.end; + } + + // 所有加入的字符串中,有几个是以pre这个字符串作为前缀的 + public int prefixNumber(String pre) { + if (pre == null) { + return 0; + } + char[] chs = pre.toCharArray(); + Node2 node = root; + int index = 0; + for (int i = 0; i < chs.length; i++) { + index = (int) chs[i]; + if (!node.nexts.containsKey(index)) { + return 0; + } + node = node.nexts.get(index); + } + return node.pass; + } + } + + public static class Right { + + private HashMap box; + + public Right() { + box = new HashMap<>(); + } + + public void insert(String word) { + if (!box.containsKey(word)) { + box.put(word, 1); + } else { + box.put(word, box.get(word) + 1); + } + } + + public void delete(String word) { + if (box.containsKey(word)) { + if (box.get(word) == 1) { + box.remove(word); + } else { + box.put(word, box.get(word) - 1); + } + } + } + + public int search(String word) { + if (!box.containsKey(word)) { + return 0; + } else { + return box.get(word); + } + } + + public int prefixNumber(String pre) { + int count = 0; + for (String cur : box.keySet()) { + if (cur.startsWith(pre)) { + count++; + } + } + return count; + } + } + + // for test + public static String generateRandomString(int strLen) { + char[] ans = new char[(int) (Math.random() * strLen) + 1]; + for (int i = 0; i < ans.length; i++) { + int value = (int) (Math.random() * 6); + ans[i] = (char) (97 + value); + } + return String.valueOf(ans); + } + + // for test + public static String[] generateRandomStringArray(int arrLen, int strLen) { + String[] ans = new String[(int) (Math.random() * arrLen) + 1]; + for (int i = 0; i < ans.length; i++) { + ans[i] = generateRandomString(strLen); + } + return ans; + } + + public static void main(String[] args) { + int arrLen = 100; + int strLen = 20; + int testTimes = 100000; + for (int i = 0; i < testTimes; i++) { + String[] arr = generateRandomStringArray(arrLen, strLen); + Trie1 trie1 = new Trie1(); + Trie2 trie2 = new Trie2(); + Right right = new Right(); + for (int j = 0; j < arr.length; j++) { + double decide = Math.random(); + if (decide < 0.25) { + trie1.insert(arr[j]); + trie2.insert(arr[j]); + right.insert(arr[j]); + } else if (decide < 0.5) { + trie1.delete(arr[j]); + trie2.delete(arr[j]); + right.delete(arr[j]); + } else if (decide < 0.75) { + int ans1 = trie1.search(arr[j]); + int ans2 = trie2.search(arr[j]); + int ans3 = right.search(arr[j]); + if (ans1 != ans2 || ans2 != ans3) { + System.out.println("Oops!"); + } + } else { + int ans1 = trie1.prefixNumber(arr[j]); + int ans2 = trie2.prefixNumber(arr[j]); + int ans3 = right.prefixNumber(arr[j]); + if (ans1 != ans2 || ans2 != ans3) { + System.out.println("Oops!"); + } + } + } + } + System.out.println("finish!"); + + } + +} diff --git a/体系学习班/class08/Code02_TrieTree.java b/体系学习班/class08/Code02_TrieTree.java new file mode 100644 index 0000000..44d35cf --- /dev/null +++ b/体系学习班/class08/Code02_TrieTree.java @@ -0,0 +1,306 @@ +package class08; + +import java.util.HashMap; + +// 该程序完全正确 +public class Code02_TrieTree { + + public static class Node1 { + public int pass; + public int end; + public Node1[] nexts; + + // char tmp = 'b' (tmp - 'a') + public Node1() { + pass = 0; + end = 0; + // 0 a + // 1 b + // 2 c + // .. .. + // 25 z + // nexts[i] == null i方向的路不存在 + // nexts[i] != null i方向的路存在 + nexts = new Node1[26]; + } + } + + public static class Trie1 { + private Node1 root; + + public Trie1() { + root = new Node1(); + } + + public void insert(String word) { + if (word == null) { + return; + } + char[] str = word.toCharArray(); + Node1 node = root; + node.pass++; + int path = 0; + for (int i = 0; i < str.length; i++) { // 从左往右遍历字符 + path = str[i] - 'a'; // 由字符,对应成走向哪条路 + if (node.nexts[path] == null) { + node.nexts[path] = new Node1(); + } + node = node.nexts[path]; + node.pass++; + } + node.end++; + } + + public void delete(String word) { + if (search(word) != 0) { + char[] chs = word.toCharArray(); + Node1 node = root; + node.pass--; + int path = 0; + for (int i = 0; i < chs.length; i++) { + path = chs[i] - 'a'; + if (--node.nexts[path].pass == 0) { + node.nexts[path] = null; + return; + } + node = node.nexts[path]; + } + node.end--; + } + } + + // word这个单词之前加入过几次 + public int search(String word) { + if (word == null) { + return 0; + } + char[] chs = word.toCharArray(); + Node1 node = root; + int index = 0; + for (int i = 0; i < chs.length; i++) { + index = chs[i] - 'a'; + if (node.nexts[index] == null) { + return 0; + } + node = node.nexts[index]; + } + return node.end; + } + + // 所有加入的字符串中,有几个是以pre这个字符串作为前缀的 + public int prefixNumber(String pre) { + if (pre == null) { + return 0; + } + char[] chs = pre.toCharArray(); + Node1 node = root; + int index = 0; + for (int i = 0; i < chs.length; i++) { + index = chs[i] - 'a'; + if (node.nexts[index] == null) { + return 0; + } + node = node.nexts[index]; + } + return node.pass; + } + } + + public static class Node2 { + public int pass; + public int end; + public HashMap nexts; + + public Node2() { + pass = 0; + end = 0; + nexts = new HashMap<>(); + } + } + + public static class Trie2 { + private Node2 root; + + public Trie2() { + root = new Node2(); + } + + public void insert(String word) { + if (word == null) { + return; + } + char[] chs = word.toCharArray(); + Node2 node = root; + node.pass++; + int index = 0; + for (int i = 0; i < chs.length; i++) { + index = (int) chs[i]; + if (!node.nexts.containsKey(index)) { + node.nexts.put(index, new Node2()); + } + node = node.nexts.get(index); + node.pass++; + } + node.end++; + } + + public void delete(String word) { + if (search(word) != 0) { + char[] chs = word.toCharArray(); + Node2 node = root; + node.pass--; + int index = 0; + for (int i = 0; i < chs.length; i++) { + index = (int) chs[i]; + if (--node.nexts.get(index).pass == 0) { + node.nexts.remove(index); + return; + } + node = node.nexts.get(index); + } + node.end--; + } + } + + // word这个单词之前加入过几次 + public int search(String word) { + if (word == null) { + return 0; + } + char[] chs = word.toCharArray(); + Node2 node = root; + int index = 0; + for (int i = 0; i < chs.length; i++) { + index = (int) chs[i]; + if (!node.nexts.containsKey(index)) { + return 0; + } + node = node.nexts.get(index); + } + return node.end; + } + + // 所有加入的字符串中,有几个是以pre这个字符串作为前缀的 + public int prefixNumber(String pre) { + if (pre == null) { + return 0; + } + char[] chs = pre.toCharArray(); + Node2 node = root; + int index = 0; + for (int i = 0; i < chs.length; i++) { + index = (int) chs[i]; + if (!node.nexts.containsKey(index)) { + return 0; + } + node = node.nexts.get(index); + } + return node.pass; + } + } + + public static class Right { + + private HashMap box; + + public Right() { + box = new HashMap<>(); + } + + public void insert(String word) { + if (!box.containsKey(word)) { + box.put(word, 1); + } else { + box.put(word, box.get(word) + 1); + } + } + + public void delete(String word) { + if (box.containsKey(word)) { + if (box.get(word) == 1) { + box.remove(word); + } else { + box.put(word, box.get(word) - 1); + } + } + } + + public int search(String word) { + if (!box.containsKey(word)) { + return 0; + } else { + return box.get(word); + } + } + + public int prefixNumber(String pre) { + int count = 0; + for (String cur : box.keySet()) { + if (cur.startsWith(pre)) { + count += box.get(cur); + } + } + return count; + } + } + + // for test + public static String generateRandomString(int strLen) { + char[] ans = new char[(int) (Math.random() * strLen) + 1]; + for (int i = 0; i < ans.length; i++) { + int value = (int) (Math.random() * 6); + ans[i] = (char) (97 + value); + } + return String.valueOf(ans); + } + + // for test + public static String[] generateRandomStringArray(int arrLen, int strLen) { + String[] ans = new String[(int) (Math.random() * arrLen) + 1]; + for (int i = 0; i < ans.length; i++) { + ans[i] = generateRandomString(strLen); + } + return ans; + } + + public static void main(String[] args) { + int arrLen = 100; + int strLen = 20; + int testTimes = 100000; + for (int i = 0; i < testTimes; i++) { + String[] arr = generateRandomStringArray(arrLen, strLen); + Trie1 trie1 = new Trie1(); + Trie2 trie2 = new Trie2(); + Right right = new Right(); + for (int j = 0; j < arr.length; j++) { + double decide = Math.random(); + if (decide < 0.25) { + trie1.insert(arr[j]); + trie2.insert(arr[j]); + right.insert(arr[j]); + } else if (decide < 0.5) { + trie1.delete(arr[j]); + trie2.delete(arr[j]); + right.delete(arr[j]); + } else if (decide < 0.75) { + int ans1 = trie1.search(arr[j]); + int ans2 = trie2.search(arr[j]); + int ans3 = right.search(arr[j]); + if (ans1 != ans2 || ans2 != ans3) { + System.out.println("Oops!"); + } + } else { + int ans1 = trie1.prefixNumber(arr[j]); + int ans2 = trie2.prefixNumber(arr[j]); + int ans3 = right.prefixNumber(arr[j]); + if (ans1 != ans2 || ans2 != ans3) { + System.out.println("Oops!"); + } + } + } + } + System.out.println("finish!"); + + } + +} diff --git a/体系学习班/class08/Code03_CountSort.java b/体系学习班/class08/Code03_CountSort.java new file mode 100644 index 0000000..d7864ec --- /dev/null +++ b/体系学习班/class08/Code03_CountSort.java @@ -0,0 +1,111 @@ +package class08; + +import java.util.Arrays; + +public class Code03_CountSort { + + // only for 0~200 value + public static void countSort(int[] arr) { + if (arr == null || arr.length < 2) { + return; + } + int max = Integer.MIN_VALUE; + for (int i = 0; i < arr.length; i++) { + max = Math.max(max, arr[i]); + } + int[] bucket = new int[max + 1]; + for (int i = 0; i < arr.length; i++) { + bucket[arr[i]]++; + } + int i = 0; + for (int j = 0; j < bucket.length; j++) { + while (bucket[j]-- > 0) { + arr[i++] = j; + } + } + } + + // for test + public static void comparator(int[] arr) { + Arrays.sort(arr); + } + + // for test + public static int[] generateRandomArray(int maxSize, int maxValue) { + int[] arr = new int[(int) ((maxSize + 1) * Math.random())]; + for (int i = 0; i < arr.length; i++) { + arr[i] = (int) ((maxValue + 1) * Math.random()); + } + return arr; + } + + // for test + public static int[] copyArray(int[] arr) { + if (arr == null) { + return null; + } + int[] res = new int[arr.length]; + for (int i = 0; i < arr.length; i++) { + res[i] = arr[i]; + } + return res; + } + + // for test + public static boolean isEqual(int[] arr1, int[] arr2) { + if ((arr1 == null && arr2 != null) || (arr1 != null && arr2 == null)) { + return false; + } + if (arr1 == null && arr2 == null) { + return true; + } + if (arr1.length != arr2.length) { + return false; + } + for (int i = 0; i < arr1.length; i++) { + if (arr1[i] != arr2[i]) { + return false; + } + } + return true; + } + + // for test + public static void printArray(int[] arr) { + if (arr == null) { + return; + } + for (int i = 0; i < arr.length; i++) { + System.out.print(arr[i] + " "); + } + System.out.println(); + } + + // for test + public static void main(String[] args) { + int testTime = 500000; + int maxSize = 100; + int maxValue = 150; + boolean succeed = true; + for (int i = 0; i < testTime; i++) { + int[] arr1 = generateRandomArray(maxSize, maxValue); + int[] arr2 = copyArray(arr1); + countSort(arr1); + comparator(arr2); + if (!isEqual(arr1, arr2)) { + succeed = false; + printArray(arr1); + printArray(arr2); + break; + } + } + System.out.println(succeed ? "Nice!" : "Fucking fucked!"); + + int[] arr = generateRandomArray(maxSize, maxValue); + printArray(arr); + countSort(arr); + printArray(arr); + + } + +} diff --git a/体系学习班/class08/Code04_RadixSort.java b/体系学习班/class08/Code04_RadixSort.java new file mode 100644 index 0000000..af35db3 --- /dev/null +++ b/体系学习班/class08/Code04_RadixSort.java @@ -0,0 +1,148 @@ +package class08; + +import java.util.Arrays; + +public class Code04_RadixSort { + + // only for no-negative value + public static void radixSort(int[] arr) { + if (arr == null || arr.length < 2) { + return; + } + radixSort(arr, 0, arr.length - 1, maxbits(arr)); + } + + public static int maxbits(int[] arr) { + int max = Integer.MIN_VALUE; + for (int i = 0; i < arr.length; i++) { + max = Math.max(max, arr[i]); + } + int res = 0; + while (max != 0) { + res++; + max /= 10; + } + return res; + } + + // arr[L..R]排序 , 最大值的十进制位数digit + public static void radixSort(int[] arr, int L, int R, int digit) { + final int radix = 10; + int i = 0, j = 0; + // 有多少个数准备多少个辅助空间 + int[] help = new int[R - L + 1]; + for (int d = 1; d <= digit; d++) { // 有多少位就进出几次 + // 10个空间 + // count[0] 当前位(d位)是0的数字有多少个 + // count[1] 当前位(d位)是(0和1)的数字有多少个 + // count[2] 当前位(d位)是(0、1和2)的数字有多少个 + // count[i] 当前位(d位)是(0~i)的数字有多少个 + int[] count = new int[radix]; // count[0..9] + for (i = L; i <= R; i++) { + // 103 1 3 + // 209 1 9 + j = getDigit(arr[i], d); + count[j]++; + } + for (i = 1; i < radix; i++) { + count[i] = count[i] + count[i - 1]; + } + for (i = R; i >= L; i--) { + j = getDigit(arr[i], d); + help[count[j] - 1] = arr[i]; + count[j]--; + } + for (i = L, j = 0; i <= R; i++, j++) { + arr[i] = help[j]; + } + } + } + + public static int getDigit(int x, int d) { + return ((x / ((int) Math.pow(10, d - 1))) % 10); + } + + // for test + public static void comparator(int[] arr) { + Arrays.sort(arr); + } + + // for test + public static int[] generateRandomArray(int maxSize, int maxValue) { + int[] arr = new int[(int) ((maxSize + 1) * Math.random())]; + for (int i = 0; i < arr.length; i++) { + arr[i] = (int) ((maxValue + 1) * Math.random()); + } + return arr; + } + + // for test + public static int[] copyArray(int[] arr) { + if (arr == null) { + return null; + } + int[] res = new int[arr.length]; + for (int i = 0; i < arr.length; i++) { + res[i] = arr[i]; + } + return res; + } + + // for test + public static boolean isEqual(int[] arr1, int[] arr2) { + if ((arr1 == null && arr2 != null) || (arr1 != null && arr2 == null)) { + return false; + } + if (arr1 == null && arr2 == null) { + return true; + } + if (arr1.length != arr2.length) { + return false; + } + for (int i = 0; i < arr1.length; i++) { + if (arr1[i] != arr2[i]) { + return false; + } + } + return true; + } + + // for test + public static void printArray(int[] arr) { + if (arr == null) { + return; + } + for (int i = 0; i < arr.length; i++) { + System.out.print(arr[i] + " "); + } + System.out.println(); + } + + // for test + public static void main(String[] args) { + int testTime = 500000; + int maxSize = 100; + int maxValue = 100000; + boolean succeed = true; + for (int i = 0; i < testTime; i++) { + int[] arr1 = generateRandomArray(maxSize, maxValue); + int[] arr2 = copyArray(arr1); + radixSort(arr1); + comparator(arr2); + if (!isEqual(arr1, arr2)) { + succeed = false; + printArray(arr1); + printArray(arr2); + break; + } + } + System.out.println(succeed ? "Nice!" : "Fucking fucked!"); + + int[] arr = generateRandomArray(maxSize, maxValue); + printArray(arr); + radixSort(arr); + printArray(arr); + + } + +} diff --git a/体系学习班/class09/Code01_LinkedListMid.java b/体系学习班/class09/Code01_LinkedListMid.java new file mode 100644 index 0000000..3283443 --- /dev/null +++ b/体系学习班/class09/Code01_LinkedListMid.java @@ -0,0 +1,162 @@ +package class09; + +import java.util.ArrayList; + +public class Code01_LinkedListMid { + + public static class Node { + public int value; + public Node next; + + public Node(int v) { + value = v; + } + } + + // head 头 + public static Node midOrUpMidNode(Node head) { + if (head == null || head.next == null || head.next.next == null) { + return head; + } + // 链表有3个点或以上 + Node slow = head.next; + Node fast = head.next.next; + while (fast.next != null && fast.next.next != null) { + slow = slow.next; + fast = fast.next.next; + } + return slow; + } + + public static Node midOrDownMidNode(Node head) { + if (head == null || head.next == null) { + return head; + } + Node slow = head.next; + Node fast = head.next; + while (fast.next != null && fast.next.next != null) { + slow = slow.next; + fast = fast.next.next; + } + return slow; + } + + public static Node midOrUpMidPreNode(Node head) { + if (head == null || head.next == null || head.next.next == null) { + return null; + } + Node slow = head; + Node fast = head.next.next; + while (fast.next != null && fast.next.next != null) { + slow = slow.next; + fast = fast.next.next; + } + return slow; + } + + public static Node midOrDownMidPreNode(Node head) { + if (head == null || head.next == null) { + return null; + } + if (head.next.next == null) { + return head; + } + Node slow = head; + Node fast = head.next; + while (fast.next != null && fast.next.next != null) { + slow = slow.next; + fast = fast.next.next; + } + return slow; + } + + public static Node right1(Node head) { + if (head == null) { + return null; + } + Node cur = head; + ArrayList arr = new ArrayList<>(); + while (cur != null) { + arr.add(cur); + cur = cur.next; + } + return arr.get((arr.size() - 1) / 2); + } + + public static Node right2(Node head) { + if (head == null) { + return null; + } + Node cur = head; + ArrayList arr = new ArrayList<>(); + while (cur != null) { + arr.add(cur); + cur = cur.next; + } + return arr.get(arr.size() / 2); + } + + public static Node right3(Node head) { + if (head == null || head.next == null || head.next.next == null) { + return null; + } + Node cur = head; + ArrayList arr = new ArrayList<>(); + while (cur != null) { + arr.add(cur); + cur = cur.next; + } + return arr.get((arr.size() - 3) / 2); + } + + public static Node right4(Node head) { + if (head == null || head.next == null) { + return null; + } + Node cur = head; + ArrayList arr = new ArrayList<>(); + while (cur != null) { + arr.add(cur); + cur = cur.next; + } + return arr.get((arr.size() - 2) / 2); + } + + public static void main(String[] args) { + Node test = null; + test = new Node(0); + test.next = new Node(1); + test.next.next = new Node(2); + test.next.next.next = new Node(3); + test.next.next.next.next = new Node(4); + test.next.next.next.next.next = new Node(5); + test.next.next.next.next.next.next = new Node(6); + test.next.next.next.next.next.next.next = new Node(7); + test.next.next.next.next.next.next.next.next = new Node(8); + + Node ans1 = null; + Node ans2 = null; + + ans1 = midOrUpMidNode(test); + ans2 = right1(test); + System.out.println(ans1 != null ? ans1.value : "无"); + System.out.println(ans2 != null ? ans2.value : "无"); + + ans1 = midOrDownMidNode(test); + ans2 = right2(test); + System.out.println(ans1 != null ? ans1.value : "无"); + System.out.println(ans2 != null ? ans2.value : "无"); + + ans1 = midOrUpMidPreNode(test); + ans2 = right3(test); + System.out.println(ans1 != null ? ans1.value : "无"); + System.out.println(ans2 != null ? ans2.value : "无"); + + ans1 = midOrDownMidPreNode(test); + ans2 = right4(test); + System.out.println(ans1 != null ? ans1.value : "无"); + System.out.println(ans2 != null ? ans2.value : "无"); + + } + +} diff --git a/体系学习班/class09/Code02_IsPalindromeList.java b/体系学习班/class09/Code02_IsPalindromeList.java new file mode 100644 index 0000000..63d6eed --- /dev/null +++ b/体系学习班/class09/Code02_IsPalindromeList.java @@ -0,0 +1,204 @@ +package class09; + +import java.util.Stack; + +public class Code02_IsPalindromeList { + + public static class Node { + public int value; + public Node next; + + public Node(int data) { + this.value = data; + } + } + + // need n extra space + public static boolean isPalindrome1(Node head) { + Stack stack = new Stack(); + Node cur = head; + while (cur != null) { + stack.push(cur); + cur = cur.next; + } + while (head != null) { + if (head.value != stack.pop().value) { + return false; + } + head = head.next; + } + return true; + } + + // need n/2 extra space + public static boolean isPalindrome2(Node head) { + if (head == null || head.next == null) { + return true; + } + Node right = head.next; + Node cur = head; + while (cur.next != null && cur.next.next != null) { + right = right.next; + cur = cur.next.next; + } + Stack stack = new Stack(); + while (right != null) { + stack.push(right); + right = right.next; + } + while (!stack.isEmpty()) { + if (head.value != stack.pop().value) { + return false; + } + head = head.next; + } + return true; + } + + // need O(1) extra space + public static boolean isPalindrome3(Node head) { + if (head == null || head.next == null) { + return true; + } + Node n1 = head; + Node n2 = head; + while (n2.next != null && n2.next.next != null) { // find mid node + n1 = n1.next; // n1 -> mid + n2 = n2.next.next; // n2 -> end + } + // n1 中点 + + + n2 = n1.next; // n2 -> right part first node + n1.next = null; // mid.next -> null + Node n3 = null; + while (n2 != null) { // right part convert + n3 = n2.next; // n3 -> save next node + n2.next = n1; // next of right node convert + n1 = n2; // n1 move + n2 = n3; // n2 move + } + n3 = n1; // n3 -> save last node + n2 = head;// n2 -> left first node + boolean res = true; + while (n1 != null && n2 != null) { // check palindrome + if (n1.value != n2.value) { + res = false; + break; + } + n1 = n1.next; // left to mid + n2 = n2.next; // right to mid + } + n1 = n3.next; + n3.next = null; + while (n1 != null) { // recover list + n2 = n1.next; + n1.next = n3; + n3 = n1; + n1 = n2; + } + return res; + } + + public static void printLinkedList(Node node) { + System.out.print("Linked List: "); + while (node != null) { + System.out.print(node.value + " "); + node = node.next; + } + System.out.println(); + } + + public static void main(String[] args) { + + Node head = null; + printLinkedList(head); + System.out.print(isPalindrome1(head) + " | "); + System.out.print(isPalindrome2(head) + " | "); + System.out.println(isPalindrome3(head) + " | "); + printLinkedList(head); + System.out.println("========================="); + + head = new Node(1); + printLinkedList(head); + System.out.print(isPalindrome1(head) + " | "); + System.out.print(isPalindrome2(head) + " | "); + System.out.println(isPalindrome3(head) + " | "); + printLinkedList(head); + System.out.println("========================="); + + head = new Node(1); + head.next = new Node(2); + printLinkedList(head); + System.out.print(isPalindrome1(head) + " | "); + System.out.print(isPalindrome2(head) + " | "); + System.out.println(isPalindrome3(head) + " | "); + printLinkedList(head); + System.out.println("========================="); + + head = new Node(1); + head.next = new Node(1); + printLinkedList(head); + System.out.print(isPalindrome1(head) + " | "); + System.out.print(isPalindrome2(head) + " | "); + System.out.println(isPalindrome3(head) + " | "); + printLinkedList(head); + System.out.println("========================="); + + head = new Node(1); + head.next = new Node(2); + head.next.next = new Node(3); + printLinkedList(head); + System.out.print(isPalindrome1(head) + " | "); + System.out.print(isPalindrome2(head) + " | "); + System.out.println(isPalindrome3(head) + " | "); + printLinkedList(head); + System.out.println("========================="); + + head = new Node(1); + head.next = new Node(2); + head.next.next = new Node(1); + printLinkedList(head); + System.out.print(isPalindrome1(head) + " | "); + System.out.print(isPalindrome2(head) + " | "); + System.out.println(isPalindrome3(head) + " | "); + printLinkedList(head); + System.out.println("========================="); + + head = new Node(1); + head.next = new Node(2); + head.next.next = new Node(3); + head.next.next.next = new Node(1); + printLinkedList(head); + System.out.print(isPalindrome1(head) + " | "); + System.out.print(isPalindrome2(head) + " | "); + System.out.println(isPalindrome3(head) + " | "); + printLinkedList(head); + System.out.println("========================="); + + head = new Node(1); + head.next = new Node(2); + head.next.next = new Node(2); + head.next.next.next = new Node(1); + printLinkedList(head); + System.out.print(isPalindrome1(head) + " | "); + System.out.print(isPalindrome2(head) + " | "); + System.out.println(isPalindrome3(head) + " | "); + printLinkedList(head); + System.out.println("========================="); + + head = new Node(1); + head.next = new Node(2); + head.next.next = new Node(3); + head.next.next.next = new Node(2); + head.next.next.next.next = new Node(1); + printLinkedList(head); + System.out.print(isPalindrome1(head) + " | "); + System.out.print(isPalindrome2(head) + " | "); + System.out.println(isPalindrome3(head) + " | "); + printLinkedList(head); + System.out.println("========================="); + + } + +} diff --git a/体系学习班/class09/Code03_SmallerEqualBigger.java b/体系学习班/class09/Code03_SmallerEqualBigger.java new file mode 100644 index 0000000..33203e4 --- /dev/null +++ b/体系学习班/class09/Code03_SmallerEqualBigger.java @@ -0,0 +1,138 @@ +package class09; + +public class Code03_SmallerEqualBigger { + + public static class Node { + public int value; + public Node next; + + public Node(int data) { + this.value = data; + } + } + + public static Node listPartition1(Node head, int pivot) { + if (head == null) { + return head; + } + Node cur = head; + int i = 0; + while (cur != null) { + i++; + cur = cur.next; + } + Node[] nodeArr = new Node[i]; + i = 0; + cur = head; + for (i = 0; i != nodeArr.length; i++) { + nodeArr[i] = cur; + cur = cur.next; + } + arrPartition(nodeArr, pivot); + for (i = 1; i != nodeArr.length; i++) { + nodeArr[i - 1].next = nodeArr[i]; + } + nodeArr[i - 1].next = null; + return nodeArr[0]; + } + + public static void arrPartition(Node[] nodeArr, int pivot) { + int small = -1; + int big = nodeArr.length; + int index = 0; + while (index != big) { + if (nodeArr[index].value < pivot) { + swap(nodeArr, ++small, index++); + } else if (nodeArr[index].value == pivot) { + index++; + } else { + swap(nodeArr, --big, index); + } + } + } + + public static void swap(Node[] nodeArr, int a, int b) { + Node tmp = nodeArr[a]; + nodeArr[a] = nodeArr[b]; + nodeArr[b] = tmp; + } + + public static Node listPartition2(Node head, int pivot) { + Node sH = null; // small head + Node sT = null; // small tail + Node eH = null; // equal head + Node eT = null; // equal tail + Node mH = null; // big head + Node mT = null; // big tail + Node next = null; // save next node + // every node distributed to three lists + while (head != null) { + next = head.next; + head.next = null; + if (head.value < pivot) { + if (sH == null) { + sH = head; + sT = head; + } else { + sT.next = head; + sT = head; + } + } else if (head.value == pivot) { + if (eH == null) { + eH = head; + eT = head; + } else { + eT.next = head; + eT = head; + } + } else { + if (mH == null) { + mH = head; + mT = head; + } else { + mT.next = head; + mT = head; + } + } + head = next; + } + // 小于区域的尾巴,连等于区域的头,等于区域的尾巴连大于区域的头 + if (sT != null) { // 如果有小于区域 + sT.next = eH; + eT = eT == null ? sT : eT; // 下一步,谁去连大于区域的头,谁就变成eT + } + // 下一步,一定是需要用eT 去接 大于区域的头 + // 有等于区域,eT -> 等于区域的尾结点 + // 无等于区域,eT -> 小于区域的尾结点 + // eT 尽量不为空的尾巴节点 + if (eT != null) { // 如果小于区域和等于区域,不是都没有 + eT.next = mH; + } + return sH != null ? sH : (eH != null ? eH : mH); + } + + public static void printLinkedList(Node node) { + System.out.print("Linked List: "); + while (node != null) { + System.out.print(node.value + " "); + node = node.next; + } + System.out.println(); + } + + public static void main(String[] args) { + Node head1 = new Node(7); + head1.next = new Node(9); + head1.next.next = new Node(1); + head1.next.next.next = new Node(8); + head1.next.next.next.next = new Node(5); + head1.next.next.next.next.next = new Node(2); + head1.next.next.next.next.next.next = new Node(5); + printLinkedList(head1); + // head1 = listPartition1(head1, 4); + head1 = listPartition2(head1, 5); + printLinkedList(head1); + + } + +} diff --git a/体系学习班/class09/Code04_CopyListWithRandom.java b/体系学习班/class09/Code04_CopyListWithRandom.java new file mode 100644 index 0000000..f42454f --- /dev/null +++ b/体系学习班/class09/Code04_CopyListWithRandom.java @@ -0,0 +1,79 @@ +package class09; + +import java.util.HashMap; + +// 测试链接 : https://leetcode.com/problems/copy-list-with-random-pointer/ +public class Code04_CopyListWithRandom { + + public static class Node { + int val; + Node next; + Node random; + + public Node(int val) { + this.val = val; + this.next = null; + this.random = null; + } + } + + public static Node copyRandomList1(Node head) { + // key 老节点 + // value 新节点 + HashMap map = new HashMap(); + Node cur = head; + while (cur != null) { + map.put(cur, new Node(cur.val)); + cur = cur.next; + } + cur = head; + while (cur != null) { + // cur 老 + // map.get(cur) 新 + // 新.next -> cur.next克隆节点找到 + map.get(cur).next = map.get(cur.next); + map.get(cur).random = map.get(cur.random); + cur = cur.next; + } + return map.get(head); + } + + public static Node copyRandomList2(Node head) { + if (head == null) { + return null; + } + Node cur = head; + Node next = null; + // 1 -> 2 -> 3 -> null + // 1 -> 1' -> 2 -> 2' -> 3 -> 3' + while (cur != null) { + next = cur.next; + cur.next = new Node(cur.val); + cur.next.next = next; + cur = next; + } + cur = head; + Node copy = null; + // 1 1' 2 2' 3 3' + // 依次设置 1' 2' 3' random指针 + while (cur != null) { + next = cur.next.next; + copy = cur.next; + copy.random = cur.random != null ? cur.random.next : null; + cur = next; + } + Node res = head.next; + cur = head; + // 老 新 混在一起,next方向上,random正确 + // next方向上,把新老链表分离 + while (cur != null) { + next = cur.next.next; + copy = cur.next; + cur.next = next; + copy.next = next != null ? next.next : null; + cur = next; + } + return res; + } + +} diff --git a/体系学习班/class10/Code01_FindFirstIntersectNode.java b/体系学习班/class10/Code01_FindFirstIntersectNode.java new file mode 100644 index 0000000..8cec65d --- /dev/null +++ b/体系学习班/class10/Code01_FindFirstIntersectNode.java @@ -0,0 +1,170 @@ +package class10; + +public class Code01_FindFirstIntersectNode { + + public static class Node { + public int value; + public Node next; + + public Node(int data) { + this.value = data; + } + } + + public static Node getIntersectNode(Node head1, Node head2) { + if (head1 == null || head2 == null) { + return null; + } + Node loop1 = getLoopNode(head1); + Node loop2 = getLoopNode(head2); + if (loop1 == null && loop2 == null) { + return noLoop(head1, head2); + } + if (loop1 != null && loop2 != null) { + return bothLoop(head1, loop1, head2, loop2); + } + return null; + } + + // 找到链表第一个入环节点,如果无环,返回null + public static Node getLoopNode(Node head) { + if (head == null || head.next == null || head.next.next == null) { + return null; + } + // n1 慢 n2 快 + Node slow = head.next; // n1 -> slow + Node fast = head.next.next; // n2 -> fast + while (slow != fast) { + if (fast.next == null || fast.next.next == null) { + return null; + } + fast = fast.next.next; + slow = slow.next; + } + // slow fast 相遇 + fast = head; // n2 -> walk again from head + while (slow != fast) { + slow = slow.next; + fast = fast.next; + } + return slow; + } + + // 如果两个链表都无环,返回第一个相交节点,如果不想交,返回null + public static Node noLoop(Node head1, Node head2) { + if (head1 == null || head2 == null) { + return null; + } + Node cur1 = head1; + Node cur2 = head2; + int n = 0; + while (cur1.next != null) { + n++; + cur1 = cur1.next; + } + while (cur2.next != null) { + n--; + cur2 = cur2.next; + } + if (cur1 != cur2) { + return null; + } + // n : 链表1长度减去链表2长度的值 + cur1 = n > 0 ? head1 : head2; // 谁长,谁的头变成cur1 + cur2 = cur1 == head1 ? head2 : head1; // 谁短,谁的头变成cur2 + n = Math.abs(n); + while (n != 0) { + n--; + cur1 = cur1.next; + } + while (cur1 != cur2) { + cur1 = cur1.next; + cur2 = cur2.next; + } + return cur1; + } + + // 两个有环链表,返回第一个相交节点,如果不想交返回null + public static Node bothLoop(Node head1, Node loop1, Node head2, Node loop2) { + Node cur1 = null; + Node cur2 = null; + if (loop1 == loop2) { + cur1 = head1; + cur2 = head2; + int n = 0; + while (cur1 != loop1) { + n++; + cur1 = cur1.next; + } + while (cur2 != loop2) { + n--; + cur2 = cur2.next; + } + cur1 = n > 0 ? head1 : head2; + cur2 = cur1 == head1 ? head2 : head1; + n = Math.abs(n); + while (n != 0) { + n--; + cur1 = cur1.next; + } + while (cur1 != cur2) { + cur1 = cur1.next; + cur2 = cur2.next; + } + return cur1; + } else { + cur1 = loop1.next; + while (cur1 != loop1) { + if (cur1 == loop2) { + return loop1; + } + cur1 = cur1.next; + } + return null; + } + } + + public static void main(String[] args) { + // 1->2->3->4->5->6->7->null + Node head1 = new Node(1); + head1.next = new Node(2); + head1.next.next = new Node(3); + head1.next.next.next = new Node(4); + head1.next.next.next.next = new Node(5); + head1.next.next.next.next.next = new Node(6); + head1.next.next.next.next.next.next = new Node(7); + + // 0->9->8->6->7->null + Node head2 = new Node(0); + head2.next = new Node(9); + head2.next.next = new Node(8); + head2.next.next.next = head1.next.next.next.next.next; // 8->6 + System.out.println(getIntersectNode(head1, head2).value); + + // 1->2->3->4->5->6->7->4... + head1 = new Node(1); + head1.next = new Node(2); + head1.next.next = new Node(3); + head1.next.next.next = new Node(4); + head1.next.next.next.next = new Node(5); + head1.next.next.next.next.next = new Node(6); + head1.next.next.next.next.next.next = new Node(7); + head1.next.next.next.next.next.next = head1.next.next.next; // 7->4 + + // 0->9->8->2... + head2 = new Node(0); + head2.next = new Node(9); + head2.next.next = new Node(8); + head2.next.next.next = head1.next; // 8->2 + System.out.println(getIntersectNode(head1, head2).value); + + // 0->9->8->6->4->5->6.. + head2 = new Node(0); + head2.next = new Node(9); + head2.next.next = new Node(8); + head2.next.next.next = head1.next.next.next.next.next; // 8->6 + System.out.println(getIntersectNode(head1, head2).value); + + } + +} diff --git a/体系学习班/class10/Code02_RecursiveTraversalBT.java b/体系学习班/class10/Code02_RecursiveTraversalBT.java new file mode 100644 index 0000000..c7f2b2c --- /dev/null +++ b/体系学习班/class10/Code02_RecursiveTraversalBT.java @@ -0,0 +1,72 @@ +package class10; + +public class Code02_RecursiveTraversalBT { + + public static class Node { + public int value; + public Node left; + public Node right; + + public Node(int v) { + value = v; + } + } + + public static void f(Node head) { + if (head == null) { + return; + } + // 1 + f(head.left); + // 2 + f(head.right); + // 3 + } + + // 先序打印所有节点 + public static void pre(Node head) { + if (head == null) { + return; + } + System.out.println(head.value); + pre(head.left); + pre(head.right); + } + + public static void in(Node head) { + if (head == null) { + return; + } + in(head.left); + System.out.println(head.value); + in(head.right); + } + + public static void pos(Node head) { + if (head == null) { + return; + } + pos(head.left); + pos(head.right); + System.out.println(head.value); + } + + public static void main(String[] args) { + Node head = new Node(1); + head.left = new Node(2); + head.right = new Node(3); + head.left.left = new Node(4); + head.left.right = new Node(5); + head.right.left = new Node(6); + head.right.right = new Node(7); + + pre(head); + System.out.println("========"); + in(head); + System.out.println("========"); + pos(head); + System.out.println("========"); + + } + +} diff --git a/体系学习班/class10/Code03_UnRecursiveTraversalBT.java b/体系学习班/class10/Code03_UnRecursiveTraversalBT.java new file mode 100644 index 0000000..96ef25e --- /dev/null +++ b/体系学习班/class10/Code03_UnRecursiveTraversalBT.java @@ -0,0 +1,118 @@ +package class10; + +import java.util.Stack; + +public class Code03_UnRecursiveTraversalBT { + + public static class Node { + public int value; + public Node left; + public Node right; + + public Node(int v) { + value = v; + } + } + + public static void pre(Node head) { + System.out.print("pre-order: "); + if (head != null) { + Stack stack = new Stack(); + stack.add(head); + while (!stack.isEmpty()) { + head = stack.pop(); + System.out.print(head.value + " "); + if (head.right != null) { + stack.push(head.right); + } + if (head.left != null) { + stack.push(head.left); + } + } + } + System.out.println(); + } + + public static void in(Node cur) { + System.out.print("in-order: "); + if (cur != null) { + Stack stack = new Stack(); + while (!stack.isEmpty() || cur != null) { + if (cur != null) { + stack.push(cur); + cur = cur.left; + } else { + cur = stack.pop(); + System.out.print(cur.value + " "); + cur = cur.right; + } + } + } + System.out.println(); + } + + public static void pos1(Node head) { + System.out.print("pos-order: "); + if (head != null) { + Stack s1 = new Stack(); + Stack s2 = new Stack(); + s1.push(head); + while (!s1.isEmpty()) { + head = s1.pop(); // 头 右 左 + s2.push(head); + if (head.left != null) { + s1.push(head.left); + } + if (head.right != null) { + s1.push(head.right); + } + } + // 左 右 头 + while (!s2.isEmpty()) { + System.out.print(s2.pop().value + " "); + } + } + System.out.println(); + } + + public static void pos2(Node h) { + System.out.print("pos-order: "); + if (h != null) { + Stack stack = new Stack(); + stack.push(h); + Node c = null; + while (!stack.isEmpty()) { + c = stack.peek(); + if (c.left != null && h != c.left && h != c.right) { + stack.push(c.left); + } else if (c.right != null && h != c.right) { + stack.push(c.right); + } else { + System.out.print(stack.pop().value + " "); + h = c; + } + } + } + System.out.println(); + } + + public static void main(String[] args) { + Node head = new Node(1); + head.left = new Node(2); + head.right = new Node(3); + head.left.left = new Node(4); + head.left.right = new Node(5); + head.right.left = new Node(6); + head.right.right = new Node(7); + + pre(head); + System.out.println("========"); + in(head); + System.out.println("========"); + pos1(head); + System.out.println("========"); + pos2(head); + System.out.println("========"); + } + +} diff --git a/体系学习班/class11/Code01_LevelTraversalBT.java b/体系学习班/class11/Code01_LevelTraversalBT.java new file mode 100644 index 0000000..12e8ef0 --- /dev/null +++ b/体系学习班/class11/Code01_LevelTraversalBT.java @@ -0,0 +1,49 @@ +package class11; + +import java.util.LinkedList; +import java.util.Queue; + +public class Code01_LevelTraversalBT { + + public static class Node { + public int value; + public Node left; + public Node right; + + public Node(int v) { + value = v; + } + } + + public static void level(Node head) { + if (head == null) { + return; + } + Queue queue = new LinkedList<>(); + queue.add(head); + while (!queue.isEmpty()) { + Node cur = queue.poll(); + System.out.println(cur.value); + if (cur.left != null) { + queue.add(cur.left); + } + if (cur.right != null) { + queue.add(cur.right); + } + } + } + + public static void main(String[] args) { + Node head = new Node(1); + head.left = new Node(2); + head.right = new Node(3); + head.left.left = new Node(4); + head.left.right = new Node(5); + head.right.left = new Node(6); + head.right.right = new Node(7); + + level(head); + System.out.println("========"); + } + +} diff --git a/体系学习班/class11/Code02_SerializeAndReconstructTree.java b/体系学习班/class11/Code02_SerializeAndReconstructTree.java new file mode 100644 index 0000000..6e6da7e --- /dev/null +++ b/体系学习班/class11/Code02_SerializeAndReconstructTree.java @@ -0,0 +1,264 @@ +package class11; + +import java.util.LinkedList; +import java.util.Queue; +import java.util.Stack; + +public class Code02_SerializeAndReconstructTree { + /* + * 二叉树可以通过先序、后序或者按层遍历的方式序列化和反序列化, + * 以下代码全部实现了。 + * 但是,二叉树无法通过中序遍历的方式实现序列化和反序列化 + * 因为不同的两棵树,可能得到同样的中序序列,即便补了空位置也可能一样。 + * 比如如下两棵树 + * __2 + * / + * 1 + * 和 + * 1__ + * \ + * 2 + * 补足空位置的中序遍历结果都是{ null, 1, null, 2, null} + * + * */ + public static class Node { + public int value; + public Node left; + public Node right; + + public Node(int data) { + this.value = data; + } + } + + public static Queue preSerial(Node head) { + Queue ans = new LinkedList<>(); + pres(head, ans); + return ans; + } + + public static void pres(Node head, Queue ans) { + if (head == null) { + ans.add(null); + } else { + ans.add(String.valueOf(head.value)); + pres(head.left, ans); + pres(head.right, ans); + } + } + + public static Queue inSerial(Node head) { + Queue ans = new LinkedList<>(); + ins(head, ans); + return ans; + } + + public static void ins(Node head, Queue ans) { + if (head == null) { + ans.add(null); + } else { + ins(head.left, ans); + ans.add(String.valueOf(head.value)); + ins(head.right, ans); + } + } + + public static Queue posSerial(Node head) { + Queue ans = new LinkedList<>(); + poss(head, ans); + return ans; + } + + public static void poss(Node head, Queue ans) { + if (head == null) { + ans.add(null); + } else { + poss(head.left, ans); + poss(head.right, ans); + ans.add(String.valueOf(head.value)); + } + } + + public static Node buildByPreQueue(Queue prelist) { + if (prelist == null || prelist.size() == 0) { + return null; + } + return preb(prelist); + } + + public static Node preb(Queue prelist) { + String value = prelist.poll(); + if (value == null) { + return null; + } + Node head = new Node(Integer.valueOf(value)); + head.left = preb(prelist); + head.right = preb(prelist); + return head; + } + + public static Node buildByPosQueue(Queue poslist) { + if (poslist == null || poslist.size() == 0) { + return null; + } + // 左右中 -> stack(中右左) + Stack stack = new Stack<>(); + while (!poslist.isEmpty()) { + stack.push(poslist.poll()); + } + return posb(stack); + } + + public static Node posb(Stack posstack) { + String value = posstack.pop(); + if (value == null) { + return null; + } + Node head = new Node(Integer.valueOf(value)); + head.right = posb(posstack); + head.left = posb(posstack); + return head; + } + + public static Queue levelSerial(Node head) { + Queue ans = new LinkedList<>(); + if (head == null) { + ans.add(null); + } else { + ans.add(String.valueOf(head.value)); + Queue queue = new LinkedList(); + queue.add(head); + while (!queue.isEmpty()) { + head = queue.poll(); // head 父 子 + if (head.left != null) { + ans.add(String.valueOf(head.left.value)); + queue.add(head.left); + } else { + ans.add(null); + } + if (head.right != null) { + ans.add(String.valueOf(head.right.value)); + queue.add(head.right); + } else { + ans.add(null); + } + } + } + return ans; + } + + public static Node buildByLevelQueue(Queue levelList) { + if (levelList == null || levelList.size() == 0) { + return null; + } + Node head = generateNode(levelList.poll()); + Queue queue = new LinkedList(); + if (head != null) { + queue.add(head); + } + Node node = null; + while (!queue.isEmpty()) { + node = queue.poll(); + node.left = generateNode(levelList.poll()); + node.right = generateNode(levelList.poll()); + if (node.left != null) { + queue.add(node.left); + } + if (node.right != null) { + queue.add(node.right); + } + } + return head; + } + + public static Node generateNode(String val) { + if (val == null) { + return null; + } + return new Node(Integer.valueOf(val)); + } + + // for test + public static Node generateRandomBST(int maxLevel, int maxValue) { + return generate(1, maxLevel, maxValue); + } + + // for test + public static Node generate(int level, int maxLevel, int maxValue) { + if (level > maxLevel || Math.random() < 0.5) { + return null; + } + Node head = new Node((int) (Math.random() * maxValue)); + head.left = generate(level + 1, maxLevel, maxValue); + head.right = generate(level + 1, maxLevel, maxValue); + return head; + } + + // for test + public static boolean isSameValueStructure(Node head1, Node head2) { + if (head1 == null && head2 != null) { + return false; + } + if (head1 != null && head2 == null) { + return false; + } + if (head1 == null && head2 == null) { + return true; + } + if (head1.value != head2.value) { + return false; + } + return isSameValueStructure(head1.left, head2.left) && isSameValueStructure(head1.right, head2.right); + } + + // for test + public static void printTree(Node head) { + System.out.println("Binary Tree:"); + printInOrder(head, 0, "H", 17); + System.out.println(); + } + + public static void printInOrder(Node head, int height, String to, int len) { + if (head == null) { + return; + } + printInOrder(head.right, height + 1, "v", len); + String val = to + head.value + to; + int lenM = val.length(); + int lenL = (len - lenM) / 2; + int lenR = len - lenM - lenL; + val = getSpace(lenL) + val + getSpace(lenR); + System.out.println(getSpace(height * len) + val); + printInOrder(head.left, height + 1, "^", len); + } + + public static String getSpace(int num) { + String space = " "; + StringBuffer buf = new StringBuffer(""); + for (int i = 0; i < num; i++) { + buf.append(space); + } + return buf.toString(); + } + + public static void main(String[] args) { + int maxLevel = 5; + int maxValue = 100; + int testTimes = 1000000; + System.out.println("test begin"); + for (int i = 0; i < testTimes; i++) { + Node head = generateRandomBST(maxLevel, maxValue); + Queue pre = preSerial(head); + Queue pos = posSerial(head); + Queue level = levelSerial(head); + Node preBuild = buildByPreQueue(pre); + Node posBuild = buildByPosQueue(pos); + Node levelBuild = buildByLevelQueue(level); + if (!isSameValueStructure(preBuild, posBuild) || !isSameValueStructure(posBuild, levelBuild)) { + System.out.println("Oops!"); + } + } + System.out.println("test finish!"); + + } +} diff --git a/体系学习班/class11/Code03_EncodeNaryTreeToBinaryTree.java b/体系学习班/class11/Code03_EncodeNaryTreeToBinaryTree.java new file mode 100644 index 0000000..aa9f2ab --- /dev/null +++ b/体系学习班/class11/Code03_EncodeNaryTreeToBinaryTree.java @@ -0,0 +1,86 @@ +package class11; + +import java.util.ArrayList; +import java.util.List; + +// 本题测试链接:https://leetcode.com/problems/encode-n-ary-tree-to-binary-tree +public class Code03_EncodeNaryTreeToBinaryTree { + + // 提交时不要提交这个类 + public static class Node { + public int val; + public List children; + + public Node() { + } + + public Node(int _val) { + val = _val; + } + + public Node(int _val, List _children) { + val = _val; + children = _children; + } + }; + + // 提交时不要提交这个类 + public static class TreeNode { + int val; + TreeNode left; + TreeNode right; + + TreeNode(int x) { + val = x; + } + } + + // 只提交这个类即可 + class Codec { + // Encodes an n-ary tree to a binary tree. + public TreeNode encode(Node root) { + if (root == null) { + return null; + } + TreeNode head = new TreeNode(root.val); + head.left = en(root.children); + return head; + } + + private TreeNode en(List children) { + TreeNode head = null; + TreeNode cur = null; + for (Node child : children) { + TreeNode tNode = new TreeNode(child.val); + if (head == null) { + head = tNode; + } else { + cur.right = tNode; + } + cur = tNode; + cur.left = en(child.children); + } + return head; + } + + // Decodes your binary tree to an n-ary tree. + public Node decode(TreeNode root) { + if (root == null) { + return null; + } + return new Node(root.val, de(root.left)); + } + + public List de(TreeNode root) { + List children = new ArrayList<>(); + while (root != null) { + Node cur = new Node(root.val, de(root.left)); + children.add(cur); + root = root.right; + } + return children; + } + + } + +} diff --git a/体系学习班/class11/Code04_PrintBinaryTree.java b/体系学习班/class11/Code04_PrintBinaryTree.java new file mode 100644 index 0000000..75125f7 --- /dev/null +++ b/体系学习班/class11/Code04_PrintBinaryTree.java @@ -0,0 +1,74 @@ +package class11; + +public class Code04_PrintBinaryTree { + + public static class Node { + public int value; + public Node left; + public Node right; + + public Node(int data) { + this.value = data; + } + } + + public static void printTree(Node head) { + System.out.println("Binary Tree:"); + printInOrder(head, 0, "H", 17); + System.out.println(); + } + + public static void printInOrder(Node head, int height, String to, int len) { + if (head == null) { + return; + } + printInOrder(head.right, height + 1, "v", len); + String val = to + head.value + to; + int lenM = val.length(); + int lenL = (len - lenM) / 2; + int lenR = len - lenM - lenL; + val = getSpace(lenL) + val + getSpace(lenR); + System.out.println(getSpace(height * len) + val); + printInOrder(head.left, height + 1, "^", len); + } + + public static String getSpace(int num) { + String space = " "; + StringBuffer buf = new StringBuffer(""); + for (int i = 0; i < num; i++) { + buf.append(space); + } + return buf.toString(); + } + + public static void main(String[] args) { + Node head = new Node(1); + head.left = new Node(-222222222); + head.right = new Node(3); + head.left.left = new Node(Integer.MIN_VALUE); + head.right.left = new Node(55555555); + head.right.right = new Node(66); + head.left.left.right = new Node(777); + printTree(head); + + head = new Node(1); + head.left = new Node(2); + head.right = new Node(3); + head.left.left = new Node(4); + head.right.left = new Node(5); + head.right.right = new Node(6); + head.left.left.right = new Node(7); + printTree(head); + + head = new Node(1); + head.left = new Node(1); + head.right = new Node(1); + head.left.left = new Node(1); + head.right.left = new Node(1); + head.right.right = new Node(1); + head.left.left.right = new Node(1); + printTree(head); + + } + +} diff --git a/体系学习班/class11/Code05_TreeMaxWidth.java b/体系学习班/class11/Code05_TreeMaxWidth.java new file mode 100644 index 0000000..787bd01 --- /dev/null +++ b/体系学习班/class11/Code05_TreeMaxWidth.java @@ -0,0 +1,114 @@ +package class11; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.Queue; + +public class Code05_TreeMaxWidth { + + public static class Node { + public int value; + public Node left; + public Node right; + + public Node(int data) { + this.value = data; + } + } + + public static int maxWidthUseMap(Node head) { + if (head == null) { + return 0; + } + Queue queue = new LinkedList<>(); + queue.add(head); + // key 在 哪一层,value + HashMap levelMap = new HashMap<>(); + levelMap.put(head, 1); + int curLevel = 1; // 当前你正在统计哪一层的宽度 + int curLevelNodes = 0; // 当前层curLevel层,宽度目前是多少 + int max = 0; + while (!queue.isEmpty()) { + Node cur = queue.poll(); + int curNodeLevel = levelMap.get(cur); + if (cur.left != null) { + levelMap.put(cur.left, curNodeLevel + 1); + queue.add(cur.left); + } + if (cur.right != null) { + levelMap.put(cur.right, curNodeLevel + 1); + queue.add(cur.right); + } + if (curNodeLevel == curLevel) { + curLevelNodes++; + } else { + max = Math.max(max, curLevelNodes); + curLevel++; + curLevelNodes = 1; + } + } + max = Math.max(max, curLevelNodes); + return max; + } + + public static int maxWidthNoMap(Node head) { + if (head == null) { + return 0; + } + Queue queue = new LinkedList<>(); + queue.add(head); + Node curEnd = head; // 当前层,最右节点是谁 + Node nextEnd = null; // 下一层,最右节点是谁 + int max = 0; + int curLevelNodes = 0; // 当前层的节点数 + while (!queue.isEmpty()) { + Node cur = queue.poll(); + if (cur.left != null) { + queue.add(cur.left); + nextEnd = cur.left; + } + if (cur.right != null) { + queue.add(cur.right); + nextEnd = cur.right; + } + curLevelNodes++; + if (cur == curEnd) { + max = Math.max(max, curLevelNodes); + curLevelNodes = 0; + curEnd = nextEnd; + } + } + return max; + } + + // for test + public static Node generateRandomBST(int maxLevel, int maxValue) { + return generate(1, maxLevel, maxValue); + } + + // for test + public static Node generate(int level, int maxLevel, int maxValue) { + if (level > maxLevel || Math.random() < 0.5) { + return null; + } + Node head = new Node((int) (Math.random() * maxValue)); + head.left = generate(level + 1, maxLevel, maxValue); + head.right = generate(level + 1, maxLevel, maxValue); + return head; + } + + public static void main(String[] args) { + int maxLevel = 10; + int maxValue = 100; + int testTimes = 1000000; + for (int i = 0; i < testTimes; i++) { + Node head = generateRandomBST(maxLevel, maxValue); + if (maxWidthUseMap(head) != maxWidthNoMap(head)) { + System.out.println("Oops!"); + } + } + System.out.println("finish!"); + + } + +} diff --git a/体系学习班/class11/Code06_SuccessorNode.java b/体系学习班/class11/Code06_SuccessorNode.java new file mode 100644 index 0000000..b33d4b5 --- /dev/null +++ b/体系学习班/class11/Code06_SuccessorNode.java @@ -0,0 +1,86 @@ +package class11; + +public class Code06_SuccessorNode { + + public static class Node { + public int value; + public Node left; + public Node right; + public Node parent; + + public Node(int data) { + this.value = data; + } + } + + public static Node getSuccessorNode(Node node) { + if (node == null) { + return node; + } + if (node.right != null) { + return getLeftMost(node.right); + } else { // 无右子树 + Node parent = node.parent; + while (parent != null && parent.right == node) { // 当前节点是其父亲节点右孩子 + node = parent; + parent = node.parent; + } + return parent; + } + } + + public static Node getLeftMost(Node node) { + if (node == null) { + return node; + } + while (node.left != null) { + node = node.left; + } + return node; + } + + public static void main(String[] args) { + Node head = new Node(6); + head.parent = null; + head.left = new Node(3); + head.left.parent = head; + head.left.left = new Node(1); + head.left.left.parent = head.left; + head.left.left.right = new Node(2); + head.left.left.right.parent = head.left.left; + head.left.right = new Node(4); + head.left.right.parent = head.left; + head.left.right.right = new Node(5); + head.left.right.right.parent = head.left.right; + head.right = new Node(9); + head.right.parent = head; + head.right.left = new Node(8); + head.right.left.parent = head.right; + head.right.left.left = new Node(7); + head.right.left.left.parent = head.right.left; + head.right.right = new Node(10); + head.right.right.parent = head.right; + + Node test = head.left.left; + System.out.println(test.value + " next: " + getSuccessorNode(test).value); + test = head.left.left.right; + System.out.println(test.value + " next: " + getSuccessorNode(test).value); + test = head.left; + System.out.println(test.value + " next: " + getSuccessorNode(test).value); + test = head.left.right; + System.out.println(test.value + " next: " + getSuccessorNode(test).value); + test = head.left.right.right; + System.out.println(test.value + " next: " + getSuccessorNode(test).value); + test = head; + System.out.println(test.value + " next: " + getSuccessorNode(test).value); + test = head.right.left.left; + System.out.println(test.value + " next: " + getSuccessorNode(test).value); + test = head.right.left; + System.out.println(test.value + " next: " + getSuccessorNode(test).value); + test = head.right; + System.out.println(test.value + " next: " + getSuccessorNode(test).value); + test = head.right.right; // 10's next is null + System.out.println(test.value + " next: " + getSuccessorNode(test)); + } + +} diff --git a/体系学习班/class11/Code07_PaperFolding.java b/体系学习班/class11/Code07_PaperFolding.java new file mode 100644 index 0000000..3f3b0db --- /dev/null +++ b/体系学习班/class11/Code07_PaperFolding.java @@ -0,0 +1,28 @@ +package class11; + +public class Code07_PaperFolding { + + public static void printAllFolds(int N) { + process(1, N, true); + System.out.println(); + } + + // 当前你来了一个节点,脑海中想象的! + // 这个节点在第i层,一共有N层,N固定不变的 + // 这个节点如果是凹的话,down = T + // 这个节点如果是凸的话,down = F + // 函数的功能:中序打印以你想象的节点为头的整棵树! + public static void process(int i, int N, boolean down) { + if (i > N) { + return; + } + process(i + 1, N, true); + System.out.print(down ? "凹 " : "凸 "); + process(i + 1, N, false); + } + + public static void main(String[] args) { + int N = 4; + printAllFolds(N); + } +} diff --git a/体系学习班/class12/Code01_IsCBT.java b/体系学习班/class12/Code01_IsCBT.java new file mode 100644 index 0000000..e0b9cab --- /dev/null +++ b/体系学习班/class12/Code01_IsCBT.java @@ -0,0 +1,149 @@ +package class12; + +import java.util.LinkedList; + +public class Code01_IsCBT { + + public static class Node { + public int value; + public Node left; + public Node right; + + public Node(int data) { + this.value = data; + } + } + + public static boolean isCBT1(Node head) { + if (head == null) { + return true; + } + LinkedList queue = new LinkedList<>(); + // 是否遇到过左右两个孩子不双全的节点 + boolean leaf = false; + Node l = null; + Node r = null; + queue.add(head); + while (!queue.isEmpty()) { + head = queue.poll(); + l = head.left; + r = head.right; + if ( + // 如果遇到了不双全的节点之后,又发现当前节点不是叶节点 + (leaf && (l != null || r != null)) + || + (l == null && r != null) + + ) { + return false; + } + if (l != null) { + queue.add(l); + } + if (r != null) { + queue.add(r); + } + if (l == null || r == null) { + leaf = true; + } + } + return true; + } + + public static boolean isCBT2(Node head) { + if (head == null) { + return true; + } + return process(head).isCBT; + } + + // 对每一棵子树,是否是满二叉树、是否是完全二叉树、高度 + public static class Info { + public boolean isFull; + public boolean isCBT; + public int height; + + public Info(boolean full, boolean cbt, int h) { + isFull = full; + isCBT = cbt; + height = h; + } + } + + public static Info process(Node X) { + if (X == null) { + return new Info(true, true, 0); + } + Info leftInfo = process(X.left); + Info rightInfo = process(X.right); + + + + int height = Math.max(leftInfo.height, rightInfo.height) + 1; + + + boolean isFull = leftInfo.isFull + && + rightInfo.isFull + && leftInfo.height == rightInfo.height; + + + boolean isCBT = false; + if (isFull) { + isCBT = true; + } else { // 以x为头整棵树,不满 + if (leftInfo.isCBT && rightInfo.isCBT) { + + + if (leftInfo.isCBT + && rightInfo.isFull + && leftInfo.height == rightInfo.height + 1) { + isCBT = true; + } + if (leftInfo.isFull + && + rightInfo.isFull + && leftInfo.height == rightInfo.height + 1) { + isCBT = true; + } + if (leftInfo.isFull + && rightInfo.isCBT && leftInfo.height == rightInfo.height) { + isCBT = true; + } + + + } + } + return new Info(isFull, isCBT, height); + } + + // for test + public static Node generateRandomBST(int maxLevel, int maxValue) { + return generate(1, maxLevel, maxValue); + } + + // for test + public static Node generate(int level, int maxLevel, int maxValue) { + if (level > maxLevel || Math.random() < 0.5) { + return null; + } + Node head = new Node((int) (Math.random() * maxValue)); + head.left = generate(level + 1, maxLevel, maxValue); + head.right = generate(level + 1, maxLevel, maxValue); + return head; + } + + public static void main(String[] args) { + int maxLevel = 5; + int maxValue = 100; + int testTimes = 1000000; + for (int i = 0; i < testTimes; i++) { + Node head = generateRandomBST(maxLevel, maxValue); + if (isCBT1(head) != isCBT2(head)) { + System.out.println("Oops!"); + } + } + System.out.println("finish!"); + } + +} diff --git a/体系学习班/class12/Code02_IsBST.java b/体系学习班/class12/Code02_IsBST.java new file mode 100644 index 0000000..30d99dc --- /dev/null +++ b/体系学习班/class12/Code02_IsBST.java @@ -0,0 +1,125 @@ +package class12; + +import java.util.ArrayList; + +public class Code02_IsBST { + + public static class Node { + public int value; + public Node left; + public Node right; + + public Node(int data) { + this.value = data; + } + } + + public static boolean isBST1(Node head) { + if (head == null) { + return true; + } + ArrayList arr = new ArrayList<>(); + in(head, arr); + for (int i = 1; i < arr.size(); i++) { + if (arr.get(i).value <= arr.get(i - 1).value) { + return false; + } + } + return true; + } + + public static void in(Node head, ArrayList arr) { + if (head == null) { + return; + } + in(head.left, arr); + arr.add(head); + in(head.right, arr); + } + + public static boolean isBST2(Node head) { + if (head == null) { + return true; + } + return process(head).isBST; + } + + public static class Info { + public boolean isBST; + public int max; + public int min; + + public Info(boolean i, int ma, int mi) { + isBST = i; + max = ma; + min = mi; + } + + } + + public static Info process(Node x) { + if (x == null) { + return null; + } + Info leftInfo = process(x.left); + Info rightInfo = process(x.right); + int max = x.value; + if (leftInfo != null) { + max = Math.max(max, leftInfo.max); + } + if (rightInfo != null) { + max = Math.max(max, rightInfo.max); + } + int min = x.value; + if (leftInfo != null) { + min = Math.min(min, leftInfo.min); + } + if (rightInfo != null) { + min = Math.min(min, rightInfo.min); + } + boolean isBST = true; + if (leftInfo != null && !leftInfo.isBST) { + isBST = false; + } + if (rightInfo != null && !rightInfo.isBST) { + isBST = false; + } + if (leftInfo != null && leftInfo.max >= x.value) { + isBST = false; + } + if (rightInfo != null && rightInfo.min <= x.value) { + isBST = false; + } + return new Info(isBST, max, min); + } + + // for test + public static Node generateRandomBST(int maxLevel, int maxValue) { + return generate(1, maxLevel, maxValue); + } + + // for test + public static Node generate(int level, int maxLevel, int maxValue) { + if (level > maxLevel || Math.random() < 0.5) { + return null; + } + Node head = new Node((int) (Math.random() * maxValue)); + head.left = generate(level + 1, maxLevel, maxValue); + head.right = generate(level + 1, maxLevel, maxValue); + return head; + } + + public static void main(String[] args) { + int maxLevel = 4; + int maxValue = 100; + int testTimes = 1000000; + for (int i = 0; i < testTimes; i++) { + Node head = generateRandomBST(maxLevel, maxValue); + if (isBST1(head) != isBST2(head)) { + System.out.println("Oops!"); + } + } + System.out.println("finish!"); + } + +} diff --git a/体系学习班/class12/Code03_IsBalanced.java b/体系学习班/class12/Code03_IsBalanced.java new file mode 100644 index 0000000..e8a6c0f --- /dev/null +++ b/体系学习班/class12/Code03_IsBalanced.java @@ -0,0 +1,102 @@ +package class12; + +public class Code03_IsBalanced { + + public static class Node { + public int value; + public Node left; + public Node right; + + public Node(int data) { + this.value = data; + } + } + + public static boolean isBalanced1(Node head) { + boolean[] ans = new boolean[1]; + ans[0] = true; + process1(head, ans); + return ans[0]; + } + + public static int process1(Node head, boolean[] ans) { + if (!ans[0] || head == null) { + return -1; + } + int leftHeight = process1(head.left, ans); + int rightHeight = process1(head.right, ans); + if (Math.abs(leftHeight - rightHeight) > 1) { + ans[0] = false; + } + return Math.max(leftHeight, rightHeight) + 1; + } + + public static boolean isBalanced2(Node head) { + return process(head).isBalanced; + } + + public static class Info{ + public boolean isBalanced; + public int height; + + public Info(boolean i, int h) { + isBalanced = i; + height = h; + } + } + + public static Info process(Node x) { + if(x == null) { + return new Info(true, 0); + } + Info leftInfo = process(x.left); + Info rightInfo = process(x.right); + int height = Math.max(leftInfo.height, rightInfo.height) + 1; + boolean isBalanced = true; + if(!leftInfo.isBalanced) { + isBalanced = false; + } + if(!rightInfo.isBalanced) { + isBalanced = false; + } + if(Math.abs(leftInfo.height - rightInfo.height) > 1) { + isBalanced = false; + } + return new Info(isBalanced, height); + } + + + + + + + // for test + public static Node generateRandomBST(int maxLevel, int maxValue) { + return generate(1, maxLevel, maxValue); + } + + // for test + public static Node generate(int level, int maxLevel, int maxValue) { + if (level > maxLevel || Math.random() < 0.5) { + return null; + } + Node head = new Node((int) (Math.random() * maxValue)); + head.left = generate(level + 1, maxLevel, maxValue); + head.right = generate(level + 1, maxLevel, maxValue); + return head; + } + + public static void main(String[] args) { + int maxLevel = 5; + int maxValue = 100; + int testTimes = 1000000; + for (int i = 0; i < testTimes; i++) { + Node head = generateRandomBST(maxLevel, maxValue); + if (isBalanced1(head) != isBalanced2(head)) { + System.out.println("Oops!"); + } + } + System.out.println("finish!"); + } + +} diff --git a/体系学习班/class12/Code04_IsFull.java b/体系学习班/class12/Code04_IsFull.java new file mode 100644 index 0000000..a83c952 --- /dev/null +++ b/体系学习班/class12/Code04_IsFull.java @@ -0,0 +1,109 @@ +package class12; + +public class Code04_IsFull { + + public static class Node { + public int value; + public Node left; + public Node right; + + public Node(int data) { + this.value = data; + } + } + + // 第一种方法 + // 收集整棵树的高度h,和节点数n + // 只有满二叉树满足 : 2 ^ h - 1 == n + public static boolean isFull1(Node head) { + if (head == null) { + return true; + } + Info1 all = process1(head); + return (1 << all.height) - 1 == all.nodes; + } + + public static class Info1 { + public int height; + public int nodes; + + public Info1(int h, int n) { + height = h; + nodes = n; + } + } + + public static Info1 process1(Node head) { + if (head == null) { + return new Info1(0, 0); + } + Info1 leftInfo = process1(head.left); + Info1 rightInfo = process1(head.right); + int height = Math.max(leftInfo.height, rightInfo.height) + 1; + int nodes = leftInfo.nodes + rightInfo.nodes + 1; + return new Info1(height, nodes); + } + + // 第二种方法 + // 收集子树是否是满二叉树 + // 收集子树的高度 + // 左树满 && 右树满 && 左右树高度一样 -> 整棵树是满的 + public static boolean isFull2(Node head) { + if (head == null) { + return true; + } + return process2(head).isFull; + } + + public static class Info2 { + public boolean isFull; + public int height; + + public Info2(boolean f, int h) { + isFull = f; + height = h; + } + } + + public static Info2 process2(Node h) { + if (h == null) { + return new Info2(true, 0); + } + Info2 leftInfo = process2(h.left); + Info2 rightInfo = process2(h.right); + boolean isFull = leftInfo.isFull && rightInfo.isFull && leftInfo.height == rightInfo.height; + int height = Math.max(leftInfo.height, rightInfo.height) + 1; + return new Info2(isFull, height); + } + + // for test + public static Node generateRandomBST(int maxLevel, int maxValue) { + return generate(1, maxLevel, maxValue); + } + + // for test + public static Node generate(int level, int maxLevel, int maxValue) { + if (level > maxLevel || Math.random() < 0.5) { + return null; + } + Node head = new Node((int) (Math.random() * maxValue)); + head.left = generate(level + 1, maxLevel, maxValue); + head.right = generate(level + 1, maxLevel, maxValue); + return head; + } + + public static void main(String[] args) { + int maxLevel = 5; + int maxValue = 100; + int testTimes = 1000000; + System.out.println("测试开始"); + for (int i = 0; i < testTimes; i++) { + Node head = generateRandomBST(maxLevel, maxValue); + if (isFull1(head) != isFull2(head)) { + System.out.println("出错了!"); + } + } + System.out.println("测试结束"); + } + +} diff --git a/体系学习班/class12/Code05_MaxSubBSTSize.java b/体系学习班/class12/Code05_MaxSubBSTSize.java new file mode 100644 index 0000000..85ee0cd --- /dev/null +++ b/体系学习班/class12/Code05_MaxSubBSTSize.java @@ -0,0 +1,157 @@ +package class12; + +import java.util.ArrayList; + +// 在线测试链接 : https://leetcode.com/problems/largest-bst-subtree +public class Code05_MaxSubBSTSize { + + // 提交时不要提交这个类 + public static class TreeNode { + public int val; + public TreeNode left; + public TreeNode right; + + public TreeNode(int value) { + val = value; + } + } + + // 提交如下的largestBSTSubtree方法,可以直接通过 + public static int largestBSTSubtree(TreeNode head) { + if (head == null) { + return 0; + } + return process(head).maxBSTSubtreeSize; + } + + public static class Info { + public int maxBSTSubtreeSize; + public int allSize; + public int max; + public int min; + + public Info(int m, int a, int ma, int mi) { + maxBSTSubtreeSize = m; + allSize = a; + max = ma; + min = mi; + } + } + + public static Info process(TreeNode x) { + if (x == null) { + return null; + } + Info leftInfo = process(x.left); + Info rightInfo = process(x.right); + int max = x.val; + int min = x.val; + int allSize = 1; + if (leftInfo != null) { + max = Math.max(leftInfo.max, max); + min = Math.min(leftInfo.min, min); + allSize += leftInfo.allSize; + } + if (rightInfo != null) { + max = Math.max(rightInfo.max, max); + min = Math.min(rightInfo.min, min); + allSize += rightInfo.allSize; + } + int p1 = -1; + if (leftInfo != null) { + p1 = leftInfo.maxBSTSubtreeSize; + } + int p2 = -1; + if (rightInfo != null) { + p2 = rightInfo.maxBSTSubtreeSize; + } + int p3 = -1; + boolean leftBST = leftInfo == null ? true : (leftInfo.maxBSTSubtreeSize == leftInfo.allSize); + boolean rightBST = rightInfo == null ? true : (rightInfo.maxBSTSubtreeSize == rightInfo.allSize); + if (leftBST && rightBST) { + boolean leftMaxLessX = leftInfo == null ? true : (leftInfo.max < x.val); + boolean rightMinMoreX = rightInfo == null ? true : (x.val < rightInfo.min); + if (leftMaxLessX && rightMinMoreX) { + int leftSize = leftInfo == null ? 0 : leftInfo.allSize; + int rightSize = rightInfo == null ? 0 : rightInfo.allSize; + p3 = leftSize + rightSize + 1; + } + } + return new Info(Math.max(p1, Math.max(p2, p3)), allSize, max, min); + } + + // 为了验证 + // 对数器方法 + public static int right(TreeNode head) { + if (head == null) { + return 0; + } + int h = getBSTSize(head); + if (h != 0) { + return h; + } + return Math.max(right(head.left), right(head.right)); + } + + // 为了验证 + // 对数器方法 + public static int getBSTSize(TreeNode head) { + if (head == null) { + return 0; + } + ArrayList arr = new ArrayList<>(); + in(head, arr); + for (int i = 1; i < arr.size(); i++) { + if (arr.get(i).val <= arr.get(i - 1).val) { + return 0; + } + } + return arr.size(); + } + + // 为了验证 + // 对数器方法 + public static void in(TreeNode head, ArrayList arr) { + if (head == null) { + return; + } + in(head.left, arr); + arr.add(head); + in(head.right, arr); + } + + // 为了验证 + // 对数器方法 + public static TreeNode generateRandomBST(int maxLevel, int maxValue) { + return generate(1, maxLevel, maxValue); + } + + // 为了验证 + // 对数器方法 + public static TreeNode generate(int level, int maxLevel, int maxValue) { + if (level > maxLevel || Math.random() < 0.5) { + return null; + } + TreeNode head = new TreeNode((int) (Math.random() * maxValue)); + head.left = generate(level + 1, maxLevel, maxValue); + head.right = generate(level + 1, maxLevel, maxValue); + return head; + } + + // 为了验证 + // 对数器方法 + public static void main(String[] args) { + int maxLevel = 4; + int maxValue = 100; + int testTimes = 1000000; + System.out.println("测试开始"); + for (int i = 0; i < testTimes; i++) { + TreeNode head = generateRandomBST(maxLevel, maxValue); + if (largestBSTSubtree(head) != right(head)) { + System.out.println("出错了!"); + } + } + System.out.println("测试结束"); + } + +} diff --git a/体系学习班/class12/Code06_MaxDistance.java b/体系学习班/class12/Code06_MaxDistance.java new file mode 100644 index 0000000..8c82c0e --- /dev/null +++ b/体系学习班/class12/Code06_MaxDistance.java @@ -0,0 +1,180 @@ +package class12; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; + +public class Code06_MaxDistance { + + public static class Node { + public int value; + public Node left; + public Node right; + + public Node(int data) { + this.value = data; + } + } + + public static int maxDistance1(Node head) { + if (head == null) { + return 0; + } + ArrayList arr = getPrelist(head); + HashMap parentMap = getParentMap(head); + int max = 0; + for (int i = 0; i < arr.size(); i++) { + for (int j = i; j < arr.size(); j++) { + max = Math.max(max, distance(parentMap, arr.get(i), arr.get(j))); + } + } + return max; + } + + public static ArrayList getPrelist(Node head) { + ArrayList arr = new ArrayList<>(); + fillPrelist(head, arr); + return arr; + } + + public static void fillPrelist(Node head, ArrayList arr) { + if (head == null) { + return; + } + arr.add(head); + fillPrelist(head.left, arr); + fillPrelist(head.right, arr); + } + + public static HashMap getParentMap(Node head) { + HashMap map = new HashMap<>(); + map.put(head, null); + fillParentMap(head, map); + return map; + } + + public static void fillParentMap(Node head, HashMap parentMap) { + if (head.left != null) { + parentMap.put(head.left, head); + fillParentMap(head.left, parentMap); + } + if (head.right != null) { + parentMap.put(head.right, head); + fillParentMap(head.right, parentMap); + } + } + + public static int distance(HashMap parentMap, Node o1, Node o2) { + HashSet o1Set = new HashSet<>(); + Node cur = o1; + o1Set.add(cur); + while (parentMap.get(cur) != null) { + cur = parentMap.get(cur); + o1Set.add(cur); + } + cur = o2; + while (!o1Set.contains(cur)) { + cur = parentMap.get(cur); + } + Node lowestAncestor = cur; + cur = o1; + int distance1 = 1; + while (cur != lowestAncestor) { + cur = parentMap.get(cur); + distance1++; + } + cur = o2; + int distance2 = 1; + while (cur != lowestAncestor) { + cur = parentMap.get(cur); + distance2++; + } + return distance1 + distance2 - 1; + } + +// public static int maxDistance2(Node head) { +// return process(head).maxDistance; +// } +// +// public static class Info { +// public int maxDistance; +// public int height; +// +// public Info(int dis, int h) { +// maxDistance = dis; +// height = h; +// } +// } +// +// public static Info process(Node X) { +// if (X == null) { +// return new Info(0, 0); +// } +// Info leftInfo = process(X.left); +// Info rightInfo = process(X.right); +// int height = Math.max(leftInfo.height, rightInfo.height) + 1; +// int maxDistance = Math.max( +// Math.max(leftInfo.maxDistance, rightInfo.maxDistance), +// leftInfo.height + rightInfo.height + 1); +// return new Info(maxDistance, height); +// } + + public static int maxDistance2(Node head) { + return process(head).maxDistance; + } + + public static class Info { + public int maxDistance; + public int height; + + public Info(int m, int h) { + maxDistance = m; + height = h; + } + + } + + public static Info process(Node x) { + if (x == null) { + return new Info(0, 0); + } + Info leftInfo = process(x.left); + Info rightInfo = process(x.right); + int height = Math.max(leftInfo.height, rightInfo.height) + 1; + int p1 = leftInfo.maxDistance; + int p2 = rightInfo.maxDistance; + int p3 = leftInfo.height + rightInfo.height + 1; + int maxDistance = Math.max(Math.max(p1, p2), p3); + return new Info(maxDistance, height); + } + + // for test + public static Node generateRandomBST(int maxLevel, int maxValue) { + return generate(1, maxLevel, maxValue); + } + + // for test + public static Node generate(int level, int maxLevel, int maxValue) { + if (level > maxLevel || Math.random() < 0.5) { + return null; + } + Node head = new Node((int) (Math.random() * maxValue)); + head.left = generate(level + 1, maxLevel, maxValue); + head.right = generate(level + 1, maxLevel, maxValue); + return head; + } + + public static void main(String[] args) { + int maxLevel = 4; + int maxValue = 100; + int testTimes = 1000000; + for (int i = 0; i < testTimes; i++) { + Node head = generateRandomBST(maxLevel, maxValue); + if (maxDistance1(head) != maxDistance2(head)) { + System.out.println("Oops!"); + } + } + System.out.println("finish!"); + } + +} diff --git a/体系学习班/class13/Code01_IsCBT.java b/体系学习班/class13/Code01_IsCBT.java new file mode 100644 index 0000000..c991c8e --- /dev/null +++ b/体系学习班/class13/Code01_IsCBT.java @@ -0,0 +1,120 @@ +package class13; + +import java.util.LinkedList; + +// 测试链接 : https://leetcode.com/problems/check-completeness-of-a-binary-tree/ + +public class Code01_IsCBT { + + // 不要提交这个类 + public static class TreeNode { + public int val; + public TreeNode left; + public TreeNode right; + + public TreeNode(int v) { + val = v; + } + } + + public static boolean isCompleteTree1(TreeNode head) { + if (head == null) { + return true; + } + LinkedList queue = new LinkedList<>(); + // 是否遇到过左右两个孩子不双全的节点 + boolean leaf = false; + TreeNode l = null; + TreeNode r = null; + queue.add(head); + while (!queue.isEmpty()) { + head = queue.poll(); + l = head.left; + r = head.right; + if ( + // 如果遇到了不双全的节点之后,又发现当前节点不是叶节点 + (leaf && (l != null || r != null)) || (l == null && r != null) + + ) { + return false; + } + if (l != null) { + queue.add(l); + } + if (r != null) { + queue.add(r); + } + if (l == null || r == null) { + leaf = true; + } + } + return true; + } + + public static boolean isCompleteTree2(TreeNode head) { + return process(head).isCBT; + } + + public static class Info { + public boolean isFull; + public boolean isCBT; + public int height; + + public Info(boolean full, boolean cbt, int h) { + isFull = full; + isCBT = cbt; + height = h; + } + } + + public static Info process(TreeNode x) { + if (x == null) { + return new Info(true, true, 0); + } + Info leftInfo = process(x.left); + Info rightInfo = process(x.right); + int height = Math.max(leftInfo.height, rightInfo.height) + 1; + boolean isFull = leftInfo.isFull && rightInfo.isFull && leftInfo.height == rightInfo.height; + boolean isCBT = false; + if (leftInfo.isFull && rightInfo.isFull && leftInfo.height == rightInfo.height) { + isCBT = true; + } else if (leftInfo.isCBT && rightInfo.isFull && leftInfo.height == rightInfo.height + 1) { + isCBT = true; + } else if (leftInfo.isFull && rightInfo.isFull && leftInfo.height == rightInfo.height + 1) { + isCBT = true; + } else if (leftInfo.isFull && rightInfo.isCBT && leftInfo.height == rightInfo.height) { + isCBT = true; + } + return new Info(isFull, isCBT, height); + } + + // for test + public static TreeNode generateRandomBST(int maxLevel, int maxValue) { + return generate(1, maxLevel, maxValue); + } + + // for test + public static TreeNode generate(int level, int maxLevel, int maxValue) { + if (level > maxLevel || Math.random() < 0.5) { + return null; + } + TreeNode head = new TreeNode((int) (Math.random() * maxValue)); + head.left = generate(level + 1, maxLevel, maxValue); + head.right = generate(level + 1, maxLevel, maxValue); + return head; + } + + public static void main(String[] args) { + int maxLevel = 5; + int maxValue = 100; + int testTimes = 1000000; + for (int i = 0; i < testTimes; i++) { + TreeNode head = generateRandomBST(maxLevel, maxValue); + if (isCompleteTree1(head) != isCompleteTree2(head)) { + System.out.println("Oops!"); + } + } + System.out.println("finish!"); + } + +} diff --git a/体系学习班/class13/Code02_MaxSubBSTHead.java b/体系学习班/class13/Code02_MaxSubBSTHead.java new file mode 100644 index 0000000..e64c37a --- /dev/null +++ b/体系学习班/class13/Code02_MaxSubBSTHead.java @@ -0,0 +1,136 @@ +package class13; + +import java.util.ArrayList; + +public class Code02_MaxSubBSTHead { + + public static class Node { + public int value; + public Node left; + public Node right; + + public Node(int data) { + this.value = data; + } + } + + public static int getBSTSize(Node head) { + if (head == null) { + return 0; + } + ArrayList arr = new ArrayList<>(); + in(head, arr); + for (int i = 1; i < arr.size(); i++) { + if (arr.get(i).value <= arr.get(i - 1).value) { + return 0; + } + } + return arr.size(); + } + + public static void in(Node head, ArrayList arr) { + if (head == null) { + return; + } + in(head.left, arr); + arr.add(head); + in(head.right, arr); + } + + public static Node maxSubBSTHead1(Node head) { + if (head == null) { + return null; + } + if (getBSTSize(head) != 0) { + return head; + } + Node leftAns = maxSubBSTHead1(head.left); + Node rightAns = maxSubBSTHead1(head.right); + return getBSTSize(leftAns) >= getBSTSize(rightAns) ? leftAns : rightAns; + } + + public static Node maxSubBSTHead2(Node head) { + if (head == null) { + return null; + } + return process(head).maxSubBSTHead; + } + + // 每一棵子树 + public static class Info { + public Node maxSubBSTHead; + public int maxSubBSTSize; + public int min; + public int max; + + public Info(Node h, int size, int mi, int ma) { + maxSubBSTHead = h; + maxSubBSTSize = size; + min = mi; + max = ma; + } + } + + public static Info process(Node X) { + if (X == null) { + return null; + } + Info leftInfo = process(X.left); + Info rightInfo = process(X.right); + int min = X.value; + int max = X.value; + Node maxSubBSTHead = null; + int maxSubBSTSize = 0; + if (leftInfo != null) { + min = Math.min(min, leftInfo.min); + max = Math.max(max, leftInfo.max); + maxSubBSTHead = leftInfo.maxSubBSTHead; + maxSubBSTSize = leftInfo.maxSubBSTSize; + } + if (rightInfo != null) { + min = Math.min(min, rightInfo.min); + max = Math.max(max, rightInfo.max); + if (rightInfo.maxSubBSTSize > maxSubBSTSize) { + maxSubBSTHead = rightInfo.maxSubBSTHead; + maxSubBSTSize = rightInfo.maxSubBSTSize; + } + } + if ((leftInfo == null ? true : (leftInfo.maxSubBSTHead == X.left && leftInfo.max < X.value)) + && (rightInfo == null ? true : (rightInfo.maxSubBSTHead == X.right && rightInfo.min > X.value))) { + maxSubBSTHead = X; + maxSubBSTSize = (leftInfo == null ? 0 : leftInfo.maxSubBSTSize) + + (rightInfo == null ? 0 : rightInfo.maxSubBSTSize) + 1; + } + return new Info(maxSubBSTHead, maxSubBSTSize, min, max); + } + + // for test + public static Node generateRandomBST(int maxLevel, int maxValue) { + return generate(1, maxLevel, maxValue); + } + + // for test + public static Node generate(int level, int maxLevel, int maxValue) { + if (level > maxLevel || Math.random() < 0.5) { + return null; + } + Node head = new Node((int) (Math.random() * maxValue)); + head.left = generate(level + 1, maxLevel, maxValue); + head.right = generate(level + 1, maxLevel, maxValue); + return head; + } + + public static void main(String[] args) { + int maxLevel = 4; + int maxValue = 100; + int testTimes = 1000000; + for (int i = 0; i < testTimes; i++) { + Node head = generateRandomBST(maxLevel, maxValue); + if (maxSubBSTHead1(head) != maxSubBSTHead2(head)) { + System.out.println("Oops!"); + } + } + System.out.println("finish!"); + } + +} diff --git a/体系学习班/class13/Code03_lowestAncestor.java b/体系学习班/class13/Code03_lowestAncestor.java new file mode 100644 index 0000000..e2a9d5f --- /dev/null +++ b/体系学习班/class13/Code03_lowestAncestor.java @@ -0,0 +1,141 @@ +package class13; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; + +public class Code03_lowestAncestor { + + public static class Node { + public int value; + public Node left; + public Node right; + + public Node(int data) { + this.value = data; + } + } + + public static Node lowestAncestor1(Node head, Node o1, Node o2) { + if (head == null) { + return null; + } + // key的父节点是value + HashMap parentMap = new HashMap<>(); + parentMap.put(head, null); + fillParentMap(head, parentMap); + HashSet o1Set = new HashSet<>(); + Node cur = o1; + o1Set.add(cur); + while (parentMap.get(cur) != null) { + cur = parentMap.get(cur); + o1Set.add(cur); + } + cur = o2; + while (!o1Set.contains(cur)) { + cur = parentMap.get(cur); + } + return cur; + } + + public static void fillParentMap(Node head, HashMap parentMap) { + if (head.left != null) { + parentMap.put(head.left, head); + fillParentMap(head.left, parentMap); + } + if (head.right != null) { + parentMap.put(head.right, head); + fillParentMap(head.right, parentMap); + } + } + + public static Node lowestAncestor2(Node head, Node a, Node b) { + return process(head, a, b).ans; + } + + public static class Info { + public boolean findA; + public boolean findB; + public Node ans; + + public Info(boolean fA, boolean fB, Node an) { + findA = fA; + findB = fB; + ans = an; + } + } + + public static Info process(Node x, Node a, Node b) { + if (x == null) { + return new Info(false, false, null); + } + Info leftInfo = process(x.left, a, b); + Info rightInfo = process(x.right, a, b); + boolean findA = (x == a) || leftInfo.findA || rightInfo.findA; + boolean findB = (x == b) || leftInfo.findB || rightInfo.findB; + Node ans = null; + if (leftInfo.ans != null) { + ans = leftInfo.ans; + } else if (rightInfo.ans != null) { + ans = rightInfo.ans; + } else { + if (findA && findB) { + ans = x; + } + } + return new Info(findA, findB, ans); + } + + // for test + public static Node generateRandomBST(int maxLevel, int maxValue) { + return generate(1, maxLevel, maxValue); + } + + // for test + public static Node generate(int level, int maxLevel, int maxValue) { + if (level > maxLevel || Math.random() < 0.5) { + return null; + } + Node head = new Node((int) (Math.random() * maxValue)); + head.left = generate(level + 1, maxLevel, maxValue); + head.right = generate(level + 1, maxLevel, maxValue); + return head; + } + + // for test + public static Node pickRandomOne(Node head) { + if (head == null) { + return null; + } + ArrayList arr = new ArrayList<>(); + fillPrelist(head, arr); + int randomIndex = (int) (Math.random() * arr.size()); + return arr.get(randomIndex); + } + + // for test + public static void fillPrelist(Node head, ArrayList arr) { + if (head == null) { + return; + } + arr.add(head); + fillPrelist(head.left, arr); + fillPrelist(head.right, arr); + } + + public static void main(String[] args) { + int maxLevel = 4; + int maxValue = 100; + int testTimes = 1000000; + for (int i = 0; i < testTimes; i++) { + Node head = generateRandomBST(maxLevel, maxValue); + Node o1 = pickRandomOne(head); + Node o2 = pickRandomOne(head); + if (lowestAncestor1(head, o1, o2) != lowestAncestor2(head, o1, o2)) { + System.out.println("Oops!"); + } + } + System.out.println("finish!"); + } + +} diff --git a/体系学习班/class13/Code04_MaxHappy.java b/体系学习班/class13/Code04_MaxHappy.java new file mode 100644 index 0000000..3cb21e7 --- /dev/null +++ b/体系学习班/class13/Code04_MaxHappy.java @@ -0,0 +1,116 @@ +package class13; + +import java.util.ArrayList; +import java.util.List; + +public class Code04_MaxHappy { + + public static class Employee { + public int happy; + public List nexts; + + public Employee(int h) { + happy = h; + nexts = new ArrayList<>(); + } + + } + + public static int maxHappy1(Employee boss) { + if (boss == null) { + return 0; + } + return process1(boss, false); + } + + // 当前来到的节点叫cur, + // up表示cur的上级是否来, + // 该函数含义: + // 如果up为true,表示在cur上级已经确定来,的情况下,cur整棵树能够提供最大的快乐值是多少? + // 如果up为false,表示在cur上级已经确定不来,的情况下,cur整棵树能够提供最大的快乐值是多少? + public static int process1(Employee cur, boolean up) { + if (up) { // 如果cur的上级来的话,cur没得选,只能不来 + int ans = 0; + for (Employee next : cur.nexts) { + ans += process1(next, false); + } + return ans; + } else { // 如果cur的上级不来的话,cur可以选,可以来也可以不来 + int p1 = cur.happy; + int p2 = 0; + for (Employee next : cur.nexts) { + p1 += process1(next, true); + p2 += process1(next, false); + } + return Math.max(p1, p2); + } + } + + public static int maxHappy2(Employee head) { + Info allInfo = process(head); + return Math.max(allInfo.no, allInfo.yes); + } + + public static class Info { + public int no; + public int yes; + + public Info(int n, int y) { + no = n; + yes = y; + } + } + + public static Info process(Employee x) { + if (x == null) { + return new Info(0, 0); + } + int no = 0; + int yes = x.happy; + for (Employee next : x.nexts) { + Info nextInfo = process(next); + no += Math.max(nextInfo.no, nextInfo.yes); + yes += nextInfo.no; + + } + return new Info(no, yes); + } + + // for test + public static Employee genarateBoss(int maxLevel, int maxNexts, int maxHappy) { + if (Math.random() < 0.02) { + return null; + } + Employee boss = new Employee((int) (Math.random() * (maxHappy + 1))); + genarateNexts(boss, 1, maxLevel, maxNexts, maxHappy); + return boss; + } + + // for test + public static void genarateNexts(Employee e, int level, int maxLevel, int maxNexts, int maxHappy) { + if (level > maxLevel) { + return; + } + int nextsSize = (int) (Math.random() * (maxNexts + 1)); + for (int i = 0; i < nextsSize; i++) { + Employee next = new Employee((int) (Math.random() * (maxHappy + 1))); + e.nexts.add(next); + genarateNexts(next, level + 1, maxLevel, maxNexts, maxHappy); + } + } + + public static void main(String[] args) { + int maxLevel = 4; + int maxNexts = 7; + int maxHappy = 100; + int testTimes = 100000; + for (int i = 0; i < testTimes; i++) { + Employee boss = genarateBoss(maxLevel, maxNexts, maxHappy); + if (maxHappy1(boss) != maxHappy2(boss)) { + System.out.println("Oops!"); + } + } + System.out.println("finish!"); + } + +} diff --git a/体系学习班/class13/Code05_LowestLexicography.java b/体系学习班/class13/Code05_LowestLexicography.java new file mode 100644 index 0000000..13ceedd --- /dev/null +++ b/体系学习班/class13/Code05_LowestLexicography.java @@ -0,0 +1,116 @@ +package class13; + +import java.util.Arrays; +import java.util.Comparator; +import java.util.TreeSet; + +public class Code05_LowestLexicography { + + public static String lowestString1(String[] strs) { + if (strs == null || strs.length == 0) { + return ""; + } + TreeSet ans = process(strs); + return ans.size() == 0 ? "" : ans.first(); + } + + // strs中所有字符串全排列,返回所有可能的结果 + public static TreeSet process(String[] strs) { + TreeSet ans = new TreeSet<>(); + if (strs.length == 0) { + ans.add(""); + return ans; + } + for (int i = 0; i < strs.length; i++) { + String first = strs[i]; + String[] nexts = removeIndexString(strs, i); + TreeSet next = process(nexts); + for (String cur : next) { + ans.add(first + cur); + } + } + return ans; + } + + // {"abc", "cks", "bct"} + // 0 1 2 + // removeIndexString(arr , 1) -> {"abc", "bct"} + public static String[] removeIndexString(String[] arr, int index) { + int N = arr.length; + String[] ans = new String[N - 1]; + int ansIndex = 0; + for (int i = 0; i < N; i++) { + if (i != index) { + ans[ansIndex++] = arr[i]; + } + } + return ans; + } + + public static class MyComparator implements Comparator { + @Override + public int compare(String a, String b) { + return (a + b).compareTo(b + a); + } + } + + public static String lowestString2(String[] strs) { + if (strs == null || strs.length == 0) { + return ""; + } + Arrays.sort(strs, new MyComparator()); + String res = ""; + for (int i = 0; i < strs.length; i++) { + res += strs[i]; + } + return res; + } + + // for test + public static String generateRandomString(int strLen) { + char[] ans = new char[(int) (Math.random() * strLen) + 1]; + for (int i = 0; i < ans.length; i++) { + int value = (int) (Math.random() * 5); + ans[i] = (Math.random() <= 0.5) ? (char) (65 + value) : (char) (97 + value); + } + return String.valueOf(ans); + } + + // for test + public static String[] generateRandomStringArray(int arrLen, int strLen) { + String[] ans = new String[(int) (Math.random() * arrLen) + 1]; + for (int i = 0; i < ans.length; i++) { + ans[i] = generateRandomString(strLen); + } + return ans; + } + + // for test + public static String[] copyStringArray(String[] arr) { + String[] ans = new String[arr.length]; + for (int i = 0; i < ans.length; i++) { + ans[i] = String.valueOf(arr[i]); + } + return ans; + } + + public static void main(String[] args) { + int arrLen = 6; + int strLen = 5; + int testTimes = 10000; + System.out.println("test begin"); + for (int i = 0; i < testTimes; i++) { + String[] arr1 = generateRandomStringArray(arrLen, strLen); + String[] arr2 = copyStringArray(arr1); + if (!lowestString1(arr1).equals(lowestString2(arr2))) { + for (String str : arr1) { + System.out.print(str + ","); + } + System.out.println(); + System.out.println("Oops!"); + } + } + System.out.println("finish!"); + } + +} diff --git a/体系学习班/class14/Code01_Light.java b/体系学习班/class14/Code01_Light.java new file mode 100644 index 0000000..7cbfde2 --- /dev/null +++ b/体系学习班/class14/Code01_Light.java @@ -0,0 +1,105 @@ +package class14; + +import java.util.HashSet; + +public class Code01_Light { + + public static int minLight1(String road) { + if (road == null || road.length() == 0) { + return 0; + } + return process(road.toCharArray(), 0, new HashSet<>()); + } + + // str[index....]位置,自由选择放灯还是不放灯 + // str[0..index-1]位置呢?已经做完决定了,那些放了灯的位置,存在lights里 + // 要求选出能照亮所有.的方案,并且在这些有效的方案中,返回最少需要几个灯 + public static int process(char[] str, int index, HashSet lights) { + if (index == str.length) { // 结束的时候 + for (int i = 0; i < str.length; i++) { + if (str[i] != 'X') { // 当前位置是点的话 + if (!lights.contains(i - 1) && !lights.contains(i) && !lights.contains(i + 1)) { + return Integer.MAX_VALUE; + } + } + } + return lights.size(); + } else { // str还没结束 + // i X . + int no = process(str, index + 1, lights); + int yes = Integer.MAX_VALUE; + if (str[index] == '.') { + lights.add(index); + yes = process(str, index + 1, lights); + lights.remove(index); + } + return Math.min(no, yes); + } + } + + public static int minLight2(String road) { + char[] str = road.toCharArray(); + int i = 0; + int light = 0; + while (i < str.length) { + if (str[i] == 'X') { + i++; + } else { + light++; + if (i + 1 == str.length) { + break; + } else { // 有i位置 i+ 1 X . + if (str[i + 1] == 'X') { + i = i + 2; + } else { + i = i + 3; + } + } + } + } + return light; + } + + // 更简洁的解法 + // 两个X之间,数一下.的数量,然后除以3,向上取整 + // 把灯数累加 + public static int minLight3(String road) { + char[] str = road.toCharArray(); + int cur = 0; + int light = 0; + for (char c : str) { + if (c == 'X') { + light += (cur + 2) / 3; + cur = 0; + } else { + cur++; + } + } + light += (cur + 2) / 3; + return light; + } + + // for test + public static String randomString(int len) { + char[] res = new char[(int) (Math.random() * len) + 1]; + for (int i = 0; i < res.length; i++) { + res[i] = Math.random() < 0.5 ? 'X' : '.'; + } + return String.valueOf(res); + } + + public static void main(String[] args) { + int len = 20; + int testTime = 100000; + for (int i = 0; i < testTime; i++) { + String test = randomString(len); + int ans1 = minLight1(test); + int ans2 = minLight2(test); + int ans3 = minLight3(test); + if (ans1 != ans2 || ans1 != ans3) { + System.out.println("oops!"); + } + } + System.out.println("finish!"); + } +} diff --git a/体系学习班/class14/Code02_LessMoneySplitGold.java b/体系学习班/class14/Code02_LessMoneySplitGold.java new file mode 100644 index 0000000..682fa12 --- /dev/null +++ b/体系学习班/class14/Code02_LessMoneySplitGold.java @@ -0,0 +1,79 @@ +package class14; + +import java.util.PriorityQueue; + +public class Code02_LessMoneySplitGold { + + // 纯暴力! + public static int lessMoney1(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + return process(arr, 0); + } + + // 等待合并的数都在arr里,pre之前的合并行为产生了多少总代价 + // arr中只剩一个数字的时候,停止合并,返回最小的总代价 + public static int process(int[] arr, int pre) { + if (arr.length == 1) { + return pre; + } + int ans = Integer.MAX_VALUE; + for (int i = 0; i < arr.length; i++) { + for (int j = i + 1; j < arr.length; j++) { + ans = Math.min(ans, process(copyAndMergeTwo(arr, i, j), pre + arr[i] + arr[j])); + } + } + return ans; + } + + public static int[] copyAndMergeTwo(int[] arr, int i, int j) { + int[] ans = new int[arr.length - 1]; + int ansi = 0; + for (int arri = 0; arri < arr.length; arri++) { + if (arri != i && arri != j) { + ans[ansi++] = arr[arri]; + } + } + ans[ansi] = arr[i] + arr[j]; + return ans; + } + + public static int lessMoney2(int[] arr) { + PriorityQueue pQ = new PriorityQueue<>(); + for (int i = 0; i < arr.length; i++) { + pQ.add(arr[i]); + } + int sum = 0; + int cur = 0; + while (pQ.size() > 1) { + cur = pQ.poll() + pQ.poll(); + sum += cur; + pQ.add(cur); + } + return sum; + } + + // for test + public static int[] generateRandomArray(int maxSize, int maxValue) { + int[] arr = new int[(int) ((maxSize + 1) * Math.random())]; + for (int i = 0; i < arr.length; i++) { + arr[i] = (int) (Math.random() * (maxValue + 1)); + } + return arr; + } + + public static void main(String[] args) { + int testTime = 100000; + int maxSize = 6; + int maxValue = 1000; + for (int i = 0; i < testTime; i++) { + int[] arr = generateRandomArray(maxSize, maxValue); + if (lessMoney1(arr) != lessMoney2(arr)) { + System.out.println("Oops!"); + } + } + System.out.println("finish!"); + } + +} diff --git a/体系学习班/class14/Code03_BestArrange.java b/体系学习班/class14/Code03_BestArrange.java new file mode 100644 index 0000000..c8a79ff --- /dev/null +++ b/体系学习班/class14/Code03_BestArrange.java @@ -0,0 +1,111 @@ +package class14; + +import java.util.Arrays; +import java.util.Comparator; + +public class Code03_BestArrange { + + public static class Program { + public int start; + public int end; + + public Program(int start, int end) { + this.start = start; + this.end = end; + } + } + + // 暴力!所有情况都尝试! + public static int bestArrange1(Program[] programs) { + if (programs == null || programs.length == 0) { + return 0; + } + return process(programs, 0, 0); + } + + // 还剩下的会议都放在programs里 + // done之前已经安排了多少会议的数量 + // timeLine目前来到的时间点是什么 + + // 目前来到timeLine的时间点,已经安排了done多的会议,剩下的会议programs可以自由安排 + // 返回能安排的最多会议数量 + public static int process(Program[] programs, int done, int timeLine) { + if (programs.length == 0) { + return done; + } + // 还剩下会议 + int max = done; + // 当前安排的会议是什么会,每一个都枚举 + for (int i = 0; i < programs.length; i++) { + if (programs[i].start >= timeLine) { + Program[] next = copyButExcept(programs, i); + max = Math.max(max, process(next, done + 1, programs[i].end)); + } + } + return max; + } + + public static Program[] copyButExcept(Program[] programs, int i) { + Program[] ans = new Program[programs.length - 1]; + int index = 0; + for (int k = 0; k < programs.length; k++) { + if (k != i) { + ans[index++] = programs[k]; + } + } + return ans; + } + + // 会议的开始时间和结束时间,都是数值,不会 < 0 + public static int bestArrange2(Program[] programs) { + Arrays.sort(programs, new ProgramComparator()); + int timeLine = 0; + int result = 0; + // 依次遍历每一个会议,结束时间早的会议先遍历 + for (int i = 0; i < programs.length; i++) { + if (timeLine <= programs[i].start) { + result++; + timeLine = programs[i].end; + } + } + return result; + } + + public static class ProgramComparator implements Comparator { + + @Override + public int compare(Program o1, Program o2) { + return o1.end - o2.end; + } + + } + + // for test + public static Program[] generatePrograms(int programSize, int timeMax) { + Program[] ans = new Program[(int) (Math.random() * (programSize + 1))]; + for (int i = 0; i < ans.length; i++) { + int r1 = (int) (Math.random() * (timeMax + 1)); + int r2 = (int) (Math.random() * (timeMax + 1)); + if (r1 == r2) { + ans[i] = new Program(r1, r1 + 1); + } else { + ans[i] = new Program(Math.min(r1, r2), Math.max(r1, r2)); + } + } + return ans; + } + + public static void main(String[] args) { + int programSize = 12; + int timeMax = 20; + int timeTimes = 1000000; + for (int i = 0; i < timeTimes; i++) { + Program[] programs = generatePrograms(programSize, timeMax); + if (bestArrange1(programs) != bestArrange2(programs)) { + System.out.println("Oops!"); + } + } + System.out.println("finish!"); + } + +} diff --git a/体系学习班/class14/Code04_IPO.java b/体系学习班/class14/Code04_IPO.java new file mode 100644 index 0000000..0ac3b85 --- /dev/null +++ b/体系学习班/class14/Code04_IPO.java @@ -0,0 +1,58 @@ +package class14; + +import java.util.Comparator; +import java.util.PriorityQueue; + +public class Code04_IPO { + + // 最多K个项目 + // W是初始资金 + // Profits[] Capital[] 一定等长 + // 返回最终最大的资金 + public static int findMaximizedCapital(int K, int W, int[] Profits, int[] Capital) { + PriorityQueue minCostQ = new PriorityQueue<>(new MinCostComparator()); + PriorityQueue maxProfitQ = new PriorityQueue<>(new MaxProfitComparator()); + for (int i = 0; i < Profits.length; i++) { + minCostQ.add(new Program(Profits[i], Capital[i])); + } + for (int i = 0; i < K; i++) { + while (!minCostQ.isEmpty() && minCostQ.peek().c <= W) { + maxProfitQ.add(minCostQ.poll()); + } + if (maxProfitQ.isEmpty()) { + return W; + } + W += maxProfitQ.poll().p; + } + return W; + } + + public static class Program { + public int p; + public int c; + + public Program(int p, int c) { + this.p = p; + this.c = c; + } + } + + public static class MinCostComparator implements Comparator { + + @Override + public int compare(Program o1, Program o2) { + return o1.c - o2.c; + } + + } + + public static class MaxProfitComparator implements Comparator { + + @Override + public int compare(Program o1, Program o2) { + return o2.p - o1.p; + } + + } + +} diff --git a/体系学习班/class14/Code05_UnionFind.java b/体系学习班/class14/Code05_UnionFind.java new file mode 100644 index 0000000..0d9e246 --- /dev/null +++ b/体系学习班/class14/Code05_UnionFind.java @@ -0,0 +1,70 @@ +package class14; + +import java.util.HashMap; +import java.util.List; +import java.util.Stack; + +public class Code05_UnionFind { + + public static class Node { + V value; + + public Node(V v) { + value = v; + } + } + + public static class UnionFind { + public HashMap> nodes; + public HashMap, Node> parents; + public HashMap, Integer> sizeMap; + + public UnionFind(List values) { + nodes = new HashMap<>(); + parents = new HashMap<>(); + sizeMap = new HashMap<>(); + for (V cur : values) { + Node node = new Node<>(cur); + nodes.put(cur, node); + parents.put(node, node); + sizeMap.put(node, 1); + } + } + + // 给你一个节点,请你往上到不能再往上,把代表返回 + public Node findFather(Node cur) { + Stack> path = new Stack<>(); + while (cur != parents.get(cur)) { + path.push(cur); + cur = parents.get(cur); + } + while (!path.isEmpty()) { + parents.put(path.pop(), cur); + } + return cur; + } + + public boolean isSameSet(V a, V b) { + return findFather(nodes.get(a)) == findFather(nodes.get(b)); + } + + public void union(V a, V b) { + Node aHead = findFather(nodes.get(a)); + Node bHead = findFather(nodes.get(b)); + if (aHead != bHead) { + int aSetSize = sizeMap.get(aHead); + int bSetSize = sizeMap.get(bHead); + Node big = aSetSize >= bSetSize ? aHead : bHead; + Node small = big == aHead ? bHead : aHead; + parents.put(small, big); + sizeMap.put(big, aSetSize + bSetSize); + sizeMap.remove(small); + } + } + + public int sets() { + return sizeMap.size(); + } + + } +} diff --git a/体系学习班/class15/Code01_FriendCircles.java b/体系学习班/class15/Code01_FriendCircles.java new file mode 100644 index 0000000..dd4253d --- /dev/null +++ b/体系学习班/class15/Code01_FriendCircles.java @@ -0,0 +1,78 @@ +package class15; + +// 本题为leetcode原题 +// 测试链接:https://leetcode.com/problems/friend-circles/ +// 可以直接通过 +public class Code01_FriendCircles { + + public static int findCircleNum(int[][] M) { + int N = M.length; + // {0} {1} {2} {N-1} + UnionFind unionFind = new UnionFind(N); + for (int i = 0; i < N; i++) { + for (int j = i + 1; j < N; j++) { + if (M[i][j] == 1) { // i和j互相认识 + unionFind.union(i, j); + } + } + } + return unionFind.sets(); + } + + public static class UnionFind { + // parent[i] = k : i的父亲是k + private int[] parent; + // size[i] = k : 如果i是代表节点,size[i]才有意义,否则无意义 + // i所在的集合大小是多少 + private int[] size; + // 辅助结构 + private int[] help; + // 一共有多少个集合 + private int sets; + + public UnionFind(int N) { + parent = new int[N]; + size = new int[N]; + help = new int[N]; + sets = N; + for (int i = 0; i < N; i++) { + parent[i] = i; + size[i] = 1; + } + } + + // 从i开始一直往上,往上到不能再往上,代表节点,返回 + // 这个过程要做路径压缩 + private int find(int i) { + int hi = 0; + while (i != parent[i]) { + help[hi++] = i; + i = parent[i]; + } + for (hi--; hi >= 0; hi--) { + parent[help[hi]] = i; + } + return i; + } + + public void union(int i, int j) { + int f1 = find(i); + int f2 = find(j); + if (f1 != f2) { + if (size[f1] >= size[f2]) { + size[f1] += size[f2]; + parent[f2] = f1; + } else { + size[f2] += size[f1]; + parent[f1] = f2; + } + sets--; + } + } + + public int sets() { + return sets; + } + } + +} diff --git a/体系学习班/class15/Code02_NumberOfIslands.java b/体系学习班/class15/Code02_NumberOfIslands.java new file mode 100644 index 0000000..6065d42 --- /dev/null +++ b/体系学习班/class15/Code02_NumberOfIslands.java @@ -0,0 +1,317 @@ +package class15; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Stack; + +// 本题为leetcode原题 +// 测试链接:https://leetcode.com/problems/number-of-islands/ +// 所有方法都可以直接通过 +public class Code02_NumberOfIslands { + + public static int numIslands3(char[][] board) { + int islands = 0; + for (int i = 0; i < board.length; i++) { + for (int j = 0; j < board[0].length; j++) { + if (board[i][j] == '1') { + islands++; + infect(board, i, j); + } + } + } + return islands; + } + + // 从(i,j)这个位置出发,把所有练成一片的'1'字符,变成0 + public static void infect(char[][] board, int i, int j) { + if (i < 0 || i == board.length || j < 0 || j == board[0].length || board[i][j] != '1') { + return; + } + board[i][j] = 0; + infect(board, i - 1, j); + infect(board, i + 1, j); + infect(board, i, j - 1); + infect(board, i, j + 1); + } + + public static int numIslands1(char[][] board) { + int row = board.length; + int col = board[0].length; + Dot[][] dots = new Dot[row][col]; + List dotList = new ArrayList<>(); + for (int i = 0; i < row; i++) { + for (int j = 0; j < col; j++) { + if (board[i][j] == '1') { + dots[i][j] = new Dot(); + dotList.add(dots[i][j]); + } + } + } + UnionFind1 uf = new UnionFind1<>(dotList); + for (int j = 1; j < col; j++) { + // (0,j) (0,0)跳过了 (0,1) (0,2) (0,3) + if (board[0][j - 1] == '1' && board[0][j] == '1') { + uf.union(dots[0][j - 1], dots[0][j]); + } + } + for (int i = 1; i < row; i++) { + if (board[i - 1][0] == '1' && board[i][0] == '1') { + uf.union(dots[i - 1][0], dots[i][0]); + } + } + for (int i = 1; i < row; i++) { + for (int j = 1; j < col; j++) { + if (board[i][j] == '1') { + if (board[i][j - 1] == '1') { + uf.union(dots[i][j - 1], dots[i][j]); + } + if (board[i - 1][j] == '1') { + uf.union(dots[i - 1][j], dots[i][j]); + } + } + } + } + return uf.sets(); + } + + public static class Dot { + + } + + public static class Node { + + V value; + + public Node(V v) { + value = v; + } + + } + + public static class UnionFind1 { + public HashMap> nodes; + public HashMap, Node> parents; + public HashMap, Integer> sizeMap; + + public UnionFind1(List values) { + nodes = new HashMap<>(); + parents = new HashMap<>(); + sizeMap = new HashMap<>(); + for (V cur : values) { + Node node = new Node<>(cur); + nodes.put(cur, node); + parents.put(node, node); + sizeMap.put(node, 1); + } + } + + public Node findFather(Node cur) { + Stack> path = new Stack<>(); + while (cur != parents.get(cur)) { + path.push(cur); + cur = parents.get(cur); + } + while (!path.isEmpty()) { + parents.put(path.pop(), cur); + } + return cur; + } + + public void union(V a, V b) { + Node aHead = findFather(nodes.get(a)); + Node bHead = findFather(nodes.get(b)); + if (aHead != bHead) { + int aSetSize = sizeMap.get(aHead); + int bSetSize = sizeMap.get(bHead); + Node big = aSetSize >= bSetSize ? aHead : bHead; + Node small = big == aHead ? bHead : aHead; + parents.put(small, big); + sizeMap.put(big, aSetSize + bSetSize); + sizeMap.remove(small); + } + } + + public int sets() { + return sizeMap.size(); + } + + } + + public static int numIslands2(char[][] board) { + int row = board.length; + int col = board[0].length; + UnionFind2 uf = new UnionFind2(board); + for (int j = 1; j < col; j++) { + if (board[0][j - 1] == '1' && board[0][j] == '1') { + uf.union(0, j - 1, 0, j); + } + } + for (int i = 1; i < row; i++) { + if (board[i - 1][0] == '1' && board[i][0] == '1') { + uf.union(i - 1, 0, i, 0); + } + } + for (int i = 1; i < row; i++) { + for (int j = 1; j < col; j++) { + if (board[i][j] == '1') { + if (board[i][j - 1] == '1') { + uf.union(i, j - 1, i, j); + } + if (board[i - 1][j] == '1') { + uf.union(i - 1, j, i, j); + } + } + } + } + return uf.sets(); + } + + public static class UnionFind2 { + private int[] parent; + private int[] size; + private int[] help; + private int col; + private int sets; + + public UnionFind2(char[][] board) { + col = board[0].length; + sets = 0; + int row = board.length; + int len = row * col; + parent = new int[len]; + size = new int[len]; + help = new int[len]; + for (int r = 0; r < row; r++) { + for (int c = 0; c < col; c++) { + if (board[r][c] == '1') { + int i = index(r, c); + parent[i] = i; + size[i] = 1; + sets++; + } + } + } + } + + // (r,c) -> i + private int index(int r, int c) { + return r * col + c; + } + + // 原始位置 -> 下标 + private int find(int i) { + int hi = 0; + while (i != parent[i]) { + help[hi++] = i; + i = parent[i]; + } + for (hi--; hi >= 0; hi--) { + parent[help[hi]] = i; + } + return i; + } + + public void union(int r1, int c1, int r2, int c2) { + int i1 = index(r1, c1); + int i2 = index(r2, c2); + int f1 = find(i1); + int f2 = find(i2); + if (f1 != f2) { + if (size[f1] >= size[f2]) { + size[f1] += size[f2]; + parent[f2] = f1; + } else { + size[f2] += size[f1]; + parent[f1] = f2; + } + sets--; + } + } + + public int sets() { + return sets; + } + + } + + // 为了测试 + public static char[][] generateRandomMatrix(int row, int col) { + char[][] board = new char[row][col]; + for (int i = 0; i < row; i++) { + for (int j = 0; j < col; j++) { + board[i][j] = Math.random() < 0.5 ? '1' : '0'; + } + } + return board; + } + + // 为了测试 + public static char[][] copy(char[][] board) { + int row = board.length; + int col = board[0].length; + char[][] ans = new char[row][col]; + for (int i = 0; i < row; i++) { + for (int j = 0; j < col; j++) { + ans[i][j] = board[i][j]; + } + } + return ans; + } + + // 为了测试 + public static void main(String[] args) { + int row = 0; + int col = 0; + char[][] board1 = null; + char[][] board2 = null; + char[][] board3 = null; + long start = 0; + long end = 0; + + row = 1000; + col = 1000; + board1 = generateRandomMatrix(row, col); + board2 = copy(board1); + board3 = copy(board1); + + System.out.println("感染方法、并查集(map实现)、并查集(数组实现)的运行结果和运行时间"); + System.out.println("随机生成的二维矩阵规模 : " + row + " * " + col); + + start = System.currentTimeMillis(); + System.out.println("感染方法的运行结果: " + numIslands3(board1)); + end = System.currentTimeMillis(); + System.out.println("感染方法的运行时间: " + (end - start) + " ms"); + + start = System.currentTimeMillis(); + System.out.println("并查集(map实现)的运行结果: " + numIslands1(board2)); + end = System.currentTimeMillis(); + System.out.println("并查集(map实现)的运行时间: " + (end - start) + " ms"); + + start = System.currentTimeMillis(); + System.out.println("并查集(数组实现)的运行结果: " + numIslands2(board3)); + end = System.currentTimeMillis(); + System.out.println("并查集(数组实现)的运行时间: " + (end - start) + " ms"); + + System.out.println(); + + row = 10000; + col = 10000; + board1 = generateRandomMatrix(row, col); + board3 = copy(board1); + System.out.println("感染方法、并查集(数组实现)的运行结果和运行时间"); + System.out.println("随机生成的二维矩阵规模 : " + row + " * " + col); + + start = System.currentTimeMillis(); + System.out.println("感染方法的运行结果: " + numIslands3(board1)); + end = System.currentTimeMillis(); + System.out.println("感染方法的运行时间: " + (end - start) + " ms"); + + start = System.currentTimeMillis(); + System.out.println("并查集(数组实现)的运行结果: " + numIslands2(board3)); + end = System.currentTimeMillis(); + System.out.println("并查集(数组实现)的运行时间: " + (end - start) + " ms"); + + } + +} diff --git a/体系学习班/class15/Code03_NumberOfIslandsII.java b/体系学习班/class15/Code03_NumberOfIslandsII.java new file mode 100644 index 0000000..d109276 --- /dev/null +++ b/体系学习班/class15/Code03_NumberOfIslandsII.java @@ -0,0 +1,165 @@ +package class15; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +// 本题为leetcode原题 +// 测试链接:https://leetcode.com/problems/number-of-islands-ii/ +// 所有方法都可以直接通过 +public class Code03_NumberOfIslandsII { + + public static List numIslands21(int m, int n, int[][] positions) { + UnionFind1 uf = new UnionFind1(m, n); + List ans = new ArrayList<>(); + for (int[] position : positions) { + ans.add(uf.connect(position[0], position[1])); + } + return ans; + } + + public static class UnionFind1 { + private int[] parent; + private int[] size; + private int[] help; + private final int row; + private final int col; + private int sets; + + public UnionFind1(int m, int n) { + row = m; + col = n; + sets = 0; + int len = row * col; + parent = new int[len]; + size = new int[len]; + help = new int[len]; + } + + private int index(int r, int c) { + return r * col + c; + } + + private int find(int i) { + int hi = 0; + while (i != parent[i]) { + help[hi++] = i; + i = parent[i]; + } + for (hi--; hi >= 0; hi--) { + parent[help[hi]] = i; + } + return i; + } + + private void union(int r1, int c1, int r2, int c2) { + if (r1 < 0 || r1 == row || r2 < 0 || r2 == row || c1 < 0 || c1 == col || c2 < 0 || c2 == col) { + return; + } + int i1 = index(r1, c1); + int i2 = index(r2, c2); + if (size[i1] == 0 || size[i2] == 0) { + return; + } + int f1 = find(i1); + int f2 = find(i2); + if (f1 != f2) { + if (size[f1] >= size[f2]) { + size[f1] += size[f2]; + parent[f2] = f1; + } else { + size[f2] += size[f1]; + parent[f1] = f2; + } + sets--; + } + } + + public int connect(int r, int c) { + int index = index(r, c); + if (size[index] == 0) { + parent[index] = index; + size[index] = 1; + sets++; + union(r - 1, c, r, c); + union(r + 1, c, r, c); + union(r, c - 1, r, c); + union(r, c + 1, r, c); + } + return sets; + } + + } + + // 课上讲的如果m*n比较大,会经历很重的初始化,而k比较小,怎么优化的方法 + public static List numIslands22(int m, int n, int[][] positions) { + UnionFind2 uf = new UnionFind2(); + List ans = new ArrayList<>(); + for (int[] position : positions) { + ans.add(uf.connect(position[0], position[1])); + } + return ans; + } + + public static class UnionFind2 { + private HashMap parent; + private HashMap size; + private ArrayList help; + private int sets; + + public UnionFind2() { + parent = new HashMap<>(); + size = new HashMap<>(); + help = new ArrayList<>(); + sets = 0; + } + + private String find(String cur) { + while (!cur.equals(parent.get(cur))) { + help.add(cur); + cur = parent.get(cur); + } + for (String str : help) { + parent.put(str, cur); + } + help.clear(); + return cur; + } + + private void union(String s1, String s2) { + if (parent.containsKey(s1) && parent.containsKey(s2)) { + String f1 = find(s1); + String f2 = find(s2); + if (!f1.equals(f2)) { + int size1 = size.get(f1); + int size2 = size.get(f2); + String big = size1 >= size2 ? f1 : f2; + String small = big == f1 ? f2 : f1; + parent.put(small, big); + size.put(big, size1 + size2); + sets--; + } + } + } + + public int connect(int r, int c) { + String key = String.valueOf(r) + "_" + String.valueOf(c); + if (!parent.containsKey(key)) { + parent.put(key, key); + size.put(key, 1); + sets++; + String up = String.valueOf(r - 1) + "_" + String.valueOf(c); + String down = String.valueOf(r + 1) + "_" + String.valueOf(c); + String left = String.valueOf(r) + "_" + String.valueOf(c - 1); + String right = String.valueOf(r) + "_" + String.valueOf(c + 1); + union(up, key); + union(down, key); + union(left, key); + union(right, key); + } + return sets; + } + + } + +} diff --git a/体系学习班/class16/Code01_BFS.java b/体系学习班/class16/Code01_BFS.java new file mode 100644 index 0000000..c2d4d9f --- /dev/null +++ b/体系学习班/class16/Code01_BFS.java @@ -0,0 +1,30 @@ +package class16; + +import java.util.HashSet; +import java.util.LinkedList; +import java.util.Queue; + +public class Code01_BFS { + + // 从node出发,进行宽度优先遍历 + public static void bfs(Node start) { + if (start == null) { + return; + } + Queue queue = new LinkedList<>(); + HashSet set = new HashSet<>(); + queue.add(start); + set.add(start); + while (!queue.isEmpty()) { + Node cur = queue.poll(); + System.out.println(cur.value); + for (Node next : cur.nexts) { + if (!set.contains(next)) { + set.add(next); + queue.add(next); + } + } + } + } + +} diff --git a/体系学习班/class16/Code02_DFS.java b/体系学习班/class16/Code02_DFS.java new file mode 100644 index 0000000..fa85ca4 --- /dev/null +++ b/体系学习班/class16/Code02_DFS.java @@ -0,0 +1,34 @@ +package class16; + +import java.util.HashSet; +import java.util.Stack; + +public class Code02_DFS { + + public static void dfs(Node node) { + if (node == null) { + return; + } + Stack stack = new Stack<>(); + HashSet set = new HashSet<>(); + stack.add(node); + set.add(node); + System.out.println(node.value); + while (!stack.isEmpty()) { + Node cur = stack.pop(); + for (Node next : cur.nexts) { + if (!set.contains(next)) { + stack.push(cur); + stack.push(next); + set.add(next); + System.out.println(next.value); + break; + } + } + } + } + + + + +} diff --git a/体系学习班/class16/Code03_TopologicalOrderBFS.java b/体系学习班/class16/Code03_TopologicalOrderBFS.java new file mode 100644 index 0000000..5e54581 --- /dev/null +++ b/体系学习班/class16/Code03_TopologicalOrderBFS.java @@ -0,0 +1,53 @@ +package class16; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.Queue; + +// OJ链接:https://www.lintcode.com/problem/topological-sorting +public class Code03_TopologicalOrderBFS { + + // 不要提交这个类 + public static class DirectedGraphNode { + public int label; + public ArrayList neighbors; + + public DirectedGraphNode(int x) { + label = x; + neighbors = new ArrayList(); + } + } + + // 提交下面的 + public static ArrayList topSort(ArrayList graph) { + HashMap indegreeMap = new HashMap<>(); + for (DirectedGraphNode cur : graph) { + indegreeMap.put(cur, 0); + } + for (DirectedGraphNode cur : graph) { + for (DirectedGraphNode next : cur.neighbors) { + indegreeMap.put(next, indegreeMap.get(next) + 1); + } + } + Queue zeroQueue = new LinkedList<>(); + for (DirectedGraphNode cur : indegreeMap.keySet()) { + if (indegreeMap.get(cur) == 0) { + zeroQueue.add(cur); + } + } + ArrayList ans = new ArrayList<>(); + while (!zeroQueue.isEmpty()) { + DirectedGraphNode cur = zeroQueue.poll(); + ans.add(cur); + for (DirectedGraphNode next : cur.neighbors) { + indegreeMap.put(next, indegreeMap.get(next) - 1); + if (indegreeMap.get(next) == 0) { + zeroQueue.offer(next); + } + } + } + return ans; + } + +} diff --git a/体系学习班/class16/Code03_TopologicalOrderDFS1.java b/体系学习班/class16/Code03_TopologicalOrderDFS1.java new file mode 100644 index 0000000..57f1bb7 --- /dev/null +++ b/体系学习班/class16/Code03_TopologicalOrderDFS1.java @@ -0,0 +1,70 @@ +package class16; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; + +// OJ链接:https://www.lintcode.com/problem/topological-sorting +public class Code03_TopologicalOrderDFS1 { + + // 不要提交这个类 + public static class DirectedGraphNode { + public int label; + public ArrayList neighbors; + + public DirectedGraphNode(int x) { + label = x; + neighbors = new ArrayList(); + } + } + + // 提交下面的 + public static class Record { + public DirectedGraphNode node; + public int deep; + + public Record(DirectedGraphNode n, int o) { + node = n; + deep = o; + } + } + + public static class MyComparator implements Comparator { + + @Override + public int compare(Record o1, Record o2) { + return o2.deep - o1.deep; + } + } + + public static ArrayList topSort(ArrayList graph) { + HashMap order = new HashMap<>(); + for (DirectedGraphNode cur : graph) { + f(cur, order); + } + ArrayList recordArr = new ArrayList<>(); + for (Record r : order.values()) { + recordArr.add(r); + } + recordArr.sort(new MyComparator()); + ArrayList ans = new ArrayList(); + for (Record r : recordArr) { + ans.add(r.node); + } + return ans; + } + + public static Record f(DirectedGraphNode cur, HashMap order) { + if (order.containsKey(cur)) { + return order.get(cur); + } + int follow = 0; + for (DirectedGraphNode next : cur.neighbors) { + follow = Math.max(follow, f(next, order).deep); + } + Record ans = new Record(cur, follow + 1); + order.put(cur, ans); + return ans; + } + +} diff --git a/体系学习班/class16/Code03_TopologicalOrderDFS2.java b/体系学习班/class16/Code03_TopologicalOrderDFS2.java new file mode 100644 index 0000000..4c90d1d --- /dev/null +++ b/体系学习班/class16/Code03_TopologicalOrderDFS2.java @@ -0,0 +1,76 @@ +package class16; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; + +// OJ链接:https://www.lintcode.com/problem/topological-sorting +public class Code03_TopologicalOrderDFS2 { + + // 不要提交这个类 + public static class DirectedGraphNode { + public int label; + public ArrayList neighbors; + + public DirectedGraphNode(int x) { + label = x; + neighbors = new ArrayList(); + } + } + + // 提交下面的 + public static class Record { + public DirectedGraphNode node; + public long nodes; + + public Record(DirectedGraphNode n, long o) { + node = n; + nodes = o; + } + } + + public static class MyComparator implements Comparator { + + @Override + public int compare(Record o1, Record o2) { + return o1.nodes == o2.nodes ? 0 : (o1.nodes > o2.nodes ? -1 : 1); + } + } + + public static ArrayList topSort(ArrayList graph) { + HashMap order = new HashMap<>(); + for (DirectedGraphNode cur : graph) { + f(cur, order); + } + ArrayList recordArr = new ArrayList<>(); + for (Record r : order.values()) { + recordArr.add(r); + } + recordArr.sort(new MyComparator()); + ArrayList ans = new ArrayList(); + for (Record r : recordArr) { + ans.add(r.node); + } + return ans; + } + + // 当前来到cur点,请返回cur点所到之处,所有的点次! + // 返回(cur,点次) + // 缓存!!!!!order + // key : 某一个点的点次,之前算过了! + // value : 点次是多少 + public static Record f(DirectedGraphNode cur, HashMap order) { + if (order.containsKey(cur)) { + return order.get(cur); + } + // cur的点次之前没算过! + long nodes = 0; + for (DirectedGraphNode next : cur.neighbors) { + nodes += f(next, order).nodes; + } + Record ans = new Record(cur, nodes + 1); + order.put(cur, ans); + return ans; + } + +} diff --git a/体系学习班/class16/Code03_TopologySort.java b/体系学习班/class16/Code03_TopologySort.java new file mode 100644 index 0000000..735dca0 --- /dev/null +++ b/体系学习班/class16/Code03_TopologySort.java @@ -0,0 +1,36 @@ +package class16; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; + +public class Code03_TopologySort { + + // directed graph and no loop + public static List sortedTopology(Graph graph) { + // key 某个节点 value 剩余的入度 + HashMap inMap = new HashMap<>(); + // 只有剩余入度为0的点,才进入这个队列 + Queue zeroInQueue = new LinkedList<>(); + for (Node node : graph.nodes.values()) { + inMap.put(node, node.in); + if (node.in == 0) { + zeroInQueue.add(node); + } + } + List result = new ArrayList<>(); + while (!zeroInQueue.isEmpty()) { + Node cur = zeroInQueue.poll(); + result.add(cur); + for (Node next : cur.nexts) { + inMap.put(next, inMap.get(next) - 1); + if (inMap.get(next) == 0) { + zeroInQueue.add(next); + } + } + } + return result; + } +} diff --git a/体系学习班/class16/Code04_Kruskal.java b/体系学习班/class16/Code04_Kruskal.java new file mode 100644 index 0000000..cb0ea62 --- /dev/null +++ b/体系学习班/class16/Code04_Kruskal.java @@ -0,0 +1,101 @@ +package class16; + +import java.util.Collection; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.PriorityQueue; +import java.util.Set; +import java.util.Stack; + +//undirected graph only +public class Code04_Kruskal { + + // Union-Find Set + public static class UnionFind { + // key 某一个节点, value key节点往上的节点 + private HashMap fatherMap; + // key 某一个集合的代表节点, value key所在集合的节点个数 + private HashMap sizeMap; + + public UnionFind() { + fatherMap = new HashMap(); + sizeMap = new HashMap(); + } + + public void makeSets(Collection nodes) { + fatherMap.clear(); + sizeMap.clear(); + for (Node node : nodes) { + fatherMap.put(node, node); + sizeMap.put(node, 1); + } + } + + private Node findFather(Node n) { + Stack path = new Stack<>(); + while(n != fatherMap.get(n)) { + path.add(n); + n = fatherMap.get(n); + } + while(!path.isEmpty()) { + fatherMap.put(path.pop(), n); + } + return n; + } + + public boolean isSameSet(Node a, Node b) { + return findFather(a) == findFather(b); + } + + public void union(Node a, Node b) { + if (a == null || b == null) { + return; + } + Node aDai = findFather(a); + Node bDai = findFather(b); + if (aDai != bDai) { + int aSetSize = sizeMap.get(aDai); + int bSetSize = sizeMap.get(bDai); + if (aSetSize <= bSetSize) { + fatherMap.put(aDai, bDai); + sizeMap.put(bDai, aSetSize + bSetSize); + sizeMap.remove(aDai); + } else { + fatherMap.put(bDai, aDai); + sizeMap.put(aDai, aSetSize + bSetSize); + sizeMap.remove(bDai); + } + } + } + } + + + public static class EdgeComparator implements Comparator { + + @Override + public int compare(Edge o1, Edge o2) { + return o1.weight - o2.weight; + } + + } + + public static Set kruskalMST(Graph graph) { + UnionFind unionFind = new UnionFind(); + unionFind.makeSets(graph.nodes.values()); + // 从小的边到大的边,依次弹出,小根堆! + PriorityQueue priorityQueue = new PriorityQueue<>(new EdgeComparator()); + for (Edge edge : graph.edges) { // M 条边 + priorityQueue.add(edge); // O(logM) + } + Set result = new HashSet<>(); + while (!priorityQueue.isEmpty()) { // M 条边 + Edge edge = priorityQueue.poll(); // O(logM) + if (!unionFind.isSameSet(edge.from, edge.to)) { // O(1) + result.add(edge); + unionFind.union(edge.from, edge.to); + } + } + return result; + } +} diff --git a/体系学习班/class16/Code05_Prim.java b/体系学习班/class16/Code05_Prim.java new file mode 100644 index 0000000..d00dc75 --- /dev/null +++ b/体系学习班/class16/Code05_Prim.java @@ -0,0 +1,94 @@ +package class16; + +import java.util.Comparator; +import java.util.HashSet; +import java.util.PriorityQueue; +import java.util.Set; + +// undirected graph only +public class Code05_Prim { + + public static class EdgeComparator implements Comparator { + + @Override + public int compare(Edge o1, Edge o2) { + return o1.weight - o2.weight; + } + + } + + public static Set primMST(Graph graph) { + // 解锁的边进入小根堆 + PriorityQueue priorityQueue = new PriorityQueue<>(new EdgeComparator()); + + // 哪些点被解锁出来了 + HashSet nodeSet = new HashSet<>(); + + + + Set result = new HashSet<>(); // 依次挑选的的边在result里 + + for (Node node : graph.nodes.values()) { // 随便挑了一个点 + // node 是开始点 + if (!nodeSet.contains(node)) { + nodeSet.add(node); + for (Edge edge : node.edges) { // 由一个点,解锁所有相连的边 + priorityQueue.add(edge); + } + while (!priorityQueue.isEmpty()) { + Edge edge = priorityQueue.poll(); // 弹出解锁的边中,最小的边 + Node toNode = edge.to; // 可能的一个新的点 + if (!nodeSet.contains(toNode)) { // 不含有的时候,就是新的点 + nodeSet.add(toNode); + result.add(edge); + for (Edge nextEdge : toNode.edges) { + priorityQueue.add(nextEdge); + } + } + } + } + // break; + } + return result; + } + + // 请保证graph是连通图 + // graph[i][j]表示点i到点j的距离,如果是系统最大值代表无路 + // 返回值是最小连通图的路径之和 + public static int prim(int[][] graph) { + int size = graph.length; + int[] distances = new int[size]; + boolean[] visit = new boolean[size]; + visit[0] = true; + for (int i = 0; i < size; i++) { + distances[i] = graph[0][i]; + } + int sum = 0; + for (int i = 1; i < size; i++) { + int minPath = Integer.MAX_VALUE; + int minIndex = -1; + for (int j = 0; j < size; j++) { + if (!visit[j] && distances[j] < minPath) { + minPath = distances[j]; + minIndex = j; + } + } + if (minIndex == -1) { + return sum; + } + visit[minIndex] = true; + sum += minPath; + for (int j = 0; j < size; j++) { + if (!visit[j] && distances[j] > graph[minIndex][j]) { + distances[j] = graph[minIndex][j]; + } + } + } + return sum; + } + + public static void main(String[] args) { + System.out.println("hello world!"); + } + +} diff --git a/体系学习班/class16/Code06_Dijkstra.java b/体系学习班/class16/Code06_Dijkstra.java new file mode 100644 index 0000000..2b819c6 --- /dev/null +++ b/体系学习班/class16/Code06_Dijkstra.java @@ -0,0 +1,160 @@ +package class16; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map.Entry; + +// no negative weight +public class Code06_Dijkstra { + + public static HashMap dijkstra1(Node from) { + HashMap distanceMap = new HashMap<>(); + distanceMap.put(from, 0); + // 打过对号的点 + HashSet selectedNodes = new HashSet<>(); + Node minNode = getMinDistanceAndUnselectedNode(distanceMap, selectedNodes); + while (minNode != null) { + // 原始点 -> minNode(跳转点) 最小距离distance + int distance = distanceMap.get(minNode); + for (Edge edge : minNode.edges) { + Node toNode = edge.to; + if (!distanceMap.containsKey(toNode)) { + distanceMap.put(toNode, distance + edge.weight); + } else { // toNode + distanceMap.put(edge.to, Math.min(distanceMap.get(toNode), distance + edge.weight)); + } + } + selectedNodes.add(minNode); + minNode = getMinDistanceAndUnselectedNode(distanceMap, selectedNodes); + } + return distanceMap; + } + + public static Node getMinDistanceAndUnselectedNode(HashMap distanceMap, HashSet touchedNodes) { + Node minNode = null; + int minDistance = Integer.MAX_VALUE; + for (Entry entry : distanceMap.entrySet()) { + Node node = entry.getKey(); + int distance = entry.getValue(); + if (!touchedNodes.contains(node) && distance < minDistance) { + minNode = node; + minDistance = distance; + } + } + return minNode; + } + + public static class NodeRecord { + public Node node; + public int distance; + + public NodeRecord(Node node, int distance) { + this.node = node; + this.distance = distance; + } + } + + public static class NodeHeap { + private Node[] nodes; // 实际的堆结构 + // key 某一个node, value 上面堆中的位置 + private HashMap heapIndexMap; + // key 某一个节点, value 从源节点出发到该节点的目前最小距离 + private HashMap distanceMap; + private int size; // 堆上有多少个点 + + public NodeHeap(int size) { + nodes = new Node[size]; + heapIndexMap = new HashMap<>(); + distanceMap = new HashMap<>(); + size = 0; + } + + public boolean isEmpty() { + return size == 0; + } + + // 有一个点叫node,现在发现了一个从源节点出发到达node的距离为distance + // 判断要不要更新,如果需要的话,就更新 + public void addOrUpdateOrIgnore(Node node, int distance) { + if (inHeap(node)) { + distanceMap.put(node, Math.min(distanceMap.get(node), distance)); + insertHeapify(heapIndexMap.get(node)); + } + if (!isEntered(node)) { + nodes[size] = node; + heapIndexMap.put(node, size); + distanceMap.put(node, distance); + insertHeapify(size++); + } + } + + public NodeRecord pop() { + NodeRecord nodeRecord = new NodeRecord(nodes[0], distanceMap.get(nodes[0])); + swap(0, size - 1); + heapIndexMap.put(nodes[size - 1], -1); + distanceMap.remove(nodes[size - 1]); + // free C++同学还要把原本堆顶节点析构,对java同学不必 + nodes[size - 1] = null; + heapify(0, --size); + return nodeRecord; + } + + private void insertHeapify(int index) { + while (distanceMap.get(nodes[index]) < distanceMap.get(nodes[(index - 1) / 2])) { + swap(index, (index - 1) / 2); + index = (index - 1) / 2; + } + } + + private void heapify(int index, int size) { + int left = index * 2 + 1; + while (left < size) { + int smallest = left + 1 < size && distanceMap.get(nodes[left + 1]) < distanceMap.get(nodes[left]) + ? left + 1 + : left; + smallest = distanceMap.get(nodes[smallest]) < distanceMap.get(nodes[index]) ? smallest : index; + if (smallest == index) { + break; + } + swap(smallest, index); + index = smallest; + left = index * 2 + 1; + } + } + + private boolean isEntered(Node node) { + return heapIndexMap.containsKey(node); + } + + private boolean inHeap(Node node) { + return isEntered(node) && heapIndexMap.get(node) != -1; + } + + private void swap(int index1, int index2) { + heapIndexMap.put(nodes[index1], index2); + heapIndexMap.put(nodes[index2], index1); + Node tmp = nodes[index1]; + nodes[index1] = nodes[index2]; + nodes[index2] = tmp; + } + } + + // 改进后的dijkstra算法 + // 从head出发,所有head能到达的节点,生成到达每个节点的最小路径记录并返回 + public static HashMap dijkstra2(Node head, int size) { + NodeHeap nodeHeap = new NodeHeap(size); + nodeHeap.addOrUpdateOrIgnore(head, 0); + HashMap result = new HashMap<>(); + while (!nodeHeap.isEmpty()) { + NodeRecord record = nodeHeap.pop(); + Node cur = record.node; + int distance = record.distance; + for (Edge edge : cur.edges) { + nodeHeap.addOrUpdateOrIgnore(edge.to, edge.weight + distance); + } + result.put(cur, distance); + } + return result; + } + +} diff --git a/体系学习班/class16/Code06_NetworkDelayTime.java b/体系学习班/class16/Code06_NetworkDelayTime.java new file mode 100644 index 0000000..0be520a --- /dev/null +++ b/体系学习班/class16/Code06_NetworkDelayTime.java @@ -0,0 +1,151 @@ +package class16; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.PriorityQueue; + +// 课上没有讲这个题,这是我给同学们找的练习题 +// leetcode 743题,可以用这道题来练习Dijkstra算法 +// 测试链接 : https://leetcode.com/problems/network-delay-time +public class Code06_NetworkDelayTime { + + // 方法一 : 普通堆 + 屏蔽已经计算过的点 + public static int networkDelayTime1(int[][] times, int n, int k) { + ArrayList> nexts = new ArrayList<>(); + for (int i = 0; i <= n; i++) { + nexts.add(new ArrayList<>()); + } + for (int[] delay : times) { + nexts.get(delay[0]).add(new int[] { delay[1], delay[2] }); + } + PriorityQueue heap = new PriorityQueue<>((a, b) -> a[1] - b[1]); + heap.add(new int[] { k, 0 }); + boolean[] used = new boolean[n + 1]; + int num = 0; + int max = 0; + while (!heap.isEmpty() && num < n) { + int[] record = heap.poll(); + int cur = record[0]; + int delay = record[1]; + if (used[cur]) { + continue; + } + used[cur] = true; + num++; + max = Math.max(max, delay); + for (int[] next : nexts.get(cur)) { + heap.add(new int[] { next[0], delay + next[1] }); + } + } + return num < n ? -1 : max; + } + + // 方法二 : 加强堆的解法 + public static int networkDelayTime2(int[][] times, int n, int k) { + ArrayList> nexts = new ArrayList<>(); + for (int i = 0; i <= n; i++) { + nexts.add(new ArrayList<>()); + } + for (int[] delay : times) { + nexts.get(delay[0]).add(new int[] { delay[1], delay[2] }); + } + Heap heap = new Heap(n); + heap.add(k, 0); + int num = 0; + int max = 0; + while (!heap.isEmpty()) { + int[] record = heap.poll(); + int cur = record[0]; + int delay = record[1]; + num++; + max = Math.max(max, delay); + for (int[] next : nexts.get(cur)) { + heap.add(next[0], delay + next[1]); + } + } + return num < n ? -1 : max; + } + + // 加强堆 + public static class Heap { + public boolean[] used; + public int[][] heap; + public int[] hIndex; + public int size; + + public Heap(int n) { + used = new boolean[n + 1]; + heap = new int[n + 1][2]; + hIndex = new int[n + 1]; + Arrays.fill(hIndex, -1); + size = 0; + } + + public void add(int cur, int delay) { + if (used[cur]) { + return; + } + if (hIndex[cur] == -1) { + heap[size][0] = cur; + heap[size][1] = delay; + hIndex[cur] = size; + heapInsert(size++); + } else { + int hi = hIndex[cur]; + if (delay <= heap[hi][1]) { + heap[hi][1] = delay; + heapInsert(hi); + } + } + } + + public int[] poll() { + int[] ans = heap[0]; + swap(0, --size); + heapify(0); + used[ans[0]] = true; + hIndex[ans[0]] = -1; + return ans; + } + + public boolean isEmpty() { + return size == 0; + } + + private void heapInsert(int i) { + int parent = (i - 1) / 2; + while (heap[i][1] < heap[parent][1]) { + swap(i, parent); + i = parent; + parent = (i - 1) / 2; + } + } + + private void heapify(int i) { + int l = (i * 2) + 1; + while (l < size) { + int smallest = l + 1 < size && heap[l + 1][1] < heap[l][1] ? (l + 1) : l; + smallest = heap[smallest][1] < heap[i][1] ? smallest : i; + if (smallest == i) { + break; + } + swap(smallest, i); + i = smallest; + l = (i * 2) + 1; + } + } + + private void swap(int i, int j) { + int[] o1 = heap[i]; + int[] o2 = heap[j]; + int o1hi = hIndex[o1[0]]; + int o2hi = hIndex[o2[0]]; + heap[i] = o2; + heap[j] = o1; + hIndex[o1[0]] = o2hi; + hIndex[o2[0]] = o1hi; + } + + } + +} diff --git a/体系学习班/class16/Edge.java b/体系学习班/class16/Edge.java new file mode 100644 index 0000000..7c56891 --- /dev/null +++ b/体系学习班/class16/Edge.java @@ -0,0 +1,14 @@ +package class16; + +public class Edge { + public int weight; + public Node from; + public Node to; + + public Edge(int weight, Node from, Node to) { + this.weight = weight; + this.from = from; + this.to = to; + } + +} diff --git a/体系学习班/class16/Graph.java b/体系学习班/class16/Graph.java new file mode 100644 index 0000000..933e43b --- /dev/null +++ b/体系学习班/class16/Graph.java @@ -0,0 +1,14 @@ +package class16; + +import java.util.HashMap; +import java.util.HashSet; + +public class Graph { + public HashMap nodes; + public HashSet edges; + + public Graph() { + nodes = new HashMap<>(); + edges = new HashSet<>(); + } +} diff --git a/体系学习班/class16/GraphGenerator.java b/体系学习班/class16/GraphGenerator.java new file mode 100644 index 0000000..fe3387d --- /dev/null +++ b/体系学习班/class16/GraphGenerator.java @@ -0,0 +1,37 @@ +package class16; + +public class GraphGenerator { + + // matrix 所有的边 + // N*3 的矩阵 + // [weight, from节点上面的值,to节点上面的值] + // + // [ 5 , 0 , 7] + // [ 3 , 0, 1] + // + public static Graph createGraph(int[][] matrix) { + Graph graph = new Graph(); + for (int i = 0; i < matrix.length; i++) { + // 拿到每一条边, matrix[i] + int weight = matrix[i][0]; + int from = matrix[i][1]; + int to = matrix[i][2]; + if (!graph.nodes.containsKey(from)) { + graph.nodes.put(from, new Node(from)); + } + if (!graph.nodes.containsKey(to)) { + graph.nodes.put(to, new Node(to)); + } + Node fromNode = graph.nodes.get(from); + Node toNode = graph.nodes.get(to); + Edge newEdge = new Edge(weight, fromNode, toNode); + fromNode.nexts.add(toNode); + fromNode.out++; + toNode.in++; + fromNode.edges.add(newEdge); + graph.edges.add(newEdge); + } + return graph; + } + +} diff --git a/体系学习班/class16/Node.java b/体系学习班/class16/Node.java new file mode 100644 index 0000000..a468aaf --- /dev/null +++ b/体系学习班/class16/Node.java @@ -0,0 +1,20 @@ +package class16; + +import java.util.ArrayList; + +// 点结构的描述 +public class Node { + public int value; + public int in; + public int out; + public ArrayList nexts; + public ArrayList edges; + + public Node(int value) { + this.value = value; + in = 0; + out = 0; + nexts = new ArrayList<>(); + edges = new ArrayList<>(); + } +} diff --git a/体系学习班/class17/Code01_Dijkstra.java b/体系学习班/class17/Code01_Dijkstra.java new file mode 100644 index 0000000..0b39615 --- /dev/null +++ b/体系学习班/class17/Code01_Dijkstra.java @@ -0,0 +1,162 @@ +package class17; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map.Entry; + +// no negative weight +public class Code01_Dijkstra { + + public static HashMap dijkstra1(Node from) { + HashMap distanceMap = new HashMap<>(); + distanceMap.put(from, 0); + // 打过对号的点 + HashSet selectedNodes = new HashSet<>(); + Node minNode = getMinDistanceAndUnselectedNode(distanceMap, selectedNodes); + while (minNode != null) { + // 原始点 -> minNode(跳转点) 最小距离distance + int distance = distanceMap.get(minNode); + for (Edge edge : minNode.edges) { + Node toNode = edge.to; + if (!distanceMap.containsKey(toNode)) { + distanceMap.put(toNode, distance + edge.weight); + } else { // toNode + distanceMap.put(edge.to, Math.min(distanceMap.get(toNode), distance + edge.weight)); + } + } + selectedNodes.add(minNode); + minNode = getMinDistanceAndUnselectedNode(distanceMap, selectedNodes); + } + return distanceMap; + } + + public static Node getMinDistanceAndUnselectedNode(HashMap distanceMap, HashSet touchedNodes) { + Node minNode = null; + int minDistance = Integer.MAX_VALUE; + for (Entry entry : distanceMap.entrySet()) { + Node node = entry.getKey(); + int distance = entry.getValue(); + if (!touchedNodes.contains(node) && distance < minDistance) { + minNode = node; + minDistance = distance; + } + } + return minNode; + } + + public static class NodeRecord { + public Node node; + public int distance; + + public NodeRecord(Node node, int distance) { + this.node = node; + this.distance = distance; + } + } + + public static class NodeHeap { + // 堆! + private Node[] nodes; + // node -> 堆上的什么位置? + + private HashMap heapIndexMap; + private HashMap distanceMap; + private int size; + + public NodeHeap(int size) { + nodes = new Node[size]; + heapIndexMap = new HashMap<>(); + distanceMap = new HashMap<>(); + size = 0; + } + + public boolean isEmpty() { + return size == 0; + } + + // 有一个点叫node,现在发现了一个从源节点出发到达node的距离为distance + // 判断要不要更新,如果需要的话,就更新 + public void addOrUpdateOrIgnore(Node node, int distance) { + if (inHeap(node)) { // update + distanceMap.put(node, Math.min(distanceMap.get(node), distance)); + insertHeapify(node, heapIndexMap.get(node)); + } + if (!isEntered(node)) { // add + nodes[size] = node; + heapIndexMap.put(node, size); + distanceMap.put(node, distance); + insertHeapify(node, size++); + } + // ignore + } + + public NodeRecord pop() { + NodeRecord nodeRecord = new NodeRecord(nodes[0], distanceMap.get(nodes[0])); + swap(0, size - 1); // 0 > size - 1 size - 1 > 0 + heapIndexMap.put(nodes[size - 1], -1); + distanceMap.remove(nodes[size - 1]); + // free C++同学还要把原本堆顶节点析构,对java同学不必 + nodes[size - 1] = null; + heapify(0, --size); + return nodeRecord; + } + + private void insertHeapify(Node node, int index) { + while (distanceMap.get(nodes[index]) < distanceMap.get(nodes[(index - 1) / 2])) { + swap(index, (index - 1) / 2); + index = (index - 1) / 2; + } + } + + private void heapify(int index, int size) { + int left = index * 2 + 1; + while (left < size) { + int smallest = left + 1 < size && distanceMap.get(nodes[left + 1]) < distanceMap.get(nodes[left]) + ? left + 1 + : left; + smallest = distanceMap.get(nodes[smallest]) < distanceMap.get(nodes[index]) ? smallest : index; + if (smallest == index) { + break; + } + swap(smallest, index); + index = smallest; + left = index * 2 + 1; + } + } + + private boolean isEntered(Node node) { + return heapIndexMap.containsKey(node); + } + + private boolean inHeap(Node node) { + return isEntered(node) && heapIndexMap.get(node) != -1; + } + + private void swap(int index1, int index2) { + heapIndexMap.put(nodes[index1], index2); + heapIndexMap.put(nodes[index2], index1); + Node tmp = nodes[index1]; + nodes[index1] = nodes[index2]; + nodes[index2] = tmp; + } + } + + // 改进后的dijkstra算法 + // 从head出发,所有head能到达的节点,生成到达每个节点的最小路径记录并返回 + public static HashMap dijkstra2(Node head, int size) { + NodeHeap nodeHeap = new NodeHeap(size); + nodeHeap.addOrUpdateOrIgnore(head, 0); + HashMap result = new HashMap<>(); + while (!nodeHeap.isEmpty()) { + NodeRecord record = nodeHeap.pop(); + Node cur = record.node; + int distance = record.distance; + for (Edge edge : cur.edges) { + nodeHeap.addOrUpdateOrIgnore(edge.to, edge.weight + distance); + } + result.put(cur, distance); + } + return result; + } + +} diff --git a/体系学习班/class17/Code02_Hanoi.java b/体系学习班/class17/Code02_Hanoi.java new file mode 100644 index 0000000..3d9ba6e --- /dev/null +++ b/体系学习班/class17/Code02_Hanoi.java @@ -0,0 +1,139 @@ +package class17; + +import java.util.Stack; + +public class Code02_Hanoi { + + public static void hanoi1(int n) { + leftToRight(n); + } + + // 请把1~N层圆盘 从左 -> 右 + public static void leftToRight(int n) { + if (n == 1) { // base case + System.out.println("Move 1 from left to right"); + return; + } + leftToMid(n - 1); + System.out.println("Move " + n + " from left to right"); + midToRight(n - 1); + } + + // 请把1~N层圆盘 从左 -> 中 + public static void leftToMid(int n) { + if (n == 1) { + System.out.println("Move 1 from left to mid"); + return; + } + leftToRight(n - 1); + System.out.println("Move " + n + " from left to mid"); + rightToMid(n - 1); + } + + public static void rightToMid(int n) { + if (n == 1) { + System.out.println("Move 1 from right to mid"); + return; + } + rightToLeft(n - 1); + System.out.println("Move " + n + " from right to mid"); + leftToMid(n - 1); + } + + public static void midToRight(int n) { + if (n == 1) { + System.out.println("Move 1 from mid to right"); + return; + } + midToLeft(n - 1); + System.out.println("Move " + n + " from mid to right"); + leftToRight(n - 1); + } + + public static void midToLeft(int n) { + if (n == 1) { + System.out.println("Move 1 from mid to left"); + return; + } + midToRight(n - 1); + System.out.println("Move " + n + " from mid to left"); + rightToLeft(n - 1); + } + + public static void rightToLeft(int n) { + if (n == 1) { + System.out.println("Move 1 from right to left"); + return; + } + rightToMid(n - 1); + System.out.println("Move " + n + " from right to left"); + midToLeft(n - 1); + } + + public static void hanoi2(int n) { + if (n > 0) { + func(n, "left", "right", "mid"); + } + } + + public static void func(int N, String from, String to, String other) { + if (N == 1) { // base + System.out.println("Move 1 from " + from + " to " + to); + } else { + func(N - 1, from, other, to); + System.out.println("Move " + N + " from " + from + " to " + to); + func(N - 1, other, to, from); + } + } + + public static class Record { + public boolean finish1; + public int base; + public String from; + public String to; + public String other; + + public Record(boolean f1, int b, String f, String t, String o) { + finish1 = false; + base = b; + from = f; + to = t; + other = o; + } + } + + public static void hanoi3(int N) { + if (N < 1) { + return; + } + Stack stack = new Stack<>(); + stack.add(new Record(false, N, "left", "right", "mid")); + while (!stack.isEmpty()) { + Record cur = stack.pop(); + if (cur.base == 1) { + System.out.println("Move 1 from " + cur.from + " to " + cur.to); + if (!stack.isEmpty()) { + stack.peek().finish1 = true; + } + } else { + if (!cur.finish1) { + stack.push(cur); + stack.push(new Record(false, cur.base - 1, cur.from, cur.other, cur.to)); + } else { + System.out.println("Move " + cur.base + " from " + cur.from + " to " + cur.to); + stack.push(new Record(false, cur.base - 1, cur.other, cur.to, cur.from)); + } + } + } + } + + public static void main(String[] args) { + int n = 3; + hanoi1(n); + System.out.println("============"); + hanoi2(n); +// System.out.println("============"); +// hanoi3(n); + } + +} diff --git a/体系学习班/class17/Code03_PrintAllSubsquences.java b/体系学习班/class17/Code03_PrintAllSubsquences.java new file mode 100644 index 0000000..05b6a03 --- /dev/null +++ b/体系学习班/class17/Code03_PrintAllSubsquences.java @@ -0,0 +1,74 @@ +package class17; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; + +public class Code03_PrintAllSubsquences { + + // s -> "abc" -> + public static List subs(String s) { + char[] str = s.toCharArray(); + String path = ""; + List ans = new ArrayList<>(); + process1(str, 0, ans, path); + return ans; + } + + // str 固定参数 + // 来到了str[index]字符,index是位置 + // str[0..index-1]已经走过了!之前的决定,都在path上 + // 之前的决定已经不能改变了,就是path + // str[index....]还能决定,之前已经确定,而后面还能自由选择的话, + // 把所有生成的子序列,放入到ans里去 + public static void process1(char[] str, int index, List ans, String path) { + if (index == str.length) { + ans.add(path); + return; + } + // 没有要index位置的字符 + process1(str, index + 1, ans, path); + // 要了index位置的字符 + process1(str, index + 1, ans, path + String.valueOf(str[index])); + } + + public static List subsNoRepeat(String s) { + char[] str = s.toCharArray(); + String path = ""; + HashSet set = new HashSet<>(); + process2(str, 0, set, path); + List ans = new ArrayList<>(); + for (String cur : set) { + ans.add(cur); + } + return ans; + } + + public static void process2(char[] str, int index, HashSet set, String path) { + if (index == str.length) { + set.add(path); + return; + } + String no = path; + process2(str, index + 1, set, no); + String yes = path + String.valueOf(str[index]); + process2(str, index + 1, set, yes); + } + + public static void main(String[] args) { + String test = "acccc"; + List ans1 = subs(test); + List ans2 = subsNoRepeat(test); + + for (String str : ans1) { + System.out.println(str); + } + System.out.println("================="); + for (String str : ans2) { + System.out.println(str); + } + System.out.println("================="); + + } + +} diff --git a/体系学习班/class17/Code04_PrintAllPermutations.java b/体系学习班/class17/Code04_PrintAllPermutations.java new file mode 100644 index 0000000..0337a95 --- /dev/null +++ b/体系学习班/class17/Code04_PrintAllPermutations.java @@ -0,0 +1,110 @@ +package class17; + +import java.util.ArrayList; +import java.util.List; + +public class Code04_PrintAllPermutations { + + public static List permutation1(String s) { + List ans = new ArrayList<>(); + if (s == null || s.length() == 0) { + return ans; + } + char[] str = s.toCharArray(); + ArrayList rest = new ArrayList(); + for (char cha : str) { + rest.add(cha); + } + String path = ""; + f(rest, path, ans); + return ans; + } + + public static void f(ArrayList rest, String path, List ans) { + if (rest.isEmpty()) { + ans.add(path); + } else { + int N = rest.size(); + for (int i = 0; i < N; i++) { + char cur = rest.get(i); + rest.remove(i); + f(rest, path + cur, ans); + rest.add(i, cur); + } + } + } + + public static List permutation2(String s) { + List ans = new ArrayList<>(); + if (s == null || s.length() == 0) { + return ans; + } + char[] str = s.toCharArray(); + g1(str, 0, ans); + return ans; + } + + public static void g1(char[] str, int index, List ans) { + if (index == str.length) { + ans.add(String.valueOf(str)); + } else { + for (int i = index; i < str.length; i++) { + swap(str, index, i); + g1(str, index + 1, ans); + swap(str, index, i); + } + } + } + + public static List permutation3(String s) { + List ans = new ArrayList<>(); + if (s == null || s.length() == 0) { + return ans; + } + char[] str = s.toCharArray(); + g2(str, 0, ans); + return ans; + } + + public static void g2(char[] str, int index, List ans) { + if (index == str.length) { + ans.add(String.valueOf(str)); + } else { + boolean[] visited = new boolean[256]; + for (int i = index; i < str.length; i++) { + if (!visited[str[i]]) { + visited[str[i]] = true; + swap(str, index, i); + g2(str, index + 1, ans); + swap(str, index, i); + } + } + } + } + + public static void swap(char[] chs, int i, int j) { + char tmp = chs[i]; + chs[i] = chs[j]; + chs[j] = tmp; + } + + public static void main(String[] args) { + String s = "acc"; + List ans1 = permutation1(s); + for (String str : ans1) { + System.out.println(str); + } + System.out.println("======="); + List ans2 = permutation2(s); + for (String str : ans2) { + System.out.println(str); + } + System.out.println("======="); + List ans3 = permutation3(s); + for (String str : ans3) { + System.out.println(str); + } + + } + +} diff --git a/体系学习班/class17/Code05_ReverseStackUsingRecursive.java b/体系学习班/class17/Code05_ReverseStackUsingRecursive.java new file mode 100644 index 0000000..731056a --- /dev/null +++ b/体系学习班/class17/Code05_ReverseStackUsingRecursive.java @@ -0,0 +1,44 @@ +package class17; + +import java.util.Stack; + +public class Code05_ReverseStackUsingRecursive { + + public static void reverse(Stack stack) { + if (stack.isEmpty()) { + return; + } + int i = f(stack); + reverse(stack); + stack.push(i); + } + + // 栈底元素移除掉 + // 上面的元素盖下来 + // 返回移除掉的栈底元素 + public static int f(Stack stack) { + int result = stack.pop(); + if (stack.isEmpty()) { + return result; + } else { + int last = f(stack); + stack.push(result); + return last; + } + } + + public static void main(String[] args) { + Stack test = new Stack(); + test.push(1); + test.push(2); + test.push(3); + test.push(4); + test.push(5); + reverse(test); + while (!test.isEmpty()) { + System.out.println(test.pop()); + } + + } + +} diff --git a/体系学习班/class17/Edge.java b/体系学习班/class17/Edge.java new file mode 100644 index 0000000..eacd58b --- /dev/null +++ b/体系学习班/class17/Edge.java @@ -0,0 +1,14 @@ +package class17; + +public class Edge { + public int weight; + public Node from; + public Node to; + + public Edge(int weight, Node from, Node to) { + this.weight = weight; + this.from = from; + this.to = to; + } + +} diff --git a/体系学习班/class17/Graph.java b/体系学习班/class17/Graph.java new file mode 100644 index 0000000..f9314a2 --- /dev/null +++ b/体系学习班/class17/Graph.java @@ -0,0 +1,14 @@ +package class17; + +import java.util.HashMap; +import java.util.HashSet; + +public class Graph { + public HashMap nodes; + public HashSet edges; + + public Graph() { + nodes = new HashMap<>(); + edges = new HashSet<>(); + } +} diff --git a/体系学习班/class17/Node.java b/体系学习班/class17/Node.java new file mode 100644 index 0000000..1056ead --- /dev/null +++ b/体系学习班/class17/Node.java @@ -0,0 +1,20 @@ +package class17; + +import java.util.ArrayList; + +// 点结构的描述 +public class Node { + public int value; + public int in; + public int out; + public ArrayList nexts; + public ArrayList edges; + + public Node(int value) { + this.value = value; + in = 0; + out = 0; + nexts = new ArrayList<>(); + edges = new ArrayList<>(); + } +} diff --git a/体系学习班/class18/Code01_RobotWalk.java b/体系学习班/class18/Code01_RobotWalk.java new file mode 100644 index 0000000..f9ee3f2 --- /dev/null +++ b/体系学习班/class18/Code01_RobotWalk.java @@ -0,0 +1,94 @@ +package class18; + +public class Code01_RobotWalk { + + public static int ways1(int N, int start, int aim, int K) { + if (N < 2 || start < 1 || start > N || aim < 1 || aim > N || K < 1) { + return -1; + } + return process1(start, K, aim, N); + } + + // 机器人当前来到的位置是cur, + // 机器人还有rest步需要去走, + // 最终的目标是aim, + // 有哪些位置?1~N + // 返回:机器人从cur出发,走过rest步之后,最终停在aim的方法数,是多少? + public static int process1(int cur, int rest, int aim, int N) { + if (rest == 0) { // 如果已经不需要走了,走完了! + return cur == aim ? 1 : 0; + } + // (cur, rest) + if (cur == 1) { // 1 -> 2 + return process1(2, rest - 1, aim, N); + } + // (cur, rest) + if (cur == N) { // N-1 <- N + return process1(N - 1, rest - 1, aim, N); + } + // (cur, rest) + return process1(cur - 1, rest - 1, aim, N) + process1(cur + 1, rest - 1, aim, N); + } + + public static int ways2(int N, int start, int aim, int K) { + if (N < 2 || start < 1 || start > N || aim < 1 || aim > N || K < 1) { + return -1; + } + int[][] dp = new int[N + 1][K + 1]; + for (int i = 0; i <= N; i++) { + for (int j = 0; j <= K; j++) { + dp[i][j] = -1; + } + } + // dp就是缓存表 + // dp[cur][rest] == -1 -> process1(cur, rest)之前没算过! + // dp[cur][rest] != -1 -> process1(cur, rest)之前算过!返回值,dp[cur][rest] + // N+1 * K+1 + return process2(start, K, aim, N, dp); + } + + // cur 范: 1 ~ N + // rest 范:0 ~ K + public static int process2(int cur, int rest, int aim, int N, int[][] dp) { + if (dp[cur][rest] != -1) { + return dp[cur][rest]; + } + // 之前没算过! + int ans = 0; + if (rest == 0) { + ans = cur == aim ? 1 : 0; + } else if (cur == 1) { + ans = process2(2, rest - 1, aim, N, dp); + } else if (cur == N) { + ans = process2(N - 1, rest - 1, aim, N, dp); + } else { + ans = process2(cur - 1, rest - 1, aim, N, dp) + process2(cur + 1, rest - 1, aim, N, dp); + } + dp[cur][rest] = ans; + return ans; + + } + + public static int ways3(int N, int start, int aim, int K) { + if (N < 2 || start < 1 || start > N || aim < 1 || aim > N || K < 1) { + return -1; + } + int[][] dp = new int[N + 1][K + 1]; + dp[aim][0] = 1; + for (int rest = 1; rest <= K; rest++) { + dp[1][rest] = dp[2][rest - 1]; + for (int cur = 2; cur < N; cur++) { + dp[cur][rest] = dp[cur - 1][rest - 1] + dp[cur + 1][rest - 1]; + } + dp[N][rest] = dp[N - 1][rest - 1]; + } + return dp[start][K]; + } + + public static void main(String[] args) { + System.out.println(ways1(5, 2, 4, 6)); + System.out.println(ways2(5, 2, 4, 6)); + System.out.println(ways3(5, 2, 4, 6)); + } + +} diff --git a/体系学习班/class18/Code02_CardsInLine.java b/体系学习班/class18/Code02_CardsInLine.java new file mode 100644 index 0000000..919a4cb --- /dev/null +++ b/体系学习班/class18/Code02_CardsInLine.java @@ -0,0 +1,116 @@ +package class18; + +public class Code02_CardsInLine { + + // 根据规则,返回获胜者的分数 + public static int win1(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + int first = f1(arr, 0, arr.length - 1); + int second = g1(arr, 0, arr.length - 1); + return Math.max(first, second); + } + + // arr[L..R],先手获得的最好分数返回 + public static int f1(int[] arr, int L, int R) { + if (L == R) { + return arr[L]; + } + int p1 = arr[L] + g1(arr, L + 1, R); + int p2 = arr[R] + g1(arr, L, R - 1); + return Math.max(p1, p2); + } + + // // arr[L..R],后手获得的最好分数返回 + public static int g1(int[] arr, int L, int R) { + if (L == R) { + return 0; + } + int p1 = f1(arr, L + 1, R); // 对手拿走了L位置的数 + int p2 = f1(arr, L, R - 1); // 对手拿走了R位置的数 + return Math.min(p1, p2); + } + + public static int win2(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + int N = arr.length; + int[][] fmap = new int[N][N]; + int[][] gmap = new int[N][N]; + for (int i = 0; i < N; i++) { + for (int j = 0; j < N; j++) { + fmap[i][j] = -1; + gmap[i][j] = -1; + } + } + int first = f2(arr, 0, arr.length - 1, fmap, gmap); + int second = g2(arr, 0, arr.length - 1, fmap, gmap); + return Math.max(first, second); + } + + // arr[L..R],先手获得的最好分数返回 + public static int f2(int[] arr, int L, int R, int[][] fmap, int[][] gmap) { + if (fmap[L][R] != -1) { + return fmap[L][R]; + } + int ans = 0; + if (L == R) { + ans = arr[L]; + } else { + int p1 = arr[L] + g2(arr, L + 1, R, fmap, gmap); + int p2 = arr[R] + g2(arr, L, R - 1, fmap, gmap); + ans = Math.max(p1, p2); + } + fmap[L][R] = ans; + return ans; + } + + // // arr[L..R],后手获得的最好分数返回 + public static int g2(int[] arr, int L, int R, int[][] fmap, int[][] gmap) { + if (gmap[L][R] != -1) { + return gmap[L][R]; + } + int ans = 0; + if (L != R) { + int p1 = f2(arr, L + 1, R, fmap, gmap); // 对手拿走了L位置的数 + int p2 = f2(arr, L, R - 1, fmap, gmap); // 对手拿走了R位置的数 + ans = Math.min(p1, p2); + } + gmap[L][R] = ans; + return ans; + } + + public static int win3(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + int N = arr.length; + int[][] fmap = new int[N][N]; + int[][] gmap = new int[N][N]; + for (int i = 0; i < N; i++) { + fmap[i][i] = arr[i]; + } + for (int startCol = 1; startCol < N; startCol++) { + int L = 0; + int R = startCol; + while (R < N) { + fmap[L][R] = Math.max(arr[L] + gmap[L + 1][R], arr[R] + gmap[L][R - 1]); + gmap[L][R] = Math.min(fmap[L + 1][R], fmap[L][R - 1]); + L++; + R++; + } + } + return Math.max(fmap[0][N - 1], gmap[0][N - 1]); + } + + public static void main(String[] args) { + int[] arr = { 5, 7, 4, 5, 8, 1, 6, 0, 3, 4, 6, 1, 7 }; + System.out.println(win1(arr)); + System.out.println(win2(arr)); + System.out.println(win3(arr)); + + } + +} diff --git a/体系学习班/class19/Code01_Knapsack.java b/体系学习班/class19/Code01_Knapsack.java new file mode 100644 index 0000000..ab355a2 --- /dev/null +++ b/体系学习班/class19/Code01_Knapsack.java @@ -0,0 +1,63 @@ +package class19; + +public class Code01_Knapsack { + + // 所有的货,重量和价值,都在w和v数组里 + // 为了方便,其中没有负数 + // bag背包容量,不能超过这个载重 + // 返回:不超重的情况下,能够得到的最大价值 + public static int maxValue(int[] w, int[] v, int bag) { + if (w == null || v == null || w.length != v.length || w.length == 0) { + return 0; + } + // 尝试函数! + return process(w, v, 0, bag); + } + + // index 0~N + // rest 负~bag + public static int process(int[] w, int[] v, int index, int rest) { + if (rest < 0) { + return -1; + } + if (index == w.length) { + return 0; + } + int p1 = process(w, v, index + 1, rest); + int p2 = 0; + int next = process(w, v, index + 1, rest - w[index]); + if (next != -1) { + p2 = v[index] + next; + } + return Math.max(p1, p2); + } + + public static int dp(int[] w, int[] v, int bag) { + if (w == null || v == null || w.length != v.length || w.length == 0) { + return 0; + } + int N = w.length; + int[][] dp = new int[N + 1][bag + 1]; + for (int index = N - 1; index >= 0; index--) { + for (int rest = 0; rest <= bag; rest++) { + int p1 = dp[index + 1][rest]; + int p2 = 0; + int next = rest - w[index] < 0 ? -1 : dp[index + 1][rest - w[index]]; + if (next != -1) { + p2 = v[index] + next; + } + dp[index][rest] = Math.max(p1, p2); + } + } + return dp[0][bag]; + } + + public static void main(String[] args) { + int[] weights = { 3, 2, 4, 7, 3, 1, 7 }; + int[] values = { 5, 6, 3, 19, 12, 4, 2 }; + int bag = 15; + System.out.println(maxValue(weights, values, bag)); + System.out.println(dp(weights, values, bag)); + } + +} diff --git a/体系学习班/class19/Code02_ConvertToLetterString.java b/体系学习班/class19/Code02_ConvertToLetterString.java new file mode 100644 index 0000000..b780ca7 --- /dev/null +++ b/体系学习班/class19/Code02_ConvertToLetterString.java @@ -0,0 +1,123 @@ +package class19; + +public class Code02_ConvertToLetterString { + + // str只含有数字字符0~9 + // 返回多少种转化方案 + public static int number(String str) { + if (str == null || str.length() == 0) { + return 0; + } + return process(str.toCharArray(), 0); + } + + // str[0..i-1]转化无需过问 + // str[i.....]去转化,返回有多少种转化方法 + public static int process(char[] str, int i) { + if (i == str.length) { + return 1; + } + // i没到最后,说明有字符 + if (str[i] == '0') { // 之前的决定有问题 + return 0; + } + // str[i] != '0' + // 可能性一,i单转 + int ways = process(str, i + 1); + if (i + 1 < str.length && (str[i] - '0') * 10 + str[i + 1] - '0' < 27) { + ways += process(str, i + 2); + } + return ways; + } + + // 从右往左的动态规划 + // 就是上面方法的动态规划版本 + // dp[i]表示:str[i...]有多少种转化方式 + public static int dp1(String s) { + if (s == null || s.length() == 0) { + return 0; + } + char[] str = s.toCharArray(); + int N = str.length; + int[] dp = new int[N + 1]; + dp[N] = 1; + for (int i = N - 1; i >= 0; i--) { + if (str[i] != '0') { + int ways = dp[i + 1]; + if (i + 1 < str.length && (str[i] - '0') * 10 + str[i + 1] - '0' < 27) { + ways += dp[i + 2]; + } + dp[i] = ways; + } + } + return dp[0]; + } + + // 从左往右的动态规划 + // dp[i]表示:str[0...i]有多少种转化方式 + public static int dp2(String s) { + if (s == null || s.length() == 0) { + return 0; + } + char[] str = s.toCharArray(); + int N = str.length; + if (str[0] == '0') { + return 0; + } + int[] dp = new int[N]; + dp[0] = 1; + for (int i = 1; i < N; i++) { + if (str[i] == '0') { + // 如果此时str[i]=='0',那么他是一定要拉前一个字符(i-1的字符)一起拼的, + // 那么就要求前一个字符,不能也是‘0’,否则拼不了。 + // 前一个字符不是‘0’就够了嘛?不够,还得要求拼完了要么是10,要么是20,如果更大的话,拼不了。 + // 这就够了嘛?还不够,你们拼完了,还得要求str[0...i-2]真的可以被分解! + // 如果str[0...i-2]都不存在分解方案,那i和i-1拼成了也不行,因为之前的搞定不了。 + if (str[i - 1] == '0' || str[i - 1] > '2' || (i - 2 >= 0 && dp[i - 2] == 0)) { + return 0; + } else { + dp[i] = i - 2 >= 0 ? dp[i - 2] : 1; + } + } else { + dp[i] = dp[i - 1]; + if (str[i - 1] != '0' && (str[i - 1] - '0') * 10 + str[i] - '0' <= 26) { + dp[i] += i - 2 >= 0 ? dp[i - 2] : 1; + } + } + } + return dp[N - 1]; + } + + // 为了测试 + public static String randomString(int len) { + char[] str = new char[len]; + for (int i = 0; i < len; i++) { + str[i] = (char) ((int) (Math.random() * 10) + '0'); + } + return String.valueOf(str); + } + + // 为了测试 + public static void main(String[] args) { + int N = 30; + int testTime = 1000000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int len = (int) (Math.random() * N); + String s = randomString(len); + int ans0 = number(s); + int ans1 = dp1(s); + int ans2 = dp2(s); + if (ans0 != ans1 || ans0 != ans2) { + System.out.println(s); + System.out.println(ans0); + System.out.println(ans1); + System.out.println(ans2); + System.out.println("Oops!"); + break; + } + } + System.out.println("测试结束"); + } + +} diff --git a/体系学习班/class19/Code03_StickersToSpellWord.java b/体系学习班/class19/Code03_StickersToSpellWord.java new file mode 100644 index 0000000..90b3a4d --- /dev/null +++ b/体系学习班/class19/Code03_StickersToSpellWord.java @@ -0,0 +1,151 @@ +package class19; + +import java.util.HashMap; + +// 本题测试链接:https://leetcode.com/problems/stickers-to-spell-word +public class Code03_StickersToSpellWord { + + public static int minStickers1(String[] stickers, String target) { + int ans = process1(stickers, target); + return ans == Integer.MAX_VALUE ? -1 : ans; + } + + // 所有贴纸stickers,每一种贴纸都有无穷张 + // target + // 最少张数 + public static int process1(String[] stickers, String target) { + if (target.length() == 0) { + return 0; + } + int min = Integer.MAX_VALUE; + for (String first : stickers) { + String rest = minus(target, first); + if (rest.length() != target.length()) { + min = Math.min(min, process1(stickers, rest)); + } + } + return min + (min == Integer.MAX_VALUE ? 0 : 1); + } + + public static String minus(String s1, String s2) { + char[] str1 = s1.toCharArray(); + char[] str2 = s2.toCharArray(); + int[] count = new int[26]; + for (char cha : str1) { + count[cha - 'a']++; + } + for (char cha : str2) { + count[cha - 'a']--; + } + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < 26; i++) { + if (count[i] > 0) { + for (int j = 0; j < count[i]; j++) { + builder.append((char) (i + 'a')); + } + } + } + return builder.toString(); + } + + public static int minStickers2(String[] stickers, String target) { + int N = stickers.length; + // 关键优化(用词频表替代贴纸数组) + int[][] counts = new int[N][26]; + for (int i = 0; i < N; i++) { + char[] str = stickers[i].toCharArray(); + for (char cha : str) { + counts[i][cha - 'a']++; + } + } + int ans = process2(counts, target); + return ans == Integer.MAX_VALUE ? -1 : ans; + } + + // stickers[i] 数组,当初i号贴纸的字符统计 int[][] stickers -> 所有的贴纸 + // 每一种贴纸都有无穷张 + // 返回搞定target的最少张数 + // 最少张数 + public static int process2(int[][] stickers, String t) { + if (t.length() == 0) { + return 0; + } + // target做出词频统计 + // target aabbc 2 2 1.. + // 0 1 2.. + char[] target = t.toCharArray(); + int[] tcounts = new int[26]; + for (char cha : target) { + tcounts[cha - 'a']++; + } + int N = stickers.length; + int min = Integer.MAX_VALUE; + for (int i = 0; i < N; i++) { + // 尝试第一张贴纸是谁 + int[] sticker = stickers[i]; + // 最关键的优化(重要的剪枝!这一步也是贪心!) + if (sticker[target[0] - 'a'] > 0) { + StringBuilder builder = new StringBuilder(); + for (int j = 0; j < 26; j++) { + if (tcounts[j] > 0) { + int nums = tcounts[j] - sticker[j]; + for (int k = 0; k < nums; k++) { + builder.append((char) (j + 'a')); + } + } + } + String rest = builder.toString(); + min = Math.min(min, process2(stickers, rest)); + } + } + return min + (min == Integer.MAX_VALUE ? 0 : 1); + } + + public static int minStickers3(String[] stickers, String target) { + int N = stickers.length; + int[][] counts = new int[N][26]; + for (int i = 0; i < N; i++) { + char[] str = stickers[i].toCharArray(); + for (char cha : str) { + counts[i][cha - 'a']++; + } + } + HashMap dp = new HashMap<>(); + dp.put("", 0); + int ans = process3(counts, target, dp); + return ans == Integer.MAX_VALUE ? -1 : ans; + } + + public static int process3(int[][] stickers, String t, HashMap dp) { + if (dp.containsKey(t)) { + return dp.get(t); + } + char[] target = t.toCharArray(); + int[] tcounts = new int[26]; + for (char cha : target) { + tcounts[cha - 'a']++; + } + int N = stickers.length; + int min = Integer.MAX_VALUE; + for (int i = 0; i < N; i++) { + int[] sticker = stickers[i]; + if (sticker[target[0] - 'a'] > 0) { + StringBuilder builder = new StringBuilder(); + for (int j = 0; j < 26; j++) { + if (tcounts[j] > 0) { + int nums = tcounts[j] - sticker[j]; + for (int k = 0; k < nums; k++) { + builder.append((char) (j + 'a')); + } + } + } + String rest = builder.toString(); + min = Math.min(min, process3(stickers, rest, dp)); + } + } + int ans = min + (min == Integer.MAX_VALUE ? 0 : 1); + dp.put(t, ans); + return ans; + } + +} diff --git a/体系学习班/class19/Code04_LongestCommonSubsequence.java b/体系学习班/class19/Code04_LongestCommonSubsequence.java new file mode 100644 index 0000000..5e49551 --- /dev/null +++ b/体系学习班/class19/Code04_LongestCommonSubsequence.java @@ -0,0 +1,124 @@ +package class19; + +// 这个问题leetcode上可以直接测 +// 链接:https://leetcode.com/problems/longest-common-subsequence/ +public class Code04_LongestCommonSubsequence { + + public static int longestCommonSubsequence1(String s1, String s2) { + if (s1 == null || s2 == null || s1.length() == 0 || s2.length() == 0) { + return 0; + } + char[] str1 = s1.toCharArray(); + char[] str2 = s2.toCharArray(); + // 尝试 + return process1(str1, str2, str1.length - 1, str2.length - 1); + } + + // str1[0...i]和str2[0...j],这个范围上最长公共子序列长度是多少? + // 可能性分类: + // a) 最长公共子序列,一定不以str1[i]字符结尾、也一定不以str2[j]字符结尾 + // b) 最长公共子序列,可能以str1[i]字符结尾、但是一定不以str2[j]字符结尾 + // c) 最长公共子序列,一定不以str1[i]字符结尾、但是可能以str2[j]字符结尾 + // d) 最长公共子序列,必须以str1[i]字符结尾、也必须以str2[j]字符结尾 + // 注意:a)、b)、c)、d)并不是完全互斥的,他们可能会有重叠的情况 + // 但是可以肯定,答案不会超过这四种可能性的范围 + // 那么我们分别来看一下,这几种可能性怎么调用后续的递归。 + // a) 最长公共子序列,一定不以str1[i]字符结尾、也一定不以str2[j]字符结尾 + // 如果是这种情况,那么有没有str1[i]和str2[j]就根本不重要了,因为这两个字符一定没用啊 + // 所以砍掉这两个字符,最长公共子序列 = str1[0...i-1]与str2[0...j-1]的最长公共子序列长度(后续递归) + // b) 最长公共子序列,可能以str1[i]字符结尾、但是一定不以str2[j]字符结尾 + // 如果是这种情况,那么我们可以确定str2[j]一定没有用,要砍掉;但是str1[i]可能有用,所以要保留 + // 所以,最长公共子序列 = str1[0...i]与str2[0...j-1]的最长公共子序列长度(后续递归) + // c) 最长公共子序列,一定不以str1[i]字符结尾、但是可能以str2[j]字符结尾 + // 跟上面分析过程类似,最长公共子序列 = str1[0...i-1]与str2[0...j]的最长公共子序列长度(后续递归) + // d) 最长公共子序列,必须以str1[i]字符结尾、也必须以str2[j]字符结尾 + // 同时可以看到,可能性d)存在的条件,一定是在str1[i] == str2[j]的情况下,才成立的 + // 所以,最长公共子序列总长度 = str1[0...i-1]与str2[0...j-1]的最长公共子序列长度(后续递归) + 1(共同的结尾) + // 综上,四种情况已经穷尽了所有可能性。四种情况中取最大即可 + // 其中b)、c)一定参与最大值的比较, + // 当str1[i] == str2[j]时,a)一定比d)小,所以d)参与 + // 当str1[i] != str2[j]时,d)压根不存在,所以a)参与 + // 但是再次注意了! + // a)是:str1[0...i-1]与str2[0...j-1]的最长公共子序列长度 + // b)是:str1[0...i]与str2[0...j-1]的最长公共子序列长度 + // c)是:str1[0...i-1]与str2[0...j]的最长公共子序列长度 + // a)中str1的范围 < b)中str1的范围,a)中str2的范围 == b)中str2的范围 + // 所以a)不用求也知道,它比不过b)啊,因为有一个样本的范围比b)小啊! + // a)中str1的范围 == c)中str1的范围,a)中str2的范围 < c)中str2的范围 + // 所以a)不用求也知道,它比不过c)啊,因为有一个样本的范围比c)小啊! + // 至此,可以知道,a)就是个垃圾,有它没它,都不影响最大值的决策 + // 所以,当str1[i] == str2[j]时,b)、c)、d)中选出最大值 + // 当str1[i] != str2[j]时,b)、c)中选出最大值 + public static int process1(char[] str1, char[] str2, int i, int j) { + if (i == 0 && j == 0) { + // str1[0..0]和str2[0..0],都只剩一个字符了 + // 那如果字符相等,公共子序列长度就是1,不相等就是0 + // 这显而易见 + return str1[i] == str2[j] ? 1 : 0; + } else if (i == 0) { + // 这里的情况为: + // str1[0...0]和str2[0...j],str1只剩1个字符了,但是str2不只一个字符 + // 因为str1只剩一个字符了,所以str1[0...0]和str2[0...j]公共子序列最多长度为1 + // 如果str1[0] == str2[j],那么此时相等已经找到了!公共子序列长度就是1,也不可能更大了 + // 如果str1[0] != str2[j],只是此时不相等而已, + // 那么str2[0...j-1]上有没有字符等于str1[0]呢?不知道,所以递归继续找 + if (str1[i] == str2[j]) { + return 1; + } else { + return process1(str1, str2, i, j - 1); + } + } else if (j == 0) { + // 和上面的else if同理 + // str1[0...i]和str2[0...0],str2只剩1个字符了,但是str1不只一个字符 + // 因为str2只剩一个字符了,所以str1[0...i]和str2[0...0]公共子序列最多长度为1 + // 如果str1[i] == str2[0],那么此时相等已经找到了!公共子序列长度就是1,也不可能更大了 + // 如果str1[i] != str2[0],只是此时不相等而已, + // 那么str1[0...i-1]上有没有字符等于str2[0]呢?不知道,所以递归继续找 + if (str1[i] == str2[j]) { + return 1; + } else { + return process1(str1, str2, i - 1, j); + } + } else { // i != 0 && j != 0 + // 这里的情况为: + // str1[0...i]和str2[0...i],str1和str2都不只一个字符 + // 看函数开始之前的注释部分 + // p1就是可能性c) + int p1 = process1(str1, str2, i - 1, j); + // p2就是可能性b) + int p2 = process1(str1, str2, i, j - 1); + // p3就是可能性d),如果可能性d)存在,即str1[i] == str2[j],那么p3就求出来,参与pk + // 如果可能性d)不存在,即str1[i] != str2[j],那么让p3等于0,然后去参与pk,反正不影响 + int p3 = str1[i] == str2[j] ? (1 + process1(str1, str2, i - 1, j - 1)) : 0; + return Math.max(p1, Math.max(p2, p3)); + } + } + + public static int longestCommonSubsequence2(String s1, String s2) { + if (s1 == null || s2 == null || s1.length() == 0 || s2.length() == 0) { + return 0; + } + char[] str1 = s1.toCharArray(); + char[] str2 = s2.toCharArray(); + int N = str1.length; + int M = str2.length; + int[][] dp = new int[N][M]; + dp[0][0] = str1[0] == str2[0] ? 1 : 0; + for (int j = 1; j < M; j++) { + dp[0][j] = str1[0] == str2[j] ? 1 : dp[0][j - 1]; + } + for (int i = 1; i < N; i++) { + dp[i][0] = str1[i] == str2[0] ? 1 : dp[i - 1][0]; + } + for (int i = 1; i < N; i++) { + for (int j = 1; j < M; j++) { + int p1 = dp[i - 1][j]; + int p2 = dp[i][j - 1]; + int p3 = str1[i] == str2[j] ? (1 + dp[i - 1][j - 1]) : 0; + dp[i][j] = Math.max(p1, Math.max(p2, p3)); + } + } + return dp[N - 1][M - 1]; + } + +} diff --git a/体系学习班/class20/Code01_PalindromeSubsequence.java b/体系学习班/class20/Code01_PalindromeSubsequence.java new file mode 100644 index 0000000..dd38429 --- /dev/null +++ b/体系学习班/class20/Code01_PalindromeSubsequence.java @@ -0,0 +1,121 @@ +package class20; + +// 测试链接:https://leetcode.com/problems/longest-palindromic-subsequence/ +public class Code01_PalindromeSubsequence { + + public static int lpsl1(String s) { + if (s == null || s.length() == 0) { + return 0; + } + char[] str = s.toCharArray(); + return f(str, 0, str.length - 1); + } + + // str[L..R]最长回文子序列长度返回 + public static int f(char[] str, int L, int R) { + if (L == R) { + return 1; + } + if (L == R - 1) { + return str[L] == str[R] ? 2 : 1; + } + int p1 = f(str, L + 1, R - 1); + int p2 = f(str, L, R - 1); + int p3 = f(str, L + 1, R); + int p4 = str[L] != str[R] ? 0 : (2 + f(str, L + 1, R - 1)); + return Math.max(Math.max(p1, p2), Math.max(p3, p4)); + } + + public static int lpsl2(String s) { + if (s == null || s.length() == 0) { + return 0; + } + char[] str = s.toCharArray(); + int N = str.length; + int[][] dp = new int[N][N]; + dp[N - 1][N - 1] = 1; + for (int i = 0; i < N - 1; i++) { + dp[i][i] = 1; + dp[i][i + 1] = str[i] == str[i + 1] ? 2 : 1; + } + for (int L = N - 3; L >= 0; L--) { + for (int R = L + 2; R < N; R++) { + dp[L][R] = Math.max(dp[L][R - 1], dp[L + 1][R]); + if (str[L] == str[R]) { + dp[L][R] = Math.max(dp[L][R], 2 + dp[L + 1][R - 1]); + } + } + } + return dp[0][N - 1]; + } + + public static int longestPalindromeSubseq1(String s) { + if (s == null || s.length() == 0) { + return 0; + } + if (s.length() == 1) { + return 1; + } + char[] str = s.toCharArray(); + char[] reverse = reverse(str); + return longestCommonSubsequence(str, reverse); + } + + public static char[] reverse(char[] str) { + int N = str.length; + char[] reverse = new char[str.length]; + for (int i = 0; i < str.length; i++) { + reverse[--N] = str[i]; + } + return reverse; + } + + public static int longestCommonSubsequence(char[] str1, char[] str2) { + int N = str1.length; + int M = str2.length; + int[][] dp = new int[N][M]; + dp[0][0] = str1[0] == str2[0] ? 1 : 0; + for (int i = 1; i < N; i++) { + dp[i][0] = str1[i] == str2[0] ? 1 : dp[i - 1][0]; + } + for (int j = 1; j < M; j++) { + dp[0][j] = str1[0] == str2[j] ? 1 : dp[0][j - 1]; + } + for (int i = 1; i < N; i++) { + for (int j = 1; j < M; j++) { + dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]); + if (str1[i] == str2[j]) { + dp[i][j] = Math.max(dp[i][j], dp[i - 1][j - 1] + 1); + } + } + } + return dp[N - 1][M - 1]; + } + + public static int longestPalindromeSubseq2(String s) { + if (s == null || s.length() == 0) { + return 0; + } + if (s.length() == 1) { + return 1; + } + char[] str = s.toCharArray(); + int N = str.length; + int[][] dp = new int[N][N]; + dp[N - 1][N - 1] = 1; + for (int i = 0; i < N - 1; i++) { + dp[i][i] = 1; + dp[i][i + 1] = str[i] == str[i + 1] ? 2 : 1; + } + for (int i = N - 3; i >= 0; i--) { + for (int j = i + 2; j < N; j++) { + dp[i][j] = Math.max(dp[i][j - 1], dp[i + 1][j]); + if (str[i] == str[j]) { + dp[i][j] = Math.max(dp[i][j], dp[i + 1][j - 1] + 2); + } + } + } + return dp[0][N - 1]; + } + +} diff --git a/体系学习班/class20/Code02_HorseJump.java b/体系学习班/class20/Code02_HorseJump.java new file mode 100644 index 0000000..f96f4a1 --- /dev/null +++ b/体系学习班/class20/Code02_HorseJump.java @@ -0,0 +1,109 @@ +package class20; + +public class Code02_HorseJump { + + // 当前来到的位置是(x,y) + // 还剩下rest步需要跳 + // 跳完rest步,正好跳到a,b的方法数是多少? + // 10 * 9 + public static int jump(int a, int b, int k) { + return process(0, 0, k, a, b); + } + + public static int process(int x, int y, int rest, int a, int b) { + if (x < 0 || x > 9 || y < 0 || y > 8) { + return 0; + } + if (rest == 0) { + return (x == a && y == b) ? 1 : 0; + } + int ways = process(x + 2, y + 1, rest - 1, a, b); + ways += process(x + 1, y + 2, rest - 1, a, b); + ways += process(x - 1, y + 2, rest - 1, a, b); + ways += process(x - 2, y + 1, rest - 1, a, b); + ways += process(x - 2, y - 1, rest - 1, a, b); + ways += process(x - 1, y - 2, rest - 1, a, b); + ways += process(x + 1, y - 2, rest - 1, a, b); + ways += process(x + 2, y - 1, rest - 1, a, b); + return ways; + } + + public static int dp(int a, int b, int k) { + int[][][] dp = new int[10][9][k + 1]; + dp[a][b][0] = 1; + for (int rest = 1; rest <= k; rest++) { + for (int x = 0; x < 10; x++) { + for (int y = 0; y < 9; y++) { + int ways = pick(dp, x + 2, y + 1, rest - 1); + ways += pick(dp, x + 1, y + 2, rest - 1); + ways += pick(dp, x - 1, y + 2, rest - 1); + ways += pick(dp, x - 2, y + 1, rest - 1); + ways += pick(dp, x - 2, y - 1, rest - 1); + ways += pick(dp, x - 1, y - 2, rest - 1); + ways += pick(dp, x + 1, y - 2, rest - 1); + ways += pick(dp, x + 2, y - 1, rest - 1); + dp[x][y][rest] = ways; + } + } + } + return dp[0][0][k]; + } + + public static int pick(int[][][] dp, int x, int y, int rest) { + if (x < 0 || x > 9 || y < 0 || y > 8) { + return 0; + } + return dp[x][y][rest]; + } + + public static int ways(int a, int b, int step) { + return f(0, 0, step, a, b); + } + + public static int f(int i, int j, int step, int a, int b) { + if (i < 0 || i > 9 || j < 0 || j > 8) { + return 0; + } + if (step == 0) { + return (i == a && j == b) ? 1 : 0; + } + return f(i - 2, j + 1, step - 1, a, b) + f(i - 1, j + 2, step - 1, a, b) + f(i + 1, j + 2, step - 1, a, b) + + f(i + 2, j + 1, step - 1, a, b) + f(i + 2, j - 1, step - 1, a, b) + f(i + 1, j - 2, step - 1, a, b) + + f(i - 1, j - 2, step - 1, a, b) + f(i - 2, j - 1, step - 1, a, b); + + } + + public static int waysdp(int a, int b, int s) { + int[][][] dp = new int[10][9][s + 1]; + dp[a][b][0] = 1; + for (int step = 1; step <= s; step++) { // 按层来 + for (int i = 0; i < 10; i++) { + for (int j = 0; j < 9; j++) { + dp[i][j][step] = getValue(dp, i - 2, j + 1, step - 1) + getValue(dp, i - 1, j + 2, step - 1) + + getValue(dp, i + 1, j + 2, step - 1) + getValue(dp, i + 2, j + 1, step - 1) + + getValue(dp, i + 2, j - 1, step - 1) + getValue(dp, i + 1, j - 2, step - 1) + + getValue(dp, i - 1, j - 2, step - 1) + getValue(dp, i - 2, j - 1, step - 1); + } + } + } + return dp[0][0][s]; + } + + // 在dp表中,得到dp[i][j][step]的值,但如果(i,j)位置越界的话,返回0; + public static int getValue(int[][][] dp, int i, int j, int step) { + if (i < 0 || i > 9 || j < 0 || j > 8) { + return 0; + } + return dp[i][j][step]; + } + + public static void main(String[] args) { + int x = 7; + int y = 7; + int step = 10; + System.out.println(ways(x, y, step)); + System.out.println(dp(x, y, step)); + + System.out.println(jump(x, y, step)); + } +} diff --git a/体系学习班/class20/Code03_Coffee.java b/体系学习班/class20/Code03_Coffee.java new file mode 100644 index 0000000..eb2b620 --- /dev/null +++ b/体系学习班/class20/Code03_Coffee.java @@ -0,0 +1,204 @@ +package class20; + +import java.util.Arrays; +import java.util.Comparator; +import java.util.PriorityQueue; + +// 题目 +// 数组arr代表每一个咖啡机冲一杯咖啡的时间,每个咖啡机只能串行的制造咖啡。 +// 现在有n个人需要喝咖啡,只能用咖啡机来制造咖啡。 +// 认为每个人喝咖啡的时间非常短,冲好的时间即是喝完的时间。 +// 每个人喝完之后咖啡杯可以选择洗或者自然挥发干净,只有一台洗咖啡杯的机器,只能串行的洗咖啡杯。 +// 洗杯子的机器洗完一个杯子时间为a,任何一个杯子自然挥发干净的时间为b。 +// 四个参数:arr, n, a, b +// 假设时间点从0开始,返回所有人喝完咖啡并洗完咖啡杯的全部过程结束后,至少来到什么时间点。 +public class Code03_Coffee { + + // 验证的方法 + // 彻底的暴力 + // 很慢但是绝对正确 + public static int right(int[] arr, int n, int a, int b) { + int[] times = new int[arr.length]; + int[] drink = new int[n]; + return forceMake(arr, times, 0, drink, n, a, b); + } + + // 每个人暴力尝试用每一个咖啡机给自己做咖啡 + public static int forceMake(int[] arr, int[] times, int kth, int[] drink, int n, int a, int b) { + if (kth == n) { + int[] drinkSorted = Arrays.copyOf(drink, kth); + Arrays.sort(drinkSorted); + return forceWash(drinkSorted, a, b, 0, 0, 0); + } + int time = Integer.MAX_VALUE; + for (int i = 0; i < arr.length; i++) { + int work = arr[i]; + int pre = times[i]; + drink[kth] = pre + work; + times[i] = pre + work; + time = Math.min(time, forceMake(arr, times, kth + 1, drink, n, a, b)); + drink[kth] = 0; + times[i] = pre; + } + return time; + } + + public static int forceWash(int[] drinks, int a, int b, int index, int washLine, int time) { + if (index == drinks.length) { + return time; + } + // 选择一:当前index号咖啡杯,选择用洗咖啡机刷干净 + int wash = Math.max(drinks[index], washLine) + a; + int ans1 = forceWash(drinks, a, b, index + 1, wash, Math.max(wash, time)); + + // 选择二:当前index号咖啡杯,选择自然挥发 + int dry = drinks[index] + b; + int ans2 = forceWash(drinks, a, b, index + 1, washLine, Math.max(dry, time)); + return Math.min(ans1, ans2); + } + + // 以下为贪心+优良暴力 + public static class Machine { + public int timePoint; + public int workTime; + + public Machine(int t, int w) { + timePoint = t; + workTime = w; + } + } + + public static class MachineComparator implements Comparator { + + @Override + public int compare(Machine o1, Machine o2) { + return (o1.timePoint + o1.workTime) - (o2.timePoint + o2.workTime); + } + + } + + // 优良一点的暴力尝试的方法 + public static int minTime1(int[] arr, int n, int a, int b) { + PriorityQueue heap = new PriorityQueue(new MachineComparator()); + for (int i = 0; i < arr.length; i++) { + heap.add(new Machine(0, arr[i])); + } + int[] drinks = new int[n]; + for (int i = 0; i < n; i++) { + Machine cur = heap.poll(); + cur.timePoint += cur.workTime; + drinks[i] = cur.timePoint; + heap.add(cur); + } + return bestTime(drinks, a, b, 0, 0); + } + + // drinks 所有杯子可以开始洗的时间 + // wash 单杯洗干净的时间(串行) + // air 挥发干净的时间(并行) + // free 洗的机器什么时候可用 + // drinks[index.....]都变干净,最早的结束时间(返回) + public static int bestTime(int[] drinks, int wash, int air, int index, int free) { + if (index == drinks.length) { + return 0; + } + // index号杯子 决定洗 + int selfClean1 = Math.max(drinks[index], free) + wash; + int restClean1 = bestTime(drinks, wash, air, index + 1, selfClean1); + int p1 = Math.max(selfClean1, restClean1); + + // index号杯子 决定挥发 + int selfClean2 = drinks[index] + air; + int restClean2 = bestTime(drinks, wash, air, index + 1, free); + int p2 = Math.max(selfClean2, restClean2); + return Math.min(p1, p2); + } + + // 贪心+优良尝试改成动态规划 + public static int minTime2(int[] arr, int n, int a, int b) { + PriorityQueue heap = new PriorityQueue(new MachineComparator()); + for (int i = 0; i < arr.length; i++) { + heap.add(new Machine(0, arr[i])); + } + int[] drinks = new int[n]; + for (int i = 0; i < n; i++) { + Machine cur = heap.poll(); + cur.timePoint += cur.workTime; + drinks[i] = cur.timePoint; + heap.add(cur); + } + return bestTimeDp(drinks, a, b); + } + + public static int bestTimeDp(int[] drinks, int wash, int air) { + int N = drinks.length; + int maxFree = 0; + for (int i = 0; i < drinks.length; i++) { + maxFree = Math.max(maxFree, drinks[i]) + wash; + } + int[][] dp = new int[N + 1][maxFree + 1]; + for (int index = N - 1; index >= 0; index--) { + for (int free = 0; free <= maxFree; free++) { + int selfClean1 = Math.max(drinks[index], free) + wash; + if (selfClean1 > maxFree) { + break; // 因为后面的也都不用填了 + } + // index号杯子 决定洗 + int restClean1 = dp[index + 1][selfClean1]; + int p1 = Math.max(selfClean1, restClean1); + // index号杯子 决定挥发 + int selfClean2 = drinks[index] + air; + int restClean2 = dp[index + 1][free]; + int p2 = Math.max(selfClean2, restClean2); + dp[index][free] = Math.min(p1, p2); + } + } + return dp[0][0]; + } + + // for test + public static int[] randomArray(int len, int max) { + int[] arr = new int[len]; + for (int i = 0; i < len; i++) { + arr[i] = (int) (Math.random() * max) + 1; + } + return arr; + } + + // for test + public static void printArray(int[] arr) { + System.out.print("arr : "); + for (int j = 0; j < arr.length; j++) { + System.out.print(arr[j] + ", "); + } + System.out.println(); + } + + public static void main(String[] args) { + int len = 10; + int max = 10; + int testTime = 10; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int[] arr = randomArray(len, max); + int n = (int) (Math.random() * 7) + 1; + int a = (int) (Math.random() * 7) + 1; + int b = (int) (Math.random() * 10) + 1; + int ans1 = right(arr, n, a, b); + int ans2 = minTime1(arr, n, a, b); + int ans3 = minTime2(arr, n, a, b); + if (ans1 != ans2 || ans2 != ans3) { + printArray(arr); + System.out.println("n : " + n); + System.out.println("a : " + a); + System.out.println("b : " + b); + System.out.println(ans1 + " , " + ans2 + " , " + ans3); + System.out.println("==============="); + break; + } + } + System.out.println("测试结束"); + + } + +} diff --git a/体系学习班/class21/Code01_MinPathSum.java b/体系学习班/class21/Code01_MinPathSum.java new file mode 100644 index 0000000..c537438 --- /dev/null +++ b/体系学习班/class21/Code01_MinPathSum.java @@ -0,0 +1,79 @@ +package class21; + +public class Code01_MinPathSum { + + public static int minPathSum1(int[][] m) { + if (m == null || m.length == 0 || m[0] == null || m[0].length == 0) { + return 0; + } + int row = m.length; + int col = m[0].length; + int[][] dp = new int[row][col]; + dp[0][0] = m[0][0]; + for (int i = 1; i < row; i++) { + dp[i][0] = dp[i - 1][0] + m[i][0]; + } + for (int j = 1; j < col; j++) { + dp[0][j] = dp[0][j - 1] + m[0][j]; + } + for (int i = 1; i < row; i++) { + for (int j = 1; j < col; j++) { + dp[i][j] = Math.min(dp[i - 1][j], dp[i][j - 1]) + m[i][j]; + } + } + return dp[row - 1][col - 1]; + } + + public static int minPathSum2(int[][] m) { + if (m == null || m.length == 0 || m[0] == null || m[0].length == 0) { + return 0; + } + int row = m.length; + int col = m[0].length; + int[] dp = new int[col]; + dp[0] = m[0][0]; + for (int j = 1; j < col; j++) { + dp[j] = dp[j - 1] + m[0][j]; + } + for (int i = 1; i < row; i++) { + dp[0] += m[i][0]; + for (int j = 1; j < col; j++) { + dp[j] = Math.min(dp[j - 1], dp[j]) + m[i][j]; + } + } + return dp[col - 1]; + } + + // for test + public static int[][] generateRandomMatrix(int rowSize, int colSize) { + if (rowSize < 0 || colSize < 0) { + return null; + } + int[][] result = new int[rowSize][colSize]; + for (int i = 0; i != result.length; i++) { + for (int j = 0; j != result[0].length; j++) { + result[i][j] = (int) (Math.random() * 100); + } + } + return result; + } + + // for test + public static void printMatrix(int[][] matrix) { + for (int i = 0; i != matrix.length; i++) { + for (int j = 0; j != matrix[0].length; j++) { + System.out.print(matrix[i][j] + " "); + } + System.out.println(); + } + } + + public static void main(String[] args) { + int rowSize = 10; + int colSize = 10; + int[][] m = generateRandomMatrix(rowSize, colSize); + System.out.println(minPathSum1(m)); + System.out.println(minPathSum2(m)); + + } +} diff --git a/体系学习班/class21/Code02_CoinsWayEveryPaperDifferent.java b/体系学习班/class21/Code02_CoinsWayEveryPaperDifferent.java new file mode 100644 index 0000000..a162b06 --- /dev/null +++ b/体系学习班/class21/Code02_CoinsWayEveryPaperDifferent.java @@ -0,0 +1,77 @@ +package class21; + +public class Code02_CoinsWayEveryPaperDifferent { + + public static int coinWays(int[] arr, int aim) { + return process(arr, 0, aim); + } + + // arr[index....] 组成正好rest这么多的钱,有几种方法 + public static int process(int[] arr, int index, int rest) { + if (rest < 0) { + return 0; + } + if (index == arr.length) { // 没钱了! + return rest == 0 ? 1 : 0; + } else { + return process(arr, index + 1, rest) + process(arr, index + 1, rest - arr[index]); + } + } + + public static int dp(int[] arr, int aim) { + if (aim == 0) { + return 1; + } + int N = arr.length; + int[][] dp = new int[N + 1][aim + 1]; + dp[N][0] = 1; + for (int index = N - 1; index >= 0; index--) { + for (int rest = 0; rest <= aim; rest++) { + dp[index][rest] = dp[index + 1][rest] + (rest - arr[index] >= 0 ? dp[index + 1][rest - arr[index]] : 0); + } + } + return dp[0][aim]; + } + + // 为了测试 + public static int[] randomArray(int maxLen, int maxValue) { + int N = (int) (Math.random() * maxLen); + int[] arr = new int[N]; + for (int i = 0; i < N; i++) { + arr[i] = (int) (Math.random() * maxValue) + 1; + } + return arr; + } + + // 为了测试 + public static void printArray(int[] arr) { + for (int i = 0; i < arr.length; i++) { + System.out.print(arr[i] + " "); + } + System.out.println(); + } + + // 为了测试 + public static void main(String[] args) { + int maxLen = 20; + int maxValue = 30; + int testTime = 1000000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int[] arr = randomArray(maxLen, maxValue); + int aim = (int) (Math.random() * maxValue); + int ans1 = coinWays(arr, aim); + int ans2 = dp(arr, aim); + if (ans1 != ans2) { + System.out.println("Oops!"); + printArray(arr); + System.out.println(aim); + System.out.println(ans1); + System.out.println(ans2); + break; + } + } + System.out.println("测试结束"); + } + +} diff --git a/体系学习班/class21/Code03_CoinsWayNoLimit.java b/体系学习班/class21/Code03_CoinsWayNoLimit.java new file mode 100644 index 0000000..7759d9c --- /dev/null +++ b/体系学习班/class21/Code03_CoinsWayNoLimit.java @@ -0,0 +1,108 @@ +package class21; + +public class Code03_CoinsWayNoLimit { + + public static int coinsWay(int[] arr, int aim) { + if (arr == null || arr.length == 0 || aim < 0) { + return 0; + } + return process(arr, 0, aim); + } + + // arr[index....] 所有的面值,每一个面值都可以任意选择张数,组成正好rest这么多钱,方法数多少? + public static int process(int[] arr, int index, int rest) { + if (index == arr.length) { // 没钱了 + return rest == 0 ? 1 : 0; + } + int ways = 0; + for (int zhang = 0; zhang * arr[index] <= rest; zhang++) { + ways += process(arr, index + 1, rest - (zhang * arr[index])); + } + return ways; + } + + public static int dp1(int[] arr, int aim) { + if (arr == null || arr.length == 0 || aim < 0) { + return 0; + } + int N = arr.length; + int[][] dp = new int[N + 1][aim + 1]; + dp[N][0] = 1; + for (int index = N - 1; index >= 0; index--) { + for (int rest = 0; rest <= aim; rest++) { + int ways = 0; + for (int zhang = 0; zhang * arr[index] <= rest; zhang++) { + ways += dp[index + 1][rest - (zhang * arr[index])]; + } + dp[index][rest] = ways; + } + } + return dp[0][aim]; + } + + public static int dp2(int[] arr, int aim) { + if (arr == null || arr.length == 0 || aim < 0) { + return 0; + } + int N = arr.length; + int[][] dp = new int[N + 1][aim + 1]; + dp[N][0] = 1; + for (int index = N - 1; index >= 0; index--) { + for (int rest = 0; rest <= aim; rest++) { + dp[index][rest] = dp[index + 1][rest]; + if (rest - arr[index] >= 0) { + dp[index][rest] += dp[index][rest - arr[index]]; + } + } + } + return dp[0][aim]; + } + + // 为了测试 + public static int[] randomArray(int maxLen, int maxValue) { + int N = (int) (Math.random() * maxLen); + int[] arr = new int[N]; + boolean[] has = new boolean[maxValue + 1]; + for (int i = 0; i < N; i++) { + do { + arr[i] = (int) (Math.random() * maxValue) + 1; + } while (has[arr[i]]); + has[arr[i]] = true; + } + return arr; + } + + // 为了测试 + public static void printArray(int[] arr) { + for (int i = 0; i < arr.length; i++) { + System.out.print(arr[i] + " "); + } + System.out.println(); + } + + // 为了测试 + public static void main(String[] args) { + int maxLen = 10; + int maxValue = 30; + int testTime = 1000000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int[] arr = randomArray(maxLen, maxValue); + int aim = (int) (Math.random() * maxValue); + int ans1 = coinsWay(arr, aim); + int ans2 = dp1(arr, aim); + int ans3 = dp2(arr, aim); + if (ans1 != ans2 || ans1 != ans3) { + System.out.println("Oops!"); + printArray(arr); + System.out.println(aim); + System.out.println(ans1); + System.out.println(ans2); + System.out.println(ans3); + break; + } + } + System.out.println("测试结束"); + } + +} diff --git a/体系学习班/class21/Code04_CoinsWaySameValueSamePapper.java b/体系学习班/class21/Code04_CoinsWaySameValueSamePapper.java new file mode 100644 index 0000000..f45c919 --- /dev/null +++ b/体系学习班/class21/Code04_CoinsWaySameValueSamePapper.java @@ -0,0 +1,148 @@ +package class21; + +import java.util.HashMap; +import java.util.Map.Entry; + +public class Code04_CoinsWaySameValueSamePapper { + + public static class Info { + public int[] coins; + public int[] zhangs; + + public Info(int[] c, int[] z) { + coins = c; + zhangs = z; + } + } + + public static Info getInfo(int[] arr) { + HashMap counts = new HashMap<>(); + for (int value : arr) { + if (!counts.containsKey(value)) { + counts.put(value, 1); + } else { + counts.put(value, counts.get(value) + 1); + } + } + int N = counts.size(); + int[] coins = new int[N]; + int[] zhangs = new int[N]; + int index = 0; + for (Entry entry : counts.entrySet()) { + coins[index] = entry.getKey(); + zhangs[index++] = entry.getValue(); + } + return new Info(coins, zhangs); + } + + public static int coinsWay(int[] arr, int aim) { + if (arr == null || arr.length == 0 || aim < 0) { + return 0; + } + Info info = getInfo(arr); + return process(info.coins, info.zhangs, 0, aim); + } + + // coins 面值数组,正数且去重 + // zhangs 每种面值对应的张数 + public static int process(int[] coins, int[] zhangs, int index, int rest) { + if (index == coins.length) { + return rest == 0 ? 1 : 0; + } + int ways = 0; + for (int zhang = 0; zhang * coins[index] <= rest && zhang <= zhangs[index]; zhang++) { + ways += process(coins, zhangs, index + 1, rest - (zhang * coins[index])); + } + return ways; + } + + public static int dp1(int[] arr, int aim) { + if (arr == null || arr.length == 0 || aim < 0) { + return 0; + } + Info info = getInfo(arr); + int[] coins = info.coins; + int[] zhangs = info.zhangs; + int N = coins.length; + int[][] dp = new int[N + 1][aim + 1]; + dp[N][0] = 1; + for (int index = N - 1; index >= 0; index--) { + for (int rest = 0; rest <= aim; rest++) { + int ways = 0; + for (int zhang = 0; zhang * coins[index] <= rest && zhang <= zhangs[index]; zhang++) { + ways += dp[index + 1][rest - (zhang * coins[index])]; + } + dp[index][rest] = ways; + } + } + return dp[0][aim]; + } + + public static int dp2(int[] arr, int aim) { + if (arr == null || arr.length == 0 || aim < 0) { + return 0; + } + Info info = getInfo(arr); + int[] coins = info.coins; + int[] zhangs = info.zhangs; + int N = coins.length; + int[][] dp = new int[N + 1][aim + 1]; + dp[N][0] = 1; + for (int index = N - 1; index >= 0; index--) { + for (int rest = 0; rest <= aim; rest++) { + dp[index][rest] = dp[index + 1][rest]; + if (rest - coins[index] >= 0) { + dp[index][rest] += dp[index][rest - coins[index]]; + } + if (rest - coins[index] * (zhangs[index] + 1) >= 0) { + dp[index][rest] -= dp[index + 1][rest - coins[index] * (zhangs[index] + 1)]; + } + } + } + return dp[0][aim]; + } + + // 为了测试 + public static int[] randomArray(int maxLen, int maxValue) { + int N = (int) (Math.random() * maxLen); + int[] arr = new int[N]; + for (int i = 0; i < N; i++) { + arr[i] = (int) (Math.random() * maxValue) + 1; + } + return arr; + } + + // 为了测试 + public static void printArray(int[] arr) { + for (int i = 0; i < arr.length; i++) { + System.out.print(arr[i] + " "); + } + System.out.println(); + } + + // 为了测试 + public static void main(String[] args) { + int maxLen = 10; + int maxValue = 20; + int testTime = 1000000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int[] arr = randomArray(maxLen, maxValue); + int aim = (int) (Math.random() * maxValue); + int ans1 = coinsWay(arr, aim); + int ans2 = dp1(arr, aim); + int ans3 = dp2(arr, aim); + if (ans1 != ans2 || ans1 != ans3) { + System.out.println("Oops!"); + printArray(arr); + System.out.println(aim); + System.out.println(ans1); + System.out.println(ans2); + System.out.println(ans3); + break; + } + } + System.out.println("测试结束"); + } + +} diff --git a/体系学习班/class21/Code05_BobDie.java b/体系学习班/class21/Code05_BobDie.java new file mode 100644 index 0000000..439fffe --- /dev/null +++ b/体系学习班/class21/Code05_BobDie.java @@ -0,0 +1,58 @@ +package class21; + +public class Code05_BobDie { + + public static double livePosibility1(int row, int col, int k, int N, int M) { + return (double) process(row, col, k, N, M) / Math.pow(4, k); + } + + // 目前在row,col位置,还有rest步要走,走完了如果还在棋盘中就获得1个生存点,返回总的生存点数 + public static long process(int row, int col, int rest, int N, int M) { + if (row < 0 || row == N || col < 0 || col == M) { + return 0; + } + // 还在棋盘中! + if (rest == 0) { + return 1; + } + // 还在棋盘中!还有步数要走 + long up = process(row - 1, col, rest - 1, N, M); + long down = process(row + 1, col, rest - 1, N, M); + long left = process(row, col - 1, rest - 1, N, M); + long right = process(row, col + 1, rest - 1, N, M); + return up + down + left + right; + } + + public static double livePosibility2(int row, int col, int k, int N, int M) { + long[][][] dp = new long[N][M][k + 1]; + for (int i = 0; i < N; i++) { + for (int j = 0; j < M; j++) { + dp[i][j][0] = 1; + } + } + for (int rest = 1; rest <= k; rest++) { + for (int r = 0; r < N; r++) { + for (int c = 0; c < M; c++) { + dp[r][c][rest] = pick(dp, N, M, r - 1, c, rest - 1); + dp[r][c][rest] += pick(dp, N, M, r + 1, c, rest - 1); + dp[r][c][rest] += pick(dp, N, M, r, c - 1, rest - 1); + dp[r][c][rest] += pick(dp, N, M, r, c + 1, rest - 1); + } + } + } + return (double) dp[row][col][k] / Math.pow(4, k); + } + + public static long pick(long[][][] dp, int N, int M, int r, int c, int rest) { + if (r < 0 || r == N || c < 0 || c == M) { + return 0; + } + return dp[r][c][rest]; + } + + public static void main(String[] args) { + System.out.println(livePosibility1(6, 6, 10, 50, 50)); + System.out.println(livePosibility2(6, 6, 10, 50, 50)); + } + +} diff --git a/体系学习班/class22/Code01_KillMonster.java b/体系学习班/class22/Code01_KillMonster.java new file mode 100644 index 0000000..77fae7b --- /dev/null +++ b/体系学习班/class22/Code01_KillMonster.java @@ -0,0 +1,100 @@ +package class22; + +public class Code01_KillMonster { + + public static double right(int N, int M, int K) { + if (N < 1 || M < 1 || K < 1) { + return 0; + } + long all = (long) Math.pow(M + 1, K); + long kill = process(K, M, N); + return (double) ((double) kill / (double) all); + } + + // 怪兽还剩hp点血 + // 每次的伤害在[0~M]范围上 + // 还有times次可以砍 + // 返回砍死的情况数! + public static long process(int times, int M, int hp) { + if (times == 0) { + return hp <= 0 ? 1 : 0; + } + if (hp <= 0) { + return (long) Math.pow(M + 1, times); + } + long ways = 0; + for (int i = 0; i <= M; i++) { + ways += process(times - 1, M, hp - i); + } + return ways; + } + + public static double dp1(int N, int M, int K) { + if (N < 1 || M < 1 || K < 1) { + return 0; + } + long all = (long) Math.pow(M + 1, K); + long[][] dp = new long[K + 1][N + 1]; + dp[0][0] = 1; + for (int times = 1; times <= K; times++) { + dp[times][0] = (long) Math.pow(M + 1, times); + for (int hp = 1; hp <= N; hp++) { + long ways = 0; + for (int i = 0; i <= M; i++) { + if (hp - i >= 0) { + ways += dp[times - 1][hp - i]; + } else { + ways += (long) Math.pow(M + 1, times - 1); + } + } + dp[times][hp] = ways; + } + } + long kill = dp[K][N]; + return (double) ((double) kill / (double) all); + } + + public static double dp2(int N, int M, int K) { + if (N < 1 || M < 1 || K < 1) { + return 0; + } + long all = (long) Math.pow(M + 1, K); + long[][] dp = new long[K + 1][N + 1]; + dp[0][0] = 1; + for (int times = 1; times <= K; times++) { + dp[times][0] = (long) Math.pow(M + 1, times); + for (int hp = 1; hp <= N; hp++) { + dp[times][hp] = dp[times][hp - 1] + dp[times - 1][hp]; + if (hp - 1 - M >= 0) { + dp[times][hp] -= dp[times - 1][hp - 1 - M]; + } else { + dp[times][hp] -= Math.pow(M + 1, times - 1); + } + } + } + long kill = dp[K][N]; + return (double) ((double) kill / (double) all); + } + + public static void main(String[] args) { + int NMax = 10; + int MMax = 10; + int KMax = 10; + int testTime = 200; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int N = (int) (Math.random() * NMax); + int M = (int) (Math.random() * MMax); + int K = (int) (Math.random() * KMax); + double ans1 = right(N, M, K); + double ans2 = dp1(N, M, K); + double ans3 = dp2(N, M, K); + if (ans1 != ans2 || ans1 != ans3) { + System.out.println("Oops!"); + break; + } + } + System.out.println("测试结束"); + } + +} diff --git a/体系学习班/class22/Code02_MinCoinsNoLimit.java b/体系学习班/class22/Code02_MinCoinsNoLimit.java new file mode 100644 index 0000000..06e4843 --- /dev/null +++ b/体系学习班/class22/Code02_MinCoinsNoLimit.java @@ -0,0 +1,121 @@ +package class22; + +public class Code02_MinCoinsNoLimit { + + public static int minCoins(int[] arr, int aim) { + return process(arr, 0, aim); + } + + // arr[index...]面值,每种面值张数自由选择, + // 搞出rest正好这么多钱,返回最小张数 + // 拿Integer.MAX_VALUE标记怎么都搞定不了 + public static int process(int[] arr, int index, int rest) { + if (index == arr.length) { + return rest == 0 ? 0 : Integer.MAX_VALUE; + } else { + int ans = Integer.MAX_VALUE; + for (int zhang = 0; zhang * arr[index] <= rest; zhang++) { + int next = process(arr, index + 1, rest - zhang * arr[index]); + if (next != Integer.MAX_VALUE) { + ans = Math.min(ans, zhang + next); + } + } + return ans; + } + } + + public static int dp1(int[] arr, int aim) { + if (aim == 0) { + return 0; + } + int N = arr.length; + int[][] dp = new int[N + 1][aim + 1]; + dp[N][0] = 0; + for (int j = 1; j <= aim; j++) { + dp[N][j] = Integer.MAX_VALUE; + } + for (int index = N - 1; index >= 0; index--) { + for (int rest = 0; rest <= aim; rest++) { + int ans = Integer.MAX_VALUE; + for (int zhang = 0; zhang * arr[index] <= rest; zhang++) { + int next = dp[index + 1][rest - zhang * arr[index]]; + if (next != Integer.MAX_VALUE) { + ans = Math.min(ans, zhang + next); + } + } + dp[index][rest] = ans; + } + } + return dp[0][aim]; + } + + public static int dp2(int[] arr, int aim) { + if (aim == 0) { + return 0; + } + int N = arr.length; + int[][] dp = new int[N + 1][aim + 1]; + dp[N][0] = 0; + for (int j = 1; j <= aim; j++) { + dp[N][j] = Integer.MAX_VALUE; + } + for (int index = N - 1; index >= 0; index--) { + for (int rest = 0; rest <= aim; rest++) { + dp[index][rest] = dp[index + 1][rest]; + if (rest - arr[index] >= 0 + && dp[index][rest - arr[index]] != Integer.MAX_VALUE) { + dp[index][rest] = Math.min(dp[index][rest], dp[index][rest - arr[index]] + 1); + } + } + } + return dp[0][aim]; + } + + // 为了测试 + public static int[] randomArray(int maxLen, int maxValue) { + int N = (int) (Math.random() * maxLen); + int[] arr = new int[N]; + boolean[] has = new boolean[maxValue + 1]; + for (int i = 0; i < N; i++) { + do { + arr[i] = (int) (Math.random() * maxValue) + 1; + } while (has[arr[i]]); + has[arr[i]] = true; + } + return arr; + } + + // 为了测试 + public static void printArray(int[] arr) { + for (int i = 0; i < arr.length; i++) { + System.out.print(arr[i] + " "); + } + System.out.println(); + } + + // 为了测试 + public static void main(String[] args) { + int maxLen = 20; + int maxValue = 30; + int testTime = 300000; + System.out.println("功能测试开始"); + for (int i = 0; i < testTime; i++) { + int N = (int) (Math.random() * maxLen); + int[] arr = randomArray(N, maxValue); + int aim = (int) (Math.random() * maxValue); + int ans1 = minCoins(arr, aim); + int ans2 = dp1(arr, aim); + int ans3 = dp2(arr, aim); + if (ans1 != ans2 || ans1 != ans3) { + System.out.println("Oops!"); + printArray(arr); + System.out.println(aim); + System.out.println(ans1); + System.out.println(ans2); + break; + } + } + System.out.println("功能测试结束"); + } + +} diff --git a/体系学习班/class22/Code03_SplitNumber.java b/体系学习班/class22/Code03_SplitNumber.java new file mode 100644 index 0000000..23193bc --- /dev/null +++ b/体系学习班/class22/Code03_SplitNumber.java @@ -0,0 +1,85 @@ +package class22; + +public class Code03_SplitNumber { + + // n为正数 + public static int ways(int n) { + if (n < 0) { + return 0; + } + if (n == 1) { + return 1; + } + return process(1, n); + } + + // 上一个拆出来的数是pre + // 还剩rest需要去拆 + // 返回拆解的方法数 + public static int process(int pre, int rest) { + if (rest == 0) { + return 1; + } + if (pre > rest) { + return 0; + } + int ways = 0; + for (int first = pre; first <= rest; first++) { + ways += process(first, rest - first); + } + return ways; + } + + public static int dp1(int n) { + if (n < 0) { + return 0; + } + if (n == 1) { + return 1; + } + int[][] dp = new int[n + 1][n + 1]; + for (int pre = 1; pre <= n; pre++) { + dp[pre][0] = 1; + dp[pre][pre] = 1; + } + for (int pre = n - 1; pre >= 1; pre--) { + for (int rest = pre + 1; rest <= n; rest++) { + int ways = 0; + for (int first = pre; first <= rest; first++) { + ways += dp[first][rest - first]; + } + dp[pre][rest] = ways; + } + } + return dp[1][n]; + } + + public static int dp2(int n) { + if (n < 0) { + return 0; + } + if (n == 1) { + return 1; + } + int[][] dp = new int[n + 1][n + 1]; + for (int pre = 1; pre <= n; pre++) { + dp[pre][0] = 1; + dp[pre][pre] = 1; + } + for (int pre = n - 1; pre >= 1; pre--) { + for (int rest = pre + 1; rest <= n; rest++) { + dp[pre][rest] = dp[pre + 1][rest]; + dp[pre][rest] += dp[pre][rest - pre]; + } + } + return dp[1][n]; + } + + public static void main(String[] args) { + int test = 39; + System.out.println(ways(test)); + System.out.println(dp1(test)); + System.out.println(dp2(test)); + } + +} diff --git a/体系学习班/class23/Code01_SplitSumClosed.java b/体系学习班/class23/Code01_SplitSumClosed.java new file mode 100644 index 0000000..6f82c49 --- /dev/null +++ b/体系学习班/class23/Code01_SplitSumClosed.java @@ -0,0 +1,94 @@ +package class23; + +public class Code01_SplitSumClosed { + + public static int right(int[] arr) { + if (arr == null || arr.length < 2) { + return 0; + } + int sum = 0; + for (int num : arr) { + sum += num; + } + return process(arr, 0, sum / 2); + } + + // arr[i...]可以自由选择,请返回累加和尽量接近rest,但不能超过rest的情况下,最接近的累加和是多少? + public static int process(int[] arr, int i, int rest) { + if (i == arr.length) { + return 0; + } else { // 还有数,arr[i]这个数 + // 可能性1,不使用arr[i] + int p1 = process(arr, i + 1, rest); + // 可能性2,要使用arr[i] + int p2 = 0; + if (arr[i] <= rest) { + p2 = arr[i] + process(arr, i + 1, rest - arr[i]); + } + return Math.max(p1, p2); + } + } + + public static int dp(int[] arr) { + if (arr == null || arr.length < 2) { + return 0; + } + int sum = 0; + for (int num : arr) { + sum += num; + } + sum /= 2; + int N = arr.length; + int[][] dp = new int[N + 1][sum + 1]; + for (int i = N - 1; i >= 0; i--) { + for (int rest = 0; rest <= sum; rest++) { + // 可能性1,不使用arr[i] + int p1 = dp[i + 1][rest]; + // 可能性2,要使用arr[i] + int p2 = 0; + if (arr[i] <= rest) { + p2 = arr[i] + dp[i + 1][rest - arr[i]]; + } + dp[i][rest] = Math.max(p1, p2); + } + } + return dp[0][sum]; + } + + public static int[] randomArray(int len, int value) { + int[] arr = new int[len]; + for (int i = 0; i < arr.length; i++) { + arr[i] = (int) (Math.random() * value); + } + return arr; + } + + public static void printArray(int[] arr) { + for (int num : arr) { + System.out.print(num + " "); + } + System.out.println(); + } + + public static void main(String[] args) { + int maxLen = 20; + int maxValue = 50; + int testTime = 10000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int len = (int) (Math.random() * maxLen); + int[] arr = randomArray(len, maxValue); + int ans1 = right(arr); + int ans2 = dp(arr); + if (ans1 != ans2) { + printArray(arr); + System.out.println(ans1); + System.out.println(ans2); + System.out.println("Oops!"); + break; + } + } + System.out.println("测试结束"); + } + +} diff --git a/体系学习班/class23/Code02_SplitSumClosedSizeHalf.java b/体系学习班/class23/Code02_SplitSumClosedSizeHalf.java new file mode 100644 index 0000000..0da40fb --- /dev/null +++ b/体系学习班/class23/Code02_SplitSumClosedSizeHalf.java @@ -0,0 +1,243 @@ +package class23; + +public class Code02_SplitSumClosedSizeHalf { + + public static int right(int[] arr) { + if (arr == null || arr.length < 2) { + return 0; + } + int sum = 0; + for (int num : arr) { + sum += num; + } + if ((arr.length & 1) == 0) { + return process(arr, 0, arr.length / 2, sum / 2); + } else { + return Math.max(process(arr, 0, arr.length / 2, sum / 2), process(arr, 0, arr.length / 2 + 1, sum / 2)); + } + } + + // arr[i....]自由选择,挑选的个数一定要是picks个,累加和<=rest, 离rest最近的返回 + public static int process(int[] arr, int i, int picks, int rest) { + if (i == arr.length) { + return picks == 0 ? 0 : -1; + } else { + int p1 = process(arr, i + 1, picks, rest); + // 就是要使用arr[i]这个数 + int p2 = -1; + int next = -1; + if (arr[i] <= rest) { + next = process(arr, i + 1, picks - 1, rest - arr[i]); + } + if (next != -1) { + p2 = arr[i] + next; + } + return Math.max(p1, p2); + } + } + + public static int dp(int[] arr) { + if (arr == null || arr.length < 2) { + return 0; + } + int sum = 0; + for (int num : arr) { + sum += num; + } + sum /= 2; + int N = arr.length; + int M = (N + 1) / 2; + int[][][] dp = new int[N + 1][M + 1][sum + 1]; + for (int i = 0; i <= N; i++) { + for (int j = 0; j <= M; j++) { + for (int k = 0; k <= sum; k++) { + dp[i][j][k] = -1; + } + } + } + for (int rest = 0; rest <= sum; rest++) { + dp[N][0][rest] = 0; + } + for (int i = N - 1; i >= 0; i--) { + for (int picks = 0; picks <= M; picks++) { + for (int rest = 0; rest <= sum; rest++) { + int p1 = dp[i + 1][picks][rest]; + // 就是要使用arr[i]这个数 + int p2 = -1; + int next = -1; + if (picks - 1 >= 0 && arr[i] <= rest) { + next = dp[i + 1][picks - 1][rest - arr[i]]; + } + if (next != -1) { + p2 = arr[i] + next; + } + dp[i][picks][rest] = Math.max(p1, p2); + } + } + } + if ((arr.length & 1) == 0) { + return dp[0][arr.length / 2][sum]; + } else { + return Math.max(dp[0][arr.length / 2][sum], dp[0][(arr.length / 2) + 1][sum]); + } + } + +// public static int right(int[] arr) { +// if (arr == null || arr.length < 2) { +// return 0; +// } +// int sum = 0; +// for (int num : arr) { +// sum += num; +// } +// return process(arr, 0, 0, sum >> 1); +// } +// +// public static int process(int[] arr, int i, int picks, int rest) { +// if (i == arr.length) { +// if ((arr.length & 1) == 0) { +// return picks == (arr.length >> 1) ? 0 : -1; +// } else { +// return (picks == (arr.length >> 1) || picks == (arr.length >> 1) + 1) ? 0 : -1; +// } +// } +// int p1 = process(arr, i + 1, picks, rest); +// int p2 = -1; +// int next2 = -1; +// if (arr[i] <= rest) { +// next2 = process(arr, i + 1, picks + 1, rest - arr[i]); +// } +// if (next2 != -1) { +// p2 = arr[i] + next2; +// } +// return Math.max(p1, p2); +// } +// +// public static int dp1(int[] arr) { +// if (arr == null || arr.length < 2) { +// return 0; +// } +// int sum = 0; +// for (int num : arr) { +// sum += num; +// } +// sum >>= 1; +// int N = arr.length; +// int M = (arr.length + 1) >> 1; +// int[][][] dp = new int[N + 1][M + 1][sum + 1]; +// for (int i = 0; i <= N; i++) { +// for (int j = 0; j <= M; j++) { +// for (int k = 0; k <= sum; k++) { +// dp[i][j][k] = -1; +// } +// } +// } +// for (int k = 0; k <= sum; k++) { +// dp[N][M][k] = 0; +// } +// if ((arr.length & 1) != 0) { +// for (int k = 0; k <= sum; k++) { +// dp[N][M - 1][k] = 0; +// } +// } +// for (int i = N - 1; i >= 0; i--) { +// for (int picks = 0; picks <= M; picks++) { +// for (int rest = 0; rest <= sum; rest++) { +// int p1 = dp[i + 1][picks][rest]; +// int p2 = -1; +// int next2 = -1; +// if (picks + 1 <= M && arr[i] <= rest) { +// next2 = dp[i + 1][picks + 1][rest - arr[i]]; +// } +// if (next2 != -1) { +// p2 = arr[i] + next2; +// } +// dp[i][picks][rest] = Math.max(p1, p2); +// } +// } +// } +// return dp[0][0][sum]; +// } + + public static int dp2(int[] arr) { + if (arr == null || arr.length < 2) { + return 0; + } + int sum = 0; + for (int num : arr) { + sum += num; + } + sum >>= 1; + int N = arr.length; + int M = (arr.length + 1) >> 1; + int[][][] dp = new int[N][M + 1][sum + 1]; + for (int i = 0; i < N; i++) { + for (int j = 0; j <= M; j++) { + for (int k = 0; k <= sum; k++) { + dp[i][j][k] = Integer.MIN_VALUE; + } + } + } + for (int i = 0; i < N; i++) { + for (int k = 0; k <= sum; k++) { + dp[i][0][k] = 0; + } + } + for (int k = 0; k <= sum; k++) { + dp[0][1][k] = arr[0] <= k ? arr[0] : Integer.MIN_VALUE; + } + for (int i = 1; i < N; i++) { + for (int j = 1; j <= Math.min(i + 1, M); j++) { + for (int k = 0; k <= sum; k++) { + dp[i][j][k] = dp[i - 1][j][k]; + if (k - arr[i] >= 0) { + dp[i][j][k] = Math.max(dp[i][j][k], dp[i - 1][j - 1][k - arr[i]] + arr[i]); + } + } + } + } + return Math.max(dp[N - 1][M][sum], dp[N - 1][N - M][sum]); + } + + // for test + public static int[] randomArray(int len, int value) { + int[] arr = new int[len]; + for (int i = 0; i < arr.length; i++) { + arr[i] = (int) (Math.random() * value); + } + return arr; + } + + // for test + public static void printArray(int[] arr) { + for (int num : arr) { + System.out.print(num + " "); + } + System.out.println(); + } + + // for test + public static void main(String[] args) { + int maxLen = 10; + int maxValue = 50; + int testTime = 10000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int len = (int) (Math.random() * maxLen); + int[] arr = randomArray(len, maxValue); + int ans1 = right(arr); + int ans2 = dp(arr); + int ans3 = dp2(arr); + if (ans1 != ans2 || ans1 != ans3) { + printArray(arr); + System.out.println(ans1); + System.out.println(ans2); + System.out.println(ans3); + System.out.println("Oops!"); + break; + } + } + System.out.println("测试结束"); + } + +} \ No newline at end of file diff --git a/体系学习班/class23/Code03_NQueens.java b/体系学习班/class23/Code03_NQueens.java new file mode 100644 index 0000000..a9e454a --- /dev/null +++ b/体系学习班/class23/Code03_NQueens.java @@ -0,0 +1,89 @@ +package class23; + +public class Code03_NQueens { + + public static int num1(int n) { + if (n < 1) { + return 0; + } + int[] record = new int[n]; + return process1(0, record, n); + } + + // 当前来到i行,一共是0~N-1行 + // 在i行上放皇后,所有列都尝试 + // 必须要保证跟之前所有的皇后不打架 + // int[] record record[x] = y 之前的第x行的皇后,放在了y列上 + // 返回:不关心i以上发生了什么,i.... 后续有多少合法的方法数 + public static int process1(int i, int[] record, int n) { + if (i == n) { + return 1; + } + int res = 0; + // i行的皇后,放哪一列呢?j列, + for (int j = 0; j < n; j++) { + if (isValid(record, i, j)) { + record[i] = j; + res += process1(i + 1, record, n); + } + } + return res; + } + + public static boolean isValid(int[] record, int i, int j) { + // 0..i-1 + for (int k = 0; k < i; k++) { + if (j == record[k] || Math.abs(record[k] - j) == Math.abs(i - k)) { + return false; + } + } + return true; + } + + // 请不要超过32皇后问题 + public static int num2(int n) { + if (n < 1 || n > 32) { + return 0; + } + // 如果你是13皇后问题,limit 最右13个1,其他都是0 + int limit = n == 32 ? -1 : (1 << n) - 1; + return process2(limit, 0, 0, 0); + } + + // 7皇后问题 + // limit : 0....0 1 1 1 1 1 1 1 + // 之前皇后的列影响:colLim + // 之前皇后的左下对角线影响:leftDiaLim + // 之前皇后的右下对角线影响:rightDiaLim + public static int process2(int limit, int colLim, int leftDiaLim, int rightDiaLim) { + if (colLim == limit) { + return 1; + } + // pos中所有是1的位置,是你可以去尝试皇后的位置 + int pos = limit & (~(colLim | leftDiaLim | rightDiaLim)); + int mostRightOne = 0; + int res = 0; + while (pos != 0) { + mostRightOne = pos & (~pos + 1); + pos = pos - mostRightOne; + res += process2(limit, colLim | mostRightOne, (leftDiaLim | mostRightOne) << 1, + (rightDiaLim | mostRightOne) >>> 1); + } + return res; + } + + public static void main(String[] args) { + int n = 15; + + long start = System.currentTimeMillis(); + System.out.println(num2(n)); + long end = System.currentTimeMillis(); + System.out.println("cost time: " + (end - start) + "ms"); + + start = System.currentTimeMillis(); + System.out.println(num1(n)); + end = System.currentTimeMillis(); + System.out.println("cost time: " + (end - start) + "ms"); + + } +} diff --git a/体系学习班/class24/Code01_SlidingWindowMaxArray.java b/体系学习班/class24/Code01_SlidingWindowMaxArray.java new file mode 100644 index 0000000..d115951 --- /dev/null +++ b/体系学习班/class24/Code01_SlidingWindowMaxArray.java @@ -0,0 +1,99 @@ +package class24; + +import java.util.LinkedList; + +public class Code01_SlidingWindowMaxArray { + + // 暴力的对数器方法 + public static int[] right(int[] arr, int w) { + if (arr == null || w < 1 || arr.length < w) { + return null; + } + int N = arr.length; + int[] res = new int[N - w + 1]; + int index = 0; + int L = 0; + int R = w - 1; + while (R < N) { + int max = arr[L]; + for (int i = L + 1; i <= R; i++) { + max = Math.max(max, arr[i]); + + } + res[index++] = max; + L++; + R++; + } + return res; + } + + public static int[] getMaxWindow(int[] arr, int w) { + if (arr == null || w < 1 || arr.length < w) { + return null; + } + // qmax 窗口最大值的更新结构 + // 放下标 + LinkedList qmax = new LinkedList(); + int[] res = new int[arr.length - w + 1]; + int index = 0; + for (int R = 0; R < arr.length; R++) { + while (!qmax.isEmpty() && arr[qmax.peekLast()] <= arr[R]) { + qmax.pollLast(); + } + qmax.addLast(R); + if (qmax.peekFirst() == R - w) { + qmax.pollFirst(); + } + if (R >= w - 1) { + res[index++] = arr[qmax.peekFirst()]; + } + } + return res; + } + + // for test + public static int[] generateRandomArray(int maxSize, int maxValue) { + int[] arr = new int[(int) ((maxSize + 1) * Math.random())]; + for (int i = 0; i < arr.length; i++) { + arr[i] = (int) (Math.random() * (maxValue + 1)); + } + return arr; + } + + // for test + public static boolean isEqual(int[] arr1, int[] arr2) { + if ((arr1 == null && arr2 != null) || (arr1 != null && arr2 == null)) { + return false; + } + if (arr1 == null && arr2 == null) { + return true; + } + if (arr1.length != arr2.length) { + return false; + } + for (int i = 0; i < arr1.length; i++) { + if (arr1[i] != arr2[i]) { + return false; + } + } + return true; + } + + public static void main(String[] args) { + int testTime = 100000; + int maxSize = 100; + int maxValue = 100; + System.out.println("test begin"); + for (int i = 0; i < testTime; i++) { + int[] arr = generateRandomArray(maxSize, maxValue); + int w = (int) (Math.random() * (arr.length + 1)); + int[] ans1 = getMaxWindow(arr, w); + int[] ans2 = right(arr, w); + if (!isEqual(ans1, ans2)) { + System.out.println("Oops!"); + } + } + System.out.println("test finish"); + } + +} diff --git a/体系学习班/class24/Code02_AllLessNumSubArray.java b/体系学习班/class24/Code02_AllLessNumSubArray.java new file mode 100644 index 0000000..7bbd5de --- /dev/null +++ b/体系学习班/class24/Code02_AllLessNumSubArray.java @@ -0,0 +1,109 @@ +package class24; + +import java.util.LinkedList; + +public class Code02_AllLessNumSubArray { + + // 暴力的对数器方法 + public static int right(int[] arr, int sum) { + if (arr == null || arr.length == 0 || sum < 0) { + return 0; + } + int N = arr.length; + int count = 0; + for (int L = 0; L < N; L++) { + for (int R = L; R < N; R++) { + int max = arr[L]; + int min = arr[L]; + for (int i = L + 1; i <= R; i++) { + max = Math.max(max, arr[i]); + min = Math.min(min, arr[i]); + } + if (max - min <= sum) { + count++; + } + } + } + return count; + } + + public static int num(int[] arr, int sum) { + if (arr == null || arr.length == 0 || sum < 0) { + return 0; + } + int N = arr.length; + int count = 0; + LinkedList maxWindow = new LinkedList<>(); + LinkedList minWindow = new LinkedList<>(); + int R = 0; + for (int L = 0; L < N; L++) { + while (R < N) { + while (!maxWindow.isEmpty() && arr[maxWindow.peekLast()] <= arr[R]) { + maxWindow.pollLast(); + } + maxWindow.addLast(R); + while (!minWindow.isEmpty() && arr[minWindow.peekLast()] >= arr[R]) { + minWindow.pollLast(); + } + minWindow.addLast(R); + if (arr[maxWindow.peekFirst()] - arr[minWindow.peekFirst()] > sum) { + break; + } else { + R++; + } + } + count += R - L; + if (maxWindow.peekFirst() == L) { + maxWindow.pollFirst(); + } + if (minWindow.peekFirst() == L) { + minWindow.pollFirst(); + } + } + return count; + } + + // for test + public static int[] generateRandomArray(int maxLen, int maxValue) { + int len = (int) (Math.random() * (maxLen + 1)); + int[] arr = new int[len]; + for (int i = 0; i < len; i++) { + arr[i] = (int) (Math.random() * (maxValue + 1)) - (int) (Math.random() * (maxValue + 1)); + } + return arr; + } + + // for test + public static void printArray(int[] arr) { + if (arr != null) { + for (int i = 0; i < arr.length; i++) { + System.out.print(arr[i] + " "); + } + System.out.println(); + } + } + + public static void main(String[] args) { + int maxLen = 100; + int maxValue = 200; + int testTime = 100000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int[] arr = generateRandomArray(maxLen, maxValue); + int sum = (int) (Math.random() * (maxValue + 1)); + int ans1 = right(arr, sum); + int ans2 = num(arr, sum); + if (ans1 != ans2) { + System.out.println("Oops!"); + printArray(arr); + System.out.println(sum); + System.out.println(ans1); + System.out.println(ans2); + break; + } + } + System.out.println("测试结束"); + + } + +} diff --git a/体系学习班/class24/Code03_GasStation.java b/体系学习班/class24/Code03_GasStation.java new file mode 100644 index 0000000..b80906f --- /dev/null +++ b/体系学习班/class24/Code03_GasStation.java @@ -0,0 +1,53 @@ +package class24; + +import java.util.LinkedList; + +// 测试链接:https://leetcode.com/problems/gas-station +public class Code03_GasStation { + + // 这个方法的时间复杂度O(N),额外空间复杂度O(N) + public static int canCompleteCircuit(int[] gas, int[] cost) { + boolean[] good = goodArray(gas, cost); + for (int i = 0; i < gas.length; i++) { + if (good[i]) { + return i; + } + } + return -1; + } + + public static boolean[] goodArray(int[] g, int[] c) { + int N = g.length; + int M = N << 1; + int[] arr = new int[M]; + for (int i = 0; i < N; i++) { + arr[i] = g[i] - c[i]; + arr[i + N] = g[i] - c[i]; + } + for (int i = 1; i < M; i++) { + arr[i] += arr[i - 1]; + } + LinkedList w = new LinkedList<>(); + for (int i = 0; i < N; i++) { + while (!w.isEmpty() && arr[w.peekLast()] >= arr[i]) { + w.pollLast(); + } + w.addLast(i); + } + boolean[] ans = new boolean[N]; + for (int offset = 0, i = 0, j = N; j < M; offset = arr[i++], j++) { + if (arr[w.peekFirst()] - offset >= 0) { + ans[i] = true; + } + if (w.peekFirst() == i) { + w.pollFirst(); + } + while (!w.isEmpty() && arr[w.peekLast()] >= arr[j]) { + w.pollLast(); + } + w.addLast(j); + } + return ans; + } + +} diff --git a/体系学习班/class24/Code04_MinCoinsOnePaper.java b/体系学习班/class24/Code04_MinCoinsOnePaper.java new file mode 100644 index 0000000..cfda95a --- /dev/null +++ b/体系学习班/class24/Code04_MinCoinsOnePaper.java @@ -0,0 +1,250 @@ +package class24; + +import java.util.HashMap; +import java.util.Map.Entry; +import java.util.LinkedList; + +public class Code04_MinCoinsOnePaper { + + public static int minCoins(int[] arr, int aim) { + return process(arr, 0, aim); + } + + public static int process(int[] arr, int index, int rest) { + if (rest < 0) { + return Integer.MAX_VALUE; + } + if (index == arr.length) { + return rest == 0 ? 0 : Integer.MAX_VALUE; + } else { + int p1 = process(arr, index + 1, rest); + int p2 = process(arr, index + 1, rest - arr[index]); + if (p2 != Integer.MAX_VALUE) { + p2++; + } + return Math.min(p1, p2); + } + } + + // dp1时间复杂度为:O(arr长度 * aim) + public static int dp1(int[] arr, int aim) { + if (aim == 0) { + return 0; + } + int N = arr.length; + int[][] dp = new int[N + 1][aim + 1]; + dp[N][0] = 0; + for (int j = 1; j <= aim; j++) { + dp[N][j] = Integer.MAX_VALUE; + } + for (int index = N - 1; index >= 0; index--) { + for (int rest = 0; rest <= aim; rest++) { + int p1 = dp[index + 1][rest]; + int p2 = rest - arr[index] >= 0 ? dp[index + 1][rest - arr[index]] : Integer.MAX_VALUE; + if (p2 != Integer.MAX_VALUE) { + p2++; + } + dp[index][rest] = Math.min(p1, p2); + } + } + return dp[0][aim]; + } + + public static class Info { + public int[] coins; + public int[] zhangs; + + public Info(int[] c, int[] z) { + coins = c; + zhangs = z; + } + } + + public static Info getInfo(int[] arr) { + HashMap counts = new HashMap<>(); + for (int value : arr) { + if (!counts.containsKey(value)) { + counts.put(value, 1); + } else { + counts.put(value, counts.get(value) + 1); + } + } + int N = counts.size(); + int[] coins = new int[N]; + int[] zhangs = new int[N]; + int index = 0; + for (Entry entry : counts.entrySet()) { + coins[index] = entry.getKey(); + zhangs[index++] = entry.getValue(); + } + return new Info(coins, zhangs); + } + + // dp2时间复杂度为:O(arr长度) + O(货币种数 * aim * 每种货币的平均张数) + public static int dp2(int[] arr, int aim) { + if (aim == 0) { + return 0; + } + // 得到info时间复杂度O(arr长度) + Info info = getInfo(arr); + int[] coins = info.coins; + int[] zhangs = info.zhangs; + int N = coins.length; + int[][] dp = new int[N + 1][aim + 1]; + dp[N][0] = 0; + for (int j = 1; j <= aim; j++) { + dp[N][j] = Integer.MAX_VALUE; + } + // 这三层for循环,时间复杂度为O(货币种数 * aim * 每种货币的平均张数) + for (int index = N - 1; index >= 0; index--) { + for (int rest = 0; rest <= aim; rest++) { + dp[index][rest] = dp[index + 1][rest]; + for (int zhang = 1; zhang * coins[index] <= aim && zhang <= zhangs[index]; zhang++) { + if (rest - zhang * coins[index] >= 0 + && dp[index + 1][rest - zhang * coins[index]] != Integer.MAX_VALUE) { + dp[index][rest] = Math.min(dp[index][rest], zhang + dp[index + 1][rest - zhang * coins[index]]); + } + } + } + } + return dp[0][aim]; + } + + // dp3时间复杂度为:O(arr长度) + O(货币种数 * aim) + // 优化需要用到窗口内最小值的更新结构 + public static int dp3(int[] arr, int aim) { + if (aim == 0) { + return 0; + } + // 得到info时间复杂度O(arr长度) + Info info = getInfo(arr); + int[] c = info.coins; + int[] z = info.zhangs; + int N = c.length; + int[][] dp = new int[N + 1][aim + 1]; + dp[N][0] = 0; + for (int j = 1; j <= aim; j++) { + dp[N][j] = Integer.MAX_VALUE; + } + // 虽然是嵌套了很多循环,但是时间复杂度为O(货币种数 * aim) + // 因为用了窗口内最小值的更新结构 + for (int i = N - 1; i >= 0; i--) { + for (int mod = 0; mod < Math.min(aim + 1, c[i]); mod++) { + // 当前面值 X + // mod mod + x mod + 2*x mod + 3 * x + LinkedList w = new LinkedList<>(); + w.add(mod); + dp[i][mod] = dp[i + 1][mod]; + for (int r = mod + c[i]; r <= aim; r += c[i]) { + while (!w.isEmpty() && (dp[i + 1][w.peekLast()] == Integer.MAX_VALUE + || dp[i + 1][w.peekLast()] + compensate(w.peekLast(), r, c[i]) >= dp[i + 1][r])) { + w.pollLast(); + } + w.addLast(r); + int overdue = r - c[i] * (z[i] + 1); + if (w.peekFirst() == overdue) { + w.pollFirst(); + } + dp[i][r] = dp[i + 1][w.peekFirst()] + compensate(w.peekFirst(), r, c[i]); + } + } + } + return dp[0][aim]; + } + + public static int compensate(int pre, int cur, int coin) { + return (cur - pre) / coin; + } + + // 为了测试 + public static int[] randomArray(int N, int maxValue) { + int[] arr = new int[N]; + for (int i = 0; i < N; i++) { + arr[i] = (int) (Math.random() * maxValue) + 1; + } + return arr; + } + + // 为了测试 + public static void printArray(int[] arr) { + for (int i = 0; i < arr.length; i++) { + System.out.print(arr[i] + " "); + } + System.out.println(); + } + + // 为了测试 + public static void main(String[] args) { + int maxLen = 20; + int maxValue = 30; + int testTime = 300000; + System.out.println("功能测试开始"); + for (int i = 0; i < testTime; i++) { + int N = (int) (Math.random() * maxLen); + int[] arr = randomArray(N, maxValue); + int aim = (int) (Math.random() * maxValue); + int ans1 = minCoins(arr, aim); + int ans2 = dp1(arr, aim); + int ans3 = dp2(arr, aim); + int ans4 = dp3(arr, aim); + if (ans1 != ans2 || ans3 != ans4 || ans1 != ans3) { + System.out.println("Oops!"); + printArray(arr); + System.out.println(aim); + System.out.println(ans1); + System.out.println(ans2); + System.out.println(ans3); + System.out.println(ans4); + break; + } + } + System.out.println("功能测试结束"); + + System.out.println("=========="); + + int aim = 0; + int[] arr = null; + long start; + long end; + int ans2; + int ans3; + + System.out.println("性能测试开始"); + maxLen = 30000; + maxValue = 20; + aim = 60000; + arr = randomArray(maxLen, maxValue); + + start = System.currentTimeMillis(); + ans2 = dp2(arr, aim); + end = System.currentTimeMillis(); + System.out.println("dp2答案 : " + ans2 + ", dp2运行时间 : " + (end - start) + " ms"); + + start = System.currentTimeMillis(); + ans3 = dp3(arr, aim); + end = System.currentTimeMillis(); + System.out.println("dp3答案 : " + ans3 + ", dp3运行时间 : " + (end - start) + " ms"); + System.out.println("性能测试结束"); + + System.out.println("==========="); + + System.out.println("货币大量重复出现情况下,"); + System.out.println("大数据量测试dp3开始"); + maxLen = 20000000; + aim = 10000; + maxValue = 10000; + arr = randomArray(maxLen, maxValue); + start = System.currentTimeMillis(); + ans3 = dp3(arr, aim); + end = System.currentTimeMillis(); + System.out.println("dp3运行时间 : " + (end - start) + " ms"); + System.out.println("大数据量测试dp3结束"); + + System.out.println("==========="); + + System.out.println("当货币很少出现重复,dp2比dp3有常数时间优势"); + System.out.println("当货币大量出现重复,dp3时间复杂度明显优于dp2"); + System.out.println("dp3的优化用到了窗口内最小值的更新结构"); + } + +} diff --git a/体系学习班/class25/Code01_MonotonousStack.java b/体系学习班/class25/Code01_MonotonousStack.java new file mode 100644 index 0000000..b823a30 --- /dev/null +++ b/体系学习班/class25/Code01_MonotonousStack.java @@ -0,0 +1,165 @@ +package class25; + +import java.util.List; +import java.util.ArrayList; +import java.util.Stack; + +public class Code01_MonotonousStack { + + // arr = [ 3, 1, 2, 3] + // 0 1 2 3 + // [ + // 0 : [-1, 1] + // 1 : [-1, -1] + // 2 : [ 1, -1] + // 3 : [ 2, -1] + // ] + public static int[][] getNearLessNoRepeat(int[] arr) { + int[][] res = new int[arr.length][2]; + // 只存位置! + Stack stack = new Stack<>(); + for (int i = 0; i < arr.length; i++) { // 当遍历到i位置的数,arr[i] + while (!stack.isEmpty() && arr[stack.peek()] > arr[i]) { + int j = stack.pop(); + int leftLessIndex = stack.isEmpty() ? -1 : stack.peek(); + res[j][0] = leftLessIndex; + res[j][1] = i; + } + stack.push(i); + } + while (!stack.isEmpty()) { + int j = stack.pop(); + int leftLessIndex = stack.isEmpty() ? -1 : stack.peek(); + res[j][0] = leftLessIndex; + res[j][1] = -1; + } + return res; + } + + public static int[][] getNearLess(int[] arr) { + int[][] res = new int[arr.length][2]; + Stack> stack = new Stack<>(); + for (int i = 0; i < arr.length; i++) { // i -> arr[i] 进栈 + while (!stack.isEmpty() && arr[stack.peek().get(0)] > arr[i]) { + List popIs = stack.pop(); + int leftLessIndex = stack.isEmpty() ? -1 : stack.peek().get(stack.peek().size() - 1); + for (Integer popi : popIs) { + res[popi][0] = leftLessIndex; + res[popi][1] = i; + } + } + if (!stack.isEmpty() && arr[stack.peek().get(0)] == arr[i]) { + stack.peek().add(Integer.valueOf(i)); + } else { + ArrayList list = new ArrayList<>(); + list.add(i); + stack.push(list); + } + } + while (!stack.isEmpty()) { + List popIs = stack.pop(); + int leftLessIndex = stack.isEmpty() ? -1 : stack.peek().get(stack.peek().size() - 1); + for (Integer popi : popIs) { + res[popi][0] = leftLessIndex; + res[popi][1] = -1; + } + } + return res; + } + + // for test + public static int[] getRandomArrayNoRepeat(int size) { + int[] arr = new int[(int) (Math.random() * size) + 1]; + for (int i = 0; i < arr.length; i++) { + arr[i] = i; + } + for (int i = 0; i < arr.length; i++) { + int swapIndex = (int) (Math.random() * arr.length); + int tmp = arr[swapIndex]; + arr[swapIndex] = arr[i]; + arr[i] = tmp; + } + return arr; + } + + // for test + public static int[] getRandomArray(int size, int max) { + int[] arr = new int[(int) (Math.random() * size) + 1]; + for (int i = 0; i < arr.length; i++) { + arr[i] = (int) (Math.random() * max) - (int) (Math.random() * max); + } + return arr; + } + + // for test + public static int[][] rightWay(int[] arr) { + int[][] res = new int[arr.length][2]; + for (int i = 0; i < arr.length; i++) { + int leftLessIndex = -1; + int rightLessIndex = -1; + int cur = i - 1; + while (cur >= 0) { + if (arr[cur] < arr[i]) { + leftLessIndex = cur; + break; + } + cur--; + } + cur = i + 1; + while (cur < arr.length) { + if (arr[cur] < arr[i]) { + rightLessIndex = cur; + break; + } + cur++; + } + res[i][0] = leftLessIndex; + res[i][1] = rightLessIndex; + } + return res; + } + + // for test + public static boolean isEqual(int[][] res1, int[][] res2) { + if (res1.length != res2.length) { + return false; + } + for (int i = 0; i < res1.length; i++) { + if (res1[i][0] != res2[i][0] || res1[i][1] != res2[i][1]) { + return false; + } + } + + return true; + } + + // for test + public static void printArray(int[] arr) { + for (int i = 0; i < arr.length; i++) { + System.out.print(arr[i] + " "); + } + System.out.println(); + } + + public static void main(String[] args) { + int size = 10; + int max = 20; + int testTimes = 2000000; + System.out.println("测试开始"); + for (int i = 0; i < testTimes; i++) { + int[] arr1 = getRandomArrayNoRepeat(size); + int[] arr2 = getRandomArray(size, max); + if (!isEqual(getNearLessNoRepeat(arr1), rightWay(arr1))) { + System.out.println("Oops!"); + printArray(arr1); + break; + } + if (!isEqual(getNearLess(arr2), rightWay(arr2))) { + System.out.println("Oops!"); + printArray(arr2); + break; + } + } + System.out.println("测试结束"); + } +} diff --git a/体系学习班/class25/Code01_MonotonousStackForNowcoder.java b/体系学习班/class25/Code01_MonotonousStackForNowcoder.java new file mode 100644 index 0000000..501b65b --- /dev/null +++ b/体系学习班/class25/Code01_MonotonousStackForNowcoder.java @@ -0,0 +1,71 @@ +package class25; + +// 测试链接 : https://www.nowcoder.com/practice/2a2c00e7a88a498693568cef63a4b7bb +// 如果在牛客上做题,可以用如下的方式来做 +// 提交如下的代码,并把主类名改成"Main" +// 可以直接通过 +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; + +public class Code01_MonotonousStackForNowcoder { + + public static int[] arr = new int[1000000]; + public static int[][] ans = new int[1000000][2]; + // stack1 : 相等值的位置也放 + // stack2 : 只放不相等值的最后一个位置 + // 比如 : arr = { 3, 3, 3, 4, 4, 6, 6, 6} + // 位置 0 1 2 3 4 5 6 7 + // 如果位置依次压栈, + // stack1中的记录是(位置) : 0 1 2 3 4 5 6 7 + // stack1中的记录是(位置) : 2 4 7 + public static int[] stack1 = new int[1000000]; + public static int[] stack2 = new int[1000000]; + + public static void main(String[] args) throws IOException { + BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); + int n = Integer.parseInt(br.readLine()); + String[] strs = br.readLine().split(" "); + for (int i = 0; i < n; i++) { + arr[i] = Integer.parseInt(strs[i]); + } + getNearLess(n); + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < n; i++) { + builder.append(ans[i][0] + " " + ans[i][1] + "\n"); + } + System.out.println(builder.toString()); + } + + public static void getNearLess(int n) { + int stackSize1 = 0; + int stackSize2 = 0; + for (int i = 0; i < n; i++) { + while (stackSize1 > 0 && arr[stack1[stackSize1 - 1]] > arr[i]) { + int curIndex = stack1[--stackSize1]; + int left = stackSize2 < 2 ? -1 : stack2[stackSize2 - 2]; + ans[curIndex][0] = left; + ans[curIndex][1] = i; + if (stackSize1 == 0 || arr[stack1[stackSize1 - 1]] != arr[curIndex]) { + stackSize2--; + } + } + if (stackSize1 != 0 && arr[stack1[stackSize1 - 1]] == arr[i]) { + stack2[stackSize2 - 1] = i; + } else { + stack2[stackSize2++] = i; + } + stack1[stackSize1++] = i; + } + while (stackSize1 != 0) { + int curIndex = stack1[--stackSize1]; + int left = stackSize2 < 2 ? -1 : stack2[stackSize2 - 2]; + ans[curIndex][0] = left; + ans[curIndex][1] = -1; + if (stackSize1 == 0 || arr[stack1[stackSize1 - 1]] != arr[curIndex]) { + stackSize2--; + } + } + } + +} diff --git a/体系学习班/class25/Code02_AllTimesMinToMax.java b/体系学习班/class25/Code02_AllTimesMinToMax.java new file mode 100644 index 0000000..2f26ac1 --- /dev/null +++ b/体系学习班/class25/Code02_AllTimesMinToMax.java @@ -0,0 +1,98 @@ +package class25; + +import java.util.Stack; + +public class Code02_AllTimesMinToMax { + + public static int max1(int[] arr) { + int max = Integer.MIN_VALUE; + for (int i = 0; i < arr.length; i++) { + for (int j = i; j < arr.length; j++) { + int minNum = Integer.MAX_VALUE; + int sum = 0; + for (int k = i; k <= j; k++) { + sum += arr[k]; + minNum = Math.min(minNum, arr[k]); + } + max = Math.max(max, minNum * sum); + } + } + return max; + } + + public static int max2(int[] arr) { + int size = arr.length; + int[] sums = new int[size]; + sums[0] = arr[0]; + for (int i = 1; i < size; i++) { + sums[i] = sums[i - 1] + arr[i]; + } + int max = Integer.MIN_VALUE; + Stack stack = new Stack(); + for (int i = 0; i < size; i++) { + while (!stack.isEmpty() && arr[stack.peek()] >= arr[i]) { + int j = stack.pop(); + max = Math.max(max, (stack.isEmpty() ? sums[i - 1] : (sums[i - 1] - sums[stack.peek()])) * arr[j]); + } + stack.push(i); + } + while (!stack.isEmpty()) { + int j = stack.pop(); + max = Math.max(max, (stack.isEmpty() ? sums[size - 1] : (sums[size - 1] - sums[stack.peek()])) * arr[j]); + } + return max; + } + + public static int[] gerenareRondomArray() { + int[] arr = new int[(int) (Math.random() * 20) + 10]; + for (int i = 0; i < arr.length; i++) { + arr[i] = (int) (Math.random() * 101); + } + return arr; + } + + public static void main(String[] args) { + int testTimes = 2000000; + System.out.println("test begin"); + for (int i = 0; i < testTimes; i++) { + int[] arr = gerenareRondomArray(); + if (max1(arr) != max2(arr)) { + System.out.println("FUCK!"); + break; + } + } + System.out.println("test finish"); + } + + // 本题可以在leetcode上找到原题 + // 测试链接 : https://leetcode.com/problems/maximum-subarray-min-product/ + // 注意测试题目数量大,要取模,但是思路和课上讲的是完全一样的 + // 注意溢出的处理即可,也就是用long类型来表示累加和 + // 还有优化就是,你可以用自己手写的数组栈,来替代系统实现的栈,也会快很多 + public static int maxSumMinProduct(int[] arr) { + int size = arr.length; + long[] sums = new long[size]; + sums[0] = arr[0]; + for (int i = 1; i < size; i++) { + sums[i] = sums[i - 1] + arr[i]; + } + long max = Long.MIN_VALUE; + int[] stack = new int[size]; + int stackSize = 0; + for (int i = 0; i < size; i++) { + while (stackSize != 0 && arr[stack[stackSize - 1]] >= arr[i]) { + int j = stack[--stackSize]; + max = Math.max(max, + (stackSize == 0 ? sums[i - 1] : (sums[i - 1] - sums[stack[stackSize - 1]])) * arr[j]); + } + stack[stackSize++] = i; + } + while (stackSize != 0) { + int j = stack[--stackSize]; + max = Math.max(max, + (stackSize == 0 ? sums[size - 1] : (sums[size - 1] - sums[stack[stackSize - 1]])) * arr[j]); + } + return (int) (max % 1000000007); + } + +} diff --git a/体系学习班/class25/Code03_LargestRectangleInHistogram.java b/体系学习班/class25/Code03_LargestRectangleInHistogram.java new file mode 100644 index 0000000..d2d1ce5 --- /dev/null +++ b/体系学习班/class25/Code03_LargestRectangleInHistogram.java @@ -0,0 +1,58 @@ +package class25; + +import java.util.Stack; + +// 测试链接:https://leetcode.com/problems/largest-rectangle-in-histogram +public class Code03_LargestRectangleInHistogram { + + public static int largestRectangleArea1(int[] height) { + if (height == null || height.length == 0) { + return 0; + } + int maxArea = 0; + Stack stack = new Stack(); + for (int i = 0; i < height.length; i++) { + while (!stack.isEmpty() && height[i] <= height[stack.peek()]) { + int j = stack.pop(); + int k = stack.isEmpty() ? -1 : stack.peek(); + int curArea = (i - k - 1) * height[j]; + maxArea = Math.max(maxArea, curArea); + } + stack.push(i); + } + while (!stack.isEmpty()) { + int j = stack.pop(); + int k = stack.isEmpty() ? -1 : stack.peek(); + int curArea = (height.length - k - 1) * height[j]; + maxArea = Math.max(maxArea, curArea); + } + return maxArea; + } + + public static int largestRectangleArea2(int[] height) { + if (height == null || height.length == 0) { + return 0; + } + int N = height.length; + int[] stack = new int[N]; + int si = -1; + int maxArea = 0; + for (int i = 0; i < height.length; i++) { + while (si != -1 && height[i] <= height[stack[si]]) { + int j = stack[si--]; + int k = si == -1 ? -1 : stack[si]; + int curArea = (i - k - 1) * height[j]; + maxArea = Math.max(maxArea, curArea); + } + stack[++si] = i; + } + while (si != -1) { + int j = stack[si--]; + int k = si == -1 ? -1 : stack[si]; + int curArea = (height.length - k - 1) * height[j]; + maxArea = Math.max(maxArea, curArea); + } + return maxArea; + } + +} diff --git a/体系学习班/class25/Code04_MaximalRectangle.java b/体系学习班/class25/Code04_MaximalRectangle.java new file mode 100644 index 0000000..e2b9d37 --- /dev/null +++ b/体系学习班/class25/Code04_MaximalRectangle.java @@ -0,0 +1,48 @@ +package class25; + +import java.util.Stack; + +// 测试链接:https://leetcode.com/problems/maximal-rectangle/ +public class Code04_MaximalRectangle { + + public static int maximalRectangle(char[][] map) { + if (map == null || map.length == 0 || map[0].length == 0) { + return 0; + } + int maxArea = 0; + int[] height = new int[map[0].length]; + for (int i = 0; i < map.length; i++) { + for (int j = 0; j < map[0].length; j++) { + height[j] = map[i][j] == '0' ? 0 : height[j] + 1; + } + maxArea = Math.max(maxRecFromBottom(height), maxArea); + } + return maxArea; + } + + // height是正方图数组 + public static int maxRecFromBottom(int[] height) { + if (height == null || height.length == 0) { + return 0; + } + int maxArea = 0; + Stack stack = new Stack(); + for (int i = 0; i < height.length; i++) { + while (!stack.isEmpty() && height[i] <= height[stack.peek()]) { + int j = stack.pop(); + int k = stack.isEmpty() ? -1 : stack.peek(); + int curArea = (i - k - 1) * height[j]; + maxArea = Math.max(maxArea, curArea); + } + stack.push(i); + } + while (!stack.isEmpty()) { + int j = stack.pop(); + int k = stack.isEmpty() ? -1 : stack.peek(); + int curArea = (height.length - k - 1) * height[j]; + maxArea = Math.max(maxArea, curArea); + } + return maxArea; + } + +} diff --git a/体系学习班/class25/Code05_CountSubmatricesWithAllOnes.java b/体系学习班/class25/Code05_CountSubmatricesWithAllOnes.java new file mode 100644 index 0000000..f24d02e --- /dev/null +++ b/体系学习班/class25/Code05_CountSubmatricesWithAllOnes.java @@ -0,0 +1,81 @@ +package class25; + +// 测试链接:https://leetcode.com/problems/count-submatrices-with-all-ones +public class Code05_CountSubmatricesWithAllOnes { + + public static int numSubmat(int[][] mat) { + if (mat == null || mat.length == 0 || mat[0].length == 0) { + return 0; + } + int nums = 0; + int[] height = new int[mat[0].length]; + for (int i = 0; i < mat.length; i++) { + for (int j = 0; j < mat[0].length; j++) { + height[j] = mat[i][j] == 0 ? 0 : height[j] + 1; + } + nums += countFromBottom(height); + } + return nums; + + } + + // 比如 + // 1 + // 1 + // 1 1 + // 1 1 1 + // 1 1 1 + // 1 1 1 + // + // 2 .... 6 .... 9 + // 如上图,假设在6位置,1的高度为6 + // 在6位置的左边,离6位置最近、且小于高度6的位置是2,2位置的高度是3 + // 在6位置的右边,离6位置最近、且小于高度6的位置是9,9位置的高度是4 + // 此时我们求什么? + // 1) 求在3~8范围上,必须以高度6作为高的矩形,有几个? + // 2) 求在3~8范围上,必须以高度5作为高的矩形,有几个? + // 也就是说,<=4的高度,一律不求 + // 那么,1) 求必须以位置6的高度6作为高的矩形,有几个? + // 3..3 3..4 3..5 3..6 3..7 3..8 + // 4..4 4..5 4..6 4..7 4..8 + // 5..5 5..6 5..7 5..8 + // 6..6 6..7 6..8 + // 7..7 7..8 + // 8..8 + // 这么多!= 21 = (9 - 2 - 1) * (9 - 2) / 2 + // 这就是任何一个数字从栈里弹出的时候,计算矩形数量的方式 + public static int countFromBottom(int[] height) { + if (height == null || height.length == 0) { + return 0; + } + int nums = 0; + int[] stack = new int[height.length]; + int si = -1; + for (int i = 0; i < height.length; i++) { + while (si != -1 && height[stack[si]] >= height[i]) { + int cur = stack[si--]; + if (height[cur] > height[i]) { + int left = si == -1 ? -1 : stack[si]; + int n = i - left - 1; + int down = Math.max(left == -1 ? 0 : height[left], height[i]); + nums += (height[cur] - down) * num(n); + } + + } + stack[++si] = i; + } + while (si != -1) { + int cur = stack[si--]; + int left = si == -1 ? -1 : stack[si]; + int n = height.length - left - 1; + int down = left == -1 ? 0 : height[left]; + nums += (height[cur] - down) * num(n); + } + return nums; + } + + public static int num(int n) { + return ((n * (1 + n)) >> 1); + } + +} diff --git a/体系学习班/class26/Code01_SumOfSubarrayMinimums.java b/体系学习班/class26/Code01_SumOfSubarrayMinimums.java new file mode 100644 index 0000000..343247b --- /dev/null +++ b/体系学习班/class26/Code01_SumOfSubarrayMinimums.java @@ -0,0 +1,156 @@ +package class26; + +// 测试链接:https://leetcode.com/problems/sum-of-subarray-minimums/ +// subArrayMinSum1是暴力解 +// subArrayMinSum2是最优解的思路 +// sumSubarrayMins是最优解思路下的单调栈优化 +// Leetcode上不要提交subArrayMinSum1、subArrayMinSum2方法,因为没有考虑取摸 +// Leetcode上只提交sumSubarrayMins方法,时间复杂度O(N),可以直接通过 +public class Code01_SumOfSubarrayMinimums { + + public static int subArrayMinSum1(int[] arr) { + int ans = 0; + for (int i = 0; i < arr.length; i++) { + for (int j = i; j < arr.length; j++) { + int min = arr[i]; + for (int k = i + 1; k <= j; k++) { + min = Math.min(min, arr[k]); + } + ans += min; + } + } + return ans; + } + + // 没有用单调栈 + public static int subArrayMinSum2(int[] arr) { + // left[i] = x : arr[i]左边,离arr[i]最近,<=arr[i],位置在x + int[] left = leftNearLessEqual2(arr); + // right[i] = y : arr[i]右边,离arr[i]最近,< arr[i],的数,位置在y + int[] right = rightNearLess2(arr); + int ans = 0; + for (int i = 0; i < arr.length; i++) { + int start = i - left[i]; + int end = right[i] - i; + ans += start * end * arr[i]; + } + return ans; + } + + public static int[] leftNearLessEqual2(int[] arr) { + int N = arr.length; + int[] left = new int[N]; + for (int i = 0; i < N; i++) { + int ans = -1; + for (int j = i - 1; j >= 0; j--) { + if (arr[j] <= arr[i]) { + ans = j; + break; + } + } + left[i] = ans; + } + return left; + } + + public static int[] rightNearLess2(int[] arr) { + int N = arr.length; + int[] right = new int[N]; + for (int i = 0; i < N; i++) { + int ans = N; + for (int j = i + 1; j < N; j++) { + if (arr[i] > arr[j]) { + ans = j; + break; + } + } + right[i] = ans; + } + return right; + } + + public static int sumSubarrayMins(int[] arr) { + int[] stack = new int[arr.length]; + int[] left = nearLessEqualLeft(arr, stack); + int[] right = nearLessRight(arr, stack); + long ans = 0; + for (int i = 0; i < arr.length; i++) { + long start = i - left[i]; + long end = right[i] - i; + ans += start * end * (long) arr[i]; + ans %= 1000000007; + } + return (int) ans; + } + + public static int[] nearLessEqualLeft(int[] arr, int[] stack) { + int N = arr.length; + int[] left = new int[N]; + int size = 0; + for (int i = N - 1; i >= 0; i--) { + while (size != 0 && arr[i] <= arr[stack[size - 1]]) { + left[stack[--size]] = i; + } + stack[size++] = i; + } + while (size != 0) { + left[stack[--size]] = -1; + } + return left; + } + + public static int[] nearLessRight(int[] arr, int[] stack) { + int N = arr.length; + int[] right = new int[N]; + int size = 0; + for (int i = 0; i < N; i++) { + while (size != 0 && arr[stack[size - 1]] > arr[i]) { + right[stack[--size]] = i; + } + stack[size++] = i; + } + while (size != 0) { + right[stack[--size]] = N; + } + return right; + } + + public static int[] randomArray(int len, int maxValue) { + int[] ans = new int[len]; + for (int i = 0; i < len; i++) { + ans[i] = (int) (Math.random() * maxValue) + 1; + } + return ans; + } + + public static void printArray(int[] arr) { + for (int i = 0; i < arr.length; i++) { + System.out.print(arr[i] + " "); + } + System.out.println(); + } + + public static void main(String[] args) { + int maxLen = 100; + int maxValue = 50; + int testTime = 100000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int len = (int) (Math.random() * maxLen); + int[] arr = randomArray(len, maxValue); + int ans1 = subArrayMinSum1(arr); + int ans2 = subArrayMinSum2(arr); + int ans3 = sumSubarrayMins(arr); + if (ans1 != ans2 || ans1 != ans3) { + printArray(arr); + System.out.println(ans1); + System.out.println(ans2); + System.out.println(ans3); + System.out.println("出错了!"); + break; + } + } + System.out.println("测试结束"); + } + +} diff --git a/体系学习班/class26/Code02_FibonacciProblem.java b/体系学习班/class26/Code02_FibonacciProblem.java new file mode 100644 index 0000000..7f50420 --- /dev/null +++ b/体系学习班/class26/Code02_FibonacciProblem.java @@ -0,0 +1,189 @@ +package class26; + +public class Code02_FibonacciProblem { + + public static int f1(int n) { + if (n < 1) { + return 0; + } + if (n == 1 || n == 2) { + return 1; + } + return f1(n - 1) + f1(n - 2); + } + + public static int f2(int n) { + if (n < 1) { + return 0; + } + if (n == 1 || n == 2) { + return 1; + } + int res = 1; + int pre = 1; + int tmp = 0; + for (int i = 3; i <= n; i++) { + tmp = res; + res = res + pre; + pre = tmp; + } + return res; + } + + // O(logN) + public static int f3(int n) { + if (n < 1) { + return 0; + } + if (n == 1 || n == 2) { + return 1; + } + // [ 1 ,1 ] + // [ 1, 0 ] + int[][] base = { + { 1, 1 }, + { 1, 0 } + }; + int[][] res = matrixPower(base, n - 2); + return res[0][0] + res[1][0]; + } + + public static int[][] matrixPower(int[][] m, int p) { + int[][] res = new int[m.length][m[0].length]; + for (int i = 0; i < res.length; i++) { + res[i][i] = 1; + } + // res = 矩阵中的1 + int[][] t = m;// 矩阵1次方 + for (; p != 0; p >>= 1) { + if ((p & 1) != 0) { + res = product(res, t); + } + t = product(t, t); + } + return res; + } + + // 两个矩阵乘完之后的结果返回 + public static int[][] product(int[][] a, int[][] b) { + int n = a.length; + int m = b[0].length; + int k = a[0].length; // a的列数同时也是b的行数 + int[][] ans = new int[n][m]; + for(int i = 0 ; i < n; i++) { + for(int j = 0 ; j < m;j++) { + for(int c = 0; c < k; c++) { + ans[i][j] += a[i][c] * b[c][j]; + } + } + } + return ans; + } + + public static int s1(int n) { + if (n < 1) { + return 0; + } + if (n == 1 || n == 2) { + return n; + } + return s1(n - 1) + s1(n - 2); + } + + public static int s2(int n) { + if (n < 1) { + return 0; + } + if (n == 1 || n == 2) { + return n; + } + int res = 2; + int pre = 1; + int tmp = 0; + for (int i = 3; i <= n; i++) { + tmp = res; + res = res + pre; + pre = tmp; + } + return res; + } + + public static int s3(int n) { + if (n < 1) { + return 0; + } + if (n == 1 || n == 2) { + return n; + } + int[][] base = { { 1, 1 }, { 1, 0 } }; + int[][] res = matrixPower(base, n - 2); + return 2 * res[0][0] + res[1][0]; + } + + public static int c1(int n) { + if (n < 1) { + return 0; + } + if (n == 1 || n == 2 || n == 3) { + return n; + } + return c1(n - 1) + c1(n - 3); + } + + public static int c2(int n) { + if (n < 1) { + return 0; + } + if (n == 1 || n == 2 || n == 3) { + return n; + } + int res = 3; + int pre = 2; + int prepre = 1; + int tmp1 = 0; + int tmp2 = 0; + for (int i = 4; i <= n; i++) { + tmp1 = res; + tmp2 = pre; + res = res + prepre; + pre = tmp1; + prepre = tmp2; + } + return res; + } + + public static int c3(int n) { + if (n < 1) { + return 0; + } + if (n == 1 || n == 2 || n == 3) { + return n; + } + int[][] base = { + { 1, 1, 0 }, + { 0, 0, 1 }, + { 1, 0, 0 } }; + int[][] res = matrixPower(base, n - 3); + return 3 * res[0][0] + 2 * res[1][0] + res[2][0]; + } + + public static void main(String[] args) { + int n = 19; + System.out.println(f1(n)); + System.out.println(f2(n)); + System.out.println(f3(n)); + System.out.println("==="); + + System.out.println(s1(n)); + System.out.println(s2(n)); + System.out.println(s3(n)); + System.out.println("==="); + + System.out.println(c1(n)); + System.out.println(c2(n)); + System.out.println(c3(n)); + System.out.println("==="); + + } + +} diff --git a/体系学习班/class26/Code03_ZeroLeftOneStringNumber.java b/体系学习班/class26/Code03_ZeroLeftOneStringNumber.java new file mode 100644 index 0000000..8cc3ce2 --- /dev/null +++ b/体系学习班/class26/Code03_ZeroLeftOneStringNumber.java @@ -0,0 +1,113 @@ +package class26; + +public class Code03_ZeroLeftOneStringNumber { + + public static int getNum1(int n) { + if (n < 1) { + return 0; + } + return process(1, n); + } + + public static int process(int i, int n) { + if (i == n - 1) { + return 2; + } + if (i == n) { + return 1; + } + return process(i + 1, n) + process(i + 2, n); + } + + public static int getNum2(int n) { + if (n < 1) { + return 0; + } + if (n == 1) { + return 1; + } + int pre = 1; + int cur = 1; + int tmp = 0; + for (int i = 2; i < n + 1; i++) { + tmp = cur; + cur += pre; + pre = tmp; + } + return cur; + } + + public static int getNum3(int n) { + if (n < 1) { + return 0; + } + if (n == 1 || n == 2) { + return n; + } + int[][] base = { { 1, 1 }, { 1, 0 } }; + int[][] res = matrixPower(base, n - 2); + return 2 * res[0][0] + res[1][0]; + } + + + + + + + public static int fi(int n) { + if (n < 1) { + return 0; + } + if (n == 1 || n == 2) { + return 1; + } + int[][] base = { { 1, 1 }, + { 1, 0 } }; + int[][] res = matrixPower(base, n - 2); + return res[0][0] + res[1][0]; + } + + + + + public static int[][] matrixPower(int[][] m, int p) { + int[][] res = new int[m.length][m[0].length]; + for (int i = 0; i < res.length; i++) { + res[i][i] = 1; + } + int[][] tmp = m; + for (; p != 0; p >>= 1) { + if ((p & 1) != 0) { + res = product(res, tmp); + } + tmp = product(tmp, tmp); + } + return res; + } + + // 两个矩阵乘完之后的结果返回 + public static int[][] product(int[][] a, int[][] b) { + int n = a.length; + int m = b[0].length; + int k = a[0].length; // a的列数同时也是b的行数 + int[][] ans = new int[n][m]; + for(int i = 0 ; i < n; i++) { + for(int j = 0 ; j < m;j++) { + for(int c = 0; c < k; c++) { + ans[i][j] += a[i][c] * b[c][j]; + } + } + } + return ans; + } + + public static void main(String[] args) { + for (int i = 0; i != 20; i++) { + System.out.println(getNum1(i)); + System.out.println(getNum2(i)); + System.out.println(getNum3(i)); + System.out.println("==================="); + } + + } +} diff --git a/体系学习班/class27/Code01_KMP.java b/体系学习班/class27/Code01_KMP.java new file mode 100644 index 0000000..68a51de --- /dev/null +++ b/体系学习班/class27/Code01_KMP.java @@ -0,0 +1,75 @@ +package class27; + +public class Code01_KMP { + + public static int getIndexOf(String s1, String s2) { + if (s1 == null || s2 == null || s2.length() < 1 || s1.length() < s2.length()) { + return -1; + } + char[] str1 = s1.toCharArray(); + char[] str2 = s2.toCharArray(); + int x = 0; + int y = 0; + // O(M) m <= n + int[] next = getNextArray(str2); + // O(N) + while (x < str1.length && y < str2.length) { + if (str1[x] == str2[y]) { + x++; + y++; + } else if (next[y] == -1) { // y == 0 + x++; + } else { + y = next[y]; + } + } + return y == str2.length ? x - y : -1; + } + + public static int[] getNextArray(char[] str2) { + if (str2.length == 1) { + return new int[] { -1 }; + } + int[] next = new int[str2.length]; + next[0] = -1; + next[1] = 0; + int i = 2; // 目前在哪个位置上求next数组的值 + int cn = 0; // 当前是哪个位置的值再和i-1位置的字符比较 + while (i < next.length) { + if (str2[i - 1] == str2[cn]) { // 配成功的时候 + next[i++] = ++cn; + } else if (cn > 0) { + cn = next[cn]; + } else { + next[i++] = 0; + } + } + return next; + } + + // for test + public static String getRandomString(int possibilities, int size) { + char[] ans = new char[(int) (Math.random() * size) + 1]; + for (int i = 0; i < ans.length; i++) { + ans[i] = (char) ((int) (Math.random() * possibilities) + 'a'); + } + return String.valueOf(ans); + } + + public static void main(String[] args) { + int possibilities = 5; + int strSize = 20; + int matchSize = 5; + int testTimes = 5000000; + System.out.println("test begin"); + for (int i = 0; i < testTimes; i++) { + String str = getRandomString(possibilities, strSize); + String match = getRandomString(possibilities, matchSize); + if (getIndexOf(str, match) != str.indexOf(match)) { + System.out.println("Oops!"); + } + } + System.out.println("test finish"); + } + +} diff --git a/体系学习班/class27/Code02_TreeEqual.java b/体系学习班/class27/Code02_TreeEqual.java new file mode 100644 index 0000000..657373a --- /dev/null +++ b/体系学习班/class27/Code02_TreeEqual.java @@ -0,0 +1,172 @@ +package class27; + +import java.util.ArrayList; + +public class Code02_TreeEqual { + + public static class Node { + public int value; + public Node left; + public Node right; + + public Node(int v) { + value = v; + } + } + + public static boolean containsTree1(Node big, Node small) { + if (small == null) { + return true; + } + if (big == null) { + return false; + } + if (isSameValueStructure(big, small)) { + return true; + } + return containsTree1(big.left, small) || containsTree1(big.right, small); + } + + public static boolean isSameValueStructure(Node head1, Node head2) { + if (head1 == null && head2 != null) { + return false; + } + if (head1 != null && head2 == null) { + return false; + } + if (head1 == null && head2 == null) { + return true; + } + if (head1.value != head2.value) { + return false; + } + return isSameValueStructure(head1.left, head2.left) + && isSameValueStructure(head1.right, head2.right); + } + + public static boolean containsTree2(Node big, Node small) { + if (small == null) { + return true; + } + if (big == null) { + return false; + } + ArrayList b = preSerial(big); + ArrayList s = preSerial(small); + String[] str = new String[b.size()]; + for (int i = 0; i < str.length; i++) { + str[i] = b.get(i); + } + + String[] match = new String[s.size()]; + for (int i = 0; i < match.length; i++) { + match[i] = s.get(i); + } + return getIndexOf(str, match) != -1; + } + + public static ArrayList preSerial(Node head) { + ArrayList ans = new ArrayList<>(); + pres(head, ans); + return ans; + } + + public static void pres(Node head, ArrayList ans) { + if (head == null) { + ans.add(null); + } else { + ans.add(String.valueOf(head.value)); + pres(head.left, ans); + pres(head.right, ans); + } + } + + public static int getIndexOf(String[] str1, String[] str2) { + if (str1 == null || str2 == null || str1.length < 1 || str1.length < str2.length) { + return -1; + } + int x = 0; + int y = 0; + int[] next = getNextArray(str2); + while (x < str1.length && y < str2.length) { + if (isEqual(str1[x], str2[y])) { + x++; + y++; + } else if (next[y] == -1) { + x++; + } else { + y = next[y]; + } + } + return y == str2.length ? x - y : -1; + } + + public static int[] getNextArray(String[] ms) { + if (ms.length == 1) { + return new int[] { -1 }; + } + int[] next = new int[ms.length]; + next[0] = -1; + next[1] = 0; + int i = 2; + int cn = 0; + while (i < next.length) { + if (isEqual(ms[i - 1], ms[cn])) { + next[i++] = ++cn; + } else if (cn > 0) { + cn = next[cn]; + } else { + next[i++] = 0; + } + } + return next; + } + + public static boolean isEqual(String a, String b) { + if (a == null && b == null) { + return true; + } else { + if (a == null || b == null) { + return false; + } else { + return a.equals(b); + } + } + } + + // for test + public static Node generateRandomBST(int maxLevel, int maxValue) { + return generate(1, maxLevel, maxValue); + } + + // for test + public static Node generate(int level, int maxLevel, int maxValue) { + if (level > maxLevel || Math.random() < 0.5) { + return null; + } + Node head = new Node((int) (Math.random() * maxValue)); + head.left = generate(level + 1, maxLevel, maxValue); + head.right = generate(level + 1, maxLevel, maxValue); + return head; + } + + public static void main(String[] args) { + int bigTreeLevel = 7; + int smallTreeLevel = 4; + int nodeMaxValue = 5; + int testTimes = 100000; + System.out.println("test begin"); + for (int i = 0; i < testTimes; i++) { + Node big = generateRandomBST(bigTreeLevel, nodeMaxValue); + Node small = generateRandomBST(smallTreeLevel, nodeMaxValue); + boolean ans1 = containsTree1(big, small); + boolean ans2 = containsTree2(big, small); + if (ans1 != ans2) { + System.out.println("Oops!"); + } + } + System.out.println("test finish!"); + + } + +} diff --git a/体系学习班/class27/Code03_IsRotation.java b/体系学习班/class27/Code03_IsRotation.java new file mode 100644 index 0000000..605cb81 --- /dev/null +++ b/体系学习班/class27/Code03_IsRotation.java @@ -0,0 +1,64 @@ +package class27; + +public class Code03_IsRotation { + + public static boolean isRotation(String a, String b) { + if (a == null || b == null || a.length() != b.length()) { + return false; + } + String b2 = b + b; + return getIndexOf(b2, a) != -1; + } + + // KMP Algorithm + public static int getIndexOf(String s, String m) { + if (s.length() < m.length()) { + return -1; + } + char[] ss = s.toCharArray(); + char[] ms = m.toCharArray(); + int si = 0; + int mi = 0; + int[] next = getNextArray(ms); + while (si < ss.length && mi < ms.length) { + if (ss[si] == ms[mi]) { + si++; + mi++; + } else if (next[mi] == -1) { + si++; + } else { + mi = next[mi]; + } + } + return mi == ms.length ? si - mi : -1; + } + + public static int[] getNextArray(char[] ms) { + if (ms.length == 1) { + return new int[] { -1 }; + } + int[] next = new int[ms.length]; + next[0] = -1; + next[1] = 0; + int pos = 2; + int cn = 0; + while (pos < next.length) { + if (ms[pos - 1] == ms[cn]) { + next[pos++] = ++cn; + } else if (cn > 0) { + cn = next[cn]; + } else { + next[pos++] = 0; + } + } + return next; + } + + public static void main(String[] args) { + String str1 = "yunzuocheng"; + String str2 = "zuochengyun"; + System.out.println(isRotation(str1, str2)); + + } + +} diff --git a/体系学习班/class28/Code01_Manacher.java b/体系学习班/class28/Code01_Manacher.java new file mode 100644 index 0000000..c75975f --- /dev/null +++ b/体系学习班/class28/Code01_Manacher.java @@ -0,0 +1,90 @@ +package class28; + +public class Code01_Manacher { + + public static int manacher(String s) { + if (s == null || s.length() == 0) { + return 0; + } + // "12132" -> "#1#2#1#3#2#" + char[] str = manacherString(s); + // 回文半径的大小 + int[] pArr = new int[str.length]; + int C = -1; + // 讲述中:R代表最右的扩成功的位置 + // coding:最右的扩成功位置的,再下一个位置 + int R = -1; + int max = Integer.MIN_VALUE; + for (int i = 0; i < str.length; i++) { // 0 1 2 + // R第一个违规的位置,i>= R + // i位置扩出来的答案,i位置扩的区域,至少是多大。 + pArr[i] = R > i ? Math.min(pArr[2 * C - i], R - i) : 1; + while (i + pArr[i] < str.length && i - pArr[i] > -1) { + if (str[i + pArr[i]] == str[i - pArr[i]]) + pArr[i]++; + else { + break; + } + } + if (i + pArr[i] > R) { + R = i + pArr[i]; + C = i; + } + max = Math.max(max, pArr[i]); + } + return max - 1; + } + + public static char[] manacherString(String str) { + char[] charArr = str.toCharArray(); + char[] res = new char[str.length() * 2 + 1]; + int index = 0; + for (int i = 0; i != res.length; i++) { + res[i] = (i & 1) == 0 ? '#' : charArr[index++]; + } + return res; + } + + // for test + public static int right(String s) { + if (s == null || s.length() == 0) { + return 0; + } + char[] str = manacherString(s); + int max = 0; + for (int i = 0; i < str.length; i++) { + int L = i - 1; + int R = i + 1; + while (L >= 0 && R < str.length && str[L] == str[R]) { + L--; + R++; + } + max = Math.max(max, R - L - 1); + } + return max / 2; + } + + // for test + public static String getRandomString(int possibilities, int size) { + char[] ans = new char[(int) (Math.random() * size) + 1]; + for (int i = 0; i < ans.length; i++) { + ans[i] = (char) ((int) (Math.random() * possibilities) + 'a'); + } + return String.valueOf(ans); + } + + public static void main(String[] args) { + int possibilities = 5; + int strSize = 20; + int testTimes = 5000000; + System.out.println("test begin"); + for (int i = 0; i < testTimes; i++) { + String str = getRandomString(possibilities, strSize); + if (manacher(str) != right(str)) { + System.out.println("Oops!"); + } + } + System.out.println("test finish"); + } + +} diff --git a/体系学习班/class28/Code02_AddShortestEnd.java b/体系学习班/class28/Code02_AddShortestEnd.java new file mode 100644 index 0000000..00d961c --- /dev/null +++ b/体系学习班/class28/Code02_AddShortestEnd.java @@ -0,0 +1,54 @@ +package class28; + +public class Code02_AddShortestEnd { + + public static String shortestEnd(String s) { + if (s == null || s.length() == 0) { + return null; + } + char[] str = manacherString(s); + int[] pArr = new int[str.length]; + int C = -1; + int R = -1; + int maxContainsEnd = -1; + for (int i = 0; i != str.length; i++) { + pArr[i] = R > i ? Math.min(pArr[2 * C - i], R - i) : 1; + while (i + pArr[i] < str.length && i - pArr[i] > -1) { + if (str[i + pArr[i]] == str[i - pArr[i]]) + pArr[i]++; + else { + break; + } + } + if (i + pArr[i] > R) { + R = i + pArr[i]; + C = i; + } + if (R == str.length) { + maxContainsEnd = pArr[i]; + break; + } + } + char[] res = new char[s.length() - maxContainsEnd + 1]; + for (int i = 0; i < res.length; i++) { + res[res.length - 1 - i] = str[i * 2 + 1]; + } + return String.valueOf(res); + } + + public static char[] manacherString(String str) { + char[] charArr = str.toCharArray(); + char[] res = new char[str.length() * 2 + 1]; + int index = 0; + for (int i = 0; i != res.length; i++) { + res[i] = (i & 1) == 0 ? '#' : charArr[index++]; + } + return res; + } + + public static void main(String[] args) { + String str1 = "abcd123321"; + System.out.println(shortestEnd(str1)); + } + +} diff --git a/体系学习班/class29/Code01_FindMinKth.java b/体系学习班/class29/Code01_FindMinKth.java new file mode 100644 index 0000000..e317550 --- /dev/null +++ b/体系学习班/class29/Code01_FindMinKth.java @@ -0,0 +1,175 @@ +package class29; + +import java.util.Comparator; +import java.util.PriorityQueue; + +public class Code01_FindMinKth { + + public static class MaxHeapComparator implements Comparator { + + @Override + public int compare(Integer o1, Integer o2) { + return o2 - o1; + } + + } + + // 利用大根堆,时间复杂度O(N*logK) + public static int minKth1(int[] arr, int k) { + PriorityQueue maxHeap = new PriorityQueue<>(new MaxHeapComparator()); + for (int i = 0; i < k; i++) { + maxHeap.add(arr[i]); + } + for (int i = k; i < arr.length; i++) { + if (arr[i] < maxHeap.peek()) { + maxHeap.poll(); + maxHeap.add(arr[i]); + } + } + return maxHeap.peek(); + } + + // 改写快排,时间复杂度O(N) + // k >= 1 + public static int minKth2(int[] array, int k) { + int[] arr = copyArray(array); + return process2(arr, 0, arr.length - 1, k - 1); + } + + public static int[] copyArray(int[] arr) { + int[] ans = new int[arr.length]; + for (int i = 0; i != ans.length; i++) { + ans[i] = arr[i]; + } + return ans; + } + + // arr 第k小的数 + // process2(arr, 0, N-1, k-1) + // arr[L..R] 范围上,如果排序的话(不是真的去排序),找位于index的数 + // index [L..R] + public static int process2(int[] arr, int L, int R, int index) { + if (L == R) { // L = =R ==INDEX + return arr[L]; + } + // 不止一个数 L + [0, R -L] + int pivot = arr[L + (int) (Math.random() * (R - L + 1))]; + int[] range = partition(arr, L, R, pivot); + if (index >= range[0] && index <= range[1]) { + return arr[index]; + } else if (index < range[0]) { + return process2(arr, L, range[0] - 1, index); + } else { + return process2(arr, range[1] + 1, R, index); + } + } + + public static int[] partition(int[] arr, int L, int R, int pivot) { + int less = L - 1; + int more = R + 1; + int cur = L; + while (cur < more) { + if (arr[cur] < pivot) { + swap(arr, ++less, cur++); + } else if (arr[cur] > pivot) { + swap(arr, cur, --more); + } else { + cur++; + } + } + return new int[] { less + 1, more - 1 }; + } + + public static void swap(int[] arr, int i1, int i2) { + int tmp = arr[i1]; + arr[i1] = arr[i2]; + arr[i2] = tmp; + } + + // 利用bfprt算法,时间复杂度O(N) + public static int minKth3(int[] array, int k) { + int[] arr = copyArray(array); + return bfprt(arr, 0, arr.length - 1, k - 1); + } + + // arr[L..R] 如果排序的话,位于index位置的数,是什么,返回 + public static int bfprt(int[] arr, int L, int R, int index) { + if (L == R) { + return arr[L]; + } + // L...R 每五个数一组 + // 每一个小组内部排好序 + // 小组的中位数组成新数组 + // 这个新数组的中位数返回 + int pivot = medianOfMedians(arr, L, R); + int[] range = partition(arr, L, R, pivot); + if (index >= range[0] && index <= range[1]) { + return arr[index]; + } else if (index < range[0]) { + return bfprt(arr, L, range[0] - 1, index); + } else { + return bfprt(arr, range[1] + 1, R, index); + } + } + + // arr[L...R] 五个数一组 + // 每个小组内部排序 + // 每个小组中位数领出来,组成marr + // marr中的中位数,返回 + public static int medianOfMedians(int[] arr, int L, int R) { + int size = R - L + 1; + int offset = size % 5 == 0 ? 0 : 1; + int[] mArr = new int[size / 5 + offset]; + for (int team = 0; team < mArr.length; team++) { + int teamFirst = L + team * 5; + // L ... L + 4 + // L +5 ... L +9 + // L +10....L+14 + mArr[team] = getMedian(arr, teamFirst, Math.min(R, teamFirst + 4)); + } + // marr中,找到中位数 + // marr(0, marr.len - 1, mArr.length / 2 ) + return bfprt(mArr, 0, mArr.length - 1, mArr.length / 2); + } + + public static int getMedian(int[] arr, int L, int R) { + insertionSort(arr, L, R); + return arr[(L + R) / 2]; + } + + public static void insertionSort(int[] arr, int L, int R) { + for (int i = L + 1; i <= R; i++) { + for (int j = i - 1; j >= L && arr[j] > arr[j + 1]; j--) { + swap(arr, j, j + 1); + } + } + } + + // for test + public static int[] generateRandomArray(int maxSize, int maxValue) { + int[] arr = new int[(int) (Math.random() * maxSize) + 1]; + for (int i = 0; i < arr.length; i++) { + arr[i] = (int) (Math.random() * (maxValue + 1)); + } + return arr; + } + + public static void main(String[] args) { + int testTime = 1000000; + int maxSize = 100; + int maxValue = 100; + System.out.println("test begin"); + for (int i = 0; i < testTime; i++) { + int[] arr = generateRandomArray(maxSize, maxValue); + int k = (int) (Math.random() * arr.length) + 1; + int ans1 = minKth1(arr, k); + int ans2 = minKth2(arr, k); + int ans3 = minKth3(arr, k); + if (ans1 != ans2 || ans2 != ans3) { + System.out.println("Oops!"); + } + } + System.out.println("test finish"); + } + +} diff --git a/体系学习班/class29/Code02_MaxTopK.java b/体系学习班/class29/Code02_MaxTopK.java new file mode 100644 index 0000000..b300171 --- /dev/null +++ b/体系学习班/class29/Code02_MaxTopK.java @@ -0,0 +1,223 @@ +package class29; + +import java.util.Arrays; + +public class Code02_MaxTopK { + + // 时间复杂度O(N*logN) + // 排序+收集 + public static int[] maxTopK1(int[] arr, int k) { + if (arr == null || arr.length == 0) { + return new int[0]; + } + int N = arr.length; + k = Math.min(N, k); + Arrays.sort(arr); + int[] ans = new int[k]; + for (int i = N - 1, j = 0; j < k; i--, j++) { + ans[j] = arr[i]; + } + return ans; + } + + // 方法二,时间复杂度O(N + K*logN) + // 解释:堆 + public static int[] maxTopK2(int[] arr, int k) { + if (arr == null || arr.length == 0) { + return new int[0]; + } + int N = arr.length; + k = Math.min(N, k); + // 从底向上建堆,时间复杂度O(N) + for (int i = N - 1; i >= 0; i--) { + heapify(arr, i, N); + } + // 只把前K个数放在arr末尾,然后收集,O(K*logN) + int heapSize = N; + swap(arr, 0, --heapSize); + int count = 1; + while (heapSize > 0 && count < k) { + heapify(arr, 0, heapSize); + swap(arr, 0, --heapSize); + count++; + } + int[] ans = new int[k]; + for (int i = N - 1, j = 0; j < k; i--, j++) { + ans[j] = arr[i]; + } + return ans; + } + + public static void heapInsert(int[] arr, int index) { + while (arr[index] > arr[(index - 1) / 2]) { + swap(arr, index, (index - 1) / 2); + index = (index - 1) / 2; + } + } + + public static void heapify(int[] arr, int index, int heapSize) { + int left = index * 2 + 1; + while (left < heapSize) { + int largest = left + 1 < heapSize && arr[left + 1] > arr[left] ? left + 1 : left; + largest = arr[largest] > arr[index] ? largest : index; + if (largest == index) { + break; + } + swap(arr, largest, index); + index = largest; + left = index * 2 + 1; + } + } + + public static void swap(int[] arr, int i, int j) { + int tmp = arr[i]; + arr[i] = arr[j]; + arr[j] = tmp; + } + + // 方法三,时间复杂度O(n + k * logk) + public static int[] maxTopK3(int[] arr, int k) { + if (arr == null || arr.length == 0) { + return new int[0]; + } + int N = arr.length; + k = Math.min(N, k); + // O(N) + int num = minKth(arr, N - k); + int[] ans = new int[k]; + int index = 0; + for (int i = 0; i < N; i++) { + if (arr[i] > num) { + ans[index++] = arr[i]; + } + } + for (; index < k; index++) { + ans[index] = num; + } + // O(k*logk) + Arrays.sort(ans); + for (int L = 0, R = k - 1; L < R; L++, R--) { + swap(ans, L, R); + } + return ans; + } + + // 时间复杂度O(N) + public static int minKth(int[] arr, int index) { + int L = 0; + int R = arr.length - 1; + int pivot = 0; + int[] range = null; + while (L < R) { + pivot = arr[L + (int) (Math.random() * (R - L + 1))]; + range = partition(arr, L, R, pivot); + if (index < range[0]) { + R = range[0] - 1; + } else if (index > range[1]) { + L = range[1] + 1; + } else { + return pivot; + } + } + return arr[L]; + } + + public static int[] partition(int[] arr, int L, int R, int pivot) { + int less = L - 1; + int more = R + 1; + int cur = L; + while (cur < more) { + if (arr[cur] < pivot) { + swap(arr, ++less, cur++); + } else if (arr[cur] > pivot) { + swap(arr, cur, --more); + } else { + cur++; + } + } + return new int[] { less + 1, more - 1 }; + } + + // for test + public static int[] generateRandomArray(int maxSize, int maxValue) { + int[] arr = new int[(int) ((maxSize + 1) * Math.random())]; + for (int i = 0; i < arr.length; i++) { + // [-? , +?] + arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random()); + } + return arr; + } + + // for test + public static int[] copyArray(int[] arr) { + if (arr == null) { + return null; + } + int[] res = new int[arr.length]; + for (int i = 0; i < arr.length; i++) { + res[i] = arr[i]; + } + return res; + } + + // for test + public static boolean isEqual(int[] arr1, int[] arr2) { + if ((arr1 == null && arr2 != null) || (arr1 != null && arr2 == null)) { + return false; + } + if (arr1 == null && arr2 == null) { + return true; + } + if (arr1.length != arr2.length) { + return false; + } + for (int i = 0; i < arr1.length; i++) { + if (arr1[i] != arr2[i]) { + return false; + } + } + return true; + } + + // for test + public static void printArray(int[] arr) { + if (arr == null) { + return; + } + for (int i = 0; i < arr.length; i++) { + System.out.print(arr[i] + " "); + } + System.out.println(); + } + + // 生成随机数组测试 + public static void main(String[] args) { + int testTime = 500000; + int maxSize = 100; + int maxValue = 100; + boolean pass = true; + System.out.println("测试开始,没有打印出错信息说明测试通过"); + for (int i = 0; i < testTime; i++) { + int k = (int) (Math.random() * maxSize) + 1; + int[] arr = generateRandomArray(maxSize, maxValue); + + int[] arr1 = copyArray(arr); + int[] arr2 = copyArray(arr); + int[] arr3 = copyArray(arr); + + int[] ans1 = maxTopK1(arr1, k); + int[] ans2 = maxTopK2(arr2, k); + int[] ans3 = maxTopK3(arr3, k); + if (!isEqual(ans1, ans2) || !isEqual(ans1, ans3)) { + pass = false; + System.out.println("出错了!"); + printArray(ans1); + printArray(ans2); + printArray(ans3); + break; + } + } + System.out.println("测试结束了,测试了" + testTime + "组,是否所有测试用例都通过?" + (pass ? "是" : "否")); + } + +} diff --git a/体系学习班/class29/Code03_ReservoirSampling.java b/体系学习班/class29/Code03_ReservoirSampling.java new file mode 100644 index 0000000..c2ce9e3 --- /dev/null +++ b/体系学习班/class29/Code03_ReservoirSampling.java @@ -0,0 +1,94 @@ +package class29; + +public class Code03_ReservoirSampling { + + public static class RandomBox { + private int[] bag; + private int N; + private int count; + + public RandomBox(int capacity) { + bag = new int[capacity]; + N = capacity; + count = 0; + } + + private int rand(int max) { + return (int) (Math.random() * max) + 1; + } + + public void add(int num) { + count++; + if (count <= N) { + bag[count - 1] = num; + } else { + if (rand(count) <= N) { + bag[rand(N) - 1] = num; + } + } + } + + public int[] choices() { + int[] ans = new int[N]; + for (int i = 0; i < N; i++) { + ans[i] = bag[i]; + } + return ans; + } + + } + + // 请等概率返回1~i中的一个数字 + public static int random(int i) { + return (int) (Math.random() * i) + 1; + } + + public static void main(String[] args) { + System.out.println("hello"); + int test = 10000; + int ballNum = 17; + int[] count = new int[ballNum + 1]; + for (int i = 0; i < test; i++) { + int[] bag = new int[10]; + int bagi = 0; + for (int num = 1; num <= ballNum; num++) { + if (num <= 10) { + bag[bagi++] = num; + } else { // num > 10 + if (random(num) <= 10) { // 一定要把num球入袋子 + bagi = (int) (Math.random() * 10); + bag[bagi] = num; + } + } + + } + for (int num : bag) { + count[num]++; + } + } + for (int i = 0; i <= ballNum; i++) { + System.out.println(count[i]); + } + + System.out.println("hello"); + int all = 100; + int choose = 10; + int testTimes = 50000; + int[] counts = new int[all + 1]; + for (int i = 0; i < testTimes; i++) { + RandomBox box = new RandomBox(choose); + for (int num = 1; num <= all; num++) { + box.add(num); + } + int[] ans = box.choices(); + for (int j = 0; j < ans.length; j++) { + counts[ans[j]]++; + } + } + + for (int i = 0; i < counts.length; i++) { + System.out.println(i + " times : " + counts[i]); + } + + } +} diff --git a/体系学习班/class30/Code01_MorrisTraversal.java b/体系学习班/class30/Code01_MorrisTraversal.java new file mode 100644 index 0000000..2e3a4a4 --- /dev/null +++ b/体系学习班/class30/Code01_MorrisTraversal.java @@ -0,0 +1,230 @@ +package class30; + +public class Code01_MorrisTraversal { + + public static class Node { + public int value; + Node left; + Node right; + + public Node(int data) { + this.value = data; + } + } + + public static void process(Node root) { + if (root == null) { + return; + } + // 1 + process(root.left); + // 2 + process(root.right); + // 3 + } + + public static void morris(Node head) { + if (head == null) { + return; + } + Node cur = head; + Node mostRight = null; + while (cur != null) { + mostRight = cur.left; + if (mostRight != null) { + while (mostRight.right != null && mostRight.right != cur) { + mostRight = mostRight.right; + } + if (mostRight.right == null) { + mostRight.right = cur; + cur = cur.left; + continue; + } else { + mostRight.right = null; + } + } + cur = cur.right; + } + } + + public static void morrisPre(Node head) { + if (head == null) { + return; + } + Node cur = head; + Node mostRight = null; + while (cur != null) { + mostRight = cur.left; + if (mostRight != null) { + while (mostRight.right != null && mostRight.right != cur) { + mostRight = mostRight.right; + } + if (mostRight.right == null) { + System.out.print(cur.value + " "); + mostRight.right = cur; + cur = cur.left; + continue; + } else { + mostRight.right = null; + } + } else { + System.out.print(cur.value + " "); + } + cur = cur.right; + } + System.out.println(); + } + + public static void morrisIn(Node head) { + if (head == null) { + return; + } + Node cur = head; + Node mostRight = null; + while (cur != null) { + mostRight = cur.left; + if (mostRight != null) { + while (mostRight.right != null && mostRight.right != cur) { + mostRight = mostRight.right; + } + if (mostRight.right == null) { + mostRight.right = cur; + cur = cur.left; + continue; + } else { + mostRight.right = null; + } + } + System.out.print(cur.value + " "); + cur = cur.right; + } + System.out.println(); + } + + public static void morrisPos(Node head) { + if (head == null) { + return; + } + Node cur = head; + Node mostRight = null; + while (cur != null) { + mostRight = cur.left; + if (mostRight != null) { + while (mostRight.right != null && mostRight.right != cur) { + mostRight = mostRight.right; + } + if (mostRight.right == null) { + mostRight.right = cur; + cur = cur.left; + continue; + } else { + mostRight.right = null; + printEdge(cur.left); + } + } + cur = cur.right; + } + printEdge(head); + System.out.println(); + } + + public static void printEdge(Node head) { + Node tail = reverseEdge(head); + Node cur = tail; + while (cur != null) { + System.out.print(cur.value + " "); + cur = cur.right; + } + reverseEdge(tail); + } + + public static Node reverseEdge(Node from) { + Node pre = null; + Node next = null; + while (from != null) { + next = from.right; + from.right = pre; + pre = from; + from = next; + } + return pre; + } + + // for test -- print tree + public static void printTree(Node head) { + System.out.println("Binary Tree:"); + printInOrder(head, 0, "H", 17); + System.out.println(); + } + + public static void printInOrder(Node head, int height, String to, int len) { + if (head == null) { + return; + } + printInOrder(head.right, height + 1, "v", len); + String val = to + head.value + to; + int lenM = val.length(); + int lenL = (len - lenM) / 2; + int lenR = len - lenM - lenL; + val = getSpace(lenL) + val + getSpace(lenR); + System.out.println(getSpace(height * len) + val); + printInOrder(head.left, height + 1, "^", len); + } + + public static String getSpace(int num) { + String space = " "; + StringBuffer buf = new StringBuffer(""); + for (int i = 0; i < num; i++) { + buf.append(space); + } + return buf.toString(); + } + + public static boolean isBST(Node head) { + if (head == null) { + return true; + } + Node cur = head; + Node mostRight = null; + Integer pre = null; + boolean ans = true; + while (cur != null) { + mostRight = cur.left; + if (mostRight != null) { + while (mostRight.right != null && mostRight.right != cur) { + mostRight = mostRight.right; + } + if (mostRight.right == null) { + mostRight.right = cur; + cur = cur.left; + continue; + } else { + mostRight.right = null; + } + } + if (pre != null && pre >= cur.value) { + ans = false; + } + pre = cur.value; + cur = cur.right; + } + return ans; + } + + public static void main(String[] args) { + Node head = new Node(4); + head.left = new Node(2); + head.right = new Node(6); + head.left.left = new Node(1); + head.left.right = new Node(3); + head.right.left = new Node(5); + head.right.right = new Node(7); + printTree(head); + morrisIn(head); + morrisPre(head); + morrisPos(head); + printTree(head); + + } + +} diff --git a/体系学习班/class30/Code02_MinDepth.java b/体系学习班/class30/Code02_MinDepth.java new file mode 100644 index 0000000..fa2160c --- /dev/null +++ b/体系学习班/class30/Code02_MinDepth.java @@ -0,0 +1,88 @@ +package class30; + +// 本题测试链接 : https://leetcode-cn.com/problems/minimum-depth-of-binary-tree/ +public class Code02_MinDepth { + + // 不提交这个类 + public static class TreeNode { + public int val; + public TreeNode left; + public TreeNode right; + + public TreeNode(int x) { + val = x; + } + } + + // 下面的方法是一般解 + public static int minDepth1(TreeNode head) { + if (head == null) { + return 0; + } + return p(head); + } + + // 返回x为头的树,最小深度是多少 + public static int p(TreeNode x) { + if (x.left == null && x.right == null) { + return 1; + } + // 左右子树起码有一个不为空 + int leftH = Integer.MAX_VALUE; + if (x.left != null) { + leftH = p(x.left); + } + int rightH = Integer.MAX_VALUE; + if (x.right != null) { + rightH = p(x.right); + } + return 1 + Math.min(leftH, rightH); + } + + // 下面的方法是morris遍历的解 + public static int minDepth2(TreeNode head) { + if (head == null) { + return 0; + } + TreeNode cur = head; + TreeNode mostRight = null; + int curLevel = 0; + int minHeight = Integer.MAX_VALUE; + while (cur != null) { + mostRight = cur.left; + if (mostRight != null) { + int rightBoardSize = 1; + while (mostRight.right != null && mostRight.right != cur) { + rightBoardSize++; + mostRight = mostRight.right; + } + if (mostRight.right == null) { // 第一次到达 + curLevel++; + mostRight.right = cur; + cur = cur.left; + continue; + } else { // 第二次到达 + if (mostRight.left == null) { + minHeight = Math.min(minHeight, curLevel); + } + curLevel -= rightBoardSize; + mostRight.right = null; + } + } else { // 只有一次到达 + curLevel++; + } + cur = cur.right; + } + int finalRight = 1; + cur = head; + while (cur.right != null) { + finalRight++; + cur = cur.right; + } + if (cur.left == null && cur.right == null) { + minHeight = Math.min(minHeight, finalRight); + } + return minHeight; + } + +} diff --git a/体系学习班/class31/Code01_SegmentTree.java b/体系学习班/class31/Code01_SegmentTree.java new file mode 100644 index 0000000..282c20a --- /dev/null +++ b/体系学习班/class31/Code01_SegmentTree.java @@ -0,0 +1,245 @@ +package class31; + +public class Code01_SegmentTree { + + public static class SegmentTree { + // arr[]为原序列的信息从0开始,但在arr里是从1开始的 + // sum[]模拟线段树维护区间和 + // lazy[]为累加和懒惰标记 + // change[]为更新的值 + // update[]为更新慵懒标记 + private int MAXN; + private int[] arr; + private int[] sum; + private int[] lazy; + private int[] change; + private boolean[] update; + + public SegmentTree(int[] origin) { + MAXN = origin.length + 1; + arr = new int[MAXN]; // arr[0] 不用 从1开始使用 + for (int i = 1; i < MAXN; i++) { + arr[i] = origin[i - 1]; + } + sum = new int[MAXN << 2]; // 用来支持脑补概念中,某一个范围的累加和信息 + lazy = new int[MAXN << 2]; // 用来支持脑补概念中,某一个范围沒有往下傳遞的纍加任務 + change = new int[MAXN << 2]; // 用来支持脑补概念中,某一个范围有没有更新操作的任务 + update = new boolean[MAXN << 2]; // 用来支持脑补概念中,某一个范围更新任务,更新成了什么 + } + + private void pushUp(int rt) { + sum[rt] = sum[rt << 1] + sum[rt << 1 | 1]; + } + + // 之前的,所有懒增加,和懒更新,从父范围,发给左右两个子范围 + // 分发策略是什么 + // ln表示左子树元素结点个数,rn表示右子树结点个数 + private void pushDown(int rt, int ln, int rn) { + if (update[rt]) { + update[rt << 1] = true; + update[rt << 1 | 1] = true; + change[rt << 1] = change[rt]; + change[rt << 1 | 1] = change[rt]; + lazy[rt << 1] = 0; + lazy[rt << 1 | 1] = 0; + sum[rt << 1] = change[rt] * ln; + sum[rt << 1 | 1] = change[rt] * rn; + update[rt] = false; + } + if (lazy[rt] != 0) { + lazy[rt << 1] += lazy[rt]; + sum[rt << 1] += lazy[rt] * ln; + lazy[rt << 1 | 1] += lazy[rt]; + sum[rt << 1 | 1] += lazy[rt] * rn; + lazy[rt] = 0; + } + } + + // 在初始化阶段,先把sum数组,填好 + // 在arr[l~r]范围上,去build,1~N, + // rt : 这个范围在sum中的下标 + public void build(int l, int r, int rt) { + if (l == r) { + sum[rt] = arr[l]; + return; + } + int mid = (l + r) >> 1; + build(l, mid, rt << 1); + build(mid + 1, r, rt << 1 | 1); + pushUp(rt); + } + + + // L~R 所有的值变成C + // l~r rt + public void update(int L, int R, int C, int l, int r, int rt) { + if (L <= l && r <= R) { + update[rt] = true; + change[rt] = C; + sum[rt] = C * (r - l + 1); + lazy[rt] = 0; + return; + } + // 当前任务躲不掉,无法懒更新,要往下发 + int mid = (l + r) >> 1; + pushDown(rt, mid - l + 1, r - mid); + if (L <= mid) { + update(L, R, C, l, mid, rt << 1); + } + if (R > mid) { + update(L, R, C, mid + 1, r, rt << 1 | 1); + } + pushUp(rt); + } + + // L~R, C 任务! + // rt,l~r + public void add(int L, int R, int C, int l, int r, int rt) { + // 任务如果把此时的范围全包了! + if (L <= l && r <= R) { + sum[rt] += C * (r - l + 1); + lazy[rt] += C; + return; + } + // 任务没有把你全包! + // l r mid = (l+r)/2 + int mid = (l + r) >> 1; + pushDown(rt, mid - l + 1, r - mid); + // L~R + if (L <= mid) { + add(L, R, C, l, mid, rt << 1); + } + if (R > mid) { + add(L, R, C, mid + 1, r, rt << 1 | 1); + } + pushUp(rt); + } + + // 1~6 累加和是多少? 1~8 rt + public long query(int L, int R, int l, int r, int rt) { + if (L <= l && r <= R) { + return sum[rt]; + } + int mid = (l + r) >> 1; + pushDown(rt, mid - l + 1, r - mid); + long ans = 0; + if (L <= mid) { + ans += query(L, R, l, mid, rt << 1); + } + if (R > mid) { + ans += query(L, R, mid + 1, r, rt << 1 | 1); + } + return ans; + } + + } + + public static class Right { + public int[] arr; + + public Right(int[] origin) { + arr = new int[origin.length + 1]; + for (int i = 0; i < origin.length; i++) { + arr[i + 1] = origin[i]; + } + } + + public void update(int L, int R, int C) { + for (int i = L; i <= R; i++) { + arr[i] = C; + } + } + + public void add(int L, int R, int C) { + for (int i = L; i <= R; i++) { + arr[i] += C; + } + } + + public long query(int L, int R) { + long ans = 0; + for (int i = L; i <= R; i++) { + ans += arr[i]; + } + return ans; + } + + } + + public static int[] genarateRandomArray(int len, int max) { + int size = (int) (Math.random() * len) + 1; + int[] origin = new int[size]; + for (int i = 0; i < size; i++) { + origin[i] = (int) (Math.random() * max) - (int) (Math.random() * max); + } + return origin; + } + + public static boolean test() { + int len = 100; + int max = 1000; + int testTimes = 5000; + int addOrUpdateTimes = 1000; + int queryTimes = 500; + for (int i = 0; i < testTimes; i++) { + int[] origin = genarateRandomArray(len, max); + SegmentTree seg = new SegmentTree(origin); + int S = 1; + int N = origin.length; + int root = 1; + seg.build(S, N, root); + Right rig = new Right(origin); + for (int j = 0; j < addOrUpdateTimes; j++) { + int num1 = (int) (Math.random() * N) + 1; + int num2 = (int) (Math.random() * N) + 1; + int L = Math.min(num1, num2); + int R = Math.max(num1, num2); + int C = (int) (Math.random() * max) - (int) (Math.random() * max); + if (Math.random() < 0.5) { + seg.add(L, R, C, S, N, root); + rig.add(L, R, C); + } else { + seg.update(L, R, C, S, N, root); + rig.update(L, R, C); + } + } + for (int k = 0; k < queryTimes; k++) { + int num1 = (int) (Math.random() * N) + 1; + int num2 = (int) (Math.random() * N) + 1; + int L = Math.min(num1, num2); + int R = Math.max(num1, num2); + long ans1 = seg.query(L, R, S, N, root); + long ans2 = rig.query(L, R); + if (ans1 != ans2) { + return false; + } + } + } + return true; + } + + public static void main(String[] args) { + int[] origin = { 2, 1, 1, 2, 3, 4, 5 }; + SegmentTree seg = new SegmentTree(origin); + int S = 1; // 整个区间的开始位置,规定从1开始,不从0开始 -> 固定 + int N = origin.length; // 整个区间的结束位置,规定能到N,不是N-1 -> 固定 + int root = 1; // 整棵树的头节点位置,规定是1,不是0 -> 固定 + int L = 2; // 操作区间的开始位置 -> 可变 + int R = 5; // 操作区间的结束位置 -> 可变 + int C = 4; // 要加的数字或者要更新的数字 -> 可变 + // 区间生成,必须在[S,N]整个范围上build + seg.build(S, N, root); + // 区间修改,可以改变L、R和C的值,其他值不可改变 + seg.add(L, R, C, S, N, root); + // 区间更新,可以改变L、R和C的值,其他值不可改变 + seg.update(L, R, C, S, N, root); + // 区间查询,可以改变L和R的值,其他值不可改变 + long sum = seg.query(L, R, S, N, root); + System.out.println(sum); + + System.out.println("对数器测试开始..."); + System.out.println("测试结果 : " + (test() ? "通过" : "未通过")); + + } + +} diff --git a/体系学习班/class31/Code02_FallingSquares.java b/体系学习班/class31/Code02_FallingSquares.java new file mode 100644 index 0000000..2f3b2a2 --- /dev/null +++ b/体系学习班/class31/Code02_FallingSquares.java @@ -0,0 +1,109 @@ +package class31; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.TreeSet; + +public class Code02_FallingSquares { + + public static class SegmentTree { + private int[] max; + private int[] change; + private boolean[] update; + + public SegmentTree(int size) { + int N = size + 1; + max = new int[N << 2]; + + change = new int[N << 2]; + update = new boolean[N << 2]; + } + + private void pushUp(int rt) { + max[rt] = Math.max(max[rt << 1], max[rt << 1 | 1]); + } + + // ln表示左子树元素结点个数,rn表示右子树结点个数 + private void pushDown(int rt, int ln, int rn) { + if (update[rt]) { + update[rt << 1] = true; + update[rt << 1 | 1] = true; + change[rt << 1] = change[rt]; + change[rt << 1 | 1] = change[rt]; + max[rt << 1] = change[rt]; + max[rt << 1 | 1] = change[rt]; + update[rt] = false; + } + } + + public void update(int L, int R, int C, int l, int r, int rt) { + if (L <= l && r <= R) { + update[rt] = true; + change[rt] = C; + max[rt] = C; + return; + } + int mid = (l + r) >> 1; + pushDown(rt, mid - l + 1, r - mid); + if (L <= mid) { + update(L, R, C, l, mid, rt << 1); + } + if (R > mid) { + update(L, R, C, mid + 1, r, rt << 1 | 1); + } + pushUp(rt); + } + + public int query(int L, int R, int l, int r, int rt) { + if (L <= l && r <= R) { + return max[rt]; + } + int mid = (l + r) >> 1; + pushDown(rt, mid - l + 1, r - mid); + int left = 0; + int right = 0; + if (L <= mid) { + left = query(L, R, l, mid, rt << 1); + } + if (R > mid) { + right = query(L, R, mid + 1, r, rt << 1 | 1); + } + return Math.max(left, right); + } + + } + + public HashMap index(int[][] positions) { + TreeSet pos = new TreeSet<>(); + for (int[] arr : positions) { + pos.add(arr[0]); + pos.add(arr[0] + arr[1] - 1); + } + HashMap map = new HashMap<>(); + int count = 0; + for (Integer index : pos) { + map.put(index, ++count); + } + return map; + } + + public List fallingSquares(int[][] positions) { + HashMap map = index(positions); + int N = map.size(); + SegmentTree segmentTree = new SegmentTree(N); + int max = 0; + List res = new ArrayList<>(); + // 每落一个正方形,收集一下,所有东西组成的图像,最高高度是什么 + for (int[] arr : positions) { + int L = map.get(arr[0]); + int R = map.get(arr[0] + arr[1] - 1); + int height = segmentTree.query(L, R, 1, N, 1) + arr[1]; + max = Math.max(max, height); + res.add(max); + segmentTree.update(L, R, height, 1, N, 1); + } + return res; + } + +} diff --git a/体系学习班/class32/Code01_IndexTree.java b/体系学习班/class32/Code01_IndexTree.java new file mode 100644 index 0000000..4c4bdab --- /dev/null +++ b/体系学习班/class32/Code01_IndexTree.java @@ -0,0 +1,83 @@ +package class32; + +public class Code01_IndexTree { + + // 下标从1开始! + public static class IndexTree { + + private int[] tree; + private int N; + + // 0位置弃而不用! + public IndexTree(int size) { + N = size; + tree = new int[N + 1]; + } + + // 1~index 累加和是多少? + public int sum(int index) { + int ret = 0; + while (index > 0) { + ret += tree[index]; + index -= index & -index; + } + return ret; + } + + // index & -index : 提取出index最右侧的1出来 + // index : 0011001000 + // index & -index : 0000001000 + public void add(int index, int d) { + while (index <= N) { + tree[index] += d; + index += index & -index; + } + } + } + + public static class Right { + private int[] nums; + private int N; + + public Right(int size) { + N = size + 1; + nums = new int[N + 1]; + } + + public int sum(int index) { + int ret = 0; + for (int i = 1; i <= index; i++) { + ret += nums[i]; + } + return ret; + } + + public void add(int index, int d) { + nums[index] += d; + } + + } + + public static void main(String[] args) { + int N = 100; + int V = 100; + int testTime = 2000000; + IndexTree tree = new IndexTree(N); + Right test = new Right(N); + System.out.println("test begin"); + for (int i = 0; i < testTime; i++) { + int index = (int) (Math.random() * N) + 1; + if (Math.random() <= 0.5) { + int add = (int) (Math.random() * V); + tree.add(index, add); + test.add(index, add); + } else { + if (tree.sum(index) != test.sum(index)) { + System.out.println("Oops!"); + } + } + } + System.out.println("test finish"); + } + +} diff --git a/体系学习班/class32/Code02_IndexTree2D.java b/体系学习班/class32/Code02_IndexTree2D.java new file mode 100644 index 0000000..12bb6e2 --- /dev/null +++ b/体系学习班/class32/Code02_IndexTree2D.java @@ -0,0 +1,57 @@ +package class32; + +// 测试链接:https://leetcode.com/problems/range-sum-query-2d-mutable +// 但这个题是付费题目 +// 提交时把类名、构造函数名从Code02_IndexTree2D改成NumMatrix +public class Code02_IndexTree2D { + private int[][] tree; + private int[][] nums; + private int N; + private int M; + + public Code02_IndexTree2D(int[][] matrix) { + if (matrix.length == 0 || matrix[0].length == 0) { + return; + } + N = matrix.length; + M = matrix[0].length; + tree = new int[N + 1][M + 1]; + nums = new int[N][M]; + for (int i = 0; i < N; i++) { + for (int j = 0; j < M; j++) { + update(i, j, matrix[i][j]); + } + } + } + + private int sum(int row, int col) { + int sum = 0; + for (int i = row + 1; i > 0; i -= i & (-i)) { + for (int j = col + 1; j > 0; j -= j & (-j)) { + sum += tree[i][j]; + } + } + return sum; + } + + public void update(int row, int col, int val) { + if (N == 0 || M == 0) { + return; + } + int add = val - nums[row][col]; + nums[row][col] = val; + for (int i = row + 1; i <= N; i += i & (-i)) { + for (int j = col + 1; j <= M; j += j & (-j)) { + tree[i][j] += add; + } + } + } + + public int sumRegion(int row1, int col1, int row2, int col2) { + if (N == 0 || M == 0) { + return 0; + } + return sum(row2, col2) + sum(row1 - 1, col1 - 1) - sum(row1 - 1, col2) - sum(row2, col1 - 1); + } + +} diff --git a/体系学习班/class32/Code03_AC1.java b/体系学习班/class32/Code03_AC1.java new file mode 100644 index 0000000..a6874e5 --- /dev/null +++ b/体系学习班/class32/Code03_AC1.java @@ -0,0 +1,105 @@ +package class32; + +import java.util.LinkedList; +import java.util.Queue; + +public class Code03_AC1 { + + public static class Node { + public int end; // 有多少个字符串以该节点结尾 + public Node fail; + public Node[] nexts; + + public Node() { + end = 0; + fail = null; + nexts = new Node[26]; + } + } + + public static class ACAutomation { + private Node root; + + public ACAutomation() { + root = new Node(); + } + + // 你有多少个匹配串,就调用多少次insert + public void insert(String s) { + char[] str = s.toCharArray(); + Node cur = root; + int index = 0; + for (int i = 0; i < str.length; i++) { + index = str[i] - 'a'; + if (cur.nexts[index] == null) { + Node next = new Node(); + cur.nexts[index] = next; + } + cur = cur.nexts[index]; + } + cur.end++; + } + + public void build() { + Queue queue = new LinkedList<>(); + queue.add(root); + Node cur = null; + Node cfail = null; + while (!queue.isEmpty()) { + cur = queue.poll(); // 父 + for (int i = 0; i < 26; i++) { // 下级所有的路 + if (cur.nexts[i] != null) { // 该路下有子节点 + cur.nexts[i].fail = root; // 初始时先设置一个值 + cfail = cur.fail; + while (cfail != null) { // cur不是头节点 + if (cfail.nexts[i] != null) { + cur.nexts[i].fail = cfail.nexts[i]; + break; + } + cfail = cfail.fail; + } + queue.add(cur.nexts[i]); + } + } + } + } + + public int containNum(String content) { + char[] str = content.toCharArray(); + Node cur = root; + Node follow = null; + int index = 0; + int ans = 0; + for (int i = 0; i < str.length; i++) { + index = str[i] - 'a'; + while (cur.nexts[index] == null && cur != root) { + cur = cur.fail; + } + cur = cur.nexts[index] != null ? cur.nexts[index] : root; + follow = cur; + while (follow != root) { + if (follow.end == -1) { + break; + } + { // 不同的需求,在这一段{ }之间修改 + ans += follow.end; + follow.end = -1; + } // 不同的需求,在这一段{ }之间修改 + follow = follow.fail; + } + } + return ans; + } + + } + + public static void main(String[] args) { + ACAutomation ac = new ACAutomation(); + ac.insert("dhe"); + ac.insert("he"); + ac.insert("c"); + ac.build(); + System.out.println(ac.containNum("cdhe")); + } + +} diff --git a/体系学习班/class32/Code04_AC2.java b/体系学习班/class32/Code04_AC2.java new file mode 100644 index 0000000..e684de3 --- /dev/null +++ b/体系学习班/class32/Code04_AC2.java @@ -0,0 +1,125 @@ +package class32; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; + +public class Code04_AC2 { + + // 前缀树的节点 + public static class Node { + // 如果一个node,end为空,不是结尾 + // 如果end不为空,表示这个点是某个字符串的结尾,end的值就是这个字符串 + public String end; + // 只有在上面的end变量不为空的时候,endUse才有意义 + // 表示,这个字符串之前有没有加入过答案 + public boolean endUse; + public Node fail; + public Node[] nexts; + + public Node() { + endUse = false; + end = null; + fail = null; + nexts = new Node[26]; + } + } + + public static class ACAutomation { + private Node root; + + public ACAutomation() { + root = new Node(); + } + + public void insert(String s) { + char[] str = s.toCharArray(); + Node cur = root; + int index = 0; + for (int i = 0; i < str.length; i++) { + index = str[i] - 'a'; + if (cur.nexts[index] == null) { + cur.nexts[index] = new Node(); + } + cur = cur.nexts[index]; + } + cur.end = s; + } + + public void build() { + Queue queue = new LinkedList<>(); + queue.add(root); + Node cur = null; + Node cfail = null; + while (!queue.isEmpty()) { + // 某个父亲,cur + cur = queue.poll(); + for (int i = 0; i < 26; i++) { // 所有的路 + // cur -> 父亲 i号儿子,必须把i号儿子的fail指针设置好! + if (cur.nexts[i] != null) { // 如果真的有i号儿子 + cur.nexts[i].fail = root; + cfail = cur.fail; + while (cfail != null) { + if (cfail.nexts[i] != null) { + cur.nexts[i].fail = cfail.nexts[i]; + break; + } + cfail = cfail.fail; + } + queue.add(cur.nexts[i]); + } + } + } + } + + // 大文章:content + public List containWords(String content) { + char[] str = content.toCharArray(); + Node cur = root; + Node follow = null; + int index = 0; + List ans = new ArrayList<>(); + for (int i = 0; i < str.length; i++) { + index = str[i] - 'a'; // 路 + // 如果当前字符在这条路上没配出来,就随着fail方向走向下条路径 + while (cur.nexts[index] == null && cur != root) { + cur = cur.fail; + } + // 1) 现在来到的路径,是可以继续匹配的 + // 2) 现在来到的节点,就是前缀树的根节点 + cur = cur.nexts[index] != null ? cur.nexts[index] : root; + follow = cur; + while (follow != root) { + if (follow.endUse) { + break; + } + // 不同的需求,在这一段之间修改 + if (follow.end != null) { + ans.add(follow.end); + follow.endUse = true; + } + // 不同的需求,在这一段之间修改 + follow = follow.fail; + } + } + return ans; + } + + } + + public static void main(String[] args) { + ACAutomation ac = new ACAutomation(); + ac.insert("dhe"); + ac.insert("he"); + ac.insert("abcdheks"); + // 设置fail指针 + ac.build(); + + List contains = ac.containWords("abcdhekskdjfafhasldkflskdjhwqaeruv"); + for (String word : contains) { + System.out.println(word); + } + } + +} diff --git a/体系学习班/class33/Hash.java b/体系学习班/class33/Hash.java new file mode 100644 index 0000000..c9c01f3 --- /dev/null +++ b/体系学习班/class33/Hash.java @@ -0,0 +1,49 @@ +package class33; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.Security; + +// 需要自己找一下javax.xml.bind的jar,然后导入到项目 +import javax.xml.bind.DatatypeConverter; + +public class Hash { + + private MessageDigest hash; + + public Hash(String algorithm) { + try { + hash = MessageDigest.getInstance(algorithm); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } + } + + public String hashCode(String input) { + return DatatypeConverter.printHexBinary(hash.digest(input.getBytes())).toUpperCase(); + } + + public static void main(String[] args) { + System.out.println("支持的算法 : "); + for (String str : Security.getAlgorithms("MessageDigest")) { + System.out.println(str); + } + System.out.println("======="); + + String algorithm = "MD5"; + Hash hash = new Hash(algorithm); + + String input1 = "zuochengyunzuochengyun1"; + String input2 = "zuochengyunzuochengyun2"; + String input3 = "zuochengyunzuochengyun3"; + String input4 = "zuochengyunzuochengyun4"; + String input5 = "zuochengyunzuochengyun5"; + System.out.println(hash.hashCode(input1)); + System.out.println(hash.hashCode(input2)); + System.out.println(hash.hashCode(input3)); + System.out.println(hash.hashCode(input4)); + System.out.println(hash.hashCode(input5)); + + } + +} diff --git a/体系学习班/class34/ReadMe.java b/体系学习班/class34/ReadMe.java new file mode 100644 index 0000000..16e42b9 --- /dev/null +++ b/体系学习班/class34/ReadMe.java @@ -0,0 +1,2 @@ +// 本章并无code,因为资源限制类题目输入需要的条件较多并且真的实现代码量巨大 +// 面试中这类题目出现也就和面试官聊解法,不会有代码实现的要求 \ No newline at end of file diff --git a/体系学习班/class35/Code01_AVLTreeMap.java b/体系学习班/class35/Code01_AVLTreeMap.java new file mode 100644 index 0000000..caafd46 --- /dev/null +++ b/体系学习班/class35/Code01_AVLTreeMap.java @@ -0,0 +1,257 @@ +package class35; + +public class Code01_AVLTreeMap { + + public static class AVLNode, V> { + public K k; + public V v; + public AVLNode l; + public AVLNode r; + public int h; + + public AVLNode(K key, V value) { + k = key; + v = value; + h = 1; + } + } + + public static class AVLTreeMap, V> { + private AVLNode root; + private int size; + + public AVLTreeMap() { + root = null; + size = 0; + } + + private AVLNode rightRotate(AVLNode cur) { + AVLNode left = cur.l; + cur.l = left.r; + left.r = cur; + cur.h = Math.max((cur.l != null ? cur.l.h : 0), (cur.r != null ? cur.r.h : 0)) + 1; + left.h = Math.max((left.l != null ? left.l.h : 0), (left.r != null ? left.r.h : 0)) + 1; + return left; + } + + private AVLNode leftRotate(AVLNode cur) { + AVLNode right = cur.r; + cur.r = right.l; + right.l = cur; + cur.h = Math.max((cur.l != null ? cur.l.h : 0), (cur.r != null ? cur.r.h : 0)) + 1; + right.h = Math.max((right.l != null ? right.l.h : 0), (right.r != null ? right.r.h : 0)) + 1; + return right; + } + + private AVLNode maintain(AVLNode cur) { + if (cur == null) { + return null; + } + int leftHeight = cur.l != null ? cur.l.h : 0; + int rightHeight = cur.r != null ? cur.r.h : 0; + if (Math.abs(leftHeight - rightHeight) > 1) { + if (leftHeight > rightHeight) { + int leftLeftHeight = cur.l != null && cur.l.l != null ? cur.l.l.h : 0; + int leftRightHeight = cur.l != null && cur.l.r != null ? cur.l.r.h : 0; + if (leftLeftHeight >= leftRightHeight) { + cur = rightRotate(cur); + } else { + cur.l = leftRotate(cur.l); + cur = rightRotate(cur); + } + } else { + int rightLeftHeight = cur.r != null && cur.r.l != null ? cur.r.l.h : 0; + int rightRightHeight = cur.r != null && cur.r.r != null ? cur.r.r.h : 0; + if (rightRightHeight >= rightLeftHeight) { + cur = leftRotate(cur); + } else { + cur.r = rightRotate(cur.r); + cur = leftRotate(cur); + } + } + } + return cur; + } + + private AVLNode findLastIndex(K key) { + AVLNode pre = root; + AVLNode cur = root; + while (cur != null) { + pre = cur; + if (key.compareTo(cur.k) == 0) { + break; + } else if (key.compareTo(cur.k) < 0) { + cur = cur.l; + } else { + cur = cur.r; + } + } + return pre; + } + + private AVLNode findLastNoSmallIndex(K key) { + AVLNode ans = null; + AVLNode cur = root; + while (cur != null) { + if (key.compareTo(cur.k) == 0) { + ans = cur; + break; + } else if (key.compareTo(cur.k) < 0) { + ans = cur; + cur = cur.l; + } else { + cur = cur.r; + } + } + return ans; + } + + private AVLNode findLastNoBigIndex(K key) { + AVLNode ans = null; + AVLNode cur = root; + while (cur != null) { + if (key.compareTo(cur.k) == 0) { + ans = cur; + break; + } else if (key.compareTo(cur.k) < 0) { + cur = cur.l; + } else { + ans = cur; + cur = cur.r; + } + } + return ans; + } + + private AVLNode add(AVLNode cur, K key, V value) { + if (cur == null) { + return new AVLNode(key, value); + } else { + if (key.compareTo(cur.k) < 0) { + cur.l = add(cur.l, key, value); + } else { + cur.r = add(cur.r, key, value); + } + cur.h = Math.max(cur.l != null ? cur.l.h : 0, cur.r != null ? cur.r.h : 0) + 1; + return maintain(cur); + } + } + + // 在cur这棵树上,删掉key所代表的节点 + // 返回cur这棵树的新头部 + private AVLNode delete(AVLNode cur, K key) { + if (key.compareTo(cur.k) > 0) { + cur.r = delete(cur.r, key); + } else if (key.compareTo(cur.k) < 0) { + cur.l = delete(cur.l, key); + } else { + if (cur.l == null && cur.r == null) { + cur = null; + } else if (cur.l == null && cur.r != null) { + cur = cur.r; + } else if (cur.l != null && cur.r == null) { + cur = cur.l; + } else { + AVLNode des = cur.r; + while (des.l != null) { + des = des.l; + } + cur.r = delete(cur.r, des.k); + des.l = cur.l; + des.r = cur.r; + cur = des; + } + } + if (cur != null) { + cur.h = Math.max(cur.l != null ? cur.l.h : 0, cur.r != null ? cur.r.h : 0) + 1; + } + return maintain(cur); + } + + public int size() { + return size; + } + + public boolean containsKey(K key) { + if (key == null) { + return false; + } + AVLNode lastNode = findLastIndex(key); + return lastNode != null && key.compareTo(lastNode.k) == 0 ? true : false; + } + + public void put(K key, V value) { + if (key == null) { + return; + } + AVLNode lastNode = findLastIndex(key); + if (lastNode != null && key.compareTo(lastNode.k) == 0) { + lastNode.v = value; + } else { + size++; + root = add(root, key, value); + } + } + + public void remove(K key) { + if (key == null) { + return; + } + if (containsKey(key)) { + size--; + root = delete(root, key); + } + } + + public V get(K key) { + if (key == null) { + return null; + } + AVLNode lastNode = findLastIndex(key); + if (lastNode != null && key.compareTo(lastNode.k) == 0) { + return lastNode.v; + } + return null; + } + + public K firstKey() { + if (root == null) { + return null; + } + AVLNode cur = root; + while (cur.l != null) { + cur = cur.l; + } + return cur.k; + } + + public K lastKey() { + if (root == null) { + return null; + } + AVLNode cur = root; + while (cur.r != null) { + cur = cur.r; + } + return cur.k; + } + + public K floorKey(K key) { + if (key == null) { + return null; + } + AVLNode lastNoBigNode = findLastNoBigIndex(key); + return lastNoBigNode == null ? null : lastNoBigNode.k; + } + + public K ceilingKey(K key) { + if (key == null) { + return null; + } + AVLNode lastNoSmallNode = findLastNoSmallIndex(key); + return lastNoSmallNode == null ? null : lastNoSmallNode.k; + } + + } + +} diff --git a/体系学习班/class36/Code01_SizeBalancedTreeMap.java b/体系学习班/class36/Code01_SizeBalancedTreeMap.java new file mode 100644 index 0000000..5077b7a --- /dev/null +++ b/体系学习班/class36/Code01_SizeBalancedTreeMap.java @@ -0,0 +1,360 @@ +package class36; + +public class Code01_SizeBalancedTreeMap { + + public static class SBTNode, V> { + public K key; + public V value; + public SBTNode l; + public SBTNode r; + public int size; // 不同的key的数量 + + public SBTNode(K key, V value) { + this.key = key; + this.value = value; + size = 1; + } + } + + public static class SizeBalancedTreeMap, V> { + private SBTNode root; + + private SBTNode rightRotate(SBTNode cur) { + SBTNode leftNode = cur.l; + cur.l = leftNode.r; + leftNode.r = cur; + leftNode.size = cur.size; + cur.size = (cur.l != null ? cur.l.size : 0) + (cur.r != null ? cur.r.size : 0) + 1; + return leftNode; + } + + private SBTNode leftRotate(SBTNode cur) { + SBTNode rightNode = cur.r; + cur.r = rightNode.l; + rightNode.l = cur; + rightNode.size = cur.size; + cur.size = (cur.l != null ? cur.l.size : 0) + (cur.r != null ? cur.r.size : 0) + 1; + return rightNode; + } + + private SBTNode maintain(SBTNode cur) { + if (cur == null) { + return null; + } + int leftSize = cur.l != null ? cur.l.size : 0; + int leftLeftSize = cur.l != null && cur.l.l != null ? cur.l.l.size : 0; + int leftRightSize = cur.l != null && cur.l.r != null ? cur.l.r.size : 0; + int rightSize = cur.r != null ? cur.r.size : 0; + int rightLeftSize = cur.r != null && cur.r.l != null ? cur.r.l.size : 0; + int rightRightSize = cur.r != null && cur.r.r != null ? cur.r.r.size : 0; + if (leftLeftSize > rightSize) { + cur = rightRotate(cur); + cur.r = maintain(cur.r); + cur = maintain(cur); + } else if (leftRightSize > rightSize) { + cur.l = leftRotate(cur.l); + cur = rightRotate(cur); + cur.l = maintain(cur.l); + cur.r = maintain(cur.r); + cur = maintain(cur); + } else if (rightRightSize > leftSize) { + cur = leftRotate(cur); + cur.l = maintain(cur.l); + cur = maintain(cur); + } else if (rightLeftSize > leftSize) { + cur.r = rightRotate(cur.r); + cur = leftRotate(cur); + cur.l = maintain(cur.l); + cur.r = maintain(cur.r); + cur = maintain(cur); + } + return cur; + } + + private SBTNode findLastIndex(K key) { + SBTNode pre = root; + SBTNode cur = root; + while (cur != null) { + pre = cur; + if (key.compareTo(cur.key) == 0) { + break; + } else if (key.compareTo(cur.key) < 0) { + cur = cur.l; + } else { + cur = cur.r; + } + } + return pre; + } + + private SBTNode findLastNoSmallIndex(K key) { + SBTNode ans = null; + SBTNode cur = root; + while (cur != null) { + if (key.compareTo(cur.key) == 0) { + ans = cur; + break; + } else if (key.compareTo(cur.key) < 0) { + ans = cur; + cur = cur.l; + } else { + cur = cur.r; + } + } + return ans; + } + + private SBTNode findLastNoBigIndex(K key) { + SBTNode ans = null; + SBTNode cur = root; + while (cur != null) { + if (key.compareTo(cur.key) == 0) { + ans = cur; + break; + } else if (key.compareTo(cur.key) < 0) { + cur = cur.l; + } else { + ans = cur; + cur = cur.r; + } + } + return ans; + } + + // 现在,以cur为头的树上,新增,加(key, value)这样的记录 + // 加完之后,会对cur做检查,该调整调整 + // 返回,调整完之后,整棵树的新头部 + private SBTNode add(SBTNode cur, K key, V value) { + if (cur == null) { + return new SBTNode(key, value); + } else { + cur.size++; + if (key.compareTo(cur.key) < 0) { + cur.l = add(cur.l, key, value); + } else { + cur.r = add(cur.r, key, value); + } + return maintain(cur); + } + } + + // 在cur这棵树上,删掉key所代表的节点 + // 返回cur这棵树的新头部 + private SBTNode delete(SBTNode cur, K key) { + cur.size--; + if (key.compareTo(cur.key) > 0) { + cur.r = delete(cur.r, key); + } else if (key.compareTo(cur.key) < 0) { + cur.l = delete(cur.l, key); + } else { // 当前要删掉cur + if (cur.l == null && cur.r == null) { + // free cur memory -> C++ + cur = null; + } else if (cur.l == null && cur.r != null) { + // free cur memory -> C++ + cur = cur.r; + } else if (cur.l != null && cur.r == null) { + // free cur memory -> C++ + cur = cur.l; + } else { // 有左有右 + SBTNode pre = null; + SBTNode des = cur.r; + des.size--; + while (des.l != null) { + pre = des; + des = des.l; + des.size--; + } + if (pre != null) { + pre.l = des.r; + des.r = cur.r; + } + des.l = cur.l; + des.size = des.l.size + (des.r == null ? 0 : des.r.size) + 1; + // free cur memory -> C++ + cur = des; + } + } + // cur = maintain(cur); + return cur; + } + + private SBTNode getIndex(SBTNode cur, int kth) { + if (kth == (cur.l != null ? cur.l.size : 0) + 1) { + return cur; + } else if (kth <= (cur.l != null ? cur.l.size : 0)) { + return getIndex(cur.l, kth); + } else { + return getIndex(cur.r, kth - (cur.l != null ? cur.l.size : 0) - 1); + } + } + + public int size() { + return root == null ? 0 : root.size; + } + + public boolean containsKey(K key) { + if (key == null) { + throw new RuntimeException("invalid parameter."); + } + SBTNode lastNode = findLastIndex(key); + return lastNode != null && key.compareTo(lastNode.key) == 0 ? true : false; + } + + // (key,value) put -> 有序表 新增、改value + public void put(K key, V value) { + if (key == null) { + throw new RuntimeException("invalid parameter."); + } + SBTNode lastNode = findLastIndex(key); + if (lastNode != null && key.compareTo(lastNode.key) == 0) { + lastNode.value = value; + } else { + root = add(root, key, value); + } + } + + public void remove(K key) { + if (key == null) { + throw new RuntimeException("invalid parameter."); + } + if (containsKey(key)) { + root = delete(root, key); + } + } + + public K getIndexKey(int index) { + if (index < 0 || index >= this.size()) { + throw new RuntimeException("invalid parameter."); + } + return getIndex(root, index + 1).key; + } + + public V getIndexValue(int index) { + if (index < 0 || index >= this.size()) { + throw new RuntimeException("invalid parameter."); + } + return getIndex(root, index + 1).value; + } + + public V get(K key) { + if (key == null) { + throw new RuntimeException("invalid parameter."); + } + SBTNode lastNode = findLastIndex(key); + if (lastNode != null && key.compareTo(lastNode.key) == 0) { + return lastNode.value; + } else { + return null; + } + } + + public K firstKey() { + if (root == null) { + return null; + } + SBTNode cur = root; + while (cur.l != null) { + cur = cur.l; + } + return cur.key; + } + + public K lastKey() { + if (root == null) { + return null; + } + SBTNode cur = root; + while (cur.r != null) { + cur = cur.r; + } + return cur.key; + } + + public K floorKey(K key) { + if (key == null) { + throw new RuntimeException("invalid parameter."); + } + SBTNode lastNoBigNode = findLastNoBigIndex(key); + return lastNoBigNode == null ? null : lastNoBigNode.key; + } + + public K ceilingKey(K key) { + if (key == null) { + throw new RuntimeException("invalid parameter."); + } + SBTNode lastNoSmallNode = findLastNoSmallIndex(key); + return lastNoSmallNode == null ? null : lastNoSmallNode.key; + } + + } + + // for test + public static void printAll(SBTNode head) { + System.out.println("Binary Tree:"); + printInOrder(head, 0, "H", 17); + System.out.println(); + } + + // for test + public static void printInOrder(SBTNode head, int height, String to, int len) { + if (head == null) { + return; + } + printInOrder(head.r, height + 1, "v", len); + String val = to + "(" + head.key + "," + head.value + ")" + to; + int lenM = val.length(); + int lenL = (len - lenM) / 2; + int lenR = len - lenM - lenL; + val = getSpace(lenL) + val + getSpace(lenR); + System.out.println(getSpace(height * len) + val); + printInOrder(head.l, height + 1, "^", len); + } + + // for test + public static String getSpace(int num) { + String space = " "; + StringBuffer buf = new StringBuffer(""); + for (int i = 0; i < num; i++) { + buf.append(space); + } + return buf.toString(); + } + + public static void main(String[] args) { + SizeBalancedTreeMap sbt = new SizeBalancedTreeMap(); + sbt.put("d", 4); + sbt.put("c", 3); + sbt.put("a", 1); + sbt.put("b", 2); + // sbt.put("e", 5); + sbt.put("g", 7); + sbt.put("f", 6); + sbt.put("h", 8); + sbt.put("i", 9); + sbt.put("a", 111); + System.out.println(sbt.get("a")); + sbt.put("a", 1); + System.out.println(sbt.get("a")); + for (int i = 0; i < sbt.size(); i++) { + System.out.println(sbt.getIndexKey(i) + " , " + sbt.getIndexValue(i)); + } + printAll(sbt.root); + System.out.println(sbt.firstKey()); + System.out.println(sbt.lastKey()); + System.out.println(sbt.floorKey("g")); + System.out.println(sbt.ceilingKey("g")); + System.out.println(sbt.floorKey("e")); + System.out.println(sbt.ceilingKey("e")); + System.out.println(sbt.floorKey("")); + System.out.println(sbt.ceilingKey("")); + System.out.println(sbt.floorKey("j")); + System.out.println(sbt.ceilingKey("j")); + sbt.remove("d"); + printAll(sbt.root); + sbt.remove("f"); + printAll(sbt.root); + + } + +} diff --git a/体系学习班/class36/Code02_SkipListMap.java b/体系学习班/class36/Code02_SkipListMap.java new file mode 100644 index 0000000..a450e78 --- /dev/null +++ b/体系学习班/class36/Code02_SkipListMap.java @@ -0,0 +1,248 @@ +package class36; + +import java.util.ArrayList; + +public class Code02_SkipListMap { + + // 跳表的节点定义 + public static class SkipListNode, V> { + public K key; + public V val; + public ArrayList> nextNodes; + + public SkipListNode(K k, V v) { + key = k; + val = v; + nextNodes = new ArrayList>(); + } + + // 遍历的时候,如果是往右遍历到的null(next == null), 遍历结束 + // 头(null), 头节点的null,认为最小 + // node -> 头,node(null, "") node.isKeyLess(!null) true + // node里面的key是否比otherKey小,true,不是false + public boolean isKeyLess(K otherKey) { + // otherKey == null -> false + return otherKey != null && (key == null || key.compareTo(otherKey) < 0); + } + + public boolean isKeyEqual(K otherKey) { + return (key == null && otherKey == null) + || (key != null && otherKey != null && key.compareTo(otherKey) == 0); + } + + } + + public static class SkipListMap, V> { + private static final double PROBABILITY = 0.5; // < 0.5 继续做,>=0.5 停 + private SkipListNode head; + private int size; + private int maxLevel; + + public SkipListMap() { + head = new SkipListNode(null, null); + head.nextNodes.add(null); // 0 + size = 0; + maxLevel = 0; + } + + // 从最高层开始,一路找下去, + // 最终,找到第0层的 mostRightLessNodeInTree(K key) { + if (key == null) { + return null; + } + int level = maxLevel; + SkipListNode cur = head; + while (level >= 0) { // 从上层跳下层 + // cur level -> level-1 + cur = mostRightLessNodeInLevel(key, cur, level--); + } + return cur; + } + + // 在level层里,如何往右移动 + // 现在来到的节点是cur,来到了cur的level层,在level层上,找到 mostRightLessNodeInLevel(K key, + SkipListNode cur, + int level) { + SkipListNode next = cur.nextNodes.get(level); + while (next != null && next.isKeyLess(key)) { + cur = next; + next = cur.nextNodes.get(level); + } + return cur; + } + + public boolean containsKey(K key) { + if (key == null) { + return false; + } + SkipListNode less = mostRightLessNodeInTree(key); + SkipListNode next = less.nextNodes.get(0); + return next != null && next.isKeyEqual(key); + } + + // 新增、改value + public void put(K key, V value) { + if (key == null) { + return; + } + // 0层上,最右一个,< key 的Node -> >key + SkipListNode less = mostRightLessNodeInTree(key); + SkipListNode find = less.nextNodes.get(0); + if (find != null && find.isKeyEqual(key)) { + find.val = value; + } else { // find == null 8 7 9 + size++; + int newNodeLevel = 0; + while (Math.random() < PROBABILITY) { + newNodeLevel++; + } + // newNodeLevel + while (newNodeLevel > maxLevel) { + head.nextNodes.add(null); + maxLevel++; + } + SkipListNode newNode = new SkipListNode(key, value); + for (int i = 0; i <= newNodeLevel; i++) { + newNode.nextNodes.add(null); + } + int level = maxLevel; + SkipListNode pre = head; + while (level >= 0) { + // level 层中,找到最右的 < key 的节点 + pre = mostRightLessNodeInLevel(key, pre, level); + if (level <= newNodeLevel) { + newNode.nextNodes.set(level, pre.nextNodes.get(level)); + pre.nextNodes.set(level, newNode); + } + level--; + } + } + } + + public V get(K key) { + if (key == null) { + return null; + } + SkipListNode less = mostRightLessNodeInTree(key); + SkipListNode next = less.nextNodes.get(0); + return next != null && next.isKeyEqual(key) ? next.val : null; + } + + public void remove(K key) { + if (containsKey(key)) { + size--; + int level = maxLevel; + SkipListNode pre = head; + while (level >= 0) { + pre = mostRightLessNodeInLevel(key, pre, level); + SkipListNode next = pre.nextNodes.get(level); + // 1)在这一层中,pre下一个就是key + // 2)在这一层中,pre的下一个key是>要删除key + if (next != null && next.isKeyEqual(key)) { + // free delete node memory -> C++ + // level : pre -> next(key) -> ... + pre.nextNodes.set(level, next.nextNodes.get(level)); + } + // 在level层只有一个节点了,就是默认节点head + if (level != 0 && pre == head && pre.nextNodes.get(level) == null) { + head.nextNodes.remove(level); + maxLevel--; + } + level--; + } + } + } + + public K firstKey() { + return head.nextNodes.get(0) != null ? head.nextNodes.get(0).key : null; + } + + public K lastKey() { + int level = maxLevel; + SkipListNode cur = head; + while (level >= 0) { + SkipListNode next = cur.nextNodes.get(level); + while (next != null) { + cur = next; + next = cur.nextNodes.get(level); + } + level--; + } + return cur.key; + } + + public K ceilingKey(K key) { + if (key == null) { + return null; + } + SkipListNode less = mostRightLessNodeInTree(key); + SkipListNode next = less.nextNodes.get(0); + return next != null ? next.key : null; + } + + public K floorKey(K key) { + if (key == null) { + return null; + } + SkipListNode less = mostRightLessNodeInTree(key); + SkipListNode next = less.nextNodes.get(0); + return next != null && next.isKeyEqual(key) ? next.key : less.key; + } + + public int size() { + return size; + } + + } + + // for test + public static void printAll(SkipListMap obj) { + for (int i = obj.maxLevel; i >= 0; i--) { + System.out.print("Level " + i + " : "); + SkipListNode cur = obj.head; + while (cur.nextNodes.get(i) != null) { + SkipListNode next = cur.nextNodes.get(i); + System.out.print("(" + next.key + " , " + next.val + ") "); + cur = next; + } + System.out.println(); + } + } + + public static void main(String[] args) { + SkipListMap test = new SkipListMap<>(); + printAll(test); + System.out.println("======================"); + test.put("A", "10"); + printAll(test); + System.out.println("======================"); + test.remove("A"); + printAll(test); + System.out.println("======================"); + test.put("E", "E"); + test.put("B", "B"); + test.put("A", "A"); + test.put("F", "F"); + test.put("C", "C"); + test.put("D", "D"); + printAll(test); + System.out.println("======================"); + System.out.println(test.containsKey("B")); + System.out.println(test.containsKey("Z")); + System.out.println(test.firstKey()); + System.out.println(test.lastKey()); + System.out.println(test.floorKey("D")); + System.out.println(test.ceilingKey("D")); + System.out.println("======================"); + test.remove("D"); + printAll(test); + System.out.println("======================"); + System.out.println(test.floorKey("D")); + System.out.println(test.ceilingKey("D")); + + + } + +} diff --git a/体系学习班/class37/Code01_CountofRangeSum.java b/体系学习班/class37/Code01_CountofRangeSum.java new file mode 100644 index 0000000..d79fa02 --- /dev/null +++ b/体系学习班/class37/Code01_CountofRangeSum.java @@ -0,0 +1,223 @@ +package class37; + +import java.util.HashSet; + +public class Code01_CountofRangeSum { + + public static int countRangeSum1(int[] nums, int lower, int upper) { + int n = nums.length; + long[] sums = new long[n + 1]; + for (int i = 0; i < n; ++i) + sums[i + 1] = sums[i] + nums[i]; + return countWhileMergeSort(sums, 0, n + 1, lower, upper); + } + + private static int countWhileMergeSort(long[] sums, int start, int end, int lower, int upper) { + if (end - start <= 1) + return 0; + int mid = (start + end) / 2; + int count = countWhileMergeSort(sums, start, mid, lower, upper) + + countWhileMergeSort(sums, mid, end, lower, upper); + int j = mid, k = mid, t = mid; + long[] cache = new long[end - start]; + for (int i = start, r = 0; i < mid; ++i, ++r) { + while (k < end && sums[k] - sums[i] < lower) + k++; + while (j < end && sums[j] - sums[i] <= upper) + j++; + while (t < end && sums[t] < sums[i]) + cache[r++] = sums[t++]; + cache[r] = sums[i]; + count += j - k; + } + System.arraycopy(cache, 0, sums, start, t - start); + return count; + } + + public static class SBTNode { + public long key; + public SBTNode l; + public SBTNode r; + public long size; // 不同key的size + public long all; // 总的size + + public SBTNode(long k) { + key = k; + size = 1; + all = 1; + } + } + + public static class SizeBalancedTreeSet { + private SBTNode root; + private HashSet set = new HashSet<>(); + + private SBTNode rightRotate(SBTNode cur) { + long same = cur.all - (cur.l != null ? cur.l.all : 0) - (cur.r != null ? cur.r.all : 0); + SBTNode leftNode = cur.l; + cur.l = leftNode.r; + leftNode.r = cur; + leftNode.size = cur.size; + cur.size = (cur.l != null ? cur.l.size : 0) + (cur.r != null ? cur.r.size : 0) + 1; + // all modify + leftNode.all = cur.all; + cur.all = (cur.l != null ? cur.l.all : 0) + (cur.r != null ? cur.r.all : 0) + same; + return leftNode; + } + + private SBTNode leftRotate(SBTNode cur) { + long same = cur.all - (cur.l != null ? cur.l.all : 0) - (cur.r != null ? cur.r.all : 0); + SBTNode rightNode = cur.r; + cur.r = rightNode.l; + rightNode.l = cur; + rightNode.size = cur.size; + cur.size = (cur.l != null ? cur.l.size : 0) + (cur.r != null ? cur.r.size : 0) + 1; + // all modify + rightNode.all = cur.all; + cur.all = (cur.l != null ? cur.l.all : 0) + (cur.r != null ? cur.r.all : 0) + same; + return rightNode; + } + + private SBTNode maintain(SBTNode cur) { + if (cur == null) { + return null; + } + long leftSize = cur.l != null ? cur.l.size : 0; + long leftLeftSize = cur.l != null && cur.l.l != null ? cur.l.l.size : 0; + long leftRightSize = cur.l != null && cur.l.r != null ? cur.l.r.size : 0; + long rightSize = cur.r != null ? cur.r.size : 0; + long rightLeftSize = cur.r != null && cur.r.l != null ? cur.r.l.size : 0; + long rightRightSize = cur.r != null && cur.r.r != null ? cur.r.r.size : 0; + if (leftLeftSize > rightSize) { + cur = rightRotate(cur); + cur.r = maintain(cur.r); + cur = maintain(cur); + } else if (leftRightSize > rightSize) { + cur.l = leftRotate(cur.l); + cur = rightRotate(cur); + cur.l = maintain(cur.l); + cur.r = maintain(cur.r); + cur = maintain(cur); + } else if (rightRightSize > leftSize) { + cur = leftRotate(cur); + cur.l = maintain(cur.l); + cur = maintain(cur); + } else if (rightLeftSize > leftSize) { + cur.r = rightRotate(cur.r); + cur = leftRotate(cur); + cur.l = maintain(cur.l); + cur.r = maintain(cur.r); + cur = maintain(cur); + } + return cur; + } + + private SBTNode add(SBTNode cur, long key, boolean contains) { + if (cur == null) { + return new SBTNode(key); + } else { + cur.all++; + if (key == cur.key) { + return cur; + } else { // 还在左滑或者右滑 + if (!contains) { + cur.size++; + } + if (key < cur.key) { + cur.l = add(cur.l, key, contains); + } else { + cur.r = add(cur.r, key, contains); + } + return maintain(cur); + } + } + } + + public void add(long sum) { + boolean contains = set.contains(sum); + root = add(root, sum, contains); + set.add(sum); + } + + public long lessKeySize(long key) { + SBTNode cur = root; + long ans = 0; + while (cur != null) { + if (key == cur.key) { + return ans + (cur.l != null ? cur.l.all : 0); + } else if (key < cur.key) { + cur = cur.l; + } else { + ans += cur.all - (cur.r != null ? cur.r.all : 0); + cur = cur.r; + } + } + return ans; + } + + // > 7 8... + // <8 ...<=7 + public long moreKeySize(long key) { + return root != null ? (root.all - lessKeySize(key + 1)) : 0; + } + + } + + public static int countRangeSum2(int[] nums, int lower, int upper) { + // 黑盒,加入数字(前缀和),不去重,可以接受重复数字 + // < num , 有几个数? + SizeBalancedTreeSet treeSet = new SizeBalancedTreeSet(); + long sum = 0; + int ans = 0; + treeSet.add(0);// 一个数都没有的时候,就已经有一个前缀和累加和为0, + for (int i = 0; i < nums.length; i++) { + sum += nums[i]; + // [sum - upper, sum - lower] + // [10, 20] ? + // < 10 ? < 21 ? + long a = treeSet.lessKeySize(sum - lower + 1); + long b = treeSet.lessKeySize(sum - upper); + ans += a - b; + treeSet.add(sum); + } + return ans; + } + + // for test + public static void printArray(int[] arr) { + for (int i = 0; i < arr.length; i++) { + System.out.print(arr[i] + " "); + } + System.out.println(); + } + + // for test + public static int[] generateArray(int len, int varible) { + int[] arr = new int[len]; + for (int i = 0; i < arr.length; i++) { + arr[i] = (int) (Math.random() * varible); + } + return arr; + } + + public static void main(String[] args) { + int len = 200; + int varible = 50; + for (int i = 0; i < 10000; i++) { + int[] test = generateArray(len, varible); + int lower = (int) (Math.random() * varible) - (int) (Math.random() * varible); + int upper = lower + (int) (Math.random() * varible); + int ans1 = countRangeSum1(test, lower, upper); + int ans2 = countRangeSum2(test, lower, upper); + if (ans1 != ans2) { + printArray(test); + System.out.println(lower); + System.out.println(upper); + System.out.println(ans1); + System.out.println(ans2); + } + } + + } + +} diff --git a/体系学习班/class37/Code02_SlidingWindowMedian.java b/体系学习班/class37/Code02_SlidingWindowMedian.java new file mode 100644 index 0000000..2f3fa63 --- /dev/null +++ b/体系学习班/class37/Code02_SlidingWindowMedian.java @@ -0,0 +1,228 @@ +package class37; + +public class Code02_SlidingWindowMedian { + + public static class SBTNode> { + public K key; + public SBTNode l; + public SBTNode r; + public int size; + + public SBTNode(K k) { + key = k; + size = 1; + } + } + + public static class SizeBalancedTreeMap> { + private SBTNode root; + + private SBTNode rightRotate(SBTNode cur) { + SBTNode leftNode = cur.l; + cur.l = leftNode.r; + leftNode.r = cur; + leftNode.size = cur.size; + cur.size = (cur.l != null ? cur.l.size : 0) + (cur.r != null ? cur.r.size : 0) + 1; + return leftNode; + } + + private SBTNode leftRotate(SBTNode cur) { + SBTNode rightNode = cur.r; + cur.r = rightNode.l; + rightNode.l = cur; + rightNode.size = cur.size; + cur.size = (cur.l != null ? cur.l.size : 0) + (cur.r != null ? cur.r.size : 0) + 1; + return rightNode; + } + + private SBTNode maintain(SBTNode cur) { + if (cur == null) { + return null; + } + int leftSize = cur.l != null ? cur.l.size : 0; + int leftLeftSize = cur.l != null && cur.l.l != null ? cur.l.l.size : 0; + int leftRightSize = cur.l != null && cur.l.r != null ? cur.l.r.size : 0; + int rightSize = cur.r != null ? cur.r.size : 0; + int rightLeftSize = cur.r != null && cur.r.l != null ? cur.r.l.size : 0; + int rightRightSize = cur.r != null && cur.r.r != null ? cur.r.r.size : 0; + if (leftLeftSize > rightSize) { + cur = rightRotate(cur); + cur.r = maintain(cur.r); + cur = maintain(cur); + } else if (leftRightSize > rightSize) { + cur.l = leftRotate(cur.l); + cur = rightRotate(cur); + cur.l = maintain(cur.l); + cur.r = maintain(cur.r); + cur = maintain(cur); + } else if (rightRightSize > leftSize) { + cur = leftRotate(cur); + cur.l = maintain(cur.l); + cur = maintain(cur); + } else if (rightLeftSize > leftSize) { + cur.r = rightRotate(cur.r); + cur = leftRotate(cur); + cur.l = maintain(cur.l); + cur.r = maintain(cur.r); + cur = maintain(cur); + } + return cur; + } + + private SBTNode findLastIndex(K key) { + SBTNode pre = root; + SBTNode cur = root; + while (cur != null) { + pre = cur; + if (key.compareTo(cur.key) == 0) { + break; + } else if (key.compareTo(cur.key) < 0) { + cur = cur.l; + } else { + cur = cur.r; + } + } + return pre; + } + + private SBTNode add(SBTNode cur, K key) { + if (cur == null) { + return new SBTNode(key); + } else { + cur.size++; + if (key.compareTo(cur.key) < 0) { + cur.l = add(cur.l, key); + } else { + cur.r = add(cur.r, key); + } + return maintain(cur); + } + } + + private SBTNode delete(SBTNode cur, K key) { + cur.size--; + if (key.compareTo(cur.key) > 0) { + cur.r = delete(cur.r, key); + } else if (key.compareTo(cur.key) < 0) { + cur.l = delete(cur.l, key); + } else { + if (cur.l == null && cur.r == null) { + // free cur memory -> C++ + cur = null; + } else if (cur.l == null && cur.r != null) { + // free cur memory -> C++ + cur = cur.r; + } else if (cur.l != null && cur.r == null) { + // free cur memory -> C++ + cur = cur.l; + } else { + SBTNode pre = null; + SBTNode des = cur.r; + des.size--; + while (des.l != null) { + pre = des; + des = des.l; + des.size--; + } + if (pre != null) { + pre.l = des.r; + des.r = cur.r; + } + des.l = cur.l; + des.size = des.l.size + (des.r == null ? 0 : des.r.size) + 1; + // free cur memory -> C++ + cur = des; + } + } + return cur; + } + + private SBTNode getIndex(SBTNode cur, int kth) { + if (kth == (cur.l != null ? cur.l.size : 0) + 1) { + return cur; + } else if (kth <= (cur.l != null ? cur.l.size : 0)) { + return getIndex(cur.l, kth); + } else { + return getIndex(cur.r, kth - (cur.l != null ? cur.l.size : 0) - 1); + } + } + + public int size() { + return root == null ? 0 : root.size; + } + + public boolean containsKey(K key) { + if (key == null) { + throw new RuntimeException("invalid parameter."); + } + SBTNode lastNode = findLastIndex(key); + return lastNode != null && key.compareTo(lastNode.key) == 0 ? true : false; + } + + public void add(K key) { + if (key == null) { + throw new RuntimeException("invalid parameter."); + } + SBTNode lastNode = findLastIndex(key); + if (lastNode == null || key.compareTo(lastNode.key) != 0) { + root = add(root, key); + } + } + + public void remove(K key) { + if (key == null) { + throw new RuntimeException("invalid parameter."); + } + if (containsKey(key)) { + root = delete(root, key); + } + } + + public K getIndexKey(int index) { + if (index < 0 || index >= this.size()) { + throw new RuntimeException("invalid parameter."); + } + return getIndex(root, index + 1).key; + } + + } + + public static class Node implements Comparable { + public int index; + public int value; + + public Node(int i, int v) { + index = i; + value = v; + } + + @Override + public int compareTo(Node o) { + return value != o.value ? Integer.valueOf(value).compareTo(o.value) + : Integer.valueOf(index).compareTo(o.index); + } + } + + public static double[] medianSlidingWindow(int[] nums, int k) { + SizeBalancedTreeMap map = new SizeBalancedTreeMap<>(); + for (int i = 0; i < k - 1; i++) { + map.add(new Node(i, nums[i])); + } + double[] ans = new double[nums.length - k + 1]; + int index = 0; + for (int i = k - 1; i < nums.length; i++) { + map.add(new Node(i, nums[i])); + if (map.size() % 2 == 0) { + Node upmid = map.getIndexKey(map.size() / 2 - 1); + Node downmid = map.getIndexKey(map.size() / 2); + ans[index++] = ((double) upmid.value + (double) downmid.value) / 2; + } else { + Node mid = map.getIndexKey(map.size() / 2); + ans[index++] = (double) mid.value; + } + map.remove(new Node(i - k + 1, nums[i - k + 1])); + } + return ans; + } + +} diff --git a/体系学习班/class37/Code03_AddRemoveGetIndexGreat.java b/体系学习班/class37/Code03_AddRemoveGetIndexGreat.java new file mode 100644 index 0000000..2c2436a --- /dev/null +++ b/体系学习班/class37/Code03_AddRemoveGetIndexGreat.java @@ -0,0 +1,259 @@ +package class37; + +import java.util.ArrayList; + +public class Code03_AddRemoveGetIndexGreat { + + public static class SBTNode { + public V value; + public SBTNode l; + public SBTNode r; + public int size; + + public SBTNode(V v) { + value = v; + size = 1; + } + } + + public static class SbtList { + private SBTNode root; + + private SBTNode rightRotate(SBTNode cur) { + SBTNode leftNode = cur.l; + cur.l = leftNode.r; + leftNode.r = cur; + leftNode.size = cur.size; + cur.size = (cur.l != null ? cur.l.size : 0) + (cur.r != null ? cur.r.size : 0) + 1; + return leftNode; + } + + private SBTNode leftRotate(SBTNode cur) { + SBTNode rightNode = cur.r; + cur.r = rightNode.l; + rightNode.l = cur; + rightNode.size = cur.size; + cur.size = (cur.l != null ? cur.l.size : 0) + (cur.r != null ? cur.r.size : 0) + 1; + return rightNode; + } + + private SBTNode maintain(SBTNode cur) { + if (cur == null) { + return null; + } + int leftSize = cur.l != null ? cur.l.size : 0; + int leftLeftSize = cur.l != null && cur.l.l != null ? cur.l.l.size : 0; + int leftRightSize = cur.l != null && cur.l.r != null ? cur.l.r.size : 0; + int rightSize = cur.r != null ? cur.r.size : 0; + int rightLeftSize = cur.r != null && cur.r.l != null ? cur.r.l.size : 0; + int rightRightSize = cur.r != null && cur.r.r != null ? cur.r.r.size : 0; + if (leftLeftSize > rightSize) { + cur = rightRotate(cur); + cur.r = maintain(cur.r); + cur = maintain(cur); + } else if (leftRightSize > rightSize) { + cur.l = leftRotate(cur.l); + cur = rightRotate(cur); + cur.l = maintain(cur.l); + cur.r = maintain(cur.r); + cur = maintain(cur); + } else if (rightRightSize > leftSize) { + cur = leftRotate(cur); + cur.l = maintain(cur.l); + cur = maintain(cur); + } else if (rightLeftSize > leftSize) { + cur.r = rightRotate(cur.r); + cur = leftRotate(cur); + cur.l = maintain(cur.l); + cur.r = maintain(cur.r); + cur = maintain(cur); + } + return cur; + } + + private SBTNode add(SBTNode root, int index, SBTNode cur) { + if (root == null) { + return cur; + } + root.size++; + int leftAndHeadSize = (root.l != null ? root.l.size : 0) + 1; + if (index < leftAndHeadSize) { + root.l = add(root.l, index, cur); + } else { + root.r = add(root.r, index - leftAndHeadSize, cur); + } + root = maintain(root); + return root; + } + + private SBTNode remove(SBTNode root, int index) { + root.size--; + int rootIndex = root.l != null ? root.l.size : 0; + if (index != rootIndex) { + if (index < rootIndex) { + root.l = remove(root.l, index); + } else { + root.r = remove(root.r, index - rootIndex - 1); + } + return root; + } + if (root.l == null && root.r == null) { + return null; + } + if (root.l == null) { + return root.r; + } + if (root.r == null) { + return root.l; + } + SBTNode pre = null; + SBTNode suc = root.r; + suc.size--; + while (suc.l != null) { + pre = suc; + suc = suc.l; + suc.size--; + } + if (pre != null) { + pre.l = suc.r; + suc.r = root.r; + } + suc.l = root.l; + suc.size = suc.l.size + (suc.r == null ? 0 : suc.r.size) + 1; + return suc; + } + + private SBTNode get(SBTNode root, int index) { + int leftSize = root.l != null ? root.l.size : 0; + if (index < leftSize) { + return get(root.l, index); + } else if (index == leftSize) { + return root; + } else { + return get(root.r, index - leftSize - 1); + } + } + + public void add(int index, V num) { + SBTNode cur = new SBTNode(num); + if (root == null) { + root = cur; + } else { + if (index <= root.size) { + root = add(root, index, cur); + } + } + } + + public V get(int index) { + SBTNode ans = get(root, index); + return ans.value; + } + + public void remove(int index) { + if (index >= 0 && size() > index) { + root = remove(root, index); + } + } + + public int size() { + return root == null ? 0 : root.size; + } + + } + + // 通过以下这个测试, + // 可以很明显的看到LinkedList的插入、删除、get效率不如SbtList + // LinkedList需要找到index所在的位置之后才能插入或者读取,时间复杂度O(N) + // SbtList是平衡搜索二叉树,所以插入或者读取时间复杂度都是O(logN) + public static void main(String[] args) { + // 功能测试 + int test = 50000; + int max = 1000000; + boolean pass = true; + ArrayList list = new ArrayList<>(); + SbtList sbtList = new SbtList<>(); + for (int i = 0; i < test; i++) { + if (list.size() != sbtList.size()) { + pass = false; + break; + } + if (list.size() > 1 && Math.random() < 0.5) { + int removeIndex = (int) (Math.random() * list.size()); + list.remove(removeIndex); + sbtList.remove(removeIndex); + } else { + int randomIndex = (int) (Math.random() * (list.size() + 1)); + int randomValue = (int) (Math.random() * (max + 1)); + list.add(randomIndex, randomValue); + sbtList.add(randomIndex, randomValue); + } + } + for (int i = 0; i < list.size(); i++) { + if (!list.get(i).equals(sbtList.get(i))) { + pass = false; + break; + } + } + System.out.println("功能测试是否通过 : " + pass); + + // 性能测试 + test = 500000; + list = new ArrayList<>(); + sbtList = new SbtList<>(); + long start = 0; + long end = 0; + + start = System.currentTimeMillis(); + for (int i = 0; i < test; i++) { + int randomIndex = (int) (Math.random() * (list.size() + 1)); + int randomValue = (int) (Math.random() * (max + 1)); + list.add(randomIndex, randomValue); + } + end = System.currentTimeMillis(); + System.out.println("ArrayList插入总时长(毫秒) : " + (end - start)); + + start = System.currentTimeMillis(); + for (int i = 0; i < test; i++) { + int randomIndex = (int) (Math.random() * (i + 1)); + list.get(randomIndex); + } + end = System.currentTimeMillis(); + System.out.println("ArrayList读取总时长(毫秒) : " + (end - start)); + + start = System.currentTimeMillis(); + for (int i = 0; i < test; i++) { + int randomIndex = (int) (Math.random() * list.size()); + list.remove(randomIndex); + } + end = System.currentTimeMillis(); + System.out.println("ArrayList删除总时长(毫秒) : " + (end - start)); + + start = System.currentTimeMillis(); + for (int i = 0; i < test; i++) { + int randomIndex = (int) (Math.random() * (sbtList.size() + 1)); + int randomValue = (int) (Math.random() * (max + 1)); + sbtList.add(randomIndex, randomValue); + } + end = System.currentTimeMillis(); + System.out.println("SbtList插入总时长(毫秒) : " + (end - start)); + + start = System.currentTimeMillis(); + for (int i = 0; i < test; i++) { + int randomIndex = (int) (Math.random() * (i + 1)); + sbtList.get(randomIndex); + } + end = System.currentTimeMillis(); + System.out.println("SbtList读取总时长(毫秒) : " + (end - start)); + + start = System.currentTimeMillis(); + for (int i = 0; i < test; i++) { + int randomIndex = (int) (Math.random() * sbtList.size()); + sbtList.remove(randomIndex); + } + end = System.currentTimeMillis(); + System.out.println("SbtList删除总时长(毫秒) : " + (end - start)); + + } + +} diff --git a/体系学习班/class37/Code04_QueueReconstructionByHeight.java b/体系学习班/class37/Code04_QueueReconstructionByHeight.java new file mode 100644 index 0000000..2513402 --- /dev/null +++ b/体系学习班/class37/Code04_QueueReconstructionByHeight.java @@ -0,0 +1,265 @@ +package class37; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.LinkedList; + +// 本题测试链接:https://leetcode.com/problems/queue-reconstruction-by-height/ +public class Code04_QueueReconstructionByHeight { + + public static int[][] reconstructQueue1(int[][] people) { + int N = people.length; + Unit[] units = new Unit[N]; + for (int i = 0; i < N; i++) { + units[i] = new Unit(people[i][0], people[i][1]); + } + Arrays.sort(units, new UnitComparator()); + ArrayList arrList = new ArrayList<>(); + for (Unit unit : units) { + arrList.add(unit.k, unit); + } + int[][] ans = new int[N][2]; + int index = 0; + for (Unit unit : arrList) { + ans[index][0] = unit.h; + ans[index++][1] = unit.k; + } + return ans; + } + + public static int[][] reconstructQueue2(int[][] people) { + int N = people.length; + Unit[] units = new Unit[N]; + for (int i = 0; i < N; i++) { + units[i] = new Unit(people[i][0], people[i][1]); + } + Arrays.sort(units, new UnitComparator()); + SBTree tree = new SBTree(); + for (int i = 0; i < N; i++) { + tree.insert(units[i].k, i); + } + LinkedList allIndexes = tree.allIndexes(); + int[][] ans = new int[N][2]; + int index = 0; + for (Integer arri : allIndexes) { + ans[index][0] = units[arri].h; + ans[index++][1] = units[arri].k; + } + return ans; + } + + public static class Unit { + public int h; + public int k; + + public Unit(int height, int greater) { + h = height; + k = greater; + } + } + + public static class UnitComparator implements Comparator { + + @Override + public int compare(Unit o1, Unit o2) { + return o1.h != o2.h ? (o2.h - o1.h) : (o1.k - o2.k); + } + + } + + public static class SBTNode { + public int value; + public SBTNode l; + public SBTNode r; + public int size; + + public SBTNode(int arrIndex) { + value = arrIndex; + size = 1; + } + } + + public static class SBTree { + private SBTNode root; + + private SBTNode rightRotate(SBTNode cur) { + SBTNode leftNode = cur.l; + cur.l = leftNode.r; + leftNode.r = cur; + leftNode.size = cur.size; + cur.size = (cur.l != null ? cur.l.size : 0) + (cur.r != null ? cur.r.size : 0) + 1; + return leftNode; + } + + private SBTNode leftRotate(SBTNode cur) { + SBTNode rightNode = cur.r; + cur.r = rightNode.l; + rightNode.l = cur; + rightNode.size = cur.size; + cur.size = (cur.l != null ? cur.l.size : 0) + (cur.r != null ? cur.r.size : 0) + 1; + return rightNode; + } + + private SBTNode maintain(SBTNode cur) { + if (cur == null) { + return null; + } + int leftSize = cur.l != null ? cur.l.size : 0; + int leftLeftSize = cur.l != null && cur.l.l != null ? cur.l.l.size : 0; + int leftRightSize = cur.l != null && cur.l.r != null ? cur.l.r.size : 0; + int rightSize = cur.r != null ? cur.r.size : 0; + int rightLeftSize = cur.r != null && cur.r.l != null ? cur.r.l.size : 0; + int rightRightSize = cur.r != null && cur.r.r != null ? cur.r.r.size : 0; + if (leftLeftSize > rightSize) { + cur = rightRotate(cur); + cur.r = maintain(cur.r); + cur = maintain(cur); + } else if (leftRightSize > rightSize) { + cur.l = leftRotate(cur.l); + cur = rightRotate(cur); + cur.l = maintain(cur.l); + cur.r = maintain(cur.r); + cur = maintain(cur); + } else if (rightRightSize > leftSize) { + cur = leftRotate(cur); + cur.l = maintain(cur.l); + cur = maintain(cur); + } else if (rightLeftSize > leftSize) { + cur.r = rightRotate(cur.r); + cur = leftRotate(cur); + cur.l = maintain(cur.l); + cur.r = maintain(cur.r); + cur = maintain(cur); + } + return cur; + } + + private SBTNode insert(SBTNode root, int index, SBTNode cur) { + if (root == null) { + return cur; + } + root.size++; + int leftAndHeadSize = (root.l != null ? root.l.size : 0) + 1; + if (index < leftAndHeadSize) { + root.l = insert(root.l, index, cur); + } else { + root.r = insert(root.r, index - leftAndHeadSize, cur); + } + root = maintain(root); + return root; + } + + private SBTNode get(SBTNode root, int index) { + int leftSize = root.l != null ? root.l.size : 0; + if (index < leftSize) { + return get(root.l, index); + } else if (index == leftSize) { + return root; + } else { + return get(root.r, index - leftSize - 1); + } + } + + private void process(SBTNode head, LinkedList indexes) { + if (head == null) { + return; + } + process(head.l, indexes); + indexes.addLast(head.value); + process(head.r, indexes); + } + + public void insert(int index, int value) { + SBTNode cur = new SBTNode(value); + if (root == null) { + root = cur; + } else { + if (index <= root.size) { + root = insert(root, index, cur); + } + } + } + + public int get(int index) { + SBTNode ans = get(root, index); + return ans.value; + } + + public LinkedList allIndexes() { + LinkedList indexes = new LinkedList<>(); + process(root, indexes); + return indexes; + } + + } + + // 通过以下这个测试, + // 可以很明显的看到LinkedList的插入和get效率不如SBTree + // LinkedList需要找到index所在的位置之后才能插入或者读取,时间复杂度O(N) + // SBTree是平衡搜索二叉树,所以插入或者读取时间复杂度都是O(logN) + public static void main(String[] args) { + // 功能测试 + int test = 10000; + int max = 1000000; + boolean pass = true; + LinkedList list = new LinkedList<>(); + SBTree sbtree = new SBTree(); + for (int i = 0; i < test; i++) { + int randomIndex = (int) (Math.random() * (i + 1)); + int randomValue = (int) (Math.random() * (max + 1)); + list.add(randomIndex, randomValue); + sbtree.insert(randomIndex, randomValue); + } + for (int i = 0; i < test; i++) { + if (list.get(i) != sbtree.get(i)) { + pass = false; + break; + } + } + System.out.println("功能测试是否通过 : " + pass); + + // 性能测试 + test = 50000; + list = new LinkedList<>(); + sbtree = new SBTree(); + long start = 0; + long end = 0; + + start = System.currentTimeMillis(); + for (int i = 0; i < test; i++) { + int randomIndex = (int) (Math.random() * (i + 1)); + int randomValue = (int) (Math.random() * (max + 1)); + list.add(randomIndex, randomValue); + } + end = System.currentTimeMillis(); + System.out.println("LinkedList插入总时长(毫秒) : " + (end - start)); + + start = System.currentTimeMillis(); + for (int i = 0; i < test; i++) { + int randomIndex = (int) (Math.random() * (i + 1)); + list.get(randomIndex); + } + end = System.currentTimeMillis(); + System.out.println("LinkedList读取总时长(毫秒) : " + (end - start)); + + start = System.currentTimeMillis(); + for (int i = 0; i < test; i++) { + int randomIndex = (int) (Math.random() * (i + 1)); + int randomValue = (int) (Math.random() * (max + 1)); + sbtree.insert(randomIndex, randomValue); + } + end = System.currentTimeMillis(); + System.out.println("SBTree插入总时长(毫秒) : " + (end - start)); + + start = System.currentTimeMillis(); + for (int i = 0; i < test; i++) { + int randomIndex = (int) (Math.random() * (i + 1)); + sbtree.get(randomIndex); + } + end = System.currentTimeMillis(); + System.out.println("SBTree读取总时长(毫秒) : " + (end - start)); + + } + +} diff --git a/体系学习班/class37/Compare.java b/体系学习班/class37/Compare.java new file mode 100644 index 0000000..5bbfd0d --- /dev/null +++ b/体系学习班/class37/Compare.java @@ -0,0 +1,408 @@ +package class37; + +import java.util.TreeMap; + +import class35.Code01_AVLTreeMap.AVLTreeMap; +import class36.Code01_SizeBalancedTreeMap.SizeBalancedTreeMap; +import class36.Code02_SkipListMap.SkipListMap; + +// 本文件为avl、sbt、skiplist三种结构的测试文件 +public class Compare { + + public static void functionTest() { + System.out.println("功能测试开始"); + TreeMap treeMap = new TreeMap<>(); + AVLTreeMap avl = new AVLTreeMap<>(); + SizeBalancedTreeMap sbt = new SizeBalancedTreeMap<>(); + SkipListMap skip = new SkipListMap<>(); + int maxK = 500; + int maxV = 50000; + int testTime = 1000000; + for (int i = 0; i < testTime; i++) { + int addK = (int) (Math.random() * maxK); + int addV = (int) (Math.random() * maxV); + treeMap.put(addK, addV); + avl.put(addK, addV); + sbt.put(addK, addV); + skip.put(addK, addV); + + int removeK = (int) (Math.random() * maxK); + treeMap.remove(removeK); + avl.remove(removeK); + sbt.remove(removeK); + skip.remove(removeK); + + int querryK = (int) (Math.random() * maxK); + if (treeMap.containsKey(querryK) != avl.containsKey(querryK) + || sbt.containsKey(querryK) != skip.containsKey(querryK) + || treeMap.containsKey(querryK) != sbt.containsKey(querryK)) { + System.out.println("containsKey Oops"); + System.out.println(treeMap.containsKey(querryK)); + System.out.println(avl.containsKey(querryK)); + System.out.println(sbt.containsKey(querryK)); + System.out.println(skip.containsKey(querryK)); + break; + } + + if (treeMap.containsKey(querryK)) { + int v1 = treeMap.get(querryK); + int v2 = avl.get(querryK); + int v3 = sbt.get(querryK); + int v4 = skip.get(querryK); + if (v1 != v2 || v3 != v4 || v1 != v3) { + System.out.println("get Oops"); + System.out.println(treeMap.get(querryK)); + System.out.println(avl.get(querryK)); + System.out.println(sbt.get(querryK)); + System.out.println(skip.get(querryK)); + break; + } + Integer f1 = treeMap.floorKey(querryK); + Integer f2 = avl.floorKey(querryK); + Integer f3 = sbt.floorKey(querryK); + Integer f4 = skip.floorKey(querryK); + if (f1 == null && (f2 != null || f3 != null || f4 != null)) { + System.out.println("floorKey Oops"); + System.out.println(treeMap.floorKey(querryK)); + System.out.println(avl.floorKey(querryK)); + System.out.println(sbt.floorKey(querryK)); + System.out.println(skip.floorKey(querryK)); + break; + } + if (f1 != null && (f2 == null || f3 == null || f4 == null)) { + System.out.println("floorKey Oops"); + System.out.println(treeMap.floorKey(querryK)); + System.out.println(avl.floorKey(querryK)); + System.out.println(sbt.floorKey(querryK)); + System.out.println(skip.floorKey(querryK)); + break; + } + if (f1 != null) { + int ans1 = f1; + int ans2 = f2; + int ans3 = f3; + int ans4 = f4; + if (ans1 != ans2 || ans3 != ans4 || ans1 != ans3) { + System.out.println("floorKey Oops"); + System.out.println(treeMap.floorKey(querryK)); + System.out.println(avl.floorKey(querryK)); + System.out.println(sbt.floorKey(querryK)); + System.out.println(skip.floorKey(querryK)); + break; + } + } + f1 = treeMap.ceilingKey(querryK); + f2 = avl.ceilingKey(querryK); + f3 = sbt.ceilingKey(querryK); + f4 = skip.ceilingKey(querryK); + if (f1 == null && (f2 != null || f3 != null || f4 != null)) { + System.out.println("ceilingKey Oops"); + System.out.println(treeMap.ceilingKey(querryK)); + System.out.println(avl.ceilingKey(querryK)); + System.out.println(sbt.ceilingKey(querryK)); + System.out.println(skip.ceilingKey(querryK)); + break; + } + if (f1 != null && (f2 == null || f3 == null || f4 == null)) { + System.out.println("ceilingKey Oops"); + System.out.println(treeMap.ceilingKey(querryK)); + System.out.println(avl.ceilingKey(querryK)); + System.out.println(sbt.ceilingKey(querryK)); + System.out.println(skip.ceilingKey(querryK)); + break; + } + if (f1 != null) { + int ans1 = f1; + int ans2 = f2; + int ans3 = f3; + int ans4 = f4; + if (ans1 != ans2 || ans3 != ans4 || ans1 != ans3) { + System.out.println("ceilingKey Oops"); + System.out.println(treeMap.ceilingKey(querryK)); + System.out.println(avl.ceilingKey(querryK)); + System.out.println(sbt.ceilingKey(querryK)); + System.out.println(skip.ceilingKey(querryK)); + break; + } + } + + } + + Integer f1 = treeMap.firstKey(); + Integer f2 = avl.firstKey(); + Integer f3 = sbt.firstKey(); + Integer f4 = skip.firstKey(); + if (f1 == null && (f2 != null || f3 != null || f4 != null)) { + System.out.println("firstKey Oops"); + System.out.println(treeMap.firstKey()); + System.out.println(avl.firstKey()); + System.out.println(sbt.firstKey()); + System.out.println(skip.firstKey()); + break; + } + if (f1 != null && (f2 == null || f3 == null || f4 == null)) { + System.out.println("firstKey Oops"); + System.out.println(treeMap.firstKey()); + System.out.println(avl.firstKey()); + System.out.println(sbt.firstKey()); + System.out.println(skip.firstKey()); + break; + } + if (f1 != null) { + int ans1 = f1; + int ans2 = f2; + int ans3 = f3; + int ans4 = f4; + if (ans1 != ans2 || ans3 != ans4 || ans1 != ans3) { + System.out.println("firstKey Oops"); + System.out.println(treeMap.firstKey()); + System.out.println(avl.firstKey()); + System.out.println(sbt.firstKey()); + System.out.println(skip.firstKey()); + break; + } + } + + f1 = treeMap.lastKey(); + f2 = avl.lastKey(); + f3 = sbt.lastKey(); + f4 = skip.lastKey(); + if (f1 == null && (f2 != null || f3 != null || f4 != null)) { + System.out.println("lastKey Oops"); + System.out.println(treeMap.lastKey()); + System.out.println(avl.lastKey()); + System.out.println(sbt.lastKey()); + System.out.println(skip.lastKey()); + break; + } + if (f1 != null && (f2 == null || f3 == null || f4 == null)) { + System.out.println("firstKey Oops"); + System.out.println(treeMap.lastKey()); + System.out.println(avl.lastKey()); + System.out.println(sbt.lastKey()); + System.out.println(skip.lastKey()); + break; + } + if (f1 != null) { + int ans1 = f1; + int ans2 = f2; + int ans3 = f3; + int ans4 = f4; + if (ans1 != ans2 || ans3 != ans4 || ans1 != ans3) { + System.out.println("lastKey Oops"); + System.out.println(treeMap.lastKey()); + System.out.println(avl.lastKey()); + System.out.println(sbt.lastKey()); + System.out.println(skip.lastKey()); + break; + } + } + if (treeMap.size() != avl.size() || sbt.size() != skip.size() || treeMap.size() != sbt.size()) { + System.out.println("size Oops"); + System.out.println(treeMap.size()); + System.out.println(avl.size()); + System.out.println(sbt.size()); + System.out.println(skip.size()); + break; + } + } + System.out.println("功能测试结束"); + } + + public static void performanceTest() { + System.out.println("性能测试开始"); + TreeMap treeMap; + AVLTreeMap avl; + SizeBalancedTreeMap sbt; + SkipListMap skip; + long start; + long end; + int max = 1000000; + treeMap = new TreeMap<>(); + avl = new AVLTreeMap<>(); + sbt = new SizeBalancedTreeMap<>(); + skip = new SkipListMap<>(); + System.out.println("顺序递增加入测试,数据规模 : " + max); + start = System.currentTimeMillis(); + for (int i = 0; i < max; i++) { + treeMap.put(i, i); + } + end = System.currentTimeMillis(); + System.out.println("treeMap 运行时间 : " + (end - start) + "ms"); + + start = System.currentTimeMillis(); + for (int i = 0; i < max; i++) { + avl.put(i, i); + } + end = System.currentTimeMillis(); + System.out.println("avl 运行时间 : " + (end - start) + "ms"); + + start = System.currentTimeMillis(); + for (int i = 0; i < max; i++) { + sbt.put(i, i); + } + end = System.currentTimeMillis(); + System.out.println("sbt 运行时间 : " + (end - start) + "ms"); + + start = System.currentTimeMillis(); + for (int i = 0; i < max; i++) { + skip.put(i, i); + } + end = System.currentTimeMillis(); + System.out.println("skip 运行时间 : " + (end - start) + "ms"); + + System.out.println("顺序递增删除测试,数据规模 : " + max); + start = System.currentTimeMillis(); + for (int i = 0; i < max; i++) { + treeMap.remove(i); + } + end = System.currentTimeMillis(); + System.out.println("treeMap 运行时间 : " + (end - start) + "ms"); + + start = System.currentTimeMillis(); + for (int i = 0; i < max; i++) { + avl.remove(i); + } + end = System.currentTimeMillis(); + System.out.println("avl 运行时间 : " + (end - start) + "ms"); + + start = System.currentTimeMillis(); + for (int i = 0; i < max; i++) { + sbt.remove(i); + } + end = System.currentTimeMillis(); + System.out.println("sbt 运行时间 : " + (end - start) + "ms"); + + start = System.currentTimeMillis(); + for (int i = 0; i < max; i++) { + skip.remove(i); + } + end = System.currentTimeMillis(); + System.out.println("skip 运行时间 : " + (end - start) + "ms"); + + System.out.println("顺序递减加入测试,数据规模 : " + max); + start = System.currentTimeMillis(); + for (int i = max; i >= 0; i--) { + treeMap.put(i, i); + } + end = System.currentTimeMillis(); + System.out.println("treeMap 运行时间 : " + (end - start) + "ms"); + + start = System.currentTimeMillis(); + for (int i = max; i >= 0; i--) { + avl.put(i, i); + } + end = System.currentTimeMillis(); + System.out.println("avl 运行时间 : " + (end - start) + "ms"); + + start = System.currentTimeMillis(); + for (int i = max; i >= 0; i--) { + sbt.put(i, i); + } + end = System.currentTimeMillis(); + System.out.println("sbt 运行时间 : " + (end - start) + "ms"); + + start = System.currentTimeMillis(); + for (int i = max; i >= 0; i--) { + skip.put(i, i); + } + end = System.currentTimeMillis(); + System.out.println("skip 运行时间 : " + (end - start) + "ms"); + + System.out.println("顺序递减删除测试,数据规模 : " + max); + start = System.currentTimeMillis(); + for (int i = max; i >= 0; i--) { + treeMap.remove(i); + } + end = System.currentTimeMillis(); + System.out.println("treeMap 运行时间 : " + (end - start) + "ms"); + + start = System.currentTimeMillis(); + for (int i = max; i >= 0; i--) { + avl.remove(i); + } + end = System.currentTimeMillis(); + System.out.println("avl 运行时间 : " + (end - start) + "ms"); + + start = System.currentTimeMillis(); + for (int i = max; i >= 0; i--) { + sbt.remove(i); + } + end = System.currentTimeMillis(); + System.out.println("sbt 运行时间 : " + (end - start) + "ms"); + + start = System.currentTimeMillis(); + for (int i = max; i >= 0; i--) { + skip.remove(i); + } + end = System.currentTimeMillis(); + System.out.println("skip 运行时间 : " + (end - start) + "ms"); + + System.out.println("随机加入测试,数据规模 : " + max); + start = System.currentTimeMillis(); + for (int i = 0; i < max; i++) { + treeMap.put((int) (Math.random() * i), i); + } + end = System.currentTimeMillis(); + System.out.println("treeMap 运行时间 : " + (end - start) + "ms"); + + start = System.currentTimeMillis(); + for (int i = max; i >= 0; i--) { + avl.put((int) (Math.random() * i), i); + } + end = System.currentTimeMillis(); + System.out.println("avl 运行时间 : " + (end - start) + "ms"); + + start = System.currentTimeMillis(); + for (int i = max; i >= 0; i--) { + sbt.put((int) (Math.random() * i), i); + } + end = System.currentTimeMillis(); + System.out.println("sbt 运行时间 : " + (end - start) + "ms"); + + start = System.currentTimeMillis(); + for (int i = max; i >= 0; i--) { + skip.put((int) (Math.random() * i), i); + } + end = System.currentTimeMillis(); + System.out.println("skip 运行时间 : " + (end - start) + "ms"); + + System.out.println("随机删除测试,数据规模 : " + max); + start = System.currentTimeMillis(); + for (int i = 0; i < max; i++) { + treeMap.remove((int) (Math.random() * i)); + } + end = System.currentTimeMillis(); + System.out.println("treeMap 运行时间 : " + (end - start) + "ms"); + + start = System.currentTimeMillis(); + for (int i = max; i >= 0; i--) { + avl.remove((int) (Math.random() * i)); + } + end = System.currentTimeMillis(); + System.out.println("avl 运行时间 : " + (end - start) + "ms"); + + start = System.currentTimeMillis(); + for (int i = max; i >= 0; i--) { + sbt.remove((int) (Math.random() * i)); + } + end = System.currentTimeMillis(); + System.out.println("sbt 运行时间 : " + (end - start) + "ms"); + + start = System.currentTimeMillis(); + for (int i = max; i >= 0; i--) { + skip.remove((int) (Math.random() * i)); + } + end = System.currentTimeMillis(); + System.out.println("skip 运行时间 : " + (end - start) + "ms"); + + System.out.println("性能测试结束"); + } + + public static void main(String[] args) { + functionTest(); + System.out.println("======"); + performanceTest(); + } + +} diff --git a/体系学习班/class38/Code01_AppleMinBags.java b/体系学习班/class38/Code01_AppleMinBags.java new file mode 100644 index 0000000..e1b5f21 --- /dev/null +++ b/体系学习班/class38/Code01_AppleMinBags.java @@ -0,0 +1,41 @@ +package class38; + +public class Code01_AppleMinBags { + + public static int minBags(int apple) { + if (apple < 0) { + return -1; + } + int bag8 = (apple >> 3); + int rest = apple - (bag8 << 3); + while(bag8 >= 0) { + // rest 个 + if(rest % 6 ==0) { + return bag8 + (rest / 6); + } else { + bag8--; + rest += 8; + } + } + return -1; + } + + public static int minBagAwesome(int apple) { + if ((apple & 1) != 0) { // 如果是奇数,返回-1 + return -1; + } + if (apple < 18) { + return apple == 0 ? 0 : (apple == 6 || apple == 8) ? 1 + : (apple == 12 || apple == 14 || apple == 16) ? 2 : -1; + } + return (apple - 18) / 8 + 3; + } + + public static void main(String[] args) { + for(int apple = 1; apple < 200;apple++) { + System.out.println(apple + " : "+ minBags(apple)); + } + + } + +} diff --git a/体系学习班/class38/Code02_EatGrass.java b/体系学习班/class38/Code02_EatGrass.java new file mode 100644 index 0000000..8b400fb --- /dev/null +++ b/体系学习班/class38/Code02_EatGrass.java @@ -0,0 +1,57 @@ +package class38; + +public class Code02_EatGrass { + + // 如果n份草,最终先手赢,返回"先手" + // 如果n份草,最终后手赢,返回"后手" + public static String whoWin(int n) { + if (n < 5) { + return n == 0 || n == 2 ? "后手" : "先手"; + } + // 进到这个过程里来,当前的先手,先选 + int want = 1; + while (want <= n) { + if (whoWin(n - want).equals("后手")) { + return "先手"; + } + if (want <= (n / 4)) { + want *= 4; + } else { + break; + } + } + return "后手"; + } + + public static String winner1(int n) { + if (n < 5) { + return (n == 0 || n == 2) ? "后手" : "先手"; + } + int base = 1; + while (base <= n) { + if (winner1(n - base).equals("后手")) { + return "先手"; + } + if (base > n / 4) { // 防止base*4之后溢出 + break; + } + base *= 4; + } + return "后手"; + } + + public static String winner2(int n) { + if (n % 5 == 0 || n % 5 == 2) { + return "后手"; + } else { + return "先手"; + } + } + + public static void main(String[] args) { + for (int i = 0; i <= 50; i++) { + System.out.println(i + " : " + whoWin(i)); + } + } + +} diff --git a/体系学习班/class38/Code03_MSumToN.java b/体系学习班/class38/Code03_MSumToN.java new file mode 100644 index 0000000..50c72be --- /dev/null +++ b/体系学习班/class38/Code03_MSumToN.java @@ -0,0 +1,44 @@ +package class38; + +public class Code03_MSumToN { + + public static boolean isMSum1(int num) { + for (int start = 1; start <= num; start++) { + int sum = start; + for (int j = start + 1; j <= num; j++) { + if (sum + j > num) { + break; + } + if (sum + j == num) { + return true; + } + sum += j; + } + } + return false; + } + + public static boolean isMSum2(int num) { +// +// return num == (num & (~num + 1)); +// +// return num == (num & (-num)); +// +// + return (num & (num - 1)) != 0; + } + + public static void main(String[] args) { + for (int num = 1; num < 200; num++) { + System.out.println(num + " : " + isMSum1(num)); + } + System.out.println("test begin"); + for (int num = 1; num < 5000; num++) { + if (isMSum1(num) != isMSum2(num)) { + System.out.println("Oops!"); + } + } + System.out.println("test end"); + + } +} diff --git a/体系学习班/class38/Code04_MoneyProblem.java b/体系学习班/class38/Code04_MoneyProblem.java new file mode 100644 index 0000000..722c5a6 --- /dev/null +++ b/体系学习班/class38/Code04_MoneyProblem.java @@ -0,0 +1,169 @@ +package class38; + +public class Code04_MoneyProblem { + + // int[] d d[i]:i号怪兽的武力 + // int[] p p[i]:i号怪兽要求的钱 + // ability 当前你所具有的能力 + // index 来到了第index个怪兽的面前 + + // 目前,你的能力是ability,你来到了index号怪兽的面前,如果要通过后续所有的怪兽, + // 请返回需要花的最少钱数 + public static long process1(int[] d, int[] p, int ability, int index) { + if (index == d.length) { + return 0; + } + if (ability < d[index]) { + return p[index] + process1(d, p, ability + d[index], index + 1); + } else { // ability >= d[index] 可以贿赂,也可以不贿赂 + return Math.min( + + p[index] + process1(d, p, ability + d[index], index + 1), + + 0 + process1(d, p, ability, index + 1)); + } + } + + public static long func1(int[] d, int[] p) { + return process1(d, p, 0, 0); + } + + // 从0....index号怪兽,花的钱,必须严格==money + // 如果通过不了,返回-1 + // 如果可以通过,返回能通过情况下的最大能力值 + public static long process2(int[] d, int[] p, int index, int money) { + if (index == -1) { // 一个怪兽也没遇到呢 + return money == 0 ? 0 : -1; + } + // index >= 0 + // 1) 不贿赂当前index号怪兽 + long preMaxAbility = process2(d, p, index - 1, money); + long p1 = -1; + if (preMaxAbility != -1 && preMaxAbility >= d[index]) { + p1 = preMaxAbility; + } + // 2) 贿赂当前的怪兽 当前的钱 p[index] + long preMaxAbility2 = process2(d, p, index - 1, money - p[index]); + long p2 = -1; + if (preMaxAbility2 != -1) { + p2 = d[index] + preMaxAbility2; + } + return Math.max(p1, p2); + } + + public static int minMoney2(int[] d, int[] p) { + int allMoney = 0; + for (int i = 0; i < p.length; i++) { + allMoney += p[i]; + } + int N = d.length; + for (int money = 0; money < allMoney; money++) { + if (process2(d, p, N - 1, money) != -1) { + return money; + } + } + return allMoney; + } + + public static long func2(int[] d, int[] p) { + int sum = 0; + for (int num : d) { + sum += num; + } + long[][] dp = new long[d.length + 1][sum + 1]; + for (int i = 0; i <= sum; i++) { + dp[0][i] = 0; + } + for (int cur = d.length - 1; cur >= 0; cur--) { + for (int hp = 0; hp <= sum; hp++) { + // 如果这种情况发生,那么这个hp必然是递归过程中不会出现的状态 + // 既然动态规划是尝试过程的优化,尝试过程碰不到的状态,不必计算 + if (hp + d[cur] > sum) { + continue; + } + if (hp < d[cur]) { + dp[cur][hp] = p[cur] + dp[cur + 1][hp + d[cur]]; + } else { + dp[cur][hp] = Math.min(p[cur] + dp[cur + 1][hp + d[cur]], dp[cur + 1][hp]); + } + } + } + return dp[0][0]; + } + + public static long func3(int[] d, int[] p) { + int sum = 0; + for (int num : p) { + sum += num; + } + // dp[i][j]含义: + // 能经过0~i的怪兽,且花钱为j(花钱的严格等于j)时的武力值最大是多少? + // 如果dp[i][j]==-1,表示经过0~i的怪兽,花钱为j是无法通过的,或者之前的钱怎么组合也得不到正好为j的钱数 + int[][] dp = new int[d.length][sum + 1]; + for (int i = 0; i < dp.length; i++) { + for (int j = 0; j <= sum; j++) { + dp[i][j] = -1; + } + } + // 经过0~i的怪兽,花钱数一定为p[0],达到武力值d[0]的地步。其他第0行的状态一律是无效的 + dp[0][p[0]] = d[0]; + for (int i = 1; i < d.length; i++) { + for (int j = 0; j <= sum; j++) { + // 可能性一,为当前怪兽花钱 + // 存在条件: + // j - p[i]要不越界,并且在钱数为j - p[i]时,要能通过0~i-1的怪兽,并且钱数组合是有效的。 + if (j >= p[i] && dp[i - 1][j - p[i]] != -1) { + dp[i][j] = dp[i - 1][j - p[i]] + d[i]; + } + // 可能性二,不为当前怪兽花钱 + // 存在条件: + // 0~i-1怪兽在花钱为j的情况下,能保证通过当前i位置的怪兽 + if (dp[i - 1][j] >= d[i]) { + // 两种可能性中,选武力值最大的 + dp[i][j] = Math.max(dp[i][j], dp[i - 1][j]); + } + } + } + int ans = 0; + // dp表最后一行上,dp[N-1][j]代表: + // 能经过0~N-1的怪兽,且花钱为j(花钱的严格等于j)时的武力值最大是多少? + // 那么最后一行上,最左侧的不为-1的列数(j),就是答案 + for (int j = 0; j <= sum; j++) { + if (dp[d.length - 1][j] != -1) { + ans = j; + break; + } + } + return ans; + } + + public static int[][] generateTwoRandomArray(int len, int value) { + int size = (int) (Math.random() * len) + 1; + int[][] arrs = new int[2][size]; + for (int i = 0; i < size; i++) { + arrs[0][i] = (int) (Math.random() * value) + 1; + arrs[1][i] = (int) (Math.random() * value) + 1; + } + return arrs; + } + + public static void main(String[] args) { + int len = 10; + int value = 20; + int testTimes = 10000; + for (int i = 0; i < testTimes; i++) { + int[][] arrs = generateTwoRandomArray(len, value); + int[] d = arrs[0]; + int[] p = arrs[1]; + long ans1 = func1(d, p); + long ans2 = func2(d, p); + long ans3 = func3(d, p); + long ans4 = minMoney2(d,p); + if (ans1 != ans2 || ans2 != ans3 || ans1 != ans4) { + System.out.println("oops!"); + } + } + + } + +} diff --git a/体系学习班/class39/Code01_SubsquenceMaxModM.java b/体系学习班/class39/Code01_SubsquenceMaxModM.java new file mode 100644 index 0000000..9890bf4 --- /dev/null +++ b/体系学习班/class39/Code01_SubsquenceMaxModM.java @@ -0,0 +1,141 @@ +package class39; + +import java.util.HashSet; +import java.util.TreeSet; + +// 给定一个非负数组arr,和一个正数m。 返回arr的所有子序列中累加和%m之后的最大值。 +public class Code01_SubsquenceMaxModM { + + public static int max1(int[] arr, int m) { + HashSet set = new HashSet<>(); + process(arr, 0, 0, set); + int max = 0; + for (Integer sum : set) { + max = Math.max(max, sum % m); + } + return max; + } + + public static void process(int[] arr, int index, int sum, HashSet set) { + if (index == arr.length) { + set.add(sum); + } else { + process(arr, index + 1, sum, set); + process(arr, index + 1, sum + arr[index], set); + } + } + + public static int max2(int[] arr, int m) { + int sum = 0; + int N = arr.length; + for (int i = 0; i < N; i++) { + sum += arr[i]; + } + boolean[][] dp = new boolean[N][sum + 1]; + for (int i = 0; i < N; i++) { + dp[i][0] = true; + } + dp[0][arr[0]] = true; + for (int i = 1; i < N; i++) { + for (int j = 1; j <= sum; j++) { + dp[i][j] = dp[i - 1][j]; + if (j - arr[i] >= 0) { + dp[i][j] |= dp[i - 1][j - arr[i]]; + } + } + } + int ans = 0; + for (int j = 0; j <= sum; j++) { + if (dp[N - 1][j]) { + ans = Math.max(ans, j % m); + } + } + return ans; + } + + public static int max3(int[] arr, int m) { + int N = arr.length; + // 0...m-1 + boolean[][] dp = new boolean[N][m]; + for (int i = 0; i < N; i++) { + dp[i][0] = true; + } + dp[0][arr[0] % m] = true; + for (int i = 1; i < N; i++) { + for (int j = 1; j < m; j++) { + // dp[i][j] T or F + dp[i][j] = dp[i - 1][j]; + int cur = arr[i] % m; + if (cur <= j) { + dp[i][j] |= dp[i - 1][j - cur]; + } else { + dp[i][j] |= dp[i - 1][m + j - cur]; + } + } + } + int ans = 0; + for (int i = 0; i < m; i++) { + if (dp[N - 1][i]) { + ans = i; + } + } + return ans; + } + + // 如果arr的累加和很大,m也很大 + // 但是arr的长度相对不大 + public static int max4(int[] arr, int m) { + if (arr.length == 1) { + return arr[0] % m; + } + int mid = (arr.length - 1) / 2; + TreeSet sortSet1 = new TreeSet<>(); + process4(arr, 0, 0, mid, m, sortSet1); + TreeSet sortSet2 = new TreeSet<>(); + process4(arr, mid + 1, 0, arr.length - 1, m, sortSet2); + int ans = 0; + for (Integer leftMod : sortSet1) { + ans = Math.max(ans, leftMod + sortSet2.floor(m - 1 - leftMod)); + } + return ans; + } + + // 从index出发,最后有边界是end+1,arr[index...end] + public static void process4(int[] arr, int index, int sum, int end, int m, TreeSet sortSet) { + if (index == end + 1) { + sortSet.add(sum % m); + } else { + process4(arr, index + 1, sum, end, m, sortSet); + process4(arr, index + 1, sum + arr[index], end, m, sortSet); + } + } + + public static int[] generateRandomArray(int len, int value) { + int[] ans = new int[(int) (Math.random() * len) + 1]; + for (int i = 0; i < ans.length; i++) { + ans[i] = (int) (Math.random() * value); + } + return ans; + } + + public static void main(String[] args) { + int len = 10; + int value = 100; + int m = 76; + int testTime = 500000; + System.out.println("test begin"); + for (int i = 0; i < testTime; i++) { + int[] arr = generateRandomArray(len, value); + int ans1 = max1(arr, m); + int ans2 = max2(arr, m); + int ans3 = max3(arr, m); + int ans4 = max4(arr, m); + if (ans1 != ans2 || ans2 != ans3 || ans3 != ans4) { + System.out.println("Oops!"); + } + } + System.out.println("test finish!"); + + } + +} diff --git a/体系学习班/class39/Code02_SnacksWays.java b/体系学习班/class39/Code02_SnacksWays.java new file mode 100644 index 0000000..bb2c538 --- /dev/null +++ b/体系学习班/class39/Code02_SnacksWays.java @@ -0,0 +1,79 @@ +package class39; + +public class Code02_SnacksWays { + + public static int ways1(int[] arr, int w) { + // arr[0...] + return process(arr, 0, w); + } + + // 从左往右的经典模型 + // 还剩的容量是rest,arr[index...]自由选择, + // 返回选择方案 + // index : 0~N + // rest : 0~w + public static int process(int[] arr, int index, int rest) { + if (rest < 0) { // 没有容量了 + // -1 无方案的意思 + return -1; + } + // rest>=0, + if (index == arr.length) { // 无零食可选 + return 1; + } + // rest >=0 + // 有零食index + // index号零食,要 or 不要 + // index, rest + // (index+1, rest) + // (index+1, rest-arr[i]) + int next1 = process(arr, index + 1, rest); // 不要 + int next2 = process(arr, index + 1, rest - arr[index]); // 要 + return next1 + (next2 == -1 ? 0 : next2); + } + + public static int ways2(int[] arr, int w) { + int N = arr.length; + int[][] dp = new int[N + 1][w + 1]; + for (int j = 0; j <= w; j++) { + dp[N][j] = 1; + } + for (int i = N - 1; i >= 0; i--) { + for (int j = 0; j <= w; j++) { + dp[i][j] = dp[i + 1][j] + ((j - arr[i] >= 0) ? dp[i + 1][j - arr[i]] : 0); + } + } + return dp[0][w]; + } + + public static int ways3(int[] arr, int w) { + int N = arr.length; + int[][] dp = new int[N][w + 1]; + for (int i = 0; i < N; i++) { + dp[i][0] = 1; + } + if (arr[0] <= w) { + dp[0][arr[0]] = 1; + } + for (int i = 1; i < N; i++) { + for (int j = 1; j <= w; j++) { + dp[i][j] = dp[i - 1][j] + ((j - arr[i]) >= 0 ? dp[i - 1][j - arr[i]] : 0); + } + } + int ans = 0; + for (int j = 0; j <= w; j++) { + ans += dp[N - 1][j]; + } + return ans; + } + + public static void main(String[] args) { + int[] arr = { 4, 3, 2, 9 }; + int w = 8; + System.out.println(ways1(arr, w)); + System.out.println(ways2(arr, w)); + System.out.println(ways3(arr, w)); + + } + +} diff --git a/体系学习班/class39/Code02_SnacksWaysMain1.java b/体系学习班/class39/Code02_SnacksWaysMain1.java new file mode 100644 index 0000000..f282098 --- /dev/null +++ b/体系学习班/class39/Code02_SnacksWaysMain1.java @@ -0,0 +1,126 @@ +// 不要拷贝包信息的内容 +package class39; + +// 课堂版本 +// 本文件是Code02_SnacksWays问题的牛客题目解答 +// 但是用的分治的方法 +// 这是牛客的测试链接: +// https://www.nowcoder.com/questionTerminal/d94bb2fa461d42bcb4c0f2b94f5d4281 +// 把如下的全部代码拷贝进编辑器(java) +// 可以直接通过 +import java.util.Map.Entry; +import java.util.Scanner; +import java.util.TreeMap; + +public class Code02_SnacksWaysMain1 { + + public static void main(String[] args) { + Scanner sc = new Scanner(System.in); + int N = sc.nextInt(); + int bag = sc.nextInt(); + int[] arr = new int[N]; + for (int i = 0; i < arr.length; i++) { + arr[i] = sc.nextInt(); + } + long ways = ways(arr, bag); + System.out.println(ways); + sc.close(); + } + + public static long ways(int[] arr, int bag) { + if (arr == null || arr.length == 0) { + return 0; + } + if (arr.length == 1) { + return arr[0] <= bag ? 2 : 1; + } + int mid = (arr.length - 1) >> 1; + TreeMap lmap = new TreeMap<>(); + long ways = process(arr, 0, 0, mid, bag, lmap); + TreeMap rmap = new TreeMap<>(); + ways += process(arr, mid + 1, 0, arr.length - 1, bag, rmap); + TreeMap rpre = new TreeMap<>(); + long pre = 0; + for (Entry entry : rmap.entrySet()) { + pre += entry.getValue(); + rpre.put(entry.getKey(), pre); + } + for (Entry entry : lmap.entrySet()) { + long lweight = entry.getKey(); + long lways = entry.getValue(); + Long floor = rpre.floorKey(bag - lweight); + if (floor != null) { + long rways = rpre.get(floor); + ways += lways * rways; + } + } + return ways + 1; + } + + + + + // arr 30 + // func(arr, 0, 14, 0, bag, map) + + // func(arr, 15, 29, 0, bag, map) + + // 从index出发,到end结束 + // 之前的选择,已经形成的累加和sum + // 零食[index....end]自由选择,出来的所有累加和,不能超过bag,每一种累加和对应的方法数,填在map里 + // 最后不能什么货都没选 + // [3,3,3,3] bag = 6 + // 0 1 2 3 + // - - - - 0 -> (0 : 1) + // - - - $ 3 -> (0 : 1)(3, 1) + // - - $ - 3 -> (0 : 1)(3, 2) + public static long func(int[] arr, int index, int end, long sum, long bag, TreeMap map) { + if(sum > bag) { + return 0; + } + // sum <= bag + if(index > end) { // 所有商品自由选择完了! + // sum + if(sum != 0) { + if (!map.containsKey(sum)) { + map.put(sum, 1L); + } else { + map.put(sum, map.get(sum) + 1); + } + return 1; + } else { + return 0; + } + } + // sum <= bag 并且 index <= end(还有货) + // 1) 不要当前index位置的货 + long ways = func(arr, index + 1, end, sum, bag, map); + + // 2) 要当前index位置的货 + ways += func(arr, index + 1, end, sum + arr[index], bag, map); + return ways; + } + + public static long process(int[] arr, int index, long w, int end, int bag, TreeMap map) { + if (w > bag) { + return 0; + } + if (index > end) { + if (w != 0) { + if (!map.containsKey(w)) { + map.put(w, 1L); + } else { + map.put(w, map.get(w) + 1); + } + return 1; + } else { + return 0; + } + } else { + long ways = process(arr, index + 1, w, end, bag, map); + ways += process(arr, index + 1, w + arr[index], end, bag, map); + return ways; + } + } + +} \ No newline at end of file diff --git a/体系学习班/class39/Code02_SnacksWaysMain2.java b/体系学习班/class39/Code02_SnacksWaysMain2.java new file mode 100644 index 0000000..0d4bf87 --- /dev/null +++ b/体系学习班/class39/Code02_SnacksWaysMain2.java @@ -0,0 +1,135 @@ +// 不要拷贝包信息的内容 +package class39; + +//优化版本 +import java.util.Arrays; +import java.util.Scanner; + +public class Code02_SnacksWaysMain2 { + + public static void main(String[] args) { + Scanner sc = new Scanner(System.in); + while (sc.hasNext()) { + size = sc.nextInt(); + long w = (long) sc.nextInt(); + for (int i = 0; i < size; i++) { + arr[i] = (long) sc.nextInt(); + } + long ways = ways(w); + System.out.println(ways); + } + sc.close(); + } + + // 用来收集所有输入的数字 + public static long[] arr = new long[31]; + public static int size = 0; + // 用来生成左部分可能的所有累加和 + public static long[] leftSum = new long[1 << 16]; + // 准备的数组可能用不完,左部分生成了多少累加和,用leftSize表示 + public static int leftSize = 0; + // 用来生成右部分可能的所有累加和 + public static long[] rightSum = new long[1 << 16]; + // 准备的数组可能用不完,左部分生成了多少累加和,用leftSize表示 + public static int rightSize = 0; + + public static long ways(long w) { + if (size == 0) { + return 0; + } + if (size == 1) { + return arr[0] <= w ? 2 : 1; + } + // 求中点 + int mid = size >> 1; + // 生成左侧的累加和 + leftSize = 0; + dfsLeft(0, mid + 1, 0L); + // 生成右侧的累加和 + rightSize = 0; + dfsRight(mid + 1, size, 0L); + // 把左侧累加和排序 + Arrays.sort(leftSum, 0, leftSize); + // 把右侧累加和排序 + Arrays.sort(rightSum, 0, rightSize); + // 解释一下,接下来的流程。 + // 举个例子,比如: + // 左侧累加和是:{0, 1, 1, 1, 2, 2, 3, 4, 4} + // 右侧累加和是:{0, 1, 2, 3, 3, 3, 4, 4, 5} + // w = 5 + // 左侧严格得到0的方法数:1 + // 右侧得到<=5的方法数(二分求出):9 + // 1 * 9 + // 左侧严格得到1的方法数:3 + // 右侧得到<=4的方法数(二分求出):8 + // 3 * 8 + // 左侧严格得到2的方法数:2 + // 右侧得到<=3的方法数(二分求出):6 + // 2 * 6 + // 左侧严格得到3的方法数:1 + // 右侧得到<=2的方法数(二分求出):3 + // 1 * 3 + // 左侧严格得到4的方法数:2 + // 右侧得到<=1的方法数(二分求出):2 + // 2 * 2 + // 都累加起来 + // 其实和课上讲的一样!多看一下例子 + long ans = 0; + long count = 1; + for (int i = 1; i < leftSize; i++) { + if (leftSum[i] != leftSum[i - 1]) { + ans += count * (long) find(w - leftSum[i - 1]); + count = 1; + } else { + count++; + } + } + ans += count * (long) find(w - leftSum[leftSize - 1]); + return ans; + } + + // 生成左部分的累加和,每一个累加和出来,都记录 + public static void dfsLeft(int cur, int end, long sum) { + if (cur == end) { // 已经终止位置了 + // 记录累加和 + leftSum[leftSize++] = sum; + } else { + // 可能性1,不要当前数 + dfsLeft(cur + 1, end, sum); + // 可能性2,要当前数 + dfsLeft(cur + 1, end, sum + arr[cur]); + } + } + + // 生成右部分的累加和,每一个累加和出来,都记录 + public static void dfsRight(int cur, int end, long sum) { + if (cur == end) { // 已经终止位置了 + // 记录累加和 + rightSum[rightSize++] = sum; + } else { + // 可能性1,不要当前数 + dfsRight(cur + 1, end, sum); + // 可能性2,要当前数 + dfsRight(cur + 1, end, sum + arr[cur]); + } + } + + // <= num的数的个数,返回 + public static int find(long num) { + int ans = -1; + int l = 0; + int r = rightSize - 1; + int m = 0; + while (l <= r) { + m = (l + r) / 2; + if (rightSum[m] <= num) { + ans = m; + l = m + 1; + } else { + r = m - 1; + } + } + return ans + 1; + } + +} \ No newline at end of file diff --git a/体系学习班/class39/Code03_10Ways.java b/体系学习班/class39/Code03_10Ways.java new file mode 100644 index 0000000..1afffb7 --- /dev/null +++ b/体系学习班/class39/Code03_10Ways.java @@ -0,0 +1,92 @@ +package class39; + +import java.util.LinkedList; + +public class Code03_10Ways { + + public static long ways1(int N) { + int zero = N; + int one = N; + LinkedList path = new LinkedList<>(); + LinkedList> ans = new LinkedList<>(); + process(zero, one, path, ans); + long count = 0; + for (LinkedList cur : ans) { + int status = 0; + for (Integer num : cur) { + if (num == 0) { + status++; + } else { + status--; + } + if (status < 0) { + break; + } + } + if (status == 0) { + count++; + } + } + return count; + } + + public static void process(int zero, int one, LinkedList path, LinkedList> ans) { + if (zero == 0 && one == 0) { + LinkedList cur = new LinkedList<>(); + for (Integer num : path) { + cur.add(num); + } + ans.add(cur); + } else { + if (zero == 0) { + path.addLast(1); + process(zero, one - 1, path, ans); + path.removeLast(); + } else if (one == 0) { + path.addLast(0); + process(zero - 1, one, path, ans); + path.removeLast(); + } else { + path.addLast(1); + process(zero, one - 1, path, ans); + path.removeLast(); + path.addLast(0); + process(zero - 1, one, path, ans); + path.removeLast(); + } + } + } + + public static long ways2(int N) { + if (N < 0) { + return 0; + } + if (N < 2) { + return 1; + } + long a = 1; + long b = 1; + long limit = N << 1; + for (long i = 1; i <= limit; i++) { + if (i <= N) { + a *= i; + } else { + b *= i; + } + } + return (b / a) / (N + 1); + } + + public static void main(String[] args) { + System.out.println("test begin"); + for (int i = 0; i < 10; i++) { + long ans1 = ways1(i); + long ans2 = ways2(i); + if (ans1 != ans2) { + System.out.println("Oops!"); + } + } + System.out.println("test finish"); + } + +} diff --git a/体系学习班/class39/Code04_DifferentBTNum.java b/体系学习班/class39/Code04_DifferentBTNum.java new file mode 100644 index 0000000..3c48911 --- /dev/null +++ b/体系学习班/class39/Code04_DifferentBTNum.java @@ -0,0 +1,66 @@ +package class39; + +public class Code04_DifferentBTNum { + +// k(0) = 1, k(1) = 1 +// +// k(n) = k(0) * k(n - 1) + k(1) * k(n - 2) + ... + k(n - 2) * k(1) + k(n - 1) * k(0) +// 或者 +// k(n) = c(2n, n) / (n + 1) +// 或者 +// k(n) = c(2n, n) - c(2n, n-1) + + public static long num1(int N) { + if (N < 0) { + return 0; + } + if (N < 2) { + return 1; + } + long[] dp = new long[N + 1]; + dp[0] = 1; + dp[1] = 1; + for (int i = 2; i <= N; i++) { + for (int leftSize = 0; leftSize < i; leftSize++) { + dp[i] += dp[leftSize] * dp[i - 1 - leftSize]; + } + } + return dp[N]; + } + + public static long num2(int N) { + if (N < 0) { + return 0; + } + if (N < 2) { + return 1; + } + long a = 1; + long b = 1; + for (int i = 1, j = N + 1; i <= N; i++, j++) { + a *= i; + b *= j; + long gcd = gcd(a, b); + a /= gcd; + b /= gcd; + } + return (b / a) / (N + 1); + } + + public static long gcd(long m, long n) { + return n == 0 ? m : gcd(n, m % n); + } + + public static void main(String[] args) { + System.out.println("test begin"); + for (int i = 0; i < 15; i++) { + long ans1 = num1(i); + long ans2 = num2(i); + if (ans1 != ans2) { + System.out.println("Oops!"); + } + } + System.out.println("test finish"); + } + +} diff --git a/体系学习班/class39/IsSum.java b/体系学习班/class39/IsSum.java new file mode 100644 index 0000000..9ff7a3c --- /dev/null +++ b/体系学习班/class39/IsSum.java @@ -0,0 +1,180 @@ +package class39; + +import java.util.HashMap; +import java.util.HashSet; + +// 这道题是一个小小的补充,课上没有讲 +// 但是如果你听过体系学习班动态规划专题和本节课的话 +// 这道题就是一道水题 +public class IsSum { + + // arr中的值可能为正,可能为负,可能为0 + // 自由选择arr中的数字,能不能累加得到sum + // 暴力递归方法 + public static boolean isSum1(int[] arr, int sum) { + if (sum == 0) { + return true; + } + if (arr == null || arr.length == 0) { + return false; + } + return process1(arr, arr.length - 1, sum); + } + + // 可以自由使用arr[0...i]上的数字,能不能累加得到sum + public static boolean process1(int[] arr, int i, int sum) { + if (sum == 0) { + return true; + } + if (i == -1) { + return false; + } + return process1(arr, i - 1, sum) || process1(arr, i - 1, sum - arr[i]); + } + + // arr中的值可能为正,可能为负,可能为0 + // 自由选择arr中的数字,能不能累加得到sum + // 记忆化搜索方法 + // 从暴力递归方法来,加了记忆化缓存,就是动态规划了 + public static boolean isSum2(int[] arr, int sum) { + if (sum == 0) { + return true; + } + if (arr == null || arr.length == 0) { + return false; + } + return process2(arr, arr.length - 1, sum, new HashMap<>()); + } + + public static boolean process2(int[] arr, int i, int sum, HashMap> dp) { + if (dp.containsKey(i) && dp.get(i).containsKey(sum)) { + return dp.get(i).get(sum); + } + boolean ans = false; + if (sum == 0) { + ans = true; + } else if (i != -1) { + ans = process2(arr, i - 1, sum, dp) || process2(arr, i - 1, sum - arr[i], dp); + } + if (!dp.containsKey(i)) { + dp.put(i, new HashMap<>()); + } + dp.get(i).put(sum, ans); + return ans; + } + + // arr中的值可能为正,可能为负,可能为0 + // 自由选择arr中的数字,能不能累加得到sum + // 经典动态规划 + public static boolean isSum3(int[] arr, int sum) { + if (sum == 0) { + return true; + } + if (arr == null || arr.length == 0) { + return false; + } + int min = 0; + int max = 0; + for (int num : arr) { + min += num < 0 ? num : 0; + max += num > 0 ? num : 0; + } + if (sum < min || sum > max) { + return false; + } + int N = arr.length; + boolean[][] dp = new boolean[N][max - min + 1]; + dp[0][-min] = true; + dp[0][arr[0] - min] = true; + for (int i = 1; i < N; i++) { + for (int j = min; j <= max; j++) { + dp[i][j - min] = dp[i - 1][j - min]; + int next = j - min - arr[i]; + dp[i][j - min] |= (next >= 0 && next <= max - min && dp[i - 1][next]); + } + } + return dp[N - 1][sum - min]; + } + + // arr中的值可能为正,可能为负,可能为0 + // 自由选择arr中的数字,能不能累加得到sum + // 分治的方法 + // 如果arr中的数值特别大,动态规划方法依然会很慢 + // 此时如果arr的数字个数不算多(40以内),哪怕其中的数值很大,分治的方法也将是最优解 + public static boolean isSum4(int[] arr, int sum) { + if (sum == 0) { + return true; + } + if (arr == null || arr.length == 0) { + return false; + } + if (arr.length == 1) { + return arr[0] == sum; + } + int N = arr.length; + int mid = N >> 1; + HashSet leftSum = new HashSet<>(); + HashSet rightSum = new HashSet<>(); + process4(arr, 0, mid, 0, leftSum); + process4(arr, mid, N, 0, rightSum); + for (int l : leftSum) { + if (rightSum.contains(sum - l)) { + return true; + } + } + return false; + } + + public static void process4(int[] arr, int i, int end, int pre, HashSet ans) { + if (i == end) { + ans.add(pre); + } else { + process4(arr, i + 1, end, pre, ans); + process4(arr, i + 1, end, pre + arr[i], ans); + } + } + + // 为了测试 + // 生成长度为len的随机数组 + // 值在[-max, max]上随机 + public static int[] randomArray(int len, int max) { + int[] arr = new int[len]; + for (int i = 0; i < len; i++) { + arr[i] = (int) (Math.random() * ((max << 1) + 1)) - max; + } + return arr; + } + + // 对数器验证所有方法 + public static void main(String[] args) { + int N = 20; + int M = 100; + int testTime = 100000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int size = (int) (Math.random() * (N + 1)); + int[] arr = randomArray(size, M); + int sum = (int) (Math.random() * ((M << 1) + 1)) - M; + boolean ans1 = isSum1(arr, sum); + boolean ans2 = isSum2(arr, sum); + boolean ans3 = isSum3(arr, sum); + boolean ans4 = isSum4(arr, sum); + if (ans1 ^ ans2 || ans3 ^ ans4 || ans1 ^ ans3) { + System.out.println("出错了!"); + System.out.print("arr : "); + for (int num : arr) { + System.out.print(num + " "); + } + System.out.println(); + System.out.println("sum : " + sum); + System.out.println("方法一答案 : " + ans1); + System.out.println("方法二答案 : " + ans2); + System.out.println("方法三答案 : " + ans3); + System.out.println("方法四答案 : " + ans4); + break; + } + } + System.out.println("测试结束"); + } + +} diff --git a/体系学习班/class40/Code01_LongestSumSubArrayLengthInPositiveArray.java b/体系学习班/class40/Code01_LongestSumSubArrayLengthInPositiveArray.java new file mode 100644 index 0000000..a2389fa --- /dev/null +++ b/体系学习班/class40/Code01_LongestSumSubArrayLengthInPositiveArray.java @@ -0,0 +1,91 @@ +package class40; + +public class Code01_LongestSumSubArrayLengthInPositiveArray { + + public static int getMaxLength(int[] arr, int K) { + if (arr == null || arr.length == 0 || K <= 0) { + return 0; + } + int left = 0; + int right = 0; + int sum = arr[0]; + int len = 0; + while (right < arr.length) { + if (sum == K) { + len = Math.max(len, right - left + 1); + sum -= arr[left++]; + } else if (sum < K) { + right++; + if (right == arr.length) { + break; + } + sum += arr[right]; + } else { + sum -= arr[left++]; + } + } + return len; + } + + // for test + public static int right(int[] arr, int K) { + int max = 0; + for (int i = 0; i < arr.length; i++) { + for (int j = i; j < arr.length; j++) { + if (valid(arr, i, j, K)) { + max = Math.max(max, j - i + 1); + } + } + } + return max; + } + + // for test + public static boolean valid(int[] arr, int L, int R, int K) { + int sum = 0; + for (int i = L; i <= R; i++) { + sum += arr[i]; + } + return sum == K; + } + + // for test + public static int[] generatePositiveArray(int size, int value) { + int[] ans = new int[size]; + for (int i = 0; i != size; i++) { + ans[i] = (int) (Math.random() * value) + 1; + } + return ans; + } + + // for test + public static void printArray(int[] arr) { + for (int i = 0; i != arr.length; i++) { + System.out.print(arr[i] + " "); + } + System.out.println(); + } + + public static void main(String[] args) { + int len = 50; + int value = 100; + int testTime = 500000; + System.out.println("test begin"); + for (int i = 0; i < testTime; i++) { + int[] arr = generatePositiveArray(len, value); + int K = (int) (Math.random() * value) + 1; + int ans1 = getMaxLength(arr, K); + int ans2 = right(arr, K); + if (ans1 != ans2) { + System.out.println("Oops!"); + printArray(arr); + System.out.println("K : " + K); + System.out.println(ans1); + System.out.println(ans2); + break; + } + } + System.out.println("test end"); + } + +} diff --git a/体系学习班/class40/Code02_LongestSumSubArrayLength.java b/体系学习班/class40/Code02_LongestSumSubArrayLength.java new file mode 100644 index 0000000..a89fb14 --- /dev/null +++ b/体系学习班/class40/Code02_LongestSumSubArrayLength.java @@ -0,0 +1,92 @@ +package class40; + +import java.util.HashMap; + +public class Code02_LongestSumSubArrayLength { + + public static int maxLength(int[] arr, int k) { + if (arr == null || arr.length == 0) { + return 0; + } + // key:前缀和 + // value : 0~value这个前缀和是最早出现key这个值的 + HashMap map = new HashMap(); + map.put(0, -1); // important + int len = 0; + int sum = 0; + for (int i = 0; i < arr.length; i++) { + sum += arr[i]; + if (map.containsKey(sum - k)) { + len = Math.max(i - map.get(sum - k), len); + } + if (!map.containsKey(sum)) { + map.put(sum, i); + } + } + return len; + } + + // for test + public static int right(int[] arr, int K) { + int max = 0; + for (int i = 0; i < arr.length; i++) { + for (int j = i; j < arr.length; j++) { + if (valid(arr, i, j, K)) { + max = Math.max(max, j - i + 1); + } + } + } + return max; + } + + // for test + public static boolean valid(int[] arr, int L, int R, int K) { + int sum = 0; + for (int i = L; i <= R; i++) { + sum += arr[i]; + } + return sum == K; + } + + // for test + public static int[] generateRandomArray(int size, int value) { + int[] ans = new int[(int) (Math.random() * size) + 1]; + for (int i = 0; i < ans.length; i++) { + ans[i] = (int) (Math.random() * value) - (int) (Math.random() * value); + } + return ans; + } + + // for test + public static void printArray(int[] arr) { + for (int i = 0; i != arr.length; i++) { + System.out.print(arr[i] + " "); + } + System.out.println(); + } + + public static void main(String[] args) { + int len = 50; + int value = 100; + int testTime = 500000; + + System.out.println("test begin"); + for (int i = 0; i < testTime; i++) { + int[] arr = generateRandomArray(len, value); + int K = (int) (Math.random() * value) - (int) (Math.random() * value); + int ans1 = maxLength(arr, K); + int ans2 = right(arr, K); + if (ans1 != ans2) { + System.out.println("Oops!"); + printArray(arr); + System.out.println("K : " + K); + System.out.println(ans1); + System.out.println(ans2); + break; + } + } + System.out.println("test end"); + + } + +} diff --git a/体系学习班/class40/Code03_LongestLessSumSubArrayLength.java b/体系学习班/class40/Code03_LongestLessSumSubArrayLength.java new file mode 100644 index 0000000..c4ebc56 --- /dev/null +++ b/体系学习班/class40/Code03_LongestLessSumSubArrayLength.java @@ -0,0 +1,103 @@ +package class40; + +public class Code03_LongestLessSumSubArrayLength { + + public static int maxLengthAwesome(int[] arr, int k) { + if (arr == null || arr.length == 0) { + return 0; + } + int[] minSums = new int[arr.length]; + int[] minSumEnds = new int[arr.length]; + minSums[arr.length - 1] = arr[arr.length - 1]; + minSumEnds[arr.length - 1] = arr.length - 1; + for (int i = arr.length - 2; i >= 0; i--) { + if (minSums[i + 1] < 0) { + minSums[i] = arr[i] + minSums[i + 1]; + minSumEnds[i] = minSumEnds[i + 1]; + } else { + minSums[i] = arr[i]; + minSumEnds[i] = i; + } + } + // 迟迟扩不进来那一块儿的开头位置 + int end = 0; + int sum = 0; + int ans = 0; + for (int i = 0; i < arr.length; i++) { + // while循环结束之后: + // 1) 如果以i开头的情况下,累加和<=k的最长子数组是arr[i..end-1],看看这个子数组长度能不能更新res; + // 2) 如果以i开头的情况下,累加和<=k的最长子数组比arr[i..end-1]短,更新还是不更新res都不会影响最终结果; + while (end < arr.length && sum + minSums[end] <= k) { + sum += minSums[end]; + end = minSumEnds[end] + 1; + } + ans = Math.max(ans, end - i); + if (end > i) { // 还有窗口,哪怕窗口没有数字 [i~end) [4,4) + sum -= arr[i]; + } else { // i == end, 即将 i++, i > end, 此时窗口概念维持不住了,所以end跟着i一起走 + end = i + 1; + } + } + return ans; + } + + public static int maxLength(int[] arr, int k) { + int[] h = new int[arr.length + 1]; + int sum = 0; + h[0] = sum; + for (int i = 0; i != arr.length; i++) { + sum += arr[i]; + h[i + 1] = Math.max(sum, h[i]); + } + sum = 0; + int res = 0; + int pre = 0; + int len = 0; + for (int i = 0; i != arr.length; i++) { + sum += arr[i]; + pre = getLessIndex(h, sum - k); + len = pre == -1 ? 0 : i - pre + 1; + res = Math.max(res, len); + } + return res; + } + + public static int getLessIndex(int[] arr, int num) { + int low = 0; + int high = arr.length - 1; + int mid = 0; + int res = -1; + while (low <= high) { + mid = (low + high) / 2; + if (arr[mid] >= num) { + res = mid; + high = mid - 1; + } else { + low = mid + 1; + } + } + return res; + } + + // for test + public static int[] generateRandomArray(int len, int maxValue) { + int[] res = new int[len]; + for (int i = 0; i != res.length; i++) { + res[i] = (int) (Math.random() * maxValue) - (maxValue / 3); + } + return res; + } + + public static void main(String[] args) { + System.out.println("test begin"); + for (int i = 0; i < 10000000; i++) { + int[] arr = generateRandomArray(10, 20); + int k = (int) (Math.random() * 20) - 5; + if (maxLengthAwesome(arr, k) != maxLength(arr, k)) { + System.out.println("Oops!"); + } + } + System.out.println("test finish"); + } + +} diff --git a/体系学习班/class40/Code04_AvgLessEqualValueLongestSubarray.java b/体系学习班/class40/Code04_AvgLessEqualValueLongestSubarray.java new file mode 100644 index 0000000..c56eef9 --- /dev/null +++ b/体系学习班/class40/Code04_AvgLessEqualValueLongestSubarray.java @@ -0,0 +1,152 @@ +package class40; + +import java.util.TreeMap; + +public class Code04_AvgLessEqualValueLongestSubarray { + + // 暴力解,时间复杂度O(N^3),用于做对数器 + public static int ways1(int[] arr, int v) { + int ans = 0; + for (int L = 0; L < arr.length; L++) { + for (int R = L; R < arr.length; R++) { + int sum = 0; + int k = R - L + 1; + for (int i = L; i <= R; i++) { + sum += arr[i]; + } + double avg = (double) sum / (double) k; + if (avg <= v) { + ans = Math.max(ans, k); + } + } + } + return ans; + } + + // 想实现的解法2,时间复杂度O(N*logN) + public static int ways2(int[] arr, int v) { + if (arr == null || arr.length == 0) { + return 0; + } + TreeMap origins = new TreeMap<>(); + int ans = 0; + int modify = 0; + for (int i = 0; i < arr.length; i++) { + int p1 = arr[i] <= v ? 1 : 0; + int p2 = 0; + int querry = -arr[i] - modify; + if (origins.floorKey(querry) != null) { + p2 = i - origins.get(origins.floorKey(querry)) + 1; + } + ans = Math.max(ans, Math.max(p1, p2)); + int curOrigin = -modify - v; + if (origins.floorKey(curOrigin) == null) { + origins.put(curOrigin, i); + } + modify += arr[i] - v; + } + return ans; + } + + // 想实现的解法3,时间复杂度O(N) + public static int ways3(int[] arr, int v) { + if (arr == null || arr.length == 0) { + return 0; + } + for (int i = 0; i < arr.length; i++) { + arr[i] -= v; + } + return maxLengthAwesome(arr, 0); + } + + // 找到数组中累加和<=k的最长子数组 + public static int maxLengthAwesome(int[] arr, int k) { + int N = arr.length; + int[] sums = new int[N]; + int[] ends = new int[N]; + sums[N - 1] = arr[N - 1]; + ends[N - 1] = N - 1; + for (int i = N - 2; i >= 0; i--) { + if (sums[i + 1] < 0) { + sums[i] = arr[i] + sums[i + 1]; + ends[i] = ends[i + 1]; + } else { + sums[i] = arr[i]; + ends[i] = i; + } + } + int end = 0; + int sum = 0; + int res = 0; + for (int i = 0; i < N; i++) { + while (end < N && sum + sums[end] <= k) { + sum += sums[end]; + end = ends[end] + 1; + } + res = Math.max(res, end - i); + if (end > i) { + sum -= arr[i]; + } else { + end = i + 1; + } + } + return res; + } + + // 用于测试 + public static int[] randomArray(int maxLen, int maxValue) { + int len = (int) (Math.random() * maxLen) + 1; + int[] ans = new int[len]; + for (int i = 0; i < len; i++) { + ans[i] = (int) (Math.random() * maxValue); + } + return ans; + } + + // 用于测试 + public static int[] copyArray(int[] arr) { + int[] ans = new int[arr.length]; + for (int i = 0; i < arr.length; i++) { + ans[i] = arr[i]; + } + return ans; + } + + // 用于测试 + public static void printArray(int[] arr) { + for (int i = 0; i < arr.length; i++) { + System.out.print(arr[i] + " "); + } + System.out.println(); + } + + // 用于测试 + public static void main(String[] args) { + System.out.println("测试开始"); + int maxLen = 20; + int maxValue = 100; + int testTime = 500000; + for (int i = 0; i < testTime; i++) { + int[] arr = randomArray(maxLen, maxValue); + int value = (int) (Math.random() * maxValue); + int[] arr1 = copyArray(arr); + int[] arr2 = copyArray(arr); + int[] arr3 = copyArray(arr); + int ans1 = ways1(arr1, value); + int ans2 = ways2(arr2, value); + int ans3 = ways3(arr3, value); + if (ans1 != ans2 || ans1 != ans3) { + System.out.println("测试出错!"); + System.out.print("测试数组:"); + printArray(arr); + System.out.println("子数组平均值不小于 :" + value); + System.out.println("方法1得到的最大长度:" + ans1); + System.out.println("方法2得到的最大长度:" + ans2); + System.out.println("方法3得到的最大长度:" + ans3); + System.out.println("========================="); + } + } + System.out.println("测试结束"); + } + +} diff --git a/体系学习班/class40/Code05_PrintMatrixSpiralOrder.java b/体系学习班/class40/Code05_PrintMatrixSpiralOrder.java new file mode 100644 index 0000000..cb3f80c --- /dev/null +++ b/体系学习班/class40/Code05_PrintMatrixSpiralOrder.java @@ -0,0 +1,53 @@ +package class40; + +public class Code05_PrintMatrixSpiralOrder { + + public static void spiralOrderPrint(int[][] matrix) { + int tR = 0; + int tC = 0; + int dR = matrix.length - 1; + int dC = matrix[0].length - 1; + while (tR <= dR && tC <= dC) { + printEdge(matrix, tR++, tC++, dR--, dC--); + } + } + + public static void printEdge(int[][] m, int tR, int tC, int dR, int dC) { + if (tR == dR) { + for (int i = tC; i <= dC; i++) { + System.out.print(m[tR][i] + " "); + } + } else if (tC == dC) { + for (int i = tR; i <= dR; i++) { + System.out.print(m[i][tC] + " "); + } + } else { + int curC = tC; + int curR = tR; + while (curC != dC) { + System.out.print(m[tR][curC] + " "); + curC++; + } + while (curR != dR) { + System.out.print(m[curR][dC] + " "); + curR++; + } + while (curC != tC) { + System.out.print(m[dR][curC] + " "); + curC--; + } + while (curR != tR) { + System.out.print(m[curR][tC] + " "); + curR--; + } + } + } + + public static void main(String[] args) { + int[][] matrix = { { 1, 2, 3, 4 }, { 5, 6, 7, 8 }, { 9, 10, 11, 12 }, + { 13, 14, 15, 16 } }; + spiralOrderPrint(matrix); + + } + +} diff --git a/体系学习班/class40/Code06_RotateMatrix.java b/体系学习班/class40/Code06_RotateMatrix.java new file mode 100644 index 0000000..4e9534f --- /dev/null +++ b/体系学习班/class40/Code06_RotateMatrix.java @@ -0,0 +1,44 @@ +package class40; + +public class Code06_RotateMatrix { + + public static void rotate(int[][] matrix) { + int a = 0; + int b = 0; + int c = matrix.length - 1; + int d = matrix[0].length - 1; + while (a < c) { + rotateEdge(matrix, a++, b++, c--, d--); + } + } + + public static void rotateEdge(int[][] m, int a, int b, int c, int d) { + int tmp = 0; + for (int i = 0; i < d - b; i++) { + tmp = m[a][b + i]; + m[a][b + i] = m[c - i][b]; + m[c - i][b] = m[c][d - i]; + m[c][d - i] = m[a + i][d]; + m[a + i][d] = tmp; + } + } + + public static void printMatrix(int[][] matrix) { + for (int i = 0; i != matrix.length; i++) { + for (int j = 0; j != matrix[0].length; j++) { + System.out.print(matrix[i][j] + " "); + } + System.out.println(); + } + } + + public static void main(String[] args) { + int[][] matrix = { { 1, 2, 3, 4 }, { 5, 6, 7, 8 }, { 9, 10, 11, 12 }, { 13, 14, 15, 16 } }; + printMatrix(matrix); + rotate(matrix); + System.out.println("========="); + printMatrix(matrix); + + } + +} diff --git a/体系学习班/class40/Code07_ZigZagPrintMatrix.java b/体系学习班/class40/Code07_ZigZagPrintMatrix.java new file mode 100644 index 0000000..01b9921 --- /dev/null +++ b/体系学习班/class40/Code07_ZigZagPrintMatrix.java @@ -0,0 +1,42 @@ +package class40; + +public class Code07_ZigZagPrintMatrix { + + public static void printMatrixZigZag(int[][] matrix) { + int tR = 0; + int tC = 0; + int dR = 0; + int dC = 0; + int endR = matrix.length - 1; + int endC = matrix[0].length - 1; + boolean fromUp = false; + while (tR != endR + 1) { + printLevel(matrix, tR, tC, dR, dC, fromUp); + tR = tC == endC ? tR + 1 : tR; + tC = tC == endC ? tC : tC + 1; + dC = dR == endR ? dC + 1 : dC; + dR = dR == endR ? dR : dR + 1; + fromUp = !fromUp; + } + System.out.println(); + } + + public static void printLevel(int[][] m, int tR, int tC, int dR, int dC, boolean f) { + if (f) { + while (tR != dR + 1) { + System.out.print(m[tR++][tC--] + " "); + } + } else { + while (dR != tR - 1) { + System.out.print(m[dR--][dC++] + " "); + } + } + } + + public static void main(String[] args) { + int[][] matrix = { { 1, 2, 3, 4 }, { 5, 6, 7, 8 }, { 9, 10, 11, 12 } }; + printMatrixZigZag(matrix); + + } + +} diff --git a/体系学习班/class40/Code08_PrintStar.java b/体系学习班/class40/Code08_PrintStar.java new file mode 100644 index 0000000..77d41ef --- /dev/null +++ b/体系学习班/class40/Code08_PrintStar.java @@ -0,0 +1,46 @@ +package class40; + +public class Code08_PrintStar { + + public static void printStar(int N) { + int leftUp = 0; + int rightDown = N - 1; + char[][] m = new char[N][N]; + for (int i = 0; i < N; i++) { + for (int j = 0; j < N; j++) { + m[i][j] = ' '; + } + } + while (leftUp <= rightDown) { + set(m, leftUp, rightDown); + leftUp += 2; + rightDown -= 2; + } + for (int i = 0; i < N; i++) { + for (int j = 0; j < N; j++) { + System.out.print(m[i][j] + " "); + } + System.out.println(); + } + } + + public static void set(char[][] m, int leftUp, int rightDown) { + for (int col = leftUp; col <= rightDown; col++) { + m[leftUp][col] = '*'; + } + for (int row = leftUp + 1; row <= rightDown; row++) { + m[row][rightDown] = '*'; + } + for (int col = rightDown - 1; col > leftUp; col--) { + m[rightDown][col] = '*'; + } + for (int row = rightDown - 1; row > leftUp + 1; row--) { + m[row][leftUp + 1] = '*'; + } + } + + public static void main(String[] args) { + printStar(5); + } + +} diff --git a/体系学习班/class41/Code01_BestSplitForAll.java b/体系学习班/class41/Code01_BestSplitForAll.java new file mode 100644 index 0000000..a4eddb9 --- /dev/null +++ b/体系学习班/class41/Code01_BestSplitForAll.java @@ -0,0 +1,72 @@ +package class41; + +public class Code01_BestSplitForAll { + + public static int bestSplit1(int[] arr) { + if (arr == null || arr.length < 2) { + return 0; + } + int N = arr.length; + int ans = 0; + for (int s = 0; s < N - 1; s++) { + int sumL = 0; + for (int L = 0; L <= s; L++) { + sumL += arr[L]; + } + int sumR = 0; + for (int R = s + 1; R < N; R++) { + sumR += arr[R]; + } + ans = Math.max(ans, Math.min(sumL, sumR)); + } + return ans; + } + + public static int bestSplit2(int[] arr) { + if (arr == null || arr.length < 2) { + return 0; + } + int N = arr.length; + int sumAll = 0; + for (int num : arr) { + sumAll += num; + } + int ans = 0; + int sumL = 0; + // [0...s] [s+1...N-1] + for (int s = 0; s < N - 1; s++) { + sumL += arr[s]; + int sumR = sumAll - sumL; + ans = Math.max(ans, Math.min(sumL, sumR)); + } + return ans; + } + + public static int[] randomArray(int len, int max) { + int[] ans = new int[len]; + for (int i = 0; i < len; i++) { + ans[i] = (int) (Math.random() * max); + } + return ans; + } + + public static void main(String[] args) { + int N = 20; + int max = 30; + int testTime = 1000000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int len = (int) (Math.random() * N); + int[] arr = randomArray(len, max); + int ans1 = bestSplit1(arr); + int ans2 = bestSplit2(arr); + if (ans1 != ans2) { + System.out.println(ans1); + System.out.println(ans2); + System.out.println("Oops!"); + } + } + System.out.println("测试结束"); + } + +} \ No newline at end of file diff --git a/体系学习班/class41/Code02_BestSplitForEveryPosition.java b/体系学习班/class41/Code02_BestSplitForEveryPosition.java new file mode 100644 index 0000000..d98b389 --- /dev/null +++ b/体系学习班/class41/Code02_BestSplitForEveryPosition.java @@ -0,0 +1,130 @@ +package class41; + +public class Code02_BestSplitForEveryPosition { + + public static int[] bestSplit1(int[] arr) { + if (arr == null || arr.length == 0) { + return new int[0]; + } + int N = arr.length; + int[] ans = new int[N]; + ans[0] = 0; + for (int range = 1; range < N; range++) { + for (int s = 0; s < range; s++) { + int sumL = 0; + for (int L = 0; L <= s; L++) { + sumL += arr[L]; + } + int sumR = 0; + for (int R = s + 1; R <= range; R++) { + sumR += arr[R]; + } + ans[range] = Math.max(ans[range], Math.min(sumL, sumR)); + } + } + return ans; + } + + // 求原来的数组arr中,arr[L...R]的累加和 + public static int sum(int[] sum, int L, int R) { + return sum[R + 1] - sum[L]; + } + + public static int[] bestSplit2(int[] arr) { + if (arr == null || arr.length == 0) { + return new int[0]; + } + int N = arr.length; + int[] ans = new int[N]; + ans[0] = 0; + int[] sum = new int[N + 1]; + for (int i = 0; i < N; i++) { + sum[i + 1] = sum[i] + arr[i]; + } + for (int range = 1; range < N; range++) { + for (int s = 0; s < range; s++) { + int sumL = sum(sum, 0, s); + int sumR = sum(sum, s + 1, range); + ans[range] = Math.max(ans[range], Math.min(sumL, sumR)); + } + } + return ans; + } + + public static int[] bestSplit3(int[] arr) { + if (arr == null || arr.length == 0) { + return new int[0]; + } + int N = arr.length; + int[] ans = new int[N]; + ans[0] = 0; + // arr = {5, 3, 1, 3} + // 0 1 2 3 + // sum ={0, 5, 8, 9, 12} + // 0 1 2 3 4 + // 0~2 -> sum[3] - sum[0] + // 1~3 -> sum[4] - sum[1] + int[] sum = new int[N + 1]; + for (int i = 0; i < N; i++) { + sum[i + 1] = sum[i] + arr[i]; + } + // 最优划分 + // 0~range-1上,最优划分是左部分[0~best] 右部分[best+1~range-1] + int best = 0; + for (int range = 1; range < N; range++) { + while (best + 1 < range) { + int before = Math.min(sum(sum, 0, best), sum(sum, best + 1, range)); + int after = Math.min(sum(sum, 0, best + 1), sum(sum, best + 2, range)); + // 注意,一定要是>=,只是>会出错 + // 课上会讲解 + if (after >= before) { + best++; + } else { + break; + } + } + ans[range] = Math.min(sum(sum, 0, best), sum(sum, best + 1, range)); + } + return ans; + } + + public static int[] randomArray(int len, int max) { + int[] ans = new int[len]; + for (int i = 0; i < len; i++) { + ans[i] = (int) (Math.random() * max); + } + return ans; + } + + public static boolean isSameArray(int[] arr1, int[] arr2) { + if (arr1.length != arr2.length) { + return false; + } + int N = arr1.length; + for (int i = 0; i < N; i++) { + if (arr1[i] != arr2[i]) { + return false; + } + } + return true; + } + + public static void main(String[] args) { + int N = 20; + int max = 30; + int testTime = 1000000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int len = (int) (Math.random() * N); + int[] arr = randomArray(len, max); + int[] ans1 = bestSplit1(arr); + int[] ans2 = bestSplit2(arr); + int[] ans3 = bestSplit3(arr); + if (!isSameArray(ans1, ans2) || !isSameArray(ans1, ans3)) { + System.out.println("Oops!"); + } + } + System.out.println("测试结束"); + } + +} diff --git a/体系学习班/class41/Code03_StoneMerge.java b/体系学习班/class41/Code03_StoneMerge.java new file mode 100644 index 0000000..793d7d2 --- /dev/null +++ b/体系学习班/class41/Code03_StoneMerge.java @@ -0,0 +1,117 @@ +package class41; + +// 四边形不等式:合并石子问题 +public class Code03_StoneMerge { + + public static int[] sum(int[] arr) { + int N = arr.length; + int[] s = new int[N + 1]; + s[0] = 0; + for (int i = 0; i < N; i++) { + s[i + 1] = s[i] + arr[i]; + } + return s; + } + + public static int w(int[] s, int l, int r) { + return s[r + 1] - s[l]; + } + + public static int min1(int[] arr) { + if (arr == null || arr.length < 2) { + return 0; + } + int N = arr.length; + int[] s = sum(arr); + return process1(0, N - 1, s); + } + + public static int process1(int L, int R, int[] s) { + if (L == R) { + return 0; + } + int next = Integer.MAX_VALUE; + for (int leftEnd = L; leftEnd < R; leftEnd++) { + next = Math.min(next, process1(L, leftEnd, s) + process1(leftEnd + 1, R, s)); + } + return next + w(s, L, R); + } + + public static int min2(int[] arr) { + if (arr == null || arr.length < 2) { + return 0; + } + int N = arr.length; + int[] s = sum(arr); + int[][] dp = new int[N][N]; + // dp[i][i] = 0 + for (int L = N - 2; L >= 0; L--) { + for (int R = L + 1; R < N; R++) { + int next = Integer.MAX_VALUE; + // dp(L..leftEnd) + dp[leftEnd+1...R] + 累加和[L...R] + for (int leftEnd = L; leftEnd < R; leftEnd++) { + next = Math.min(next, dp[L][leftEnd] + dp[leftEnd + 1][R]); + } + dp[L][R] = next + w(s, L, R); + } + } + return dp[0][N - 1]; + } + + public static int min3(int[] arr) { + if (arr == null || arr.length < 2) { + return 0; + } + int N = arr.length; + int[] s = sum(arr); + int[][] dp = new int[N][N]; + int[][] best = new int[N][N]; + for (int i = 0; i < N - 1; i++) { + best[i][i + 1] = i; + dp[i][i + 1] = w(s, i, i + 1); + } + for (int L = N - 3; L >= 0; L--) { + for (int R = L + 2; R < N; R++) { + int next = Integer.MAX_VALUE; + int choose = -1; + for (int leftEnd = best[L][R - 1]; leftEnd <= best[L + 1][R]; leftEnd++) { + int cur = dp[L][leftEnd] + dp[leftEnd + 1][R]; + if (cur <= next) { + next = cur; + choose = leftEnd; + } + } + best[L][R] = choose; + dp[L][R] = next + w(s, L, R); + } + } + return dp[0][N - 1]; + } + + public static int[] randomArray(int len, int maxValue) { + int[] arr = new int[len]; + for (int i = 0; i < len; i++) { + arr[i] = (int) (Math.random() * maxValue); + } + return arr; + } + + public static void main(String[] args) { + int N = 15; + int maxValue = 100; + int testTime = 1000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int len = (int) (Math.random() * N); + int[] arr = randomArray(len, maxValue); + int ans1 = min1(arr); + int ans2 = min2(arr); + int ans3 = min3(arr); + if (ans1 != ans2 || ans1 != ans3) { + System.out.println("Oops!"); + } + } + System.out.println("测试结束"); + } + +} diff --git a/体系学习班/class41/Code04_SplitArrayLargestSum.java b/体系学习班/class41/Code04_SplitArrayLargestSum.java new file mode 100644 index 0000000..8d644e6 --- /dev/null +++ b/体系学习班/class41/Code04_SplitArrayLargestSum.java @@ -0,0 +1,193 @@ +package class41; + +// leetcode原题 +// 测试链接:https://leetcode.com/problems/split-array-largest-sum/ +public class Code04_SplitArrayLargestSum { + + // 求原数组arr[L...R]的累加和 + public static int sum(int[] sum, int L, int R) { + return sum[R + 1] - sum[L]; + } + + // 不优化枚举的动态规划方法,O(N^2 * K) + public static int splitArray1(int[] nums, int K) { + int N = nums.length; + int[] sum = new int[N + 1]; + for (int i = 0; i < N; i++) { + sum[i + 1] = sum[i] + nums[i]; + } + int[][] dp = new int[N][K + 1]; + for (int j = 1; j <= K; j++) { + dp[0][j] = nums[0]; + } + for (int i = 1; i < N; i++) { + dp[i][1] = sum(sum, 0, i); + } + // 每一行从上往下 + // 每一列从左往右 + // 根本不去凑优化位置对儿! + for (int i = 1; i < N; i++) { + for (int j = 2; j <= K; j++) { + int ans = Integer.MAX_VALUE; + // 枚举是完全不优化的! + for (int leftEnd = 0; leftEnd <= i; leftEnd++) { + int leftCost = leftEnd == -1 ? 0 : dp[leftEnd][j - 1]; + int rightCost = leftEnd == i ? 0 : sum(sum, leftEnd + 1, i); + int cur = Math.max(leftCost, rightCost); + if (cur < ans) { + ans = cur; + } + } + dp[i][j] = ans; + } + } + return dp[N - 1][K]; + } + + // 课上现场写的方法,用了枚举优化,O(N * K) + public static int splitArray2(int[] nums, int K) { + int N = nums.length; + int[] sum = new int[N + 1]; + for (int i = 0; i < N; i++) { + sum[i + 1] = sum[i] + nums[i]; + } + int[][] dp = new int[N][K + 1]; + int[][] best = new int[N][K + 1]; + for (int j = 1; j <= K; j++) { + dp[0][j] = nums[0]; + best[0][j] = -1; + } + for (int i = 1; i < N; i++) { + dp[i][1] = sum(sum, 0, i); + best[i][1] = -1; + } + // 从第2列开始,从左往右 + // 每一列,从下往上 + // 为什么这样的顺序?因为要去凑(左,下)优化位置对儿! + for (int j = 2; j <= K; j++) { + for (int i = N - 1; i >= 1; i--) { + int down = best[i][j - 1]; + // 如果i==N-1,则不优化上限 + int up = i == N - 1 ? N - 1 : best[i + 1][j]; + int ans = Integer.MAX_VALUE; + int bestChoose = -1; + for (int leftEnd = down; leftEnd <= up; leftEnd++) { + int leftCost = leftEnd == -1 ? 0 : dp[leftEnd][j - 1]; + int rightCost = leftEnd == i ? 0 : sum(sum, leftEnd + 1, i); + int cur = Math.max(leftCost, rightCost); + // 注意下面的if一定是 < 课上的错误就是此处!当时写的 <= ! + // 也就是说,只有取得明显的好处才移动! + // 举个例子来说明,比如[2,6,4,4],3个画匠时候,如下两种方案都是最优: + // (2,6) (4) 两个画匠负责 | (4) 最后一个画匠负责 + // (2,6) (4,4)两个画匠负责 | 最后一个画匠什么也不负责 + // 第一种方案划分为,[0~2] [3~3] + // 第二种方案划分为,[0~3] [无] + // 两种方案的答案都是8,但是划分点位置一定不要移动! + // 只有明显取得好处时(<),划分点位置才移动! + // 也就是说后面的方案如果==前面的最优,不要移动!只有优于前面的最优,才移动 + // 比如上面的两个方案,如果你移动到了方案二,你会得到: + // [2,6,4,4] 三个画匠时,最优为[0~3](前两个画家) [无](最后一个画家), + // 最优划分点为3位置(best[3][3]) + // 那么当4个画匠时,也就是求解dp[3][4]时 + // 因为best[3][3] = 3,这个值提供了dp[3][4]的下限 + // 而事实上dp[3][4]的最优划分为: + // [0~2](三个画家处理) [3~3] (一个画家处理),此时最优解为6 + // 所以,你就得不到dp[3][4]的最优解了,因为划分点已经越过2了 + // 提供了对数器验证,你可以改成<=,对数器和leetcode都过不了 + // 这里是<,对数器和leetcode都能通过 + // 这里面会让同学们感到困惑的点: + // 为啥==的时候,不移动,只有<的时候,才移动呢?例子懂了,但是道理何在? + // 哈哈哈哈哈,看了邮局选址问题,你更懵,请看42节! + if (cur < ans) { + ans = cur; + bestChoose = leftEnd; + } + } + dp[i][j] = ans; + best[i][j] = bestChoose; + } + } + return dp[N - 1][K]; + } + + public static int splitArray3(int[] nums, int M) { + long sum = 0; + for (int i = 0; i < nums.length; i++) { + sum += nums[i]; + } + long l = 0; + long r = sum; + long ans = 0; + while (l <= r) { + long mid = (l + r) / 2; + long cur = getNeedParts(nums, mid); + if (cur <= M) { + ans = mid; + r = mid - 1; + } else { + l = mid + 1; + } + } + return (int) ans; + } + + public static int getNeedParts(int[] arr, long aim) { + for (int i = 0; i < arr.length; i++) { + if (arr[i] > aim) { + return Integer.MAX_VALUE; + } + } + int parts = 1; + int all = arr[0]; + for (int i = 1; i < arr.length; i++) { + if (all + arr[i] > aim) { + parts++; + all = arr[i]; + } else { + all += arr[i]; + } + } + return parts; + } + + public static int[] randomArray(int len, int maxValue) { + int[] arr = new int[len]; + for (int i = 0; i < len; i++) { + arr[i] = (int) (Math.random() * maxValue); + } + return arr; + } + + public static void printArray(int[] arr) { + for (int i = 0; i < arr.length; i++) { + System.out.print(arr[i] + " "); + } + System.out.println(); + } + + public static void main(String[] args) { + int N = 100; + int maxValue = 100; + int testTime = 10000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int len = (int) (Math.random() * N) + 1; + int M = (int) (Math.random() * N) + 1; + int[] arr = randomArray(len, maxValue); + int ans1 = splitArray1(arr, M); + int ans2 = splitArray2(arr, M); + int ans3 = splitArray3(arr, M); + if (ans1 != ans2 || ans1 != ans3) { + System.out.print("arr : "); + printArray(arr); + System.out.println("M : " + M); + System.out.println("ans1 : " + ans1); + System.out.println("ans2 : " + ans2); + System.out.println("ans3 : " + ans3); + System.out.println("Oops!"); + break; + } + } + System.out.println("测试结束"); + } +} diff --git a/体系学习班/class42/Code01_PostOfficeProblem.java b/体系学习班/class42/Code01_PostOfficeProblem.java new file mode 100644 index 0000000..c6fa893 --- /dev/null +++ b/体系学习班/class42/Code01_PostOfficeProblem.java @@ -0,0 +1,115 @@ +package class42; + +import java.util.Arrays; + +public class Code01_PostOfficeProblem { + + public static int min1(int[] arr, int num) { + if (arr == null || num < 1 || arr.length < num) { + return 0; + } + int N = arr.length; + int[][] w = new int[N + 1][N + 1]; + for (int L = 0; L < N; L++) { + for (int R = L + 1; R < N; R++) { + w[L][R] = w[L][R - 1] + arr[R] - arr[(L + R) >> 1]; + } + } + int[][] dp = new int[N][num + 1]; + for (int i = 0; i < N; i++) { + dp[i][1] = w[0][i]; + } + for (int i = 1; i < N; i++) { + for (int j = 2; j <= Math.min(i, num); j++) { + int ans = Integer.MAX_VALUE; + for (int k = 0; k <= i; k++) { + ans = Math.min(ans, dp[k][j - 1] + w[k + 1][i]); + } + dp[i][j] = ans; + } + } + return dp[N - 1][num]; + } + + public static int min2(int[] arr, int num) { + if (arr == null || num < 1 || arr.length < num) { + return 0; + } + int N = arr.length; + int[][] w = new int[N + 1][N + 1]; + for (int L = 0; L < N; L++) { + for (int R = L + 1; R < N; R++) { + w[L][R] = w[L][R - 1] + arr[R] - arr[(L + R) >> 1]; + } + } + int[][] dp = new int[N][num + 1]; + int[][] best = new int[N][num + 1]; + for (int i = 0; i < N; i++) { + dp[i][1] = w[0][i]; + best[i][1] = -1; + } + for (int j = 2; j <= num; j++) { + for (int i = N - 1; i >= j; i--) { + int down = best[i][j - 1]; + int up = i == N - 1 ? N - 1 : best[i + 1][j]; + int ans = Integer.MAX_VALUE; + int bestChoose = -1; + for (int leftEnd = down; leftEnd <= up; leftEnd++) { + int leftCost = leftEnd == -1 ? 0 : dp[leftEnd][j - 1]; + int rightCost = leftEnd == i ? 0 : w[leftEnd + 1][i]; + int cur = leftCost + rightCost; + if (cur <= ans) { + ans = cur; + bestChoose = leftEnd; + } + } + dp[i][j] = ans; + best[i][j] = bestChoose; + } + } + return dp[N - 1][num]; + } + + // for test + public static int[] randomSortedArray(int len, int range) { + int[] arr = new int[len]; + for (int i = 0; i != len; i++) { + arr[i] = (int) (Math.random() * range); + } + Arrays.sort(arr); + return arr; + } + + // for test + public static void printArray(int[] arr) { + for (int i = 0; i != arr.length; i++) { + System.out.print(arr[i] + " "); + } + System.out.println(); + } + + // for test + public static void main(String[] args) { + int N = 30; + int maxValue = 100; + int testTime = 10000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int len = (int) (Math.random() * N) + 1; + int[] arr = randomSortedArray(len, maxValue); + int num = (int) (Math.random() * N) + 1; + int ans1 = min1(arr, num); + int ans2 = min2(arr, num); + if (ans1 != ans2) { + printArray(arr); + System.out.println(num); + System.out.println(ans1); + System.out.println(ans2); + System.out.println("Oops!"); + } + } + System.out.println("测试结束"); + + } + +} diff --git a/体系学习班/class42/Code02_ThrowChessPiecesProblem.java b/体系学习班/class42/Code02_ThrowChessPiecesProblem.java new file mode 100644 index 0000000..d996c70 --- /dev/null +++ b/体系学习班/class42/Code02_ThrowChessPiecesProblem.java @@ -0,0 +1,166 @@ +package class42; + +// leetcode测试链接:https://leetcode.com/problems/super-egg-drop +// 方法1和方法2会超时 +// 方法3勉强通过 +// 方法4打败100% +// 方法5打败100%,方法5是在方法4的基础上做了进一步的常数优化 +public class Code02_ThrowChessPiecesProblem { + + public static int superEggDrop1(int kChess, int nLevel) { + if (nLevel < 1 || kChess < 1) { + return 0; + } + return Process1(nLevel, kChess); + } + + // rest还剩多少层楼需要去验证 + // k还有多少颗棋子能够使用 + // 一定要验证出最高的不会碎的楼层!但是每次都是坏运气。 + // 返回至少需要扔几次? + public static int Process1(int rest, int k) { + if (rest == 0) { + return 0; + } + if (k == 1) { + return rest; + } + int min = Integer.MAX_VALUE; + for (int i = 1; i != rest + 1; i++) { // 第一次扔的时候,仍在了i层 + min = Math.min(min, Math.max(Process1(i - 1, k - 1), Process1(rest - i, k))); + } + return min + 1; + } + + public static int superEggDrop2(int kChess, int nLevel) { + if (nLevel < 1 || kChess < 1) { + return 0; + } + if (kChess == 1) { + return nLevel; + } + int[][] dp = new int[nLevel + 1][kChess + 1]; + for (int i = 1; i != dp.length; i++) { + dp[i][1] = i; + } + for (int i = 1; i != dp.length; i++) { + for (int j = 2; j != dp[0].length; j++) { + int min = Integer.MAX_VALUE; + for (int k = 1; k != i + 1; k++) { + min = Math.min(min, Math.max(dp[k - 1][j - 1], dp[i - k][j])); + } + dp[i][j] = min + 1; + } + } + return dp[nLevel][kChess]; + } + + public static int superEggDrop3(int kChess, int nLevel) { + if (nLevel < 1 || kChess < 1) { + return 0; + } + if (kChess == 1) { + return nLevel; + } + int[][] dp = new int[nLevel + 1][kChess + 1]; + for (int i = 1; i != dp.length; i++) { + dp[i][1] = i; + } + int[][] best = new int[nLevel + 1][kChess + 1]; + for (int i = 1; i != dp[0].length; i++) { + dp[1][i] = 1; + best[1][i] = 1; + } + for (int i = 2; i < nLevel + 1; i++) { + for (int j = kChess; j > 1; j--) { + int ans = Integer.MAX_VALUE; + int bestChoose = -1; + int down = best[i - 1][j]; + int up = j == kChess ? i : best[i][j + 1]; + for (int first = down; first <= up; first++) { + int cur = Math.max(dp[first - 1][j - 1], dp[i - first][j]); + if (cur <= ans) { + ans = cur; + bestChoose = first; + } + } + dp[i][j] = ans + 1; + best[i][j] = bestChoose; + } + } + return dp[nLevel][kChess]; + } + + public static int superEggDrop4(int kChess, int nLevel) { + if (nLevel < 1 || kChess < 1) { + return 0; + } + int[] dp = new int[kChess]; + int res = 0; + while (true) { + res++; + int previous = 0; + for (int i = 0; i < dp.length; i++) { + int tmp = dp[i]; + dp[i] = dp[i] + previous + 1; + previous = tmp; + if (dp[i] >= nLevel) { + return res; + } + } + } + } + + public static int superEggDrop5(int kChess, int nLevel) { + if (nLevel < 1 || kChess < 1) { + return 0; + } + int bsTimes = log2N(nLevel) + 1; + if (kChess >= bsTimes) { + return bsTimes; + } + int[] dp = new int[kChess]; + int res = 0; + while (true) { + res++; + int previous = 0; + for (int i = 0; i < dp.length; i++) { + int tmp = dp[i]; + dp[i] = dp[i] + previous + 1; + previous = tmp; + if (dp[i] >= nLevel) { + return res; + } + } + } + } + + public static int log2N(int n) { + int res = -1; + while (n != 0) { + res++; + n >>>= 1; + } + return res; + } + + public static void main(String[] args) { + int maxN = 500; + int maxK = 30; + int testTime = 1000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int N = (int) (Math.random() * maxN) + 1; + int K = (int) (Math.random() * maxK) + 1; + int ans2 = superEggDrop2(K, N); + int ans3 = superEggDrop3(K, N); + int ans4 = superEggDrop4(K, N); + int ans5 = superEggDrop5(K, N); + if (ans2 != ans3 || ans4 != ans5 || ans2 != ans4) { + System.out.println("出错了!"); + } + } + System.out.println("测试结束"); + } + +} diff --git a/体系学习班/class43/Code01_CanIWin.java b/体系学习班/class43/Code01_CanIWin.java new file mode 100644 index 0000000..608b1f1 --- /dev/null +++ b/体系学习班/class43/Code01_CanIWin.java @@ -0,0 +1,116 @@ +package class43; + +// leetcode 464题 +public class Code01_CanIWin { + + // 1~choose 拥有的数字 + // total 一开始的剩余 + // 返回先手会不会赢 + public static boolean canIWin0(int choose, int total) { + if (total == 0) { + return true; + } + if ((choose * (choose + 1) >> 1) < total) { + return false; + } + int[] arr = new int[choose]; + for (int i = 0; i < choose; i++) { + arr[i] = i + 1; + } + // arr[i] != -1 表示arr[i]这个数字还没被拿走 + // arr[i] == -1 表示arr[i]这个数字已经被拿走 + // 集合,arr,1~choose + return process(arr, total); + } + + // 当前轮到先手拿, + // 先手只能选择在arr中还存在的数字, + // 还剩rest这么值, + // 返回先手会不会赢 + public static boolean process(int[] arr, int rest) { + if (rest <= 0) { + return false; + } + // 先手去尝试所有的情况 + for (int i = 0; i < arr.length; i++) { + if (arr[i] != -1) { + int cur = arr[i]; + arr[i] = -1; + boolean next = process(arr, rest - cur); + arr[i] = cur; + if (!next) { + return true; + } + } + } + return false; + } + + // 这个是暴力尝试,思路是正确的,超时而已 + public static boolean canIWin1(int choose, int total) { + if (total == 0) { + return true; + } + if ((choose * (choose + 1) >> 1) < total) { + return false; + } + return process1(choose, 0, total); + } + + // 当前轮到先手拿, + // 先手可以拿1~choose中的任何一个数字 + // status i位如果为0,代表没拿,当前可以拿 + // i位为1,代表已经拿过了,当前不能拿 + // 还剩rest这么值, + // 返回先手会不会赢 + public static boolean process1(int choose, int status, int rest) { + if (rest <= 0) { + return false; + } + for (int i = 1; i <= choose; i++) { + if (((1 << i) & status) == 0) { // i 这个数字,是此时先手的决定! + if (!process1(choose, (status | (1 << i)), rest - i)) { + return true; + } + } + } + return false; + } + + // 暴力尝试改动态规划而已 + public static boolean canIWin2(int choose, int total) { + if (total == 0) { + return true; + } + if ((choose * (choose + 1) >> 1) < total) { + return false; + } + int[] dp = new int[1 << (choose + 1)]; + // dp[status] == 1 true + // dp[status] == -1 false + // dp[status] == 0 process(status) 没算过!去算! + return process2(choose, 0, total, dp); + } + + // 为什么明明status和rest是两个可变参数,却只用status来代表状态(也就是dp) + // 因为选了一批数字之后,得到的和一定是一样的,所以rest是由status决定的,所以rest不需要参与记忆化搜索 + public static boolean process2(int choose, int status, int rest, int[] dp) { + if (dp[status] != 0) { + return dp[status] == 1 ? true : false; + } + boolean ans = false; + if (rest > 0) { + for (int i = 1; i <= choose; i++) { + if (((1 << i) & status) == 0) { + if (!process2(choose, (status | (1 << i)), rest - i, dp)) { + ans = true; + break; + } + } + } + } + dp[status] = ans ? 1 : -1; + return ans; + } + +} diff --git a/体系学习班/class43/Code02_TSP.java b/体系学习班/class43/Code02_TSP.java new file mode 100644 index 0000000..bf984a6 --- /dev/null +++ b/体系学习班/class43/Code02_TSP.java @@ -0,0 +1,295 @@ +package class43; + +import java.util.ArrayList; +import java.util.List; + +public class Code02_TSP { + + public static int t1(int[][] matrix) { + int N = matrix.length; // 0...N-1 + // set + // set.get(i) != null i这座城市在集合里 + // set.get(i) == null i这座城市不在集合里 + List set = new ArrayList<>(); + for (int i = 0; i < N; i++) { + set.add(1); + } + return func1(matrix, set, 0); + } + + // 任何两座城市之间的距离,可以在matrix里面拿到 + // set中表示着哪些城市的集合, + // start这座城一定在set里, + // 从start出发,要把set中所有的城市过一遍,最终回到0这座城市,最小距离是多少 + public static int func1(int[][] matrix, List set, int start) { + int cityNum = 0; + for (int i = 0; i < set.size(); i++) { + if (set.get(i) != null) { + cityNum++; + } + } + if (cityNum == 1) { + return matrix[start][0]; + } + // cityNum > 1 不只start这一座城 + set.set(start, null); + int min = Integer.MAX_VALUE; + for (int i = 0; i < set.size(); i++) { + if (set.get(i) != null) { + // start -> i i... -> 0 + int cur = matrix[start][i] + func1(matrix, set, i); + min = Math.min(min, cur); + } + } + set.set(start, 1); + return min; + } + + public static int t2(int[][] matrix) { + int N = matrix.length; // 0...N-1 + // 7座城 1111111 + int allCity = (1 << N) - 1; + return f2(matrix, allCity, 0); + } + + // 任何两座城市之间的距离,可以在matrix里面拿到 + // set中表示着哪些城市的集合, + // start这座城一定在set里, + // 从start出发,要把set中所有的城市过一遍,最终回到0这座城市,最小距离是多少 + public static int f2(int[][] matrix, int cityStatus, int start) { + // cityStatus == cityStatux & (~cityStaus + 1) + + if (cityStatus == (cityStatus & (~cityStatus + 1))) { + return matrix[start][0]; + } + + // 把start位的1去掉, + cityStatus &= (~(1 << start)); + int min = Integer.MAX_VALUE; + // 枚举所有的城市 + for (int move = 0; move < matrix.length; move++) { + if ((cityStatus & (1 << move)) != 0) { + int cur = matrix[start][move] + f2(matrix, cityStatus, move); + min = Math.min(min, cur); + } + } + cityStatus |= (1 << start); + return min; + } + + public static int t3(int[][] matrix) { + int N = matrix.length; // 0...N-1 + // 7座城 1111111 + int allCity = (1 << N) - 1; + int[][] dp = new int[1 << N][N]; + for (int i = 0; i < (1 << N); i++) { + for (int j = 0; j < N; j++) { + dp[i][j] = -1; + } + } + return f3(matrix, allCity, 0, dp); + } + + // 任何两座城市之间的距离,可以在matrix里面拿到 + // set中表示着哪些城市的集合, + // start这座城一定在set里, + // 从start出发,要把set中所有的城市过一遍,最终回到0这座城市,最小距离是多少 + public static int f3(int[][] matrix, int cityStatus, int start, int[][] dp) { + if (dp[cityStatus][start] != -1) { + return dp[cityStatus][start]; + } + if (cityStatus == (cityStatus & (~cityStatus + 1))) { + dp[cityStatus][start] = matrix[start][0]; + } else { + // 把start位的1去掉, + cityStatus &= (~(1 << start)); + int min = Integer.MAX_VALUE; + // 枚举所有的城市 + for (int move = 0; move < matrix.length; move++) { + if (move != start && (cityStatus & (1 << move)) != 0) { + int cur = matrix[start][move] + f3(matrix, cityStatus, move, dp); + min = Math.min(min, cur); + } + } + cityStatus |= (1 << start); + dp[cityStatus][start] = min; + } + return dp[cityStatus][start]; + } + + public static int t4(int[][] matrix) { + int N = matrix.length; // 0...N-1 + int statusNums = 1 << N; + int[][] dp = new int[statusNums][N]; + + for (int status = 0; status < statusNums; status++) { + for (int start = 0; start < N; start++) { + if ((status & (1 << start)) != 0) { + if (status == (status & (~status + 1))) { + dp[status][start] = matrix[start][0]; + } else { + int min = Integer.MAX_VALUE; + // start 城市在status里去掉之后,的状态 + int preStatus = status & (~(1 << start)); + // start -> i + for (int i = 0; i < N; i++) { + if ((preStatus & (1 << i)) != 0) { + int cur = matrix[start][i] + dp[preStatus][i]; + min = Math.min(min, cur); + } + } + dp[status][start] = min; + } + } + } + } + return dp[statusNums - 1][0]; + } + + // matrix[i][j] -> i城市到j城市的距离 + public static int tsp1(int[][] matrix, int origin) { + if (matrix == null || matrix.length < 2 || origin < 0 || origin >= matrix.length) { + return 0; + } + // 要考虑的集合 + ArrayList cities = new ArrayList<>(); + // cities[0] != null 表示0城在集合里 + // cities[i] != null 表示i城在集合里 + for (int i = 0; i < matrix.length; i++) { + cities.add(1); + } + // null,1,1,1,1,1,1 + // origin城不参与集合 + cities.set(origin, null); + return process(matrix, origin, cities, origin); + } + + // matrix 所有距离,存在其中 + // origin 固定参数,唯一的目标 + // cities 要考虑的集合,一定不含有origin + // 当前来到的城市是谁,cur + public static int process(int[][] matrix, int aim, ArrayList cities, int cur) { + boolean hasCity = false; // 集团中还是否有城市 + int ans = Integer.MAX_VALUE; + for (int i = 0; i < cities.size(); i++) { + if (cities.get(i) != null) { + hasCity = true; + cities.set(i, null); + // matrix[cur][i] + f(i, 集团(去掉i) ) + ans = Math.min(ans, matrix[cur][i] + process(matrix, aim, cities, i)); + cities.set(i, 1); + } + } + return hasCity ? ans : matrix[cur][aim]; + } + + // cities 里,一定含有cur这座城 + // 解决的是,集合从cur出发,通过集合里所有的城市,最终来到aim,最短距离 + public static int process2(int[][] matrix, int aim, ArrayList cities, int cur) { + if (cities.size() == 1) { + return matrix[cur][aim]; + } + cities.set(cur, null); + int ans = Integer.MAX_VALUE; + for (int i = 0; i < cities.size(); i++) { + if (cities.get(i) != null) { + int dis = matrix[cur][i] + process2(matrix, aim, cities, i); + ans = Math.min(ans, dis); + } + } + cities.set(cur, 1); + return ans; + } + + public static int tsp2(int[][] matrix, int origin) { + if (matrix == null || matrix.length < 2 || origin < 0 || origin >= matrix.length) { + return 0; + } + int N = matrix.length - 1; // 除去origin之后是n-1个点 + int S = 1 << N; // 状态数量 + int[][] dp = new int[S][N]; + int icity = 0; + int kcity = 0; + for (int i = 0; i < N; i++) { + icity = i < origin ? i : i + 1; + // 00000000 i + dp[0][i] = matrix[icity][origin]; + } + for (int status = 1; status < S; status++) { + // 尝试每一种状态 status = 0 0 1 0 0 0 0 0 0 + // 下标 8 7 6 5 4 3 2 1 0 + for (int i = 0; i < N; i++) { + // i 枚举的出发城市 + dp[status][i] = Integer.MAX_VALUE; + if ((1 << i & status) != 0) { + // 如果i这座城是可以枚举的,i = 6 , i对应的原始城的编号,icity + icity = i < origin ? i : i + 1; + for (int k = 0; k < N; k++) { // i 这一步连到的点,k + if ((1 << k & status) != 0) { // i 这一步可以连到k + kcity = k < origin ? k : k + 1; // k对应的原始城的编号,kcity + dp[status][i] = Math.min(dp[status][i], dp[status ^ (1 << i)][k] + matrix[icity][kcity]); + } + } + } + } + } + int ans = Integer.MAX_VALUE; + for (int i = 0; i < N; i++) { + icity = i < origin ? i : i + 1; + ans = Math.min(ans, dp[S - 1][i] + matrix[origin][icity]); + } + return ans; + } + + public static int[][] generateGraph(int maxSize, int maxValue) { + int len = (int) (Math.random() * maxSize) + 1; + int[][] matrix = new int[len][len]; + for (int i = 0; i < len; i++) { + for (int j = 0; j < len; j++) { + matrix[i][j] = (int) (Math.random() * maxValue) + 1; + } + } + for (int i = 0; i < len; i++) { + matrix[i][i] = 0; + } + return matrix; + } + + public static void main(String[] args) { + int len = 10; + int value = 100; + System.out.println("功能测试开始"); + for (int i = 0; i < 20000; i++) { + int[][] matrix = generateGraph(len, value); + int origin = (int) (Math.random() * matrix.length); + int ans1 = t3(matrix); + int ans2 = t4(matrix); + int ans3 = tsp2(matrix, origin); + if (ans1 != ans2 || ans1 != ans3) { + System.out.println("fuck"); + } + } + System.out.println("功能测试结束"); + + len = 22; + System.out.println("性能测试开始,数据规模 : " + len); + int[][] matrix = new int[len][len]; + for (int i = 0; i < len; i++) { + for (int j = 0; j < len; j++) { + matrix[i][j] = (int) (Math.random() * value) + 1; + } + } + for (int i = 0; i < len; i++) { + matrix[i][i] = 0; + } + long start; + long end; + start = System.currentTimeMillis(); + t4(matrix); + end = System.currentTimeMillis(); + System.out.println("运行时间 : " + (end - start) + " 毫秒"); + System.out.println("性能测试结束"); + + } + +} diff --git a/体系学习班/class43/Code03_PavingTile.java b/体系学习班/class43/Code03_PavingTile.java new file mode 100644 index 0000000..c4bf363 --- /dev/null +++ b/体系学习班/class43/Code03_PavingTile.java @@ -0,0 +1,215 @@ +package class43; + +public class Code03_PavingTile { + + /* + * 2*M铺地的问题非常简单,这个是解决N*M铺地的问题 + */ + + public static int ways1(int N, int M) { + if (N < 1 || M < 1 || ((N * M) & 1) != 0) { + return 0; + } + if (N == 1 || M == 1) { + return 1; + } + int[] pre = new int[M]; // pre代表-1行的状况 + for (int i = 0; i < pre.length; i++) { + pre[i] = 1; + } + return process(pre, 0, N); + } + + // pre 表示level-1行的状态 + // level表示,正在level行做决定 + // N 表示一共有多少行 固定的 + // level-2行及其之上所有行,都摆满砖了 + // level做决定,让所有区域都满,方法数返回 + public static int process(int[] pre, int level, int N) { + if (level == N) { // base case + for (int i = 0; i < pre.length; i++) { + if (pre[i] == 0) { + return 0; + } + } + return 1; + } + + // 没到终止行,可以选择在当前的level行摆瓷砖 + int[] op = getOp(pre); + return dfs(op, 0, level, N); + } + + // op[i] == 0 可以考虑摆砖 + // op[i] == 1 只能竖着向上 + public static int dfs(int[] op, int col, int level, int N) { + // 在列上自由发挥,玩深度优先遍历,当col来到终止列,i行的决定做完了 + // 轮到i+1行,做决定 + if (col == op.length) { + return process(op, level + 1, N); + } + int ans = 0; + // col位置不横摆 + ans += dfs(op, col + 1, level, N); // col位置上不摆横转 + // col位置横摆, 向右 + if (col + 1 < op.length && op[col] == 0 && op[col + 1] == 0) { + op[col] = 1; + op[col + 1] = 1; + ans += dfs(op, col + 2, level, N); + op[col] = 0; + op[col + 1] = 0; + } + return ans; + } + + public static int[] getOp(int[] pre) { + int[] cur = new int[pre.length]; + for (int i = 0; i < pre.length; i++) { + cur[i] = pre[i] ^ 1; + } + return cur; + } + + // Min (N,M) 不超过 32 + public static int ways2(int N, int M) { + if (N < 1 || M < 1 || ((N * M) & 1) != 0) { + return 0; + } + if (N == 1 || M == 1) { + return 1; + } + int max = Math.max(N, M); + int min = Math.min(N, M); + int pre = (1 << min) - 1; + return process2(pre, 0, max, min); + } + + // 上一行的状态,是pre,limit是用来对齐的,固定参数不用管 + // 当前来到i行,一共N行,返回填满的方法数 + public static int process2(int pre, int i, int N, int M) { + if (i == N) { // base case + return pre == ((1 << M) - 1) ? 1 : 0; + } + int op = ((~pre) & ((1 << M) - 1)); + return dfs2(op, M - 1, i, N, M); + } + + public static int dfs2(int op, int col, int level, int N, int M) { + if (col == -1) { + return process2(op, level + 1, N, M); + } + int ans = 0; + ans += dfs2(op, col - 1, level, N, M); + if ((op & (1 << col)) == 0 && col - 1 >= 0 && (op & (1 << (col - 1))) == 0) { + ans += dfs2((op | (3 << (col - 1))), col - 2, level, N, M); + } + return ans; + } + + // 记忆化搜索的解 + // Min(N,M) 不超过 32 + public static int ways3(int N, int M) { + if (N < 1 || M < 1 || ((N * M) & 1) != 0) { + return 0; + } + if (N == 1 || M == 1) { + return 1; + } + int max = Math.max(N, M); + int min = Math.min(N, M); + int pre = (1 << min) - 1; + int[][] dp = new int[1 << min][max + 1]; + for (int i = 0; i < dp.length; i++) { + for (int j = 0; j < dp[0].length; j++) { + dp[i][j] = -1; + } + } + return process3(pre, 0, max, min, dp); + } + + public static int process3(int pre, int i, int N, int M, int[][] dp) { + if (dp[pre][i] != -1) { + return dp[pre][i]; + } + int ans = 0; + if (i == N) { + ans = pre == ((1 << M) - 1) ? 1 : 0; + } else { + int op = ((~pre) & ((1 << M) - 1)); + ans = dfs3(op, M - 1, i, N, M, dp); + } + dp[pre][i] = ans; + return ans; + } + + public static int dfs3(int op, int col, int level, int N, int M, int[][] dp) { + if (col == -1) { + return process3(op, level + 1, N, M, dp); + } + int ans = 0; + ans += dfs3(op, col - 1, level, N, M, dp); + if (col > 0 && (op & (3 << (col - 1))) == 0) { + ans += dfs3((op | (3 << (col - 1))), col - 2, level, N, M, dp); + } + return ans; + } + + // 严格位置依赖的动态规划解 + public static int ways4(int N, int M) { + if (N < 1 || M < 1 || ((N * M) & 1) != 0) { + return 0; + } + if (N == 1 || M == 1) { + return 1; + } + int big = N > M ? N : M; + int small = big == N ? M : N; + int sn = 1 << small; + int limit = sn - 1; + int[] dp = new int[sn]; + dp[limit] = 1; + int[] cur = new int[sn]; + for (int level = 0; level < big; level++) { + for (int status = 0; status < sn; status++) { + if (dp[status] != 0) { + int op = (~status) & limit; + dfs4(dp[status], op, 0, small - 1, cur); + } + } + for (int i = 0; i < sn; i++) { + dp[i] = 0; + } + int[] tmp = dp; + dp = cur; + cur = tmp; + } + return dp[limit]; + } + + public static void dfs4(int way, int op, int index, int end, int[] cur) { + if (index == end) { + cur[op] += way; + } else { + dfs4(way, op, index + 1, end, cur); + if (((3 << index) & op) == 0) { // 11 << index 可以放砖 + dfs4(way, op | (3 << index), index + 1, end, cur); + } + } + } + + public static void main(String[] args) { + int N = 8; + int M = 6; + System.out.println(ways1(N, M)); + System.out.println(ways2(N, M)); + System.out.println(ways3(N, M)); + System.out.println(ways4(N, M)); + + N = 10; + M = 10; + System.out.println("========="); + System.out.println(ways3(N, M)); + System.out.println(ways4(N, M)); + } + +} diff --git a/体系学习班/class44/Code01_LastSubstringInLexicographicalOrder.java b/体系学习班/class44/Code01_LastSubstringInLexicographicalOrder.java new file mode 100644 index 0000000..27ac147 --- /dev/null +++ b/体系学习班/class44/Code01_LastSubstringInLexicographicalOrder.java @@ -0,0 +1,136 @@ +package class44; + +// 测试链接: https://leetcode.com/problems/last-substring-in-lexicographical-order/ +public class Code01_LastSubstringInLexicographicalOrder { + + public static String lastSubstring(String s) { + if (s == null || s.length() == 0) { + return s; + } + int N = s.length(); + char[] str = s.toCharArray(); + int min = Integer.MAX_VALUE; + int max = Integer.MIN_VALUE; + for (char cha : str) { + min = Math.min(min, cha); + max = Math.max(max, cha); + } + int[] arr = new int[N]; + for (int i = 0; i < N; i++) { + arr[i] = str[i] - min + 1; + } + DC3 dc3 = new DC3(arr, max - min + 1); + return s.substring(dc3.sa[N - 1]); + } + + public static class DC3 { + + public int[] sa; + + public DC3(int[] nums, int max) { + sa = sa(nums, max); + } + + private int[] sa(int[] nums, int max) { + int n = nums.length; + int[] arr = new int[n + 3]; + for (int i = 0; i < n; i++) { + arr[i] = nums[i]; + } + return skew(arr, n, max); + } + + private int[] skew(int[] nums, int n, int K) { + int n0 = (n + 2) / 3, n1 = (n + 1) / 3, n2 = n / 3, n02 = n0 + n2; + int[] s12 = new int[n02 + 3], sa12 = new int[n02 + 3]; + for (int i = 0, j = 0; i < n + (n0 - n1); ++i) { + if (0 != i % 3) { + s12[j++] = i; + } + } + radixPass(nums, s12, sa12, 2, n02, K); + radixPass(nums, sa12, s12, 1, n02, K); + radixPass(nums, s12, sa12, 0, n02, K); + int name = 0, c0 = -1, c1 = -1, c2 = -1; + for (int i = 0; i < n02; ++i) { + if (c0 != nums[sa12[i]] || c1 != nums[sa12[i] + 1] || c2 != nums[sa12[i] + 2]) { + name++; + c0 = nums[sa12[i]]; + c1 = nums[sa12[i] + 1]; + c2 = nums[sa12[i] + 2]; + } + if (1 == sa12[i] % 3) { + s12[sa12[i] / 3] = name; + } else { + s12[sa12[i] / 3 + n0] = name; + } + } + if (name < n02) { + sa12 = skew(s12, n02, name); + for (int i = 0; i < n02; i++) { + s12[sa12[i]] = i + 1; + } + } else { + for (int i = 0; i < n02; i++) { + sa12[s12[i] - 1] = i; + } + } + int[] s0 = new int[n0], sa0 = new int[n0]; + for (int i = 0, j = 0; i < n02; i++) { + if (sa12[i] < n0) { + s0[j++] = 3 * sa12[i]; + } + } + radixPass(nums, s0, sa0, 0, n0, K); + int[] sa = new int[n]; + for (int p = 0, t = n0 - n1, k = 0; k < n; k++) { + int i = sa12[t] < n0 ? sa12[t] * 3 + 1 : (sa12[t] - n0) * 3 + 2; + int j = sa0[p]; + if (sa12[t] < n0 ? leq(nums[i], s12[sa12[t] + n0], nums[j], s12[j / 3]) + : leq(nums[i], nums[i + 1], s12[sa12[t] - n0 + 1], nums[j], nums[j + 1], s12[j / 3 + n0])) { + sa[k] = i; + t++; + if (t == n02) { + for (k++; p < n0; p++, k++) { + sa[k] = sa0[p]; + } + } + } else { + sa[k] = j; + p++; + if (p == n0) { + for (k++; t < n02; t++, k++) { + sa[k] = sa12[t] < n0 ? sa12[t] * 3 + 1 : (sa12[t] - n0) * 3 + 2; + } + } + } + } + return sa; + } + + private void radixPass(int[] nums, int[] input, int[] output, int offset, int n, int k) { + int[] cnt = new int[k + 1]; + for (int i = 0; i < n; ++i) { + cnt[nums[input[i] + offset]]++; + } + for (int i = 0, sum = 0; i < cnt.length; ++i) { + int t = cnt[i]; + cnt[i] = sum; + sum += t; + } + for (int i = 0; i < n; ++i) { + output[cnt[nums[input[i] + offset]]++] = input[i]; + } + } + + private boolean leq(int a1, int a2, int b1, int b2) { + return a1 < b1 || (a1 == b1 && a2 <= b2); + } + + private boolean leq(int a1, int a2, int a3, int b1, int b2, int b3) { + return a1 < b1 || (a1 == b1 && leq(a2, a3, b2, b3)); + } + + } + +} diff --git a/体系学习班/class44/DC3.java b/体系学习班/class44/DC3.java new file mode 100644 index 0000000..47e7601 --- /dev/null +++ b/体系学习班/class44/DC3.java @@ -0,0 +1,168 @@ +package class44; + +public class DC3 { + + public int[] sa; + + public int[] rank; + + public int[] height; + + // 构造方法的约定: + // 数组叫nums,如果你是字符串,请转成整型数组nums + // 数组中,最小值>=1 + // 如果不满足,处理成满足的,也不会影响使用 + // max, nums里面最大值是多少 + public DC3(int[] nums, int max) { + sa = sa(nums, max); + rank = rank(); + height = height(nums); + } + + private int[] sa(int[] nums, int max) { + int n = nums.length; + int[] arr = new int[n + 3]; + for (int i = 0; i < n; i++) { + arr[i] = nums[i]; + } + return skew(arr, n, max); + } + + private int[] skew(int[] nums, int n, int K) { + int n0 = (n + 2) / 3, n1 = (n + 1) / 3, n2 = n / 3, n02 = n0 + n2; + int[] s12 = new int[n02 + 3], sa12 = new int[n02 + 3]; + for (int i = 0, j = 0; i < n + (n0 - n1); ++i) { + if (0 != i % 3) { + s12[j++] = i; + } + } + radixPass(nums, s12, sa12, 2, n02, K); + radixPass(nums, sa12, s12, 1, n02, K); + radixPass(nums, s12, sa12, 0, n02, K); + int name = 0, c0 = -1, c1 = -1, c2 = -1; + for (int i = 0; i < n02; ++i) { + if (c0 != nums[sa12[i]] || c1 != nums[sa12[i] + 1] || c2 != nums[sa12[i] + 2]) { + name++; + c0 = nums[sa12[i]]; + c1 = nums[sa12[i] + 1]; + c2 = nums[sa12[i] + 2]; + } + if (1 == sa12[i] % 3) { + s12[sa12[i] / 3] = name; + } else { + s12[sa12[i] / 3 + n0] = name; + } + } + if (name < n02) { + sa12 = skew(s12, n02, name); + for (int i = 0; i < n02; i++) { + s12[sa12[i]] = i + 1; + } + } else { + for (int i = 0; i < n02; i++) { + sa12[s12[i] - 1] = i; + } + } + int[] s0 = new int[n0], sa0 = new int[n0]; + for (int i = 0, j = 0; i < n02; i++) { + if (sa12[i] < n0) { + s0[j++] = 3 * sa12[i]; + } + } + radixPass(nums, s0, sa0, 0, n0, K); + int[] sa = new int[n]; + for (int p = 0, t = n0 - n1, k = 0; k < n; k++) { + int i = sa12[t] < n0 ? sa12[t] * 3 + 1 : (sa12[t] - n0) * 3 + 2; + int j = sa0[p]; + if (sa12[t] < n0 ? leq(nums[i], s12[sa12[t] + n0], nums[j], s12[j / 3]) + : leq(nums[i], nums[i + 1], s12[sa12[t] - n0 + 1], nums[j], nums[j + 1], s12[j / 3 + n0])) { + sa[k] = i; + t++; + if (t == n02) { + for (k++; p < n0; p++, k++) { + sa[k] = sa0[p]; + } + } + } else { + sa[k] = j; + p++; + if (p == n0) { + for (k++; t < n02; t++, k++) { + sa[k] = sa12[t] < n0 ? sa12[t] * 3 + 1 : (sa12[t] - n0) * 3 + 2; + } + } + } + } + return sa; + } + + private void radixPass(int[] nums, int[] input, int[] output, int offset, int n, int k) { + int[] cnt = new int[k + 1]; + for (int i = 0; i < n; ++i) { + cnt[nums[input[i] + offset]]++; + } + for (int i = 0, sum = 0; i < cnt.length; ++i) { + int t = cnt[i]; + cnt[i] = sum; + sum += t; + } + for (int i = 0; i < n; ++i) { + output[cnt[nums[input[i] + offset]]++] = input[i]; + } + } + + private boolean leq(int a1, int a2, int b1, int b2) { + return a1 < b1 || (a1 == b1 && a2 <= b2); + } + + private boolean leq(int a1, int a2, int a3, int b1, int b2, int b3) { + return a1 < b1 || (a1 == b1 && leq(a2, a3, b2, b3)); + } + + private int[] rank() { + int n = sa.length; + int[] ans = new int[n]; + for (int i = 0; i < n; i++) { + ans[sa[i]] = i; + } + return ans; + } + + private int[] height(int[] s) { + int n = s.length; + int[] ans = new int[n]; + for (int i = 0, k = 0; i < n; ++i) { + if (rank[i] != 0) { + if (k > 0) { + --k; + } + int j = sa[rank[i] - 1]; + while (i + k < n && j + k < n && s[i + k] == s[j + k]) { + ++k; + } + ans[rank[i]] = k; + } + } + return ans; + } + + // 为了测试 + public static int[] randomArray(int len, int maxValue) { + int[] arr = new int[len]; + for (int i = 0; i < len; i++) { + arr[i] = (int) (Math.random() * maxValue) + 1; + } + return arr; + } + + // 为了测试 + public static void main(String[] args) { + int len = 100000; + int maxValue = 100; + long start = System.currentTimeMillis(); + new DC3(randomArray(len, maxValue), maxValue); + long end = System.currentTimeMillis(); + System.out.println("数据量 " + len + ", 运行时间 " + (end - start) + " ms"); + } + +} \ No newline at end of file diff --git a/体系学习班/class44/DC3_Algorithm.pdf b/体系学习班/class44/DC3_Algorithm.pdf new file mode 100644 index 0000000000000000000000000000000000000000..f053a6749606ad2ae8abbdffb6bc2a959c2f1617 GIT binary patch literal 192716 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 literal 0 HcmV?d00001 diff --git a/体系学习班/class45/Code01_InsertS2MakeMostAlphabeticalOrder.java b/体系学习班/class45/Code01_InsertS2MakeMostAlphabeticalOrder.java new file mode 100644 index 0000000..0962781 --- /dev/null +++ b/体系学习班/class45/Code01_InsertS2MakeMostAlphabeticalOrder.java @@ -0,0 +1,261 @@ +package class45; + +public class Code01_InsertS2MakeMostAlphabeticalOrder { + + // 暴力方法 + public static String right(String s1, String s2) { + if (s1 == null || s1.length() == 0) { + return s2; + } + if (s2 == null || s2.length() == 0) { + return s1; + } + String p1 = s1 + s2; + String p2 = s2 + s1; + String ans = p1.compareTo(p2) > 0 ? p1 : p2; + for (int end = 1; end < s1.length(); end++) { + String cur = s1.substring(0, end) + s2 + s1.substring(end); + if (cur.compareTo(ans) > 0) { + ans = cur; + } + } + return ans; + } + + // 正式方法 O(N+M) + O(M^2) + // N : s1长度 + // M : s2长度 + public static String maxCombine(String s1, String s2) { + if (s1 == null || s1.length() == 0) { + return s2; + } + if (s2 == null || s2.length() == 0) { + return s1; + } + char[] str1 = s1.toCharArray(); + char[] str2 = s2.toCharArray(); + int N = str1.length; + int M = str2.length; + int min = str1[0]; + int max = str1[0]; + for (int i = 1; i < N; i++) { + min = Math.min(min, str1[i]); + max = Math.max(max, str1[i]); + } + for (int i = 0; i < M; i++) { + min = Math.min(min, str2[i]); + max = Math.max(max, str2[i]); + } + int[] all = new int[N + M + 1]; + int index = 0; + for (int i = 0; i < N; i++) { + all[index++] = str1[i] - min + 2; + } + all[index++] = 1; + for (int i = 0; i < M; i++) { + all[index++] = str2[i] - min + 2; + } + DC3 dc3 = new DC3(all, max - min + 2); + int[] rank = dc3.rank; + int comp = N + 1; + for (int i = 0; i < N; i++) { + if (rank[i] < rank[comp]) { + int best = bestSplit(s1, s2, i); + return s1.substring(0, best) + s2 + s1.substring(best); + } + } + return s1 + s2; + } + + public static int bestSplit(String s1, String s2, int first) { + int N = s1.length(); + int M = s2.length(); + int end = N; + for (int i = first, j = 0; i < N && j < M; i++, j++) { + if (s1.charAt(i) < s2.charAt(j)) { + end = i; + break; + } + } + String bestPrefix = s2; + int bestSplit = first; + for (int i = first + 1, j = M - 1; i <= end; i++, j--) { + String curPrefix = s1.substring(first, i) + s2.substring(0, j); + if (curPrefix.compareTo(bestPrefix) >= 0) { + bestPrefix = curPrefix; + bestSplit = i; + } + } + return bestSplit; + } + + public static class DC3 { + + public int[] sa; + + public int[] rank; + + public DC3(int[] nums, int max) { + sa = sa(nums, max); + rank = rank(); + } + + private int[] sa(int[] nums, int max) { + int n = nums.length; + int[] arr = new int[n + 3]; + for (int i = 0; i < n; i++) { + arr[i] = nums[i]; + } + return skew(arr, n, max); + } + + private int[] skew(int[] nums, int n, int K) { + int n0 = (n + 2) / 3, n1 = (n + 1) / 3, n2 = n / 3, n02 = n0 + n2; + int[] s12 = new int[n02 + 3], sa12 = new int[n02 + 3]; + for (int i = 0, j = 0; i < n + (n0 - n1); ++i) { + if (0 != i % 3) { + s12[j++] = i; + } + } + radixPass(nums, s12, sa12, 2, n02, K); + radixPass(nums, sa12, s12, 1, n02, K); + radixPass(nums, s12, sa12, 0, n02, K); + int name = 0, c0 = -1, c1 = -1, c2 = -1; + for (int i = 0; i < n02; ++i) { + if (c0 != nums[sa12[i]] || c1 != nums[sa12[i] + 1] || c2 != nums[sa12[i] + 2]) { + name++; + c0 = nums[sa12[i]]; + c1 = nums[sa12[i] + 1]; + c2 = nums[sa12[i] + 2]; + } + if (1 == sa12[i] % 3) { + s12[sa12[i] / 3] = name; + } else { + s12[sa12[i] / 3 + n0] = name; + } + } + if (name < n02) { + sa12 = skew(s12, n02, name); + for (int i = 0; i < n02; i++) { + s12[sa12[i]] = i + 1; + } + } else { + for (int i = 0; i < n02; i++) { + sa12[s12[i] - 1] = i; + } + } + int[] s0 = new int[n0], sa0 = new int[n0]; + for (int i = 0, j = 0; i < n02; i++) { + if (sa12[i] < n0) { + s0[j++] = 3 * sa12[i]; + } + } + radixPass(nums, s0, sa0, 0, n0, K); + int[] sa = new int[n]; + for (int p = 0, t = n0 - n1, k = 0; k < n; k++) { + int i = sa12[t] < n0 ? sa12[t] * 3 + 1 : (sa12[t] - n0) * 3 + 2; + int j = sa0[p]; + if (sa12[t] < n0 ? leq(nums[i], s12[sa12[t] + n0], nums[j], s12[j / 3]) + : leq(nums[i], nums[i + 1], s12[sa12[t] - n0 + 1], nums[j], nums[j + 1], s12[j / 3 + n0])) { + sa[k] = i; + t++; + if (t == n02) { + for (k++; p < n0; p++, k++) { + sa[k] = sa0[p]; + } + } + } else { + sa[k] = j; + p++; + if (p == n0) { + for (k++; t < n02; t++, k++) { + sa[k] = sa12[t] < n0 ? sa12[t] * 3 + 1 : (sa12[t] - n0) * 3 + 2; + } + } + } + } + return sa; + } + + private void radixPass(int[] nums, int[] input, int[] output, int offset, int n, int k) { + int[] cnt = new int[k + 1]; + for (int i = 0; i < n; ++i) { + cnt[nums[input[i] + offset]]++; + } + for (int i = 0, sum = 0; i < cnt.length; ++i) { + int t = cnt[i]; + cnt[i] = sum; + sum += t; + } + for (int i = 0; i < n; ++i) { + output[cnt[nums[input[i] + offset]]++] = input[i]; + } + } + + private boolean leq(int a1, int a2, int b1, int b2) { + return a1 < b1 || (a1 == b1 && a2 <= b2); + } + + private boolean leq(int a1, int a2, int a3, int b1, int b2, int b3) { + return a1 < b1 || (a1 == b1 && leq(a2, a3, b2, b3)); + } + + private int[] rank() { + int n = sa.length; + int[] ans = new int[n]; + for (int i = 0; i < n; i++) { + ans[sa[i]] = i; + } + return ans; + } + + } + + // for test + public static String randomNumberString(int len, int range) { + char[] str = new char[len]; + for (int i = 0; i < len; i++) { + str[i] = (char) ((int) (Math.random() * range) + '0'); + } + return String.valueOf(str); + } + + // for test + public static void main(String[] args) { + int range = 10; + int len = 50; + int testTime = 100000; + System.out.println("功能测试开始"); + for (int i = 0; i < testTime; i++) { + int s1Len = (int) (Math.random() * len); + int s2Len = (int) (Math.random() * len); + String s1 = randomNumberString(s1Len, range); + String s2 = randomNumberString(s2Len, range); + String ans1 = right(s1, s2); + String ans2 = maxCombine(s1, s2); + if (!ans1.equals(ans2)) { + System.out.println("Oops!"); + System.out.println(s1); + System.out.println(s2); + System.out.println(ans1); + System.out.println(ans2); + break; + } + } + System.out.println("功能测试结束"); + + System.out.println("=========="); + + System.out.println("性能测试开始"); + int s1Len = 1000000; + int s2Len = 500; + String s1 = randomNumberString(s1Len, range); + String s2 = randomNumberString(s2Len, range); + long start = System.currentTimeMillis(); + maxCombine(s1, s2); + long end = System.currentTimeMillis(); + System.out.println("运行时间 : " + (end - start) + " ms"); + System.out.println("性能测试结束"); + } + +} diff --git a/体系学习班/class45/Code02_CreateMaximumNumber.java b/体系学习班/class45/Code02_CreateMaximumNumber.java new file mode 100644 index 0000000..1e9cc28 --- /dev/null +++ b/体系学习班/class45/Code02_CreateMaximumNumber.java @@ -0,0 +1,250 @@ +package class45; + +// 测试链接: https://leetcode.com/problems/create-maximum-number/ +public class Code02_CreateMaximumNumber { + + public static int[] maxNumber1(int[] nums1, int[] nums2, int k) { + int len1 = nums1.length; + int len2 = nums2.length; + if (k < 0 || k > len1 + len2) { + return null; + } + int[] res = new int[k]; + int[][] dp1 = getdp(nums1); // 生成dp1这个表,以后从nums1中,只要固定拿N个数, + int[][] dp2 = getdp(nums2); + // get1 从arr1里拿的数量 + // K - get1 从arr2里拿的数量 + for (int get1 = Math.max(0, k - len2); get1 <= Math.min(k, len1); get1++) { + // arr1 挑 get1个,怎么得到一个最优结果 + int[] pick1 = maxPick(nums1, dp1, get1); + int[] pick2 = maxPick(nums2, dp2, k - get1); + int[] merge = merge(pick1, pick2); + res = preMoreThanLast(res, 0, merge, 0) ? res : merge; + } + return res; + } + + public static int[] merge(int[] nums1, int[] nums2) { + int k = nums1.length + nums2.length; + int[] ans = new int[k]; + for (int i = 0, j = 0, r = 0; r < k; ++r) { + ans[r] = preMoreThanLast(nums1, i, nums2, j) ? nums1[i++] : nums2[j++]; + } + return ans; + } + + public static boolean preMoreThanLast(int[] nums1, int i, int[] nums2, int j) { + while (i < nums1.length && j < nums2.length && nums1[i] == nums2[j]) { + i++; + j++; + } + return j == nums2.length || (i < nums1.length && nums1[i] > nums2[j]); + } + + public static int[] maxNumber2(int[] nums1, int[] nums2, int k) { + int len1 = nums1.length; + int len2 = nums2.length; + if (k < 0 || k > len1 + len2) { + return null; + } + int[] res = new int[k]; + int[][] dp1 = getdp(nums1); + int[][] dp2 = getdp(nums2); + for (int get1 = Math.max(0, k - len2); get1 <= Math.min(k, len1); get1++) { + int[] pick1 = maxPick(nums1, dp1, get1); + int[] pick2 = maxPick(nums2, dp2, k - get1); + int[] merge = mergeBySuffixArray(pick1, pick2); + res = moreThan(res, merge) ? res : merge; + } + return res; + } + + public static boolean moreThan(int[] pre, int[] last) { + int i = 0; + int j = 0; + while (i < pre.length && j < last.length && pre[i] == last[j]) { + i++; + j++; + } + return j == last.length || (i < pre.length && pre[i] > last[j]); + } + + public static int[] mergeBySuffixArray(int[] nums1, int[] nums2) { + int size1 = nums1.length; + int size2 = nums2.length; + int[] nums = new int[size1 + 1 + size2]; + for (int i = 0; i < size1; i++) { + nums[i] = nums1[i] + 2; + } + nums[size1] = 1; + for (int j = 0; j < size2; j++) { + nums[j + size1 + 1] = nums2[j] + 2; + } + DC3 dc3 = new DC3(nums, 11); + int[] rank = dc3.rank; + int[] ans = new int[size1 + size2]; + int i = 0; + int j = 0; + int r = 0; + while (i < size1 && j < size2) { + ans[r++] = rank[i] > rank[j + size1 + 1] ? nums1[i++] : nums2[j++]; + } + while (i < size1) { + ans[r++] = nums1[i++]; + } + while (j < size2) { + ans[r++] = nums2[j++]; + } + return ans; + } + + public static class DC3 { + + public int[] sa; + + public int[] rank; + + public DC3(int[] nums, int max) { + sa = sa(nums, max); + rank = rank(); + } + + private int[] sa(int[] nums, int max) { + int n = nums.length; + int[] arr = new int[n + 3]; + for (int i = 0; i < n; i++) { + arr[i] = nums[i]; + } + return skew(arr, n, max); + } + + private int[] skew(int[] nums, int n, int K) { + int n0 = (n + 2) / 3, n1 = (n + 1) / 3, n2 = n / 3, n02 = n0 + n2; + int[] s12 = new int[n02 + 3], sa12 = new int[n02 + 3]; + for (int i = 0, j = 0; i < n + (n0 - n1); ++i) { + if (0 != i % 3) { + s12[j++] = i; + } + } + radixPass(nums, s12, sa12, 2, n02, K); + radixPass(nums, sa12, s12, 1, n02, K); + radixPass(nums, s12, sa12, 0, n02, K); + int name = 0, c0 = -1, c1 = -1, c2 = -1; + for (int i = 0; i < n02; ++i) { + if (c0 != nums[sa12[i]] || c1 != nums[sa12[i] + 1] || c2 != nums[sa12[i] + 2]) { + name++; + c0 = nums[sa12[i]]; + c1 = nums[sa12[i] + 1]; + c2 = nums[sa12[i] + 2]; + } + if (1 == sa12[i] % 3) { + s12[sa12[i] / 3] = name; + } else { + s12[sa12[i] / 3 + n0] = name; + } + } + if (name < n02) { + sa12 = skew(s12, n02, name); + for (int i = 0; i < n02; i++) { + s12[sa12[i]] = i + 1; + } + } else { + for (int i = 0; i < n02; i++) { + sa12[s12[i] - 1] = i; + } + } + int[] s0 = new int[n0], sa0 = new int[n0]; + for (int i = 0, j = 0; i < n02; i++) { + if (sa12[i] < n0) { + s0[j++] = 3 * sa12[i]; + } + } + radixPass(nums, s0, sa0, 0, n0, K); + int[] sa = new int[n]; + for (int p = 0, t = n0 - n1, k = 0; k < n; k++) { + int i = sa12[t] < n0 ? sa12[t] * 3 + 1 : (sa12[t] - n0) * 3 + 2; + int j = sa0[p]; + if (sa12[t] < n0 ? leq(nums[i], s12[sa12[t] + n0], nums[j], s12[j / 3]) + : leq(nums[i], nums[i + 1], s12[sa12[t] - n0 + 1], nums[j], nums[j + 1], s12[j / 3 + n0])) { + sa[k] = i; + t++; + if (t == n02) { + for (k++; p < n0; p++, k++) { + sa[k] = sa0[p]; + } + } + } else { + sa[k] = j; + p++; + if (p == n0) { + for (k++; t < n02; t++, k++) { + sa[k] = sa12[t] < n0 ? sa12[t] * 3 + 1 : (sa12[t] - n0) * 3 + 2; + } + } + } + } + return sa; + } + + private void radixPass(int[] nums, int[] input, int[] output, int offset, int n, int k) { + int[] cnt = new int[k + 1]; + for (int i = 0; i < n; ++i) { + cnt[nums[input[i] + offset]]++; + } + for (int i = 0, sum = 0; i < cnt.length; ++i) { + int t = cnt[i]; + cnt[i] = sum; + sum += t; + } + for (int i = 0; i < n; ++i) { + output[cnt[nums[input[i] + offset]]++] = input[i]; + } + } + + private boolean leq(int a1, int a2, int b1, int b2) { + return a1 < b1 || (a1 == b1 && a2 <= b2); + } + + private boolean leq(int a1, int a2, int a3, int b1, int b2, int b3) { + return a1 < b1 || (a1 == b1 && leq(a2, a3, b2, b3)); + } + + private int[] rank() { + int n = sa.length; + int[] ans = new int[n]; + for (int i = 0; i < n; i++) { + ans[sa[i]] = i; + } + return ans; + } + + } + + public static int[][] getdp(int[] arr) { + int size = arr.length; // 0~N-1 + int pick = arr.length + 1; // 1 ~ N + int[][] dp = new int[size][pick]; + // get 不从0开始,因为拿0个无意义 + for (int get = 1; get < pick; get++) { // 1 ~ N + int maxIndex = size - get; + // i~N-1 + for (int i = size - get; i >= 0; i--) { + if (arr[i] >= arr[maxIndex]) { + maxIndex = i; + } + dp[i][get] = maxIndex; + } + } + return dp; + } + + public static int[] maxPick(int[] arr, int[][] dp, int pick) { + int[] res = new int[pick]; + for (int resIndex = 0, dpRow = 0; pick > 0; pick--, resIndex++) { + res[resIndex] = arr[dp[dpRow][pick]]; + dpRow = dp[dpRow][pick] + 1; + } + return res; + } + +} diff --git a/体系学习班/class45/Code03_LongestCommonSubstringConquerByHeight.java b/体系学习班/class45/Code03_LongestCommonSubstringConquerByHeight.java new file mode 100644 index 0000000..2b92fff --- /dev/null +++ b/体系学习班/class45/Code03_LongestCommonSubstringConquerByHeight.java @@ -0,0 +1,286 @@ +package class45; + +// 最长公共子串问题是面试常见题目之一 +// 假设str1长度N,str2长度M +// 因为最优解的难度所限,一般在面试场上回答出O(N*M)的解法已经是比较优秀了 +// 因为得到O(N*M)的解法,就已经需要用到动态规划了 +// 但其实这个问题的最优解是O(N+M),为了达到这个复杂度可是不容易 +// 首先需要用到DC3算法得到后缀数组(sa) +// 进而用sa数组去生成height数组 +// 而且在生成的时候,还有一个不回退的优化,都非常不容易理解 +// 这就是后缀数组在面试算法中的地位 : 德高望重的噩梦 +public class Code03_LongestCommonSubstringConquerByHeight { + + public static int lcs1(String s1, String s2) { + if (s1 == null || s2 == null || s1.length() == 0 || s2.length() == 0) { + return 0; + } + char[] str1 = s1.toCharArray(); + char[] str2 = s2.toCharArray(); + int row = 0; + int col = str2.length - 1; + int max = 0; + while (row < str1.length) { + int i = row; + int j = col; + int len = 0; + while (i < str1.length && j < str2.length) { + if (str1[i] != str2[j]) { + len = 0; + } else { + len++; + } + if (len > max) { + max = len; + } + i++; + j++; + } + if (col > 0) { + col--; + } else { + row++; + } + } + return max; + } + + public static int lcs2(String s1, String s2) { + if (s1 == null || s2 == null || s1.length() == 0 || s2.length() == 0) { + return 0; + } + char[] str1 = s1.toCharArray(); + char[] str2 = s2.toCharArray(); + int N = str1.length; + int M = str2.length; + int min = str1[0]; + int max = str1[0]; + for (int i = 1; i < N; i++) { + min = Math.min(min, str1[i]); + max = Math.max(max, str1[i]); + } + for (int i = 0; i < M; i++) { + min = Math.min(min, str2[i]); + max = Math.max(max, str2[i]); + } + int[] all = new int[N + M + 1]; + int index = 0; + for (int i = 0; i < N; i++) { + all[index++] = str1[i] - min + 2; + } + all[index++] = 1; + for (int i = 0; i < M; i++) { + all[index++] = str2[i] - min + 2; + } + DC3 dc3 = new DC3(all, max - min + 2); + int n = all.length; + int[] sa = dc3.sa; + int[] height = dc3.height; + int ans = 0; + for (int i = 1; i < n; i++) { + int Y = sa[i - 1]; + int X = sa[i]; + if (Math.min(X, Y) < N && Math.max(X, Y) > N) { + ans = Math.max(ans, height[i]); + } + } + return ans; + } + + public static class DC3 { + + public int[] sa; + + public int[] rank; + + public int[] height; + + public DC3(int[] nums, int max) { + sa = sa(nums, max); + rank = rank(); + height = height(nums); + } + + private int[] sa(int[] nums, int max) { + int n = nums.length; + int[] arr = new int[n + 3]; + for (int i = 0; i < n; i++) { + arr[i] = nums[i]; + } + return skew(arr, n, max); + } + + private int[] skew(int[] nums, int n, int K) { + int n0 = (n + 2) / 3, n1 = (n + 1) / 3, n2 = n / 3, n02 = n0 + n2; + int[] s12 = new int[n02 + 3], sa12 = new int[n02 + 3]; + for (int i = 0, j = 0; i < n + (n0 - n1); ++i) { + if (0 != i % 3) { + s12[j++] = i; + } + } + radixPass(nums, s12, sa12, 2, n02, K); + radixPass(nums, sa12, s12, 1, n02, K); + radixPass(nums, s12, sa12, 0, n02, K); + int name = 0, c0 = -1, c1 = -1, c2 = -1; + for (int i = 0; i < n02; ++i) { + if (c0 != nums[sa12[i]] || c1 != nums[sa12[i] + 1] || c2 != nums[sa12[i] + 2]) { + name++; + c0 = nums[sa12[i]]; + c1 = nums[sa12[i] + 1]; + c2 = nums[sa12[i] + 2]; + } + if (1 == sa12[i] % 3) { + s12[sa12[i] / 3] = name; + } else { + s12[sa12[i] / 3 + n0] = name; + } + } + if (name < n02) { + sa12 = skew(s12, n02, name); + for (int i = 0; i < n02; i++) { + s12[sa12[i]] = i + 1; + } + } else { + for (int i = 0; i < n02; i++) { + sa12[s12[i] - 1] = i; + } + } + int[] s0 = new int[n0], sa0 = new int[n0]; + for (int i = 0, j = 0; i < n02; i++) { + if (sa12[i] < n0) { + s0[j++] = 3 * sa12[i]; + } + } + radixPass(nums, s0, sa0, 0, n0, K); + int[] sa = new int[n]; + for (int p = 0, t = n0 - n1, k = 0; k < n; k++) { + int i = sa12[t] < n0 ? sa12[t] * 3 + 1 : (sa12[t] - n0) * 3 + 2; + int j = sa0[p]; + if (sa12[t] < n0 ? leq(nums[i], s12[sa12[t] + n0], nums[j], s12[j / 3]) + : leq(nums[i], nums[i + 1], s12[sa12[t] - n0 + 1], nums[j], nums[j + 1], s12[j / 3 + n0])) { + sa[k] = i; + t++; + if (t == n02) { + for (k++; p < n0; p++, k++) { + sa[k] = sa0[p]; + } + } + } else { + sa[k] = j; + p++; + if (p == n0) { + for (k++; t < n02; t++, k++) { + sa[k] = sa12[t] < n0 ? sa12[t] * 3 + 1 : (sa12[t] - n0) * 3 + 2; + } + } + } + } + return sa; + } + + private void radixPass(int[] nums, int[] input, int[] output, int offset, int n, int k) { + int[] cnt = new int[k + 1]; + for (int i = 0; i < n; ++i) { + cnt[nums[input[i] + offset]]++; + } + for (int i = 0, sum = 0; i < cnt.length; ++i) { + int t = cnt[i]; + cnt[i] = sum; + sum += t; + } + for (int i = 0; i < n; ++i) { + output[cnt[nums[input[i] + offset]]++] = input[i]; + } + } + + private boolean leq(int a1, int a2, int b1, int b2) { + return a1 < b1 || (a1 == b1 && a2 <= b2); + } + + private boolean leq(int a1, int a2, int a3, int b1, int b2, int b3) { + return a1 < b1 || (a1 == b1 && leq(a2, a3, b2, b3)); + } + + private int[] rank() { + int n = sa.length; + int[] ans = new int[n]; + for (int i = 0; i < n; i++) { + ans[sa[i]] = i; + } + return ans; + } + + private int[] height(int[] s) { + int n = s.length; + int[] ans = new int[n]; + // 依次求h[i] , k = 0 + for (int i = 0, k = 0; i < n; ++i) { + if (rank[i] != 0) { + if (k > 0) { + --k; + } + int j = sa[rank[i] - 1]; + while (i + k < n && j + k < n && s[i + k] == s[j + k]) { + ++k; + } + // h[i] = k + ans[rank[i]] = k; + } + } + return ans; + } + + } + + // for test + public static String randomNumberString(int len, int range) { + char[] str = new char[len]; + for (int i = 0; i < len; i++) { + str[i] = (char) ((int) (Math.random() * range) + 'a'); + } + return String.valueOf(str); + } + + public static void main(String[] args) { + int len = 30; + int range = 5; + int testTime = 100000; + System.out.println("功能测试开始"); + for (int i = 0; i < testTime; i++) { + int N1 = (int) (Math.random() * len); + int N2 = (int) (Math.random() * len); + String str1 = randomNumberString(N1, range); + String str2 = randomNumberString(N2, range); + int ans1 = lcs1(str1, str2); + int ans2 = lcs2(str1, str2); + if (ans1 != ans2) { + System.out.println("Oops!"); + } + } + System.out.println("功能测试结束"); + System.out.println("=========="); + + System.out.println("性能测试开始"); + len = 80000; + range = 26; + long start; + long end; + + String str1 = randomNumberString(len, range); + String str2 = randomNumberString(len, range); + + start = System.currentTimeMillis(); + int ans1 = lcs1(str1, str2); + end = System.currentTimeMillis(); + System.out.println("方法1结果 : " + ans1 + " , 运行时间 : " + (end - start) + " ms"); + + start = System.currentTimeMillis(); + int ans2 = lcs2(str1, str2); + end = System.currentTimeMillis(); + System.out.println("方法2结果 : " + ans2 + " , 运行时间 : " + (end - start) + " ms"); + + System.out.println("性能测试结束"); + + } + +} diff --git a/体系学习班/class45/Code04_LongestRepeatingSubstring.java b/体系学习班/class45/Code04_LongestRepeatingSubstring.java new file mode 100644 index 0000000..a6090e3 --- /dev/null +++ b/体系学习班/class45/Code04_LongestRepeatingSubstring.java @@ -0,0 +1,196 @@ +package class45; + +// 一个非常经典的题 +// 这道题课上没有讲 +// 后缀数组的模版题 +// 需要学会DC3算法生成后缀数组 +// 需要学会课上讲的如何生成高度数组 +// 时间复杂度O(N),连官方题解都没有做到的时间复杂度,但这才是最优解 +// 测试链接 : https://leetcode.cn/problems/longest-repeating-substring/ +public class Code04_LongestRepeatingSubstring { + + public static int longestRepeatingSubstring(String s) { + if (s == null || s.length() == 0) { + return 0; + } + char[] str = s.toCharArray(); + int n = str.length; + int min = str[0]; + int max = str[0]; + for (int i = 1; i < n; i++) { + min = Math.min(min, str[i]); + max = Math.max(max, str[i]); + } + int[] all = new int[n]; + for (int i = 0; i < n; i++) { + all[i] = str[i] - min + 1; + } + DC3 dc3 = new DC3(all, max - min + 1); + int ans = 0; + for (int i = 1; i < n; i++) { + ans = Math.max(ans, dc3.height[i]); + } + return ans; + } + + public static class DC3 { + public int[] sa; + public int[] rank; + public int[] height; + + public DC3(int[] nums, int max) { + sa = sa(nums, max); + rank = rank(); + height = height(nums); + } + + private int[] sa(int[] nums, int max) { + int n = nums.length; + int[] arr = new int[n + 3]; + for (int i = 0; i < n; i++) { + arr[i] = nums[i]; + } + return skew(arr, n, max); + } + + private int[] skew(int[] nums, int n, int K) { + int n0 = (n + 2) / 3, n1 = (n + 1) / 3, n2 = n / 3, n02 = n0 + n2; + int[] s12 = new int[n02 + 3], sa12 = new int[n02 + 3]; + for (int i = 0, j = 0; i < n + (n0 - n1); ++i) { + if (0 != i % 3) { + s12[j++] = i; + } + } + radixPass(nums, s12, sa12, 2, n02, K); + radixPass(nums, sa12, s12, 1, n02, K); + radixPass(nums, s12, sa12, 0, n02, K); + int name = 0, c0 = -1, c1 = -1, c2 = -1; + for (int i = 0; i < n02; ++i) { + if (c0 != nums[sa12[i]] || c1 != nums[sa12[i] + 1] || c2 != nums[sa12[i] + 2]) { + name++; + c0 = nums[sa12[i]]; + c1 = nums[sa12[i] + 1]; + c2 = nums[sa12[i] + 2]; + } + if (1 == sa12[i] % 3) { + s12[sa12[i] / 3] = name; + } else { + s12[sa12[i] / 3 + n0] = name; + } + } + if (name < n02) { + sa12 = skew(s12, n02, name); + for (int i = 0; i < n02; i++) { + s12[sa12[i]] = i + 1; + } + } else { + for (int i = 0; i < n02; i++) { + sa12[s12[i] - 1] = i; + } + } + int[] s0 = new int[n0], sa0 = new int[n0]; + for (int i = 0, j = 0; i < n02; i++) { + if (sa12[i] < n0) { + s0[j++] = 3 * sa12[i]; + } + } + radixPass(nums, s0, sa0, 0, n0, K); + int[] sa = new int[n]; + for (int p = 0, t = n0 - n1, k = 0; k < n; k++) { + int i = sa12[t] < n0 ? sa12[t] * 3 + 1 : (sa12[t] - n0) * 3 + 2; + int j = sa0[p]; + if (sa12[t] < n0 ? leq(nums[i], s12[sa12[t] + n0], nums[j], s12[j / 3]) + : leq(nums[i], nums[i + 1], s12[sa12[t] - n0 + 1], nums[j], nums[j + 1], s12[j / 3 + n0])) { + sa[k] = i; + t++; + if (t == n02) { + for (k++; p < n0; p++, k++) { + sa[k] = sa0[p]; + } + } + } else { + sa[k] = j; + p++; + if (p == n0) { + for (k++; t < n02; t++, k++) { + sa[k] = sa12[t] < n0 ? sa12[t] * 3 + 1 : (sa12[t] - n0) * 3 + 2; + } + } + } + } + return sa; + } + + private void radixPass(int[] nums, int[] input, int[] output, int offset, int n, int k) { + int[] cnt = new int[k + 1]; + for (int i = 0; i < n; ++i) { + cnt[nums[input[i] + offset]]++; + } + for (int i = 0, sum = 0; i < cnt.length; ++i) { + int t = cnt[i]; + cnt[i] = sum; + sum += t; + } + for (int i = 0; i < n; ++i) { + output[cnt[nums[input[i] + offset]]++] = input[i]; + } + } + + private boolean leq(int a1, int a2, int b1, int b2) { + return a1 < b1 || (a1 == b1 && a2 <= b2); + } + + private boolean leq(int a1, int a2, int a3, int b1, int b2, int b3) { + return a1 < b1 || (a1 == b1 && leq(a2, a3, b2, b3)); + } + + private int[] rank() { + int n = sa.length; + int[] ans = new int[n]; + for (int i = 0; i < n; i++) { + ans[sa[i]] = i; + } + return ans; + } + + private int[] height(int[] s) { + int n = s.length; + int[] ans = new int[n]; + for (int i = 0, k = 0; i < n; ++i) { + if (rank[i] != 0) { + if (k > 0) { + --k; + } + int j = sa[rank[i] - 1]; + while (i + k < n && j + k < n && s[i + k] == s[j + k]) { + ++k; + } + ans[rank[i]] = k; + } + } + return ans; + } + + } + + // 为了测试, 不用提交 + public static String randomString(int n, int r) { + char[] str = new char[n]; + for (int i = 0; i < n; i++) { + str[i] = (char) ((int) (Math.random() * r) + 'a'); + } + return String.valueOf(str); + } + + // 为了测试, 不用提交 + public static void main(String[] args) { + int n = 500000; + int r = 3; + long start = System.currentTimeMillis(); + longestRepeatingSubstring(randomString(n, r)); + long end = System.currentTimeMillis(); + System.out.println("字符长度为 " + n + ", 字符种类数为 " + r + " 时"); + System.out.println("求最长重复子串的运行时间 : " + (end - start) + " 毫秒"); + } + +} diff --git a/体系学习班/class46/Code01_BurstBalloons.java b/体系学习班/class46/Code01_BurstBalloons.java new file mode 100644 index 0000000..71d2602 --- /dev/null +++ b/体系学习班/class46/Code01_BurstBalloons.java @@ -0,0 +1,107 @@ +package class46; + +// 本题测试链接 : https://leetcode.com/problems/burst-balloons/ +public class Code01_BurstBalloons { + + public static int maxCoins0(int[] arr) { + // [3,2,1,3] + // [1,3,2,1,3,1] + int N = arr.length; + int[] help = new int[N + 2]; + for (int i = 0; i < N; i++) { + help[i + 1] = arr[i]; + } + help[0] = 1; + help[N + 1] = 1; + return func(help, 1, N); + } + + // L-1位置,和R+1位置,永远不越界,并且,[L-1] 和 [R+1] 一定没爆呢! + // 返回,arr[L...R]打爆所有气球,最大得分是什么 + public static int func(int[] arr, int L, int R) { + if (L == R) { + return arr[L - 1] * arr[L] * arr[R + 1]; + } + // 尝试每一种情况,最后打爆的气球,是什么位置 + // L...R + // L位置的气球,最后打爆 + int max = func(arr, L + 1, R) + arr[L - 1] * arr[L] * arr[R + 1]; + // R位置的气球,最后打爆 + max = Math.max(max, func(arr, L, R - 1) + arr[L - 1] * arr[R] * arr[R + 1]); + // 尝试所有L...R,中间的位置,(L,R) + for (int i = L + 1; i < R; i++) { + // i位置的气球,最后打爆 + int left = func(arr, L, i - 1); + int right = func(arr, i + 1, R); + int last = arr[L - 1] * arr[i] * arr[R + 1]; + int cur = left + right + last; + max = Math.max(max, cur); + } + return max; + } + + public static int maxCoins1(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + if (arr.length == 1) { + return arr[0]; + } + int N = arr.length; + int[] help = new int[N + 2]; + help[0] = 1; + help[N + 1] = 1; + for (int i = 0; i < N; i++) { + help[i + 1] = arr[i]; + } + return process(help, 1, N); + } + + // 打爆arr[L..R]范围上的所有气球,返回最大的分数 + // 假设arr[L-1]和arr[R+1]一定没有被打爆 + public static int process(int[] arr, int L, int R) { + if (L == R) {// 如果arr[L..R]范围上只有一个气球,直接打爆即可 + return arr[L - 1] * arr[L] * arr[R + 1]; + } + // 最后打爆arr[L]的方案,和最后打爆arr[R]的方案,先比较一下 + int max = Math.max(arr[L - 1] * arr[L] * arr[R + 1] + process(arr, L + 1, R), + arr[L - 1] * arr[R] * arr[R + 1] + process(arr, L, R - 1)); + // 尝试中间位置的气球最后被打爆的每一种方案 + for (int i = L + 1; i < R; i++) { + max = Math.max(max, arr[L - 1] * arr[i] * arr[R + 1] + process(arr, L, i - 1) + process(arr, i + 1, R)); + } + return max; + } + + public static int maxCoins2(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + if (arr.length == 1) { + return arr[0]; + } + int N = arr.length; + int[] help = new int[N + 2]; + help[0] = 1; + help[N + 1] = 1; + for (int i = 0; i < N; i++) { + help[i + 1] = arr[i]; + } + int[][] dp = new int[N + 2][N + 2]; + for (int i = 1; i <= N; i++) { + dp[i][i] = help[i - 1] * help[i] * help[i + 1]; + } + for (int L = N; L >= 1; L--) { + for (int R = L + 1; R <= N; R++) { + int ans = help[L - 1] * help[L] * help[R + 1] + dp[L + 1][R]; + ans = Math.max(ans, help[L - 1] * help[R] * help[R + 1] + dp[L][R - 1]); + for (int i = L + 1; i < R; i++) { + ans = Math.max(ans, help[L - 1] * help[i] * help[R + 1] + dp[L][i - 1] + dp[i + 1][R]); + } + dp[L][R] = ans; + } + } + return dp[1][N]; + } + +} diff --git a/体系学习班/class46/Code02_RemoveBoxes.java b/体系学习班/class46/Code02_RemoveBoxes.java new file mode 100644 index 0000000..8db4d8f --- /dev/null +++ b/体系学习班/class46/Code02_RemoveBoxes.java @@ -0,0 +1,81 @@ +package class46; + +// 本题测试链接 : https://leetcode.com/problems/remove-boxes/ +public class Code02_RemoveBoxes { + + // arr[L...R]消除,而且前面跟着K个arr[L]这个数 + // 返回:所有东西都消掉,最大得分 + public static int func1(int[] arr, int L, int R, int K) { + if (L > R) { + return 0; + } + int ans = func1(arr, L + 1, R, 0) + (K + 1) * (K + 1); + + // 前面的K个X,和arr[L]数,合在一起了,现在有K+1个arr[L]位置的数 + for (int i = L + 1; i <= R; i++) { + if (arr[i] == arr[L]) { + ans = Math.max(ans, func1(arr, L + 1, i - 1, 0) + func1(arr, i, R, K + 1)); + } + } + return ans; + } + + public static int removeBoxes1(int[] boxes) { + int N = boxes.length; + int[][][] dp = new int[N][N][N]; + int ans = process1(boxes, 0, N - 1, 0, dp); + return ans; + } + + public static int process1(int[] boxes, int L, int R, int K, int[][][] dp) { + if (L > R) { + return 0; + } + if (dp[L][R][K] > 0) { + return dp[L][R][K]; + } + int ans = process1(boxes, L + 1, R, 0, dp) + (K + 1) * (K + 1); + for (int i = L + 1; i <= R; i++) { + if (boxes[i] == boxes[L]) { + ans = Math.max(ans, process1(boxes, L + 1, i - 1, 0, dp) + process1(boxes, i, R, K + 1, dp)); + } + } + dp[L][R][K] = ans; + return ans; + } + + public static int removeBoxes2(int[] boxes) { + int N = boxes.length; + int[][][] dp = new int[N][N][N]; + int ans = process2(boxes, 0, N - 1, 0, dp); + return ans; + } + + public static int process2(int[] boxes, int L, int R, int K, int[][][] dp) { + if (L > R) { + return 0; + } + if (dp[L][R][K] > 0) { + return dp[L][R][K]; + } + // 找到开头, + // 1,1,1,1,1,5 + // 3 4 5 6 7 8 + // ! + int last = L; + while (last + 1 <= R && boxes[last + 1] == boxes[L]) { + last++; + } + // K个1 (K + last - L) last + int pre = K + last - L; + int ans = (pre + 1) * (pre + 1) + process2(boxes, last + 1, R, 0, dp); + for (int i = last + 2; i <= R; i++) { + if (boxes[i] == boxes[L] && boxes[i - 1] != boxes[L]) { + ans = Math.max(ans, process2(boxes, last + 1, i - 1, 0, dp) + process2(boxes, i, R, pre + 1, dp)); + } + } + dp[L][R][K] = ans; + return ans; + } + +} diff --git a/体系学习班/class46/Code03_DeleteAdjacentSameCharacter.java b/体系学习班/class46/Code03_DeleteAdjacentSameCharacter.java new file mode 100644 index 0000000..0b1e7f3 --- /dev/null +++ b/体系学习班/class46/Code03_DeleteAdjacentSameCharacter.java @@ -0,0 +1,176 @@ +package class46; + +// 如果一个字符相邻的位置没有相同字符,那么这个位置的字符出现不能被消掉 +// 比如:"ab",其中a和b都不能被消掉 +// 如果一个字符相邻的位置有相同字符,就可以一起消掉 +// 比如:"abbbc",中间一串的b是可以被消掉的,消除之后剩下"ac" +// 某些字符如果消掉了,剩下的字符认为重新靠在一起 +// 给定一个字符串,你可以决定每一步消除的顺序,目标是请尽可能多的消掉字符,返回最少的剩余字符数量 +// 比如:"aacca", 如果先消掉最左侧的"aa",那么将剩下"cca",然后把"cc"消掉,剩下的"a"将无法再消除,返回1 +// 但是如果先消掉中间的"cc",那么将剩下"aaa",最后都消掉就一个字符也不剩了,返回0,这才是最优解。 +// 再比如:"baaccabb", +// 如果先消除最左侧的两个a,剩下"bccabb", +// 如果再消除最左侧的两个c,剩下"babb", +// 最后消除最右侧的两个b,剩下"ba"无法再消除,返回2 +// 而最优策略是: +// 如果先消除中间的两个c,剩下"baaabb", +// 如果再消除中间的三个a,剩下"bbb", +// 最后消除三个b,不留下任何字符,返回0,这才是最优解 +public class Code03_DeleteAdjacentSameCharacter { + + // 暴力解 + public static int restMin1(String s) { + if (s == null) { + return 0; + } + if (s.length() < 2) { + return s.length(); + } + int minLen = s.length(); + for (int L = 0; L < s.length(); L++) { + for (int R = L + 1; R < s.length(); R++) { + if (canDelete(s.substring(L, R + 1))) { + minLen = Math.min(minLen, restMin1(s.substring(0, L) + s.substring(R + 1, s.length()))); + } + } + } + return minLen; + } + + public static boolean canDelete(String s) { + char[] str = s.toCharArray(); + for (int i = 1; i < str.length; i++) { + if (str[i - 1] != str[i]) { + return false; + } + } + return true; + } + + // 优良尝试的暴力递归版本 + public static int restMin2(String s) { + if (s == null) { + return 0; + } + if (s.length() < 2) { + return s.length(); + } + char[] str = s.toCharArray(); + return process(str, 0, str.length - 1, false); + } + + // str[L...R] 前面有没有跟着[L]字符,has T 有 F 无 + // L,R,has + // 最少能剩多少字符,消不了 + public static int process(char[] str, int L, int R, boolean has) { + if (L > R) { + return 0; + } + if (L == R) { + return has ? 0 : 1; + } + int index = L; + int K = has ? 1 : 0; + while (index <= R && str[index] == str[L]) { + K++; + index++; + } + // index表示,第一个不是[L]字符的位置 + int way1 = (K > 1 ? 0 : 1) + process(str, index, R, false); + int way2 = Integer.MAX_VALUE; + for (int split = index; split <= R; split++) { + if (str[split] == str[L] && str[split] != str[split - 1]) { + if (process(str, index, split - 1, false) == 0) { + way2 = Math.min(way2, process(str, split, R, K != 0)); + } + } + } + return Math.min(way1, way2); + } + + // 优良尝试的动态规划版本 + public static int restMin3(String s) { + if (s == null) { + return 0; + } + if (s.length() < 2) { + return s.length(); + } + char[] str = s.toCharArray(); + int N = str.length; + int[][][] dp = new int[N][N][2]; + for (int i = 0; i < N; i++) { + for (int j = 0; j < N; j++) { + for (int k = 0; k < 2; k++) { + dp[i][j][k] = -1; + } + } + } + return dpProcess(str, 0, N - 1, false, dp); + } + + public static int dpProcess(char[] str, int L, int R, boolean has, int[][][] dp) { + if (L > R) { + return 0; + } + int K = has ? 1 : 0; + if (dp[L][R][K] != -1) { + return dp[L][R][K]; + } + int ans = 0; + if (L == R) { + ans = (K == 0 ? 1 : 0); + } else { + int index = L; + int all = K; + while (index <= R && str[index] == str[L]) { + all++; + index++; + } + int way1 = (all > 1 ? 0 : 1) + dpProcess(str, index, R, false, dp); + int way2 = Integer.MAX_VALUE; + for (int split = index; split <= R; split++) { + if (str[split] == str[L] && str[split] != str[split - 1]) { + if (dpProcess(str, index, split - 1, false, dp) == 0) { + way2 = Math.min(way2, dpProcess(str, split, R, all > 0, dp)); + } + } + } + ans = Math.min(way1, way2); + } + dp[L][R][K] = ans; + return ans; + } + + public static String randomString(int len, int variety) { + char[] str = new char[len]; + for (int i = 0; i < len; i++) { + str[i] = (char) ((int) (Math.random() * variety) + 'a'); + } + return String.valueOf(str); + } + + public static void main(String[] args) { + int maxLen = 16; + int variety = 3; + int testTime = 100000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int len = (int) (Math.random() * maxLen); + String str = randomString(len, variety); + int ans1 = restMin1(str); + int ans2 = restMin2(str); + int ans3 = restMin3(str); + if (ans1 != ans2 || ans1 != ans3) { + System.out.println(str); + System.out.println(ans1); + System.out.println(ans2); + System.out.println(ans3); + System.out.println("出错了!"); + break; + } + } + System.out.println("测试结束"); + } + +} diff --git a/体系学习班/class46/Code04_MaxSumLengthNoMore.java b/体系学习班/class46/Code04_MaxSumLengthNoMore.java new file mode 100644 index 0000000..be50615 --- /dev/null +++ b/体系学习班/class46/Code04_MaxSumLengthNoMore.java @@ -0,0 +1,100 @@ +package class46; + +import java.util.LinkedList; + +// 给定一个数组arr,和一个正数M +// 返回在子数组长度不大于M的情况下,最大的子数组累加和 +public class Code04_MaxSumLengthNoMore { + + // O(N^2)的解法,暴力解,用作对数器 + public static int test(int[] arr, int M) { + if (arr == null || arr.length == 0 || M < 1) { + return 0; + } + int N = arr.length; + int max = Integer.MIN_VALUE; + for (int L = 0; L < N; L++) { + int sum = 0; + for (int R = L; R < N; R++) { + if (R - L + 1 > M) { + break; + } + sum += arr[R]; + max = Math.max(max, sum); + } + } + return max; + } + + // O(N)的解法,最优解 + public static int maxSum(int[] arr, int M) { + if (arr == null || arr.length == 0 || M < 1) { + return 0; + } + int N = arr.length; + int[] sum = new int[N]; + sum[0] = arr[0]; + for (int i = 1; i < N; i++) { + sum[i] = sum[i - 1] + arr[i]; + } + LinkedList qmax = new LinkedList<>(); + int i = 0; + int end = Math.min(N, M); + for (; i < end; i++) { + while (!qmax.isEmpty() && sum[qmax.peekLast()] <= sum[i]) { + qmax.pollLast(); + } + qmax.add(i); + } + int max = sum[qmax.peekFirst()]; + int L = 0; + for (; i < N; L++, i++) { + if (qmax.peekFirst() == L) { + qmax.pollFirst(); + } + while (!qmax.isEmpty() && sum[qmax.peekLast()] <= sum[i]) { + qmax.pollLast(); + } + qmax.add(i); + max = Math.max(max, sum[qmax.peekFirst()] - sum[L]); + } + for (; L < N - 1; L++) { + if (qmax.peekFirst() == L) { + qmax.pollFirst(); + } + max = Math.max(max, sum[qmax.peekFirst()] - sum[L]); + } + return max; + } + + // 用作测试 + public static int[] randomArray(int len, int max) { + int[] arr = new int[len]; + for (int i = 0; i < len; i++) { + arr[i] = (int) (Math.random() * max) - (int) (Math.random() * max); + } + return arr; + } + + // 用作测试 + public static void main(String[] args) { + int maxN = 50; + int maxValue = 100; + int testTime = 1000000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int N = (int) (Math.random() * maxN); + int M = (int) (Math.random() * maxN); + int[] arr = randomArray(N, maxValue); + int ans1 = test(arr, M); + int ans2 = maxSum(arr, M); + if (ans1 != ans2) { + System.out.println(ans1); + System.out.println(ans2); + System.out.println("Oops!"); + } + } + System.out.println("测试结束"); + } + +} diff --git a/体系学习班/class46/Code05_HuffmanTree.java b/体系学习班/class46/Code05_HuffmanTree.java new file mode 100644 index 0000000..65b6be8 --- /dev/null +++ b/体系学习班/class46/Code05_HuffmanTree.java @@ -0,0 +1,214 @@ +package class46; + +import java.util.Comparator; +import java.util.HashMap; +import java.util.Map.Entry; +import java.util.PriorityQueue; + +// 本文件不牵扯任何byte类型的转化 +// 怎么转byte自己来,我只负责huffman算法本身的正确实现 +// 字符串为空的时候,自己处理边界吧 +// 实现的代码通过了大样本随机测试的对数器 +// 可以从main函数的内容开始看起 +public class Code05_HuffmanTree { + + // 根据文章str, 生成词频统计表 + public static HashMap countMap(String str) { + HashMap ans = new HashMap<>(); + char[] s = str.toCharArray(); + for (char cha : s) { + if (!ans.containsKey(cha)) { + ans.put(cha, 1); + } else { + ans.put(cha, ans.get(cha) + 1); + } + } + return ans; + } + + public static class Node { + public int count; + public Node left; + public Node right; + + public Node(int c) { + count = c; + } + } + + public static class NodeComp implements Comparator { + + @Override + public int compare(Node o1, Node o2) { + return o1.count - o2.count; + } + + } + + // 根据由文章生成词频表countMap,生成哈夫曼编码表 + // key : 字符 + // value: 该字符编码后的二进制形式 + // 比如,频率表 A:60, B:45, C:13 D:69 E:14 F:5 G:3 + // A 10 + // B 01 + // C 0011 + // D 11 + // E 000 + // F 00101 + // G 00100 + public static HashMap huffmanForm(HashMap countMap) { + HashMap ans = new HashMap<>(); + if (countMap.size() == 1) { + for (char key : countMap.keySet()) { + ans.put(key, "0"); + } + return ans; + } + HashMap nodes = new HashMap<>(); + PriorityQueue heap = new PriorityQueue<>(new NodeComp()); + for (Entry entry : countMap.entrySet()) { + Node cur = new Node(entry.getValue()); + char cha = entry.getKey(); + nodes.put(cur, cha); + heap.add(cur); + } + while (heap.size() != 1) { + Node a = heap.poll(); + Node b = heap.poll(); + Node h = new Node(a.count + b.count); + h.left = a; + h.right = b; + heap.add(h); + } + Node head = heap.poll(); + fillForm(head, "", nodes, ans); + return ans; + } + + public static void fillForm(Node head, String pre, HashMap nodes, HashMap ans) { + if (nodes.containsKey(head)) { + ans.put(nodes.get(head), pre); + } else { + fillForm(head.left, pre + "0", nodes, ans); + fillForm(head.right, pre + "1", nodes, ans); + } + } + + // 原始字符串str,根据哈夫曼编码表,转译成哈夫曼编码返回 + public static String huffmanEncode(String str, HashMap huffmanForm) { + char[] s = str.toCharArray(); + StringBuilder builder = new StringBuilder(); + for (char cha : s) { + builder.append(huffmanForm.get(cha)); + } + return builder.toString(); + } + + // 原始字符串的哈夫曼编码huffmanEncode,根据哈夫曼编码表,还原成原始字符串 + public static String huffmanDecode(String huffmanEncode, HashMap huffmanForm) { + TrieNode root = createTrie(huffmanForm); + TrieNode cur = root; + char[] encode = huffmanEncode.toCharArray(); + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < encode.length; i++) { + int index = encode[i] == '0' ? 0 : 1; + cur = cur.nexts[index]; + if (cur.nexts[0] == null && cur.nexts[1] == null) { + builder.append(cur.value); + cur = root; + } + } + return builder.toString(); + } + + public static TrieNode createTrie(HashMap huffmanForm) { + TrieNode root = new TrieNode(); + for (char key : huffmanForm.keySet()) { + char[] path = huffmanForm.get(key).toCharArray(); + TrieNode cur = root; + for (int i = 0; i < path.length; i++) { + int index = path[i] == '0' ? 0 : 1; + if (cur.nexts[index] == null) { + cur.nexts[index] = new TrieNode(); + } + cur = cur.nexts[index]; + } + cur.value = key; + } + return root; + } + + public static class TrieNode { + public char value; + public TrieNode[] nexts; + + public TrieNode() { + value = 0; + nexts = new TrieNode[2]; + } + } + + // 为了测试 + public static String randomNumberString(int len, int range) { + char[] str = new char[len]; + for (int i = 0; i < len; i++) { + str[i] = (char) ((int) (Math.random() * range) + 'a'); + } + return String.valueOf(str); + } + + // 为了测试 + public static void main(String[] args) { + // 根据词频表生成哈夫曼编码表 + HashMap map = new HashMap<>(); + map.put('A', 60); + map.put('B', 45); + map.put('C', 13); + map.put('D', 69); + map.put('E', 14); + map.put('F', 5); + map.put('G', 3); + HashMap huffmanForm = huffmanForm(map); + for (Entry entry : huffmanForm.entrySet()) { + System.out.println(entry.getKey() + " : " + entry.getValue()); + } + System.out.println("===================="); + // str是原始字符串 + String str = "CBBBAABBACAABDDEFBA"; + System.out.println(str); + // countMap是根据str建立的词频表 + HashMap countMap = countMap(str); + // hf是根据countMap生成的哈夫曼编码表 + HashMap hf = huffmanForm(countMap); + // huffmanEncode是原始字符串转译后的哈夫曼编码 + String huffmanEncode = huffmanEncode(str, hf); + System.out.println(huffmanEncode); + // huffmanDecode是哈夫曼编码还原成的原始字符串 + String huffmanDecode = huffmanDecode(huffmanEncode, hf); + System.out.println(huffmanDecode); + System.out.println("===================="); + System.out.println("大样本随机测试开始"); + // 字符串最大长度 + int len = 500; + // 所含字符种类 + int range = 26; + // 随机测试进行的次数 + int testTime = 100000; + for (int i = 0; i < testTime; i++) { + int N = (int) (Math.random() * len) + 1; + String test = randomNumberString(N, range); + HashMap counts = countMap(test); + HashMap form = huffmanForm(counts); + String encode = huffmanEncode(test, form); + String decode = huffmanDecode(encode, form); + if (!test.equals(decode)) { + System.out.println(test); + System.out.println(encode); + System.out.println(decode); + System.out.println("出错了!"); + } + } + System.out.println("大样本随机测试结束"); + } + +} diff --git a/体系学习班/class47/Code01_StrangePrinter.java b/体系学习班/class47/Code01_StrangePrinter.java new file mode 100644 index 0000000..b7172db --- /dev/null +++ b/体系学习班/class47/Code01_StrangePrinter.java @@ -0,0 +1,78 @@ +package class47; + +// 本题测试链接 : https://leetcode.com/problems/strange-printer/ +public class Code01_StrangePrinter { + + public static int strangePrinter1(String s) { + if (s == null || s.length() == 0) { + return 0; + } + char[] str = s.toCharArray(); + return process1(str, 0, str.length - 1); + } + + // 要想刷出str[L...R]的样子! + // 返回最少的转数 + public static int process1(char[] str, int L, int R) { + if (L == R) { + return 1; + } + // L...R + int ans = R - L + 1; + for (int k = L + 1; k <= R; k++) { + // L...k-1 k....R + ans = Math.min(ans, process1(str, L, k - 1) + process1(str, k, R) - (str[L] == str[k] ? 1 : 0)); + } + return ans; + } + + public static int strangePrinter2(String s) { + if (s == null || s.length() == 0) { + return 0; + } + char[] str = s.toCharArray(); + int N = str.length; + int[][] dp = new int[N][N]; + return process2(str, 0, N - 1, dp); + } + + public static int process2(char[] str, int L, int R, int[][] dp) { + if (dp[L][R] != 0) { + return dp[L][R]; + } + int ans = R - L + 1; + if (L == R) { + ans = 1; + } else { + for (int k = L + 1; k <= R; k++) { + ans = Math.min(ans, process2(str, L, k - 1, dp) + process2(str, k, R, dp) - (str[L] == str[k] ? 1 : 0)); + } + } + dp[L][R] = ans; + return ans; + } + + public static int strangePrinter3(String s) { + if (s == null || s.length() == 0) { + return 0; + } + char[] str = s.toCharArray(); + int N = str.length; + int[][] dp = new int[N][N]; + dp[N - 1][N - 1] = 1; + for (int i = 0; i < N - 1; i++) { + dp[i][i] = 1; + dp[i][i + 1] = str[i] == str[i + 1] ? 1 : 2; + } + for (int L = N - 3; L >= 0; L--) { + for (int R = L + 2; R < N; R++) { + dp[L][R] = R - L + 1; + for (int k = L + 1; k <= R; k++) { + dp[L][R] = Math.min(dp[L][R], dp[L][k - 1] + dp[k][R] - (str[L] == str[k] ? 1 : 0)); + } + } + } + return dp[0][N - 1]; + } + +} diff --git a/体系学习班/class47/Code02_RestoreWays.java b/体系学习班/class47/Code02_RestoreWays.java new file mode 100644 index 0000000..c9c583a --- /dev/null +++ b/体系学习班/class47/Code02_RestoreWays.java @@ -0,0 +1,246 @@ +package class47; + +// 整型数组arr长度为n(3 <= n <= 10^4),最初每个数字是<=200的正数且满足如下条件: +// 1. 0位置的要求:arr[0]<=arr[1] +// 2. n-1位置的要求:arr[n-1]<=arr[n-2] +// 3. 中间i位置的要求:arr[i]<=max(arr[i-1],arr[i+1]) +// 但是在arr有些数字丢失了,比如k位置的数字之前是正数,丢失之后k位置的数字为0 +// 请你根据上述条件,计算可能有多少种不同的arr可以满足以上条件 +// 比如 [6,0,9] 只有还原成 [6,9,9]满足全部三个条件,所以返回1种,即[6,9,9]达标 +public class Code02_RestoreWays { + + public static int ways0(int[] arr) { + return process0(arr, 0); + } + + public static int process0(int[] arr, int index) { + if (index == arr.length) { + return isValid(arr) ? 1 : 0; + } else { + if (arr[index] != 0) { + return process0(arr, index + 1); + } else { + int ways = 0; + for (int v = 1; v < 201; v++) { + arr[index] = v; + ways += process0(arr, index + 1); + } + arr[index] = 0; + return ways; + } + } + } + + public static boolean isValid(int[] arr) { + if (arr[0] > arr[1]) { + return false; + } + if (arr[arr.length - 1] > arr[arr.length - 2]) { + return false; + } + for (int i = 1; i < arr.length - 1; i++) { + if (arr[i] > Math.max(arr[i - 1], arr[i + 1])) { + return false; + } + } + return true; + } + + public static int ways1(int[] arr) { + int N = arr.length; + if (arr[N - 1] != 0) { + return process1(arr, N - 1, arr[N - 1], 2); + } else { + int ways = 0; + for (int v = 1; v < 201; v++) { + ways += process1(arr, N - 1, v, 2); + } + return ways; + } + } + + // 如果i位置的数字变成了v, + // 并且arr[i]和arr[i+1]的关系为s, + // s==0,代表arr[i] < arr[i+1] 右大 + // s==1,代表arr[i] == arr[i+1] 右=当前 + // s==2,代表arr[i] > arr[i+1] 右小 + // 返回0...i范围上有多少种有效的转化方式? + public static int process1(int[] arr, int i, int v, int s) { + if (i == 0) { // 0...i 只剩一个数了,0...0 + return ((s == 0 || s == 1) && (arr[0] == 0 || v == arr[0])) ? 1 : 0; + } + // i > 0 + if (arr[i] != 0 && v != arr[i]) { + return 0; + } + // i>0 ,并且, i位置的数真的可以变成V, + int ways = 0; + if (s == 0 || s == 1) { // [i] -> V <= [i+1] + for (int pre = 1; pre < 201; pre++) { + ways += process1(arr, i - 1, pre, pre < v ? 0 : (pre == v ? 1 : 2)); + } + } else { // ? 当前 > 右 当前 <= max{左,右} + for (int pre = v; pre < 201; pre++) { + ways += process1(arr, i - 1, pre, pre == v ? 1 : 2); + } + } + return ways; + } + + public static int zuo(int[] arr, int i, int v, int s) { + if (i == 0) { // 0...i 只剩一个数了,0...0 + return ((s == 0 || s == 1) && (arr[0] == 0 || v == arr[0])) ? 1 : 0; + } + // i > 0 + if (arr[i] != 0 && v != arr[i]) { + return 0; + } + // i>0 ,并且, i位置的数真的可以变成V, + int ways = 0; + if (s == 0 || s == 1) { // [i] -> V <= [i+1] + for (int pre = 1; pre < v; pre++) { + ways += zuo(arr, i - 1, pre, 0); + } + } + ways += zuo(arr, i - 1, v, 1); + for (int pre = v + 1; pre < 201; pre++) { + ways += zuo(arr, i - 1, pre, 2); + } + return ways; + } + + public static int ways2(int[] arr) { + int N = arr.length; + int[][][] dp = new int[N][201][3]; + if (arr[0] != 0) { + dp[0][arr[0]][0] = 1; + dp[0][arr[0]][1] = 1; + } else { + for (int v = 1; v < 201; v++) { + dp[0][v][0] = 1; + dp[0][v][1] = 1; + } + } + for (int i = 1; i < N; i++) { + for (int v = 1; v < 201; v++) { + for (int s = 0; s < 3; s++) { + if (arr[i] == 0 || v == arr[i]) { + if (s == 0 || s == 1) { + for (int pre = 1; pre < v; pre++) { + dp[i][v][s] += dp[i - 1][pre][0]; + } + } + dp[i][v][s] += dp[i - 1][v][1]; + for (int pre = v + 1; pre < 201; pre++) { + dp[i][v][s] += dp[i - 1][pre][2]; + } + } + } + } + } + if (arr[N - 1] != 0) { + return dp[N - 1][arr[N - 1]][2]; + } else { + int ways = 0; + for (int v = 1; v < 201; v++) { + ways += dp[N - 1][v][2]; + } + return ways; + } + } + + public static int ways3(int[] arr) { + int N = arr.length; + int[][][] dp = new int[N][201][3]; + if (arr[0] != 0) { + dp[0][arr[0]][0] = 1; + dp[0][arr[0]][1] = 1; + } else { + for (int v = 1; v < 201; v++) { + dp[0][v][0] = 1; + dp[0][v][1] = 1; + } + } + int[][] presum = new int[201][3]; + for (int v = 1; v < 201; v++) { + for (int s = 0; s < 3; s++) { + presum[v][s] = presum[v - 1][s] + dp[0][v][s]; + } + } + for (int i = 1; i < N; i++) { + for (int v = 1; v < 201; v++) { + for (int s = 0; s < 3; s++) { + if (arr[i] == 0 || v == arr[i]) { + if (s == 0 || s == 1) { + dp[i][v][s] += sum(1, v - 1, 0, presum); + } + dp[i][v][s] += dp[i - 1][v][1]; + dp[i][v][s] += sum(v + 1, 200, 2, presum); + } + } + } + for (int v = 1; v < 201; v++) { + for (int s = 0; s < 3; s++) { + presum[v][s] = presum[v - 1][s] + dp[i][v][s]; + } + } + } + if (arr[N - 1] != 0) { + return dp[N - 1][arr[N - 1]][2]; + } else { + return sum(1, 200, 2, presum); + } + } + + public static int sum(int begin, int end, int relation, int[][] presum) { + return presum[end][relation] - presum[begin - 1][relation]; + } + + // for test + public static int[] generateRandomArray(int len) { + int[] ans = new int[len]; + for (int i = 0; i < ans.length; i++) { + if (Math.random() < 0.5) { + ans[i] = 0; + } else { + ans[i] = (int) (Math.random() * 200) + 1; + } + } + return ans; + } + + // for test + public static void printArray(int[] arr) { + System.out.println("arr size : " + arr.length); + for (int i = 0; i < arr.length; i++) { + System.out.print(arr[i] + " "); + } + System.out.println(); + } + + public static void main(String[] args) { + int len = 4; + int testTime = 15; + System.out.println("功能测试开始"); + for (int i = 0; i < testTime; i++) { + int N = (int) (Math.random() * len) + 2; + int[] arr = generateRandomArray(N); + int ans0 = ways0(arr); + int ans1 = ways1(arr); + int ans2 = ways2(arr); + int ans3 = ways3(arr); + if (ans0 != ans1 || ans2 != ans3 || ans0 != ans2) { + System.out.println("Oops!"); + } + } + System.out.println("功能测试结束"); + System.out.println("==========="); + int N = 100000; + int[] arr = generateRandomArray(N); + long begin = System.currentTimeMillis(); + ways3(arr); + long end = System.currentTimeMillis(); + System.out.println("run time : " + (end - begin) + " ms"); + } + +} diff --git a/体系学习班/class47/Code03_DinicAlgorithm.java b/体系学习班/class47/Code03_DinicAlgorithm.java new file mode 100644 index 0000000..efed284 --- /dev/null +++ b/体系学习班/class47/Code03_DinicAlgorithm.java @@ -0,0 +1,139 @@ +// 本题测试链接: +// https://lightoj.com/problem/internet-bandwidth +// 这是一道DinicAlgorithm算法的题 +// 把如下代码粘贴进网页所提供的java编译器环境中 +// 不需要修改任何内容可以直接通过 +// 请看网页上的题目描述并结合main函数的写法去了解这个模板的用法 + +package class47; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.Scanner; + +public class Code03_DinicAlgorithm { + + public static class Edge { + public int from; + public int to; + public int available; + + public Edge(int a, int b, int c) { + from = a; + to = b; + available = c; + } + } + + public static class Dinic { + private int N; + private ArrayList> nexts; + private ArrayList edges; + private int[] depth; + private int[] cur; + + public Dinic(int nums) { + N = nums + 1; + nexts = new ArrayList<>(); + for (int i = 0; i <= N; i++) { + nexts.add(new ArrayList<>()); + } + edges = new ArrayList<>(); + depth = new int[N]; + cur = new int[N]; + } + + public void addEdge(int u, int v, int r) { + int m = edges.size(); + edges.add(new Edge(u, v, r)); + nexts.get(u).add(m); + edges.add(new Edge(v, u, 0)); + nexts.get(v).add(m + 1); + } + + public int maxFlow(int s, int t) { + int flow = 0; + while (bfs(s, t)) { + Arrays.fill(cur, 0); + flow += dfs(s, t, Integer.MAX_VALUE); + Arrays.fill(depth, 0); + } + return flow; + } + + private boolean bfs(int s, int t) { + LinkedList queue = new LinkedList<>(); + queue.addFirst(s); + boolean[] visited = new boolean[N]; + visited[s] = true; + while (!queue.isEmpty()) { + int u = queue.pollLast(); + for (int i = 0; i < nexts.get(u).size(); i++) { + Edge e = edges.get(nexts.get(u).get(i)); + int v = e.to; + if (!visited[v] && e.available > 0) { + visited[v] = true; + depth[v] = depth[u] + 1; + if (v == t) { + break; + } + queue.addFirst(v); + } + } + } + return visited[t]; + } + + // 当前来到了s点,s可变 + // 最终目标是t,t固定参数 + // r,收到的任务 + // 收集到的流,作为结果返回,ans <= r + private int dfs(int s, int t, int r) { + if (s == t || r == 0) { + return r; + } + int f = 0; + int flow = 0; + // s点从哪条边开始试 -> cur[s] + for (; cur[s] < nexts.get(s).size(); cur[s]++) { + int ei = nexts.get(s).get(cur[s]); + Edge e = edges.get(ei); + Edge o = edges.get(ei ^ 1); + if (depth[e.to] == depth[s] + 1 && (f = dfs(e.to, t, Math.min(e.available, r))) != 0) { + e.available -= f; + o.available += f; + flow += f; + r -= f; + if (r <= 0) { + break; + } + } + } + return flow; + } + } + + public static void main(String[] args) { + Scanner cin = new Scanner(System.in); + int cases = cin.nextInt(); + for (int i = 1; i <= cases; i++) { + int n = cin.nextInt(); + int s = cin.nextInt(); + int t = cin.nextInt(); + int m = cin.nextInt(); + Dinic dinic = new Dinic(n); + for (int j = 0; j < m; j++) { + int from = cin.nextInt(); + int to = cin.nextInt(); + int weight = cin.nextInt(); + dinic.addEdge(from, to, weight); + dinic.addEdge(to, from, weight); + } + int ans = dinic.maxFlow(s, t); + System.out.println("Case " + i + ": " + ans); + } + cin.close(); + } + +} \ No newline at end of file diff --git a/大厂刷题班/class01/Code01_CordCoverMaxPoint.java b/大厂刷题班/class01/Code01_CordCoverMaxPoint.java new file mode 100644 index 0000000..a95eaf4 --- /dev/null +++ b/大厂刷题班/class01/Code01_CordCoverMaxPoint.java @@ -0,0 +1,87 @@ +package class01; + +import java.util.Arrays; + +public class Code01_CordCoverMaxPoint { + + public static int maxPoint1(int[] arr, int L) { + int res = 1; + for (int i = 0; i < arr.length; i++) { + int nearest = nearestIndex(arr, i, arr[i] - L); + res = Math.max(res, i - nearest + 1); + } + return res; + } + + public static int nearestIndex(int[] arr, int R, int value) { + int L = 0; + int index = R; + while (L <= R) { + int mid = L + ((R - L) >> 1); + if (arr[mid] >= value) { + index = mid; + R = mid - 1; + } else { + L = mid + 1; + } + } + return index; + } + + public static int maxPoint2(int[] arr, int L) { + int left = 0; + int right = 0; + int N = arr.length; + int max = 0; + while (left < N) { + while (right < N && arr[right] - arr[left] <= L) { + right++; + } + max = Math.max(max, right - (left++)); + } + return max; + } + + // for test + public static int test(int[] arr, int L) { + int max = 0; + for (int i = 0; i < arr.length; i++) { + int pre = i - 1; + while (pre >= 0 && arr[i] - arr[pre] <= L) { + pre--; + } + max = Math.max(max, i - pre); + } + return max; + } + + // for test + public static int[] generateArray(int len, int max) { + int[] ans = new int[(int) (Math.random() * len) + 1]; + for (int i = 0; i < ans.length; i++) { + ans[i] = (int) (Math.random() * max); + } + Arrays.sort(ans); + return ans; + } + + public static void main(String[] args) { + int len = 100; + int max = 1000; + int testTime = 100000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int L = (int) (Math.random() * max); + int[] arr = generateArray(len, max); + int ans1 = maxPoint1(arr, L); + int ans2 = maxPoint2(arr, L); + int ans3 = test(arr, L); + if (ans1 != ans2 || ans2 != ans3) { + System.out.println("oops!"); + break; + } + } + + } + +} diff --git a/大厂刷题班/class01/Code02_CountFiles.java b/大厂刷题班/class01/Code02_CountFiles.java new file mode 100644 index 0000000..f09d132 --- /dev/null +++ b/大厂刷题班/class01/Code02_CountFiles.java @@ -0,0 +1,40 @@ +package class01; + +import java.io.File; +import java.util.Stack; + +public class Code02_CountFiles { + + // 注意这个函数也会统计隐藏文件 + public static int getFileNumber(String folderPath) { + File root = new File(folderPath); + if (!root.isDirectory() && !root.isFile()) { + return 0; + } + if (root.isFile()) { + return 1; + } + Stack stack = new Stack<>(); + stack.add(root); + int files = 0; + while (!stack.isEmpty()) { + File folder = stack.pop(); + for (File next : folder.listFiles()) { + if (next.isFile()) { + files++; + } + if (next.isDirectory()) { + stack.push(next); + } + } + } + return files; + } + + public static void main(String[] args) { + // 你可以自己更改目录 + String path = "/Users/zuochengyun/Desktop/"; + System.out.println(getFileNumber(path)); + } + +} diff --git a/大厂刷题班/class01/Code03_Near2Power.java b/大厂刷题班/class01/Code03_Near2Power.java new file mode 100644 index 0000000..43aca3b --- /dev/null +++ b/大厂刷题班/class01/Code03_Near2Power.java @@ -0,0 +1,22 @@ +package class01; + +public class Code03_Near2Power { + + // 已知n是正数 + // 返回大于等于,且最接近n的,2的某次方的值 + public static final int tableSizeFor(int n) { + n--; + n |= n >>> 1; + n |= n >>> 2; + n |= n >>> 4; + n |= n >>> 8; + n |= n >>> 16; + return (n < 0) ? 1 : n + 1; + } + + public static void main(String[] args) { + int cap = 120; + System.out.println(tableSizeFor(cap)); + } + +} diff --git a/大厂刷题班/class01/Code04_MinSwapStep.java b/大厂刷题班/class01/Code04_MinSwapStep.java new file mode 100644 index 0000000..d659451 --- /dev/null +++ b/大厂刷题班/class01/Code04_MinSwapStep.java @@ -0,0 +1,74 @@ +package class01; + +public class Code04_MinSwapStep { + + // 一个数组中只有两种字符'G'和'B', + // 可以让所有的G都放在左侧,所有的B都放在右侧 + // 或者可以让所有的G都放在右侧,所有的B都放在左侧 + // 但是只能在相邻字符之间进行交换操作,请问请问至少需要交换几次, + public static int minSteps1(String s) { + if (s == null || s.equals("")) { + return 0; + } + char[] str = s.toCharArray(); + int step1 = 0; + int gi = 0; + for (int i = 0; i < str.length; i++) { + if (str[i] == 'G') { + step1 += i - (gi++); + } + } + int step2 = 0; + int bi = 0; + for (int i = 0; i < str.length; i++) { + if (str[i] == 'B') { + step2 += i - (bi++); + } + } + return Math.min(step1, step2); + } + + // 可以让G在左,或者在右 + public static int minSteps2(String s) { + if (s == null || s.equals("")) { + return 0; + } + char[] str = s.toCharArray(); + int step1 = 0; + int step2 = 0; + int gi = 0; + int bi = 0; + for (int i = 0; i < str.length; i++) { + if (str[i] == 'G') { // 当前的G,去左边 方案1 + step1 += i - (gi++); + } else {// 当前的B,去左边 方案2 + step2 += i - (bi++); + } + } + return Math.min(step1, step2); + } + + // 为了测试 + public static String randomString(int maxLen) { + char[] str = new char[(int) (Math.random() * maxLen)]; + for (int i = 0; i < str.length; i++) { + str[i] = Math.random() < 0.5 ? 'G' : 'B'; + } + return String.valueOf(str); + } + + public static void main(String[] args) { + int maxLen = 100; + int testTime = 1000000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + String str = randomString(maxLen); + int ans1 = minSteps1(str); + int ans2 = minSteps2(str); + if (ans1 != ans2) { + System.out.println("Oops!"); + } + } + System.out.println("测试结束"); + } +} \ No newline at end of file diff --git a/大厂刷题班/class01/Code05_LongestIncreasingPath.java b/大厂刷题班/class01/Code05_LongestIncreasingPath.java new file mode 100644 index 0000000..682f6b5 --- /dev/null +++ b/大厂刷题班/class01/Code05_LongestIncreasingPath.java @@ -0,0 +1,54 @@ +package class01; + +public class Code05_LongestIncreasingPath { + + public static int longestIncreasingPath1(int[][] matrix) { + int ans = 0; + int N = matrix.length; + int M = matrix[0].length; + for (int i = 0; i < N; i++) { + for (int j = 0; j < M; j++) { + ans = Math.max(ans, process1(matrix, i, j)); + } + } + return ans; + } + + // 从m[i][j]开始走,走出来的最长递增链,返回! + public static int process1(int[][] m, int i, int j) { + int up = i > 0 && m[i][j] < m[i - 1][j] ? process1(m, i - 1, j) : 0; + int down = i < (m.length - 1) && m[i][j] < m[i + 1][j] ? process1(m, i + 1, j) : 0; + int left = j > 0 && m[i][j] < m[i][j - 1] ? process1(m, i, j - 1) : 0; + int right = j < (m[0].length - 1) && m[i][j] < m[i][j + 1] ? process1(m, i, j + 1) : 0; + return Math.max(Math.max(up, down), Math.max(left, right)) + 1; + } + + public static int longestIncreasingPath2(int[][] matrix) { + int ans = 0; + int N = matrix.length; + int M = matrix[0].length; + int[][] dp = new int[N][M]; + for (int i = 0; i < N; i++) { + for (int j = 0; j < M; j++) { + ans = Math.max(ans, process2(matrix, i, j, dp)); + } + } + return ans; + } + + // 从m[i][j]开始走,走出来的最长递增链,返回! + public static int process2(int[][] m, int i, int j, int[][] dp) { + if (dp[i][j] != 0) { + return dp[i][j]; + } + // (i,j)不越界 + int up = i > 0 && m[i][j] < m[i - 1][j] ? process2(m, i - 1, j, dp) : 0; + int down = i < (m.length - 1) && m[i][j] < m[i + 1][j] ? process2(m, i + 1, j, dp) : 0; + int left = j > 0 && m[i][j] < m[i][j - 1] ? process2(m, i, j - 1, dp) : 0; + int right = j < (m[0].length - 1) && m[i][j] < m[i][j + 1] ? process2(m, i, j + 1, dp) : 0; + int ans = Math.max(Math.max(up, down), Math.max(left, right)) + 1; + dp[i][j] = ans; + return ans; + } + +} diff --git a/大厂刷题班/class01/Code06_AOE.java b/大厂刷题班/class01/Code06_AOE.java new file mode 100644 index 0000000..6b8e72c --- /dev/null +++ b/大厂刷题班/class01/Code06_AOE.java @@ -0,0 +1,304 @@ +package class01; + +import java.util.Arrays; + +/* + * 给定两个数组x和hp,长度都是N。 + * x数组一定是有序的,x[i]表示i号怪兽在x轴上的位置 + * hp数组不要求有序,hp[i]表示i号怪兽的血量 + * 为了方便起见,可以认为x数组和hp数组中没有负数。 + * 再给定一个正数range,表示如果法师释放技能的范围长度(直径!) + * 被打到的每只怪兽损失1点血量。 + * 返回要把所有怪兽血量清空,至少需要释放多少次aoe技能? + * 三个参数:int[] x, int[] hp, int range + * 返回:int 次数 + * */ +public class Code06_AOE { + + // 纯暴力解法 + // 太容易超时 + // 只能小样本量使用 + public static int minAoe1(int[] x, int[] hp, int range) { + boolean allClear = true; + for (int i = 0; i < hp.length; i++) { + if (hp[i] > 0) { + allClear = false; + break; + } + } + if (allClear) { + return 0; + } else { + int ans = Integer.MAX_VALUE; + for (int left = 0; left < x.length; left++) { + if (hasHp(x, hp, left, range)) { + minusOneHp(x, hp, left, range); + ans = Math.min(ans, 1 + minAoe1(x, hp, range)); + addOneHp(x, hp, left, range); + } + } + return ans; + } + } + + public static boolean hasHp(int[] x, int[] hp, int left, int range) { + for (int index = left; index < x.length && x[index] - x[left] <= range; index++) { + if (hp[index] > 0) { + return true; + } + } + return false; + } + + public static void minusOneHp(int[] x, int[] hp, int left, int range) { + for (int index = left; index < x.length && x[index] - x[left] <= range; index++) { + hp[index]--; + } + } + + public static void addOneHp(int[] x, int[] hp, int left, int range) { + for (int index = left; index < x.length && x[index] - x[left] <= range; index++) { + hp[index]++; + } + } + + // 为了验证 + // 不用线段树,但是贪心的思路,和课上一样 + // 1) 总是用技能的最左边缘刮死当前最左侧的没死的怪物 + // 2) 然后向右找下一个没死的怪物,重复步骤1) + public static int minAoe2(int[] x, int[] hp, int range) { + // 举个例子: + // 如果怪兽情况如下, + // 怪兽所在,x数组 : 2 3 5 6 7 9 + // 怪兽血量,hp数组 : 2 4 1 2 3 1 + // 怪兽编号 : 0 1 2 3 4 5 + // 技能直径,range = 2 + int n = x.length; + int[] cover = new int[n]; + // 首先求cover数组, + // 如果技能左边界就在0号怪兽,那么技能到2号怪兽就覆盖不到了 + // 所以cover[0] = 2; + // 如果技能左边界就在1号怪兽,那么技能到3号怪兽就覆盖不到了 + // 所以cover[1] = 3; + // 如果技能左边界就在2号怪兽,那么技能到5号怪兽就覆盖不到了 + // 所以cover[2] = 5; + // 如果技能左边界就在3号怪兽,那么技能到5号怪兽就覆盖不到了 + // 所以cover[3] = 5; + // 如果技能左边界就在4号怪兽,那么技能到6号怪兽(越界位置)就覆盖不到了 + // 所以cover[4] = 6(越界位置); + // 如果技能左边界就在5号怪兽,那么技能到6号怪兽(越界位置)就覆盖不到了 + // 所以cover[5] = 6(越界位置); + // 综上: + // 如果怪兽情况如下, + // 怪兽所在,x数组 : 2 3 5 6 7 9 + // 怪兽血量,hp数组 : 2 4 1 2 3 1 + // 怪兽编号 : 0 1 2 3 4 5 + // cover数组情况 : 2 3 5 5 6 6 + // 技能直径,range = 2 + // cover[i] = j,表示如果技能左边界在i怪兽,那么技能会影响i...j-1号所有的怪兽 + // 就是如下的for循环,在求cover数组 + int r = 0; + for (int i = 0; i < n; i++) { + while (r < n && x[r] - x[i] <= range) { + r++; + } + cover[i] = r; + } + int ans = 0; + for (int i = 0; i < n; i++) { + // 假设来到i号怪兽了 + // 如果i号怪兽的血量>0,说明i号怪兽没死 + // 根据我们课上讲的贪心: + // 我们要让技能的左边界,刮死当前的i号怪兽 + // 这样能够让技能尽可能的往右释放,去尽可能的打掉右侧的怪兽 + // 此时cover[i],正好的告诉我们,技能影响多大范围。 + // 比如当前来到100号怪兽,血量30 + // 假设cover[100] == 200 + // 说明,技能左边界在100位置,可以影响100号到199号怪兽的血量。 + // 为了打死100号怪兽,我们释放技能30次, + // 释放的时候,100号到199号怪兽都掉血,30点 + // 然后继续向右寻找没死的怪兽,像课上讲的一样 + if (hp[i] > 0) { + int minus = hp[i]; + for (int index = i; index < cover[i]; index++) { + hp[index] -= minus; + } + ans += minus; + } + } + return ans; + } + + // 正式方法 + // 关键点就是: + // 1) 线段树 + // 2) 总是用技能的最左边缘刮死当前最左侧的没死的怪物 + // 3) 然后向右找下一个没死的怪物,重复步骤2) + public static int minAoe3(int[] x, int[] hp, int range) { + int n = x.length; + int[] cover = new int[n]; + int r = 0; + // cover[i] : 如果i位置是技能的最左侧,技能往右的range范围内,最右影响到哪 + for (int i = 0; i < n; i++) { + while (r < n && x[r] - x[i] <= range) { + r++; + } + cover[i] = r - 1; + } + SegmentTree st = new SegmentTree(hp); + st.build(1, n, 1); + int ans = 0; + for (int i = 1; i <= n; i++) { + int leftHP = st.query(i, i, 1, n, 1); + if (leftHP > 0) { + ans += leftHP; + st.add(i, cover[i - 1] + 1, -leftHP, 1, n, 1); + } + } + return ans; + } + + public static class SegmentTree { + private int MAXN; + private int[] arr; + private int[] sum; + private int[] lazy; + + public SegmentTree(int[] origin) { + MAXN = origin.length + 1; + arr = new int[MAXN]; + for (int i = 1; i < MAXN; i++) { + arr[i] = origin[i - 1]; + } + sum = new int[MAXN << 2]; + lazy = new int[MAXN << 2]; + } + + private void pushUp(int rt) { + sum[rt] = sum[rt << 1] + sum[rt << 1 | 1]; + } + + private void pushDown(int rt, int ln, int rn) { + if (lazy[rt] != 0) { + lazy[rt << 1] += lazy[rt]; + sum[rt << 1] += lazy[rt] * ln; + lazy[rt << 1 | 1] += lazy[rt]; + sum[rt << 1 | 1] += lazy[rt] * rn; + lazy[rt] = 0; + } + } + + public void build(int l, int r, int rt) { + if (l == r) { + sum[rt] = arr[l]; + return; + } + int mid = (l + r) >> 1; + build(l, mid, rt << 1); + build(mid + 1, r, rt << 1 | 1); + pushUp(rt); + } + + public void add(int L, int R, int C, int l, int r, int rt) { + if (L <= l && r <= R) { + sum[rt] += C * (r - l + 1); + lazy[rt] += C; + return; + } + int mid = (l + r) >> 1; + pushDown(rt, mid - l + 1, r - mid); + if (L <= mid) { + add(L, R, C, l, mid, rt << 1); + } + if (R > mid) { + add(L, R, C, mid + 1, r, rt << 1 | 1); + } + pushUp(rt); + } + + public int query(int L, int R, int l, int r, int rt) { + if (L <= l && r <= R) { + return sum[rt]; + } + int mid = (l + r) >> 1; + pushDown(rt, mid - l + 1, r - mid); + int ans = 0; + if (L <= mid) { + ans += query(L, R, l, mid, rt << 1); + } + if (R > mid) { + ans += query(L, R, mid + 1, r, rt << 1 | 1); + } + return ans; + } + + } + + // 为了测试 + public static int[] randomArray(int n, int valueMax) { + int[] ans = new int[n]; + for (int i = 0; i < n; i++) { + ans[i] = (int) (Math.random() * valueMax) + 1; + } + return ans; + } + + // 为了测试 + public static int[] copyArray(int[] arr) { + int N = arr.length; + int[] ans = new int[N]; + for (int i = 0; i < N; i++) { + ans[i] = arr[i]; + } + return ans; + } + + // 为了测试 + public static void main(String[] args) { + int N = 50; + int X = 500; + int H = 60; + int R = 10; + int testTime = 50000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int len = (int) (Math.random() * N) + 1; + int[] x2 = randomArray(len, X); + Arrays.sort(x2); + int[] hp2 = randomArray(len, H); + int[] x3 = copyArray(x2); + int[] hp3 = copyArray(hp2); + int range = (int) (Math.random() * R) + 1; + int ans2 = minAoe2(x2, hp2, range); + int ans3 = minAoe3(x3, hp3, range); + if (ans2 != ans3) { + System.out.println("出错了!"); + } + } + System.out.println("测试结束"); + + N = 500000; + long start; + long end; + int[] x2 = randomArray(N, N); + Arrays.sort(x2); + int[] hp2 = new int[N]; + for (int i = 0; i < N; i++) { + hp2[i] = i * 5 + 10; + } + int[] x3 = copyArray(x2); + int[] hp3 = copyArray(hp2); + int range = 10000; + + start = System.currentTimeMillis(); + System.out.println(minAoe2(x2, hp2, range)); + end = System.currentTimeMillis(); + System.out.println("运行时间 : " + (end - start) + " 毫秒"); + + start = System.currentTimeMillis(); + System.out.println(minAoe3(x3, hp3, range)); + end = System.currentTimeMillis(); + System.out.println("运行时间 : " + (end - start) + " 毫秒"); + } + +} diff --git a/大厂刷题班/class01/Code07_TargetSum.java b/大厂刷题班/class01/Code07_TargetSum.java new file mode 100644 index 0000000..a91f560 --- /dev/null +++ b/大厂刷题班/class01/Code07_TargetSum.java @@ -0,0 +1,127 @@ +package class01; + +import java.util.HashMap; + +// leetcode 494题 +public class Code07_TargetSum { + + public static int findTargetSumWays1(int[] arr, int s) { + return process1(arr, 0, s); + } + + // 可以自由使用arr[index....]所有的数字! + // 搞出rest这个数,方法数是多少?返回 + // index == 7 rest = 13 + // map "7_13" 256 + public static int process1(int[] arr, int index, int rest) { + if (index == arr.length) { // 没数了! + return rest == 0 ? 1 : 0; + } + // 还有数!arr[index] arr[index+1 ... ] + return process1(arr, index + 1, rest - arr[index]) + process1(arr, index + 1, rest + arr[index]); + } + + public static int findTargetSumWays2(int[] arr, int s) { + return process2(arr, 0, s, new HashMap<>()); + } + + public static int process2(int[] arr, int index, int rest, HashMap> dp) { + if (dp.containsKey(index) && dp.get(index).containsKey(rest)) { + return dp.get(index).get(rest); + } + // 否则,没命中! + int ans = 0; + if (index == arr.length) { + ans = rest == 0 ? 1 : 0; + } else { + ans = process2(arr, index + 1, rest - arr[index], dp) + process2(arr, index + 1, rest + arr[index], dp); + } + if (!dp.containsKey(index)) { + dp.put(index, new HashMap<>()); + } + dp.get(index).put(rest, ans); + return ans; + } + + // 优化点一 : + // 你可以认为arr中都是非负数 + // 因为即便是arr中有负数,比如[3,-4,2] + // 因为你能在每个数前面用+或者-号 + // 所以[3,-4,2]其实和[3,4,2]达成一样的效果 + // 那么我们就全把arr变成非负数,不会影响结果的 + // 优化点二 : + // 如果arr都是非负数,并且所有数的累加和是sum + // 那么如果target> 1); + } + + // 求非负数组nums有多少个子集,累加和是s + // 二维动态规划 + // 不用空间压缩 + public static int subset1(int[] nums, int s) { + if (s < 0) { + return 0; + } + int n = nums.length; + // dp[i][j] : nums前缀长度为i的所有子集,有多少累加和是j? + int[][] dp = new int[n + 1][s + 1]; + // nums前缀长度为0的所有子集,有多少累加和是0?一个:空集 + dp[0][0] = 1; + for (int i = 1; i <= n; i++) { + for (int j = 0; j <= s; j++) { + dp[i][j] = dp[i - 1][j]; + if (j - nums[i - 1] >= 0) { + dp[i][j] += dp[i - 1][j - nums[i - 1]]; + } + } + } + return dp[n][s]; + } + + // 求非负数组nums有多少个子集,累加和是s + // 二维动态规划 + // 用空间压缩: + // 核心就是for循环里面的:for (int i = s; i >= n; i--) { + // 为啥不枚举所有可能的累加和?只枚举 n...s 这些累加和? + // 因为如果 i - n < 0,dp[i]怎么更新?和上一步的dp[i]一样!所以不用更新 + // 如果 i - n >= 0,dp[i]怎么更新?上一步的dp[i] + 上一步dp[i - n]的值,这才需要更新 + public static int subset2(int[] nums, int s) { + if (s < 0) { + return 0; + } + int[] dp = new int[s + 1]; + dp[0] = 1; + for (int n : nums) { + for (int i = s; i >= n; i--) { + dp[i] += dp[i - n]; + } + } + return dp[s]; + } + +} diff --git a/大厂刷题班/class02/Code01_ChooseWork.java b/大厂刷题班/class02/Code01_ChooseWork.java new file mode 100644 index 0000000..4630a33 --- /dev/null +++ b/大厂刷题班/class02/Code01_ChooseWork.java @@ -0,0 +1,48 @@ +package class02; + +import java.util.Arrays; +import java.util.Comparator; +import java.util.TreeMap; + +public class Code01_ChooseWork { + + public static class Job { + public int money; + public int hard; + + public Job(int m, int h) { + money = m; + hard = h; + } + } + + public static class JobComparator implements Comparator { + @Override + public int compare(Job o1, Job o2) { + return o1.hard != o2.hard ? (o1.hard - o2.hard) : (o2.money - o1.money); + } + } + + public static int[] getMoneys(Job[] job, int[] ability) { + Arrays.sort(job, new JobComparator()); + // key : 难度 value:报酬 + TreeMap map = new TreeMap<>(); + map.put(job[0].hard, job[0].money); + // pre : 上一份进入map的工作 + Job pre = job[0]; + for (int i = 1; i < job.length; i++) { + if (job[i].hard != pre.hard && job[i].money > pre.money) { + pre = job[i]; + map.put(pre.hard, pre.money); + } + } + int[] ans = new int[ability.length]; + for (int i = 0; i < ability.length; i++) { + // ability[i] 当前人的能力 <= ability[i] 且离它最近的 + Integer key = map.floorKey(ability[i]); + ans[i] = key != null ? map.get(key) : 0; + } + return ans; + } + +} diff --git a/大厂刷题班/class02/Code02_Cola.java b/大厂刷题班/class02/Code02_Cola.java new file mode 100644 index 0000000..c41d0fb --- /dev/null +++ b/大厂刷题班/class02/Code02_Cola.java @@ -0,0 +1,151 @@ +package class02; + +public class Code02_Cola { + /* + * 买饮料 时间限制: 3000MS 内存限制: 589824KB 题目描述: + * 游游今年就要毕业了,和同学们在携程上定制了日本毕业旅行。愉快的一天行程结束后大家回到了酒店房间,这时候同学们都很口渴, + * 石头剪刀布选出游游去楼下的自动贩卖机给大家买可乐。 贩卖机只支持硬币支付,且收退都只支持10 ,50,100 + * 三种面额。一次购买行为只能出一瓶可乐,且每次购买后总是找零最小枚数的硬币。(例如投入100圆,可乐30圆,则找零50圆一枚,10圆两枚) + * 游游需要购买的可乐数量是 m,其中手头拥有的 10,50,100 面额硬币的枚数分别是 a,b,c,可乐的价格是x(x是10的倍数)。 + * 如果游游优先使用大面额购买且钱是够的情况下,请计算出需要投入硬币次数? 输入描述 依次输入, 需要可乐的数量为 m 10元的张数为 a 50元的张数为 b + * 100元的张树为 c 1瓶可乐的价格为 x 输出描述 输出当前金额下需要投入硬币的次数 + * 例如需要购买2瓶可乐,每瓶可乐250圆,手里有100圆3枚,50圆4枚,10圆1枚。 购买第1瓶投递100圆3枚,找50圆 购买第2瓶投递50圆5枚 + * 所以是总共需要操作8次金额投递操作 样例输入 2 1 4 3 250 样例输出 8 + */ + + // 暴力尝试,为了验证正式方法而已 + public static int right(int m, int a, int b, int c, int x) { + int[] qian = { 100, 50, 10 }; + int[] zhang = { c, b, a }; + int puts = 0; + while (m != 0) { + int cur = buy(qian, zhang, x); + if (cur == -1) { + return -1; + } + puts += cur; + m--; + } + return puts; + } + + public static int buy(int[] qian, int[] zhang, int rest) { + int first = -1; + for (int i = 0; i < 3; i++) { + if (zhang[i] != 0) { + first = i; + break; + } + } + if (first == -1) { + return -1; + } + if (qian[first] >= rest) { + zhang[first]--; + giveRest(qian, zhang, first + 1, qian[first] - rest, 1); + return 1; + } else { + zhang[first]--; + int next = buy(qian, zhang, rest - qian[first]); + if (next == -1) { + return -1; + } + return 1 + next; + } + } + + // 正式的方法 + // 要买的可乐数量,m + // 100元有a张 + // 50元有b张 + // 10元有c张 + // 可乐单价x + public static int putTimes(int m, int a, int b, int c, int x) { + // 0 1 2 + int[] qian = { 100, 50, 10 }; + int[] zhang = { c, b, a }; + // 总共需要多少次投币 + int puts = 0; + // 之前面值的钱还剩下多少总钱数 + int preQianRest = 0; + // 之前面值的钱还剩下多少总张数 + int preQianZhang = 0; + for (int i = 0; i < 3 && m != 0; i++) { + // 要用之前剩下的钱、当前面值的钱,共同买第一瓶可乐 + // 之前的面值剩下多少钱,是preQianRest + // 之前的面值剩下多少张,是preQianZhang + // 之所以之前的面值会剩下来,一定是剩下的钱,一直攒不出一瓶可乐的单价 + // 当前的面值付出一些钱+之前剩下的钱,此时有可能凑出一瓶可乐来 + // 那么当前面值参与搞定第一瓶可乐,需要掏出多少张呢?就是curQianFirstBuyZhang + int curQianFirstBuyZhang = (x - preQianRest + qian[i] - 1) / qian[i]; + if (zhang[i] >= curQianFirstBuyZhang) { // 如果之前的钱和当前面值的钱,能凑出第一瓶可乐 + // 凑出来了一瓶可乐也可能存在找钱的情况, + giveRest(qian, zhang, i + 1, (preQianRest + qian[i] * curQianFirstBuyZhang) - x, 1); + puts += curQianFirstBuyZhang + preQianZhang; + zhang[i] -= curQianFirstBuyZhang; + m--; + } else { // 如果之前的钱和当前面值的钱,不能凑出第一瓶可乐 + preQianRest += qian[i] * zhang[i]; + preQianZhang += zhang[i]; + continue; + } + // 凑出第一瓶可乐之后,当前的面值有可能能继续买更多的可乐 + // 以下过程就是后续的可乐怎么用当前面值的钱来买 + // 用当前面值的钱,买一瓶可乐需要几张 + int curQianBuyOneColaZhang = (x + qian[i] - 1) / qian[i]; + // 用当前面值的钱,一共可以搞定几瓶可乐 + int curQianBuyColas = Math.min(zhang[i] / curQianBuyOneColaZhang, m); + // 用当前面值的钱,每搞定一瓶可乐,收货机会吐出多少零钱 + int oneTimeRest = qian[i] * curQianBuyOneColaZhang - x; + // 每次买一瓶可乐,吐出的找零总钱数是oneTimeRest + // 一共买的可乐数是curQianBuyColas,所以把零钱去提升后面几种面值的硬币数, + // 就是giveRest的含义 + giveRest(qian, zhang, i + 1, oneTimeRest, curQianBuyColas); + // 当前面值去搞定可乐这件事,一共投了几次币 + puts += curQianBuyOneColaZhang * curQianBuyColas; + // 还剩下多少瓶可乐需要去搞定,继续用后面的面值搞定去吧 + m -= curQianBuyColas; + // 当前面值可能剩下若干张,要参与到后续买可乐的过程中去, + // 所以要更新preQianRest和preQianZhang + zhang[i] -= curQianBuyOneColaZhang * curQianBuyColas; + preQianRest = qian[i] * zhang[i]; + preQianZhang = zhang[i]; + } + return m == 0 ? puts : -1; + } + + public static void giveRest(int[] qian, int[] zhang, int i, int oneTimeRest, int times) { + for (; i < 3; i++) { + zhang[i] += (oneTimeRest / qian[i]) * times; + oneTimeRest %= qian[i]; + } + } + + public static void main(String[] args) { + int testTime = 1000; + int zhangMax = 10; + int colaMax = 10; + int priceMax = 20; + System.out.println("如果错误会打印错误数据,否则就是正确"); + System.out.println("test begin"); + for (int i = 0; i < testTime; i++) { + int m = (int) (Math.random() * colaMax); + int a = (int) (Math.random() * zhangMax); + int b = (int) (Math.random() * zhangMax); + int c = (int) (Math.random() * zhangMax); + int x = ((int) (Math.random() * priceMax) + 1) * 10; + int ans1 = putTimes(m, a, b, c, x); + int ans2 = right(m, a, b, c, x); + if (ans1 != ans2) { + System.out.println("int m = " + m + ";"); + System.out.println("int a = " + a + ";"); + System.out.println("int b = " + b + ";"); + System.out.println("int c = " + c + ";"); + System.out.println("int x = " + x + ";"); + break; + } + } + System.out.println("test end"); + } + +} diff --git a/大厂刷题班/class02/Code03_ReceiveAndPrintOrderLine.java b/大厂刷题班/class02/Code03_ReceiveAndPrintOrderLine.java new file mode 100644 index 0000000..7db9ba3 --- /dev/null +++ b/大厂刷题班/class02/Code03_ReceiveAndPrintOrderLine.java @@ -0,0 +1,89 @@ +package class02; + +import java.util.HashMap; + +public class Code03_ReceiveAndPrintOrderLine { + + public static class Node { + public String info; + public Node next; + + public Node(String str) { + info = str; + } + } + + public static class MessageBox { + private HashMap headMap; + private HashMap tailMap; + private int waitPoint; + + public MessageBox() { + headMap = new HashMap(); + tailMap = new HashMap(); + waitPoint = 1; + } + + // 消息的编号,info消息的内容, 消息一定从1开始 + public void receive(int num, String info) { + if (num < 1) { + return; + } + Node cur = new Node(info); + // num~num + headMap.put(num, cur); + tailMap.put(num, cur); + // 建立了num~num这个连续区间的头和尾 + // 查询有没有某个连续区间以num-1结尾 + if (tailMap.containsKey(num - 1)) { + tailMap.get(num - 1).next = cur; + tailMap.remove(num - 1); + headMap.remove(num); + } + // 查询有没有某个连续区间以num+1开头的 + if (headMap.containsKey(num + 1)) { + cur.next = headMap.get(num + 1); + tailMap.remove(num); + headMap.remove(num + 1); + } + if (num == waitPoint) { + print(); + } + } + + private void print() { + Node node = headMap.get(waitPoint); + headMap.remove(waitPoint); + while (node != null) { + System.out.print(node.info + " "); + node = node.next; + waitPoint++; + } + tailMap.remove(waitPoint-1); + System.out.println(); + } + + } + + public static void main(String[] args) { + // MessageBox only receive 1~N + MessageBox box = new MessageBox(); + // 1.... + System.out.println("这是2来到的时候"); + box.receive(2,"B"); // - 2" + System.out.println("这是1来到的时候"); + box.receive(1,"A"); // 1 2 -> print, trigger is 1 + box.receive(4,"D"); // - 4 + box.receive(5,"E"); // - 4 5 + box.receive(7,"G"); // - 4 5 - 7 + box.receive(8,"H"); // - 4 5 - 7 8 + box.receive(6,"F"); // - 4 5 6 7 8 + box.receive(3,"C"); // 3 4 5 6 7 8 -> print, trigger is 3 + box.receive(9,"I"); // 9 -> print, trigger is 9 + box.receive(10,"J"); // 10 -> print, trigger is 10 + box.receive(12,"L"); // - 12 + box.receive(13,"M"); // - 12 13 + box.receive(11,"K"); // 11 12 13 -> print, trigger is 11 + + } +} diff --git a/大厂刷题班/class02/Code04_Drive.java b/大厂刷题班/class02/Code04_Drive.java new file mode 100644 index 0000000..a5a3a07 --- /dev/null +++ b/大厂刷题班/class02/Code04_Drive.java @@ -0,0 +1,116 @@ +package class02; + +import java.util.Arrays; + +public class Code04_Drive { + + // 课上的现场版本 + // income -> N * 2 的矩阵 N是偶数! + // 0 [9, 13] + // 1 [45,60] + public static int maxMoney1(int[][] income) { + if (income == null || income.length < 2 || (income.length & 1) != 0) { + return 0; + } + int N = income.length; // 司机数量一定是偶数,所以才能平分,A N /2 B N/2 + int M = N >> 1; // M = N / 2 要去A区域的人 + return process1(income, 0, M); + } + + // index.....所有的司机,往A和B区域分配! + // A区域还有rest个名额! + // 返回把index...司机,分配完,并且最终A和B区域同样多的情况下,index...这些司机,整体收入最大是多少! + public static int process1(int[][] income, int index, int rest) { + if (index == income.length) { + return 0; + } + // 还剩下司机! + if (income.length - index == rest) { + return income[index][0] + process1(income, index + 1, rest - 1); + } + if (rest == 0) { + return income[index][1] + process1(income, index + 1, rest); + } + // 当前司机,可以去A,或者去B + int p1 = income[index][0] + process1(income, index + 1, rest - 1); + int p2 = income[index][1] + process1(income, index + 1, rest); + return Math.max(p1, p2); + } + + // 严格位置依赖的动态规划版本 + public static int maxMoney2(int[][] income) { + int N = income.length; + int M = N >> 1; + int[][] dp = new int[N + 1][M + 1]; + for (int i = N - 1; i >= 0; i--) { + for (int j = 0; j <= M; j++) { + if (N - i == j) { + dp[i][j] = income[i][0] + dp[i + 1][j - 1]; + } else if (j == 0) { + dp[i][j] = income[i][1] + dp[i + 1][j]; + } else { + int p1 = income[i][0] + dp[i + 1][j - 1]; + int p2 = income[i][1] + dp[i + 1][j]; + dp[i][j] = Math.max(p1, p2); + } + } + } + return dp[0][M]; + } + + // 这题有贪心策略 : + // 假设一共有10个司机,思路是先让所有司机去A,得到一个总收益 + // 然后看看哪5个司机改换门庭(去B),可以获得最大的额外收益 + // 这道题有贪心策略,打了我的脸 + // 但是我课上提到的技巧请大家重视 + // 根据数据量猜解法可以省去大量的多余分析,节省时间 + // 这里感谢卢圣文同学 + public static int maxMoney3(int[][] income) { + int N = income.length; + int[] arr = new int[N]; + int sum = 0; + for (int i = 0; i < N; i++) { + arr[i] = income[i][1] - income[i][0]; + sum += income[i][0]; + } + Arrays.sort(arr); + int M = N >> 1; + for (int i = N - 1; i >= M; i--) { + sum += arr[i]; + } + return sum; + } + + // 返回随机len*2大小的正数矩阵 + // 值在0~value-1之间 + public static int[][] randomMatrix(int len, int value) { + int[][] ans = new int[len << 1][2]; + for (int i = 0; i < ans.length; i++) { + ans[i][0] = (int) (Math.random() * value); + ans[i][1] = (int) (Math.random() * value); + } + return ans; + } + + public static void main(String[] args) { + int N = 10; + int value = 100; + int testTime = 500; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int len = (int) (Math.random() * N) + 1; + int[][] matrix = randomMatrix(len, value); + int ans1 = maxMoney1(matrix); + int ans2 = maxMoney2(matrix); + int ans3 = maxMoney3(matrix); + if (ans1 != ans2 || ans1 != ans3) { + System.out.println(ans1); + System.out.println(ans2); + System.out.println(ans3); + System.out.println("Oops!"); + } + } + System.out.println("测试结束"); + } + +} diff --git a/大厂刷题班/class02/Code05_SetAll.java b/大厂刷题班/class02/Code05_SetAll.java new file mode 100644 index 0000000..329964a --- /dev/null +++ b/大厂刷题班/class02/Code05_SetAll.java @@ -0,0 +1,48 @@ +package class02; + +import java.util.HashMap; + +public class Code05_SetAll { + + public static class MyValue { + public V value; + public long time; + + public MyValue(V v, long t) { + value = v; + time = t; + } + } + + public static class MyHashMap { + private HashMap> map; + private long time; + private MyValue setAll; + + public MyHashMap() { + map = new HashMap<>(); + time = 0; + setAll = new MyValue(null, -1); + } + + public void put(K key, V value) { + map.put(key, new MyValue(value, time++)); + } + + public void setAll(V value) { + setAll = new MyValue(value, time++); + } + + public V get(K key) { + if (!map.containsKey(key)) { + return null; + } + if (map.get(key).time > setAll.time) { + return map.get(key).value; + } else { + return setAll.value; + } + } + } + +} diff --git a/大厂刷题班/class02/Code06_MinLengthForSort.java b/大厂刷题班/class02/Code06_MinLengthForSort.java new file mode 100644 index 0000000..5c3f74b --- /dev/null +++ b/大厂刷题班/class02/Code06_MinLengthForSort.java @@ -0,0 +1,30 @@ +package class02; + +// 本题测试链接 : https://leetcode.com/problems/shortest-unsorted-continuous-subarray/ +public class Code06_MinLengthForSort { + + public static int findUnsortedSubarray(int[] nums) { + if (nums == null || nums.length < 2) { + return 0; + } + int N = nums.length; + int right = -1; + int max = Integer.MIN_VALUE; + for (int i = 0; i < N; i++) { + if (max > nums[i]) { + right = i; + } + max = Math.max(max, nums[i]); + } + int min = Integer.MAX_VALUE; + int left = N; + for (int i = N - 1; i >= 0; i--) { + if (min < nums[i]) { + left = i; + } + min = Math.min(min, nums[i]); + } + return Math.max(0, right - left + 1); + } + +} diff --git a/大厂刷题班/class03/Code01_LongestSubstringWithoutRepeatingCharacters.java b/大厂刷题班/class03/Code01_LongestSubstringWithoutRepeatingCharacters.java new file mode 100644 index 0000000..120a441 --- /dev/null +++ b/大厂刷题班/class03/Code01_LongestSubstringWithoutRepeatingCharacters.java @@ -0,0 +1,27 @@ +package class03; + +// 本题测试链接 : https://leetcode.com/problems/longest-substring-without-repeating-characters/ +public class Code01_LongestSubstringWithoutRepeatingCharacters { + + public static int lengthOfLongestSubstring(String s) { + if (s == null || s.equals("")) { + return 0; + } + char[] str = s.toCharArray(); + int[] map = new int[256]; + for (int i = 0; i < 256; i++) { + map[i] = -1; + } + map[str[0]] = 0; + int N = str.length; + int ans = 1; + int pre = 1; + for (int i = 1; i < N; i++) { + pre = Math.min(i - map[str[i]], pre + 1); + ans = Math.max(ans, pre); + map[str[i]] = i; + } + return ans; + } + +} diff --git a/大厂刷题班/class03/Code02_HowManyTypes.java b/大厂刷题班/class03/Code02_HowManyTypes.java new file mode 100644 index 0000000..9d40789 --- /dev/null +++ b/大厂刷题班/class03/Code02_HowManyTypes.java @@ -0,0 +1,82 @@ +package class03; + +import java.util.HashSet; + +public class Code02_HowManyTypes { + + /* + * 只由小写字母(a~z)组成的一批字符串,都放在字符类型的数组String[] arr中, + * 如果其中某两个字符串,所含有的字符种类完全一样,就将两个字符串算作一类 比如:baacba和bac就算作一类 + * 虽然长度不一样,但是所含字符的种类完全一样(a、b、c) 返回arr中有多少类? + * + */ + + public static int types1(String[] arr) { + HashSet types = new HashSet<>(); + for (String str : arr) { + char[] chs = str.toCharArray(); + boolean[] map = new boolean[26]; + for (int i = 0; i < chs.length; i++) { + map[chs[i] - 'a'] = true; + } + String key = ""; + for (int i = 0; i < 26; i++) { + if (map[i]) { + key += String.valueOf((char) (i + 'a')); + } + } + types.add(key); + } + return types.size(); + } + + public static int types2(String[] arr) { + HashSet types = new HashSet<>(); + for (String str : arr) { + char[] chs = str.toCharArray(); + int key = 0; + for(int i = 0 ; i < chs.length;i++) { + key |= (1 << (chs[i] - 'a')); + } + types.add(key); + } + return types.size(); + } + + // for test + public static String[] getRandomStringArray(int possibilities, int strMaxSize, int arrMaxSize) { + String[] ans = new String[(int) (Math.random() * arrMaxSize) + 1]; + for (int i = 0; i < ans.length; i++) { + ans[i] = getRandomString(possibilities, strMaxSize); + } + return ans; + } + + // for test + public static String getRandomString(int possibilities, int strMaxSize) { + char[] ans = new char[(int) (Math.random() * strMaxSize) + 1]; + for (int i = 0; i < ans.length; i++) { + ans[i] = (char) ((int) (Math.random() * possibilities) + 'a'); + } + return String.valueOf(ans); + } + + public static void main(String[] args) { + int possibilities = 5; + int strMaxSize = 10; + int arrMaxSize = 100; + int testTimes = 500000; + System.out.println("test begin, test time : " + testTimes); + for (int i = 0; i < testTimes; i++) { + String[] arr = getRandomStringArray(possibilities, strMaxSize, arrMaxSize); + int ans1 = types1(arr); + int ans2 = types2(arr); + if (ans1 != ans2) { + System.out.println("Oops!"); + } + } + System.out.println("test finish"); + + } + +} diff --git a/大厂刷题班/class03/Code03_Largest1BorderedSquare.java b/大厂刷题班/class03/Code03_Largest1BorderedSquare.java new file mode 100644 index 0000000..9d1f053 --- /dev/null +++ b/大厂刷题班/class03/Code03_Largest1BorderedSquare.java @@ -0,0 +1,59 @@ +package class03; + +// 本题测试链接 : https://leetcode.com/problems/largest-1-bordered-square/ +public class Code03_Largest1BorderedSquare { + + public static int largest1BorderedSquare(int[][] m) { + int[][] right = new int[m.length][m[0].length]; + int[][] down = new int[m.length][m[0].length]; + setBorderMap(m, right, down); + for (int size = Math.min(m.length, m[0].length); size != 0; size--) { + if (hasSizeOfBorder(size, right, down)) { + return size * size; + } + } + return 0; + } + + public static void setBorderMap(int[][] m, int[][] right, int[][] down) { + int r = m.length; + int c = m[0].length; + if (m[r - 1][c - 1] == 1) { + right[r - 1][c - 1] = 1; + down[r - 1][c - 1] = 1; + } + for (int i = r - 2; i != -1; i--) { + if (m[i][c - 1] == 1) { + right[i][c - 1] = 1; + down[i][c - 1] = down[i + 1][c - 1] + 1; + } + } + for (int i = c - 2; i != -1; i--) { + if (m[r - 1][i] == 1) { + right[r - 1][i] = right[r - 1][i + 1] + 1; + down[r - 1][i] = 1; + } + } + for (int i = r - 2; i != -1; i--) { + for (int j = c - 2; j != -1; j--) { + if (m[i][j] == 1) { + right[i][j] = right[i][j + 1] + 1; + down[i][j] = down[i + 1][j] + 1; + } + } + } + } + + public static boolean hasSizeOfBorder(int size, int[][] right, int[][] down) { + for (int i = 0; i != right.length - size + 1; i++) { + for (int j = 0; j != right[0].length - size + 1; j++) { + if (right[i][j] >= size && down[i][j] >= size && right[i + size - 1][j] >= size + && down[i][j + size - 1] >= size) { + return true; + } + } + } + return false; + } + +} diff --git a/大厂刷题班/class03/Code04_MaxPairNumber.java b/大厂刷题班/class03/Code04_MaxPairNumber.java new file mode 100644 index 0000000..a77909c --- /dev/null +++ b/大厂刷题班/class03/Code04_MaxPairNumber.java @@ -0,0 +1,126 @@ +package class03; + +import java.util.Arrays; + +// 给定一个数组arr,代表每个人的能力值。再给定一个非负数k。 +// 如果两个人能力差值正好为k,那么可以凑在一起比赛,一局比赛只有两个人 +// 返回最多可以同时有多少场比赛 +public class Code04_MaxPairNumber { + + // 暴力解 + public static int maxPairNum1(int[] arr, int k) { + if (k < 0) { + return -1; + } + return process1(arr, 0, k); + } + + public static int process1(int[] arr, int index, int k) { + int ans = 0; + if (index == arr.length) { + for (int i = 1; i < arr.length; i += 2) { + if (arr[i] - arr[i - 1] == k) { + ans++; + } + } + } else { + for (int r = index; r < arr.length; r++) { + swap(arr, index, r); + ans = Math.max(ans, process1(arr, index + 1, k)); + swap(arr, index, r); + } + } + return ans; + } + + public static void swap(int[] arr, int i, int j) { + int tmp = arr[i]; + arr[i] = arr[j]; + arr[j] = tmp; + } + + // 时间复杂度O(N*logN) + public static int maxPairNum2(int[] arr, int k) { + if (k < 0 || arr == null || arr.length < 2) { + return 0; + } + Arrays.sort(arr); + int ans = 0; + int N = arr.length; + int L = 0; + int R = 0; + boolean[] usedR = new boolean[N]; + while (L < N && R < N) { + if (usedR[L]) { + L++; + } else if (L >= R) { + R++; + } else { // 不止一个数,而且都没用过! + int distance = arr[R] - arr[L]; + if (distance == k) { + ans++; + usedR[R++] = true; + L++; + } else if (distance < k) { + R++; + } else { + L++; + } + } + } + return ans; + } + + // 为了测试 + public static int[] randomArray(int len, int value) { + int[] arr = new int[len]; + for (int i = 0; i < arr.length; i++) { + arr[i] = (int) (Math.random() * value); + } + return arr; + } + + // 为了测试 + public static void printArray(int[] arr) { + for (int i = 0; i < arr.length; i++) { + System.out.print(arr[i] + " "); + } + System.out.println(); + } + + // 为了测试 + public static int[] copyArray(int[] arr) { + int[] ans = new int[arr.length]; + for (int i = 0; i < arr.length; i++) { + ans[i] = arr[i]; + } + return ans; + } + + public static void main(String[] args) { + int maxLen = 10; + int maxValue = 20; + int maxK = 5; + int testTime = 1000; + System.out.println("功能测试开始"); + for (int i = 0; i < testTime; i++) { + int N = (int) (Math.random() * (maxLen + 1)); + int[] arr = randomArray(N, maxValue); + int[] arr1 = copyArray(arr); + int[] arr2 = copyArray(arr); + int k = (int) (Math.random() * (maxK + 1)); + int ans1 = maxPairNum1(arr1, k); + int ans2 = maxPairNum2(arr2, k); + if (ans1 != ans2) { + System.out.println("Oops!"); + printArray(arr); + System.out.println(k); + System.out.println(ans1); + System.out.println(ans2); + break; + } + } + System.out.println("功能测试结束"); + } + +} diff --git a/大厂刷题班/class03/Code05_BoatsToSavePeople.java b/大厂刷题班/class03/Code05_BoatsToSavePeople.java new file mode 100644 index 0000000..d5cc68f --- /dev/null +++ b/大厂刷题班/class03/Code05_BoatsToSavePeople.java @@ -0,0 +1,74 @@ +package class03; + +import java.util.Arrays; + +// 给定一个正数数组arr,代表若干人的体重 +// 再给定一个正数limit,表示所有船共同拥有的载重量 +// 每艘船最多坐两人,且不能超过载重 +// 想让所有的人同时过河,并且用最好的分配方法让船尽量少 +// 返回最少的船数 +// 测试链接 : https://leetcode.com/problems/boats-to-save-people/ +public class Code05_BoatsToSavePeople { + + public static int numRescueBoats1(int[] arr, int limit) { + if (arr == null || arr.length == 0) { + return 0; + } + int N = arr.length; + Arrays.sort(arr); + if (arr[N - 1] > limit) { + return -1; + } + int lessR = -1; + for (int i = N - 1; i >= 0; i--) { + if (arr[i] <= (limit / 2)) { + lessR = i; + break; + } + } + if (lessR == -1) { + return N; + } + int L = lessR; + int R = lessR + 1; + int noUsed = 0; + while (L >= 0) { + int solved = 0; + while (R < N && arr[L] + arr[R] <= limit) { + R++; + solved++; + } + if (solved == 0) { + noUsed++; + L--; + } else { + L = Math.max(-1, L - solved); + } + } + int all = lessR + 1; + int used = all - noUsed; + int moreUnsolved = (N - all) - used; + return used + ((noUsed + 1) >> 1) + moreUnsolved; + } + + // 首尾双指针的解法 + public static int numRescueBoats2(int[] people, int limit) { + Arrays.sort(people); + int ans = 0; + int l = 0; + int r = people.length - 1; + int sum = 0; + while (l <= r) { + sum = l == r ? people[l] : people[l] + people[r]; + if (sum > limit) { + r--; + } else { + l++; + r--; + } + ans++; + } + return ans; + } + +} diff --git a/大厂刷题班/class03/Code06_ClosestSubsequenceSum.java b/大厂刷题班/class03/Code06_ClosestSubsequenceSum.java new file mode 100644 index 0000000..1cb2c0b --- /dev/null +++ b/大厂刷题班/class03/Code06_ClosestSubsequenceSum.java @@ -0,0 +1,46 @@ +package class03; + +import java.util.Arrays; + +// 本题测试链接 : https://leetcode.com/problems/closest-subsequence-sum/ +// 本题数据量描述: +// 1 <= nums.length <= 40 +// -10^7 <= nums[i] <= 10^7 +// -10^9 <= goal <= 10^9 +// 通过这个数据量描述可知,需要用到分治,因为数组长度不大 +// 而值很大,用动态规划的话,表会爆 +public class Code06_ClosestSubsequenceSum { + + public static int[] l = new int[1 << 20]; + public static int[] r = new int[1 << 20]; + + public static int minAbsDifference(int[] nums, int goal) { + if (nums == null || nums.length == 0) { + return goal; + } + int le = process(nums, 0, nums.length >> 1, 0, 0, l); + int re = process(nums, nums.length >> 1, nums.length, 0, 0, r); + Arrays.sort(l, 0, le); + Arrays.sort(r, 0, re--); + int ans = Math.abs(goal); + for (int i = 0; i < le; i++) { + int rest = goal - l[i]; + while (re > 0 && Math.abs(rest - r[re - 1]) <= Math.abs(rest - r[re])) { + re--; + } + ans = Math.min(ans, Math.abs(rest - r[re])); + } + return ans; + } + + public static int process(int[] nums, int index, int end, int sum, int fill, int[] arr) { + if (index == end) { + arr[fill++] = sum; + } else { + fill = process(nums, index + 1, end, sum, fill, arr); + fill = process(nums, index + 1, end, sum + nums[index], fill, arr); + } + return fill; + } + +} diff --git a/大厂刷题班/class03/Code07_FreedomTrail.java b/大厂刷题班/class03/Code07_FreedomTrail.java new file mode 100644 index 0000000..a3ef36d --- /dev/null +++ b/大厂刷题班/class03/Code07_FreedomTrail.java @@ -0,0 +1,62 @@ +package class03; + +import java.util.ArrayList; +import java.util.HashMap; + +// 本题测试链接 : https://leetcode.com/problems/freedom-trail/ +public class Code07_FreedomTrail { + + public static int findRotateSteps(String r, String k) { + char[] ring = r.toCharArray(); + int N = ring.length; + HashMap> map = new HashMap<>(); + for (int i = 0; i < N; i++) { + if (!map.containsKey(ring[i])) { + map.put(ring[i], new ArrayList<>()); + } + map.get(ring[i]).add(i); + } + char[] str = k.toCharArray(); + int M = str.length; + int[][] dp = new int[N][M + 1]; + // hashmap + // dp[][] == -1 : 表示没算过! + for (int i = 0; i < N; i++) { + for (int j = 0; j <= M; j++) { + dp[i][j] = -1; + } + } + return process(0, 0, str, map, N, dp); + } + + // 电话里:指针指着的上一个按键preButton + // 目标里:此时要搞定哪个字符?keyIndex + // map : key 一种字符 value: 哪些位置拥有这个字符 + // N: 电话大小 + // f(0, 0, aim, map, N) + public static int process(int preButton, int index, char[] str, HashMap> map, int N, + int[][] dp) { + if (dp[preButton][index] != -1) { + return dp[preButton][index]; + } + int ans = Integer.MAX_VALUE; + if (index == str.length) { + ans = 0; + } else { + // 还有字符需要搞定呢! + char cur = str[index]; + ArrayList nextPositions = map.get(cur); + for (int next : nextPositions) { + int cost = dial(preButton, next, N) + 1 + process(next, index + 1, str, map, N, dp); + ans = Math.min(ans, cost); + } + } + dp[preButton][index] = ans; + return ans; + } + + public static int dial(int i1, int i2, int size) { + return Math.min(Math.abs(i1 - i2), Math.min(i1, i2) + size - Math.max(i1, i2)); + } + +} diff --git a/大厂刷题班/class03/Code08_DistanceKNodes.java b/大厂刷题班/class03/Code08_DistanceKNodes.java new file mode 100644 index 0000000..30de2e8 --- /dev/null +++ b/大厂刷题班/class03/Code08_DistanceKNodes.java @@ -0,0 +1,105 @@ +package class03; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; + +public class Code08_DistanceKNodes { + + public static class Node { + public int value; + public Node left; + public Node right; + + public Node(int v) { + value = v; + } + } + + public static List distanceKNodes(Node root, Node target, int K) { + HashMap parents = new HashMap<>(); + parents.put(root, null); + createParentMap(root, parents); + Queue queue = new LinkedList<>(); + HashSet visited = new HashSet<>(); + queue.offer(target); + visited.add(target); + int curLevel = 0; + List ans = new ArrayList<>(); + while (!queue.isEmpty()) { + int size = queue.size(); + while (size-- > 0) { + Node cur = queue.poll(); + if (curLevel == K) { + ans.add(cur); + } + if (cur.left != null && !visited.contains(cur.left)) { + visited.add(cur.left); + queue.offer(cur.left); + } + if (cur.right != null && !visited.contains(cur.right)) { + visited.add(cur.right); + queue.offer(cur.right); + } + if (parents.get(cur) != null && !visited.contains(parents.get(cur))) { + visited.add(parents.get(cur)); + queue.offer(parents.get(cur)); + } + } + curLevel++; + if (curLevel > K) { + break; + } + } + return ans; + } + + public static void createParentMap(Node cur, HashMap parents) { + if (cur == null) { + return; + } + if (cur.left != null) { + parents.put(cur.left, cur); + createParentMap(cur.left, parents); + } + if (cur.right != null) { + parents.put(cur.right, cur); + createParentMap(cur.right, parents); + } + } + + public static void main(String[] args) { + Node n0 = new Node(0); + Node n1 = new Node(1); + Node n2 = new Node(2); + Node n3 = new Node(3); + Node n4 = new Node(4); + Node n5 = new Node(5); + Node n6 = new Node(6); + Node n7 = new Node(7); + Node n8 = new Node(8); + + n3.left = n5; + n3.right = n1; + n5.left = n6; + n5.right = n2; + n1.left = n0; + n1.right = n8; + n2.left = n7; + n2.right = n4; + + Node root = n3; + Node target = n5; + int K = 2; + + List ans = distanceKNodes(root, target, K); + for (Node o1 : ans) { + System.out.println(o1.value); + } + + } + +} diff --git a/大厂刷题班/class04/Code01_QueryHobby.java b/大厂刷题班/class04/Code01_QueryHobby.java new file mode 100644 index 0000000..20babf4 --- /dev/null +++ b/大厂刷题班/class04/Code01_QueryHobby.java @@ -0,0 +1,115 @@ +package class04; + +import java.util.ArrayList; +import java.util.HashMap; + +public class Code01_QueryHobby { + + /* + * 今日头条原题 + * + * 数组为{3, 2, 2, 3, 1},查询为(0, 3, 2)。意思是在数组里下标0~3这个范围上,有几个2?返回2。 + * 假设给你一个数组arr,对这个数组的查询非常频繁,请返回所有查询的结果 + * + */ + + public static class QueryBox1 { + private int[] arr; + + public QueryBox1(int[] array) { + arr = new int[array.length]; + for (int i = 0; i < arr.length; i++) { + arr[i] = array[i]; + } + } + + public int query(int L, int R, int v) { + int ans = 0; + for (; L <= R; L++) { + if (arr[L] == v) { + ans++; + } + } + return ans; + } + } + + public static class QueryBox2 { + private HashMap> map; + + public QueryBox2(int[] arr) { + map = new HashMap<>(); + for (int i = 0; i < arr.length; i++) { + if (!map.containsKey(arr[i])) { + map.put(arr[i], new ArrayList<>()); + } + map.get(arr[i]).add(i); + } + } + + public int query(int L, int R, int value) { + if (!map.containsKey(value)) { + return 0; + } + ArrayList indexArr = map.get(value); + // 查询 < L 的下标有几个 + int a = countLess(indexArr, L); + // 查询 < R+1 的下标有几个 + int b = countLess(indexArr, R + 1); + return b - a; + } + + // 在有序数组arr中,用二分的方法数出 arr, int limit) { + int L = 0; + int R = arr.size() - 1; + int mostRight = -1; + while (L <= R) { + int mid = L + ((R - L) >> 1); + if (arr.get(mid) < limit) { + mostRight = mid; + L = mid + 1; + } else { + R = mid - 1; + } + } + return mostRight + 1; + } + + } + + public static int[] generateRandomArray(int len, int value) { + int[] ans = new int[(int) (Math.random() * len) + 1]; + for (int i = 0; i < ans.length; i++) { + ans[i] = (int) (Math.random() * value) + 1; + } + return ans; + } + + public static void main(String[] args) { + int len = 300; + int value = 20; + int testTimes = 1000; + int queryTimes = 1000; + System.out.println("test begin"); + for (int i = 0; i < testTimes; i++) { + int[] arr = generateRandomArray(len, value); + int N = arr.length; + QueryBox1 box1 = new QueryBox1(arr); + QueryBox2 box2 = new QueryBox2(arr); + for (int j = 0; j < queryTimes; j++) { + int a = (int) (Math.random() * N); + int b = (int) (Math.random() * N); + int L = Math.min(a, b); + int R = Math.max(a, b); + int v = (int) (Math.random() * value) + 1; + if (box1.query(L, R, v) != box2.query(L, R, v)) { + System.out.println("Oops!"); + } + } + } + System.out.println("test end"); + } + +} diff --git a/大厂刷题班/class04/Code02_SubArrayMaxSum.java b/大厂刷题班/class04/Code02_SubArrayMaxSum.java new file mode 100644 index 0000000..25477fa --- /dev/null +++ b/大厂刷题班/class04/Code02_SubArrayMaxSum.java @@ -0,0 +1,35 @@ +package class04; + +// 本题测试链接 : https://leetcode.com/problems/maximum-subarray/ +public class Code02_SubArrayMaxSum { + + public static int maxSubArray(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + int max = Integer.MIN_VALUE; + int cur = 0; + for (int i = 0; i < arr.length; i++) { + cur += arr[i]; + max = Math.max(max, cur); + cur = cur < 0 ? 0 : cur; + } + return max; + } + + public static int maxSubArray2(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + // 上一步,dp的值 + // dp[0] + int pre = arr[0]; + int max = arr[0]; + for (int i = 1; i < arr.length; i++) { + pre = Math.max(arr[i], arr[i] + pre); + max = Math.max(max, pre); + } + return max; + } + +} diff --git a/大厂刷题班/class04/Code03_SubMatrixMaxSum.java b/大厂刷题班/class04/Code03_SubMatrixMaxSum.java new file mode 100644 index 0000000..2940774 --- /dev/null +++ b/大厂刷题班/class04/Code03_SubMatrixMaxSum.java @@ -0,0 +1,75 @@ +package class04; + +public class Code03_SubMatrixMaxSum { + + public static int maxSum(int[][] m) { + if (m == null || m.length == 0 || m[0].length == 0) { + return 0; + } + // O(N^2 * M) + int N = m.length; + int M = m[0].length; + int max = Integer.MIN_VALUE; + for (int i = 0; i < N; i++) { + // i~j + int[] s = new int[M]; + for (int j = i; j < N; j++) { + for (int k = 0; k < M; k++) { + s[k] += m[j][k]; + } + max = Math.max(max, maxSubArray(s)); + } + } + return max; + } + + public static int maxSubArray(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + int max = Integer.MIN_VALUE; + int cur = 0; + for (int i = 0; i < arr.length; i++) { + cur += arr[i]; + max = Math.max(max, cur); + cur = cur < 0 ? 0 : cur; + } + return max; + } + + // 本题测试链接 : https://leetcode-cn.com/problems/max-submatrix-lcci/ + public static int[] getMaxMatrix(int[][] m) { + int N = m.length; + int M = m[0].length; + int max = Integer.MIN_VALUE; + int cur = 0; + int a = 0; + int b = 0; + int c = 0; + int d = 0; + for (int i = 0; i < N; i++) { + int[] s = new int[M]; + for (int j = i; j < N; j++) { + cur = 0; + int begin = 0; + for (int k = 0; k < M; k++) { + s[k] += m[j][k]; + cur += s[k]; + if (max < cur) { + max = cur; + a = i; + b = begin; + c = j; + d = k; + } + if (cur < 0) { + cur = 0; + begin = k + 1; + } + } + } + } + return new int[] { a, b, c, d }; + } + +} diff --git a/大厂刷题班/class04/Code04_SubArrayMaxSumFollowUp.java b/大厂刷题班/class04/Code04_SubArrayMaxSumFollowUp.java new file mode 100644 index 0000000..0d5cccd --- /dev/null +++ b/大厂刷题班/class04/Code04_SubArrayMaxSumFollowUp.java @@ -0,0 +1,59 @@ +package class04; + +// 在线测试链接 : https://leetcode.com/problems/house-robber/ +public class Code04_SubArrayMaxSumFollowUp { + + public static int rob1(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + if (arr.length == 1) { + return arr[0]; + } + int[] dp = new int[arr.length]; + // dp[i] : arr[0..i]挑选,满足不相邻设定的情况下,随意挑选,最大的累加和 + dp[0] = arr[0]; + dp[1] = Math.max(arr[0], arr[1]); + for (int i = 2; i < arr.length; i++) { + int p1 = dp[i - 1]; + int p2 = arr[i] + Math.max(dp[i - 2], 0); + dp[i] = Math.max(p1, p2); + } + return dp[arr.length - 1]; + } + + // 给定一个数组arr,在不能取相邻数的情况下,返回所有组合中的最大累加和 + // 思路: + // 定义dp[i] : 表示arr[0...i]范围上,在不能取相邻数的情况下,返回所有组合中的最大累加和 + // 在arr[0...i]范围上,在不能取相邻数的情况下,得到的最大累加和,可能性分类: + // 可能性 1) 选出的组合,不包含arr[i]。那么dp[i] = dp[i-1] + // 比如,arr[0...i] = {3,4,-4},最大累加和是不包含i位置数的时候 + // + // 可能性 2) 选出的组合,只包含arr[i]。那么dp[i] = arr[i] + // 比如,arr[0...i] = {-3,-4,4},最大累加和是只包含i位置数的时候 + // + // 可能性 3) 选出的组合,包含arr[i], 且包含arr[0...i-2]范围上的累加和。那么dp[i] = arr[i] + dp[i-2] + // 比如,arr[0...i] = {3,1,4},最大累加和是3和4组成的7,因为相邻不能选,所以i-1位置的数要跳过 + // + // 综上所述:dp[i] = Max { dp[i-1], arr[i] , arr[i] + dp[i-2] } + public static int rob2(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + int N = arr.length; + if (N == 1) { + return arr[0]; + } + if (N == 2) { + return Math.max(arr[0], arr[1]); + } + int[] dp = new int[N]; + dp[0] = arr[0]; + dp[1] = Math.max(arr[0], arr[1]); + for (int i = 2; i < N; i++) { + dp[i] = Math.max(Math.max(dp[i - 1], arr[i]), arr[i] + dp[i - 2]); + } + return dp[N - 1]; + } + +} diff --git a/大厂刷题班/class04/Code05_CandyProblem.java b/大厂刷题班/class04/Code05_CandyProblem.java new file mode 100644 index 0000000..694aab2 --- /dev/null +++ b/大厂刷题班/class04/Code05_CandyProblem.java @@ -0,0 +1,145 @@ +package class04; + +// 测试链接 : https://leetcode.com/problems/candy/ +public class Code05_CandyProblem { + + // 这是原问题的优良解 + // 时间复杂度O(N),额外空间复杂度O(N) + public static int candy1(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + int N = arr.length; + int[] left = new int[N]; + for (int i = 1; i < N; i++) { + if (arr[i - 1] < arr[i]) { + left[i] = left[i - 1] + 1; + } + } + int[] right = new int[N]; + for (int i = N - 2; i >= 0; i--) { + if (arr[i] > arr[i + 1]) { + right[i] = right[i + 1] + 1; + } + } + int ans = 0; + for (int i = 0; i < N; i++) { + ans += Math.max(left[i], right[i]); + } + return ans + N; + } + + // 这是原问题空间优化后的解 + // 时间复杂度O(N),额外空间复杂度O(1) + public static int candy2(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + int index = nextMinIndex2(arr, 0); + int res = rightCands(arr, 0, index++); + int lbase = 1; + int next = 0; + int rcands = 0; + int rbase = 0; + while (index != arr.length) { + if (arr[index] > arr[index - 1]) { + res += ++lbase; + index++; + } else if (arr[index] < arr[index - 1]) { + next = nextMinIndex2(arr, index - 1); + rcands = rightCands(arr, index - 1, next++); + rbase = next - index + 1; + res += rcands + (rbase > lbase ? -lbase : -rbase); + lbase = 1; + index = next; + } else { + res += 1; + lbase = 1; + index++; + } + } + return res; + } + + public static int nextMinIndex2(int[] arr, int start) { + for (int i = start; i != arr.length - 1; i++) { + if (arr[i] <= arr[i + 1]) { + return i; + } + } + return arr.length - 1; + } + + public static int rightCands(int[] arr, int left, int right) { + int n = right - left + 1; + return n + n * (n - 1) / 2; + } + + // 这是进阶问题的最优解,不要提交这个 + // 时间复杂度O(N), 额外空间复杂度O(1) + public static int candy3(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + int index = nextMinIndex3(arr, 0); + int[] data = rightCandsAndBase(arr, 0, index++); + int res = data[0]; + int lbase = 1; + int same = 1; + int next = 0; + while (index != arr.length) { + if (arr[index] > arr[index - 1]) { + res += ++lbase; + same = 1; + index++; + } else if (arr[index] < arr[index - 1]) { + next = nextMinIndex3(arr, index - 1); + data = rightCandsAndBase(arr, index - 1, next++); + if (data[1] <= lbase) { + res += data[0] - data[1]; + } else { + res += -lbase * same + data[0] - data[1] + data[1] * same; + } + index = next; + lbase = 1; + same = 1; + } else { + res += lbase; + same++; + index++; + } + } + return res; + } + + public static int nextMinIndex3(int[] arr, int start) { + for (int i = start; i != arr.length - 1; i++) { + if (arr[i] < arr[i + 1]) { + return i; + } + } + return arr.length - 1; + } + + public static int[] rightCandsAndBase(int[] arr, int left, int right) { + int base = 1; + int cands = 1; + for (int i = right - 1; i >= left; i--) { + if (arr[i] == arr[i + 1]) { + cands += base; + } else { + cands += ++base; + } + } + return new int[] { cands, base }; + } + + public static void main(String[] args) { + int[] test1 = { 3, 0, 5, 5, 4, 4, 0 }; + System.out.println(candy2(test1)); + + int[] test2 = { 3, 0, 5, 5, 4, 4, 0 }; + System.out.println(candy3(test2)); + } + +} diff --git a/大厂刷题班/class04/Code06_MakeNo.java b/大厂刷题班/class04/Code06_MakeNo.java new file mode 100644 index 0000000..2a4af46 --- /dev/null +++ b/大厂刷题班/class04/Code06_MakeNo.java @@ -0,0 +1,59 @@ +package class04; + +public class Code06_MakeNo { + + // 生成长度为size的达标数组 + // 达标:对于任意的 i 等长奇数达标来 + // base -> 等长偶数达标来 + int[] ans = new int[size]; + int index = 0; + for (; index < halfSize; index++) { + ans[index] = base[index] * 2 - 1; + } + for (int i = 0; index < size; index++, i++) { + ans[index] = base[i] * 2; + } + return ans; + } + + // 检验函数 + public static boolean isValid(int[] arr) { + int N = arr.length; + for (int i = 0; i < N; i++) { + for (int k = i + 1; k < N; k++) { + for (int j = k + 1; j < N; j++) { + if (arr[i] + arr[j] == 2 * arr[k]) { + return false; + } + } + } + } + return true; + } + + public static void main(String[] args) { + System.out.println("test begin"); + for (int N = 1; N < 1000; N++) { + int[] arr = makeNo(N); + if (!isValid(arr)) { + System.out.println("Oops!"); + } + } + System.out.println("test end"); + System.out.println(isValid(makeNo(1042))); + System.out.println(isValid(makeNo(2981))); + } + +} diff --git a/大厂刷题班/class04/Code07_InterleavingString.java b/大厂刷题班/class04/Code07_InterleavingString.java new file mode 100644 index 0000000..f53e113 --- /dev/null +++ b/大厂刷题班/class04/Code07_InterleavingString.java @@ -0,0 +1,53 @@ +package class04; + +// 本题测试链接 : https://leetcode.com/problems/interleaving-string/ +public class Code07_InterleavingString { + + public static boolean isInterleave(String s1, String s2, String s3) { + if (s1 == null || s2 == null || s3 == null) { + return false; + } + char[] str1 = s1.toCharArray(); + char[] str2 = s2.toCharArray(); + char[] str3 = s3.toCharArray(); + if (str3.length != str1.length + str2.length) { + return false; + } + boolean[][] dp = new boolean[str1.length + 1][str2.length + 1]; + dp[0][0] = true; + for (int i = 1; i <= str1.length; i++) { + if (str1[i - 1] != str3[i - 1]) { + break; + } + dp[i][0] = true; + } + for (int j = 1; j <= str2.length; j++) { + if (str2[j - 1] != str3[j - 1]) { + break; + } + dp[0][j] = true; + } + for (int i = 1; i <= str1.length; i++) { + for (int j = 1; j <= str2.length; j++) { + if ( + (str1[i - 1] == str3[i + j - 1] && dp[i - 1][j]) + + + + + || + + + + (str2[j - 1] == str3[i + j - 1] && dp[i][j - 1]) + + + ) { + dp[i][j] = true; + } + } + } + return dp[str1.length][str2.length]; + } + +} diff --git a/大厂刷题班/class04/Code08_TheSkylineProblem.java b/大厂刷题班/class04/Code08_TheSkylineProblem.java new file mode 100644 index 0000000..29f3ccd --- /dev/null +++ b/大厂刷题班/class04/Code08_TheSkylineProblem.java @@ -0,0 +1,73 @@ +package class04; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import java.util.Map.Entry; +import java.util.TreeMap; + +// 本题测试链接 : https://leetcode.com/problems/the-skyline-problem/ +public class Code08_TheSkylineProblem { + + public static class Node { + public int x; + public boolean isAdd; + public int h; + + public Node(int x, boolean isAdd, int h) { + this.x = x; + this.isAdd = isAdd; + this.h = h; + } + } + + public static class NodeComparator implements Comparator { + @Override + public int compare(Node o1, Node o2) { + return o1.x - o2.x; + } + } + + public static List> getSkyline(int[][] matrix) { + Node[] nodes = new Node[matrix.length * 2]; + for (int i = 0; i < matrix.length; i++) { + nodes[i * 2] = new Node(matrix[i][0], true, matrix[i][2]); + nodes[i * 2 + 1] = new Node(matrix[i][1], false, matrix[i][2]); + } + Arrays.sort(nodes, new NodeComparator()); + // key 高度 value 次数 + TreeMap mapHeightTimes = new TreeMap<>(); + TreeMap mapXHeight = new TreeMap<>(); + for (int i = 0; i < nodes.length; i++) { + if (nodes[i].isAdd) { + if (!mapHeightTimes.containsKey(nodes[i].h)) { + mapHeightTimes.put(nodes[i].h, 1); + } else { + mapHeightTimes.put(nodes[i].h, mapHeightTimes.get(nodes[i].h) + 1); + } + } else { + if (mapHeightTimes.get(nodes[i].h) == 1) { + mapHeightTimes.remove(nodes[i].h); + } else { + mapHeightTimes.put(nodes[i].h, mapHeightTimes.get(nodes[i].h) - 1); + } + } + if (mapHeightTimes.isEmpty()) { + mapXHeight.put(nodes[i].x, 0); + } else { + mapXHeight.put(nodes[i].x, mapHeightTimes.lastKey()); + } + } + List> ans = new ArrayList<>(); + for (Entry entry : mapXHeight.entrySet()) { + int curX = entry.getKey(); + int curMaxHeight = entry.getValue(); + if (ans.isEmpty() || ans.get(ans.size() - 1).get(1) != curMaxHeight) { + ans.add(new ArrayList<>(Arrays.asList(curX, curMaxHeight))); + } + } + return ans; + } + +} diff --git a/大厂刷题班/class05/Code01_ConstructBinarySearchTreeFromPreorderTraversal.java b/大厂刷题班/class05/Code01_ConstructBinarySearchTreeFromPreorderTraversal.java new file mode 100644 index 0000000..3eaf865 --- /dev/null +++ b/大厂刷题班/class05/Code01_ConstructBinarySearchTreeFromPreorderTraversal.java @@ -0,0 +1,115 @@ +package class05; + +import java.util.Stack; + +// 本题测试链接 : https://leetcode.com/problems/construct-binary-search-tree-from-preorder-traversal/ +public class Code01_ConstructBinarySearchTreeFromPreorderTraversal { + + // 不用提交这个类 + public static class TreeNode { + public int val; + public TreeNode left; + public TreeNode right; + + public TreeNode() { + } + + public TreeNode(int val) { + this.val = val; + } + + public TreeNode(int val, TreeNode left, TreeNode right) { + this.val = val; + this.left = left; + this.right = right; + } + } + + // 提交下面的方法 + public static TreeNode bstFromPreorder1(int[] pre) { + if (pre == null || pre.length == 0) { + return null; + } + return process1(pre, 0, pre.length - 1); + } + + public static TreeNode process1(int[] pre, int L, int R) { + if (L > R) { + return null; + } + int firstBig = L + 1; + for (; firstBig <= R; firstBig++) { + if (pre[firstBig] > pre[L]) { + break; + } + } + TreeNode head = new TreeNode(pre[L]); + head.left = process1(pre, L + 1, firstBig - 1); + head.right = process1(pre, firstBig, R); + return head; + } + + // 已经是时间复杂度最优的方法了,但是常数项还能优化 + public static TreeNode bstFromPreorder2(int[] pre) { + if (pre == null || pre.length == 0) { + return null; + } + int N = pre.length; + int[] nearBig = new int[N]; + for (int i = 0; i < N; i++) { + nearBig[i] = -1; + } + Stack stack = new Stack<>(); + for (int i = 0; i < N; i++) { + while (!stack.isEmpty() && pre[stack.peek()] < pre[i]) { + nearBig[stack.pop()] = i; + } + stack.push(i); + } + return process2(pre, 0, N - 1, nearBig); + } + + public static TreeNode process2(int[] pre, int L, int R, int[] nearBig) { + if (L > R) { + return null; + } + int firstBig = (nearBig[L] == -1 || nearBig[L] > R) ? R + 1 : nearBig[L]; + TreeNode head = new TreeNode(pre[L]); + head.left = process2(pre, L + 1, firstBig - 1, nearBig); + head.right = process2(pre, firstBig, R, nearBig); + return head; + } + + // 最优解 + public static TreeNode bstFromPreorder3(int[] pre) { + if (pre == null || pre.length == 0) { + return null; + } + int N = pre.length; + int[] nearBig = new int[N]; + for (int i = 0; i < N; i++) { + nearBig[i] = -1; + } + int[] stack = new int[N]; + int size = 0; + for (int i = 0; i < N; i++) { + while (size != 0 && pre[stack[size - 1]] < pre[i]) { + nearBig[stack[--size]] = i; + } + stack[size++] = i; + } + return process3(pre, 0, N - 1, nearBig); + } + + public static TreeNode process3(int[] pre, int L, int R, int[] nearBig) { + if (L > R) { + return null; + } + int firstBig = (nearBig[L] == -1 || nearBig[L] > R) ? R + 1 : nearBig[L]; + TreeNode head = new TreeNode(pre[L]); + head.left = process3(pre, L + 1, firstBig - 1, nearBig); + head.right = process3(pre, firstBig, R, nearBig); + return head; + } + +} diff --git a/大厂刷题班/class05/Code02_LeftRightSameTreeNumber.java b/大厂刷题班/class05/Code02_LeftRightSameTreeNumber.java new file mode 100644 index 0000000..7b53125 --- /dev/null +++ b/大厂刷题班/class05/Code02_LeftRightSameTreeNumber.java @@ -0,0 +1,95 @@ +package class05; + +// 如果一个节点X,它左树结构和右树结构完全一样,那么我们说以X为头的子树是相等子树 +// 给定一棵二叉树的头节点head,返回head整棵树上有多少棵相等子树 +public class Code02_LeftRightSameTreeNumber { + + public static class Node { + public int value; + public Node left; + public Node right; + + public Node(int v) { + value = v; + } + } + + // 时间复杂度O(N * logN) + public static int sameNumber1(Node head) { + if (head == null) { + return 0; + } + return sameNumber1(head.left) + sameNumber1(head.right) + (same(head.left, head.right) ? 1 : 0); + } + + public static boolean same(Node h1, Node h2) { + if (h1 == null ^ h2 == null) { + return false; + } + if (h1 == null && h2 == null) { + return true; + } + // 两个都不为空 + return h1.value == h2.value && same(h1.left, h2.left) && same(h1.right, h2.right); + } + + // 时间复杂度O(N) + public static int sameNumber2(Node head) { + String algorithm = "SHA-256"; + Hash hash = new Hash(algorithm); + return process(head, hash).ans; + } + + public static class Info { + public int ans; + public String str; + + public Info(int a, String s) { + ans = a; + str = s; + } + } + + public static Info process(Node head, Hash hash) { + if (head == null) { + return new Info(0, hash.hashCode("#,")); + } + Info l = process(head.left, hash); + Info r = process(head.right, hash); + int ans = (l.str.equals(r.str) ? 1 : 0) + l.ans + r.ans; + String str = hash.hashCode(String.valueOf(head.value) + "," + l.str + r.str); + return new Info(ans, str); + } + + public static Node randomBinaryTree(int restLevel, int maxValue) { + if (restLevel == 0) { + return null; + } + Node head = Math.random() < 0.2 ? null : new Node((int) (Math.random() * maxValue)); + if (head != null) { + head.left = randomBinaryTree(restLevel - 1, maxValue); + head.right = randomBinaryTree(restLevel - 1, maxValue); + } + return head; + } + + public static void main(String[] args) { + int maxLevel = 8; + int maxValue = 4; + int testTime = 100000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + Node head = randomBinaryTree(maxLevel, maxValue); + int ans1 = sameNumber1(head); + int ans2 = sameNumber2(head); + if (ans1 != ans2) { + System.out.println("出错了!"); + System.out.println(ans1); + System.out.println(ans2); + } + } + System.out.println("测试结束"); + + } + +} diff --git a/大厂刷题班/class05/Code03_EditCost.java b/大厂刷题班/class05/Code03_EditCost.java new file mode 100644 index 0000000..0079c51 --- /dev/null +++ b/大厂刷题班/class05/Code03_EditCost.java @@ -0,0 +1,89 @@ +package class05; + +public class Code03_EditCost { + + public static int minCost1(String s1, String s2, int ic, int dc, int rc) { + if (s1 == null || s2 == null) { + return 0; + } + char[] str1 = s1.toCharArray(); + char[] str2 = s2.toCharArray(); + int N = str1.length + 1; + int M = str2.length + 1; + int[][] dp = new int[N][M]; + // dp[0][0] = 0 + for (int i = 1; i < N; i++) { + dp[i][0] = dc * i; + } + for (int j = 1; j < M; j++) { + dp[0][j] = ic * j; + } + for (int i = 1; i < N; i++) { + for (int j = 1; j < M; j++) { + dp[i][j] = dp[i - 1][j - 1] + (str1[i - 1] == str2[j - 1] ? 0 : rc); + dp[i][j] = Math.min(dp[i][j], dp[i][j - 1] + ic); + dp[i][j] = Math.min(dp[i][j], dp[i - 1][j] + dc); + } + } + return dp[N - 1][M - 1]; + } + + public static int minCost2(String str1, String str2, int ic, int dc, int rc) { + if (str1 == null || str2 == null) { + return 0; + } + char[] chs1 = str1.toCharArray(); + char[] chs2 = str2.toCharArray(); + char[] longs = chs1.length >= chs2.length ? chs1 : chs2; + char[] shorts = chs1.length < chs2.length ? chs1 : chs2; + if (chs1.length < chs2.length) { + int tmp = ic; + ic = dc; + dc = tmp; + } + int[] dp = new int[shorts.length + 1]; + for (int i = 1; i <= shorts.length; i++) { + dp[i] = ic * i; + } + for (int i = 1; i <= longs.length; i++) { + int pre = dp[0]; + dp[0] = dc * i; + for (int j = 1; j <= shorts.length; j++) { + int tmp = dp[j]; + if (longs[i - 1] == shorts[j - 1]) { + dp[j] = pre; + } else { + dp[j] = pre + rc; + } + dp[j] = Math.min(dp[j], dp[j - 1] + ic); + dp[j] = Math.min(dp[j], tmp + dc); + pre = tmp; + } + } + return dp[shorts.length]; + } + + public static void main(String[] args) { + String str1 = "ab12cd3"; + String str2 = "abcdf"; + System.out.println(minCost1(str1, str2, 5, 3, 2)); + System.out.println(minCost2(str1, str2, 5, 3, 2)); + + str1 = "abcdf"; + str2 = "ab12cd3"; + System.out.println(minCost1(str1, str2, 3, 2, 4)); + System.out.println(minCost2(str1, str2, 3, 2, 4)); + + str1 = ""; + str2 = "ab12cd3"; + System.out.println(minCost1(str1, str2, 1, 7, 5)); + System.out.println(minCost2(str1, str2, 1, 7, 5)); + + str1 = "abcdf"; + str2 = ""; + System.out.println(minCost1(str1, str2, 2, 9, 8)); + System.out.println(minCost2(str1, str2, 2, 9, 8)); + + } + +} diff --git a/大厂刷题班/class05/Code04_DeleteMinCost.java b/大厂刷题班/class05/Code04_DeleteMinCost.java new file mode 100644 index 0000000..7aebee3 --- /dev/null +++ b/大厂刷题班/class05/Code04_DeleteMinCost.java @@ -0,0 +1,258 @@ +package class05; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; + +public class Code04_DeleteMinCost { + + // 题目: + // 给定两个字符串s1和s2,问s2最少删除多少字符可以成为s1的子串? + // 比如 s1 = "abcde",s2 = "axbc" + // 返回 1 + + // 解法一 + // 求出str2所有的子序列,然后按照长度排序,长度大的排在前面。 + // 然后考察哪个子序列字符串和s1的某个子串相等(KMP),答案就出来了。 + // 分析: + // 因为题目原本的样本数据中,有特别说明s2的长度很小。所以这么做也没有太大问题,也几乎不会超时。 + // 但是如果某一次考试给定的s2长度远大于s1,这么做就不合适了。 + public static int minCost1(String s1, String s2) { + List s2Subs = new ArrayList<>(); + process(s2.toCharArray(), 0, "", s2Subs); + s2Subs.sort(new LenComp()); + for (String str : s2Subs) { + if (s1.indexOf(str) != -1) { // indexOf底层和KMP算法代价几乎一样,也可以用KMP代替 + return s2.length() - str.length(); + } + } + return s2.length(); + } + + public static void process(char[] str2, int index, String path, List list) { + if (index == str2.length) { + list.add(path); + return; + } + process(str2, index + 1, path, list); + process(str2, index + 1, path + str2[index], list); + } + + // x字符串只通过删除的方式,变到y字符串 + // 返回至少要删几个字符 + // 如果变不成,返回Integer.Max + public static int onlyDelete(char[] x, char[] y) { + if (x.length < y.length) { + return Integer.MAX_VALUE; + } + int N = x.length; + int M = y.length; + int[][] dp = new int[N + 1][M + 1]; + for (int i = 0; i <= N; i++) { + for (int j = 0; j <= M; j++) { + dp[i][j] = Integer.MAX_VALUE; + } + } + dp[0][0] = 0; + // dp[i][j]表示前缀长度 + for (int i = 1; i <= N; i++) { + dp[i][0] = i; + } + for (int xlen = 1; xlen <= N; xlen++) { + for (int ylen = 1; ylen <= Math.min(M, xlen); ylen++) { + if (dp[xlen - 1][ylen] != Integer.MAX_VALUE) { + dp[xlen][ylen] = dp[xlen - 1][ylen] + 1; + } + if (x[xlen - 1] == y[ylen - 1] && dp[xlen - 1][ylen - 1] != Integer.MAX_VALUE) { + dp[xlen][ylen] = Math.min(dp[xlen][ylen], dp[xlen - 1][ylen - 1]); + } + } + } + return dp[N][M]; + } + + public static class LenComp implements Comparator { + + @Override + public int compare(String o1, String o2) { + return o2.length() - o1.length(); + } + + } + + // 解法二 + // 生成所有s1的子串 + // 然后考察每个子串和s2的编辑距离(假设编辑距离只有删除动作且删除一个字符的代价为1) + // 如果s1的长度较小,s2长度较大,这个方法比较合适 + public static int minCost2(String s1, String s2) { + if (s1.length() == 0 || s2.length() == 0) { + return s2.length(); + } + int ans = Integer.MAX_VALUE; + char[] str2 = s2.toCharArray(); + for (int start = 0; start < s1.length(); start++) { + for (int end = start + 1; end <= s1.length(); end++) { + // str1[start....end] + // substring -> [ 0,1 ) + ans = Math.min(ans, distance(str2, s1.substring(start, end).toCharArray())); + } + } + return ans == Integer.MAX_VALUE ? s2.length() : ans; + } + + // 求str2到s1sub的编辑距离 + // 假设编辑距离只有删除动作且删除一个字符的代价为1 + public static int distance(char[] str2, char[] s1sub) { + int row = str2.length; + int col = s1sub.length; + int[][] dp = new int[row][col]; + // dp[i][j]的含义: + // str2[0..i]仅通过删除行为变成s1sub[0..j]的最小代价 + // 可能性一: + // str2[0..i]变的过程中,不保留最后一个字符(str2[i]), + // 那么就是通过str2[0..i-1]变成s1sub[0..j]之后,再最后删掉str2[i]即可 -> dp[i][j] = dp[i-1][j] + 1 + // 可能性二: + // str2[0..i]变的过程中,想保留最后一个字符(str2[i]),然后变成s1sub[0..j], + // 这要求str2[i] == s1sub[j]才有这种可能, 然后str2[0..i-1]变成s1sub[0..j-1]即可 + // 也就是str2[i] == s1sub[j] 的条件下,dp[i][j] = dp[i-1][j-1] + dp[0][0] = str2[0] == s1sub[0] ? 0 : Integer.MAX_VALUE; + for (int j = 1; j < col; j++) { + dp[0][j] = Integer.MAX_VALUE; + } + for (int i = 1; i < row; i++) { + dp[i][0] = (dp[i - 1][0] != Integer.MAX_VALUE || str2[i] == s1sub[0]) ? i : Integer.MAX_VALUE; + } + for (int i = 1; i < row; i++) { + for (int j = 1; j < col; j++) { + dp[i][j] = Integer.MAX_VALUE; + if (dp[i - 1][j] != Integer.MAX_VALUE) { + dp[i][j] = dp[i - 1][j] + 1; + } + if (str2[i] == s1sub[j] && dp[i - 1][j - 1] != Integer.MAX_VALUE) { + dp[i][j] = Math.min(dp[i][j], dp[i - 1][j - 1]); + } + + } + } + return dp[row - 1][col - 1]; + } + + // 解法二的优化 + public static int minCost3(String s1, String s2) { + if (s1.length() == 0 || s2.length() == 0) { + return s2.length(); + } + char[] str2 = s2.toCharArray(); + char[] str1 = s1.toCharArray(); + int M = str2.length; + int N = str1.length; + int[][] dp = new int[M][N]; + int ans = M; + for (int start = 0; start < N; start++) { // 开始的列数 + dp[0][start] = str2[0] == str1[start] ? 0 : M; + for (int row = 1; row < M; row++) { + dp[row][start] = (str2[row] == str1[start] || dp[row - 1][start] != M) ? row : M; + } + ans = Math.min(ans, dp[M - 1][start]); + // 以上已经把start列,填好 + // 以下要把dp[...][start+1....N-1]的信息填好 + // start...end end - start +2 + for (int end = start + 1; end < N && end - start < M; end++) { + // 0... first-1 行 不用管 + int first = end - start; + dp[first][end] = (str2[first] == str1[end] && dp[first - 1][end - 1] == 0) ? 0 : M; + for (int row = first + 1; row < M; row++) { + dp[row][end] = M; + if (dp[row - 1][end] != M) { + dp[row][end] = dp[row - 1][end] + 1; + } + if (dp[row - 1][end - 1] != M && str2[row] == str1[end]) { + dp[row][end] = Math.min(dp[row][end], dp[row - 1][end - 1]); + } + } + ans = Math.min(ans, dp[M - 1][end]); + } + } + return ans; + } + + // 来自学生的做法,时间复杂度O(N * M平方) + // 复杂度和方法三一样,但是思路截然不同 + public static int minCost4(String s1, String s2) { + char[] str1 = s1.toCharArray(); + char[] str2 = s2.toCharArray(); + HashMap> map1 = new HashMap<>(); + for (int i = 0; i < str1.length; i++) { + ArrayList list = map1.getOrDefault(str1[i], new ArrayList()); + list.add(i); + map1.put(str1[i], list); + } + int ans = 0; + // 假设删除后的str2必以i位置开头 + // 那么查找i位置在str1上一共有几个,并对str1上的每个位置开始遍历 + // 再次遍历str2一次,看存在对应str1中i后续连续子串可容纳的最长长度 + for (int i = 0; i < str2.length; i++) { + if (map1.containsKey(str2[i])) { + ArrayList keyList = map1.get(str2[i]); + for (int j = 0; j < keyList.size(); j++) { + int cur1 = keyList.get(j) + 1; + int cur2 = i + 1; + int count = 1; + for (int k = cur2; k < str2.length && cur1 < str1.length; k++) { + if (str2[k] == str1[cur1]) { + cur1++; + count++; + } + } + ans = Math.max(ans, count); + } + } + } + return s2.length() - ans; + } + + public static String generateRandomString(int l, int v) { + int len = (int) (Math.random() * l); + char[] str = new char[len]; + for (int i = 0; i < len; i++) { + str[i] = (char) ('a' + (int) (Math.random() * v)); + } + return String.valueOf(str); + } + + public static void main(String[] args) { + + char[] x = { 'a', 'b', 'c', 'd' }; + char[] y = { 'a', 'd' }; + + System.out.println(onlyDelete(x, y)); + + int str1Len = 20; + int str2Len = 10; + int v = 5; + int testTime = 10000; + boolean pass = true; + System.out.println("test begin"); + for (int i = 0; i < testTime; i++) { + String str1 = generateRandomString(str1Len, v); + String str2 = generateRandomString(str2Len, v); + int ans1 = minCost1(str1, str2); + int ans2 = minCost2(str1, str2); + int ans3 = minCost3(str1, str2); + int ans4 = minCost4(str1, str2); + if (ans1 != ans2 || ans3 != ans4 || ans1 != ans3) { + pass = false; + System.out.println(str1); + System.out.println(str2); + System.out.println(ans1); + System.out.println(ans2); + System.out.println(ans3); + System.out.println(ans4); + break; + } + } + System.out.println("test pass : " + pass); + } + +} diff --git a/大厂刷题班/class05/Hash.java b/大厂刷题班/class05/Hash.java new file mode 100644 index 0000000..c995ac9 --- /dev/null +++ b/大厂刷题班/class05/Hash.java @@ -0,0 +1,49 @@ +package class05; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.Security; + +// 这个包的jar,你需要自己下载一下,导入到项目中 +import javax.xml.bind.DatatypeConverter; + +public class Hash { + + private MessageDigest hash; + + public Hash(String algorithm) { + try { + hash = MessageDigest.getInstance(algorithm); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } + } + + public String hashCode(String input) { + return DatatypeConverter.printHexBinary(hash.digest(input.getBytes())).toUpperCase(); + } + + public static void main(String[] args) { + System.out.println("支持的算法 : "); + for (String str : Security.getAlgorithms("MessageDigest")) { + System.out.println(str); + } + System.out.println("======="); + + String algorithm = "SHA-256"; + Hash hash = new Hash(algorithm); + + String input1 = "zuochengyunzuochengyun1"; + String input2 = "zuochengyunzuochengyun2"; + String input3 = "zuochengyunzuochengyun3"; + String input4 = "zuochengyunzuochengyun4"; + String input5 = "zuochengyunzuochengyun5"; + System.out.println(hash.hashCode(input1)); + System.out.println(hash.hashCode(input2)); + System.out.println(hash.hashCode(input3)); + System.out.println(hash.hashCode(input4)); + System.out.println(hash.hashCode(input5)); + + } + +} diff --git a/大厂刷题班/class06/Code01_MaxXOR.java b/大厂刷题班/class06/Code01_MaxXOR.java new file mode 100644 index 0000000..d6ee49d --- /dev/null +++ b/大厂刷题班/class06/Code01_MaxXOR.java @@ -0,0 +1,130 @@ +package class06; + +public class Code01_MaxXOR { + + // O(N^2) + public static int maxXorSubarray1(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + // 准备一个前缀异或和数组eor + // eor[i] = arr[0...i]的异或结果 + int[] eor = new int[arr.length]; + eor[0] = arr[0]; + // 生成eor数组,eor[i]代表arr[0..i]的异或和 + for (int i = 1; i < arr.length; i++) { + eor[i] = eor[i - 1] ^ arr[i]; + } + int max = Integer.MIN_VALUE; + for (int j = 0; j < arr.length; j++) { + for (int i = 0; i <= j; i++) { // 依次尝试arr[0..j]、arr[1..j]..arr[i..j]..arr[j..j] + max = Math.max(max, i == 0 ? eor[j] : eor[j] ^ eor[i - 1]); + } + } + return max; + } + + // 前缀树的Node结构 + // nexts[0] -> 0方向的路 + // nexts[1] -> 1方向的路 + // nexts[0] == null 0方向上没路! + // nexts[0] != null 0方向有路,可以跳下一个节点 + // nexts[1] == null 1方向上没路! + // nexts[1] != null 1方向有路,可以跳下一个节点 + public static class Node { + public Node[] nexts = new Node[2]; + } + + // 基于本题,定制前缀树的实现 + public static class NumTrie { + // 头节点 + public Node head = new Node(); + + public void add(int newNum) { + Node cur = head; + for (int move = 31; move >= 0; move--) { + int path = ((newNum >> move) & 1); + cur.nexts[path] = cur.nexts[path] == null ? new Node() : cur.nexts[path]; + cur = cur.nexts[path]; + } + } + + // 该结构之前收集了一票数字,并且建好了前缀树 + // num和 谁 ^ 最大的结果(把结果返回) + public int maxXor(int num) { + Node cur = head; + int ans = 0; + for (int move = 31; move >= 0; move--) { + // 取出num中第move位的状态,path只有两种值0就1,整数 + int path = (num >> move) & 1; + // 期待遇到的东西 + int best = move == 31 ? path : (path ^ 1); + // 实际遇到的东西 + best = cur.nexts[best] != null ? best : (best ^ 1); + // (path ^ best) 当前位位异或完的结果 + ans |= (path ^ best) << move; + cur = cur.nexts[best]; + } + return ans; + } + } + + // O(N) + public static int maxXorSubarray2(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + int max = Integer.MIN_VALUE; + // 0~i整体异或和 + int xor = 0; + NumTrie numTrie = new NumTrie(); + numTrie.add(0); + for (int i = 0; i < arr.length; i++) { + xor ^= arr[i]; // 0 ~ i + max = Math.max(max, numTrie.maxXor(xor)); + numTrie.add(xor); + } + return max; + } + + // for test + public static int[] generateRandomArray(int maxSize, int maxValue) { + int[] arr = new int[(int) ((maxSize + 1) * Math.random())]; + for (int i = 0; i < arr.length; i++) { + arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random()); + } + return arr; + } + + // for test + public static void printArray(int[] arr) { + if (arr == null) { + return; + } + for (int i = 0; i < arr.length; i++) { + System.out.print(arr[i] + " "); + } + System.out.println(); + } + + // for test + public static void main(String[] args) { + int testTime = 500000; + int maxSize = 30; + int maxValue = 50; + boolean succeed = true; + for (int i = 0; i < testTime; i++) { + int[] arr = generateRandomArray(maxSize, maxValue); + int comp = maxXorSubarray1(arr); + int res = maxXorSubarray2(arr); + if (res != comp) { + succeed = false; + printArray(arr); + System.out.println(res); + System.out.println(comp); + break; + } + } + System.out.println(succeed ? "Nice!" : "Fucking fucked!"); + } +} diff --git a/大厂刷题班/class06/Code02_MaximumXorOfTwoNumbersInAnArray.java b/大厂刷题班/class06/Code02_MaximumXorOfTwoNumbersInAnArray.java new file mode 100644 index 0000000..4ee4c6e --- /dev/null +++ b/大厂刷题班/class06/Code02_MaximumXorOfTwoNumbersInAnArray.java @@ -0,0 +1,50 @@ +package class06; + +// 本题测试链接 : https://leetcode.com/problems/maximum-xor-of-two-numbers-in-an-array/ +public class Code02_MaximumXorOfTwoNumbersInAnArray { + + public static class Node { + public Node[] nexts = new Node[2]; + } + + public static class NumTrie { + public Node head = new Node(); + + public void add(int newNum) { + Node cur = head; + for (int move = 31; move >= 0; move--) { + int path = ((newNum >> move) & 1); + cur.nexts[path] = cur.nexts[path] == null ? new Node() : cur.nexts[path]; + cur = cur.nexts[path]; + } + } + + public int maxXor(int sum) { + Node cur = head; + int res = 0; + for (int move = 31; move >= 0; move--) { + int path = (sum >> move) & 1; + int best = move == 31 ? path : (path ^ 1); + best = cur.nexts[best] != null ? best : (best ^ 1); + res |= (path ^ best) << move; + cur = cur.nexts[best]; + } + return res; + } + } + + public static int findMaximumXOR(int[] arr) { + if (arr == null || arr.length < 2) { + return 0; + } + int max = Integer.MIN_VALUE; + NumTrie numTrie = new NumTrie(); + numTrie.add(arr[0]); + for (int i = 1; i < arr.length; i++) { + max = Math.max(max, numTrie.maxXor(arr[i])); + numTrie.add(arr[i]); + } + return max; + } + +} diff --git a/大厂刷题班/class06/Code03_MaximumXorWithAnElementFromArray.java b/大厂刷题班/class06/Code03_MaximumXorWithAnElementFromArray.java new file mode 100644 index 0000000..7ad2f29 --- /dev/null +++ b/大厂刷题班/class06/Code03_MaximumXorWithAnElementFromArray.java @@ -0,0 +1,67 @@ +package class06; + +// 测试链接 : https://leetcode.com/problems/maximum-xor-with-an-element-from-array/ +public class Code03_MaximumXorWithAnElementFromArray { + + public static int[] maximizeXor(int[] nums, int[][] queries) { + int N = nums.length; + NumTrie trie = new NumTrie(); + for (int i = 0; i < N; i++) { + trie.add(nums[i]); + } + int M = queries.length; + int[] ans = new int[M]; + for (int i = 0; i < M; i++) { + ans[i] = trie.maxXorWithXBehindM(queries[i][0], queries[i][1]); + } + return ans; + } + + public static class Node { + public int min; + public Node[] nexts; + + public Node() { + min = Integer.MAX_VALUE; + nexts = new Node[2]; + } + } + + public static class NumTrie { + public Node head = new Node(); + + public void add(int num) { + Node cur = head; + head.min = Math.min(head.min, num); + for (int move = 30; move >= 0; move--) { + int path = ((num >> move) & 1); + cur.nexts[path] = cur.nexts[path] == null ? new Node() : cur.nexts[path]; + cur = cur.nexts[path]; + cur.min = Math.min(cur.min, num); + } + } + + // 这个结构中,已经收集了一票数字 + // 请返回哪个数字与X异或的结果最大,返回最大结果 + // 但是,只有<=m的数字,可以被考虑 + public int maxXorWithXBehindM(int x, int m) { + if (head.min > m) { + return -1; + } + // 一定存在某个数可以和x结合 + Node cur = head; + int ans = 0; + for (int move = 30; move >= 0; move--) { + int path = (x >> move) & 1; + // 期待遇到的东西 + int best = (path ^ 1); + best ^= (cur.nexts[best] == null || cur.nexts[best].min > m) ? 1 : 0; + // best变成了实际遇到的 + ans |= (path ^ best) << move; + cur = cur.nexts[best]; + } + return ans; + } + } + +} diff --git a/大厂刷题班/class06/Code04_MostXorZero.java b/大厂刷题班/class06/Code04_MostXorZero.java new file mode 100644 index 0000000..a73e5c5 --- /dev/null +++ b/大厂刷题班/class06/Code04_MostXorZero.java @@ -0,0 +1,122 @@ +package class06; + +import java.util.ArrayList; +import java.util.HashMap; + +public class Code04_MostXorZero { + + // 暴力方法 + public static int comparator(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + int N = arr.length; + int[] eor = new int[N]; + eor[0] = arr[0]; + for (int i = 1; i < N; i++) { + eor[i] = eor[i - 1] ^ arr[i]; + } + return process(eor, 1, new ArrayList<>()); + } + + // index去决定:前一坨部分,结不结束! + // 如果结束!就把index放入到parts里去 + // 如果不结束,就不放 + public static int process(int[] eor, int index, ArrayList parts) { + int ans = 0; + if (index == eor.length) { + parts.add(eor.length); + ans = eorZeroParts(eor, parts); + parts.remove(parts.size() - 1); + } else { + int p1 = process(eor, index + 1, parts); + parts.add(index); + int p2 = process(eor, index + 1, parts); + parts.remove(parts.size() - 1); + ans = Math.max(p1, p2); + } + return ans; + } + + public static int eorZeroParts(int[] eor, ArrayList parts) { + int L = 0; + int ans = 0; + for (Integer end : parts) { + if ((eor[end - 1] ^ (L == 0 ? 0 : eor[L - 1])) == 0) { + ans++; + } + L = end; + } + return ans; + } + + // 时间复杂度O(N)的方法 + public static int mostXor(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + int N = arr.length; + int[] dp = new int[N]; + + // key 某一个前缀异或和 + // value 这个前缀异或和上次出现的位置(最晚!) + HashMap map = new HashMap<>(); + map.put(0, -1); + // 0~i整体的异或和 + int xor = 0; + for (int i = 0; i < N; i++) { + xor ^= arr[i]; + if (map.containsKey(xor)) { // 可能性2 + int pre = map.get(xor); + dp[i] = pre == -1 ? 1 : (dp[pre] + 1); + } + if (i > 0) { + dp[i] = Math.max(dp[i - 1], dp[i]); + } + map.put(xor, i); + } + return dp[N - 1]; + } + + // for test + public static int[] generateRandomArray(int maxSize, int maxValue) { + int[] arr = new int[(int) ((maxSize + 1) * Math.random())]; + for (int i = 0; i < arr.length; i++) { + arr[i] = (int) ((maxValue + 1) * Math.random()); + } + return arr; + } + + // for test + public static void printArray(int[] arr) { + if (arr == null) { + return; + } + for (int i = 0; i < arr.length; i++) { + System.out.print(arr[i] + " "); + } + System.out.println(); + } + + // for test + public static void main(String[] args) { + int testTime = 150000; + int maxSize = 12; + int maxValue = 5; + boolean succeed = true; + for (int i = 0; i < testTime; i++) { + int[] arr = generateRandomArray(maxSize, maxValue); + int res = mostXor(arr); + int comp = comparator(arr); + if (res != comp) { + succeed = false; + printArray(arr); + System.out.println(res); + System.out.println(comp); + break; + } + } + System.out.println(succeed ? "Nice!" : "Fucking fucked!"); + } + +} diff --git a/大厂刷题班/class06/Code05_Nim.java b/大厂刷题班/class06/Code05_Nim.java new file mode 100644 index 0000000..621ecc0 --- /dev/null +++ b/大厂刷题班/class06/Code05_Nim.java @@ -0,0 +1,18 @@ +package class06; + +public class Code05_Nim { + + // 保证arr是正数数组 + public static void printWinner(int[] arr) { + int eor = 0; + for (int num : arr) { + eor ^= num; + } + if (eor == 0) { + System.out.println("后手赢"); + } else { + System.out.println("先手赢"); + } + } + +} diff --git a/大厂刷题班/class07/Code01_MaxAndValue.java b/大厂刷题班/class07/Code01_MaxAndValue.java new file mode 100644 index 0000000..fb9bda9 --- /dev/null +++ b/大厂刷题班/class07/Code01_MaxAndValue.java @@ -0,0 +1,102 @@ +package class07; + +// 给定一个正数组成的数组,长度一定大于1,求数组中哪两个数与的结果最大 +public class Code01_MaxAndValue { + + // O(N^2)的暴力解 + public static int maxAndValue1(int[] arr) { + int N = arr.length; + int max = Integer.MIN_VALUE; + for (int i = 0; i < N; i++) { + for (int j = i + 1; j < N; j++) { + max = Math.max(max, arr[i] & arr[j]); + } + } + return max; + } + + // O(N)的解 + // 因为是正数,所以不用考虑符号位(31位) + // 首先来到30位,假设剩余的数字有N个(整体),看看这一位是1的数,有几个 + // 如果有0个、或者1个 + // 说明不管怎么在数组中选择,任何两个数&的结果在第30位上都不可能有1了 + // 答案在第30位上的状态一定是0, + // 保留剩余的N个数,继续考察第29位,谁也不淘汰(因为谁也不行,干脆接受30位上没有1的事实) + // 如果有2个, + // 说明答案就是这两个数(直接返回答案),因为别的数在第30位都没有1,就这两个数有。 + // 如果有>2个,比如K个 + // 说明答案一定只用在这K个数中去选择某两个数,因为别的数在第30位都没有1,就这K个数有。 + // 答案在第30位上的状态一定是1, + // 只把这K个数作为剩余的数,继续考察第29位,其他数都淘汰掉 + // ..... + // 现在来到i位,假设剩余的数字有M个,看看这一位是1的数,有几个 + // 如果有0个、或者1个 + // 说明不管怎么在M个数中选择,任何两个数&的结果在第i位上都不可能有1了 + // 答案在第i位上的状态一定是0, + // 保留剩余的M个数,继续考察第i-1位 + // 如果有2个, + // 说明答案就是这两个数(直接返回答案),因为别的数在第i位都没有1,就这两个数有。 + // 如果有>2个,比如K个 + // 说明答案一定只用在这K个数中去选择某两个数,因为别的数在第i位都没有1,就这K个数有。 + // 答案在第i位上的状态一定是1, + // 只把这K个数作为剩余的数,继续考察第i-1位,其他数都淘汰掉 + public static int maxAndValue2(int[] arr) { + // arr[0...M-1] arr[M....] + int M = arr.length; + int ans = 0; + for (int bit = 30; bit >= 0; bit--) { + // arr[0...M-1] arr[M...] + int i = 0; + int tmp = M; + while (i < M) { // arr[0...M-1] + if ((arr[i] & (1 << bit)) == 0) { + swap(arr, i, --M); + } else { + i++; + } + } + if (M == 2) { // arr[0,1] + return arr[0] & arr[1]; + } + if (M < 2) { + M = tmp; + } else { // > 2个数 bit位上有1 + ans |= (1 << bit); + } + } + return ans; + } + + public static void swap(int[] arr, int i, int j) { + int tmp = arr[i]; + arr[i] = arr[j]; + arr[j] = tmp; + } + + public static int[] randomArray(int size, int range) { + int[] arr = new int[size]; + for (int i = 0; i < size; i++) { + arr[i] = (int) (Math.random() * range) + 1; + } + return arr; + } + + public static void main(String[] args) { + int maxSize = 50; + int range = 30; + int testTime = 1000000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int size = (int) (Math.random() * maxSize) + 2; + int[] arr = randomArray(size, range); + int ans1 = maxAndValue1(arr); + int ans2 = maxAndValue2(arr); + if (ans1 != ans2) { + System.out.println("Oops!"); + } + } + System.out.println("测试结束"); + + } + +} diff --git a/大厂刷题班/class07/Code02_MinCameraCover.java b/大厂刷题班/class07/Code02_MinCameraCover.java new file mode 100644 index 0000000..ebde2ee --- /dev/null +++ b/大厂刷题班/class07/Code02_MinCameraCover.java @@ -0,0 +1,113 @@ +package class07; + +// 本题测试链接 : https://leetcode.com/problems/binary-tree-cameras/ +public class Code02_MinCameraCover { + + public static class TreeNode { + public int value; + public TreeNode left; + public TreeNode right; + } + + public static int minCameraCover1(TreeNode root) { + Info data = process1(root); + return (int) Math.min(data.uncovered + 1, Math.min(data.coveredNoCamera, data.coveredHasCamera)); + } + + // 潜台词:x是头节点,x下方的点都被覆盖的情况下 + public static class Info { + public long uncovered; // x没有被覆盖,x为头的树至少需要几个相机 + public long coveredNoCamera; // x被相机覆盖,但是x没相机,x为头的树至少需要几个相机 + public long coveredHasCamera; // x被相机覆盖了,并且x上放了相机,x为头的树至少需要几个相机 + + public Info(long un, long no, long has) { + uncovered = un; + coveredNoCamera = no; + coveredHasCamera = has; + } + } + + // 所有可能性都穷尽了 + public static Info process1(TreeNode X) { + if (X == null) { // base case + return new Info(Integer.MAX_VALUE, 0, Integer.MAX_VALUE); + } + + Info left = process1(X.left); + Info right = process1(X.right); + // x uncovered x自己不被覆盖,x下方所有节点,都被覆盖 + // 左孩子: 左孩子没被覆盖,左孩子以下的点都被覆盖 + // 左孩子被覆盖但没相机,左孩子以下的点都被覆盖 + // 左孩子被覆盖也有相机,左孩子以下的点都被覆盖 + long uncovered = left.coveredNoCamera + right.coveredNoCamera; + + // x下方的点都被covered,x也被cover,但x上没相机 + long coveredNoCamera = Math.min( + // 1) + left.coveredHasCamera + right.coveredHasCamera, + + Math.min( + // 2) + left.coveredHasCamera + right.coveredNoCamera, + + // 3) + left.coveredNoCamera + right.coveredHasCamera)); + + + + + // x下方的点都被covered,x也被cover,且x上有相机 + long coveredHasCamera = + Math.min(left.uncovered, Math.min(left.coveredNoCamera, left.coveredHasCamera)) + + + Math.min(right.uncovered, Math.min(right.coveredNoCamera, right.coveredHasCamera)) + + + 1; + + return new Info(uncovered, coveredNoCamera, coveredHasCamera); + } + + public static int minCameraCover2(TreeNode root) { + Data data = process2(root); + return data.cameras + (data.status == Status.UNCOVERED ? 1 : 0); + } + + // 以x为头,x下方的节点都是被covered,x自己的状况,分三种 + public static enum Status { + UNCOVERED, COVERED_NO_CAMERA, COVERED_HAS_CAMERA + } + + // 以x为头,x下方的节点都是被covered,得到的最优解中: + // x是什么状态,在这种状态下,需要至少几个相机 + public static class Data { + public Status status; + public int cameras; + + public Data(Status status, int cameras) { + this.status = status; + this.cameras = cameras; + } + } + + public static Data process2(TreeNode X) { + if (X == null) { + return new Data(Status.COVERED_NO_CAMERA, 0); + } + Data left = process2(X.left); + Data right = process2(X.right); + int cameras = left.cameras + right.cameras; + + // 左、或右,哪怕有一个没覆盖 + if (left.status == Status.UNCOVERED || right.status == Status.UNCOVERED) { + return new Data(Status.COVERED_HAS_CAMERA, cameras + 1); + } + + // 左右孩子,不存在没被覆盖的情况 + if (left.status == Status.COVERED_HAS_CAMERA || right.status == Status.COVERED_HAS_CAMERA) { + return new Data(Status.COVERED_NO_CAMERA, cameras); + } + // 左右孩子,不存在没被覆盖的情况,也都没有相机 + return new Data(Status.UNCOVERED, cameras); + } + +} diff --git a/大厂刷题班/class07/Code03_MaxGap.java b/大厂刷题班/class07/Code03_MaxGap.java new file mode 100644 index 0000000..486c6d3 --- /dev/null +++ b/大厂刷题班/class07/Code03_MaxGap.java @@ -0,0 +1,46 @@ +package class07; + +// 测试链接 : https://leetcode.com/problems/maximum-gap/ +public class Code03_MaxGap { + + public static int maximumGap(int[] nums) { + if (nums == null || nums.length < 2) { + return 0; + } + int len = nums.length; + int min = Integer.MAX_VALUE; + int max = Integer.MIN_VALUE; + for (int i = 0; i < len; i++) { + min = Math.min(min, nums[i]); + max = Math.max(max, nums[i]); + } + if (min == max) { + return 0; + } + boolean[] hasNum = new boolean[len + 1]; + int[] maxs = new int[len + 1]; + int[] mins = new int[len + 1]; + int bid = 0; + for (int i = 0; i < len; i++) { + bid = bucket(nums[i], len, min, max); + mins[bid] = hasNum[bid] ? Math.min(mins[bid], nums[i]) : nums[i]; + maxs[bid] = hasNum[bid] ? Math.max(maxs[bid], nums[i]) : nums[i]; + hasNum[bid] = true; + } + int res = 0; + int lastMax = maxs[0]; + int i = 1; + for (; i <= len; i++) { + if (hasNum[i]) { + res = Math.max(res, mins[i] - lastMax); + lastMax = maxs[i]; + } + } + return res; + } + + public static int bucket(long num, long len, long min, long max) { + return (int) ((num - min) * len / (max - min)); + } + +} diff --git a/大厂刷题班/class07/Code04_Power2Diffs.java b/大厂刷题班/class07/Code04_Power2Diffs.java new file mode 100644 index 0000000..ab6c553 --- /dev/null +++ b/大厂刷题班/class07/Code04_Power2Diffs.java @@ -0,0 +1,97 @@ +package class07; + +import java.util.Arrays; +import java.util.HashSet; + +public class Code04_Power2Diffs { + + /* + * 给定一个有序数组arr,其中值可能为正、负、0。 返回arr中每个数都平方之后不同的结果有多少种? + * + * 给定一个数组arr,先递减然后递增,返回arr中有多少个绝对值不同的数字? + * + */ + + // 时间复杂度O(N),额外空间复杂度O(N) + public static int diff1(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + HashSet set = new HashSet<>(); + for (int cur : arr) { + set.add(cur * cur); + } + return set.size(); + } + + // 时间复杂度O(N),额外空间复杂度O(1) + public static int diff2(int[] arr) { + int N = arr.length; + int L = 0; + int R = N - 1; + int count = 0; + int leftAbs = 0; + int rightAbs = 0; + while (L <= R) { + count++; + leftAbs = Math.abs(arr[L]); + rightAbs = Math.abs(arr[R]); + if (leftAbs < rightAbs) { + while (R >= 0 && Math.abs(arr[R]) == rightAbs) { + R--; + } + } else if (leftAbs > rightAbs) { + while (L < N && Math.abs(arr[L]) == leftAbs) { + L++; + } + } else { + while (L < N && Math.abs(arr[L]) == leftAbs) { + L++; + } + while (R >= 0 && Math.abs(arr[R]) == rightAbs) { + R--; + } + } + } + return count; + } + + // for test + public static int[] randomSortedArray(int len, int value) { + int[] ans = new int[(int) (Math.random() * len) + 1]; + for (int i = 0; i < ans.length; i++) { + ans[i] = (int) (Math.random() * value) - (int) (Math.random() * value); + } + Arrays.sort(ans); + return ans; + } + + // for test + public static void printArray(int[] arr) { + for (int cur : arr) { + System.out.print(cur + " "); + } + System.out.println(); + } + + public static void main(String[] args) { + int len = 100; + int value = 500; + int testTimes = 200000; + System.out.println("test begin"); + for (int i = 0; i < testTimes; i++) { + int[] arr = randomSortedArray(len, value); + int ans1 = diff1(arr); + int ans2 = diff2(arr); + if (ans1 != ans2) { + printArray(arr); + System.out.println(ans1); + System.out.println(ans2); + System.out.println("Oops!"); + break; + } + } + System.out.println("test finish"); + } + +} diff --git a/大厂刷题班/class07/Code05_WorldBreak.java b/大厂刷题班/class07/Code05_WorldBreak.java new file mode 100644 index 0000000..134073a --- /dev/null +++ b/大厂刷题班/class07/Code05_WorldBreak.java @@ -0,0 +1,237 @@ +package class07; + +import java.util.HashSet; + +public class Code05_WorldBreak { + /* + * + * 假设所有字符都是小写字母. 大字符串是str. arr是去重的单词表, 每个单词都不是空字符串且可以使用任意次. + * 使用arr中的单词有多少种拼接str的方式. 返回方法数. + * + */ + + public static int ways(String str, String[] arr) { + HashSet set = new HashSet<>(); + for (String candidate : arr) { + set.add(candidate); + } + return process(str, 0, set); + } + + // 所有的可分解字符串,都已经放在了set中 + // str[i....] 能够被set中的贴纸分解的话,返回分解的方法数 + public static int process(String str, int i, HashSet set) { + if (i == str.length()) { // 没字符串需要分解了! + return 1; + } + // i....还有字符串需要分解 + int ways = 0; + // [i ... end] 前缀串 每一个前缀串 + for (int end = i; end < str.length(); end++) { + String pre = str.substring(i, end + 1);// [) + if (set.contains(pre)) { + ways += process(str, end + 1, set); + } + } + return ways; + } + + public static int ways1(String str, String[] arr) { + if (str == null || str.length() == 0 || arr == null || arr.length == 0) { + return 0; + } + HashSet map = new HashSet<>(); + for (String s : arr) { + map.add(s); + } + return f(str, map, 0); + } + + public static int f(String str, HashSet map, int index) { + if (index == str.length()) { + return 1; + } + int ways = 0; + for (int end = index; end < str.length(); end++) { + if (map.contains(str.substring(index, end + 1))) { + ways += f(str, map, end + 1); + } + } + return ways; + } + + public static int ways2(String str, String[] arr) { + if (str == null || str.length() == 0 || arr == null || arr.length == 0) { + return 0; + } + HashSet map = new HashSet<>(); + for (String s : arr) { + map.add(s); + } + int N = str.length(); + int[] dp = new int[N + 1]; + dp[N] = 1; + for (int i = N - 1; i >= 0; i--) { + for (int end = i; end < N; end++) { + if (map.contains(str.substring(i, end + 1))) { + dp[i] += dp[end + 1]; + } + } + } + return dp[0]; + } + + public static class Node { + public boolean end; + public Node[] nexts; + + public Node() { + end = false; + nexts = new Node[26]; + } + } + + public static int ways3(String str, String[] arr) { + if (str == null || str.length() == 0 || arr == null || arr.length == 0) { + return 0; + } + Node root = new Node(); + for (String s : arr) { + char[] chs = s.toCharArray(); + Node node = root; + int index = 0; + for (int i = 0; i < chs.length; i++) { + index = chs[i] - 'a'; + if (node.nexts[index] == null) { + node.nexts[index] = new Node(); + } + node = node.nexts[index]; + } + node.end = true; + } + return g(str.toCharArray(), root, 0); + } + + // str[i...] 被分解的方法数,返回 + + public static int g(char[] str, Node root, int i) { + if (i == str.length) { + return 1; + } + int ways = 0; + Node cur = root; + // i...end + for (int end = i; end < str.length; end++) { + int path = str[end] - 'a'; + if (cur.nexts[path] == null) { + break; + } + cur = cur.nexts[path]; + if (cur.end) { // i...end + ways += g(str, root, end + 1); + } + } + return ways; + } + + public static int ways4(String s, String[] arr) { + if (s == null || s.length() == 0 || arr == null || arr.length == 0) { + return 0; + } + Node root = new Node(); + for (String str : arr) { + char[] chs = str.toCharArray(); + Node node = root; + int index = 0; + for (int i = 0; i < chs.length; i++) { + index = chs[i] - 'a'; + if (node.nexts[index] == null) { + node.nexts[index] = new Node(); + } + node = node.nexts[index]; + } + node.end = true; + } + char[] str = s.toCharArray(); + int N = str.length; + int[] dp = new int[N + 1]; + dp[N] = 1; + for (int i = N - 1; i >= 0; i--) { + Node cur = root; + for (int end = i; end < N; end++) { + int path = str[end] - 'a'; + if (cur.nexts[path] == null) { + break; + } + cur = cur.nexts[path]; + if (cur.end) { + dp[i] += dp[end + 1]; + } + } + } + return dp[0]; + } + + // 以下的逻辑都是为了测试 + public static class RandomSample { + public String str; + public String[] arr; + + public RandomSample(String s, String[] a) { + str = s; + arr = a; + } + } + + // 随机样本产生器 + public static RandomSample generateRandomSample(char[] candidates, int num, int len, int joint) { + String[] seeds = randomSeeds(candidates, num, len); + HashSet set = new HashSet<>(); + for (String str : seeds) { + set.add(str); + } + String[] arr = new String[set.size()]; + int index = 0; + for (String str : set) { + arr[index++] = str; + } + StringBuilder all = new StringBuilder(); + for (int i = 0; i < joint; i++) { + all.append(arr[(int) (Math.random() * arr.length)]); + } + return new RandomSample(all.toString(), arr); + } + + public static String[] randomSeeds(char[] candidates, int num, int len) { + String[] arr = new String[(int) (Math.random() * num) + 1]; + for (int i = 0; i < arr.length; i++) { + char[] str = new char[(int) (Math.random() * len) + 1]; + for (int j = 0; j < str.length; j++) { + str[j] = candidates[(int) (Math.random() * candidates.length)]; + } + arr[i] = String.valueOf(str); + } + return arr; + } + + public static void main(String[] args) { + char[] candidates = { 'a', 'b' }; + int num = 20; + int len = 4; + int joint = 5; + int testTimes = 30000; + boolean testResult = true; + for (int i = 0; i < testTimes; i++) { + RandomSample sample = generateRandomSample(candidates, num, len, joint); + int ans1 = ways1(sample.str, sample.arr); + int ans2 = ways2(sample.str, sample.arr); + int ans3 = ways3(sample.str, sample.arr); + int ans4 = ways4(sample.str, sample.arr); + if (ans1 != ans2 || ans3 != ans4 || ans2 != ans4) { + testResult = false; + } + } + System.out.println(testTimes + "次随机测试是否通过:" + testResult); + } + +} diff --git a/大厂刷题班/class07/Code06_SplitStringMaxValue.java b/大厂刷题班/class07/Code06_SplitStringMaxValue.java new file mode 100644 index 0000000..c6e76cc --- /dev/null +++ b/大厂刷题班/class07/Code06_SplitStringMaxValue.java @@ -0,0 +1,137 @@ +package class07; + +import java.util.HashMap; + +public class Code06_SplitStringMaxValue { + + // 暴力解 + public static int maxRecord1(String str, int K, String[] parts, int[] record) { + if (str == null || str.length() == 0) { + return 0; + } + HashMap records = new HashMap<>(); + for (int i = 0; i < parts.length; i++) { + records.put(parts[i], record[i]); + } + return process(str, 0, K, records); + } + + public static int process(String str, int index, int rest, HashMap records) { + if (rest < 0) { + return -1; + } + if (index == str.length()) { + return rest == 0 ? 0 : -1; + } + int ans = -1; + for (int end = index; end < str.length(); end++) { + String first = str.substring(index, end + 1); + int next = records.containsKey(first) ? process(str, end + 1, rest - 1, records) : -1; + if (next != -1) { + ans = Math.max(ans, records.get(first) + next); + } + } + return ans; + } + + // 动态规划解 + public static int maxRecord2(String str, int K, String[] parts, int[] record) { + if (str == null || str.length() == 0) { + return 0; + } + HashMap records = new HashMap<>(); + for (int i = 0; i < parts.length; i++) { + records.put(parts[i], record[i]); + } + int N = str.length(); + int[][] dp = new int[N + 1][K + 1]; + for (int rest = 1; rest <= K; rest++) { + dp[N][rest] = -1; + } + for (int index = N - 1; index >= 0; index--) { + for (int rest = 0; rest <= K; rest++) { + int ans = -1; + for (int end = index; end < N; end++) { + String first = str.substring(index, end + 1); + int next = rest > 0 && records.containsKey(first) ? dp[end + 1][rest - 1] : -1; + if (next != -1) { + ans = Math.max(ans, records.get(first) + next); + } + } + dp[index][rest] = ans; + } + } + return dp[0][K]; + } + + // 动态规划解 + 前缀树优化 + public static int maxRecord3(String s, int K, String[] parts, int[] record) { + if (s == null || s.length() == 0) { + return 0; + } + TrieNode root = rootNode(parts, record); + char[] str = s.toCharArray(); + int N = str.length; + int[][] dp = new int[N + 1][K + 1]; + for (int rest = 1; rest <= K; rest++) { + dp[N][rest] = -1; + } + for (int index = N - 1; index >= 0; index--) { + for (int rest = 0; rest <= K; rest++) { + int ans = -1; + TrieNode cur = root; + for (int end = index; end < N; end++) { + int path = str[end] - 'a'; + if (cur.nexts[path] == null) { + break; + } + cur = cur.nexts[path]; + int next = rest > 0 && cur.value != -1 ? dp[end + 1][rest - 1] : -1; + if (next != -1) { + ans = Math.max(ans, cur.value + next); + } + } + dp[index][rest] = ans; + } + } + return dp[0][K]; + } + + public static class TrieNode { + public TrieNode[] nexts; + public int value; + + public TrieNode() { + nexts = new TrieNode[26]; + value = -1; + } + } + + public static TrieNode rootNode(String[] parts, int[] record) { + TrieNode root = new TrieNode(); + for (int i = 0; i < parts.length; i++) { + char[] str = parts[i].toCharArray(); + TrieNode cur = root; + for (int j = 0; j < str.length; j++) { + int path = str[j] - 'a'; + if (cur.nexts[path] == null) { + cur.nexts[path] = new TrieNode(); + } + cur = cur.nexts[path]; + } + cur.value = record[i]; + } + return root; + } + + public static void main(String[] args) { + String str = "abcdefg"; + int K = 3; + String[] parts = { "abc", "def", "g", "ab", "cd", "efg", "defg" }; + int[] record = { 1, 1, 1, 3, 3, 3, 2 }; + System.out.println(maxRecord1(str, K, parts, record)); + System.out.println(maxRecord2(str, K, parts, record)); + System.out.println(maxRecord3(str, K, parts, record)); + } + +} diff --git a/大厂刷题班/class08/Code01_ExpressionCompute.java b/大厂刷题班/class08/Code01_ExpressionCompute.java new file mode 100644 index 0000000..6d1192b --- /dev/null +++ b/大厂刷题班/class08/Code01_ExpressionCompute.java @@ -0,0 +1,71 @@ +package class08; + +import java.util.LinkedList; + +// 本题测试链接 : https://leetcode.com/problems/basic-calculator-iii/ +public class Code01_ExpressionCompute { + + public static int calculate(String str) { + return f(str.toCharArray(), 0)[0]; + } + + // 请从str[i...]往下算,遇到字符串终止位置或者右括号,就停止 + // 返回两个值,长度为2的数组 + // 0) 负责的这一段的结果是多少 + // 1) 负责的这一段计算到了哪个位置 + public static int[] f(char[] str, int i) { + LinkedList que = new LinkedList(); + int cur = 0; + int[] bra = null; + // 从i出发,开始撸串 + while (i < str.length && str[i] != ')') { + if (str[i] >= '0' && str[i] <= '9') { + cur = cur * 10 + str[i++] - '0'; + } else if (str[i] != '(') { // 遇到的是运算符号 + addNum(que, cur); + que.addLast(String.valueOf(str[i++])); + cur = 0; + } else { // 遇到左括号了 + bra = f(str, i + 1); + cur = bra[0]; + i = bra[1] + 1; + } + } + addNum(que, cur); + return new int[] { getNum(que), i }; + } + + public static void addNum(LinkedList que, int num) { + if (!que.isEmpty()) { + int cur = 0; + String top = que.pollLast(); + if (top.equals("+") || top.equals("-")) { + que.addLast(top); + } else { + cur = Integer.valueOf(que.pollLast()); + num = top.equals("*") ? (cur * num) : (cur / num); + } + } + que.addLast(String.valueOf(num)); + } + + public static int getNum(LinkedList que) { + int res = 0; + boolean add = true; + String cur = null; + int num = 0; + while (!que.isEmpty()) { + cur = que.pollFirst(); + if (cur.equals("+")) { + add = true; + } else if (cur.equals("-")) { + add = false; + } else { + num = Integer.valueOf(cur); + res += add ? num : (-num); + } + } + return res; + } + +} diff --git a/大厂刷题班/class08/Code02_ContainerWithMostWater.java b/大厂刷题班/class08/Code02_ContainerWithMostWater.java new file mode 100644 index 0000000..9053a24 --- /dev/null +++ b/大厂刷题班/class08/Code02_ContainerWithMostWater.java @@ -0,0 +1,32 @@ +package class08; + +// 本题测试链接 : https://leetcode.com/problems/container-with-most-water/ +public class Code02_ContainerWithMostWater { + + public static int maxArea1(int[] h) { + int max = 0; + int N = h.length; + for (int i = 0; i < N; i++) { // h[i] + for (int j = i + 1; j < N; j++) { // h[j] + max = Math.max(max, Math.min(h[i], h[j]) * (j - i)); + } + } + return max; + } + + public static int maxArea2(int[] h) { + int max = 0; + int l = 0; + int r = h.length - 1; + while (l < r) { + max = Math.max(max, Math.min(h[l], h[r]) * (r - l)); + if (h[l] > h[r]) { + r--; + } else { + l++; + } + } + return max; + } + +} diff --git a/大厂刷题班/class08/Code03_FindWordInMatrix.java b/大厂刷题班/class08/Code03_FindWordInMatrix.java new file mode 100644 index 0000000..e985c43 --- /dev/null +++ b/大厂刷题班/class08/Code03_FindWordInMatrix.java @@ -0,0 +1,159 @@ +package class08; + +/* + * 给定一个char[][] matrix,也就是char类型的二维数组,再给定一个字符串word, + * 可以从任何一个某个位置出发,可以走上下左右,能不能找到word? + * 比如: + * char[][] m = { + * { 'a', 'b', 'z' }, + * { 'c', 'd', 'o' }, + * { 'f', 'e', 'o' }, + * }; + * word = "zooe" + * 是可以找到的 + * + * 设定1:可以走重复路的情况下,返回能不能找到 + * 比如,word = "zoooz",是可以找到的,z -> o -> o -> o -> z,因为允许走一条路径中已经走过的字符 + * + * 设定2:不可以走重复路的情况下,返回能不能找到 + * 比如,word = "zoooz",是不可以找到的,因为允许走一条路径中已经走过的字符不能重复走 + * + * 写出两种设定下的code + * + * */ +public class Code03_FindWordInMatrix { + + // 可以走重复的设定 + public static boolean findWord1(char[][] m, String word) { + if (word == null || word.equals("")) { + return true; + } + if (m == null || m.length == 0 || m[0] == null || m[0].length == 0) { + return false; + } + char[] w = word.toCharArray(); + int N = m.length; + int M = m[0].length; + int len = w.length; + // dp[i][j][k]表示:必须以m[i][j]这个字符结尾的情况下,能不能找到w[0...k]这个前缀串 + boolean[][][] dp = new boolean[N][M][len]; + for (int i = 0; i < N; i++) { + for (int j = 0; j < M; j++) { + dp[i][j][0] = m[i][j] == w[0]; + } + } + for (int k = 1; k < len; k++) { + for (int i = 0; i < N; i++) { + for (int j = 0; j < M; j++) { + dp[i][j][k] = (m[i][j] == w[k] && checkPrevious(dp, i, j, k)); + } + } + } + for (int i = 0; i < N; i++) { + for (int j = 0; j < M; j++) { + if (dp[i][j][len - 1]) { + return true; + } + } + } + return false; + } + + // 可以走重复路 + // 从m[i][j]这个字符出发,能不能找到str[k...]这个后缀串 + public static boolean canLoop(char[][] m, int i, int j, char[] str, int k) { + if (k == str.length) { + return true; + } + if (i == -1 || i == m.length || j == -1 || j == m[0].length || m[i][j] != str[k]) { + return false; + } + // 不越界!m[i][j] == str[k] 对的上的! + // str[k+1....] + boolean ans = false; + if (canLoop(m, i + 1, j, str, k + 1) || canLoop(m, i - 1, j, str, k + 1) || canLoop(m, i, j + 1, str, k + 1) + || canLoop(m, i, j - 1, str, k + 1)) { + ans = true; + } + return ans; + } + + // 不能走重复路 + // 从m[i][j]这个字符出发,能不能找到str[k...]这个后缀串 + public static boolean noLoop(char[][] m, int i, int j, char[] str, int k) { + if (k == str.length) { + return true; + } + if (i == -1 || i == m.length || j == -1 || j == m[0].length || m[i][j] != str[k]) { + return false; + } + // 不越界!也不是回头路!m[i][j] == str[k] 也对的上! + m[i][j] = 0; + boolean ans = false; + if (noLoop(m, i + 1, j, str, k + 1) || noLoop(m, i - 1, j, str, k + 1) || noLoop(m, i, j + 1, str, k + 1) + || noLoop(m, i, j - 1, str, k + 1)) { + ans = true; + } + m[i][j] = str[k]; + return ans; + } + + public static boolean checkPrevious(boolean[][][] dp, int i, int j, int k) { + boolean up = i > 0 ? (dp[i - 1][j][k - 1]) : false; + boolean down = i < dp.length - 1 ? (dp[i + 1][j][k - 1]) : false; + boolean left = j > 0 ? (dp[i][j - 1][k - 1]) : false; + boolean right = j < dp[0].length - 1 ? (dp[i][j + 1][k - 1]) : false; + return up || down || left || right; + } + + // 不可以走重复路的设定 + public static boolean findWord2(char[][] m, String word) { + if (word == null || word.equals("")) { + return true; + } + if (m == null || m.length == 0 || m[0] == null || m[0].length == 0) { + return false; + } + char[] w = word.toCharArray(); + for (int i = 0; i < m.length; i++) { + for (int j = 0; j < m[0].length; j++) { + if (process(m, i, j, w, 0)) { + return true; + } + } + } + return false; + } + + // 从m[i][j]这个字符出发,能不能找到w[k...]这个后缀串 + public static boolean process(char[][] m, int i, int j, char[] str, int k) { + if (k == str.length) { + return true; + } + if (i == -1 || i == m.length || j == -1 || j == m[0].length || m[i][j] == 0 || m[i][j] != str[k]) { + return false; + } + m[i][j] = 0; + boolean ans = false; + if (process(m, i + 1, j, str, k + 1) || process(m, i - 1, j, str, k + 1) || process(m, i, j + 1, str, k + 1) + || process(m, i, j - 1, str, k + 1)) { + ans = true; + } + m[i][j] = str[k]; + return ans; + } + + public static void main(String[] args) { + char[][] m = { { 'a', 'b', 'z' }, { 'c', 'd', 'o' }, { 'f', 'e', 'o' }, }; + String word1 = "zoooz"; + String word2 = "zoo"; + // 可以走重复路的设定 + System.out.println(findWord1(m, word1)); + System.out.println(findWord1(m, word2)); + // 不可以走重复路的设定 + System.out.println(findWord2(m, word1)); + System.out.println(findWord2(m, word2)); + + } + +} diff --git a/大厂刷题班/class08/Code04_SnakeGame.java b/大厂刷题班/class08/Code04_SnakeGame.java new file mode 100644 index 0000000..1aa5316 --- /dev/null +++ b/大厂刷题班/class08/Code04_SnakeGame.java @@ -0,0 +1,188 @@ +package class08; + +import java.util.Arrays; + +public class Code04_SnakeGame { + + public static int walk1(int[][] matrix) { + if (matrix == null || matrix.length == 0 || matrix[0].length == 0) { + return 0; + } + int res = Integer.MIN_VALUE; + for (int i = 0; i < matrix.length; i++) { + for (int j = 0; j < matrix[0].length; j++) { + int[] ans = process(matrix, i, j); + res = Math.max(res, Math.max(ans[0], ans[1])); + } + } + return res; + } + + public static int zuo(int[][] matrix) { + if (matrix == null || matrix.length == 0 || matrix[0].length == 0) { + return 0; + } + int ans = 0; + for (int i = 0; i < matrix.length; i++) { + for (int j = 0; j < matrix[0].length; j++) { + Info cur = f(matrix, i, j); + ans = Math.max(ans, Math.max(cur.no, cur.yes)); + } + } + return ans; + } + + public static class Info { + public int no; + public int yes; + + public Info(int n, int y) { + no = n; + yes = y; + } + } + + // 蛇从某一个最左列,且最优的空降点降落 + // 沿途走到(i,j)必须停! + // 返回,一次能力也不用,获得的最大成长值 + // 返回,用了一次能力,获得的最大成长值 + // 如果蛇从某一个最左列,且最优的空降点降落,不用能力,怎么都到不了(i,j),那么no = -1 + // 如果蛇从某一个最左列,且最优的空降点降落,用了一次能力,怎么都到不了(i,j),那么yes = -1 + public static Info f(int[][] matrix, int i, int j) { + if (j == 0) { // 最左列 + int no = Math.max(matrix[i][0], -1); + int yes = Math.max(-matrix[i][0], -1); + return new Info(no, yes); + } + // j > 0 不在最左列 + int preNo = -1; + int preYes = -1; + Info pre = f(matrix, i, j - 1); + preNo = Math.max(pre.no, preNo); + preYes = Math.max(pre.yes, preYes); + if (i > 0) { + pre = f(matrix, i - 1, j - 1); + preNo = Math.max(pre.no, preNo); + preYes = Math.max(pre.yes, preYes); + } + if (i < matrix.length - 1) { + pre = f(matrix, i + 1, j - 1); + preNo = Math.max(pre.no, preNo); + preYes = Math.max(pre.yes, preYes); + } + int no = preNo == -1 ? -1 : (Math.max(-1, preNo + matrix[i][j])); + // 能力只有一次,是之前用的! + int p1 = preYes == -1 ? -1 : (Math.max(-1, preYes + matrix[i][j])); + // 能力只有一次,就当前用! + int p2 = preNo == -1 ? -1 : (Math.max(-1, preNo - matrix[i][j])); + int yes = Math.max(Math.max(p1, p2), -1); + return new Info(no, yes); + } + + // 从假想的最优左侧到达(i,j)的旅程中 + // 0) 在没有使用过能力的情况下,返回路径最大和,没有可能到达的话,返回负 + // 1) 在使用过能力的情况下,返回路径最大和,没有可能到达的话,返回负 + public static int[] process(int[][] m, int i, int j) { + if (j == 0) { // (i,j)就是最左侧的位置 + return new int[] { m[i][j], -m[i][j] }; + } + int[] preAns = process(m, i, j - 1); + // 所有的路中,完全不使用能力的情况下,能够到达的最好长度是多大 + int preUnuse = preAns[0]; + // 所有的路中,使用过一次能力的情况下,能够到达的最好长度是多大 + int preUse = preAns[1]; + if (i - 1 >= 0) { + preAns = process(m, i - 1, j - 1); + preUnuse = Math.max(preUnuse, preAns[0]); + preUse = Math.max(preUse, preAns[1]); + } + if (i + 1 < m.length) { + preAns = process(m, i + 1, j - 1); + preUnuse = Math.max(preUnuse, preAns[0]); + preUse = Math.max(preUse, preAns[1]); + } + // preUnuse 之前旅程,没用过能力 + // preUse 之前旅程,已经使用过能力了 + int no = -1; // 之前没使用过能力,当前位置也不使用能力,的最优解 + int yes = -1; // 不管是之前使用能力,还是当前使用了能力,请保证能力只使用一次,最优解 + if (preUnuse >= 0) { + no = m[i][j] + preUnuse; + yes = -m[i][j] + preUnuse; + } + if (preUse >= 0) { + yes = Math.max(yes, m[i][j] + preUse); + } + return new int[] { no, yes }; + } + + public static int walk2(int[][] matrix) { + if (matrix == null || matrix.length == 0 || matrix[0].length == 0) { + return 0; + } + int max = Integer.MIN_VALUE; + int[][][] dp = new int[matrix.length][matrix[0].length][2]; + for (int i = 0; i < dp.length; i++) { + dp[i][0][0] = matrix[i][0]; + dp[i][0][1] = -matrix[i][0]; + max = Math.max(max, Math.max(dp[i][0][0], dp[i][0][1])); + } + for (int j = 1; j < matrix[0].length; j++) { + for (int i = 0; i < matrix.length; i++) { + int preUnuse = dp[i][j - 1][0]; + int preUse = dp[i][j - 1][1]; + if (i - 1 >= 0) { + preUnuse = Math.max(preUnuse, dp[i - 1][j - 1][0]); + preUse = Math.max(preUse, dp[i - 1][j - 1][1]); + } + if (i + 1 < matrix.length) { + preUnuse = Math.max(preUnuse, dp[i + 1][j - 1][0]); + preUse = Math.max(preUse, dp[i + 1][j - 1][1]); + } + dp[i][j][0] = -1; + dp[i][j][1] = -1; + if (preUnuse >= 0) { + dp[i][j][0] = matrix[i][j] + preUnuse; + dp[i][j][1] = -matrix[i][j] + preUnuse; + } + if (preUse >= 0) { + dp[i][j][1] = Math.max(dp[i][j][1], matrix[i][j] + preUse); + } + max = Math.max(max, Math.max(dp[i][j][0], dp[i][j][1])); + } + } + return max; + } + + public static int[][] generateRandomArray(int row, int col, int value) { + int[][] arr = new int[row][col]; + for (int i = 0; i < arr.length; i++) { + for (int j = 0; j < arr[0].length; j++) { + arr[i][j] = (int) (Math.random() * value) * (Math.random() > 0.5 ? -1 : 1); + } + } + return arr; + } + + public static void main(String[] args) { + int N = 7; + int M = 7; + int V = 10; + int times = 1000000; + for (int i = 0; i < times; i++) { + int r = (int) (Math.random() * (N + 1)); + int c = (int) (Math.random() * (M + 1)); + int[][] matrix = generateRandomArray(r, c, V); + int ans1 = zuo(matrix); + int ans2 = walk2(matrix); + if (ans1 != ans2) { + for (int j = 0; j < matrix.length; j++) { + System.out.println(Arrays.toString(matrix[j])); + } + System.out.println("Oops ans1: " + ans1 + " ans2:" + ans2); + break; + } + } + System.out.println("finish"); + } + +} diff --git a/大厂刷题班/class09/Code01_LightProblem.java b/大厂刷题班/class09/Code01_LightProblem.java new file mode 100644 index 0000000..c42f962 --- /dev/null +++ b/大厂刷题班/class09/Code01_LightProblem.java @@ -0,0 +1,384 @@ +package class09; + +/* + * 给定一个数组arr,长度为N,arr中的值不是0就是1 + * arr[i]表示第i栈灯的状态,0代表灭灯,1代表亮灯 + * 每一栈灯都有开关,但是按下i号灯的开关,会同时改变i-1、i、i+2栈灯的状态 + * 问题一: + * 如果N栈灯排成一条直线,请问最少按下多少次开关,能让灯都亮起来 + * 排成一条直线说明: + * i为中间位置时,i号灯的开关能影响i-1、i和i+1 + * 0号灯的开关只能影响0和1位置的灯 + * N-1号灯的开关只能影响N-2和N-1位置的灯 + * + * 问题二: + * 如果N栈灯排成一个圈,请问最少按下多少次开关,能让灯都亮起来 + * 排成一个圈说明: + * i为中间位置时,i号灯的开关能影响i-1、i和i+1 + * 0号灯的开关能影响N-1、0和1位置的灯 + * N-1号灯的开关能影响N-2、N-1和0位置的灯 + * + * */ +public class Code01_LightProblem { + + // 无环改灯问题的暴力版本 + public static int noLoopRight(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + if (arr.length == 1) { + return arr[0] == 1 ? 0 : 1; + } + if (arr.length == 2) { + return arr[0] != arr[1] ? Integer.MAX_VALUE : (arr[0] ^ 1); + } + return f1(arr, 0); + } + + public static int f1(int[] arr, int i) { + if (i == arr.length) { + return valid(arr) ? 0 : Integer.MAX_VALUE; + } + int p1 = f1(arr, i + 1); + change1(arr, i); + int p2 = f1(arr, i + 1); + change1(arr, i); + p2 = (p2 == Integer.MAX_VALUE) ? p2 : (p2 + 1); + return Math.min(p1, p2); + } + + public static void change1(int[] arr, int i) { + if (i == 0) { + arr[0] ^= 1; + arr[1] ^= 1; + } else if (i == arr.length - 1) { + arr[i - 1] ^= 1; + arr[i] ^= 1; + } else { + arr[i - 1] ^= 1; + arr[i] ^= 1; + arr[i + 1] ^= 1; + } + } + + public static boolean valid(int[] arr) { + for (int i = 0; i < arr.length; i++) { + if (arr[i] == 0) { + return false; + } + } + return true; + } + + // 无环改灯问题的递归版本 + public static int noLoopMinStep1(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + if (arr.length == 1) { + return arr[0] ^ 1; + } + if (arr.length == 2) { + return arr[0] != arr[1] ? Integer.MAX_VALUE : (arr[0] ^ 1); + } + // 不变0位置的状态 + int p1 = process1(arr, 2, arr[0], arr[1]); + // 改变0位置的状态 + int p2 = process1(arr, 2, arr[0] ^ 1, arr[1] ^ 1); + if (p2 != Integer.MAX_VALUE) { + p2++; + } + return Math.min(p1, p2); + } + + // 当前在哪个位置上,做选择,nextIndex - 1 = cur ,当前! + // cur - 1 preStatus + // cur curStatus + // 0....cur-2 全亮的! + public static int process1(int[] arr, int nextIndex, int preStatus, int curStatus) { + if (nextIndex == arr.length) { // 当前来到最后一个开关的位置 + return preStatus != curStatus ? (Integer.MAX_VALUE) : (curStatus ^ 1); + } + // 没到最后一个按钮呢! + // i < arr.length + if (preStatus == 0) { // 一定要改变 + curStatus ^= 1; + int cur = arr[nextIndex] ^ 1; + int next = process1(arr, nextIndex + 1, curStatus, cur); + return next == Integer.MAX_VALUE ? next : (next + 1); + } else { // 一定不能改变 + return process1(arr, nextIndex + 1, curStatus, arr[nextIndex]); + } + } + + // 无环改灯问题的迭代版本 + public static int noLoopMinStep2(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + if (arr.length == 1) { + return arr[0] == 1 ? 0 : 1; + } + if (arr.length == 2) { + return arr[0] != arr[1] ? Integer.MAX_VALUE : (arr[0] ^ 1); + } + int p1 = traceNoLoop(arr, arr[0], arr[1]); + int p2 = traceNoLoop(arr, arr[0] ^ 1, arr[1] ^ 1); + p2 = (p2 == Integer.MAX_VALUE) ? p2 : (p2 + 1); + return Math.min(p1, p2); + } + + public static int traceNoLoop(int[] arr, int preStatus, int curStatus) { + int i = 2; + int op = 0; + while (i != arr.length) { + if (preStatus == 0) { + op++; + preStatus = curStatus ^ 1; + curStatus = arr[i++] ^ 1; + } else { + preStatus = curStatus; + curStatus = arr[i++]; + } + } + return (preStatus != curStatus) ? Integer.MAX_VALUE : (op + (curStatus ^ 1)); + } + + // 有环改灯问题的暴力版本 + public static int loopRight(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + if (arr.length == 1) { + return arr[0] == 1 ? 0 : 1; + } + if (arr.length == 2) { + return arr[0] != arr[1] ? Integer.MAX_VALUE : (arr[0] ^ 1); + } + return f2(arr, 0); + } + + public static int f2(int[] arr, int i) { + if (i == arr.length) { + return valid(arr) ? 0 : Integer.MAX_VALUE; + } + int p1 = f2(arr, i + 1); + change2(arr, i); + int p2 = f2(arr, i + 1); + change2(arr, i); + p2 = (p2 == Integer.MAX_VALUE) ? p2 : (p2 + 1); + return Math.min(p1, p2); + } + + public static void change2(int[] arr, int i) { + arr[lastIndex(i, arr.length)] ^= 1; + arr[i] ^= 1; + arr[nextIndex(i, arr.length)] ^= 1; + } + + public static int lastIndex(int i, int N) { + return i == 0 ? (N - 1) : (i - 1); + } + + public static int nextIndex(int i, int N) { + return i == N - 1 ? 0 : (i + 1); + } + + // 有环改灯问题的递归版本 + public static int loopMinStep1(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + if (arr.length == 1) { + return arr[0] == 1 ? 0 : 1; + } + if (arr.length == 2) { + return arr[0] != arr[1] ? Integer.MAX_VALUE : (arr[0] ^ 1); + } + if (arr.length == 3) { + return (arr[0] != arr[1] || arr[0] != arr[2]) ? Integer.MAX_VALUE : (arr[0] ^ 1); + } + // 0不变,1不变 + int p1 = process2(arr, 3, arr[1], arr[2], arr[arr.length - 1], arr[0]); + // 0改变,1不变 + int p2 = process2(arr, 3, arr[1] ^ 1, arr[2], arr[arr.length - 1] ^ 1, arr[0] ^ 1); + // 0不变,1改变 + int p3 = process2(arr, 3, arr[1] ^ 1, arr[2] ^ 1, arr[arr.length - 1], arr[0] ^ 1); + // 0改变,1改变 + int p4 = process2(arr, 3, arr[1], arr[2] ^ 1, arr[arr.length - 1] ^ 1, arr[0]); + p2 = p2 != Integer.MAX_VALUE ? (p2 + 1) : p2; + p3 = p3 != Integer.MAX_VALUE ? (p3 + 1) : p3; + p4 = p4 != Integer.MAX_VALUE ? (p4 + 2) : p4; + return Math.min(Math.min(p1, p2), Math.min(p3, p4)); + } + + + // 下一个位置是,nextIndex + // 当前位置是,nextIndex - 1 -> curIndex + // 上一个位置是, nextIndex - 2 -> preIndex preStatus + // 当前位置是,nextIndex - 1, curStatus + // endStatus, N-1位置的状态 + // firstStatus, 0位置的状态 + // 返回,让所有灯都亮,至少按下几次按钮 + + // 当前来到的位置(nextIndex - 1),一定不能是1!至少从2开始 + // nextIndex >= 3 + public static int process2(int[] arr, + int nextIndex, int preStatus, int curStatus, + int endStatus, int firstStatus) { + + if (nextIndex == arr.length) { // 最后一按钮! + return (endStatus != firstStatus || endStatus != preStatus) ? Integer.MAX_VALUE : (endStatus ^ 1); + } + // 当前位置,nextIndex - 1 + // 当前的状态,叫curStatus + // 如果不按下按钮,下一步的preStatus, curStatus + // 如果按下按钮,下一步的preStatus, curStatus ^ 1 + // 如果不按下按钮,下一步的curStatus, arr[nextIndex] + // 如果按下按钮,下一步的curStatus, arr[nextIndex] ^ 1 + int noNextPreStatus = 0; + int yesNextPreStatus = 0; + int noNextCurStatus =0; + int yesNextCurStatus = 0; + int noEndStatus = 0; + int yesEndStatus = 0; + if(nextIndex < arr.length - 1) {// 当前没来到N-2位置 + noNextPreStatus = curStatus; + yesNextPreStatus = curStatus ^ 1; + noNextCurStatus = arr[nextIndex]; + yesNextCurStatus = arr[nextIndex] ^ 1; + } else if(nextIndex == arr.length - 1) {// 当前来到的就是N-2位置 + noNextPreStatus = curStatus; + yesNextPreStatus = curStatus ^ 1; + noNextCurStatus = endStatus; + yesNextCurStatus = endStatus ^ 1; + noEndStatus = endStatus; + yesEndStatus = endStatus ^ 1; + } + if(preStatus == 0) { + int next = process2(arr, nextIndex + 1, yesNextPreStatus, yesNextCurStatus, + nextIndex == arr.length - 1 ? yesEndStatus : endStatus, firstStatus); + return next == Integer.MAX_VALUE ? next : (next + 1); + }else { + return process2(arr, nextIndex + 1, noNextPreStatus, noNextCurStatus, + nextIndex == arr.length - 1 ? noEndStatus : endStatus, firstStatus); + + } +// int curStay = (nextIndex == arr.length - 1) ? endStatus : arr[nextIndex]; +// int curChange = (nextIndex == arr.length - 1) ? (endStatus ^ 1) : (arr[nextIndex] ^ 1); +// int endChange = (nextIndex == arr.length - 1) ? curChange : endStatus; +// if (preStatus == 0) { +// int next = process2(arr, nextIndex + 1, curStatus ^ 1, curChange, endChange, firstStatus); +// return next == Integer.MAX_VALUE ? next : (next + 1); +// } else { +// return process2(arr, nextIndex + 1, curStatus, curStay, endStatus, firstStatus); +// } + } + + // 有环改灯问题的迭代版本 + public static int loopMinStep2(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + if (arr.length == 1) { + return arr[0] == 1 ? 0 : 1; + } + if (arr.length == 2) { + return arr[0] != arr[1] ? Integer.MAX_VALUE : (arr[0] ^ 1); + } + if (arr.length == 3) { + return (arr[0] != arr[1] || arr[0] != arr[2]) ? Integer.MAX_VALUE : (arr[0] ^ 1); + } + // 0不变,1不变 + int p1 = traceLoop(arr, arr[1], arr[2], arr[arr.length - 1], arr[0]); + // 0改变,1不变 + int p2 = traceLoop(arr, arr[1] ^ 1, arr[2], arr[arr.length - 1] ^ 1, arr[0] ^ 1); + // 0不变,1改变 + int p3 = traceLoop(arr, arr[1] ^ 1, arr[2] ^ 1, arr[arr.length - 1], arr[0] ^ 1); + // 0改变,1改变 + int p4 = traceLoop(arr, arr[1], arr[2] ^ 1, arr[arr.length - 1] ^ 1, arr[0]); + p2 = p2 != Integer.MAX_VALUE ? (p2 + 1) : p2; + p3 = p3 != Integer.MAX_VALUE ? (p3 + 1) : p3; + p4 = p4 != Integer.MAX_VALUE ? (p4 + 2) : p4; + return Math.min(Math.min(p1, p2), Math.min(p3, p4)); + } + + public static int traceLoop(int[] arr, int preStatus, int curStatus, int endStatus, int firstStatus) { + int i = 3; + int op = 0; + while (i < arr.length - 1) { + if (preStatus == 0) { + op++; + preStatus = curStatus ^ 1; + curStatus = (arr[i++] ^ 1); + } else { + preStatus = curStatus; + curStatus = arr[i++]; + } + } + if (preStatus == 0) { + op++; + preStatus = curStatus ^ 1; + endStatus ^= 1; + curStatus = endStatus; + } else { + preStatus = curStatus; + curStatus = endStatus; + } + return (endStatus != firstStatus || endStatus != preStatus) ? Integer.MAX_VALUE : (op + (endStatus ^ 1)); + } + + // 生成长度为len的随机数组,值只有0和1两种值 + public static int[] randomArray(int len) { + int[] arr = new int[len]; + for (int i = 0; i < arr.length; i++) { + arr[i] = (int) (Math.random() * 2); + } + return arr; + } + + public static void main(String[] args) { + System.out.println("如果没有任何Oops打印,说明所有方法都正确"); + System.out.println("test begin"); + int testTime = 20000; + int lenMax = 12; + for (int i = 0; i < testTime; i++) { + int len = (int) (Math.random() * lenMax); + int[] arr = randomArray(len); + int ans1 = noLoopRight(arr); + int ans2 = noLoopMinStep1(arr); + int ans3 = noLoopMinStep2(arr); + if (ans1 != ans2 || ans1 != ans3) { + System.out.println("1 Oops!"); + } + } + for (int i = 0; i < testTime; i++) { + int len = (int) (Math.random() * lenMax); + int[] arr = randomArray(len); + int ans1 = loopRight(arr); + int ans2 = loopMinStep1(arr); + int ans3 = loopMinStep2(arr); + if (ans1 != ans2 || ans1 != ans3) { + System.out.println("2 Oops!"); + } + } + System.out.println("test end"); + + int len = 100000000; + System.out.println("性能测试"); + System.out.println("数组大小:" + len); + int[] arr = randomArray(len); + long start = 0; + long end = 0; + start = System.currentTimeMillis(); + noLoopMinStep2(arr); + end = System.currentTimeMillis(); + System.out.println("noLoopMinStep2 run time: " + (end - start) + "(ms)"); + + start = System.currentTimeMillis(); + loopMinStep2(arr); + end = System.currentTimeMillis(); + System.out.println("loopMinStep2 run time: " + (end - start) + "(ms)"); + } + +} diff --git a/大厂刷题班/class09/Code02_RemoveInvalidParentheses.java b/大厂刷题班/class09/Code02_RemoveInvalidParentheses.java new file mode 100644 index 0000000..f13423e --- /dev/null +++ b/大厂刷题班/class09/Code02_RemoveInvalidParentheses.java @@ -0,0 +1,64 @@ +package class09; + +import java.util.ArrayList; +import java.util.List; + +// 测试链接 : https://leetcode.com/problems/remove-invalid-parentheses/ +public class Code02_RemoveInvalidParentheses { + + // 来自leetcode投票第一的答案,实现非常好,我们来赏析一下 + public static List removeInvalidParentheses(String s) { + List ans = new ArrayList<>(); + remove(s, ans, 0, 0, new char[] { '(', ')' }); + return ans; + } + + // modifyIndex <= checkIndex + // 只查s[checkIndex....]的部分,因为之前的一定已经调整对了 + // 但是之前的部分是怎么调整对的,调整到了哪?就是modifyIndex + // 比如: + // ( ( ) ( ) ) ) ... + // 0 1 2 3 4 5 6 + // 一开始当然checkIndex = 0,modifyIndex = 0 + // 当查到6的时候,发现不对了, + // 然后可以去掉2位置、4位置的 ),都可以 + // 如果去掉2位置的 ), 那么下一步就是 + // ( ( ( ) ) ) ... + // 0 1 2 3 4 5 6 + // checkIndex = 6 ,modifyIndex = 2 + // 如果去掉4位置的 ), 那么下一步就是 + // ( ( ) ( ) ) ... + // 0 1 2 3 4 5 6 + // checkIndex = 6 ,modifyIndex = 4 + // 也就是说, + // checkIndex和modifyIndex,分别表示查的开始 和 调的开始,之前的都不用管了 par ( ) + public static void remove(String s, List ans, int checkIndex, int deleteIndex, char[] par) { + for (int count = 0, i = checkIndex; i < s.length(); i++) { + if (s.charAt(i) == par[0]) { + count++; + } + if (s.charAt(i) == par[1]) { + count--; + } + // i check计数<0的第一个位置 + if (count < 0) { + for (int j = deleteIndex; j <= i; ++j) { + // 比如 + if (s.charAt(j) == par[1] && (j == deleteIndex || s.charAt(j - 1) != par[1])) { + remove( + s.substring(0, j) + s.substring(j + 1, s.length()), + ans, i, j, par); + } + } + return; + } + } + String reversed = new StringBuilder(s).reverse().toString(); + if (par[0] == '(') { + remove(reversed, ans, 0, 0, new char[] { ')', '(' }); + } else { + ans.add(reversed); + } + } + +} diff --git a/大厂刷题班/class09/Code03_LIS.java b/大厂刷题班/class09/Code03_LIS.java new file mode 100644 index 0000000..42e197b --- /dev/null +++ b/大厂刷题班/class09/Code03_LIS.java @@ -0,0 +1,55 @@ +package class09; + +// 本题测试链接 : https://leetcode.com/problems/longest-increasing-subsequence +public class Code03_LIS { + + public static int lengthOfLIS(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + // ends数组 + // ends[i]表示 : 目前所有长度为i+1的递增子序列的最小结尾 + int[] ends = new int[arr.length]; + // 根据含义, 一开始ends[0] = arr[0] + ends[0] = arr[0]; + // ends有效区范围是0...right,right往右为无效区 + // 所以一开始right = 0, 表示有效区只有0...0范围 + int right = 0; + // 最长递增子序列的长度 + // 全局变量,抓取每一步的答案,取最大的结果 + int max = 1; + for (int i = 1; i < arr.length; i++) { + int l = 0; + int r = right; + // 在ends[l...r]范围上二分 + // 如果 当前数(arr[i]) > ends[m],砍掉左侧 + // 如果 当前数(arr[i]) <= ends[m],砍掉右侧 + // 整个二分就是在ends里寻找 >= 当前数(arr[i])的最左位置 + // 就是从while里面出来时,l所在的位置。 + // 如果ends中不存在 >= 当前数(arr[i])的情况,将返回有效区的越界位置 + // 也就是从while里面出来时,l所在的位置,是有效区的越界位置 + // 比如 : ends = { 3, 5, 9, 12, 再往右无效} + // 如果当前数为8, 从while里面出来时,l将来到2位置 + // 比如 : ends = { 3, 5, 9, 12, 再往右无效} + // 如果当前数为13, 从while里面出来时,l将来到有效区的越界位置,4位置 + while (l <= r) { + int m = (l + r) / 2; + if (arr[i] > ends[m]) { + l = m + 1; + } else { + r = m - 1; + } + } + // 从while里面出来,看l的位置 + // 如果l比right大,说明扩充了有效区,那么right变量要随之变大 + // 如果l不比right大,说明l没有来到有效区的越界位置,right不变 + right = Math.max(right, l); + // l的位置,就是当前数应该填到ends数组里的位置 + ends[l] = arr[i]; + // 更新全局变量 + max = Math.max(max, l + 1); + } + return max; + } + +} \ No newline at end of file diff --git a/大厂刷题班/class09/Code04_EnvelopesProblem.java b/大厂刷题班/class09/Code04_EnvelopesProblem.java new file mode 100644 index 0000000..ea22dd4 --- /dev/null +++ b/大厂刷题班/class09/Code04_EnvelopesProblem.java @@ -0,0 +1,60 @@ +package class09; + +import java.util.Arrays; +import java.util.Comparator; + +// 本题测试链接 : https://leetcode.com/problems/russian-doll-envelopes/ +public class Code04_EnvelopesProblem { + + public static int maxEnvelopes(int[][] matrix) { + Envelope[] arr = sort(matrix); + int[] ends = new int[matrix.length]; + ends[0] = arr[0].h; + int right = 0; + int l = 0; + int r = 0; + int m = 0; + for (int i = 1; i < arr.length; i++) { + l = 0; + r = right; + while (l <= r) { + m = (l + r) / 2; + if (arr[i].h > ends[m]) { + l = m + 1; + } else { + r = m - 1; + } + } + right = Math.max(right, l); + ends[l] = arr[i].h; + } + return right + 1; + } + + public static class Envelope { + public int l; + public int h; + + public Envelope(int weight, int hight) { + l = weight; + h = hight; + } + } + + public static class EnvelopeComparator implements Comparator { + @Override + public int compare(Envelope o1, Envelope o2) { + return o1.l != o2.l ? o1.l - o2.l : o2.h - o1.h; + } + } + + public static Envelope[] sort(int[][] matrix) { + Envelope[] res = new Envelope[matrix.length]; + for (int i = 0; i < matrix.length; i++) { + res[i] = new Envelope(matrix[i][0], matrix[i][1]); + } + Arrays.sort(res, new EnvelopeComparator()); + return res; + } + +} diff --git a/大厂刷题班/class09/Code05_IsStepSum.java b/大厂刷题班/class09/Code05_IsStepSum.java new file mode 100644 index 0000000..5bdd807 --- /dev/null +++ b/大厂刷题班/class09/Code05_IsStepSum.java @@ -0,0 +1,58 @@ +package class09; + +import java.util.HashMap; + +public class Code05_IsStepSum { + + public static boolean isStepSum(int stepSum) { + int L = 0; + int R = stepSum; + int M = 0; + int cur = 0; + while (L <= R) { + M = L + ((R - L) >> 1); + cur = stepSum(M); + if (cur == stepSum) { + return true; + } else if (cur < stepSum) { + L = M + 1; + } else { + R = M - 1; + } + } + return false; + } + + public static int stepSum(int num) { + int sum = 0; + while (num != 0) { + sum += num; + num /= 10; + } + return sum; + } + + // for test + public static HashMap generateStepSumNumberMap(int numMax) { + HashMap map = new HashMap<>(); + for (int i = 0; i <= numMax; i++) { + map.put(stepSum(i), i); + } + return map; + } + + // for test + public static void main(String[] args) { + int max = 1000000; + int maxStepSum = stepSum(max); + HashMap ans = generateStepSumNumberMap(max); + System.out.println("测试开始"); + for (int i = 0; i <= maxStepSum; i++) { + if (isStepSum(i) ^ ans.containsKey(i)) { + System.out.println("出错了!"); + } + } + System.out.println("测试结束"); + } + +} diff --git a/大厂刷题班/class10/Code01_JumpGame.java b/大厂刷题班/class10/Code01_JumpGame.java new file mode 100644 index 0000000..bcb2fe1 --- /dev/null +++ b/大厂刷题班/class10/Code01_JumpGame.java @@ -0,0 +1,23 @@ +package class10; + +// 本题测试链接 : https://leetcode.com/problems/jump-game-ii/ +public class Code01_JumpGame { + + public static int jump(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + int step = 0; + int cur = 0; + int next = 0; + for (int i = 0; i < arr.length; i++) { + if (cur < i) { + step++; + cur = next; + } + next = Math.max(next, i + arr[i]); + } + return step; + } + +} diff --git a/大厂刷题班/class10/Code02_TopK.java b/大厂刷题班/class10/Code02_TopK.java new file mode 100644 index 0000000..d563da6 --- /dev/null +++ b/大厂刷题班/class10/Code02_TopK.java @@ -0,0 +1,154 @@ +package class10; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.TreeSet; + +// 本题测试链接:https://www.lintcode.com/problem/top-k-frequent-words-ii/ +// 以上的代码不要粘贴, 把以下的代码粘贴进java环境编辑器 +// 把类名和构造方法名改成TopK, 可以直接通过 +public class Code02_TopK { + + private Node[] heap; + private int heapSize; + // 词频表 key abc value (abc,7) + private HashMap strNodeMap; + private HashMap nodeIndexMap; + private NodeHeapComp comp; + private TreeSet treeSet; + + public Code02_TopK(int K) { + heap = new Node[K]; + heapSize = 0; + strNodeMap = new HashMap(); + nodeIndexMap = new HashMap(); + comp = new NodeHeapComp(); + treeSet = new TreeSet<>(new NodeTreeSetComp()); + } + + public static class Node { + public String str; + public int times; + + public Node(String s, int t) { + str = s; + times = t; + } + } + + public static class NodeHeapComp implements Comparator { + + @Override + public int compare(Node o1, Node o2) { + return o1.times != o2.times ? (o1.times - o2.times) : (o2.str.compareTo(o1.str)); + } + + } + + public static class NodeTreeSetComp implements Comparator { + + @Override + public int compare(Node o1, Node o2) { + return o1.times != o2.times ? (o2.times - o1.times) : (o1.str.compareTo(o2.str)); + } + + } + + public void add(String str) { + if (heap.length == 0) { + return; + } + // str 找到对应节点 curNode + Node curNode = null; + // 对应节点 curNode 在堆上的位置 + int preIndex = -1; + if (!strNodeMap.containsKey(str)) { + curNode = new Node(str, 1); + strNodeMap.put(str, curNode); + nodeIndexMap.put(curNode, -1); + } else { + curNode = strNodeMap.get(str); + // 要在time++之前,先在treeSet中删掉 + // 原因是因为一但times++,curNode在treeSet中的排序就失效了 + // 这种失效会导致整棵treeSet出现问题 + if (treeSet.contains(curNode)) { + treeSet.remove(curNode); + } + curNode.times++; + preIndex = nodeIndexMap.get(curNode); + } + if (preIndex == -1) { + if (heapSize == heap.length) { + if (comp.compare(heap[0], curNode) < 0) { + treeSet.remove(heap[0]); + treeSet.add(curNode); + nodeIndexMap.put(heap[0], -1); + nodeIndexMap.put(curNode, 0); + heap[0] = curNode; + heapify(0, heapSize); + } + } else { + treeSet.add(curNode); + nodeIndexMap.put(curNode, heapSize); + heap[heapSize] = curNode; + heapInsert(heapSize++); + } + } else { + treeSet.add(curNode); + heapify(preIndex, heapSize); + } + } + + public List topk() { + ArrayList ans = new ArrayList<>(); + for (Node node : treeSet) { + ans.add(node.str); + } + return ans; + } + + private void heapInsert(int index) { + while (index != 0) { + int parent = (index - 1) / 2; + if (comp.compare(heap[index], heap[parent]) < 0) { + swap(parent, index); + index = parent; + } else { + break; + } + } + } + + private void heapify(int index, int heapSize) { + int l = index * 2 + 1; + int r = index * 2 + 2; + int smallest = index; + while (l < heapSize) { + if (comp.compare(heap[l], heap[index]) < 0) { + smallest = l; + } + if (r < heapSize && comp.compare(heap[r], heap[smallest]) < 0) { + smallest = r; + } + if (smallest != index) { + swap(smallest, index); + } else { + break; + } + index = smallest; + l = index * 2 + 1; + r = index * 2 + 2; + } + } + + private void swap(int index1, int index2) { + nodeIndexMap.put(heap[index1], index2); + nodeIndexMap.put(heap[index2], index1); + Node tmp = heap[index1]; + heap[index1] = heap[index2]; + heap[index2] = tmp; + } + +} \ No newline at end of file diff --git a/大厂刷题班/class10/Code03_KInversePairs.java b/大厂刷题班/class10/Code03_KInversePairs.java new file mode 100644 index 0000000..79a6775 --- /dev/null +++ b/大厂刷题班/class10/Code03_KInversePairs.java @@ -0,0 +1,43 @@ +package class10; + +// 测试链接 : https://leetcode.com/problems/k-inverse-pairs-array/ +public class Code03_KInversePairs { + + public static int kInversePairs(int n, int k) { + if (n < 1 || k < 0) { + return 0; + } + int[][] dp = new int[n + 1][k + 1]; + dp[0][0] = 1; + int mod = 1000000007; + for (int i = 1; i <= n; i++) { + dp[i][0] = 1; + for (int j = 1; j <= k; j++) { + dp[i][j] = (dp[i][j - 1] + dp[i - 1][j]) % mod; + if (j >= i) { + dp[i][j] = (dp[i][j] - dp[i - 1][j - i] + mod) % mod; + } + } + } + return dp[n][k]; + } + + public static int kInversePairs2(int n, int k) { + if (n < 1 || k < 0) { + return 0; + } + int[][] dp = new int[n + 1][k + 1]; + dp[0][0] = 1; + for (int i = 1; i <= n; i++) { + dp[i][0] = 1; + for (int j = 1; j <= k; j++) { + dp[i][j] = dp[i - 1][j] + dp[i][j - 1]; + if (j >= i) { + dp[i][j] -= dp[i - 1][j - i]; + } + } + } + return dp[n][k]; + } + +} diff --git a/大厂刷题班/class10/Code04_BSTtoDoubleLinkedList.java b/大厂刷题班/class10/Code04_BSTtoDoubleLinkedList.java new file mode 100644 index 0000000..b937b66 --- /dev/null +++ b/大厂刷题班/class10/Code04_BSTtoDoubleLinkedList.java @@ -0,0 +1,57 @@ +package class10; + +// 本题测试链接 : https://leetcode.com/problems/convert-binary-search-tree-to-sorted-doubly-linked-list/ +public class Code04_BSTtoDoubleLinkedList { + + // 提交时不要提交这个类 + public static class Node { + public int value; + public Node left; + public Node right; + + public Node(int data) { + this.value = data; + } + } + + // 提交下面的代码 + public static Node treeToDoublyList(Node head) { + if (head == null) { + return null; + } + Info allInfo = process(head); + allInfo.end.right = allInfo.start; + allInfo.start.left = allInfo.end; + return allInfo.start; + } + + public static class Info { + public Node start; + public Node end; + + public Info(Node start, Node end) { + this.start = start; + this.end = end; + } + } + + public static Info process(Node X) { + if (X == null) { + return new Info(null, null); + } + Info lInfo = process(X.left); + Info rInfo = process(X.right); + if (lInfo.end != null) { + lInfo.end.right = X; + } + X.left = lInfo.end; + X.right = rInfo.start; + if (rInfo.start != null) { + rInfo.start.left = X; + } + // 整体链表的头 lInfo.start != null ? lInfo.start : X + // 整体链表的尾 rInfo.end != null ? rInfo.end : X + return new Info(lInfo.start != null ? lInfo.start : X, rInfo.end != null ? rInfo.end : X); + } + +} \ No newline at end of file diff --git a/大厂刷题班/class10/Code05_BooleanEvaluation.java b/大厂刷题班/class10/Code05_BooleanEvaluation.java new file mode 100644 index 0000000..9219a79 --- /dev/null +++ b/大厂刷题班/class10/Code05_BooleanEvaluation.java @@ -0,0 +1,156 @@ +package class10; + +// 本题测试链接 : https://leetcode-cn.com/problems/boolean-evaluation-lcci/ +public class Code05_BooleanEvaluation { + + public static int countEval0(String express, int desired) { + if (express == null || express.equals("")) { + return 0; + } + char[] exp = express.toCharArray(); + int N = exp.length; + Info[][] dp = new Info[N][N]; + Info allInfo = func(exp, 0, exp.length - 1, dp); + return desired == 1 ? allInfo.t : allInfo.f; + } + + public static class Info { + public int t; + public int f; + + public Info(int tr, int fa) { + t = tr; + f = fa; + } + } + + // 限制: + // L...R上,一定有奇数个字符 + // L位置的字符和R位置的字符,非0即1,不能是逻辑符号! + // 返回str[L...R]这一段,为true的方法数,和false的方法数 + public static Info func(char[] str, int L, int R, Info[][] dp) { + if (dp[L][R] != null) { + return dp[L][R]; + } + int t = 0; + int f = 0; + if (L == R) { + t = str[L] == '1' ? 1 : 0; + f = str[L] == '0' ? 1 : 0; + } else { // L..R >=3 + // 每一个种逻辑符号,split枚举的东西 + // 都去试试最后结合 + for (int split = L + 1; split < R; split += 2) { + Info leftInfo = func(str, L, split - 1, dp); + Info rightInfo = func(str, split + 1, R, dp); + int a = leftInfo.t; + int b = leftInfo.f; + int c = rightInfo.t; + int d = rightInfo.f; + switch (str[split]) { + case '&': + t += a * c; + f += b * c + b * d + a * d; + break; + case '|': + t += a * c + a * d + b * c; + f += b * d; + break; + case '^': + t += a * d + b * c; + f += a * c + b * d; + break; + } + } + + } + dp[L][R] = new Info(t, f); + return dp[L][R]; + } + + public static int countEval1(String express, int desired) { + if (express == null || express.equals("")) { + return 0; + } + char[] exp = express.toCharArray(); + return f(exp, desired, 0, exp.length - 1); + } + + public static int f(char[] str, int desired, int L, int R) { + if (L == R) { + if (str[L] == '1') { + return desired; + } else { + return desired ^ 1; + } + } + int res = 0; + if (desired == 1) { + for (int i = L + 1; i < R; i += 2) { + switch (str[i]) { + case '&': + res += f(str, 1, L, i - 1) * f(str, 1, i + 1, R); + break; + case '|': + res += f(str, 1, L, i - 1) * f(str, 0, i + 1, R); + res += f(str, 0, L, i - 1) * f(str, 1, i + 1, R); + res += f(str, 1, L, i - 1) * f(str, 1, i + 1, R); + break; + case '^': + res += f(str, 1, L, i - 1) * f(str, 0, i + 1, R); + res += f(str, 0, L, i - 1) * f(str, 1, i + 1, R); + break; + } + } + } else { + for (int i = L + 1; i < R; i += 2) { + switch (str[i]) { + case '&': + res += f(str, 0, L, i - 1) * f(str, 1, i + 1, R); + res += f(str, 1, L, i - 1) * f(str, 0, i + 1, R); + res += f(str, 0, L, i - 1) * f(str, 0, i + 1, R); + break; + case '|': + res += f(str, 0, L, i - 1) * f(str, 0, i + 1, R); + break; + case '^': + res += f(str, 1, L, i - 1) * f(str, 1, i + 1, R); + res += f(str, 0, L, i - 1) * f(str, 0, i + 1, R); + break; + } + } + } + return res; + } + + public static int countEval2(String express, int desired) { + if (express == null || express.equals("")) { + return 0; + } + char[] exp = express.toCharArray(); + int N = exp.length; + int[][][] dp = new int[2][N][N]; + dp[0][0][0] = exp[0] == '0' ? 1 : 0; + dp[1][0][0] = dp[0][0][0] ^ 1; + for (int i = 2; i < exp.length; i += 2) { + dp[0][i][i] = exp[i] == '1' ? 0 : 1; + dp[1][i][i] = exp[i] == '0' ? 0 : 1; + for (int j = i - 2; j >= 0; j -= 2) { + for (int k = j; k < i; k += 2) { + if (exp[k + 1] == '&') { + dp[1][j][i] += dp[1][j][k] * dp[1][k + 2][i]; + dp[0][j][i] += (dp[0][j][k] + dp[1][j][k]) * dp[0][k + 2][i] + dp[0][j][k] * dp[1][k + 2][i]; + } else if (exp[k + 1] == '|') { + dp[1][j][i] += (dp[0][j][k] + dp[1][j][k]) * dp[1][k + 2][i] + dp[1][j][k] * dp[0][k + 2][i]; + dp[0][j][i] += dp[0][j][k] * dp[0][k + 2][i]; + } else { + dp[1][j][i] += dp[0][j][k] * dp[1][k + 2][i] + dp[1][j][k] * dp[0][k + 2][i]; + dp[0][j][i] += dp[0][j][k] * dp[0][k + 2][i] + dp[1][j][k] * dp[1][k + 2][i]; + } + } + } + } + return dp[desired][0][N - 1]; + } + +} diff --git a/大厂刷题班/class11/Code01_MinimumInsertionStepsToMakeAStringPalindrome.java b/大厂刷题班/class11/Code01_MinimumInsertionStepsToMakeAStringPalindrome.java new file mode 100644 index 0000000..94e3bd8 --- /dev/null +++ b/大厂刷题班/class11/Code01_MinimumInsertionStepsToMakeAStringPalindrome.java @@ -0,0 +1,173 @@ +package class11; + +import java.util.ArrayList; +import java.util.List; + +// 本题测试链接 : https://leetcode.com/problems/minimum-insertion-steps-to-make-a-string-palindrome/ +public class Code01_MinimumInsertionStepsToMakeAStringPalindrome { + + // 测试链接只测了本题的第一问,直接提交可以通过 + public static int minInsertions(String s) { + if (s == null || s.length() < 2) { + return 0; + } + char[] str = s.toCharArray(); + int N = str.length; + int[][] dp = new int[N][N]; + for (int i = 0; i < N - 1; i++) { + dp[i][i + 1] = str[i] == str[i + 1] ? 0 : 1; + } + for (int i = N - 3; i >= 0; i--) { + for (int j = i + 2; j < N; j++) { + dp[i][j] = Math.min(dp[i][j - 1], dp[i + 1][j]) + 1; + if (str[i] == str[j]) { + dp[i][j] = Math.min(dp[i][j], dp[i + 1][j - 1]); + } + } + } + return dp[0][N - 1]; + } + + // 本题第二问,返回其中一种结果 + public static String minInsertionsOneWay(String s) { + if (s == null || s.length() < 2) { + return s; + } + char[] str = s.toCharArray(); + int N = str.length; + int[][] dp = new int[N][N]; + for (int i = 0; i < N - 1; i++) { + dp[i][i + 1] = str[i] == str[i + 1] ? 0 : 1; + } + for (int i = N - 3; i >= 0; i--) { + for (int j = i + 2; j < N; j++) { + dp[i][j] = Math.min(dp[i][j - 1], dp[i + 1][j]) + 1; + if (str[i] == str[j]) { + dp[i][j] = Math.min(dp[i][j], dp[i + 1][j - 1]); + } + } + } + + int L = 0; + int R = N - 1; + char[] ans = new char[N + dp[L][R]]; + int ansl = 0; + int ansr = ans.length - 1; + while (L < R) { + if (dp[L][R - 1] == dp[L][R] - 1) { + ans[ansl++] = str[R]; + ans[ansr--] = str[R--]; + } else if (dp[L + 1][R] == dp[L][R] - 1) { + ans[ansl++] = str[L]; + ans[ansr--] = str[L++]; + } else { + ans[ansl++] = str[L++]; + ans[ansr--] = str[R--]; + } + } + if (L == R) { + ans[ansl] = str[L]; + } + return String.valueOf(ans); + } + + // 本题第三问,返回所有可能的结果 + public static List minInsertionsAllWays(String s) { + List ans = new ArrayList<>(); + if (s == null || s.length() < 2) { + ans.add(s); + } else { + char[] str = s.toCharArray(); + int N = str.length; + int[][] dp = new int[N][N]; + for (int i = 0; i < N - 1; i++) { + dp[i][i + 1] = str[i] == str[i + 1] ? 0 : 1; + } + for (int i = N - 3; i >= 0; i--) { + for (int j = i + 2; j < N; j++) { + dp[i][j] = Math.min(dp[i][j - 1], dp[i + 1][j]) + 1; + if (str[i] == str[j]) { + dp[i][j] = Math.min(dp[i][j], dp[i + 1][j - 1]); + } + } + } + int M = N + dp[0][N - 1]; + char[] path = new char[M]; + process(str, dp, 0, N - 1, path, 0, M - 1, ans); + } + return ans; + } + + // 当前来到的动态规划中的格子,(L,R) + // path .... [pl....pr] .... + public static void process(char[] str, int[][] dp, int L, int R, char[] path, int pl, int pr, List ans) { + if (L >= R) { // L > R L==R + if (L == R) { + path[pl] = str[L]; + } + ans.add(String.valueOf(path)); + } else { + if (dp[L][R - 1] == dp[L][R] - 1) { + path[pl] = str[R]; + path[pr] = str[R]; + process(str, dp, L, R - 1, path, pl + 1, pr - 1, ans); + } + if (dp[L + 1][R] == dp[L][R] - 1) { + path[pl] = str[L]; + path[pr] = str[L]; + process(str, dp, L + 1, R, path, pl + 1, pr - 1, ans); + } + if (str[L] == str[R] && (L == R - 1 || dp[L + 1][R - 1] == dp[L][R])) { + path[pl] = str[L]; + path[pr] = str[R]; + process(str, dp, L + 1, R - 1, path, pl + 1, pr - 1, ans); + } + } + } + + public static void main(String[] args) { + String s = null; + String ans2 = null; + List ans3 = null; + + System.out.println("本题第二问,返回其中一种结果测试开始"); + s = "mbadm"; + ans2 = minInsertionsOneWay(s); + System.out.println(ans2); + + s = "leetcode"; + ans2 = minInsertionsOneWay(s); + System.out.println(ans2); + + s = "aabaa"; + ans2 = minInsertionsOneWay(s); + System.out.println(ans2); + System.out.println("本题第二问,返回其中一种结果测试结束"); + + System.out.println(); + + System.out.println("本题第三问,返回所有可能的结果测试开始"); + s = "mbadm"; + ans3 = minInsertionsAllWays(s); + for (String way : ans3) { + System.out.println(way); + } + System.out.println(); + + s = "leetcode"; + ans3 = minInsertionsAllWays(s); + for (String way : ans3) { + System.out.println(way); + } + System.out.println(); + + s = "aabaa"; + ans3 = minInsertionsAllWays(s); + for (String way : ans3) { + System.out.println(way); + } + System.out.println(); + System.out.println("本题第三问,返回所有可能的结果测试结束"); + } + +} diff --git a/大厂刷题班/class11/Code02_PalindromePartitioningII.java b/大厂刷题班/class11/Code02_PalindromePartitioningII.java new file mode 100644 index 0000000..08633db --- /dev/null +++ b/大厂刷题班/class11/Code02_PalindromePartitioningII.java @@ -0,0 +1,207 @@ +package class11; + +import java.util.ArrayList; +import java.util.List; + +// 本题测试链接 : https://leetcode.com/problems/palindrome-partitioning-ii/ +public class Code02_PalindromePartitioningII { + + // 测试链接只测了本题的第一问,直接提交可以通过 + public static int minCut(String s) { + if (s == null || s.length() < 2) { + return 0; + } + char[] str = s.toCharArray(); + int N = str.length; + boolean[][] checkMap = createCheckMap(str, N); + int[] dp = new int[N + 1]; + dp[N] = 0; + for (int i = N - 1; i >= 0; i--) { + if (checkMap[i][N - 1]) { + dp[i] = 1; + } else { + int next = Integer.MAX_VALUE; + for (int j = i; j < N; j++) { + if (checkMap[i][j]) { + next = Math.min(next, dp[j + 1]); + } + } + dp[i] = 1 + next; + } + } + return dp[0] - 1; + } + + public static boolean[][] createCheckMap(char[] str, int N) { + boolean[][] ans = new boolean[N][N]; + for (int i = 0; i < N - 1; i++) { + ans[i][i] = true; + ans[i][i + 1] = str[i] == str[i + 1]; + } + ans[N - 1][N - 1] = true; + for (int i = N - 3; i >= 0; i--) { + for (int j = i + 2; j < N; j++) { + ans[i][j] = str[i] == str[j] && ans[i + 1][j - 1]; + } + } + return ans; + } + + // 本题第二问,返回其中一种结果 + public static List minCutOneWay(String s) { + List ans = new ArrayList<>(); + if (s == null || s.length() < 2) { + ans.add(s); + } else { + char[] str = s.toCharArray(); + int N = str.length; + boolean[][] checkMap = createCheckMap(str, N); + int[] dp = new int[N + 1]; + dp[N] = 0; + for (int i = N - 1; i >= 0; i--) { + if (checkMap[i][N - 1]) { + dp[i] = 1; + } else { + int next = Integer.MAX_VALUE; + for (int j = i; j < N; j++) { + if (checkMap[i][j]) { + next = Math.min(next, dp[j + 1]); + } + } + dp[i] = 1 + next; + } + } + // dp[i] (0....5) 回文! dp[0] == dp[6] + 1 + // (0....5) 6 + for (int i = 0, j = 1; j <= N; j++) { + if (checkMap[i][j - 1] && dp[i] == dp[j] + 1) { + ans.add(s.substring(i, j)); + i = j; + } + } + } + return ans; + } + + // 本题第三问,返回所有结果 + public static List> minCutAllWays(String s) { + List> ans = new ArrayList<>(); + if (s == null || s.length() < 2) { + List cur = new ArrayList<>(); + cur.add(s); + ans.add(cur); + } else { + char[] str = s.toCharArray(); + int N = str.length; + boolean[][] checkMap = createCheckMap(str, N); + int[] dp = new int[N + 1]; + dp[N] = 0; + for (int i = N - 1; i >= 0; i--) { + if (checkMap[i][N - 1]) { + dp[i] = 1; + } else { + int next = Integer.MAX_VALUE; + for (int j = i; j < N; j++) { + if (checkMap[i][j]) { + next = Math.min(next, dp[j + 1]); + } + } + dp[i] = 1 + next; + } + } + process(s, 0, 1, checkMap, dp, new ArrayList<>(), ans); + } + return ans; + } + + // s[0....i-1] 存到path里去了 + // s[i..j-1]考察的分出来的第一份 + public static void process(String s, int i, int j, boolean[][] checkMap, int[] dp, + List path, + List> ans) { + if (j == s.length()) { // s[i...N-1] + if (checkMap[i][j - 1] && dp[i] == dp[j] + 1) { + path.add(s.substring(i, j)); + ans.add(copyStringList(path)); + path.remove(path.size() - 1); + } + } else {// s[i...j-1] + if (checkMap[i][j - 1] && dp[i] == dp[j] + 1) { + path.add(s.substring(i, j)); + process(s, j, j + 1, checkMap, dp, path, ans); + path.remove(path.size() - 1); + } + process(s, i, j + 1, checkMap, dp, path, ans); + } + } + + public static List copyStringList(List list) { + List ans = new ArrayList<>(); + for (String str : list) { + ans.add(str); + } + return ans; + } + + public static void main(String[] args) { + String s = null; + List ans2 = null; + List> ans3 = null; + + System.out.println("本题第二问,返回其中一种结果测试开始"); + s = "abacbc"; + ans2 = minCutOneWay(s); + for (String str : ans2) { + System.out.print(str + " "); + } + System.out.println(); + + s = "aabccbac"; + ans2 = minCutOneWay(s); + for (String str : ans2) { + System.out.print(str + " "); + } + System.out.println(); + + s = "aabaa"; + ans2 = minCutOneWay(s); + for (String str : ans2) { + System.out.print(str + " "); + } + System.out.println(); + System.out.println("本题第二问,返回其中一种结果测试结束"); + System.out.println(); + System.out.println("本题第三问,返回所有可能结果测试开始"); + s = "cbbbcbc"; + ans3 = minCutAllWays(s); + for (List way : ans3) { + for (String str : way) { + System.out.print(str + " "); + } + System.out.println(); + } + System.out.println(); + + s = "aaaaaa"; + ans3 = minCutAllWays(s); + for (List way : ans3) { + for (String str : way) { + System.out.print(str + " "); + } + System.out.println(); + } + System.out.println(); + + s = "fcfffcffcc"; + ans3 = minCutAllWays(s); + for (List way : ans3) { + for (String str : way) { + System.out.print(str + " "); + } + System.out.println(); + } + System.out.println(); + System.out.println("本题第三问,返回所有可能结果测试结束"); + } + +} diff --git a/大厂刷题班/class12/Code01_ContainAllCharExactly.java b/大厂刷题班/class12/Code01_ContainAllCharExactly.java new file mode 100644 index 0000000..1774deb --- /dev/null +++ b/大厂刷题班/class12/Code01_ContainAllCharExactly.java @@ -0,0 +1,125 @@ +package class12; + +import java.util.Arrays; + +// 本题测试链接 : https://leetcode.com/problems/permutation-in-string/ +public class Code01_ContainAllCharExactly { + + public static int containExactly1(String s, String a) { + if (s == null || a == null || s.length() < a.length()) { + return -1; + } + char[] aim = a.toCharArray(); + Arrays.sort(aim); + String aimSort = String.valueOf(aim); + for (int L = 0; L < s.length(); L++) { + for (int R = L; R < s.length(); R++) { + char[] cur = s.substring(L, R + 1).toCharArray(); + Arrays.sort(cur); + String curSort = String.valueOf(cur); + if (curSort.equals(aimSort)) { + return L; + } + } + } + return -1; + } + + public static int containExactly2(String s, String a) { + if (s == null || a == null || s.length() < a.length()) { + return -1; + } + char[] str = s.toCharArray(); + char[] aim = a.toCharArray(); + for (int L = 0; L <= str.length - aim.length; L++) { + if (isCountEqual(str, L, aim)) { + return L; + } + } + return -1; + } + + public static boolean isCountEqual(char[] str, int L, char[] aim) { + int[] count = new int[256]; + for (int i = 0; i < aim.length; i++) { + count[aim[i]]++; + } + for (int i = 0; i < aim.length; i++) { + if (count[str[L + i]]-- == 0) { + return false; + } + } + return true; + } + + public static int containExactly3(String s1, String s2) { + if (s1 == null || s2 == null || s1.length() < s2.length()) { + return -1; + } + char[] str2 = s2.toCharArray(); + int M = str2.length; + int[] count = new int[256]; + for (int i = 0; i < M; i++) { + count[str2[i]]++; + } + int all = M; + char[] str1 = s1.toCharArray(); + int R = 0; + // 0~M-1 + for (; R < M; R++) { // 最早的M个字符,让其窗口初步形成 + if (count[str1[R]]-- > 0) { + all--; + } + } + // 窗口初步形成了,并没有判断有效无效,决定下一个位置一上来判断 + // 接下来的过程,窗口右进一个,左吐一个 + for (; R < str1.length; R++) { + if (all == 0) { // R-1 + return R - M; + } + if (count[str1[R]]-- > 0) { + all--; + } + if (count[str1[R - M]]++ >= 0) { + all++; + } + } + return all == 0 ? R - M : -1; + } + + // for test + public static String getRandomString(int possibilities, int maxSize) { + char[] ans = new char[(int) (Math.random() * maxSize) + 1]; + for (int i = 0; i < ans.length; i++) { + ans[i] = (char) ((int) (Math.random() * possibilities) + 'a'); + } + return String.valueOf(ans); + } + + public static void main(String[] args) { + int possibilities = 5; + int strMaxSize = 20; + int aimMaxSize = 10; + int testTimes = 500000; + System.out.println("test begin, test time : " + testTimes); + for (int i = 0; i < testTimes; i++) { + String str = getRandomString(possibilities, strMaxSize); + String aim = getRandomString(possibilities, aimMaxSize); + int ans1 = containExactly1(str, aim); + int ans2 = containExactly2(str, aim); + int ans3 = containExactly3(str, aim); + if (ans1 != ans2 || ans2 != ans3) { + System.out.println("Oops!"); + System.out.println(str); + System.out.println(aim); + System.out.println(ans1); + System.out.println(ans2); + System.out.println(ans3); + break; + } + } + System.out.println("test finish"); + + } + +} diff --git a/大厂刷题班/class12/Code03_FindKthMinNumber.java b/大厂刷题班/class12/Code03_FindKthMinNumber.java new file mode 100644 index 0000000..c36cbda --- /dev/null +++ b/大厂刷题班/class12/Code03_FindKthMinNumber.java @@ -0,0 +1,103 @@ +package class12; + +// 本题测试链接 : https://leetcode.com/problems/median-of-two-sorted-arrays/ +public class Code03_FindKthMinNumber { + + public double findMedianSortedArrays(int[] nums1, int[] nums2) { + int size = nums1.length + nums2.length; + boolean even = (size & 1) == 0; + if (nums1.length != 0 && nums2.length != 0) { + if (even) { + return (double) (findKthNum(nums1, nums2, size / 2) + findKthNum(nums1, nums2, size / 2 + 1)) / 2D; + } else { + return findKthNum(nums1, nums2, size / 2 + 1); + } + } else if (nums1.length != 0) { + if (even) { + return (double) (nums1[(size - 1) / 2] + nums1[size / 2]) / 2; + } else { + return nums1[size / 2]; + } + } else if (nums2.length != 0) { + if (even) { + return (double) (nums2[(size - 1) / 2] + nums2[size / 2]) / 2; + } else { + return nums2[size / 2]; + } + } else { + return 0; + } + } + + // 进阶问题 : 在两个都有序的数组中,找整体第K小的数 + // 可以做到O(log(Min(M,N))) + public static int findKthNum(int[] arr1, int[] arr2, int kth) { + int[] longs = arr1.length >= arr2.length ? arr1 : arr2; + int[] shorts = arr1.length < arr2.length ? arr1 : arr2; + int l = longs.length; + int s = shorts.length; + if (kth <= s) { // 1) + return getUpMedian(shorts, 0, kth - 1, longs, 0, kth - 1); + } + if (kth > l) { // 3) + if (shorts[kth - l - 1] >= longs[l - 1]) { + return shorts[kth - l - 1]; + } + if (longs[kth - s - 1] >= shorts[s - 1]) { + return longs[kth - s - 1]; + } + return getUpMedian(shorts, kth - l, s - 1, longs, kth - s, l - 1); + } + // 2) s < k <= l + if (longs[kth - s - 1] >= shorts[s - 1]) { + return longs[kth - s - 1]; + } + return getUpMedian(shorts, 0, s - 1, longs, kth - s, kth - 1); + } + + + + + // A[s1...e1] + // B[s2...e2] + // 一定等长! + // 返回整体的,上中位数!8(4) 10(5) 12(6) + public static int getUpMedian(int[] A, int s1, int e1, int[] B, int s2, int e2) { + int mid1 = 0; + int mid2 = 0; + while (s1 < e1) { + // mid1 = s1 + (e1 - s1) >> 1 + mid1 = (s1 + e1) / 2; + mid2 = (s2 + e2) / 2; + if (A[mid1] == B[mid2]) { + return A[mid1]; + } + // 两个中点一定不等! + if (((e1 - s1 + 1) & 1) == 1) { // 奇数长度 + if (A[mid1] > B[mid2]) { + if (B[mid2] >= A[mid1 - 1]) { + return B[mid2]; + } + e1 = mid1 - 1; + s2 = mid2 + 1; + } else { // A[mid1] < B[mid2] + if (A[mid1] >= B[mid2 - 1]) { + return A[mid1]; + } + e2 = mid2 - 1; + s1 = mid1 + 1; + } + } else { // 偶数长度 + if (A[mid1] > B[mid2]) { + e1 = mid1; + s2 = mid2 + 1; + } else { + e2 = mid2; + s1 = mid1 + 1; + } + } + } + return Math.min(A[s1], B[s2]); + } + +} diff --git a/大厂刷题班/class12/Code03_LongestConsecutive.java b/大厂刷题班/class12/Code03_LongestConsecutive.java new file mode 100644 index 0000000..8beb48d --- /dev/null +++ b/大厂刷题班/class12/Code03_LongestConsecutive.java @@ -0,0 +1,65 @@ +package class12; + +import java.util.HashMap; +import java.util.HashSet; + +// 本题测试链接 : https://leetcode.com/problems/longest-consecutive-sequence/ +public class Code03_LongestConsecutive { + + // 课上讲的解法 + public static int longestConsecutive(int[] nums) { + HashMap map = new HashMap<>(); + int len = 0; + for (int num : nums) { + if (!map.containsKey(num)) { + map.put(num, 1); + int preLen = map.containsKey(num - 1) ? map.get(num - 1) : 0; + int posLen = map.containsKey(num + 1) ? map.get(num + 1) : 0; + int all = preLen + posLen + 1; + map.put(num - preLen, all); + map.put(num + posLen, all); + len = Math.max(len, all); + } + } + return len; + } + + // 补充一个两张表:头表、尾表。非常好理解的方法 + // 不是最优解,但是好理解 + public static int longestConsecutive2(int[] nums) { + HashMap headMap = new HashMap(); + HashMap tailMap = new HashMap(); + HashSet visited = new HashSet<>(); + for (int num : nums) { + if (!visited.contains(num)) { + visited.add(num); + headMap.put(num, 1); + tailMap.put(num, 1); + if (tailMap.containsKey(num - 1)) { + int preLen = tailMap.get(num - 1); + int preHead = num - preLen; + headMap.put(preHead, preLen + 1); + tailMap.put(num, preLen + 1); + headMap.remove(num); + tailMap.remove(num - 1); + } + if (headMap.containsKey(num + 1)) { + int preLen = tailMap.get(num); + int preHead = num - preLen + 1; + int postLen = headMap.get(num + 1); + int postTail = num + postLen; + headMap.put(preHead, preLen + postLen); + tailMap.put(postTail, preLen + postLen); + headMap.remove(num + 1); + tailMap.remove(num); + } + } + } + int ans = 0; + for (int len : headMap.values()) { + ans = Math.max(ans, len); + } + return ans; + } + +} diff --git a/大厂刷题班/class12/Code04_RegularExpressionMatch.java b/大厂刷题班/class12/Code04_RegularExpressionMatch.java new file mode 100644 index 0000000..c4b35b7 --- /dev/null +++ b/大厂刷题班/class12/Code04_RegularExpressionMatch.java @@ -0,0 +1,135 @@ +package class12; + +// 测试链接 : https://leetcode.com/problems/regular-expression-matching/ +public class Code04_RegularExpressionMatch { + + public static boolean isValid(char[] s, char[] e) { + // s中不能有'.' or '*' + for (int i = 0; i < s.length; i++) { + if (s[i] == '*' || s[i] == '.') { + return false; + } + } + // 开头的e[0]不能是'*',没有相邻的'*' + for (int i = 0; i < e.length; i++) { + if (e[i] == '*' && (i == 0 || e[i - 1] == '*')) { + return false; + } + } + return true; + } + + // 初始尝试版本,不包含斜率优化 + public static boolean isMatch1(String str, String exp) { + if (str == null || exp == null) { + return false; + } + char[] s = str.toCharArray(); + char[] e = exp.toCharArray(); + return isValid(s, e) && process(s, e, 0, 0); + } + + // str[si.....] 能不能被 exp[ei.....]配出来! true false + public static boolean process(char[] s, char[] e, int si, int ei) { + if (ei == e.length) { // exp 没了 str? + return si == s.length; + } + // exp[ei]还有字符 + // ei + 1位置的字符,不是* + if (ei + 1 == e.length || e[ei + 1] != '*') { + // ei + 1 不是* + // str[si] 必须和 exp[ei] 能配上! + return si != s.length && (e[ei] == s[si] || e[ei] == '.') && process(s, e, si + 1, ei + 1); + } + // exp[ei]还有字符 + // ei + 1位置的字符,是*! + while (si != s.length && (e[ei] == s[si] || e[ei] == '.')) { + if (process(s, e, si, ei + 2)) { + return true; + } + si++; + } + return process(s, e, si, ei + 2); + } + + // 改记忆化搜索+斜率优化 + public static boolean isMatch2(String str, String exp) { + if (str == null || exp == null) { + return false; + } + char[] s = str.toCharArray(); + char[] e = exp.toCharArray(); + if (!isValid(s, e)) { + return false; + } + int[][] dp = new int[s.length + 1][e.length + 1]; + // dp[i][j] = 0, 没算过! + // dp[i][j] = -1 算过,返回值是false + // dp[i][j] = 1 算过,返回值是true + return isValid(s, e) && process2(s, e, 0, 0, dp); + } + + public static boolean process2(char[] s, char[] e, int si, int ei, int[][] dp) { + if (dp[si][ei] != 0) { + return dp[si][ei] == 1; + } + boolean ans = false; + if (ei == e.length) { + ans = si == s.length; + } else { + if (ei + 1 == e.length || e[ei + 1] != '*') { + ans = si != s.length && (e[ei] == s[si] || e[ei] == '.') && process2(s, e, si + 1, ei + 1, dp); + } else { + if (si == s.length) { // ei ei+1 * + ans = process2(s, e, si, ei + 2, dp); + } else { // si没结束 + if (s[si] != e[ei] && e[ei] != '.') { + ans = process2(s, e, si, ei + 2, dp); + } else { // s[si] 可以和 e[ei]配上 + ans = process2(s, e, si, ei + 2, dp) || process2(s, e, si + 1, ei, dp); + } + } + } + } + dp[si][ei] = ans ? 1 : -1; + return ans; + } + + // 动态规划版本 + 斜率优化 + public static boolean isMatch3(String str, String pattern) { + if (str == null || pattern == null) { + return false; + } + char[] s = str.toCharArray(); + char[] p = pattern.toCharArray(); + if (!isValid(s, p)) { + return false; + } + int N = s.length; + int M = p.length; + boolean[][] dp = new boolean[N + 1][M + 1]; + dp[N][M] = true; + for (int j = M - 1; j >= 0; j--) { + dp[N][j] = (j + 1 < M && p[j + 1] == '*') && dp[N][j + 2]; + } + // dp[0..N-2][M-1]都等于false,只有dp[N-1][M-1]需要讨论 + if (N > 0 && M > 0) { + dp[N - 1][M - 1] = (s[N - 1] == p[M - 1] || p[M - 1] == '.'); + } + for (int i = N - 1; i >= 0; i--) { + for (int j = M - 2; j >= 0; j--) { + if (p[j + 1] != '*') { + dp[i][j] = ((s[i] == p[j]) || (p[j] == '.')) && dp[i + 1][j + 1]; + } else { + if ((s[i] == p[j] || p[j] == '.') && dp[i + 1][j]) { + dp[i][j] = true; + } else { + dp[i][j] = dp[i][j + 2]; + } + } + } + } + return dp[0][0]; + } + +} diff --git a/大厂刷题班/class13/Code01_NCardsABWin.java b/大厂刷题班/class13/Code01_NCardsABWin.java new file mode 100644 index 0000000..4a2b832 --- /dev/null +++ b/大厂刷题班/class13/Code01_NCardsABWin.java @@ -0,0 +1,169 @@ +package class13; + +import java.text.DecimalFormat; + +public class Code01_NCardsABWin { + + // 谷歌面试题 + // 面值为1~10的牌组成一组, + // 每次你从组里等概率的抽出1~10中的一张 + // 下次抽会换一个新的组,有无限组 + // 当累加和<17时,你将一直抽牌 + // 当累加和>=17且<21时,你将获胜 + // 当累加和>=21时,你将失败 + // 返回获胜的概率 + public static double f1() { + return p1(0); + } + + // 游戏的规则,如上 + // 当你来到cur这个累加和的时候,获胜概率是多少返回! + public static double p1(int cur) { + if (cur >= 17 && cur < 21) { + return 1.0; + } + if (cur >= 21) { + return 0.0; + } + double w = 0.0; + for (int i = 1; i <= 10; i++) { + w += p1(cur + i); + } + return w / 10; + } + + // 谷歌面试题扩展版 + // 面值为1~N的牌组成一组, + // 每次你从组里等概率的抽出1~N中的一张 + // 下次抽会换一个新的组,有无限组 + // 当累加和=a且=b时,你将失败 + // 返回获胜的概率,给定的参数为N,a,b + public static double f2(int N, int a, int b) { + if (N < 1 || a >= b || a < 0 || b < 0) { + return 0.0; + } + if (b - a >= N) { + return 1.0; + } + // 所有参数都合法,并且b-a < N + return p2(0, N, a, b); + } + + // 游戏规则,如上,int N, int a, int b,固定参数! + // cur,目前到达了cur的累加和 + // 返回赢的概率 + public static double p2(int cur, int N, int a, int b) { + if (cur >= a && cur < b) { + return 1.0; + } + if (cur >= b) { + return 0.0; + } + double w = 0.0; + for (int i = 1; i <= N; i++) { + w += p2(cur + i, N, a, b); + } + return w / N; + } + + // f2的改进版本,用到了观察位置优化枚举的技巧 + // 可以课上讲一下 + public static double f3(int N, int a, int b) { + if (N < 1 || a >= b || a < 0 || b < 0) { + return 0.0; + } + if (b - a >= N) { + return 1.0; + } + return p3(0, N, a, b); + } + + public static double p3(int cur, int N, int a, int b) { + if (cur >= a && cur < b) { + return 1.0; + } + if (cur >= b) { + return 0.0; + } + if (cur == a - 1) { + return 1.0 * (b - a) / N; + } + double w = p3(cur + 1, N, a, b) + p3(cur + 1, N, a, b) * N; + if (cur + 1 + N < b) { + w -= p3(cur + 1 + N, N, a, b); + } + return w / N; + } + + // f3的改进版本的动态规划 + // 可以课上讲一下 + public static double f4(int N, int a, int b) { + if (N < 1 || a >= b || a < 0 || b < 0) { + return 0.0; + } + if (b - a >= N) { + return 1.0; + } + double[] dp = new double[b]; + for (int i = a; i < b; i++) { + dp[i] = 1.0; + } + if (a - 1 >= 0) { + dp[a - 1] = 1.0 * (b - a) / N; + } + for (int cur = a - 2; cur >= 0; cur--) { + double w = dp[cur + 1] + dp[cur + 1] * N; + if (cur + 1 + N < b) { + w -= dp[cur + 1 + N]; + } + dp[cur] = w / N; + } + return dp[0]; + } + + public static void main(String[] args) { + int N = 10; + int a = 17; + int b = 21; + System.out.println("N = " + N + ", a = " + a + ", b = " + b); + System.out.println(f1()); + System.out.println(f2(N, a, b)); + System.out.println(f3(N, a, b)); + System.out.println(f4(N, a, b)); + + int maxN = 15; + int maxM = 20; + int testTime = 100000; + System.out.println("测试开始"); + System.out.println("比对double类型答案可能会有精度对不准的问题"); + System.out.println("所以答案一律只保留小数点后四位进行比对"); + System.out.println("如果没有错误提示, 说明验证通过"); + DecimalFormat df = new DecimalFormat("#.####"); + for (int i = 0; i < testTime; i++) { + N = (int) (Math.random() * maxN); + a = (int) (Math.random() * maxM); + b = (int) (Math.random() * maxM); + double ans2 = Double.valueOf(df.format(f2(N, a, b))); + double ans3 = Double.valueOf(df.format(f3(N, a, b))); + double ans4 = Double.valueOf(df.format(f4(N, a, b))); + if (ans2 != ans3 || ans2 != ans4) { + System.out.println("Oops!"); + System.out.println(N + " , " + a + " , " + b); + System.out.println(ans2); + System.out.println(ans3); + System.out.println(ans4); + } + } + System.out.println("测试结束"); + + N = 10000; + a = 67834; + b = 72315; + System.out.println("N = " + N + ", a = " + a + ", b = " + b + "时, 除了方法4外都超时"); + System.out.print("方法4答案: "); + System.out.println(f4(N, a, b)); + } + +} diff --git a/大厂刷题班/class13/Code02_SuperWashingMachines.java b/大厂刷题班/class13/Code02_SuperWashingMachines.java new file mode 100644 index 0000000..bd23ae4 --- /dev/null +++ b/大厂刷题班/class13/Code02_SuperWashingMachines.java @@ -0,0 +1,34 @@ +package class13; + +// 本题测试链接 : https://leetcode.com/problems/super-washing-machines/ +public class Code02_SuperWashingMachines { + + public static int findMinMoves(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + int size = arr.length; + int sum = 0; + for (int i = 0; i < size; i++) { + sum += arr[i]; + } + if (sum % size != 0) { + return -1; + } + int avg = sum / size; + int leftSum = 0; + int ans = 0; + for (int i = 0; i < arr.length; i++) { + int leftRest = leftSum - i * avg; + int rightRest = (sum - leftSum - arr[i]) - (size - i - 1) * avg; + if (leftRest < 0 && rightRest < 0) { + ans = Math.max(ans, Math.abs(leftRest) + Math.abs(rightRest)); + } else { + ans = Math.max(ans, Math.max(Math.abs(leftRest), Math.abs(rightRest))); + } + leftSum += arr[i]; + } + return ans; + } + +} diff --git a/大厂刷题班/class13/Code03_ScrambleString.java b/大厂刷题班/class13/Code03_ScrambleString.java new file mode 100644 index 0000000..49244c4 --- /dev/null +++ b/大厂刷题班/class13/Code03_ScrambleString.java @@ -0,0 +1,186 @@ +package class13; + +// 本题测试链接 : https://leetcode.com/problems/scramble-string/ +public class Code03_ScrambleString { + + public static boolean isScramble0(String s1, String s2) { + if ((s1 == null && s2 != null) || (s1 != null && s2 == null)) { + return false; + } + if (s1 == null && s2 == null) { + return true; + } + if (s1.equals(s2)) { + return true; + } + char[] str1 = s1.toCharArray(); + char[] str2 = s2.toCharArray(); + if (!sameTypeSameNumber(str1, str2)) { + return false; + } + return process0(str1, 0, str1.length - 1, str2, 0, str2.length - 1); + } + + // str1[L1...R1] str2[L2...R2] 是否互为玄变串 + // 一定保证这两段是等长的! + public static boolean process0(char[] str1, int L1, int R1, char[] str2, int L2, int R2) { + if (L1 == R1) { + return str1[L1] == str2[L2]; + } + for (int leftEnd = L1; leftEnd < R1; leftEnd++) { + boolean p1 = process0(str1, L1, leftEnd, str2, L2, L2 + leftEnd - L1) + && process0(str1, leftEnd + 1, R1, str2, L2 + leftEnd - L1 + 1, R2); + boolean p2 = process0(str1, L1, leftEnd, str2, R2 - (leftEnd - L1), R2) + && process0(str1, leftEnd + 1, R1, str2, L2, R2 - (leftEnd - L1) - 1); + if (p1 || p2) { + return true; + } + } + return false; + } + + public static boolean sameTypeSameNumber(char[] str1, char[] str2) { + if (str1.length != str2.length) { + return false; + } + int[] map = new int[256]; + for (int i = 0; i < str1.length; i++) { + map[str1[i]]++; + } + for (int i = 0; i < str2.length; i++) { + if (--map[str2[i]] < 0) { + return false; + } + } + return true; + } + + public static boolean isScramble1(String s1, String s2) { + if ((s1 == null && s2 != null) || (s1 != null && s2 == null)) { + return false; + } + if (s1 == null && s2 == null) { + return true; + } + if (s1.equals(s2)) { + return true; + } + char[] str1 = s1.toCharArray(); + char[] str2 = s2.toCharArray(); + if (!sameTypeSameNumber(str1, str2)) { + return false; + } + int N = s1.length(); + return process1(str1, str2, 0, 0, N); + } + + // 返回str1[从L1开始往右长度为size的子串]和str2[从L2开始往右长度为size的子串]是否互为旋变字符串 + // 在str1中的这一段和str2中的这一段一定是等长的,所以只用一个参数size + public static boolean process1(char[] str1, char[] str2, int L1, int L2, int size) { + if (size == 1) { + return str1[L1] == str2[L2]; + } + // 枚举每一种情况,有一个计算出互为旋变就返回true。都算不出来最后返回false + for (int leftPart = 1; leftPart < size; leftPart++) { + if ( + // 如果1左对2左,并且1右对2右 + (process1(str1, str2, L1, L2, leftPart) + && process1(str1, str2, L1 + leftPart, L2 + leftPart, size - leftPart)) || + // 如果1左对2右,并且1右对2左 + (process1(str1, str2, L1, L2 + size - leftPart, leftPart) + && process1(str1, str2, L1 + leftPart, L2, size - leftPart))) { + return true; + } + } + return false; + } + + public static boolean isScramble2(String s1, String s2) { + if ((s1 == null && s2 != null) || (s1 != null && s2 == null)) { + return false; + } + if (s1 == null && s2 == null) { + return true; + } + if (s1.equals(s2)) { + return true; + } + char[] str1 = s1.toCharArray(); + char[] str2 = s2.toCharArray(); + if (!sameTypeSameNumber(str1, str2)) { + return false; + } + int N = s1.length(); + int[][][] dp = new int[N][N][N + 1]; + // dp[i][j][k] = 0 processDP(i,j,k)状态之前没有算过的 + // dp[i][j][k] = -1 processDP(i,j,k)状态之前算过的,返回值是false + // dp[i][j][k] = 1 processDP(i,j,k)状态之前算过的,返回值是true + return process2(str1, str2, 0, 0, N, dp); + } + + public static boolean process2(char[] str1, char[] str2, int L1, int L2, int size, int[][][] dp) { + if (dp[L1][L2][size] != 0) { + return dp[L1][L2][size] == 1; + } + boolean ans = false; + if (size == 1) { + ans = str1[L1] == str2[L2]; + } else { + for (int leftPart = 1; leftPart < size; leftPart++) { + if ((process2(str1, str2, L1, L2, leftPart, dp) + && process2(str1, str2, L1 + leftPart, L2 + leftPart, size - leftPart, dp)) + || (process2(str1, str2, L1, L2 + size - leftPart, leftPart, dp) + && process2(str1, str2, L1 + leftPart, L2, size - leftPart, dp))) { + ans = true; + break; + } + } + } + dp[L1][L2][size] = ans ? 1 : -1; + return ans; + } + + public static boolean isScramble3(String s1, String s2) { + if ((s1 == null && s2 != null) || (s1 != null && s2 == null)) { + return false; + } + if (s1 == null && s2 == null) { + return true; + } + if (s1.equals(s2)) { + return true; + } + char[] str1 = s1.toCharArray(); + char[] str2 = s2.toCharArray(); + if (!sameTypeSameNumber(str1, str2)) { + return false; + } + int N = s1.length(); + boolean[][][] dp = new boolean[N][N][N + 1]; + for (int L1 = 0; L1 < N; L1++) { + for (int L2 = 0; L2 < N; L2++) { + dp[L1][L2][1] = str1[L1] == str2[L2]; + } + } + // 第一层for循环含义是:依次填size=2层、size=3层..size=N层,每一层都是一个二维平面 + // 第二、三层for循环含义是:在具体的一层,整个面都要填写,所以用两个for循环去填一个二维面 + // L1的取值氛围是[0,N-size],因为从L1出发往右长度为size的子串,L1是不能从N-size+1出发的,这样往右就不够size个字符了 + // L2的取值范围同理 + // 第4层for循环完全是递归函数怎么写,这里就怎么改的 + for (int size = 2; size <= N; size++) { + for (int L1 = 0; L1 <= N - size; L1++) { + for (int L2 = 0; L2 <= N - size; L2++) { + for (int leftPart = 1; leftPart < size; leftPart++) { + if ((dp[L1][L2][leftPart] && dp[L1 + leftPart][L2 + leftPart][size - leftPart]) + || (dp[L1][L2 + size - leftPart][leftPart] && dp[L1 + leftPart][L2][size - leftPart])) { + dp[L1][L2][size] = true; + break; + } + } + } + } + } + return dp[0][0][N]; + } + +} diff --git a/大厂刷题班/class13/Code04_BricksFallingWhenHit.java b/大厂刷题班/class13/Code04_BricksFallingWhenHit.java new file mode 100644 index 0000000..657095c --- /dev/null +++ b/大厂刷题班/class13/Code04_BricksFallingWhenHit.java @@ -0,0 +1,149 @@ +package class13; + +// 本题测试链接 : https://leetcode.com/problems/bricks-falling-when-hit/ +public class Code04_BricksFallingWhenHit { + + public static int[] hitBricks(int[][] grid, int[][] hits) { + for (int i = 0; i < hits.length; i++) { + if (grid[hits[i][0]][hits[i][1]] == 1) { + grid[hits[i][0]][hits[i][1]] = 2; + } + } + UnionFind unionFind = new UnionFind(grid); + int[] ans = new int[hits.length]; + for (int i = hits.length - 1; i >= 0; i--) { + if (grid[hits[i][0]][hits[i][1]] == 2) { + ans[i] = unionFind.finger(hits[i][0], hits[i][1]); + } + } + return ans; + } + + // 并查集 + public static class UnionFind { + private int N; + private int M; + // 有多少块砖,连到了天花板上 + private int cellingAll; + // 原始矩阵,因为炮弹的影响,1 -> 2 + private int[][] grid; + // cellingSet[i] = true; i 是头节点,所在的集合是天花板集合 + private boolean[] cellingSet; + private int[] fatherMap; + private int[] sizeMap; + private int[] stack; + + public UnionFind(int[][] matrix) { + initSpace(matrix); + initConnect(); + } + + private void initSpace(int[][] matrix) { + grid = matrix; + N = grid.length; + M = grid[0].length; + int all = N * M; + cellingAll = 0; + cellingSet = new boolean[all]; + fatherMap = new int[all]; + sizeMap = new int[all]; + stack = new int[all]; + for (int row = 0; row < N; row++) { + for (int col = 0; col < M; col++) { + if (grid[row][col] == 1) { + int index = row * M + col; + fatherMap[index] = index; + sizeMap[index] = 1; + if (row == 0) { + cellingSet[index] = true; + cellingAll++; + } + } + } + } + } + + private void initConnect() { + for (int row = 0; row < N; row++) { + for (int col = 0; col < M; col++) { + union(row, col, row - 1, col); + union(row, col, row + 1, col); + union(row, col, row, col - 1); + union(row, col, row, col + 1); + } + } + } + + private int find(int row, int col) { + int stackSize = 0; + int index = row * M + col; + while (index != fatherMap[index]) { + stack[stackSize++] = index; + index = fatherMap[index]; + } + while (stackSize != 0) { + fatherMap[stack[--stackSize]] = index; + } + return index; + } + + private void union(int r1, int c1, int r2, int c2) { + if (valid(r1, c1) && valid(r2, c2)) { + int father1 = find(r1, c1); + int father2 = find(r2, c2); + if (father1 != father2) { + int size1 = sizeMap[father1]; + int size2 = sizeMap[father2]; + boolean status1 = cellingSet[father1]; + boolean status2 = cellingSet[father2]; + if (size1 <= size2) { + fatherMap[father1] = father2; + sizeMap[father2] = size1 + size2; + if (status1 ^ status2) { + cellingSet[father2] = true; + cellingAll += status1 ? size2 : size1; + } + } else { + fatherMap[father2] = father1; + sizeMap[father1] = size1 + size2; + if (status1 ^ status2) { + cellingSet[father1] = true; + cellingAll += status1 ? size2 : size1; + } + } + } + } + } + + private boolean valid(int row, int col) { + return row >= 0 && row < N && col >= 0 && col < M && grid[row][col] == 1; + } + + public int cellingNum() { + return cellingAll; + } + + public int finger(int row, int col) { + grid[row][col] = 1; + int cur = row * M + col; + if (row == 0) { + cellingSet[cur] = true; + cellingAll++; + } + fatherMap[cur] = cur; + sizeMap[cur] = 1; + int pre = cellingAll; + union(row, col, row - 1, col); + union(row, col, row + 1, col); + union(row, col, row, col - 1); + union(row, col, row, col + 1); + int now = cellingAll; + if (row == 0) { + return now - pre; + } else { + return now == pre ? 0 : now - pre - 1; + } + } + } + +} diff --git a/大厂刷题班/class14/Code01_Parentheses.java b/大厂刷题班/class14/Code01_Parentheses.java new file mode 100644 index 0000000..acd810f --- /dev/null +++ b/大厂刷题班/class14/Code01_Parentheses.java @@ -0,0 +1,97 @@ +package class14; + +public class Code01_Parentheses { + + public static boolean valid(String s) { + char[] str = s.toCharArray(); + int count = 0; + for (int i = 0; i < str.length; i++) { + count += str[i] == '(' ? 1 : -1; + if (count < 0) { + return false; + } + } + return count == 0; + } + + public static int needParentheses(String s) { + char[] str = s.toCharArray(); + int count = 0; + int need = 0; + for (int i = 0; i < str.length; i++) { + if (str[i] == '(') { + count++; + } else { // 遇到的是')' + if (count == 0) { + need++; + } else { + count--; + } + } + } + return count + need; + } + + public static boolean isValid(char[] str) { + if (str == null || str.length == 0) { + return false; + } + int status = 0; + for (int i = 0; i < str.length; i++) { + if (str[i] != ')' && str[i] != '(') { + return false; + } + if (str[i] == ')' && --status < 0) { + return false; + } + if (str[i] == '(') { + status++; + } + } + return status == 0; + } + + public static int deep(String s) { + char[] str = s.toCharArray(); + if (!isValid(str)) { + return 0; + } + int count = 0; + int max = 0; + for (int i = 0; i < str.length; i++) { + if (str[i] == '(') { + max = Math.max(max, ++count); + } else { + count--; + } + } + return max; + } + + // s只由(和)组成 + // 求最长有效括号子串长度 + // 本题测试链接 : https://leetcode.com/problems/longest-valid-parentheses/ + public static int longestValidParentheses(String s) { + if (s == null || s.length() < 2) { + return 0; + } + char[] str = s.toCharArray(); + // dp[i] : 子串必须以i位置结尾的情况下,往左最远能扩出多长的有效区域 + int[] dp = new int[str.length]; + // dp[0] = 0; ( ) + int pre = 0; + int ans = 0; + for (int i = 1; i < str.length; i++) { + if (str[i] == ')') { + // 当前谁和i位置的),去配! + pre = i - dp[i - 1] - 1; // 与str[i]配对的左括号的位置 pre + if (pre >= 0 && str[pre] == '(') { + dp[i] = dp[i - 1] + 2 + (pre > 0 ? dp[pre - 1] : 0); + } + } + ans = Math.max(ans, dp[i]); + } + return ans; + } + +} diff --git a/大厂刷题班/class14/Code02_MaxSubArraySumLessOrEqualK.java b/大厂刷题班/class14/Code02_MaxSubArraySumLessOrEqualK.java new file mode 100644 index 0000000..0456e4b --- /dev/null +++ b/大厂刷题班/class14/Code02_MaxSubArraySumLessOrEqualK.java @@ -0,0 +1,28 @@ +package class14; + +import java.util.TreeSet; + +public class Code02_MaxSubArraySumLessOrEqualK { + + // 请返回arr中,求个子数组的累加和,是<=K的,并且是最大的。 + // 返回这个最大的累加和 + public static int getMaxLessOrEqualK(int[] arr, int K) { + // 记录i之前的,前缀和,按照有序表组织 + TreeSet set = new TreeSet(); + // 一个数也没有的时候,就已经有一个前缀和是0了 + set.add(0); + int max = Integer.MIN_VALUE; + int sum = 0; + // 每一步的i,都求子数组必须以i结尾的情况下,求个子数组的累加和,是<=K的,并且是最大的 + for (int i = 0; i < arr.length; i++) { + sum += arr[i]; // sum -> arr[0..i]; + if (set.ceiling(sum - K) != null) { + max = Math.max(max, sum - set.ceiling(sum - K)); + } + set.add(sum); // 当前的前缀和加入到set中去 + } + return max; + + } + +} diff --git a/大厂刷题班/class14/Code03_BiggestBSTTopologyInTree.java b/大厂刷题班/class14/Code03_BiggestBSTTopologyInTree.java new file mode 100644 index 0000000..c45d2f8 --- /dev/null +++ b/大厂刷题班/class14/Code03_BiggestBSTTopologyInTree.java @@ -0,0 +1,65 @@ +package class14; + +// 本题测试链接 : https://www.nowcoder.com/practice/e13bceaca5b14860b83cbcc4912c5d4a +// 提交以下的代码,并把主类名改成Main +// 可以直接通过 +import java.util.Scanner; + +public class Code03_BiggestBSTTopologyInTree { + + public static void main(String[] args) { + Scanner sc = new Scanner(System.in); + int n = sc.nextInt(); + int h = sc.nextInt(); + int[][] tree = new int[n + 1][3]; + for (int i = 1; i <= n; i++) { + int c = sc.nextInt(); + int l = sc.nextInt(); + int r = sc.nextInt(); + tree[l][0] = c; + tree[r][0] = c; + tree[c][1] = l; + tree[c][2] = r; + } + System.out.println(maxBSTTopology(h, tree, new int[n + 1])); + sc.close(); + } + + // h: 代表当前的头节点 + // t: 代表树 t[i][0]是i节点的父节点、t[i][1]是i节点的左孩子、t[i][2]是i节点的右孩子 + // m: i节点为头的最大bst拓扑结构大小 -> m[i] + // 返回: 以h为头的整棵树上,最大bst拓扑结构的大小 + public static int maxBSTTopology(int h, int[][] t, int[] m) { + if (h == 0) { + return 0; + } + int l = t[h][1]; + int r = t[h][2]; + int c = 0; + int p1 = maxBSTTopology(l, t, m); + int p2 = maxBSTTopology(r, t, m); + while (l < h && m[l] != 0) { + l = t[l][2]; + } + if (m[l] != 0) { + c = m[l]; + while (l != h) { + m[l] -= c; + l = t[l][0]; + } + } + while (r > h && m[r] != 0) { + r = t[r][1]; + } + if (m[r] != 0) { + c = m[r]; + while (r != h) { + m[r] -= c; + r = t[r][0]; + } + } + m[h] = m[t[h][1]] + m[t[h][2]] + 1; + return Math.max(Math.max(p1, p2), m[h]); + } + +} diff --git a/大厂刷题班/class14/Code04_CompleteTreeNodeNumber.java b/大厂刷题班/class14/Code04_CompleteTreeNodeNumber.java new file mode 100644 index 0000000..5e0c09b --- /dev/null +++ b/大厂刷题班/class14/Code04_CompleteTreeNodeNumber.java @@ -0,0 +1,45 @@ +package class14; + +//本题测试链接 : https://leetcode.cn/problems/count-complete-tree-nodes/ +public class Code04_CompleteTreeNodeNumber { + + // 提交时不要提交这个类 + public class TreeNode { + int val; + TreeNode left; + TreeNode right; + } + + // 提交如下的方法 + public static int countNodes(TreeNode head) { + if (head == null) { + return 0; + } + return bs(head, 1, mostLeftLevel(head, 1)); + } + + // 当前来到node节点,node节点在level层,总层数是h + // 返回node为头的子树(必是完全二叉树),有多少个节点 + public static int bs(TreeNode node, int Level, int h) { + if (Level == h) { + return 1; + } + if (mostLeftLevel(node.right, Level + 1) == h) { + return (1 << (h - Level)) + bs(node.right, Level + 1, h); + } else { + return (1 << (h - Level - 1)) + bs(node.left, Level + 1, h); + } + } + + // 如果node在第level层, + // 求以node为头的子树,最大深度是多少 + // node为头的子树,一定是完全二叉树 + public static int mostLeftLevel(TreeNode node, int level) { + while (node != null) { + level++; + node = node.left; + } + return level - 1; + } + +} diff --git a/大厂刷题班/class14/Code05_RecoverBinarySearchTree.java b/大厂刷题班/class14/Code05_RecoverBinarySearchTree.java new file mode 100644 index 0000000..2ee6e62 --- /dev/null +++ b/大厂刷题班/class14/Code05_RecoverBinarySearchTree.java @@ -0,0 +1,511 @@ +package class14; + +import java.util.Stack; + +// 本题测试链接 : https://leetcode.com/problems/recover-binary-search-tree/ +public class Code05_RecoverBinarySearchTree { + + // 不要提交这个类 + public static class TreeNode { + public int val; + public TreeNode left; + public TreeNode right; + + public TreeNode(int v) { + val = v; + } + } + + // 如果能过leetcode,只需要提交这个方法即可 + // 但其实recoverTree2才是正路,只不过leetcode没有那么考 + public static void recoverTree(TreeNode root) { + TreeNode[] errors = twoErrors(root); + if (errors[0] != null && errors[1] != null) { + int tmp = errors[0].val; + errors[0].val = errors[1].val; + errors[1].val = tmp; + } + } + + public static TreeNode[] twoErrors(TreeNode head) { + TreeNode[] ans = new TreeNode[2]; + if (head == null) { + return ans; + } + TreeNode cur = head; + TreeNode mostRight = null; + TreeNode pre = null; + TreeNode e1 = null; + TreeNode e2 = null; + while (cur != null) { + mostRight = cur.left; + if (mostRight != null) { + while (mostRight.right != null && mostRight.right != cur) { + mostRight = mostRight.right; + } + if (mostRight.right == null) { + mostRight.right = cur; + cur = cur.left; + continue; + } else { + mostRight.right = null; + } + } + if (pre != null && pre.val >= cur.val) { + e1 = e1 == null ? pre : e1; + e2 = cur; + } + pre = cur; + cur = cur.right; + } + ans[0] = e1; + ans[1] = e2; + return ans; + } + + // 以下的方法,提交leetcode是通过不了的,但那是因为leetcode的验证方式有问题 + // 但其实!以下的方法,才是正路!在结构上彻底交换两个节点,而不是值交换 + public static TreeNode recoverTree2(TreeNode head) { + TreeNode[] errs = getTwoErrNodes(head); + TreeNode[] parents = getTwoErrParents(head, errs[0], errs[1]); + TreeNode e1 = errs[0]; + TreeNode e1P = parents[0]; + TreeNode e1L = e1.left; + TreeNode e1R = e1.right; + TreeNode e2 = errs[1]; + TreeNode e2P = parents[1]; + TreeNode e2L = e2.left; + TreeNode e2R = e2.right; + if (e1 == head) { + if (e1 == e2P) { + e1.left = e2L; + e1.right = e2R; + e2.right = e1; + e2.left = e1L; + } else if (e2P.left == e2) { + e2P.left = e1; + e2.left = e1L; + e2.right = e1R; + e1.left = e2L; + e1.right = e2R; + } else { + e2P.right = e1; + e2.left = e1L; + e2.right = e1R; + e1.left = e2L; + e1.right = e2R; + } + head = e2; + } else if (e2 == head) { + if (e2 == e1P) { + e2.left = e1L; + e2.right = e1R; + e1.left = e2; + e1.right = e2R; + } else if (e1P.left == e1) { + e1P.left = e2; + e1.left = e2L; + e1.right = e2R; + e2.left = e1L; + e2.right = e1R; + } else { + e1P.right = e2; + e1.left = e2L; + e1.right = e2R; + e2.left = e1L; + e2.right = e1R; + } + head = e1; + } else { + if (e1 == e2P) { + if (e1P.left == e1) { + e1P.left = e2; + e1.left = e2L; + e1.right = e2R; + e2.left = e1L; + e2.right = e1; + } else { + e1P.right = e2; + e1.left = e2L; + e1.right = e2R; + e2.left = e1L; + e2.right = e1; + } + } else if (e2 == e1P) { + if (e2P.left == e2) { + e2P.left = e1; + e2.left = e1L; + e2.right = e1R; + e1.left = e2; + e1.right = e2R; + } else { + e2P.right = e1; + e2.left = e1L; + e2.right = e1R; + e1.left = e2; + e1.right = e2R; + } + } else { + if (e1P.left == e1) { + if (e2P.left == e2) { + e1.left = e2L; + e1.right = e2R; + e2.left = e1L; + e2.right = e1R; + e1P.left = e2; + e2P.left = e1; + } else { + e1.left = e2L; + e1.right = e2R; + e2.left = e1L; + e2.right = e1R; + e1P.left = e2; + e2P.right = e1; + } + } else { + if (e2P.left == e2) { + e1.left = e2L; + e1.right = e2R; + e2.left = e1L; + e2.right = e1R; + e1P.right = e2; + e2P.left = e1; + } else { + e1.left = e2L; + e1.right = e2R; + e2.left = e1L; + e2.right = e1R; + e1P.right = e2; + e2P.right = e1; + } + } + } + } + return head; + } + + public static TreeNode[] getTwoErrNodes(TreeNode head) { + TreeNode[] errs = new TreeNode[2]; + if (head == null) { + return errs; + } + Stack stack = new Stack(); + TreeNode pre = null; + while (!stack.isEmpty() || head != null) { + if (head != null) { + stack.push(head); + head = head.left; + } else { + head = stack.pop(); + if (pre != null && pre.val > head.val) { + errs[0] = errs[0] == null ? pre : errs[0]; + errs[1] = head; + } + pre = head; + head = head.right; + } + } + return errs; + } + + public static TreeNode[] getTwoErrParents(TreeNode head, TreeNode e1, TreeNode e2) { + TreeNode[] parents = new TreeNode[2]; + if (head == null) { + return parents; + } + Stack stack = new Stack(); + while (!stack.isEmpty() || head != null) { + if (head != null) { + stack.push(head); + head = head.left; + } else { + head = stack.pop(); + if (head.left == e1 || head.right == e1) { + parents[0] = head; + } + if (head.left == e2 || head.right == e2) { + parents[1] = head; + } + head = head.right; + } + } + return parents; + } + + // for test -- print tree + public static void printTree(TreeNode head) { + System.out.println("Binary Tree:"); + printInOrder(head, 0, "H", 17); + System.out.println(); + } + + public static void printInOrder(TreeNode head, int height, String to, int len) { + if (head == null) { + return; + } + printInOrder(head.right, height + 1, "v", len); + String val = to + head.val + to; + int lenM = val.length(); + int lenL = (len - lenM) / 2; + int lenR = len - lenM - lenL; + val = getSpace(lenL) + val + getSpace(lenR); + System.out.println(getSpace(height * len) + val); + printInOrder(head.left, height + 1, "^", len); + } + + public static String getSpace(int num) { + String space = " "; + StringBuffer buf = new StringBuffer(""); + for (int i = 0; i < num; i++) { + buf.append(space); + } + return buf.toString(); + } + + // 为了测试 + public static boolean isBST(TreeNode head) { + if (head == null) { + return false; + } + Stack stack = new Stack(); + TreeNode pre = null; + while (!stack.isEmpty() || head != null) { + if (head != null) { + stack.push(head); + head = head.left; + } else { + head = stack.pop(); + if (pre != null && pre.val > head.val) { + return false; + } + pre = head; + head = head.right; + } + } + return true; + } + + public static void main(String[] args) { + TreeNode head = new TreeNode(5); + head.left = new TreeNode(3); + head.right = new TreeNode(7); + head.left.left = new TreeNode(2); + head.left.right = new TreeNode(4); + head.right.left = new TreeNode(6); + head.right.right = new TreeNode(8); + head.left.left.left = new TreeNode(1); + printTree(head); + System.out.println(isBST(head)); + + System.out.println("situation 1"); + TreeNode head1 = new TreeNode(7); + head1.left = new TreeNode(3); + head1.right = new TreeNode(5); + head1.left.left = new TreeNode(2); + head1.left.right = new TreeNode(4); + head1.right.left = new TreeNode(6); + head1.right.right = new TreeNode(8); + head1.left.left.left = new TreeNode(1); + printTree(head1); + System.out.println(isBST(head1)); + TreeNode res1 = recoverTree2(head1); + printTree(res1); + System.out.println(isBST(res1)); + + System.out.println("situation 2"); + TreeNode head2 = new TreeNode(6); + head2.left = new TreeNode(3); + head2.right = new TreeNode(7); + head2.left.left = new TreeNode(2); + head2.left.right = new TreeNode(4); + head2.right.left = new TreeNode(5); + head2.right.right = new TreeNode(8); + head2.left.left.left = new TreeNode(1); + printTree(head2); + System.out.println(isBST(head2)); + TreeNode res2 = recoverTree2(head2); + printTree(res2); + System.out.println(isBST(res2)); + + System.out.println("situation 3"); + TreeNode head3 = new TreeNode(8); + head3.left = new TreeNode(3); + head3.right = new TreeNode(7); + head3.left.left = new TreeNode(2); + head3.left.right = new TreeNode(4); + head3.right.left = new TreeNode(6); + head3.right.right = new TreeNode(5); + head3.left.left.left = new TreeNode(1); + printTree(head3); + System.out.println(isBST(head3)); + TreeNode res3 = recoverTree2(head3); + printTree(res3); + System.out.println(isBST(res3)); + + System.out.println("situation 4"); + TreeNode head4 = new TreeNode(3); + head4.left = new TreeNode(5); + head4.right = new TreeNode(7); + head4.left.left = new TreeNode(2); + head4.left.right = new TreeNode(4); + head4.right.left = new TreeNode(6); + head4.right.right = new TreeNode(8); + head4.left.left.left = new TreeNode(1); + printTree(head4); + System.out.println(isBST(head4)); + TreeNode res4 = recoverTree2(head4); + printTree(res4); + System.out.println(isBST(res4)); + + System.out.println("situation 5"); + TreeNode head5 = new TreeNode(2); + head5.left = new TreeNode(3); + head5.right = new TreeNode(7); + head5.left.left = new TreeNode(5); + head5.left.right = new TreeNode(4); + head5.right.left = new TreeNode(6); + head5.right.right = new TreeNode(8); + head5.left.left.left = new TreeNode(1); + printTree(head5); + System.out.println(isBST(head5)); + TreeNode res5 = recoverTree2(head5); + printTree(res5); + System.out.println(isBST(res5)); + + System.out.println("situation 6"); + TreeNode head6 = new TreeNode(4); + head6.left = new TreeNode(3); + head6.right = new TreeNode(7); + head6.left.left = new TreeNode(2); + head6.left.right = new TreeNode(5); + head6.right.left = new TreeNode(6); + head6.right.right = new TreeNode(8); + head6.left.left.left = new TreeNode(1); + printTree(head6); + System.out.println(isBST(head6)); + TreeNode res6 = recoverTree2(head6); + printTree(res6); + System.out.println(isBST(res6)); + + System.out.println("situation 7"); + TreeNode head7 = new TreeNode(5); + head7.left = new TreeNode(4); + head7.right = new TreeNode(7); + head7.left.left = new TreeNode(2); + head7.left.right = new TreeNode(3); + head7.right.left = new TreeNode(6); + head7.right.right = new TreeNode(8); + head7.left.left.left = new TreeNode(1); + printTree(head7); + System.out.println(isBST(head7)); + TreeNode res7 = recoverTree2(head7); + printTree(res7); + System.out.println(isBST(res7)); + + System.out.println("situation 8"); + TreeNode head8 = new TreeNode(5); + head8.left = new TreeNode(3); + head8.right = new TreeNode(8); + head8.left.left = new TreeNode(2); + head8.left.right = new TreeNode(4); + head8.right.left = new TreeNode(6); + head8.right.right = new TreeNode(7); + head8.left.left.left = new TreeNode(1); + printTree(head8); + System.out.println(isBST(head8)); + TreeNode res8 = recoverTree2(head8); + printTree(res8); + System.out.println(isBST(res8)); + + System.out.println("situation 9"); + TreeNode head9 = new TreeNode(5); + head9.left = new TreeNode(2); + head9.right = new TreeNode(7); + head9.left.left = new TreeNode(3); + head9.left.right = new TreeNode(4); + head9.right.left = new TreeNode(6); + head9.right.right = new TreeNode(8); + head9.left.left.left = new TreeNode(1); + printTree(head9); + System.out.println(isBST(head9)); + TreeNode res9 = recoverTree2(head9); + printTree(res9); + System.out.println(isBST(res9)); + + System.out.println("situation 10"); + TreeNode head10 = new TreeNode(5); + head10.left = new TreeNode(3); + head10.right = new TreeNode(6); + head10.left.left = new TreeNode(2); + head10.left.right = new TreeNode(4); + head10.right.left = new TreeNode(7); + head10.right.right = new TreeNode(8); + head10.left.left.left = new TreeNode(1); + printTree(head10); + System.out.println(isBST(head10)); + TreeNode res10 = recoverTree2(head10); + printTree(res10); + System.out.println(isBST(res10)); + + System.out.println("situation 11"); + TreeNode head11 = new TreeNode(5); + head11.left = new TreeNode(3); + head11.right = new TreeNode(7); + head11.left.left = new TreeNode(6); + head11.left.right = new TreeNode(4); + head11.right.left = new TreeNode(2); + head11.right.right = new TreeNode(8); + head11.left.left.left = new TreeNode(1); + printTree(head11); + System.out.println(isBST(head11)); + TreeNode res11 = recoverTree2(head11); + printTree(res11); + System.out.println(isBST(res11)); + + System.out.println("situation 12"); + TreeNode head12 = new TreeNode(5); + head12.left = new TreeNode(3); + head12.right = new TreeNode(7); + head12.left.left = new TreeNode(8); + head12.left.right = new TreeNode(4); + head12.right.left = new TreeNode(6); + head12.right.right = new TreeNode(2); + head12.left.left.left = new TreeNode(1); + printTree(head12); + System.out.println(isBST(head12)); + TreeNode res12 = recoverTree2(head12); + printTree(res12); + System.out.println(isBST(res12)); + + System.out.println("situation 13"); + TreeNode head13 = new TreeNode(5); + head13.left = new TreeNode(3); + head13.right = new TreeNode(7); + head13.left.left = new TreeNode(2); + head13.left.right = new TreeNode(6); + head13.right.left = new TreeNode(4); + head13.right.right = new TreeNode(8); + head13.left.left.left = new TreeNode(1); + printTree(head13); + System.out.println(isBST(head13)); + TreeNode res13 = recoverTree2(head13); + printTree(res13); + System.out.println(isBST(res13)); + + System.out.println("situation 14"); + TreeNode head14 = new TreeNode(5); + head14.left = new TreeNode(3); + head14.right = new TreeNode(7); + head14.left.left = new TreeNode(2); + head14.left.right = new TreeNode(8); + head14.right.left = new TreeNode(6); + head14.right.right = new TreeNode(4); + head14.left.left.left = new TreeNode(1); + printTree(head14); + System.out.println(isBST(head14)); + TreeNode res14 = recoverTree2(head14); + printTree(res14); + System.out.println(isBST(res14)); + } + +} diff --git a/大厂刷题班/class14/Code06_MissingNumber.java b/大厂刷题班/class14/Code06_MissingNumber.java new file mode 100644 index 0000000..9abca16 --- /dev/null +++ b/大厂刷题班/class14/Code06_MissingNumber.java @@ -0,0 +1,29 @@ +package class14; + +// 测试链接:https://leetcode.com/problems/first-missing-positive/ +public class Code06_MissingNumber { + + public static int firstMissingPositive(int[] arr) { + // l是盯着的位置 + // 0 ~ L-1有效区 + int L = 0; + int R = arr.length; + while (L != R) { + if (arr[L] == L + 1) { + L++; + } else if (arr[L] <= L || arr[L] > R || arr[arr[L] - 1] == arr[L]) { // 垃圾的情况 + swap(arr, L, --R); + } else { + swap(arr, L, arr[L] - 1); + } + } + return L + 1; + } + + public static void swap(int[] arr, int i, int j) { + int tmp = arr[i]; + arr[i] = arr[j]; + arr[j] = tmp; + } + +} diff --git a/大厂刷题班/class15/Code01_BestTimeToBuyAndSellStock.java b/大厂刷题班/class15/Code01_BestTimeToBuyAndSellStock.java new file mode 100644 index 0000000..3660c43 --- /dev/null +++ b/大厂刷题班/class15/Code01_BestTimeToBuyAndSellStock.java @@ -0,0 +1,21 @@ +package class15; + +// leetcode 121 +public class Code01_BestTimeToBuyAndSellStock { + + public static int maxProfit(int[] prices) { + if (prices == null || prices.length == 0) { + return 0; + } + // 必须在0时刻卖掉,[0] - [0] + int ans = 0; + // arr[0...0] + int min = prices[0]; + for (int i = 1; i < prices.length; i++) { + min = Math.min(min, prices[i]); + ans = Math.max(ans, prices[i] - min); + } + return ans; + } + +} diff --git a/大厂刷题班/class15/Code02_BestTimeToBuyAndSellStockII.java b/大厂刷题班/class15/Code02_BestTimeToBuyAndSellStockII.java new file mode 100644 index 0000000..c751f54 --- /dev/null +++ b/大厂刷题班/class15/Code02_BestTimeToBuyAndSellStockII.java @@ -0,0 +1,17 @@ +package class15; + +//leetcode 122 +public class Code02_BestTimeToBuyAndSellStockII { + + public static int maxProfit(int[] prices) { + if (prices == null || prices.length == 0) { + return 0; + } + int ans = 0; + for (int i = 1; i < prices.length; i++) { + ans += Math.max(prices[i] - prices[i-1], 0); + } + return ans; + } + +} diff --git a/大厂刷题班/class15/Code03_BestTimeToBuyAndSellStockIII.java b/大厂刷题班/class15/Code03_BestTimeToBuyAndSellStockIII.java new file mode 100644 index 0000000..a1aadde --- /dev/null +++ b/大厂刷题班/class15/Code03_BestTimeToBuyAndSellStockIII.java @@ -0,0 +1,23 @@ +package class15; + +//leetcode 123 +public class Code03_BestTimeToBuyAndSellStockIII { + + public static int maxProfit(int[] prices) { + if (prices == null || prices.length < 2) { + return 0; + } + int ans = 0; + int doneOnceMinusBuyMax = -prices[0]; + int doneOnceMax = 0; + int min = prices[0]; + for (int i = 1; i < prices.length; i++) { + min = Math.min(min, prices[i]); + ans = Math.max(ans, doneOnceMinusBuyMax + prices[i]); + doneOnceMax = Math.max(doneOnceMax, prices[i] - min); + doneOnceMinusBuyMax = Math.max(doneOnceMinusBuyMax, doneOnceMax - prices[i]); + } + return ans; + } + +} diff --git a/大厂刷题班/class15/Code04_BestTimeToBuyAndSellStockIV.java b/大厂刷题班/class15/Code04_BestTimeToBuyAndSellStockIV.java new file mode 100644 index 0000000..7b8bd72 --- /dev/null +++ b/大厂刷题班/class15/Code04_BestTimeToBuyAndSellStockIV.java @@ -0,0 +1,65 @@ +package class15; + +//leetcode 188 +public class Code04_BestTimeToBuyAndSellStockIV { + + public static int maxProfit(int K, int[] prices) { + if (prices == null || prices.length == 0) { + return 0; + } + int N = prices.length; + if (K >= N / 2) { + return allTrans(prices); + } + int[][] dp = new int[K + 1][N]; + int ans = 0; + for (int tran = 1; tran <= K; tran++) { + int pre = dp[tran][0]; + int best = pre - prices[0]; + for (int index = 1; index < N; index++) { + pre = dp[tran - 1][index]; + dp[tran][index] = Math.max(dp[tran][index - 1], prices[index] + best); + best = Math.max(best, pre - prices[index]); + ans = Math.max(dp[tran][index], ans); + } + } + return ans; + } + + public static int allTrans(int[] prices) { + int ans = 0; + for (int i = 1; i < prices.length; i++) { + ans += Math.max(prices[i] - prices[i - 1], 0); + } + return ans; + } + + // 课上写的版本,对了 + public static int maxProfit2(int K, int[] arr) { + if (arr == null || arr.length == 0 || K < 1) { + return 0; + } + int N = arr.length; + if (K >= N / 2) { + return allTrans(arr); + } + int[][] dp = new int[N][K + 1]; + // dp[...][0] = 0 + // dp[0][...] = arr[0.0] 0 + for (int j = 1; j <= K; j++) { + // dp[1][j] + int p1 = dp[0][j]; + int best = Math.max(dp[1][j - 1] - arr[1], dp[0][j - 1] - arr[0]); + dp[1][j] = Math.max(p1, best + arr[1]); + // dp[1][j] 准备好一些枚举,接下来准备好的枚举 + for (int i = 2; i < N; i++) { + p1 = dp[i - 1][j]; + int newP = dp[i][j - 1] - arr[i]; + best = Math.max(newP, best); + dp[i][j] = Math.max(p1, best + arr[i]); + } + } + return dp[N - 1][K]; + } + +} diff --git a/大厂刷题班/class15/Code05_BestTimeToBuyAndSellStockWithCooldown.java b/大厂刷题班/class15/Code05_BestTimeToBuyAndSellStockWithCooldown.java new file mode 100644 index 0000000..0dea85d --- /dev/null +++ b/大厂刷题班/class15/Code05_BestTimeToBuyAndSellStockWithCooldown.java @@ -0,0 +1,101 @@ +package class15; + +//leetcode 309 +public class Code05_BestTimeToBuyAndSellStockWithCooldown { + + // 该方法是对的,提交之后大量的测试用例通过,最后几个会超时 + // 如果把这个方法改成动态规划,是可以通过的,但这个尝试不是最优解 + public static int maxProfit1(int[] prices) { + return process1(prices, false, 0, 0); + } + + // buy == false 目前可以交易,而且当前没有购买行为 + // buy == true 已经买了,买入价buyPrices,待卖出 + public static int process1(int[] prices, boolean buy, int index, int buyPrices) { + if (index >= prices.length) { + return 0; + } + if (buy) { + int noSell = process1(prices, true, index + 1, buyPrices); + int yesSell = prices[index] - buyPrices + process1(prices, false, index + 2, 0); + return Math.max(noSell, yesSell); + } else { + int noBuy = process1(prices, false, index + 1, 0); + int yesBuy = process1(prices, true, index + 1, prices[index]); + return Math.max(noBuy, yesBuy); + } + } + + // 最优尝试如下: + // buy[i] : 在0...i范围上,最后一次操作是buy动作, + // 这最后一次操作有可能发生在i位置,也可能发生在i之前 + // buy[i]值的含义是:max{ 所有可能性[之前交易获得的最大收益 - 最后buy动作的收购价格] } + // 比如:arr[0...i]假设为[1,3,4,6,2,7,1...i之后的数字不用管] + // 什么叫,所有可能性[之前交易获得的最大收益 - 最后buy动作的收购价格]? + // 比如其中一种可能性: + // 假设最后一次buy动作发生在2这个数的时候,那么之前的交易只能在[1,3,4]上结束,因为6要cooldown的, + // 此时最大收益是多少呢?是4-1==3。那么,之前交易获得的最大收益 - 最后buy动作的收购价格 = 3 - 2 = 1 + // 另一种可能性: + // 再比如最后一次buy动作发生在最后的1这个数的时候,那么之前的交易只能在[1,3,4,6,2]上发生,因为7要cooldown的, + // 此时最大收益是多少呢?是6-1==5。那么,之前交易获得的最大收益 - 最后buy动作的收购价格 = 5 - 1 = 4 + // 除了上面两种可能性之外,还有很多可能性,你可以假设每个数字都是最后buy动作的时候, + // 所有可能性中,(之前交易获得的最大收益 - 最后buy动作的收购价格)的最大值,就是buy[i]的含义 + // 为啥buy[i]要算之前的最大收益 - 最后一次收购价格?尤其是最后为什么要减这么一下? + // 因为这样一来,当你之后以X价格做成一笔交易的时候,当前最好的总收益直接就是 X + buy[i]了 + // + // sell[i] :0...i范围上,最后一次操作是sell动作,这最后一次操作有可能发生在i位置,也可能发生在之前 + // sell[i]值的含义:0...i范围上,最后一次动作是sell的情况下,最好的收益 + // + // 于是通过分析,能得到以下的转移方程: + // buy[i] = Math.max(buy[i - 1], sell[i - 2] - prices[i]) + + + + + // 如果i位置没有发生buy行为,说明有没有i位置都一样,那么buy[i] = buy[i-1],这显而易见 + // 如果i位置发生了buy行为, 那么buy[i] = sell[i - 2] - prices[i], + // 因为你想在i位置买的话,你必须保证之前交易行为发生在0...i-2上, + // 因为如果i-1位置有可能参与交易的话,i位置就要cooldown了, + // 而且之前交易行为必须以sell结束,你才能buy,而且要保证之前交易尽可能得到最好的利润, + // 这正好是sell[i - 2]所代表的含义,并且根据buy[i]的定义,最后一定要 - prices[i] + // + // sell[i] = Math.max(sell[i - 1], buy[i - 1] + prices[i]) + // 如果i位置没有发生sell行为,那么sell[i] = sell[i-1],这显而易见 + // 如果i位置发生了sell行为,那么我们一定要找到 {之前获得尽可能好的收益 - 最后一次的收购价格尽可能低}, + // 而这正好是buy[i - 1]的含义!之前所有的"尽可能"中,最好的一个! + public static int maxProfit2(int[] prices) { + if (prices.length < 2) { + return 0; + } + int N = prices.length; + int[] buy = new int[N]; + int[] sell = new int[N]; + // buy[0] 不需要设置 buy[0] = -arr[0] + // sell[0] = 0 + buy[1] = Math.max(-prices[0], -prices[1]); + sell[1] = Math.max(0, prices[1] - prices[0]); + for (int i = 2; i < N; i++) { + buy[i] = Math.max(buy[i - 1], sell[i - 2] - prices[i]); + sell[i] = Math.max(sell[i - 1], buy[i - 1] + prices[i]); + } + return sell[N - 1]; + } + + // 最优解就是方法2的空间优化而已 + public static int maxProfit3(int[] prices) { + if (prices.length < 2) { + return 0; + } + int buy1 = Math.max(-prices[0], -prices[1]); + int sell1 = Math.max(0, prices[1] - prices[0]); + int sell2 = 0; + for (int i = 2; i < prices.length; i++) { + int tmp = sell1; + sell1 = Math.max(sell1, buy1 + prices[i]); + buy1 = Math.max(buy1, sell2 - prices[i]); + sell2 = tmp; + } + return sell1; + } + +} diff --git a/大厂刷题班/class15/Code06_BestTimeToBuyAndSellStockWithTransactionFee.java b/大厂刷题班/class15/Code06_BestTimeToBuyAndSellStockWithTransactionFee.java new file mode 100644 index 0000000..121e8a6 --- /dev/null +++ b/大厂刷题班/class15/Code06_BestTimeToBuyAndSellStockWithTransactionFee.java @@ -0,0 +1,27 @@ +package class15; + +//leetcode 714 +public class Code06_BestTimeToBuyAndSellStockWithTransactionFee { + + public static int maxProfit(int[] arr, int fee) { + if (arr == null || arr.length < 2) { + return 0; + } + int N = arr.length; + // 0..0 0 -[0] - fee + int bestbuy = -arr[0] - fee; + // 0..0 卖 0 + int bestsell = 0; + for (int i = 1; i < N; i++) { + // 来到i位置了! + // 如果在i必须买 收入 - 批发价 - fee + int curbuy = bestsell - arr[i] - fee; + // 如果在i必须卖 整体最优(收入 - 良好批发价 - fee) + int cursell = bestbuy + arr[i]; + bestbuy = Math.max(bestbuy, curbuy); + bestsell = Math.max(bestsell, cursell); + } + return bestsell; + } + +} diff --git a/大厂刷题班/class16/Code01_IsSum.java b/大厂刷题班/class16/Code01_IsSum.java new file mode 100644 index 0000000..210c277 --- /dev/null +++ b/大厂刷题班/class16/Code01_IsSum.java @@ -0,0 +1,206 @@ +package class16; + +import java.util.HashMap; +import java.util.HashSet; + +// 这道题是一个小小的补充,课上没有讲 +// 但是如果你听过体系学习班动态规划专题和本节课的话 +// 这道题就是一道水题 +public class Code01_IsSum { + + // arr中的值可能为正,可能为负,可能为0 + // 自由选择arr中的数字,能不能累加得到sum + // 暴力递归方法 + public static boolean isSum1(int[] arr, int sum) { + if (sum == 0) { + return true; + } + if (arr == null || arr.length == 0) { + return false; + } + return process1(arr, arr.length - 1, sum); + } + + // 可以自由使用arr[0...i]上的数字,能不能累加得到sum + public static boolean process1(int[] arr, int i, int sum) { + if (sum == 0) { + return true; + } + if (i == -1) { + return false; + } + return process1(arr, i - 1, sum) || process1(arr, i - 1, sum - arr[i]); + } + + // arr中的值可能为正,可能为负,可能为0 + // 自由选择arr中的数字,能不能累加得到sum + // 记忆化搜索方法 + // 从暴力递归方法来,加了记忆化缓存,就是动态规划了 + public static boolean isSum2(int[] arr, int sum) { + if (sum == 0) { + return true; + } + if (arr == null || arr.length == 0) { + return false; + } + return process2(arr, arr.length - 1, sum, new HashMap<>()); + } + + public static boolean process2(int[] arr, int i, int sum, HashMap> dp) { + if (dp.containsKey(i) && dp.get(i).containsKey(sum)) { + return dp.get(i).get(sum); + } + boolean ans = false; + if (sum == 0) { + ans = true; + } else if (i != -1) { + ans = process2(arr, i - 1, sum, dp) || process2(arr, i - 1, sum - arr[i], dp); + } + if (!dp.containsKey(i)) { + dp.put(i, new HashMap<>()); + } + dp.get(i).put(sum, ans); + return ans; + } + + // arr中的值可能为正,可能为负,可能为0 + // 自由选择arr中的数字,能不能累加得到sum + // 经典动态规划 + public static boolean isSum3(int[] arr, int sum) { + if (sum == 0) { + return true; + } + // sum != 0 + if (arr == null || arr.length == 0) { + return false; + } + // arr有数,sum不为0 + int min = 0; + int max = 0; + for (int num : arr) { + min += num < 0 ? num : 0; + max += num > 0 ? num : 0; + } + // min~max + if (sum < min || sum > max) { + return false; + } + + // min <= sum <= max + int N = arr.length; + // dp[i][j] + // + // 0 1 2 3 4 5 6 7 (实际的对应) + // -7 -6 -5 -4 -3 -2 -1 0(想象中) + // + // dp[0][-min] -> dp[0][7] -> dp[0][0] + boolean[][] dp = new boolean[N][max - min + 1]; + // dp[0][0] = true + dp[0][-min] = true; + // dp[0][arr[0]] = true + dp[0][arr[0] - min] = true; + for (int i = 1; i < N; i++) { + for (int j = min; j <= max; j++) { + // dp[i][j] = dp[i-1][j] + dp[i][j - min] = dp[i - 1][j - min]; + // dp[i][j] |= dp[i-1][j - arr[i]] + int next = j - min - arr[i]; + dp[i][j - min] |= (next >= 0 && next <= max - min && dp[i - 1][next]); + } + } + // dp[N-1][sum] + return dp[N - 1][sum - min]; + } + + // arr中的值可能为正,可能为负,可能为0 + // 自由选择arr中的数字,能不能累加得到sum + // 分治的方法 + // 如果arr中的数值特别大,动态规划方法依然会很慢 + // 此时如果arr的数字个数不算多(40以内),哪怕其中的数值很大,分治的方法也将是最优解 + public static boolean isSum4(int[] arr, int sum) { + if (sum == 0) { + return true; + } + if (arr == null || arr.length == 0) { + return false; + } + if (arr.length == 1) { + return arr[0] == sum; + } + int N = arr.length; + int mid = N >> 1; + HashSet leftSum = new HashSet<>(); + HashSet rightSum = new HashSet<>(); + // 0...mid-1 + process4(arr, 0, mid, 0, leftSum); + // mid..N-1 + process4(arr, mid, N, 0, rightSum); + // 单独查看,只使用左部分,能不能搞出sum + // 单独查看,只使用右部分,能不能搞出sum + // 左+右,联合能不能搞出sum + // 左部分搞出所有累加和的时候,包含左部分一个数也没有,这种情况的,leftsum表里,0 + // 17 17 + for (int l : leftSum) { + if (rightSum.contains(sum - l)) { + return true; + } + } + return false; + } + + // arr[0...i-1]决定已经做完了!形成的累加和是pre + // arr[i...end - 1] end(终止) 所有数字随意选择, + // arr[0...end-1]所有可能的累加和存到ans里去 + public static void process4(int[] arr, int i, int end, int pre, HashSet ans) { + if (i == end) { + ans.add(pre); + } else { + process4(arr, i + 1, end, pre, ans); + process4(arr, i + 1, end, pre + arr[i], ans); + } + } + + // 为了测试 + // 生成长度为len的随机数组 + // 值在[-max, max]上随机 + public static int[] randomArray(int len, int max) { + int[] arr = new int[len]; + for (int i = 0; i < len; i++) { + arr[i] = (int) (Math.random() * ((max << 1) + 1)) - max; + } + return arr; + } + + // 对数器验证所有方法 + public static void main(String[] args) { + int N = 20; + int M = 100; + int testTime = 100000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int size = (int) (Math.random() * (N + 1)); + int[] arr = randomArray(size, M); + int sum = (int) (Math.random() * ((M << 1) + 1)) - M; + boolean ans1 = isSum1(arr, sum); + boolean ans2 = isSum2(arr, sum); + boolean ans3 = isSum3(arr, sum); + boolean ans4 = isSum4(arr, sum); + if (ans1 ^ ans2 || ans3 ^ ans4 || ans1 ^ ans3) { + System.out.println("出错了!"); + System.out.print("arr : "); + for (int num : arr) { + System.out.print(num + " "); + } + System.out.println(); + System.out.println("sum : " + sum); + System.out.println("方法一答案 : " + ans1); + System.out.println("方法二答案 : " + ans2); + System.out.println("方法三答案 : " + ans3); + System.out.println("方法四答案 : " + ans4); + break; + } + } + System.out.println("测试结束"); + } + +} diff --git a/大厂刷题班/class16/Code02_SmallestUnFormedSum.java b/大厂刷题班/class16/Code02_SmallestUnFormedSum.java new file mode 100644 index 0000000..645b3af --- /dev/null +++ b/大厂刷题班/class16/Code02_SmallestUnFormedSum.java @@ -0,0 +1,123 @@ +package class16; + +import java.util.Arrays; +import java.util.HashSet; + +public class Code02_SmallestUnFormedSum { + + public static int unformedSum1(int[] arr) { + if (arr == null || arr.length == 0) { + return 1; + } + HashSet set = new HashSet(); + process(arr, 0, 0, set); + int min = Integer.MAX_VALUE; + for (int i = 0; i != arr.length; i++) { + min = Math.min(min, arr[i]); + } + for (int i = min + 1; i != Integer.MIN_VALUE; i++) { + if (!set.contains(i)) { + return i; + } + } + return 0; + } + + public static void process(int[] arr, int i, int sum, HashSet set) { + if (i == arr.length) { + set.add(sum); + return; + } + process(arr, i + 1, sum, set); + process(arr, i + 1, sum + arr[i], set); + } + + public static int unformedSum2(int[] arr) { + if (arr == null || arr.length == 0) { + return 1; + } + int sum = 0; + int min = Integer.MAX_VALUE; + for (int i = 0; i != arr.length; i++) { + sum += arr[i]; + min = Math.min(min, arr[i]); + } + // boolean[][] dp ... + int N = arr.length; + boolean[][] dp = new boolean[N][sum + 1]; + for (int i = 0; i < N; i++) {// arr[0..i] 0 + dp[i][0] = true; + } + dp[0][arr[0]] = true; + for (int i = 1; i < N; i++) { + for (int j = 1; j <= sum; j++) { + dp[i][j] = dp[i - 1][j] || ((j - arr[i] >= 0) ? dp[i - 1][j - arr[i]] : false); + } + } + for (int j = min; j <= sum; j++) { + if (!dp[N - 1][j]) { + return j; + } + } + return sum + 1; + } + + // 已知arr中肯定有1这个数 + public static int unformedSum3(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + Arrays.sort(arr); // O (N * logN) + int range = 1; + // arr[0] == 1 + for (int i = 1; i != arr.length; i++) { + if (arr[i] > range + 1) { + return range + 1; + } else { + range += arr[i]; + } + } + return range + 1; + } + + public static int[] generateArray(int len, int maxValue) { + int[] res = new int[len]; + for (int i = 0; i != res.length; i++) { + res[i] = (int) (Math.random() * maxValue) + 1; + } + return res; + } + + public static void printArray(int[] arr) { + for (int i = 0; i != arr.length; i++) { + System.out.print(arr[i] + " "); + } + System.out.println(); + } + + public static void main(String[] args) { + int len = 27; + int max = 30; + int[] arr = generateArray(len, max); + printArray(arr); + long start = System.currentTimeMillis(); + System.out.println(unformedSum1(arr)); + long end = System.currentTimeMillis(); + System.out.println("cost time: " + (end - start) + " ms"); + System.out.println("======================================"); + + start = System.currentTimeMillis(); + System.out.println(unformedSum2(arr)); + end = System.currentTimeMillis(); + System.out.println("cost time: " + (end - start) + " ms"); + System.out.println("======================================"); + + System.out.println("set arr[0] to 1"); + arr[0] = 1; + start = System.currentTimeMillis(); + System.out.println(unformedSum3(arr)); + end = System.currentTimeMillis(); + System.out.println("cost time: " + (end - start) + " ms"); + + } +} diff --git a/大厂刷题班/class16/Code03_MinPatches.java b/大厂刷题班/class16/Code03_MinPatches.java new file mode 100644 index 0000000..de5eb5d --- /dev/null +++ b/大厂刷题班/class16/Code03_MinPatches.java @@ -0,0 +1,82 @@ +package class16; + +import java.util.Arrays; + +public class Code03_MinPatches { + + // arr请保证有序,且正数 1~aim + public static int minPatches(int[] arr, int aim) { + int patches = 0; // 缺多少个数字 + long range = 0; // 已经完成了1 ~ range的目标 + Arrays.sort(arr); + for (int i = 0; i != arr.length; i++) { + // arr[i] + // 要求:1 ~ arr[i]-1 范围被搞定! + while (arr[i] - 1 > range) { // arr[i] 1 ~ arr[i]-1 + range += range + 1; // range + 1 是缺的数字 + patches++; + if (range >= aim) { + return patches; + } + } + // 要求被满足了! + range += arr[i]; + if (range >= aim) { + return patches; + } + } + while (aim >= range + 1) { + range += range + 1; + patches++; + } + return patches; + } + + // 嘚瑟 + public static int minPatches2(int[] arr, int K) { + int patches = 0; // 缺多少个数字 + int range = 0; // 已经完成了1 ~ range的目标 + for (int i = 0; i != arr.length; i++) { + // 1~range + // 1 ~ arr[i]-1 + while (arr[i] > range + 1) { // arr[i] 1 ~ arr[i]-1 + + if (range > Integer.MAX_VALUE - range - 1) { + return patches + 1; + } + + range += range + 1; // range + 1 是缺的数字 + patches++; + if (range >= K) { + return patches; + } + } + if (range > Integer.MAX_VALUE - arr[i]) { + return patches; + } + range += arr[i]; + if (range >= K) { + return patches; + } + } + while (K >= range + 1) { + if (K == range && K == Integer.MAX_VALUE) { + return patches; + } + if (range > Integer.MAX_VALUE - range - 1) { + return patches + 1; + } + range += range + 1; + patches++; + } + return patches; + } + + public static void main(String[] args) { + int[] test = { 1, 2, 31, 33 }; + int n = 2147483647; + System.out.println(minPatches(test, n)); + + } + +} diff --git a/大厂刷题班/class16/Code04_MergeRecord.java b/大厂刷题班/class16/Code04_MergeRecord.java new file mode 100644 index 0000000..a7b1798 --- /dev/null +++ b/大厂刷题班/class16/Code04_MergeRecord.java @@ -0,0 +1,220 @@ +package class16; + +public class Code04_MergeRecord { + + /* + * 腾讯原题 + * + * 给定整数power,给定一个数组arr,给定一个数组reverse。含义如下: + * arr的长度一定是2的power次方,reverse中的每个值一定都在0~power范围。 + * 例如power = 2, arr = {3, 1, 4, 2},reverse = {0, 1, 0, 2} + * 任何一个在前的数字可以和任何一个在后的数组,构成一对数。可能是升序关系、相等关系或者降序关系。 + * 比如arr开始时有如下的降序对:(3,1)、(3,2)、(4,2),一共3个。 + * 接下来根据reverse对arr进行调整: + * reverse[0] = 0, 表示在arr中,划分每1(2的0次方)个数一组,然后每个小组内部逆序,那么arr变成 + * [3,1,4,2],此时有3个逆序对。 + * reverse[1] = 1, 表示在arr中,划分每2(2的1次方)个数一组,然后每个小组内部逆序,那么arr变成 + * [1,3,2,4],此时有1个逆序对 + * reverse[2] = 0, 表示在arr中,划分每1(2的0次方)个数一组,然后每个小组内部逆序,那么arr变成 + * [1,3,2,4],此时有1个逆序对。 + * reverse[3] = 2, 表示在arr中,划分每4(2的2次方)个数一组,然后每个小组内部逆序,那么arr变成 + * [4,2,3,1],此时有5个逆序对。 + * 所以返回[3,1,1,5],表示每次调整之后的逆序对数量。 + * + * 输入数据状况: + * power的范围[0,20] + * arr长度范围[1,10的7次方] + * reverse长度范围[1,10的6次方] + * + * */ + + public static int[] reversePair1(int[] originArr, int[] reverseArr, int power) { + int[] ans = new int[reverseArr.length]; + for (int i = 0; i < reverseArr.length; i++) { + reverseArray(originArr, 1 << (reverseArr[i])); + ans[i] = countReversePair(originArr); + } + return ans; + } + + public static void reverseArray(int[] originArr, int teamSize) { + if (teamSize < 2) { + return; + } + for (int i = 0; i < originArr.length; i += teamSize) { + reversePart(originArr, i, i + teamSize - 1); + } + } + + public static void reversePart(int[] arr, int L, int R) { + while (L < R) { + int tmp = arr[L]; + arr[L++] = arr[R]; + arr[R--] = tmp; + } + } + + public static int countReversePair(int[] originArr) { + int ans = 0; + for (int i = 0; i < originArr.length; i++) { + for (int j = i + 1; j < originArr.length; j++) { + if (originArr[i] > originArr[j]) { + ans++; + } + } + } + return ans; + } + + public static int[] reversePair2(int[] originArr, int[] reverseArr, int power) { + int[] reverse = copyArray(originArr); + reversePart(reverse, 0, reverse.length - 1); + int[] recordDown = new int[power + 1]; + int[] recordUp = new int[power + 1]; + process(originArr, 0, originArr.length - 1, power, recordDown); + process(reverse, 0, reverse.length - 1, power, recordUp); + int[] ans = new int[reverseArr.length]; + for (int i = 0; i < reverseArr.length; i++) { + int curPower = reverseArr[i]; + for (int p = 1; p <= curPower; p++) { + int tmp = recordDown[p]; + recordDown[p] = recordUp[p]; + recordUp[p] = tmp; + } + for (int p = 1; p <= power; p++) { + ans[i] += recordDown[p]; + } + } + return ans; + } + + // originArr[L...R]完成排序! + // L...M左 M...R右 merge + // L...R 2的power次方 + + public static void process(int[] originArr, int L, int R, int power, int[] record) { + if (L == R) { + return; + } + int mid = L + ((R - L) >> 1); + process(originArr, L, mid, power - 1, record); + process(originArr, mid + 1, R, power - 1, record); + record[power] += merge(originArr, L, mid, R); + } + + public static int merge(int[] arr, int L, int m, int r) { + int[] help = new int[r - L + 1]; + int i = 0; + int p1 = L; + int p2 = m + 1; + int ans = 0; + while (p1 <= m && p2 <= r) { + ans += arr[p1] > arr[p2] ? (m - p1 + 1) : 0; + help[i++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++]; + } + while (p1 <= m) { + help[i++] = arr[p1++]; + } + while (p2 <= r) { + help[i++] = arr[p2++]; + } + for (i = 0; i < help.length; i++) { + arr[L + i] = help[i]; + } + return ans; + } + + // for test + public static int[] generateRandomOriginArray(int power, int value) { + int[] ans = new int[1 << power]; + for (int i = 0; i < ans.length; i++) { + ans[i] = (int) (Math.random() * value); + } + return ans; + } + + // for test + public static int[] generateRandomReverseArray(int len, int power) { + int[] ans = new int[len]; + for (int i = 0; i < ans.length; i++) { + ans[i] = (int) (Math.random() * (power + 1)); + } + return ans; + } + + // for test + public static void printArray(int[] arr) { + System.out.println("arr size : " + arr.length); + for (int i = 0; i < arr.length; i++) { + System.out.print(arr[i] + " "); + } + System.out.println(); + } + + // for test + public static int[] copyArray(int[] arr) { + if (arr == null) { + return null; + } + int[] res = new int[arr.length]; + for (int i = 0; i < arr.length; i++) { + res[i] = arr[i]; + } + return res; + } + + // for test + public static boolean isEqual(int[] arr1, int[] arr2) { + if ((arr1 == null && arr2 != null) || (arr1 != null && arr2 == null)) { + return false; + } + if (arr1 == null && arr2 == null) { + return true; + } + if (arr1.length != arr2.length) { + return false; + } + for (int i = 0; i < arr1.length; i++) { + if (arr1[i] != arr2[i]) { + return false; + } + } + return true; + } + + public static void main(String[] args) { + int powerMax = 8; + int msizeMax = 10; + int value = 30; + int testTime = 50000; + System.out.println("test begin"); + for (int i = 0; i < testTime; i++) { + int power = (int) (Math.random() * powerMax) + 1; + int msize = (int) (Math.random() * msizeMax) + 1; + int[] originArr = generateRandomOriginArray(power, value); + int[] originArr1 = copyArray(originArr); + int[] originArr2 = copyArray(originArr); + int[] reverseArr = generateRandomReverseArray(msize, power); + int[] reverseArr1 = copyArray(reverseArr); + int[] reverseArr2 = copyArray(reverseArr); + int[] ans1 = reversePair1(originArr1, reverseArr1, power); + int[] ans2 = reversePair2(originArr2, reverseArr2, power); + if (!isEqual(ans1, ans2)) { + System.out.println("Oops!"); + } + } + System.out.println("test finish!"); + + powerMax = 20; + msizeMax = 1000000; + value = 1000; + int[] originArr = generateRandomOriginArray(powerMax, value); + int[] reverseArr = generateRandomReverseArray(msizeMax, powerMax); + // int[] ans1 = reversePair1(originArr1, reverseArr1, powerMax); + long start = System.currentTimeMillis(); + reversePair2(originArr, reverseArr, powerMax); + long end = System.currentTimeMillis(); + System.out.println("run time : " + (end - start) + " ms"); + } + +} diff --git a/大厂刷题班/class16/Code05_JosephusProblem.java b/大厂刷题班/class16/Code05_JosephusProblem.java new file mode 100644 index 0000000..0aaebd8 --- /dev/null +++ b/大厂刷题班/class16/Code05_JosephusProblem.java @@ -0,0 +1,120 @@ +package class16; + +// 本题测试链接 : https://leetcode-cn.com/problems/yuan-quan-zhong-zui-hou-sheng-xia-de-shu-zi-lcof/ +public class Code05_JosephusProblem { + + // 提交直接通过 + // 给定的编号是0~n-1的情况下,数到m就杀 + // 返回谁会活? + public int lastRemaining1(int n, int m) { + return getLive(n, m) - 1; + } + + // 课上题目的设定是,给定的编号是1~n的情况下,数到m就杀 + // 返回谁会活? + public static int getLive(int n, int m) { + if (n == 1) { + return 1; + } + return (getLive(n - 1, m) + m - 1) % n + 1; + } + + // 提交直接通过 + // 给定的编号是0~n-1的情况下,数到m就杀 + // 返回谁会活? + // 这个版本是迭代版 + public int lastRemaining2(int n, int m) { + int ans = 1; + int r = 1; + while (r <= n) { + ans = (ans + m - 1) % (r++) + 1; + } + return ans - 1; + } + + // 以下的code针对单链表,不要提交 + public static class Node { + public int value; + public Node next; + + public Node(int data) { + this.value = data; + } + } + + public static Node josephusKill1(Node head, int m) { + if (head == null || head.next == head || m < 1) { + return head; + } + Node last = head; + while (last.next != head) { + last = last.next; + } + int count = 0; + while (head != last) { + if (++count == m) { + last.next = head.next; + count = 0; + } else { + last = last.next; + } + head = last.next; + } + return head; + } + + public static Node josephusKill2(Node head, int m) { + if (head == null || head.next == head || m < 1) { + return head; + } + Node cur = head.next; + int size = 1; // tmp -> list size + while (cur != head) { + size++; + cur = cur.next; + } + int live = getLive(size, m); // tmp -> service node position + while (--live != 0) { + head = head.next; + } + head.next = head; + return head; + } + + public static void printCircularList(Node head) { + if (head == null) { + return; + } + System.out.print("Circular List: " + head.value + " "); + Node cur = head.next; + while (cur != head) { + System.out.print(cur.value + " "); + cur = cur.next; + } + System.out.println("-> " + head.value); + } + + public static void main(String[] args) { + Node head1 = new Node(1); + head1.next = new Node(2); + head1.next.next = new Node(3); + head1.next.next.next = new Node(4); + head1.next.next.next.next = new Node(5); + head1.next.next.next.next.next = head1; + printCircularList(head1); + head1 = josephusKill1(head1, 3); + printCircularList(head1); + + Node head2 = new Node(1); + head2.next = new Node(2); + head2.next.next = new Node(3); + head2.next.next.next = new Node(4); + head2.next.next.next.next = new Node(5); + head2.next.next.next.next.next = head2; + printCircularList(head2); + head2 = josephusKill2(head2, 3); + printCircularList(head2); + + } + +} \ No newline at end of file diff --git a/大厂刷题班/class17/Code01_FindNumInSortedMatrix.java b/大厂刷题班/class17/Code01_FindNumInSortedMatrix.java new file mode 100644 index 0000000..16aea90 --- /dev/null +++ b/大厂刷题班/class17/Code01_FindNumInSortedMatrix.java @@ -0,0 +1,34 @@ +package class17; + +public class Code01_FindNumInSortedMatrix { + + public static boolean isContains(int[][] matrix, int K) { + int row = 0; + int col = matrix[0].length - 1; + while (row < matrix.length && col > -1) { + if (matrix[row][col] == K) { + return true; + } else if (matrix[row][col] > K) { + col--; + } else { + row++; + } + } + return false; + } + + public static void main(String[] args) { + int[][] matrix = new int[][] { { 0, 1, 2, 3, 4, 5, 6 }, // 0 + { 10, 12, 13, 15, 16, 17, 18 }, // 1 + { 23, 24, 25, 26, 27, 28, 29 }, // 2 + { 44, 45, 46, 47, 48, 49, 50 }, // 3 + { 65, 66, 67, 68, 69, 70, 71 }, // 4 + { 96, 97, 98, 99, 100, 111, 122 }, // 5 + { 166, 176, 186, 187, 190, 195, 200 }, // 6 + { 233, 243, 321, 341, 356, 370, 380 } // 7 + }; + int K = 233; + System.out.println(isContains(matrix, K)); + } + +} diff --git a/大厂刷题班/class17/Code02_KthSmallestElementInSortedMatrix.java b/大厂刷题班/class17/Code02_KthSmallestElementInSortedMatrix.java new file mode 100644 index 0000000..0dcd8e9 --- /dev/null +++ b/大厂刷题班/class17/Code02_KthSmallestElementInSortedMatrix.java @@ -0,0 +1,110 @@ +package class17; + +import java.util.Comparator; +import java.util.PriorityQueue; + +// 本题测试链接 : https://leetcode.com/problems/kth-smallest-element-in-a-sorted-matrix/ +public class Code02_KthSmallestElementInSortedMatrix { + + // 堆的方法 + public static int kthSmallest1(int[][] matrix, int k) { + int N = matrix.length; + int M = matrix[0].length; + PriorityQueue heap = new PriorityQueue<>(new NodeComparator()); + boolean[][] set = new boolean[N][M]; + heap.add(new Node(matrix[0][0], 0, 0)); + set[0][0] = true; + int count = 0; + Node ans = null; + while (!heap.isEmpty()) { + ans = heap.poll(); + if (++count == k) { + break; + } + int row = ans.row; + int col = ans.col; + if (row + 1 < N && !set[row + 1][col]) { + heap.add(new Node(matrix[row + 1][col], row + 1, col)); + set[row + 1][col] = true; + } + if (col + 1 < M && !set[row][col + 1]) { + heap.add(new Node(matrix[row][col + 1], row, col + 1)); + set[row][col + 1] = true; + } + } + return ans.value; + } + + public static class Node { + public int value; + public int row; + public int col; + + public Node(int v, int r, int c) { + value = v; + row = r; + col = c; + } + + } + + public static class NodeComparator implements Comparator { + + @Override + public int compare(Node o1, Node o2) { + return o1.value - o2.value; + } + + } + + // 二分的方法 + public static int kthSmallest2(int[][] matrix, int k) { + int N = matrix.length; + int M = matrix[0].length; + int left = matrix[0][0]; + int right = matrix[N - 1][M - 1]; + int ans = 0; + while (left <= right) { + int mid = left + ((right - left) >> 1); + // <=mid 有几个 <= mid 在矩阵中真实出现的数,谁最接近mid + Info info = noMoreNum(matrix, mid); + if (info.num < k) { + left = mid + 1; + } else { + ans = info.near; + right = mid - 1; + } + } + return ans; + } + + public static class Info { + public int near; + public int num; + + public Info(int n1, int n2) { + near = n1; + num = n2; + } + } + + public static Info noMoreNum(int[][] matrix, int value) { + int near = Integer.MIN_VALUE; + int num = 0; + int N = matrix.length; + int M = matrix[0].length; + int row = 0; + int col = M - 1; + while (row < N && col >= 0) { + if (matrix[row][col] <= value) { + near = Math.max(near, matrix[row][col]); + num += col + 1; + row++; + } else { + col--; + } + } + return new Info(near, num); + } + +} diff --git a/大厂刷题班/class17/Code03_PalindromePairs.java b/大厂刷题班/class17/Code03_PalindromePairs.java new file mode 100644 index 0000000..b3d110b --- /dev/null +++ b/大厂刷题班/class17/Code03_PalindromePairs.java @@ -0,0 +1,104 @@ +package class17; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +// 测试链接 : https://leetcode.com/problems/palindrome-pairs/ +public class Code03_PalindromePairs { + + public static List> palindromePairs(String[] words) { + HashMap wordset = new HashMap<>(); + for (int i = 0; i < words.length; i++) { + wordset.put(words[i], i); + } + List> res = new ArrayList<>(); + //{ [6,23] 、 [7,13] } + for (int i = 0; i < words.length; i++) { + // i words[i] + // findAll(字符串,在i位置,wordset) 返回所有生成的结果返回 + res.addAll(findAll(words[i], i, wordset)); + } + return res; + } + + public static List> findAll(String word, int index, HashMap words) { + List> res = new ArrayList<>(); + String reverse = reverse(word); + Integer rest = words.get(""); + if (rest != null && rest != index && word.equals(reverse)) { + addRecord(res, rest, index); + addRecord(res, index, rest); + } + int[] rs = manacherss(word); + int mid = rs.length >> 1; + for (int i = 1; i < mid; i++) { + if (i - rs[i] == -1) { + rest = words.get(reverse.substring(0, mid - i)); + if (rest != null && rest != index) { + addRecord(res, rest, index); + } + } + } + for (int i = mid + 1; i < rs.length; i++) { + if (i + rs[i] == rs.length) { + rest = words.get(reverse.substring((mid << 1) - i)); + if (rest != null && rest != index) { + addRecord(res, index, rest); + } + } + } + return res; + } + + public static void addRecord(List> res, int left, int right) { + List newr = new ArrayList<>(); + newr.add(left); + newr.add(right); + res.add(newr); + } + + public static int[] manacherss(String word) { + char[] mchs = manachercs(word); + int[] rs = new int[mchs.length]; + int center = -1; + int pr = -1; + for (int i = 0; i != mchs.length; i++) { + rs[i] = pr > i ? Math.min(rs[(center << 1) - i], pr - i) : 1; + while (i + rs[i] < mchs.length && i - rs[i] > -1) { + if (mchs[i + rs[i]] != mchs[i - rs[i]]) { + break; + } + rs[i]++; + } + if (i + rs[i] > pr) { + pr = i + rs[i]; + center = i; + } + } + return rs; + } + + public static char[] manachercs(String word) { + char[] chs = word.toCharArray(); + char[] mchs = new char[chs.length * 2 + 1]; + int index = 0; + for (int i = 0; i != mchs.length; i++) { + mchs[i] = (i & 1) == 0 ? '#' : chs[index++]; + } + return mchs; + } + + public static String reverse(String str) { + char[] chs = str.toCharArray(); + int l = 0; + int r = chs.length - 1; + while (l < r) { + char tmp = chs[l]; + chs[l++] = chs[r]; + chs[r--] = tmp; + } + return String.valueOf(chs); + } + +} \ No newline at end of file diff --git a/大厂刷题班/class17/Code04_DistinctSubseq.java b/大厂刷题班/class17/Code04_DistinctSubseq.java new file mode 100644 index 0000000..ff047e4 --- /dev/null +++ b/大厂刷题班/class17/Code04_DistinctSubseq.java @@ -0,0 +1,101 @@ +package class17; + +// 测试链接 : https://leetcode-cn.com/problems/21dk04/ +public class Code04_DistinctSubseq { + + public static int numDistinct1(String S, String T) { + char[] s = S.toCharArray(); + char[] t = T.toCharArray(); + return process(s, t, s.length, t.length); + } + + public static int process(char[] s, char[] t, int i, int j) { + if (j == 0) { + return 1; + } + if (i == 0) { + return 0; + } + int res = process(s, t, i - 1, j); + if (s[i - 1] == t[j - 1]) { + res += process(s, t, i - 1, j - 1); + } + return res; + } + + // S[...i]的所有子序列中,包含多少个字面值等于T[...j]这个字符串的子序列 + // 记为dp[i][j] + // 可能性1)S[...i]的所有子序列中,都不以s[i]结尾,则dp[i][j]肯定包含dp[i-1][j] + // 可能性2)S[...i]的所有子序列中,都必须以s[i]结尾, + // 这要求S[i] == T[j],则dp[i][j]包含dp[i-1][j-1] + public static int numDistinct2(String S, String T) { + char[] s = S.toCharArray(); + char[] t = T.toCharArray(); + // dp[i][j] : s[0..i] T[0...j] + + // dp[i][j] : s只拿前i个字符做子序列,有多少个子序列,字面值等于T的前j个字符的前缀串 + int[][] dp = new int[s.length + 1][t.length + 1]; + // dp[0][0] + // dp[0][j] = s只拿前0个字符做子序列, T前j个字符 + for (int j = 0; j <= t.length; j++) { + dp[0][j] = 0; + } + for (int i = 0; i <= s.length; i++) { + dp[i][0] = 1; + } + for (int i = 1; i <= s.length; i++) { + for (int j = 1; j <= t.length; j++) { + dp[i][j] = dp[i - 1][j] + (s[i - 1] == t[j - 1] ? dp[i - 1][j - 1] : 0); + } + } + return dp[s.length][t.length]; + } + + public static int numDistinct3(String S, String T) { + char[] s = S.toCharArray(); + char[] t = T.toCharArray(); + int[] dp = new int[t.length + 1]; + dp[0] = 1; + for (int j = 1; j <= t.length; j++) { + dp[j] = 0; + } + for (int i = 1; i <= s.length; i++) { + for (int j = t.length; j >= 1; j--) { + dp[j] += s[i - 1] == t[j - 1] ? dp[j - 1] : 0; + } + } + return dp[t.length]; + } + + public static int dp(String S, String T) { + char[] s = S.toCharArray(); + char[] t = T.toCharArray(); + int N = s.length; + int M = t.length; + int[][] dp = new int[N][M]; + // s[0..0] T[0..0] dp[0][0] + dp[0][0] = s[0] == t[0] ? 1 : 0; + for (int i = 1; i < N; i++) { + dp[i][0] = s[i] == t[0] ? (dp[i - 1][0] + 1) : dp[i - 1][0]; + } + for (int i = 1; i < N; i++) { + for (int j = 1; j <= Math.min(i, M - 1); j++) { + dp[i][j] = dp[i - 1][j]; + if (s[i] == t[j]) { + dp[i][j] += dp[i - 1][j - 1]; + } + } + } + return dp[N - 1][M - 1]; + } + + public static void main(String[] args) { + String S = "1212311112121132"; + String T = "13"; + + System.out.println(numDistinct3(S, T)); + System.out.println(dp(S, T)); + + } + +} diff --git a/大厂刷题班/class17/Code05_DistinctSubseqValue.java b/大厂刷题班/class17/Code05_DistinctSubseqValue.java new file mode 100644 index 0000000..17f00d5 --- /dev/null +++ b/大厂刷题班/class17/Code05_DistinctSubseqValue.java @@ -0,0 +1,50 @@ +package class17; + +import java.util.HashMap; + +// 本题测试链接 : https://leetcode.com/problems/distinct-subsequences-ii/ +public class Code05_DistinctSubseqValue { + + public static int distinctSubseqII(String s) { + if (s == null || s.length() == 0) { + return 0; + } + int m = 1000000007; + char[] str = s.toCharArray(); + int[] count = new int[26]; + int all = 1; // 算空集 + for (char x : str) { + int add = (all - count[x - 'a'] + m) % m; + all = (all + add) % m; + count[x - 'a'] = (count[x - 'a'] + add) % m; + } + return all - 1; + } + + public static int zuo(String s) { + if (s == null || s.length() == 0) { + return 0; + } + int m = 1000000007; + char[] str = s.toCharArray(); + HashMap map = new HashMap<>(); + int all = 1; // 一个字符也没遍历的时候,有空集 + for (char x : str) { + int newAdd = all; +// int curAll = all + newAdd - (map.containsKey(x) ? map.get(x) : 0); + int curAll = all; + curAll = (curAll + newAdd) % m; + curAll = (curAll - (map.containsKey(x) ? map.get(x) : 0) + m) % m; + all = curAll; + map.put(x, newAdd); + } + return all; + } + + public static void main(String[] args) { + String s = "bccaccbaabbc"; + System.out.println(distinctSubseqII(s) + 1); + System.out.println(zuo(s)); + } + +} diff --git a/大厂刷题班/class18/Code01_HanoiProblem.java b/大厂刷题班/class18/Code01_HanoiProblem.java new file mode 100644 index 0000000..588377e --- /dev/null +++ b/大厂刷题班/class18/Code01_HanoiProblem.java @@ -0,0 +1,97 @@ +package class18; + +public class Code01_HanoiProblem { + + public static int step1(int[] arr) { + if (arr == null || arr.length == 0) { + return -1; + } + return process(arr, arr.length - 1, 1, 2, 3); + } + + // 目标是: 把0~i的圆盘,从from全部挪到to上 + // 返回,根据arr中的状态arr[0..i],它是最优解的第几步? + // f(i, 3 , 2, 1) f(i, 1, 3, 2) f(i, 3, 1, 2) + public static int process(int[] arr, int i, int from, int other, int to) { + if (i == -1) { + return 0; + } + if (arr[i] != from && arr[i] != to) { + return -1; + } + if (arr[i] == from) { // 第一大步没走完 + return process(arr, i - 1, from, to, other); + } else { // arr[i] == to + // 已经走完1,2两步了, + int rest = process(arr, i - 1, other, from, to); // 第三大步完成的程度 + if (rest == -1) { + return -1; + } + return (1 << i) + rest; + } + } + + public static int step2(int[] arr) { + if (arr == null || arr.length == 0) { + return -1; + } + int from = 1; + int mid = 2; + int to = 3; + int i = arr.length - 1; + int res = 0; + int tmp = 0; + while (i >= 0) { + if (arr[i] != from && arr[i] != to) { + return -1; + } + if (arr[i] == to) { + res += 1 << i; + tmp = from; + from = mid; + } else { + tmp = to; + to = mid; + } + mid = tmp; + i--; + } + return res; + } + + public static int kth(int[] arr) { + int N = arr.length; + return step(arr, N - 1, 1, 3, 2); + } + + // 0...index这些圆盘,arr[0..index] index+1层塔 + // 在哪?from 去哪?to 另一个是啥?other + // arr[0..index]这些状态,是index+1层汉诺塔问题的,最优解第几步 + public static int step(int[] arr, int index, int from, int to, int other) { + if (index == -1) { + return 0; + } + if (arr[index] == other) { + return -1; + } + // arr[index] == from arr[index] == to; + if (arr[index] == from) { + return step(arr, index - 1, from, other, to); + } else { + int p1 = (1 << index) - 1; + int p2 = 1; + int p3 = step(arr, index - 1, other, to, from); + if (p3 == -1) { + return -1; + } + return p1 + p2 + p3; + } + } + + public static void main(String[] args) { + int[] arr = { 3, 3, 2, 1 }; + System.out.println(step1(arr)); + System.out.println(step2(arr)); + System.out.println(kth(arr)); + } +} diff --git a/大厂刷题班/class18/Code02_ShortestBridge.java b/大厂刷题班/class18/Code02_ShortestBridge.java new file mode 100644 index 0000000..cac7b20 --- /dev/null +++ b/大厂刷题班/class18/Code02_ShortestBridge.java @@ -0,0 +1,96 @@ +package class18; + +// 本题测试链接 : https://leetcode.com/problems/shortest-bridge/ +public class Code02_ShortestBridge { + + public static int shortestBridge(int[][] m) { + int N = m.length; + int M = m[0].length; + int all = N * M; + int island = 0; + int[] curs = new int[all]; + int[] nexts = new int[all]; + int[][] records = new int[2][all]; + for (int i = 0; i < N; i++) { + for (int j = 0; j < M; j++) { + if (m[i][j] == 1) { // 当前位置发现了1! + // 把这一片的1,都变成2,同时,抓上来了,这一片1组成的初始队列 + // curs, 把这一片的1到自己的距离,都设置成1了,records + int queueSize = infect(m, i, j, N, M, curs, 0, records[island]); + int V = 1; + while (queueSize != 0) { + V++; + // curs里面的点,上下左右,records[点]==0, nexts + queueSize = bfs(N, M, all, V, curs, queueSize, nexts, records[island]); + int[] tmp = curs; + curs = nexts; + nexts = tmp; + } + island++; + } + } + } + int min = Integer.MAX_VALUE; + for (int i = 0; i < all; i++) { + min = Math.min(min, records[0][i] + records[1][i]); + } + return min - 3; + } + + // 当前来到m[i][j] , 总行数是N,总列数是M + // m[i][j]感染出去(找到这一片岛所有的1),把每一个1的坐标,放入到int[] curs队列! + // 1 (a,b) -> curs[index++] = (a * M + b) + // 1 (c,d) -> curs[index++] = (c * M + d) + // 二维已经变成一维了, 1 (a,b) -> a * M + b + // 设置距离record[a * M +b ] = 1 + public static int infect(int[][] m, int i, int j, int N, int M, int[] curs, int index, int[] record) { + if (i < 0 || i == N || j < 0 || j == M || m[i][j] != 1) { + return index; + } + // m[i][j] 不越界,且m[i][j] == 1 + m[i][j] = 2; + int p = i * M + j; + record[p] = 1; + // 收集到不同的1 + curs[index++] = p; + index = infect(m, i - 1, j, N, M, curs, index, record); + index = infect(m, i + 1, j, N, M, curs, index, record); + index = infect(m, i, j - 1, N, M, curs, index, record); + index = infect(m, i, j + 1, N, M, curs, index, record); + return index; + } + + // 二维原始矩阵中,N总行数,M总列数 + // all 总 all = N * M + // V 要生成的是第几层 curs V-1 nexts V + // record里面拿距离 + public static int bfs(int N, int M, int all, int V, + int[] curs, int size, int[] nexts, int[] record) { + int nexti = 0; // 我要生成的下一层队列成长到哪了? + for (int i = 0; i < size; i++) { + // curs[i] -> 一个位置 + int up = curs[i] < M ? -1 : curs[i] - M; + int down = curs[i] + M >= all ? -1 : curs[i] + M; + int left = curs[i] % M == 0 ? -1 : curs[i] - 1; + int right = curs[i] % M == M - 1 ? -1 : curs[i] + 1; + if (up != -1 && record[up] == 0) { + record[up] = V; + nexts[nexti++] = up; + } + if (down != -1 && record[down] == 0) { + record[down] = V; + nexts[nexti++] = down; + } + if (left != -1 && record[left] == 0) { + record[left] = V; + nexts[nexti++] = left; + } + if (right != -1 && record[right] == 0) { + record[right] = V; + nexts[nexti++] = right; + } + } + return nexti; + } + +} diff --git a/大厂刷题班/class18/Code03_CherryPickup.java b/大厂刷题班/class18/Code03_CherryPickup.java new file mode 100644 index 0000000..07b9c54 --- /dev/null +++ b/大厂刷题班/class18/Code03_CherryPickup.java @@ -0,0 +1,70 @@ +package class18; + +// 牛客的测试链接: +// https://www.nowcoder.com/questionTerminal/8ecfe02124674e908b2aae65aad4efdf +// 把如下的全部代码拷贝进java编辑器 +// 把文件大类名字改成Main,可以直接通过 + +import java.util.Scanner; + +public class Code03_CherryPickup { + + public static void main(String[] args) { + Scanner sc = new Scanner(System.in); + int N = sc.nextInt(); + int M = sc.nextInt(); + int[][] matrix = new int[N][M]; + for (int i = 0; i < N; i++) { + for (int j = 0; j < M; j++) { + matrix[i][j] = sc.nextInt(); + } + } + int ans = cherryPickup(matrix); + System.out.println(ans); + sc.close(); + } + + public static int cherryPickup(int[][] grid) { + int N = grid.length; + int M = grid[0].length; + int[][][] dp = new int[N][M][N]; + for (int i = 0; i < N; i++) { + for (int j = 0; j < M; j++) { + for (int k = 0; k < N; k++) { + dp[i][j][k] = Integer.MIN_VALUE; + } + } + } + int ans = process(grid, 0, 0, 0, dp); + return ans < 0 ? 0 : ans; + } + + public static int process(int[][] grid, int x1, int y1, int x2, int[][][] dp) { + if (x1 == grid.length || y1 == grid[0].length || x2 == grid.length || x1 + y1 - x2 == grid[0].length) { + return Integer.MIN_VALUE; + } + if (dp[x1][y1][x2] != Integer.MIN_VALUE) { + return dp[x1][y1][x2]; + } + if (x1 == grid.length - 1 && y1 == grid[0].length - 1) { + dp[x1][y1][x2] = grid[x1][y1]; + return dp[x1][y1][x2]; + } + int next = Integer.MIN_VALUE; + next = Math.max(next, process(grid, x1 + 1, y1, x2 + 1, dp)); + next = Math.max(next, process(grid, x1 + 1, y1, x2, dp)); + next = Math.max(next, process(grid, x1, y1 + 1, x2, dp)); + next = Math.max(next, process(grid, x1, y1 + 1, x2 + 1, dp)); + if (grid[x1][y1] == -1 || grid[x2][x1 + y1 - x2] == -1 || next == -1) { + dp[x1][y1][x2] = -1; + return dp[x1][y1][x2]; + } + if (x1 == x2) { + dp[x1][y1][x2] = grid[x1][y1] + next; + return dp[x1][y1][x2]; + } + dp[x1][y1][x2] = grid[x1][y1] + grid[x2][x1 + y1 - x2] + next; + return dp[x1][y1][x2]; + } + +} \ No newline at end of file diff --git a/大厂刷题班/class18/Code04_TopKSumCrossTwoArrays.java b/大厂刷题班/class18/Code04_TopKSumCrossTwoArrays.java new file mode 100644 index 0000000..9820986 --- /dev/null +++ b/大厂刷题班/class18/Code04_TopKSumCrossTwoArrays.java @@ -0,0 +1,93 @@ +package class18; + +// 牛客的测试链接: +// https://www.nowcoder.com/practice/7201cacf73e7495aa5f88b223bbbf6d1 +// 不要提交包信息,把import底下的类名改成Main,提交下面的代码可以直接通过 +// 因为测试平台会卡空间,所以把set换成了动态加和减的结构 + +import java.util.Scanner; +import java.util.Comparator; +import java.util.HashSet; +import java.util.PriorityQueue; + +public class Code04_TopKSumCrossTwoArrays { + + public static void main(String[] args) { + Scanner sc = new Scanner(System.in); + int N = sc.nextInt(); + int K = sc.nextInt(); + int[] arr1 = new int[N]; + int[] arr2 = new int[N]; + for (int i = 0; i < N; i++) { + arr1[i] = sc.nextInt(); + } + for (int i = 0; i < N; i++) { + arr2[i] = sc.nextInt(); + } + int[] topK = topKSum(arr1, arr2, K); + for (int i = 0; i < K; i++) { + System.out.print(topK[i] + " "); + } + System.out.println(); + sc.close(); + } + + // 放入大根堆中的结构 + public static class Node { + public int index1;// arr1中的位置 + public int index2;// arr2中的位置 + public int sum;// arr1[index1] + arr2[index2]的值 + + public Node(int i1, int i2, int s) { + index1 = i1; + index2 = i2; + sum = s; + } + } + + // 生成大根堆的比较器 + public static class MaxHeapComp implements Comparator { + @Override + public int compare(Node o1, Node o2) { + return o2.sum - o1.sum; + } + } + + public static int[] topKSum(int[] arr1, int[] arr2, int topK) { + if (arr1 == null || arr2 == null || topK < 1) { + return null; + } + int N = arr1.length; + int M = arr2.length; + topK = Math.min(topK, N * M); + int[] res = new int[topK]; + int resIndex = 0; + PriorityQueue maxHeap = new PriorityQueue<>(new MaxHeapComp()); + HashSet set = new HashSet<>(); + int i1 = N - 1; + int i2 = M - 1; + maxHeap.add(new Node(i1, i2, arr1[i1] + arr2[i2])); + set.add(x(i1, i2, M)); + while (resIndex != topK) { + Node curNode = maxHeap.poll(); + res[resIndex++] = curNode.sum; + i1 = curNode.index1; + i2 = curNode.index2; + set.remove(x(i1, i2, M)); + if (i1 - 1 >= 0 && !set.contains(x(i1 - 1, i2, M))) { + set.add(x(i1 - 1, i2, M)); + maxHeap.add(new Node(i1 - 1, i2, arr1[i1 - 1] + arr2[i2])); + } + if (i2 - 1 >= 0 && !set.contains(x(i1, i2 - 1, M))) { + set.add(x(i1, i2 - 1, M)); + maxHeap.add(new Node(i1, i2 - 1, arr1[i1] + arr2[i2 - 1])); + } + } + return res; + } + + public static long x(int i1, int i2, int M) { + return (long) i1 * (long) M + (long) i2; + } + +} diff --git a/大厂刷题班/class19/Code01_LRUCache.java b/大厂刷题班/class19/Code01_LRUCache.java new file mode 100644 index 0000000..4cae760 --- /dev/null +++ b/大厂刷题班/class19/Code01_LRUCache.java @@ -0,0 +1,142 @@ +package class19; + +import java.util.HashMap; + +// 本题测试链接 : https://leetcode.com/problems/lru-cache/ +// 提交时把类名和构造方法名改成 : LRUCache +public class Code01_LRUCache { + + public Code01_LRUCache(int capacity) { + cache = new MyCache<>(capacity); + } + + private MyCache cache; + + public int get(int key) { + Integer ans = cache.get(key); + return ans == null ? -1 : ans; + } + + public void put(int key, int value) { + cache.set(key, value); + } + + public static class Node { + public K key; + public V value; + public Node last; + public Node next; + + public Node(K key, V value) { + this.key = key; + this.value = value; + } + } + + public static class NodeDoubleLinkedList { + private Node head; + private Node tail; + + public NodeDoubleLinkedList() { + head = null; + tail = null; + } + + // 现在来了一个新的node,请挂到尾巴上去 + public void addNode(Node newNode) { + if (newNode == null) { + return; + } + if (head == null) { + head = newNode; + tail = newNode; + } else { + tail.next = newNode; + newNode.last = tail; + tail = newNode; + } + } + + // node 入参,一定保证!node在双向链表里! + // node原始的位置,左右重新连好,然后把node分离出来 + // 挂到整个链表的尾巴上 + public void moveNodeToTail(Node node) { + if (tail == node) { + return; + } + if (head == node) { + head = node.next; + head.last = null; + } else { + node.last.next = node.next; + node.next.last = node.last; + } + node.last = tail; + node.next = null; + tail.next = node; + tail = node; + } + + public Node removeHead() { + if (head == null) { + return null; + } + Node res = head; + if (head == tail) { + head = null; + tail = null; + } else { + head = res.next; + res.next = null; + head.last = null; + } + return res; + } + + } + + public static class MyCache { + private HashMap> keyNodeMap; + private NodeDoubleLinkedList nodeList; + private final int capacity; + + public MyCache(int cap) { + keyNodeMap = new HashMap>(); + nodeList = new NodeDoubleLinkedList(); + capacity = cap; + } + + public V get(K key) { + if (keyNodeMap.containsKey(key)) { + Node res = keyNodeMap.get(key); + nodeList.moveNodeToTail(res); + return res.value; + } + return null; + } + + // set(Key, Value) + // 新增 更新value的操作 + public void set(K key, V value) { + if (keyNodeMap.containsKey(key)) { + Node node = keyNodeMap.get(key); + node.value = value; + nodeList.moveNodeToTail(node); + } else { // 新增! + Node newNode = new Node(key, value); + keyNodeMap.put(key, newNode); + nodeList.addNode(newNode); + if (keyNodeMap.size() == capacity + 1) { + removeMostUnusedCache(); + } + } + } + + private void removeMostUnusedCache() { + Node removeNode = nodeList.removeHead(); + keyNodeMap.remove(removeNode.key); + } + + } + +} diff --git a/大厂刷题班/class19/Code02_LFUCache.java b/大厂刷题班/class19/Code02_LFUCache.java new file mode 100644 index 0000000..30ed43a --- /dev/null +++ b/大厂刷题班/class19/Code02_LFUCache.java @@ -0,0 +1,204 @@ +package class19; + +import java.util.HashMap; + +// 本题测试链接 : https://leetcode.com/problems/lfu-cache/ +// 提交时把类名和构造方法名改为 : LFUCache +public class Code02_LFUCache { + + public Code02_LFUCache(int K) { + capacity = K; + size = 0; + records = new HashMap<>(); + heads = new HashMap<>(); + headList = null; + } + + private int capacity; // 缓存的大小限制,即K + private int size; // 缓存目前有多少个节点 + private HashMap records;// 表示key(Integer)由哪个节点(Node)代表 + private HashMap heads; // 表示节点(Node)在哪个桶(NodeList)里 + private NodeList headList; // 整个结构中位于最左的桶 + + // 节点的数据结构 + public static class Node { + public Integer key; + public Integer value; + public Integer times; // 这个节点发生get或者set的次数总和 + public Node up; // 节点之间是双向链表所以有上一个节点 + public Node down;// 节点之间是双向链表所以有下一个节点 + + public Node(int k, int v, int t) { + key = k; + value = v; + times = t; + } + } + + // 桶结构 + public static class NodeList { + public Node head; // 桶的头节点 + public Node tail; // 桶的尾节点 + public NodeList last; // 桶之间是双向链表所以有前一个桶 + public NodeList next; // 桶之间是双向链表所以有后一个桶 + + public NodeList(Node node) { + head = node; + tail = node; + } + + // 把一个新的节点加入这个桶,新的节点都放在顶端变成新的头部 + public void addNodeFromHead(Node newHead) { + newHead.down = head; + head.up = newHead; + head = newHead; + } + + // 判断这个桶是不是空的 + public boolean isEmpty() { + return head == null; + } + + // 删除node节点并保证node的上下环境重新连接 + public void deleteNode(Node node) { + if (head == tail) { + head = null; + tail = null; + } else { + if (node == head) { + head = node.down; + head.up = null; + } else if (node == tail) { + tail = node.up; + tail.down = null; + } else { + node.up.down = node.down; + node.down.up = node.up; + } + } + node.up = null; + node.down = null; + } + } + + // removeNodeList:刚刚减少了一个节点的桶 + // 这个函数的功能是,判断刚刚减少了一个节点的桶是不是已经空了。 + // 1)如果不空,什么也不做 + // + // 2)如果空了,removeNodeList还是整个缓存结构最左的桶(headList)。 + // 删掉这个桶的同时也要让最左的桶变成removeNodeList的下一个。 + // + // 3)如果空了,removeNodeList不是整个缓存结构最左的桶(headList)。 + // 把这个桶删除,并保证上一个的桶和下一个桶之间还是双向链表的连接方式 + // + // 函数的返回值表示刚刚减少了一个节点的桶是不是已经空了,空了返回true;不空返回false + private boolean modifyHeadList(NodeList removeNodeList) { + if (removeNodeList.isEmpty()) { + if (headList == removeNodeList) { + headList = removeNodeList.next; + if (headList != null) { + headList.last = null; + } + } else { + removeNodeList.last.next = removeNodeList.next; + if (removeNodeList.next != null) { + removeNodeList.next.last = removeNodeList.last; + } + } + return true; + } + return false; + } + + // 函数的功能 + // node这个节点的次数+1了,这个节点原来在oldNodeList里。 + // 把node从oldNodeList删掉,然后放到次数+1的桶中 + // 整个过程既要保证桶之间仍然是双向链表,也要保证节点之间仍然是双向链表 + private void move(Node node, NodeList oldNodeList) { + oldNodeList.deleteNode(node); + // preList表示次数+1的桶的前一个桶是谁 + // 如果oldNodeList删掉node之后还有节点,oldNodeList就是次数+1的桶的前一个桶 + // 如果oldNodeList删掉node之后空了,oldNodeList是需要删除的,所以次数+1的桶的前一个桶,是oldNodeList的前一个 + NodeList preList = modifyHeadList(oldNodeList) ? oldNodeList.last : oldNodeList; + // nextList表示次数+1的桶的后一个桶是谁 + NodeList nextList = oldNodeList.next; + if (nextList == null) { + NodeList newList = new NodeList(node); + if (preList != null) { + preList.next = newList; + } + newList.last = preList; + if (headList == null) { + headList = newList; + } + heads.put(node, newList); + } else { + if (nextList.head.times.equals(node.times)) { + nextList.addNodeFromHead(node); + heads.put(node, nextList); + } else { + NodeList newList = new NodeList(node); + if (preList != null) { + preList.next = newList; + } + newList.last = preList; + newList.next = nextList; + nextList.last = newList; + if (headList == nextList) { + headList = newList; + } + heads.put(node, newList); + } + } + } + + public void put(int key, int value) { + if (capacity == 0) { + return; + } + if (records.containsKey(key)) { + Node node = records.get(key); + node.value = value; + node.times++; + NodeList curNodeList = heads.get(node); + move(node, curNodeList); + } else { + if (size == capacity) { + Node node = headList.tail; + headList.deleteNode(node); + modifyHeadList(headList); + records.remove(node.key); + heads.remove(node); + size--; + } + Node node = new Node(key, value, 1); + if (headList == null) { + headList = new NodeList(node); + } else { + if (headList.head.times.equals(node.times)) { + headList.addNodeFromHead(node); + } else { + NodeList newList = new NodeList(node); + newList.next = headList; + headList.last = newList; + headList = newList; + } + } + records.put(key, node); + heads.put(node, headList); + size++; + } + } + + public int get(int key) { + if (!records.containsKey(key)) { + return -1; + } + Node node = records.get(key); + node.times++; + NodeList curNodeList = heads.get(node); + move(node, curNodeList); + return node.value; + } + +} \ No newline at end of file diff --git a/大厂刷题班/class19/Code03_OneNumber.java b/大厂刷题班/class19/Code03_OneNumber.java new file mode 100644 index 0000000..36f2cd6 --- /dev/null +++ b/大厂刷题班/class19/Code03_OneNumber.java @@ -0,0 +1,83 @@ +package class19; + +public class Code03_OneNumber { + + public static int solution1(int num) { + if (num < 1) { + return 0; + } + int count = 0; + for (int i = 1; i != num + 1; i++) { + count += get1Nums(i); + } + return count; + } + + public static int get1Nums(int num) { + int res = 0; + while (num != 0) { + if (num % 10 == 1) { + res++; + } + num /= 10; + } + return res; + } + + + // 1 ~ num 这个范围上,画了几道1 + public static int solution2(int num) { + if (num < 1) { + return 0; + } + // num -> 13625 + // len = 5位数 + int len = getLenOfNum(num); + if (len == 1) { + return 1; + } + // num 13625 + // tmp1 10000 + // + // num 7872328738273 + // tmp1 1000000000000 + int tmp1 = powerBaseOf10(len - 1); + // num最高位 num / tmp1 + int first = num / tmp1; + // 最高1 N % tmp1 + 1 + // 最高位first tmp1 + int firstOneNum = first == 1 ? num % tmp1 + 1 : tmp1; + // 除去最高位之外,剩下1的数量 + // 最高位1 10(k-2次方) * (k-1) * 1 + // 最高位first 10(k-2次方) * (k-1) * first + int otherOneNum = first * (len - 1) * (tmp1 / 10); + return firstOneNum + otherOneNum + solution2(num % tmp1); + } + + public static int getLenOfNum(int num) { + int len = 0; + while (num != 0) { + len++; + num /= 10; + } + return len; + } + + public static int powerBaseOf10(int base) { + return (int) Math.pow(10, base); + } + + public static void main(String[] args) { + int num = 50000000; + long start1 = System.currentTimeMillis(); + System.out.println(solution1(num)); + long end1 = System.currentTimeMillis(); + System.out.println("cost time: " + (end1 - start1) + " ms"); + + long start2 = System.currentTimeMillis(); + System.out.println(solution2(num)); + long end2 = System.currentTimeMillis(); + System.out.println("cost time: " + (end2 - start2) + " ms"); + + } +} diff --git a/大厂刷题班/class19/Code04_SmallestRangeCoveringElementsfromKLists.java b/大厂刷题班/class19/Code04_SmallestRangeCoveringElementsfromKLists.java new file mode 100644 index 0000000..5a49728 --- /dev/null +++ b/大厂刷题班/class19/Code04_SmallestRangeCoveringElementsfromKLists.java @@ -0,0 +1,58 @@ +package class19; + +import java.util.Comparator; +import java.util.List; +import java.util.TreeSet; + +// 本题测试链接 : https://leetcode.com/problems/smallest-range-covering-elements-from-k-lists/ +public class Code04_SmallestRangeCoveringElementsfromKLists { + + public static class Node { + public int value; + public int arrid; + public int index; + + public Node(int v, int ai, int i) { + value = v; + arrid = ai; + index = i; + } + } + + public static class NodeComparator implements Comparator { + + @Override + public int compare(Node o1, Node o2) { + return o1.value != o2.value ? o1.value - o2.value : o1.arrid - o2.arrid; + } + + } + + public static int[] smallestRange(List> nums) { + int N = nums.size(); + TreeSet orderSet = new TreeSet<>(new NodeComparator()); + for (int i = 0; i < N; i++) { + orderSet.add(new Node(nums.get(i).get(0), i, 0)); + } + boolean set = false; + int a = 0; + int b = 0; + while (orderSet.size() == N) { + Node min = orderSet.first(); + Node max = orderSet.last(); + if (!set || (max.value - min.value < b - a)) { + set = true; + a = min.value; + b = max.value; + } + min = orderSet.pollFirst(); + int arrid = min.arrid; + int index = min.index + 1; + if (index != nums.get(arrid).size()) { + orderSet.add(new Node(nums.get(arrid).get(index), arrid, index)); + } + } + return new int[] { a, b }; + } + +} diff --git a/大厂刷题班/class19/Code05_CardsProblem.java b/大厂刷题班/class19/Code05_CardsProblem.java new file mode 100644 index 0000000..5b9092c --- /dev/null +++ b/大厂刷题班/class19/Code05_CardsProblem.java @@ -0,0 +1,166 @@ +package class19; + +import java.util.LinkedList; + +/* + * 一张扑克有3个属性,每种属性有3种值(A、B、C) + * 比如"AAA",第一个属性值A,第二个属性值A,第三个属性值A + * 比如"BCA",第一个属性值B,第二个属性值C,第三个属性值A + * 给定一个字符串类型的数组cards[],每一个字符串代表一张扑克 + * 从中挑选三张扑克,一个属性达标的条件是:这个属性在三张扑克中全一样,或全不一样 + * 挑选的三张扑克达标的要求是:每种属性都满足上面的条件 + * 比如:"ABC"、"CBC"、"BBC" + * 第一张第一个属性为"A"、第二张第一个属性为"C"、第三张第一个属性为"B",全不一样 + * 第一张第二个属性为"B"、第二张第二个属性为"B"、第三张第二个属性为"B",全一样 + * 第一张第三个属性为"C"、第二张第三个属性为"C"、第三张第三个属性为"C",全一样 + * 每种属性都满足在三张扑克中全一样,或全不一样,所以这三张扑克达标 + * 返回在cards[]中任意挑选三张扑克,达标的方法数 + * + * */ +public class Code05_CardsProblem { + + public static int ways1(String[] cards) { + LinkedList picks = new LinkedList<>(); + return process1(cards, 0, picks); + } + + public static int process1(String[] cards, int index, LinkedList picks) { + if (picks.size() == 3) { + return getWays1(picks); + } + if (index == cards.length) { + return 0; + } + int ways = process1(cards, index + 1, picks); + picks.addLast(cards[index]); + ways += process1(cards, index + 1, picks); + picks.pollLast(); + return ways; + } + + public static int getWays1(LinkedList picks) { + char[] s1 = picks.get(0).toCharArray(); + char[] s2 = picks.get(1).toCharArray(); + char[] s3 = picks.get(2).toCharArray(); + for (int i = 0; i < 3; i++) { + if ((s1[i] != s2[i] && s1[i] != s3[i] && s2[i] != s3[i]) || (s1[i] == s2[i] && s1[i] == s3[i])) { + continue; + } + return 0; + } + return 1; + } + + public static int ways2(String[] cards) { + int[] counts = new int[27]; + for (String s : cards) { + char[] str = s.toCharArray(); + counts[(str[0] - 'A') * 9 + (str[1] - 'A') * 3 + (str[2] - 'A') * 1]++; + } + int ways = 0; + for (int status = 0; status < 27; status++) { + int n = counts[status]; + if (n > 2) { + ways += n == 3 ? 1 : (n * (n - 1) * (n - 2) / 6); + } + } + LinkedList path = new LinkedList<>(); + for (int i = 0; i < 27; i++) { + if (counts[i] != 0) { + path.addLast(i); + ways += process2(counts, i, path); + path.pollLast(); + } + } + return ways; + } + + // 之前的牌面,拿了一些 ABC BBB ... + // pre = BBB + // ABC ... + // pre = ABC + // ABC BBB CAB + // pre = CAB + // 牌面一定要依次变大,所有形成的有效牌面,把方法数返回 + public static int process2(int[] counts, int pre, LinkedList path) { + if (path.size() == 3) { + return getWays2(counts, path); + } + int ways = 0; + for (int next = pre + 1; next < 27; next++) { + if (counts[next] != 0) { + path.addLast(next); + ways += process2(counts, next, path); + path.pollLast(); + } + } + return ways; + } + + public static int getWays2(int[] counts, LinkedList path) { + int v1 = path.get(0); + int v2 = path.get(1); + int v3 = path.get(2); + for (int i = 9; i > 0; i /= 3) { + int cur1 = v1 / i; + int cur2 = v2 / i; + int cur3 = v3 / i; + v1 %= i; + v2 %= i; + v3 %= i; + if ((cur1 != cur2 && cur1 != cur3 && cur2 != cur3) || (cur1 == cur2 && cur1 == cur3)) { + continue; + } + return 0; + } + v1 = path.get(0); + v2 = path.get(1); + v3 = path.get(2); + return counts[v1] * counts[v2] * counts[v3]; + } + + // for test + public static String[] generateCards(int size) { + int n = (int) (Math.random() * size) + 3; + String[] ans = new String[n]; + for (int i = 0; i < n; i++) { + char cha0 = (char) ((int) (Math.random() * 3) + 'A'); + char cha1 = (char) ((int) (Math.random() * 3) + 'A'); + char cha2 = (char) ((int) (Math.random() * 3) + 'A'); + ans[i] = String.valueOf(cha0) + String.valueOf(cha1) + String.valueOf(cha2); + } + return ans; + } + + // for test + public static void main(String[] args) { + int size = 20; + int testTime = 100000; + System.out.println("test begin"); + for (int i = 0; i < testTime; i++) { + String[] arr = generateCards(size); + int ans1 = ways1(arr); + int ans2 = ways2(arr); + if (ans1 != ans2) { + for (String str : arr) { + System.out.println(str); + } + System.out.println(ans1); + System.out.println(ans2); + break; + } + } + System.out.println("test finish"); + + long start = 0; + long end = 0; + String[] arr = generateCards(10000000); + System.out.println("arr size : " + arr.length + " runtime test begin"); + start = System.currentTimeMillis(); + ways2(arr); + end = System.currentTimeMillis(); + System.out.println("run time : " + (end - start) + " ms"); + System.out.println("runtime test end"); + } + +} diff --git a/大厂刷题班/class20/Code01_PreAndInArrayToPosArray.java b/大厂刷题班/class20/Code01_PreAndInArrayToPosArray.java new file mode 100644 index 0000000..abc2bcc --- /dev/null +++ b/大厂刷题班/class20/Code01_PreAndInArrayToPosArray.java @@ -0,0 +1,233 @@ +package class20; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; + +public class Code01_PreAndInArrayToPosArray { + + public static int[] zuo(int[] pre, int[] in) { + if (pre == null || in == null || pre.length != in.length) { + return null; + } + int N = pre.length; + HashMap inMap = new HashMap<>(); + for (int i = 0; i < N; i++) { + inMap.put(in[i], i); + } + int[] pos = new int[N]; + func(pre, 0, N - 1, in, 0, N - 1, pos, 0, N - 1, inMap); + return pos; + } + + public static void func(int[] pre, int L1, int R1, int[] in, int L2, int R2, int[] pos, int L3, int R3, + HashMap inMap) { + if (L1 > R1) { + return; + } + if (L1 == R1) { + pos[L3] = pre[L1]; + } else { + pos[R3] = pre[L1]; + int index = inMap.get(pre[L1]); + func(pre, L1 + 1, L1 + index - L2, in, L2, index - 1, pos, L3, L3 + index - L2 - 1, inMap); + func(pre, L1 + index - L2 + 1, R1, in, index + 1, R2, pos, L3 + index - L2, R3 - 1, inMap); + } + } + + public static class Node { + public int value; + public Node left; + public Node right; + + public Node(int v) { + value = v; + } + } + + public static int[] preInToPos1(int[] pre, int[] in) { + if (pre == null || in == null || pre.length != in.length) { + return null; + } + int N = pre.length; + int[] pos = new int[N]; + process1(pre, 0, N - 1, in, 0, N - 1, pos, 0, N - 1); + return pos; + } + + // L1...R1 L2...R2 L3...R3 + public static void process1(int[] pre, int L1, int R1, int[] in, int L2, int R2, int[] pos, int L3, int R3) { + if (L1 > R1) { + return; + } + if (L1 == R1) { + pos[L3] = pre[L1]; + return; + } + pos[R3] = pre[L1]; + int mid = L2; + for (; mid <= R2; mid++) { + if (in[mid] == pre[L1]) { + break; + } + } + int leftSize = mid - L2; + process1(pre, L1 + 1, L1 + leftSize, in, L2, mid - 1, pos, L3, L3 + leftSize - 1); + process1(pre, L1 + leftSize + 1, R1, in, mid + 1, R2, pos, L3 + leftSize, R3 - 1); + } + + public static int[] preInToPos2(int[] pre, int[] in) { + if (pre == null || in == null || pre.length != in.length) { + return null; + } + int N = pre.length; + HashMap inMap = new HashMap<>(); + for (int i = 0; i < N; i++) { + inMap.put(in[i], i); + } + int[] pos = new int[N]; + process2(pre, 0, N - 1, in, 0, N - 1, pos, 0, N - 1, inMap); + return pos; + } + + public static void process2(int[] pre, int L1, int R1, int[] in, int L2, int R2, int[] pos, int L3, int R3, + HashMap inMap) { + if (L1 > R1) { + return; + } + if (L1 == R1) { + pos[L3] = pre[L1]; + return; + } + pos[R3] = pre[L1]; + int mid = inMap.get(pre[L1]); + int leftSize = mid - L2; + process2(pre, L1 + 1, L1 + leftSize, in, L2, mid - 1, pos, L3, L3 + leftSize - 1, inMap); + process2(pre, L1 + leftSize + 1, R1, in, mid + 1, R2, pos, L3 + leftSize, R3 - 1, inMap); + } + + // for test + public static int[] getPreArray(Node head) { + ArrayList arr = new ArrayList<>(); + fillPreArray(head, arr); + int[] ans = new int[arr.size()]; + for (int i = 0; i < ans.length; i++) { + ans[i] = arr.get(i); + } + return ans; + } + + // for test + public static void fillPreArray(Node head, ArrayList arr) { + if (head == null) { + return; + } + arr.add(head.value); + fillPreArray(head.left, arr); + fillPreArray(head.right, arr); + } + + // for test + public static int[] getInArray(Node head) { + ArrayList arr = new ArrayList<>(); + fillInArray(head, arr); + int[] ans = new int[arr.size()]; + for (int i = 0; i < ans.length; i++) { + ans[i] = arr.get(i); + } + return ans; + } + + // for test + public static void fillInArray(Node head, ArrayList arr) { + if (head == null) { + return; + } + fillInArray(head.left, arr); + arr.add(head.value); + fillInArray(head.right, arr); + } + + // for test + public static int[] getPosArray(Node head) { + ArrayList arr = new ArrayList<>(); + fillPostArray(head, arr); + int[] ans = new int[arr.size()]; + for (int i = 0; i < ans.length; i++) { + ans[i] = arr.get(i); + } + return ans; + } + + // for test + public static void fillPostArray(Node head, ArrayList arr) { + if (head == null) { + return; + } + fillPostArray(head.left, arr); + fillPostArray(head.right, arr); + arr.add(head.value); + } + + // for test + public static Node generateRandomTree(int value, int maxLevel) { + HashSet hasValue = new HashSet(); + return createTree(value, 1, maxLevel, hasValue); + } + + // for test + public static Node createTree(int value, int level, int maxLevel, HashSet hasValue) { + if (level > maxLevel) { + return null; + } + int cur = 0; + do { + cur = (int) (Math.random() * value) + 1; + } while (hasValue.contains(cur)); + hasValue.add(cur); + Node head = new Node(cur); + head.left = createTree(value, level + 1, maxLevel, hasValue); + head.right = createTree(value, level + 1, maxLevel, hasValue); + return head; + } + + // for test + public static boolean isEqual(int[] arr1, int[] arr2) { + if ((arr1 == null && arr2 != null) || (arr1 != null && arr2 == null)) { + return false; + } + if (arr1 == null && arr2 == null) { + return true; + } + if (arr1.length != arr2.length) { + return false; + } + for (int i = 0; i < arr1.length; i++) { + if (arr1[i] != arr2[i]) { + return false; + } + } + return true; + } + + public static void main(String[] args) { + System.out.println("test begin"); + int maxLevel = 5; + int value = 1000; + int testTime = 100000; + for (int i = 0; i < testTime; i++) { + Node head = generateRandomTree(value, maxLevel); + int[] pre = getPreArray(head); + int[] in = getInArray(head); + int[] pos = getPosArray(head); + int[] ans1 = preInToPos1(pre, in); + int[] ans2 = preInToPos2(pre, in); + int[] classAns = zuo(pre, in); + if (!isEqual(pos, ans1) || !isEqual(ans1, ans2) || !isEqual(pos, classAns)) { + System.out.println("Oops!"); + } + } + System.out.println("test end"); + + } +} diff --git a/大厂刷题班/class20/Code02_LargestComponentSizebyCommonFactor.java b/大厂刷题班/class20/Code02_LargestComponentSizebyCommonFactor.java new file mode 100644 index 0000000..bb746cb --- /dev/null +++ b/大厂刷题班/class20/Code02_LargestComponentSizebyCommonFactor.java @@ -0,0 +1,109 @@ +package class20; + +import java.util.HashMap; + +// 本题为leetcode原题 +// 测试链接:https://leetcode.com/problems/largest-component-size-by-common-factor/ +// 方法1会超时,但是方法2直接通过 +public class Code02_LargestComponentSizebyCommonFactor { + + public static int largestComponentSize1(int[] arr) { + int N = arr.length; + UnionFind set = new UnionFind(N); + for (int i = 0; i < N; i++) { + for (int j = i + 1; j < N; j++) { + if (gcd(arr[i], arr[j]) != 1) { + set.union(i, j); + } + } + } + return set.maxSize(); + } + + public static int largestComponentSize2(int[] arr) { + int N = arr.length; + // arr中,N个位置,在并查集初始时,每个位置自己是一个集合 + UnionFind unionFind = new UnionFind(N); + // key 某个因子 value 哪个位置拥有这个因子 + HashMap fatorsMap = new HashMap<>(); + for (int i = 0; i < N; i++) { + int num = arr[i]; + // 求出根号N, -> limit + int limit = (int) Math.sqrt(num); + for (int j = 1; j <= limit; j++) { + if (num % j == 0) { + if (j != 1) { + if (!fatorsMap.containsKey(j)) { + fatorsMap.put(j, i); + } else { + unionFind.union(fatorsMap.get(j), i); + } + } + int other = num / j; + if (other != 1) { + if (!fatorsMap.containsKey(other)) { + fatorsMap.put(other, i); + } else { + unionFind.union(fatorsMap.get(other), i); + } + } + } + } + } + return unionFind.maxSize(); + } + + // O(1) + // m,n 要是正数,不能有任何一个等于0 + public static int gcd(int a, int b) { + return b == 0 ? a : gcd(b, a % b); + } + + public static class UnionFind { + private int[] parents; + private int[] sizes; + private int[] help; + + public UnionFind(int N) { + parents = new int[N]; + sizes = new int[N]; + help = new int[N]; + for (int i = 0; i < N; i++) { + parents[i] = i; + sizes[i] = 1; + } + } + + public int maxSize() { + int ans = 0; + for (int size : sizes) { + ans = Math.max(ans, size); + } + return ans; + } + + private int find(int i) { + int hi = 0; + while (i != parents[i]) { + help[hi++] = i; + i = parents[i]; + } + for (hi--; hi >= 0; hi--) { + parents[help[hi]] = i; + } + return i; + } + + public void union(int i, int j) { + int f1 = find(i); + int f2 = find(j); + if (f1 != f2) { + int big = sizes[f1] >= sizes[f2] ? f1 : f2; + int small = big == f1 ? f2 : f1; + parents[small] = big; + sizes[big] = sizes[f1] + sizes[f2]; + } + } + } + +} diff --git a/大厂刷题班/class20/Code03_ShuffleProblem.java b/大厂刷题班/class20/Code03_ShuffleProblem.java new file mode 100644 index 0000000..fabd281 --- /dev/null +++ b/大厂刷题班/class20/Code03_ShuffleProblem.java @@ -0,0 +1,154 @@ +package class20; + +import java.util.Arrays; + +public class Code03_ShuffleProblem { + + // 数组的长度为len,调整前的位置是i,返回调整之后的位置 + // 下标不从0开始,从1开始 + public static int modifyIndex1(int i, int len) { + if (i <= len / 2) { + return 2 * i; + } else { + return 2 * (i - (len / 2)) - 1; + } + } + + // 数组的长度为len,调整前的位置是i,返回调整之后的位置 + // 下标不从0开始,从1开始 + public static int modifyIndex2(int i, int len) { + return (2 * i) % (len + 1); + } + + // 主函数 + // 数组必须不为空,且长度为偶数 + public static void shuffle(int[] arr) { + if (arr != null && arr.length != 0 && (arr.length & 1) == 0) { + shuffle(arr, 0, arr.length - 1); + } + } + + // 在arr[L..R]上做完美洗牌的调整(arr[L..R]范围上一定要是偶数个数字) + public static void shuffle(int[] arr, int L, int R) { + while (R - L + 1 > 0) { // 切成一块一块的解决,每一块的长度满足(3^k)-1 + int len = R - L + 1; + int base = 3; + int k = 1; + // 计算小于等于len并且是离len最近的,满足(3^k)-1的数 + // 也就是找到最大的k,满足3^k <= len+1 + while (base <= (len + 1) / 3) { // base > (N+1)/3 + base *= 3; + k++; + } + // 3^k -1 + // 当前要解决长度为base-1的块,一半就是再除2 + int half = (base - 1) / 2; + // [L..R]的中点位置 + int mid = (L + R) / 2; + // 要旋转的左部分为[L+half...mid], 右部分为arr[mid+1..mid+half] + // 注意在这里,arr下标是从0开始的 + rotate(arr, L + half, mid, mid + half); + // 旋转完成后,从L开始算起,长度为base-1的部分进行下标连续推 + cycles(arr, L, base - 1, k); + // 解决了前base-1的部分,剩下的部分继续处理 + L = L + base - 1; // L -> [] [+1...R] + } + } + + // 从start位置开始,往右len的长度这一段,做下标连续推 + // 出发位置依次为1,3,9... + public static void cycles(int[] arr, int start, int len, int k) { + // 找到每一个出发位置trigger,一共k个 + // 每一个trigger都进行下标连续推 + // 出发位置是从1开始算的,而数组下标是从0开始算的。 + for (int i = 0, trigger = 1; i < k; i++, trigger *= 3) { + int preValue = arr[trigger + start - 1]; + int cur = modifyIndex2(trigger, len); + while (cur != trigger) { + int tmp = arr[cur + start - 1]; + arr[cur + start - 1] = preValue; + preValue = tmp; + cur = modifyIndex2(cur, len); + } + arr[cur + start - 1] = preValue; + } + } + + // [L..M]为左部分,[M+1..R]为右部分,左右两部分互换 + public static void rotate(int[] arr, int L, int M, int R) { + reverse(arr, L, M); + reverse(arr, M + 1, R); + reverse(arr, L, R); + } + + // [L..R]做逆序调整 + public static void reverse(int[] arr, int L, int R) { + while (L < R) { + int tmp = arr[L]; + arr[L++] = arr[R]; + arr[R--] = tmp; + } + } + + public static void wiggleSort(int[] arr) { + if (arr == null || arr.length == 0) { + return; + } + // 假设这个排序是额外空间复杂度O(1)的,当然系统提供的排序并不是,你可以自己实现一个堆排序 + Arrays.sort(arr); + if ((arr.length & 1) == 1) { + shuffle(arr, 1, arr.length - 1); + } else { + shuffle(arr, 0, arr.length - 1); + for (int i = 0; i < arr.length; i += 2) { + int tmp = arr[i]; + arr[i] = arr[i + 1]; + arr[i + 1] = tmp; + } + } + } + + // for test + public static boolean isValidWiggle(int[] arr) { + for (int i = 1; i < arr.length; i++) { + if ((i & 1) == 1 && arr[i] < arr[i - 1]) { + return false; + } + if ((i & 1) == 0 && arr[i] > arr[i - 1]) { + return false; + } + } + return true; + } + + // for test + public static void printArray(int[] arr) { + for (int i = 0; i < arr.length; i++) { + System.out.print(arr[i] + " "); + } + System.out.println(); + } + + // for test + public static int[] generateArray() { + int len = (int) (Math.random() * 10) * 2; + int[] arr = new int[len]; + for (int i = 0; i < len; i++) { + arr[i] = (int) (Math.random() * 100); + } + return arr; + } + + public static void main(String[] args) { + for (int i = 0; i < 5000000; i++) { + int[] arr = generateArray(); + wiggleSort(arr); + if (!isValidWiggle(arr)) { + System.out.println("ooops!"); + printArray(arr); + break; + } + } + } + +} diff --git a/大厂刷题班/class20/Code04_PalindromeWays.java b/大厂刷题班/class20/Code04_PalindromeWays.java new file mode 100644 index 0000000..0608e0c --- /dev/null +++ b/大厂刷题班/class20/Code04_PalindromeWays.java @@ -0,0 +1,91 @@ +package class20; + +public class Code04_PalindromeWays { + + public static int ways1(String str) { + if (str == null || str.length() == 0) { + return 0; + } + char[] s = str.toCharArray(); + char[] path = new char[s.length]; + return process(str.toCharArray(), 0, path, 0); + } + + public static int process(char[] s, int si, char[] path, int pi) { + if (si == s.length) { + return isP(path, pi) ? 1 : 0; + } + int ans = process(s, si + 1, path, pi); + path[pi] = s[si]; + ans += process(s, si + 1, path, pi + 1); + return ans; + } + + public static boolean isP(char[] path, int pi) { + if (pi == 0) { + return false; + } + int L = 0; + int R = pi - 1; + while (L < R) { + if (path[L++] != path[R--]) { + return false; + } + } + return true; + } + + public static int ways2(String str) { + if (str == null || str.length() == 0) { + return 0; + } + char[] s = str.toCharArray(); + int n = s.length; + int[][] dp = new int[n][n]; + for (int i = 0; i < n; i++) { + dp[i][i] = 1; + } + for (int i = 0; i < n - 1; i++) { + dp[i][i + 1] = s[i] == s[i + 1] ? 3 : 2; + } + for (int L = n - 3; L >= 0; L--) { + for (int R = L + 2; R < n; R++) { + dp[L][R] = dp[L + 1][R] + dp[L][R - 1] - dp[L + 1][R - 1]; + if (s[L] == s[R]) { + dp[L][R] += dp[L + 1][R - 1] + 1; + } + } + } + return dp[0][n - 1]; + } + + public static String randomString(int len, int types) { + char[] str = new char[len]; + for (int i = 0; i < str.length; i++) { + str[i] = (char) ('a' + (int) (Math.random() * types)); + } + return String.valueOf(str); + } + + public static void main(String[] args) { + int N = 10; + int types = 5; + int testTimes = 100000; + System.out.println("测试开始"); + for (int i = 0; i < testTimes; i++) { + int len = (int) (Math.random() * N); + String str = randomString(len, types); + int ans1 = ways1(str); + int ans2 = ways2(str); + if (ans1 != ans2) { + System.out.println(str); + System.out.println(ans1); + System.out.println(ans2); + System.out.println("Oops!"); + break; + } + } + System.out.println("测试结束"); + } + +} diff --git a/大厂刷题班/class21/TreeChainPartition.java b/大厂刷题班/class21/TreeChainPartition.java new file mode 100644 index 0000000..94ff19c --- /dev/null +++ b/大厂刷题班/class21/TreeChainPartition.java @@ -0,0 +1,416 @@ +package class21; + +import java.util.HashMap; + +public class TreeChainPartition { + + public static class TreeChain { + // 时间戳 0 1 2 3 4 + private int tim; + // 节点个数是n,节点编号是1~n + private int n; + // 谁是头 + private int h; + // 朴素树结构 + private int[][] tree; + // 权重数组 原始的0节点权重是6 -> val[1] = 6 + private int[] val; + // father数组一个平移,因为标号要+1 + private int[] fa; + // 深度数组! + private int[] dep; + // son[i] = 0 i这个节点,没有儿子 + // son[i] != 0 j i这个节点,重儿子是j + private int[] son; + // siz[i] i这个节点为头的子树,有多少个节点 + private int[] siz; + // top[i] = j i这个节点,所在的重链,头是j + private int[] top; + // dfn[i] = j i这个节点,在dfs序中是第j个 + private int[] dfn; + // 如果原来的节点a,权重是10 + // 如果a节点在dfs序中是第5个节点, tnw[5] = 10 + private int[] tnw; + // 线段树,在tnw上,玩连续的区间查询或者更新 + private SegmentTree seg; + + public TreeChain(int[] father, int[] values) { + // 原始的树 tree,弄好了,可以从i这个点,找到下级的直接孩子 + // 上面的一大堆结构,准备好了空间,values -> val + // 找到头部点 + initTree(father, values); + // fa; + // dep; + // son; + // siz; + dfs1(h, 0); + // top; + // dfn; + // tnw; + dfs2(h, h); + seg = new SegmentTree(tnw); + seg.build(1, n, 1); + } + + private void initTree(int[] father, int[] values) { + tim = 0; + n = father.length + 1; + tree = new int[n][]; + val = new int[n]; + fa = new int[n]; + dep = new int[n]; + son = new int[n]; + siz = new int[n]; + top = new int[n]; + dfn = new int[n]; + tnw = new int[n--]; + int[] cnum = new int[n]; + for (int i = 0; i < n; i++) { + val[i + 1] = values[i]; + } + for (int i = 0; i < n; i++) { + if (father[i] == i) { + h = i + 1; + } else { + cnum[father[i]]++; + } + } + tree[0] = new int[0]; + for (int i = 0; i < n; i++) { + tree[i + 1] = new int[cnum[i]]; + } + for (int i = 0; i < n; i++) { + if (i + 1 != h) { + tree[father[i] + 1][--cnum[father[i]]] = i + 1; + } + } + } + + // u 当前节点 + // f u的父节点 + private void dfs1(int u, int f) { + fa[u] = f; + dep[u] = dep[f] + 1; + siz[u] = 1; + int maxSize = -1; + for (int v : tree[u]) { // 遍历u节点,所有的直接孩子 + dfs1(v, u); + siz[u] += siz[v]; + if (siz[v] > maxSize) { + maxSize = siz[v]; + son[u] = v; + } + } + } + + // u当前节点 + // t是u所在重链的头部 + private void dfs2(int u, int t) { + dfn[u] = ++tim; + top[u] = t; + tnw[tim] = val[u]; + if (son[u] != 0) { // 如果u有儿子 siz[u] > 1 + dfs2(son[u], t); + for (int v : tree[u]) { + if (v != son[u]) { + dfs2(v, v); + } + } + } + } + + // head为头的子树上,所有节点值+value + // 因为节点经过平移,所以head(原始节点) -> head(平移节点) + public void addSubtree(int head, int value) { + // 原始点编号 -> 平移编号 + head++; + // 平移编号 -> dfs编号 dfn[head] + seg.add(dfn[head], dfn[head] + siz[head] - 1, value, 1, n, 1); + } + + public int querySubtree(int head) { + head++; + return seg.query(dfn[head], dfn[head] + siz[head] - 1, 1, n, 1); + } + + public void addChain(int a, int b, int v) { + a++; + b++; + while (top[a] != top[b]) { + if (dep[top[a]] > dep[top[b]]) { + seg.add(dfn[top[a]], dfn[a], v, 1, n, 1); + a = fa[top[a]]; + } else { + seg.add(dfn[top[b]], dfn[b], v, 1, n, 1); + b = fa[top[b]]; + } + } + if (dep[a] > dep[b]) { + seg.add(dfn[b], dfn[a], v, 1, n, 1); + } else { + seg.add(dfn[a], dfn[b], v, 1, n, 1); + } + } + + public int queryChain(int a, int b) { + a++; + b++; + int ans = 0; + while (top[a] != top[b]) { + if (dep[top[a]] > dep[top[b]]) { + ans += seg.query(dfn[top[a]], dfn[a], 1, n, 1); + a = fa[top[a]]; + } else { + ans += seg.query(dfn[top[b]], dfn[b], 1, n, 1); + b = fa[top[b]]; + } + } + if (dep[a] > dep[b]) { + ans += seg.query(dfn[b], dfn[a], 1, n, 1); + } else { + ans += seg.query(dfn[a], dfn[b], 1, n, 1); + } + return ans; + } + } + + public static class SegmentTree { + private int MAXN; + private int[] arr; + private int[] sum; + private int[] lazy; + + public SegmentTree(int[] origin) { + MAXN = origin.length; + arr = origin; + sum = new int[MAXN << 2]; + lazy = new int[MAXN << 2]; + } + + private void pushUp(int rt) { + sum[rt] = sum[rt << 1] + sum[rt << 1 | 1]; + } + + private void pushDown(int rt, int ln, int rn) { + if (lazy[rt] != 0) { + lazy[rt << 1] += lazy[rt]; + sum[rt << 1] += lazy[rt] * ln; + lazy[rt << 1 | 1] += lazy[rt]; + sum[rt << 1 | 1] += lazy[rt] * rn; + lazy[rt] = 0; + } + } + + public void build(int l, int r, int rt) { + if (l == r) { + sum[rt] = arr[l]; + return; + } + int mid = (l + r) >> 1; + build(l, mid, rt << 1); + build(mid + 1, r, rt << 1 | 1); + pushUp(rt); + } + + public void add(int L, int R, int C, int l, int r, int rt) { + if (L <= l && r <= R) { + sum[rt] += C * (r - l + 1); + lazy[rt] += C; + return; + } + int mid = (l + r) >> 1; + pushDown(rt, mid - l + 1, r - mid); + if (L <= mid) { + add(L, R, C, l, mid, rt << 1); + } + if (R > mid) { + add(L, R, C, mid + 1, r, rt << 1 | 1); + } + pushUp(rt); + } + + public int query(int L, int R, int l, int r, int rt) { + if (L <= l && r <= R) { + return sum[rt]; + } + int mid = (l + r) >> 1; + pushDown(rt, mid - l + 1, r - mid); + int ans = 0; + if (L <= mid) { + ans += query(L, R, l, mid, rt << 1); + } + if (R > mid) { + ans += query(L, R, mid + 1, r, rt << 1 | 1); + } + return ans; + } + + } + + // 为了测试,这个结构是暴力但正确的方法 + public static class Right { + private int n; + private int[][] tree; + private int[] fa; + private int[] val; + private HashMap path; + + public Right(int[] father, int[] value) { + n = father.length; + tree = new int[n][]; + fa = new int[n]; + val = new int[n]; + for (int i = 0; i < n; i++) { + fa[i] = father[i]; + val[i] = value[i]; + } + int[] help = new int[n]; + int h = 0; + for (int i = 0; i < n; i++) { + if (father[i] == i) { + h = i; + } else { + help[father[i]]++; + } + } + for (int i = 0; i < n; i++) { + tree[i] = new int[help[i]]; + } + for (int i = 0; i < n; i++) { + if (i != h) { + tree[father[i]][--help[father[i]]] = i; + } + } + path = new HashMap<>(); + } + + public void addSubtree(int head, int value) { + val[head] += value; + for (int next : tree[head]) { + addSubtree(next, value); + } + } + + public int querySubtree(int head) { + int ans = val[head]; + for (int next : tree[head]) { + ans += querySubtree(next); + } + return ans; + } + + public void addChain(int a, int b, int v) { + path.clear(); + path.put(a, null); + while (a != fa[a]) { + path.put(fa[a], a); + a = fa[a]; + } + while (!path.containsKey(b)) { + val[b] += v; + b = fa[b]; + } + val[b] += v; + while (path.get(b) != null) { + b = path.get(b); + val[b] += v; + } + } + + public int queryChain(int a, int b) { + path.clear(); + path.put(a, null); + while (a != fa[a]) { + path.put(fa[a], a); + a = fa[a]; + } + int ans = 0; + while (!path.containsKey(b)) { + ans += val[b]; + b = fa[b]; + } + ans += val[b]; + while (path.get(b) != null) { + b = path.get(b); + ans += val[b]; + } + return ans; + } + + } + + // 为了测试 + // 随机生成N个节点树,可能是多叉树,并且一定不是森林 + // 输入参数N要大于0 + public static int[] generateFatherArray(int N) { + int[] order = new int[N]; + for (int i = 0; i < N; i++) { + order[i] = i; + } + for (int i = N - 1; i >= 0; i--) { + swap(order, i, (int) (Math.random() * (i + 1))); + } + int[] ans = new int[N]; + ans[order[0]] = order[0]; + for (int i = 1; i < N; i++) { + ans[order[i]] = order[(int) (Math.random() * i)]; + } + return ans; + } + + // 为了测试 + public static void swap(int[] arr, int i, int j) { + int tmp = arr[i]; + arr[i] = arr[j]; + arr[j] = tmp; + } + + // 为了测试 + public static int[] generateValueArray(int N, int V) { + int[] ans = new int[N]; + for (int i = 0; i < N; i++) { + ans[i] = (int) (Math.random() * V) + 1; + } + return ans; + } + + // 对数器 + public static void main(String[] args) { + int N = 50000; + int V = 100000; + int[] father = generateFatherArray(N); + int[] values = generateValueArray(N, V); + TreeChain tc = new TreeChain(father, values); + Right right = new Right(father, values); + int testTime = 1000000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + double decision = Math.random(); + if (decision < 0.25) { + int head = (int) (Math.random() * N); + int value = (int) (Math.random() * V); + tc.addSubtree(head, value); + right.addSubtree(head, value); + } else if (decision < 0.5) { + int head = (int) (Math.random() * N); + if (tc.querySubtree(head) != right.querySubtree(head)) { + System.out.println("出错了!"); + } + } else if (decision < 0.75) { + int a = (int) (Math.random() * N); + int b = (int) (Math.random() * N); + int value = (int) (Math.random() * V); + tc.addChain(a, b, value); + right.addChain(a, b, value); + } else { + int a = (int) (Math.random() * N); + int b = (int) (Math.random() * N); + if (tc.queryChain(a, b) != right.queryChain(a, b)) { + System.out.println("出错了!"); + } + } + } + System.out.println("测试结束"); + } + +} diff --git a/大厂刷题班/class22/Code01_MaximumSumof3NonOverlappingSubarrays.java b/大厂刷题班/class22/Code01_MaximumSumof3NonOverlappingSubarrays.java new file mode 100644 index 0000000..22f2a37 --- /dev/null +++ b/大厂刷题班/class22/Code01_MaximumSumof3NonOverlappingSubarrays.java @@ -0,0 +1,102 @@ +package class22; + +// 本题测试链接 : https://leetcode.com/problems/maximum-sum-of-3-non-overlapping-subarrays/ +public class Code01_MaximumSumof3NonOverlappingSubarrays { + +// public static int[] maxSumArray1(int[] arr) { +// int N = arr.length; +// int[] help = new int[N]; +// // help[i] 子数组必须以i位置结尾的情况下,累加和最大是多少? +// help[0] = arr[0]; +// for (int i = 1; i < N; i++) { +// int p1 = arr[i]; +// int p2 = arr[i] + help[i - 1]; +// help[i] = Math.max(p1, p2); +// } +// // dp[i] 在0~i范围上,随意选一个子数组,累加和最大是多少? +// int[] dp = new int[N]; +// dp[0] = help[0]; +// for (int i = 1; i < N; i++) { +// int p1 = help[i]; +// int p2 = dp[i - 1]; +// dp[i] = Math.max(p1, p2); +// } +// return dp; +// } +// +// public static int maxSumLenK(int[] arr, int k) { +// int N = arr.length; +// // 子数组必须以i位置的数结尾,长度一定要是K,累加和最大是多少? +// // help[0] help[k-2] ... +// int sum = 0; +// for (int i = 0; i < k - 1; i++) { +// sum += arr[i]; +// } +// // 0...k-2 k-1 sum +// int[] help = new int[N]; +// for (int i = k - 1; i < N; i++) { +// // 0..k-2 +// // 01..k-1 +// sum += arr[i]; +// help[i] = sum; +// // i == k-1 +// sum -= arr[i - k + 1]; +// } +// // help[i] - > dp[i] 0-..i K +// +// } + + public static int[] maxSumOfThreeSubarrays(int[] nums, int k) { + int N = nums.length; + int[] range = new int[N]; + int[] left = new int[N]; + int sum = 0; + for (int i = 0; i < k; i++) { + sum += nums[i]; + } + range[0] = sum; + left[k - 1] = 0; + int max = sum; + for (int i = k; i < N; i++) { + sum = sum - nums[i - k] + nums[i]; + range[i - k + 1] = sum; + left[i] = left[i - 1]; + if (sum > max) { + max = sum; + left[i] = i - k + 1; + } + } + sum = 0; + for (int i = N - 1; i >= N - k; i--) { + sum += nums[i]; + } + max = sum; + int[] right = new int[N]; + right[N - k] = N - k; + for (int i = N - k - 1; i >= 0; i--) { + sum = sum - nums[i + k] + nums[i]; + right[i] = right[i + 1]; + if (sum >= max) { + max = sum; + right[i] = i; + } + } + int a = 0; + int b = 0; + int c = 0; + max = 0; + for (int i = k; i < N - 2 * k + 1; i++) { // 中间一块的起始点 (0...k-1)选不了 i == N-1 + int part1 = range[left[i - 1]]; + int part2 = range[i]; + int part3 = range[right[i + k]]; + if (part1 + part2 + part3 > max) { + max = part1 + part2 + part3; + a = left[i - 1]; + b = i; + c = right[i + k]; + } + } + return new int[] { a, b, c }; + } + +} diff --git a/大厂刷题班/class22/Code02_TrappingRainWater.java b/大厂刷题班/class22/Code02_TrappingRainWater.java new file mode 100644 index 0000000..e022a12 --- /dev/null +++ b/大厂刷题班/class22/Code02_TrappingRainWater.java @@ -0,0 +1,28 @@ +package class22; + +// 本题测试链接 : https://leetcode.com/problems/trapping-rain-water/ +public class Code02_TrappingRainWater { + + public static int trap(int[] arr) { + if (arr == null || arr.length < 2) { + return 0; + } + int N = arr.length; + int L = 1; + int leftMax = arr[0]; + int R = N - 2; + int rightMax = arr[N - 1]; + int water = 0; + while (L <= R) { + if (leftMax <= rightMax) { + water += Math.max(0, leftMax - arr[L]); + leftMax = Math.max(leftMax, arr[L++]); + } else { + water += Math.max(0, rightMax - arr[R]); + rightMax = Math.max(rightMax, arr[R--]); + } + } + return water; + } + +} diff --git a/大厂刷题班/class22/Code03_TrappingRainWaterII.java b/大厂刷题班/class22/Code03_TrappingRainWaterII.java new file mode 100644 index 0000000..144dbac --- /dev/null +++ b/大厂刷题班/class22/Code03_TrappingRainWaterII.java @@ -0,0 +1,76 @@ +package class22; + +import java.util.PriorityQueue; + +// 本题测试链接 : https://leetcode.com/problems/trapping-rain-water-ii/ +public class Code03_TrappingRainWaterII { + + public static class Node { + public int value; + public int row; + public int col; + + public Node(int v, int r, int c) { + value = v; + row = r; + col = c; + } + + } + + public static int trapRainWater(int[][] heightMap) { + if (heightMap == null || heightMap.length == 0 || heightMap[0] == null || heightMap[0].length == 0) { + return 0; + } + int N = heightMap.length; + int M = heightMap[0].length; + boolean[][] isEnter = new boolean[N][M]; + PriorityQueue heap = new PriorityQueue<>((a, b) -> a.value - b.value); + for (int col = 0; col < M - 1; col++) { + isEnter[0][col] = true; + heap.add(new Node(heightMap[0][col], 0, col)); + } + for (int row = 0; row < N - 1; row++) { + isEnter[row][M - 1] = true; + heap.add(new Node(heightMap[row][M - 1], row, M - 1)); + } + for (int col = M - 1; col > 0; col--) { + isEnter[N - 1][col] = true; + heap.add(new Node(heightMap[N - 1][col], N - 1, col)); + } + for (int row = N - 1; row > 0; row--) { + isEnter[row][0] = true; + heap.add(new Node(heightMap[row][0], row, 0)); + } + int water = 0; + int max = 0; + while (!heap.isEmpty()) { + Node cur = heap.poll(); + max = Math.max(max, cur.value); + int r = cur.row; + int c = cur.col; + if (r > 0 && !isEnter[r - 1][c]) { + water += Math.max(0, max - heightMap[r - 1][c]); + isEnter[r - 1][c] = true; + heap.add(new Node(heightMap[r - 1][c], r - 1, c)); + } + if (r < N - 1 && !isEnter[r + 1][c]) { + water += Math.max(0, max - heightMap[r + 1][c]); + isEnter[r + 1][c] = true; + heap.add(new Node(heightMap[r + 1][c], r + 1, c)); + } + if (c > 0 && !isEnter[r][c - 1]) { + water += Math.max(0, max - heightMap[r][c - 1]); + isEnter[r][c - 1] = true; + heap.add(new Node(heightMap[r][c - 1], r, c - 1)); + } + if (c < M - 1 && !isEnter[r][c + 1]) { + water += Math.max(0, max - heightMap[r][c + 1]); + isEnter[r][c + 1] = true; + heap.add(new Node(heightMap[r][c + 1], r, c + 1)); + } + } + return water; + } + +} diff --git a/大厂刷题班/class22/Code04_VisibleMountains.java b/大厂刷题班/class22/Code04_VisibleMountains.java new file mode 100644 index 0000000..24dc0be --- /dev/null +++ b/大厂刷题班/class22/Code04_VisibleMountains.java @@ -0,0 +1,194 @@ +package class22; + +import java.util.HashSet; +import java.util.Stack; + +public class Code04_VisibleMountains { + + // 栈中放的记录, + // value就是指,times是收集的个数 + public static class Record { + public int value; + public int times; + + public Record(int value) { + this.value = value; + this.times = 1; + } + } + + public static int getVisibleNum(int[] arr) { + if (arr == null || arr.length < 2) { + return 0; + } + int N = arr.length; + int maxIndex = 0; + // 先在环中找到其中一个最大值的位置,哪一个都行 + for (int i = 0; i < N; i++) { + maxIndex = arr[maxIndex] < arr[i] ? i : maxIndex; + } + Stack stack = new Stack(); + // 先把(最大值,1)这个记录放入stack中 + stack.push(new Record(arr[maxIndex])); + // 从最大值位置的下一个位置开始沿next方向遍历 + int index = nextIndex(maxIndex, N); + // 用“小找大”的方式统计所有可见山峰对 + int res = 0; + // 遍历阶段开始,当index再次回到maxIndex的时候,说明转了一圈,遍历阶段就结束 + while (index != maxIndex) { + // 当前数要进入栈,判断会不会破坏第一维的数字从顶到底依次变大 + // 如果破坏了,就依次弹出栈顶记录,并计算山峰对数量 + while (stack.peek().value < arr[index]) { + int k = stack.pop().times; + // 弹出记录为(X,K),如果K==1,产生2对; 如果K>1,产生2*K + C(2,K)对。 + res += getInternalSum(k) + 2 * k; + } + // 当前数字arr[index]要进入栈了,如果和当前栈顶数字一样就合并 + // 不一样就把记录(arr[index],1)放入栈中 + if (stack.peek().value == arr[index]) { + stack.peek().times++; + } else { // > + stack.push(new Record(arr[index])); + } + index = nextIndex(index, N); + } + // 清算阶段开始了 + // 清算阶段的第1小阶段 + while (stack.size() > 2) { + int times = stack.pop().times; + res += getInternalSum(times) + 2 * times; + } + // 清算阶段的第2小阶段 + if (stack.size() == 2) { + int times = stack.pop().times; + res += getInternalSum(times) + + (stack.peek().times == 1 ? times : 2 * times); + } + // 清算阶段的第3小阶段 + res += getInternalSum(stack.pop().times); + return res; + } + + // 如果k==1返回0,如果k>1返回C(2,k) + public static int getInternalSum(int k) { + return k == 1 ? 0 : (k * (k - 1) / 2); + } + + // 环形数组中当前位置为i,数组长度为size,返回i的下一个位置 + public static int nextIndex(int i, int size) { + return i < (size - 1) ? (i + 1) : 0; + } + + // 环形数组中当前位置为i,数组长度为size,返回i的上一个位置 + public static int lastIndex(int i, int size) { + return i > 0 ? (i - 1) : (size - 1); + } + + // for test, O(N^2)的解法,绝对正确 + public static int rightWay(int[] arr) { + if (arr == null || arr.length < 2) { + return 0; + } + int res = 0; + HashSet equalCounted = new HashSet<>(); + for (int i = 0; i < arr.length; i++) { + // 枚举从每一个位置出发,根据“小找大”原则能找到多少对儿,并且保证不重复找 + res += getVisibleNumFromIndex(arr, i, equalCounted); + } + return res; + } + + // for test + // 根据“小找大”的原则返回从index出发能找到多少对 + // 相等情况下,比如arr[1]==3,arr[5]==3 + // 之前如果从位置1找过位置5,那么等到从位置5出发时就不再找位置1(去重) + // 之前找过的、所有相等情况的山峰对,都保存在了equalCounted中 + public static int getVisibleNumFromIndex(int[] arr, int index, + HashSet equalCounted) { + int res = 0; + for (int i = 0; i < arr.length; i++) { + if (i != index) { // 不找自己 + if (arr[i] == arr[index]) { + String key = Math.min(index, i) + "_" + Math.max(index, i); + // 相等情况下,确保之前没找过这一对 + if (equalCounted.add(key) && isVisible(arr, index, i)) { + res++; + } + } else if (isVisible(arr, index, i)) { // 不相等的情况下直接找 + res++; + } + } + } + return res; + } + + // for test + // 调用该函数的前提是,lowIndex和highIndex一定不是同一个位置 + // 在“小找大”的策略下,从lowIndex位置能不能看到highIndex位置 + // next方向或者last方向有一个能走通,就返回true,否则返回false + public static boolean isVisible(int[] arr, int lowIndex, int highIndex) { + if (arr[lowIndex] > arr[highIndex]) { // “大找小”的情况直接返回false + return false; + } + int size = arr.length; + boolean walkNext = true; + int mid = nextIndex(lowIndex, size); + // lowIndex通过next方向走到highIndex,沿途不能出现比arr[lowIndex]大的数 + while (mid != highIndex) { + if (arr[mid] > arr[lowIndex]) { + walkNext = false;// next方向失败 + break; + } + mid = nextIndex(mid, size); + } + boolean walkLast = true; + mid = lastIndex(lowIndex, size); + // lowIndex通过last方向走到highIndex,沿途不能出现比arr[lowIndex]大的数 + while (mid != highIndex) { + if (arr[mid] > arr[lowIndex]) { + walkLast = false; // last方向失败 + break; + } + mid = lastIndex(mid, size); + } + return walkNext || walkLast; // 有一个成功就是能相互看见 + } + + // for test + public static int[] getRandomArray(int size, int max) { + int[] arr = new int[(int) (Math.random() * size)]; + for (int i = 0; i < arr.length; i++) { + arr[i] = (int) (Math.random() * max); + } + return arr; + } + + // for test + public static void printArray(int[] arr) { + if (arr == null || arr.length == 0) { + return; + } + for (int i = 0; i < arr.length; i++) { + System.out.print(arr[i] + " "); + } + System.out.println(); + } + + public static void main(String[] args) { + int size = 10; + int max = 10; + int testTimes = 3000000; + System.out.println("test begin!"); + for (int i = 0; i < testTimes; i++) { + int[] arr = getRandomArray(size, max); + if (rightWay(arr) != getVisibleNum(arr)) { + printArray(arr); + System.out.println(rightWay(arr)); + System.out.println(getVisibleNum(arr)); + break; + } + } + System.out.println("test end!"); + } + +} \ No newline at end of file diff --git a/大厂刷题班/class22/Code05_TallestBillboard.java b/大厂刷题班/class22/Code05_TallestBillboard.java new file mode 100644 index 0000000..ede1d89 --- /dev/null +++ b/大厂刷题班/class22/Code05_TallestBillboard.java @@ -0,0 +1,38 @@ +package class22; + +import java.util.HashMap; + +// 本题测试链接 : https://leetcode.com/problems/tallest-billboard/ +public class Code05_TallestBillboard { + + public int tallestBillboard(int[] rods) { + // key 集合对的某个差 + // value 满足差值为key的集合对中,最好的一对,较小集合的累加和 + // 较大 -> value + key + HashMap dp = new HashMap<>(), cur; + dp.put(0, 0);// 空集 和 空集 + for (int num : rods) { + if (num != 0) { + // cur 内部数据完全和dp一样 + cur = new HashMap<>(dp); // 考虑x之前的集合差值状况拷贝下来 + for (int d : cur.keySet()) { + int diffMore = cur.get(d); // 最好的一对,较小集合的累加和 + // x决定放入,比较大的那个 + dp.put(d + num, Math.max(diffMore, dp.getOrDefault(num + d, 0))); + // x决定放入,比较小的那个 + // 新的差值 Math.abs(x - d) + // 之前差值为Math.abs(x - d),的那一对,就要和这一对,决策一下 + // 之前那一对,较小集合的累加和diffXD + int diffXD = dp.getOrDefault(Math.abs(num - d), 0); + if (d >= num) { // x决定放入比较小的那个, 但是放入之后,没有超过这一对较大的那个 + dp.put(d - num, Math.max(diffMore + num, diffXD)); + } else { // x决定放入比较小的那个, 但是放入之后,没有超过这一对较大的那个 + dp.put(num - d, Math.max(diffMore + d, diffXD)); + } + } + } + } + return dp.get(0); + } + +} diff --git a/大厂刷题班/class23/Code01_LCATarjanAndTreeChainPartition.java b/大厂刷题班/class23/Code01_LCATarjanAndTreeChainPartition.java new file mode 100644 index 0000000..43154cb --- /dev/null +++ b/大厂刷题班/class23/Code01_LCATarjanAndTreeChainPartition.java @@ -0,0 +1,352 @@ +package class23; + +import java.util.HashSet; + +public class Code01_LCATarjanAndTreeChainPartition { + + // 给定数组tree大小为N,表示一共有N个节点 + // tree[i] = j 表示点i的父亲是点j,tree一定是一棵树而不是森林 + // queries是二维数组,大小为M*2,每一个长度为2的数组都表示一条查询 + // [4,9], 表示想查询4和9之间的最低公共祖先 + // [3,7], 表示想查询3和7之间的最低公共祖先 + // tree和queries里面的所有值,都一定在0~N-1之间 + // 返回一个数组ans,大小为M,ans[i]表示第i条查询的答案 + + // 暴力方法 + public static int[] query1(int[] father, int[][] queries) { + int M = queries.length; + int[] ans = new int[M]; + HashSet path = new HashSet<>(); + for (int i = 0; i < M; i++) { + int jump = queries[i][0]; + while (father[jump] != jump) { + path.add(jump); + jump = father[jump]; + } + path.add(jump); + jump = queries[i][1]; + while (!path.contains(jump)) { + jump = father[jump]; + } + ans[i] = jump; + path.clear(); + } + return ans; + } + + // 离线批量查询最优解 -> Tarjan + 并查集 + // 如果有M条查询,时间复杂度O(N + M) + // 但是必须把M条查询一次给全,不支持在线查询 + public static int[] query2(int[] father, int[][] queries) { + int N = father.length; + int M = queries.length; + int[] help = new int[N]; + int h = 0; + for (int i = 0; i < N; i++) { + if (father[i] == i) { + h = i; + } else { + help[father[i]]++; + } + } + int[][] mt = new int[N][]; + for (int i = 0; i < N; i++) { + mt[i] = new int[help[i]]; + } + for (int i = 0; i < N; i++) { + if (i != h) { + mt[father[i]][--help[father[i]]] = i; + } + } + for (int i = 0; i < M; i++) { + if (queries[i][0] != queries[i][1]) { + help[queries[i][0]]++; + help[queries[i][1]]++; + } + } + int[][] mq = new int[N][]; + int[][] mi = new int[N][]; + for (int i = 0; i < N; i++) { + mq[i] = new int[help[i]]; + mi[i] = new int[help[i]]; + } + for (int i = 0; i < M; i++) { + if (queries[i][0] != queries[i][1]) { + mq[queries[i][0]][--help[queries[i][0]]] = queries[i][1]; + mi[queries[i][0]][help[queries[i][0]]] = i; + mq[queries[i][1]][--help[queries[i][1]]] = queries[i][0]; + mi[queries[i][1]][help[queries[i][1]]] = i; + } + } + int[] ans = new int[M]; + UnionFind uf = new UnionFind(N); + process(h, mt, mq, mi, uf, ans); + for (int i = 0; i < M; i++) { + if (queries[i][0] == queries[i][1]) { + ans[i] = queries[i][0]; + } + } + return ans; + } + + // 当前来到head点 + // mt是整棵树 head下方有哪些点 mt[head] = {a,b,c,d} head的孩子是abcd + // mq问题列表 head有哪些问题 mq[head] = {x,y,z} (head,x) (head,y) (head z) + // mi得到问题的答案,填在ans的什么地方 {6,12,34} + // uf 并查集 + public static void process(int head, int[][] mt, int[][] mq, int[][] mi, UnionFind uf, int[] ans) { + for (int next : mt[head]) { // head有哪些孩子,都遍历去吧! + process(next, mt, mq, mi, uf, ans); + uf.union(head, next); + uf.setTag(head, head); + } + // 解决head的问题! + int[] q = mq[head]; + int[] i = mi[head]; + for (int k = 0; k < q.length; k++) { + // head和谁有问题 q[k] 答案填哪 i[k] + int tag = uf.getTag(q[k]); + if (tag != -1) { + ans[i[k]] = tag; + } + } + } + + public static class UnionFind { + private int[] f; // father -> 并查集里面father信息,i -> i的father + private int[] s; // size[] -> 集合 --> i size[i] + private int[] t; // tag[] -> 集合 ---> tag[i] = ? + private int[] h; // 栈?并查集搞扁平化 + + public UnionFind(int N) { + f = new int[N]; + s = new int[N]; + t = new int[N]; + h = new int[N]; + for (int i = 0; i < N; i++) { + f[i] = i; + s[i] = 1; + t[i] = -1; + } + } + + private int find(int i) { + int j = 0; + // i -> j -> k -> s -> a -> a + while (i != f[i]) { + h[j++] = i; + i = f[i]; + } + // i -> a + // j -> a + // k -> a + // s -> a + while (j > 0) { + h[--j] = i; + } + // a + return i; + } + + public void union(int i, int j) { + int fi = find(i); + int fj = find(j); + if (fi != fj) { + int si = s[fi]; + int sj = s[fj]; + int m = si >= sj ? fi : fj; + int l = m == fi ? fj : fi; + f[l] = m; + s[m] += s[l]; + } + } + + // 集合的某个元素是i,请把整个集合打上统一的标签,tag + public void setTag(int i, int tag) { + t[find(i)] = tag; + } + + // 集合的某个元素是i,请把整个集合的tag信息返回 + public int getTag(int i) { + return t[find(i)]; + } + + } + + // 在线查询最优解 -> 树链剖分 + // 空间复杂度O(N), 支持在线查询,单次查询时间复杂度O(logN) + // 如果有M次查询,时间复杂度O(N + M * logN) + public static int[] query3(int[] father, int[][] queries) { + TreeChain tc = new TreeChain(father); + int M = queries.length; + int[] ans = new int[M]; + for (int i = 0; i < M; i++) { + // x y ? + // x x x + if (queries[i][0] == queries[i][1]) { + ans[i] = queries[i][0]; + } else { + ans[i] = tc.lca(queries[i][0], queries[i][1]); + } + } + return ans; + } + + public static class TreeChain { + private int n; + private int h; + private int[][] tree; + private int[] fa; + private int[] dep; + private int[] son; + private int[] siz; + private int[] top; + + public TreeChain(int[] father) { + initTree(father); + dfs1(h, 0); + dfs2(h, h); + } + + private void initTree(int[] father) { + n = father.length + 1; + tree = new int[n][]; + fa = new int[n]; + dep = new int[n]; + son = new int[n]; + siz = new int[n]; + top = new int[n--]; + int[] cnum = new int[n]; + for (int i = 0; i < n; i++) { + if (father[i] == i) { + h = i + 1; + } else { + cnum[father[i]]++; + } + } + tree[0] = new int[0]; + for (int i = 0; i < n; i++) { + tree[i + 1] = new int[cnum[i]]; + } + for (int i = 0; i < n; i++) { + if (i + 1 != h) { + tree[father[i] + 1][--cnum[father[i]]] = i + 1; + } + } + } + + private void dfs1(int u, int f) { + fa[u] = f; + dep[u] = dep[f] + 1; + siz[u] = 1; + int maxSize = -1; + for (int v : tree[u]) { + dfs1(v, u); + siz[u] += siz[v]; + if (siz[v] > maxSize) { + maxSize = siz[v]; + son[u] = v; + } + } + } + + private void dfs2(int u, int t) { + top[u] = t; + if (son[u] != 0) { + dfs2(son[u], t); + for (int v : tree[u]) { + if (v != son[u]) { + dfs2(v, v); + } + } + } + } + + public int lca(int a, int b) { + a++; + b++; + while (top[a] != top[b]) { + if (dep[top[a]] > dep[top[b]]) { + a = fa[top[a]]; + } else { + b = fa[top[b]]; + } + } + return (dep[a] < dep[b] ? a : b) - 1; + } + } + + // 为了测试 + // 随机生成N个节点树,可能是多叉树,并且一定不是森林 + // 输入参数N要大于0 + public static int[] generateFatherArray(int N) { + int[] order = new int[N]; + for (int i = 0; i < N; i++) { + order[i] = i; + } + for (int i = N - 1; i >= 0; i--) { + swap(order, i, (int) (Math.random() * (i + 1))); + } + int[] ans = new int[N]; + ans[order[0]] = order[0]; + for (int i = 1; i < N; i++) { + ans[order[i]] = order[(int) (Math.random() * i)]; + } + return ans; + } + + // 为了测试 + // 随机生成M条查询,点有N个,点的编号在0~N-1之间 + // 输入参数M和N都要大于0 + public static int[][] generateQueries(int M, int N) { + int[][] ans = new int[M][2]; + for (int i = 0; i < M; i++) { + ans[i][0] = (int) (Math.random() * N); + ans[i][1] = (int) (Math.random() * N); + } + return ans; + } + + // 为了测试 + public static void swap(int[] arr, int i, int j) { + int tmp = arr[i]; + arr[i] = arr[j]; + arr[j] = tmp; + } + + // 为了测试 + public static boolean equal(int[] a, int[] b) { + if (a.length != b.length) { + return false; + } + for (int i = 0; i < a.length; i++) { + if (a[i] != b[i]) { + return false; + } + } + return true; + } + + // 为了测试 + public static void main(String[] args) { + int N = 1000; + int M = 200; + int testTime = 50000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int size = (int) (Math.random() * N) + 1; + int ques = (int) (Math.random() * M) + 1; + int[] father = generateFatherArray(size); + int[][] queries = generateQueries(ques, size); + int[] ans1 = query1(father, queries); + int[] ans2 = query2(father, queries); + int[] ans3 = query3(father, queries); + if (!equal(ans1, ans2) || !equal(ans1, ans3)) { + System.out.println("出错了!"); + break; + } + } + System.out.println("测试结束"); + } + +} diff --git a/大厂刷题班/class23/Code02_MaxABSBetweenLeftAndRight.java b/大厂刷题班/class23/Code02_MaxABSBetweenLeftAndRight.java new file mode 100644 index 0000000..af3cd49 --- /dev/null +++ b/大厂刷题班/class23/Code02_MaxABSBetweenLeftAndRight.java @@ -0,0 +1,63 @@ +package class23; + +public class Code02_MaxABSBetweenLeftAndRight { + + public static int maxABS1(int[] arr) { + int res = Integer.MIN_VALUE; + int maxLeft = 0; + int maxRight = 0; + for (int i = 0; i != arr.length - 1; i++) { + maxLeft = Integer.MIN_VALUE; + for (int j = 0; j != i + 1; j++) { + maxLeft = Math.max(arr[j], maxLeft); + } + maxRight = Integer.MIN_VALUE; + for (int j = i + 1; j != arr.length; j++) { + maxRight = Math.max(arr[j], maxRight); + } + res = Math.max(Math.abs(maxLeft - maxRight), res); + } + return res; + } + + public static int maxABS2(int[] arr) { + int[] lArr = new int[arr.length]; + int[] rArr = new int[arr.length]; + lArr[0] = arr[0]; + rArr[arr.length - 1] = arr[arr.length - 1]; + for (int i = 1; i < arr.length; i++) { + lArr[i] = Math.max(lArr[i - 1], arr[i]); + } + for (int i = arr.length - 2; i > -1; i--) { + rArr[i] = Math.max(rArr[i + 1], arr[i]); + } + int max = 0; + for (int i = 0; i < arr.length - 1; i++) { + max = Math.max(max, Math.abs(lArr[i] - rArr[i + 1])); + } + return max; + } + + public static int maxABS3(int[] arr) { + int max = Integer.MIN_VALUE; + for (int i = 0; i < arr.length; i++) { + max = Math.max(arr[i], max); + } + return max - Math.min(arr[0], arr[arr.length - 1]); + } + + public static int[] generateRandomArray(int length) { + int[] arr = new int[length]; + for (int i = 0; i != arr.length; i++) { + arr[i] = (int) (Math.random() * 1000) - 499; + } + return arr; + } + + public static void main(String[] args) { + int[] arr = generateRandomArray(200); + System.out.println(maxABS1(arr)); + System.out.println(maxABS2(arr)); + System.out.println(maxABS3(arr)); + } +} diff --git a/大厂刷题班/class23/Code03_LongestIntegratedLength.java b/大厂刷题班/class23/Code03_LongestIntegratedLength.java new file mode 100644 index 0000000..8ae031c --- /dev/null +++ b/大厂刷题班/class23/Code03_LongestIntegratedLength.java @@ -0,0 +1,106 @@ +package class23; + +import java.util.Arrays; +import java.util.HashSet; + +public class Code03_LongestIntegratedLength { + + public static int maxLen(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + int N = arr.length; + HashSet set = new HashSet<>(); + int ans = 1; + for (int L = 0; L < N; L++) { + set.clear(); + int min = arr[L]; + int max = arr[L]; + set.add(arr[L]); + // L..R + for (int R = L + 1; R < N; R++) { + // L....R + if(set.contains(arr[R])) { + break; + } + set.add(arr[R]); + min = Math.min(min, arr[R]); + max = Math.max(max, arr[R]); + if(max - min == R - L) { + ans = Math.max(ans, R - L + 1); + } + } + } + return ans; + + } + + public static int getLIL1(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + int len = 0; + // O(N^3 * log N) + for (int start = 0; start < arr.length; start++) { // 子数组所有可能的开头 + for (int end = start; end < arr.length; end++) { // 开头为start的情况下,所有可能的结尾 + // arr[s ... e] + // O(N * logN) + if (isIntegrated(arr, start, end)) { + len = Math.max(len, end - start + 1); + } + } + } + return len; + } + + public static boolean isIntegrated(int[] arr, int left, int right) { + int[] newArr = Arrays.copyOfRange(arr, left, right + 1); // O(N) + Arrays.sort(newArr); // O(N*logN) + for (int i = 1; i < newArr.length; i++) { + if (newArr[i - 1] != newArr[i] - 1) { + return false; + } + } + return true; + } + + public static int getLIL2(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + int len = 0; + int max = 0; + int min = 0; + HashSet set = new HashSet(); + for (int L = 0; L < arr.length; L++) { // L 左边界 + // L ....... + set.clear(); + max = Integer.MIN_VALUE; + min = Integer.MAX_VALUE; + for (int R = L; R < arr.length; R++) { // R 右边界 + // arr[L..R]这个子数组在验证 l...R L...r+1 l...r+2 + if (set.contains(arr[R])) { + // arr[L..R]上开始 出现重复值了,arr[L..R往后]不需要验证了, + // 一定不是可整合的 + break; + } + // arr[L..R]上无重复值 + set.add(arr[R]); + max = Math.max(max, arr[R]); + min = Math.min(min, arr[R]); + if (max - min == R - L) { // L..R 是可整合的 + len = Math.max(len, R - L + 1); + } + } + } + return len; + } + + public static void main(String[] args) { + int[] arr = { 5, 5, 3, 2, 6, 4, 3 }; + System.out.println(getLIL1(arr)); + System.out.println(getLIL2(arr)); + + } + +} diff --git a/大厂刷题班/class23/Code04_FindKMajority.java b/大厂刷题班/class23/Code04_FindKMajority.java new file mode 100644 index 0000000..91ea4c5 --- /dev/null +++ b/大厂刷题班/class23/Code04_FindKMajority.java @@ -0,0 +1,113 @@ +package class23; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map.Entry; + +public class Code04_FindKMajority { + + public static void printHalfMajor(int[] arr) { + int cand = 0; + int HP = 0; + for (int i = 0; i < arr.length; i++) { + if (HP == 0) { + cand = arr[i]; + HP = 1; + } else if (arr[i] == cand) { + HP++; + } else { + HP--; + } + } + if(HP == 0) { + System.out.println("no such number."); + return; + } + HP = 0; + for (int i = 0; i < arr.length; i++) { + if (arr[i] == cand) { + HP++; + } + } + if (HP > arr.length / 2) { + System.out.println(cand); + } else { + System.out.println("no such number."); + } + } + + public static void printKMajor(int[] arr, int K) { + if (K < 2) { + System.out.println("the value of K is invalid."); + return; + } + // 攒候选,cands,候选表,最多K-1条记录! > N / K次的数字,最多有K-1个 + HashMap cands = new HashMap(); + for (int i = 0; i != arr.length; i++) { + if (cands.containsKey(arr[i])) { + cands.put(arr[i], cands.get(arr[i]) + 1); + } else { // arr[i] 不是候选 + if (cands.size() == K - 1) { // 当前数肯定不要!,每一个候选付出1点血量,血量变成0的候选,要删掉! + allCandsMinusOne(cands); + } else { + cands.put(arr[i], 1); + } + } + } + // 所有可能的候选,都在cands表中!遍历一遍arr,每个候选收集真实次数 + + + + + HashMap reals = getReals(arr, cands); + boolean hasPrint = false; + for (Entry set : cands.entrySet()) { + Integer key = set.getKey(); + if (reals.get(key) > arr.length / K) { + hasPrint = true; + System.out.print(key + " "); + } + } + System.out.println(hasPrint ? "" : "no such number."); + } + + public static void allCandsMinusOne(HashMap map) { + List removeList = new LinkedList(); + for (Entry set : map.entrySet()) { + Integer key = set.getKey(); + Integer value = set.getValue(); + if (value == 1) { + removeList.add(key); + } + map.put(key, value - 1); + } + for (Integer removeKey : removeList) { + map.remove(removeKey); + } + } + + public static HashMap getReals(int[] arr, + HashMap cands) { + HashMap reals = new HashMap(); + for (int i = 0; i != arr.length; i++) { + int curNum = arr[i]; + if (cands.containsKey(curNum)) { + if (reals.containsKey(curNum)) { + reals.put(curNum, reals.get(curNum) + 1); + } else { + reals.put(curNum, 1); + } + } + } + return reals; + } + + public static void main(String[] args) { + int[] arr = { 1, 2, 3, 1, 1, 2, 1 }; + printHalfMajor(arr); + int K = 4; + printKMajor(arr, K); + } + +} diff --git a/大厂刷题班/class23/Code05_MinimumCostToMergeStones.java b/大厂刷题班/class23/Code05_MinimumCostToMergeStones.java new file mode 100644 index 0000000..45f6c84 --- /dev/null +++ b/大厂刷题班/class23/Code05_MinimumCostToMergeStones.java @@ -0,0 +1,159 @@ +package class23; + +// 本题测试链接 : https://leetcode.com/problems/minimum-cost-to-merge-stones/ +public class Code05_MinimumCostToMergeStones { + +// // arr[L...R]一定要整出P份,合并的最小代价,返回! +// public static int f(int[] arr, int K, int L, int R, int P) { +// if(从L到R根本不可能弄出P份) { +// return Integer.MAX_VALUE; +// } +// // 真的有可能的! +// if(P == 1) { +// return L == R ? 0 : (f(arr, K, L, R, K) + 最后一次大合并的代价); +// } +// int ans = Integer.MAX_VALUE; +// // 真的有可能,P > 1 +// for(int i = L; i < R;i++) { +// // L..i(1份) i+1...R(P-1份) +// int left = f(arr, K, L, i, 1); +// if(left == Integer.MAX_VALUE) { +// continue; +// } +// int right = f(arr, K, i+1,R,P-1); +// if(right == Integer.MAX_VALUE) { +// continue; +// } +// int all = left + right; +// ans = Math.min(ans, all); +// } +// return ans; +// } + + public static int mergeStones1(int[] stones, int K) { + int n = stones.length; + if ((n - 1) % (K - 1) > 0) { + return -1; + } + int[] presum = new int[n + 1]; + for (int i = 0; i < n; i++) { + presum[i + 1] = presum[i] + stones[i]; + } + return process1(0, n - 1, 1, stones, K, presum); + } + + // part >= 1 + // arr[L..R] 一定要弄出part份,返回最低代价 + // arr、K、presum(前缀累加和数组,求i..j的累加和,就是O(1)了) + public static int process1(int L, int R, int P, int[] arr, int K, int[] presum) { + if (L == R) { // arr[L..R] + return P == 1 ? 0 : -1; + } + // L ... R 不只一个数 + if (P == 1) { + int next = process1(L, R, K, arr, K, presum); + if (next == -1) { + return -1; + } else { + return next + presum[R + 1] - presum[L]; + } + } else { // P > 1 + int ans = Integer.MAX_VALUE; + // L...mid是第1块,剩下的是part-1块 + for (int mid = L; mid < R; mid += K - 1) { + // L..mid(一份) mid+1...R(part - 1) + int next1 = process1(L, mid, 1, arr, K, presum); + int next2 = process1(mid + 1, R, P - 1, arr, K, presum); + if (next1 != -1 && next2 != -1) { + ans = Math.min(ans, next1 + next2); + } + } + return ans; + } + } + + public static int mergeStones2(int[] stones, int K) { + int n = stones.length; + if ((n - 1) % (K - 1) > 0) { // n个数,到底能不能K个相邻的数合并,最终变成1个数! + return -1; + } + int[] presum = new int[n + 1]; + for (int i = 0; i < n; i++) { + presum[i + 1] = presum[i] + stones[i]; + } + int[][][] dp = new int[n][n][K + 1]; + return process2(0, n - 1, 1, stones, K, presum, dp); + } + + public static int process2(int L, int R, int P, int[] arr, int K, int[] presum, int[][][] dp) { + if (dp[L][R][P] != 0) { + return dp[L][R][P]; + } + if (L == R) { + return P == 1 ? 0 : -1; + } + if (P == 1) { + int next = process2(L, R, K, arr, K, presum, dp); + if (next == -1) { + dp[L][R][P] = -1; + return -1; + } else { + dp[L][R][P] = next + presum[R + 1] - presum[L]; + return next + presum[R + 1] - presum[L]; + } + } else { + int ans = Integer.MAX_VALUE; + // i...mid是第1块,剩下的是part-1块 + for (int mid = L; mid < R; mid += K - 1) { + int next1 = process2(L, mid, 1, arr, K, presum, dp); + int next2 = process2(mid + 1, R, P - 1, arr, K, presum, dp); + if (next1 == -1 || next2 == -1) { + dp[L][R][P] = -1; + return -1; + } else { + ans = Math.min(ans, next1 + next2); + } + } + dp[L][R][P] = ans; + return ans; + } + } + + // for test + public static int[] generateRandomArray(int maxSize, int maxValue) { + int[] arr = new int[(int) (maxSize * Math.random()) + 1]; + for (int i = 0; i < arr.length; i++) { + arr[i] = (int) ((maxValue + 1) * Math.random()); + } + return arr; + } + + // for test + public static void printArray(int[] arr) { + if (arr == null) { + return; + } + for (int i = 0; i < arr.length; i++) { + System.out.print(arr[i] + " "); + } + System.out.println(); + } + + public static void main(String[] args) { + int maxSize = 12; + int maxValue = 100; + System.out.println("Test begin"); + for (int testTime = 0; testTime < 100000; testTime++) { + int[] arr = generateRandomArray(maxSize, maxValue); + int K = (int) (Math.random() * 7) + 2; + int ans1 = mergeStones1(arr, K); + int ans2 = mergeStones2(arr, K); + if (ans1 != ans2) { + System.out.println(ans1); + System.out.println(ans2); + } + } + + } + +} diff --git a/大厂刷题班/class24/Code01_Split4Parts.java b/大厂刷题班/class24/Code01_Split4Parts.java new file mode 100644 index 0000000..8ac489e --- /dev/null +++ b/大厂刷题班/class24/Code01_Split4Parts.java @@ -0,0 +1,89 @@ +package class24; + +import java.util.HashMap; +import java.util.HashSet; + +public class Code01_Split4Parts { + + public static boolean canSplits1(int[] arr) { + if (arr == null || arr.length < 7) { + return false; + } + HashSet set = new HashSet(); + int sum = 0; + for (int i = 0; i < arr.length; i++) { + sum += arr[i]; + } + int leftSum = arr[0]; + for (int i = 1; i < arr.length - 1; i++) { + set.add(String.valueOf(leftSum) + "_" + String.valueOf(sum - leftSum - arr[i])); + leftSum += arr[i]; + } + int l = 1; + int lsum = arr[0]; + int r = arr.length - 2; + int rsum = arr[arr.length - 1]; + while (l < r - 3) { + if (lsum == rsum) { + String lkey = String.valueOf(lsum * 2 + arr[l]); + String rkey = String.valueOf(rsum * 2 + arr[r]); + if (set.contains(lkey + "_" + rkey)) { + return true; + } + lsum += arr[l++]; + } else if (lsum < rsum) { + lsum += arr[l++]; + } else { + rsum += arr[r--]; + } + } + return false; + } + + public static boolean canSplits2(int[] arr) { + if (arr == null || arr.length < 7) { + return false; + } + // key 某一个累加和, value出现的位置 + HashMap map = new HashMap(); + int sum = arr[0]; + for (int i = 1; i < arr.length; i++) { + map.put(sum, i); + sum += arr[i]; + } + int lsum = arr[0]; // 第一刀左侧的累加和 + for (int s1 = 1; s1 < arr.length - 5; s1++) { // s1是第一刀的位置 + int checkSum = lsum * 2 + arr[s1]; // 100 x 100 100*2 + x + if (map.containsKey(checkSum)) { + int s2 = map.get(checkSum); // j -> y + checkSum += lsum + arr[s2]; + if (map.containsKey(checkSum)) { // 100 * 3 + x + y + int s3 = map.get(checkSum); // k -> z + if (checkSum + arr[s3] + lsum == sum) { + return true; + } + } + } + lsum += arr[s1]; + } + return false; + } + + public static int[] generateRondomArray() { + int[] res = new int[(int) (Math.random() * 10) + 7]; + for (int i = 0; i < res.length; i++) { + res[i] = (int) (Math.random() * 10) + 1; + } + return res; + } + + public static void main(String[] args) { + int testTime = 3000000; + for (int i = 0; i < testTime; i++) { + int[] arr = generateRondomArray(); + if (canSplits1(arr) ^ canSplits2(arr)) { + System.out.println("Error"); + } + } + } +} diff --git a/大厂刷题班/class24/Code02_KthMinPair.java b/大厂刷题班/class24/Code02_KthMinPair.java new file mode 100644 index 0000000..ebd3d96 --- /dev/null +++ b/大厂刷题班/class24/Code02_KthMinPair.java @@ -0,0 +1,180 @@ +package class24; + +import java.util.Arrays; +import java.util.Comparator; + +public class Code02_KthMinPair { + + public static class Pair { + public int x; + public int y; + + Pair(int a, int b) { + x = a; + y = b; + } + } + + public static class PairComparator implements Comparator { + + @Override + public int compare(Pair arg0, Pair arg1) { + return arg0.x != arg1.x ? arg0.x - arg1.x : arg0.y - arg1.y; + } + + } + + // O(N^2 * log (N^2))的复杂度,你肯定过不了 + // 返回的int[] 长度是2,{3,1} int[2] = [3,1] + public static int[] kthMinPair1(int[] arr, int k) { + int N = arr.length; + if (k > N * N) { + return null; + } + Pair[] pairs = new Pair[N * N]; + int index = 0; + for (int i = 0; i < N; i++) { + for (int j = 0; j < N; j++) { + pairs[index++] = new Pair(arr[i], arr[j]); + } + } + Arrays.sort(pairs, new PairComparator()); + return new int[] { pairs[k - 1].x, pairs[k - 1].y }; + } + + // O(N*logN)的复杂度,你肯定过了 + public static int[] kthMinPair2(int[] arr, int k) { + int N = arr.length; + if (k > N * N) { + return null; + } + // O(N*logN) + Arrays.sort(arr); + // 第K小的数值对,第一维数字,是什么 是arr中 + int fristNum = arr[(k - 1) / N]; + int lessFristNumSize = 0;// 数出比fristNum小的数有几个 + int fristNumSize = 0; // 数出==fristNum的数有几个 + // <= fristNum + for (int i = 0; i < N && arr[i] <= fristNum; i++) { + if (arr[i] < fristNum) { + lessFristNumSize++; + } else { + fristNumSize++; + } + } + int rest = k - (lessFristNumSize * N); + return new int[] { fristNum, arr[(rest - 1) / fristNumSize] }; + } + + // O(N)的复杂度,你肯定蒙了 + public static int[] kthMinPair3(int[] arr, int k) { + int N = arr.length; + if (k > N * N) { + return null; + } + // 在无序数组中,找到第K小的数,返回值 + // 第K小,以1作为开始 + int fristNum = getMinKth(arr, (k - 1) / N); + // 第1维数字 + int lessFristNumSize = 0; + int fristNumSize = 0; + for (int i = 0; i < N; i++) { + if (arr[i] < fristNum) { + lessFristNumSize++; + } + if (arr[i] == fristNum) { + fristNumSize++; + } + } + int rest = k - (lessFristNumSize * N); + return new int[] { fristNum, getMinKth(arr, (rest - 1) / fristNumSize) }; + } + + // 改写快排,时间复杂度O(N) + // 在无序数组arr中,找到,如果排序的话,arr[index]的数是什么? + public static int getMinKth(int[] arr, int index) { + int L = 0; + int R = arr.length - 1; + int pivot = 0; + int[] range = null; + while (L < R) { + pivot = arr[L + (int) (Math.random() * (R - L + 1))]; + range = partition(arr, L, R, pivot); + if (index < range[0]) { + R = range[0] - 1; + } else if (index > range[1]) { + L = range[1] + 1; + } else { + return pivot; + } + } + return arr[L]; + } + + public static int[] partition(int[] arr, int L, int R, int pivot) { + int less = L - 1; + int more = R + 1; + int cur = L; + while (cur < more) { + if (arr[cur] < pivot) { + swap(arr, ++less, cur++); + } else if (arr[cur] > pivot) { + swap(arr, cur, --more); + } else { + cur++; + } + } + return new int[] { less + 1, more - 1 }; + } + + public static void swap(int[] arr, int i, int j) { + int tmp = arr[i]; + arr[i] = arr[j]; + arr[j] = tmp; + } + + // 为了测试,生成值也随机,长度也随机的随机数组 + public static int[] getRandomArray(int max, int len) { + int[] arr = new int[(int) (Math.random() * len) + 1]; + for (int i = 0; i < arr.length; i++) { + arr[i] = (int) (Math.random() * max) - (int) (Math.random() * max); + } + return arr; + } + + // 为了测试 + public static int[] copyArray(int[] arr) { + if (arr == null) { + return null; + } + int[] res = new int[arr.length]; + for (int i = 0; i < arr.length; i++) { + res[i] = arr[i]; + } + return res; + } + + // 随机测试了百万组,保证三种方法都是对的 + public static void main(String[] args) { + int max = 100; + int len = 30; + int testTimes = 100000; + System.out.println("test bagin, test times : " + testTimes); + for (int i = 0; i < testTimes; i++) { + int[] arr = getRandomArray(max, len); + int[] arr1 = copyArray(arr); + int[] arr2 = copyArray(arr); + int[] arr3 = copyArray(arr); + int N = arr.length * arr.length; + int k = (int) (Math.random() * N) + 1; + int[] ans1 = kthMinPair1(arr1, k); + int[] ans2 = kthMinPair2(arr2, k); + int[] ans3 = kthMinPair3(arr3, k); + if (ans1[0] != ans2[0] || ans2[0] != ans3[0] || ans1[1] != ans2[1] || ans2[1] != ans3[1]) { + System.out.println("Oops!"); + } + } + System.out.println("test finish"); + } + +} diff --git a/大厂刷题班/class24/Code03_NotContains4.java b/大厂刷题班/class24/Code03_NotContains4.java new file mode 100644 index 0000000..0a52911 --- /dev/null +++ b/大厂刷题班/class24/Code03_NotContains4.java @@ -0,0 +1,144 @@ +package class24; + +// 里程表不能含有4,给定一个数num,返回他是里程表里的第几个 +public class Code03_NotContains4 { + + // num中一定没有4这个数字 + public static long notContains4Nums1(long num) { + long count = 0; + for (long i = 1; i <= num; i++) { + if (isNot4(i)) { + count++; + } + } + return count; + } + + public static boolean isNot4(long num) { + while (num != 0) { + if (num % 10 == 4) { + return false; + } + num /= 10; + } + return true; + } + + // arr[1] : 比1位数还少1位,有几个数(数字里可以包含0但是不可以包含4)?0个 + // arr[2] : 比2位数还少1位,有几个数(数字里可以包含0但是不可以包含4)?9个 + // 1 -> 0 1 2 3 - 5 6 7 8 9 = 9 + // arr[3] :比3位数还少1位,有几个数(数字里可以包含0但是不可以包含4)?81个 + // 1 : 0 (0 1 2 3 - 5 6 7 8 9) = 9 + // 2 : + // 1 (0 1 2 3 - 5 6 7 8 9) = 9 + // 2 (0 1 2 3 - 5 6 7 8 9) = 9 + // 3 (0 1 2 3 - 5 6 7 8 9) = 9 + // 5 (0 1 2 3 - 5 6 7 8 9) = 9 + // ... + // 9 (0 1 2 3 - 5 6 7 8 9) = 9 + public static long[] arr = { 0L, 1L, 9L, 81L, 729L, 6561L, 59049L, 531441L, 4782969L, 43046721L, 387420489L, + 3486784401L, 31381059609L, 282429536481L, 2541865828329L, 22876792454961L, 205891132094649L, + 1853020188851841L, 16677181699666569L, 150094635296999121L, 1350851717672992089L }; + + // num中一定没有4这个数字 + public static long notContains4Nums2(long num) { + if (num <= 0) { + return 0; + } + // num的十进制位数,len + int len = decimalLength(num); + // 35621 + // 10000 + long offset = offset(len); + + // 第一位数字 + long first = num / offset; + return arr[len] - 1 + (first - (first < 4 ? 1 : 2)) * arr[len] + process(num % offset, offset / 10, len - 1); + } + + // num之前,有开头! + // 在算0的情况下,num是第几个数字 + // num 76217 + // 10000 + // 6217 + // 1000 + // len + public static long process(long num, long offset, int len) { + if (len == 0) { + return 1; + } + long first = num / offset; + return (first < 4 ? first : (first - 1)) * arr[len] + process(num % offset, offset / 10, len - 1); + } + + // num的十进制位数 + // num = 7653210 + // 7 + public static int decimalLength(long num) { + int len = 0; + while (num != 0) { + len++; + num /= 10; + } + return len; + } + + // len = 6 + // 100000 + // len = 4 + // 1000 + // 3452168 + // 1000000 + // 3 + public static long offset(int len) { + long offset = 1; + for (int i = 1; i < len; i++) { + offset *= 10L; + } + return offset; + } + + // 讲完之后想到了课上同学的留言 + // 突然意识到,这道题的本质是一个9进制的数转成10进制的数 + // 不过好在课上的解法有实际意义,就是这种求解的方式,很多题目都这么弄 + // 还有课上的时间复杂度和"9进制的数转成10进制的数"的做法,时间复杂度都是O(lg N) + // 不过"9进制的数转成10进制的数"毫无疑问是最优解 + public static long notContains4Nums3(long num) { + if (num <= 0) { + return 0; + } + long ans = 0; + for (long base = 1, cur = 0; num != 0; num /= 10, base *= 9) { + cur = num % 10; + ans += (cur < 4 ? cur : cur - 1) * base; + } + return ans; + } + + public static void main(String[] args) { + long max = 88888888L; + System.out.println("功能测试开始,验证 0 ~ " + max + " 以内所有的结果"); + for (long i = 0; i <= max; i++) { + // 测试的时候,输入的数字i里不能含有4,这是题目的规定! + if (isNot4(i) && notContains4Nums2(i) != notContains4Nums3(i)) { + System.out.println("Oops!"); + } + } + System.out.println("如果没有打印Oops说明验证通过"); + + long num = 8173528638135L; + long start; + long end; + System.out.println("性能测试开始,计算 num = " + num + " 的答案"); + start = System.currentTimeMillis(); + long ans2 = notContains4Nums2(num); + end = System.currentTimeMillis(); + System.out.println("方法二答案 : " + ans2 + ", 运行时间 : " + (end - start) + " ms"); + + start = System.currentTimeMillis(); + long ans3 = notContains4Nums3(num); + end = System.currentTimeMillis(); + System.out.println("方法三答案 : " + ans3 + ", 运行时间 : " + (end - start) + " ms"); + } + +} diff --git a/大厂刷题班/class24/Code04_Painting.java b/大厂刷题班/class24/Code04_Painting.java new file mode 100644 index 0000000..229a906 --- /dev/null +++ b/大厂刷题班/class24/Code04_Painting.java @@ -0,0 +1,75 @@ +package class24; + +import java.util.ArrayList; +import java.util.List; + +public class Code04_Painting { + // N * M的棋盘 + // 每种颜色的格子数必须相同的 + // 相邻格子染的颜色必须不同 + // 所有格子必须染色 + // 返回至少多少种颜色可以完成任务 + + public static int minColors(int N, int M) { + // 颜色数量是i + for (int i = 2; i < N * M; i++) { + int[][] matrix = new int[N][M]; + // 下面这一句可知,需要的最少颜色数i,一定是N*M的某个因子 + if ((N * M) % i == 0 && can(matrix, N, M, i)) { + return i; + } + } + return N * M; + } + + // 在matrix上染色,返回只用pNum种颜色是否可以做到要求 + public static boolean can(int[][] matrix, int N, int M, int pNum) { + int all = N * M; + int every = all / pNum; + ArrayList rest = new ArrayList<>(); + rest.add(0); + for (int i = 1; i <= pNum; i++) { + rest.add(every); + } + return process(matrix, N, M, pNum, 0, 0, rest); + } + + public static boolean process(int[][] matrix, int N, int M, int pNum, int row, int col, List rest) { + if (row == N) { + return true; + } + if (col == M) { + return process(matrix, N, M, pNum, row + 1, 0, rest); + } + int left = col == 0 ? 0 : matrix[row][col - 1]; + int up = row == 0 ? 0 : matrix[row - 1][col]; + for (int color = 1; color <= pNum; color++) { + if (color != left && color != up && rest.get(color) > 0) { + int count = rest.get(color); + rest.set(color, count - 1); + matrix[row][col] = color; + if (process(matrix, N, M, pNum, row, col + 1, rest)) { + return true; + } + rest.set(color, count); + matrix[row][col] = 0; + } + } + return false; + } + + public static void main(String[] args) { + // 根据代码16行的提示,打印出答案,看看是答案是哪个因子 + for (int N = 2; N < 10; N++) { + for (int M = 2; M < 10; M++) { + System.out.println("N = " + N); + System.out.println("M = " + M); + System.out.println("ans = " + minColors(N, M)); + System.out.println("==========="); + } + } + // 打印答案,分析可知,是N*M最小的质数因子,原因不明,也不重要 + // 反正打表法猜出来了 + } + +} diff --git a/大厂刷题班/class24/Code05_MinWindowLength.java b/大厂刷题班/class24/Code05_MinWindowLength.java new file mode 100644 index 0000000..60508cd --- /dev/null +++ b/大厂刷题班/class24/Code05_MinWindowLength.java @@ -0,0 +1,80 @@ +package class24; + +public class Code05_MinWindowLength { + + public static int minLength(String s1, String s2) { + if (s1 == null || s2 == null || s1.length() < s2.length()) { + return Integer.MAX_VALUE; + } + char[] str1 = s1.toCharArray(); + char[] str2 = s2.toCharArray(); + int[] map = new int[256]; // map[37] = 4 37 4次 + for (int i = 0; i != str2.length; i++) { + map[str2[i]]++; + } + int all = str2.length; + + // [L,R-1] R + // [L,R) -> [0,0) + int L = 0; + int R = 0; + int minLen = Integer.MAX_VALUE; + while (R != str1.length) { + map[str1[R]]--; + if (map[str1[R]] >= 0) { + all--; + } + if (all == 0) { // 还完了 + while (map[str1[L]] < 0) { + map[str1[L++]]++; + } + // [L..R] + minLen = Math.min(minLen, R - L + 1); + all++; + map[str1[L++]]++; + } + R++; + } + return minLen == Integer.MAX_VALUE ? 0 : minLen; + } + + // 测试链接 : https://leetcode.com/problems/minimum-window-substring/ + public static String minWindow(String s, String t) { + if (s.length() < t.length()) { + return ""; + } + char[] str = s.toCharArray(); + char[] target = t.toCharArray(); + int[] map = new int[256]; + for (char cha : target) { + map[cha]++; + } + int all = target.length; + int L = 0; + int R = 0; + int minLen = Integer.MAX_VALUE; + int ansl = -1; + int ansr = -1; + while (R != str.length) { + map[str[R]]--; + if (map[str[R]] >= 0) { + all--; + } + if (all == 0) { + while (map[str[L]] < 0) { + map[str[L++]]++; + } + if (minLen > R - L + 1) { + minLen = R - L + 1; + ansl = L; + ansr = R; + } + all++; + map[str[L++]]++; + } + R++; + } + return minLen == Integer.MAX_VALUE ? "" : s.substring(ansl, ansr + 1); + } + +} diff --git a/大厂刷题班/class24/Code06_RemoveDuplicateLettersLessLexi.java b/大厂刷题班/class24/Code06_RemoveDuplicateLettersLessLexi.java new file mode 100644 index 0000000..2672411 --- /dev/null +++ b/大厂刷题班/class24/Code06_RemoveDuplicateLettersLessLexi.java @@ -0,0 +1,74 @@ +package class24; + +// 本题测试链接 : https://leetcode.com/problems/remove-duplicate-letters/ +public class Code06_RemoveDuplicateLettersLessLexi { + + // 在str中,每种字符都要保留一个,让最后的结果,字典序最小 ,并返回 + public static String removeDuplicateLetters1(String str) { + if (str == null || str.length() < 2) { + return str; + } + int[] map = new int[256]; + for (int i = 0; i < str.length(); i++) { + map[str.charAt(i)]++; + } + int minACSIndex = 0; + for (int i = 0; i < str.length(); i++) { + minACSIndex = str.charAt(minACSIndex) > str.charAt(i) ? i : minACSIndex; + if (--map[str.charAt(i)] == 0) { + break; + } + } + // 0...break(之前) minACSIndex + // str[minACSIndex] 剩下的字符串str[minACSIndex+1...] -> 去掉str[minACSIndex]字符 -> s' + // s'... + return String.valueOf(str.charAt(minACSIndex)) + removeDuplicateLetters1( + str.substring(minACSIndex + 1).replaceAll(String.valueOf(str.charAt(minACSIndex)), "")); + } + + public static String removeDuplicateLetters2(String s) { + char[] str = s.toCharArray(); + // 小写字母ascii码值范围[97~122],所以用长度为26的数组做次数统计 + // 如果map[i] > -1,则代表ascii码值为i的字符的出现次数 + // 如果map[i] == -1,则代表ascii码值为i的字符不再考虑 + int[] map = new int[26]; + for (int i = 0; i < str.length; i++) { + map[str[i] - 'a']++; + } + char[] res = new char[26]; + int index = 0; + int L = 0; + int R = 0; + while (R != str.length) { + // 如果当前字符是不再考虑的,直接跳过 + // 如果当前字符的出现次数减1之后,后面还能出现,直接跳过 + if (map[str[R] - 'a'] == -1 || --map[str[R] - 'a'] > 0) { + R++; + } else { // 当前字符需要考虑并且之后不会再出现了 + // 在str[L..R]上所有需要考虑的字符中,找到ascii码最小字符的位置 + int pick = -1; + for (int i = L; i <= R; i++) { + if (map[str[i] - 'a'] != -1 && (pick == -1 || str[i] < str[pick])) { + pick = i; + } + } + // 把ascii码最小的字符放到挑选结果中 + res[index++] = str[pick]; + // 在上一个的for循环中,str[L..R]范围上每种字符的出现次数都减少了 + // 需要把str[pick + 1..R]上每种字符的出现次数加回来 + for (int i = pick + 1; i <= R; i++) { + if (map[str[i] - 'a'] != -1) { // 只增加以后需要考虑字符的次数 + map[str[i] - 'a']++; + } + } + // 选出的ascii码最小的字符,以后不再考虑了 + map[str[pick] - 'a'] = -1; + // 继续在str[pick + 1......]上重复这个过程 + L = pick + 1; + R = L; + } + } + return String.valueOf(res, 0, index); + } + +} diff --git a/大厂刷题班/class25/Code01_IPToCIDR.java b/大厂刷题班/class25/Code01_IPToCIDR.java new file mode 100644 index 0000000..5cc59bd --- /dev/null +++ b/大厂刷题班/class25/Code01_IPToCIDR.java @@ -0,0 +1,90 @@ +package class25; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +// 本题测试链接 : https://leetcode.com/problems/ip-to-cidr/ +public class Code01_IPToCIDR { + + public static List ipToCIDR(String ip, int n) { + // ip -> 32位整数 + int cur = status(ip); + // cur这个状态,最右侧的1,能表示下2的几次方 + int maxPower = 0; + // 已经解决了多少ip了 + int solved = 0; + // 临时变量 + int power = 0; + List ans = new ArrayList<>(); + while (n > 0) { + // cur最右侧的1,能搞定2的几次方个ip + // cur : 000...000 01001000 + // 3 + maxPower = mostRightPower(cur); + // cur : 0000....0000 00001000 -> 2的3次方的问题 + // sol : 0000....0000 00000001 -> 1 2的0次方 + // sol : 0000....0000 00000010 -> 2 2的1次方 + // sol : 0000....0000 00000100 -> 4 2的2次方 + // sol : 0000....0000 00001000 -> 8 2的3次方 + solved = 1; + power = 0; + // 怕溢出 + // solved + while ((solved << 1) <= n && (power + 1) <= maxPower) { + solved <<= 1; + power++; + } + ans.add(content(cur, power)); + n -= solved; + cur += solved; + } + return ans; + } + + // ip -> int(32位状态) + public static int status(String ip) { + int ans = 0; + int move = 24; + for (String str : ip.split("\\.")) { + // 17.23.16.5 "17" "23" "16" "5" + // "17" -> 17 << 24 + // "23" -> 23 << 16 + // "16" -> 16 << 8 + // "5" -> 5 << 0 + ans |= Integer.valueOf(str) << move; + move -= 8; + } + return ans; + } + + public static HashMap map = new HashMap<>(); + // 1 000000....000000 -> 2的32次方 + + public static int mostRightPower(int num) { + // map只会生成1次,以后直接用 + if (map.isEmpty()) { + map.put(0, 32); + for (int i = 0; i < 32; i++) { + // 00...0000 00000001 2的0次方 + // 00...0000 00000010 2的1次方 + // 00...0000 00000100 2的2次方 + // 00...0000 00001000 2的3次方 + map.put(1 << i, i); + } + } + // num & (-num) -> num & (~num+1) -> 提取出最右侧的1 + return map.get(num & (-num)); + } + + public static String content(int status, int power) { + StringBuilder builder = new StringBuilder(); + for (int move = 24; move >= 0; move -= 8) { + builder.append(((status & (255 << move)) >>> move) + "."); + } + builder.setCharAt(builder.length() - 1, '/'); + builder.append(32 - power); + return builder.toString(); + } + +} diff --git a/大厂刷题班/class25/Code02_3Sum.java b/大厂刷题班/class25/Code02_3Sum.java new file mode 100644 index 0000000..363b4e3 --- /dev/null +++ b/大厂刷题班/class25/Code02_3Sum.java @@ -0,0 +1,70 @@ +package class25; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +// 本题测试链接 : https://leetcode.com/problems/3sum/ +public class Code02_3Sum { + + public static List> threeSum(int[] nums) { + Arrays.sort(nums); + int N = nums.length; + List> ans = new ArrayList<>(); + for (int i = N - 1; i > 1; i--) { // 三元组最后一个数,是arr[i] 之前....二元组 + arr[i] + if (i == N - 1 || nums[i] != nums[i + 1]) { + List> nexts = twoSum(nums, i - 1, -nums[i]); + for (List cur : nexts) { + cur.add(nums[i]); + ans.add(cur); + } + } + } + return ans; + } + + // nums[0...end]这个范围上,有多少个不同二元组,相加==target,全返回 + // {-1,5} K = 4 + // {1, 3} + public static List> twoSum(int[] nums, int end, int target) { + int L = 0; + int R = end; + List> ans = new ArrayList<>(); + while (L < R) { + if (nums[L] + nums[R] > target) { + R--; + } else if (nums[L] + nums[R] < target) { + L++; + } else { // nums[L] + nums[R] == target + if (L == 0 || nums[L - 1] != nums[L]) { + List cur = new ArrayList<>(); + cur.add(nums[L]); + cur.add(nums[R]); + ans.add(cur); + } + L++; + } + } + return ans; + } + + public static int findPairs(int[] nums, int k) { + Arrays.sort(nums); + int left = 0, right = 1; + int result = 0; + while (left < nums.length && right < nums.length) { + if (left == right || nums[right] - nums[left] < k) { + right++; + } else if (nums[right] - nums[left] > k) { + left++; + } else { + left++; + result++; + while (left < nums.length && nums[left] == nums[left - 1]) + left++; + } + } + return result; + } + +} diff --git a/大厂刷题班/class25/Code03_MaxPointsOnALine.java b/大厂刷题班/class25/Code03_MaxPointsOnALine.java new file mode 100644 index 0000000..e1599c7 --- /dev/null +++ b/大厂刷题班/class25/Code03_MaxPointsOnALine.java @@ -0,0 +1,68 @@ +package class25; + +import java.util.HashMap; +import java.util.Map; + +// 本题测试链接: https://leetcode.com/problems/max-points-on-a-line/ +public class Code03_MaxPointsOnALine { + + // [ + // [1,3] + // [4,9] + // [5,7] + // ] + + public static int maxPoints(int[][] points) { + if (points == null) { + return 0; + } + if (points.length <= 2) { + return points.length; + } + // key = 3 + // value = {7 , 10} -> 斜率为3/7的点 有10个 + // {5, 15} -> 斜率为3/5的点 有15个 + Map> map = new HashMap>(); + int result = 0; + for (int i = 0; i < points.length; i++) { + map.clear(); + int samePosition = 1; + int sameX = 0; + int sameY = 0; + int line = 0; + for (int j = i + 1; j < points.length; j++) { // i号点,和j号点,的斜率关系 + int x = points[j][0] - points[i][0]; + int y = points[j][1] - points[i][1]; + if (x == 0 && y == 0) { + samePosition++; + } else if (x == 0) { + sameX++; + } else if (y == 0) { + sameY++; + } else { // 普通斜率 + int gcd = gcd(x, y); + x /= gcd; + y /= gcd; + // x / y + if (!map.containsKey(x)) { + map.put(x, new HashMap()); + } + if (!map.get(x).containsKey(y)) { + map.get(x).put(y, 0); + } + map.get(x).put(y, map.get(x).get(y) + 1); + line = Math.max(line, map.get(x).get(y)); + } + } + result = Math.max(result, Math.max(Math.max(sameX, sameY), line) + samePosition); + } + return result; + } + + // 保证初始调用的时候,a和b不等于0 + // O(1) + public static int gcd(int a, int b) { + return b == 0 ? a : gcd(b, a % b); + } + +} diff --git a/大厂刷题班/class25/Code04_GasStation.java b/大厂刷题班/class25/Code04_GasStation.java new file mode 100644 index 0000000..cc044e6 --- /dev/null +++ b/大厂刷题班/class25/Code04_GasStation.java @@ -0,0 +1,104 @@ +package class25; + +// 本题测试链接 : https://leetcode.com/problems/gas-station/ +// 注意本题的实现比leetcode上的问法更加通用 +// leetcode只让返回其中一个良好出发点的位置 +// 本题是返回结果数组,每一个出发点是否是良好出发点都求出来了 +// 得到结果数组的过程,时间复杂度O(N),额外空间复杂度O(1) +public class Code04_GasStation { + + public static int canCompleteCircuit(int[] gas, int[] cost) { + if (gas == null || gas.length == 0) { + return -1; + } + if (gas.length == 1) { + return gas[0] < cost[0] ? -1 : 0; + } + boolean[] good = stations(cost, gas); + for (int i = 0; i < gas.length; i++) { + if (good[i]) { + return i; + } + } + return -1; + } + + public static boolean[] stations(int[] cost, int[] gas) { + if (cost == null || gas == null || cost.length < 2 || cost.length != gas.length) { + return null; + } + int init = changeDisArrayGetInit(cost, gas); + return init == -1 ? new boolean[cost.length] : enlargeArea(cost, init); + } + + public static int changeDisArrayGetInit(int[] dis, int[] oil) { + int init = -1; + for (int i = 0; i < dis.length; i++) { + dis[i] = oil[i] - dis[i]; + if (dis[i] >= 0) { + init = i; + } + } + return init; + } + + public static boolean[] enlargeArea(int[] dis, int init) { + boolean[] res = new boolean[dis.length]; + int start = init; + int end = nextIndex(init, dis.length); + int need = 0; + int rest = 0; + do { + // 当前来到的start已经在连通区域中,可以确定后续的开始点一定无法转完一圈 + if (start != init && start == lastIndex(end, dis.length)) { + break; + } + // 当前来到的start不在连通区域中,就扩充连通区域 + // start(5) -> 联通区的头部(7) -> 2 + // start(-2) -> 联通区的头部(7) -> 9 + if (dis[start] < need) { // 当前start无法接到连通区的头部 + need -= dis[start]; + } else { // 当前start可以接到连通区的头部,开始扩充连通区域的尾巴 + // start(7) -> 联通区的头部(5) + rest += dis[start] - need; + need = 0; + while (rest >= 0 && end != start) { + rest += dis[end]; + end = nextIndex(end, dis.length); + } + // 如果连通区域已经覆盖整个环,当前的start是良好出发点,进入2阶段 + if (rest >= 0) { + res[start] = true; + connectGood(dis, lastIndex(start, dis.length), init, res); + break; + } + } + start = lastIndex(start, dis.length); + } while (start != init); + return res; + } + + // 已知start的next方向上有一个良好出发点 + // start如果可以达到这个良好出发点,那么从start出发一定可以转一圈 + public static void connectGood(int[] dis, int start, int init, boolean[] res) { + int need = 0; + while (start != init) { + if (dis[start] < need) { + need -= dis[start]; + } else { + res[start] = true; + need = 0; + } + start = lastIndex(start, dis.length); + } + } + + public static int lastIndex(int index, int size) { + return index == 0 ? (size - 1) : index - 1; + } + + public static int nextIndex(int index, int size) { + return index == size - 1 ? 0 : (index + 1); + } + +} diff --git a/大厂刷题班/class26/Code01_MinRange.java b/大厂刷题班/class26/Code01_MinRange.java new file mode 100644 index 0000000..537cc65 --- /dev/null +++ b/大厂刷题班/class26/Code01_MinRange.java @@ -0,0 +1,119 @@ +package class26; + +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import java.util.TreeSet; + +public class Code01_MinRange { + + // 本题为求最小包含区间 + // 测试链接 : + // https://leetcode.com/problems/smallest-range-covering-elements-from-k-lists/ + public static class Node { + public int val; + public int arr; + public int idx; + + public Node(int value, int arrIndex, int index) { + val = value; + arr = arrIndex; + idx = index; + } + } + + public static class NodeComparator implements Comparator { + + @Override + public int compare(Node a, Node b) { + return a.val != b.val ? (a.val - b.val) : (a.arr - b.arr); + } + + } + + public static int[] smallestRange(List> nums) { + int N = nums.size(); + TreeSet set = new TreeSet<>(new NodeComparator()); + for (int i = 0; i < N; i++) { + set.add(new Node(nums.get(i).get(0), i, 0)); + } + int r = Integer.MAX_VALUE; + int a = 0; + int b = 0; + while (set.size() == N) { + Node max = set.last(); + Node min = set.pollFirst(); + if (max.val - min.val < r) { + r = max.val - min.val; + a = min.val; + b = max.val; + } + if (min.idx < nums.get(min.arr).size() - 1) { + set.add(new Node(nums.get(min.arr).get(min.idx + 1), min.arr, min.idx + 1)); + } + } + return new int[] { a, b }; + } + + // 以下为课堂题目,在main函数里有对数器 + public static int minRange1(int[][] m) { + int min = Integer.MAX_VALUE; + for (int i = 0; i < m[0].length; i++) { + for (int j = 0; j < m[1].length; j++) { + for (int k = 0; k < m[2].length; k++) { + min = Math.min(min, + Math.abs(m[0][i] - m[1][j]) + Math.abs(m[1][j] - m[2][k]) + Math.abs(m[2][k] - m[0][i])); + } + } + } + return min; + } + + public static int minRange2(int[][] matrix) { + int N = matrix.length; + TreeSet set = new TreeSet<>(new NodeComparator()); + for (int i = 0; i < N; i++) { + set.add(new Node(matrix[i][0], i, 0)); + } + int min = Integer.MAX_VALUE; + while (set.size() == N) { + min = Math.min(min, set.last().val - set.first().val); + Node cur = set.pollFirst(); + if (cur.idx < matrix[cur.arr].length - 1) { + set.add(new Node(matrix[cur.arr][cur.idx + 1], cur.arr, cur.idx + 1)); + } + } + return min << 1; + } + + public static int[][] generateRandomMatrix(int n, int v) { + int[][] m = new int[3][]; + int s = 0; + for (int i = 0; i < 3; i++) { + s = (int) (Math.random() * n) + 1; + m[i] = new int[s]; + for (int j = 0; j < s; j++) { + m[i][j] = (int) (Math.random() * v); + } + Arrays.sort(m[i]); + } + return m; + } + + public static void main(String[] args) { + int n = 20; + int v = 200; + int t = 1000000; + System.out.println("测试开始"); + for (int i = 0; i < t; i++) { + int[][] m = generateRandomMatrix(n, v); + int ans1 = minRange1(m); + int ans2 = minRange2(m); + if (ans1 != ans2) { + System.out.println("出错了!"); + } + } + System.out.println("测试结束"); + } + +} diff --git a/大厂刷题班/class26/Code02_WordSearchII.java b/大厂刷题班/class26/Code02_WordSearchII.java new file mode 100644 index 0000000..fcfaeec --- /dev/null +++ b/大厂刷题班/class26/Code02_WordSearchII.java @@ -0,0 +1,122 @@ +package class26; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; + +// 本题测试链接 : https://leetcode.com/problems/word-search-ii/ +public class Code02_WordSearchII { + + public static class TrieNode { + public TrieNode[] nexts; + public int pass; + public boolean end; + + public TrieNode() { + nexts = new TrieNode[26]; + pass = 0; + end = false; + } + + } + + public static void fillWord(TrieNode head, String word) { + head.pass++; + char[] chs = word.toCharArray(); + int index = 0; + TrieNode node = head; + for (int i = 0; i < chs.length; i++) { + index = chs[i] - 'a'; + if (node.nexts[index] == null) { + node.nexts[index] = new TrieNode(); + } + node = node.nexts[index]; + node.pass++; + } + node.end = true; + } + + public static String generatePath(LinkedList path) { + char[] str = new char[path.size()]; + int index = 0; + for (Character cha : path) { + str[index++] = cha; + } + return String.valueOf(str); + } + + public static List findWords(char[][] board, String[] words) { + TrieNode head = new TrieNode(); // 前缀树最顶端的头 + HashSet set = new HashSet<>(); + for (String word : words) { + if (!set.contains(word)) { + fillWord(head, word); + set.add(word); + } + } + // 答案 + List ans = new ArrayList<>(); + // 沿途走过的字符,收集起来,存在path里 + LinkedList path = new LinkedList<>(); + for (int row = 0; row < board.length; row++) { + for (int col = 0; col < board[0].length; col++) { + // 枚举在board中的所有位置 + // 每一个位置出发的情况下,答案都收集 + process(board, row, col, path, head, ans); + } + } + return ans; + } + + // 从board[row][col]位置的字符出发, + // 之前的路径上,走过的字符,记录在path里 + // cur还没有登上,有待检查能不能登上去的前缀树的节点 + // 如果找到words中的某个str,就记录在 res里 + // 返回值,从row,col 出发,一共找到了多少个str + public static int process( + char[][] board, int row, int col, + LinkedList path, TrieNode cur, + List res) { + char cha = board[row][col]; + if (cha == 0) { // 这个row col位置是之前走过的位置 + return 0; + } + // (row,col) 不是回头路 cha 有效 + + int index = cha - 'a'; + // 如果没路,或者这条路上最终的字符串之前加入过结果里 + if (cur.nexts[index] == null || cur.nexts[index].pass == 0) { + return 0; + } + // 没有走回头路且能登上去 + cur = cur.nexts[index]; + path.addLast(cha);// 当前位置的字符加到路径里去 + int fix = 0; // 从row和col位置出发,后续一共搞定了多少答案 + // 当我来到row col位置,如果决定不往后走了。是不是已经搞定了某个字符串了 + if (cur.end) { + res.add(generatePath(path)); + cur.end = false; + fix++; + } + // 往上、下、左、右,四个方向尝试 + board[row][col] = 0; + if (row > 0) { + fix += process(board, row - 1, col, path, cur, res); + } + if (row < board.length - 1) { + fix += process(board, row + 1, col, path, cur, res); + } + if (col > 0) { + fix += process(board, row, col - 1, path, cur, res); + } + if (col < board[0].length - 1) { + fix += process(board, row, col + 1, path, cur, res); + } + board[row][col] = cha; + path.pollLast(); + cur.pass -= fix; + return fix; + } + +} diff --git a/大厂刷题班/class26/Code03_ExpressionAddOperators.java b/大厂刷题班/class26/Code03_ExpressionAddOperators.java new file mode 100644 index 0000000..2422591 --- /dev/null +++ b/大厂刷题班/class26/Code03_ExpressionAddOperators.java @@ -0,0 +1,65 @@ +package class26; + +import java.util.LinkedList; +import java.util.List; + +// 本题测试链接 : https://leetcode.com/problems/expression-add-operators/ +public class Code03_ExpressionAddOperators { + + public static List addOperators(String num, int target) { + List ret = new LinkedList<>(); + if (num.length() == 0) { + return ret; + } + // 沿途的数字拷贝和+ - * 的决定,放在path里 + char[] path = new char[num.length() * 2 - 1]; + // num -> char[] + char[] digits = num.toCharArray(); + long n = 0; + for (int i = 0; i < digits.length; i++) { // 尝试0~i前缀作为第一部分 + n = n * 10 + digits[i] - '0'; + path[i] = digits[i]; + dfs(ret, path, i + 1, 0, n, digits, i + 1, target); // 后续过程 + if (n == 0) { + break; + } + } + return ret; + } + + // char[] digits 固定参数,字符类型数组,等同于num + // int target 目标 + // char[] path 之前做的决定,已经从左往右依次填写的字符在其中,可能含有'0'~'9' 与 * - + + // int len path[0..len-1]已经填写好,len是终止 + // int pos 字符类型数组num, 使用到了哪 + // left -> 前面固定的部分 cur -> 前一块 + // 默认 left + cur ... + public static void dfs(List res, char[] path, int len, + long left, long cur, + char[] num, int index, int aim) { + if (index == num.length) { + if (left + cur == aim) { + res.add(new String(path, 0, len)); + } + return; + } + long n = 0; + int j = len + 1; + for (int i = index; i < num.length; i++) { // pos ~ i + // 试每一个可能的前缀,作为第一个数字! + // num[index...i] 作为第一个数字! + n = n * 10 + num[i] - '0'; + path[j++] = num[i]; + path[len] = '+'; + dfs(res, path, j, left + cur, n, num, i + 1, aim); + path[len] = '-'; + dfs(res, path, j, left + cur, -n, num, i + 1, aim); + path[len] = '*'; + dfs(res, path, j, left, cur * n, num, i + 1, aim); + if (num[index] == '0') { + break; + } + } + } + +} diff --git a/大厂刷题班/class26/Code04_WordLadderII.java b/大厂刷题班/class26/Code04_WordLadderII.java new file mode 100644 index 0000000..d55ad1e --- /dev/null +++ b/大厂刷题班/class26/Code04_WordLadderII.java @@ -0,0 +1,100 @@ +package class26; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; + +// 本题测试链接 : https://leetcode.com/problems/word-ladder-ii/ +public class Code04_WordLadderII { + + public static List> findLadders(String start, String end, List list) { + list.add(start); + HashMap> nexts = getNexts(list); + HashMap fromDistances = getDistances(start, nexts); + List> res = new ArrayList<>(); + if (!fromDistances.containsKey(end)) { + return res; + } + HashMap toDistances = getDistances(end, nexts); + LinkedList pathList = new LinkedList<>(); + getShortestPaths(start, end, nexts, fromDistances, toDistances, pathList, res); + return res; + } + + public static HashMap> getNexts(List words) { + HashSet dict = new HashSet<>(words); + HashMap> nexts = new HashMap<>(); + for (int i = 0; i < words.size(); i++) { + nexts.put(words.get(i), getNext(words.get(i), dict)); + } + return nexts; + } + + // word, 在表中,有哪些邻居,把邻居们,生成list返回 + public static List getNext(String word, HashSet dict) { + ArrayList res = new ArrayList(); + char[] chs = word.toCharArray(); + for (char cur = 'a'; cur <= 'z'; cur++) { + for (int i = 0; i < chs.length; i++) { + if (chs[i] != cur) { + char tmp = chs[i]; + chs[i] = cur; + if (dict.contains(String.valueOf(chs))) { + res.add(String.valueOf(chs)); + } + chs[i] = tmp; + } + } + } + return res; + } + + // 生成距离表,从start开始,根据邻居表,宽度优先遍历,对于能够遇到的所有字符串,生成(字符串,距离)这条记录,放入距离表中 + public static HashMap getDistances(String start, HashMap> nexts) { + HashMap distances = new HashMap<>(); + distances.put(start, 0); + Queue queue = new LinkedList<>(); + queue.add(start); + HashSet set = new HashSet<>(); + set.add(start); + while (!queue.isEmpty()) { + String cur = queue.poll(); + for (String next : nexts.get(cur)) { + if (!set.contains(next)) { + distances.put(next, distances.get(cur) + 1); + queue.add(next); + set.add(next); + } + } + } + return distances; + } + + // cur 当前来到的字符串 可变 + // to 目标,固定参数 + // nexts 每一个字符串的邻居表 + // cur 到开头距离5 -> 到开头距离是6的支路 fromDistances距离表 + // cur 到结尾距离5 -> 到开头距离是4的支路 toDistances距离表 + // path : 来到cur之前,深度优先遍历之前的历史是什么 + // res : 当遇到cur,把历史,放入res,作为一个结果 + public static void getShortestPaths(String cur, String to, HashMap> nexts, + HashMap fromDistances, HashMap toDistances, LinkedList path, + List> res) { + path.add(cur); + if (to.equals(cur)) { + res.add(new LinkedList(path)); + } else { + for (String next : nexts.get(cur)) { + if (fromDistances.get(next) == fromDistances.get(cur) + 1 + && toDistances.get(next) == toDistances.get(cur) - 1) { + getShortestPaths(next, to, nexts, fromDistances, toDistances, path, res); + } + } + } + path.pollLast(); + } + +} diff --git a/大厂刷题班/class27/Code01_PickBands.java b/大厂刷题班/class27/Code01_PickBands.java new file mode 100644 index 0000000..eaaa435 --- /dev/null +++ b/大厂刷题班/class27/Code01_PickBands.java @@ -0,0 +1,221 @@ +package class27; + +import java.util.Arrays; + +public class Code01_PickBands { + + // 每一个项目都有三个数,[a,b,c]表示这个项目a和b乐队参演,花费为c + // 每一个乐队可能在多个项目里都出现了,但是只能挑一次 + // nums是可以挑选的项目数量,所以一定会有nums*2只乐队被挑选出来 + // 乐队的全部数量一定是nums*2,且标号一定是0 ~ nums*2-1 + // 返回一共挑nums轮(也就意味着一定请到所有的乐队),最少花费是多少? + public static int minCost(int[][] programs, int nums) { + if (nums == 0 || programs == null || programs.length == 0) { + return 0; + } + int size = clean(programs); + int[] map1 = init(1 << (nums << 1)); + int[] map2 = null; + if ((nums & 1) == 0) { + // nums = 8 , 4 + f(programs, size, 0, 0, 0, nums >> 1, map1); + map2 = map1; + } else { + // nums == 7 4 -> map1 3 -> map2 + f(programs, size, 0, 0, 0, nums >> 1, map1); + map2 = init(1 << (nums << 1)); + f(programs, size, 0, 0, 0, nums - (nums >> 1), map2); + } + // 16 mask : 0..00 1111.1111(16个) + // 12 mask : 0..00 1111.1111(12个) + int mask = (1 << (nums << 1)) - 1; + int ans = Integer.MAX_VALUE; + for (int i = 0; i < map1.length; i++) { + if (map1[i] != Integer.MAX_VALUE && map2[mask & (~i)] != Integer.MAX_VALUE) { + ans = Math.min(ans, map1[i] + map2[mask & (~i)]); + } + } + return ans == Integer.MAX_VALUE ? -1 : ans; + } + + // [ + // [9, 1, 100] + // [2, 9 , 50] + // ... + // ] + public static int clean(int[][] programs) { + int x = 0; + int y = 0; + for (int[] p : programs) { + x = Math.min(p[0], p[1]); + y = Math.max(p[0], p[1]); + p[0] = x; + p[1] = y; + } + Arrays.sort(programs, (a, b) -> a[0] != b[0] ? (a[0] - b[0]) : (a[1] != b[1] ? (a[1] - b[1]) : (a[2] - b[2]))); + x = programs[0][0]; + y = programs[0][1]; + int n = programs.length; + // (0, 1, ? ) + for (int i = 1; i < n; i++) { + if (programs[i][0] == x && programs[i][1] == y) { + programs[i] = null; + } else { + x = programs[i][0]; + y = programs[i][1]; + } + } + int size = 1; + for (int i = 1; i < n; i++) { + if (programs[i] != null) { + programs[size++] = programs[i]; + } + } + // programs[0...size-1] + return size; + } + + public static int[] init(int size) { + int[] arr = new int[size]; + for (int i = 0; i < size; i++) { + arr[i] = Integer.MAX_VALUE; + } + return arr; + } + + public static void f(int[][] programs, int size, int index, int status, int cost, int rest, int[] map) { + if (rest == 0) { + map[status] = Math.min(map[status], cost); + } else { + if (index != size) { + f(programs, size, index + 1, status, cost, rest, map); + int pick = 0 | (1 << programs[index][0]) | (1 << programs[index][1]); + if ((pick & status) == 0) { + f(programs, size, index + 1, status | pick, cost + programs[index][2], rest - 1, map); + } + } + } + } + +// // 如果nums,2 * nums 只乐队 +// // (1 << (nums << 1)) - 1 +// // programs 洗数据 size +// // nums = 8 16只乐队 +// +// // process(programs, size, (1 << (nums << 1)) - 1, 0, 4, 0, 0) +// +// public static int minCost = Integer.MAX_VALUE; +// +// public static int[] map = new int[1 << 16]; // map初始化全变成系统最大 +// +// +// +// public static void process(int[][] programs, int size, int index, int rest, int pick, int cost) { +// if (rest == 0) { +// +// map[pick] = Math.min(map[pick], cost); +// +// } else { // 还有项目可挑 +// if (index != size) { +// // 不考虑当前的项目!programs[index]; +// process(programs, size, index + 1, rest, pick, cost); +// // 考虑当前的项目!programs[index]; +// int x = programs[index][0]; +// int y = programs[index][1]; +// int cur = (1 << x) | (1 << y); +// if ((pick & cur) == 0) { // 终于可以考虑了! +// process(programs, size, index + 1, rest - 1, pick | cur, cost + programs[index][2]); +// } +// } +// } +// } +// +// +// public static void zuo(int[] arr, int index, int rest) { +// if(rest == 0) { +// 停止 +// } +// if(index != arr.length) { +// zuo(arr, index + 1, rest); +// zuo(arr, index + 1, rest - 1); +// } +// } + + // 为了测试 + public static int right(int[][] programs, int nums) { + min = Integer.MAX_VALUE; + r(programs, 0, nums, 0, 0); + return min == Integer.MAX_VALUE ? -1 : min; + } + + public static int min = Integer.MAX_VALUE; + + public static void r(int[][] programs, int index, int rest, int pick, int cost) { + if (rest == 0) { + min = Math.min(min, cost); + } else { + if (index < programs.length) { + r(programs, index + 1, rest, pick, cost); + int cur = (1 << programs[index][0]) | (1 << programs[index][1]); + if ((pick & cur) == 0) { + r(programs, index + 1, rest - 1, pick | cur, cost + programs[index][2]); + } + } + } + } + + // 为了测试 + public static int[][] randomPrograms(int N, int V) { + int nums = N << 1; + int n = nums * (nums - 1); + int[][] programs = new int[n][3]; + for (int i = 0; i < n; i++) { + int a = (int) (Math.random() * nums); + int b = 0; + do { + b = (int) (Math.random() * nums); + } while (b == a); + programs[i][0] = a; + programs[i][1] = b; + programs[i][2] = (int) (Math.random() * V) + 1; + } + return programs; + } + + // 为了测试 + public static void main(String[] args) { + int N = 4; + int V = 100; + int T = 10000; + System.out.println("测试开始"); + for (int i = 0; i < T; i++) { + int nums = (int) (Math.random() * N) + 1; + int[][] programs = randomPrograms(nums, V); + int ans1 = right(programs, nums); + int ans2 = minCost(programs, nums); + if (ans1 != ans2) { + System.out.println("Oops!"); + break; + } + } + System.out.println("测试结束"); + + long start; + long end; + int[][] programs; + + programs = randomPrograms(7, V); + start = System.currentTimeMillis(); + right(programs, 7); + end = System.currentTimeMillis(); + System.out.println("right方法,在nums=7时候的运行时间(毫秒) : " + (end - start)); + + programs = randomPrograms(10, V); + start = System.currentTimeMillis(); + minCost(programs, 10); + end = System.currentTimeMillis(); + System.out.println("minCost方法,在nums=10时候的运行时间(毫秒) : " + (end - start)); + + } + +} diff --git a/大厂刷题班/class27/Code02_MinPeople.java b/大厂刷题班/class27/Code02_MinPeople.java new file mode 100644 index 0000000..64dc565 --- /dev/null +++ b/大厂刷题班/class27/Code02_MinPeople.java @@ -0,0 +1,61 @@ +package class27; + +import java.util.Arrays; + +public class Code02_MinPeople { + +// 三道题如下: +// 微信面试题 +// 题目一: +// 股民小 A 有一天穿越回到了 n 天前,他能记住某只股票连续 n 天的价格;他手上有足够多的启动资金,他可以在这 n 天内多次交易,但是有个限制 +// 如果已经购买了一个股票,在卖出它之前就不能再继续购买股票了。 +// 到 n 天之后,小 A 不能再持有股票。 +// 求问 这 n 天内,小 A 能够获得的利润的最大值 +// 如果不需要手续费的话,求最大的利润 +// function(number) { +// return number +// } +// 输入: prices = [3, 1, 2, 8, 5, 9] +// 输出: 11 +// +// 题目二: +// 企鹅厂每年都会发文化衫,文化衫有很多种,厂庆的时候,企鹅们都需要穿文化衫来拍照 +// 一次采访中,记者随机遇到的企鹅,企鹅会告诉记者还有多少企鹅跟他穿一种文化衫 +// 我们将这些回答放在 answers 数组里,返回鹅厂中企鹅的最少数量。 +// 输入: answers = [1] 输出:2 +// 输入: answers = [1, 1, 2] 输出:5 +// Leetcode题目:https://leetcode.com/problems/rabbits-in-forest/ +// +// 题目三: +// WXG 的秘书有一堆的文件袋,现在秘书需要把文件袋嵌套收纳起来。请你帮他计算下,最大嵌套数量。 +// 给你一个二维整数数组 folders ,其中 folders[i] = [wi, hi] ,表示第 i 个文件袋的宽度和长度 +// 当某一个文件袋的宽度和长度都比这个另外一个文件袋大的时候,前者就能把后者装起来,称之为一组文件袋。 +// 请计算,最大的一组文件袋的数量是多少。 +// 实例 +// 输入:[[6,10],[11,14],[6,1],[16,14],[13,2]] +// 输出: 3 + + // 题目一,股票系列专题,大厂刷题班15节 + // 题目三,最长递增子序列专题,大厂刷题班第9节 + // 我们来讲一下题目二 + public static int numRabbits(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + Arrays.sort(arr); + int x = arr[0]; + int c = 1; + int ans = 0; + for (int i = 1; i < arr.length; i++) { + if (x != arr[i]) { + ans += ((c + x) / (x + 1)) * (x + 1); + x = arr[i]; + c = 1; + } else { + c++; + } + } + return ans + ((c + x) / (x + 1)) * (x + 1); + } + +} diff --git a/大厂刷题班/class27/Problem_0001_TwoSum.java b/大厂刷题班/class27/Problem_0001_TwoSum.java new file mode 100644 index 0000000..d1c4619 --- /dev/null +++ b/大厂刷题班/class27/Problem_0001_TwoSum.java @@ -0,0 +1,19 @@ +package class27; + +import java.util.HashMap; + +public class Problem_0001_TwoSum { + + public static int[] twoSum(int[] nums, int target) { + // key 某个之前的数 value 这个数出现的位置 + HashMap map = new HashMap<>(); + for (int i = 0; i < nums.length; i++) { + if (map.containsKey(target - nums[i])) { + return new int[] { map.get(target - nums[i]), i }; + } + map.put(nums[i], i); + } + return new int[] { -1, -1 }; + } + +} diff --git a/大厂刷题班/class27/Problem_0007_ReverseInteger.java b/大厂刷题班/class27/Problem_0007_ReverseInteger.java new file mode 100644 index 0000000..a26a5dc --- /dev/null +++ b/大厂刷题班/class27/Problem_0007_ReverseInteger.java @@ -0,0 +1,21 @@ +package class27; + +public class Problem_0007_ReverseInteger { + + public static int reverse(int x) { + boolean neg = ((x >>> 31) & 1) == 1; + x = neg ? x : -x; + int m = Integer.MIN_VALUE / 10; + int o = Integer.MIN_VALUE % 10; + int res = 0; + while (x != 0) { + if (res < m || (res == m && x % 10 < o)) { + return 0; + } + res = res * 10 + x % 10; + x /= 10; + } + return neg ? res : Math.abs(res); + } + +} diff --git a/大厂刷题班/class27/说明 b/大厂刷题班/class27/说明 new file mode 100644 index 0000000..dc0c1f5 --- /dev/null +++ b/大厂刷题班/class27/说明 @@ -0,0 +1,12 @@ +leetcode高频题 +leetcode全题目列表 : https://leetcode.com/problemset/all/ +在全题目列表的右侧栏,点击Top Interview Questions +或者直接进入右侧链接 : https://leetcode.com/problemset/all/?listId=wpwgkgt +即可看到leetcode高频题全列表 +本节课解决leetcode高频题列表中的如下题目 : +0001 : 大厂刷题班, 第27节, 本节 +0002 : 算法新手班, 第4节第5题 +0003 : 大厂刷题班, 第3节第1题 +0004 : 大厂刷题班, 第12节第3题 +0005 : 体系学习班, Manacher算法 +0007 : 大厂刷题班, 第27节, 本节 \ No newline at end of file diff --git a/大厂刷题班/class28/Problem_0008_StringToInteger.java b/大厂刷题班/class28/Problem_0008_StringToInteger.java new file mode 100644 index 0000000..ecab645 --- /dev/null +++ b/大厂刷题班/class28/Problem_0008_StringToInteger.java @@ -0,0 +1,80 @@ +package class28; + +public class Problem_0008_StringToInteger { + + public static int myAtoi(String s) { + if (s == null || s.equals("")) { + return 0; + } + s = removeHeadZero(s.trim()); + if (s == null || s.equals("")) { + return 0; + } + char[] str = s.toCharArray(); + if (!isValid(str)) { + return 0; + } + // str 是符合日常书写的,正经整数形式 + boolean posi = str[0] == '-' ? false : true; + int minq = Integer.MIN_VALUE / 10; + int minr = Integer.MIN_VALUE % 10; + int res = 0; + int cur = 0; + for (int i = (str[0] == '-' || str[0] == '+') ? 1 : 0; i < str.length; i++) { + // 3 cur = -3 '5' cur = -5 '0' cur = 0 + cur = '0' - str[i]; + if ((res < minq) || (res == minq && cur < minr)) { + return posi ? Integer.MAX_VALUE : Integer.MIN_VALUE; + } + res = res * 10 + cur; + } + // res 负 + if (posi && res == Integer.MIN_VALUE) { + return Integer.MAX_VALUE; + } + return posi ? -res : res; + } + + public static String removeHeadZero(String str) { + boolean r = (str.startsWith("+") || str.startsWith("-")); + int s = r ? 1 : 0; + for (; s < str.length(); s++) { + if (str.charAt(s) != '0') { + break; + } + } + // s 到了第一个不是'0'字符的位置 + int e = -1; + // 左<-右 + for (int i = str.length() - 1; i >= (r ? 1 : 0); i--) { + if (str.charAt(i) < '0' || str.charAt(i) > '9') { + e = i; + } + } + // e 到了最左的 不是数字字符的位置 + return (r ? String.valueOf(str.charAt(0)) : "") + str.substring(s, e == -1 ? str.length() : e); + } + + public static boolean isValid(char[] chas) { + if (chas[0] != '-' && chas[0] != '+' && (chas[0] < '0' || chas[0] > '9')) { + return false; + } + if ((chas[0] == '-' || chas[0] == '+') && chas.length == 1) { + return false; + } + // 0 +... -... num + for (int i = 1; i < chas.length; i++) { + if (chas[i] < '0' || chas[i] > '9') { + return false; + } + } + return true; + } + + public static void main(String[] args) { + System.out.println(Integer.MAX_VALUE); + } + + + +} diff --git a/大厂刷题班/class28/Problem_0012_IntegerToRoman.java b/大厂刷题班/class28/Problem_0012_IntegerToRoman.java new file mode 100644 index 0000000..d495efc --- /dev/null +++ b/大厂刷题班/class28/Problem_0012_IntegerToRoman.java @@ -0,0 +1,20 @@ +package class28; + +public class Problem_0012_IntegerToRoman { + + public static String intToRoman(int num) { + String[][] c = { + { "", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX" }, + { "", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC" }, + { "", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM" }, + { "", "M", "MM", "MMM" } }; + StringBuilder roman = new StringBuilder(); + roman + .append(c[3][num / 1000 % 10]) + .append(c[2][num / 100 % 10]) + .append(c[1][num / 10 % 10]) + .append(c[0][num % 10]); + return roman.toString(); + } + +} diff --git a/大厂刷题班/class28/Problem_0013_RomanToInteger.java b/大厂刷题班/class28/Problem_0013_RomanToInteger.java new file mode 100644 index 0000000..d8ae1bc --- /dev/null +++ b/大厂刷题班/class28/Problem_0013_RomanToInteger.java @@ -0,0 +1,45 @@ +package class28; + +public class Problem_0013_RomanToInteger { + + public static int romanToInt(String s) { + // C M X C I X + // 100 1000 10 100 1 10 + int nums[] = new int[s.length()]; + for (int i = 0; i < s.length(); i++) { + switch (s.charAt(i)) { + case 'M': + nums[i] = 1000; + break; + case 'D': + nums[i] = 500; + break; + case 'C': + nums[i] = 100; + break; + case 'L': + nums[i] = 50; + break; + case 'X': + nums[i] = 10; + break; + case 'V': + nums[i] = 5; + break; + case 'I': + nums[i] = 1; + break; + } + } + int sum = 0; + for (int i = 0; i < nums.length - 1; i++) { + if (nums[i] < nums[i + 1]) { + sum -= nums[i]; + } else { + sum += nums[i]; + } + } + return sum + nums[nums.length - 1]; + } + +} diff --git a/大厂刷题班/class28/Problem_0014_LongestCommonPrefix.java b/大厂刷题班/class28/Problem_0014_LongestCommonPrefix.java new file mode 100644 index 0000000..0ee811c --- /dev/null +++ b/大厂刷题班/class28/Problem_0014_LongestCommonPrefix.java @@ -0,0 +1,28 @@ +package class28; + +public class Problem_0014_LongestCommonPrefix { + + public static String longestCommonPrefix(String[] strs) { + if (strs == null || strs.length == 0) { + return ""; + } + char[] chs = strs[0].toCharArray(); + int min = Integer.MAX_VALUE; + for (String str : strs) { + char[] tmp = str.toCharArray(); + int index = 0; + while (index < tmp.length && index < chs.length) { + if (chs[index] != tmp[index]) { + break; + } + index++; + } + min = Math.min(index, min); + if (min == 0) { + return ""; + } + } + return strs[0].substring(0, min); + } + +} diff --git a/大厂刷题班/class28/Problem_0017_LetterCombinationsOfAPhoneNumber.java b/大厂刷题班/class28/Problem_0017_LetterCombinationsOfAPhoneNumber.java new file mode 100644 index 0000000..14fb701 --- /dev/null +++ b/大厂刷题班/class28/Problem_0017_LetterCombinationsOfAPhoneNumber.java @@ -0,0 +1,43 @@ +package class28; + +import java.util.ArrayList; +import java.util.List; + +public class Problem_0017_LetterCombinationsOfAPhoneNumber { + + public static char[][] phone = { + { 'a', 'b', 'c' }, // 2 0 + { 'd', 'e', 'f' }, // 3 1 + { 'g', 'h', 'i' }, // 4 2 + { 'j', 'k', 'l' }, // 5 3 + { 'm', 'n', 'o' }, // 6 + { 'p', 'q', 'r', 's' }, // 7 + { 't', 'u', 'v' }, // 8 + { 'w', 'x', 'y', 'z' }, // 9 + }; + + // "23" + public static List letterCombinations(String digits) { + List ans = new ArrayList<>(); + if (digits == null || digits.length() == 0) { + return ans; + } + char[] str = digits.toCharArray(); + char[] path = new char[str.length]; + process(str, 0, path, ans); + return ans; + } + + public static void process(char[] str, int index, char[] path, List ans) { + if (index == str.length) { + ans.add(String.valueOf(path)); + } else { + char[] cands = phone[str[index] - '2']; + for (char cur : cands) { + path[index] = cur; + process(str, index + 1, path, ans); + } + } + } + +} diff --git a/大厂刷题班/class28/Problem_0019_RemoveNthNodeFromEndofList.java b/大厂刷题班/class28/Problem_0019_RemoveNthNodeFromEndofList.java new file mode 100644 index 0000000..97cdc29 --- /dev/null +++ b/大厂刷题班/class28/Problem_0019_RemoveNthNodeFromEndofList.java @@ -0,0 +1,33 @@ +package class28; + +public class Problem_0019_RemoveNthNodeFromEndofList { + + public static class ListNode { + public int val; + public ListNode next; + } + + public static ListNode removeNthFromEnd(ListNode head, int n) { + ListNode cur = head; + ListNode pre = null; + while (cur != null) { + n--; + if (n == -1) { + pre = head; + } + if (n < -1) { + pre = pre.next; + } + cur = cur.next; + } + if (n > 0) { + return head; + } + if (pre == null) { + return head.next; + } + pre.next = pre.next.next; + return head; + } + +} diff --git a/大厂刷题班/class28/Problem_0020_ValidParentheses.java b/大厂刷题班/class28/Problem_0020_ValidParentheses.java new file mode 100644 index 0000000..1914ead --- /dev/null +++ b/大厂刷题班/class28/Problem_0020_ValidParentheses.java @@ -0,0 +1,30 @@ +package class28; + +public class Problem_0020_ValidParentheses { + + public static boolean isValid(String s) { + if (s == null || s.length() == 0) { + return true; + } + char[] str = s.toCharArray(); + int N = str.length; + char[] stack = new char[N]; + int size = 0; + for (int i = 0; i < N; i++) { + char cha = str[i]; + if (cha == '(' || cha == '[' || cha == '{') { + stack[size++] = cha == '(' ? ')' : (cha == '[' ? ']' : '}'); + } else { + if (size == 0) { + return false; + } + char last = stack[--size]; + if (cha != last) { + return false; + } + } + } + return size == 0; + } + +} diff --git a/大厂刷题班/class28/Problem_0022_GenerateParentheses.java b/大厂刷题班/class28/Problem_0022_GenerateParentheses.java new file mode 100644 index 0000000..7ca9625 --- /dev/null +++ b/大厂刷题班/class28/Problem_0022_GenerateParentheses.java @@ -0,0 +1,71 @@ +package class28; + +import java.util.ArrayList; +import java.util.List; + +public class Problem_0022_GenerateParentheses { + + public static List generateParenthesis(int n) { + char[] path = new char[n << 1]; + List ans = new ArrayList<>(); + process(path, 0, 0, n, ans); + return ans; + } + + + // path 做的决定 path[0....index-1]做完决定的! + // path[index.....] 还没做决定,当前轮到index位置做决定! + + public static void process(char[] path, int index, int leftMinusRight, int leftRest, List ans) { + if (index == path.length) { + ans.add(String.valueOf(path)); + } else { + // index ( ) + if (leftRest > 0) { + path[index] = '('; + process(path, index + 1, leftMinusRight + 1, leftRest - 1, ans); + } + if (leftMinusRight > 0) { + path[index] = ')'; + process(path, index + 1, leftMinusRight - 1, leftRest, ans); + } + } + } + + // 不剪枝的做法 + public static List generateParenthesis2(int n) { + char[] path = new char[n << 1]; + List ans = new ArrayList<>(); + process2(path, 0, ans); + return ans; + } + + public static void process2(char[] path, int index, List ans) { + if (index == path.length) { + if (isValid(path)) { + ans.add(String.valueOf(path)); + } + } else { + path[index] = '('; + process2(path, index + 1, ans); + path[index] = ')'; + process2(path, index + 1, ans); + } + } + + public static boolean isValid(char[] path) { + int count = 0; + for (char cha : path) { + if (cha == '(') { + count++; + } else { + count--; + } + if (count < 0) { + return false; + } + } + return count == 0; + } + +} diff --git a/大厂刷题班/class28/Problem_0026_RemoveDuplicatesFromSortedArray.java b/大厂刷题班/class28/Problem_0026_RemoveDuplicatesFromSortedArray.java new file mode 100644 index 0000000..fc2e419 --- /dev/null +++ b/大厂刷题班/class28/Problem_0026_RemoveDuplicatesFromSortedArray.java @@ -0,0 +1,21 @@ +package class28; + +public class Problem_0026_RemoveDuplicatesFromSortedArray { + + public static int removeDuplicates(int[] nums) { + if (nums == null) { + return 0; + } + if (nums.length < 2) { + return nums.length; + } + int done = 0; + for (int i = 1; i < nums.length; i++) { + if (nums[i] != nums[done]) { + nums[++done] = nums[i]; + } + } + return done + 1; + } + +} diff --git a/大厂刷题班/class28/Problem_0034_FindFirstAndLastPositionOfElementInSortedArray.java b/大厂刷题班/class28/Problem_0034_FindFirstAndLastPositionOfElementInSortedArray.java new file mode 100644 index 0000000..92be1c0 --- /dev/null +++ b/大厂刷题班/class28/Problem_0034_FindFirstAndLastPositionOfElementInSortedArray.java @@ -0,0 +1,33 @@ +package class28; + +public class Problem_0034_FindFirstAndLastPositionOfElementInSortedArray { + + public static int[] searchRange(int[] nums, int target) { + if (nums == null || nums.length == 0) { + return new int[] { -1, -1 }; + } + int L = lessMostRight(nums, target) + 1; + if (L == nums.length || nums[L] != target) { + return new int[] { -1, -1 }; + } + return new int[] { L, lessMostRight(nums, target + 1) }; + } + + public static int lessMostRight(int[] arr, int num) { + int L = 0; + int R = arr.length - 1; + int M = 0; + int ans = -1; + while (L <= R) { + M = L + ((R - L) >> 1); + if (arr[M] < num) { + ans = M; + L = M + 1; + } else { + R = M - 1; + } + } + return ans; + } + +} diff --git a/大厂刷题班/class28/Problem_0036_ValidSudoku.java b/大厂刷题班/class28/Problem_0036_ValidSudoku.java new file mode 100644 index 0000000..4a89cf5 --- /dev/null +++ b/大厂刷题班/class28/Problem_0036_ValidSudoku.java @@ -0,0 +1,26 @@ +package class28; + +public class Problem_0036_ValidSudoku { + + public static boolean isValidSudoku(char[][] board) { + boolean[][] row = new boolean[9][10]; + boolean[][] col = new boolean[9][10]; + boolean[][] bucket = new boolean[9][10]; + for (int i = 0; i < 9; i++) { + for (int j = 0; j < 9; j++) { + int bid = 3 * (i / 3) + (j / 3); + if (board[i][j] != '.') { + int num = board[i][j] - '0'; + if (row[i][num] || col[j][num] || bucket[bid][num]) { + return false; + } + row[i][num] = true; + col[j][num] = true; + bucket[bid][num] = true; + } + } + } + return true; + } + +} diff --git a/大厂刷题班/class28/Problem_0037_SudokuSolver.java b/大厂刷题班/class28/Problem_0037_SudokuSolver.java new file mode 100644 index 0000000..7501622 --- /dev/null +++ b/大厂刷题班/class28/Problem_0037_SudokuSolver.java @@ -0,0 +1,61 @@ +package class28; + +public class Problem_0037_SudokuSolver { + + public static void solveSudoku(char[][] board) { + boolean[][] row = new boolean[9][10]; + boolean[][] col = new boolean[9][10]; + boolean[][] bucket = new boolean[9][10]; + initMaps(board, row, col, bucket); + process(board, 0, 0, row, col, bucket); + } + + public static void initMaps(char[][] board, boolean[][] row, boolean[][] col, boolean[][] bucket) { + for (int i = 0; i < 9; i++) { + for (int j = 0; j < 9; j++) { + int bid = 3 * (i / 3) + (j / 3); + if (board[i][j] != '.') { + int num = board[i][j] - '0'; + row[i][num] = true; + col[j][num] = true; + bucket[bid][num] = true; + } + } + } + } + + // 当前来到(i,j)这个位置,如果已经有数字,跳到下一个位置上 + // 如果没有数字,尝试1~9,不能和row、col、bucket冲突 + public static boolean process(char[][] board, int i, int j, boolean[][] row, boolean[][] col, boolean[][] bucket) { + if (i == 9) { + return true; + } + // 当离开(i,j),应该去哪?(nexti, nextj) + int nexti = j != 8 ? i : i + 1; + int nextj = j != 8 ? j + 1 : 0; + if (board[i][j] != '.') { + return process(board, nexti, nextj, row, col, bucket); + } else { + // 可以尝试1~9 + int bid = 3 * (i / 3) + (j / 3); + for (int num = 1; num <= 9; num++) { // 尝试每一个数字1~9 + if ((!row[i][num]) && (!col[j][num]) && (!bucket[bid][num])) { + // 可以尝试num + row[i][num] = true; + col[j][num] = true; + bucket[bid][num] = true; + board[i][j] = (char) (num + '0'); + if (process(board, nexti, nextj, row, col, bucket)) { + return true; + } + row[i][num] = false; + col[j][num] = false; + bucket[bid][num] = false; + board[i][j] = '.'; + } + } + return false; + } + } + +} diff --git a/大厂刷题班/class28/Problem_0038_CountAndSay.java b/大厂刷题班/class28/Problem_0038_CountAndSay.java new file mode 100644 index 0000000..cd719f3 --- /dev/null +++ b/大厂刷题班/class28/Problem_0038_CountAndSay.java @@ -0,0 +1,33 @@ +package class28; + +public class Problem_0038_CountAndSay { + + public static String countAndSay(int n) { + if (n < 1) { + return ""; + } + if (n == 1) { + return "1"; + } + char[] last = countAndSay(n - 1).toCharArray(); + StringBuilder ans = new StringBuilder(); + int times = 1; + for (int i = 1; i < last.length; i++) { + if (last[i - 1] == last[i]) { + times++; + } else { + ans.append(String.valueOf(times)); + ans.append(String.valueOf(last[i - 1])); + times = 1; + } + } + ans.append(String.valueOf(times)); + ans.append(String.valueOf(last[last.length - 1])); + return ans.toString(); + } + + public static void main(String[] args) { + System.out.println(countAndSay(20)); + } + +} diff --git a/大厂刷题班/class28/Problem_0049_GroupAnagrams.java b/大厂刷题班/class28/Problem_0049_GroupAnagrams.java new file mode 100644 index 0000000..8b325aa --- /dev/null +++ b/大厂刷题班/class28/Problem_0049_GroupAnagrams.java @@ -0,0 +1,52 @@ +package class28; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; + +public class Problem_0049_GroupAnagrams { + + public static List> groupAnagrams1(String[] strs) { + HashMap> map = new HashMap>(); + for (String str : strs) { + int[] record = new int[26]; + for (char cha : str.toCharArray()) { + record[cha - 'a']++; + } + StringBuilder builder = new StringBuilder(); + for (int value : record) { + builder.append(String.valueOf(value)).append("_"); + } + String key = builder.toString(); + if (!map.containsKey(key)) { + map.put(key, new ArrayList()); + } + map.get(key).add(str); + } + List> res = new ArrayList>(); + for (List list : map.values()) { + res.add(list); + } + return res; + } + + public static List> groupAnagrams2(String[] strs) { + HashMap> map = new HashMap>(); + for (String str : strs) { + char[] chs = str.toCharArray(); + Arrays.sort(chs); + String key = String.valueOf(chs); + if (!map.containsKey(key)) { + map.put(key, new ArrayList()); + } + map.get(key).add(str); + } + List> res = new ArrayList>(); + for (List list : map.values()) { + res.add(list); + } + return res; + } + +} diff --git a/大厂刷题班/class28/说明 b/大厂刷题班/class28/说明 new file mode 100644 index 0000000..ec23675 --- /dev/null +++ b/大厂刷题班/class28/说明 @@ -0,0 +1,33 @@ +leetcode高频题 +leetcode全题目列表 : https://leetcode.com/problemset/all/ +在全题目列表的右侧栏,点击Top Interview Questions +或者直接进入右侧链接 : https://leetcode.com/problemset/all/?listId=wpwgkgt +即可看到leetcode高频题全列表 +本节课解决leetcode高频题列表中的如下题目 : +0008 : 大厂刷题班, 第28节, 本节 +0010 : 大厂刷题班, 第12节第4题 +0011 : 大厂刷题班, 第8节第2题 +0012 : 大厂刷题班, 第28节, 本节 +0013 : 大厂刷题班, 第28节, 本节 +0014 : 大厂刷题班, 第28节, 本节 +0015 : 大厂刷题班, 第25节第2题 +0017 : 大厂刷题班, 第28节, 本节 +0019 : 大厂刷题班, 第28节, 本节 +0020 : 大厂刷题班, 第28节, 本节 +0021 : 算法新手班, 第4节第6题 +0022 : 大厂刷题班, 第28节, 本节 +0023 : 算法新手班, 第6节第1题 +0026 : 大厂刷题班, 第28节, 本节 +0028 : 体系学习班, KMP算法 +0029 : 算法新手班, 第5节第3题 +0034 : 大厂刷题班, 第28节, 本节 +0036 : 大厂刷题班, 第28节, 本节 +0037 : 大厂刷题班, 第28节, 本节 +0038 : 大厂刷题班, 第28节, 本节 +0041 : 大厂刷题班, 第14节第6题 +0042 : 大厂刷题班, 第22节第2题 +0044 : 大厂刷题班, 第12节第4题 +0045 : 大厂刷题班, 第10节第1题 +0046 : 体系学习班, 第17节第4题 +0048 : 体系学习班, 第40节第6题 +0049 : 大厂刷题班, 第28节, 本节 \ No newline at end of file diff --git a/大厂刷题班/class29/Problem_0033_SearchInRotatedSortedArray.java b/大厂刷题班/class29/Problem_0033_SearchInRotatedSortedArray.java new file mode 100644 index 0000000..ba8841a --- /dev/null +++ b/大厂刷题班/class29/Problem_0033_SearchInRotatedSortedArray.java @@ -0,0 +1,67 @@ +package class29; + +public class Problem_0033_SearchInRotatedSortedArray { + + // arr,原本是有序数组,旋转过,而且左部分长度不知道 + // 找num + // num所在的位置返回 + public static int search(int[] arr, int num) { + int L = 0; + int R = arr.length - 1; + int M = 0; + while (L <= R) { + // M = L + ((R - L) >> 1) + M = (L + R) / 2; + if (arr[M] == num) { + return M; + } + // arr[M] != num + // [L] == [M] == [R] != num 无法二分 + if (arr[L] == arr[M] && arr[M] == arr[R]) { + while (L != M && arr[L] == arr[M]) { + L++; + } + // 1) L == M L...M 一路都相等 + // 2) 从L到M终于找到了一个不等的位置 + if (L == M) { // L...M 一路都相等 + L = M + 1; + continue; + } + } + // ... + // arr[M] != num + // [L] [M] [R] 不都一样的情况, 如何二分的逻辑 + if (arr[L] != arr[M]) { + if (arr[M] > arr[L]) { // L...M 一定有序 + if (num >= arr[L] && num < arr[M]) { // 3 [L] == 1 [M] = 5 L...M - 1 + R = M - 1; + } else { // 9 [L] == 2 [M] = 7 M... R + L = M + 1; + } + } else { // [L] > [M] L....M 存在断点 + if (num > arr[M] && num <= arr[R]) { + L = M + 1; + } else { + R = M - 1; + } + } + } else { /// [L] [M] [R] 不都一样, [L] === [M] -> [M]!=[R] + if (arr[M] < arr[R]) { + if (num > arr[M] && num <= arr[R]) { + L = M + 1; + } else { + R = M - 1; + } + } else { + if (num >= arr[L] && num < arr[M]) { + R = M - 1; + } else { + L = M + 1; + } + } + } + } + return -1; + } + +} diff --git a/大厂刷题班/class29/Problem_0050_PowXN.java b/大厂刷题班/class29/Problem_0050_PowXN.java new file mode 100644 index 0000000..77d5288 --- /dev/null +++ b/大厂刷题班/class29/Problem_0050_PowXN.java @@ -0,0 +1,39 @@ +package class29; + +public class Problem_0050_PowXN { + + public static int pow(int a, int n) { + int ans = 1; + int t = a; + while (n != 0) { + if ((n & 1) != 0) { + ans *= t; + } + t *= t; + n >>= 1; + } + return ans; + } + + // x的n次方,n可能是负数 + public static double myPow(double x, int n) { + if (n == 0) { + return 1D; + } + int pow = Math.abs(n == Integer.MIN_VALUE ? n + 1 : n); + double t = x; + double ans = 1D; + while (pow != 0) { + if ((pow & 1) != 0) { + ans *= t; + } + pow >>= 1; + t = t * t; + } + if (n == Integer.MIN_VALUE) { + ans *= x; + } + return n < 0 ? (1D / ans) : ans; + } + +} diff --git a/大厂刷题班/class29/Problem_0056_MergeIntervals.java b/大厂刷题班/class29/Problem_0056_MergeIntervals.java new file mode 100644 index 0000000..d912b49 --- /dev/null +++ b/大厂刷题班/class29/Problem_0056_MergeIntervals.java @@ -0,0 +1,30 @@ +package class29; + +import java.util.Arrays; + +public class Problem_0056_MergeIntervals { + + public static int[][] merge(int[][] intervals) { + if (intervals.length == 0) { + return new int[0][0]; + } + Arrays.sort(intervals, (a, b) -> a[0] - b[0]); + int s = intervals[0][0]; + int e = intervals[0][1]; + int size = 0; + for (int i = 1; i < intervals.length; i++) { + if (intervals[i][0] > e) { + intervals[size][0] = s; + intervals[size++][1] = e; + s = intervals[i][0]; + e = intervals[i][1]; + } else { + e = Math.max(e, intervals[i][1]); + } + } + intervals[size][0] = s; + intervals[size++][1] = e; + return Arrays.copyOf(intervals, size); + } + +} diff --git a/大厂刷题班/class29/Problem_0062_UniquePaths.java b/大厂刷题班/class29/Problem_0062_UniquePaths.java new file mode 100644 index 0000000..c5ffd25 --- /dev/null +++ b/大厂刷题班/class29/Problem_0062_UniquePaths.java @@ -0,0 +1,30 @@ +package class29; + +public class Problem_0062_UniquePaths { + + // m 行 + // n 列 + // 下:m-1 + // 右:n-1 + public static int uniquePaths(int m, int n) { + int right = n - 1; + int all = m + n - 2; + long o1 = 1; + long o2 = 1; + // o1乘进去的个数 一定等于 o2乘进去的个数 + for (int i = right + 1, j = 1; i <= all; i++, j++) { + o1 *= i; + o2 *= j; + long gcd = gcd(o1, o2); + o1 /= gcd; + o2 /= gcd; + } + return (int) o1; + } + + // 调用的时候,请保证初次调用时,m和n都不为0 + public static long gcd(long m, long n) { + return n == 0 ? m : gcd(n, m % n); + } + +} diff --git a/大厂刷题班/class29/Problem_0066_PlusOne.java b/大厂刷题班/class29/Problem_0066_PlusOne.java new file mode 100644 index 0000000..3717e40 --- /dev/null +++ b/大厂刷题班/class29/Problem_0066_PlusOne.java @@ -0,0 +1,19 @@ +package class29; + +public class Problem_0066_PlusOne { + + public static int[] plusOne(int[] digits) { + int n = digits.length; + for (int i = n - 1; i >= 0; i--) { + if (digits[i] < 9) { + digits[i]++; + return digits; + } + digits[i] = 0; + } + int[] ans = new int[n + 1]; + ans[0] = 1; + return ans; + } + +} diff --git a/大厂刷题班/class29/Problem_0069_SqrtX.java b/大厂刷题班/class29/Problem_0069_SqrtX.java new file mode 100644 index 0000000..f58a642 --- /dev/null +++ b/大厂刷题班/class29/Problem_0069_SqrtX.java @@ -0,0 +1,30 @@ +package class29; + +public class Problem_0069_SqrtX { + + // x一定非负,输入可以保证 + public static int mySqrt(int x) { + if (x == 0) { + return 0; + } + if (x < 3) { + return 1; + } + // x >= 3 + long ans = 1; + long L = 1; + long R = x; + long M = 0; + while (L <= R) { + M = (L + R) / 2; + if (M * M <= x) { + ans = M; + L = M + 1; + } else { + R = M - 1; + } + } + return (int) ans; + } + +} diff --git a/大厂刷题班/class29/Problem_0073_SetMatrixZeroes.java b/大厂刷题班/class29/Problem_0073_SetMatrixZeroes.java new file mode 100644 index 0000000..be15049 --- /dev/null +++ b/大厂刷题班/class29/Problem_0073_SetMatrixZeroes.java @@ -0,0 +1,79 @@ +package class29; + +public class Problem_0073_SetMatrixZeroes { + + public static void setZeroes1(int[][] matrix) { + boolean row0Zero = false; + boolean col0Zero = false; + int i = 0; + int j = 0; + for (i = 0; i < matrix[0].length; i++) { + if (matrix[0][i] == 0) { + row0Zero = true; + break; + } + } + for (i = 0; i < matrix.length; i++) { + if (matrix[i][0] == 0) { + col0Zero = true; + break; + } + } + for (i = 1; i < matrix.length; i++) { + for (j = 1; j < matrix[0].length; j++) { + if (matrix[i][j] == 0) { + matrix[i][0] = 0; + matrix[0][j] = 0; + } + } + } + for (i = 1; i < matrix.length; i++) { + for (j = 1; j < matrix[0].length; j++) { + if (matrix[i][0] == 0 || matrix[0][j] == 0) { + matrix[i][j] = 0; + } + } + } + if (row0Zero) { + for (i = 0; i < matrix[0].length; i++) { + matrix[0][i] = 0; + } + } + if (col0Zero) { + for (i = 0; i < matrix.length; i++) { + matrix[i][0] = 0; + } + } + } + + public static void setZeroes2(int[][] matrix) { + boolean col0 = false; + int i = 0; + int j = 0; + for (i = 0; i < matrix.length; i++) { + for (j = 0; j < matrix[0].length; j++) { + if (matrix[i][j] == 0) { + matrix[i][0] = 0; + if (j == 0) { + col0 = true; + } else { + matrix[0][j] = 0; + } + } + } + } + for (i = matrix.length - 1; i >= 0; i--) { + for (j = 1; j < matrix[0].length; j++) { + if (matrix[i][0] == 0 || matrix[0][j] == 0) { + matrix[i][j] = 0; + } + } + } + if (col0) { + for (i = 0; i < matrix.length; i++) { + matrix[i][0] = 0; + } + } + } + +} diff --git a/大厂刷题班/class29/说明 b/大厂刷题班/class29/说明 new file mode 100644 index 0000000..75954f5 --- /dev/null +++ b/大厂刷题班/class29/说明 @@ -0,0 +1,20 @@ +leetcode高频题 +leetcode全题目列表 : https://leetcode.com/problemset/all/ +在全题目列表的右侧栏,点击Top Interview Questions +或者直接进入右侧链接 : https://leetcode.com/problemset/all/?listId=wpwgkgt +即可看到leetcode高频题全列表 +本节课解决leetcode高频题列表中的如下题目 : +0033 : 大厂刷题班, 第29节, 本节 +0050 : 大厂刷题班, 第29节, 本节 +0053 : 体系学习班, 第40节第2题 +0054 : 体系学习班, 第40节第5题 +0055 : 大厂刷题班, 第10节第1题 +0056 : 大厂刷题班, 第29节, 本节 +0062 : 大厂刷题班, 第29节, 本节 +0066 : 大厂刷题班, 第29节, 本节 +0069 : 大厂刷题班, 第29节, 本节 +0070 : 体系学习班, 第26节第2题 +0073 : 大厂刷题班, 第29节, 本节 +0075 : 体系学习班, 第5节第2题, 快排中的荷兰国旗问题 +0076 : 大厂刷题班, 第24节第5题 +0078 : 体系学习班, 第17节题目3, 生成子序列问题和本题一样的 \ No newline at end of file diff --git a/大厂刷题班/class30/Problem_0079_WordSearch.java b/大厂刷题班/class30/Problem_0079_WordSearch.java new file mode 100644 index 0000000..8d3d0da --- /dev/null +++ b/大厂刷题班/class30/Problem_0079_WordSearch.java @@ -0,0 +1,41 @@ +package class30; + +public class Problem_0079_WordSearch { + + public static boolean exist(char[][] board, String word) { + char[] w = word.toCharArray(); + for (int i = 0; i < board.length; i++) { + for (int j = 0; j < board[0].length; j++) { + if (f(board, i, j, w, 0)) { + return true; + } + } + } + return false; + } + + // 目前到达了b[i][j],word[k....] + // 从b[i][j]出发,能不能搞定word[k....] true false + public static boolean f(char[][] b, int i, int j, char[] w, int k) { + if (k == w.length) { + return true; + } + // word[k.....] 有字符 + // 如果(i,j)越界,返回false + if (i < 0 || i == b.length || j < 0 || j == b[0].length) { + return false; + } + if (b[i][j] != w[k]) { + return false; + } + char tmp = b[i][j]; + b[i][j] = 0; + boolean ans = f(b, i - 1, j, w, k + 1) + || f(b, i + 1, j, w, k + 1) + || f(b, i, j - 1, w, k + 1) + || f(b, i, j + 1, w, k + 1); + b[i][j] = tmp; + return ans; + } + +} diff --git a/大厂刷题班/class30/Problem_0088_MergeSortedArray.java b/大厂刷题班/class30/Problem_0088_MergeSortedArray.java new file mode 100644 index 0000000..4b24fc8 --- /dev/null +++ b/大厂刷题班/class30/Problem_0088_MergeSortedArray.java @@ -0,0 +1,22 @@ +package class30; + +public class Problem_0088_MergeSortedArray { + + public static void merge(int[] nums1, int m, int[] nums2, int n) { + int index = nums1.length; + while (m > 0 && n > 0) { + if (nums1[m - 1] >= nums2[n - 1]) { + nums1[--index] = nums1[--m]; + } else { + nums1[--index] = nums2[--n]; + } + } + while (m > 0) { + nums1[--index] = nums1[--m]; + } + while (n > 0) { + nums1[--index] = nums2[--n]; + } + } + +} diff --git a/大厂刷题班/class30/Problem_0091_DecodeWays.java b/大厂刷题班/class30/Problem_0091_DecodeWays.java new file mode 100644 index 0000000..0db1ac8 --- /dev/null +++ b/大厂刷题班/class30/Problem_0091_DecodeWays.java @@ -0,0 +1,87 @@ +package class30; + +public class Problem_0091_DecodeWays { + + public static int numDecodings1(String s) { + if (s == null || s.length() == 0) { + return 0; + } + char[] str = s.toCharArray(); + return process(str, 0); + } + + // 潜台词:str[0...index-1]已经转化完了,不用操心了 + // str[index....] 能转出多少有效的,返回方法数 + public static int process(char[] str, int index) { + if (index == str.length) { + return 1; + } + if (str[index] == '0') { + return 0; + } + int ways = process(str, index + 1); + if (index + 1 == str.length) { + return ways; + } + int num = (str[index] - '0') * 10 + str[index + 1] - '0'; + if (num < 27) { + ways += process(str, index + 2); + } + return ways; + } + + public static int numDecodings2(String s) { + if (s == null || s.length() == 0) { + return 0; + } + char[] str = s.toCharArray(); + int N = str.length; + // dp[i] -> process(str, index)返回值 index 0 ~ N + int[] dp = new int[N + 1]; + dp[N] = 1; + + // dp依次填好 dp[i] dp[i+1] dp[i+2] + for (int i = N - 1; i >= 0; i--) { + if (str[i] != '0') { + dp[i] = dp[i + 1]; + if (i + 1 == str.length) { + continue; + } + int num = (str[i] - '0') * 10 + str[i + 1] - '0'; + if (num <= 26) { + dp[i] += dp[i + 2]; + } + } + } + return dp[0]; + } + + public static int numDecodings(String s) { + if (s == null || s.length() == 0) { + return 0; + } + char[] str = s.toCharArray(); + int N = str.length; + int[] dp = new int[N + 1]; + dp[N] = 1; + for (int i = N - 1; i >= 0; i--) { + if (str[i] == '0') { + dp[i] = 0; + } else if (str[i] == '1') { + dp[i] = dp[i + 1]; + if (i + 1 < N) { + dp[i] += dp[i + 2]; + } + } else if (str[i] == '2') { + dp[i] = dp[i + 1]; + if (i + 1 < str.length && (str[i + 1] >= '0' && str[i + 1] <= '6')) { + dp[i] += dp[i + 2]; + } + } else { + dp[i] = dp[i + 1]; + } + } + return dp[0]; + } + +} diff --git a/大厂刷题班/class30/Problem_0098_ValidateBinarySearchTree.java b/大厂刷题班/class30/Problem_0098_ValidateBinarySearchTree.java new file mode 100644 index 0000000..d3471d8 --- /dev/null +++ b/大厂刷题班/class30/Problem_0098_ValidateBinarySearchTree.java @@ -0,0 +1,42 @@ +package class30; + +public class Problem_0098_ValidateBinarySearchTree { + + public static class TreeNode { + int val; + TreeNode left; + TreeNode right; + } + + public boolean isValidBST(TreeNode root) { + if (root == null) { + return true; + } + TreeNode cur = root; + TreeNode mostRight = null; + Integer pre = null; + boolean ans = true; + while (cur != null) { + mostRight = cur.left; + if (mostRight != null) { + while (mostRight.right != null && mostRight.right != cur) { + mostRight = mostRight.right; + } + if (mostRight.right == null) { + mostRight.right = cur; + cur = cur.left; + continue; + } else { + mostRight.right = null; + } + } + if (pre != null && pre >= cur.val) { + ans = false; + } + pre = cur.val; + cur = cur.right; + } + return ans; + } + +} diff --git a/大厂刷题班/class30/Problem_0101_SymmetricTree.java b/大厂刷题班/class30/Problem_0101_SymmetricTree.java new file mode 100644 index 0000000..f5cdfe7 --- /dev/null +++ b/大厂刷题班/class30/Problem_0101_SymmetricTree.java @@ -0,0 +1,30 @@ +package class30; + +public class Problem_0101_SymmetricTree { + + public static class TreeNode { + int val; + TreeNode left; + TreeNode right; + } + + public boolean isSymmetric(TreeNode root) { + return isMirror(root, root); + } + + // 一棵树是原始树 head1 + // 另一棵是翻面树 head2 + public static boolean isMirror(TreeNode head1, TreeNode head2) { + if (head1 == null && head2 == null) { + return true; + } + if (head1 != null && head2 != null) { + return head1.val == head2.val + && isMirror(head1.left, head2.right) + && isMirror(head1.right, head2.left); + } + // 一个为空,一个不为空 false + return false; + } + +} diff --git a/大厂刷题班/class30/Problem_0103_BinaryTreeZigzagLevelOrderTraversal.java b/大厂刷题班/class30/Problem_0103_BinaryTreeZigzagLevelOrderTraversal.java new file mode 100644 index 0000000..546fab2 --- /dev/null +++ b/大厂刷题班/class30/Problem_0103_BinaryTreeZigzagLevelOrderTraversal.java @@ -0,0 +1,52 @@ +package class30; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; + +public class Problem_0103_BinaryTreeZigzagLevelOrderTraversal { + + public static class TreeNode { + int val; + TreeNode left; + TreeNode right; + } + + public static List> zigzagLevelOrder(TreeNode root) { + List> ans = new ArrayList<>(); + if (root == null) { + return ans; + } + LinkedList deque = new LinkedList<>(); + deque.add(root); + int size = 0; + boolean isHead = true; + while (!deque.isEmpty()) { + size = deque.size(); + List curLevel = new ArrayList<>(); + for (int i = 0; i < size; i++) { + TreeNode cur = isHead ? deque.pollFirst() : deque.pollLast(); + curLevel.add(cur.val); + if(isHead) { + if (cur.left != null) { + deque.addLast(cur.left); + } + if (cur.right != null) { + deque.addLast(cur.right); + } + }else { + if (cur.right != null) { + deque.addFirst(cur.right); + } + if (cur.left != null) { + deque.addFirst(cur.left); + } + } + } + ans.add(curLevel); + isHead = !isHead; + } + return ans; + } + +} diff --git a/大厂刷题班/class30/Problem_0108_ConvertSortedArrayToBinarySearchTree.java b/大厂刷题班/class30/Problem_0108_ConvertSortedArrayToBinarySearchTree.java new file mode 100644 index 0000000..0914140 --- /dev/null +++ b/大厂刷题班/class30/Problem_0108_ConvertSortedArrayToBinarySearchTree.java @@ -0,0 +1,33 @@ +package class30; + +public class Problem_0108_ConvertSortedArrayToBinarySearchTree { + + public static class TreeNode { + int val; + TreeNode left; + TreeNode right; + + TreeNode(int val) { + this.val = val; + } + } + + public TreeNode sortedArrayToBST(int[] nums) { + return process(nums, 0, nums.length - 1); + } + + public static TreeNode process(int[] nums, int L, int R) { + if (L > R) { + return null; + } + if (L == R) { + return new TreeNode(nums[L]); + } + int M = (L + R) / 2; + TreeNode head = new TreeNode(nums[M]); + head.left = process(nums, L, M - 1); + head.right = process(nums, M + 1, R); + return head; + } + +} diff --git a/大厂刷题班/class30/Problem_0116_PopulatingNextRightPointersInEachNode.java b/大厂刷题班/class30/Problem_0116_PopulatingNextRightPointersInEachNode.java new file mode 100644 index 0000000..f3d82de --- /dev/null +++ b/大厂刷题班/class30/Problem_0116_PopulatingNextRightPointersInEachNode.java @@ -0,0 +1,77 @@ +package class30; + +public class Problem_0116_PopulatingNextRightPointersInEachNode { + + // 不要提交这个类 + public static class Node { + public int val; + public Node left; + public Node right; + public Node next; + } + + // 提交下面的代码 + public static Node connect(Node root) { + if (root == null) { + return root; + } + MyQueue queue = new MyQueue(); + queue.offer(root); + while (!queue.isEmpty()) { + // 第一个弹出的节点 + Node pre = null; + int size = queue.size; + for (int i = 0; i < size; i++) { + Node cur = queue.poll(); + if (cur.left != null) { + queue.offer(cur.left); + } + if (cur.right != null) { + queue.offer(cur.right); + } + if (pre != null) { + pre.next = cur; + } + pre = cur; + } + } + return root; + } + + public static class MyQueue { + public Node head; + public Node tail; + public int size; + + public MyQueue() { + head = null; + tail = null; + size = 0; + } + + public boolean isEmpty() { + return size == 0; + } + + public void offer(Node cur) { + size++; + if (head == null) { + head = cur; + tail = cur; + } else { + tail.next = cur; + tail = cur; + } + } + + public Node poll() { + size--; + Node ans = head; + head = head.next; + ans.next = null; + return ans; + } + + } + +} diff --git a/大厂刷题班/class30/Problem_0118_PascalTriangle.java b/大厂刷题班/class30/Problem_0118_PascalTriangle.java new file mode 100644 index 0000000..867990f --- /dev/null +++ b/大厂刷题班/class30/Problem_0118_PascalTriangle.java @@ -0,0 +1,23 @@ +package class30; + +import java.util.ArrayList; +import java.util.List; + +public class Problem_0118_PascalTriangle { + + public static List> generate(int numRows) { + List> ans = new ArrayList<>(); + for (int i = 0; i < numRows; i++) { + ans.add(new ArrayList<>()); + ans.get(i).add(1); + } + for (int i = 1; i < numRows; i++) { + for (int j = 1; j < i; j++) { + ans.get(i).add(ans.get(i - 1).get(j - 1) + ans.get(i - 1).get(j)); + } + ans.get(i).add(1); + } + return ans; + } + +} diff --git a/大厂刷题班/class30/Problem_0119_PascalTriangleII.java b/大厂刷题班/class30/Problem_0119_PascalTriangleII.java new file mode 100644 index 0000000..79bfc78 --- /dev/null +++ b/大厂刷题班/class30/Problem_0119_PascalTriangleII.java @@ -0,0 +1,19 @@ +package class30; + +import java.util.ArrayList; +import java.util.List; + +public class Problem_0119_PascalTriangleII { + + public List getRow(int rowIndex) { + List ans = new ArrayList<>(); + for (int i = 0; i <= rowIndex; i++) { + for (int j = i - 1; j > 0; j--) { + ans.set(j, ans.get(j - 1) + ans.get(j)); + } + ans.add(1); + } + return ans; + } + +} diff --git a/大厂刷题班/class30/Problem_0124_BinaryTreeMaximumPathSum.java b/大厂刷题班/class30/Problem_0124_BinaryTreeMaximumPathSum.java new file mode 100644 index 0000000..de6b07c --- /dev/null +++ b/大厂刷题班/class30/Problem_0124_BinaryTreeMaximumPathSum.java @@ -0,0 +1,206 @@ +package class30; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; + +// follow up : 如果要求返回整个路径怎么做? +public class Problem_0124_BinaryTreeMaximumPathSum { + + public static class TreeNode { + int val; + TreeNode left; + TreeNode right; + + public TreeNode(int v) { + val = v; + } + + } + + public static int maxPathSum(TreeNode root) { + if (root == null) { + return 0; + } + return process(root).maxPathSum; + } + + // 任何一棵树,必须汇报上来的信息 + public static class Info { + public int maxPathSum; + public int maxPathSumFromHead; + + public Info(int path, int head) { + maxPathSum = path; + maxPathSumFromHead = head; + } + } + + public static Info process(TreeNode x) { + if (x == null) { + return null; + } + Info leftInfo = process(x.left); + Info rightInfo = process(x.right); + // x 1)只有x 2)x往左扎 3)x往右扎 + int maxPathSumFromHead = x.val; + if (leftInfo != null) { + maxPathSumFromHead = Math.max(maxPathSumFromHead, x.val + leftInfo.maxPathSumFromHead); + } + if (rightInfo != null) { + maxPathSumFromHead = Math.max(maxPathSumFromHead, x.val + rightInfo.maxPathSumFromHead); + } + // x整棵树最大路径和 1) 只有x 2)左树整体的最大路径和 3) 右树整体的最大路径和 + int maxPathSum = x.val; + if (leftInfo != null) { + maxPathSum = Math.max(maxPathSum, leftInfo.maxPathSum); + } + if (rightInfo != null) { + maxPathSum = Math.max(maxPathSum, rightInfo.maxPathSum); + } + // 4) x只往左扎 5)x只往右扎 + maxPathSum = Math.max(maxPathSumFromHead, maxPathSum); + // 6)一起扎 + if (leftInfo != null && rightInfo != null && leftInfo.maxPathSumFromHead > 0 + && rightInfo.maxPathSumFromHead > 0) { + maxPathSum = Math.max(maxPathSum, leftInfo.maxPathSumFromHead + rightInfo.maxPathSumFromHead + x.val); + } + return new Info(maxPathSum, maxPathSumFromHead); + } + + // 如果要返回路径的做法 + public static List getMaxSumPath(TreeNode head) { + List ans = new ArrayList<>(); + if (head != null) { + Data data = f(head); + HashMap fmap = new HashMap<>(); + fmap.put(head, head); + fatherMap(head, fmap); + fillPath(fmap, data.from, data.to, ans); + } + return ans; + } + + public static class Data { + public int maxAllSum; + public TreeNode from; + public TreeNode to; + public int maxHeadSum; + public TreeNode end; + + public Data(int a, TreeNode b, TreeNode c, int d, TreeNode e) { + maxAllSum = a; + from = b; + to = c; + maxHeadSum = d; + end = e; + } + } + + public static Data f(TreeNode x) { + if (x == null) { + return null; + } + Data l = f(x.left); + Data r = f(x.right); + int maxHeadSum = x.val; + TreeNode end = x; + if (l != null && l.maxHeadSum > 0 && (r == null || l.maxHeadSum > r.maxHeadSum)) { + maxHeadSum += l.maxHeadSum; + end = l.end; + } + if (r != null && r.maxHeadSum > 0 && (l == null || r.maxHeadSum > l.maxHeadSum)) { + maxHeadSum += r.maxHeadSum; + end = r.end; + } + int maxAllSum = Integer.MIN_VALUE; + TreeNode from = null; + TreeNode to = null; + if (l != null) { + maxAllSum = l.maxAllSum; + from = l.from; + to = l.to; + } + if (r != null && r.maxAllSum > maxAllSum) { + maxAllSum = r.maxAllSum; + from = r.from; + to = r.to; + } + int p3 = x.val + (l != null && l.maxHeadSum > 0 ? l.maxHeadSum : 0) + + (r != null && r.maxHeadSum > 0 ? r.maxHeadSum : 0); + if (p3 > maxAllSum) { + maxAllSum = p3; + from = (l != null && l.maxHeadSum > 0) ? l.end : x; + to = (r != null && r.maxHeadSum > 0) ? r.end : x; + } + return new Data(maxAllSum, from, to, maxHeadSum, end); + } + + public static void fatherMap(TreeNode h, HashMap map) { + if (h.left == null && h.right == null) { + return; + } + if (h.left != null) { + map.put(h.left, h); + fatherMap(h.left, map); + } + if (h.right != null) { + map.put(h.right, h); + fatherMap(h.right, map); + } + } + + public static void fillPath(HashMap fmap, TreeNode a, TreeNode b, List ans) { + if (a == b) { + ans.add(a); + } else { + HashSet ap = new HashSet<>(); + TreeNode cur = a; + while (cur != fmap.get(cur)) { + ap.add(cur); + cur = fmap.get(cur); + } + ap.add(cur); + cur = b; + TreeNode lca = null; + while (lca == null) { + if (ap.contains(cur)) { + lca = cur; + } else { + cur = fmap.get(cur); + } + } + while (a != lca) { + ans.add(a); + a = fmap.get(a); + } + ans.add(lca); + ArrayList right = new ArrayList<>(); + while (b != lca) { + right.add(b); + b = fmap.get(b); + } + for (int i = right.size() - 1; i >= 0; i--) { + ans.add(right.get(i)); + } + } + } + + public static void main(String[] args) { + TreeNode head = new TreeNode(4); + head.left = new TreeNode(-7); + head.right = new TreeNode(-5); + head.left.left = new TreeNode(9); + head.left.right = new TreeNode(9); + head.right.left = new TreeNode(4); + head.right.right = new TreeNode(3); + + List maxPath = getMaxSumPath(head); + + for (TreeNode n : maxPath) { + System.out.println(n.val); + } + } + +} \ No newline at end of file diff --git a/大厂刷题班/class30/Problem_0639_DecodeWaysII.java b/大厂刷题班/class30/Problem_0639_DecodeWaysII.java new file mode 100644 index 0000000..a04422b --- /dev/null +++ b/大厂刷题班/class30/Problem_0639_DecodeWaysII.java @@ -0,0 +1,161 @@ +package class30; + +public class Problem_0639_DecodeWaysII { + + public static int numDecodings0(String str) { + return f(str.toCharArray(), 0); + } + + public static int f(char[] str, int i) { + if (i == str.length) { + return 1; + } + if (str[i] == '0') { + return 0; + } + // str[index]有字符且不是'0' + if (str[i] != '*') { + // str[index] = 1~9 + // i -> 单转 + int p1 = f(str, i + 1); + if (i + 1 == str.length) { + return p1; + } + if (str[i + 1] != '*') { + int num = (str[i] - '0') * 10 + str[i + 1] - '0'; + int p2 = 0; + if (num < 27) { + p2 = f(str, i + 2); + } + return p1 + p2; + } else { // str[i+1] == '*' + // i i+1 -> 一起转 1* 2* 3* 9* + int p2 = 0; + if (str[i] < '3') { + p2 = f(str, i + 2) * (str[i] == '1' ? 9 : 6); + } + return p1 + p2; + } + } else { // str[i] == '*' 1~9 + // i 单转 9种 + int p1 = 9 * f(str, i + 1); + if (i + 1 == str.length) { + return p1; + } + if (str[i + 1] != '*') { + // * 0 10 20 + // * 1 11 21 + // * 2 12 22 + // * 3 13 23 + // * 6 16 26 + // * 7 17 + // * 8 18 + // * 9 19 + int p2 = (str[i + 1] < '7' ? 2 : 1) * f(str, i + 2); + return p1 + p2; + } else { // str[i+1] == * + // ** + // 11~19 9 + // 21 ~26 6 + // 15 + int p2 = 15 * f(str, i + 2); + return p1 + p2; + } + } + } + + public static long mod = 1000000007; + + public static int numDecodings1(String str) { + long[] dp = new long[str.length()]; + return (int) ways1(str.toCharArray(), 0, dp); + } + + public static long ways1(char[] s, int i, long[] dp) { + if (i == s.length) { + return 1; + } + if (s[i] == '0') { + return 0; + } + if (dp[i] != 0) { + return dp[i]; + } + long ans = ways1(s, i + 1, dp) * (s[i] == '*' ? 9 : 1); + if (s[i] == '1' || s[i] == '2' || s[i] == '*') { + if (i + 1 < s.length) { + if (s[i + 1] == '*') { + ans += ways1(s, i + 2, dp) * (s[i] == '*' ? 15 : (s[i] == '1' ? 9 : 6)); + } else { + if (s[i] == '*') { + ans += ways1(s, i + 2, dp) * (s[i + 1] < '7' ? 2 : 1); + } else { + ans += ((s[i] - '0') * 10 + s[i + 1] - '0') < 27 ? ways1(s, i + 2, dp) : 0; + } + } + } + } + ans %= mod; + dp[i] = ans; + return ans; + } + + public static int numDecodings2(String str) { + char[] s = str.toCharArray(); + int n = s.length; + long[] dp = new long[n + 1]; + dp[n] = 1; + for (int i = n - 1; i >= 0; i--) { + if (s[i] != '0') { + dp[i] = dp[i + 1] * (s[i] == '*' ? 9 : 1); + if (s[i] == '1' || s[i] == '2' || s[i] == '*') { + if (i + 1 < n) { + if (s[i + 1] == '*') { + dp[i] += dp[i + 2] * (s[i] == '*' ? 15 : (s[i] == '1' ? 9 : 6)); + } else { + if (s[i] == '*') { + dp[i] += dp[i + 2] * (s[i + 1] < '7' ? 2 : 1); + } else { + dp[i] += ((s[i] - '0') * 10 + s[i + 1] - '0') < 27 ? dp[i + 2] : 0; + } + } + } + } + dp[i] %= mod; + } + } + return (int) dp[0]; + } + + public static int numDecodings3(String str) { + char[] s = str.toCharArray(); + int n = s.length; + long a = 1; + long b = 1; + long c = 0; + for (int i = n - 1; i >= 0; i--) { + if (s[i] != '0') { + c = b * (s[i] == '*' ? 9 : 1); + if (s[i] == '1' || s[i] == '2' || s[i] == '*') { + if (i + 1 < n) { + if (s[i + 1] == '*') { + c += a * (s[i] == '*' ? 15 : (s[i] == '1' ? 9 : 6)); + } else { + if (s[i] == '*') { + c += a * (s[i + 1] < '7' ? 2 : 1); + } else { + c += a * (((s[i] - '0') * 10 + s[i + 1] - '0') < 27 ? 1 : 0); + } + } + } + } + } + c %= mod; + a = b; + b = c; + c = 0; + } + return (int) b; + } + +} diff --git a/大厂刷题班/class30/说明 b/大厂刷题班/class30/说明 new file mode 100644 index 0000000..81b1132 --- /dev/null +++ b/大厂刷题班/class30/说明 @@ -0,0 +1,26 @@ +leetcode高频题 +leetcode全题目列表 : https://leetcode.com/problemset/all/ +在全题目列表的右侧栏,点击Top Interview Questions +或者直接进入右侧链接 : https://leetcode.com/problemset/all/?listId=wpwgkgt +即可看到leetcode高频题全列表 +本节课解决leetcode高频题列表中的如下题目 : +0079 : 大厂刷题班, 第30节, 本节 +0084 : 体系学习班, 第25节第3题 +0088 : 大厂刷题班, 第30节, 本节 +0091 : 体系学习班, 第19节第2题 +0639 : 本题不再高频题列表中, 但本题是0091的难度加强题, 相似度很强, 大厂刷题班, 第30节, 本节 +0094 : 体系学习班, 第30节第1题, Morris遍历 +0098 : 大厂刷题班, 第30节, 本节 +0101 : 大厂刷题班, 第30节, 本节 +0102 : 新手班, 第7节第1题 +0103 : 大厂刷题班, 第30节, 本节 +0104 : 太简单了, 体系学习班, 二叉树的递归套路、Morris遍历都可以做, 跳过 +0105 : 新手班, 第6节第5题 +0108 : 大厂刷题班, 第30节, 本节 +0116 : 大厂刷题班, 第30节, 本节 +0118 : 大厂刷题班, 第30节, 本节 +0119 : 本题不在高频题列表中,但和0118类似, 大厂刷题班, 第30节, 本节 +0121 : 大厂刷题班, 第15节第1题 +0122 : 大厂刷题班, 第15节第2题 +0123 : 大厂刷题班, 第15节第3题 +0124 : 大厂刷题班, 第30节, 本节 \ No newline at end of file diff --git a/大厂刷题班/class31/Problem_0125_ValidPalindrome.java b/大厂刷题班/class31/Problem_0125_ValidPalindrome.java new file mode 100644 index 0000000..e55e04b --- /dev/null +++ b/大厂刷题班/class31/Problem_0125_ValidPalindrome.java @@ -0,0 +1,52 @@ +package class31; + +public class Problem_0125_ValidPalindrome { + + // 忽略空格、忽略大小写 -> 是不是回文 + // 数字不在忽略大小写的范围内 + public static boolean isPalindrome(String s) { + if (s == null || s.length() == 0) { + return true; + } + char[] str = s.toCharArray(); + int L = 0; + int R = str.length - 1; + while (L < R) { + // 英文(大小写) + 数字 + if (validChar(str[L]) && validChar(str[R])) { + if (!equal(str[L], str[R])) { + return false; + } + L++; + R--; + } else { + L += validChar(str[L]) ? 0 : 1; + R -= validChar(str[R]) ? 0 : 1; + } + } + return true; + } + + public static boolean validChar(char c) { + return isLetter(c) || isNumber(c); + } + + public static boolean equal(char c1, char c2) { + if (isNumber(c1) || isNumber(c2)) { + return c1 == c2; + } + // a A 32 + // b B 32 + // c C 32 + return (c1 == c2) || (Math.max(c1, c2) - Math.min(c1, c2) == 32); + } + + public static boolean isLetter(char c) { + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); + } + + public static boolean isNumber(char c) { + return (c >= '0' && c <= '9'); + } + +} diff --git a/大厂刷题班/class31/Problem_0127_WordLadder.java b/大厂刷题班/class31/Problem_0127_WordLadder.java new file mode 100644 index 0000000..6a4056f --- /dev/null +++ b/大厂刷题班/class31/Problem_0127_WordLadder.java @@ -0,0 +1,124 @@ +package class31; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; + +public class Problem_0127_WordLadder { + + // start,出发的单词 + // to, 目标单位 + // list, 列表 + // to 一定属于list + // start未必 + // 返回变幻的最短路径长度 + public static int ladderLength1(String start, String to, List list) { + list.add(start); + + // key : 列表中的单词,每一个单词都会有记录! + // value : key这个单词,有哪些邻居! + HashMap> nexts = getNexts(list); + // abc 出发 abc -> abc 0 + // + // bbc 1 + HashMap distanceMap = new HashMap<>(); + distanceMap.put(start, 1); + HashSet set = new HashSet<>(); + set.add(start); + Queue queue = new LinkedList<>(); + queue.add(start); + while (!queue.isEmpty()) { + String cur = queue.poll(); + Integer distance = distanceMap.get(cur); + for (String next : nexts.get(cur)) { + if (next.equals(to)) { + return distance + 1; + } + if (!set.contains(next)) { + set.add(next); + queue.add(next); + distanceMap.put(next, distance + 1); + } + } + + } + return 0; + } + + public static HashMap> getNexts(List words) { + HashSet dict = new HashSet<>(words); + HashMap> nexts = new HashMap<>(); + for (int i = 0; i < words.size(); i++) { + nexts.put(words.get(i), getNext(words.get(i), dict)); + } + return nexts; + } + + // 应该根据具体数据状况决定用什么来找邻居 + // 1)如果字符串长度比较短,字符串数量比较多,以下方法适合 + // 2)如果字符串长度比较长,字符串数量比较少,以下方法不适合 + public static ArrayList getNext(String word, HashSet dict) { + ArrayList res = new ArrayList(); + char[] chs = word.toCharArray(); + for (int i = 0; i < chs.length; i++) { + for (char cur = 'a'; cur <= 'z'; cur++) { + if (chs[i] != cur) { + char tmp = chs[i]; + chs[i] = cur; + if (dict.contains(String.valueOf(chs))) { + res.add(String.valueOf(chs)); + } + chs[i] = tmp; + } + } + } + return res; + } + + public static int ladderLength2(String beginWord, String endWord, List wordList) { + HashSet dict = new HashSet<>(wordList); + if (!dict.contains(endWord)) { + return 0; + } + HashSet startSet = new HashSet<>(); + HashSet endSet = new HashSet<>(); + HashSet visit = new HashSet<>(); + startSet.add(beginWord); + endSet.add(endWord); + for (int len = 2; !startSet.isEmpty(); len++) { + // startSet是较小的,endSet是较大的 + HashSet nextSet = new HashSet<>(); + for (String w : startSet) { + // w -> a(nextSet) + // a b c + // 0 + // 1 + // 2 + for (int j = 0; j < w.length(); j++) { + char[] ch = w.toCharArray(); + for (char c = 'a'; c <= 'z'; c++) { + if (c != w.charAt(j)) { + ch[j] = c; + String next = String.valueOf(ch); + if (endSet.contains(next)) { + return len; + } + if (dict.contains(next) && !visit.contains(next)) { + nextSet.add(next); + visit.add(next); + } + } + } + } + } + // startSet(小) -> nextSet(某个大小) 和 endSet大小来比 + startSet = (nextSet.size() < endSet.size()) ? nextSet : endSet; + endSet = (startSet == nextSet) ? endSet : nextSet; + } + return 0; + } + +} diff --git a/大厂刷题班/class31/Problem_0130_SurroundedRegions.java b/大厂刷题班/class31/Problem_0130_SurroundedRegions.java new file mode 100644 index 0000000..209ae30 --- /dev/null +++ b/大厂刷题班/class31/Problem_0130_SurroundedRegions.java @@ -0,0 +1,115 @@ +package class31; + +public class Problem_0130_SurroundedRegions { + +// // m -> 二维数组, 不是0就是1 +// // +// public static void infect(int[][] m, int i, int j) { +// if (i < 0 || i == m.length || j < 0 || j == m[0].length || m[i][j] != 1) { +// return; +// } +// // m[i][j] == 1 +// m[i][j] = 2; +// infect(m, i - 1, j); +// infect(m, i + 1, j); +// infect(m, i, j - 1); +// infect(m, i, j + 1); +// } + + public static void solve1(char[][] board) { + boolean[] ans = new boolean[1]; + for (int i = 0; i < board.length; i++) { + for (int j = 0; j < board[0].length; j++) { + if (board[i][j] == 'O') { + ans[0] = true; + can(board, i, j, ans); + board[i][j] = ans[0] ? 'T' : 'F'; + } + } + } + for (int i = 0; i < board.length; i++) { + for (int j = 0; j < board[0].length; j++) { + char can = board[i][j]; + if (can == 'T' || can == 'F') { + board[i][j] = '.'; + change(board, i, j, can); + } + } + } + + } + + public static void can(char[][] board, int i, int j, boolean[] ans) { + if (i < 0 || i == board.length || j < 0 || j == board[0].length) { + ans[0] = false; + return; + } + if (board[i][j] == 'O') { + board[i][j] = '.'; + can(board, i - 1, j, ans); + can(board, i + 1, j, ans); + can(board, i, j - 1, ans); + can(board, i, j + 1, ans); + } + } + + public static void change(char[][] board, int i, int j, char can) { + if (i < 0 || i == board.length || j < 0 || j == board[0].length) { + return; + } + if (board[i][j] == '.') { + board[i][j] = can == 'T' ? 'X' : 'O'; + change(board, i - 1, j, can); + change(board, i + 1, j, can); + change(board, i, j - 1, can); + change(board, i, j + 1, can); + } + } + + // 从边界开始感染的方法,比第一种方法更好 + public static void solve2(char[][] board) { + if (board == null || board.length == 0 || board[0] == null || board[0].length == 0) { + return; + } + int N = board.length; + int M = board[0].length; + for (int j = 0; j < M; j++) { + if (board[0][j] == 'O') { + free(board, 0, j); + } + if (board[N - 1][j] == 'O') { + free(board, N - 1, j); + } + } + for (int i = 1; i < N - 1; i++) { + if (board[i][0] == 'O') { + free(board, i, 0); + } + if (board[i][M - 1] == 'O') { + free(board, i, M - 1); + } + } + for (int i = 0; i < N; i++) { + for (int j = 0; j < M; j++) { + if (board[i][j] == 'O') { + board[i][j] = 'X'; + } + if (board[i][j] == 'F') { + board[i][j] = 'O'; + } + } + } + } + + public static void free(char[][] board, int i, int j) { + if (i < 0 || i == board.length || j < 0 || j == board[0].length || board[i][j] != 'O') { + return; + } + board[i][j] = 'F'; + free(board, i + 1, j); + free(board, i - 1, j); + free(board, i, j + 1); + free(board, i, j - 1); + } + +} diff --git a/大厂刷题班/class31/Problem_0139_WordBreak.java b/大厂刷题班/class31/Problem_0139_WordBreak.java new file mode 100644 index 0000000..245e1d6 --- /dev/null +++ b/大厂刷题班/class31/Problem_0139_WordBreak.java @@ -0,0 +1,97 @@ +package class31; + +import java.util.List; + +// lintcode也有测试,数据量比leetcode大很多 : https://www.lintcode.com/problem/107/ +public class Problem_0139_WordBreak { + + public static class Node { + public boolean end; + public Node[] nexts; + + public Node() { + end = false; + nexts = new Node[26]; + } + } + + public static boolean wordBreak1(String s, List wordDict) { + Node root = new Node(); + for (String str : wordDict) { + char[] chs = str.toCharArray(); + Node node = root; + int index = 0; + for (int i = 0; i < chs.length; i++) { + index = chs[i] - 'a'; + if (node.nexts[index] == null) { + node.nexts[index] = new Node(); + } + node = node.nexts[index]; + } + node.end = true; + } + char[] str = s.toCharArray(); + int N = str.length; + boolean[] dp = new boolean[N + 1]; + dp[N] = true; // dp[i] word[i.....] 能不能被分解 + // dp[N] word[N...] -> "" 能不能够被分解 + // dp[i] ... dp[i+1....] + for (int i = N - 1; i >= 0; i--) { + // i + // word[i....] 能不能够被分解 + // i..i i+1.... + // i..i+1 i+2... + Node cur = root; + for (int end = i; end < N; end++) { + cur = cur.nexts[str[end] - 'a']; + if (cur == null) { + break; + } + // 有路! + if (cur.end) { + // i...end 真的是一个有效的前缀串 end+1.... 能不能被分解 + dp[i] |= dp[end + 1]; + } + if (dp[i]) { + break; + } + } + } + return dp[0]; + } + + public static int wordBreak2(String s, List wordDict) { + Node root = new Node(); + for (String str : wordDict) { + char[] chs = str.toCharArray(); + Node node = root; + int index = 0; + for (int i = 0; i < chs.length; i++) { + index = chs[i] - 'a'; + if (node.nexts[index] == null) { + node.nexts[index] = new Node(); + } + node = node.nexts[index]; + } + node.end = true; + } + char[] str = s.toCharArray(); + int N = str.length; + int[] dp = new int[N + 1]; + dp[N] = 1; + for (int i = N - 1; i >= 0; i--) { + Node cur = root; + for (int end = i; end < N; end++) { + cur = cur.nexts[str[end] - 'a']; + if (cur == null) { + break; + } + if (cur.end) { + dp[i] += dp[end + 1]; + } + } + } + return dp[0]; + } + +} diff --git a/大厂刷题班/class31/Problem_0140_WordBreakII.java b/大厂刷题班/class31/Problem_0140_WordBreakII.java new file mode 100644 index 0000000..3290b88 --- /dev/null +++ b/大厂刷题班/class31/Problem_0140_WordBreakII.java @@ -0,0 +1,104 @@ +package class31; + +import java.util.ArrayList; +import java.util.List; + +public class Problem_0140_WordBreakII { + + public static class Node { + public String path; + public boolean end; + public Node[] nexts; + + public Node() { + path = null; + end = false; + nexts = new Node[26]; + } + } + + public static List wordBreak(String s, List wordDict) { + char[] str = s.toCharArray(); + Node root = gettrie(wordDict); + boolean[] dp = getdp(s, root); + ArrayList path = new ArrayList<>(); + List ans = new ArrayList<>(); + process(str, 0, root, dp, path, ans); + return ans; + } + + // str[index.....] 是要搞定的字符串 + // dp[0...N-1] 0... 1.... 2... N-1... 在dp里 + // root 单词表所有单词生成的前缀树头节点 + // path str[0..index-1]做过决定了,做的决定放在path里 + public static void process(char[] str, int index, Node root, boolean[] dp, ArrayList path, + List ans) { + if (index == str.length) { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < path.size() - 1; i++) { + builder.append(path.get(i) + " "); + } + builder.append(path.get(path.size() - 1)); + ans.add(builder.toString()); + } else { + Node cur = root; + for (int end = index; end < str.length; end++) { + // str[i..end] (能不能拆出来) + int road = str[end] - 'a'; + if (cur.nexts[road] == null) { + break; + } + cur = cur.nexts[road]; + if (cur.end && dp[end + 1]) { + // [i...end] 前缀串 + // str.subString(i,end+1) [i..end] + path.add(cur.path); + process(str, end + 1, root, dp, path, ans); + path.remove(path.size() - 1); + } + } + } + } + + public static Node gettrie(List wordDict) { + Node root = new Node(); + for (String str : wordDict) { + char[] chs = str.toCharArray(); + Node node = root; + int index = 0; + for (int i = 0; i < chs.length; i++) { + index = chs[i] - 'a'; + if (node.nexts[index] == null) { + node.nexts[index] = new Node(); + } + node = node.nexts[index]; + } + node.path = str; + node.end = true; + } + return root; + } + + public static boolean[] getdp(String s, Node root) { + char[] str = s.toCharArray(); + int N = str.length; + boolean[] dp = new boolean[N + 1]; + dp[N] = true; + for (int i = N - 1; i >= 0; i--) { + Node cur = root; + for (int end = i; end < N; end++) { + int path = str[end] - 'a'; + if (cur.nexts[path] == null) { + break; + } + cur = cur.nexts[path]; + if (cur.end && dp[end + 1]) { + dp[i] = true; + break; + } + } + } + return dp; + } + +} diff --git a/大厂刷题班/class31/Problem_0148_SortList.java b/大厂刷题班/class31/Problem_0148_SortList.java new file mode 100644 index 0000000..8b1bf26 --- /dev/null +++ b/大厂刷题班/class31/Problem_0148_SortList.java @@ -0,0 +1,227 @@ +package class31; + +public class Problem_0148_SortList { + + public static class ListNode { + int val; + ListNode next; + + public ListNode(int v) { + val = v; + } + } + + // 链表的归并排序 + // 时间复杂度O(N*logN), 因为是链表所以空间复杂度O(1) + public static ListNode sortList1(ListNode head) { + int N = 0; + ListNode cur = head; + while (cur != null) { + N++; + cur = cur.next; + } + ListNode h = head; + ListNode teamFirst = head; + ListNode pre = null; + for (int len = 1; len < N; len <<= 1) { + while (teamFirst != null) { + // 左组从哪到哪 ls le + // 右组从哪到哪 rs re + // 左 右 next + ListNode[] hthtn = hthtn(teamFirst, len); + // ls...le rs...re -> merge去 + // 整体的头、整体的尾 + ListNode[] mhmt = merge(hthtn[0], hthtn[1], hthtn[2], hthtn[3]); + if (h == teamFirst) { + h = mhmt[0]; + pre = mhmt[1]; + } else { + pre.next = mhmt[0]; + pre = mhmt[1]; + } + teamFirst = hthtn[4]; + } + teamFirst = h; + pre = null; + } + return h; + } + + public static ListNode[] hthtn(ListNode teamFirst, int len) { + ListNode ls = teamFirst; + ListNode le = teamFirst; + ListNode rs = null; + ListNode re = null; + ListNode next = null; + int pass = 0; + while (teamFirst != null) { + pass++; + if (pass <= len) { + le = teamFirst; + } + if (pass == len + 1) { + rs = teamFirst; + } + if (pass > len) { + re = teamFirst; + } + if (pass == (len << 1)) { + break; + } + teamFirst = teamFirst.next; + } + le.next = null; + if (re != null) { + next = re.next; + re.next = null; + } + return new ListNode[] { ls, le, rs, re, next }; + } + + public static ListNode[] merge(ListNode ls, ListNode le, ListNode rs, ListNode re) { + if (rs == null) { + return new ListNode[] { ls, le }; + } + ListNode head = null; + ListNode pre = null; + ListNode cur = null; + ListNode tail = null; + while (ls != le.next && rs != re.next) { + if (ls.val <= rs.val) { + cur = ls; + ls = ls.next; + } else { + cur = rs; + rs = rs.next; + } + if (pre == null) { + head = cur; + pre = cur; + } else { + pre.next = cur; + pre = cur; + } + } + if (ls != le.next) { + while (ls != le.next) { + pre.next = ls; + pre = ls; + tail = ls; + ls = ls.next; + } + } else { + while (rs != re.next) { + pre.next = rs; + pre = rs; + tail = rs; + rs = rs.next; + } + } + return new ListNode[] { head, tail }; + } + + // 链表的快速排序 + // 时间复杂度O(N*logN), 空间复杂度O(logN) + public static ListNode sortList2(ListNode head) { + int n = 0; + ListNode cur = head; + while (cur != null) { + cur = cur.next; + n++; + } + return process(head, n).head; + } + + public static class HeadAndTail { + public ListNode head; + public ListNode tail; + + public HeadAndTail(ListNode h, ListNode t) { + head = h; + tail = t; + } + } + + public static HeadAndTail process(ListNode head, int n) { + if (n == 0) { + return new HeadAndTail(head, head); + } + int index = (int) (Math.random() * n); + ListNode cur = head; + while (index-- != 0) { + cur = cur.next; + } + Record r = partition(head, cur); + HeadAndTail lht = process(r.lhead, r.lsize); + HeadAndTail rht = process(r.rhead, r.rsize); + if (lht.tail != null) { + lht.tail.next = r.mhead; + } + r.mtail.next = rht.head; + return new HeadAndTail(lht.head != null ? lht.head : r.mhead, rht.tail != null ? rht.tail : r.mtail); + } + + public static class Record { + public ListNode lhead; + public int lsize; + public ListNode rhead; + public int rsize; + public ListNode mhead; + public ListNode mtail; + + public Record(ListNode lh, int ls, ListNode rh, int rs, ListNode mh, ListNode mt) { + lhead = lh; + lsize = ls; + rhead = rh; + rsize = rs; + mhead = mh; + mtail = mt; + } + } + + public static Record partition(ListNode head, ListNode mid) { + ListNode lh = null; + ListNode lt = null; + int ls = 0; + ListNode mh = null; + ListNode mt = null; + ListNode rh = null; + ListNode rt = null; + int rs = 0; + ListNode tmp = null; + while (head != null) { + tmp = head.next; + head.next = null; + if (head.val < mid.val) { + if (lh == null) { + lh = head; + lt = head; + } else { + lt.next = head; + lt = head; + } + ls++; + } else if (head.val > mid.val) { + if (rh == null) { + rh = head; + rt = head; + } else { + rt.next = head; + rt = head; + } + rs++; + } else { + if (mh == null) { + mh = head; + mt = head; + } else { + mt.next = head; + mt = head; + } + } + head = tmp; + } + return new Record(lh, ls, rh, rs, mh, mt); + } + +} diff --git a/大厂刷题班/class31/Problem_0150_EvaluateReversePolishNotation.java b/大厂刷题班/class31/Problem_0150_EvaluateReversePolishNotation.java new file mode 100644 index 0000000..54cf55e --- /dev/null +++ b/大厂刷题班/class31/Problem_0150_EvaluateReversePolishNotation.java @@ -0,0 +1,40 @@ +package class31; + +import java.util.Stack; + +public class Problem_0150_EvaluateReversePolishNotation { + + public static int evalRPN(String[] tokens) { + Stack stack = new Stack<>(); + for (String str : tokens) { + if (str.equals("+") || str.equals("-") || str.equals("*") || str.equals("/")) { + compute(stack, str); + } else { + stack.push(Integer.valueOf(str)); + } + } + return stack.peek(); + } + + public static void compute(Stack stack, String op) { + int num2 = stack.pop(); + int num1 = stack.pop(); + int ans = 0; + switch (op) { + case "+": + ans = num1 + num2; + break; + case "-": + ans = num1 - num2; + break; + case "*": + ans = num1 * num2; + break; + case "/": + ans = num1 / num2; + break; + } + stack.push(ans); + } + +} diff --git a/大厂刷题班/class31/说明 b/大厂刷题班/class31/说明 new file mode 100644 index 0000000..a3b9f3f --- /dev/null +++ b/大厂刷题班/class31/说明 @@ -0,0 +1,21 @@ +leetcode高频题 +leetcode全题目列表 : https://leetcode.com/problemset/all/ +在全题目列表的右侧栏,点击Top Interview Questions +或者直接进入右侧链接 : https://leetcode.com/problemset/all/?listId=wpwgkgt +即可看到leetcode高频题全列表 +本节课解决leetcode高频题列表中的如下题目 : +0125 : 大厂刷题班, 第31节, 本节 +0127 : 大厂刷题班, 第31节, 本节 +0128 : 大厂刷题班, 第12节第3题 +0130 : 大厂刷题班, 第31节, 本节 +0131 : 大厂刷题班, 第11节第2题 +0134 : 体系学习班, 第24节第3题 & 大厂刷题班, 第25节第4题 +0136 : 体系学习班, 第2节第2题 +0138 : 体系学习班, 第9节第4题 +0139 : 大厂刷题班, 第31节, 本节 +0140 : 大厂刷题班, 第31节, 本节 +0141 : 体系学习班, 第10节第1题 +0146 : 大厂刷题班, 第19节第1题 +0148 : 大厂刷题班, 第31节, 本节 +0149 : 大厂刷题班, 第25节第3题 +0150 : 大厂刷题班, 第31节, 本节 \ No newline at end of file diff --git a/大厂刷题班/class32/Problem_0152_MaximumProductSubarray.java b/大厂刷题班/class32/Problem_0152_MaximumProductSubarray.java new file mode 100644 index 0000000..92e5deb --- /dev/null +++ b/大厂刷题班/class32/Problem_0152_MaximumProductSubarray.java @@ -0,0 +1,45 @@ +package class32; + +public class Problem_0152_MaximumProductSubarray { + + + public static double max(double[] arr) { + if(arr == null || arr.length == 0) { + return 0; // 报错! + } + int n = arr.length; + // 上一步的最大 + double premax = arr[0]; + // 上一步的最小 + double premin = arr[0]; + double ans = arr[0]; + for(int i = 1; i < n; i++) { + double p1 = arr[i]; + double p2 = arr[i] * premax; + double p3 = arr[i] * premin; + double curmax = Math.max(Math.max(p1, p2), p3); + double curmin = Math.min(Math.min(p1, p2), p3); + ans = Math.max(ans, curmax); + premax = curmax; + premin = curmin; + } + return ans; + } + + + + public static int maxProduct(int[] nums) { + int ans = nums[0]; + int min = nums[0]; + int max = nums[0]; + for (int i = 1; i < nums.length; i++) { + int curmin = Math.min(nums[i], Math.min(min * nums[i], max * nums[i])); + int curmax = Math.max(nums[i], Math.max(min * nums[i], max * nums[i])); + min = curmin; + max = curmax; + ans = Math.max(ans, max); + } + return ans; + } + +} diff --git a/大厂刷题班/class32/Problem_0163_MissingRanges.java b/大厂刷题班/class32/Problem_0163_MissingRanges.java new file mode 100644 index 0000000..e6ca7fd --- /dev/null +++ b/大厂刷题班/class32/Problem_0163_MissingRanges.java @@ -0,0 +1,35 @@ +package class32; + +import java.util.ArrayList; +import java.util.List; + +public class Problem_0163_MissingRanges { + + public static List findMissingRanges(int[] nums, int lower, int upper) { + List ans = new ArrayList<>(); + for (int num : nums) { + if (num > lower) { + ans.add(miss(lower, num - 1)); + } + if (num == upper) { + return ans; + } + lower = num + 1; + } + if (lower <= upper) { + ans.add(miss(lower, upper)); + } + return ans; + } + + // 生成"lower->upper"的字符串,如果lower==upper,只用生成"lower" + public static String miss(int lower, int upper) { + String left = String.valueOf(lower); + String right = ""; + if (upper > lower) { + right = "->" + String.valueOf(upper); + } + return left + right; + } + +} diff --git a/大厂刷题班/class32/Problem_0166_FractionToRecurringDecimal.java b/大厂刷题班/class32/Problem_0166_FractionToRecurringDecimal.java new file mode 100644 index 0000000..9dbef7e --- /dev/null +++ b/大厂刷题班/class32/Problem_0166_FractionToRecurringDecimal.java @@ -0,0 +1,46 @@ +package class32; + +import java.util.HashMap; + +public class Problem_0166_FractionToRecurringDecimal { + + public static String fractionToDecimal(int numerator, int denominator) { + if (numerator == 0) { + return "0"; + } + StringBuilder res = new StringBuilder(); + // "+" or "-" + res.append(((numerator > 0) ^ (denominator > 0)) ? "-" : ""); + long num = Math.abs((long) numerator); + long den = Math.abs((long) denominator); + // integral part + res.append(num / den); + num %= den; + if (num == 0) { + return res.toString(); + } + // fractional part + res.append("."); + HashMap map = new HashMap(); + map.put(num, res.length()); + while (num != 0) { + num *= 10; + res.append(num / den); + num %= den; + if (map.containsKey(num)) { + int index = map.get(num); + res.insert(index, "("); + res.append(")"); + break; + } else { + map.put(num, res.length()); + } + } + return res.toString(); + } + + public static void main(String[] args) { + System.out.println(fractionToDecimal(127, 999)); + } + +} diff --git a/大厂刷题班/class32/Problem_0171_ExcelSheetColumnNumber.java b/大厂刷题班/class32/Problem_0171_ExcelSheetColumnNumber.java new file mode 100644 index 0000000..bed8659 --- /dev/null +++ b/大厂刷题班/class32/Problem_0171_ExcelSheetColumnNumber.java @@ -0,0 +1,15 @@ +package class32; + +public class Problem_0171_ExcelSheetColumnNumber { + + // 这道题反过来也要会写 + public static int titleToNumber(String s) { + char[] str = s.toCharArray(); + int ans = 0; + for (int i = 0; i < str.length; i++) { + ans = ans * 26 + (str[i] - 'A') + 1; + } + return ans; + } + +} diff --git a/大厂刷题班/class32/Problem_0172_FactorialTrailingZeroes.java b/大厂刷题班/class32/Problem_0172_FactorialTrailingZeroes.java new file mode 100644 index 0000000..c0ca71e --- /dev/null +++ b/大厂刷题班/class32/Problem_0172_FactorialTrailingZeroes.java @@ -0,0 +1,14 @@ +package class32; + +public class Problem_0172_FactorialTrailingZeroes { + + public static int trailingZeroes(int n) { + int ans = 0; + while (n != 0) { + n /= 5; + ans += n; + } + return ans; + } + +} diff --git a/大厂刷题班/class32/Problem_0189_RotateArray.java b/大厂刷题班/class32/Problem_0189_RotateArray.java new file mode 100644 index 0000000..4eb4480 --- /dev/null +++ b/大厂刷题班/class32/Problem_0189_RotateArray.java @@ -0,0 +1,60 @@ +package class32; + +public class Problem_0189_RotateArray { + + public void rotate1(int[] nums, int k) { + int N = nums.length; + k = k % N; + reverse(nums, 0, N - k - 1); + reverse(nums, N - k, N - 1); + reverse(nums, 0, N - 1); + } + + public static void reverse(int[] nums, int L, int R) { + while (L < R) { + int tmp = nums[L]; + nums[L++] = nums[R]; + nums[R--] = tmp; + } + } + + public static void rotate2(int[] nums, int k) { + int N = nums.length; + k = k % N; + if (k == 0) { + return; + } + int L = 0; + int R = N - 1; + int lpart = N - k; + int rpart = k; + int same = Math.min(lpart, rpart); + int diff = lpart - rpart; + exchange(nums, L, R, same); + while (diff != 0) { + if (diff > 0) { + L += same; + lpart = diff; + } else { + R -= same; + rpart = -diff; + } + same = Math.min(lpart, rpart); + diff = lpart - rpart; + exchange(nums, L, R, same); + } + } + + public static void exchange(int[] nums, int start, int end, int size) { + int i = end - size + 1; + int tmp = 0; + while (size-- != 0) { + tmp = nums[start]; + nums[start] = nums[i]; + nums[i] = tmp; + start++; + i++; + } + } + +} diff --git a/大厂刷题班/class32/Problem_0190_ReverseBits.java b/大厂刷题班/class32/Problem_0190_ReverseBits.java new file mode 100644 index 0000000..5851d87 --- /dev/null +++ b/大厂刷题班/class32/Problem_0190_ReverseBits.java @@ -0,0 +1,48 @@ +package class32; + +public class Problem_0190_ReverseBits { + + // 代码看着很魔幻吧? + // 给个例子,假设n二进制为: + // 1011 0111 0011 1001 0011 1111 0110 1010 + // 解释一下,第一行,是把n左边16位,和n右边16位交换 + // n = (n >>> 16) | (n << 16); + // 因为 n >>> 16 就是左边16位被移动到了右侧 + // 同时 n << 16 就是右边16位被移动到了左侧 + // 又 | 在了一起,所以,n变成了 + // 0011 1111 0110 1010 1011 0111 0011 1001 + + // 第二行, + // n = ((n & 0xff00ff00) >>> 8) | ((n & 0x00ff00ff) << 8); + // (n & 0xff00ff00) + // 这一句意思是,左侧开始算0~7位,保留;8~15位,全变0;16~23位,保留;24~31位,全变0 + // 0011 1111 0000 0000 1011 0111 0000 0000 + // (n & 0xff00ff00) >>> 8 这句就是上面的值,统一向右移动8位,变成: + // 0000 0000 0011 1111 0000 0000 1011 0111 + // + // + // (n & 0x00ff00ff) + // 这一句意思是,左侧开始算0~7位,全变0;8~15位,保留;16~23位,全变0;24~31位,保留 + // 0000 0000 0110 1010 0000 0000 0011 1001 + // (n & 0x00ff00ff) << 8 这句就是上面的值,统一向左移动8位,变成: + // 0110 1010 0000 0000 0011 1001 0000 0000 + // 那么 ((n & 0xff00ff00) >>> 8) | ((n & 0x00ff00ff) << 8) + // 什么效果?就是n的0~7位和8~15位交换了,16~23位和24~31位交换了 + // 0110 1010 0011 1111 0011 1001 1011 0111 + + // 也就是说,整个过程是n的左16位,和右16位交换 + // n的左16位的内部,左8位和右8位交换;n的右16位的内部,左8位和右8位交换 + // 接下来的一行,其实是,从左边开始算,0~7位内部,左4和右4交换;8~15位,左4和右4交换;... + // 接下来的一行,其实是,从左边开始算,0~3位内部,左2和右2交换;4~7位,左2和右2交换;... + // 最后的一行,其实是,从左边开始算,0~1位内部,左1和右1交换;2~3位,左1和右1交换;... + public static int reverseBits(int n) { + // n的高16位,和n的低16位,交换 + n = (n >>> 16) | (n << 16); + n = ((n & 0xff00ff00) >>> 8) | ((n & 0x00ff00ff) << 8); + n = ((n & 0xf0f0f0f0) >>> 4) | ((n & 0x0f0f0f0f) << 4); + n = ((n & 0xcccccccc) >>> 2) | ((n & 0x33333333) << 2); + n = ((n & 0xaaaaaaaa) >>> 1) | ((n & 0x55555555) << 1); + return n; + } + +} diff --git a/大厂刷题班/class32/Problem_0191_NumberOf1Bits.java b/大厂刷题班/class32/Problem_0191_NumberOf1Bits.java new file mode 100644 index 0000000..f5973f0 --- /dev/null +++ b/大厂刷题班/class32/Problem_0191_NumberOf1Bits.java @@ -0,0 +1,26 @@ +package class32; + +public class Problem_0191_NumberOf1Bits { + + // n的二进制形式,有几个1? + public static int hammingWeight1(int n) { + int bits = 0; + int rightOne = 0; + while(n != 0) { + bits++; + rightOne = n & (-n); + n ^= rightOne; + } + return bits; + } + + public static int hammingWeight2(int n) { + n = (n & 0x55555555) + ((n >>> 1) & 0x55555555); + n = (n & 0x33333333) + ((n >>> 2) & 0x33333333); + n = (n & 0x0f0f0f0f) + ((n >>> 4) & 0x0f0f0f0f); + n = (n & 0x00ff00ff) + ((n >>> 8) & 0x00ff00ff); + n = (n & 0x0000ffff) + ((n >>> 16) & 0x0000ffff); + return n; + } + +} diff --git a/大厂刷题班/class32/Problem_0202_HappyNumber.java b/大厂刷题班/class32/Problem_0202_HappyNumber.java new file mode 100644 index 0000000..3e54bbc --- /dev/null +++ b/大厂刷题班/class32/Problem_0202_HappyNumber.java @@ -0,0 +1,59 @@ +package class32; + +import java.util.HashSet; +import java.util.TreeSet; + +public class Problem_0202_HappyNumber { + + public static boolean isHappy1(int n) { + HashSet set = new HashSet<>(); + while (n != 1) { + int sum = 0; + while (n != 0) { + int r = n % 10; + sum += r * r; + n /= 10; + } + n = sum; + if (set.contains(n)) { + break; + } + set.add(n); + } + return n == 1; + } + + // 实验代码 + public static TreeSet sum(int n) { + TreeSet set = new TreeSet<>(); + while (!set.contains(n)) { + set.add(n); + int sum = 0; + while (n != 0) { + sum += (n % 10) * (n % 10); + n /= 10; + } + n = sum; + } + return set; + } + + public static boolean isHappy2(int n) { + while (n != 1 && n != 4) { + int sum = 0; + while (n != 0) { + sum += (n % 10) * (n % 10); + n /= 10; + } + n = sum; + } + return n == 1; + } + + public static void main(String[] args) { + for (int i = 1; i < 1000; i++) { + System.out.println(sum(i)); + } + } + +} diff --git a/大厂刷题班/class32/Problem_0204_CountPrimes.java b/大厂刷题班/class32/Problem_0204_CountPrimes.java new file mode 100644 index 0000000..0245051 --- /dev/null +++ b/大厂刷题班/class32/Problem_0204_CountPrimes.java @@ -0,0 +1,30 @@ +package class32; + +public class Problem_0204_CountPrimes { + + public static int countPrimes(int n) { + if (n < 3) { + return 0; + } + // j已经不是素数了,f[j] = true; + boolean[] f = new boolean[n]; + int count = n / 2; // 所有偶数都不要,还剩几个数 + // 跳过了1、2 3、5、7、 + for (int i = 3; i * i < n; i += 2) { + if (f[i]) { + continue; + } + // 3 -> 3 * 3 = 9 3 * 5 = 15 3 * 7 = 21 + // 7 -> 7 * 7 = 49 7 * 9 = 63 + // 13 -> 13 * 13 13 * 15 + for (int j = i * i; j < n; j += 2 * i) { + if (!f[j]) { + --count; + f[j] = true; + } + } + } + return count; + } + +} diff --git a/大厂刷题班/class32/SequenceM.java b/大厂刷题班/class32/SequenceM.java new file mode 100644 index 0000000..daa764f --- /dev/null +++ b/大厂刷题班/class32/SequenceM.java @@ -0,0 +1,162 @@ +package class32; + +import java.util.Arrays; + +// 给定一个数组arr,arr[i] = j,表示第i号试题的难度为j。给定一个非负数M +// 想出一张卷子,对于任何相邻的两道题目,前一题的难度不能超过后一题的难度+M +// 返回所有可能的卷子种数 +public class SequenceM { + + // 纯暴力方法,生成所有排列,一个一个验证 + public static int ways1(int[] arr, int m) { + if (arr == null || arr.length == 0) { + return 0; + } + return process(arr, 0, m); + } + + public static int process(int[] arr, int index, int m) { + if (index == arr.length) { + for (int i = 1; i < index; i++) { + if (arr[i - 1] > arr[i] + m) { + return 0; + } + } + return 1; + } + int ans = 0; + for (int i = index; i < arr.length; i++) { + swap(arr, index, i); + ans += process(arr, index + 1, m); + swap(arr, index, i); + } + return ans; + } + + public static void swap(int[] arr, int i, int j) { + int tmp = arr[i]; + arr[i] = arr[j]; + arr[j] = tmp; + } + + // 时间复杂度O(N * logN) + // 从左往右的动态规划 + 范围上二分 + public static int ways2(int[] arr, int m) { + if (arr == null || arr.length == 0) { + return 0; + } + Arrays.sort(arr); + int all = 1; + for (int i = 1; i < arr.length; i++) { + all = all * (num(arr, i - 1, arr[i] - m) + 1); + } + return all; + } + + // arr[0..r]上返回>=t的数有几个, 二分的方法 + // 找到 >=t 最左的位置a, 然后返回r - a + 1就是个数 + public static int num(int[] arr, int r, int t) { + int i = 0; + int j = r; + int m = 0; + int a = r + 1; + while (i <= j) { + m = (i + j) / 2; + if (arr[m] >= t) { + a = m; + j = m - 1; + } else { + i = m + 1; + } + } + return r - a + 1; + } + + // 时间复杂度O(N * logV) + // 从左往右的动态规划 + IndexTree + public static int ways3(int[] arr, int m) { + if (arr == null || arr.length == 0) { + return 0; + } + int max = Integer.MIN_VALUE; + int min = Integer.MAX_VALUE; + for (int num : arr) { + max = Math.max(max, num); + min = Math.min(min, num); + } + IndexTree itree = new IndexTree(max - min + 2); + Arrays.sort(arr); + int a = 0; + int b = 0; + int all = 1; + itree.add(arr[0] - min + 1, 1); + for (int i = 1; i < arr.length; i++) { + a = arr[i] - min + 1; + b = i - (a - m - 1 >= 1 ? itree.sum(a - m - 1) : 0); + all = all * (b + 1); + itree.add(a, 1); + } + return all; + } + + // 注意开始下标是1,不是0 + public static class IndexTree { + + private int[] tree; + private int N; + + public IndexTree(int size) { + N = size; + tree = new int[N + 1]; + } + + public int sum(int index) { + int ret = 0; + while (index > 0) { + ret += tree[index]; + index -= index & -index; + } + return ret; + } + + public void add(int index, int d) { + while (index <= N) { + tree[index] += d; + index += index & -index; + } + } + } + + // 为了测试 + public static int[] randomArray(int len, int value) { + int[] arr = new int[len]; + for (int i = 0; i < len; i++) { + arr[i] = (int) (Math.random() * (value + 1)); + } + return arr; + } + + // 为了测试 + public static void main(String[] args) { + int N = 10; + int value = 20; + int testTimes = 1000; + System.out.println("测试开始"); + for (int i = 0; i < testTimes; i++) { + int len = (int) (Math.random() * (N + 1)); + int[] arr = randomArray(len, value); + int m = (int) (Math.random() * (value + 1)); + int ans1 = ways1(arr, m); + int ans2 = ways2(arr, m); + int ans3 = ways3(arr, m); + if (ans1 != ans2 || ans1 != ans3) { + System.out.println("出错了!"); + System.out.println(ans1); + System.out.println(ans2); + System.out.println(ans3); + } + } + System.out.println("测试结束"); + } + +} diff --git a/大厂刷题班/class32/说明 b/大厂刷题班/class32/说明 new file mode 100644 index 0000000..4f72a2d --- /dev/null +++ b/大厂刷题班/class32/说明 @@ -0,0 +1,25 @@ +leetcode高频题 +leetcode全题目列表 : https://leetcode.com/problemset/all/ +在全题目列表的右侧栏,点击Top Interview Questions +或者直接进入右侧链接 : https://leetcode.com/problemset/all/?listId=wpwgkgt +即可看到leetcode高频题全列表 +本节课解决leetcode高频题列表中的如下题目 : +0152 : 大厂刷题班, 第32节, 本节 +0155 : 体系学习班, 第3节第5题 +0160 : 体系学习班, 第10节第1题 +0162 : 体系学习班, 第1节第6题 +0163 : 大厂刷题班, 第32节, 本节 +0166 : 大厂刷题班, 第32节, 本节 +0169 : 大厂刷题班, 第23节第4题 +0171 : 大厂刷题班, 第32节, 本节 +0172 : 大厂刷题班, 第32节, 本节 +0179 : 体系学习班, 第13节第5题 +0188 : 大厂刷题班, 第15节第4题 +0189 : 大厂刷题班, 第32节, 本节 +0190 : 大厂刷题班, 第32节, 本节 +0191 : 大厂刷题班, 第32节, 本节 +0198 : 大厂刷题班, 第4节第4题 +0200 : 体系学习班, 第15节第2题、第3题 +0202 : 大厂刷题班, 第32节, 本节 +0204 : 大厂刷题班, 第32节, 本节 +补充题 SequenceM : 拼多多, 卷子合法数量问题 \ No newline at end of file diff --git a/大厂刷题班/class33/Problem_0207_CourseSchedule.java b/大厂刷题班/class33/Problem_0207_CourseSchedule.java new file mode 100644 index 0000000..282454a --- /dev/null +++ b/大厂刷题班/class33/Problem_0207_CourseSchedule.java @@ -0,0 +1,113 @@ +package class33; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.Queue; + +public class Problem_0207_CourseSchedule { + + // 一个node,就是一个课程 + // name是课程的编号 + // in是课程的入度 + public static class Course { + public int name; + public int in; + public ArrayList nexts; + + public Course(int n) { + name = n; + in = 0; + nexts = new ArrayList<>(); + } + } + + public static boolean canFinish1(int numCourses, int[][] prerequisites) { + if (prerequisites == null || prerequisites.length == 0) { + return true; + } + // 一个编号 对应 一个课的实例 + HashMap nodes = new HashMap<>(); + for (int[] arr : prerequisites) { + int to = arr[0]; + int from = arr[1]; // from -> to + if (!nodes.containsKey(to)) { + nodes.put(to, new Course(to)); + } + if (!nodes.containsKey(from)) { + nodes.put(from, new Course(from)); + } + Course t = nodes.get(to); + Course f = nodes.get(from); + f.nexts.add(t); + t.in++; + } + int needPrerequisiteNums = nodes.size(); + Queue zeroInQueue = new LinkedList<>(); + for (Course node : nodes.values()) { + if (node.in == 0) { + zeroInQueue.add(node); + } + } + int count = 0; + while (!zeroInQueue.isEmpty()) { + Course cur = zeroInQueue.poll(); + count++; + for (Course next : cur.nexts) { + if (--next.in == 0) { + zeroInQueue.add(next); + } + } + } + return count == needPrerequisiteNums; + } + + // 和方法1算法过程一样 + // 但是写法优化了 + public static boolean canFinish2(int courses, int[][] relation) { + if (relation == null || relation.length == 0) { + return true; + } + // 3 : 0 1 2 + // nexts : 0 {} + // 1 {} + // 2 {} + // 3 {0,1,2} + ArrayList> nexts = new ArrayList<>(); + for (int i = 0; i < courses; i++) { + nexts.add(new ArrayList<>()); + } + // 3 入度 1 in[3] == 1 + int[] in = new int[courses]; + for (int[] arr : relation) { + // arr[1] from arr[0] to + nexts.get(arr[1]).add(arr[0]); + in[arr[0]]++; + } + + // 队列 + int[] zero = new int[courses]; + // 该队列有效范围是[l,r) + // 新来的数,放哪?r位置,r++ + // 出队列的数,从哪拿?l位置,l++ + // l == r 队列无元素 l < r 队列有元素 + int l = 0; + int r = 0; + for (int i = 0; i < courses; i++) { + if (in[i] == 0) { + zero[r++] = i; + } + } + int count = 0; + while (l != r) { + count++; // zero[l] 出队列 l++ + for (int next : nexts.get(zero[l++])) { + if (--in[next] == 0) { + zero[r++] = next; + } + } + } + return count == nexts.size(); + } + +} diff --git a/大厂刷题班/class33/Problem_0210_CourseScheduleII.java b/大厂刷题班/class33/Problem_0210_CourseScheduleII.java new file mode 100644 index 0000000..33017c2 --- /dev/null +++ b/大厂刷题班/class33/Problem_0210_CourseScheduleII.java @@ -0,0 +1,71 @@ +package class33; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.Queue; + +public class Problem_0210_CourseScheduleII { + + public static class Node { + public int name; + public int in; + public ArrayList nexts; + + public Node(int n) { + name = n; + in = 0; + nexts = new ArrayList<>(); + } + } + + public int[] findOrder(int numCourses, int[][] prerequisites) { + int[] ans = new int[numCourses]; + for (int i = 0; i < numCourses; i++) { + ans[i] = i; + } + if (prerequisites == null || prerequisites.length == 0) { + return ans; + } + HashMap nodes = new HashMap<>(); + for (int[] arr : prerequisites) { + int to = arr[0]; + int from = arr[1]; + if (!nodes.containsKey(to)) { + nodes.put(to, new Node(to)); + } + if (!nodes.containsKey(from)) { + nodes.put(from, new Node(from)); + } + Node t = nodes.get(to); + Node f = nodes.get(from); + f.nexts.add(t); + t.in++; + } + int index = 0; + Queue zeroInQueue = new LinkedList<>(); + for (int i = 0; i < numCourses; i++) { + if (!nodes.containsKey(i)) { + ans[index++] = i; + } else { + if (nodes.get(i).in == 0) { + zeroInQueue.add(nodes.get(i)); + } + } + } + int needPrerequisiteNums = nodes.size(); + int count = 0; + while (!zeroInQueue.isEmpty()) { + Node cur = zeroInQueue.poll(); + ans[index++] = cur.name; + count++; + for (Node next : cur.nexts) { + if (--next.in == 0) { + zeroInQueue.add(next); + } + } + } + return count == needPrerequisiteNums ? ans : new int[0]; + } + +} diff --git a/大厂刷题班/class33/Problem_0213_HouseRobberII.java b/大厂刷题班/class33/Problem_0213_HouseRobberII.java new file mode 100644 index 0000000..cc8dcb2 --- /dev/null +++ b/大厂刷题班/class33/Problem_0213_HouseRobberII.java @@ -0,0 +1,50 @@ +package class33; + +public class Problem_0213_HouseRobberII { + + // arr 长度大于等于1 + public static int pickMaxSum(int[] arr) { + int n = arr.length; + // dp[i] : arr[0..i]范围上,随意选择,但是,任何两数不能相邻。得到的最大累加和是多少? + int[] dp = new int[n]; + dp[0] = arr[0]; + dp[1] = Math.max(arr[0], arr[1]); + for (int i = 2; i < n; i++) { + int p1 = arr[i]; + int p2 = dp[i - 1]; + int p3 = arr[i] + dp[i - 2]; + dp[i] = Math.max(p1, Math.max(p2, p3)); + } + return dp[n - 1]; + } + + public static int rob(int[] nums) { + if (nums == null || nums.length == 0) { + return 0; + } + if (nums.length == 1) { + return nums[0]; + } + if (nums.length == 2) { + return Math.max(nums[0], nums[1]); + } + int pre2 = nums[0]; + int pre1 = Math.max(nums[0], nums[1]); + for (int i = 2; i < nums.length - 1; i++) { + int tmp = Math.max(pre1, nums[i] + pre2); + pre2 = pre1; + pre1 = tmp; + } + int ans1 = pre1; + pre2 = nums[1]; + pre1 = Math.max(nums[1], nums[2]); + for (int i = 3; i < nums.length; i++) { + int tmp = Math.max(pre1, nums[i] + pre2); + pre2 = pre1; + pre1 = tmp; + } + int ans2 = pre1; + return Math.max(ans1, ans2); + } + +} diff --git a/大厂刷题班/class33/Problem_0237_DeleteNodeInLinkedList.java b/大厂刷题班/class33/Problem_0237_DeleteNodeInLinkedList.java new file mode 100644 index 0000000..9aeab6c --- /dev/null +++ b/大厂刷题班/class33/Problem_0237_DeleteNodeInLinkedList.java @@ -0,0 +1,15 @@ +package class33; + +public class Problem_0237_DeleteNodeInLinkedList { + + public static class ListNode { + int val; + ListNode next; + } + + public void deleteNode(ListNode node) { + node.val = node.next.val; + node.next = node.next.next; + } + +} diff --git a/大厂刷题班/class33/Problem_0238_ProductOfArrayExceptSelf.java b/大厂刷题班/class33/Problem_0238_ProductOfArrayExceptSelf.java new file mode 100644 index 0000000..a605d46 --- /dev/null +++ b/大厂刷题班/class33/Problem_0238_ProductOfArrayExceptSelf.java @@ -0,0 +1,24 @@ +package class33; + +public class Problem_0238_ProductOfArrayExceptSelf { + + public int[] productExceptSelf(int[] nums) { + int n = nums.length; + int[] ans = new int[n]; + ans[0] = nums[0]; + for (int i = 1; i < n; i++) { + ans[i] = ans[i - 1] * nums[i]; + } + int right = 1; + for (int i = n - 1; i > 0; i--) { + ans[i] = ans[i - 1] * right; + right *= nums[i]; + } + ans[0] = right; + return ans; + } + + // 扩展 : 如果仅仅是不能用除号,把结果直接填在nums里呢? + // 解法:数一共几个0;每一个位得到结果就是,a / b,位运算替代 /,之前的课讲过(新手班) + +} diff --git a/大厂刷题班/class33/Problem_0242_ValidAnagram.java b/大厂刷题班/class33/Problem_0242_ValidAnagram.java new file mode 100644 index 0000000..c6f9e49 --- /dev/null +++ b/大厂刷题班/class33/Problem_0242_ValidAnagram.java @@ -0,0 +1,23 @@ +package class33; + +public class Problem_0242_ValidAnagram { + + public static boolean isAnagram(String s, String t) { + if (s.length() != t.length()) { + return false; + } + char[] str1 = s.toCharArray(); + char[] str2 = t.toCharArray(); + int[] count = new int[256]; + for (char cha : str1) { + count[cha]++; + } + for (char cha : str2) { + if (--count[cha] < 0) { + return false; + } + } + return true; + } + +} diff --git a/大厂刷题班/class33/Problem_0251_Flatten2DVector.java b/大厂刷题班/class33/Problem_0251_Flatten2DVector.java new file mode 100644 index 0000000..6dd56e1 --- /dev/null +++ b/大厂刷题班/class33/Problem_0251_Flatten2DVector.java @@ -0,0 +1,53 @@ +package class33; + +public class Problem_0251_Flatten2DVector { + + public static class Vector2D { + private int[][] matrix; + private int row; + private int col; + private boolean curUse; + + public Vector2D(int[][] v) { + matrix = v; + row = 0; + col = -1; + curUse = true; + hasNext(); + } + + public int next() { + int ans = matrix[row][col]; + curUse = true; + hasNext(); + return ans; + } + + public boolean hasNext() { + if (row == matrix.length) { + return false; + } + if (!curUse) { + return true; + } + // (row,col)用过了 + if (col < matrix[row].length - 1) { + col++; + } else { + col = 0; + do { + row++; + } while (row < matrix.length && matrix[row].length == 0); + } + // 新的(row,col) + if (row != matrix.length) { + curUse = false; + return true; + } else { + return false; + } + } + + } + +} diff --git a/大厂刷题班/class33/Problem_0269_AlienDictionary.java b/大厂刷题班/class33/Problem_0269_AlienDictionary.java new file mode 100644 index 0000000..a89e295 --- /dev/null +++ b/大厂刷题班/class33/Problem_0269_AlienDictionary.java @@ -0,0 +1,65 @@ +package class33; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.Queue; + +public class Problem_0269_AlienDictionary { + + public static String alienOrder(String[] words) { + if (words == null || words.length == 0) { + return ""; + } + int N = words.length; + HashMap indegree = new HashMap<>(); + for (int i = 0; i < N; i++) { + for (char c : words[i].toCharArray()) { + indegree.put(c, 0); + } + } + HashMap> graph = new HashMap<>(); + for (int i = 0; i < N - 1; i++) { + char[] cur = words[i].toCharArray(); + char[] nex = words[i + 1].toCharArray(); + int len = Math.min(cur.length, nex.length); + int j = 0; + for (; j < len; j++) { + if (cur[j] != nex[j]) { + if (!graph.containsKey(cur[j])) { + graph.put(cur[j], new HashSet<>()); + } + if (!graph.get(cur[j]).contains(nex[j])) { + graph.get(cur[j]).add(nex[j]); + indegree.put(nex[j], indegree.get(nex[j]) + 1); + } + break; + } + } + if (j < cur.length && j == nex.length) { + return ""; + } + } + StringBuilder ans = new StringBuilder(); + Queue q = new LinkedList<>(); + for (Character key : indegree.keySet()) { + if (indegree.get(key) == 0) { + q.offer(key); + } + } + while (!q.isEmpty()) { + char cur = q.poll(); + ans.append(cur); + if (graph.containsKey(cur)) { + for (char next : graph.get(cur)) { + indegree.put(next, indegree.get(next) - 1); + if (indegree.get(next) == 0) { + q.offer(next); + } + } + } + } + return ans.length() == indegree.size() ? ans.toString() : ""; + } + +} diff --git a/大厂刷题班/class33/Problem_0277_FindTheCelebrity.java b/大厂刷题班/class33/Problem_0277_FindTheCelebrity.java new file mode 100644 index 0000000..a91abc4 --- /dev/null +++ b/大厂刷题班/class33/Problem_0277_FindTheCelebrity.java @@ -0,0 +1,38 @@ +package class33; + +public class Problem_0277_FindTheCelebrity { + + // 提交时不要提交这个函数,因为默认系统会给你这个函数 + // knows方法,自己不认识自己 + public static boolean knows(int x, int i) { + return true; + } + + // 只提交下面的方法 0 ~ n-1 + public int findCelebrity(int n) { + // 谁可能成为明星,谁就是cand + int cand = 0; + for (int i = 0; i < n; ++i) { + if (knows(cand, i)) { + cand = i; + } + } + // cand是什么?唯一可能是明星的人! + // 下一步就是验证,它到底是不是明星 + // 1) cand是不是不认识所有的人 cand...(右侧cand都不认识) + // 所以,只用验证 ....cand的左侧即可 + for (int i = 0; i < cand; ++i) { + if (knows(cand, i)) { + return -1; + } + } + // 2) 是不是所有的人都认识cand + for (int i = 0; i < n; ++i) { + if (!knows(i, cand)) { + return -1; + } + } + return cand; + } + +} diff --git a/大厂刷题班/class33/Problem_0279_PerfectSquares.java b/大厂刷题班/class33/Problem_0279_PerfectSquares.java new file mode 100644 index 0000000..645680b --- /dev/null +++ b/大厂刷题班/class33/Problem_0279_PerfectSquares.java @@ -0,0 +1,70 @@ +package class33; + +public class Problem_0279_PerfectSquares { + + // 暴力解 + public static int numSquares1(int n) { + int res = n, num = 2; + while (num * num <= n) { + int a = n / (num * num), b = n % (num * num); + res = Math.min(res, a + numSquares1(b)); + num++; + } + return res; + } + + // 1 : 1, 4, 9, 16, 25, 36, ... + // 4 : 7, 15, 23, 28, 31, 39, 47, 55, 60, 63, 71, ... + // 规律解 + // 规律一:个数不超过4 + // 规律二:出现1个的时候,显而易见 + // 规律三:任何数 % 8 == 7,一定是4个 + // 规律四:任何数消去4的因子之后,剩下rest,rest % 8 == 7,一定是4个 + public static int numSquares2(int n) { + int rest = n; + while (rest % 4 == 0) { + rest /= 4; + } + if (rest % 8 == 7) { + return 4; + } + int f = (int) Math.sqrt(n); + if (f * f == n) { + return 1; + } + for (int first = 1; first * first <= n; first++) { + int second = (int) Math.sqrt(n - first * first); + if (first * first + second * second == n) { + return 2; + } + } + return 3; + } + + // 数学解 + // 1)四平方和定理 + // 2)任何数消掉4的因子,结论不变 + public static int numSquares3(int n) { + while (n % 4 == 0) { + n /= 4; + } + if (n % 8 == 7) { + return 4; + } + for (int a = 0; a * a <= n; ++a) { + // a * a + b * b = n + int b = (int) Math.sqrt(n - a * a); + if (a * a + b * b == n) { + return (a > 0 && b > 0) ? 2 : 1; + } + } + return 3; + } + + public static void main(String[] args) { + for (int i = 1; i < 1000; i++) { + System.out.println(i + " , " + numSquares1(i)); + } + } + +} diff --git a/大厂刷题班/class33/Problem_0283_MoveZeroes.java b/大厂刷题班/class33/Problem_0283_MoveZeroes.java new file mode 100644 index 0000000..168e59d --- /dev/null +++ b/大厂刷题班/class33/Problem_0283_MoveZeroes.java @@ -0,0 +1,20 @@ +package class33; + +public class Problem_0283_MoveZeroes { + + public static void moveZeroes(int[] nums) { + int to = 0; + for (int i = 0; i < nums.length; i++) { + if (nums[i] != 0) { + swap(nums, to++, i); + } + } + } + + public static void swap(int[] arr, int i, int j) { + int tmp = arr[i]; + arr[i] = arr[j]; + arr[j] = tmp; + } + +} diff --git a/大厂刷题班/class33/说明 b/大厂刷题班/class33/说明 new file mode 100644 index 0000000..d91e8cc --- /dev/null +++ b/大厂刷题班/class33/说明 @@ -0,0 +1,32 @@ +leetcode高频题 +leetcode全题目列表 : https://leetcode.com/problemset/all/ +在全题目列表的右侧栏,点击Top Interview Questions +或者直接进入右侧链接 : https://leetcode.com/problemset/all/?listId=wpwgkgt +即可看到leetcode高频题全列表 +本节课解决leetcode高频题列表中的如下题目 : +0206 : 太简单,跳过 +0207 : 大厂刷题班, 第33节, 本节 +0208 : 体系学习班, 第8节第1题 +0210 : 大厂刷题班, 第33节, 本节 +0212 : 大厂刷题班, 第26节第1题 +0213 : 大厂刷题班, 第33节, 本节 +0215 : 体系学习班, 第29节第1题 +0217 : 太简单,跳过 +0218 : 大厂刷题班, 第4节第8题 +0227 : 大厂刷题班, 第8节第1题 +0230 : 太简单了,跳过。二叉树基本遍历、Morris遍历都可以解决 +0234 : 体系学习班, 第9节第2题 +0236 : 体系学习班, 第13节第3题 扩展在 大厂刷题班, 第23节第1题 +0237 : 大厂刷题班, 第33节, 本节 +0238 : 大厂刷题班, 第33节, 本节 +0239 : 体系学习班, 第24节第1题 +0240 : 大厂刷题班, 第17节第1题 +0242 : 大厂刷题班, 第33节, 本节 +0251 : 大厂刷题班, 第33节, 本节 +0253 : 体系学习班, 第14节第3题 +0268 : 大厂刷题班, 第14节第6题 +0269 : 大厂刷题班, 第33节, 本节 +0277 : 大厂刷题班, 第33节, 本节 +0279 : 大厂刷题班, 第33节, 本节 +0283 : 大厂刷题班, 第33节, 本节 +0285 : 太简单了,跳过。用二叉树普通遍历或者Morris遍历都可以解决 \ No newline at end of file diff --git a/大厂刷题班/class34/Problem_0287_FindTheDuplicateNumber.java b/大厂刷题班/class34/Problem_0287_FindTheDuplicateNumber.java new file mode 100644 index 0000000..dcd64b5 --- /dev/null +++ b/大厂刷题班/class34/Problem_0287_FindTheDuplicateNumber.java @@ -0,0 +1,23 @@ +package class34; + +public class Problem_0287_FindTheDuplicateNumber { + + public static int findDuplicate(int[] nums) { + if (nums == null || nums.length < 2) { + return -1; + } + int slow = nums[0]; + int fast = nums[nums[0]]; + while (slow != fast) { + slow = nums[slow]; + fast = nums[nums[fast]]; + } + fast = 0; + while (slow != fast) { + fast = nums[fast]; + slow = nums[slow]; + } + return slow; + } + +} diff --git a/大厂刷题班/class34/Problem_0289_GameOfLife.java b/大厂刷题班/class34/Problem_0289_GameOfLife.java new file mode 100644 index 0000000..c986ef0 --- /dev/null +++ b/大厂刷题班/class34/Problem_0289_GameOfLife.java @@ -0,0 +1,43 @@ +package class34; + +// 有关这个游戏更有意思、更完整的内容: +// https://www.bilibili.com/video/BV1rJ411n7ri +// 也推荐这个up主 +public class Problem_0289_GameOfLife { + + public static void gameOfLife(int[][] board) { + int N = board.length; + int M = board[0].length; + for (int i = 0; i < N; i++) { + for (int j = 0; j < M; j++) { + int neighbors = neighbors(board, i, j); + if (neighbors == 3 || (board[i][j] == 1 && neighbors == 2)) { + board[i][j] |= 2; + } + } + } + for (int i = 0; i < N; i++) { + for (int j = 0; j < M; j++) { + board[i][j] >>= 1; + } + } + } + + // b[i][j] 这个位置的数,周围有几个1 + public static int neighbors(int[][] b, int i, int j) { + return f(b, i - 1, j - 1) + + f(b, i - 1, j) + + f(b, i - 1, j + 1) + + f(b, i, j - 1) + + f(b, i, j + 1) + + f(b, i + 1, j - 1) + + f(b, i + 1, j) + + f(b, i + 1, j + 1); + } + + // b[i][j] 上面有1,就返回1,上面不是1,就返回0 + public static int f(int[][] b, int i, int j) { + return (i >= 0 && i < b.length && j >= 0 && j < b[0].length && (b[i][j] & 1) == 1) ? 1 : 0; + } + +} diff --git a/大厂刷题班/class34/Problem_0295_FindMedianFromDataStream.java b/大厂刷题班/class34/Problem_0295_FindMedianFromDataStream.java new file mode 100644 index 0000000..4fa279f --- /dev/null +++ b/大厂刷题班/class34/Problem_0295_FindMedianFromDataStream.java @@ -0,0 +1,46 @@ +package class34; + +import java.util.PriorityQueue; + +public class Problem_0295_FindMedianFromDataStream { + + class MedianFinder { + + private PriorityQueue maxh; + private PriorityQueue minh; + + public MedianFinder() { + maxh = new PriorityQueue<>((a, b) -> b - a); + minh = new PriorityQueue<>((a, b) -> a - b); + } + + public void addNum(int num) { + if (maxh.isEmpty() || maxh.peek() >= num) { + maxh.add(num); + } else { + minh.add(num); + } + balance(); + } + + public double findMedian() { + if (maxh.size() == minh.size()) { + return (double) (maxh.peek() + minh.peek()) / 2; + } else { + return maxh.size() > minh.size() ? maxh.peek() : minh.peek(); + } + } + + private void balance() { + if (Math.abs(maxh.size() - minh.size()) == 2) { + if (maxh.size() > minh.size()) { + minh.add(maxh.poll()); + } else { + maxh.add(minh.poll()); + } + } + } + + } + +} \ No newline at end of file diff --git a/大厂刷题班/class34/Problem_0315_CountOfSmallerNumbersAfterSelf.java b/大厂刷题班/class34/Problem_0315_CountOfSmallerNumbersAfterSelf.java new file mode 100644 index 0000000..7cd8821 --- /dev/null +++ b/大厂刷题班/class34/Problem_0315_CountOfSmallerNumbersAfterSelf.java @@ -0,0 +1,69 @@ +package class34; + +import java.util.ArrayList; +import java.util.List; + +public class Problem_0315_CountOfSmallerNumbersAfterSelf { + + public static class Node { + public int value; + public int index; + + public Node(int v, int i) { + value = v; + index = i; + } + } + + public static List countSmaller(int[] nums) { + List ans = new ArrayList<>(); + if (nums == null) { + return ans; + } + for (int i = 0; i < nums.length; i++) { + ans.add(0); + } + if (nums.length < 2) { + return ans; + } + Node[] arr = new Node[nums.length]; + for (int i = 0; i < arr.length; i++) { + arr[i] = new Node(nums[i], i); + } + process(arr, 0, arr.length - 1, ans); + return ans; + } + + public static void process(Node[] arr, int l, int r, List ans) { + if (l == r) { + return; + } + int mid = l + ((r - l) >> 1); + process(arr, l, mid, ans); + process(arr, mid + 1, r, ans); + merge(arr, l, mid, r, ans); + } + + public static void merge(Node[] arr, int l, int m, int r, List ans) { + Node[] help = new Node[r - l + 1]; + int i = help.length - 1; + int p1 = m; + int p2 = r; + while (p1 >= l && p2 >= m + 1) { + if (arr[p1].value > arr[p2].value) { + ans.set(arr[p1].index, ans.get(arr[p1].index) + p2 - m); + } + help[i--] = arr[p1].value > arr[p2].value ? arr[p1--] : arr[p2--]; + } + while (p1 >= l) { + help[i--] = arr[p1--]; + } + while (p2 >= m + 1) { + help[i--] = arr[p2--]; + } + for (i = 0; i < help.length; i++) { + arr[l + i] = help[i]; + } + } + +} diff --git a/大厂刷题班/class34/Problem_0324_WiggleSortII.java b/大厂刷题班/class34/Problem_0324_WiggleSortII.java new file mode 100644 index 0000000..bd3d929 --- /dev/null +++ b/大厂刷题班/class34/Problem_0324_WiggleSortII.java @@ -0,0 +1,186 @@ +package class34; + +public class Problem_0324_WiggleSortII { + + // 时间复杂度O(N),额外空间复杂度O(1) + public static void wiggleSort(int[] nums) { + if (nums == null || nums.length < 2) { + return; + } + int N = nums.length; + // 小 中 右 + findIndexNum(nums, 0, nums.length - 1, N / 2); + if ((N & 1) == 0) { + // R L -> L R + shuffle(nums, 0, nums.length - 1); + // R1 L1 R2 L2 R3 L3 R4 L4 + // L4 R4 L3 R3 L2 R2 L1 R1 -> 代码中的方式,可以的! + // L1 R1 L2 R2 L3 R3 L4 R4 -> 课上的分析,是不行的!不能两两交换! + reverse(nums, 0, nums.length - 1); + // 做个实验,如果把上一行的code注释掉(reverse过程),然后跑下面注释掉的for循环代码 + // for循环的代码就是两两交换,会发现对数器报错,说明两两交换是不行的, 必须整体逆序 +// for (int i = 0; i < nums.length; i += 2) { +// swap(nums, i, i + 1); +// } + } else { + shuffle(nums, 1, nums.length - 1); + } + } + + public static int findIndexNum(int[] arr, int L, int R, int index) { + int pivot = 0; + int[] range = null; + while (L < R) { + pivot = arr[L + (int) (Math.random() * (R - L + 1))]; + range = partition(arr, L, R, pivot); + if (index >= range[0] && index <= range[1]) { + return arr[index]; + } else if (index < range[0]) { + R = range[0] - 1; + } else { + L = range[1] + 1; + } + } + return arr[L]; + } + + public static int[] partition(int[] arr, int L, int R, int pivot) { + int less = L - 1; + int more = R + 1; + int cur = L; + while (cur < more) { + if (arr[cur] < pivot) { + swap(arr, ++less, cur++); + } else if (arr[cur] > pivot) { + swap(arr, cur, --more); + } else { + cur++; + } + } + return new int[] { less + 1, more - 1 }; + } + + public static void shuffle(int[] nums, int l, int r) { + while (r - l + 1 > 0) { + int lenAndOne = r - l + 2; + int bloom = 3; + int k = 1; + while (bloom <= lenAndOne / 3) { + bloom *= 3; + k++; + } + int m = (bloom - 1) / 2; + int mid = (l + r) / 2; + rotate(nums, l + m, mid, mid + m); + cycles(nums, l - 1, bloom, k); + l = l + bloom - 1; + } + } + + public static void cycles(int[] nums, int base, int bloom, int k) { + for (int i = 0, trigger = 1; i < k; i++, trigger *= 3) { + int next = (2 * trigger) % bloom; + int cur = next; + int record = nums[next + base]; + int tmp = 0; + nums[next + base] = nums[trigger + base]; + while (cur != trigger) { + next = (2 * cur) % bloom; + tmp = nums[next + base]; + nums[next + base] = record; + cur = next; + record = tmp; + } + } + } + + public static void rotate(int[] arr, int l, int m, int r) { + reverse(arr, l, m); + reverse(arr, m + 1, r); + reverse(arr, l, r); + } + + public static void reverse(int[] arr, int l, int r) { + while (l < r) { + swap(arr, l++, r--); + } + } + + public static void swap(int[] nums, int i, int j) { + int tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + + // 为了测试,暴力方法 + // 把arr全排列尝试一遍, + // 其中任何一个排列能做到 < > < > ... 返回true; + // 如果没有任何一个排列能做到,返回false; + public static boolean test(int[] arr) { + return process(arr, 0); + } + + // 为了测试 + public static boolean process(int[] arr, int index) { + if (index == arr.length) { + return valid(arr); + } + for (int i = index; i < arr.length; i++) { + swap(arr, index, i); + if (process(arr, index + 1)) { + return true; + } + swap(arr, index, i); + } + return false; + } + + // 为了测试 + public static boolean valid(int[] arr) { + boolean more = true; + for (int i = 1; i < arr.length; i++) { + if ((more && arr[i - 1] >= arr[i]) || (!more && arr[i - 1] <= arr[i])) { + return false; + } + more = !more; + } + return true; + } + + // 为了测试 + public static int[] randomArray(int n, int v) { + int[] ans = new int[n]; + for (int i = 0; i < n; i++) { + ans[i] = (int) (Math.random() * v); + } + return ans; + } + + // 为了测试 + public static int[] copyArray(int[] arr) { + int[] ans = new int[arr.length]; + for (int i = 0; i < arr.length; i++) { + ans[i] = arr[i]; + } + return ans; + } + + // 为了测试 + public static void main(String[] args) { + int N = 10; + int V = 10; + int testTime = 10000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int n = (int) (Math.random() * N) + 1; + int[] arr1 = randomArray(n, V); + int[] arr2 = copyArray(arr1); + wiggleSort(arr1); + if (valid(arr1) != test(arr2)) { + System.out.println("出错了!"); + } + } + System.out.println("测试结束"); + } + +} diff --git a/大厂刷题班/class34/Problem_0326_PowerOfThree.java b/大厂刷题班/class34/Problem_0326_PowerOfThree.java new file mode 100644 index 0000000..48b59c1 --- /dev/null +++ b/大厂刷题班/class34/Problem_0326_PowerOfThree.java @@ -0,0 +1,14 @@ +package class34; + +public class Problem_0326_PowerOfThree { + + // 如果一个数字是3的某次幂,那么这个数一定只含有3这个质数因子 + // 1162261467是int型范围内,最大的3的幂,它是3的19次方 + // 这个1162261467只含有3这个质数因子,如果n也是只含有3这个质数因子,那么 + // 1162261467 % n == 0 + // 反之如果1162261467 % n != 0 说明n一定含有其他因子 + public static boolean isPowerOfThree(int n) { + return (n > 0 && 1162261467 % n == 0); + } + +} diff --git a/大厂刷题班/class34/Problem_0328_OddEvenLinkedList.java b/大厂刷题班/class34/Problem_0328_OddEvenLinkedList.java new file mode 100644 index 0000000..0718c20 --- /dev/null +++ b/大厂刷题班/class34/Problem_0328_OddEvenLinkedList.java @@ -0,0 +1,43 @@ +package class34; + +public class Problem_0328_OddEvenLinkedList { + + // 提交时不要提交这个类 + public static class ListNode { + int val; + ListNode next; + } + + public ListNode oddEvenList(ListNode head) { + ListNode firstOdd = null; + ListNode firstEven = null; + ListNode odd = null; + ListNode even = null; + ListNode next = null; + int count = 1; + while (head != null) { + next = head.next; + head.next = null; + if ((count & 1) == 1) { + firstOdd = firstOdd == null ? head : firstOdd; + if (odd != null) { + odd.next = head; + } + odd = head; + } else { + firstEven = firstEven == null ? head : firstEven; + if (even != null) { + even.next = head; + } + even = head; + } + count++; + head = next; + } + if (odd != null) { + odd.next = firstEven; + } + return firstOdd != null ? firstOdd : firstEven; + } + +} diff --git a/大厂刷题班/class34/Problem_0340_LongestSubstringWithAtMostKDistinctCharacters.java b/大厂刷题班/class34/Problem_0340_LongestSubstringWithAtMostKDistinctCharacters.java new file mode 100644 index 0000000..37124f5 --- /dev/null +++ b/大厂刷题班/class34/Problem_0340_LongestSubstringWithAtMostKDistinctCharacters.java @@ -0,0 +1,29 @@ +package class34; + +public class Problem_0340_LongestSubstringWithAtMostKDistinctCharacters { + + public static int lengthOfLongestSubstringKDistinct(String s, int k) { + if (s == null || s.length() == 0 || k < 1) { + return 0; + } + char[] str = s.toCharArray(); + int N = str.length; + int[] count = new int[256]; + int diff = 0; + int R = 0; + int ans = 0; + for (int i = 0; i < N; i++) { + // R 窗口的右边界 + while (R < N && (diff < k || (diff == k && count[str[R]] > 0))) { + diff += count[str[R]] == 0 ? 1 : 0; + count[str[R++]]++; + } + // R 来到违规的第一个位置 + ans = Math.max(ans, R - i); + diff -= count[str[i]] == 1 ? 1 : 0; + count[str[i]]--; + } + return ans; + } + +} diff --git a/大厂刷题班/class34/Problem_0341_FlattenNestedListIterator.java b/大厂刷题班/class34/Problem_0341_FlattenNestedListIterator.java new file mode 100644 index 0000000..a5bc724 --- /dev/null +++ b/大厂刷题班/class34/Problem_0341_FlattenNestedListIterator.java @@ -0,0 +1,110 @@ +package class34; + +import java.util.Iterator; +import java.util.List; +import java.util.Stack; + +public class Problem_0341_FlattenNestedListIterator { + + // 不要提交这个接口类 + public interface NestedInteger { + + // @return true if this NestedInteger holds a single integer, rather than a + // nested list. + public boolean isInteger(); + + // @return the single integer that this NestedInteger holds, if it holds a + // single integer + // Return null if this NestedInteger holds a nested list + public Integer getInteger(); + + // @return the nested list that this NestedInteger holds, if it holds a nested + // list + // Return null if this NestedInteger holds a single integer + public List getList(); + } + + public class NestedIterator implements Iterator { + + private List list; + private Stack stack; + private boolean used; + + public NestedIterator(List nestedList) { + list = nestedList; + stack = new Stack<>(); + stack.push(-1); + used = true; + hasNext(); + } + + @Override + public Integer next() { + Integer ans = null; + if (!used) { + ans = get(list, stack); + used = true; + hasNext(); + } + return ans; + } + + @Override + public boolean hasNext() { + if (stack.isEmpty()) { + return false; + } + if (!used) { + return true; + } + if (findNext(list, stack)) { + used = false; + } + return !used; + } + + private Integer get(List nestedList, Stack stack) { + int index = stack.pop(); + Integer ans = null; + if (!stack.isEmpty()) { + ans = get(nestedList.get(index).getList(), stack); + } else { + ans = nestedList.get(index).getInteger(); + } + stack.push(index); + return ans; + } + + private boolean findNext(List nestedList, Stack stack) { + int index = stack.pop(); + if (!stack.isEmpty() && findNext(nestedList.get(index).getList(), stack)) { + stack.push(index); + return true; + } + for (int i = index + 1; i < nestedList.size(); i++) { + if (pickFirst(nestedList.get(i), i, stack)) { + return true; + } + } + return false; + } + + private boolean pickFirst(NestedInteger nested, int position, Stack stack) { + if (nested.isInteger()) { + stack.add(position); + return true; + } else { + List actualList = nested.getList(); + for (int i = 0; i < actualList.size(); i++) { + if (pickFirst(actualList.get(i), i, stack)) { + stack.add(position); + return true; + } + } + } + return false; + } + + } + +} diff --git a/大厂刷题班/class34/Problem_0348_DesignTicTacToe.java b/大厂刷题班/class34/Problem_0348_DesignTicTacToe.java new file mode 100644 index 0000000..58f4851 --- /dev/null +++ b/大厂刷题班/class34/Problem_0348_DesignTicTacToe.java @@ -0,0 +1,47 @@ +package class34; + +public class Problem_0348_DesignTicTacToe { + + class TicTacToe { + private int[][] rows; + private int[][] cols; + private int[] leftUp; + private int[] rightUp; + private boolean[][] matrix; + private int N; + + public TicTacToe(int n) { + // rows[a][1] : 1这个人,在a行上,下了几个 + // rows[b][2] : 2这个人,在b行上,下了几个 + rows = new int[n][3]; //0 1 2 + cols = new int[n][3]; + // leftUp[2] = 7 : 2这个人,在左对角线上,下了7个 + leftUp = new int[3]; + // rightUp[1] = 9 : 1这个人,在右对角线上,下了9个 + rightUp = new int[3]; + matrix = new boolean[n][n]; + N = n; + } + + public int move(int row, int col, int player) { + if (matrix[row][col]) { + return 0; + } + matrix[row][col] = true; + rows[row][player]++; + cols[col][player]++; + if (row == col) { + leftUp[player]++; + } + if (row + col == N - 1) { + rightUp[player]++; + } + if (rows[row][player] == N || cols[col][player] == N || leftUp[player] == N || rightUp[player] == N) { + return player; + } + return 0; + } + + } + +} diff --git a/大厂刷题班/class34/Problem_0380_InsertDeleteGetRandom.java b/大厂刷题班/class34/Problem_0380_InsertDeleteGetRandom.java new file mode 100644 index 0000000..2a81aca --- /dev/null +++ b/大厂刷题班/class34/Problem_0380_InsertDeleteGetRandom.java @@ -0,0 +1,52 @@ +package class34; + +import java.util.HashMap; + +// 测试链接 : https://leetcode.com/problems/insert-delete-getrandom-o1/ +public class Problem_0380_InsertDeleteGetRandom { + + public class RandomizedSet { + + private HashMap keyIndexMap; + private HashMap indexKeyMap; + private int size; + + public RandomizedSet() { + keyIndexMap = new HashMap(); + indexKeyMap = new HashMap(); + size = 0; + } + + public boolean insert(int val) { + if (!keyIndexMap.containsKey(val)) { + keyIndexMap.put(val, size); + indexKeyMap.put(size++, val); + return true; + } + return false; + } + + public boolean remove(int val) { + if (keyIndexMap.containsKey(val)) { + int deleteIndex = keyIndexMap.get(val); + int lastIndex = --size; + int lastKey = indexKeyMap.get(lastIndex); + keyIndexMap.put(lastKey, deleteIndex); + indexKeyMap.put(deleteIndex, lastKey); + keyIndexMap.remove(val); + indexKeyMap.remove(lastIndex); + return true; + } + return false; + } + + public int getRandom() { + if (size == 0) { + return -1; + } + int randomIndex = (int) (Math.random() * size); + return indexKeyMap.get(randomIndex); + } + } + +} diff --git a/大厂刷题班/class34/Problem_0384_ShuffleAnArray.java b/大厂刷题班/class34/Problem_0384_ShuffleAnArray.java new file mode 100644 index 0000000..dfb0e0b --- /dev/null +++ b/大厂刷题班/class34/Problem_0384_ShuffleAnArray.java @@ -0,0 +1,34 @@ +package class34; + +public class Problem_0384_ShuffleAnArray { + + class Solution { + private int[] origin; + private int[] shuffle; + private int N; + + public Solution(int[] nums) { + origin = nums; + N = nums.length; + shuffle = new int[N]; + for (int i = 0; i < N; i++) { + shuffle[i] = origin[i]; + } + } + + public int[] reset() { + return origin; + } + + public int[] shuffle() { + for (int i = N - 1; i >= 0; i--) { + int r = (int) (Math.random() * (i + 1)); + int tmp = shuffle[r]; + shuffle[r] = shuffle[i]; + shuffle[i] = tmp; + } + return shuffle; + } + } + +} diff --git a/大厂刷题班/class34/说明 b/大厂刷题班/class34/说明 new file mode 100644 index 0000000..c23f435 --- /dev/null +++ b/大厂刷题班/class34/说明 @@ -0,0 +1,30 @@ +leetcode高频题 +leetcode全题目列表 : https://leetcode.com/problemset/all/ +在全题目列表的右侧栏,点击Top Interview Questions +或者直接进入右侧链接 : https://leetcode.com/problemset/all/?listId=wpwgkgt +即可看到leetcode高频题全列表 +本节课解决leetcode高频题列表中的如下题目 : +0287 : 大厂刷题班, 第34节, 本节 +0289 : 大厂刷题班, 第34节, 本节 +0295 : 大厂刷题班, 第34节, 本节 +0297 : 体系学习班, 第11节第2题 +0300 : 大厂刷题班, 第9节第3题 +0308 : 体系学习班, 第32节第2题 +0309 : 大厂刷题班, 第15节第5题 +0315 : 大厂刷题班, 第34节, 本节 +0322 : 体系学习班, 硬币找零专题 : 第21节第2、3、4题, 第22节第2题, 第24节第4题 +0324 : 大厂刷题班, 第34节, 本节 +0326 : 大厂刷题班, 第34节, 本节 +0328 : 大厂刷题班, 第34节, 本节 +0329 : 大厂刷题班, 第1节第5题 +0334 : 大厂刷题班, 第9节第3题的变形, 问 : 最长递增子序列长度能否超过2而已, 跳过 +0340 : 大厂刷题班, 第34节, 本节 +0341 : 大厂刷题班, 第34节, 本节 +0344 : 太简单了, 跳过 +0348 : 大厂刷题班, 第34节, 本节 +0350 : 太简单了, 跳过 +0371 : 新手班, 第5节第3题 +0378 : 大厂刷题班, 第17节第2题 +0380 : 大厂刷题班, 第34节, 本节 +0384 : 大厂刷题班, 第34节, 本节 +0387 : 太简单了, 跳过 \ No newline at end of file diff --git a/大厂刷题班/class35/Code01_StringKth.java b/大厂刷题班/class35/Code01_StringKth.java new file mode 100644 index 0000000..9ed97dc --- /dev/null +++ b/大厂刷题班/class35/Code01_StringKth.java @@ -0,0 +1,96 @@ +package class35; + +import java.util.ArrayList; +import java.util.List; + +// 给定一个长度len,表示一共有几位 +// 所有字符都是小写(a~z),可以生成长度为1,长度为2, +// 长度为3...长度为len的所有字符串 +// 如果把所有字符串根据字典序排序,每个字符串都有所在的位置。 +// 给定一个字符串str,给定len,请返回str是总序列中的第几个 +// 比如len = 4,字典序的前几个字符串为: +// a aa aaa aaaa aaab ... aaaz ... azzz b ba baa baaa ... bzzz c ... +// a是这个序列中的第1个,bzzz是这个序列中的第36558个 +public class Code01_StringKth { + // 思路: + // cdb,总共长度为7,请问cdb是第几个? + // 第一位c : + // 以a开头,剩下长度为(0~6)的所有可能性有几个 + // + + // 以b开头,剩下长度为(0~6)的所有可能性有几个 + // + + // 以c开头,剩下长度为(0)的所有可能性有几个 + // 第二位d : + // + + // 以ca开头的情况下,剩下长度为(0~5)的所有可能性有几个 + // + + // 以cb开头的情况下,剩下长度为(0~5)的所有可能性有几个 + // + + // 以cc开头的情况下,剩下长度为(0~5)的所有可能性有几个 + // + + // 以cd开头的情况下,剩下长度为(0)的所有可能性有几个 + // 第三位b + // + + // 以cda开头的情况下,剩下长度为(0~4)的所有可能性有几个 + // + + // 以cdb开头的情况下,剩下长度为(0)的所有可能性有几个 + public static int kth(String s, int len) { + if (s == null || s.length() == 0 || s.length() > len) { + return -1; + } + char[] num = s.toCharArray(); + int ans = 0; + for (int i = 0, rest = len - 1; i < num.length; i++, rest--) { + ans += (num[i] - 'a') * f(rest) + 1; + } + return ans; + } + + // 不管以什么开头,剩下长度为(0~len)的所有可能性有几个 + public static int f(int len) { + int ans = 1; + for (int i = 1, base = 26; i <= len; i++, base *= 26) { + ans += base; + } + return ans; + } + + // 为了测试 + public static List all(int len) { + List ans = new ArrayList<>(); + for (int i = 1; i <= len; i++) { + char[] path = new char[i]; + process(path, 0, ans); + } + return ans; + } + + // 为了测试 + public static void process(char[] path, int index, List ans) { + if (index == path.length) { + ans.add(String.valueOf(path)); + } else { + for (char c = 'a'; c <= 'z'; c++) { + path[index] = c; + process(path, index + 1, ans); + } + } + } + + public static void main(String[] args) { + int len = 4; + // 暴力方法得到所有字符串 + List ans = all(len); + // 根据字典序排序,所有字符串都在其中 + ans.sort((a, b) -> a.compareTo(b)); + + String test = "bzzz"; + // 根据我们的方法算出test是第几个? + // 注意我们算出的第几个,是从1开始的 + // 而下标是从0开始的,所以变成index,还需要-1 + int index = kth(test, len) - 1; + // 验证 + System.out.println(ans.get(index)); + } + +} diff --git a/大厂刷题班/class35/Code02_MagicStone.java b/大厂刷题班/class35/Code02_MagicStone.java new file mode 100644 index 0000000..9e77c80 --- /dev/null +++ b/大厂刷题班/class35/Code02_MagicStone.java @@ -0,0 +1,49 @@ +package class35; + +import java.util.Arrays; + +// 来自小红书 +// [0,4,7] : 0表示这里石头没有颜色,如果变红代价是4,如果变蓝代价是7 +// [1,X,X] : 1表示这里石头已经是红,而且不能改颜色,所以后两个数X无意义 +// [2,X,X] : 2表示这里石头已经是蓝,而且不能改颜色,所以后两个数X无意义 +// 颜色只可能是0、1、2,代价一定>=0 +// 给你一批这样的小数组,要求最后必须所有石头都有颜色,且红色和蓝色一样多,返回最小代价 +// 如果怎么都无法做到所有石头都有颜色、且红色和蓝色一样多,返回-1 +public class Code02_MagicStone { + + public static int minCost(int[][] stones) { + int n = stones.length; + if ((n & 1) != 0) { + return -1; + } + Arrays.sort(stones, (a, b) -> a[0] == 0 && b[0] == 0 ? (b[1] - b[2] - a[1] + a[2]) : (a[0] - b[0])); + int zero = 0; + int red = 0; + int blue = 0; + int cost = 0; + for (int i = 0; i < n; i++) { + if (stones[i][0] == 0) { + zero++; + cost += stones[i][1]; + } else if (stones[i][0] == 1) { + red++; + } else { + blue++; + } + } + if (red > (n >> 1) || blue > (n >> 1)) { + return -1; + } + blue = zero - ((n >> 1) - red); + for (int i = 0; i < blue; i++) { + cost += stones[i][2] - stones[i][1]; + } + return cost; + } + + public static void main(String[] args) { + int[][] stones = { { 1, 5, 3 }, { 2, 7, 9 }, { 0, 6, 4 }, { 0, 7, 9 }, { 0, 2, 1 }, { 0, 5, 9 } }; + System.out.println(minCost(stones)); + } + +} diff --git a/大厂刷题班/class35/Code03_WatchMovieMaxTime.java b/大厂刷题班/class35/Code03_WatchMovieMaxTime.java new file mode 100644 index 0000000..137845c --- /dev/null +++ b/大厂刷题班/class35/Code03_WatchMovieMaxTime.java @@ -0,0 +1,136 @@ +package class35; + +import java.util.Arrays; + +// 来自小红书 +// 一场电影开始和结束时间可以用一个小数组来表示["07:30","12:00"] +// 已知有2000场电影开始和结束都在同一天,这一天从00:00开始到23:59结束 +// 一定要选3场完全不冲突的电影来观看,返回最大的观影时间 +// 如果无法选出3场完全不冲突的电影来观看,返回-1 +public class Code03_WatchMovieMaxTime { + + // 暴力方法,枚举前三场所有的可能全排列 + public static int maxEnjoy1(int[][] movies) { + if (movies.length < 3) { + return -1; + } + return process1(movies, 0); + } + + public static int process1(int[][] movies, int index) { + if (index == 3) { + int start = 0; + int watch = 0; + for (int i = 0; i < 3; i++) { + if (start > movies[i][0]) { + return -1; + } + watch += movies[i][1] - movies[i][0]; + start = movies[i][1]; + } + return watch; + } else { + int ans = -1; + for (int i = index; i < movies.length; i++) { + swap(movies, index, i); + ans = Math.max(ans, process1(movies, index + 1)); + swap(movies, index, i); + } + return ans; + } + } + + public static void swap(int[][] movies, int i, int j) { + int[] tmp = movies[i]; + movies[i] = movies[j]; + movies[j] = tmp; + } + + // 优化后的递归解 + public static int maxEnjoy2(int[][] movies) { + Arrays.sort(movies, (a, b) -> a[0] != b[0] ? (a[0] - b[0]) : (a[1] - b[1])); + return process2(movies, 0, 0, 3); + } + + public static int process2(int[][] movies, int index, int time, int rest) { + if (index == movies.length) { + return rest == 0 ? 0 : -1; + } + int p1 = process2(movies, index + 1, time, rest); + int next = movies[index][0] >= time && rest > 0 ? process2(movies, index + 1, movies[index][1], rest - 1) : -1; + int p2 = next != -1 ? (movies[index][1] - movies[index][0] + next) : -1; + return Math.max(p1, p2); + } + + // 记忆化搜索的动态规划 + public static int maxEnjoy3(int[][] movies) { + Arrays.sort(movies, (a, b) -> a[0] != b[0] ? (a[0] - b[0]) : (a[1] - b[1])); + + int max = 0; + for (int[] movie : movies) { + max = Math.max(max, movie[1]); + } + + int[][][] dp = new int[movies.length][max + 1][4]; + for (int i = 0; i < movies.length; i++) { + for (int j = 0; j <= max; j++) { + for (int k = 0; k <= 3; k++) { + dp[i][j][k] = -2;// 用-2代表没算过这个过程 + } + } + } + return process3(movies, 0, 0, 3, dp); + } + + public static int process3(int[][] movies, int index, int time, int rest, int[][][] dp) { + if (index == movies.length) { + return rest == 0 ? 0 : -1; + } + if (dp[index][time][rest] != -2) { + return dp[index][time][rest]; + } + int p1 = process3(movies, index + 1, time, rest, dp); + int next = movies[index][0] >= time && rest > 0 ? process3(movies, index + 1, movies[index][1], rest - 1, dp) + : -1; + int p2 = next != -1 ? (movies[index][1] - movies[index][0] + next) : -1; + int ans = Math.max(p1, p2); + dp[index][time][rest] = ans; + return ans; + } + + // 为了测试 + public static int[][] randomMovies(int len, int time) { + int[][] movies = new int[len][2]; + for (int i = 0; i < len; i++) { + int a = (int) (Math.random() * time); + int b = (int) (Math.random() * time); + movies[i][0] = Math.min(a, b); + movies[i][1] = Math.max(a, b); + } + return movies; + } + + public static void main(String[] args) { + int n = 10; + int t = 20; + int testTime = 10000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int len = (int) (Math.random() * n) + 1; + int[][] movies = randomMovies(len, t); + int ans1 = maxEnjoy1(movies); + int ans2 = maxEnjoy2(movies); + int ans3 = maxEnjoy3(movies); + if (ans1 != ans2 || ans1 != ans3) { + for (int[] m : movies) { + System.out.println(m[0] + " , " + m[1]); + } + System.out.println(ans1); + System.out.println(ans2); + System.out.println("出错了"); + } + } + System.out.println("测试结束"); + } + +} diff --git a/大厂刷题班/class35/Code04_WalkToEnd.java b/大厂刷题班/class35/Code04_WalkToEnd.java new file mode 100644 index 0000000..50ba235 --- /dev/null +++ b/大厂刷题班/class35/Code04_WalkToEnd.java @@ -0,0 +1,53 @@ +package class35; + +import java.util.PriorityQueue; + +// 来自网易 +// map[i][j] == 0,代表(i,j)是海洋,渡过的话代价是2 +// map[i][j] == 1,代表(i,j)是陆地,渡过的话代价是1 +// map[i][j] == 2,代表(i,j)是障碍,无法渡过 +// 每一步上、下、左、右都能走,返回从左上角走到右下角最小代价是多少,如果无法到达返回-1 +public class Code04_WalkToEnd { + + public static int minCost(int[][] map) { + if (map[0][0] == 2) { + return -1; + } + int n = map.length; + int m = map[0].length; + PriorityQueue heap = new PriorityQueue<>((a, b) -> a.cost - b.cost); + boolean[][] visited = new boolean[n][m]; + add(map, 0, 0, 0, heap, visited); + while (!heap.isEmpty()) { + Node cur = heap.poll(); + if (cur.row == n - 1 && cur.col == m - 1) { + return cur.cost; + } + add(map, cur.row - 1, cur.col, cur.cost, heap, visited); + add(map, cur.row + 1, cur.col, cur.cost, heap, visited); + add(map, cur.row, cur.col - 1, cur.cost, heap, visited); + add(map, cur.row, cur.col + 1, cur.cost, heap, visited); + } + return -1; + } + + public static void add(int[][] m, int i, int j, int pre, PriorityQueue heap, boolean[][] visited) { + if (i >= 0 && i < m.length && j >= 0 && j < m[0].length && m[i][j] != 2 && !visited[i][j]) { + heap.add(new Node(i, j, pre + (m[i][j] == 0 ? 2 : 1))); + visited[i][j] = true; + } + } + + public static class Node { + public int row; + public int col; + public int cost; + + public Node(int a, int b, int c) { + row = a; + col = b; + cost = c; + } + } + +} diff --git a/大厂刷题班/class35/Code05_CircleCandy.java b/大厂刷题班/class35/Code05_CircleCandy.java new file mode 100644 index 0000000..e027a13 --- /dev/null +++ b/大厂刷题班/class35/Code05_CircleCandy.java @@ -0,0 +1,58 @@ +package class35; + +// 来自网易 +// 给定一个正数数组arr,表示每个小朋友的得分 +// 任何两个相邻的小朋友,如果得分一样,怎么分糖果无所谓,但如果得分不一样,分数大的一定要比分数少的多拿一些糖果 +// 假设所有的小朋友坐成一个环形,返回在不破坏上一条规则的情况下,需要的最少糖果数 +public class Code05_CircleCandy { + + public static int minCandy(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + if (arr.length == 1) { + return 1; + } + int n = arr.length; + int minIndex = 0; + for (int i = 0; i < n; i++) { + if (arr[i] <= arr[lastIndex(i, n)] && arr[i] <= arr[nextIndex(i, n)]) { + minIndex = i; + break; + } + } + int[] nums = new int[n + 1]; + for (int i = 0; i <= n; i++, minIndex = nextIndex(minIndex, n)) { + nums[i] = arr[minIndex]; + } + int[] left = new int[n + 1]; + left[0] = 1; + for (int i = 1; i <= n; i++) { + left[i] = nums[i] > nums[i - 1] ? (left[i - 1] + 1) : 1; + } + int[] right = new int[n + 1]; + right[n] = 1; + for (int i = n - 1; i >= 0; i--) { + right[i] = nums[i] > nums[i + 1] ? (right[i + 1] + 1) : 1; + } + int ans = 0; + for (int i = 0; i < n; i++) { + ans += Math.max(left[i], right[i]); + } + return ans; + } + + public static int nextIndex(int i, int n) { + return i == n - 1 ? 0 : (i + 1); + } + + public static int lastIndex(int i, int n) { + return i == 0 ? (n - 1) : (i - 1); + } + + public static void main(String[] args) { + int[] arr = { 3, 4, 2, 3, 2 }; + System.out.println(minCandy(arr)); + } + +} diff --git a/大厂刷题班/class35/Problem_0347_TopKFrequentElements.java b/大厂刷题班/class35/Problem_0347_TopKFrequentElements.java new file mode 100644 index 0000000..656e584 --- /dev/null +++ b/大厂刷题班/class35/Problem_0347_TopKFrequentElements.java @@ -0,0 +1,54 @@ +package class35; + +import java.util.Comparator; +import java.util.HashMap; +import java.util.PriorityQueue; + +public class Problem_0347_TopKFrequentElements { + + public static class Node { + public int num; + public int count; + + public Node(int k) { + num = k; + count = 1; + } + } + + public static class CountComparator implements Comparator { + + @Override + public int compare(Node o1, Node o2) { + return o1.count - o2.count; + } + + } + + public static int[] topKFrequent(int[] nums, int k) { + HashMap map = new HashMap<>(); + for (int num : nums) { + if (!map.containsKey(num)) { + map.put(num, new Node(num)); + } else { + map.get(num).count++; + } + } + PriorityQueue heap = new PriorityQueue<>(new CountComparator()); + for (Node node : map.values()) { + if (heap.size() < k || (heap.size() == k && node.count > heap.peek().count)) { + heap.add(node); + } + if (heap.size() > k) { + heap.poll(); + } + } + int[] ans = new int[k]; + int index = 0; + while (!heap.isEmpty()) { + ans[index++] = heap.poll().num; + } + return ans; + } + +} diff --git a/大厂刷题班/class35/Problem_0395_LongestSubstringWithAtLeastKRepeatingCharacters.java b/大厂刷题班/class35/Problem_0395_LongestSubstringWithAtLeastKRepeatingCharacters.java new file mode 100644 index 0000000..9298e38 --- /dev/null +++ b/大厂刷题班/class35/Problem_0395_LongestSubstringWithAtLeastKRepeatingCharacters.java @@ -0,0 +1,110 @@ +package class35; + +public class Problem_0395_LongestSubstringWithAtLeastKRepeatingCharacters { + + public static int longestSubstring1(String s, int k) { + char[] str = s.toCharArray(); + int N = str.length; + int max = 0; + for (int i = 0; i < N; i++) { + int[] count = new int[256]; + int collect = 0; + int satisfy = 0; + for (int j = i; j < N; j++) { + if (count[str[j]] == 0) { + collect++; + } + if (count[str[j]] == k - 1) { + satisfy++; + } + count[str[j]]++; + if (collect == satisfy) { + max = Math.max(max, j - i + 1); + } + } + } + return max; + } + + public static int longestSubstring2(String s, int k) { + char[] str = s.toCharArray(); + int N = str.length; + int max = 0; + for (int require = 1; require <= 26; require++) { + // 3种 + // a~z 出现次数 + int[] count = new int[26]; + // 目前窗口内收集了几种字符了 + int collect = 0; + // 目前窗口内出现次数>=k次的字符,满足了几种 + int satisfy = 0; + // 窗口右边界 + int R = -1; + for (int L = 0; L < N; L++) { // L要尝试每一个窗口的最左位置 + // [L..R] R+1 + while (R + 1 < N && !(collect == require && count[str[R + 1] - 'a'] == 0)) { + R++; + if (count[str[R] - 'a'] == 0) { + collect++; + } + if (count[str[R] - 'a'] == k - 1) { + satisfy++; + } + count[str[R] - 'a']++; + } + // [L...R] + if (satisfy == require) { + max = Math.max(max, R - L + 1); + } + // L++ + if (count[str[L] - 'a'] == 1) { + collect--; + } + if (count[str[L] - 'a'] == k) { + satisfy--; + } + count[str[L] - 'a']--; + } + } + return max; + } + + // 会超时,但是思路的确是正确的 + public static int longestSubstring3(String s, int k) { + return process(s.toCharArray(), 0, s.length() - 1, k); + } + + public static int process(char[] str, int L, int R, int k) { + if (L > R) { + return 0; + } + int[] counts = new int[26]; + for (int i = L; i <= R; i++) { + counts[str[i] - 'a']++; + } + char few = 0; + int min = Integer.MAX_VALUE; + for (int i = 0; i < 26; i++) { + if (counts[i] != 0 && min > counts[i]) { + few = (char) (i + 'a'); + min = counts[i]; + } + } + if (min >= k) { + return R - L + 1; + } + int pre = 0; + int max = Integer.MIN_VALUE; + for (int i = L; i <= R; i++) { + if (str[i] == few) { + max = Math.max(max, process(str, pre, i - 1, k)); + pre = i + 1; + } + } + if (pre != R + 1) { + max = Math.max(max, process(str, pre, R, k)); + } + return max; + } + +} diff --git a/大厂刷题班/class35/Problem_0412_FizzBuzz.java b/大厂刷题班/class35/Problem_0412_FizzBuzz.java new file mode 100644 index 0000000..82268d9 --- /dev/null +++ b/大厂刷题班/class35/Problem_0412_FizzBuzz.java @@ -0,0 +1,24 @@ +package class35; + +import java.util.ArrayList; +import java.util.List; + +public class Problem_0412_FizzBuzz { + + public static List fizzBuzz(int n) { + ArrayList ans = new ArrayList<>(); + for (int i = 1; i <= n; i++) { + if (i % 15 == 0) { + ans.add("FizzBuzz"); + } else if (i % 5 == 0) { + ans.add("Buzz"); + } else if (i % 3 == 0) { + ans.add("Fizz"); + } else { + ans.add(String.valueOf(i)); + } + } + return ans; + } + +} diff --git a/大厂刷题班/class35/Problem_0454_4SumII.java b/大厂刷题班/class35/Problem_0454_4SumII.java new file mode 100644 index 0000000..809fdfa --- /dev/null +++ b/大厂刷题班/class35/Problem_0454_4SumII.java @@ -0,0 +1,32 @@ +package class35; + +import java.util.HashMap; + +public class Problem_0454_4SumII { + + public static int fourSumCount(int[] A, int[] B, int[] C, int[] D) { + HashMap map = new HashMap<>(); + int sum = 0; + for (int i = 0; i < A.length; i++) { + for (int j = 0; j < B.length; j++) { + sum = A[i] + B[j]; + if (!map.containsKey(sum)) { + map.put(sum, 1); + } else { + map.put(sum, map.get(sum) + 1); + } + } + } + int ans = 0; + for (int i = 0; i < C.length; i++) { + for (int j = 0; j < D.length; j++) { + sum = C[i] + D[j]; + if (map.containsKey(-sum)) { + ans += map.get(-sum); + } + } + } + return ans; + } + +} diff --git a/大厂刷题班/class35/Problem_0673_NumberOfLongestIncreasingSubsequence.java b/大厂刷题班/class35/Problem_0673_NumberOfLongestIncreasingSubsequence.java new file mode 100644 index 0000000..72d76b2 --- /dev/null +++ b/大厂刷题班/class35/Problem_0673_NumberOfLongestIncreasingSubsequence.java @@ -0,0 +1,90 @@ +package class35; + +import java.util.ArrayList; +import java.util.TreeMap; + +public class Problem_0673_NumberOfLongestIncreasingSubsequence { + + // 好理解的方法,时间复杂度O(N^2) + public static int findNumberOfLIS1(int[] nums) { + if (nums == null || nums.length == 0) { + return 0; + } + int n = nums.length; + int[] lens = new int[n]; + int[] cnts = new int[n]; + lens[0] = 1; + cnts[0] = 1; + int maxLen = 1; + int allCnt = 1; + for (int i = 1; i < n; i++) { + int preLen = 0; + int preCnt = 1; + for (int j = 0; j < i; j++) { + if (nums[j] >= nums[i] || preLen > lens[j]) { + continue; + } + if (preLen < lens[j]) { + preLen = lens[j]; + preCnt = cnts[j]; + } else { + preCnt += cnts[j]; + } + } + lens[i] = preLen + 1; + cnts[i] = preCnt; + if (maxLen < lens[i]) { + maxLen = lens[i]; + allCnt = cnts[i]; + } else if (maxLen == lens[i]) { + allCnt += cnts[i]; + } + } + return allCnt; + } + + // 优化后的最优解,时间复杂度O(N*logN) + public static int findNumberOfLIS2(int[] nums) { + if (nums == null || nums.length == 0) { + return 0; + } + ArrayList> dp = new ArrayList<>(); + int len = 0; + int cnt = 0; + for (int num : nums) { + // num之前的长度,num到哪个长度len+1 + len = search(dp, num); + // cnt : 最终要去加底下的记录,才是应该填入的value + if (len == 0) { + cnt = 1; + } else { + TreeMap p = dp.get(len - 1); + cnt = p.firstEntry().getValue() - (p.ceilingKey(num) != null ? p.get(p.ceilingKey(num)) : 0); + } + if (len == dp.size()) { + dp.add(new TreeMap()); + dp.get(len).put(num, cnt); + } else { + dp.get(len).put(num, dp.get(len).firstEntry().getValue() + cnt); + } + } + return dp.get(dp.size() - 1).firstEntry().getValue(); + } + + // 二分查找,返回>=num最左的位置 + public static int search(ArrayList> dp, int num) { + int l = 0, r = dp.size() - 1, m = 0; + int ans = dp.size(); + while (l <= r) { + m = (l + r) / 2; + if (dp.get(m).firstKey() >= num) { + ans = m; + r = m - 1; + } else { + l = m + 1; + } + } + return ans; + } + +} diff --git a/大厂刷题班/class35/Problem_0687_LongestUnivaluePath.java b/大厂刷题班/class35/Problem_0687_LongestUnivaluePath.java new file mode 100644 index 0000000..c4466ed --- /dev/null +++ b/大厂刷题班/class35/Problem_0687_LongestUnivaluePath.java @@ -0,0 +1,62 @@ +package class35; + +public class Problem_0687_LongestUnivaluePath { + + public static class TreeNode { + public int val; + public TreeNode left; + public TreeNode right; + + public TreeNode(int v) { + val = v; + } + } + + public static int longestUnivaluePath(TreeNode root) { + if (root == null) { + return 0; + } + return process(root).max - 1; + } + + // 建设以x节点为头的树,返回两个信息 + public static class Info { + // 在一条路径上:要求每个节点通过且只通过一遍 + public int len; // 路径必须从x出发且只能往下走的情况下,路径的最大距离 + public int max; // 路径不要求必须从x出发的情况下,整棵树的合法路径最大距离 + + public Info(int l, int m) { + len = l; + max = m; + } + } + + private static Info process(TreeNode x) { + if (x == null) { + return new Info(0, 0); + } + TreeNode l = x.left; + TreeNode r = x.right; + // 左树上,不要求从左孩子出发,最大路径 + // 左树上,必须从左孩子出发,往下的最大路径 + Info linfo = process(l); + // 右树上,不要求从右孩子出发,最大路径 + // 右树上,必须从右孩子出发,往下的最大路径 + Info rinfo = process(r); + // 必须从x出发的情况下,往下的最大路径 + int len = 1; + if (l != null && l.val == x.val) { + len = linfo.len + 1; + } + if (r != null && r.val == x.val) { + len = Math.max(len, rinfo.len + 1); + } + // 不要求从x出发,最大路径 + int max = Math.max(Math.max(linfo.max, rinfo.max), len); + if (l != null && r != null && l.val == x.val && r.val == x.val) { + max = Math.max(max, linfo.len + rinfo.len + 1); + } + return new Info(len, max); + } + +} diff --git a/大厂刷题班/class35/说明 b/大厂刷题班/class35/说明 new file mode 100644 index 0000000..0c4a832 --- /dev/null +++ b/大厂刷题班/class35/说明 @@ -0,0 +1,21 @@ +leetcode高频题 +leetcode全题目列表 : https://leetcode.com/problemset/all/ +在全题目列表的右侧栏,点击Top Interview Questions +或者直接进入右侧链接 : https://leetcode.com/problemset/all/?listId=wpwgkgt +即可看到leetcode高频题全列表 +本节课解决leetcode高频题列表中的如下题目 : +0347 : 大厂刷题班, 第35节, 本节 +0395 : 大厂刷题班, 第35节, 本节 +0412 : 大厂刷题班, 第35节, 本节 +0454 : 大厂刷题班, 第35节, 本节 +0673 : 大厂刷题班, 第35节, 本节 +0687 : 大厂刷题班, 第35节, 本节 +0772 : 大厂刷题班, 第8节第1题 +至此,Leetcode高频题系列完结 + +本节附加题 +Code01 : 2021年8月大厂真实笔试题 +Code02 : 2021年8月大厂真实笔试题 +Code03 : 2021年8月大厂真实笔试题 +Code04 : 2021年8月大厂真实笔试题 +Code05 : 2021年8月大厂真实笔试题 \ No newline at end of file diff --git a/大厂刷题班/class36/Code01_ReverseInvertString.java b/大厂刷题班/class36/Code01_ReverseInvertString.java new file mode 100644 index 0000000..395b64a --- /dev/null +++ b/大厂刷题班/class36/Code01_ReverseInvertString.java @@ -0,0 +1,108 @@ +package class36; + +// 来自网易 +// 规定:L[1]对应a,L[2]对应b,L[3]对应c,...,L[25]对应y +// S1 = a +// S(i) = S(i-1) + L[i] + reverse(invert(S(i-1))); +// 解释invert操作: +// S1 = a +// S2 = aby +// 假设invert(S(2)) = 甲乙丙 +// a + 甲 = 26, 那么 甲 = 26 - 1 = 25 -> y +// b + 乙 = 26, 那么 乙 = 26 - 2 = 24 -> x +// y + 丙 = 26, 那么 丙 = 26 - 25 = 1 -> a +// 如上就是每一位的计算方式,所以invert(S2) = yxa +// 所以S3 = S2 + L[3] + reverse(invert(S2)) = aby + c + axy = abycaxy +// invert(abycaxy) = yxawyba, 再reverse = abywaxy +// 所以S4 = abycaxy + d + abywaxy = abycaxydabywaxy +// 直到S25结束 +// 给定两个参数n和k,返回Sn的第k位是什么字符,n从1开始,k从1开始 +// 比如n=4,k=2,表示S4的第2个字符是什么,返回b字符 +public class Code01_ReverseInvertString { + + public static int[] lens = null; + + public static void fillLens() { + lens = new int[26]; + lens[1] = 1; + for (int i = 2; i <= 25; i++) { + lens[i] = (lens[i - 1] << 1) + 1; + } + } + + // 求sn中的第k个字符 + // O(n), s <= 25 O(1) + public static char kth(int n, int k) { + if (lens == null) { + fillLens(); + } + if (n == 1) { // 无视k + return 'a'; + } + // sn half + int half = lens[n - 1]; + if (k <= half) { + return kth(n - 1, k); + } else if (k == half + 1) { + return (char) ('a' + n - 1); + } else { + // sn + // 我需要右半区,从左往右的第a个 + // 需要找到,s(n-1)从右往左的第a个 + // 当拿到字符之后,invert一下,就可以返回了! + return invert(kth(n - 1, ((half + 1) << 1) - k)); + } + } + + public static char invert(char c) { + return (char) (('a' << 1) + 24 - c); + } + + // 为了测试 + public static String generateString(int n) { + String s = "a"; + for (int i = 2; i <= n; i++) { + s = s + (char) ('a' + i - 1) + reverseInvert(s); + } + return s; + } + + // 为了测试 + public static String reverseInvert(String s) { + char[] invert = invert(s).toCharArray(); + for (int l = 0, r = invert.length - 1; l < r; l++, r--) { + char tmp = invert[l]; + invert[l] = invert[r]; + invert[r] = tmp; + } + return String.valueOf(invert); + } + + // 为了测试 + public static String invert(String s) { + char[] str = s.toCharArray(); + for (int i = 0; i < str.length; i++) { + str[i] = invert(str[i]); + } + return String.valueOf(str); + } + + // 为了测试 + public static void main(String[] args) { + int n = 20; + String str = generateString(n); + int len = str.length(); + System.out.println("测试开始"); + for (int i = 1; i <= len; i++) { + if (str.charAt(i - 1) != kth(n, i)) { + System.out.println(i); + System.out.println(str.charAt(i - 1)); + System.out.println(kth(n, i)); + System.out.println("出错了!"); + break; + } + } + System.out.println("测试结束"); + } + +} diff --git a/大厂刷题班/class36/Code02_Ratio01Split.java b/大厂刷题班/class36/Code02_Ratio01Split.java new file mode 100644 index 0000000..e06b928 --- /dev/null +++ b/大厂刷题班/class36/Code02_Ratio01Split.java @@ -0,0 +1,68 @@ +package class36; + +import java.util.HashMap; + +// 来自京东 +// 把一个01字符串切成多个部分,要求每一部分的0和1比例一样,同时要求尽可能多的划分 +// 比如 : 01010101 +// 01 01 01 01 这是一种切法,0和1比例为 1 : 1 +// 0101 0101 也是一种切法,0和1比例为 1 : 1 +// 两种切法都符合要求,但是那么尽可能多的划分为第一种切法,部分数为4 +// 比如 : 00001111 +// 只有一种切法就是00001111整体作为一块,那么尽可能多的划分,部分数为1 +// 给定一个01字符串str,假设长度为N,要求返回一个长度为N的数组ans +// 其中ans[i] = str[0...i]这个前缀串,要求每一部分的0和1比例一样,同时要求尽可能多的划分下,部分数是多少 +// 输入: str = "010100001" +// 输出: ans = [1, 1, 1, 2, 1, 2, 1, 1, 3] +public class Code02_Ratio01Split { + + // 001010010100... + public static int[] split(int[] arr) { + + // key : 分子 + // value : 属于key的分母表, 每一个分母,及其 分子/分母 这个比例,多少个前缀拥有 + HashMap> pre = new HashMap<>(); + int n = arr.length; + int[] ans = new int[n]; + int zero = 0; // 0出现的次数 + int one = 0; // 1出现的次数 + for (int i = 0; i < n; i++) { + if (arr[i] == 0) { + zero++; + } else { + one++; + } + if (zero == 0 || one == 0) { + ans[i] = i + 1; + } else { // 0和1,都有数量 -> 最简分数 + int gcd = gcd(zero, one); + int a = zero / gcd; + int b = one / gcd; + // a / b 比例,之前有多少前缀拥有? 3+1 4 5+1 6 + if (!pre.containsKey(a)) { + pre.put(a, new HashMap<>()); + } + if (!pre.get(a).containsKey(b)) { + pre.get(a).put(b, 1); + } else { + pre.get(a).put(b, pre.get(a).get(b) + 1); + } + ans[i] = pre.get(a).get(b); + } + } + return ans; + } + + public static int gcd(int m, int n) { + return n == 0 ? m : gcd(n, m % n); + } + + public static void main(String[] args) { + int[] arr = { 0, 1, 0, 1, 0, 1, 1, 0 }; + int[] ans = split(arr); + for (int i = 0; i < ans.length; i++) { + System.out.print(ans[i] + " "); + } + } + +} diff --git a/大厂刷题班/class36/Code03_MatchCount.java b/大厂刷题班/class36/Code03_MatchCount.java new file mode 100644 index 0000000..b7746c0 --- /dev/null +++ b/大厂刷题班/class36/Code03_MatchCount.java @@ -0,0 +1,66 @@ +package class36; + +// 来自美团 +// 给定两个字符串s1和s2 +// 返回在s1中有多少个子串等于s2 +public class Code03_MatchCount { + + public static int sa(String s1, String s2) { + if (s1 == null || s2 == null || s1.length() < s2.length()) { + return 0; + } + char[] str1 = s1.toCharArray(); + char[] str2 = s2.toCharArray(); + return count(str1, str2); + } + + // 改写kmp为这道题需要的功能 + public static int count(char[] str1, char[] str2) { + int x = 0; + int y = 0; + int count = 0; + int[] next = getNextArray(str2); + while (x < str1.length) { + if (str1[x] == str2[y]) { + x++; + y++; + if (y == str2.length) { + count++; + y = next[y]; + } + } else if (next[y] == -1) { + x++; + } else { + y = next[y]; + } + } + return count; + } + + // next数组多求一位 + // 比如:str2 = aaaa + // 那么,next = -1,0,1,2,3 + // 最后一个3表示,终止位置之前的字符串最长前缀和最长后缀的匹配长度 + // 也就是next数组补一位 + public static int[] getNextArray(char[] str2) { + if (str2.length == 1) { + return new int[] { -1, 0 }; + } + int[] next = new int[str2.length + 1]; + next[0] = -1; + next[1] = 0; + int i = 2; + int cn = 0; + while (i < next.length) { + if (str2[i - 1] == str2[cn]) { + next[i++] = ++cn; + } else if (cn > 0) { + cn = next[cn]; + } else { + next[i++] = 0; + } + } + return next; + } + +} diff --git a/大厂刷题班/class36/Code04_ComputeExpressionValue.java b/大厂刷题班/class36/Code04_ComputeExpressionValue.java new file mode 100644 index 0000000..5e8c7dd --- /dev/null +++ b/大厂刷题班/class36/Code04_ComputeExpressionValue.java @@ -0,0 +1,58 @@ +package class36; + +// 来自美团 +// () 分值为2 +// (()) 分值为3 +// ((())) 分值为4 +// 也就是说,每包裹一层,分数就是里面的分值+1 +// ()() 分值为2 * 2 +// (())() 分值为3 * 2 +// 也就是说,每连接一段,分数就是各部分相乘,以下是一个结合起来的例子 +// (()())()(()) -> (2 * 2 + 1) * 2 * 3 -> 30 +// 给定一个括号字符串str,已知str一定是正确的括号结合,不会有违规嵌套 +// 返回分数 +public class Code04_ComputeExpressionValue { + + public static int sores(String s) { + return compute(s.toCharArray(), 0)[0]; + } + + // s[i.....] 遇到 ')' 或者 终止位置 停! + // 返回值:int[] 长度就是2 + // 0 :分数是多少 + // 1 : 来到了什么位置停的! + public static int[] compute(char[] s, int i) { + if (s[i] == ')') { + return new int[] { 1, i }; + } + int ans = 1; + while (i < s.length && s[i] != ')') { + int[] info = compute(s, i + 1); + ans *= info[0] + 1; + i = info[1] + 1; + } + return new int[] { ans, i }; + } + + public static void main(String[] args) { + + String str1 = "(()())()(())"; + System.out.println(sores(str1)); + + // (()()) + (((()))) + ((())()) + // (()()) -> 2 * 2 + 1 -> 5 + // (((()))) -> 5 + // ((())()) -> ((2 + 1) * 2) + 1 -> 7 + // 所以下面的结果应该是175 + String str2 = "(()())(((())))((())())"; + System.out.println(sores(str2)); + + // (()()()) + (()(())) + // (()()()) -> 2 * 2 * 2 + 1 -> 9 + // (()(())) -> 2 * 3 + 1 -> 7 + // 所以下面的结果应该是63 + String str3 = "(()()())(()(()))"; + System.out.println(sores(str3)); + } + +} diff --git a/大厂刷题班/class36/Code05_Query3Problems.java b/大厂刷题班/class36/Code05_Query3Problems.java new file mode 100644 index 0000000..68c68f5 --- /dev/null +++ b/大厂刷题班/class36/Code05_Query3Problems.java @@ -0,0 +1,132 @@ +package class36; + +// 来自美团 +// 给定一个数组arr,长度为N,做出一个结构,可以高效的做如下的查询 +// 1) int querySum(L,R) : 查询arr[L...R]上的累加和 +// 2) int queryAim(L,R) : 查询arr[L...R]上的目标值,目标值定义如下: +// 假设arr[L...R]上的值为[a,b,c,d],a+b+c+d = s +// 目标值为 : (s-a)^2 + (s-b)^2 + (s-c)^2 + (s-d)^2 +// 3) int queryMax(L,R) : 查询arr[L...R]上的最大值 +// 要求: +// 1) 初始化该结构的时间复杂度不能超过O(N*logN) +// 2) 三个查询的时间复杂度不能超过O(logN) +// 3) 查询时,认为arr的下标从1开始,比如 : +// arr = [ 1, 1, 2, 3 ]; +// querySum(1, 3) -> 4 +// queryAim(2, 4) -> 50 +// queryMax(1, 4) -> 3 +public class Code05_Query3Problems { + + public static class SegmentTree { + private int[] max; + private int[] change; + private boolean[] update; + + public SegmentTree(int N) { + max = new int[N << 2]; + change = new int[N << 2]; + update = new boolean[N << 2]; + for (int i = 0; i < max.length; i++) { + max[i] = Integer.MIN_VALUE; + } + } + + private void pushUp(int rt) { + max[rt] = Math.max(max[rt << 1], max[rt << 1 | 1]); + } + + // ln表示左子树元素结点个数,rn表示右子树结点个数 + private void pushDown(int rt, int ln, int rn) { + if (update[rt]) { + update[rt << 1] = true; + update[rt << 1 | 1] = true; + change[rt << 1] = change[rt]; + change[rt << 1 | 1] = change[rt]; + max[rt << 1] = change[rt]; + max[rt << 1 | 1] = change[rt]; + update[rt] = false; + } + } + + public void update(int L, int R, int C, int l, int r, int rt) { + if (L <= l && r <= R) { + update[rt] = true; + change[rt] = C; + max[rt] = C; + return; + } + int mid = (l + r) >> 1; + pushDown(rt, mid - l + 1, r - mid); + if (L <= mid) { + update(L, R, C, l, mid, rt << 1); + } + if (R > mid) { + update(L, R, C, mid + 1, r, rt << 1 | 1); + } + pushUp(rt); + } + + public int query(int L, int R, int l, int r, int rt) { + if (L <= l && r <= R) { + return max[rt]; + } + int mid = (l + r) >> 1; + pushDown(rt, mid - l + 1, r - mid); + int left = 0; + int right = 0; + if (L <= mid) { + left = query(L, R, l, mid, rt << 1); + } + if (R > mid) { + right = query(L, R, mid + 1, r, rt << 1 | 1); + } + return Math.max(left, right); + } + + } + + public static class Query { + public int[] sum1; + public int[] sum2; + public SegmentTree st; + public int m; + + public Query(int[] arr) { + int n = arr.length; + m = arr.length + 1; + sum1 = new int[m]; + sum2 = new int[m]; + st = new SegmentTree(m); + for (int i = 0; i < n; i++) { + sum1[i + 1] = sum1[i] + arr[i]; + sum2[i + 1] = sum2[i] + arr[i] * arr[i]; + st.update(i + 1, i + 1, arr[i], 1, m, 1); + } + + } + + public int querySum(int L, int R) { + return sum1[R] - sum1[L - 1]; + } + + public int queryAim(int L, int R) { + int sumPower2 = querySum(L, R); + sumPower2 *= sumPower2; + return sum2[R] - sum2[L - 1] + (R - L - 1) * sumPower2; + } + + public int queryMax(int L, int R) { + return st.query(L, R, 1, m, 1); + } + + } + + public static void main(String[] args) { + int[] arr = { 1, 1, 2, 3 }; + Query q = new Query(arr); + System.out.println(q.querySum(1, 3)); + System.out.println(q.queryAim(2, 4)); + System.out.println(q.queryMax(1, 4)); + } + +} diff --git a/大厂刷题班/class36/Code06_NodeWeight.java b/大厂刷题班/class36/Code06_NodeWeight.java new file mode 100644 index 0000000..22c429e --- /dev/null +++ b/大厂刷题班/class36/Code06_NodeWeight.java @@ -0,0 +1,48 @@ +package class36; + +import java.util.HashMap; + +// 来自美团 +// 有一棵树,给定头节点h,和结构数组m,下标0弃而不用 +// 比如h = 1, m = [ [] , [2,3], [4], [5,6], [], [], []] +// 表示1的孩子是2、3; 2的孩子是4; 3的孩子是5、6; 4、5和6是叶节点,都不再有孩子 +// 每一个节点都有颜色,记录在c数组里,比如c[i] = 4, 表示节点i的颜色为4 +// 一开始只有叶节点是有权值的,记录在w数组里, +// 比如,如果一开始就有w[i] = 3, 表示节点i是叶节点、且权值是3 +// 现在规定非叶节点i的权值计算方式: +// 根据i的所有直接孩子来计算,假设i的所有直接孩子,颜色只有a,b,k +// w[i] = Max { +// (颜色为a的所有孩子个数 + 颜色为a的孩子权值之和), +// (颜色为b的所有孩子个数 + 颜色为b的孩子权值之和), +// (颜色为k的所有孩子个数 + 颜色k的孩子权值之和) +// } +// 请计算所有孩子的权值并返回 +public class Code06_NodeWeight { + + // 当前来到h节点, + // h的直接孩子,在哪呢?m[h] = {a,b,c,d,e} + // 每个节点的颜色在哪?比如i号节点,c[i]就是i号节点的颜色 + // 每个节点的权值在哪?比如i号节点,w[i]就是i号节点的权值 + // void : 把w数组填满就是这个函数的目标 + public static void w(int h, int[][] m, int[] w, int[] c) { + if (m[h].length == 0) { // 叶节点 + return; + } + // 有若干个直接孩子 + // 1 7个 + // 3 10个 + HashMap colors = new HashMap(); + // 1 20 + // 3 45 + HashMap weihts = new HashMap(); + for (int child : m[h]) { + w(child, m, w, c); + colors.put(c[child], colors.getOrDefault(c[child], 0) + 1); + weihts.put(c[child], weihts.getOrDefault(c[child], 0) + w[child]); + } + for (int color : colors.keySet()) { + w[h] = Math.max(w[h], colors.get(color) + weihts.get(color)); + } + } + +} diff --git a/大厂刷题班/class36/Code07_PickAddMax.java b/大厂刷题班/class36/Code07_PickAddMax.java new file mode 100644 index 0000000..e0c2b1f --- /dev/null +++ b/大厂刷题班/class36/Code07_PickAddMax.java @@ -0,0 +1,80 @@ +package class36; + +import java.util.Arrays; + +// 来自腾讯 +// 给定一个数组arr,当拿走某个数a的时候,其他所有的数都+a +// 请返回最终所有数都拿走的最大分数 +// 比如: [2,3,1] +// 当拿走3时,获得3分,数组变成[5,4] +// 当拿走5时,获得5分,数组变成[9] +// 当拿走9时,获得9分,数组变成[] +// 这是最大的拿取方式,返回总分17 +public class Code07_PickAddMax { + + // 最优解 + public static int pick(int[] arr) { + Arrays.sort(arr); + int ans = 0; + for (int i = arr.length - 1; i >= 0; i--) { + ans = (ans << 1) + arr[i]; + } + return ans; + } + + // 纯暴力方法,为了测试 + public static int test(int[] arr) { + if (arr.length == 1) { + return arr[0]; + } + int ans = 0; + for (int i = 0; i < arr.length; i++) { + int[] rest = removeAddOthers(arr, i); + ans = Math.max(ans, arr[i] + test(rest)); + } + return ans; + } + + // 为了测试 + public static int[] removeAddOthers(int[] arr, int i) { + int[] rest = new int[arr.length - 1]; + int ri = 0; + for (int j = 0; j < i; j++) { + rest[ri++] = arr[j] + arr[i]; + } + for (int j = i + 1; j < arr.length; j++) { + rest[ri++] = arr[j] + arr[i]; + } + return rest; + } + + // 为了测试 + public static int[] randomArray(int len, int value) { + int[] arr = new int[len]; + for (int i = 0; i < len; i++) { + arr[i] = (int) (Math.random() * value) + 1; + } + return arr; + } + + // 为了测试 + public static void main(String[] args) { + int N = 7; + int V = 10; + int testTime = 10000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int len = (int) (Math.random() * N) + 1; + int[] arr = randomArray(len, V); + int ans1 = pick(arr); + int ans2 = test(arr); + if (ans1 != ans2) { + System.out.println(ans1); + System.out.println(ans2); + System.out.println("出错了!"); + } + } + System.out.println("测试结束"); + } + +} diff --git a/大厂刷题班/class36/Code08_MinBoatEvenNumbers.java b/大厂刷题班/class36/Code08_MinBoatEvenNumbers.java new file mode 100644 index 0000000..2267013 --- /dev/null +++ b/大厂刷题班/class36/Code08_MinBoatEvenNumbers.java @@ -0,0 +1,98 @@ +package class36; + +import java.util.Arrays; + +// 来自腾讯 +// 给定一个正数数组arr,代表每个人的体重。给定一个正数limit代表船的载重,所有船都是同样的载重量 +// 每个人的体重都一定不大于船的载重 +// 要求: +// 1, 可以1个人单独一搜船 +// 2, 一艘船如果坐2人,两个人的体重相加需要是偶数,且总体重不能超过船的载重 +// 3, 一艘船最多坐2人 +// 返回如果想所有人同时坐船,船的最小数量 +public class Code08_MinBoatEvenNumbers { + + public static int minBoat(int[] arr, int limit) { + Arrays.sort(arr); + int odd = 0; + int even = 0; + for (int num : arr) { + if ((num & 1) == 0) { + even++; + } else { + odd++; + } + } + int[] odds = new int[odd]; + int[] evens = new int[even]; + for (int i = arr.length - 1; i >= 0; i--) { + if ((arr[i] & 1) == 0) { + evens[--even] = arr[i]; + } else { + odds[--odd] = arr[i]; + } + } + return min(odds, limit) + min(evens, limit); + } + + public static int min(int[] arr, int limit) { + if (arr == null || arr.length == 0) { + return 0; + } + int N = arr.length; + if (arr[N - 1] > limit) { + return -1; + } + int lessR = -1; + for (int i = N - 1; i >= 0; i--) { + if (arr[i] <= (limit / 2)) { + lessR = i; + break; + } + } + if (lessR == -1) { + return N; + } + int L = lessR; + int R = lessR + 1; + int noUsed = 0; + while (L >= 0) { + int solved = 0; + while (R < N && arr[L] + arr[R] <= limit) { + R++; + solved++; + } + if (solved == 0) { + noUsed++; + L--; + } else { + L = Math.max(-1, L - solved); + } + } + int all = lessR + 1; + int used = all - noUsed; + int moreUnsolved = (N - all) - used; + return used + ((noUsed + 1) >> 1) + moreUnsolved; + } + + // 首尾双指针的解法 + public static int numRescueBoats2(int[] people, int limit) { + Arrays.sort(people); + int ans = 0; + int l = 0; + int r = people.length - 1; + int sum = 0; + while (l <= r) { + sum = l == r ? people[l] : people[l] + people[r]; + if (sum > limit) { + r--; + } else { + l++; + r--; + } + ans++; + } + return ans; + } + +} diff --git a/大厂刷题班/class36/Code09_MaxKLenSequence.java b/大厂刷题班/class36/Code09_MaxKLenSequence.java new file mode 100644 index 0000000..0ba90a1 --- /dev/null +++ b/大厂刷题班/class36/Code09_MaxKLenSequence.java @@ -0,0 +1,87 @@ +package class36; + +import java.util.TreeSet; + +// 来自腾讯 +// 给定一个字符串str,和一个正数k +// 返回长度为k的所有子序列中,字典序最大的子序列 +public class Code09_MaxKLenSequence { + + public static String maxString(String s, int k) { + if (k <= 0 || s.length() < k) { + return ""; + } + char[] str = s.toCharArray(); + int n = str.length; + char[] stack = new char[n]; + int size = 0; + for (int i = 0; i < n; i++) { + while (size > 0 && stack[size - 1] < str[i] && size + n - i > k) { + size--; + } + if (size + n - i == k) { + return String.valueOf(stack, 0, size) + s.substring(i); + } + stack[size++] = str[i]; + } + return String.valueOf(stack, 0, k); + } + + // 为了测试 + public static String test(String str, int k) { + if (k <= 0 || str.length() < k) { + return ""; + } + TreeSet ans = new TreeSet<>(); + process(0, 0, str.toCharArray(), new char[k], ans); + return ans.last(); + } + + // 为了测试 + public static void process(int si, int pi, char[] str, char[] path, TreeSet ans) { + if (si == str.length) { + if (pi == path.length) { + ans.add(String.valueOf(path)); + } + } else { + process(si + 1, pi, str, path, ans); + if (pi < path.length) { + path[pi] = str[si]; + process(si + 1, pi + 1, str, path, ans); + } + } + } + + // 为了测试 + public static String randomString(int len, int range) { + char[] str = new char[len]; + for (int i = 0; i < len; i++) { + str[i] = (char) ((int) (Math.random() * range) + 'a'); + } + return String.valueOf(str); + } + + public static void main(String[] args) { + int n = 12; + int r = 5; + int testTime = 10000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int len = (int) (Math.random() * (n + 1)); + String str = randomString(len, r); + int k = (int) (Math.random() * (str.length() + 1)); + String ans1 = maxString(str, k); + String ans2 = test(str, k); + if (!ans1.equals(ans2)) { + System.out.println("出错了!"); + System.out.println(str); + System.out.println(k); + System.out.println(ans1); + System.out.println(ans2); + break; + } + } + System.out.println("测试结束"); + } + +} diff --git a/大厂刷题班/class36/Code10_StoneGameIV.java b/大厂刷题班/class36/Code10_StoneGameIV.java new file mode 100644 index 0000000..5bb9521 --- /dev/null +++ b/大厂刷题班/class36/Code10_StoneGameIV.java @@ -0,0 +1,63 @@ +package class36; + +// 来自哈喽单车 +// 本题是leetcode原题 : https://leetcode.com/problems/stone-game-iv/ +public class Code10_StoneGameIV { + + // 当前的!先手,会不会赢 + // 打表,不能发现规律 + public static boolean winnerSquareGame1(int n) { + if (n == 0) { + return false; + } + // 当前的先手,会尝试所有的情况,1,4,9,16,25,36.... + for (int i = 1; i * i <= n; i++) { + // 当前的先手,决定拿走 i * i 这个平方数 + // 它的对手会不会赢? winnerSquareGame1(n - i * i) + if (!winnerSquareGame1(n - i * i)) { + return true; + } + } + return false; + } + + public static boolean winnerSquareGame2(int n) { + int[] dp = new int[n + 1]; + dp[0] = -1; + return process2(n, dp); + } + + public static boolean process2(int n, int[] dp) { + if (dp[n] != 0) { + return dp[n] == 1 ? true : false; + } + boolean ans = false; + for (int i = 1; i * i <= n; i++) { + if (!process2(n - i * i, dp)) { + ans = true; + break; + } + } + dp[n] = ans ? 1 : -1; + return ans; + } + + public static boolean winnerSquareGame3(int n) { + boolean[] dp = new boolean[n + 1]; + for (int i = 1; i <= n; i++) { + for (int j = 1; j * j <= i; j++) { + if (!dp[i - j * j]) { + dp[i] = true; + break; + } + } + } + return dp[n]; + } + + public static void main(String[] args) { + int n = 10000000; + System.out.println(winnerSquareGame3(n)); + } + +} diff --git a/大厂刷题班/class36/Code11_BusRoutes.java b/大厂刷题班/class36/Code11_BusRoutes.java new file mode 100644 index 0000000..2fb7af3 --- /dev/null +++ b/大厂刷题班/class36/Code11_BusRoutes.java @@ -0,0 +1,59 @@ +package class36; + +import java.util.ArrayList; +import java.util.HashMap; + +// 来自三七互娱 +// Leetcode原题 : https://leetcode.com/problems/bus-routes/ +public class Code11_BusRoutes { + + // 0 : [1,3,7,0] + // 1 : [7,9,6,2] + // .... + // 返回:返回换乘几次+1 -> 返回一共坐了多少条线的公交。 + public static int numBusesToDestination(int[][] routes, int source, int target) { + if (source == target) { + return 0; + } + int n = routes.length; + // key : 车站 + // value : list -> 该车站拥有哪些线路! + HashMap> map = new HashMap<>(); + for (int i = 0; i < n; i++) { + for (int j = 0; j < routes[i].length; j++) { + if (!map.containsKey(routes[i][j])) { + map.put(routes[i][j], new ArrayList<>()); + } + map.get(routes[i][j]).add(i); + } + } + ArrayList queue = new ArrayList<>(); + boolean[] set = new boolean[n]; + for (int route : map.get(source)) { + queue.add(route); + set[route] = true; + } + int len = 1; + while (!queue.isEmpty()) { + ArrayList nextLevel = new ArrayList<>(); + for (int route : queue) { + int[] bus = routes[route]; + for (int station : bus) { + if (station == target) { + return len; + } + for (int nextRoute : map.get(station)) { + if (!set[nextRoute]) { + nextLevel.add(nextRoute); + set[nextRoute] = true; + } + } + } + } + queue = nextLevel; + len++; + } + return -1; + } + +} diff --git a/大厂刷题班/class36/说明 b/大厂刷题班/class36/说明 new file mode 100644 index 0000000..97dd816 --- /dev/null +++ b/大厂刷题班/class36/说明 @@ -0,0 +1,12 @@ +Code01 : 2021年8月大厂真实笔试题 +Code02 : 2021年8月大厂真实笔试题 +Code03 : 2021年8月大厂真实笔试题 +Code04 : 2021年8月大厂真实笔试题 +Code05 : 2021年8月大厂真实笔试题 +Code06 : 2021年8月大厂真实笔试题 +Code07 : 2021年8月大厂真实笔试题 +Code08 : 2021年8月大厂真实笔试题 +Code09 : 2021年8月大厂真实笔试题 +Code10 : 2021年8月大厂真实笔试题 +Code11 : 2021年8月大厂真实笔试题 +Code12 : 2021年8月大厂真实笔试题 \ No newline at end of file diff --git a/大厂刷题班/class37/Code01_ArrangeProject.java b/大厂刷题班/class37/Code01_ArrangeProject.java new file mode 100644 index 0000000..a448960 --- /dev/null +++ b/大厂刷题班/class37/Code01_ArrangeProject.java @@ -0,0 +1,50 @@ +package class37; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.Queue; + +// 来自网易 +// 刚入职网易互娱,新人mini项目便如火如荼的开展起来。为了更好的项目协作与管理, +// 小易决定将学到的甘特图知识用于mini项目时间预估。小易先把项目中每一项工作以任务的形式列举出来, +// 每项任务有一个预计花费时间与前置任务表,必须完成了该任务的前置任务才能着手去做该任务。 +// 作为经验PM,小易把任务划分得井井有条,保证没有前置任务或者前置任务全数完成的任务,都可以同时进行。 +// 小易给出了这样一个任务表,请作为程序的你计算需要至少多长时间才能完成所有任务。 +// 输入第一行为一个正整数T,表示数据组数。 +// 对于接下来每组数据,第一行为一个正整数N,表示一共有N项任务。 +// 接下来N行,每行先有两个整数Di和Ki,表示完成第i个任务的预计花费时间为Di天,该任务有Ki个前置任务。 +// 之后为Ki个整数Mj,表示第Mj个任务是第i个任务的前置任务。 +// 数据范围:对于所有数据,满足1<=T<=3, 1<=N, Mj<=100000, 0<=Di<=1000, 0<=sum(Ki)<=N*2。 +public class Code01_ArrangeProject { + + public static int dayCount(ArrayList[] nums, int[] days, int[] headCount) { + Queue head = countHead(headCount); + int maxDay = 0; + int[] countDay = new int[days.length]; + while (!head.isEmpty()) { + int cur = head.poll(); + countDay[cur] += days[cur]; + for (int j = 0; j < nums[cur].size(); j++) { + headCount[nums[cur].get(j)]--; + if (headCount[nums[cur].get(j)] == 0) { + head.offer(nums[cur].get(j)); + } + countDay[nums[cur].get(j)] = Math.max(countDay[nums[cur].get(j)], countDay[cur]); + } + } + for (int i = 0; i < countDay.length; i++) { + maxDay = Math.max(maxDay, countDay[i]); + } + return maxDay; + } + + private static Queue countHead(int[] headCount) { + Queue queue = new LinkedList<>(); + for (int i = 0; i < headCount.length; i++) { + if (headCount[i] == 0) + queue.offer(i); // 没有前驱任务 + } + return queue; + } + +} diff --git a/大厂刷题班/class37/Code02_GameForEveryStepWin.java b/大厂刷题班/class37/Code02_GameForEveryStepWin.java new file mode 100644 index 0000000..077707f --- /dev/null +++ b/大厂刷题班/class37/Code02_GameForEveryStepWin.java @@ -0,0 +1,111 @@ +package class37; + +// 来自字节 +// 扑克牌中的红桃J和梅花Q找不到了,为了利用剩下的牌做游戏,小明设计了新的游戏规则 +// 1) A,2,3,4....10,J,Q,K分别对应1到13这些数字,大小王对应0 +// 2) 游戏人数为2人,轮流从牌堆里摸牌,每次摸到的牌只有“保留”和“使用”两个选项,且当前轮必须做出选择 +// 3) 如果选择“保留”当前牌,那么当前牌的分数加到总分里,并且可以一直持续到游戏结束 +// 4) 如果选择“使用”当前牌,那么当前牌的分数*3,加到总分上去,但是只有当前轮,下一轮,下下轮生效,之后轮效果消失。 +// 5) 每一轮总分大的人获胜 +// 假设小明知道每一轮对手做出选择之后的总分,返回小明在每一轮都赢的情况下,最终的最大分是多少 +// 如果小明怎么都无法保证每一轮都赢,返回-1 +public class Code02_GameForEveryStepWin { + +// public static max(int[] cands, int[] sroces) { +// return f(cands, sroces, 0, 0, 0, 0); +// } + + // 当前来到index位置,牌是cands[index]值 + // 对手第i轮的得分,sroces[i] + // int hold : i之前保留的牌的总分 + // int cur : 当前轮得到的,之前的牌只算上使用的效果,加成是多少 + // int next : 之前的牌,对index的下一轮,使用效果加成是多少 + // 返回值:如果i...最后,不能全赢,返回-1 + // 如果i...最后,能全赢,返回最后一轮的最大值 + + // index -> 26种 + // hold -> (1+2+3+..13) -> 91 -> 91 * 4 - (11 + 12) -> 341 + // cur -> 26 + // next -> 13 + // 26 * 341 * 26 * 13 -> ? * (10 ^ 5) + public static int f(int[] cands, int[] sroces, int index, int hold, int cur, int next) { + if (index == 25) { // 最后一张 + int all = hold + cur + cands[index] * 3; + if (all <= sroces[index]) { + return -1; + } + return all; + } + // 不仅最后一张 + // 保留 + int all1 = hold + cur + cands[index]; + int p1 = -1; + if (all1 > sroces[index]) { + p1 = f(cands, sroces, index + 1, hold + cands[index], next, 0); + } + // 爆发 + int all2 = hold + cur + cands[index] * 3; + int p2 = -1; + if (all2 > sroces[index]) { + p2 = f(cands, sroces, index + 1, hold, next + cands[index] * 3, cands[index] * 3); + } + return Math.max(p1, p2); + } + + // 26 * 341 * 78 * 39 = 2 * (10 ^ 7) + public static int process(int[] cards, int[] scores, int index, int hold, int cur, int next) { + if (index == 25) { + int all = hold + cur + cards[index] * 3; + if (all > scores[index]) { + return all; + } else { + return -1; + } + } else { + int d1 = hold + cur + cards[index]; + int p1 = -1; + if (d1 > scores[index]) { + p1 = process(cards, scores, index + 1, hold + cards[index], next, 0); + } + int d2 = hold + cur + cards[index] * 3; + int p2 = -1; + if (d2 > scores[index]) { + p2 = process(cards, scores, index + 1, hold, next + cards[index] * 3, cards[index] * 3); + } + return Math.max(p1, p2); + } + } + + + + // cur -> 牌点数 -> * 3 之后是效果 + // next -> 牌点数 -> * 3之后是效果 + public static int p(int[] cands, int[] sroces, int index, int hold, int cur, int next) { + if (index == 25) { // 最后一张 + int all = hold + cur * 3 + cands[index] * 3; + if (all <= sroces[index]) { + return -1; + } + return all; + } + // 不仅最后一张 + // 保留 + int all1 = hold + cur * 3 + cands[index]; + int p1 = -1; + if (all1 > sroces[index]) { + p1 = f(cands, sroces, index + 1, hold + cands[index], next, 0); + } + // 爆发 + int all2 = hold + cur * 3 + cands[index] * 3; + int p2 = -1; + if (all2 > sroces[index]) { + p2 = f(cands, sroces, index + 1, hold, next + cands[index], cands[index]); + } + return Math.max(p1, p2); + } + + // 改出动态规划,记忆化搜索! + + + +} diff --git a/大厂刷题班/class37/Problem_0114_FlattenBinaryTreeToLinkedList.java b/大厂刷题班/class37/Problem_0114_FlattenBinaryTreeToLinkedList.java new file mode 100644 index 0000000..b6ba6ec --- /dev/null +++ b/大厂刷题班/class37/Problem_0114_FlattenBinaryTreeToLinkedList.java @@ -0,0 +1,93 @@ +package class37; + +// 注意,我们课上讲了一个别的题,并不是leetcode 114 +// 我们课上讲的是,把一棵搜索二叉树变成有序链表,怎么做 +// 而leetcode 114是,把一棵树先序遍历的结果串成链表 +// 所以我更新了代码,这个代码是leetcode 114的实现 +// 利用morris遍历 +public class Problem_0114_FlattenBinaryTreeToLinkedList { + + // 这个类不用提交 + public static class TreeNode { + public int val; + public TreeNode left; + public TreeNode right; + + public TreeNode(int value) { + val = value; + } + } + + // 普通解 + public static void flatten1(TreeNode root) { + process(root); + } + + public static class Info { + public TreeNode head; + public TreeNode tail; + + public Info(TreeNode h, TreeNode t) { + head = h; + tail = t; + } + } + + public static Info process(TreeNode head) { + if (head == null) { + return null; + } + Info leftInfo = process(head.left); + Info rightInfo = process(head.right); + head.left = null; + head.right = leftInfo == null ? null : leftInfo.head; + TreeNode tail = leftInfo == null ? head : leftInfo.tail; + tail.right = rightInfo == null ? null : rightInfo.head; + tail = rightInfo == null ? tail : rightInfo.tail; + return new Info(head, tail); + } + + // Morris遍历的解 + public static void flatten2(TreeNode root) { + if (root == null) { + return; + } + TreeNode pre = null; + TreeNode cur = root; + TreeNode mostRight = null; + while (cur != null) { + mostRight = cur.left; + if (mostRight != null) { + while (mostRight.right != null && mostRight.right != cur) { + mostRight = mostRight.right; + } + if (mostRight.right == null) { + mostRight.right = cur; + if (pre != null) { + pre.left = cur; + } + pre = cur; + cur = cur.left; + continue; + } else { + mostRight.right = null; + } + } else { + if (pre != null) { + pre.left = cur; + } + pre = cur; + } + cur = cur.right; + } + cur = root; + TreeNode next = null; + while (cur != null) { + next = cur.left; + cur.left = null; + cur.right = next; + cur = next; + } + } + +} diff --git a/大厂刷题班/class37/Problem_0221_MaximalSquare.java b/大厂刷题班/class37/Problem_0221_MaximalSquare.java new file mode 100644 index 0000000..38bfe06 --- /dev/null +++ b/大厂刷题班/class37/Problem_0221_MaximalSquare.java @@ -0,0 +1,40 @@ +package class37; + +public class Problem_0221_MaximalSquare { + + public static int maximalSquare(char[][] m) { + if (m == null || m.length == 0 || m[0].length == 0) { + return 0; + } + int N = m.length; + int M = m[0].length; + int[][] dp = new int[N + 1][M + 1]; + int max = 0; + for (int i = 0; i < N; i++) { + if (m[i][0] == '1') { + dp[i][0] = 1; + max = 1; + } + } + for (int j = 1; j < M; j++) { + if (m[0][j] == '1') { + dp[0][j] = 1; + max = 1; + } + } + for (int i = 1; i < N; i++) { + for (int j = 1; j < M; j++) { + if (m[i][j] == '1') { + dp[i][j] = Math.min( + Math.min(dp[i - 1][j], + dp[i][j - 1]), + dp[i - 1][j - 1]) + + 1; + max = Math.max(max, dp[i][j]); + } + } + } + return max * max; + } + +} diff --git a/大厂刷题班/class37/Problem_0226_InvertBinaryTree.java b/大厂刷题班/class37/Problem_0226_InvertBinaryTree.java new file mode 100644 index 0000000..d48dd9d --- /dev/null +++ b/大厂刷题班/class37/Problem_0226_InvertBinaryTree.java @@ -0,0 +1,21 @@ +package class37; + +public class Problem_0226_InvertBinaryTree { + + public class TreeNode { + public int val; + public TreeNode left; + public TreeNode right; + } + + public static TreeNode invertTree(TreeNode root) { + if (root == null) { + return null; + } + TreeNode left = root.left; + root.left = invertTree(root.right); + root.right = invertTree(left); + return root; + } + +} diff --git a/大厂刷题班/class37/Problem_0337_HouseRobberIII.java b/大厂刷题班/class37/Problem_0337_HouseRobberIII.java new file mode 100644 index 0000000..88bc718 --- /dev/null +++ b/大厂刷题班/class37/Problem_0337_HouseRobberIII.java @@ -0,0 +1,37 @@ +package class37; + +public class Problem_0337_HouseRobberIII { + + public static class TreeNode { + public int val; + public TreeNode left; + public TreeNode right; + } + + public static int rob(TreeNode root) { + Info info = process(root); + return Math.max(info.no, info.yes); + } + + public static class Info { + public int no; + public int yes; + + public Info(int n, int y) { + no = n; + yes = y; + } + } + + public static Info process(TreeNode x) { + if (x == null) { + return new Info(0, 0); + } + Info leftInfo = process(x.left); + Info rightInfo = process(x.right); + int no = Math.max(leftInfo.no, leftInfo.yes) + Math.max(rightInfo.no, rightInfo.yes); + int yes = x.val + leftInfo.no + rightInfo.no; + return new Info(no, yes); + } + +} diff --git a/大厂刷题班/class37/Problem_0394_DecodeString.java b/大厂刷题班/class37/Problem_0394_DecodeString.java new file mode 100644 index 0000000..0387f14 --- /dev/null +++ b/大厂刷题班/class37/Problem_0394_DecodeString.java @@ -0,0 +1,50 @@ +package class37; + +public class Problem_0394_DecodeString { + + public static String decodeString(String s) { + char[] str = s.toCharArray(); + return process(str, 0).ans; + } + + public static class Info { + public String ans; + public int stop; + + public Info(String a, int e) { + ans = a; + stop = e; + } + } + + // s[i....] 何时停?遇到 ']' 或者遇到 s的终止位置,停止 + // 返回Info + // 0) 串 + // 1) 算到了哪 + public static Info process(char[] s, int i) { + StringBuilder ans = new StringBuilder(); + int count = 0; + while (i < s.length && s[i] != ']') { + if ((s[i] >= 'a' && s[i] <= 'z') || (s[i] >= 'A' && s[i] <= 'Z')) { + ans.append(s[i++]); + } else if (s[i] >= '0' && s[i] <= '9') { + count = count * 10 + s[i++] - '0'; + } else { // str[index] = '[' + Info next = process(s, i + 1); + ans.append(timesString(count, next.ans)); + count = 0; + i = next.stop + 1; + } + } + return new Info(ans.toString(), i); + } + + public static String timesString(int times, String str) { + StringBuilder ans = new StringBuilder(); + for (int i = 0; i < times; i++) { + ans.append(str); + } + return ans.toString(); + } + +} diff --git a/大厂刷题班/class37/Problem_0406_QueueReconstructionByHeight.java b/大厂刷题班/class37/Problem_0406_QueueReconstructionByHeight.java new file mode 100644 index 0000000..793a8cf --- /dev/null +++ b/大厂刷题班/class37/Problem_0406_QueueReconstructionByHeight.java @@ -0,0 +1,264 @@ +package class37; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.LinkedList; + +public class Problem_0406_QueueReconstructionByHeight { + + public static int[][] reconstructQueue1(int[][] people) { + int N = people.length; + Unit[] units = new Unit[N]; + for (int i = 0; i < N; i++) { + units[i] = new Unit(people[i][0], people[i][1]); + } + Arrays.sort(units, new UnitComparator()); + ArrayList arrList = new ArrayList<>(); + for (Unit unit : units) { + arrList.add(unit.k, unit); + } + int[][] ans = new int[N][2]; + int index = 0; + for (Unit unit : arrList) { + ans[index][0] = unit.h; + ans[index++][1] = unit.k; + } + return ans; + } + + public static int[][] reconstructQueue2(int[][] people) { + int N = people.length; + Unit[] units = new Unit[N]; + for (int i = 0; i < N; i++) { + units[i] = new Unit(people[i][0], people[i][1]); + } + Arrays.sort(units, new UnitComparator()); + SBTree tree = new SBTree(); + for (int i = 0; i < N; i++) { + tree.insert(units[i].k, i); + } + LinkedList allIndexes = tree.allIndexes(); + int[][] ans = new int[N][2]; + int index = 0; + for (Integer arri : allIndexes) { + ans[index][0] = units[arri].h; + ans[index++][1] = units[arri].k; + } + return ans; + } + + public static class Unit { + public int h; + public int k; + + public Unit(int height, int greater) { + h = height; + k = greater; + } + } + + public static class UnitComparator implements Comparator { + + @Override + public int compare(Unit o1, Unit o2) { + return o1.h != o2.h ? (o2.h - o1.h) : (o1.k - o2.k); + } + + } + + public static class SBTNode { + public int value; + public SBTNode l; + public SBTNode r; + public int size; + + public SBTNode(int arrIndex) { + value = arrIndex; + size = 1; + } + } + + public static class SBTree { + private SBTNode root; + + private SBTNode rightRotate(SBTNode cur) { + SBTNode leftNode = cur.l; + cur.l = leftNode.r; + leftNode.r = cur; + leftNode.size = cur.size; + cur.size = (cur.l != null ? cur.l.size : 0) + (cur.r != null ? cur.r.size : 0) + 1; + return leftNode; + } + + private SBTNode leftRotate(SBTNode cur) { + SBTNode rightNode = cur.r; + cur.r = rightNode.l; + rightNode.l = cur; + rightNode.size = cur.size; + cur.size = (cur.l != null ? cur.l.size : 0) + (cur.r != null ? cur.r.size : 0) + 1; + return rightNode; + } + + private SBTNode maintain(SBTNode cur) { + if (cur == null) { + return null; + } + int leftSize = cur.l != null ? cur.l.size : 0; + int leftLeftSize = cur.l != null && cur.l.l != null ? cur.l.l.size : 0; + int leftRightSize = cur.l != null && cur.l.r != null ? cur.l.r.size : 0; + int rightSize = cur.r != null ? cur.r.size : 0; + int rightLeftSize = cur.r != null && cur.r.l != null ? cur.r.l.size : 0; + int rightRightSize = cur.r != null && cur.r.r != null ? cur.r.r.size : 0; + if (leftLeftSize > rightSize) { + cur = rightRotate(cur); + cur.r = maintain(cur.r); + cur = maintain(cur); + } else if (leftRightSize > rightSize) { + cur.l = leftRotate(cur.l); + cur = rightRotate(cur); + cur.l = maintain(cur.l); + cur.r = maintain(cur.r); + cur = maintain(cur); + } else if (rightRightSize > leftSize) { + cur = leftRotate(cur); + cur.l = maintain(cur.l); + cur = maintain(cur); + } else if (rightLeftSize > leftSize) { + cur.r = rightRotate(cur.r); + cur = leftRotate(cur); + cur.l = maintain(cur.l); + cur.r = maintain(cur.r); + cur = maintain(cur); + } + return cur; + } + + private SBTNode insert(SBTNode root, int index, SBTNode cur) { + if (root == null) { + return cur; + } + root.size++; + int leftAndHeadSize = (root.l != null ? root.l.size : 0) + 1; + if (index < leftAndHeadSize) { + root.l = insert(root.l, index, cur); + } else { + root.r = insert(root.r, index - leftAndHeadSize, cur); + } + root = maintain(root); + return root; + } + + private SBTNode get(SBTNode root, int index) { + int leftSize = root.l != null ? root.l.size : 0; + if (index < leftSize) { + return get(root.l, index); + } else if (index == leftSize) { + return root; + } else { + return get(root.r, index - leftSize - 1); + } + } + + private void process(SBTNode head, LinkedList indexes) { + if (head == null) { + return; + } + process(head.l, indexes); + indexes.addLast(head.value); + process(head.r, indexes); + } + + public void insert(int index, int value) { + SBTNode cur = new SBTNode(value); + if (root == null) { + root = cur; + } else { + if (index <= root.size) { + root = insert(root, index, cur); + } + } + } + + public int get(int index) { + SBTNode ans = get(root, index); + return ans.value; + } + + public LinkedList allIndexes() { + LinkedList indexes = new LinkedList<>(); + process(root, indexes); + return indexes; + } + + } + + // 通过以下这个测试, + // 可以很明显的看到LinkedList的插入和get效率不如SBTree + // LinkedList需要找到index所在的位置之后才能插入或者读取,时间复杂度O(N) + // SBTree是平衡搜索二叉树,所以插入或者读取时间复杂度都是O(logN) + public static void main(String[] args) { + // 功能测试 + int test = 10000; + int max = 1000000; + boolean pass = true; + LinkedList list = new LinkedList<>(); + SBTree sbtree = new SBTree(); + for (int i = 0; i < test; i++) { + int randomIndex = (int) (Math.random() * (i + 1)); + int randomValue = (int) (Math.random() * (max + 1)); + list.add(randomIndex, randomValue); + sbtree.insert(randomIndex, randomValue); + } + for (int i = 0; i < test; i++) { + if (list.get(i) != sbtree.get(i)) { + pass = false; + break; + } + } + System.out.println("功能测试是否通过 : " + pass); + + // 性能测试 + test = 50000; + list = new LinkedList<>(); + sbtree = new SBTree(); + long start = 0; + long end = 0; + + start = System.currentTimeMillis(); + for (int i = 0; i < test; i++) { + int randomIndex = (int) (Math.random() * (i + 1)); + int randomValue = (int) (Math.random() * (max + 1)); + list.add(randomIndex, randomValue); + } + end = System.currentTimeMillis(); + System.out.println("LinkedList插入总时长(毫秒) : " + (end - start)); + + start = System.currentTimeMillis(); + for (int i = 0; i < test; i++) { + int randomIndex = (int) (Math.random() * (i + 1)); + list.get(randomIndex); + } + end = System.currentTimeMillis(); + System.out.println("LinkedList读取总时长(毫秒) : " + (end - start)); + + start = System.currentTimeMillis(); + for (int i = 0; i < test; i++) { + int randomIndex = (int) (Math.random() * (i + 1)); + int randomValue = (int) (Math.random() * (max + 1)); + sbtree.insert(randomIndex, randomValue); + } + end = System.currentTimeMillis(); + System.out.println("SBTree插入总时长(毫秒) : " + (end - start)); + + start = System.currentTimeMillis(); + for (int i = 0; i < test; i++) { + int randomIndex = (int) (Math.random() * (i + 1)); + sbtree.get(randomIndex); + } + end = System.currentTimeMillis(); + System.out.println("SBTree读取总时长(毫秒) : " + (end - start)); + + } + +} diff --git a/大厂刷题班/class37/Problem_0437_PathSumIII.java b/大厂刷题班/class37/Problem_0437_PathSumIII.java new file mode 100644 index 0000000..253c27c --- /dev/null +++ b/大厂刷题班/class37/Problem_0437_PathSumIII.java @@ -0,0 +1,44 @@ +package class37; + +import java.util.HashMap; + +public class Problem_0437_PathSumIII { + + public class TreeNode { + public int val; + public TreeNode left; + public TreeNode right; + } + + public static int pathSum(TreeNode root, int sum) { + HashMap preSumMap = new HashMap<>(); + preSumMap.put(0L, 1); + return process(root, sum, 0, preSumMap); + } + + // 返回方法数 + public static int process(TreeNode x, int sum, long preAll, HashMap preSumMap) { + if (x == null) { + return 0; + } + long all = preAll + x.val; + int ans = 0; + if (preSumMap.containsKey(all - sum)) { + ans = preSumMap.get(all - sum); + } + if (!preSumMap.containsKey(all)) { + preSumMap.put(all, 1); + } else { + preSumMap.put(all, preSumMap.get(all) + 1); + } + ans += process(x.left, sum, all, preSumMap); + ans += process(x.right, sum, all, preSumMap); + if (preSumMap.get(all) == 1) { + preSumMap.remove(all); + } else { + preSumMap.put(all, preSumMap.get(all) - 1); + } + return ans; + } + +} diff --git a/大厂刷题班/class37/说明 b/大厂刷题班/class37/说明 new file mode 100644 index 0000000..8ee8a8f --- /dev/null +++ b/大厂刷题班/class37/说明 @@ -0,0 +1,30 @@ +leetcode最受欢迎100题 +leetcode全题目列表 : https://leetcode.com/problemset/all/ +在全题目列表的右侧栏,点击Top 100 Liked Questions +或者直接进入右侧链接 : https://leetcode.com/problemset/all/?listId=79h8rn6 +即可看到leetcode最受欢迎100题全列表 + +大厂刷题班27节~35节已经讲完leetcode高频题系列 +leetcode高频题系列和leetcode最受欢迎100题系列题目有重合 +以下为leetcode最受欢迎100题不和leetcode高频题重合的题号,其他的都重复了,不再讲述 +0032 : 大厂刷题班, 第14节第1题 +0039 : 体系学习班, 硬币找零专题 : 第21节第2、3、4题, 第22节第2题, 第24节第4题。本题就是无限张找零问题,不再重复讲述 +0064 : 体系学习班, 第21节第1题 +0072 : 大厂刷题班, 第5节第3题 +0085 : 体系学习班, 第25节第4题 +0096 : 体系学习班, 第39节第4题, 卡特兰数 +0114 : 大厂刷题班, 第37节, 本节 +0142 : 体系学习班, 第10节第1题 +0221 : 大厂刷题班, 第37节, 本节 +0226 : 大厂刷题班, 第37节, 本节 +0337 : 体系学习班, 第13节, 第4题, 还是这道题的加强版(多叉树) +0338 : 和leetcode第191题重复, 大厂刷题班第30节讲过了 +0394 : 大厂刷题班, 第37节, 本节 +0406 : 大厂刷题班, 第37节, 本节 +0416 : 体系学习班, 第23节第1题, 还是这道题的加强版 +0437 : 大厂刷题班, 第37节, 本节 +剩余题目在下一节 + +本节附加题 +code01 : 2021年8月大厂真实笔试题 +code02 : 2021年8月大厂真实笔试题 \ No newline at end of file diff --git a/大厂刷题班/class38/Code01_FillGapMinStep.java b/大厂刷题班/class38/Code01_FillGapMinStep.java new file mode 100644 index 0000000..361a734 --- /dev/null +++ b/大厂刷题班/class38/Code01_FillGapMinStep.java @@ -0,0 +1,103 @@ +package class38; + +// 来自字节 +// 给定两个数a和b +// 第1轮,把1选择给a或者b +// 第2轮,把2选择给a或者b +// ... +// 第i轮,把i选择给a或者b +// 想让a和b的值一样大,请问至少需要多少轮? +public class Code01_FillGapMinStep { + + // 暴力方法 + // 不要让a、b过大! + public static int minStep0(int a, int b) { + if (a == b) { + return 0; + } + int limit = 15; + return process(a, b, 1, limit); + } + + public static int process(int a, int b, int i, int n) { + if (i > n) { + return Integer.MAX_VALUE; + } + if (a + i == b || a == b + i) { + return i; + } + return Math.min(process(a + i, b, i + 1, n), process(a, b + i, i + 1, n)); + } + + public static int minStep1(int a, int b) { + if (a == b) { + return 0; + } + int s = Math.abs(a - b); + int num = 1; + int sum = 0; + for (; !(sum >= s && (sum - s) % 2 == 0); num++) { + sum += num; + } + return num - 1; + } + + public static int minStep2(int a, int b) { + if (a == b) { + return 0; + } + int s = Math.abs(a - b); + // 找到sum >= s, 最小的i + int begin = best(s << 1); + for (; (begin * (begin + 1) / 2 - s) % 2 != 0;) { + begin++; + } + return begin; + } + + public static int best(int s2) { + int L = 0; + int R = 1; + for (; R * (R + 1) < s2;) { + L = R; + R *= 2; + } + int ans = 0; + while (L <= R) { + int M = (L + R) / 2; + if (M * (M + 1) >= s2) { + ans = M; + R = M - 1; + } else { + L = M + 1; + } + } + return ans; + } + + public static void main(String[] args) { + System.out.println("功能测试开始"); + for (int a = 1; a < 100; a++) { + for (int b = 1; b < 100; b++) { + int ans1 = minStep0(a, b); + int ans2 = minStep1(a, b); + int ans3 = minStep2(a, b); + if (ans1 != ans2 || ans1 != ans3) { + System.out.println("出错了!"); + System.out.println(a + " , " + b); + break; + } + } + } + System.out.println("功能测试结束"); + + int a = 19019; + int b = 8439284; + int ans2 = minStep1(a, b); + int ans3 = minStep2(a, b); + System.out.println(ans2); + System.out.println(ans3); + + } + +} diff --git a/大厂刷题班/class38/Code02_GreatWall.java b/大厂刷题班/class38/Code02_GreatWall.java new file mode 100644 index 0000000..af9cf35 --- /dev/null +++ b/大厂刷题班/class38/Code02_GreatWall.java @@ -0,0 +1,183 @@ +package class38; + +// 360笔试题 +// 长城守卫军 +// 题目描述: +// 长城上有连成一排的n个烽火台,每个烽火台都有士兵驻守。 +// 第i个烽火台驻守着ai个士兵,相邻峰火台的距离为1。另外,有m位将军, +// 每位将军可以驻守一个峰火台,每个烽火台可以有多个将军驻守, +// 将军可以影响所有距离他驻守的峰火台小于等于x的烽火台。 +// 每个烽火台的基础战斗力为士兵数,另外,每个能影响此烽火台的将军都能使这个烽火台的战斗力提升k。 +// 长城的战斗力为所有烽火台的战斗力的最小值。 +// 请问长城的最大战斗力可以是多少? +// 输入描述 +// 第一行四个正整数n,m,x,k(1<=x<=n<=10^5,0<=m<=10^5,1<=k<=10^5) +// 第二行n个整数ai(0<=ai<=10^5) +// 输出描述 仅一行,一个整数,表示长城的最大战斗力 +// 样例输入 +// 5 2 1 2 +// 4 4 2 4 4 +// 样例输出 +// 6 +public class Code02_GreatWall { + + public static int maxForce(int[] wall, int m, int x, int k) { + long L = 0; + long R = 0; + for (int num : wall) { + R = Math.max(R, num); + } + R += m * k; + long ans = 0; + while (L <= R) { + long M = (L + R) / 2; + if (can(wall, m, x, k, M)) { + ans = M; + L = M + 1; + } else { + R = M - 1; + } + } + return (int) ans; + } + + public static boolean can(int[] wall, int m, int x, int k, long limit) { + int N = wall.length; + // 注意:下标从1开始 + SegmentTree st = new SegmentTree(wall); + st.build(1, N, 1); + int need = 0; + for (int i = 0; i < N; i++) { + // 注意:下标从1开始 + long cur = st.query(i + 1, i + 1, 1, N, 1); + if (cur < limit) { + int add = (int) ((limit - cur + k - 1) / k); + need += add; + if (need > m) { + return false; + } + st.add(i + 1, Math.min(i + x, N), add * k, 1, N, 1); + } + } + return true; + } + + public static class SegmentTree { + private int MAXN; + private int[] arr; + private int[] sum; + private int[] lazy; + private int[] change; + private boolean[] update; + + public SegmentTree(int[] origin) { + MAXN = origin.length + 1; + arr = new int[MAXN]; + for (int i = 1; i < MAXN; i++) { + arr[i] = origin[i - 1]; + } + sum = new int[MAXN << 2]; + lazy = new int[MAXN << 2]; + change = new int[MAXN << 2]; + update = new boolean[MAXN << 2]; + } + + private void pushUp(int rt) { + sum[rt] = sum[rt << 1] + sum[rt << 1 | 1]; + } + + private void pushDown(int rt, int ln, int rn) { + if (update[rt]) { + update[rt << 1] = true; + update[rt << 1 | 1] = true; + change[rt << 1] = change[rt]; + change[rt << 1 | 1] = change[rt]; + lazy[rt << 1] = 0; + lazy[rt << 1 | 1] = 0; + sum[rt << 1] = change[rt] * ln; + sum[rt << 1 | 1] = change[rt] * rn; + update[rt] = false; + } + if (lazy[rt] != 0) { + lazy[rt << 1] += lazy[rt]; + sum[rt << 1] += lazy[rt] * ln; + lazy[rt << 1 | 1] += lazy[rt]; + sum[rt << 1 | 1] += lazy[rt] * rn; + lazy[rt] = 0; + } + } + + public void build(int l, int r, int rt) { + if (l == r) { + sum[rt] = arr[l]; + return; + } + int mid = (l + r) >> 1; + build(l, mid, rt << 1); + build(mid + 1, r, rt << 1 | 1); + pushUp(rt); + } + + public void update(int L, int R, int C, int l, int r, int rt) { + if (L <= l && r <= R) { + update[rt] = true; + change[rt] = C; + sum[rt] = C * (r - l + 1); + lazy[rt] = 0; + return; + } + int mid = (l + r) >> 1; + pushDown(rt, mid - l + 1, r - mid); + if (L <= mid) { + update(L, R, C, l, mid, rt << 1); + } + if (R > mid) { + update(L, R, C, mid + 1, r, rt << 1 | 1); + } + pushUp(rt); + } + + public void add(int L, int R, int C, int l, int r, int rt) { + if (L <= l && r <= R) { + sum[rt] += C * (r - l + 1); + lazy[rt] += C; + return; + } + int mid = (l + r) >> 1; + pushDown(rt, mid - l + 1, r - mid); + if (L <= mid) { + add(L, R, C, l, mid, rt << 1); + } + if (R > mid) { + add(L, R, C, mid + 1, r, rt << 1 | 1); + } + pushUp(rt); + } + + public long query(int L, int R, int l, int r, int rt) { + if (L <= l && r <= R) { + return sum[rt]; + } + int mid = (l + r) >> 1; + pushDown(rt, mid - l + 1, r - mid); + long ans = 0; + if (L <= mid) { + ans += query(L, R, l, mid, rt << 1); + } + if (R > mid) { + ans += query(L, R, mid + 1, r, rt << 1 | 1); + } + return ans; + } + + } + + public static void main(String[] args) { + int[] wall = { 3, 1, 1, 1, 3 }; + int m = 2; + int x = 3; + int k = 1; + System.out.println(maxForce(wall, m, x, k)); + } + +} diff --git a/大厂刷题班/class38/Problem_0438_FindAllAnagramsInAString.java b/大厂刷题班/class38/Problem_0438_FindAllAnagramsInAString.java new file mode 100644 index 0000000..3f68453 --- /dev/null +++ b/大厂刷题班/class38/Problem_0438_FindAllAnagramsInAString.java @@ -0,0 +1,58 @@ +package class38; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +public class Problem_0438_FindAllAnagramsInAString { + + public static List findAnagrams(String s, String p) { + List ans = new ArrayList<>(); + if (s == null || p == null || s.length() < p.length()) { + return ans; + } + char[] str = s.toCharArray(); + int N = str.length; + char[] pst = p.toCharArray(); + int M = pst.length; + HashMap map = new HashMap<>(); + for (char cha : pst) { + if (!map.containsKey(cha)) { + map.put(cha, 1); + } else { + map.put(cha, map.get(cha) + 1); + } + } + int all = M; + for (int end = 0; end < M - 1; end++) { + if (map.containsKey(str[end])) { + int count = map.get(str[end]); + if (count > 0) { + all--; + } + map.put(str[end], count - 1); + } + } + for (int end = M - 1, start = 0; end < N; end++, start++) { + if (map.containsKey(str[end])) { + int count = map.get(str[end]); + if (count > 0) { + all--; + } + map.put(str[end], count - 1); + } + if (all == 0) { + ans.add(start); + } + if (map.containsKey(str[start])) { + int count = map.get(str[start]); + if (count >= 0) { + all++; + } + map.put(str[start], count + 1); + } + } + return ans; + } + +} diff --git a/大厂刷题班/class38/Problem_0448_FindAllNumbersDisappearedInAnArray.java b/大厂刷题班/class38/Problem_0448_FindAllNumbersDisappearedInAnArray.java new file mode 100644 index 0000000..3dcf07e --- /dev/null +++ b/大厂刷题班/class38/Problem_0448_FindAllNumbersDisappearedInAnArray.java @@ -0,0 +1,42 @@ +package class38; + +import java.util.ArrayList; +import java.util.List; + +public class Problem_0448_FindAllNumbersDisappearedInAnArray { + + public static List findDisappearedNumbers(int[] nums) { + List ans = new ArrayList<>(); + if (nums == null || nums.length == 0) { + return ans; + } + int N = nums.length; + for (int i = 0; i < N; i++) { + // 从i位置出发,去玩下标循环怼 + walk(nums, i); + } + for (int i = 0; i < N; i++) { + if (nums[i] != i + 1) { + ans.add(i + 1); + } + } + return ans; + } + + public static void walk(int[] nums, int i) { + while (nums[i] != i + 1) { // 不断从i发货 + int nexti = nums[i] - 1; + if (nums[nexti] == nexti + 1) { + break; + } + swap(nums, i, nexti); + } + } + + public static void swap(int[] nums, int i, int j) { + int tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + +} diff --git a/大厂刷题班/class38/Problem_0617_MergeTwoBinaryTrees.java b/大厂刷题班/class38/Problem_0617_MergeTwoBinaryTrees.java new file mode 100644 index 0000000..942d269 --- /dev/null +++ b/大厂刷题班/class38/Problem_0617_MergeTwoBinaryTrees.java @@ -0,0 +1,31 @@ +package class38; + +public class Problem_0617_MergeTwoBinaryTrees { + + public static class TreeNode { + public int val; + public TreeNode left; + public TreeNode right; + + public TreeNode(int val) { + this.val = val; + } + } + + // 当前,一棵树的头是t1,另一颗树的头是t2 + // 请返回,整体merge之后的头 + public static TreeNode mergeTrees(TreeNode t1, TreeNode t2) { + if (t1 == null) { + return t2; + } + if (t2 == null) { + return t1; + } + // t1和t2都不是空 + TreeNode merge = new TreeNode(t1.val + t2.val); + merge.left = mergeTrees(t1.left, t2.left); + merge.right = mergeTrees(t1.right, t2.right); + return merge; + } + +} diff --git a/大厂刷题班/class38/Problem_0621_TaskScheduler.java b/大厂刷题班/class38/Problem_0621_TaskScheduler.java new file mode 100644 index 0000000..24b4b59 --- /dev/null +++ b/大厂刷题班/class38/Problem_0621_TaskScheduler.java @@ -0,0 +1,33 @@ +package class38; + +public class Problem_0621_TaskScheduler { + + // ['A', 'B', 'A'] + public static int leastInterval(char[] tasks, int free) { + int[] count = new int[256]; + // 出现最多次的任务,到底是出现了几次 + int maxCount = 0; + for (char task : tasks) { + count[task]++; + maxCount = Math.max(maxCount, count[task]); + } + // 有多少种任务,都出现最多次 + int maxKinds = 0; + for (int task = 0; task < 256; task++) { + if (count[task] == maxCount) { + maxKinds++; + } + } + // maxKinds : 有多少种任务,都出现最多次 + // maxCount : 最多次,是几次? + // 砍掉最后一组剩余的任务数 + int tasksExceptFinalTeam = tasks.length - maxKinds; + int spaces = (free + 1) * (maxCount - 1); + // 到底几个空格最终会留下! + int restSpaces = Math.max(0, spaces - tasksExceptFinalTeam); + return tasks.length + restSpaces; + // return Math.max(tasks.length, ((n + 1) * (maxCount - 1) + maxKinds)); + } + + +} diff --git a/大厂刷题班/class38/Problem_0647_PalindromicSubstrings.java b/大厂刷题班/class38/Problem_0647_PalindromicSubstrings.java new file mode 100644 index 0000000..e51180a --- /dev/null +++ b/大厂刷题班/class38/Problem_0647_PalindromicSubstrings.java @@ -0,0 +1,49 @@ +package class38; + +public class Problem_0647_PalindromicSubstrings { + + public static int countSubstrings(String s) { + if (s == null || s.length() == 0) { + return 0; + } + int[] dp = getManacherDP(s); + int ans = 0; + for (int i = 0; i < dp.length; i++) { + ans += dp[i] >> 1; + } + return ans; + } + + public static int[] getManacherDP(String s) { + char[] str = manacherString(s); + int[] pArr = new int[str.length]; + int C = -1; + int R = -1; + for (int i = 0; i < str.length; i++) { + pArr[i] = R > i ? Math.min(pArr[2 * C - i], R - i) : 1; + while (i + pArr[i] < str.length && i - pArr[i] > -1) { + if (str[i + pArr[i]] == str[i - pArr[i]]) + pArr[i]++; + else { + break; + } + } + if (i + pArr[i] > R) { + R = i + pArr[i]; + C = i; + } + } + return pArr; + } + + public static char[] manacherString(String str) { + char[] charArr = str.toCharArray(); + char[] res = new char[str.length() * 2 + 1]; + int index = 0; + for (int i = 0; i != res.length; i++) { + res[i] = (i & 1) == 0 ? '#' : charArr[index++]; + } + return res; + } + +} diff --git a/大厂刷题班/class38/Problem_0739_DailyTemperatures.java b/大厂刷题班/class38/Problem_0739_DailyTemperatures.java new file mode 100644 index 0000000..92b0712 --- /dev/null +++ b/大厂刷题班/class38/Problem_0739_DailyTemperatures.java @@ -0,0 +1,34 @@ +package class38; + +import java.util.ArrayList; +import java.util.List; +import java.util.Stack; + +public class Problem_0739_DailyTemperatures { + + public static int[] dailyTemperatures(int[] arr) { + if (arr == null || arr.length == 0) { + return new int[0]; + } + int N = arr.length; + int[] ans = new int[N]; + Stack> stack = new Stack<>(); + for (int i = 0; i < N; i++) { + while (!stack.isEmpty() && arr[stack.peek().get(0)] < arr[i]) { + List popIs = stack.pop(); + for (Integer popi : popIs) { + ans[popi] = i - popi; + } + } + if (!stack.isEmpty() && arr[stack.peek().get(0)] == arr[i]) { + stack.peek().add(Integer.valueOf(i)); + } else { + ArrayList list = new ArrayList<>(); + list.add(i); + stack.push(list); + } + } + return ans; + } + +} diff --git a/大厂刷题班/class38/Problem_0763_PartitionLabels.java b/大厂刷题班/class38/Problem_0763_PartitionLabels.java new file mode 100644 index 0000000..c72954a --- /dev/null +++ b/大厂刷题班/class38/Problem_0763_PartitionLabels.java @@ -0,0 +1,28 @@ +package class38; + +import java.util.ArrayList; +import java.util.List; + +public class Problem_0763_PartitionLabels { + + public static List partitionLabels(String S) { + char[] str = S.toCharArray(); + int[] far = new int[26]; + for (int i = 0; i < str.length; i++) { + far[str[i] - 'a'] = i; + } + List ans = new ArrayList<>(); + int left = 0; + int right = far[str[0] - 'a']; + for (int i = 1; i < str.length; i++) { + if (i > right) { + ans.add(right - left + 1); + left = i; + } + right = Math.max(right, far[str[i] - 'a']); + } + ans.add(right - left + 1); + return ans; + } + +} diff --git a/大厂刷题班/class38/说明 b/大厂刷题班/class38/说明 new file mode 100644 index 0000000..5fc1f1f --- /dev/null +++ b/大厂刷题班/class38/说明 @@ -0,0 +1,25 @@ +leetcode最受欢迎100题 +leetcode全题目列表 : https://leetcode.com/problemset/all/ +在全题目列表的右侧栏,点击Top 100 Liked Questions +或者直接进入右侧链接 : https://leetcode.com/problemset/all/?listId=79h8rn6 +即可看到leetcode最受欢迎100题全列表 + +大厂刷题班27节~35节已经讲完leetcode高频题系列 +leetcode高频题系列和leetcode最受欢迎100题系列题目有重合 +以下为leetcode最受欢迎100题不和leetcode高频题重合的题号,其他的都重复了,不再讲述 +0438 : 大厂刷题班, 第38节, 本节 +0448 : 大厂刷题班, 第38节, 本节 +0494 : 大厂刷题班, 第1节第7题 +0543 : 体系学习班, 第12节第6题 +0560 : 大厂刷题班, 第37节, Leetcode题目437与本题思路相同, 课上也讲了该题做法 +0581 : 大厂刷题班, 第1节第6题 +0617 : 大厂刷题班, 第38节, 本节 +0621 : 大厂刷题班, 第38节, 本节 +0647 : 大厂刷题班, 第38节, 本节 +0739 : 大厂刷题班, 第38节, 本节 +0763 : 大厂刷题班, 第38节, 本节 +至此,leetcode最受欢迎100题系列完结 + +本节附加题 +code01 : 2021年8月大厂真实笔试题 +code02 : 2021年8月大厂真实笔试题 \ No newline at end of file diff --git a/大厂刷题班/class39/Code01_01AddValue.java b/大厂刷题班/class39/Code01_01AddValue.java new file mode 100644 index 0000000..5609d29 --- /dev/null +++ b/大厂刷题班/class39/Code01_01AddValue.java @@ -0,0 +1,46 @@ +package class39; + +// 来自腾讯 +// 给定一个只由0和1组成的字符串S,假设下标从1开始,规定i位置的字符价值V[i]计算方式如下 : +// 1) i == 1时,V[i] = 1 +// 2) i > 1时,如果S[i] != S[i-1],V[i] = 1 +// 3) i > 1时,如果S[i] == S[i-1],V[i] = V[i-1] + 1 +// 你可以随意删除S中的字符,返回整个S的最大价值 +// 字符串长度<=5000 +public class Code01_01AddValue { + + public static int max1(String s) { + if (s == null || s.length() == 0) { + return 0; + } + char[] str = s.toCharArray(); + int[] arr = new int[str.length]; + for (int i = 0; i < arr.length; i++) { + arr[i] = str[i] == '0' ? 0 : 1; + } + return process1(arr, 0, 0, 0); + } + + // 递归含义 : + // 目前在arr[index...]上做选择, str[index...]的左边,最近的数字是lastNum + // 并且lastNum所带的价值,已经拉高到baseValue + // 返回在str[index...]上做选择,最终获得的最大价值 + // index -> 0 ~ 4999 + // lastNum -> 0 or 1 + // baseValue -> 1 ~ 5000 + // 5000 * 2 * 5000 -> 5 * 10^7(过!) + public static int process1(int[] arr, int index, int lastNum, int baseValue) { + if (index == arr.length) { + return 0; + } + int curValue = lastNum == arr[index] ? (baseValue + 1) : 1; + // 当前index位置的字符保留 + int next1 = process1(arr, index + 1, arr[index], curValue); + // 当前index位置的字符不保留 + int next2 = process1(arr, index + 1, lastNum, baseValue); + return Math.max(curValue + next1, next2); + } + + // 请看体系学习班,动态规划章节,把上面的递归改成动态规划!看完必会 + +} diff --git a/大厂刷题班/class39/Code02_ValidSequence.java b/大厂刷题班/class39/Code02_ValidSequence.java new file mode 100644 index 0000000..37e499e --- /dev/null +++ b/大厂刷题班/class39/Code02_ValidSequence.java @@ -0,0 +1,113 @@ +package class39; + +// 来自腾讯 +// 给定一个长度为n的数组arr,求有多少个子数组满足 : +// 子数组两端的值,是这个子数组的最小值和次小值,最小值和次小值谁在最左和最右无所谓 +// n<=100000(10^5) n*logn O(N) +public class Code02_ValidSequence { + + + public static int nums(int[] arr) { + if (arr == null || arr.length < 2) { + return 0; + } + int n = arr.length; + int[] values = new int[n]; + int[] times = new int[n]; + int size = 0; + int ans = 0; + for (int i = 0; i < arr.length; i++) { + while (size != 0 && values[size - 1] > arr[i]) { + size--; + ans += times[size] + cn2(times[size]); + } + if (size != 0 && values[size - 1] == arr[i]) { + times[size - 1]++; + } else { + values[size] = arr[i]; + times[size++] = 1; + } + } + while (size != 0) { + ans += cn2(times[--size]); + } + for (int i = arr.length - 1; i >= 0; i--) { + while (size != 0 && values[size - 1] > arr[i]) { + ans += times[--size]; + } + if (size != 0 && values[size - 1] == arr[i]) { + times[size - 1]++; + } else { + values[size] = arr[i]; + times[size++] = 1; + } + } + return ans; + } + + public static int cn2(int n) { + return (n * (n - 1)) >> 1; + } + + // 为了测试 + // 暴力方法 + public static int test(int[] arr) { + if (arr == null || arr.length < 2) { + return 0; + } + int ans = 0; + for (int s = 0; s < arr.length; s++) { + for (int e = s + 1; e < arr.length; e++) { + int max = Math.max(arr[s], arr[e]); + boolean valid = true; + for (int i = s + 1; i < e; i++) { + if (arr[i] < max) { + valid = false; + break; + } + } + ans += valid ? 1 : 0; + } + } + return ans; + } + + // 为了测试 + public static int[] randomArray(int n, int v) { + int[] arr = new int[n]; + for (int i = 0; i < n; i++) { + arr[i] = (int) (Math.random() * v); + } + return arr; + } + + // 为了测试 + public static void printArray(int[] arr) { + for (int num : arr) { + System.out.print(num + " "); + } + System.out.println(); + } + + // 为了测试 + public static void main(String[] args) { + int n = 30; + int v = 30; + int testTime = 100000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int m = (int) (Math.random() * n); + int[] arr = randomArray(m, v); + int ans1 = nums(arr); + int ans2 = test(arr); + if (ans1 != ans2) { + System.out.println("出错了!"); + printArray(arr); + System.out.println(ans1); + System.out.println(ans2); + } + } + System.out.println("测试结束"); + } + +} diff --git a/大厂刷题班/class39/Code03_SequenceKDifferentKinds.java b/大厂刷题班/class39/Code03_SequenceKDifferentKinds.java new file mode 100644 index 0000000..82803e7 --- /dev/null +++ b/大厂刷题班/class39/Code03_SequenceKDifferentKinds.java @@ -0,0 +1,63 @@ +package class39; + +// 来自百度 +// 给定一个字符串str,和一个正数k +// str子序列的字符种数必须是k种,返回有多少子序列满足这个条件 +// 已知str中都是小写字母 +// 原始是取mod +// 本节在尝试上,最难的 +// 搞出桶来,组合公式 +public class Code03_SequenceKDifferentKinds { + + // bu -> {6,7,0,0,6,3} + // 0 1 2 3 4 5 + // a b c d e f + // 在桶数组bu[index....] 一定要凑出rest种来!请问几种方法! + public static int f(int[] bu, int index, int rest) { + if (index == bu.length) { + return rest == 0 ? 1 : 0; + } + // 最后形成的子序列,一个index代表的字符也没有! + int p1 = f(bu, index + 1, rest); + // 最后形成的子序列,一定要包含index代表的字符,几个呢?(所有可能性都要算上!) + int p2 = 0; + if (rest > 0) { // 剩余的种数,没耗尽,可以包含当前桶的字符 + p2 = (1 << bu[index] - 1) * f(bu, index + 1, rest - 1); + } + return p1 + p2; + } + + public static int nums(String s, int k) { + char[] str = s.toCharArray(); + int[] counts = new int[26]; + for (char c : str) { + counts[c - 97]++; + } + return ways(counts, 0, k); + } + + public static int ways(int[] c, int i, int r) { + if (r == 0) { + return 1; + } + if (i == c.length) { + return 0; + } + // math(n) -> 2 ^ n -1 + return math(c[i]) * ways(c, i + 1, r - 1) + ways(c, i + 1, r); + } + + // n个不同的球 + // 挑出1个的方法数 + 挑出2个的方法数 + ... + 挑出n个的方法数为: + // C(n,1) + C(n,2) + ... + C(n,n) == (2 ^ n) -1 + public static int math(int n) { + return (1 << n) - 1; + } + + public static void main(String[] args) { + String str = "acbbca"; + int k = 3; + System.out.println(nums(str, k)); + } + +} diff --git a/大厂刷题班/class39/Code04_JumpGameOnMatrix.java b/大厂刷题班/class39/Code04_JumpGameOnMatrix.java new file mode 100644 index 0000000..3acbcc4 --- /dev/null +++ b/大厂刷题班/class39/Code04_JumpGameOnMatrix.java @@ -0,0 +1,239 @@ +package class39; + +// 来自京东 +// 给定一个二维数组matrix,matrix[i][j] = k代表: +// 从(i,j)位置可以随意往右跳<=k步,或者从(i,j)位置可以随意往下跳<=k步 +// 如果matrix[i][j] = 0,代表来到(i,j)位置必须停止 +// 返回从matrix左上角到右下角,至少要跳几次 +// 已知matrix中行数n <= 5000, 列数m <= 5000 +// matrix中的值,<= 5000 +// 最弟弟的技巧也过了。最优解 -> dp+枚举优化(线段树,体系学习班) +public class Code04_JumpGameOnMatrix { + + // 暴力方法,仅仅是做对数器 + // 如果无法到达会返回系统最大值 + public static int jump1(int[][] map) { + return process(map, 0, 0); + } + + // 当前来到的位置是(row,col) + // 目标:右下角 + // 当前最大能跳多远,map[row][col]值决定,只能向右、或者向下 + // 返回,到达右下角,最小跳几次? + // 5000 * 5000 = 25000000 -> 2 * (10 ^ 7) + public static int process(int[][] map, int row, int col) { + if (row == map.length - 1 && col == map[0].length - 1) { + return 0; + } + // 如果没到右下角 + if (map[row][col] == 0) { + return Integer.MAX_VALUE; + } + // 当前位置,可以去很多的位置,next含义: + // 在所有能去的位置里,哪个位置最后到达右下角,跳的次数最少,就是next + int next = Integer.MAX_VALUE; + // 往下能到达的位置,全试一遍 + for (int down = row + 1; down < map.length && (down - row) <= map[row][col]; down++) { + next = Math.min(next, process(map, down, col)); + } + // 往右能到达的位置,全试一遍 + for (int right = col + 1; right < map[0].length && (right - col) <= map[row][col]; right++) { + next = Math.min(next, process(map, row, right)); + } + // 如果所有下一步的位置,没有一个能到右下角,next = 系统最大! + // 返回系统最大! + // next != 系统最大 7 + 1 + return next != Integer.MAX_VALUE ? (next + 1) : next; + } + + // 优化方法, 利用线段树做枚举优化 + // 因为线段树,下标从1开始 + // 所以,该方法中所有的下标,请都从1开始,防止乱! + public static int jump2(int[][] arr) { + int n = arr.length; + int m = arr[0].length; + int[][] map = new int[n + 1][m + 1]; + for (int a = 0, b = 1; a < n; a++, b++) { + for (int c = 0, d = 1; c < m; c++, d++) { + map[b][d] = arr[a][c]; + } + } + SegmentTree[] rowTrees = new SegmentTree[n + 1]; + for (int i = 1; i <= n; i++) { + rowTrees[i] = new SegmentTree(m); + } + SegmentTree[] colTrees = new SegmentTree[m + 1]; + for (int i = 1; i <= m; i++) { + colTrees[i] = new SegmentTree(n); + } + rowTrees[n].update(m, m, 0, 1, m, 1); + colTrees[m].update(n, n, 0, 1, n, 1); + for (int col = m - 1; col >= 1; col--) { + if (map[n][col] != 0) { + int left = col + 1; + int right = Math.min(col + map[n][col], m); + int next = rowTrees[n].query(left, right, 1, m, 1); + if (next != Integer.MAX_VALUE) { + rowTrees[n].update(col, col, next + 1, 1, m, 1); + colTrees[col].update(n, n, next + 1, 1, n, 1); + } + } + } + for (int row = n - 1; row >= 1; row--) { + if (map[row][m] != 0) { + int up = row + 1; + int down = Math.min(row + map[row][m], n); + int next = colTrees[m].query(up, down, 1, n, 1); + if (next != Integer.MAX_VALUE) { + rowTrees[row].update(m, m, next + 1, 1, m, 1); + colTrees[m].update(row, row, next + 1, 1, n, 1); + } + } + } + for (int row = n - 1; row >= 1; row--) { + for (int col = m - 1; col >= 1; col--) { + if (map[row][col] != 0) { + // (row,col) 往右是什么范围呢?[left,right] + int left = col + 1; + int right = Math.min(col + map[row][col], m); + int next1 = rowTrees[row].query(left, right, 1, m, 1); + // (row,col) 往下是什么范围呢?[up,down] + int up = row + 1; + int down = Math.min(row + map[row][col], n); + int next2 = colTrees[col].query(up, down, 1, n, 1); + int next = Math.min(next1, next2); + if (next != Integer.MAX_VALUE) { + rowTrees[row].update(col, col, next + 1, 1, m, 1); + colTrees[col].update(row, row, next + 1, 1, n, 1); + } + } + } + } + return rowTrees[1].query(1, 1, 1, m, 1); + } + + // 区间查询最小值的线段树 + // 注意下标从1开始,不从0开始 + // 比如你传入size = 8 + // 则位置对应为1~8,而不是0~7 + public static class SegmentTree { + private int[] min; + private int[] change; + private boolean[] update; + + public SegmentTree(int size) { + int N = size + 1; + min = new int[N << 2]; + change = new int[N << 2]; + update = new boolean[N << 2]; + update(1, size, Integer.MAX_VALUE, 1, size, 1); + } + + private void pushUp(int rt) { + min[rt] = Math.min(min[rt << 1], min[rt << 1 | 1]); + } + + private void pushDown(int rt, int ln, int rn) { + if (update[rt]) { + update[rt << 1] = true; + update[rt << 1 | 1] = true; + change[rt << 1] = change[rt]; + change[rt << 1 | 1] = change[rt]; + min[rt << 1] = change[rt]; + min[rt << 1 | 1] = change[rt]; + update[rt] = false; + } + } + + // 最后三个参数是固定的, 每次传入相同的值即可: + // l = 1(固定) + // r = size(你设置的线段树大小) + // rt = 1(固定) + public void update(int L, int R, int C, int l, int r, int rt) { + if (L <= l && r <= R) { + update[rt] = true; + change[rt] = C; + min[rt] = C; + return; + } + int mid = (l + r) >> 1; + pushDown(rt, mid - l + 1, r - mid); + if (L <= mid) { + update(L, R, C, l, mid, rt << 1); + } + if (R > mid) { + update(L, R, C, mid + 1, r, rt << 1 | 1); + } + pushUp(rt); + } + + // 最后三个参数是固定的, 每次传入相同的值即可: + // l = 1(固定) + // r = size(你设置的线段树大小) + // rt = 1(固定) + public int query(int L, int R, int l, int r, int rt) { + if (L <= l && r <= R) { + return min[rt]; + } + int mid = (l + r) >> 1; + pushDown(rt, mid - l + 1, r - mid); + int left = Integer.MAX_VALUE; + int right = Integer.MAX_VALUE; + if (L <= mid) { + left = query(L, R, l, mid, rt << 1); + } + if (R > mid) { + right = query(L, R, mid + 1, r, rt << 1 | 1); + } + return Math.min(left, right); + } + + } + + // 为了测试 + public static int[][] randomMatrix(int n, int m, int v) { + int[][] ans = new int[n][m]; + for (int i = 0; i < n; i++) { + for (int j = 0; j < m; j++) { + ans[i][j] = (int) (Math.random() * v); + } + } + return ans; + } + + // 为了测试 + public static void main(String[] args) { + // 先展示一下线段树的用法,假设N=100 + // 初始化时,1~100所有位置的值都是系统最大 + System.out.println("线段树展示开始"); + int N = 100; + SegmentTree st = new SegmentTree(N); + // 查询8~19范围上的最小值 + System.out.println(st.query(8, 19, 1, N, 1)); + // 把6~14范围上对应的值都修改成56 + st.update(6, 14, 56, 1, N, 1); + // 查询8~19范围上的最小值 + System.out.println(st.query(8, 19, 1, N, 1)); + // 以上是线段树的用法,你可以随意使用update和query方法 + // 线段树的详解请看体系学习班 + System.out.println("线段树展示结束"); + + // 以下为正式测试 + int len = 10; + int value = 8; + int testTimes = 10000; + System.out.println("对数器测试开始"); + for (int i = 0; i < testTimes; i++) { + int n = (int) (Math.random() * len) + 1; + int m = (int) (Math.random() * len) + 1; + int[][] map = randomMatrix(n, m, value); + int ans1 = jump1(map); + int ans2 = jump2(map); + if (ans1 != ans2) { + System.out.println("出错了!"); + } + } + System.out.println("对数器测试结束"); + } + +} diff --git a/大厂刷题班/class39/Code05_0123Disappear.java b/大厂刷题班/class39/Code05_0123Disappear.java new file mode 100644 index 0000000..c1b64aa --- /dev/null +++ b/大厂刷题班/class39/Code05_0123Disappear.java @@ -0,0 +1,82 @@ +package class39; + +// 真实笔试,忘了哪个公司,但是绝对大厂 +// 一个子序列的消除规则如下: +// 1) 在某一个子序列中,如果'1'的左边有'0',那么这两个字符->"01"可以消除 +// 2) 在某一个子序列中,如果'3'的左边有'2',那么这两个字符->"23"可以消除 +// 3) 当这个子序列的某个部分消除之后,认为其他字符会自动贴在一起,可以继续寻找消除的机会 +// 比如,某个子序列"0231",先消除掉"23",那么剩下的字符贴在一起变成"01",继续消除就没有字符了 +// 如果某个子序列通过最优良的方式,可以都消掉,那么这样的子序列叫做“全消子序列” +// 一个只由'0'、'1'、'2'、'3'四种字符组成的字符串str,可以生成很多子序列,返回“全消子序列”的最大长度 +// 字符串str长度 <= 200 +// 体系学习班,代码46节,第2题+第3题 +public class Code05_0123Disappear { + + // str[L...R]上,都能消掉的子序列,最长是多少? + public static int f(char[] str, int L, int R) { + if (L >= R) { + return 0; + } + if (L == R - 1) { + return (str[L] == '0' && str[R] == '1') || (str[L] == '2' && str[R] == '3') ? 2 : 0; + } + // L...R 有若干个字符 > 2 + // str[L...R]上,都能消掉的子序列,最长是多少? + // 可能性1,能消掉的子序列完全不考虑str[L],最长是多少? + int p1 = f(str, L + 1, R); + if (str[L] == '1' || str[L] == '3') { + return p1; + } + // str[L] =='0' 或者 '2' + // '0' 去找 '1' + // '2' 去找 '3' + char find = str[L] == '0' ? '1' : '3'; + int p2 = 0; + // L() ...... + for (int i = L + 1; i <= R; i++) { + // L(0) ..... i(1) i+1....R + if (str[i] == find) { + p2 = Math.max(p2, f(str, L + 1, i - 1) + 2 + f(str, i + 1, R)); + } + } + return Math.max(p1, p2); + } + + public static int maxDisappear(String str) { + if (str == null || str.length() == 0) { + return 0; + } + return disappear(str.toCharArray(), 0, str.length() - 1); + } + + // s[l..r]范围上,如题目所说的方式,最长的都能消掉的子序列长度 + public static int disappear(char[] s, int l, int r) { + if (l >= r) { + return 0; + } + if (l == r - 1) { + return (s[l] == '0' && s[r] == '1') || (s[l] == '2' && s[r] == '3') ? 2 : 0; + } + int p1 = disappear(s, l + 1, r); + if (s[l] == '1' || s[l] == '3') { + return p1; + } + int p2 = 0; + char find = s[l] == '0' ? '1' : '3'; + for (int i = l + 1; i <= r; i++) { + if (s[i] == find) { + p2 = Math.max(p2, disappear(s, l + 1, i - 1) + 2 + disappear(s, i + 1, r)); + } + } + return Math.max(p1, p2); + } + + public static void main(String[] args) { + String str1 = "010101"; + System.out.println(maxDisappear(str1)); + + String str2 = "021331"; + System.out.println(maxDisappear(str2)); + } + +} diff --git a/大厂刷题班/class40/Code01_SplitTo01.java b/大厂刷题班/class40/Code01_SplitTo01.java new file mode 100644 index 0000000..25713fc --- /dev/null +++ b/大厂刷题班/class40/Code01_SplitTo01.java @@ -0,0 +1,195 @@ +package class40; + +import java.util.ArrayList; +import java.util.HashMap; + +// 腾讯 +// 分裂问题 +// 一个数n,可以分裂成一个数组[n/2, n%2, n/2] +// 这个数组中哪个数不是1或者0,就继续分裂下去 +// 比如 n = 5,一开始分裂成[2, 1, 2] +// [2, 1, 2]这个数组中不是1或者0的数,会继续分裂下去,比如两个2就继续分裂 +// [2, 1, 2] -> [1, 0, 1, 1, 1, 0, 1] +// 那么我们说,5最后分裂成[1, 0, 1, 1, 1, 0, 1] +// 每一个数都可以这么分裂,在最终分裂的数组中,假设下标从1开始 +// 给定三个数n、l、r,返回n的最终分裂数组里[l,r]范围上有几个1 +// n <= 2 ^ 50,n是long类型 +// r - l <= 50000,l和r是int类型 +// 我们的课加个码: +// n是long类型随意多大都行 +// l和r也是long类型随意多大都行,但要保证l<=r +public class Code01_SplitTo01 { + +// public static long nums3(long n, long l, long r) { +// HashMap lenMap = new HashMap<>(); +// len(n, lenMap); +// HashMap onesMap = new HashMap<>(); +// ones(n, onesMap); +// } + + // n = 100 + // n = 100, 最终裂变的数组,长度多少? + // n = 50, 最终裂变的数组,长度多少? + // n = 25, 最终裂变的数组,长度多少? + // .. + // n = 1 ,.最终裂变的数组,长度多少? + // 请都填写到lenMap中去! + public static long len(long n, HashMap lenMap) { + if (n == 1 || n == 0) { + lenMap.put(n, 1L); + return 1; + } else { + // n > 1 + long half = len(n / 2, lenMap); + long all = half * 2 + 1; + lenMap.put(n, all); + return all; + } + } + + // n = 100 + // n = 100, 最终裂变的数组中,一共有几个1 + // n = 50, 最终裂变的数组,一共有几个1 + // n = 25, 最终裂变的数组,一共有几个1 + // .. + // n = 1 ,.最终裂变的数组,一共有几个1 + // 请都填写到onesMap中去! + public static long ones(long num, HashMap onesMap) { + if (num == 1 || num == 0) { + onesMap.put(num, num); + return num; + } + // n > 1 + long half = ones(num / 2, onesMap); + long mid = num % 2 == 1 ? 1 : 0; + long all = half * 2 + mid; + onesMap.put(num, all); + return all; + } + + // + + public static long nums1(long n, long l, long r) { + if (n == 1 || n == 0) { + return n == 1 ? 1 : 0; + } + long half = size(n / 2); + long left = l > half ? 0 : nums1(n / 2, l, Math.min(half, r)); + long mid = (l > half + 1 || r < half + 1) ? 0 : (n & 1); + long right = r > half + 1 ? nums1(n / 2, Math.max(l - half - 1, 1), r - half - 1) : 0; + return left + mid + right; + } + + public static long size(long n) { + if (n == 1 || n == 0) { + return 1; + } else { + long half = size(n / 2); + return (half << 1) + 1; + } + } + + public static long nums2(long n, long l, long r) { + HashMap allMap = new HashMap<>(); + return dp(n, l, r, allMap); + } + + public static long dp(long n, long l, long r, HashMap allMap) { + if (n == 1 || n == 0) { + return n == 1 ? 1 : 0; + } + long half = size(n / 2); + long all = (half << 1) + 1; + long mid = n & 1; + if (l == 1 && r >= all) { + if (allMap.containsKey(n)) { + return allMap.get(n); + } else { + long count = dp(n / 2, 1, half, allMap); + long ans = (count << 1) + mid; + allMap.put(n, ans); + return ans; + } + } else { + mid = (l > half + 1 || r < half + 1) ? 0 : mid; + long left = l > half ? 0 : dp(n / 2, l, Math.min(half, r), allMap); + long right = r > half + 1 ? dp(n / 2, Math.max(l - half - 1, 1), r - half - 1, allMap) : 0; + return left + mid + right; + } + } + + // 为了测试 + // 彻底生成n的最终分裂数组返回 + public static ArrayList test(long n) { + ArrayList arr = new ArrayList<>(); + process(n, arr); + return arr; + } + + public static void process(long n, ArrayList arr) { + if (n == 1 || n == 0) { + arr.add((int) n); + } else { + process(n / 2, arr); + arr.add((int) (n % 2)); + process(n / 2, arr); + } + } + + public static void main(String[] args) { + long num = 671; + ArrayList ans = test(num); + int testTime = 10000; + System.out.println("功能测试开始"); + for (int i = 0; i < testTime; i++) { + int a = (int) (Math.random() * ans.size()) + 1; + int b = (int) (Math.random() * ans.size()) + 1; + int l = Math.min(a, b); + int r = Math.max(a, b); + int ans1 = 0; + for (int j = l - 1; j < r; j++) { + if (ans.get(j) == 1) { + ans1++; + } + } + long ans2 = nums1(num, l, r); + long ans3 = nums2(num, l, r); + if (ans1 != ans2 || ans1 != ans3) { + System.out.println("出错了!"); + } + } + System.out.println("功能测试结束"); + System.out.println("=============="); + + System.out.println("性能测试开始"); + num = (2L << 50) + 22517998136L; + long l = 30000L; + long r = 800000200L; + long start; + long end; + start = System.currentTimeMillis(); + System.out.println("nums1结果 : " + nums1(num, l, r)); + end = System.currentTimeMillis(); + System.out.println("nums1花费时间(毫秒) : " + (end - start)); + + start = System.currentTimeMillis(); + System.out.println("nums2结果 : " + nums2(num, l, r)); + end = System.currentTimeMillis(); + System.out.println("nums2花费时间(毫秒) : " + (end - start)); + System.out.println("性能测试结束"); + System.out.println("=============="); + + System.out.println("单独展示nums2方法强悍程度测试开始"); + num = (2L << 55) + 22517998136L; + l = 30000L; + r = 6431000002000L; + start = System.currentTimeMillis(); + System.out.println("nums2结果 : " + nums2(num, l, r)); + end = System.currentTimeMillis(); + System.out.println("nums2花费时间(毫秒) : " + (end - start)); + System.out.println("单独展示nums2方法强悍程度测试结束"); + System.out.println("=============="); + + } + +} diff --git a/大厂刷题班/class40/Code02_Mod3Max.java b/大厂刷题班/class40/Code02_Mod3Max.java new file mode 100644 index 0000000..6bbc985 --- /dev/null +++ b/大厂刷题班/class40/Code02_Mod3Max.java @@ -0,0 +1,247 @@ +package class40; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.TreeSet; + +// 来自去哪儿网 +// 给定一个arr,里面的数字都是0~9 +// 你可以随意使用arr中的数字,哪怕打乱顺序也行 +// 请拼出一个能被3整除的,最大的数字,用str形式返回 +public class Code02_Mod3Max { + + public static String max1(int[] arr) { + Arrays.sort(arr); + for (int l = 0, r = arr.length - 1; l < r; l++, r--) { + int tmp = arr[l]; + arr[l] = arr[r]; + arr[r] = tmp; + } + StringBuilder builder = new StringBuilder(); + TreeSet set = new TreeSet<>((a, b) -> Integer.valueOf(b).compareTo(Integer.valueOf(a))); + process1(arr, 0, builder, set); + return set.isEmpty() ? "" : set.first(); + } + + public static void process1(int[] arr, int index, StringBuilder builder, TreeSet set) { + if (index == arr.length) { + if (builder.length() != 0 && Integer.valueOf(builder.toString()) % 3 == 0) { + set.add(builder.toString()); + } + } else { + process1(arr, index + 1, builder, set); + builder.append(arr[index]); + process1(arr, index + 1, builder, set); + builder.deleteCharAt(builder.length() - 1); + } + } + + public static String max2(int[] arr) { + if (arr == null || arr.length == 0) { + return ""; + } + Arrays.sort(arr); + for (int l = 0, r = arr.length - 1; l < r; l++, r--) { + int tmp = arr[l]; + arr[l] = arr[r]; + arr[r] = tmp; + } + if (arr[0] == 0) { + return "0"; + } + String ans = process2(arr, 0, 0); + String res = ans.replaceAll("^(0+)", ""); + if (!res.equals("")) { + return res; + } + return ans.equals("") ? ans : "0"; + } + + // arr中的数字一定是0~9 + // arr是经过排序的,并且是从大到小排序,比如[9,8,7,7,7,3,1]等 + // 这个递归函数的含义 : + // 在arr[index...一直到最后]上做选择,arr[0...index-1]就当不存在 + // 每个位置的字符可以要、也可以不要,但是!选出来的数字拼完之后的结果,在%3之后,余数一定要是mod! + // 返回在上面设定的情况下,最大的数是多少? + // 如果存在这样的数,返回字符串的形式 + // 如果不存在这样的数,返回特殊字符串,比如"$",代表不可能 + // 这个递归函数可以很轻易的改出动态规划 + public static String process2(int[] arr, int index, int mod) { + if (index == arr.length) { + return mod == 0 ? "" : "$"; + } + String p1 = "$"; + int nextMod = nextMod(mod, arr[index] % 3); + String next = process2(arr, index + 1, nextMod); + if (!next.equals("$")) { + p1 = String.valueOf(arr[index]) + next; + } + String p2 = process2(arr, index + 1, mod); + if (p1.equals("$") && p2.equals("$")) { + return "$"; + } + if (!p1.equals("$") && !p2.equals("$")) { + return smaller(p1, p2) ? p2 : p1; + } + return p1.equals("$") ? p2 : p1; + } + + public static int nextMod(int require, int current) { + if (require == 0) { + if (current == 0) { + return 0; + } else if (current == 1) { + return 2; + } else { + return 1; + } + } else if (require == 1) { + if (current == 0) { + return 1; + } else if (current == 1) { + return 0; + } else { + return 2; + } + } else { // require == 2 + if (current == 0) { + return 2; + } else if (current == 1) { + return 1; + } else { + return 0; + } + } + } + + public static boolean smaller(String p1, String p2) { + if (p1.length() != p2.length()) { + return p1.length() < p2.length(); + } + return p1.compareTo(p2) < 0; + } + + // 贪心的思路解法 : + // 先得到数组的累加和,记为sum + // 1) 如果sum%3==0,说明所有数从大到小拼起来就可以了 + // 2) 如果sum%3==1,说明多了一个余数1, + // 只需要删掉一个最小的数(该数是%3==1的数); + // 如果没有,只需要删掉两个最小的数(这两个数都是%3==2的数); + // 3) 如果sum%3==2,说明多了一个余数2, + // 只需要删掉一个最小的数(该数是%3==2的数); + // 如果没有,只需要删掉两个最小的数(这两个数都是%3==1的数); + // 如果上面都做不到,说明拼不成 + public static String max3(int[] A) { + if (A == null || A.length == 0) { + return ""; + } + int mod = 0; + ArrayList arr = new ArrayList<>(); + for (int num : A) { + arr.add(num); + mod += num; + mod %= 3; + } + if ((mod == 1 || mod == 2) && !remove(arr, mod, 3 - mod)) { + return ""; + } + if (arr.isEmpty()) { + return ""; + } + arr.sort((a, b) -> b - a); + if (arr.get(0) == 0) { + return "0"; + } + StringBuilder builder = new StringBuilder(); + for (int num : arr) { + builder.append(num); + } + return builder.toString(); + } + + // 在arr中,要么删掉最小的一个、且%3之后余数是first的数 + // 如果做不到,删掉最小的两个、且%3之后余数是second的数 + // 如果能做到返回true,不能做到返回false + public static boolean remove(ArrayList arr, int first, int second) { + if (arr.size() == 0) { + return false; + } + arr.sort((a, b) -> compare(a, b, first, second)); + int size = arr.size(); + if (arr.get(size - 1) % 3 == first) { + arr.remove(size - 1); + return true; + } else if (size > 1 && arr.get(size - 1) % 3 == second && arr.get(size - 2) % 3 == second) { + arr.remove(size - 1); + arr.remove(size - 2); + return true; + } else { + return false; + } + } + + // a和b比较: + // 如果余数一样,谁大谁放前面 + // 如果余数不一样,余数是0的放最前面、余数是s的放中间、余数是f的放最后 + public static int compare(int a, int b, int f, int s) { + int ma = a % 3; + int mb = b % 3; + if (ma == mb) { + return b - a; + } else { + if (ma == 0 || mb == 0) { + return ma == 0 ? -1 : 1; + } else { + return ma == s ? -1 : 1; + } + } + } + + // 为了测试 + public static int[] randomArray(int len) { + int[] arr = new int[len]; + for (int i = 0; i < len; i++) { + arr[i] = (int) (Math.random() * 10); + } + return arr; + } + + // 为了测试 + public static int[] copyArray(int[] arr) { + int[] ans = new int[arr.length]; + for (int i = 0; i < arr.length; i++) { + ans[i] = arr[i]; + } + return ans; + } + + // 为了测试 + public static void main(String[] args) { + int N = 10; + int testTimes = 10000; + System.out.println("测试开始"); + for (int i = 0; i < testTimes; i++) { + int len = (int) (Math.random() * N); + int[] arr1 = randomArray(len); + int[] arr2 = copyArray(arr1); + int[] arr3 = copyArray(arr1); + String ans1 = max1(arr1); + String ans2 = max2(arr2); + String ans3 = max3(arr3); + if (!ans1.equals(ans2) || !ans1.equals(ans3)) { + System.out.println("出错了!"); + for (int num : arr3) { + System.out.print(num + " "); + } + System.out.println(); + System.out.println(ans1); + System.out.println(ans2); + System.out.println(ans3); + break; + } + } + System.out.println("测试结束"); + + } + +} diff --git a/大厂刷题班/class40/Code03_MaxMeetingScore.java b/大厂刷题班/class40/Code03_MaxMeetingScore.java new file mode 100644 index 0000000..467b08d --- /dev/null +++ b/大厂刷题班/class40/Code03_MaxMeetingScore.java @@ -0,0 +1,109 @@ +package class40; + +import java.util.Arrays; +import java.util.PriorityQueue; + +// 给定int[][] meetings,比如 +// { +// {66, 70} 0号会议截止时间66,获得收益70 +// {25, 90} 1号会议截止时间25,获得收益90 +// {50, 30} 2号会议截止时间50,获得收益30 +// } +// 一开始的时间是0,任何会议都持续10的时间,但是一个会议一定要在该会议截止时间之前开始 +// 只有一个会议室,任何会议不能共用会议室,一旦一个会议被正确安排,将获得这个会议的收益 +// 请返回最大的收益 +public class Code03_MaxMeetingScore { + + public static int maxScore1(int[][] meetings) { + Arrays.sort(meetings, (a, b) -> a[0] - b[0]); + int[][] path = new int[meetings.length][]; + int size = 0; + return process1(meetings, 0, path, size); + } + + public static int process1(int[][] meetings, int index, int[][] path, int size) { + if (index == meetings.length) { + int time = 0; + int ans = 0; + for (int i = 0; i < size; i++) { + if (time + 10 <= path[i][0]) { + ans += path[i][1]; + time += 10; + } else { + return 0; + } + } + return ans; + } + int p1 = process1(meetings, index + 1, path, size); + path[size] = meetings[index]; + int p2 = process1(meetings, index + 1, path, size + 1); + // path[size] = null; + return Math.max(p1, p2); + } + + public static int maxScore2(int[][] meetings) { + Arrays.sort(meetings, (a, b) -> a[0] - b[0]); + PriorityQueue heap = new PriorityQueue<>(); + int time = 0; + // 已经把所有会议,按照截止时间,从小到大,排序了! + // 截止时间一样的,谁排前谁排后,无所谓 + for (int i = 0; i < meetings.length; i++) { + if (time + 10 <= meetings[i][0]) { + heap.add(meetings[i][1]); + time += 10; + } else { + if (!heap.isEmpty() && heap.peek() < meetings[i][1]) { + heap.poll(); + heap.add(meetings[i][1]); + } + } + } + int ans = 0; + while (!heap.isEmpty()) { + ans += heap.poll(); + } + return ans; + } + + public static int[][] randomMeetings(int n, int t, int s) { + int[][] ans = new int[n][2]; + for (int i = 0; i < n; i++) { + ans[i][0] = (int) (Math.random() * t) + 1; + ans[i][1] = (int) (Math.random() * s) + 1; + } + return ans; + } + + public static int[][] copyMeetings(int[][] meetings) { + int n = meetings.length; + int[][] ans = new int[n][2]; + for (int i = 0; i < n; i++) { + ans[i][0] = meetings[i][0]; + ans[i][1] = meetings[i][1]; + } + return ans; + } + + public static void main(String[] args) { + int n = 12; + int t = 100; + int s = 500; + int testTime = 10000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int size = (int) (Math.random() * n) + 1; + int[][] meetings1 = randomMeetings(size, t, s); + int[][] meetings2 = copyMeetings(meetings1); + int ans1 = maxScore1(meetings1); + int ans2 = maxScore2(meetings2); + if (ans1 != ans2) { + System.out.println("出错了!"); + System.out.println(ans1); + System.out.println(ans2); + } + } + System.out.println("测试结束"); + } + +} diff --git a/大厂刷题班/class40/Code04_LetASorted.java b/大厂刷题班/class40/Code04_LetASorted.java new file mode 100644 index 0000000..051bd9b --- /dev/null +++ b/大厂刷题班/class40/Code04_LetASorted.java @@ -0,0 +1,54 @@ +package class40; + +// 给定两个数组A和B,长度都是N +// A[i]不可以在A中和其他数交换,只可以选择和B[i]交换(0<=i= lastA && process1(A, B, i + 1, A[i])) { + return true; + } + // 第二种选择 : A[i]和B[i]交换 + if (B[i] >= lastA && process1(A, B, i + 1, B[i])) { + return true; + } + return false; + } + + public static boolean letASorted2(int[] A, int[] B) { + return process2(A, B, 0, true); + } + + // 当前推进到了i位置,对于A和B都是i位置 + // A[i]前一个数字是否来自A : + // 如果来自A,fromA = true;如果来自B,fromA = false; + // 能否通过题意中的操作,A[i] B[i] 让A整体有序 + // 好处:可变参数成了int + boolean,时间复杂度可以做到O(N) + public static boolean process2(int[] A, int[] B, int i, boolean fromA) { + if (i == A.length) { + return true; + } + if (i == 0 || (A[i] >= (fromA ? A[i - 1] : B[i - 1])) && process2(A, B, i + 1, true)) { + return true; + } + if (i == 0 || (B[i] >= (fromA ? A[i - 1] : B[i - 1])) && process2(A, B, i + 1, false)) { + return true; + } + return false; + } + + // 也可以彻底的贪心:就让A此时的值尽量小!也是可以的。时间复杂度O(N) + +} diff --git a/大厂刷题班/class40/Code05_AllSame.java b/大厂刷题班/class40/Code05_AllSame.java new file mode 100644 index 0000000..ae8359b --- /dev/null +++ b/大厂刷题班/class40/Code05_AllSame.java @@ -0,0 +1,135 @@ +package class40; + +// 来自腾讯 +// 比如arr = {3,1,2,4} +// 下标对应是:0 1 2 3 +// 你最开始选择一个下标进行操作,一旦最开始确定了是哪个下标,以后都只能在这个下标上进行操作 +// 比如你选定1下标,1下标上面的数字是1,你可以选择变化这个数字,比如你让这个数字变成2 +// 那么arr = {3,2,2,4} +// 下标对应是:0 1 2 3 +// 因为你最开始确定了1这个下标,所以你以后都只能对这个下标进行操作, +// 但是,和你此时下标上的数字一样的、且位置连成一片的数字,会跟着一起变 +// 比如你选择让此时下标1的数字2变成3, +// 那么arr = {3,3,3,4} 可以看到下标1和下标2的数字一起变成3,这是规则!一定会一起变 +// 下标对应是:0 1 2 3 +// 接下来,你还是只能对1下标进行操作,那么数字一样的、且位置连成一片的数字(arr[0~2]这个范围)都会一起变 +// 决定变成4 +// 那么arr = {4,4,4,4} +// 下标对应是:0 1 2 3 +// 至此,所有数都成一样的了,你在下标1上做了3个决定(第一次变成2,第二次变成3,第三次变成4), +// 因为联动规则,arr全刷成一种数字了 +// 给定一个数组arr,最开始选择哪个下标,你随意 +// 你的目的是一定要让arr都成为一种数字,注意联动效果会一直生效 +// 返回最小的变化数 +// arr长度 <= 200, arr中的值 <= 10^6 +public class Code05_AllSame { + + public static int allSame1(int[] arr) { + int ans = Integer.MAX_VALUE; + for (int i = 0; i < arr.length; i++) { + ans = Math.min(ans, process1(arr, i - 1, arr[i], i + 1)); + } + return ans; + } + + // 左边arr[0..left],如果left == -1,说明没有左边了 + // 右边arr[right...n-1],如果right == n,说明没有右边了 + // 中间的值是midV,中间的值代表中间一整个部分的值,中间部分有可能是一个数,也可能是一堆数,但是值都为midV + // 返回arr都刷成一样的,最小代价是多少 + // left 可能性 : N + // right 可能性 : N + // midV 可能性 : arr中的最大值! + public static int process1(int[] arr, int left, int midV, int right) { + for (; left >= 0 && arr[left] == midV;) { + left--; + } + for (; right < arr.length && arr[right] == midV;) { + right++; + } + if (left == -1 && right == arr.length) { + return 0; + } + int p1 = Integer.MAX_VALUE; + if (left >= 0) { + p1 = process1(arr, left - 1, arr[left], right) + 1; + } + int p2 = Integer.MAX_VALUE; + if (right < arr.length) { + p2 = process1(arr, left, arr[right], right + 1) + 1; + } + return Math.min(p1, p2); + } + + public static int allSame2(int[] arr) { + int ans = Integer.MAX_VALUE; + for (int i = 0; i < arr.length; i++) { + ans = Math.min(ans, process2(arr, i - 1, true, i + 1)); + } + return ans; + } + + // 左边arr[0..l],如果left == -1,说明没有左边了 + // 右边arr[r...n-1],如果right == n,说明没有右边了 + // 中间的值代表arr[l+1...r-1]这个部分的值已经刷成了一种 + // 中间的值,如果和arr[l+1]一样,isLeft就是true + // 中间的值,如果和arr[r-1]一样,isLeft就是false + // 返回arr都刷成一样的,最小代价是多少 + // left 可能性 : N + // right 可能性 : N + // isLeft 可能性 : true/false,两种 + public static int process2(int[] arr, int l, boolean isLeft, int r) { + int left = l; + for (; left >= 0 && arr[left] == (isLeft ? arr[l + 1] : arr[r - 1]);) { + left--; + } + int right = r; + for (; right < arr.length && arr[right] == (isLeft ? arr[l + 1] : arr[r - 1]);) { + right++; + } + if (left == -1 && right == arr.length) { + return 0; + } + int p1 = Integer.MAX_VALUE; + if (left >= 0) { + p1 = process2(arr, left - 1, true, right) + 1; + } + int p2 = Integer.MAX_VALUE; + if (right < arr.length) { + p2 = process2(arr, left, false, right + 1) + 1; + } + return Math.min(p1, p2); + } + // 如上的递归,请改动态规划,具体参考体系学习班,动态规划大章节! + + // 为了测试 + public static int[] randomArray(int maxSize, int maxNum) { + int size = 2 + (int) (Math.random() * maxSize); + int[] arr = new int[size]; + for (int i = 0; i < size; i++) { + arr[i] = 1 + (int) (Math.random() * maxSize); + } + return arr; + } + + // 为了测试 + public static void main(String[] args) { + System.out.println("测试开始"); + for (int i = 0; i < 10000; i++) { + int[] arr = randomArray(20, 10); + int ans1 = allSame1(arr); + int ans2 = allSame2(arr); + if (ans1 != ans2) { + System.out.println("出错了!!!"); + for (int i1 : arr) { + System.out.print(i1 + " "); + } + System.out.println(); + System.out.println(ans1); + System.out.println(ans2); + break; + } + } + System.out.println("测试结束"); + } + +} diff --git a/大厂刷题班/class41/Code01_MinSwapTimes.java b/大厂刷题班/class41/Code01_MinSwapTimes.java new file mode 100644 index 0000000..70ad912 --- /dev/null +++ b/大厂刷题班/class41/Code01_MinSwapTimes.java @@ -0,0 +1,88 @@ +package class41; + +// 来自小红书 +// 一个无序数组长度为n,所有数字都不一样,并且值都在[0...n-1]范围上 +// 返回让这个无序数组变成有序数组的最小交换次数 +public class Code01_MinSwapTimes { + + // 纯暴力,arr长度大一点都会超时 + // 但是绝对正确 + public static int minSwap1(int[] arr) { + return process1(arr, 0); + } + + // 让arr变有序,最少的交换次数是多少!返回 + // times, 之前已经做了多少次交换 + public static int process1(int[] arr, int times) { + boolean sorted = true; + for (int i = 1; i < arr.length; i++) { + if (arr[i - 1] > arr[i]) { + sorted = false; + break; + } + } + if (sorted) { + return times; + } + // 数组现在是无序的状态! + if (times >= arr.length - 1) { + return Integer.MAX_VALUE; + } + int ans = Integer.MAX_VALUE; + for (int i = 0; i < arr.length; i++) { + for (int j = i + 1; j < arr.length; j++) { + swap(arr, i, j); + ans = Math.min(ans, process1(arr, times + 1)); + swap(arr, i, j); + } + } + return ans; + } + + public static void swap(int[] arr, int i, int j) { + int tmp = arr[i]; + arr[i] = arr[j]; + arr[j] = tmp; + } + + // 已知arr中,只有0~n-1这些值,并且都出现1次 + public static int minSwap2(int[] arr) { + int ans = 0; + for (int i = 0; i < arr.length; i++) { + while (i != arr[i]) { + swap(arr, i, arr[i]); + ans++; + } + } + return ans; + } + + // 为了测试 + public static int[] randomArray(int len) { + int[] arr = new int[len]; + for (int i = 0; i < len; i++) { + arr[i] = i; + } + for (int i = 0; i < len; i++) { + swap(arr, i, (int) (Math.random() * len)); + } + return arr; + } + + public static void main(String[] args) { + int n = 6; + int testTime = 2000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int len = (int) (Math.random() * n) + 1; + int[] arr = randomArray(len); + int ans1 = minSwap1(arr); + int ans2 = minSwap2(arr); + if (ans1 != ans2) { + System.out.println("出错了!"); + } + } + System.out.println("测试结束"); + } + +} diff --git a/大厂刷题班/class41/Code02_PoemProblem.java b/大厂刷题班/class41/Code02_PoemProblem.java new file mode 100644 index 0000000..617ca29 --- /dev/null +++ b/大厂刷题班/class41/Code02_PoemProblem.java @@ -0,0 +1,381 @@ +package class41; + +import java.util.Arrays; +import java.util.HashMap; + +// 来自小红书 +// 有四种诗的韵律分别为: AABB、ABAB、ABBA、AAAA +// 比如 : 1 1 3 3就属于AABB型的韵律、6 6 6 6就属于AAAA型的韵律等等 +// 一个数组arr,当然可以生成很多的子序列,如果某个子序列一直以韵律的方式连接起来,我们称这样的子序列是有效的 +// 比如, arr = { 1, 1, 15, 1, 34, 1, 2, 67, 3, 3, 2, 4, 15, 3, 17, 4, 3, 7, 52, 7, 81, 9, 9 } +// arr的一个子序列为{1, 1, 1, 1, 2, 3, 3, 2, 4, 3, 4, 3, 7, 7, 9, 9} +// 其中1, 1, 1, 1是AAAA、2, 3, 3, 2是ABBA、4, 3, 4, 3是ABAB、7, 7, 9, 9是AABB +// 可以看到,整个子序列一直以韵律的方式连接起来,所以这个子序列是有效的 +// 给定一个数组arr, 返回最长的有效子序列长度 +// 题目限制 : arr长度 <= 4000, arr中的值<= 10^9 +// 离散化之后,arr长度 <= 4000, arr中的值<= 4000 +public class Code02_PoemProblem { + + // arr[i.....]符合规则连接的最长子序列长度 +// public static int zuo(int[] arr, int i) { +// if (i + 4 > arr.length) { +// return 0; +// } +// // 最终的,符合规则连接的最长子序列长度,就是不要i位置的字符 +// int p0 = zuo(arr, i + 1); +// // p1使用for循环搞定的! +// int p1 = 找到arr[i..s]是最短的,且能搞出AABB来的(4个) + zuo(arr, s + 1); +// // p2使用for循环搞定的! +// int p2 = 找到arr[i..t]是最短的,且能搞出ABAB来的(4个) + zuo(arr, t + 1); +// // p3使用for循环搞定的! +// int p3 = 找到arr[i..k]是最短的,且能搞出ABBA来的(4个) + zuo(arr, k + 1); +// // p4没用 +// int p4 = 找到arr[i..f]是最短的,且能搞出AAAA来的(4个) + zuo(arr, f + 1); +// return p0~p4的最大值 +// } + + // AABB + // ABAB + // ABBA + // AAAA + public static int maxLen1(int[] arr) { + if (arr == null || arr.length < 4) { + return 0; + } + int[] path = new int[arr.length]; + return process1(arr, 0, path, 0); + } + + public static int process1(int[] arr, int index, int[] path, int size) { + if (index == arr.length) { + if (size % 4 != 0) { + return 0; + } else { + for (int i = 0; i < size; i += 4) { + if (!valid(path, i)) { + return 0; + } + } + return size; + } + } else { + int p1 = process1(arr, index + 1, path, size); + path[size] = arr[index]; + int p2 = process1(arr, index + 1, path, size + 1); + return Math.max(p1, p2); + } + } + + public static boolean valid(int[] p, int i) { + // AABB + // ABAB + // ABBA + // AAAA + return (p[i] == p[i + 1] && p[i + 2] == p[i + 3]) + || (p[i] == p[i + 2] && p[i + 1] == p[i + 3] && p[i] != p[i + 1]) + || (p[i] == p[i + 3] && p[i + 1] == p[i + 2] && p[i] != p[i + 1]); + } + + // 0 : [3,6,9] + // 1 : [2,7,13] + // 2 : [23] + // [ + // [3,6,9] + // ] + public static int maxLen2(int[] arr) { + if (arr == null || arr.length < 4) { + return 0; + } + int n = arr.length; + int[] sorted = Arrays.copyOf(arr, n); + Arrays.sort(sorted); + HashMap vmap = new HashMap<>(); + int index = 0; + vmap.put(sorted[0], index++); + for (int i = 1; i < n; i++) { + if (sorted[i] != sorted[i - 1]) { + vmap.put(sorted[i], index++); + } + } + int[] sizeArr = new int[index]; + for (int i = 0; i < n; i++) { + arr[i] = vmap.get(arr[i]); + sizeArr[arr[i]]++; + } + int[][] imap = new int[index][]; + for (int i = 0; i < index; i++) { + imap[i] = new int[sizeArr[i]]; + } + for (int i = n - 1; i >= 0; i--) { + imap[arr[i]][--sizeArr[arr[i]]] = i; + } + return process2(arr, imap, 0); + } + + // AABB + // ABAB + // ABBA + // AAAA + public static int process2(int[] varr, int[][] imap, int i) { + if (i + 4 > varr.length) { + return 0; + } + int p0 = process2(varr, imap, i + 1); + // AABB + int p1 = 0; + int rightClosedP1A2 = rightClosed(imap, varr[i], i); + if (rightClosedP1A2 != -1) { + for (int next = rightClosedP1A2 + 1; next < varr.length; next++) { + if (varr[i] != varr[next]) { + int rightClosedP1B2 = rightClosed(imap, varr[next], next); + if (rightClosedP1B2 != -1) { + p1 = Math.max(p1, 4 + process2(varr, imap, rightClosedP1B2 + 1)); + } + } + } + } + + // ABAB + int p2 = 0; + for (int p2B1 = i + 1; p2B1 < varr.length; p2B1++) { + if (varr[i] != varr[p2B1]) { + int rightClosedP2A2 = rightClosed(imap, varr[i], p2B1); + if (rightClosedP2A2 != -1) { + int rightClosedP2B2 = rightClosed(imap, varr[p2B1], rightClosedP2A2); + if (rightClosedP2B2 != -1) { + p2 = Math.max(p2, 4 + process2(varr, imap, rightClosedP2B2 + 1)); + } + } + } + } + + // ABBA + int p3 = 0; + for (int p3B1 = i + 1; p3B1 < varr.length; p3B1++) { + if (varr[i] != varr[p3B1]) { + int rightClosedP3B2 = rightClosed(imap, varr[p3B1], p3B1); + if (rightClosedP3B2 != -1) { + int rightClosedP3A2 = rightClosed(imap, varr[i], rightClosedP3B2); + if (rightClosedP3A2 != -1) { + p3 = Math.max(p3, 4 + process2(varr, imap, rightClosedP3A2 + 1)); + } + } + } + } + // AAAA + int p4 = 0; + int rightClosedP4A2 = rightClosed(imap, varr[i], i); + int rightClosedP4A3 = rightClosedP4A2 == -1 ? -1 : rightClosed(imap, varr[i], rightClosedP4A2); + int rightClosedP4A4 = rightClosedP4A3 == -1 ? -1 : rightClosed(imap, varr[i], rightClosedP4A3); + if (rightClosedP4A4 != -1) { + p4 = Math.max(p4, 4 + process2(varr, imap, rightClosedP4A4 + 1)); + } + return Math.max(p0, Math.max(Math.max(p1, p2), Math.max(p3, p4))); + } + + public static int rightClosed(int[][] imap, int v, int i) { + int left = 0; + int right = imap[v].length - 1; + int ans = -1; + while (left <= right) { + int mid = (left + right) / 2; + if (imap[v][mid] <= i) { + left = mid + 1; + } else { + ans = mid; + right = mid - 1; + } + } + return ans == -1 ? -1 : imap[v][ans]; + } + + public static int maxLen3(int[] arr) { + if (arr == null || arr.length < 4) { + return 0; + } + int n = arr.length; + int[] sorted = Arrays.copyOf(arr, n); + Arrays.sort(sorted); + HashMap vmap = new HashMap<>(); + int index = 0; + vmap.put(sorted[0], index++); + for (int i = 1; i < n; i++) { + if (sorted[i] != sorted[i - 1]) { + vmap.put(sorted[i], index++); + } + } + int[] sizeArr = new int[index]; + for (int i = 0; i < n; i++) { + arr[i] = vmap.get(arr[i]); + sizeArr[arr[i]]++; + } + int[][] imap = new int[index][]; + for (int i = 0; i < index; i++) { + imap[i] = new int[sizeArr[i]]; + } + for (int i = n - 1; i >= 0; i--) { + imap[arr[i]][--sizeArr[arr[i]]] = i; + } + int[] dp = new int[n + 1]; + for (int i = n - 4; i >= 0; i--) { + int p0 = dp[i + 1]; + // AABB + int p1 = 0; + int rightClosedP1A2 = rightClosed(imap, arr[i], i); + if (rightClosedP1A2 != -1) { + for (int next = rightClosedP1A2 + 1; next < arr.length; next++) { + if (arr[i] != arr[next]) { + int rightClosedP1B2 = rightClosed(imap, arr[next], next); + if (rightClosedP1B2 != -1) { + p1 = Math.max(p1, 4 + dp[rightClosedP1B2 + 1]); + } + } + } + } + + // ABAB + int p2 = 0; + for (int p2B1 = i + 1; p2B1 < arr.length; p2B1++) { + if (arr[i] != arr[p2B1]) { + int rightClosedP2A2 = rightClosed(imap, arr[i], p2B1); + if (rightClosedP2A2 != -1) { + int rightClosedP2B2 = rightClosed(imap, arr[p2B1], rightClosedP2A2); + if (rightClosedP2B2 != -1) { + p2 = Math.max(p2, 4 + dp[rightClosedP2B2 + 1]); + } + } + } + } + + // ABBA + int p3 = 0; + for (int p3B1 = i + 1; p3B1 < arr.length; p3B1++) { + if (arr[i] != arr[p3B1]) { + int rightClosedP3B2 = rightClosed(imap, arr[p3B1], p3B1); + if (rightClosedP3B2 != -1) { + int rightClosedP3A2 = rightClosed(imap, arr[i], rightClosedP3B2); + if (rightClosedP3A2 != -1) { + p3 = Math.max(p3, 4 + dp[rightClosedP3A2 + 1]); + } + } + } + } + // AAAA + int p4 = 0; + int rightClosedP4A2 = rightClosed(imap, arr[i], i); + int rightClosedP4A3 = rightClosedP4A2 == -1 ? -1 : rightClosed(imap, arr[i], rightClosedP4A2); + int rightClosedP4A4 = rightClosedP4A3 == -1 ? -1 : rightClosed(imap, arr[i], rightClosedP4A3); + if (rightClosedP4A4 != -1) { + p4 = Math.max(p4, 4 + dp[rightClosedP4A4 + 1]); + } + dp[i] = Math.max(p0, Math.max(Math.max(p1, p2), Math.max(p3, p4))); + } + return dp[0]; + } + + // 课堂有同学提出了贪心策略(这题还真是有贪心策略),是正确的 + // AABB + // ABAB + // ABBA + // AAAA + // 先看前三个规则:AABB、ABAB、ABBA + // 首先A、A、B、B的全排列为: + // AABB -> AABB + // ABAB -> ABAB + // ABBA -> ABBA + // BBAA -> 等同于AABB,因为A和B谁在前、谁在后都算是 : AABB的范式 + // BABA -> 等同于ABAB,因为A和B谁在前、谁在后都算是 : ABAB的范式 + // BAAB -> 等同于ABBA,因为A和B谁在前、谁在后都算是 : ABBA的范式 + // 也就是说,AABB、ABAB、ABBA这三个规则,可以这么用: + // 只要有两个不同的数,都出现2次,那么这一共4个数就一定符合韵律规则。 + // 所以: + // 1) 当来到arr中的一个数字num的时候, + // 如果num已经出现了2次了, 只要之前还有一个和num不同的数, + // 也出现了两次,则一定符合了某个规则, 长度直接+4,然后清空所有的统计 + // 2) 当来到arr中的一个数字num的时候, + // 如果num已经出现了4次了(规则四), 长度直接+4,然后清空所有的统计 + // 但是如果我去掉某个规则,该贪心直接报废,比如韵律规则变成: + // AABB、ABAB、AAAA + // 因为少了ABBA, 所以上面的化简不成立了, 得重新分析新规则下的贪心策略 + // 而尝试的方法就更通用(也就是maxLen3),只是减少一个分支而已 + // 这个贪心费了很多心思,值得点赞! + public static int maxLen4(int[] arr) { + // 统计某个数(key),出现的次数(value) + HashMap map = new HashMap<>(); + // tow代表目前有多少数出现了2次 + int two = 0; + // ans代表目前符合韵律链接的子序列增长到了多长 + int ans = 0; + // 当前的num出现了几次 + int numTimes = 0; + for (int num : arr) { + // 对当前的num,做次数统计 + map.put(num, map.getOrDefault(num, 0) + 1); + // 把num出现的次数拿出来 + numTimes = map.get(num); + // 如果num刚刚出现了2次, 那么目前出现了2次的数,的数量,需要增加1个 + two += numTimes == 2 ? 1 : 0; + // 下面的if代表 : + // 如果目前有2个数出现2次了,可以连接了 + // 如果目前有1个数出现4次了,可以连接了 + if (two == 2 || numTimes == 4) { + ans += 4; + map.clear(); + two = 0; + } + } + return ans; + } + + // 为了测试 + public static int[] randomArray(int len, int value) { + int[] arr = new int[len]; + for (int i = 0; i < len; i++) { + arr[i] = (int) (Math.random() * value); + } + return arr; + } + + // 为了测试 + public static void main(String[] args) { + + // 1111 2332 4343 7799 + int[] test = { 1, 1, 15, 1, 34, 1, 2, 67, 3, 3, 2, 4, 15, 3, 17, 4, 3, 7, 52, 7, 81, 9, 9 }; + System.out.println(maxLen1(test)); + System.out.println(maxLen2(test)); + System.out.println(maxLen3(test)); + System.out.println(maxLen4(test)); + System.out.println("==========="); + + int len = 16; + int value = 10; + int[] arr = randomArray(len, value); + int[] arr1 = Arrays.copyOf(arr, arr.length); + int[] arr2 = Arrays.copyOf(arr, arr.length); + int[] arr3 = Arrays.copyOf(arr, arr.length); + int[] arr4 = Arrays.copyOf(arr, arr.length); + System.out.println(maxLen1(arr1)); + System.out.println(maxLen2(arr2)); + System.out.println(maxLen3(arr3)); + System.out.println(maxLen4(arr4)); + + System.out.println("==========="); + + long start; + long end; + int[] longArr = randomArray(4000, 20); + start = System.currentTimeMillis(); + System.out.println(maxLen3(longArr)); + end = System.currentTimeMillis(); + System.out.println("运行时间(毫秒) : " + (end - start)); + System.out.println("==========="); + + start = System.currentTimeMillis(); + System.out.println(maxLen4(longArr)); + end = System.currentTimeMillis(); + System.out.println("运行时间(毫秒) : " + (end - start)); + System.out.println("==========="); + + } + +} diff --git a/大厂刷题班/class41/Code03_MagicGoToAim.java b/大厂刷题班/class41/Code03_MagicGoToAim.java new file mode 100644 index 0000000..cf6c447 --- /dev/null +++ b/大厂刷题班/class41/Code03_MagicGoToAim.java @@ -0,0 +1,71 @@ +package class41; + +import java.util.PriorityQueue; + +// 来自网易互娱 +// N个结点之间,表世界存在双向通行的道路,里世界存在双向通行的传送门. +// 若走表世界的道路,花费一分钟. +// 若走里世界的传送门,不花费时间,但是接下来一分钟不能走传送门. +// 输入: T为测试用例的组数,对于每组数据: +// 第一行:N M1 M2 N代表结点的个数1到N +// 接下来M1行 每行两个数,u和v,表示表世界u和v之间存在道路 +// 接下来M2行 每行两个数,u和v,表示里世界u和v之间存在传送门 +// 现在处于1号结点,最终要到达N号结点,求最小的到达时间 保证所有输入均有效,不存在环等情况 +public class Code03_MagicGoToAim { + + // 城市编号从0开始,编号对应0~n-1 + // roads[i]是一个数组,表示i能走路达到的城市有哪些,每条路都花费1分钟 + // gates[i]是一个数组,表示i能传送达到的城市有哪些 + // 返回从0到n-1的最少用时 + public static int fast(int n, int[][] roads, int[][] gates) { + int[][] distance = new int[2][n]; + // 因为从0开始走,所以distance[0][0] = 0, distance[1][0] = 0 + // distance[0][i] -> 0 : 前一个城市到达i,是走路的方式, 最小代价,distance[0][i] + // distance[1][i] -> 1 : 前一个城市到达i,是传送的方式, 最小代价,distance[1][i] + for (int i = 1; i < n; i++) { + distance[0][i] = Integer.MAX_VALUE; + distance[1][i] = Integer.MAX_VALUE; + } + // 小根堆,根据距离排序,距离小的点,在上! + PriorityQueue heap = new PriorityQueue<>((a, b) -> a.cost - b.cost); + heap.add(new Node(0, 0, 0)); + boolean[][] visited = new boolean[2][n]; + while (!heap.isEmpty()) { + Node cur = heap.poll(); + if (visited[cur.preTransfer][cur.city]) { + continue; + } + visited[cur.preTransfer][cur.city] = true; + // 走路的方式 + for (int next : roads[cur.city]) { + if (distance[0][next] > cur.cost + 1) { + distance[0][next] = cur.cost + 1; + heap.add(new Node(0, next, distance[0][next])); + } + } + // 传送的方式 + if (cur.preTransfer == 0) { + for (int next : gates[cur.city]) { + if (distance[1][next] > cur.cost) { + distance[1][next] = cur.cost; + heap.add(new Node(1, next, distance[1][next])); + } + } + } + } + return Math.min(distance[0][n - 1], distance[1][n - 1]); + } + + public static class Node { + public int preTransfer; + public int city; + public int cost; + + public Node(int a, int b, int c) { + preTransfer = a; + city = b; + cost = c; + } + } + +} \ No newline at end of file diff --git a/大厂刷题班/class41/Problem_0031_NextPermutation.java b/大厂刷题班/class41/Problem_0031_NextPermutation.java new file mode 100644 index 0000000..5848f43 --- /dev/null +++ b/大厂刷题班/class41/Problem_0031_NextPermutation.java @@ -0,0 +1,44 @@ +package class41; + +public class Problem_0031_NextPermutation { + + public static void nextPermutation(int[] nums) { + int N = nums.length; + // 从右往左第一次降序的位置 + int firstLess = -1; + for (int i = N - 2; i >= 0; i--) { + if (nums[i] < nums[i + 1]) { + firstLess = i; + break; + } + } + if (firstLess < 0) { + reverse(nums, 0, N - 1); + } else { + int rightClosestMore = -1; + // 找最靠右的、同时比nums[firstLess]大的数,位置在哪 + // 这里其实也可以用二分优化,但是这种优化无关紧要了 + for (int i = N - 1; i > firstLess; i--) { + if (nums[i] > nums[firstLess]) { + rightClosestMore = i; + break; + } + } + swap(nums, firstLess, rightClosestMore); + reverse(nums, firstLess + 1, N - 1); + } + } + + public static void reverse(int[] nums, int L, int R) { + while (L < R) { + swap(nums, L++, R--); + } + } + + public static void swap(int[] nums, int i, int j) { + int tmp = nums[i]; + nums[i] = nums[j]; + nums[j] = tmp; + } + +} diff --git a/大厂刷题班/class42/Problem_0265_PaintHouseII.java b/大厂刷题班/class42/Problem_0265_PaintHouseII.java new file mode 100644 index 0000000..a89f7d9 --- /dev/null +++ b/大厂刷题班/class42/Problem_0265_PaintHouseII.java @@ -0,0 +1,56 @@ +package class42; + +public class Problem_0265_PaintHouseII { + + // costs[i][k] i号房子用k颜色刷的花费 + // 要让0...N-1的房子相邻不同色 + // 返回最小花费 + public static int minCostII(int[][] costs) { + int N = costs.length; + if (N == 0) { + return 0; + } + int K = costs[0].length; + // 之前取得的最小代价、取得最小代价时的颜色 + int preMin1 = 0; + int preEnd1 = -1; + // 之前取得的次小代价、取得次小代价时的颜色 + int preMin2 = 0; + int preEnd2 = -1; + for (int i = 0; i < N; i++) { // i房子 + int curMin1 = Integer.MAX_VALUE; + int curEnd1 = -1; + int curMin2 = Integer.MAX_VALUE; + int curEnd2 = -1; + for (int j = 0; j < K; j++) { // j颜色! + if (j != preEnd1) { + if (preMin1 + costs[i][j] < curMin1) { + curMin2 = curMin1; + curEnd2 = curEnd1; + curMin1 = preMin1 + costs[i][j]; + curEnd1 = j; + } else if (preMin1 + costs[i][j] < curMin2) { + curMin2 = preMin1 + costs[i][j]; + curEnd2 = j; + } + } else if (j != preEnd2) { + if (preMin2 + costs[i][j] < curMin1) { + curMin2 = curMin1; + curEnd2 = curEnd1; + curMin1 = preMin2 + costs[i][j]; + curEnd1 = j; + } else if (preMin2 + costs[i][j] < curMin2) { + curMin2 = preMin2 + costs[i][j]; + curEnd2 = j; + } + } + } + preMin1 = curMin1; + preEnd1 = curEnd1; + preMin2 = curMin2; + preEnd2 = curEnd2; + } + return preMin1; + } + +} diff --git a/大厂刷题班/class42/Problem_0272_ClosestBinarySearchTreeValueII.java b/大厂刷题班/class42/Problem_0272_ClosestBinarySearchTreeValueII.java new file mode 100644 index 0000000..62d98f2 --- /dev/null +++ b/大厂刷题班/class42/Problem_0272_ClosestBinarySearchTreeValueII.java @@ -0,0 +1,109 @@ +package class42; + +import java.util.LinkedList; +import java.util.List; +import java.util.Stack; + +public class Problem_0272_ClosestBinarySearchTreeValueII { + + public static class TreeNode { + public int val; + public TreeNode left; + public TreeNode right; + + public TreeNode(int val) { + this.val = val; + } + } + + // 这个解法来自讨论区的回答,最优解实现的很易懂且漂亮 + public static List closestKValues(TreeNode root, double target, int k) { + List ret = new LinkedList<>(); + // >=8,最近的节点,而且需要快速找后继的这么一种结构 + Stack moreTops = new Stack<>(); + // <=8,最近的节点,而且需要快速找前驱的这么一种结构 + Stack lessTops = new Stack<>(); + getMoreTops(root, target, moreTops); + getLessTops(root, target, lessTops); + if (!moreTops.isEmpty() && !lessTops.isEmpty() && moreTops.peek().val == lessTops.peek().val) { + getPredecessor(lessTops); + } + while (k-- > 0) { + if (moreTops.isEmpty()) { + ret.add(getPredecessor(lessTops)); + } else if (lessTops.isEmpty()) { + ret.add(getSuccessor(moreTops)); + } else { + double diffs = Math.abs((double) moreTops.peek().val - target); + double diffp = Math.abs((double) lessTops.peek().val - target); + if (diffs < diffp) { + ret.add(getSuccessor(moreTops)); + } else { + ret.add(getPredecessor(lessTops)); + } + } + } + return ret; + } + + // 在root为头的树上 + // 找到>=target,且最接近target的节点 + // 并且找的过程中,只要某个节点x往左走了,就把x放入moreTops里 + public static void getMoreTops(TreeNode root, double target, Stack moreTops) { + while (root != null) { + if (root.val == target) { + moreTops.push(root); + break; + } else if (root.val > target) { + moreTops.push(root); + root = root.left; + } else { + root = root.right; + } + } + } + + // 在root为头的树上 + // 找到<=target,且最接近target的节点 + // 并且找的过程中,只要某个节点x往右走了,就把x放入lessTops里 + public static void getLessTops(TreeNode root, double target, Stack lessTops) { + while (root != null) { + if (root.val == target) { + lessTops.push(root); + break; + } else if (root.val < target) { + lessTops.push(root); + root = root.right; + } else { + root = root.left; + } + } + } + + // 返回moreTops的头部的值 + // 并且调整moreTops : 为了以后能很快的找到返回节点的后继节点 + public static int getSuccessor(Stack moreTops) { + TreeNode cur = moreTops.pop(); + int ret = cur.val; + cur = cur.right; + while (cur != null) { + moreTops.push(cur); + cur = cur.left; + } + return ret; + } + + // 返回lessTops的头部的值 + // 并且调整lessTops : 为了以后能很快的找到返回节点的前驱节点 + public static int getPredecessor(Stack lessTops) { + TreeNode cur = lessTops.pop(); + int ret = cur.val; + cur = cur.left; + while (cur != null) { + lessTops.push(cur); + cur = cur.right; + } + return ret; + } + +} diff --git a/大厂刷题班/class42/Problem_0273_IntegerToEnglishWords.java b/大厂刷题班/class42/Problem_0273_IntegerToEnglishWords.java new file mode 100644 index 0000000..480cab2 --- /dev/null +++ b/大厂刷题班/class42/Problem_0273_IntegerToEnglishWords.java @@ -0,0 +1,78 @@ +package class42; + +public class Problem_0273_IntegerToEnglishWords { + + public static String num1To19(int num) { + if (num < 1 || num > 19) { + return ""; + } + String[] names = { "One ", "Two ", "Three ", "Four ", "Five ", "Six ", "Seven ", "Eight ", "Nine ", "Ten ", + "Eleven ", "Twelve ", "Thirteen ", "Fourteen ", "Fifteen ", "Sixteen ", "Seventeen", "Eighteen ", + "Nineteen " }; + return names[num - 1]; + } + + public static String num1To99(int num) { + if (num < 1 || num > 99) { + return ""; + } + if (num < 20) { + return num1To19(num); + } + int high = num / 10; + String[] tyNames = { "Twenty ", "Thirty ", "Forty ", "Fifty ", "Sixty ", "Seventy ", "Eighty ", "Ninety " }; + return tyNames[high - 2] + num1To19(num % 10); + } + + public static String num1To999(int num) { + if (num < 1 || num > 999) { + return ""; + } + if (num < 100) { + return num1To99(num); + } + int high = num / 100; + return num1To19(high) + "Hundred " + num1To99(num % 100); + } + + public static String numberToWords(int num) { + if (num == 0) { + return "Zero"; + } + String res = ""; + if (num < 0) { + res = "Negative "; + } + if (num == Integer.MIN_VALUE) { + res += "Two Billion "; + num %= -2000000000; + } + num = Math.abs(num); + int high = 1000000000; + int highIndex = 0; + String[] names = { "Billion ", "Million ", "Thousand ", "" }; + while (num != 0) { + int cur = num / high; + num %= high; + if (cur != 0) { + res += num1To999(cur); + res += names[highIndex]; + } + high /= 1000; + highIndex++; + } + return res.trim(); + } + + public static void main(String[] args) { + int test = Integer.MIN_VALUE; + System.out.println(test); + + test = -test; + System.out.println(test); + + int num = -10001; + System.out.println(numberToWords(num)); + } + +} diff --git a/大厂刷题班/class42/Problem_0296_BestMeetingPoint.java b/大厂刷题班/class42/Problem_0296_BestMeetingPoint.java new file mode 100644 index 0000000..8987edf --- /dev/null +++ b/大厂刷题班/class42/Problem_0296_BestMeetingPoint.java @@ -0,0 +1,48 @@ +package class42; + +public class Problem_0296_BestMeetingPoint { + + public static int minTotalDistance(int[][] grid) { + int N = grid.length; + int M = grid[0].length; + int[] iOnes = new int[N]; + int[] jOnes = new int[M]; + for (int i = 0; i < N; i++) { + for (int j = 0; j < M; j++) { + if (grid[i][j] == 1) { + iOnes[i]++; + jOnes[j]++; + } + } + } + int total = 0; + int i = 0; + int j = N - 1; + int iRest = 0; + int jRest = 0; + while (i < j) { + if (iOnes[i] + iRest <= iOnes[j] + jRest) { + total += iOnes[i] + iRest; + iRest += iOnes[i++]; + } else { + total += iOnes[j] + jRest; + jRest += iOnes[j--]; + } + } + i = 0; + j = M - 1; + iRest = 0; + jRest = 0; + while (i < j) { + if (jOnes[i] + iRest <= jOnes[j] + jRest) { + total += jOnes[i] + iRest; + iRest += jOnes[i++]; + } else { + total += jOnes[j] + jRest; + jRest += jOnes[j--]; + } + } + return total; + } + +} diff --git a/大厂刷题班/class42/Problem_0335_SelfCrossing.java b/大厂刷题班/class42/Problem_0335_SelfCrossing.java new file mode 100644 index 0000000..cd3d8d8 --- /dev/null +++ b/大厂刷题班/class42/Problem_0335_SelfCrossing.java @@ -0,0 +1,28 @@ +package class42; + +public class Problem_0335_SelfCrossing { + + public static boolean isSelfCrossing(int[] x) { + if (x == null || x.length < 4) { + return false; + } + if ((x.length > 3 && x[2] <= x[0] && x[3] >= x[1]) + || (x.length > 4 + && ((x[3] <= x[1] && x[4] >= x[2]) || (x[3] == x[1] && x[0] + x[4] >= x[2])))) { + return true; + } + for (int i = 5; i < x.length; i++) { + if (x[i - 1] <= x[i - 3] && ((x[i] >= x[i - 2]) + || (x[i - 2] >= x[i - 4] && x[i - 5] + x[i - 1] >= x[i - 3] && x[i - 4] + x[i] >= x[i - 2]))) { + return true; + } + } + return false; + } + + public static void main(String[] args) { + int[] arr = { 2, 2, 3, 2, 2 }; + System.out.println(isSelfCrossing(arr)); + } + +} diff --git a/大厂刷题班/class43/Code01_SumNoPositiveMinCost.java b/大厂刷题班/class43/Code01_SumNoPositiveMinCost.java new file mode 100644 index 0000000..fb039a6 --- /dev/null +++ b/大厂刷题班/class43/Code01_SumNoPositiveMinCost.java @@ -0,0 +1,197 @@ +package class43; + +import java.util.Arrays; + +// 来自微软面试 +// 给定一个正数数组arr长度为n、正数x、正数y +// 你的目标是让arr整体的累加和<=0 +// 你可以对数组中的数num执行以下三种操作中的一种,且每个数最多能执行一次操作 : +// 1)不变 +// 2)可以选择让num变成0,承担x的代价 +// 3)可以选择让num变成-num,承担y的代价 +// 返回你达到目标的最小代价 +// 数据规模 : 面试时面试官没有说数据规模 +public class Code01_SumNoPositiveMinCost { + + // 动态规划 + public static int minOpStep1(int[] arr, int x, int y) { + int sum = 0; + for (int num : arr) { + sum += num; + } + return process1(arr, x, y, 0, sum); + } + + // arr[i...]自由选择,每个位置的数可以执行三种操作中的一种! + // 执行变0的操作,x操作,代价 -> x + // 执行变相反数的操作,y操作,代价 -> y + // 还剩下sum这么多累加和,需要去搞定! + // 返回搞定了sum,最低代价是多少? + public static int process1(int[] arr, int x, int y, int i, int sum) { + if (sum <= 0) { + return 0; + } + // sum > 0 没搞定 + if (i == arr.length) { + return Integer.MAX_VALUE; + } + // 第一选择,什么也不干! + int p1 = process1(arr, x, y, i + 1, sum); + // 第二选择,执行x的操作,变0 x + 后续 + int p2 = Integer.MAX_VALUE; + int next2 = process1(arr, x, y, i + 1, sum - arr[i]); + if (next2 != Integer.MAX_VALUE) { + p2 = x + next2; + } + // 第三选择,执行y的操作,变相反数 x + 后续 7 -7 -14 + int p3 = Integer.MAX_VALUE; + int next3 = process1(arr, x, y, i + 1, sum - (arr[i] << 1)); + if (next3 != Integer.MAX_VALUE) { + p3 = y + next3; + } + return Math.min(p1, Math.min(p2, p3)); + } + + // 贪心(最优解) + public static int minOpStep2(int[] arr, int x, int y) { + Arrays.sort(arr); // 小 -> 大 + int n = arr.length; + for (int l = 0, r = n - 1; l <= r; l++, r--) { + int tmp = arr[l]; + arr[l] = arr[r]; + arr[r] = tmp; + } + // arr 大 -> 小 + if (x >= y) { // 没有任何必要执行x操作 + int sum = 0; + for (int num : arr) { + sum += num; + } + int cost = 0; + for (int i = 0; i < n && sum > 0; i++) { + sum -= arr[i] << 1; + cost += y; + } + return cost; + } else { + for (int i = n - 2; i >= 0; i--) { + arr[i] += arr[i + 1]; + } + int benefit = 0; + // 注意,可以不二分,用不回退的方式! + // 执行Y操作的数,有0个的时候 + int left = mostLeft(arr, 0, benefit); + int cost = left * x; + for (int i = 0; i < n - 1; i++) { + // 0..i 这些数,都执行Y + benefit += arr[i] - arr[i + 1]; + left = mostLeft(arr, i + 1, benefit); + cost = Math.min(cost, (i + 1) * y + (left - i - 1) * x); + } + return cost; + } + } + + // arr是后缀和数组, arr[l...]中找到值<=v的最左位置 + public static int mostLeft(int[] arr, int l, int v) { + int r = arr.length - 1; + int m = 0; + int ans = arr.length; + while (l <= r) { + m = (l + r) / 2; + if (arr[m] <= v) { + ans = m; + r = m - 1; + } else { + l = m + 1; + } + } + return ans; + } + + // 不回退 + public static int minOpStep3(int[] arr, int x, int y) { + // 系统排序,小 -> 大 + Arrays.sort(arr); + int n = arr.length; + // 如何变成 大 -> 小 + for (int l = 0, r = n - 1; l <= r; l++, r--) { + int tmp = arr[l]; + arr[l] = arr[r]; + arr[r] = tmp; + } + if (x >= y) { + int sum = 0; + for (int num : arr) { + sum += num; + } + int cost = 0; + for (int i = 0; i < n && sum > 0; i++) { + sum -= arr[i] << 1; + cost += y; + } + return cost; + } else { + // 0个数执行Y + int benefit = 0; + // 全部的数都需要执行x,才能让累加和<=0 + int cost = arr.length * x; + int holdSum = 0; + for (int yRight = 0, holdLeft = n; yRight < holdLeft - 1; yRight++) { + benefit += arr[yRight]; + while (holdLeft - 1 > yRight && holdSum + arr[holdLeft - 1] <= benefit) { + holdSum += arr[holdLeft - 1]; + holdLeft--; + } + // 0...yRight x holdLeft.... + cost = Math.min(cost, (yRight + 1) * y + (holdLeft - yRight - 1) * x); + } + return cost; + } + } + + // 为了测试 + public static int[] randomArray(int len, int v) { + int[] arr = new int[len]; + for (int i = 0; i < len; i++) { + arr[i] = (int) (Math.random() * v) + 1; + } + return arr; + } + + // 为了测试 + public static int[] copyArray(int[] arr) { + int[] ans = new int[arr.length]; + for (int i = 0; i < arr.length; i++) { + ans[i] = arr[i]; + } + return ans; + } + + // 为了测试 + public static void main(String[] args) { + int n = 12; + int v = 20; + int c = 10; + int testTime = 10000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int len = (int) (Math.random() * n); + int[] arr = randomArray(len, v); + int[] arr1 = copyArray(arr); + int[] arr2 = copyArray(arr); + int[] arr3 = copyArray(arr); + int x = (int) (Math.random() * c); + int y = (int) (Math.random() * c); + int ans1 = minOpStep1(arr1, x, y); + int ans2 = minOpStep2(arr2, x, y); + int ans3 = minOpStep3(arr3, x, y); + if (ans1 != ans2 || ans1 != ans3) { + System.out.println("出错了!"); + } + } + System.out.println("测试结束"); + + } + +} diff --git a/大厂刷题班/class43/Code02_MinCostToYeahArray.java b/大厂刷题班/class43/Code02_MinCostToYeahArray.java new file mode 100644 index 0000000..de9ccdc --- /dev/null +++ b/大厂刷题班/class43/Code02_MinCostToYeahArray.java @@ -0,0 +1,304 @@ +package class43; + +import java.util.Arrays; + +// 来自360笔试 +// 给定一个正数数组arr,长度为n,下标0~n-1 +// arr中的0、n-1位置不需要达标,它们分别是最左、最右的位置 +// 中间位置i需要达标,达标的条件是 : arr[i-1] > arr[i] 或者 arr[i+1] > arr[i]哪个都可以 +// 你每一步可以进行如下操作:对任何位置的数让其-1 +// 你的目的是让arr[1~n-2]都达标,这时arr称之为yeah!数组 +// 返回至少要多少步可以让arr变成yeah!数组 +// 数据规模 : 数组长度 <= 10000,数组中的值<=500 +public class Code02_MinCostToYeahArray { + + public static final int INVALID = Integer.MAX_VALUE; + + // 纯暴力方法,只是为了结果对 + // 时间复杂度极差 + public static int minCost0(int[] arr) { + if (arr == null || arr.length < 3) { + return 0; + } + int n = arr.length; + int min = INVALID; + for (int num : arr) { + min = Math.min(min, num); + } + int base = min - n; + return process0(arr, base, 0); + } + + public static int process0(int[] arr, int base, int index) { + if (index == arr.length) { + for (int i = 1; i < arr.length - 1; i++) { + if (arr[i - 1] <= arr[i] && arr[i] >= arr[i + 1]) { + return INVALID; + } + } + return 0; + } else { + int ans = INVALID; + int tmp = arr[index]; + for (int cost = 0; arr[index] >= base; cost++, arr[index]--) { + int next = process0(arr, base, index + 1); + if (next != INVALID) { + ans = Math.min(ans, cost + next); + } + } + arr[index] = tmp; + return ans; + } + } + + // 递归方法,已经把尝试写出 + public static int minCost1(int[] arr) { + if (arr == null || arr.length < 3) { + return 0; + } + int min = INVALID; + for (int num : arr) { + min = Math.min(min, num); + } + for (int i = 0; i < arr.length; i++) { + arr[i] += arr.length - min; + } + return process1(arr, 1, arr[0], true); + } + + // 当前来到index位置,值arr[index] + // pre : 前一个位置的值,可能减掉了一些,所以不能用arr[index-1] + // preOk : 前一个位置的值,是否被它左边的数变有效了 + // 返回 : 让arr都变有效,最小代价是什么? + public static int process1(int[] arr, int index, int pre, boolean preOk) { + if (index == arr.length - 1) { // 已经来到最后一个数了 + return preOk || pre < arr[index] ? 0 : INVALID; + } + // 当前index,不是最后一个数! + int ans = INVALID; + if (preOk) { + for (int cur = arr[index]; cur >= 0; cur--) { + int next = process1(arr, index + 1, cur, cur < pre); + if (next != INVALID) { + ans = Math.min(ans, arr[index] - cur + next); + } + } + } else { + for (int cur = arr[index]; cur > pre; cur--) { + int next = process1(arr, index + 1, cur, false); + if (next != INVALID) { + ans = Math.min(ans, arr[index] - cur + next); + } + } + } + return ans; + } + + // 初改动态规划方法,就是参考minCost1,改出来的版本 + public static int minCost2(int[] arr) { + if (arr == null || arr.length < 3) { + return 0; + } + int min = INVALID; + for (int num : arr) { + min = Math.min(min, num); + } + int n = arr.length; + for (int i = 0; i < n; i++) { + arr[i] += n - min; + } + int[][][] dp = new int[n][2][]; + for (int i = 1; i < n; i++) { + dp[i][0] = new int[arr[i - 1] + 1]; + dp[i][1] = new int[arr[i - 1] + 1]; + Arrays.fill(dp[i][0], INVALID); + Arrays.fill(dp[i][1], INVALID); + } + for (int pre = 0; pre <= arr[n - 2]; pre++) { + dp[n - 1][0][pre] = pre < arr[n - 1] ? 0 : INVALID; + dp[n - 1][1][pre] = 0; + } + for (int index = n - 2; index >= 1; index--) { + for (int pre = 0; pre <= arr[index - 1]; pre++) { + for (int cur = arr[index]; cur > pre; cur--) { + int next = dp[index + 1][0][cur]; + if (next != INVALID) { + dp[index][0][pre] = Math.min(dp[index][0][pre], arr[index] - cur + next); + } + } + for (int cur = arr[index]; cur >= 0; cur--) { + int next = dp[index + 1][cur < pre ? 1 : 0][cur]; + if (next != INVALID) { + dp[index][1][pre] = Math.min(dp[index][1][pre], arr[index] - cur + next); + } + } + } + } + return dp[1][1][arr[0]]; + } + + // minCost2动态规划 + 枚举优化 + // 改出的这个版本,需要一些技巧,但很可惜不是最优解 + // 虽然不是最优解,也足以通过100%的case了, + // 这种技法的练习,非常有意义 + public static int minCost3(int[] arr) { + if (arr == null || arr.length < 3) { + return 0; + } + int min = INVALID; + for (int num : arr) { + min = Math.min(min, num); + } + int n = arr.length; + for (int i = 0; i < n; i++) { + arr[i] += n - min; + } + int[][][] dp = new int[n][2][]; + for (int i = 1; i < n; i++) { + dp[i][0] = new int[arr[i - 1] + 1]; + dp[i][1] = new int[arr[i - 1] + 1]; + } + for (int p = 0; p <= arr[n - 2]; p++) { + dp[n - 1][0][p] = p < arr[n - 1] ? 0 : INVALID; + } + int[][] best = best(dp, n - 1, arr[n - 2]); + for (int i = n - 2; i >= 1; i--) { + for (int p = 0; p <= arr[i - 1]; p++) { + if (arr[i] < p) { + dp[i][1][p] = best[1][arr[i]]; + } else { + dp[i][1][p] = Math.min(best[0][p], p > 0 ? best[1][p - 1] : INVALID); + } + dp[i][0][p] = arr[i] <= p ? INVALID : best[0][p + 1]; + } + best = best(dp, i, arr[i - 1]); + } + return dp[1][1][arr[0]]; + } + + public static int[][] best(int[][][] dp, int i, int v) { + int[][] best = new int[2][v + 1]; + best[0][v] = dp[i][0][v]; + for (int p = v - 1; p >= 0; p--) { + best[0][p] = dp[i][0][p] == INVALID ? INVALID : v - p + dp[i][0][p]; + best[0][p] = Math.min(best[0][p], best[0][p + 1]); + } + best[1][0] = dp[i][1][0] == INVALID ? INVALID : v + dp[i][1][0]; + for (int p = 1; p <= v; p++) { + best[1][p] = dp[i][1][p] == INVALID ? INVALID : v - p + dp[i][1][p]; + best[1][p] = Math.min(best[1][p], best[1][p - 1]); + } + return best; + } + + // 最终的最优解,贪心 + // 时间复杂度O(N) + // 请注意,重点看上面的方法 + // 这个最优解容易理解,但让你学到的东西不是很多 + public static int yeah(int[] arr) { + if (arr == null || arr.length < 3) { + return 0; + } + int n = arr.length; + int[] nums = new int[n + 2]; + nums[0] = Integer.MAX_VALUE; + nums[n + 1] = Integer.MAX_VALUE; + for (int i = 0; i < arr.length; i++) { + nums[i + 1] = arr[i]; + } + int[] leftCost = new int[n + 2]; + int pre = nums[0]; + int change = 0; + for (int i = 1; i <= n; i++) { + change = Math.min(pre - 1, nums[i]); + leftCost[i] = nums[i] - change + leftCost[i - 1]; + pre = change; + } + int[] rightCost = new int[n + 2]; + pre = nums[n + 1]; + for (int i = n; i >= 1; i--) { + change = Math.min(pre - 1, nums[i]); + rightCost[i] = nums[i] - change + rightCost[i + 1]; + pre = change; + } + int ans = Integer.MAX_VALUE; + for (int i = 1; i <= n; i++) { + ans = Math.min(ans, leftCost[i] + rightCost[i + 1]); + } + return ans; + } + + // 为了测试 + public static int[] randomArray(int len, int v) { + int[] arr = new int[len]; + for (int i = 0; i < len; i++) { + arr[i] = (int) (Math.random() * v) + 1; + } + return arr; + } + + // 为了测试 + public static int[] copyArray(int[] arr) { + int[] ans = new int[arr.length]; + for (int i = 0; i < arr.length; i++) { + ans[i] = arr[i]; + } + return ans; + } + + // 为了测试 + public static void main(String[] args) { + int len = 7; + int v = 10; + int testTime = 100; + System.out.println("=========="); + System.out.println("功能测试开始"); + for (int i = 0; i < testTime; i++) { + int n = (int) (Math.random() * len) + 1; + int[] arr = randomArray(n, v); + int[] arr0 = copyArray(arr); + int[] arr1 = copyArray(arr); + int[] arr2 = copyArray(arr); + int[] arr3 = copyArray(arr); + int[] arr4 = copyArray(arr); + int ans0 = minCost0(arr0); + int ans1 = minCost1(arr1); + int ans2 = minCost2(arr2); + int ans3 = minCost3(arr3); + int ans4 = yeah(arr4); + if (ans0 != ans1 || ans0 != ans2 || ans0 != ans3 || ans0 != ans4) { + System.out.println("出错了!"); + } + } + System.out.println("功能测试结束"); + System.out.println("=========="); + + System.out.println("性能测试开始"); + + len = 10000; + v = 500; + System.out.println("生成随机数组长度:" + len); + System.out.println("生成随机数组值的范围:[1, " + v + "]"); + int[] arr = randomArray(len, v); + int[] arr3 = copyArray(arr); + int[] arrYeah = copyArray(arr); + long start; + long end; + start = System.currentTimeMillis(); + int ans3 = minCost3(arr3); + end = System.currentTimeMillis(); + System.out.println("minCost3方法:"); + System.out.println("运行结果: " + ans3 + ", 时间(毫秒) : " + (end - start)); + + start = System.currentTimeMillis(); + int ansYeah = yeah(arrYeah); + end = System.currentTimeMillis(); + System.out.println("yeah方法:"); + System.out.println("运行结果: " + ansYeah + ", 时间(毫秒) : " + (end - start)); + + System.out.println("性能测试结束"); + System.out.println("=========="); + + } + +} diff --git a/大厂刷题班/class44/Problem_0248_StrobogrammaticNumberIII.java b/大厂刷题班/class44/Problem_0248_StrobogrammaticNumberIII.java new file mode 100644 index 0000000..c5684db --- /dev/null +++ b/大厂刷题班/class44/Problem_0248_StrobogrammaticNumberIII.java @@ -0,0 +1,216 @@ +package class44; + +public class Problem_0248_StrobogrammaticNumberIII { + + public static int strobogrammaticInRange(String l, String h) { + char[] low = l.toCharArray(); + char[] high = h.toCharArray(); + if (!equalMore(low, high)) { + return 0; + } + int lowLen = low.length; + int highLen = high.length; + if (lowLen == highLen) { + int up1 = up(low, 0, false, 1); + int up2 = up(high, 0, false, 1); + return up1 - up2 + (valid(high) ? 1 : 0); + } + int ans = 0; + // lowLen = 3 hightLen = 7 + // 4 5 6 + for (int i = lowLen + 1; i < highLen; i++) { + ans += all(i); + } + ans += up(low, 0, false, 1); + ans += down(high, 0, false, 1); + return ans; + } + + public static boolean equalMore(char[] low, char[] cur) { + if (low.length != cur.length) { + return low.length < cur.length; + } + for (int i = 0; i < low.length; i++) { + if (low[i] != cur[i]) { + return low[i] < cur[i]; + } + } + return true; + } + + public static boolean valid(char[] str) { + int L = 0; + int R = str.length - 1; + while (L <= R) { + boolean t = L != R; + if (convert(str[L++], t) != str[R--]) { + return false; + } + } + return true; + } + + // left想得到cha字符,right配合应该做什么决定, + // 如果left怎么也得不到cha字符,返回-1;如果能得到,返回right配合应做什么决定 + // 比如,left!=right,即不是同一个位置 + // left想得到0,那么就right就需要是0 + // left想得到1,那么就right就需要是1 + // left想得到6,那么就right就需要是9 + // left想得到8,那么就right就需要是8 + // left想得到9,那么就right就需要是6 + // 除此了这些之外,left不能得到别的了。 + // 比如,left==right,即是同一个位置 + // left想得到0,那么就right就需要是0 + // left想得到1,那么就right就需要是1 + // left想得到8,那么就right就需要是8 + // 除此了这些之外,left不能得到别的了,比如: + // left想得到6,那么就right就需要是9,而left和right是一个位置啊,怎么可能即6又9,返回-1 + // left想得到9,那么就right就需要是6,而left和right是一个位置啊,怎么可能即9又6,返回-1 + public static int convert(char cha, boolean diff) { + switch (cha) { + case '0': + return '0'; + case '1': + return '1'; + case '6': + return diff ? '9' : -1; + case '8': + return '8'; + case '9': + return diff ? '6' : -1; + default: + return -1; + } + } + + // low [左边已经做完决定了 left.....right 右边已经做完决定了] + // 左边已经做完决定的部分,如果大于low的原始,leftMore = true; + // 左边已经做完决定的部分,如果不大于low的原始,那一定是相等,不可能小于,leftMore = false; + // 右边已经做完决定的部分,如果小于low的原始,rightLessEqualMore = 0; + // 右边已经做完决定的部分,如果等于low的原始,rightLessEqualMore = 1; + // 右边已经做完决定的部分,如果大于low的原始,rightLessEqualMore = 2; + // rightLessEqualMore < = > + // 0 1 2 + // 返回 :没做决定的部分,随意变,几个有效的情况?返回! + public static int up(char[] low, int left, boolean leftMore, int rightLessEqualMore) { + int N = low.length; + int right = N - 1 - left; + if (left > right) { // 都做完决定了! + // 如果左边做完决定的部分大于原始 或者 如果左边做完决定的部分等于原始&左边做完决定的部分不小于原始 + // 有效! + // 否则,无效! + return leftMore || (!leftMore && rightLessEqualMore != 0) ? 1 : 0; + } + // 如果上面没有return,说明决定没做完,就继续做 + if (leftMore) { // 如果左边做完决定的部分大于原始 + return num(N - (left << 1)); + } else { // 如果左边做完决定的部分等于原始 + int ways = 0; + // 当前left做的决定,大于原始的left + for (char cha = (char) (low[left] + 1); cha <= '9'; cha++) { + if (convert(cha, left != right) != -1) { + ways += up(low, left + 1, true, rightLessEqualMore); + } + } + // 当前left做的决定,等于原始的left + int convert = convert(low[left], left != right); + if (convert != -1) { + if (convert < low[right]) { + ways += up(low, left + 1, false, 0); + } else if (convert == low[right]) { + ways += up(low, left + 1, false, rightLessEqualMore); + } else { + ways += up(low, left + 1, false, 2); + } + } + return ways; + } + } + + // ll < = + // rs < = > + public static int down(char[] high, int left, boolean ll, int rs) { + int N = high.length; + int right = N - 1 - left; + if (left > right) { + return ll || (!ll && rs != 2) ? 1 : 0; + } + if (ll) { + return num(N - (left << 1)); + } else { + int ways = 0; + for (char cha = (N != 1 && left == 0) ? '1' : '0'; cha < high[left]; cha++) { + if (convert(cha, left != right) != -1) { + ways += down(high, left + 1, true, rs); + } + } + int convert = convert(high[left], left != right); + if (convert != -1) { + if (convert < high[right]) { + ways += down(high, left + 1, false, 0); + } else if (convert == high[right]) { + ways += down(high, left + 1, false, rs); + } else { + ways += down(high, left + 1, false, 2); + } + } + return ways; + } + } + + public static int num(int bits) { + if (bits == 1) { + return 3; + } + if (bits == 2) { + return 5; + } + int p2 = 3; + int p1 = 5; + int ans = 0; + for (int i = 3; i <= bits; i++) { + ans = 5 * p2; + p2 = p1; + p1 = ans; + } + return ans; + } + + // 如果是最开始 : + // Y X X X Y + // -> 1 X X X 1 + // -> 8 X X X 8 + // -> 9 X X X 6 + // -> 6 X X X 9 + // 如果不是最开始 : + // Y X X X Y + // -> 0 X X X 0 + // -> 1 X X X 1 + // -> 8 X X X 8 + // -> 9 X X X 6 + // -> 6 X X X 9 + // 所有的len位数,有几个有效的? + public static int all(int len) { + int ans = (len & 1) == 0 ? 1 : 3; + for (int i = (len & 1) == 0 ? 2 : 3; i < len; i += 2) { + ans *= 5; + } + return ans << 2; + } + + // 我们课上讲的 + public static int all(int len, boolean init) { + if (len == 0) { // init == true,不可能调用all(0) + return 1; + } + if (len == 1) { + return 3; + } + if (init) { + return all(len - 2, false) << 2; + } else { + return all(len - 2, false) * 5; + } + } + +} diff --git a/大厂刷题班/class44/Problem_0317_ShortestDistanceFromAllBuildings.java b/大厂刷题班/class44/Problem_0317_ShortestDistanceFromAllBuildings.java new file mode 100644 index 0000000..6a6648b --- /dev/null +++ b/大厂刷题班/class44/Problem_0317_ShortestDistanceFromAllBuildings.java @@ -0,0 +1,237 @@ +package class44; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.Queue; + +public class Problem_0317_ShortestDistanceFromAllBuildings { + + // 如果grid中0比较少,用这个方法比较好 + public static int shortestDistance1(int[][] grid) { + int ans = Integer.MAX_VALUE; + int N = grid.length; + int M = grid[0].length; + int buildings = 0; + Position[][] positions = new Position[N][M]; + for (int i = 0; i < N; i++) { + for (int j = 0; j < M; j++) { + if (grid[i][j] == 1) { + buildings++; + } + positions[i][j] = new Position(i, j, grid[i][j]); + } + } + if (buildings == 0) { + return 0; + } + for (int i = 0; i < N; i++) { + for (int j = 0; j < M; j++) { + ans = Math.min(ans, bfs(positions, buildings, i, j)); + } + } + return ans == Integer.MAX_VALUE ? -1 : ans; + } + + public static int bfs(Position[][] positions, int buildings, int i, int j) { + if (positions[i][j].v != 0) { + return Integer.MAX_VALUE; + } + HashMap levels = new HashMap<>(); + Queue queue = new LinkedList<>(); + Position from = positions[i][j]; + levels.put(from, 0); + queue.add(from); + int ans = 0; + int solved = 0; + while (!queue.isEmpty() && solved != buildings) { + Position cur = queue.poll(); + int level = levels.get(cur); + if (cur.v == 1) { + ans += level; + solved++; + } else { + add(queue, levels, positions, cur.r - 1, cur.c, level + 1); + add(queue, levels, positions, cur.r + 1, cur.c, level + 1); + add(queue, levels, positions, cur.r, cur.c - 1, level + 1); + add(queue, levels, positions, cur.r, cur.c + 1, level + 1); + } + } + return solved == buildings ? ans : Integer.MAX_VALUE; + } + + public static class Position { + public int r; + public int c; + public int v; + + public Position(int row, int col, int value) { + r = row; + c = col; + v = value; + } + } + + public static void add(Queue q, HashMap l, Position[][] p, int i, int j, int level) { + if (i >= 0 && i < p.length && j >= 0 && j < p[0].length && p[i][j].v != 2 && !l.containsKey(p[i][j])) { + l.put(p[i][j], level); + q.add(p[i][j]); + } + } + + // 如果grid中1比较少,用这个方法比较好 + public static int shortestDistance2(int[][] grid) { + int N = grid.length; + int M = grid[0].length; + int ones = 0; + int zeros = 0; + Info[][] infos = new Info[N][M]; + for (int i = 0; i < N; i++) { + for (int j = 0; j < M; j++) { + if (grid[i][j] == 1) { + infos[i][j] = new Info(i, j, 1, ones++); + } else if (grid[i][j] == 0) { + infos[i][j] = new Info(i, j, 0, zeros++); + } else { + infos[i][j] = new Info(i, j, 2, Integer.MAX_VALUE); + } + } + } + if (ones == 0) { + return 0; + } + int[][] distance = new int[ones][zeros]; + for (int i = 0; i < N; i++) { + for (int j = 0; j < M; j++) { + if (infos[i][j].v == 1) { + bfs(infos, i, j, distance); + } + } + } + int ans = Integer.MAX_VALUE; + for (int i = 0; i < zeros; i++) { + int sum = 0; + for (int j = 0; j < ones; j++) { + if (distance[j][i] == 0) { + sum = Integer.MAX_VALUE; + break; + } else { + sum += distance[j][i]; + } + } + ans = Math.min(ans, sum); + } + return ans == Integer.MAX_VALUE ? -1 : ans; + } + + public static class Info { + public int r; + public int c; + public int v; + public int t; + + public Info(int row, int col, int value, int th) { + r = row; + c = col; + v = value; + t = th; + } + } + + public static void bfs(Info[][] infos, int i, int j, int[][] distance) { + HashMap levels = new HashMap<>(); + Queue queue = new LinkedList<>(); + Info from = infos[i][j]; + add(queue, levels, infos, from.r - 1, from.c, 1); + add(queue, levels, infos, from.r + 1, from.c, 1); + add(queue, levels, infos, from.r, from.c - 1, 1); + add(queue, levels, infos, from.r, from.c + 1, 1); + while (!queue.isEmpty()) { + Info cur = queue.poll(); + int level = levels.get(cur); + distance[from.t][cur.t] = level; + add(queue, levels, infos, cur.r - 1, cur.c, level + 1); + add(queue, levels, infos, cur.r + 1, cur.c, level + 1); + add(queue, levels, infos, cur.r, cur.c - 1, level + 1); + add(queue, levels, infos, cur.r, cur.c + 1, level + 1); + } + } + + public static void add(Queue q, HashMap l, Info[][] infos, int i, int j, int level) { + if (i >= 0 && i < infos.length && j >= 0 && j < infos[0].length && infos[i][j].v == 0 + && !l.containsKey(infos[i][j])) { + l.put(infos[i][j], level); + q.add(infos[i][j]); + } + } + + // 方法三的大流程和方法二完全一样,从每一个1出发,而不从0出发 + // 运行时间快主要是因为常数优化,以下是优化点: + // 1) 宽度优先遍历时,一次解决一层,不是一个一个遍历: + // int size = que.size(); + // level++; + // for (int k = 0; k < size; k++) { ... } + // 2) pass的值每次减1何用?只有之前所有的1都到达的0,才有必要继续尝试的意思 + // 也就是说,如果某个1,自我封闭,之前的1根本到不了现在这个1附近的0,就没必要继续尝试了 + // if (nextr >= 0 && nextr < grid.length + // && nextc >= 0 && nextc < grid[0].length + // && grid[nextr][nextc] == pass) + // 3) int[] trans = { 0, 1, 0, -1, 0 }; 的作用是迅速算出上、下、左、右 + // 4) 如果某个1在计算时,它周围已经没有pass值了,可以提前宣告1之间是不连通的 + // step = bfs(grid, dist, i, j, pass--, trans); + // if (step == Integer.MAX_VALUE) { + // return -1; + // } + // 5) 最要的优化,每个1到某个0的距离是逐渐叠加的,每个1给所有的0叠一次(宽度优先遍历) + // dist[nextr][nextc] += level; + public static int shortestDistance3(int[][] grid) { + int[][] dist = new int[grid.length][grid[0].length]; + int pass = 0; + int step = Integer.MAX_VALUE; + int[] trans = { 0, 1, 0, -1, 0 }; + for (int i = 0; i < grid.length; i++) { + for (int j = 0; j < grid[0].length; j++) { + if (grid[i][j] == 1) { + step = bfs(grid, dist, i, j, pass--, trans); + if (step == Integer.MAX_VALUE) { + return -1; + } + } + } + } + return step == Integer.MAX_VALUE ? -1 : step; + } + + // 原始矩阵是grid,但是所有的路(0),被改了 + // 改成了啥?改成认为,pass才是路!原始矩阵中的1和2呢?不变! + // dist,距离压缩表,之前的bfs,也就是之前每个1,走到某个0,总距离和都在dist里 + // row,col 宽度优先遍历的,出发点! + // trans -> 炫技的,上下左右 + // 返回值代表,进行完这一遍bfs,压缩距离表中(dist),最小值是谁? + // 如果突然发现,无法联通!返回系统最大! + public static int bfs(int[][] grid, int[][] dist, int row, int col, int pass, int[] trans) { + Queue que = new LinkedList(); + que.offer(new int[] { row, col }); + int level = 0; + int ans = Integer.MAX_VALUE; + while (!que.isEmpty()) { + int size = que.size(); + level++; + for (int k = 0; k < size; k++) { + int[] node = que.poll(); + for (int i = 1; i < trans.length; i++) { // 上下左右 + int nextr = node[0] + trans[i - 1]; + int nextc = node[1] + trans[i]; + if (nextr >= 0 && nextr < grid.length && nextc >= 0 && nextc < grid[0].length + && grid[nextr][nextc] == pass) { + que.offer(new int[] { nextr, nextc }); + dist[nextr][nextc] += level; + ans = Math.min(ans, dist[nextr][nextc]); + grid[nextr][nextc]--; + } + } + } + } + return ans; + } + +} diff --git a/大厂刷题班/class44/Problem_0992_SubarraysWithKDifferentIntegers.java b/大厂刷题班/class44/Problem_0992_SubarraysWithKDifferentIntegers.java new file mode 100644 index 0000000..97c111c --- /dev/null +++ b/大厂刷题班/class44/Problem_0992_SubarraysWithKDifferentIntegers.java @@ -0,0 +1,71 @@ +package class44; + +import java.util.HashMap; + +public class Problem_0992_SubarraysWithKDifferentIntegers { + + // nums 数组,题目规定,nums中的数字,不会超过nums的长度 + // [ ]长度为5,0~5 + public static int subarraysWithKDistinct1(int[] nums, int k) { + int n = nums.length; + // k-1种数的窗口词频统计 + int[] lessCounts = new int[n + 1]; + // k种数的窗口词频统计 + int[] equalCounts = new int[n + 1]; + int lessLeft = 0; + int equalLeft = 0; + int lessKinds = 0; + int equalKinds = 0; + int ans = 0; + for (int r = 0; r < n; r++) { + // 当前刚来到r位置! + if (lessCounts[nums[r]] == 0) { + lessKinds++; + } + if (equalCounts[nums[r]] == 0) { + equalKinds++; + } + lessCounts[nums[r]]++; + equalCounts[nums[r]]++; + while (lessKinds == k) { + if (lessCounts[nums[lessLeft]] == 1) { + lessKinds--; + } + lessCounts[nums[lessLeft++]]--; + } + while (equalKinds > k) { + if (equalCounts[nums[equalLeft]] == 1) { + equalKinds--; + } + equalCounts[nums[equalLeft++]]--; + } + ans += lessLeft - equalLeft; + } + return ans; + } + + public static int subarraysWithKDistinct2(int[] arr, int k) { + return numsMostK(arr, k) - numsMostK(arr, k - 1); + } + + public static int numsMostK(int[] arr, int k) { + int i = 0, res = 0; + HashMap count = new HashMap<>(); + for (int j = 0; j < arr.length; ++j) { + if (count.getOrDefault(arr[j], 0) == 0) { + k--; + } + count.put(arr[j], count.getOrDefault(arr[j], 0) + 1); + while (k < 0) { + count.put(arr[i], count.get(arr[i]) - 1); + if (count.get(arr[i]) == 0) { + k++; + } + i++; + } + res += j - i + 1; + } + return res; + } + +} diff --git a/大厂刷题班/class45/Code01_SplitBuildingBlock.java b/大厂刷题班/class45/Code01_SplitBuildingBlock.java new file mode 100644 index 0000000..b63b36c --- /dev/null +++ b/大厂刷题班/class45/Code01_SplitBuildingBlock.java @@ -0,0 +1,87 @@ +package class45; + +import java.util.Arrays; + +// 来自京东笔试 +// 小明手中有n块积木,并且小明知道每块积木的重量。现在小明希望将这些积木堆起来 +// 要求是任意一块积木如果想堆在另一块积木上面,那么要求: +// 1) 上面的积木重量不能小于下面的积木重量 +// 2) 上面积木的重量减去下面积木的重量不能超过x +// 3) 每堆中最下面的积木没有重量要求 +// 现在小明有一个机会,除了这n块积木,还可以获得k块任意重量的积木。 +// 小明希望将积木堆在一起,同时希望积木堆的数量越少越好,你能帮他找到最好的方案么? +// 输入描述: +// 第一行三个整数n,k,x,1<=n<=200000,0<=x,k<=1000000000 +// 第二行n个整数,表示积木的重量,任意整数范围都在[1,1000000000] +// 样例输出: +// 13 1 38 +// 20 20 80 70 70 70 420 5 1 5 1 60 90 +// 1 1 5 5 20 20 60 70 70 70 80 90 420 -> 只有1块魔法积木,x = 38 +// 输出:2 +// 解释: +// 两堆分别是 +// 1 1 5 5 20 20 (50) 60 70 70 70 80 90 +// 420 +// 其中x是一个任意重量的积木,夹在20和60之间可以让积木继续往上搭 +public class Code01_SplitBuildingBlock { + + // 这是启发解 + // arr是从小到大排序的,x是限制,固定参数 + // 当前来到i位置,积木重量arr[i] + // 潜台词 : 当前i位置的积木在一个堆里,堆的开头在哪?之前已经决定了 + // i i+1 该在一起 or 该用魔法积木弥合 or 该分家 + // 返回值:arr[i....]最少能分几个堆? + public static int zuo(int[] arr, int x, int i, int r) { + if (i == arr.length - 1) { + return 1; + } + // i没到最后一个数 + if (arr[i + 1] - arr[i] <= x) { // 一定贴在一起 + return zuo(arr, x, i + 1, r); + } else { // 弥合!分家 + // 分家 + int p1 = 1 + zuo(arr, x, i + 1, r); + // 弥合 + int p2 = Integer.MAX_VALUE; + int need = (arr[i + 1] - arr[i] - 1) / x; + if (r >= need) { + p2 = zuo(arr, x, i + 1, r - need); + } + return Math.min(p1, p2); + } + } + + // 这是最优解 + // arr里装着所有积木的重量 + // k是魔法积木的数量,每一块魔法积木都能变成任何重量 + // x差值,后 - 前 <= x + public static int minSplit(int[] arr, int k, int x) { + Arrays.sort(arr); + int n = arr.length; + int[] needs = new int[n]; + int size = 0; + int splits = 1; + for (int i = 1; i < n; i++) { + if (arr[i] - arr[i - 1] > x) { + needs[size++] = arr[i] - arr[i - 1]; + splits++; + } + } + if (splits == 1 || x == 0 || k == 0) { + return splits; + } + // 试图去利用魔法积木,弥合堆! + Arrays.sort(needs, 0, size); + for (int i = 0; i < size; i++) { + int need = (needs[i] - 1) / x; + if (k >= need) { + splits--; + k -= need; + } else { + break; + } + } + return splits; + } + +} diff --git a/大厂刷题班/class45/Problem_0291_WordPatternII.java b/大厂刷题班/class45/Problem_0291_WordPatternII.java new file mode 100644 index 0000000..a961a6d --- /dev/null +++ b/大厂刷题班/class45/Problem_0291_WordPatternII.java @@ -0,0 +1,62 @@ +package class45; + +import java.util.HashSet; + +public class Problem_0291_WordPatternII { + + public static boolean wordPatternMatch(String pattern, String str) { + return match(str, pattern, 0, 0, new String[26], new HashSet<>()); + } + + // 题目有限制,str和pattern其中的字符,一定是a~z小写 + // p[a] -> "abc" + // p[b] -> "fbf" + // 需要指代的表最多26长度 + // String[] map -> new String[26] + // p[a] -> "abc" map[0] -> "abc" + // p[b] -> "fbf" map[1] -> "fbf"; + // p[z] -> "kfk" map[25] -> "kfk" + // HashSet set -> map中指代了哪些字符串 + // str[si.......] 是不是符合 p[pi......]?符合返回true,不符合返回false + // 之前的决定!由map和set,告诉我!不能冲突! + public static boolean match(String s, String p, int si, int pi, String[] map, HashSet set) { + if (pi == p.length() && si == s.length()) { + return true; + } + // str和pattern,并没有都结束! + if (pi == p.length() || si == s.length()) { + return false; + } + // str和pattern,都没结束! + + char ch = p.charAt(pi); + String cur = map[ch - 'a']; + if (cur != null) { // 当前p[pi]已经指定过了! + return si + cur.length() <= s.length() // 不能越界! + && cur.equals(s.substring(si, si + cur.length())) + && match(s, p, si + cur.length(), pi + 1, map, set); + } + // p[pi]没指定! + int end = s.length(); + // 剪枝!重要的剪枝! + for (int i = p.length() - 1; i > pi; i--) { + end -= map[p.charAt(i) - 'a'] == null ? 1 : map[p.charAt(i) - 'a'].length(); + } + for (int i = si; i < end; i++) { + // 从si出发的所有前缀串,全试 + cur = s.substring(si, i + 1); + // 但是,只有这个前缀串,之前没占过别的坑!才能去尝试 + if (!set.contains(cur)) { + set.add(cur); + map[ch - 'a'] = cur; + if (match(s, p, i + 1, pi + 1, map, set)) { + return true; + } + map[ch - 'a'] = null; + set.remove(cur); + } + } + return false; + } + +} diff --git a/大厂刷题班/class45/Problem_0403_FrogJump.java b/大厂刷题班/class45/Problem_0403_FrogJump.java new file mode 100644 index 0000000..08540e5 --- /dev/null +++ b/大厂刷题班/class45/Problem_0403_FrogJump.java @@ -0,0 +1,40 @@ +package class45; + +import java.util.HashMap; +import java.util.HashSet; + +public class Problem_0403_FrogJump { + + public static boolean canCross(int[] stones) { + HashSet set = new HashSet<>(); + for (int num : stones) { + set.add(num); + } + HashMap> dp = new HashMap<>(); + return jump(1, 1, stones[stones.length - 1], set, dp); + } + + public static boolean jump(int cur, int pre, int end, HashSet set, + HashMap> dp) { + if (cur == end) { + return true; + } + if (!set.contains(cur)) { + return false; + } + if (dp.containsKey(cur) && dp.get(cur).containsKey(pre)) { + return dp.get(cur).get(pre); + } + boolean ans = (pre > 1 && jump(cur + pre - 1, pre - 1, end, set, dp)) + || jump(cur + pre, pre, end, set, dp) + || jump(cur + pre + 1, pre + 1, end, set, dp); + if (!dp.containsKey(cur)) { + dp.put(cur, new HashMap<>()); + } + if (!dp.get(cur).containsKey(pre)) { + dp.get(cur).put(pre, ans); + } + return ans; + } + +} diff --git a/大厂刷题班/class45/Problem_2035_PartitionArrayIntoTwoArraysToMinimizeSumDifference.java b/大厂刷题班/class45/Problem_2035_PartitionArrayIntoTwoArraysToMinimizeSumDifference.java new file mode 100644 index 0000000..3592e53 --- /dev/null +++ b/大厂刷题班/class45/Problem_2035_PartitionArrayIntoTwoArraysToMinimizeSumDifference.java @@ -0,0 +1,58 @@ +package class45; + +import java.util.HashMap; +import java.util.TreeSet; + +public class Problem_2035_PartitionArrayIntoTwoArraysToMinimizeSumDifference { + + public static int minimumDifference(int[] arr) { + int size = arr.length; + int half = size >> 1; + HashMap> lmap = new HashMap<>(); + process(arr, 0, half, 0, 0, lmap); + HashMap> rmap = new HashMap<>(); + process(arr, half, size, 0, 0, rmap); + int sum = 0; + for (int num : arr) { + sum += num; + } + int ans = Integer.MAX_VALUE; + for (int leftNum : lmap.keySet()) { + for (int leftSum : lmap.get(leftNum)) { + Integer rightSum = rmap.get(half - leftNum).floor((sum >> 1) - leftSum); + if (rightSum != null) { + int pickSum = leftSum + rightSum; + int restSum = sum - pickSum; + ans = Math.min(ans, restSum - pickSum); + } + } + } + return ans; + } + + + // arr -> 8 0 1 2 3 4 5 6 7 + // process(arr, 0, 4) [0,4) + // process(arr, 4, 8) [4,8) + // arr[index....end-1]这个范围上,去做选择 + // pick挑了几个数! + // sum挑的这些数,累加和是多少! + // map记录结果 + // HashMap> map + // key -> 挑了几个数,比如挑了3个数,但是形成累加和可能多个! + // value -> 有序表,都记下来! + // 整个过程,纯暴力!2^15 -> 3万多,纯暴力跑完,依然很快! + public static void process(int[] arr, int index, int end, int pick, int sum, + HashMap> map) { + if (index == end) { + if (!map.containsKey(pick)) { + map.put(pick, new TreeSet<>()); + } + map.get(pick).add(sum); + } else { + process(arr, index + 1, end, pick, sum, map); + process(arr, index + 1, end, pick + 1, sum + arr[index], map); + } + } + +} diff --git a/大厂刷题班/class46/Problem_0363_MaxSumOfRectangleNoLargerThanK.java b/大厂刷题班/class46/Problem_0363_MaxSumOfRectangleNoLargerThanK.java new file mode 100644 index 0000000..2694c13 --- /dev/null +++ b/大厂刷题班/class46/Problem_0363_MaxSumOfRectangleNoLargerThanK.java @@ -0,0 +1,77 @@ +package class46; + +import java.util.TreeSet; + +public class Problem_0363_MaxSumOfRectangleNoLargerThanK { + + public static int nearK(int[] arr, int k) { + if (arr == null || arr.length == 0) { + return Integer.MIN_VALUE; + } + TreeSet set = new TreeSet<>(); + set.add(0); + int ans = Integer.MIN_VALUE; + int sum = 0; + for (int i = 0; i < arr.length; i++) { + // 讨论子数组必须以i位置结尾,最接近k的累加和是多少? + sum += arr[i]; + // 找之前哪个前缀和 >= sum - k 且最接近 + // 有序表中,ceiling(x) 返回>=x且最接近的! + // 有序表中,floor(x) 返回<=x且最接近的! + Integer find = set.ceiling(sum - k); + if(find != null) { + int curAns = sum - find; + ans = Math.max(ans, curAns); + } + set.add(sum); + } + return ans; + } + + public static int maxSumSubmatrix(int[][] matrix, int k) { + if (matrix == null || matrix[0] == null) { + return 0; + } + if (matrix.length > matrix[0].length) { + matrix = rotate(matrix); + } + int row = matrix.length; + int col = matrix[0].length; + int res = Integer.MIN_VALUE; + TreeSet sumSet = new TreeSet<>(); + for (int s = 0; s < row; s++) { + int[] colSum = new int[col]; + for (int e = s; e < row; e++) { + // s ~ e 这些行 选的子矩阵必须包含、且只包含s行~e行的数据 + // 0 ~ 0 0 ~ 1 0 ~ 2 。。。 + // 1 ~ 2 1 ~ 2 1 ~ 3 。。。 + sumSet.add(0); + int rowSum = 0; + for (int c = 0; c < col; c++) { + colSum[c] += matrix[e][c]; + rowSum += colSum[c]; + Integer it = sumSet.ceiling(rowSum - k); + if (it != null) { + res = Math.max(res, rowSum - it); + } + sumSet.add(rowSum); + } + sumSet.clear(); + } + } + return res; + } + + public static int[][] rotate(int[][] matrix) { + int N = matrix.length; + int M = matrix[0].length; + int[][] r = new int[M][N]; + for (int i = 0; i < N; i++) { + for (int j = 0; j < M; j++) { + r[j][i] = matrix[i][j]; + } + } + return r; + } + +} diff --git a/大厂刷题班/class46/Problem_0391_PerfectRectangle.java b/大厂刷题班/class46/Problem_0391_PerfectRectangle.java new file mode 100644 index 0000000..b041a47 --- /dev/null +++ b/大厂刷题班/class46/Problem_0391_PerfectRectangle.java @@ -0,0 +1,59 @@ +package class46; + +import java.util.HashMap; + +public class Problem_0391_PerfectRectangle { + + public static boolean isRectangleCover(int[][] matrix) { + if (matrix.length == 0 || matrix[0].length == 0) { + return false; + } + int l = Integer.MAX_VALUE; + int r = Integer.MIN_VALUE; + int d = Integer.MAX_VALUE; + int u = Integer.MIN_VALUE; + HashMap> map = new HashMap<>(); + int area = 0; + for (int[] rect : matrix) { + add(map, rect[0], rect[1]); + add(map, rect[0], rect[3]); + add(map, rect[2], rect[1]); + add(map, rect[2], rect[3]); + area += (rect[2] - rect[0]) * (rect[3] - rect[1]); + l = Math.min(rect[0], l); + d = Math.min(rect[1], d); + r = Math.max(rect[2], r); + u = Math.max(rect[3], u); + } + return checkPoints(map, l, d, r, u) && area == (r - l) * (u - d); + } + + public static void add(HashMap> map, int row, int col) { + if (!map.containsKey(row)) { + map.put(row, new HashMap<>()); + } + map.get(row).put(col, map.get(row).getOrDefault(col, 0) + 1); + } + + public static boolean checkPoints(HashMap> map, int l, int d, int r, int u) { + if (map.get(l).getOrDefault(d, 0) != 1 + || map.get(l).getOrDefault(u, 0) != 1 + || map.get(r).getOrDefault(d, 0) != 1 + || map.get(r).getOrDefault(u, 0) != 1) { + return false; + } + map.get(l).remove(d); + map.get(l).remove(u); + map.get(r).remove(d); + map.get(r).remove(u); + for (int key : map.keySet()) { + for (int value : map.get(key).values()) { + if ((value & 1) != 0) { + return false; + } + } + } + return true; + } + +} diff --git a/大厂刷题班/class46/Problem_0411_MinimumUniqueWordAbbreviation.java b/大厂刷题班/class46/Problem_0411_MinimumUniqueWordAbbreviation.java new file mode 100644 index 0000000..29d840f --- /dev/null +++ b/大厂刷题班/class46/Problem_0411_MinimumUniqueWordAbbreviation.java @@ -0,0 +1,174 @@ +package class46; + +public class Problem_0411_MinimumUniqueWordAbbreviation { + + // 区分出来之后,缩写的长度,最短是多少? + public static int min = Integer.MAX_VALUE; + + // 取得缩写的长度最短的时候,决定是什么(fix) + public static int best = 0; + + public static int abbrLen(int fix, int len) { + int ans = 0; + int cnt = 0; + for (int i = 0; i < len; i++) { + if ((fix & (1 << i)) != 0) { + ans++; + if (cnt != 0) { + ans += (cnt > 9 ? 2 : 1) - cnt; + } + cnt = 0; + } else { + cnt++; + } + } + if (cnt != 0) { + ans += (cnt > 9 ? 2 : 1) - cnt; + } + return ans; + } + + // 原始的字典,被改了 + // target : abc 字典中的词 : bbb -> 101 -> int -> + // fix -> int -> 根本不用值,用状态 -> 每一位保留还是不保留的决定 + public static boolean canFix(int[] words, int fix) { + for (int word : words) { + if ((fix & word) == 0) { + return false; + } + } + return true; + } + + // 利用位运算加速 + public static String minAbbreviation1(String target, String[] dictionary) { + min = Integer.MAX_VALUE; + best = 0; + char[] t = target.toCharArray(); + int len = t.length; + int siz = 0; + for (String word : dictionary) { + if (word.length() == len) { + siz++; + } + } + int[] words = new int[siz]; + int index = 0; + for (String word : dictionary) { + if (word.length() == len) { + char[] w = word.toCharArray(); + int status = 0; + for (int j = 0; j < len; j++) { + if (t[j] != w[j]) { + status |= 1 << j; + } + } + words[index++] = status; + } + } + dfs1(words, len, 0, 0); + StringBuilder builder = new StringBuilder(); + int count = 0; + for (int i = 0; i < len; i++) { + if ((best & (1 << i)) != 0) { + if (count > 0) { + builder.append(count); + } + builder.append(t[i]); + count = 0; + } else { + count++; + } + } + if (count > 0) { + builder.append(count); + } + return builder.toString(); + } + + // 所有字典中的单词现在都变成了int,放在words里 + // 0....len-1 位去决定保留还是不保留!当前来到index位 + // 之前做出的决定! + public static void dfs1(int[] words, int len, int fix, int index) { + if (!canFix(words, fix)) { + if (index < len) { + dfs1(words, len, fix, index + 1); + dfs1(words, len, fix | (1 << index), index + 1); + } + } else { + // 决定是fix,一共的长度是len,求出缩写是多长? + int ans = abbrLen(fix, len); + if (ans < min) { + min = ans; + best = fix; + } + } + } + + // 进一步设计剪枝,注意diff的用法 + public static String minAbbreviation2(String target, String[] dictionary) { + min = Integer.MAX_VALUE; + best = 0; + char[] t = target.toCharArray(); + int len = t.length; + int siz = 0; + for (String word : dictionary) { + if (word.length() == len) { + siz++; + } + } + int[] words = new int[siz]; + int index = 0; + // 用来剪枝 + int diff = 0; + for (String word : dictionary) { + if (word.length() == len) { + char[] w = word.toCharArray(); + int status = 0; + for (int j = 0; j < len; j++) { + if (t[j] != w[j]) { + status |= 1 << j; + } + } + words[index++] = status; + diff |= status; + } + } + dfs2(words, len, diff, 0, 0); + StringBuilder builder = new StringBuilder(); + int count = 0; + for (int i = 0; i < len; i++) { + if ((best & (1 << i)) != 0) { + if (count > 0) { + builder.append(count); + } + builder.append(t[i]); + count = 0; + } else { + count++; + } + } + if (count > 0) { + builder.append(count); + } + return builder.toString(); + } + + public static void dfs2(int[] words, int len, int diff, int fix, int index) { + if (!canFix(words, fix)) { + if (index < len) { + dfs2(words, len, diff, fix, index + 1); + if ((diff & (1 << index)) != 0) { + dfs2(words, len, diff, fix | (1 << index), index + 1); + } + } + } else { + int ans = abbrLen(fix, len); + if (ans < min) { + min = ans; + best = fix; + } + } + } + +} diff --git a/大厂刷题班/class46/Problem_0425_WordSquares.java b/大厂刷题班/class46/Problem_0425_WordSquares.java new file mode 100644 index 0000000..24f2418 --- /dev/null +++ b/大厂刷题班/class46/Problem_0425_WordSquares.java @@ -0,0 +1,87 @@ +package class46; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; + +// 注意!课上介绍题目设定的时候,有一点点小错 +// 题目描述如下: +// 给定n个字符串,并且每个字符串长度一定是n,请组成单词方阵,比如: +// 给定4个字符串,长度都是4,["ball","area","lead","lady"] +// 可以组成如下的方阵: +// b a l l +// a r e a +// l e a d +// l a d y +// 什么叫单词方阵?如上的方阵可以看到, +// 第1行和第1列都是"ball",第2行和第2列都是"area",第3行和第3列都是"lead",第4行和第4列都是"lady" +// 所以如果有N个单词,单词方阵是指: +// 一个N*N的二维矩阵,并且i行和i列都是某个单词,不要求全部N个单词都在这个方阵里。 +// 请返回所有可能的单词方阵。 +// 示例: +// 输入: words = ["abat","baba","atan","atal"] +// 输出: [["baba","abat","baba","atal"],["baba","abat","baba","atan"]] +// 解释: +// 可以看到输出里,有两个链表,代表两个单词方阵 +// 第一个如下: +// b a b a +// a b a t +// b a b a +// a t a l +// 这个方阵里没有atan,因为不要求全部单词都在方阵里 +// 第二个如下: +// b a b a +// a b a t +// b a b a +// a t a n +// 这个方阵里没有atal,因为不要求全部单词都在方阵里 +// 课上说的是:一个N*N的二维矩阵,并且i行和i列都是某个单词,要求全部N个单词都在这个方阵里 +// 原题说的是:一个N*N的二维矩阵,并且i行和i列都是某个单词,不要求全部N个单词都在这个方阵里 +// 讲的过程没错,但是介绍题意的时候,这里失误了 +public class Problem_0425_WordSquares { + + public static List> wordSquares(String[] words) { + int n = words[0].length(); + // 所有单词,所有前缀字符串,都会成为key! + HashMap> map = new HashMap<>(); + for (String word : words) { + for (int end = 0; end <= n; end++) { + String prefix = word.substring(0, end); + if (!map.containsKey(prefix)) { + map.put(prefix, new ArrayList<>()); + } + map.get(prefix).add(word); + } + } + List> ans = new ArrayList<>(); + process(0, n, map, new LinkedList<>(), ans); + return ans; + } + + // i, 当前填到第i号单词,从0开始,填到n-1 + // map, 前缀所拥有的单词 + // path, 之前填过的单词, 0...i-1填过的 + // ans, 收集答案 + public static void process(int i, int n, HashMap> map, LinkedList path, + List> ans) { + if (i == n) { + ans.add(new ArrayList<>(path)); + } else { + // 把限制求出来,前缀的限制! + StringBuilder builder = new StringBuilder(); + for (String pre : path) { + builder.append(pre.charAt(i)); + } + String prefix = builder.toString(); + if (map.containsKey(prefix)) { + for (String next : map.get(prefix)) { + path.addLast(next); + process(i + 1, n, map, path, ans); + path.pollLast(); + } + } + } + } + +} diff --git a/大厂刷题班/class47/Code01_DynamicSegmentTree.java b/大厂刷题班/class47/Code01_DynamicSegmentTree.java new file mode 100644 index 0000000..6a75a39 --- /dev/null +++ b/大厂刷题班/class47/Code01_DynamicSegmentTree.java @@ -0,0 +1,135 @@ +package class47; + +// 只支持单点增加 + 范围查询的动态开点线段树(累加和) +public class Code01_DynamicSegmentTree { + + public static class Node { + public int sum; + public Node left; + public Node right; + } + + // arr[0] -> 1 + // 线段树,从1开始下标! + public static class DynamicSegmentTree { + public Node root; + public int size; + + public DynamicSegmentTree(int max) { + root = new Node(); + size = max; + } + + // 下标i这个位置的数,增加v + public void add(int i, int v) { + add(root, 1, size, i, v); + } + + // c-> cur 当前节点!表达的范围 l~r + // i位置的数,增加v + // 潜台词!i一定在l~r范围上! + private void add(Node c, int l, int r, int i, int v) { + if (l == r) { + c.sum += v; + } else { // l~r 还可以划分 + int mid = (l + r) / 2; + if (i <= mid) { // l ~ mid + if (c.left == null) { + c.left = new Node(); + } + add(c.left, l, mid, i, v); + } else { // mid + 1 ~ r + if (c.right == null) { + c.right = new Node(); + } + add(c.right, mid + 1, r, i, v); + } + c.sum = (c.left != null ? c.left.sum : 0) + (c.right != null ? c.right.sum : 0); + } + } + + // s~e范围的累加和,告诉我! + public int query(int s, int e) { + return query(root, 1, size, s, e); + } + + // 当前节点c,表达的范围l~r + // 收到了一个任务,s~e这个任务! + // s~e这个任务,影响了多少l~r范围的数,把答案返回! + private int query(Node c, int l, int r, int s, int e) { + if (c == null) { + return 0; + } + if (s <= l && r <= e) { // 3~6 1~100任务 + return c.sum; + } + // 有影响,但又不是全影响 + // l ~ r + // l~mid mid+1~r + int mid = (l + r) / 2; + // 1~100 + // 1~50 51 ~ 100 + // 任务 s~e 53~76 + if (e <= mid) { + return query(c.left, l, mid, s, e); + } else if (s > mid) { + return query(c.right, mid + 1, r, s, e); + } else { + return query(c.left, l, mid, s, e) + query(c.right, mid + 1, r, s, e); + } + } + + } + + public static class Right { + public int[] arr; + + public Right(int size) { + arr = new int[size + 1]; + } + + public void add(int i, int v) { + arr[i] += v; + } + + public int query(int s, int e) { + int sum = 0; + for (int i = s; i <= e; i++) { + sum += arr[i]; + } + return sum; + } + + } + + public static void main(String[] args) { + int size = 10000; + int testTime = 50000; + int value = 500; + DynamicSegmentTree dst = new DynamicSegmentTree(size); + Right right = new Right(size); + System.out.println("测试开始"); + for (int k = 0; k < testTime; k++) { + if (Math.random() < 0.5) { + int i = (int) (Math.random() * size) + 1; + int v = (int) (Math.random() * value); + dst.add(i, v); + right.add(i, v); + } else { + int a = (int) (Math.random() * size) + 1; + int b = (int) (Math.random() * size) + 1; + int s = Math.min(a, b); + int e = Math.max(a, b); + int ans1 = dst.query(s, e); + int ans2 = right.query(s, e); + if (ans1 != ans2) { + System.out.println("出错了!"); + System.out.println(ans1); + System.out.println(ans2); + } + } + } + System.out.println("测试结束"); + } + +} diff --git a/大厂刷题班/class47/Code02_DynamicSegmentTree.java b/大厂刷题班/class47/Code02_DynamicSegmentTree.java new file mode 100644 index 0000000..1dca034 --- /dev/null +++ b/大厂刷题班/class47/Code02_DynamicSegmentTree.java @@ -0,0 +1,198 @@ +package class47; + +// 同时支持范围增加 + 范围修改 + 范围查询的动态开点线段树(累加和) +// 真的用到!才去建立 +// 懒更新,及其所有的东西,和普通线段树,没有任何区别! +public class Code02_DynamicSegmentTree { + + public static class Node { + public int sum; + public int lazy; + public int change; + public boolean update; + public Node left; + public Node right; + } + + public static class DynamicSegmentTree { + public Node root; + public int size; + + public DynamicSegmentTree(int max) { + root = new Node(); + size = max; + } + + private void pushUp(Node c) { + c.sum = c.left.sum + c.right.sum; + } + + private void pushDown(Node p, int ln, int rn) { + if (p.left == null) { + p.left = new Node(); + } + if (p.right == null) { + p.right = new Node(); + } + if (p.update) { + p.left.update = true; + p.right.update = true; + p.left.change = p.change; + p.right.change = p.change; + p.left.lazy = 0; + p.right.lazy = 0; + p.left.sum = p.change * ln; + p.right.sum = p.change * rn; + p.update = false; + } + if (p.lazy != 0) { + p.left.lazy += p.lazy; + p.right.lazy += p.lazy; + p.left.sum += p.lazy * ln; + p.right.sum += p.lazy * rn; + p.lazy = 0; + } + } + + public void update(int s, int e, int v) { + update(root, 1, size, s, e, v); + } + + private void update(Node c, int l, int r, int s, int e, int v) { + if (s <= l && r <= e) { + c.update = true; + c.change = v; + c.sum = v * (r - l + 1); + c.lazy = 0; + } else { + int mid = (l + r) >> 1; + pushDown(c, mid - l + 1, r - mid); + if (s <= mid) { + update(c.left, l, mid, s, e, v); + } + if (e > mid) { + update(c.right, mid + 1, r, s, e, v); + } + pushUp(c); + } + } + + public void add(int s, int e, int v) { + add(root, 1, size, s, e, v); + } + + private void add(Node c, int l, int r, int s, int e, int v) { + if (s <= l && r <= e) { + c.sum += v * (r - l + 1); + c.lazy += v; + } else { + int mid = (l + r) >> 1; + pushDown(c, mid - l + 1, r - mid); + if (s <= mid) { + add(c.left, l, mid, s, e, v); + } + if (e > mid) { + add(c.right, mid + 1, r, s, e, v); + } + pushUp(c); + } + } + + public int query(int s, int e) { + return query(root, 1, size, s, e); + } + + private int query(Node c, int l, int r, int s, int e) { + if (s <= l && r <= e) { + return c.sum; + } + int mid = (l + r) >> 1; + pushDown(c, mid - l + 1, r - mid); + int ans = 0; + if (s <= mid) { + ans += query(c.left, l, mid, s, e); + } + if (e > mid) { + ans += query(c.right, mid + 1, r, s, e); + } + return ans; + } + + } + + public static class Right { + public int[] arr; + + public Right(int size) { + arr = new int[size + 1]; + } + + public void add(int s, int e, int v) { + for (int i = s; i <= e; i++) { + arr[i] += v; + } + } + + public void update(int s, int e, int v) { + for (int i = s; i <= e; i++) { + arr[i] = v; + } + } + + public int query(int s, int e) { + int sum = 0; + for (int i = s; i <= e; i++) { + sum += arr[i]; + } + return sum; + } + + } + + public static void main(String[] args) { + int n = 1000; + int value = 50; + int createTimes = 5000; + int operateTimes = 5000; + System.out.println("测试开始"); + for (int i = 0; i < createTimes; i++) { + int size = (int) (Math.random() * n) + 1; + DynamicSegmentTree dst = new DynamicSegmentTree(size); + Right right = new Right(size); + for (int k = 0; k < operateTimes; k++) { + double choose = Math.random(); + if (choose < 0.333) { + int a = (int) (Math.random() * size) + 1; + int b = (int) (Math.random() * size) + 1; + int s = Math.min(a, b); + int e = Math.max(a, b); + int v = (int) (Math.random() * value); + dst.update(s, e, v); + right.update(s, e, v); + } else if (choose < 0.666) { + int a = (int) (Math.random() * size) + 1; + int b = (int) (Math.random() * size) + 1; + int s = Math.min(a, b); + int e = Math.max(a, b); + int v = (int) (Math.random() * value); + dst.add(s, e, v); + right.add(s, e, v); + } else { + int a = (int) (Math.random() * size) + 1; + int b = (int) (Math.random() * size) + 1; + int s = Math.min(a, b); + int e = Math.max(a, b); + int ans1 = dst.query(s, e); + int ans2 = right.query(s, e); + if (ans1 != ans2) { + System.out.println("出错了!"); + System.out.println(ans1); + System.out.println(ans2); + } + } + } + } + System.out.println("测试结束"); + } + +} diff --git a/大厂刷题班/class47/Problem_0315_CountOfSmallerNumbersAfterSelf.java b/大厂刷题班/class47/Problem_0315_CountOfSmallerNumbersAfterSelf.java new file mode 100644 index 0000000..7f01d20 --- /dev/null +++ b/大厂刷题班/class47/Problem_0315_CountOfSmallerNumbersAfterSelf.java @@ -0,0 +1,94 @@ +package class47; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +// 利用只支持单点增加 + 范围查询的动态开点线段树(累加和),解决leetcode 315 +public class Problem_0315_CountOfSmallerNumbersAfterSelf { + + public static class Node { + public int sum; + public Node left; + public Node right; + } + + public static class DynamicSegmentTree { + public Node root; + public int size; + + public DynamicSegmentTree(int max) { + root = new Node(); + size = max; + } + + public void add(int i, int v) { + add(root, 1, size, i, v); + } + + private void add(Node c, int l, int r, int i, int v) { + if (l == r) { + c.sum += v; + } else { + int mid = (l + r) / 2; + if (i <= mid) { + if (c.left == null) { + c.left = new Node(); + } + add(c.left, l, mid, i, v); + } else { + if (c.right == null) { + c.right = new Node(); + } + add(c.right, mid + 1, r, i, v); + } + c.sum = (c.left != null ? c.left.sum : 0) + (c.right != null ? c.right.sum : 0); + } + } + + public int query(int s, int e) { + return query(root, 1, size, s, e); + } + + private int query(Node c, int l, int r, int s, int e) { + if (c == null) { + return 0; + } + if (s <= l && r <= e) { + return c.sum; + } + int mid = (l + r) / 2; + if (e <= mid) { + return query(c.left, l, mid, s, e); + } else if (s > mid) { + return query(c.right, mid + 1, r, s, e); + } else { + return query(c.left, l, mid, s, e) + query(c.right, mid + 1, r, s, e); + } + } + + } + + public static List countSmaller(int[] nums) { + List ans = new ArrayList<>(); + if (nums == null || nums.length == 0) { + return ans; + } + int n = nums.length; + for (int i = 0; i < n; i++) { + ans.add(0); + } + int[][] arr = new int[n][]; + for (int i = 0; i < n; i++) { + arr[i] = new int[] { nums[i], i }; + } + Arrays.sort(arr, (a, b) -> (a[0] - b[0])); + DynamicSegmentTree dst = new DynamicSegmentTree(n); + for (int[] num : arr) { + ans.set(num[1], dst.query(num[1] + 1, n)); + dst.add(num[1] + 1, 1); + } + return ans; + } + +} diff --git a/大厂刷题班/class47/Problem_0358_RearrangeStringKDistanceApart.java b/大厂刷题班/class47/Problem_0358_RearrangeStringKDistanceApart.java new file mode 100644 index 0000000..60319c7 --- /dev/null +++ b/大厂刷题班/class47/Problem_0358_RearrangeStringKDistanceApart.java @@ -0,0 +1,64 @@ +package class47; + +import java.util.ArrayList; +import java.util.Arrays; + +// 本题的解法思路与leetcode 621题 TaskScheduler 问题是一样的 +public class Problem_0358_RearrangeStringKDistanceApart { + + public String rearrangeString(String s, int k) { + if (s == null || s.length() < k) { + return s; + } + char[] str = s.toCharArray(); + int[][] cnts = new int[256][2]; + for (int i = 0; i < 256; i++) { + cnts[i] = new int[] { i, 0 }; + } + int maxCount = 0; + for (char task : str) { + cnts[task][1]++; + maxCount = Math.max(maxCount, cnts[task][1]); + } + int maxKinds = 0; + for (int task = 0; task < 256; task++) { + if (cnts[task][1] == maxCount) { + maxKinds++; + } + } + int N = str.length; + if (!isValid(N, k, maxCount, maxKinds)) { + return ""; + } + ArrayList ans = new ArrayList<>(); + for (int i = 0; i < maxCount; i++) { + ans.add(new StringBuilder()); + } + Arrays.sort(cnts, (a, b) -> (b[1] - a[1])); + int i = 0; + for (; i < 256 && cnts[i][1] == maxCount; i++) { + for (int j = 0; j < maxCount; j++) { + ans.get(j).append((char) cnts[i][0]); + } + } + int out = 0; + for (; i < 256; i++) { + for (int j = 0; j < cnts[i][1]; j++) { + ans.get(out).append((char) cnts[i][0]); + out = out == ans.size() - 2 ? 0 : out + 1; + } + } + StringBuilder builder = new StringBuilder(); + for (StringBuilder b : ans) { + builder.append(b.toString()); + } + return builder.toString(); + } + + public static boolean isValid(int N, int k, int maxCount, int maxKinds) { + int restTasks = N - maxKinds; + int spaces = k * (maxCount - 1); + return spaces - restTasks <= 0; + } + +} diff --git a/大厂刷题班/class47/Problem_0428_SerializeAndDeserializeNaryTree.java b/大厂刷题班/class47/Problem_0428_SerializeAndDeserializeNaryTree.java new file mode 100644 index 0000000..6b9c1df --- /dev/null +++ b/大厂刷题班/class47/Problem_0428_SerializeAndDeserializeNaryTree.java @@ -0,0 +1,103 @@ +package class47; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; + +public class Problem_0428_SerializeAndDeserializeNaryTree { + + // 不要提交这个类 + public static class Node { + public int val; + public List children; + + public Node() { + children = new ArrayList<>(); + } + + public Node(int _val) { + val = _val; + children = new ArrayList<>(); + } + + public Node(int _val, List _children) { + val = _val; + children = _children; + } + }; + + // 提交下面这个类 + public static class Codec { + + public static String serialize(Node root) { + if (root == null) { // 空树!直接返回# + return "#"; + } + StringBuilder builder = new StringBuilder(); + serial(builder, root); + return builder.toString(); + } + + // 当前来到head + private static void serial(StringBuilder builder, Node head) { + builder.append(head.val + ","); + if (!head.children.isEmpty()) { + builder.append("[,"); + for (Node next : head.children) { + serial(builder, next); + } + builder.append("],"); + } + } + + public static Node deserialize(String data) { + if (data.equals("#")) { + return null; + } + String[] splits = data.split(","); + Queue queue = new LinkedList<>(); + for (String str : splits) { + queue.offer(str); + } + return deserial(queue); + } + + private static Node deserial(Queue queue) { + Node cur = new Node(Integer.valueOf(queue.poll())); + cur.children = new ArrayList<>(); + if (!queue.isEmpty() && queue.peek().equals("[")) { + queue.poll(); + while (!queue.peek().equals("]")) { + Node child = deserial(queue); + cur.children.add(child); + } + queue.poll(); + } + return cur; + } + + } + + public static void main(String[] args) { + // 如果想跑以下的code,请把Codec类描述和内部所有方法改成static的 + Node a = new Node(1); + Node b = new Node(2); + Node c = new Node(3); + Node d = new Node(4); + Node e = new Node(5); + Node f = new Node(6); + Node g = new Node(7); + a.children.add(b); + a.children.add(c); + a.children.add(d); + b.children.add(e); + b.children.add(f); + e.children.add(g); + String content = Codec.serialize(a); + System.out.println(content); + Node head = Codec.deserialize(content); + System.out.println(content.equals(Codec.serialize(head))); + } + +} diff --git a/大厂刷题班/class47/Problem_0465_OptimalAccountBalancing.java b/大厂刷题班/class47/Problem_0465_OptimalAccountBalancing.java new file mode 100644 index 0000000..d1fe945 --- /dev/null +++ b/大厂刷题班/class47/Problem_0465_OptimalAccountBalancing.java @@ -0,0 +1,149 @@ +package class47; + +import java.util.Arrays; +import java.util.HashMap; + +// 需要证明: +// 一个集合中,假设整体的累加和为K, +// 不管该集合用了什么样的0集合划分方案,当一个新的数到来时: +// 1) 如果该数是-K,那么任何0集合的划分方案中,因为新数字的加入,0集合的数量都会+1 +// 2) 如果该数不是-K,那么任何0集合的划分方案中,0集合的数量都会不变 +public class Problem_0465_OptimalAccountBalancing { + + // 用位信息替代集合结构的暴力尝试 + public static int minTransfers1(int[][] transactions) { + // 不管转账有几笔,最终每个人收到的钱,如果是0,不进入debt数组 + // 只有最终收到的钱,不等于0的人,进入debt数组 + // 收到的钱,4,说明该给出去! + // 收到的钱,-4,说明该要回来! + // debt数组的累加和,必为0! + int[] debt = debts(transactions); + int N = debt.length; + return N - process1(debt, (1 << N) - 1, 0, N); + } + + // set -> int -> 不使用值 -> 只使用状态! + // 001110 0号人,不在集合里;1、2、3号人在集合里,4、5号人不在集合里! + // sum -> set这个集合累加和是多少?sum被set决定的! + // debt数组,收到的钱的数组(固定) + // N, debt的长度(固定) + // 返回值含义 : set这个集合中,最多能划分出多少个小集合累加和是0,返回累加和是0的小集合最多的数量 + public static int process1(int[] debt, int set, int sum, int N) { + // set中只有一个人的时候! + // debt中,没有0的,所以每个人一定都需要转账! + if ((set & (set - 1)) == 0) { + return 0; + } + int value = 0; + int max = 0; + // 尝试,每一个人都最后考虑 + // 0,如果最后考虑 + // 1,如果最后考虑 + // 2,如果最后考虑 + // .... + // n-1,最后考虑 + // 011001 + // 0(在) + // 1(不能考虑!因为不在集合里!) + for (int i = 0; i < N; i++) { + value = debt[i]; + if ((set & (1 << i)) != 0) { // i号人真的在集合里,才能考虑! + // 011001 + // 3号人在 + // 3号人之前,010001(考虑0号人和4号人剩下的情况) + // process ( set ^ (1 << i) , sum - value ) + max = Math.max(max, process1(debt, set ^ (1 << i), sum - value, N)); + } + } + return sum == 0 ? max + 1 : max; + } + + // 上面的尝试过程 + 记忆化搜索 + // 最优解 + public static int minTransfers2(int[][] transactions) { + int[] debt = debts(transactions); + int N = debt.length; + int sum = 0; + for (int num : debt) { + sum += num; + } + int[] dp = new int[1 << N]; + Arrays.fill(dp, -1); + return N - process2(debt, (1 << N) - 1, sum, N, dp); + } + + public static int process2(int[] debt, int set, int sum, int N, int[] dp) { + if (dp[set] != -1) { + return dp[set]; + } + int ans = 0; + if ((set & (set - 1)) != 0) { + int value = 0; + int max = 0; + for (int i = 0; i < N; i++) { + value = debt[i]; + if ((set & (1 << i)) != 0) { + max = Math.max(max, process2(debt, set ^ (1 << i), sum - value, N, dp)); + } + } + ans = sum == 0 ? max + 1 : max; + } + dp[set] = ans; + return ans; + } + + public static int[] debts(int[][] transactions) { + HashMap map = new HashMap<>(); + for (int[] tran : transactions) { + map.put(tran[0], map.getOrDefault(tran[0], 0) + tran[2]); + map.put(tran[1], map.getOrDefault(tran[1], 0) - tran[2]); + } + int N = 0; + for (int value : map.values()) { + if (value != 0) { + N++; + } + } + int[] debt = new int[N]; + int index = 0; + for (int value : map.values()) { + if (value != 0) { + debt[index++] = value; + } + } + return debt; + } + + // 为了测试 + public static int[][] randomTrans(int s, int n, int m) { + int[][] trans = new int[s][3]; + for (int i = 0; i < s; i++) { + trans[i][0] = (int) (Math.random() * n); + trans[i][1] = (int) (Math.random() * n); + trans[i][2] = (int) (Math.random() * m) + 1; + } + return trans; + } + + // 为了测试 + public static void main(String[] args) { + int s = 8; + int n = 8; + int m = 10; + int testTime = 10000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int[][] trans = randomTrans(s, n, m); + int ans1 = minTransfers1(trans); + int ans2 = minTransfers2(trans); + if (ans1 != ans2) { + System.out.println("Oops!"); + System.out.println(ans1); + System.out.println(ans2); + break; + } + } + System.out.println("测试结束"); + } + +} diff --git a/大厂刷题班/class47/Problem_0475_Heaters.java b/大厂刷题班/class47/Problem_0475_Heaters.java new file mode 100644 index 0000000..cd0f7b0 --- /dev/null +++ b/大厂刷题班/class47/Problem_0475_Heaters.java @@ -0,0 +1,113 @@ +package class47; + +import java.util.Arrays; + +public class Problem_0475_Heaters { + + // 比如地点是7, 9, 14 + // 供暖点的位置: 1 3 4 5 13 15 17 + // 先看地点7 + // 由1供暖,半径是6 + // 由3供暖,半径是4 + // 由4供暖,半径是3 + // 由5供暖,半径是2 + // 由13供暖,半径是6 + // 由此可知,地点7应该由供暖点5来供暖,半径是2 + // 再看地点9 + // 供暖点不回退 + // 由5供暖,半径是4 + // 由13供暖,半径是4 + // 由15供暖,半径是6 + // 由此可知,地点9应该由供暖点13来供暖,半径是4 + // 为什么是13而不是5?因为接下来的地点都会更靠右,所以半径一样的时候,就应该选更右的供暖点 + // 再看地点14 + // 供暖点不回退 + // 由13供暖,半径是1 + // 由15供暖,半径是1 + // 由17供暖,半径是3 + // 由此可知,地点14应该由供暖点15来供暖,半径是1 + // 以此类推 + public static int findRadius(int[] houses, int[] heaters) { + Arrays.sort(houses); + Arrays.sort(heaters); + int ans = 0; + // 时间复杂度O(N) + // i是地点,j是供暖点 + for (int i = 0, j = 0; i < houses.length; i++) { + while (!best(houses, heaters, i, j)) { + j++; + } + ans = Math.max(ans, Math.abs(heaters[j] - houses[i])); + } + return ans; + } + + // 这个函数含义: + // 当前的地点houses[i]由heaters[j]来供暖是最优的吗? + // 当前的地点houses[i]由heaters[j]来供暖,产生的半径是a + // 当前的地点houses[i]由heaters[j + 1]来供暖,产生的半径是b + // 如果a < b, 说明是最优,供暖不应该跳下一个位置 + // 如果a >= b, 说明不是最优,应该跳下一个位置 + public static boolean best(int[] houses, int[] heaters, int i, int j) { + return j == heaters.length - 1 + || Math.abs(heaters[j] - houses[i]) < Math.abs(heaters[j + 1] - houses[i]); + } + + // 下面这个方法不对,你能找出原因嘛?^_^ + public static int findRadius2(int[] houses, int[] heaters) { + Arrays.sort(houses); + Arrays.sort(heaters); + int ans = 0; + // 时间复杂度O(N) + // i是地点,j是供暖点 + for (int i = 0, j = 0; i < houses.length; i++) { + while (!best2(houses, heaters, i, j)) { + j++; + } + ans = Math.max(ans, Math.abs(heaters[j] - houses[i])); + } + return ans; + } + + public static boolean best2(int[] houses, int[] heaters, int i, int j) { + return j == heaters.length - 1 || Math.abs(heaters[j] - houses[i]) <= Math.abs(heaters[j + 1] - houses[i]); + } + + // 为了测试 + public static int[] randomArray(int len, int v) { + int[] arr = new int[len]; + for (int i = 0; i < len; i++) { + arr[i] = (int) (Math.random() * v) + 1; + } + return arr; + } + + // 为了测试 + public static void main(String[] args) { + int len = 5; + int v = 10; + int testTime = 10000; + for (int i = 0; i < testTime; i++) { + int[] a = randomArray(len, v); + int[] b = randomArray(len, v); + int ans1 = findRadius(a, b); + int ans2 = findRadius2(a, b); + if (ans1 != ans2) { + System.out.println("A : "); + for (int num : a) { + System.out.print(num + " "); + } + System.out.println(); + System.out.println("B : "); + for (int num : b) { + System.out.print(num + " "); + } + System.out.println(); + System.out.println(ans1); + System.out.println(ans2); + break; + } + } + } + +} diff --git a/大厂刷题班/class48/Code01_MinKthPairMinusABS.java b/大厂刷题班/class48/Code01_MinKthPairMinusABS.java new file mode 100644 index 0000000..14868c8 --- /dev/null +++ b/大厂刷题班/class48/Code01_MinKthPairMinusABS.java @@ -0,0 +1,122 @@ +package class48; + +import java.util.Arrays; + +// 来自学员问题 +// 比如{ 5, 3, 1, 4 } +// 全部数字对是:(5,3)、(5,1)、(5,4)、(3,1)、(3,4)、(1,4) +// 数字对的差值绝对值: 2、4、1、2、1、3 +// 差值绝对值排序后:1、1、2、2、3、4 +// 给定一个数组arr,和一个正数k +// 返回arr中所有数字对差值的绝对值,第k小的是多少 +// arr = { 5, 3, 1, 4 }, k = 4 +// 返回2 +public class Code01_MinKthPairMinusABS { + + // 暴力解,生成所有数字对差值绝对值,排序,拿出第k个,k从1开始 + public static int kthABS1(int[] arr, int k) { + int n = arr.length; + int m = ((n - 1) * n) >> 1; + if (m == 0 || k < 1 || k > m) { + return -1; + } + int[] abs = new int[m]; + int size = 0; + for (int i = 0; i < n; i++) { + for (int j = i + 1; j < n; j++) { + abs[size++] = Math.abs(arr[i] - arr[j]); + } + } + Arrays.sort(abs); + return abs[k - 1]; + } + + // 二分 + 不回退 + public static int kthABS2(int[] arr, int k) { + int n = arr.length; + if (n < 2 || k < 1 || k > ((n * (n - 1)) >> 1)) { + return -1; + } + Arrays.sort(arr); + // 0 ~ 大-小 二分 + // l ~ r + int left = 0; + int right = arr[n - 1] - arr[0]; + int mid = 0; + int rightest = -1; + while (left <= right) { + mid = (left + right) / 2; + // 数字对差值的绝对值<=mid的数字对个数,是不是 < k个的! + if (valid(arr, mid, k)) { + rightest = mid; + left = mid + 1; + } else { + right = mid - 1; + } + } + return rightest + 1; + } + + // 假设arr中的所有数字对,差值绝对值<=limit的个数为x + // 如果 x < k,达标,返回true + // 如果 x >= k,不达标,返回false + public static boolean valid(int[] arr, int limit, int k) { + int x = 0; + for (int l = 0, r = 1; l < arr.length; r = Math.max(r, ++l)) { + while (r < arr.length && arr[r] - arr[l] <= limit) { + r++; + } + x += r - l - 1; + } + return x < k; + } + + // 为了测试 + public static int[] randomArray(int n, int v) { + int[] ans = new int[n]; + for (int i = 0; i < ans.length; i++) { + ans[i] = (int) (Math.random() * v); + } + return ans; + } + + // 为了测试 + public static void main(String[] args) { + int size = 100; + int value = 100; + int testTime = 10000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int n = (int) (Math.random() * size); + int k = (int) (Math.random() * (n * (n - 1) / 2)) + 1; + int[] arr = randomArray(n, value); + int ans1 = kthABS1(arr, k); + int ans2 = kthABS2(arr, k); + if (ans1 != ans2) { + System.out.print("arr : "); + for (int num : arr) { + System.out.print(num + " "); + } + System.out.println(); + System.out.println("k : " + k); + System.out.println("ans1 : " + ans1); + System.out.println("ans2 : " + ans2); + System.out.println("出错了!"); + break; + } + } + System.out.println("测试结束"); + + long start; + long end; + int n = 500000; + int v = 50000000; + int[] arr = randomArray(n, v); + int k = (int) (Math.random() * (n * (n - 1) / 2)) + 1; + start = System.currentTimeMillis(); + kthABS2(arr, k); + end = System.currentTimeMillis(); + System.out.println("大数据量运行时间(毫秒):" + (end - start)); + } + +} diff --git a/大厂刷题班/class48/Problem_0472_ConcatenatedWords.java b/大厂刷题班/class48/Problem_0472_ConcatenatedWords.java new file mode 100644 index 0000000..533b537 --- /dev/null +++ b/大厂刷题班/class48/Problem_0472_ConcatenatedWords.java @@ -0,0 +1,124 @@ +package class48; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class Problem_0472_ConcatenatedWords { + + public static class TrieNode { + public boolean end; + public TrieNode[] nexts; + + public TrieNode() { + end = false; + nexts = new TrieNode[26]; + } + } + + public static void insert(TrieNode root, char[] s) { + int path = 0; + for (char c : s) { + path = c - 'a'; + if (root.nexts[path] == null) { + root.nexts[path] = new TrieNode(); + } + root = root.nexts[path]; + } + root.end = true; + } + + // 方法1:前缀树优化 + public static List findAllConcatenatedWordsInADict1(String[] words) { + List ans = new ArrayList<>(); + if (words == null || words.length < 3) { + return ans; + } + // 字符串数量 >= 3个 + Arrays.sort(words, (str1, str2) -> str1.length() - str2.length()); + TrieNode root = new TrieNode(); + for (String str : words) { + char[] s = str.toCharArray(); // "" 题目要求 + if (s.length > 0 && split1(s, root, 0)) { + ans.add(str); + } else { + insert(root, s); + } + } + return ans; + } + + // 字符串s[i....]能不能被分解? + // 之前的元件,全在前缀树上,r就是前缀树头节点 + public static boolean split1(char[] s, TrieNode r, int i) { + boolean ans = false; + if (i == s.length) { // 没字符了! + ans = true; + } else { // 还有字符 + TrieNode c = r; + // s[i.....] + // s[i..end]作前缀,看看是不是一个元件!f(end+1)... + for (int end = i; end < s.length; end++) { + int path = s[end] - 'a'; + if (c.nexts[path] == null) { + break; + } + c = c.nexts[path]; + if (c.end && split1(s, r, end + 1)) { + ans = true; + break; + } + } + } + return ans; + } + + // 提前准备好动态规划表 + public static int[] dp = new int[1000]; + + // 方法二:前缀树优化 + 动态规划优化 + public static List findAllConcatenatedWordsInADict2(String[] words) { + List ans = new ArrayList<>(); + if (words == null || words.length < 3) { + return ans; + } + Arrays.sort(words, (str1, str2) -> str1.length() - str2.length()); + TrieNode root = new TrieNode(); + for (String str : words) { + char[] s = str.toCharArray(); + Arrays.fill(dp, 0, s.length + 1, 0); + if (s.length > 0 && split2(s, root, 0, dp)) { + ans.add(str); + } else { + insert(root, s); + } + } + return ans; + } + + public static boolean split2(char[] s, TrieNode r, int i, int[] dp) { + if (dp[i] != 0) { + return dp[i] == 1; + } + boolean ans = false; + if (i == s.length) { + ans = true; + } else { + TrieNode c = r; + for (int end = i; end < s.length; end++) { + int path = s[end] - 'a'; + if (c.nexts[path] == null) { + break; + } + c = c.nexts[path]; + if (c.end && split2(s, r, end + 1, dp)) { + ans = true; + break; + } + } + } + dp[i] = ans ? 1 : -1; + return ans; + } + +} diff --git a/大厂刷题班/class48/Problem_0483_SmallestGoodBase.java b/大厂刷题班/class48/Problem_0483_SmallestGoodBase.java new file mode 100644 index 0000000..b11f38d --- /dev/null +++ b/大厂刷题班/class48/Problem_0483_SmallestGoodBase.java @@ -0,0 +1,33 @@ +package class48; + +public class Problem_0483_SmallestGoodBase { + + // ""4651" -> 4651 + public static String smallestGoodBase(String n) { + long num = Long.valueOf(n); + // n这个数,需要从m位开始试,固定位数,一定要有m位! + for (int m = (int) (Math.log(num + 1) / Math.log(2)); m > 2; m--) { + // num开m次方 + long l = (long) (Math.pow(num, 1.0 / m)); + long r = (long) (Math.pow(num, 1.0 / (m - 1))) + 1L; + while (l <= r) { + long k = l + ((r - l) >> 1); + long sum = 0L; + long base = 1L; + for (int i = 0; i < m && sum <= num; i++) { + sum += base; + base *= k; + } + if (sum < num) { + l = k + 1; + } else if (sum > num) { + r = k - 1; + } else { + return String.valueOf(k); + } + } + } + return String.valueOf(num - 1); + } + +} diff --git a/大厂刷题班/class48/Problem_0499_TheMazeIII.java b/大厂刷题班/class48/Problem_0499_TheMazeIII.java new file mode 100644 index 0000000..38bd2ec --- /dev/null +++ b/大厂刷题班/class48/Problem_0499_TheMazeIII.java @@ -0,0 +1,87 @@ +package class48; + +public class Problem_0499_TheMazeIII { + + // 节点:来到了哪?(r,c)这个位置 + // 从哪个方向来的!d -> 0 1 2 3 4 + // 之前做了什么决定让你来到这个位置。 + public static class Node { + public int r; + public int c; + public int d; + public String p; + + public Node(int row, int col, int dir, String path) { + r = row; + c = col; + d = dir; + p = path; + } + + } + + public static String findShortestWay(int[][] maze, int[] ball, int[] hole) { + int n = maze.length; + int m = maze[0].length; + Node[] q1 = new Node[n * m], q2 = new Node[n * m]; + int s1 = 0, s2 = 0; + boolean[][][] visited = new boolean[maze.length][maze[0].length][4]; + s1 = spread(maze, n, m, new Node(ball[0], ball[1], 4, ""), visited, q1, s1); + while (s1 != 0) { + for (int i = 0; i < s1; i++) { + Node cur = q1[i]; + if (hole[0] == cur.r && hole[1] == cur.c) { + return cur.p; + } + s2 = spread(maze, n, m, cur, visited, q2, s2); + } + Node[] tmp = q1; + q1 = q2; + q2 = tmp; + s1 = s2; + s2 = 0; + } + return "impossible"; + } + + public static int[][] to = { { 1, 0 }, { 0, -1 }, { 0, 1 }, { -1, 0 }, { 0, 0 } }; + + public static String[] re = { "d", "l", "r", "u" }; + + // maze迷宫,走的格子 + // n 行数 + // m 列数 + // 当前来到的节点,cur -> (r,c) 方向 路径(决定) + // v [行][列][方向] 一个格子,其实在宽度有限遍历时,是4个点! + // q 下一层的队列 + // s 下一层队列填到了哪,size + // 当前点cur,该分裂分裂,该继续走继续走,所产生的一下层的点,进入q,s++ + // 返回值:q增长到了哪?返回size -> s + public static int spread(int[][] maze, int n, int m, + Node cur, boolean[][][] v, Node[] q, int s) { + int d = cur.d; + int r = cur.r + to[d][0]; + int c = cur.c + to[d][1]; + // 分裂去! + if (d == 4 || r < 0 || r == n || c < 0 || c == m || maze[r][c] != 0) { + for (int i = 0; i < 4; i++) { + if (i != d) { + r = cur.r + to[i][0]; + c = cur.c + to[i][1]; + if (r >= 0 && r < n && c >= 0 && c < m && maze[r][c] == 0 && !v[r][c][i]) { + v[r][c][i] = true; + Node next = new Node(r, c, i, cur.p + re[i]); + q[s++] = next; + } + } + } + } else { // 不分裂!继续走! + if (!v[r][c][d]) { + v[r][c][d] = true; + q[s++] = new Node(r, c, d, cur.p); + } + } + return s; + } + +} diff --git a/大厂刷题班/class49/Problem_0377_CombinationSumIV.java b/大厂刷题班/class49/Problem_0377_CombinationSumIV.java new file mode 100644 index 0000000..13e7ffa --- /dev/null +++ b/大厂刷题班/class49/Problem_0377_CombinationSumIV.java @@ -0,0 +1,64 @@ +package class49; + +import java.util.Arrays; + +public class Problem_0377_CombinationSumIV { + + // 当前剩下的值是rest, + // nums中所有的值,都可能作为分解rest的,第一块!全试一试 + // nums, 无重复,都是正 + // rest, + public static int ways(int rest, int[] nums) { + if (rest < 0) { + return 0; + } + if (rest == 0) { + return 1; + } + int ways = 0; + for (int num : nums) { + ways += ways(rest - num, nums); + } + return ways; + } + + public static int[] dp = new int[1001]; + + public static int combinationSum41(int[] nums, int target) { + Arrays.fill(dp, 0, target + 1, -1); + return process1(nums, target); + } + + public static int process1(int[] nums, int rest) { + if (rest < 0) { + return 0; + } + if (dp[rest] != -1) { + return dp[rest]; + } + int ans = 0; + if (rest == 0) { + ans = 1; + } else { + for (int num : nums) { + ans += process1(nums, rest - num); + } + } + dp[rest] = ans; + return ans; + } + + // 剪枝 + 严格位置依赖的动态规划 + public static int combinationSum42(int[] nums, int target) { + Arrays.sort(nums); + int[] dp = new int[target + 1]; + dp[0] = 1; + for (int rest = 1; rest <= target; rest++) { + for (int i = 0; i < nums.length && nums[i] <= rest; i++) { + dp[rest] += dp[rest - nums[i]]; + } + } + return dp[target]; + } + +} diff --git a/大厂刷题班/class49/Problem_0440_KthSmallestInLexicographicalOrder.java b/大厂刷题班/class49/Problem_0440_KthSmallestInLexicographicalOrder.java new file mode 100644 index 0000000..9514700 --- /dev/null +++ b/大厂刷题班/class49/Problem_0440_KthSmallestInLexicographicalOrder.java @@ -0,0 +1,88 @@ +package class49; + +// 这道题在leetcode上,所有题解都只能做到O( (logN) 平方)的解 +// 我们课上讲的是O(logN)的解 +// 打败所有题解 +public class Problem_0440_KthSmallestInLexicographicalOrder { + + public static int[] offset = { 0, 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000 }; + + public static int[] number = { 0, 1, 11, 111, 1111, 11111, 111111, 1111111, 11111111, 111111111, 1111111111 }; + + public static int findKthNumber(int n, int k) { + // 数字num,有几位,len位 + // 65237, 5位,len = 5 + int len = len(n); + // 65237, 开头数字,6,first + int first = n / offset[len]; + // 65237,左边有几个? + int left = (first - 1) * number[len]; + int pick = 0; + int already = 0; + if (k <= left) { + // k / a 向上取整-> (k + a - 1) / a + pick = (k + number[len] - 1) / number[len]; + already = (pick - 1) * number[len]; + return kth((pick + 1) * offset[len] - 1, len, k - already); + } + int mid = number[len - 1] + (n % offset[len]) + 1; + if (k - left <= mid) { + return kth(n, len, k - left); + } + k -= left + mid; + len--; + pick = (k + number[len] - 1) / number[len] + first; + already = (pick - first - 1) * number[len]; + return kth((pick + 1) * offset[len] - 1, len, k - already); + } + + public static int len(int n) { + int len = 0; + while (n != 0) { + n /= 10; + len++; + } + return len; + } + + public static int kth(int max, int len, int kth) { + // 中间范围还管不管的着! + // 有任何一步,中间位置没命中,左或者右命中了,那以后就都管不着了! + // 但是开始时,肯定是管的着的! + boolean closeToMax = true; + int ans = max / offset[len]; + while (--kth > 0) { + max %= offset[len--]; + int pick = 0; + if (!closeToMax) { + pick = (kth - 1) / number[len]; + ans = ans * 10 + pick; + kth -= pick * number[len]; + } else { + int first = max / offset[len]; + int left = first * number[len]; + if (kth <= left) { + closeToMax = false; + pick = (kth - 1) / number[len]; + ans = ans * 10 + pick; + kth -= pick * number[len]; + continue; + } + kth -= left; + int mid = number[len - 1] + (max % offset[len]) + 1; + if (kth <= mid) { + ans = ans * 10 + first; + continue; + } + closeToMax = false; + kth -= mid; + len--; + pick = (kth + number[len] - 1) / number[len] + first; + ans = ans * 10 + pick; + kth -= (pick - first - 1) * number[len]; + } + } + return ans; + } + +} diff --git a/大厂刷题班/class49/Problem_0446_ArithmeticSlicesIISubsequence.java b/大厂刷题班/class49/Problem_0446_ArithmeticSlicesIISubsequence.java new file mode 100644 index 0000000..c34f2f3 --- /dev/null +++ b/大厂刷题班/class49/Problem_0446_ArithmeticSlicesIISubsequence.java @@ -0,0 +1,30 @@ +package class49; + +import java.util.ArrayList; +import java.util.HashMap; + +public class Problem_0446_ArithmeticSlicesIISubsequence { + + // 时间复杂度是O(N^2),最优解的时间复杂度 + public static int numberOfArithmeticSlices(int[] arr) { + int N = arr.length; + int ans = 0; + ArrayList> maps = new ArrayList<>(); + for (int i = 0; i < N; i++) { + maps.add(new HashMap<>()); + // ....j...i(结尾) + for (int j = i - 1; j >= 0; j--) { + long diff = (long) arr[i] - (long) arr[j]; + if (diff <= Integer.MIN_VALUE || diff > Integer.MAX_VALUE) { + continue; + } + int dif = (int) diff; + int count = maps.get(j).getOrDefault(dif, 0); + ans += count; + maps.get(i).put(dif, maps.get(i).getOrDefault(dif, 0) + count + 1); + } + } + return ans; + } + +} diff --git a/大厂刷题班/class49/Problem_0489_RobotRoomCleaner.java b/大厂刷题班/class49/Problem_0489_RobotRoomCleaner.java new file mode 100644 index 0000000..a52a3d2 --- /dev/null +++ b/大厂刷题班/class49/Problem_0489_RobotRoomCleaner.java @@ -0,0 +1,57 @@ +package class49; + +import java.util.HashSet; + +public class Problem_0489_RobotRoomCleaner { + + // 不要提交这个接口的内容 + interface Robot { + public boolean move(); + + public void turnLeft(); + + public void turnRight(); + + public void clean(); + } + + // 提交下面的内容 + public static void cleanRoom(Robot robot) { + clean(robot, 0, 0, 0, new HashSet<>()); + } + + private static final int[][] ds = { { -1, 0 }, { 0, 1 }, { 1, 0 }, { 0, -1 } }; + + // 机器人robot, + // 当前来到的位置(x,y),且之前没来过 + // 机器人脸冲什么方向d,0 1 2 3 + // visited里记录了机器人走过哪些位置 + // 函数的功能:不要重复走visited里面的位置,把剩下的位置,都打扫干净! + // 而且要回去! + public static void clean(Robot robot, int x, int y, int d, HashSet visited) { + robot.clean(); + visited.add(x + "_" + y); + for (int i = 0; i < 4; i++) { + // d = 0 : 0 1 2 3 + // d = 1 : 1 2 3 0 + // d = 2 : 2 3 0 1 + // d = 3 : 3 0 1 2 + // 下一步的方向! + int nd = (i + d) % 4; + // 当下一步的方向定了!下一步的位置在哪?(nx, ny) + int nx = ds[nd][0] + x; + int ny = ds[nd][1] + y; + if (!visited.contains(nx + "_" + ny) && robot.move()) { + clean(robot, nx, ny, nd, visited); + } + robot.turnRight(); + } + // 负责回去:之前的位置,怎么到你的!你要回去,而且方向和到你之前,要一致! + robot.turnRight(); + robot.turnRight(); + robot.move(); + robot.turnRight(); + robot.turnRight(); + } + +} diff --git a/大厂刷题班/class49/Problem_0527_WordAbbreviation.java b/大厂刷题班/class49/Problem_0527_WordAbbreviation.java new file mode 100644 index 0000000..97440d6 --- /dev/null +++ b/大厂刷题班/class49/Problem_0527_WordAbbreviation.java @@ -0,0 +1,48 @@ +package class49; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +public class Problem_0527_WordAbbreviation { + + public static List wordsAbbreviation(List words) { + int len = words.size(); + List res = new ArrayList<>(); + HashMap> map = new HashMap<>(); + for (int i = 0; i < len; i++) { + res.add(makeAbbr(words.get(i), 1)); + List list = map.getOrDefault(res.get(i), new ArrayList<>()); + list.add(i); + map.put(res.get(i), list); + } + int[] prefix = new int[len]; + for (int i = 0; i < len; i++) { + if (map.get(res.get(i)).size() > 1) { + List indexes = map.get(res.get(i)); + map.remove(res.get(i)); + for (int j : indexes) { + prefix[j]++; + res.set(j, makeAbbr(words.get(j), prefix[j])); + List list = map.getOrDefault(res.get(j), new ArrayList<>()); + list.add(j); + map.put(res.get(j), list); + } + i--; + } + } + return res; + } + + public static String makeAbbr(String s, int k) { + if (k >= s.length() - 2) { + return s; + } + StringBuilder builder = new StringBuilder(); + builder.append(s.substring(0, k)); + builder.append(s.length() - 1 - k); + builder.append(s.charAt(s.length() - 1)); + return builder.toString(); + } + +} diff --git a/大厂刷题班/class49/Problem_0548_SplitArrayEithEqualSum.java b/大厂刷题班/class49/Problem_0548_SplitArrayEithEqualSum.java new file mode 100644 index 0000000..3009e59 --- /dev/null +++ b/大厂刷题班/class49/Problem_0548_SplitArrayEithEqualSum.java @@ -0,0 +1,45 @@ +package class49; + +public class Problem_0548_SplitArrayEithEqualSum { + + public static boolean splitArray(int[] nums) { + if (nums.length < 7) { + return false; + } + int[] sumLeftToRight = new int[nums.length]; + int[] sumRightToLeft = new int[nums.length]; + int s = 0; + for (int i = 0; i < nums.length; i++) { + sumLeftToRight[i] = s; + s += nums[i]; + } + s = 0; + for (int i = nums.length - 1; i >= 0; i--) { + sumRightToLeft[i] = s; + s += nums[i]; + } + for (int i = 1; i < nums.length - 5; i++) { + for (int j = nums.length - 2; j > i + 3; j--) { + if (sumLeftToRight[i] == sumRightToLeft[j] && find(sumLeftToRight, sumRightToLeft, i, j)) { + return true; + } + } + } + return false; + } + + public static boolean find(int[] sumLeftToRight, int[] sumRightToLeft, int l, int r) { + int s = sumLeftToRight[l]; + int prefix = sumLeftToRight[l + 1]; + int suffix = sumRightToLeft[r - 1]; + for (int i = l + 2; i < r - 1; i++) { + int s1 = sumLeftToRight[i] - prefix; + int s2 = sumRightToLeft[i] - suffix; + if (s1 == s2 && s1 == s) { + return true; + } + } + return false; + } + +} diff --git a/大厂刷题班/class49/Problem_0564_FindTheClosestPalindrome.java b/大厂刷题班/class49/Problem_0564_FindTheClosestPalindrome.java new file mode 100644 index 0000000..b8ff906 --- /dev/null +++ b/大厂刷题班/class49/Problem_0564_FindTheClosestPalindrome.java @@ -0,0 +1,72 @@ +package class49; + +public class Problem_0564_FindTheClosestPalindrome { + + public static String nearestPalindromic(String n) { + Long num = Long.valueOf(n); + Long raw = getRawPalindrome(n); + Long big = raw > num ? raw : getBigPalindrome(raw); + Long small = raw < num ? raw : getSmallPalindrome(raw); + return String.valueOf(big - num >= num - small ? small : big); + } + + public static Long getRawPalindrome(String n) { + char[] chs = n.toCharArray(); + int len = chs.length; + for (int i = 0; i < len / 2; i++) { + chs[len - 1 - i] = chs[i]; + } + return Long.valueOf(String.valueOf(chs)); + } + + public static Long getBigPalindrome(Long raw) { + char[] chs = String.valueOf(raw).toCharArray(); + char[] res = new char[chs.length + 1]; + res[0] = '0'; + for (int i = 0; i < chs.length; i++) { + res[i + 1] = chs[i]; + } + int size = chs.length; + for (int j = (size - 1) / 2 + 1; j >= 0; j--) { + if (++res[j] > '9') { + res[j] = '0'; + } else { + break; + } + } + int offset = res[0] == '1' ? 1 : 0; + size = res.length; + for (int i = size - 1; i >= (size + offset) / 2; i--) { + res[i] = res[size - i - offset]; + } + return Long.valueOf(String.valueOf(res)); + } + + public static Long getSmallPalindrome(Long raw) { + char[] chs = String.valueOf(raw).toCharArray(); + char[] res = new char[chs.length]; + int size = res.length; + for (int i = 0; i < size; i++) { + res[i] = chs[i]; + } + for (int j = (size - 1) / 2; j >= 0; j--) { + if (--res[j] < '0') { + res[j] = '9'; + } else { + break; + } + } + if (res[0] == '0') { + res = new char[size - 1]; + for (int i = 0; i < res.length; i++) { + res[i] = '9'; + } + return size == 1 ? 0 : Long.parseLong(String.valueOf(res)); + } + for (int k = 0; k < size / 2; k++) { + res[size - 1 - k] = res[k]; + } + return Long.valueOf(String.valueOf(res)); + } + +} diff --git a/大厂刷题班/class50/Problem_0568_MaximumVacationDays.java b/大厂刷题班/class50/Problem_0568_MaximumVacationDays.java new file mode 100644 index 0000000..0821911 --- /dev/null +++ b/大厂刷题班/class50/Problem_0568_MaximumVacationDays.java @@ -0,0 +1,50 @@ +package class50; + +public class Problem_0568_MaximumVacationDays { + + public static int maxVacationDays(int[][] fly, int[][] day) { + int n = fly.length; + int k = day[0].length; + // pas[i] = {a, b, c} + // 从a、b、c能飞到i + int[][] pass = new int[n][]; + for (int i = 0; i < n; i++) { + int s = 0; + for (int j = 0; j < n; j++) { + if (fly[j][i] != 0) { + s++; + } + } + pass[i] = new int[s]; + for (int j = n - 1; j >= 0; j--) { + if (fly[j][i] != 0) { + pass[i][--s] = j; + } + } + } + // dp[i][j] -> 第i周必须在j这座城,0~i-1周(随意),最大休假天数 + int[][] dp = new int[k][n]; + // 飞的时机,是周一早上飞,认为对时间没有影响,直接到某个城,然后过一周 + dp[0][0] = day[0][0]; + for (int j = 1; j < n; j++) { + dp[0][j] = fly[0][j] != 0 ? day[j][0] : -1; + } + for (int i = 1; i < k; i++) { // 第i周 + for (int j = 0; j < n; j++) { // 在j号城过! + // 第i周,要怎么到j号城 + // 下面max的初始值,我第i-1周,就在j号城,选择不动地方,进入第i周 + int max = dp[i - 1][j]; + for (int p : pass[j]) { // 枚举什么?能到j号城的城市p + max = Math.max(max, dp[i - 1][p]); + } + dp[i][j] = max != -1 ? max + day[j][i] : -1; + } + } + int ans = 0; + for (int i = 0; i < n; i++) { + ans = Math.max(ans, dp[k - 1][i]); + } + return ans; + } + +} diff --git a/大厂刷题班/class50/Problem_0587_ErectTheFence.java b/大厂刷题班/class50/Problem_0587_ErectTheFence.java new file mode 100644 index 0000000..8b7aabb --- /dev/null +++ b/大厂刷题班/class50/Problem_0587_ErectTheFence.java @@ -0,0 +1,55 @@ +package class50; + +import java.util.Arrays; + +public class Problem_0587_ErectTheFence { + + public static int[][] outerTrees(int[][] points) { + int n = points.length; + int s = 0; + int[][] stack = new int[n << 1][]; + // x小的排前面,x一样的,y小的排前面 + Arrays.sort(points, (a, b) -> a[0] != b[0] ? a[0] - b[0] : a[1] - b[1]); + for (int i = 0; i < n; i++) { + while (s > 1 && cross(stack[s - 2], stack[s - 1], points[i]) > 0) { + s--; + } + stack[s++] = points[i]; + } + for (int i = n - 2; i >= 0; i--) { + while (s > 1 && cross(stack[s - 2], stack[s - 1], points[i]) > 0) { + s--; + } + stack[s++] = points[i]; + } + // 去重返回 + Arrays.sort(stack, 0, s, (a, b) -> b[0] == a[0] ? b[1] - a[1] : b[0] - a[0]); + n = 1; + for (int i = 1; i < s; i++) { + // 如果i点,x和y,与i-1点,x和y都一样 + // i点与i-1点,在同一个位置,此时,i点不保留 + if (stack[i][0] != stack[i - 1][0] || stack[i][1] != stack[i - 1][1]) { + stack[n++] = stack[i]; + } + } + return Arrays.copyOf(stack, n); + } + + // 叉乘的实现 + // 假设有a、b、c三个点,并且给出每个点的(x,y)位置 + // 从a到c的向量,在从a到b的向量的哪一侧? + // 如果a到c的向量,在从a到b的向量右侧,返回正数 + // 如果a到c的向量,在从a到b的向量左侧,返回负数 + // 如果a到c的向量,和从a到b的向量重合,返回0 + public static int cross(int[] a, int[] b, int[] c) { + return (b[1] - a[1]) * (c[0] - b[0]) - (b[0] - a[0]) * (c[1] - b[1]); + } + + public static void main(String[] args) { + int[] a = { 4, 4 }; + int[] b = { 1, 1 }; + int[] c = { 1, 5 }; + System.out.println(cross(a, b, c)); + } + +} diff --git a/大厂刷题班/class50/Problem_0588_DesignInMemoryFileSystem.java b/大厂刷题班/class50/Problem_0588_DesignInMemoryFileSystem.java new file mode 100644 index 0000000..e44ea18 --- /dev/null +++ b/大厂刷题班/class50/Problem_0588_DesignInMemoryFileSystem.java @@ -0,0 +1,107 @@ +package class50; + +import java.util.ArrayList; +import java.util.List; +import java.util.TreeMap; + +public class Problem_0588_DesignInMemoryFileSystem { + + class FileSystem { + + public class Node { + // 文件名、目录名 + public String name; + // content == null 意味着这个节点是目录 + // content != null 意味着这个节点是文件 + public StringBuilder content; + public TreeMap nexts; + + // 构造目录 + public Node(String n) { + name = n; + content = null; + nexts = new TreeMap<>(); + } + + // 构造文件,c是文件内容 + public Node(String n, String c) { + name = n; + content = new StringBuilder(c); + nexts = new TreeMap<>(); + } + + } + + public Node head; + + public FileSystem() { + head = new Node(""); + } + + public List ls(String path) { + List ans = new ArrayList<>(); + Node cur = head; + String[] parts = path.split("/"); + int n = parts.length; + for (int i = 1; i < n; i++) { + if (!cur.nexts.containsKey(parts[i])) { + return ans; + } + cur = cur.nexts.get(parts[i]); + } + // cur结束了!来到path最后的节点,该返回了 + // ls a/b/c cur 来到c目录 + // 如果c是目录,那么就要返回c下面所有的东西! + // 如果c是文件,那么就值返回c + if (cur.content == null) { + ans.addAll(cur.nexts.keySet()); + } else { + ans.add(cur.name); + } + return ans; + } + + public void mkdir(String path) { + Node cur = head; + String[] parts = path.split("/"); + int n = parts.length; + for (int i = 1; i < n; i++) { + if (!cur.nexts.containsKey(parts[i])) { + cur.nexts.put(parts[i], new Node(parts[i])); + } + cur = cur.nexts.get(parts[i]); + } + } + + public void addContentToFile(String path, String content) { + Node cur = head; + String[] parts = path.split("/"); + int n = parts.length; + for (int i = 1; i < n - 1; i++) { + if (!cur.nexts.containsKey(parts[i])) { + cur.nexts.put(parts[i], new Node(parts[i])); + } + cur = cur.nexts.get(parts[i]); + } + // 来到的是倒数第二的节点了!注意for! + if (!cur.nexts.containsKey(parts[n - 1])) { + cur.nexts.put(parts[n - 1], new Node(parts[n - 1], "")); + } + cur.nexts.get(parts[n - 1]).content.append(content); + } + + public String readContentFromFile(String path) { + Node cur = head; + String[] parts = path.split("/"); + int n = parts.length; + for (int i = 1; i < n; i++) { + if (!cur.nexts.containsKey(parts[i])) { + cur.nexts.put(parts[i], new Node(parts[i])); + } + cur = cur.nexts.get(parts[i]); + } + return cur.content.toString(); + } + } + +} diff --git a/大厂刷题班/class50/Problem_0600_NonnegativeIntegersWithoutConsecutiveOnes.java b/大厂刷题班/class50/Problem_0600_NonnegativeIntegersWithoutConsecutiveOnes.java new file mode 100644 index 0000000..48f461d --- /dev/null +++ b/大厂刷题班/class50/Problem_0600_NonnegativeIntegersWithoutConsecutiveOnes.java @@ -0,0 +1,80 @@ +package class50; + +public class Problem_0600_NonnegativeIntegersWithoutConsecutiveOnes { + +// // f(0, false, 5,n) +// // 6 5 ... 0 -1 n +// // 0 ? 停 +// +// // 5 4 3 2 1 0 -1 +// // n : 1 0 1 1 1 0 +// // 0 1 i +// // pre : 第i+1位做的决定,0还是1 +// // 在 第i+1位做的决定 是pre的情况下,从index位开始,往后都做决定! +// // 但是,不能有相邻的1,请问有多少决定!返回! +// // alreadyLess : 之前的决定,是不是已经导致你到index的时候,已经比n小了! +// // pre -> 0 1 +// // alreadyLess -> true false +// // index -> n的位数,(logN) +// // 2 * 2 * logN +// // dp[2][] +// // int alreadyLess 0 1 +// public int f(int pre, boolean alreadyLess, int index, int n) { +// if (index == -1) { +// return 1; +// } +// if (pre == 1) { +// // 只能做0的决定,然后去往i-1位置 +// boolean curLessOrMore = alreadyLess | ((n & 1 << index) != 0); +// return f(0, curLessOrMore, index - 1, n); +// } else { // pre == 0的决定 +// // 可能性1,继续做0的决定 +// boolean curLessOrMore = alreadyLess | ((n & 1 << index) != 0); +// int p1 = f(0, curLessOrMore, index - 1, n); +// // 可能性2,做1的决定 +// // 1)pre == 0的决定, 2) +// int p2 = 0; +// if (alreadyLess || (n & 1 << index) != 0) { +// p2 = f(1, alreadyLess, index - 1, n); +// } +// return p1 + p2; +// } +// } + + public static int findIntegers(int n) { + int i = 31; + for (; i >= 0; i--) { + if ((n & (1 << i)) != 0) { + break; + } + } + // for循环出来之后,i表示,n最高位的1,在哪? + // 从这个位置,往右边低位上走! + int[][][] dp = new int[2][2][i + 1]; + return f(0, 0, i, n, dp); + } + + + public static int f(int pre, int alreadyLess, int index, int num, int[][][] dp) { + if (index == -1) { + return 1; + } + if (dp[pre][alreadyLess][index] != 0) { + return dp[pre][alreadyLess][index]; + } + int ans = 0; + if (pre == 1) { + ans = f(0, Math.max(alreadyLess, (num & (1 << index)) != 0 ? 1 : 0), index - 1, num, dp); + } else { + if ((num & (1 << index)) == 0 && alreadyLess == 0) { + ans = f(0, alreadyLess, index - 1, num, dp); + } else { + ans = f(1, alreadyLess, index - 1, num, dp) + + f(0, Math.max(alreadyLess, (num & (1 << index)) != 0 ? 1 : 0), index - 1, num, dp); + } + } + dp[pre][alreadyLess][index] = ans; + return ans; + } + +} diff --git a/大厂刷题班/class51/LCP_0003_Robot.java b/大厂刷题班/class51/LCP_0003_Robot.java new file mode 100644 index 0000000..14451b3 --- /dev/null +++ b/大厂刷题班/class51/LCP_0003_Robot.java @@ -0,0 +1,144 @@ +package class51; + +import java.util.Arrays; +import java.util.HashSet; + +// leetcode题目 : https://leetcode-cn.com/problems/programmable-robot/ +public class LCP_0003_Robot { + + public static boolean robot1(String command, int[][] obstacles, int x, int y) { + int X = 0; + int Y = 0; + HashSet set = new HashSet<>(); + set.add(0); + for (char c : command.toCharArray()) { + X += c == 'R' ? 1 : 0; + Y += c == 'U' ? 1 : 0; + set.add((X << 10) | Y); + } + // 不考虑任何额外的点,机器人能不能到达,(x,y) + if (!meet1(x, y, X, Y, set)) { + return false; + } + for (int[] ob : obstacles) { // ob[0] ob[1] + if (ob[0] <= x && ob[1] <= y && meet1(ob[0], ob[1], X, Y, set)) { + return false; + } + } + return true; + } + + // 一轮以内,X,往右一共有几个单位 + // Y, 往上一共有几个单位 + // set, 一轮以内的所有可能性 + // (x,y)要去的点 + // 机器人从(0,0)位置,能不能走到(x,y) + public static boolean meet1(int x, int y, int X, int Y, HashSet set) { + if (X == 0) { // Y != 0 往上肯定走了! + return x == 0; + } + if (Y == 0) { + return y == 0; + } + // 至少几轮? + int atLeast = Math.min(x / X, y / Y); + // 经历过最少轮数后,x剩多少? + int rx = x - atLeast * X; + // 经历过最少轮数后,y剩多少? + int ry = y - atLeast * Y; + return set.contains((rx << 10) | ry); + } + + // 此处为一轮以内,x和y最大能移动的步数,对应的2的几次方 + // 比如本题,x和y最大能移动1000步,就对应2的10次方 + // 如果换一个数据量,x和y最大能移动5000步,就对应2的13次方 + // 只需要根据数据量修改这一个变量,剩下的代码不需要调整 + public static final int bit = 10; + // 如果,x和y最大能移动的步数,对应2的bit次方 + // 那么一个坐标(x,y),所有的可能性就是:(2 ^ bit) ^ 2 = 2 ^ (bit * 2) + // 也就是,(1 << (bit << 1))个状态,记为bits + public static int bits = (1 << (bit << 1)); + // 为了表示下bits个状态,需要几个整数? + // 32位只需要一个整数,所以bits个状态,需要bits / 32 个整数 + // 即整型长度需要 : bits >> 5 + public static int[] set = new int[bits >> 5]; + + public static boolean robot2(String command, int[][] obstacles, int x, int y) { + Arrays.fill(set, 0); + set[0] = 1; + int X = 0; + int Y = 0; + for (char c : command.toCharArray()) { + X += c == 'R' ? 1 : 0; + Y += c == 'U' ? 1 : 0; + add((X << 10) | Y); + } + if (!meet2(x, y, X, Y)) { + return false; + } + for (int[] ob : obstacles) { + if (ob[0] <= x && ob[1] <= y && meet2(ob[0], ob[1], X, Y)) { + return false; + } + } + return true; + } + + public static boolean meet2(int x, int y, int X, int Y) { + if (X == 0) { + return x == 0; + } + if (Y == 0) { + return y == 0; + } + int atLeast = Math.min(x / X, y / Y); + int rx = x - atLeast * X; + int ry = y - atLeast * Y; + return contains((rx << 10) | ry); + } + + public static void add(int status) { + set[status >> 5] |= 1 << (status & 31); + } + + public static boolean contains(int status) { + return (status < bits) && (set[status >> 5] & (1 << (status & 31))) != 0; + } + + // int num -> 32位的状态 + // 请打印这32位状态 + public static void printBinary(int num) { + for (int i = 31; i >= 0; i--) { + System.out.print((num & (1 << i)) != 0 ? "1" : "0"); + } + System.out.println(); + } + + public static void main(String[] args) { + int x = 7; + printBinary(x); + + int y = 4; + + printBinary(y); + + // x_y 组合! + int c = (x << 10) | y; + printBinary(c); + + System.out.println(c); + + // 0 ~ 1048575 任何一个数,bit来表示的! +// int[] set = new int[32768]; +// set[0] = int 32 位 0~31这些数出现过没出现过 +// set[1] = int 32 位 32~63这些数出现过没出现过 + + // 0 ~ 1048575 +// int[] set = new int[32768]; +// int num = 738473; // 32 bit int +//// set[ 734873 / 32 ] // 734873 % 32 +// boolean exist = (set[num / 32] & (1 << (num % 32))) != 0; + + } + +} diff --git a/大厂刷题班/class51/Problem_0630_CourseScheduleIII.java b/大厂刷题班/class51/Problem_0630_CourseScheduleIII.java new file mode 100644 index 0000000..fb7e70d --- /dev/null +++ b/大厂刷题班/class51/Problem_0630_CourseScheduleIII.java @@ -0,0 +1,33 @@ +package class51; + +import java.util.Arrays; +import java.util.PriorityQueue; + +public class Problem_0630_CourseScheduleIII { + + public static int scheduleCourse(int[][] courses) { + // courses[i] = {花费,截止} + Arrays.sort(courses, (a, b) -> a[1] - b[1]); + // 花费时间的大根堆 + PriorityQueue heap = new PriorityQueue<>((a, b) -> b - a); + // 时间点 + int time = 0; + for (int[] c : courses) { + // + if (time + c[0] <= c[1]) { // 当前时间 + 花费 <= 截止时间的 + heap.add(c[0]); + time += c[0]; + } else { // 当前时间 + 花费 > 截止时间的, 只有淘汰掉某课,当前的课才能进来! + if (!heap.isEmpty() && heap.peek() > c[0]) { +// time -= heap.poll(); +// heap.add(c[0]); +// time += c[0]; + heap.add(c[0]); + time += c[0] - heap.poll(); + } + } + } + return heap.size(); + } + +} diff --git a/大厂刷题班/class51/Problem_0642_DesignSearchAutocompleteSystem.java b/大厂刷题班/class51/Problem_0642_DesignSearchAutocompleteSystem.java new file mode 100644 index 0000000..6eec56f --- /dev/null +++ b/大厂刷题班/class51/Problem_0642_DesignSearchAutocompleteSystem.java @@ -0,0 +1,134 @@ +package class51; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.TreeSet; + +public class Problem_0642_DesignSearchAutocompleteSystem { + + class AutocompleteSystem { + + public class TrieNode { + public TrieNode father; + public String path; + public TrieNode[] nexts; + + public TrieNode(TrieNode f, String p) { + father = f; + path = p; + nexts = new TrieNode[27]; + } + } + + public class WordCount implements Comparable { + public String word; + public int count; + + public WordCount(String w, int c) { + word = w; + count = c; + } + + public int compareTo(WordCount o) { + return count != o.count ? (o.count - count) : word.compareTo(o.word); + } + } + + // 题目的要求,只输出排名前3的列表 + public final int top = 3; + public final TrieNode root = new TrieNode(null, ""); + // 某个前缀树节点,上面的有序表,不在这个节点内部 + // 外挂 + public HashMap> nodeRankMap = new HashMap<>(); + + // 字符串 "abc" 7次 -> ("abc", 7) + public HashMap wordCountMap = new HashMap<>(); + + + public String path; + public TrieNode cur; + + // ' ' -> 0 + // 'a' -> 1 + // 'b' -> 2 + // ... + // 'z' -> 26 + // '`' a b .. z + private int f(char c) { + return c == ' ' ? 0 : (c - '`'); + } + + public AutocompleteSystem(String[] sentences, int[] times) { + path = ""; + cur = root; + for (int i = 0; i < sentences.length; i++) { + String word = sentences[i]; + int count = times[i]; + WordCount wc = new WordCount(word, count - 1); + wordCountMap.put(word, wc); + for (char c : word.toCharArray()) { + input(c); + } + input('#'); + } + } + + // 之前已经有一些历史了! + // 当前键入 c 字符 + // 请顺着之前的历史,根据c字符是什么,继续 + // path : 之前键入的字符串整体 + // cur : 当前滑到了前缀树的哪个节点 + + public List input(char c) { + List ans = new ArrayList<>(); + if (c != '#') { + path += c; + int i = f(c); + if (cur.nexts[i] == null) { + cur.nexts[i] = new TrieNode(cur, path); + } + cur = cur.nexts[i]; + if (!nodeRankMap.containsKey(cur)) { + nodeRankMap.put(cur, new TreeSet<>()); + } + int k = 0; + // for循环本身就是根据排序后的顺序来遍历! + for (WordCount wc : nodeRankMap.get(cur)) { + if (k == top) { + break; + } + ans.add(wc.word); + k++; + } + } + // c = # path = "abcde" + // # + // # + // # + // a b .. # + if (c == '#' && !path.equals("")) { + // 真的有一个,有效字符串需要加入!path + if (!wordCountMap.containsKey(path)) { + wordCountMap.put(path, new WordCount(path, 0)); + } + // 有序表内部的小对象,该小对象参与排序的指标数据改变 + // 但是有序表并不会自动刷新 + // 所以,删掉老的,加新的! + WordCount oldOne = wordCountMap.get(path); + WordCount newOne = new WordCount(path, oldOne.count + 1); + while (cur != root) { + nodeRankMap.get(cur).remove(oldOne); + nodeRankMap.get(cur).add(newOne); + cur = cur.father; + } + wordCountMap.put(path, newOne); + path = ""; + // cur 回到头了 + } + return ans; + } + + } + +} diff --git a/大厂刷题班/class51/Problem_0875_KokoEatingBananas.java b/大厂刷题班/class51/Problem_0875_KokoEatingBananas.java new file mode 100644 index 0000000..9a8f4a4 --- /dev/null +++ b/大厂刷题班/class51/Problem_0875_KokoEatingBananas.java @@ -0,0 +1,34 @@ +package class51; + +public class Problem_0875_KokoEatingBananas { + + public static int minEatingSpeed(int[] piles, int h) { + int L = 1; + int R = 0; + for (int pile : piles) { + R = Math.max(R, pile); + } + int ans = 0; + int M = 0; + while (L <= R) { + M = L + ((R - L) >> 1); + if (hours(piles, M) <= h) { + ans = M; + R = M - 1; + } else { + L = M + 1; + } + } + return ans; + } + + public static long hours(int[] piles, int speed) { + long ans = 0; + int offset = speed - 1; + for (int pile : piles) { + ans += (pile + offset) / speed; + } + return ans; + } + +} diff --git a/大厂刷题班/class51/Problem_1035_UncrossedLines.java b/大厂刷题班/class51/Problem_1035_UncrossedLines.java new file mode 100644 index 0000000..04d1c4d --- /dev/null +++ b/大厂刷题班/class51/Problem_1035_UncrossedLines.java @@ -0,0 +1,86 @@ +package class51; + +import java.util.HashMap; + +public class Problem_1035_UncrossedLines { + + // 针对这个题的题意,做的动态规划 + public static int maxUncrossedLines1(int[] A, int[] B) { + if (A == null || A.length == 0 || B == null || B.length == 0) { + return 0; + } + int N = A.length; + int M = B.length; + // dp[i][j]代表: A[0...i]对应B[0...j]最多能划几条线 + int[][] dp = new int[N][M]; + if (A[0] == B[0]) { + dp[0][0] = 1; + } + for (int j = 1; j < M; j++) { + dp[0][j] = A[0] == B[j] ? 1 : dp[0][j - 1]; + } + for (int i = 1; i < N; i++) { + dp[i][0] = A[i] == B[0] ? 1 : dp[i - 1][0]; + } + // 某个值(key),上次在A中出现的位置(value) + HashMap AvalueLastIndex = new HashMap<>(); + AvalueLastIndex.put(A[0], 0); + // 某个值(key),上次在B中出现的位置(value) + HashMap BvalueLastIndex = new HashMap<>(); + for (int i = 1; i < N; i++) { + AvalueLastIndex.put(A[i], i); + BvalueLastIndex.put(B[0], 0); + for (int j = 1; j < M; j++) { + BvalueLastIndex.put(B[j], j); + // 可能性1,就是不让A[i]去划线 + int p1 = dp[i - 1][j]; + // 可能性2,就是不让B[j]去划线 + int p2 = dp[i][j - 1]; + // 可能性3,就是要让A[i]去划线,那么如果A[i]==5,它跟谁划线? + // 贪心的点:一定是在B[0...j]中,尽量靠右侧的5 + int p3 = 0; + if (BvalueLastIndex.containsKey(A[i])) { + int last = BvalueLastIndex.get(A[i]); + p3 = (last > 0 ? dp[i - 1][last - 1] : 0) + 1; + } + // 可能性4,就是要让B[j]去划线,那么如果B[j]==7,它跟谁划线? + // 贪心的点:一定是在A[0...i]中,尽量靠右侧的7 + int p4 = 0; + if (AvalueLastIndex.containsKey(B[j])) { + int last = AvalueLastIndex.get(B[j]); + p4 = (last > 0 ? dp[last - 1][j - 1] : 0) + 1; + } + dp[i][j] = Math.max(Math.max(p1, p2), Math.max(p3, p4)); + } + BvalueLastIndex.clear(); + } + return dp[N - 1][M - 1]; + } + + // 但是其实这个题,不就是求两个数组的最长公共子序列吗? + public static int maxUncrossedLines2(int[] A, int[] B) { + if (A == null || A.length == 0 || B == null || B.length == 0) { + return 0; + } + int N = A.length; + int M = B.length; + int[][] dp = new int[N][M]; + dp[0][0] = A[0] == B[0] ? 1 : 0; + for (int j = 1; j < M; j++) { + dp[0][j] = A[0] == B[j] ? 1 : dp[0][j - 1]; + } + for (int i = 1; i < N; i++) { + dp[i][0] = A[i] == B[0] ? 1 : dp[i - 1][0]; + } + for (int i = 1; i < N; i++) { + for (int j = 1; j < M; j++) { + int p1 = dp[i - 1][j]; + int p2 = dp[i][j - 1]; + int p3 = A[i] == B[j] ? (1 + dp[i - 1][j - 1]) : 0; + dp[i][j] = Math.max(p1, Math.max(p2, p3)); + } + } + return dp[N - 1][M - 1]; + } + +} diff --git a/大厂刷题班/class52/Problem_0656_CoinPath.java b/大厂刷题班/class52/Problem_0656_CoinPath.java new file mode 100644 index 0000000..04439c5 --- /dev/null +++ b/大厂刷题班/class52/Problem_0656_CoinPath.java @@ -0,0 +1,67 @@ +package class52; + +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; + +public class Problem_0656_CoinPath { + + // arr 0 -> n-1 + // arr[i] = -1 死了! + public static int minCost(int[] arr, int jump) { + if (arr == null || arr.length == 0) { + return 0; + } + int n = arr.length; + if (arr[0] == -1 || arr[n - 1] == -1) { + return -1; + } + // dp[i] : 从0位置开始出发,到达i位置的最小代价 + int[] dp = new int[n]; + dp[0] = arr[0]; + for (int i = 1; i < n; i++) { + dp[i] = Integer.MAX_VALUE; + if (arr[i] != -1) { + for (int pre = Math.max(0, i - jump); pre < i; pre++) { + if (dp[pre] != -1) { + dp[i] = Math.min(dp[i], dp[pre] + arr[i]); + } + } + } + dp[i] = dp[i] == Integer.MAX_VALUE ? -1 : dp[i]; + } + return dp[n - 1]; + } + + public static List cheapestJump(int[] arr, int jump) { + int n = arr.length; + int[] best = new int[n]; + int[] last = new int[n]; + int[] size = new int[n]; + Arrays.fill(best, Integer.MAX_VALUE); + Arrays.fill(last, -1); + best[0] = 0; + for (int i = 0; i < n; i++) { + if (arr[i] != -1) { + for (int j = Math.max(0, i - jump); j < i; j++) { + if (arr[j] != -1) { + int cur = best[j] + arr[i]; + // 1) 代价低换方案! + // 2) 代价一样,但是点更多,换方案! + if (cur < best[i] || (cur == best[i] && size[i] - 1 < size[j])) { + best[i] = cur; + last[i] = j; + size[i] = size[j] + 1; + } + } + } + } + } + List path = new LinkedList<>(); + for (int cur = n - 1; cur >= 0; cur = last[cur]) { + path.add(0, cur + 1); + } + return path.get(0) != 1 ? new LinkedList<>() : path; + } + +} diff --git a/大厂刷题班/class52/Problem_0683_KEmptySlots.java b/大厂刷题班/class52/Problem_0683_KEmptySlots.java new file mode 100644 index 0000000..7d9b8d7 --- /dev/null +++ b/大厂刷题班/class52/Problem_0683_KEmptySlots.java @@ -0,0 +1,75 @@ +package class52; + +public class Problem_0683_KEmptySlots { + + public static int kEmptySlots1(int[] bulbs, int k) { + int n = bulbs.length; + int[] days = new int[n]; + for (int i = 0; i < n; i++) { + days[bulbs[i] - 1] = i + 1; + } + int ans = Integer.MAX_VALUE; + if (k == 0) { + for (int i = 1; i < n; i++) { + ans = Math.min(ans, Math.max(days[i - 1], days[i])); + } + } else { + int[] minq = new int[n]; + int l = 0; + int r = -1; + for (int i = 1; i < n && i < k; i++) { + while (l <= r && days[minq[r]] >= days[i]) { + r--; + } + minq[++r] = i; + } + for (int i = 1, j = k; j < n - 1; i++, j++) { + while (l <= r && days[minq[r]] >= days[j]) { + r--; + } + minq[++r] = j; + int cur = Math.max(days[i - 1], days[j + 1]); + if (days[minq[l]] > cur) { + ans = Math.min(ans, cur); + } + if (i == minq[l]) { + l++; + } + } + } + return (ans == Integer.MAX_VALUE) ? -1 : ans; + } + + public static int kEmptySlots2(int[] bulbs, int k) { + int n = bulbs.length; + int[] days = new int[n]; + for (int i = 0; i < n; i++) { + days[bulbs[i] - 1] = i + 1; + } + int ans = Integer.MAX_VALUE; + for (int left = 0, mid = 1, right = k + 1; right < n; mid++) { + // 验证指针mid + // mid 永远不和left撞上的! + // 1) mid在left和right中间验证的时候,没通过! + // 2) mid是撞上right的时候 + if (days[mid] <= Math.max(days[left], days[right])) { +// if(mid == right) { // left...right 达标的! +// int cur = Math.max(days[left], days[right]); +// ans = Math.min(ans, cur); +// left = mid; +// right = mid + k + 1; +// } else { // 验证不通过! +// left = mid; +// right = mid + k + 1; +// } + if (mid == right) { // 收答案! + ans = Math.min(ans, Math.max(days[left], days[right])); + } + left = mid; + right = mid + k + 1; + } + } + return (ans == Integer.MAX_VALUE) ? -1 : ans; + } + +} diff --git a/大厂刷题班/class52/Problem_1488_AvoidFloodInTheCity.java b/大厂刷题班/class52/Problem_1488_AvoidFloodInTheCity.java new file mode 100644 index 0000000..ac6ec55 --- /dev/null +++ b/大厂刷题班/class52/Problem_1488_AvoidFloodInTheCity.java @@ -0,0 +1,81 @@ +package class52; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.PriorityQueue; + +public class Problem_1488_AvoidFloodInTheCity { + + // rains[i] = j 第i天轮到j号湖泊下雨 + // 规定,下雨日,干啥 : -1 + // 不下雨日,如果没有湖泊可抽 : 1 + public static int[] avoidFlood(int[] rains) { + int n = rains.length; + int[] ans = new int[n]; + int[] invalid = new int[0]; + // key : 某个湖 + // value : 这个湖在哪些位置降雨 + // 4 : {3,7,19,21} + // 1 : { 13 } + // 2 : {4, 56} + HashMap> map = new HashMap<>(); + for (int i = 0; i < n; i++) { + if (rains[i] != 0) { // 第i天要下雨,rains[i] + // 3天 9号 + // 9号 { 3 } + // 9号 {1, 3} + if (!map.containsKey(rains[i])) { + map.put(rains[i], new LinkedList<>()); + } + map.get(rains[i]).addLast(i); + } + } + // 没抽干的湖泊表 + // 某个湖如果满了,加入到set里 + // 某个湖被抽干了,从set中移除 + HashSet set = new HashSet<>(); + // 这个堆的堆顶表示最先处理的湖是哪个 + PriorityQueue heap = new PriorityQueue<>(); + for (int i = 0; i < n; i++) { // 0 1 2 3 ... + if (rains[i] != 0) { + if (set.contains(rains[i])) { + return invalid; + } + // 放入到没抽干的表里 + set.add(rains[i]); + map.get(rains[i]).pollFirst(); + if (!map.get(rains[i]).isEmpty()) { + heap.add(new Work(rains[i], map.get(rains[i]).peekFirst())); + } + // 题目规定 + ans[i] = -1; + } else { // 今天干活! + if (heap.isEmpty()) { + ans[i] = 1; + } else { + Work cur = heap.poll(); + set.remove(cur.lake); + ans[i] = cur.lake; + } + } + } + return ans; + } + + public static class Work implements Comparable { + public int lake; + public int nextRain; + + public Work(int l, int p) { + lake = l; + nextRain = p; + } + + @Override + public int compareTo(Work o) { + return nextRain - o.nextRain; + } + } + +} diff --git a/算法周更班/class_2021_11_4_week/Code01_RetainTree.java b/算法周更班/class_2021_11_4_week/Code01_RetainTree.java new file mode 100644 index 0000000..f787c7b --- /dev/null +++ b/算法周更班/class_2021_11_4_week/Code01_RetainTree.java @@ -0,0 +1,89 @@ +package class_2021_11_4_week; + +import java.util.ArrayList; +import java.util.List; + +public class Code01_RetainTree { + + public static class Node { + // 值 + public int value; + // 是否保留 + public boolean retain; + // 下级节点 + public List nexts; + + public Node(int v, boolean r) { + value = v; + retain = r; + nexts = new ArrayList<>(); + } + } + + // 给定一棵树的头节点head + // 请按照题意,保留节点,没有保留的节点删掉 + // 树调整完之后,返回头节点 + public static Node retain(Node x) { + if (x.nexts.isEmpty()) { + return x.retain ? x : null; + } + // x下层有节点 + List newNexts = new ArrayList<>(); + for (Node next : x.nexts) { + Node newNext = retain(next); + if (newNext != null) { + newNexts.add(newNext); + } + } + // x.nexts 老的链表,下级节点 + // newNexts 新的链表,只有保留的在里面 + // + if (!newNexts.isEmpty() || x.retain) { + x.nexts = newNexts; + return x; + } + return null; + } + + // 先序打印 + public static void preOrderPrint(Node head) { + System.out.println(head.value); + for (Node next : head.nexts) { + preOrderPrint(next); + } + } + + public static void main(String[] args) { + Node n1 = new Node(1, false); + Node n2 = new Node(2, true); + Node n3 = new Node(3, false); + Node n4 = new Node(4, false); + Node n5 = new Node(5, false); + Node n6 = new Node(6, true); + Node n7 = new Node(7, true); + Node n8 = new Node(8, false); + Node n9 = new Node(9, false); + Node n10 = new Node(10, false); + Node n11 = new Node(11, false); + Node n12 = new Node(12, false); + Node n13 = new Node(13, true); + + n1.nexts.add(n2); + n1.nexts.add(n3); + n2.nexts.add(n4); + n2.nexts.add(n5); + n3.nexts.add(n6); + n3.nexts.add(n7); + n6.nexts.add(n8); + n6.nexts.add(n9); + n6.nexts.add(n10); + n7.nexts.add(n11); + n7.nexts.add(n12); + n9.nexts.add(n13); + + Node head = retain(n1); + preOrderPrint(head); + + } + +} diff --git a/算法周更班/class_2021_11_4_week/Code02_GuessNumberHigherOrLowerII.java b/算法周更班/class_2021_11_4_week/Code02_GuessNumberHigherOrLowerII.java new file mode 100644 index 0000000..acc1216 --- /dev/null +++ b/算法周更班/class_2021_11_4_week/Code02_GuessNumberHigherOrLowerII.java @@ -0,0 +1,107 @@ +package class_2021_11_4_week; + +// 测试链接 : https://leetcode.com/problems/guess-number-higher-or-lower-ii/ +public class Code02_GuessNumberHigherOrLowerII { + + // 正确的数字,在1~n之间 + // 每次猜错,花费就是你猜的数字 + // 返回:永远最倒霉的情况下,也能赢的胜利,所需要的最少钱数 + public static int minGold(int n) { + return zuo(1, n); + } + + // 目前锁定了,正确的数字,在L~R范围上,除了这个范围都不可能了! + // 返回,永远最倒霉的情况下,猜中这个数字,所需要的最少钱数 + public static int zuo(int L, int R) { + if (L == R) { + return 0; + } + if (L == R - 1) { + return L; + } + int min = Math.min(L + zuo(L + 1, R), R + zuo(L, R - 1)); + for (int i = L + 1; i < R; i++) { + min = Math.min(min, i + Math.max(zuo(L, i - 1), zuo(i + 1, R))); + } + return min; + } + + public static int minGold2(int n) { + + // L -> 1~n + // R -> 1~n + int[][] dp = new int[n + 1][n + 1]; + // 因为初始化都是0,所以dp的对角线,不用填了 + for (int i = 1; i < n; i++) { + dp[i][i + 1] = i; + } + for (int L = n - 2; L >= 1; L--) { + for (int R = L + 2; R <= n; R++) { + int min = Math.min(L + dp[L + 1][R], R + dp[L][R - 1]); + for (int i = L + 1; i < R; i++) { + min = Math.min(min, i + Math.max(dp[L][i - 1], dp[i + 1][R])); + } + dp[L][R] = min; + } + } + return dp[1][n]; + } + + // 暴力递归 + public static int getMoneyAmount1(int n) { + return process1(1, n); + } + + // 假设现在在L ~ R的范围上, 猜数字 + // 返回:确保获胜的最小现金数,不管答案是哪个数字 + // 注意:所谓的“确保获胜”,以及“不管答案是哪个数字”,意味着你每次永远面临猜错的最差情况! + public static int process1(int L, int R) { + // 说明L~R范围,只剩下一个数字了,那不用猜了,获胜了 + if (L == R) { + return 0; + } + // 说明L~R范围,只剩下两个数字了 + // 比如: 5 6 + // 假设永远会遇到最差情况, + // 那么当然猜5,因为最差情况下,也只需要耗费5的代价,然后就知道了答案是6 + // 不能猜6,因为最差情况下,需要耗费6的代价,然后才知道答案是5 + // 所以当然选代价低的!请深刻理解:每次永远面临猜错的最差情况! + if (L == R - 1) { + return L; + } + // 如果说明L~R范围,不仅仅两个数字 + // 比如:5 6 7 8 9 + // 首先尝试5,如果最差情况出现,代价为:5 + 6~9范围上的尝试 + // 最后尝试9,如果最差情况出现,代价为:9 + 5~8范围上的尝试 + int ans = Math.min(L + process1(L + 1, R), R + process1(L, R - 1)); + // 进而尝试6,如果最差情况出现,代价为:6 + Max { 5~5范围上的尝试 ,7~9范围上的尝试} + // 这是因为猜了6,会告诉你,猜高了还是猜低了,所以左右两侧的待定范围,一定会只走一侧 + // 又因为永远会遇到最差情况,所以,一定会走最难受的那一侧,所以是 Max { 5~5范围上的尝试 ,7~9范围上的尝试} + // 进而尝试7,如果最差情况出现,代价为:7 + Max { 5~6范围上的尝试 ,8~9范围上的尝试} + // 进而尝试8,如果最差情况出现,代价为:8 + Max { 5~7范围上的尝试 ,9~9范围上的尝试} + // 所有尝试中,取代价最小值 + for (int M = L + 1; M < R; M++) { + ans = Math.min(ans, M + Math.max(process1(L, M - 1), process1(M + 1, R))); + } + return ans; + } + + // 上面的暴力递归改动态规划 + // 提交到leetcode上可以直接通过 + public static int getMoneyAmount2(int n) { + int[][] dp = new int[n + 1][n + 1]; + for (int i = 1; i < n; i++) { + dp[i][i + 1] = i; + } + for (int L = n - 2; L >= 1; L--) { + for (int R = L + 2; R <= n; R++) { + dp[L][R] = Math.min(L + dp[L + 1][R], R + dp[L][R - 1]); + for (int M = L + 1; M < R; M++) { + dp[L][R] = Math.min(dp[L][R], M + Math.max(dp[L][M - 1], dp[M + 1][R])); + } + } + } + return dp[1][n]; + } + +} diff --git a/算法周更班/class_2021_11_4_week/Code03_StartToEndBinaryOneTarget.java b/算法周更班/class_2021_11_4_week/Code03_StartToEndBinaryOneTarget.java new file mode 100644 index 0000000..fb477d1 --- /dev/null +++ b/算法周更班/class_2021_11_4_week/Code03_StartToEndBinaryOneTarget.java @@ -0,0 +1,337 @@ +package class_2021_11_4_week; + +import java.util.Arrays; + +// 来自真实面试,同学给我的问题 +// 限制:0 <= start <= end,0 <= target <= 64 +// [start,end]范围上的数字,有多少数字二进制中1的个数等于target +public class Code03_StartToEndBinaryOneTarget { + + // 暴力方法 + // 为了验证 + public static long nums1(long start, long end, int target) { + if (start < 0 || end < 0 || start > end || target < 0) { + return -1; + } + long ans = 0; + for (long i = start; i <= end; i++) { + if (bitOne(i) == target) { + ans++; + } + } + return ans; + } + + public static int bitOne(long num) { + int bits = 0; + for (int i = 63; i >= 0; i--) { + if ((num & (1L << i)) != 0) { + bits++; + } + } + return bits; + } + + // 正式方法 + // 递归展示其思路,但是不做动态规划的优化 + public static long nums2(long start, long end, int target) { + if (start < 0 || end < 0 || start > end || target < 0) { + return -1; + } + if (start == 0 && end == 0) { + return target == 0 ? 1 : 0; + } + // 寻找end这数,最高位的1在哪? + int ehigh = 62; + while ((end & (1L << ehigh)) == 0) { + ehigh--; + } + if (start == 0) { + return process2(ehigh, 0, target, end); + } else { // 170 ~ 3657 0 ~ 169 0 ~ 3657 + start--; + int shigh = 62; + while (shigh >= 0 && (start & (1L << shigh)) == 0) { + shigh--; + } + return process2(ehigh, 0, target, end) - process2(shigh, 0, target, start); + } + } + // 11 10 9 8 7 6 5 4 3 2 1 0 + // num : 0 1 1 0 0 1 1 0 0 1 0 1 0 + // 1..... + // 0..... + + // 如果num最高位的1在i位,也就是说num[i...0]才有意义,比i再高的位置都是0 + // 那么从第i位开始做决定,依次往低位进行决定 + // index : 当前来到哪一位做决定,index在i~0之间,从高到低 + // less : 之间做的决定是不是已经比num小了 + // rest : 还剩几个1需要出现 + // long process(index, less, rest, num)含义 : + // [i...index+1]上面已经做过决定了,接下来要在[index...0]上面做决定 + // 在[i...index+1]上面的决定是不是比num[i...index+1]小, + // 如果是,less = 1 + // 如果还没有,一定说明之前的决定==num[i...index+1],此时less = 0 + // 在[index...0]上面做决定的过程中一定要出现rest个1 + // 返回[index...0]上,有多少合法的决定 + // 这个方法可以改动态规划,因为:index范围62~0, less范围0或者1,rest范围0~target + // 自己改动态规划吧 + // 只有index、less、rest这三个有效可变参数、num是固定参数 + // 所以可以改成三维动态规划 + // + // num [h....index+1] 决定完了,需要保证之前做的决定,不能比num大, + // 1) 之前做的决定 已经小于 num所对应的前缀状态了, + // 2) 之前做的决定 等于 num所对应的前缀状态了, + // index..... 去做决定吧! + // less == 1 之前做的决定 已经小于 num所对应的前缀状态了 + // less == 0 之前做的决定 等于 num所对应的前缀状态 + // 剩余几个1,需要出现! + // [index.....] + // index -> 62~0 63种变化 + // less -> 0 1 2种变化 + // rest -> 0 64种变化 + // 63 * 2 * 64 + public static long process2(int index, int less, int rest, long num) { + if (rest > index + 1) { + return 0; + } + // rest <= index + 1 + if (rest == 0) { + return 1L; + } + // 0 < rest <= index + 1 + // 还有1需要去消耗 + // 位数也够用 + if (less == 1) { // less == 1 之前做的决定 已经小于 num所对应的前缀状态了 + if (rest == index + 1) { + return 1; + } else { + // 后面剩余的位数 > 需要消耗掉1的数量的! + // 某些位置填1,某些位置填0 + // index 0 1 + // index 0 + // process2(index - 1, 1, rest, num ); + // index 1 + // process2(index - 1, 1, rest - 1, num); + return process2(index - 1, 1, rest - 1, num) + process2(index - 1, 1, rest, num); + } + } else { // less == 0, 之前做的决定 等于 num所对应的前缀状态的 + if (rest == index + 1) { // 后面剩余的位数,必须都填1,才能消耗完 + // index + // 1 1 1 1 1 1 1 1 1 + // num + // 1 + // index 1 + // + // num 111111111 1 + return (num & (1L << index)) == 0 ? 0 : process2(index - 1, 0, rest - 1, num); + } else { + // less == 0, 之前做的决定 等于 num所对应的前缀状态的 + // 后面剩余的位数 > 需要消耗掉1的数量的! + // 某些位置填1,某些位置填0 + if ((num & (1L << index)) == 0) { + return process2(index - 1, 0, rest, num); + } else { // num 当前位置 1 + // index 1 + // index 0 + return process2(index - 1, 0, rest - 1, num) + process2(index - 1, 1, rest, num); + } + } + } + } + + // 最优解方法 + // 方法二的思路 + 动态规划 + public static long nums3(long start, long end, int target) { + if (start < 0 || end < 0 || start > end || target < 0) { + return -1; + } + if (start == 0 && end == 0) { + return target == 0 ? 1 : 0; + } + int ehigh = 62; + while ((end & (1L << ehigh)) == 0) { + ehigh--; + } + long[][][] dpe = new long[ehigh + 1][2][target + 1]; + for (int i = 0; i <= ehigh; i++) { + Arrays.fill(dpe[i][0], -1); + Arrays.fill(dpe[i][1], -1); + } + long anse = process3(ehigh, 0, target, end, dpe); + if (start == 0) { + return anse; + } else { + start--; + int shigh = 62; + while (shigh >= 0 && (start & (1L << shigh)) == 0) { + shigh--; + } + long[][][] dps = new long[shigh + 1][2][target + 1]; + for (int i = 0; i <= shigh; i++) { + Arrays.fill(dps[i][0], -1); + Arrays.fill(dps[i][1], -1); + } + long anss = process3(shigh, 0, target, start, dps); + return anse - anss; + } + } + + // dp 傻缓存 + // dp 63 * 2 * 64 + // 一定能装下所有的解! + // index+less+rest + // dp[index][less][rest] 请直接拿数据 + // dp[index][less][rest] == -1 表示 没算过,去算! + // dp[index][less][rest] != -1 表示 算过!结果就是dp[index][less][rest] + public static long process3(int index, int less, int rest, long num, long[][][] dp) { + if (rest > index + 1) { + return 0; + } + if (rest == 0) { + return 1L; + } + if (dp[index][less][rest] != -1) { + return dp[index][less][rest]; + } + // 没算过! + long ans = 0; + if (less == 1) { + if (rest == index + 1) { + ans = 1; + } else { + ans = process3(index - 1, 1, rest - 1, num, dp) + process3(index - 1, 1, rest, num, dp); + } + } else { + if (rest == index + 1) { + ans = (num & (1L << index)) == 0 ? 0 : process3(index - 1, 0, rest - 1, num, dp); + } else { + if ((num & (1L << index)) == 0) { + ans = process3(index - 1, 0, rest, num, dp); + } else { + ans = process3(index - 1, 0, rest - 1, num, dp) + process3(index - 1, 1, rest, num, dp); + } + } + } + dp[index][less][rest] = ans; + return ans; + } + + // 利用排列组合的方法 + public static long nums4(long start, long end, int target) { + if (start < 0 || end < 0 || start > end || target < 0) { + return -1; + } + long anse = process4(63, target, end); + if (start == 0) { + return anse; + } else { + long anss = process4(63, target, start - 1); + return anse - anss; + } + } + + public static long process4(int index, int rest, long num) { + if (rest > index + 1) { + return 0; + } + if (rest == 0) { + return 1; + } + if ((num & (1L << index)) == 0) { + return process4(index - 1, rest, num); + } else { + return c(index, rest) + process4(index - 1, rest - 1, num); + } + } + + // 求C(N,A)的解 + // N! / (A! * (N - A)!) + // 即 : (A+1 * A+2 * ... * N) / (2 * 3 * 4 * (N-A)) + // 为了不溢出,每一步求一个最大公约数,然后消掉 + public static long c(long n, long a) { + if (n < a) { + return 0L; + } + long up = 1L; + long down = 1L; + for (long i = a + 1, j = 2; i <= n || j <= n - a;) { + if (i <= n) { + up *= i++; + } + if (j <= n - a) { + down *= j++; + } + long gcd = gcd(up, down); + up /= gcd; + down /= gcd; + } + return up / down; + } + + // 求m和n的最大公约数 + public static long gcd(long m, long n) { + return n == 0 ? m : gcd(n, m % n); + } + + public static void main(String[] args) { + long range = 600L; + System.out.println("功能测试开始"); + for (long start = 0L; start < range; start++) { + for (long end = start; end < range; end++) { + int target = (int) (Math.random() * 10); + long ans1 = nums1(start, end, target); + long ans2 = nums2(start, end, target); + long ans3 = nums3(start, end, target); + long ans4 = nums4(start, end, target); + if (ans1 != ans2 || ans1 != ans3 || ans1 != ans4) { + System.out.println("出错了!"); + } + } + } + System.out.println("功能测试结束"); + + long start = 33281731L; + long end = 204356810L; + int target = 17; + long startTime; + long endTime; + long ans1; + long ans2; + long ans3; + long ans4; + + System.out.println("大范围性能测试,开始"); + startTime = System.currentTimeMillis(); + ans1 = nums1(start, end, target); + endTime = System.currentTimeMillis(); + System.out.println("方法一答案:" + ans1 + ", 运行时间(毫秒) : " + (endTime - startTime)); + startTime = System.currentTimeMillis(); + ans2 = nums2(start, end, target); + endTime = System.currentTimeMillis(); + System.out.println("方法二答案:" + ans2 + ", 运行时间(毫秒) : " + (endTime - startTime)); + startTime = System.currentTimeMillis(); + ans3 = nums3(start, end, target); + endTime = System.currentTimeMillis(); + System.out.println("方法三答案:" + ans3 + ", 运行时间(毫秒) : " + (endTime - startTime)); + ans4 = nums4(start, end, target); + endTime = System.currentTimeMillis(); + System.out.println("方法四答案:" + ans4 + ", 运行时间(毫秒) : " + (endTime - startTime)); + System.out.println("大范围性能测试,结束"); + + System.out.println("超大范围性能测试,开始"); + start = 88193819381L; + end = 92371283713182371L; + target = 30; + startTime = System.currentTimeMillis(); + ans3 = nums3(start, end, target); + endTime = System.currentTimeMillis(); + System.out.println("方法三答案:" + ans3 + ", 运行时间(毫秒) : " + (endTime - startTime)); + startTime = System.currentTimeMillis(); + ans4 = nums4(start, end, target); + endTime = System.currentTimeMillis(); + System.out.println("方法四答案:" + ans4 + ", 运行时间(毫秒) : " + (endTime - startTime)); + System.out.println("超大范围性能测试,结束"); + } + +} diff --git a/算法周更班/class_2021_12_1_week/Code01_XtoYMinDistance.java b/算法周更班/class_2021_12_1_week/Code01_XtoYMinDistance.java new file mode 100644 index 0000000..d0aca05 --- /dev/null +++ b/算法周更班/class_2021_12_1_week/Code01_XtoYMinDistance.java @@ -0,0 +1,171 @@ +package class_2021_12_1_week; + +import java.util.PriorityQueue; + +// 来自真实面试,同学给我的问题 +public class Code01_XtoYMinDistance { + + // 暴力方法 + // dfs尝试所有情况 + // 没有优化,就是纯暴力 + public static int minDistance1(int n, int[][] roads, int x, int y) { + // 第一步生成邻接矩阵 + int[][] map = new int[n + 1][n + 1]; + for (int i = 0; i <= n; i++) { + for (int j = 0; j <= n; j++) { + map[i][j] = Integer.MAX_VALUE; + } + } + for (int[] road : roads) { + map[road[0]][road[1]] = Math.min(map[road[0]][road[1]], road[2]); + map[road[1]][road[0]] = Math.min(map[road[1]][road[0]], road[2]); + } + boolean[] visited = new boolean[n + 1]; + return process(x, y, n, map, visited); + } + + // 当前来到的城市是cur,最终目的地是aim,一共有1~n这些城市 + // 所有城市之间的距离都在map里 + // 之前已经走过了哪些城市都记录在了visited里面,请不要重复经过 + // 返回从cur到aim所有可能的路里,最小距离是多少 + public static int process(int cur, int aim, int n, int[][] map, boolean[] visited) { + if (visited[cur]) { + return Integer.MAX_VALUE; + } + if (cur == aim) { + return 0; + } + visited[cur] = true; + int ans = Integer.MAX_VALUE; + for (int next = 1; next <= n; next++) { + if (next != cur && map[cur][next] != Integer.MAX_VALUE) { + int rest = process(next, aim, n, map, visited); + if (rest != Integer.MAX_VALUE) { + ans = Math.min(ans, map[cur][next] + rest); + } + } + } + visited[cur] = false; + return ans; + } + + // Dijkstra的解 + // n是城市数量 + // 城市编号:1 ~ n 0弃而不用 + public static int minDistance2(int n, int[][] roads, int x, int y) { + // 第一步生成邻接矩阵 + int[][] map = new int[n + 1][n + 1]; + for (int i = 0; i <= n; i++) { + for (int j = 0; j <= n; j++) { + map[i][j] = Integer.MAX_VALUE; + } + } + // 建立路! + for (int[] road : roads) { + map[road[0]][road[1]] = Math.min(map[road[0]][road[1]], road[2]); + map[road[1]][road[0]] = Math.min(map[road[1]][road[0]], road[2]); + } + // computed[i] = true,表示从源出发点到i这个城市,已经计算出最短距离了 + // computed[i] = false,表示从源出发点到i这个城市,还没有计算出最短距离 + boolean[] computed = new boolean[n + 1]; + // 距离小根堆 + PriorityQueue heap = new PriorityQueue<>((a, b) -> (a.pathSum - b.pathSum)); + heap.add(new Node(x, 0)); + while (!heap.isEmpty()) { + // x -> ... -> 当前的城市, 有距离 + Node cur = heap.poll(); + if (computed[cur.city]) { + continue; + } + // 没算过 + // 开始算! + if (cur.city == y) { + return cur.pathSum; + } + computed[cur.city] = true; + for (int next = 1; next <= n; next++) { + if (next != cur.city && map[cur.city][next] != Integer.MAX_VALUE && !computed[next]) { + heap.add(new Node(next, cur.pathSum + map[cur.city][next])); + } + } + } + return Integer.MAX_VALUE; + } + + // 当前来到的Node,注意这不是城市的意思,这是就是一个普通的封装类 + // Node封装了,当前来到的城市是什么,以及,从源出发点到这个城市的路径和是多少? + public static class Node { + // 当前来到的城市编号 + public int city; + // 从源出发点到这个城市的路径和 + public int pathSum; + + public Node(int c, int p) { + city = c; + pathSum = p; + } + } + + // 为了测试 + // 城市1~n + // 随机生成m条道路 + // 每一条路的距离,在1~v之间 + public static int[][] randomRoads(int n, int m, int v) { + int[][] roads = new int[m][3]; + for (int i = 0; i < m; i++) { + int from = (int) (Math.random() * n) + 1; + int to = (int) (Math.random() * n) + 1; + int distance = (int) (Math.random() * v) + 1; + roads[i] = new int[] { from, to, distance }; + } + return roads; + } + + // 为了测试 + public static void main(String[] args) { + // 城市数量n,下标从1开始,不从0开始 + int n = 4; + // 边的数量m,m的值不能大于n * (n-1) / 2 + int m = 4; + // 所的路有m条 + // [a,b,c]表示a和b之间有路,距离为3,根据题意,本题中的边都是无向边 + // 假设有两条路 + // [1,3,7],这条路是从1到3,距离是7 + // [1,3,4],这条路是从1到3,距离是4 + // 那么应该忽略[1,3,7],因为[1,3,4]比它好 + int[][] roads = new int[m][3]; + roads[0] = new int[] { 1, 2, 4 }; + roads[1] = new int[] { 1, 3, 1 }; + roads[2] = new int[] { 1, 4, 1 }; + roads[3] = new int[] { 2, 3, 1 }; + // 求从x到y的最短距离是多少,x和y应该在[1,n]之间 + int x = 2; + int y = 4; + + // 暴力方法的解 + System.out.println(minDistance1(n, roads, x, y)); + + // Dijkstra的解 + System.out.println(minDistance2(n, roads, x, y)); + + // 下面开始随机验证 + int cityMaxSize = 12; + int pathMax = 30; + int testTimes = 20000; + System.out.println("测试开始"); + for (int i = 0; i < testTimes; i++) { + n = (int) (Math.random() * cityMaxSize) + 1; + m = (int) (Math.random() * n * (n - 1) / 2) + 1; + roads = randomRoads(n, m, pathMax); + x = (int) (Math.random() * n) + 1; + y = (int) (Math.random() * n) + 1; + int ans1 = minDistance1(n, roads, x, y); + int ans2 = minDistance2(n, roads, x, y); + if (ans1 != ans2) { + System.out.println("出错了!"); + } + } + System.out.println("测试结束"); + } + +} diff --git a/算法周更班/class_2021_12_1_week/Code02_4KeysKeyboard.java b/算法周更班/class_2021_12_1_week/Code02_4KeysKeyboard.java new file mode 100644 index 0000000..7b37442 --- /dev/null +++ b/算法周更班/class_2021_12_1_week/Code02_4KeysKeyboard.java @@ -0,0 +1,38 @@ +package class_2021_12_1_week; + +// 测试链接 : https://leetcode.com/problems/4-keys-keyboard/ +public class Code02_4KeysKeyboard { + + // 可以证明: + // 来到i的时候,包括i在内最多有连续4次粘贴行为 + // 不可能更多,如果有连续5次粘贴,一定就不再是最优解 + // 假设开始时,A的数量为S,看如下的变化过程,我们称这是行为一: + // 开始 全选 复制(粘贴板S个A) 粘贴 粘贴 粘贴 粘贴 粘贴 + // S S S 2*S 3*S 4*S 5*S 6*S + // 但是,注意看如下的行为二: + // 开始 全选 复制(粘贴板S个A) 粘贴 全选 复制(粘贴板2S个A) 粘贴 粘贴 + // S S S 2*S 2*S 2*S 4*S 6*S + // 行为一,经历8步,最后是6*S个A + // 行为二,经历8步,最后是6*S个A + // 但是行为二在粘贴板上有2S个A,而行为一在粘贴板上有S个A + // 所以行为一没有行为二优 + // 以此说明:来到i的时候,包括i在内最多有连续4次粘贴行为 + // 那么就尝试:连续1次、连续2次、连续3次、连续4次粘贴行为即可 + public static int maxA(int n) { + // dp[0] 1步以内的最优解 + // dp[1] 2步以内的最优解 + // dp[2] 3步以内的最优解 + // dp[i] i+1步以内的最优解 + int[] dp = new int[n]; + for (int i = 0; i < 6 && i < n; i++) { + dp[i] = i + 1; + } + for (int i = 6; i < n; i++) { + dp[i] = Math.max( + Math.max(dp[i - 3] * 2, dp[i - 4] * 3), + Math.max(dp[i - 5] * 4, dp[i - 6] * 5)); + } + return dp[n - 1]; + } + +} diff --git a/算法周更班/class_2021_12_1_week/Code03_RedundantConnectionII.java b/算法周更班/class_2021_12_1_week/Code03_RedundantConnectionII.java new file mode 100644 index 0000000..97ea609 --- /dev/null +++ b/算法周更班/class_2021_12_1_week/Code03_RedundantConnectionII.java @@ -0,0 +1,102 @@ +package class_2021_12_1_week; + +// 来自微软,真正考的时候阉割了难度 +// 测试链接 : https://leetcode.com/problems/redundant-connection-ii/ +public class Code03_RedundantConnectionII { + + // [ + // [1 , 5] 1 > 5 + // [7 , 3] 7 > 3 + + + // ] + // 边的数量 = 点的数量! + public static int[] findRedundantDirectedConnection(int[][] edges) { + // N是点的数量 + // 点的编号,1~N,没有0 + int N = edges.length; + // 并查集!N个点,去初始化,每个点各自是一个集合 + UnionFind uf = new UnionFind(N); + // pre[i] = 0 来到i节点是第一次 + // pre[i] = 6 之前来过i,是从6来的! + int[] pre = new int[N + 1]; + + // 如果,没有入度为2的点, + // first second 都维持是null + // 如果,有入度为2的点,那么也只可能有一个 + // 比如入度为2的点,是5 + // first = [3,5] + // second = [12,5] + int[] first = null; + int[] second = null; + // 有没有环!非常不单纯!含义复杂! + int[] circle = null; + for (int i = 0; i < N; i++) { // 遍历每条边! + int from = edges[i][0]; + int to = edges[i][1]; + if (pre[to] != 0) { // 不止一次来过to! + first = new int[] { pre[to], to }; + second = edges[i]; + } else { // 第一次到达to, + pre[to] = from; + if (uf.same(from, to)) { + circle = edges[i]; + } else { + uf.union(from, to); + } + } + } + // 重点解析!这是啥??? + // first != null + // 有入度为2的点! + return first != null ? (circle != null ? first : second) : circle; + } + + public static class UnionFind { + private int[] f; + private int[] s; + private int[] h; + + public UnionFind(int N) { + f = new int[N + 1]; + s = new int[N + 1]; + h = new int[N + 1]; + for (int i = 0; i <= N; i++) { + f[i] = i; + s[i] = 1; + } + } + + private int find(int i) { + int hi = 0; + while (i != f[i]) { + h[hi++] = i; + i = f[i]; + } + while (hi > 0) { + f[h[--hi]] = i; + } + return i; + } + + public boolean same(int i, int j) { + return find(i) == find(j); + } + + public void union(int i, int j) { + int fi = find(i); + int fj = find(j); + if (fi != fj) { + if (s[fi] >= s[fj]) { + f[fj] = fi; + s[fi] = s[fi] + s[fj]; + } else { + f[fi] = fj; + s[fj] = s[fi] + s[fj]; + } + } + } + + } + +} diff --git a/算法周更班/class_2021_12_2_week/Code01_FindAllPeopleWithSecret.java b/算法周更班/class_2021_12_2_week/Code01_FindAllPeopleWithSecret.java new file mode 100644 index 0000000..e59d154 --- /dev/null +++ b/算法周更班/class_2021_12_2_week/Code01_FindAllPeopleWithSecret.java @@ -0,0 +1,102 @@ +package class_2021_12_2_week; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +// 链接测试 : https://leetcode.com/problems/find-all-people-with-secret/ +public class Code01_FindAllPeopleWithSecret { + + public List findAllPeople(int n, int[][] meetings, int firstPerson) { + // 0~n-1号专家,各自建立小集合 + // (0, firstPerson)合在一起,作为知道秘密的集合 + UnionFind uf = new UnionFind(n, firstPerson); + int m = meetings.length; + Arrays.sort(meetings, (a, b) -> a[2] - b[2]); + // [1,7,1] [2,4,2] [3,6,2] + // 1,7 2,4 3,6 + int[] help = new int[m << 1]; + help[0] = meetings[0][0]; + help[1] = meetings[0][1]; + int size = 2; + for (int i = 1; i < m; i++) { + // i 2 + if (meetings[i][2] != meetings[i - 1][2]) { + share(help, size, uf); + help[0] = meetings[i][0]; + help[1] = meetings[i][1]; + size = 2; + } else { + help[size++] = meetings[i][0]; + help[size++] = meetings[i][1]; + } + } + share(help, size, uf); + List ans = new ArrayList<>(); + for (int i = 0; i < n; i++) { + if (uf.know(i)) { + ans.add(i); + } + } + return ans; + } + + public static void share(int[] help, int size, UnionFind uf) { + for (int i = 0; i < size; i += 2) { + uf.union(help[i], help[i + 1]); + } + for (int i = 0; i < size; i++) { + if (!uf.know(help[i])) { + uf.isolate(help[i]); + } + } + } + + public static class UnionFind { + public int[] father; + public boolean[] sect; + public int[] help; + + public UnionFind(int n, int first) { + father = new int[n]; + sect = new boolean[n]; + help = new int[n]; + for (int i = 1; i < n; i++) { + father[i] = i; + } + father[first] = 0; + sect[0] = true; + } + + private int find(int i) { + int hi = 0; + while (i != father[i]) { + help[hi++] = i; + i = father[i]; + } + for (hi--; hi >= 0; hi--) { + father[help[hi]] = i; + } + return i; + } + + public void union(int i, int j) { + int fatheri = find(i); + int fatherj = find(j); + if (fatheri != fatherj) { + father[fatherj] = fatheri; + sect[fatheri] |= sect[fatherj]; + } + } + + public boolean know(int i) { + return sect[find(i)]; + } + + public void isolate(int i) { + father[i] = i; + } + + } + +} diff --git a/算法周更班/class_2021_12_2_week/Code02_AwayFromBlackHole.java b/算法周更班/class_2021_12_2_week/Code02_AwayFromBlackHole.java new file mode 100644 index 0000000..709dcd8 --- /dev/null +++ b/算法周更班/class_2021_12_2_week/Code02_AwayFromBlackHole.java @@ -0,0 +1,122 @@ +package class_2021_12_2_week; + +// 来自美团 +// 所有黑洞的中心点记录在holes数组里 +// 比如[[3,5] [6,9]]表示,第一个黑洞在(3,5),第二个黑洞在(6,9) +// 并且所有黑洞的中心点都在左下角(0,0),右上角(x,y)的区域里 +// 飞船一旦开始进入黑洞,就会被吸进黑洞里 +// 返回: +// 如果统一所有黑洞的半径,最大半径是多少,依然能保证飞船从(0,0)能到达(x,y) +// 1000 1000*1000 10^6 * 二分 +public class Code02_AwayFromBlackHole { + + public static int maxRadius(int[][] holes, int x, int y) { + int L = 1; + int R = Math.max(x, y); + int ans = 0; + while (L <= R) { + int M = (L + R) / 2; + if (ok(holes, M, x, y)) { + ans = M; + L = M + 1; + } else { + R = M - 1; + } + } + return ans; + } + + public static boolean ok(int[][] holes, int r, int x, int y) { + int n = holes.length; + UnionFind uf = new UnionFind(holes, n, r); + for (int i = 0; i < n; i++) { + for (int j = i; j < n; j++) { + if (touch(holes[i][0], holes[i][1], holes[j][0], holes[j][1], r)) { + uf.union(i, j); + } + if (uf.block(i, x, y)) { + return false; + } + } + } + return true; + } + + public static boolean touch(int x1, int y1, int x2, int y2, int r) { + return (r << 1) >= Math.sqrt((Math.pow(Math.abs(x1 - x2), 2) + Math.pow(Math.abs(y1 - y2), 2))); + } + + public static class UnionFind { + public int[] father; + public int[] size; + public int[] xmin; + public int[] xmax; + public int[] ymin; + public int[] ymax; + public int[] help; + + public UnionFind(int[][] holes, int n, int r) { + father = new int[n]; + size = new int[n]; + xmin = new int[n]; + xmax = new int[n]; + ymin = new int[n]; + ymax = new int[n]; + help = new int[n]; + for (int i = 0; i < n; i++) { + father[i] = i; + size[i] = 1; + xmin[i] = holes[i][0] - r; + xmax[i] = holes[i][0] + r; + ymin[i] = holes[i][1] - r; + ymax[i] = holes[i][1] + r; + } + } + + private int find(int i) { + int hi = 0; + while (i != father[i]) { + help[hi++] = i; + i = father[i]; + } + for (hi--; hi >= 0; hi--) { + father[help[hi]] = i; + } + return i; + } + + public void union(int i, int j) { + int fatheri = find(i); + int fatherj = find(j); + if (fatheri != fatherj) { + int sizei = size[fatheri]; + int sizej = size[fatherj]; + int big = sizei >= sizej ? fatheri : fatherj; + int small = big == fatheri ? fatherj : fatheri; + father[small] = big; + size[big] = sizei + sizej; + xmin[big] = Math.min(xmin[big], xmin[small]); + xmax[big] = Math.max(xmax[big], xmax[small]); + ymin[big] = Math.min(ymin[big], ymin[small]); + ymax[big] = Math.max(ymax[big], ymax[small]); + } + } + + public boolean block(int i, int x, int y) { + i = find(i); + return (xmin[i] <= 0 && xmax[i] >= x) + || (ymin[i] <= 0 && ymax[i] >= y) + || (xmin[i] <= 0 && ymin[i] <= 0) + || (xmax[i] >= x && ymax[i] >= y); + } + + } + + public static void main(String[] args) { + int[][] holes = { { 1, 2 }, { 4, 4 }, { 3, 0 }, { 5, 2 } }; + int x = 4; + int y = 6; + System.out.println(maxRadius(holes, x, y)); + } + +} diff --git a/算法周更班/class_2021_12_2_week/Code03_MagicSum.java b/算法周更班/class_2021_12_2_week/Code03_MagicSum.java new file mode 100644 index 0000000..5105972 --- /dev/null +++ b/算法周更班/class_2021_12_2_week/Code03_MagicSum.java @@ -0,0 +1,301 @@ +package class_2021_12_2_week; + +import java.util.Arrays; + +// 来自真实面试,同学给我的问题 +// arr数组长度为n, magic数组长度为m +// 含义: +// arr = { 3, 1, 4, 5, 7 } +// 如果完全不改变arr中的值,那么收益就是累加和 = 3 + 1 + 4 + 5 + 7 = 20 +// magics = { +// {0,2,5} 表示arr[0~2]中的任何一个值,都能改成5 +// {3,4,3} 表示arr[3~4]中的任何一个值,都能改成3 +// {1,3,7} 表示arr[1~3]中的任何一个值,都能改成7 +// } +// 就是说,magics中的任何一组数据{a,b,c},都表示一种操作, +// 你可以选择arr[a~b]中任何一个数字,变成c。 +// 并且每一种操作,都可以执行任意次 +// 其中 0 <= a <= b < n +// 那么经过若干次的魔法操作,你当然可能得到arr的更大的累加和 +// 返回arr尽可能大的累加和 +// n <= 10^7 +// m <= 10^6 +// arr中的值和c的范围 <= 10^12 +public class Code03_MagicSum { + + // 暴力解,写出来为了验证正式方法而已 + public static int maxSum1(int[] arr, int[][] magics) { + int[] help = Arrays.copyOf(arr, arr.length); + for (int[] m : magics) { + int l = m[0]; + int r = m[1]; + int c = m[2]; + for (int i = l; i <= r; i++) { + help[i] = Math.max(help[i], c); + } + } + int sum = 0; + for (int num : help) { + sum += num; + } + return sum; + } + + // O(N) + O(M * logM) + O(M * logN) + O(N * logN) + public static int maxSum2(int[] arr, int[][] magics) { + int n = arr.length; + // 线段树里的下标,从1开始,不从0开始! + SegmentTree2 st = new SegmentTree2(n); + Arrays.sort(magics, (a, b) -> (a[2] - b[2])); + for (int[] magic : magics) { + st.update(magic[0] + 1, magic[1] + 1, magic[2], 1, n, 1); + } + int ans = 0; + for (int i = 0; i < n; i++) { + ans += Math.max(st.query(i + 1, i + 1, 1, n, 1), arr[i]); + } + return ans; + } + + // 这是一棵普通的线段树 + // 区间上维持最大值的线段树 + // 支持区间值更新 + // 支持区间最大值查询 + public static class SegmentTree2 { + private int[] max; + private int[] change; + private boolean[] update; + + public SegmentTree2(int size) { + int N = size + 1; + max = new int[N << 2]; + change = new int[N << 2]; + update = new boolean[N << 2]; + } + + private void pushUp(int rt) { + max[rt] = Math.max(max[rt << 1], max[rt << 1 | 1]); + } + + private void pushDown(int rt, int ln, int rn) { + if (update[rt]) { + update[rt << 1] = true; + update[rt << 1 | 1] = true; + change[rt << 1] = change[rt]; + change[rt << 1 | 1] = change[rt]; + max[rt << 1] = change[rt]; + max[rt << 1 | 1] = change[rt]; + update[rt] = false; + } + } + + public void update(int L, int R, int C, int l, int r, int rt) { + if (L <= l && r <= R) { + update[rt] = true; + change[rt] = C; + max[rt] = C; + return; + } + int mid = (l + r) >> 1; + pushDown(rt, mid - l + 1, r - mid); + if (L <= mid) { + update(L, R, C, l, mid, rt << 1); + } + if (R > mid) { + update(L, R, C, mid + 1, r, rt << 1 | 1); + } + pushUp(rt); + } + + public int query(int L, int R, int l, int r, int rt) { + if (L <= l && r <= R) { + return max[rt]; + } + int mid = (l + r) >> 1; + pushDown(rt, mid - l + 1, r - mid); + int left = 0; + int right = 0; + if (L <= mid) { + left = query(L, R, l, mid, rt << 1); + } + if (R > mid) { + right = query(L, R, mid + 1, r, rt << 1 | 1); + } + return Math.max(left, right); + } + + } + + // O(N) + O(M * logM) + O(M * logN) + O(N) + public static int maxSum3(int[] arr, int[][] magics) { + int n = arr.length; + SegmentTree3 st = new SegmentTree3(n); + Arrays.sort(magics, (a, b) -> (a[2] - b[2])); + for (int[] magic : magics) { + st.update(magic[0] + 1, magic[1] + 1, magic[2], 1, n, 1); + } + int ans = 0; + int[] query = st.buildSingleQuery(n); + for (int i = 0; i < n; i++) { + ans += Math.max(query[i], arr[i]); + } + return ans; + } + + // 为方法三特别定制的线段树 + // 区间上维持最大值的线段树 + // 支持区间值更新 + // 为本道题定制了一个方法: + // 假设全是单点查询,请统一返回所有单点的结果(一个结果数组,里面有所有单点记录) + public static class SegmentTree3 { + private int[] max; + private int[] change; + private boolean[] update; + + public SegmentTree3(int size) { + int N = size + 1; + max = new int[N << 2]; + change = new int[N << 2]; + update = new boolean[N << 2]; + } + + private void pushUp(int rt) { + max[rt] = Math.max(max[rt << 1], max[rt << 1 | 1]); + } + + private void pushDown(int rt, int ln, int rn) { + if (update[rt]) { + update[rt << 1] = true; + update[rt << 1 | 1] = true; + change[rt << 1] = change[rt]; + change[rt << 1 | 1] = change[rt]; + max[rt << 1] = change[rt]; + max[rt << 1 | 1] = change[rt]; + update[rt] = false; + } + } + + public void update(int L, int R, int C, int l, int r, int rt) { + if (L <= l && r <= R) { + update[rt] = true; + change[rt] = C; + max[rt] = C; + return; + } + int mid = (l + r) >> 1; + pushDown(rt, mid - l + 1, r - mid); + if (L <= mid) { + update(L, R, C, l, mid, rt << 1); + } + if (R > mid) { + update(L, R, C, mid + 1, r, rt << 1 | 1); + } + pushUp(rt); + } + + public int index = 0; + + public int[] buildSingleQuery(int n) { + int[] ans = new int[n + 1]; + process(ans, 1, n, 1); + return ans; + } + + private void process(int[] ans, int l, int r, int rt) { + if (l == r) { + ans[index++] = max[rt]; + } else { + int mid = (l + r) >> 1; + pushDown(rt, mid - l + 1, r - mid); + process(ans, l, mid, rt << 1); + process(ans, mid + 1, r, rt << 1 | 1); + } + } + + } + + // 为了测试 + public static int[] generateRandomArray(int n, int value) { + int[] arr = new int[n]; + for (int i = 0; i < n; i++) { + arr[i] = (int) (Math.random() * value) + 1; + } + return arr; + } + + // 为了测试 + public static int[][] generateRandomMagics(int n, int m, int value) { + int[][] magics = new int[m][3]; + for (int[] magic : magics) { + int a = (int) (Math.random() * n); + int b = (int) (Math.random() * n); + int c = (int) (Math.random() * value) + 1; + magic[0] = Math.min(a, b); + magic[1] = Math.max(a, b); + magic[2] = c; + } + return magics; + } + + // 为了测试 + public static void main(String[] args) { + int n = 30; + int m = 15; + int v = 1000; + int testTimes = 10000; + System.out.println("测试开始"); + for (int i = 0; i < testTimes; i++) { + int N = (int) (Math.random() * n) + 1; + int M = (int) (Math.random() * m) + 1; + int[] arr = generateRandomArray(N, v); + int[][] magics = generateRandomMagics(N, M, v); + int ans1 = maxSum1(arr, magics); + int ans2 = maxSum2(arr, magics); + int ans3 = maxSum3(arr, magics); + if (ans1 != ans2 || ans1 != ans3) { + System.out.println(ans1); + System.out.println(ans2); + System.out.println(ans3); + for (int num : arr) { + System.out.print(num + " "); + } + System.out.println(); + for (int[] magic : magics) { + System.out.println("[ " + magic[0] + " , " + magic[1] + " , " + magic[2] + " ] "); + } + System.out.println("出错了!"); + break; + } + } + System.out.println("测试结束"); + + System.out.println("性能测试开始"); + System.out.println("n的数据量将达到10^7"); + System.out.println("m的数据量将达到10^6"); + System.out.println("为了防止溢出,每个值的范围控制在10以内"); + + n = 10000000; + m = 1000000; + v = 10; + + int[] arr = generateRandomArray(n, v); + int[][] magics = generateRandomMagics(n, m, v); + + long start; + long end; + + start = System.currentTimeMillis(); + int ans2 = maxSum2(arr, magics); + end = System.currentTimeMillis(); + System.out.println("方法二的结果 : " + ans2 + ", 方法二的运行时间: " + (end - start) + " 毫秒"); + + start = System.currentTimeMillis(); + int ans3 = maxSum3(arr, magics); + end = System.currentTimeMillis(); + System.out.println("方法三的结果 : " + ans3 + ", 方法三的运行时间: " + (end - start) + " 毫秒"); + + System.out.println("性能测试结束"); + + } + +} diff --git a/算法周更班/class_2021_12_2_week/Code04_LowestCommonAncestorOfABinaryTreeIV.java b/算法周更班/class_2021_12_2_week/Code04_LowestCommonAncestorOfABinaryTreeIV.java new file mode 100644 index 0000000..a1d654a --- /dev/null +++ b/算法周更班/class_2021_12_2_week/Code04_LowestCommonAncestorOfABinaryTreeIV.java @@ -0,0 +1,57 @@ +package class_2021_12_2_week; + +import java.util.HashSet; + +// 测试链接 : https://leetcode.com/problems/lowest-common-ancestor-of-a-binary-tree-iv/ +public class Code04_LowestCommonAncestorOfABinaryTreeIV { + + public static class TreeNode { + public int val; + public TreeNode left; + public TreeNode right; + } + + public TreeNode lowestCommonAncestor(TreeNode root, TreeNode[] nodes) { + HashSet set = new HashSet<>(); + for (TreeNode node : nodes) { + set.add(node.val); + } + return process(root, set, set.size()).find; + } + + public static class Info { + // 找没找到最低公共祖先 + // 没找到,find = null + // 找到了最低公共祖先,find是最低公共祖先 + public TreeNode find; + // 我这颗子树上,删掉了几个节点! + public int removes; + + public Info(TreeNode f, int r) { + find = f; + removes = r; + } + } + + public static Info process(TreeNode x, HashSet set, int all) { + if (x == null) { + return new Info(null, 0); + } + Info left = process(x.left, set, all); + if (left.find != null) { + return left; + } + Info right = process(x.right, set, all); + if (right.find != null) { + return right; + } + int cur = set.contains(x.val) ? 1 : 0; + set.remove(x.val); + if (left.removes + right.removes + cur == all) { + return new Info(x, all); + } else { + return new Info(null, left.removes + right.removes + cur); + } + } + +} diff --git a/算法周更班/class_2021_12_2_week/Code05_Colors.java b/算法周更班/class_2021_12_2_week/Code05_Colors.java new file mode 100644 index 0000000..20be746 --- /dev/null +++ b/算法周更班/class_2021_12_2_week/Code05_Colors.java @@ -0,0 +1,325 @@ +package class_2021_12_2_week; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; + +// 来自美团 +// 给定一棵多叉树的头节点head +// 每个节点的颜色只会是0、1、2、3中的一种 +// 任何两个节点之间的都有路径 +// 如果节点a和节点b的路径上,包含全部的颜色,这条路径算达标路径 +// (a -> ... -> b)和(b -> ... -> a)算两条路径 +// 求多叉树上达标的路径一共有多少? +// 点的数量 <= 10^5 +public class Code05_Colors { + + public static class Node { + public int color; + public List nexts; + + public Node(int c) { + color = c; + nexts = new ArrayList<>(); + } + } + + // 暴力方法 + // 为了验证 + public static int colors1(Node head) { + if (head == null) { + return 0; + } + HashMap map = new HashMap<>(); + parentMap(head, null, map); + List allNodes = new ArrayList<>(); + for (Node cur : map.keySet()) { + allNodes.add(cur); + } + int ans = 0; + for (int i = 0; i < allNodes.size(); i++) { + for (int j = i + 1; j < allNodes.size(); j++) { + if (ok(allNodes.get(i), allNodes.get(j), map)) { + ans++; + } + } + } + return ans << 1; + } + + public static void parentMap(Node cur, Node pre, HashMap map) { + if (cur != null) { + map.put(cur, pre); + for (Node next : cur.nexts) { + parentMap(next, cur, map); + } + } + } + + public static boolean ok(Node a, Node b, HashMap map) { + HashSet aPath = new HashSet<>(); + Node cur = a; + while (cur != null) { + aPath.add(cur); + cur = map.get(cur); + } + Node lowest = b; + while (!aPath.contains(lowest)) { + lowest = map.get(lowest); + } + int colors = 1 << lowest.color; + cur = a; + while (cur != lowest) { + colors |= (1 << cur.color); + cur = map.get(cur); + } + cur = b; + while (cur != lowest) { + colors |= (1 << cur.color); + cur = map.get(cur); + } + return colors == 15; + } + + // 正式方法 + public static long colors2(Node head) { + if (head == null) { + return 0; + } + return process2(head).all; + } + + public static class Info { + // 我这棵子树,总共合法的路径有多少? + public long all; + // 课上没有强调!但是请务必注意! + // 一定要从头节点出发的情况下! + // 一定要从头节点出发的情况下! + // 一定要从头节点出发的情况下! + // 走出来每种状态路径的条数 + public long[] colors; + + public Info() { + all = 0; + colors = new long[16]; + } + } + + public static Info process2(Node h) { + Info ans = new Info(); + // 头节点拥有的颜色 + // 2 0100 0 0001 3 1000 + int hs = 1 << h.color; + ans.colors[hs] = 1; + if (!h.nexts.isEmpty()) { + int n = h.nexts.size(); + // 0(不用) 1 2 3 4 + Info[] infos = new Info[n + 1]; + for (int i = 1; i <= n; i++) { + infos[i] = process2(h.nexts.get(i - 1)); + ans.all += infos[i].all; + } + long[][] lefts = new long[n + 2][16]; + for (int i = 1; i <= n; i++) { + for (int status = 1; status < 16; status++) { + lefts[i][status] = lefts[i - 1][status] + infos[i].colors[status]; + } + } + long[][] rights = new long[n + 2][16]; + for (int i = n; i >= 1; i--) { + for (int status = 1; status < 16; status++) { + rights[i][status] = rights[i + 1][status] + infos[i].colors[status]; + } + } + for (int status = 1; status < 16; status++) { + // x : 0010 子:0001 10个 + // 0011 + 10个 + ans.colors[status | hs] += rights[1][status]; + } + // 头节点出发,全颜色搞定,100个,200 + ans.all += ans.colors[15] << 1; + for (int from = 1; from <= n; from++) { + for (int fromStatus = 1; fromStatus < 16; fromStatus++) { + for (int toStatus = 1; toStatus < 16; toStatus++) { + if ((fromStatus | toStatus | hs) == 15) { + ans.all += infos[from].colors[fromStatus] + * (lefts[from - 1][toStatus] + rights[from + 1][toStatus]); + } + } + } + } + } + return ans; + } + + // 最后的优化版本 + // 和方法二没有本质区别 + // 优化的点:每个状态需要和哪些状态结合,都放在辅助数组consider里 + public static long colors3(Node head) { + if (head == null) { + return 0; + } + return process3(head).all; + } + + public static int[][] consider = { {}, // 0 + { 14, 15 }, // 1 -> 0001 + { 13, 15 }, // 2 -> 0010 + { 12, 13, 14, 15 }, // 3 -> 0011 + { 11, 15 }, // 4 -> 0100 + { 10, 11, 14, 15 }, // 5 -> 0101 + { 9, 11, 13, 15 }, // 6 -> 0110 + { 8, 9, 10, 11, 12, 13, 14, 15 }, // 7 -> 0111 + { 7, 15 }, // 8 -> 1000 + { 6, 7, 14, 15 }, // 9 -> 1001 + { 5, 7, 13, 15 }, // 10 -> 1010 + { 4, 5, 6, 7, 12, 13, 14, 15 }, // 11 -> 1011 + { 3, 7, 11, 15 }, // 12 -> 1100 + { 2, 3, 6, 7, 10, 11, 14, 15 }, // 13 -> 1101 + { 1, 3, 5, 7, 9, 11, 13, 15 }, // 14 -> 1110 + { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 } // 15 -> 1111 + }; + + public static Info process3(Node h) { + Info ans = new Info(); + int hs = 1 << h.color; + ans.colors[hs] = 1; + if (!h.nexts.isEmpty()) { + int n = h.nexts.size(); + Info[] infos = new Info[n + 1]; + for (int i = 1; i <= n; i++) { + infos[i] = process3(h.nexts.get(i - 1)); + ans.all += infos[i].all; + } + long[][] lefts = new long[n + 2][16]; + for (int i = 1; i <= n; i++) { + for (int status = 1; status < 16; status++) { + lefts[i][status] = lefts[i - 1][status] + infos[i].colors[status]; + } + } + long[][] rights = new long[n + 2][16]; + for (int i = n; i >= 1; i--) { + for (int status = 1; status < 16; status++) { + rights[i][status] = rights[i + 1][status] + infos[i].colors[status]; + } + } + for (int status = 1; status < 16; status++) { + ans.colors[status | hs] += rights[1][status]; + } + ans.all += ans.colors[15] << 1; + for (int from = 1; from <= n; from++) { + for (int fromStatus = 1; fromStatus < 16; fromStatus++) { + for (int toStatus : consider[fromStatus | hs]) { + ans.all += infos[from].colors[fromStatus] + * (lefts[from - 1][toStatus] + rights[from + 1][toStatus]); + } + } + } + } + return ans; + } + + // 为了测试 + public static Node randomTree(int len, int childs) { + Node head = new Node((int) (Math.random() * 4)); + generate(head, len - 1, childs); + return head; + } + + // 为了测试 + public static void generate(Node pre, int restLen, int childs) { + if (restLen == 0) { + return; + } + int size = (int) (Math.random() * childs); + for (int i = 0; i < size; i++) { + Node next = new Node((int) (Math.random() * 4)); + generate(next, restLen - 1, childs); + pre.nexts.add(next); + } + } + + // 为了测试 + public static void printTree(Node head) { + System.out.print(head.color + " "); + if (!head.nexts.isEmpty()) { + System.out.print("( "); + for (Node next : head.nexts) { + printTree(next); + System.out.print(" , "); + } + System.out.print(") "); + } + } + + // 为了测试 + // 生成高度为9的满5叉树,每个节点的颜色在0~3上随机 + // 这棵树的节点个数已经达到5 * 10^5的规模 + public static Node randomTree() { + Queue curq = new LinkedList<>(); + Queue nexq = new LinkedList<>(); + Node head = new Node((int) (Math.random() * 4)); + curq.add(head); + for (int len = 1; len < 9; len++) { + while (!curq.isEmpty()) { + Node cur = curq.poll(); + for (int i = 0; i < 5; i++) { + Node next = new Node((int) (Math.random() * 4)); + cur.nexts.add(next); + nexq.add(next); + } + } + Queue tmp = nexq; + nexq = curq; + curq = tmp; + } + return head; + } + + // 为了测试 + public static void main(String[] args) { + int len = 6; + int childs = 6; + int testTime = 3000; + System.out.println("功能测试开始"); + for (int i = 0; i < testTime; i++) { + Node head = randomTree(len, childs); + int ans1 = colors1(head); + long ans2 = colors2(head); + long ans3 = colors3(head); + if (ans1 != ans2 || ans2 != ans3) { + System.out.println("出错了"); + printTree(head); + System.out.println(); + System.out.println(ans1); + System.out.println(ans2); + System.out.println(ans3); + break; + } + } + System.out.println("功能测试结束"); + + System.out.println("性能测试开始"); + + Node h = randomTree(); + System.out.println("节点数量达到 5*(10^5) 规模"); + long start; + long end; + + start = System.currentTimeMillis(); + long ans2 = colors2(h); + end = System.currentTimeMillis(); + System.out.println("方法二答案 : " + ans2 + ", 方法二运行时间 : " + (end - start) + " 毫秒"); + + start = System.currentTimeMillis(); + long ans3 = colors3(h); + end = System.currentTimeMillis(); + System.out.println("方法三答案 : " + ans3 + ", 方法三运行时间 : " + (end - start) + " 毫秒"); + + System.out.println("性能测试结束"); + } + +} diff --git a/算法周更班/class_2021_12_3_week/Code01_RightMoveInBinaryTree.java b/算法周更班/class_2021_12_3_week/Code01_RightMoveInBinaryTree.java new file mode 100644 index 0000000..b493b9f --- /dev/null +++ b/算法周更班/class_2021_12_3_week/Code01_RightMoveInBinaryTree.java @@ -0,0 +1,74 @@ +package class_2021_12_3_week; + +// 测试链接 : https://www.nowcoder.com/test/33701596/summary +// 本题目为第1题 +// 牛客网判题过程不好,卡常数了 +// 课上会说什么是"卡常数" +// 卡常数怎么反馈? +public class Code01_RightMoveInBinaryTree { + + // 这个类不需要提交 + public static class TreeNode { + int val = 0; + TreeNode left = null; + TreeNode right = null; + + public TreeNode(int val) { + this.val = val; + } + } + + // 提交下面的代码 + + public static TreeNode[] queue = new TreeNode[300000]; + + public static int[] ends = new int[50]; + + public static TreeNode cyclicShiftTree(TreeNode root, int k) { + int l = 0; + int r = 0; + queue[r++] = root; + int level = 0; + while (l != r) { + ends[level] = r; + while (l < ends[level]) { + TreeNode cur = queue[l++]; + if (cur != null) { + queue[r++] = cur.left; + queue[r++] = cur.right; + } + } + level++; + } + for (int i = level - 1; i > 0; i--) { + + // 当前层 : curLeft....curRight + // 3(null) 4(a) 5(null) 6(b) + // 下一层 :downLeft....downRight + // 7 8 9 10 + // downIndex : 下一层需要根据,k和下一层的长度,来右移。右移之后,从哪个位置开始,分配节点给当前层第一个不空的节点 + int downLeft = ends[i - 1]; + int downRight = ends[i] - 1; + int downRightSize = k % (downRight - downLeft + 1); + int downIndex = downRightSize == 0 ? downLeft : (downRight - downRightSize + 1); + int curLeft = i - 2 >= 0 ? ends[i - 2] : 0; + int curRight = ends[i - 1] - 1; + for (int j = curLeft; j <= curRight; j++) { + if (queue[j] != null) { + queue[j].left = queue[downIndex]; + downIndex = nextIndex(downIndex, downLeft, downRight); + queue[j].right = queue[downIndex]; + downIndex = nextIndex(downIndex, downLeft, downRight); + } + } + } + return root; + } + + // l......r i -> next index + // 4.....9 i = 7 8 9 4 + public static int nextIndex(int i, int l, int r) { + return i == r ? l : i + 1; + } + +} diff --git a/算法周更班/class_2021_12_3_week/Code02_BinaryNegate.java b/算法周更班/class_2021_12_3_week/Code02_BinaryNegate.java new file mode 100644 index 0000000..bf2e23e --- /dev/null +++ b/算法周更班/class_2021_12_3_week/Code02_BinaryNegate.java @@ -0,0 +1,25 @@ +package class_2021_12_3_week; + +//测试链接 : https://www.nowcoder.com/test/33701596/summary +//本题目为第2题 +public class Code02_BinaryNegate { + + public static String maxLexicographical(String num) { + char[] arr = num.toCharArray(); + int i = 0; + while (i < arr.length) { + if (arr[i] == '0') { + break; + } + i++; + } + while(i < arr.length) { + if(arr[i] == '1') { + break; + } + arr[i++] = '1'; + } + return String.valueOf(arr); + } + +} diff --git a/算法周更班/class_2021_12_3_week/Code03_OneCountsInKSystem.java b/算法周更班/class_2021_12_3_week/Code03_OneCountsInKSystem.java new file mode 100644 index 0000000..b7b6d2d --- /dev/null +++ b/算法周更班/class_2021_12_3_week/Code03_OneCountsInKSystem.java @@ -0,0 +1,58 @@ +package class_2021_12_3_week; + +// 测试链接 : https://www.nowcoder.com/test/33701596/summary +// 本题目为第3题 +// 核心方法,在大厂刷题班19节,第3题 +public class Code03_OneCountsInKSystem { + + public static long minM(int n, int k) { + int len = bits(n, k); + long l = 1; + long r = power(k, len + 1); + long ans = r; + while (l <= r) { + long m = l + ((r - l) >> 1); + if (ones(m, k) >= n) { + ans = m; + r = m - 1; + } else { + l = m + 1; + } + } + return ans; + } + + public static int bits(long num, int k) { + int len = 0; + while (num != 0) { + len++; + num /= k; + } + return len; + } + + public static long power(long base, int power) { + long ans = 1; + while (power != 0) { + if ((power & 1) != 0) { + ans *= base; + } + base *= base; + power >>= 1; + } + return ans; + } + + public static long ones(long num, int k) { + int len = bits(num, k); + if (len <= 1) { + return len; + } + long offset = power(k, len - 1); + long first = num / offset; + long curOne = first == 1 ? (num % offset) + 1 : offset; + long restOne = first * (len - 1) * (offset / k); + return curOne + restOne + ones(num % offset, k); + } + +} diff --git a/算法周更班/class_2021_12_3_week/Code04_CutOffTreesForGolfEvent.java b/算法周更班/class_2021_12_3_week/Code04_CutOffTreesForGolfEvent.java new file mode 100644 index 0000000..87cd3a5 --- /dev/null +++ b/算法周更班/class_2021_12_3_week/Code04_CutOffTreesForGolfEvent.java @@ -0,0 +1,79 @@ +package class_2021_12_3_week; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; + +// 测试链接 : https://leetcode.com/problems/cut-off-trees-for-golf-event/ +public class Code04_CutOffTreesForGolfEvent { + + public static int cutOffTree(List> forest) { + int n = forest.size(); + int m = forest.get(0).size(); + // [ [3,5,2], [1,9,4] , [2,6,10] ] + // 低 中 高 + ArrayList cells = new ArrayList<>(); + for (int i = 0; i < n; i++) { + for (int j = 0; j < m; j++) { + int val = forest.get(i).get(j); + if (val > 1) { + cells.add(new int[] { i, j, val }); + } + } + } + cells.sort((a, b) -> a[2] - b[2]); + int ans = 0, lastR = 0, lastC = 0; + for (int[] cell : cells) { + int step = bestWalk(forest, lastR, lastC, cell[0], cell[1]); + if (step == -1) { + return -1; + } + ans += step; + lastR = cell[0]; + lastC = cell[1]; + forest.get(lastR).set(lastC, 1); + } + return ans; + } + + public static int[] next = { -1, 0, 1, 0, -1 }; + + // 0 1 2 3 4 + // i + // 行 + next[i-1] + // 列 + next[i] + // i == 1 -> 上 + // i == 2 -> 右 + // i == 3 -> 下 + // i == 4 -> 左 + public static int bestWalk(List> forest, int sr, int sc, int tr, int tc) { + int n = forest.size(); + int m = forest.get(0).size(); + boolean[][] seen = new boolean[n][m]; + LinkedList deque = new LinkedList<>(); + deque.offerFirst(new int[] { 0, sr, sc }); + while (!deque.isEmpty()) { + int[] cur = deque.pollFirst(); + int step = cur[0], r = cur[1], c = cur[2]; + if (r == tr && c == tc) { + return step; + } + seen[r][c] = true; + for (int i = 1; i < 5; i++) { // (r,c) 上下左右,全试试! + int nr = r + next[i - 1]; + int nc = c + next[i]; + if (nr >= 0 && nr < n && nc >= 0 && nc < m && !seen[nr][nc] && forest.get(nr).get(nc) > 0) { + int[] move = { step + 1, nr, nc }; + // 更近的话 + if ((i == 1 && r > tr) || (i == 2 && c < tc) || (i == 3 && r < tr) || (i == 4 && c > tc)) { + deque.offerFirst(move); + } else { // 更远的话,放到尾部! + deque.offerLast(move); + } + } + } + } + return -1; + } + +} diff --git a/算法周更班/class_2021_12_3_week/Code05_MinContinuousFragment.java b/算法周更班/class_2021_12_3_week/Code05_MinContinuousFragment.java new file mode 100644 index 0000000..002800a --- /dev/null +++ b/算法周更班/class_2021_12_3_week/Code05_MinContinuousFragment.java @@ -0,0 +1,162 @@ +package class_2021_12_3_week; + +// 来自CMU入学申请考试 +// 给定一个长度为 N 的字符串 S,由字符'a'和'b'组成,空隙由 '?' 表示 +// 你的任务是用a字符或b字符替换每个间隙, +// 替换完成后想让连续出现同一种字符的最长子串尽可能短 +// 例如,S = "aa??bbb", +// 如果将"??"替换为"aa" ,即"aaaabbb",则由相等字符组成的最长子串长度为4 +// 如果将"??"替换为"ba" ,即"aababbb",则由相等字符组成的最长子串长度为3 +// 那么方案二是更好的结果,返回3 +// S的长度 <= 10^6 +public class Code05_MinContinuousFragment { + + // 暴力方法 + // 为了验证 + public static int minContinuous1(String s) { + if (s == null || s.length() == 0) { + return 0; + } + char[] str = s.toCharArray(); + return process1(str, 0); + } + + public static int process1(char[] str, int index) { + if (index == str.length) { + return maxLen(str); + } else { + if (str[index] != '?') { + return process1(str, index + 1); + } else { + str[index] = 'a'; + int p1 = process1(str, index + 1); + str[index] = 'b'; + int p2 = process1(str, index + 1); + str[index] = '?'; + return Math.min(p1, p2); + } + } + } + + // 正式方法 + // 时间复杂度O(N) + public static int minContinuous2(String s) { + if (s == null || s.length() == 0) { + return 0; + } + char[] str = s.toCharArray(); + int N = str.length; + int L = 0; + int R = -1; + for (int i = 0; i < N; i++) { + if (str[i] != '?') { + set(str, L, R); + L = i + 1; + R = i; + } else { + R++; + } + } + set(str, L, R); + // 下面的for循环,是单独处理,条件5) + for (int i = 1; i < N; i++) { + if (str[i] == '?') { + // baaaa?bbbbbbbba + for (L = i - 1; L >= 0 && str[L] == str[i - 1]; L--) + ; + for (R = i + 1; R < N && str[R] == str[i + 1]; R++) + ; + L = i - L - 1; + R = R - i - 1; + if (L <= R) { + str[i] = str[i - 1]; + } else { + str[i] = str[i + 1]; + } + } + } + return maxLen(str); + } + + // L...R 都是? + // 如果这一坨问号,满足1)2)3)4)中的一种,就填好 + // 如果满足5),就不填!a?b + public static void set(char[] str, int L, int R) { + int N = str.length; + if (L > R) { + return; + } + if (L == 0 && R == N - 1) { + for (int i = 0; i < N; i++) { + str[i] = (i & 1) == 0 ? 'a' : 'b'; + } + } else if (L == 0) { + for (int i = R; i >= 0; i--) { + str[i] = str[i + 1] == 'a' ? 'b' : 'a'; + } + } else if (R == N - 1) { + for (int i = L; i < str.length; i++) { + str[i] = str[i - 1] == 'a' ? 'b' : 'a'; + } + } else { + if (str[L - 1] == str[R + 1] || L != R) { + for (; L <= R; L++, R--) { + str[L] = str[L - 1] == 'a' ? 'b' : 'a'; + str[R] = str[R + 1] == 'a' ? 'b' : 'a'; + } + } + } + } + + public static int maxLen(char[] str) { + int ans = 1; + int cur = 1; + for (int i = 1; i < str.length; i++) { + if (str[i] != str[i - 1]) { + ans = Math.max(ans, cur); + cur = 1; + } else { + cur++; + } + } + ans = Math.max(ans, cur); + return ans; + } + + public static char[] arr = { 'a', 'b', '?' }; + + public static String randomString(int len) { + int N = (int) (Math.random() * (len + 1)); + char[] str = new char[N]; + for (int i = 0; i < N; i++) { + str[i] = arr[(int) (Math.random() * 3)]; + } + return String.valueOf(str); + } + + public static void main(String[] args) { + int len = 35; + int testTime = 10000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + String s = randomString(len); + int ans1 = minContinuous1(s); + int ans2 = minContinuous2(s); + if (ans1 != ans2) { + System.out.println(s); + System.out.println(ans1); + System.out.println(ans2); + } + } + System.out.println("测试结束"); + + len = 10000000; + String s = randomString(len); + long start = System.currentTimeMillis(); + minContinuous2(s); + long end = System.currentTimeMillis(); + System.out.println("运行时间(毫秒):" + (end - start)); + + } + +} diff --git a/算法周更班/class_2021_12_4_week/Code01_FiveNodesListNumbers.java b/算法周更班/class_2021_12_4_week/Code01_FiveNodesListNumbers.java new file mode 100644 index 0000000..82ed17e --- /dev/null +++ b/算法周更班/class_2021_12_4_week/Code01_FiveNodesListNumbers.java @@ -0,0 +1,58 @@ +package class_2021_12_4_week; + +import java.util.HashSet; + +// 来自美团 +// 给定一个无向图 +// 从任何一个点x出发,比如有一条路径: x -> a -> b -> c -> y +// 这条路径上有5个点并且5个点都不一样的话,我们说(x,a,b,c,y)是一条合法路径 +// 这条合法路径的代表,就是x,a,b,c,y所组成的集合,我们叫做代表集合 +// 如果从b到y,还有一条路径叫(b,a,c,x,y),那么(x,a,b,c,y)和(b,a,c,x,y)是同一个代表集合 +// 返回这个无向图中所有合法路径的代表集合数量 +// 题目给定点的数量n <= 15,边的数量m <= 60 +// 所有的点编号都是从0~n-1的 +public class Code01_FiveNodesListNumbers { + + // graph[i] = { a, b, c} 代表:点i直接相邻的节点有a,b,c + // graph[j] = { d } 代表:点j直接相邻的节点有d + // 所以二维数组graph可以表示无向图 + // 0 : + // 1 : + // 2 : + // n-1 : + public static int validPathSets(int[][] graph) { + int n = graph.length; + // 任何一个合法路径的集合,都被弄成了整数形式 + // 0010010011 -> int + // 甲 : 0011010011 + // 乙 : 0011010011 + // 丙 : 0011010011 + HashSet set = new HashSet<>(); + // 下面的过程:从每个点出发,0、1、2、3、。。。 + // 从x点出发,往外最多迈5步,所产生的所有路径,都要! + for (int from = 0; from < n; from++) { + dfs(0, 0, from, graph, set); + } + return set.size(); + } + + // int status -> 已经走过了哪些点的集合 -> 00001101 + // int len -> 已经往外几步了! + // int cur -> 当前来到的是几号点! + // int[][] graph -> 图 + // HashSet set -> 收集所有合法路径的点集合! + public static void dfs(int status, int len, int cur, int[][] graph, HashSet set) { + if ((status & (1 << cur)) == 0) { // 之前走过的点,不包括cur,迈上去! + len++; + status |= 1 << cur; + if (len == 5) { + set.add(status); + } else { + for (int next : graph[cur]) { + dfs(status, len, next, graph, set); + } + } + } + } + +} diff --git a/算法周更班/class_2021_12_4_week/Code02_MergeArea.java b/算法周更班/class_2021_12_4_week/Code02_MergeArea.java new file mode 100644 index 0000000..ea97aa0 --- /dev/null +++ b/算法周更班/class_2021_12_4_week/Code02_MergeArea.java @@ -0,0 +1,143 @@ +package class_2021_12_4_week; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +// 来自北京北明数科信息技术有限公司 +// area表示的是地区全路径,最多可能有6级,用分隔符连接,分隔符是 spliter, +// 分隔符是逗号 +// 例如: +// area = 中国,四川,成都 或者 中国,浙江,杭州 或者 中国,浙江,义乌 +// spliter = , +// count表示门店数 +// class AreaResource { +// String area; +// String spliter; +// long count; +// } + +// area = "中国,四川,成都" +// spliter = "," +// count = 10 + +// 现在需要把 List 进行字符串转换,供下游处理,需要做到同级的地域能合并 +// 比如 +// area为 中国,四川,成都 有10个门店 +// 中国,浙江,杭州 有25个门店 +// 中国,浙江,义乌 有22个门店 +// 中国,四川,成都 有25个门店 +// spliter为逗号 "," 最终转化成JSON的形式,并且同级的地域需要被合并,最终生成的JSON字符串如下所示 +// +// 返回: { +// "中国": +// {"四川":{"成都":35]}, +// "浙江":{"义乌":22,"杭州":25}}} +// 请实现下面的方法 public String mergeCount(List areas) +public class Code02_MergeArea { + + // 系统给你的原始类 + public static class AreaResource { + public String area; + public String spliter; + public long count; + + public AreaResource(String a, String s, long c) { + area = a; + spliter = s; + count = c; + } + } + + // 要实现的方法 + public static String mergeCount(List areas) { + Area all = new Area("", 0); + for (AreaResource r : areas) { + // 中国,四川,成都 , 10个门店 + String[] path = r.area.split(r.spliter); + // 中国 四川 成都 + long count = r.count; + f(path, 0, all, count); + } + return all.toString(); + } + + // [中国,四川,成都] + // 0 1 2 + public static void f(String[] path, int index, Area pre, long count) { + if (index == path.length) { + pre.count += count; + } else { + // 前一个节点 pre 中国 + // cur = 四川 + String cur = path[index]; + if (!pre.next.containsKey(cur)) { + pre.next.put(cur, new Area(cur, 0)); + } + f(path, index + 1, pre.next.get(cur), count); + } + } + + // 自己定义的!前缀树节点 + public static class Area { + // 地区名称:中国 + // 四川 + // 成都 + public String name; + // 中国 key : 省名 value: 下级节点 + public HashMap next; + public long count; + + public Area(String n, long c) { + name = n; + count = c; + next = new HashMap<>(); + } + + public String toString() { + StringBuilder ans = new StringBuilder(); + // : "" + // str = "中国": + // str = "成都":100 + if (!name.equals("")) { + ans.append("\"" + name + "\"" + ":"); + } + if (next.isEmpty()) { + ans.append(count); + } else { + // "中国":{ 四川如何如何,河南如何如何,江苏如何如何} + ans.append("{"); + for (Area child : next.values()) { + ans.append(child.toString() + ","); + } + ans.setCharAt(ans.length() - 1, '}'); + } + return ans.toString(); + } + } + + public static void main(String[] args) { + AreaResource a1 = new AreaResource("中国,四川,成都", ",", 10); + AreaResource a2 = new AreaResource("中国,浙江,杭州", ",", 50); + AreaResource a3 = new AreaResource("中国,浙江,杭州", ",", 25); + AreaResource a4 = new AreaResource("中国,浙江,义务", ",", 22); + AreaResource a5 = new AreaResource("中国,四川,成都", ",", 15); + AreaResource a6 = new AreaResource("中国,四川,攀枝花", ",", 12); + AreaResource a7 = new AreaResource("中国,浙江,宁波", ",", 16); + + List areas = new ArrayList<>(); + areas.add(a1); + areas.add(a2); + areas.add(a3); + areas.add(a4); + areas.add(a5); + areas.add(a6); + areas.add(a7); + + String ans = mergeCount(areas); + + System.out.println(ans); + + } + +} diff --git a/算法周更班/class_2021_12_4_week/Code03_HowManyObtuseAngles.java b/算法周更班/class_2021_12_4_week/Code03_HowManyObtuseAngles.java new file mode 100644 index 0000000..1cae781 --- /dev/null +++ b/算法周更班/class_2021_12_4_week/Code03_HowManyObtuseAngles.java @@ -0,0 +1,42 @@ +package class_2021_12_4_week; + +import java.util.Arrays; + +// 来自hulu +// 有一个以原点为圆心,半径为1的圆 +// 在这个圆的圆周上,有一些点 +// 因为所有的点都在圆周上,所以每个点可以有很简练的表达 +// 比如:用0来表示一个圆周上的点,这个点就在(1,0)位置 +// 比如:用6000来表示一个点,这个点是(1,0)点沿着圆周逆时针转60.00度之后所在的位置 +// 比如:用18034来表示一个点,这个点是(1,0)点沿着圆周逆时针转180.34度之后所在的位置 +// 这样一来,所有的点都可以用[0, 36000)范围上的数字来表示 +// 那么任意三个点都可以组成一个三角形,返回能组成钝角三角形的数量 +public class Code03_HowManyObtuseAngles { + + public static long obtuseAngles(int[] arr) { + // n长度的排序,O(N * logN) + // O(N) + int n = arr.length; + int m = n << 1; + int[] enlarge = new int[m]; + Arrays.sort(arr); + for (int i = 0; i < n; i++) { + enlarge[i] = arr[i]; + enlarge[i + n] = arr[i] + 36000; + } + long ans = 0; + // 这里不用二分查找(太慢),能做一个不回退的优化 + for (int L = 0, R = 0; L < n; L++) { + while (R < m && enlarge[R] - enlarge[L] < 18000) { + R++; + } + ans += num(R - L - 1); + } + return ans; + } + + public static long num(long nodes) { + return nodes < 2 ? 0 : ((nodes - 1) * nodes) >> 1; + } + +} diff --git a/算法周更班/class_2021_12_4_week/Code04_MaximumNumberOfVisiblePoints.java b/算法周更班/class_2021_12_4_week/Code04_MaximumNumberOfVisiblePoints.java new file mode 100644 index 0000000..9e05247 --- /dev/null +++ b/算法周更班/class_2021_12_4_week/Code04_MaximumNumberOfVisiblePoints.java @@ -0,0 +1,43 @@ +package class_2021_12_4_week; + +import java.util.Arrays; +import java.util.List; + +// 整个二维平面算是一张地图,给定[x,y],表示你站在x行y列 +// 你可以选择面朝的任何方向 +// 给定一个正数值angle,表示你视野的角度为 +// 这个角度内你可以看无穷远,这个角度外你看不到任何东西 +// 给定一批点的二维坐标,返回你在朝向最好的情况下,最多能看到几个点 +// 测试链接 : https://leetcode.com/problems/maximum-number-of-visible-points/ +public class Code04_MaximumNumberOfVisiblePoints { + + public static int visiblePoints(List> points, int angle, List location) { + int n = points.size(); + int a = location.get(0); + int b = location.get(1); + int zero = 0; + double[] arr = new double[n << 1]; + int m = 0; + for (int i = 0; i < n; i++) { + int x = points.get(i).get(0) - a; + int y = points.get(i).get(1) - b; + if (x == 0 && y == 0) { + zero++; + } else { + arr[m] = Math.toDegrees(Math.atan2(y, x)); + arr[m + 1] = arr[m] + 360; + m += 2; + } + } + Arrays.sort(arr, 0, m); + int max = 0; + for (int L = 0, R = 0; L < n; L++) { + while (R < m && arr[R] - arr[L] <= angle) { + R++; + } + max = Math.max(max, R - L); + } + return max + zero; + } + +} diff --git a/算法周更班/class_2021_12_4_week/Code05_SplitApples.java b/算法周更班/class_2021_12_4_week/Code05_SplitApples.java new file mode 100644 index 0000000..f8d6e42 --- /dev/null +++ b/算法周更班/class_2021_12_4_week/Code05_SplitApples.java @@ -0,0 +1,123 @@ +package class_2021_12_4_week; + +//有m个同样的苹果,认为苹果之间无差别 +//有n个同样的盘子,认为盘子之间也无差别 +//还有,比如5个苹果如果放进3个盘子, +//那么1、3、1和1、1、3和3、1、1的放置方法,也认为是一种方法 +//如上的设定下,返回有多少种放置方法 +//测试链接 : https://www.nowcoder.com/practice/bfd8234bb5e84be0b493656e390bdebf +//提交以下的code,提交时请把类名改成"Main" +import java.util.Arrays; +import java.util.Scanner; + +public class Code05_SplitApples { + + public static void main(String[] args) { + Scanner sc = new Scanner(System.in); + while (sc.hasNext()) { + int m = sc.nextInt(); + int n = sc.nextInt(); + int ways = ways3(m, n); + System.out.println(ways); + } + sc.close(); + } + + // 思路来自于分裂数问题 + // 体系学习班代码第22节,题目3,split number问题 + public static int ways1(int apples, int plates) { + return process1(1, apples, plates); + } + + // pre : 上一个盘子分到的苹果数量,当前的盘子分到的数量不能小于pre + // apples : 剩余的苹果数量 + // plates : 剩余的盘子数量 + // 在盘子够用的情况下,把苹果分完,有几种方法 + public static int process1(int pre, int apples, int plates) { + if (apples == 0) { + return 1; + } + // apples != 0 + if (plates == 0) { + return 0; + } + // apples != 0 && plates != 0 + if (pre > apples) { + return 0; + } + // apples != 0 && plates != 0 && pre <= apples + int way = 0; + // 之前的盘子分了3个苹果,现在还剩下8个苹果 + // 当前的盘子,可以装几个苹果:3、4、5、6、7、8 + for (int cur = pre; cur <= apples; cur++) { + way += process1(cur, apples - cur, plates - 1); + } + return way; + } + + // 新的尝试,最优解 + // 苹果有apples个,盘子有plates个 + // 返回有几种摆法 + // 如果苹果数为0,有1种摆法:什么也不摆 + // 如果苹果数不为0,但是盘子数为0,有0种摆法(做不到) + // 如果苹果数不为0,盘子数也不为0,进行如下的情况讨论: + // 假设苹果数为apples,盘子数为plates + // 可能性 1) apples < plates + // 这种情况下,一定有多余的盘子,这些盘子完全没用,所以砍掉 + // 后续是f(apples, apples) + // 可能性 2) apples >= plates + // 在可能性2)下,讨论摆法,有如下两种选择 + // 选择a) 不是所有的盘子都使用 + // 选择b) 就是所有的盘子都使用 + // 对于选择a),既然不是所有盘子都使用,那么后续就是f(apples, plates - 1) + // 意思是:既然不是所有盘子都使用,那盘子减少一个,然后继续讨论吧! + // 对于选择b),既然就是所有的盘子都使用,那么先把所有盘子都摆上1个苹果。 + // 剩余苹果数 = apples - plates + // 然后继续讨论,剩下的这些苹果,怎么摆进plates个盘子里, + // 所以后续是f(apples - plates, plates) + public static int ways2(int apples, int plates) { + if (apples == 0) { + return 1; + } + if (plates == 0) { + return 0; + } + if (plates > apples) { + return ways2(apples, apples); + } else { // apples >= plates; + return ways2(apples, plates - 1) + ways2(apples - plates, plates); + } + } + + // 上面最优解尝试的记忆化搜索版本 + public static int[][] dp = null; + + public static int ways3(int apples, int plates) { + if (dp == null) { + dp = new int[11][11]; + for (int i = 0; i <= 10; i++) { + Arrays.fill(dp[i], -1); + } + } + return process3(apples, plates, dp); + } + + public static int process3(int apples, int plates, int[][] dp) { + if (dp[apples][plates] != -1) { + return dp[apples][plates]; + } + int ans = 0; + if (apples == 0) { + ans = 1; + } else if (plates == 0) { + ans = 0; + } else if (plates > apples) { + ans = process3(apples, apples, dp); + } else { + ans = process3(apples, plates - 1, dp) + process3(apples - plates, plates, dp); + } + dp[apples][plates] = ans; + return ans; + } + +} \ No newline at end of file diff --git a/算法周更班/class_2021_12_5_week/Code01_LoudAndRich.java b/算法周更班/class_2021_12_5_week/Code01_LoudAndRich.java new file mode 100644 index 0000000..36fccd4 --- /dev/null +++ b/算法周更班/class_2021_12_5_week/Code01_LoudAndRich.java @@ -0,0 +1,68 @@ +package class_2021_12_5_week; + +import java.util.ArrayList; + +// 测试链接 : https://leetcode.com/problems/loud-and-rich/ +public class Code01_LoudAndRich { + + // richer[i] = {a, b} a比b更有钱 a -> b + // quiet[i] = k, i这个人安静值是k + public static int[] loudAndRich(int[][] richer, int[] quiet) { + int N = quiet.length; + // a -> b + // a -> c + // b -> c + // a : b c + // b : c + // nexts[0] = {5,7,3} + // 0 : 5 7 3 + // 5最没钱的, + // nexts[5] = { } + ArrayList> nexts = new ArrayList<>(); + for (int i = 0; i < N; i++) { + // 0 : {} + // 1 : {} + // n-1 : {} + nexts.add(new ArrayList<>()); + } + // 入度 + // 0 : 0 + // 1 : 2 + int[] degree = new int[N]; + for (int[] r : richer) { + // [a,b] a -> b + nexts.get(r[0]).add(r[1]); + degree[r[1]]++; + } + // 所有入度为0的点,入队列 + int[] zeroQueue = new int[N]; + int l = 0; + int r = 0; + for (int i = 0; i < N; i++) { + if (degree[i] == 0) { + zeroQueue[r++] = i; + } + } + // ans[i] = j : 比i有钱的所有人里,j最安静 + int[] ans = new int[N]; + for (int i = 0; i < N; i++) { + ans[i] = i; + } + while (l < r) { // 如果队列不空 + // 弹出一个入度为0的点 + int cur = zeroQueue[l++]; + // 1) 消除当前cur的影响! + for (int next : nexts.get(cur)) { + // cur : 比cur有钱,最安静的!ans[cur] + if (quiet[ans[next]] > quiet[ans[cur]]) { + ans[next] = ans[cur]; + } + if (--degree[next] == 0) { + zeroQueue[r++] = next; + } + } + } + return ans; + } + +} diff --git a/算法周更班/class_2021_12_5_week/Code02_DoAllJobs.java b/算法周更班/class_2021_12_5_week/Code02_DoAllJobs.java new file mode 100644 index 0000000..1081e2b --- /dev/null +++ b/算法周更班/class_2021_12_5_week/Code02_DoAllJobs.java @@ -0,0 +1,129 @@ +package class_2021_12_5_week; + +import java.util.Arrays; +import java.util.PriorityQueue; + +// 来自hulu +// 有n个人,m个任务,任务之间有依赖记录在int[][] depends里 +// 比如: depends[i] = [a, b],表示a任务依赖b任务的完成 +// 其中 0 <= a < m,0 <= b < m +// 1个人1天可以完成1个任务,每个人都会选当前能做任务里,标号最小的任务 +// 一个任务所依赖的任务都完成了,该任务才能开始做 +// 返回n个人做完m个任务,需要几天 +public class Code02_DoAllJobs { + + public static int days(int n, int m, int[][] depends) { + if (n < 1) { + return -1; + } + if (m <= 0) { + return 0; + } + // nexts[0] = {1,4} + int[][] nexts = nexts(depends, m); + int[] indegree = indegree(nexts, m); + // 工人队列! + PriorityQueue workers = new PriorityQueue<>(); + for (int i = 0; i < n; i++) { + workers.add(0); + } + // zeroIn : 放着工作,放着可以开始做的工作,不能做的任务,不在其中 + // 小根堆:标号小的任务,一定要先做! + PriorityQueue zeroIn = new PriorityQueue<>(); + for (int i = 0; i < m; i++) { + if (indegree[i] == 0) { + zeroIn.add(i); + } + } + // start[i] :i之前必须完成的任务,占了几天,导致i任务只能从那天开始! + int[] start = new int[m]; + // 完成所有任务的最大天数 + int finishAll = 0; + int done = 0; + while (!zeroIn.isEmpty()) { // 有任务可做 + // 当前可以做的任务中,标号最小的任务 + int job = zeroIn.poll(); + // 当前可用的工人里,最早醒的! + int wake = workers.poll(); + // job 何时完成呢? + // (工人醒来,开工时间)最晚的!+1 + int finish = Math.max(start[job], wake) + 1; + finishAll = Math.max(finishAll, finish); + done++; + // 消除影响 + for (int next : nexts[job]) { + start[next] = Math.max(start[next], finish); + if (--indegree[next] == 0) { + zeroIn.add(next); + } + } + workers.add(finish); + } + return done == m ? finishAll : -1; + } + + public static int[][] nexts(int[][] depends, int m) { + Arrays.sort(depends, (a, b) -> a[1] - b[1]); + int n = depends.length; + int[][] nexts = new int[m][0]; + if (n == 0) { + return nexts; + } + int size = 1; + for (int i = 1; i < n; i++) { + if (depends[i - 1][1] != depends[i][1]) { + int from = depends[i - 1][1]; + nexts[from] = new int[size]; + for (int k = 0, j = i - size; k < size; k++, j++) { + nexts[from][k] = depends[j][0]; + } + size = 1; + } else { + size++; + } + } + int from = depends[n - 1][1]; + nexts[from] = new int[size]; + for (int k = 0, j = n - size; k < size; k++, j++) { + nexts[from][k] = depends[j][0]; + } + return nexts; + } + + public static int[] indegree(int[][] nexts, int m) { + int[] indegree = new int[m]; + for (int i = 0; i < m; i++) { + for (int j = 0; j < nexts[i].length; j++) { + indegree[nexts[i][j]]++; + } + } + return indegree; + } + + public static void main(String[] args) { + // 2 -> 5 -> 6 + // | + // v + // 1 -> 4 -> 7 + // ^ + // | + // 0 -> 3 + // 两个人 + // {1,2} 工人队列 + // 0 : 干0号工作 ,1 + // 0 : 干1号工作 ,1 + // 1 : 干2号工作,2 + int[][] d = { + { 3, 0 }, + { 4, 1 }, + { 5, 2 }, + { 4, 3 }, + { 6, 5 }, + { 7, 4 }, + { 7, 6 } + }; + System.out.println(days(3, 8, d)); + System.out.println(days(2, 8, d)); + } + +} diff --git a/算法周更班/class_2021_12_5_week/Code03_WaysToBuildWall.java b/算法周更班/class_2021_12_5_week/Code03_WaysToBuildWall.java new file mode 100644 index 0000000..5cdf26b --- /dev/null +++ b/算法周更班/class_2021_12_5_week/Code03_WaysToBuildWall.java @@ -0,0 +1,64 @@ +package class_2021_12_5_week; + +// 来自hulu +// 你只有1*1、1*2、1*3、1*4,四种规格的砖块 +// 你想铺满n行m列的区域,规则如下: +// 1)不管那种规格的砖,都只能横着摆 +// 比如1*3这种规格的砖,3长度是水平方向,1长度是竖直方向 +// 2)会有很多方法铺满整个区域,整块区域哪怕有一点点不一样,就算不同的方法 +// 3)区域内部(不算区域整体的4条边界),不能有任何砖块的边界线,是从上一直贯穿到下的直线 +// 返回符合三条规则下,铺满n行m列的区域,有多少种不同的摆放方法 +public class Code03_WaysToBuildWall { + + public static long[] r = { 0, 1, 2, 4, 8 }; + + public static long ways(int n, int m) { + if (n <= 0 || m <= 1) { + return 1; + } + // len[i] = 一共有1行的情况下,列的长度为i的时候有几种摆法(所有,不分合法和非法) + long[] len = new long[m + 1]; + for (int i = 1; i <= Math.min(m, 4); i++) { + len[i] = r[i]; + } + for (int i = 5; i <= m; i++) { + len[i] = len[i - 1] + len[i - 2] + len[i - 3] + len[i - 4]; + } + // any[i] = 一共有n行的情况下,列的长度为i的时候有几种摆法(所有,不分合法和非法) + long[] any = new long[m + 1]; + for (int i = 1; i <= m; i++) { + // n * i的区域:总共的摆法!不区分合法、不合法 + any[i] = power(len[i], n); + } + // solid[i] = 一共有n行的情况下,列的长度为i的时候有几种合法的摆法 + long[] solid = new long[m + 1]; + solid[1] = 1; + for (int i = 2; i <= m; i++) { + long invalid = 0; + // N * i + // 1) (N * 1 合法) * (N * (i-1) 总共) + // 2) (N * 2 合法) * (N * (i-2) 总共) + // 3) (N * 3 合法) * (N * (i-3) 总共) + // + // j) (N * j 合法) * (N * (i-j) 总共) + for (int j = 1; j < i; j++) { + invalid += solid[j] * any[i - j]; + } + solid[i] = any[i] - invalid; + } + return solid[m]; + } + + public static long power(long base, int power) { + long ans = 1; + while (power != 0) { + if ((power & 1) != 0) { + ans *= base; + } + base *= base; + power >>= 1; + } + return ans; + } + +} diff --git a/算法周更班/class_2022_01_1_week/Code01_ABDisappear.java b/算法周更班/class_2022_01_1_week/Code01_ABDisappear.java new file mode 100644 index 0000000..c842d9d --- /dev/null +++ b/算法周更班/class_2022_01_1_week/Code01_ABDisappear.java @@ -0,0 +1,131 @@ +package class_2022_01_1_week; + +// 来自阿里 +// 给定一个只由'a'和'b'组成的字符串str, +// str中"ab"和"ba"子串都可以消除,消除之后剩下字符会重新靠在一起,继续出现可以消除的子串... +// 你的任务是决定一种消除的顺序,最后让str消除到尽可能的短 +// 返回尽可能的短的剩余字符串 +public class Code01_ABDisappear { + + // 暴力尝试,尝试所有的可能性 + // 所有情况都枚举,所有先后消除的顺序都暴力尝试 + public static String disappear1(String str) { + String ans = str; + for (int i = 1; i < str.length(); i++) { + // i == 1 0 1 是不是a和b都全,2... + // i == 2 1 2 是不是a和b都全,03.... + // i == 3 2 3 是不是a和b都全,014.... + // (i-1 i)扣掉! + boolean hasA = str.charAt(i - 1) == 'a' || str.charAt(i) == 'a'; + boolean hasB = str.charAt(i - 1) == 'b' || str.charAt(i) == 'b'; + if (hasA && hasB) { + String curAns = disappear1(str.substring(0, i - 1) + str.substring(i + 1)); + if (curAns.length() < ans.length()) { + ans = curAns; + } + } + } + return ans; + } + + // 好一点的方法尝试 + // 遇到第一个能消除的就消除, + // 剩下的字符串继续:遇到第一个能消除的就消除 + // 剩下的字符串继续:遇到第一个能消除的就消除 + // ... + public static String disappear2(String str) { + String ans = str; + for (int i = 1; i < str.length(); i++) { + boolean hasA = str.charAt(i - 1) == 'a' || str.charAt(i) == 'a'; + boolean hasB = str.charAt(i - 1) == 'b' || str.charAt(i) == 'b'; + if (hasA && hasB) { + return disappear2(str.substring(0, i - 1) + str.substring(i + 1)); + } + } + return ans; + } + + // 时间复杂度O(N) + // 利用栈 + public static String disappear3(String s) { + char[] str = s.toCharArray(); + int n = str.length; + // 用数组结构,自己实现栈 + int[] stack = new int[n]; + int size = 0; + for (int i = 0; i < n; i++) { + boolean hasA = size != 0 && str[stack[size - 1]] == 'a'; + boolean hasB = size != 0 && str[stack[size - 1]] == 'b'; + hasA |= str[i] == 'a'; + hasB |= str[i] == 'b'; + if (hasA && hasB) { + size--; + } else { + stack[size++] = i; + } + } + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < size; i++) { + builder.append(str[stack[i]]); + } + return builder.toString(); + } + + // 贪心,时间复杂度O(N) + // A的个数如果比B的个数多,那么一定只剩下A + // B的个数如果比A的个数多,那么一定只剩下B + public static String disappear4(String s) { + int count = 0; + for (char cha : s.toCharArray()) { + count += cha == 'a' ? 1 : -1; + } + StringBuilder builder = new StringBuilder(); + char rest = count > 0 ? 'a' : 'b'; + count = Math.abs(count); + for (int i = 0; i < count; i++) { + builder.append(rest); + } + return builder.toString(); + } + + // 为了测试 + public static String randomString(int len, int varible) { + char[] str = new char[len]; + for (int i = 0; i < str.length; i++) { + str[i] = (char) ((int) (Math.random() * varible) + 'a'); + } + return String.valueOf(str); + } + + // 为了测试 + // 对数器,三种截然不同的方法进行验证 + public static void main(String[] args) { + // 字符串最大长度,可以随意改变 + // 不过长度太大的话,方法一会超时 + int n = 14; + // 字符的种类,可以随意改变 + int v = 2; + // 测试次数 + int testTime = 20000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int len = (int) (Math.random() * n) + 1; + String test = randomString(len, v); + String ans1 = disappear1(test); + String ans2 = disappear2(test); + String ans3 = disappear3(test); + String ans4 = disappear4(test); + if (!ans1.equals(ans2) || !ans1.equals(ans3) || !ans1.equals(ans4)) { + System.out.println(test); + System.out.println(ans1); + System.out.println(ans2); + System.out.println(ans3); + System.out.println(ans4); + System.out.println("出错了!"); + break; + } + } + System.out.println("测试结束"); + } + +} diff --git a/算法周更班/class_2022_01_1_week/Code02_CatAndMouse.java b/算法周更班/class_2022_01_1_week/Code02_CatAndMouse.java new file mode 100644 index 0000000..2803f1a --- /dev/null +++ b/算法周更班/class_2022_01_1_week/Code02_CatAndMouse.java @@ -0,0 +1,233 @@ +package class_2022_01_1_week; + +import java.util.ArrayList; +import java.util.Arrays; + +// 测试链接 : https://leetcode.com/problems/cat-and-mouse/ +public class Code02_CatAndMouse { + + // 不贪心,就递归 + 记忆化搜索 + public static int catMouseGame1(int[][] graph) { + int n = graph.length; + // 9 : 2 * 8 * 9 再大,平局 + int limit = ((n * (n - 1)) << 1) + 1; + int[][][] dp = new int[n][n][limit]; + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + Arrays.fill(dp[i][j], -1); + } + } + return process(graph, limit, 2, 1, 1, dp); + } + + // 贪心 + 递归 + 记忆化搜索 + // 但是不对! + // 再次强调!不要去证明! + public static int catMouseGame2(int[][] graph) { + int n = graph.length; + // 这里! + // int limit = (n << 1) + 2; 还会出错,但是概率很小,需要多跑几次 + // int limit = (n << 1) + 3; 就没错了,或者说,概率小到很难重现 + // 为啥?我屌你为啥! + // 讲了这节课之后,leetcode又增加了测试用例 + // 导致 int limit = (n << 1) + 3; 也会出错 + // 改成如下的+12,可以通过,但是,如果去人为构造错误用例,一定还会出现错误 + int limit = (n << 1) + 12; + int[][][] dp = new int[n][n][limit]; + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + Arrays.fill(dp[i][j], -1); + } + } + return process(graph, limit, 2, 1, 1, dp); + } + + // dp[][][] 傻缓存! + // dp[cat][mouse][turn] == -1 这个状态之前没算过! + // dp[cat][mouse][turn] == 0 这个状态之前算过!平局! + // dp[cat][mouse][turn] == 1 这个状态之前算过!老鼠赢! + // dp[cat][mouse][turn] == 2 这个状态之前算过!猫赢! + // 固定参数!轮数不要超过limit!如果超过,就算平局! + public static int process(int[][] graph, int limit, int cat, int mouse, int turn, int[][][] dp) { + if (turn == limit) { + return 0; + } + if (dp[cat][mouse][turn] != -1) { + return dp[cat][mouse][turn]; + } + int ans = 0; + if (cat == mouse) { + ans = 2; + } else if (mouse == 0) { + ans = 1; + } else { + if ((turn & 1) == 1) { // 老鼠回合 + ans = 2; + for (int next : graph[mouse]) { + int p = process(graph, limit, cat, next, turn + 1, dp); + ans = p == 1 ? 1 : (p == 0 ? 0 : ans); + if (ans == 1) { + break; + } + } + } else { // 猫回合 + ans = 1; + for (int next : graph[cat]) { + if (next != 0) { + int p = process(graph, limit, next, mouse, turn + 1, dp); + ans = p == 2 ? 2 : (p == 0 ? 0 : ans); + if (ans == 2) { + break; + } + } + } + } + } + dp[cat][mouse][turn] = ans; + return ans; + } + + // 为了测试 + // 暴力尝试 + public static int right(int[][] graph) { + int n = graph.length; + boolean[][][] path = new boolean[n][n][2]; + return win(graph, 2, 1, 1, path); + } + + // 暴力尝试 + // 返回值:int + // 0 : 平局 + // 1 : 老鼠赢 + // 2 : 猫赢 + // int[][] graph + // 0 : {3, 7, 9} + // 1 : + // 2 : + // 3 : {0, } + // ... + // 7 : {0, } + // ... + // 9 : {0, } + // 猫此时的位置 -> cat + // mouse + // turn == 1 老鼠的回合 turn == 0 猫的回合 + // 当第一次出现,在老鼠的回合,猫在5位置 ,老鼠在7位置 + // path[cat][mouse][1] = false + // 不是第一次出现 path[cat][mouse][1] = true + public static int win(int[][] graph, int cat, int mouse, int turn, boolean[][][] path) { + if (path[cat][mouse][turn]) { // 之前来到过这个状态!平局! + return 0; + } + // 没来过! + path[cat][mouse][turn] = true; + int ans = 0; + if (cat == mouse) { + ans = 2; + } else if (mouse == 0) { + ans = 1; + } else { // 游戏继续 + if (turn == 1) { // 老鼠回合 + // 最差情况,是猫赢! + ans = 2; + for (int next : graph[mouse]) { + int p = win(graph, cat, next, 0, path); + // p的返回值如果是1,代表老鼠赢,那么最差结果ans = 1,直接返回! + ans = p == 1 ? 1 : (p == 0 ? 0 : ans); + if (ans == 1) { + break; + } + } + } else { // 猫回合 + ans = 1; + for (int next : graph[cat]) { + if (next != 0) { + int p = win(graph, next, mouse, turn ^ 1, path); + ans = p == 2 ? 2 : (p == 0 ? 0 : ans); + if (ans == 2) { + break; + } + } + } + } + } + path[cat][mouse][turn] = false; + return ans; + } + + // 为了测试 + public static int[][] randomGraph(int n) { + ArrayList> g = new ArrayList<>(); + for (int i = 0; i < n; i++) { + g.add(new ArrayList<>()); + } + for (int i = 0; i < n; i++) { + for (int j = i + 1; j < n; j++) { + if (Math.random() > 0.5) { + g.get(i).add(j); + g.get(j).add(i); + } + } + } + int[][] graph = new int[n][]; + for (int i = 0; i < n; i++) { + int m = g.get(i).size(); + graph[i] = new int[m]; + for (int j = 0; j < m; j++) { + graph[i][j] = g.get(i).get(j); + } + } + return graph; + } + + // 为了测试 + public static void main(String[] args) { + System.out.println("证什么证!?对数器万岁!"); + int N = 7; + int testTime = 3000000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int n = (int) (Math.random() * N) + 3; + int[][] graph = randomGraph(n); + int ans1 = catMouseGame1(graph); + int ans2 = catMouseGame2(graph); + if (ans1 != ans2) { + for (int row = 0; row < graph.length; row++) { + System.out.print(row + " : "); + for (int next : graph[row]) { + System.out.print(next + " "); + } + System.out.println(); + } + System.out.println("ans1 : " + ans1); + System.out.println("ans2 : " + ans2); + System.out.println("出错了!"); + break; + } + } + System.out.println("测试结束"); + + // 给你记录一个错误的例子 + int[][] graph = { + // 0 : + { 2, 6, 7 }, + // 1 : + { 3, 4, 5, 7 }, + // 2 : + { 0, 3, 4, 7 }, + // 3 : + { 1, 2, 5, 6 }, + // 4 : + { 1, 2, 5, 7 }, + // 5 : + { 1, 3, 4, 6 }, + // 6 : + { 0, 3, 5, 7 }, + // 7 : + { 0, 1, 2, 4, 6 } }; + System.out.println(catMouseGame1(graph)); + System.out.println(catMouseGame2(graph)); + + } + +} diff --git a/算法周更班/class_2022_01_1_week/Code03_MaximumScoreFromPerformingMultiplicationOperations.java b/算法周更班/class_2022_01_1_week/Code03_MaximumScoreFromPerformingMultiplicationOperations.java new file mode 100644 index 0000000..9eb588b --- /dev/null +++ b/算法周更班/class_2022_01_1_week/Code03_MaximumScoreFromPerformingMultiplicationOperations.java @@ -0,0 +1,58 @@ +package class_2022_01_1_week; + +// 测试链接 : https://leetcode.com/problems/maximum-score-from-performing-multiplication-operations/ +public class Code03_MaximumScoreFromPerformingMultiplicationOperations { + + // B数组消耗完之前,A数组不会耗尽,题目输入保证的! + // A[left...right] + // B[0..i-1]已经消耗完了!B[i...m-1] + // 直到把B数组消耗完,能获得的最大分数返回 + public static int zuo(int[] A, int[] B, int left, int right) { + int leftAlready = left; + int rightAlready = A.length - right - 1; + int i = leftAlready + rightAlready; + if (i == B.length) { + return 0; + } + // 没消耗完 + int p1 = A[left] * B[i] + zuo(A, B, left + 1, right); + int p2 = A[right] * B[i] + zuo(A, B, left, right - 1); + return Math.max(p1, p2); + } + + public static int maximumScore1(int[] A, int[] B) { + if (A == null || A.length == 0 || B == null || B.length == 0 || A.length < B.length) { + return 0; + } + return process1(A, B, 0, A.length - 1); + } + + public static int process1(int[] A, int[] B, int L, int R) { + int indexB = L + A.length - R - 1; + if (indexB == B.length) { + return 0; + } else { + int p1 = A[L] * B[indexB] + process1(A, B, L + 1, R); + int p2 = A[R] * B[indexB] + process1(A, B, L, R - 1); + return Math.max(p1, p2); + } + } + + public static int maximumScore2(int[] A, int[] B) { + if (A == null || A.length == 0 || B == null || B.length == 0 || A.length < B.length) { + return 0; + } + int N = A.length; + int M = B.length; + int[][] dp = new int[M + 1][M + 1]; + for (int L = M - 1; L >= 0; L--) { + for (int j = L + 1; j <= M; j++) { + int R = N - M + j - 1; + int indexB = L + N - R - 1; + dp[L][j] = Math.max(A[L] * B[indexB] + dp[L + 1][j], A[R] * B[indexB] + dp[L][j - 1]); + } + } + return dp[0][M]; + } + +} diff --git a/算法周更班/class_2022_01_1_week/Code04_MinDistanceFromLeftUpToRightDownWalk4Directions.java b/算法周更班/class_2022_01_1_week/Code04_MinDistanceFromLeftUpToRightDownWalk4Directions.java new file mode 100644 index 0000000..9f2f6ff --- /dev/null +++ b/算法周更班/class_2022_01_1_week/Code04_MinDistanceFromLeftUpToRightDownWalk4Directions.java @@ -0,0 +1,119 @@ +package class_2022_01_1_week; + +import java.util.PriorityQueue; + +// 来自学员问题 +// 给定一个二维数组,其中全是非负数 +// 每一步都可以往上、下、左、右四个方向运动 +// 走过的路径,会沿途累加数字 +// 返回从左下角走到右下角的累加和最小的多少 +public class Code04_MinDistanceFromLeftUpToRightDownWalk4Directions { + + public static int bestWalk1(int[][] map) { + int n = map.length; + int m = map[0].length; + int step = n * m - 1; + return process(map, n, m, step, 0, 0, 0, 0); + } + + public static int process(int[][] map, int n, int m, int limit, int step, int row, int col, int cost) { + if (row < 0 || row == n || col < 0 || col == m || step > limit) { + return Integer.MAX_VALUE; + } + if (row == n - 1 && col == m - 1) { + return cost + map[row][col]; + } + cost += map[row][col]; + int p1 = process(map, n, m, limit, step + 1, row - 1, col, cost); + int p2 = process(map, n, m, limit, step + 1, row + 1, col, cost); + int p3 = process(map, n, m, limit, step + 1, row, col - 1, cost); + int p4 = process(map, n, m, limit, step + 1, row, col + 1, cost); + return Math.min(Math.min(p1, p2), Math.min(p3, p4)); + } + + public static int bestWalk2(int[][] map) { + + int n = map.length; + int m = map[0].length; + // 堆 + // 每一个对象,都是一个小数组 + // {dis, row, col} + // 0 1 2 + PriorityQueue heap = new PriorityQueue<>((a, b) -> a[0] - b[0]); + // X,0,1 已经弹出了! 以后在遇到(0,1)的事情,不管! + // poped记录哪些位置弹出,哪些没有! + boolean[][] poped = new boolean[n][m]; + heap.add(new int[] { map[0][0], 0, 0 }); + int ans = 0; + while (!heap.isEmpty()) { + int[] cur = heap.poll(); + int dis = cur[0]; + int row = cur[1]; + int col = cur[2]; + if (poped[row][col]) { + continue; + } + // 接下来就是要处理这个位置了! + poped[row][col] = true; + if (row == n - 1 && col == m - 1) { + ans = dis; + break; + } + add(dis, row - 1, col, n, m, map, poped, heap); + add(dis, row + 1, col, n, m, map, poped, heap); + add(dis, row, col - 1, n, m, map, poped, heap); + add(dis, row, col + 1, n, m, map, poped, heap); + } + return ans; + } + + public static void add(int pre, int row, int col, int n, int m, int[][] map, boolean[][] used, + PriorityQueue heap) { + if (row >= 0 && row < n && col >= 0 && col < m && !used[row][col]) { + heap.add(new int[] { pre + map[row][col], row, col }); + } + } + + // 为了测试 + public static int[][] randomMatrix(int n, int m, int v) { + int[][] ans = new int[n][m]; + for (int i = 0; i < n; i++) { + for (int j = 0; j < m; j++) { + ans[i][j] = (int) (Math.random() * v); + } + } + return ans; + } + + // 为了测试 + public static void main(String[] args) { + int n = 4; + int m = 4; + int v = 10; + int testTime = 10; + System.out.println("功能测试开始"); + for (int i = 0; i < testTime; i++) { + int[][] map = randomMatrix(n, m, v); + int ans1 = bestWalk1(map); + int ans2 = bestWalk2(map); + if (ans1 != ans2) { + System.out.println("出错了!"); + } + } + System.out.println("功能测试结束"); + + n = 1000; + m = 1000; + v = 100; + int[][] map = randomMatrix(n, m, v); + System.out.println("性能测试开始"); + System.out.println("数据规模 : " + n + " * " + m); + System.out.println("值的范围 : 0 ~ " + v); + long start = System.currentTimeMillis(); + bestWalk2(map); + long end = System.currentTimeMillis(); + System.out.println("运行时间(毫秒) : " + (end - start)); + System.out.println("性能测试结束"); + } + +} diff --git a/算法周更班/class_2022_01_1_week/Problem_0913_CatAndMouse.java b/算法周更班/class_2022_01_1_week/Problem_0913_CatAndMouse.java new file mode 100644 index 0000000..a02a782 --- /dev/null +++ b/算法周更班/class_2022_01_1_week/Problem_0913_CatAndMouse.java @@ -0,0 +1,93 @@ +package class_2022_01_1_week; + +import java.util.ArrayList; + +// 本题是Code02_CatAndMoused的补充code +// leetcode官网增加了测试用例 +// 进一步说明,贪心的方式其实是没有道理的 +// 不过原来贪心的方法,把源代码第35行 +// int limit = (n << 1) + 3; +// 改成: +// int limit = (n << 1) + 12; +// 依然继续能通过 +// 但是贪心依然是没道理的,人为构造的话总能找出反例来 +// 所以补充了一个拓扑排序的解法 +// 这个方法,没在课上讲,有空我们补充一下 +public class Problem_0913_CatAndMouse { + + public static final int MOUSE_TURN = 0, CAT_TURN = 1; + public static final int DRAW = 0, MOUSE_WIN = 1, CAT_WIN = 2; + + public int catMouseGame(int[][] graph) { + int n = graph.length; + int[][][] indegree = new int[n][n][2]; + for (int i = 0; i < n; i++) { + for (int j = 1; j < n; j++) { + indegree[i][j][MOUSE_TURN] = graph[i].length; + indegree[i][j][CAT_TURN] = graph[j].length; + } + } + for (int i = 0; i < n; i++) { + for (int node : graph[0]) { + indegree[i][node][CAT_TURN]--; + } + } + int[][][] ans = new int[n][n][2]; + int[][] queue = new int[n * n * 2][]; + int left = 0; + int right = 0; + for (int j = 1; j < n; j++) { + ans[0][j][MOUSE_TURN] = MOUSE_WIN; + ans[0][j][CAT_TURN] = MOUSE_WIN; + queue[right++] = new int[] { 0, j, MOUSE_TURN }; + queue[right++] = new int[] { 0, j, CAT_TURN }; + } + for (int i = 1; i < n; i++) { + ans[i][i][MOUSE_TURN] = CAT_WIN; + ans[i][i][CAT_TURN] = CAT_WIN; + queue[right++] = new int[] { i, i, MOUSE_TURN }; + queue[right++] = new int[] { i, i, CAT_TURN }; + } + while (left != right) { + int[] cur = queue[left++]; + int mouse = cur[0], cat = cur[1], turn = cur[2]; + int curAns = ans[mouse][cat][turn]; + for (int[] prevState : preStatus(graph, mouse, cat, turn)) { + int prevMouse = prevState[0], prevCat = prevState[1], prevTurn = prevState[2]; + if (ans[prevMouse][prevCat][prevTurn] == DRAW) { + boolean canWin = (curAns == MOUSE_WIN && prevTurn == MOUSE_TURN) + || (curAns == CAT_WIN && prevTurn == CAT_TURN); + if (canWin) { + ans[prevMouse][prevCat][prevTurn] = curAns; + queue[right++] = new int[] { prevMouse, prevCat, prevTurn }; + } else { + if (--indegree[prevMouse][prevCat][prevTurn] == 0) { + int lose = prevTurn == MOUSE_TURN ? CAT_WIN : MOUSE_WIN; + ans[prevMouse][prevCat][prevTurn] = lose; + queue[right++] = new int[] { prevMouse, prevCat, prevTurn }; + } + } + } + } + } + return ans[1][2][MOUSE_TURN]; + } + + public ArrayList preStatus(int[][] graph, int mouse, int cat, int turn) { + ArrayList prevStates = new ArrayList(); + int prevTurn = turn == MOUSE_TURN ? CAT_TURN : MOUSE_TURN; + if (prevTurn == MOUSE_TURN) { + for (int prev : graph[mouse]) { + prevStates.add(new int[] { prev, cat, prevTurn }); + } + } else { + for (int prev : graph[cat]) { + if (prev != 0) { + prevStates.add(new int[] { mouse, prev, prevTurn }); + } + } + } + return prevStates; + } + +} diff --git a/算法周更班/class_2022_01_2_week/Code01_StringCounts.java b/算法周更班/class_2022_01_2_week/Code01_StringCounts.java new file mode 100644 index 0000000..dd029a7 --- /dev/null +++ b/算法周更班/class_2022_01_2_week/Code01_StringCounts.java @@ -0,0 +1,13 @@ +package class_2022_01_2_week; + +// 给定一个非常大的List list +// 每一个字符串类似 : "hello,world,have,hello,world" +// 这一个字符串中,有2个hello,2个world,1个have +// 请设计一种多线程处理方案,统计list中每一个字符串,切分出来的单词数量,并且汇总 +// 最终返回一个HashMap表示每个字符串在list中一共出现几次 +public class Code01_StringCounts { + + // 多线程设计 + 算法 + // 本题没有代码实现,会在课上讲述思路 + +} diff --git a/算法周更班/class_2022_01_2_week/Code02_BrickAll.java b/算法周更班/class_2022_01_2_week/Code02_BrickAll.java new file mode 100644 index 0000000..c6c1cc9 --- /dev/null +++ b/算法周更班/class_2022_01_2_week/Code02_BrickAll.java @@ -0,0 +1,49 @@ +package class_2022_01_2_week; + +import java.util.Arrays; + +// 给定一个正数数组arr,其中每个值代表砖块长度 +// 所有砖块等高等宽,只有长度有区别 +// 每一层可以用1块或者2块砖来摆 +// 要求每一层的长度一样 +// 要求必须使用所有的砖块 +// 请问最多摆几层 +// 如果无法做到,返回-1 +public class Code02_BrickAll { + + public static int maxLevels(int[] arr) { + if (arr == null) { + return 0; + } + int n = arr.length; + if (n < 2) { + return n; + } + Arrays.sort(arr); + int p1 = levels(arr, arr[n - 1]); + int p2 = levels(arr, arr[n - 1] + arr[0]); + return Math.max(p1, p2); + } + + // 要求所有砖必须都使用,并且每一层最多两块砖,并且每一层长度都是len + // 返回能摆几层,如果无法做到返回-1 + public static int levels(int[] arr, int len) { + int ans = 0; + int L = 0; + int R = arr.length - 1; + while (L <= R) { + if (arr[R] == len) { + R--; + ans++; + } else if (L < R && arr[L] + arr[R] == len) { + L++; + R--; + ans++; + } else { + return -1; + } + } + return ans; + } + +} diff --git a/算法周更班/class_2022_01_2_week/Code03_StringNumberConvertBinaryAndHexadecimal.java b/算法周更班/class_2022_01_2_week/Code03_StringNumberConvertBinaryAndHexadecimal.java new file mode 100644 index 0000000..fbccc34 --- /dev/null +++ b/算法周更班/class_2022_01_2_week/Code03_StringNumberConvertBinaryAndHexadecimal.java @@ -0,0 +1,119 @@ +package class_2022_01_2_week; + +// 来自兴业数金 +// 给定一个字符串形式的数,比如"3421"或者"-8731" +// 如果这个数不在-32768~32767范围上,那么返回"NODATA" +// 如果这个数在-32768~32767范围上,那么这个数就没有超过16个二进制位所能表达的范围 +// 返回这个数的2进制形式的字符串和16进制形式的字符串,用逗号分割 +public class Code03_StringNumberConvertBinaryAndHexadecimal { + + // 请保证输入的num字符串一定是数字的形式 + public static String convert(String num) { + // 因为-32768~32767所有的数,最多6个字符,所以超过就返回"NODATA" + if (num == null || num.length() == 0 || num.length() > 6) { + return "NODATA"; + } + // 既然长度不超过6,那么转成整数一定不会有问题 + // 当然你也可以自己写这个转化过程,这个是比较简单的 + int n = Integer.valueOf(num); + // 如果转换完成后超过了范围,那么返回"NODATA" + if (n < -32768 || n > 32767) { + return "NODATA"; + } + // 接下来n就是一个在范围上的数字 + // 我们要取出16位信息(info),这包括: + // 提取出n的14位~0位的信息 : 也就是(n & 65535) + // 提取出第15位符号位信息 : 如果n<0,第15位就是1,如果n>=0第15位就是0 + // 然后把(15位)和(14位~0位),或在一起 + // 比如5323,32位二进制形式如下: + // 00000000000000000001010011001011 + // 14位~0位是: + // _________________001010011001011 + // 15位符号位应该是0: + // ________________0_______________ + // 所以info应该是: + // ________________0001010011001011 + // + // 再比如-6725,32位二进制形式如下: + // 11111111111111111110010110111011 + // 14位~0位是: + // _________________110010110111011 + // 15位符号位应该是1: + // ________________1_______________ + // 所以info应该是: + // ________________1110010110111011 + int info = (n < 0 ? (1 << 15) : 0) | (n & 65535); + // 此时info的15位~0位,就是我们要的;info的更高位完全没用 + StringBuilder builder = new StringBuilder(); + // 依次提取二进制信息很简单 + for (int i = 15; i >= 0; i--) { + builder.append((info & (1 << i)) != 0 ? '1' : '0'); + } + builder.append(",0x"); + // 依次提取16进制的时候,每4位提取 + // 依次提取4位信息的时候 + // 0000 -> 0 -> '0' + // 0001 -> 1 -> '1' + // ... + // 1001 -> 9 -> '9' + // 1010 -> 10 -> 'a' + // 1011 -> 11 -> 'b' + // 1100 -> 12 -> 'c' + // 1101 -> 13 -> 'd' + // 1110 -> 14 -> 'e' + // 1111 -> 15 -> 'f' + for (int i = 12; i >= 0; i -= 4) { + // 1111 << 12 + // ( info & (1111 << 12) ) >> 12 + // ( info & (1111 << 8)) >> 8 + // ( info & (1111 << 4)) >> 4 + // ( info & (1111 << 0)) >> 0 + int cur = (info & (15 << i)) >> i; + if (cur < 10) { + builder.append(cur); + } else { + switch (cur) { + case 10: + builder.append('a'); + break; + case 11: + builder.append('b'); + break; + case 12: + builder.append('c'); + break; + case 13: + builder.append('d'); + break; + case 14: + builder.append('e'); + break; + default: + builder.append('f'); + } + } + } + return builder.toString(); + } + + public static void main(String[] args) { + String num1 = "0"; + System.out.println(convert(num1)); + + String zuo = "457"; + System.out.println(convert(zuo)); + + String num2 = "-32768"; + System.out.println(convert(num2)); + + String num3 = "32768"; + System.out.println(convert(num3)); + + String num4 = "32767"; + System.out.println(convert(num4)); + + String num5 = "-1"; + System.out.println(convert(num5)); + } + +} diff --git a/算法周更班/class_2022_01_2_week/Code04_MinimumOperationsToMakeTheArrayKIncreasing.java b/算法周更班/class_2022_01_2_week/Code04_MinimumOperationsToMakeTheArrayKIncreasing.java new file mode 100644 index 0000000..06aebcc --- /dev/null +++ b/算法周更班/class_2022_01_2_week/Code04_MinimumOperationsToMakeTheArrayKIncreasing.java @@ -0,0 +1,49 @@ +package class_2022_01_2_week; + +// 来自Leetcode周赛第5题 +// 测试链接 : https://leetcode.com/problems/minimum-operations-to-make-the-array-k-increasing/ +public class Code04_MinimumOperationsToMakeTheArrayKIncreasing { + + public static int kIncreasing(int[] arr, int k) { + int n = arr.length; + // a / b = 向下取整 + // a / b 向上取整 -> (a + b - 1) / b + int[] help = new int[(n + k - 1) / k]; + int ans = 0; + for (int i = 0; i < k; i++) { + ans += need(arr, help, n, i, k); + } + return ans; + } + + // arr[start , start + k, start + 2k, start + 3k,....] + // 辅助数组help,为了求最长递增子序列,需要开辟的空间,具体看体系学习班 + // 上面的序列,要改几个数,能都有序! + public static int need(int[] arr, int[] help, int n, int start, int k) { + int j = 0; + int size = 0; + for (; start < n; start += k, j++) { + size = insert(help, size, arr[start]); + } + return j - size; + } + + public static int insert(int[] help, int size, int num) { + int l = 0; + int r = size - 1; + int m = 0; + int ans = size; + while (l <= r) { + m = (l + r) / 2; + if (help[m] > num) { + ans = m; + r = m - 1; + } else { + l = m + 1; + } + } + help[ans] = num; + return ans == size ? size + 1 : size; + } + +} diff --git a/算法周更班/class_2022_01_2_week/Code05_MagicTowSubarrayMakeMaxSum.java b/算法周更班/class_2022_01_2_week/Code05_MagicTowSubarrayMakeMaxSum.java new file mode 100644 index 0000000..d169640 --- /dev/null +++ b/算法周更班/class_2022_01_2_week/Code05_MagicTowSubarrayMakeMaxSum.java @@ -0,0 +1,129 @@ +package class_2022_01_2_week; + +// 来自美团 +// 小美有一个长度为n的数组,为了使得这个数组的和尽量大,她向会魔法的小团进行求助 +// 小团可以选择数组中至多两个不相交的子数组,并将区间里的数全都变为原来的10倍 +// 小团想知道他的魔法最多可以帮助小美将数组的和变大到多少? +public class Code05_MagicTowSubarrayMakeMaxSum { + + // 比较暴力的方法 + // 用于对数器 + public static int maxSum1(int[] arr) { + int n = arr.length; + int[] presum = presum(arr); + int ans = maxSumAtMostOneRangeMagic(presum, 0, n - 1); + for (int split = 0; split < n - 1; split++) { + ans = Math.max(ans, + maxSumAtMostOneRangeMagic(presum, 0, split) + maxSumAtMostOneRangeMagic(presum, split + 1, n - 1)); + } + return ans; + } + + public static int[] presum(int[] arr) { + int n = arr.length; + int[] presum = new int[n]; + presum[0] = arr[0]; + for (int i = 1; i < n; i++) { + presum[i] = presum[i - 1] + arr[i]; + } + return presum; + } + + public static int sum(int[] presum, int l, int r) { + return l > r ? 0 : (l == 0 ? presum[r] : (presum[r] - presum[l - 1])); + } + + public static int maxSumAtMostOneRangeMagic(int[] presum, int l, int r) { + if (l > r) { + return 0; + } + int ans = sum(presum, l, r); + for (int s = l; s <= r; s++) { + for (int e = s; e <= r; e++) { + ans = Math.max(ans, sum(presum, l, s - 1) + sum(presum, s, e) * 10 + sum(presum, e + 1, r)); + } + } + return ans; + } + + // 正式方法,时间复杂度O(N) + public static int maxSum2(int[] arr) { + int n = arr.length; + if (n == 0) { + return 0; + } + if (n == 1) { + return Math.max(arr[0], arr[0] * 10); + } + // dp[i] + // 1) arr[0...i]原始累加和 + // 2) dp[i-1] + arr[i] + // 3) magic[i] + + // : arr[0..i]范围上,可以没有10倍区域、或者有10倍区域但是最多有一个的情况下, + // 最大累加和是多少? + // 可能性1:就是没有10倍区域,那就是arr[0..i]的累加和, 这个好弄! + // + // 可能性2:有一个10倍区域 + // a : arr[i]不在10倍区域里,但是之前可能有,那么就是dp[i-1] + arr[i] + // + // b : arr[i]在10倍区域里 + // 甲:arr[0..i-1]没有10倍区域,arr[i]自己10倍,arr[0..i-1] + 10 * arr[i] + // 乙:arr[0..i-1]中i-1位置在10倍区域里,arr[i]也在10倍区域里 + // magic[i] : magic[i] ..i i + // 对于乙,要求知道magic[j]的信息 + // magic[j]:arr[0..j]范围上,j一定要在10倍区域里,并且只有一个10倍区域的情况下,最大累加和 + // 可能性1:只有arr[j]是10倍,arr[0..j-1]没有10倍 + // 可能性2:magic[j-1] + 10 * arr[j] + int sum = arr[n - 1]; + int magic = sum * 10; + int[] right = new int[n]; + right[n - 1] = Math.max(sum, sum * 10); + for (int i = n - 2; i >= 0; i--) { + magic = 10 * arr[i] + Math.max(sum, magic); + sum += arr[i]; + right[i] = Math.max(Math.max(sum, right[i + 1] + arr[i]), magic); + } + int ans = right[0]; + sum = arr[0]; + magic = sum * 10; + int dp = Math.max(sum, sum * 10); + ans = Math.max(ans, dp + right[1]); + for (int i = 1; i < n - 1; i++) { + magic = 10 * arr[i] + Math.max(sum, magic); + sum += arr[i]; + dp = Math.max(Math.max(sum, dp + arr[i]), magic); + ans = Math.max(ans, dp + right[i + 1]); + } + return ans; + } + + // 为了测试 + public static int[] randomArray(int len, int value) { + int[] arr = new int[len]; + for (int i = 0; i < len; i++) { + arr[i] = (int) (Math.random() * value) - (int) (Math.random() * value); + } + return arr; + } + + // 为了测试 + public static void main(String[] args) { + int len = 30; + int value = 100; + int testTime = 500000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int n = (int) (Math.random() * len) + 1; + int[] arr = randomArray(n, value); + int ans1 = maxSum1(arr); + int ans2 = maxSum2(arr); + if (ans1 != ans2) { + System.out.println("出错了!"); + break; + } + } + System.out.println("测试结束"); + } + +} diff --git a/算法周更班/class_2022_01_2_week/Code06_QuietSum.java b/算法周更班/class_2022_01_2_week/Code06_QuietSum.java new file mode 100644 index 0000000..faa1584 --- /dev/null +++ b/算法周更班/class_2022_01_2_week/Code06_QuietSum.java @@ -0,0 +1,290 @@ +package class_2022_01_2_week; + +// 给定一个非负数组arr,学生依次坐在0~N-1位置,每个值表示学生的安静值 +// 如果在i位置安置插班生,那么i位置的安静值变成0,同时任何同学都会被影响到而减少安静值 +// 同学安静值减少的量: N - 这个同学到插班生的距离 +// 但是减到0以下的话,当做0处理 +// 返回一个和arr等长的ans数组,ans[i]表示如果把插班生安排在i位置,所有学生的安静值的和 +// 比如 : arr = {3,4,2,1,5},应该返回{4,3,2,3,4} +// 比如 : arr = {10,1,10,10,10},应该返回{24,27,20,20,22} +// arr长度 <= 10^5 +// arr中值 <= 2 * 10^5 +public class Code06_QuietSum { + + // 为了测试 + // 彻底暴力的方法 + public static long[] quiet1(int[] arr) { + if (arr == null || arr.length == 0) { + return new long[0]; + } + int n = arr.length; + long[] ans = new long[n]; + for (int i = 0; i < n; i++) { + long sum = 0; + for (int j = 0; j < i; j++) { + sum += Math.max(0, arr[j] - (n - Math.abs(i - j))); + } + for (int j = i + 1; j < n; j++) { + sum += Math.max(0, arr[j] - (n - Math.abs(i - j))); + } + ans[i] = sum; + } + return ans; + } + + // 时间复杂度O(N * logN)的方法 + public static long[] quiet2(int[] arr) { + if (arr == null || arr.length == 0) { + return new long[0]; + } + int n = arr.length; + SBTree sbt = new SBTree(); + long[] ans = new long[n]; + // arr[n-1] + n - 1 - n + // + sbt.add(arr[n - 1] - 1); + for (int i = n - 2; i >= 0; i--) { + long moreCount = sbt.moreCount(i); + long moreSum = sbt.moreSum(i); + ans[i] = moreSum - (moreCount * i); + sbt.add(arr[i] + i - n); + } + sbt = new SBTree(); + sbt.add(arr[0] - 1); + for (int i = 1, j = n - 2; i < n; i++, j--) { + long moreCount = sbt.moreCount(j); + long moreSum = sbt.moreSum(j); + ans[i] += moreSum - (moreCount * j); + sbt.add(arr[i] + j - n); + } + return ans; + } + + public static class SBTNode { + public int value; + // 和业务无关 + // 不同key的size,纯粹为了树的平衡调整 + public int size; + // 和业务有关 + // 加过几个数字 + public int all; + // 和业务有关 + // 子树的累加和 + public long sum; + public SBTNode l; + public SBTNode r; + + public SBTNode(int v) { + value = v; + size = 1; + all = 1; + sum = v; + } + } + + // add count(?) sum(?) logN复杂度! + // 有序表提供的性能,size - balanced - tree + public static class SBTree { + + private SBTNode root; + + private SBTNode rightRotate(SBTNode cur) { + int curCount = cur.all - (cur.l != null ? cur.l.all : 0) - (cur.r != null ? cur.r.all : 0); + long curSum = cur.sum - (cur.l != null ? cur.l.sum : 0) - (cur.r != null ? cur.r.sum : 0); + SBTNode leftNode = cur.l; + cur.l = leftNode.r; + leftNode.r = cur; + leftNode.size = cur.size; + leftNode.all = cur.all; + leftNode.sum = cur.sum; + cur.size = (cur.l != null ? cur.l.size : 0) + (cur.r != null ? cur.r.size : 0) + 1; + cur.all = (cur.l != null ? cur.l.all : 0) + (cur.r != null ? cur.r.all : 0) + curCount; + cur.sum = (cur.l != null ? cur.l.sum : 0) + (cur.r != null ? cur.r.sum : 0) + curSum; + return leftNode; + } + + private SBTNode leftRotate(SBTNode cur) { + int curCount = cur.all - (cur.l != null ? cur.l.all : 0) - (cur.r != null ? cur.r.all : 0); + long curSum = cur.sum - (cur.l != null ? cur.l.sum : 0) - (cur.r != null ? cur.r.sum : 0); + SBTNode rightNode = cur.r; + cur.r = rightNode.l; + rightNode.l = cur; + rightNode.size = cur.size; + rightNode.all = cur.all; + rightNode.sum = cur.sum; + cur.size = (cur.l != null ? cur.l.size : 0) + (cur.r != null ? cur.r.size : 0) + 1; + cur.all = (cur.l != null ? cur.l.all : 0) + (cur.r != null ? cur.r.all : 0) + curCount; + cur.sum = (cur.l != null ? cur.l.sum : 0) + (cur.r != null ? cur.r.sum : 0) + curSum; + return rightNode; + } + + private SBTNode maintain(SBTNode cur) { + if (cur == null) { + return null; + } + long leftSize = cur.l != null ? cur.l.size : 0; + long leftLeftSize = cur.l != null && cur.l.l != null ? cur.l.l.size : 0; + long leftRightSize = cur.l != null && cur.l.r != null ? cur.l.r.size : 0; + long rightSize = cur.r != null ? cur.r.size : 0; + long rightLeftSize = cur.r != null && cur.r.l != null ? cur.r.l.size : 0; + long rightRightSize = cur.r != null && cur.r.r != null ? cur.r.r.size : 0; + if (leftLeftSize > rightSize) { + cur = rightRotate(cur); + cur.r = maintain(cur.r); + cur = maintain(cur); + } else if (leftRightSize > rightSize) { + cur.l = leftRotate(cur.l); + cur = rightRotate(cur); + cur.l = maintain(cur.l); + cur.r = maintain(cur.r); + cur = maintain(cur); + } else if (rightRightSize > leftSize) { + cur = leftRotate(cur); + cur.l = maintain(cur.l); + cur = maintain(cur); + } else if (rightLeftSize > leftSize) { + cur.r = rightRotate(cur.r); + cur = leftRotate(cur); + cur.l = maintain(cur.l); + cur.r = maintain(cur.r); + cur = maintain(cur); + } + return cur; + } + + public void add(int v) { + root = add(root, v, contains(root, v)); + } + + private boolean contains(SBTNode cur, int v) { + if (cur == null) { + return false; + } else if (cur.value == v) { + return true; + } else if (cur.value > v) { + return contains(cur.l, v); + } else { + return contains(cur.r, v); + } + } + + private SBTNode add(SBTNode cur, int v, boolean contains) { + if (cur == null) { + return new SBTNode(v); + } else { + if (!contains) { + cur.size++; + } + cur.all++; + cur.sum += v; + if (cur.value == v) { + return cur; + } else if (cur.value > v) { + cur.l = add(cur.l, v, contains); + } else { + cur.r = add(cur.r, v, contains); + } + return maintain(cur); + } + } + + public long moreCount(int num) { + return moreCount(root, num); + } + + private long moreCount(SBTNode cur, int num) { + if (cur == null) { + return 0; + } + if (cur.value <= num) { + return moreCount(cur.r, num); + } else { // cur.value > num + return cur.all - (cur.l != null ? cur.l.all : 0) + moreCount(cur.l, num); + } + } + + public long moreSum(int num) { + return moreSum(root, num); + } + + private long moreSum(SBTNode cur, int num) { + if (cur == null) { + return 0; + } + if (cur.value <= num) { + return moreSum(cur.r, num); + } else { // cur.value > num + return cur.sum - (cur.l != null ? cur.l.sum : 0) + moreSum(cur.l, num); + } + } + + } + + // 为了测试 + public static int[] randomArray(int len, int v) { + int[] ans = new int[len]; + for (int i = 0; i < len; i++) { + ans[i] = (int) (Math.random() * (v + 1)); + } + return ans; + } + + public static boolean sameArray(long[] arr1, long[] arr2) { + if (arr1.length != arr2.length) { + return false; + } + int n = arr1.length; + for (int i = 0; i < n; i++) { + if (arr1[i] != arr2[i]) { + return false; + } + } + return true; + } + + // 为了测试 + public static void main(String[] args) { + int N = 1000; + int V = 5000; + int testTime = 10000; + System.out.println("功能测试开始"); + for (int i = 0; i < testTime; i++) { + int size = (int) (Math.random() * N) + 1; + int[] arr = randomArray(size, V); + long[] ans1 = quiet1(arr); + long[] ans2 = quiet2(arr); + if (!sameArray(ans1, ans2)) { + System.out.println("出错了!"); + for (int num : arr) { + System.out.print(num + " "); + } + System.out.println(); + for (long num : ans1) { + System.out.print(num + " "); + } + System.out.println(); + for (long num : ans2) { + System.out.print(num + " "); + } + System.out.println(); + break; + } + } + System.out.println("功能测试结束"); + + System.out.println("性能测试开始"); + N = 200000; + V = 500000; + long start; + long end; + int[] arr = randomArray(N, V); + start = System.currentTimeMillis(); + quiet2(arr); + end = System.currentTimeMillis(); + System.out.println("测试数组长度 : " + N); + System.out.println("测试数组数值最大值 : " + V); + System.out.println("运行时间(毫秒) : " + (end - start)); + System.out.println("性能测试结束"); + } + +} diff --git a/算法周更班/class_2022_01_3_week/Code01_AStarAlgorithm.java b/算法周更班/class_2022_01_3_week/Code01_AStarAlgorithm.java new file mode 100644 index 0000000..0116371 --- /dev/null +++ b/算法周更班/class_2022_01_3_week/Code01_AStarAlgorithm.java @@ -0,0 +1,195 @@ +package class_2022_01_3_week; + +import java.util.PriorityQueue; + +// A*算法 +// 过程和Dijskra高度相处 +// 有到终点的预估函数 +// 只要预估值<=客观上最优距离,就是对的 +// 预估函数是一种吸引力: +// 1)合适的吸引力可以提升算法的速度 +// 2)吸引力“过强”会出现错误 +// 讲述A*算法 +// 预估终点距离选择曼哈顿距离 +// 要求先在体系学习班图的章节听过"Dijkstra算法" +public class Code01_AStarAlgorithm { + + // Dijkstra算法 + // map[i][j] == 0 代表障碍 + // map[i][j] > 0 代表通行代价 + public static int minDistance1(int[][] map, int startX, int startY, int targetX, int targetY) { + if (map[startX][startY] == 0 || map[targetX][targetY] == 0) { + return Integer.MAX_VALUE; + } + int n = map.length; + int m = map[0].length; + // heap小根堆 + // [20,1,7] + PriorityQueue heap = new PriorityQueue<>((a, b) -> a[0] - b[0]); + // 1,7已经处理过了,closed[1][7] = true + boolean[][] closed = new boolean[n][m]; + heap.add(new int[] { map[startX][startY], startX, startY }); + int ans = Integer.MAX_VALUE; + while (!heap.isEmpty()) { + int[] cur = heap.poll(); + int dis = cur[0]; + int row = cur[1]; + int col = cur[2]; + if (closed[row][col]) { + continue; + } + closed[row][col] = true; + if (row == targetX && col == targetY) { + ans = dis; + break; + } + add1(dis, row - 1, col, n, m, map, closed, heap); + add1(dis, row + 1, col, n, m, map, closed, heap); + add1(dis, row, col - 1, n, m, map, closed, heap); + add1(dis, row, col + 1, n, m, map, closed, heap); + } + return ans; + } + + public static void add1(int pre, int row, int col, int n, int m, int[][] map, boolean[][] closed, + PriorityQueue heap) { + if (row >= 0 && row < n && col >= 0 && col < m && map[row][col] != 0 && !closed[row][col]) { + heap.add(new int[] { pre + map[row][col], row, col }); + } + } + + // A*算法 + // map[i][j] == 0 代表障碍 + // map[i][j] > 0 代表通行代价 + public static int minDistance2(int[][] map, int startX, int startY, int targetX, int targetY) { + if (map[startX][startY] == 0 || map[targetX][targetY] == 0) { + return Integer.MAX_VALUE; + } + int n = map.length; + int m = map[0].length; + // [20,1,7] + // [20,?,1,7] + PriorityQueue heap = new PriorityQueue<>((a, b) -> (a[0] + a[1]) - (b[0] + b[1])); + boolean[][] closed = new boolean[n][m]; + heap.add(new int[] { map[startX][startY], distance(startX, startY, targetX, targetY), startX, startY }); + int ans = Integer.MAX_VALUE; + while (!heap.isEmpty()) { + int[] cur = heap.poll(); + int fromDistance = cur[0]; + int row = cur[2]; + int col = cur[3]; + if (closed[row][col]) { + continue; + } + closed[row][col] = true; + if (row == targetX && col == targetY) { + ans = fromDistance; + break; + } + add2(fromDistance, row - 1, col, targetX, targetY, n, m, map, closed, heap); + add2(fromDistance, row + 1, col, targetX, targetY, n, m, map, closed, heap); + add2(fromDistance, row, col - 1, targetX, targetY, n, m, map, closed, heap); + add2(fromDistance, row, col + 1, targetX, targetY, n, m, map, closed, heap); + } + return ans; + } + + public static void add2(int pre, int row, int col, int targetX, int targetY, int n, int m, int[][] map, + boolean[][] closed, PriorityQueue heap) { + if (row >= 0 && row < n && col >= 0 && col < m && map[row][col] != 0 && !closed[row][col]) { + heap.add(new int[] { pre + map[row][col], distance(row, col, targetX, targetY), row, col }); + } + + } + + // 曼哈顿距离 + public static int distance(int curX, int curY, int targetX, int targetY) { + return Math.abs(targetX - curX) + Math.abs(targetY - curY); + } + + // 为了测试 + // map[i][j] == 0 代表障碍 + // map[i][j] > 0 代表通行代价 + public static int[][] randomMap(int len, int value) { + int[][] ans = new int[len][len]; + for (int i = 0; i < len; i++) { + for (int j = 0; j < len; j++) { + if (Math.random() < 0.2) { + ans[i][j] = 0; + } else { + ans[i][j] = (int) (Math.random() * value); + } + } + } + return ans; + } + + // 为了测试 + public static void printMap(int[][] map) { + for (int i = 0; i < map.length; i++) { + for (int j = 0; j < map[0].length; j++) { + System.out.print((map[i][j] == 0 ? "X" : map[i][j]) + " "); + } + System.out.println(); + } + } + + public static void main(String[] args) { + int len = 100; + int value = 50; + int testTime = 10000; + System.out.println("功能测试开始"); + for (int i = 0; i < testTime; i++) { + int n = (int) (Math.random() * len) + 2; + int[][] map = randomMap(n, value); + int startX = (int) (Math.random() * n); + int startY = (int) (Math.random() * n); + int targetX = (int) (Math.random() * n); + int targetY = (int) (Math.random() * n); + int ans1 = minDistance1(map, startX, startY, targetX, targetY); + int ans2 = minDistance2(map, startX, startY, targetX, targetY); + if (ans1 != ans2) { + System.out.println("出错了!"); + printMap(map); + System.out.println(startX + "," + startY); + System.out.println(targetX + "," + targetY); + System.out.println(ans1); + System.out.println(ans2); + break; + } + } + System.out.println("功能测试结束"); + + int[][] map = new int[4000][4000]; + for (int i = 0; i < map.length; i++) { + for (int j = 0; j < map[0].length; j++) { + map[i][j] = (int) (Math.random() * 10); + } + } + int startX = 0; + int startY = 0; + int targetX = 3456; + int targetY = 3728; + + long start = System.currentTimeMillis(); + minDistance2(map, startX, startY, targetX, targetY); + long end = System.currentTimeMillis(); + System.out.println("运行时间(毫秒) : " + (end - start)); + +// System.out.println("性能测试开始"); +// int n = 10000; +// int v = 500; +// int[][] map = randomMap(n, v); +// int startX = 0; +// int startY = 0; +// int targetX = n - 1; +// int targetY = n - 1; +// System.out.println("数据规模 : " + n + " * " + n); +// long start = System.currentTimeMillis(); +// minDistance2(map, startX, startY, targetX, targetY); +// long end = System.currentTimeMillis(); +// System.out.println("运行时间(毫秒) : " + (end - start)); +// System.out.println("性能测试结束"); + } + +} diff --git a/算法周更班/class_2022_01_3_week/Code02_EscapeALargeMaze.java b/算法周更班/class_2022_01_3_week/Code02_EscapeALargeMaze.java new file mode 100644 index 0000000..1482d8d --- /dev/null +++ b/算法周更班/class_2022_01_3_week/Code02_EscapeALargeMaze.java @@ -0,0 +1,74 @@ +package class_2022_01_3_week; + +import java.util.HashSet; +import java.util.LinkedList; +import java.util.Queue; + +// 在一个10^6 * 10^6的网格中, +// source = [sx, sy]是出发位置,target = [tx, ty]是目标位置 +// 数组blocked是封锁的方格列表, +// blocked[i] = [xi, yi] 表示(xi, yi)的方格是禁止通行的 +// 每次移动都可以走上、下、左、右四个方向 +// 但是来到的位置不能在封锁列表blocked上 +// 同时不允许走出网格 +// 如果从source能到达target返回 true。否则返回false。 +// 测试链接 : https://leetcode.com/problems/escape-a-large-maze/ +// 本题最强的优化是一个剪枝 +public class Code02_EscapeALargeMaze { + + public static long offset = 1000000; + + public boolean isEscapePossible(int[][] blocked, int[] source, int[] target) { + int n = blocked.length; + int maxPoints = n * (n - 1) / 2; + HashSet blockSet = new HashSet<>(); + for (int i = 0; i < n; i++) { + blockSet.add((long) blocked[i][0] * offset + blocked[i][1]); + } + return bfs(source[0], source[1], target[0], target[1], maxPoints, blockSet) + && bfs(target[0], target[1], source[0], source[1], maxPoints, blockSet); + } + + public static boolean bfs(int fromX, int fromY, int toX, int toY, int maxPoints, HashSet blockSet) { + HashSet visited = new HashSet<>(); + Queue queue = new LinkedList<>(); + visited.add((long) fromX * offset + fromY); + queue.add((long) fromX * offset + fromY); + while (!queue.isEmpty() && (visited.size() <= maxPoints)) { + long cur = queue.poll(); + long curX = cur / offset; + long curY = cur - curX * offset; + if (findAndAdd(curX - 1, curY, toX, toY, blockSet, visited, queue) + || findAndAdd(curX + 1, curY, toX, toY, blockSet, visited, queue) + || findAndAdd(curX, curY - 1, toX, toY, blockSet, visited, queue) + || findAndAdd(curX, curY + 1, toX, toY, blockSet, visited, queue)) { + return true; + } + } + return visited.size() > maxPoints; + } + + // 来到的点,(row, col) + // 要寻找的目标点,toX, toY + // HashSet blockSet存着不能走的格子!障碍点! + // HashSet visited, Queue queue 为了宽度优先遍历服务的! + // visited,已经处理过的点,请不要重复的放入queue + // 如果已经到达了(toX, toY) + public static boolean findAndAdd(long row, long col, + int toX, int toY, HashSet blockSet, + HashSet visited, Queue queue) { + if (row < 0 || row == offset || col < 0 || col == offset) { + return false; + } + if (row == toX && col == toY) { + return true; + } + long value = row * offset + col; + if (!blockSet.contains(value) && !visited.contains(value)) { + visited.add(value); + queue.add(value); + } + return false; + } + +} diff --git a/算法周更班/class_2022_01_3_week/Code03_ShortestSubarrayWithSumAtLeastK.java b/算法周更班/class_2022_01_3_week/Code03_ShortestSubarrayWithSumAtLeastK.java new file mode 100644 index 0000000..59d12d3 --- /dev/null +++ b/算法周更班/class_2022_01_3_week/Code03_ShortestSubarrayWithSumAtLeastK.java @@ -0,0 +1,74 @@ +package class_2022_01_3_week; + +// 来自字节跳动 +// 给定一个数组arr,其中的值有可能正、负、0 +// 给定一个正数k +// 返回累加和>=k的所有子数组中,最短的子数组长度 +// 本题测试链接 : https://leetcode.com/problems/shortest-subarray-with-sum-at-least-k/ +public class Code03_ShortestSubarrayWithSumAtLeastK { + + public static int shortestSubarray1(int[] arr, int k) { + if (arr == null || arr.length < 1) { + return -1; + } + int n = arr.length + 1; + long[] sum = new long[n]; + for (int i = 1; i < n; i++) { + sum[i] = sum[i - 1] + arr[i - 1]; + } + int[] stack = new int[n]; + int size = 1; + int ans = Integer.MAX_VALUE; + for (int i = 1; i < n; i++) { + int mostRight = mostRight(sum, stack, size, sum[i] - k); + ans = Math.min(ans, mostRight == -1 ? Integer.MAX_VALUE : (i - mostRight)); + while (size > 0 && sum[stack[size - 1]] >= sum[i]) { + size--; + } + stack[size++] = i; + } + return ans == Integer.MAX_VALUE ? -1 : ans; + } + + public static int mostRight(long[] sum, int[] stack, int size, long aim) { + int l = 0; + int r = size - 1; + int m = 0; + int ans = -1; + while (l <= r) { + m = (l + r) / 2; + if (sum[stack[m]] <= aim) { + ans = stack[m]; + l = m + 1; + } else { + r = m - 1; + } + } + return ans; + } + + public static int shortestSubarray2(int[] arr, int K) { + int N = arr.length; + long[] sum = new long[N + 1]; + for (int i = 0; i < N; i++) { + sum[i + 1] = sum[i] + arr[i]; + } + int ans = Integer.MAX_VALUE; + int[] dq = new int[N + 1]; + int l = 0; + int r = 0; + for (int i = 0; i < N + 1; i++) { + // 头部开始,符合条件的,从头部弹出! + while (l != r && sum[i] - sum[dq[l]] >= K) { + ans = Math.min(ans, i - dq[l++]); + } + // 尾部开始,前缀和比当前的前缀和大于等于的,从尾部弹出! + while (l != r && sum[dq[r - 1]] >= sum[i]) { + r--; + } + dq[r++] = i; + } + return ans != Integer.MAX_VALUE ? ans : -1; + } + +} diff --git a/算法周更班/class_2022_01_4_week/Code01_BuyThingsAboutCollocation.java b/算法周更班/class_2022_01_4_week/Code01_BuyThingsAboutCollocation.java new file mode 100644 index 0000000..d52ffb3 --- /dev/null +++ b/算法周更班/class_2022_01_4_week/Code01_BuyThingsAboutCollocation.java @@ -0,0 +1,172 @@ +package class_2022_01_4_week; + +// things是一个N*3的二维数组,商品有N件,商品编号从1~N +// 比如things[3] = [300, 2, 6] +// 代表第3号商品:价格300,重要度2,它是6号商品的附属商品 +// 再比如things[6] = [500, 3, 0] +// 代表第6号商品:价格500,重要度3,它不是任何附属,它是主商品 +// 每件商品的收益是价格*重要度,花费就是价格 +// 如果一个商品是附属品,那么只有它附属的主商品购买了,它才能被购买 +// 任何一个附属商品,只会有1个主商品 +// 任何一个主商品的附属商品数量,不会超过2件 +// 主商品和附属商品的层级最多有2层 +// 给定二维数组things、钱数money,返回整体花费不超过money的情况下,最大的收益总和 +// 测试链接 : https://www.nowcoder.com/practice/f9c6f980eeec43ef85be20755ddbeaf4 +// 请把如下的代码的主类名改为"Main", 可以直接通过 +import java.util.ArrayList; +import java.util.Scanner; + +public class Code01_BuyThingsAboutCollocation { + +// // index.....货自由选择!剩余的钱数是rest, +// // 返回:在不花超的情况下,返回最大的价值 +// public static int f(int[] prices, int[] values, int index, int rest) { +// if(rest < 0) { +// return -1; +// } +// // rest >= 0 +// if(index == prices.length) { // 没货了! +// return 0; +// } +// // rest >=0 有货! +// // 可能1 :index.... 自由选择,不要index位置的货! +// int p1 = f(prices, values, index + 1, rest); +// // 可能性2:index.... 自由选择,要index位置的货! +// int p2 = -1; +// int next = f(prices, values, index + 1, rest - prices[index]); +// if(next != -1) { +// p2 = values[index] + next; +// } +// return Math.max(p1, p2); +// } +// +// // 商品组 主商品,重要度3,价格9 附件!重要度6,价格7 +// // 0 : [ [3,9], [6,7], [4, 3] ] +// // 1 : [ [4,7] ] 没有附件,只有主商品 +// // 2 : [ [5,100] , [2,1] ] +// +// public static int p(int[][][] matrix, int index, int rest) { +// if(rest < 0) { +// return -1; +// } +// if(index == matrix.length) { +// return 0; +// } +// // 有商品组!也有钱>=0; +// int[][] team = matrix[index]; +// if(team.length == 1) { // 要 、不要 +// int p1 = p(matrix, index + 1, rest); +// int p2 = -1; +// int next = p(matrix, index + 1, rest - team[0][1]); +// if(next != -1) { +// p2 = team[0][0] * team[0][1] + next; +// } +// return Math.max(p1, p2); +// }else if(team.length == 2) { // a b 不要 a ab +// int[] a = team[0]; +// int[] b = team[1]; +// int p1 = p(matrix, index + 1, rest); +// +// int p2 = -1;// 只要a +// int next2 = p(matrix, index + 1, rest - a[1]); +// if(next2 != -1) { +// p2 = a[0] * a[1] + next2; +// } +// +// int p3 = -1;// 要 ab +// int next3= p(matrix, index + 1, rest - a[1] - b[1]); +// if(next3 != -1) { +// p3 = a[0] * a[1] + b[0] * b[1] + next3; +// } +// return Math.max(p1, Math.max(p2, p3)); +// }else { // a : b c 不要 a ab ac abc +// +// } +// +// +// +// } + + public static void main(String[] args) { + Scanner sc = new Scanner(System.in); + while (sc.hasNext()) { + int money = sc.nextInt(); + int size = sc.nextInt(); + ArrayList> things = new ArrayList<>(); + things.add(new ArrayList<>()); + for (int i = 0; i < size; i++) { + ArrayList cur = new ArrayList<>(); + cur.add(new int[] { sc.nextInt(), sc.nextInt(), sc.nextInt() }); + things.add(cur); + } + int n = clean(things, size); + int ans = maxScore(things, n, money); + System.out.println(ans); + } + sc.close(); + } + + public static int clean(ArrayList> things, int size) { + for (int i = 1; i <= size; i++) { + int[] cur = things.get(i).get(0); + if (cur[2] != 0) { + things.get(i).clear(); + things.get(cur[2]).add(cur); + } + } + int n = 0; + for (int i = 0; i <= size; i++) { + if (!things.get(i).isEmpty()) { + things.set(n++, things.get(i)); + } + } + return n; + } + + public static int maxScore(ArrayList> things, int n, int money) { + int[][] dp = new int[n][money + 1]; + for (int i = 0; i < n; i++) { + for (int j = 0; j <= money; j++) { + dp[i][j] = -2; + } + } + return process(things, n, 0, money, dp); + } + + public static int process(ArrayList> things, int n, int index, int rest, int[][] dp) { + if (rest < 0) { + return -1; + } + if (index == n) { + return 0; + } + if (dp[index][rest] != -2) { + return dp[index][rest]; + } + ArrayList project = things.get(index); + int[] a = project.get(0); + int[] b = project.size() > 1 ? project.get(1) : null; + int[] c = project.size() > 2 ? project.get(2) : null; + int p1 = process(things, n, index + 1, rest, dp); + int p2 = process(things, n, index + 1, rest - a[0], dp); + if (p2 != -1) { + p2 += a[0] * a[1]; + } + int p3 = b != null ? process(things, n, index + 1, rest - a[0] - b[0], dp) : -1; + if (p3 != -1) { + p3 += a[0] * a[1] + b[0] * b[1]; + } + int p4 = c != null ? process(things, n, index + 1, rest - a[0] - c[0], dp) : -1; + if (p4 != -1) { + p4 += a[0] * a[1] + c[0] * c[1]; + } + int p5 = c != null ? process(things, n, index + 1, rest - a[0] - b[0] - c[0], dp) : -1; + if (p5 != -1) { + p5 += a[0] * a[1] + b[0] * b[1] + c[0] * c[1]; + } + int ans = Math.max(Math.max(Math.max(p1, p2), Math.max(p3, p4)), p5); + dp[index][rest] = ans; + return ans; + } + +} diff --git a/算法周更班/class_2022_01_4_week/Code02_SplitToMArraysMinScore.java b/算法周更班/class_2022_01_4_week/Code02_SplitToMArraysMinScore.java new file mode 100644 index 0000000..fd6ac74 --- /dev/null +++ b/算法周更班/class_2022_01_4_week/Code02_SplitToMArraysMinScore.java @@ -0,0 +1,178 @@ +package class_2022_01_4_week; + +import java.util.ArrayList; +import java.util.LinkedList; + +// 来自美团 +// 小团去参加军训,军训快要结束了,长官想要把大家一排n个人分成m组,然后让每组分别去参加阅兵仪式 +// 只能选择相邻的人一组,不能随意改变队伍中人的位置 +// 阅兵仪式上会进行打分,其中有一个奇怪的扣分点是每组的最大差值,即每组最大值减去最小值 +// 长官想要让这分成的m组总扣分量最小,即这m组分别的极差之和最小 +// 长官正在思索如何安排中,就让小团来帮帮他吧 +public class Code02_SplitToMArraysMinScore { + + +// public static int splitArrayMinScore(int[] arr, int k) { +// // arr[L...R] +// int[][] record = creatRecord(arr); +// return f(arr, arr.length - 1, k, record, .); +// } +// +// +// +// public static int[][] creatRecord(int[] arr) { +// +// // arr[L...R] 最大值 - 最小值 是多少? +// int n = arr.length; +// int[][] ans = new int[n][n]; +// for() { +// for() { +// +// } +// } +// +// // ans[3][6] = arr[3...6] 最大值 - 最小值 是多少? +// +// } +// +// // arr[0......7] 8 +// // arr[0...3] 4 +// // arr[0..index] index + 1 +// // arr[0....index]的数,一定要分成part组 +// // 返回最小的扣分量 +// public static int f(int[] arr, int index, int part, int[][] record) { +// if (index + 1 <= part) { +// return 0; +// } +// // 数字的个数 > 划分部分的 +// // 0...index part组 +// // 0..7 3份! +// // 最后一份,最右的一份: 7..7 +// // 最后一份,最右的一份: 6..7 之前 0...5 2份 +// // 最后一份,最右的一份: 5..7 +// // 最后一份,最右的一份: 4..7 +// // ... +// // 最后一份,最右的一份: 0..7 +// int ans = Integer.MAX_VALUE; +// for (int rightTeamFirst = index; rightTeamFirst >= 0; rightTeamFirst--) { +// int rightTeamCost = record[rightTeamFirst][index]; +// int preCost = f(arr, rightTeamFirst - 1, part - 1, record); +// int curAllCost = preCost + rightTeamCost; +// ans = Math.min(ans, curAllCost); +// } +// return ans; +// } + + // 暴力方法 + // 为了验证 + public static int minScore1(int[] arr, int m) { + if (m == 0) { + return 0; + } + return process(arr, 1, m, arr[0], arr[0]); + } + + public static int process(int[] arr, int index, int rest, int preMin, int preMax) { + if (index == arr.length) { + return rest == 1 ? (preMax - preMin) : -1; + } + int p1 = process(arr, index + 1, rest, Math.min(preMin, arr[index]), Math.max(preMax, arr[index])); + int p2next = process(arr, index + 1, rest - 1, arr[index], arr[index]); + int p2 = p2next == -1 ? -1 : (preMax - preMin + p2next); + if (p1 == -1) { + return p2; + } + if (p2 == -1) { + return p1; + } + return Math.min(p1, p2); + } + + public static int score(ArrayList> sets) { + int ans = 0; + for (LinkedList set : sets) { + if (set.isEmpty()) { + return Integer.MAX_VALUE; + } + int max = Integer.MIN_VALUE; + int min = Integer.MAX_VALUE; + for (int num : set) { + max = Math.max(max, num); + min = Math.min(min, num); + } + ans += max - min; + } + return ans; + } + + // 正式方法 + // 时间复杂度O(M * N * N) + // 特别难的一个结论 : 这道题不能利用四边形不等式,我试了 + // 这个特别难的结论不要求必须掌握,因为四边形不等式的技巧非常冷门 + public static int minScore2(int[] arr, int m) { + if (m == 0) { + return 0; + } + int n = arr.length; + int[][] score = new int[n][n]; + for (int i = 0; i < n; i++) { + int max = arr[i]; + int min = arr[i]; + score[i][i] = max - min; + for (int j = i + 1; j < n; j++) { + max = Math.max(max, arr[j]); + min = Math.min(min, arr[j]); + score[i][j] = max - min; + } + } + int[][] dp = new int[m + 1][n]; + for (int i = 0; i < n; i++) { + dp[1][i] = score[0][i]; + } + for (int split = 2; split <= m; split++) { + for (int i = split; i < n; i++) { + dp[split][i] = dp[split - 1][i]; + for (int j = 1; j <= i; j++) { + dp[split][i] = Math.min(dp[split][i], dp[split - 1][j - 1] + score[j][i]); + } + } + } + return dp[m][n - 1]; + } + + // 为了测试 + public static int[] randomArray(int n, int v) { + int[] arr = new int[n]; + for (int i = 0; i < n; i++) { + arr[i] = (int) (Math.random() * v); + } + return arr; + } + + public static void main(String[] args) { + int len = 15; + int value = 50; + int testTime = 20000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int n = (int) (Math.random() * len) + 1; + int[] arr = randomArray(n, value); + int m = (int) (Math.random() * n) + 1; + int ans1 = minScore1(arr, m); + int ans2 = minScore2(arr, m); + if (ans1 != ans2) { + System.out.println("出错了!"); + for (int num : arr) { + System.out.print(num + " "); + } + System.out.println(); + System.out.println("m : " + m); + System.out.println(ans1); + System.out.println(ans2); + break; + } + } + System.out.println("测试结束"); + } + +} diff --git a/算法周更班/class_2022_01_4_week/Code03_RandomPickWithBlacklist.java b/算法周更班/class_2022_01_4_week/Code03_RandomPickWithBlacklist.java new file mode 100644 index 0000000..55f6365 --- /dev/null +++ b/算法周更班/class_2022_01_4_week/Code03_RandomPickWithBlacklist.java @@ -0,0 +1,52 @@ +package class_2022_01_4_week; + +import java.util.Arrays; +import java.util.HashMap; + +// 给定一个包含 [0,n) 中不重复整数的黑名单 blacklist +// 写一个函数从 [0, n) 中返回一个不在 blacklist 中的随机整数 +// 对它进行优化使其尽量少调用系统方法 Math.random() +// 1 <= n <= 1000000000 +// 0 <= blacklist.length < min(100000, N) +// 测试链接: https://leetcode.com/problems/random-pick-with-blacklist/ +public class Code03_RandomPickWithBlacklist { + + class Solution { + + // 0~99 所有的数字都可以随机,可能有若干黑名单! + // 填到洞里去,size -> 0~size-1 是最后调整的下标范围,紧实的! + private int size; + + // 13 -> 99 + // 27 -> 98 + private HashMap convert = new HashMap<>(); + + public Solution(int n, int[] blackArray) { + Arrays.sort(blackArray); + int m = blackArray.length; + // [34, 56, 78, 103, ..., 980] + // 0 1 2 3 100 + for (int i = 0; i < m && blackArray[i] < n; i++) { + for (n--; n > blackArray[i]; n--) { + if (n == blackArray[m - 1]) { + m--; + } else { + convert.put(blackArray[i], n); + break; + } + } + } + size = n; + } + + public int pick() { + int ans = (int) (Math.random() * size); + // 13 -> 99 + // 17 -> 98 + // 35 -> 35 + return convert.containsKey(ans) ? convert.get(ans) : ans; + } + + } + +} \ No newline at end of file diff --git a/算法周更班/class_2022_01_4_week/Code04_BattleshipsInABoard.java b/算法周更班/class_2022_01_4_week/Code04_BattleshipsInABoard.java new file mode 100644 index 0000000..3708220 --- /dev/null +++ b/算法周更班/class_2022_01_4_week/Code04_BattleshipsInABoard.java @@ -0,0 +1,21 @@ +package class_2022_01_4_week; + +// 来自米哈游 +// 测试链接 : https://leetcode.com/problems/battleships-in-a-board/ +public class Code04_BattleshipsInABoard { + + public static int countBattleships(char[][] m) { + int ans = 0; + for (int i = 0; i < m.length; i++) { + for (int j = 0; j < m[0].length; j++) { + if ((m[i][j] == 'X') + && (i == 0 || m[i - 1][j] != 'X') + && (j == 0 || m[i][j - 1] != 'X')) { + ans++; + } + } + } + return ans; + } + +} diff --git a/算法周更班/class_2022_02_2_week/Code01_24Game.java b/算法周更班/class_2022_02_2_week/Code01_24Game.java new file mode 100644 index 0000000..af75442 --- /dev/null +++ b/算法周更班/class_2022_02_2_week/Code01_24Game.java @@ -0,0 +1,97 @@ +package class_2022_02_2_week; + +// 测试链接 : https://leetcode.com/problems/24-game/ +public class Code01_24Game { + + public static boolean judgePoint24(int[] cards) { + if (cards == null || cards.length == 0) { + return false; + } + int n = cards.length; + Number[] arr = new Number[n]; + for (int i = 0; i < n; i++) { + arr[i] = new Number(cards[i], 1); + } + return judge(arr, cards.length); + } + + // arr中,有效的范围arr[0...size-1] ... 再往右,都无效了,不用看了! + public static boolean judge(Number[] arr, int size) { + if (size == 1) { + return arr[0].numerator == 24 && arr[0].denominator == 1; + } + for (int i = 0; i < size; i++) { + for (int j = i + 1; j < size; j++) { + Number inum = arr[i]; + Number jnum = arr[j]; + arr[j] = arr[size - 1]; + arr[i] = add(inum, jnum); + if (judge(arr, size - 1)) { + return true; + } + arr[i] = minus(inum, jnum); + if (judge(arr, size - 1)) { + return true; + } + arr[i] = minus(jnum, inum); + if (judge(arr, size - 1)) { + return true; + } + arr[i] = multiply(inum, jnum); + if (judge(arr, size - 1)) { + return true; + } + arr[i] = divide(inum, jnum); + if (arr[i] != null && judge(arr, size - 1)) { + return true; + } + arr[i] = divide(jnum, inum); + if (arr[i] != null && judge(arr, size - 1)) { + return true; + } + arr[i] = inum; + arr[j] = jnum; + } + } + return false; + } + + public static class Number { + public int numerator; + public int denominator; + + public Number(int n, int d) { + numerator = n; + denominator = d; + } + } + + public static Number add(Number a, Number b) { + return simple(a.numerator * b.denominator + b.numerator * a.denominator, a.denominator * b.denominator); + } + + public static Number minus(Number a, Number b) { + return simple(a.numerator * b.denominator - b.numerator * a.denominator, a.denominator * b.denominator); + } + + public static Number multiply(Number a, Number b) { + return simple(a.numerator * b.numerator, a.denominator * b.denominator); + } + + public static Number divide(Number a, Number b) { + return b.numerator == 0 ? null : simple(a.numerator * b.denominator, a.denominator * b.numerator); + } + + public static Number simple(int up, int down) { + if (up == 0) { + return new Number(0, 1); + } + int gcd = Math.abs(gcd(up, down)); + return new Number(up / gcd, down / gcd); + } + + public static int gcd(int a, int b) { + return b == 0 ? a : gcd(b, a % b); + } + +} diff --git a/算法周更班/class_2022_02_2_week/Code02_DesignBitset.java b/算法周更班/class_2022_02_2_week/Code02_DesignBitset.java new file mode 100644 index 0000000..f5842f9 --- /dev/null +++ b/算法周更班/class_2022_02_2_week/Code02_DesignBitset.java @@ -0,0 +1,95 @@ +package class_2022_02_2_week; + +// 测试链接 : https://leetcode-cn.com/problems/design-bitset/ +public class Code02_DesignBitset { + + class Bitset { + + // size int 32 + // size / 32 向上取整 + private int[] bits; + private final int size; + private int zeros; + private int ones; + private boolean reverse; + + public Bitset(int n) { + bits = new int[(n + 31) / 32]; + size = n; + zeros = n; + ones = 0; + reverse = false; + } + + // 把idx位置的状态,如果是0,变成1;如果是1,没有变化! + public void fix(int idx) { + int index = idx / 32; + int bit = idx % 32; + if (!reverse) { + if ((bits[index] & (1 << bit)) == 0) { + zeros--; + ones++; + bits[index] |= (1 << bit); + } + } else { + if ((bits[index] & (1 << bit)) != 0) { + zeros--; + ones++; + bits[index] ^= (1 << bit); + } + } + } + + // 1 > 0 + public void unfix(int idx) { + int index = idx / 32; + int bit = idx % 32; + if (!reverse) { + if ((bits[index] & (1 << bit)) != 0) { + ones--; + zeros++; + bits[index] ^= (1 << bit); + } + } else { + if ((bits[index] & (1 << bit)) == 0) { + ones--; + zeros++; + bits[index] |= (1 << bit); + } + } + } + + // 0变1,1变0 + public void flip() { + reverse = !reverse; + int tmp = zeros; + zeros = ones; + ones = tmp; + } + + // 是不是所有的位都是1 + public boolean all() { + return ones == size; + } + + // 是不是至少有1位是1 + public boolean one() { + return ones > 0; + } + + // 返回1的数量! + public int count() { + return ones; + } + + public String toString() { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < size; i++) { + int status = bits[i / 32] & (1 << (i % 32)); + builder.append(reverse ? (status == 0 ? '1' : '0') : (status == 0 ? '0' : '1')); + } + return builder.toString(); + } + } + +} diff --git a/算法周更班/class_2022_02_2_week/Code03_FindKthSmallestPairDistance.java b/算法周更班/class_2022_02_2_week/Code03_FindKthSmallestPairDistance.java new file mode 100644 index 0000000..225af64 --- /dev/null +++ b/算法周更班/class_2022_02_2_week/Code03_FindKthSmallestPairDistance.java @@ -0,0 +1,64 @@ +package class_2022_02_2_week; + +import java.util.Arrays; + +// 测试链接 : https://leetcode.com/problems/find-k-th-smallest-pair-distance/ +public class Code03_FindKthSmallestPairDistance { + +// public static int smallestDistancePair(int[] nums, int k) { +// int n = nums.length; +// Arrays.sort(nums); +// int l = 0; +// int r = nums[n - 1] - nums[0]; +// int ans = 0; +// while (l <= r) { +// int cnt = 0; +// int m = l + ((r - l) >> 1); +// for (int i = 0, j = 0; i < n; i++) { +// while (j < n && nums[j] <= nums[i] + m) { +// j++; +// } +// cnt += j - i - 1; +// } +// if (cnt >= k) { +// ans = m; +// r = m - 1; +// } else { +// l = m + 1; +// } +// } +// return ans; +// } + + public static int smallestDistancePair(int[] nums, int k) { + int n = nums.length; + Arrays.sort(nums); + int l = 0; + int r = nums[n - 1] - nums[0]; + int ans = 0; + while (l <= r) { + int dis = l + ((r - l) >> 1); + int cnt = f(nums, dis); + if (cnt >= k) { + ans = dis; + r = dis - 1; + } else { + l = dis + 1; + } + } + return ans; + } + + // <= dis的数字对,有几个,返回 + public static int f(int[] arr, int dis) { + int cnt = 0; + for (int l = 0, r = 0; l < arr.length; l++) { + while (r < arr.length && arr[r] <= arr[l] + dis) { + r++; + } + cnt += r - l - 1; + } + return cnt; + } + +} diff --git a/算法周更班/class_2022_02_2_week/Code04_ReachingPoints.java b/算法周更班/class_2022_02_2_week/Code04_ReachingPoints.java new file mode 100644 index 0000000..b4f2e39 --- /dev/null +++ b/算法周更班/class_2022_02_2_week/Code04_ReachingPoints.java @@ -0,0 +1,38 @@ +package class_2022_02_2_week; + +// 测试链接 : https://leetcode.com/problems/reaching-points/ +public class Code04_ReachingPoints { + + // 会超时,但是揭示了大思路 + public static boolean reachingPoints1(int sx, int sy, int tx, int ty) { + while (tx != ty) { + if (tx < ty) { + ty -= tx; + } else { + tx -= ty; + } + if (sx == tx && sy == ty) { + return true; + } + } + return false; + } + + // 对大体思路的优化 + // s ( 5, 10) + // t (100, 65) + public static boolean reachingPoints2(int sx, int sy, int tx, int ty) { + while (sx < tx && sy < ty) { + if (tx < ty) { + ty %= tx; + } else { + tx %= ty; + } + } + // 1) startx >= tx + // 2) starty >= ty + return (sx == tx && sy <= ty && (ty - sy) % sx == 0) + || (sy == ty && sx <= tx && (tx - sx) % sy == 0); + } + +} diff --git a/算法周更班/class_2022_02_2_week/Code05_RecoverTheOriginalArray.java b/算法周更班/class_2022_02_2_week/Code05_RecoverTheOriginalArray.java new file mode 100644 index 0000000..43c665b --- /dev/null +++ b/算法周更班/class_2022_02_2_week/Code05_RecoverTheOriginalArray.java @@ -0,0 +1,48 @@ +package class_2022_02_2_week; + +import java.util.Arrays; + +// 测试链接 : https://leetcode.com/problems/recover-the-original-array/ +public class Code05_RecoverTheOriginalArray { + + public static int[] recoverArray(int[] nums) { + Arrays.sort(nums); + int n = nums.length; + // nums[0] -> 小数组的第0个 + int m = n >> 1; + // 谁是大数组的第0个?不知道,试!first位置的数! + for (int first = 1; first <= m; first++) { + // d = 2 * k; k正数! + int d = nums[first] - nums[0]; + if (d > 0 && (d & 1) == 0) { + // 试图生成原始数组!ans! + int[] ans = new int[m]; + int i = 0; + boolean[] set = new boolean[n]; + int k = d >> 1; + int l = 0; + int r = first; + while (r < n) { + while (set[l]) { + l++; + } + if (l == r) { + r++; + } else if (nums[r] - nums[l] > d) { + break; + } else if (nums[r] - nums[l] < d) { + r++; + } else { + set[r++] = true; + ans[i++] = nums[l++] + k; + } + } + if (i == m) { + return ans; + } + } + } + return null; + } + +} diff --git a/算法周更班/class_2022_02_3_week/Code01_CheapestFlightsWithinKStops.java b/算法周更班/class_2022_02_3_week/Code01_CheapestFlightsWithinKStops.java new file mode 100644 index 0000000..5833260 --- /dev/null +++ b/算法周更班/class_2022_02_3_week/Code01_CheapestFlightsWithinKStops.java @@ -0,0 +1,75 @@ +package class_2022_02_3_week; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map.Entry; + +// 测试链接 : https://leetcode.com/problems/cheapest-flights-within-k-stops/ +public class Code01_CheapestFlightsWithinKStops { + + // 类似宽度优先遍历 + // n,城市数量,0...n-1 + // flights = { {0,7,100}, {15,3,300} .... } + // src -> 源点 + // dst -> 目标点 + // k -> 最多能中转几次 + public static int findCheapestPrice1(int n, int[][] flights, int src, int dst, int k) { + + // 图 + // 0 : {1, 500} {7, 20} {13, 70} + ArrayList> graph = new ArrayList<>(); + for (int i = 0; i < n; i++) { + graph.add(new ArrayList()); + } + for (int[] line : flights) { + // [0, 7 , 200] + // 0 : { {7, 200} } + graph.get(line[0]).add(new int[] { line[1], line[2] }); + } + // distance[i] : 从源点src出发的情况下,到达i号点,最少费用是多少 + int[] cost = new int[n]; + Arrays.fill(cost, Integer.MAX_VALUE); + cost[src] = 0; + HashMap curMap = new HashMap<>(); + curMap.put(src, 0); + for (int i = 0; i <= k; i++) { + HashMap nextMap = new HashMap<>(); + for (Entry entry : curMap.entrySet()) { + // A -> from 100 + // from -> ???? + int from = entry.getKey(); + int preCost = entry.getValue(); + for (int[] line : graph.get(from)) { + int to = line[0]; + int curCost = line[1]; + cost[to] = Math.min(cost[to], preCost + curCost); + nextMap.put(to, + Math.min(nextMap.getOrDefault(to, Integer.MAX_VALUE), + preCost + curCost)); + } + } + curMap = nextMap; + } + return cost[dst] == Integer.MAX_VALUE ? -1 : cost[dst]; + } + + // Bellman Ford + // 快! + public static int findCheapestPrice2(int n, int[][] flights, int src, int dst, int k) { + int[] cost = new int[n]; + Arrays.fill(cost, Integer.MAX_VALUE); + cost[src] = 0; + for (int i = 0; i <= k; i++) { + int[] next = Arrays.copyOf(cost, n); + for (int[] f : flights) { + if (cost[f[0]] != Integer.MAX_VALUE) { + next[f[1]] = Math.min(next[f[1]], cost[f[0]] + f[2]); + } + } + cost = next; + } + return cost[dst] == Integer.MAX_VALUE ? -1 : cost[dst]; + } + +} diff --git a/算法周更班/class_2022_02_3_week/Code02_MinimumNumberOfDaysToEatNOranges.java b/算法周更班/class_2022_02_3_week/Code02_MinimumNumberOfDaysToEatNOranges.java new file mode 100644 index 0000000..ff78efd --- /dev/null +++ b/算法周更班/class_2022_02_3_week/Code02_MinimumNumberOfDaysToEatNOranges.java @@ -0,0 +1,35 @@ +package class_2022_02_3_week; + +import java.util.HashMap; + +// 来自学员看到的腾讯面试 +// 测试链接 : https://leetcode.com/problems/minimum-number-of-days-to-eat-n-oranges/ +public class Code02_MinimumNumberOfDaysToEatNOranges { + + // 所有的答案都填在这个表里 + // 这个表对所有的过程共用 + public static HashMap dp = new HashMap<>(); + + public static int minDays(int n) { + if (n <= 1) { + return n; + } + if (dp.containsKey(n)) { + return dp.get(n); + } + // 1) 吃掉一个橘子 + // 2) 如果n能被2整除,吃掉一半的橘子,剩下一半 + // 3) 如果n能被3正数,吃掉三分之二的橘子,剩下三分之一 + // 因为方法2)和3),是按比例吃橘子,所以必然会非常快 + // 所以,决策如下: + // 可能性1:为了使用2)方法,先把橘子吃成2的整数倍,然后直接干掉一半,剩下的n/2调用递归 + // 即,n % 2 + 1 + minDays(n/2) + // 可能性2:为了使用3)方法,先把橘子吃成3的整数倍,然后直接干掉三分之二,剩下的n/3调用递归 + // 即,n % 3 + 1 + minDays(n/3) + // 至于方法1),完全是为了这两种可能性服务的,因为能按比例吃,肯定比一个一个吃快(显而易见的贪心) + int ans = Math.min(n % 2 + 1 + minDays(n / 2), n % 3 + 1 + minDays(n / 3)); + dp.put(n, ans); + return ans; + } + +} diff --git a/算法周更班/class_2022_02_3_week/Code03_RobotBoundedInCircle.java b/算法周更班/class_2022_02_3_week/Code03_RobotBoundedInCircle.java new file mode 100644 index 0000000..b13dd5e --- /dev/null +++ b/算法周更班/class_2022_02_3_week/Code03_RobotBoundedInCircle.java @@ -0,0 +1,40 @@ +package class_2022_02_3_week; + +// 测试链接 : https://leetcode.com/problems/robot-bounded-in-circle/ +public class Code03_RobotBoundedInCircle { + + public static boolean isRobotBounded(String ins) { + int row = 0; + int col = 0; + int direction = 0; // 0 1 2 3 + char[] str = ins.toCharArray(); + for (char cur : str) { + if (cur == 'R') { + direction = right(direction); + } else if (cur == 'L') { + direction = left(direction); + } else { + row = row(direction, row); + col = col(direction, col); + } + } + return row == 0 && col == 0 || direction != 0; + } + + public static int left(int direction) { + return direction == 0 ? 3 : (direction - 1); + } + + public static int right(int direction) { + return direction == 3 ? 0 : (direction + 1); + } + + public static int row(int direction, int r) { + return (direction == 1 || direction == 3) ? r : (r + (direction == 0 ? 1 : -1)); + } + + public static int col(int direction, int c) { + return (direction == 0 || direction == 2) ? c : (c + (direction == 1 ? 1 : -1)); + } + +} diff --git a/算法周更班/class_2022_02_3_week/Code04_MaxTeamNumber.java b/算法周更班/class_2022_02_3_week/Code04_MaxTeamNumber.java new file mode 100644 index 0000000..75a5ecc --- /dev/null +++ b/算法周更班/class_2022_02_3_week/Code04_MaxTeamNumber.java @@ -0,0 +1,94 @@ +package class_2022_02_3_week; + +import java.util.Arrays; + +// 来自微软 +// 给定一个数组arr,一个正数num,一个正数k +// 可以把arr中的某些数字拿出来组成一组,要求该组中的最大值减去最小值<=num +// 且该组数字的个数一定要正好等于k +// 每个数字只能选择进某一组,不能进多个组 +// 返回arr中最多有多少组 +public class Code04_MaxTeamNumber { + + // 对数器方法 + public static int maxTeams1(int[] arr, int num, int k) { + Arrays.sort(arr); + return process1(arr, 0, new int[arr.length], 0, num, k); + } + + public static int process1(int[] arr, int index, int[] path, int size, int num, int k) { + if (index == arr.length) { + if (size % k != 0) { + return 0; + } else { + for (int start = 0; start < size; start += k) { + if (path[start + k - 1] - path[start] > num) { + return 0; + } + } + return size / k; + } + } else { + int p1 = process1(arr, index + 1, path, size, num, k); + path[size] = arr[index]; + int p2 = process1(arr, index + 1, path, size + 1, num, k); + return Math.max(p1, p2); + } + } + + // 正式方法 + // 时间复杂度O(N * logN) + public static int maxTeams2(int[] arr, int num, int k) { + int n = arr.length; + if (k > n) { + return 0; + } + Arrays.sort(arr); + int[] dp = new int[n]; + dp[k - 1] = arr[k - 1] - arr[0] <= num ? 1 : 0; + for (int i = k; i < n; i++) { + int p1 = dp[i - 1]; + int p2 = (arr[i] - arr[i - k + 1] <= num ? 1 : 0) + dp[i - k]; + dp[i] = Math.max(p1, p2); + } + return dp[n - 1]; + } + + // 为了测试 + public static int[] randomArray(int len, int value) { + int[] ans = new int[len]; + for (int i = 0; i < len; i++) { + ans[i] = (int) (Math.random() * value); + } + return ans; + } + + // 为了测试 + public static void main(String[] args) { + int n = 18; + int v = 50; + int testTimes = 20000; + System.out.println("测试开始"); + for (int i = 0; i < testTimes; i++) { + int len = (int) (Math.random() * n) + 1; + int[] arr = randomArray(len, v); + int num = (int) (Math.random() * v) + 1; + int k = (int) (Math.random() * len) + 1; + int ans1 = maxTeams1(arr, num, k); + int ans2 = maxTeams2(arr, num, k); + if (ans1 != ans2) { + for (int number : arr) { + System.out.print(number + " "); + } + System.out.println(); + System.out.println("num : " + num); + System.out.println("k : " + k); + System.out.println(ans1); + System.out.println(ans2); + break; + } + } + System.out.println("测试结束"); + } + +} diff --git a/算法周更班/class_2022_02_3_week/Code05_StoneGameIX.java b/算法周更班/class_2022_02_3_week/Code05_StoneGameIX.java new file mode 100644 index 0000000..86015f7 --- /dev/null +++ b/算法周更班/class_2022_02_3_week/Code05_StoneGameIX.java @@ -0,0 +1,16 @@ +package class_2022_02_3_week; + +// 测试链接 : https://leetcode.com/problems/stone-game-ix/ +public class Code05_StoneGameIX { + + public static boolean stoneGameIX(int[] stones) { + int[] counts = new int[3]; + for (int num : stones) { + counts[num % 3]++; + } + return counts[0] % 2 == 0 + ? counts[1] != 0 && counts[2] != 0 + : Math.abs(counts[1] - counts[2]) > 2; + } + +} diff --git a/算法周更班/class_2022_02_4_week/Code01_SplitSameNumberWays.java b/算法周更班/class_2022_02_4_week/Code01_SplitSameNumberWays.java new file mode 100644 index 0000000..122d3ad --- /dev/null +++ b/算法周更班/class_2022_02_4_week/Code01_SplitSameNumberWays.java @@ -0,0 +1,36 @@ +package class_2022_02_4_week; + +// 来自微软 +// 比如,str = "ayxbx" +// 有以下4种切法 : a | yxbx、ay | xbx、ayx | bx、ayxb | x +// 其中第1、3、4种切法符合:x和y的个数,至少在左右两块中的一块里有相同的数量 +// 所以返回3 +// 给定一个字符串str,长度为N +// 你有N-1种划分方法,把str切成左右两半,返回有几种切法满足: +// x和y的个数,至少在左右两块中的一块里有相同的数量 +public class Code01_SplitSameNumberWays { + + public static int splitSameNumberWays(char[] str) { + if (str == null || str.length == 0) { + return 0; + } + int xAll = 0; + int yAll = 0; + for (char c : str) { + xAll += c == 'x' ? 1 : 0; + yAll += c == 'y' ? 1 : 0; + } + int leftX = str[0] == 'x' ? 1 : 0; + int leftY = str[0] == 'y' ? 1 : 0; + int ans = 0; + for (int i = 1; i < str.length; i++) { + if (leftX == leftY || (xAll - leftX) == (yAll - leftY)) { + ans++; + } + leftX += str[i] == 'x' ? 1 : 0; + leftY += str[i] == 'y' ? 1 : 0; + } + return ans; + } + +} diff --git a/算法周更班/class_2022_02_4_week/Code02_NearBiggerNoSameNeighbour.java b/算法周更班/class_2022_02_4_week/Code02_NearBiggerNoSameNeighbour.java new file mode 100644 index 0000000..5ad0e47 --- /dev/null +++ b/算法周更班/class_2022_02_4_week/Code02_NearBiggerNoSameNeighbour.java @@ -0,0 +1,69 @@ +package class_2022_02_4_week; + +// 来自微软 +// 给定一个正数num,要返回一个大于num的数,并且每一位和相邻位的数字不能相等 +// 返回达标的数字中,最小的那个 +// 10^9 +public class Code02_NearBiggerNoSameNeighbour { + + public static int near(int num) { + // num = 174 + // "0175" + // num = 899 + // "0900" + // num = 999 + // "01000" + char[] raw = ("0" + String.valueOf(num + 1)).toCharArray(); + process(raw); + return Integer.valueOf(String.valueOf(raw)); + } + + public static void process(char[] raw) { + for (int i = 1; i < raw.length; i++) { + if (raw[i - 1] == raw[i]) { + addOne(raw, i); + for (int j = i + 1; j < raw.length; j++) { + raw[j] = '0'; + } + process(raw); + return; + } + } + } + + // 99..... + // +1 + //100 + public static void addOne(char[] r, int i) { + boolean up = true; + while (up && r[i] == '9') { + r[i--] = '0'; + } + r[i]++; + } + + public static void main(String[] args) { + char[] test = new char[] { '0', '1', '2', '3' }; + + System.out.println(Integer.valueOf(String.valueOf(test))); + + int num1 = 55; + System.out.println(near(num1)); + + int num2 = 1765; + System.out.println(near(num2)); + + int num3 = 98; + System.out.println(near(num3)); + + int num4 = 44432; + System.out.println(near(num4)); + + int num5 = 3298; + System.out.println(near(num5)); + + int num6 = 9999998; + System.out.println(near(num6)); + } + +} diff --git a/算法周更班/class_2022_02_4_week/Code03_PartitionArrayForMaximumSum.java b/算法周更班/class_2022_02_4_week/Code03_PartitionArrayForMaximumSum.java new file mode 100644 index 0000000..43a70ac --- /dev/null +++ b/算法周更班/class_2022_02_4_week/Code03_PartitionArrayForMaximumSum.java @@ -0,0 +1,53 @@ +package class_2022_02_4_week; + +import java.util.Arrays; + +// 测试链接 : https://leetcode.com/problems/partition-array-for-maximum-sum/ +public class Code03_PartitionArrayForMaximumSum { + + public static int maxSumAfterPartitioning1(int[] arr, int k) { + int[] dp = new int[arr.length]; + Arrays.fill(dp, -1); + return process1(arr, k, arr.length - 1, dp); + } + + // 永远不变的固定参数 : arr, k + // 可变参数 : index + // 缓存 : dp + // process含义 : arr[0...index]最优划分搞出的最大和是多少,返回 + public static int process1(int[] arr, int k, int index, int[] dp) { + if (index == -1) { + return 0; + } + if (dp[index] != -1) { + return dp[index]; + } + int max = Integer.MIN_VALUE; + int ans = Integer.MIN_VALUE; + for (int i = index, j = 1; i >= 0 && j <= k; i--, j++) { + max = Math.max(max, arr[i]); + ans = Math.max(ans, process1(arr, k, i - 1, dp) + (index - i + 1) * max); + } + dp[index] = ans; + return ans; + } + + public static int maxSumAfterPartitioning2(int[] arr, int k) { + if (arr == null || arr.length == 0) { + return 0; + } + int n = arr.length; + int[] dp = new int[n]; + dp[0] = arr[0]; + for (int i = 1; i < n; i++) { + dp[i] = arr[i] + dp[i - 1]; + int max = arr[i]; + for (int j = i - 1; j >= 0 && (i - j + 1) <= k; j--) { + max = Math.max(max, arr[j]); + dp[i] = Math.max(dp[i], max * (i - j + 1) + (j - 1 >= 0 ? dp[j - 1] : 0)); + } + } + return dp[n - 1]; + } + +} diff --git a/算法周更班/class_2022_02_4_week/Code04_NumberOfDescendingTriples.java b/算法周更班/class_2022_02_4_week/Code04_NumberOfDescendingTriples.java new file mode 100644 index 0000000..18f929c --- /dev/null +++ b/算法周更班/class_2022_02_4_week/Code04_NumberOfDescendingTriples.java @@ -0,0 +1,134 @@ +package class_2022_02_4_week; + +import java.util.Arrays; + +// 返回一个数组中,所有降序三元组的数量 +// 比如 : {5, 3, 4, 2, 1} +// 所有降序三元组为 : +// {5, 3, 2}、{5, 3, 1}、{5, 4, 2}、{5, 4, 1} +// {5, 2, 1}、{3, 2, 1}、{4, 2, 1} +// 所以返回数量7 +public class Code04_NumberOfDescendingTriples { + + // 暴力方法 + // 对数器 + public static int num1(int[] arr) { + if (arr == null || arr.length < 3) { + return 0; + } + return process(arr, 0, new int[3], 0); + } + + public static int process(int[] arr, int index, int[] path, int size) { + if (size == 3) { + return path[0] > path[1] && path[1] > path[2] ? 1 : 0; + } + int ans = 0; + if (index < arr.length) { + ans = process(arr, index + 1, path, size); + path[size] = arr[index]; + ans += process(arr, index + 1, path, size + 1); + } + return ans; + } + + // 正式方法 + // 时间复杂度O(N * logN) + // 利用index tree + public static int num2(int[] arr) { + if (arr == null || arr.length < 3) { + return 0; + } + int n = arr.length; + int[] sorted = Arrays.copyOf(arr, n); + Arrays.sort(sorted); + int max = Integer.MIN_VALUE; + for (int i = 0; i < n; i++) { + arr[i] = rank(sorted, arr[i]); + max = Math.max(max, arr[i]); + } + IndexTree it2 = new IndexTree(max); + IndexTree it3 = new IndexTree(max); + int ans = 0; + for (int i = n - 1; i >= 0; i--) { + ans += arr[i] == 1 ? 0 : it3.sum(arr[i] - 1); + it2.add(arr[i], 1); + it3.add(arr[i], arr[i] == 1 ? 0 : it2.sum(arr[i] - 1)); + } + return ans; + } + + // 返回>=num, 最左位置 + public static int rank(int[] sorted, int num) { + int l = 0; + int r = sorted.length - 1; + int m = 0; + int ans = 0; + while (l <= r) { + m = (l + r) / 2; + if (sorted[m] >= num) { + ans = m; + r = m - 1; + } else { + l = m + 1; + } + } + return ans + 1; + } + + // 下标从1开始 + public static class IndexTree { + + private int[] tree; + private int N; + + // 0位置弃而不用 + public IndexTree(int size) { + N = size; + tree = new int[N + 1]; + } + + // 1~index 累加和是多少? + public int sum(int index) { + int ret = 0; + while (index > 0) { + ret += tree[index]; + index -= index & -index; + } + return ret; + } + + public void add(int index, int d) { + while (index <= N) { + tree[index] += d; + index += index & -index; + } + } + } + + // 为了测试 + public static int[] randomArray(int len, int value) { + int[] arr = new int[len]; + for (int i = 0; i < arr.length; i++) { + arr[i] = (int) (Math.random() * value) - (int) (Math.random() * value); + } + return arr; + } + + public static void main(String[] args) { + int len = 20; + int value = 100; + int testTimes = 10000; + System.out.println("测试开始"); + for (int i = 0; i < testTimes; i++) { + int[] arr = randomArray(len, value); + int ans1 = num1(arr); + int ans2 = num2(arr); + if (ans1 != ans2) { + System.out.println("出错了!"); + } + } + System.out.println("测试结束"); + } + +} diff --git a/算法周更班/class_2022_02_4_week/Code05_GroupsOfStrings.java b/算法周更班/class_2022_02_4_week/Code05_GroupsOfStrings.java new file mode 100644 index 0000000..9a77b32 --- /dev/null +++ b/算法周更班/class_2022_02_4_week/Code05_GroupsOfStrings.java @@ -0,0 +1,195 @@ +package class_2022_02_4_week; + +import java.util.HashMap; + +// 测试链接 : https://leetcode.com/problems/groups-of-strings/ +public class Code05_GroupsOfStrings { + + // 可能会超时,或者打败比例很低 + // 因为常数优化不到位 + public static int[] groupStrings1(String[] words) { + int n = words.length; + // 0 1 2 ... n-1 + UnionFind uf = new UnionFind(n); + int[] strs = new int[n]; + // abd -> 0..01011 7 + // 0..01011 key value 7 + HashMap stands = new HashMap<>(); + for (int i = 0; i < n; i++) { + int status = 0; + for (char c : words[i].toCharArray()) { + status |= 1 << (c - 'a'); + } + strs[i] = status; + if (stands.containsKey(status)) { + uf.union(stands.get(status), i); + } else { + stands.put(status, i); + } + } + for (int i = 0; i < n; i++) { + // 一个字符串,状态 + int status = strs[i]; + for (int j = 0; j < 26; j++) { + // 001101 + // a + // 001101 + // b + // 001111 + // c + // 001101 + // z + //.. + uf.union(i, stands.get(status | (1 << j))); + } + // 有的字符,减少一遍 + for (int j = 0; j < 26; j++) { + if ((status & (1 << j)) != 0) { + uf.union(i, stands.get(status ^ (1 << j))); + } + } + for (int has = 0; has < 26; has++) { + if ((status & (1 << has)) != 0) { + status ^= 1 << has; + for (int replace = 0; replace < 26; replace++) { + uf.union(i, stands.get(status | (1 << replace))); + } + status |= 1 << has; + } + } + } + return new int[] { uf.sets(), uf.maxSize() }; + } + + // 肯定通过 + // 打败比例达标 + // 优化了常数时间 + public static int[] groupStrings2(String[] words) { + int n = words.length; + UnionFind uf = new UnionFind(n); + int[] strs = new int[n]; + HashMap stands = new HashMap<>(); + for (int i = 0; i < n; i++) { + int status = 0; + for (char c : words[i].toCharArray()) { + status |= 1 << (c - 'a'); + } + strs[i] = status; + if (stands.containsKey(status)) { + uf.union(stands.get(status), i); + } else { + stands.put(status, i); + } + } + for (int i = 0; i < n; i++) { + int yes = strs[i]; + int no = (~yes) & ((1 << 26) - 1); + int tmpYes = yes; + int tmpNo = no; + int rightOneYes = 0; + int rightOneNo = 0; + + + // 0....0 0110011 + // + // 0....0 0110011 + // 0....0 0000001 -> 用 + + // 0....0 0110010 + // 0....0 0000010 -> 用 + + // 0....0 0110000 + + while (tmpYes != 0) { + rightOneYes = tmpYes & (-tmpYes); + uf.union(i, stands.get(yes ^ rightOneYes)); + tmpYes ^= rightOneYes; + } + + + + + + // tmpNo = 该去试试什么添加! + while(tmpNo != 0) { + rightOneNo = tmpNo & (-tmpNo); + uf.union(i, stands.get(yes | rightOneNo)); + tmpNo ^= rightOneNo; + } + tmpYes = yes; + while (tmpYes != 0) { + rightOneYes = tmpYes & (-tmpYes); + tmpNo = no; + while (tmpNo != 0) { + rightOneNo = tmpNo & (-tmpNo); + uf.union(i, stands.get((yes ^ rightOneYes) | rightOneNo)); + tmpNo ^= rightOneNo; + } + tmpYes ^= rightOneYes; + } + } + return new int[] { uf.sets(), uf.maxSize() }; + } + + public static class UnionFind { + private int[] parent; + private int[] size; + private int[] help; + + public UnionFind(int N) { + parent = new int[N]; + size = new int[N]; + help = new int[N]; + for (int i = 0; i < N; i++) { + parent[i] = i; + size[i] = 1; + } + } + + private int find(int i) { + int hi = 0; + while (i != parent[i]) { + help[hi++] = i; + i = parent[i]; + } + for (hi--; hi >= 0; hi--) { + parent[help[hi]] = i; + } + return i; + } + + public void union(Integer i, Integer j) { + if (i == null || j == null) { + return; + } + int f1 = find(i); + int f2 = find(j); + if (f1 != f2) { + if (size[f1] >= size[f2]) { + size[f1] += size[f2]; + parent[f2] = f1; + } else { + size[f2] += size[f1]; + parent[f1] = f2; + } + } + } + + public int sets() { + int ans = 0; + for (int i = 0; i < parent.length; i++) { + ans += parent[i] == i ? 1 : 0; + } + return ans; + } + + public int maxSize() { + int ans = 0; + for (int s : size) { + ans = Math.max(ans, s); + } + return ans; + } + } + +} diff --git a/算法周更班/class_2022_03_1_week/Code01_StronglyConnectedComponents.java b/算法周更班/class_2022_03_1_week/Code01_StronglyConnectedComponents.java new file mode 100644 index 0000000..a5886d4 --- /dev/null +++ b/算法周更班/class_2022_03_1_week/Code01_StronglyConnectedComponents.java @@ -0,0 +1,105 @@ +package class_2022_03_1_week; + +import java.util.ArrayList; + +// tarjan算法求有向图的强连通分量 +public class Code01_StronglyConnectedComponents { + + public static class StronglyConnectedComponents { + public ArrayList> nexts; + public int n; + public int[] stack; + public int stackSize; + public int[] dfn; + public int[] low; + public int cnt; + public int[] scc; + public int sccn; + + // 请保证点的编号从1开始,不从0开始 + // 注意: + // 如果edges里有0、1、2...n这些点,那么容器edges的大小为n+1 + // 但是0点是弃而不用的,所以1..n才是有效的点,所以有效大小是n + public StronglyConnectedComponents(ArrayList> edges) { + nexts = edges; + init(); + scc(); + } + + private void init() { + n = nexts.size(); + stack = new int[n]; + stackSize = 0; + dfn = new int[n]; + low = new int[n]; + cnt = 0; + scc = new int[n]; + sccn = 0; + n--; + } + + private void scc() { + for (int i = 1; i <= n; i++) { + if (dfn[i] == 0) { + tarjan(i); + } + } + } + + // low[] + // dfn[] + // stack[] + // int stackSize + // boolean isStack[] + // int cnt; + // int sccn; + // scc[] + private void tarjan(int p) { + low[p] = dfn[p] = ++cnt; + stack[stackSize++] = p; + for (int q : nexts.get(p)) { + // q 当前p的每一个孩子 + if (dfn[q] == 0) { + tarjan(q); + } + // q 肯定遍历过 1) 遍历过,结算了!2)遍历过,没结算 + if (scc[q] == 0) { // scc[q]!=0 q已经属于某个集团了!不能用来更新 + low[p] = Math.min(low[p], low[q]); + } + } + if (low[p] == dfn[p]) { + sccn++; + int top = 0; + do { + top = stack[--stackSize]; + scc[top] = sccn; + } while (top != p); + } + } + + public int[] getScc() { + return scc; + } + + public int getSccn() { + return sccn; + } + + public ArrayList> getShortGraph() { + ArrayList> shortGraph = new ArrayList>(); + for (int i = 0; i <= sccn; i++) { + shortGraph.add(new ArrayList()); + } + for (int u = 1; u <= n; u++) { + for (int v : nexts.get(u)) { + if (scc[u] != scc[v]) { + shortGraph.get(scc[u]).add(scc[v]); + } + } + } + return shortGraph; + } + + } + +} diff --git a/算法周更班/class_2022_03_1_week/Code02_NetworkOfSchools.java b/算法周更班/class_2022_03_1_week/Code02_NetworkOfSchools.java new file mode 100644 index 0000000..4c280cc --- /dev/null +++ b/算法周更班/class_2022_03_1_week/Code02_NetworkOfSchools.java @@ -0,0 +1,148 @@ +package class_2022_03_1_week; + +// 强连通分量练习题目 +// N个学校之间有单向的网络,每个学校得到一套软件后,可以通过单向网络向周边的学校传输 +// 问题1:初始至少需要向多少个学校发放软件,使得网络内所有的学校最终都能得到软件 +// 问题2:至少需要添加几条传输线路(边),使任意向一个学校发放软件后 +// 经过若干次传送,网络内所有的学校最终都能得到软件 +// 2 <= N <= 1000 +// 从题意中抽象出的算法模型, 给定一个有向图,求: +// 1) 至少要选几个顶点,才能做到从这些顶点出发,可以到达全部顶点 +// 2) 至少要加多少条边,才能使得从任何一个顶点出发,都能到达全部顶点 +// 测试链接 : http://poj.org/problem?id=1236 +// 注册一下 -> 页面上点击"submit" -> 语言选择java +// 然后把如下代码粘贴进去, 把主类名改成"Main", 可以直接通过 +import java.util.ArrayList; +import java.util.Scanner; + +public class Code02_NetworkOfSchools { + + public static void main(String[] args) { + Scanner sc = new Scanner(System.in); + while (sc.hasNext()) { + int n = sc.nextInt(); + ArrayList> edges = new ArrayList>(); + for (int i = 0; i <= n; i++) { + edges.add(new ArrayList()); + } + for (int from = 1; from <= n; from++) { + int to = 0; + while ((to = sc.nextInt()) != 0) { + edges.get(from).add(to); + } + } + StronglyConnectedComponents scc = new StronglyConnectedComponents(edges); + int sccn = scc.getSccn(); + int[] in = new int[sccn + 1]; + int[] out = new int[sccn + 1]; + ArrayList> dag = scc.getShortGraph(); + for (int i = 1; i <= sccn; i++) { + for (int j : dag.get(i)) { + out[i]++; + in[j]++; + } + } + int zeroIn = 0; + int zeroOut = 0; + for (int i = 1; i <= sccn; i++) { + if (in[i] == 0) { + zeroIn++; + } + if (out[i] == 0) { + zeroOut++; + } + } + System.out.println(zeroIn); + System.out.println(sccn == 1 ? 0 : Math.max(zeroIn, zeroOut)); + } + sc.close(); + } + + public static class StronglyConnectedComponents { + public ArrayList> nexts; + public int n; + public int[] stack; + public int stackSize; + public int[] dfn; + public int[] low; + public int cnt; + public int[] scc; + public int sccn; + + // 请保证点的编号从1开始,不从0开始 + // 注意: + // 如果edges里有0、1、2...n这些点,那么容器edges的大小为n+1 + // 但是0点是弃而不用的,所以1..n才是有效的点,所以有效大小是n + public StronglyConnectedComponents(ArrayList> edges) { + nexts = edges; + init(); + scc(); + } + + private void init() { + n = nexts.size(); + stack = new int[n]; + stackSize = 0; + dfn = new int[n]; + low = new int[n]; + cnt = 0; + scc = new int[n]; + sccn = 0; + n--; + } + + private void scc() { + for (int i = 1; i <= n; i++) { + if (dfn[i] == 0) { + tarjan(i); + } + } + } + + private void tarjan(int p) { + low[p] = dfn[p] = ++cnt; + stack[stackSize++] = p; + for (int q : nexts.get(p)) { + if (dfn[q] == 0) { + tarjan(q); + } + if (scc[q] == 0) { + low[p] = Math.min(low[p], low[q]); + } + } + if (low[p] == dfn[p]) { + sccn++; + int top = 0; + do { + top = stack[--stackSize]; + scc[top] = sccn; + } while (top != p); + } + } + + public int[] getScc() { + return scc; + } + + public int getSccn() { + return sccn; + } + + public ArrayList> getShortGraph() { + ArrayList> shortGraph = new ArrayList>(); + for (int i = 0; i <= sccn; i++) { + shortGraph.add(new ArrayList()); + } + for (int u = 1; u <= n; u++) { + for (int v : nexts.get(u)) { + if (scc[u] != scc[v]) { + shortGraph.get(scc[u]).add(scc[v]); + } + } + } + return shortGraph; + } + + } + +} diff --git a/算法周更班/class_2022_03_1_week/Code03_PopularCows.java b/算法周更班/class_2022_03_1_week/Code03_PopularCows.java new file mode 100644 index 0000000..1851176 --- /dev/null +++ b/算法周更班/class_2022_03_1_week/Code03_PopularCows.java @@ -0,0 +1,150 @@ +package class_2022_03_1_week; + +// 强连通分量练习题目 +// A -> B,表示A认为B是红人 +// A -> B -> C,表示A认为B是红人,B认为C是红人,规定“认为”关系有传递性,所以A也认为C是红人 +// 给定一张有向图,方式是给定M个有序对(A, B) +// (A, B)表示A认为B是红人,该关系具有传递性 +// 给定的有序对中可能包含(A, B)和(B, C),但不包含(A,C) +// 求被其他所有人认为是红人的总数。 +// 测试链接 : http://poj.org/problem?id=2186 +// 注册一下 -> 页面上点击"submit" -> 语言选择java +// 然后把如下代码粘贴进去, 把主类名改成"Main", 可以直接通过 +import java.util.ArrayList; +import java.util.Scanner; + +public class Code03_PopularCows { + + public static void main(String[] args) { + Scanner sc = new Scanner(System.in); + while (sc.hasNext()) { + int n = sc.nextInt(); + int m = sc.nextInt(); + ArrayList> edges = new ArrayList>(); + for (int i = 0; i <= n; i++) { + edges.add(new ArrayList()); + } + for (int i = 0; i < m; i++) { + int from = sc.nextInt(); + int to = sc.nextInt(); + edges.get(from).add(to); + } + StronglyConnectedComponents connectedComponents = new StronglyConnectedComponents(edges); + int sccn = connectedComponents.getSccn(); + if (sccn == 1) { + System.out.println(n); + } else { + ArrayList> dag = connectedComponents.getShortGraph(); + int zeroOut = 0; + int outScc = 0; + for (int i = 1; i <= sccn; i++) { + if (dag.get(i).size() == 0) { + zeroOut++; + outScc = i; + } + } + if (zeroOut > 1) { + System.out.println(0); + } else { + int[] scc = connectedComponents.getScc(); + int ans = 0; + for (int i = 1; i <= n; i++) { + if (scc[i] == outScc) { + ans++; + } + } + System.out.println(ans); + } + } + } + sc.close(); + } + + public static class StronglyConnectedComponents { + public ArrayList> nexts; + public int n; + public int[] stack; + public int stackSize; + public int[] dfn; + public int[] low; + public int cnt; + public int[] scc; + public int sccn; + + // 请保证点的编号从1开始,不从0开始 + // 注意: + // 如果edges里有0、1、2...n这些点,那么容器edges的大小为n+1 + // 但是0点是弃而不用的,所以1..n才是有效的点,所以有效大小是n + public StronglyConnectedComponents(ArrayList> edges) { + nexts = edges; + init(); + scc(); + } + + private void init() { + n = nexts.size(); + stack = new int[n]; + stackSize = 0; + dfn = new int[n]; + low = new int[n]; + cnt = 0; + scc = new int[n]; + sccn = 0; + n--; + } + + private void scc() { + for (int i = 1; i <= n; i++) { + if (dfn[i] == 0) { + tarjan(i); + } + } + } + + private void tarjan(int p) { + low[p] = dfn[p] = ++cnt; + stack[stackSize++] = p; + for (int q : nexts.get(p)) { + if (dfn[q] == 0) { + tarjan(q); + } + if (scc[q] == 0) { + low[p] = Math.min(low[p], low[q]); + } + } + if (low[p] == dfn[p]) { + sccn++; + int top = 0; + do { + top = stack[--stackSize]; + scc[top] = sccn; + } while (top != p); + } + } + + public int[] getScc() { + return scc; + } + + public int getSccn() { + return sccn; + } + + public ArrayList> getShortGraph() { + ArrayList> shortGraph = new ArrayList>(); + for (int i = 0; i <= sccn; i++) { + shortGraph.add(new ArrayList()); + } + for (int u = 1; u <= n; u++) { + for (int v : nexts.get(u)) { + if (scc[u] != scc[v]) { + shortGraph.get(scc[u]).add(scc[v]); + } + } + } + return shortGraph; + } + + } + +} diff --git a/算法周更班/class_2022_03_1_week/Code04_IgniteMinBombs.java b/算法周更班/class_2022_03_1_week/Code04_IgniteMinBombs.java new file mode 100644 index 0000000..fab1a24 --- /dev/null +++ b/算法周更班/class_2022_03_1_week/Code04_IgniteMinBombs.java @@ -0,0 +1,328 @@ +package class_2022_03_1_week; + +import java.util.ArrayList; +import java.util.TreeMap; + +// 来自亚马逊 +// 帖子链接 : https://www.nowcoder.com/discuss/826182 +// 这道题是帖子里第2题 +// 在一个地图上有若干个炸弹,每个炸弹会呈现十字型引爆 +// 每个炸弹都有其当量值,这个值决定了这个炸弹的爆炸半径 +// 如果一个炸弹被引爆时,有其它炸弹在其爆炸半径内,那么其它炸弹也会爆炸 +// 请问使地图上所有炸弹爆炸所需的最少人为引爆次数。 +// 例如: +// 0,0,0,0,0 +// 0,0,0,1,0 +// 0,0,0,0,0 +// 上图中val为1的单元是一个炸弹,人为引爆后地图变成下面的样子: +// 0, 0, 0,-1, 0 +// 0, 0,-1,-1,-1 +// 0, 0, 0,-1, 0 +// 题目并没有给数据量,面经题目的通病 +public class Code04_IgniteMinBombs { + + // 暴力方法 + // 为了测试 + public static int minBombs1(int[][] map) { + int n = map.length; + int m = 0; + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + m += map[i][j] == 0 ? 0 : 1; + } + } + int[][] bombs = new int[m][2]; + m = 0; + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + if (map[i][j] != 0) { + bombs[m][0] = i; + bombs[m++][1] = j; + } + } + } + int[] arr = new int[m]; + for (int i = 0; i < m; i++) { + arr[i] = i; + } + return process1(arr, 0, bombs, map); + } + + public static int process1(int[] arr, int index, int[][] bombs, int[][] map) { + int ans = Integer.MAX_VALUE; + if (index == arr.length) { + ans = orderIgnite(arr, bombs, map); + } else { + for (int i = index; i < arr.length; i++) { + swap(arr, index, i); + ans = Math.min(ans, process1(arr, index + 1, bombs, map)); + swap(arr, index, i); + } + } + return ans; + } + + public static void swap(int[] arr, int i, int j) { + int tmp = arr[i]; + arr[i] = arr[j]; + arr[j] = tmp; + } + + public static int orderIgnite(int[] arr, int[][] bombs, int[][] map) { + int[][] copy = copyMap(map); + int ans = 0; + for (int i : arr) { + int row = bombs[i][0]; + int col = bombs[i][1]; + if (copy[row][col] != -1) { + ans++; + burn(copy, row, col, copy[row][col]); + } + } + return ans; + } + + public static int[][] copyMap(int[][] map) { + int n = map.length; + int[][] ans = new int[n][n]; + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + ans[i][j] = map[i][j]; + } + } + return ans; + } + + public static void burn(int[][] map, int i, int j, int v) { + map[i][j] = -1; + ArrayList queue = new ArrayList<>(); + for (int row = i - 1, cnt = 1; row >= 0 && cnt <= v; row--, cnt++) { + if (map[row][j] > 0) { + queue.add(new int[] { row, j, map[row][j] }); + } + map[row][j] = -1; + } + for (int row = i + 1, cnt = 1; row < map.length && cnt <= v; row++, cnt++) { + if (map[row][j] > 0) { + queue.add(new int[] { row, j, map[row][j] }); + } + map[row][j] = -1; + } + for (int col = j - 1, cnt = 1; col >= 0 && cnt <= v; col--, cnt++) { + if (map[i][col] > 0) { + queue.add(new int[] { i, col, map[i][col] }); + } + map[i][col] = -1; + } + for (int col = j + 1, cnt = 1; col < map.length && cnt <= v; col++, cnt++) { + if (map[i][col] > 0) { + queue.add(new int[] { i, col, map[i][col] }); + } + map[i][col] = -1; + } + for (int[] next : queue) { + burn(map, next[0], next[1], next[2]); + } + } + + // 正式方法 + // 用到有序表 + 强连通分量 + public static int minBombs2(int[][] map) { + int n = map.length; + ArrayList> rowTreeMaps = new ArrayList<>(); + ArrayList> colTreeMaps = new ArrayList<>(); + for (int i = 0; i < n; i++) { + rowTreeMaps.add(new TreeMap<>()); + colTreeMaps.add(new TreeMap<>()); + } + int m = 0; + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + if (map[i][j] != 0) { + m++; + rowTreeMaps.get(i).put(j, m); + colTreeMaps.get(j).put(i, m); + } + } + } + ArrayList> edges = new ArrayList<>(); + for (int i = 0; i <= m; i++) { + edges.add(new ArrayList<>()); + } + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + if (map[i][j] != 0) { + TreeMap rowTreeMap = rowTreeMaps.get(i); + TreeMap colTreeMap = colTreeMaps.get(j); + int from = rowTreeMap.get(j); + int col = j - 1; + while (rowTreeMap.floorKey(col) != null && j - rowTreeMap.floorKey(col) <= map[i][j]) { + col = rowTreeMap.floorKey(col); + edges.get(from).add(rowTreeMap.get(col)); + col--; + } + col = j + 1; + while (rowTreeMap.ceilingKey(col) != null && rowTreeMap.ceilingKey(col) - j <= map[i][j]) { + col = rowTreeMap.ceilingKey(col); + edges.get(from).add(rowTreeMap.get(col)); + col++; + } + int row = i - 1; + while (colTreeMap.floorKey(row) != null && i - colTreeMap.floorKey(row) <= map[i][j]) { + row = colTreeMap.floorKey(row); + edges.get(from).add(colTreeMap.get(row)); + row--; + } + row = i + 1; + while (colTreeMap.ceilingKey(row) != null && colTreeMap.ceilingKey(row) - i <= map[i][j]) { + row = colTreeMap.ceilingKey(row); + edges.get(from).add(colTreeMap.get(row)); + row++; + } + } + } + } + StronglyConnectedComponents scc = new StronglyConnectedComponents(edges); + int sccn = scc.getSccn(); + int[] in = new int[sccn + 1]; + int[] out = new int[sccn + 1]; + ArrayList> dag = scc.getShortGraph(); + for (int i = 1; i <= sccn; i++) { + for (int j : dag.get(i)) { + out[i]++; + in[j]++; + } + } + int zeroIn = 0; + for (int i = 1; i <= sccn; i++) { + if (in[i] == 0) { + zeroIn++; + } + } + return zeroIn; + } + + public static class StronglyConnectedComponents { + public ArrayList> nexts; + public int n; + public int[] stack; + public int stackSize; + public int[] dfn; + public int[] low; + public int cnt; + public int[] scc; + public int sccn; + + // 请保证点的编号从1开始,不从0开始 + // 注意: + // 如果edges里有0、1、2...n这些点,那么容器edges的大小为n+1 + // 但是0点是弃而不用的,所以1..n才是有效的点,所以有效大小是n + public StronglyConnectedComponents(ArrayList> edges) { + nexts = edges; + init(); + scc(); + } + + private void init() { + n = nexts.size(); + stack = new int[n]; + stackSize = 0; + dfn = new int[n]; + low = new int[n]; + cnt = 0; + scc = new int[n]; + sccn = 0; + n--; + } + + private void scc() { + for (int i = 1; i <= n; i++) { + if (dfn[i] == 0) { + tarjan(i); + } + } + } + + private void tarjan(int p) { + low[p] = dfn[p] = ++cnt; + stack[stackSize++] = p; + for (int q : nexts.get(p)) { + if (dfn[q] == 0) { + tarjan(q); + } + if (scc[q] == 0) { + low[p] = Math.min(low[p], low[q]); + } + } + if (low[p] == dfn[p]) { + sccn++; + int top = 0; + do { + top = stack[--stackSize]; + scc[top] = sccn; + } while (top != p); + } + } + + public int[] getScc() { + return scc; + } + + public int getSccn() { + return sccn; + } + + public ArrayList> getShortGraph() { + ArrayList> shortGraph = new ArrayList>(); + for (int i = 0; i <= sccn; i++) { + shortGraph.add(new ArrayList()); + } + for (int u = 1; u <= n; u++) { + for (int v : nexts.get(u)) { + if (scc[u] != scc[v]) { + shortGraph.get(scc[u]).add(scc[v]); + } + } + } + return shortGraph; + } + + } + + // 为了测试 + public static int[][] randomMatrix(int n, int m, int v) { + int[][] map = new int[n][n]; + for (int i = 0; i < m; i++) { + map[(int) (Math.random() * n)][(int) (Math.random() * n)] = (int) (Math.random() * v) + 1; + } + return map; + } + + // 为了测试 + public static void main(String[] args) { + int n = 8; + int m = 8; + int v = 5; + int testTime = 1000; + System.out.println("测试开始"); + for (int k = 0; k < testTime; k++) { + int[][] map = randomMatrix(n, m, v); + int ans1 = minBombs1(map); + int ans2 = minBombs2(map); + if (ans1 != ans2) { + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + System.out.print(map[i][j] + " "); + } + System.out.println(); + } + System.out.println(ans1); + System.out.println(ans2); + System.out.println("出错了!"); + } + + } + System.out.println("测试结束"); + } + +} diff --git a/算法周更班/class_2022_03_2_week/Code01_MeetingCheck.java b/算法周更班/class_2022_03_2_week/Code01_MeetingCheck.java new file mode 100644 index 0000000..0757332 --- /dev/null +++ b/算法周更班/class_2022_03_2_week/Code01_MeetingCheck.java @@ -0,0 +1,202 @@ +package class_2022_03_2_week; + +import java.util.Arrays; + +// 来自字节飞书团队 +// 在字节跳动,大家都使用飞书的日历功能进行会议室的预订,遇到会议高峰时期, +// 会议室就可能不够用,现在请你实现一个算法,判断预订会议时是否有空的会议室可用。 +// 为简化问题,这里忽略会议室的大小,认为所有的会议室都是等价的, +// 只要空闲就可以容纳任意的会议,并且: +// 1. 所有的会议预订都是当日预订当日的时段 +// 2. 会议时段是一个左闭右开的时间区间,精确到分钟 +// 3. 每个会议室刚开始都是空闲状态,同一时间一个会议室只能进行一场会议 +// 4. 会议一旦预订成功就会按时进行 +// 比如上午11点到中午12点的会议即[660, 720) +// 给定一个会议室总数m +// 一个预定事件由[a,b,c]代表 : +// a代表预定动作的发生时间,早来早得; b代表会议的召开时间; c代表会议的结束时间 +// 给定一个n*3的二维数组,即可表示所有预定事件 +// 返回一个长度为n的boolean类型的数组,表示每一个预定时间是否成功 +public class Code01_MeetingCheck { + + public static boolean[] reserveMeetings(int m, int[][] meetings) { + // 会议的总场次 + int n = meetings.length; + // 开头时间,结尾时间 + int[] ranks = new int[n << 1]; + for (int i = 0; i < n; i++) { + ranks[i] = meetings[i][1]; + ranks[i + n] = meetings[i][2] - 1; + } + Arrays.sort(ranks); + // 0 : [6, 100, 200] + // 1 : [4, 30, 300] + // 30,1 100,2 200,3 300,4 + // [0,6,2,3] + // [1,4,1,4] + // + // 0 T/F , 1, T/ 2, + + // [1,4,1,4] [0,6,2,3] .... + int[][] reMeetings = new int[n][4]; + int max = 0; + for (int i = 0; i < n; i++) { + reMeetings[i][0] = i; + reMeetings[i][1] = meetings[i][0]; + reMeetings[i][2] = rank(ranks, meetings[i][1]); + reMeetings[i][3] = rank(ranks, meetings[i][2] - 1); + max = Math.max(max, reMeetings[i][3]); + } + SegmentTree st = new SegmentTree(max); + Arrays.sort(reMeetings, (a, b) -> a[1] - b[1]); + boolean[] ans = new boolean[n]; + for (int[] meeting : reMeetings) { + if (st.queryMax(meeting[2], meeting[3]) < m) { + ans[meeting[0]] = true; + st.add(meeting[2], meeting[3], 1); + } + } + return ans; + } + + // 返回>=num, 最左位置 + public static int rank(int[] sorted, int num) { + int l = 0; + int r = sorted.length - 1; + int m = 0; + int ans = 0; + while (l <= r) { + m = (l + r) / 2; + if (sorted[m] >= num) { + ans = m; + r = m - 1; + } else { + l = m + 1; + } + } + return ans + 1; + } + + public static class SegmentTree { + private int n; + private int[] max; + private int[] lazy; + + public SegmentTree(int maxSize) { + n = maxSize; + max = new int[n << 2]; + lazy = new int[n << 2]; + } + + private void pushUp(int rt) { + max[rt] = Math.max(max[rt << 1], max[rt << 1 | 1]); + } + + private void pushDown(int rt, int ln, int rn) { + if (lazy[rt] != 0) { + lazy[rt << 1] += lazy[rt]; + max[rt << 1] += lazy[rt]; + lazy[rt << 1 | 1] += lazy[rt]; + max[rt << 1 | 1] += lazy[rt]; + lazy[rt] = 0; + } + } + + public void add(int L, int R, int C) { + add(L, R, C, 1, n, 1); + } + + private void add(int L, int R, int C, int l, int r, int rt) { + if (L <= l && r <= R) { + max[rt] += C; + lazy[rt] += C; + return; + } + int mid = (l + r) >> 1; + pushDown(rt, mid - l + 1, r - mid); + if (L <= mid) { + add(L, R, C, l, mid, rt << 1); + } + if (R > mid) { + add(L, R, C, mid + 1, r, rt << 1 | 1); + } + pushUp(rt); + } + + public int queryMax(int L, int R) { + return queryMax(L, R, 1, n, 1); + } + + private int queryMax(int L, int R, int l, int r, int rt) { + if (L <= l && r <= R) { + return max[rt]; + } + int mid = (l + r) >> 1; + pushDown(rt, mid - l + 1, r - mid); + int ans = 0; + if (L <= mid) { + ans = Math.max(ans, queryMax(L, R, l, mid, rt << 1)); + } + if (R > mid) { + ans = Math.max(ans, queryMax(L, R, mid + 1, r, rt << 1 | 1)); + } + return ans; + } + + } + + // 为了测试线段树 + public static class Right { + public int[] arr; + + public Right(int maxSize) { + arr = new int[maxSize + 1]; + } + + public void add(int L, int R, int C) { + for (int i = L; i <= R; i++) { + arr[i] += C; + } + } + + public int queryMax(int L, int R) { + int ans = 0; + for (int i = L; i <= R; i++) { + ans = Math.max(ans, arr[i]); + } + return ans; + } + + } + + // 测试线段树的对数器 + public static void main(String[] args) { + int N = 50; + int V = 10; + int testTimes1 = 1000; + int testTimes2 = 1000; + System.out.println("测试线段树开始"); + for (int i = 0; i < testTimes1; i++) { + int n = (int) (Math.random() * N) + 1; + SegmentTree st = new SegmentTree(n); + Right right = new Right(n); + for (int j = 0; j < testTimes2; j++) { + int a = (int) (Math.random() * n) + 1; + int b = (int) (Math.random() * n) + 1; + int L = Math.min(a, b); + int R = Math.max(a, b); + if (Math.random() < 0.5) { + int C = (int) (Math.random() * V); + st.add(L, R, C); + right.add(L, R, C); + } else { + if (st.queryMax(L, R) != right.queryMax(L, R)) { + System.out.println("出错了!"); + } + } + } + } + System.out.println("测试线段树结束"); + } + +} diff --git a/算法周更班/class_2022_03_2_week/Code02_StringCheck.java b/算法周更班/class_2022_03_2_week/Code02_StringCheck.java new file mode 100644 index 0000000..5524a47 --- /dev/null +++ b/算法周更班/class_2022_03_2_week/Code02_StringCheck.java @@ -0,0 +1,65 @@ +package class_2022_03_2_week; + +import java.util.Arrays; + +// 来自字节飞书团队 +// 小歪每次会给你两个字符串: +// 笔记s1和关键词s2,请你写一个函数 +// 判断s2的排列之一是否是s1的子串 +// 如果是,返回true +// 否则,返回false +public class Code02_StringCheck { + + public static boolean check1(String s1, String s2) { + if (s1.length() < s2.length()) { + return false; + } + char[] str2 = s2.toCharArray(); + Arrays.sort(str2); + s2 = String.valueOf(str2); + for (int L = 0; L < s1.length(); L++) { + for (int R = L; R < s1.length(); R++) { + char[] cur = s1.substring(L, R + 1).toCharArray(); + Arrays.sort(cur); + String curSort = String.valueOf(cur); + if (curSort.equals(s2)) { + return true; + } + } + } + return false; + } + + public static boolean check2(String s1, String s2) { + if (s1.length() < s2.length()) { + return false; + } + char[] str2 = s2.toCharArray(); + int[] count = new int[256]; + for (int i = 0; i < str2.length; i++) { + count[str2[i]]++; + } + int M = str2.length; + char[] st1 = s1.toCharArray(); + int inValidTimes = 0; + int R = 0; + for (; R < M; R++) { + if (count[st1[R]]-- <= 0) { + inValidTimes++; + } + } + for (; R < st1.length; R++) { + if (inValidTimes == 0) { + return true; + } + if (count[st1[R]]-- <= 0) { + inValidTimes++; + } + if (count[st1[R - M]]++ < 0) { + inValidTimes--; + } + } + return inValidTimes == 0; + } + +} diff --git a/算法周更班/class_2022_03_2_week/Code03_AiFill.java b/算法周更班/class_2022_03_2_week/Code03_AiFill.java new file mode 100644 index 0000000..a71cb85 --- /dev/null +++ b/算法周更班/class_2022_03_2_week/Code03_AiFill.java @@ -0,0 +1,112 @@ +package class_2022_03_2_week; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.TreeSet; + +// 来自字节飞书团队 +// 语法补全功能,比如"as soon as possible" +// 当我们识别到"as soon as"时, 基本即可判定用户需要键入"possible" +// 设计一个统计词频的模型,用于这个功能 +// 类似(prefix, next word)这样的二元组 +// 比如一个上面的句子"as soon as possible" +// 有产生如下的二元组(as, soon, 1)、(as soon, as, 1)、(as soon as, possible, 1) +// 意思是这一个句子产生了如下的统计: +// 当前缀为"as",接下来的单词是"soon",有了1个期望点 +// 当前缀为"as soon",接下来的单词是"as",有了1个期望点 +// 当前缀为"as soon as",接下来的单词是"possible",有了1个期望点 +// 那么如果给你很多的句子,当然就可以产生很多的期望点,同一个前缀下,同一个next word的期望点可以累加 +// 现在给你n个句子,让你来建立统计 +// 然后给你m个句子,作为查询 +// 最后给你k,表示每个句子作为前缀的情况下,词频排在前k名的联想 +// 返回m个结果,每个结果最多k个单词 +public class Code03_AiFill { + + public static class TrieNode { + public String word; + public int times; + public HashMap nextNodes; + public TreeSet nextRanks; + + public TrieNode(String w) { + word = w; + times = 1; + nextNodes = new HashMap<>(); + nextRanks = new TreeSet<>((a, b) -> a.times != b.times ? (b.times - a.times) : a.word.compareTo(b.word)); + } + + } + + public static class AI { + public TrieNode root; + public int topk; + + public AI(List sentences, int k) { + root = new TrieNode(""); + topk = k; + for (String sentence : sentences) { + fill(sentence); + } + } + + public void fill(String sentence) { + TrieNode cur = root; + TrieNode next = null; + for (String word : sentence.split(" ")) { + if (!cur.nextNodes.containsKey(word)) { + next = new TrieNode(word); + cur.nextNodes.put(word, next); + cur.nextRanks.add(next); + } else { + next = cur.nextNodes.get(word); + cur.nextRanks.remove(next); + next.times++; + cur.nextRanks.add(next); + } + cur = next; + } + } + + public List suggest(String sentence) { + List ans = new ArrayList<>(); + TrieNode cur = root; + for (String word : sentence.split(" ")) { + if (!cur.nextNodes.containsKey(word)) { + return ans; + } else { + cur = cur.nextNodes.get(word); + } + } + for (TrieNode n : cur.nextRanks) { + ans.add(n.word); + if (ans.size() == topk) { + break; + } + } + return ans; + } + + } + + public static void main(String[] args) { + ArrayList sentences = new ArrayList<>(); + sentences.add("i think you are good"); + sentences.add("i think you are fine"); + sentences.add("i think you are good man"); + int k = 2; + AI ai = new AI(sentences, k); + for (String ans : ai.suggest("i think you are")) { + System.out.println(ans); + } + System.out.println("====="); + ai.fill("i think you are fucking good"); + ai.fill("i think you are fucking great"); + ai.fill("i think you are fucking genius"); + for (String ans : ai.suggest("i think you are")) { + System.out.println(ans); + } + System.out.println("====="); + } + +} diff --git a/算法周更班/class_2022_03_2_week/Code04_SameTeams.java b/算法周更班/class_2022_03_2_week/Code04_SameTeams.java new file mode 100644 index 0000000..65ec1b0 --- /dev/null +++ b/算法周更班/class_2022_03_2_week/Code04_SameTeams.java @@ -0,0 +1,62 @@ +package class_2022_03_2_week; + +import java.util.ArrayList; +import java.util.HashMap; + +// 来自字节飞书团队 +// 假设数组a和数组b为两组信号 +// 1) length(b) <= length(a) +// 2) 对于任意0<=i indices; + public HashMap nexts; + + public TrieNode() { + indices = new ArrayList<>(); + nexts = new HashMap<>(); + } + + } + + public static int[] sameTeamsArray(int[][] bs, int[][] as) { + int m = bs.length; + TrieNode root = new TrieNode(); + TrieNode cur = null; + for (int i = 0; i < m; i++) { + int k = bs[i].length; + cur = root; + for (int j = 1; j < k; j++) { + int diff = bs[i][j] - bs[i][j - 1]; + if (!cur.nexts.containsKey(diff)) { + cur.nexts.put(diff, new TrieNode()); + } + cur = cur.nexts.get(diff); + } + cur.indices.add(i); + } + int[] ans = new int[m]; + int n = as.length; + for (int i = 0; i < n; i++) { + int k = as[i].length; + cur = root; + for (int j = 1; j < k; j++) { + int diff = as[i][j] - as[i][j - 1]; + if (!cur.nexts.containsKey(diff)) { + break; + } + cur = cur.nexts.get(diff); + for (int index : cur.indices) { + ans[index]++; + } + } + } + return ans; + } + +} diff --git a/算法周更班/class_2022_03_2_week/Code05_NumberOfDivisibleByM.java b/算法周更班/class_2022_03_2_week/Code05_NumberOfDivisibleByM.java new file mode 100644 index 0000000..03e500a --- /dev/null +++ b/算法周更班/class_2022_03_2_week/Code05_NumberOfDivisibleByM.java @@ -0,0 +1,69 @@ +package class_2022_03_2_week; + +// 来自微软 +// 给定一个数组arr,给定一个正数M +// 如果arr[i] + arr[j]可以被M整除,并且i < j,那么(i,j)叫做一个M整除对 +// 返回arr中M整除对的总数量 +public class Code05_NumberOfDivisibleByM { + + public static int num1(int[] arr, int m) { + int n = arr.length; + int ans = 0; + for (int i = 0; i < n; i++) { + for (int j = i + 1; j < n; j++) { + if (Math.abs(arr[i] + arr[j]) % m == 0) { + ans++; + } + } + } + return ans; + } + + public static int num2(int[] arr, int m) { + int n = arr.length; + int[] cnts = new int[m]; + int ans = 0; + for (int i = n - 1; i >= 0; i--) { + int cur = (arr[i] % m + m) % m; + ans += cnts[(m - cur) % m]; + cnts[cur]++; + } + return ans; + } + + // 为了测试 + public static int[] randomArray(int n, int v) { + int[] arr = new int[n]; + for (int i = 0; i < n; i++) { + arr[i] = (int) (Math.random() * v) - (int) (Math.random() * v); + } + return arr; + } + + public static void main(String[] args) { + int len = 50; + int value = 50; + int testTime = 20000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int n = (int) (Math.random() * len) + 1; + int[] arr = randomArray(n, value); + int m = (int) (Math.random() * value) + 1; + int ans1 = num1(arr, m); + int ans2 = num2(arr, m); + if (ans1 != ans2) { + System.out.println("出错了!"); + for (int num : arr) { + System.out.print(num + " "); + } + System.out.println(); + System.out.println("m = " + m); + System.out.println(ans1); + System.out.println(ans2); + break; + } + } + System.out.println("测试结束"); + } + +} diff --git a/算法周更班/class_2022_03_2_week/Code06_JobMinDays.java b/算法周更班/class_2022_03_2_week/Code06_JobMinDays.java new file mode 100644 index 0000000..fc36ecc --- /dev/null +++ b/算法周更班/class_2022_03_2_week/Code06_JobMinDays.java @@ -0,0 +1,100 @@ +package class_2022_03_2_week; + +// 来自微软 +// 给定一个正数数组arr,长度为N,依次代表N个任务的难度,给定一个正数k +// 你只能从0任务开始,依次处理到N-1号任务结束,就是一定要从左往右处理任务 +// 只不过,难度差距绝对值不超过k的任务,可以在一天之内都完成 +// 返回完成所有任务的最少天数 +public class Code06_JobMinDays { + + public static int minDays1(int[] arr, int k) { + int n = arr.length; + int[] dp = new int[n]; + dp[0] = 1; + for (int i = 1; i < n; i++) { + dp[i] = dp[i - 1] + 1; + int min = arr[i]; + int max = arr[i]; + for (int j = i - 1; j >= 0; j--) { + min = Math.min(min, arr[j]); + max = Math.max(max, arr[j]); + if (max - min <= k) { + dp[i] = Math.min(dp[i], 1 + (j - 1 >= 0 ? dp[j - 1] : 0)); + } else { + break; + } + } + } + return dp[n - 1]; + } + + public static int minDays2(int[] arr, int k) { + int n = arr.length; + int[] dp = new int[n]; + int[] windowMax = new int[n]; + int[] windowMin = new int[n]; + int maxL = 0; + int maxR = 0; + int minL = 0; + int minR = 0; + int L = 0; + for (int i = 0; i < n; i++) { + while (maxL < maxR && arr[windowMax[maxR - 1]] <= arr[i]) { + maxR--; + } + windowMax[maxR++] = i; + while (minL < minR && arr[windowMin[minR - 1]] >= arr[i]) { + minR--; + } + windowMin[minR++] = i; + while (arr[windowMax[maxL]] - arr[windowMin[minL]] > k) { + if (windowMax[maxL] == L) { + maxL++; + } + if (windowMin[minL] == L) { + minL++; + } + L++; + } + dp[i] = 1 + (L - 1 >= 0 ? dp[L - 1] : 0); + } + return dp[n - 1]; + } + + // 为了测试 + public static int[] randomArray(int n, int v) { + int[] arr = new int[n]; + for (int i = 0; i < n; i++) { + arr[i] = (int) (Math.random() * v); + } + return arr; + } + + // 为了测试 + public static void main(String[] args) { + int len = 50; + int value = 20; + int testTime = 20000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int n = (int) (Math.random() * len) + 1; + int[] arr = randomArray(n, value); + int k = (int) (Math.random() * value); + int ans1 = minDays1(arr, k); + int ans2 = minDays2(arr, k); + if (ans1 != ans2) { + System.out.println("出错了!"); + for (int num : arr) { + System.out.print(num + " "); + } + System.out.println(); + System.out.println("k = " + k); + System.out.println(ans1); + System.out.println(ans2); + break; + } + } + System.out.println("测试结束"); + } + +} diff --git a/算法周更班/class_2022_03_2_week/Code07_MinWaitingTime.java b/算法周更班/class_2022_03_2_week/Code07_MinWaitingTime.java new file mode 100644 index 0000000..90cbeed --- /dev/null +++ b/算法周更班/class_2022_03_2_week/Code07_MinWaitingTime.java @@ -0,0 +1,95 @@ +package class_2022_03_2_week; + +import java.util.PriorityQueue; + +// 来自谷歌 +// 给定一个数组arr,长度为n +// 表示n个服务员,每个人服务一个人的时间 +// 给定一个正数m,表示有m个人等位 +// 如果你是刚来的人,请问你需要等多久? +// 假设:m远远大于n,比如n<=1000, m <= 10的9次方,该怎么做? +public class Code07_MinWaitingTime { + + public static int minWaitingTime1(int[] arr, int m) { + if (arr == null || arr.length == 0) { + return -1; + } + PriorityQueue heap = new PriorityQueue<>((a, b) -> (a[0] - b[0])); + int n = arr.length; + for (int i = 0; i < n; i++) { + heap.add(new int[] { 0, arr[i] }); + } + for (int i = 0; i < m; i++) { + int[] cur = heap.poll(); + cur[0] += cur[1]; + heap.add(cur); + } + return heap.peek()[0]; + } + + public static int minWaitingTime2(int[] arr, int m) { + if (arr == null || arr.length == 0) { + return -1; + } + int best = Integer.MAX_VALUE; + for (int num : arr) { + best = Math.min(best, num); + } + int left = 0; + int right = best * m; + int mid = 0; + int near = 0; + while (left <= right) { + mid = (left + right) / 2; + int cover = 0; + for (int num : arr) { + cover += (mid / num) + 1; + } + if (cover >= m + 1) { + near = mid; + right = mid - 1; + } else { + left = mid + 1; + } + } + return near; + } + + // 为了测试 + public static int[] randomArray(int n, int v) { + int[] arr = new int[n]; + for (int i = 0; i < n; i++) { + arr[i] = (int) (Math.random() * v) + 1; + } + return arr; + } + + // 为了测试 + public static void main(String[] args) { + int len = 50; + int value = 30; + int mMax = 3000; + int testTime = 20000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int n = (int) (Math.random() * len) + 1; + int[] arr = randomArray(n, value); + int m = (int) (Math.random() * mMax); + int ans1 = minWaitingTime1(arr, m); + int ans2 = minWaitingTime2(arr, m); + if (ans1 != ans2) { + System.out.println("出错了!"); + for (int num : arr) { + System.out.print(num + " "); + } + System.out.println(); + System.out.println("m : " + m); + System.out.println(ans1); + System.out.println(ans2); + break; + } + } + System.out.println("测试结束"); + } + +} diff --git a/算法周更班/class_2022_03_2_week/Code08_TimeNSpace1LowestCommonAncestor.java b/算法周更班/class_2022_03_2_week/Code08_TimeNSpace1LowestCommonAncestor.java new file mode 100644 index 0000000..f4e91aa --- /dev/null +++ b/算法周更班/class_2022_03_2_week/Code08_TimeNSpace1LowestCommonAncestor.java @@ -0,0 +1,111 @@ +package class_2022_03_2_week; + +// 如何时间复杂度O(N),额外空间复杂度O(1),解决最低公共祖先问题? +// 测试链接 : https://leetcode.com/problems/lowest-common-ancestor-of-a-binary-tree/ +public class Code08_TimeNSpace1LowestCommonAncestor { + + // 这个类不要提交 + public static class TreeNode { + public int val; + public TreeNode left; + public TreeNode right; + } + + // 提交以下的方法 + // 该方法亮点在于:时间复杂度O(N),额外空间复杂度O(1) + public static TreeNode lowestCommonAncestor(TreeNode head, TreeNode o1, TreeNode o2) { + if (findFirst(o1.left, o1, o2) != null || findFirst(o1.right, o1, o2) != null) { + return o1; + } + if (findFirst(o2.left, o1, o2) != null || findFirst(o2.right, o1, o2) != null) { + return o2; + } + TreeNode leftAim = findFirst(head, o1, o2); + TreeNode cur = head; + TreeNode mostRight = null; + TreeNode ans = null; + while (cur != null) { + mostRight = cur.left; + if (mostRight != null) { + while (mostRight.right != null && mostRight.right != cur) { + mostRight = mostRight.right; + } + if (mostRight.right == null) { + mostRight.right = cur; + cur = cur.left; + continue; + } else { + mostRight.right = null; + if (findLeftAim(cur.left, leftAim)) { + if (ans == null && findFirst(leftAim.right, o1, o2) != null) { + ans = leftAim; + } + leftAim = cur; + } + } + } + cur = cur.right; + } + return ans != null ? ans : (findFirst(leftAim.right, o1, o2) != null ? leftAim : head); + } + + public static boolean findLeftAim(TreeNode head, TreeNode leftAim) { + TreeNode tail = reverseEdge(head); + TreeNode cur = tail; + boolean ans = false; + while (cur != null) { + if (cur == leftAim) { + ans = true; + } + cur = cur.right; + } + reverseEdge(tail); + return ans; + } + + public static TreeNode reverseEdge(TreeNode from) { + TreeNode pre = null; + TreeNode next = null; + while (from != null) { + next = from.right; + from.right = pre; + pre = from; + from = next; + } + return pre; + } + + public static TreeNode findFirst(TreeNode head, TreeNode o1, TreeNode o2) { + if (head == null) { + return null; + } + TreeNode cur = head; + TreeNode mostRight = null; + TreeNode first = null; + while (cur != null) { + mostRight = cur.left; + if (mostRight != null) { + while (mostRight.right != null && mostRight.right != cur) { + mostRight = mostRight.right; + } + if (mostRight.right == null) { + if (first == null && (cur == o1 || cur == o2)) { + first = cur; + } + mostRight.right = cur; + cur = cur.left; + continue; + } else { + mostRight.right = null; + } + } else { + if (first == null && (cur == o1 || cur == o2)) { + first = cur; + } + } + cur = cur.right; + } + return first; + } + +} diff --git a/算法周更班/class_2022_03_3_week/Code01_LongestUncontinuousSet.java b/算法周更班/class_2022_03_3_week/Code01_LongestUncontinuousSet.java new file mode 100644 index 0000000..c32f6e2 --- /dev/null +++ b/算法周更班/class_2022_03_3_week/Code01_LongestUncontinuousSet.java @@ -0,0 +1,100 @@ +package class_2022_03_3_week; + +import java.util.Arrays; + +// 来自美团 +// 给定一个数组arr,你可以随意挑选其中的数字 +// 但是你挑选的数中,任何两个数a和b,必须Math.abs(a - b) > 1 +// 返回你最多能挑选几个数 +public class Code01_LongestUncontinuousSet { + + // 暴力方法 + // 为了验证 + public static int longestUncontinuous1(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + Arrays.sort(arr); + return process1(arr, 0, new int[arr.length], 0); + } + + public static int process1(int[] arr, int i, int[] path, int j) { + if (i == arr.length) { + for (int k = 1; k < j; k++) { + if (path[k - 1] + 1 >= path[k]) { + return 0; + } + } + return j; + } else { + int p1 = process1(arr, i + 1, path, j); + path[j] = arr[i]; + int p2 = process1(arr, i + 1, path, j + 1); + return Math.max(p1, p2); + } + } + + // 最优解 + public static int longestUncontinuous2(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + Arrays.sort(arr); + int n = arr.length; + int size = 1; + for (int i = 1; i < n; i++) { + if (arr[i] != arr[size - 1]) { + arr[size++] = arr[i]; + } + } + int[] dp = new int[size]; + dp[0] = 1; + int ans = 1; + for (int i = 1; i < size; i++) { + dp[i] = 1; + if (arr[i] - arr[i - 1] > 1) { + dp[i] = 1 + dp[i - 1]; + } + if (i - 2 >= 0 && arr[i] - arr[i - 2] > 1) { + dp[i] = Math.max(dp[i], 1 + dp[i - 2]); + } + ans = Math.max(ans, dp[i]); + } + return ans; + } + + // 为了测试 + public static int[] randomArray(int n, int v) { + int[] arr = new int[n]; + for (int i = 0; i < n; i++) { + arr[i] = (int) (Math.random() * v) - (int) (Math.random() * v); + } + return arr; + } + + // 为了测试 + public static void main(String[] args) { + int len = 10; + int value = 20; + int testTime = 2000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int n = (int) (Math.random() * len) + 1; + int[] arr = randomArray(n, value); + int ans1 = longestUncontinuous1(arr); + int ans2 = longestUncontinuous2(arr); + if (ans1 != ans2) { + System.out.println("出错了!"); + for (int num : arr) { + System.out.print(num + " "); + } + System.out.println(); + System.out.println(ans1); + System.out.println(ans2); + break; + } + } + System.out.println("测试结束"); + } + +} diff --git a/算法周更班/class_2022_03_3_week/Code02_CutDouFu.java b/算法周更班/class_2022_03_3_week/Code02_CutDouFu.java new file mode 100644 index 0000000..d0a695b --- /dev/null +++ b/算法周更班/class_2022_03_3_week/Code02_CutDouFu.java @@ -0,0 +1,54 @@ +package class_2022_03_3_week; + +import java.util.Arrays; + +// 来自美团 +// 有一块10000 * 10000 * 10000的立方体豆腐 +// 豆腐的前左下角放在(0,0,0)点,豆腐的后右上角放在(10000,10000,10000)点 +// 下面给出切法的数据结构 +// [a,b] +// a = 1,表示x = b处,一把无穷大的刀平行于yz面贯穿豆腐切过去 +// a = 2,表示y = b处,一把无穷大的刀平行于xz面贯穿豆腐切过去 +// a = 3,表示z = b处,一把无穷大的刀平行于xy面贯穿豆腐切过去 +// a = 1 or 2 or 3,0<=b<=10000 +// 给定一个n*2的二维数组,表示切了n刀 +// 返回豆腐中最大的一块体积是多少 +public class Code02_CutDouFu { + + public static long maxCut(int[][] cuts) { + if (cuts == null || cuts.length == 0) { + return 10000L * 10000L * 10000L; + } + // 0 类型 + // 1 切哪了 + Arrays.sort(cuts, (a, b) -> a[0] != b[0] ? (a[0] - b[0]) : (a[1] - b[1])); + int n = cuts.length; + int i = 0; + int xMaxDiff = 0; + int pre = 0; + while (i < n && cuts[i][0] == 1) { + xMaxDiff = Math.max(xMaxDiff, cuts[i][1] - pre); + pre = cuts[i][1]; + i++; + } + xMaxDiff = Math.max(xMaxDiff, 10000 - pre); + int yMaxDiff = 0; + pre = 0; + while (i < n && cuts[i][0] == 2) { + yMaxDiff = Math.max(yMaxDiff, cuts[i][1] - pre); + pre = cuts[i][1]; + i++; + } + yMaxDiff = Math.max(yMaxDiff, 10000 - pre); + int zMaxDiff = 0; + pre = 0; + while (i < n && cuts[i][0] == 3) { + zMaxDiff = Math.max(zMaxDiff, cuts[i][1] - pre); + pre = cuts[i][1]; + i++; + } + zMaxDiff = Math.max(zMaxDiff, 10000 - pre); + return (long) xMaxDiff * (long) yMaxDiff * (long) zMaxDiff; + } + +} diff --git a/算法周更班/class_2022_03_3_week/Code03_MaxSumOnReverseArray.java b/算法周更班/class_2022_03_3_week/Code03_MaxSumOnReverseArray.java new file mode 100644 index 0000000..4bb6815 --- /dev/null +++ b/算法周更班/class_2022_03_3_week/Code03_MaxSumOnReverseArray.java @@ -0,0 +1,105 @@ +package class_2022_03_3_week; + +// 来自美团 +// 最大子段和是 +// 一个经典问题,即对于一个数组找出其和最大的子数组。 +// 现在允许你在求解该问题之前翻转这个数組的连续一段 +// 如翻转(1,2,3,4,5,6)的第三个到第五个元素組成的子数组得到的是(1,2,5,4,3,6), +// 则翻转后该数组的最大子段和最大能达到多少? +// 来自字节 +// 几乎一样的题,来自字节笔试第4题 +// 给定两个数組values和numbers, +// values[i]表示i号宝石的单品价值 +// numbers[i]表示i号宝石的数量 +// i号宝石的总价值 = values[i] * numbers[i] +// 如果有一种魔法,可以翻转任何区间L...R的宝石,也就是改变L..R的宝石排列,变成逆序的 +// 求在允许用一次魔法的情况下,任取一段连续区间,能达到的最大价值 +// 这两个问法解法都几乎一样,区别无非是: +// 美团的: 可进行一次翻转情况下,子数组最大累加和 +// 字节的: 可进行一次翻转情况下,子数组最大价值和 +public class Code03_MaxSumOnReverseArray { + + public static int maxSumReverse1(int[] arr) { + int ans = Integer.MIN_VALUE; + for (int L = 0; L < arr.length; L++) { + for (int R = L; R < arr.length; R++) { + reverse(arr, L, R); + ans = Math.max(ans, maxSum(arr)); + reverse(arr, L, R); + } + } + return ans; + } + + public static void reverse(int[] arr, int L, int R) { + while (L < R) { + int tmp = arr[L]; + arr[L++] = arr[R]; + arr[R--] = tmp; + } + } + + public static int maxSum(int[] arr) { + int pre = arr[0]; + int max = arr[0]; + for (int i = 1; i < arr.length; i++) { + pre = Math.max(arr[i], arr[i] + pre); + max = Math.max(max, pre); + } + return max; + } + + public static int maxSumReverse2(int[] arr) { + int n = arr.length; + int[] prefix = new int[n]; + prefix[n - 1] = arr[n - 1]; + for (int i = n - 2; i >= 0; i--) { + prefix[i] = arr[i] + Math.max(0, prefix[i + 1]); + } + int ans = prefix[0]; + int suffix = arr[0]; + int maxSuffix = suffix; + for (int i = 1; i < n; i++) { + ans = Math.max(ans, maxSuffix + prefix[i]); + suffix = arr[i] + Math.max(0, suffix); + maxSuffix = Math.max(maxSuffix, suffix); + } + ans = Math.max(ans, maxSuffix); + return ans; + } + + // 为了测试 + public static int[] randomArray(int n, int v) { + int[] arr = new int[n]; + for (int i = 0; i < n; i++) { + arr[i] = (int) (Math.random() * v) - (int) (Math.random() * v); + } + return arr; + } + + // 为了测试 + public static void main(String[] args) { + int len = 50; + int value = 20; + int testTime = 20000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int n = (int) (Math.random() * len) + 1; + int[] arr = randomArray(n, value); + int ans1 = maxSumReverse1(arr); + int ans2 = maxSumReverse2(arr); + if (ans1 != ans2) { + System.out.println("出错了!"); + for (int num : arr) { + System.out.print(num + " "); + } + System.out.println(); + System.out.println(ans1); + System.out.println(ans2); + break; + } + } + System.out.println("测试结束"); + } + +} diff --git a/算法周更班/class_2022_03_3_week/Code04_ArrangeAddGetMax.java b/算法周更班/class_2022_03_3_week/Code04_ArrangeAddGetMax.java new file mode 100644 index 0000000..f7b0de6 --- /dev/null +++ b/算法周更班/class_2022_03_3_week/Code04_ArrangeAddGetMax.java @@ -0,0 +1,150 @@ +package class_2022_03_3_week; + +import java.util.Arrays; + +// 来自美团 +// void add(int L, int R, int C)代表在arr[L...R]上每个数加C +// int get(int L, int R)代表查询arr[L...R]上的累加和 +// 假设你可以在所有操作开始之前,重新排列arr +// 请返回每一次get查询的结果都加在一起最大能是多少 +// 输入参数: +// int[] arr : 原始数组 +// int[][] ops,二维数组每一行解释如下: +// [a,b,c],如果数组有3个数,表示调用add(a,b,c) +// [a,b],如果数组有2个数,表示调用get(a,b) +// a和b表示arr范围,范围假设从1开始,不从0开始 +// 输出: +// 假设你可以在开始时重新排列arr,返回所有get操作返回值累计和最大是多少? +public class Code04_ArrangeAddGetMax { + + public static int maxGets(int[] arr, int[][] ops) { + int n = arr.length; + SegmentTree getTree = new SegmentTree(n); + for (int[] op : ops) { + if (op.length == 2) { + getTree.add(op[0], op[1], 1); + } + } + int[][] getCnts = new int[n][2]; + for (int i = 1, j = 0; i <= n; i++, j++) { + getCnts[j][0] = j; + getCnts[j][1] = getTree.get(i, i); + } + Arrays.sort(getCnts, (a, b) -> a[1] - b[1]); + Arrays.sort(arr); + int[] reArrange = new int[n]; + for (int i = 0; i < n; i++) { + reArrange[getCnts[i][0]] = arr[i]; + } + SegmentTree st = new SegmentTree(reArrange); + int ans = 0; + for (int[] op : ops) { + if (op.length == 3) { + st.add(op[0], op[1], op[2]); + } else { + ans += st.get(op[0], op[1]); + } + } + return ans; + } + + public static class SegmentTree { + private int n; + private int[] arr; + private int[] sum; + private int[] lazy; + + public SegmentTree(int size) { + n = size + 1; + sum = new int[n << 2]; + lazy = new int[n << 2]; + n--; + } + + public SegmentTree(int[] origin) { + n = origin.length + 1; + arr = new int[n]; + for (int i = 1; i < n; i++) { + arr[i] = origin[i - 1]; + } + sum = new int[n << 2]; + lazy = new int[n << 2]; + build(1, --n, 1); + } + + private void build(int l, int r, int rt) { + if (l == r) { + sum[rt] = arr[l]; + return; + } + int mid = (l + r) >> 1; + build(l, mid, rt << 1); + build(mid + 1, r, rt << 1 | 1); + pushUp(rt); + } + + private void pushUp(int rt) { + sum[rt] = sum[rt << 1] + sum[rt << 1 | 1]; + } + + private void pushDown(int rt, int ln, int rn) { + if (lazy[rt] != 0) { + lazy[rt << 1] += lazy[rt]; + sum[rt << 1] += lazy[rt] * ln; + lazy[rt << 1 | 1] += lazy[rt]; + sum[rt << 1 | 1] += lazy[rt] * rn; + lazy[rt] = 0; + } + } + + public void add(int L, int R, int C) { + add(L, R, C, 1, n, 1); + } + + private void add(int L, int R, int C, int l, int r, int rt) { + if (L <= l && r <= R) { + sum[rt] += C * (r - l + 1); + lazy[rt] += C; + return; + } + int mid = (l + r) >> 1; + pushDown(rt, mid - l + 1, r - mid); + if (L <= mid) { + add(L, R, C, l, mid, rt << 1); + } + if (R > mid) { + add(L, R, C, mid + 1, r, rt << 1 | 1); + } + pushUp(rt); + } + + public int get(int L, int R) { + return query(L, R, 1, n, 1); + } + + private int query(int L, int R, int l, int r, int rt) { + if (L <= l && r <= R) { + return sum[rt]; + } + int mid = (l + r) >> 1; + pushDown(rt, mid - l + 1, r - mid); + int ans = 0; + if (L <= mid) { + ans += query(L, R, l, mid, rt << 1); + } + if (R > mid) { + ans += query(L, R, mid + 1, r, rt << 1 | 1); + } + return ans; + } + + } + + public static void main(String[] args) { + int[] arr = { 1, 2, 3, 4, 5 }; + int[][] ops = { { 1, 3 }, { 2, 4 }, { 1, 5 } }; + System.out.println(maxGets(arr, ops)); + + } + +} diff --git a/算法周更班/class_2022_03_3_week/Code05_EatFish.java b/算法周更班/class_2022_03_3_week/Code05_EatFish.java new file mode 100644 index 0000000..7ab2b89 --- /dev/null +++ b/算法周更班/class_2022_03_3_week/Code05_EatFish.java @@ -0,0 +1,106 @@ +package class_2022_03_3_week; + +// 来自bilibili +// 现在有N条鱼,每条鱼的体积为Ai,从左到右排列,数组arr给出 +// 每一轮,左边的大鱼一定会吃掉右边比自己小的第一条鱼, +// 并且每条鱼吃比自己小的鱼的事件是同时发生的。 +// 返回多少轮之后,鱼的数量会稳定 +// 注意:6 6 3 3 +// 第一轮过后 : +// 对于两个6来说,右边比自己小的第一条鱼都是第1个3,所以只有这个3被吃掉, +// 数组变成 : 6 6 3(第2个3) +// 第二轮过后 : 6 6 +// 返回2 +public class Code05_EatFish { + + public static int minTurns1(int[] arr) { + int ans = 0; + for (;; ans++) { + int[] rest = eatRest(arr); + if (arr.length == rest.length) { + break; + } + arr = rest; + } + return ans; + } + + public static int[] eatRest(int[] arr) { + if (arr.length == 0) { + return new int[0]; + } + int n = arr.length; + boolean[] delete = new boolean[n]; + int len = n; + for (int i = 0; i < n; i++) { + for (int j = i + 1; j < n; j++) { + if (arr[i] > arr[j]) { + if (!delete[j]) { + delete[j] = true; + len--; + } + break; + } + } + } + int[] rest = new int[len]; + for (int i = 0, j = 0; i < n; i++) { + if (!delete[i]) { + rest[j++] = arr[i]; + } + } + return rest; + } + + public static int minTurns2(int[] arr) { + int n = arr.length; + int[][] stack = new int[n][2]; + int stackSize = 0; + int ans = 0; + for (int i = n - 1; i >= 0; i--) { + int curAns = 0; + while (stackSize > 0 && stack[stackSize - 1][0] < arr[i]) { + curAns = Math.max(curAns + 1, stack[--stackSize][1]); + } + stack[stackSize][0] = arr[i]; + stack[stackSize++][1] = curAns; + ans = Math.max(ans, curAns); + } + return ans; + } + + // 为了测试 + public static int[] randomArray(int n, int v) { + int[] arr = new int[n]; + for (int i = 0; i < n; i++) { + arr[i] = (int) (Math.random() * v); + } + return arr; + } + + // 为了测试 + public static void main(String[] args) { + int len = 50; + int value = 20; + int testTime = 20000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int n = (int) (Math.random() * len) + 1; + int[] arr = randomArray(n, value); + int ans1 = minTurns1(arr); + int ans2 = minTurns2(arr); + if (ans1 != ans2) { + System.out.println("出错了!"); + for (int num : arr) { + System.out.print(num + " "); + } + System.out.println(); + System.out.println(ans1); + System.out.println(ans2); + break; + } + } + System.out.println("测试结束"); + } + +} diff --git a/算法周更班/class_2022_03_3_week/Code06_FinancialProduct.java b/算法周更班/class_2022_03_3_week/Code06_FinancialProduct.java new file mode 100644 index 0000000..05a6219 --- /dev/null +++ b/算法周更班/class_2022_03_3_week/Code06_FinancialProduct.java @@ -0,0 +1,51 @@ +package class_2022_03_3_week; + +import java.util.Arrays; + +// 来自银联编程比赛 +// 某公司计划推出一批投资项目。 product[i] = price 表示第 i 个理财项目的投资金额 price 。 +// 客户在按需投资时,需要遵循以下规则: +// 客户在首次对项目 product[i] 投资时,需要投入金额 price +// 对已完成首次投资的项目 product[i] 可继续追加投入, +// 但追加投入的金额需小于上一次对该项目的投入(追加投入为大于 0 的整数) +// 为控制市场稳定,每人交易次数不得大于 limit。(首次投资和追加投入均记作 1 次交易) +// 若对所有理财项目中最多进行 limit 次交易,使得投入金额总和最大,请返回这个最大值的总和。 +// 测试链接 : https://leetcode-cn.com/contest/cnunionpay-2022spring/problems/I4mOGz/ +public class Code06_FinancialProduct { + + public static long mod = 1000000007L; + + public int maxInvestment(int[] arr, int limit) { + Arrays.sort(arr); + int n = arr.length; + long ans = 0; + int r = n - 1; + int l = r; + while (limit > 0 && r != -1) { + while (l >= 0 && arr[l] == arr[r]) { + l--; + } + int big = arr[r]; + int small = l == -1 ? 0 : arr[l]; + int teams = n - l - 1; + int all = (big - small) * teams; + if (limit >= all) { + ans += get(big, small + 1, teams); + ans %= mod; + limit -= all; + } else { + int a = limit / teams; + ans += get(big, big - a + 1, teams) + (long) (big - a) * (long) (limit % teams); + ans %= mod; + limit = 0; + } + r = l; + } + return (int) (ans % mod); + } + + public static long get(long up, long down, long num) { + return num * ((up + down) * (up - down + 1) / 2); + } + +} diff --git a/算法周更班/class_2022_03_3_week/Code07_CoopDevelop.java b/算法周更班/class_2022_03_3_week/Code07_CoopDevelop.java new file mode 100644 index 0000000..d931fa6 --- /dev/null +++ b/算法周更班/class_2022_03_3_week/Code07_CoopDevelop.java @@ -0,0 +1,51 @@ +package class_2022_03_3_week; + +import java.util.HashMap; + +// 来自银联编程比赛 +// 为了不断提高用户使用的体验,开发团队正在对产品进行全方位的开发和优化。 +// 已知开发团队共有若干名成员,skills[i] 表示第 i 名开发人员掌握技能列表。 +// 如果两名成员各自拥有至少一门对方未拥有的技能,则这两名成员可以「合作开发」。 +// 请返回当前有多少对开发成员满足「合作开发」的条件。 +// 由于答案可能很大,请你返回答案对 10^9 + 7 取余的结果。 +// 测试链接 : https://leetcode-cn.com/contest/cnunionpay-2022spring/problems/lCh58I/ +public class Code07_CoopDevelop { + + public static long mod = 1000000007L; + + public static int coopDevelop(int[][] skills) { + int n = skills.length; + // key : 子集 + // value : 个数 + HashMap noFullSetsNums = new HashMap<>(); + for (int[] people : skills) { + fillNoFullMap(people, 0, 0, true, noFullSetsNums); + } + HashMap cntsNums = new HashMap<>(); + long minus = 0L; + for (int[] people : skills) { + long status = 0L; + for (int skill : people) { + status = (status << 10) | skill; + } + minus += noFullSetsNums.getOrDefault(status, 0L); + minus += cntsNums.getOrDefault(status, 0L); + cntsNums.put(status, cntsNums.getOrDefault(status, 0L) + 1); + } + long ans = (long) n * (long) (n - 1) / 2L; + return (int) ((ans - minus) % mod); + } + + public static void fillNoFullMap(int[] people, int i, long status, boolean full, + HashMap noFullSetsNums) { + if (i == people.length) { + if (!full) { + noFullSetsNums.put(status, noFullSetsNums.getOrDefault(status, 0L) + 1); + } + } else { + fillNoFullMap(people, i + 1, status, false, noFullSetsNums); + fillNoFullMap(people, i + 1, (status << 10) | people[i], full, noFullSetsNums); + } + } + +} diff --git a/算法周更班/class_2022_03_4_week/Code01_ArrangeJob.java b/算法周更班/class_2022_03_4_week/Code01_ArrangeJob.java new file mode 100644 index 0000000..8cd7e9b --- /dev/null +++ b/算法周更班/class_2022_03_4_week/Code01_ArrangeJob.java @@ -0,0 +1,66 @@ +package class_2022_03_4_week; + +import java.util.Arrays; +import java.util.PriorityQueue; + +// 来自学员的考试 +// 来自华为 +// 给定一个n*2的二维数组,表示有n个任务 +// 一个信息是任务能够开始做的时间,另一个信息是任务的结束期限,后者一定大于前者,且数值上都是正数 +// 你作为单线程的人,不能并行处理任务,但是每个任务都只需要一个单位时间完成 +// 你需要将所有任务的执行时间,位于开始做的时间和最后期限之间 +// 返回你能否做到这一点 +public class Code01_ArrangeJob { + + // 1 开 7 + // 5 闭 end没有用! + public static class TimePoint { + // 时间 + public int time; + public int end; + // add = true time 任务的添加时间 + // add = false time 任务的结束时间 + public boolean add; + + public TimePoint(int t, int e, boolean a) { + time = t; + end = e; + add = a; + } + + } + + public static boolean canDo(int[][] jobs) { + if (jobs == null || jobs.length < 2) { + return true; + } + int n = jobs.length; + TimePoint[] arr = new TimePoint[n << 1]; + for (int i = 0; i < n; i++) { + arr[i] = new TimePoint(jobs[i][0], jobs[i][1], true); + arr[i + n] = new TimePoint(jobs[i][1], jobs[i][1], false); + } + Arrays.sort(arr, (a, b) -> a.time - b.time); + PriorityQueue heap = new PriorityQueue<>(); + // 经过一个一个的时间点,遭遇事件:添加时间、检查时间 + for (int i = 0, lastTime = arr[0].time; i < arr.length; i++) { + if (arr[i].add) { + heap.add(arr[i].end); + } else { // 检查时间 + int curTime = arr[i].time; + for (int j = lastTime; j < curTime; j++) { + if (heap.isEmpty()) { + break; + } + heap.poll(); + } + if (heap.peek() <= curTime) { + return false; + } + lastTime = curTime; + } + } + return true; + } + +} diff --git a/算法周更班/class_2022_03_4_week/Code02_BuyGoodsHaveDiscount.java b/算法周更班/class_2022_03_4_week/Code02_BuyGoodsHaveDiscount.java new file mode 100644 index 0000000..5d768a8 --- /dev/null +++ b/算法周更班/class_2022_03_4_week/Code02_BuyGoodsHaveDiscount.java @@ -0,0 +1,89 @@ +package class_2022_03_4_week; + +// 来自字节内部训练营 +// 某公司游戏平台的夏季特惠开始了,你决定入手一些游戏。现在你一共有X元的预算。 +// 该平台上所有的 n 个游戏均有折扣,标号为 i 的游戏的原价a_i元,现价只要b_i元 +// 也就是说该游戏可以优惠 a_i - b_i,并且你购买该游戏能获得快乐值为 w_i +// 由于优惠的存在,你可能做出一些冲动消费导致最终买游戏的总费用超过预算, +// 只要满足 : 获得的总优惠金额不低于超过预算的总金额 +// 那在心理上就不会觉得吃亏。 +// 现在你希望在心理上不觉得吃亏的前提下,获得尽可能多的快乐值。 +// 测试链接 : https://leetcode-cn.com/problems/tJau2o/ +// 提交以下的code,将主类名字改成"Main" +// 可以直接通过 +import java.util.Scanner; + +public class Code02_BuyGoodsHaveDiscount { + + public static void main(String[] args) { + Scanner sc = new Scanner(System.in); + while (sc.hasNext()) { + int n = sc.nextInt(); + int money = sc.nextInt(); + int[] costs = new int[n]; + long[] values = new long[n]; + int size = 0; + long ans = 0; + for (int i = 0; i < n; i++) { + // 打折前 + int pre = sc.nextInt(); + // 打折后 + int pos = sc.nextInt(); + // 满足度 + int happy = sc.nextInt(); + // 节省的钱(save) = 打折前(pre) - 打折后(pos) + int save = pre - pos; + // 带来的好处(well) = 节省的钱 - 打折后(pos) + int well = save - pos; + // 比如,一件"一定要买的商品": + // 预算 = 100,商品原价 = 10,打折后 = 3 + // 那么好处 = (10 - 3) - 3 = 4 + // 所以,这件商品把预算增加到了104,一定要买 + // 接下来,比如一件"需要考虑的商品",预算 = 104,商品原价 = 10,打折后 = 8 + // 那么好处 = (10 - 8) - 8 = -6 + // 这件商品,就花掉6元! + // 也就是说,以后花的不是打折后的值,是"坏处" + int cost = -well; + if (well >= 0) { + money += well; + ans += happy; + } else { + costs[size] = cost; + values[size++] = happy; + } + + } + long[][] dp = new long[size + 1][money + 1]; + for (int a = 0; a <= size; a++) { + for (int b = 0; b <= money; b++) { + dp[a][b] = -2; + } + } + ans += process(costs, values, size, 0, money, dp); + System.out.println(ans); + } + sc.close(); + } + + public static long process(int[] costs, long[] values, int size, int i, int money, long[][] dp) { + if (money < 0) { + return -1; + } + if (i == size) { + return 0; + } + if (dp[i][money] != -2) { + return dp[i][money]; + } + long p1 = process(costs, values, size, i + 1, money, dp); + long p2 = -1; + long next = process(costs, values, size, i + 1, money - costs[i], dp); + if (next != -1) { + p2 = values[i] + next; + } + long ans = Math.max(p1, p2); + dp[i][money] = ans; + return ans; + } + +} \ No newline at end of file diff --git a/算法周更班/class_2022_03_4_week/Code03_MinTowNumberSumABS.java b/算法周更班/class_2022_03_4_week/Code03_MinTowNumberSumABS.java new file mode 100644 index 0000000..1a46cf2 --- /dev/null +++ b/算法周更班/class_2022_03_4_week/Code03_MinTowNumberSumABS.java @@ -0,0 +1,159 @@ +package class_2022_03_4_week; + +import java.util.Arrays; + +// 来自学员问题 +// 给定一个数组arr,可能有正、有负、有0,无序 +// 只能挑选两个数字,想尽量让两个数字加起来的绝对值尽量小 +// 返回可能的最小的值 +public class Code03_MinTowNumberSumABS { + + public static int minSumABS1(int[] arr) { + if (arr == null || arr.length < 2) { + return -1; + } + int ans = Integer.MAX_VALUE; + for (int i = 0; i < arr.length; i++) { + for (int j = i + 1; j < arr.length; j++) { + ans = Math.min(ans, Math.abs(arr[i] + arr[j])); + } + } + return ans; + } + + public static int minSumABS2(int[] arr) { + if (arr == null || arr.length < 2) { + return -1; + } + Arrays.sort(arr); + int n = arr.length; + int split = -1; + for (int i = 0; i < n; i++) { + if (arr[i] >= 0) { + split = i; + break; + } + } + if (split == 0) { + return arr[0] + arr[1]; + } + if (split == -1) { + return -arr[n - 2] - arr[n - 1]; + } + int ans = Integer.MAX_VALUE; + if (split + 1 < n) { + ans = arr[split] + arr[split + 1]; + } + if (split - 2 >= 0) { + ans = Math.min(ans, -arr[split - 1] - arr[split - 2]); + } + for (int i = 0; i < split; i++) { + ans = Math.min(ans, Math.abs(arr[i] + near(arr, split, -arr[i]))); + } + return ans; + } + + // arr[start...]是有序的 + // 返回离num最近的数字 + public static int near(int[] arr, int start, int num) { + int l = start; + int r = arr.length - 1; + int m = 0; + int ans = -1; + while (l <= r) { + m = (l + r) / 2; + if (arr[m] <= num) { + ans = m; + l = m + 1; + } else { + r = m - 1; + } + } + if (ans == -1) { + return arr[start]; + } else { + if (ans == arr.length - 1) { + return arr[arr.length - 1]; + } else { + if (Math.abs(arr[ans] - num) <= Math.abs(arr[ans + 1] - num)) { + return arr[ans]; + } else { + return arr[ans + 1]; + } + } + } + } + + public static int minSumABS3(int[] arr) { + if (arr == null || arr.length < 2) { + return -1; + } + Arrays.sort(arr); + int n = arr.length; + int split = -1; + for (int i = 0; i < n; i++) { + if (arr[i] >= 0) { + split = i; + break; + } + } + if (split == 0) { + return arr[0] + arr[1]; + } + if (split == -1) { + return -arr[n - 2] - arr[n - 1]; + } + int ans = Integer.MAX_VALUE; + if (split + 1 < n) { + ans = arr[split] + arr[split + 1]; + } + if (split - 2 >= 0) { + ans = Math.min(ans, -arr[split - 1] - arr[split - 2]); + } + int r = n - 1; + for (int l = 0; l < split; l++) { + ans = Math.min(ans, Math.abs(arr[l] + arr[r])); + while (r - 1 >= split && Math.abs(arr[l] + arr[r]) >= Math.abs(arr[l] + arr[r - 1])) { + ans = Math.min(ans, Math.abs(arr[l] + arr[--r])); + } + } + return ans; + } + + // 为了测试 + public static int[] randomArray(int n, int v) { + int[] arr = new int[n]; + for (int i = 0; i < n; i++) { + arr[i] = (int) (Math.random() * v) - (int) (Math.random() * v); + } + return arr; + } + + // 为了测试 + public static void main(String[] args) { + int len = 50; + int value = 500; + int testTime = 20000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int n = (int) (Math.random() * len) + 1; + int[] arr = randomArray(n, value); + int ans1 = minSumABS1(arr); + int ans2 = minSumABS2(arr); + int ans3 = minSumABS3(arr); + if (ans1 != ans2 || ans1 != ans3) { + System.out.println("出错了!"); + for (int num : arr) { + System.out.print(num + " "); + } + System.out.println(); + System.out.println(ans1); + System.out.println(ans2); + System.out.println(ans3); + break; + } + } + System.out.println("测试结束"); + } + +} diff --git a/算法周更班/class_2022_03_4_week/Code04_JumpToTargets.java b/算法周更班/class_2022_03_4_week/Code04_JumpToTargets.java new file mode 100644 index 0000000..88a286e --- /dev/null +++ b/算法周更班/class_2022_03_4_week/Code04_JumpToTargets.java @@ -0,0 +1,45 @@ +package class_2022_03_4_week; + +// 来自字节 +// 一开始在0位置,每一次都可以向左或者向右跳 +// 第i次能向左或者向右跳严格的i步 +// 请问从0到x位置,至少跳几次可以到达 +// 字节考的问题其实就是这个问题 +// leetcode测试链接 : https://leetcode.com/problems/reach-a-number/ +public class Code04_JumpToTargets { + + public static int reachNumber(long target) { + if (target == 0) { + return 0; + } + target = Math.abs(target); + long l = 0; + long r = target; + long m = 0; + long near = 0; + while (l <= r) { + m = (l + r) / 2; + if (sum(m) >= target) { + near = m; + r = m - 1; + } else { + l = m + 1; + } + } + if (sum(near) == target) { + return (int)near; + } + if (((sum(near) - target) & 1) == 1) { + near++; + } + if (((sum(near) - target) & 1) == 1) { + near++; + } + return (int)near; + } + + public static long sum(long n) { + return (n * (n + 1)) / 2; + } + +} diff --git a/算法周更班/class_2022_03_4_week/Code05_HowManyWaysFromBottomToTop.java b/算法周更班/class_2022_03_4_week/Code05_HowManyWaysFromBottomToTop.java new file mode 100644 index 0000000..2816a0e --- /dev/null +++ b/算法周更班/class_2022_03_4_week/Code05_HowManyWaysFromBottomToTop.java @@ -0,0 +1,81 @@ +package class_2022_03_4_week; + +// 来自理想汽车 +// a -> b,代表a在食物链中被b捕食 +// 给定一个有向无环图,返回 +// 这个图中从最初级动物到最顶级捕食者的食物链有几条 +// 线上测试链接 : https://www.luogu.com.cn/problem/P4017 +// 以下代码都提交,提交时把主类名改成"Main"即可 +// 注意:洛谷测试平台对java提交非常不友好,空间限制可能会卡住,C++的提交就完全不会 +// 所以提交时如果显示失败,就多提交几次,一定是能成功的 +// 这道题本身就是用到拓扑排序,没什么特别的 +// 但是为了提交能通过,逼迫我在空间上做的优化值得好好说一下,可以推广到其他题目 +import java.util.Arrays; +import java.util.Scanner; + +public class Code05_HowManyWaysFromBottomToTop { + + public static int[] in = new int[5001]; + public static boolean[] out = new boolean[5001]; + public static int[] lines = new int[5001]; + public static int[] headEdge = new int[5001]; + public static int[] queue = new int[5001]; + public static int mod = 80112002; + public static int n = 0; + + public static void main(String[] args) { + Scanner sc = new Scanner(System.in); + while (sc.hasNext()) { + Arrays.fill(in, 0); + Arrays.fill(out, false); + Arrays.fill(lines, 0); + Arrays.fill(headEdge, 0); + n = sc.nextInt(); + int m = sc.nextInt(); + int[] preEdge = new int[m + 1]; + int[] edgesTo = new int[m + 1]; + for (int i = 1; i <= m; i++) { + int from = sc.nextInt(); + int to = sc.nextInt(); + edgesTo[i] = to; + preEdge[i] = headEdge[from]; + headEdge[from] = i; + out[from] = true; + in[to]++; + } + System.out.println(howManyWays(preEdge, edgesTo)); + } + sc.close(); + } + + public static int howManyWays(int[] preEdge, int[] edgesTo) { + int ql = 0; + int qr = 0; + for (int i = 1; i <= n; i++) { + if (in[i] == 0) { + queue[qr++] = i; + lines[i] = 1; + } + } + while (ql < qr) { + int cur = queue[ql++]; + int edge = headEdge[cur]; + while (edge != 0) { + int next = edgesTo[edge]; + lines[next] = (lines[next] + lines[cur]) % mod; + if (--in[next] == 0) { + queue[qr++] = next; + } + edge = preEdge[edge]; + } + } + int ans = 0; + for (int i = 1; i <= n; i++) { + if (!out[i]) { + ans = (ans + lines[i]) % mod; + } + } + return ans; + } + +} diff --git a/算法周更班/class_2022_03_4_week/Code06_LongestContinuousTrees.java b/算法周更班/class_2022_03_4_week/Code06_LongestContinuousTrees.java new file mode 100644 index 0000000..0e77cc9 --- /dev/null +++ b/算法周更班/class_2022_03_4_week/Code06_LongestContinuousTrees.java @@ -0,0 +1,21 @@ +package class_2022_03_4_week; + +// 来自学员问题 +// 给定一个数字n,表示一开始有编号1~n的树木,列成一条直线 +// 给定一个有序数组arr,表示现在哪些树已经没了,arr[i]一定在[1,n]范围 +// 给定一个数字m,表示你可以补种多少棵树 +// 返回补种之后,最长的连续树木,有多少棵 +public class Code06_LongestContinuousTrees { + + public static int longestTrees(int n, int m, int[] arr) { + int ans = 0; + int start = 1; + for (int i = 0, j = m; j < arr.length; i++, j++) { + ans = Math.max(ans, arr[j] - start); + start = arr[i] + 1; + } + ans = Math.max(ans, n - start + 1); + return ans; + } + +} diff --git a/算法周更班/class_2022_03_4_week/Code07_IrregularSudoku.java b/算法周更班/class_2022_03_4_week/Code07_IrregularSudoku.java new file mode 100644 index 0000000..0c2e7f4 --- /dev/null +++ b/算法周更班/class_2022_03_4_week/Code07_IrregularSudoku.java @@ -0,0 +1,127 @@ +package class_2022_03_4_week; + +// 来自网易 +// 不规则数独问题 +// 3*3填数独 +// 每一行要填1~3 +// 每一列要填1~3 +// 3*3的区域会拆分成不规则的三个集团区域 +// 每个集团区域3个格子 +// 每个集团的区域都一定是一个连在一起的整体,可能不规则 +// 每个集团内要填1~3 +// 如果只有一个解返回"Unique",如果有多个解返回"Multiple",如果没有解返回"No" +// 解析请看,大厂刷题班,28节,leetcode原题,数独那两个题 +// 本题就是改变一下桶的归属而已 +public class Code07_IrregularSudoku { + + // sudoku[i][j] == 0,代表这个位置没有数字,需要填 + // sudoku[i][j] != 0,代表这个位置有数字,不需要填 + // map[0] = {0,0}、{0,1}、{1,0} 代表0集团拥有的三个点 + // map[i] = {a,b}、{c,d}、{e,f} 代表i集团拥有的三个点 + public static String solution(int[][] sudoku, int[][][] map) { + boolean[][] row = new boolean[3][4]; + boolean[][] col = new boolean[3][4]; + boolean[][] bucket = new boolean[3][4]; + int[][] own = new int[3][3]; + for (int i = 0; i < 3; i++) { + for (int[] arr : map[i]) { + own[arr[0]][arr[1]] = i; + } + } + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { + if (sudoku[i][j] != 0) { + row[i][sudoku[i][j]] = true; + col[j][sudoku[i][j]] = true; + bucket[own[i][j]][sudoku[i][j]] = true; + } + } + } + int ans = process(sudoku, 0, 0, row, col, bucket, own); + return ans == 0 ? "No" : (ans == 1 ? "Unique" : "Multiple"); + } + + public static int process(int[][] sudoku, int i, int j, boolean[][] row, boolean[][] col, boolean[][] bucket, + int[][] own) { + if (i == 3) { + return 1; + } + int nexti = j != 2 ? i : i + 1; + int nextj = j != 2 ? j + 1 : 0; + if (sudoku[i][j] != 0) { + return process(sudoku, nexti, nextj, row, col, bucket, own); + } else { + int ans = 0; + int bid = own[i][j]; + for (int num = 1; num <= 3; num++) { + if ((!row[i][num]) && (!col[j][num]) && (!bucket[bid][num])) { + row[i][num] = true; + col[j][num] = true; + bucket[bid][num] = true; + sudoku[i][j] = num; + ans += process(sudoku, nexti, nextj, row, col, bucket, own); + row[i][num] = false; + col[j][num] = false; + bucket[bid][num] = false; + sudoku[i][j] = 0; + if (ans > 1) { + return ans; + } + } + } + return ans; + } + } + + public static void main(String[] args) { + int[][] sudoku1 = { + { 0, 2, 0 }, + { 1, 0, 2 }, + { 0, 0, 0 } + }; + int[][][] map1 = { + { { 0, 0 }, { 0, 1 }, { 1, 0 } }, + { { 0, 2 }, { 1, 1 }, { 1, 2 } }, + { { 2, 0 }, { 2, 1 }, { 2, 2 } } + }; + System.out.println(solution(sudoku1, map1)); + + int[][] sudoku2 = { + { 0, 0, 3 }, + { 0, 0, 0 }, + { 0, 0, 0 } + }; + int[][][] map2 = { + { { 0, 0 }, { 1, 0 }, { 1, 1 } }, + { { 0, 1 }, { 0, 2 }, { 1, 2 } }, + { { 2, 0 }, { 2, 1 }, { 2, 2 } } + }; + System.out.println(solution(sudoku2, map2)); + + int[][] sudoku3 = { + { 0, 0, 3 }, + { 1, 0, 0 }, + { 0, 0, 2 } + }; + int[][][] map3 = { + { { 0, 0 }, { 1, 0 }, { 1, 1 } }, + { { 0, 1 }, { 0, 2 }, { 1, 2 } }, + { { 2, 0 }, { 2, 1 }, { 2, 2 } } + }; + System.out.println(solution(sudoku3, map3)); + + int[][] sudoku4 = { + { 3, 0, 3 }, + { 1, 0, 0 }, + { 0, 0, 2 } + }; + int[][][] map4 = { + { { 0, 0 }, { 1, 0 }, { 1, 1 } }, + { { 0, 1 }, { 0, 2 }, { 1, 2 } }, + { { 2, 0 }, { 2, 1 }, { 2, 2 } } + }; + System.out.println(solution(sudoku4, map4)); + + } + +} diff --git a/算法周更班/class_2022_03_4_week/Code08_EggXtoY.java b/算法周更班/class_2022_03_4_week/Code08_EggXtoY.java new file mode 100644 index 0000000..424af25 --- /dev/null +++ b/算法周更班/class_2022_03_4_week/Code08_EggXtoY.java @@ -0,0 +1,82 @@ +package class_2022_03_4_week; + +// 来自学员问题 +// 大妈一开始手上有x个鸡蛋,她想让手上的鸡蛋数量变成y +// 操作1 : 从仓库里拿出1个鸡蛋到手上,x变成x+1个 +// 操作2 : 如果手上的鸡蛋数量是3的整数倍,大妈可以直接把三分之二的鸡蛋放回仓库,手里留下三分之一 +// 返回从x到y的最小操作次数 +// 1 <= x,y <= 10^18 +// 练一下,用平凡解做限制 +public class Code08_EggXtoY { + + // 彻底贪心! + public static int minTimes1(int x, int y) { + if (x <= y) { + return y - x; + } + // 0 0 + // 1 2 + // 2 1 + int mod = x % 3; + // 鸡蛋拿到3的整数倍,需要耗费的行动点数 + int need = mod == 0 ? 0 : (mod == 1 ? 2 : 1); + return need + 1 + minTimes1((x + 2) / 3, y); + } + + public static int minTimes2(int x, int y) { + if (x <= y) { + return y - x; + } + int limit = minTimes1(x, y); + int[][] dp = new int[x + limit + 1][limit + 1]; + return process(x, y, 0, limit, dp); + } + + // 当前鸡蛋数量cur,目标aim + // 之前已经用了多少行动点,pre + // limit : 一定行动点,超过limit,不需要尝试了! + public static int process(int cur, int aim, int pre, int limit, int[][] dp) { + if (pre > limit) { + return Integer.MAX_VALUE; + } + if (dp[cur][pre] != 0) { + return dp[cur][pre]; + } + int ans = 0; + if (cur == aim) { + ans = pre; + } else { + int p1 = process(cur + 1, aim, pre + 1, limit, dp); + int p2 = Integer.MAX_VALUE; + if (cur % 3 == 0) { + p2 = process(cur / 3, aim, pre + 1, limit, dp); + } + ans = Math.min(p1, p2); + } + dp[cur][pre] = ans; + return ans; + } + + public static void main(String[] args) { + int max = 3000; + int testTime = 500; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int x = (int) (Math.random() * max) + 1; + int y = (int) (Math.random() * max) + 1; + int ans1 = minTimes1(x, y); + int ans2 = minTimes2(x, y); + if (ans1 != ans2) { + System.out.println("出错了!"); + System.out.println("x = " + x); + System.out.println("y = " + y); + System.out.println(ans1); + System.out.println(ans2); + break; + } + } + System.out.println("测试结束"); + + } + +} diff --git a/算法周更班/class_2022_03_5_week/Code01_KMAlgorithm.java b/算法周更班/class_2022_03_5_week/Code01_KMAlgorithm.java new file mode 100644 index 0000000..adc3ced --- /dev/null +++ b/算法周更班/class_2022_03_5_week/Code01_KMAlgorithm.java @@ -0,0 +1,166 @@ +// 注意本文件中,graph不是邻接矩阵的含义,而是一个二部图 +// 在长度为N的邻接矩阵matrix中,所有的点有N个,matrix[i][j]表示点i到点j的距离或者权重 +// 而在二部图graph中,所有的点有2*N个,行所对应的点有N个,列所对应的点有N个 +// 而且认为,行所对应的点之间是没有路径的,列所对应的点之间也是没有路径的! + +package class_2022_03_5_week; + +import java.util.Arrays; + +// km算法 +// O(N^3),最大匹配问题,最优的解! +public class Code01_KMAlgorithm { + + // 暴力解 + public static int right(int[][] graph) { + int N = graph.length; + int[] to = new int[N]; + for (int i = 0; i < N; i++) { + to[i] = 1; + } + return process(0, to, graph); + } + + public static int process(int from, int[] to, int[][] graph) { + if (from == graph.length) { + return 0; + } + int ans = 0; + for (int i = 0; i < to.length; i++) { + if (to[i] == 1) { + to[i] = 0; + ans = Math.max(ans, graph[from][i] + process(from + 1, to, graph)); + to[i] = 1; + } + } + return ans; + } + + public static int km(int[][] graph) { + int N = graph.length; + int[] match = new int[N]; + int[] lx = new int[N]; + int[] ly = new int[N]; + // dfs过程中,碰过的点! + boolean[] x = new boolean[N]; + boolean[] y = new boolean[N]; + // 降低的预期! + // 公主上,打一个,降低预期的值,只维持最小! + int[] slack = new int[N]; + int invalid = Integer.MAX_VALUE; + for (int i = 0; i < N; i++) { + match[i] = -1; + lx[i] = -invalid; + for (int j = 0; j < N; j++) { + lx[i] = Math.max(lx[i], graph[i][j]); + } + ly[i] = 0; + } + for (int from = 0; from < N; from++) { + for (int i = 0; i < N; i++) { + slack[i] = invalid; + } + Arrays.fill(x, false); + Arrays.fill(y, false); + // dfs() : from王子,能不能不降预期,匹配成功! + // 能:dfs返回true! + // 不能:dfs返回false! + while (!dfs(from, x, y, lx, ly, match, slack, graph)) { + // 刚才的dfs,失败了! + // 需要拿到,公主的slack里面,预期下降幅度的最小值! + int d = invalid; + for (int i = 0; i < N; i++) { + if (!y[i] && slack[i] < d) { + d = slack[i]; + } + } + // 按照最小预期来调整预期 + for (int i = 0; i < N; i++) { + if (x[i]) { + lx[i] = lx[i] - d; + } + if (y[i]) { + ly[i] = ly[i] + d; + } + } + Arrays.fill(x, false); + Arrays.fill(y, false); + // 然后回到while里,再次尝试 + } + } + int ans = 0; + for (int i = 0; i < N; i++) { + ans += (lx[i] + ly[i]); + } + return ans; + } + + // from, 当前的王子 + // x,王子碰没碰过 + // y, 公主碰没碰过 + // lx,所有王子的预期 + // ly, 所有公主的预期 + // match,所有公主,之前的分配,之前的爷们! + // slack,连过,但没允许的公主,最小下降的幅度 + // map,报价,所有王子对公主的报价 + // 返回,from号王子,不降预期能不能配成! + public static boolean dfs(int from, boolean[] x, boolean[] y, int[] lx, int[] ly, int[] match, int[] slack, + int[][] map) { + int N = map.length; + x[from] = true; + for (int to = 0; to < N; to++) { + if (!y[to]) { // 只有没dfs过的公主,才会去尝试 + int d = lx[from] + ly[to] - map[from][to]; + if (d != 0) {// 如果当前的路不符合预期,更新公主的slack值 + slack[to] = Math.min(slack[to], d); + } else { // 如果当前的路符合预期,尝试直接拿下,或者抢夺让之前的安排倒腾去 + y[to] = true; + if (match[to] == -1 || dfs(match[to], x, y, lx, ly, match, slack, map)) { + match[to] = from; + return true; + } + } + } + } + return false; + } + + // 为了测试 + public static int[][] randomGraph(int N, int V) { + int[][] graph = new int[N][N]; + for (int i = 0; i < N; i++) { + for (int j = i + 1; j < N; j++) { + int num = (int) (Math.random() * V); + graph[i][j] = num; + graph[j][i] = num; + } + } + return graph; + } + + // 为了测试 + public static void main(String[] args) { + int N = 10; + int V = 20; + int testTime = 100; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int[][] graph = randomGraph(N, V); + int ans1 = right(graph); + int ans2 = km(graph); + if (ans1 != ans2) { + System.out.println("Oops!"); + for (int r = 0; r < graph.length; r++) { + for (int c = 0; c < graph.length; c++) { + System.out.print(graph[r][c] + " "); + } + System.out.println(); + } + System.out.println(ans1); + System.out.println(ans2); + } + } + System.out.println("测试结束"); + } + +} diff --git a/算法周更班/class_2022_03_5_week/Code02_ToAllSpace.java b/算法周更班/class_2022_03_5_week/Code02_ToAllSpace.java new file mode 100644 index 0000000..00e499a --- /dev/null +++ b/算法周更班/class_2022_03_5_week/Code02_ToAllSpace.java @@ -0,0 +1,213 @@ +package class_2022_03_5_week; + +import java.util.Arrays; + +// 来自微软 +// 在N*N的正方形棋盘中,有N*N个棋子,那么每个格子正好可以拥有一个棋子 +// 但是现在有些棋子聚集到一个格子上了,比如: +// 2 0 3 +// 0 1 0 +// 3 0 0 +// 如上的二维数组代表,一共3*3个格子 +// 但是有些格子有2个棋子、有些有3个、有些有1个、有些没有 +// 请你用棋子移动的方式,让每个格子都有一个棋子 +// 每个棋子可以上、下、左、右移动,每移动一步算1的代价 +// 返回最小的代价 +public class Code02_ToAllSpace { + + // 暴力解 + // 作为对数器 + public static int minDistance1(int[][] map) { + int n = 0; + int m = 0; + for (int i = 0; i < map.length; i++) { + for (int j = 0; j < map[0].length; j++) { + n += Math.max(0, map[i][j] - 1); + m += map[i][j] == 0 ? 1 : 0; + } + } + if (n != m || n == 0) { + return 0; + } + int[][] nodes = new int[n][2]; + int[][] space = new int[m][2]; + n = 0; + m = 0; + for (int i = 0; i < map.length; i++) { + for (int j = 0; j < map[0].length; j++) { + for (int k = 2; k <= map[i][j]; k++) { + nodes[n][0] = i; + nodes[n++][1] = j; + } + if (map[i][j] == 0) { + space[m][0] = i; + space[m++][1] = j; + } + } + } + return process1(nodes, 0, space); + } + + public static int process1(int[][] nodes, int index, int[][] space) { + int ans = 0; + if (index == nodes.length) { + for (int i = 0; i < nodes.length; i++) { + ans += distance(nodes[i], space[i]); + } + } else { + ans = Integer.MAX_VALUE; + for (int i = index; i < nodes.length; i++) { + swap(nodes, index, i); + ans = Math.min(ans, process1(nodes, index + 1, space)); + swap(nodes, index, i); + } + } + return ans; + } + + public static void swap(int[][] nodes, int i, int j) { + int[] tmp = nodes[i]; + nodes[i] = nodes[j]; + nodes[j] = tmp; + } + + public static int distance(int[] a, int[] b) { + return Math.abs(a[0] - b[0]) + Math.abs(a[1] - b[1]); + } + + // 正式方法 + // KM算法 + public static int minDistance2(int[][] map) { + int n = 0; + int m = 0; + for (int i = 0; i < map.length; i++) { + for (int j = 0; j < map[0].length; j++) { + n += Math.max(0, map[i][j] - 1); + m += map[i][j] == 0 ? 1 : 0; + } + } + if (n != m || n == 0) { + return 0; + } + int[][] nodes = new int[n][2]; + int[][] space = new int[m][2]; + n = 0; + m = 0; + for (int i = 0; i < map.length; i++) { + for (int j = 0; j < map[0].length; j++) { + for (int k = 2; k <= map[i][j]; k++) { + nodes[n][0] = i; + nodes[n++][1] = j; + } + if (map[i][j] == 0) { + space[m][0] = i; + space[m++][1] = j; + } + } + } + int[][] graph = new int[n][n]; + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + graph[i][j] = -distance(nodes[i], space[j]); + } + } + return -km(graph); + } + + public static int km(int[][] graph) { + int N = graph.length; + int[] match = new int[N]; + int[] lx = new int[N]; + int[] ly = new int[N]; + boolean[] x = new boolean[N]; + boolean[] y = new boolean[N]; + int[] slack = new int[N]; + int invalid = Integer.MAX_VALUE; + for (int i = 0; i < N; i++) { + match[i] = -1; + lx[i] = -invalid; + for (int j = 0; j < N; j++) { + lx[i] = Math.max(lx[i], graph[i][j]); + } + ly[i] = 0; + } + for (int from = 0; from < N; from++) { + for (int i = 0; i < N; i++) { + slack[i] = invalid; + } + Arrays.fill(x, false); + Arrays.fill(y, false); + while (!dfs(from, x, y, lx, ly, match, slack, graph)) { + int d = invalid; + for (int i = 0; i < N; i++) { + if (!y[i] && slack[i] < d) { + d = slack[i]; + } + } + for (int i = 0; i < N; i++) { + if (x[i]) { + lx[i] = lx[i] - d; + } + if (y[i]) { + ly[i] = ly[i] + d; + } + } + Arrays.fill(x, false); + Arrays.fill(y, false); + } + } + int ans = 0; + for (int i = 0; i < N; i++) { + ans += (lx[i] + ly[i]); + } + return ans; + } + + public static boolean dfs(int from, boolean[] x, boolean[] y, int[] lx, int[] ly, int[] match, int[] slack, + int[][] map) { + int N = map.length; + x[from] = true; + for (int to = 0; to < N; to++) { + if (!y[to]) { + int d = lx[from] + ly[to] - map[from][to]; + if (d != 0) { + slack[to] = Math.min(slack[to], d); + } else { + y[to] = true; + if (match[to] == -1 || dfs(match[to], x, y, lx, ly, match, slack, map)) { + match[to] = from; + return true; + } + } + } + } + return false; + } + + // 为了测试 + public static int[][] randomValidMatrix(int len) { + int[][] ans = new int[len][len]; + int all = len * len; + for (int i = 1; i <= all; i++) { + ans[(int) (Math.random() * len)][(int) (Math.random() * len)]++; + } + return ans; + } + + public static void main(String[] args) { + int len = 4; + int testTimes = 1000; + System.out.println("测试开始"); + for (int i = 0; i < testTimes; i++) { + int[][] map = randomValidMatrix(len); + int ans1 = minDistance1(map); + int ans2 = minDistance2(map); + if (ans1 != ans2) { + System.out.println("出错了!"); + break; + } + } + System.out.println("测试结束"); + } + +} diff --git a/算法周更班/class_2022_03_5_week/Code03_MaximumAndSumOfArray.java b/算法周更班/class_2022_03_5_week/Code03_MaximumAndSumOfArray.java new file mode 100644 index 0000000..433d74d --- /dev/null +++ b/算法周更班/class_2022_03_5_week/Code03_MaximumAndSumOfArray.java @@ -0,0 +1,90 @@ +package class_2022_03_5_week; + +import java.util.Arrays; + +// 测试链接 : https://leetcode.com/problems/maximum-and-sum-of-array/ +public class Code03_MaximumAndSumOfArray { + + public static int maximumANDSum(int[] arr, int m) { + m <<= 1; + int[][] graph = new int[m][m]; + for (int i = 0; i < arr.length; i++) { + for (int j = 0, num = 1; j < m; num++, j += 2) { + graph[i][j] = arr[i] & num; + graph[i][j + 1] = arr[i] & num; + } + } + return km(graph); + } + + public static int km(int[][] graph) { + int N = graph.length; + int[] match = new int[N]; + int[] lx = new int[N]; + int[] ly = new int[N]; + boolean[] x = new boolean[N]; + boolean[] y = new boolean[N]; + int[] slack = new int[N]; + int invalid = Integer.MAX_VALUE; + for (int i = 0; i < N; i++) { + match[i] = -1; + lx[i] = -invalid; + for (int j = 0; j < N; j++) { + lx[i] = Math.max(lx[i], graph[i][j]); + } + ly[i] = 0; + } + for (int from = 0; from < N; from++) { + for (int i = 0; i < N; i++) { + slack[i] = invalid; + } + Arrays.fill(x, false); + Arrays.fill(y, false); + while (!dfs(from, x, y, lx, ly, match, slack, graph)) { + int d = invalid; + for (int i = 0; i < N; i++) { + if (!y[i] && slack[i] < d) { + d = slack[i]; + } + } + for (int i = 0; i < N; i++) { + if (x[i]) { + lx[i] = lx[i] - d; + } + if (y[i]) { + ly[i] = ly[i] + d; + } + } + Arrays.fill(x, false); + Arrays.fill(y, false); + } + } + int ans = 0; + for (int i = 0; i < N; i++) { + ans += (lx[i] + ly[i]); + } + return ans; + } + + public static boolean dfs(int from, boolean[] x, boolean[] y, int[] lx, int[] ly, int[] match, int[] slack, + int[][] map) { + int N = map.length; + x[from] = true; + for (int to = 0; to < N; to++) { + if (!y[to]) { + int d = lx[from] + ly[to] - map[from][to]; + if (d != 0) { + slack[to] = Math.min(slack[to], d); + } else { + y[to] = true; + if (match[to] == -1 || dfs(match[to], x, y, lx, ly, match, slack, map)) { + match[to] = from; + return true; + } + } + } + } + return false; + } + +} diff --git a/算法周更班/class_2022_03_5_week/Code04_KillAllSameTime.java b/算法周更班/class_2022_03_5_week/Code04_KillAllSameTime.java new file mode 100644 index 0000000..051368c --- /dev/null +++ b/算法周更班/class_2022_03_5_week/Code04_KillAllSameTime.java @@ -0,0 +1,18 @@ +package class_2022_03_5_week; + +// 来自网易 +// 我军一起干掉敌人的最少移动数 +// km算法的又一个题 +// 给定一个矩阵int[][] matrix +// matrix[i][j] == -2,代表此处(i,j)有山脉,无法通行 +// matrix[i][j] == -1,代表此处(i,j)是一个敌军 +// matrix[i][j] == 0,代表此处(i,j)是空地,可以自由行动 +// matrix[i][j] > 0,代表此处(i,j)是一个我军,行动能力就是matrix[i][j] +// 我军只能上、下、左、右移动,只可以穿过同样是我军的地点和空地的地点,但是最多移动matrix[i][j]步 +// 任何一个我军都不能穿过山脉,任何一个我军可以来到敌军的位置,表示消灭了敌军,但是如果这么做了,这个我军就不能再移动了 +// 你可以任意决定所有我军的行动策略,每一步你都可以随意选择一个友军移动随意方向的,但是必须合法。 +// 如果你可以让所有我军在消耗完自身的行动能力之前,消灭所有的敌军,请返回总距离的最小值 +// 如果你就是无法消灭所有敌军,返回-1 +public class Code04_KillAllSameTime { + +} diff --git a/算法周更班/class_2022_04_1_week/Code01_FourNumbersMinusOne.java b/算法周更班/class_2022_04_1_week/Code01_FourNumbersMinusOne.java new file mode 100644 index 0000000..006bd9d --- /dev/null +++ b/算法周更班/class_2022_04_1_week/Code01_FourNumbersMinusOne.java @@ -0,0 +1,58 @@ +package class_2022_04_1_week; + +import java.util.Arrays; + +// 来自阿里笔试 +// 牛牛今年上幼儿园了,老师叫他学习减法 +// 老师给了他5个数字,他每次操作可以选择其中的4个数字减1 +// 减一之后的数字不能小于0,因为幼儿园的牛牛还没有接触过负数 +// 现在牛牛想知道,自己最多可以进行多少次这样的操作 +// 扩展问题来自leetcode 2141,掌握了这个题原始问题就非常简单了 +// leetcode测试链接 : https://leetcode.com/problems/maximum-running-time-of-n-computers/ +public class Code01_FourNumbersMinusOne { + + public static long maxRunTime(int n, int[] arr) { + Arrays.sort(arr); + int size = arr.length; + long[] sums = new long[size]; + sums[0] = arr[0]; + for (int i = 1; i < size; i++) { + sums[i] = sums[i - 1] + arr[i]; + } + long l = 0; + long m = 0; + long r = sums[size - 1] / n; + long ans = -1; + while (l <= r) { + m = (l + r) / 2; + if (ok(arr, sums, m, n)) { + ans = m; + l = m + 1; + } else { + r = m - 1; + } + } + return ans; + } + + public static boolean ok(int[] arr, long[] sum, long time, int num) { + int l = 0; + int m = 0; + int r = arr.length - 1; + int left = arr.length; + // >= time 最左 + while (l <= r) { + m = (l + r) / 2; + if (arr[m] >= time) { + left = m; + r = m - 1; + } else { + l = m + 1; + } + } + num -= arr.length - left; + long rest = left == 0 ? 0 : sum[left - 1]; + return time * (long) num <= rest; + } + +} diff --git a/算法周更班/class_2022_04_1_week/Code02_MaxOrSmallestSubarray.java b/算法周更班/class_2022_04_1_week/Code02_MaxOrSmallestSubarray.java new file mode 100644 index 0000000..702618b --- /dev/null +++ b/算法周更班/class_2022_04_1_week/Code02_MaxOrSmallestSubarray.java @@ -0,0 +1,116 @@ +package class_2022_04_1_week; + +// 来自学员问题 +// 找到非负数组中拥有"最大或的结果"的最短子数组 +public class Code02_MaxOrSmallestSubarray { + + public static int longest1(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + int max = 0; + for (int num : arr) { + max |= num; + } + if (max == 0) { + return 1; + } + int n = arr.length; + int ans = n; + for (int i = 0; i < n; i++) { + int cur = 0; + for (int j = i; j < n; j++) { + cur |= arr[j]; + if (cur == max) { + ans = Math.min(ans, j - i + 1); + break; + } + } + } + return ans; + } + + public static int longest2(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + int max = 0; + for (int num : arr) { + max |= num; + } + if (max == 0) { + return 1; + } + int n = arr.length; + int ans = n; + int[] counts = new int[32]; + int l = 0; + int cur = 0; + for (int r = 0; r < n; r++) { + cur = add(counts, arr[r]); + while (cur == max) { + cur = delete(counts, arr[l++]); + } + if (l > 0) { + cur = add(counts, arr[--l]); + } + if (cur == max) { + ans = Math.min(ans, r - l + 1); + } + } + return ans; + } + + public static int add(int[] counts, int num) { + int ans = 0; + for (int i = 0; i < 32; i++) { + counts[i] += (num & (1 << i)) != 0 ? 1 : 0; + ans |= (counts[i] > 0 ? 1 : 0) << i; + } + return ans; + } + + public static int delete(int[] counts, int num) { + int ans = 0; + for (int i = 0; i < 32; i++) { + counts[i] -= (num & (1 << i)) != 0 ? 1 : 0; + ans |= (counts[i] > 0 ? 1 : 0) << i; + } + return ans; + } + + // 为了测试 + public static int[] randomArray(int n, int v) { + int[] arr = new int[n]; + for (int i = 0; i < n; i++) { + arr[i] = (int) (Math.random() * v); + } + return arr; + } + + // 为了测试 + public static void main(String[] args) { + int len = 50; + int value = 50000; + int testTime = 20000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int n = (int) (Math.random() * len) + 1; + int[] arr = randomArray(n, value); + int ans1 = longest1(arr); + int ans2 = longest2(arr); + if (ans1 != ans2) { + System.out.println("出错了!"); + for (int num : arr) { + System.out.print(num + " "); + } + System.out.println(); + System.out.println(ans1); + System.out.println(ans2); + break; + } + } + System.out.println("测试结束"); + } + +} diff --git a/算法周更班/class_2022_04_1_week/Code03_ArrangeMeetingPosCancelPre.java b/算法周更班/class_2022_04_1_week/Code03_ArrangeMeetingPosCancelPre.java new file mode 100644 index 0000000..ea560b7 --- /dev/null +++ b/算法周更班/class_2022_04_1_week/Code03_ArrangeMeetingPosCancelPre.java @@ -0,0 +1,219 @@ +package class_2022_04_1_week; + +import java.util.ArrayList; +import java.util.Arrays; + +// 来自通维数码 +// 每个会议给定开始和结束时间 +// 后面的会议如果跟前面的会议有任何冲突,完全取消冲突的、之前的会议,安排当前的 +// 给定一个会议数组,返回安排的会议列表 +public class Code03_ArrangeMeetingPosCancelPre { + + // 比较暴力的解 + // 为了对数器来验证 + public static ArrayList arrange1(int[][] meetings) { + int max = 0; + for (int[] meeting : meetings) { + max = Math.max(max, meeting[1]); + } + boolean[] occupy = new boolean[max + 1]; + ArrayList ans = new ArrayList<>(); + for (int i = meetings.length - 1; i >= 0; i--) { + int[] cur = meetings[i]; + boolean add = true; + for (int j = cur[0]; j < cur[1]; j++) { + if (occupy[j]) { + add = false; + break; + } + } + if (add) { + ans.add(cur); + } + for (int j = cur[0]; j < cur[1]; j++) { + occupy[j] = true; + } + } + return ans; + } + + // 最优解 + // 会议有N个,时间复杂度O(N*logN) + public static ArrayList arrange2(int[][] meetings) { + int n = meetings.length; + // n << 1 -> n*2 + int[] rank = new int[n << 1]; + for (int i = 0; i < meetings.length; i++) { + rank[i] = meetings[i][0]; // 会议开头点 + rank[i + n] = meetings[i][1] - 1; // 会议的结束点 + } + Arrays.sort(rank); + // n*2 + SegmentTree st = new SegmentTree(n << 1); + // 哪些会议安排了,放入到ans里去! + ArrayList ans = new ArrayList<>(); + // 从右往左遍历,意味着,后出现的会议,先看看能不能安排 + for (int i = meetings.length - 1; i >= 0; i--) { + // cur 当前会议 + int[] cur = meetings[i]; + // cur[0] = 17万 -> 6 + int from = rank(rank, cur[0]); + // cur[1] = 90 -> 89 -> 2 + int to = rank(rank, cur[1] - 1); + if (st.sum(from, to) == 0) { + ans.add(cur); + } + st.add(from, to, 1); + } + return ans; + } + + public static int rank(int[] rank, int num) { + int l = 0; + int r = rank.length - 1; + int m = 0; + int ans = 0; + while (l <= r) { + m = (l + r) / 2; + if (rank[m] >= num) { + ans = m; + r = m - 1; + } else { + l = m + 1; + } + } + return ans + 1; + } + + public static class SegmentTree { + private int n; + private int[] sum; + private int[] lazy; + + public SegmentTree(int size) { + n = size + 1; + sum = new int[n << 2]; + lazy = new int[n << 2]; + n--; + } + + private void pushUp(int rt) { + sum[rt] = sum[rt << 1] + sum[rt << 1 | 1]; + } + + private void pushDown(int rt, int ln, int rn) { + if (lazy[rt] != 0) { + lazy[rt << 1] += lazy[rt]; + sum[rt << 1] += lazy[rt] * ln; + lazy[rt << 1 | 1] += lazy[rt]; + sum[rt << 1 | 1] += lazy[rt] * rn; + lazy[rt] = 0; + } + } + + public void add(int L, int R, int C) { + add(L, R, C, 1, n, 1); + } + + private void add(int L, int R, int C, int l, int r, int rt) { + if (L <= l && r <= R) { + sum[rt] += C * (r - l + 1); + lazy[rt] += C; + return; + } + int mid = (l + r) >> 1; + pushDown(rt, mid - l + 1, r - mid); + if (L <= mid) { + add(L, R, C, l, mid, rt << 1); + } + if (R > mid) { + add(L, R, C, mid + 1, r, rt << 1 | 1); + } + pushUp(rt); + } + + public int sum(int L, int R) { + return query(L, R, 1, n, 1); + } + + private int query(int L, int R, int l, int r, int rt) { + if (L <= l && r <= R) { + return sum[rt]; + } + int mid = (l + r) >> 1; + pushDown(rt, mid - l + 1, r - mid); + int ans = 0; + if (L <= mid) { + ans += query(L, R, l, mid, rt << 1); + } + if (R > mid) { + ans += query(L, R, mid + 1, r, rt << 1 | 1); + } + return ans; + } + + } + + // 为了测试 + public static int[][] randomMeeting(int len, int time) { + int[][] meetings = new int[len][2]; + for (int i = 0; i < len; i++) { + int a = (int) (Math.random() * (time + 1)); + int b = (int) (Math.random() * (time + 1)); + if (a == b) { + b++; + } + meetings[i][0] = Math.min(a, b); + meetings[i][1] = Math.max(a, b); + } + return meetings; + } + + // 为了测试 + public static int[][] copyMeetings(int[][] meetings) { + int len = meetings.length; + int[][] ans = new int[len][2]; + for (int i = 0; i < len; i++) { + ans[i][0] = meetings[i][0]; + ans[i][1] = meetings[i][1]; + } + return ans; + } + + // 为了测试 + public static boolean equal(ArrayList arr1, ArrayList arr2) { + if (arr1.size() != arr2.size()) { + return false; + } + for (int i = 0; i < arr1.size(); i++) { + int[] a = arr1.get(i); + int[] b = arr2.get(i); + if (a[0] != b[0] || a[1] != b[1]) { + return false; + } + } + return true; + } + + public static void main(String[] args) { + int n = 100; + int t = 5000; + int testTime = 20000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int len = (int) (Math.random() * n) + 1; + int[][] meetings1 = randomMeeting(len, t); + int[][] meetings2 = copyMeetings(meetings1); + ArrayList ans1 = arrange1(meetings1); + ArrayList ans2 = arrange2(meetings2); + if (!equal(ans1, ans2)) { + System.out.println("出错了!"); + System.out.println(ans1.size()); + System.out.println(ans2.size()); + System.out.println("===="); + } + } + System.out.println("测试结束"); + } + +} diff --git a/算法周更班/class_2022_04_1_week/Code04_MaxScoreMoveInBoard.java b/算法周更班/class_2022_04_1_week/Code04_MaxScoreMoveInBoard.java new file mode 100644 index 0000000..a7a4935 --- /dev/null +++ b/算法周更班/class_2022_04_1_week/Code04_MaxScoreMoveInBoard.java @@ -0,0 +1,88 @@ +package class_2022_04_1_week; + +// 来自小红书 +// 小红书第一题: +// 薯队长从北向南穿过一片红薯地(南北长M,东西宽N),红薯地被划分为1x1的方格, +// 他可以从北边的任何一个格子出发,到达南边的任何一个格子, +// 但每一步只能走到东南、正南、西南方向的三个格子之一, +// 而且不能跨出红薯地,他可以获得经过的格子上的所有红薯,请问他可以获得最多的红薯个数。 +public class Code04_MaxScoreMoveInBoard { + +// // 目前来到(row, col)的位置 +// // 只能往不越界的三个方向移动:西南、正南、东南 +// // 一旦遇到最南格子,停 +// // 返回这个过程中,最大的收成 +// public static int f(int[][] board, int row, int col) { +// if (col < 0 || col == board[0].length) { +// return 0; +// } +// if (row == board.length - 1) { +// return board[row][col]; +// } +// int p1 = f(board, row + 1, col - 1); +// int p2 = f(board, row + 1, col); +// int p3 = f(board, row + 1, col + 1); +// return board[row][col] + Math.max(p1, Math.max(p2, p3)); +// } + + public static int maxScore(int[][] map) { + int ans = 0; + for (int col = 0; col < map[0].length; col++) { + ans = Math.max(ans, process(map, 0, col)); + } + return ans; + } + + public static int process(int[][] map, int row, int col) { + if (col < 0 || col == map[0].length) { + return -1; + } + if (row == map.length - 1) { + return map[row][col]; + } + int cur = map[row][col]; + int next1 = process(map, row + 1, col - 1); + int next2 = process(map, row + 1, col); + int next3 = process(map, row + 1, col + 1); + return cur + Math.max(Math.max(next1, next2), next3); + } + + public static int maxScore2(int[][] map) { + int ans = 0; + int n = map.length; + int m = map[0].length; + int[][] dp = new int[n][m]; + for (int i = 0; i < n; i++) { + for (int j = 0; j < m; j++) { + dp[i][j] = -2; // -2表示,这个格子没算过! + } + } + for (int col = 0; col < map[0].length; col++) { + ans = Math.max(ans, process2(map, 0, col, dp)); + } + return ans; + } + + public static int process2(int[][] map, int row, int col, int[][] dp) { + if (col < 0 || col == map[0].length) { + return -1; + } + if (dp[row][col] != -2) { + return dp[row][col]; + } + // 继续算! + int ans = 0; + if (row == map.length - 1) { + ans = map[row][col]; + } else { + int cur = map[row][col]; + int next1 = process2(map, row + 1, col - 1, dp); + int next2 = process2(map, row + 1, col, dp); + int next3 = process2(map, row + 1, col + 1, dp); + ans = cur + Math.max(Math.max(next1, next2), next3); + } + dp[row][col] = ans; + return ans; + } + +} diff --git a/算法周更班/class_2022_04_1_week/Code05_PickKnumbersNearTowNumberMaxDiff.java b/算法周更班/class_2022_04_1_week/Code05_PickKnumbersNearTowNumberMaxDiff.java new file mode 100644 index 0000000..519ea65 --- /dev/null +++ b/算法周更班/class_2022_04_1_week/Code05_PickKnumbersNearTowNumberMaxDiff.java @@ -0,0 +1,57 @@ +package class_2022_04_1_week; + +import java.util.Arrays; + +// 小红书第二题: +// 薯队长最近在参加了一个活动,主办方提供了N个礼物以供挑选, +// 每个礼物有一个价值,范围在0 ~ 10^9之间, +// 薯队长可以从中挑选k个礼物 +// 返回:其中价值最接近的两件礼物之间相差值尽可能大的结果 +// 我们课上讲 +// +// 小红书第三题: +// 薯队长最近在玩一个游戏,这个游戏桌上会有一排不同颜色的方块, +// 每次薯队长可以选择一个方块,便可以消除这个方块以及和他左右相临的 +// 若干的颜色相同的方块,而每次消除的方块越多,得分越高。 +// 具体来说,桌上有以个方块排成一排 (1 <= N <= 200), +// 每个方块有一个颜色,用1~N之间的一个整数表示,相同的数宇代表相同的颜色, +// 每次消除的时候,会把连续的K个相同颜色的方块消除,并得到K*K的分数, +// 直到所有方块都消除。显然,不同的消除顺序得分不同,薯队长希望您能告诉他,这个游戏最多能得到多少分 +// 体系学习班,代码46节,视频在47节,消箱子原题,RemoveBoxes +public class Code05_PickKnumbersNearTowNumberMaxDiff { + + public static int maxNear(int[] arr, int k) { + if (arr.length < k) { + return -1; + } + Arrays.sort(arr); + int n = arr.length; + int l = 0; + int r = arr[n - 1] - arr[0]; + int m = 0; + int ans = 0; + while (l <= r) { + m = (l + r) / 2; + if (yeah(arr, k, m)) { + ans = m; + l = m + 1; + } else { + r = m - 1; + } + } + return ans; + } + + public static boolean yeah(int[] arr, int k, int limit) { + int last = arr[0]; + int pick = 1; + for (int i = 1; i < arr.length; i++) { + if (arr[i] - last >= limit) { + pick++; + last = arr[i]; + } + } + return pick >= k; + } + +} diff --git a/算法周更班/class_2022_04_1_week/Code06_TopMinSubsquenceSum.java b/算法周更班/class_2022_04_1_week/Code06_TopMinSubsquenceSum.java new file mode 100644 index 0000000..67ad8c5 --- /dev/null +++ b/算法周更班/class_2022_04_1_week/Code06_TopMinSubsquenceSum.java @@ -0,0 +1,114 @@ +package class_2022_04_1_week; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.PriorityQueue; + +// 给定一个数组arr,含有n个数字,都是非负数 +// 给定一个正数k +// 返回所有子序列中,累加和最小的前k个子序列累加和 +// 假设K不大,怎么算最快? +public class Code06_TopMinSubsquenceSum { + + public static int[] topMinSum1(int[] arr, int k) { + ArrayList allAns = new ArrayList<>(); + process(arr, 0, 0, allAns); + allAns.sort((a, b) -> a.compareTo(b)); + int[] ans = new int[k]; + for (int i = 0; i < k; i++) { + ans[i] = allAns.get(i); + } + return ans; + } + + public static void process(int[] arr, int index, int sum, ArrayList ans) { + if (index == arr.length) { + ans.add(sum); + } else { + process(arr, index + 1, sum, ans); + process(arr, index + 1, sum + arr[index], ans); + } + } + + public static int[] topMinSum2(int[] arr, int k) { + Arrays.sort(arr); + // (最右的下标,集合的累加和) + PriorityQueue heap = new PriorityQueue<>((a, b) -> a[1] - b[1]); + heap.add(new int[] { 0, arr[0] }); + int[] ans = new int[k]; + // ans[0] = 0 + // 0 1 2 k-1 + // k个! + for (int i = 1; i < k; i++) { + int[] cur = heap.poll(); + // (7, 100) + // 左 :8, 100 - arr[7] + arr[8] + // 右 :8, 100 + arr[8] + int last = cur[0]; + int sum = cur[1]; + ans[i] = sum; + if (last + 1 < arr.length) { + heap.add(new int[] { last + 1, sum - arr[last] + arr[last + 1] }); + heap.add(new int[] { last + 1, sum + arr[last + 1] }); + } + } + return ans; + } + + // 为了测试 + public static int[] randomArray(int len, int value) { + int[] arr = new int[len]; + for (int i = 0; i < len; i++) { + arr[i] = (int) (Math.random() * value); + } + return arr; + } + + // 为了测试 + public static boolean equals(int[] ans1, int[] ans2) { + if (ans1.length != ans2.length) { + return false; + } + for (int i = 0; i < ans1.length; i++) { + if (ans1[i] != ans2[i]) { + return false; + } + } + return true; + } + + // 为了测试 + public static void main(String[] args) { + int n = 10; + int v = 40; + int testTime = 5000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int len = (int) (Math.random() * n) + 1; + int[] arr = randomArray(len, v); + int k = (int) (Math.random() * ((1 << len) - 1)) + 1; + int[] ans1 = topMinSum1(arr, k); + int[] ans2 = topMinSum2(arr, k); + if (!equals(ans1, ans2)) { + System.out.println("出错了!"); + System.out.print("arr : "); + for (int num : arr) { + System.out.print(num + " "); + } + System.out.println(); + System.out.println("k : " + k); + for (int num : ans1) { + System.out.print(num + " "); + } + System.out.println(); + for (int num : ans2) { + System.out.print(num + " "); + } + System.out.println(); + break; + } + } + System.out.println("测试结束"); + } + +} diff --git a/算法周更班/class_2022_04_1_week/Code07_TopMaxSubsquenceSum.java b/算法周更班/class_2022_04_1_week/Code07_TopMaxSubsquenceSum.java new file mode 100644 index 0000000..0312656 --- /dev/null +++ b/算法周更班/class_2022_04_1_week/Code07_TopMaxSubsquenceSum.java @@ -0,0 +1,124 @@ +package class_2022_04_1_week; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.PriorityQueue; + +// 来自Amazon +// 给定一个数组arr,含有n个数字,可能有正、有负、有0 +// 给定一个正数k +// 返回所有子序列中,累加和最大的前k个子序列累加和 +// 假设K不大,怎么算最快? +public class Code07_TopMaxSubsquenceSum { + + public static int[] topMaxSum1(int[] arr, int k) { + ArrayList allAns = new ArrayList<>(); + process(arr, 0, 0, allAns); + allAns.sort((a, b) -> a.compareTo(b)); + int[] ans = new int[k]; + for (int i = allAns.size() - 1, j = 0; j < k; i--, j++) { + ans[j] = allAns.get(i); + } + return ans; + } + + public static void process(int[] arr, int index, int sum, ArrayList ans) { + if (index == arr.length) { + ans.add(sum); + } else { + process(arr, index + 1, sum, ans); + process(arr, index + 1, sum + arr[index], ans); + } + } + + public static int[] topMaxSum2(int[] arr, int k) { + int sum = 0; + for (int i = 0; i < arr.length; i++) { + if (arr[i] >= 0) { + sum += arr[i]; + } else { + arr[i] = -arr[i]; + } + } + int[] ans = topMinSum(arr, k); + for (int i = 0; i < ans.length; i++) { + ans[i] = sum - ans[i]; + } + return ans; + } + + public static int[] topMinSum(int[] arr, int k) { + Arrays.sort(arr); + PriorityQueue heap = new PriorityQueue<>((a, b) -> a[1] - b[1]); + heap.add(new int[] { 0, arr[0] }); + int[] ans = new int[k]; + for (int i = 1; i < k; i++) { + int[] cur = heap.poll(); + int last = cur[0]; + int sum = cur[1]; + ans[i] = sum; + if (last + 1 < arr.length) { + heap.add(new int[] { last + 1, sum - arr[last] + arr[last + 1] }); + heap.add(new int[] { last + 1, sum + arr[last + 1] }); + } + } + return ans; + } + + // 为了测试 + public static int[] randomArray(int len, int value) { + int[] arr = new int[len]; + for (int i = 0; i < len; i++) { + arr[i] = (int) (Math.random() * value) + 1; + } + return arr; + } + + // 为了测试 + public static boolean equals(int[] ans1, int[] ans2) { + if (ans1.length != ans2.length) { + return false; + } + for (int i = 0; i < ans1.length; i++) { + if (ans1[i] != ans2[i]) { + return false; + } + } + return true; + } + + // 为了测试 + public static void main(String[] args) { + int n = 10; + int v = 40; + int testTime = 5000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int len = (int) (Math.random() * n) + 1; + int[] arr = randomArray(len, v); + int k = (int) (Math.random() * ((1 << len) - 1)) + 1; + int[] ans1 = topMaxSum1(arr, k); + int[] ans2 = topMaxSum2(arr, k); + if (!equals(ans1, ans2)) { + System.out.println("出错了!"); + System.out.print("arr : "); + for (int num : arr) { + System.out.print(num + " "); + } + System.out.println(); + System.out.println("k : " + k); + for (int num : ans1) { + System.out.print(num + " "); + } + System.out.println(); + for (int num : ans2) { + System.out.print(num + " "); + } + System.out.println(); + break; + } + } + System.out.println("测试结束"); + } + +} diff --git a/算法周更班/class_2022_04_2_week/Code01_SumOfValuesAboutPrimes.java b/算法周更班/class_2022_04_2_week/Code01_SumOfValuesAboutPrimes.java new file mode 100644 index 0000000..93a9468 --- /dev/null +++ b/算法周更班/class_2022_04_2_week/Code01_SumOfValuesAboutPrimes.java @@ -0,0 +1,114 @@ +package class_2022_04_2_week; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.HashMap; + +// 来自携程 +// 给出n个数字,你可以任选其中一些数字相乘,相乘之后得到的新数字x +// x的价值是x的不同质因子的数量 +// 返回所有选择数字的方案中,得到的x的价值之和 +public class Code01_SumOfValuesAboutPrimes { + + // 工具! + // 返回num质数因子列表(去重) + // 时间复杂度,根号(num) + public static ArrayList primes(long num) { + ArrayList ans = new ArrayList<>(); + for (long i = 2; i * i <= num && num > 1; i++) { + if (num % i == 0) { + ans.add(i); + while (num % i == 0) { + num /= i; + } + } + } + if (num != 1) { + ans.add(num); + } + return ans; + } + + public static long sumOfValues1(int[] arr) { + return process1(arr, 0, 1); + } + + public static long process1(int[] arr, int index, long pre) { + if (index == arr.length) { + return (long) primes(pre).size(); + } + long p1 = process1(arr, index + 1, pre); + long p2 = process1(arr, index + 1, pre * (long) arr[index]); + return p1 + p2; + } + + public static long sumOfValues2(int[] arr) { + // key : 某个质数因子 + // value : 有多少个数含有这个因子 + HashMap cntMap = new HashMap<>(); + for (int num : arr) { + for (long factor : primes(num)) { + cntMap.put(factor, cntMap.getOrDefault(factor, 0L) + 1L); + } + } + int n = arr.length; + long ans = 0; + // count :含有这个因子的数,有多少个 + // others : 不含有这个因子的数,有多少个 + for (long count : cntMap.values()) { + long others = n - count; + ans += (power(2, count) - 1) * power(2, others); + } + return ans; + } + + public static long power(long num, long n) { + if (n == 0L) { + return 1L; + } + long ans = 1L; + while (n > 0) { + if ((n & 1) != 0) { + ans *= num; + } + num *= num; + n >>= 1; + } + return ans; + } + + // 为了测试 + public static int[] randomArray(int len, int value) { + int[] arr = new int[len]; + for (int i = 0; i < len; i++) { + arr[i] = (int) (Math.random() * value) + 1; + } + return arr; + } + + // 为了测试 + public static void main(String[] args) { + int n = 10; + int v = 20; + int testTime = 5000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int[] arr = randomArray(n, v); + long ans1 = sumOfValues1(arr); + long ans2 = sumOfValues2(arr); + if (ans1 != ans2) { + System.out.println("出错了!"); + BigInteger all = new BigInteger("1"); + for (int num : arr) { + all = all.multiply(new BigInteger(String.valueOf(num))); + } + System.out.println("所有数都乘起来 : " + all.toString()); + System.out.println("长整型最大范围 : " + Long.MAX_VALUE); + System.out.println("可以看看上面的打印,看看是不是因为溢出了"); + break; + } + } + System.out.println("测试结束"); + } + +} diff --git a/算法周更班/class_2022_04_2_week/Code02_MinDistanceFromLeftUpToRightDown.java b/算法周更班/class_2022_04_2_week/Code02_MinDistanceFromLeftUpToRightDown.java new file mode 100644 index 0000000..12c6b77 --- /dev/null +++ b/算法周更班/class_2022_04_2_week/Code02_MinDistanceFromLeftUpToRightDown.java @@ -0,0 +1,125 @@ +package class_2022_04_2_week; + +import java.util.PriorityQueue; + +// 来自网易 +// 3.27笔试 +// 一个二维矩阵,上面只有 0 和 1,只能上下左右移动 +// 如果移动前后的元素值相同,则耗费 1 ,否则耗费 2。 +// 问从左上到右下的最小耗费 +public class Code02_MinDistanceFromLeftUpToRightDown { + + // 一个错误的贪心 + // 网上帖子最流行的解答,看似对,其实不行 + public static int bestWalk1(int[][] map) { + int n = map.length; + int m = map[0].length; + int[][] dp = new int[n][m]; + for (int i = 1; i < m; i++) { + dp[0][i] = dp[0][i - 1] + (map[0][i - 1] == map[0][i] ? 1 : 2); + } + for (int i = 1; i < n; i++) { + dp[i][0] = dp[i - 1][0] + (map[i - 1][0] == map[i][0] ? 1 : 2); + } + for (int i = 1; i < n; i++) { + for (int j = 1; j < m; j++) { + dp[i][j] = dp[i - 1][j] + (map[i - 1][j] == map[i][j] ? 1 : 2); + dp[i][j] = Math.min(dp[i][j], dp[i][j - 1] + (map[i][j - 1] == map[i][j] ? 1 : 2)); + } + } + return dp[n - 1][m - 1]; + } + + // 正确的解法 + // Dijskra + public static int bestWalk2(int[][] map) { + int n = map.length; + int m = map[0].length; + // 小根堆:[代价,行,列] + // 根据代价,谁代价小,谁放在堆的上面 + PriorityQueue heap = new PriorityQueue<>((a, b) -> a[0] - b[0]); + // poped[i][j] == true 已经弹出过了!不要再处理,直接忽略! + // poped[i][j] == false 之间(i,j)没弹出过!要处理 + boolean[][] poped = new boolean[n][m]; + heap.add(new int[] { 0, 0, 0 }); + int ans = 0; + while (!heap.isEmpty()) { + // 当前弹出了,[代价,行,列],当前位置 + int[] cur = heap.poll(); + int dis = cur[0]; + int row = cur[1]; + int col = cur[2]; + if (poped[row][col]) { + continue; + } + // 第一次弹出! + poped[row][col] = true; + if (row == n - 1 && col == m - 1) { + ans = dis; + break; + } + add(dis, row - 1, col, map[row][col], n, m, map, poped, heap); + add(dis, row + 1, col, map[row][col], n, m, map, poped, heap); + add(dis, row, col - 1, map[row][col], n, m, map, poped, heap); + add(dis, row, col + 1, map[row][col], n, m, map, poped, heap); + } + return ans; + } + + // preDistance : 之前的距离 + // int row, int col : 当前要加入的是什么位置 + // preValue : 前一个格子是什么值, + // int n, int m :边界,固定参数 + // map: 每一个格子的值,都在map里 + // boolean[][] poped : 当前位置如果是弹出过的位置,要忽略! + // PriorityQueue heap : 小根堆 + public static void add(int preDistance, + int row, int col, int preValue, int n, int m, + int[][] map, boolean[][] poped, + PriorityQueue heap) { + if (row >= 0 && row < n && col >= 0 && col < m + && !poped[row][col]) { + heap.add(new int[] { + preDistance + (map[row][col] == preValue ? 1 : 2), + row, col }); + } + } + + // 为了测试 + public static int[][] randomMatrix(int n, int m) { + int[][] ans = new int[n][m]; + for (int i = 0; i < n; i++) { + for (int j = 0; j < m; j++) { + ans[i][j] = (int) (Math.random() * 2); + } + } + return ans; + } + + // 为了测试 + public static void main(String[] args) { + int n = 100; + int m = 100; + int testTime = 10000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int[][] map = randomMatrix(n, m); + int ans1 = bestWalk1(map); + int ans2 = bestWalk2(map); + if (ans1 != ans2) { + System.out.println("出错了!"); + for (int[] arr : map) { + for (int num : arr) { + System.out.print(num + " "); + } + System.out.println(); + } + System.out.println(ans1); + System.out.println(ans2); + break; + } + } + System.out.println("测试结束"); + } + +} diff --git a/算法周更班/class_2022_04_2_week/Code03_MaxSumDividedBy7.java b/算法周更班/class_2022_04_2_week/Code03_MaxSumDividedBy7.java new file mode 100644 index 0000000..0412aab --- /dev/null +++ b/算法周更班/class_2022_04_2_week/Code03_MaxSumDividedBy7.java @@ -0,0 +1,82 @@ +package class_2022_04_2_week; + +// 来自美团 +// 3.26笔试 +// 给定一个非负数组,任意选择数字,使累加和最大且为7的倍数,返回最大累加和 +// n比较大,10的5次方 +public class Code03_MaxSumDividedBy7 { + + public static int maxSum1(int[] arr) { + return process1(arr, 0, 0); + } + + public static int process1(int[] arr, int index, int pre) { + if (index == arr.length) { + return pre % 7 == 0 ? pre : 0; + } + int p1 = process1(arr, index + 1, pre); + int p2 = process1(arr, index + 1, pre + arr[index]); + return Math.max(p1, p2); + } + + public static int maxSum2(int[] arr) { + if (arr == null || arr.length == 0) { + return 0; + } + int n = arr.length; + int[][] dp = new int[n][7]; + for (int i = 0; i < n; i++) { + for (int j = 1; j < 7; j++) { + dp[i][j] = -1; + } + } + dp[0][arr[0] % 7] = arr[0]; + for (int i = 1; i < n; i++) { + // 当前arr[i] % 7 的余数 + int curMod = arr[i] % 7; + for (int j = 0; j < 7; j++) { + dp[i][j] = dp[i - 1][j]; + int findMod = (7 - curMod + j) % 7; + if (dp[i - 1][findMod] != -1) { + dp[i][j] = Math.max(dp[i][j], dp[i - 1][findMod] + arr[i]); + } + } + } + return dp[n - 1][0] == -1 ? 0 : dp[n - 1][0]; + } + + // 为了测试 + public static int[] randomArray(int n, int v) { + int[] arr = new int[n]; + for (int i = 0; i < n; i++) { + arr[i] = (int) (Math.random() * v) + 1; + } + return arr; + } + + // 为了测试 + public static void main(String[] args) { + int len = 12; + int value = 30; + int testTime = 2000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int n = (int) (Math.random() * len) + 1; + int[] arr = randomArray(n, value); + int ans1 = maxSum1(arr); + int ans2 = maxSum2(arr); + if (ans1 != ans2) { + System.out.println("出错了!"); + for (int num : arr) { + System.out.print(num + " "); + } + System.out.println(); + System.out.println(ans1); + System.out.println(ans2); + break; + } + } + System.out.println("测试结束"); + } + +} diff --git a/算法周更班/class_2022_04_2_week/Code04_AllJobFinishTime.java b/算法周更班/class_2022_04_2_week/Code04_AllJobFinishTime.java new file mode 100644 index 0000000..3e8ca27 --- /dev/null +++ b/算法周更班/class_2022_04_2_week/Code04_AllJobFinishTime.java @@ -0,0 +1,48 @@ +package class_2022_04_2_week; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.Queue; + +// 来自美团 +// 3.26笔试 +// 给定一个正数n, 表示有0~n-1号任务 +// 给定一个长度为n的数组time,time[i]表示i号任务做完的时间 +// 给定一个二维数组matrix +// matrix[j] = {a, b} 代表:a任务想要开始,依赖b任务的完成 +// 只要能并行的任务都可以并行,但是任何任务只有依赖的任务完成,才能开始 +// 返回一个长度为n的数组ans,表示每个任务完成的时间 +// 输入可以保证没有循环依赖 +public class Code04_AllJobFinishTime { + + public static int[] finishTime(int n, int[] time, int[][] matrix) { + ArrayList> nexts = new ArrayList<>(); + for (int i = 0; i < n; i++) { + nexts.add(new ArrayList<>()); + } + int[] in = new int[n]; + for (int[] line : matrix) { + nexts.get(line[1]).add(line[0]); + in[line[0]]++; + } + Queue zeroInQueue = new LinkedList<>(); + int[] ans = new int[n]; + for (int i = 0; i < n; i++) { + if (in[i] == 0) { + zeroInQueue.add(i); + } + } + while (!zeroInQueue.isEmpty()) { + int cur = zeroInQueue.poll(); + ans[cur] += time[cur]; + for (int next : nexts.get(cur)) { + ans[next] = Math.max(ans[next], ans[cur]); + if (--in[next] == 0) { + zeroInQueue.add(next); + } + } + } + return ans; + } + +} diff --git a/算法周更班/class_2022_04_2_week/Code05_TowLongestSubarraySame01Number.java b/算法周更班/class_2022_04_2_week/Code05_TowLongestSubarraySame01Number.java new file mode 100644 index 0000000..1941890 --- /dev/null +++ b/算法周更班/class_2022_04_2_week/Code05_TowLongestSubarraySame01Number.java @@ -0,0 +1,95 @@ +package class_2022_04_2_week; + +import java.util.HashMap; + +// 来自百度 +// 给出一个长度为n的01串,现在请你找到两个区间, +// 使得这两个区间中,1的个数相等,0的个数也相等 +// 这两个区间可以相交,但是不可以完全重叠,即两个区间的左右端点不可以完全一样 +// 现在请你找到两个最长的区间,满足以上要求。 +public class Code05_TowLongestSubarraySame01Number { + + public static int longest1(int[] arr) { + HashMap> map = new HashMap<>(); + for (int i = 0; i < arr.length; i++) { + int zero = 0; + int one = 0; + for (int j = i; j < arr.length; j++) { + zero += arr[j] == 0 ? 1 : 0; + one += arr[j] == 1 ? 1 : 0; + map.putIfAbsent(zero, new HashMap<>()); + map.get(zero).put(one, map.get(zero).getOrDefault(one, 0) + 1); + } + } + int ans = 0; + for (int zeros : map.keySet()) { + for (int ones : map.get(zeros).keySet()) { + int num = map.get(zeros).get(ones); + if (num > 1) { + ans = Math.max(ans, zeros + ones); + } + } + } + return ans; + } + + public static int longest2(int[] arr) { + int leftZero = -1; + int rightZero = -1; + int leftOne = -1; + int rightOne = -1; + for (int i = 0; i < arr.length; i++) { + if (arr[i] == 0) { + leftZero = i; + break; + } + } + for (int i = 0; i < arr.length; i++) { + if (arr[i] == 1) { + leftOne = i; + break; + } + } + for (int i = arr.length - 1; i >= 0; i--) { + if (arr[i] == 0) { + rightZero = i; + break; + } + } + for (int i = arr.length - 1; i >= 0; i--) { + if (arr[i] == 1) { + rightOne = i; + break; + } + } + int p1 = rightZero - leftZero; + int p2 = rightOne - leftOne; + return Math.max(p1, p2); + } + + public static int[] randomArray(int len) { + int[] ans = new int[len]; + for (int i = 0; i < len; i++) { + ans[i] = (int) (Math.random() * 2); + } + return ans; + } + + public static void main(String[] args) { + int n = 500; + int testTime = 200; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int size = (int) (Math.random() * n) + 2; + int[] arr = randomArray(size); + int ans1 = longest1(arr); + int ans2 = longest2(arr); + if (ans1 != ans2) { + System.out.println("出错了!"); + break; + } + } + System.out.println("测试结束"); + } + +} diff --git a/算法周更班/class_2022_04_2_week/Code06_PerfectPairNumber.java b/算法周更班/class_2022_04_2_week/Code06_PerfectPairNumber.java new file mode 100644 index 0000000..32d3212 --- /dev/null +++ b/算法周更班/class_2022_04_2_week/Code06_PerfectPairNumber.java @@ -0,0 +1,53 @@ +package class_2022_04_2_week; + +// 来自阿里 +// x = { a, b, c, d } +// y = { e, f, g, h } +// x、y两个小数组长度都是4 +// 如果有: a + e = b + f = c + g = d + h +// 那么说x和y是一个完美对 +// 题目给定N个小数组,每个小数组长度都是K +// 返回这N个小数组中,有多少完美对 +// 本题测试链接 : https://www.nowcoder.com/practice/f5a3b5ab02ed4202a8b54dfb76ad035e +// 提交如下代码,把主类名改成Main +// 可以直接通过 +import java.util.HashMap; +import java.util.Scanner; + +public class Code06_PerfectPairNumber { + + public static void main(String[] args) { + Scanner sc = new Scanner(System.in); + while (sc.hasNext()) { + int n = sc.nextInt(); + int m = sc.nextInt(); + int[][] matrix = new int[n][m]; + for (int i = 0; i < n; i++) { + for (int j = 0; j < m; j++) { + matrix[i][j] = sc.nextInt(); + } + } + long ans = perfectPairs(matrix); + System.out.println(ans); + } + sc.close(); + } + + public static long perfectPairs(int[][] matrix) { + long ans = 0; + // key : 字符串 特征,差值特征 : "_5_-2_6_9" + HashMap counts = new HashMap<>(); + for (int[] arr : matrix) { + StringBuilder self = new StringBuilder(); + StringBuilder minus = new StringBuilder(); + for (int i = 1; i < arr.length; i++) { + self.append("_" + (arr[i] - arr[i - 1])); + minus.append("_" + (arr[i - 1] - arr[i])); + } + ans += counts.getOrDefault(minus.toString(), 0); + counts.put(self.toString(), counts.getOrDefault(self.toString(), 0) + 1); + } + return ans; + } + +} \ No newline at end of file diff --git a/算法周更班/class_2022_04_2_week/Code07_MaxMoneyMostMin.java b/算法周更班/class_2022_04_2_week/Code07_MaxMoneyMostMin.java new file mode 100644 index 0000000..61b6ed6 --- /dev/null +++ b/算法周更班/class_2022_04_2_week/Code07_MaxMoneyMostMin.java @@ -0,0 +1,236 @@ +package class_2022_04_2_week; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.LinkedList; + +// 来自快手 +// 某公司年会上,大家要玩一食发奖金游戏,一共有n个员工, +// 每个员工都有建设积分和捣乱积分 +// 他们需要排成一队,在队伍最前面的一定是老板,老板也有建设积分和捣乱积分 +// 排好队后,所有员工都会获得各自的奖金, +// 该员工奖金 = 排在他前面所有人的建设积分乘积 / 该员工自己的捣乱积分,向下取整 +// 为了公平(放屁),老板希望 : 让获得奖金最高的员工,所获得的奖金尽可能少 +// 所以想请你帮他重新排一下队伍,返回奖金最高的员工获得的、尽可能少的奖金数额 +// 快手考试的时候,给定的数据量,全排列的代码也能过的! +// 1 <= n <= 1000, 1<= 积分 <= 10000; +public class Code07_MaxMoneyMostMin { + + // 暴力方法 + // 为了验证 + // a : 老板的贡献积分 + // b : 老板的捣乱积分 + // value[i] : i号员工的贡献积分 + // trouble[i] : i号员工的捣乱积分 + // 返回 : 奖金最高的员工获得的、尽可能少的奖金数额 + public static long mostMin1(int a, int b, int[] value, int[] trouble) { + return process1(a, value, trouble, 0); + } + + public static long process1(int boss, int[] value, int[] trouble, int index) { + if (index == value.length) { + long valueAll = boss; + long ans = 0; + for (int i = 0; i < value.length; i++) { + ans = Math.max(ans, valueAll / trouble[i]); + valueAll *= value[i]; + } + return ans; + } else { + long ans = Long.MAX_VALUE; + for (int i = index; i < value.length; i++) { + swap(value, trouble, i, index); + ans = Math.min(ans, process1(boss, value, trouble, index + 1)); + swap(value, trouble, i, index); + } + return ans; + } + } + + public static void swap(int[] value, int[] trouble, int i, int j) { + int tmp = value[i]; + value[i] = value[j]; + value[j] = tmp; + tmp = trouble[i]; + trouble[i] = trouble[j]; + trouble[j] = tmp; + } + + // 正式方法 + // 所有员工数量为N + // 假设所有员工建设积分乘起来为M + // 时间复杂度O(N * logN * logM) + public static long mostMin2(int a, int b, int[] value, int[] trouble) { + int n = value.length; + long l = 0; + long r = 0; + long valueAll = a; + long[][] staff = new long[n][2]; + for (int i = 0; i < n; i++) { + r = Math.max(r, valueAll / trouble[i]); + valueAll *= value[i]; + staff[i][0] = (long) value[i] * (long) trouble[i]; + staff[i][1] = value[i]; + } + Arrays.sort(staff, (x, y) -> (y[0] >= x[0] ? 1 : -1)); + long m = 0; + long ans = 0; + while (l <= r) { + m = l + ((r - l) >> 1); + if (yeah(valueAll, staff, m)) { + ans = m; + r = m - 1; + } else { + l = m + 1; + } + } + return ans; + } + + // staff长度为N,时间复杂度O(N * logN) + public static boolean yeah(long all, long[][] staff, long limit) { + int n = staff.length; + SegmentTree st = new SegmentTree(n); + HashMap> map = new HashMap<>(); + for (int i = 0, index = 1; i < n; i++, index++) { + int value = (int) staff[i][1]; + st.update(index, value); + if (!map.containsKey(value)) { + map.put(value, new LinkedList<>()); + } + map.get(value).addLast(index); + } + for (int k = 0; k < n; k++) { + int right = boundary(staff, all, limit); + if (right == 0) { + return false; + } + int max = st.max(right); + if (max == 0) { + return false; + } + int index = map.get(max).pollFirst(); + st.update(index, 0); + all /= max; + } + return true; + } + + public static int boundary(long[][] staff, long all, long limit) { + int l = 0; + int r = staff.length - 1; + int m = 0; + int ans = -1; + while (l <= r) { + m = l + ((r - l) >> 1); + if (all / staff[m][0] <= limit) { + ans = m; + l = m + 1; + } else { + r = m - 1; + } + } + return ans + 1; + } + + public static class SegmentTree { + private int n; + private int[] max; + private int[] update; + + public SegmentTree(int maxSize) { + n = maxSize + 1; + max = new int[n << 2]; + update = new int[n << 2]; + Arrays.fill(update, -1); + } + + public void update(int index, int c) { + update(index, index, c, 1, n, 1); + } + + public int max(int right) { + return max(1, right, 1, n, 1); + } + + private void pushUp(int rt) { + max[rt] = Math.max(max[rt << 1], max[rt << 1 | 1]); + } + + private void pushDown(int rt, int ln, int rn) { + if (update[rt] != -1) { + update[rt << 1] = update[rt]; + max[rt << 1] = update[rt]; + update[rt << 1 | 1] = update[rt]; + max[rt << 1 | 1] = update[rt]; + update[rt] = -1; + } + } + + private void update(int L, int R, int C, int l, int r, int rt) { + if (L <= l && r <= R) { + max[rt] = C; + update[rt] = C; + return; + } + int mid = (l + r) >> 1; + pushDown(rt, mid - l + 1, r - mid); + if (L <= mid) { + update(L, R, C, l, mid, rt << 1); + } + if (R > mid) { + update(L, R, C, mid + 1, r, rt << 1 | 1); + } + pushUp(rt); + } + + private int max(int L, int R, int l, int r, int rt) { + if (L <= l && r <= R) { + return max[rt]; + } + int mid = (l + r) >> 1; + pushDown(rt, mid - l + 1, r - mid); + int ans = 0; + if (L <= mid) { + ans = Math.max(ans, max(L, R, l, mid, rt << 1)); + } + if (R > mid) { + ans = Math.max(ans, max(L, R, mid + 1, r, rt << 1 | 1)); + } + return ans; + } + + } + + // 为了测试 + public static int[] randomArray(int len, int value) { + int[] arr = new int[len]; + for (int i = 0; i < len; i++) { + arr[i] = (int) (Math.random() * value) + 1; + } + return arr; + } + + // 为了测试 + public static void main(String[] args) { + int n = 9; + int v = 50; + int testTime = 5000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int a = (int) (Math.random() * v) + 1; + int b = (int) (Math.random() * v) + 1; + int len = (int) (Math.random() * n); + int[] value = randomArray(len, v); + int[] trouble = randomArray(len, v); + long ans1 = mostMin1(a, b, value, trouble); + long ans2 = mostMin2(a, b, value, trouble); + if (ans1 != ans2) { + System.out.println("出错了!"); + break; + } + } + System.out.println("测试结束"); + } + +} diff --git a/算法周更班/class_2022_04_3_week/Code01_MaxOneNumbers.java b/算法周更班/class_2022_04_3_week/Code01_MaxOneNumbers.java new file mode 100644 index 0000000..6348b83 --- /dev/null +++ b/算法周更班/class_2022_04_3_week/Code01_MaxOneNumbers.java @@ -0,0 +1,79 @@ +package class_2022_04_3_week; + +// 小红书 +// 3.13 笔试 +// 数组里有0和1,一定要翻转一个区间,翻转:0变1,1变0 +// 请问翻转后可以使得1的个数最多是多少? +public class Code01_MaxOneNumbers { + + public static int maxOneNumbers1(int[] arr) { + int ans = 0; + for (int l = 0; l < arr.length; l++) { + for (int r = l; r < arr.length; r++) { + reverse(arr, l, r); + ans = Math.max(ans, oneNumbers(arr)); + reverse(arr, l, r); + } + } + return ans; + } + + public static void reverse(int[] arr, int l, int r) { + for (int i = l; i <= r; i++) { + arr[i] ^= 1; + } + } + + public static int oneNumbers(int[] arr) { + int ans = 0; + for (int num : arr) { + ans += num; + } + return ans; + } + + public static int maxOneNumbers2(int[] arr) { + int ans = 0; + for (int num : arr) { + ans += num; + } + for (int i = 0; i < arr.length; i++) { + arr[i] = arr[i] == 0 ? 1 : -1; + } + int max = Integer.MIN_VALUE; + int cur = 0; + for (int i = 0; i < arr.length; i++) { + cur += arr[i]; + max = Math.max(max, cur); + cur = cur < 0 ? 0 : cur; + } + return ans + max; + } + + // 为了测试 + public static int[] randomArray(int n) { + int[] arr = new int[n]; + for (int i = 0; i < n; i++) { + arr[i] = (int) (Math.random() * 2); + } + return arr; + } + + // 为了测试 + public static void main(String[] args) { + int N = 100; + int testTime = 20000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int n = (int) (Math.random() * N) + 1; + int[] arr = randomArray(n); + int ans1 = maxOneNumbers1(arr); + int ans2 = maxOneNumbers2(arr); + if (ans1 != ans2) { + System.out.println("出错了!"); + } + } + System.out.println("测试结束"); + } + +} diff --git a/算法周更班/class_2022_04_3_week/Code02_RMQ.java b/算法周更班/class_2022_04_3_week/Code02_RMQ.java new file mode 100644 index 0000000..e6d5c5c --- /dev/null +++ b/算法周更班/class_2022_04_3_week/Code02_RMQ.java @@ -0,0 +1,118 @@ +package class_2022_04_3_week; + +// 小红书 +// 3.13 笔试 +// 给定一个数组,想随时查询任何范围上的最大值 +// 如果只是根据初始数组建立、并且以后没有修改, +// 那么RMQ方法比线段树方法好实现,时间复杂度O(N*logN),额外空间复杂度O(N*logN) +public class Code02_RMQ { + + public static class RMQ { + public int[][] max; + + // 下标一定要从1开始,没有道理!就是约定俗成! + public RMQ(int[] arr) { + // 长度! + int n = arr.length; + // 2的几次方,可以拿下n + int k = power2(n); + // n*logn + max = new int[n + 1][k + 1]; + for (int i = 1; i <= n; i++) { + // i 0:从下标i开始,往下连续的2的0次方个数,中,最大值 + // 1...1个 + // 2...1个 + // 3...1个 + max[i][0] = arr[i - 1]; + } + for (int j = 1; (1 << j) <= n; j++) { + // i...连续的、2的1次方个数,这个范围,最大值 + // i...连续的、2的2次方个数,这个范围,最大值 + // i...连续的、2的3次方个数,这个范围,最大值 + for (int i = 1; i + (1 << j) - 1 <= n; i++) { + // max[10][3] + // 下标10开始,连续的8个数,最大值是多少 + // 1) max[10][2] + // 2) max[14][2] + max[i][j] = Math.max( + max[i][j - 1], + max[i + (1 << (j - 1))][j - 1]); + } + } + } + + public int max(int l, int r) { + // l...r -> r - l + 1 -> 2的哪个次方最接近它! + int k = power2(r - l + 1); + return Math.max(max[l][k], max[r - (1 << k) + 1][k]); + } + + private int power2(int m) { + int ans = 0; + while ((1 << ans) <= (m >> 1)) { + ans++; + } + return ans; + } + + } + + // 为了测试 + public static class Right { + public int[][] max; + + public Right(int[] arr) { + int n = arr.length; + max = new int[n + 1][n + 1]; + for (int l = 1; l <= n; l++) { + max[l][l] = arr[l - 1]; + for (int r = l + 1; r <= n; r++) { + max[l][r] = Math.max(max[l][r - 1], arr[r - 1]); + } + } + } + + public int max(int l, int r) { + return max[l][r]; + } + + } + + // 为了测试 + public static int[] randomArray(int n, int v) { + int[] arr = new int[n]; + for (int i = 0; i < n; i++) { + arr[i] = (int) (Math.random() * v); + } + return arr; + } + + // 为了测试 + public static void main(String[] args) { + int N = 150; + int V = 200; + int testTimeOut = 20000; + int testTimeIn = 200; + System.out.println("测试开始"); + for (int i = 0; i < testTimeOut; i++) { + int n = (int) (Math.random() * N) + 1; + int[] arr = randomArray(n, V); + int m = arr.length; + RMQ rmq = new RMQ(arr); + Right right = new Right(arr); + for (int j = 0; j < testTimeIn; j++) { + int a = (int) (Math.random() * m) + 1; + int b = (int) (Math.random() * m) + 1; + int l = Math.min(a, b); + int r = Math.max(a, b); + int ans1 = rmq.max(l, r); + int ans2 = right.max(l, r); + if (ans1 != ans2) { + System.out.println("出错了!"); + } + } + } + System.out.println("测试结束"); + } + +} diff --git a/算法周更班/class_2022_04_3_week/Code03_ValidSortedArrayWays.java b/算法周更班/class_2022_04_3_week/Code03_ValidSortedArrayWays.java new file mode 100644 index 0000000..309219f --- /dev/null +++ b/算法周更班/class_2022_04_3_week/Code03_ValidSortedArrayWays.java @@ -0,0 +1,125 @@ +package class_2022_04_3_week; + +import java.util.Arrays; + +// 来自腾讯音乐 +// 原本数组中都是大于0、小于等于k的数字,是一个单调不减的数组 +// 其中可能有相等的数字,总体趋势是递增的 +// 但是其中有些位置的数被替换成了0,我们需要求出所有的把0替换的方案数量: +// 1)填充的每一个数可以大于等于前一个数,小于等于后一个数 +// 2)填充的每一个数不能大于k +public class Code03_ValidSortedArrayWays { + + // 动态规划 + public static long ways1(int[] nums, int k) { + int n = nums.length; + // dp[i][j] : 一共i个格子,随意填,但是不能降序,j种数可以选 + long[][] dp = new long[n + 1][k + 1]; + for (int i = 1; i <= n; i++) { + dp[i][1] = 1; + } + for (int i = 1; i <= k; i++) { + dp[1][i] = i; + } + for (int i = 2; i <= n; i++) { + for (int j = 2; j <= k; j++) { + dp[i][j] = dp[i - 1][j] + dp[i][j - 1]; + } + } + long res = 1; + for (int i = 0, j = 0; i < nums.length; i++) { + if (nums[i] == 0) { + j = i + 1; + while (j < nums.length && nums[j] == 0) { + j++; + } + int leftValue = i - 1 >= 0 ? nums[i - 1] : 1; + int rightValue = j < nums.length ? nums[j] : k; + res *= dp[j - i][rightValue - leftValue + 1]; + i = j; + } + } + return res; + } + + // 数学方法 + // a ~ b范围的数字随便选,可以选重复的数,一共选m个 + // 选出有序序列的方案数:C ( m, b - a + m ) + public static long ways2(int[] nums, int k) { + long res = 1; + for (int i = 0, j = 0; i < nums.length; i++) { + if (nums[i] == 0) { + j = i + 1; + while (j < nums.length && nums[j] == 0) { + j++; + } + int leftValue = i - 1 >= 0 ? nums[i - 1] : 1; + int rightValue = j < nums.length ? nums[j] : k; + int numbers = j - i; + res *= c(rightValue - leftValue + numbers, numbers); + i = j; + } + } + return res; + } + + // 从一共a个数里,选b个数,方法数是多少 + public static long c(int a, int b) { + if (a == b) { + return 1; + } + long x = 1; + long y = 1; + for (int i = b + 1, j = 1; i <= a; i++, j++) { + x *= i; + y *= j; + long gcd = gcd(x, y); + x /= gcd; + y /= gcd; + } + return x / y; + } + + public static long gcd(long m, long n) { + return n == 0 ? m : gcd(n, m % n); + } + + // 为了测试 + public static int[] randomArray(int n, int k) { + int[] ans = new int[n]; + for (int i = 0; i < n; i++) { + ans[i] = (int) (Math.random() * k) + 1; + } + Arrays.sort(ans); + for (int i = 0; i < n; i++) { + ans[i] = Math.random() < 0.5 ? 0 : ans[i]; + } + return ans; + } + + // 为了测试 + public static void main(String[] args) { + int N = 20; + int K = 30; + int testTimes = 10000; + System.out.println("测试开始"); + for (int i = 0; i < testTimes; i++) { + int n = (int) (Math.random() * N) + 1; + int k = (int) (Math.random() * K) + 1; + int[] arr = randomArray(n, k); + long ans1 = ways1(arr, k); + long ans2 = ways2(arr, k); + if (ans1 != ans2) { + System.out.println("出错了"); + for (int num : arr) { + System.out.print(num + " "); + } + System.out.println(); + System.out.println(ans1); + System.out.println(ans2); + } + } + System.out.println("测试结束"); + } + +} diff --git a/算法周更班/class_2022_04_3_week/Code04_SumEvenSubNumber.java b/算法周更班/class_2022_04_3_week/Code04_SumEvenSubNumber.java new file mode 100644 index 0000000..b30658b --- /dev/null +++ b/算法周更班/class_2022_04_3_week/Code04_SumEvenSubNumber.java @@ -0,0 +1,84 @@ +package class_2022_04_3_week; + +// 来自学员问题 +// 总长度为n的数组中,所有长度为k的子序列里,有多少子序列的和为偶数 +public class Code04_SumEvenSubNumber { + + public static int number1(int[] arr, int k) { + if (arr == null || arr.length == 0 || k < 1 || k > arr.length) { + return 0; + } + return process1(arr, 0, k, 0); + } + + public static int process1(int[] arr, int index, int rest, int sum) { + if (index == arr.length) { + return rest == 0 && (sum & 1) == 0 ? 1 : 0; + } else { + return process1(arr, index + 1, rest, sum) + process1(arr, index + 1, rest - 1, sum + arr[index]); + } + } + + public static int number2(int[] arr, int k) { + if (arr == null || arr.length == 0 || k < 1 || k > arr.length) { + return 0; + } + int n = arr.length; + // even[i][j] : 在前i个数的范围上(0...i-1),一定选j个数,加起来是偶数的子序列个数 + // odd[i][j] : 在前i个数的范围上(0...i-1),一定选j个数,加起来是奇数的子序列个数 + int[][] even = new int[n + 1][k + 1]; + int[][] odd = new int[n + 1][k + 1]; + for (int i = 0; i <= n; i++) { + // even[0][0] = 1; + // even[1][0] = 1; + // even[2][0] = 1; + // even[n][0] = 1; + even[i][0] = 1; + } + for (int i = 1; i <= n; i++) { + for (int j = 1; j <= Math.min(i, k); j++) { + even[i][j] = even[i - 1][j]; + odd[i][j] = odd[i - 1][j]; + even[i][j] += (arr[i - 1] & 1) == 0 ? even[i - 1][j - 1] : odd[i - 1][j - 1]; + odd[i][j] += (arr[i - 1] & 1) == 0 ? odd[i - 1][j - 1] : even[i - 1][j - 1]; + } + } + return even[n][k]; + } + + // 为了测试 + public static int[] randomArray(int n, int v) { + int[] ans = new int[n]; + for (int i = 0; i < n; i++) { + ans[i] = (int) (Math.random() * v); + } + return ans; + } + + // 为了测试 + public static void main(String[] args) { + int N = 20; + int V = 30; + int testTimes = 3000; + System.out.println("测试开始"); + for (int i = 0; i < testTimes; i++) { + int n = (int) (Math.random() * N) + 1; + int[] arr = randomArray(n, V); + int k = (int) (Math.random() * n) + 1; + int ans1 = number1(arr, k); + int ans2 = number2(arr, k); + if (ans1 != ans2) { + System.out.println("出错了"); + for (int num : arr) { + System.out.print(num + " "); + } + System.out.println(); + System.out.println(k); + System.out.println(ans1); + System.out.println(ans2); + } + } + System.out.println("测试结束"); + } + +} diff --git a/算法周更班/class_2022_04_3_week/Code05_ModKSubstringNumbers.java b/算法周更班/class_2022_04_3_week/Code05_ModKSubstringNumbers.java new file mode 100644 index 0000000..c69cec2 --- /dev/null +++ b/算法周更班/class_2022_04_3_week/Code05_ModKSubstringNumbers.java @@ -0,0 +1,83 @@ +package class_2022_04_3_week; + +// 来自微众 +// 4.11笔试 +// 给定n位长的数字字符串和正数k,求该子符串能被k整除的子串个数 +// (n<=1000,k<=100) +public class Code05_ModKSubstringNumbers { + + // 暴力方法 + // 为了验证 + public static int modWays1(String s, int k) { + int n = s.length(); + int ans = 0; + for (int i = 0; i < n; i++) { + for (int j = i; j < n; j++) { + if (Long.valueOf(s.substring(i, j + 1)) % k == 0) { + ans++; + } + } + } + return ans; + } + + // 正式方法 + // 时间复杂度O(N * k) + public static int modWays2(String s, int k) { + int[] cur = new int[k]; + // 帮忙迁移 + int[] next = new int[k]; + // 0...i 整体余几? + int mod = 0; + // 答案:统计有多少子串的值%k == 0 + int ans = 0; + for (char cha : s.toCharArray()) { + for (int i = 0; i < k; i++) { + // i -> 10个 + // (i * 10) % k + next[(i * 10) % k] += cur[i]; + cur[i] = 0; + } + int[] tmp = cur; + cur = next; + next = tmp; + mod = (mod * 10 + (cha - '0')) % k; + ans += (mod == 0 ? 1 : 0) + cur[mod]; + cur[mod]++; + } + return ans; + } + + // 为了测试 + public static String randomNumber(int n) { + char[] ans = new char[n]; + for (int i = 0; i < n; i++) { + ans[i] = (char) ((int) (Math.random() * 10) + '0'); + } + return String.valueOf(ans); + } + + // 为了测试 + public static void main(String[] args) { + int N = 18; + int K = 20; + int testTime = 10000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + String str = randomNumber((int) (Math.random() * N) + 1); + int k = (int) (Math.random() * K) + 1; + int ans1 = modWays1(str, k); + int ans2 = modWays2(str, k); + if (ans1 != ans2) { + System.out.println("出错了!"); + System.out.println(str); + System.out.println(k); + System.out.println(ans1); + System.out.println(ans2); + break; + } + } + System.out.println("测试结束"); + } + +} diff --git a/算法周更班/class_2022_05_1_week/Code01_JumMinSameValue.java b/算法周更班/class_2022_05_1_week/Code01_JumMinSameValue.java new file mode 100644 index 0000000..8274de1 --- /dev/null +++ b/算法周更班/class_2022_05_1_week/Code01_JumMinSameValue.java @@ -0,0 +1,79 @@ +package class_2022_05_1_week; + +import java.util.ArrayList; +import java.util.HashMap; + +// 来自蔚来汽车 +// 给你一个整数数组 arr ,你一开始在数组的第一个元素处(下标为 0)。 +// 每一步,你可以从下标 i 跳到下标 i + 1 、i - 1 或者 j : +// i + 1 需满足:i + 1 < arr.length +// i - 1 需满足:i - 1 >= 0 +// j 需满足:arr[i] == arr[j] 且 i != j +// 请你返回到达数组最后一个元素的下标处所需的 最少操作次数 。 +// 注意:任何时候你都不能跳到数组外面。 +// leetcode测试链接 : https://leetcode-cn.com/problems/jump-game-iv/ +public class Code01_JumMinSameValue { + + public static int minJumps(int[] arr) { + int n = arr.length; + // 为了找某个值,有哪些位置,能快一些 + // key : 某个值9, + // value : 列表:0,7,19 + HashMap> valueIndex = new HashMap<>(); + for (int i = 0; i < n; i++) { + if (!valueIndex.containsKey(arr[i])) { + valueIndex.put(arr[i], new ArrayList<>()); + } + valueIndex.get(arr[i]).add(i); + } + // i会有哪些展开:左,右,i通过自己的值,能蹦到哪些位置上去 + // 宽度优先遍历,遍历过的位置,不希望重复处理 + // visited[i] == false:i位置,之前没来过,可以处理 + // visited[i] == true : i位置,之前来过,可以跳过 + boolean[] visited = new boolean[n]; + int[] queue = new int[n]; + int l = 0; + int r = 0; + // 0位置加到队列里去 + queue[r++] = 0; + visited[0] = true; + int jump = 0; + // 宽度优先遍历 + // 一次,遍历一整层! + // 该技巧,多次出现! + while (l != r) { // 队列里还有东西的意思! + // 此时的r记录! + // 0 1 2 | 3 4 5 6 7 8 + // 当前层的终止位置 + int tmp = r; + for (; l < tmp; l++) { // 遍历当前层! + int cur = queue[l]; + if (cur == n - 1) { + return jump; + } + if (cur + 1 < n && !visited[cur + 1]) { + visited[cur + 1] = true; + queue[r++] = cur + 1; + } + // cur > 0 cur - 1 >=0 + if (cur > 0 && !visited[cur - 1]) { + visited[cur - 1] = true; + queue[r++] = cur - 1; + } + // i -> 9 + // 值同样为9的那些位置,也能去 + for (int next : valueIndex.get(arr[cur])) { + if (!visited[next]) { + visited[next] = true; + queue[r++] = next; + } + } + // 重要优化! + valueIndex.get(arr[cur]).clear(); + } + jump++; + } + return -1; + } + +} diff --git a/算法周更班/class_2022_05_1_week/Code02_WhoWin21Balls.java b/算法周更班/class_2022_05_1_week/Code02_WhoWin21Balls.java new file mode 100644 index 0000000..6be690a --- /dev/null +++ b/算法周更班/class_2022_05_1_week/Code02_WhoWin21Balls.java @@ -0,0 +1,160 @@ +package class_2022_05_1_week; + +// 来自微众 +// 人工智能岗 +// 一开始有21个球,甲和乙轮流拿球,甲先、乙后 +// 每个人在自己的回合,一定要拿不超过3个球,不能不拿 +// 最终谁的总球数为偶数,谁赢 +// 请问谁有必胜策略 +public class Code02_WhoWin21Balls { + + // balls = 21 + // ball是奇数 + public static String win(int balls) { + return process(0, balls, 0, 0); + } + + // 憋递归! + // turn 谁的回合! + // turn == 0 甲回合 + // turn == 1 乙回合 + // rest剩余球的数量 + // 之前,jiaBalls、yiBalls告诉你! + // 当前,根据turn,知道是谁的回合! + // 当前,还剩多少球,rest + // 返回:谁会赢! + public static String process(int turn, int rest, int jia, int yi) { + if (rest == 0) { + return (jia & 1) == 0 ? "甲" : "乙"; + } + // rest > 0, 还剩下球! + if (turn == 0) { // 甲的回合! + // 甲,自己赢!甲赢! + for (int pick = 1; pick <= Math.min(rest, 3); pick++) { + // pick 甲当前做的选择 + if (process(1, rest - pick, jia + pick, yi).equals("甲")) { + return "甲"; + } + } + return "乙"; + } else { + for (int pick = 1; pick <= Math.min(rest, 3); pick++) { + // pick 甲当前做的选择 + if (process(0, rest - pick, jia, yi + pick).equals("乙")) { + return "乙"; + } + } + return "甲"; + } + } + + // 我们补充一下设定,假设一开始的球数量不是21,是任意的正数 + // 如果最终两个人拿的都是偶数,认为无人获胜,平局 + // 如果最终两个人拿的都是奇数,认为无人获胜,平局 + // rest代表目前剩下多少球 + // cur == 0 代表目前是甲行动 + // cur == 1 代表目前是乙行动 + // first == 0 代表目前甲所选的球数,加起来是偶数 + // first == 1 代表目前甲所选的球数,加起来是奇数 + // second == 0 代表目前乙所选的球数,加起来是偶数 + // second == 1 代表目前乙所选的球数,加起来是奇数 + // 返回选完了rest个球,谁会赢,只会返回"甲"、"乙"、"平" + // win1方法,就是彻底暴力的做所有尝试,并且返回最终的胜利者 + // 在甲的回合,甲会尝试所有的可能,以保证自己会赢,如果自己怎么都不会赢,那也要尽量平局,如果这个也不行,只能对方赢 + // 在乙的回合,乙会尝试所有的可能,以保证自己会赢,如果自己怎么都不会赢,那也要尽量平局,如果这个也不行,只能对方赢 + // 算法和数据结构体系学习班,视频39章节,牛羊吃草问题,就是类似这种递归 + public static String win1(int rest, int cur, int first, int second) { + if (rest == 0) { + if (first == 0 && second == 1) { + return "甲"; + } + if (first == 1 && second == 0) { + return "乙"; + } + return "平"; + } + if (cur == 0) { // 甲行动 + String bestAns = "乙"; + for (int pick = 1; pick <= Math.min(3, rest); pick++) { + String curAns = win1(rest - pick, 1, first ^ (pick & 1), second); + if (curAns.equals("甲")) { + bestAns = "甲"; + break; + } + if (curAns.equals("平")) { + bestAns = "平"; + } + } + return bestAns; + } else { // 乙行动 + String bestAns = "甲"; + for (int pick = 1; pick <= Math.min(3, rest); pick++) { + String curAns = win1(rest - pick, 0, first, second ^ (pick & 1)); + if (curAns.equals("乙")) { + bestAns = "乙"; + break; + } + if (curAns.equals("平")) { + bestAns = "平"; + } + } + return bestAns; + } + } + + // 下面的win2方法,仅仅是把win1方法,做了记忆化搜索 + // 变成了动态规划 + public static String[][][][] dp = new String[5000][2][2][2]; + + public static String win2(int rest, int cur, int first, int second) { + if (rest == 0) { + if (first == 0 && second == 1) { + return "甲"; + } + if (first == 1 && second == 0) { + return "乙"; + } + return "平"; + } + if (dp[rest][cur][first][second] != null) { + return dp[rest][cur][first][second]; + } + if (cur == 0) { // 甲行动 + String bestAns = "乙"; + for (int pick = 1; pick <= Math.min(3, rest); pick++) { + String curAns = win2(rest - pick, 1, first ^ (pick & 1), second); + if (curAns.equals("甲")) { + bestAns = "甲"; + break; + } + if (curAns.equals("平")) { + bestAns = "平"; + } + } + dp[rest][cur][first][second] = bestAns; + return bestAns; + } else { // 乙行动 + String bestAns = "甲"; + for (int pick = 1; pick <= Math.min(3, rest); pick++) { + String curAns = win2(rest - pick, 0, first, second ^ (pick & 1)); + if (curAns.equals("乙")) { + bestAns = "乙"; + break; + } + if (curAns.equals("平")) { + bestAns = "平"; + } + } + dp[rest][cur][first][second] = bestAns; + return bestAns; + } + } + + // 为了测试 + public static void main(String[] args) { + for (int balls = 1; balls <= 500; balls += 2) { + System.out.println("球数为 " + balls + " 时 , 赢的是 " + win(balls)); + } + } + +} \ No newline at end of file diff --git a/算法周更班/class_2022_05_1_week/Code03_FindDuplicateOnlyOne.java b/算法周更班/class_2022_05_1_week/Code03_FindDuplicateOnlyOne.java new file mode 100644 index 0000000..ecbc931 --- /dev/null +++ b/算法周更班/class_2022_05_1_week/Code03_FindDuplicateOnlyOne.java @@ -0,0 +1,114 @@ +package class_2022_05_1_week; + +import java.util.Arrays; +import java.util.HashSet; + +// 来自学员问题,真实大厂面试题 +// 1、2、3...n-1、n、n、n+1、n+2... +// 在这个序列中,只有一个数字有重复(n) +// 这个序列是无序的,找到重复数字n +// 这个序列是有序的,找到重复数字n +public class Code03_FindDuplicateOnlyOne { + + // 为了测试 + // 绝对正确,但是直接遍历+哈希表,没有得分的方法 + public static int right(int[] arr) { + HashSet set = new HashSet<>(); + for (int num : arr) { + if (set.contains(num)) { + return num; + } + set.add(num); + } + return -1; + } + + // 符合题目要求的、无序数组,找重复数 + // 时间复杂度O(N),额外空间复杂度O(1) + public static int findDuplicate(int[] arr) { + if (arr == null || arr.length < 2) { + return -1; + } + int slow = arr[0]; + int fast = arr[arr[0]]; + while (slow != fast) { + slow = arr[slow]; + fast = arr[arr[fast]]; + } + // slow == fast + fast = 0; + while (slow != fast) { + fast = arr[fast]; + slow = arr[slow]; + } + // 再相遇!一个结论 + return slow; + } + + // 符合题目要求的、有序数组,找重复数 + // 时间复杂度O(logN),额外空间复杂度O(1) + public static int findDuplicateSorted(int[] arr) { + if (arr == null || arr.length < 2) { + return -1; + } + int l = 0; + int r = arr.length - 1; + int m = 0; + int ans = -1; + while (l <= r) { + m = (l + r) / 2; + if ((m - 1 >= 0 && arr[m - 1] == arr[m]) || (m + 1 < arr.length && arr[m + 1] == arr[m])) { + ans = arr[m]; + break; + } + if (m - l == arr[m] - arr[l]) { + l = m + 1; + } else { + r = m - 1; + } + } + return ans; + } + + // 为了测试 + public static int[] randomArray(int n) { + int[] ans = new int[n + 1]; + for (int i = 0; i < n; i++) { + ans[i] = i + 1; + } + ans[n] = (int) (Math.random() * n) + 1; + for (int i = n; i > 0; i--) { + int j = (int) (Math.random() * (i + 1)); + int tmp = ans[i]; + ans[i] = ans[j]; + ans[j] = tmp; + } + return ans; + } + + // 为了测试 + public static void main(String[] args) { + int N = 10; + int testTime = 5000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int[] arr = randomArray((int) (Math.random() * N) + 1); + if (right(arr) != findDuplicate(arr)) { + System.out.println("未排序情况出错!"); + } + Arrays.sort(arr); + if (right(arr) != findDuplicateSorted(arr)) { + System.out.println("排序情况出错!"); + for (int num : arr) { + System.out.print(num + " "); + } + System.out.println(); + System.out.println(right(arr)); + System.out.println(findDuplicateSorted(arr)); + break; + } + } + System.out.println("测试结束"); + } + +} diff --git a/算法周更班/class_2022_05_1_week/Code04_SumOfQuadraticSum.java b/算法周更班/class_2022_05_1_week/Code04_SumOfQuadraticSum.java new file mode 100644 index 0000000..0af7de4 --- /dev/null +++ b/算法周更班/class_2022_05_1_week/Code04_SumOfQuadraticSum.java @@ -0,0 +1,113 @@ +package class_2022_05_1_week; + +// 来自学员问题,蓝桥杯练习题 +// f(i) : i的所有因子,每个因子都平方之后,累加起来 +// 比如f(10) = 1平方 + 2平方 + 5平方 + 10平方 = 1 + 4 + 25 + 100 = 130 +// 给定一个数n,求f(1) + f(2) + .. + f(n) +// n <= 10的9次方 +// O(n)的方法都会超时!低于它的! +// O(根号N)的方法,就过了,一个思路 +// O(log N)的方法, +public class Code04_SumOfQuadraticSum { + + // 暴力方法 + public static long sum1(long n) { + int[] cnt = new int[(int) n + 1]; + for (int num = 1; num <= n; num++) { + for (int j = 1; j <= num; j++) { + if (num % j == 0) { + cnt[j]++; + } + } + } + long ans = 0; + for (long i = 1; i <= n; i++) { + ans += i * i * (long) (cnt[(int) i]); + } + return ans; + } + + // 正式方法 + // 时间复杂度O(开平方根N + 开平方根N * logN) + public static long sum2(long n) { + // 100 -> 10 + // 200 -> 14 + long sqrt = (long) Math.pow((double) n, 0.5); + long ans = 0; + for (long i = 1; i <= sqrt; i++) { + ans += i * i * (n / i); + } + // 后半段 + // 给你一个个数,二分出几个因子,处在这个个数上! + // 由最大个数(根号N), 开始二分 + for (long k = n / (sqrt + 1); k >= 1; k--) { + ans += sumOfLimitNumber(n, k); + } + return ans; + } + + // 平方和公式n(n+1)(2n+1)/6 + public static long sumOfLimitNumber(long v, long n) { + long r = cover(v, n); + long l = cover(v, n + 1); + return ((r * (r + 1) * ((r << 1) + 1) + - l * (l + 1) * ((l << 1) + 1)) * n) + / 6; + } + + public static long cover(long v, long n) { + long l = 1; + long r = v; + long m = 0; + long ans = 0; + while (l <= r) { + m = (l + r) / 2; + if (m * n <= v) { + ans = m; + l = m + 1; + } else { + r = m - 1; + } + } + return ans; + } + + // 实验 + // 解法来自观察 + // 打表(暴力) + // f(1) + ... + f(n) + public static void test(int n) { + int[] cnt = new int[n + 1]; + for (int num = 1; num <= n; num++) { + for (int j = 1; j <= num; j++) { + if (num % j == 0) { + cnt[j]++; + } + } + } + for (int i = 1; i <= n; i++) { + System.out.println("因子 : " + i + ", 个数 : " + cnt[i]); + } + } + + public static void main(String[] args) { + +// test(100); + + System.out.println("测试开始"); + for (long i = 1; i < 1000; i++) { + if (sum1(i) != sum2(i)) { + System.out.println("出错了!"); + } + } + System.out.println("测试结束"); + + long n = 50000000000L; // 5 * 10的10次方 + long start = System.currentTimeMillis(); + sum2(n); + long end = System.currentTimeMillis(); + System.out.println("大样本测试,n = " + n); + System.out.println("运行时间 : " + (end - start) + " ms"); + } + +} diff --git a/算法周更班/class_2022_05_1_week/Code05_PalindromeStringNoLessKLenNoOverlapingMaxParts.java b/算法周更班/class_2022_05_1_week/Code05_PalindromeStringNoLessKLenNoOverlapingMaxParts.java new file mode 100644 index 0000000..31f67a2 --- /dev/null +++ b/算法周更班/class_2022_05_1_week/Code05_PalindromeStringNoLessKLenNoOverlapingMaxParts.java @@ -0,0 +1,131 @@ +package class_2022_05_1_week; + +// 来自optiver +// 给定一个字符串str,和一个正数k +// 你可以随意的划分str成多个子串, +// 目的是找到在某一种划分方案中,有尽可能多的回文子串,长度>=k,并且没有重合 +// 返回有几个回文子串 +public class Code05_PalindromeStringNoLessKLenNoOverlapingMaxParts { + + // 暴力尝试 + // 为了测试 + // 可以改成动态规划,但不是最优解 + public static int max1(String s, int k) { + if (s == null || s.length() == 0) { + return 0; + } + char[] str = s.toCharArray(); + return process1(str, 0, k); + } + + public static int process1(char[] str, int index, int k) { + if (str.length - index < k) { + return 0; + } + int ans = process1(str, index + 1, k); + for (int i = index + k - 1; i < str.length; i++) { + if (isPalindrome(str, index, i)) { + ans = Math.max(ans, 1 + process1(str, i + 1, k)); + } + } + return ans; + } + + public static boolean isPalindrome(char[] str, int L, int R) { + while (L < R) { + if (str[L++] != str[R--]) { + return false; + } + } + return true; + } + + // 最优解 + // 时间复杂度O(N) + public static int max2(String s, int k) { + if (s == null || s.length() == 0) { + return 0; + } + char[] str = manacherString(s); + int[] p = new int[str.length]; + int ans = 0; + int next = 0; + // k == 5 回文串长度要 >= 5 + // next == 0 + // 0.... 8 第一块! + // next -> 9 + // 9.....17 第二块! + // next -> 18 + // 18....23 第三块 + // next一直到最后! + while ((next = manacherFind(str, p, next, k)) != -1) { + next = str[next] == '#' ? next : (next + 1); + ans++; + } + return ans; + } + + public static char[] manacherString(String s) { + char[] str = s.toCharArray(); + char[] ans = new char[s.length() * 2 + 1]; + int index = 0; + for (int i = 0; i != ans.length; i++) { + ans[i] = (i & 1) == 0 ? '#' : str[index++]; + } + return ans; + } + + // s[l...]字符串只在这个范围上,且s[l]一定是'#' + // 从下标l开始,之前都不算,一旦有某个中心回文半径>k,马上返回右边界 + public static int manacherFind(char[] s, int[] p, int l, int k) { + int c = l - 1; + int r = l - 1; + int n = s.length; + for (int i = l; i < s.length; i++) { + p[i] = r > i ? Math.min(p[2 * c - i], r - i) : 1; + while (i + p[i] < n && i - p[i] > l - 1 && s[i + p[i]] == s[i - p[i]]) { + if (++p[i] > k) { + return i + k; + } + } + if (i + p[i] > r) { + r = i + p[i]; + c = i; + } + } + return -1; + } + + // 为了测试 + public static String randomString(int n, int r) { + char[] str = new char[(int) (Math.random() * n)]; + for (int i = 0; i < str.length; i++) { + str[i] = (char) ((int) (Math.random() * r) + 'a'); + } + return String.valueOf(str); + } + + // 为了测试 + public static void main(String[] args) { + int n = 20; + int r = 3; + int testTime = 50000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + String str = randomString(n, r); + int k = (int) (Math.random() * str.length()) + 1; + int ans1 = max1(str, k); + int ans2 = max2(str, k); + if (ans1 != ans2) { + System.out.println(str); + System.out.println(k); + System.out.println(ans1); + System.out.println(ans2); + System.out.println("出错了!"); + break; + } + } + System.out.println("测试结束"); + } + +} diff --git a/算法周更班/class_2022_05_2_week/Code01_TwoObjectMaxValue.java b/算法周更班/class_2022_05_2_week/Code01_TwoObjectMaxValue.java new file mode 100644 index 0000000..be8c0f8 --- /dev/null +++ b/算法周更班/class_2022_05_2_week/Code01_TwoObjectMaxValue.java @@ -0,0 +1,155 @@ +package class_2022_05_2_week; + +import java.util.Arrays; + +// 来自字节 +// 5.6笔试 +// 给定N件物品,每个物品有重量(w[i])、有价值(v[i]) +// 只能最多选两件商品,重量不超过bag,返回价值最大能是多少? +// N <= 10^5, w[i] <= 10^5, v[i] <= 10^5, bag <= 10^5 +// 本题的关键点:什么数据范围都很大,唯独只需要最多选两件商品,这个可以利用一下 +public class Code01_TwoObjectMaxValue { + + // 暴力方法 + // 为了验证而写的方法 + public static int max1(int[] w, int[] v, int bag) { + return process1(w, v, 0, 2, bag); + } + + public static int process1(int[] w, int[] v, int index, int restNumber, int restWeight) { + if (restNumber < 0 || restWeight < 0) { + return -1; + } + if (index == w.length) { + return 0; + } + int p1 = process1(w, v, index + 1, restNumber, restWeight); + int p2 = -1; + int next = process1(w, v, index + 1, restNumber - 1, restWeight - w[index]); + if (next != -1) { + p2 = v[index] + next; + } + return Math.max(p1, p2); + } + + // 正式方法 + // 时间复杂度O(N * logN) + public static int max2(int[] w, int[] v, int bag) { + int n = w.length; + int[][] arr = new int[n][2]; + for (int i = 0; i < n; i++) { + arr[i][0] = w[i]; + arr[i][1] = v[i]; + } + // O(N * logN) + Arrays.sort(arr, (a, b) -> (a[0] - b[0])); + // 重量从轻到重,依次标号1、2、3、4.... + // 价值依次被构建成了RMQ结构 + // O(N * logN) + RMQ rmq = new RMQ(arr); + int ans = 0; + // N * logN + for (int i = 0, j = 1; i < n && arr[i][0] <= bag; i++, j++) { + // 当前来到0号货物,RMQ结构1号 + // 当前来到i号货物,RMQ结构i+1号 + // 查询重量的边界,重量 边界 <= bag - 当前货物的重量 + // 货物数组中,找到 <= 边界,最右的位置i + // RMQ,位置 i + 1 + int right = right(arr, bag - arr[i][0]) + 1; + int rest = 0; + // j == i + 1,当前的货物,在RMQ里的下标 + if (right == j) { + rest = rmq.max(1, right - 1); + } else if (right < j) { + rest = rmq.max(1, right); + } else { // right > j + rest = Math.max(rmq.max(1, j - 1), rmq.max(j + 1, right)); + } + ans = Math.max(ans, arr[i][1] + rest); + } + return ans; + } + + public static int right(int[][] arr, int limit) { + int l = 0; + int r = arr.length - 1; + int m = 0; + int ans = -1; + while (l <= r) { + m = (l + r) / 2; + if (arr[m][0] <= limit) { + ans = m; + l = m + 1; + } else { + r = m - 1; + } + } + return ans; + } + + public static class RMQ { + public int[][] max; + + public RMQ(int[][] arr) { + int n = arr.length; + int k = power2(n); + max = new int[n + 1][k + 1]; + for (int i = 1; i <= n; i++) { + max[i][0] = arr[i - 1][1]; + } + for (int j = 1; (1 << j) <= n; j++) { + for (int i = 1; i + (1 << j) - 1 <= n; i++) { + max[i][j] = Math.max(max[i][j - 1], max[i + (1 << (j - 1))][j - 1]); + } + } + } + + public int max(int l, int r) { + if (r < l) { + return 0; + } + int k = power2(r - l + 1); + return Math.max(max[l][k], max[r - (1 << k) + 1][k]); + } + + private int power2(int m) { + int ans = 0; + while ((1 << ans) <= (m >> 1)) { + ans++; + } + return ans; + } + + } + + // 为了测试 + public static int[] randomArray(int n, int v) { + int[] arr = new int[n]; + for (int i = 0; i < n; i++) { + arr[i] = (int) (Math.random() * v); + } + return arr; + } + + // 为了测试 + public static void main(String[] args) { + int N = 12; + int V = 20; + int testTimes = 5000; + System.out.println("测试开始"); + for (int i = 0; i < testTimes; i++) { + int n = (int) (Math.random() * N) + 1; + int[] w = randomArray(n, V); + int[] v = randomArray(n, V); + int bag = (int) (Math.random() * V * 3); + int ans1 = max1(w, v, bag); + int ans2 = max2(w, v, bag); + if (ans1 != ans2) { + System.out.println("出错了!"); + break; + } + } + System.out.println("测试结束"); + } + +} diff --git a/算法周更班/class_2022_05_2_week/Code02_ModifyOneNumberModXWays.java b/算法周更班/class_2022_05_2_week/Code02_ModifyOneNumberModXWays.java new file mode 100644 index 0000000..ec32159 --- /dev/null +++ b/算法周更班/class_2022_05_2_week/Code02_ModifyOneNumberModXWays.java @@ -0,0 +1,84 @@ +package class_2022_05_2_week; + +// 来自网易 +// 小红拿到了一个长度为N的数组arr,她准备只进行一次修改 +// 可以将数组中任意一个数arr[i],修改为不大于P的正数(修改后的数必须和原数不同) +// 并使得所有数之和为X的倍数 +// 小红想知道,一共有多少种不同的修改方案 +// 1 <= N, X <= 10^5 +// 1 <= arr[i], P <= 10^9 +public class Code02_ModifyOneNumberModXWays { + + public static int ways1(int[] arr, int p, int x) { + long sum = 0; + for (int num : arr) { + sum += num; + } + int ans = 0; + for (int num : arr) { + sum -= num; + for (int v = 1; v <= p; v++) { + if (v != num) { + if ((sum + v) % x == 0) { + ans++; + } + } + } + sum += num; + } + return ans; + } + + public static int ways2(int[] arr, int p, int x) { + long sum = 0; + for (int num : arr) { + sum += num; + } + int ans = 0; + for (int num : arr) { + ans += cnt(p, x, num, (x - (int) ((sum - num) % x)) % x); + } + return ans; + } + + // 当前数字num + // 1~p以内,不能是num的情况下,% x == mod的数字有几个 + // O(1) + public static int cnt(int p, int x, int num, int mod) { + // p/x 至少有几个 + // (p % x) >= mod ? 1 : 0 + // 在不考虑变出来的数,是不是num的情况下,算一下有几个数,符合要求 + int ans = (p / x) + ((p % x) >= mod ? 1 : 0) - (mod == 0 ? 1 : 0); + // 不能等于num! + return ans - ((num <= p && num % x == mod) ? 1 : 0); + } + + // 为了测试 + public static int[] randomArray(int n, int v) { + int[] ans = new int[n]; + for (int i = 0; i < n; i++) { + ans[i] = (int) (Math.random() * v) + 1; + } + return ans; + } + + public static void main(String[] args) { + int len = 100; + int value = 100; + int testTime = 100000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int n = (int) (Math.random() * len) + 1; + int[] arr = randomArray(n, value); + int p = (int) (Math.random() * value) + 1; + int x = (int) (Math.random() * value) + 1; + int ans1 = ways1(arr, p, x); + int ans2 = ways2(arr, p, x); + if (ans1 != ans2) { + System.out.println("出错了!"); + } + } + System.out.println("测试结束"); + } + +} diff --git a/算法周更班/class_2022_05_2_week/Code03_SortedSubsequenceMaxSum.java b/算法周更班/class_2022_05_2_week/Code03_SortedSubsequenceMaxSum.java new file mode 100644 index 0000000..967b8ef --- /dev/null +++ b/算法周更班/class_2022_05_2_week/Code03_SortedSubsequenceMaxSum.java @@ -0,0 +1,162 @@ +package class_2022_05_2_week; + +import java.util.Arrays; + +// 来自字节 +// 一共有n个人,从左到右排列,依次编号0~n-1 +// h[i]是第i个人的身高 +// v[i]是第i个人的分数 +// 要求从左到右选出一个子序列,在这个子序列中的人,从左到右身高是不下降的 +// 返回所有符合要求的子序列中,分数最大累加和是多大 +// n <= 10的5次方, 1 <= h[i] <= 10的9次方, 1 <= v[i] <= 10的9次方 +public class Code03_SortedSubsequenceMaxSum { + + // 为了测试 + // 绝对正确的暴力方法 + public static int right(int[] h, int[] v) { + return process(h, v, 0, 0); + } + + public static int process(int[] h, int[] v, int index, int preValue) { + if (index == h.length) { + return 0; + } + int p1 = process(h, v, index + 1, preValue); + int p2 = h[index] >= preValue ? (v[index] + process(h, v, index + 1, h[index])) : 0; + return Math.max(p1, p2); + } + + // 正式方法 + // 时间复杂度O(N * logN) + public static int maxSum(int[] h, int[] v) { + int n = h.length; + int[] rank = new int[n]; + for (int i = 0; i < n; i++) { + rank[i] = h[i]; + } + Arrays.sort(rank); + SegmentTree st = new SegmentTree(n); + for (int i = 0; i < n; i++) { + int height = rank(rank, h[i]); + // 1~height max + st.update(height, st.max(height) + v[i]); + } + return st.max(n); + } + + // [150, 152, 160, 175] 160 + // 1 2 3 4 + // 3 + public static int rank(int[] rank, int num) { + int l = 0; + int r = rank.length - 1; + int m = 0; + int ans = 0; + while (l <= r) { + m = (l + r) / 2; + if (rank[m] >= num) { + ans = m; + r = m - 1; + } else { + l = m + 1; + } + } + return ans + 1; + } + + public static class SegmentTree { + private int n; + private int[] max; + private int[] update; + + public SegmentTree(int maxSize) { + n = maxSize + 1; + max = new int[n << 2]; + update = new int[n << 2]; + Arrays.fill(update, -1); + } + + public void update(int index, int c) { + update(index, index, c, 1, n, 1); + } + + public int max(int right) { + return max(1, right, 1, n, 1); + } + + private void pushUp(int rt) { + max[rt] = Math.max(max[rt << 1], max[rt << 1 | 1]); + } + + private void pushDown(int rt, int ln, int rn) { + if (update[rt] != -1) { + update[rt << 1] = update[rt]; + max[rt << 1] = update[rt]; + update[rt << 1 | 1] = update[rt]; + max[rt << 1 | 1] = update[rt]; + update[rt] = -1; + } + } + + private void update(int L, int R, int C, int l, int r, int rt) { + if (L <= l && r <= R) { + max[rt] = C; + update[rt] = C; + return; + } + int mid = (l + r) >> 1; + pushDown(rt, mid - l + 1, r - mid); + if (L <= mid) { + update(L, R, C, l, mid, rt << 1); + } + if (R > mid) { + update(L, R, C, mid + 1, r, rt << 1 | 1); + } + pushUp(rt); + } + + private int max(int L, int R, int l, int r, int rt) { + if (L <= l && r <= R) { + return max[rt]; + } + int mid = (l + r) >> 1; + pushDown(rt, mid - l + 1, r - mid); + int ans = 0; + if (L <= mid) { + ans = Math.max(ans, max(L, R, l, mid, rt << 1)); + } + if (R > mid) { + ans = Math.max(ans, max(L, R, mid + 1, r, rt << 1 | 1)); + } + return ans; + } + + } + + // 为了测试 + public static int[] randomArray(int n, int v) { + int[] ans = new int[n]; + for (int i = 0; i < n; i++) { + ans[i] = (int) (Math.random() * v) + 1; + } + return ans; + } + + // 为了测试 + public static void main(String[] args) { + int N = 30; + int V = 100; + int testTime = 20000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int n = (int) (Math.random() * N) + 1; + int[] h = randomArray(n, V); + int[] v = randomArray(n, V); + if (right(h, v) != maxSum(h, v)) { + System.out.println("出错了!"); + } + } + System.out.println("测试结束"); + } + +} diff --git a/算法周更班/class_2022_05_2_week/Code04_OneEdgeMagicMinPathSum.java b/算法周更班/class_2022_05_2_week/Code04_OneEdgeMagicMinPathSum.java new file mode 100644 index 0000000..c2e88d5 --- /dev/null +++ b/算法周更班/class_2022_05_2_week/Code04_OneEdgeMagicMinPathSum.java @@ -0,0 +1,173 @@ +package class_2022_05_2_week; + +import java.util.ArrayList; +import java.util.PriorityQueue; + +// 来自网易 +// 给出一个有n个点,m条有向边的图 +// 你可以施展魔法,把有向边,变成无向边 +// 比如A到B的有向边,权重为7。施展魔法之后,A和B通过该边到达彼此的代价都是7。 +// 求,允许施展一次魔法的情况下,1到n的最短路,如果不能到达,输出-1。 +// n为点数, 每条边用(a,b,v)表示,含义是a到b的这条边,权值为v +// 点的数量 <= 10^5,边的数量 <= 2 * 10^5,1 <= 边的权值 <= 10^6 +public class Code04_OneEdgeMagicMinPathSum { + + // 为了测试 + // 相对暴力的解 + // 尝试每条有向边,都变一次无向边,然后跑一次dijkstra算法 + // 那么其中一定有最好的答案 + public static int min1(int n, int[][] roads) { + int ans = Integer.MAX_VALUE; + for (int i = 0; i < roads.length; i++) { + ArrayList> graph = new ArrayList<>(); + for (int j = 0; j <= n; j++) { + graph.add(new ArrayList<>()); + } + graph.get(roads[i][1]).add(new int[] { roads[i][0], roads[i][2] }); + for (int[] r : roads) { + graph.get(r[0]).add(new int[] { r[1], r[2] }); + } + ans = Math.min(ans, dijkstra1(n, graph)); + } + return ans == Integer.MAX_VALUE ? -1 : ans; + } + + public static int dijkstra1(int n, ArrayList> graph) { + PriorityQueue heap = new PriorityQueue<>((a, b) -> a[1] - b[1]); + boolean[] visited = new boolean[n + 1]; + heap.add(new int[] { 1, 0 }); + int ans = Integer.MAX_VALUE; + while (!heap.isEmpty()) { + int[] cur = heap.poll(); + if (cur[0] == n) { + ans = cur[1]; + break; + } + if (visited[cur[0]]) { + continue; + } + visited[cur[0]] = true; + for (int[] edge : graph.get(cur[0])) { + int to = edge[0]; + int weight = edge[1]; + if (!visited[to]) { + heap.add(new int[] { to, cur[1] + weight }); + } + } + } + return ans; + } + + // 最优解 + // 时间复杂度O(N * logN) + // N <= 2 * 10^5 + public static int min2(int n, int[][] roads) { + ArrayList> graph = new ArrayList<>(); + for (int i = 0; i <= n; i++) { + graph.add(new ArrayList<>()); + } + for (int[] r : roads) { + graph.get(r[0]).add(new int[] { 0, r[1], r[2] }); + graph.get(r[1]).add(new int[] { 1, r[0], r[2] }); + } + PriorityQueue heap = new PriorityQueue<>((a, b) -> a[2] - b[2]); + boolean[][] visited = new boolean[2][n + 1]; + // a -> 0,a 1,a + // boolean[] visted = new boolean[n+1] + // visted[i] == true 去过了!从队列里弹出来过了!以后别碰了! + // visted[i] == false 没去过!第一次从队列里弹出来!当前要处理! + // 0,1,0 -> 之前没有走过魔法路,当前来到1号出发点,代价是0 + heap.add(new int[] { 0, 1, 0 }); + int ans = Integer.MAX_VALUE; + while (!heap.isEmpty()) { + int[] cur = heap.poll(); + if (visited[cur[0]][cur[1]]) { + continue; + } + visited[cur[0]][cur[1]] = true; + if (cur[1] == n) { + ans = Math.min(ans, cur[2]); + if (visited[0][n] && visited[1][n]) { + break; + } + } + for (int[] edge : graph.get(cur[1])) { + // 当前来到cur + // 之前有没有走过魔法路径:cur[0] == 0 ,没走过!cur[0] = 1, 走过了 + // 当前来到的点是啥,cur[1],点编号! + // 之前的总代价是啥?cur[2] + // cur,往下,能走的,所有的路在哪? + // 当前的路,叫edge + // 当前的路,是不是魔法路!edge[0] = 0 , 不是魔法路 + // edge[0] == 1,是魔法路 + // cur[0] + edge[0] == 0 + // 路 :0 5 20 + // 当前路,不是魔法路,去往的点是5号点,该路权重是20 + // 路 :1 7 13 + // 当前路,是魔法路,去往的点是7号点,该路权重是13 + if (cur[0] + edge[0] == 0) { + if (!visited[0][edge[1]]) { + heap.add(new int[] { 0, edge[1], cur[2] + edge[2] }); + } + } + // cur[0] + edge[0] == 1 + // 0 1 + // 1 0 + if (cur[0] + edge[0] == 1) { + if (!visited[1][edge[1]]) { + heap.add(new int[] { 1, edge[1], cur[2] + edge[2] }); + } + } + // 1 1 == 2 + } + } + return ans == Integer.MAX_VALUE ? -1 : ans; + } + + // 为了测试 + public static int[][] randomRoads(int n, int v) { + int m = (int) (Math.random() * (n * (n - 1) / 2)) + 1; + int[][] roads = new int[m][3]; + for (int i = 0; i < m; i++) { + roads[i][0] = (int) (Math.random() * n) + 1; + roads[i][1] = (int) (Math.random() * n) + 1; + roads[i][2] = (int) (Math.random() * v) + 1; + } + return roads; + } + + // 为了测试 + public static void main(String[] args) { + int N = 20; + int V = 30; + int testTime = 20000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int n = (int) (Math.random() * N) + 1; + int[][] roads = randomRoads(n, V); + if (min1(n, roads) != min2(n, roads)) { + System.out.println("出错了!"); + } + } + System.out.println("测试结束"); + + // 点的数量10^5 + int n = 100000; + // 边的数量2 * 10^5 + int m = 200000; + // 时间复杂度很明显和边的权重是没关系的 + // 所以这里设置小一点,防止出现溢出的解 + int v = 100; + int[][] roads = new int[m][3]; + for (int i = 0; i < m; i++) { + roads[i][0] = (int) (Math.random() * n) + 1; + roads[i][1] = (int) (Math.random() * n) + 1; + roads[i][2] = (int) (Math.random() * v) + 1; + } + long start = System.currentTimeMillis(); + System.out.println("运行结果 : " + min2(n, roads)); + long end = System.currentTimeMillis(); + System.out.println("运行时间(毫秒) : " + (end - start)); + } + +} diff --git a/算法周更班/class_2022_05_2_week/Code05_RedAndWhiteSquares.java b/算法周更班/class_2022_05_2_week/Code05_RedAndWhiteSquares.java new file mode 100644 index 0000000..040b33c --- /dev/null +++ b/算法周更班/class_2022_05_2_week/Code05_RedAndWhiteSquares.java @@ -0,0 +1,211 @@ +package class_2022_05_2_week; + +// 来自网易 +// 小红拿到了一个大立方体,该大立方体由1*1*1的小方块拼成,初始每个小方块都是白色。 +// 小红可以每次选择一个小方块染成红色 +// 每次小红可能选择同一个小方块重复染色 +// 每次染色以后,你需要帮小红回答出当前的白色连通块数 +// 如果两个小方块共用同一个面,且颜色相同,则它们是连通的 +// 给定n、m、h,表示大立方体的长、宽、高 +// 给定k次操作,每一次操作用(a, b, c)表示在大立方体的该位置进行染色 +// 返回长度为k的数组,表示每一次操作后,白色方块的连通块数 +// n * m * h <= 10 ^ 5,k <= 10 ^ 5 +public class Code05_RedAndWhiteSquares { + + // 暴力方法 + // 时间复杂度(k * n * m * h); + public static int[] blocks1(int n, int m, int h, int[][] ops) { + int k = ops.length; + int[][][] cube = new int[n][m][h]; + int value = 1; + int[] ans = new int[k]; + for (int i = 0; i < k; i++) { + cube[ops[i][0]][ops[i][1]][ops[i][2]] = -1; + for (int x = 0; x < n; x++) { + for (int y = 0; y < m; y++) { + for (int z = 0; z < h; z++) { + if (cube[x][y][z] != -1 && cube[x][y][z] != value) { + ans[i]++; + infect(cube, x, y, z, value); + } + } + } + } + value++; + } + return ans; + } + + public static void infect(int[][][] cube, int a, int b, int c, int change) { + if (a < 0 || a == cube.length || b < 0 || b == cube[0].length || c < 0 || c == cube[0][0].length + || cube[a][b][c] == -1 || cube[a][b][c] == change) { + return; + } + cube[a][b][c] = change; + infect(cube, a - 1, b, c, change); + infect(cube, a + 1, b, c, change); + infect(cube, a, b - 1, c, change); + infect(cube, a, b + 1, c, change); + infect(cube, a, b, c - 1, change); + infect(cube, a, b, c + 1, change); + } + + // 最优解 + // O(k + n * m * h) + public static int[] blocks2(int n, int m, int h, int[][] ops) { + int k = ops.length; + int[][][] red = new int[n][m][h]; + for (int[] op : ops) { + red[op[0]][op[1]][op[2]]++; + } + UnionFind uf = new UnionFind(n, m, h, red); + int[] ans = new int[k]; + for (int i = k - 1; i >= 0; i--) { + ans[i] = uf.sets; + int x = ops[i][0]; + int y = ops[i][1]; + int z = ops[i][2]; + if (--red[x][y][z] == 0) { + // x, y ,z 这个格子,变白,建立自己的小集合 + // 然后6个方向,集合该合并合并 + uf.finger(x, y, z); + } + } + return ans; + } + + public static class UnionFind { + public int n; + public int m; + public int h; + public int[] father; + public int[] size; + public int[] help; + public int sets; + + public UnionFind(int a, int b, int c, int[][][] red) { + n = a; + m = b; + h = c; + int len = n * m * h; + father = new int[len]; + size = new int[len]; + help = new int[len]; + for (int x = 0; x < n; x++) { + for (int y = 0; y < m; y++) { + for (int z = 0; z < h; z++) { + if (red[x][y][z] == 0) { + finger(x, y, z); + } + } + } + } + } + + public void finger(int x, int y, int z) { + // x,y,z + // 一维数值 + int i = index(x, y, z); + father[i] = i; + size[i] = 1; + sets++; + union(i, x - 1, y, z); + union(i, x + 1, y, z); + union(i, x, y - 1, z); + union(i, x, y + 1, z); + union(i, x, y, z - 1); + union(i, x, y, z + 1); + } + + private int index(int x, int y, int z) { + return z * n * m + y * n + x; + } + + private void union(int i, int x, int y, int z) { + if (x < 0 || x == n || y < 0 || y == m || z < 0 || z == h) { + return; + } + int j = index(x, y, z); + if (size[j] == 0) { + return; + } + i = find(i); + j = find(j); + if (i != j) { + if (size[i] >= size[j]) { + father[j] = i; + size[i] += size[j]; + } else { + father[i] = j; + size[j] += size[i]; + } + sets--; + } + } + + private int find(int i) { + int s = 0; + while (i != father[i]) { + help[s++] = i; + i = father[i]; + } + while (s > 0) { + father[help[--s]] = i; + } + return i; + } + + } + + // 为了测试 + public static int[][] randomOps(int n, int m, int h) { + int size = (int) (Math.random() * (n * m * h)) + 1; + int[][] ans = new int[size][3]; + for (int i = 0; i < size; i++) { + ans[i][0] = (int) (Math.random() * n); + ans[i][1] = (int) (Math.random() * m); + ans[i][2] = (int) (Math.random() * h); + } + return ans; + } + + // 为了测试 + public static void main(String[] args) { + int size = 10; + int testTime = 5000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int n = (int) (Math.random() * size) + 1; + int m = (int) (Math.random() * size) + 1; + int h = (int) (Math.random() * size) + 1; + int[][] ops = randomOps(n, m, h); + int[] ans1 = blocks1(n, m, h, ops); + int[] ans2 = blocks2(n, m, h, ops); + for (int j = 0; j < ops.length; j++) { + if (ans1[j] != ans2[j]) { + System.out.println("出错了!"); + } + } + } + System.out.println("测试结束"); + + // 立方体达到10^6规模 + int n = 100; + int m = 100; + int h = 100; + int len = n * m * h; + // 操作条数达到10^6规模 + int[][] ops = new int[len][3]; + for (int i = 0; i < len; i++) { + ops[i][0] = (int) (Math.random() * n); + ops[i][1] = (int) (Math.random() * m); + ops[i][2] = (int) (Math.random() * h); + } + long start = System.currentTimeMillis(); + blocks2(n, m, h, ops); + long end = System.currentTimeMillis(); + System.out.println("运行时间(毫秒) : " + (end - start)); + + } + +} diff --git a/算法周更班/class_2022_05_3_week/Code01_MaxNumberUnderLimit.java b/算法周更班/class_2022_05_3_week/Code01_MaxNumberUnderLimit.java new file mode 100644 index 0000000..924ac09 --- /dev/null +++ b/算法周更班/class_2022_05_3_week/Code01_MaxNumberUnderLimit.java @@ -0,0 +1,188 @@ +package class_2022_05_3_week; + +import java.util.Arrays; + +// 来自字节 +// 输入: +// 去重数组arr,里面的数只包含0~9 +// limit,一个数字 +// 返回: +// 要求比limit小的情况下,能够用arr拼出来的最大数字 +public class Code01_MaxNumberUnderLimit { + + public static int tmp = 0; + + // 暴力尝试的方法 + public static int maxNumber1(int[] arr, int limit) { + tmp = 0; + Arrays.sort(arr); + limit--; + int offset = 1; + while (offset <= limit / 10) { + offset *= 10; + } + process1(arr, 0, offset, limit); + if (tmp == 0) { + int rest = 0; + offset /= 10; + while (offset > 0) { + rest += arr[arr.length - 1] * offset; + offset /= 10; + } + return rest; + } + return tmp; + } + + public static void process1(int[] arr, int num, int offset, int limit) { + if (offset == 0) { + if (num <= limit) { + tmp = Math.max(tmp, num); + } + } else { + for (int cur : arr) { + process1(arr, num * 10 + cur, offset / 10, limit); + } + } + } + + // 正式方法 + public static int maxNumber2(int[] arr, int limit) { + // arr里面是不重复的数字,且只包含0~9 + Arrays.sort(arr); + limit--; + // <= limit 且最大的数字 + // 68886 + // 10000 + // 为了取数而设计的! + // 457 + // 100 + int offset = 1; + while (offset <= limit / 10) { + offset *= 10; + } + int ans = process2(arr, limit, offset); + if (ans != -1) { + return ans; + } else { + offset /= 10; + int rest = 0; + while (offset > 0) { + rest += arr[arr.length - 1] * offset; + offset /= 10; + } + return rest; + } + } + + // 可以选哪些数字,都在arr里,arr是有序的,[3,6,8,9] + // limit : <= limit 且尽量的大! 68886 + // offset : 10000 + // 1000 + // 100 + // offset 下标用! + public static int process2(int[] arr, int limit, int offset) { + // 之前的数字和limit完全一样,且limit所有数字都一样 + if (offset == 0) { + return limit; + } + // 当前的数字 + // 68886 + // 10000 + // 6 + int cur = (limit / offset) % 10; + // 6 arr中 <=6 最近的! + int near = near(arr, cur); + if (near == -1) { + return -1; + } else if (arr[near] == cur) { // 找出来的数字,真的和当前数字cur一样! + int ans = process2(arr, limit, offset / 10); + if (ans != -1) { + return ans; + } else if (near > 0) { // 虽然后续没成功,但是我自己还能下降!还能选更小的数字 + near--; + return (limit / (offset * 10)) * offset * 10 + (arr[near] * offset) + rest(arr, offset / 10); + } else { // 后续没成功,我自己也不能再下降了!宣告失败,往上返回! + return -1; + } + } else { // arr[near] < cur + return (limit / (offset * 10)) * offset * 10 + (arr[near] * offset) + rest(arr, offset / 10); + } + } + + // 比如offset = 100 + // 一共3位数 + // 那么就把arr中最大的数字x,拼成xxx,返回 + // 比如offset = 10000 + // 一共5位数 + // 那么就把arr中最大的数字x,拼成xxxxx,返回 + public static int rest(int[] arr, int offset) { + int rest = 0; + while (offset > 0) { + rest += arr[arr.length - 1] * offset; + offset /= 10; + } + return rest; + } + + // 在有序数组arr中,找到<=num,且最大的数字,在arr中的位置返回 + // 如果所有数字都大于num,返回-1 + // [3,6,9] num = 4 3 + // [5,7,9] num = 4 -1 + public static int near(int[] arr, int num) { + int l = 0; + int r = arr.length - 1; + int m = 0; + int near = -1; + while (l <= r) { + m = (l + r) / 2; + if (arr[m] <= num) { + near = m; + l = m + 1; + } else { + r = m - 1; + } + } + return near; + } + + // 为了测试 + public static int[] randomArray() { + int[] arr = new int[(int) (Math.random() * 10) + 1]; + boolean[] cnt = new boolean[10]; + for (int i = 0; i < arr.length; i++) { + do { + arr[i] = (int) (Math.random() * 10); + } while (cnt[arr[i]]); + cnt[arr[i]] = true; + } + return arr; + } + + public static void main(String[] args) { + int max = 3000; + int testTime = 100; + System.out.println("测试开始"); + for (int i = 0; i < max; i++) { + int[] arr = randomArray(); + for (int j = 0; j < testTime; j++) { + int ans1 = maxNumber1(arr, i); + int ans2 = maxNumber2(arr, i); + if (ans1 != ans2) { + System.out.println("出错了!"); + System.out.println("数组为 :"); + for (int num : arr) { + System.out.print(num + " "); + } + System.out.println(); + System.out.println("数字为 :" + i); + System.out.println(ans1); + System.out.println(ans2); + } + } + } + System.out.println("测试结束"); + + } + +} diff --git a/算法周更班/class_2022_05_3_week/Code02_RemoveNumbersNotIncreasingAll.java b/算法周更班/class_2022_05_3_week/Code02_RemoveNumbersNotIncreasingAll.java new file mode 100644 index 0000000..9c09f2f --- /dev/null +++ b/算法周更班/class_2022_05_3_week/Code02_RemoveNumbersNotIncreasingAll.java @@ -0,0 +1,162 @@ +package class_2022_05_3_week; + +// 来自京东 +// 4.2笔试 +// 给定一个数组arr,长度为N,arr中所有的值都在1~K范围上 +// 你可以删除数字,目的是让arr的最长递增子序列长度小于K +// 返回至少删除几个数字能达到目的 +// N <= 10^4,K <= 10^2 +public class Code02_RemoveNumbersNotIncreasingAll { + + // 暴力方法 + // 为了验证 + public static int minRemove1(int[] arr, int k) { + return process1(arr, 0, new int[arr.length], 0, k); + } + + public static int process1(int[] arr, int index, int[] path, int size, int k) { + if (index == arr.length) { + return lengthOfLIS(path, size) < k ? (arr.length - size) : Integer.MAX_VALUE; + } else { + int p1 = process1(arr, index + 1, path, size, k); + path[size] = arr[index]; + int p2 = process1(arr, index + 1, path, size + 1, k); + return Math.min(p1, p2); + } + } + + public static int lengthOfLIS(int[] arr, int size) { + if (size == 0) { + return 0; + } + int[] ends = new int[size]; + ends[0] = arr[0]; + int right = 0; + int l = 0; + int r = 0; + int m = 0; + int max = 1; + for (int i = 1; i < size; i++) { + l = 0; + r = right; + while (l <= r) { + m = (l + r) / 2; + if (arr[i] > ends[m]) { + l = m + 1; + } else { + r = m - 1; + } + } + right = Math.max(right, l); + ends[l] = arr[i]; + max = Math.max(max, l + 1); + } + return max; + } + + // arr[0...index-1]上,选择了一些数字,之前的决定! + // len长度了!len = 3 : 1 2 3 + // arr[index....]是能够决定的,之前的,已经不能再决定了 + // 返回:让最终保留的数字,凑不足k长度的情况下,至少要删几个! + public static int zuo(int[] arr, int index, int len, int k) { + if (len == k) { + return Integer.MAX_VALUE; + } + // 凑的(1...len)还不到(1...k) + if (index == arr.length) { + return 0; + } + // 没凑到 < k, 有数字! + int cur = arr[index]; + // 可能性1:保留 + // 可能性2:删除 + // 1...3 3 + if (len >= cur || len + 1 < cur) { + return zuo(arr, index + 1, len, k); + } + // 1..3 4 + // len + 1 == cur + // 可能性1:保留 + int p1 = zuo(arr, index + 1, len + 1, k); + // 可能性2:删除 + int p2 = Integer.MAX_VALUE; + int next2 = zuo(arr, index + 1, len, k); + if(next2 != Integer.MAX_VALUE) { + p2 = 1 + next2; + } + return Math.min(p1, p2); + } + + // 正式方法 + // 时间复杂度O(N*K) + public static int minRemove2(int[] arr, int k) { + int n = arr.length; + int[][] dp = new int[n][k]; + for (int i = 0; i < n; i++) { + for (int j = 0; j < k; j++) { + dp[i][j] = -1; + } + } + return process2(arr, k, 0, 0, dp); + } + + public static int process2(int[] arr, int k, int index, int range, int[][] dp) { + if (range == k) { + return Integer.MAX_VALUE; + } + if (index == arr.length) { + return 0; + } + if (dp[index][range] != -1) { + return dp[index][range]; + } + int ans = 0; + if (arr[index] == range + 1) { + int p1 = process2(arr, k, index + 1, range, dp); + p1 += p1 != Integer.MAX_VALUE ? 1 : 0; + int p2 = process2(arr, k, index + 1, range + 1, dp); + ans = Math.min(p1, p2); + } else { + ans = process2(arr, k, index + 1, range, dp); + } + dp[index][range] = ans; + return ans; + } + + // 为了验证 + public static int[] randomArray(int len, int k) { + int[] arr = new int[len]; + for (int i = 0; i < len; i++) { + arr[i] = (int) (Math.random() * k) + 1; + } + return arr; + } + + // 为了验证 + public static void main(String[] args) { + int N = 15; + int K = 6; + int testTime = 10000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int len = (int) (Math.random() * N) + 1; + int k = (int) (Math.random() * K) + 1; + int[] arr = randomArray(len, k); + int ans1 = minRemove1(arr, k); + int ans2 = minRemove2(arr, k); + if (ans1 != ans2) { + System.out.println("出错了!"); + for (int num : arr) { + System.out.print(num + " "); + } + System.out.println(); + System.out.println("k : " + k); + System.out.println("ans1 : " + ans1); + System.out.println("ans2 : " + ans2); + break; + } + } + System.out.println("测试结束"); + } + +} diff --git a/算法周更班/class_2022_05_3_week/Code03_NumberOfCannon.java b/算法周更班/class_2022_05_3_week/Code03_NumberOfCannon.java new file mode 100644 index 0000000..6e482c1 --- /dev/null +++ b/算法周更班/class_2022_05_3_week/Code03_NumberOfCannon.java @@ -0,0 +1,97 @@ +package class_2022_05_3_week; + +import java.util.TreeMap; +import java.util.TreeSet; + +// 来自学员问题 +// 给定一个数组arr,表示从早到晚,依次会出现的导弹的高度 +// 大炮打导弹的时候,如果一旦大炮定了某个高度去打,那么这个大炮每次打的高度都必须下降一点 +// 1) 如果只有一个大炮,返回最多能拦截多少导弹 +// 2) 如果所有的导弹都必须拦截,返回最少的大炮数量 +public class Code03_NumberOfCannon { + + public static int numOfCannon(int[] arr) { + // key : 某个大炮打的结尾数值 + // value : 有多少个大炮有同样的结尾数值 + // 比如: + // 一共有A、B、C三个大炮 + // 如果A大炮此时打的高度是17,B大炮此时打的高度是7,C大炮此时打的高度是13 + // 那么在表中: + // 7, 1 + // 13, 1 + // 17, 1 + // 如果A大炮此时打的高度是13,B大炮此时打的高度是7,C大炮此时打的高度是13 + // 那么在表中: + // 7, 1 + // 13, 2 + TreeMap ends = new TreeMap<>(); + for (int num : arr) { + if (ends.ceilingKey(num + 1) == null) { + ends.put(Integer.MAX_VALUE, 1); + } + int ceilKey = ends.ceilingKey(num + 1); + if (ends.get(ceilKey) > 1) { + ends.put(ceilKey, ends.get(ceilKey) - 1); + } else { + ends.remove(ceilKey); + } + ends.put(num, ends.getOrDefault(num, 0) + 1); + } + int ans = 0; + for (int value : ends.values()) { + ans += value; + } + return ans; + } + + public static void main(String[] args) { + + // 有序表来说 + // add + // remove + // ceiling + // <= floor + // O(logN)! + TreeSet set = new TreeSet<>(); + + set.add(17); + set.add(20); + set.add(25); + + // >= 23 + System.out.println(set.ceiling(26)); + + // 有序表是去重的,key去重 + // A :99 + // B : 99 + // C : 99 + + TreeMap map = new TreeMap<>(); + map.put(99, 3); + // 76 + + if (map.ceilingKey(76) == null) { + // 没有大炮可以打76 + // 新开一门大炮,打76 + // 这个新跑,只能打75~ + map.put(75, map.getOrDefault(75, 0) + 1); + } else { // 之前有大炮可以打76,不需要新开一门炮! + int key = map.ceilingKey(76); + // 99 -1 75 +1 + + if(map.get(key) > 1) { + map.put(key, map.get(key) - 1); + }else { + map.remove(key); + } + + map.put(75, map.getOrDefault(75, 0) + 1); + } + + +// +// int[] arr = { 15, 7, 14, 6, 5, 13, 5, 10, 9 }; +// System.out.println(numOfCannon(arr)); + } + +} diff --git a/算法周更班/class_2022_05_3_week/Code04_MinJumpUsePre.java b/算法周更班/class_2022_05_3_week/Code04_MinJumpUsePre.java new file mode 100644 index 0000000..692109d --- /dev/null +++ b/算法周更班/class_2022_05_3_week/Code04_MinJumpUsePre.java @@ -0,0 +1,154 @@ +package class_2022_05_3_week; + +import java.util.Arrays; + +// 来自学员问题 +// 为了给刷题的同学一些奖励,力扣团队引入了一个弹簧游戏机 +// 游戏机由 N 个特殊弹簧排成一排,编号为 0 到 N-1 +// 初始有一个小球在编号 0 的弹簧处。若小球在编号为 i 的弹簧处 +// 通过按动弹簧,可以选择把小球向右弹射 jump[i] 的距离,或者向左弹射到任意左侧弹簧的位置 +// 也就是说,在编号为 i 弹簧处按动弹簧, +// 小球可以弹向 0 到 i-1 中任意弹簧或者 i+jump[i] 的弹簧(若 i+jump[i]>=N ,则表示小球弹出了机器) +// 小球位于编号 0 处的弹簧时不能再向左弹。 +// 为了获得奖励,你需要将小球弹出机器。 +// 请求出最少需要按动多少次弹簧,可以将小球从编号 0 弹簧弹出整个机器,即向右越过编号 N-1 的弹簧。 +// 测试链接 : https://leetcode-cn.com/problems/zui-xiao-tiao-yue-ci-shu/ +public class Code04_MinJumpUsePre { + + // 宽度优先遍历 + // N*logN + public int minJump(int[] jump) { + int n = jump.length; + int[] queue = new int[n]; + int l = 0; + int r = 0; + queue[r++] = 0; + IndexTree it = new IndexTree(n); + // 1...n初始化的时候 每个位置填上1 + for (int i = 1; i < n; i++) { + it.add(i, 1); + } + int step = 0; + while (l != r) { // 队列里面还有东西 + // tmp记录了当前层的终止位置! + int tmp = r; + // 当前层的所有节点,都去遍历! + for (; l < tmp; l++) { + int cur = queue[l]; + int forward = cur + jump[cur]; + if (forward >= n) { + return step + 1; + } + if (it.value(forward) != 0) { + queue[r++] = forward; + it.add(forward, -1); + } + // cur + // 1....cur-1 cur + while (it.sum(cur - 1) != 0) { + int find = find(it, cur - 1); + it.add(find, -1); + queue[r++] = find; + } + } + step++; + } + return -1; + } + + public static int find(IndexTree it, int right) { + int left = 0; + int mid = 0; + int find = 0; + while (left <= right) { + mid = (left + right) / 2; + if (it.sum(mid) > 0) { + find = mid; + right = mid - 1; + } else { + left = mid + 1; + } + } + return find; + } + + public static class IndexTree { + + private int[] tree; + private int N; + + public IndexTree(int size) { + N = size; + tree = new int[N + 1]; + } + + public int value(int index) { + if (index == 0) { + return sum(0); + } else { + return sum(index) - sum(index - 1); + } + } + + public int sum(int i) { + int index = i + 1; + int ret = 0; + while (index > 0) { + ret += tree[index]; + index -= index & -index; + } + return ret; + } + + public void add(int i, int d) { + int index = i + 1; + while (index <= N) { + tree[index] += d; + index += index & -index; + } + } + + } + + // 感谢黄汀同学 + // 弄出了时间复杂度O(N)的过程 + // 和大厂刷题班,第10节,jump game类似 + public int minJump2(int[] jump) { + int N = jump.length; + int ans = N; + int next = jump[0]; + if (next >= N) { + return 1; + } + if (next + jump[next] >= N) { + return 2; + } + // dp[i] : 来到i位置,最少跳几步? + int[] dp = new int[N + 1]; + Arrays.fill(dp, N); + // dis[i] : <= i步的情况下,最远能跳到哪? + int[] dis = new int[N]; + // 如果从0开始向前跳,<=1步的情况下,最远当然能到next + dis[1] = next; + // 如果从0开始向前跳,<=2步的情况下,最远可能比next + jump[next]要远, + // 这里先设置,以后可能更新 + dis[2] = next + jump[next]; + dp[next + jump[next]] = 2; + int step = 1; + for (int i = 1; i < N; i++) { + if (i > dis[step]) { + step++; + } + dp[i] = Math.min(dp[i], step + 1); + next = i + jump[i]; + if (next >= N) { + ans = Math.min(ans, dp[i] + 1); + } else if (dp[next] > dp[i] + 1) { + dp[next] = dp[i] + 1; + dis[dp[next]] = Math.max(dis[dp[next]], next); + } + } + return ans; + } + +} diff --git a/算法周更班/class_2022_05_4_week/Code01_SomeDPFromVT.java b/算法周更班/class_2022_05_4_week/Code01_SomeDPFromVT.java new file mode 100644 index 0000000..7f25965 --- /dev/null +++ b/算法周更班/class_2022_05_4_week/Code01_SomeDPFromVT.java @@ -0,0 +1,246 @@ +package class_2022_05_4_week; + +// 来自弗吉尼亚理工大学(VT),算法考试卷 +// 精选了还可以的几道题 +// 这些都是简单难度的动态规划,是面试中最常见的难度 +// 这几个题都有一些非常小的常见技巧可说 +public class Code01_SomeDPFromVT { + +// // arr[i][0] : 有趣值 +// // arr[i][1] : 进攻值 +// // arr[index...]所有的方案自由选择 +// // 必须让restFunny、restOffense值 <= 0 +// // 返回最小的方案数量(index...) +// public static int process(int[][] arr, int index, int restFunny, int restOffense) { +// if (restFunny <= 0 && restOffense <= 0) { +// return 0; +// } +// // 有的值,还没扣完 +// if (index == arr.length) { +// return Integer.MAX_VALUE; // 无效值 +// } +// // 有的值还没扣完 但是还有方案可选 +// // index号方案 不要 要 +// int p1 = process(arr, index + 1, restFunny, restOffense); +// // 要用index号方案 +// int p2 = Integer.MAX_VALUE; +// int next = process(arr, index + 1, restFunny - arr[index][0], restOffense - arr[index][1]); +// if (next != Integer.MAX_VALUE) { +// p2 = 1 + next; +// } +// return Math.min(p1, p2); +// } + + // 题目1 + // 方案1 : {7, 10} + // xxxx : {a , b} + // 1 2 3 4 + // FunnyGoal = 100 + // OffenseGoal = 130 + // 找到一个最少方案数,让FunnyGoal、OffenseGoal,都大于等于 + // 定义如下尝试过程 + // 贴纸数组stickers + // stickers[i][0] : i号贴纸的Funny值 + // stickers[i][1] : i号贴纸的Offense值 + // index....所有的贴纸,随便选择。index之前的贴纸不能选择, + // 在让restFunny和restOffense都小于等于0的要求下,返回最少的贴纸数量 + public static int minStickers1(int[][] stickers, int funnyGoal, int offenseGoal) { + return process1(stickers, 0, funnyGoal, offenseGoal); + } + + public static int process1(int[][] stickers, int index, int restFunny, int restOffense) { + if (restFunny <= 0 && restOffense <= 0) { + return 0; + } + if (index == stickers.length) { + return Integer.MAX_VALUE; + } + // 不选当前的贴纸 + int p1 = process1(stickers, index + 1, restFunny, restOffense); + // 选当前贴纸 + int p2 = Integer.MAX_VALUE; + int next2 = process1(stickers, index + 1, Math.max(0, restFunny - stickers[index][0]), + Math.max(0, restOffense - stickers[index][1])); + if (next2 != Integer.MAX_VALUE) { + p2 = next2 + 1; + } + return Math.min(p1, p2); + } + + // 改动态规划 + public static int minStickers2(int[][] stickers, int funnyGoal, int offenseGoal) { + int[][][] dp = new int[stickers.length][funnyGoal + 1][offenseGoal + 1]; + for (int i = 0; i < stickers.length; i++) { + for (int j = 0; j <= funnyGoal; j++) { + for (int k = 0; k <= offenseGoal; k++) { + dp[i][j][k] = -1; + } + } + } + return process2(stickers, 0, funnyGoal, offenseGoal, dp); + } + + public static int process2(int[][] stickers, int index, int restFunny, int restOffense, int[][][] dp) { + if (restFunny <= 0 && restOffense <= 0) { + return 0; + } + if (index == stickers.length) { + return Integer.MAX_VALUE; + } + if (dp[index][restFunny][restOffense] != -1) { + return dp[index][restFunny][restOffense]; + } + // 不选当前的贴纸 + int p1 = process2(stickers, index + 1, restFunny, restOffense, dp); + // 选当前贴纸 + int p2 = Integer.MAX_VALUE; + int next2 = process2(stickers, index + 1, Math.max(0, restFunny - stickers[index][0]), + Math.max(0, restOffense - stickers[index][1]), dp); + if (next2 != Integer.MAX_VALUE) { + p2 = next2 + 1; + } + int ans = Math.min(p1, p2); + dp[index][restFunny][restOffense] = ans; + return ans; + } + + // 严格位置依赖的动态规划 + public static int minStickers3(int[][] stickers, int funnyGoal, int offenseGoal) { + int n = stickers.length; + int[][][] dp = new int[n + 1][funnyGoal + 1][offenseGoal + 1]; + for (int f = 0; f <= funnyGoal; f++) { + for (int o = 0; o <= offenseGoal; o++) { + if (f != 0 || o != 0) { + dp[n][f][o] = Integer.MAX_VALUE; + } + } + } + for (int i = n - 1; i >= 0; i--) { + for (int f = 0; f <= funnyGoal; f++) { + for (int o = 0; o <= offenseGoal; o++) { + if (f != 0 || o != 0) { + int p1 = dp[i + 1][f][o]; + int p2 = Integer.MAX_VALUE; + int next2 = dp[i + 1][Math.max(0, f - stickers[i][0])][Math.max(0, o - stickers[i][1])]; + if (next2 != Integer.MAX_VALUE) { + p2 = next2 + 1; + } + dp[i][f][o] = Math.min(p1, p2); + } + } + } + } + return dp[0][funnyGoal][offenseGoal]; + } + + // 题目2 + // 绳子总长度为M + // 100 -> M + // (6, 100) (7,23) (10,34) -> arr + // 每一个长度的绳子对应一个价格,比如(6, 10)表示剪成长度为6的绳子,对应价格10 + // 可以重复切出某个长度的绳子 + // 定义递归如下: + // 所有可以切出来的长度 对应 价值都在数组ropes里 + // ropes[i] = {6, 10} 代表i方案为:切出长度为6的绳子,可以卖10元 + // index....所有的方案,随便选择。index之前的方案,不能选择 + // 返回最大的价值 + // 自己去改动态规划 + // arr[i][0] -> i号方案能切多少长度 + // arr[i][1] -> 切出来这个长度,就能获得的价值 + // arr[index....]自由选择,绳子还剩restLen长度 + // 返回,最大价值 + public static int maxValue(int[][] arr, int index, int restLen) { + if (restLen <= 0 || index == arr.length) { + return 0; + } + // 绳子还有剩余、且还有方案 + // index号方案 + // 不选 + int p1 = maxValue(arr, index + 1, restLen); + // 选 + int p2 = 0; + if (arr[index][0] <= restLen) { // 剩余绳子够长,才能选当前方案 + p2 = arr[index][1] + maxValue(arr, index, restLen - arr[index][0]); + } + return Math.max(p1, p2); + } + +// public static int maxValue(int[][] ropes, int index, int restLen) { +// if (restLen <= 0) { +// return 0; +// } +// // 当前index方案,就是不考虑 +// int p1 = maxValue(ropes, index + 1, restLen); +// // 当前index方案,考虑,然后因为可以重复选,所以注意下面的子过程调用 +// int p2 = -1; +// if (ropes[index][0] <= restLen) { +// // 当前index方案,选了一份 +// // 但是下面依然可以重复的选index方案 +// // 所以子过程里的index不增加,只是剩余长度减少 +// p2 = ropes[index][1] + maxValue(ropes, index, restLen - ropes[index][0]); +// } +// return Math.max(p1, p2); +// } + + // 题目3 + // 每一个序列都是[a,b]的形式,a < b + // 序列连接的方式为,前一个序列的b,要等于后一个序列的a + // 比如 : [3, 7]、[7, 13]、[13, 26]这三个序列就可以依次连接 + // 给定若干个序列,求最大连接的数量 + // 定义尝试过程如下 + // arr[i] = {4, 9}表示,第i个序列4开始,9结束 + // pre : 代表选择的上一个序列,的,index是多少 + // 比如选择的上一个序列如果是(4,9),是第5个序列,那么pre==5 + // 特别注意:如果从来没有选过序列,那么pre == -1 + // 这个函数含义 : + // index....所有的序列,随便选择。index之前的序列,不能选择 + // 上一个选择的序列,是pre号,如果pre==-1,说明之前没有选择过序列 + // 返回题目要求的那种连接方式下,最大的序列数量 + // [5,13] [1,19] [2, 3] [79, 81] ... + // [1,19] [2, 3] [5, 13] [79, 81] + // arr[i][0] : 开头 + // arr[i][1] : 结尾 + // arr已经根据开头排过序了! + // preEnd index + // [1, 3] [2, 4] [4, 7] + // 0 1 2 + // maxLen(0, -1) + // 0(选) -> maxLen(1, 0) + // 在arr[index...]选择序列,之前选的,离index最近的序列,位置在preIndex + // 请返回,index...能链接起来的,序列数量的最大值 + public static int maxLen(int[][] arr, int index, int preIndex) { + if (index == arr.length) { + return 0; + } + // 还有序列可以选 + // index号序列 + // 不选 + int p1 = maxLen(arr, index + 1, preIndex); + // 选 + int p2 = 0; + // [3,17] index(9,24) + if (arr[preIndex][1] == arr[index][0]) { // 才能选 + p2 = 1 + maxLen(arr, index + 1, index); + } + return Math.max(p1, p2); + } + + // O(N^2) + public static int maxNumberSubsequence(int[][] arr, int index, int pre) { + if (index == arr.length) { + return 0; + } + // 就是不要当前序列 + int p1 = maxNumberSubsequence(arr, index + 1, pre); + // 要当前序列 + int p2 = -1; + if (pre == -1 || arr[pre][1] == arr[index][0]) { + p2 = 1 + maxNumberSubsequence(arr, index + 1, index); + } + return Math.max(p1, p2); + } + + // O(N) + + +} diff --git a/算法周更班/class_2022_05_4_week/Code02_MinSetForEveryRange.java b/算法周更班/class_2022_05_4_week/Code02_MinSetForEveryRange.java new file mode 100644 index 0000000..28d2617 --- /dev/null +++ b/算法周更班/class_2022_05_4_week/Code02_MinSetForEveryRange.java @@ -0,0 +1,48 @@ +package class_2022_05_4_week; + +import java.util.Arrays; +import java.util.HashSet; + +// 给定区间的范围[xi,yi],xi<=yi,且都是正整数 +// 找出一个坐标集合set,set中有若干个数字 +// set要和每个给定的区间,有交集 +// 求set的最少需要几个数 +// 比如给定区间 : [5, 8] [1, 7] [2, 4] [1, 9] +// set最小可以是: {2, 6}或者{2, 5}或者{4, 5} +public class Code02_MinSetForEveryRange { + + public static int minSet(int[][] ranges) { + int n = ranges.length; + // events[i] = {a, b, c} + // a == 0, 表示这是一个区间的开始事件,这个区间结束位置是b + // a == 1, 表示这是一个区间的结束事件,b的值没有意义 + // c表示这个事件的时间点,不管是开始事件还是结束事件,都会有c这个值 + int[][] events = new int[n << 1][3]; + for (int i = 0; i < n; i++) { + // [3, 7] + // (0,7,3) + // (1,X,7) + events[i][0] = 0; + events[i][1] = ranges[i][1]; + events[i][2] = ranges[i][0]; + events[i + n][0] = 1; + events[i + n][2] = ranges[i][1]; + } + Arrays.sort(events, (a, b) -> a[2] - b[2]); + // 容器 + HashSet tmp = new HashSet<>(); + int ans = 0; + for (int[] event : events) { + if (event[0] == 0) { + tmp.add(event[1]); + } else { + if (tmp.contains(event[2])) { + ans++; + tmp.clear(); + } + } + } + return ans; + } + +} diff --git a/算法周更班/class_2022_05_4_week/Code03_MaxIncreasingSubarrayCanDeleteContinuousPart.java b/算法周更班/class_2022_05_4_week/Code03_MaxIncreasingSubarrayCanDeleteContinuousPart.java new file mode 100644 index 0000000..6eeb8ec --- /dev/null +++ b/算法周更班/class_2022_05_4_week/Code03_MaxIncreasingSubarrayCanDeleteContinuousPart.java @@ -0,0 +1,221 @@ +package class_2022_05_4_week; + +import java.util.Arrays; + +// 来自字节 +// 5.6笔试 +// 给定一个数组arr,长度为n,最多可以删除一个连续子数组, +// 求剩下的数组,严格连续递增的子数组最大长度 +// n <= 10^6 +public class Code03_MaxIncreasingSubarrayCanDeleteContinuousPart { + + // 暴力方法 + // 为了验证 + public static int maxLen1(int[] arr) { + int ans = max(arr); + int n = arr.length; + for (int L = 0; L < n; L++) { + for (int R = L; R < n; R++) { + int[] cur = delete(arr, L, R); + ans = Math.max(ans, max(cur)); + } + } + return ans; + } + + public static int[] delete(int[] arr, int L, int R) { + int n = arr.length; + int[] ans = new int[n - (R - L + 1)]; + int index = 0; + for (int i = 0; i < L; i++) { + ans[index++] = arr[i]; + } + for (int i = R + 1; i < n; i++) { + ans[index++] = arr[i]; + } + return ans; + } + + public static int max(int[] arr) { + if (arr.length == 0) { + return 0; + } + int ans = 1; + int cur = 1; + for (int i = 1; i < arr.length; i++) { + if (arr[i] > arr[i - 1]) { + cur++; + } else { + cur = 1; + } + ans = Math.max(ans, cur); + } + return ans; + } + + // 正式方法 + // 时间复杂度O(N*logN) + public static int maxLen2(int[] arr) { + if (arr.length == 0) { + return 0; + } + int n = arr.length; + int[] sorted = new int[n]; + for (int i = 0; i < n; i++) { + sorted[i] = arr[i]; + } + Arrays.sort(sorted); + SegmentTree st = new SegmentTree(n); + st.update(rank(sorted, arr[0]), 1); + int[] dp = new int[n]; + dp[0] = 1; + int ans = 1; + // 一个数字也不删!长度! + int cur = 1; + for (int i = 1; i < n; i++) { + int rank = rank(sorted, arr[i]); + // (dp[i - 1] + 1) + int p1 = arr[i - 1] < arr[i] ? (dp[i - 1] + 1) : 1; +// // rank : 就是当前的数字 +// // 1~rank-1 : 第二个信息的max + int p2 = rank > 1 ? (st.max(rank - 1) + 1) : 1; + dp[i] = Math.max(p1, p2); + ans = Math.max(ans, dp[i]); + if (arr[i] > arr[i - 1]) { + cur++; + } else { + cur = 1; + } + // 我的当前值是rank + // 之前有没有还是rank的记录! + if (st.get(rank) < cur) { + st.update(rank, cur); + } + } + return ans; + } + + public static int rank(int[] sorted, int num) { + int l = 0; + int r = sorted.length - 1; + int m = 0; + int ans = -1; + while (l <= r) { + m = (l + r) / 2; + if (sorted[m] >= num) { + ans = m; + r = m - 1; + } else { + l = m + 1; + } + } + return ans + 1; + } + + public static class SegmentTree { + private int n; + private int[] max; + private int[] update; + + public SegmentTree(int maxSize) { + n = maxSize + 1; + max = new int[n << 2]; + update = new int[n << 2]; + Arrays.fill(update, -1); + } + + public int get(int index) { + return max(index, index, 1, n, 1); + } + + public void update(int index, int c) { + update(index, index, c, 1, n, 1); + } + + public int max(int right) { + return max(1, right, 1, n, 1); + } + + private void pushUp(int rt) { + max[rt] = Math.max(max[rt << 1], max[rt << 1 | 1]); + } + + private void pushDown(int rt, int ln, int rn) { + if (update[rt] != -1) { + update[rt << 1] = update[rt]; + max[rt << 1] = update[rt]; + update[rt << 1 | 1] = update[rt]; + max[rt << 1 | 1] = update[rt]; + update[rt] = -1; + } + } + + private void update(int L, int R, int C, int l, int r, int rt) { + if (L <= l && r <= R) { + max[rt] = C; + update[rt] = C; + return; + } + int mid = (l + r) >> 1; + pushDown(rt, mid - l + 1, r - mid); + if (L <= mid) { + update(L, R, C, l, mid, rt << 1); + } + if (R > mid) { + update(L, R, C, mid + 1, r, rt << 1 | 1); + } + pushUp(rt); + } + + private int max(int L, int R, int l, int r, int rt) { + if (L <= l && r <= R) { + return max[rt]; + } + int mid = (l + r) >> 1; + pushDown(rt, mid - l + 1, r - mid); + int ans = 0; + if (L <= mid) { + ans = Math.max(ans, max(L, R, l, mid, rt << 1)); + } + if (R > mid) { + ans = Math.max(ans, max(L, R, mid + 1, r, rt << 1 | 1)); + } + return ans; + } + + } + + // 为了验证 + public static int[] randomArray(int len, int v) { + int[] arr = new int[len]; + for (int i = 0; i < len; i++) { + arr[i] = (int) (Math.random() * v) - (int) (Math.random() * v); + } + return arr; + } + + // 为了验证 + public static void main(String[] args) { + int n = 100; + int v = 20; + int testTime = 5000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int m = (int) (Math.random() * n); + int[] arr = randomArray(m, v); + int ans1 = maxLen1(arr); + int ans2 = maxLen2(arr); + if (ans1 != ans2) { + for (int num : arr) { + System.out.print(num + " "); + } + System.out.println(); + System.out.println(ans1); + System.out.println(ans2); + break; + } + } + System.out.println("测试结束"); + } + +} diff --git a/算法周更班/class_2022_05_4_week/Code04_ABCSameNumber.java b/算法周更班/class_2022_05_4_week/Code04_ABCSameNumber.java new file mode 100644 index 0000000..65d61f4 --- /dev/null +++ b/算法周更班/class_2022_05_4_week/Code04_ABCSameNumber.java @@ -0,0 +1,169 @@ +package class_2022_05_4_week; + +// 来自京东 +// 4.2笔试 +// 给定一个长度为3N的数组,其中最多含有0、1、2三种值 +// 你可以把任何一个连续区间上的数组,全变成0、1、2中的一种 +// 目的是让0、1、2三种数字的个数都是N +// 返回最小的变化次数 +public class Code04_ABCSameNumber { + + // 暴力方法 + // 为了验证不会超过2次 + public static int minTimes1(int[] arr) { + int[] set = new int[arr.length]; + for (int i = 0; i < arr.length; i++) { + set[i] = arr[i]; + } + return process1(set, 0, arr); + } + + public static int process1(int[] set, int time, int[] origin) { + int[] cnt = new int[3]; + for (int num : set) { + cnt[num]++; + } + if (cnt[0] == cnt[1] && cnt[0] == cnt[2]) { + return time; + } else { + if (time == 2) { + return 3; + } + int ans = Integer.MAX_VALUE; + for (int L = 0; L < set.length; L++) { + for (int R = L; R < set.length; R++) { + set(set, L, R, 0); + ans = Math.min(ans, process1(set, time + 1, origin)); + set(set, L, R, 1); + ans = Math.min(ans, process1(set, time + 1, origin)); + set(set, L, R, 2); + ans = Math.min(ans, process1(set, time + 1, origin)); + rollback(set, L, R, origin); + } + } + return ans; + } + } + + public static void set(int[] set, int L, int R, int v) { + for (int i = L; i <= R; i++) { + set[i] = v; + } + } + + public static void rollback(int[] set, int L, int R, int[] origin) { + for (int i = L; i <= R; i++) { + set[i] = origin[i]; + } + } + + // 正式方法 + // 时间复杂度O(N) + public static int minTimes2(int[] arr) { + int[] cnt = new int[3]; + for (int num : arr) { + cnt[num]++; + } + if (cnt[0] == cnt[1] && cnt[0] == cnt[2]) { + return 0; + } + int n = arr.length; + int m = n / 3; + if ((cnt[0] < m && cnt[1] < m) || (cnt[0] < m && cnt[2] < m) || (cnt[1] < m && cnt[2] < m)) { + return 2; + } else { // 只有一种数的个数是小于m的 + return once(arr, cnt, m) ? 1 : 2; + } + } + + // 只有一种数是少于N/3 + public static boolean once(int[] arr, int[] cnt, int m) { + int lessV = cnt[0] < m ? 0 : (cnt[1] < m ? 1 : 2); + int lessT = lessV == 0 ? cnt[0] : (lessV == 1 ? cnt[1] : cnt[2]); + if (cnt[0] > m && modify(arr, 0, cnt[0], lessV, lessT)) { + return true; + } + if (cnt[1] > m && modify(arr, 1, cnt[1], lessV, lessT)) { + return true; + } + if (cnt[2] > m && modify(arr, 2, cnt[2], lessV, lessT)) { + return true; + } + return false; + } + + // 0 -> 10个 + // 1 -> 10个 + // 2 -> 10个 + // ========== + // 0 -> 7个 + // 2 -> 12个 1 -> 11个 + // 多的数 2 + // 少的数 0 + public static boolean modify(int[] arr, + int more, int moreT, + int less, int lessT) { + int[] cnt = new int[3]; + cnt[less] = lessT; + cnt[more] = moreT; + // 目标 + int aim = arr.length / 3; + int L = 0; + int R = 0; + while (R < arr.length || cnt[more] <= aim) { + // cnt[more] 窗口之外,多的数有几个? + if (cnt[more] > aim) { + // R++ 窗口右边界,右移 + cnt[arr[R++]]--; + } else if (cnt[more] < aim) { + cnt[arr[L++]]++; + } else { // 在窗口之外,多的数,够了! + // 少的数,和,另一种数other,能不能平均!都是10个! + if (cnt[less] + R - L < aim) { + cnt[arr[R++]]--; + } else if (cnt[less] + R - L > aim) { + cnt[arr[L++]]++; + } else { + return true; + } + } + } + return false; + } + + // 为了验证 + public static int[] randomArray(int len) { + int[] arr = new int[len]; + for (int i = 0; i < len; i++) { + arr[i] = (int) (Math.random() * 3); + } + return arr; + } + + // 为了验证 + public static void main(String[] args) { + // 数组长度一定是3的整数倍,且 <= 3*n + // 如下代码是验证操作次数一定不大于2次 + // 24个,8个 + int n = 8; + int testTime = 2000; + System.out.println("测试开始"); + for (int i = 0; i < testTime; i++) { + int m = ((int) (Math.random() * n) + 1) * 3; + int[] arr = randomArray(m); + int ans1 = minTimes1(arr); + int ans2 = minTimes2(arr); + if (ans1 != ans2) { + for (int num : arr) { + System.out.print(num + " "); + } + System.out.println(); + System.out.println(ans1); + System.out.println(ans2); + break; + } + } + System.out.println("测试结束"); + } + +} diff --git a/算法周更班/class_2022_06_1_week/Code01_WhereWillTheBallFall.java b/算法周更班/class_2022_06_1_week/Code01_WhereWillTheBallFall.java new file mode 100644 index 0000000..6727152 --- /dev/null +++ b/算法周更班/class_2022_06_1_week/Code01_WhereWillTheBallFall.java @@ -0,0 +1,46 @@ +package class_2022_06_1_week; + +// 最好打开链接看图 +// 用一个大小为 m x n 的二维网格 grid 表示一个箱子 +// 你有 n 颗球。箱子的顶部和底部都是开着的。 +// 箱子中的每个单元格都有一个对角线挡板,跨过单元格的两个角, +// 可以将球导向左侧或者右侧。 +// 将球导向右侧的挡板跨过左上角和右下角,在网格中用 1 表示。 +// 将球导向左侧的挡板跨过右上角和左下角,在网格中用 -1 表示。 +// 在箱子每一列的顶端各放一颗球。每颗球都可能卡在箱子里或从底部掉出来。 +// 如果球恰好卡在两块挡板之间的 "V" 形图案,或者被一块挡导向到箱子的任意一侧边上,就会卡住。 +// 返回一个大小为 n 的数组 answer , +// 其中 answer[i] 是球放在顶部的第 i 列后从底部掉出来的那一列对应的下标, +// 如果球卡在盒子里,则返回 -1 +// 本题测试链接 : https://leetcode.com/problems/where-will-the-ball-fall/ +public class Code01_WhereWillTheBallFall { + + public static int[] findBall(int[][] grid) { + int n = grid.length; + int m = grid[0].length; + int[] ans = new int[m]; + for (int col = 0; col < m; col++) { + // (0,0) (0,1) (0,2) + int i = 0; + int j = col; + while (i < n) { + // (i,j) 左上 -> 右下的格子 grid[i][j] == 1 + // (i+1, j+1) + // (i,j) 右上 -> 左下的格子 grid[i][j] == -1 + // (i+1, j-1) + int jnext = j + grid[i][j]; + if (jnext < 0 || jnext == m || grid[i][j] != grid[i][jnext]) { + ans[col] = -1; + break; + } + i++; + j = jnext; + } + if (i == n) { + ans[col] = j; + } + } + return ans; + } + +} diff --git a/算法周更班/class_2022_06_1_week/Code02_UniqueSubstringsInWraparoundString.java b/算法周更班/class_2022_06_1_week/Code02_UniqueSubstringsInWraparoundString.java new file mode 100644 index 0000000..7e0b6a9 --- /dev/null +++ b/算法周更班/class_2022_06_1_week/Code02_UniqueSubstringsInWraparoundString.java @@ -0,0 +1,34 @@ +package class_2022_06_1_week; + +// 把字符串 s 看作 "abcdefghijklmnopqrstuvwxyz" 的无限环绕字符串, +// 所以 s 看起来是这样的: +// ...zabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcd.... +// 现在给定另一个字符串 p 。返回 s 中 不同 的 p 的 非空子串 的数量 +// 测试链接 : https://leetcode.com/problems/unique-substrings-in-wraparound-string/ +public class Code02_UniqueSubstringsInWraparoundString { + + public int findSubstringInWraproundString(String s) { + char[] str = s.toCharArray(); + int n = str.length; + int ans = 0; + int len = 1; + // 256 0~255 + int[] max = new int[256]; + max[str[0]]++; + for (int i = 1; i < n; i++) { + char cur = str[i]; + char pre = str[i - 1]; + if ((pre == 'z' && cur == 'a') || pre + 1 == cur) { + len++; + } else { + len = 1; + } + max[cur] = Math.max(max[cur], len); + } + for (int i = 0; i < 256; i++) { + ans += max[i]; + } + return ans; + } + +} diff --git a/算法周更班/class_2022_06_1_week/Code03_NumberOfAtoms.java b/算法周更班/class_2022_06_1_week/Code03_NumberOfAtoms.java new file mode 100644 index 0000000..b41256f --- /dev/null +++ b/算法周更班/class_2022_06_1_week/Code03_NumberOfAtoms.java @@ -0,0 +1,104 @@ +package class_2022_06_1_week; + +import java.util.TreeMap; + +// 给你一个字符串化学式 formula ,返回 每种原子的数量 。 +// 原子总是以一个大写字母开始,接着跟随 0 个或任意个小写字母,表示原子的名字。 +// 如果数量大于 1,原子后会跟着数字表示原子的数量。如果数量等于 1 则不会跟数字。 +// 例如,"H2O" 和 "H2O2" 是可行的,但 "H1O2" 这个表达是不可行的。 +// 两个化学式连在一起可以构成新的化学式。 +// 例如 "H2O2He3Mg4" 也是化学式。 +// 由括号括起的化学式并佐以数字(可选择性添加)也是化学式。 +// 例如 "(H2O2)" 和 "(H2O2)3" 是化学式。 +// 返回所有原子的数量,格式为:第一个(按字典序)原子的名字,跟着它的数量(如果数量大于 1), +// 然后是第二个原子的名字(按字典序),跟着它的数量(如果数量大于 1),以此类推。 +// 示例 1: +// 输入:formula = "H2O" +// 输出:"H2O" +// 解释:原子的数量是 {'H': 2, 'O': 1}。 +// 示例 2: +// 输入:formula = "Mg(OH)2" +// 输出:"H2MgO2" +// 解释:原子的数量是 {'H': 2, 'Mg': 1, 'O': 2}。 +// 示例 3: +// 输入:formula = "K4(ON(SO3)2)2" +// 输出:"K4N2O14S4" +// 解释:原子的数量是 {'K': 4, 'N': 2, 'O': 14, 'S': 4}。 +// 测试链接 : https://leetcode.com/problems/number-of-atoms/ +public class Code03_NumberOfAtoms { + + public static String countOfAtoms(String str) { + char[] s = str.toCharArray(); + Info info = process(s, 0); + StringBuilder builder = new StringBuilder(); + for (String key : info.cntMap.keySet()) { + builder.append(key); + int cnt = info.cntMap.get(key); + if (cnt > 1) { + builder.append(cnt); + } + } + return builder.toString(); + } + + public static class Info { + public TreeMap cntMap; + public int end; + + public Info(TreeMap c, int e) { + cntMap = c; + end = e; + } + } + + public static Info process(char[] s, int i) { + TreeMap cntMap = new TreeMap<>(); + int cnt = 0; + StringBuilder builder = new StringBuilder(); + Info info = null; + while (i < s.length && s[i] != ')') { + if (s[i] >= 'A' && s[i] <= 'Z' || s[i] == '(') { + if (builder.length() != 0 || info != null) { + cnt = cnt == 0 ? 1 : cnt; + if (builder.length() != 0) { + String key = builder.toString(); + cntMap.put(key, cntMap.getOrDefault(key, 0) + cnt); + builder.delete(0, builder.length()); + } else { + for (String key : info.cntMap.keySet()) { + cntMap.put(key, cntMap.getOrDefault(key, 0) + info.cntMap.get(key) * cnt); + } + info = null; + } + cnt = 0; + } + if (s[i] == '(') { + info = process(s, i + 1); + i = info.end + 1; + } else { + builder.append(s[i++]); + } + } else if (s[i] >= 'a' && s[i] <= 'z') { + builder.append(s[i++]); + } else { + cnt = cnt * 10 + s[i++] - '0'; + } + } + if (builder.length() != 0 || info != null) { + cnt = cnt == 0 ? 1 : cnt; + if (builder.length() != 0) { + String key = builder.toString(); + cntMap.put(key, cntMap.getOrDefault(key, 0) + cnt); + builder.delete(0, builder.length()); + } else { + for (String key : info.cntMap.keySet()) { + cntMap.put(key, cntMap.getOrDefault(key, 0) + info.cntMap.get(key) * cnt); + } + info = null; + } + cnt = 0; + } + return new Info(cntMap, i); + } + +} diff --git a/算法周更班/class_2022_06_1_week/Code04_SubstringWithLargestVariance.java b/算法周更班/class_2022_06_1_week/Code04_SubstringWithLargestVariance.java new file mode 100644 index 0000000..86d8d59 --- /dev/null +++ b/算法周更班/class_2022_06_1_week/Code04_SubstringWithLargestVariance.java @@ -0,0 +1,98 @@ +package class_2022_06_1_week; + +// 字符串的 波动 定义为子字符串中出现次数 最多 的字符次数与出现次数 最少 的字符次数之差。 +// 给你一个字符串 s ,它只包含小写英文字母。请你返回 s 里所有 子字符串的 最大波动 值。 +// 子字符串 是一个字符串的一段连续字符序列。 +// 注意:必须同时有,最多字符和最少字符的字符串才是有效的 +// 测试链接 : https://leetcode.cn/problems/substring-with-largest-variance/ +public class Code04_SubstringWithLargestVariance { + + public static int largestVariance1(String s) { + if (s == null || s.length() == 0) { + return 0; + } + int n = s.length(); + // a b a c b b a + // 0 1 0 2 1 1 0 + int[] arr = new int[n]; + for (int i = 0; i < n; i++) { + arr[i] = s.charAt(i) - 'a'; + } + int ans = 0; + // 26 * 26 * n O(N) + for (int more = 0; more < 26; more++) { + for (int less = 0; less < 26; less++) { + if (more != less) { + int continuousA = 0; + boolean appearB = false; + int max = 0; + // 从左到右遍历, + for (int i = 0; i < n; i++) { + if (arr[i] != more && arr[i] != less) { + continue; + } + if (arr[i] == more) { // 当前字符是more + continuousA++; + if (appearB) { + max++; + } + } else { // 当前字符是B + max = Math.max(max, continuousA) - 1; + continuousA = 0; + appearB = true; + } + ans = Math.max(ans, max); + } + } + } + } + return ans; + } + + public static int largestVariance2(String s) { + if (s == null || s.length() == 0) { + return 0; + } + int n = s.length(); + // a b a c b b a + // 0 1 0 2 1 1 0 + int[] arr = new int[n]; + for (int i = 0; i < n; i++) { + arr[i] = s.charAt(i) - 'a'; + } + // dp[a][b] = more a less b max + // dp[b][a] = more b less a max + int[][] dp = new int[26][26]; + // continuous[a][b] more a less b 连续出现a的次数 + // continuous[b][a] more b less a 连续出现b的次数 + int[][] continuous = new int[26][26]; + // appear[a][b] more a less b b有没有出现过 + // appear[b][a] more b less a a有没有出现过 + boolean[][] appear = new boolean[26][26]; + int ans = 0; + // 26 * N + for (int i : arr) { + for (int j = 0; j < 26; j++) { + if (j != i) { + // i,j + // more i less j 三个变量 连续出现i,j有没有出现过,i-j max + // more j less i 三个变量 连续出现j,i有没有出现过,j-i max + ++continuous[i][j]; + if (appear[i][j]) { + ++dp[i][j]; + } + if (!appear[j][i]) { + appear[j][i] = true; + dp[j][i] = continuous[j][i] - 1; + } else { + dp[j][i] = Math.max(dp[j][i], continuous[j][i]) - 1; + } + continuous[j][i] = 0; + ans = Math.max(ans, Math.max(dp[j][i], dp[i][j])); + } + } + } + return ans; + } + +} diff --git a/算法周更班/class_2022_06_2_week/Code01_MostStonesRemovedWithSameRowOrColumn.java b/算法周更班/class_2022_06_2_week/Code01_MostStonesRemovedWithSameRowOrColumn.java new file mode 100644 index 0000000..fd951c1 --- /dev/null +++ b/算法周更班/class_2022_06_2_week/Code01_MostStonesRemovedWithSameRowOrColumn.java @@ -0,0 +1,87 @@ +package class_2022_06_2_week; + +import java.util.HashMap; + +// n块石头放置在二维平面中的一些整数坐标点上 +// 每个坐标点上最多只能有一块石头 +// 如果一块石头的 同行或者同列 上有其他石头存在,那么就可以移除这块石头。 +// 给你一个长度为 n 的数组 stones , +// 其中 stones[i] = [xi, yi] 表示第 i 块石头的位置, +// 返回 可以移除的石子 的最大数量。 +// 测试链接 : https://leetcode.com/problems/most-stones-removed-with-same-row-or-column/ +public class Code01_MostStonesRemovedWithSameRowOrColumn { + + public static int removeStones(int[][] stones) { + int n = stones.length; + HashMap rowPre = new HashMap(); + HashMap colPre = new HashMap(); + UnionFind uf = new UnionFind(n); + for (int i = 0; i < n; i++) { + int x = stones[i][0]; + int y = stones[i][1]; + if (!rowPre.containsKey(x)) { + rowPre.put(x, i); + } else { + uf.union(i, rowPre.get(x)); + } + if (!colPre.containsKey(y)) { + colPre.put(y, i); + } else { + uf.union(i, colPre.get(y)); + } + } + return n - uf.sets(); + } + + public static class UnionFind { + + public int[] father; + public int[] size; + public int[] help; + public int sets; + + public UnionFind(int n) { + father = new int[n]; + size = new int[n]; + help = new int[n]; + for (int i = 0; i < n; i++) { + father[i] = i; + size[i] = 1; + } + sets = n; + } + + private int find(int i) { + int hi = 0; + while (i != father[i]) { + help[hi++] = i; + i = father[i]; + } + while (hi != 0) { + father[help[--hi]] = i; + } + return i; + } + + public void union(int i, int j) { + int fi = find(i); + int fj = find(j); + if (fi != fj) { + if (size[fi] >= size[fj]) { + father[fj] = fi; + size[fi] += size[fj]; + } else { + father[fi] = fj; + size[fj] += size[fi]; + } + sets--; + } + } + + public int sets() { + return sets; + } + + } + +} diff --git a/算法周更班/class_2022_06_2_week/Code02_Solution.HEIC b/算法周更班/class_2022_06_2_week/Code02_Solution.HEIC new file mode 100644 index 0000000000000000000000000000000000000000..e9fb575ce50583bd076026cf44db2dbc4ab32d4e GIT binary patch literal 2358360 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�+ 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ⅈ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

Q9u~; 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�qokvkDIY z?k}Y7GKIj27Q&hK4a5mIS9*39L>;%#HeG%)ZY)&KF{$(D{3oMUo?M*(8fo^I1GV?V z>-{V14OyV66(np^Z9BBN1Y`;DP*r#Yo!yfD6M>budwp$%v0ZXU8% z%?P7byL=z(As2b2zlYPckE3E)Mbr=rsgDF??F1z}RY?-~>A^@=Lna-b#P*cDdoM-} z+tfRYjI8OvdtrVvs@G*hg~6bOHfIpxTLKfbTb=3cYY9d;0nbTq*;eY}=@}9m>StV_ z6sQ<~p}P@Fe4YdyQhUmGqP2-8he2{_G=c-Zxj2_pCGme}1rMLRRm1f+bB5>gY7M6UcUy+hCs+)|E@ovQf{W-K^YBMHfo&qeXs6A|0X8P9o|&&l z_95rUMNiVZij(+Yz(+{sw;7^+)_2yi`Y5iSVtC$0-%7NS?du>irBH~H1*RDo{LuNT z!7At27pv_x$9c8d#V62#PdNi16SlJr{Al8I_7~CQDT0c_IsE>%k>NX^pq~c4RNd6@V1t_&BW z_;VOw$0De!b1S36UF-#`X%JtGYPg^xE=lO^oZsGkwP#xZ@?g*jy2z!3c%;LeYg4Jf?~JZEPIv$$%=YaA2_N#0 zmgi6+_}jl3!6>0{v?}ziyoB~2&lc#3MxS(tP6iNd?FZHlaKqzK{Vq%V|Kf;gdMt16 zcscG}5OWcwyDfBt{NDns>pe*FCReFZ^@}Q7ZY-lV8&Z`K$tgX+cl2ZShEbvQ|8PP> zhYG>0#O{)t7A-YtZy-_>{1Q=w1enO*d?;2!f=sKg?G;HpX{g_KE>8bIIjJlL&^_-;z7k`UZ)YHmn#}TN`Z18Pi_Swh~gvB`1z| zpj8|UtajzM&(RXT13(zPOU8(~Ndg`9KTr~3~tJL`(HY+&khtEa~516dYVThty=ghP*Ga~Dr6hHrqPiwy%wB?+$qCYs6U#Cbl` zGMV>ghE`-PM{0}$WDKT2)@8eJN<|u1?i*KfwYgy#@HnV-aN!jo4@4i&SGCw1AqtQh zpW>RJ^QT{}P;M9;Y5{l+-!ox06G_n3i-d&%JnpA@BSkFr$fo8hmQmQM=sLRuR zA{x70$a6-7nni=;qMevUxUIj0rH0@1MAm>JGPEa#*GUy`&{)BoD<&uJ$^n<@=>Xq2 z10P%YCMlagU^0b6FbVSV*iF<0x(Zje?TCO6S;pVIoZlKmD4>Oo8&@v)Z?Bgy(V%7@ z;EV{peJIy@Z-E5fv`ezBBF!dIhzzGL0oAFT|K|9}5aa(_B19iljeQQw!SexBZr;mx zynFza1cF-x*DIq}55l|?hz(HVWd`%}TH}J+aLTbCBJv!$d>$Fxx}T@IpU%n<*ddh; zNFJ##F)+RB#j&_YAz$!FstE%i@=r#CxAae1XUX5sX@Bek-sy2+`pJe!SaeC*FSL20 z{1N2m?zGv?=EX$^=@09$XRTBS1RE6_w+Y??@9A$_NBZAA+u%FyJ>h#Rrx>ctNu%ui zm@&P0g>-#&$rk&PR#IROar~X-3lZ5qR>03ywhf6f$pq@@wO&=Tq}ju^nNU5^QlhRe zJ-0Kp92C$2`ZIKtDj>A7Kn-4)vSqC_nu0eKXAv#g8kALDTXfXm14uL6%e*^wC04iJ zdYE6ofnSQ#jD^DNYS$k?@dC%Qwj@?Eux1|82{}8Jc(frPASGfoGF(v@&2a8mE%)Pt zZ9mh_#4uQU;GTsWj6d=vXh+jmHIZW~jUW7X1B4VPWsfD=O>Yx^8j9Qy#}bOzc@VJD z5{sG8OBYIl)?y7&M8terb^od#8M8hofqQg)oVBOo{2g;)-pMw*IM_ajTS?&GSQ1^i z%Z&WUef-dHy#V)qx^T~QRBTAPzekp+_Eht%!vP5=^vR6c^(vH48lSKI)iCa)UDzBF z9Be1414BIix=PnDx5Mr#Xj)BwQPP7F74=O=f(Mim zbf%~zLCp=Ho%{#{m{1=V9xQy;TCVXo`yZqYYW5GVi#wKfaNvuo!)f`lv}TS(5JK>e z{D)9hR_$5JX;6j>-A_**cS3?RBELUBZ^=(PQW*p|`alBXOG9&=6m_}Az=79chY_1? z2x2F#dXnHbLRYW2e$vOZ-YXxHD7A7^Y1d6vD;O09i*?4U6I(Q3qzwB8pJ5M!rK}{j&^G^+Nubw9SmKJ1-CUNs zU0W!wMTk?1a_}L}947`S6)6Uksa&sq0Es=`O;H~#5tK1h)#E9T3+urT%D(=ZT;tt1 zx>C-Gd_OHfp*&uv^zAz2oa7g<=miVJ)A?OHy>9`@3CrWagc}-!Zxr=R z`fF61*5(a8fCPcgoAxg;{qOjnKBt9W!}>IP?8lVVqi3JUrc&pmHJNjl>9Jjmto}Lk zXSRb^n_{Fp@M^eAO7yrLX})a1KDQrzM>S^oxc_-U^9H`4phb6ZuU+JbDPWAl+d*=a zIcW85_T#9NE<+%yGoS~94`tm8#qOmZvnUKmhK;LAJV3ee@w#_dmS*qnN#qMxJn@fZOdhj&F)|M_uJHZp-WQDozfec$4@Fi5rN@l3e5tVD(q@Hu zk%^8?F;#85DwLiMsx9Nyg|}1pzKw!YPE6qU$0#+({{lj>0}`Q;t_H-Hhz`9?tAMhh zjL4>5vvixbl50U^w~v!FFxzSLlt0uin5bGAtoce-##;Efb;xziYUoEqydi9 zD-~2W0DzZ$8Nls>KZMF8+~(Tdh767z(tb z1tKy+=04Jml;pmtl!2Xkz};|(mL}h*JO%_86JY{sVzg{~W#XEl1QZu5ErMm#>doOY%zV&}?~ILJc*6j9drV%i%w;P3 z0t1!Qs1K0&SpK&Qgn-c)=)8dX1-sLlu2T|D4F-0?*z$O%-kb$k#1}+$6d*$*;(1ap z>WT2)XKYykLdYpu4d%7h&Qkc9BCgZG7h=m?2Q8hC9m6}@UPtiRs&lL`&1>oeRs;&i zMikl~9uL*(ww}`eB&M`u+vS6TtDzRVLvA_R!qkM>&5!zhBmeAX<+#=ZVdQPNq(9LF zb)||4tyb5R$Q6fWQZ&5JP(%la!D+J3s0!LYY9HndxGZPt#qz}o_N`{obKvg|YSNxL z?caW$+^yokX6t>0u0F_k$S+ouSnt5HtGBfOVPiey&HqhC6IV^%n}MvLW4F|8%vpNllw2l zKLguD#fXkOejVzB>ex@A9E@izL{@kk*0YN)mT57teg=cQB+p7Vd1wUk?iou*ztI@w zY-6h_t#{RmPxC)Z5v|Z@IFi$9r?5nS^X#mGD}HJ10`VXb8Y@rVgLxd4Hax(`*z^Wj zAtP(VsjoxdW7zn&)8>Kq>hE1qD)q`~Fz^`{#}t2TBeF9L6P2(8NEEl%6Jw}b%&uEt ze`y3*zx{O-rcQNqPhDa*1E#lE&%v%5$q_+p;xPf7eD zm&&KGr#VY{X6ECO)z3w&q0MxZun19(k>WRd4sQc1sWq0yXfwdLfXZ7{h!rLuQ1sp# z1eH6IpTEnYN)0XHcx=JKdtme7EvJ67Z>MPTSRF1Bg%yf*cea)Dl~WFBl$-MAP1n#Y z6L58xt3(002DhpfTn+7SnFSZ7tqHcYLk5e8k1t6^0SOwt+#1d+co-9opn{YH0RO|{L9_ItGb3uaYjFK0&z_Z1{uRl z7fBO8OlriZHRK2@97*f&bhD7Vmi`l5UJx{#-sdpzraFy)2*rek=|;@KeS?mG3XVbe z#T#UUeWibJNX?Bbm<=ka4n%k7MO3JRgj6Zo%tQ?)BZ#&`zpVamfMG|8zh-hZlI5QT zFkeE0+w9*r!9X>zp14rj3F#)h@`wk3!BB6m1voSDwP1e|@8*^RDxHqQ=l(LN*?U*5 zZXB}uOSqSE2(_&=e|x^z^}Zns6RHpg=Owq9Jc>A|v`dqsdy23F323pe$jxs1gQpkK zlfZFHiFeFY=g7m&mEmev#djSk_j8r3*{j}K;xuajE*eRhsb%1LUr`tqVeSl4sFx|d zq59^V&4JA#z;@dr;Fqk&*y5NWv_@Nicv!Q;z%d&p+=n&gg(I_}0FUG6W>|1EzAeln zy!Nyk$$@{*knwdcerD9?S>anBnF!^V_=botg&9~6LM;9zFHXD+gNsf%8X4eDFAA|M z{p663%C+JyjydR7PqpQ;8LUW08-w_`H+ed;S3f>zAliA?R7a5vmfjbx9`szBxog`3 zFVa+v%RrUKAb$L2*T4oUPSy0$I($I&+G2#+gN1gC({&Q7E7#)&etrZuqKL^WH#Ln) zsUpM7*i;>`&*xH?R)#p5CufzcNO}D=$BC?XL%1pmd3{t61yMxWg6rSP8etW}Jn)ZR`A3?anMThk^aL&NhR zw>c$pXUbi0I}`4p8*E%e0aR!l$`yn}cMS5lVT4<3PgpLYQ*=p9AwA{s()iCWf@ z=LHseVy#~&tTMybDuHVtJb>_*EVOUXDtJgt&wW5<3F9 zE&;6F=-m^v8Nc6Y-LbTF#wdY7A??bR$=3+BZU!xleCh- zrgj3yHP0tofSX|JVE&hz+uC>=qUJx6_pof;O?;YF4|gUe`I{9IT+30+I$E0fsh zWf8pp?G%*`I?>ASJ072~!JRjOd;>zIH`h%OV4+N+9-8Ygt$z_2p48qKBQq1YwZe|n z5|0Q(XhA=SGQqH}h%wff#_cCbM)FBgaGR~_LVQ@=viX#cwO&p`E=E4+x97(4IKfeN z5&Cc!)donLiMz+oP|mpeQ<2UKq~o7c-4J-Cm-VUj5-4WoBZ0?zL_1&#|0x-ZJ3*d` z5;p8Sbb@f(dn!_>nJL7DT^w0ebS{G@V+=fu=;3n=T?bb>#*QMCOGfX(U$Qv_<5Jv? zTM7yX@-i*tp)O?2wtCoB8qvQE!t5qu9qH!GshQ$j$WZa_4Zq&2N^y3voQhal;R(~C zTZ;HUJ{tC}_-WUrTm@w{j60!_7tis}b5$K8*k#JJ;+dq(19r;5BWDoIy2nGEhBl17I)>i5TquosjIX6?11%7 zuWKJ|A>?%)5C~>uJzo!pH$GC1C13PHq;ItF78ghS2?u{-=Zh&9^rV9Rg_UDD z$7wVc*to5cX7ZVCb08hnM~sAiH=krU!)aKz0er3P``Nzo69M`IW8T=jj>U^)UAalA&{e2PkUKMfqKxDk0uH)SKfbXOpep;d zy3c!nbO4>`5uFuQcBrB}G~S(~`5p{h_eC(@6>Z-&k{RD~(v?q}8pmFwvcIL+>vMf zG>Z|lbeuR9jY>9C9gaUk+%3H1!}4HX^Nm%?QbTBq`{-6oh8Z&TB%!`4uVxS{VjZKj z{eKz)mYfixlR$toR8G{S9ISxA;40f^t6BRKG;P=Blun0}%p4tEL+O@7qmaSubS!o8 zFK5paN72{i%#l%IKfUNr=eB?t#_73xHFW{HYEkf3Lk$W&m+JbJtg9M9w zf4qHH|I+vrg&bSaLqrq`!87q6c7t*XA=XWl`l)YQhGA7zR+vJYc}y#N0`8rVeE?1q z0xZk@zIssrSkD(DKAluR9QRnZEyK+{*y}L;xG2_s@r4O60ZsYswBUGMb^JlD#CUtm zKC^c!M)IqKjCm{r)XAGUK2au|K~p5|$9T$e(3VE7uKGEf!rEuru4jPqyhHwr;&48} zWH=F4lOAWkrt77;WYusRWjH%XLCltZLB{|22vcHgavq)Rj(>h6cD}9yROhYn%$w=I zUH22>=Y&z<&4ex$3uh3Gx(WBwX`0;~8>)yIAM7~9pXh* zx$!3AqwD30vf{#d@<|zJ7{NZW%nRshZcO6<^9qg>gAhF@!+E78aZzW_z)T_g0@h&D z$AC5`7mI&dqT)0G^lPQ=%%UK`u-)$HVEQ>bAe-tn%inhJWNqyZvysJr)DESU{P%uH zu~!-8H_T71*u=T+x-O zCyCc`QbVAJ6P~adIbYHV^6k~mQe_dm^U1pUyV%=4EBO|Olnh-k+$xf^)gKE}^c+Rs z`H_W(U-jx8&4lU>xbdfou#bj}U&{80C{Tp;ydi&5I=78PSAmxF(v}-u>LB;&w<&SJ z!c(_&iZ0_>%agD^9^`2S%z{X$gkIhA-esZbxh8gG-~AO(h?Vz%*zYHzNuG!4s-k*a z-h~?J4t=d5bq41P_j59ZT1WSDRCV(kXb%S@xzp;&4Q} z)IT)^pyP%x1z%}3I?a!oB@YdLhBIGUd8~sGc3|U@V88~yL=Nm%mcMvzHl1#!yb;*IJEi?>#5aHua3IjNf_4s> zXj>3_dkX0Ad0av80`fpwO?C`Uf@Ii^U)f45KKiciN{h7XP#-aqu*OaSRtZe`l+l*e zBnt{M`ZR6MO6&%n@?(X}x-8lW`{==r;Yr|KNeYY*b&`PR zU85uVxg>2^Vi~R8zEsyrwVb9C1^wSgOVR&+c0>JZqI0|^g(}+hi!6I7^B^7)b>TE^ zf)4LAT!pfIu-EBx=*bBp5GQxGv3q|^9}0}?-+wz`o4f%@$J&h0CcL9O=?o)SP;P7} zB>hu$vW~MHoQ(2s8DQE&#~JK+t0{qE2voebk?}6ntHh^vf7?04j?SNot`U64t2$S$ z&`arJvcnZO8_3h8pYmPLfpUQ(4AcoA=g)^i^ES2PTM>%@HUCcPq_~9Ss`g`xYu>13 z0KAQ?wtL18$}5I?H{%d3v|AtoQ8i~h(jvbD@s6jOp>um*vB36blDe}$C&hwuKu^Q) zX>y;!Oqpx#+~5HNp{nqJa%p~m%FpdK&^-NM+SEWP%z+Z-xO&I8S14>@I!K7w1F zcBvSZH(hh-ou|Zt<-X$_fY!s|llcW%ZSRt$O+)6A85_$+mp7}0zADjNEJ<%M|CX7^ zwaeOyJIc2QE^-f_w9HKInrePTgm$Szp9rxuibj!t&SR>;k73d#bs`Zd@=VBzOy2gK zKekCSy|-h$?*sE=Sy9`-NL_LvEs^#}KB5_+ymJGJYgQvyd!P;=AM!)-VCqQq3&U5N z1V_Q>OBjC;%Jy>?tj^rW+_pQ0qV&hE#@b}u-l)AaAZG^nzzuo0F(+unV3;O##;K>2 z*tqZJiMmdS6BfTNSjZwdv)JI&IK<8-Tg2j;3Yqdan3&ubNVwPsQThjN#u`6LOF8Ip zfLlhz?RAmOO37wEA8BEv_Y+-Ka@^i4ut_!7*1*;2PKeKj$0lII5=$GqjHf){kr|pQ z9^(w^WBJUzEjJA#TO}4QBnc5WfG@s#M|rMR=11D0LscJupJ`}?XeYuO;XVDR30f2v zuU`36+rjf-=14dV~}6$+;b-L=0Y9Awzf?uytxZ3w5H=vtOMHe~X?R<2`YPct3| zFa^qsa}qN$7Hiavu$nqu4WBgC!@<`)!0Bmo%;&O3H~JEWAOy+d?ufWU9j=lzi)pF|3WywXsO_ zNVCAo<~31?kUYvrgc2+FggSx23lz0O&B$$e`2gU)!0yJbGv`wwZ394`Ob3P~Vp)^q zW({THpE(tyap;NqliPAw`$l}-pr2)bZ+iOQSY`hH+?=AUhT%RvuGH&8ERYfo>7EqR z2{M|IRc51OhsFuc)sX+@C1Yf3!U+A0q&wHRAEo9NJwoXT=BS&-4<)kG2yV zsnz~LNe?*+pc39Kkm@x`X3R=HhWX!W;lPPJeVnG{OZ#7Zy1`}&|HeRcH6ze7gi!o& z0xacovZ%blwMtJ6DdVv4a4GgepmdS=ai^R5QfVUmuzNemWXOdb7M^yD5yYB`!%L_9P0!&uB=?8U`ty^soX=qYGr^S_IeMcg7> z21#5KAEbz#_}1p*EN$eqb(GVTaFeV)klh?}cTA1GG{YG4LqV(iPaLo!W~xa{uo%k+ zQgi!1?E;%SNXz~Lmezy3TpS(|kP5?GMF9XJpN!)@Vp3aep0n<@M@^hm3lQp(&+^M0 zpnuDDzZ9F25xuiQ73(?*W>cYDAyXYsN>_u*V0&8R9Cv$aUwBy7&C#3{y{7;+PkuBz;YAPf?Nn{%x5!(_uKyY$@pfERkk|h+QU4Q(md4(m_1=#v4-0-Xj&}{@I3u!V$j&DO_vQlUMiKNJB7}^c4)iR^O-jT z_UcTmg2$n&K2g-8drDLPi1rPCR>wBVF(S@~)NJYuc{1}1fF9zdwll-iB}~H;r8Aqv z#4R-FD(>o~t}aAp?i3?ijt=19W{E}=<5YTl`e8fN4=sysFMZZ;)reXHSspZ0BdS1f zboxGsw42oaH~3t_3a^Gi)Qt~yk5esqk0#Lb`hDiQx*01($%L$JmvjgCVs2&kNoUQ zPunR^)wq&g%`!p9RauA>1%8gUYYxQs-o9$#fN6`VNWF^w4yS__u-90Kamqy0D%i&- z=O%UOV2E+`G32dR0=Hm%Pp*i@&Lcjm!+NOYj2 zrULt$QMt|%(rb-kcR&sC*M*$6w<_Vhh_YFA|KaRH?UJZoJNImbm--lIFjD$Xhl~r|zs38Or<4Rbc8d~RkfCYrPAFtCtzh`r(<;;_Uju$IbnM{g%X^2DjbhZtEe3L--M*rYgIjVw| zK+0)~cpyk}mYGCx_0|XL0LWAC)pFh&;Ni0j`ug;8JZR(I(KSkMDwv52nj@^1Y-1Qj zqrny+tD@DBeg!MYd|hvjuZ@sE^#X2Xeq*#W@rEp+`)$XEIN$3AguP5FGe)aJrC}(F zda1wRX>Cm>^mVHxASfFHDCWsC&zUJ-`rkgG2L{uB;ZABhEp| z?Q+4mhGClFY#6Ny6ewnt_48{z2)Qzvc+d*Y2}Cm?!7U*oAa(PAoCJ!I+KzS29k>an z6#scze^bWXG80LXCdy@0>I*b-^0af=j{I|4*DKB?tXcMp-(Hs&Kg;_(f*t52RL#Jb zP{=&FTueG7;7UbC#tgs9b;{(nC`S*Z-@Lix6r^9VRR|1&gakQewc(3ZljTf3f^fBY130vq0bmv-3H${YDX%7lZc|QmXn!=Pz+&E%u~-#% zvxnElnt24f7MbF-b5UPC!02zhv!dliEMCg?_yg7Kc_UNL&1249_Uwc8fA27hAP;f1 za*+-0W9iZaG9hEZGQdw|8A)S=bs1ub10y&hNU@YQJ6;a=#S_IDes71qF4aWm76|n2 zyzp}8hXqyI(F^5(PHBY*QPx`4p4rgZ!lukzb~II5^u zilLUS z>6LII1V@+}RRXL7VBj)C)kx3GwtL5luGES2A6kgkbNs6%*?#t8EOCmdrn8k5)46C` z-&rDhi;i|2l)Bo8J@>%INk)IT8PZvO1EO?LUvr2mje(eZV9;r%nVr z{;pgD@ys_Hv*;XZi~uyNOu22| zCXcAKOPdic$16-$H5b$H_h>{L`V}@?a?*ZgqL3E4g8Rk6mnyCR+5@p^M>1WeoIu## z0%K?zjc-B~T0o!67Q-3Ff?-{3ze3=aYWd@m)OQGc!V@7tt72Ur;C0Z6KI8EZK6vP3 zBq{TG7f?cw5*#`@d}w=9n_ngfO2r{#$QVOt8GoJ=o4v@Fks&xTBad-iRhKfdkil3` z_wK9NRh}hqOJ5CZf5inDiWqO__PNZAJZNmrv0L>ei5fgJqn0c80%^{7X>eSp+yNi` z)ysVGD*Nbr1DywuA`lLVZF_Ts3)Zi~t{PKciQfBJ;z- zCLn)FTHJi(Dlr4P$Y;y-Lz-U6a!;o#bp6YXF z8WLn^OwxaYxGLJo?H1GAkE<6fU$_mMn`2MPfBcT@9K304TXlF^%CEOyDjlzDc zO2|)Kvz)orE--F|e>`?Kxz?S(q{C+)<9)z#I{LekwDn7hD0eMsx;!Q|#>@8EzCdDa zY)YxArOhgO3sBI(@E)8;~BP$+2qq?#TTQ-^x$s$8tfQQ9YzBWlxmX!j%{zT*6Ck(U_v zde1u;eI|8b0l%__(rAyUJ0WqJ@gAzaX{y&_-CYF{C|Zto?CeahzLuG<;*pH*nVosf zCwUIRAZ%4^?)+|3+O{<77x+HNA49NFtb0D+12Bp|Cq_oMII@s7)$i+nQ{*M zsk_q6-!>fTQ%>B(51jt-{hx@!y!uUISy=Fj4)X~icr zLW16ES1zmESHienWmMfGBksS2_l^^{M%6)M75l8T5lJlz^5*!hja(0BsRsWjD^*iU z!oyMc9Z0M^mNl9|n~oZCp^JK&6uO|Oa6Fj4ycSgGwQfftw!~r4WrT~PTqsV@TipREN~JL_qX>sy+UiP zn#{ld=d(BXEYytN5umA1t}}=ux!|cG0YQ(NJJqmf?I+3}#l6|Z2zEj*;nD}3ve|vs zqV(05o{3Mda3i0E*=?^u-G4(76-+D2mmiI4e}&aF069R$zm4!%#eAV(Jj9qvcWIzi z#OLbJlDLoI(crhXu;mkIE`nT;G5G*YuIe>Bj(WJAj2{tIWa2V>KU-|vjqHHX8g{Wl zb{BMxge?tLFg5)=oiNEf&b=_u!*#CVkb^;yWjv%BG8SS%T=DvKs_urED`8jE%(hS) z+WxfiMY);oT81O(n<+b~4sC}IZr2TM?nkh2&zKpU-TqY+$ayKVlsv|ScL%mG>t|ZD z-v$`xM17&kfJoUcerp3Zf*LD_=0{hKlcYi`KEwdWBx|(*;sVb}mX(Z#CrB)rEIGH& z@V&Fom|e@;v$#2^Nuu$5Etx0DblqaNuhc+ugbMJ+W( z8w1BW?=q3Ky%Hy2RPq1ybI`2@W{{RebuLB|OP(h%WQE{-)=c3YOT?F6bo@Q&w-1te z9twCa8y_jq6hl1OY&6PrNcQo@K;brb!Z=BBqNczY=v$tmX_&FK5UvLt$G-sy*aRwR z{g{ZT@(rwPhE}Mek>ARY73;k}@8I&r5-D@!?)JZSbKlXu{}%ehhjQhC4>N<1TYfO| z%d8Z9c!aljG6W1Mr9i(MeI&&+ri8(I(YE8mB}ikxMH#3)0Ohxr4EJ*Z3-)vPNvY_8 zL!W_Oa1s#k&5p6PA2R(Kq*qS*bCZ>Mkxd?lJxhl{L1wY5CT!buc$4S_UId2oKVVkE zvX|`@L1yfE!dMjr+(oIr4A;EwDY!FoG}P>}4?GgmX)L?`#In9UTG-62Qh<*)c}2Xk z7?lIE_?dhpco zKzFSWiXU_I3+xMz>61+F{m^pq-mC%R?fpuQWOUFAu$2}rbfr_qLv=}VW~7;%+On%% zngen^x}J6G^pKQ_aKAG^!AC}h z-J)Nuqa3=eFZ2~tNW1&&!M`syngwOBM3-k=_B)fbdOxG#u@;&tHR<$LtAn#ja&@Td z-uR?cnCPx2Q2wp^*qLTXp^??!qF7MsaVxomSumVvZ|zu20Q1hl7$%Qw6Dxu`!3#V| zlh#rQB8;2uQB2BV^8dvp{ZIF{?QPhHoFEPx3;8DOZ8gmKVV!LHsQN^!AjIBLj_(x* zCi=1n8jYVkXksqQ;&PTqNO=4e_-7>M6b-qo^P+M&o&YQ<2eqQ^4{*5by#5mBZsKb1 zo~bbO6kxw~DnT=*a^)Nar)_(ZHDBplolnIQ&#PJ(_pb)v#-$`arU5~Pl^obMEO6aervo&b&t)i7;7REhiWzjWPQR6YeXu0GU6 zlZfP_k~mlG0nz;a=mO4HdbO5Ba9$B<%7|b*8C6)KyZG}1BAw93Fk%lG3*87;3z6%? z$VkV2n0Al3?~;501HaJveQ@=3!k(efX0P+d%*|*{-f_s|;6pu)Hb25e+owh5(qk~$BDM0~vF71U9+_5OtB0%L z2X7sDVL_OJ~$ur zvvIh#e%Q+UkhOJV`&TWW&bHf_UwX|W+lPFu-l0k#jJNmlJ}`J~SHB*^#-DE+l74x7 z!jVE(%c2kP-9MCW3j;F<#Rc*L!vTm(ZI#^&cZ%qhqRqntWbW+RCQ0y;9^;|K=d zajky){sWPdGoEq%0JCb(3?%id7D2nWw(`PR2>b?aA)_6E)YgOSfFrDRjKwlq%^W{^ zhs$fVE7prEg!}UQY62c;hp!~hq_9dutGpiM42kx$zowYhvJqeVlCTLxd~{FSX@btk zOL4nn8p1A62i1$}MQi$N#OLml{Z8+ZLx{}U=v1C@Ug>jEqm2>k`!yTNdLnXboYJoJ zeT0!rA%jpM_Rzf|>oWfIl+afzO?Eo7zV8-@fk6%;(44;5c-HE}`0*cwolJIUDlDdapjas7MmKK29mivxN7o7&u-0#9Sa$iB_sS@Ba=awss3cA|zm zmdFRO!!-xVH&>!aY?PB?uMUN8V4!S>M#Jnua-<{zAocsfxq&DL9_l)m0wzXtS;YJREz2hmPST&0FLIqRIT_9+qRg>Qh)JrF6bA5e zw2!qHQqy(H+N$u*+^Ehd6MFVP1n{8q!hq^2hC7|;yl7L`iMJLDvPB{g1Sz&&`SZ^qaEI`Am4u6ZQpl>sjhh1!}YS$cB;lP!~NzJz6jq!RIzY^FR>d*PA)IZYq&44`O!i`AvGZTC1galwX)bs(Fr>kXS zAx{H;aV?IWkLMbji-mTM#y+Y{;ej1bZ&Qpz{Kcqw&qFTZGG8Dh=mcws88SmC>XblAX@Mw2RXRtWdH`i4%d%o(iK&3!xaHfr=IKAuq}$Xtukrp9NdJ%Sr=yb0 zpRN&NJe?A_bCtL4%cR5I4$tM#O1ikS)Y^SP#{f~T&-}+{0vNqh*s<1hSFzfd-TnIE zt*6}{6|lyeFbr^&l)^`iCUefKn0!2KnDuNmNbnjZVj2J!OON-SP@B0xb=5Je;tykp zih30PcpZ)ABcD&3VDck%h`?d6J2KaBwxizN%f`+T(fOjbG{Kr(3^7!mE-!pvS~Ojp zT_XWHTCC`+7OO3Ci0IgoYt?IJ%0oi?-2Yi7tvr(o_7nu?v(n0*76@klGZo`D{ycV5 zs`G|##_DCPt)R%=m3`PA=3FWsPQVT6I_4$4250|^_aOuJn#nr2DHv5*2+4)Rg*7B; zTRXXpB=`$)gs1Hwg!cV2GyEohVFtkyM6W8NEAkG7Ox(?dnB#V(b20W<)qBC1G+-Zi zWdCU7J567e(uiMSjQZR5~mS@3Ll!$O^u|U4Z&K*p|j*(R1-cUzbo%%9*$P0AH_pJOl!9c~2-F=kd?@)I%_c)?Ts5OEUs=?+56g{9grNu4r7mikxyaw8$J=V z*ZS4NKApRYarg@P(_s|(i|1?r&|XUS(ae|#!Xs8Zs}*@x)qoH*oetMnc}in-T@1GF z1P%$obx(WkL zi#e$B>&Wd!Kt4D&AtWyI*?$*fe<_JmfI1{_nCK<^wF(>CnZ;60DOq%e*EfKRyxoBa zW-pQzPbJK=in6Gr)!Rzw{AFr!Zjbmw&{dy^H>MZbd9l9xOlX-22cgZK2DC4ibMxiK zOLQjL&$pRBo&}X9#X>~%wNA`uU%2dHb%RJ|j4=jv8lzmuY&H7p6-42cYh zw+-+X2KR4FfH{oBPF|jd*g>R3fWH6N1YA-5-W%q)77$#jqh@umDwJY03aW<<$bu1~PlLU+m_?l57 z;o<4NGVz#G>ushNy$PTDIDWu@w_(t)3S|WeHVfil4HUP!Xor8vjrGTf1fspO=>czh z{*qi=hd#)&Ji~js>H-+MNkDP*|Fzs!epIH@HWU|yq&JEZdyVWEL-Q~D^zYku_#M)q zJ2k^<@>B^|o;|MuV*C5S&&j>p0H*4Jc}swDIJw1(cNgDwk_@?sAK5^ zcb%)c0z1^IFQu4M8D!NrF(2fv&?X4Qf8@%AC6kn0o`dS>N$2C{&r2w+bud|~{b?m# zXIu^+>?oC5A)Zlo*^1f-S3YJBp|>y_0ztc05w20(v+E}Fx|$TNy}6FH&rm%ry}ZOay*8f*-idmMvCP54%i2rscTss#{tftJy#gHVbliZnw?(!(l51U zF+YCFV$VnrkT1Aude?)Fqd6x|&*Jr0!c`5XY5pL30C2jxSWN8a0EZZhQg`$TZa<%1 z#tI?d%lU2DN%V=Ol1k1i%a>+L9dw}M6!sFm!YV@u05Q}CLvL+$S6&06vf0aU3Nf?T z5Z$i^fZ;~wV>v`scP4qUgF5Z+5fQB(ST$=lw`I8VnHMl;P)-yy+TgbB2zztkNdR|3 zm3p*fm&d)K`KZY)jMrtaqi}MmqpkHw&Y;=)HtvYB-3C^&|M{O@7P3s|JtBQ(wl<>> z>q+QYqTMfQa{lq7Bjm!P0e1J5^rc<{oHC@Db{iye6BjD&f|f0_tprQbMu${m@|( zbUrjkhtHwdZ^0A8?HWb9_e=AEcmeAqrI9B8ESB2?wK7Par=K#f&2J_5f?j8o17KKv z)7fvWVGMA`+_=i{znswmBOvv>+3|Hv!3f1wte$HjBJG{@%+K`K{J_=@cK}DD2&Jx} zE$>jnR!8muQGRrih+0|kK8L4XUG6w&bkqFpr3<>ZD~O2r!G0Qx7-$U`(234n7O(0p zcz+T-#4L#6+X!)B+yBYU;5nwL4p4fqPM zYK=>qlg42Q`sv7^`m0#S1J<>JHg+sf0@tmc4H}>{@%N!R=6TnyZaRaGf(RIV!@?Cj z`-m&*;2ukCFJcPrt-~R=w*5=kz<4Ercs9Mbsj+jK9f+%ZMyt8hTS;>gG|H+Sn8{ zD>U<8@F2Ut3~A*Vf+oA+*nxO2X4A!V;S(FeJJmPoU=XHIua1=x-EW3*FkaXH*MDR9 zZqd@yXklmQt1icz{`=VqdZ)@>C?cmJmi!3f!;D^6y!X?X#2Plz+bXgcUjq_|j0jDr zX;P+_`x-DpB}iYF=3+6x7hRFI+aRq-fS2`ROg2Qkm_vgOnWq}ycN7N?M>oD7QHO0O zNcFdU6$R-1!NL3TX)}`4p^)&of7ey?_@Lmt6|zD%isaUBNhU%30sZE+87-j(C^b=+ zUHVl=9r{Uhsw-M9pKrqSj7k}Q4cQG%#Od)mb6!ypY=%n0|<}On8R%7!wHnNfo zX?P8z8$om;^{^>x^l;$bzB@ZXyaMd1%OF;g{ZtndU5M9NuzJYp;*Hd%_dK~k3p$p&b~WkX?8g=7e08n zA8i*Lk%Q71j4g|4o-Z=(Gbh`JSR%?iid@`9oW7r`m%euqhnkal1}2d5)wZmcsU4=# z-{{0>u@KcWDbYQKG>nvpzzgG*M6g4T;?VgaK&mQ87af+;*4Xb01TI#sUew};Zd8MI zCVb`_fefP*QcqN`U(nqg&OA509kD&GrkL5)j?g&xN&=d}gYGJ9Az8Yi-#((tGVe|c zOyQ->ZE+Pkk&^6TV$^+6CSMf6PGS_Fze?lTQt_E&V=2OCK%V1WI}cB(V$7YhcVlTc zh$$)eI_hSq_F}D9w&-F7nJ&$u1EK5J1!N&PT7wV`iIhH-_O}>j#DqyGL&5niq{bv4 z$d&rVOY&IGP`x(T*V<*Y<-D*2fhFTglA0Mi9Fj<9uQ3Rv*gw$KF5BMIsju7(vn;)~ zhG>S-E#@7XuxPt1eq8Z&YwlRTTvp`GKZ;!pMPv?uAwy)}eJG(>xkt)zA4j$=+#y*| z6k)g<&$F9mmD=%rFpoInE4Iu*QGPt5Ja}d&YlZK?gP4`iot0~C)v`o;3auNxk#~PyVTtPAV6CR_IM1u*h?W7_x z(aXv@vbLu4DlskDKwTj5u`56F4~{9W@GJB%Q7)ec9)U3#Z;5WCwT#}-8Lbm{KgfX# zqcwK0oN3k|#5}FflPRTsH4cfqR!R6Q;GZv~NScrGn}Y%%A8p+Ikx+rcv$Rx6jwg+h z+n`R@Z-bsc%N&NqtR4qDc&lNeuyVU(`5JHKIS{FG;u90wh}dZ}!pL@H6I$ORLqI}G zYgop;iivayD=ia%6;8!>{lJa}cBE2vigRN7Idj7c6z1HgC|)0gXgI6CebFVLUKm;$ z{Eo10rgA~HFm|Q#3c-shFt*eYvzQapptU;3GOMyxd^|)eVO^1P9~aq4j%`QKWxG$i z=F()oFc~xgPCl8xfAkF3)NzMk`*LzZ=jM-v`)-7II7!Ce;}$9b#o+xBiv0mU33DUv zgXzI>jt>h=*SZ5QBX=NygJxXtq}%K?fm#w?n?u5Whc>tr?A%4!QkihJPcV-VP*&gvPTi>+xLU!UBDd5Dk8Hjx>-#A z6$YH?i4!8Eu^%Ba6RV8FW?{BP z1F!fduxq~XM)%ggC-6YJZ${Da82Dh7Ev6R)buK{3ql`1Cvkfo0tgjqftjxegCDLv} zQHu8bL;c{gO5kvI9bLyZL$gFPR&Zs%ni7Mco3JzlOyFK>Tu80Gy`SzSfKZ-4D}$ zDuc!fyJ$9XK=zv4X4f#7<^lp46GA#o$o0Kwm~iCZxPI$c&D-^bZ0?~B#qNep)m~A9 z5=~EaVxMmVM_!}R?ijU;>1}HfbI|ZgNf^t{GGaJR_J~Nv*Xr!Sf=D8b0~0-v;D4*J zWmj=$AMt%Ek`+Vx0r?h`@jO!%pAk(cH>-B1c?_$UGq6U4@yiQw1M5KhXiUfggkRxBaO9(s zNCeqP_kSiCP>kJ~^ry7qofN;J*%ci-%w@JV0DW#_k%-Ik9z5M0L}o!sS`n-8H-x@{ z{PZ}E++|FEi(P+x@{#ePCJox_`=L@bPl)cfeH=jHtJQ!X~K0Z$Na1JP0ei8B1kcHUtDqweuENO z-_WZwVYO?8MISC1Rdc@xmiS=mRZFPbC$OYIMxydwu)NI}ZJnL!5pM-$#N2gt;F-HmILo;x2~{Db<{T* z>F<2dDFo#O1RA9iTZt2vK?x@;DR1&^6}W^udu)eI%DvdJC+mcv(YpKrnh9;X*-(RM zRWapkJWQuhyc*3ysQ>QQ7oJJI?6$Xw-(t3mWO7oRrqgkTy>@Ap9jkj)md!{{F&bnX z8#6*pG-XI?eDM6xs%qjCiS-Gp3aiX3xdDCx-uE!lP^e{%m{KbkmLlW#XYy#AP7S`v zc^O3BOOIkV{ecRa$tFr6i3fL94%wN>mJs7CV$*)}!X6;4k>vTJKPEFEeNa=fpk9KT zeH#oj&e8dyMa?iOo{Y9gcIv)@ZpB#3yefGW<9qw!WgO&%hfD=xp|1YY5bUuy<0k#0 zFrSZYX5`2(;v*<^%mdUf!93vwi#{D{69pI2har=Z!mJDe^+htHUw6Vq_#*p^TAHH~ z%FBNUir$vYmAr`6%Hb=3vDJ7r9gv}&hShHRf%>)nn{ zfl(QWwM_4ciLUD+?A{xk!50CuSPT0a9A;fGUEgV*565MtP<44s&l!a~qtJ}zbGa)V zl9KDQ-4@HD`-_~2n~2r+>a!v9*8~$6l!WH%5!EdAERoVH);l@rSo9?LM$s!TQyGH! zC7^a&5@v|e$-*jeB6XZ=fnU89#R|wIg_Pe#N>;p+BFruJijpA%f`MKEF`HsDtY|e> z3P80Yejb{hQiL&@`(uiw5yl1;y%t1Zyh!ZeE(`RvVwwDESZ&8q(M^Em=LfXNWLh;R zrf6)~(KvoM7*GPJuLFEu`) zc5|Y3DN}ss&`Fh-2FZt;@b>J#Fu0FqU%3e2j}1_^dsW_w=n2)Bfj*@O*0+m{rbW?^yRhd~TFH{ZTb4)2tBaI-@4YX! z-<1d{cbhj85+P;Ty7GNxP=^7yCoZ8jWON90`#Fa&66?OqZr>cm0J{#rd-u3n3NxG! z@EDD2bqmkJ*bg+I8gGJ->qzrWCQaUu22Ho37o&X&}6hNN;<>*$!TEb?92j z7HdqNad8KhFLCmzi+n~WKyOIV8Jn1W4utbMo*O%AD`w2i>$cG%y;Ns3g=2}vNRP;u zo`?MwqG8l9f3<%ED0^KP>R}fWqF)+Tff#f-JpBe(-shL2+&03w&s<<+4RSmF6Ed%! z;L;?(Xd8VIYkteMEg)L3HFikHiLwsoO7o9i=B^WV2-kzm3~)r~uMJuS%D#rZt}3hW zD_H;M#pHbSfy2QINoXy94k7$U$1eFi%)G*%Z%k%buny9Y%@sVuV~gygJem|7UJPc7 zLLKs*oEPO;Vr(7K+r?2@$(+))W>6tXNa#;e6@!`rNOtY)DYDA~Y$SRsV6-U4%qStD zDP$gmN{rAkPCTG=<_Z#FM21NY-$+1azbnLZZJ-F%qbv~UUqpFr9O{8=>;Hj#0jF|| z6J_-*HE<~|lDG`pfR7FB4iVq#hQ!%aF;%>$o@%k?&p%F?EWhCyH}ch8nqoG*#}hiu zlFHpEirLxD2FIZ$`jX3E6rWQ9hDDY*#5=f+J#1<4%#E#xHEgM`yjuqSMud1C6||<+ zG158$-j_$tQ!SaPPlFllps0FlpG$kv=I;EM?7+>N{PCGgAKKvur?Pt+Ewh)1i`PMG zoBQxMNULOVmpQV7`0B(-5)DhwL?0rJsj9;rH0aZIr}U#<85>K$RI8lF{Azigo$o8M zCEr5Qe2m<|E39-*qsbLslcw5L8QJ5M1PgC@)i@sJT(91^KVqvb9A+-2TmbO-9}(@s zAnB6VnzaC{Z+7gH3|D|9ITU5WUh=MdOV&wG^ixMRiEwxu_`zrsG}3rJOSR6S)43^M z2{E^FnZ!~v<@7W3?jjd=C|4#hv1!4t`a@bl2&fFu5OcxXt~BoyHTP*sT`I0lsIsr| zEs!u|{?H3_)`a@pAdq~BOMv_aD?Q{7*k92&%XMD|`^EpKunIP<$Q!&$jS#UT$~m4j zgA$T+QZ+^tal2Nj`cA>N5^nPvTZX*kPp=d~NAz4S0+ z`j@D@kG^!)JxIIX^V0Zw>txEi5lCgCsCxD8^N;Og6EIMXPdcon&O_kasv|Y#yYcYM zod`&d!6&$lwZL8@<4@0@JLSa?LW4%Gy>2xq^N{q=bVS&{8v@{YM|-PXOVKwQRk_Hd zGc{%4QJ0@?3|40_AbMQ(Y~9Sw1+G^ZJO7<&lZ_m>IwZ?raripxUDip+fS~b?u<zyd_V#I%toEdB z9wojS-h60x(qb8MBSZ|Z20e01tj5Ij+Q9R1b)>A4Kks_Du)G&Dx%kJfaEj6Rt(YiE zLSWdg?7fP~K*xxG(RuAxO2x8)a^(v~u>8Xo>}LhVLR zm1me%i2uICFlWzGqg`7PdRQZ~sP^_64yxW&ABW&hQD+#@gf#5ho_5-8M+wN}9hN-@ zoCqBMfcMOYVJdZb;t32hR9XvNRr3^U9z= zou0PvQ%$eIHWANqv)e`7slD&zl8vhh0N|o|oO?*uc~A85eH-+g`$t*kt}!U-SHbOzUqAQIV2(fe7P2FWq2#jk%bP-T%Ss%ve^GFSUf>+Ym5jcWWQICf|J@{#3$4aZ@dl_)Po z+m1-jO;o^8WR(G5qI-tMFJt;&l1itp994t*#!btPIPwvZW9O;@*Jvi7y7RcB;O8wH zrbkB$;1KE;SCb1#Hm-u8X~W0B52#hGPP|ggV!2qg9MPackzUvQg42rccHsP+m-{Sq zf`gMV1_wq_a>A}sRiMU44o+5-Nyv>SxYz#t+w_ECXj zYDA)%_y>#cn&@@ji&Dd^LNd7^L$pF0YK4!*_#BJ>`B!RkP^6dmNF4k~PH|C>ce>9kSe9l%ki|vfxe&U<5Rq$z z#lf|~@6KAqE~R2B$nJ}%vVA~wzRD#@Sa&SPF$rLOZ;Q^1stI6|0sA1JZT)%R zQ)n!#AL!UDoJ%`}j8fj#&b(fQAIHc2V>3|uIlx3{r z{(d!t`r=|gQkm3Ue6F^G5p7o+*Hh7B-C0x@U}S@{bnxxWw;vdNOlJVEsO=+Z>TMX! zJnDuON5uMszqabGQ5gD4Ng7928dUhkI1F9bPs023oAcU}<(ett$kE?n3RQ4zMIhbYq?u;B*FBa}-K2J{cw$s-RZ`QIO zuCJydyhKdTlNnoJFxKl1DZ(pqztmSnR-2uew_ZHf)?BIGGVgio*fyzQ%OZ&zqnE%y9RK1PZbB4l! ziNlHR4;YgIz?zcqFKZbWYbHx*3+r%7w9bbz*}5>mm-G{H#^KQ<0=E)lV_%!z%?H

s?o6&ua`Prt{d{`VkjIv75VffLsniQ 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

S+C__}n<0{2KS_L74pdk>D$ve| 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

0gZ@geK4 ziA2au(xEyvTv#nQ7ztXwOY{>qEF;r)r6!1~Ih$ECg*;LX%hQE0^d(H;+Y=)`LxSxT z$%A+~|WW)>zDg-V8@?nnKQ{8wBC!(j){n?)!;x%5`7g>psC z+oDLf0R7b>UVsGWNONv%_M(``vKQR7vh$AY)5IOa=lOfvQ*e?#OO?u1VK99ixlYN% zG27*-d8^-nLEFHR9H2{r!kPr=zi48eJ7sayrR4WA9c;smol~95vlXFJWVNa70THB~ ztuRa5JlGZcyR?U+a=+!1hAW_FQsYHbztRKbaL|lI^(n_Oc=jub9AZD^V|KtK%}P=l z?^=84Vl)W9EsTb(H9i(TBDK!O)rO-u5XXDwZ%5gJ)UBIZ3+8e#L~{>VsJV8U35_($ zPaS6e^^@ifdV2c)Us1xZC(uisi*2=ZM1s#U0VKkI1nBgNa8?8UMTUxE`A? zVRl&Wf*;wOi`x^ZxIOa`M3cXkIV)^N74pkd3-($Iwz?ieyN6ZT&78}EKDKf@$y--B zGq#b#MMr)CA%Zi~+N|0A^BWJG2Svl37NV|$$Jm?gsW-A)Q(U-=l-RLGW0=QoyWYxU zu9Z?~dq@E{lmlVPG&Wsz|UN9w{aK*!;yElmlYrUsqfNe(0_@6Ue; zSc{OF#vd)#YMT1~&lHzZqx`-F3JW8bWydaXVs<#Ov;?z*z@9%wlBBAh^5a!kQT>3F z0RWU*YCD#O>-7F9OnLWT@aj@ry~+_4qm2B@M>7k5_{30Jt_P8$)UX;e zw<@cYh&Qj*OT+?r#lYBYN1Y@sXP$j_m)g6bsuW%(D65@^R894E@;bnJKo)*Xjb+#b zzR#MD8YYqiTgOC|%A!zybN*MxfS!x75{6G&W9*g%*qdRmLZHKr2Yo#BL4io&#P&QT z240?NJi`+dl`b_547T5qFATQ&ggvni$<9qEYpW+c_(i*{bc}7#jbt)1P*LU0i~J7> zuJ@2Kw%K)3)As}Wb>;Y3{AGE;!~y(Jo|=SZe_`G6c#hYsRpXiF7*O8QpkkoC(WFzx z)YY*wMG&uK^=R|`7Rog9yR4t)3dif7kK2%+(eeR)q-w&xB6Nqd&Wng?gw}%%-qn4f-HIM`a=iX-BDjQ zFjzfnC(Q91etHdnOl>@;Muw9|+3y;8gj>jY`lpceyJ_ymL_Kb7ur#~iMvMvqwiB7$ z7@AEI4K^pw5@s4YsTxQ2sUTpCs2w~}fzYpCU$g#ZT9SK>J_!6fRo*GqqbY|$y0mDRdtYa_9EuNsnmu=RiE4CYI&0(D*z$|aJzjBJWc za+3{p-4O<_sq8Zw#g>WJdVqmZ+`31vHO_zAo--sM$&rJPM|09?kFuj9rW#q99MTxXq0?NEiYC2u<`pF!iOL|< ztaUJd{lBZ6ristLs2&E1f{lz`tj#7HnhK#0y1KPu zXMJd~Al_@|OF4kD7Mh4jX4O2)zyciS+>}Um83T2i=)m8dsR@@F6WV%pS&8zt60O@D z5UpjFH;3%KGsbKnx##YvRInZrOsE#zsNGD|8?}G5YDcy&cCn@6ZTr%#l=^Q&`IrA} zRYup{X9{l59C-*lKPs6YW*FZfFEH&k=g#IHXI@VvLZf>rG0g@u<#B=ydq6kajMYm|49HO0G4P1TjP-$F>ND#rT6QT972q zTpe?Z+zv<>81eac0AdKl#nHZJ}K zA`aAW`rzsivK!ZcLQIWagt9F2AOlSYKHXN0+oWv00{+rq%;{$JB!d+|z%lwBe;Uek z&Tz&^6l-uo=JhwOWHsJj2UH$Fo*`80x2%2dbtNwF|E#mvnV~#jFlh@NhJJfhZd=?cBD~w ztA+-lwST!hUSzNP6;r;(4IQ2gN&TRLgMB9{3<)c1%>x|(1bWLF7ySi^vPH$$0>8n< zaJ(1{!J!pTE7Vxb(i)SRQFVktgdAvq{HihXU6}bD)i%(FzQCH@>Mn

hW@Y1#iJn0@k;0oB7g9Y$Zb!3HlM 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%0A0S3s@Cv|SIyL>}i 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

)A!YvfBiw^gEI`EHa$9tq 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 }d} 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