From cbc319092fbdf897f4ca3497be5c6fb9c623e5f7 Mon Sep 17 00:00:00 2001 From: caixinwang Date: Wed, 25 May 2022 18:41:35 +0800 Subject: [PATCH] Initial commit --- .gitignore | 2 + .idea/.gitignore | 8 + .idea/libraries/Java_EE_6_Java_EE_6.xml | 17 + .idea/misc.xml | 6 + .idea/modules.xml | 8 + AlgorithmZuo.iml | 12 + lib/javax.annotation.jar | Bin 0 -> 7713 bytes lib/javax.ejb.jar | Bin 0 -> 47581 bytes lib/javax.jms.jar | Bin 0 -> 25957 bytes lib/javax.persistence.jar | Bin 0 -> 129793 bytes lib/javax.resource.jar | Bin 0 -> 44511 bytes lib/javax.servlet.jar | Bin 0 -> 69933 bytes lib/javax.servlet.jsp.jar | Bin 0 -> 78836 bytes lib/javax.servlet.jsp.jstl.jar | Bin 0 -> 28120 bytes lib/javax.transaction.jar | Bin 0 -> 9714 bytes src/class01/Code01_SelectionSort.java | 115 +++++ src/class01/Code02_BubbleSort.java | 110 +++++ src/class01/Code03_InsertionSort.java | 130 ++++++ src/class01/Code04_BSExist.java | 65 +++ src/class01/Code05_BSNearLeft.java | 75 ++++ src/class01/Code05_BSNearRight.java | 75 ++++ src/class01/Code06_BSAwesome.java | 79 ++++ src/class01/Code07_EvenTimesOddTimes.java | 35 ++ src/class01/Code08_GetMax.java | 19 + src/class01/Code09_FindOneLessValueIndex.java | 46 ++ src/class02/Code01_Swap.java | 71 +++ src/class02/Code02_EvenTimesOddTimes.java | 83 ++++ src/class02/Code03_KM.java | 159 +++++++ src/class03/Code01_ReverseList.java | 221 ++++++++++ src/class03/Code02_DeleteGivenValue.java | 38 ++ ...Code03_DoubleEndsQueueToStackAndQueue.java | 183 ++++++++ src/class03/Code04_RingArray.java | 50 +++ src/class03/Code05_GetMinStack.java | 105 +++++ .../Code06_TwoStacksImplementQueue.java | 60 +++ .../Code07_TwoQueueImplementStack.java | 90 ++++ src/class03/Code08_GetMax.java | 24 ++ src/class03/HashMapAndSortedMap.java | 128 ++++++ src/class04/Code01_MergeSort.java | 147 +++++++ src/class04/Code02_SmallSum.java | 137 ++++++ src/class04/Code03_ReversePair.java | 130 ++++++ src/class04/Code04_BiggerThanRightTwice.java | 135 ++++++ src/class05/Code01_CountOfRangeSum.java | 63 +++ src/class05/Code02_PartitionAndQuickSort.java | 197 +++++++++ ...de03_QuickSortRecursiveAndUnrecursive.java | 195 +++++++++ .../Code04_DoubleLinkedListQuickSort.java | 300 +++++++++++++ src/class06/Code01_Comparator.java | 168 ++++++++ src/class06/Code02_Heap.java | 192 +++++++++ src/class06/Code03_HeapSort.java | 158 +++++++ .../Code04_SortArrayDistanceLessK.java | 127 ++++++ src/class07/Code01_CoverMax.java | 155 +++++++ src/class07/Code02_EveryStepShowBoss.java | 303 +++++++++++++ src/class07/HeapGreater.java | 112 +++++ src/class07/Inner.java | 9 + src/class08/Code01_TrieTree.java | 299 +++++++++++++ src/class08/Code02_TrieTree.java | 314 ++++++++++++++ src/class08/Code03_CountSort.java | 111 +++++ src/class08/Code04_RadixSort.java | 148 +++++++ src/class09/Code01_LinkedListMid.java | 164 +++++++ src/class09/Code02_IsPalindromeList.java | 204 +++++++++ src/class09/Code03_SmallerEqualBigger.java | 138 ++++++ src/class09/Code04_CopyListWithRandom.java | 79 ++++ .../Code01_FindFirstIntersectNode.java | 170 ++++++++ src/class10/Code02_RecursiveTraversalBT.java | 72 ++++ .../Code03_UnRecursiveTraversalBT.java | 118 +++++ src/class11/Code01_LevelTraversalBT.java | 49 +++ .../Code02_SerializeAndReconstructTree.java | 264 ++++++++++++ .../Code03_EncodeNaryTreeToBinaryTree.java | 86 ++++ src/class11/Code04_PrintBinaryTree.java | 74 ++++ src/class11/Code05_TreeMaxWidth.java | 114 +++++ src/class11/Code06_SuccessorNode.java | 86 ++++ src/class11/Code07_PaperFolding.java | 28 ++ src/class12/Code01_IsCBT.java | 147 +++++++ src/class12/Code02_IsBST.java | 125 ++++++ src/class12/Code03_IsBalanced.java | 102 +++++ src/class12/Code04_IsFull.java | 109 +++++ src/class12/Code05_MaxSubBSTSize.java | 240 +++++++++++ src/class12/Code06_MaxDistance.java | 180 ++++++++ src/class13/Code01_IsCBT.java | 117 +++++ src/class13/Code02_MaxSubBSTHead.java | 136 ++++++ src/class13/Code03_lowestAncestor.java | 141 ++++++ src/class13/Code04_MaxHappy.java | 116 +++++ src/class13/Code05_LowestLexicography.java | 116 +++++ src/class14/Code01_Light.java | 85 ++++ src/class14/Code02_LessMoneySplitGold.java | 79 ++++ src/class14/Code03_BestArrange.java | 111 +++++ src/class14/Code04_IPO.java | 59 +++ src/class14/Code05_UnionFind.java | 76 ++++ src/class15/Code01_FriendCircles.java | 78 ++++ src/class15/Code02_NumberOfIslands.java | 317 ++++++++++++++ src/class15/Code03_NumberOfIslandsII.java | 165 +++++++ src/class16/Code01_BFS.java | 30 ++ src/class16/Code02_DFS.java | 34 ++ src/class16/Code03_TopologicalOrderBFS.java | 53 +++ src/class16/Code03_TopologicalOrderDFS1.java | 70 +++ src/class16/Code03_TopologicalOrderDFS2.java | 76 ++++ src/class16/Code03_TopologySort.java | 38 ++ src/class16/Code04_Kruskal.java | 101 +++++ src/class16/Code05_Prim.java | 91 ++++ src/class16/Code06_Dijkstra.java | 171 ++++++++ src/class16/Code06_NetworkDelayTime.java | 151 +++++++ src/class16/Edge.java | 14 + src/class16/Graph.java | 14 + src/class16/GraphGenerator.java | 37 ++ src/class16/Node.java | 20 + src/class17/Code01_Dijkstra.java | 162 +++++++ src/class17/Code02_Hanoi.java | 139 ++++++ src/class17/Code03_PrintAllSubsquences.java | 74 ++++ src/class17/Code04_PrintAllPermutations.java | 107 +++++ .../Code05_ReverseStackUsingRecursive.java | 44 ++ src/class17/Edge.java | 14 + src/class17/Graph.java | 14 + src/class17/Node.java | 20 + src/class18/Code01_RobotWalk.java | 94 ++++ src/class18/Code02_CardsInLine.java | 116 +++++ src/class19/Code01_Knapsack.java | 63 +++ src/class19/Code02_ConvertToLetterString.java | 123 ++++++ src/class19/Code03_StickersToSpellWord.java | 151 +++++++ .../Code04_LongestCommonSubsequence.java | 124 ++++++ src/class20/Code01_PalindromeSubsequence.java | 121 ++++++ src/class20/Code02_HorseJump.java | 109 +++++ src/class20/Code03_Coffee.java | 204 +++++++++ src/class21/Code01_MinPathSum.java | 79 ++++ .../Code02_CoinsWayEveryPaperDifferent.java | 77 ++++ src/class21/Code03_CoinsWayNoLimit.java | 108 +++++ .../Code04_CoinsWaySameValueSamePapper.java | 148 +++++++ src/class21/Code05_BobDie.java | 58 +++ src/class22/Code01_KillMonster.java | 100 +++++ src/class22/Code02_MinCoinsNoLimit.java | 121 ++++++ src/class22/Code03_SplitNumber.java | 85 ++++ src/class23/Code01_SplitSumClosed.java | 94 ++++ .../Code02_SplitSumClosedSizeHalf.java | 243 +++++++++++ src/class23/Code03_NQueens.java | 97 +++++ src/class24/Code01_SlidingWindowMaxArray.java | 99 +++++ src/class24/Code02_AllLessNumSubArray.java | 109 +++++ src/class24/Code03_GasStation.java | 53 +++ src/class24/Code04_MinCoinsOnePaper.java | 250 +++++++++++ src/class25/Code01_MonotonousStack.java | 165 +++++++ src/class25/Code02_AllTimesMinToMax.java | 98 +++++ .../Code03_LargestRectangleInHistogram.java | 58 +++ src/class25/Code04_MaximalRectangle.java | 48 +++ .../Code05_CountSubmatricesWithAllOnes.java | 81 ++++ src/class26/Code01_SumOfSubarrayMinimums.java | 156 +++++++ src/class26/Code02_FibonacciProblem.java | 186 ++++++++ .../Code03_ZeroLeftOneStringNumber.java | 109 +++++ src/class27/Code01_KMP.java | 75 ++++ src/class27/Code02_TreeEqual.java | 172 ++++++++ src/class27/Code03_IsRotation.java | 64 +++ src/class28/Code01_Manacher.java | 90 ++++ src/class28/Code02_AddShortestEnd.java | 54 +++ src/class29/Code01_FindMinKth.java | 175 ++++++++ src/class29/Code02_MaxTopK.java | 223 ++++++++++ src/class29/Code03_ReservoirSampling.java | 94 ++++ src/class30/Code01_MorrisTraversal.java | 230 ++++++++++ src/class30/Code05_MinHeight.java | 118 +++++ src/class31/Code01_SegmentTree.java | 245 +++++++++++ src/class31/Code02_FallingSquares.java | 109 +++++ src/class32/Code01_IndexTree.java | 83 ++++ src/class32/Code02_IndexTree2D.java | 57 +++ src/class32/Code03_AC1.java | 105 +++++ src/class32/Code04_AC2.java | 125 ++++++ src/class33/Hash.java | 48 +++ src/class34/ReadMe.java | 2 + src/class35/Code01_AVLTreeMap.java | 257 +++++++++++ src/class36/Code01_SizeBalancedTreeMap.java | 360 ++++++++++++++++ src/class36/Code02_SkipListMap.java | 248 +++++++++++ src/class37/Code01_CountofRangeSum.java | 223 ++++++++++ src/class37/Code02_SlidingWindowMedian.java | 228 ++++++++++ .../Code03_AddRemoveGetIndexGreat.java | 259 +++++++++++ .../Code04_QueueReconstructionByHeight.java | 265 ++++++++++++ src/class37/Compare.java | 408 ++++++++++++++++++ src/class38/Code01_AppleMinBags.java | 41 ++ src/class38/Code02_EatGrass.java | 57 +++ src/class38/Code03_MSumToN.java | 44 ++ src/class38/Code04_MoneyProblem.java | 169 ++++++++ src/class39/Code01_SubsquenceMaxModM.java | 141 ++++++ src/class39/Code02_SnacksWays.java | 79 ++++ src/class39/Code02_SnacksWaysMain.java | 125 ++++++ src/class39/Code03_10Ways.java | 92 ++++ src/class39/Code04_DifferentBTNum.java | 66 +++ src/class39/IsSum.java | 180 ++++++++ ...ngestSumSubArrayLengthInPositiveArray.java | 91 ++++ .../Code02_LongestSumSubArrayLength.java | 92 ++++ .../Code03_LongestLessSumSubArrayLength.java | 103 +++++ ...de04_AvgLessEqualValueLongestSubarray.java | 152 +++++++ .../Code05_PrintMatrixSpiralOrder.java | 53 +++ src/class40/Code06_RotateMatrix.java | 44 ++ src/class40/Code07_ZigZagPrintMatrix.java | 42 ++ src/class40/Code08_PrintStar.java | 46 ++ src/class41/Code01_BestSplitForAll.java | 72 ++++ .../Code02_BestSplitForEveryPosition.java | 130 ++++++ src/class41/Code03_StoneMerge.java | 117 +++++ src/class41/Code04_SplitArrayLargestSum.java | 193 +++++++++ src/class42/Code01_PostOfficeProblem.java | 115 +++++ .../Code02_ThrowChessPiecesProblem.java | 166 +++++++ src/class43/Code01_CanIWin.java | 116 +++++ src/class43/Code02_TSP.java | 295 +++++++++++++ src/class43/Code03_PavingTile.java | 215 +++++++++ ...1_LastSubstringInLexicographicalOrder.java | 136 ++++++ src/class44/DC3.java | 168 ++++++++ src/class44/DC3_Algorithm.pdf | Bin 0 -> 192716 bytes ...e01_InsertS2MakeMostAlphabeticalOrder.java | 261 +++++++++++ src/class45/Code02_CreateMaximumNumber.java | 250 +++++++++++ ...LongestCommonSubstringConquerByHeight.java | 286 ++++++++++++ src/class46/Code01_BurstBalloons.java | 107 +++++ src/class46/Code02_RemoveBoxes.java | 81 ++++ .../Code03_DeleteAdjacentSameCharacter.java | 176 ++++++++ src/class46/Code04_MaxSumLengthNoMore.java | 100 +++++ src/class46/Code05_HuffmanTree.java | 214 +++++++++ src/class47/Code01_StrangePrinter.java | 78 ++++ src/class47/Code02_RestoreWays.java | 246 +++++++++++ src/class47/Code03_DinicAlgorithm.java | 139 ++++++ 211 files changed, 24355 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/.gitignore create mode 100644 .idea/libraries/Java_EE_6_Java_EE_6.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 AlgorithmZuo.iml create mode 100644 lib/javax.annotation.jar create mode 100644 lib/javax.ejb.jar create mode 100644 lib/javax.jms.jar create mode 100644 lib/javax.persistence.jar create mode 100644 lib/javax.resource.jar create mode 100644 lib/javax.servlet.jar create mode 100644 lib/javax.servlet.jsp.jar create mode 100644 lib/javax.servlet.jsp.jstl.jar create mode 100644 lib/javax.transaction.jar create mode 100644 src/class01/Code01_SelectionSort.java create mode 100644 src/class01/Code02_BubbleSort.java create mode 100644 src/class01/Code03_InsertionSort.java create mode 100644 src/class01/Code04_BSExist.java create mode 100644 src/class01/Code05_BSNearLeft.java create mode 100644 src/class01/Code05_BSNearRight.java create mode 100644 src/class01/Code06_BSAwesome.java create mode 100644 src/class01/Code07_EvenTimesOddTimes.java create mode 100644 src/class01/Code08_GetMax.java create mode 100644 src/class01/Code09_FindOneLessValueIndex.java create mode 100644 src/class02/Code01_Swap.java create mode 100644 src/class02/Code02_EvenTimesOddTimes.java create mode 100644 src/class02/Code03_KM.java create mode 100644 src/class03/Code01_ReverseList.java create mode 100644 src/class03/Code02_DeleteGivenValue.java create mode 100644 src/class03/Code03_DoubleEndsQueueToStackAndQueue.java create mode 100644 src/class03/Code04_RingArray.java create mode 100644 src/class03/Code05_GetMinStack.java create mode 100644 src/class03/Code06_TwoStacksImplementQueue.java create mode 100644 src/class03/Code07_TwoQueueImplementStack.java create mode 100644 src/class03/Code08_GetMax.java create mode 100644 src/class03/HashMapAndSortedMap.java create mode 100644 src/class04/Code01_MergeSort.java create mode 100644 src/class04/Code02_SmallSum.java create mode 100644 src/class04/Code03_ReversePair.java create mode 100644 src/class04/Code04_BiggerThanRightTwice.java create mode 100644 src/class05/Code01_CountOfRangeSum.java create mode 100644 src/class05/Code02_PartitionAndQuickSort.java create mode 100644 src/class05/Code03_QuickSortRecursiveAndUnrecursive.java create mode 100644 src/class05/Code04_DoubleLinkedListQuickSort.java create mode 100644 src/class06/Code01_Comparator.java create mode 100644 src/class06/Code02_Heap.java create mode 100644 src/class06/Code03_HeapSort.java create mode 100644 src/class06/Code04_SortArrayDistanceLessK.java create mode 100644 src/class07/Code01_CoverMax.java create mode 100644 src/class07/Code02_EveryStepShowBoss.java create mode 100644 src/class07/HeapGreater.java create mode 100644 src/class07/Inner.java create mode 100644 src/class08/Code01_TrieTree.java create mode 100644 src/class08/Code02_TrieTree.java create mode 100644 src/class08/Code03_CountSort.java create mode 100644 src/class08/Code04_RadixSort.java create mode 100644 src/class09/Code01_LinkedListMid.java create mode 100644 src/class09/Code02_IsPalindromeList.java create mode 100644 src/class09/Code03_SmallerEqualBigger.java create mode 100644 src/class09/Code04_CopyListWithRandom.java create mode 100644 src/class10/Code01_FindFirstIntersectNode.java create mode 100644 src/class10/Code02_RecursiveTraversalBT.java create mode 100644 src/class10/Code03_UnRecursiveTraversalBT.java create mode 100644 src/class11/Code01_LevelTraversalBT.java create mode 100644 src/class11/Code02_SerializeAndReconstructTree.java create mode 100644 src/class11/Code03_EncodeNaryTreeToBinaryTree.java create mode 100644 src/class11/Code04_PrintBinaryTree.java create mode 100644 src/class11/Code05_TreeMaxWidth.java create mode 100644 src/class11/Code06_SuccessorNode.java create mode 100644 src/class11/Code07_PaperFolding.java create mode 100644 src/class12/Code01_IsCBT.java create mode 100644 src/class12/Code02_IsBST.java create mode 100644 src/class12/Code03_IsBalanced.java create mode 100644 src/class12/Code04_IsFull.java create mode 100644 src/class12/Code05_MaxSubBSTSize.java create mode 100644 src/class12/Code06_MaxDistance.java create mode 100644 src/class13/Code01_IsCBT.java create mode 100644 src/class13/Code02_MaxSubBSTHead.java create mode 100644 src/class13/Code03_lowestAncestor.java create mode 100644 src/class13/Code04_MaxHappy.java create mode 100644 src/class13/Code05_LowestLexicography.java create mode 100644 src/class14/Code01_Light.java create mode 100644 src/class14/Code02_LessMoneySplitGold.java create mode 100644 src/class14/Code03_BestArrange.java create mode 100644 src/class14/Code04_IPO.java create mode 100644 src/class14/Code05_UnionFind.java create mode 100644 src/class15/Code01_FriendCircles.java create mode 100644 src/class15/Code02_NumberOfIslands.java create mode 100644 src/class15/Code03_NumberOfIslandsII.java create mode 100644 src/class16/Code01_BFS.java create mode 100644 src/class16/Code02_DFS.java create mode 100644 src/class16/Code03_TopologicalOrderBFS.java create mode 100644 src/class16/Code03_TopologicalOrderDFS1.java create mode 100644 src/class16/Code03_TopologicalOrderDFS2.java create mode 100644 src/class16/Code03_TopologySort.java create mode 100644 src/class16/Code04_Kruskal.java create mode 100644 src/class16/Code05_Prim.java create mode 100644 src/class16/Code06_Dijkstra.java create mode 100644 src/class16/Code06_NetworkDelayTime.java create mode 100644 src/class16/Edge.java create mode 100644 src/class16/Graph.java create mode 100644 src/class16/GraphGenerator.java create mode 100644 src/class16/Node.java create mode 100644 src/class17/Code01_Dijkstra.java create mode 100644 src/class17/Code02_Hanoi.java create mode 100644 src/class17/Code03_PrintAllSubsquences.java create mode 100644 src/class17/Code04_PrintAllPermutations.java create mode 100644 src/class17/Code05_ReverseStackUsingRecursive.java create mode 100644 src/class17/Edge.java create mode 100644 src/class17/Graph.java create mode 100644 src/class17/Node.java create mode 100644 src/class18/Code01_RobotWalk.java create mode 100644 src/class18/Code02_CardsInLine.java create mode 100644 src/class19/Code01_Knapsack.java create mode 100644 src/class19/Code02_ConvertToLetterString.java create mode 100644 src/class19/Code03_StickersToSpellWord.java create mode 100644 src/class19/Code04_LongestCommonSubsequence.java create mode 100644 src/class20/Code01_PalindromeSubsequence.java create mode 100644 src/class20/Code02_HorseJump.java create mode 100644 src/class20/Code03_Coffee.java create mode 100644 src/class21/Code01_MinPathSum.java create mode 100644 src/class21/Code02_CoinsWayEveryPaperDifferent.java create mode 100644 src/class21/Code03_CoinsWayNoLimit.java create mode 100644 src/class21/Code04_CoinsWaySameValueSamePapper.java create mode 100644 src/class21/Code05_BobDie.java create mode 100644 src/class22/Code01_KillMonster.java create mode 100644 src/class22/Code02_MinCoinsNoLimit.java create mode 100644 src/class22/Code03_SplitNumber.java create mode 100644 src/class23/Code01_SplitSumClosed.java create mode 100644 src/class23/Code02_SplitSumClosedSizeHalf.java create mode 100644 src/class23/Code03_NQueens.java create mode 100644 src/class24/Code01_SlidingWindowMaxArray.java create mode 100644 src/class24/Code02_AllLessNumSubArray.java create mode 100644 src/class24/Code03_GasStation.java create mode 100644 src/class24/Code04_MinCoinsOnePaper.java create mode 100644 src/class25/Code01_MonotonousStack.java create mode 100644 src/class25/Code02_AllTimesMinToMax.java create mode 100644 src/class25/Code03_LargestRectangleInHistogram.java create mode 100644 src/class25/Code04_MaximalRectangle.java create mode 100644 src/class25/Code05_CountSubmatricesWithAllOnes.java create mode 100644 src/class26/Code01_SumOfSubarrayMinimums.java create mode 100644 src/class26/Code02_FibonacciProblem.java create mode 100644 src/class26/Code03_ZeroLeftOneStringNumber.java create mode 100644 src/class27/Code01_KMP.java create mode 100644 src/class27/Code02_TreeEqual.java create mode 100644 src/class27/Code03_IsRotation.java create mode 100644 src/class28/Code01_Manacher.java create mode 100644 src/class28/Code02_AddShortestEnd.java create mode 100644 src/class29/Code01_FindMinKth.java create mode 100644 src/class29/Code02_MaxTopK.java create mode 100644 src/class29/Code03_ReservoirSampling.java create mode 100644 src/class30/Code01_MorrisTraversal.java create mode 100644 src/class30/Code05_MinHeight.java create mode 100644 src/class31/Code01_SegmentTree.java create mode 100644 src/class31/Code02_FallingSquares.java create mode 100644 src/class32/Code01_IndexTree.java create mode 100644 src/class32/Code02_IndexTree2D.java create mode 100644 src/class32/Code03_AC1.java create mode 100644 src/class32/Code04_AC2.java create mode 100644 src/class33/Hash.java create mode 100644 src/class34/ReadMe.java create mode 100644 src/class35/Code01_AVLTreeMap.java create mode 100644 src/class36/Code01_SizeBalancedTreeMap.java create mode 100644 src/class36/Code02_SkipListMap.java create mode 100644 src/class37/Code01_CountofRangeSum.java create mode 100644 src/class37/Code02_SlidingWindowMedian.java create mode 100644 src/class37/Code03_AddRemoveGetIndexGreat.java create mode 100644 src/class37/Code04_QueueReconstructionByHeight.java create mode 100644 src/class37/Compare.java create mode 100644 src/class38/Code01_AppleMinBags.java create mode 100644 src/class38/Code02_EatGrass.java create mode 100644 src/class38/Code03_MSumToN.java create mode 100644 src/class38/Code04_MoneyProblem.java create mode 100644 src/class39/Code01_SubsquenceMaxModM.java create mode 100644 src/class39/Code02_SnacksWays.java create mode 100644 src/class39/Code02_SnacksWaysMain.java create mode 100644 src/class39/Code03_10Ways.java create mode 100644 src/class39/Code04_DifferentBTNum.java create mode 100644 src/class39/IsSum.java create mode 100644 src/class40/Code01_LongestSumSubArrayLengthInPositiveArray.java create mode 100644 src/class40/Code02_LongestSumSubArrayLength.java create mode 100644 src/class40/Code03_LongestLessSumSubArrayLength.java create mode 100644 src/class40/Code04_AvgLessEqualValueLongestSubarray.java create mode 100644 src/class40/Code05_PrintMatrixSpiralOrder.java create mode 100644 src/class40/Code06_RotateMatrix.java create mode 100644 src/class40/Code07_ZigZagPrintMatrix.java create mode 100644 src/class40/Code08_PrintStar.java create mode 100644 src/class41/Code01_BestSplitForAll.java create mode 100644 src/class41/Code02_BestSplitForEveryPosition.java create mode 100644 src/class41/Code03_StoneMerge.java create mode 100644 src/class41/Code04_SplitArrayLargestSum.java create mode 100644 src/class42/Code01_PostOfficeProblem.java create mode 100644 src/class42/Code02_ThrowChessPiecesProblem.java create mode 100644 src/class43/Code01_CanIWin.java create mode 100644 src/class43/Code02_TSP.java create mode 100644 src/class43/Code03_PavingTile.java create mode 100644 src/class44/Code01_LastSubstringInLexicographicalOrder.java create mode 100644 src/class44/DC3.java create mode 100644 src/class44/DC3_Algorithm.pdf create mode 100644 src/class45/Code01_InsertS2MakeMostAlphabeticalOrder.java create mode 100644 src/class45/Code02_CreateMaximumNumber.java create mode 100644 src/class45/Code03_LongestCommonSubstringConquerByHeight.java create mode 100644 src/class46/Code01_BurstBalloons.java create mode 100644 src/class46/Code02_RemoveBoxes.java create mode 100644 src/class46/Code03_DeleteAdjacentSameCharacter.java create mode 100644 src/class46/Code04_MaxSumLengthNoMore.java create mode 100644 src/class46/Code05_HuffmanTree.java create mode 100644 src/class47/Code01_StrangePrinter.java create mode 100644 src/class47/Code02_RestoreWays.java create mode 100644 src/class47/Code03_DinicAlgorithm.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..267c321 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +# 项目排除路径 +/out/ \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..35410ca --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# 默认忽略的文件 +/shelf/ +/workspace.xml +# 基于编辑器的 HTTP 客户端请求 +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/libraries/Java_EE_6_Java_EE_6.xml b/.idea/libraries/Java_EE_6_Java_EE_6.xml new file mode 100644 index 0000000..82b06f1 --- /dev/null +++ b/.idea/libraries/Java_EE_6_Java_EE_6.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..abf7648 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..7222057 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/AlgorithmZuo.iml b/AlgorithmZuo.iml new file mode 100644 index 0000000..35d749d --- /dev/null +++ b/AlgorithmZuo.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/lib/javax.annotation.jar b/lib/javax.annotation.jar new file mode 100644 index 0000000000000000000000000000000000000000..52dca7f561c5d579ca764cb25b96d3f539cf242a GIT binary patch literal 7713 zcmb7IcRbYpAGbNeaW+{w*(2pEPGx6vgityhP8^xp;ZXLLk(E`lB9X|7?3Jt%nHjlc zQ+{{8{UXWl@q69l-20FB`|0y}zFzOwcxtKQ6VTw0U_M!e=O8!-41hy`qpqMUBdDyQ zDEy-p2L~TVOO==ey94j%Hm$!Nrors^by!_SLs?NlM^{K)@tb;ko0_ViP!GAPAYW&D zS1DBFt=QOVZq*G22(8~hA?#UlzF+}#k-;Qev@n_(1{#y_CwF;X zCWX3TwiV&a;v&%jW2OzCd1ID1!KXhBxOu=i<6 zv@%AAY@={-aB?sqyC1EGJ<55MN7Z3Sm?hj?77j!H^l4O=O2Z{!(<^nI2_KzB1zj3S z1!1C>Yq}+VH=bo_12sn^0tH9HxROmxI{Ycem4H zmmMmrBVVvn76z3E(famPHJuJhnl`s7^USTORTiU(h6mD~s`iPz_4vw5b95eKsCj(k zl>7R^OGP!C9JTt-8s{d@Rkz4~6W z7))gseZ@t!wKY>nrCsB<9KR8f?N8xyp0#*bNO%QLX7CbOa`q{ky3IXml_D(i$S~Bt z#(e)yE!R=hYS}qD$=M+togAFaoPLtq-K9}~2}p#tlh6BfadKKlo07%$Q$Hw*T3!Bf z!F7nBv#d(|wLav%mr2B|wLxb(-GS;!6+sVLykDuE+5Wm&gJ*wFJVP`zI$1jn*ch?i zvM9n?W?`KnThM-%McpDrQr7Z&R%nSvWqVTbdLOmM%iG%z!fnS33wVvMZ&9rV^n!`= z2nSDSolg01g<^4}ET#0;j)Q_kk$9AaX7m(4nPh>YsFY|g;PVf;COyl~(6L+R)O0Vv zPrGe*>Cv--!aqTC+BbRmpWd5ZeNKa#_80sXOPn|@1Y^b}F+yA72Yp<}HH1p#W$-%d zFB6$*`gwi4;LjkHj!83^Ag#z!*ud~qb}W+|M>6REmxp6`v~&AKBQ{1cG(NE{`+->H z=Db2F&4iwN6d6nUE}o(K_Ha{%&5#9nh~}F$t0xYdO3G~!R=h7T%fY4Q+J%{cZQ?sO zSwNC~BB||oe)Q6#&CmNHyGb(Pg(qBQrh~`yw4x+OgU_4y zOb=r5x{_c|o`R)XJXTfo2e{Y7;Y#&X%-;8_=Y6YDH9mc%@AVS+UbMTN(bc;+Y)p>Rh#X9qJlmyENM z6&&e=Fxx8ux^DLHe_4&wF|wnUq4^Q0n$RXor4naocbQ)co}Hr^NyK$O5@MHRabqLud0DEn^xqJU~ zn|n}Qa>ptI-jpwbq+!&;fTxRMe6CZYR6E~=d67=8_#M(YKTOXnLB^vG(oMNanIwdQ z&zHjhwh5X7mXNW;$7V~8FlPLL(eE3n2@E^U5*lg_7S~ox5~6-Pr~v? zjZksdjLTI%wZ!6y38lN#HgD8vODijLcXxtKE8RkDt_eWJR$H>1uJwQOjn(C;c*MzG z7m^+fg!0i9XL1Hg$#C7$ua^d_%iW!jApvB8At#~))$?CC44s=71U%y)V5_o`5cDcR zkc>JgdR_Q3=M~c?ZG`_};j@|CoS509W-eh%{HRMa1L+5srb#kq<1Vs>$v94`%iR$Z z1zG0AILZpGgTXd|kPG$973A(uva*$1h0ng;vV=$~?0^s}g(URGCJ~>^3?96;tD7)v z0C0UXiIh@+a^DsLTcPHzGGrRKd_Q|p_G;#3`IlUAJ56(dr%JoZ`o%nzI>Dh|wUxZ< z#kd6}TZ+)+E!7px9)3Hb?~DUBjlmPU8cD9f&pK1>g4Jdi=9PS^LSArP=kOblFHjoJ z@o4KCvVD%0?+nO{F{oDrukHos%^_EQX-o-1*m#y)z(yI1+4=Whu6-en@(Cl}#o6VVSO_q*>SVa?VtH}< z!BFCZ&DHI#(|6Xb0GDd5ZPQ3}U5UMl`WB@MFS)&%Us)sIBMx6j)CMkkfARQ$uarJ{ zl5JTp;i|iaB)MlIQN~&!C-r^lhm8^bZ-Bl^^vlrlxVH^!V=I0;x;Je7vfjaxw#%h_ z)O`cBcPhN*q0|;7UE?2rd_$J%M5-$UjIRq&=Y^Tm^jtLwzaQ^Zb0x-+F$QvlD7&)8 zllO`uolj`7cB$x1v(6^Sz;JTmq<^hqQ8ArT&s~rxW3g4W!6ei=n*5w<(ry$>ARnel zs4x^*F^6!)bZabK_}D<9TCixu;Toq#ozvzztIb60{$}F?d5zM}Yh{!5+2nQ_%1hOG z+K$%>csoJq@;AI$T$&+oPK{eEh-3=OP}{%a*1l>a)f%_$o0(G!dV-d?I88AOPQCc# z8^T{x53g=){A`|??FrAb43OGP!+|x3H$bvQ)dT^_>`aM zXpC^pKH(hapi)i42ss`rl-HhgtW;lbyq zXQF4vM?4IleLqXU(GvmFQG7bW&72(&PHxAv{Q}3HlZRvaeg`k#+ z$K9pGIA`dmgN~lBPX7<0k!~_JHb3J&QdJdSlqNuPi;3+@exdPQ$}Cml_bylNQxV>5 zlOX-XcTyM(?s%gHO?fq4&o&l8Z72UE1h=$ehHIvZ9(m!y$5CA48z$*u!F~C+xPV;i z&p)s7&{<_ifZ|7uh2VMFi2)OzQ_k?)m$^qbP3jrOCEIV@B-DU>-TFt6*D>Jf6^5us zfreyd2~a80Zqjaw?(v-D;Y#h)N+^Xinw%u^CyOuGH~`gQx}G*x`Vs@~HwUgJ~us)3Q?&7*UDtCxlw zgytEO2F*Wwg0R~-7EdgKm{3AVHFx*=M*-KNI&9omA4@Cmv9xNz9c&R!zY&X>Ujw>U zXw#m}wFYctsp2EA(K2Zli#PJz%^#77H9~@vk-?icg?4a%@Z2g4d8bfYb>DG1HNrQs zXemMyCt>iklcJ2L7%TP5*{>IBP7c z2?&n2Q!V-Q`Nf>hlc>yp0BMhQ=zKIaz+aD$1~Ir? zHm)wY)V!+IsK;>2Z)I1ezI`@}8ePm9dgTI~-_5nMCjcKntj!CW-pL}97Jd0bRc7$^ zn^ws9P1_3LlkYAW1bL7ze1FI4qR`dTm@`{G$@-ALF1%FON~FBoDd69X5__cujE`Zp z?pS7z&kTDh0Yj{v>o0L2s!au72B=_{*t{YBwZxv|LS#BK@%tB~T$wl2?uYuB_b1Y1 z$XOa%xMyyjqxZhU27Xr|a`i;TBaf;t8R;)-YA#;DH8xArS}bd*LWYbHYDSEE<*M@p z2}?%}%Xh@u=|a}38URY+_Ajj-NtA1xw>5jUX$c5Rxox7M6aG}bs*=s10nx{>-E>M8 zo}<>p+RDnu`WV$0R6j1r4A%)i8G$6Re62QOACCH_PdA&zKspFGr4iY5@p2c_$qqsY zACX*K=#a9v{jKu`vRgtuUk$car{F~nckCI=&v>?WN%qS%gqWxT=Dm`^R3EJy$lYMH zsB`D3;-*CM?itmM+7+1Tudsk^?jiv*qUGShmPH)?Q1#el*^Ag5ftY)q%7~$R=2*(X z#|AUh87cGMEF7h(x);c0h=*UVurbp^owc3JY76rHXbITBVSW`a!sIHz)x0g(Bpk4p zgo6i#@Q~9fQZr^_ot|Dko_H0F7wX7US?fo(aq9pkft(NSCGlU>=3ZlK289b`RN3&F zLGzzzgnisCebu;ao#d!%J{<3@b>CaVs^bc@I3$OxSs}#UL0@WBGAx*vxy?==)#q*z zX&-=^3MG^*48kj~zYEG*r#zzuIvIz@+V7vyYFZx6mhzwocq$nIQXMh!T+UP>30f|8 z$B;WPfl9a;EqME3*&EK99>6-uyuFM;j83GSm6?(;zDLM#U7;1d`t8i*`e%$roFj2w zgrefz8@O0%Ygu7LBp6y-e^PsNeWX2-+FLfqlt$sd9g&AQ!F2X!D|xsD0*SyXYdsx?AWaI{?q${@SY|`wFlk7b%9#-03i>R{hEwp;FsDG<2^MHB`71I)f zw88`d)Z%q!Vn*V6?w1q#@y#{j8Uw;vf$vPMy@uyP+i1vqX8F4XYK5N=^i#i8(XvF3 znIkO2;dpiXWBn>JJ@TSoV<(eL<$^Vw}T-MZI5|)<7ENz1O*oW zjVbFaNXq5x$&&PM+`r~yrhO|SWv1+!Z!S7?fpw9Irbdb&5DnBDk6H(^%6;7{TpDCh zC0xu{M04y|B=Y}6*UxJo_Ft{8r!0TfWHpB~I4F}9tAlit%Jx$~WS6#WFetbAXV@m# z>)F3L2#*0JZuNp^=|?-4`$Ovyo6L6{7l67>J|L58t|T%$gE3lcpC3^axHO<$L_Rfw zDCqTvE8pI-cj59r;%|D64fOAME?wa!2@aN)IGYRX#GE~_%>nu?MwohH!b zq499UW}Ugk4Y!hih7=a7W_t~gK)sAh22rmv?r6Qv$phQ?JTS^reNX?4L+u%*>02%% z{xeOIvmoq!{|AR&7Z-Ea2lnneE*=fe?@B+0`8~rb{lD8U=^m^5YpMQz^q}yL)%o{L z8ngU$^nmJ*MIgmFI^AFe`h9D{EdMLl|NRVBu-~^i%<|tezsdLe4~><34+_ET`)4sL zY_a*{_#rv}AO%>ogW4xnJl?l?45@#h{UIeE#yhA0VinVUQ^dID54JXI^totg8~LtL)*80j0698!2hYJ9mYR63S(uEeRBXDga2c6i!1 zH;Utt4~$ZWK@YaR*wJC%x+#wXJv2xhhCSHc{mud(=2rg^NWa+pvx`2Ae6YL0wuk%n zfcjYE{bmsh`8U^N+oFBj038Ln*ChS!^@Al2+a~QBKPGqn`Mwy4zc)>XU-@8y$5zRG ayGQqbfd2}7!o6gSEfc3Ot66$1-hTi%8LHX< literal 0 HcmV?d00001 diff --git a/lib/javax.ejb.jar b/lib/javax.ejb.jar new file mode 100644 index 0000000000000000000000000000000000000000..4ebf5ecd4c9ab83540ef206f6c5a1906db008098 GIT binary patch literal 47581 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

x$jzCx>QKI9Dc;_z4e z0Yjof!jtYPhbWx5hKGB$%LhfCBm1`-iGRzk7{pT4|G=z*;ASA7z{7XMS%+kLwXk%W>j7B@a_J!C@WeN1+*~V>{Bl#-HQiNtX^sL^z#~Mk&VLmX>zsDS*--Z5!6_ zmyRl7s(i+;QDxn)?`n6UYg4?s_nq&1alMX7Ku2!YsIXC8I_2ltv;oQhymtI+SybF} z&5%yV8Fnis+qSDJR969o0ePgX?>;nd8ilT!8EO$W(t2lKb^a@_Kv!Z*Qr`py`u0g3 zZbg(P;3`80t34!4MxMUfAtf5-l2pfc`~4o`Nn>PHWMZ{b^}Tq!IwLnS=D7Q@nP_AJ z8sVco2#(CK5g9n>xsoML==2oKhcCtLFy{?{xX#>Z&lk>Rso+VQP2bI;*p#A39_3ua z3zya53Tr%pUuzV&vlWDW1X|$xfqs}%)d9;}8Om^7+L!id9t6GxKHtOdzTXeqV9Jx! z3tmZxybfD?S*MPdg8Yyzxeny(r4u#+Pi{N;939nD8jYt&PnrFIsp6>KC+OLmrLq&S^b}zlDjCytmd7*0xc^f^MC9@ z=&NP_ano*sGCn+17@8=dpTx`IX{m-AX2O1xKfI3v*rmYo7&K&ngiW$r9mr_rx)sM_ z#Oz=4Vv`zRZpXu&Mmb(!)N6q=g+V9i!6~U(QvqD8>wnK=8H%^MaQx|0(^ETQWB?(j z)>@rFuE&URSsM)!vFUiB*5>)ainsa+K~=f=`~V@-tbQ7awxjYeWi7nicAh733(G?(In)J1z$v! zL)gO*mdA8wN2rh$t0s*R_F)t71Fc-W$DmA4%YZ5OBX?nfnfoN)+l~Aka750IO#Bo~ zZe&XByG|qJYr-bX^f>*UgsC_FPBhVcu zE?M4|@Ruv)k2hBFW{aTLN~uCB#iXo{tcREvLbp5b!I5W6lPQIXmT&ztMU$l&mh67# zAVy>wOf`Z@GYN~<9~>2ZQ)I%iXWE?%z?cj|Q{04$##f^$+D6A`k9Np#ZT=(obE*6&ga+De#a*1}r|Tm%hJ3*HK%S{$iBTjr6E zcRAkU5^&yPn!YG)RYb1Mp?J?nfnrr6wM5#lYRlUNv%b0z1qno-+KO5py~U(0-o|J7 zZG>rh9+nU6g?vloX{oe!XiIW&JI|RXQKd3)S|&LDu#KENkxJ{^hzwD-$szetxtXs;FE fq85Hs^+j9}Vd)!)Er7{XjDPYXacZ#z&d&6IINz)K literal 0 HcmV?d00001 diff --git a/lib/javax.resource.jar b/lib/javax.resource.jar new file mode 100644 index 0000000000000000000000000000000000000000..696a2345878907025784d2e6e49c6e6b41d1cfac GIT binary patch literal 44511 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 Rg4PB8C6U3vu!Mnr`hR(p1f~E0 literal 0 HcmV?d00001 diff --git a/lib/javax.servlet.jar b/lib/javax.servlet.jar new file mode 100644 index 0000000000000000000000000000000000000000..0519e4a4e16c207f18527175c6a4425b2b1bacbd GIT binary patch literal 69933 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 b^7P&R>&oaDM@qlP6 literal 0 HcmV?d00001 diff --git a/lib/javax.servlet.jsp.jar b/lib/javax.servlet.jsp.jar new file mode 100644 index 0000000000000000000000000000000000000000..9c0631cea0fd56031db19fc3edbb4db4bfbb923a GIT binary patch literal 78836 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 literal 0 HcmV?d00001 diff --git a/lib/javax.transaction.jar b/lib/javax.transaction.jar new file mode 100644 index 0000000000000000000000000000000000000000..729c6952389cd4769d5d840b3d1a7941b3c0f63e GIT binary patch literal 9714 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 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/src/class01/Code03_InsertionSort.java b/src/class01/Code03_InsertionSort.java new file mode 100644 index 0000000..36664d4 --- /dev/null +++ b/src/class01/Code03_InsertionSort.java @@ -0,0 +1,130 @@ +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); + } + } + } + + public static void insertionSort_02(int[] arr) { + if (arr == null || arr.length < 2) { + return; + } + int p,i; + for (p=1;p0&&temp [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);//利用java内部的排序算法保证一定对 + 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_02(arr); + printArray(arr); + } + +} diff --git a/src/class01/Code04_BSExist.java b/src/class01/Code04_BSExist.java new file mode 100644 index 0000000..d89be00 --- /dev/null +++ b/src/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/src/class01/Code05_BSNearLeft.java b/src/class01/Code05_BSNearLeft.java new file mode 100644 index 0000000..eaf5804 --- /dev/null +++ b/src/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/src/class01/Code05_BSNearRight.java b/src/class01/Code05_BSNearRight.java new file mode 100644 index 0000000..f3b6f25 --- /dev/null +++ b/src/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/src/class01/Code06_BSAwesome.java b/src/class01/Code06_BSAwesome.java new file mode 100644 index 0000000..8df78e4 --- /dev/null +++ b/src/class01/Code06_BSAwesome.java @@ -0,0 +1,79 @@ +package class01; + +import java.util.Arrays; + +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); +// Arrays.sort(arr); + int ans = getLessIndex(arr); + if (!isRight(arr, ans)) { + System.out.println("出错了!"); + break; + } + } + System.out.println("测试结束"); + } + +} diff --git a/src/class01/Code07_EvenTimesOddTimes.java b/src/class01/Code07_EvenTimesOddTimes.java new file mode 100644 index 0000000..12ec590 --- /dev/null +++ b/src/class01/Code07_EvenTimesOddTimes.java @@ -0,0 +1,35 @@ +package class01; + +public class Code07_EvenTimesOddTimes { + public static void printOddTimesOneNumber(int[] arr){ + int eor=0; + for(int i:arr) eor^=i;//出来之后eor就是我们要的 + System.out.println(eor); + } + + public static void printOddTimesTwoNumber(int[] arr){ + int eor=0; + for(int i:arr) eor^=i;//出来之后eor=a^b + /** + * 我们异或了所有的数得到了eor=a^b,现在要分别得到a和b还缺少一个条件。 + * 一个思路就是不去异或所有的数,我们只异或一部分的数,并且这一部分只包含 a和b的其中一个 + * 方法是找出a与b不同的那一位,利用掩码将a和b划分成两个子集,每个子集异或进去可以达到a或者是b。 + * 问题关键来到如何找出a和b不同的那一位,事实上a^b为1的那些位都是a和b不同的位,这里我们找a^b最右边的1 + * 小结论:一个数和它的补码的与运算得到的就是最右边的那一位---想想补码是怎么求的 + */ + int mask=eor&(~eor+1);//得到了掩码 + int aORb=0,another=0 ; + for(int i:arr) { + if((i&mask)==mask) aORb^=i;//该位置为1的全部异或起来 + } + another=eor^aORb; + System.out.println(another+" "+aORb); + } + + public static void main(String[] args) { + int [] arr1={1,1,1,6,6,2,2}; + int [] arr2={1,1,1,6,6,6,2,2}; + printOddTimesOneNumber(arr1); + printOddTimesTwoNumber(arr2); + } +} diff --git a/src/class01/Code08_GetMax.java b/src/class01/Code08_GetMax.java new file mode 100644 index 0000000..1e46437 --- /dev/null +++ b/src/class01/Code08_GetMax.java @@ -0,0 +1,19 @@ +package class01; + +public class Code08_GetMax { + + public static int getMax(int[] arr) { + return process(arr, 0, arr.length - 1); + } + + public static int process(int[] arr, int L, int R) { + if (L == R) { + return arr[L]; + } + int mid = L + ((R - L) >> 1); + int leftMax = process(arr, L, mid); + int rightMax = process(arr, mid + 1, R); + return Math.max(leftMax, rightMax); + } + +} diff --git a/src/class01/Code09_FindOneLessValueIndex.java b/src/class01/Code09_FindOneLessValueIndex.java new file mode 100644 index 0000000..1c1793d --- /dev/null +++ b/src/class01/Code09_FindOneLessValueIndex.java @@ -0,0 +1,46 @@ +package class01; + +public class Code09_FindOneLessValueIndex { + + public static int getLessIndex(int[] arr) { + if (arr == null || arr.length == 0) { + return -1; // no exist + } + 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 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[] arr = { 6, 5, 3, 4, 6, 7, 8 }; + printArray(arr); + int index = getLessIndex(arr); + System.out.println("index: " + index + ", value: " + arr[index]); + + } + +} diff --git a/src/class02/Code01_Swap.java b/src/class02/Code01_Swap.java new file mode 100644 index 0000000..c625614 --- /dev/null +++ b/src/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/src/class02/Code02_EvenTimesOddTimes.java b/src/class02/Code02_EvenTimesOddTimes.java new file mode 100644 index 0000000..13ac877 --- /dev/null +++ b/src/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/src/class02/Code03_KM.java b/src/class02/Code03_KM.java new file mode 100644 index 0000000..a530445 --- /dev/null +++ b/src/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/src/class03/Code01_ReverseList.java b/src/class03/Code01_ReverseList.java new file mode 100644 index 0000000..4b0318a --- /dev/null +++ b/src/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/src/class03/Code02_DeleteGivenValue.java b/src/class03/Code02_DeleteGivenValue.java new file mode 100644 index 0000000..647047a --- /dev/null +++ b/src/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/src/class03/Code03_DoubleEndsQueueToStackAndQueue.java b/src/class03/Code03_DoubleEndsQueueToStackAndQueue.java new file mode 100644 index 0000000..e9b30a9 --- /dev/null +++ b/src/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/src/class03/Code04_RingArray.java b/src/class03/Code04_RingArray.java new file mode 100644 index 0000000..cad73b4 --- /dev/null +++ b/src/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/src/class03/Code05_GetMinStack.java b/src/class03/Code05_GetMinStack.java new file mode 100644 index 0000000..91b20ae --- /dev/null +++ b/src/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/src/class03/Code06_TwoStacksImplementQueue.java b/src/class03/Code06_TwoStacksImplementQueue.java new file mode 100644 index 0000000..483d7a7 --- /dev/null +++ b/src/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/src/class03/Code07_TwoQueueImplementStack.java b/src/class03/Code07_TwoQueueImplementStack.java new file mode 100644 index 0000000..a2a3c54 --- /dev/null +++ b/src/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/src/class03/Code08_GetMax.java b/src/class03/Code08_GetMax.java new file mode 100644 index 0000000..81661d8 --- /dev/null +++ b/src/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/src/class03/HashMapAndSortedMap.java b/src/class03/HashMapAndSortedMap.java new file mode 100644 index 0000000..99f67fc --- /dev/null +++ b/src/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/src/class04/Code01_MergeSort.java b/src/class04/Code01_MergeSort.java new file mode 100644 index 0000000..c0ddb6b --- /dev/null +++ b/src/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/src/class04/Code02_SmallSum.java b/src/class04/Code02_SmallSum.java new file mode 100644 index 0000000..1167a64 --- /dev/null +++ b/src/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/src/class04/Code03_ReversePair.java b/src/class04/Code03_ReversePair.java new file mode 100644 index 0000000..0e6e531 --- /dev/null +++ b/src/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/src/class04/Code04_BiggerThanRightTwice.java b/src/class04/Code04_BiggerThanRightTwice.java new file mode 100644 index 0000000..7eb4f7e --- /dev/null +++ b/src/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/src/class05/Code01_CountOfRangeSum.java b/src/class05/Code01_CountOfRangeSum.java new file mode 100644 index 0000000..559a7d8 --- /dev/null +++ b/src/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/src/class05/Code02_PartitionAndQuickSort.java b/src/class05/Code02_PartitionAndQuickSort.java new file mode 100644 index 0000000..e8a9d7a --- /dev/null +++ b/src/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/src/class05/Code03_QuickSortRecursiveAndUnrecursive.java b/src/class05/Code03_QuickSortRecursiveAndUnrecursive.java new file mode 100644 index 0000000..57a7657 --- /dev/null +++ b/src/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/src/class05/Code04_DoubleLinkedListQuickSort.java b/src/class05/Code04_DoubleLinkedListQuickSort.java new file mode 100644 index 0000000..a3014dc --- /dev/null +++ b/src/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/src/class06/Code01_Comparator.java b/src/class06/Code01_Comparator.java new file mode 100644 index 0000000..af23b64 --- /dev/null +++ b/src/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/src/class06/Code02_Heap.java b/src/class06/Code02_Heap.java new file mode 100644 index 0000000..28140a1 --- /dev/null +++ b/src/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/src/class06/Code03_HeapSort.java b/src/class06/Code03_HeapSort.java new file mode 100644 index 0000000..49e743a --- /dev/null +++ b/src/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/src/class06/Code04_SortArrayDistanceLessK.java b/src/class06/Code04_SortArrayDistanceLessK.java new file mode 100644 index 0000000..0d1b1ea --- /dev/null +++ b/src/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/src/class07/Code01_CoverMax.java b/src/class07/Code01_CoverMax.java new file mode 100644 index 0000000..6260f97 --- /dev/null +++ b/src/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/src/class07/Code02_EveryStepShowBoss.java b/src/class07/Code02_EveryStepShowBoss.java new file mode 100644 index 0000000..a4c123b --- /dev/null +++ b/src/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/src/class07/HeapGreater.java b/src/class07/HeapGreater.java new file mode 100644 index 0000000..eab68a4 --- /dev/null +++ b/src/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/src/class07/Inner.java b/src/class07/Inner.java new file mode 100644 index 0000000..d3d5326 --- /dev/null +++ b/src/class07/Inner.java @@ -0,0 +1,9 @@ +package class07; + +public class Inner { + public T value; + + public Inner(T v) { + value = v; + } +} diff --git a/src/class08/Code01_TrieTree.java b/src/class08/Code01_TrieTree.java new file mode 100644 index 0000000..f7f9cf1 --- /dev/null +++ b/src/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/src/class08/Code02_TrieTree.java b/src/class08/Code02_TrieTree.java new file mode 100644 index 0000000..f1227ae --- /dev/null +++ b/src/class08/Code02_TrieTree.java @@ -0,0 +1,314 @@ +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; + //如果你只要加入小写的26个字母,那么你最多只需要26条路就行了。这个路其实就是一个指针。利用null来标记路存不存在 + // 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++; + + } + + /** + * 如果到某一个地方pass为0了,那么说明后面的pass值一定都为0.因为pass为0说明只有一个字符串会走这条路, + * 而现在这个字符串你要删掉,那么只需要把后面的路彻底放弃就行了。也就是pass=0给了我们额外信息。 + * @param word + */ + 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;//26个大小的空间不够就用这个,Integer表示这条路的ASCII码值 + + 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/src/class08/Code03_CountSort.java b/src/class08/Code03_CountSort.java new file mode 100644 index 0000000..d7864ec --- /dev/null +++ b/src/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/src/class08/Code04_RadixSort.java b/src/class08/Code04_RadixSort.java new file mode 100644 index 0000000..af35db3 --- /dev/null +++ b/src/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/src/class09/Code01_LinkedListMid.java b/src/class09/Code01_LinkedListMid.java new file mode 100644 index 0000000..87ac28a --- /dev/null +++ b/src/class09/Code01_LinkedListMid.java @@ -0,0 +1,164 @@ +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 头。1)输入链表头节点,奇数长度返回中点,偶数长度返回上中点 + 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; + } + //2)输入链表头节点,奇数长度返回中点,偶数长度返回下中点 + 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; + } + + //3)输入链表头节点,奇数长度返回中点前一个,偶数长度返回上中点前一个 + 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; + } + + //4)输入链表头节点,奇数长度返回中点前一个,偶数长度返回下中点前一个 + 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/src/class09/Code02_IsPalindromeList.java b/src/class09/Code02_IsPalindromeList.java new file mode 100644 index 0000000..63d6eed --- /dev/null +++ b/src/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/src/class09/Code03_SmallerEqualBigger.java b/src/class09/Code03_SmallerEqualBigger.java new file mode 100644 index 0000000..33203e4 --- /dev/null +++ b/src/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/src/class09/Code04_CopyListWithRandom.java b/src/class09/Code04_CopyListWithRandom.java new file mode 100644 index 0000000..f42454f --- /dev/null +++ b/src/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/src/class10/Code01_FindFirstIntersectNode.java b/src/class10/Code01_FindFirstIntersectNode.java new file mode 100644 index 0000000..8cec65d --- /dev/null +++ b/src/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/src/class10/Code02_RecursiveTraversalBT.java b/src/class10/Code02_RecursiveTraversalBT.java new file mode 100644 index 0000000..c7f2b2c --- /dev/null +++ b/src/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/src/class10/Code03_UnRecursiveTraversalBT.java b/src/class10/Code03_UnRecursiveTraversalBT.java new file mode 100644 index 0000000..a101d7c --- /dev/null +++ b/src/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/src/class11/Code01_LevelTraversalBT.java b/src/class11/Code01_LevelTraversalBT.java new file mode 100644 index 0000000..12e8ef0 --- /dev/null +++ b/src/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/src/class11/Code02_SerializeAndReconstructTree.java b/src/class11/Code02_SerializeAndReconstructTree.java new file mode 100644 index 0000000..6e6da7e --- /dev/null +++ b/src/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/src/class11/Code03_EncodeNaryTreeToBinaryTree.java b/src/class11/Code03_EncodeNaryTreeToBinaryTree.java new file mode 100644 index 0000000..aa9f2ab --- /dev/null +++ b/src/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/src/class11/Code04_PrintBinaryTree.java b/src/class11/Code04_PrintBinaryTree.java new file mode 100644 index 0000000..75125f7 --- /dev/null +++ b/src/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/src/class11/Code05_TreeMaxWidth.java b/src/class11/Code05_TreeMaxWidth.java new file mode 100644 index 0000000..787bd01 --- /dev/null +++ b/src/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/src/class11/Code06_SuccessorNode.java b/src/class11/Code06_SuccessorNode.java new file mode 100644 index 0000000..b33d4b5 --- /dev/null +++ b/src/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/src/class11/Code07_PaperFolding.java b/src/class11/Code07_PaperFolding.java new file mode 100644 index 0000000..3f3b0db --- /dev/null +++ b/src/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/src/class12/Code01_IsCBT.java b/src/class12/Code01_IsCBT.java new file mode 100644 index 0000000..19b2f48 --- /dev/null +++ b/src/class12/Code01_IsCBT.java @@ -0,0 +1,147 @@ +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/src/class12/Code02_IsBST.java b/src/class12/Code02_IsBST.java new file mode 100644 index 0000000..c3c55a8 --- /dev/null +++ b/src/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 = 1000; + 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/src/class12/Code03_IsBalanced.java b/src/class12/Code03_IsBalanced.java new file mode 100644 index 0000000..e8a6c0f --- /dev/null +++ b/src/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/src/class12/Code04_IsFull.java b/src/class12/Code04_IsFull.java new file mode 100644 index 0000000..a83c952 --- /dev/null +++ b/src/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/src/class12/Code05_MaxSubBSTSize.java b/src/class12/Code05_MaxSubBSTSize.java new file mode 100644 index 0000000..bd17329 --- /dev/null +++ b/src/class12/Code05_MaxSubBSTSize.java @@ -0,0 +1,240 @@ +package class12; + +import java.util.ArrayList; + +public class Code05_MaxSubBSTSize { + + 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 int maxSubBSTSize1(Node head) { + if (head == null) { + return 0; + } + int h = getBSTSize(head); + if (h != 0) { + return h; + } + return Math.max(maxSubBSTSize1(head.left), maxSubBSTSize1(head.right)); + } + +// public static int maxSubBSTSize2(Node head) { +// if (head == null) { +// return 0; +// } +// return process(head).maxSubBSTSize; +// } +// +// +// +// +// +// +// +// +// +// // 任何子树 +// public static class Info { +// public boolean isAllBST; +// public int maxSubBSTSize; +// public int min; +// public int max; +// +// public Info(boolean is, int size, int mi, int ma) { +// isAllBST = is; +// 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; +// +// if(leftInfo != null) { +// min = Math.min(min, leftInfo.min); +// max = Math.max(max, leftInfo.max); +// } +// if(rightInfo != null) { +// min = Math.min(min, rightInfo.min); +// max = Math.max(max, rightInfo.max); +// } +// +// +// +// +// +// +// +// int maxSubBSTSize = 0; +// if(leftInfo != null) { +// maxSubBSTSize = leftInfo.maxSubBSTSize; +// } +// if(rightInfo !=null) { +// maxSubBSTSize = Math.max(maxSubBSTSize, rightInfo.maxSubBSTSize); +// } +// boolean isAllBST = false; +// +// +// if( +// // 左树整体需要是搜索二叉树 +// ( leftInfo == null ? true : leftInfo.isAllBST ) +// && +// ( rightInfo == null ? true : rightInfo.isAllBST ) +// && +// // 左树最大值 X.value) +// +// +// ) { +// +// maxSubBSTSize = +// (leftInfo == null ? 0 : leftInfo.maxSubBSTSize) +// + +// (rightInfo == null ? 0 : rightInfo.maxSubBSTSize) +// + +// 1; +// isAllBST = true; +// +// +// } +// return new Info(isAllBST, maxSubBSTSize, min, max); +// } + + public static int maxSubBSTSize2(Node 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(Node x) { + if (x == null) { + return null; + } + Info leftInfo = process(x.left); + Info rightInfo = process(x.right); + int max = x.value; + int min = x.value; + 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.value); + boolean rightMinMoreX = rightInfo == null ? true : (x.value < 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); + } + + // 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 (maxSubBSTSize1(head) != maxSubBSTSize2(head)) { + System.out.println("Oops!"); + } + } + System.out.println("finish!"); + } + +} diff --git a/src/class12/Code06_MaxDistance.java b/src/class12/Code06_MaxDistance.java new file mode 100644 index 0000000..8c82c0e --- /dev/null +++ b/src/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/src/class13/Code01_IsCBT.java b/src/class13/Code01_IsCBT.java new file mode 100644 index 0000000..da365d1 --- /dev/null +++ b/src/class13/Code01_IsCBT.java @@ -0,0 +1,117 @@ +package class13; + +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) { + 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 (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 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/src/class13/Code02_MaxSubBSTHead.java b/src/class13/Code02_MaxSubBSTHead.java new file mode 100644 index 0000000..e64c37a --- /dev/null +++ b/src/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/src/class13/Code03_lowestAncestor.java b/src/class13/Code03_lowestAncestor.java new file mode 100644 index 0000000..e2a9d5f --- /dev/null +++ b/src/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/src/class13/Code04_MaxHappy.java b/src/class13/Code04_MaxHappy.java new file mode 100644 index 0000000..3cb21e7 --- /dev/null +++ b/src/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/src/class13/Code05_LowestLexicography.java b/src/class13/Code05_LowestLexicography.java new file mode 100644 index 0000000..13ceedd --- /dev/null +++ b/src/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/src/class14/Code01_Light.java b/src/class14/Code01_Light.java new file mode 100644 index 0000000..22428a9 --- /dev/null +++ b/src/class14/Code01_Light.java @@ -0,0 +1,85 @@ +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; + } + + // 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 = 1000; + for (int i = 0; i < testTime; i++) { + String test = randomString(len); + int ans1 = minLight1(test); + int ans2 = minLight2(test); + if (ans1 != ans2) { + System.out.println("oops!"); + } + } + System.out.println("finish!"); + } +} diff --git a/src/class14/Code02_LessMoneySplitGold.java b/src/class14/Code02_LessMoneySplitGold.java new file mode 100644 index 0000000..682fa12 --- /dev/null +++ b/src/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/src/class14/Code03_BestArrange.java b/src/class14/Code03_BestArrange.java new file mode 100644 index 0000000..ed7ecdb --- /dev/null +++ b/src/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/src/class14/Code04_IPO.java b/src/class14/Code04_IPO.java new file mode 100644 index 0000000..60e1edb --- /dev/null +++ b/src/class14/Code04_IPO.java @@ -0,0 +1,59 @@ +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/src/class14/Code05_UnionFind.java b/src/class14/Code05_UnionFind.java new file mode 100644 index 0000000..f08ace1 --- /dev/null +++ b/src/class14/Code05_UnionFind.java @@ -0,0 +1,76 @@ +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; + } + } + + /** + * nodes表用来记录每一个结点 + * parents表用来记录每一个结点所在集合的代表结点 + * size表只有当一个结点是代表结点的时候才会有记录 + * @param + */ + 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/src/class15/Code01_FriendCircles.java b/src/class15/Code01_FriendCircles.java new file mode 100644 index 0000000..dd4253d --- /dev/null +++ b/src/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/src/class15/Code02_NumberOfIslands.java b/src/class15/Code02_NumberOfIslands.java new file mode 100644 index 0000000..6065d42 --- /dev/null +++ b/src/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/src/class15/Code03_NumberOfIslandsII.java b/src/class15/Code03_NumberOfIslandsII.java new file mode 100644 index 0000000..d109276 --- /dev/null +++ b/src/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/src/class16/Code01_BFS.java b/src/class16/Code01_BFS.java new file mode 100644 index 0000000..c2d4d9f --- /dev/null +++ b/src/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/src/class16/Code02_DFS.java b/src/class16/Code02_DFS.java new file mode 100644 index 0000000..fa85ca4 --- /dev/null +++ b/src/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/src/class16/Code03_TopologicalOrderBFS.java b/src/class16/Code03_TopologicalOrderBFS.java new file mode 100644 index 0000000..5e54581 --- /dev/null +++ b/src/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/src/class16/Code03_TopologicalOrderDFS1.java b/src/class16/Code03_TopologicalOrderDFS1.java new file mode 100644 index 0000000..57f1bb7 --- /dev/null +++ b/src/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/src/class16/Code03_TopologicalOrderDFS2.java b/src/class16/Code03_TopologicalOrderDFS2.java new file mode 100644 index 0000000..4c90d1d --- /dev/null +++ b/src/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/src/class16/Code03_TopologySort.java b/src/class16/Code03_TopologySort.java new file mode 100644 index 0000000..f783474 --- /dev/null +++ b/src/class16/Code03_TopologySort.java @@ -0,0 +1,38 @@ +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/src/class16/Code04_Kruskal.java b/src/class16/Code04_Kruskal.java new file mode 100644 index 0000000..cb0ea62 --- /dev/null +++ b/src/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/src/class16/Code05_Prim.java b/src/class16/Code05_Prim.java new file mode 100644 index 0000000..b2f6299 --- /dev/null +++ b/src/class16/Code05_Prim.java @@ -0,0 +1,91 @@ +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/src/class16/Code06_Dijkstra.java b/src/class16/Code06_Dijkstra.java new file mode 100644 index 0000000..15ae446 --- /dev/null +++ b/src/class16/Code06_Dijkstra.java @@ -0,0 +1,171 @@ +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; + } + } + + /** + * 这里我们使用了Node数组,回想堆排序时候的堆,我们是一个int数组,因为现在是Node数组,所以我们不能直接根据Node来比较 + * 所以这里我们需要一个distanceMap来提供一个比较的途径。然后因为我们还需要更新堆中的某一个结点,更改完之后需要调整,所以 + * 这里我们还需要一个heapIndexMao,这个map有两个作用,一个是判断一个结点是否进入过堆中--通过index是否为-1判断。另外一个 + * 作用就是得到一个结点在堆中的下标,这样方便我们调整。 + */ + public static class NodeHeap { + private Node[] nodes; // 实际的堆结构 + // key 某一个node, value 上面堆中的位置.如果这里的位置为-1说明这结点曾经进来过但是现在不在堆上了。 + 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(node, heapIndexMap.get(node)); + } + if (!isEntered(node)) { + nodes[size] = node; + heapIndexMap.put(node, size); + distanceMap.put(node, distance); + insertHeapify(node, size++); + } + } + + /** + * 这个方法是用来服务外面的Dijkstra函数的。Dijkstra需要的是结点和距离的一个二元组信息,所以这里我们把node和distance + * 封装在一起弹出给外面。 + * @return + */ + 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(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) {//判断一个结点是否进来过堆,不管是-1还是一些其它的值 + return heapIndexMap.containsKey(node); + } + + private boolean inHeap(Node node) {//一个结点进来过并且在位置不等于-1它就在堆上。 + 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()) {//从小跟堆里面弹一条记录我就加一条在result里面 + 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/src/class16/Code06_NetworkDelayTime.java b/src/class16/Code06_NetworkDelayTime.java new file mode 100644 index 0000000..0be520a --- /dev/null +++ b/src/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/src/class16/Edge.java b/src/class16/Edge.java new file mode 100644 index 0000000..7c56891 --- /dev/null +++ b/src/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/src/class16/Graph.java b/src/class16/Graph.java new file mode 100644 index 0000000..933e43b --- /dev/null +++ b/src/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/src/class16/GraphGenerator.java b/src/class16/GraphGenerator.java new file mode 100644 index 0000000..fe3387d --- /dev/null +++ b/src/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/src/class16/Node.java b/src/class16/Node.java new file mode 100644 index 0000000..a468aaf --- /dev/null +++ b/src/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/src/class17/Code01_Dijkstra.java b/src/class17/Code01_Dijkstra.java new file mode 100644 index 0000000..0b39615 --- /dev/null +++ b/src/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/src/class17/Code02_Hanoi.java b/src/class17/Code02_Hanoi.java new file mode 100644 index 0000000..3d9ba6e --- /dev/null +++ b/src/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/src/class17/Code03_PrintAllSubsquences.java b/src/class17/Code03_PrintAllSubsquences.java new file mode 100644 index 0000000..05b6a03 --- /dev/null +++ b/src/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/src/class17/Code04_PrintAllPermutations.java b/src/class17/Code04_PrintAllPermutations.java new file mode 100644 index 0000000..c01729e --- /dev/null +++ b/src/class17/Code04_PrintAllPermutations.java @@ -0,0 +1,107 @@ +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(); + 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/src/class17/Code05_ReverseStackUsingRecursive.java b/src/class17/Code05_ReverseStackUsingRecursive.java new file mode 100644 index 0000000..731056a --- /dev/null +++ b/src/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/src/class17/Edge.java b/src/class17/Edge.java new file mode 100644 index 0000000..eacd58b --- /dev/null +++ b/src/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/src/class17/Graph.java b/src/class17/Graph.java new file mode 100644 index 0000000..f9314a2 --- /dev/null +++ b/src/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/src/class17/Node.java b/src/class17/Node.java new file mode 100644 index 0000000..1056ead --- /dev/null +++ b/src/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/src/class18/Code01_RobotWalk.java b/src/class18/Code01_RobotWalk.java new file mode 100644 index 0000000..f9ee3f2 --- /dev/null +++ b/src/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/src/class18/Code02_CardsInLine.java b/src/class18/Code02_CardsInLine.java new file mode 100644 index 0000000..919a4cb --- /dev/null +++ b/src/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/src/class19/Code01_Knapsack.java b/src/class19/Code01_Knapsack.java new file mode 100644 index 0000000..ab355a2 --- /dev/null +++ b/src/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/src/class19/Code02_ConvertToLetterString.java b/src/class19/Code02_ConvertToLetterString.java new file mode 100644 index 0000000..224375d --- /dev/null +++ b/src/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/src/class19/Code03_StickersToSpellWord.java b/src/class19/Code03_StickersToSpellWord.java new file mode 100644 index 0000000..90b3a4d --- /dev/null +++ b/src/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/src/class19/Code04_LongestCommonSubsequence.java b/src/class19/Code04_LongestCommonSubsequence.java new file mode 100644 index 0000000..5e49551 --- /dev/null +++ b/src/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/src/class20/Code01_PalindromeSubsequence.java b/src/class20/Code01_PalindromeSubsequence.java new file mode 100644 index 0000000..dd38429 --- /dev/null +++ b/src/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/src/class20/Code02_HorseJump.java b/src/class20/Code02_HorseJump.java new file mode 100644 index 0000000..f96f4a1 --- /dev/null +++ b/src/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/src/class20/Code03_Coffee.java b/src/class20/Code03_Coffee.java new file mode 100644 index 0000000..eb2b620 --- /dev/null +++ b/src/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/src/class21/Code01_MinPathSum.java b/src/class21/Code01_MinPathSum.java new file mode 100644 index 0000000..c537438 --- /dev/null +++ b/src/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/src/class21/Code02_CoinsWayEveryPaperDifferent.java b/src/class21/Code02_CoinsWayEveryPaperDifferent.java new file mode 100644 index 0000000..a162b06 --- /dev/null +++ b/src/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/src/class21/Code03_CoinsWayNoLimit.java b/src/class21/Code03_CoinsWayNoLimit.java new file mode 100644 index 0000000..7759d9c --- /dev/null +++ b/src/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/src/class21/Code04_CoinsWaySameValueSamePapper.java b/src/class21/Code04_CoinsWaySameValueSamePapper.java new file mode 100644 index 0000000..f45c919 --- /dev/null +++ b/src/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/src/class21/Code05_BobDie.java b/src/class21/Code05_BobDie.java new file mode 100644 index 0000000..439fffe --- /dev/null +++ b/src/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/src/class22/Code01_KillMonster.java b/src/class22/Code01_KillMonster.java new file mode 100644 index 0000000..77fae7b --- /dev/null +++ b/src/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/src/class22/Code02_MinCoinsNoLimit.java b/src/class22/Code02_MinCoinsNoLimit.java new file mode 100644 index 0000000..06e4843 --- /dev/null +++ b/src/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/src/class22/Code03_SplitNumber.java b/src/class22/Code03_SplitNumber.java new file mode 100644 index 0000000..23193bc --- /dev/null +++ b/src/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/src/class23/Code01_SplitSumClosed.java b/src/class23/Code01_SplitSumClosed.java new file mode 100644 index 0000000..6f82c49 --- /dev/null +++ b/src/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/src/class23/Code02_SplitSumClosedSizeHalf.java b/src/class23/Code02_SplitSumClosedSizeHalf.java new file mode 100644 index 0000000..0da40fb --- /dev/null +++ b/src/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/src/class23/Code03_NQueens.java b/src/class23/Code03_NQueens.java new file mode 100644 index 0000000..8d1548a --- /dev/null +++ b/src/class23/Code03_NQueens.java @@ -0,0 +1,97 @@ +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.... 后续有多少合法的方法数 + + /** + * + * @param i:表示我现在要决定放置的皇后的位置。 + * @param record:记录每一个皇后放的位置 + * @param n:总共要放多少个皇后 + * @return + */ + 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/src/class24/Code01_SlidingWindowMaxArray.java b/src/class24/Code01_SlidingWindowMaxArray.java new file mode 100644 index 0000000..d115951 --- /dev/null +++ b/src/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/src/class24/Code02_AllLessNumSubArray.java b/src/class24/Code02_AllLessNumSubArray.java new file mode 100644 index 0000000..7bbd5de --- /dev/null +++ b/src/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/src/class24/Code03_GasStation.java b/src/class24/Code03_GasStation.java new file mode 100644 index 0000000..b80906f --- /dev/null +++ b/src/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/src/class24/Code04_MinCoinsOnePaper.java b/src/class24/Code04_MinCoinsOnePaper.java new file mode 100644 index 0000000..cfda95a --- /dev/null +++ b/src/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/src/class25/Code01_MonotonousStack.java b/src/class25/Code01_MonotonousStack.java new file mode 100644 index 0000000..b823a30 --- /dev/null +++ b/src/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/src/class25/Code02_AllTimesMinToMax.java b/src/class25/Code02_AllTimesMinToMax.java new file mode 100644 index 0000000..2f26ac1 --- /dev/null +++ b/src/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/src/class25/Code03_LargestRectangleInHistogram.java b/src/class25/Code03_LargestRectangleInHistogram.java new file mode 100644 index 0000000..d2d1ce5 --- /dev/null +++ b/src/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/src/class25/Code04_MaximalRectangle.java b/src/class25/Code04_MaximalRectangle.java new file mode 100644 index 0000000..e2b9d37 --- /dev/null +++ b/src/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/src/class25/Code05_CountSubmatricesWithAllOnes.java b/src/class25/Code05_CountSubmatricesWithAllOnes.java new file mode 100644 index 0000000..f24d02e --- /dev/null +++ b/src/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/src/class26/Code01_SumOfSubarrayMinimums.java b/src/class26/Code01_SumOfSubarrayMinimums.java new file mode 100644 index 0000000..343247b --- /dev/null +++ b/src/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/src/class26/Code02_FibonacciProblem.java b/src/class26/Code02_FibonacciProblem.java new file mode 100644 index 0000000..248c731 --- /dev/null +++ b/src/class26/Code02_FibonacciProblem.java @@ -0,0 +1,186 @@ +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 = muliMatrix(res, t); + } + t = muliMatrix(t, t); + } + return res; + } + + // 两个矩阵乘完之后的结果返回 + public static int[][] muliMatrix(int[][] m1, int[][] m2) { + int[][] res = new int[m1.length][m2[0].length]; + for (int i = 0; i < m1.length; i++) { + for (int j = 0; j < m2[0].length; j++) { + for (int k = 0; k < m2.length; k++) { + res[i][j] += m1[i][k] * m2[k][j]; + } + } + } + return res; + } + + 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/src/class26/Code03_ZeroLeftOneStringNumber.java b/src/class26/Code03_ZeroLeftOneStringNumber.java new file mode 100644 index 0000000..b21906e --- /dev/null +++ b/src/class26/Code03_ZeroLeftOneStringNumber.java @@ -0,0 +1,109 @@ +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 = muliMatrix(res, tmp); + } + tmp = muliMatrix(tmp, tmp); + } + return res; + } + + public static int[][] muliMatrix(int[][] m1, int[][] m2) { + int[][] res = new int[m1.length][m2[0].length]; + for (int i = 0; i < m1.length; i++) { + for (int j = 0; j < m2[0].length; j++) { + for (int k = 0; k < m2.length; k++) { + res[i][j] += m1[i][k] * m2[k][j]; + } + } + } + return res; + } + + 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/src/class27/Code01_KMP.java b/src/class27/Code01_KMP.java new file mode 100644 index 0000000..68a51de --- /dev/null +++ b/src/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/src/class27/Code02_TreeEqual.java b/src/class27/Code02_TreeEqual.java new file mode 100644 index 0000000..657373a --- /dev/null +++ b/src/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/src/class27/Code03_IsRotation.java b/src/class27/Code03_IsRotation.java new file mode 100644 index 0000000..605cb81 --- /dev/null +++ b/src/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/src/class28/Code01_Manacher.java b/src/class28/Code01_Manacher.java new file mode 100644 index 0000000..c75975f --- /dev/null +++ b/src/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/src/class28/Code02_AddShortestEnd.java b/src/class28/Code02_AddShortestEnd.java new file mode 100644 index 0000000..00d961c --- /dev/null +++ b/src/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/src/class29/Code01_FindMinKth.java b/src/class29/Code01_FindMinKth.java new file mode 100644 index 0000000..e317550 --- /dev/null +++ b/src/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/src/class29/Code02_MaxTopK.java b/src/class29/Code02_MaxTopK.java new file mode 100644 index 0000000..b300171 --- /dev/null +++ b/src/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/src/class29/Code03_ReservoirSampling.java b/src/class29/Code03_ReservoirSampling.java new file mode 100644 index 0000000..c2ce9e3 --- /dev/null +++ b/src/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/src/class30/Code01_MorrisTraversal.java b/src/class30/Code01_MorrisTraversal.java new file mode 100644 index 0000000..2e3a4a4 --- /dev/null +++ b/src/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/src/class30/Code05_MinHeight.java b/src/class30/Code05_MinHeight.java new file mode 100644 index 0000000..9814825 --- /dev/null +++ b/src/class30/Code05_MinHeight.java @@ -0,0 +1,118 @@ +package class30; + +public class Code05_MinHeight { + + public static class Node { + public int val; + public Node left; + public Node right; + + public Node(int x) { + val = x; + } + } + + public static int minHeight1(Node head) { + if (head == null) { + return 0; + } + return p(head); + } + + // 返回x为头的树,最小深度是多少 + public static int p(Node 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 minHeight2(Node head) { + if (head == null) { + return 0; + } + Node cur = head; + Node 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; + } + + // 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 treeLevel = 7; + int nodeMaxValue = 5; + int testTimes = 100000; + System.out.println("test begin"); + for (int i = 0; i < testTimes; i++) { + Node head = generateRandomBST(treeLevel, nodeMaxValue); + int ans1 = minHeight1(head); + int ans2 = minHeight2(head); + if (ans1 != ans2) { + System.out.println("Oops!"); + } + } + System.out.println("test finish!"); + + } + +} diff --git a/src/class31/Code01_SegmentTree.java b/src/class31/Code01_SegmentTree.java new file mode 100644 index 0000000..282c20a --- /dev/null +++ b/src/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/src/class31/Code02_FallingSquares.java b/src/class31/Code02_FallingSquares.java new file mode 100644 index 0000000..2f3b2a2 --- /dev/null +++ b/src/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/src/class32/Code01_IndexTree.java b/src/class32/Code01_IndexTree.java new file mode 100644 index 0000000..4c4bdab --- /dev/null +++ b/src/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/src/class32/Code02_IndexTree2D.java b/src/class32/Code02_IndexTree2D.java new file mode 100644 index 0000000..12bb6e2 --- /dev/null +++ b/src/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/src/class32/Code03_AC1.java b/src/class32/Code03_AC1.java new file mode 100644 index 0000000..a6874e5 --- /dev/null +++ b/src/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/src/class32/Code04_AC2.java b/src/class32/Code04_AC2.java new file mode 100644 index 0000000..e684de3 --- /dev/null +++ b/src/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/src/class33/Hash.java b/src/class33/Hash.java new file mode 100644 index 0000000..18eb58a --- /dev/null +++ b/src/class33/Hash.java @@ -0,0 +1,48 @@ +//package class33; +// +//import java.security.MessageDigest; +//import java.security.NoSuchAlgorithmException; +//import java.security.Security; +// +//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 DatatypeConerter.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/src/class34/ReadMe.java b/src/class34/ReadMe.java new file mode 100644 index 0000000..16e42b9 --- /dev/null +++ b/src/class34/ReadMe.java @@ -0,0 +1,2 @@ +// 本章并无code,因为资源限制类题目输入需要的条件较多并且真的实现代码量巨大 +// 面试中这类题目出现也就和面试官聊解法,不会有代码实现的要求 \ No newline at end of file diff --git a/src/class35/Code01_AVLTreeMap.java b/src/class35/Code01_AVLTreeMap.java new file mode 100644 index 0000000..caafd46 --- /dev/null +++ b/src/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/src/class36/Code01_SizeBalancedTreeMap.java b/src/class36/Code01_SizeBalancedTreeMap.java new file mode 100644 index 0000000..5077b7a --- /dev/null +++ b/src/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/src/class36/Code02_SkipListMap.java b/src/class36/Code02_SkipListMap.java new file mode 100644 index 0000000..a450e78 --- /dev/null +++ b/src/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/src/class37/Code01_CountofRangeSum.java b/src/class37/Code01_CountofRangeSum.java new file mode 100644 index 0000000..d79fa02 --- /dev/null +++ b/src/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/src/class37/Code02_SlidingWindowMedian.java b/src/class37/Code02_SlidingWindowMedian.java new file mode 100644 index 0000000..2f3fa63 --- /dev/null +++ b/src/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/src/class37/Code03_AddRemoveGetIndexGreat.java b/src/class37/Code03_AddRemoveGetIndexGreat.java new file mode 100644 index 0000000..2c2436a --- /dev/null +++ b/src/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/src/class37/Code04_QueueReconstructionByHeight.java b/src/class37/Code04_QueueReconstructionByHeight.java new file mode 100644 index 0000000..2513402 --- /dev/null +++ b/src/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/src/class37/Compare.java b/src/class37/Compare.java new file mode 100644 index 0000000..5bbfd0d --- /dev/null +++ b/src/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/src/class38/Code01_AppleMinBags.java b/src/class38/Code01_AppleMinBags.java new file mode 100644 index 0000000..e1b5f21 --- /dev/null +++ b/src/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/src/class38/Code02_EatGrass.java b/src/class38/Code02_EatGrass.java new file mode 100644 index 0000000..8b400fb --- /dev/null +++ b/src/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/src/class38/Code03_MSumToN.java b/src/class38/Code03_MSumToN.java new file mode 100644 index 0000000..50c72be --- /dev/null +++ b/src/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/src/class38/Code04_MoneyProblem.java b/src/class38/Code04_MoneyProblem.java new file mode 100644 index 0000000..722c5a6 --- /dev/null +++ b/src/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/src/class39/Code01_SubsquenceMaxModM.java b/src/class39/Code01_SubsquenceMaxModM.java new file mode 100644 index 0000000..9890bf4 --- /dev/null +++ b/src/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/src/class39/Code02_SnacksWays.java b/src/class39/Code02_SnacksWays.java new file mode 100644 index 0000000..bb2c538 --- /dev/null +++ b/src/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/src/class39/Code02_SnacksWaysMain.java b/src/class39/Code02_SnacksWaysMain.java new file mode 100644 index 0000000..a224dc7 --- /dev/null +++ b/src/class39/Code02_SnacksWaysMain.java @@ -0,0 +1,125 @@ +//不要拷贝包信息的内容 +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_SnacksWaysMain { + + 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/src/class39/Code03_10Ways.java b/src/class39/Code03_10Ways.java new file mode 100644 index 0000000..1afffb7 --- /dev/null +++ b/src/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/src/class39/Code04_DifferentBTNum.java b/src/class39/Code04_DifferentBTNum.java new file mode 100644 index 0000000..3c48911 --- /dev/null +++ b/src/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/src/class39/IsSum.java b/src/class39/IsSum.java new file mode 100644 index 0000000..9ff7a3c --- /dev/null +++ b/src/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/src/class40/Code01_LongestSumSubArrayLengthInPositiveArray.java b/src/class40/Code01_LongestSumSubArrayLengthInPositiveArray.java new file mode 100644 index 0000000..a2389fa --- /dev/null +++ b/src/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/src/class40/Code02_LongestSumSubArrayLength.java b/src/class40/Code02_LongestSumSubArrayLength.java new file mode 100644 index 0000000..a89fb14 --- /dev/null +++ b/src/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/src/class40/Code03_LongestLessSumSubArrayLength.java b/src/class40/Code03_LongestLessSumSubArrayLength.java new file mode 100644 index 0000000..c4ebc56 --- /dev/null +++ b/src/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/src/class40/Code04_AvgLessEqualValueLongestSubarray.java b/src/class40/Code04_AvgLessEqualValueLongestSubarray.java new file mode 100644 index 0000000..c56eef9 --- /dev/null +++ b/src/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/src/class40/Code05_PrintMatrixSpiralOrder.java b/src/class40/Code05_PrintMatrixSpiralOrder.java new file mode 100644 index 0000000..cb3f80c --- /dev/null +++ b/src/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/src/class40/Code06_RotateMatrix.java b/src/class40/Code06_RotateMatrix.java new file mode 100644 index 0000000..4e9534f --- /dev/null +++ b/src/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/src/class40/Code07_ZigZagPrintMatrix.java b/src/class40/Code07_ZigZagPrintMatrix.java new file mode 100644 index 0000000..01b9921 --- /dev/null +++ b/src/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/src/class40/Code08_PrintStar.java b/src/class40/Code08_PrintStar.java new file mode 100644 index 0000000..77d41ef --- /dev/null +++ b/src/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/src/class41/Code01_BestSplitForAll.java b/src/class41/Code01_BestSplitForAll.java new file mode 100644 index 0000000..a4eddb9 --- /dev/null +++ b/src/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/src/class41/Code02_BestSplitForEveryPosition.java b/src/class41/Code02_BestSplitForEveryPosition.java new file mode 100644 index 0000000..d98b389 --- /dev/null +++ b/src/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/src/class41/Code03_StoneMerge.java b/src/class41/Code03_StoneMerge.java new file mode 100644 index 0000000..793d7d2 --- /dev/null +++ b/src/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/src/class41/Code04_SplitArrayLargestSum.java b/src/class41/Code04_SplitArrayLargestSum.java new file mode 100644 index 0000000..8d644e6 --- /dev/null +++ b/src/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/src/class42/Code01_PostOfficeProblem.java b/src/class42/Code01_PostOfficeProblem.java new file mode 100644 index 0000000..c6fa893 --- /dev/null +++ b/src/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/src/class42/Code02_ThrowChessPiecesProblem.java b/src/class42/Code02_ThrowChessPiecesProblem.java new file mode 100644 index 0000000..d996c70 --- /dev/null +++ b/src/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/src/class43/Code01_CanIWin.java b/src/class43/Code01_CanIWin.java new file mode 100644 index 0000000..608b1f1 --- /dev/null +++ b/src/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/src/class43/Code02_TSP.java b/src/class43/Code02_TSP.java new file mode 100644 index 0000000..bf984a6 --- /dev/null +++ b/src/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/src/class43/Code03_PavingTile.java b/src/class43/Code03_PavingTile.java new file mode 100644 index 0000000..c4bf363 --- /dev/null +++ b/src/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/src/class44/Code01_LastSubstringInLexicographicalOrder.java b/src/class44/Code01_LastSubstringInLexicographicalOrder.java new file mode 100644 index 0000000..27ac147 --- /dev/null +++ b/src/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/src/class44/DC3.java b/src/class44/DC3.java new file mode 100644 index 0000000..47e7601 --- /dev/null +++ b/src/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/src/class44/DC3_Algorithm.pdf b/src/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/src/class45/Code01_InsertS2MakeMostAlphabeticalOrder.java b/src/class45/Code01_InsertS2MakeMostAlphabeticalOrder.java new file mode 100644 index 0000000..0962781 --- /dev/null +++ b/src/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/src/class45/Code02_CreateMaximumNumber.java b/src/class45/Code02_CreateMaximumNumber.java new file mode 100644 index 0000000..1e9cc28 --- /dev/null +++ b/src/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/src/class45/Code03_LongestCommonSubstringConquerByHeight.java b/src/class45/Code03_LongestCommonSubstringConquerByHeight.java new file mode 100644 index 0000000..2b92fff --- /dev/null +++ b/src/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/src/class46/Code01_BurstBalloons.java b/src/class46/Code01_BurstBalloons.java new file mode 100644 index 0000000..71d2602 --- /dev/null +++ b/src/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/src/class46/Code02_RemoveBoxes.java b/src/class46/Code02_RemoveBoxes.java new file mode 100644 index 0000000..8db4d8f --- /dev/null +++ b/src/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/src/class46/Code03_DeleteAdjacentSameCharacter.java b/src/class46/Code03_DeleteAdjacentSameCharacter.java new file mode 100644 index 0000000..0b1e7f3 --- /dev/null +++ b/src/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/src/class46/Code04_MaxSumLengthNoMore.java b/src/class46/Code04_MaxSumLengthNoMore.java new file mode 100644 index 0000000..be50615 --- /dev/null +++ b/src/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/src/class46/Code05_HuffmanTree.java b/src/class46/Code05_HuffmanTree.java new file mode 100644 index 0000000..65b6be8 --- /dev/null +++ b/src/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/src/class47/Code01_StrangePrinter.java b/src/class47/Code01_StrangePrinter.java new file mode 100644 index 0000000..b7172db --- /dev/null +++ b/src/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/src/class47/Code02_RestoreWays.java b/src/class47/Code02_RestoreWays.java new file mode 100644 index 0000000..c9c583a --- /dev/null +++ b/src/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/src/class47/Code03_DinicAlgorithm.java b/src/class47/Code03_DinicAlgorithm.java new file mode 100644 index 0000000..efed284 --- /dev/null +++ b/src/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