面试题10:斐波那契数列
题目一:斐波那契数列
题目一:写一个函数,输入n,求斐波那契(Fibonacci)数列的第n项。斐波那契数列的定义如下:
f(x) = \begin{cases} 0 & n=0 \\ 1 & n=1 \\ f(n-1)+f(n-2) &{n>1}\\ \end{cases}
- 算法:循环,并把结果取模防止溢出
测试:力扣
public int fib(int n) { if (n < 0) { return -1; } if (n < 2) { return n; } int fibMinusOne = 1; int fibMinusTwo = 0; int fibN = 0; for (int i = 2; i <= n; i++) { fibN = (fibMinusOne + fibMinusTwo) % 1000000007; // 避免溢出;如果放到最后取模可能会在这一步发生溢出使最终结果错误 fibMinusTwo = fibMinusOne; fibMinusOne = fibN; } return fibN; }
- 算法:使用long数据类型
public long fibonacci(int n) { if (n < 0) { return -1; } if (n < 2) { return n; } long fibOne = 0; long fibTwo = 1; long finN = 0; for (int i = 2; i <= n; i++) { finN = fibOne + fibTwo; fibOne = fibTwo; fibTwo = finN; } return finN; }
- 算法:自下向上和自上向下的动态规划
// DP-bottom up time-O(n) space-O(n) public int fib_(int n) { if (n < 0) { return -1; } if(n < 2) return n; int[] fib = new int[n + 1]; fib[1] = 1; for(int i = 2; i <= n; i++) { fib[i] = fib[i - 1] + fib[i - 2]; } return fib[n]; } // DP-top down time-O(n) space-O(n) public int fib(int n) { if (n < 0) { return -1; } int[] fib = new int[n+1]; return fibDP(n, fib); } public int fibDP(int n, int[] fib) { if(n < 2) return n; else if(fib[n] != 0) return fib[n]; else return fib[n] = fib(n - 1) + fib(n - 2); }
- 算法:矩阵运算
算法:用归纳法可以证明以下数学公式:
\left[ \begin{matrix} f(n) & f(n-1) \\ f(n-1) & f(n-2) \\ \end{matrix} \right] = {\left[ \begin{matrix} 1 & 1 \\ 1 & 0 \\ \end{matrix} \right]}^{n-1}然后初始矩阵的乘方可以通过以下递归实现O(logn)时间的算法
a^n = \begin{cases} a^{\frac{n}{2}} * a^{\frac{n}{2}} & n为偶数\\ a^{\frac{n-1}{2}} * a^{\frac{n-1}{2}} * a & n为奇数\\ \end{cases}
测试:自测
public long fibonacci_(int n) { if (n < 0) { return -1; } if (n < 2) { return n; } Matrix2By2 matrix2By2 = MatrixPower(n-1); return matrix2By2.m00; } class Matrix2By2 { long m00;long m01;long m10;long m11; Matrix2By2() {} Matrix2By2(long x, long y, long z, long w) { m00 = x;m01 = y;m10 = z;m11 = w;} } private Matrix2By2 MatrixMultiply(Matrix2By2 matrix1, Matrix2By2 matrix2) { return new Matrix2By2(matrix1.m00 * matrix2.m00 + matrix1.m01 * matrix2.m10, matrix1.m00 * matrix2.m01 + matrix1.m01 * matrix2.m11, matrix1.m10 * matrix2.m00 + matrix1.m11 * matrix2.m10, matrix1.m10 * matrix2.m01 + matrix1.m11 * matrix2.m11); } private Matrix2By2 MatrixPower(int n) { Matrix2By2 matrix = new Matrix2By2(); if (n == 1) { matrix = new Matrix2By2(1, 1, 1, 0); } else if(n % 2 == 0) { matrix = MatrixPower(n / 2); matrix = MatrixMultiply(matrix, matrix); } else if(n % 2 == 1) { matrix = MatrixPower((n - 1) / 2); matrix = MatrixMultiply(matrix, matrix); matrix = MatrixMultiply(matrix, new Matrix2By2(1, 1, 1, 0)); } return matrix; }
题目二:青蛙跳台阶1
题目二:一个青蛙一次可以跳上一级台阶,也可以跳上两级台阶。求该青蛙跳上n级台阶总共有多少种跳法。
算法:同斐波那契数列,但初始项为f(1) = 1; f(2) = 2;,且不能再用矩阵来计算
题目三:青蛙跳台阶2
题目三:一个青蛙一次可以跳上一级台阶,也可以跳上两级台阶······也可以跳上n级台阶。求该青蛙跳上n级台阶总共有多少种跳法。
算法:f(n) = f(1)+f(2)+···+f(n-1)+1
f(n) = 2^(n-1)
题目四:2X1矩形覆盖2Xn矩形
题目四:用2X1的小矩形横着或竖着去覆盖2Xn的大矩形总共有多少种方法。
算法:最左边第一块竖着放,则剩余2X(n-1);最左边第一块横着放,则必须再来一块横着放与它对齐,则剩余2X(n-2);所以f(n)=f(n-1)+f(n-2);f(1)=1;f(2)=2;
面试题11:旋转数组的最小数字
题目:把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如数组{3, 4, 5, 1, 2}为{1, 2, 3, 4, 5}的一个旋转,该数组的最小值为1。
测试:力扣
public int minArray(int[] numbers) { if (numbers == null || numbers.length == 0) { return -1; } int index1 = 0; int index2 = numbers.length - 1; int indexMid = (index1 + index2) >> 1; if (numbers[index1] == numbers[index2] && numbers[indexMid] == numbers[index1]) { return minArrayInorder(numbers); } indexMid = index1; while (numbers[index1] >= numbers[index2]) { if (index2 - index1 <= 1) { return numbers[index2]; } indexMid = (index1 + index2) >> 1; if (numbers[indexMid] >= numbers[index1]) { index1 = indexMid; } if (numbers[indexMid] <= numbers[index2]) { index2 = indexMid; } } return numbers[indexMid]; } private int minArrayInorder(int[] numbers) { int res = numbers[0]; for (int i = 0; i < numbers.length; i++) { if (numbers[i] < res) { res = numbers[i]; } } return res; }
面试题12:矩阵中的路径
题目:请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中任意一格开始,每一步可以在矩阵中向左、右、上、下移动一格。如果一条路径经过了矩阵的某一格,那么该路径不能再次进入该格子。例如在下面的3×4的矩阵中包含一条字符串“bfce”的路径(路径中的字母用下划线标出)。但矩阵中不包含字符串“abfb”的路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入这个格子。
A B T G
C F C S
J D E H
测试:力扣
public boolean exist(char[][] board, String word) { // 排除异常输入 if (board == null || board.length == 0) { return false; } for (int i = 0; i < board.length; i++) { if (board[i] == null || board[i].length == 0) { return false; } else { if (i > 0 && board[i].length != board[i-1].length) { return false; } } } if (word == null || word.length() == 0) { return true; } int rows = board.length; int cols = board[0].length; boolean[][] visited = new boolean[rows][cols]; int pathLength = 0; for (int i = 0; i < rows; i++) { for (int j = 0; j < cols; j++) { if (hasPathCore(board, i, j, word, pathLength, visited)) { return true; } } } return false; } private boolean hasPathCore(char[][] board, int row, int col, String word, int pathLength, boolean[][] visited) { if (pathLength == word.length()) { return true; } boolean hasPath = false; if (row >= 0 && row < board.length && col >= 0 && col < board[0].length && board[row][col] == word.charAt(pathLength) && !visited[row][col]) { pathLength++; visited[row][col] = true; hasPath = hasPathCore(board, row, col+1, word, pathLength, visited) || hasPathCore(board, row, col-1, word, pathLength, visited) || hasPathCore(board, row+1, col, word, pathLength, visited) || hasPathCore(board, row-1, col, word, pathLength, visited); if (!hasPath) { pathLength--; visited[row][col] = false; } } return hasPath; }
面试题13:机器人的运动范围
题目:地上有一个m行n列的方格。一个机器人从坐标(0, 0)的格子开始移动,它每一次可以向左、右、上、下移动一格,但不能进入行坐标和列坐标的数位之和大于k的格子。例如,当k为18时,机器人能够进入方格(35,37),因为3+5+3+7=18。但它不能进入方格(35,38),因为3+5+3+8=19。请问该机器人能够到达多少个格子?
- 算法:
- visited数组 + 递归
- 任意位置可运动范围等于当前这个位置加上四个方向的运动范围
public int movingCount(int rows, int cols, int threshold) { if (rows <= 0 || cols <= 0 || threshold < 0) { return 0; } boolean[][] visited = new boolean[rows][cols]; return movingCountCore(rows, cols, 0, 0, visited, threshold); } private int movingCountCore(int rows, int cols, int row, int col, boolean[][] visited, int threshold) { int count = 0; if (check(rows, cols, row, col, visited, threshold)) { visited[row][col] = true; // all together how many grids the root can arrive count = 1 + movingCountCore(rows, cols, row, col+1, visited, threshold) + movingCountCore(rows, cols, row, col-1, visited, threshold) + movingCountCore(rows, cols, row+1, col, visited, threshold) + movingCountCore(rows, cols, row-1, col, visited, threshold); } return count; } private boolean check(int rows, int cols, int row, int col, boolean[][] visited, int threshold) { if (row >= 0 && row < rows && col >= 0 && col < cols && !visited[row][col] && getDigitSum(row) + getDigitSum(col) <= threshold) { return true; } return false; } private int getDigitSum(int x) { int sum = 0; while (x > 0) { sum += x % 10; x /= 10; } return sum; }
面试题14:剪绳子
题目:给你一根长度为n绳子,请把绳子剪成m段(m、n都是整数,n>1并且m≥1)。每段的绳子的长度记为k[0]、k[1]、……、k[m]。k[0]k[1]…*k[m]可能的最大乘积是多少?例如当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到最大的乘积18。
- 算法:动态规划
测试:力扣
public int cuttingRope(int n) { // 异常输入 if (n < 0) { return -1; } // 特殊输入 if (n < 2) { return 0; } if (n < 4) { return n - 1; } int[] product = new int[n+1]; product[0] = 0; // 注意这里的product[x]的初始化不是n=x的结果,而是动态规划的初始状态 product[1] = 1; product[2] = 2; product[3] = 3; for (int i = 4; i <= n; i++) { int max = 0; for (int j = 1; j <= i >> 1; j++) { int products = product[j]*product[i-j]; if (max < products) { max = products; } } product[i] = max; } return product[n]; }
- 算法:贪婪算法
算法:当n≥5时,尽可能多地剪长度为3的绳子;当剩下的绳子为4时,把绳子剪成两段长度为2的绳子。
证明:当n≥5时,可以证明2(n-2)>n并且3(n-3)>n,也就是说,当绳子长度大于等于5时,就把它剪成长度为3或者2的绳子段;另外3(n-3)>2(n-2),因此应该尽可能地多剪长度为3的绳子段;当绳子的长度为4时,显然2×2>3×1;因为2×2=4,也可以选择不剪。
测试:力扣
public int cuttingRope(int n) { // 异常输入 if (n < 0) { return -1; } // 特殊输入 if (n < 2) { return 0; } if (n < 4) { return n - 1; } int timeOf3 = n / 3; if (n == 1 + timeOf3 * 3) { timeOf3 -= 1; } int timeOf2 = (n - timeOf3 * 3) / 2; return (int)(Math.pow(3, timeOf3) * Math.pow(2, timeOf2)); }
说明:另一种形式的贪婪算法
测试:力扣
public int cuttingRope_1(int n) { // 异常输入 if (n < 0) { return -1; } int[] product = new int[60]; product[0] = 0; product[1] = 0; product[2] = 1; product[3] = 2; product[4] = 4; product[5] = 6; product[6] = 9; for (int i = 7; i <= n; i++) { product[i] = product[i-3] * 3; } return product[n]; }
- 算法:取余避免溢出
public int cuttingRope(int n) { // 异常输入 if (n < 0) { return -1; } // 特殊输入 if (n < 2) { return 0; } if (n < 4) { return n - 1; } int timeOf3 = n / 3; if (n == 1 + timeOf3 * 3) { timeOf3 -= 1; } int timeOf2 = (n - timeOf3 * 3) / 2; long res = 1; // long数据类型避免中间状态溢出 for (int i = 0; i < timeOf3; i ++) { res *= 3; res %= 1000000007; } for (int i = 0; i < timeOf2; i ++) { res *= 2; res %= 1000000007; } return (int)res; }
面试题15:二进制中1的个数
题目:请实现一个函数,输入一个整数,输出该数二进制表示中1的个数。例如把9表示成二进制是1001,有2位是1。因此如果输入9,该函数输出2。
- 算法:和n-1做按位与
public int numberOf1(int n) { int count = 0; while (n != 0) { count++; n &= n-1; } return count; }
- 算法:和2的幂逐个做按位与
public int numberOf1(int n) { int count = 0; int flag = 1; while (flag != 0) { if ((n & flag) != 0) { count++; } flag <<= 1; } return count; }
- 算法:逐位进行逻辑右移
public int numberOf1(int n) { int count = 0; while (n != 0) { if ((n & 1) == 1) { count++; } n >>>= 1; } return count; }