剑指offer刷题笔记(二)
面试题10- I. 斐波那契数列
写一个函数,输入 n ,求斐波那契(Fibonacci)数列的第 n 项。斐波那契数列的定义如下:
F(0) = 0, F(1) = 1
F(N) = F(N - 1) + F(N - 2), 其中 N > 1.
斐波那契数列由 0 和 1 开始,之后的斐波那契数就是由之前的两数相加而得出。
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
思路:斐波那契数列是很经典的一道题目,通常在学校里是作为递归的一道经典题目来讲解的,但是斐波那契数列在递归中会产生大量的重复计算,
如在计算F(7)时需要计算F(6)F(5),在计算F(6)的时候就需要计算F(5)F(4),这时F(5)就被重复计算了,思考一下如何改善,我们可以从下往上进行计算,而非递归一般从上往下,在计算F(5)
的时候,我们就已经知道F(4)和F(3)的值,这样就可以剔除掉重复计算。
class Solution {
public int fib(int n) {
if(n==0||n==1){
return n;
}
long[] dp=new long[n+1];
dp[0]=0;
dp[1]=1;
for(int i=2;i<=n;i++){
dp[i]=dp[i-1]+dp[i-2];
dp[i]=dp[i]%1000000007;
}
return (int)dp[n];
}
}
面试题10- II. 青蛙跳台阶问题
一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
输入:n = 2
输出:2
输入:n = 7
输出:21
0 <= n <= 100
思路 这时一道变形的斐波那契数列问题,假设青蛙跳2级台阶,他可以跳上一级,也可以二级,如果跳三级台阶,那么就出现了两种可能,一是跳上一级,然后再选择跳一级和二级,二是跳上二级再跳一级
相当于F(3)=F(2)+F(1)
基于这个思路和上一题的解法,我们可以得出这一题的解法。
class Solution {
public int numWays(int n) {
if(n==0||n==1){
return 1;
}
long[] dp=new long[n+1];
dp[0]=1;
dp[1]=1;
for(int i=2;i<=n;i++){
dp[i]=dp[i-1]+dp[i-2];
dp[i]=dp[i]%1000000007;
}
return (int)dp[n];
}
}
面试题11. 旋转数组的最小数字
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如,数组 [3,4,5,1,2] 为 [1,2,3,4,5] 的一个旋转,该数组的最小值为1。
输入:[3,4,5,1,2]
输出:1
输入:[2,2,2,0,1]
输出:0
思路:观察这个数组,我们就可以发现,这个数组中是半有序的,因此我们可以采取二分查找的办法。
class Solution {
public int minArray(int[] numbers) {
if(numbers.length==0){
return -1;
}
int left=0;
int right=numbers.length-1;
if(numbers[left]numbers[right]){
left=mid+1;
}
else if(numbers[mid]==numbers[right]){
--right;
}
else{
right=mid;
}
}
return numbers[right];
}
}
面试题12. 矩阵中的路径
请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一格开始,每一步可以在矩阵中向左、右、上、下移动一格。如果一条路径经过了矩阵的某一格,那么该路径不能再次进入该格子。例如,在下面的3×4的矩阵中包含一条字符串“bfce”的路径(路径中的字母用加粗标出)。
[["a","b","c","e"],
["s","f","c","s"],
["a","d","e","e"]]
但矩阵中不包含字符串“abfb”的路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入这个格子。
输入:board =
[["A","B","C","E"],
["S","F","C","S"],
["A","D","E","E"]],
word = "ABCCED"
输出:true
思路
这题可以考虑使用回溯法,注意一下对边界值的判断,同时使用了一个辅助数组来存储对矩阵的访问状态,如搜索ABCCED,访问矩阵[0][0],符合要求,置辅助数组[0]为ture,代表已访问,然后访问[1][0]不符合,退回[0][0],继续[0][1]符合,依次类推。
class Solution {
public boolean exist(char[][] board, String word) {
if(board.length==0){
return false;
}
int rows=board.length;
int cols=board[0].length;
int pathLength=0;
if(word.length()>rows*cols){
return false;
}
boolean[] visted=new boolean[rows*cols];
for(int row=0;rowword.length()-1){
return true;
}
if(word.charAt(pathLength)=='\0'){
return true;
}
if(row>=rows||col>=cols){
return false;
}
if(row>=0&&row=0&&col
面试题13. 机器人的运动范围
地上有一个m行n列的方格,从坐标 [0,0] 到坐标 [m-1,n-1] 。一个机器人从坐标 [0, 0] 的格子开始移动,它每次可以向左、右、上、下移动一格(不能移动到方格外),也不能进入行坐标和列坐标的数位之和大于k的格子。例如,当k为18时,机器人能够进入方格 [35, 37] ,因为3+5+3+7=18。但它不能进入方格 [35, 38],因为3+5+3+8=19。请问该机器人能够到达多少个格子?
输入:m = 2, n = 3, k = 1
输出:3
输入:m = 3, n = 1, k = 0
输出:1
- 1 <= n,m <= 100
- 0 <= k <= 20
思路
这题与上一题的矩阵有相似之处,都是通过回溯法来解决。
class Solution {
public int movingCount(int m, int n, int k) {
if(m<=0||n<=0||k<0){
return -1;
}
boolean[] visted=new boolean[m*n];
int count=0;
count=countNumbers(m,n,k,0,0,visted);
return count;
}
public int countNumbers(int rows,int cols,int k,int row,int col,boolean[] visted){
if(col<0||col>=cols||row<0||row>=rows||visted[row*cols+col]){
return 0;
}
int count=0;
if(row>=0&&row=0&&col0){
sum+=numbers%10;
numbers/=10;
}
return sum;
}
}
面试题14- I. 剪绳子
给你一根长度为 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。
输入: 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1
输入: 10
输出: 36
解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36
- 2 <= n <= 58
思路:
动态规划,这个问题的目标是求剪出的各段绳子乘积的最大值,如我们可以将长度为4的绳子分割为3和1,也可以是2和2,这时一个大问题就被我们分解成了两个子问题,2或者3和1的在分割,那么我们就将一个大问题分解为了两个子问题的最优解,为了避免重复求解子问题,我们就从下往上,从解决最小问题开始。
class Solution {
public int cuttingRope(int n) {
if(n<2){
return 0;
}
if(n==2){
return 1;
}
if(n==3){
return 2;
}
int[] length=new int[n+1];
length[0]=0;
length[1]=1;
length[2]=2;
length[3]=3;
int max=0;
for(int i=4;i<=n;i++){
max=0;
for(int j=1;j<=i/2;j++){
max=max>length[j]*length[i-j]?max:length[j]*length[i-j];
length[i]=max;
}
}
max=length[n];
return max;
}
}
变种
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
限制:
2 <= n <= 1000
这一题我们可以看到n的范围明显变大了,因此牵涉到了两个问题,一个是结果的存储,一个是耗时的变大。
结果的存储我们可以考虑用long来存储,并时时将其取模运算,而耗时,我们可以考虑一下,在动态规划的过程中,若是我们每一步都取最优结果,那么组合起来会是一个最优结果吗,这就是贪心法。
我们可以从数学角度上进行证明,在n>=5时,我们要尽量多取长度为3的绳子,3(n-3)>n,显而易见,同时,剩余长度只有4米时,我们要选择将4米绳子分为2米和2米,2*2>1*3,同时在n>=5时,3(n-3)>2(n-2)。
class Solution {
public int cuttingRope(int n) {
if (n < 2) {
return 0;
}
if (n == 2) {
return 1;
}
if (n == 3) {
return 2;
}
int length3 = n / 3;
if (n % 3 == 1) {
length3--;
}
long sum = 1;
int length2 = (n - length3 * 3) / 2;
for (int i = 0; i < length3; i++) {
sum *= 3;
sum %= 1000000007;
}
for (int i = 0; i < length2;i++){
sum *= 2;
sum %= 1000000007;
}
return (int)sum;
}
}
面试题15. 二进制中1的个数
请实现一个函数,输入一个整数,输出该数二进制表示中 1 的个数。例如,把 9 表示成二进制是 1001,有 2 位是 1。因此,如果输入 9,则该函数输出 2。
输入:00000000000000000000000000001011
输出:3
解释:输入的二进制串 00000000000000000000000000001011 中,共有三位为 '1'。
输入:00000000000000000000000010000000
输出:1
解释:输入的二进制串 00000000000000000000000010000000 中,共有一位为 '1'。
思路:我们将输入字符与1进行与运算,就可以得到输入字符二进制的最右边的字符是否是1,同时,我们将1左移1位,这样就可以得到输入字符倒数第二位,依次类推,进行32次。
public class Solution {
// you need to treat n as an unsigned value
public int hammingWeight(int n) {
int flag=1;
int count=0;
while(flag!=0){
if((flag&n)!=0){
count++;
}
flag=flag<<1;
}
return count;
}
}
另一种思路
我们可不可以不进行32次判断,而是输入字符中有多少个1就进行多少次判断呢?我们观察一下9,将其转为2进制为1001,我们将9减去1,得到的二进制就是1000,与原数字进行与操作,还是1000,那么当最后一位不是1呢?10,二进制,1010,我们对其减1,得到1001,再与原数字进行与操作,得到1000,那么一个整数的二进制表示中有多少个1,我们就可以进行多少次操作。
public class Solution {
// you need to treat n as an unsigned value
public int hammingWeight(int n) {
int count=0;
while(n!=0){
n=(n-1)&n;
count++;
}
return count;
}
}