剑指Offer 13-16题解
目前我做过的剑指Offer…
剑指Offer 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。请问该机器人能够到达多少个格子?
来源:力扣(LeetCode)
分析 :
从起点【0,0】开始走能走的全部格子个数 <可采用DFS BFS>
其中约数有 格子i , j 的位数和不能大于 k <剪枝>
方法一 :DFS+剪枝
建立一个代表格子的二维数组vertor<vector<bool>>
从起点开始DFS, 遍历后标记 已进入即 false->true
一个计算数位和的函数
剪枝条件为 越过数组边界 + 数位和 > k
代码 : 时间(nm) 空间(nm)
class Solution {
public:
int movingCount(int m, int n, int k) {
vector<vector<bool>> visited(m,vector(n,false)); //创建数组表示此空间
int vi=visited.size(),vj=visited[0].size(); //计算空间边界
return DFS(visited,0,0,vi,vj,k);
}
int DFS(vector<vector<bool>> &visited,int i,int j,int vi,int vj,int k)
{ //i,j, 代表起点 ,vi,vj,边界 剪枝参数
if(i<0||i>=vi||j<0||j>=vj||visited[i][j]||weishuhe(i,j)>k) //剪枝判断条件
return 0;
visited[i][j]=true; //标记已进入
return 1+DFS(visited,i+1,j,vi,vj,k)+DFS(visited,i-1,j,vi,vj,k)+DFS(visited,i,j-1,vi,vj,k)+DFS(visited,i,j+1,vi,vj,k); //返回当前格子 + 接下来的可行格子
}
int weishuhe(int i,int j)
{
int sum=0;
while(i) //i各位数和
{
sum+=i%10;
i=i/10;
}
while(j) //j各位数和
{
sum+=j%10;
j=j/10;
}
return sum;
}
};
方法二: BFS +剪枝
建立一个代表格子的二维数组vertor<vector<bool>>
创建一个队列 辅助实现BFS
剪枝条件不变 越界 + 位数和 > k
代码:时间(nm) 空间(nm)
class Solution {
public:
int movingCount(int m, int n, int k) {
//开辟一个m,n数组表示格子
vector<vector<bool>> visited(m,vector(n,false));
//辅助队列q
queue<vector<int>> q;
//初值 i,j,i的位数和,j的位数和
q.push({0,0,0,0});
//读取队首数组
vector<int> x;
//记录可进入的格子数
int s=0;
//遍历条件 队列不空
while(q.size()>0)
{
x=q.front();
q.pop();
//剪枝
if(x[0]>m-1||x[1]>n-1||x[2]+x[3]>k||visited[x[0]][x[1]])
continue;
visited[x[0]][x[1]]=true;
++s;
//入队 这里位数和用的数学规律 进位 位数和会减少8 不进位则加1
q.push({x[0]+1,x[1],(x[0]+1)%10==0?x[2]-8:x[2]+1,x[3]});
q.push({x[0],x[1]+1,x[2],(x[1]+1)%10==0?x[3]-8:x[3]+1});
}
return s;
}
剑指Offer 14-l. 剪绳子
给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]…k[m-1] 。请问 k[0]k[1]…*k[m-1] 可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。
来源:力扣(LeetCode)
分析:
可采用动态规划的思想 :
可贪心思想:
可数学方法:
方法一: 动态规划 时间(n^2) 空间(n)
先看看是不是长度小于4的特殊情况(因为必须剪成至少2段)
列举出长度n为1,2,3的剪法的最大积 (不要求必须剪成至少两段)(因为是子段,实际绳子已经是大于等于两段了)
当长度 n 增加的过程中,切割成两段,并记录这两段绳子剪发最大积的积,遍历每种可能取最大值为长度n的剪法最大积
累加循环至所需值
class Solution {
public:
//判断那个数更大
int max(int a,int b)
{
return a>b? a:b;
}
int cuttingRope(int n) {
if(n<4) //是否是特殊情况
return n-1;
//初始化数组
vector<int> dp(n+1);
//列出其1,2,3的最大剪积
dp[1]=1;
dp[2]=2;
dp[3]=3;
//开始动态规划
for(int i=4;i<=n;++i)
{
for(int j=1;j<=i/2;j++)
{
//将其分为 (i-j) 和 j 两段
//找出两段((i-j) ,j )的最大剪积(dp[i-j] ,dp[j])的 最大积
dp[i]=max(dp[i],dp[j]*dp[i-j]);
}
}
return dp[n];
}
};
方法二 : 贪心算法 时间(n) 空间(1)
一般情况 在 (n>4) 时,分两段的积 > 不分, 分三段的积>分两段
所以在长不小于四的情况下尽量多分出3,为什么是3呢,因为大于3可再分且再分的积更大
特殊情况 (n<4) 不分反而更大,若没有经历上一步则分出一个1(去满足必须分一段以上)
class Solution {
public:
int cuttingRope(int n) {
//小于4且未分过,结果分出一个1
if(n<4)
return n-1;
int i=1;
//大于4的情况 尽量多分
while(n>4)
{
n-=3;
//每分一次
//总积多×3
i*=3;
}
return i*n;
}
};
方法三:数学方法
数学方法很厉害,我就不介绍了,我们应该更注重思维
算出来的结果就如贪心得出来的一样,我就不介绍了
代码:
class Solution {
public:
int cuttingRope(int n) {
if(n<4)//小于4 情况
return n-1;
//大于4的情况
int a=n/3,b=n%3;
if(b==0)
return pow(3,a);
if(b==1)
return pow(3,a-1)*4;
return pow(3,a)*b;
}
};
剑指Offer 14-ll. 剪绳子
给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]…k[m - 1] 。请问 k[0]k[1]…*k[m - 1] 可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。来源:力扣(LeetCode)
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
来源:力扣(LeetCode)
分析:和上题一样
此处因为数据量非常大,所以用取余操作
因为数据量巨大,所以动态规划也不太推荐,相比之下动态规划的时间和空间都会很大
方法一: 贪心 时间(n) 空间(1)
代码:
class Solution {
public:
int cuttingRope(int n) {
//小于4的情况
if(n<4)
return n-1;
//用long 储存是因为1e9*3大于int 最大值
//也可用 unsign int
long int i=1;
int p=1e9+7;
while(n>4)
{
n-=3;
i=(i*3)%p;
}
return (i*n)%p;
}
};
剑指Offer 15. 二进制中1的个数
请实现一个函数,输入一个整数(以二进制串形式),输出该数二进制表示中 1 的个数。例如,把 9 表示成二进制是 1001,有 2 位是 1。因此,如果输入 9,则该函数输出 2。
来源:力扣(LeetCode)
思路:
除二 判断余数
位运算
方法一: 除二 判余 时间(n) 空间(1)
代码:
class Solution {
public:
int hammingWeight(uint32_t n) {
int i=0;
while(n)
{
//除二 判断余数
if(n%2!=0)
++i;
n/=2;
}
return i;
}
};
方法二:位运算 时间(n) 空间(1)
class Solution {
public:
int hammingWeight(uint32_t n) {
int i=0;
while(n)
{
//n与1 与运算
if(n&1==1)
++i;
//n右移
n=n>>1;
}
return i;
}
};
剑指Offer 16. 数值的整数次方
实现函数double Power(double base, int exponent),求base的exponent次方。不得使用库函数,同时不需要考虑大数问题。
来源:力扣(LeetCode)
思路:
暴力直接累成 (暴力都不推荐 且此题暴力超时)
二分法幂运算 优化
方法一 : 时间(log n) 空间(1)
优化累成
判断n的2进制数位是否为1 循环 x = x^2 n 右移
代码:
class Solution {
public:
double myPow(double x, int n) {
double result=1;
//注意取值范围
long l=n;
//负数 取倒 取反
if(l<0)
{
x=1/x;
l=-l;
}
while(l)
{
//二进制位是否为一
if(l&1)
result*=x;
//x变平方
x*=x;
//右移l
l>>=1;
}
return result;
}
};