虽然疫情仍是严峻,但总会过去。在此居家办公之际,应该趁这个时机好好提高下自我,多读书多看报,少吃零食多运动哈哈。git
最近看一些文章和题目有接触一些算法题,我整理了一下收录daily-question 的 algorithm 文件夹中,后续会继续增长,本文分享我整理的十个算法题目。github
问题描述:算法
一个矩阵中只有 0 和 1 两种值,每一个位置均可以和本身的上、下、左、右 四个位置相连,若是有一片 1 连在一块儿,这个部分叫作一个岛,求一个矩阵中有多少个岛?数组
举例: 下面这个矩阵中有4个岛。bash
let arrIsland = [
[0,0,1,0,1,0],
[1,1,1,0,1,0],
[1,0,0,1,0,0],
[0,0,0,0,0,1]
]
// 四个岛分别是 【(0,2)(1,0)(1,1)(1,2)(2,0)】 【(0,5),(1,5)】 【(2,3)】【(3,5)】
复制代码
思路:dom
用递归与双循环实现,循环中递归找到一个岛(即找出 1 及其上下左右的 1),将此岛标记(我标记为2),而后重复依次找出剩下的岛函数
注意边界状况及不等于1的状况,此时应结束递归。post
参考答案:性能
function islandCount(arr){
if (!arr || arr.length === 0) {
return;
};
let N = arr.length, M = arr[0].length, res = 0;
for(let i = 0; i < N; i++){
for(let j = 0; j < M; j++){
if (arr[i][j] === 1) {
++res;
infect(arr,i,j,N,M);
}
}
}
return res;
}
// 递归函数,传入 数组arr, x坐标 i, y坐标j 数组长度N及数组内元素长度M
function infect(arr,i,j,N,M){
// 处理边界状况及不为1的状况,此时结束递归
if (i < 0 || j < 0 || i >= N || j >= M || arr[i][j] !== 1) {
return;
};
arr[i][j] = 2; // 将找到的岛元素标记,避免重复
infect(arr,i,j-1,N,M); // 向左寻找
infect(arr,i+1,j,N,M); // 向右寻找
infect(arr,i,j+1,N,M); // 向下寻找
infect(arr,i-1,j,N,M); // 向上寻找
}
let arrIsland = [
[0,0,1,0,1,0],
[1,1,1,0,1,0],
[1,0,0,1,0,0],
[0,0,0,0,0,1]
];
console.log(islandCount(arrIsland)); // 4
复制代码
关于汉诺塔:测试
汉诺塔:汉诺塔(又称河内塔)问题是源于印度一个古老传说的益智玩具。大梵天创造世界的时候作了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序从新摆放在另外一根柱子上。而且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘。
参考答案:
function hanoiProcess(n,from,to,help){
if (n < 1) {
return;
}
if (n == 1) { // 最后一个从from移到to
console.log("Move 1 from " + from + " to " + to);
} else{
hanoiProcess(n-1, from, help, to); // 前n-1个从from移到help上,能够借助to
console.log("Move "+ n +" from " + from + " to " + to);
hanoiProcess(n-1, help, to, from); // 再把n-1个从help移到to,能够借助from
}
}
hanoiProcess(3, "左", "右", "中");
// Move 1 from 左 to 右
// Move 2 from 左 to 中
// Move 1 from 右 to 中
// Move 3 from 左 to 右
// Move 1 from 中 to 左
// Move 2 from 中 to 右
// Move 1 from 左 to 右
复制代码
问题描述:
母牛每一年生一只母牛,新出生的母牛成长三年后也能每一年生一只母牛,假设不会死。求 N 年后,母牛的数量。
思路:
由于新生的母牛,只有等到第四年才能生小母牛。因此前 4 年,只有原来的一头母牛每一年生一头。
第一年:原始牛 共 1 头
第二年:原始牛生了 牛 A 共两头
第三年: 原始牛生了 牛 A , 牛 B 共三头
第四年: 原始牛生了 牛 A , 牛 B, 牛 C ,共四头
第五年: 原始牛生了 牛 A , 牛 B, 牛 C,牛 D 共五头, 牛 A 生了牛 A1 共两头,其中牛 A 多算了一次。咱们将牛 A 视为一头新的原始牛抽离出去,则原来的原始牛生了 牛 B, 牛 C,牛 D 共四头, 新原始牛生了牛 A1 共两头,从这里你会发现,第五年剩的数量 = 第四年生的的数量 + 第二年生的数量
接着算第六年:原始牛生了 牛 A , 牛 B, 牛 C,牛 D,牛 F 共六头,牛 A 生了牛 A1,牛 A2 共三头, 牛 B 生了牛 B1 共两头
单独把牛 A 拿出来作原始牛,你会发现剩下的
原始牛生了 牛B, 牛C,牛D,牛F, 牛B生了牛B1共两头
与第五年的状况原始牛生了 牛A , 牛B, 牛C,牛D 共五头, 牛A生了牛A1共两头
及其类似,数量规则一致只是名字不同而已,那么 第六年的数量 = 第五年的数量 + 第三年的数量,以此类推能够得出 f(n) = f(n-1) + f(n-3), 当 n <= 4 时, f(n) = n, 是否是有点斐波那契数列的感受??能够用递归实现!!
参考答案:
function cow(n) {
if (n < 1) {
return;
}
let count = 0;
if (n > 4) {
count = cow(n - 1) + cow(n - 3);
} else {
count = n;
}
return count;
}
let n = 7;
console.log(n + ' 年后,牛的数量是: ' + cow(n));
// 7 年后,牛的数量是: 13
复制代码
例如字符串: (ababccdeajxac)
function getMaxNumberOfChar(str) {
return (str + '').split('').reduce(
function(pre, cur, index, arr) {
cur in pre ? pre[cur]++ : (pre[cur] = 1);
pre[cur] > pre.value && ((pre.char = cur), (pre.value = pre[cur]));
return pre;
},
{ value: 0 }
);
}
getMaxNumberOfChar('ababccdeajxac'); // Object {value: 4, a: 4, char: "a", b: 2, c: 3…}
复制代码
此外,能够考虑用正则来辅助处理:
function getMaxNumberOfChar(str) {
return (str + '')
.split('')
.sort()
.join('')
.match(/(\w)\1*/g) // \1表示\w匹配到的字母 \1是匹配第一个分组匹配到的内容
.reduce(
function(pre, cur) {
return cur.length > pre.value
? { value: cur.length, char: cur[0] }
: pre;
},
{ value: 0 }
);
}
getMaxNumberOfChar('ababccdeajxac'); // Object {value: 4, char: "a"}
复制代码
这里拓展一下 reduce 函数的用法
// reduce 函数
// array.reduce(function(accumulator, currentValue, currentIndex, arr), initialValue)
// reducer回调函数自己接受几个参数,第一个参数是 accumulator 累加器,第二个是数组中的 item,第三个参数是该项的索引,最后一个参数是原始数组的引用。
// initialValue 为reduce初始值,不然视数组第一个值为初始值,选填
const array1 = [1, 2, 3, 4];
// 1 + 2 + 3 + 4
console.log(
array1.reduce((accumulator, currentValue) => {
console.log(accumulator, currentValue);
return accumulator + currentValue;
})
);
复制代码
尽量的全面正确的解析一个任意 url 的全部参数为 Object,注意边界条件的处理
let url =
'http://www.suporka.com/?user=suporka&id=123&id=456&city=%E5%8C%97%E4%BA%AC&enabled';
parseParam(url);
/* 结果 { user: 'suporka', id: [ 123, 456 ], // 重复出现的 key 要组装成数组,能被转成数字的就转成数字类型 city: '北京', // 中文需解码 enabled: true, // 未指定值得 key 约定为 true } */
复制代码
解法
function parseParam(url) {
const paramsStr = /.+\?(.+)$/.exec(url)[1]; // 将 ? 后面的字符串取出来
const paramsArr = paramsStr.split('&'); // 将字符串以 & 分割后存到数组中
let paramsObj = {};
// 将 params 存到对象中
paramsArr.forEach(param => {
if (/=/.test(param)) {
// 处理有 value 的参数
let [key, val] = param.split('='); // 分割 key 和 value
val = decodeURIComponent(val); // 解码
val = /^\d+$/.test(val) ? parseFloat(val) : val; // 判断是否转为数字
if (paramsObj.hasOwnProperty(key)) {
// 若是对象有 key,则添加一个值
paramsObj[key] = [].concat(paramsObj[key], val);
} else {
// 若是对象没有这个 key,建立 key 并设置值
paramsObj[key] = val;
}
} else {
// 处理没有 value 的参数
paramsObj[param] = true;
}
});
return paramsObj;
}
复制代码
题目:
楼梯台阶有 12 阶,一步只能走 1 阶或者 2 阶,那么,请问走完楼梯有多少走法?
这里涉及到动态规划,所谓动态规划,意思就是说,大事化小,小事化了。术语的话,包含三个,最优子结构,边界,状态转移公式
再来分析这道题目——
走到最后一个台阶的前一个状况,只能有两种,就是从第 11 台阶走一步上来,或者从 10 台阶走两步上来,那么无论有多少走法走到了 11 阶假设是 X 种走法吧,假设是 Y 种走法走到了 10 阶,那么,走到 12 阶的走法必定是 X+Y,这个是成立的吧。这就是最优子结构
那什么是边界呢?本例子中,走到第一个台阶,就一种走法吧,没有台阶,那就 0 种走法吧,走到第二个台阶,也就 2 种走法,其实这就是边界了。
那么状态转移公式就水到渠成了, F(n) = F(n-1) + F(n-2),看起来是否是有点像斐波那契数列??
代码以下:
function fun(n) {
if (n < 0) {
return 0;
}
if (n === 1) {
return 1;
}
if (n === 2) {
return 2;
}
return fun(n - 1) + fun(n - 2);
}
console.log('12台阶的走法 :' + fun(12));
复制代码
咱们以前在斐波那契数列里面讲过,这种递归有性能问题,根据斐波那契数列的优化,改写代码以下:
function fun(n) {
if (n < 0) {
return 0;
}
if (n === 1) {
return 1;
}
if (n === 2) {
return 2;
}
var a = 1;
var b = 2;
var temp = 0;
for (var i = 3; i <= n; i++) {
temp = a + b;
a = b;
b = temp;
}
return temp;
}
console.log('12台阶的走法 :' + fun(12));
复制代码
先来道简单的题目:
给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们组成的数组。 你不能重复利用这个数组中一样的元素。
复制代码
比较容易想到的方法是用两层循环,不断遍历找出和为目标值的两个元素,而后存进数组。
var nums = [8, 9, 2, 15, 7, 1];
var target = 9;
var twoSum = function(nums, target) {
var result = [];
for (var i = 0; i < nums.length; i++) {
for (var j = i + 1; j < nums.length; j++) {
if (nums[i] + nums[j] === target) {
result.push([nums[i], nums[j]]);
}
}
}
return result;
};
console.log(twoSum(nums, target)); //[ [ 8, 1 ], [ 2, 7 ] ]
复制代码
若是要求咱们使用递归,该如何实现呢?
这个和我上一个算法《走楼梯的动态规划》有些类似,咱们也来动态规划下:
假设数组和目标值以下
var nums = [8, 9, 2, 15, 7, 1];
var target = 9;
复制代码
首先咱们拿出第一个元素 8 ,再从后面剩下的[9, 2, 15, 7 ,1]
找 9-8 (即1)
, 找到与目标值差值(这里是 1)则返回这个组合,找不到返回空数组
而后再从剩下的[9, 2, 15, 7 ,1]
找出组合值等于目标值的数组,即重复 1 步骤
将 1,2 步骤合并就是咱们所求的组合
状态转移公式为 f(n) = f(n 首项与目标值差值组合).concat(f(n - 1))
边界。当数组长度小于取出元素个数时,返回空数组。当取出元素为 1 时,返回目标值数组。
// 如下是代码
var nums = [8, 9, 2, 15, 7, 1];
var target = 10;
function search(arr, m, n = 2) {
if (arr.length < n) return [];
if (n === 1) return arr.filter(i => i === m);
return search(arr.slice(1), m - arr[0], 1)
.map(item => [arr[0], item])
.concat(search(arr.slice(1), m));
}
console.log(search(nums, target));
复制代码
升级版
从一个数组中找出 N 个数,其和为 M 的全部可能
复制代码
从上面得知,若是使用循环,取出 2 个数就是两层循环,3 个数就是三层循环,以此类推,n 越大循环越多,显然不可取。因此选择第二种方法,也就是递归。
上面已经为咱们写好了递归的雏形,如今对其进行改造
上面边界 n === 1
其实还能够降为 0, 由于当 n === 0 && m === 0
时,上一步的 arr[0]
就是咱们要找的最后一个数,并且在 map 函数中,咱们已经将 arr[0]
置为首位,此时只要返回一个长度为 1 且首项为空的数组([[]]
),而且在 map 函数中将其 item([]
) 展开便可
注:这里要花点时间好好理解下,比较绕
// 代码以下
function search(arr, m, n) {
if (n === 0) return m === 0 ? [[]] : [];
if (arr.length < n) return [];
return search(arr.slice(1), m - arr[0], n - 1)
.map(item => [arr[0], ...item])
.concat(search(arr.slice(1), m, n));
}
// 测试一下
var nums = [8, 9, 2, 15, 7, 1];
var target = 10;
console.log(search(nums, target, 3)); // [[2,7,1]]
复制代码
仍是一样的问题:
从一个数组中找出 N 个数,其和为 M 的全部可能
复制代码
数组中选取不固定数值 N ,咱们能够尝试着使用标记的方式,咱们把 1 表示成选取状态, 把 0 表示成未选取状态。
假设数组 const arr=[1,2,3,4]
,对应着每一个元素都有标记 0 或者 1 。若是 N=4 ,也就是在这个数组中,须要选择 4 个元素,那么对应的标记就只有一种可能 1111 ,若是 N=3 ,那就有 4 种可能,分别是 1110 、 1101 、1011 以及 0111 (也就是 C4 取 3->4 ) 种可能。
标记中有几个 1 就是表明选取了几个数,而后再去遍历这些 1 全部可能存在的排列方式,最后作一个判断,这个判断就是:每一种排列方式,都表明着数组中不一样位置的被选中的数的组合,因此这里就是将选中的这些数字,进行求和运算,而后判断求出的和是否是等于 M 。
0101 明显就是二进制嘛
对于 arr 来讲,有 4 个元素,对应的选择方式就是从 0000( N = 0 )到 1111( N = 4 )的全部可能。
而 1111 就是 15 的二进制,也就是说这全部的可能其实对应的就是 0 - 15 中全部数对应的二进制。
这里的问题最终变成了如何从数组长度 4 推导出 0 - 15
这里采用了位运算--左移运算, 1 << 4 的结果是 16 。 因此咱们能够创建这样一个迭代:
const arr = [1, 2, 3, 4];
let len = arr.length,
bit = 1 << len;
// 这里忽略了 0 的状况(N = 0),取值就是 1 - 15
for (let i = 1; i < bit; i++) {
// ...
}
复制代码
最简单的方式:
const n = num => num.toString(2).replace(/0/g, '').length;
复制代码
这其实也是一道算法常考题,由于位运算是不须要编译的,确定速度最快。
PS: 若是不理解位运算为什么会提升性能的同窗,能够自行搜索一下位运算。简单点说就是:位运算直接用二进制进行表示,省去了中间过程的各类复杂转换,提升了速度。
咱们尝试使用 & 运算来解决这个问题
首先咱们确定知道 1 & 1 = 1; 1 & 0 = 0
这些结论的。因此咱们从 15 & 14 => 14
能够推导出 1111 & 1110 => 1110
,为何能够这样推导呢,由于 15 的二进制就是 1111 ,14 同理。
咱们能够看到,经过上面的操做消掉了最后的 1。
因此咱们能够创建一个迭代,经过统计消除的次数,就能肯定最终有几个 1 了。代码以下:
const n = num => {
let count = 0;
while (num) {
num &= num - 1;
count++;
}
return count;
};
复制代码
如今咱们已经能够把全部的选取可能转变为遍历一个数组,而后经过迭代数组中的每一个数对应的二进制,有几个 1 来肯定选取元素的个数。
那么,如今须要的最后一层判断就是选取的这些数字和必须等于 M
这里其实就是创建一个映射:
1110 到 [1, 2, 3, 4] 的映射,就表明选取了 2, 3, 4,而后判断 2 + 3 + 4 与 M 。
这里能够这样看:1110 中的左边第一个 1 对应着数组 [1, 2, 3, 4] 中的 1 。
如今有一个问题,该如何创建这个映射关系呢?
咱们知道前者 1110 其实就是对应的外层遍历中的 i = 14 的状况。
再看看数组[1, 2, 3, 4] ,咱们能够将元素及其位置分别映射为 1000 0100 0010 0001。
实现方式也是经过位运算--左位移来实现:
1 << inx ,inx 为数组的下标。
复制代码
对 位掩码 不熟悉的童鞋会有点晕,这里简单科普下:
实质上,这里的 1 << j ,是指使用 1 的移位来生成其中仅设置第 j 位的位掩码。
好比:14 的二进制表示为 1110,其表明(从右往左)选取了第 2 , 3 , 4 位。
那么(下面故意写成上下对应的方式):
// demo1
1110 & 0001 = 0000;
// demo2
1110 & 0010 = 0010;
复制代码
PS: 经过上面代码,咱们能够看到上下对应的 0 和 1 在进行 & 运算之后,得出的结果和在 js 中进行相同条件下 & 运算的结果相同。
因此:
1 << 0 // 1 -> 0001
1 << 1 // 2 -> 0010
1 << 2 // 4 -> 0100
1 << 3 // 8 -> 1000
// 说白了,就是把左边的值变成二进制形式,而后左移或者右移,超出补 0
// 因此, 1110 对应着 第一位没有选取,那么 1110 & 0001(设置为第一位的位掩码) = 0,若是 i & (1 << inx) !== 0 表明该位被选取了
for(let j = 0; j < arr.length; j++){
if((i & (1 << j) !== 0) {
// 表明这个数被选取了,咱们作累加求和就行
}
}
复制代码
复制代码因此综上所述,最终代码实现以下:
// 参数依次为目标数组、选取元素数目、目标和
const search = (arr, count, sum) => {
// 计算某选择状况下有几个 `1`,也就是选择元素的个数
const n = num => {
let count = 0;
while (num) {
num &= num - 1;
count++;
}
return count;
};
let len = arr.length,
bit = 1 << len,
res = [];
// 遍历全部的选择状况
for (let i = 1; i < bit; i++) {
// 知足选择的元素个数 === count
if (n(i) === count) {
let s = 0,
temp = [];
// 每一种知足个数为 N 的选择状况下,继续判断是否知足 和为 M
for (let j = 0; j < len; j++) {
// 创建映射,找出选择位上的元素
if ((i & (1 << j)) !== 0) {
s += arr[j];
temp.push(arr[j]);
}
}
// 若是这种选择状况知足和为 M
if (s === sum) {
res.push(temp);
}
}
}
return res;
};
复制代码
这里位操做思路和代码都很棒。可是 bit 位移有溢出问题。当数组长度大于 30 的时候,位操做已经溢出不精准。所以仅供参考其思想,不能做为其标准答案。
原文地址: 从一个数组中找出 N 个数,其和为 M 的全部可能--最 nice 的解法
问题: 输入 n 个整数,找出其中最大的 K 个数。例如输入 4,5,1,6,2,7,3,8 这 8 个数字,则最大的 4 个数字是 8,7,6,5,。
比较简单的是将这些数字组合成一个数组,而后进行从大到小进行排序,取前 K 个便可。
对整个数组进行排序有点浪费,咱们只是取前 K 个,后面剩下的不进行排序也行。所以,对此数组使用选择算法获取前 K 个数便可。
function getLargeNumber(array, k) {
if (array == null || k <= 0 || k > array.length) {
return [];
}
let length = array.length,
i,
j,
maxIndex,
maxValue,
temp;
for (i = 0; i < k; i++) {
maxIndex = i;
maxValue = array[maxIndex];
for (j = i + 1; j < length; j++) {
//经过循环选出最小的
if (array[j] > maxValue) {
maxIndex = j;
maxValue = array[maxIndex];
}
}
// 交换位置
temp = array[i];
array[i] = maxValue;
array[maxIndex] = temp;
}
return array.slice(0, k);
}
// 测试一下
var nums = [8, 9, 2, 15, 7, 1];
console.log(getLargeNumber(nums, 3)); // [15,9,8]
复制代码
咱们能够利用快排中 partion 函数的思想来作作题。
由于 partion 可使得序列分为 2 部分:左边的值都小于哨兵,右边的值都大于哨兵。因此咱们只要找处处于第 k 位置的哨兵便可,也就是说找到第 k 大的值所在的位置便可,那么它的左边的 k-1 值都小于等于第 k 大值。显然,前 k 个值即为咱们所求的最小 k 个数。在咱们的划分过程有 3 种状况:
var nums = [8, 9, 2, 15, 7, 1, 13, 35, 24];
function getLargeNumber(array, k) {
if (array == null || k <= 0 || k > array.length) {
return [];
}
partition(array, 0, array.length - 1, k - 1);
return array.slice(0, k);
}
function partition(array, low, high, k) {
if (low < high) {
// 获取 low 至 high 之间一个随机数
let pivot = Math.floor(Math.random() * (high - low + 1)) + low;
// 此随机数对应的元素与最后一位暂时交换(后面会再交换一次),咱们先找到有多少个数大于此随机数,大的话从左到右排列
swap(array, pivot, high);
let index = low;
for (let i = low; i < high; i++) {
if (array[i] > array[high]) {
// 这里即是一次选择排序
swap(array, i, index);
index++;
}
}
// 交换数组第index个和刚才置于数组末尾的随机数组元素,这样array[index]左边的数都比array[index]大
swap(array, index, high);
// 若是index > k,说明咱们刚才排的范围过大,应该缩小范围再次递归寻找
// 若是 index < k,说明咱们刚才拍的范围太小,应该扩大范围再次递归寻找
if (index > k) {
partition(array, low, index - 1, k);
} else if (index < k) {
partition(array, index + 1, high, k);
}
}
}
function swap(array, one, two) {
[array[one], array[two]] = [array[two], array[one]];
}
console.log(getLargeNumber(nums, 3)); // [35,24,15]
复制代码
咱们知道,最小堆的顶部结点为该堆的最小值,所以咱们建立一个具备 K 的节点的最小堆(能够先取该数组的前 K 个元素)调整为最小堆。
以后将第 K + 1 个元素与堆顶对比,若是大于堆顶元素,则说明堆顶元素不是第 K 大的值,所以将堆顶元素替换为第 K + 1 个元素,并调整此最小堆,以此类推至数组的最后一个元素,则最后整个最小堆即为所求答案。
// 创建堆
function buildHeap(nums) {
// 注意这里的头节点是从0开始的,因此最后一个非叶子结点是 parseInt(nums.length/2)-1
let start = parseInt(nums.length / 2) - 1;
let size = nums.length;
// 从最后一个非叶子结点开始调整,直至堆顶。
for (let i = start; i >= 0; i--) {
adjustHeap(nums, i, size);
}
return nums;
}
// 调整最小堆,使index的值小于于左右节点
function adjustHeap(nums, index, size) {
// 交换后可能会破坏堆结构,须要循环使得每个父节点都大于左右结点
while (true) {
let min = index;
let left = index * 2 + 1; // 左节点
let right = index * 2 + 2; // 右节点
if (left < size && nums[min] > nums[left]) min = left;
if (right < size && nums[min] > nums[right]) min = right;
// 若是左右结点大于当前的结点则交换,并再循环一遍判断交换后的左右结点位置是否破坏了堆结构(比左右结点小了)
if (index !== min) {
[nums[index], nums[min]] = [nums[min], nums[index]];
index = min;
} else {
break;
}
}
}
// 获取最大的前K个数
function getLargeNumber(nums, k) {
// 建立一个具备 K 的节点的最小堆(能够先取该数组的前 K 个元素)调整为最小堆。
let minHeap = buildHeap(nums.slice(0, k));
for (let i = k; i < nums.length; i++) {
// 将第 i 个元素与堆顶对比,若是大于堆顶元素,则说明堆顶元素不是第 K 大的值,所以将堆顶元素替换为第 i 个元素
if (minHeap[0] < nums[i]) {
minHeap[0] = nums[i];
// 替换后调整此最小堆
adjustHeap(minHeap, 0, k);
}
}
return minHeap;
}
var nums = [8, 9, 2, 15, 7, 1, 13, 35, 24];
console.log(getLargeNumber(nums, 4)); // [13,15,24,35]
复制代码
何为丑数?丑数就是只包含质因数 2, 3, 5 的正整数。
试着写出一个函数,判断传入的是否为丑数?
function isUgly (num) {
if (typeof +num !== 'number') return false
if (num < 1) return false;
// 从大往小除,先从5开始
while(num % 5 === 0) {
num /= 5;
}
while(num % 3 === 0) {
num /= 3;
}
while(num % 2 === 0) {
num /= 2;
}
return num === 1;
}
// 测试一下
isUgly(18) // true
isUgly(7) // false
复制代码