diff --git a/notes/剑指 offer 题解.md b/notes/剑指 offer 题解.md index 0ac7d7e4..1bac6bbb 100644 --- a/notes/剑指 offer 题解.md +++ b/notes/剑指 offer 题解.md @@ -10,8 +10,8 @@ * [9. 用两个栈实现队列](#9-用两个栈实现队列) * [10.1 斐波那契数列](#101-斐波那契数列) * [10.2 跳台阶](#102-跳台阶) -* [10.3 变态跳台阶](#103-变态跳台阶) -* [10.4 矩形覆盖](#104-矩形覆盖) +* [10.3 矩形覆盖](#103-矩形覆盖) +* [10.4 变态跳台阶](#104-变态跳台阶) * [11. 旋转数组的最小数字](#11-旋转数组的最小数字) * [12. 矩阵中的路径](#12-矩阵中的路径) * [13. 机器人的运动范围](#13-机器人的运动范围) @@ -465,7 +465,7 @@ public int pop() throws Exception { ## 题目描述 -求菲波那契数列的第 n 项,n <= 39。 +求斐波那契数列的第 n 项,n <= 39。

@@ -535,23 +535,6 @@ public class Solution { ## 解题思路 -复杂度:O(N) + O(N) - -```java -public int JumpFloor(int n) { - if (n == 1) - return 1; - int[] dp = new int[n]; - dp[0] = 1; - dp[1] = 2; - for (int i = 2; i < n; i++) - dp[i] = dp[i - 1] + dp[i - 2]; - return dp[n - 1]; -} -``` - -复杂度:O(N) + O(1) - ```java public int JumpFloor(int n) { if (n <= 2) @@ -567,7 +550,32 @@ public int JumpFloor(int n) { } ``` -# 10.3 变态跳台阶 +# 10.3 矩形覆盖 + +[NowCoder](https://www.nowcoder.com/practice/72a5a919508a4251859fb2cfb987a0e6?tpId=13&tqId=11163&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) + +## 题目描述 + +我们可以用 2\*1 的小矩形横着或者竖着去覆盖更大的矩形。请问用 n 个 2\*1 的小矩形无重叠地覆盖一个 2\*n 的大矩形,总共有多少种方法? + +## 解题思路 + +```java +public int RectCover(int n) { + if (n <= 2) + return n; + int pre2 = 1, pre1 = 2; + int result = 0; + for (int i = 3; i <= n; i++) { + result = pre2 + pre1; + pre2 = pre1; + pre1 = result; + } + return result; +} +``` + +# 10.4 变态跳台阶 [NowCoder](https://www.nowcoder.com/practice/22243d016f6b47f2a6928b4313c85387?tpId=13&tqId=11162&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) @@ -588,47 +596,6 @@ public int JumpFloorII(int target) { } ``` -# 10.4 矩形覆盖 - -[NowCoder](https://www.nowcoder.com/practice/72a5a919508a4251859fb2cfb987a0e6?tpId=13&tqId=11163&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) - -## 题目描述 - -我们可以用 2\*1 的小矩形横着或者竖着去覆盖更大的矩形。请问用 n 个 2\*1 的小矩形无重叠地覆盖一个 2\*n 的大矩形,总共有多少种方法? - -## 解题思路 - -复杂度:O(N) + O(N) - -```java -public int RectCover(int n) { - if (n <= 2) - return n; - int[] dp = new int[n]; - dp[0] = 1; - dp[1] = 2; - for (int i = 2; i < n; i++) - dp[i] = dp[i - 1] + dp[i - 2]; - return dp[n - 1]; -} -``` - -复杂度:O(N) + O(1) - -```java -public int RectCover(int n) { - if (n <= 2) - return n; - int pre2 = 1, pre1 = 2; - int result = 0; - for (int i = 3; i <= n; i++) { - result = pre2 + pre1; - pre2 = pre1; - pre1 = result; - } - return result; -} -``` # 11. 旋转数组的最小数字 @@ -638,18 +605,34 @@ public int RectCover(int n) { 把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。 -例如数组 {3, 4, 5, 1, 2} 为 {1, 2, 3, 4, 5} 的一个旋转,该数组的最小值为 1。NOTE:给出的所有元素都大于 0,若数组大小为 0,请返回 0。 +例如数组 {3, 4, 5, 1, 2} 为 {1, 2, 3, 4, 5} 的一个旋转,该数组的最小值为 1。 ## 解题思路 +在一个有序数组中查找一个元素可以用二分查找,二分查找也称为折半查找,每次都能将查找区间减半,这种折半特性的算法时间复杂度都为 O(logN)。 + +本题可以修改二分查找算法进行求解: + - 当 nums[m] <= nums[h] 的情况下,说明解在 [l, m] 之间,此时令 h = m; - 否则解在 [m + 1, h] 之间,令 l = m + 1。 -因为 h 的赋值表达式为 h = m,因此循环体的循环条件应该为 l < h,详细解释请见 [Leetcode 题解](https://github.com/CyC2018/Interview-Notebook/blob/master/notes/Leetcode%20%E9%A2%98%E8%A7%A3.md) 二分查找部分。 +```java +public int minNumberInRotateArray(int[] nums) { + if (nums.length == 0) + return 0; + int l = 0, h = nums.length - 1; + while (l < h) { + int m = l + (h - l) / 2; + if (nums[m] <= nums[h]) + h = m; + else + l = m + 1; + } + return nums[l]; +} +``` -但是如果出现 nums[l] == nums[m] == nums[h],那么此时无法确定解在哪个区间,需要切换到顺序查找。 - -复杂度:O(logN) + O(1) +如果数组元素允许重复的话,那么就会出现一个特殊的情况:nums[l] == nums[m] == nums[h],那么此时无法确定解在哪个区间,需要切换到顺序查找。例如对于数组 {1,1,1,0,1},l、m 和 h 指向的数都为 1,此时无法知道最小数字 0 在哪个区间。 ```java public int minNumberInRotateArray(int[] nums) { @@ -737,7 +720,9 @@ private char[][] buildMatrix(char[] array) { ## 题目描述 -地上有一个 m 行和 n 列的方格。一个机器人从坐标 (0, 0) 的格子开始移动,每一次只能向左右上下四个方向移动一格,但是不能进入行坐标和列坐标的数位之和大于 k 的格子。例如,当 k 为 18 时,机器人能够进入方格(35, 37),因为 3+5+3+7=18。但是,它不能进入方格(35, 38),因为 3+5+3+8=19。请问该机器人能够达到多少个格子? +地上有一个 m 行和 n 列的方格。一个机器人从坐标 (0, 0) 的格子开始移动,每一次只能向左右上下四个方向移动一格,但是不能进入行坐标和列坐标的数位之和大于 k 的格子。 + +例如,当 k 为 18 时,机器人能够进入方格 (35,37),因为 3+5+3+7=18。但是,它不能进入方格 (35,37),因为 3+5+3+8=19。请问该机器人能够达到多少个格子? ## 解题思路 @@ -806,7 +791,7 @@ return 36 (10 = 3 + 3 + 4) ### 贪心 -尽可能多剪长度为 3 的绳子,并且不允许有长度为 1 的绳子出现,如果出现了,就从已经切好长度为 3 的绳子中拿出一段与长度为 1 的绳子重新组合,把它们切成两段长度为 2 的绳子。 +尽可能多剪长度为 3 的绳子,并且不允许有长度为 1 的绳子出现。如果出现了,就从已经切好长度为 3 的绳子中拿出一段与长度为 1 的绳子重新组合,把它们切成两段长度为 2 的绳子。 证明:当 n >= 5 时,3(n - 3) - 2(n - 2) = n - 5 >= 0。因此把长度大于 5 的绳子切成两段,令其中一段长度为 3 可以使得两段的乘积最大。 @@ -847,19 +832,9 @@ public int integerBreak(int n) { 输入一个整数,输出该数二进制表示中 1 的个数。 -### Integer.bitCount() - -```java -public int NumberOf1(int n) { - return Integer.bitCount(n); -} -``` - ### n&(n-1) -O(M) 时间复杂度解法,其中 M 表示 1 的个数。 - -该位运算是去除 n 的位级表示中最低的那一位。 +该位运算去除 n 的位级表示中最低的那一位。 ``` n : 10110100 @@ -867,6 +842,9 @@ n-1 : 10110011 n&(n-1) : 10110000 ``` +时间复杂度:O(M),其中 M 表示 1 的个数。 + + ```java public int NumberOf1(int n) { int cnt = 0; @@ -878,13 +856,22 @@ public int NumberOf1(int n) { } ``` + +### Integer.bitCount() + +```java +public int NumberOf1(int n) { + return Integer.bitCount(n); +} +``` + # 16. 数值的整数次方 [NowCoder](https://www.nowcoder.com/practice/1a834e5e3e1a4b7ba251417554e07c00?tpId=13&tqId=11165&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) ## 题目描述 -给定一个 double 类型的浮点数 base 和 int 类型的整数 exponent。求 base 的 exponent 次方。 +给定一个 double 类型的浮点数 base 和 int 类型的整数 exponent,求 base 的 exponent 次方。 ## 解题思路 @@ -892,7 +879,7 @@ public int NumberOf1(int n) {

-因为 (x\*x)n/2 可以通过递归求解,并且每递归一次,n 都减小一半,因此整个算法的时间复杂度为 O(logN)。 +因为 (x\*x)n/2 可以通过递归求解,并且每次递归 n 都减小一半,因此整个算法的时间复杂度为 O(logN)。 ```java public double Power(double base, int exponent) { @@ -1028,6 +1015,7 @@ public ListNode deleteDuplication(ListNode pHead) { ```java public boolean match(char[] str, char[] pattern) { + int m = str.length, n = pattern.length; boolean[][] dp = new boolean[m + 1][n + 1]; @@ -1058,9 +1046,24 @@ public boolean match(char[] str, char[] pattern) { ## 题目描述 -请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。 +```html +true + +"+100" +"5e2" +"-123" +"3.1416" +"-1E-16" + +false + +"12e" +"1a3.14" +"1.2.3" +"+-5" +"12e+4.3" +``` -例如,字符串 "+100","5e2","-123","3.1416" 和 "-1E-16" 都表示数值。但是 "12e","1a3.14","1.2.3","+-5" 和 "12e+4.3" 都不是。 ## 解题思路 @@ -1124,8 +1127,7 @@ public void reOrderArray(int[] nums) {

```java -public ListNode FindKthToTail(ListNode head, int k) -{ +public ListNode FindKthToTail(ListNode head, int k) { if (head == null) return null; ListNode P1 = head; @@ -1148,9 +1150,7 @@ public ListNode FindKthToTail(ListNode head, int k) ## 题目描述 -一个链表中包含环,请找出该链表的环的入口结点。 - -要求不能使用额外的空间。 +一个链表中包含环,请找出该链表的环的入口结点。要求不能使用额外的空间。 ## 解题思路 @@ -1161,8 +1161,7 @@ public ListNode FindKthToTail(ListNode head, int k)

```java -public ListNode EntryNodeOfLoop(ListNode pHead) -{ +public ListNode EntryNodeOfLoop(ListNode pHead) { if (pHead == null || pHead.next == null) return null; ListNode slow = pHead, fast = pHead; @@ -1304,8 +1303,6 @@ private boolean isSubtreeWithRoot(TreeNode root1, TreeNode root2) { ## 解题思路 -### 递归 - ```java public void Mirror(TreeNode root) { if (root == null) @@ -1322,29 +1319,6 @@ private void swap(TreeNode root) { } ``` -### 迭代 - -```java -public void Mirror(TreeNode root) { - Stack stack = new Stack<>(); - stack.push(root); - while (!stack.isEmpty()) { - TreeNode node = stack.pop(); - if (node == null) - continue; - swap(node); - stack.push(node.left); - stack.push(node.right); - } -} - -private void swap(TreeNode node) { - TreeNode t = node.left; - node.left = node.right; - node.right = t; -} -``` - # 28 对称的二叉树 [NowCder](https://www.nowcoder.com/practice/ff05d44dfdb04e1d83bdbdab320efbcb?tpId=13&tqId=11211&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) @@ -1445,7 +1419,9 @@ public int min() { ## 题目描述 -输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列 1,2,3,4,5 是某栈的压入顺序,序列 4,5,3,2,1 是该压栈序列对应的一个弹出序列,但 4,3,5,1,2 就不可能是该压栈序列的弹出序列。 +输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。 + +例如序列 1,2,3,4,5 是某栈的压入顺序,序列 4,5,3,2,1 是该压栈序列对应的一个弹出序列,但 4,3,5,1,2 就不可能是该压栈序列的弹出序列。 ## 解题思路 @@ -1838,7 +1814,7 @@ private void backtracking(char[] chars, boolean[] hasUsed, StringBuilder s) { 多数投票问题,可以利用 Boyer-Moore Majority Vote Algorithm 来解决这个问题,使得时间复杂度为 O(N)。 -使用 cnt 来统计一个元素出现的次数,当遍历到的元素和统计元素不相等时,令 cnt--。如果前面查找了 i 个元素,且 cnt == 0,说明前 i 个元素没有 majority,或者有 majority,但是出现的次数少于 i / 2 ,因为如果多于 i / 2 的话 cnt 就一定不会为 0 。此时剩下的 n - i 个元素中,majority 的数目依然多于 (n - i) / 2,因此继续查找就能找出 majority。 +使用 cnt 来统计一个元素出现的次数,当遍历到的元素和统计元素相等时,令 cnt++,否则令 cnt--。如果前面查找了 i 个元素,且 cnt == 0,说明前 i 个元素没有 majority,或者有 majority,但是出现的次数少于 i / 2 ,因为如果多于 i / 2 的话 cnt 就一定不会为 0 。此时剩下的 n - i 个元素中,majority 的数目依然多于 (n - i) / 2,因此继续查找就能找出 majority。 ```java public int MoreThanHalfNum_Solution(int[] nums) {