1.剑指 Offer 13. 机器人的运动范围
思路:
设计到搜索题目,一般用广度优先(队列)或者深度优先(递归)来解决。
数位之和计算:
设一个数字x,将x对10取余可以求得其个位数,对x向下取整即向右移动一位,可以删除个位数得到十位数。
因此可以通过循环求得数位之和s
int sums(int x)
int s=0;
while(x!=0){
s+=x%10;
x=x/10;
}
return s;
数位和增量公式:
由于机器人每次只能移动一格,因此每次只需计算x到x±1的数位和增量
设x的数位和为Sx,x+1的数位和就为Sx+1;
①当(x+1)对10取余等于0时,Sx+1 = Sx - 8;
②当对10取余不等于0时,则Sx+1=Sx +1;
(x + 1) % 10 != 0 ? s_x + 1 : s_x - 8;
方法一:
深度优先遍历DFS:可以理解为暴力法模拟机器人在矩阵中的所有路径,DFS通过递归,先朝一个方向搜索到底,再回溯至上个节点,沿一个方向搜索,依次类推。
剪枝:在搜索中,遇到数位和超出目标值、此元素已访问。则应立即返回,称之为可行性剪枝。
算法解析:
- 递归参数:当前元素在矩阵中的行列索引i,j,两者的数位和si,sj
- 终止条件:当行列索引越界;数位和超出目标值;当前元素已访问过,这三种情况时,返回0,不计入可达解
- 递推工作:
- 标记当前单元格:将索引(i,j)存入数组中
- 搜索下一个单元格:计算当前元素下、右两个方向的数位和,并开启下层递归
- 回溯返回值:返回右方可达解+下方可达解+1。
代码:
class Solution {
int m,n,k;
boolean[][] visited;
public int movingCount(int m,int n,int k){
this.m = m;this.n=n;this.k=k;
this.visited = new boolean[m][n];
return dfs(0,0,0,0);
}
public int dfs(int i,int j,int si,int sj){
if(i>=m||j>=n||si+sj>k||visited[i][j])
return 0;
visited[i][j]=true;
return dfs(i+1,j,(i+1)%10 !=0 ?si+1:si-8,sj)+dfs(i,j+1,si,(j+1)%10 !=0 ?sj+1:sj-8)+1;
}
}
方法二
广度优先遍历BFS:和DFS相比,二者都是遍历整个矩阵,不同点在于搜索顺序不同。DFS是朝一个方向走到底再回退,二BFS则是按照平推的方式向前搜索,通常使用队列实现广度优先遍历
算法解析
- 初始化:将机器人初始点(0,0)加入到队列queue
- 迭代终止条件:queue为空,代表已遍历完所有可达解
- 迭代工作:
- 单元格出队:将队首单元格的索引、数位和弹出,作为当前搜索的单元格
- 判断是否跳过:若 索引越界、数位和超出目标值、当前元素已访问过,执行continue
- 标记当前单元格:将索引(i,j)存入集合visited
- 单元格入队:将当前元素的下方有方单元格的索引、数位和加入queue
- 返回值:集合visited的长度len
代码:
class Solution {
public int movingCount(int m,int n,int k){
boolean[][] visited =new boolean[m][n];
int res = 0;
Queue<int[]> queue = new LinkedList<int[]>();
queue.add(new int[]{0,0,0,0});
while(queue.size() > 0){
int[] x = queue.poll();
int i = x[0],j=x[1],si=x[2],sj =x[3];
if(i>=m||j>=n||k<si+sj||visited[i][j])
continue;
visited[i][j] = true;
res ++;
queue.add(new int[]{i+1,j,(i+1)%10 !=0?si+1:si-8,sj});
queue.add(new int[]{i,j+1,si,(j+1)%10 !=0?sj+1:sj-8});
}
return res
}
}
2.剑指 Offer 14- I. 剪绳子
思路:数学推导,找规律
代码:
class Solution{
public int cuttingRope(int n){
if(n<=3)
return n-1;
int a=n/3,b=n%3;
if(b==0)
return (int)Math.pow(3,a);
if(b==1)
return (int)Math.pow(3,a-1)*4;
return (int)Math.pow(3,a)*2;
}
}
3.剑指 Offer 14- II. 剪绳子 II
思路:和上题差不多,只是多了个大数取余,贪心算法。
-
如果 n == 2,返回1,如果 n == 3,返回2,两个可以合并成n小于4的时候返回n - 1
-
如果 n == 4,返回4
-
如果 n > 4,分成尽可能多的长度为3的小段,每次循环长度n减去3,乘积res乘以3;最后返回时乘以小于等于4的最后一小段;每次乘法操作后记得取余就行
-
以上2和3可以合并
代码:
class Solution {
public int cuttingRope(int n) {
if(n < 4){
return n - 1;
}
long res = 1;
while(n > 4){
res = res * 3 % 1000000007;
n -= 3;
}
return (int) (res * n % 1000000007);
}
}
4.剑指 Offer 15. 二进制中1的个数
思路:
逐位判断:判断最后一位是否为1,计数,再将n右移一位,循环
代码:
public class Solution {
public int hammingWeight(int n) {
int res = 0;
while(n != 0) {
res += n & 1;
n >>>= 1;
}
return res;
}
}
巧用 n & (n - 1)n&(n−1)
- (n−1) 解析: 二进制数字 nn 最右边的 11 变成 00 ,此 11 右边的 00 都变成 11
- n & (n - 1)n&(n−1) 解析: 二进制数字 nn 最右边的 11 变成 00 ,其余不变。
代码:
public class Solution {
public int hammingWeight(int n) {
int res = 0;
while(n != 0) {
res ++;
n &= n-1;
}
return res;
}
}
5.剑指 Offer 16. 数值的整数次方
思路:
递归
- 如果n == 0,返回1
- 如果n < 0,最终结果为 1/x^{-n}
- 如果n为奇数,最终结果为 x * x ^ {n - 1}
- 如果n为偶数,最终结果为 x ^ {2*(n/2)}
代码
class Solution {
public double myPow(double x, int n) {
if(n==0){
return 1;
}else if(n<0){
return 1/(x*myPow(x,-n-1));
}else if(n%2==1){
return x*myPow(x,n-1);
}else{
return myPow(x*x,n/2);
}
}
}
迭代
class Solution {
public double myPow(double x, int n) {
long b = n;
if(b < 0){
x = 1 / x;
b = - b;
}
double res = 1;
while(b != 0){
if(b % 2 != 0){
res *= x;
}
b >>= 1;
x *= x;
}
return res;
}
}
快速幂解析(二分法角度)
思路:
-
转化为位运算:
向下整除 n // 2等价于 右移一位 n >> 1 ;
取余数n%2 等价于 判断二进制最右一位值n&1 ; -
算法
代码
class Solution {
public double myPow(double x, int n) {
if(x == 0) return 0;
long b = n;
double res = 1.0;
if(b < 0) {
x = 1 / x;
b = -b;
}
while(b > 0) {
if((b & 1) == 1) res *= x;
x *= x;
b >>= 1;
}
return res;
}
}