普通视图

发现新文章,点击刷新页面。
昨天 — 2026年2月26日首页

程序员都该掌握的“质因数分解”

作者 JYeontu
2026年2月26日 11:55

说在前面

还记得小学数学课上的“质因数分解”吗?这个看似基础的概念,实际上是现代数论的基石。在草稿纸上进行 质因数分解 大家应该都会,那怎么通过代码来实现呢?它又能解决什么问题?

什么是质因数分解?

概念

质因数分解 = 把一个合数,拆成「若干个质数相乘」的形式

例子

12 = 2 × 2 × 3
  • 2、3 都是质数(只能被 1 和自己整除)
  • 12 是合数(能继续拆)
  • 拆到不能再拆,只剩质数,就叫质因数分解

定理

数学里有一条超级重要的定理:

任何一个大于 1 的整数,只有唯一一种质因数分解方式

怎么做质因数分解?

最实用、最好用的方法:短除法

步骤

  • 1.从最小的质数 2 开始试
  • 2.能除就除,除到不能除为止
  • 3.再换下一个质数 3、5、7、11…
  • 4.直到最后结果是 1

例子

180 进行质因数分解

180 ÷ 2 = 90
90  ÷ 2 = 45
45  ÷ 3 = 15
15  ÷ 3 = 5
5   ÷ 5 = 1

所以: 180 = 2² × 3² × 5¹

质因数分解有什么用?

1. 将“乘除”降维成“加减”

在编程中进行算数乘除运算很容易会遇到两个问题:

  • 数字溢出:几个数一相乘,结果可能超出计算机能表示的最大整数范围

  • 精度丢失:一旦引入除法,就可能出现小数,而浮点数的存储和比较天生存在精度误差
1 / 6 * 5 * 5 * 2 * 3

上面这个式子我们快速过一遍不难看出最后的结果应该是 25,但是电脑算出来的结果却是 24.999999999999996

质因子分解 便可以比较优雅的避免这两个问题

例子

我们可以把每个数字“升维”,用一个指数向量来表示它:

12 = 2² × 3¹ × 5⁰ => 向量 [2, 1, 0]
10 = 2¹ × 3⁰ × 5¹ => 向量 [1, 0, 1]
  • 乘法 → 向量加法 12 × 10 = 120 对应的向量运算是:[2, 1, 0] + [1, 0, 1] = [3, 1, 1]

    验证一下:120 = 8 × 3 × 5 = 2³ × 3¹ × 5¹。向量正是 [3, 1, 1]

  • 除法 → 向量减法 120 / 10 = 12 对应的向量运算是:[3, 1, 1] - [1, 0, 1] = [2, 1, 0]

    结果 [2, 1, 0] 正是 12 的向量表示

通过质因数分解,我们可以将复杂的、易出错的乘除法,转换成了简单、精确的整数加减法。

2.最大公因数、最小公倍数

辗转相除法 求最大公因数大家都知道吧,那质因数分解 也能求最大公因数你们知道吗?

比如求 1830GCDLCM

分解

18 = 2¹ × 3²
30 = 2¹ × 3¹ × 5¹

求最大公因数 (GCD)

取每个公共质因子的最低次幂,然后相乘

  • 公共质因子是 23
  • 2 的最低次幂是 min(1, 1) = 1
  • 3 的最低次幂是 min(2, 1) = 1
  • GCD = 2¹ × 3¹ = 6

求最小公倍数 (LCM)

取所有出现过的质因子的最高次幂,然后相乘

  • 所有质因子是 2, 3, 5
  • 2 的最高次幂是 max(1, 1) = 1
  • 3 的最高次幂是 max(2, 1) = 2
  • 5 的最高次幂是 max(0, 1) = 1
  • LCM = 2¹ × 3² × 5¹ = 90

3.现代密码学的基石

我们每天都在使用的 HTTPS、网上银行、数字签名,其安全性的根基,都与质因数分解的“不对称性”有关。

RSA 加密算法。其核心思想可以通俗地理解为:

给你两个巨大的质数 pq,让你把它们乘起来得到 N,这在计算上非常容易。 但是,反过来,只告诉你乘积 N,让你找出原始的 pq 是什么,这在计算上极其困难。

代码实现

说了这么多,那我们如何用代码来实现质因数分解呢?其实非常简单:

/**
 * 对一个正整数进行质因数分解
 * @param {number} n - 需要分解的正整数
 * @returns {Map<number, number>} - 返回一个 Map,键是质因子,值是其指数
 */
function primeFactorize(n) {
  if (n <= 1) {
    return new Map();
  }
  const factors = new Map();
  // 不断除以2,处理所有偶数因子
  while (n % 2 === 0) {
    factors.set(2, (factors.get(2) || 0) + 1);
    n /= 2;
  }
  // 从3开始遍历奇数,直到 n 的平方根
  // 如果 n 有一个大于其平方根的因子,必然会有一个小于其平方根的因子
  for (let i = 3; i * i <= n; i += 2) {
    while (n % i === 0) {
      factors.set(i, (factors.get(i) || 0) + 1);
      n /= i;
    }
  }
  // 如果最后 n 还大于1,那么 n 本身也是一个质数
  if (n > 1) {
    factors.set(n, (factors.get(n) || 0) + 1);
  }
  return factors;
}
console.log(primeFactorize(120)); 
// 输出: Map { 2 => 3, 3 => 1, 5 => 1 }
console.log(primeFactorize(999));
// 输出: Map {3 => 3, 37 => 1}

公众号

关注公众号『 前端也能这么有趣 』,获取更多有趣内容~

发送 加群 还能加入前端交流群,和大家一起讨论技术、分享经验,偶尔也能摸鱼聊天~

说在后面

🎉 这里是 JYeontu,现在是一名前端工程师,有空会刷刷算法题,平时喜欢打羽毛球 🏸 ,平时也喜欢写些东西,既为自己记录 📋,也希望可以对大家有那么一丢丢的帮助,写的不好望多多谅解 🙇,写错的地方望指出,定会认真改进 😊,偶尔也会在自己的公众号『前端也能这么有趣』发一些比较有趣的文章,有兴趣的也可以关注下。在此谢谢大家的支持,我们下文再见 🙌。

昨天以前首页

实现一个超萌的柯基交互输入框

作者 JYeontu
2026年2月24日 10:12

说在前面

看腻了千篇一律的常规输入框,是时候给输入框加点趣味性了,今天我们来实现一个会和用户互动的柯基输入框:鼠标移动时柯基会扭头盯着你看,点击页面会眨眼,输入文字时还会弹动身体、弹出气泡反馈。

  • 视线跟随:鼠标在页面移动,柯基的瞳孔会精准跟着鼠标转,聚焦输入框时还会盯着输入框看;
  • 趣味眨眼:点击页面任意位置,柯基会俏皮眨眼,动画超自然;
  • 打字反馈:在输入框打字时,柯基会轻轻弹动身体,还会根据输入字数弹出不同的气泡文案(比如 “在写啥呢...”“写了好多呀~”);
  • 3D 视角:鼠标移动时柯基整体会有轻微 3D 旋转,视觉上更立体生动。

在线体验

在线预览

jyeontu.xyz/htmlDemo/柯基…

codePen

codepen.io/yongtaozhen…

码上掘金

code.juejin.cn/pen/7608923…

关键代码实现

1.用 SVG 画一只的柯基

前面有一篇文章介绍了怎么将图片转为 SVG,这里可以直接在找一张柯基的图片来将其转为 SVG,转换工具地址如下:

jyeontu.xyz/htmlDemo/图片…

对转换工具实现感兴趣的同学可以查看这篇文章:mp.weixin.qq.com/s/c6qVOu_hT…

2.CSS 实现基础动画

转换成 SVG 之后我们需要将柯基的眼睛单独设为带 ID 的元素,方便做眨眼动画

  • 眨眼动画:用@keyframes做垂直缩放(scaleY),模拟闭眼效果;
@keyframes wink-animation {
  0%, 100% { transform: scaleY(1); } /* 正常状态 */
  50% { transform: scaleY(0.2); } /* 闭眼状态:垂直缩放到20% */
}
.wink { animation: wink-animation 0.3s ease-in-out; }
  • 打字弹动:给柯基容器加弹跳动画,输入文字时触发,增强互动感;
@keyframes bounce-typing {
  0%, 100% { transform: translateY(0) scale(1); }
  30% { transform: translateY(-6px) scale(1.05); } /* 向上弹+轻微放大 */
  60% { transform: translateY(2px) scale(0.98); } /* 轻微回落+缩小 */
}
.dog-container.bounce { animation: bounce-typing 0.4s ease-out; }
  • 气泡提示:用绝对定位做气泡样式,通过opacity控制显隐,加小三角伪元素模拟气泡尾巴。
.thought-bubble {
  position: absolute;
  opacity: 0; /* 默认隐藏 */
  transform: translate(-50%, -100%) scale(0.8); /* 初始缩小+位移 */
  transition: opacity 0.2s, transform 0.2s; /* 过渡动画 */
}
.thought-bubble.show {
  opacity: 1; /* 显示 */
  transform: translate(-50%, -100%) scale(1); /* 恢复正常大小 */
}
.thought-bubble::after {
  content: "";
  position: absolute;
  left: 50%;
  bottom: -8px;
  margin-left: -6px;
  border: 6px solid transparent;
  border-top-color: #fff; /* 气泡尾巴的核心:透明边框+顶部白色 */
  border-bottom: none;
}

3.JS 实现交互逻辑

(1)3D 视角旋转(柯基扭头)

// 计算鼠标偏离页面中心的比例,转换为旋转角度
const centerX = window.innerWidth / 2;
const centerY = window.innerHeight / 2;
// 旋转角度:鼠标在中心上方→rotateX为正(柯基低头),右侧→rotateY为正(柯基左转)
const rotateX = ((mouseY - centerY) / centerY) * -10;
const rotateY = ((mouseX - centerX) / centerX) * 10;
// 应用3D旋转
dogContainer.style.transform = `rotateX(${rotateX}deg) rotateY(${rotateY}deg)`;

(2)确定瞳孔的目标位置

// 确定瞳孔要看向的目标点
let pointInSvg;
if (lookAtInput) {
  // 输入框聚焦时,看向预设的输入框位置
  pointInSvg = lookAtInputPoint;
} else {
  // 否则,把鼠标的屏幕坐标转换为SVG内部坐标
  const svgPoint = dogSvg.createSVGPoint();
  svgPoint.x = mouseX;
  svgPoint.y = mouseY;
  // 坐标转换:屏幕坐标 → SVG内部坐标(解决SVG嵌套/缩放导致的错位)
  pointInSvg = svgPoint.matrixTransform(dogSvg.getScreenCTM().inverse());
}

(3)计算瞳孔的目标偏移(以左眼为例)

// 计算左眼瞳孔的目标位置
// 目标点与眼球中心的偏移量
const deltaLeftX = pointInSvg.x - leftEyeCenter.x;
const deltaLeftY = pointInSvg.y - leftEyeCenter.y;
// 计算角度(Math.atan2:根据y/x算弧度,范围-π到π)
const angleLeft = Math.atan2(deltaLeftY, deltaLeftX);
// 计算距离(勾股定理)
const distanceLeft = Math.sqrt(deltaLeftX ** 2 + deltaLeftY ** 2);
// 目标位置:眼球中心 + 沿角度方向的偏移(不超过maxRadius)
const targetLeftX = leftEyeCenter.x + Math.cos(angleLeft) * Math.min(distanceLeft, maxRadius);
const targetLeftY = leftEyeCenter.y + Math.sin(angleLeft) * Math.min(distanceLeft, maxRadius);
  • Math.atan2(dy, dx) :核心函数,返回从 x 轴到点 (dx, dy) 的弧度,用于确定瞳孔的移动方向;
  • Math.min(distanceLeft, maxRadius) :限制偏移距离,避免瞳孔移出眼眶;
  • Math.cos(angle)/Math.sin(angle) :将弧度转换为 x/y 方向的偏移量。

(4)缓动更新瞳孔位置

// 缓动更新瞳孔位置(避免瞬间移动,更自然)
currentPupilPos.left.x += (targetLeftX - currentPupilPos.left.x) * smoothingFactor;
currentPupilPos.left.y += (targetLeftY - currentPupilPos.left.y) * smoothingFactor;
// 右眼同理(代码略)

// 应用位置到SVG元素
leftPupil.setAttribute("transform", `translate(${currentPupilPos.left.x}, ${currentPupilPos.left.y})`);
rightPupil.setAttribute("transform", `translate(${currentPupilPos.right.x}, ${currentPupilPos.right.y})`);

// 循环执行动画(requestAnimationFrame:浏览器刷新频率同步,流畅不卡顿)
requestAnimationFrame(animate);
}
// 启动动画循环
animate();
  • 缓动公式:当前位置 += (目标位置 - 当前位置) × 平滑因子

    每次只移动 “剩余距离的一小部分”,比如剩余 10px,平滑因子 0.08 就移动 0.8px,距离越近移动越慢,最终无限接近目标位置,视觉上就是 “顺滑跟随”;

  • requestAnimationFrame:替代setInterval,让动画和浏览器刷新频率(60 帧 / 秒)同步,避免卡顿。

(5)输入框交互

监听输入框的 focus/blur/input 事件,实现 “聚焦看输入框、打字弹动 + 气泡提示”:

// 聚焦输入框:让柯基看向输入框
inputEl.addEventListener("focus", () => {
  lookAtInput = true;
});
// 失焦:恢复看鼠标,隐藏气泡
inputEl.addEventListener("blur", () => {
  lookAtInput = false;
  thoughtBubble.classList.remove("show");
  thoughtBubble.textContent = "";
});
// 输入文字:弹动+气泡提示
inputEl.addEventListener("input", () => {
  const len = inputEl.value.length;
  if (len > 0) {
    // 根据字数切换文案
    thoughtBubble.textContent = len >= 10 ? "写了好多呀~" : len >= 5 ? "汪!写得好~" : "在写啥呢...";
    thoughtBubble.classList.add("show");
    // 气泡1.8秒后自动隐藏
    clearTimeout(bubbleHideTimer);
    bubbleHideTimer = setTimeout(() => {
      thoughtBubble.classList.remove("show");
    }, 1800);
  } else {
    thoughtBubble.classList.remove("show");
  }
  // 弹动动画(防抖:避免快速输入时重复触发)
  clearTimeout(bounceDebounce);
  dogContainer.classList.add("bounce");
  bounceDebounce = setTimeout(() => {
    dogContainer.classList.remove("bounce");
  }, 420); // 动画时长400ms,留20ms余量
});

(6)点击眨眼

window.addEventListener("click", () => {
  // 避免重复触发(比如快速点击)
  if (leftEyeGroup.classList.contains("wink")) return;
  // 添加眨眼类
  leftEyeGroup.classList.add("wink");
  rightEyeGroup.classList.add("wink");
  // 300ms后移除(和动画时长一致)
  setTimeout(() => {
    leftEyeGroup.classList.remove("wink");
    rightEyeGroup.classList.remove("wink");
  }, 300);
});

源码地址

gitee

gitee.com/zheng_yongt…

github

github.com/yongtaozhen…


  • 🌟 觉得有帮助的可以点个 star~
  • 🖊 有什么问题或错误可以指出,欢迎 pr~
  • 📬 有什么想要实现的功能或想法可以联系我~

公众号

关注公众号『 前端也能这么有趣 』,获取更多有趣内容~

发送 加群 还能加入前端交流群,和大家一起讨论技术、分享经验,偶尔也能摸鱼聊天~

说在后面

🎉 这里是 JYeontu,现在是一名前端工程师,有空会刷刷算法题,平时喜欢打羽毛球 🏸 ,平时也喜欢写些东西,既为自己记录 📋,也希望可以对大家有那么一丢丢的帮助,写的不好望多多谅解 🙇,写错的地方望指出,定会认真改进 😊,偶尔也会在自己的公众号『前端也能这么有趣』发一些比较有趣的文章,有兴趣的也可以关注下。在此谢谢大家的支持,我们下文再见 🙌。

❌
❌