外观
外观
耶温
3312字约11分钟
2024-08-22
千里之行,始于足下
每日学习一点,碎片化学习(算法或者面试题),记录下来,方便查阅。
注意点:
给定一个整数数组
nums
和一个整数目标值target
,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
我的思路:
target - nums[i]
的值/**
* @param {number[]} nums
* @param {number} target
* @return {number[]}
*/
var twoSum = function(nums, target) {
// 循环遍历数组
for (let i = 0; i < nums.length; i++) {
// 查看是否存在 target - nums[i] 的值
let num = target - nums[i]
// 这里需要注意 i+1 是因为 i 之前的已经遍历过了
if (nums.includes(num,i+1)) {
// 这里需要注意 nums.includes(num,i+1) 是因为 nums 可能存在重复的元素
return [i, nums.indexOf(num, i + 1)]
}
}
// 如果没有找到,返回空数组
return []
};
最快解法:
const twoSum = (nums, target) => {
//创建一个对象 存储出现过的数字,和对应的索引
const prevNums = {};
for (let i = 0; i < nums.length; i++) {
// 储存当前项
const curNum = nums[i];
// 获取目标差值 target - nums[i]
const targetNum = target - curNum;
// 在prevNums中获取目标的索引
const targetNumIndex = prevNums[targetNum]; // 使用对象的方式储存,获取值会更快
// 如果存在,直接返回 [目标元素的索引,当前索引]
if (targetNumIndex !== undefined) {
return [targetNumIndex, i];
} else {
// 如果不存在,说明之前没出现过目标元素
// 存入当前的元素和对应的索引
prevNums[curNum] = i;
}
}
}
// 作者:笨猪爆破组
// 链接:https://leetcode.cn/problems/two-sum/solutions/301539/qing-xi-de-bian-liang-ming-ming-bang-zhu-ji-yi-bu-/
// 来源:力扣(LeetCode)
// 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
浮点数:在 JavaScript 中,浮点数是通过 Number 类型来表示的。JavaScript 使用 IEEE 754 标准的双精度浮点数格式,这意味着它可以表示非常大的数和非常小的数,但在进行浮点数运算时可能会遇到精度问题。
// 表示可以安全使用的整数范围。
console.log(Number.MAX_SAFE_INTEGER)
console.log(Number.MIN_SAFE_INTEGER)
// 表示最大值和最小值
console.log(Number.MAX_VALUE) // 1.7976931348623157e+308
console.log(Number.MIN_VALUE) // 5e-324
console.log(0.1 + 0.2) // 输出 0.30000000000000004
解决方案:
toFixed()
方法格式化浮点数。需要注意的是toFixed()
方法会四舍五入,所以可能会有精度问题。而且转换之后是字符串类型。需要在需要时再转换为数字类型。let a = 0.1 + 0.2
console.log(a.toFixed(1)) // 输出 '0.3'
a = parseFloat(a.toFixed(1))
console.log(a) // 输出 0.3
let a = 0.1;
let b = 0.2;
let sum = (a * 10 + b * 10) / 10; // 先乘以 10 再除以 10
console.log(sum); // 输出 0.3
我们可以封装一个浮点数计算的方法,将浮点数转换为整数进行计算,然后再转换回浮点数。
(function (global) {
// 获取小数位数
function getDecimalLength(num) {
const str = num.toString();
const dotIndex = str.indexOf('.');
return dotIndex === -1 ? 0 : str.length - dotIndex - 1;
}
// 获取 所乘数
function getFactor(a, b) {
const factorA = getDecimalLength(a);
const factorB = getDecimalLength(b);
return Math.pow(10, Math.max(factorA, factorB));
}
const FloatMath = {
add: function (a, b) {
const factor = getFactor(a, b)
return (a * factor + b * factor) / factor;
},
subtract: function (a, b) {
const factor = getFactor(a, b)
return (a * factor - b * factor) / factor;
},
multiply: function (a, b) {
const factor = getFactor(a, b)
return (a * factor) * (b * factor) / (factor * factor);
},
divide: function (a, b) {
const factor = getFactor(a, b)
return (a * factor) / (b * factor);
},
};
// 将插件暴露到全局对象
global.FloatMath = FloatMath;
})(this);
测试使用
console.log(FloatMath.add(0.1, 0.2)); // 0.3
console.log(FloatMath.add(1, 0.2)); // 1.2
console.log(FloatMath.add(1, 0.233333)); // 1.233333
console.log(FloatMath.add(0.1, 0.222));// 0.322
console.log(FloatMath.subtract(0.3, 0.111));// 0.189
console.log(FloatMath.subtract(0.3, 2));// -1.7
console.log(FloatMath.multiply(0.1, 0.2));// 0.02
console.log(FloatMath.multiply(0.1, 10));// 1
console.log(FloatMath.multiply(0.5, 0.8));// 0.4
console.log(FloatMath.divide(0.3, 1));// 0.3
console.log(FloatMath.divide(0.1, 0.31));// 0.3225806451612903
console.log(FloatMath.divide(100, 1.1)); // 90.9090909090909
给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。请你将两个数相加,并以相同形式返回一个表示和的链表。你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
eg:输入:l1 = [2,4,3], l2 = [5,6,4] 输出:[7,0,8]
我的思路:
// 定义链表节点 JS本身没有提供链表的相关方法,需要自己实现
class ListNode {
constructor(val) {
this.val = val; // 节点的值
this.next = null; // 指向下一个节点的引用,初始为 null
}
}
/**
* @param {ListNode} l1
* @param {ListNode} l2
* @return {ListNode}
*/
var addTwoNumbers = function (l1, l2) {
let head = new ListNode(0); // 创建一个虚拟头节点
let current = head; // 当前节点指针
let carry = 0; // 进位
while (l1 || l2 || carry) { // 遍历两个链表 需要注意 当l1 l2 都为空时 也需要判断carry是否为0
let sum = (l1 ? l1.val : 0) + (l2 ? l2.val : 0) + carry; // 计算当前节点的值
carry = Math.floor(sum / 10); // 计算进位
current.next = new ListNode(sum % 10); // 创建新节点
current = current.next; // 移动指针
if (l1) l1 = l1.next;
if (l2) l2 = l2.next;
}
return head.next;
};
需要注意的是,在本地测试时,需要自己创建链表测试。
const l1 = new ListNode(2);
l1.next = new ListNode(4);
l1.next.next = new ListNode(3);
const l2 = new ListNode(5);
l2.next = new ListNode(6);
l2.next.next = new ListNode(4);
console.log(addTwoNumbers(l1, l2))
实现可以处理带参数的函数,并且在第一次调用时会执行函数,后续调用将返回第一次调用的结果。
定义一个一次性函数插件。
(function (global) {
// 一次性函数插件
function once(fn) {
let executed = false; // 标记函数是否已执行
let result; // 存储函数的返回值
return function (...args) {
if (!executed) {
executed = true; // 设置标记为已执行
result = fn.apply(this, args); // 执行函数并保存结果
}
return result; // 返回函数的结果
};
}
// 将插件暴露到全局对象
if (typeof module === 'object' && typeof module.exports === 'object') {
// CommonJS 支持
module.exports = once;
} else {
// 浏览器环境
global.once = once;
}
})(this);
引入使用。
<script src="./index.js"></script>
<script>
const sayHello = once(function(name) {
console.log(`Hello, ${name}!`);
return `Hello, ${name}!`;
});
sayHello('Alice'); // 输出: Hello, Alice!
sayHello('Bob'); // 不会输出任何内容
</script>
给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串的长度。 eg:输入: s = "abcabcbb" 输出: 3 解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
输入: s = "bbbbb" 输出: 1 解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。
输入: s = "pwwkew" 输出: 3 解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
我的解法:
var lengthOfLongestSubstring = function (s) {
let str = '', num = 0; // str为当前无重复字符的子串,num为最长子串的长度
for (let i = 0; i < s.length; i++) { // 遍历字符串
let index = str.indexOf(s[i]); // 判断当前字符是否在子串中
if (index !== -1) str = str.slice(index + 1); // 如果在子串中,则截取子串中该字符后面的部分
str += s[i]; // 将当前字符添加到子串中
num = str.length >= num ? str.length : num; // 更新最长子串的长度
}
return num;
};
给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。
输入:nums1 = [1,3], nums2 = [2] 输出:2.00000 解释:合并数组 = [1,2,3] ,中位数 2
输入:nums1 = [1,2], nums2 = [3,4] 输出:2.50000 解释:合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5
我的解法:
var findMedianSortedArrays = function (nums1, nums2) {
var arr = nums1.concat(nums2); // 合并两个数组
arr.sort((a, b) => a - b); // 对数组进行排序
var len = arr.length; // 获取数组的长度
if (len % 2 === 0) { // 如果数组长度为偶数
return (arr[len / 2] + arr[len / 2 - 1]) / 2;// 返回中间两个数的平均值
} else { // 如果数组长度为奇数
return arr[(len - 1) / 2]; // 返回中间的数
}
};
二分法查找:
var findMedianSortedArrays = function (nums1, nums2) {
// 确保 nums1 是较短的数组
if (nums1.length > nums2.length) {
[nums1, nums2] = [nums2, nums1]
}
// 获取数组的长度
let m = nums1.length
let n = nums2.length
// 定义中位数的位置
let medianIndex = Math.floor((m + n + 1) / 2)
// 定义左右指针
let left = 0
let right = m
while (left <= right){ // 二分查找
// 计算 num1 中间位置
let midnum1 = Math.floor((left + right) / 2)
// 计算 num2 中间位置 根据中位数的位置计算 num2 中间位置
// 中位数的位置 = (m+n+1)/2
let midnum2 = medianIndex - midnum1
// 定义num1 和 num2 中间 左右边界数据
let left1 = midnum1 === 0? -Infinity : nums1[midnum1 - 1]
let right1 = midnum1 === m? Infinity : nums1[midnum1]
let left2 = midnum2 === 0? -Infinity : nums2[midnum2 - 1]
let right2 = midnum2 === n? Infinity : nums2[midnum2]
// 比较 num1 和 num2 中间 左右边界数据
// 根据比较结果 移动左右指针 继续二分查找 或者 返回中位数
if(left1 > right2){
// num1 中间位置的左边数据大于 num2 中间位置的右边数据
// 说明 num1 中间位置的左边数据过大 移动右指针
right = midnum1 - 1
}else if(left2 > right1){
// num1 中间位置的右边数据小于 num2 中间位置的左边数据
// 说明 num1 中间位置的右边数据过小 移动左指针
left = midnum1 + 1
}else{
// num1 和 num2 中间 左右边界数据符合要求
// 计算中位数
if((m + n) % 2 === 1){
// 如果是奇数个 中位数就是 num1 和 num2 中间 左边界数据中较大的那个
return Math.max(left1, left2)
}else{
// 如果是偶数个 中位数就是 左边界的最大值和右边界的最小值 的平均值
return (Math.max(left1, left2) + Math.min(right1, right2)) / 2
}
}
}
}
给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
输入: nums = [-1,0,3,5,9,12], target = 9 输出: 4 解释: 9 出现在 nums 中并且下标为 4
var findTargetIndexArrays = function (nums, target) {
let right = nums.length - 1 // 右指针
let left = 0 // 左指针
while (left <= right) { // 左指针小于右指针
let mid = Math.floor((right + left) / 2) // 中间指针
if (nums[mid] == target) { // 找到目标
return mid
} else if (nums[mid] > target) { // 目标在左半部分
right = mid - 1 // 右指针移动到中间指针左边
} else {
left = mid + 1 // 左指针移动到中间指针右边
}
}
return -1
}
ESM和CommonJS是两种不同的模块化规范,它们之间的主要区别在于它们如何处理模块的导入和导出。
CommonJS是Node.js的模块化规范,它使用require和module.exports来导入和导出模块。CommonJS模块是同步加载的,这意味着它们在执行时会阻塞代码的执行,直到模块加载完成。
ESM是ECMAScript 6引入的模块化规范,它使用import和export来导入和导出模块。ESM模块是异步加载的,这意味着它们不会阻塞代码的执行,可以在模块加载完成之前继续执行其他代码。
提示
关于 ESM 与 CommonJS 模块的详细内容可以查看:JavaScript-ESM与CommonJS)
事件循环(Event Loop)是 JavaScript 中处理异步操作的核心机制。它使得 JavaScript 能够在单线程环境中执行非阻塞的代码,处理事件和执行回调函数。
提示
关于 Event Loop 的详细内容可以查看:JavaScript-Event Loop)
给你一个字符串 s,找到 s 中最长的回文子串。
输入:s = "babad" 输出:"bab" 解释:"aba" 同样是符合题意的答案。
输入:s = "cbbd" 输出:"bb" 思路:
我的解法:
var longestPalindrome = function (s) {
let maxStr = s[0] // 最长回文子串 默认值
for (let i = 0; i < s.length; i++) {
// 奇数回文子串
isPlaindrome(i, i + 1)
isPlaindrome(i, i)
}
function isPlaindrome(l, r) {
// 回文子串
while (s[l] === s[r] && l >= 0 && r < s.length) {
l-- // 左指针
r++ // 右指针
}
let str = s.slice(l + 1, r) // 截取子串
if (str.length > maxStr.length) { // 判断是否是最大回文子串
maxStr = str
}
}
return maxStr
};