diff --git a/notes/剑指 offer 题解.md b/notes/剑指 offer 题解.md index 4851618c..c0f792fb 100644 --- a/notes/剑指 offer 题解.md +++ b/notes/剑指 offer 题解.md @@ -461,6 +461,8 @@ public class Solution { 一只青蛙一次可以跳上 1 级台阶,也可以跳上 2 级。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。 +## 解题思路 + ```java public int JumpFloor(int n) { if (n == 1) return 1; @@ -480,6 +482,8 @@ public int JumpFloor(int n) { 一只青蛙一次可以跳上 1 级台阶,也可以跳上 2 级……它也可以跳上 n 级。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。 +## 解题思路 + ```java public int JumpFloorII(int n) { int[] dp = new int[n]; @@ -499,6 +503,8 @@ public int JumpFloorII(int n) { 我们可以用 2\*1 的小矩形横着或者竖着去覆盖更大的矩形。请问用 n 个 2\*1 的小矩形无重叠地覆盖一个 2\*n 的大矩形,总共有多少种方法? +## 解题思路 + ```java public int RectCover(int n) { if (n < 2) return n; @@ -685,7 +691,7 @@ int maxProductAfterCuttin(int length) { 输入一个整数,输出该数二进制表示中 1 的个数。其中负数用补码表示 -**使用库函数** +### Integer.bitCount() ```java public int NumberOf1(int n) { @@ -693,9 +699,11 @@ public int NumberOf1(int n) { } ``` -**O(logM) 时间复杂度解法,其中 M 表示 1 的个数** +### n&(n-1) -n&(n-1) 该位运算是去除 n 的位级表示中最低的那一位。例如对于二进制表示 10110100,减去 1 得到 10110011,这两个数相与得到 10110000。 +O(logM) 时间复杂度解法,其中 M 表示 1 的个数。 + +该位运算是去除 n 的位级表示中最低的那一位。例如对于二进制表示 10110100,减去 1 得到 10110011,这两个数相与得到 10110000。 ```java public int NumberOf1(int n) { @@ -782,16 +790,16 @@ private void printNumber(char[] number) { ## 解题思路 -- 如果链表不是尾节点,那么可以直接将下一个节点的值赋给节点,令节点指向下下个节点,然后删除下一个节点,时间复杂度为 O(1)。 +① 如果链表不是尾节点,那么可以直接将下一个节点的值赋给节点,令节点指向下下个节点,然后删除下一个节点,时间复杂度为 O(1)。

-- 否则,就需要先遍历链表,找到节点的前一个节点,然后让前一个节点指向节点的下一个节点,时间复杂度为 O(N)。 +② 否则,就需要先遍历链表,找到节点的前一个节点,然后让前一个节点指向节点的下一个节点,时间复杂度为 O(N)。

-- 综上,如果进行 N 次操作,那么大约需要移动节点的次数为 N-1+N=2N-1,其中 N-1 表示不是链表尾节点情况下的移动次数,N 表示是尾节点情况下的移动次数。(2N-1)/N \~ 2,因此该算法的时间复杂度为 O(1)。 +③ 综上,如果进行 N 次操作,那么大约需要移动节点的次数为 N-1+N=2N-1,其中 N-1 表示不是链表尾节点情况下的移动次数,N 表示是尾节点情况下的移动次数。(2N-1)/N \~ 2,因此该算法的时间复杂度为 O(1)。 ```java public ListNode deleteNode(ListNode head, ListNode tobeDelete) { @@ -819,9 +827,8 @@ Input : 1->2->3->3->4->4->5 Output : 1->2->5 ``` -**解题描述** +## 解题描述 -递归。 ```java public ListNode deleteDuplication(ListNode pHead) { @@ -844,7 +851,9 @@ public ListNode deleteDuplication(ListNode pHead) { ## 题目描述 -请实现一个函数用来匹配包括 '.' 和 '\*' 的正则表达式。模式中的字符 '.' 表示任意一个字符,而 '\*' 表示它前面的字符可以出现任意次(包含 0 次)。 在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串 "aaa" 与模式 "a.a" 和 "ab\*ac\*a" 匹配,但是与 "aa.a" 和 "ab\*a" 均不匹配. +请实现一个函数用来匹配包括 '.' 和 '\*' 的正则表达式。模式中的字符 '.' 表示任意一个字符,而 '\*' 表示它前面的字符可以出现任意次(包含 0 次)。 在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串 "aaa" 与模式 "a.a" 和 "ab\*ac\*a" 匹配,但是与 "aa.a" 和 "ab\*a" 均不匹配。 + +## 解题思路 ```java public boolean match(char[] s, char[] p) { @@ -879,6 +888,8 @@ public boolean match(char[] s, char[] p) { 请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串 "+100","5e2","-123","3.1416" 和 "-1E-16" 都表示数值。 但是 "12e","1a3.14","1.2.3","+-5" 和 "12e+4.3" 都不是。 +## 解题思路 + ```java public boolean isNumeric(char[] str) { String string = String.valueOf(str); @@ -888,14 +899,13 @@ public boolean isNumeric(char[] str) { # 21. 调整数组顺序使奇数位于偶数前面 -**题目要求** +## 题目描述 保证奇数和奇数,偶数和偶数之间的相对位置不变,这和书本不太一样。 ## 解题思路 -- 时间复杂度 : O(n2) -- 空间复杂度 : O(1) +复杂度:O(n2) + O(1) ```java public void reOrderArray(int[] array) { @@ -915,8 +925,7 @@ public void reOrderArray(int[] array) { } ``` -- 时间复杂度 : O(n) -- 空间复杂度 : O(n) +复杂度:O(n) + O(n) ```java public void reOrderArray(int[] array) { @@ -937,6 +946,8 @@ public void reOrderArray(int[] array) { 设链表的长度为 N。设两个指针 P1 和 P2,先让 P1 移动 K 个节点,则还有 N - K 个节点可以移动。此时让 P1 和 P2 同时移动,可以知道当 P1 移动到链表结尾时,P2 移动到 N - K 个节点处,该位置就是倒数第 K 个节点。 +## 解题思路 +

```java @@ -988,8 +999,6 @@ public ListNode EntryNodeOfLoop(ListNode pHead) { ## 解题思路 -头插法 - ```java public ListNode ReverseList(ListNode head) { ListNode newList = new ListNode(-1); @@ -1009,6 +1018,8 @@ public ListNode ReverseList(ListNode head) {

+## 解题思路 + ```java public ListNode Merge(ListNode list1, ListNode list2) { ListNode head = new ListNode(-1); @@ -1035,6 +1046,8 @@ public ListNode Merge(ListNode list1, ListNode list2) {

+## 解题思路 + ```java public boolean HasSubtree(TreeNode root1, TreeNode root2) { if (root1 == null || root2 == null) return false; @@ -1056,6 +1069,8 @@ private boolean isSubtree(TreeNode root1, TreeNode root2) {

+## 解题思路 + ```java public void Mirror(TreeNode root) { if (root == null) return; @@ -1079,6 +1094,8 @@ public void Mirror(TreeNode root) { 3 4 4 3 ``` +## 解题思路 + ```java boolean isSymmetrical(TreeNode pRoot) { if (pRoot == null) return true; @@ -1107,6 +1124,8 @@ boolean isSymmetrical(TreeNode t1, TreeNode t2) { 平衡二叉树左右子树高度差不超过 1。 +## 解题思路 + ```java private boolean isBalanced = true; @@ -1132,6 +1151,8 @@ private int height(TreeNode root) {

+## 解题思路 + ```java public ArrayList printMatrix(int[][] matrix) { ArrayList ret = new ArrayList<>(); @@ -1153,6 +1174,8 @@ public ArrayList printMatrix(int[][] matrix) { 定义栈的数据结构,请在该类型中实现一个能够得到栈最小元素的 min 函数。 +## 解题思路 + ```java private Stack stack = new Stack<>(); private Stack minStack = new Stack<>(); @@ -1185,6 +1208,8 @@ public int min() { 输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列 1,2,3,4,5 是某栈的压入顺序,序列 4,5,3,2,1 是该压栈序列对应的一个弹出序列,但 4,3,5,1,2 就不可能是该压栈序列的弹出序列。 +## 解题思路 + ```java public boolean IsPopOrder(int[] pushA, int[] popA) { int n = pushA.length; @@ -1241,6 +1266,8 @@ public ArrayList PrintFromTopToBottom(TreeNode root) { 和上题几乎一样。 +## 解题思路 + ```java ArrayList> Print(TreeNode pRoot) { ArrayList> ret = new ArrayList<>(); @@ -1268,6 +1295,8 @@ ArrayList> Print(TreeNode pRoot) { 请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推。 +## 解题思路 + ```java public ArrayList> Print(TreeNode pRoot) { ArrayList> ret = new ArrayList<>(); @@ -1306,6 +1335,8 @@ public ArrayList> Print(TreeNode pRoot) {

+## 解题思路 + ```java public boolean VerifySquenceOfBST(int[] sequence) { if (sequence.length == 0) return false; @@ -1334,6 +1365,8 @@ private boolean verify(int[] sequence, int first, int last) {

+## 解题思路 + ```java private ArrayList> ret = new ArrayList<>(); @@ -1420,6 +1453,8 @@ public RandomListNode Clone(RandomListNode pHead) {

+## 解题思路 + ```java private TreeNode pre = null; public TreeNode Convert(TreeNode pRootOfTree) { @@ -1445,6 +1480,8 @@ private void inOrder(TreeNode node) { 请实现两个函数,分别用来序列化和反序列化二叉树。 +## 解题思路 + ```java public class Solution { @@ -1481,6 +1518,8 @@ public class Solution { 输入一个字符串 , 按字典序打印出该字符串中字符的所有排列。例如输入字符串 abc, 则打印出由字符 a, b, c 所能排列出来的所有字符串 abc, acb, bac, bca, cab 和 cba。 +## 解题思路 + ```java private ArrayList ret = new ArrayList<>(); @@ -1538,10 +1577,11 @@ public int MoreThanHalfNum_Solution(int[] nums) { # 40. 最小的 K 个数 -**快速选择** +## 解题思路 -- 时间复杂度:O(N) -- 空间复杂度:O(1) +### 快速选择 + +- 复杂度:O(N) + O(1) - 只有当可以修改数组元素时才可以使用 快速排序的 partition() 方法,会返回一个整数 j 使得 a[lo..j-1] 小于等于 a[j],且 a[j+1..hi] 大于等于 a[j],此时 a[j] 就是数组的第 j 大元素,可以利用这个特性找出数组的第 K 个元素,这种找第 K 个元素的算法称为快速选择算法。 @@ -1601,10 +1641,9 @@ private boolean less(int v, int w) { } ``` -**大小为 K 的最小堆** +### 大小为 K 的最小堆 -- 时间复杂度:O(NlogK) -- 空间复杂度:O(K) +- 复杂度:O(NlogK) + O(K) - 特别适合处理海量数据 应该注意的是,应该使用大顶堆来维护最小堆,而不能直接创建一个小顶堆并设置一个大小,企图让小顶堆中的元素都是最小元素。 @@ -1632,6 +1671,8 @@ public ArrayList GetLeastNumbers_Solution(int[] input, int k) { 如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。 +## 解题思路 + ```java public class Solution { // 大顶堆,存储左半边元素 @@ -1672,6 +1713,8 @@ public class Solution { 请实现一个函数用来找出字符流中第一个只出现一次的字符。例如,当从字符流中只读出前两个字符 "go" 时,第一个只出现一次的字符是 "g"。当从该字符流中读出前六个字符“google" 时,第一个只出现一次的字符是 "l"。 +## 解题思路 + ```java public class Solution { private int[] cnts = new int[256]; @@ -1696,7 +1739,9 @@ public class Solution { ## 题目描述 -{6,-3,-2,7,-15,1,2,2},连续子向量的最大和为 8(从第 0 个开始,到第 3 个为止) +{6,-3,-2,7,-15,1,2,2},连续子向量的最大和为 8(从第 0 个开始,到第 3 个为止)。 + +## 解题思路 ```java public int FindGreatestSumOfSubArray(int[] nums) { @@ -1733,6 +1778,8 @@ public int NumberOf1Between1AndN_Solution(int n) { 数字以 0123456789101112131415... 的格式序列化到一个字符串中,求这个字符串的第 index 位。 +## 解题思路 + ```java public int digitAtIndex(int index) { if (index < 0) return -1; @@ -1804,6 +1851,8 @@ public String PrintMinNumber(int[] numbers) { 给定一个数字,按照如下规则翻译成字符串:0 翻译成“a”,1 翻译成“b”...25 翻译成“z”。一个数字有多种翻译可能,例如 12258 一共有 5 种,分别是 bccfi,bwfi,bczi,mcfi,mzi。实现一个函数,用来计算一个数字有多少种不同的翻译方法。 +## 解题思路 + ```java public int getTranslationCount(String number) { int n = number.length(); @@ -1861,6 +1910,8 @@ public int getMaxValue(int[][] values) { 输入一个字符串(只包含 a\~z 的字符),求其最长不含重复字符的子字符串的长度。例如对于 arabcacfr,最长不含重复字符的子字符串为 acfr,长度为 4。 +## 解题思路 + ```java public int longestSubStringWithoutDuplication(String str) { int curLen = 0; @@ -1887,6 +1938,8 @@ public int longestSubStringWithoutDuplication(String str) { 把只包含因子 2、3 和 5 的数称作丑数(Ugly Number)。例如 6、8 都是丑数,但 14 不是,因为它包含因子 7。 习惯上我们把 1 当做是第一个丑数。求按从小到大的顺序的第 N 个丑数。 +## 解题思路 + ```java public int GetUglyNumber_Solution(int N) { if (N <= 6) return N; @@ -1923,6 +1976,8 @@ public int FirstNotRepeatingChar(String str) { 在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数 P。 +## 解题思路 + ```java private long cnt = 0; private int[] tmp; // 在这里创建辅助数组,而不是在 merge() 递归函数中创建 @@ -2123,7 +2178,9 @@ public ArrayList FindNumbersWithSum(int[] array, int sum) { ## 题目描述 -和为 100 的连续序列有 18, 19, 20, 21, 22 +和为 100 的连续序列有 18, 19, 20, 21, 22。 + +## 解题思路 ```java public ArrayList> FindContinuousSequence(int sum) { @@ -2200,6 +2257,8 @@ private void reverse(char[] c, int start, int end) { 对于一个给定的字符序列 S,请你把其循环左移 K 位后的序列输出。例如,字符序列 S=”abcXYZdef”, 要求输出循环左移 3 位后的结果,即“XYZdefabc”。 +## 解题思路 + ```java public String LeftRotateString(String str, int k) { if (str.length() == 0) return ""; @@ -2225,7 +2284,9 @@ private void reverse(char[] c, int i, int j) { ## 题目描述 -给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值。例如,如果输入数组 {2, 3, 4, 2, 6, 2, 5, 1} 及滑动窗口的大小 3,那么一共存在 6 个滑动窗口,他们的最大值分别为 {4, 4, 6, 6, 6, 5}; +给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值。例如,如果输入数组 {2, 3, 4, 2, 6, 2, 5, 1} 及滑动窗口的大小 3,那么一共存在 6 个滑动窗口,他们的最大值分别为 {4, 4, 6, 6, 6, 5}。 + +## 解题思路 ```java public ArrayList maxInWindows(int[] num, int size) { @@ -2250,7 +2311,11 @@ public ArrayList maxInWindows(int[] num, int size) { 把 n 个骰子仍在地上,求点数和为 s 的概率。 -最直观的动态规划解法,O(n2) 的空间复杂度。 +## 解题思路 + +### 动态规划解法 + +空间复杂度:O(n2) ```java private static int face = 6; @@ -2276,7 +2341,9 @@ public double countProbability(int n, int s) { } ``` -使用旋转数组将空间复杂度降低为 O(n) +### 动态规划解法 + 旋转数组 + +空间复杂度:O(n) ```java private static int face = 6; @@ -2310,6 +2377,8 @@ public double countProbability(int n, int s) { 五张牌,其中大小鬼为癞子,牌面大小为 0。判断是否能组成顺子。 +## 解题思路 + ```java public boolean isContinuous(int [] numbers) { if(numbers.length < 5) return false; @@ -2350,6 +2419,8 @@ public int LastRemaining_Solution(int n, int m) { 可以有一次买入和一次卖出,买入必须在前。求最大收益。 +## 解题思路 + ```java public int maxProfit(int[] prices) { int n = prices.length; @@ -2368,7 +2439,9 @@ public int maxProfit(int[] prices) { ## 题目描述 -求 1+2+3+...+n,要求不能使用乘除法、for、while、if、else、switch、case 等关键字及条件判断语句(A?B:C) +求 1+2+3+...+n,要求不能使用乘除法、for、while、if、else、switch、case 等关键字及条件判断语句(A?B:C)。 + +## 解题思路 ```java public int Sum_Solution(int n) { @@ -2395,6 +2468,8 @@ public int Add(int num1, int num2) { 给定一个数组 A[0, 1,..., n-1], 请构建一个数组 B[0, 1,..., n-1], 其中 B 中的元素 B[i]=A[0]\*A[1]\*...\*A[i-1]\*A[i+1]\*...\*A[n-1]。不能使用除法。 +## 解题思路 + ```java public int[] multiply(int[] A) { int n = A.length;