Problem:
给定一个 double 类型的浮点数 base 和 int 类型的整数 exponent,求 base 的 exponent 次方。
Intuition:
这个题目的第一反应是不是一个for循环就搞定了吗?然后写下下面一段代码
1 |
|
但是这样是明显会被面试官diss的,指数部分为0或者小于0的时候都没有处理,底数指数均为0的时候也没有抛出异常。
Solution:
1 | // 指数为正数的情况 |
给定一个 double 类型的浮点数 base 和 int 类型的整数 exponent,求 base 的 exponent 次方。
这个题目的第一反应是不是一个for循环就搞定了吗?然后写下下面一段代码
1 |
|
但是这样是明显会被面试官diss的,指数部分为0或者小于0的时候都没有处理,底数指数均为0的时候也没有抛出异常。
1 | // 指数为正数的情况 |
输入一个整数,输出该数二进制表示中 1 的个数。
这一题考的就是位运算了,这里求解的时候有几个点要注意。首先,千万不要用除法。除法效率比位运算低得多,面试官看到了肯定要扣分。其二,一定要注意负数符号扩展的情况,比如下面这段代码就是错误的
1 | public int numberOf1(int n) { |
因为如果数字是负数的话,符号位是1.我们对原来的数字不断按位右移得到的是0XFFFFFFFF,不但无法得到正确结果还会进入死循环,正确的做法是移动标志位,代码如下。
1 | public int numberOfs1(int n) { |
把一根绳子剪成多段,并且使得每段的长度乘积最大。
这一题比较容易,算是动态规划的开胃菜。首先我们聊一聊什么样的问题适合用动态规划。动态规划通常适合于要求我们求一个问题的最优解(通常是最大值或者最小值),而且该问题能够分解成若干个字问题,并且子问题之间还有重叠的更小的子问题,就可以考虑用动态规划来解决这个问题。下面的解法我们自己实现的方案既用了贪心算法的思想,也用了动态规划的思想。我们发现仅仅由dp[i-2],dp[i-3]就可以决定dp[i]里面的内容,原因是dp[i-p](p>3)里面的内容一定是可以由dp[i-2]和dp[i-3]来表示的。举个简单的例子,如果最优解是要用到dp[i-4],那么接下里剩下的4肯定是切成22,那么这种情况就与dp[i-2]2的情况是完全一致的。如果是dp[i-5],那么剩下对于5的切法一定是23,这个和dp[i-2]3或者dp[i-3]*2就是一致的。依次类推,这时属于贪心算法的内容。接下来就用动态规划来存之前计算的结果就可以了。
1 | //dynamic programming+greedy algorithm |
地上有一个 m 行和 n 列的方格。一个机器人从坐标 (0, 0) 的格子开始移动,每一次只能向左右上下四个方向移动一格,但是不能进入行坐标和列坐标的数位之和大于 k 的格子。
例如,当 k 为 18 时,机器人能够进入方格 (35,37),因为 3+5+3+7=18。但是,它不能进入方格 (35,38),因为 3+5+3+8=19。请问该机器人能够达到多少个格子?
使用深度优先搜索(Depth First Search,DFS)方法进行求解。回溯是深度优先搜索的一种特例,它在一次搜索过程中需要设置一些本次搜索过程的局部状态,并在本次搜索结束之后清除状态。这一题和前面一题比较类似,机器人从坐标(0,0)开始移动,当它准备进入坐标为(i,j)的格子时,通过检查坐标的数位和来判断机器人是否能够进入。如果机器人能够进入坐标为(i,j)的格子,再判断它是否能够进入4个相邻的格子(i,j-1),(i-1,j),(i,j+1),(i+1,j)。设置visited数组的原因是为了避免重复访问导致进入死循环,我们保证如果一个点已经被访问过它就不会再被check了从而保证程序能够在有限时间内退出。
1 | public class CodingInterview_013 { |
判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向上下左右移动一个格子。如果一条路径经过了矩阵中的某一个格子,则该路径不能再进入该格子。
使用回溯法(backtracking)进行求解,它是一种暴力搜索方法,通过搜索所有可能的结果来求解问题。回溯法在一次搜索结束时需要进行回溯(回退),将这一次搜索过程中设置的状态进行清除,从而开始一次新的搜索过程。在这一题中我们要做的是额外用一个数组来记录矩阵中的点是否被访问过的状态。
个人理解回溯法主要适用于程序需要你进行尝试的情况。那么我们在尝试一步后会修改某些状态,那么在尝试不成功的时候要注意对这些状态进行复原。这个过程颇类似于函数调用的时候对程序上下文进行压栈的情况。
1 | public class CodingInterview_012 { |
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。

此时问题的关键在于确定对半分得到的两个数组哪一个是旋转数组,哪一个是非递减数组。我们很容易知道非递减数组的第一个元素一定小于等于最后一个元素。那么对于初始情况来说,一个是非递减数组和一个旋转数组。其中最小的数肯定在旋转数组中,那么我们每次二分法都扔掉递增数组,只保留旋转数组。同时保证递增数组中最小的数字(也是第一个元素)被留下来。
通过修改二分查找算法进行求解(l 代表 low,m 代表 mid,h 代表 high):
当 nums[m] <= nums[h] 时,表示 [m, h] 区间内的数组是非递减数组,[l, m] 区间内的数组是旋转数组,此时令 h = m;
否则 [m + 1, h] 区间内的数组是旋转数组,令 l = m + 1。

1 | public int minNumberInRotateArray(int[] nums) { |
做到上面这里核心思路也就完成了,面试官应该也比较满意了,但是解法中还有一点没有考虑到。如果数组元素允许重复,会出现一个特殊的情况:nums[l] == nums[m] == nums[h],此时无法确定解在哪个区间,需要切换到顺序查找。例如对于数组 {1,1,1,0,1},l、m 和 h 指向的数都为 1,此时无法知道最小数字 0 在哪个区间。
1 | public int minNumberInRotateArray(int[] nums) { |
一只青蛙一次可以跳上 1 级台阶,也可以跳上 2 级… 它也可以跳上 n 级。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。
数学推导
跳上 n-1 级台阶,可以从 n-2 级跳 1 级上去,也可以从 n-3 级跳 2 级上去…,那么
f(n-1) = f(n-2) + f(n-3) + … + f(0)
同样,跳上 n 级台阶,可以从 n-1 级跳 1 级上去,也可以从 n-2 级跳 2 级上去… ,那么
f(n) = f(n-1) + f(n-2) + … + f(0)
综上可得
f(n) - f(n-1) = f(n-1)
即
f(n) = 2*f(n-1)
所以 f(n) 是一个等比数列
1 | public int JumpFloorII(int target) { |
一只青蛙一次可以跳上 1 级台阶,也可以跳上 2 级。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。
跳 n 阶台阶,可以先跳 1 阶台阶,再跳 n-1 阶台阶;或者先跳 2 阶台阶,再跳 n-2 阶台阶。而 n-1 和 n-2 阶台阶的跳法可以看成子问题,该问题就又可以转化成斐波那契数列的问题了。
1 | public int JumpFloor(int n) { |
我们可以用 21 的小矩形横着或者竖着去覆盖更大的矩形。请问用 n 个 21 的小矩形无重叠地覆盖一个 2*n 的大矩形,总共有多少种方法?
这个问题就是一个地地道道的斐波那契数列的问题,但是因为隐藏的比较深,看题的时候可能第一时间没办法发现这个事实。不过我们可以做到的是看到这个题第一时间反应过来这是一个递归的问题。我们可以轻易发现n=1时只有一种摆法,n=2时只有两种摆法。如果n>2,那么我们考虑整个图形的左上角。如果左上角的矩形是竖着摆的,那么这种情况就是f(n-1).如果左上角的矩形时横着摆的(21),那么我们可以发现它的下面也必须是一个横着摆的矩形。这样上下两个矩形一起就会拼成一个22的矩形,那么这种情况就是f(n-2)。鉴于左上角的矩形只有两种摆法情况(横着摆或者竖着摆),所以这个问题就被完全转化成了一个斐波那契数列的问题。
1 | public int RectCover(int n) { |
求斐波那契数列的第 n 项,n <= 39。
斐波那契数列很适合使用动态规划来求解,它在数学上是使用递归来定义的,公式为F(n) = F(n-1) + F(n-2).
使用动态规划来将重复计算的结果具有”记忆性”,就可以将时间复杂度降低为O(n).
1 | public class Solution { |