题目10 -- 斐波那契数列
题目一: 求斐波那契数列的第n项。 写一个函数,输入n,求斐波那契额(Fibonacci)数列的第n项。斐波那契额数列的定义如下:
斐波那契数列问题,和书中C++版本没什么不同,不再赘述
public long fibonacci(int n) {
if (n <= 0) {
return 0;
}
if (n == 1) {
return 1;
}
return fibonacci(n - 1) + fibonacci(n - 2);
}
public long fibonacci2(int n) {
if (n <= 0) {
return 0;
}
if (n == 1) {
return 1;
}
int fib1 = 0;
int fib2 = 1;
int fib = fib1 + fib2;
for (i = 2; i <= n; i++) {
fib = fib1 + fib2;
fib1 = fib2;
fib2 = fib;
}
return fib;
}
题目11 -- 旋转数组的最小数字
题目:把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如,数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。
二分法起手,通过array[mid]判断最小值在左半边区域or右半边区域。写出基本框架后,补充异常情况,并且在测试用例中考虑如下情况:
- 没旋转,完全是普通的递增数组,{1,2,3,4,5}
- 包含重复值的数组,{3,4,5,3,3,3,3}
- 所有值都是一个数字的数组,{3,3,3,3,3}
public class Solution {
/**
* 1. regular eg. 3,4,5,1,2
* 1. accumulate eg. 1,2,3,4,5
* 2. same number eg. 3,3,3,3,3
*/
public int findMinOfRotateArray(int[] array) {
if (array == null || array.length == 0) {
throw new IllegalArgumentException("arrays is empty");
}
int left = 0;
int right = array.length - 1;
int mid = left;
while (array[left] >= array[right]) {
if (right - left == 1) {
mid = right;
break;
}
mid = (right - left) >> 1 + left;
if (array[mid] == array[left] && array[mid] == array[right]) {
return findInOrder(array, left, right);
}
if (array[left] <= array[mid]) { // min value in right region
mid = left;
} else if (array[mid] <= array[right]) { // min value in left region
mid = right;
}
}
return array[mid];
}
private int findInOrder(int[] array, int left, int right) {
int result = array[left];
for (int i = left+1; i <= right; i++) {
result = Math.max(result, array[i]);
}
return result;
}
}
面试题12 -- 矩阵中的路径
题目:请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一格开始,每一步去可以在矩阵中左、右、上、下移动一格。如果一条路径经过了矩阵的某一格,那么该路径不能再次进入该格子。例如,在下面的3*4的矩阵中包含一条字符串"bfce"的路径(路径中的字母用下划线标出)。但矩阵中不包含字符串"abfb"的路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入这个格子。
public class Solution {
public boolean findPathInMatrix(char[][] matrix, String str) {
if (matrix == null || matrix.length == 0 || matrix[0].length == 0 || str == null) {
return false;
}
boolean[][] visited = new boolean[matrix.length][matrix[0].length];
for (int i = 0; i < matrix.length; i++) {
for (int j = 0; j < matrix[0].length; j++) {
if (doFindPathInMatrix(matrix, i, j, str, 0, visited)) {
return true;
}
}
}
return false;
}
private boolean doFindPathInMatrix(char[][] matrix, int row, int col, String str, int index, boolean[][] visited) {
if (index == str.length()) {
return true;
}
boolean found = false;
if (row >= 0 && row < matrix.length && col >= 0 && col < matrix[0].length
&& !visited[row][col] && index < str.length()
&& matrix[row][col] == str.charAt(index)) {
visited[row][col] = true;
index++;
found = doFindPathInMatrix(matrix, row-1, col, str, index, visited)
|| doFindPathInMatrix(matrix, row, col+1, str, index, visited)
|| doFindPathInMatrix(matrix, row+1, col, str, index, visited)
|| doFindPathInMatrix(matrix, row, col-1, str, index, visited);
if (!found) {
visited[row][col] = false;
}
}
return found;
}
}
面试题13 -- 机器人的运动范围
题目:地上有一个m行n列的方格。一个机器人从左表(0,0)的格子开始移动,它每次可以向左、右、上、下移动一格,但不能进入行左表和列坐标的数位之和大于k的格子。例如,当k为18时,机器人能够进入方格(35,37),因为3+5+3+7=18。但它不能进入方格(35,38),因为3+5+3+8=19。请问该机器人能够到达多少个格子?
搜索的问题,书中提出的是用DFS回溯法解决,其实也可以通过BFS搜索解决。从(0,0)开始,机器人在每个格子(如0,0)上,都面临上下左右四个方向的选择,如(0,0)就是机器人在这个格子上向上移动后可能走的格子数 + 向下移动后可能走的格子数 + 向左移动后可能走的格子数 + 向右移动后可能走的格子数
public class Solution {
public int movingCount(int threshold, int rows, int cols) {
if (threshold <= 0 || rows <= 0 || cols <0) {
return 0;
}
boolean[][] visited = new boolean[rows][cols];
return movingCountCore(threshold, visited, 0, 0);
}
private int movingCountCore(int threshold, boolean[][] visited, int i, int j) {
if (check(threshold, visited, i, j)) {
visited[i][j] = true;
return 1 + movingCountCore(threshold, visited, i-1, j) +
movingCountCore(threshold, visited, i, j+1) +
movingCountCore(threshold, visited, i+1, j) +
movingCountCore(threshold, visited, i, j-1);
}
return 0;
}
private boolean check(int threshold, boolean[][] visited, int i, int j) {
return i >= 0 && i < visited.length
&& j >= 0 && j < visited[0].length
&& !visited[i][j]
&& getDigitSum(i, j) <= threshold;
}
private int getDigitSum(int i, int j) {
int sum = 0;
while (i != 0) {
sum += i % 10;
i /= 10;
}
while (j != 0) {
sum += j % 10;
j /= 10;
}
return sum;
}
}
面试题14 -- 剪绳子
题目:给你一根长度为n的绳子,请把绳子剪成n段(m,n都是整数,n>1并且m>1),每段绳子的长度记为k[0],k[1],k[2]...k[m]。请问k[0]*k[1]*...*k[m]可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。
这道题关于动态规划解法,书中描述的过于简单了,定义f(n)为长度为n的绳子的解法,首先通过列出f(1)、f(2)、f(3)...的规律,不难发现长度为n的绳子最大乘积为
f(n) = max(f(i) * f(n-i)), 1<=i<n
从而发现动态规划的重叠子问题、局部最优解的规律
书中对于dp[0]、dp[1]、dp[2]、dp[3]进行了自己的定义,因为在i<=3时,i本身是有可能大于dp[i]的,而从dp[4]开始,一定有dp[4]>4、dp[5]>5...
本文的解法只考虑直观的判断,即把i本身也考虑在内,代码如下
public class Solution {
public int cutRope(int n) {
if (n < 1) {
throw new IllegalArgumentException("number n is negative.");
}
if (n <= 2) {
return 1;
}
int[] maxProducts = new int[n+1];
maxProducts[1] = 1;
for (int len = 2; len <= n; len++) {
for (int i = 1; i <= len/2; i++) {
int maxLeft = Math.max(maxProducts[i], i);
int maxRight = Math.max(maxProducts[len - i], len - i);
maxProducts[len] = Math.max(maxProducts[len], maxLeft * maxRight);
}
}
return maxProducts[n];
}
}
贪心算法的解法是尽量将绳子切成长度为3的,其次是2,最次是1,实现较为简单,但是证明的过程需要公式求导等数学手段,可以参考 面试题14- I. 剪绳子(数学推导 / 贪心思想,清晰图解) - 剪绳子 - 力扣(LeetCode)
public int cutRopeGreedy(int n) {
if (n < 2) {
return 0;
} else if (n == 2) {
return 1;
} else if (n == 3) {
return 2;
}
int timesOf3 = n / 3;
if (n - timesOf3 * 3 == 1) {
timesOf3 -= 1;
}
int timesOf2 = (n - timesOf3 * 3) / 2;
return (int) (Math.pow(3, timesOf3) * Math.pow(2, timesOf2));
}
面试题15 -- 二进制中1的个数
题目:请实现一个函数,输入一个整数,输出该数二进制表示中1的个数。例如,把9表示成二进制是1001,有2位是1。因此,如果输入9,则该函数输出2。
题目比较简单,不再赘述。
public class Solution {
public int numberOfOne(int n) {
int count = 0;
int flag = 1;
while (flag != 0) { // flag依次和n的每个bit位对比
if ((flag & n) != 0) {
count++;
}
flag <<= 1;
}
return count;
}
public int numberOfOneBit(int n) {
int count = 0;
while (n != 0) {
n &= (n - 1); // n & (n-1),每次都能干掉一个1
count++;
}
return count;
}
}