【学习笔记】算法101--数学(二)2.2篇

前言:
学习《算法101》,每天进步一点点,加油💪。
一、3的幂
    给定一个整数,写一个函数来判断它是否是 3 的幂次方。
    示例 1:

输入: 27
输出: true

    示例 2:

输入: 0
输出: false

    示例 3:

输入: 9
输出: true

    示例 4:

输入: 45
输出: false

    方法一:循环求解
    思路
    可以利用循环解决。排除特殊情况后,用待确定的数字 n,循环除以 3,看是否能被 3 整除。
    详解
1、判断特殊情况,若待定值 n 小于 1 则直接返回 false;
2、循环判断待定值 n 是否可以被 3 整除;
3、若不可以被 3 整除则返回 false,若可以则将该数字除以 3,直至循环结束;
4、其余情况则返回 true。
    代码

/**
 * @param {number} n
 * @return {boolean}
 */
const isPowerOfThree = function (n) {
  if (n < 1) {
    return false;
  }
  while (n > 1) {
    // 如果该数字不能被 3 整除,则直接输出 false
    if (n % 3 !== 0) {
      return false;
    } else {
      n = n / 3;
    }
  }
  return true;
};

    方法二:递归求解
    思路
    递归的思路类似于循环,只不过将循环体改为方法的递归调用。
    详解
1、判断特殊情况 n === 1 时,直接返回 true;
2、判断特殊情况 n <= 0 时,直接返回 false;
3、若待定值 n 可以被 3 整除,则开始递归;
4、若不满足上述条件,则返回 false。
    代码

/**
 * @param {number} n
 * @return {boolean}
 */
const isPowerOfThree = function (n) {
  // n === 1,即 3 的 0 次幂,返回 true
  if (n === 1) {
    return true;
  }
  if (n <= 0) {
    return false;
  }
  if (n % 3 === 0) {
    // 递归调用 isPowerOfThree 方法
    return isPowerOfThree(n / 3);
  }
  return false;
};

    方法三:神奇的解法
    思路
    进阶!既无循环又无递归。
    既然,要判断输入值是否为3的幂,可以巧妙的依赖它是否能被3的幂的极大值整除来作为判断依据。因此,首先要找到3的最大次幂。
    计算机中最大的整数是 2147483647,转换成 16 进制为 0x7fffffff。Math.log(x) / Math.log(y) 方法可以求出以 y 为底,x 的对数,即 y 的多少次幂的值是 x,我们称之为 maxPow。由于该值不能被整除,此处 maxPow 只需取整数部分。最后,我们可以利用 Math.pow 求出 3 的幂的极大值 maxValue,并检查该值是否能整除待确定的输入值。
    详解
1、判断特殊情况 n <= 0 时,直接返回 false;
2、求计算机允许情况下 3 的最大次幂, 记为 maxPow;
3、求 3 的 maxPow 次幂值;
4、判断 3 的 maxPow 次幂值是否能整除待定值 n 。
    代码

/**
 * @param {number} n
 * @return {boolean}
 */
const isPowerOfThree = function (n) {
  if (n <= 0) {
    return false;
  }
  // 求 3 的最大次幂
  const maxPow = parseInt((Math.log(0x7fffffff) / Math.log(3)));
  // 求 3 的 maxPow 次幂值
  const maxValue = Math.pow(3, maxPow);
  // 判断该值是否能整除待定值 n
  return (maxValue % n === 0);
};

二、Excel表序列号
    给定一个Excel表格中的列名称,返回其相应的列序号。
    示例 :

A -> 1
B -> 2
C -> 3
...
Z -> 26
AA -> 27
AB -> 28
输入: "A",
输出: 1
输入: "AB",
输出: 28

    方法一
    思路
    从末尾开始取得每一个字符对应的数 cur = c.charCodeAt() - 64( 因为 A 的caarCode为 64) 因为有 26 个字母,所以相当于 26 进制,每 26 个数则向前进一位 数字总和 sum += 当前数 进制位数 进制位数 = 26,初始化进制位数 carry = 1。
    详解
1.创建临时变量 sum 和始化进制位数 carry;
2.循环数组;
3.数字总和 sum += 当前数 * 进制位数;
4.进制位数 *= 26。

/**
 * @param {string} s
 * @return {number}
 */
const titleToNumber = function (s) {
  let sum = 0;
  let i = s.length - 1;
  let carry = 1;
  while (i >= 0) {
    const cur = s[i].charCodeAt() - 64;
    sum += cur * carry;
    carry *= 26;
    i--;
  }
  return sum;
};

    方法二
    思路
    因为有26个字母,相当于26进制转10进制。
    详解

  1. 26进制转化为10进制公式,ans = ans * 26 + num;
  2. 比如:AB = 126+2 = 28,ZY = 2626+25 = 701 。
/**
 * @param {string} s
 * @return {number}
 */
const titleToNumber = function (s) {
  const arr = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'];
  const len = s.length;
  let sum = 0;
  for (let i = 0; i < len; i++) {
    sum = (arr.indexOf(s[i]) + 1) * Math.pow(26, len - 1 - i) + sum;
  }
  return sum;
};

二、快乐局
    编写一个算法来判断一个数是不是“快乐数”。
    一个“快乐数”定义为:对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和,然后重复这个过程直到这个数变为 1,也可能是无限循环但始终变不到 1。如果可以变为 1,那么这个数就是快乐数。
    示例 :

输入: 19
输出: true
解释:
1^2 + 9^2 = 82
8^2 + 2^2 = 68
6^2 + 8^2 = 100
1^2 + 0^2 + 0^2 = 1

    方法一:尾递归
    思路
    根据示例来看,函数的执行过程是一个可递归的过程,首先,我们先写一个递归函数来模拟这个执行过程,然后按照示例 输入 19 来验证编写函数正确性, 然后 输入 任意数字(比方说 99999),这时,会发现报内存溢出的错误,那这道题就变成了如何解决堆栈溢出的问题: 首先,我们要考虑的是,为什么会内存溢出?从题目中,我们可以看到"也可能是无限循环但始终变不到 1",是"无限循环"导致内存溢出, 那我们就应该想一个方式去终结这个"死循环"。首先我们要找到这个循环的规律,怎么找?把递归内容打印(console.log)出来。这时,你会发现一个有规律的死循环。 那么,我们只要用一个变量(once)记录已经输入过的值,一旦出现第二次相同输入,就终止递归,并返回"非快乐数"的结果(false)。
    详解
1.申请一个变量来存放已经执行过函数的"输入",如果出现重复输入,则说明进入了死循环,从"示例"来看:{19:true,82:true,100:true} ;
2.将输入 19 转化为数组([1,9]);
3.将[1,9]进行平方和运算(1² + 9² = 82);
4.判断平方和的结果是不是等于 1,若果是,则为"快乐数",否,则继续执行 fn 函数;
5.直到平方和等于 1 或者判定为死循环。

const fn = function (n, once) {
  if (once[n]) {
    return false;
  }
  const list = n.toString().split('');
  let result = 0;
  once[n] = true;
  list.forEach(val => {
    result += Math.pow(parseInt(val, 10), 2);
  });
  if (result === 1) {
    return true;
  } else {
    return fn(result, once);
  }
};

/**
 *
 * @param {number} n
 * @return {boolean}
 */
const isHappy = function (n) {
  const once = {};
  return fn(n, once);
};

    方法二:尾递归
    思路
    其实和方法一类似,只不过终止循环的条件有所不同,输入 99,观察"函数一"执行时的输出可得,如果函数执行过程中出现 4,16,37,58,89,145,42,20 就不是快乐数, 为了稍微提高一下函数执行效率,你也可以简单的枚举以下特殊的快乐数,比方说:1,10,100。

// 函数一
const isHappy = function (n){
	console.log('n = ',n);
	let result = 0;
	const list = n.toString().split('');
	list.forEach(val => {
		result += Math.pow(parseInt(val,10),2);
	});
	if (result === 1) {
		return true;
	} else {
		return isHappy(result);
	}
};

    详解
1.已知非快乐数[4,16,37,58,89,145,42,20];
2.已知快乐数:1;
3.将输入(19)转化为数组([1,9]);
4.将[1,9]进行平方和的结果是不是等于1,如若是,则为“快乐数”,否,则继续执行fn函数;
5.直到平方和等于1或者判定为非快乐数。

const isHappy = function (n) {
	const unHappy = [4,16,37,58,89,145,42,20];
	if (n === 1) {
		return true;
	}
	if (unHappy.indexOf(n) > -1) {
		return false;
	}
	let result = 0;
	const list = n.toString().split('');
	list.forEach(val => {
		result += Math.pow(parseInt(val,10),2);
	});
	if(result === 1) {
		return true;
	}else {
		return isHappy(result);
	}
}

    判断优化
    可以从unHappy取任意一个数作为判断条件,可以减少indexOf函数带来的时间消耗。

const isHappy = function (n) {
	if (n === 1) {
		return true;
	}
	if (n === 4) {
		return false;
	}
	let result = 0;
	const list = n.toString().split('');
	list.forEach(val => {
		result += Math.pow(parseInt(val,10),2);
	});
	if (result === 1){
		return true;
	}else {
		return isHappy(result);
	}
}

三、阶乘后的零
    给定一个整数n,返回n!结果尾数中零的数量。
    示例 1:

输入: 3
输出: 0
解释: 3! = 6, 尾数中没有零。

    示例 2:

输入: 5
输出: 1
解释: 5! = 120, 尾数中有 1 个零.

    方法一:暴力法
    思路
1.尾数中有0必定是10的倍数;
2.尾数中有多少个0,就是整个数能有多少个因子10;
3.因子10又可以拆成25,因此,就是整个数字可以拆分成多少了25;
4.因为在因子中2的数量一定比5多,所以,实际上我们只要找到因子5的个数,就可以找到尾数中0的个数了,所以,这个问题就可以转换成因子5的个数。
    详解
1.循环1~n,找出能被5整除的数字;
2.找到能被5整除的数字,找该数字能被拆分成多少个因子5;
3.所有的个数相加就是尾数0的个数。

const trailingZeroes = function(n) {
	let count = 0;
	for(let i = 1;i <= n;i++){
		let num = i;
		if (num % 5 === 0) {
			while(num % 5 === 0 && num !== 0){
				count += 1;
				num = parseInt(num/5);
			}
		}
	}
	return count;
}

    方法二:
    思路
    整体思路和方法一基本一致,都是找因子5的个数,只是方法二是在找因子5的个数时做文章,用耗时更少的方法来找5的个数。
    详解

  1. n! 这些乘数中,每隔 5 个数,肯定会有一个数至少能拆出一个 5 因子。所以 n / 5 = 至少会出现的 5 的个数。
  2. 因为 n / 5 并不能完全算出 5 因子的个数,比如若某个数 25 = 5 * 5,分解后得到的 5 也算一个,所以能被 25 因式分解相当于会出现 2 个 5 因子,而第一步中除以 5 算个数的时候已经算了一个了,所以相当于比之前会多一个 5 因子;
  3. 依此类推,能被 25 5 = 125 因式分解的相当于比之前按 25 因式分解的时候又多出一个 5 因子。能被 125 5 = 625 因式分解的相当于比按 125 因式分解时又多出一个 5 因子。还有 625 * 5 ……
    所以n! 的结果可以拆分为多少个 5 因子呢:
    n/5 + n/25 + n /125 + n/625 + …
function trailingZeroes(n) {
	let count = 0;
	while (n > 0) {
		n = parseInt(n / 5);
		count += n;
	}
	return count;
}

参考博客: 《面试助力,算法101:JavaScript描述》 https://101.zoo.team/shu-xue/shu-xue-part-23-de-mi-excel-biao-lie-xu-hao-kuai-le-shu-he-jie-cheng-hou-de-ling

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值