├── .gitignore ├── LICENSE ├── README.md ├── code └── offer │ ├── .idea │ ├── codeStyles │ │ ├── Project.xml │ │ └── codeStyleConfig.xml │ ├── dictionaries │ │ ├── Administrator.xml │ │ └── haiyu.xml │ ├── encodings.xml │ ├── inspectionProfiles │ │ └── Project_Default.xml │ ├── misc.xml │ ├── modules.xml │ ├── smartfox_info.xml │ ├── uiDesigner.xml │ ├── vcs.xml │ └── workspace.xml │ ├── offer.iml │ └── src │ ├── Chap2 │ ├── CutString.java │ ├── Fibonacci.java │ ├── FindDuplicate.java │ ├── FindIn2DArray.java │ ├── FromTail2Head.java │ ├── InOrderNextNode.java │ ├── JumpFloor.java │ ├── JumpFloor2.java │ ├── MergeTwoSortedArray.java │ ├── MinNumberInRotateArray.java │ ├── NumberOf1.java │ ├── PathIn2DArray.java │ ├── ReConstructTree.java │ ├── RectCover.java │ ├── ReplaceSpace.java │ ├── RobotMove.java │ ├── SingletonImp.java │ ├── SingletonImp2.java │ ├── SingletonImp3.java │ ├── SingletonImp4.java │ ├── SingletonImp5.java │ ├── SingletonImp6.java │ ├── TwoQueueImpStack.java │ └── TwoStackImpQueue.java │ ├── Chap3 │ ├── DeleteDuplicationNode.java │ ├── DeleteNode.java │ ├── EntryNodeOfLoop.java │ ├── FindKthToTailInLinkedList.java │ ├── MergeTwoOrderedList.java │ ├── Numeric.java │ ├── Power.java │ ├── PrintFrom1ToMaxOfNDigit.java │ ├── ReMatch.java │ ├── ReOrderArray.java │ ├── ReverseLinkedList.java │ └── SubTree.java │ ├── Chap4 │ ├── BSTTransToDLinkedList.java │ ├── BinaryTreeLevelOrder.java │ ├── CloneLinkedList.java │ ├── Combination.java │ ├── EightQueens.java │ ├── FindPathInBT.java │ ├── MinStack.java │ ├── MirrorTree.java │ ├── Permutation.java │ ├── PermutationExt.java │ ├── PrintMatrix.java │ ├── PrintTreeEveryLayer.java │ ├── PrintTreeZ.java │ ├── SerializeBT.java │ ├── StackIncludeFuncMin.java │ ├── StackPopOrder.java │ ├── SymmetricalTree.java │ └── VeritySeqOfSearchBST.java │ ├── Chap5 │ ├── ADigitInNumberSeq.java │ ├── AppearOnceInStream.java │ ├── FindGreatestSumOfSubArray.java │ ├── FirstAppearOnceChar.java │ ├── FirstPublicNode.java │ ├── InversePairs.java │ ├── LeastKNums.java │ ├── LongestSubstring.java │ ├── MaxGiftVal.java │ ├── MedianInStream.java │ ├── MoreThanHalf.java │ ├── NumOf1.java │ ├── PrintMinNumber.java │ ├── TransLateNumToString.java │ └── UglyNumber.java │ ├── Chap6 │ ├── Add.java │ ├── AppearOnlyOnce.java │ ├── BalancedTree.java │ ├── ContinuousSeq.java │ ├── DepthOfTree.java │ ├── FindNumsAppearOnce.java │ ├── FindTheLossNumber.java │ ├── KthNode.java │ ├── LastInCircle.java │ ├── MaxDiff.java │ ├── MaxInWindow.java │ ├── MaxQueue.java │ ├── Multiply.java │ ├── NumOfK.java │ ├── PlayCard.java │ ├── PrintProbability.java │ ├── ReverseWords.java │ ├── RotateString.java │ ├── Sum.java │ ├── TwoSum.java │ └── ValEqualsIndex.java │ └── Chap7 │ ├── LastSameInBST.java │ ├── LastSameInTree.java │ ├── LastSameInTreeParent.java │ ├── LowestCommonAncestor.java │ └── Str2Int.java └── notes ├── 剑指offer面试题 52--两个链表的第一个公共结点.md ├── 剑指offer面试题10--斐波那契数列.md ├── 剑指offer面试题11--旋转数组中的最小数字.md ├── 剑指offer面试题12--矩阵中的路径.md ├── 剑指offer面试题13--机器人的运动范围.md ├── 剑指offer面试题14--剪绳子.md ├── 剑指offer面试题15--二进制中1的个数.md ├── 剑指offer面试题16--数值的整数次方.md ├── 剑指offer面试题17--打印从1到最大的n位十进制数.md ├── 剑指offer面试题18——删除链表的结点.md ├── 剑指offer面试题19--正则表达式匹配.md ├── 剑指offer面试题2--单例模式.md ├── 剑指offer面试题20--表示数值的字符串.md ├── 剑指offer面试题21--调整数组的顺序使奇数位于偶数前面.md ├── 剑指offer面试题22--链表中倒数第k个结点.md ├── 剑指offer面试题23--链表中环的入口结点.md ├── 剑指offer面试题24--反转链表.md ├── 剑指offer面试题25--合并两个有序链表.md ├── 剑指offer面试题26--树的子结构.md ├── 剑指offer面试题27--对称的二叉树.md ├── 剑指offer面试题28--二叉树的镜像.md ├── 剑指offer面试题29--顺时针打印矩阵.md ├── 剑指offer面试题3--数组中的重复数字.md ├── 剑指offer面试题30--包含min方法的栈.md ├── 剑指offer面试题31--栈的压入、弹出序列.md ├── 剑指offer面试题32--从上到下打印二叉树.md ├── 剑指offer面试题33--二叉搜索树的后序遍历序列.md ├── 剑指offer面试题35--复杂链表的复制.md ├── 剑指offer面试题36--二叉搜索树与双向链表.md ├── 剑指offer面试题37--序列化二叉树.md ├── 剑指offer面试题38--字符串的排列.md ├── 剑指offer面试题39--数组中出现次数超过一半的数字.md ├── 剑指offer面试题4--二维数组中的查找.md ├── 剑指offer面试题40--最小的k个数.md ├── 剑指offer面试题41--数据流中的中位数.md ├── 剑指offer面试题42--连续子数组的最大和.md ├── 剑指offer面试题43--1~n整数中1出现的次数.md ├── 剑指offer面试题44--数字序列中的某一位数字.md ├── 剑指offer面试题45--把数组排成最小的数.md ├── 剑指offer面试题46--把数字翻译成字符串.md ├── 剑指offer面试题47--礼物的最大价值.md ├── 剑指offer面试题48--最长不含重复字符串的子字符串.md ├── 剑指offer面试题49--丑数.md ├── 剑指offer面试题5--替换空格.md ├── 剑指offer面试题50--第一个只出现一次的字符.md ├── 剑指offer面试题51--在排序数组中查找数字.md ├── 剑指offer面试题53--数组中的逆序对.md ├── 剑指offer面试题54--二叉搜索树中排名为k的结点.md ├── 剑指offer面试题55--二叉树的深度.md ├── 剑指offer面试题56--数组中数字出现的次数.md ├── 剑指offer面试题57--和为s的数字.md ├── 剑指offer面试题58--反转字符串.md ├── 剑指offer面试题59--队列的最大值.md ├── 剑指offer面试题6--从尾到头打印链表.md ├── 剑指offer面试题60--n个骰子的点数.md ├── 剑指offer面试题61--扑克牌中的顺子.md ├── 剑指offer面试题62--圆圈中最后剩下的数字.md ├── 剑指offer面试题63--股票的最大利润.md ├── 剑指offer面试题64--求1+2+3+...+n.md ├── 剑指offer面试题65--不用加减乘除做加法.md ├── 剑指offer面试题66--构建乘积数组.md ├── 剑指offer面试题67--把字符串转换成整数.md ├── 剑指offer面试题68--树中两个结点的最低公共祖先.md ├── 剑指offer面试题7--重建二叉树.md ├── 剑指offer面试题8--二叉树中序遍历的下一个结点.md ├── 剑指offer面试题9--两个栈实现队列.md └── 剑指off面试题34--二叉树中和为某一值的路径.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.jar 15 | *.war 16 | *.ear 17 | *.zip 18 | *.tar.gz 19 | *.rar 20 | 21 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 22 | hs_err_pid* 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 sunhaiyu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # coding-interviews 2 | 《剑指offer》(第二版)Java实现 3 | -------------------------------------------------------------------------------- /code/offer/.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /code/offer/.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /code/offer/.idea/dictionaries/Administrator.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /code/offer/.idea/dictionaries/haiyu.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /code/offer/.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /code/offer/.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /code/offer/.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /code/offer/.idea/smartfox_info.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /code/offer/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /code/offer/offer.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /code/offer/src/Chap2/CutString.java: -------------------------------------------------------------------------------- 1 | package Chap2; 2 | 3 | /** 4 | * 给你一根长度为n的绳子,把绳子剪成m段(m、n都是整数且m > 1, n > 1),m段绳子的长度依然是整数,求m段绳子的长度乘积最大为多少? 5 | * 比如绳子长度为8,我们可以分成2段、3段、4段...8段,只有分成3段且长度分别是2、3、3时能得到最大乘积18 6 | */ 7 | public class CutString { 8 | /** 9 | * 动态规划版本 10 | * f(n)定义为将长度为n的绳子分成若干段后的各段长度的最大乘积(最优解),在剪第一刀时有n-1种剪法,可选择在0 < i < n处下刀 11 | * 在i处下刀,分成长度为i的左半绳子和长度为n-i的右半绳子,对于这两根绳子,定义最优解为f(i)和f(n-i),于是f(n) = max(f(i) * f(n-i)),即求出各种相乘可能中的最大值就是f(n)的最优解 12 | * 就这样从上到下的分下去,但是问题的解决从下到上。即先求出f(2)、f(3)的最优解,然后根据这两个值求出f(4)、f(5)...直到f(n) 13 | * f(2) = 1,因为只能分成两半 14 | * f(3) = 2,因为分成两段2*1 大于分成三段的1*1*1 15 | * ... 16 | */ 17 | public static int maxProductAfterCutting(int length) { 18 | // 长度为1时不满足题意,返回0 19 | if (length < 2) { 20 | return 0; 21 | } 22 | // f(2) 23 | if (length == 2) { 24 | return 1; 25 | } 26 | // f(3) 27 | if (length == 3) { 28 | return 2; 29 | } 30 | // 加1是因为需要访问到products[length] 31 | int[] products = new int[length + 1]; 32 | // 下面这三个存的不是f(1)、f(2)、f(3),只是单纯的长度而已 33 | products[1] = 1; 34 | products[2] = 2; 35 | products[3] = 3; 36 | // 从products[4]到products[length]放的是f(4)~f(n)的值 37 | for (int i = 4; i <= length; i++) { 38 | int max = 0; 39 | 40 | // 对所有相乘情况进行遍历求出f(i)的最优解 41 | for (int j = 1; j <= i / 2; j++) { 42 | int product = products[j] * products[i - j]; 43 | if (product > max) { 44 | max = product; 45 | } 46 | } 47 | // 得到f(i)的最优解 48 | products[i] = max; 49 | } 50 | // 返回f(n) 51 | return products[length]; 52 | } 53 | 54 | /** 55 | * 贪婪法,不断分出长度为3的绳子,如果最后只剩下长度为1的绳子,退一步,将得到长度为4的绳子,然后将这长度为4的绳子分成2*2(这样分是因为2*2大于原来的3*1) 56 | * 因此n = 4时不能分出长度为3的绳子,而n = 2,n = 3可直接返回 57 | * 当n >=5时候,满足n >=5这个不等式的有2*(n-2) > n以及3*(n-3) > n 58 | * 注意到2+n-2 = 3+n-3 = n,也就是说分出的两个相乘的数要满足和为n,且同样的n,3*(n-3)的值更大,这就是为什么要不断分出长度为3的绳子的原因 59 | */ 60 | public static int maxProductAfterCutting2(int length) { 61 | // 长度为1时不满足题意,返回0 62 | if (length < 2) { 63 | return 0; 64 | } 65 | // f(2) 66 | if (length == 2) { 67 | return 1; 68 | } 69 | // f(3) 70 | if (length == 3) { 71 | return 2; 72 | } 73 | // 统计能分出多少段长度为3的绳子 74 | int timesOf3 = length / 3; 75 | // 如果最有只剩下长度为1的绳子,需要退一步,得到长度为4的绳子,重新分成2*2的 76 | if (length - timesOf3 * 3 == 1) { 77 | timesOf3--; 78 | } 79 | // 到这步length - timesOf3 * 3的值只可能是0,2,4,所以timesOf2只可能是0, 1, 2 80 | int timesOf2 = (length - timesOf3 * 3) / 2; 81 | return (int) Math.pow(3, timesOf3) * (int) Math.pow(2, timesOf2); 82 | 83 | } 84 | public static void main(String[] args) { 85 | System.out.println(maxProductAfterCutting(8)); 86 | System.out.println(maxProductAfterCutting2(8)); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /code/offer/src/Chap2/Fibonacci.java: -------------------------------------------------------------------------------- 1 | package Chap2; 2 | 3 | /** 4 | * 现在要求输入一个整数n,请你输出斐波那契数列的第n项。 5 | */ 6 | public class Fibonacci { 7 | /** 8 | * 推荐迭代法 9 | */ 10 | public int fib(int n) { 11 | if (n <= 0) { 12 | return 0; 13 | } 14 | 15 | int a = 0; 16 | int b = 1; 17 | while (n > 0) { 18 | b = a + b; 19 | a = b - a; // a + b -a -> a = b也就是原来的b赋值给a 20 | n--; 21 | } 22 | return a; 23 | } 24 | 25 | /** 26 | * 递归不推荐 27 | */ 28 | public int fib2(int n) { 29 | if (n <= 0) { 30 | return 0; 31 | } 32 | 33 | if (n == 1) { 34 | return 1; 35 | } 36 | 37 | return fib2(n-1) +fib(n-2); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /code/offer/src/Chap2/FindDuplicate.java: -------------------------------------------------------------------------------- 1 | package Chap2; 2 | 3 | import java.util.Arrays; 4 | 5 | /** 6 | * 在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 7 | * 例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是第一个重复的数字2或者3。 8 | */ 9 | public class FindDuplicate { 10 | /** 11 | * 我想到的方法,先将数组排序,如果有重复元素将会相邻。然后相邻元素两两比较即可 12 | * @param numbers 待查找的数组 13 | * @param length 数组的长度,其实就是numbers.length 14 | * @param duplication 用于保存重复数字,第一个被找到的重复数字存放在duplication[0]中 15 | * @return 如果在数组中有重复元素 16 | */ 17 | public boolean duplicate2(int numbers[],int length,int [] duplication) { 18 | if (numbers == null || length == 0){ 19 | return false; 20 | } 21 | 22 | Arrays.sort(numbers); 23 | for (int i = 0;i < length - 1;i++) { 24 | if (numbers[i] == numbers[i + 1]) { 25 | duplication[0] = numbers[i]; 26 | return true; 27 | } 28 | } 29 | return false; 30 | } 31 | 32 | /** 33 | * 推荐的做法,通过交换元素,将值i保存到numbers[i] 34 | * 在numbers[i]不和i相等时,如果numbers[i]和numbers[numbers[i]]相等就说明重复元素; 35 | * 否则就交换这两个元素,这个过程相当于排序。举个例子,通过交换将2放入numbers[2]。 36 | */ 37 | public boolean duplicate(int numbers[],int length,int [] duplication) { 38 | if (numbers == null || length <= 0) { 39 | return false; 40 | } 41 | for (int i = 0;i < length;i++){ 42 | if (numbers[i] < 0 || numbers[i] > length -1) { 43 | return false; 44 | } 45 | } 46 | 47 | for (int i = 0; i< length; i++) { 48 | while (numbers[i] != i) { 49 | // 现在numbers[i] != i ,设numbers[i] = j,所以如果下面的if成立,就是numbers[i] == numbers[j],说明找到 重复 50 | if (numbers[i] == numbers[numbers[i]]) { 51 | duplication[0] = numbers[i]; 52 | return true; 53 | } 54 | swap(numbers, i, numbers[i]); 55 | } 56 | } 57 | return false; 58 | } 59 | 60 | private void swap(int[] numbers, int p, int q) { 61 | int temp = numbers[p]; 62 | numbers[p] = numbers[q]; 63 | numbers[q] = temp; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /code/offer/src/Chap2/FindIn2DArray.java: -------------------------------------------------------------------------------- 1 | package Chap2; 2 | 3 | /** 4 | * 在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。 5 | * 完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。 6 | */ 7 | public class FindIn2DArray { 8 | /** 9 | * 我想到的做法,对每一行的一维数组作二分查找. 10 | * 如果矩阵是M*N的,一次二分查找是O(lg N), M行就是O(Mlg N) 11 | * 12 | * @param target 要查找的数 13 | * @param array 二维数组 14 | * @return true如果在数组中找到target 15 | */ 16 | public boolean Find2(int target, int[][] array) { 17 | if (array != null && array.length > 0) { 18 | // 注意high在循环外,一旦值更新下次循环不会再被初始化,可减少比较次数 19 | int high = array[0].length - 1; 20 | for (int i = 0; i < array.length; i++) { 21 | int low = 0; 22 | while (low <= high) { 23 | int mid = low + (high - low) / 2; 24 | if (target > array[i][mid]) { 25 | low = mid + 1; 26 | } else if (target < array[i][mid]) { 27 | high = mid - 1; 28 | } else { 29 | return true; 30 | } 31 | } 32 | } 33 | return false; 34 | } 35 | return false; 36 | } 37 | 38 | /** 39 | * 更推荐的做法,由于矩阵从上到下递增,从左到右递增。 40 | * 总是和二维矩阵的右上角元素比较(对称地,左下角也可以) 41 | * 如果目标比右上角的大,删除该行,行指针向下移动;如果比右上角的小,删除该列,列指针左移 42 | */ 43 | public boolean Find(int target, int[][] array) { 44 | if (array != null && array.length > 0) { 45 | int N = array.length; // 总行数 46 | int col = array[0].length - 1; // 列索引 47 | int row = 0; // 行索引 48 | // array[row][col]是右上角的元素 49 | while (row < N && col >= 0) { 50 | if (target < array[row][col]) { 51 | col--; 52 | } else if (target > array[row][col]) { 53 | row++; 54 | } else { 55 | return true; 56 | } 57 | } 58 | return false; 59 | } 60 | return false; 61 | } 62 | } -------------------------------------------------------------------------------- /code/offer/src/Chap2/FromTail2Head.java: -------------------------------------------------------------------------------- 1 | package Chap2; 2 | 3 | 4 | import java.util.LinkedList; 5 | import java.util.ArrayList; 6 | 7 | /** 8 | * 输入一个链表的头节点,从尾到头打印链表每个节点的值。 9 | */ 10 | 11 | public class FromTail2Head { 12 | // 节点类的定义 13 | private class ListNode { 14 | int val; 15 | ListNode next = null; 16 | 17 | ListNode(int val) { 18 | this.val = val; 19 | } 20 | } 21 | 22 | /** 23 | * 更推荐使用栈,正序压入,逆序弹出 24 | * 25 | * @param listNode 链表的头结点 26 | * @return 从尾到头排列的结点 27 | */ 28 | public ArrayList printListFromTailToHead(ListNode listNode) { 29 | LinkedList stack = new LinkedList<>(); 30 | for (ListNode node = listNode; node != null; node = node.next) { 31 | stack.push(node.val); 32 | } 33 | return new ArrayList<>(stack); 34 | } 35 | 36 | /** 37 | * 利用递归,先递归到最后一个结点后开始依次返回。链表如果很长不适合用递归,递归深度将很大 38 | */ 39 | private ArrayList a = new ArrayList<>(); 40 | 41 | public ArrayList printListFromTailToHead2(ListNode listNode) { 42 | if (listNode != null) { 43 | printListFromTailToHead(listNode.next); 44 | a.add(listNode.val); 45 | } 46 | return a; 47 | } 48 | } -------------------------------------------------------------------------------- /code/offer/src/Chap2/InOrderNextNode.java: -------------------------------------------------------------------------------- 1 | package Chap2; 2 | 3 | /** 4 | * 给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。 5 | * 注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。 6 | */ 7 | public class InOrderNextNode { 8 | 9 | private class TreeLinkNode { 10 | int val; 11 | TreeLinkNode left = null; 12 | TreeLinkNode right = null; 13 | // next指向父结点 14 | TreeLinkNode next = null; 15 | 16 | TreeLinkNode(int val) { 17 | this.val = val; 18 | } 19 | } 20 | 21 | public TreeLinkNode GetNext(TreeLinkNode pNode) { 22 | // 如果当前结点右子树不空,那么中序下一个结点是右子树的最左子结点(如果有的话);如果右子树没有左子结点就返回右子树根结点 23 | if (pNode.right != null) { 24 | pNode = pNode.right; 25 | while (pNode.left != null) { 26 | pNode = pNode.left; 27 | } 28 | return pNode; 29 | } 30 | // 如果当前子结点pNode右子树为空 31 | // 返回上层的父结点,如果父结点的右子结点就是当前结点,继续返回到上层的父结点...直到父结点的左子结点等于当前结点 32 | while (pNode.next != null && pNode.next.right == pNode) { 33 | pNode = pNode.next; 34 | } 35 | // 如果父结点的左子结点等于当前结点,说明下一个要遍历的结点就是父结点了;或者父结点为空(说明当前结点是root),还是返回父结点(null) 36 | // pNode.next == null 或者 pNode.next.left == pNode 37 | return pNode.next; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /code/offer/src/Chap2/JumpFloor.java: -------------------------------------------------------------------------------- 1 | package Chap2; 2 | 3 | /** 4 | * 一只青蛙一次可以跳上1级台阶,也可以跳上2级。 5 | * 求该青蛙跳上一个n级的台阶总共有多少种跳法。 6 | */ 7 | public class JumpFloor { 8 | /** 9 | * 到达1级台阶只有1种可能,到达2级台阶有2种可能;可记为f(1) = 1,f(2) = 2 10 | * 要到达3级台阶,可以选择在1级台阶处起跳,也可以选择在2级台阶处起跳,所以只需到达1级台阶的可能情况+到达2级台阶的可能情况,即f(3) = f(2) +f(1) 11 | * 同理到达n级台阶,可以在n-1级台阶起跳,也可在n-2级台阶起跳,f(n) = f(n-2)+f(n-1) 12 | * 可以看做是斐波那契数列 13 | * 14 | * @param target 要到达的第n级台阶 15 | * @return 到达n级台阶总共的跳法可能 16 | */ 17 | public int jumpFloor(int target) { 18 | if (target <= 0) { 19 | return 0; 20 | } 21 | if (target == 1) { 22 | return 1; 23 | } 24 | 25 | int a = 1; 26 | int b = 2; 27 | while (target > 1) { 28 | b = a + b; 29 | a = b - a; 30 | target--; 31 | } 32 | return a; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /code/offer/src/Chap2/JumpFloor2.java: -------------------------------------------------------------------------------- 1 | package Chap2; 2 | 3 | /** 4 | * 一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。 5 | * 求该青蛙跳上一个n级的台阶总共有多少种跳法。 6 | */ 7 | public class JumpFloor2 { 8 | /** 9 | * 到达1级台阶只有1种可能,到达2级台阶有2种可能;可记为f(1) = 1,f(2) = 2 10 | * 要到达3级台阶,可以选择在1级台阶处起跳,也可以选择在2级台阶处起跳,也可直接跳到3级,所以只需到达1级台阶的可能情况 + 到达2级台阶的可能情况 + 1,即f(3) = f(2) +f(1) + 1 11 | * 同理到达n级台阶,可以在n-1级台阶起跳,可在n-2、n-1、n-3...级台阶起跳,f(n) = f(n-1)+f(n-2)+f(n-3)...+1, 12 | * 如果令f(n-n) = f(0) = 1,上式可表示为f(n) = f(n-1)+f(n-2)+f(n-3)...+f(n-n), 13 | * 14 | * !!还有种更好理解的思路:前n-1级台阶,每级台阶都有两种选择——跳到此或不跳到此,对于最后一级n级,没得选择,必须跳到这里 15 | * 所以有2^(n-1)种跳法 16 | * 17 | * @param target 要到达的第n级台阶 18 | * @return 达n级台阶总共的跳法可能 19 | */ 20 | public int jumpFloorII(int target) { 21 | if (target <= 0) { 22 | return 0; 23 | } 24 | return (int) Math.pow(2, target - 1); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /code/offer/src/Chap2/MergeTwoSortedArray.java: -------------------------------------------------------------------------------- 1 | package Chap2; 2 | 3 | import java.util.Arrays; 4 | 5 | /** 6 | * 将有序数组B归并到有序数组A中(A能容纳下B),归并后的A数组也是有序的 7 | */ 8 | public class MergeTwoSortedArray { 9 | public static void merge(Integer[] a, Integer[] b) { 10 | // 统计a数组中有值元素的个数 11 | int len = 0; 12 | for (int i = 0;i < a.length;i++) { 13 | if (a[i] != null) { 14 | len++; 15 | } 16 | } 17 | int pA = len-1; // 指向a数组不为空的最后一个元素末尾 18 | int pB = b.length-1; // 指向b数组不为空的最后一个元素末尾 19 | int k = len + b.length -1; // 指向归并后数组不为空的最后一个元素末尾 20 | 21 | while (k >= 0) { 22 | // a数组取完了,取b数组中的 23 | if (pA < 0) { 24 | a[k--] = b[pB--]; 25 | // b数组取完了,取a数组中的 26 | } else if (pB < 0) { 27 | a[k--] = a[pA--]; 28 | // 否则谁的大取谁的值 29 | } else if (a[pA] > b[pB]) { 30 | a[k--]= a[pA--]; 31 | } else { 32 | a[k--] = b[pB--]; 33 | } 34 | } 35 | } 36 | 37 | public static void main(String[] args) { 38 | Integer[] a = new Integer[10]; 39 | Integer[] b= {1, 3, 5, 7, 9}; 40 | // {2, 4, 6, 8, 10} 41 | for (int i = 0; i < 5; i++) { 42 | a[i] = 2 * i + 2; 43 | } 44 | 45 | merge(a, b); 46 | System.out.println(Arrays.toString(a)); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /code/offer/src/Chap2/MinNumberInRotateArray.java: -------------------------------------------------------------------------------- 1 | package Chap2; 2 | 3 | /** 4 | * 把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。 输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。 5 | * 例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。 6 | * NOTE:给出的所有元素都大于0,若数组大小为0,请返回0 7 | */ 8 | public class MinNumberInRotateArray { 9 | /** 10 | * 二分查找法 11 | * @param array 旋转数组 12 | * @return 旋转数组的最小值 13 | */ 14 | public int minNumberInRotateArray(int [] array) { 15 | if (array==null || array.length == 0) { 16 | return 0; 17 | } 18 | 19 | int low = 0; 20 | int high = array.length - 1; 21 | while (low < high) { 22 | int mid = low + (high - low) / 2; 23 | if (array[mid] > array[high]) { 24 | low = mid + 1; 25 | } else if (array[mid] < array[high]) { 26 | high = mid; 27 | } else { 28 | high--; 29 | } 30 | } 31 | return array[low]; 32 | } 33 | 34 | /** 35 | * 顺序查找 36 | */ 37 | public int minNumberInRotateArray2(int [] array) { 38 | if (array.length == 0) { 39 | return 0; 40 | } 41 | 42 | for (int i = 0;i < array.length - 1;i++) { 43 | if (array[i] > array[i + 1]) { 44 | return array[i + 1]; 45 | } 46 | } 47 | return array[0]; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /code/offer/src/Chap2/NumberOf1.java: -------------------------------------------------------------------------------- 1 | package Chap2; 2 | 3 | /** 4 | * 输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。 5 | */ 6 | public class NumberOf1 { 7 | /** 8 | * 减1后的值比原值相与,就是将二进制表示中最后一个1变成0 9 | */ 10 | public int numberOf1(int n) { 11 | int count = 0; 12 | 13 | while (n != 0) { 14 | n = (n-1) & n; 15 | count++; 16 | } 17 | return count; 18 | } 19 | 20 | /** 21 | * 左移版本 22 | */ 23 | public int numberOf1_1(int n) { 24 | int count = 0; 25 | int flag = 1; 26 | while (flag != 0) { 27 | if ((n & flag)!= 0) { 28 | count++; 29 | } 30 | flag = flag << 1; 31 | } 32 | return count; 33 | } 34 | 35 | /** 36 | * Java中的无符号右移 >>> 不论正负数都是补0 37 | */ 38 | public int numberOf1_2(int n) { 39 | int count = 0; 40 | 41 | while (n != 0) { 42 | if ((n & 1) == 1) { 43 | count++; 44 | } 45 | n = n >>> 1; 46 | } 47 | return count; 48 | } 49 | /** 50 | * 判断一个整数是不是2的正整数次方。 51 | */ 52 | public boolean isExponOf2(int n) { 53 | return numberOf1(n) == 1; 54 | } 55 | 56 | /** 57 | * 输入两个整数m和n,计算需要改变m的二进制表示中的几位才能得到n。 58 | * 比如10的二进制是1010,13的二进制是1101,则需要改变3次。 59 | * @param m 一个整数 60 | * @param n 另一个整数 61 | * @return 需要改变的位数 62 | */ 63 | public int bitNumNeedsToBeChanged(int m, int n) { 64 | return numberOf1(m ^ n); 65 | } 66 | 67 | public static void main(String[] args) { 68 | NumberOf1 a = new NumberOf1(); 69 | System.out.println(a.isExponOf2(8)); 70 | System.out.println(a.bitNumNeedsToBeChanged(10, 13)); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /code/offer/src/Chap2/PathIn2DArray.java: -------------------------------------------------------------------------------- 1 | package Chap2; 2 | 3 | /** 4 | * 请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。 5 | * 如果一条路径经过了矩阵中的某一个格子,则该路径不能再进入该格子。 例如下面矩阵 6 | 7 | a b t g 8 | c f c s 9 | j d e h 10 | 11 | 包含一条字符串"bfce"的路径,但是矩阵中不包含"abfb"路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入该格子。 12 | */ 13 | public class PathIn2DArray { 14 | public boolean hasPath(char[] matrix, int rows, int cols, char[] str) { 15 | // 用于将当前路径上的访问过的点标记为“已访问”,防止同一个点访问两次 16 | boolean[] marked = new boolean[matrix.length]; 17 | // 所有点都作为起点搜索一次 18 | for (int row = 0; row < rows; row++) { 19 | for (int col = 0; col < cols; col++) { 20 | if (hasPathTo(matrix, rows, cols, row, col, str, 0, marked)) { 21 | return true; 22 | } 23 | } 24 | } 25 | return false; 26 | } 27 | 28 | private boolean hasPathTo(char[] matrix, int rows, int cols, int row, int col, char[] str, int len, boolean[] marked) { 29 | int index = row * cols + col; 30 | if (row < 0 || row >= rows || col < 0 || col >= cols || matrix[index] != str[len] || marked[index]) { 31 | return false; 32 | } 33 | // 递归深度能到字符串末尾,说明有这条路径 34 | if (len == str.length - 1) { 35 | return true; 36 | } 37 | 38 | marked[index] = true; 39 | // 四个方向上有没有可以到达下一个字符的路径,有任意一个方向可以就继续下一个字符的搜索 40 | if (hasPathTo(matrix, rows, cols, row, col - 1, str, len + 1, marked) || 41 | hasPathTo(matrix, rows, cols, row - 1, col, str, len + 1, marked) || 42 | hasPathTo(matrix, rows, cols, row, col + 1, str, len + 1, marked) || 43 | hasPathTo(matrix, rows, cols, row + 1, col, str, len + 1, marked)) { 44 | return true; 45 | } 46 | // 对于搜索失败需要回溯的路径上的点,则要重新标记为“未访问”,方便另辟蹊径时能访问到 47 | marked[index] = false; 48 | return false; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /code/offer/src/Chap2/ReConstructTree.java: -------------------------------------------------------------------------------- 1 | package Chap2; 2 | 3 | /** 4 | * 输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。 5 | * 例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。 6 | */ 7 | public class ReConstructTree { 8 | 9 | private class TreeNode { 10 | int val; 11 | TreeNode left; 12 | TreeNode right; 13 | 14 | TreeNode(int x) { 15 | val = x; 16 | } 17 | } 18 | 19 | public TreeNode reConstructBinaryTree(int[] pre, int[] in) { 20 | TreeNode root = reConstructBinaryTree(pre, 0, pre.length - 1, in, 0, in.length - 1); 21 | return root; 22 | } 23 | 24 | /** 25 | * 递归! 26 | * [preStart + 1, preStart + i - inStart]是前序序列中左子树封闭区间 27 | * [preStart + i - inStart + 1, preEnd]是前序序列中右子树封闭区间 28 | * 29 | * [inStart, i - 1]是中序序列中左子树封闭区间 30 | * [i + 1, inEnd]是中序序列中右子树封闭区间 31 | * 32 | * @param pre 前序序列 33 | * @param preStart 前序序列封闭区间的左指针 34 | * @param preEnd 前序序列封闭区间的右指针 35 | * @param in 中序序列 36 | * @param inStart 中序序列封闭区间的左指针 37 | * @param inEnd 中序序列封闭区间的右指针 38 | * @return 树的根结点 39 | */ 40 | private TreeNode reConstructBinaryTree(int[] pre, int preStart, int preEnd, int[] in, int inStart, int inEnd) { 41 | // 还有子数组就继续递归,不存在子数组了(表现为end > start),就返回空子树给父结点 42 | if (preStart > preEnd || inStart > inEnd) { 43 | return null; 44 | } 45 | int rootVal = pre[preStart]; 46 | TreeNode root = new TreeNode(rootVal); 47 | for (int i = inStart; i <= inEnd; i++) { 48 | if (in[i] == rootVal) { 49 | root.left = reConstructBinaryTree(pre, preStart + 1, preStart + i - inStart, in, inStart, i - 1); 50 | root.right = reConstructBinaryTree(pre, preStart + i - inStart + 1, preEnd, in, i + 1, inEnd); 51 | } 52 | } 53 | return root; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /code/offer/src/Chap2/RectCover.java: -------------------------------------------------------------------------------- 1 | package Chap2; 2 | 3 | /** 4 | * 我们可以用2*1的小矩形横着或者竖着去覆盖更大的矩形。 5 | * 请问用n个2*1的小矩形无重叠地覆盖一个2*n的大矩形,总共有多少种方法? 6 | */ 7 | public class RectCover { 8 | /** 9 | * 和JumpFloor的代码一模一样! 10 | */ 11 | public int RectCover(int target) { 12 | if (target <= 0) { 13 | return 0; 14 | } 15 | if (target == 1) { 16 | return 1; 17 | } 18 | 19 | int a = 1; 20 | int b = 2; 21 | while (target > 1) { 22 | b = a + b; 23 | a = b - a; 24 | target--; 25 | } 26 | return a; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /code/offer/src/Chap2/ReplaceSpace.java: -------------------------------------------------------------------------------- 1 | package Chap2; 2 | 3 | /** 4 | * 请实现一个函数,将一个字符串中的空格替换成“%20”。 5 | * 例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。 6 | */ 7 | public class ReplaceSpace { 8 | /** 9 | * 遍历每一个字符,如果是空格就使用Java库函数replace替换 10 | * 11 | * @param str 原字符串 12 | * @return 替换空格后的字符串 13 | */ 14 | public String replaceSpace2(StringBuffer str) { 15 | if (str == null) { 16 | return null; 17 | } 18 | 19 | for (int i = 0; i < str.length(); i++) { 20 | if (str.charAt(i) == ' ') { 21 | str.replace(i, i + 1, "%20"); 22 | } 23 | } 24 | return str.toString(); 25 | } 26 | 27 | /** 28 | * 更加简洁的库函数方法 29 | */ 30 | public String replaceSpace3(StringBuffer str) { 31 | return str.toString().replaceAll("\\s", "%20"); 32 | } 33 | 34 | /** 35 | * 库函数很方便,但是也要理解原理,所以掌握下面的方法是很有必要的。 36 | * 1、统计空格数目 37 | * 2、增长原字符串的长度 38 | * 3、两个指针,一个指向原来字符串末尾,一个指向新字符串末尾。从后往前扫描字符串,并左移指针 39 | */ 40 | public String replaceSpace(StringBuffer str) { 41 | if (str == null) { 42 | return null; 43 | } 44 | // 统计空格个数 45 | int spaceNum = 0; 46 | for (int i = 0;i < str.length();i++) { 47 | if (str.charAt(i) == ' ') { 48 | spaceNum++; 49 | } 50 | } 51 | // 原来字符串的末尾指针 52 | int oldP = str.length() - 1; 53 | // 设置新长度 54 | str.setLength(str.length() + 2*spaceNum); 55 | // 新的字符串的末尾指针 56 | int newP = str.length() - 1; 57 | while (oldP >=0 && newP > oldP) { 58 | if (str.charAt(oldP) == ' ') { 59 | str.setCharAt(newP--, '0'); 60 | str.setCharAt(newP--, '2'); 61 | str.setCharAt(newP--, '%'); 62 | } else { 63 | str.setCharAt(newP--, str.charAt(oldP)); 64 | } 65 | oldP--; 66 | } 67 | return str.toString(); 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /code/offer/src/Chap2/RobotMove.java: -------------------------------------------------------------------------------- 1 | package Chap2; 2 | 3 | /** 4 | * 地上有一个m行和n列的方格。一个机器人从坐标0,0的格子开始移动,每一次只能向左,右,上,下四个方向移动一格,但是不能进入行坐标和列坐标的数位之和大于k的格子。 5 | * 例如,当k为18时,机器人能够进入方格(35,37),因为3+5+3+7 = 18。但是,它不能进入方格(35,38),因为3+5+3+8 = 19。请问该机器人能够达到多少个格子? 6 | */ 7 | public class RobotMove { 8 | public int movingCount(int threshold, int rows, int cols) { 9 | if (rows <= 0 || cols <= 0 || threshold < 0) { 10 | return 0; 11 | } 12 | 13 | boolean[] marked = new boolean[rows * cols]; 14 | return move(0, 0, threshold, rows, cols, marked); 15 | 16 | } 17 | 18 | /** 19 | * 递归方法,每到一个格子就四个方向搜索 20 | * 21 | * @param row 当前行 22 | * @param col 当前列 23 | * @param threshold 门限值 24 | * @param rows 总行数 25 | * @param cols 总列数 26 | * @param marked 是否访问过 27 | * @return 当前格子加上4个方向能访问到的格子数的总和 28 | */ 29 | private int move(int row, int col, int threshold, int rows, int cols, boolean[] marked) { 30 | int count = 0; 31 | if (checked(row, col, threshold, rows, cols, marked)) { 32 | marked[row * cols + col] = true; 33 | 34 | count = move(row - 1, col, threshold, rows, cols, marked) + 35 | move(row + 1, col, threshold, rows, cols, marked) + 36 | move(row, col - 1, threshold, rows, cols, marked) + 37 | move(row, col + 1, threshold, rows, cols, marked) + 1; 38 | } 39 | return count; 40 | } 41 | 42 | /** 43 | * 判断当前格子是否超过门限值,以及边界值的判断 44 | * 45 | * @return true如果当前格子满足条件 46 | */ 47 | private boolean checked(int row, int col, int threshold, int rows, int cols, boolean[] marked) { 48 | return row >= 0 && row < rows && col >= 0 && col < cols && !marked[row * cols + col] && digitSum(row) + digitSum(col) <= threshold; 49 | } 50 | 51 | /** 52 | * 比如数字1234,每位数相加的和将返回10 53 | * @param number 某数字 54 | * @return 该数字的数位之和 55 | */ 56 | private int digitSum(int number) { 57 | int sum = 0; 58 | while (number > 0) { 59 | sum += number % 10; 60 | number /= 10; 61 | } 62 | return sum; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /code/offer/src/Chap2/SingletonImp.java: -------------------------------------------------------------------------------- 1 | package Chap2; 2 | /** 3 | * 单例模式,饿汉模式,不管为不为空,先直接new一个出来 4 | */ 5 | public class SingletonImp { 6 | // 饿汉模式 7 | private static SingletonImp singletonImp = new SingletonImp(); 8 | // 私有化(private)该类的构造函数 9 | private SingletonImp() { 10 | } 11 | 12 | public static SingletonImp getInstance() { 13 | return singletonImp; 14 | } 15 | 16 | public static void main(String[] args) { 17 | System.out.println(SingletonImp.getInstance()); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /code/offer/src/Chap2/SingletonImp2.java: -------------------------------------------------------------------------------- 1 | package Chap2; 2 | 3 | /** 4 | * 单例模式,懒汉模式,为空才new 5 | */ 6 | public class SingletonImp2 { 7 | private static SingletonImp2 singletonImp2; 8 | 9 | private SingletonImp2() {} 10 | // 懒汉模式 11 | public static SingletonImp2 getInstance() { 12 | if (singletonImp2 == null) { 13 | singletonImp2 = new SingletonImp2(); 14 | } 15 | return singletonImp2; 16 | } 17 | 18 | public static void main(String[] args) { 19 | System.out.println(SingletonImp2.getInstance()); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /code/offer/src/Chap2/SingletonImp3.java: -------------------------------------------------------------------------------- 1 | package Chap2; 2 | 3 | /** 4 | * SingletonImp2在多线程中,如果多个线程同时运行到if (singletonImp2 == null) 就会创建多个对象 5 | * 所以加上同步锁 6 | */ 7 | public class SingletonImp3 { 8 | private static SingletonImp3 singletonImp3; 9 | 10 | private SingletonImp3() {} 11 | // 懒汉模式 12 | public static SingletonImp3 getInstance() { 13 | // 同步锁的加入 14 | synchronized (SingletonImp3.class) { 15 | if (singletonImp3 == null) { 16 | singletonImp3 = new SingletonImp3(); 17 | } 18 | } 19 | return singletonImp3; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /code/offer/src/Chap2/SingletonImp4.java: -------------------------------------------------------------------------------- 1 | package Chap2; 2 | 3 | /** 4 | * SingletonImp3中每次调用getInstance都会加同步锁,而加锁是一个很耗时的过程 5 | * 实际上加锁只需要在第一次创建对象时 6 | */ 7 | public class SingletonImp4 { 8 | private static SingletonImp4 singletonImp4; 9 | 10 | private SingletonImp4() {} 11 | 12 | public static SingletonImp4 getInstance() { 13 | // 第一次创建时才加锁 14 | if (singletonImp4 == null) { 15 | synchronized (SingletonImp4.class) { 16 | if (singletonImp4 == null) { 17 | singletonImp4 = new SingletonImp4(); 18 | } 19 | } 20 | } 21 | 22 | return singletonImp4; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /code/offer/src/Chap2/SingletonImp5.java: -------------------------------------------------------------------------------- 1 | package Chap2; 2 | 3 | /** 4 | * 静态代码块只在类加载的时候调用一次(静态方法调用等第一次用到该类的时候) 5 | */ 6 | public class SingletonImp5 { 7 | private static SingletonImp5 singletonImp5; 8 | private SingletonImp5() {} 9 | 10 | static { 11 | singletonImp5 = new SingletonImp5(); 12 | } 13 | 14 | public static SingletonImp5 getInstance() { 15 | return singletonImp5; 16 | } 17 | 18 | 19 | public static void func() { 20 | 21 | } 22 | 23 | public static void main(String[] args) { 24 | // 调用任意静态方法,都会创建实例,导致过早创建 25 | SingletonImp5.func(); 26 | System.out.println(SingletonImp5.singletonImp5); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /code/offer/src/Chap2/SingletonImp6.java: -------------------------------------------------------------------------------- 1 | package Chap2; 2 | 3 | /** 4 | * SingletonImp5使用静态代码块,如果该类中还有其他静态方法,调用后就会执行静态代码块使得对象过早创建 5 | * 使用一个静态类来创建Singleton,其他静态方法只要没有调用Nested.singletonImp6就不会创建Singleton 6 | * 实现了需要时才创建实例对象,避免过早创建 7 | */ 8 | public class SingletonImp6 { 9 | private SingletonImp6() {} 10 | 11 | // 专门用于创建Singleton的静态类 12 | private static class Nested { 13 | private static SingletonImp6 singletonImp6; 14 | static { 15 | singletonImp6 = new SingletonImp6(); 16 | } 17 | } 18 | 19 | public static SingletonImp6 getInstance() { 20 | return Nested.singletonImp6; 21 | } 22 | 23 | public static void func() { 24 | 25 | } 26 | 27 | public static void main(String[] args) { 28 | System.out.println(SingletonImp6.getInstance()); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /code/offer/src/Chap2/TwoQueueImpStack.java: -------------------------------------------------------------------------------- 1 | package Chap2; 2 | 3 | import java.util.LinkedList; 4 | import java.util.Queue; 5 | 6 | /** 7 | * 两个队列实现一个栈 8 | */ 9 | public class TwoQueueImpStack { 10 | private Queue a; 11 | private Queue another; 12 | 13 | public TwoQueueImpStack() { 14 | this.a = new LinkedList<>(); 15 | this.another = new LinkedList<>(); 16 | } 17 | 18 | public void push(int node) { 19 | if (a.isEmpty() && another.isEmpty()) { 20 | a.offer(node); 21 | } else if (!a.isEmpty()){ 22 | a.offer(node); 23 | } else { 24 | another.offer(node); 25 | } 26 | } 27 | 28 | public int pop() { 29 | if (a.isEmpty() && another.isEmpty()) { 30 | throw new RuntimeException("栈已空"); 31 | } 32 | 33 | if (!another.isEmpty()) { 34 | int size = another.size(); 35 | // 除了最后一个元素,其他都删除并复制到另外一个队列中 36 | for (int i = 0; i < size - 1; i++) { 37 | a.offer(another.poll()); 38 | } 39 | return another.poll(); 40 | } else { 41 | int size = a.size(); 42 | for (int i = 0; i < size -1; i++) { 43 | another.offer(a.poll()); 44 | } 45 | return a.poll(); 46 | } 47 | } 48 | 49 | 50 | public static void main(String[] args) { 51 | TwoQueueImpStack a = new TwoQueueImpStack(); 52 | a.push(54); 53 | a.push(55); 54 | a.push(56); 55 | System.out.println(a.pop()); 56 | System.out.println(a.pop()); 57 | a.push(53); 58 | System.out.println(a.pop()); 59 | a.push(52); 60 | System.out.println(a.pop()); 61 | System.out.println(a.pop()); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /code/offer/src/Chap2/TwoStackImpQueue.java: -------------------------------------------------------------------------------- 1 | package Chap2; 2 | 3 | import java.util.LinkedList; 4 | 5 | /** 6 | * 两个栈实现一个队列 7 | */ 8 | public class TwoStackImpQueue { 9 | 10 | private LinkedList stack1 = new LinkedList<>(); 11 | private LinkedList stack2 = new LinkedList<>(); 12 | 13 | public void enqueue(int node) { 14 | stack1.push(node); 15 | } 16 | 17 | public int dequeue() { 18 | if (stack1.isEmpty() && stack2.isEmpty()) { 19 | throw new RuntimeException("队列为空"); 20 | } 21 | 22 | if (stack2.isEmpty()) { 23 | while (!stack1.isEmpty()) { 24 | stack2.push(stack1.pop()); 25 | } 26 | } 27 | return stack2.pop(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /code/offer/src/Chap3/DeleteDuplicationNode.java: -------------------------------------------------------------------------------- 1 | package Chap3; 2 | 3 | /** 4 | * 在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 5 | * 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5 6 | * 注意重复的结点不保留:并不是将重复结点删除到只剩一个,而是重复结点的全部会被删除。所以 7 | * 链表1->2->3->3->4->4->5不是1->2->3->4->5 8 | */ 9 | public class DeleteDuplicationNode { 10 | 11 | private class ListNode { 12 | int val; 13 | ListNode next = null; 14 | 15 | ListNode(int val) { 16 | this.val = val; 17 | } 18 | } 19 | 20 | public ListNode deleteDuplication_2(ListNode pHead) { 21 | if (pHead == null || pHead.next == null) { 22 | return pHead; 23 | } 24 | // 当前结点的前一个结点 25 | ListNode pre = null; 26 | // 当前结点 27 | ListNode cur = pHead; 28 | while (cur != null && cur.next != null) { 29 | // 如果当前结点和下一个结点值相同 30 | if (cur.val == cur.next.val) { 31 | int val = cur.val; 32 | // 跳过所有和cur相同的值 33 | while (cur != null && (cur.val == val)) { 34 | cur = cur.next; 35 | } 36 | // 跳出循环得到的是第一个和cur.val不同的结点 37 | // pre为空说明头结点就是重复结点,因此需要重新设置头结点 38 | if (pre == null) pHead = cur; 39 | // 否则cur之前的pre的下一个结点何cur连接 40 | else pre.next = cur; 41 | // 不相等就像前推进,更新cur和pre 42 | } else { 43 | pre = cur; 44 | cur = cur.next; 45 | } 46 | } 47 | return pHead; 48 | } 49 | 50 | public ListNode deleteDuplication(ListNode pHead) { 51 | if (pHead == null || pHead.next == null) { 52 | return pHead; 53 | } 54 | // 建立一个头结点代替原来的pHead 55 | ListNode first = new ListNode(pHead.val - 1); 56 | first.next = pHead; 57 | // 当前结点的前一个结点 58 | ListNode pre = first; 59 | // 当前结点 60 | ListNode cur = pHead; 61 | while (cur != null && cur.next != null) { 62 | if (cur.val == cur.next.val) { 63 | int val = cur.val; 64 | while (cur != null && (cur.val == val)) { 65 | cur = cur.next; 66 | } 67 | pre.next = cur; 68 | } else { 69 | pre = cur; 70 | cur = cur.next; 71 | } 72 | } 73 | // 这里不能返回pHead,因为pHead也可能被删除了 74 | return pre.next; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /code/offer/src/Chap3/DeleteNode.java: -------------------------------------------------------------------------------- 1 | package Chap3; 2 | 3 | public class DeleteNode { 4 | 5 | private Node first; 6 | 7 | private class Node { 8 | int val; 9 | Node next; 10 | } 11 | 12 | /** 13 | * 常规方法,从first开始找到要删除结点的前一个结点,时间复杂度为O(n) 14 | */ 15 | public void deleteNode_2(Node toBeDel) { 16 | if (first == null || toBeDel == null) { 17 | return; 18 | } 19 | // 要删除的就是链表头,它没有前一个结点 20 | if (first == toBeDel) { 21 | first = first.next; 22 | } else { 23 | Node cur = first; 24 | // 找到被删除结点的前一个结点 25 | while (cur != null && cur.next != toBeDel) { 26 | cur = cur.next; 27 | } 28 | if (cur != null) { 29 | // cur为toBeDel的前一个结点 30 | cur.next = cur.next.next; 31 | } 32 | } 33 | } 34 | 35 | /** 36 | * 将toBeDel的下一个结点j的值复制给toBeDel。然后将toBeDel指向j的下一个结点 37 | */ 38 | public void deleteNode(Node toBeDel) { 39 | if (first == null || toBeDel == null) { 40 | return; 41 | } 42 | // 要删除的不是最后一个结点 43 | if (toBeDel.next != null) { 44 | Node p = toBeDel.next; 45 | toBeDel.val = p.val; 46 | toBeDel.next = p.next; 47 | // 是尾结点也是头结点 48 | } else if (first == toBeDel) { 49 | first = first.next; 50 | // 仅仅是尾结点,即在含有多个结点的链表中删除尾结点 51 | } else { 52 | Node cur = first; 53 | while (cur.next != toBeDel) { 54 | cur = cur.next; 55 | } 56 | cur.next = null; 57 | } 58 | 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /code/offer/src/Chap3/EntryNodeOfLoop.java: -------------------------------------------------------------------------------- 1 | package Chap3; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | import java.util.Set; 6 | import java.util.HashSet; 7 | 8 | /** 9 | * 一个链表中包含环,请找出该链表的环的入口结点。 10 | */ 11 | public class EntryNodeOfLoop { 12 | private class ListNode { 13 | int val; 14 | ListNode next = null; 15 | 16 | ListNode(int val) { 17 | this.val = val; 18 | } 19 | } 20 | 21 | /** 22 | * 双指针法 23 | * @param pHead 链表头结点 24 | * @return 环的入口结点 25 | */ 26 | public ListNode entryNodeOfLoop(ListNode pHead) 27 | { 28 | ListNode pFast = pHead; 29 | ListNode pSlow = pHead; 30 | while (pFast != null && pFast.next != null) { 31 | pFast = pFast.next.next; 32 | pSlow = pSlow.next; 33 | // 运行到此说明有环 34 | if (pFast == pSlow) { 35 | pFast = pHead; 36 | while (pFast != pSlow) { 37 | pFast = pFast.next; 38 | pSlow = pSlow.next; 39 | } 40 | // 在入口节点处相遇 41 | return pSlow; 42 | } 43 | } 44 | 45 | return null; 46 | } 47 | 48 | /** 49 | * 利用Set不可添加重复元素的性质 50 | */ 51 | public ListNode entryNodeOfLoop_set(ListNode pHead) { 52 | if (pHead == null) { 53 | return null; 54 | } 55 | 56 | Set set = new HashSet<>(); 57 | ListNode cur = pHead; 58 | while (cur != null) { 59 | if (!set.add(cur)) { 60 | return cur; 61 | } 62 | cur = cur.next; 63 | } 64 | return null; 65 | } 66 | 67 | /** 68 | * Map,思路同Set 69 | */ 70 | public ListNode entryNodeOfLoop_map(ListNode pHead) { 71 | if (pHead == null) { 72 | return null; 73 | } 74 | 75 | Map map = new HashMap<>(); 76 | ListNode cur = pHead; 77 | while (cur != null) { 78 | if (map.containsKey(cur)) { 79 | return cur; 80 | } 81 | map.put(cur, true); 82 | cur = cur.next; 83 | } 84 | return null; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /code/offer/src/Chap3/FindKthToTailInLinkedList.java: -------------------------------------------------------------------------------- 1 | package Chap3; 2 | 3 | /** 4 | * 输入一个链表,输出该链表中倒数第k个结点。 5 | */ 6 | public class FindKthToTailInLinkedList { 7 | 8 | private class ListNode { 9 | int val; 10 | ListNode next = null; 11 | 12 | ListNode(int val) { 13 | this.val = val; 14 | } 15 | } 16 | 17 | public ListNode FindKthToTail(ListNode head, int k) { 18 | if (k <= 0 || head == null) { 19 | return null; 20 | } 21 | 22 | ListNode a = head; 23 | ListNode b = head; 24 | // 第一个指针先移动k -1步 25 | for (int i = 0; i < k - 1; i++) { 26 | // 如果k的值比链表结点数还大,就会出现这种情况 27 | if (a.next == null) { 28 | return null; 29 | } 30 | a = a.next; 31 | } 32 | // 然后两个指针同时移动到末尾 33 | while (a.next != null) { 34 | a = a.next; 35 | b = b.next; 36 | } 37 | return b; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /code/offer/src/Chap3/MergeTwoOrderedList.java: -------------------------------------------------------------------------------- 1 | package Chap3; 2 | 3 | /** 4 | * 输入两个单调递增的链表,输出两个链表合成后的链表. 5 | * 当然我们需要合成后的链表满足单调不减规则。 6 | */ 7 | public class MergeTwoOrderedList { 8 | private class ListNode { 9 | int val; 10 | ListNode next = null; 11 | 12 | ListNode(int val) { 13 | this.val = val; 14 | } 15 | } 16 | 17 | /** 18 | * 非递归实现 19 | * @param list1 有序链表1 20 | * @param list2 有序链表2 21 | * @return 合并后的有序链表 22 | */ 23 | public ListNode merge(ListNode list1, ListNode list2) { 24 | if (list1 == null) { 25 | return list2; 26 | } 27 | if (list2 == null) { 28 | return list1; 29 | } 30 | 31 | ListNode p1 = list1; 32 | ListNode p2 = list2; 33 | ListNode head = null; 34 | // 两个链表中哪个头结点的值小,就以此作为新链表的头结点 35 | if (p1.val <= p2.val) { 36 | head = p1; 37 | p1 = p1.next; 38 | } else { 39 | head = p2; 40 | p2 = p2.next; 41 | } 42 | 43 | ListNode cur = head; 44 | 45 | while (p1 != null || p2 != null) { 46 | // list1取完了,直接将p2全部插入到新链表后面,p2 =null表示添加完毕后立即终止 47 | if (p1 == null) { 48 | cur.next = p2; 49 | break; 50 | // list2取完了,直接将p1全部插入到新链表后面 51 | } else if (p2 == null) { 52 | cur.next = p1; 53 | break; 54 | // 否则只能一个个比较,将较小的插入到后面,同时指针移动 55 | } else if (p1.val < p2.val) { 56 | cur.next = p1; 57 | cur = cur.next; 58 | p1 = p1.next; 59 | } else { 60 | cur.next = p2; 61 | cur = cur.next; 62 | p2 = p2.next; 63 | } 64 | } 65 | return head; 66 | } 67 | 68 | public ListNode mergeRecur(ListNode list1,ListNode list2) { 69 | if (list1 == null) { 70 | return list2; 71 | } 72 | if (list2 == null) { 73 | return list1; 74 | } 75 | 76 | ListNode mergedHead = null; 77 | if (list1.val < list2.val) { 78 | mergedHead = list1; 79 | mergedHead.next = mergeRecur(list1.next, list2); 80 | } else { 81 | mergedHead = list2; 82 | mergedHead.next = mergeRecur(list1, list2.next); 83 | } 84 | return mergedHead; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /code/offer/src/Chap3/Numeric.java: -------------------------------------------------------------------------------- 1 | package Chap3; 2 | 3 | /** 4 | * 请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。 5 | * 例如,字符串"+100","5e2","-123","3.1416"和"-1E-16"都表示数值。 6 | * 但是"12e","1a3.14","1.2.3","+-5"和"12e+4.3"都不是。 7 | */ 8 | public class Numeric { 9 | public boolean isNumeric(char[] str) { 10 | if (str == null) { 11 | return false; 12 | } 13 | return new String(str).matches("[+-]?[0-9]*(\\.[0-9]*)?([eE][+-]?[0-9]+)?"); 14 | } 15 | 16 | public boolean isNumeric_2(char[] str) { 17 | // null或者空字符串不是数字 18 | if (str == null || str.length == 0) { 19 | return false; 20 | } 21 | // 长度只为1,必须是数0~9之间 22 | if (str.length == 1) { 23 | return str[0] >= '0' && str[0] <= '9'; 24 | } 25 | 26 | boolean hasDot = false; 27 | boolean hasE = false; 28 | boolean hasSign = false; 29 | 30 | for (int i = 0; i < str.length; i++) { 31 | if (str[i] == '+' || str[i] == '-') { 32 | // 第二次出现正负号,前一个字符必须是e或者E 33 | if (hasSign && str[i - 1] != 'e' && str[i - 1] != 'E') return false; 34 | // 第一次出现正负号且不在开头,前一个字符也必须是e或者E 35 | if (!hasSign && i > 0 && str[i - 1] != 'e' && str[i - 1] != 'E') return false; 36 | hasSign = true; 37 | } else if (str[i] == '.') { 38 | // 只能出现一次'.',e和E之后不可出现'.' 39 | if (hasDot || hasE) return false; 40 | hasDot = true; 41 | } else if (str[i] == 'e' || str[i] == 'E') { 42 | // e或E后必须有数字 43 | if (i == str.length -1) return false; 44 | // 只能有一个e或者E 45 | if (hasE) return false; 46 | hasE = true; 47 | // 最后判断如果是 +-eE.之外的字符,不匹配 48 | } else if (str[i] < '0' || str[i] > '9') { 49 | return false; 50 | } 51 | } 52 | return true; 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /code/offer/src/Chap3/Power.java: -------------------------------------------------------------------------------- 1 | package Chap3; 2 | 3 | /** 4 | * 给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。 5 | * 不得使用库函数直接实现,无需考虑大数问题。 6 | */ 7 | public class Power { 8 | /** 9 | * 容易想到的蹩脚办法 10 | * 11 | * @param base 基数 12 | * @param exponent 次幂 13 | * @return base^exponent 14 | */ 15 | public double power_2(double base, int exponent) { 16 | 17 | if (base == 0) { 18 | return 0; 19 | } 20 | 21 | double result = 1.0; 22 | int positiveExponent = Math.abs(exponent); 23 | for (int i = 0; i < positiveExponent; i++) { 24 | result *= base; 25 | } 26 | return exponent < 0 ? 1 /result : result; 27 | } 28 | 29 | /** 30 | * 非递归。推荐的做法,复杂度O(lg n) 31 | */ 32 | public double power(double base, int exponent) { 33 | if (base == 0) { 34 | return 0; 35 | } 36 | 37 | double result = 1.0; 38 | int positiveExponent = Math.abs(exponent); 39 | 40 | while (positiveExponent != 0) { 41 | // positiveExponent & 1这句是判断奇数的 42 | if ((positiveExponent & 1) == 1) { 43 | result *= base; 44 | } 45 | 46 | base *= base; 47 | // 右移1位等于除以2 48 | positiveExponent = positiveExponent >> 1; 49 | } 50 | 51 | return exponent < 0 ? 1 / result : result; 52 | } 53 | 54 | /** 55 | * 和上面是同一个思路,递归版本。 56 | */ 57 | private double powerUnsigned(double base, int exponent) { 58 | if (exponent == 0) { 59 | return 1; 60 | } 61 | if (exponent == 1) { 62 | return base; 63 | } 64 | 65 | double result = powerUnsigned(base, exponent >> 1); 66 | result *= result; 67 | if ((exponent & 1) == 1) { 68 | result *= base; 69 | } 70 | return result; 71 | } 72 | 73 | public double power_1(double base, int exponent) { 74 | if (base == 0) { 75 | return 0; 76 | } 77 | int positiveExponent = Math.abs(exponent); 78 | double result = powerUnsigned(base, positiveExponent); 79 | return exponent < 0 ? 1 / result : result; 80 | } 81 | 82 | public static void main(String[] args) { 83 | Power a = new Power(); 84 | System.out.println(a.power_2(2, -2)); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /code/offer/src/Chap3/ReMatch.java: -------------------------------------------------------------------------------- 1 | package Chap3; 2 | 3 | /** 4 | * 请实现一个函数用来匹配包括'.'和'*'的正则表达式。模式中的字符'.'表示任意一个字符,而'*'表示它前面的字符可以出现任意次(包含0次)。 5 | * 在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串"aaa"与模式"a.a"和"ab*ac*a"匹配,但是与"aa.a"和"ab*a"均不匹配 6 | */ 7 | public class ReMatch { 8 | public boolean match(char[] str, char[] pattern) 9 | { 10 | if (str == null || pattern == null) { 11 | return false; 12 | } 13 | 14 | return matchRecur(str, pattern, 0, 0); 15 | } 16 | 17 | private boolean matchRecur(char[] str, char[] pattern, int s, int p) { 18 | if (s == str.length && p == pattern.length) { 19 | return true; 20 | } 21 | // 模式串比文本串先到末尾,肯定没有匹配成功 22 | if (p == pattern.length && s < str.length) { 23 | return false; 24 | } 25 | // 两种情况,1、模式和文本都没有到结尾 26 | // 2、或者文本到了结尾而文本还没有到结尾,此时肯定会调用else分支 27 | // 第二个字符是* 28 | if (p < pattern.length-1 && pattern[p + 1] == '*') { 29 | if ((s < str.length && str[s] == pattern[p]) || (pattern[p]== '.' && s < str.length)) 30 | return matchRecur(str, pattern, s, p + 2) || 31 | matchRecur(str, pattern, s + 1, p+2) || 32 | matchRecur(str,pattern,s + 1, p); 33 | else 34 | return matchRecur(str, pattern, s, p + 2); 35 | } 36 | // 第二个字符不是* 37 | if ((s < str.length && str[s] == pattern[p]) || (pattern[p]== '.' && s < str.length)) { 38 | return matchRecur(str, pattern, s + 1, p + 1); 39 | } 40 | return false; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /code/offer/src/Chap3/ReOrderArray.java: -------------------------------------------------------------------------------- 1 | package Chap3; 2 | 3 | import java.util.Arrays; 4 | import java.util.function.Predicate; 5 | 6 | /** 7 | * 输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于位于数组的后半部分, 8 | * 并保证奇数和奇数,偶数和偶数之间的相对位置不变。 9 | */ 10 | public class ReOrderArray { 11 | public void reOrderArray(int[] array) { 12 | if (array == null || array.length == 0) { 13 | return; 14 | } 15 | int len = array.length; 16 | // 左往右扫描的指针 17 | int p = 0; 18 | // 辅助数组 19 | int[] temp = new int[len]; 20 | // 左往右扫描,只存入奇数 21 | for (int i = 0; i < len; i++) { 22 | if (isOdd(array[i])) 23 | temp[p++] = array[i]; 24 | } 25 | // 再次扫描,只存入偶数 26 | for (int i = 0; i < len; i++) { 27 | if (!isOdd(array[i])) 28 | temp[p++] = array[i]; 29 | } 30 | // 覆盖原数组 31 | for (int i = 0; i < len; i++) { 32 | array[i] = temp[i]; 33 | } 34 | } 35 | 36 | private boolean isOdd(int val) { 37 | return (val & 1) == 1; 38 | } 39 | 40 | public void reOrderArray(int[] array, Predicate p) { 41 | if (array == null || array.length == 0) { 42 | return; 43 | } 44 | int pBegin = 0; 45 | int pEnd = array.length - 1; 46 | while (pBegin < pEnd) { 47 | // 左到右找出第一个偶数 48 | while (pBegin < pEnd && p.test(array[pBegin])) 49 | pBegin++; 50 | // 右到左找出第一个奇数 51 | while (pBegin < pEnd && !p.test(array[pEnd])) 52 | pEnd--; 53 | 54 | if (pBegin < pEnd) { 55 | int temp = array[pBegin]; 56 | array[pBegin] = array[pEnd]; 57 | array[pEnd] = temp; 58 | } 59 | } 60 | } 61 | 62 | public static void main(String[] args) { 63 | ReOrderArray reOrder = new ReOrderArray(); 64 | int[] a = {3, 2, 1, 9, 8, 7, 4, 5, 6}; 65 | reOrder.reOrderArray(a, p -> (p & 1) == 1); 66 | System.out.println(Arrays.toString(a)); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /code/offer/src/Chap3/ReverseLinkedList.java: -------------------------------------------------------------------------------- 1 | package Chap3; 2 | 3 | import java.util.LinkedList; 4 | 5 | /** 6 | * 输入一个链表的头结点,反转链表后,并返回反转链表的头结点。 7 | */ 8 | public class ReverseLinkedList { 9 | private class ListNode { 10 | int val; 11 | ListNode next = null; 12 | 13 | ListNode(int val) { 14 | this.val = val; 15 | } 16 | } 17 | 18 | /** 19 | * 用栈,空间复杂度为O(N)不推荐 20 | * 21 | * @param head 头结点 22 | * @return 反转之后的链表头结点 23 | */ 24 | public ListNode reverseList_2(ListNode head) { 25 | if (head == null) { 26 | return null; 27 | } 28 | LinkedList stack = new LinkedList<>(); 29 | ListNode cur = head; 30 | while (cur != null) { 31 | stack.push(cur); 32 | cur = cur.next; 33 | } 34 | 35 | head = stack.pop(); 36 | cur = head; 37 | while (!stack.isEmpty()) { 38 | cur.next = stack.pop(); 39 | cur = cur.next; 40 | } 41 | cur.next = null; 42 | 43 | return head; 44 | } 45 | 46 | /** 47 | * 推荐,使用三个指针 48 | */ 49 | public ListNode reverseList(ListNode head) { 50 | ListNode pre = null; 51 | ListNode next = null; 52 | ListNode cur = head; 53 | while (cur != null) { 54 | next = cur.next; 55 | cur.next = pre; 56 | pre = cur; 57 | cur = next; 58 | } 59 | return pre; 60 | } 61 | 62 | /** 63 | * 递归,不太推荐。虽然很巧妙 64 | */ 65 | public ListNode reverseListRecur(ListNode head) { 66 | if (head == null || head.next == null) return head; 67 | 68 | ListNode revHead = reverseListRecur(head.next); 69 | ListNode nextNode = head.next; 70 | nextNode.next = head; 71 | head.next = null; 72 | return revHead; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /code/offer/src/Chap3/SubTree.java: -------------------------------------------------------------------------------- 1 | package Chap3; 2 | 3 | /** 4 | * 输入两棵二叉树A,B,判断B是不是A的子结构。 5 | * ps:我们约定空树不是任意一个树的子结构 6 | */ 7 | public class SubTree { 8 | 9 | private class TreeNode { 10 | int val = 0; 11 | TreeNode left = null; 12 | TreeNode right = null; 13 | 14 | public TreeNode(int val) { 15 | this.val = val; 16 | 17 | } 18 | 19 | } 20 | 21 | public boolean hasSubTree(TreeNode root1,TreeNode root2) { 22 | boolean result = false; 23 | if (root1 != null && root2 != null) { 24 | // 根结点相同,紧接着判断子树结构一样不 25 | if (root1.val == root2.val) { 26 | result = doesTree1HaveTree2(root1, root2); 27 | } 28 | // 当前根结点不同,或者即使根结点相同子树结构不同,还需继续遍历,递归判断左右子树 29 | if (!result) { 30 | result = hasSubTree(root1.left, root2); 31 | } 32 | 33 | if (!result) { 34 | result = hasSubTree(root1.right, root2); 35 | } 36 | } 37 | 38 | return result; 39 | } 40 | 41 | private boolean doesTree1HaveTree2(TreeNode node1,TreeNode node2) { 42 | // node2到达叶子结点的左右子结点了都还相等,说明是树1的子结构 43 | if (node2 == null) { 44 | return true; 45 | } 46 | // 如果node2没有到叶子结点的左右子结点,而node1先到了说明树2比树1还大,返回false 47 | if (node1 == null) { 48 | return false; 49 | } 50 | 51 | if (node1.val != node2.val) { 52 | return false; 53 | } 54 | 55 | return doesTree1HaveTree2(node1.left, node2.left) && doesTree1HaveTree2(node1.right, node2.right); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /code/offer/src/Chap4/BSTTransToDLinkedList.java: -------------------------------------------------------------------------------- 1 | package Chap4; 2 | 3 | /** 4 | * 输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。 5 | * 要求不能创建任何新的结点,只能调整树中结点指针的指向。 6 | */ 7 | public class BSTTransToDLinkedList { 8 | private class TreeNode { 9 | int val = 0; 10 | TreeNode left = null; 11 | TreeNode right = null; 12 | 13 | public TreeNode(int val) { 14 | this.val = val; 15 | 16 | } 17 | 18 | } 19 | 20 | // 当前结点的前驱 21 | private TreeNode preNode; 22 | 23 | public TreeNode Convert(TreeNode pRootOfTree) { 24 | if (pRootOfTree == null) { 25 | return null; 26 | } 27 | 28 | TreeNode root = pRootOfTree; 29 | // 得到双向链表 30 | inOrder(root); 31 | // 向左找到双向链表的头结点 32 | while (root.left != null) { 33 | root = root.left; 34 | } 35 | return root; 36 | 37 | } 38 | 39 | // 中序遍历并改变指针 40 | private void inOrder(TreeNode node) { 41 | if (node == null) return; 42 | 43 | inOrder(node.left); 44 | 45 | node.left = preNode; 46 | if (preNode != null) { 47 | preNode.right = node; 48 | } 49 | preNode = node; 50 | 51 | inOrder(node.right); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /code/offer/src/Chap4/BinaryTreeLevelOrder.java: -------------------------------------------------------------------------------- 1 | package Chap4; 2 | 3 | import java.util.ArrayList; 4 | import java.util.LinkedList; 5 | import java.util.Queue; 6 | 7 | /** 8 | * 不分行,从上往下打印出二叉树的每个节点,同层节点从左至右打印。即层序遍历 9 | */ 10 | public class BinaryTreeLevelOrder { 11 | private class TreeNode { 12 | int val = 0; 13 | TreeNode left = null; 14 | TreeNode right = null; 15 | 16 | public TreeNode(int val) { 17 | this.val = val; 18 | 19 | } 20 | 21 | } 22 | 23 | public ArrayList PrintFromTopToBottom(TreeNode root) { 24 | ArrayList list = new ArrayList<>(); 25 | 26 | if (root == null) { 27 | return list; 28 | } 29 | 30 | Queue queue = new LinkedList<>(); 31 | queue.offer(root); 32 | while (!queue.isEmpty()) { 33 | TreeNode node = queue.poll(); 34 | list.add(node.val); 35 | 36 | if (node.left != null) queue.offer(node.left); 37 | if (node.right != null) queue.offer(node.right); 38 | } 39 | return list; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /code/offer/src/Chap4/CloneLinkedList.java: -------------------------------------------------------------------------------- 1 | package Chap4; 2 | 3 | /** 4 | * 输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的head 5 | * (注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空) 6 | */ 7 | public class CloneLinkedList { 8 | private class RandomListNode { 9 | int label; 10 | RandomListNode next = null; 11 | RandomListNode random = null; 12 | 13 | RandomListNode(int label) { 14 | this.label = label; 15 | } 16 | } 17 | 18 | 19 | /** 20 | * 1、为每个结点的next插入一个和该结点的值一样的结点 21 | * 2、设置每个复制结点的random结点 22 | * 3、将链表拆分,返回复制链表的头结点 23 | * 24 | * @param pHead 原链表头结点 25 | * @return 复制链表的头结点,不可直接返回原链接结点的引用,必须使用new出来的RandomListNode 26 | */ 27 | public RandomListNode Clone(RandomListNode pHead) { 28 | if (pHead == null) return null; 29 | 30 | copyNode(pHead); 31 | setCloneRandomNode(pHead); 32 | return splitList(pHead); 33 | } 34 | 35 | // 1. 为每个结点的next插入一个和该结点的值一样的结点 36 | private void copyNode(RandomListNode head) { 37 | RandomListNode cur = head; 38 | while (cur != null) { 39 | RandomListNode cloneNode = new RandomListNode(cur.label); 40 | cloneNode.next = cur.next; 41 | cur.next = cloneNode; 42 | 43 | cur = cloneNode.next; 44 | } 45 | } 46 | 47 | // 2. 设置每个复制结点的random 48 | private void setCloneRandomNode(RandomListNode head) { 49 | RandomListNode cur = head; 50 | while (cur != null) { 51 | RandomListNode cloneNode = cur.next; 52 | if (cur.random != null) { 53 | cloneNode.random = cur.random.next; 54 | } 55 | cur = cloneNode.next; 56 | } 57 | } 58 | 59 | // 3. 拆分链表 60 | private RandomListNode splitList(RandomListNode head) { 61 | RandomListNode cur = head; 62 | RandomListNode cloneHead = cur.next; 63 | 64 | while (cur != null) { 65 | RandomListNode cloneNode = cur.next; 66 | cur.next = cur.next.next; 67 | 68 | if (cloneNode.next != null) { 69 | cloneNode.next = cloneNode.next.next; 70 | } 71 | 72 | cur = cur.next; 73 | } 74 | return cloneHead; 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /code/offer/src/Chap4/Combination.java: -------------------------------------------------------------------------------- 1 | package Chap4; 2 | 3 | import java.util.*; 4 | 5 | /** 6 | * 求字符的所有排列,允许组合中有重复元素 7 | */ 8 | public class Combination { 9 | /** 10 | * 其实就是求C(n, m) 其中n == str.length; m == num 11 | * 12 | * @param str 字符序列 13 | * @param num 选几个字符进行组合 14 | * @return C(n, m)的集合 15 | */ 16 | public List combinationAccordingToNum(String str, int num) { 17 | List list = new ArrayList<>(); 18 | if (str == null || str.length() == 0 || num > str.length()) { 19 | return list; 20 | } 21 | StringBuilder sb = new StringBuilder(); 22 | 23 | collect(str, sb, num, list); 24 | return list; 25 | } 26 | 27 | /** 28 | * 求所有组合情况 29 | */ 30 | public List combination(String str) { 31 | List list = new ArrayList<>(); 32 | if (str == null || str.length() == 0) { 33 | return list; 34 | } 35 | StringBuilder sb = new StringBuilder(); 36 | 37 | // 收集字符个数为i的组合 38 | for (int i = 1; i <= str.length(); i++) { 39 | collect(str, sb, i, list); 40 | } 41 | return list; 42 | } 43 | 44 | private void collect(String str, StringBuilder sb, int number, List list) { 45 | // 两个if顺序不可交换,否则C(n, n)不会存入到list中:即collect("", sb, 0)时,要先判断num==0存入后,再判断str.length ==0决定不再递归 46 | if (number == 0) { 47 | if (!list.contains(sb.toString())) 48 | // 字符已经去重过,无需判断 !list.contains 49 | list.add(sb.toString()); 50 | return; 51 | } 52 | 53 | // 当str为""时候直接返回,不然下一句charAt(0)就会越界 54 | if (str.length() == 0) { 55 | return; 56 | } 57 | 58 | // 公式C(n, m) = C(n-1, m-1)+ C(n-1, m) 59 | // 第一个字符是组合中的第一个字符,在剩下的n-1个字符中选m-1个字符 60 | sb.append(str.charAt(0)); // 第一个字符选中 61 | collect(str.substring(1), sb, number - 1, list); 62 | sb.deleteCharAt(sb.length() - 1); // 取消选中第一个字符 63 | // 第一个字符不是组合中的第一个字符,在剩下的n-1个字符中选m个字符 64 | collect(str.substring(1), sb, number, list); 65 | } 66 | 67 | public static void main(String[] args) { 68 | Combination c = new Combination(); 69 | System.out.println(c.combination("abcca")); 70 | System.out.println(c.combination("abc")); 71 | System.out.println(c.combinationAccordingToNum("aabbc", 2)); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /code/offer/src/Chap4/EightQueens.java: -------------------------------------------------------------------------------- 1 | package Chap4; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Arrays; 5 | import java.util.List; 6 | 7 | /** 8 | * 8X8的国际棋盘上,有8个皇后,使其不能相互攻击,即它们不能在同一行、同一列、且同一条对角 9 | */ 10 | public class EightQueens { 11 | /** 12 | * 1、保证不同行:使用一个数组表示不同行的皇后,八个皇后则int[] queens = new int[8],其中queens[i]表示位于第i行的皇后,这保证了皇后不位于同一行 13 | * 2、保证不同列:为queens[i]赋值各不相同的数值,queens[i] = j表示位于i行的皇后也位于j列,每个i赋予了不同的j值保证了不同行的皇后也不位于不同列 14 | * 3、保证在同一条对角线:如果在同一条对角线,说明正方形的行数等于列数,即当j > i时: 15 | * j - i == queens[j] -queens[i](第j行的皇后在第i行的皇后右下方);或者j - i == queens[i] -queens[j](第j行的皇后在第i行的皇后左下方) 16 | *

17 | * 这又是一个全排列的扩展问题,先求出所有的排列可能,从中排除不符合要求的摆放方法即可 18 | */ 19 | public List possibilitiesOfQueensPlaced() { 20 | // 大小为8的数组,且数值各不相同,任意排列都保证了不同行不同列 21 | int[] queens = {0, 1, 2, 3, 4, 5, 6, 7}; 22 | List list = new ArrayList<>(); 23 | 24 | PermutationExt p = new PermutationExt(); 25 | List all = p.permutation(queens); 26 | for (int[] arr : all) { 27 | if (!isLocatedSameDiagonal(arr)) list.add(arr); 28 | } 29 | return list; 30 | } 31 | 32 | /** 33 | * 检查任意两个皇后是否在同一条对角线上 34 | */ 35 | private boolean isLocatedSameDiagonal(int[] queens) { 36 | for (int i = 0; i < queens.length; i++) { 37 | for (int j = i + 1; j < queens.length; j++) { 38 | if (j - i == queens[j] - queens[i] || j - i == queens[i] - queens[j]) { 39 | return true; 40 | } 41 | } 42 | } 43 | return false; 44 | } 45 | 46 | public static void main(String[] args) { 47 | EightQueens queens = new EightQueens(); 48 | List l = queens.possibilitiesOfQueensPlaced(); 49 | System.out.println("共有" + l.size() + "种放置方法"); 50 | for (int[] arr : l) { 51 | System.out.println(Arrays.toString(arr)); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /code/offer/src/Chap4/FindPathInBT.java: -------------------------------------------------------------------------------- 1 | package Chap4; 2 | 3 | import java.util.ArrayList; 4 | 5 | public class FindPathInBST { 6 | private class TreeNode { 7 | int val = 0; 8 | TreeNode left = null; 9 | TreeNode right = null; 10 | 11 | public TreeNode(int val) { 12 | this.val = val; 13 | 14 | } 15 | 16 | } 17 | 18 | /** 19 | * @param root 二叉树根结点 20 | * @param target 目标值 21 | * @return 所有和目标值相同的路径上结点的集合 22 | */ 23 | public ArrayList> FindPath(TreeNode root,int target) { 24 | ArrayList> res = new ArrayList<>(); 25 | if(root == null) return res; 26 | ArrayList path = new ArrayList<>(); 27 | preOrder(root, target, path, res); 28 | return res; 29 | } 30 | 31 | private void preOrder(TreeNode root, int curVal,ArrayList path, ArrayList> res) { 32 | if (root == null) return; 33 | // 模拟结点进栈 34 | path.add(root.val); 35 | curVal -= root.val; 36 | // 只有在叶子结点处才判断是否和目标值相同,若相同加入列表中 37 | if (root.left == null && root.right == null) { 38 | if (curVal == 0) res.add(new ArrayList<>(path)); 39 | } 40 | preOrder(root.left, curVal, path, res); 41 | preOrder(root.right, curVal, path, res); 42 | // 模拟结点出栈 43 | path.remove(path.size() - 1); 44 | curVal += root.val; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /code/offer/src/Chap4/MinStack.java: -------------------------------------------------------------------------------- 1 | package Chap4; 2 | 3 | import java.util.LinkedList; 4 | 5 | public class MinStack { 6 | 7 | private LinkedList stack; 8 | private LinkedList minStack; 9 | 10 | /** initialize your data structure here. */ 11 | public MinStack() { 12 | stack=new LinkedList<>(); 13 | minStack = new LinkedList<>(); 14 | } 15 | 16 | public void push(int x) { 17 | stack.push(x); 18 | if (minStack.isEmpty() || x <= minStack.peek()) { 19 | minStack.push(x); 20 | } 21 | } 22 | 23 | public void pop() { 24 | if (stack.isEmpty()) { 25 | return; 26 | } 27 | if (stack.pop().equals(minStack.peek())) { 28 | minStack.pop(); 29 | } 30 | } 31 | 32 | public int top() { 33 | if (stack.isEmpty()) { 34 | return -1; 35 | } 36 | return stack.peek(); 37 | } 38 | 39 | public int min() { 40 | if (minStack.isEmpty()) { 41 | return -1; 42 | } 43 | return minStack.peek(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /code/offer/src/Chap4/MirrorTree.java: -------------------------------------------------------------------------------- 1 | package Chap4; 2 | 3 | import java.util.LinkedList; 4 | import java.util.Queue; 5 | 6 | /** 7 | * 操作给定的二叉树,将其变换为源二叉树的镜像。 8 | */ 9 | public class MirrorTree { 10 | private class TreeNode { 11 | int val = 0; 12 | TreeNode left = null; 13 | TreeNode right = null; 14 | 15 | public TreeNode(int val) { 16 | this.val = val; 17 | 18 | } 19 | 20 | } 21 | 22 | /** 23 | * 递归版本 24 | */ 25 | public void mirrorRecur(TreeNode root) { 26 | exchangeChildren(root); 27 | } 28 | 29 | private void exchangeChildren(TreeNode node) { 30 | if (node == null) { 31 | return; 32 | } 33 | if (node.left == null && node.right == null) { 34 | return; 35 | } 36 | // 交换两个子结点 37 | TreeNode temp = node.left; 38 | node.left = node.right; 39 | node.right = temp; 40 | 41 | if (node.left != null) exchangeChildren(node.left); 42 | if (node.right != null) exchangeChildren(node.right); 43 | } 44 | 45 | /** 46 | * 非递归版本,层序遍历 47 | */ 48 | public void mirror(TreeNode root) { 49 | if (root == null) { 50 | return; 51 | } 52 | 53 | Queue queue = new LinkedList<>(); 54 | queue.offer(root); 55 | while (!queue.isEmpty()) { 56 | TreeNode node = queue.poll(); 57 | // 交换两个子结点 58 | if (node.left != null || node.right != null) { 59 | TreeNode temp = node.left; 60 | node.left = node.right; 61 | node.right = temp; 62 | } 63 | if (node.left != null) queue.offer(node.left); 64 | if (node.right != null) queue.offer(node.right); 65 | } 66 | } 67 | 68 | /** 69 | * 非递归,前序遍历 70 | */ 71 | public void mirror_preOrder(TreeNode root) { 72 | LinkedList stack = new LinkedList<>(); 73 | // 当前结点不为空,或者为空但有可以返回的父结点(可以进行pop操作)都可以进入循环 74 | while (root != null || !stack.isEmpty()) { 75 | while (root != null) { 76 | stack.push(root); 77 | // 交换两个子结点 78 | if (root.left != null || root.right != null) { 79 | TreeNode temp = root.left; 80 | root.left = root.right; 81 | root.right = temp; 82 | } 83 | root = root.left; 84 | } 85 | 86 | if (!stack.isEmpty()) { 87 | root = stack.pop(); 88 | root = root.right; 89 | } 90 | } 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /code/offer/src/Chap4/Permutation.java: -------------------------------------------------------------------------------- 1 | package Chap4; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collections; 5 | 6 | /** 7 | * 输入一个字符串,按字典序打印出该字符串中字符的所有全排列。 8 | * 例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。 9 | *

10 | * 通用的根据输入字符串得到全排列的程序,排序是为了保证字典序 11 | */ 12 | public class Permutation { 13 | public ArrayList permutation(String str) { 14 | ArrayList list = new ArrayList<>(); 15 | if (str == null || str.length() == 0) { 16 | return list; 17 | } 18 | 19 | collect(str.toCharArray(), 0, list); 20 | Collections.sort(list); 21 | return list; 22 | } 23 | 24 | private void collect(char[] chars, int begin, ArrayList list) { 25 | if (begin == chars.length - 1) { 26 | // 不能存入相同的排列 27 | String s = String.valueOf(chars); 28 | if (!list.contains(s)) { 29 | list.add(s); 30 | return; 31 | } 32 | } 33 | 34 | for (int i = begin; i < chars.length; i++) { 35 | swap(chars, i, begin); 36 | collect(chars, begin + 1, list); 37 | swap(chars, i, begin); 38 | } 39 | 40 | } 41 | 42 | private void swap(char[] chars, int i, int j) { 43 | char temp = chars[j]; 44 | chars[j] = chars[i]; 45 | chars[i] = temp; 46 | } 47 | 48 | public static void main(String[] args) { 49 | Permutation a = new Permutation(); 50 | System.out.println(a.permutation("aab")); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /code/offer/src/Chap4/PermutationExt.java: -------------------------------------------------------------------------------- 1 | package Chap4; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Arrays; 5 | import java.util.List; 6 | 7 | /** 8 | * 通用的根据数组输入得到全排列的程序 9 | */ 10 | public class PermutationExt { 11 | /** 12 | * 应用排列解决问题1:输入一个含有8个数字的数组, 13 | * 判断有没有可能吧这8个数字分别放到正方体的8个顶点,使得正方体三对相对的面上的4个顶点的和相等 14 | */ 15 | public List possibilitiesOfCube(int[] array) { 16 | List list = new ArrayList<>(); 17 | if (array == null || array.length == 0) { 18 | return list; 19 | } 20 | 21 | // 得到全排列集合 22 | List all = permutation(array); 23 | // 筛选:满足三个对立面的值相等才会被加入最终结果集中 24 | for (int[] arr : all) { 25 | if (checkSum(arr)) list.add(arr); 26 | } 27 | return list; 28 | } 29 | 30 | public List permutation(int[] array) { 31 | List list = new ArrayList<>(); 32 | collect(array, 0, list); 33 | return list; 34 | } 35 | 36 | private void collect(int[] array, int begin, List list) { 37 | if (begin == array.length - 1) { 38 | // list中没有同样的序列 39 | if (!has(list, array)) { 40 | // 必须使用副本,不能直接传入引用,否则list所有的int[]对象最后都一样 41 | list.add(Arrays.copyOf(array, array.length)); 42 | return; 43 | } 44 | } 45 | 46 | for (int i = begin; i < array.length; i++) { 47 | swap(array, i, begin); 48 | collect(array, begin + 1, list); 49 | swap(array, i, begin); 50 | } 51 | } 52 | 53 | private boolean checkSum(int[] array) { 54 | if ((array[0] + array[2] + array[4] + array[6] == array[1] + array[3] + array[5] + array[7]) && 55 | (array[0] + array[1] + array[2] + array[3] == array[4] + array[5] + array[6] + array[7]) && 56 | (array[2] + array[3] + array[6] + array[7] == array[0] + array[1] + array[4] + array[5])) { 57 | return true; 58 | } 59 | return false; 60 | } 61 | 62 | private boolean has(List list, int[] a) { 63 | for (int[] arr : list) { 64 | if (equal(arr, a)) return true; 65 | } 66 | 67 | return false; 68 | } 69 | 70 | private boolean equal(int[] a, int[] b) { 71 | for (int i = 0; i < a.length; i++) { 72 | if (a[i] != b[i]) return false; 73 | } 74 | return true; 75 | } 76 | 77 | private void swap(int[] array, int i, int j) { 78 | int temp = array[i]; 79 | array[i] = array[j]; 80 | array[j] = temp; 81 | } 82 | 83 | public static void main(String[] args) { 84 | PermutationExt p = new PermutationExt(); 85 | int[] a = {8, 3, 5, 4, 1, 2, 5, 6}; 86 | List list = p.possibilitiesOfCube(a); 87 | System.out.println("有" + list.size() + "种可能"); 88 | for (int[] arr : list) { 89 | System.out.println(Arrays.toString(arr)); 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /code/offer/src/Chap4/PrintMatrix.java: -------------------------------------------------------------------------------- 1 | package Chap4; 2 | 3 | import java.util.ArrayList; 4 | 5 | /** 6 | * 输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字 7 | */ 8 | public class PrintMatrix { 9 | 10 | public ArrayList printMatrix(int[][] matrix) { 11 | if (matrix == null || matrix.length == 0) { 12 | return null; 13 | } 14 | 15 | ArrayList list = new ArrayList<>(); 16 | int left = 0; 17 | int right = matrix[0].length - 1; 18 | int top = 0; 19 | int bottom = matrix.length - 1; 20 | 21 | while (left <= right && top <= bottom) { 22 | // 从左往右,有一行即可 23 | for (int col = left; col <= right; col++) list.add(matrix[top][col]); 24 | // 从上往下,保证至少有两行 25 | if (top < bottom) { 26 | for (int row = top + 1; row <= bottom; row++) list.add(matrix[row][right]); 27 | } 28 | // 从右往左,至少两行两列 29 | if (top < bottom && left < right) { 30 | for (int col = right - 1; col >= left; col--) list.add(matrix[bottom][col]); 31 | } 32 | // 从下往上,保证至少三行两列 33 | if (top < bottom - 1 && left < right) { 34 | for (int row = bottom - 1; row > top; row--) list.add(matrix[row][left]); 35 | } 36 | // 缩小矩形 37 | left++; 38 | right--; 39 | top++; 40 | bottom--; 41 | } 42 | return list; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /code/offer/src/Chap4/PrintTreeEveryLayer.java: -------------------------------------------------------------------------------- 1 | package Chap4; 2 | 3 | import java.util.LinkedList; 4 | import java.util.Queue; 5 | 6 | /** 7 | * 逐层打印二叉树,即打印完一层后要有换行符 8 | */ 9 | public class PrintTreeEveryLayer { 10 | private class TreeNode { 11 | int val = 0; 12 | TreeNode left = null; 13 | TreeNode right = null; 14 | 15 | public TreeNode(int val) { 16 | this.val = val; 17 | 18 | } 19 | 20 | } 21 | 22 | public void printEveryLayer2(TreeNode root) { 23 | if (root == null) { 24 | return; 25 | } 26 | 27 | Queue queue = new LinkedList<>(); 28 | queue.offer(root); 29 | // 下一层的结点数 30 | int nextLevel = 0; 31 | // 本层还有多少结点未打印 32 | int toBePrinted = 1; 33 | while (!queue.isEmpty()) { 34 | TreeNode node = queue.poll(); 35 | System.out.print(node.val + " "); 36 | // 每打印一个,减1 37 | toBePrinted--; 38 | // 每一个元素加入队列,加1 39 | if (node.left != null) { 40 | queue.offer(node.left); 41 | nextLevel++; 42 | } 43 | 44 | if (node.right != null) { 45 | queue.offer(node.right); 46 | nextLevel++; 47 | } 48 | // 本层打印完毕 49 | if (toBePrinted == 0) { 50 | System.out.println(); 51 | toBePrinted = nextLevel; 52 | nextLevel = 0; 53 | } 54 | } 55 | } 56 | // 也是分行打印,比上面简洁 57 | public void printEveryLayer(TreeNode root) { 58 | if (root == null) return; 59 | Queue queue = new LinkedList<>(); 60 | queue.offer(root); 61 | while (!queue.isEmpty()) { 62 | int layerSize = queue.size(); 63 | for (int i = 0; i < layerSize; i++) { 64 | TreeNode node = queue.poll(); 65 | System.out.println(node.val+" "); 66 | if (node.left != null) queue.offer(node.left); 67 | if (node.right != null) queue.offer(node.right); 68 | } 69 | System.out.println(); 70 | } 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /code/offer/src/Chap4/PrintTreeZ.java: -------------------------------------------------------------------------------- 1 | package Chap4; 2 | 3 | import java.util.ArrayList; 4 | import java.util.LinkedList; 5 | 6 | /** 7 | * 按照Z字形打印二叉树,即先打印根结点,然后从右往左打印第二层,从左往右打印第三层...以此类推 8 | */ 9 | public class PrintTreeZ { 10 | private class TreeNode { 11 | int val = 0; 12 | TreeNode left = null; 13 | TreeNode right = null; 14 | 15 | public TreeNode(int val) { 16 | this.val = val; 17 | 18 | } 19 | 20 | } 21 | 22 | public void printTreeZ(TreeNode root) { 23 | if (root == null) return; 24 | LinkedList stackOdd = new LinkedList<>(); 25 | LinkedList stackEven = new LinkedList<>(); 26 | stackOdd.push(root); 27 | // 只要还有一个栈不为空就继续 28 | while (!stackOdd.isEmpty() || !stackEven.isEmpty()) { 29 | if (!stackOdd.isEmpty()) { 30 | while (!stackOdd.isEmpty()) { 31 | TreeNode node = stackOdd.pop(); 32 | System.out.println((node.val + " ")); 33 | if (node.left != null) stackEven.push(node.left); 34 | if (node.right != null) stackEven.push(node.right); 35 | } 36 | } else { 37 | while (!stackEven.isEmpty()) { 38 | TreeNode node = stackEven.pop(); 39 | System.out.println((node.val + " ")); 40 | if (node.right != null) stackOdd.push(node.right); 41 | if (node.left != null) stackOdd.push(node.left); 42 | } 43 | } 44 | System.out.println(); 45 | } 46 | } 47 | 48 | public ArrayList> print(TreeNode root) { 49 | ArrayList> list = new ArrayList<>(); 50 | if (root == null) { 51 | return list; 52 | } 53 | 54 | LinkedList stackOdd = new LinkedList<>(); 55 | LinkedList stackEven = new LinkedList<>(); 56 | stackOdd.push(root); 57 | // 只要还有一个栈不为空就继续 58 | while (!stackOdd.isEmpty() || !stackEven.isEmpty()) { 59 | ArrayList layerList = new ArrayList<>(); 60 | if (!stackOdd.isEmpty()) { 61 | while (!stackOdd.isEmpty()) { 62 | TreeNode node = stackOdd.pop(); 63 | layerList.add(node.val); 64 | if (node.left != null) stackEven.push(node.left); 65 | if (node.right != null) stackEven.push(node.right); 66 | } 67 | } else { 68 | while (!stackEven.isEmpty()) { 69 | TreeNode node = stackEven.pop(); 70 | layerList.add(node.val); 71 | if (node.right != null) stackOdd.push(node.right); 72 | if (node.left != null) stackOdd.push(node.left); 73 | } 74 | } 75 | list.add(layerList); 76 | } 77 | return list; 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /code/offer/src/Chap4/SerializeBT.java: -------------------------------------------------------------------------------- 1 | package Chap4; 2 | 3 | /** 4 | * 请实现两个函数,分别用来序列化和反序列化二叉树 5 | */ 6 | public class SerializeBT { 7 | 8 | private class TreeNode { 9 | int val = 0; 10 | TreeNode left = null; 11 | TreeNode right = null; 12 | 13 | public TreeNode(int val) { 14 | this.val = val; 15 | 16 | } 17 | 18 | } 19 | 20 | /* 21 | * 刚开始想法太死板了,只记得中序和前序两个序列才能决定一棵唯一的二叉树,于是分别进行了前序、中序遍历,中序和前序的序列用"|"分隔,之后根据分隔符分成前序和中序序列,最后采用面试题7————重建二叉树的思路进行反序列化 22 | * 其实遇到空指针可以也用一个字符表示,比如#,这样前序遍历序列就可以表示唯一的一棵二叉树了。 23 | * 而二叉树的建立,必须先要建立根结点再建立左右子树(root为空怎么调用root.left是吧),所以必须前序建立二叉树,那么序列化时也应该用前序遍历,保证了根结点在序列前面 24 | * 25 | * 不能使用中序遍历,因为中序扩展序列是一个无效的序列,比如 26 | * A B 27 | * / \ \ 28 | * B C 和 A 中序扩展序列都是 #B#A#C# 29 | * \ 30 | * C 31 | */ 32 | 33 | // 结点值用String[] seq保存,index是seq的索引 34 | private int index = -1; 35 | 36 | public String Serialize(TreeNode root) { 37 | if (root == null) { 38 | return null; 39 | } 40 | StringBuilder sb = new StringBuilder(); 41 | preOrder(root, sb); 42 | return sb.toString(); 43 | 44 | } 45 | 46 | public TreeNode Deserialize(String str) { 47 | if (str == null) { 48 | return null; 49 | } 50 | 51 | String[] seq = str.split("\\s"); 52 | 53 | return reconstructBST(seq); 54 | 55 | } 56 | 57 | private TreeNode reconstructBST(String[] seq) { 58 | ++index; 59 | if (seq[index].equals("#")) return null; 60 | 61 | TreeNode root = new TreeNode(Integer.parseInt(seq[index])); 62 | root.left = reconstructBST(seq); 63 | root.right = reconstructBST(seq); 64 | return root; 65 | } 66 | 67 | private void preOrder(TreeNode node, StringBuilder sb) { 68 | if (node == null) { 69 | sb.append("# "); 70 | return; 71 | } 72 | sb.append(node.val).append(" "); 73 | preOrder(node.left, sb); 74 | preOrder(node.right, sb); 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /code/offer/src/Chap4/StackIncludeFuncMin.java: -------------------------------------------------------------------------------- 1 | package Chap4; 2 | 3 | import java.util.LinkedList; 4 | 5 | /** 6 | * 实现栈的数据结构,包含min方法可以以O(1)的时间复杂度获得栈中的最小值 7 | */ 8 | public class StackIncludeFuncMin { 9 | private LinkedList stack = new LinkedList<>(); 10 | // 辅助栈,用于存储当前最小值 11 | private LinkedList stackMin = new LinkedList<>(); 12 | 13 | public void push(int node) { 14 | stack.push(node); 15 | if (stackMin.isEmpty() || node < stackMin.peek()) { 16 | stackMin.push(node); 17 | } else { 18 | stackMin.push(stackMin.peek()); 19 | } 20 | } 21 | 22 | public void pop() { 23 | if (stack.isEmpty()) { 24 | throw new RuntimeException("stack is empty!"); 25 | } 26 | stack.pop(); 27 | stackMin.pop(); 28 | } 29 | 30 | public int top() { 31 | if (stack.isEmpty()) { 32 | throw new RuntimeException("stack is empty!"); 33 | } 34 | return stack.peek(); 35 | } 36 | 37 | public int min() { 38 | if (stackMin.isEmpty()) { 39 | throw new RuntimeException("stack is empty!"); 40 | } 41 | return stackMin.peek(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /code/offer/src/Chap4/StackPopOrder.java: -------------------------------------------------------------------------------- 1 | package Chap4; 2 | 3 | import java.util.LinkedList; 4 | 5 | /** 6 | * 输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。 7 | * 假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列 8 | * (注意:这两个序列的长度是相等的) 9 | */ 10 | public class StackPopOrder { 11 | 12 | public boolean IsPopOrder(int [] pushA,int [] popA) { 13 | if (pushA == null || popA == null || pushA.length == 0 || popA.length == 0) { 14 | return false; 15 | } 16 | // 辅助栈 17 | LinkedList stackAux = new LinkedList<>(); 18 | int popIndex = 0; 19 | for (int i = 0;i < pushA.length;i++) { 20 | // 按照入栈序列依次压入辅助栈中 21 | stackAux.push(pushA[i]); 22 | // 每入栈一次和出栈序列比较,如果栈顶和当前出栈元素相同,则弹出同时当前弹出元素指针前移; 23 | // 如果下一个栈顶元素还和当前弹出元素相同,继续弹出 24 | while (!stackAux.isEmpty() && stackAux.peek() == popA[popIndex]) { 25 | stackAux.pop(); 26 | popIndex++; 27 | } 28 | } 29 | // 如果出栈顺序正确,模拟一次进出栈后,辅助栈应该为空。不为空说明序列不正确 30 | return stackAux.isEmpty(); 31 | } 32 | } -------------------------------------------------------------------------------- /code/offer/src/Chap4/SymmetricalTree.java: -------------------------------------------------------------------------------- 1 | package Chap4; 2 | 3 | import java.util.LinkedList; 4 | import java.util.Queue; 5 | 6 | /** 7 | * 请实现一个函数,用来判断一颗二叉树是不是对称的。 8 | * 注意,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的。 9 | */ 10 | public class SymmetricalTree { 11 | public class TreeNode { 12 | int val = 0; 13 | TreeNode left = null; 14 | TreeNode right = null; 15 | 16 | public TreeNode(int val) { 17 | this.val = val; 18 | 19 | } 20 | 21 | } 22 | 23 | /** 24 | * 非递归,队列实现(栈也可以实现) 25 | */ 26 | public boolean isSymmetrical(TreeNode pRoot) { 27 | if (pRoot == null) return true; 28 | 29 | Queue queueA = new LinkedList<>(); 30 | Queue queueB = new LinkedList<>(); 31 | queueA.offer(pRoot.left); 32 | queueB.offer(pRoot.right); 33 | TreeNode left = null; 34 | TreeNode right = null; 35 | 36 | while (!queueA.isEmpty() && !queueB.isEmpty()) { 37 | left = queueA.poll(); 38 | right = queueB.poll(); 39 | // 两个都空跳过 40 | if (left == null && right == null) continue; 41 | // 只有一个空,不对称 42 | if (left == null || right == null) return false; 43 | // 两个都不空,比较值 44 | if (left.val != right.val) return false; 45 | // 两两对称的加入 46 | // 左孩子的左孩子,右孩子的右孩子 47 | queueA.offer(left.left); 48 | queueB.offer(right.right); 49 | // 左孩子的右孩子,右孩子的左孩子 50 | queueA.offer(left.right); 51 | queueB.offer(right.left); 52 | } 53 | return true; 54 | } 55 | 56 | /** 57 | * 递归实现 58 | */ 59 | public boolean isSymmetricalRecur(TreeNode pRoot) { 60 | if (pRoot == null) return true; 61 | return isSymmetrical(pRoot.left, pRoot.right); 62 | } 63 | 64 | private boolean isSymmetrical(TreeNode root1, TreeNode root2) { 65 | if (root1 == null && root2 == null) return true; 66 | if (root1 == null || root2 == null) return false; 67 | if (root1.val != root2.val) return false; 68 | 69 | return isSymmetrical(root1.left, root2.right) 70 | && isSymmetrical(root1.right, root2.left); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /code/offer/src/Chap4/VeritySeqOfSearchBST.java: -------------------------------------------------------------------------------- 1 | package Chap4; 2 | 3 | /** 4 | * 输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。假设输入的数组的任意两个数字都互不相同。 5 | */ 6 | public class VeritySeqOfSearchBST { 7 | public boolean verifySquenceOfBST(int[] sequence) { 8 | if (sequence == null || sequence.length == 0) return false; 9 | return isSearchBST(sequence, 0, sequence.length - 1); 10 | } 11 | 12 | private boolean isSearchBST(int[] seq, int begin, int end) { 13 | // begin比end大说明上层结点没有左子树或者右子树,begin == end说明该本层结点没有子树,无需比较了 14 | // 这两种情况都应该返回true 15 | if (begin >= end) return true; 16 | 17 | int rootVal = seq[end]; 18 | int i = begin; 19 | // 左子树都比root小 20 | while (i < end && seq[i] < rootVal) { 21 | i++; 22 | } 23 | // 找到了左右子树的分界,[begin, boundary-1]为左子树,[boundary, end -1]是右子树 24 | int boundary = i; 25 | while (i < end) { 26 | // 右子树中还存在比root小的说明不是二叉搜索树 27 | if (seq[i] < rootVal) return false; 28 | i++; 29 | } 30 | // 左右子树必须同时都是二叉搜索树 31 | return isSearchBST(seq, begin, boundary - 1) && isSearchBST(seq, boundary, end - 1); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /code/offer/src/Chap5/ADigitInNumberSeq.java: -------------------------------------------------------------------------------- 1 | package Chap5; 2 | 3 | /** 4 | * 数字以0123456789101112131415....的格式序列化得到一个字符序列中,在这个序列中,第5位(从0开始计数)是5,第13位是1,第19位是4,等等。 5 | * 请写一个函数,求任意第n位对应的数字 6 | */ 7 | public class ADigitInNumberSeq { 8 | /** 9 | * 方法1:逐个列举 10 | */ 11 | public int numAtSeq(int index) { 12 | if (index < 0) return -1; 13 | 14 | int i = 0; 15 | int sum = 0; 16 | while (true) { 17 | sum += countDigits(i); 18 | // 一定不要包含= 19 | if (sum > index) break; 20 | i++; 21 | } 22 | // sum - n是超出的部分,减去1是因为下标从0开始 23 | return digitAt(i, sum - index - 1); 24 | } 25 | 26 | /** 27 | * 返回某数的第d位, 第0位是个位,第1位是十位,以此类推 28 | */ 29 | private int digitAt(int value, int d) { 30 | return (value / (int) Math.pow(10, d)) % 10; 31 | } 32 | 33 | /** 34 | * 计算某数有多少位 35 | */ 36 | private int countDigits(int num) { 37 | if (num == 0) return 1; 38 | 39 | int count = 0; 40 | while (num != 0) { 41 | num /= 10; 42 | count++; 43 | } 44 | return count; 45 | } 46 | 47 | /***************************************** 48 | * 方法2 49 | */ 50 | public int numAtSeq2(int index) { 51 | if (index < 0) return -1; 52 | // 位数,digits = 1表示一位数,0-9区间;digits = 2表示两位数,10-99区间... 53 | int digits = 1; 54 | while (true) { 55 | int numbers = numOfRange(digits); 56 | if (index < numbers * digits) { 57 | return digitAt2(index, digits); 58 | } 59 | index -= numbers * digits; 60 | digits++; 61 | } 62 | } 63 | 64 | /** 65 | * 根据位数得到范围内的个数,比如1位,0~9共10个 66 | * 2位,10~99共90个 67 | * 3位,100~999共900个 68 | * ... 69 | */ 70 | private int numOfRange(int n) { 71 | if (n == 1) return 10; 72 | 73 | return (int) (9 * Math.pow(10, n - 1)); 74 | } 75 | 76 | /** 77 | * n位数范围内的的第一个数,比如1位数,0~9,第一个是0 78 | * 2位数,10~99,第一个数是10 79 | * 3位数,100~199,第一个数是100 80 | */ 81 | private int beginNumber(int n) { 82 | if (n == 1) return 0; 83 | return (int) Math.pow(10, n - 1); 84 | } 85 | 86 | private int digitAt2(int seqIndex, int digits) { 87 | int number = beginNumber(digits) + seqIndex / digits; 88 | return digitAt(number, digits - seqIndex % digits - 1); 89 | } 90 | 91 | public static void main(String[] args) { 92 | ADigitInNumberSeq a = new ADigitInNumberSeq(); 93 | System.out.println(a.numAtSeq(1001)); 94 | System.out.println(a.numAtSeq2(1001)); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /code/offer/src/Chap5/AppearOnceInStream.java: -------------------------------------------------------------------------------- 1 | package Chap5; 2 | 3 | public class AppearOnceInStream { 4 | private int[] count; 5 | private int index; 6 | public AppearOnceInStream() { 7 | count = new int[256]; 8 | for (int i = 0; i < count.length; i++) { 9 | count[i] = -1; 10 | } 11 | } 12 | 13 | public void insert(char c) { 14 | if (count[c] == -1) count[c] = index; 15 | else if (count[c] >= 0) count[c] = -2; 16 | index++; 17 | } 18 | 19 | public char firstAppearOnceChar() { 20 | int minIndex = Integer.MAX_VALUE; 21 | char c = '\0'; 22 | for (int i = 0; i < count.length; i++) { 23 | if (count[i] >= 0 && count[i] < minIndex) { 24 | minIndex = count[i]; 25 | c = (char)i; 26 | } 27 | } 28 | return c; 29 | } 30 | 31 | public static void main(String[] args) { 32 | AppearOnceInStream a = new AppearOnceInStream(); 33 | a.insert('g'); 34 | a.insert('o'); 35 | System.out.println(a.firstAppearOnceChar()); 36 | a.insert('o'); 37 | a.insert('g'); 38 | a.insert('l'); 39 | a.insert('e'); 40 | System.out.println(a.firstAppearOnceChar()); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /code/offer/src/Chap5/FindGreatestSumOfSubArray.java: -------------------------------------------------------------------------------- 1 | package Chap5; 2 | 3 | /** 4 | * 输入一个整型数组,数组里正负数都可能有,数组中的一个或者连续的多个整数组成一个子数组。 5 | * 求所有子数组的和的最大值,要求时间复杂度为O(n) 6 | */ 7 | public class FindGreatestSumOfSubArray { 8 | public int findGreatestSumOfSubArray(int[] array) { 9 | if (array == null || array.length == 0) return 0; 10 | 11 | int maxSum = array[0]; 12 | int curSum = array[0]; 13 | for (int i = 1; i < array.length; i++) { 14 | if (curSum < 0) curSum = array[i]; 15 | else curSum += array[i]; 16 | if (curSum > maxSum) maxSum = curSum; 17 | } 18 | return maxSum; 19 | } 20 | 21 | /** 22 | * 动态规划,其实和上面是一样的代码... 23 | * @param array 24 | * @return 25 | */ 26 | public int FindGreatestSumOfSubArray2(int[] array) { 27 | if (array == null || array.length == 0) return 0; 28 | 29 | int maxSum = array[0]; 30 | int curSum = array[0]; 31 | for (int i = 1; i < array.length; i++) { 32 | // if (curSum + array[i] < array[i]),也就是if (curSum < 0) 则curSum的结果是array[i] 33 | // 否则curSum的值是curSum + array[i] 34 | curSum = Math.max(curSum + array[i], array[i]); 35 | // 如果curSum > maxSum,则maxSum取curSum,否则maxSum = maxSum 36 | maxSum = Math.max(curSum, maxSum); 37 | } 38 | return maxSum; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /code/offer/src/Chap5/InversePairs.java: -------------------------------------------------------------------------------- 1 | package Chap5; 2 | 3 | /** 4 | * 在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。 5 | * 输入一个数组,求出这个数组中的逆序对的总数 6 | */ 7 | public class InversePairs { 8 | public int inversePairs(int[] array) { 9 | if (array == null) return 0; 10 | int[] aux = new int[array.length]; 11 | return sort(array, aux, 0, array.length - 1); 12 | } 13 | 14 | private int sort(int[] array, int[] aux, int low, int high) { 15 | if (high <= low) return 0; 16 | int mid = low + (high - low) / 2; 17 | int left = sort(array, aux, low, mid); 18 | int right = sort(array, aux, mid + 1, high); 19 | int merged = merge(array, aux, low, mid, high); 20 | return left + right + merged; 21 | } 22 | 23 | private int merge(int[] array, int[] aux, int low, int mid, int high) { 24 | int count = 0; 25 | int len = (high - low) / 2; 26 | int i = mid; 27 | int j = high; 28 | 29 | for (int k = low; k <= high; k++) { 30 | aux[k] = array[k]; 31 | } 32 | 33 | for (int k = high; k >= low; k--) { 34 | if (i < low) array[k] = aux[j--]; 35 | else if (j < mid + 1) array[k] = aux[i--]; 36 | else if (aux[i] > aux[j]) { 37 | // 在归并排序的基础上,在这里求count 38 | count += j - low - len; 39 | array[k] = aux[i--]; 40 | } else array[k] = aux[j--]; 41 | } 42 | return count; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /code/offer/src/Chap5/LongestSubstring.java: -------------------------------------------------------------------------------- 1 | package Chap5; 2 | 3 | /** 4 | * 请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子字符串的长度。 5 | * 假设字符串中只包含'a'~'z'之间的字符,例如在字符串"arabcacfr"中,最长的不含重复字符的子字符串是"acfr",长度为4 6 | */ 7 | public class LongestSubstring { 8 | public int lengthOfLongestSubstring(String str) { 9 | if (str == null || str.length() == 0) return 0; 10 | int curLen = 0; 11 | int maxLen = 0; 12 | // 0~25表示a~z,position[0] = index,表明a上次出现在index处 13 | int[] position = new int[256]; 14 | for (int i = 0; i < 256; i++) { 15 | position[i] = -1; 16 | } 17 | 18 | for (int i = 0; i < str.length(); i++) { 19 | int preIndex = position[str.charAt(i)]; 20 | // 字符第一次出现,或者d > f(i -1) 21 | if (preIndex == -1 || i - preIndex > curLen) curLen++; 22 | // d <= f(i -1) 23 | else { 24 | curLen = i - preIndex; 25 | } 26 | // 记录当前字符出现的位置 27 | position[str.charAt(i)] = i; 28 | if (curLen > maxLen) maxLen = curLen; 29 | } 30 | return maxLen; 31 | 32 | } 33 | 34 | public static void main(String[] args) { 35 | LongestSubstring l = new LongestSubstring(); 36 | System.out.println(l.lengthOfLongestSubstring("abcdefaz")); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /code/offer/src/Chap5/MaxGiftVal.java: -------------------------------------------------------------------------------- 1 | package Chap5; 2 | 3 | /** 4 | * 在一个mxn的棋盘的每一格斗放油一个礼物,每个礼物都有一定的价值(大于0) 5 | * 从棋盘的左上角开始,每次可以往右边或者下边移动一格,知道到达棋盘的右下角。 6 | * 给定一个棋盘和上面的礼物,计算我们最多可以拿到多少价值的礼物 7 | */ 8 | public class MaxGiftVal { 9 | /** 10 | * 方法一:递归,两个方向的深度优先搜索,用一个对象数组保存最大值(只需一个长度) 11 | */ 12 | public int getMax(int[] gifts, int rows, int cols) { 13 | if (gifts == null || gifts.length == 0) return 0; 14 | int[] max = {0}; 15 | select(gifts, 0, 0, rows, cols, 0, max); 16 | return max[0]; 17 | } 18 | 19 | private void select(int[] gifts, int row, int col, int rows, int cols, int val, int[] max) { 20 | if (row >= rows || col >= cols) return; 21 | // 一维数组表示,对应着二维数组中的array[row][col] 22 | val += gifts[row * cols + col]; 23 | // 到达右下角,和max比较 24 | if (row == rows - 1 && col == cols - 1) { 25 | if (val > max[0]) max[0] = val; 26 | } 27 | select(gifts, row + 1, col, rows, cols, val, max); 28 | select(gifts, row, col + 1, rows, cols, val, max); 29 | } 30 | 31 | /** 32 | * 方法2:动态规划,到达f(i,j)处拥有的礼物价值和有两种情况: 33 | * 1、从左边来,即f(i, j) = f(i, j -1) + gift(i, j) 34 | * 2、从上边来,即f(i, j) = f(i -1, j) + gift(i, j) 35 | * 36 | * 保证到达每一个格子得到的礼物价值之和都是最大的,也就是取max[f(i, j-1), f(i-1, j)] +gift(i, j) 37 | * 可以发现,要知道当前格子能获得最大礼物价值,需要用到当前格子左边一个和上面一个格子的最大礼物价值和 38 | */ 39 | 40 | public int getMaxVal(int[] gifts, int rows, int cols) { 41 | if (gifts == null || gifts.length == 0) return 0; 42 | int[][] maxVal = new int[rows][cols]; 43 | for (int row = 0; row < rows; row++) { 44 | for (int col = 0; col < cols; col++) { 45 | int left = 0; 46 | int up = 0; 47 | if (row > 0) up = maxVal[row -1][col]; 48 | if (col > 0) left = maxVal[row][col -1]; 49 | maxVal[row][col] = Math.max(up, left) + gifts[row *cols+col]; 50 | } 51 | } 52 | return maxVal[rows-1][cols-1]; 53 | } 54 | // 上面动态方法的优化,将二位数阿奴换成一维数组 55 | public int betterGetMaxVal(int[] gifts, int rows, int cols) { 56 | if (gifts == null || gifts.length == 0) return 0; 57 | int[] maxVal = new int[cols]; 58 | for (int row = 0; row < rows; row++) { 59 | for (int col = 0; col < cols; col++) { 60 | int left = 0; 61 | int up = 0; 62 | if (row > 0) up = maxVal[col]; 63 | if (col > 0) left = maxVal[col -1]; 64 | maxVal[col] = Math.max(up, left) + gifts[row *cols+col]; 65 | } 66 | } 67 | return maxVal[cols-1]; 68 | } 69 | 70 | public static void main(String[] args) { 71 | MaxGiftVal m = new MaxGiftVal(); 72 | int[] gifts = {1, 10, 43, 23, 12, 2, 9, 6, 15, 7, 14, 1, 3, 7, 6, 5}; 73 | System.out.println(m.getMax(gifts, 4, 4)); 74 | System.out.println(m.getMaxVal(gifts, 4, 4)); 75 | System.out.println(m.betterGetMaxVal(gifts, 4, 4)); 76 | 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /code/offer/src/Chap5/MedianInStream.java: -------------------------------------------------------------------------------- 1 | package Chap5; 2 | 3 | import java.util.PriorityQueue; 4 | import java.util.Comparator; 5 | 6 | /** 7 | * 如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。 8 | */ 9 | public class MedianInStream { 10 | 11 | private PriorityQueue maxPQ = new PriorityQueue<>(Comparator.reverseOrder()); 12 | private PriorityQueue minPQ = new PriorityQueue<>(); 13 | private int count; 14 | 15 | public void Insert(Integer num) { 16 | if (count == 0) { 17 | maxPQ.offer(num); 18 | // 当前数据流为奇数个时,存入最小堆中 19 | } else if ((count & 1) == 1) { 20 | // 如果要存入最小堆的元素比最大堆的最大元素小,将不能保证最小堆的最小元素大于最大堆的最大元素 21 | // 此时需要将最大堆的最大元素给最小堆,然后将这个元素存入最大堆中 22 | if (num < maxPQ.peek()) { 23 | minPQ.offer(maxPQ.poll()); 24 | maxPQ.offer(num); 25 | } else { 26 | minPQ.offer(num); 27 | } 28 | // 当前数据流为偶数个时,存入最大堆 29 | } else if ((count & 1) == 0) { 30 | // 如果要存入最大堆的元素比最小堆的最小元素大,将不能保证最小堆的最小元素大于最大堆的最大元素 31 | // 此时需要将最小堆的最小元素给最大堆,然后将这个元素存入最小堆中 32 | if (num > minPQ.peek()) { 33 | maxPQ.offer(minPQ.poll()); 34 | minPQ.offer(num); 35 | } else { 36 | maxPQ.offer(num); 37 | } 38 | } 39 | count++; 40 | } 41 | 42 | public Double GetMedian() { 43 | // 当数据流读个数为奇数时,最大堆的元素个数比最小堆多1,因此中位数在最大堆中 44 | if ((count & 1) == 1) return Double.valueOf(maxPQ.peek()); 45 | // 当数据流个数为偶数时,最大堆和最小堆的元素个数一样,两个堆的元素都要用到 46 | return Double.valueOf((maxPQ.peek() + minPQ.peek())) / 2; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /code/offer/src/Chap5/MoreThanHalf.java: -------------------------------------------------------------------------------- 1 | package Chap5; 2 | 3 | /** 4 | * 数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。 5 | * 例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。 6 | */ 7 | public class MoreThanHalf { 8 | 9 | public int moreThanHalfNum(int[] array) { 10 | if (array == null || array.length == 0) return 0; 11 | 12 | int mid = select(array, array.length / 2); 13 | return checkMoreThanHalf(array, mid); 14 | } 15 | 16 | /** 17 | * 统计中位数是否超过数组元素个数的一半,若没有默认返回0 18 | */ 19 | private int checkMoreThanHalf(int[] array, int number) { 20 | int count = 0; 21 | for (int a : array) { 22 | if (a == number) count++; 23 | } 24 | 25 | return count > array.length / 2 ? number : 0; 26 | } 27 | 28 | /** 29 | * 选择排名为k的元素,只是部分排序,时间复杂度为O(N) 30 | */ 31 | private int select(int[] array, int k) { 32 | int low = 0; 33 | int high = array.length - 1; 34 | // high==low时只有一个元素,不切分 35 | while (high > low) { 36 | int j = partition(array, low, high); 37 | if (j == k) return array[k]; 38 | else if (j > k) high = j - 1; 39 | else if (j < k) low = j + 1; 40 | } 41 | 42 | return array[k]; 43 | } 44 | 45 | /** 46 | * 快速排序的切分方法 47 | */ 48 | private int partition(int[] array, int low, int high) { 49 | int i = low; 50 | int j = high + 1; 51 | int v = array[low]; 52 | while (true) { 53 | while (array[++i] < v) if (i == high) break; 54 | while (array[--j] > v) if (j == low) break; 55 | if (i >= j) break; 56 | swap(array, i, j); 57 | } 58 | swap(array, low, j); 59 | return j; 60 | } 61 | 62 | private void swap(int[] array, int i, int j) { 63 | int temp = array[i]; 64 | array[i] = array[j]; 65 | array[j] = temp; 66 | } 67 | 68 | public int findNumMoreThanHalf(int[] array) { 69 | if (array == null || array.length == 0) return 0; 70 | 71 | int count = 1; 72 | int result = array[0]; 73 | for (int i = 1; i < array.length; i++) { 74 | if (count == 0) { 75 | result = array[i]; 76 | count = 1; 77 | } 78 | 79 | else if (array[i] == result) count++; 80 | else count--; 81 | } 82 | 83 | return checkMoreThanHalf(array, result); 84 | } 85 | 86 | } 87 | 88 | -------------------------------------------------------------------------------- /code/offer/src/Chap5/NumOf1.java: -------------------------------------------------------------------------------- 1 | package Chap5; 2 | 3 | /** 4 | * 输入一个整数n,求1~n这n个整数的十进制表示中1出现的次数, 5 | * 例如输入12, 1~12中出现1的有1、10、11、12共5次 6 | */ 7 | public class NumOf1 { 8 | /** 9 | * 方法1,计算每个数字中1的个数,复杂度O(nlgn) 10 | */ 11 | public int NumberOf1From1To(int n) { 12 | // 正负数不影响1的个数,统一变成非负数 13 | if (n < 0) n = Math.abs(n); 14 | 15 | int count = 0; 16 | // 循环求n个数字,共O(nlgn)的时间 17 | for (int i = 1; i <= n; i++) { 18 | count += numOf1(i); 19 | } 20 | return count; 21 | } 22 | 23 | /** 24 | * O(lgn)的复杂度求一个数中含有1的数量 25 | */ 26 | private int numOf1(int n) { 27 | int count = 0; 28 | while (n != 0) { 29 | if (n % 10 == 1) count++; 30 | n = n / 10; 31 | } 32 | return count; 33 | } 34 | 35 | /** 36 | * 方法2:使用StringBuilder将所有数字拼接,无脑数数 37 | */ 38 | public int numOf1Between1AndN(int n) { 39 | // 正负数不影响1的个数,统一变成非负数 40 | if (n < 0) n = Math.abs(n); 41 | 42 | StringBuilder sb = new StringBuilder(); 43 | for (int i = 0; i < n; i++) { 44 | sb.append(i); 45 | } 46 | 47 | int count = 0; 48 | for (int i = 0; i < sb.length(); i++) { 49 | if (sb.charAt(i) == '1') { 50 | count++; 51 | } 52 | } 53 | return count; 54 | } 55 | 56 | /** 57 | * 方法三:神一样的方法 58 | */ 59 | public int numberOf1(int n) { 60 | int ones = 0; 61 | for (long m = 1; m <= n; m *= 10) { 62 | long a = n / m; 63 | long b = n % m; 64 | if (a % 10 == 0) ones += a / 10 * m; 65 | else if (a % 10 == 1) ones += (a / 10 * m) + (b + 1); 66 | else ones += (a / 10 + 1) * m; 67 | } 68 | return ones; 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /code/offer/src/Chap5/PrintMinNumber.java: -------------------------------------------------------------------------------- 1 | package Chap5; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | /** 7 | * 输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。 8 | * 例如输入数组{3,32,321},则打印出这三个数字能排成的最小数字为321323 9 | */ 10 | public class PrintMinNumber { 11 | public String printMinNumber(int[] numbers) { 12 | if (numbers == null || numbers.length == 0) return ""; 13 | 14 | List list = new ArrayList<>(); 15 | for (int a : numbers) { 16 | list.add(a); 17 | } 18 | 19 | list.sort((a, b) -> (a + "" + b).compareTo(b + "" + a)); 20 | StringBuilder sb = new StringBuilder(); 21 | for (int a : list) { 22 | sb.append(a); 23 | } 24 | return sb.toString(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /code/offer/src/Chap5/UglyNumber.java: -------------------------------------------------------------------------------- 1 | package Chap5; 2 | 3 | /** 4 | * 把只包含因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含因子7。 5 | * 习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第N个丑数。 6 | */ 7 | public class UglyNumber { 8 | public int uglyNumber(int index) { 9 | if (index <= 0) return 0; 10 | int t2 = 0; 11 | int t3 = 0; 12 | int t5 = 0; 13 | int[] res = new int[index]; 14 | res[0] = 1; 15 | for (int i = 1; i < index; i++) { 16 | int m2 = res[t2] * 2; 17 | int m3 = res[t3] * 3; 18 | int m5 = res[t5] * 5; 19 | // 三个候选中最小的就是下一个丑数 20 | res[i] = Math.min(m2, Math.min(m3, m5)); 21 | // 选择某个丑数后ti * i,指针右移从丑数集合中选择下一个丑数和i相乘 22 | // 注意是三个连续的if,也就是三个if都有可能执行。这种情况发生在三个候选中有多个最小值,指针都要右移,不然会存入重复的丑数 23 | if (res[i] == m2) t2++; 24 | if (res[i] == m3) t3++; 25 | if (res[i] == m5) t5++; 26 | } 27 | return res[index - 1]; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /code/offer/src/Chap6/Add.java: -------------------------------------------------------------------------------- 1 | package Chap6; 2 | 3 | /** 4 | * 写一个函数,求两个整数之和, 5 | * 要求在函数体内不得使用"+"、"-"、"x"、"÷"四则运算符号。 6 | */ 7 | public class Add { 8 | public int add(int num1, int num2) { 9 | // num2 != 0 说明还有进位需要相加 10 | while (num2 != 0) { 11 | int sum = num1 ^ num2; 12 | int carry = (num1 & num2) << 1; 13 | num1 = sum; 14 | num2 = carry; 15 | } 16 | return num1; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /code/offer/src/Chap6/AppearOnlyOnce.java: -------------------------------------------------------------------------------- 1 | package Chap6; 2 | 3 | /** 4 | * 数组中唯一出现一次的数字。 5 | * 在一个数组中除了一个数字只出现一次之外,其他数字都出现了三次,请找出那个只出现一次的数字 6 | */ 7 | public class AppearOnlyOnce { 8 | public int findNumberAppearOnlyOnce(int[] numbers) { 9 | if (numbers == null || numbers.length == 0) throw new RuntimeException("无效输入"); 10 | int[] bitSum = new int[32]; 11 | int bitMask = 1; 12 | // 注意先对最低位做位运算,bitSum[0]存的最高位,bitSum[31]存的最低位 13 | for (int i = 31; i >= 0; i--) { 14 | for (int number : numbers) { 15 | int bit = number & bitMask; 16 | if (bit != 0) bitSum[i] += 1; 17 | } 18 | bitMask = bitMask << 1; 19 | } 20 | int result = 0; 21 | // 转换成十进制时,从最高位开始,从由左至右第一个不为0的位数开始 22 | for (int i = 0; i < 32; i++) { 23 | result = result << 1; 24 | // bitSum[i] % 3为0说明只出现一次的那个数第i为也是0;反之亦反 25 | result += bitSum[i] % 3; 26 | } 27 | return result; 28 | } 29 | 30 | 31 | public static void main(String[] args) { 32 | int[] a = {1, 1, 1, 4}; 33 | AppearOnlyOnce appearOnlyOnce = new AppearOnlyOnce(); 34 | System.out.println(appearOnlyOnce.findNumberAppearOnlyOnce(a)); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /code/offer/src/Chap6/BalancedTree.java: -------------------------------------------------------------------------------- 1 | package Chap6; 2 | 3 | public class BalancedTree { 4 | private class TreeNode { 5 | int val = 0; 6 | TreeNode left = null; 7 | TreeNode right = null; 8 | 9 | public TreeNode(int val) { 10 | this.val = val; 11 | 12 | } 13 | 14 | } 15 | 16 | /** 17 | * 方法1:递归地求每个结点的左右子树深度差,有重复计算 18 | */ 19 | public boolean isBalancedTree(TreeNode root) { 20 | if (root == null) return true; 21 | int left = depth(root.left); 22 | int right = depth(root.right); 23 | if (Math.abs(left - right) > 1) return false; 24 | return isBalancedTree(root.left) && isBalancedTree(root.right); 25 | } 26 | 27 | private int depth(TreeNode root) { 28 | if (root == null) return 0; 29 | int left = depth(root.left); 30 | int right = depth(root.right); 31 | return Math.max(left, right) + 1; 32 | } 33 | 34 | /** 35 | * 方法2:修改求二叉树深度的方法: 36 | * 只要有某个结点不平衡,将一直返回-1直到root;否则就正常返回树的深度 37 | */ 38 | public boolean isBalanced(TreeNode root) { 39 | return depth2(root) >= 0; 40 | } 41 | 42 | private int depth2(TreeNode root) { 43 | if (root == null) return 0; 44 | int left = depth2(root.left); 45 | int right = depth2(root.right); 46 | return left >= 0 && right >= 0 && Math.abs(left - right) <= 1 ? Math.max(left, right) + 1 : -1; 47 | } 48 | 49 | /** 50 | * 方法3:后序遍历,为了传引用使用了对象数组 51 | */ 52 | public boolean IsBalanced_Solution(TreeNode root) { 53 | return isBalance(root, new int[1]); 54 | } 55 | 56 | public boolean isBalance(TreeNode root, int[] depth) { 57 | if (root == null) { 58 | depth[0] = 0; 59 | return true; 60 | } 61 | boolean left = isBalance(root.left, depth); 62 | // 左子树的深度 63 | int leftDepth = depth[0]; 64 | // 右子树的深度 65 | boolean right = isBalance(root.right, depth); 66 | int rightDepth = depth[0]; 67 | // 当前结点的深度 68 | depth[0] = Math.max(leftDepth + 1, rightDepth + 1); 69 | if (left && right && Math.abs(leftDepth - rightDepth) <= 1) return true; 70 | return false; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /code/offer/src/Chap6/ContinuousSeq.java: -------------------------------------------------------------------------------- 1 | package Chap6; 2 | 3 | import java.util.ArrayList; 4 | 5 | /** 6 | * 和为s的连续正数序列 7 | * 输入一个正数s,打印出所有何为s的连续正数序列(至少含有两个数)。 8 | * 例如输入15,由于1+2+3+4+5 = 4+5+6 = 7+8,所有打印出三个连续的序列1~5,4~6,7~8 9 | */ 10 | public class ContinuousSeq { 11 | public ArrayList > FindContinuousSequence(int sum) { 12 | ArrayList> list = new ArrayList<>(); 13 | if (sum <= 2) return list; 14 | int small = 1; 15 | int big = 2; 16 | int curSum = small + big; 17 | int mid = (sum + 1) / 2; 18 | while (small < mid) { 19 | while (curSum > sum && small < mid) { 20 | curSum -= small; 21 | small++; 22 | } 23 | if (curSum == sum) list.add(addFromSmallToBig(small, big)); 24 | big++; 25 | curSum += big; 26 | } 27 | return list; 28 | } 29 | 30 | /** 31 | * 对一个连续区间内的数字求和 32 | */ 33 | private ArrayList addFromSmallToBig(int small, int big) { 34 | ArrayList list = new ArrayList<>(); 35 | for (int i = small; i<= big; i++) { 36 | list.add(i); 37 | } 38 | return list; 39 | } 40 | 41 | public static void main(String[] args) { 42 | ContinuousSeq continuousSeq = new ContinuousSeq(); 43 | System.out.println(continuousSeq.FindContinuousSequence(9)); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /code/offer/src/Chap6/DepthOfTree.java: -------------------------------------------------------------------------------- 1 | package Chap6; 2 | 3 | import java.util.LinkedList; 4 | import java.util.Queue; 5 | 6 | /** 7 | * 输入一棵二叉树,求该树的深度。 8 | * 从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。 9 | */ 10 | public class DepthOfTree { 11 | private class TreeNode { 12 | int val = 0; 13 | TreeNode left = null; 14 | TreeNode right = null; 15 | 16 | public TreeNode(int val) { 17 | this.val = val; 18 | 19 | } 20 | 21 | } 22 | 23 | /** 24 | * 递归版本 25 | */ 26 | public int TreeDepth(TreeNode root) { 27 | if (root == null) return 0; 28 | int left = TreeDepth(root.left); 29 | int right = TreeDepth(root.right); 30 | return Math.max(left, right) + 1; 31 | } 32 | 33 | /** 34 | * 非递归,层序遍历 35 | */ 36 | public int depth(TreeNode root) { 37 | if (root == null) return 0; 38 | Queue queue = new LinkedList<>(); 39 | int depth = 0; 40 | queue.offer(root); 41 | while (!queue.isEmpty()) { 42 | int layerSize = queue.size(); 43 | for (int i = 0; i < layerSize; i++) { 44 | TreeNode node = queue.poll(); 45 | if (node.left != null) queue.offer(node.left); 46 | if ((node.right) != null) queue.offer(node.right); 47 | } 48 | depth++; 49 | } 50 | return depth; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /code/offer/src/Chap6/FindNumsAppearOnce.java: -------------------------------------------------------------------------------- 1 | package Chap6; 2 | 3 | /** 4 | * 一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。 5 | * 要求时间复杂度为O(n),空间复杂度为O(1). 6 | */ 7 | public class FindNumsAppearOnce { 8 | //num1,num2分别为长度为1的数组。传出参数 9 | //将num1[0],num2[0]设置为返回结果 10 | public void findNumsAppearOnce(int[] array, int num1[], int num2[]) { 11 | if (array == null || array.length < 2) return; 12 | int res = 0; 13 | // 这步得到两个只出现一次的数的异或值 14 | for (int i = 0; i < array.length; i++) { 15 | res ^= array[i]; 16 | } 17 | // res肯定不为0,那么res必然有某一位是1,找到第一个1的索引,假设为n; 18 | // 第n位的异或值为1,也说明了这两个数的第n位不相同 19 | int indexOfFirstOne = firstBitOfOne(res); 20 | // 以第n位是不是1为标准,将数组拆分成两个 21 | // 相同数字一定会被分到同一个子数组,因为相同的数字第n位也是相同的;只出现一次的那两个数字肯定不会分到一个数组中,因为他们的第n位异或值为1,说明他们第n位不相同 22 | for (int i = 0; i < array.length; i++) { 23 | if (isBitOfOne(array[i], indexOfFirstOne)) num1[0] ^= array[i]; 24 | else num2[0] ^= array[i]; 25 | } 26 | } 27 | 28 | /** 29 | * 找到从右往左数的第一个1的索引,如0110的第一个1索引为1 30 | */ 31 | private int firstBitOfOne(int val) { 32 | int index = 0; 33 | while (val != 0) { 34 | if ((val & 1) == 1) return index; 35 | val = val >> 1; 36 | index++; 37 | } 38 | return -1; 39 | } 40 | 41 | /** 42 | * 判断从右往左数的第index位是不是1 43 | */ 44 | private boolean isBitOfOne(int val, int index) { 45 | val = val >> index; 46 | return (val & 1) == 1; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /code/offer/src/Chap6/FindTheLossNumber.java: -------------------------------------------------------------------------------- 1 | package Chap6; 2 | 3 | /** 4 | * 0~n-1中缺失的数 5 | * 一个长度为n -1的递增排序数组中的所有数字都是唯一的,并且每个数字的都在范围0~n-1之内。 6 | * 在范围内0~n-1内的n个数字中有且只有一个数字不在该数组中,找出这个数字 7 | */ 8 | public class FindTheLossNumber { 9 | public int findLoss(int[] array) { 10 | if (array == null) return -1; 11 | int low = 0; 12 | int len = array.length; 13 | int high = len - 1; 14 | while (low <= high) { 15 | int mid = low + (high - low) / 2; 16 | if (mid != array[mid]) { 17 | if (mid == 0 || mid -1 == array[mid -1]) return mid; 18 | else high = mid - 1; 19 | } else { 20 | low = mid + 1; 21 | } 22 | } 23 | if (low == len) return len; 24 | // 无效的输入数组,如不是递增排序,或者有的数字超出了0~n-1的范围 25 | return -1; 26 | } 27 | 28 | public static void main(String[] args) { 29 | int[] a = {0, 1, 2, 3, 4, 5, 6, 7, 8}; 30 | FindTheLossNumber findTheLossNumber = new FindTheLossNumber(); 31 | System.out.println(findTheLossNumber.findLoss(a)); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /code/offer/src/Chap6/KthNode.java: -------------------------------------------------------------------------------- 1 | package Chap6; 2 | 3 | import java.util.LinkedList; 4 | 5 | /** 6 | * 给定一颗二叉搜索树,请找出排名第k的结点。 7 | */ 8 | public class KthNode { 9 | private class TreeNode { 10 | int val = 0; 11 | TreeNode left = null; 12 | TreeNode right = null; 13 | 14 | public TreeNode(int val) { 15 | this.val = val; 16 | 17 | } 18 | } 19 | 20 | public TreeNode findKthNode(TreeNode pRoot, int k) { 21 | if (pRoot == null || k <= 0) return null; 22 | LinkedList stack = new LinkedList<>(); 23 | int count = 0; 24 | while (pRoot != null || !stack.isEmpty()) { 25 | while (pRoot != null) { 26 | stack.push(pRoot); 27 | pRoot = pRoot.left; 28 | } 29 | if (!stack.isEmpty()) { 30 | pRoot = stack.pop(); 31 | if (++count == k) return pRoot; 32 | pRoot = pRoot.right; 33 | } 34 | } 35 | return null; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /code/offer/src/Chap6/LastInCircle.java: -------------------------------------------------------------------------------- 1 | package Chap6; 2 | 3 | import java.util.LinkedList; 4 | import java.util.List; 5 | 6 | /** 7 | * 0, 1, 2...,n - 1这n个数字排成一个圆圈,一开始从数字0开始,从这个圆圈里删除第m个数字;然后从被删除的数字后一位开始计数,继续删除第m个数字... 8 | * 重复这个过程,直到最后只剩一个数字为止。求出这个圆圈里剩下的最后一个数字。 9 | */ 10 | public class LastInCircle { 11 | public int lastRemaining(int n, int m) { 12 | if (n <= 0 || m <= 0) return -1; 13 | List list = new LinkedList<>(); 14 | for (int i = 0; i < n; i++) list.add(i); 15 | int p = 0; 16 | while (list.size() > 1) { 17 | for (int k = 0; k < m - 1; k++) { 18 | p++; 19 | // 走到链表尾部时 20 | if (p == list.size()) p = 0; 21 | } 22 | list.remove(p); 23 | // 删除的正好是链表的最后一个元素 24 | if (p == list.size()) p = 0; 25 | } 26 | 27 | return list.get(0); 28 | } 29 | 30 | public int LastRemaining_Solution(int n, int m) { 31 | if (n <= 0 || m <= 0) return -1; 32 | List list = new LinkedList<>(); 33 | for (int i = 0; i < n; i++) list.add(i); 34 | int removeIndex = 0; 35 | while (list.size() > 1) { 36 | removeIndex = (removeIndex + m - 1) % list.size(); 37 | list.remove(removeIndex); 38 | } 39 | return list.get(0); 40 | } 41 | 42 | /** 43 | * 数学规律:约瑟夫环问题 44 | */ 45 | public int lastNumInCycle(int n, int m) { 46 | if (n <= 0 || m <= 0) return -1; 47 | int f = 0; 48 | for (int i = 2; i <= n; i++) { 49 | f = (f + m) % i; 50 | } 51 | return f; 52 | } 53 | 54 | 55 | public static void main(String[] args) { 56 | LastInCircle lastInCircle = new LastInCircle(); 57 | System.out.println(lastInCircle.lastRemaining(5, 3)); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /code/offer/src/Chap6/MaxDiff.java: -------------------------------------------------------------------------------- 1 | package Chap6; 2 | 3 | /** 4 | * 假设某股票的价格按照时间先后顺序存储在数组中,问买卖该股票一次可能获得的最大利润是多少? 5 | * 如一支股票在某段时间内的价格为{9, 11, 8, 5, 7, 12, 16, 14}那么能在价格为5的时候购入并在价格为16时卖出,能获得最大利润11 6 | */ 7 | public class MaxDiff { 8 | public int getMaxDiff(int[] prices) { 9 | if (prices == null || prices.length < 2) return 0; 10 | int min = prices[0]; 11 | int maxDiff = 0; 12 | for (int i = 1; i < prices.length; i++) { 13 | if (prices[i] < min) min = prices[i]; 14 | int curDiff = prices[i] - min; 15 | if (curDiff > maxDiff) maxDiff = curDiff; 16 | } 17 | return maxDiff; 18 | } 19 | 20 | public static void main(String[] args) { 21 | int[] prices = {9, 11, 8, 5, 7, 12, 16, 14}; 22 | MaxDiff maxDiff = new MaxDiff(); 23 | System.out.println(maxDiff.getMaxDiff(prices)); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /code/offer/src/Chap6/MaxInWindow.java: -------------------------------------------------------------------------------- 1 | package Chap6; 2 | 3 | import java.util.*; 4 | 5 | /** 6 | * 题目1:滑动窗口的最大值。 7 | * 给定一个数组和滑动窗口的大小,请找出所有滑动窗口里的最大值。 8 | * 例如,如果输入数组{2, 3, 4, 2, 6, 2, 5}以及滑动窗口的大小3,那么一共存在6个滑动窗口 9 | * 他们的最大值分别为{4,4,6,6,6,5} 10 | */ 11 | public class MaxInWindow { 12 | /** 13 | * 方法1:使用优先队列 14 | */ 15 | public ArrayList maxInWindows(int[] num, int size) { 16 | ArrayList list = new ArrayList<>(); 17 | if (num == null || num.length < size || size <= 0) return list; 18 | PriorityQueue maxHeap = new PriorityQueue<>(Comparator.reverseOrder()); 19 | int j = 0; 20 | for (int i = 0; i < num.length; i++) { 21 | maxHeap.offer(num[i]); 22 | if (maxHeap.size() >= size) { 23 | list.add(maxHeap.peek()); 24 | maxHeap.remove(num[j++]); 25 | } 26 | } 27 | return list; 28 | } 29 | /** 30 | * 方法2: 使用双端队列,存放下标 31 | */ 32 | public ArrayList maxInWindow2(int[] num, int size) { 33 | ArrayList list = new ArrayList<>(); 34 | if (num == null || num.length < size || size <= 0) return list; 35 | Deque deque = new LinkedList<>(); 36 | for (int i = 0; i < num.length; i++) { 37 | while (!deque.isEmpty() && num[i] >= num[deque.peekLast()]) deque.pollLast(); 38 | if (!deque.isEmpty() && i - deque.peekFirst() >= size) deque.pollFirst(); 39 | deque.offerLast(i); 40 | if (i +1 >= size) list.add(num[deque.peekFirst()]); 41 | } 42 | return list; 43 | } 44 | } -------------------------------------------------------------------------------- /code/offer/src/Chap6/MaxQueue.java: -------------------------------------------------------------------------------- 1 | package Chap6; 2 | 3 | import java.util.Deque; 4 | import java.util.Queue; 5 | import java.util.LinkedList; 6 | 7 | /** 8 | * 定义一个队列,实现max方法得到队列中的最大值。 9 | * 要求入列、出列以及邱最大值的方法时间复杂度都是O(1) 10 | */ 11 | public class MaxQueue { 12 | 13 | private Queue queue; 14 | private Deque maxQueue; 15 | 16 | 17 | public MaxQueue() { 18 | queue = new LinkedList<>(); 19 | maxQueue = new LinkedList<>(); 20 | } 21 | 22 | public int max_value() { 23 | if (maxQueue.isEmpty()) { 24 | return -1; 25 | } 26 | return maxQueue.peekFirst(); 27 | } 28 | 29 | public void push_back(int value) { 30 | queue.offer(value); 31 | while (!maxQueue.isEmpty() && value > maxQueue.peekLast()) { 32 | maxQueue.pollLast(); 33 | } 34 | maxQueue.offerLast(value); 35 | 36 | } 37 | 38 | public int pop_front() { 39 | if (queue.isEmpty()) { 40 | return -1; 41 | } 42 | int tmp = queue.poll(); 43 | if (tmp == maxQueue.peekFirst()) { 44 | maxQueue.pollFirst(); 45 | } 46 | return tmp; 47 | } 48 | 49 | } 50 | 51 | -------------------------------------------------------------------------------- /code/offer/src/Chap6/Multiply.java: -------------------------------------------------------------------------------- 1 | package Chap6; 2 | 3 | /** 4 | * 给定一个数组A[0, 1,...n - 1], 5 | * 请构建一个数组A[0, 1,...n - 1],其中B中的元素B[i] = A[0]*A[1]*...*A[i - 1]*A[i + 1]*...*A[n - 1] 6 | * 不能使用除法。 7 | */ 8 | public class Multiply { 9 | public int[] multiply(int[] A) { 10 | if (A == null || A.length < 2) return null; 11 | int len = A.length; 12 | int[] B = new int[len]; 13 | B[0] = 1; 14 | for (int i = 1; i < len; i++) { 15 | // 计算C[i] 16 | B[i] = B[i - 1] * A[i - 1]; 17 | } 18 | int d = 1; 19 | for (int j = len - 2; j >= 0; j--) { 20 | // 计算D[i] 21 | d *= A[j + 1]; 22 | // B[i] = C[i] * D[i] 23 | B[j] *= d; 24 | } 25 | return B; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /code/offer/src/Chap6/NumOfK.java: -------------------------------------------------------------------------------- 1 | package Chap6; 2 | 3 | /** 4 | * 统计一个数字在排序数组中出现的次数。 5 | */ 6 | public class NumOfK { 7 | /** 8 | * 方法一:遍历,O(n)复杂度,不推荐 9 | */ 10 | public int getNumberOfK(int[] array, int k) { 11 | if (array == null) return 0; 12 | 13 | int count = 0; 14 | for (int i = 0; i < array.length; i++) { 15 | if (array[i] == k) { 16 | count++; 17 | } 18 | } 19 | return count; 20 | } 21 | 22 | /** 23 | * 方法二:二分法找到第一个k和最后一个k,时间复杂度O(nlgn) 24 | */ 25 | public int numberOfK(int[] array, int k) { 26 | if (array == null) return 0; 27 | int from = getFirstOfK(array, k, 0, array.length - 1); 28 | int to = getLastOfK(array, k, 0, array.length - 1); 29 | if (from == -1 && to == -1) return 0; 30 | else return to - from + 1; 31 | } 32 | 33 | private int getFirstOfK(int[] array, int k, int low, int high) { 34 | while (low <= high) { 35 | int mid = low + (high - low) / 2; 36 | if (k < array[mid]) high = mid - 1; 37 | else if (k > array[mid]) low = mid + 1; 38 | else { 39 | if (mid > 0 && array[mid - 1] == k) high = mid - 1; 40 | else return mid; 41 | } 42 | } 43 | return -1; 44 | } 45 | 46 | private int getLastOfK(int[] array, int k, int low, int high) { 47 | while (low <= high) { 48 | int mid = low + (high - low) / 2; 49 | if (k < array[mid]) high = mid - 1; 50 | else if (k > array[mid]) low = mid + 1; 51 | else { 52 | if (mid < array.length - 1 && array[mid + 1] == k) low = mid + 1; 53 | else return mid; 54 | } 55 | } 56 | return -1; 57 | } 58 | 59 | /** 60 | * 巧妙的方法3:得到与k相邻的两个浮点数排名 61 | * 因为数组中元素时int型的,改为查找浮点型的值,比如找3的次数,就找2.5和3.5之间的元素个数, 62 | * 稍微改变二分查找的返回值就能得到数组中排名为k的方法 63 | */ 64 | public int numOfK(int[] array, int k) { 65 | if (array == null) return 0; 66 | return rank(array, k + 0.5) - rank(array, k - 0.5); 67 | } 68 | 69 | // 因为数组中元素都是int型的,改为查找浮点型的值,比如要获得3的出现次数,就找2.5和3.5之间的元素个数, 70 | // 稍微改变二分查找的返回值就能得到数组中排名为k的方法 71 | private int rank(int[] array, double k) { 72 | int low = 0; 73 | int high = array.length - 1; 74 | while (low <= high) { 75 | int mid = low + (high - low) / 2; 76 | if (k < array[mid]) high = mid - 1; 77 | else if (k > array[mid]) low = mid + 1; 78 | } 79 | return low; 80 | } 81 | 82 | public static void main(String[] args) { 83 | int[] a = {1, 2, 3, 3, 3, 3, 4, 5}; 84 | NumOfK k = new NumOfK(); 85 | System.out.println(k.numOfK(a, 3)); 86 | } 87 | } -------------------------------------------------------------------------------- /code/offer/src/Chap6/PlayCard.java: -------------------------------------------------------------------------------- 1 | package Chap6; 2 | 3 | import java.util.Arrays; 4 | 5 | /** 6 | * 从扑克牌中随机抽5张牌,判断是不是一个顺子,即这五张牌是不是连续的。2~10是数字本身,A为1,J为11,Q为12,K为13,而大小王可以看成任意数字。 7 | */ 8 | public class PlayCard { 9 | /** 10 | * 方法1:排序,计算大小王的个数和总间隔 11 | */ 12 | public boolean isContinuous(int[] numbers) { 13 | if (numbers == null || numbers.length != 5) return false; 14 | Arrays.sort(numbers); 15 | int joker = 0; 16 | // 统计大小王的个数,题目说了最多四个 17 | for (int i = 0; i < 4; i++) { 18 | if (numbers[i] == 0) joker++; 19 | } 20 | // 是对子直接返回false 21 | // 相邻数字gap为0,4~6之间间隔1,所以有减1的操作 22 | int totalGap = 0; 23 | for (int i = numbers.length - 1; i > joker; i--) { 24 | if (numbers[i] == numbers[i - 1]) return false; 25 | // 统计总间隔 26 | totalGap += numbers[i] - numbers[i - 1] - 1; 27 | } 28 | // 总的间隔要小于joker的数量才是顺子 29 | return totalGap <= joker; 30 | } 31 | 32 | /** 33 | * 方法2:除了0之外,其他数字不可重复出现;最大最小值只差不得超过5 34 | */ 35 | public boolean isContinuous2(int [] numbers) { 36 | if (numbers == null || numbers.length != 5) return false; 37 | int[] count = new int[14]; 38 | int max = -1; 39 | int min = 14; 40 | for (int number : numbers) { 41 | count[number]++; 42 | // 对除了0之外的其他数计算最大最小值 43 | if (number != 0) { 44 | if (count[number] > 1) return false; 45 | if (number > max) max = number; 46 | if (number < min) min = number; 47 | } 48 | } 49 | // 如果没有0,最大最小值之差为4,有0还能凑成顺子的,差值小于4;大于4肯定不能凑成顺子 50 | return max - min < 5; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /code/offer/src/Chap6/ReverseWords.java: -------------------------------------------------------------------------------- 1 | package Chap6; 2 | 3 | /** 4 | * 输入一个英文句子,翻转句子中单词的顺序,但单词内的顺序不变。为简单起见,标点符号和普通字母一样处理。 5 | * 例如输入"I am a student."则输出"student. a am I" 6 | */ 7 | public class ReverseWords { 8 | /** 9 | * 方法1:使用split,不能处理单词之间有多个空格的情况 10 | */ 11 | public String ReverseSentence(String str) { 12 | if (str == null || str.trim().equals("")) return str; 13 | 14 | String[] words = str.split("\\s+"); 15 | StringBuilder sb = new StringBuilder(); 16 | for (int i = words.length - 1; i >= 0; i--) { 17 | sb.append(words[i]); 18 | if (i > 0) sb.append(" "); 19 | } 20 | return sb.toString(); 21 | } 22 | 23 | /** 24 | * 方法2:先整体反转,再局部反转 25 | */ 26 | public String reverseWords(String str) { 27 | if (str == null) return null; 28 | char[] chars = str.toCharArray(); 29 | int len = chars.length; 30 | reverse(chars, 0, len -1); 31 | int low = 0; 32 | int high = 0; 33 | while (low < len) { 34 | if (chars[low] == ' ') { 35 | low++; 36 | high++; 37 | // chars[low]不为空格 38 | } else if (high == len || chars[high] == ' ') { 39 | reverse(chars, low, --high); 40 | low = ++high; 41 | // chars[low]和chars[high]都不为空格 42 | } else high++; 43 | } 44 | return new String(chars); 45 | } 46 | 47 | private void reverse(char[] chars, int low, int high) { 48 | while (low < high) { 49 | char c = chars[high]; 50 | chars[high] = chars[low]; 51 | chars[low] = c; 52 | low++; 53 | high--; 54 | } 55 | } 56 | 57 | public static void main(String[] args) { 58 | ReverseWords reverseWords = new ReverseWords(); 59 | System.out.println(reverseWords.ReverseSentence("b a")); 60 | System.out.println(reverseWords.reverseWords("b a")); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /code/offer/src/Chap6/RotateString.java: -------------------------------------------------------------------------------- 1 | package Chap6; 2 | 3 | /** 4 | * 字符串的左旋操作是把字符串前面的若干个字符转移到字符串的尾部。 5 | * 比如输入字符串"abcdefg"和一个数字2,则左旋转后得到字符串"cdefgab" 6 | */ 7 | public class RotateString { 8 | /** 9 | * 方法1:使用StringBuilder 10 | */ 11 | public String leftRotateString(String str, int n) { 12 | if (str == null || n < 0 || n > str.length()) return null; 13 | StringBuilder sb = new StringBuilder(str); 14 | sb.append(sb.substring(0, n)); 15 | return sb.substring(n, sb.length()); 16 | } 17 | 18 | /** 19 | * 三次反转:索引n将字符串分成两个子字符串,分别反转这两个子字符串,然后反转整个字符串。 20 | */ 21 | public String leftRotateString2(String str, int n) { 22 | if (str == null || n < 0 || n > str.length()) return ""; 23 | char[] chars = str.toCharArray(); 24 | reverse(chars, 0, n-1); 25 | reverse(chars, n, str.length() - 1); 26 | reverse(chars, 0, str.length() - 1); 27 | return new String(chars); 28 | } 29 | 30 | private void reverse(char[] chars, int low, int high) { 31 | while (low < high) { 32 | char c = chars[high]; 33 | chars[high] = chars[low]; 34 | chars[low] = c; 35 | low++; 36 | high--; 37 | } 38 | } 39 | 40 | public static void main(String[] args) { 41 | RotateString rotateString = new RotateString(); 42 | System.out.println(rotateString.leftRotateString("abcdefg", 2)); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /code/offer/src/Chap6/Sum.java: -------------------------------------------------------------------------------- 1 | package Chap6; 2 | 3 | /** 4 | * 求1+2+3+...+n, 5 | * 要求不能使用乘除法、for、while、if、else、switch、case等关键词以及三元运算符等。 6 | */ 7 | public class Sum { 8 | /** 9 | * 递归,因为要求不能用if,那就用逻辑与的短路特性 10 | */ 11 | public int Sum_Solution(int n) { 12 | int sum = n; 13 | boolean b = n > 0 && (sum += Sum_Solution(n - 1)) > 0; 14 | return sum; 15 | } 16 | 17 | /** 18 | * n(n+1)/2 == (n^2 + n) >> 1; 19 | */ 20 | public int sum2(int n) { 21 | return ((int)Math.pow(n, 2) + n) >> 1; 22 | } 23 | 24 | public static void main(String[] args) { 25 | Sum sum = new Sum(); 26 | System.out.println(sum.sum2(100)); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /code/offer/src/Chap6/TwoSum.java: -------------------------------------------------------------------------------- 1 | package Chap6; 2 | 3 | import java.util.ArrayList; 4 | 5 | /** 6 | * 输入一个递增排序的数组和一个数字S,在数组中查找两个数,使得他们的和正好是S; 7 | * 如果有多对数字的和等于S,输出两个数的乘积最小的。 8 | */ 9 | public class TwoSum { 10 | public ArrayList FindNumbersWithSum(int[] array, int sum) { 11 | ArrayList list = new ArrayList<>(); 12 | if (array == null || array.length <= 1) return list; 13 | 14 | int low = 0; 15 | int high = array.length - 1; 16 | 17 | while (low < high) { 18 | if (array[low] + array[high] == sum) { 19 | list.add(array[low]); 20 | list.add(array[high]); 21 | break; 22 | } else if (array[low] + array[high] > sum) 23 | high--; 24 | else low++; 25 | } 26 | return list; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /code/offer/src/Chap6/ValEqualsIndex.java: -------------------------------------------------------------------------------- 1 | package Chap6; 2 | 3 | /** 4 | * 数组中数值和下标相等的元素。 5 | * 假设一个单调递增的数组里的每个元素都是整数并且是唯一的。 6 | * 找出数组中任意一个数值等于其下标的元素。比如在数组{-3, -1, 1, 3, 5},数字3和它的下标相等 7 | */ 8 | public class ValEqualsIndex { 9 | public int findValEqualsIndex(int[] array) { 10 | if (array == null) return -1; 11 | int low = 0; 12 | int high = array.length - 1; 13 | while (low <= high) { 14 | int mid = low + (high - low) / 2; 15 | if (mid > array[mid]) low = mid + 1; 16 | else if (mid < array[mid]) high = mid - 1; 17 | if (mid == array[mid]) return mid; 18 | } 19 | return -1; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /code/offer/src/Chap7/LastSameInBST.java: -------------------------------------------------------------------------------- 1 | package Chap7; 2 | 3 | /** 4 | * 输入一棵二叉查找树的两个结点,返回它们的最低公共祖先。 5 | */ 6 | public class LastSameInBST { 7 | private class Node { 8 | private Node left, right; 9 | private int val; 10 | public Node(int val) { 11 | this.val = val; 12 | } 13 | } 14 | 15 | public Node findLastSame(Node root, Node a, Node b) { 16 | Node cur = root; 17 | while (cur != null) { 18 | if (cur.val < a.val && cur.val < b.val) { 19 | cur = cur.right; 20 | } else if (cur.val > a.val && cur.val > b.val) { 21 | cur = cur.left; 22 | } else { 23 | return cur; 24 | } 25 | } 26 | return null; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /code/offer/src/Chap7/LastSameInTree.java: -------------------------------------------------------------------------------- 1 | package Chap7; 2 | 3 | import java.util.LinkedList; 4 | import java.util.List; 5 | 6 | /** 7 | * 输入一棵普通树的两个结点,返回它们的最低公共祖先。 8 | */ 9 | public class LastSameInTree { 10 | 11 | private class Node { 12 | List children; 13 | int val; 14 | } 15 | 16 | public Node findLastSame(Node root, Node a, Node b) { 17 | if (root == null || a == null || b == null) return null; 18 | 19 | LinkedList path1 = new LinkedList<>(); 20 | LinkedList path2 = new LinkedList<>(); 21 | 22 | LinkedList res1 = new LinkedList<>(); 23 | LinkedList res2 = new LinkedList<>(); 24 | collectNode(root, a, path1, res1); 25 | collectNode(root, b, path2, res2); 26 | return getLastSameNode(path1, path2); 27 | } 28 | 29 | /** 30 | * 收集含有结点node的路径上的所有结点,形成一条链表 31 | */ 32 | private void collectNode(Node root, Node node, LinkedList path, LinkedList res) { 33 | if (root == null || node == null) return; 34 | path.add(root); 35 | if (root = node) { 36 | res.addAll(path); 37 | } 38 | for (Node child : root.children) { 39 | if (collectNode(child, node, path)); 40 | } 41 | // 该条路径上没找到结点node就要从路径中移除 42 | path.remove(path.size() - 1); 43 | } 44 | 45 | /** 46 | * 两个链表前面的结点都是相同的,找到最后一个相同的结点就是最低公共祖先 47 | */ 48 | private Node getLastSameNode(LinkedList path1, LinkedList path2) { 49 | Node lastSameNode = null; 50 | while (!path1.isEmpty() && !path2.isEmpty()) { 51 | if (path1.peekFirst() == path2.removeFirst()) { 52 | lastSameNode = path1.removeFirst(); 53 | } else { 54 | return lastSameNode; 55 | } 56 | } 57 | return lastSameNode; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /code/offer/src/Chap7/LastSameInTreeParent.java: -------------------------------------------------------------------------------- 1 | package Chap7; 2 | 3 | /** 4 | * 输入一棵普通树(拥有指向父结点的指针)的两个结点,返回它们的最低公共祖先。 5 | */ 6 | public class LastSameInTreeParent { 7 | private class Node { 8 | Node parent; 9 | int val; 10 | 11 | public Node() { 12 | this.val = val; 13 | } 14 | } 15 | 16 | /** 17 | * 变成了两条链表的第一个公共结点问题 18 | */ 19 | public Node findLastSame(Node node1, Node node2) { 20 | Node cur1 = node1; 21 | Node cur2 = node2; 22 | int len1 = 0; 23 | int len2 = 0; 24 | // 计算链表1的长度 25 | while (cur1 != null) { 26 | len1++; 27 | cur1 = cur1.parent; 28 | } 29 | // 计算链表2的长度 30 | while (cur2 != null) { 31 | len2++; 32 | cur2 = cur2.parent; 33 | } 34 | // 长链表先走若干步,和短链表的尾部对齐 35 | if (len2 > len1) { 36 | for (int i = 0; i < len2 - len1; i++) node2 = node2.parent; 37 | } 38 | 39 | if (len1 > len2) { 40 | for (int i = 0; i < len1 - len2; i++) node1 = node1.parent; 41 | } 42 | // 同时前进,第一个相等的结点即是 43 | while (node1 != null && node2 != null) { 44 | if (node1 == node2) return node1; 45 | node1 = node1.parent; 46 | node2 = node2.parent; 47 | } 48 | return null; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /code/offer/src/Chap7/LowestCommonAncestor.java: -------------------------------------------------------------------------------- 1 | package Chap7; 2 | 3 | public class LowestCommonAncestor { 4 | private class TreeNode { 5 | int val; 6 | TreeNode left; 7 | TreeNode right; 8 | TreeNode(int x) { val = x; } 9 | } 10 | 11 | public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { 12 | if(root == null) return null; // 如果树为空,直接返回null 13 | if(root == p || root == q) return root; // 如果 p和q中有等于 root的,那么它们的最近公共祖先即为root(一个节点也可以是它自己的祖先) 14 | TreeNode left = lowestCommonAncestor(root.left, p, q); // 递归遍历左子树,只要在左子树中找到了p或q,则先找到谁就返回谁 15 | TreeNode right = lowestCommonAncestor(root.right, p, q); // 递归遍历右子树,只要在右子树中找到了p或q,则先找到谁就返回谁 16 | if(left == null) return right; // 如果在左子树中 p和 q都找不到,则 p和 q一定都在右子树中,右子树中先遍历到的那个就是最近公共祖先(一个节点也可以是它自己的祖先) 17 | else if(right == null) return left; // 否则,如果 left不为空,在左子树中有找到节点(p或q),这时候要再判断一下右子树中的情况,如果在右子树中,p和q都找不到,则 p和q一定都在左子树中,左子树中先遍历到的那个就是最近公共祖先(一个节点也可以是它自己的祖先) 18 | else return root; //否则,当 left和 right均不为空时,说明 p、q节点分别在 root异侧, 最近公共祖先即为 root 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /code/offer/src/Chap7/Str2Int.java: -------------------------------------------------------------------------------- 1 | package Chap7; 2 | 3 | /** 4 | * 将一个字符串转换成一个整数,要求不能使用字符串转换整数的库函数。 5 | * 数值为0或者字符串不是一个合法的数值则返回0 6 | */ 7 | public class Str2Int { 8 | public static boolean valid; 9 | 10 | public static int strToInt(String str) { 11 | valid = false; 12 | if (str == null || str.length() == 0) { 13 | return 0; 14 | } 15 | 16 | boolean isNegative = false; 17 | long number = 0; 18 | for (int i = 0; i < str.length(); i++) { 19 | // 第一位是正负号就跳过 20 | if (i == 0 && str.charAt(i) == '+' || str.charAt(i) == '-') { 21 | if (str.charAt(i) == '-') { 22 | isNegative = true; 23 | } 24 | if (str.length() == 1) { 25 | return 0; 26 | } 27 | continue; 28 | } 29 | // 中间有任何字符不是数字直接返回 30 | if (str.charAt(i) < '0' || str.charAt(i) > '9') { 31 | return 0; 32 | } 33 | 34 | int flag = isNegative ? -1 : 1; 35 | // '4'的ASCII码就比'0'的ASCII码大4,所以可以减去'0'的ASCII码将字符转数字 36 | number = number * 10 + flag * (str.charAt(i) - '0'); 37 | if (!isNegative && number > Integer.MAX_VALUE || isNegative && number < Integer.MIN_VALUE) { 38 | return 0; 39 | } 40 | } 41 | 42 | valid = true; 43 | return (int)number; 44 | } 45 | 46 | public static void main(String[] args) { 47 | System.out.println(strToInt("12")); 48 | System.out.println(strToInt("-12")); 49 | System.out.println(strToInt("+12")); 50 | System.out.println(strToInt("+")+ " "+ Str2Int.valid); 51 | System.out.println(strToInt("0")+ " "+ Str2Int.valid); 52 | System.out.println(strToInt("12345678901112")+ " "+ Str2Int.valid); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /notes/剑指offer面试题10--斐波那契数列.md: -------------------------------------------------------------------------------- 1 | # 剑指offer面试题10--斐波那契数列 2 | 3 | > ``` 4 | > 现在要求输入一个整数n,请你输出斐波那契数列的第n项。 5 | > ``` 6 | 7 | 我想到的是迭代法,从底向上的方法:先得到f(0)、f(1)的值,然后根据这两个值计算序列后面的值。 8 | 9 | ```java 10 | package Chap2; 11 | 12 | public class Fibonacci { 13 | /** 14 | * 推荐迭代法 15 | */ 16 | public int fib(int n) { 17 | if (n <= 0) { 18 | return 0; 19 | } 20 | 21 | int a = 0; 22 | int b = 1; 23 | while (n > 0) { 24 | b = a + b; 25 | a = b - a; // a + b -a -> a = b也就是原来的b赋值给a 26 | n--; 27 | } 28 | return a; 29 | } 30 | } 31 | 32 | ``` 33 | 34 | 当然还有递归法,时空开销较大,不推荐。 35 | 36 | ```java 37 | public int fib2(int n) { 38 | if (n <= 0) { 39 | return 0; 40 | } 41 | 42 | if (n == 1) { 43 | return 1; 44 | } 45 | 46 | return fib2(n-1) +fib(n-2); 47 | } 48 | ``` 49 | 50 | ## 相关题目--青蛙跳台阶 51 | 52 | > ``` 53 | > 一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法。 54 | > ``` 55 | 56 | 到达1级台阶只有1种可能,到达2级台阶有2种可能;可记为f(1) = 1,f(2) = 2。要到达3级台阶,可以选择在1级台阶处起跳,也可以选择在2级台阶处起跳,所以只需求到达1级台阶的可能情况 + 到达2级台阶的可能情况,即f(3) = f(2) +f(1) 57 | 58 | 同理到达n级台阶,可以在n-1级台阶起跳,也可在n-2级台阶起跳,f(n) = f(n-2)+f(n-1) 59 | 60 | 可以看做是斐波那契数列。 61 | 62 | ```java 63 | package Chap2; 64 | 65 | public class JumpFloor { 66 | /** 67 | * @param target 要到达的第n级台阶 68 | * @return 到达n级台阶总共的跳法可能 69 | */ 70 | public int jumpFloor(int target) { 71 | if (target <= 0) { 72 | return 0; 73 | } 74 | if (target == 1) { 75 | return 1; 76 | } 77 | 78 | int a = 1; 79 | int b = 2; 80 | while (target > 1) { 81 | b = a + b; 82 | a = b - a; 83 | target--; 84 | } 85 | return a; 86 | } 87 | } 88 | 89 | ``` 90 | 91 | 这道题还有扩展,如下 92 | 93 | > ``` 94 | > 一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。 95 | > ``` 96 | 97 | 注意和上题的区别。 98 | 99 | 到达1级台阶只有1种可能,到达2级台阶有2种可能;可记为f(1) = 1,f(2) = 2。要到达3级台阶,可以选择在1级台阶处起跳,也可以选择在2级台阶处起跳,也可直接跳到3级,所以只需求到达1级台阶的可能情况 + 到达2级台阶的可能情况 + 1,即f(3) = f(2) +f(1) + 1同理到达n级台阶,可以在n-1级台阶起跳,可在n-2、n-1、n-3...级台阶起跳,f(n) = f(n-1)+f(n-2)+f(n-3)...+1,如果令f(n-n) = f(0) = 1,上式可表示为f(n) = f(n-1)+f(n-2)+f(n-3)...+f(n-n),有了通项公式找出规律也不是难事了,不过还有种更好理解的思路:前n-1级台阶,每级台阶都有两种选择——跳到此或不跳到此,对于最后一级n级,没得选择,必须跳到这里,所以总共有有`2^(n-1)`种跳法。 100 | 101 | ## 矩形覆盖问题 102 | 103 | > ``` 104 | > 我们可以用2*1的小矩形横着或者竖着去覆盖更大的矩形。请问用n个2*1的小矩形无重叠地覆盖一个2*n的大矩形,总共有多少种方法? 105 | > ``` 106 | 107 | 这个问题本质还是斐波那契数列... 108 | 109 | 覆盖`2*n`的矩形的方法记为f(n)。 110 | 111 | - 当最后一步是竖着放时候,说明前一步已经覆盖了`2*(n-1)`,记为f(n - 1) 112 | - 当最后一步是横着放的时候,倒数第二次也必然是横着放的。这个状态已经覆盖了`2* (n-2)`,记为f(n - 2) 113 | 114 | 因为最后一步只有横竖放两种可能,所以将上述两种方法可能性加起来即可。 115 | 116 | 覆盖`2*1`的矩形只有1种方法,覆盖`2*2`的矩形有横竖2种方法。于是f(1) = 1, f(2) = 2 117 | 118 | 这和能跳1级也能跳2级的青蛙是一样的。代码直接copy过来就行! 119 | 120 | --- 121 | 122 | by @sunhaiyu 123 | 124 | 2017.12.16 125 | -------------------------------------------------------------------------------- /notes/剑指offer面试题11--旋转数组中的最小数字.md: -------------------------------------------------------------------------------- 1 | # 剑指offer面试题11--旋转数组中的最小数字 2 | 3 | > ``` 4 | > 把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。 输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。 例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。 NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。 5 | > ``` 6 | 7 | **题目给出的是非递减排序数组,所以不是严格递增的,可能有相同元素的情况。** 8 | 9 | ## 顺序遍历 10 | 11 | 我的解法:两个子数组都是递增的,只有在两个子数组的分界线处,才会有前一个字符大于后一个字符。时间复杂度为O(n) 12 | 13 | ```java 14 | public int minNumberInRotateArray(int [] array) { 15 | if (array.length == 0) { 16 | return 0; 17 | } 18 | 19 | for (int i = 0;i < array.length - 1;i++) { 20 | if (array[i] > array[i + 1]) { 21 | return array[i + 1]; 22 | } 23 | } 24 | return array[0]; 25 | } 26 | ``` 27 | 28 | ## 二分查找 29 | 30 | 由于有序数组旋转后,被分成了两个有序的子数组,因此可以用**二分查找**,且左半数组的值全大于等于右半数组的值。我们要找的最小元素恰好是右半数组的第一个元素,或者说左半数组末尾后一个元素。 31 | 32 | 和二分查找一样,一个指针low指向数组首元素,一个high指向尾元素。还有一个指针mid,这里注意:mid不是要和哪个特定的值比较来缩小范围,根据旋转数组的特点,我们始终将mid和high处的值比较。分三种情况 33 | 34 | - array[mid] > array[high];此时mid一定还处于左半数组中,而要找的最小值在右半数组中,最小值肯定在mid的右边,所以可以直接将low移动到mid的下一个位置,即low = mid + 1。举个例子{3, 4, 5, 1, 2},mid处的5比high处的2大,直接更新low = 3,数组被缩小到{1, 2} 35 | - array[mid] < array[high];此时mid一定处于右半数组中,最小值可能在mid处也可能在mid的左边。所以high只能缩小到mid处,即high = low。举个例子{4, 5, 1, 2, 3},mid处的1小于high处的3,只能将high移动到1处,数组缩小为{4, 5, 1},如果像上面类似令high = mid - 1,最小值再这个例子中就被跳过了! 36 | - array[mid] == array[high];此时无法分辨mid处于左半数组还是右半数组。比如{1, 0, 1, 1, 1}和{1, 1, 1, 0, 1}都是数组{0, 1, 1, 1, 1}的旋转数组。此时mid处和high处的值一样,若根据low缩小范围,对于{1, 0, 1, 1, 1}最小值将被跳过;如果根据high缩小范围,对于{1, 1, 1, 0, 1}最小值也会被跳过。此时的处理方法是暂时放弃二分查找,**既然mid处和high处值相同,那么让`high--`,让mid和high的前一个值继续比较。**如果mid和high处都是最小值,就算放弃了high最后还是会在mid处找到最小值。 37 | 38 | **只要low < high(不含等于),就不断重复上面过程,最后将范围缩小到只有一个元素后,low == high跳出循环。**其实low == high时候还进入循环,也没有错,此时只会造成high--,而我们返回的是array[low],值不会影响,但是何必进行这次无意义的比较呢。 39 | 40 | 根据上面的描述已经可以写出代码了。 41 | 42 | ```java 43 | package Chap2; 44 | 45 | public class MinNumberInRotateArray { 46 | 47 | public int minNumberInRotateArray(int [] array) { 48 | if (array==null || array.length == 0) { 49 | return 0; 50 | } 51 | 52 | int low = 0; 53 | int high = array.length - 1; 54 | while (low < high) { 55 | int mid = low + (high - low) / 2; 56 | // 此时mid一定在左半数组中,最小值在mid的右边 57 | if (array[mid] > array[high]) { 58 | low = mid + 1; 59 | // 此时mid一定在右半数组中,最小值在mid的左边或就是mid本身 60 | } else if (array[mid] < array[high]) { 61 | high = mid; 62 | // 暂时放弃二分查找,和前一个字符继续比较 63 | } else { 64 | high--; 65 | } 66 | } 67 | // low == high时推出循环并返回 68 | return array[low]; 69 | } 70 | } 71 | 72 | ``` 73 | 74 | 时间复杂度为O(lg n)。 75 | 76 | 上面始终将mid和high比较,可不可以mid和low比较呢,可以但是和与high比较相比更麻烦。 77 | 78 | 试想如果最后范围缩小到剩下{1, 2}此时array[low] == array[mid],如果low++就跳过最小元素了,此种情况可以写一个`min(int low, int high)`方法,直接返回[low, high]范围内的最小值。比起用high来和mid比较,麻烦了不少。 79 | 80 | --- 81 | 82 | by @sunhaiyu 83 | 84 | 2017.12.18 85 | -------------------------------------------------------------------------------- /notes/剑指offer面试题13--机器人的运动范围.md: -------------------------------------------------------------------------------- 1 | # 剑指offer面试题13--机器人的运动范围 2 | 3 | > ``` 4 | > 地上有一个m行和n列的方格。一个机器人从坐标0,0的格子开始移动,每一次只能向左,右,上,下四个方向移动一格,但是不能进入行坐标和列坐标的数位之和大于k的格子。 例如,当k为18时,机器人能够进入方格(35,37),因为3+5+3+7 = 18。但是,它不能进入方格(35,38),因为3+5+3+8 = 19。请问该机器人能够达到多少个格子? 5 | > ``` 6 | 7 | 此题和*面试题12——矩阵中的路径*有相似之处,依然是回溯法。每来到一个新的且满足条件的格子时,计数加1。除矩形的边界外,任意一个方格都有四个方向可以选择,选择任一方向后来到新的格子又可以选择四个方向,但是一个到达过的格子不能进入两次,因为这将导致对同一个格子的重复计数。也就是说,**一个格子一旦满足条件进入后,就被永久标记为“访问过”,一个满足条件的格子只能使计数值加1。**这是和面试题12有区别的地方(那个例子中是搜索路径,失败路径上的点要重新标记为“未访问”,因为另开辟的新路径需要探索这些点)。 8 | 9 | 这道题用通俗的话来讲就是:m行n列的所有方格中,有多少个满足**行坐标和列坐标的数位之和小于等于门限值k**的格子? 10 | 11 | 代码和面试题12长得有点像,但是两个问题是有明显区别的! 12 | 13 | ```java 14 | package Chap2; 15 | 16 | public class RobotMove { 17 | public int movingCount(int threshold, int rows, int cols) { 18 | if (rows <= 0 || cols <= 0 || threshold < 0) { 19 | return 0; 20 | } 21 | 22 | boolean[] marked = new boolean[rows * cols]; 23 | // 从(0, 0)处开始 24 | return move(0, 0, threshold, rows, cols, marked); 25 | 26 | } 27 | 28 | /** 29 | * 递归方法,每到一个格子就四个方向搜索 30 | * 31 | * @param row 当前行 32 | * @param col 当前列 33 | * @param threshold 门限值 34 | * @param rows 总行数 35 | * @param cols 总列数 36 | * @param marked 是否访问过 37 | * @return 当前格子数(等于1)加上4个方向能访问到的格子数的总和 38 | */ 39 | private int move(int row, int col, int threshold, int rows, int cols, boolean[] marked) { 40 | int count = 0; 41 | if (checked(row, col, threshold, rows, cols, marked)) { 42 | marked[row * cols + col] = true; 43 | // 递归对四个方向计数,注意四个方向的搜索不是同时发生, 44 | count = move(row - 1, col, threshold, rows, cols, marked) + 45 | move(row + 1, col, threshold, rows, cols, marked) + 46 | move(row, col - 1, threshold, rows, cols, marked) + 47 | move(row, col + 1, threshold, rows, cols, marked) + 1; 48 | } 49 | return count; 50 | } 51 | 52 | /** 53 | * 判断当前格子是否超过门限值,以及边界值的判断 54 | * 55 | * @return true如果当前格子满足条件 56 | */ 57 | private boolean checked(int row, int col, int threshold, int rows, int cols, boolean[] marked) { 58 | return row >= 0 && row < rows && col >= 0 && col < cols && !marked[row * cols + col] && digitSum(row) + digitSum(col) <= threshold; 59 | } 60 | 61 | /** 62 | * 比如数字1234,每位数相加的和将返回10 63 | * @param number 某数字 64 | * @return 该数字的数位之和 65 | */ 66 | private int digitSum(int number) { 67 | int sum = 0; 68 | while (number > 0) { 69 | sum += number % 10; 70 | number /= 10; 71 | } 72 | return sum; 73 | } 74 | } 75 | 76 | ``` 77 | 78 | `checked`方法用于判断当前坐标是否满足边界条件,是否超过门限值。 79 | 80 | 递归方法`move`是核心,注意递归对四个方向计数时,四个方向的搜索不是同时发生的,一个方向搜索失败(遇到边界或超过门限值)后,退回来进行下一个方向的搜索,回溯法就体现在此。 81 | 82 | --- 83 | 84 | by @sunhaiyu 85 | 86 | 2017.12.18 87 | -------------------------------------------------------------------------------- /notes/剑指offer面试题15--二进制中1的个数.md: -------------------------------------------------------------------------------- 1 | # 剑指offer面试题15--二进制中1的个数 2 | 3 | > ``` 4 | > 输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。 5 | > ``` 6 | 7 | 容易想到的思路:该数的各位不断和1相与,然后将该数右移1位,直到所有位都比较过。 8 | 9 | ```java 10 | public int numberOf1_2(int n) { 11 | int count = 0; 12 | 13 | while (n != 0) { 14 | if ((n & 1) == 1) { 15 | count++; 16 | } 17 | n = n >> 1; 18 | } 19 | return count; 20 | } 21 | ``` 22 | 23 | **注意!上面的代码是错误的!对于非负数来说没有问题,但是当传入负数的时候,由于`>>`是带符号的右移,对于负数来说高位会以1补位,n永远也不会等于9,因此会出现无限循环。** 24 | 25 | 在Java中右移分两种,一种是上面那样带符号的右移,用`>>`表示,如果数是正数高位以0补位,如果是负数高位以1补位;还有就是无符号的右移,用`>>>`表示,不论正负数,统统高位以0补位。因此只需改动将上述程序的`>>`改成`>>>`即可通过。 26 | 27 | ## 右移版本 28 | 29 | 下面的程序才是正确的。 30 | 31 | ```java 32 | public int numberOf1_2(int n) { 33 | int count = 0; 34 | 35 | while (n != 0) { 36 | if ((n & 1) == 1) { 37 | count++; 38 | } 39 | n = n >>> 1; 40 | } 41 | return count; 42 | } 43 | ``` 44 | 45 | ## 左移版本 46 | 47 | 上面的第一个程序之所以会出现无限循环,是因为我们改变了被输入的数本身。换个角度,我们不改变输入的数,而是通过改变一个变量,那么不管是输入正负数都能得到正确答案。 48 | 49 | 所以用一个1和输入数的每一位相与,然后将这个1不断**左移**。 50 | 51 | ```java 52 | public int numberOf1_1(int n) { 53 | int count = 0; 54 | int flag = 1; 55 | while (flag != 0) { 56 | if ((n & flag)!= 0) { 57 | count++; 58 | } 59 | // 每次改变的只是这个变量,输入的数始终没有被改变过 60 | flag = flag << 1; 61 | } 62 | return count; 63 | } 64 | ``` 65 | 66 | ## 更为巧妙的方法 67 | 68 | 要想出这样的方法,需要经过一定的分析。将任意数减去1,,有两种情况 69 | 70 | - 该数二进制表示的最低位就是1,比如数7,二进制表示为111,此时直接减去1即可; 71 | - 最低位不为1,假设从右往左出现的第一个1,位置为m。则该数减去1后,位置m处的1会变成0,m之后所有的0都会变成1。比如数12,二进制表示为1100,减去1,先找到从右往左数的第一个1,这个位置的1变成0,其后的两个0变成1,即变成了1011。此时,如果将减去1后得到的1011和原来的1100相与,得到1000,相当于是将最右边的那个1变成了0。 72 | 73 | 也就是说:**把一个整数减去1之后再与原来的整数做位与运算,得到的结果相当于将原整数的二进制表示中最右边的1变成0。** 74 | 75 | 基于这个推论,写出如下程序。 76 | 77 | ```java 78 | public int numberOf1(int n) { 79 | int count = 0; 80 | // 只要数不为0,其二进制表示中至少含有一个1 81 | while (n != 0) { 82 | // 将最右边的1变成0 83 | n = (n-1) & n; 84 | // 每将一个1变成了0,就计数一次 85 | count++; 86 | } 87 | return count; 88 | } 89 | ``` 90 | 91 | ## 相关的题目 92 | 93 | 利用上面的结果,我们可以解决很多相关的问题。 94 | 95 | 比如下面的两道 96 | 97 | ``` 98 | 1、写一个函数判断一个整数是不是2的正整数次方。 99 | 100 | 2、输入两个整数m和n,计算需要改变m的二进制表示中的几位才能得到n。比如10的二进制是1010,13的二进制是1101,则需要改变3次。 101 | ``` 102 | 103 | 对于问题1:一个整数如果是2的正整数次方,那么这个数必然大于0的,且它的二进制表示中有且只有一位1。所以下面一句就能判断。 104 | 105 | ```java 106 | public boolean isExponOf2(int n) { 107 | return numberOf1(n) == 1; 108 | } 109 | ``` 110 | 111 | 对于问题2:先对这两个数求异或,两个数的二进制表示中不同的位就会得到1,相同的位得到0。我要要改变的正是两个数中不同的位,因此,统计异或后二进制中1个个数,即是需要改变的位数。 112 | 113 | ```java 114 | public int bitNumNeedsToBeChanged(int m, int n) { 115 | return numberOf1(m ^ n); 116 | } 117 | ``` 118 | 119 | --- 120 | 121 | by @sunhaiyu 122 | 123 | 2017.12.19 124 | 125 | -------------------------------------------------------------------------------- /notes/剑指offer面试题19--正则表达式匹配.md: -------------------------------------------------------------------------------- 1 | # 剑指offer面试题19--正则表达式匹配 2 | 3 | > ``` 4 | > 请实现一个函数用来匹配包括'.'和'*'的正则表达式。模式中的字符'.'表示任意一个字符,而'*'表示它前面的字符可以出现任意次(包含0次)。在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串"aaa"与模式"a.a"和"ab*ac*a"匹配,但是与"aa.a"和"ab*a"均不匹配 5 | > ``` 6 | 7 | 注意`.`就是一个字符,而`*`前面必须有一个字符(可以是`.`)才有意义,所以可以将`x*`看成一个整体,其中x表示任意字符。`*`在匹配时有两种情况,第二个字符是`*`或者不是。 8 | 9 | - 第二个字符不是`*`号。这种情况很简单,第二个字符要么是`.`要么是一个具体的字符。此时如果第一个字符匹配成功了,只需将模式和文本指针都前进一位。比如`ab`和`ac`以及`ab`和`.b`,分别对应着字符一样、模式字符为`.`的情况。**第一个字符匹配失败了,直接就可以得出结论——匹配失败。** 10 | 11 | - 第二个字符是`*`。有几种情况: 12 | 13 | 1、`*`匹配0次,比如`a*ab`和`ab`匹配,此时需要将模式指针前移2位,文本指针保持不动; 14 | 15 | 2、`*`匹配了1次,比如`a*b`和`ab`匹配,此时需要将模式指针前移2位,文本指针前移1位; 16 | 17 | 3、`*`匹配了多次,比如`a*b`和`aaab`匹配,此时需要将模式保持不动,文本指针前移1位; 18 | 19 | **同样的比较第二个字符的前提是第一个字符已经匹配成功。** 20 | 21 | 22 | 23 | 我们将长度任意的模式和文本分解成一个或者两个字符,可以用递归地方法写出如下程序: 24 | 25 | ```java 26 | package Chap3; 27 | 28 | public class ReMatch { 29 | public boolean match(char[] str, char[] pattern) 30 | { 31 | if (str == null || pattern == null) { 32 | return false; 33 | } 34 | 35 | return matchRecur(str, pattern, 0, 0); 36 | } 37 | 38 | private boolean matchRecur(char[] str, char[] pattern, int s, int p) { 39 | if (s == str.length && p == pattern.length) { 40 | return true; 41 | } 42 | // 模式串比文本串先到末尾,肯定没有匹配成功 43 | if (p == pattern.length && s < str.length) { 44 | return false; 45 | } 46 | // 两种情况,1、模式和文本都没有到结尾 47 | // 2、或者文本到了结尾而文本还没有到结尾,此时肯定会调用else分支 48 | // 第二个字符是* 49 | if (p < pattern.length-1 && pattern[p + 1] == '*') { 50 | if ((s < str.length && str[s] == pattern[p]) || (pattern[p]== '.' && s < str.length)) 51 | return matchRecur(str, pattern, s, p + 2) || 52 | matchRecur(str, pattern, s + 1, p+2) || 53 | matchRecur(str,pattern,s + 1, p); 54 | else 55 | return matchRecur(str, pattern, s, p + 2); 56 | } 57 | // 第二个字符不是* 58 | if ((s < str.length && str[s] == pattern[p]) || (pattern[p]== '.' && s < str.length)) { 59 | return matchRecur(str, pattern, s + 1, p + 1); 60 | } 61 | return false; 62 | } 63 | } 64 | 65 | ``` 66 | 67 | 如果匹配的最后两个指针都到达末尾,说明完全匹配上了,返回true。如果模式指针先于文本到达末尾,一定是匹配失败了,举个例子,`a*bcd`和`abcdefg`。 68 | 69 | 接下来的两个if分别是第二个字符为`*`和不为`*`的情况。 70 | 71 | - 第二个字符为`*`。在保证第一个字符已经匹配上的情况下,`*`可以有三种匹配方式,有任一种方式匹配成功即可,所以return语句中用的是`||`。 72 | - 第二个字符不为`*`。当第一个字符已经匹配上的情况下,直接将模式和文本的指针前移一位,即比较下一个字符。 73 | 74 | 如果不是上面的情况(比如第一个字符就匹配失败了),返回false。 75 | 76 | --- 77 | 78 | by @sunhaiyu 79 | 80 | 2017.12.25 -------------------------------------------------------------------------------- /notes/剑指offer面试题20--表示数值的字符串.md: -------------------------------------------------------------------------------- 1 | # 剑指offer面试题20--表示数值的字符串 2 | 3 | > ``` 4 | > 请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串"+100","5e2","-123","3.1416"和"-1E-16"都表示数值。但是"12e","1a3.14","1.2.3","+-5"和"12e+4.3"都不是。 5 | > ``` 6 | 7 | ## 正则表达式 8 | 9 | 这道题有点没意思,就是考虑各种情况,实际上可能还是考虑得不全。第一想到就是直接使用正则表达式。对正则不熟写起来可能要花些时间。 10 | 11 | ```java 12 | public boolean isNumeric(char[] str) { 13 | if (str == null) { 14 | return false; 15 | } 16 | return new String(str).matches("[+-]?[0-9]*(\\.[0-9]*)?([eE][+-]?[0-9]+)?"); 17 | } 18 | ``` 19 | 20 | 首先第1位可是是+-号中的任意一个,也可以没有;然后是整数部分0~9中任意多位,也可以没有整数部分;然后是小数部分,也是可有可无的,所以用`?`表示小数部分只能出现0次或者1次。e或者E部分也是只能出现0次或者1次,但是一旦出现e或者E,后面必须有整数,所以是`[0-9]+`,表示这个整数至少是一位。 21 | 22 | ## 考虑多种情况,自己实现 23 | 24 | 如果不使用字符串自带的`matches`方法呢? 25 | 26 | - 字符串长度为1,这个字符必须是0~9中的其中一个 27 | - 第二次出现正负号,该正负号的前一个字符必须是e或者E,比如`-5E-3` 28 | - 第一次出现正负号,且不在开头出现,该正负号的前一个字符必须是e或者E,比如`5E-3` 29 | - 小数点只能出现一次,且不能出现在e或者E的后面 30 | - e或者E只能出现一次,e和E的后一个字符必须是整数 31 | - 如果字符不是`+-eE.`,那么它必须是0~9中任意一个;否则不匹配。 32 | 33 | 在遍历完所有字符后,如果上面的条件都满足了,就返回true表示这是一个数字。 34 | 35 | ```java 36 | package Chap3; 37 | 38 | public class Numeric { 39 | public boolean isNumeric_2(char[] str) { 40 | // null或者空字符串不是数字 41 | if (str == null || str.length == 0) { 42 | return false; 43 | } 44 | // 长度只为1,必须是数0~9之间 45 | if (str.length == 1) { 46 | return str[0] >= '0' && str[0] <= '9'; 47 | } 48 | 49 | boolean hasDot = false; 50 | boolean hasE = false; 51 | boolean hasSign = false; 52 | 53 | for (int i = 0; i < str.length; i++) { 54 | if (str[i] == '+' || str[i] == '-') { 55 | // 第二次出现正负号,前一个字符必须是e或者E 56 | if (hasSign && str[i - 1] != 'e' && str[i - 1] != 'E') return false; 57 | // 第一次出现正负号且不在开头,前一个字符也必须是e或者E 58 | if (!hasSign && i > 0 && str[i - 1] != 'e' && str[i - 1] != 'E') return false; 59 | hasSign = true; 60 | } else if (str[i] == '.') { 61 | // 只能出现一次'.',e和E之后不可出现'.' 62 | if (hasDot || hasE) return false; 63 | hasDot = true; 64 | } else if (str[i] == 'e' || str[i] == 'E') { 65 | // e或E后必须有数字 66 | if (i == str.length -1) return false; 67 | // 只能有一个e或者E 68 | if (hasE) return false; 69 | hasE = true; 70 | // 最后判断如果是 +-eE.之外的字符,不匹配 71 | } else if (str[i] < '0' || str[i] > '9') { 72 | return false; 73 | } 74 | } 75 | return true; 76 | } 77 | 78 | } 79 | 80 | ``` 81 | 82 | 还是想说真麻烦,可能还有情况没有考虑到的... 83 | 84 | --- 85 | 86 | by @sunhaiyu 87 | 88 | 2017.12.25 89 | -------------------------------------------------------------------------------- /notes/剑指offer面试题22--链表中倒数第k个结点.md: -------------------------------------------------------------------------------- 1 | # 剑指offer面试题22--链表中倒数第k个结点 2 | 3 | > ``` 4 | > 输入一个链表,输出该链表中倒数第k个结点。 5 | > ``` 6 | 7 | 设置两个指针a、b,若当a指针到链表末尾时,b指针正好指向倒数第k个结点,此时返回指针b处的结点即可,这时a指针的位置减去b指针的位置等于k-1,如果将两个指针同步地倒退到链表头结点处,b指针会位于位置0处的头结点,而a指针位于位置k-1处的结点。所以如果我们反过来思考:一开始先让a指针走k-1步,b不动,之后两个指针同时移动,直到a到了链表末尾,此时返回b指针处的结点就是倒数第k个结点。**这种方法只需遍历一次链表。**时间复杂度为O(n)。 8 | 9 | 以上说的是普通情况,如果k比链表长度还要大怎么办?比如链表长度为5,偏要求倒数第6个结点,当然没有——这时需要返回空。考虑到这种情况,可以在指针a先走k-1步的过程中,加入判断:**因为指针a最多走到链表的尾结点处**,所以如果在循环内出现了`a.next == null`(紧接着`a = a.next`,指针a将走到尾结点之后,这之后才跳出循环),就说明k比链表长度还大,这种情况直接返回null即可。当然如果k <= 0也是没有意义的。**参数k是传入的,一定要讨论它的各种取值。** 10 | 11 | ```java 12 | package Chap3; 13 | 14 | public class FindKthToTailInLinkedList { 15 | private class ListNode { 16 | int val; 17 | ListNode next = null; 18 | 19 | ListNode(int val) { 20 | this.val = val; 21 | } 22 | } 23 | 24 | public ListNode FindKthToTail(ListNode head, int k) { 25 | if (k <= 0 || head == null) { 26 | return null; 27 | } 28 | 29 | ListNode a = head; 30 | ListNode b = head; 31 | // 第一个指针先移动k -1步 32 | for (int i = 0; i < k - 1; i++) { 33 | // 如果k的值比链表结点数还大,就会出现这种情况 34 | if (a.next == null) { 35 | return null; 36 | } 37 | a = a.next; 38 | } 39 | // 然后两个指针同时移动到末尾 40 | while (a.next != null) { 41 | a = a.next; 42 | b = b.next; 43 | } 44 | return b; 45 | } 46 | } 47 | 48 | ``` 49 | 50 | --- 51 | 52 | by @sunhaiyu 53 | 54 | 2017.12.25 55 | -------------------------------------------------------------------------------- /notes/剑指offer面试题24--反转链表.md: -------------------------------------------------------------------------------- 1 | # 剑指offer面试题24--反转链表 2 | 3 | > ``` 4 | > 输入一个链表的头结点,反转链表后,并返回反转链表的头结点。 5 | > ``` 6 | 7 | ## 用栈实现,不推荐 8 | 9 | 本题容易想到用栈,但是空间复杂度为O(N)不推荐。 10 | 11 | ```java 12 | package Chap3; 13 | 14 | import java.util.LinkedList; 15 | 16 | /** 17 | * 输入一个链表的头结点,反转链表后,并返回反转链表的头结点。 18 | */ 19 | public class ReverseLinkedList { 20 | private class ListNode { 21 | int val; 22 | ListNode next = null; 23 | 24 | ListNode(int val) { 25 | this.val = val; 26 | } 27 | } 28 | 29 | /** 30 | * 用栈,空间复杂度为O(N)不推荐 31 | * 32 | * @param head 头结点 33 | * @return 反转之后的链表头结点 34 | */ 35 | public ListNode reverseList_2(ListNode head) { 36 | if (head == null) { 37 | return null; 38 | } 39 | LinkedList stack = new LinkedList<>(); 40 | ListNode cur = head; 41 | while (cur != null) { 42 | stack.push(cur); 43 | cur = cur.next; 44 | } 45 | 46 | head = stack.pop(); 47 | cur = head; 48 | while (!stack.isEmpty()) { 49 | cur.next = stack.pop(); 50 | cur = cur.next; 51 | } 52 | cur.next = null; 53 | 54 | return head; 55 | } 56 | } 57 | 58 | ``` 59 | 60 | 思路就是链表的所有结点存入栈中,弹出的第一个结点作为头结点。然后不断往后添加结点,需要注意的是添加最后一个元素的时候,next要指向空(因为栈中最后一个元素实际上是原链表的头结点,该结点的next不为null)。 61 | 62 | ## 三个指针,推荐 63 | 64 | 可以设置三个指针,分别指向前一结点、当前结点、后一结点。当前结点的next本来是指向它的后一结点的,现在让其指向它的前一个结点,就实现了链表的反转。**但是当前结点与它后一个结点链接断开了**,因此在反转链表之前需要保存当前结点的下一个结点的指针。以便链表反转的过程向前推进(当前指针和前一指针前移)。 65 | 66 | ```java 67 | public ListNode reverseList(ListNode head) { 68 | ListNode pre = null; 69 | ListNode next = null; 70 | ListNode cur = head; 71 | while (cur != null) { 72 | // 当前结点的后一结点需要保存下来 73 | next = cur.next; 74 | // 反转 75 | cur.next = pre; 76 | // 指针前移 77 | pre = cur; 78 | cur = next; 79 | } 80 | // 最后pre到达的位置刚好是最末尾那个结点,即反转后的头结点 81 | return pre; 82 | } 83 | ``` 84 | 85 | ## 递归解法 86 | 87 | 递归解法还是有必要学习下的。如果链表为空,就返回null;如果链表只有一个结点,就返回该结点。链表长度大于等于2时思路时,**先递归到倒数第一个结点处**。即下面代码中的revHead,然后返回它,递归调用的返回中逐层返回的都是这个revHead,这保证了最后返回的是反转后的头结点。接着递归调用返回到上一层。nextNode就是最后一个结点,而head是倒数第二个结点,让nextNode.next 指向head就实现了链表的反转,**之后倒数第二个结点的next要指向null,因为它原先是指向nextNode的,同时也保证了最后一个结点的next也指向null的正确性。** 88 | 89 | ```java 90 | public ListNode reverseListRecur(ListNode head) { 91 | if (head == null || head.next == null) return head; 92 | 93 | ListNode revHead = reverseListRecur(head.next); 94 | ListNode nextNode = head.next; 95 | nextNode.next = head; 96 | head.next = null; 97 | return revHead; 98 | } 99 | ``` 100 | 101 | --- 102 | 103 | by @sunhaiyu 104 | 105 | 2017.12.27 106 | -------------------------------------------------------------------------------- /notes/剑指offer面试题26--树的子结构.md: -------------------------------------------------------------------------------- 1 | # 剑指offer面试题26--树的子结构 2 | 3 | > ``` 4 | > 输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构) 5 | > ``` 6 | 7 | 二叉树这种递归的数据结构。想到用递归的方法解决是很自然的。 8 | 9 | 首先我们要在二树A中找到和树B根结点值一样的结点R,结点R可能有多个,因为树A中的结点值可能不止一个与树B的根结点值相同。对于每个结点R,其子树都有可能是树A的子结构,因此**只要还未在树A中找到一个子结构,就要继续遍历树A判断其他R结点。** 10 | 11 | 对于某一个R结点,我们要求根结点R的左子结点、右子结点和树B根结点的左子结点、右子结点分别相等(值相等),而且R的子结点的左右子结点也要和B的子结点值相等......直到和树B的叶子结点也相等才说树A包含树B。这是个递归的过程。 12 | 13 | 对于这两个步骤,可写出如下 14 | 15 | ```java 16 | package Chap3; 17 | 18 | public class SubTree { 19 | 20 | private class TreeNode { 21 | int val = 0; 22 | TreeNode left = null; 23 | TreeNode right = null; 24 | 25 | public TreeNode(int val) { 26 | this.val = val; 27 | 28 | } 29 | 30 | } 31 | 32 | public boolean hasSubTree(TreeNode root1,TreeNode root2) { 33 | boolean result = false; 34 | if (root1 != null && root2 != null) { 35 | // 根结点相同,紧接着判断子树结构一样不 36 | if (root1.val == root2.val) { 37 | // 如果子树结构一样,返回true,也不用再对树A遍历了 38 | result = doesTree1HaveTree2(root1, root2); 39 | } 40 | // 当前根结点不同,或者即使根结点相同子树结构不同,还需继续遍历,递归判断左右子树 41 | if (!result) { 42 | result = hasSubTree(root1.left, root2); 43 | } 44 | 45 | if (!result) { 46 | result = hasSubTree(root1.right, root2); 47 | } 48 | } 49 | 50 | return result; 51 | } 52 | 53 | private boolean doesTree1HaveTree2(TreeNode node1,TreeNode node2) { 54 | // node2到达叶子结点的左右子结点了都还相等,说明是树1的子结构 55 | if (node2 == null) { 56 | return true; 57 | } 58 | // 如果node2没有到叶子结点的左右子结点,而node1先到了说明树2比树1还大,返回false 59 | if (node1 == null) { 60 | return false; 61 | } 62 | 63 | if (node1.val != node2.val) { 64 | return false; 65 | } 66 | // 递归比较其左右子结点,左右子结点必须都相等所以用的是&& 67 | return doesTree1HaveTree2(node1.left, node2.left) && doesTree1HaveTree2(node1.right, node2.right); 68 | } 69 | } 70 | 71 | ``` 72 | 73 | 有一点要注意,下面**两个if的判断顺序不可颠倒**。如果node2没有左右子结点了,说明叶子结点也和树B比较过了且相等,说明树A包含树B,因此返回true。如果第一个if没通过,到第二个if,说明node2还没到叶子结点,但现在node1却先于树B到达了叶子结点,说明树A该处的子树不包含树B,需要继续遍历树A。 74 | 75 | ```java 76 | // node2到达叶子结点的左右子结点了都还相等,说明是树1的子结构 77 | if (node2 == null) { 78 | return true; 79 | } 80 | // 如果node2没有到叶子结点的左右子结点,而node1先到了说明树2比树1还大,返回false 81 | if (node1 == null) { 82 | return false; 83 | } 84 | ``` 85 | 86 | --- 87 | 88 | by @sunhaiyu 89 | 90 | 2017.12.27 91 | -------------------------------------------------------------------------------- /notes/剑指offer面试题29--顺时针打印矩阵.md: -------------------------------------------------------------------------------- 1 | # 剑指offer面试题29--顺时针打印矩阵 2 | 3 | > ``` 4 | > 输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,例如,如果输入如下矩阵: 5 | > 1 2 3 4 6 | > 5 6 7 8 7 | > 9 10 11 12 8 | > 13 14 15 16 9 | > 则依次打印出数字 10 | > 1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10. 11 | > ``` 12 | 13 | 顺时针打印一个矩阵需要按照如下顺序: 14 | 15 | 1. 从左往右遍历每个数字,只需保证至少有一行即可。 16 | 2. 从上往下遍历每个数字,保证至少有两行。 17 | 3. 从右往左遍历每个数字,除了保证至少有两行,还要保证至少两列。 18 | 4. 从下往上遍历每个数字,保证至少有三行两列。 19 | 20 | 每打印完一圈,矩形区域缩小,对内层的矩形再次执行上述操作(按顺序)....到最后一个矩形时,矩形区域很小,上述操作可能不会全部执行,那也没有关系,只要限制了条件,后续的遍历是不会得到执行的。 21 | 22 | 关键是如何控制矩形的边界,正确地缩小矩形的边界。为此设置了4个变量 23 | 24 | - left表示矩形的左边界 25 | - right表示矩形的右边界 26 | - top表示矩形的上边界 27 | - bottom表示矩形的下边界 28 | 29 | 一开始 30 | 31 | ``` 32 | left = top = 0; 33 | right = 列数 34 | bottom = 行数 35 | ``` 36 | 37 | 每次打印完一圈后,缩小矩形区域,于是 38 | 39 | ``` 40 | left++;right--; 41 | top++;bottom--; 42 | ``` 43 | 44 | 搞清楚上面打印顺序和相应的条件后,可写出如下程序: 45 | 46 | ```java 47 | package Chap4; 48 | 49 | import java.util.ArrayList; 50 | 51 | public class PrintMatrix { 52 | 53 | public ArrayList printMatrix(int[][] matrix) { 54 | if (matrix == null || matrix.length == 0) { 55 | return null; 56 | } 57 | 58 | ArrayList list = new ArrayList<>(); 59 | int left = 0; 60 | int right = matrix[0].length - 1; 61 | int top = 0; 62 | int bottom = matrix.length - 1; 63 | 64 | while (left <= right && top <= bottom) { 65 | // 从左往右,有一行即可 66 | for (int col = left; col <= right; col++) list.add(matrix[top][col]); 67 | // 从上往下,保证至少有两行 68 | if (top < bottom) { 69 | for (int row = top + 1; row <= bottom; row++) list.add(matrix[row][right]); 70 | } 71 | // 从右往左,至少两行两列 72 | if (top < bottom && left < right) { 73 | for (int col = right - 1; col >= left; col--) list.add(matrix[bottom][col]); 74 | } 75 | // 从下往上,保证至少三行两列 76 | if (top < bottom - 1 && left < right) { 77 | for (int row = bottom - 1; row > top; row--) list.add(matrix[row][left]); 78 | } 79 | // 缩小矩形 80 | left++; 81 | right--; 82 | top++; 83 | bottom--; 84 | } 85 | return list; 86 | } 87 | } 88 | 89 | ``` 90 | 91 | 只要`left <= right && top <= bottom`说明还存在矩形区域,需要继续打印。 92 | 93 | --- 94 | 95 | by @sunhaiyu 96 | 97 | 2018.1.5 -------------------------------------------------------------------------------- /notes/剑指offer面试题3--数组中的重复数字.md: -------------------------------------------------------------------------------- 1 | # 剑指offer面试题3--数组中的重复数字 2 | 3 | > ``` 4 | > 在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是第一个重复的数字2或者3。 5 | > ``` 6 | 7 | ## 排序后相邻元素两两比较 8 | 9 | 我想到的方法是:先对数组排序,如果有重复元素排序后将会相邻。然后相邻元素两两比较,有相等的情况就找到了重复数字。排序一个长度为n的数组时间复杂度为`O(nlg n)`。 10 | 11 | 代码如下 12 | 13 | ```java 14 | public boolean duplicate2(int numbers[],int length,int [] duplication) { 15 | if (numbers == null || length == 0){ 16 | return false; 17 | } 18 | 19 | Arrays.sort(numbers); 20 | for (int i = 0;i < length - 1;i++) { 21 | if (numbers[i] == numbers[i + 1]) { 22 | duplication[0] = numbers[i]; 23 | return true; 24 | } 25 | } 26 | return false; 27 | } 28 | ``` 29 | 30 | ## 利用题干信息——长度为n的数组里的所有数字都在0到n-1的范围内 31 | 32 | 上面的实现对于任意数组都是通用的。**我们要善于抓住题干的已知信息**,比如题目中明确说明了长度为n的数组里面的数字全是[0, n-1]之间的。从这句话能获得什么信息呢?如果将数组排好序,那么数组每一个位置都有`numbers[i] = i`,如果有重复元素,说明[0, n-1]中某些数字空缺了,某些数字有多个。那么必然有某个`j !=i`的`numbers[j] == numbers[i]`,如果找到满足上述条件的数字,就找到了重复数字。 33 | 34 | - 如果值i没有在正确的位置(满足`numbers[i] == i`),通过交换两个元素,将值i放到正确的位置。这个过程可以看成是**排序**。 35 | - 当`numbers[i] != i`,若令`numbers[i] = j`,显然`j != i`;如果此时`numbers[j] == numbers[i]`说明在与i不同的位置j找到重复元素。否则重复上一步。 36 | 37 | 38 | 39 | ```java 40 | package Chap2; 41 | 42 | import java.util.Arrays; 43 | 44 | public class FindDuplicate { 45 | /** 46 | * 推荐的做法,通过交换元素,将值i保存到numbers[i] 47 | * 在numbers[i]不和i相等时,如果numbers[i]和numbers[numbers[i]]相等就说明重复元素; 48 | * 否则就交换这两个元素,这个过程相当于排序。举个例子,通过交换将2放入numbers[2]。 49 | 50 | * @param numbers 待查找的数组 51 | * @param length 数组的长度,其实就是numbers.length 52 | * @param duplication 用于保存重复数字,第一个被找到的重复数字存放在duplication[0]中 53 | * @return 如果在数组中有重复元素 54 | */ 55 | public boolean duplicate(int numbers[],int length,int [] duplication) { 56 | if (numbers == null || length <= 0) { 57 | return false; 58 | } 59 | for (int i = 0;i < length;i++){ 60 | if (numbers[i] < 0 || numbers[i] > length -1) { 61 | return false; 62 | } 63 | } 64 | 65 | for (int i = 0; i< length; i++) { 66 | while (numbers[i] != i) { 67 | // 现在numbers[i] != i ,设numbers[i] = j,所以如果下面的if成立,就是numbers[i] == numbers[j],说明找到 重复 68 | if (numbers[i] == numbers[numbers[i]]) { 69 | duplication[0] = numbers[i]; 70 | return true; 71 | } 72 | swap(numbers, i, numbers[i]); 73 | } 74 | } 75 | return false; 76 | } 77 | // 交换numbers[i]和numbers[numbers[i]] 78 | private void swap(int[] numbers, int p, int q) { 79 | int temp = numbers[p]; 80 | numbers[p] = numbers[q]; 81 | numbers[q] = temp; 82 | } 83 | } 84 | 85 | ``` 86 | 87 | 代码中尽管有一个两重循环,但是每个数字只需交换一两次就能被放在正确的位置上,因此总的时间复杂度为O(n)。而且所有操作都在原数组上进行,没有引入额外的空间,空间复杂度为O(1)。 88 | 89 | --- 90 | 91 | by @sunhaiyu 92 | 93 | 2017.12.14 94 | -------------------------------------------------------------------------------- /notes/剑指offer面试题31--栈的压入、弹出序列.md: -------------------------------------------------------------------------------- 1 | # 剑指offer面试题31--栈的压入、弹出序列 2 | 3 | > ``` 4 | > 输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列 5 | > (注意:这两个序列的长度是相等的) 6 | > ``` 7 | 8 | 题目只是给出了两个序列,并没有栈,所有我们需要自己定义**一个辅助栈**帮助我们模拟入栈出栈过程。 9 | 10 | 需要用到一个指针表示在出栈序列中的当前出栈元素。每次入栈一个元素,之后立刻和出栈序列中当前出栈元素对比,若相同就弹出刚压入的元素、同时当前弹出元素指针前移。之后还要继续比较,如果栈顶还和当前弹出元素相同则需要接着弹出。否则压入入栈序列中的下一个元素。 11 | 12 | 正常情况下,如果出栈顺序正确,当入栈序列中的元素都被压入后,辅助栈能按照出栈序列全部弹出。**如果当元素都被压入后,辅助栈没能弹出所有元素(不为空),说明出栈顺序是错误的。** 13 | 14 | ```java 15 | package Chap4; 16 | 17 | import java.util.LinkedList; 18 | 19 | public class StackPopOrder { 20 | 21 | public boolean IsPopOrder(int [] pushA,int [] popA) { 22 | if (pushA == null || popA == null || pushA.length == 0 || popA.length == 0) { 23 | return false; 24 | } 25 | // 辅助栈 26 | LinkedList stackAux = new LinkedList<>(); 27 | int popIndex = 0; 28 | for (int i = 0;i < pushA.length;i++) { 29 | // 按照入栈序列依次压入辅助栈中 30 | stackAux.push(pushA[i]); 31 | // 每入栈一次和出栈序列比较,如果栈顶和当前出栈元素相同,则弹出同时当前弹出元素指针前移; 32 | // 如果下一个栈顶元素还和当前弹出元素相同,继续弹出 33 | while (!stackAux.isEmpty() && stackAux.peek() == popA[popIndex]) { 34 | stackAux.pop(); 35 | popIndex++; 36 | } 37 | } 38 | // 如果出栈顺序正确,模拟一次进出栈后,辅助栈应该为空。不为空说明序列不正确 39 | return stackAux.isEmpty(); 40 | } 41 | } 42 | ``` 43 | 44 | 假如入栈顺序是`[1, 2, 3, 4, 5]`举个出栈顺序正确的例子`[4, 5, 3, 2, 1]`: 45 | 46 | | 操作 | 辅助栈 | 出栈 | 47 | | ---- | ---------- | ---- | 48 | | 压入1 | 1 | | 49 | | 压入2 | 1, 2 | | 50 | | 压入3 | 1, 2, 3 | | 51 | | 压入4 | 1, 2 ,3 ,4 | | 52 | | 弹出4 | 1,2 ,3 | 4 | 53 | | 压入5 | 1,2, 3 ,5 | | 54 | | 弹出5 | 1, 2, 3 | 5 | 55 | | 弹出3 | 1,2 | 3 | 56 | | 弹出2 | 1 | 2 | 57 | | 弹出1 | | 1 | 58 | 59 | 再举个错误的出栈顺序的例子`[4,3,5,1,2]` 60 | 61 | | 操作 | 辅助栈 | 出栈 | 62 | | ---- | ---------- | ---- | 63 | | 压入1 | 1 | | 64 | | 压入2 | 1, 2 | | 65 | | 压入3 | 1, 2, 3 | | 66 | | 压入4 | 1, 2 ,3 ,4 | | 67 | | 弹出4 | 1,2 ,3 | 4 | 68 | | 弹出3 | 1, 2 | 3 | 69 | | 压入5 | 1, 2, 5 | | 70 | | 弹出5 | 1, 2 | 5 | 71 | | 弹出2 | | 2 | 72 | | 弹出1 | | 1 | 73 | 74 | 最后只剩两个元素1, 2时,由于2在栈顶1在栈底,不能先弹出1在弹出2,所以这个出栈顺序是错误的。 75 | 76 | --- 77 | 78 | by @sunhaiyu 79 | 80 | 2018.1.6 81 | -------------------------------------------------------------------------------- /notes/剑指offer面试题33--二叉搜索树的后序遍历序列.md: -------------------------------------------------------------------------------- 1 | # 剑指offer面试题33--二叉搜索树的后序遍历序列 2 | 3 | > ``` 4 | > 输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则输出Yes,否则输出No。假设输入的数组的任意两个数字都互不相同。 5 | > ``` 6 | 7 | 注意是二叉搜索(查找)树,特点是父结点的左子树都比父结点小,父结点的右子树都比父结点大。因此其后序遍历的序列有一定的规律: 8 | 9 | - 序列最后一位必然是树的根结点; 10 | - 序列前部分是根结点的左子树,后部分是根结点的右子树;具体来说:将序列各个元素和和序列最后一位的根结点比较,序列前部分都小于根结点的值,这部分子序列是左子树;序列后部分的值都大于根结点,这部分子序列是右子树; 11 | 12 | **根据二叉树的递归结构,可以将树分成左子树、根结点、右子树,对于每棵子树可以继续按照上面的方式分下去。于是原序列可被划分为若干个子序列,每个子序列表示一棵子树。** 13 | 14 | 比如对于一个序列{5, 7, 6, 9, 11, 10, 8},根结点为8,{5, 7, 6}因为都小于8所以是8的左子树,而{9, 11, 10}都大于8所以是8的右子树,而对于子树{5, 7, 6},最后一个元素6是根结点.....以此类推。 15 | 16 | 因此解决本题:首先要从序列中获得根结点,然后找到序列中左右子树的分界线,由此将序列分成三部分:左子树、根结点、右子树。再对左右子树进行递归操作。递归终止的条件是:上层结点只有左子树或只有右子树,或者当前结点是叶子结点,即没有子结点。这两种情况都应该返回true(可以画图举几个例子模拟下,理解为什么) 17 | 18 | 思路清晰后,程序就好写了,如下: 19 | 20 | ```java 21 | package Chap4; 22 | 23 | public class VeritySeqOfSearchBST { 24 | public boolean verifySquenceOfBST(int[] sequence) { 25 | if (sequence == null || sequence.length == 0) return false; 26 | return isSearchBST(sequence, 0, sequence.length - 1); 27 | } 28 | 29 | private boolean isSearchBST(int[] seq, int begin, int end) { 30 | // begin比end大说明上层结点没有左子树或者右子树,begin == end说明该本层结点没有子树,无需比较了 31 | // 这两种情况都应该返回true 32 | if (begin >= end) return true; 33 | 34 | int rootVal = seq[end]; 35 | int i = begin; 36 | // 左子树都比root小 37 | while (i < end && seq[i] < rootVal) { 38 | i++; 39 | } 40 | // 找到了左右子树的分界,[begin, boundary-1]为左子树,[boundary, end -1]是右子树 41 | int boundary = i; 42 | while (i < end) { 43 | // 右子树中还存在比root小的说明不是二叉搜索树 44 | if (seq[i] < rootVal) return false; 45 | i++; 46 | } 47 | // 左右子树必须同时都是二叉搜索树 48 | return isSearchBST(seq, begin, boundary - 1) && isSearchBST(seq, boundary, end - 1); 49 | } 50 | } 51 | 52 | ``` 53 | 54 | 递归中主要判断某个序列或者子序列构成的树是不是二叉搜索树,在定下来左右子树的分界线后,如果右子树中还存在比root小的说明该子树不是二叉搜索树,直接返回false;注意必须满足**左右子树都是二叉搜索树**。 55 | 56 | --- 57 | 58 | by @sunhaiyu 59 | 60 | 2018.1.11 -------------------------------------------------------------------------------- /notes/剑指offer面试题36--二叉搜索树与双向链表.md: -------------------------------------------------------------------------------- 1 | # 剑指offer面试题36--二叉搜索树与双向链表 2 | 3 | > ``` 4 | > 输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。 5 | > ``` 6 | 7 | 看到这道题我第一反映就是,二叉树的线索化,不过还是有些区别的,下面会讨论。按照二叉搜索树的特点,最左边的结点是值最小的,而题目要求得到排序的双向链表,所以基本确定下来**中序遍历**的方法。 8 | 9 | 二叉树的线索化:是针对每个叶则结点,为了将空指针利用起来,可以将叶子结点的左子结点指向其遍历顺序的前驱,右子结点指向遍历序列的后继。根据遍历顺序的不同,线索化也分为前序、中序、后序。二叉树的结点定义中需要加入布尔变量,用来标示每个结点的左右指针是否被线索化了(这些标志只可能在叶子结点为true) 10 | 11 | ```java 12 | public static class Node { 13 | private T data; 14 | private Node lchild; 15 | private Node rchild; 16 | private boolean isleftThread; 17 | private boolean isRightThread; 18 | 19 | public Node(T data) { 20 | this.data = data; 21 | isleftThread = false; 22 | isRightThread = false; 23 | } 24 | } 25 | 26 | // 当前访问结点的前一个结点 27 | private Node preNode; 28 | 29 | public void inOrderThread(Node node) { 30 | if (node == null) { 31 | return; 32 | } 33 | inOrderThread(node.lchild); 34 | 35 | if (node.lchild == null) { 36 | node.lchild = preNode; 37 | node.isleftThread = true; 38 | } 39 | 40 | if (preNode != null && preNode.rchild == null) { 41 | preNode.rchild = node; 42 | preNode.isRightThread = true; 43 | } 44 | // preNode始终表示上一个访问的结点 45 | preNode = node; 46 | inOrderThread(node.rchild); 47 | } 48 | 49 | public void inOrderThread() { 50 | inOrderThread(root); 51 | } 52 | ``` 53 | 54 | 而本题要实现双向链表,不光是叶子结点,所有结点的左右指针都要重新设置。因此将上面的限制条件(左右子结点为空才线索化)去掉,就得到本题的答案! 55 | 56 | ```java 57 | package Chap4; 58 | 59 | public class BSTTransToDLinkedList { 60 | // 当前结点的前驱 61 | private TreeNode preNode; 62 | 63 | public TreeNode Convert(TreeNode pRootOfTree) { 64 | if (pRootOfTree == null) { 65 | return null; 66 | } 67 | 68 | TreeNode root = pRootOfTree; 69 | // 得到双向链表 70 | inOrder(root); 71 | // 向左找到双向链表的头结点 72 | while (root.left != null) { 73 | root = root.left; 74 | } 75 | return root; 76 | 77 | } 78 | 79 | // 中序遍历并改变指针 80 | private void inOrder(TreeNode node) { 81 | if (node == null) return; 82 | 83 | inOrder(node.left); 84 | 85 | node.left = preNode; 86 | if (preNode != null) { 87 | preNode.right = node; 88 | } 89 | preNode = node; 90 | 91 | inOrder(node.right); 92 | } 93 | } 94 | 95 | ``` 96 | 97 | 最后因为需要返回双向链表的头结点,只需从二叉树的根结点开始,向左遍历,即可找到双向链表的头结点。 98 | 99 | --- 100 | 101 | by @sunhaiyu 102 | 103 | 2018.1.11 104 | -------------------------------------------------------------------------------- /notes/剑指offer面试题37--序列化二叉树.md: -------------------------------------------------------------------------------- 1 | # 剑指offer面试题37--序列化二叉树 2 | 3 | > 请实现两个函数,分别用来序列化和反序列化二叉树。 4 | 5 | 刚开始想法太死板了,只记得中序和前序或者中序和后续两个序列才能决定一棵唯一的二叉树,于是分别进行了前序、中序遍历,前序和中序的序列用"|"分隔,之后再根据这个分隔符分成前序和中序序列,最后采用面试题7——重建二叉树的思路进行反序列化。思路是正确的但是太麻烦。 6 | 7 | 其实遇到空指针可以也用一个特殊的字符表示,比如“#”,**这样前序遍历序列就可以表示唯一的一棵二叉树了。**对于空指针也用一个字符表示,可称这样的序列为**扩展序列**。而二叉树的建立,必须先要建立根结点再建立左右子树(root为空怎么调用root.left是吧),所以必须前序建立二叉树,那么序列化时也应该用前序遍历,保证了根结点在序列前面。 8 | 9 | 不能使用中序遍历,因为**中序扩展序列**是一个无效的序列,比如 10 | 11 | ``` 12 | A B 13 | / \ \ 14 | B C 和 A 中序扩展序列都是 #B#A#C# 15 | \ 16 | C 17 | ``` 18 | 19 | 先来看序列化的代码,其实就是在前序遍历的基础上,如果遇到空指针就用“#”表示。 20 | 21 | ```java 22 | package Chap4; 23 | 24 | public class SerializeBT { 25 | // 结点值用String[] seq保存,index是seq的索引 26 | private int index = -1; 27 | 28 | public String serialize(TreeNode root) { 29 | if (root == null) { 30 | return null; 31 | } 32 | StringBuilder sb = new StringBuilder(); 33 | preOrder(root, sb); 34 | return sb.toString(); 35 | 36 | } 37 | // 前序遍历 38 | private void preOrder(TreeNode node, StringBuilder sb) { 39 | if (node == null) { 40 | sb.append("# "); 41 | return; 42 | } 43 | sb.append(node.val).append(" "); 44 | preOrder(node.left, sb); 45 | preOrder(node.right, sb); 46 | } 47 | 48 | } 49 | 50 | ``` 51 | 52 | 再来看反序列化,通过前序遍历得到的字符串,重建二叉树。 53 | 54 | ```java 55 | package Chap4; 56 | 57 | public class SerializeBT { 58 | // 结点值用String[] seq保存,index是seq的索引 59 | private int index = -1; 60 | 61 | public TreeNode deserialize(String str) { 62 | if (str == null) { 63 | return null; 64 | } 65 | 66 | String[] seq = str.split("\\s"); 67 | 68 | return reconstructBST(seq); 69 | 70 | } 71 | 72 | private TreeNode reconstructBST(String[] seq) { 73 | ++index; 74 | if (seq[index].equals("#")) return null; 75 | 76 | TreeNode root = new TreeNode(Integer.parseInt(seq[index])); 77 | root.left = reconstructBST(seq); 78 | root.right = reconstructBST(seq); 79 | return root; 80 | } 81 | 82 | } 83 | 84 | ``` 85 | 86 | 由于前序遍历时每存入一个结点值或者存入“#”后面都紧跟着一个空格。所以最后得到的序列时这样的格式`A B # # C # #`,可以根据空格将其分割成`[A, B, #, #, C, #, #]`这样就还原了各个结点的值,根据这些值重建二叉树。由于得到的是二叉树的前序序列,因此也要以前序重建二叉树,当遇到结点值是“#”时说明这是一个空指针,那么返回null给上层的父结点。如果不为“#”就递归地重建该结点的左右子树。注意这里使用了一个int型的index,用于表示当前结点在String[] seq中的索引,无需担心index在seq中会造成数组下标越界,因为最后一个结点的左右子树肯定是null,必然会终止递归。 87 | 88 | --- 89 | 90 | by @sunhaiyu 91 | 92 | 2018.1.15 -------------------------------------------------------------------------------- /notes/剑指offer面试题42--连续子数组的最大和.md: -------------------------------------------------------------------------------- 1 | # 剑指offer面试题42--连续子数组的最大和 2 | 3 | > ``` 4 | > 输入一个整型数组,数组里正负数都可能有,数组中的一个或者连续的多个整数组成一个子数组。求所有子数组的和的最大值,要求时间复杂度为O(n) 5 | > ``` 6 | 7 | 枚举所有的子数组,从中选择最大和可能是很容易想到的办法了,但是总共有$n(n+1) /2$个子数组,最快也需要$O(n^2)$时间复杂度,Pass。 8 | 9 | 我们可以举例分析数组的特点比如{1, -2, 3, 10, -4, 7, 2, -5}。首先记录下第一个元素,先假设它为最大和。当1加上-2时变成了-1,再加上3等于2,3前面加了一堆还不如不加,所以应该直接从3开始加,即**如果当前累加和是负数,那么它加上当前元素将使得新的累加和比当前元素还要小,此时应该将之前的累加和丢弃,从当前元素开始累加。** 10 | 11 | 按照上述方法,新的累加和为3大于当前最大和,因此最大和更新为3;加10,当前最大和变成13,继续-4,当前累加和为9,并不大于当前最大和,因此最大和不更新;加7、加2,当前和为18,大于最大和,当前最大和更新为18;最后-5,当前累加和为13,不大于最大和18。遍历完毕,返回最大和18.如果搞清楚了上面的思路,可以很容易写出如下代码: 12 | 13 | ```java 14 | package Chap5; 15 | 16 | public class FindGreatestSumOfSubArray { 17 | public int findGreatestSumOfSubArray(int[] array) { 18 | if (array == null || array.length == 0) return 0; 19 | 20 | int maxSum = array[0]; 21 | int curSum = array[0]; 22 | for (int i = 1; i < array.length; i++) { 23 | // 丢弃累结合,从当前元素开始累加 24 | if (curSum < 0) curSum = array[i]; 25 | // 累加 26 | else curSum += array[i]; 27 | // 和当前最大和比较,若比它大就更新 28 | if (curSum > maxSum) maxSum = curSum; 29 | } 30 | return maxSum; 31 | } 32 | } 33 | 34 | ``` 35 | 36 | 没几行,只需遍历一次,时间复杂度为O(n). 37 | 38 | ## 动态规划 39 | 40 | 还可以用动态规划的思想,用$f(i)$表示以第i个数字结尾的子数组,其中$0 \le i < n $,那么我们要求的就是$max[f(i)]$。 41 | 42 | $$f(i) = array[i], i=0 或者f(i-1) < 0$$ 43 | 44 | $$f(i) = f(i -1) + array[i], i \neq 0 且f(i -1) \ge 0$$ 45 | 46 | **动态规划最重要的就是要保存中间计算结果**,这里$f(i)$其实就是上面的curSum,$max[f(i)]$的计算变成了两两比较,其实就是上面的maxSum。可以看到和上面的方法是异曲同工的。这里保存的中间计算结果就是curSum和maxSum,当$f(i-1) < 0$时对应着上面curSum < 0. 否则就累加,然后求$max[f(i)]$可以通过两两比较得到最终的最大和,也就是$f(i)$和当前的max比较选择较大的那个。 47 | 48 | ```java 49 | package Chap5; 50 | 51 | public class FindGreatestSumOfSubArray { 52 | /** 53 | * 动态规划,其实和上面是一样的代码... 54 | */ 55 | public int FindGreatestSumOfSubArray2(int[] array) { 56 | if (array == null || array.length == 0) return 0; 57 | 58 | int maxSum = array[0]; 59 | int curSum = array[0]; 60 | for (int i = 1; i < array.length; i++) { 61 | // if (curSum + array[i] < array[i]),也就是if (curSum < 0) 则curSum的结果是array[i] 62 | // 否则curSum的值是curSum + array[i] 63 | curSum = Math.max(curSum + array[i], array[i]); 64 | // 如果curSum > maxSum,则maxSum取curSum,否则maxSum = maxSum 65 | maxSum = Math.max(curSum, maxSum); 66 | } 67 | return maxSum; 68 | } 69 | } 70 | 71 | ``` 72 | 73 | --- 74 | 75 | by @sunhaiyu 76 | 77 | 2017.1.18 78 | -------------------------------------------------------------------------------- /notes/剑指offer面试题45--把数组排成最小的数.md: -------------------------------------------------------------------------------- 1 | # 剑指offer面试题45--把数组排成最小的数 2 | 3 | > ``` 4 | > 输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。例如输入数组{3,32,321},则打印出这三个数字能排成的最小数字为321323。 5 | > ``` 6 | 7 | 这道题可以对数组排序,比如对于普通的数组{23, 12, 32},可能会想到按照自然序排序后,得到{12, 23, 32}然后直接拼接起来就得到了最小的数字122332,但是像题中的例子按照自然序排好后就是{3, 32, 321},如果直接拼接得到332321,是不是最小呢?可以发现排好后是{321, 32, 3},才能得到最小数321323。**可见这已经不是自然序了,所以想到需要自定义一种比较方法,得到一种新的排序方式。** 8 | 9 | 既然是要拼接数组中所有的数,保证拼接后的数最小,我们从最小的问题出发,当数组中只有两个数时,情况很简单,比如{3, 32},有用两种方式拼接他们`"3"+"32"`和`"32"+"3"`,分别为332和323,因为323 < 332,所以将32排在3的前面,也就是对于数组中的任意两个数m和n,如何mn < nm,则应该将m排在n的前面。在Java中很好实现,只需重写一个Comparator即可。使用Java 8的lambda表达式可以简化这一过程。 10 | 11 | ```java 12 | package Chap5; 13 | 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | 17 | public class PrintMinNumber { 18 | public String printMinNumber(int[] numbers) { 19 | if (numbers == null || numbers.length == 0) return ""; 20 | 21 | List list = new ArrayList<>(); 22 | for (int a : numbers) { 23 | list.add(a); 24 | } 25 | // 这句是核心,即比较ab和ba的大小,小的排在前面 26 | list.sort((a, b) -> (a + "" + b).compareTo(b + "" + a)); 27 | StringBuilder sb = new StringBuilder(); 28 | for (int a : list) { 29 | sb.append(a); 30 | } 31 | return sb.toString(); 32 | } 33 | } 34 | 35 | ``` 36 | 37 | --- 38 | 39 | by @sunhaiyu 40 | 41 | 2018.1.22 -------------------------------------------------------------------------------- /notes/剑指offer面试题48--最长不含重复字符串的子字符串.md: -------------------------------------------------------------------------------- 1 | # 剑指offer面试题48--最长不含重复字符串的子字符串 2 | 3 | > ``` 4 | > 请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子字符串的长度。假设字符串中只包含'a'~'z'之间的字符,例如在字符串"arabcacfr"中,最长的不含重复字符的子字符串是"acfr",长度为4 5 | > ``` 6 | 7 | 动态规划,**定义$f(i)$表示以第i个字符为结尾的不含重复字符的子字符串长度。** 8 | 9 | 如果第i个字符之前没有出现过,则$f(i) = f(i -1) +1$,比如‘abc',$f(0) = 1$是必然的,然后字符’b‘之前没有出现过,则$f(1) = f(0)+1$, 字符’c'之前没有出现过,那么$f(2) = f(1) +1$,每次计算都会用到上一次计算的结果。 10 | 11 | 如果第i个字符之前出现过呢?找到该字符上次出现的位置preIndex,当前位置i - preIndex就得到这两个重复字符之间的距离,设为d。此时有两种情况 12 | 13 | - 如果`d <= f(i-1)`,说明当前重复字符必然在f(i-1)所对应的字符串中,比如’bdcefgc‘,当前字符c前面出现过了,preIndex为2,此时`d = 6 -2 = 4` ,小于` f(i -1) = 6 (bdcefg)`我们只好丢弃前一次出现的字符c及其前面的所有字符,得到当前最长不含重复字符的子字符串为’efgc‘,即`f(i) = 4`, 多举几个例子就知道,应该让`f(i) = d`; 14 | 15 | - 如果`d > f(i-1)`, 这说明当前重复字符必然在f(i-1)所对应的字符串**之前**,比如`erabcdabr`当前字符r和索引1处的r重复,`preIndex =1, i = 8,d = 7`。而`f(i -1) = 4 (cdab)`,此时直接加1即可,即`f(i) = f(i-1) +1` 16 | 17 | 18 | 根据这两种情况可写出代码如下: 19 | 20 | ```java 21 | package Chap5; 22 | 23 | public class LongestSubstring { 24 | public int lengthOfLongestSubstring(String str) { 25 | if (str == null || str.length() == 0) return 0; 26 | int curLen = 0; 27 | int maxLen = 0; 28 | // 0~25表示a~z,position[0] = index,表明a上次出现在index处 29 | int[] position = new int[256]; 30 | for (int i = 0; i < 256; i++) { 31 | position[i] = -1; 32 | } 33 | 34 | for (int i = 0; i < str.length(); i++) { 35 | int preIndex = position[str.charAt(i)]; 36 | // 字符第一次出现,或者d > f(i -1) 37 | if (preIndex == -1 || i - preIndex > curLen) curLen++; 38 | // d <= f(i -1) 39 | else { 40 | curLen = i - preIndex; 41 | } 42 | // 记录当前字符出现的位置 43 | position[str.charAt(i)] = i; 44 | if (curLen > maxLen) maxLen = curLen; 45 | } 46 | return maxLen; 47 | 48 | } 49 | } 50 | 51 | ``` 52 | 53 | 用了一个数组position代替哈希表,记录每个字符上次出现的位置,能以O(1)的时间完成,先要将position中的值全初始化为-1,因为上次出现的位置可能含有索引0。 curLen就是上面说到的$f(i -1)$ 54 | 55 | 如果某个字符第一次出现,那么它上次出现的位置preIndex为-1。当前最长不重复子字符串直接加1。 56 | 57 | 只有else语句中curLen才可能变小,因此要即使保存到maxLen,因为这个curLen可能就是最长的。而且if语句中curLen++并没有和maxLen比较,所以除了循环后还要再和maxLen比较一次。 58 | 59 | --- 60 | 61 | by @sunhaiyu 62 | 63 | 2018.1.24 64 | -------------------------------------------------------------------------------- /notes/剑指offer面试题49--丑数.md: -------------------------------------------------------------------------------- 1 | # 剑指offer面试题49--丑数 2 | 3 | > ``` 4 | > 把只包含因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含因子7。习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第N个丑数。 5 | > ``` 6 | 7 | 穷举法,挨个判断是否是丑数,即使那个数不是丑数也要判断,显然时间复杂度太高。 8 | 9 | 有没有方法每一步计算只是得到丑数呢?根据丑数的定义,所有丑数都是2、3、5这三个因子的任意搭配的任意多次乘积,比如2x2,2x3, 2x2x3x5等等。那么从1开始,分别乘以2、3、5,得到2、3、5三个丑数,但是这并不是正确的排序,我们知道3、5之间还有个4也是丑数。1之后的下一个丑数,一定是2、3、5其中的一个,显然应该选三者中最小的2,现在丑数集合为{1, 2}且有序,刚才选走的2是1x2得到的,因此下一个和2相乘的丑数应该是1之后的数字2(丑数集合已经有序,直接选择下一个)。现在又得到三个候选的丑数4、3、5,再次选择三者中最小的3,得到当前丑数集合{1, 2, 3},刚被选走的3由1x3得到,因此下一个要和3相乘的按照丑数集合的顺序应该是2,然后又得到了三个候选的丑数4、6、5,选择最小的4.....不断重复,自始至终只和丑数打交道。 10 | 11 | 设定三个数t2、t3、t3专门用于分别和2、3、5相乘,某次选择中选走了ti,那么ti从丑数集合中选择下一个数,下次再和i相乘生成一个新的候选丑数,本次没有被选中的,下次继续参与比较。这样能保证下一个丑数一定在三个候选项中,且是三个候选项中最小的那个。 12 | 13 | ```java 14 | package Chap5; 15 | 16 | public class UglyNumber { 17 | public int uglyNumber(int index) { 18 | if (index <= 0) return 0; 19 | int t2 = 0; 20 | int t3 = 0; 21 | int t5 = 0; 22 | int[] res = new int[index]; 23 | // 第一个丑数为1 24 | res[0] = 1; 25 | for (int i = 1; i < index; i++) { 26 | int m2 = res[t2] * 2; 27 | int m3 = res[t3] * 3; 28 | int m5 = res[t5] * 5; 29 | // 三个候选中最小的就是下一个丑数 30 | res[i] = Math.min(m2, Math.min(m3, m5)); 31 | // 选择某个丑数后ti * i,指针右移从丑数集合中选择下一个丑数和i相乘,注意是三个连续的if,也就是三个if都有可能执行。这种情况发生在三个候选中有多个最小值,指针都要右移,不然会存入重复的丑数 32 | if (res[i] == m2) t2++; 33 | if (res[i] == m3) t3++; 34 | if (res[i] == m5) t5++; 35 | } 36 | return res[index - 1]; 37 | } 38 | } 39 | 40 | ``` 41 | 42 | --- 43 | 44 | by @sunhaiyu 45 | 46 | 2018.1.25 47 | 48 | -------------------------------------------------------------------------------- /notes/剑指offer面试题53--数组中的逆序对.md: -------------------------------------------------------------------------------- 1 | # 剑指offer面试题53--数组中的逆序对 2 | 3 | > ``` 4 | > 在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数. 5 | > ``` 6 | 7 | 暴力法很直观,拿第一个数和之后的每个数比较,然后拿第二个数和之后的每个数比较.....需要两个for循环可得到结果,时间复杂度为$O(n^2)$ 8 | 9 | 用数组的归并过程来分析这道题,归并排序就是自上而下地将数组分割成左右两半子数组,然后递归地将子数组不断分割下去,最后子数组的大小为1,对于大小为1的子数组没有排序和归并的必要,因此递归到此结束;之后自下而上地对这若干个子数组两两归并、四四归并......每次归并后左右子数组都分别有序,最后再将整个数组归并,因而整个数组也有序了。 10 | 11 | 现在分析一个简单的例子,对于数组{7, 5, 6, 4},先分成了两个子数组,左子数组{7, 5}和右子数组{6, 4},进而分成左子数组{7}和右子数组{5}....7大于5,所以这是一个逆序对,同样6大于4也是一个逆序对。现在得到两个逆序对了。然后开始两两归并,左子数组和右子数组现已分别排序,{5, 7}和 {4, 6},**因为大的数在右边,所以考虑从右边开始比较**。比如7大于6,那么7肯定也大于4,所以如果左边某个数p1比右边某个数p2大了,p1无需再和p2之前的所有数进行比较,这就减少了比较次数。那么在右子数组中比7小的有多少呢?多举几个例子就能发现通用的公式:**p2及其之前的元素个数减去左子数组的长度。**要求整个数组的逆序对总数,只需将每个子数组中的逆序对个数累加即可。 12 | 13 | 理解上述分析后,其实本题就可以直接使用归并排序,只是在左子数组中的某个元素大于右子数组某个元素时,多加一步——计算逆序对个数即可。 14 | 15 | ```java 16 | package Chap5; 17 | 18 | public class InversePairs { 19 | public int inversePairs(int[] array) { 20 | if (array == null) return 0; 21 | int[] aux = new int[array.length]; 22 | return sort(array, aux, 0, array.length - 1); 23 | } 24 | 25 | private int sort(int[] array, int[] aux, int low, int high) { 26 | if (high <= low) return 0; 27 | int mid = low + (high - low) / 2; 28 | int left = sort(array, aux, low, mid); 29 | int right = sort(array, aux, mid + 1, high); 30 | int merged = merge(array, aux, low, mid, high); 31 | return left + right + merged; 32 | } 33 | 34 | private int merge(int[] array, int[] aux, int low, int mid, int high) { 35 | int count = 0; 36 | int len = (high - low) / 2; 37 | int i = mid; 38 | int j = high; 39 | 40 | for (int k = low; k <= high; k++) { 41 | aux[k] = array[k]; 42 | } 43 | 44 | for (int k = high; k >= low; k--) { 45 | if (i < low) array[k] = aux[j--]; 46 | else if (j < mid + 1) array[k] = aux[i--]; 47 | else if (aux[i] > aux[j]) { 48 | // 在归并排序的基础上,在这里求count 49 | count += j - low - len; 50 | array[k] = aux[i--]; 51 | } else array[k] = aux[j--]; 52 | } 53 | return count; 54 | } 55 | } 56 | 57 | ``` 58 | 59 | 关键就是merge方法,和传统的归并排序相比,有了返回值,在`aux[i] > aux[j]`时多了一句`count += j - low - len;`以计算逆序对的个数。**j - low得到p2及其之前元素的个数,len表示左子数组的大小,按上面的分析,这个值就是逆序对的个数。** 60 | 61 | 我们知道归并排序的时间复杂度是$O(nlgn)$,但是需要O(n)的空间,所以这是个那空间换时间的例子。 62 | 63 | --- 64 | 65 | by @sunhaiyu 66 | 67 | 2018.1.27 68 | -------------------------------------------------------------------------------- /notes/剑指offer面试题54--二叉搜索树中排名为k的结点.md: -------------------------------------------------------------------------------- 1 | # 剑指offer面试题54--二叉搜索树中排名为k的结点 2 | 3 | > ``` 4 | > 给定一颗二叉搜索树,请找出排名第k的结点。 5 | > ``` 6 | 7 | 注意是二叉搜索树,这说明对于任何结点,有父结点大于其左子结点且小于右子结点。**如果中序遍历这棵树,就能得到递增排序的序列。** 8 | 9 | 接下来就很简单了,只需中序遍历到第k个结点,然后立即返回就行了。感觉对于这道题,非递归的中序遍历更好写一点。 10 | 11 | ```java 12 | package Chap6; 13 | 14 | import java.util.LinkedList; 15 | 16 | public class KthNode { 17 | public TreeNode findKthNode(TreeNode pRoot, int k) { 18 | if (pRoot == null || k <= 0) return null; 19 | LinkedList stack = new LinkedList<>(); 20 | int count = 0; 21 | while (pRoot != null || !stack.isEmpty()) { 22 | while (pRoot != null) { 23 | stack.push(pRoot); 24 | pRoot = pRoot.left; 25 | } 26 | if (!stack.isEmpty()) { 27 | pRoot = stack.pop(); 28 | // 一个计数器,遍历到第k个就立即返回 29 | if (++count == k) return pRoot; 30 | pRoot = pRoot.right; 31 | } 32 | } 33 | return null; 34 | } 35 | } 36 | 37 | ``` 38 | 39 | --- 40 | 41 | by @sunhaiyu 42 | 43 | 2018.1.29 44 | -------------------------------------------------------------------------------- /notes/剑指offer面试题6--从尾到头打印链表.md: -------------------------------------------------------------------------------- 1 | # 剑指offer面试题6--从尾到头打印链表 2 | 3 | > ``` 4 | > 输入一个链表的头节点,从尾到头打印链表每个节点的值。 5 | > ``` 6 | 7 | ## 使用栈 8 | 9 | 我的做法:典型的后进先出,使用栈,顺序遍历一遍链表,依次将每个值入栈。得到的就是尾节点在前,头节点在后的列表。 10 | 11 | ```java 12 | package Chap2; 13 | 14 | import java.util.LinkedList; 15 | import java.util.ArrayList; 16 | 17 | public class FromTail2Head { 18 | // 节点类的定义 19 | private class ListNode { 20 | int val; 21 | ListNode next = null; 22 | 23 | ListNode(int val) { 24 | this.val = val; 25 | } 26 | } 27 | 28 | /** 29 | * 更推荐使用栈,正序压入,尾节点就在最前面了 30 | * 31 | * @param listNode 链表的头结点 32 | * @return 从尾到头排列的结点 33 | */ 34 | public ArrayList printListFromTailToHead(ListNode listNode) { 35 | LinkedList stack = new LinkedList<>(); 36 | for (ListNode node = listNode; node != null; node = node.next) { 37 | stack.push(node.val); 38 | } 39 | return new ArrayList<>(stack); 40 | } 41 | } 42 | ``` 43 | 44 | 使用了“万能的”LinkedList,将其当栈使用。 45 | 46 | ## 使用递归 47 | 48 | 递归的本质也是栈(系统创建的),下面采用递归的解法。 49 | 50 | ```java 51 | /** 52 | * 利用递归,先递归到最后一个结点后开始依次返回。链表如果很长不适合用递归,递归深度将很大 53 | */ 54 | 55 | // 这是一个类成员变量 56 | private ArrayList a = new ArrayList<>(); 57 | 58 | public ArrayList printListFromTailToHead2(ListNode listNode) { 59 | if (listNode != null) { 60 | printListFromTailToHead(listNode.next); 61 | a.add(listNode.val); 62 | } 63 | return a; 64 | } 65 | ``` 66 | 67 | 可以看到如果没到链表尾部,就不断递归,直到最后一个节点,之后开始返回,执行下一句add方法,此时第一个被添加的是尾节点,最后一步递归调用的返回(即第一次递归调用)将添加最开始的头节点。因此得到的也是从尾到头的列表。 68 | 69 | 递归通常代码更简洁,但是如果**链表很长时,递归调用的层级十分深,速度将大大降低。**所以更推荐用第一种——栈的方法。 70 | 71 | --- 72 | 73 | by @sunhaiyu 74 | 75 | 2017.12.14 76 | -------------------------------------------------------------------------------- /notes/剑指offer面试题61--扑克牌中的顺子.md: -------------------------------------------------------------------------------- 1 | # 剑指offer面试题61--扑克牌中的顺子 2 | 3 | > ``` 4 | > 从扑克牌中随机抽5张牌,判断是不是一个顺子,即这五张牌是不是连续的。2~10是数字本身,A为1,J为11,Q为12,K为13,而大小王可以看成任意数字。 5 | > ``` 6 | 7 | 这道题有两种思路。 8 | 9 | ## 方法1:用大小王填补间隔 10 | 11 | 正常的顺子比如23456,假如含有大小王呢?03467也能构成顺子,虽然4和6中间有一个间隔,但是因为存在一张大小王,刚好可以让它成为5而填补上4和6之间的间隔。再假设是00367也能构成顺子,3和6之间有两个间隔但是刚好有两个王可以填补。更特殊的02345, 00234, 这种没有本身间隔的牌组合,因为大小王可以作任何数字,显然也构成顺子。 12 | 13 | 接下来看不能构成顺子的例子,34578,以及03678或者00378,可以发现**当王的个数比间隔数少的话就不能构成顺子,反之如果王的个数大于等于间隔数,就能构成顺子。** 14 | 15 | 要计算相邻两张牌的间隔,需要牌组合已经有序,因此第一步就要对五张牌排序。因此这种思路需要O(nlgn)的时间。 16 | 17 | 基于上述思路写出如下代码: 18 | 19 | ```java 20 | package Chap6; 21 | 22 | import java.util.Arrays; 23 | 24 | public class PlayCard { 25 | /** 26 | * 方法1:排序,计算大小王的个数和总间隔 27 | */ 28 | public boolean isContinuous(int[] numbers) { 29 | if (numbers == null || numbers.length != 5) return false; 30 | Arrays.sort(numbers); 31 | int joker = 0; 32 | // 统计大小王的个数,题目说了最多四个 33 | for (int i = 0; i < 4; i++) { 34 | if (numbers[i] == 0) joker++; 35 | } 36 | // 是对子直接返回false 37 | // 相邻数字gap为0,4~6之间间隔1,所以有减1的操作 38 | int totalGap = 0; 39 | for (int i = numbers.length - 1; i > joker; i--) { 40 | if (numbers[i] == numbers[i - 1]) return false; 41 | // 统计总间隔 42 | totalGap += numbers[i] - numbers[i - 1] - 1; 43 | } 44 | // 总的间隔要小于joker的数量才是顺子 45 | return totalGap <= joker; 46 | } 47 | } 48 | 49 | ``` 50 | 51 | ## 方法2:顺子本身的规律 52 | 53 | 五张牌组合成的顺子,都有哪些共性呢?如果牌中没有大小王,那么23456, 56789等这样的组合才是顺子;即五张牌中最大值和最小值的差始终是4,且任意两张牌不重复;现在假设有大小王(用0表示),02345、03467、00367都是顺子,但03678、00378不是顺子。发现大小王可多次出现,因此五张牌中0可以重复出现。除开0之外,其他牌不能重复出现且最大值与最小值的差小于5。综合以上两种情况,要想构成顺子,需要满足以下条件: 54 | 55 | - 除开0之外,其他任意牌不得重复出现 56 | - 除开0之外的其他牌,最大值和最小值的差要小于5 57 | 58 | 基于以上两条规则,可写出如下代码: 59 | 60 | ```java 61 | package Chap6; 62 | 63 | public class PlayCard { 64 | /** 65 | * 方法2:除了0之外,其他数字不可重复出现;最大最小值差不得超过5 66 | */ 67 | public boolean isContinuous2(int [] numbers) { 68 | if (numbers == null || numbers.length != 5) return false; 69 | int[] count = new int[14]; 70 | int max = -1; 71 | int min = 14; 72 | for (int number : numbers) { 73 | count[number]++; 74 | // 对除了0之外的其他数计算最大最小值 75 | if (number != 0) { 76 | if (count[number] > 1) return false; 77 | if (number > max) max = number; 78 | if (number < min) min = number; 79 | } 80 | } 81 | // 如果没有0,最大最小值之差为4,有0还能凑成顺子的,差值小于4;大于4肯定不能凑成顺子 82 | return max - min < 5; 83 | } 84 | } 85 | 86 | ``` 87 | 88 | 这种思路无需排序,时间复杂度为是O(n),但使用了额外空间,由于额外空间的大小固定为14,可以认为空间仍是常数级别的。 89 | 90 | --- 91 | 92 | by @sunhaiyu 93 | 94 | 2018.2.6 95 | -------------------------------------------------------------------------------- /notes/剑指offer面试题63--股票的最大利润.md: -------------------------------------------------------------------------------- 1 | # 剑指offer面试题63--股票的最大利润 2 | 3 | ```text 4 | 假设某股票的价格按照时间先后顺序存储在数组中,问买卖该股票一次可能获得的最大利润是多少? 5 | 如一支股票在某段时间内的价格为{9, 11, 8, 5, 7, 12, 16, 14}那么能在价格为5的时候购入并在价格为16时卖出,能获得最大利润11 6 | ``` 7 | 8 | 咋一看好像只要求出数组中的最大/最小值就完事了,但是数组中的值是按照时间顺序排列的,这就是说假如数组中最小值在最大值之后,它们的差值不会是股票的最大利润。因此我们要求的是数组中**排在后面的数(售出价格)与排在前面的数(购入价格)的最大差值**。 9 | 10 | 暴力法是$O(n^2)$的复杂度,就不考虑了。我们力求在一次循环中就求出最大差值。假设当前访问的数组下标是i,**只需记住当前下标之前所有元素中的最小值即可**,用一个int型的变量min维护这个最小值,每次用当前数字和min作差,差值用maxDiff保存,遍历一遍数组即可得出最大的差值。 11 | 12 | ```java 13 | package Chap6; 14 | 15 | public class MaxDiff { 16 | public int getMaxDiff(int[] prices) { 17 | if (prices == null || prices.length < 2) return 0; 18 | int min = prices[0]; 19 | int maxDiff = 0; 20 | for (int i = 1; i < prices.length; i++) { 21 | if (prices[i] < min) min = prices[i]; 22 | int curDiff = prices[i] - min; 23 | if (curDiff > maxDiff) maxDiff = curDiff; 24 | } 25 | return maxDiff; 26 | } 27 | } 28 | 29 | ``` 30 | 31 | 一次遍历就搞定,时间复杂度是$O(n)$ 32 | 33 | --- 34 | 35 | by @sunhaiyu 36 | 37 | 2018.2.13 -------------------------------------------------------------------------------- /notes/剑指offer面试题64--求1+2+3+...+n.md: -------------------------------------------------------------------------------- 1 | # 剑指offer面试题64--求1+2+3+...+n 2 | 3 | ```text 4 | 求1+2+3+...+n,要求不能使用乘除法、for、while、if、else、switch、case等关键词以及三元运算符等。 5 | ``` 6 | 7 | 这道题没有多大意义...... 8 | 9 | ## 方法1,递归 10 | 11 | 不过该做还是得做。循环不能用,很自然想到用递归。 12 | 13 | ```java 14 | package Chap6; 15 | 16 | public class Sum { 17 | public int Sum_Solution(int n) { 18 | if (n == 1) return 1; 19 | return n + Sum_Solution(n -1); 20 | } 21 | } 22 | 23 | ``` 24 | 25 | 但是题目又要求不能用if语句,那就把if替换掉吧。**使用逻辑与的短路特性**可以构造出一个条件语句出来,逻辑与的短路特性是指:如果前面的条件已经不成立,后面的条件不会被判断了。上面的递归中当`n == 1`时就终止递归了直接返回n(也就是1);所以只需将上面程序改一改: 26 | 27 | ```java 28 | public int Sum_Solution(int n) { 29 | int sum = n; 30 | boolean b = n > 0 && (sum += Sum_Solution(n - 1)) > 0; 31 | return sum; 32 | } 33 | ``` 34 | 35 | 当n > 0时,逻辑与两边都是true,因此递归会继续深入下去,但是当n == 1时,进行最后一次递归此时n == 0, `boolean b`由于不满足n > 0的条件,所以后面的递归不会得到执行,此时直接返回了`sum == n == 1`,和if语句达到了异曲同工的作用。 36 | 37 | ## 方法2,数学公式 38 | 39 | 等差数列求和公式大家都很熟悉,如下: 40 | 41 | $$S_n = n(n+1)/2$$ 42 | 43 | 题目中说了不能用乘除法,但是没说不能用**加法和位运算**,所以上式稍作变形可以得到 44 | 45 | $$S_n = (n^2 + n) >> 1$$ 46 | 47 | 其中`>> 1`表示右移一位,其实就是除以2。那这下就简单多了,哈哈。 48 | 49 | ```java 50 | public int sum2(int n) { 51 | return ((int)Math.pow(n, 2) + n) >> 1; 52 | } 53 | ``` 54 | 55 | --- 56 | 57 | by @sunhaiyu 58 | 59 | 2018.2.13 -------------------------------------------------------------------------------- /notes/剑指offer面试题65--不用加减乘除做加法.md: -------------------------------------------------------------------------------- 1 | # 剑指offer面试题65--不用加减乘除做加法 2 | 3 | ```text 4 | 写一个函数,求两个整数之和,要求在函数体内不得使用"+"、"-"、"x"、"÷"四则运算符号。 5 | ``` 6 | 7 | 不能用四则运算,自然容易想到用位运算。但如何使用位运算作加法呢?先来分析十进制是如何作加法的。比如要`18 + 6` 8 | 9 | ```text 10 | 1 8 11 | + 0 6 12 | —————— 13 | 1 4 14 | + 1 0 15 | —————— 16 | 2 4 17 | 18 | 1) 个位和个位相加,十位与十位相加,但不进位; 19 | 2) 6 + 8产生一位进位得到10,1 + 0不产生进位; 20 | 3) 不进位的和14 + 个位的进位10 = 24。 21 | 22 | 没有进位了,计算停止。 23 | 24 | ``` 25 | 26 | 可以看到十进制作加法可以细分为以上三步。那么对于二进制的加法呢?18的二进制是10010,6的二进制是110 27 | 28 | ```text 29 | 10010 30 | + 00110 31 | ———————— 32 | 10100 33 | + 00100 (还有进位,继续计算) 34 | ———————— 35 | 10000 36 | + 01000 (没有进位,停止计算) 37 | ———————— 38 | 10100 39 | ``` 40 | 41 | 10100正好是24的十进制表示。换成二进制作加法,原理一样。关键是:**如果还有进位,要不断累加,直到没有进位,此时计算才停止。** 42 | 43 | 对于二进制,**不进位的的加法就是异或运算,计算进位只需将两个数相与并向左移一位(也就是进位)。** 44 | 45 | 接下来写代码就不难了。 46 | 47 | ```java 48 | package Chap6; 49 | 50 | public class Add { 51 | public int add(int num1, int num2) { 52 | // num2 != 0 说明还有进位需要相加 53 | while (num2 != 0) { 54 | int sum = num1 ^ num2; 55 | int carry = (num1 & num2) << 1; 56 | num1 = sum; 57 | num2 = carry; 58 | } 59 | return num1; 60 | } 61 | } 62 | 63 | ``` 64 | 65 | 上面代码中sum表示不进位的加法和,carry表示进位,每次迭代都将sum和carry进行不进位相加,num2等于0(即不再有进位)时,退出循环返回num1即可。 66 | 67 | ## 扩展--不引用新变量交换两个变量 68 | 69 | 一般写个swap方法,需要引入第三个变量。如下 70 | 71 | ```java 72 | public int swap(int a, int b) { 73 | int temp = a; 74 | a = b; 75 | b = temp; 76 | } 77 | ``` 78 | 79 | 如果要交换的数据类型是int型的数字,如何不使用新的变量,交换两个变量的值?使用如下的运算即可完成。 80 | 81 | ```java 82 | a = a + b; 83 | b = a - b; 84 | a = a - b; 85 | 86 | 或者 87 | 88 | a = a ^ b; 89 | b = a ^ b; 90 | a = a ^ b; 91 | ``` 92 | 93 | 在纸上推导下就能验证上面式子的正确性。 94 | 95 | --- 96 | 97 | by @sunhaiyu 98 | 99 | 2018.2.13 100 | -------------------------------------------------------------------------------- /notes/剑指offer面试题66--构建乘积数组.md: -------------------------------------------------------------------------------- 1 | # 剑指offer面试题66--构建乘积数组 2 | 3 | ```text 4 | 给定一个数组A[0, 1,...n - 1],请构建一个数组A[0, 1,...n - 1],其中B中的元素B[i] = A[0]*A[1]*...*A[i - 1]*A[i + 1]*...*A[n - 1] 5 | 6 | 不能使用除法。 7 | ``` 8 | 9 | 如果可以使用除法,直接除以A[i]就可以得到B[i],但是现在要求了不能使用除法,只好另辟蹊径了。 10 | 11 | 一种方法是剔除A[i]进行连乘计算B[i],计算一次的时间是 $O(n)$,需要计算n次所以总的时间复杂度为$O(n^2)$ 12 | 13 | 有没有$O(n)$的算法呢。 14 | 15 | 注意到B[i]是 **除了A[i]** 之外A[0]到A[n - 1]的连乘。那么可以考虑从A[i]处将乘积分成两部分。`C[i] = A[0]*A[1]*...*A[i - 1],D[i] = A[i + 1]*...A[n - 2]*A[n -1]`; 16 | 17 | 而`C[i] = C[i -1]*A[i -1],D[i] = D[i + 1]*A[i + 1]` 18 | 19 | 根据这些关系计算出C[i]和D[i],最后求得`B[i] = C[i] * D[i]`即可。 20 | 21 | ![](http://picmeup.oss-cn-hangzhou.aliyuncs.com/coding/Snipaste_2018-10-20_21-55-32.png) 22 | 23 | 如上图,表中每一行的第一个数就是其后每个数字连乘,没有被乘上的数字A[i]就用1表示了,反正乘积不变。显然C[0] = 1,我们可以用`C[i] = C[i -1]*A[i -1]`计算出对角线左边的三角中各个A[i]的值;显然D[n -1] = 1,再`D[i] = D[i + 1]*A[i + 1]`计算对角线右边的三角中各个A[i]的值,然后将两者相乘就能得到B[i]。 24 | 25 | ```java 26 | package Chap6; 27 | 28 | public class Multiply { 29 | public int[] multiply(int[] A) { 30 | if (A == null || A.length < 2) return null; 31 | int len = A.length; 32 | int[] B = new int[len]; 33 | B[0] = 1; 34 | for (int i = 1; i < len; i++) { 35 | // 计算C[i] 36 | B[i] = B[i - 1] * A[i - 1]; 37 | } 38 | int d = 1; 39 | for (int j = len - 2; j >= 0; j--) { 40 | // 计算D[i] 41 | d *= A[j + 1]; 42 | // B[i] = C[i] * D[i] 43 | B[j] *= d; 44 | } 45 | return B; 46 | } 47 | } 48 | 49 | ``` 50 | 51 | 计算C[i]是一个for循环,花了$O(n)$的时间,计算D[i]并顺便计算B[i]也是一个for循环,花了$O(n)$的时间,总的时间复杂度为$O(n)$ 52 | 53 | --- 54 | 55 | by @sunhaiyu 56 | 57 | 2108.2.13 58 | -------------------------------------------------------------------------------- /notes/剑指offer面试题67--把字符串转换成整数.md: -------------------------------------------------------------------------------- 1 | # 剑指offer热身--字符串转整数 2 | 3 | ```text 4 | 将一个字符串转换成一个整数,要求不能使用字符串转换整数的库函数。 数值为0或者字符串不是一个合法的数值则返回0 5 | ``` 6 | 7 | 我们知道Java内置了`Integer.parseInt(String s)`,直接调用即可。如果要自己写呢,你可能很快想出如下代码 8 | 9 | ```java 10 | public static int str2Int { 11 | // 核心代码 12 | int number = 0; 13 | for (int i = 0; i < s.length(); i++) { 14 | // '4'的ASCII码就比'0'的ASCII码大4,所以可以减去'0'的ASCII码将字符转数字 15 | number = number * 10 + s.charAt(i) - '0'; 16 | } 17 | } 18 | ``` 19 | 20 | `s.charAt(i)`取出的每一位是ASCII码,需要将其转换为数字。**我们知道字符'0' ~'9'在ASCII码表中是连续的,这意味着字符'4'就比字符'0'大4,因此任何数字字符减去'0'的ASCII码得到的值就是该数字字符对应的数字。** 21 | 22 | 可是这样的程序稳定吗?试想下面的测试用例`[null,"", "+34", "-56"]`,任意一个都不能通过测试。**因此一定要考虑边界条件和错误检查。** 23 | 24 | 首先判断是否为空指针,否则一切方法调用都会引发空指针异常,然后是空字符串判断;之后对于字符串的首字符要特殊对待,首字符除了可以是数字,还可以是正负号,首字符之后的其他字符只能是数字。 25 | 26 | 再看细节,如果首字符是负号,那么在返回数字的时候也应该是负数。如果首字符是正号,我们知道符号可以省略不写。再次,如果字符串转换成数字后**溢出**,应该返回0。最后对于包括溢出在内的各种非法输入,约定返回0,可如果我们输入的就是'0',转换的结果也是0,这种情况下根据返回值0要怎么区分到底是非法输入呢还是本身就是输入了0?这里我们准备使用一个全局的布尔变量用以区分。 27 | 28 | 考虑了那么多,可以写出如下严谨很多的代码。 29 | 30 | ```java 31 | package Chap7; 32 | 33 | public class Str2Int { 34 | public static boolean valid; 35 | 36 | public static int strToInt(String str) { 37 | valid = false; 38 | if (str == null || str.length() == 0) { 39 | return 0; 40 | } 41 | 42 | boolean isNegative = false; 43 | long number = 0; 44 | for (int i = 0; i < str.length(); i++) { 45 | // 第一位是正负号就跳过 46 | if (i == 0 && str.charAt(i) == '+' || str.charAt(i) == '-') { 47 | if (str.charAt(i) == '-') { 48 | isNegative = true; 49 | } 50 | if (str.length() == 1) { 51 | return 0; 52 | } 53 | continue; 54 | } 55 | // 中间有任何字符不是数字直接返回 56 | if (str.charAt(i) < '0' || str.charAt(i) > '9') { 57 | return 0; 58 | } 59 | 60 | int flag = isNegative ? -1 : 1; 61 | // '4'的ASCII码就比'0'的ASCII码大4,所以可以减去'0'的ASCII码将字符转数字 62 | number = number * 10 + flag * (str.charAt(i) - '0'); 63 | if (!isNegative && number > Integer.MAX_VALUE || isNegative && number < Integer.MIN_VALUE) { 64 | return 0; 65 | } 66 | } 67 | // 全部字符都检查过了,说明字符串合法 68 | valid = true; 69 | return (int)number; 70 | } 71 | 72 | public static void main(String[] args) { 73 | System.out.println(strToInt("12")); 74 | System.out.println(strToInt("-12")); 75 | System.out.println(strToInt("+12")); 76 | System.out.println(strToInt("+")+ " "+ Str2Int.valid); 77 | System.out.println(strToInt("0")+ " "+ Str2Int.valid); 78 | System.out.println(strToInt("12345678901112")+ " "+ Str2Int.valid); 79 | } 80 | } 81 | 82 | ``` 83 | 84 | --- 85 | 86 | by @sunhaiyu 87 | 88 | 2018.2.14 89 | -------------------------------------------------------------------------------- /notes/剑指offer面试题7--重建二叉树.md: -------------------------------------------------------------------------------- 1 | # 剑指offer面试题7--重建二叉树 2 | 3 | > ``` 4 | > 输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。 5 | > ``` 6 | 7 | 由中序遍历序列和前序或后序任一序列就能确定唯一的一棵二叉树,中序遍历序列是必须要的。 8 | 9 | 二叉树本来就是一种递归的结构,某个父结点,你可以说它拥有一个左子结点和一个右子结点,也可以说它拥有一棵以左子结点为根的左子树和一颗右子结点为根的右子树。因此二叉树的构建采用递归的方法是一种很自然的想法。 10 | 11 | 一棵树从上到下可以分解成了左子树、根结点、右子树,对于每一棵子树,又可以继续分解下去...一直到树底的叶子结点。相反,树的构建是从下到上的顺序,叶子结点可以看做最小的子树,这些子树的根结点成为其他结点的左右子结点,于是产生了更大的子树,这些子树继续成为上层结点的左右子结点....一直到根结点的做右子结点也确定下来。 12 | 13 | 关键是要找到每棵树的根结点,**前序遍历的第一个结点就是树的根结点;在中序遍历里找到这个结点,其左边的结点都是根结点的左子树,其右边的结点都是根结点的右子树。假如根结点左边有M个结点,那么在前序序列中,根结点后的M个结点也是属于根结点的左子树的。前序序列中余下的后面的结点自然属于根结点的右子树**。这样就可以把中序遍历的数组从根结点处分解成左右子树(对应的有两个子数组),然后递归地对这两个子数组执行同样的操作。现在重点是要在子数组中找到根结点——它依然是数组的第一个元素! 14 | 15 | 比如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6}。前序序列可知根结点是1,于是从中序序列可知,{4, 7, 2}属于根结点1的左子树,有3个结点;{5, 3 ,8, 6}属于根结点1的右子树。回到前序序列,除开第一个根结点,其后3个结点{2, 4, 7}就是左子树,余下的{3, 5, 6 ,8}是右子树。按照这些关系可以精确地将数组分解成两个子数组,递归地对两个数组进行同样的操作即可。 16 | 17 | ```java 18 | package Chap2; 19 | 20 | 21 | public class ReConstructTree { 22 | 23 | private class TreeNode { 24 | int val; 25 | TreeNode left; 26 | TreeNode right; 27 | 28 | TreeNode(int x) { 29 | val = x; 30 | } 31 | } 32 | 33 | public TreeNode reConstructBinaryTree(int[] pre, int[] in) { 34 | TreeNode root = reConstructBinaryTree(pre, 0, pre.length - 1, in, 0, in.length - 1); 35 | return root; 36 | } 37 | 38 | /** 39 | * 递归! 40 | * [preStart + 1, preStart + i - inStart]是前序序列中左子树封闭区间 41 | * [preStart + i - inStart + 1, preEnd]是前序序列中右子树封闭区间 42 | * 43 | * [inStart, i - 1]是中序序列中左子树封闭区间 44 | * [i + 1, inEnd]是中序序列中右子树封闭区间 45 | * 46 | * @param pre 前序序列 47 | * @param preStart 前序序列封闭区间的左指针 48 | * @param preEnd 前序序列封闭区间的右指针 49 | * @param in 中序序列 50 | * @param inStart 中序序列封闭区间的左指针 51 | * @param inEnd 中序序列封闭区间的右指针 52 | * @return 树的根结点 53 | */ 54 | private TreeNode reConstructBinaryTree(int[] pre, int preStart, int preEnd, int[] in, int inStart, int inEnd) { 55 | // 还能分解成子数组就继续递归,不能分解(表现为end > start),就返回空子树给父结点 56 | if (preStart > preEnd || inStart > inEnd) { 57 | return null; 58 | } 59 | int rootVal = pre[preStart]; 60 | TreeNode root = new TreeNode(rootVal); 61 | for (int i = inStart; i <= inEnd; i++) { 62 | if (in[i] == rootVal) { 63 | root.left = reConstructBinaryTree(pre, preStart + 1, preStart + i - inStart, in, inStart, i - 1); 64 | root.right = reConstructBinaryTree(pre, preStart + i - inStart + 1, preEnd, in, i + 1, inEnd); 65 | } 66 | } 67 | return root; 68 | } 69 | } 70 | 71 | ``` 72 | 73 | --- 74 | 75 | by @sunhaiyu 76 | 77 | 2017.12.16 -------------------------------------------------------------------------------- /notes/剑指offer面试题8--二叉树中序遍历的下一个结点.md: -------------------------------------------------------------------------------- 1 | # 剑指offer面试题8--二叉树中序遍历的下一个结点 2 | 3 | > ``` 4 | > 给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。 5 | > ``` 6 | 7 | 要找出中序遍历的下一个结点,要分几种情况探讨。 8 | 9 | - 如果当前结点的右子结点不为空,那么下一个结点就是以该右子结点为根的子树的最左子结点; 10 | - 如果当前结点的右子结点为空,看它的父结点。此时分两种情况,如果父结点的右子结点就是当前结点,说明这个结点在中序遍历中已经被访问过了,需要继续往上看其父结点...直到父结点的左子结点是当前结点为止,该父结点就是下一个结点。如果在一直往上的过程中已经到达根结点,而根结点的父结点为null,这种情况说明当前结点已经是中序序列的最后一个结点了,不存在下一个结点,应该返回null. 11 | 12 | ```java 13 | package Chap2; 14 | 15 | 16 | public class InOrderNextNode { 17 | 18 | private class TreeLinkNode { 19 | int val; 20 | TreeLinkNode left = null; 21 | TreeLinkNode right = null; 22 | // next指向父结点 23 | TreeLinkNode next = null; 24 | 25 | TreeLinkNode(int val) { 26 | this.val = val; 27 | } 28 | } 29 | 30 | public TreeLinkNode GetNext(TreeLinkNode pNode) { 31 | // 如果当前结点右子树不空,那么中序下一个结点是右子树的最左子结点(如果有的话);如果右子树没有左子结点就返回右子树根结点 32 | if (pNode.right != null) { 33 | pNode = pNode.right; 34 | while (pNode.left != null) { 35 | pNode = pNode.left; 36 | } 37 | return pNode; 38 | } 39 | // 如果当前子结点pNode右子树为空 40 | // 返回上层的父结点,如果父结点的右子结点就是当前结点,继续返回到上层的父结点...直到父结点的左子结点等于当前结点 41 | while (pNode.next != null && pNode.next.right == pNode) { 42 | pNode = pNode.next; 43 | } 44 | // 如果父结点的左子结点等于当前结点,说明下一个要遍历的结点就是父结点了;或者父结点为空(说明当前结点是root),还是返回父结点(null) 45 | // pNode.next == null 或者 pNode.next.left == pNode 46 | return pNode.next; 47 | } 48 | } 49 | 50 | ``` 51 | 52 | --- 53 | 54 | by @sunhaiyu 55 | 56 | 2017.12.16 57 | 58 | -------------------------------------------------------------------------------- /notes/剑指off面试题34--二叉树中和为某一值的路径.md: -------------------------------------------------------------------------------- 1 | # 剑指off面试题34--二叉树中和为某一值的路径 2 | 3 | > ``` 4 | > 输入一颗二叉树和一个整数,打印出二叉树中结点值的和为输入整数的所有路径。路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。 5 | > ``` 6 | 7 | 很自然的想法是每条路径都和目标值比较,如果相同就将这条路径存入一个集合中,本质上是个**深度优先搜索**。 8 | 9 | 路径可以用一个列表表示,因为满足要求的路径首先要是从根结点到叶子结点的一个结点序列,而只有前序遍历能先存入根结点。使用前序遍历,每访问一个结点随即就存入到当前路径中,直到访问并存入叶子结点后,将当前路径中结点的值么目标值进行比较,如果相同就存入一个集合中,当左叶子结点那条路径比较后,就切换到右叶子结点(叶子结点)那条路径,此时需要从列表中移除刚存入的左叶子结点,而且当左右叶子结点的路径都比较完后,需要返回上层结点,得继续从列表的末尾移除结点。也就是当前路径始终存放的是**从根结点到当前结点**的所有结点。 10 | 11 | 表示路径的列表可以用栈,但是在打印路径时,由于要先打印出根结点,而栈中根结点在栈底,不好获取到。因此采用ArrayList就好了,可以像栈一样删除末尾元素,也可以很方便地访问到先存入的根结点。 12 | 13 | ```java 14 | package Chap4; 15 | 16 | import java.util.ArrayList; 17 | 18 | public class FindPathInBST { 19 | private class TreeNode { 20 | int val = 0; 21 | TreeNode left = null; 22 | TreeNode right = null; 23 | 24 | public TreeNode(int val) { 25 | this.val = val; 26 | 27 | } 28 | 29 | } 30 | 31 | /** 32 | * @param root 二叉树根结点 33 | * @param target 目标值 34 | * @return 所有和目标值相同的路径上结点的集合 35 | */ 36 | public ArrayList> FindPath(TreeNode root,int target) { 37 | ArrayList> res = new ArrayList<>(); 38 | if(root == null) return res; 39 | ArrayList path = new ArrayList<>(); 40 | preOrder(root, target, path, res); 41 | return res; 42 | } 43 | 44 | private void preOrder(TreeNode root, int curVal,ArrayList path, ArrayList> res) { 45 | if (root == null) return; 46 | // 模拟结点进栈 47 | path.add(root.val); 48 | curVal -= root.val; 49 | // 只有在叶子结点处才判断是否和目标值相同,若相同加入列表中 50 | if (root.left == null && root.right == null) { 51 | if (curVal == 0) res.add(new ArrayList<>(path)); 52 | } 53 | preOrder(root.left, curVal, path, res); 54 | preOrder(root.right, curVal, path, res); 55 | // 模拟结点出栈 56 | path.remove(path.size() - 1); 57 | curVal += root.val; 58 | } 59 | } 60 | ``` 61 | 62 | 最为关键的就是这句`path.remove(path.size() - 1);`它**模拟了递归调用方法结束,当前访问结点的出栈。** 63 | 64 | --- 65 | 66 | by @sunhaiyu 67 | 68 | 2018.1.11 --------------------------------------------------------------------------------