Repository: guokaide/algorithm Branch: master Commit: 9bee14b08b06 Files: 167 Total size: 284.7 KB Directory structure: gitextract_6zdwcmqq/ ├── .gitignore ├── README.md ├── algorithms/ │ └── src/ │ ├── array/ │ │ ├── BinarySearch.java │ │ ├── BinarySearchTest.java │ │ ├── GenericArray.java │ │ ├── MinNumberInRotatedArray.java │ │ └── MinNumberInRotatedArrayTest.java │ ├── joseph/ │ │ └── Joseph.java │ ├── linkedlist/ │ │ ├── FindMidNode.java │ │ ├── FindMidNodeTest.java │ │ ├── SingleLinkedList.java │ │ └── SingleLinkedListTest.java │ ├── lru/ │ │ ├── LRU.java │ │ └── LRUTest.java │ ├── queue/ │ │ ├── ArrayQueue.java │ │ ├── CircularQueue.java │ │ ├── ListQueue.java │ │ └── Queue.java │ └── stack/ │ ├── ArrayStack.java │ ├── DynamicArrayStack.java │ ├── ListStack.java │ ├── SampleBrowser.java │ └── Stack.java ├── appendix/ │ ├── instructions.md │ └── 刷题笔记.md ├── leetcode/ │ └── src/ │ ├── linkedlistcycle_141/ │ │ └── LinkedListCycle.java │ ├── lrucache_146/ │ │ └── LRUCache.java │ ├── mergetwosortedlist_21/ │ │ └── Merge2SortedLists.java │ ├── middleofthelinkedlist_876/ │ │ └── MiddleNode.java │ ├── palindromelinkedlist_234/ │ │ └── PalindromeLinkedList.java │ ├── removenthnodefromendoflist_19/ │ │ └── RemoveNthNodeFromEndOfList.java │ ├── reverselinkedlist_206/ │ │ └── ReverseList.java │ ├── threesum_015/ │ │ └── ThreeSum.java │ └── twosum_001/ │ └── TwoSum.java ├── offer/ │ └── src/ │ └── com/ │ └── ex/ │ ├── offer/ │ │ ├── Attention.txt │ │ ├── Ex_03_FindDuplicatedNumInArray.java │ │ ├── Ex_03_FindDuplicatedNumInWithoutChangeArray.java │ │ ├── Ex_04_FindNumIn2VArray.java │ │ ├── Ex_05_ReplaceSpace.java │ │ ├── Ex_06_PrintListFromTailToHead.java │ │ ├── Ex_07_ReConstructBT.java │ │ ├── Ex_08_DescendantNode.java │ │ ├── Ex_08_GetNextNodeInBT.java │ │ ├── Ex_09_QueueWithTwoStack.java │ │ ├── Ex_09_StackWithTwoQueue.java │ │ ├── Ex_10_Fibonacci.java │ │ ├── Ex_10_JumpFloor.java │ │ ├── Ex_10_JumpFloorII.java │ │ ├── Ex_10_RectCover.java │ │ ├── Ex_11_MinNumOfRotatingArray.java │ │ ├── Ex_11_SortAges.java │ │ ├── Ex_12_HasPathInMatrix.java │ │ ├── Ex_13_MovingCount.java │ │ ├── Ex_14_MaxProductionAfterCutting.java │ │ ├── Ex_15_Count1.java │ │ ├── Ex_15_CountDifferInMN.java │ │ ├── Ex_15_IsPowerOf2.java │ │ ├── Ex_16_Power.java │ │ ├── Ex_17_AddTwoBigNumber.java │ │ ├── Ex_17_Print1ToMaxNDigits.java │ │ ├── Ex_18_DeleteDuplicatedNode.java │ │ ├── Ex_18_DeleteNodeInSList.java │ │ ├── Ex_19_Match.java │ │ ├── Ex_20_IsNumber.java │ │ ├── Ex_21_ReOrderArray.java │ │ ├── Ex_22_FindKthNodeToTail.java │ │ ├── Ex_22_FindMedianNodeInList.java │ │ ├── Ex_23_EntryNodeInList.java │ │ ├── Ex_24_ReverseSList.java │ │ ├── Ex_25_MergeList.java │ │ ├── Ex_26_IsSubTree.java │ │ ├── Ex_27_MirrorOfTree.java │ │ ├── Ex_28_SymmetricalTree.java │ │ ├── Ex_29_PrintMatrixWithClockWise.java │ │ ├── Ex_30_StackWithMinFunction.java │ │ ├── Ex_31_IsPopOrder.java │ │ ├── Ex_32_PrintTreeByLayer.java │ │ ├── Ex_32_PrintTreeWithZhi.java │ │ ├── Ex_32_TraverseTreeByLayer.java │ │ ├── Ex_33_VerifySequenceOfBST.java │ │ ├── Ex_34_FindPathInBT.java │ │ ├── Ex_34_FindPathInBT_1.java │ │ ├── Ex_34_FindPathInBT_2.java │ │ ├── Ex_35_CloneList.java │ │ ├── Ex_36_ConvertBetweenBSTAndDList.java │ │ ├── Ex_37_SerializeBT.java │ │ ├── Ex_38_EightQueen.java │ │ ├── Ex_38_StringCombination.java │ │ ├── Ex_38_StringPermutation.java │ │ ├── Ex_39_KthSmallestNumInArrayWithoutSort.java │ │ ├── Ex_39_MoreThanHalfNum.java │ │ ├── Ex_40_GetLeastKNumbers.java │ │ ├── Ex_41_MedianInDataFlow.java │ │ ├── Ex_42_MaxSumOfSubArray.java │ │ ├── Ex_43_NumberOf1Between1AndN.java │ │ ├── Ex_44_ANumInArray.java │ │ ├── Ex_45_MinNumByConcatArray.java │ │ ├── Ex_46_ConvertStringToIP.java │ │ ├── Ex_46_TranslationFromIntToString.java │ │ ├── Ex_47_MaxGiftValue.java │ │ ├── Ex_48_LongestSubStringWithDuplication.java │ │ ├── Ex_49_UglyNumber.java │ │ ├── Ex_50_FirstNotRepeatedChar.java │ │ ├── Ex_50_FirstNotRepeatedCharInDataFlow.java │ │ ├── Ex_50_StringUtilsSolutions.java │ │ ├── Ex_51_InversePairs.java │ │ ├── Ex_51_InversePairs_BigData.java │ │ ├── Ex_52_FirstCommonNode.java │ │ ├── Ex_53_GetMissingNumber.java │ │ ├── Ex_53_GetNumberOfK.java │ │ ├── Ex_53_GetNumberSameAsIndex.java │ │ ├── Ex_54_KthNodeInBST.java │ │ ├── Ex_55_BalancedBT.java │ │ ├── Ex_55_DepthOfBT.java │ │ ├── Ex_56_AppearanceOnce.java │ │ ├── Ex_56_AppearanceOnce_Continued.java │ │ ├── Ex_57_FindContinuousSequence.java │ │ ├── Ex_57_FindNumbersWithSum.java │ │ ├── Ex_58_ROL.java │ │ ├── Ex_58_ReverseSentence.java │ │ ├── Ex_59_MaxNumOfSlidingWindow.java │ │ ├── Ex_59_QueueWithMax.java │ │ ├── Ex_60_ProbabilityOfS.java │ │ ├── Ex_61_ContinuousSequence.java │ │ ├── Ex_62_Josephus.java │ │ ├── Ex_63_MaxProfits.java │ │ ├── Ex_64_SumFrom1ToN.java │ │ ├── Ex_65_AddWithoutCarry.java │ │ ├── Ex_65_SwapNumWithoutTemp.java │ │ ├── Ex_66_ConstructMultiplyArray.java │ │ ├── Ex_67_StringToInt.java │ │ ├── Ex_68_LowestCommonAncestor.java │ │ ├── ListNode.java │ │ ├── Node.java │ │ ├── RandomListNode.java │ │ ├── TestUtils.java │ │ ├── TreeLinkNode.java │ │ └── TreeNode.java │ └── singleton/ │ ├── Singleton1.java │ ├── Singleton2.java │ ├── Singleton3.java │ ├── Singleton4.java │ ├── Singleton5.java │ └── singleton.md ├── practice/ │ ├── Final.md │ ├── README.md │ └── src/ │ └── io/ │ └── gkd/ │ ├── ListNode.java │ ├── Node.java │ ├── TreeNode.java │ ├── lectures/ │ │ ├── lecture04/ │ │ │ ├── Lc077_Combine.java │ │ │ └── Lc078_SubSets.java │ │ └── lecture05/ │ │ ├── Lc094_InOrderTraversal.java │ │ ├── Lc105_BuildTree.java │ │ ├── Lc236_LCA.java │ │ ├── Lc297_Codec.java │ │ ├── Lc429_LevelOrderNTree.java │ │ └── Lc589_PreOrderNTree.java │ ├── week01/ │ │ ├── Lc021_MergeTwoLists.java │ │ └── Lc066_PlusOne.java │ ├── week02/ │ │ ├── Lc146_LRUCache.java │ │ └── Lc697_FindShortestSubArray.java │ └── week03/ │ ├── Lc106_BuildTree.java │ ├── Lc210_FindOrder2.java │ └── NOTES.md ├── questions/ │ └── questions.md ├── solutions/ │ └── 剑指offer 题解.md └── summary/ └── algorithm.md ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ # Compiled class file *.class # Log file *.log # BlueJ files *.ctxt # Mobile Tools for Java (J2ME) .mtj.tmp/ # Package Files # *.jar *.war *.nar *.ear *.zip *.tar.gz *.rar # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* # idea *.idea *.iml target/ *.mvn *db # others *.extract .DS_Store ================================================ FILE: README.md ================================================ # [算法练级计划](https://mp.weixin.qq.com/s/6vuaECCmrxrchr5Hc11S5w) > **既然终要承受痛苦,那么尝试思考的痛总归比承受学习的苦更有意义。** 在正式开始之前,邀请你阅读「[The Key To Accelerating Your Coding Skills](http://blog.thefirehoseproject.com/posts/learn-to-code-and-be-self-reliant/)」,这篇文章将会告诉你如何快速有效地提高自己的编程能力。 ## Introduction **算法练级计划**以面试算法题目为线索,总结归纳面试涉及的算法知识点,整理LeetCode以及「剑指offer」出现的面试题目,在大量的LeetCode题目中梳理一个刷题脉络,让大家能够在有限的时间内,通过面试算法题目的练习,提高算法能力,更加有效地准备面试~ * 了解算法练级计划:[算法练级计划](https://mp.weixin.qq.com/s/6vuaECCmrxrchr5Hc11S5w) * 在开启算法练级计划之前,需要同学们了解至少一门编程语言,我在这里推荐Java语言,Java学习参见:[Java练级攻略](https://mp.weixin.qq.com/s/i-j27vWXPS4kGmxO7i9p9w),Java学习资源参见:[Java练级资源包](https://mp.weixin.qq.com/s/BomvPTaoAV1rDI6X8yDs-w) * 准备校招面试时,参见校招攻略:[2019秋招经验谈](https://mp.weixin.qq.com/s/iVHSbojhMSIL37K-UbM41A) ## *1*、 Data Structure and Algorithms ### 目录 [Data Structure and Algorithms](https://github.com/guokaide/algorithm/blob/master/summary/algorithm.md) **数据结构与算法的要点总结**,包括数组、链表、栈、队列、二分查找、排序...不断更新中(每周至少更新一个知识点)... ### 正文 * [数组](https://github.com/guokaide/algorithm/blob/master/summary/algorithm.md#%E6%95%B0%E7%BB%84) * [链表](https://github.com/guokaide/algorithm/blob/master/summary/algorithm.md#%E9%93%BE%E8%A1%A8) * [栈](https://github.com/guokaide/algorithm/blob/master/summary/algorithm.md#%E6%A0%88) * [队列](https://github.com/guokaide/algorithm/blob/master/summary/algorithm.md#%E9%98%9F%E5%88%97) * [二分查找算法](https://github.com/guokaide/algorithm/blob/master/summary/algorithm.md#%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE) * [排序算法](https://github.com/guokaide/algorithm/blob/master/summary/algorithm.md#%E6%8E%92%E5%BA%8F) * [设计思想](https://github.com/guokaide/algorithm/blob/master/summary/algorithm.md#%E8%AE%BE%E8%AE%A1%E6%80%9D%E6%83%B3) * [缓存](https://github.com/guokaide/algorithm/blob/master/summary/algorithm.md#%E7%BC%93%E5%AD%98) ## *2*、 Questions ### 目录 [Questions](https://github.com/guokaide/algorithm/blob/master/questions/questions.md) > **Talk is cheap, show me the code.** 算法练级计划核心部分,通过coding,提高代码能力,掌握数据结构与算法。 所有问题均选自与校招面试真题,包括问答题,算法题,手撕代码题等。 大家可以点击更新列表中的**Title**列的题目,开始算法练级挑战,加油~ *ps.不断更新中(每周至少更新3个问题)*... ### 正文 #### [数组](https://github.com/guokaide/algorithm/blob/master/questions/questions.md#%E6%95%B0%E7%BB%84) |#|Title|Finished| |:---:|:---|:---| |001|[数组与泛型动态数组](https://github.com/guokaide/algorithm/blob/master/questions/questions.md#1-%E6%95%B0%E7%BB%84%E4%B8%8E%E6%B3%9B%E5%9E%8B%E5%8A%A8%E6%80%81%E6%95%B0%E7%BB%84)|Yes| |002|[1000万整数中查找某个数](https://github.com/guokaide/algorithm/blob/master/questions/questions.md#2-1000%E4%B8%87%E6%95%B4%E6%95%B0%E4%B8%AD%E6%9F%A5%E6%89%BE%E6%9F%90%E4%B8%AA%E6%95%B0)|Yes| |003|[约瑟夫问题](https://github.com/guokaide/algorithm/blob/master/questions/questions.md#3-%E7%BA%A6%E7%91%9F%E5%A4%AB%E9%97%AE%E9%A2%98)|Yes| #### [链表](https://github.com/guokaide/algorithm/blob/master/questions/questions.md#%E9%93%BE%E8%A1%A8) |#|Title|Finished| |:---:|:---|:---| |001|[链表与数组](https://github.com/guokaide/algorithm/blob/master/questions/questions.md#1-%E9%93%BE%E8%A1%A8%E4%B8%8E%E6%95%B0%E7%BB%84)|Yes| |002|[Reverse Linked List](https://github.com/guokaide/algorithm/blob/master/questions/questions.md#2-reverse-linked-list)|Yes| |003|[Middle of the Linked List](https://github.com/guokaide/algorithm/blob/master/questions/questions.md#3-middle-of-the-linked-list)|Yes| |004|[LRU Cache](https://github.com/guokaide/algorithm/blob/master/questions/questions.md#4-lru-cache)|Yes| |005|[Palindrome Linked List](https://github.com/guokaide/algorithm/blob/master/questions/questions.md#5-palindrome-linked-list)|Yes| |006|[Linked List Cycle](https://github.com/guokaide/algorithm/blob/master/questions/questions.md#6-linked-list-cycle)|Yes| |007|[Merge Two Sorted Lists](https://github.com/guokaide/algorithm/blob/master/questions/questions.md#7-merge-two-sorted-lists)|Yes| |008|[Remove Nth Node From End of List](https://github.com/guokaide/algorithm/blob/master/questions/questions.md#8-remove-nth-node-from-end-of-list)|Yes| #### [栈](https://github.com/guokaide/algorithm/blob/master/questions/questions.md#%E6%A0%88) |#|Title|Finished| |:---:|:---|:---| |001|[Implement Stack using Array](https://github.com/guokaide/algorithm/blob/master/questions/questions.md#1-implement-stack-using-array)|Yes| |002|[Implement Stack using Linked List](https://github.com/guokaide/algorithm/blob/master/questions/questions.md#2-implement-stack-using-linked-list)|Yes| |003|[Implement Stack using Queues](https://github.com/guokaide/algorithm/blob/master/questions/questions.md#3-implement-stack-using-queues)|No| |004|[Implement Queue using Stacks](https://github.com/guokaide/algorithm/blob/master/questions/questions.md#4-implement-queue-using-stacks)|No| |005|[Valid Parentheses](https://github.com/guokaide/algorithm/blob/master/questions/questions.md#5-valid-parentheses)|No| |006|[Min Stack](https://github.com/guokaide/algorithm/blob/master/questions/questions.md#6-min-stack)|No| |007|[Implement the Forward and Backward Functions of the Browser](https://github.com/guokaide/algorithm/blob/master/questions/questions.md#7-implement-the-forward-and-backward-functions-of-the-browser)|No| #### [队列](https://github.com/guokaide/algorithm/blob/master/questions/questions.md#%E9%98%9F%E5%88%97) |#|Title|Finished| |:---:|:---|:---| |001|[Implement Queue using Array](https://github.com/guokaide/algorithm/blob/master/questions/questions.md#1-implement-queue-using-array)|Yes| |002|[Implement Queue using Linked List](https://github.com/guokaide/algorithm/blob/master/questions/questions.md#2-implement-queue-using-linked-list)|Yes| |003|[Design Circular Queue](https://github.com/guokaide/algorithm/blob/master/questions/questions.md#3-design-circular-queue)|No| |004|[Design Circular Deque](https://github.com/guokaide/algorithm/blob/master/questions/questions.md#4-design-circular-deque)|No| #### [二分查找算法](https://github.com/guokaide/algorithm/blob/master/questions/questions.md#%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE%E7%AE%97%E6%B3%95) |#|Title|Finished| |:---:|:---|:---| |001|[二分查找算法](https://github.com/guokaide/algorithm/blob/master/questions/questions.md#1-%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE%E7%AE%97%E6%B3%95)|Yes| |002|[二分查找算法变形问题](https://github.com/guokaide/algorithm/blob/master/questions/questions.md#2-%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE%E7%AE%97%E6%B3%95%E5%8F%98%E5%BD%A2%E9%97%AE%E9%A2%98)|Yes| |003|[旋转数组中的最小值](https://github.com/guokaide/algorithm/blob/master/questions/questions.md#3-%E6%97%8B%E8%BD%AC%E6%95%B0%E7%BB%84%E4%B8%AD%E7%9A%84%E6%9C%80%E5%B0%8F%E5%80%BC)|Yes| |004|[Sqrt(x)](https://github.com/guokaide/algorithm/blob/master/questions/questions.md#4-sqrtx)|No| #### [递归](https://github.com/guokaide/algorithm/blob/master/questions/questions.md#%E9%80%92%E5%BD%92) |#|Title|Finished| |:---:|:---|:---| |001|[Pow(x, n)](https://github.com/guokaide/algorithm/blob/master/questions/questions.md#1-powx-n)|No| ## *3*、 leetcode |#|Title|Difficulty|Solution| |:---:|:---|:---|:---| |876|[Middle of the Linked List](https://leetcode.com/problems/middle-of-the-linked-list/)|Easy|[Java](https://github.com/guokaide/algorithm/tree/master/leetcode/src/middleofthelinkedlist_876)| |234|[Palindrome Linked List](https://leetcode.com/problems/palindrome-linked-list/)|Easy|[Java](https://github.com/guokaide/algorithm/tree/master/leetcode/src/palindromelinkedlist_234)| |206|[Reverse Linked List](https://leetcode.com/problems/reverse-linked-list/)|Easy|[Java](https://github.com/guokaide/algorithm/tree/master/leetcode/src/reverselinkedlist_206)| |146|[LRU Cache](https://leetcode.com/problems/lru-cache/description/) |Hard|[Java](https://github.com/guokaide/algorithm/tree/master/leetcode/src/lrucache_146)| |141|[Linked List Cycle](https://leetcode.com/problems/linked-list-cycle/)|Easy|[Java](https://github.com/guokaide/algorithm/tree/master/leetcode/src/linkedlistcycle_141)| |021|[Merge Two Sorted Lists](https://leetcode.com/problems/merge-two-sorted-lists/)|Easy|[Java](https://github.com/guokaide/algorithm/tree/master/leetcode/src/mergetwosortedlist_21)| |019|[Remove Nth Node From End of List](https://leetcode.com/problems/remove-nth-node-from-end-of-list/)|Medium|[Java](https://github.com/guokaide/algorithm/tree/master/leetcode/src/removenthnodefromendoflist_19)| ## *4*、 剑指offer * [「剑指offer题解」(Java版)](https://github.com/guokaide/algorithm/blob/master/solutions/%E5%89%91%E6%8C%87offer%20%E9%A2%98%E8%A7%A3.md) 「剑指offer」的Java版本题解,不断更新中... * 更新列表 |#|Title|Solution| |:---:|:---|:---| |002|[实现单例模式](https://github.com/guokaide/algorithm/blob/master/solutions/%E5%89%91%E6%8C%87offer%20%E9%A2%98%E8%A7%A3.md#2-%E5%AE%9E%E7%8E%B0%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F)|[Java](https://github.com/guokaide/algorithm/tree/master/offer/src/com/ex/singleton)| |003|[数组中重复的数字](https://github.com/guokaide/algorithm/blob/master/solutions/%E5%89%91%E6%8C%87offer%20%E9%A2%98%E8%A7%A3.md#3-%E6%95%B0%E7%BB%84%E4%B8%AD%E9%87%8D%E5%A4%8D%E7%9A%84%E6%95%B0%E5%AD%97)|[Java](https://github.com/guokaide/algorithm/blob/master/offer/src/com/ex/offer/Ex_03_FindDuplicatedNumInArray.java)| ## Appendix #### 1. 如何进行代码测试? * [题目:二分查找算法](https://github.com/guokaide/algorithm/blob/master/questions/questions.md#1-%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE%E7%AE%97%E6%B3%95) * [测试:二分查找算法测试示例](https://github.com/guokaide/algorithm/blob/master/algorithms/src/array/BinarySearchTest.java) #### 2.刷题笔记 * [戳我](../master/appendix/刷题笔记.md) ## 欢迎大家关注我的公众号: 【算法修炼笔记】 主要分享校招笔试面试经验、数据结构与算法、计算机网络、操作系统、数据库以及法律知识等,完成从码农到工程师的进阶之路~ ![algo](https://github.com/guokaide/algorithm/blob/master/pictures/algo.jpg) ================================================ FILE: algorithms/src/array/BinarySearch.java ================================================ package array; public class BinarySearch { // 最简单的二分查找算法:针对有序无重复元素数组 // 迭代 public static int binarySearch(int[] array, int target) { if (array == null) return -1; int lo = 0; int hi = array.length-1; // 始终在[lo, hi]范围内查找target while (lo <= hi) { int mid = lo + ((hi - lo) >> 1); // 这里若是 (lo + hi) / 2 有可能造成整型溢出 if (array[mid] > target) { hi = mid - 1; } else if (array[mid] < target) { lo = mid + 1; } else { return mid; } } return -1; } // 递归 public static int binarySearchRecur(int[] array, int target) { if (array == null) return -1; return bs(array, target, 0, array.length-1); } private static int bs(int[] array, int target, int lo, int hi) { if (lo <= hi) { int mid = lo + ((hi - lo) >> 1); if (array[mid] > target) { return bs(array, target, lo, mid-1); } else if (array[mid] < target) { return bs(array, target, mid+1, hi); } else { return mid; } } return -1; } // 二分查找变形问题 // 1. 查找第一个值等于给定值的元素 public static int bsFirst(int[] array, int target) { int lo = 0; int hi = array.length - 1; while (lo <= hi) { int mid = lo + ((hi - lo) >> 1); if (array[mid] > target) { hi = mid - 1; } else if (array[mid] < target) { lo = mid + 1; } else { if (mid == lo || array[mid-1] != array[mid]) { return mid; } else { hi = mid - 1; } } } return -1; } // 2. 查找最后一个值等于给定值的元素 public static int bsLast(int[] array, int target) { int lo = 0; int hi = array.length - 1; while (lo <= hi) { int mid = lo + ((hi - lo) >> 1); if (array[mid] > target) { hi = mid - 1; } else if (array[mid] < target) { lo = mid + 1; } else { if (mid == hi || array[mid] != array[mid+1]) { return mid; } else { lo = mid + 1; } } } return -1; } // 3. 查找第一个大于等于给定值的元素 public static int bsFistGE(int[] array, int target) { int lo = 0; int hi = array.length - 1; while (lo <= hi) { int mid = lo + ((hi - lo) >> 1); if (array[mid] >= target) { if (mid == 0 || array[mid-1] < target) { return mid; } else { hi = mid - 1; } } else { lo = mid + 1; } } return -1; } // 4. 查找最后一个小于等于给定值的元素 public static int bsLastLE(int[] array, int target) { int lo = 0; int hi = array.length - 1; while (lo <= hi) { int mid = lo + ((hi - lo) >> 1); if (array[mid] <= target) { if (mid == hi || array[mid+1] > target) { return mid; } else { lo = mid + 1; } } else { hi = mid - 1; } } return -1; } } ================================================ FILE: algorithms/src/array/BinarySearchTest.java ================================================ package array; import org.junit.Assert; import org.junit.Test; public class BinarySearchTest { // 测试示例(完备) @Test public void testBinarySearch() { int notFound = -1; int target = 6; // 测试数组为null int[] arr = null; Assert.assertEquals(notFound, BinarySearch.binarySearch(arr, target)); // 测试数组为空 int[] arr1 = new int[0]; Assert.assertEquals(notFound, BinarySearch.binarySearch(arr1, target)); // 功能测试 int[] arr2 = new int[] {3,4,5,6,8,9,12,20,25}; // 功能测试:not Found int target1 = 30; Assert.assertEquals(notFound, BinarySearch.binarySearch(arr2, target1)); // 功能测试:Found int target2 = 5; int expected = 2; Assert.assertEquals(expected, BinarySearch.binarySearch(arr2, target2)); } @Test public void testBinarySearchRecur() { int notFound = -1; int target = 6; // 测试数组为null int[] arr = null; Assert.assertEquals(notFound, BinarySearch.binarySearchRecur(arr, target)); // 测试数组为空 int[] arr1 = new int[0]; Assert.assertEquals(notFound, BinarySearch.binarySearchRecur(arr1, target)); // 功能测试 int[] arr2 = new int[] {3,4,5,6,8,9,12,20,25}; // 功能测试:not Found int target1 = 30; Assert.assertEquals(notFound, BinarySearch.binarySearchRecur(arr2, target1)); // 功能测试:Found int target2 = 5; int expected = 2; Assert.assertEquals(expected, BinarySearch.binarySearchRecur(arr2, target2)); } // 以下只进行功能测试 @Test public void testBsFirst() { int[] arr = new int[] {3,4,5,5,5,6,8,9,12,20,25}; int target = 5; int expected = 2; Assert.assertEquals(expected, BinarySearch.bsFirst(arr, target)); int target1 = 6; int expected1 = 5; Assert.assertEquals(expected1, BinarySearch.bsFirst(arr, target1)); int target2 = 30; int expected2 = -1; Assert.assertEquals(expected2, BinarySearch.bsFirst(arr, target2)); } @Test public void testBsLast() { int[] arr = new int[] {3,4,5,5,5,6,8,9,12,20,25}; int target = 5; int expected = 4; Assert.assertEquals(expected, BinarySearch.bsLast(arr, target)); int target1 = 6; int expected1 = 5; Assert.assertEquals(expected1, BinarySearch.bsLast(arr, target1)); int target2 = 30; int expected2 = -1; Assert.assertEquals(expected2, BinarySearch.bsLast(arr, target2)); } @Test public void testBsFirstGE() { int[] arr = new int[] {3,4,5,5,5,6,8,9,12,20,25}; int target = 5; int expected = 2; Assert.assertEquals(expected, BinarySearch.bsFistGE(arr, target)); int target1 = 7; int expected1 = 6; Assert.assertEquals(expected1, BinarySearch.bsFistGE(arr, target1)); int target2 = 30; int expected2 = -1; Assert.assertEquals(expected2, BinarySearch.bsFistGE(arr, target2)); } @Test public void testBsLastLE() { int[] arr = new int[] {3,4,5,5,5,6,8,9,12,20,25}; int target = 5; int expected = 4; Assert.assertEquals(expected, BinarySearch.bsLastLE(arr, target)); int target1 = 7; int expected1 = 5; Assert.assertEquals(expected1, BinarySearch.bsLastLE(arr, target1)); int target2 = -2; int expected2 = -1; Assert.assertEquals(expected2, BinarySearch.bsLastLE(arr, target2)); } } ================================================ FILE: algorithms/src/array/GenericArray.java ================================================ package array; /** * 泛型动态数组 * * @param * */ public class GenericArray { private T[] data; private int size; //数组当前元素个数,保证其可用范围为[0, size] // 根据传入容量,构造Array public GenericArray(int capacity) { this.data = (T[]) new Object[capacity]; this.size = 0; } // 无参构造方法,默认数组容量为10 public GenericArray() { this(10); } // 获取数组容量 public int getCapacity() { return data.length; } // 获取当前数组元素个数 public int getSize() { return size; } // 判断数组是否为空 public boolean isEmpty() { return size == 0; } // 修改 index 位置的元素 public void set(int index, T e) { checkIndex(index); data[index] = e; } // 获取对应 index 位置的元素 public T get(int index) { checkIndex(index); return data[index]; } // 查看数组是否包含元素e public boolean contains(T e) { for (int i = 0; i < size; i++) { if (data[i].equals(e)) { // 注意这里 return true; } } return false; } // 获取对应元素的下标, 未找到,返回 -1 public int find(T e) { for (int i = 0; i < size; i++) { if (data[i].equals(e)) { return i; } } return -1; } // 在 index 位置,插入元素e, 时间复杂度 O(m+n) public void add(int index, T e) { checkIndex(index); if (size == data.length) { resize(2 * size); } for (int i = size - 1; i >= index; i++) { data[i+1] = data[i]; } data[index] = e; size++; } // 向数组头插入元素 public void addFirst(T e) { add(0, e); } // 向数组尾插入元素 public void addLast(T e) { add(size, e); } // 删除 index 位置的元素,并返回 public T remove(int index) { checkIndexForRemove(index); T ret = data[index]; for (int i = index; i < size; i++) { data[i] = data[i+1]; } size--; // 缩小容量 if (size == data.length / 4 && data.length / 2 != 0) { resize(data.length / 2); } return ret; } // 删除第一个元素 public T removeFirst() { return remove(0); } // 删除末尾元素 public T removeLast() { return remove(size-1); } // 从数组中删除指定元素 public void removeElement(T e) { int index = find(e); if (index != -1) { remove(index); } } @Override public String toString() { StringBuilder builder = new StringBuilder(); builder.append(String.format("Array size = %d, capacity = %d \n", size, data.length)); builder.append('['); for (int i = 0; i < size; i++) { builder.append(data[i]); if (i != size - 1) { builder.append(", "); } } builder.append(']'); return builder.toString(); } // 扩容方法,时间复杂度 O(n) private void resize(int capacity) { T[] temp = (T[]) new Object[capacity]; for (int i = 0; i < data.length; i++) { temp[i] = data[i]; } data = temp; } private void checkIndex(int index) { if (index < 0 || index > size) { throw new IllegalArgumentException("Add failed! Require index >= 0 and index <= size."); } } private void checkIndexForRemove(int index) { if (index < 0 || index >= size) { throw new IllegalArgumentException("Remove failed! Require index >= 0 and index < size."); } } } ================================================ FILE: algorithms/src/array/MinNumberInRotatedArray.java ================================================ package array; public class MinNumberInRotatedArray { public static int getMinNumber(int[] arr) { if (arr == null || arr.length < 1) { throw new IllegalArgumentException("Invalid Array!"); } // 循环不变条件 [左侧递增数组 | 右侧递增数组] int l = 0; // l始终指向左侧递增数组 int r = arr.length-1; // r始终指向右侧递增数组 while (arr[l] >= arr[r]) { if (r - l == 1) { return arr[r]; } int mid = l + ((r-l) >> 1); // 若 arr[l] == arr[r] && arr[l] == arr[mid], // arr[mid]无法判定属于左侧递增数组还是右侧递增数组,因此只能顺序查找 if (arr[l] == arr[r] && arr[l] == arr[mid]) { return getMinInOrder(arr, l, r); } if (arr[mid] >= arr[l]) { l = mid; } else if (arr[mid] <= arr[r]) { r = mid; } } return arr[l]; // 若数组是已经排序的数组(从小到大),min = arr[l]。 } private static int getMinInOrder(int[] arr, int l, int r) { int min = arr[l]; for (int i = l+1 ; i <= r; i++) { if (arr[i] < min) { min = arr[i]; } } return min; } } ================================================ FILE: algorithms/src/array/MinNumberInRotatedArrayTest.java ================================================ package array; import org.junit.Assert; import org.junit.Test; public class MinNumberInRotatedArrayTest { @Test public void testGetMinNumber() { int[] arr = new int[] {3,4,5,1,2}; int expected = 1; Assert.assertEquals(expected, MinNumberInRotatedArray.getMinNumber(arr)); int[] arr1 = new int[] {1,2,3,4,5}; Assert.assertEquals(expected, MinNumberInRotatedArray.getMinNumber(arr1)); int[] arr2 = new int[] {1,0,1,1,1}; int expected1 = 0; Assert.assertEquals(expected1, MinNumberInRotatedArray.getMinNumber(arr2)); int[] arr3 = new int[] {1,1,1,0,1}; Assert.assertEquals(expected1, MinNumberInRotatedArray.getMinNumber(arr3)); } } ================================================ FILE: algorithms/src/joseph/Joseph.java ================================================ package joseph; /** * Joseph Question * * Solution: 圆圈长度为n时的解f(n,m)可以看成是圆圈长度为n-1时的解f(n-1,m)加m。 * 但是因为是循环的(圆圈),因此需要对相应的长度取余。 * f(n,m) = (f(n-1,m) + m) % n n > 1 * f(n,m) = 0 n = 1 * * 如:要解决n = 5, m = 3的情形: * n = 1: 0 0 * n = 2: 0,1 (0+3)%2 = 1; * n = 3: 0,1,2 (1+3)%3 = 1; * n = 4: 0,1,2,3 (1+3)%4 = 0; * n = 5: 0,1,2,3,4 (0+3)%5 = 3; */ public class Joseph { public int lastRemain(int n, int m) { if (n < 1 || m < 1) { return -1; } if (n == 1) { return 0; } return (lastRemain(n-1, m) + m) % n; } public int lastRemainIter(int n, int m) { if (n < 1 || m < 1) { return -1; } int last = 0; for (int i = 2; i <= n; i++) { last = (last + m) % i; } return last; } } ================================================ FILE: algorithms/src/linkedlist/FindMidNode.java ================================================ package linkedlist; public class FindMidNode { // 1. T(n) = O(2*n) 遍历2次 public static Node findMidNode(Node head) { if (head == null) { return null; } int len = 0; Node p = head; while(p != null) { len++; p = p.next; } p = head; for (int i = 0; i < len/2; i++) { p = p.next; } return p; } // 2. T(n) = O(n) 遍历1次 // 快慢指针法 public static Node findMidNodeFast(Node head) { if (head == null) { return null; } Node fast = head; Node slow = head; while (fast != null && fast.next != null) { fast = fast.next.next; slow = slow.next; } return slow; } public static Node createNode(int value) { return new Node(value, null); } public static class Node { public int data; public Node next; public Node(int data, Node next) { this.data = data; this.next = next; } } } ================================================ FILE: algorithms/src/linkedlist/FindMidNodeTest.java ================================================ package linkedlist; import org.junit.Assert; import org.junit.Test; public class FindMidNodeTest { @Test public void testFindMidNode() { FindMidNode.Node head = FindMidNode.createNode(0); head.next = FindMidNode.createNode(1); head.next.next = FindMidNode.createNode(2); head.next.next.next = FindMidNode.createNode(3); head.next.next.next.next = FindMidNode.createNode(4); //[0,1,2,3,4] FindMidNode.Node node1 = FindMidNode.findMidNode(head); head.next.next.next.next.next = FindMidNode.createNode(5); //[0,1,2,3,4,5] FindMidNode.Node node2 = FindMidNode.findMidNode(head); int expected1 = 2; int expected2 = 3; Assert.assertEquals(expected1, node1.data); Assert.assertEquals(expected2, node2.data); } @Test public void testFindMidNodeFast() { FindMidNode.Node head = FindMidNode.createNode(0); head.next = FindMidNode.createNode(1); head.next.next = FindMidNode.createNode(2); head.next.next.next = FindMidNode.createNode(3); head.next.next.next.next = FindMidNode.createNode(4); //[0,1,2,3,4] FindMidNode.Node node1 = FindMidNode.findMidNodeFast(head); head.next.next.next.next.next = FindMidNode.createNode(5); //[0,1,2,3,4,5] FindMidNode.Node node2 = FindMidNode.findMidNodeFast(head); int expected1 = 2; int expected2 = 3; Assert.assertEquals(expected1, node1.data); Assert.assertEquals(expected2, node2.data); } } ================================================ FILE: algorithms/src/linkedlist/SingleLinkedList.java ================================================ package linkedlist; public class SingleLinkedList { private Node head = null; // 1. search public Node findByValue(int value) { if (head == null) { return null; } for (Node p = head; p != null; p = p.next) { if (p.data == value) return p; } return null; } public Node findByIndex(int index) { if (head == null || index < 0) { return null; } int temp = 0; for (Node p = head; p != null; p = p.next) { if (temp == index) return p; temp++; } return null; } // 2. Insert public void insertToHead(int value) { Node newNode = createNode(value); insertToHead(newNode); } public void insertToHead(Node newNode) { if (newNode == null) { return; } newNode.next = head; head = newNode; } public void insertToTail(int value) { Node newNode = createNode(value); insertToTail(newNode); } public void insertToTail(Node newNode) { if (newNode == null) { return; } if (head == null) { head = newNode; return; } Node temp = head; while (temp.next != null) { temp = temp.next; } temp.next = newNode; } public void insertAfter(Node p, int value) { Node newNode = createNode(value); insertAfter(p, newNode); } public void insertAfter(Node p, Node newNode) { if (p == null || newNode == null) { return; } newNode.next = p.next; p.next = newNode; } public void insertBefore(Node p, int value) { Node newNode = createNode(value); insertBefore(p, newNode); } public void insertBefore(Node p, Node newNode) { if (p == null || newNode == null) { return; } if (p == head) { insertToHead(newNode); return; } Node before = head; while (before.next != p) { before = before.next; } if (before == null) { return; } else { newNode.next = p; before.next = newNode; } } // 3. delete public void deleteByNode(Node p) { if (head == null || p == null) { return; } if (p == head) { head = head.next; return; } Node before = head; while (before.next != p) { before = before.next; } if (before == null) { return; } else { before.next = p.next; } } public void deleteByValue(int value) { Node before = null; for (Node p = head; p != null; p = p.next) { if (p.getData() == value) { if (p == head) { head = head.next; p = head; } else { before.next = p.next; p = before; } } before = p; } } public void printAll() { System.out.print("SingleLinkedList: ["); for (Node p = head; p != null; p = p.next) { System.out.print(p.getData()); if (p.next != null) { System.out.print(","); } } System.out.print("]"); System.out.println(); } public static Node createNode(int value) { return new Node(value, null); } public static class Node { private int data; private Node next; public Node(int data, Node next) { this.data = data; this.next = next; } public int getData() { return data; } } } ================================================ FILE: algorithms/src/linkedlist/SingleLinkedListTest.java ================================================ package linkedlist; import org.junit.Test; public class SingleLinkedListTest { @Test public void testSingleLinkedList() { SingleLinkedList list = new SingleLinkedList(); //[] list.insertToTail(2); list.insertToTail(3); list.insertToTail(4); // [2,3,4] list.printAll(); list.insertToHead(1); list.insertToHead(0); // [0,1,2,3,4] list.printAll(); SingleLinkedList.Node node = list.findByIndex(2); list.insertBefore(node, 10); list.insertAfter(node, 10); // [0,1,10,2,10,3,4] list.printAll(); list.deleteByValue(10); list.printAll(); // [0,1,2,3,4] SingleLinkedList.Node p = list.findByValue(2); System.out.println(node.getData() == p.getData()); // true } } ================================================ FILE: algorithms/src/lru/LRU.java ================================================ package lru; import java.util.HashMap; import java.util.Iterator; /** * LRU Cache: 优先淘汰最久未使用的数据 * Solution: 双向链表+HashMap * 1. 访问某个节点时,将其从原来的位置删除,并且重新插入到链表头部(保证链表尾部存储最近最久未使用的节点)。当节点数量大于缓存的 * 最大空间时,就淘汰链表尾部的节点。使用双端链表是为了保证删除操作时间复杂度为O(1)。 * 2. 为了使得删除操作的时间复杂度是O(1),就不能使用遍历的方式找到某个节点。HashMap存储的是Key到节点的映射,通过Key就能够以O(1) * 的时间得到节点,然后再以O(1)的时间将其从双向队列中删除。 * 具体步骤: * 1. get(): * 访问某个节点时,若这个节点不存在,返回null; * 若这个节点存在,则将该节点从该位置删除,然后插入到链表头部,最后返回该节点的值。 * 2. put(): * 插入节点时,若这个节点存在,则将这个节点从原位置删除,然后插入到链表头部。 * 若这个节点不存在,则新建一个节点,然后将这个节点插入链表头部,然后将该节点插入到map中 * 此时,若map中节点的数量大于cache的容量,则将该链表尾部节点删除,同时在map中删除链表尾部节点。 * * Example: * head <-> 1,1 <-> 2,2 <-> 3,3 <-> tail; * put(2,2): head <-> 2,2 <-> 1,1 <-> 3,3 <-> tail; * get(3): head <-> 3,3 <-> 2,2 <-> 1,1 <-> tail; * * @param * @param */ public class LRU implements Iterable { private Node head; private Node tail; private HashMap map; private int maxSize; private class Node { Node pre; Node next; K k; V v; public Node(K k, V v) { this.k = k; this.v = v; } } public LRU(int maxSize) { this.maxSize = maxSize; this.map = new HashMap<>(maxSize * 4 / 3); head = new Node(null, null); tail = new Node(null, null); head.next = tail; tail.pre = head; } // O(1) public V get(K key) { if (!map.containsKey(key)) { return null; } Node node = map.get(key); unlink(node); appendToHead(node); return node.v; } // O(1) public void put(K key, V value) { if (map.containsKey(key)) { Node node = map.get(key); unlink(node); } Node node = new Node(key, value); appendToHead(node); map.put(key, node); if (map.size() > maxSize) { Node toRemove = removeTail(); map.remove(toRemove.k); } } private Node removeTail() { Node node = tail.pre; Node pre = node.pre; tail.pre = pre; pre.next = tail; node.next = null; node.pre = null; return node; } private void appendToHead(Node node) { Node next = head.next; node.next = next; node.pre = head; next.pre = node; head.next = node; } private void unlink(Node node) { Node pre = node.pre; Node next = node.next; pre.next = next; next.pre = pre; node.pre = null; node.next = null; } @Override public Iterator iterator() { return new LRUIterator<>(); } private class LRUIterator implements Iterator { private Node cur = head.next; @Override public boolean hasNext() { return cur != tail; } @Override public K next() { Node node = cur; cur = cur.next; return (K) node.k; } } } ================================================ FILE: algorithms/src/lru/LRUTest.java ================================================ package lru; import org.junit.Assert; import org.junit.Test; public class LRUTest { @Test public void testLRU() { LRU lru = new LRU<>(5); Assert.assertEquals(null, lru.get(1)); lru.put(1,1); Assert.assertEquals(Integer.valueOf(1), lru.get(1)); lru.put(2,2); lru.put(3,3); lru.put(4,4); lru.put(5,5); for (Integer integer : lru) { System.out.print(integer + " "); } System.out.println(); Assert.assertEquals(Integer.valueOf(2), lru.get(2)); for (Integer integer : lru) { System.out.print(integer + " "); } System.out.println(); lru.put(6,6); for (Integer integer : lru) { System.out.print(integer + " "); } } } ================================================ FILE: algorithms/src/queue/ArrayQueue.java ================================================ package queue; public class ArrayQueue implements Queue { private T[] items; private int capacity; private int head; private int tail; public ArrayQueue(int capacity) { this.capacity = capacity; this.items = (T[]) new Object[capacity]; this.head = 0; this.tail = 0; } // T(N)=O(1) @Override public boolean enqueue(T item) { if (tail == capacity) { // head == 0 && tail == capacity, queue is full. if (head == 0) { return false; } // 数据搬移,空出位置 for (int i = head; i < tail; i++) { items[i-head] = items[i]; } // 搬移完之后,更新head和tail tail -= head; head = 0; } items[tail] = item; tail++; return true; } // T(N)=O(1) @Override public T dequeue() { if (head == tail) { return null; } T tmp = items[head]; head++; return tmp; } // for test public void printAll() { System.out.print("["); for (int i = head; i < tail; i++) { System.out.print(items[i] + " "); } System.out.print("]"); System.out.println(); } public static void main(String[] args) { ArrayQueue queue = new ArrayQueue<>(4); queue.printAll(); queue.enqueue("A"); queue.printAll(); queue.enqueue("B"); queue.enqueue("C"); queue.enqueue("D"); queue.printAll(); queue.enqueue("E"); queue.printAll(); queue.dequeue(); queue.dequeue(); queue.dequeue(); queue.printAll(); } } ================================================ FILE: algorithms/src/queue/CircularQueue.java ================================================ package queue; public class CircularQueue { private T[] items; private int capacity; private int head; private int tail; public CircularQueue(int capacity) { this.capacity = capacity; this.items = (T[]) new Object[capacity]; this.head = 0; this.tail = 0; } // T(N)=O(1) public boolean enqueue(T item) { // 队满条件 if ((tail + 1) % capacity == head) { return false; } items[tail] = item; tail = (tail + 1) % capacity; return true; } // T(N)=O(1) public T dequeue() { // 队空条件 if (head == tail) { return null; } T tmp = items[head]; items[head] = null; // 这里可以选择置为nulL,也可以不置为null. head = (head + 1) % capacity; return tmp; } // for test public void printAll() { System.out.print("["); for (int i = 0; i < capacity; i++) { System.out.print(items[i] + " "); } System.out.print("]"); System.out.println(); } public static void main(String[] args) { CircularQueue queue = new CircularQueue<>(4); queue.printAll(); queue.enqueue("A"); queue.printAll(); queue.enqueue("B"); queue.enqueue("C"); queue.enqueue("D"); queue.printAll(); queue.enqueue("E"); queue.printAll(); queue.dequeue(); queue.dequeue(); queue.enqueue("E"); queue.printAll(); } } ================================================ FILE: algorithms/src/queue/ListQueue.java ================================================ package queue; public class ListQueue implements Queue { Node head = null; Node tail = null; // T(N)=O(1) @Override public boolean enqueue(T item) { Node newNode = new Node(item, null); if (tail == null) { tail = newNode; head = newNode; return true; } tail.next = newNode; tail = tail.next; return true; } // T(N)=O(1) @Override public T dequeue() { if (head == null) { return null; } Node tmp = head; head = head.next; tmp.next = null; return (T) tmp.getData(); } // for test public void printAll() { System.out.print("["); for (Node p = head; p != null; p = p.next) { System.out.print(p.getData() + " "); } System.out.print("]"); System.out.println(); } private static class Node{ private T data; private Node next; public Node(T data, Node next) { this.data = data; this.next = next; } public T getData() { return this.data; } } public static void main(String[] args) { ListQueue queue = new ListQueue<>(); queue.printAll(); queue.enqueue("A"); queue.printAll(); queue.enqueue("B"); queue.enqueue("C"); queue.enqueue("D"); queue.printAll(); queue.enqueue("E"); queue.printAll(); queue.dequeue(); queue.dequeue(); queue.dequeue(); queue.printAll(); } } ================================================ FILE: algorithms/src/queue/Queue.java ================================================ package queue; public interface Queue { boolean enqueue(T item); T dequeue(); } ================================================ FILE: algorithms/src/stack/ArrayStack.java ================================================ package stack; public class ArrayStack implements Stack { private T[] items; // 数组 private int top; // 栈中元素的个数(或者说是栈顶指针) private int capacity; // 栈的容量 public ArrayStack(int capacity) { this.capacity = capacity; this.items = (T[]) new Object[capacity]; this.top = 0; } // T(N)=O(1) @Override public boolean push(T item) { // 数组空间不足,无法push if (top == capacity) { return false; } // size为待push的位置 items[top] = item; top++; return true; } // T(N)=O(1) @Override public T pop() { // 数组为空,无法pop if (top == 0) { return null; } T item = items[top -1]; top--; return item; } // T(N)=O(1) @Override public T peek() { if (top == 0) { return null; } return items[top -1]; } public static void main(String[] args) { ArrayStack stack = new ArrayStack<>(4); stack.push("A"); stack.push("B"); stack.push("C"); stack.push("D"); System.out.println(stack.peek()); stack.pop(); System.out.println(stack.peek()); stack.pop(); stack.pop(); stack.pop(); stack.pop(); System.out.println(stack.peek()); } } ================================================ FILE: algorithms/src/stack/DynamicArrayStack.java ================================================ package stack; /** * 支持动态扩容的栈 * * @param */ public class DynamicArrayStack implements Stack { private T[] items; private int top; private int capacity; public DynamicArrayStack(int capacity) { this.capacity = capacity; this.items = (T[]) new Object[capacity]; this.top = 0; } // T(N)=O(1): 思考一下,这里涉及到了数组的动态扩容, // 那么时间复杂度为什么仍然是O(1) @Override public boolean push(T item) { if (top == capacity) { capacity = capacity * 2; resize(capacity); } items[top] = item; top++; return false; } // T(N)=O(1) @Override public T pop() { if (top == 0) { return null; } T item = items[top -1]; top--; return item; } // T(N)=O(1) @Override public T peek() { if (top == 0) { return null; } return items[top -1]; } private void resize(int size) { T[] tmp = (T[]) new Object[size]; for (int i = 0; i < items.length; i++) { tmp[i] = this.items[i]; } this.items = tmp; } public static void main(String[] args) { DynamicArrayStack stack = new DynamicArrayStack<>(4); stack.push("A"); stack.push("B"); stack.push("C"); stack.push("D"); System.out.println(stack.peek()); stack.push("E"); System.out.println(stack.peek()); stack.pop(); System.out.println(stack.peek()); stack.pop(); stack.pop(); stack.pop(); stack.pop(); System.out.println(stack.peek()); } } ================================================ FILE: algorithms/src/stack/ListStack.java ================================================ package stack; public class ListStack implements Stack { private Node top = null; // T(N)=O(1) @Override public boolean push(T item) { Node newNode = new Node(item, null); // 需要判断栈是否为空 if (top == null) { top = newNode; } else { newNode.next = top; top = newNode; } return true; } // T(N)=O(1) @Override public T pop() { if (top == null) { return null; } Node tmp = top; top = top.next; tmp.next = null; // 释放删除的top return (T) tmp.getData(); } // T(N)=O(1) @Override public T peek() { if (top == null) { return null; } return (T) top.getData(); } public static class Node { private T data; private Node next; public Node(T data, Node next) { this.data = data; this.next = next; } public T getData() { return this.data; } } public static void main(String[] args) { ListStack stack = new ListStack<>(); stack.push("A"); stack.push("B"); stack.push("C"); stack.push("D"); System.out.println(stack.peek()); stack.pop(); System.out.println(stack.peek()); stack.pop(); stack.pop(); stack.pop(); stack.pop(); System.out.println(stack.peek()); } } ================================================ FILE: algorithms/src/stack/SampleBrowser.java ================================================ package stack; /** * 浏览器页面的前进和后退功能 */ public class SampleBrowser { private String currentPage; // private Stack backwardStack; private Stac } ================================================ FILE: algorithms/src/stack/Stack.java ================================================ package stack; public interface Stack { boolean push(T item); T pop(); T peek(); } ================================================ FILE: appendix/instructions.md ================================================ # 上传与更新本地代码到Github 1. 远程操作: * Github上创建Repo 2. 本地操作: * git clone https://github.com/guokaide/leetcode * cd leetcode * git init * git add . * git remote add origin https://github.com/guokaide/leetcode (将本地仓库与远程关联) * git commit -m "update all files" * git pull origin master (将remote所有的操作更新到本地,避免local与remote冲突) * git push -u origin master 3. 注:核心更新操作 * git status * git add * * git commit -m "comments" * git pull origin master * git push origin master ================================================ FILE: appendix/刷题笔记.md ================================================ # 刷题笔记 > It does not matter how slowly you go as long as you do not stop. ## 如何有效学习数据结构与算法? ### 如何精通一个领域? * Chunk it up 切碎知识点 将数据结构与算法的基本知识点分解开来,分解结果见:[数据结构与算法脑图]( http://naotu.baidu.com/file/cd094852bfc84ec5e1868867936451c3?token=ef906723267f2f9f ) * Deliberate Practicing 刻意练习 对分解之后的知识点进行分解训练和反复练习。 * 明白一个`原则`:基本功是区别业余和职业选手的根本,我们的目标是成为职业选手。 * 明白一个`误区`:**刷一道算法题,刷一遍是完全不够的,需要一遍遍刷题**。 * 明白一个`事实`:反复练习自己薄弱的地方,扩张舒适区,会让我们成长更快。 * Feedback 反馈 反馈对于学习的作用,主要是将揉碎的知识点再串联起来,形成体系结构,这样就建立了一个 `知识点-> 专项反复练习-> 建立知识体系`的学习闭环。 * 即时反馈 * 主动型反馈(自己去找) * 高手代码,例如 Github,LeetCode(题解、Discussion) * 第一视角直播 * 被动式反馈(高手指点) * Code Review * 教练看你打,然后给你反馈 ### 如何精通数据结构与算法呢? > 如何反复刷题呢?答案是:五步刷题法,我们称为***五毒神掌***。 1. 刷题第***1***遍 * 5分钟时间:读题+思考 * 若5分钟思考不出来,直接看解法,要注意:**对每一道题目,思考和比较这个题的多种解法,最好是全部解法** * **背诵、默写好的解法**:这是积累代码能力的一个很好的办法。 2. 刷题第***2***遍 * 马上自己写 -> LeetCode提交 * **多种解法进行比较、体会 -> 优化** 3. 刷题第***3***遍 * **一天后**,再重复做题 * 针对不同解法的熟练程度 -> 对薄弱的地方进行专项练习 4. 刷题第***4***遍 * **一周后**,反复回来练习相同的题目 5. 刷题第***5***遍 * **面试前一周**进行恢复性训练。 > 如何刷一道题呢? 答案是:**切题四件套**。 1. Clarification: 多沟通,多思考,确保正确理解问题 ***(面试的时候,和面试官过一遍题目)*** 2. Possible solutions: 思考关于这个题目的所有解法,比较其优劣,优化其性能 * compare(time/space) * optimal(加强):例如空间换时间、升维(一维到二维) ***(提出多种解法,给出最优解法)*** 3. Coding: 就是不停的写,多写,提高Coding能力的唯一法宝 ***(代码实现)*** 4. Test cases ***(阐述测试样例)*** ## 工具 ### [visualgo](https://visualgo.net/zh) ### [bigocheatsheet](https://www.bigocheatsheet.com/) ## 代码模板 ### 递归代码模板 Recursion * Python ```python def recursion(level, param1, param2, ...): # recursion terminator if (level > MAX_LEVEL): # process result return # process logic in current level process(level, data...) # drill down self.recursion(level + 1, p1, ...) # restore current level status if needed ``` * Java ```java public void recursion(int level, int param) { // recursion terminator if (level > MAX_LEVEL) { // process result return; } // process logic in current level process(level, param); // drill down recursion( level: level + 1, newParam); // restore current level status if needed } ``` ### 分治代码模板 Divide and Conquer ```python def divide_conquer(problem, param1, param2, ...): # recursion terminator if problem is None: print_result return # divide: prepare data data = prepare_data(problem) subproblems = split_problem(problem, data) # conquer: conquer subproblems subresult1 = self.divide_conquer(subproblems[0], p1, ...) subresult2 = self.divide_conquer(subproblems[1], p1, ...) subresult3 = self.divide_conquer(subproblems[2], p1, ...) … # merge: process and generate the final result result = process_result(subresult1, subresult2, subresult3, …) # restore current level states if needed ``` ### 查找-搜索-遍历 模板 查找的特点是: * 找到特定元素 搜索的特点是: * 每个节点都要访问一次 * 每个节点仅仅访问一次 * 每个节点访问顺序不限 #### 二分查找(Binary Search) ```python left, right = 0, len(array) - 1 while left <= right: mid = (left + right) / 2 if array[mid] == target: # find the target break or return result elif array[mid] < target: left = mid + 1 else: right = mid - 1 ``` #### 深度优先搜索(DFS, Depth First Search) ```python # 递归写法 visited = set() def dfs(node, visited): # terminator if node in visited: return # already visited visited.add(node) # process current node here # ... for next_node in node.children(): if next_node not in visited: dfs(next_node, visited) # 非递归写法 def DFS(self, tree): if tree.root is None: return [] visited, stack = [], [tree.root] while stack: node = stack.pop() visited.add(node) process(node) nodes = generate_realted_nodes(node) stack.push(nodes) # other processing work ... ``` #### 广度优先遍历(BFS, Breadth First Search) ```python def BFS(graph, start, end): visited = set() queue = [] queue.append([start]) while queue: node = queue.pop() visited.add(node) process(node) nodes = generate_related_nodes(node) queue.push(nodes) # other processing work ... ``` #### A* ```python def AstarSearch(graph, start, end): pq = collections.priority_queue() # 优先级 —> 估价函数 pq.append([start]) visited.add(start) while pq: node = pq.pop() # can we add more intelligence here ? visited.add(node) process(node) nodes = generate_related_nodes(node) unvisited = [node for node in nodes if node not in visited] pq.push(unvisited) ``` ### 归并排序 ### Trie #### 定义 Trie 树,也叫“字典树”。Trie 树也是一种树形结构。它是专门用来处理字符串匹配的数据结构,**用来解决一组字符串集合中快速查找某个字符串的问题**。 #### 本质 Trie 树的本质,就是利用字符串之间的公共前缀,将重复的前缀合并在一起,组成一颗多个字符串共用前缀的树。这种存储方式避免了重复存储一组字符串的相同前缀子串。 #### 实现 ##### API * void insert(char[] text) // 插入字符串 * boolean search(char[] pattern) // 查找字符串 * boolean startsWith(String prefix) // 查找前缀 ##### 代码 假设字符串由a~z这26个小写字母构成,数组下标为0的位置存储指向子节点a的的指针,下标为1的位置存储指向子节点b的指针,以此类推,下标为25的位置存储指向子节点z的指针。如果某个子节点不存在,则对应下边位置处存储为null。 ```java public class TrieNode { public char data; public TrieNode[] children = new TrieNode[26]; public boolean isEndingChar = false; public TrieNode(char data) { this.data = data; } } public class Triee { // 根节点,存储无意义字符 private TrieNode root = new TrieNode('/'); // 插入字符串 public void insert(char[] text) { TrieNode p = root; for (int i = 0; i < text.length; ++i) { int index = text[i] - 'a'; if (p.children[index] == null) { TrieNode newNode = new TrieNode(text[i]); p.children[index] = newNode; } p = p.children[index]; } p.isEndingChar = true; } // 查找字符串 public boolean search(char[] pattern) { TrieNode p = root; for (int i = 0; i < pattern.length; ++i) { int index = pattern[i] - 'a'; if (p.children[index] == null) { return false; } p = p.children[index]; } if (p.isEndingChar == false) { return false; // pattern 仅仅是前缀, 无法完全匹配 } else { return true; // 完全匹配 } } } ``` ```python class Trie(object): def __init__(self): self.root = {} self.end_of_word = "#" def insert(self, word): node = self.root for char in word: node = node.setdefault(char, {}) node[self.end_of_word] = self.end_of_word def search(self, word): node = self.root for char in word: if char not in node: return False node = node[char] return self.end_of_word in node def startsWith(self, prefix): node = self.root for char in prefix: if char not in node: return False node = node[char] return True ``` [LeetCode 208. implement-trie-prefix-tree](https://leetcode-cn.com/problems/implement-trie-prefix-tree/) ```java class Trie { private TrieNode root; /** Initialize your data structure here. */ public Trie() { this.root = new TrieNode('/'); } /** Inserts a word into the trie. */ public void insert(String word) { TrieNode p = root; for (int i = 0; i < word.length(); i++) { int index = word.charAt(i) - 'a'; if (p.children[index] == null) { TrieNode newNode = new TrieNode(word.charAt(i)); p.children[index] = newNode; } p = p.children[index]; } p.isEndingChar = true; } /** Returns if the word is in the trie. */ public boolean search(String word) { TrieNode p = root; for (int i = 0; i < word.length(); i++) { int index = word.charAt(i) - 'a'; if (p.children[index] == null) { return false; } p = p.children[index]; } if (p.isEndingChar) return true; else return false; } /** Returns if there is any word in the trie that starts with the given prefix. */ public boolean startsWith(String prefix) { TrieNode p = root; for (int i = 0; i < prefix.length(); i++) { int index = prefix.charAt(i) - 'a'; if (p.children[index] == null) { return false; } p = p.children[index]; } return true; } class TrieNode { public char data; public TrieNode[] children = new TrieNode[26]; public boolean isEndingChar = false; public TrieNode(char data) { this.data = data; } } } /** * Your Trie object will be instantiated and called as such: * Trie obj = new Trie(); * obj.insert(word); * boolean param_2 = obj.search(word); * boolean param_3 = obj.startsWith(prefix); */ ``` #### 性能 ##### 时间复杂度 如果在一组字符串中,频繁地查询某些字符串,用 Trie 树会非常高效。 构建 Trie 树的过程,需要扫描所有的字符串,因此时间复杂度为 `O(N)`,其中`N` 为所有字符串长度之和。 构建 Trie 树的过程,时间复杂度较高,但是一旦构建成功,后续查询操作会非常高效。每次查询时,若查询的字符串长度为`k`,那么我们只需要对比大约 `k`个节点,就能完成查询操作,与原来的那组字符串长度和个数都没有关系。因此,构建 Trie 树之后,查询操作的时间复杂度为 `O(k)`, 其中 `k` 表示要查找的字符串的长度。 ##### 空间复杂度 从前面的实现,可以看出,Trie 树的每个节点都需要维护一个长度为26的数组,如果字符串不仅包含小写字母,而且还包含大写字母,数字以及中文等,那需要的存储空间就更多了。Trie 树本质是为了避免重复存储相同的公共前缀,但是在重复的前缀不多的情况下,Trie 树不但不能节省内存,还有可能会浪费更多的内存。 Trie 树 是一种空间换时间的实现,尽管比较耗费空间,但是查找确实高效。 #### Trie 树的应用 Trie 树解决了在一组字符串集合中查找字符串的问题,相同的问题,我们其实还可以通过散列表或者红黑树解决。 事实上,Trie 树在一组字符串中查找字符串的表现并不是很好,它对要处理的字符串有极其严苛的要求。 1. 字符串中包含的字符集不能太大。字符集太大,将会浪费很多存储空间。 2. 要求多个字符串前缀重合比较多,不然将会浪费很多存储空间。 3. 通过指针穿起来的数据块是不连续的,而 Trie 树中用到了指针,所以对缓存不友好,性能上会打折扣。 事实上,Trie 树只是不适合精确匹配查找,而且不适合用来做动态数据的查找,这种问题更加适合用散列表或者红黑树解决。 `Trie 树更加适合查找前缀匹配的字符串`,例如Google时的关键词提示功能,自动补全等功能,或者是统计单词频次等问题。 ### UnionFind ```java class UnionFind { private int count = 0; private int[] parent; /* 初始化为 n 个集合*/ public UnionFind(int n) { count = n; parent = new int[n]; for (int i = 0; i < n; i++) { parent[i] = i; } } /* 确定 q 属于哪个子集合 */ public int find(int p) { while (p != parent[p]) { parent[p] = parent[parent[p]]; p = parent[p]; } return p; } /* 合并 p 和 q 所在的集合*/ public void union(int p, int q) { int rootP = find(p); int rootQ = find(q); if (rooP == rootQ) return; parent[rootP] = rootQ; count--; } } ``` ```python def init(p): p = [i for i in range(n)] def union(self, p, i, j): p1 = self.parent(p, i) p2 = self.parent(p, j) p[p1] = p2 def parent(self, p, i): root = i; while p[root] != root: root = p[root] while p[i] != i: # 路径压缩 x = i i = p[i] p[x] = root return root ``` ### 八皇后问题 https://shimo.im/docs/hV9GdhcrddcDJWwv ## Dynamic Programming > Simplifying a complicated problem by breaking it down into simpler sub-problems in a recursive manner ### 动态规划基本理论 #### 一个模型:多阶段决策最优解模型 动态规划一般用于解决最优问题。多阶段决策最优解模型是指动态规划适合解决的问题的模型。解决问题的过程需要经历多个决策过程,每个决策过程都对应着一组状态。动态规划的目标在于寻找一组决策序列(每个决策过程的状态),通过这组决策序列产生最终期望的求解的最优值。 #### 三个特征 ##### 最优子结构 最优子结构是指问题的最优解包含子问题的最优解。我们可以通过子问题的最优解得到问题的最优解,也就是说,后面的状态可以通过前面的状态推导出来。 ##### 无后效性 无后效性有两层含义: * 在推导后面状态的时候,我们只关心前面阶段的状态值,而不关心这个状态值的求解过程; * 某个阶段的状态一旦确定,就不受之后阶段的决策影响,即状态定了就定了。 ##### 重复子问题 不同的决策序列,到达某个相同的阶段的时候,可能会产生重复的状态。 ##### 复杂度 1、状态存在更多的维度(二维、三维或者更多,甚至需要压缩) 2、状态方程更加复杂 #### 实例 > 问题:给定一个`n * n`的矩阵` w[n][n]`,矩阵存储的均为正整数。棋子从矩阵的左上角出发去右下角,每次只能向右或者是向下移动1位。从左上角到右下角有很多不同的路径可以走。规定每条路径经过的数字之和就是这条路径的长度。求从左上角到右下角的最短路径。 ##### 分析: ***这个问题是否满足`一个模型`呢?*** 从 `(0, 0)` 出发到 `(n-1, n-1)`,总计`2*(n-1)`步,对应着`2*(n-1)`个阶段,每个阶段都有向右或者向下2种决策,因此每个阶段都会对应一个状态集合。问题的目标在于找到一个状态序列,从而确定 `(0, 0)` 出发到 `(n-1, n-1)`的最短距离。因此整个问题是一个`多阶段决策最优解`问题。 ***这个问题是否满足`三大特征`呢?*** 对于任意一个节点`(i, j)`来说,从 `(0, 0)` 出发到 `(i, j)`存在多种路线,因此可能存在`重复子问题`。 对于任意一个节点`(i, j)`来说,`(i, j)`这个位置的状态需要通过 `(i-1, j)`以及 `(i, j-1)`两个位置的状态来确定,但是并不需要关心这2个位置的状态的求解过程。而且,由于仅仅允许向右或者向下运动,因此前面阶段的状态确定了之后,不会被后面的状态所改变,因此满足`无后效性`的特征。 定义状态为:`min_dist(i, j)`,表示从 `(0, 0)` 出发到 `(i, j)`的最短距离。那么到达`(i, j)`的最短路径必然经过`(i-1, j)`或 `(i, j-1)`,因此,到达`(i, j)`的最短路径必然包含到达这2个位置的最短路径之一。因此满足 `最优子结构` 的特征。 综上所述,`min_dist(i, j) = w[i][j] + min(min_dist(i, j-1), min_dist(i-1, j))`。 ##### 求解: ```java // 回溯算法 private int min = Integer.MAX_VALUE; public void minDist(int i, int j, int dist, int[][] w, int n) { // terminator if (i == n && j == n) { if (dist < min) min = dist; return; } // drill down if (i < n) { minDist(i + 1, j, dist + w[i][j], w, n); } if (j < n) { minDist(i, j + 1, dist + w[i][j], w, n); } } ``` ```java // 递归+备忘录(缓存)减少重复计算 private int[][] mem = new int[n][n]; public int minDist(int i, int j, int[][] w) { if (i == 0 && j == 0) { return w[0][0]; } if (mem[i][j] > 0) { return mem[i][j]; } int minLeft = Integer.MAX_VALUE; if (j-1>=0 ) { minLeft = minDist(i, j-1); } int minUp = Integer.MAX_VALUE; if (i-1>= 0) { minUp = minDist(i-1, j); } int curMinDist = w[i][j] + Math.min(minLeft, minUp); mem[i][j] = curMinDist; return curMinDist; } ``` ```java // 动态规划 public int minDist(int[][] w, int n) { int[][] states = new int[n][n]; int sum = 0; for (int j = 0; j < n; j++) { sum += w[0][j]; states[0][j] = sum; } sum = 0; for (int i = 0; i < n; i++) { sum += w[i][0]; states[i][0] = sum; } for (int i = 1; i < n; i++) { for (int j = 1; j < n; j++) { states[i][j] = w[i][j] + Math.min(states[i][j-1], states[i-1][j]); } } return states[n-1][n-1]; } ``` ### 一维动态规划:Fibnacci ```java # 递归:自顶向下 O(2^N) int fib(int n) { if (n <= 1) { return n; } return fib(n - 1) + fib(n - 2); // 改进:return n <= 1 ? n : fib(n-1) + fib(n-2); } # 递归 + 备忘录 (记忆化搜索) O(N) int fib(int n, int[] memo) { if (n <= 1) { return n; } if (memo[n] == 0) { memo[n] = fib(n-1, memo) + fib(n-2, memo); } return memo[n]; } # 动态规划:自底向上 O(N) int fib(int n) { int[] dp = new int[n]; dp[0] = 0; dp[1] = 1; for (int i = 2; i <= n; i++) { dp[i] = dp[i-1] + dp[i-2]; } return dp[n]; } # 一维的Equation: f(n) = f(n-1) + f(n-2) ``` ### 二维动态规划:Count the paths ```java // 递归 int countPaths(boolean[][] grid, int row, int col) { if (!validSqure(grid, row, col)) return 0; if (isAtEnd(grid, row, col)) return 1; return countPaths(grid, row + 1, col) + countPaths(grid, row, col+1); } opt[i, j] = opt[i+1, j] + opt[i, j+1]; if a[i, j] == '空地': opt[i, j] = opt[i+1, j] + opt[i, j+1]; else: opt[i, j] = 0; 对比: # 一维的Equation: f(n) = f(n-1) + f(n-2) # 二维的Equation: opt[i, j] = opt[i+1, j] + opt[i, j+1]; ``` ## 总结代码库(示例) * Valid anagram ```python # 思路:手动模拟hashtable,将字符串”a-z“的ASCII码作key,计数求差异 def isAnagram(self, s: str, t: str) -> bool: arr1, arr2 = [0]*26, [0]*26 for i in s: arr1[ord(i) - ord('a')] += 1 for i in t: arr2[ord(i) - ord('a')] += 1 return arr1 == arr2 # 思路:map计数,对比计数差异 def isAnagram(self, s: str, t: str) -> bool: dict1, dict2 = {}, {} for item in s: dict1[item] = dict1.get(item,0) + 1 for item in t: dict2[item] = dict2.get(item,0) + 1 return dict1 == dict2 # 思路:数组排序后比较差异 def isAnagram(self, s: str, t: str) -> bool: return sorted(s) == sorted(t) ``` ```java public class Solution { public boolean isAnagram(String s, String t) { if(s.length() != t.length()) return false; int [] a = new int [26]; for(Character c : s.toCharArray()) a[c - 'a']++; for(Character c : t.toCharArray()) { if(a[c -'a'] == 0) return false; a[c - 'a']--; } return true; } public boolean isAnagram(String s1, String s2) { int[] freq = new int[256]; for(int i = 0; i < s1.length(); i++) freq[s1.charAt(i)]++; for(int i = 0; i < s2.length(); i++) if(--freq[s2.charAt(i)] < 0) return false; return s1.length() == s2.length(); } public boolean isAnagram(String s, String t) { char[] sChar = s.toCharArray(); char[] tChar = t.toCharArray(); Arrays.sort(sChar); Arrays.sort(tChar); return Arrays.equals(sChar, tChar); } } ``` * Group Anagrams ```python def groupAnagrams(self, strs): d = {} for w in sorted(strs): key = tuple(sorted(w)) d[key] = d.get(key, []) + [w] return d.values() def groupAnagrams(self, strs): dic = {} for item in sorted(strs): sortedItem = ''.join(sorted(item)) dic[sortedItem] = dic.get(sortedItem, []) + [item] return dic.values() ``` ```java public List> groupAnagrams(String[] strs) { List> res = new ArrayList<>(); HashMap> map = new HashMap<>(); Arrays.sort(strs); for (int i = 0; i < strs.length; i++) { String temp = strs[i]; char[] ch = temp.toCharArray(); Arrays.sort(ch); if (map.containsKey(String.valueOf(ch))) { map.get(String.valueOf(ch)).add(strs[i]); } else { List each = new ArrayList<>(); each.add(strs[i]); map.put(String.valueOf(ch), each); } } for (List item: map.values()) { res.add(item); } return res; } ``` * Two sum ```python def twoSum(self, nums, target): d = dict() for index,num in enumerate(nums): if d.get(num) == None: d[target - num] = index else: return [d.get(num), index ``` ```java public int[] twoSum(int[] nums, int target) { HashMap tracker = new HashMap(); int len = nums.length; for (int i = 0; i < len; i++){ if (tracker.containsKey(nums[i])){ int left = tracker.get(nums[i]); return new int[]{left+1, i+1}; } else { tracker.put(target - nums[i], i); } } return new int[2]; } ``` ## 刷题开始 ### Array #### 283. [move zeros]( https://leetcode-cn.com/problems/move-zeroes/ ) > 方法1:将所有的非零元素都填充到数组前侧,然后将0填充到数组后侧 `#双指针法` ```java class Solution { public void moveZeroes(int[] nums) { int lastNotZeroIndex = 0; for (int i = 0; i < nums.length; ++i) { if (nums[i] != 0) { nums[lastNotZeroIndex++] = nums[i]; } } for (int i = lastNotZeroIndex; i < nums.length; ++i) { nums[i] = 0; } } } ``` > 方法2:一维数组的坐标转换 i, j `#双指针法` > > 用 j 记录上一个可能为0的值的索引,用 i 遍历数组,当遇到不为0的值的时候,将该元素 num[i] 与 nums[j] 交换,保证 j 前面的元素均为非0值。 ```java class Solution { public void moveZeroes(int[] nums) { int lastZeroIndex = 0; for (int i = 0; i < nums.length; ++i) { if (nums[i] != 0) { int temp = nums[i]; nums[i] = nums[lastZeroIndex]; nums[lastZeroIndex] = temp; lastZeroIndex++; } } } } ``` #### 11. [container with most water]( https://leetcode-cn.com/problems/container-with-most-water/ ) > 思路: > > 1.`枚举`: left bar x, right bar y, (y - x) * min_height O(n^2) `#枚举` ```java // 遍历数组的一个常见的办法:遍历左右边界,且左右边界不能重复 class Solution { public int maxArea(int[] a) { int max = 0 for (int i = 0; < a.length - 1; ++i) { for (int j = i + 1; j < a.length; ++j) { int area = (j - i) * Math.min(a[i], a[j]); max = Math.max(max, area); } } return max; } } ``` > 2.`左右夹逼/双指针法`:左右边界 i, j, 向中间收敛 `#双指针法:左右夹中间,中间到两边` ```java // 遍历数组的一个常见的办法:遍历左右边界,且左右边界不能重复 class Solution { public int maxArea(int[] a) { int max = 0 for (int i = 0, j = a.length - 1; i < j; ) { int minHeight = a[i] < a[j] ? a[i++] : a[j--]; max = Math.max(max, (j - i + 1) * minHeight); } return max; } } ``` #### 70. [climbing stairs](https://leetcode-cn.com/problems/climbing-stairs/) ```java class Solution { public int climbStairs(int n) { if (n <= 2) return n; int f1 = 1; int f2 = 2; int f3 = 3; for (int i = 3; i <= n; i++) { f3 = f1 + f2; f1 = f2; f2 = f1; } return f3; } } ``` #### 1. [2sum](https://leetcode-cn.com/problems/two-sum/) > 方法1:暴力解法 O(n^2) ```java // 遍历数组的一个常见的办法:遍历左右边界,且左右边界不能重复 class Solution { public int[] twoSum(int[] nums, int target) { for (int i = 0; i < nums.length - 1; ++i) { for (int j = i + 1; j < nums.length; ++j) { if (nums[i] + nums[j] == target) { return new int[] {i, j}; } } } return new int[2]; } } ``` > 方法2:两遍哈希表 > > 思想:空间换时间 > > ***保持数组中的每个元素与其索引相互对应的最好方式是什么? 哈希表。*** > > 哈希表查找的时间复杂度为 O(1)。 ```java class Solution { public int[] twoSum(int[] nums, int target) { Map map = new HashMap<>(); for (int i = 0; i < nums.length; ++i) { map.put(target - nums[i], i); } for (int i = 0; i < nums.length; ++i) { if (map.containsKey(nums[i]) && i != map.get(nums[i])) { return new int[] {i, map.get(nums[i])}; } } return new int[2]; } } ``` > 方法3:一遍哈希表 > > 由于在遍历数组的过程中,可以回过头来查找哈希表中是否存储了目标元素的值,因此,没有必要遍历完整的数组将目标元素的值存储到哈希表中,可以边遍历边存储。 ```java class Solution { public int[] twoSum(int[] nums, int target) { Map map = new HashMap<>(); for (int i = 0; i < nums.length; ++i) { int component = target - nums[i]; if (map.containsKey(component)) { return new int[] {map.get(component), i}; } map.put(nums[i], i); } return new int[2]; } } ``` #### 15. [3sum](https://leetcode-cn.com/problems/3sum/) > 思路:转化 a + b = -c > > 1.暴力解法:三重循环 > > 2.HashMap > > 3.`左右夹逼/双指针法`,这种办法有时候需要排序。 ### Linked List #### 141. [linked-list-cycle](https://leetcode.com/problems/linked-list-cycle) > 1.暴力解法:遍历链表,hash/set > > 2.`快慢指针 ` `#快慢指针法` ### Stack #### 20. [valid parentheses](https://leetcode-cn.com/problems/valid-parentheses/description/) > 为啥这个题目可以用栈解决? 具有最近相关性 > > 1.暴力解法:不断replace匹配的括号 -> "" O(n^2) > > a. (){}[] > > b.((({[]}))) > > 2.Stack #### 155. [min Stack](https://leetcode-cn.com/problems/min-stack/) > `两个队列实现栈` > > `两个栈实现队列` #### 84. [largest rectangle in histogram](https://leetcode-cn.com/problems/largest-rectangle-in-histogram/) > 思考一下这个题目和 [container with most water]( https://leetcode-cn.com/problems/container-with-most-water/ ) 有何差别? > > 差别在于这个题目的高度是指所有柱子中最低的高度,而 [container with most water]( https://leetcode-cn.com/problems/container-with-most-water/ ) 则是左右两边最小的高度。 > > 1.暴力解法 O(n^3) > > ```java > for i -> 0, n-2 > for j -> i+1, n-1 > (i, j) -> 最小高度, area > update max-area > ``` > > 2.`暴力加速` > > ```java > for i -> 0, n-1 > 找到 left bound, right bound // 固定中间一个高度,找到左右两边比他小的最小值 > area = height[i] * (right - left) > update max-area > ``` > > 3.`Stack:有序栈(单调栈)找左右边界` `#单调栈` > > 维护一个从小到大的有序栈 > > 那么左边界left bound在栈里,而右边界则是比栈顶元素小的新元素 > > 如果新元素的值大于栈顶元素,说明栈顶元素的右边界没有找到。 > > 构造这个有序栈的过程,其实就是不断找到栈顶元素的右边界的过程(左边界在栈里)。 ### Queue #### 239. [sliding window maximum](https://leetcode-cn.com/problems/sliding-window-maximum/) > 1.暴力 O(n*k) > > 2.`deque O(n)` 滑动窗口 -> 队列 `#单调队列` ### Hash Table #### [242. valid anagram](https://leetcode-cn.com/problems/valid-anagram/description/) > `#切题四件套` > > clarification > > -确定异位词是什么意思? > > -确定大小写是否敏感? > > 方法1: > > 暴力 sort -> sorted_str 是否相等? O(NlogN) > > 方法2:哈希表 > > 统计每个字符出现的频次 > > (1)第一个string,碰到字母加1,第二个string,碰到同样的字母减1,最后看map是否为空 > > (2)int[256] 的数组,ascii -> index #### [49.group anagrams](https://leetcode-cn.com/problems/group-anagrams/) ### Tree #### [94. binary tree inorder traversal](https://leetcode-cn.com/problems/binary-tree-inorder-traversal/) #### [98. validate binary search tree](https://leetcode-cn.com/problems/validate-binary-search-tree/) > 中序遍历是递增的。 ### Recursion #### [70. climbing stairs](https://leetcode-cn.com/problems/climbing-stairs/) > 找 `最近重复性` > > 1: 1 > > 2: 2 > > 3: f(1) + f(2) 1的总的走法,跨2步走上3 + 2的总的走法,跨1步走上3 mutual exclusive, complete exhaustive > > 4: f(2) + f(3) > > ... > > n: f(n) = f(n-1) + f(n-2) Fibonacci ```java class Solution { public int climbStairs(int n) { if(n <= 2) return n; return climbStairs(n-1) + climbStairs(n-2); } } ``` #### [22. generate parenthess * ](https://leetcode-cn.com/problems/generate-parentheses/) ```java // 递归模板 class Solution { public void generateParenthesis(int n) { generate(0, 2 * n, ""); } private void generate(int level, int max, String s) { // terminator if (level >= max) { System.out.println(s); return; } // process current logic String s1 = s + "("; String s2 = s + ")"; // drill down generate(level + 1, max, s1); generate(level + 1, max, s2); // reverse states } } // 检查括号合法性 // left 随时加,只要不超标 n // right 左括号个数 > 右括号个数 class Solution { List result; public List generateParenthesis(int n) { result = new ArrayList<>(); generate(0, 0, n, ""); } private void generate(int left, int right, int n, String s) { // terminator if (left == n && right == n) { result.add(s); return; } // process current logic String s1 = s + "("; String s2 = s + ")"; // drill down if (left < n) { generate(left + 1, right, n, s1); } if (left > right) { generate(left, right + 1, n, s2); } generate(level + 1, max, s1); generate(level + 1, max, s2); // reverse states } } // 本质就是DFS ``` ### Divide and conquer #### [50. Pow(x, n)](https://leetcode-cn.com/problems/powx-n/) > 1.暴力 O(n) > > ```java > result = 1; > for (int i = 0; i < n; i++) { > result *= x; > } > ``` > > 2.分治 O(logn) > > `template`: 1. terminator 2. process (`divide` your big problem) 3. drill down (`conquer` your subproblems ) 4. `merge` sub result 5. reverse states. > > ```java > pow(x, n): > subproblem: subresult = pow(x, n/2); > mege: > if (n % 2 == 1) { > result = subresult * subresult * x; > } else { > result = subresult * subresult; > } > ``` #### [78. subsets](https://leetcode-cn.com/problems/subsets/) * ```java public List> subsets(int[] nums) { List> ans = new ArrayList<>(); if (nums == null) return ans; dfs(ans, nums, new ArrayList(), 0); return ans; } private void dfs(List> ans, int[] nums, List list, int index) { // terminator if (index == nums.length) { ans.add(new ArrayList(list)); return; } // not pick the number at this index dfs(ans, nums, list, index + 1); // pick the number at this index list.add(num[index]); dfs(ans, nums, list, index + 1); // reverse the current state list.remove(list.size() - 1); } // or private void dfs(List> ans, int[] nums, List list, int index) { // terminator if (index == nums.length) { ans.add(new ArrayList(list)); return; } // not pick the number at this index dfs(ans, nums, list.clone, index + 1); // pick the number at this index list.add(num[index]); dfs(ans, nums, list.clone, index + 1); // reverse the current state } ``` ```python class Solution(object): def subsets(self, nums): result =[[]] for num in nums: newsets = [] for subset in result: new_subset = subset + [num] newsets.append(new_subset) result.extend(newsets) return result ``` #### [169. majority element](https://leetcode-cn.com/problems/majority-element/description/) #### [17. letter combinations of a phone number](https://leetcode-cn.com/problems/letter-combinations-of-a-phone-number/) * ```java public List letterCombination(String digits) { if (digits == null || digits.length() == 0) { return new ArrayList(); } Map map = new HashMap<>(); map.put('2', "abc"); map.put('3', "def"); map.put('4', "ghi"); map.put('5', "jkl"); map.put('6', "mno"); map.put('7', "pqrs"); map.put('8', "tuv"); map.put('9', "wxyz"); List res = new ArrayList<>(); search("", digits, 0, res, map); return res; } private void search(String s, String digits, int i, // level List res, Map map) { // terminator if (i == digits.length) { res.add(s); return; } // process String letters = map.get(digits.charAt(i)); for (int j = 0; j < letters.length(); j++) { // drill down search(s+letters.charAt(j), digits, i+1, res, map); } } ``` #### [51. n queens](https://leetcode-cn.com/problems/n-queens/) ```python def sovleNQueen(self, n): if n < 1: return [] self.result = [] # 之前的皇后所攻击的位置(列,pie, na) self.cols = set(); self.pie = set(); self.na = set(); self.DFS(n, 0, []) return self._generate_result(n) def DFS(self, n, row, cur_state): # ternimator if row >= n: self.result.append(cur_state) return # current level! Do it! for col in range(n): # 遍历列 column if col in self.cols or row + col in self.pie or row - col in self.na: # go die continue # update the flags self.cols.add(col) self.pie.add(row+col) self.na.add(row-col) self.DFS(n, row + 1, cur_state + [col]) # reverse state self.cols.remove(col) self.pie.remove(row+col) self.na.remove(row-col) def _generate_result(self, n): board = [] for res in self.result: for i in res: board.append("." * i + "Q" + "." * (n - i - 1)) return [board[i: i + n] for i in range(0, len(board), n)] ``` ### Binary Search #### [69. sqrtx](https://leetcode-cn.com/problems/sqrtx/) ```java class Solution { public int mySqrt(int x) { if (x == 0 || x == 1) { return x; } long left = 1; long right = x / 2; // (x/2)^2 >= x while (left < right) { long mid = left + (right - left + 1) / 2; if (mid * mid > x) { right = mid - 1; } else { left = mid; } } return (int)left; } } ``` * 扩展阅读:[牛顿迭代法](https://www.beyond3d.com/content/articles/8/) #### [33. search in rotated sorted array](https://leetcode-cn.com/problems/search-in-rotated-sorted-array/) > 1.暴力遍历 O(N) > > 2.还原O(log N) -> 升序 -> 二分查找O(log N) > > 3.二分查找O(log N) ```java class Solution { public int search(int[] nums, int target) { int left = 0; int right = nums.length - 1; while (left < right) { int mid = left + (right - left) / 2; if (nums[0] <= nums[mid] && (target > nums[mid] || target < nums[0])) { left = mid + 1; } else if (target > nums[mid] && target < nums[0]) { left = mid + 1; } else { right = mid; } } return left == right && nums[left] == target ? left : -1; } } ``` ### BFS & DFS #### [102. binary tree level order traversal](https://leetcode-cn.com/problems/binary-tree-level-order-traversal/#/description) > 1.BFS > > 2.DFS #### [22. generate parenthess * ](https://leetcode-cn.com/problems/generate-parentheses/) > DFS #### [200. number of islands](https://leetcode-cn.com/problems/number-of-islands/) > floodfill ```java class Solution { int[] dx = new int[]{-1, 1, 0, 0}; int[] dy = new int[]{0, 0, -1, 1}; char[][] g; public int numIslands(char[][] grid) { int islands = 0; g = grid; for (int i = 0; i < g.length; i++) { for (int j = 0; j < g[i].length; j++) { if (g[i][j] == '0') continue; islands += sink(i, j); } } return islands; } private int sink(int i, int j) { if (g[i][j] == '0') { return 0; } g[i][j] = '0'; for (int k = 0; k < dx.length; k++) { int x = i + dx[k]; int y = j + dy[k]; if (x >= 0 && x < g.length && y >= 0 && y < g[x].length) { if (g[x][y] == '0') continue; sink(x, y); } } return 1; } } ``` #### [79. word search](https://leetcode-cn.com/problems/word-search/) 与212一起看 ### Greedy > 从后往前 > > 从前往后 > > 从某个点切入往某一边 #### [455. assign cookies]( https://leetcode-cn.com/problems/assign-cookies/ ) #### [122. best time to buy and sell stock ii](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-ii/description/) #### [55. jump game](https://leetcode-cn.com/problems/jump-game/) ```java class Solution { public boolean canJump(int[] nums) { if (nums == null) { return false; } int endReachable = nums.length - 1; for (int i = nums.length - 1; i >= 0; i--) { if (nums[i] + i >= endReachable) { endReachable = i; } } return endReachable == 0; } } ``` ### Dynamic Programming #### [70. climbing stairs](https://leetcode-cn.com/problems/climbing-stairs/) > 找 `最近重复性` > > 1: 1 > > 2: 2 > > 3: f(1) + f(2) 1的总的走法,跨2步走上3 + 2的总的走法,跨1步走上3 mutual exclusive, complete exhaustive > > 4: f(2) + f(3) > > ... > > n: f(n) = f(n-1) + f(n-2) Fibonacci > ```java class Solution { public int climbStairs(int n) { if(n <= 2) return n; return climbStairs(n-1) + climbStairs(n-2); } } class Solution { public int climbStairs(int n) { if (n <= 2) return n; int f1 = 1; int f2 = 2; int f3 = 3; for (int i = 3; i <= n; i++) { f3 = f1 + f2; f1 = f2; f2 = f1; } return f3; } } class Solution { public int climbStairs(int n) { if (n <= 2) return n; int[] dp = new int[n]; dp[0] = 1; dp[1] = 2; for (int i = 2; i < n; i++) { dp[i] = dp[i-1] + dp[i-2]; } return dp[n-1]; } } ``` > 进阶: > > (1)可以走1, 2,3步,如何解决?easy -> f(n) = f(n-1) + f(n-2) + f(n-3) > > (2)给定一个数组[x1, x2, ..., xn ] 表示可以走 x1,x2, ..., xn步, 如何解决? > > (2)相邻2步的步伐不同,如何解决? medium ##### 小结:一维动态规划:Fibnacci ```java # 递归:自顶向下 O(2^N) int fib(int n) { if (n <= 1) { return n; } return fib(n - 1) + fib(n - 2); // 改进:return n <= 1 ? n : fib(n-1) + fib(n-2); } # 递归 + 备忘录 (记忆化搜索) O(N) int fib(int n, int[] memo) { if (n <= 1) { return n; } if (memo[n] == 0) { memo[n] = fib(n-1, memo) + fib(n-2, memo); } return memo[n]; } # 动态规划:自底向上 O(N) int fib(int n) { int[] dp = new int[n+1]; dp[0] = 0; dp[1] = 1; for (int i = 2; i <= n; i++) { dp[i] = dp[i-1] + dp[i-2]; } return dp[n]; } # 一维的Equation: f(n) = f(n-1) + f(n-2) ``` #### [62. unique paths](https://leetcode-cn.com/problems/unique-paths/) > ``` > opt[i, j] = opt[i+1, j] + opt[i, j+1]; > if a[i, j] == '空地': > opt[i, j] = opt[i+1, j] + opt[i, j+1]; > else: > opt[i, j] = 0; > ``` ```java int countPaths(boolean[][] grid, int row, int col) { if (!validSqure(grid, row, col)) return 0; if (isAtEnd(grid, row, col)) return 1; return countPaths(grid, row + 1, col) + countPaths(grid, row, col+1); } class Solution { // 二维DP数组 public int uniquePaths(int m, int n) { int[][] dp = new int[m][n]; for (int i = 0; i < m; i++) { dp[i][0] = 1; } for (int j = 0; j < n; j++) { dp[0][j] = 1; } for (int i = 1; i < m; i++) { for (int j = 1; j < n; j++) { dp[i][j] = dp[i-1][j] + dp[i][j-1]; } } return dp[m-1][n-1]; } // 一维DP数组:将每一行都压缩在固定的一行更新,更新m次 public int uniquePaths(int m, int n) { int[] dp = new int[n]; Arrays.fill(dp, 1); for (int i = 1; i < m; i++) { for (int j = 1; j < n; j++) { dp[j] += dp[j-1]; } } return dp[n-1]; } } ``` #### [65. unique paths ii](https://leetcode-cn.com/problems/unique-paths-ii/submissions/) ```java class Solution { public int uniquePathsWithObstacles(int[][] obstacleGrid) { int n = obstacleGrid[0].length; int[] dp = new int[n]; dp[0] = 1; for (int[] row : obstacleGrid) { // 注意这种写法 for (int j = 0; j < n; j++) { // 这里j一定要从0开始,因为row[0]可能是障碍物 if (row[j] == 1) { dp[j] = 0; } else if (j > 0){ dp[j] += dp[j-1]; } } } return dp[n-1]; } } ``` #### [64. minimum path sum](https://leetcode-cn.com/problems/minimum-path-sum/submissions/) ```java // 暴力解法 class Solution { public int minPathSum(int[][] grid) { return minPathSum(0, 0, grid); } private int minPathSum(int i, int j, int[][] grid) { if (i == grid.length || j == grid[0].length) return Integer.MAX_VALUE; if (i == grid.length - 1 && j == grid[0].length - 1) { return grid[i][j]; } return grid[i][j] + Math.min(minPathSum(i+1, j, grid), minPathSum(i, j+1, grid)); } } // 动态规划 class Solution { public int minPathSum(int[][] grid) { if (grid == null || grid.length <= 0 || grid[0].length <= 0) { return 0; } int m = grid.length; int n = grid[0].length; int[][] states = new int[m][n]; int sum = 0; for (int j = 0; j < n; j++) { sum += grid[0][j]; states[0][j] = sum; } sum = 0; for (int i = 0; i < m; i++) { sum += grid[i][0]; states[i][0] = sum; } for (int i = 1; i < m; i++) { for (int j = 1; j < n; j++) { states[i][j] = grid[i][j] + Math.min(states[i][j-1], states[i-1][j]); } } return states[m-1][n-1]; } } ``` ##### 小结:二维动态规划:Count the paths ```java // 递归 int countPaths(boolean[][] grid, int row, int col) { if (!validSqure(grid, row, col)) return 0; if (isAtEnd(grid, row, col)) return 1; return countPaths(grid, row + 1, col) + countPaths(grid, row, col+1); } opt[i, j] = opt[i+1, j] + opt[i, j+1]; if a[i, j] == '空地': opt[i, j] = opt[i+1, j] + opt[i, j+1]; else: opt[i, j] = 0; 对比: # 一维的Equation: f(n) = f(n-1) + f(n-2) # 二维的Equation: opt[i, j] = opt[i+1, j] + opt[i, j+1]; ``` #### [120. triangle](https://leetcode-cn.com/problems/triangle/description/) ([一个不错的分析过程](https://leetcode.com/problems/triangle/discuss/38735/Python-easy-to-understand-solutions-(top-down-bottom-up).)) > 1、brute-force 递归, n层: left or right: O(2^N) > > 2、DP > > a. 重复性 `problem(i, j) = min(sub(i+1, j) , sub(i+1, j+1)) + a[i][j]` > > b. 状态数组 `f(i, j) ` > > c. DP方程 `f(i, j) = min(f(i+1, j) , f(i+1, j+1)) + a[i][j]` > ```python class Solution: def minimumTotal(self, triangle): """ :type triangle: List[List[int]] :rtype: int """ dp = triangle for i in range(len(triangle) - 2, -1, -1): for j in range(len(triangle[i])): dp[i][j] += min(dp[i+1][j], dp[i+1][j+1]) print(triangle[0][0]) return dp[0][0] ``` ```java class Solution { public int minimumTotal(List> triangle) { int[] dp = new int[triangle.size() + 1]; for (int i = triangle.size() - 1; i >= 0; i--) { for (int j = 0; j < triangle.get(i).size(); j++) { dp[j] = Math.min(dp[j], dp[j+1]) + triangle.get(i).get(j); } } return dp[0]; } } ``` ```java class Solution { int maxRow; public int minimumTotal(List> triangle) { maxRow = triange.size(); return helper(0, 0, triangle); } private int helper(int row, int col, List> triangle) { if (row == maxRow-1) { return triangle.get(row).get(col); } int left = helper(row+1, col, triangle); int right = helper(row+1, col+1, triangle); return Math.min(left, right) + triangle.get(row).get(col); } } ``` ```java class Solution { int maxRow; Integer[][] memo; public int minimumTotal(List> triangle) { maxRow = triange.size(); memo = new Integer[row][row]; return helper(0, 0, triangle); } private int helper(int row, int col, List> triangle) { if (memo[row][col] != null) { return memo[row][col]; } if (row == maxRow-1) { return memo[row][col] = triangle.get(row).get(col); } int left = helper(row+1, col, triangle); int right = helper(row+1, col+1, triangle); return memo[row][col] = Math.min(left, right) + triangle.get(row).get(col); } } ``` #### [53. maximum sum subarray](https://leetcode-cn.com/problems/maximum-subarray/) > 1、暴力 O(N^2) > > 2、DP > > a. 重复性 max_sum(i) = Max(max_sum(i-1) , 0) + a[i] max_sum(i) : 表示以第i个元素结尾(包含)的连续子数组最大和 > > b. 状态数组 f(i) > > c. DP方程 f(i) = max(f(i-1), 0) + a[i] ```python class Solution(object): def maxSubArray(self, nums): """ 1. dp[i] = max(nums[i], nums[i] + dp[i-1]) 2. 最大子序列和 = 当前元素最大(之前元素和为负) 或者 包含之前+当前之后最大 """ dp = nums for i in range(1, len(nums)): dp[i] = max(0, dp[i-1]) + nums[i] return max(dp) ``` #### [152. maximum product subarray](https://leetcode-cn.com/problems/maximum-product-subarray/description/) #### [322. coin change]( https://leetcode-cn.com/problems/coin-change/description/ ) ([推荐题解](https://leetcode-cn.com/problems/coin-change/solution/ling-qian-dui-huan-by-leetcode/)) > 1、暴力递归 > > 2、BFS > > 3、DP > > a. 重复性 > > b. DP array > > c. DP equation: f(n) = min{f(n-k), for k in [1, 2, 5]} + 1 > > > > 变形:若问`共有多少种组合方式`? > > 分析:这个问题就类似于爬楼梯问题,爬楼梯每次可以爬1阶,每次可以爬2阶,每次也可以爬5阶,问爬到11阶有多少种方式?(不同之处在于硬币[1,2,1]和[1,1,2]是一种情况,而爬楼梯则不是) ```java public class Solution { public int coinChange(int[] coins, int amount) { return coinChange(0, coins, amount); } private int coinChange(int index, int[] coins, int amount) { if (amount == 0) { return 0; } if (index < coins.length && amount > 0) { int maxVal = amount / coins[index]; int minCost = Integer.MAX_VALUE; for (int x = 0; x <= maxVal; x++) { if (amount >= x * coins[index]) { int res = coinChange(index+1, coins, amount - x * coins[index]); if (res != -1) { minCost = Math.min(minCost, res + x); } } } return minCost == Integer.MAX_VALUE ? -1 : minCost; } return -1; } } ``` ```java public class Solution { public int coinChange(int[] coins, int amount) { if (amount <=0) return 0; return coinChange(coins, amount, new int[amount]); } private int coinChange(int[] coins, int remain, int[] count) { if (remain < 0) { return -1; } if (remain == 0) { return 0; } if (count[remain-1] != 0) { return count[remain-1]; } int min = Integer.MAX_VALUE; for (int coin : coins) { int res = coinChange(coins, remain - coin, count); if (res >= 0 && res < min) { min = res + 1; } } count[remain - 1] = min == Integer.MAX_VALUE ? -1 : min; return count[remain - 1]; } } ``` ```java class Solution { public int coinChange(int[] coins, int amount) { int max = amount + 1; int[] dp = new int[amount + 1]; Arrays.fill(dp, max); dp[0] = 0; for (int i = 1; i <= amount; i++) { for (int j = 0; j < coins.length; j++) { if (coins[j] <= i) { dp[i] = Math.min(dp[i], dp[i-coins[j]] + 1); } } } return dp[amount] > amount ? -1 : dp[amount]; } } ``` #### [1143. longest common subsequence](https://leetcode-cn.com/problems/longest-common-subsequence/) > `# 经验` > > 1.对于2个字符串的比较,很多时候,我们会从字符串的尾部向前看。 > > 2.对于2个字符串的变化问题,很多时候会表示成一个二维数组,行和列的元素分别是2个字符串的字符 ```java class Solution { public int longestCommonSubsequence(String text1, String text2) { if (text1 == null || text2 == null) { return 0; } int n = text1.length(); int m = text2.length(); int[][] dp = new int[n+1][m+1]; for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) { if (text1.charAt(i-1) == text2.charAt(j-1)) { dp[i][j] = dp[i-1][j-1] + 1; } else { dp[i][j] = Math.max(dp[i-1][j], dp[i][j-1]); } } } return dp[n][m]; } public int longestCommonSubsequence(String text1, String text2) { if (text1 == null || text2 == null) { return 0; } int n = text1.length(); int m = text2.length(); int[] dp = new int[m+1]; for (int i = 1; i <= n; i++) { int temp = 0; for (int j = 1; j <= m; j++) { int prev = temp; temp = dp[j]; if (text1.charAt(i-1) == text2.charAt(j-1)) { dp[j] = prev + 1; } else { dp[j] = Math.max(dp[j], dp[j-1]) ; } } } return dp[m]; } } ``` #### [72. edit distance (莱文斯坦距离)](https://leetcode-cn.com/problems/edit-distance/) > 1、BFS, two-ended BFS > > 2、DP > > `dp[i][j] // word1.substr(0, i) 与 word2.substr(0, j)的编辑距离` > > (1) if w1[i] == w2[j] > > w1: ............x (i) > > w2: .............x (j) > > edit_dist(i, j) = edit_dist(i-1, j-1) > > (2) if w1[i] != w2[j] > > w1: ............x (i) > > w2: .............y (j) > > edit_dist(i, j) = > > min( > > ​ edit_dist(i-1, j-1) + 1 // x -> y (w1) or y -> x (w2) > > ​ edit_dist(i - 1, j) + 1 // 删除 x > > ​ edit_dist(i, j - 1) + 1 // 删除 y > > ) ```java class Solution { // 单独处理空字符串的dp public int minDistance(String word1, String word2) { if (word1 == null || word2 == null) { return 0; } int n = word1.length(); int m = word2.length(); if (n == 0) { return m; } else if (m == 0) { return n; } int[][] dp = new int[n][m]; for (int j = 0; j < m; j++) { if (word1.charAt(0) == word2.charAt(j)) dp[0][j] = j; else if (j != 0) dp[0][j] = dp[0][j-1] + 1; else dp[0][j] = 1; } for (int i = 0; i < n; i++) { if (word1.charAt(i) == word2.charAt(0)) dp[i][0] = i; else if (i != 0) dp[i][0] = dp[i-1][0] + 1; else dp[i][0] = 1; } for (int i = 1; i < n; i++) { for (int j = 1; j < m; j++) { if (word1.charAt(i) == word2.charAt(j)) { dp[i][j] = min(dp[i-1][j] + 1, dp[i][j-1] + 1, dp[i-1][j-1]); } else { dp[i][j] = min( dp[i-1][j], dp[i][j-1], dp[i-1][j-1]) + 1; } } } return dp[n-1][m-1]; } // 统一处理空字符串的dp public int minDistance(String word1, String word2) { int n = word1.length(); int m = word2.length(); int[][] dp = new int[n+1][m+1]; for (int j = 0; j <= m; j++) { dp[0][j] = j; } for (int i = 0; i <= n; i++) { dp[i][0] = i; } for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) { if (word1.charAt(i-1) == word2.charAt(j-1)) { dp[i][j] = dp[i-1][j-1]; } else { dp[i][j] = min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1]) + 1; } } } return dp[n][m]; } private int min(int x, int y, int z) { return Math.min(x, Math.min(y, z)); } } ``` #### [198. house robber](https://leetcode-cn.com/problems/house-robber/) > `a[i] `: 0...i 能偷到的 max value : a[n-1] > > `a[i][0,1]`: 0: 表示第 i 个房子不偷,1:偷 > > > > `a[i][0] = max(a[i-1][0], a[i-1][1]) ` > > `a[i][1] = a[i-1][0] + nums[i]` ```java class Solution { public int rob(int[] nums) { if (nums == null || nums.length == 0) { return 0; } int n = nums.length; int[][] dp = new int[n][2]; dp[0][0] = 0; dp[0][1] = nums[0]; for(int i = 1; i < n; i++) { dp[i][0] = Math.max(dp[i-1][0], dp[i-1][1]); dp[i][1] = dp[i-1][0] + nums[i]; } return Math.max(dp[n-1][0], dp[n-1][1]); } } ``` > `a[i] `: 0...i ,且包含了 `nums[i]` 必偷的情形,max value: max(a) > > `a[i] = max(a[i-1], a[i-2] + nums[i])` ```java class Solution { public int rob(int[] nums) { if (nums == null || nums.length == 0) { return 0; } if (nums.length == 1) return nums[0]; int n = nums.length; int[] dp = new int[n]; dp[0] = nums[0]; dp[1] = Math.max(nums[0], nums[1]); int res = Math.max(dp[0], dp[1]); for(int i = 2; i < n; i++) { dp[i] = Math.max(dp[i-1], dp[i-2] + nums[i]); res = Math.max(res, dp[i]); } return res; } } ``` #### 股票问题 [分析](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock/solution/yi-ge-fang-fa-tuan-mie-6-dao-gu-piao-wen-ti-by-l-3/) ### Trie #### [208. implement trie prefix tree](https://leetcode-cn.com/problems/implement-trie-prefix-tree/) ```java class Trie { private TrieNode root; /** Initialize your data structure here. */ public Trie() { this.root = new TrieNode('/'); } /** Inserts a word into the trie. */ public void insert(String word) { TrieNode p = root; for (int i = 0; i < word.length(); i++) { int index = word.charAt(i) - 'a'; if (p.children[index] == null) { TrieNode newNode = new TrieNode(word.charAt(i)); p.children[index] = newNode; } p = p.children[index]; } p.isEndingChar = true; } /** Returns if the word is in the trie. */ public boolean search(String word) { TrieNode p = root; for (int i = 0; i < word.length(); i++) { int index = word.charAt(i) - 'a'; if (p.children[index] == null) { return false; } p = p.children[index]; } if (p.isEndingChar) return true; else return false; } /** Returns if there is any word in the trie that starts with the given prefix. */ public boolean startsWith(String prefix) { TrieNode p = root; for (int i = 0; i < prefix.length(); i++) { int index = prefix.charAt(i) - 'a'; if (p.children[index] == null) { return false; } p = p.children[index]; } return true; } class TrieNode { public char data; public TrieNode[] children = new TrieNode[26]; public boolean isEndingChar = false; public TrieNode(char data) { this.data = data; } } } /** * Your Trie object will be instantiated and called as such: * Trie obj = new Trie(); * obj.insert(word); * boolean param_2 = obj.search(word); * boolean param_3 = obj.startsWith(prefix); */ ``` #### [212. word search ii](https://leetcode-cn.com/problems/word-search-ii/) > 1、words 遍历 --> board 中 search > > 2、Trie > > a. all words --> Trie > > b. board --> DFS ```python dx = [-1, 1, 0, 0] dy = [0, 0, -1, 1] END_OF_WORD = "#" class Solution(object): def findWords(self, board, words): """ :type board: List[List[str]] :type words: List[str] :rtype: List[str] """ if not board or not board[0]: return [] if not words: return [] self.result = set() # 根据words构建Trie root = {} for word in words: node = root for char in word: node = node.setdefault(char, {}) node[END_OF_WORD] = END_OF_WORD self.m, self.n = len(board), len(board[0]) for i in range(self.m): for j in range(self.n): if board[i][j] in root: # 剪枝 self._dfs(board, i, j, "", root) return list(self.result) def _dfs(self, board, i, j, cur_word, cur_dict): # terminator cur_word += board[i][j] cur_dict = cur_dict[board[i][j]] if END_OF_WORD in cur_dict: self.result.add(cur_word) # process current logic tmp, board[i][j] = board[i][j], '@' # @表示 board[i][j]用过了,就不再用了 # drill down for k in range(4): x, y = i + dx[k], j + dy[k] if 0 <= x < self.m and 0 <= y < self.n and board[x][y] != '@' and board[x][y] in cur_dict: self._dfs(board, x, y, cur_word, cur_dict) # 恢复 board board[i][j] = tmp ``` ### Sort #### [242. valid anagram](https://leetcode-cn.com/problems/valid-anagram/) ```java class Solution { public boolean isAnagram(String s, String t) { if (s.length() != t.length()) return false; char[] sArray = s.toCharArray(); char[] tArray = t.toCharArray(); Arrays.sort(sArray); Arrays.sort(tArray); return Arrays.equals(sArray, tArray); } } ``` ### 位运算 #### [231. power of two](https://leetcode-cn.com/problems/power-of-two/) ================================================ FILE: leetcode/src/linkedlistcycle_141/LinkedListCycle.java ================================================ package linkedlistcycle_141; /** * 141. Linked List Cycle * https://leetcode.com/problems/linked-list-cycle/ */ public class LinkedListCycle { public boolean hasCycle(ListNode head) { if (head == null || head.next == null) { return false; } ListNode fast = head; ListNode slow = head; while (fast != null && fast.next != null) { fast = fast.next.next; slow = slow.next; if (fast == slow) return true; } return false; } public static class ListNode { int val; ListNode next; ListNode(int x) { val = x;} } } ================================================ FILE: leetcode/src/lrucache_146/LRUCache.java ================================================ package lrucache_146; import java.util.HashMap; /** * 146. LRU Cache * https://leetcode.com/problems/lru-cache/description/ */ public class LRUCache { private Node head; private Node tail; private HashMap map; private int capacity; public LRUCache(int capacity) { this.capacity = capacity; this.map = new HashMap<>(capacity * 4 / 3); head = new Node(-1,-1); tail = new Node(-1,-1); head.next = tail; tail.pre = head; } public int get(int key) { // key 不存在 if (!map.containsKey(key)) { return -1; } // key 存在 Node node = map.get(key); delete(node); insertToHead(node); return node.value; } public void put(int key, int value) { // key 存在 if (map.containsKey(key)) { Node node = map.get(key); delete(node); } // key 不存在 Node node = new Node(key, value); insertToHead(node); map.put(key, node); if (map.size() > capacity) { Node remove = deleteTail(); map.remove(remove.key); } } private void delete(Node node) { Node next = node.next; Node pre = node.pre; pre.next = next; next.pre = pre; node.next = null; node.pre = null; } private void insertToHead(Node node) { Node next = head.next; node.next = next; node.pre = head; next.pre = node; head.next = node; } private Node deleteTail() { Node node = tail.pre; Node pre = node.pre; tail.pre = pre; pre.next = tail; node.next = null; node.pre = null; return node; } private class Node { int key; int value; Node pre; Node next; public Node (int key, int value) { this.key = key; this.value = value; } } } ================================================ FILE: leetcode/src/mergetwosortedlist_21/Merge2SortedLists.java ================================================ package mergetwosortedlist_21; /** * 21. Merge Two Sorted Lists * https://leetcode.com/problems/merge-two-sorted-lists/ */ public class Merge2SortedLists { public ListNode mergeTwoLists(ListNode l1, ListNode l2) { if (l1 == null) return l2; if (l2 == null) return l1; // 利用哨兵(前哨节点)简化实现难度 ListNode outpost = new ListNode(-1); ListNode temp = outpost; while (l1 != null && l2 != null) { if (l1.val <= l2.val) { temp.next = l1; l1 = l1.next; } else { temp.next = l2; l2 = l2.next; } temp = temp.next; } if (l1 == null) { temp.next = l2; } if (l2 == null) { temp.next = l1; } return outpost.next; } public ListNode mergeTwoListsRecur(ListNode l1, ListNode l2) { if (l1 == null) return l2; if (l2 == null) return l1; if (l1.val < l2.val) { l1.next = mergeTwoListsRecur(l1.next, l2); return l1; } else { l2.next = mergeTwoListsRecur(l1, l2.next); return l2; } } public static class ListNode { int val; ListNode next; ListNode(int x) { val = x;} } } ================================================ FILE: leetcode/src/middleofthelinkedlist_876/MiddleNode.java ================================================ package middleofthelinkedlist_876; /** * 876. Middle of the Linked List * https://leetcode.com/problems/middle-of-the-linked-list/ */ public class MiddleNode { public ListNode middleNode(ListNode head) { ListNode fast = head; ListNode slow = head; while (fast != null && fast.next != null) { fast = fast.next.next; slow = slow.next; } return slow; } public static class ListNode { int val; ListNode next; ListNode(int x) { val = x;} } } ================================================ FILE: leetcode/src/palindromelinkedlist_234/PalindromeLinkedList.java ================================================ package palindromelinkedlist_234; /** * 234. Palindrome Linked List * https://leetcode.com/problems/palindrome-linked-list/ * Solution: * 1. 找到中间节点,将单链表后半段逆置 * 2. 左半段从左到右,右半段从右到左,比较2段链表是否对应相等 * 3. 对比完之后,最好能将右半段逆置,还原链表 * 示例: * 1->2->2->1 * 1->2->2<-1 (第2个2指向null) */ public class PalindromeLinkedList { public boolean isPalindrome(ListNode head) { // 1. 找到单链表的中间节点 ListNode fast = head; ListNode slow = head; while (fast != null && fast.next != null) { fast = fast.next.next; slow = slow.next; // 此时slow就是中间节点 } // 2. 从中间节点开始,将右半段链表逆置 ListNode pre = null; ListNode next = null; while (slow != null) { next = slow.next; slow.next = pre; pre = slow; slow = next; } // 3. 左半段从左到右,右半段从右到左,比较2段链表是否对应相等 fast = head; slow = pre; while (slow != null) { if (fast.val != slow.val) { return false; } fast = fast.next; slow = slow.next; } // 4. 对比完之后,最好能将右半段逆置,还原链表 return true; } public static class ListNode { int val; ListNode next; ListNode(int x) { this.val = val; } } } ================================================ FILE: leetcode/src/removenthnodefromendoflist_19/RemoveNthNodeFromEndOfList.java ================================================ package removenthnodefromendoflist_19; /** * 19. Remove Nth Node From End of List * https://leetcode.com/problems/remove-nth-node-from-end-of-list/ */ public class RemoveNthNodeFromEndOfList { public ListNode removeNthFromEnd(ListNode head, int n) { ListNode p1 = head; for (int i = 0; i < n; i++) { p1 = p1.next; } ListNode p2 = head; // 要删除的节点 ListNode pre = null; while (p1 != null) { p1 = p1.next; pre = p2; p2 = p2.next; } if (p2 == head) { // 要删除的节点为head,特殊处理 head = head.next; } else { pre.next = p2.next; p2.next = null; } return head; } public static class ListNode { int val; ListNode next; ListNode(int x) { this.val = val; } } } ================================================ FILE: leetcode/src/reverselinkedlist_206/ReverseList.java ================================================ package reverselinkedlist_206; /** * 206. Reverse Linked List * https://leetcode.com/problems/reverse-linked-list/ */ public class ReverseList { public ListNode reverseList(ListNode head) { if (head == null || head.next == null) return head; ListNode pre = null; ListNode next = null; while (head != null) { next = head.next; head.next = pre; pre = head; head = next; } return pre; } public static class ListNode { int val; ListNode next; ListNode(int x) { this.val = val; } } } ================================================ FILE: leetcode/src/threesum_015/ThreeSum.java ================================================ package threesum_015; public class ThreeSum { } ================================================ FILE: leetcode/src/twosum_001/TwoSum.java ================================================ package twosum_001; public class TwoSum { public static void main(String[] args) { } } ================================================ FILE: offer/src/com/ex/offer/Attention.txt ================================================ - 面试沟通 (1)面试时,要经常和面试官沟通。沟通好题目条件、问题的意图之后再动手coding或者回答问题。 (2)面试中,若面试官提出新的概念,面试者需要和面试官积极沟通,多问几个问题,把概念搞清楚。 (3)面试中,若有多种解法,且多种解法各有优缺点,则需要和面试官沟通,确定问题需要的最合适的解法。 (4)考虑性能的时候,是关注时间还是空间?其复杂度要求是多少? 考虑实现的时候,是否可以修改原来的结构?是否可以在新的结构中操作? - 排序和查找 --查找:顺序查找 二分查找 哈希表查找 二叉排序树(其实就是二叉搜索树)查找 (1) 二分查找:要求在排序或者是部分排序的数组中查找一个数字或者统计某个数字出现的次数,可以尝试二分查找。 (2) 哈希表&二叉排序树:重点在于考察数据结构本身,不在于算法 (3) 哈希表:优点是能够在O(1)内找到某一个元素,是效率最高的查找方式。缺点是需要额外空间实现哈希表。 -- 排序:插入排序 冒泡排序 归并排序 快速排序 关注其实现,性能,场景等 - 回溯法: (1)类似于暴力解答 (2)常常使用递归,回溯法很适合递归实现。递归可以用栈来模拟。 注:二维数组或者迷宫问题,可以尝试回溯法。 - 递归与循环: (1)递归:由于递归实现更加简洁,若没有特殊要求,可以优先选用递归实现。 效率问题:递归可能存在大量的重复运算,因此性能可能弱于循环。如:Ex_10_Fibonacci 栈溢出问题:递归还可能引起调用栈溢出的问题。 (2)思考:一般用递归的方式思考答案,用循环的方式实现代码。 - 动态规划 (1)动态规划多可以用递归解决; (2)但是递归中有很多需要重复计算的项,如Ex_10_Fibonacci (3)动态规划从下到上,将重复的子问题的答案存储起来,减少了重复计算的时间浪费,但是增加了空间的消耗。 - 贪心算法 - 说明 (1) 若求某个问题的最优解,且该问题可以分解为多个子问题,则可以尝试动态规划。 递归操作是自上而下,动态规划则是自下而上,避免不必要的重复计算。 若是某个问题有特殊的选择,那么这个问题的最优解可能就是贪婪算法得出的。 (2) 思考路线:递归->动态规划->贪婪算法 - 位运算 (1) 基本运算:与、或、异或、左移、右移 (2) 把一个整数减1之后再和原来的整数做位与运算,相当于将整数的二进制表示中的最右边的1变成0: x = (x - 1) & x : 将x的二进制最右边的1变成了0 - 高质量的代码 -- 代码的规范性 (1)书写清晰 (2)布局清晰 (3)命名合理 -- 代码的完整性 (1)功能测试:基本功能 (2)边界测试:边界值 (3)负面测试:非法值 -- 代码的鲁棒性:程序对输入的合法性判断以及对非法输入的处理 --- 容错性 --- 防御性编程 --注:在处理链表问题时,当我们用一个指针遍历链表无法解决问题时,可以尝试用2个指针解决问题。 可以让一个指针遍历的速度快一点(一次走2步,或者先走若干步),让另外一个指针遍历的速度慢一点 --注: (1)若面试题是关于n位的整数且没有限定n的取值范围,或者输入任意大小的整数,则此题目可能需要考虑大数问题。 (2)大数问题如何处理?字符串是一种有效的地表示大数地方法。 (3)例:Ex_17_AddTwoBigNumber - 解决面试题的思路 -- 在解决笔试或者面试题目之前,应该思考清楚题目的解决思路,然后再进行编码。 -- 画图 -- 举例 -- 分解 -- 注: 1. 二叉树遍历 -- 先序 Stack T26:树的子结构; T7:重建二叉树 T37:树的序列化和反序列化 -- 中序 Stack T7:重建二叉树 -- 后序 Stack T33:判断一个序列是否是BST的后序(先序)遍历序列; -- Morris遍历 Stack -- 宽度优先遍历:层遍历(也叫广度优先遍历)T32:从上到下打印二叉树 -- 不分行 Queue -- 分行 Queue + 记录节点 -- 之字形 Stack * 2 -- 深度优先遍历:先序遍历 T34:二叉树中和为某一个值的路径 2. 二叉树分类 -- 二叉搜索树 -- 红黑树 -- 二叉堆 3. 技巧: -- 若要求处理二叉树的遍历序列,则可以先找到二叉树的根节点, 然后基于根节点将二叉树的遍历序列分成左子树和右子树对应的子序列 分界:i 然后递归处理这2个子序列.模式如下 process(sequence, start, end) -> process(sequence, start, i-1) -> process(sequence, i, end) --- 例如:面试题33:判断一个序列是否是BST的后序(先序)遍历序列; 面试题7:重建二叉树 - 优化时间和空间效率 -- 注:若需要判断多个字符是否在某个字符串中出现过或者统计其出现的次数,考虑哈希表,或者考虑基于数组创建一个简单的哈希表, 这样可以用很小得空间消耗换来时间效率的提升。 -- 如何降低时间复杂度? 1. 改用更高效的算法 2. 空间换时间 -- 递归与动态规划 - 面试考察能力 --编程能力 --学习能力 --抽象建模能力 --发散思维能力 --知识迁移能力 --沟通能力 ******************************************************* - 编写程序时需要考虑的一些问题: -- 关注鲁棒性:边界条件 特殊输入 错误处理等 -- 编写过程: 测试用例 -> 找出规律 -> 编程 -> 检验 (基本功能、边界条件、错误处理) -> 优化 ================================================ FILE: offer/src/com/ex/offer/Ex_03_FindDuplicatedNumInArray.java ================================================ package com.ex.offer; import java.util.HashMap; /** * problem 3: 数组中重复的数字 * 在一个长度为n的数组里的所有数字都在0到n-1的范围内。 * 数组中某些数字是重复的,但不知道有几个数字是重复的。 * 也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 * 例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是第一个重复的数字2。 */ public class Ex_03_FindDuplicatedNumInArray { public boolean duplicate(int numbers[], int [] duplication) { if (numbers == null || numbers.length < 2) { return false; } HashMap map = new HashMap<>(); for (int i = 0; i < numbers.length; i++) { if (!map.containsKey(numbers[i])) { map.put(numbers[i], 1); } else { duplication[0] = numbers[i]; return true; } } return false; } /** * 由于numbers数组中的元素[0,n-1] * 若没有重复元素,排序之后i位置为i * 若有重复元素,则i位置为i,但是其他位置也可能有i * 思路: * 遍历数组 * 若i位置为i,i++ * 若i位置不是i * (1)m = numbers[i], 则m的正确位置为numbers[m], 若numbers[m] = m,则重复元素就是m,否则 * (2) 交换m到正确的位置上。 * @param numbers * @param duplication * @return */ public boolean duplicateImprove(int numbers[], int [] duplication) { if (numbers == null || numbers.length < 2) { return false; } for (int i = 0; i < numbers.length; i++) { if (numbers[i] == i) { i++; } else { int m = numbers[i]; if (numbers[m] == m) { duplication[0] = m; return true; } else { swap(numbers, i, m); } } } return false; } private static void swap(int[] arr, int i, int j) { int tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp; } } ================================================ FILE: offer/src/com/ex/offer/Ex_03_FindDuplicatedNumInWithoutChangeArray.java ================================================ package com.ex.offer; /** * problem 3: 不修改数组找出重复的数字。 [binary search] * * 特性:若数组中没有重复元素,则[x,y]范围内的数字个数为y-x+1,若超出这个个数,则说明有重复的数字。 */ public class Ex_03_FindDuplicatedNumInWithoutChangeArray { public static int duplicate(int numbers[]) { if (numbers == null || numbers.length < 2) { return -1; } int start = 1; int end = numbers.length - 1; while(start <= end) { int mid = start + ((end - start) >> 1); // 这里 >> 一定要放在括号内 int count = countRange(numbers, start, mid); if (start == end) { if (count > 1) { return start; } else { break; } } if (count > (mid - start + 1)) { end = mid; } else { start = mid + 1; } } return -1; } private static int countRange(int[] numbers, int start, int end) { if (numbers == null) { return 0; } int count = 0; for (int i = 0; i < numbers.length; i++) { if (numbers[i] <= end && numbers[i] >= start) { count++; } } return count; } public static void main(String[] args) { int[] numbers = new int[]{2, 3, 5, 4, 3, 2, 6, 7}; int duplicate = duplicate(numbers); System.out.println(duplicate); } } ================================================ FILE: offer/src/com/ex/offer/Ex_04_FindNumIn2VArray.java ================================================ package com.ex.offer; /** * 1.在一个二维数组中,每一行都按照从左到右递增的顺序排序, * 每一列都按照从上到下递增的顺序排序。请完成一个函数, * 输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。 */ public class Ex_04_FindNumIn2VArray { public static boolean Find(int target, int[][] array) { if (array == null || array.length == 0) { return false; } //从右上角向左下遍历 int row = 0; int col = array[0].length - 1; while (row < array.length && col > -1) { if (array[row][col] == target) { return true; } else if (array[row][col] < target) { row++; } else { col--; } } return false; } public static void main(String[] args) { int[][] array = new int[][] { { 0, 1, 2, 3, 4, 5, 6 },// 0 { 10, 12, 13, 15, 16, 17, 18 },// 1 { 23, 24, 25, 26, 27, 28, 29 },// 2 { 44, 45, 46, 47, 48, 49, 50 },// 3 { 65, 66, 67, 68, 69, 70, 71 },// 4 { 96, 97, 98, 99, 100, 111, 122 },// 5 { 166, 176, 186, 187, 190, 195, 200 },// 6 { 233, 243, 321, 341, 356, 370, 380 } // 7 }; int target = 233; System.out.println(Find(target, array)); } } ================================================ FILE: offer/src/com/ex/offer/Ex_05_ReplaceSpace.java ================================================ package com.ex.offer; /** * 2.请实现一个函数,将一个字符串中的空格替换成“%20”。 * 例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。 */ public class Ex_05_ReplaceSpace { public static String replaceSpace(StringBuffer str) { String s = str.toString(); String res = s.replaceAll(" ", "%20"); return res; } /** * 问题1:替换字符串,是在原来的字符串上做替换,还是新开辟一个字符串做替换! * 问题2:在当前字符串替换,怎么替换才更有效率(不考虑java里现有的replace方法)。 * 从前往后替换,后面的字符要不断往后移动,要多次移动,所以效率低下 * 从后往前,先计算需要多少空间,然后从后往前移动,则每个字符只移动一次,这样效率更高。 * * @param str * @return */ public static String replaceBlank(StringBuffer str) { if (str == null) { return null; } int spaceNum = 0; for (int i = 0; i < str.length(); i++) { if (str.charAt(i) == ' ') { spaceNum++; } } int len = str.length() + 2 * spaceNum; int indexOld = str.length() - 1; int indexNew = len - 1; str.setLength(len); while (indexOld >= 0) { if (str.charAt(indexOld) == ' ') { str.setCharAt(indexNew--, '0'); str.setCharAt(indexNew--, '2'); str.setCharAt(indexNew--, '%'); } else { str.setCharAt(indexNew--, str.charAt(indexOld)); } indexOld--; } return str.toString(); } public static void main(String[] args) { StringBuffer str = new StringBuffer(); str.append("We Are Happy"); String s = replaceBlank(str); System.out.print(s); System.out.println(); StringBuffer str1 = new StringBuffer(); str1.append(""); String s1 = replaceBlank(str1); System.out.print(s1); } } ================================================ FILE: offer/src/com/ex/offer/Ex_06_PrintListFromTailToHead.java ================================================ package com.ex.offer; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Stack; public class Ex_06_PrintListFromTailToHead { public static ArrayList printListFromTailToHead(ListNode listNode) { if (listNode == null) { return new ArrayList<>(); } Stack stack = new Stack<>(); ArrayList list = new ArrayList<>(); for (ListNode node = listNode; node != null; node = node.next) { stack.push(node.val); } while(!stack.isEmpty()) { list.add(stack.pop()); } return list; } public static ArrayList printListFromTailToHeadRecur(ListNode listNode) { ArrayList list = new ArrayList<>(); if (listNode != null) { if (listNode.next != null) { ArrayList temp = new ArrayList<>(); temp = printListFromTailToHeadRecur(listNode.next); for (Integer i : temp) { list.add(i); } } list.add(listNode.val); } return list; } public static ArrayList printListFromTailToHeadWithCollections(ListNode listNode) { ArrayList list = new ArrayList<>(); for (ListNode p = listNode; p != null; p = p.next) { list.add(p.val); } Collections.reverse(list); return list; } public static void main(String[] args) { ListNode head = new ListNode(1); head.next = new ListNode(2); head.next.next = new ListNode(3); head.next.next.next = new ListNode(4); ArrayList list = new ArrayList<>(); list = printListFromTailToHeadRecur(head); } } ================================================ FILE: offer/src/com/ex/offer/Ex_07_ReConstructBT.java ================================================ package com.ex.offer; /** * 4.输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。 * 假设输入的前序遍历和中序遍历的结果中都不含重复的数字。 * 例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6}, * 则重建二叉树并返回。 * * 思路: * 1.pre负责确定树的root; * 2.in负责确定树的root.left,root.right,即左右子树; * 3.递归1,2 */ public class Ex_07_ReConstructBT { public static TreeNode reConstructBinaryTree(int[] pre, int[] in) { if (pre == null || in == null || pre.length == 0 || in.length == 0) { return null; } TreeNode root = reConstructBinaryTree(pre, 0, pre.length-1, in, 0, in.length-1); return root; } private static TreeNode reConstructBinaryTree(int[] pre, int preStart, int preEnd, int[] in, int inStart, int inEnd) { if (preStart > preEnd || inStart > inEnd) { return null; } TreeNode root = new TreeNode(pre[preStart]); for (int i = inStart; i <= inEnd; i++) { if (in[i] == pre[preStart]) { root.left = reConstructBinaryTree(pre, preStart+1, preStart+(i-inStart), in, inStart, i-1); root.right = reConstructBinaryTree(pre, preStart + 1 + (i-inStart), preEnd, in, i+1, inEnd); break; } } return root; } } ================================================ FILE: offer/src/com/ex/offer/Ex_08_DescendantNode.java ================================================ package com.ex.offer; public class Ex_08_DescendantNode { 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 getNextNode(Node node) { if (node == null) { return node; } if (node.right != null) { return getLeftMost(node.right); } else { Node parent = node.parent; while (parent != null && parent.left != 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: " + getNextNode(test).value); test = head.left.left.right; System.out.println(test.value + " next: " + getNextNode(test).value); test = head.left; System.out.println(test.value + " next: " + getNextNode(test).value); test = head.left.right; System.out.println(test.value + " next: " + getNextNode(test).value); test = head.left.right.right; System.out.println(test.value + " next: " + getNextNode(test).value); test = head; System.out.println(test.value + " next: " + getNextNode(test).value); test = head.right.left.left; System.out.println(test.value + " next: " + getNextNode(test).value); test = head.right.left; System.out.println(test.value + " next: " + getNextNode(test).value); test = head.right; System.out.println(test.value + " next: " + getNextNode(test).value); test = head.right.right; // 10's next is null System.out.println(test.value + " next: " + getNextNode(test)); } } ================================================ FILE: offer/src/com/ex/offer/Ex_08_GetNextNodeInBT.java ================================================ package com.ex.offer; /** * 后继节点 */ public class Ex_08_GetNextNodeInBT { public TreeLinkNode GetNext(TreeLinkNode pNode) { if (pNode == null) { return null; } if (pNode.right != null) { return getMostLeft(pNode.right); } else { TreeLinkNode parent = pNode.parent; while (parent != null && parent.left != pNode) { pNode = parent; parent = parent.parent; } return parent; } } private TreeLinkNode getMostLeft(TreeLinkNode node) { while (node.left != null) { node = node.left; } return node; } } ================================================ FILE: offer/src/com/ex/offer/Ex_09_QueueWithTwoStack.java ================================================ package com.ex.offer; import java.util.Stack; /** * problem 9: 用两个栈来实现一个队列,完成队列的Push和Pop操作。 队列中的元素为int类型。 */ public class Ex_09_QueueWithTwoStack { Stack stack1 = new Stack(); Stack stack2 = new Stack(); public void push(int node) { stack1.push(node); } public int pop() { int res = 0; while (!stack1.isEmpty()) { res = stack1.pop(); if (!stack1.isEmpty()) { stack2.push(res); } } while (!stack2.isEmpty()) { stack1.push(stack2.pop()); } return res; } } ================================================ FILE: offer/src/com/ex/offer/Ex_09_StackWithTwoQueue.java ================================================ package com.ex.offer; import java.util.LinkedList; import java.util.Queue; public class Ex_09_StackWithTwoQueue { Queue queue1 = new LinkedList<>(); Queue queue2 = new LinkedList<>(); public void push(int node) { queue1.add(node); } public int pop() { int res = 0; while (!queue1.isEmpty()) { res = queue1.poll(); if (!queue1.isEmpty()) { queue2.add(res); } } while (!queue2.isEmpty()) { queue1.add(queue2.poll()); } return res; } public static void main(String[] args) { Ex_09_StackWithTwoQueue stack = new Ex_09_StackWithTwoQueue(); stack.push(1); stack.push(2); stack.push(3); stack.push(4); System.out.println(stack.pop()); System.out.println(stack.pop()); System.out.println(stack.pop()); System.out.println(stack.pop()); System.out.println(stack.pop()); System.out.println(stack.pop()); } } ================================================ FILE: offer/src/com/ex/offer/Ex_10_Fibonacci.java ================================================ package com.ex.offer; public class Ex_10_Fibonacci { public static int fib1(int n) { if (n <= 0) { return 0; } if (n == 1) { return 1; } return fib1(n-1) + fib1(n-2); } /** * |Fn+1 Fn | |1 1|^n * |Fn Fn-1 | |1 0| * @param n * @return */ public static int fib2(int n) { return -1; } public static int Fibonacci(int n) { if (n <= 0) { return 0; } if (n == 1) { return 1; } int fib1 = 0; int fib2 = 1; int fibN = 0; for (int i = 2; i <= n; i++) { fibN = fib1 + fib2; fib1 = fib2; fib2 = fibN; } return fibN; } public static void main(String[] args) { int n = 5; System.out.println(fib1(n)); System.out.println(Fibonacci(n)); } } ================================================ FILE: offer/src/com/ex/offer/Ex_10_JumpFloor.java ================================================ package com.ex.offer; public class Ex_10_JumpFloor { public int jumpFloor(int target) { if (target == 1 || target == 2) { return target; } return jumpFloor(target - 1) + jumpFloor(target - 2); } public int JumpFloor(int target) { if (target == 1 || target == 2) { return target; } int jump1 = 1; int jump2 = 2; int jumpN = 0; for (int i = 3; i <= target; i++) { jumpN = jump1 + jump2; jump1 = jump2; jump2 = jumpN; } return jumpN; } } ================================================ FILE: offer/src/com/ex/offer/Ex_10_JumpFloorII.java ================================================ package com.ex.offer; /** * f(n) * f(1) = 1; * f(2) = f(2-1) + f(2-2) = 1 + 1 = 2; * f(3) = f(3-1) + f(3-2) + f(3-2) * = f(0) + f(1) + f(2) * f(n-1) = f(0) + f(1) + f(2) + ... + f(n-2) * f(n) = f(0) + f(1) + f(2) + ... + f(n-2) + f(n-1) * = f(n-1) + f(n-1) * = 2 * f(n-1) * = 2 ^ 2 * f(n-2) * = ... * = 2 ^ (n-1) * f(1) * = 2 ^ (n-1) * * f(n) = 2 ^ (n-1) */ public class Ex_10_JumpFloorII { public static int JumpFloorII(int target) { if (target == 1) { return 1; } int res = 1; for (int i = 1; i < target; i++) { res *= 2; } return res; } } ================================================ FILE: offer/src/com/ex/offer/Ex_10_RectCover.java ================================================ package com.ex.offer; public class Ex_10_RectCover { public int RectCover(int target) { if (target == 1 || target == 2) { return target; } int rect1 = 1; int rect2 = 2; int rectN = 0; for (int i = 3; i <= target; i++) { rectN = rect1 + rect2; rect1 = rect2; rect2 = rectN; } return rectN; } } ================================================ FILE: offer/src/com/ex/offer/Ex_11_MinNumOfRotatingArray.java ================================================ package com.ex.offer; /** * 把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。 * 输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。 * 例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。 * NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。 */ public class Ex_11_MinNumOfRotatingArray { public static int minNumberInRotateArray(int [] array) { if (array == null || array.length <= 0) { return 0; } int low = 0; int high = array.length - 1; int mid = low; // 若是旋转数组本身,则第一个元素是最小值,直接不进入循环 // 在这里,假设前面的递增数组第一个元素 > 后面的递增数组最后一个元素 while (array[low] >= array[high]) { if (high - low == 1) { mid = high; break; } mid = low + (high - low) / 2; // 如果arr[low] == array[mid] == array[high], // 则无法用中间和两边比较的方式判断mid属于左边的递增序列还是右边的递增序列 if (array[low] == array[high] && array[low] == array[mid]) { return minInOrder(array, low, high); } if (array[mid] >= array[low]) { // 此时,mid位于第一个递增数组中,因此min位于第二个递增数组中 low = mid; } else if (array[mid] <= array[high]){ high = mid; } } return array[mid]; } private static int minInOrder(int[] array, int low, int high) { int min = array[low]; for (int i = low + 1; i <= high; i++) { if (array[i] < min) { min = array[i]; } } return min; } public static void main(String[] args) { int[] array = new int[]{3,4,5,1,2}; int[] array1 = new int[]{1,2,3,4,5}; System.out.println(minNumberInRotateArray(array)); System.out.println(minNumberInRotateArray(array1)); int[] array2 = new int[]{1, 0, 1, 1, 1}; int[] array3 = new int[]{1, 1, 1, 0, 1}; System.out.println(minNumberInRotateArray(array2)); System.out.println(minNumberInRotateArray(array3)); } } ================================================ FILE: offer/src/com/ex/offer/Ex_11_SortAges.java ================================================ package com.ex.offer; public class Ex_11_SortAges { // counting sort public static void sortAges(int[] ages) { if (ages == null || ages.length <= 0) { return; } int maxAge = 99; int[] countAge = new int[maxAge + 1]; // 0~99 for (int i = 0; i < ages.length; i++) { countAge[ages[i]]++; } int index = 0; for (int i = 0; i <= maxAge; i++) { // i:年龄 for (int j = 0; j < countAge[i]; j++) { // j:年龄的个数 ages[index++] = i; } } } public static void printArray(int[] arr) { for (int i : arr) { System.out.print(i + " "); } System.out.println(); } public static void main(String[] args) { int[] ages = new int[]{2, 8, 99, 6, 20, 99, 88, 77 ,99, 44, 55, 55, 77, 23, 18}; printArray(ages); sortAges(ages); printArray(ages); } } ================================================ FILE: offer/src/com/ex/offer/Ex_12_HasPathInMatrix.java ================================================ package com.ex.offer; public class Ex_12_HasPathInMatrix { public static boolean hasPath(char[] matrix, int rows, int cols, char[] str) { if (matrix == null || matrix.length <= 0 || str == null || str.length <= 0 || rows <= 0 || cols <= 0 || matrix.length != rows * cols || matrix.length < str.length) { return false; } boolean[] visited = new boolean[rows * cols]; int pathLength = 0; for (int i = 0; i < rows; i++) { for (int j = 0; j < cols; j++) { if (hasPathCore(matrix, rows, cols,str, i, j, visited, pathLength)) { return true; } } } return false; } public static boolean hasPathCore(char[] matrix, int rows, int cols, char[] str, int rowStart, int colStart, boolean[] visited, int pathLength) { boolean flag = false; if (rowStart >= 0 && rowStart < rows && colStart >= 0 && colStart < cols && !visited[rowStart*cols+colStart] && matrix[rowStart*cols+colStart] == str[pathLength]) { pathLength++; visited[rowStart*cols+colStart] = true; if (pathLength == str.length) { return true; } flag = hasPathCore(matrix, rows, cols, str, rowStart, colStart+1, visited, pathLength) || hasPathCore(matrix, rows, cols, str, rowStart+1, colStart, visited, pathLength) || hasPathCore(matrix, rows, cols, str, rowStart, colStart-1, visited, pathLength) || hasPathCore(matrix, rows, cols, str, rowStart-1, colStart, visited, pathLength); if (!flag) { pathLength--; visited[rowStart*cols+colStart] = false; } } return flag; } public static void main(String[] args) { String m = "ABCESFCSADEE"; String s = "ABCCED"; char[] matrix = m.toCharArray(); char[] str = s.toCharArray(); System.out.println(hasPath(matrix, 3, 4, str)); } } ================================================ FILE: offer/src/com/ex/offer/Ex_13_MovingCount.java ================================================ package com.ex.offer; public class Ex_13_MovingCount { public int movingCount(int threshold, int rows, int cols) { if (threshold < 0 || rows <= 0 || cols <= 0) { return 0; } boolean[] visited = new boolean[rows * cols]; int count = helper(threshold, rows, cols, 0, 0, visited); return count; } public int helper(int threshold, int rows, int cols, int row, int col, boolean[] visited) { int count = 0; if (check(threshold, rows, cols, row, col, visited)) { visited[row * cols + col] = true; count = 1 + helper(threshold, rows, cols, row, col+1, visited) + helper(threshold, rows, cols, row+1, col, visited) + helper(threshold, rows, cols, row, col-1, visited) + helper(threshold, rows, cols, row-1, col, visited); } return count; } public boolean check(int threshold, int rows, int cols, int row, int col, boolean[] visited) { if (row >= 0 && row < rows && col >= 0 && col < cols && !visited[row * cols + col] && getDigitSum(row) + getDigitSum(col) <= threshold) { return true; } return false; } public int getDigitSum(int x) { int sum = 0; while (x > 0) { sum += x % 10; x = x / 10; } return sum; } } ================================================ FILE: offer/src/com/ex/offer/Ex_14_MaxProductionAfterCutting.java ================================================ package com.ex.offer; public class Ex_14_MaxProductionAfterCutting { public static int maxProduction(int n) { if (n == 1) { return 0; } if (n == 2) { return 1; } if (n == 3) { return 2; } int[] products = new int[n+1]; products[0] = 0; products[1] = 1; products[2] = 2; products[3] = 3; int max = 0; for (int i = 4; i <= n; i++) { max = 0; for (int j = 0; j <= i/2; j++) { int product = products[j] * products[i-j]; if (product > max) { max = product; } products[i] = max; } } max = products[n]; return max; } public static int maxProductionWithGreedy(int n) { if (n == 1) { return 0; } if (n == 2) { return 1; } if (n == 3) { return 2; } int timesOf3 = n/3; // 此时 n < 3 if (n - timesOf3 * 3 == 1) { timesOf3--; // 此时 n > 5 } int timesOf2 = (n - timesOf3 * 3) / 2; // 此时 n < 2 return (int) (Math.pow(2, timesOf2) * Math.pow(3, timesOf3)); } public static void main(String[] args) { int n = 8; System.out.println(maxProduction(n)); System.out.println(maxProductionWithGreedy(n)); } } ================================================ FILE: offer/src/com/ex/offer/Ex_15_Count1.java ================================================ package com.ex.offer; public class Ex_15_Count1 { public static int NumberOf1(int n) { int count = 0; int flag = 1; while (flag != 0) { if ((n & flag) != 0) { count++; } flag = flag << 1; } return count; } public static int NumberOf1Optimal(int n) { int count = 0; while (n != 0) { count++; n = (n-1) & n; } return count; } public static void main(String[] args) { System.out.println(NumberOf1(10)); System.out.println(NumberOf1Optimal(10)); } } ================================================ FILE: offer/src/com/ex/offer/Ex_15_CountDifferInMN.java ================================================ package com.ex.offer; public class Ex_15_CountDifferInMN { /** * 计算需要改变m的二进制表示中的多少位才能得到n. * @param m * @param n * @return */ public static int countDiffBetweenMN(int m, int n) { int x = m ^ n; // 1. m ^ n : x中1的个数表示m和n中不同的位数个数 int count = 0; while (x != 0) { count++; x = (x-1) & x; } return count; } public static void main(String[] args) { int m = 10; int n = 13; System.out.println(countDiffBetweenMN(10, 13)); } } ================================================ FILE: offer/src/com/ex/offer/Ex_15_IsPowerOf2.java ================================================ package com.ex.offer; public class Ex_15_IsPowerOf2 { /** * 判断1个数是否是2的整数次幂,例如2 4 8 ... * 特点:若一个数是2的整数此幂,则这个数的二进制只有一个1 * (n-1) & n 将n中最右边的一个1变成了0 * @param n * @return */ public static boolean isPowerOf2(int n) { if (n < 2) { return false; } n = (n - 1) & n; if (n == 0) { return true; } else { return false; } } public static void main(String[] args) { int n = 100; for (int i = 0; i <= n; i++) { if (isPowerOf2(i)) System.out.println(i); } } } ================================================ FILE: offer/src/com/ex/offer/Ex_16_Power.java ================================================ package com.ex.offer; public class Ex_16_Power { /** * f(n) = a ^ n * 1. a != 0 * 2. n < 0 时, f(n) = 1 / a ^ abs(-n) * 3. n > 0 时, f(n) = a ^ n * @param base * @param exponent * @return */ public static double Power(double base, int exponent) { boolean InvalidInput_flag = false; // 全局变量标识是否为非法输入 if (base == 0) { InvalidInput_flag = true; return 0.0; } int absExp = Math.abs(exponent); double result = PowerWithUnsignedExponent(base, absExp); if (exponent < 0) { return 1 / result; } else { return result; } } public static double PowerWithUnsignedExponent(double base, int exponent) { double result = 1.0; for (int i = 1; i <= exponent; i++) { result *= base; } return result; } public static double PowerWithUnsignedExponentOptimal(double base, int exponent) { if (exponent == 0) { return 1; } if (exponent == 1) { return base; } double result = PowerWithUnsignedExponentOptimal(base, exponent >> 1); result *= result; if ((exponent & 0x1) == 1) { result *= base; } return result; } } ================================================ FILE: offer/src/com/ex/offer/Ex_17_AddTwoBigNumber.java ================================================ package com.ex.offer; public class Ex_17_AddTwoBigNumber { /** * 大数相加问题: * 大数相加不能直接使用基本的int类型,因为int可以表示的整数有限,不能满足大数的要求。 * 可以使用字符串来表示大数,模拟大数相加的过程。 * * 思路: * 1. 反转字符串,便于从低位到高位的相加; * 2. 对齐字符串,短字符串的高位用 ‘0’ 补齐,便于相加; * 3. 相加。 * @param n1 * @param n2 * @return */ public static String add(StringBuffer n1, StringBuffer n2) { StringBuffer result = new StringBuffer(); n1.reverse(); n2.reverse(); int len1 = n1.length(); int len2 = n2.length(); int len = len1 > len2 ? len1 : len2; boolean nOverFlow = false; int nTakeOver = 0; if (len1 < len2) { for (int i = len1; i < len2; i++) { n1.append('0'); } } else { for (int i = len2; i < len1; i++) { n2.append('0'); } } for (int i = 0; i < len; i++) { int nSum = Integer.parseInt(n1.charAt(i)+"") + (n2.charAt(i) - '0'); nSum += nTakeOver; if (nSum >= 10) { if (i == len - 1) { nOverFlow = true; } nTakeOver = 1; result.append(nSum - 10); } else { nTakeOver = 0; result.append(nSum); } } if (nOverFlow) { result.append(nTakeOver); } return result.reverse().toString(); } public static void main(String[] args) { StringBuffer s1 = new StringBuffer("-999"); StringBuffer s2 = new StringBuffer("99"); System.out.println(add(s1, s2)); } } ================================================ FILE: offer/src/com/ex/offer/Ex_17_Print1ToMaxNDigits.java ================================================ package com.ex.offer; public class Ex_17_Print1ToMaxNDigits { /** * 1. 实现基本功能,但是当n很大时,count会出现int范围不够的问题,改成long,仍然会有相同的问题 * @param n */ public static void print1ToMaxNDigits_1 (int n) { int count = 1; int i = 0; while (i < n) { count *= 10; i++; } for (int k = 1; k < count; k++) { System.out.print(k + " "); if (k % 10 == 0) { System.out.println(); } } } /** * 2. 字符串存储数字 * * @param n */ public static void print1ToMaxNDigits_2 (int n) { if (n <= 0) { return; } StringBuffer number = new StringBuffer(); for (int i = 0; i < n; i++) { number.append('0'); } while (!Increment(number)) { printNumber(number); } } /** * n位所有的十进制数:n个0~9的全排列 * @param n */ public static void print1ToMaxNDigits_3 (int n) { if (n <= 0) { return; } StringBuffer s = new StringBuffer(); for (int i = 0; i < n; i++) { s.append('0'); } for (int i = 0; i < 10; i++) { s.setCharAt(0, (char)(i+'0')); //“xx” x:0~9 print1ToMaxNDigits_3_Recur(s, n, 0); } } /** * index 数字已经确定,要确定index + 1位的数字 可选范围[0,9] * @param s * @param n * @param index */ public static void print1ToMaxNDigits_3_Recur(StringBuffer s, int n, int index) { if (index == n-1) { printNumber(s); return; } for (int i = 0; i < 10; i++) { s.setCharAt(index + 1, (char)(i+'0')); print1ToMaxNDigits_3_Recur(s, n, index + 1); } } public static boolean Increment(StringBuffer s) { boolean isOverflow = false; int nTakeOver = 0; //进位 int len = s.length(); for (int i = len-1; i >= 0; i--) { int nSum = s.charAt(i) - '0' + nTakeOver; // 若是最后1位,加1 if (i == len-1) { nSum++; } // 若高位第一位之后的某位nSum >= 10, 则需要执行进位操作 // 若高位第一位nSum >= 10, 则直接返回true,说明已经执行到了最高位,且最高位进位了 if (nSum >= 10) { if (i == 0) { isOverflow = true; } else { nSum -= 10; nTakeOver = 1; s.setCharAt(i, (char)(nSum + '0')); } } else { // 若高位第一位之后的任何一位的nSum < 10, 则直接退出 s.setCharAt(i, (char)(nSum + '0')); break; } } return isOverflow; } // 有效位前面的0不打印 public static void printNumber(StringBuffer s) { boolean isBeginWith0 = true; for (int i = 0; i < s.length(); i++) { if (isBeginWith0 && s.charAt(i) != '0') { isBeginWith0 = false; } if (!isBeginWith0) { System.out.print(s.charAt(i)); } } System.out.println(); } public static void main(String[] args) { // print1ToMaxNDigits_1(2); // System.out.println(); // print1ToMaxNDigits_2(2); // System.out.println(); print1ToMaxNDigits_3(2); } } ================================================ FILE: offer/src/com/ex/offer/Ex_18_DeleteDuplicatedNode.java ================================================ package com.ex.offer; public class Ex_18_DeleteDuplicatedNode { public static ListNode deleteDuplication (ListNode pHead) { ListNode first = new ListNode(-1); // 设置一个前哨节点,不用考虑pre为空的情况 first.next = pHead; ListNode last = first; ListNode p = pHead; while (p != null && p.next != null) { if (p.val == p.next.val) { int val = p.val; while (p != null && p.val == val) { p = p.next; } last.next = p; } else { last = p; p = p.next; } } return first.next; } public static ListNode deleteDuplication1 (ListNode pHead) { if (pHead == null || pHead.next == null) { return pHead; } ListNode pre = null; ListNode cur = pHead; while (cur != null) { ListNode next = cur.next; boolean needDelete = false; if (next != null && cur.val == next.val) { needDelete = true; } if (!needDelete) { pre = cur; cur = next; } else { int val = cur.val; while (cur != null && cur.val == val) { cur = cur.next; } if (pre == null) { pHead = cur; } else { pre.next = cur; } } } return pHead; } public static void main(String[] args) { ListNode head = new ListNode(1); ListNode node1 = new ListNode(2); ListNode node2 = new ListNode(3); ListNode node3 = new ListNode(3); ListNode node4 = new ListNode(4); ListNode node5 = new ListNode(4); ListNode node6 = new ListNode(5); head.next = node1; node1.next = node2; node2.next = node3; node3.next = node4; node4.next = node5; node5.next = node6; ListNode h1 = deleteDuplication1(head); TestUtils.printList_ListNode(h1); } } ================================================ FILE: offer/src/com/ex/offer/Ex_18_DeleteNodeInSList.java ================================================ package com.ex.offer; public class Ex_18_DeleteNodeInSList { public static Node deleteNode(Node head, Node toBeDeleted) { if (head == null || toBeDeleted == null) { return head; } // toBeDeleted is not tail if (toBeDeleted.next != null) { Node temp = toBeDeleted.next; toBeDeleted.val = temp.val; toBeDeleted.next = temp.next; temp = null; } else if (head == toBeDeleted){ // toBeDeleted is tail and list length = 1 toBeDeleted = null; head = null; } else { // // toBeDeleted is tail and list length != 1 Node pNode = head; while (pNode.next != toBeDeleted) { pNode = pNode.next; } pNode.next = null; } return head; } public static void main(String[] args) { Node node1 = new Node(1); node1.next = null; Node head1 = deleteNode(node1, node1); // case 2:注意 Java中是值传递 TestUtils.printList(head1); Node node2 = new Node(2); Node node3 = new Node(3); Node node4 = new Node(4); Node node5 = new Node(5); node2.next = node3; node3.next = node4; node4.next = node5; node5.next = null; Node head2 = deleteNode(node2, node2); // case 1 TestUtils.printList(head2); Node head3 = deleteNode(head2, node5); // case 3 TestUtils.printList(head3); } } ================================================ FILE: offer/src/com/ex/offer/Ex_19_Match.java ================================================ package com.ex.offer; /** * 当模式中的第二个字符不是“*”时: * 1、如果字符串第一个字符和模式中的第一个字符相匹配,那么字符串和模式都后移一个字符,然后匹配剩余的。 * 2、如果 字符串第一个字符和模式中的第一个字符相不匹配,直接返回false。 * * 而当模式中的第二个字符是“*”时: * 如果字符串第一个字符跟模式第一个字符不匹配,则模式后移2个字符,继续匹配。如果字符串第一个字符跟模式第一个字符匹配,可以有3种匹配方式: * 1、模式后移2字符,相当于x*被忽略; * 2、字符串后移1字符,模式后移2字符; * 3、字符串后移1字符,模式不变,即继续匹配字符下一位,因为*可以匹配多位; */ public class Ex_19_Match { public boolean match(char[] str, char[] pattern) { if (str == null || pattern == null) { return false; } return matchCore(str, 0, pattern, 0); } private boolean matchCore(char[] str, int strIndex, char[] pattern, int patIndex) { // 有效性检验: str 和 pattern都到尾,则匹配成功 if (strIndex == str.length && patIndex == pattern.length) { return true; } // pattern 先到尾,则匹配失败 if (strIndex != str.length && patIndex == pattern.length) { return false; } // pattern第2个是*,且str第1个和pattern第1个匹配,分3种情况;若不匹配,则pattern后移2位 if (patIndex + 1 < pattern.length && pattern[patIndex + 1] == '*') { if (strIndex != str.length && (pattern[patIndex] == str[strIndex] || pattern[patIndex] == '.')) { return matchCore(str, strIndex, pattern, patIndex+2) // 模式串后移2位,相当于忽略x* || matchCore(str, strIndex+1, pattern, patIndex+2) // 字符串,模式串都后移1位,相当于忽略* || matchCore(str, strIndex+1, pattern, patIndex); // 字符串后移1位,相当于*之前扩展了。 } else { return matchCore(str, strIndex, pattern, patIndex+2); } } // pattern第2个不是* ,且str 第1个和pattern第1个匹配,则后移一位,否则直接返回false if (strIndex != str.length && (pattern[patIndex] == str[strIndex] || pattern[patIndex] == '.')) { return matchCore(str, strIndex+1, pattern, patIndex+1); } return false; } } ================================================ FILE: offer/src/com/ex/offer/Ex_20_IsNumber.java ================================================ package com.ex.offer; import org.junit.Test; public class Ex_20_IsNumber { /** * A.B[E|e]C * A.B: A B任意有其一即可 * XEC: X C 缺一不可 */ private int index = 0; public boolean isNumeric(char[] str) { if (str == null || str.length == 0) { return false; } boolean flag = scanInteger(str); if (index < str.length && str[index] == '.') { index++; flag = scanUnsignedInteger(str) || flag; //若是||的话,必须先执行scan函数,否则index无法更新 } if (index < str.length && (str[index] == 'E' || str[index] == 'e')) { index++; flag = flag && scanInteger(str); } return flag && index == str.length; } private boolean scanInteger(char[] str) { if (index < str.length && (str[index] == '+' || str[index] == '-')) { index++; } return scanUnsignedInteger(str); } private boolean scanUnsignedInteger(char[] str) { int start = index; while (index < str.length && str[index] >= '0' && str[index] <= '9') { index++; } return start < index; } /** 以下对正则进行解释: [\\+\\-]?            -> 正或负符号出现与否 \\d*                 -> 整数部分是否出现,如-.34 或 +3.34均符合 (\\.\\d+)?           -> 如果出现小数点,那么小数点后面必须有数字,否则一起不出现 ([eE][\\+\\-]?\\d+)? -> 如果存在指数部分,那么e或E肯定出现,+或-可以不出现,                         紧接着必须跟着整数;或者整个部分都不出现 */ public boolean isNumberic1(char[] str) { String string = String.valueOf(str); return string.matches("[\\+\\-]?\\d*(\\.\\d+)?([eE][\\+\\-]?\\d+)?"); } @Test public void test() { String str = "123.45e+6"; char[] chars = str.toCharArray(); System.out.println(isNumeric(chars)); System.out.println(isNumberic1(chars)); } } ================================================ FILE: offer/src/com/ex/offer/Ex_21_ReOrderArray.java ================================================ package com.ex.offer; import org.junit.Test; public class Ex_21_ReOrderArray { public void reOrderArray(int [] array) { if (array == null || array.length == 0) { return; } int i = 0; while (i < array.length) { while (i < array.length && (array[i] & 1) == 1) { i++; } int j = i+1; while (j < array.length && (array[j] & 1) != 1) { j++; } if (j < array.length) { int tmp = array[j]; for (int k = j-1; k >= i; k--) { array[k+1] = array[k]; } array[i] = tmp; i++; } else { break; } } } public void reOrderArray1(int [] array) { if (array == null || array.length == 0) { return; } int i = 0; int j = array.length-1; while (i < j) { while ((array[i] & 1) != 0) { i++; } while ((array[j] & 1) == 0) { j--; } if (i > j) break; swap(array, i, j); } } public void reOrderArray2(int [] array) { if(array == null || array.length == 0) { return; } for (int i = 0; i < array.length-1; i++) { for (int j = 0; j < array.length-1-i; j++) { if (array[j] % 2 == 0 && array[j+1] % 2 == 1) { swap(array, j, j+1); } } } } private void swap(int[] array, int i, int j) { int tmp = array[i]; array[i] = array[j]; array[j] = tmp; } @Test public void test() { int[] array = new int[] {1,2,3,4,5,6,7}; reOrderArray1(array); TestUtils.printArray(array); } } ================================================ FILE: offer/src/com/ex/offer/Ex_22_FindKthNodeToTail.java ================================================ package com.ex.offer; import org.junit.Test; public class Ex_22_FindKthNodeToTail { /** * 1. 空链表 * 2. k <= 0 * 3. k > len * @param head * @param k * @return */ public ListNode FindKthToTail(ListNode head, int k) { // 1. 边界条件: head == null k <= 0 if (head == null || k <= 0) return null; ListNode before = head; ListNode after = head; for (int i = 0; i < k-1; i++) { before = before.next; } // 2. n < k: 第k个元素before已经到null,则k > n if (before == null) return null; while (before.next != null) { before = before.next; after = after.next; } return after; } @Test public void test() { ListNode head = new ListNode(1); head.next = new ListNode(2); head.next.next = new ListNode(3); head.next.next.next = new ListNode(4); head.next.next.next.next = new ListNode(5); FindKthToTail(head, 5); } } ================================================ FILE: offer/src/com/ex/offer/Ex_22_FindMedianNodeInList.java ================================================ package com.ex.offer; import org.junit.Test; public class Ex_22_FindMedianNodeInList { public ListNode findMedian(ListNode head) { if (head == null) return null; ListNode before = head; ListNode after = head; while (before.next != null && before.next.next != null) { before = before.next.next; after = after.next; } return after; } @Test public void test() { ListNode head = new ListNode(1); head.next = new ListNode(2); head.next.next = new ListNode(3); head.next.next.next = new ListNode(4); //head.next.next.next.next = new ListNode(5); System.out.println(findMedian(head).val); } } ================================================ FILE: offer/src/com/ex/offer/Ex_23_EntryNodeInList.java ================================================ package com.ex.offer; public class Ex_23_EntryNodeInList { public ListNode EntryNodeOfLoop(ListNode pHead) { if (pHead == null) { return null; } ListNode fast = pHead; ListNode slow = pHead; while (fast.next != null && fast.next.next != null) { fast = fast.next.next; slow = slow.next; if (fast == slow) { break; } } if (fast.next == null || fast.next.next == null) return null; fast = pHead; while (fast != slow) { fast = fast.next; slow = slow.next; } return fast; } } ================================================ FILE: offer/src/com/ex/offer/Ex_24_ReverseSList.java ================================================ package com.ex.offer; public class Ex_24_ReverseSList { public ListNode ReverseList(ListNode head) { if (head == null) return null; ListNode pre = null; ListNode cur = head; ListNode next = null; // 指向剩余的需要被reverse的链表 while (cur != null) { next = cur.next; // 记录:剩余的需要被reverse的链表 cur.next = pre; // 核心:cur -> pre pre = cur; // 更新pre cur = next; // 更新cur } head = pre; return head; } public ListNode reverseList(ListNode head) { //若是空链表或者tail节点 if (head == null || head.next == null) return head; //先反转 head.next -> ... 例如: 1->2->3->4->null 1-> |2<-3<-4| ListNode reverseHead = reverseList(head.next); //修改head指向 null <- 1 <- |2<-3<-4| head.next.next = head; head.next = null; return reverseHead; } } ================================================ FILE: offer/src/com/ex/offer/Ex_25_MergeList.java ================================================ package com.ex.offer; import org.junit.Test; public class Ex_25_MergeList { public ListNode Merge(ListNode list1, ListNode list2) { if (list1 == null) return list2; if (list2 == null) return list1; ListNode head = null; if (list1.val < list2.val) { head = list1; head.next = Merge(list1.next, list2); } else { head = list2; head.next = Merge(list1, list2.next); } return head; } public ListNode Merge1(ListNode list1, ListNode list2) { if (list1 == null) return list2; if (list2 == null) return list1; ListNode pre = new ListNode(-1); ListNode node = pre; ListNode p1 = list1; ListNode p2 = list2; while (p1 != null && p2 != null) { while (p1 != null && p1.val < p2.val) { node.next = new ListNode(p1.val); node = node.next; p1 = p1.next; } if (p1 == null) { while (p2 != null) { node.next = new ListNode(p2.val); node = node.next; p2 = p2.next; } } while (p2 != null && p2.val < p1.val) { node.next = new ListNode(p2.val); node = node.next; p2 = p2.next; } if (p2 == null) { while (p1 != null) { node.next = new ListNode(p1.val); node = node.next; p1 = p1.next; } } } return pre.next; } @Test public void test() { ListNode head = new ListNode(1); head.next = new ListNode(3); head.next.next = new ListNode(5); ListNode head1 = new ListNode(2); head1.next = new ListNode(4); head1.next.next = new ListNode(6); ListNode h = Merge(head, head1); TestUtils.printList_ListNode(h); } @Test public void test1() { ListNode head = new ListNode(1); head.next = new ListNode(3); head.next.next = new ListNode(5); ListNode head1 = new ListNode(2); head1.next = new ListNode(4); head1.next.next = new ListNode(6); ListNode h1 = Merge1(head, head1); TestUtils.printList_ListNode(h1); } } ================================================ FILE: offer/src/com/ex/offer/Ex_26_IsSubTree.java ================================================ package com.ex.offer; // 此题目还可以将Tree A B 序列化,然后看B字符串是否在A字符串中,KMP算法 public class Ex_26_IsSubTree { public boolean HasSubtree(TreeNode root1, TreeNode root2) { if (root1 == null || root2 == null) return false; return doesTree1HasTree2(root1, root2) || HasSubtree(root1.left, root2) || HasSubtree(root1.right, root2); } public boolean doesTree1HasTree2(TreeNode root1, TreeNode root2) { if (root2 == null) { return true; } if (root1 == null) { return false; } if (root1.val != root2.val) { return false; } return doesTree1HasTree2(root1.left, root2.left) && doesTree1HasTree2(root1.right, root2.right); } } ================================================ FILE: offer/src/com/ex/offer/Ex_27_MirrorOfTree.java ================================================ package com.ex.offer; public class Ex_27_MirrorOfTree { public void Mirror(TreeNode root) { if (root == null) return; if (root.left == null && root.right == null) return; swapLeftAndRight(root); Mirror(root.left); Mirror(root.right); } public void swapLeftAndRight(TreeNode root) { if (root == null) return; TreeNode tmp = root.left; root.left = root.right; root.right =tmp; } } ================================================ FILE: offer/src/com/ex/offer/Ex_28_SymmetricalTree.java ================================================ package com.ex.offer; public class Ex_28_SymmetricalTree { boolean isSymmetrical(TreeNode pRoot) { if (pRoot == null) return true; return isSymmetrical(pRoot, pRoot); } // 树的前序遍历和对称前序遍历 boolean isSymmetrical(TreeNode pRoot1, TreeNode pRoot2) { // 递归结束条件 if (pRoot1 == null && pRoot2 == null) return true; if (pRoot1 == null || pRoot2 == null) return false; if (pRoot1.val != pRoot2.val) { return false; } return isSymmetrical(pRoot1.left, pRoot2.right) && isSymmetrical(pRoot1.right, pRoot2.left); } // 树的前序遍历 public boolean doesTree1HasTree2(TreeNode root1, TreeNode root2) { // 递归结束条件 if (root2 == null) { return true; } if (root1 == null) { return false; } if (root1.val != root2.val) { return false; } return doesTree1HasTree2(root1.left, root2.left) && doesTree1HasTree2(root1.right, root2.right); } } ================================================ FILE: offer/src/com/ex/offer/Ex_29_PrintMatrixWithClockWise.java ================================================ package com.ex.offer; import java.util.ArrayList; public class Ex_29_PrintMatrixWithClockWise { public ArrayList printMatrix(int [][] matrix) { if (matrix == null || matrix.length <= 0) { return new ArrayList<>(); } ArrayList result = new ArrayList<>(); ArrayList temp = new ArrayList<>(); int r1 = 0; int c1 = 0; int r2 = matrix.length-1; int c2 = matrix[r2].length-1; while (r1 <= r2 && c1 <= c2) { temp = printMatrixWithCircle(matrix, r1++, c1++, r2--, c2--); for (Integer i : temp) { result.add(i); } } return result; } public ArrayList printMatrixWithCircle(int[][] matrix, int r1, int c1, int r2, int c2) { ArrayList result = new ArrayList<>(); if (r1 == r2) { for (int i = c1; i <= c2; i++) { result.add(matrix[r1][i]); } return result; } if (c1 == c2) { for (int i = r1; i <= r2; i++) { result.add(matrix[i][c1]); } return result; } for (int i = c1; i < c2; i++) { result.add(matrix[r1][i]); } for (int i = r1; i < r2; i++) { result.add(matrix[i][c2]); } for (int i = c2; i > c1; i--) { result.add(matrix[r2][i]); } for (int i = r2; i > r1; i--) { result.add(matrix[i][c1]); } return result; } } ================================================ FILE: offer/src/com/ex/offer/Ex_30_StackWithMinFunction.java ================================================ package com.ex.offer; import java.util.Stack; public class Ex_30_StackWithMinFunction { Stack stack = new Stack<>(); Stack help = new Stack<>(); public void push(int node) { stack.push(node); if (help.isEmpty()) { help.push(node); } if (help.peek() >= node) { help.push(node); } else { help.push(help.peek()); } } public void pop() { if (!stack.isEmpty()) { stack.pop(); help.pop(); } } public int top() { if (!stack.isEmpty()) { return stack.peek(); } return -1; } public int min() { if (!help.isEmpty()) { return help.peek(); } return -1; } } ================================================ FILE: offer/src/com/ex/offer/Ex_31_IsPopOrder.java ================================================ package com.ex.offer; import java.util.Stack; public class Ex_31_IsPopOrder { public boolean IsPopOrder(int [] pushA,int [] popA) { Stack stack = new Stack<>(); int index = 0; // push: 1 2 3 4 5 // pop : 4 5 3 2 1 // stack : 1 2 3 -4- | <- 5 push: empty pop: 4 5 3 2 1 // stack : 1 2 3 5 | push: empty pop: 5 3 2 1 for (int i = 0; i < pushA.length; i++) { while (!stack.isEmpty() && popA[index] == stack.peek()) { stack.pop(); // 遍历完push之前pop出去的 index++; } stack.push(pushA[i]); } // 遍历完push之后pop出去的 while (!stack.isEmpty() && popA[index] == stack.peek()) { stack.pop(); index++; } return stack.isEmpty(); } } ================================================ FILE: offer/src/com/ex/offer/Ex_32_PrintTreeByLayer.java ================================================ package com.ex.offer; import java.util.ArrayList; import java.util.LinkedList; import java.util.Queue; public class Ex_32_PrintTreeByLayer { ArrayList> Print(TreeNode pRoot) { ArrayList> result = new ArrayList<>(); ArrayList nodes = new ArrayList<>(); int countOfNextLevel = 0; // 统计下一层的节点数 int left = 1; // 统计本层剩余的打印数 if (pRoot == null) return result; Queue queue = new LinkedList<>(); queue.add(pRoot); while (!queue.isEmpty()) { TreeNode x = queue.poll(); left--; nodes.add(x.val); if (x.left != null) { queue.add(x.left); countOfNextLevel++; } if (x.right != null) { queue.add(x.right); countOfNextLevel++; } if (left == 0) { result.add(new ArrayList<>(nodes)); // 一定要注意这里,拷贝构造,而不能直接将nodes(这只是个引用)扔进去 nodes.clear(); left = countOfNextLevel; countOfNextLevel = 0; } } return result; } } ================================================ FILE: offer/src/com/ex/offer/Ex_32_PrintTreeWithZhi.java ================================================ package com.ex.offer; import java.util.ArrayList; import java.util.Stack; public class Ex_32_PrintTreeWithZhi { public ArrayList > Print(TreeNode pRoot) { ArrayList> result = new ArrayList<>(); ArrayList nodes = new ArrayList<>(); if (pRoot == null) return result; Stack s1 = new Stack<>(); // 奇数层 1 Stack s2 = new Stack<>(); // 偶数层 0 int cur = 1; int next = 0; s1.add(pRoot); while (!s1.isEmpty() || !s2.isEmpty()) { TreeNode x = null; if (cur == 1) { x = s1.pop(); } else { x = s2.pop(); } nodes.add(x.val); if (cur == 1) { if (x.left != null) { s2.push(x.left); } if (x.right != null) { s2.push(x.right); } } else { if (x.right != null) { s1.push(x.right); } if (x.left != null) { s1.push(x.left); } } if (cur == 1 && s1.isEmpty() && !nodes.isEmpty()) { result.add(new ArrayList<>(nodes)); nodes.clear(); next = 1-next; cur = 1-cur; } if (cur == 0 && s2.isEmpty() && !nodes.isEmpty() ) { result.add(new ArrayList<>(nodes)); nodes.clear(); next = 1-next; cur = 1-cur; } } return result; } // 此答案有错 public ArrayList> Print1(TreeNode pRoot) { ArrayList> result = new ArrayList<>(); ArrayList nodes = new ArrayList<>(); if (pRoot == null) return result; Stack[] stacks = new Stack[2]; int current = 0; //奇数层 int next = 1; //偶数层 stacks[current].push(pRoot); while (!stacks[0].isEmpty() || !stacks[1].isEmpty()) { TreeNode x = stacks[current].pop(); nodes.add(x.val); if (current == 0) { if (x.left != null) { stacks[next].push(x.left); } if (x.right != null) { stacks[next].push(x.right); } } else { if (x.right != null) { stacks[next].push(x.right); } if (x.left != null) { stacks[next].push(x.left); } } if (stacks[current].isEmpty()) { result.add(new ArrayList<>(nodes)); nodes.clear(); current = 1 - current; next = 1- next; } } return result; } } ================================================ FILE: offer/src/com/ex/offer/Ex_32_TraverseTreeByLayer.java ================================================ package com.ex.offer; import java.util.ArrayList; import java.util.LinkedList; import java.util.Queue; /** * 注1: * 树的按层遍历借助于 队列 * 树的先中后序遍历借助于 栈 * * 注2: * 树的按层遍历本质上说就是广度优先遍历二叉树(树是图的一种特殊退化形式) * 那么如何广度优先遍历有向图呢? * * A:无论是广度优先遍历有向图还是二叉树,都需要用到队列。 * 首先把起始节点(或者根节点(对树而言))放入队列 * 然后每次从队列的头部取出一个节点,遍历这个节点之后把它能够到达的节点(或者子节点(对树而言))都一次放入队列中 * 重复以上遍历过程,知道队列中所有的节点全部被遍历完。 * */ public class Ex_32_TraverseTreeByLayer { public ArrayList PrintFromTopToBottom(TreeNode root) { ArrayList result = new ArrayList<>(); if (root == null) return result; Queue queue = new LinkedList<>(); queue.add(root); while (!queue.isEmpty()) { TreeNode temp = queue.poll(); result.add(temp.val); if (temp.left != null) queue.add(temp.left); if (temp.right != null) queue.add(temp.right); } return result; } } ================================================ FILE: offer/src/com/ex/offer/Ex_33_VerifySequenceOfBST.java ================================================ package com.ex.offer; import org.junit.Test; public class Ex_33_VerifySequenceOfBST { public boolean VerifySequenceOfBST(int [] sequence) { if (sequence == null || sequence.length <= 0) { return false; } return verifySequence(sequence, 0, sequence.length-1); } public boolean verifySequence(int[] sequence, int start, int end) { if (sequence == null || start > end) { return false; } // 若数组区间[start, end]元素为0则停止递归,其实可以去掉这个判断 if (start == end) { return true; } int root = sequence[end]; // 找到左右子树的分界点[i-1,i] // 小于root: [start, i-1] int i = start; for ( ; i < end; i++) { if (sequence[i] > root) { break; } } // 大于root: [i, end-1] int j = i; for ( ; j < end; j++) { if (sequence[j] < root) { return false; } } // 判断左子树 boolean left = true; // 若左子树为空,则左子树为后序遍历序列 if (i > start) { left = verifySequence(sequence, start, i-1); } // 判断右子树 boolean right = true; // 若右子树为空,则右子树为后序遍历序列 if (i < end) { right = verifySequence(sequence, i, end-1); } return left && right; } @Test public void test() { int[] arr = new int[] {5, 7, 6, 9, 11, 10, 8}; boolean result = VerifySequenceOfBST(arr); System.out.println(result); } } ================================================ FILE: offer/src/com/ex/offer/Ex_34_FindPathInBT.java ================================================ package com.ex.offer; import org.junit.Test; import java.util.ArrayList; public class Ex_34_FindPathInBT { public ArrayList> FindPath(TreeNode root, int target) { ArrayList> pathList = new ArrayList<>(); if (root == null) { return pathList; } ArrayList path = new ArrayList<>(); findPath(root, target, path, pathList); return pathList; } private void findPath(TreeNode root, int target, ArrayList path, ArrayList> pathList) { if (root == null) return; if (root.left == null && root.right == null) { if (root.val == target) { path.add(root.val); pathList.add(path); } return; } path.add(root.val); ArrayList pathCopy = new ArrayList<>(path); findPath(root.left, target - root.val, path, pathList); findPath(root.right, target - root.val, pathCopy, pathList); return; } @Test public void test() { TreeNode node1 = new TreeNode(10); TreeNode node2 = new TreeNode(5); TreeNode node3 = new TreeNode(12); TreeNode node4 = new TreeNode(4); TreeNode node5 = new TreeNode(7); node1.left = node2; node1.right = node3; node2.left = node4; node2.right = node5; ArrayList> list = FindPath(node1, 22); int a = 5; } } ================================================ FILE: offer/src/com/ex/offer/Ex_34_FindPathInBT_1.java ================================================ package com.ex.offer; import org.junit.Test; import java.util.ArrayList; public class Ex_34_FindPathInBT_1 { ArrayList> pathList = new ArrayList<>(); ArrayList path = new ArrayList<>(); public ArrayList> FindPath(TreeNode root, int target) { if (root == null) return pathList; findPath(root, target); return pathList; } private void findPath(TreeNode root, int target) { path.add(root.val); if (root.val == target && root.left == null && root.right == null) { pathList.add(new ArrayList<>(path)); } if (root.left != null) { findPath(root.left, target - root.val); } if (root.right != null) { findPath(root.right, target - root.val); } path.remove(path.size()-1); return; } } ================================================ FILE: offer/src/com/ex/offer/Ex_34_FindPathInBT_2.java ================================================ package com.ex.offer; import java.util.ArrayList; public class Ex_34_FindPathInBT_2 { ArrayList> pathList = new ArrayList<>(); ArrayList path = new ArrayList<>(); public ArrayList> FindPath(TreeNode root, int target) { if (root == null) return pathList; findPath(root, target, 0); return pathList; } private void findPath(TreeNode root, int target, int sum) { path.add(root.val); sum += root.val; if (sum == target && root.left == null && root.right == null) { pathList.add(new ArrayList<>(path)); } if (root.left != null) { findPath(root.left, target, sum); } if (root.right != null) { findPath(root.right, target, sum); } path.remove(path.size()-1); return; } } ================================================ FILE: offer/src/com/ex/offer/Ex_35_CloneList.java ================================================ package com.ex.offer; import java.util.HashMap; import java.util.Map; public class Ex_35_CloneList { public RandomListNode Clone(RandomListNode pHead) { if (pHead == null) return pHead; RandomListNode node = pHead; // A - A' - B - B' - C - C' - null while (node != null) { RandomListNode cloneNode = new RandomListNode(node.label); cloneNode.next = node.next; node.next = cloneNode; node = cloneNode.next; } // 设置Random node = pHead; while (node != null) { RandomListNode cloneNode = node.next; if (node.random != null) cloneNode.random = node.random.next; node = cloneNode.next; } RandomListNode head = pHead.next; // 拆分链表 node = pHead; RandomListNode node1 = head; while (node != null) { node.next = node1.next; node = node.next; if (node == null) break; node1.next = node.next; node1 = node1.next; } return head; } public RandomListNode Clone1(RandomListNode pHead) { if (pHead == null) return pHead; Map map = new HashMap<>(); RandomListNode node = pHead; while (node != null) { map.put(node, new RandomListNode(node.label)); } for (RandomListNode key : map.keySet()) { map.get(key).next = map.get(key.next); map.get(key).random = map.get(key.random); } return map.get(0); } } ================================================ FILE: offer/src/com/ex/offer/Ex_36_ConvertBetweenBSTAndDList.java ================================================ package com.ex.offer; import java.util.Stack; public class Ex_36_ConvertBetweenBSTAndDList { TreeNode head = null; TreeNode tail = null; public TreeNode Convert(TreeNode pRootOfTree) { convert(pRootOfTree); return head; } public void convert(TreeNode pRootOfTree) { if (pRootOfTree == null) return; convert(pRootOfTree.left); if (head == null) { // 第一次,标记head节点 head = pRootOfTree; tail = pRootOfTree; } else { tail.right = pRootOfTree; pRootOfTree.left = tail; tail = pRootOfTree; } convert(pRootOfTree.right); } public TreeNode ConvertIterative(TreeNode pRootOfTree) { if (pRootOfTree == null) return null; Stack stack = new Stack<>(); TreeNode node = pRootOfTree; TreeNode pre = null; TreeNode head = null; boolean isFirst = true; while (node != null || !stack.isEmpty()) { while (node != null) { stack.push(node); node = node.left; } node = stack.pop(); if (isFirst) { head = node; pre = head; isFirst = false; } else { pre.right = node; node.left = pre; pre = node; } node = node.right; } return head; } } ================================================ FILE: offer/src/com/ex/offer/Ex_37_SerializeBT.java ================================================ package com.ex.offer; public class Ex_37_SerializeBT { String Serialize(TreeNode root) { StringBuilder sb = new StringBuilder(); if (root == null) { return sb.toString(); } serialize(root, sb); return sb.toString(); } void serialize(TreeNode root, StringBuilder sb) { if (root == null) { sb.append("#,"); return; } sb.append(root.val + ","); serialize(root.left, sb); serialize(root.right, sb); } int index = -1; TreeNode Deserialize(String str) { if (str.length() == 0) return null; String[] strs = str.split(","); return Deserialize(strs); } TreeNode Deserialize(String[] strs) { index++; // 遍历strs,待返回root时,遍历结束 TreeNode node = null; if (!strs[index].equals("#")) { node = new TreeNode(Integer.parseInt(strs[index])); node.left = Deserialize(strs); node.right = Deserialize(strs); } return node; } } ================================================ FILE: offer/src/com/ex/offer/Ex_38_EightQueen.java ================================================ package com.ex.offer; import org.junit.Test; // 8皇后问题与回溯法:https://www.cnblogs.com/bigmoyan/p/4521683.html // // 回溯法 // 引例:设想将你放在迷宫之中,要想走出迷宫,最直接的办法是什么?试!先选择一条路开始走, // 走不通就退回去尝试别的路,走不通接着回退,直到走通为止。 // 本质: 暴力破解。只不过是在暴力破解之前,利用了一些条件减少了暴力破解的一些步骤。 public class Ex_38_EightQueen { private static final int QUEEN_NUMBER = 8; // 皇后个数 private int[] columns = new int[QUEEN_NUMBER]; // 每个皇后存储的列 (row, col), row天然不相等 private int total = 0; public int solution() { queen(0); return total; } private void queen(int row) { if (row == QUEEN_NUMBER) { total++; } else { for (int col = 0; col != QUEEN_NUMBER; col++) { columns[row] = col; if (isPut(row)) { queen(row+1); } } } } private boolean isPut(int row) { for (int i = 0; i != row; i++) { if (columns[row] == columns[i] || row - i == Math.abs(columns[row]-columns[i])) { return false; } } return true; } @Test public void test() { System.out.println(solution()); //92 } } ================================================ FILE: offer/src/com/ex/offer/Ex_38_StringCombination.java ================================================ package com.ex.offer; import org.junit.Test; import java.util.ArrayList; public class Ex_38_StringCombination { public ArrayList Combination(String str) { ArrayList res = new ArrayList<>(); if (str == null || str.length() <= 0) return res; combination(str.toCharArray(), 0, res); return res; } private void combination(char[] chars, int startIndex, ArrayList res) { if (startIndex == chars.length) { StringBuilder sb = new StringBuilder(); for (char c : chars) { if (c != 0) sb.append(c); } res.add(sb.toString()); return; } combination(chars, startIndex+1, res); char temp = chars[startIndex]; chars[startIndex] = 0; combination(chars, startIndex+1, res); chars[startIndex] = temp; } @Test public void test() { ArrayList res = Combination(new String("abc")); for (String str : res) { System.out.println(str); } } } ================================================ FILE: offer/src/com/ex/offer/Ex_38_StringPermutation.java ================================================ package com.ex.offer; import java.util.ArrayList; import java.util.Collections; public class Ex_38_StringPermutation { public ArrayList Permutation(String str) { ArrayList res = new ArrayList<>(); if (str == null || str.length() <= 0) return res; permutation(str.toCharArray(), 0, res); Collections.sort(res); return res; } private void permutation(char[] chars, int startIndex, ArrayList res) { if (startIndex == chars.length-1) { String str = String.valueOf(chars); if (!res.contains(str)) { res.add(str); } } else { // 第一个字符与后面所有字符逐个交换:a|bcde b|acde c|bade d|bcae e|bcda // 第一个字符固定,第二个字符和后面所有字符逐个交换:a || b|cde c|bde d|cbe e|cdb for (int i = startIndex; i < chars.length; i++) { swap(chars, startIndex, i); permutation(chars, startIndex + 1, res); // a || b|cde c|bde d|cbe e|cdb swap(chars, startIndex, i); } } } private void swap(char[] chars, int i, int j) { char tmp = chars[i]; chars[i] = chars[j]; chars[j] = tmp; } } ================================================ FILE: offer/src/com/ex/offer/Ex_39_KthSmallestNumInArrayWithoutSort.java ================================================ package com.ex.offer; public class Ex_39_KthSmallestNumInArrayWithoutSort { /** * 找到第K个最小的数字,同时RAND-PARTITION之后,K个数(包含第K个数)左侧为最小的K个数 * @param arr * @param K * @return */ public static int kthNumIte(int[] arr, int K) { if (arr == null || arr.length < K || K <= 0 || arr.length <= 0) { return -1; } int low = 0; int high = arr.length - 1; int p = partition(arr, low, high); while (p != K-1) { if (p > K - 1) { p = partition(arr, low, p-1); } else { p = partition(arr, p+1, high); } } return arr[p]; } public static int kthNum(int[] arr, int K) { if (arr == null || arr.length < K || K <= 0 || arr.length <= 0) { return -1; } return kthNum(arr, K, 0, arr.length-1); } /** * RAND-SELECT(array, K, low, high) * 1. if low == high, RETURN array[low] // 递归结束的条件 * 2. p = RAND-PARTITION(arr, low, high) // [low, high]中,pivot元素在数组中的index * 3. count = p - low + 1; // [low, high]中, pivot元素是第几个元素 * 4. if count > k RETURN RAND-SELECT(array, K, low, p-1) * else if count < K RETURN RAND-SELECT(array, K-count, p+1, high) * else RETURN arr[p]; * * @param arr * @param K * @param low * @param high * @return */ public static int kthNum(int[] arr, int K, int low, int high) { if (low == high) { return arr[low]; } else{ int p = partition(arr, low, high); int count = p - low + 1; // count:从low开始第count个值 if (count > K) { return kthNum(arr, K, low, p-1); } else if (count < K) { return kthNum(arr, K-count, p+1, high); } else { return arr[p]; } } } /** * RAND-PARTITION(array, low, high) * * 1. 随机挑选一个元素作为主元pivot,将该元素换到low位置; * 2. 指定2个指针: * 2.1 i:指向low,若arr[i] < pivot,则i++; * 2.2 j:指向high,若arr[j] > pivot, 则j--; * 不断重复2.1 2.2,此时,arr[i] >= pivot, arr[j] <= pivot, 交换arr[i], arr[j] * 3. 重复步骤2,直到 i < j条件不满足 * 4. 交换 arr[low] 和 arr[j] * low i j * [pivot, <= x, >= x] * * @param arr * @param low * @param high * @return */ public static int partition(int[] arr, int low, int high) { if (low == high) { return low; } int pIndex = low + (int)(Math.random() * (high - low)); swap(arr, pIndex, low); int pivot = arr[low]; // pivot始终选择第一个元素 int i = low; int j = high + 1; while (i < j) { while (arr[++i] < pivot) { if (i == high) { break; } } while (arr[--j] > pivot) { if (j == low) { break; } } if (i > j) { break; } swap(arr, i, j); } swap(arr, j, low); return j; } public static void swap(int[] arr, int i, int j) { int tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp; } public static void main(String[] args) { int[] arr = new int[] {6, 4, 7, -2, 9, 6, 8, 2, -5, 5}; // ArrayUtils.printArray(arr); // int p = partition(arr, 0, arr.length-1); // System.out.println(p + " : " + arr[p]); // ArrayUtils.printArray(arr); System.out.println("======================================"); TestUtils.printArray(arr); for (int K = 1; K <= arr.length; K++) { int kth = kthNum(arr, K); System.out.print(kth + " "); } int kth = kthNum(arr, 0); System.out.print(kth + " "); System.out.println(); System.out.println("======================================"); int[] arr1 = new int[] {6, 4, 7, -2, 9, 6, 8, 2, -5, 5}; TestUtils.printArray(arr1); for (int K = 1; K <= arr1.length; K++) { int kth1 = kthNumIte(arr1, K); System.out.print(kth1 + " "); } int kth1 = kthNumIte(arr1, 0); System.out.print(kth1 + " "); System.out.println(); System.out.println("======================================"); int[] arr2 = new int[] {6, 4, 7, -2, 9, 6, 8, 2, -5, 5}; int K = 5; TestUtils.printArray(arr2); int kth2 = kthNumIte(arr2, K); System.out.println(K + ":" + arr2[K-1]); TestUtils.printArray(arr2); } } ================================================ FILE: offer/src/com/ex/offer/Ex_39_MoreThanHalfNum.java ================================================ package com.ex.offer; public class Ex_39_MoreThanHalfNum { public static int moreThanHalfNum(int[] array) { if (array == null || array.length <= 0) { return 0; } if (array.length == 1) { return array[0]; } int low = 0; int high = array.length-1; int mid = low + (high - low) / 2; int p = partition(array, low, high); while (p != mid) { if (p < mid) { p = partition(array, p+1, high); } else { p = partition(array, low, p-1); } } int result = array[mid]; if (!checkMoreThanHalf(array, result)) { result = 0; } return result; } public static boolean checkMoreThanHalf(int[] arr, int result) { int times = 0; for (int i = 0; i < arr.length; i++) { if (arr[i] == result) { times++; } } if (times * 2 <= arr.length) { return false; } return true; } public static int partition(int[] arr, int low, int high) { if (low == high) { return low; } int pIndex = low + (int) (Math.random() *(high - low)); swap(arr, low, pIndex); int pivot = arr[low]; int i = low; int j = high + 1; while (i < j) { while (arr[++i] < pivot) { if (i == high) { break; } } while (arr[--j] > pivot) { if (j == low) { break; } } if (i > j) { break; } swap(arr, i, j); } swap(arr, j, low); return j; } public static void swap(int[] arr, int i, int j) { int tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp; } public static int moreThanHalfNumWithoutChangeArray(int[] array) { if (array == null || array.length <= 0) { return 0; } int result = array[0]; int times = 1; for (int i = 1; i < array.length; i++) { if (times == 0) { result = array[i]; times = 1; } else if (array[i] == result) { times++; } else { times--; } } if (!checkMoreThanHalf(array, result)) { result = 0; } return result; } public static void main(String[] args) { int[] arr = new int[]{1,2,3,2,2,2,5,4,2}; System.out.println(moreThanHalfNumWithoutChangeArray(arr)); System.out.println(moreThanHalfNumWithoutChangeArray(new int[]{1})); System.out.println("============================="); TestUtils.printArray(arr); System.out.println(moreThanHalfNum(arr)); TestUtils.printArray(arr); } } ================================================ FILE: offer/src/com/ex/offer/Ex_40_GetLeastKNumbers.java ================================================ package com.ex.offer; import java.util.ArrayList; public class Ex_40_GetLeastKNumbers { public static ArrayList getLeastKNumbers(int[] input, int k) { if (input == null || k <= 0 || input.length < k || input.length <= 0) { return null; } int low = 0; int high = input.length - 1; int p = partition(input, low, high); while (p != k-1) { if (p > k-1) { p = partition(input, low, p-1); } else { p = partition(input, p+1, high); } } ArrayList res = new ArrayList<>(); for (int i = 0; i < k; i++) { res.add(input[i]); } return res; } // 找到第k小的数(那其左边就是比arr[k]小的k个数) public static int kthNum(int[] input, int k) { if (input == null || k <= 0 || input.length < k || input.length <= 0) { return -1; } int low = 0; int high = input.length - 1; int p = partition(input, low, high); while (p != k-1) { if (p > k-1) { p = partition(input, low, p-1); } else { p = partition(input, p+1, high); } } return input[p]; } public static int partition(int[] arr, int low, int high) { if (low == high) { return low; } int pivotIndex = low + (int) (Math.random() * (high - low)); swap(arr, low, pivotIndex); int pivot = arr[low]; int i = low; int j = high + 1; while (i < j) { while (arr[++i] < pivot) { if (i == high) break; } while (arr[--j] > pivot) { if (j == low) break; } if (i > j) break; swap(arr, i, j); } swap(arr, j, low); return j; } public static void swap(int[] arr, int i, int j) { int tmp = arr[i]; arr[i] = arr[j]; arr[j]= tmp; } public static void main(String[] args) { int[] arr = new int[] {4,5,1,6,2,7,3,8}; for (int i : getLeastKNumbers(arr, 8)) { System.out.println(i); } System.out.println(kthNum(arr, 5)); } } ================================================ FILE: offer/src/com/ex/offer/Ex_41_MedianInDataFlow.java ================================================ package com.ex.offer; import java.util.Comparator; import java.util.PriorityQueue; public class Ex_41_MedianInDataFlow { private PriorityQueue maxHeap = new PriorityQueue<>(new MaxHeapComparator()); private PriorityQueue minHeap = new PriorityQueue<>(new MinHeapComparator()); public void Insert(Integer num) { if (this.maxHeap.isEmpty()) { this.maxHeap.add(num); return; } if (num <= this.maxHeap.peek()) { this.maxHeap.add(num); } else { if (this.minHeap.isEmpty()) { this.minHeap.add(num); return; } if (num >= this.minHeap.peek()) { this.minHeap.add(num); } else { this.maxHeap.add(num); } } modifyTwoHeapSize(); } public Double GetMedian() { int maxHeapSize = this.maxHeap.size(); int minHeapSize = this.minHeap.size(); // 若MaxHeap或者minHeap为空,则直接return null if (maxHeapSize + minHeapSize == 0) { return null; } Integer max = this.maxHeap.peek(); Integer min = this.minHeap.peek(); if (maxHeapSize == minHeapSize) { return (max + min) / 2.0; } return 1.0 * (maxHeapSize > minHeapSize ? max : min); } private void modifyTwoHeapSize() { if (this.maxHeap.size() == this.minHeap.size() + 2) { this.minHeap.add(this.maxHeap.poll()); } if (this.minHeap.size() == this.maxHeap.size() + 2) { this.maxHeap.add(this.minHeap.poll()); } } private static class MinHeapComparator implements Comparator { @Override public int compare(Integer o1, Integer o2) { if (o1 > o2) { return 1; } else { return -1; } } } private static class MaxHeapComparator implements Comparator { @Override public int compare(Integer o1, Integer o2) { return o2 > o1 ? 1 : -1; } } } ================================================ FILE: offer/src/com/ex/offer/Ex_42_MaxSumOfSubArray.java ================================================ package com.ex.offer; // 此题目类似于:Ex_63_MaxProfits public class Ex_42_MaxSumOfSubArray { public int FindGreatestSumOfSubArray(int[] array) { if (array == null || array.length < 1) { return 0; } int max = Integer.MIN_VALUE; int curSum = 0; for (int i = 0; i < array.length; i++) { curSum += array[i]; max = Math.max(max, curSum); curSum = curSum < 0 ? 0 : curSum; } return max; } } ================================================ FILE: offer/src/com/ex/offer/Ex_43_NumberOf1Between1AndN.java ================================================ package com.ex.offer; //https://www.cnblogs.com/xuanxufeng/p/6854105.html public class Ex_43_NumberOf1Between1AndN { public int NumberOf1Between1AndN_Solution1(int n) { if (n < 1) { return 0; } int count = 0; for (int i = 1; i <= n; i++) { count += numberOf1(i); } return count; } private int numberOf1(int num) { int count = 0; while (num != 0) { if (num % 10 == 1) { count++; } num = num / 10; } return count; } public int NumberOf1Between1AndN_Solution(int n) { int ones = 0; // ones:1的个数 // m:分别指的是个位,十位,百位...,即:10 100 1000 ... m是在以10倍增长,因此可能超过int型,变成long // 分别按照个位十位百位统计各个位子上的个数 for (long m = 1; m <= n; m *= 10) { //a = n / m; // n的高位 //b = n % m; // n的低位 ones += (n/m + 8) / 10 * m + (n/m % 10 == 1 ? n%m + 1 : 0); } return ones; } } ================================================ FILE: offer/src/com/ex/offer/Ex_44_ANumInArray.java ================================================ package com.ex.offer; import org.junit.Test; public class Ex_44_ANumInArray { public int digitAt(int index) { if (index < 0) { return -1; } int digits = 1; while (true) { int numbers = countNumbersOf(digits); if (index < numbers * digits) { return digitAt(index, digits); } index -= numbers * digits; digits++; } } private int countNumbersOf(int digits) { if (digits == 1) { return 10; } return 9 * (int)Math.pow(10, digits-1); } private int digitAt(int index, int digits) { int startNumber = digits == 1 ? 0 : (int)Math.pow(10, digits-1); int endNumber = startNumber + index / digits; index = digits - index % digits; for (int i = 1; i < index; i++) { endNumber /= 10; } return endNumber % 10; } @Test public void test() { System.out.println(digitAt(1001)); } } ================================================ FILE: offer/src/com/ex/offer/Ex_45_MinNumByConcatArray.java ================================================ package com.ex.offer; import java.util.Arrays; import java.util.Comparator; // 最小字典序问题 public class Ex_45_MinNumByConcatArray { public String PrintMinNumber(int [] numbers) { if(numbers == null || numbers.length <= 0) { return ""; } String[] strs = new String[numbers.length]; for (int i = 0; i < numbers.length; i++) { strs[i] = String.valueOf(numbers[i]); } Arrays.sort(strs, new StrComparator()); String res = ""; for (String str : strs) { res += str; } return res; } private static class StrComparator implements Comparator { @Override public int compare(String o1, String o2) { return (o1+o2).compareTo(o2+o1); } } } ================================================ FILE: offer/src/com/ex/offer/Ex_46_ConvertStringToIP.java ================================================ package com.ex.offer; /** * 附加题目 * * problem 5: [String][递归][动态规划] * * 给定一个由数字组成的字符串, 请返回能返回多少种合法的ipv4组合。 * * 知识点: * IPv4: 点分十进制 * 1. x.x.x.x: 4 parts * 2. 0 <= x <= 255 * 3. x不能有0a这种形式 */ public class Ex_46_ConvertStringToIP { public static int convertStringToIp1(String str) { if (str == null || str.length() < 4 || str.length() > 12) { return 0; } char[] chars = str.toCharArray(); return process1(chars, 0, 0); } /** * * @param chars * @param i 0~i-1已经形成合法的IPv4的部分,考察i~N-1能组合出多少种 * @param parts 0~i-1已经形成合法IPv4的部分的部分数 * @return */ public static int process1(char[] chars, int i, int parts) { // if (i > chars.length || parts > 4) { // return 0; // } if (i == chars.length) { return parts == 4 ? 1 : 0; } int res = 0; res += process1(chars, i+1, parts+1); if (chars[i] == '0') { return res; } if (i + 2 <= chars.length) { res += process1(chars, i+2, parts+1); } if (i + 3 <= chars.length) { int sum = (chars[i] - '0') * 100 + (chars[i+1] - '0') * 10 + (chars[i+2] - '0'); if (sum <= 255) { return res + process1(chars, i+3, parts + 1); } } return res; } public static int convertStringToIp2(String str) { if (str == null || str.length() < 4 || str.length() > 12) { return 0; } char[] chars = str.toCharArray(); int[][] dp = new int[chars.length+1][6]; dp[chars.length][4] = 1; for (int i = dp.length-2; i >= 0; i--) { for (int j = dp[i].length - 2; j >= 0; j--) { dp[i][j] = dp[i+1][j+1]; if (chars[i] != '0') { if (i+2 < dp.length) { dp[i][j] = dp[i][j] + dp[i+2][j+1]; } if (i+3 < dp.length) { int sum = (chars[i] - '0') * 100 + (chars[i+1] - '0') * 10 + (chars[i+2] - '0'); if (sum < 256) { dp[i][j] = dp[i][j] + dp[i + 3][j + 1]; } } } } } return dp[0][0]; } public static String getRandomNumberString() { char[] chas = new char[(int) (Math.random() * 10) + 3]; for (int i = 0; i < chas.length; i++) { chas[i] = (char) (48 + (int) (Math.random() * 10)); } return String.valueOf(chas); } public static void main(String[] args) { int testTime = 3000000; boolean hasErr = false; for (int i = 0; i < testTime; i++) { String test = getRandomNumberString(); if (convertStringToIp1(test) != convertStringToIp2(test)) { hasErr = true; } } if (hasErr) { System.out.println("233333"); } else { System.out.println("666666"); } } } ================================================ FILE: offer/src/com/ex/offer/Ex_46_TranslationFromIntToString.java ================================================ package com.ex.offer; import org.junit.Test; /** * String -> Another String */ public class Ex_46_TranslationFromIntToString { // 动态规划:从下而上(从后往前) public int translation(int num) { if (num < 0) { return -1; } String str = String.valueOf(num); int len = str.length(); int counts[] = new int[len]; counts[len-1] = 1; for (int i = len-2; i>=0; i--) { int count = 0; count = counts[i+1]; int d1 = str.charAt(i) - '0'; int d2 = str.charAt(i+1) - '0'; int convertedNum = d1 * 10 + d2; if (convertedNum >= 10 && convertedNum <=25) { if (i < len-2) { count += counts[i+2]; } else { count += 1; } } counts[i] = count; } return counts[0]; } // 递归:自上而下(从前到后) public int translation1(int num) { if (num < 0) { return -1; } String str = String.valueOf(num); char[] chars = str.toCharArray(); return translationCore(chars, 0); } private int translationCore(char[] chars, int start) { if (start == chars.length) { return 1; } int res = 0; res += translationCore(chars, start + 1); if (chars[start] == '0') { return res; } if (start + 1 < chars.length) { int sum = (chars[start] - '0') * 10 + (chars[start+1] - '0'); if (sum < 26) { res += translationCore(chars, start + 2); } } return res; } @Test public void test() { System.out.println(translation(1234)); System.out.println(translation(12258)); } } ================================================ FILE: offer/src/com/ex/offer/Ex_47_MaxGiftValue.java ================================================ package com.ex.offer; import org.junit.Test; public class Ex_47_MaxGiftValue { public int getMaxValue(int[][] matrix) { if (matrix == null || matrix.length <= 0 || matrix[0].length <= 0) { return -1; } int rowSize = matrix.length; int colSize = matrix[0].length; int[][] result = new int[rowSize][colSize]; for (int i = 0; i < rowSize; i++) { for (int j = 0; j < colSize; j++) { int left = 0; int up = 0; if (i > 0) { left = result[i-1][j]; } if (j > 0) { up = result[i][j-1]; } result[i][j] = Math.max(left, up) + matrix[i][j]; } } return result[rowSize-1][colSize-1]; } public int getMaxValue1(int[][] matrix) { if (matrix == null || matrix.length <= 0 || matrix[0].length <= 0) { return -1; } return process(matrix, 0, 0); } private int process(int[][] matrix, int startRow, int startCol) { int rowSize = matrix.length; int colSize = matrix[0].length; int curValue = matrix[startRow][startCol]; if (startRow == rowSize && startCol == colSize) { return matrix[startRow][startCol]; } int down = 0; int right = 0; if (startRow + 1 < rowSize) { down = process(matrix, startRow + 1, startCol); } if (startCol + 1 < colSize) { right = process(matrix, startRow, startCol + 1); } return curValue + Math.max(down, right); } @Test public void test() { int[][] matrix = new int[][] { {1 , 10, 3 , 8 }, {12, 2 , 9 , 6 }, {5 , 7 , 4 , 11}, {3, 7 , 16, 5 } }; System.out.println(getMaxValue(matrix)); System.out.println(getMaxValue1(matrix)); } } ================================================ FILE: offer/src/com/ex/offer/Ex_48_LongestSubStringWithDuplication.java ================================================ package com.ex.offer; import org.junit.Test; // 这个题目和Ex_42_MaxSumOfSubArray类似 public class Ex_48_LongestSubStringWithDuplication { public int longestSubString(String str) { if (str == null || str.length() < 1) { return 0; } int curLen = 0; int maxLen = 0; // 注意这里利用一个int[]记录了每个字符上次出现的位置,这样就可以计算出每个位置和他上次出现位置的距离 // 注意其下标为:char - 'a' int[] lastPositions = new int[26]; // 某个字符上一次出现的索引,从未出现为-1; for (int i = 0; i < 26; i++) { lastPositions[i] = -1; } for (int i = 0; i < str.length(); i++) { int lastIndex = lastPositions[str.charAt(i) - 'a']; int distance = i - lastIndex; if (lastIndex < -1 || distance > curLen) { curLen++; } else { curLen = distance; } if (curLen > maxLen) maxLen = curLen; lastPositions[str.charAt(i) - 'a'] = i; // 更新最后出现位置 } return Math.max(maxLen, curLen); } @Test public void test() { String str = "arabcacfr"; System.out.println(longestSubString(str)); } } ================================================ FILE: offer/src/com/ex/offer/Ex_49_UglyNumber.java ================================================ package com.ex.offer; import org.junit.Test; public class Ex_49_UglyNumber { // 保存所有丑数,同时只检查丑数,不检查非丑数 public int getUglyNumber(int index) { if (index <= 0) { return 0; } int[] uglyNumbers = new int[index]; uglyNumbers[0] = 1; int nextIndex = 1; int indexOf2 = 0; // nextIndex之前(包括)最小的索引(*2得到的丑数) int indexOf3 = 0; int indexOf5 = 0; while (nextIndex < index) { int nextUgly = min(uglyNumbers[indexOf2] * 2, uglyNumbers[indexOf3] * 3, uglyNumbers[indexOf5] * 5); uglyNumbers[nextIndex] = nextUgly; while (uglyNumbers[indexOf2] * 2 <= nextUgly) { indexOf2++; } while (uglyNumbers[indexOf3] * 3 <= nextUgly) { indexOf3++; } while (uglyNumbers[indexOf5] * 5 <= nextUgly) { indexOf5++; } nextIndex++; } return uglyNumbers[index-1]; } private int min(int x, int y, int z) { int min = x < y ? x : y; return min < z ? min : z; } // sol 1: 暴力解法 public int getUglyNumber1(int index) { if (index <= 0) { return -1; } int number = 0; int uglyCount = 0; while (uglyCount < index) { number++; if (isUgly(number)) { uglyCount++; } } return number; } // 丑数的性质: // 若一个数仅能被2, 3, 5整除,那么除去所有的2 3 5 因子,之后应该是1 // 若一个数是丑数,则这个数的2,3, 5倍都是丑数。 public boolean isUgly(int number) { if (number == 1) { return true; } while (number % 2 == 0) { number /= 2; } while (number % 3 == 0) { number /= 3; } while (number % 5 == 0) { number /= 5; } return number == 1 ? true : false; } @Test public void test() { int number = 14; System.out.println(getUglyNumber(1500)); System.out.println(getUglyNumber1(1500)); } } ================================================ FILE: offer/src/com/ex/offer/Ex_50_FirstNotRepeatedChar.java ================================================ package com.ex.offer; import java.util.HashMap; public class Ex_50_FirstNotRepeatedChar { public int FirstNotRepeatingChar(String str) { if (str == null || str.length() < 1) { return -1; } int[] hashTable = new int[256]; // 简单的HashTable for (int i = 0; i < 256; i++) { hashTable[i] = 0; } for (int i = 0; i < str.length(); i++) { hashTable[str.charAt(i)]++; } int index = -1; for (int i = 0; i < str.length(); i++) { if (hashTable[str.charAt(i)] == 1) { index = i; break; } } return index; } public int FirstNotRepeatingChar1(String str) { if (str == null || str.length() < 1) { return -1; } HashMap map = new HashMap<>(); for (int i = 0; i < str.length(); i++) { if (!map.containsKey(str.charAt(i))) { map.put(str.charAt(i), 1); } else { int count = map.get(str.charAt(i)).intValue() + 1; map.put(str.charAt(i), count); } } int index = 0; for (int i = 0; i < str.length(); i++) { if (map.get(str.charAt(i)).intValue() == 1) { index = i; break; } } return index; } } ================================================ FILE: offer/src/com/ex/offer/Ex_50_FirstNotRepeatedCharInDataFlow.java ================================================ package com.ex.offer; public class Ex_50_FirstNotRepeatedCharInDataFlow { private static final int TABLE_SIZE = 256; // 保存字符在字符串中的位置 // -1:初始值 // -2:重复 // >= 0:表示第一次出现 private int[] hashTable = new int[TABLE_SIZE]; private int index = 0; public Ex_50_FirstNotRepeatedCharInDataFlow() { for (int i = 0; i < TABLE_SIZE; i++) { hashTable[i] = -1; } } //Insert one char from string stream public void Insert(char ch) { if (hashTable[ch] == -1) { hashTable[ch] = index; } else if (hashTable[ch] >= 0) { hashTable[ch] = -2; } index++; } //return the first appearance once char in current string stream public char FirstAppearingOnce() { char c = '#'; int minIndex = Integer.MAX_VALUE; for (int i = 0; i < TABLE_SIZE; i++) { if (hashTable[i] >= 0 && hashTable[i] < minIndex) { c = (char)i; minIndex = hashTable[i]; } } return c; } } ================================================ FILE: offer/src/com/ex/offer/Ex_50_StringUtilsSolutions.java ================================================ package com.ex.offer; import org.junit.Test; import java.util.HashMap; public class Ex_50_StringUtilsSolutions { public String deleteString(String str1, String str2) { if (str1 == null || str1.length() <= 0) return null; if (str2 == null || str2.length() <= 0) return str1; HashMap map = new HashMap<>(); for (int i = 0; i < str2.length(); i++) { map.put(str2.charAt(i), 1); } StringBuilder sb = new StringBuilder(); for (int i = 0; i < str1.length(); i++) { if (!map.containsKey(str1.charAt(i))) { sb.append(str1.charAt(i)); } } return sb.toString(); } public String deleteDuplicatedChar(String str) { if (str == null || str.length() <= 0) { return null; } HashMap map = new HashMap<>(); for (int i = 0; i < str.length(); i++) { if (!map.containsKey(str.charAt(i))) { map.put(str.charAt(i), 1); } else { map.put(str.charAt(i), map.get(str.charAt(i)).intValue() + 1); } } StringBuilder sb = new StringBuilder(); for (int i = 0; i < str.length(); i++) { if (map.get(str.charAt(i)).intValue() > 1) { sb.append(str.charAt(i)); map.put(str.charAt(i), 0); } if (map.get(str.charAt(i)).intValue() == 1) { sb.append(str.charAt(i)); } } return sb.toString(); } public boolean isAnagram(String str1, String str2) { if (str1 == null || str1.length() <= 0 || str2 == null || str2.length() <= 0 || str1.length() != str2.length()) { return false; } HashMap map = new HashMap<>(); for (int i = 0; i < str1.length(); i++) { if (!map.containsKey(str1.charAt(i))) { map.put(str1.charAt(i), 1); } else { map.put(str1.charAt(i), map.get(str1.charAt(i)).intValue() + 1); } } for (int i = 0; i < str2.length(); i++) { if (map.containsKey(str2.charAt(i))) { map.put(str2.charAt(i), map.get(str2.charAt(i)).intValue()-1); } } for (Character c : map.keySet()) { if (map.get(c).intValue() != 0) { return false; } } return true; } @Test public void testDeleteString() { String str1 = "We are students"; String str2 = "aeiou"; System.out.println(deleteString(str1, str2)); } @Test public void testDeleteDuplicatedChar() { String str = "google"; System.out.println(deleteDuplicatedChar(str)); } @Test public void testIsAnagram() { String str1 = "silent"; String str2 = "listen"; String str3 = "livexy"; System.out.println(isAnagram(str1, str2)); System.out.println(isAnagram(str1, str3)); } } ================================================ FILE: offer/src/com/ex/offer/Ex_51_InversePairs.java ================================================ package com.ex.offer; import java.util.Arrays; public class Ex_51_InversePairs { public int InversePairs(int [] array) { if (array == null || array.length < 2) { return 0; } int[] copy = Arrays.copyOf(array, array.length); int count = mergeSort(array, 0, array.length-1); return count; } private int mergeSort(int[] array, int low, int high) { if (low == high) { return 0; } int mid = low + ((high-low) >> 1); int left = mergeSort(array, low, mid); int right = mergeSort(array, mid+1, high); int merge = merge(array, low, mid, high); return left + right + merge ; } private int merge(int[] array, int low, int mid, int high) { int[] help = new int[high-low+1]; int i = 0; int p1 = low; int p2 = mid+1; int res = 0; while (p1 <= mid && p2 <= high) { res += array[p1] > array[p2] ? (high-p2+1) : 0; help[i++] = array[p1] > array[p2] ? array[p1++] : array[p2++]; } while (p1 <= mid) { help[i++] = array[p1++]; } while (p2 <= high) { help[i++] = array[p2++]; } for (i = 0; i < help.length; i++) { array[low + i] = help[i]; } return res; } } ================================================ FILE: offer/src/com/ex/offer/Ex_51_InversePairs_BigData.java ================================================ package com.ex.offer; import java.util.Arrays; public class Ex_51_InversePairs_BigData { public int InversePairs(int [] array) { if (array == null || array.length < 2) { return 0; } int count = mergeSort(array, 0, array.length-1); return count; } private int mergeSort(int[] array, int low, int high) { if (low == high) { return 0; } int mid = low + ((high-low) >> 1); int left = mergeSort(array, low, mid); int right = mergeSort(array, mid+1, high); int merge = merge(array, low, mid, high); return (left + right + merge) % 1000000007 ; } private int merge(int[] array, int low, int mid, int high) { int[] help = new int[high-low+1]; int i = 0; int p1 = low; int p2 = mid+1; int res = 0; while (p1 <= mid && p2 <= high) { res += array[p1] > array[p2] ? (high-p2+1): 0; res = res >= 1000000007? res % 1000000007 : res; // 数据过大,会溢出 help[i++] = array[p1] > array[p2] ? array[p1++] : array[p2++]; } while (p1 <= mid) { help[i++] = array[p1++]; } while (p2 <= high) { help[i++] = array[p2++]; } for (i = 0; i < help.length; i++) { array[low + i] = help[i]; } return res; } } ================================================ FILE: offer/src/com/ex/offer/Ex_52_FirstCommonNode.java ================================================ package com.ex.offer; public class Ex_52_FirstCommonNode { public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) { if (pHead1 == null || pHead2 == null) { return null; } int len1 = getLengthOfList(pHead1); int len2 = getLengthOfList(pHead2); int diff = Math.abs(len1-len2); ListNode longListHead = len1 > len2 ? pHead1 : pHead2; ListNode shortListHead = len1 < len2 ? pHead1 : pHead2; for (int i = 0; i < diff; i++) { longListHead = longListHead.next; } while (longListHead != null && longListHead.val != shortListHead.val) { longListHead = longListHead.next; shortListHead = shortListHead.next; } return longListHead != null ? longListHead : null; } private int getLengthOfList(ListNode pHead1) { if (pHead1 == null) { return 0; } int len = 0; for (ListNode node = pHead1; node != null; node = node.next) { len++; } return len; } } ================================================ FILE: offer/src/com/ex/offer/Ex_53_GetMissingNumber.java ================================================ package com.ex.offer; public class Ex_53_GetMissingNumber { public int getMissingNumber(int[] array) { if (array == null || array.length < 1) { return -1; } int low = 0; int high = array.length-1; while (low <= high) { int mid = low + ((high - low) >> 1); if (array[mid] != mid) { if (mid == 0 || array[mid-1] == mid-1) { return mid; } high = mid-1; } else { low = mid + 1; } } if (low == array.length-1) { return array.length; } return -1; } } ================================================ FILE: offer/src/com/ex/offer/Ex_53_GetNumberOfK.java ================================================ package com.ex.offer; import org.junit.Test; public class Ex_53_GetNumberOfK { public int GetNumberOfK(int [] array , int k) { if (array == null || array.length <= 0) { return 0; } int left = getFirstIndexOfK(array, k, 0, array.length-1); int right = getLastIndexOfK(array, k, 0, array.length-1); if (left != -1 && right != -1) { return right - left + 1; } return 0; } private int getFirstIndexOfK(int[] array, int k, int low, int high) { if (low > high) { return -1; } int midIndex = low + ((high-low) >> 1); int midData = array[midIndex]; if (midData == k) { if (midIndex > 0 && array[midIndex-1] != midData || midIndex == 0) { return midIndex; } else { high = midIndex-1; } } else if (midData > k) { high = midIndex - 1; } else { low = midIndex + 1; } return getFirstIndexOfK(array, k, low, high); } private int getLastIndexOfK(int[] array, int k, int low, int high) { if (low > high) { return -1; } int midIndex = low + ((high-low) >> 1); int midData = array[midIndex]; if (midData == k) { if (midIndex < array.length-1 && array[midIndex+1] != k || midIndex == array.length-1) { return midIndex; } else { low = midIndex + 1; } } else if (midData > k) { high = midIndex - 1; } else { low = midIndex + 1; } return getLastIndexOfK(array, k, low, high); } @Test public void test() { int[] array = new int[]{1,3,3,3,3,3,4,5}; System.out.println(getFirstIndexOfK(array, 2, 0, array.length-1)); System.out.println(getLastIndexOfK(array, 2, 0, array.length-1)); System.out.println(GetNumberOfK(array, 2)); } } ================================================ FILE: offer/src/com/ex/offer/Ex_53_GetNumberSameAsIndex.java ================================================ package com.ex.offer; import org.junit.Test; public class Ex_53_GetNumberSameAsIndex { public int getNumberSameAsIndex(int[] array) { if (array == null || array.length <= 0) { return -1; } int low = 0; int high = array.length-1; while (low <= high) { int midIndex = low + ((high - low) >> 1); int midData = array[midIndex]; if (midData == midIndex) { return midData; } else if (midData > midIndex) { high = midIndex-1; } else { low = midIndex + 1; } } return -1; } @Test public void test() { int[] array = new int[] {-3,-1,1,3,5}; System.out.println(getNumberSameAsIndex(array)); } } ================================================ FILE: offer/src/com/ex/offer/Ex_54_KthNodeInBST.java ================================================ package com.ex.offer; public class Ex_54_KthNodeInBST { int count = 0; // 递归全局变量 TreeNode KthNode(TreeNode pRoot, int k) { if (pRoot != null) { TreeNode node = KthNode(pRoot.left, k); if (node != null) { return node; } count++; if (count == k) { return pRoot; } node = KthNode(pRoot.right, k); if (node != null) { return node; } } return null; } } ================================================ FILE: offer/src/com/ex/offer/Ex_55_BalancedBT.java ================================================ package com.ex.offer; public class Ex_55_BalancedBT { // Java没有引用和指针,可以使用全局变量,也可以使用数组引用 int depth = 0; public boolean IsBalanced_Solution(TreeNode root) { return isBalance(root); } private boolean isBalance(TreeNode root) { if (root == null) { depth = 0; // 深度为0 return true; } boolean left = isBalance(root.left); int leftDepth = depth; // 左子树深度 boolean right = isBalance(root.right); int rightDepth = depth; // 右子树深度 depth = Math.max(leftDepth + 1, rightDepth + 1); // root深度(后序遍历) if (left && right && Math.abs(leftDepth - rightDepth) <= 1) { return true; } return false; } //pos-order遍历,使用辅助数组depth来保存每一步遍历的深度,同时返回该节点是否是平衡的 public boolean IsBalanced_Solution1(TreeNode root) { return isBalance(root, new int[1]); } public boolean isBalance(TreeNode root, int[] depth) { if (root == null) { depth[0] = 0; return true; } boolean left = isBalance(root.left, depth); int leftdepth = depth[0]; boolean right = isBalance(root.right, depth); int rightdepth = depth[0]; depth[0] = Math.max(leftdepth + 1, rightdepth + 1); if (left && right && Math.abs(leftdepth - rightdepth) <= 1) return true; return false; } // 从上到下遍历: 遍历每个结点,借助一个获取树深度的递归函数, // 根据该结点的左右子树高度差判断是否平衡,然后递归地对左右子树进行判断。 public boolean IsBalanced_Solution2(TreeNode root) { if (root == null) { return true; } int leftDepth = TreeDepth(root.left); int rightDepth = TreeDepth(root.right); int diff = leftDepth - rightDepth; if (diff > 1 || diff < -1) { return false; } return IsBalanced_Solution2(root.left) && IsBalanced_Solution2(root.right); } private int TreeDepth(TreeNode root) { if (root == null) { return 0; } int leftTreeDepth = TreeDepth(root.left); int rightTreeDepth = TreeDepth(root.right); return leftTreeDepth > rightTreeDepth ? (leftTreeDepth + 1) : (rightTreeDepth + 1); } // 从下到上遍历: // 从上到下遍历在判断上层结点的时候,会多次重复遍历下层结点,增加了不必要的开销。 // 如果改为从下往上遍历,如果子树是平衡二叉树,则返回子树的高度; // 如果发现子树不是平衡二叉树,则直接停止遍历,这样至多只对每个结点访问一次。 public boolean IsBalanced_Solution3(TreeNode root) { return getDepth(root) != -1; } // 若左右子树高度差大于1,则返回-1(不是平衡二叉树) // 若左右子树高度差不大于1,则返回子树的高度 private int getDepth(TreeNode root) { if (root == null) { return 0; } int leftDepth = getDepth(root.left); if (leftDepth == -1) return -1; int rightDepth = getDepth(root.right); if (rightDepth == -1) return -1; return Math.abs(leftDepth - rightDepth) > 1 ? -1 : (Math.max(leftDepth, rightDepth) + 1); } } ================================================ FILE: offer/src/com/ex/offer/Ex_55_DepthOfBT.java ================================================ package com.ex.offer; public class Ex_55_DepthOfBT { public int TreeDepth(TreeNode root) { if (root == null) { return 0; } int leftTreeDepth = TreeDepth(root.left); int rightTreeDepth = TreeDepth(root.right); return leftTreeDepth > rightTreeDepth ? (leftTreeDepth + 1) : (rightTreeDepth + 1); } } ================================================ FILE: offer/src/com/ex/offer/Ex_56_AppearanceOnce.java ================================================ package com.ex.offer; //num1,num2分别为长度为1的数组。传出参数 //将num1[0],num2[0]设置为返回结果 public class Ex_56_AppearanceOnce { public void FindNumsAppearOnce(int [] array,int num1[] , int num2[]) { if (array == null || array.length < 2) { return; } int xor = 0; for (int i = 0; i < array.length; i++) { xor = xor ^ array[i]; // xor = a ^ b = num1[0] ^ num2[0] } int indexOf1 = findFirst1_Bit(xor); for (int i = 0; i < array.length; i++) { if (is1_Bit(array[i], indexOf1)) { num1[0] = num1[0] ^ array[i]; } else { num2[0] = num2[0] ^ array[i]; } } } // 从右到左第1bit为1的index private int findFirst1_Bit(int num) { int index = 0; int len = 8 * 4; while (index < len && (num & 1) == 0) { num = num >> 1; index++; } return index; } // 判断从右到左第index位是不是1 private boolean is1_Bit(int num, int index) { num = num >> index; return (num & 1) == 1; } } ================================================ FILE: offer/src/com/ex/offer/Ex_56_AppearanceOnce_Continued.java ================================================ package com.ex.offer; import org.junit.Test; public class Ex_56_AppearanceOnce_Continued { public int FindNumAppearOnce(int[] array) { if (array == null || array.length < 1) { return -1; } int[] bitSum = new int[8 * 4];// 所有元素的各个bit分别相加 for (int i = 0; i < array.length; i++) { int bitFlag = 1; // bit & 1 = bit 如:xxx & 010 = 0x0 for (int j = 31; j >= 0; j--) { int bit = array[i] & bitFlag; // 每一位都是0x0...或者000... bitSum[j] += bit == 0 ? 0 : 1; // 但是这里只能加1或者0 bitFlag = bitFlag << 1; } } int result = 0; for (int i = 0; i < 32; i++) { result = result << 1; result += bitSum[i] % 3; } return result; } @Test public void test() { int[] array = new int[] {4, 5,5,5,9,9,9,8,8,8, 20, 4,4}; System.out.println(FindNumAppearOnce(array)); } } ================================================ FILE: offer/src/com/ex/offer/Ex_57_FindContinuousSequence.java ================================================ package com.ex.offer; import org.junit.Test; import java.util.ArrayList; public class Ex_57_FindContinuousSequence { public ArrayList> FindContinuousSequence(int sum) { ArrayList> res = new ArrayList<>(); if (sum < 3) { return res; } int small = 1; int big = 2; int mid = (sum + 1) / 2; int curSum = small+big; while (small < mid) { if (curSum == sum) { res.add(new ArrayList<>(full(small, big))); // 一定要注意这里,要拷贝构造,不能直接传入引用 } while (curSum > sum && small < mid) { curSum -= small; small++; if (curSum == sum) { res.add(new ArrayList<>(full(small, big))); } } big++; curSum+=big; } return res; } private ArrayList full(int small, int big) { ArrayList list = new ArrayList<>(); for (int i = small; i <= big; i++) { list.add(i); } return list; } @Test public void test() { ArrayList> list = FindContinuousSequence(15); for (ArrayList x : list) { for (Integer i : x) { System.out.print(i + " "); } System.out.println(); } } } ================================================ FILE: offer/src/com/ex/offer/Ex_57_FindNumbersWithSum.java ================================================ package com.ex.offer; import org.junit.Test; import java.util.ArrayList; public class Ex_57_FindNumbersWithSum { public ArrayList FindNumbersWithSum(int [] array, int sum) { ArrayList res = new ArrayList<>(); if (array == null || array.length <= 1) return res; int left = 0; int right = array.length-1; int curSum = 0; ArrayList leftList = new ArrayList<>(); ArrayList rightList = new ArrayList<>(); boolean isFound = false; while (left < right) { curSum = array[left] + array[right]; if (curSum == sum) { leftList.add(left); rightList.add(right); left++; right--; isFound = true; } else if (curSum < sum) { left++; } else { right--; } } if (!isFound) return res; int min = Integer.MAX_VALUE; int minLeft = -1; int minRight = -1; for (int i = 0; i < leftList.size(); i++) { int tmp = array[leftList.get(i)] * array[rightList.get(i)]; if (tmp < min) { min = tmp; // 注意修改min的值 minLeft = leftList.get(i); minRight = rightList.get(i); } } if (minLeft != -1) { res.add(array[minLeft]); res.add(array[minRight]); } return res; } @Test public void test() { int[] array = new int[] {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20}; ArrayList res = FindNumbersWithSum(array, -1); for (Integer i : res) { System.out.println(i); } } } ================================================ FILE: offer/src/com/ex/offer/Ex_58_ROL.java ================================================ package com.ex.offer; import org.junit.Test; public class Ex_58_ROL { public String LeftRotateString(String str,int n) { if (str == null || str.length() <= 0 || n < 0 || n > str.length()) { return ""; } char[] chars = str.toCharArray(); reverse(chars, 0, n-1); reverse(chars, n, chars.length-1); reverse(chars, 0, chars.length-1); return String.valueOf(chars); } private void reverse(char[] chars, int start, int end) { for (int i = start, j = end; i <= j; i++, j--) { char temp = chars[i]; chars[i] = chars[j]; chars[j] = temp; } } @Test public void test() { String str = "abcXYZdef"; System.out.println(LeftRotateString(str, 3)); } } ================================================ FILE: offer/src/com/ex/offer/Ex_58_ReverseSentence.java ================================================ package com.ex.offer; import org.junit.Test; public class Ex_58_ReverseSentence { public String ReverseSentence(String str) { // 边界条件1:str == null or str = "" if (str == null || str.length() <= 0) { return ""; } // 注意这里:String是不可变的,因此我们需要将String转换为char[] char[] chars = str.toCharArray(); reverse(chars, 0, chars.length-1); int start = 0; // 指向单词的开头 while (start < chars.length && chars[start] == ' ') { start++; // 排除开头的' ' } // 边界条件2:str = " " (全部都是' ') if (start == chars.length) { return str; } // start 和 end像是在接力 for (int end = start; end < chars.length; end++) { if (chars[end] == ' ') { reverse(chars, start, --end); end += 2; start = end; } } reverse(chars, start, chars.length-1); return String.valueOf(chars); } private void reverse(char[] chars, int start, int end) { for (int i = start, j = end; i <= j; i++, j--) { char temp = chars[i]; chars[i] = chars[j]; chars[j] = temp; } } @Test public void test() { String str = "I am a student."; String str1 = " "; System.out.println(ReverseSentence(str)); //System.out.println(ReverseSentence(str1)); System.out.println(str1.length()); } } ================================================ FILE: offer/src/com/ex/offer/Ex_59_MaxNumOfSlidingWindow.java ================================================ package com.ex.offer; import org.junit.Test; import java.util.ArrayList; import java.util.LinkedList; public class Ex_59_MaxNumOfSlidingWindow { public ArrayList maxInWindows(int [] num, int size) { ArrayList res = new ArrayList<>(); if (num == null || num.length <= 0 || size <= 0 || size > num.length) { return res; } LinkedList qMax = new LinkedList<>(); // 双端队列:左端更新max,右端添加数据 int left = 0; for (int right = 0; right < num.length; right++) { // 更新右端数据 while (!qMax.isEmpty() && num[qMax.peekLast()] <= num[right]) { qMax.pollLast(); } qMax.addLast(right); // 更新max:如果max的索引不在窗口内,则更新 if (qMax.peekFirst() == right - size) { qMax.pollFirst(); } // 待窗口达到size,输出max if (right >= size-1) { res.add(num[qMax.peekFirst()]); left++; } } return res; } @Test public void test() { int[] num = new int[] {2,3,4,2,6,2,5,1}; int size = 3; ArrayList list = maxInWindows(num, size); for (Integer i : list) { System.out.print(i + " "); } } } ================================================ FILE: offer/src/com/ex/offer/Ex_59_QueueWithMax.java ================================================ package com.ex.offer; import org.junit.Test; import java.util.LinkedList; public class Ex_59_QueueWithMax { private LinkedList data = new LinkedList<>(); private LinkedList qMax = new LinkedList<>(); public void push_back(Integer x) { if (!qMax.isEmpty() && qMax.peekLast() > x) { qMax.addLast(qMax.peek()); } qMax.addLast(x); data.addLast(x); } public Integer pop_front() { if (!data.isEmpty()) { qMax.pollFirst(); return data.pollFirst(); } return null; } public Integer max() { if (!qMax.isEmpty()) { return qMax.peekFirst(); } return null; } } ================================================ FILE: offer/src/com/ex/offer/Ex_60_ProbabilityOfS.java ================================================ package com.ex.offer; import org.junit.Test; // https://www.cnblogs.com/keedor/p/4474471.html // https://www.cnblogs.com/AndyJee/p/4686208.html public class Ex_60_ProbabilityOfS { private static final int MAX_VALUE = 6; // 骰子的最大点数 // probability(n, currentLeft, currentSum, sum) public void printProbability_1(int number) { if (number < 1) { return; } int maxSum = number * MAX_VALUE; int[] sums = new int[maxSum-number + 1]; for (int i = 0; i < sums.length; i++) { sums[i] = 0; } probability(number, sums); double total = Math.pow(MAX_VALUE, number); for (int i = 0; i < sums.length; i++) { System.out.println(sums[i]/total); } } private void probability(int number, int[] sums) { for(int i = 1; i <= MAX_VALUE; i++) { probability(number, number - 1, i, sums); } } /** * @param number : 总共几个骰子 * @param current:现在剩下几个骰子 * @param sum:现在已有的和 * @param sums */ private void probability(int number, int current, int sum, int[] sums) { if (current == 0) { sums[sum - number]++; return; } for (int i = 1; i <= MAX_VALUE; i++) { probability(number, current-1, sum + i, sums); } } /** * * k:骰子个数 * n:点数和 * f(k, n) = f(k-1, n-1) + f(k-1, n-2) + f(k-1, n-3) + f(k-1, n-4) + f(k-1, n-5) + f(k-1, n-6); * f(k-1, n-1):表示第k-1个骰子投了n-1点,则第k个骰子投1个点就可以得到n * f(n) = f(n-1) + f(n-2) + f(n-3) + f(n-4) + f(n-5) + f(n-6) * @param number */ public void printProbability_2(int number) { if (number < 1) { return; } /** * 我们需要将中间值存起来以减少递归过程中的重复计算问题,可以考虑我们用两个数组AB, * A在B之上得到,B又在A之上再次得到,这样AB互相作为对方的中间值, * 其实这个思想跟斐波拉契迭代算法中用中间变量保存n-1,n-2的值有异曲同工之妙 */ int[][] sums = new int[2][MAX_VALUE * number + 1]; // [1,number] int flag = 0; // 抛掷第一枚骰子 [1,2,3,...,MAX_VALUE] for (int i = 1; i <= MAX_VALUE; i++) { sums[flag][i] = 1; } // 第二次抛掷骰子开始 for (int k = 2; k <= number; k++) { // 第k次掷色子,和最小为k,和最大为MAX_VALUE * k for (int i = k; i <= MAX_VALUE * k; i++) { int temp = 0; for (int j = 1; j <= i && j <= MAX_VALUE; j++) { // j<=i,防止数组越界 temp += sums[flag][i-j]; } sums[1-flag][i] = temp; } flag = 1- flag; } double total = Math.pow(MAX_VALUE, number); for (int i = number; i < sums[flag].length; i++) { System.out.println(sums[flag][i]/total); } } @Test public void test1() { printProbability_1(2); System.out.println(); } @Test public void test2() { printProbability_2(2); } } ================================================ FILE: offer/src/com/ex/offer/Ex_61_ContinuousSequence.java ================================================ package com.ex.offer; import java.util.Arrays; public class Ex_61_ContinuousSequence { public boolean isContinuous(int [] numbers) { if (numbers == null || numbers.length < 5) { return false; } Arrays.sort(numbers); int numberOfZero = 0; int gap = 0; for (int i = 0; i < numbers.length && numbers[i] == 0; i++) { numberOfZero++; } int small = numberOfZero; // 0之后的第一位索引 int big = small + 1; while (big < numbers.length) { if (numbers[small] == numbers[big]) { return false; } gap += numbers[big] - numbers[small] - 1; small = big; big++; } return gap > numberOfZero ? false : true; } } ================================================ FILE: offer/src/com/ex/offer/Ex_62_Josephus.java ================================================ package com.ex.offer; import org.junit.Test; import java.util.ArrayList; // 约瑟夫问题其实是一个环型链表 // 假设 n > m, 则第一个要删除的位置是 index = m-1, 第二个是 (index + m) % size // 每次要删除的位置是 (lastIndex + m) % size; public class Ex_62_Josephus { public int LastRemaining_Solution(int n, int m) { if (n <= 0 || m <= 0) { return -1; } ArrayList list = new ArrayList<>(); for (int i = 0; i < n; i++) { list.add(i); } int index = -1; while (list.size() > 1) { index = (index + m) % list.size(); list.remove(index); index--; } return list.get(0); } /** * f(n, m) = [f(n-1, m) + m] % n (n > 1) * * @param n * @param m * @return */ public int LastRemaining_Solution1(int n, int m) { if (n <= 0 || m <= 0) { return -1; } int lastRemain = 0; for (int i = 2; i <= n; i++) { lastRemain = (lastRemain + m) % i; } return lastRemain; } @Test public void test() { System.out.println(LastRemaining_Solution(5, 3)); System.out.println(LastRemaining_Solution1(5, 3)); } } ================================================ FILE: offer/src/com/ex/offer/Ex_63_MaxProfits.java ================================================ package com.ex.offer; import org.junit.Test; // 此题目类似于Ex_42_MaxSumOfSubArray public class Ex_63_MaxProfits { public int maxProfits(int[] prices) { if (prices == null || prices.length < 2) { return -1; } int minPrice = prices[0]; // i之前[0,i-1]的最小值,控制左侧 int maxDiff = prices[1] - minPrice; // 控制右侧,遍历了所有可能最大差值 for (int i = 2; i < prices.length; i++) { int curDiff = prices[i] - minPrice; if (curDiff > maxDiff) { maxDiff = curDiff; } if (prices[i] < minPrice) { minPrice = prices[i]; } } return maxDiff; } @Test public void test() { int[] prices = new int[] {9,11,8,5,7,12,16,14}; System.out.println(maxProfits(prices)); } } ================================================ FILE: offer/src/com/ex/offer/Ex_64_SumFrom1ToN.java ================================================ package com.ex.offer; import org.junit.Test; public class Ex_64_SumFrom1ToN { public int Sum_Solution(int n) { int sum = n; boolean stop = (n > 0) && ((sum += Sum_Solution(n-1)) > 1); return sum; } @Test public void test() { System.out.println(Sum_Solution(10)); } } ================================================ FILE: offer/src/com/ex/offer/Ex_65_AddWithoutCarry.java ================================================ package com.ex.offer; public class Ex_65_AddWithoutCarry { public int Add(int num1,int num2) { int sum = 0; int carry = 0; do { sum = num1 ^ num2; //不进位加法(不断执行不进位加法,直到进位为0,则不进位加法=进位加法) carry = (num1 & num2) << 1; num1 = sum; num2 = carry; } while (num2 != 0); return sum; } } ================================================ FILE: offer/src/com/ex/offer/Ex_65_SwapNumWithoutTemp.java ================================================ package com.ex.offer; public class Ex_65_SwapNumWithoutTemp { public int[] swap1 (int a, int b) { int[] res = new int[2]; //================================= a = a + b; b = a - b; // b = (a + b) - b a = a - b; // a = (a + b) - a //================================= res[0] = a; res[1] = b; return res; } public int[] swap2 (int a, int b) { int[] res = new int[2]; //================================= a = a ^ b; b = a ^ b; // b = (a ^ b) ^ b = a ^ (b ^ b) = a ^ 0 = a a = a ^ b; //================================= res[0] = a; res[1] = b; return res; } } ================================================ FILE: offer/src/com/ex/offer/Ex_66_ConstructMultiplyArray.java ================================================ package com.ex.offer; public class Ex_66_ConstructMultiplyArray { public int[] multiply(int[] A) { int[] res = new int[A.length]; if (A == null || A.length < 2) { return res; } int[] C = new int[A.length]; int[] D = new int[A.length]; C[0] = 1; D[D.length-1] = 1; for (int i = 1; i < C.length; i++) { C[i] = C[i-1] * A[i-1]; } for (int i = D.length-2; i >= 0; i--) { D[i] = D[i+1] * A[i+1]; } for (int i = 0; i < res.length; i++) { res[i] = C[i] * D[i]; } return res; } } ================================================ FILE: offer/src/com/ex/offer/Ex_67_StringToInt.java ================================================ package com.ex.offer; import org.junit.Test; public class Ex_67_StringToInt { /** * 说明:当输入不合法时,返回0。但是由于正常输出也可能有0, * 因此需要设置全局变量isValid标记返回的0是因为输入不合法返回 * * @param str * @return */ public int StrToInt(String str) { boolean isValid = true; // 边界1:str = null // 边界2:str = "" if (str == null || str == "") { isValid = false; return 0; } int number = 0; boolean minus = false; for (int i = 0; i < str.length(); i++) { char c = str.charAt(i); if (i == 0) { if (c == '+') continue; if (c == '-') { minus = true; continue; } } // 边界3:输入存在非法字符 // [0,1,2,...,9]的ASII码[48,49,...,57] // a = 97 // A = 65 // x = (char)x - '0' (char -> int) if (c < 48 || c > 57) { isValid = false; return 0; } number = number * 10 + (c - '0'); } return minus ? -1 * number : number; } @Test public void test() { String str1 = null; String str2 = ""; // str2 = " "; String str3 = "1234"; String str4 = "+1234"; String str5 = "-1234"; String str6 = "123a5"; System.out.println(StrToInt(str1)); System.out.println(StrToInt(str2)); System.out.println(StrToInt(str3)); System.out.println(StrToInt(str4)); System.out.println(StrToInt(str5)); System.out.println(StrToInt(str6)); } } ================================================ FILE: offer/src/com/ex/offer/Ex_68_LowestCommonAncestor.java ================================================ package com.ex.offer; public class Ex_68_LowestCommonAncestor { // case1: 二叉树(二叉搜索树) public TreeNode commonAncestor(TreeNode root, TreeNode node1, TreeNode node2) { if (root == null) { return null; } return null; } // case2: 不是二叉树,但是存在指向父节点的指针 // case3: 不是二叉树,不存在指向父节点的指针 } ================================================ FILE: offer/src/com/ex/offer/ListNode.java ================================================ package com.ex.offer; /** * 单链表节点 */ public class ListNode { int val; ListNode next; public ListNode(int val) { this.val = val; } } ================================================ FILE: offer/src/com/ex/offer/Node.java ================================================ package com.ex.offer; /** * 单链表节点 */ public class Node { int val; Node next; Node (int val) { this.val = val; } } ================================================ FILE: offer/src/com/ex/offer/RandomListNode.java ================================================ package com.ex.offer; /** * 含有指向随机节点指针的单链表节点 */ public class RandomListNode { int label; RandomListNode next = null; RandomListNode random = null; RandomListNode(int label) { this.label = label; } } ================================================ FILE: offer/src/com/ex/offer/TestUtils.java ================================================ package com.ex.offer; import java.util.Arrays; public class TestUtils { public static void printArray(int[] arr) { for (int i : arr) { System.out.print(i + " "); } System.out.println(); } public static void sort(int[] arr) { Arrays.sort(arr); } public static int partition(int[] arr, int low, int high) { if (low == high) { return low; } int pIndex = low + (int) (Math.random() *(high - low)); swap(arr, low, pIndex); int pivot = arr[low]; int i = low; int j = high + 1; while (i < j) { while (arr[++i] < pivot) { if (i == high) { break; } } while (arr[--j] > pivot) { if (j == low) { break; } } if (i > j) { break; } swap(arr, i, j); } swap(arr, j, low); return j; } public static void swap(int[] arr, int i, int j) { int tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp; } // cal public static int getDigitSum(int x) { int sum = 0; while (x > 0) { sum += x % 10; x = x / 10; } return sum; } /** * 判断x的奇偶性 * if x is odd, then x & 1 == 1 3 & 1 = 0011 & 0001 = 0001 * if x is even, then x & 1 == 0 2 & 1 = 0010 & 0001 = 0000 * * @param x * @return */ public static boolean isOdd(int x) { if ((x & 0x1) == 1) { return true; } return false; } public static void printStingToInt(StringBuffer s) { System.out.println(s.toString()); for (int i = 0; i < s.length(); i++) { int num1 = s.charAt(i); // 返回s中各个字符的ASII码 int num2 = s.charAt(i) - '0'; // 返回s中各个字符的ASII码 - ‘0’的ASII码 System.out.print("s.charAt(i) = " + s.charAt(i) + "; int(s.charAt(i)) = " + num1 + "; int(s.charAt(i) - '0') = " + num2); System.out.println(); } } /** * 大数问题:字符串模拟加法 * * @param s * @return */ public static boolean Increment(StringBuffer s) { boolean isOverflow = false; int nTakeOver = 0; // 进位 int nLength = s.length(); for (int i = nLength - 1; i >= 0; i--) { int nSum = s.charAt(i) - '0' + nTakeOver; // 加1操作:如果是最后一位,增加1 if (i == nLength - 1) { nSum++; } if (nSum >= 10) { if (i == 0) { isOverflow = true; } else { nSum -= 10; nTakeOver = 1; s.setCharAt(i, (char)('0' + nSum)); } } else { // 若有一位的nSum < 10,则该可以直接退出,不满足 999...99 s.setCharAt(i, (char)('0' + nSum)); break; } } return isOverflow; } public static void printList(Node head) { System.out.print("Single Linked List: "); if (head == null) { System.out.print("empty list."); } for (Node node = head; node != null; node = node.next) { System.out.print(node.val + " "); } System.out.println(); } public static void printList_ListNode(ListNode head) { System.out.print("Single Linked List: "); if (head == null) { System.out.print("empty list."); } for (ListNode node = head; node != null; node = node.next) { System.out.print(node.val + " "); } System.out.println(); } public static void main(String[] args) { // for (int i = 0; i < 10; i++) { // System.out.println(i + ":" + isOdd(i)); // } StringBuffer s = new StringBuffer(); s.append('9'); s.append('9'); s.append('9'); s.append('9'); s.append('9'); //98765 //printStingToInt(s); Increment(s); System.out.println(s.toString()); } } ================================================ FILE: offer/src/com/ex/offer/TreeLinkNode.java ================================================ package com.ex.offer; /** * 含有指向父节点指针的二叉树节点 * Ex_08_GetNextNodeInBT */ public class TreeLinkNode { int val; TreeLinkNode left = null; TreeLinkNode right = null; TreeLinkNode parent = null; TreeLinkNode(int val) { this.val = val; } } ================================================ FILE: offer/src/com/ex/offer/TreeNode.java ================================================ package com.ex.offer; /** * 二叉树节点 */ public class TreeNode { int val; TreeNode left; TreeNode right; TreeNode(int x) { this.val = x; } } ================================================ FILE: offer/src/com/ex/singleton/Singleton1.java ================================================ package com.ex.singleton; // V1.0: 饿汉式 public class Singleton1 { // instance必须是static变量(类变量) private static Singleton1 instance = new Singleton1(); // private无参构造函数: Java会自动为没有明确声明构造函数的类, // 定义一个public的无参构造函数,若没有这个private无参构造函数, // 外部可以随意new Singleton private Singleton1() {} // getInstance必须是static方法(类方法) public static Singleton1 getInstance() { return instance; } } ================================================ FILE: offer/src/com/ex/singleton/Singleton2.java ================================================ package com.ex.singleton; // V2.0: 懒汉式 public class Singleton2 { private static Singleton2 instance = null; private Singleton2() {} public static Singleton2 getInstance() { if (instance == null) { instance = new Singleton2(); } return instance; } } ================================================ FILE: offer/src/com/ex/singleton/Singleton3.java ================================================ package com.ex.singleton; // V3.0: 双重校验 public class Singleton3 { // volatile提供可见性(工作内存的值能立即在主内存中可见), // 保证getInstance返回的是初始化完全的对象 private static volatile Singleton3 instance = null; private Singleton3() {} public static Singleton3 getInstance() { // 同步之前进行null检查,避免进入代价相对昂贵的同步块 if (instance == null) { synchronized (Singleton3.class) { // 持有锁之后进行null检查,避免上一个线程已经初始化变量 if (instance == null) { instance = new Singleton3(); } } } return instance; } } ================================================ FILE: offer/src/com/ex/singleton/Singleton4.java ================================================ package com.ex.singleton; // V4.0: 静态内部类 public class Singleton4 { private static class SingletonHolder { public static Singleton4 instance = new Singleton4(); } public static Singleton4 getInstance() { return SingletonHolder.instance; } } ================================================ FILE: offer/src/com/ex/singleton/Singleton5.java ================================================ package com.ex.singleton; // V5.0: 枚举方法 public enum Singleton5 { INSTANCE; private int id; public int getId() { return id; } public void setId(int id) { this.id = id; } public static void main(String[] args) { //调用 Singleton5.INSTANCE.setId(1); Singleton5.INSTANCE.getId(); } } ================================================ FILE: offer/src/com/ex/singleton/singleton.md ================================================ # 单例模式 ## 单例模式的好处 单例模式适用于应用中频繁创建的对象,尤其是频繁创建的重量级对象。 例如统一的配置文件。 使用单例模式能够提升性能,减小内存开销。 ## 单例模式的实现 ### 1.饿汉式 饿汉式加载类的时候,就会创建类的实例对象,那么如果不用这个单例的话,就会消耗内存,浪费性能。 因此,需要懒加载(lazy-load) ### 2.懒汉式 懒汉式改进了饿汉式的性能问题,但是又带来了一个问题,即线程安全问题。 在多线程的情况下,会出现多个实例,违背了单例模式的原则。 ### 3.双重校验(双检锁) 双重校验能够保证单例模式的实例唯一性,同时能够兼顾效率和线程安全。 ### 4.静态内部类 静态内部类的方式实现,可以保证多线程对象的唯一性,同时可以降低双重校验中同步锁机制的代价。 这种方式还有个好处,就是静态内部类不会在单例类加载的时候就加载,而是在调用`getInstance()` 方法时才加载。 ### 5.枚举方法 by Josh Bloch (《Effective Java》作者) 好处: (1)自由序列化 (2)保证只有一个实例 (3)线程安全 ================================================ FILE: practice/Final.md ================================================ # 总结 时间过得很快,不知不觉间,一切就结束了。 无论课程多么精彩,似乎精彩总是他们的,我什么都没有。无论如何,感谢各位工作人员的辛勤付出。 很多课程都没来得及消化和理解,后续仍然需要进一步的学习、总结、实践。 复习总结列表: 1. [《第一课 数组、链表、栈、队列 * 》](https://shimo.im/docs/RCdRgwWjVWJppyJj/ ) 2. [《第二课 前缀和、差分、双指针、单调栈、单调队列》](https://shimo.im/docs/HvwKPhKHk9dcH9Q6/ ) 3. To be continued ... ================================================ FILE: practice/README.md ================================================ # Practice ================================================ FILE: practice/src/io/gkd/ListNode.java ================================================ package io.gkd; public class ListNode { public int val; public ListNode next; public ListNode() { } public ListNode(int val) { this.val = val; } public ListNode(int val, ListNode next) { this.val = val; this.next = next; } } ================================================ FILE: practice/src/io/gkd/Node.java ================================================ package io.gkd; import java.util.List; public 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; } } ================================================ FILE: practice/src/io/gkd/TreeNode.java ================================================ package io.gkd; public class TreeNode { public int val; public TreeNode left; public TreeNode right; public TreeNode() {} public TreeNode(int val) { this.val = val; } public TreeNode(int val, TreeNode left, TreeNode right) { this.val = val; this.left = left; this.right = right; } } ================================================ FILE: practice/src/io/gkd/lectures/lecture04/Lc077_Combine.java ================================================ package io.gkd.lectures.lecture04; import java.util.ArrayList; import java.util.List; public class Lc077_Combine { public List> combine(int n, int k) { this.n = n; this.k = k; findSubsets(1); return ans; } private void findSubsets(int index) { // 剪枝:选的数大于 k 个,或者剩下的全部选了也不够 k 个,就可以直接退出了 if (subset.size() > k || subset.size() + n - index + 1 < k) return; if (index == n + 1) { ans.add(new ArrayList<>(subset)); // make a copy return; } findSubsets(index + 1); subset.add(index); findSubsets(index + 1); subset.remove(subset.size() - 1); } private List> ans = new ArrayList<>(); private List subset = new ArrayList<>(); private int n; private int k; } ================================================ FILE: practice/src/io/gkd/lectures/lecture04/Lc078_SubSets.java ================================================ package io.gkd.lectures.lecture04; import java.util.ArrayList; import java.util.List; /** * Medium * 78. https://leetcode-cn.com/problems/subsets/submissions/ */ public class Lc078_SubSets { public List> subsets(int[] nums) { findSubsets(nums, 0); return ans; } // 递归去枚举 nums[0], nums[1], nums[2], ..., nums[n-1] 这 n 个数选或者不选 private void findSubsets(int[] nums, int index) { if (index == nums.length) { ans.add(new ArrayList<>(subset)); // make a copy return; } // 不选 index 位置的数 findSubsets(nums, index + 1); subset.add(nums[index]); // 选 index 位置的数 findSubsets(nums, index + 1); // 恢复现场,防止一次递归结束,subset 被上一次递归污染 subset.remove(subset.size() - 1); } private List> ans = new ArrayList<>(); private List subset = new ArrayList<>(); } ================================================ FILE: practice/src/io/gkd/lectures/lecture05/Lc094_InOrderTraversal.java ================================================ package io.gkd.lectures.lecture05; import io.gkd.TreeNode; import java.util.ArrayList; import java.util.List; public class Lc094_InOrderTraversal { private List list = new ArrayList<>(); public List inorderTraversal(TreeNode root) { find(root); return list; } public void find(TreeNode root) { if (root == null) return; find(root.left); list.add(root.val); find(root.right); } } ================================================ FILE: practice/src/io/gkd/lectures/lecture05/Lc105_BuildTree.java ================================================ package io.gkd.lectures.lecture05; import io.gkd.TreeNode; /** * Medium * 105. https://leetcode-cn.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal/ * * preorder [3 | 9 | 20 15 7] 找到了 root, 但是不确定左右子树的大小 [l1, r1] * inorder [9 | 3 | 15 20 7] 知道 root,自然知道了左右子树的大小 [l2, r2]. 左子树大小 = mid - l2 * l2 mid r2 * * 3 * / \ * [9] [20 15 7] preorer * [9] [15 20 7] inorer * left right */ public class Lc105_BuildTree { public TreeNode buildTree(int[] preorder, int[] inorder) { return build(preorder, 0, preorder.length - 1, inorder, 0, inorder.length - 1); } private TreeNode build(int[] preorder, int l1, int r1, int[] inorder, int l2, int r2) { if (l1 > r1) return null; TreeNode root = new TreeNode(preorder[l1]); // 通过找到 root 在 inorder 中的位置,确定左右子树的大小 int mid = l2; while (inorder[mid] != root.val) mid++; int leftSize = mid - l2; root.left = build(preorder, l1 + 1, l1 + leftSize, inorder, l2, mid - 1); root.right = build(preorder, l1 + leftSize + 1, r1, inorder, mid + 1, r2); return root; } } ================================================ FILE: practice/src/io/gkd/lectures/lecture05/Lc236_LCA.java ================================================ package io.gkd.lectures.lecture05; import io.gkd.TreeNode; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; /** * Medium * 236.https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-tree/ * * 向上标记法 */ public class Lc236_LCA { // 存储每个节点的 father private Map father; // Time: O(n) public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { this.father = new HashMap<>(); // TreeNode.val -> root TreeNode calcFather(root); // 存储其中一个节点的所有祖先 Set redNodes = new HashSet<>(); redNodes.add(root); // 记录从 p 向上的所有祖先,标记为红色 while(p != root) { redNodes.add(p); p = father.get(p.val); } // 在 p 的祖先中找到距离 q 最近的祖先,即 q 向上走,走到距离最近的一个红点 while (!redNodes.contains(q)) { q = father.get(q.val); } return q; } // 深度优先遍历记录每个节点的父节点 private void calcFather(TreeNode root) { if (root == null) return; if (root.left != null) { father.put(root.left.val, root); calcFather(root.left); } if (root.right != null) { father.put(root.right.val, root); calcFather(root.right); } } } ================================================ FILE: practice/src/io/gkd/lectures/lecture05/Lc297_Codec.java ================================================ package io.gkd.lectures.lecture05; import io.gkd.TreeNode; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * Hard * 297. https://leetcode-cn.com/problems/serialize-and-deserialize-binary-tree/ */ public class Lc297_Codec { // deserialized string list private List seq = new ArrayList<>(); // deserialized string list pointer private int curr; // Encodes a tree to a single string. // 1 2 null null 3 4 null null 5 null null public String serialize(TreeNode root) { traverse(root); return String.join(" ", seq); } // Decodes your encoded data to tree. public TreeNode deserialize(String data) { seq = Arrays.asList(data.split(" ")); curr = 0; return calc(); } private TreeNode calc() { if (seq.get(curr).equals("null")) { curr++; return null; } TreeNode root = new TreeNode(Integer.parseInt(seq.get(curr))); curr++; root.left = calc(); root.right = calc(); return root; } private void traverse(TreeNode root) { // for judge leaf node if (root == null) { seq.add("null"); return; } seq.add(Integer.toString(root.val)); traverse(root.left); traverse(root.right); } } ================================================ FILE: practice/src/io/gkd/lectures/lecture05/Lc429_LevelOrderNTree.java ================================================ package io.gkd.lectures.lecture05; import io.gkd.Node; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import java.util.Queue; /** * Medium * 429. https://leetcode-cn.com/problems/n-ary-tree-level-order-traversal/ */ public class Lc429_LevelOrderNTree { public List> levelOrder(Node root) { // result: [[1], [3, 2, 4], [5, 6]] List> result = new ArrayList<>(); if (root == null) return result; // Queue: Node - depth Queue> q = new LinkedList<>(); // Put root in Queue q.add(new Pair(root, 0)); while (!q.isEmpty()) { Pair pair = q.poll(); Node node = pair.getKey(); int depth = pair.getValue(); // initialize a list in every depth(0...) if (result.size() <= depth) { result.add(new ArrayList()); } result.get(depth).add(node.val); for (Node child : node.children) { q.add(new Pair<>(child, depth + 1)); } } return result; } static class Pair { public K key; public V value; public Pair(K key, V value) { this.key = key; this.value = value; } public K getKey() { return key; } public V getValue() { return value; } } } ================================================ FILE: practice/src/io/gkd/lectures/lecture05/Lc589_PreOrderNTree.java ================================================ package io.gkd.lectures.lecture05; import io.gkd.Node; import java.util.ArrayList; import java.util.List; import java.util.Stack; /** * Easy * 589. https://leetcode-cn.com/problems/n-ary-tree-preorder-traversal/ */ public class Lc589_PreOrderNTree { private List list = new ArrayList<>(); public List preorder(Node root) { find(root); return list; } private void find(Node root) { if (root == null) return; list.add(root.val); for (Node node : root.children) { find(node); } } public List preorderIter(Node root) { List list = new ArrayList<>(); if (root == null) return list; Stack stack = new Stack<>(); stack.push(root); while(!stack.isEmpty()) { Node r = stack.pop(); list.add(r.val); List children = r.children; for (int i = children.size() - 1; i >= 0; i--) { stack.push(children.get(i)); } } return list; } } ================================================ FILE: practice/src/io/gkd/week01/Lc021_MergeTwoLists.java ================================================ package io.gkd.week01; import io.gkd.ListNode; /** * Easy * 21. https://leetcode-cn.com/problems/merge-two-sorted-lists/ */ public class Lc021_MergeTwoLists { public ListNode mergeTwoLists(ListNode l1, ListNode l2) { ListNode sentinel = new ListNode(); ListNode cur = sentinel; while (l1 != null && l2 != null) { if (l1.val <= l2.val) { cur.next = l1; l1 = l1.next; } else { cur.next = l2; l2 = l2.next; } cur = cur.next; } if (l1 != null) { cur.next = l1; } else { cur.next = l2; } return sentinel.next; } } ================================================ FILE: practice/src/io/gkd/week01/Lc066_PlusOne.java ================================================ package io.gkd.week01; /** * Easy * 66. https://leetcode-cn.com/problems/plus-one/submissions/ */ public class Lc066_PlusOne { public int[] plusOne(int[] digits) { for (int i = digits.length - 1; i >= 0; i--) { digits[i] = (digits[i] + 1) % 10; if (digits[i] != 0) { return digits; } } int[] res = new int[digits.length + 1]; res[0] = 1; return res; } } ================================================ FILE: practice/src/io/gkd/week02/Lc146_LRUCache.java ================================================ package io.gkd.week02; import java.util.HashMap; /** * Medium * 146. https://leetcode-cn.com/problems/lru-cache/ * Time: put、get: O(1) * Space: O(capacity) */ public class Lc146_LRUCache { // key - Node HashMap cache; int capacity; // sentinel head Node head; // sentinel tail Node tail; public Lc146_LRUCache(int capacity) { this.capacity = capacity; this.cache = new HashMap<>(); this.head = new Node(); this.tail = new Node(); // Doubly Linked List: head - tail this.head.next = this.tail; this.tail.pre = this.head; } public int get(int key) { Node node = this.cache.get(key); if (node == null) { return -1; } removeNode(node); addToHead(node); return node.val; } // head -> latest -> ... -> oldest -> tail // head -> 1 -> 2 -> tail public void put(int key, int value) { if (cache.containsKey(key)) { removeNode(cache.get(key)); } Node node = new Node(key, value); addToHead(node); this.cache.put(key, node); if (this.cache.size() > capacity) { Node oldestNode = tail.pre; removeNode(oldestNode); this.cache.remove(oldestNode.key); } } // O(1) private void addToHead(Node node) { // node - head.next node.next = this.head.next; this.head.next.pre = node; // head - node node.pre = this.head; this.head.next = node; } // O(1) private void removeNode(Node node) { node.pre.next = node.next; node.next.pre = node.pre; } static class Node { public int key; public int val; public Node pre; public Node next; public Node() {} public Node(int key, int val) { this.key = key; this.val = val; } } } ================================================ FILE: practice/src/io/gkd/week02/Lc697_FindShortestSubArray.java ================================================ package io.gkd.week02; import java.util.HashMap; import java.util.Map; /** * Easy * 697. https://leetcode-cn.com/problems/degree-of-an-array/ */ public class Lc697_FindShortestSubArray { public int findShortestSubArray(int[] nums) { // map: // key - element // value - value[] // - value[0]: element count; // - value[1]: first element index; // - value[2]: last element index HashMap map = new HashMap<>(); int degree = 0; for (int i = 0; i < nums.length; i++) { int element = nums[i]; if (map.containsKey(element)) { int[] value = map.get(element); value[0]++; value[2] = i; degree = Math.max(degree, value[0]); } else { map.put(element, new int[] {1, i, i}); degree = Math.max(degree, 1); } } // System.out.println(degree); int shortestSubArrayLen = 50000; for (Map.Entry entry : map.entrySet()) { int[] value = entry.getValue(); if (degree == value[0]) { shortestSubArrayLen = Math.min(shortestSubArrayLen, value[2] - value[1] + 1); } } return shortestSubArrayLen; } } ================================================ FILE: practice/src/io/gkd/week03/Lc106_BuildTree.java ================================================ package io.gkd.week03; import io.gkd.TreeNode; /** * Medium * 106. https://leetcode-cn.com/problems/construct-binary-tree-from-inorder-and-postorder-traversal/ * * postorder [9 | 15 7 20 | 3] * inorder [9 | 3 | 15 20 7] * l1 mid r1 * 3 * / \ * [9] [15 7 20] * [9] [15 20 7] */ public class Lc106_BuildTree { public TreeNode buildTree(int[] inorder, int[] postorder) { return build(inorder, 0, inorder.length, postorder, 0, postorder.length - 1); } private TreeNode build(int[] inorder, int l1, int r1, int[] postorder, int l2, int r2) { if (l2 > r2) return null; TreeNode root = new TreeNode(postorder[r2]); int mid = l1; while (inorder[mid] != root.val) mid++; int leftSize = mid - l1; root.left = build(inorder, l1, mid - 1, postorder, l2, l2 + leftSize - 1); root.right = build(inorder, mid + 1, r1, postorder, l2 + leftSize, r2 - 1); return root; } } ================================================ FILE: practice/src/io/gkd/week03/Lc210_FindOrder2.java ================================================ package io.gkd.week03; import java.util.LinkedList; import java.util.Queue; public class Lc210_FindOrder2 { public int[] findOrder(int numCourses, int[][] prerequisites) { if (numCourses == 0) return new int[0]; int[] inDegrees = new int[numCourses]; // 建立入度表 // 对于有先修课的课程,计算有几门先修课 for (int[] p : prerequisites) { inDegrees[p[0]]++; } // 入度为0的节点队列 Queue queue = new LinkedList<>(); for (int i = 0; i < inDegrees.length; i++) { if (inDegrees[i] == 0) queue.offer(i); } int count = 0; // 记录可以学完的课程数量 int[] res = new int[numCourses]; // 可以学完的课程 // 根据提供的先修课列表,删除入度为 0 的节点 while (!queue.isEmpty()){ int curr = queue.poll(); res[count++] = curr; // 将可以学完的课程加入结果当中 for (int[] p : prerequisites) { if (p[1] == curr){ inDegrees[p[0]]--; if (inDegrees[p[0]] == 0) queue.offer(p[0]); } } } if (count == numCourses) return res; return new int[0]; } } ================================================ FILE: practice/src/io/gkd/week03/NOTES.md ================================================ # 本周总结 本周主要理解了树的相关内容,图的内容还在学习中。 ## 树 * 树的定义 * 二叉树 * 完全二叉树 * 满二叉树 * 二(多)叉树的遍历 * 基环树 ## 图 * 链表、树、基环树、图的关系 * 图的表示 * 图的遍历 ================================================ FILE: questions/questions.md ================================================ # Questions ## 数组 ### 1. 数组与泛型动态数组 [[Solution1](https://github.com/guokaide/algorithm/blob/master/summary/algorithm.md)][[Solution2](https://github.com/guokaide/algorithm/blob/master/algorithms/src/array/GenericArray.java)]请谈谈数组的特点,并且实现泛型动态数组(即Vector in C++ or ArrayList in Java)的插入、删除以及查找操作,比较原生数组与泛型动态数组的区别。查看泛型动态数组API: ```java package array; /** * 泛型动态数组 * * @param */ public class GenericArray { private T[] data; private int size; // 根据传入容量,构造Array public GenericArray(int capacity) { data = (T[]) new Object[capacity]; size = 0; } // 无参构造方法,默认数组容量为10 public GenericArray() { this(10); } // 获取数组容量 public int getCapacity() { } // 获取当前元素个数 public int count() { } // 判断数组是否为空 public boolean isEmpty() { } // 修改 index 位置的元素 public void set(int index, T e) { } // 获取对应 index 位置的元素 public T get(int index) { } // 查看数组是否包含元素e public boolean contains(T e) { } // 获取对应元素的下标, 未找到,返回 -1 public int find(T e) { } // 在 index 位置,插入元素e, 时间复杂度 O(m+n) public void add(int index, T e) { } // 向数组头插入元素 public void addFirst(T e) { } // 向数组尾插入元素 public void addLast(T e) { } // 删除 index 位置的元素,并返回 public T remove(int index) { } // 删除第一个元素 public T removeFirst() { } // 删除末尾元素 public T removeLast() { } // 从数组中删除指定元素 public void removeElement(T e) { } @Override public String toString() { StringBuilder builder = new StringBuilder(); builder.append(String.format("Array size = %d, capacity = %d \n", size, data.length)); builder.append('['); for (int i = 0; i < size; i++) { builder.append(data[i]); if (i != size - 1) { builder.append(", "); } } builder.append(']'); return builder.toString(); } // 扩容方法,时间复杂度 O(n) private void resize(int capacity) { } private void checkIndex(int index) { if (index < 0 || index > size) { throw new IllegalArgumentException("Add failed! Require index >=0 and index <= size."); } } private void checkIndexForRemove(int index) { if (index < 0 || index >= size) { throw new IllegalArgumentException("Remove failed! Require index >= 0 and index < size."); } } } ``` ### 2. 1000万整数中查找某个数 [[Solution](https://github.com/guokaide/algorithm/blob/master/summary/algorithm.md#%E9%97%AE%E9%A2%98)]假设我们有 1000 万个整数数据,每个数据占 8 个字节,如何设计数据结构和算法,快速判断某个整数是否出现在这 1000 万数据中? 我们希望这个功能不要占用太多的内存空间,最多不要超过 100MB,你会怎么做呢? ### 3. 约瑟夫问题 [[Solution](https://github.com/guokaide/algorithm/blob/master/algorithms/src/joseph/Joseph.java)]0,1,...,n-1这n个数字排成一个圆圈,从数字0开始,每次从这个圆圈里删除第m个数字。求这个圆圈里剩下的最后一个数字。 ## 链表 ### 1. 链表与数组 [[Solution1](https://github.com/guokaide/algorithm/blob/master/summary/algorithm.md)][[Solution2](https://github.com/guokaide/algorithm/blob/master/algorithms/src/linkedlist/SingleLinkedList.java)]请谈谈链表的特点,比较数组与链表的不同,并实现单链表的基本操作:插入、删除以及查找操作。查看单链表API: ```java package linkedlist; /** * 1)单链表的插入、删除、查找操作; * 2)链表中存储的是int类型的数据; * */ public class SingleLinkedList { private Node head = null; public Node findByValue(int value) { } public Node findByIndex(int index) { } public void insertToHead(int value) { } public void insertToHead(Node newNode) { } public void insertToTail(int value) { } public void insertToTail(Node newNode) { } public void insertAfter(Node p, int value) { } public void insertAfter(Node p, Node newNode) { } public void insertBefore(Node p, int value) { } public void insertBefore(Node p, Node newNode) { } public void deleteByNode(Node p) { } public void deleteByValue(int value) { } public void printAll() { } public static Node createNode(int value) { return new Node(value, null); } public static class Node { private int data; private Node next; public Node(int data, Node next) { this.data = data; this.next = next; } public int getData() { return data; } } } ``` ### 2. Reverse Linked List [[Solution](https://github.com/guokaide/algorithm/tree/master/leetcode/src/reverselinkedlist_206)][206.Reverse Linked List](https://leetcode.com/problems/reverse-linked-list/description/) ### 3. Middle of the Linked List [[Solution](https://github.com/guokaide/algorithm/blob/master/algorithms/src/linkedlist/FindMidNode.java)]给定一个单链表,请找到其中间节点。例如:1->2->3, 中间节点为2; 1->2->3->4, 中间节点为3。 或者 leetcode版本:[876.Middle of the Linked List ](https://leetcode.com/problems/middle-of-the-linked-list/ ) ### 4. LRU Cache [[Solution](https://github.com/guokaide/algorithm/blob/master/algorithms/src/lru/LRU.java)][146.LRU Cache](https://leetcode.com/problems/lru-cache/description/) ### 5. Palindrome Linked List [[Solution](https://github.com/guokaide/algorithm/blob/master/leetcode/src/palindromelinkedlist_234/PalindromeLinkedList.java)]如果一个字符串是通过单链表来存储的,该如何来判断是一个回文串呢?其时间复杂度和空间复杂度是多少?或者leetcode版本:[234.Palindrome Linked List ](https://leetcode.com/problems/palindrome-linked-list/ ) ### 6. Linked List Cycle [[Solution](https://github.com/guokaide/algorithm/blob/master/leetcode/src/linkedlistcycle_141/LinkedListCycle.java)][141. Linked List Cycle](https://leetcode.com/problems/linked-list-cycle/ ) ### 7. Merge Two Sorted Lists [[Solution](https://github.com/guokaide/algorithm/blob/master/leetcode/src/mergetwosortedlist_21/Merge2SortedLists.java)][21. Merge Two Sorted Lists](https://leetcode.com/problems/merge-two-sorted-lists/ ) ### 8. Remove Nth Node From End of List [[Solution](https://github.com/guokaide/algorithm/blob/master/leetcode/src/removenthnodefromendoflist_19/RemoveNthNodeFromEndOfList.java)][19. Remove Nth Node From End of List ](https://leetcode.com/problems/remove-nth-node-from-end-of-list/ ) ## 栈 ### 1. Implement Stack using Array [[Solution](https://github.com/guokaide/algorithm/blob/master/algorithms/src/stack/ArrayStack.java)] 实现顺序栈。**顺序栈**:用数组实现的栈。栈的API如下: ```java public class ArrayStack { public boolean push(int item) { } public int pop() { } } ``` **扩展问题:** [[Solution](https://github.com/guokaide/algorithm/blob/master/algorithms/src/stack/DynamicArrayStack.java)]你是否能够实现支持动态扩容的顺序栈?是否能够分析其操作的时间复杂度?(动态扩容的实现方法见问题【数组-1.数组与泛型动态数组】) ### 2. Implement Stack using Linked List [[Solution](https://github.com/guokaide/algorithm/blob/master/algorithms/src/stack/ListStack.java)] 实现链式栈。**链式栈**:用链表实现的栈。栈的API如下: ```java public class ListStack { public boolean push(int item) { } public int pop() { } } ``` ### 3. Implement Stack using Queues [Solution][225. Implement Stack using Queues](https://leetcode.com/problems/implement-stack-using-queues/) ### 4. Implement Queue using Stacks [Solution][232. Implement Queue using Stacks](https://leetcode.com/problems/implement-queue-using-stacks/) ### 5. Valid Parentheses [Solution][20. Valid Parentheses](https://leetcode.com/problems/valid-parentheses/) ### 6. Min Stack [Solution][155. Min Stack](https://leetcode.com/problems/min-stack/) ### 7. Implement the Forward and Backward Functions of the Browser [Solution] 浏览器的前进和后退功能:当你依次访问完一串页面 a-b-c 之后,点击浏览器的后退按钮,就可以查看之前浏览过的页面 b 和 a。当你后退到页面a,点击前进按钮,就可以重新查看页面 b 和 c。但是,如果你后退到页面 b 后,点击了新的页面 d,那就无法再通过前进、后退功能查看页面 c 了。请实现这个功能。 ## 队列 ### 1. Implement Queue using Array [[Solution](https://github.com/guokaide/algorithm/blob/master/algorithms/src/queue/ArrayQueue.java)] 实现顺序队列。**顺序队列**:用数组实现的队列。API如下: ```java public class ArrayQueue { public boolean enqueue(int Item) { } public int dequeue() { } } ``` ### 2. Implement Queue using Linked List [[Solution](https://github.com/guokaide/algorithm/blob/master/algorithms/src/queue/ListQueue.java)] 实现链式队列。**链式队列**:用链表实现的队列。API如下: ```java public class ListQueue { public boolean enqueue(int Item) { } public int dequeue() { } } ``` ### 3. Design Circular Queue [Solution][622. Design Circular Queue](https://leetcode.com/problems/design-circular-queue/) ### 4. Design Circular Deque [Solution][641. Design Circular Deque](https://leetcode.com/problems/design-circular-deque/) ## 二分查找算法 ### 1. 二分查找算法 [[Solution](https://github.com/guokaide/algorithm/blob/master/algorithms/src/array/BinarySearch.java)] 请实现正确的二分查找算法(递归与非递归),并分析其时间复杂度(O(nlogn))。 ### 2. 二分查找算法变形问题 [[Solution](https://github.com/guokaide/algorithm/blob/master/algorithms/src/array/BinarySearch.java)] 给定一个有序数组, - 查找第一个值等于给定值的元素 - 查找最后一个值等于给定值的元素 - 查找第一个大于等于给定值的元素 - 查找最后一个小于等于给定值的元素 ### 3. 旋转数组中的最小值 [[Solution](https://github.com/guokaide/algorithm/blob/master/algorithms/src/array/MinNumberInRotatedArray.java)] 数组的旋转:将一个数组最开始的若干个元素搬到数组的末尾,即为数组的旋转。输入一个递增排序的数组的一个旋转,数组旋转数组的最小值。例如,数组{3,4,5,1,2}是数组{1,2,3,4,5}的一个旋转,该数组的最小值为1。要求时间复杂度为O(logn)。 ### 4. Sqrt(x) [Solution][69. Sqrt(x)](https://leetcode.com/problems/sqrtx/description/) ## 递归 ### 1. Pow(x, n) [Solution][50. Pow(x,n)](https://leetcode.com/problems/powx-n/description/) ================================================ FILE: solutions/剑指offer 题解.md ================================================ # 剑指offer 题解 ## 2. 实现单例模式 ### 什么是单例模式? **单例模式**,顾名思义就是整个系统只能有1个实例存在,不能再多了。 ### 单例模式的好处 单例模式适用于应用中频繁创建的对象,尤其是频繁创建的重量级对象。 例如统一的配置文件。 使用单例模式能够提升性能,减小内存开销。 ### 单例模式的实现 #### 0. 概述 如何实现一个单例模式呢?我们讨论一下这个过程。 首先我们搞一个类出来: ```java public class Singleton { } ``` 空空如也,非常好,但是有个问题,大家可以在外面随便`new Singleton()`,那系统里就不止一个Singleton实例了,因此需要我们阻挡`new Singleton()`。那就不写构造方法呗?当然不行。因为如果不写构造方法,系统会默认分配无参构造器。那就直接将构造方法改为`private`呗。 ```java public class Singleton { private Singleton() {} } ``` 好,既然外面造不出Singleton实例了,那就自己造呗。 ```java public class Singleton { private static final Singleton instance = new Singleton(); private Singleton() {} } ``` `private`关键字保证了Singleton实例的私有性,不可见性,不可访问性;`static`关键字保证了Singleton实例的静态性,与类同在,不依赖于类的实例化就在内存中永生;`final`关键字保证这个实例是常量,不可修改。 那么既然外部无法创造这个实例,如何获取这个实例呢?整个`getInstance()`静态方法在外部获取这个实例,而且这个方法必须是`public`的。 ```java public class Singleton { private static final Singleton instance = new Singleton(); private Singleton() {} public static Singleton getInstance() { return instance; } } ``` 到此,外部就可以通过`Singleton.getInstance()`获取这个单例。 这就是一个简单的**单例模式**的雏形(这其实是一个饿汉式单例模式)。以下介绍几种面试中常见的单例模式实现。 #### 1.饿汉式 饿汉式加载类的时候,就会创建类的实例对象,那么如果不用这个单例的话,就会消耗内存,浪费性能。 因此,需要懒加载(lazy-load),即延迟加载。 ```java public class Singleton { // instance必须是static变量(类变量) private static Singleton instance = new Singleton(); // private无参构造函数: Java会自动为没有明确声明构造函数的类, // 定义一个public的无参构造函数,若没有这个private无参构造函数, // 外部可以随意new Singleton private Singleton() {} // getInstance必须是static方法(类方法) public static Singleton getInstance() { return instance; } } ``` #### 2.懒汉式 懒汉式改进了饿汉式的性能问题,但是又带来了一个问题,即线程安全问题。 在多线程的情况下,会出现多个实例,违背了单例模式的原则。 ```java public class Singleton { private static Singleton instance = null; private Singleton() {} public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } } ``` #### 3.双重校验(双检锁) 双重校验能够保证单例模式的实例唯一性,同时能够兼顾效率和线程安全。 ```java public class Singleton { // volatile提供可见性(工作内存的值能立即在主内存中可见), // 保证getInstance返回的是初始化完全的对象 private static volatile Singleton instance = null; private Singleton() {} public static Singleton getInstance() { // 同步之前进行null检查,避免进入代价相对昂贵的同步块 if (instance == null) { synchronized (Singleton.class) { // 持有锁之后进行null检查,避免上一个线程已经初始化变量 if (instance == null) { instance = new Singleton(); } } } return instance; } } ``` #### 4.静态内部类 静态内部类的方式实现,可以保证多线程对象的唯一性,同时可以降低双重校验中同步锁机制的代价。 这种方式还有个好处,就是静态内部类不会在单例类加载的时候就加载,而是在调用`getInstance()` 方法时才加载。 ```java public class Singleton { private static class SingletonHolder { public static Singleton instance = new Singleton(); } public static Singleton getInstance() { return SingletonHolder.instance; } } ``` #### 5.枚举方法 枚举方法是由《Effective Java》的作者Josh Bloch提出,其好处在于: * 自由序列化 * 保证只有一个实例 * 线程安全 ```java public enum Singleton { INSTANCE; private int id; public int getId() { return id; } public void setId(int id) { this.id = id; } public static void main(String[] args) { //调用 Singleton.INSTANCE.setId(1); Singleton.INSTANCE.getId(); } } ``` ## 3. 数组中重复的数字 ### 3.1 找出数组中重复的数字 在一个长度为n的数组里的所有的数字都在0~n-1的范围内。数组中某些数字是重复的,但不知道有几个数字重复,也不知道每个数字重复了几次。请找出数组中任意的一个数字。 例如,输入长度为7的数组{2,3,1,0,2,5,3},那么对应输出的是重复数字2或者3。 [nowcoder](https://www.nowcoder.com/practice/623a5ac0ea5b4e5f95552655361ae0a8?tpId=13&tqId=11203&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) **Solution** 1.遍历数组,同时使用HashMap记录数组中每个元素出现的个数,当某个元素个数大于1时,输出该元素即可。 时间复杂度O(n),空间复杂度O(n)。 实现如下: ```java public boolean duplicate(int numbers[], int [] duplication) { if (numbers == null || numbers.length < 2) { return false; } HashMap map = new HashMap<>(); for (int i = 0; i < numbers.length; i++) { if (!map.containsKey(numbers[i])) { map.put(numbers[i], 1); } else { duplication[0] = numbers[i]; return true; } } return false; } ``` 2.方法1并未使用题目中数组(numbers)元素都在0~n-1这个条件,如果使用这个条件的话,会发现若数组中没有重复元素的话,那么长度为n的数组,排序之后,应该是{0,1,2,...,n-1},即k位置的元素为k。若有重复元素的话,那么k位置元素为k,其他位置也可能有k。利用这个特征遍历数组,若k位置的值为k,则遍历下一个元素;若k位置的元素的值不是k,则将k位置元素与numbers[k]位置元素交换,直到k位置的元素为k,然后遍历下一个元素,若在交换的过程中,发现k位置的元素等于numbers[k]位置的值,则k就是所求重复的值,结束遍历。 时间复杂度O(n),空间复杂度O(1)。 实现如下: ```java public boolean duplicate(int numbers[], int [] duplication) { if (numbers == null || numbers.length < 2) { return false; } for (int i = 0; i < numbers.length; i++) { if (numbers[i] == i) { i++; } else { int m = numbers[i]; if (numbers[m] == m) { duplication[0] = m; return true; } else { int tmp = numbers[i]; numbers[i] = numbers[m]; numbers[m] = tmp; } } } return false; } ``` ### 3.2 不修改数组找出重复的数字 在一个长度为n+1的数组里的所有的数字都在1~n的范围内,所以数组中至少有一个数字是重复的,请在不修改数组的情形下,找出数组中任意一个重复数字。 例如,输入长度为8的数组{2,3,5,4,3,2,6,7},那么对应输出的是重复数字2或者3。 ================================================ FILE: summary/algorithm.md ================================================ # Algorithm ## 数组 ### 数组的基本概念 **数组(Array)**:数组是一种**线性表**数据结构。它用一组**连续的内存空间**,来存储一组具有**相同数据类型**的数据。 * 线性表与非线性表 * 线性表(Linear List):数据排成像一条线一样的结构。每个线性表上的数据最多只有前和后2个方向。如:数组、链表、队列、栈等。 * 非线性表:数据之间不是简单的前后关系。如:二叉树、堆、图等。 * 连续的内存空间和相同类型的数据 * 正是因为这2个限制,数组具有一个杀手级的特性:**随机访问**。即其根据下标随机访问的时间复杂度为O(1)。但是要**注意**数组中查找一个元素的时间复杂度不是O(1)。 * 但是也正是因为数组存储需要连续的内存空间,因此其插入和删除操作非常低效,因为为了保证数组数据的连续性,需要做大量的数据搬移工作。 ### 数组的基本操作 * 插入: * 实现:将某个元素插入到数组第k个位置,需要将k~n部分的元素向后搬移一位,然后插入元素。若插入到数组的末尾,时间复杂度O(1);插入到数组的开头,时间复杂度O(n);插入的平均时间复杂度为(1+2+...+n)/n = O(n)。注意这个实现针对的数组是有序的。 * **优化**:若数组只是一个存储数据的集合,其元素是无序的,则插入时不需要搬移数据。此时若想将某个元素插入到数组第k个位置,首先将该位置的元素移动到数组末尾,然后将待插入元素插入到第k个位置,时间复杂度降为O(1)。但是要注意这个优化是针对数组是无序的,这个优化其实是放弃了数组逻辑上的连续。 * 删除 * 实现:若要删除第k个位置的元素,则需要将k+1~n个元素向前搬移一位。若删除数组的末尾,时间复杂度O(1);删除数组的开头,时间复杂度O(n);删除平均时间复杂度为 O(n)。 * **优化**:将多次删除一起执行。当我们要删除多个元素的时候,并不立即进行搬移操作,而是将多个元素标记为已删除,然后当数组满了的时候,将标记的元素一起删除,这样就减少了数组搬移的次数,提高了效率。这就是**JVM中标记-清除**算法的核心思想。 * 随机访问 * 实现:数组的随机访问,只需要根据首地址和下标通过寻址公式计算出对应内存地址即可。 ### 数组的内存模型 * 访问数组的本质:计算机为每一个数组分配了一段连续的内存,计算机通过访问内存的地址访问内存,因此访问数组的本质就是访问一段连续内存。对内存的要求较高。 * 既然访问数组其实是访问一段连续内存,那么计算机就可以根据数组的首地址和下标,通过公式计算出每一个元素所在的位置。数组访问寻址公式: * 给定一维数组`a[n]`, 则`a[k]`的内存地址为:`a[k]_address = base_address + k * type_size`。 * 给定二维数组`a[m][n]`则`a[i][j]`的内存地址为:`a[i][j]_address = base_address + (i*n + j) * type_size `。 ## 链表 ### 链表的基本概念 **链表(Linked List)**:链表是一种**线性表**数据结构。它用一组**不连续的内存空间**,来存储一组具有**相同数据类型**的数据。 ### 链表的基本操作 - 插入:链表的插入只需要调整前后节点的指针即可,时间复杂度为O(1) - 删除:链表的删除只需要调整前后节点的指针即可,时间复杂度为O(1) - 随机访问:链表的随机访问,无法像数组一样通过寻址公式计算出下标的内存位置,只能通过遍历找到相应的节点,时间复杂度为O(n)。 ### 链表的分类 #### 1. 单链表 结构:head -> data -> data -> null 特点:结构简单,但是随机访问某个节点时,只能从头到尾遍历,找到这个节点。 #### 2. 循环链表 结构:head -> data -> data -> head 特点:与单链表相比,其优势在于可以从尾到头遍历。 #### 3. 双向链表 结构:head <-> data <-> data -> null 特点:双向链表最大的优势在于找到前驱结点的时间复杂度为O(1),正因为这个特点,使得双向链表在某些情况下,插入、删除等操作要比单链表简单高效,尽管时间复杂度都是O(1)。这也是为什么双向链表占用的内存要高于单链表,但是日常使用中,双向链表更加常用的原因。**Java中LinkedHashMap就用双向链表来记录插入的键值对的顺序。** #### 4. 双向循环链表 双向循环链表是双向链表与循环链表的组合。 结构:head <-> data <-> data -> head #### 问题:为什么双向链表比单链表高效? **插入和删除** (1)删除操作 在实际开发中,从链表删除一个数据主要是以下2种情况: * 删除某个“值等于给定值”的节点 * 删除给定指针指向的节点 事实上,删除操作本身的时间复杂度为O(1),单链表和双向链表的性能差别,主要在于查找的过程。 对于第一种情况,无论单链表还是双向链表,无论是插入还是删除,都需要从头到尾遍历找到“值等于给定值”的节点,因此二者的时间复杂度均为O(n)。 对于第二种情况,已经找到了要删除的节点(记为q),但是要删除这个节点需要知道其前驱节点(记为p)。单链表中,无法立即知道q的前驱节点p,因此只能从头遍历,直到`p->next = q`,这个过程的时间复杂度是O(n); 而双向链表要删除的节点q已经保存了前驱节点p的指针。因此可以O(1)的时间找到p。因此单链表删除操作时间复杂度为O(n),而双向链表为O(1)。 (2)插入操作 分析同上。 **查找** 对于一个有序链表,双向链表按值查找的效率要高于单链表。因为,我们可以记录上一次查找的位置p,每次查找时,比较要查找的值与p的关系,若小于p,则向前,反之则向后,平均只需要查找一半的数据。 因此,双向链表在删除和插入给定节点以及在有序链表中查找值这几种情形下,效率要高于单链表。 事实上,这里的双向链表的性能来自于其对空间的牺牲,它的空间占用要高于单链表,体现了 **“空间换时间”** 的设计思想。 #### ### 数组 VS 链表 **1. 内存** 从内存来讲,数组要求内存是一块连续的内存空间,而链表则不需要内存空间连续,例如,如果申请一个100MB大小的数组,当内存中没有连续的大于等于100MB的内存空间时,即使内存的总空间大于100MB,内存也会申请失败;而链表则不需要连续的内存空间,链表通过指针将不同的内存块串联起来,如果内存的总空间大于100MB,那么申请100MB的链表则没有任何问题。 尽管数组需要连续的内存空间,对内存的要求较高,但是正因为这个特性,数组可以借助CPU缓存机制,预读数组中的数据,因此访问效率更高。而链表由于内存不连续,因此对CPU缓存不友好,无法预读。 若代码对内存使用比较苛刻,那么使用数组会比较好。因为链表的每个节点都需要使用额外的内存存储指向下一个节点的指针,额外内存高于数组。 链表在进行插入和删除操作的时候,导致内存频繁的申请和释放,容易造成内存碎片。在Java中,有可能到导致频繁的GC。 **2. 基本操作** | 时间复杂度 | 数组 | 链表 | | :--------: | :--: | :--: | | 插入、删除 | O(n) | O(n) | | 随机访问 | O(1) | O(n) | 根据二者的时间复杂度,在随机访问更加频繁的场景下(插入、删除操作非常少),数组更加适用;在插入、删除操作频繁的场景下,链表更加适用。 ## 栈 ### 栈的基本概念 **栈(stack)**: 栈是一种“操作受限”的**线性表**数据结构。栈元素具有后进先出,先进后出的特点。 ### 栈的存储 **顺序栈**:用数组实现的栈。 **链式栈**:用链表实现的栈。 ### 栈的基本操作 栈具有2个基本操作:栈的插入和删除,而且只能在一端进行插入和删除操作。 栈的插入和删除操作的时间复杂度为O(1)。 示例:一摞叠在一起的盘子能很好地说明栈。放盘子的时候,都是从下往下一个个放;取盘子的时候,则是从上往下一个个取,不能从中间任意抽出。只涉及到了线性表一端的插入和删除。 ### 栈的应用 **当某个数据集合只涉及在一端插入和删除数据,并且满足后进先出、先进后出的特性,那么就应该首选栈作为其数据结构。** 例如,函数调用就是依托栈实现的。 ## 队列 ### 队列的基本概念 **队列(Queue)**:队列是一种“操作受限”的线性表数据结构。队列元素具有先进先出,后进后出的特点。 ### 队列的存储 **顺序队列**:用数组实现的队列。 **链式队列**:用链表实现的队列。 ### 队列的基本操作 队列具有2个基本操作:入队和出队。 队列的入队和出队的时间复杂度均为O(1)。 示例:排队买票,先来的先买,后来的只能排到队尾,不能插队。 ### 队列的应用 **阻塞队列**是在队列的基础上增加了阻塞操作。当队列为空的时候,从队头取数据会被阻塞,直到队列中存在数据的时候,才返回数据;当队列满了的时候,插入数据操作会被阻塞,直到队列中有空闲位置时再插入数据然后再返回。我们可以使用阻塞队列实现**生产者-消费者模型**。 在多线程的情形下,就会存在安全问题,这就需要并发队列了。 **并发队列**是指线程安全的队列。并发队列最简单的实现就是在`enqueue()`和`dequeue()`方法上加锁,但是锁的粒度比较大,因此并发度不是很好,同一时刻仅仅允许一个操作。但是,基于数组的循环队列,利用CAS原子操作,可以实现非常高效的并发队列。这也是循环队列比链式队列应用更加广泛的原因。 如何实现无锁并发队列? 使用数组+CAS的方式可以实现。在入队前,获取tail的位置,入队时,比较tail是否发生变化,若没有发生变化,则允许入队,反之入队失败。出队操作分析同上。 **对于大部分资源有限的场景,当没有空闲资源时,基本上都可以通过队列实现请求排队。** 例如线程池,分布式应用中的消息队列(如kafka)等。 **问题**:当线程池没有空闲线程时,新的任务请求线程资源时,线程池该如何处理? 一般有2种策略: * 非阻塞的处理方式,直接拒绝任务请求 * 阻塞的处理方式,将请求排队,等到有空闲线程时,取出排队的请求继续处理。 那么如何存储排队的请求呢?一般希望公平的处理每个请求,因此按照先来的请求先处理,那么就适合用队列来存储请求。那么队列该如何实现呢?是基于数组还是基于链表呢? * 基于链表的实现方式,可以实现一个可以支持无限排队的**无界队列**(unbounded queue),但是可能导致多多的请求等待,请求的响应时间过长。因此,针对响应时间比较敏感的系统,基于链表实现的无界队列的线程池是不合适的。 * 基于数组的实现方式,可以实现一个**有界队列**(bounded queue),队列大小有限,因此当请求数量超过队列大小时,接下来的请求就只能拒绝处理。这种方式适用于响应时间比较敏感的系统。队列的大小的设计就至关重要了。太大会导致等待请求太多,太小则会造成无法充分利用系统资源,发挥最大性能。 ## 二分查找 ### 二分查找 * 二分查找:高效的有序数据集合查找算法 * 时间复杂度:O(logn) * O(logn)是一个快到很恐怖复杂度。例如:当n=2^32(约42亿)时,logn=32。即从42亿有序数据中二分查找一个数据,最多32次。相反,O(2^n)是一个慢的恐怖的时间复杂度。 * 应用场景: * 针对数组,原因是: * 数组:根据下标随机访问的时间复杂度O(1) * 链表:根据下标随机访问的时间复杂度O(n) * 针对有序数据 * 二分查找只能用在插入、删除操作不频繁的静态数据中,一次排序多次查找的场景中,因为排序最快O(nlogn) * 那么动态数据集合如何快速查找某个数据呢?答案是二叉树。 * 数据量太小不适合二分查找 * 数据量太小,直接顺序遍历即可,二分查找的优势并不明显 * 但是如果数据之间的比较操作比较耗时间,那么减少比较操作会大大提高性能,因此即使数据量小,但是二分查找可以减小比较次数,例如数组中存储的是长度超过300的字符串,这样长度的字符串的比较则会非常耗时间。 * 数据量太大不适合二分查找 * 二分查找需要依赖数组,而数组为了支持随机访问的特性,要求内存空间是连续的,因此对内存的要求比较苛刻。比如,要存储1GB的数据,则需要1GB的连续内存空间。 * 这里的**连续**是指:即使有2GB内存空间剩余,但是这些空间是零散的,没有连续的1GB的空间,那照样无法申请一个1GB的数组。 ### 问题 假设我们有 1000 万个整数数据,每个数据占 8 个字节,如何设计数据结构和算法,快速判断某个整数是否出现在这 1000 万数据中? 我们希望这个功能不要占用太多的内存空间,最多不要超过 100MB,你会怎么做呢? * 1000 * 10^4 * 8 / 10^6 = 80MB,可以使用数组存储80MB的整数,然后排序,使用二分查找取得某个数据。 * 大部分情况下,使用二分查找可以解决的问题,散列表和二叉树也可以解决,那么是否可以用散列表或者二叉树呢?答案是否定的。因为散列表和二叉树都需要额外的存储,100MB必然不够。 ## 排序 | 排序算法 | 最好 | 最坏 | 平均 | 额外空间复杂度 | 稳定性 | 原地排序 | | -------- | ---------- | ---------- | ---------- | -------------- | ------ | :------: | | 冒泡排序 | O(n) | O(n^2) | O(n^2) | O(1) | 是 | 是 | | 插入排序 | O(n) | O(n^2) | O(n^2) | O(1) | 是 | 是 | | 选择排序 | O(n^2) | O(n^2) | O(n^2) | O(1) | 否 | 是 | | 归并排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(n) | 是 | 否 | | 快速排序 | O(nlogn) | O(n^2) | O(nlogn) | O(1) | 否 | 是 | | 堆排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(1) | 否 | 是 | | 桶排序 | O(n) | - | - | O(n) | 是 | 否 | | 计数排序 | O(n+k)(k是数据范围) | - | - | O(k) | 是 | 否 | | 基数排序 | O(n*d)(d是维度) | - | - | O(n*d) | 是 | 否 | ## 递归 ### 什么是递归? ### 如何实现递归? ### 递归存在的问题是什么? ### 递归的应用 ## 设计思想 ### 空间换时间 当内存空间较为充足的时候,如果需要追求代码的执行速度,那么我们就可以选择空间复杂度相对较高,时间复杂度相对较低的数据结构或者算法。 ### 时间换空间 当内存比较紧缺的时候,例如代码执行在手机或单片机上的时候,那么我们就需要选择空间复杂度相对较低,空间复杂度相对较低的数据结构或者算法。 事实上,我们不可能既保证时间的效率又保证空间的效率,我们只能尽力实现二者的平衡,根据实际权衡时间和空间。 ## 缓存 **缓存**是一种提高数据读取性能的技术,在软件开发与硬件设计中应用非常广泛。 ### 缓存的设计思想 缓存利用了**空间换时间**的设计思想。 如果我们把数据存储在硬盘上,会比较节省内存,但是查找数据的时候,则需要从硬盘把数据读入,速度非常慢。通过缓存技术,事先将数据从硬盘加载到了内存中,这样查询数据的时候,就可以从内存中直接读取,因为内存中读取数据的速度远远快于硬盘中读取数据的速度,因此速度非常快,但是此时内存空间就变少了,相当于利用空间换时间。 ### 缓存的特征 **命中率**:当某个请求通过访问缓存而得到响应时,缓存命中。缓存的命中率越高,则缓存的利用率也就越高。 **最大空间**:缓存通常位于内存中,内存速度虽然要快于硬盘,但是内存的空间要远远小于硬盘,因此缓存的最大空间不可能非常大。 **淘汰策略**:缓存的大小有限,当缓存存放的数据量大于最大空间时,就涉及到数据的淘汰问题。常见的缓存淘汰策略有: - 最少使用策略LFU(Least Frequently Used):优先淘汰最少使用的数据。 - 先进先出策略FIFO(First In, First Out):在实时性要求的场景下,需要经常访问最新的数据,就可以使用FIFO,使得最先进入的数据被淘汰。 - 最近最少使用策略LRU(Least Recently Used):优先淘汰最久未使用的数据,也就是上次被访问时间距离现在最久的数据。LRU可以保证内存中的数据都是热点数据,即经常被访问的数据,从而保证缓存的命中率。