这是我参与8月更文挑战的第20天,活动详情查看:8月更文挑战
机器人的运动范围
剑指Offer 13. 机器人的运动范围
地上有一个m行m列的方格,从坐标 [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。请问该机器人能够到达多少个格子?
示例1:
输入:m = 2, n = 3, k = 1
输出:3
示例2:
输入:m = 3, n = 1, k = 0
输出:1
提示:
- 1 <= n,m <= 100
- 0 <= k <= 20
题解
法一 BFS
看到题目中有“向x移动”之类的字眼就会想到BFS。而且必有除方格临界值外的限制条件:“行坐标和列坐标的数位之和大于k”。
根据题意大致可以划分以下几个关键点:
- 位数和的计算
- 四周方向的遍历
- 限制条件
- 格子数的统计
逻辑梳理
位数和的计算
-
利用字符串
将数值划分成由位数组成的数组,然后累加每个位数上的值。
function getSum(num){
let stringAry = num.toString().split('');
return stringAry.reduce((a,b)=>Number(a)+Number(b),0);
}
-
数学公式
- 对数值进行基于10的取余,得到该位数上的值
- 更新数值(更新后的数值 = 更新前的数值/10),就移除了之前的位数
- 对更新后的数值进行相同的操作(取余,更新)
- 当数值为0时,得出计算结果
function getSum(num){
let answer = 0;
while(num){
answer += num % 10;
// 向下取整,因为可能出现小数
num = Math.floor(num/10);
}
return answer;
}
四周方向的遍历
可以使用 方向数组 来进行遍历辅助。
// 方向数组
const directionAry = [
[-1, 0], // 上
[0, 1], // 右
[1, 0], // 下
[0, -1] // 左
];
本题可以简化成“向右”和“向下”的遍历操作
限制条件
单元格坐标 [ i, j ]不能超过其方格的边界
- i >= 0
- j >= 0
- i < m
- j < m
- getSum(offsetX) + getSum(offsetY) > k(题目规定)
此外,已经到达过的单元格,是不需要再纳入到到达个数统计到范畴内的,最便捷的做法就是使用Set,Set这一数据结构满足了 项的唯一性。
格子数的统计
利用Set的优点即可。
代码实现
/**
* @param {number} m
* @param {number} n
* @param {number} k
* @return {number}
*/
var movingCount = function(m, n, k) {
function getSum(num){
let answer = 0;
while(num){
answer += num % 10;
num = Math.floor(num / 10);
}
return answer;
}
// 方向数组
const directionAry = [
[-1,0], // 上
[0,1], // 右
[1,0], // 下
[0,-1], // 左
];
// 已经走过的坐标
let set = new Set(['0,0']);
// 遍历的坐标队列,题目要求从[0,0]开始走
let queue = [[0,0]];
// 遍历队列中的坐标
while(queue.length){
// 移除队首坐标
let [x,y] = queue.shift();
// 遍历方向
for(let i=0;i<4;i++){
let offsetX = x + directionAry[i][0];
let offsetY = y + directionAry[i][1];
// 临界值判断
if(offsetX < 0 || offsetX >= m || offsetY < 0 || offsetY >= n || getSum(offsetX) + getSum(offsetY) > k || set.has(`${offsetX},${offsetY}`)){
continue;
}
// 走过的格子就不再纳入统计
set.add(`${offsetX},${offsetY}`);
// 将该坐标加入队列(因为这个坐标的四周没有走过,需要纳入下次的遍历)
queue.push([offsetX,offsetY]);
}
}
// 走过坐标的个数就是可以到达的格子数
return set.size;
};
法二 DFS
/**
* @param {number} m
* @param {number} n
* @param {number} k
* @return {number}
*/
var movingCount = function(m,n,k){
function getSum(num){
let answer = 0;
while(num){
answer += num % 10;
num = Math.floor(num / 10);
}
return answer;
}
const directedArr = [
[1,0], // down
[0,1] // right
];
var set = new Set(['0,0']);
dfs(0,0,k);
function dfs(x,y,k){
for(let i = 0;i < 2;i++){
let offsetX = x + directedArr[i][0];
let offsetY = y + directedArr[i][1];
if(offsetX<0 || offsetX>m-1 || offsetY<0 || offsetY>n-1 || getSum(offsetX) + getSum(offsetY) > k || set.has(`${offsetX},${offsetY}`)){
continue;
}
set.add(`${offsetX},${offsetY}`);
dfs(offsetX,offsetY,k);
}
}
return set.size;
}
剪绳子
剑指Offer 14 - I. 剪绳子
给你一根长度为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。
示例1:
输入: 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1
示例2:
输入: 10
输出: 36
解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36
提示:2 <= n <= 58
题解
对7来说,可以拆成 3+4,最大乘积是12
对8来说,可以拆成 3+3+2,最大乘积是18
法一 动态规划
状态数组dp[i]表示:数字i拆分为至少两个正整数之和的最大乘积。为了方便计算,dp的长度是n+1,值初始化为1。
显然dp[2]等于1,外层循环应该从3开始遍历,一直到n停止。内存循环j从1开始遍历,一直到i之前停止,它代表数字 i 可以拆分成 j+(i-j)。但j*(i-j)不一定是最大乘积,因为i-j不一定大于dp[i-j](数字 i - j 拆分成整数之和的最大乘积),这里要选择最大的值作为 dp[i] 的结果。空间复杂度是O(N),时间复杂度O(N^2) 。代码实现如下:
/**
* @param {number} n
* @return {number}
*/
var cuttingRope = function(n){
const dp = new Array(n+1).fill(1);
for(let i = 3;i <= n;i++){
for(let j = 1;j < i;++j){
dp[i] = Math.max(dp[i],j * (i - j),j * dp[i - j]);
}
}
return dp[n];
}
法二 贪心法
找规律的思路,拆成多个2和3的和,保证乘积最大。因为2和3可以合成任何数字;根据贪心算法,尽量将原数拆成更多的3,然后再拆成更多的2,保证拆出来的整数的乘积结果最大。
但是有不足,如果整数n的形式是3k+1,如7时,照上面规则会被拆成“3+3+1“。应该被拆成”3+4“才对。
综上,算法的整体思路是:
- n除3的结果为a,余数是b
- 当b为0,直接将a个3相乘
- 当b为1,直接将(a-1)个3相乘,再乘以4
- 当b为2,将a个3相乘,再乘以2
空间复杂度为O(1),时间复杂度为O(1)。代码实现如下:
/**
* @param {number} n
* @return {number}
*/
var cuttingRope = function(n){
if(n===2) return 1;
if(n===3) return 2;
// a的含义:n能拆成的3的个数
const a = Math.floor(n/3);
const b = n % 3;
// n是3的倍数
if(b===0) return Math.pow(3,a);
// n是3k+1
if(b===1) return Math.pow(3,a-1) * 4;
return Math.pow(3,a) * 2;
}
坚持每日一练!前端小萌新一枚,希望能点个赞哇~