递归和循环
面试题10:斐波那契数列
题目一:求斐波那契数列的第n项。
写一个函数,输入n,求斐波那契(Fibonacci)数列的第n项(从0开始,第0项为0)。斐波那契数列的定义如下:
代码实现
public class Solution{
public int Fibonacci(int n) {
int[] arr = {0, 1};
if(n <= 1) {
return arr[n];
}
int fibAddOne = 1;
int fibAddTwo = 0;
int fibN = 0;
for(int i = 2 ; i <= n; i++) {
fibN = fibAddOne + fibAddTwo;
fibAddTwo = fibAddOne;
fibAddOne = fibN;
}
return fibN;
}
}
题目二:跳台阶
一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法(先后次序不同算不同的结果)。
分析:跳上第n级台阶,要么是从第n-1级跳一级上来,要么是从第n-2级跳两级上来。
代码实现
public class Solution{
public int JumpFloor(int target) {
if(target == 0) {
return -1;
}else if(target == 1) {
return 1;
}else {
int addOne = 1;
int addTwo = 1;
int fN = -1;
for(int i = 2; i <= target; i++) {
fN = addOne + addTwo;
addTwo = addOne;
addOne = fN;
}
return fN;
}
}
}
题目三:变态跳台阶
一只青蛙一次可以跳上1级台阶 也可以跳上 2级 ……它也可以跳上n级 。 求该青蛙跳上一个n级的台阶总共有多少种跳法。
分析:要跳上第 n 级台阶可以从第 n−1级台阶一次跳上来 也可以从第n−2 级台阶一次跳上来也可以从第 n−3级台阶一次跳上来也可以从第 1 级台阶一次跳上来 。即,
代码实现
public class Solution{
public int JumpFloorII(int target) {
if(target == 0) {
return -1;
}else {
int fN = 1;
for(int i = 2; i <= target; i++) {
fN *= 2;
}
return fN;
}
}
}
题目四:矩形覆盖
我们可以用2*1的小矩形横着或者竖着去覆盖更大的矩形。请问用 n个2*1的小矩形无重叠地覆盖一个2*n的大矩形,总共有多少种方法?
分析:考虑 2*8的情况。当竖着放的时候,右边还剩下 2*7的区域。当横着放的时候,下面也必须横着放一个矩形,右边还剩下 2*6的区域。
代码实现
public class Solution{
public int RectCover(int target) {
if(target == 0) {
return 0;
}else if(target == 1) {
return 1;
}else {
int addOne = 1;
int addTwo = 1;
int fN = -1;
for(int i = 2; i <= target; i++) {
fN = addOne + addTwo;
addTwo = addOne;
addOne = fN;
}
return fN;
}
}
}
查找和排序
面试题11:旋转数组的最小数字
题目:把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个非减排序的数组的一个旋转,输出旋转数组的最小元素。例如数组 {3,4,5,1,2}为 {1,2,3,4,5}的一个旋转,该数组的最小值为1。 NOTE:给出的所有元素都大于0,若数组大小为 0,请返回0。
分析:旋转之后的数组可以划分为两个排序的子数组,而且前面子数组的元素都大于或者等于后面子数组的元素,而最小的元素刚好是这两个子数组的分界线。在排序的数组中可以使用二分查找法来实现O(logn)的查找。
利用两个指针分别指向数组的第一个元素和最后一个元素。按照题目中旋转的规则,第一个元素应该是大于或者等于最后一个元素的(还有特例)。接着可以找到数组中间的元素。如果该中间元素位于前面的递增子数组,那么它应该大于或者等于第一个指针指向的元素。此时数组中的最小元素应该位于该中间元素的后面,可以将第一个指针指向该中间元素。
如果中间元素位于后面的递增子数组,那么它应该小于或者等于第二个指针指向的元素。此时该数组中的最小数字应该位于该中间元素的前面,可以将第二个指针指向该中间元素。
因此,第一个指针始终指向前面递增数组的元素,第二个指针始终指向后面递增数组的元素。最终第一个指针将指向前面子数组的最后一个元素,而第二个指针将指向后面子数组的第一个元素。也就是说,它们最终会指向两个相邻的元素,而第二个指针指向的元素刚好就是最小的元素,这就是循环结束的条件。
代码实现
public class Solution{
public int minNumberInRotateArray(int[] array) {
if(array.length == 0) {
return 0;
}
int left = 0;
int right = array.length - 1;
int mid = -1;
while(array[left] >= array[right]) {
if(right - left == 1) {
mid = right;
break;
}
mid = left + (left + right) / 2;
if(array[mid] >= array[left]) {
left = mid;
}
if(array[mid] <= array[right]) {
right = mid;
}
}
return array[mid];
}
}
回溯法
面试题12:矩阵中的路径
题目:请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有 字符的路径。路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。如果一条路径经过了矩阵中的某一个格子,则之后不能再次进入这个格子。 例如 a b c e s f c s a d e e 这样的 3 X 4 矩阵中包含一条字符串 "bcced"的路径,但是矩阵中不包含"abcb"路径,因为字符串的第一个字符 b占据了矩阵中的第一行第二个格子之后,路径不能再次进入该格子。
分析:首先,在矩阵中任选一个格子作为路径的起点。假设矩阵中某个格子的字符为ch,并且这个格子对应于路径上的第 i 个字符。如果路径上的第 i 个字符不是 ch,那么这个格子不可能处在路径上的第 i 个位置。如果路径上的第 i 个字符正好是 ch,那么到相邻的格子寻找路径上的第 i+1个字符。除矩阵边界上的格子之外,其他格子都有 4个相邻的格子。重复这个过程,直到路径上的所有字符都在矩阵中找到相应的位置。
由于回溯法的递归特性,路径可以被看成一个栈。当在矩阵中定位了路径中前 n个字符的位置之后,在与第 n 个字符对应的格子的周围都没有找到第 n+1 个字符,这时候只好在路径上回到第 n-1 个字符,重新定位第 n 个字符。
由于路径不能重复进入矩阵的格子,所以还需要定义和字符矩阵大小一样的布尔值矩阵,用来表示路径是否已经进入了每个格子。
代码实现
public class Solution{
public boolean hasPath(char[] matrix, int rows, int cols, char[] str) {
if(matrix == null || rows < 1 || cols < 1 || str == null) {
return false;
}
boolean flag = false;
boolean[] visited = new boolean[matrix.length];
for(int i = 0; i < rows; i++) {
for(int j = 0; j < cols; j++) {
if(judge(matrix, i, j, rows, cols, visited, str, 0)) {
flag = true;
}
}
}
return flag;
}
public boolean judge(char[] matrix, int i, int j, int rows, int cols,
boolean[] visited, char[] str, int k) {
int index = i * cols + j;
if(i < 0 || j < 0 || i >= rows || j >= cols
|| matrix[index] != str[k] || visited[index] == ture) {
return false;
}
if(k == str.length - 1) {
return true; //退出条件
}
visited[index] = true;
//在四周寻找下一个字符
if(judge(matrix, i - 1, j, rows, cols, visited, str, k + 1) ||
judge(matrix, i + 1, j, rows, cols, visited, str, k + 1) ||
judge(matrix , i, j - 1, rows, cols, visited, str, k + 1) ||
judge(matrix , i, j + 1, rows, cols, visited, str, k + 1)) {
return true;
}
//没有找到对应的字符,还原
visited[index] = false;
return false;
}
}
面试题13:机器人的运动范围
题目:地上有一个m行和 n列的方格。一个机器人从坐标 0,0的格子开始移动,每一次只能向左,右,上,下四个方向移动一格,但是不能进入行坐标和列坐标的数位之和大于 k的格子。 例如,当 k为 18时,机器人能够进入方格( 35,37),因为 3+5+3+7 = 18。但是,它不能进入方格( 35,38),因为 3+5+3+8 = 19。请问该机器人能够达到多少个格子?
分析:分析:机器人从坐标(0,0)开始移动,当它准备进入坐标为 (i , j)的格子时,通过检查坐标的数位和来判断机器人是否能够进入。如果机器人能够进入坐标为(i, j)的格子,则再判断它能否进入4个相邻的格子 (i, j-1)、 (i-1, j)、 (i+1, j) 、 (i, j+1)。
代码分析
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];
int count = judge(threshold, rows, cols, 0, 0, visited);
return count;
}
public int judge(int threshold, int rows, int cols, int i, int j,
boolean[] visited) {
int index = i * cols + j;
int count = 0;
if(i < 0 || j < 0 || i >= rows || j >= cols ||
numSum(i) + numSum(j) > threshold || visited[index] == true) {
return 0;
}
visited[index] = true;
count = 1 + judge(threshold, rows, cols, i - 1, j, visited)
+ judge(threshold, rows, cols, i + 1, j, visited)
+ judge(threshold, rows, cols, i, j - 1, visited)
+ judge(threshold, rows, cols, i, j + 1, visited);
return count;
}
public int numSum(int num) {
int sum = 0;
while(num > 0) {
sum += num % 10;
num /= 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。
分析:当n≥5时, 我们尽可能多地剪长度为3的绳子;当剩下的绳子长度为4时,把绳子剪成两段长度为2的绳子。
代码实现
###贪婪算法###
public class Solution{
public static int maxProduct(int length) {
if(length < 2) {
return 0;
}else if(length == 2) {
return 1;
}else if(length == 3) {
return 2;
}
//尽可能多地去剪长度为3的绳子段
int timesOf3 = length / 3;
//当绳子最后剩下的长度为4时,把绳子剪成长度为2的两段
if(length - 3 * timesOf3 == 1) {
timesOf3 -= 1;
}
int timesOf2 = (length - 3 * timesOf3) / 2;
return (int)Math.pow(3, timesOf3) * (int)Math.pow(2, timesOf2);
}
}
位运算
面试题15:二进制中1的个数
题目:输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。
分析:一个数与该数减一的结果进行与运算,会把该数右边(低位)第一个1变为 0,如果它的右边还有0,则所有的0都变成1,而该数左边(高位)保持不变。例如: 1100 (12)减去1为 1011 (11),两者进行与运算为 1000,sum=1; 1000减去 1为 0111,两者与运算为 0000,sum = 2 。
代码分析
public class Solution{
public int NumberOf1(int n) {
int sum = 0;
while(n != 0) {
sum++;
n = n & (n - 1);
}
return sum;
}
}