剑指offer(java)—— 算法和数据操作

递归和循环

面试题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;
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值