最近在看《剑指offer》,写几篇文章来记录一下,今天有如下几道算法题:算法
斐波那契数列
二进制中1的个数
数组中重复的数字
二维数组中的查找
替换空格
数值的整数次方
写一个函数,输入n,求斐波那契数列的第n项。
递归解法重复计算太多,效率过低,这里咱们使用时间复杂度为O(n)的方法。数组
解题思路
从下往上计算,首先根据f(0)和f(1)算出f(2),再根据f(1)和f(2)算出f(3).......以此类推就能够算出第n项。app
public class Fibonacci { public static long fibonacci(long n){ if (n<0) throw new RuntimeException("无效的输入"); if (n==0) return 0; if (n==1) return 1; long fibOne=0; long fibTwo=1; long fibSum=0; for (int i = 2; i <= n; i++) { fibSum=fibOne+fibTwo; fibOne=fibTwo; fibTwo=fibSum; } return fibSum; } }
请实现一个函数,输入一个整数,输出该数二进制表示中1的个数。
例如,把9表示成二进制就是1001,则输出2.
若是一个整数与1作 &(与)运算的结果是1,则表示该整数最右边一位是1,不然是0函数
public static int Solution1(int n) { int count = 0; //JAVA语言规范中,int整形占四个字节,总计32位 //对每个位置与1进行求与操做,再累加就能够求出当前数字的表示是多少位1 for (int i = 0; i < 32; i++) { if ((n & 1) == 1) { count++; } n = n >> 1; } return count; }
首先把n和1作与运算,判断n的最低位是否是1。接着把1左移一位获得2,再和n作&运算,就能判断n的次低位是否是1,这样反复左移,每次都能判断n的其中一位是否是1优化
public static int Solution2(int n) { int count = 0; int k = 1; for (int i = 0; i < 32; i++) { if ((n & k) == k) { count++; } k = k << 1; } return count; }
把一个整数减去1,再和原整数作与运算,会把该整数最右边的1变为0
该方法整数中有几个1就只须要循环几回指针
public static int Solution3(int n) { int count = 0; //数字的二进制表示中有多少个1就进行多少次操做 while (n != 0) { count++; //从最右边的1开始,每一次操做都使n的最右的一个1变成了0 n = n & (n - 1); } return count; }
在一个长度为n的数组里的全部数字都在0到n-1的范围。数组中某些数字是重复的,但不知道有几个数字重复,也不知道每一个数字重复的次数。 请找出数组中任意一个重复的数字。例如若是输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是重复的数字2或者3。
解题思路
咱们注意到数组中的数字都在0
到n-1
的范围内。若是这个数组中没有重复的数字,那么当数组排序后数字i
将出如今下标为i
的位置。code
从头至尾扫描这个数组中的每一个数字。当扫描到下标为i
的数字的时候,首先比较这个数字(用m
表示)是否是i。 若是是,接着扫描下一个数字。若是不是,再拿它和第m
个数字进行比较。 若是它和第m个数字相等,就找到一个重复的数字(该数字在下标为i和m的位置都出现了)。 若是它和第m个数字不想等,就把第i个数字和第m个数字交换,把m放到属于它的位置。 接下来再重复这个比较,交换的过程,直到发现一个重复的数字。排序
以数组{2,3,1,0,2,5,3}为例来分析找到重复数字的步骤。数组的第0个数字(从0开始计数,和数组的下标保持一致)是2, 与它的下标不相等,因而把它和下标为2的数字1交换,交换后的数组是{1,3,2,0,2,5,3}。 此时第0 个数字是1,仍然与它的下标不相等,继续把它和下标为1的数字3交换,获得数组{0,1,2,3,2,5,3}。 此时第0 个数字为0,接着扫描下一个数字,在接下来的几个数字中,下标为1,2,3的三个数字分别为1,2,3, 他们的下标和数值都分别相等,所以不须要作任何操做。 接下来扫描下标为4的数字2.因为它的值与它的下标不相等,再比较它和下标为2的数字。 注意到此时数组中下标为2的数字也是2,也就是数字2和下标为2和下标4的两个位置都出现了,所以找到一个重复的数字。递归
public class Test { public static int duplication;//用来保存重复数字 public static boolean duplicate(int[] arr) { //判断输入是否合法 if (arr == null || arr.length == 0) { return false; } for (int i = 0; i < arr.length; i++) { if (arr[i] < 0 || arr[i] >= arr.length) { return false; } } //核心代码 for (int i = 0; i < arr.length; i++) { while (arr[i] != i) { //若是两个数相等则返回true if (arr[i] == arr[arr[i]]) { duplication = arr[i]; System.out.println(arr[i]); return true; } //不然交换数字 int temp = arr[i]; arr[i] = arr[temp]; arr[temp] = temp; } } return false; } }
在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。 请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
解题思路
(1)首先选取数组中右上角的数字。若是该数字等于要查找的数字,则结束查找过程。
(2)若是该数字大于要查找的数字,则剔除这个数字所在的列。
(3)若是该数字小于要查找的数字,则剔除这个数字所在的行。ci
public class Test { public static boolean find(int[][] matrix, int number) { if (matrix == null || matrix.length < 1 || matrix[0].length < 1) { return false; } int rows = matrix.length; // 数组的行数 int cols = matrix[1].length; // 数组行的列数 int row = 0; // 起始开始的行号 int col = cols - 1; // 起始开始的列号 while (row >= 0 && row < rows && col >= 0 && col < cols) { if (matrix[row][col] == number) { // 若是找到了就直接退出 return true; } else if (matrix[row][col] > number) { col--; // 列数减一,表明向左移动 } else { // row++; // 行数加一,表明向下移动 } } return false; } }
请实现一个函数,把字符串中的每一个空格替换成"%20",例如“We are happy”,则输出“We%20are%20happy”
很容易想到能够直接遍历字符串替换空格,可是这样的话须要重复移动多个元素,时间复杂度为O(n^2),咱们来看下只须要移动一次的方法。
解题思路
首先遍历字符串,统计出空格数,计算出替换后的字符串的总长度。 每替换一个空格,长度增长2,所以替换后的字符串长度等于原来的长度加上2*空格数
。 咱们从字符串后面开始复制和替换。准备两个指针P1和P2,P1指向原始字符串末尾,P2指向替换以后的字符串的末尾。 接下来咱们向前移动指针P1,逐个把它指向的字符复制到P2指向的位置,当碰到空格后,把P1向前移动1格, 同时把P2向前移动3格。当P1和P2指向同一位置时,代表全部空格已替换完毕。
public class Test { public static void replaceBlank(char[] c) { // 判断输入是否合法 if (c == null || c.length <= 0) { return; } // 统计字符数组中的空白字符数 int numberOfBlank = 0; //统计c实际的字符数量 int numberOfPractical=0; int i=0; //'\u0000'是char默认值 while(c[i]!='\u0000'){ numberOfPractical++; if(c[i]==' ') numberOfBlank++; i++; } // 计算转换后的字符长度 int newLength = numberOfBlank * 2 + numberOfPractical; if (newLength > c.length) { // 若是转换后的长度大于数组的最大长度,直接返回失败 return; } // 若是没有空白字符就不用处理 if (numberOfBlank == 0) { return; } // indexOfOriginal 为指向原始字符串末尾的指针 int indexOfOriginal=numberOfPractical; // indexOfNew 为指向替换以后的字符串末尾的指针 int indexOfNew=newLength; // 当 indexOfOriginal = indexOfNew 时代表空格已所有替换完毕 while (indexOfOriginal >= 0 && indexOfOriginal < indexOfNew) { // 如是当前字符是空白字符,进行"%20"替换 if (c[indexOfOriginal] == ' ') { c[indexOfNew--] = '0'; c[indexOfNew--] = '2'; c[indexOfNew--] = '%'; } else { // 不然移动字符 c[indexOfNew--] = c[indexOfOriginal]; } indexOfOriginal--; } } }
实现函数double power(double base,int exponent),求base的exponent次方,不须要考虑大数问题。
很容易能够想到下面这种解法:
public static double power(double base, int exponent) throws Exception { if (base == 0.0 && exponent < 0) throw new Exception("无效输入"); //指数绝对值 int absExponent = exponent < 0 ? Math.abs(exponent) : exponent; double result = absPower(base, absExponent); //若是指数为负数,则转换成结果的倒数 if (exponent < 0) result = 1.0 / result; return result; } //求base的exponent次方 private static double absPower(double base, int exponent) { double result = 1.0; while (exponent-- > 0) { result *= base; } return result; }
这种解法全面但不够高效,假如算2^32则须要作31次乘法。
若是求2的32次方,咱们能够先求2^1,而后2^2,2^4,2^8.....直到2^32,这样就不用作31次乘法了,大大优化效率。
private static double absPower(double base, int exponent) { if (exponent == 0) return 1; if (exponent == 1) return base; double result = absPower(base, exponent >> 1); result *= result; //判断是否为奇数 if ((exponent & 1) == 1){ result *= base; } return result; }