链表
17. 环形链表 II
var detectCycle = function(head) {
let fast = head, slow = head;
do{
//检查是否有环 + 寻找相遇点 encounter point
if(!fast || !fast.next) return null;
fast = fast.next.next
slow = slow.next
}while(fast != slow)
fast = head;
//寻找入口 entrance
//为什么这能寻找入口,请看下图
while(fast != slow){
fast = fast.next
slow = slow.next
}
return fast;
};
58. 环形链表
var hasCycle = function(head) {
let fast = head;
let slow = head;
while (fast &&fast.next&& slow){
fast = fast.next.next
slow = slow.next
if(fast === slow){
return true;
}
}
return false
};
25. 删除链表的倒数第 N 个结点
Fast 先走k次,再一起走的的时候fast走没了 就是slow要删除节点的时候;返回值为head
var removeNthFromEnd = function(head, n) {
let fast = head;
let slow = head;
for(let i=0;i<n;i++){
fast = fast.next
}
// 如果fast为null 说明删除第一个 直接取第二个即可
if(fast == null)return head.next;
while (fast&&fast.next!=null){
fast = fast.next
slow = slow.next
}
// .next时候才能跳过 要不然相当改的是变量值
slow.next = slow.next.next
return head;
};
88.两两交换链表中的节点
给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。
你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
function swapPairs(head) {
// 创建一个哨兵节点,它的next指针指向链表的头节点
let dummy = new ListNode(0);
dummy.next = head;
// 当前节点指针初始化为哨兵节点
let current = dummy;
while (current.next !== null && current.next.next !== null) {
// 初始化两个待交换节点
let firstNode = current.next;
let secondNode = current.next.next;
// 交换操作
firstNode.next = secondNode.next;
secondNode.next = firstNode;
current.next = secondNode;
// 移动当前节点指针至下一对待交换节点的前驱节点
current = firstNode;
}
// 返回哨兵节点的下一个节点,即交换后的头节点
return dummy.next;
}
19. 反转链表
var reverseList = function(head) {
let pre = null // 反转链表
let current = head
while (current){
// 临时存当前节点的下一个
const tempCurrent = current.next
// 当前的 下一个指向上一个
current.next = pre
// 反转拼接
pre = current
// 当前继续循环
current = tempCurrent
}
return pre
};
30. K 个一组翻转链表
给你链表的头节点
head
,每k
**个节点一组进行翻转,请你返回修改后的链表。
var reverseKGroup = function(head, k) {
let currentNode = head;
let count = 0;
// 计数检查是否有足够的节点进行下一次翻转
while (currentNode != null && count != k) {
currentNode = currentNode.next;
count++;
}
// 如果有足够的节点进行翻转
if (count == k) {
// 翻转这 k 个节点
currentNode = reverseKGroup(currentNode, k); // 翻转剩余部分,并将结果连接到当前部分
while (count-- > 0) { // 翻转当前 k 个节点
let temp = head.next; // 临时保存下一个节点
head.next = currentNode; // 将当前节点的 next 指向翻转的剩余部分
currentNode = head; // 将 currentNode 移动到当前节点
head = temp; // 移动 head 到下一个节点
}
head = currentNode;
}
return head;
};
24. 合并两个有序链表
var mergeTwoLists = function(list1, list2) {
let temp = new ListNode(0);
// 存着这个链表节点 一直做赋值用
let result = temp
while (list1!==null&&list2!=null){
if(list1.val<=list2.val){
// 赋值
result.next = list1
// 跳转
list1 = list1.next
}else{
result.next = list2
list2 = list2.next
}
// 跳转
result = result.next
}
// 没遍历完的直接拼接
result.next = list1||list2
return temp.next;
};
61. 相交链表
var getIntersectionNode = function(headA, headB) {
let ap = headA,
bp = headB;
while (ap !== bp) {
if (ap === null) {
ap = headB;
} else {
ap = ap.next;
}
if (bp === null) {
bp = headA;
} else {
bp = bp.next;
}
}
return ap;
};
77.两个链表的第一个公共节点
var getIntersectionNode = function(headA, headB) {
if(!headA||!headB) return null;
// 走完自己的 在另一方一步一步走 相当于第二次循环必会相遇
var a=headA,b=headB;
while(a!=b){
a=a?a.next:headB;
b=b?b.next:headA;
}
return a;
};
62. 合并 K 个升序链表
var mergeKLists = function (lists) {
const list = [];
for (let i = 0; i < lists.length; i++) {
let node = lists[i];
while (node) {
list.push(node.val);
node = node.next;
}
}
list.sort((a, b) => a - b);
const res = new ListNode();
let now = res;
// console.log(list)
for (let i = 0; i < list.length; i++) {
now.next = new ListNode(list[i]);
now = now.next;
}
return res.next;
};
// 分治
function mergeTwoLists(l1, l2) {
if (!l1) return l2;
if (!l2) return l1;
if (l1.val < l2.val) {
l1.next = mergeTwoLists(l1.next, l2);
return l1;
} else {
l2.next = mergeTwoLists(l1, l2.next);
return l2;
}
}
function mergeKLists(lists) {
if (lists.length === 0) return null;
if (lists.length === 1) return lists[0];
const mid = Math.floor(lists.length / 2);
const l1 = mergeKLists(lists.slice(0, mid));
const l2 = mergeKLists(lists.slice(mid));
return mergeTwoLists(l1, l2);
}
78.复杂链表的复制
var copyRandomList = function(head) {
if (!head) {
return null;
}
const map = new Map();
let node = head; // 当前节点
const newHead = new Node(node.val);
let newNode = newHead; // 当前节点的copy
map.set(node, newNode);
while (node.next) {
newNode.next = new Node(node.next.val);
node = node.next;
newNode = newNode.next;
map.set(node, newNode);
}
newNode = newHead;
node = head;
while (newNode) {
newNode.random = map.get(node.random);
newNode = newNode.next;
node = node.next;
}
return newHead;
};
树
6. 路径总和 === target
参数当前节点和累加和; 没有子节点了到达终点 判断target。哪个有子节点继续累加递归
var hasPathSum = function(root, targetSum) {
if(!root) return false;
let rt = false
recurse(root,root.val)
return rt
// 参数是某个节点 到某个节点的和; 注意边界
function recurse(root, sum) {
// 到达叶子结点 且总和相等
if(!root.left && !root.right && (targetSum == sum)){
rt = true
}
if(root.left){
recurse(root.left,sum+root.left.val)
}
if(root.right){
recurse(root.right,sum+root.right.val)
}
}
};
10. 求根节点到叶节点数字之和
var sumNumbers = function(root) {
// 参数:当前节点 当前节点前的和
function defs (node, sumNum){
// 如果当前节点没有 返回0
if(node === null){
return 0 // ****
}
// 当前节点有值 计算和
sumNum = 10*sumNum + node.val
// 是否为叶子节点 叶子节点直接返回和
if(node.left === null && node.right===null){
return sumNum
}
// 不是叶子节点继续调用这个方法
return defs(node.left, sumNum) + defs(node.right, sumNum)
}
return defs(root,0)
};
14. 二叉树的层序遍历
主要是queue 存储每一层的节点;循环的时候 先拿queue长度再循环;循环时候取值 push queue
var levelOrder = function(root) {
if(root === null) return []
let result = [];
// queue存储每一行 节点
let queue = [root]
while (queue.length){
// 一层长度
const leveSize = queue.length
// 一层的值
const tempArr = []
for(let i=0;i<leveSize;i++){
// 每次循环首部删除
const current = queue.shift()
tempArr.push(current.val)
current.left && queue.push(current.left)
current.right && queue.push(current.right)
}
result.push(tempArr)
}
return result
};
26.二叉树的最近公共祖先
var lowestCommonAncestor = function(root, p, q) {
// 没值 或者是目标值 直接返回根
if(!root || root === p || root === q){
return root
}
const left = lowestCommonAncestor(root.left, p, q)
const right = lowestCommonAncestor(root.right, p, q)
// 左子树右子树都有 root就是公共祖先
if(left&&right){
return root
}
// 只有一个有值 就是left或者right
return left?left:right
};
28. 两棵二叉搜索树中的所有元素
给你
root1
和root2
这两棵二叉搜索树。请你返回一个列表,其中包含 两棵树 中的所有整数并按 升序 排序根据二叉树的特点鲜中序获取到两棵树的值再 两棵树对比排序;排完后没有比较到的直接追加在最后
var getAllElements = function(root1, root2) {
let arr1=[],arr2=[], result=[]
// 中序遍历两棵树 再对比值
function getValue(root, arr){
if(!root)return;
root.left&&getValue(root.left, arr)
arr.push(root.val)
root.right&&getValue(root.right, arr)
}
getValue(root1,arr1)
getValue(root2,arr2)
while (arr1.length&&arr2.length){
if(arr1[0]<arr2[0]){
result.push(arr1.shift())
}else {
result.push(arr2.shift())
}
}
// 没比完的追加在后边
return [...result, ...arr1,...arr2]
};
49. 二叉搜索树中第K小的元素
// 二叉搜索树的一个关键特性是,对于任何给定的节点,其左子树的所有节点值都小于该节点,而其右子树的所有节点值都大于该节点。
var kthSmallest = function(root, k) {
let result =[]
function getVal(node){
if(node ==null || result.length>=k)return;
getVal(node.left)
result.push(node.val)
getVal(node.right)
}
getVal(root)
return result[k-1]
};
43. 二叉树的最大深度
var maxDepth = function(root) {
if(root === null){
return 0
}
// 每一层最大值 加 1
return Math.max(maxDepth(root.left),maxDepth(root.right)) + 1
};
53. 对称二叉树
var isSymmetric = function(root) {
// 判断两个树是否是镜像对称的
function isMirror(left, right) {
// 都为null 到 两边的叶子节点 返回true
if (left === null && right === null) return true;
// 有一个为null 另一不为null 证明不是对称的
if (left === null || right === null) return false;
// 左子树的右子树 和 右子树的左子树比较 都相同的为对称二叉树
return (left.val === right.val) && isMirror(left.right, right.left) && isMirror(left.left, right.right);
}
// 判断一棵树是否是对称的
return isMirror(root, root);
};
92.二叉树的中序遍历
// 中序遍历 左中右
function inorderTraversal(root) {
const result = [];
function traverse(node) {
if (node === null) return;
traverse(node.left); // 遍历左子树
result.push(node.val); // 访问根节点
traverse(node.right); // 遍历右子树
}
traverse(root);
return result;
}
// 迭代法
function inorderTraversalIterative(root) {
const result = [];
const stack = [];
let current = root;
while (current !== null || stack.length > 0) {
// 先尽可能地访问左子树
while (current !== null) {
stack.push(current);
current = current.left;
}
// 当左子树访问完毕,回溯到最近的父节点
current = stack.pop();
result.push(current.val); // 访问根节点
// 转向右子树
current = current.right;
}
return result;
}
// 前序遍历 中左右
function preorderTraversal(root) {
const result = [];
function traverse(node) {
if (node === null) return;
result.push(node.val); // 访问根节点
traverse(node.left); // 遍历左子树
traverse(node.right); // 遍历右子树
}
traverse(root);
return result;
}
// 迭代法
function preorderTraversalIterative(root) {
if (root === null) return [];
const result = [];
const stack = [root];
while (stack.length > 0) {
const node = stack.pop();
result.push(node.val); // 访问根节点
// 先右后左入栈,保证左子树先处理
if (node.right !== null) stack.push(node.right);
if (node.left !== null) stack.push(node.left);
}
return result;
}
// 后续遍历 左右中
function postorderTraversal(root) {
const result = [];
function traverse(node) {
if (node === null) return;
traverse(node.left); // 遍历左子树
traverse(node.right); // 遍历右子树
result.push(node.val); // 访问根节点
}
traverse(root);
return result;
}
// 迭代法
function postorderTraversalIterative(root) {
if (root === null) return [];
const result = [];
const stack = [root];
let prev = null;
while (stack.length > 0) {
const curr = stack[stack.length - 1];
if (!prev || prev.left === curr || prev.right === curr) {
if (curr.left) {
stack.push(curr.left);
} else if (curr.right) {
stack.push(curr.right);
}
} else if (curr.left === prev) {
if (curr.right) {
stack.push(curr.right);
}
} else {
result.push(curr.val);
stack.pop();
}
prev = curr;
}
return result;
}
97.翻转二叉树
// 左右节点交换
var invertTree = function(root) {
resetTree(root)
function resetTree(node) {
if(!root){return root}
let left = node.left
let right = node.right
if(left||right){
node.right = left
node.left = right
// 交换后继续递归 左右子树
left&&resetTree(left)
right&&resetTree(right)
}
}
return root;
};
数组
2. 合并两个有序数组
相当于nums1长度变大;因为是有序的两个数组 谁大谁往后放;比较完最小长度 如果nums2还有剩余插到中间的空里
var merge = function(nums1, m, nums2, n) {
let k = m + n -1
m = m-1
n = n-1
while (m>=0 && n>=0){
if(nums2[n]>nums1[m]){
nums1[k] = nums2[n]
n--;
}else{
nums1[k] = nums1[m];
m--
}
k--;
}
while (n>=0){
nums1[k] = nums2[n]
k--;
n--;
}
return nums1;
};
3. 两数之和
巧用map存储已经遍历的值
var twoSum = function(nums, target) {
let tempMap = new Map()
for(let i=0;i<nums.length;i++){
if(tempMap.has(target - nums[i])){
return [tempMap.get(target - nums[i]), i]
}else{
tempMap.set(nums[i],i)
}
}
return false;
};
13. 三数之和
var threeSum = function(nums) {
// 先排序
nums.sort((a, b) => a - b);
const result = [];
// nums.length - 2
for (let i = 0; i < nums.length - 2; i++) {
// 重复的跳过
if (i > 0 && nums[i] == nums[i - 1]) continue;
let left = i + 1;
let right = nums.length - 1;
while (left < right) {
const sum = nums[i] + nums[left] + nums[right];
if (sum === 0) {
// 找到符合值存储 然后左右都跳过重复的 继续下一次循环
result.push([nums[i], nums[left], nums[right]]);
while (nums[left] === nums[left + 1]) left++;
while (nums[right - 1] === nums[right]) right--;
left++;
right--;
} else if (sum < 0) {
// 小于目标值左+
left++;
} else {
// 大于目标值右-
right--;
}
}
}
return result;
};
5. 最大子数组和
两个值分别存储累加值和最大值;累加后和当前值比 如果不大于累加值 则使用当前值
var maxSubArray = function(nums) {
let currentMax = nums[0]//存储累加值
let globalMax = nums[0]// 存储最大值
for(let i=1;i<nums.length;i++){
// 累加和 + 当前值 如果大则取当前值的和 否则用当前值
currentMax = Math.max(currentMax+nums[i], nums[i])
// 累加值与最大值比较
globalMax = Math.max(currentMax, globalMax)
}
return globalMax;
};
9. 全排列
全排列没有重复项
var permute = function(nums) {
const result = []
// 参数: 当前已经排列的数组
function backTrack(currents){
// 如果当前排列的数组长度已经和要排列的数组长度一致 则算是一种排列方式
if(currents.length === nums.length){
result.push([...currents]);
return
}
// 1.遍历要排列的数组
for(let num of nums){
// 2.如果已经在当前排列数组内部则进行下一个
if(currents.includes(num)){
continue;
}
// 3.把不在当前数组内的数 添加到当前排列数组里
// 4.继续调这个排列方法;再继续挨个数试
backTrack([...currents,num])
}
}
backTrack([])
return result;
};
41. 组合总和 === target
var combinationSum = function(candidates, target) {
const result = []
candidates.sort((a, b) => a - b); // 首先对数组进行排序
function backTrack(remaining, path, start){
if(remaining==target){
// 推入副本
result.push([...path]);
return
}
for(let i=start; i<candidates.length;i++){
if (remaining+candidates[i] > target) {
// 如果当前值大于剩余值,不需要继续探索
break;
}
// 继续探索
backTrack(remaining+candidates[i],[...path,candidates[i]],i)
}
}
backTrack(0,[], 0)
return result;
};
71.n个骰子的点数
// 回溯
var dicesProbability = function(n) {
let rt = {} // 存各种点数
let cont = 0 // 存总次数
function dfs(oNum) {
// 如果个数够了 证明是一次随机 记录当前点数 并且次数++
if(oNum.len == n){
// 重复的直接+1
if(rt[oNum.rt]){
rt[oNum.rt] += 1
} else{
rt[oNum.rt] = 1
}
cont++;
return
}
for(let i = 1; i < 7; i++){
// 循环6个点数 每个骰子都 六种可能
dfs({
len:oNum.len + 1,
rt:oNum.rt + i
})
}
}
dfs({len:0, rt:0})
let rtArr = Object.values(rt)
return rtArr.map(num=>{
return num/cont
})
};
16. 数组中的第K个最大元素
var findKthLargest = function(nums, k) {
// 第k大个元素 就是从小排序的倒数第几个元素 nums.length - k
return quickSelect(nums, 0, nums.length - 1, nums.length - k);
};
// 要排序的数组 最小 最大 第几个
function quickSelect(nums, low, high, k) {
const pivot = nums[Math.floor((low + high) / 2)];
let left = low;
let right = high;
while (left <= right) {
// 左侧的比中间值小的
while (nums[left] < pivot) left++;
// 右侧的比中间值大的
while (nums[right] > pivot) right--;
// 如果还没相遇 两个互换
if (left <= right) {
[nums[left], nums[right]] = [nums[right], nums[left]];
left++;
right--;
}
}
// 如果目标在 right侧的左边;去最低值和right的区间找
if (k <= right) return quickSelect(nums, low, right, k);
// 如果目标在 left侧的右边;去left和最高值的区间找
if (k >= left) return quickSelect(nums, left, high, k);
return nums[k];
}
21. 长度最小的子数组 === target
双指针;初始化最小值为Infinity; 先加到大于等于target值 再缩短长度,再继续加 记录最短长度
// 先加到target值 再缩短长度,再继续加 记录最短长度
var minSubArrayLen = function(target, nums) {
let left = 0;
let result =0;
let minLen = Infinity
for(let i=0;i<nums.length;i++){
result +=nums[i];
// 和 大于等于 target 开始缩短
while (result>=target){
minLen = Math.min(minLen, i-left+1)
result -= nums[left]
left++
}
}
return minLen === Infinity ? 0 : minLen;
};
80.和为s的连续正数序列
var findContinuousSequence = function(target) {
const max = target - 1, // 可选的最大正数范围 [1, max]
queue = [], // 单调递增队列
res = [] // 结果集
let sum = 0
for (let v = 1; v <= max; v++) {
// 一次将范围值入队
sum += v, queue.push(v)
// 当大于期望值target 时 出队且更新sum
while (sum > target) sum -= queue.shift()
// 当满足条件 存入结果集
if (sum === target && queue.length >= 2) res.push([...queue])
}
return res
};
72.0~n-1中缺失的数字
var missingNumber = function(nums) {
let left = 0;
let right = nums.length-1;
let mid = 0
while (left<=right) {
mid = Math.floor((left+right)/2)
if(mid==nums[mid]){ // 如果对应上 缺失的肯定在右边;左指针移动
left = mid + 1
}else{
right = mid -1 // 如果对应不上 缺失的肯定在左边;右指针移动
}
}
return left // left是满足条件的最小索引
};
let nums = [0,1,2,3,4,5,6,7,9]
37. 寻找重复数
var findDuplicate = function(nums) {
let low = 1;
let high = nums.length - 1;
while (low < high) {
const mid = Math.floor((low + high) / 2);
let count = 0;
// 计算数组中有多少个数小于等于 mid
nums.forEach((num) => {
if (num <= mid) {
count++;
}
});
// 根据抽屉原理缩小搜索范围 如果count大 证明重复数在左边
if (count > mid) {
high = mid;
} else {
low = mid + 1;
}
}
// low 和 high 相遇的地方就是重复的数字
return low;
};
29. 将数组分成和相等的三个部分
给你一个整数数组
arr
,只有可以将其划分为三个和相等的 非空 部分时才返回true
,否则返回false
。先看总数能不能被3整除; 然后得到每份多大值;循环的时候 得到target 计数 计数满足后 返回true 否则返回false
var canThreePartsEqualSum = function(arr) {
let totalSum = arr.reduce((a,b)=>a+b)
// 首先看总和能不能被3整除
if(totalSum%3!==0) return false;
// 每一块的值
let partSum = totalSum/3
let sum = 0, parts = 0;
for(let num of arr){
sum +=num;
if(sum === partSum){
parts++;
sum =0;
}
// 如果计算两部分 后一部分基本可以确定的 但是如果是 [-1,1,-1,1]这种会有问题 也可以当作一个优化项
if(parts === 3)return true;
}
return false;
};
31. 最长连续递增序列
给定一个未经排序的整数数组,找到最长且 连续递增的子序列,并返回该序列的长度。
重点是连续。如果不连续了 累加器=1 连续了就一致+;最大值每次比较
var findLengthOfLCIS = function(arr) {
if (arr.length === 0) return 0;
let maxLen = 1;
let currentLen = 1;
for (let i = 1; i < arr.length; i++) {
if (arr[i] > arr[i - 1]) {
currentLen++;
maxLen = Math.max(maxLen, currentLen);
} else {
currentLen = 1;
}
}
return maxLen;
};
32. 最长递增子序列
给你一个整数数组
nums
,找到其中最长严格递增子序列的长度。初始化一个数组 dp 与输入数组 nums 长度相同,每个元素的值为 1。
第一层遍历所有;每个遍历它前面的值 只要有比当前值小的 看这个点的dep值+1 与当前的dep比较 哪个大取哪个
如果需要得到最长的子序列。可以把dep里存数组。比较的时候用长度P「初始值可以用map填进去当前数组」
var lengthOfLIS = function(nums) {
const dep = Array(nums.length).fill(1)
let result = 0
for(let i=0;i<nums.length;i++){
for(let j=0;j<i;j++){
if(nums[i]>nums[j]){
// 可以不连续 上升就可以
dep[i] = Math.max(dep[i],dep[j]+1)
}
}
result = Math.max(result,dep[i])
}
return result;
};
93.最长重复子数组
给两个整数数组 A
和 B
,返回两个数组中公共的、长度最长的子数组的长度。
示例:
输入: A: [1,2,3,2,1] B: [3,2,1,4,7] 输出:3 解释: 长度最长的公共子数组是 [3, 2, 1] 。
function findLength(nums1, nums2) {
let m = nums1.length, n = nums2.length;
//用一个二维数组 记录前面的相等次数。取最大值
let dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0));
let maxLength = 0;
for (let i = 1; i <= m; i++) {
for (let j = 1; j <= n; j++) {
if (nums1[i - 1] === nums2[j - 1]) {
// 主要看前一个。当前值相等 前一个的值+1为现在的值
dp[i][j] = dp[i - 1][j - 1] + 1;
maxLength = Math.max(maxLength, dp[i][j]);
} else {
dp[i][j] = 0;
}
}
}
return maxLength;
}
34. 打乱数组
/**
* @param {number[]} nums
*/
var Solution = function(nums) {
this.original = nums.slice(); // 保存原始数组的副本
this.array = nums;
};
/**
* Resets the array to its original configuration and return it.
* @return {number[]}
*/
Solution.prototype.reset = function() {
this.array = this.original.slice(); // 恢复原始数组的副本
return this.array;
};
/**
* Returns a random shuffling of the array.
* @return {number[]}
*/
Solution.prototype.shuffle = function() {
for (let i = 0; i < this.array.length; i++) {
let rand = Math.floor(Math.random() * (i + 1)); // 随机选择一个小于或等于 i 的索引
// 交换当前元素和随机选择的元素
[this.array[i], this.array[rand]] = [this.array[rand], this.array[i]];
}
return this.array;
};
76.调整数组顺序使奇数位于偶数前面
var exchange = function(nums) {
let rt = []
for(let i=0,len=nums.length;i<len;i++){
if(nums[i]%2 ==0){
rt.push(nums[i])
}else{
rt.unshift(nums[i])
}
}
return rt;
};
let rt = exchange([1,2,3,4])
console.log(rt)
85.删除有序数组中的重复项
var removeDuplicates = function(nums) {
if (nums.length == 0) return 0;
let i = 0;
for (let j = 1; j < nums.length; j++) {
// 如果下一个和当前的相等就直接跳过 不想等时候 把不相等的移动到 前面来覆盖掉想等的值
if (nums[j] != nums[i]) {
i++;
// 不相等时候直接下一个赋值到当前值
nums[i] = nums[j];
}
}
return i + 1;
};
// 使用了splice截取数组
var removeDuplicates = function(nums) {
let len = nums.length
for(let i=0;i<len;i++){
if(nums[i] == nums[i-1]){
nums.splice(i,1)
len--
i--
}
}
return len
};
86.两个数组的交集
题:给定两个数组 nums1
和 nums2
,返回 它们的交集 。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序 。
let rtArr =[]
while (arr1.length) {
let item = arr1.pop()
let index = arr2.indexOf (item)
if (index != -1&&!rtArr.includes(item)) {
// 确保重复的不被漏掉
arr2.splice(index, 1)
rtArr.push(item)
}
}
return rtArr
栈
75.用两个栈实现队列
var CQueue = function() {
this.inStack = []
this.outStack =[]
};
/**
* @param {number} value
* @return {void}
*/
CQueue.prototype.appendTail = function(value) {
this.inStack.push(value)
};
/**
* @return {number}
*/
CQueue.prototype.deleteHead = function() {
const {inStack,outStack} = this
if(this.outStack.length){
return outStack.pop()
} else {
// 如果出栈数据没有 把进栈的数据倒过去
while(inStack.length){
outStack.push(inStack.pop())
}
return outStack.pop() || -1
}
};
94.LRU 缓存机制
运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制 。
实现 LRUCache
类:
LRUCache(int capacity)
以正整数作为容量capacity
初始化 LRU 缓存int get(int key)
如果关键字key
存在于缓存中,则返回关键字的值,否则返回-1
。void put(int key, int value)
如果关键字已经存在,则变更其数据值;如果关键字不存在,则插入该组「关键字-值」。当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。
进阶:你是否可以在 O(1)
时间复杂度内完成这两种操作?
This.map.keys().next().value 是map的第一个键
var LRUCache = function(capacity) {
this.capacity = capacity;
this.map = new Map();
};
LRUCache.prototype.get = function(key) {
if(this.map.has(key)){
let temp=this.map.get(key)
this.map.delete(key);//******
this.map.set(key, temp);
return temp
}else{
return -1
}
};
LRUCache.prototype.put = function(key, value) {
if(this.map.has(key)){
this.map.delete(key);//******
}
this.map.set(key,value);
if(this.map.size > this.capacity){
this.map.delete(this.map.keys().next().value);
}
};
字符串
1. 无重复字符的最长子串
var lengthOfLongestSubstring = function(s) {
let l = 0; // 定义左指针
let r=0
let res = 0; // 结果
let map = new Map(); // 存放字符和对应下标
let len = s.length
while (r<len) {
// 两个重复的情况 所以要 map.get(s[r]) >= l
if (map.has(s[r]) && map.get(s[r]) >= l) {
l = map.get(s[r]) + 1; // 出现重复 取其下一个 出现重复的和最后边的重复了 就取其下一个正好
}
res = Math.max(res, r - l + 1); // 计算结果 ab 字符串是1-0+1
map.set(s[r], r); // 存下每个字符的下标
r++
}
return res;
};
15. 最长回文子串
var longestPalindrome = function(s) {
let start =0;
let end =0;
let maxLen = 0
// 从中间扩散到两边 一直相等一直变长
function getOther(left, right){
while (left>=0&&right<s.length&&s[left]===s[right]){
left--;
right++;
}
// 进来基数时候进来就会进循环 偶数时候不相等也默认是1
return right - left - 1
}
for(let i=0;i<s.length;i++){
// 考虑奇数偶数
const len1 = getOther(i, i);// 奇数
const len2 = getOther(i, i+1)// 偶数
const len = Math.max(len1,len2)
if(maxLen<len){
maxLen = len
start = i - Math.floor((len-1)/2)// 奇数的时候 向前len-1的一半********
end = i + Math.floor(len/2)
}
}
return s.substring(start,end+1)
};
4. 比较版本号
版本号特点是字符串的。用.分开的字符串。从高位到底位只要大就大 和 字符串的总长度无关
var compareVersion = function(version1, version2) {
// map(NUmber) 直接转换成number的数组 可以直接比较;大于小于的时候直接返回;循环完没有返回则返回0
var v1 = version1.split('.').map(Number)
var v2 = version2.split('.').map(Number)
var vm = Math.max(v1.length, v2.length)
for(let i=0;i<vm;i++){
if((v1[i]||0)>(v2[i]||0)){
return 1;
}
if((v1[i]||0)<(v2[i]||0)){
return -1;
}
}
return 0;
};
7. 有效的括号
栈存储左括号,遇到右括号 与之对应则弹出 没有对应返回false;最后栈空则全部匹配
var isValid = function(s) {
const mapping = {
')': '(',
'}': '{',
']': '['
};
let stack = []
for(char of s){
// 如果是左侧存入栈 如果是右侧看是不是和栈顶的一样
if(mapping[char]){
// 如果不一样 返回false 一样的话pop出栈
if(mapping[char] !== stack.pop()){
return false
}
}else {
stack.push(char)
}
}
// 最后栈空 为正好全部匹配
return stack.length === 0
};
8. 字符串相加
进制两个相加时候 从底位开始 取余和删除。注意字符串转成数字相加
var addStrings = function(num1, num2) {
let superfluous = 0;// 默认没有进位
let result = ''// 默认是空字符串
let i=num1.length -1,j=num2.length-1
// i>=0 j>=0 一直循环到最后
while(i>=0||j>=0||superfluous>0){
// ~~(num1[i]||0) 取出来的是字符串 一定要转成number
const sum = ~~(num1[i]||0) + ~~(num2[j]||0) + superfluous
// sum/10 进位的值
superfluous = Math.floor(sum/10)
// sum%10 个位 当前位的值
result = (sum%10) + result
i--;
j--;
}
return result;
};
27. 最大交换 交换完数最大
给定一个非负整数,你至多可以交换一次数字中的任意两位。返回你能得到的最大值。
只要后边的数「最大数」比前面的数大 移动到前面来就是最大数
var maximumSwap = function(num) {
const numArr = num.toString().split('')
const lastArr = Array(10).fill(-1);
// 存储每个数字的位置
for(let i = 0;i<numArr.length;i++){
lastArr[numArr[i]] = i
}
for(let i=0;i<numArr.length;i++){
// 比当前值大 且位置在当前值后边 遍历9- numarr【ℹ️】 就是找 比他大的数
for(let j=9;j>numArr[i];j--){
if(lastArr[j]>i){
// 交换是交换位置 lastArr[j] 才是j的位置 并不是j
[numArr[i], numArr[lastArr[j]]] = [numArr[lastArr[j]], numArr[i]]
return Number(numArr.join(''))
}
}
}
return num;
};
51. Excel表列名称
var convertToTitle = function(columnNumber) {
let result = ''
while (columnNumber>0){
// 列在0 开始 都要-1
const current =(columnNumber-1)%26;
result = String.fromCharCode(current + 65) + result
columnNumber = Math.floor((columnNumber-1)/26)
}
return result
};
52. Excel 表列序号 相当于26进制
var titleToNumber = function(columnTitle) {
// let result = 0
// let step = 1
// while (columnTitle){
// const end = columnTitle.substring(columnTitle.length-1,columnTitle.length)
// result += step*(end.charCodeAt(0)-64)
// step *=26;
// columnTitle = columnTitle.substring(0,columnTitle.length-1)
// console.log(columnTitle)
// }
// return result;
// uniCode 码; a:97 A:65 ;str.charCodeAt(i)如果不写i 默认取str的unicode
// 最大开始遍历 每次*26。 进制 如果是10进制 就*10
let result = 0;
for (let i = 0; i < columnTitle.length; i++) {
result = result * 26 + (columnTitle.charCodeAt(i) - 64);
}
return result;
};
82.回文数
从低位开始。倒过来
var isPalindrome = function(x) {
if(x<0){
return false
}
let tempX = x
let rt = 0
while (x) {
rt = 10*rt + x%10 // 加上一位。每次加一位 原来的相左移动一位所有*10
x = Math.floor(x/10)// 向右移动 /10
}
if(rt == tempX){
return true
} else {
return false
}
};
70.替换空格
// 注意一点 字符串不是引用类型 直接改的话不会变化;s[2] = '20%' s是不发生变化的;
// 所有用一个字符串一直拼接
function replaceSpace(s){
let rt = ''
for(let i=0,len=s.length;i<len;i++){
if(s[i] === ' '){
rt += '%20'
} else {
rt += s[i]
}
}
return rt;
}
98.写一个算法 实现一个字符串的规则解析:
例子:a(b)<2>c 输出:abbc,a(b(c)<3>de)<2>f 输出abcccdebcccdef;()代表重复内容,<>代表重复的次数
function parseAndRepeat(str) {
let stack = ['']; // 初始化栈,栈底是空字符串,用于累加结果
let i = 0; // 遍历字符串的索引
while (i < str.length) {
let char = str[i];
if (char === '(') {
// 遇到 '(',开始新的重复区块
stack.push('');
i++;
} else if (char === ')') {
// 遇到 ')',结束当前重复区块
let repeatedStr = stack.pop(); // 获取当前重复区块内容
i++;
// 查找重复次数
let repeatTimes = 0;
if (str[i] === '<') {
i++;
let numStart = i;
while (str[i] !== '>') {
i++;
}
repeatTimes = parseInt(str.slice(numStart, i));
i++;
}
stack[stack.length - 1] += repeatedStr.repeat(repeatTimes);
} else {
// 非特殊字符,直接加入当前字符串
stack[stack.length - 1] += char;
i++;
}
}
return stack.join(''); // 将栈中的所有字符串合并
}
// 示例用法
console.log(parseAndRepeat('a(b)<2>c')); // 输出:abbc
console.log(parseAndRepeat('a(b(c)<3>de)<2>f')); // 输出:abcccdebcccdef
特殊
11. 螺旋矩阵
var spiralOrder = function(matrix) {
const result = [];
let top=0,bottom=matrix.length-1;
let left=0,right=matrix[0].length-1;
while (left<=right&&top<=bottom){
for(let i=left;i<=right;i++){
result.push(matrix[top][i])
}
top++
for(let i=top;i<=bottom;i++){
result.push(matrix[i][right])
}
right--
// top++ 可能大于 bottom;最后一行数据时边界
if(bottom>=top){
for(let i=right;i>=left;i--){
result.push(matrix[bottom][i])
}
bottom--
}
// 最后一列时边界
if(right>=left){
for(let i=bottom;i>=top;i--){
result.push(matrix[i][left])
}
left++
}
}
return result;
};
12. 买卖股票的最佳时机
var maxProfit = function(prices) {
// 买入最大值 初始化时候
let inPrice = Number.MAX_VALUE;
let result = 0;
for(let i= 0; i<prices.length;i++){
// 比买入价便宜就继续买
if(inPrice>prices[i]){
inPrice = prices[i]
}
result = Math.max(result, prices[i]-inPrice)
}
return result
};
18. 岛屿数量
发现一个岛屿 把与其接壤的全部设为0 接壤的岛屿算一个
var numIslands = function(grid) {
let count = 0;
for(let i=0;i<grid.length;i++){
for(let j=0;j<grid[i].length;j++){
if(grid[i][j]==='1'){
count++;
defs(i,j, grid)
}
}
}
// 挨着的岛算是一个岛屿
function defs(i,j){
//超出边界 或者不是岛屿直接返回
if(i<0||i>=grid.length||j<0||j>=grid[0].length||grid[i][j]==='0'){
return
}
// 是岛屿已经计数 标记为0 把岛屿周围的1都标记为0
grid[i][j] = '0'
defs(i-1,j)
defs(i+1,j)
defs(i,j-1)
defs(i,j+1)
}
return count;
};
22. 岛屿的最大面积
找到岛屿计算其附近岛屿累加 「当前岛屿置为0;避免重复计算」与最大值比较
var maxAreaOfIsland = function(grid) {
let result = 0,bottom=grid.length-1,right=grid[0].length-1;
for(let i=0;i<=bottom;i++){
for(let j=0;j<=right;j++){
if(grid[i][j] === 1){
// 找到一个岛屿 累加周围的
result = Math.max(result, getOther(i,j))
}
}
}
function getOther(i,j){
// 边界或者不是岛屿的 返回0
if(i<0||i>bottom||j<0||j>right||grid[i][j]===0){
return 0
}
// 计数 标记
let num = 1
grid[i][j] = 0
// 四个方向去找
num += getOther(i-1,j)
num += getOther(i+1,j)
num += getOther(i,j-1)
num += getOther(i,j+1)
return num;
}
return result;
};
40. 被围绕的区域
var solve = function(board) {
const rows = board.length;
const cols = board[0].length;
function dfs(x,y){
if(x<0||y<0||x>=rows||y>=cols||board[x][y]!='O')return;
// 是0的标记为你/ 没包裹着
board[x][y] = '/'
dfs(x+1,y)
dfs(x-1,y)
dfs(x,y+1)
dfs(x,y-1)
}
// 四个边发起 为0的则没被包裹
for(let i=0;i<rows;i++){
dfs(i,0)
dfs(i,cols-1)
}
for(let i=0;i<cols;i++){
dfs(0,i)
dfs(rows-1,i)
}
console.log(board)
for(let i=0;i<rows;i++){
for(let j=0;j<cols;j++){
if (board[i][j] === 'O') board[i][j] = 'X';
if (board[i][j] === '/') board[i][j] = 'O';
}
}
return board;
};
20. 接雨水
哪边底从哪边开始。接的雨水 最大值-当前值
var trap = function(height) {
let left = 0;
let right = height.length-1;
let leftMax = 0
let rightMax = 0
let result = 0;
while (left<right){
// 哪边低从哪边开始
if(height[left]<height[right]){
if(height[left]>leftMax){
leftMax = height[left]
}else{
result +=(leftMax - height[left])
}
left ++
}else{
if(height[right]> rightMax){
rightMax = height[right]
}else {
result +=(rightMax - height[right])
}
right--;
}
}
return result;
};
23. 爬楼梯
后边的雨前面的数据有关系。基本用这种方法;获取了前几个值 后边的循环计算
var climbStairs = function(n) {
const dep =[1,2]
//后一次是前两次的和
for(let i=2;i<n;i++){
dep[i] = dep[i-1] + dep[i-2]
}
return dep[n-1]
};
81.整数拆分。7 dp[i]= dp[i-3]*3
// 动态规划
var integerBreak = function(n) {
const dp = [];
dp[2] = 1;
dp[3] = 2;
dp[4] = 4;
dp[5] = 6;
dp[6] = 9;
for(let i = 7; i <= n ; i++){
dp[i] = dp[i - 3] * 3;
}
return dp[n];
};
// 找规律
var integerBreak = function(n) {
if(n == 2) return 1;
if(n == 3) return 2;
let result = 1;
while(n > 4){
n -= 3;
result *= 3;
}
return (n * result);
};
48. 外观数列
var countAndSay = function(n) {
if(n==1) return '1';
let prev = '1'
// *****i<=n 有n个数
for(let i=2;i<=n;i++){
let curr = ''
let j=0;
// 遍历pre
while (j<prev.length){
let count = 1
// 累加相同
while (j<prev.length&&prev[j]===prev[j+1]){
count++;
j++
}
// j++
curr += count + prev[j]
j++
}
prev = curr
}
return prev;
};
33. 打家劫舍
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
知道前两个的时候 第三个偷不偷 值看前面两个的值 如果。三个情况只能选 1 3 或者2
var rob = function(nums) {
// 一家都没有 返回0
if(nums.length==0)return 0;
if(nums.length ==1)return nums[0]
// 动态规划 只有一家 只有两家的情况
let dep = [nums[0], Math.max(nums[0],nums[1])];
for(let i=2;i<nums.length;i++){
// 每一家如果偷 得到钱的情况 就是这一家加上这家的上上家 如果不偷就是前一家 偷了得到的钱 ****
dep[i] = Math.max(dep[i-1],dep[i-2]+nums[i])
}
return dep.pop()
};
56. 不同路径
var uniquePaths = function(m, n) {
const dp = Array(m).fill(Array(n).fill(0));
// 初始化第一行和第一列
for(let i=0;i<m;i++){
dp[i][0] = 1
}
for(let i=0;i<n;i++){
dp[0][i] = 1
}
// 循环在1开始
for(let i=1;i<m;i++){
for(let j=1;j<n;j++){
dp[i][j] = dp[i][j-1]+dp[i-1][j]
}
}
return dp[m-1][n-1]
};
34 零钱兑换 II
dp 每种零钱可以找的数量; dp[i - coin] 前面找零需要的数量
function coinChange(coins, amount) {
const dp = new Array(amount + 1).fill(amount + 1);
// 0的时候不用找
dp[0] = 0;
for (let i = 1; i <= amount; i++) {
for (let coin of coins) {
// 零钱大于要找的钱的时候
if (i >= coin) {
dp[i] = Math.min(dp[i], dp[i - coin] + 1);
}
}
}
return dp[amount] > amount ? -1 : dp[amount];
}
44. 柠檬水找零
var lemonadeChange = function(bills) {
let five = 0, ten =0;
for(let bill of bills){
if(bill === 5){
five++
}
if(bill === 10){
if(five<=0)return false;
ten++;
five--;
}
if(bill === 20){
if(ten>0 && five>0){
ten--;
five--;
continue;
}
if(five>2){
five-=3
continue;
}
return false
}
}
return true;
};
35. 寻找旋转排序数组中的最小值 II
var findMin = function(nums) {
// 升序螺旋 如果旋转了 旋转点一定小于前一个值;如果旋转后和原来一样 第一个就是最小值
for(let i =1;i<nums.length;i++){
if(nums[i]<nums[i-1]){
return nums[i]
}
}
return nums[0]
};
36. 移掉 K 位数字 使剩下的数字最小
给你一个以字符串表示的非负整数
num
和一个整数k
,移除这个数中的k
位数字,使得剩下的数字最小。请你以字符串形式返回这个最小的数字。
var removeKdigits = function(num, k) {
let stack = []
for(let step of num){
// 如果后边的值比前面的值小就 用后边的值把前面的值删除掉; 删除掉k个之后不再删除
while(stack.length>0&&k>0&&stack.slice(-1)>step){
stack.pop()
k--;
}
stack.push(step)
}
// 如果上边没删完 删除后边的
while (k>0){
stack.pop()
k--
}
// 如果第一个值是0的情况处理下
while (stack[0] == '0'){
stack.shift()
}
return stack.join('')||'0'
};
39. 括号生成
var generateParenthesis = function(n) {
const result = []
function backTrack(s,left, right){
if(s.length == 2*n){
result.push(s)
return
}
if(left<n){
backTrack(s+'(', left+1, right)
}
// 右边小于左边
if(right<left){
backTrack(s+')', left, right+1)
}
}
backTrack('',0,0)
return result;
};
42. 逆波兰表达式求值
var evalRPN = function(tokens) {
const stack = [];
for (let token of tokens) {
// isFinite 判断是数 还是操作符。是数直接压入栈中
if (isFinite(token)) {
stack.push(parseInt(token, 10)); // 明确指定十进制
} else {
// 符号时候 栈中弹出两个数做为计算; 计算的结果压入栈中
let right = stack.pop();
let left = stack.pop();
switch (token) {
case '+':
stack.push(left + right);
break;
case '-':
stack.push(left - right);
break;
case '*':
stack.push(left * right);
break;
case '/':
// 结果应该向零取整
stack.push(parseInt(left / right, 10));
break;
}
}
}
return stack.pop();
};
46. 移动零
var moveZeroes = function(nums) {
let insert = 0
for(let i=0;i<nums.length;i++){
if(nums[i]!=0){
nums[insert] = nums[i]
insert++
}
}
while (insert<nums.length){
nums[insert] = 0;
insert++;
}
return nums;
};
var moveZeroes = function(nums) {
let slow = 0;
for (let fast = 0; fast < nums.length; fast++) {
if (nums[fast] !== 0) {
// 把非零元素移动到slow的位置
nums[slow] = nums[fast];
// 当fast和slow不相等时,说明中间有零存在,需要将当前fast的位置设置为0
if (fast !== slow) {
nums[fast] = 0;
}
slow++;
}
}
};
47. 最后一块石头的重量
var lastStoneWeight = function(stones) {
while (stones.length>1){
// 排序很重要
stones.sort((a,b)=>{return a-b})
const y = stones.pop()
const x = stones.pop()
if(y>x){
stones.push(y-x)
}
}
return stones[0]||0;
};
50. 矩形重叠
右上角大于左下角。左下角小于右上角 则有重叠。相当于大的大于小的。小的小于大的则有重叠。 重叠和完全包裹不一样要注意
var isRectangleOverlap = function(rec1, rec2) {
// 解构赋值,获取矩形的坐标
let [x1, y1, x2, y2] = rec1;
let [x3, y3, x4, y4] = rec2;
// 检查是否满足重叠的条件
return x2 > x3 && x1 < x4 && y2 > y3 && y1 < y4;
};
LCR 54. 最小栈
var MinStack = function() {
this.stack = [];
this.minStack = [];
};
/**
* @param {number} x
* @return {void}
*/
MinStack.prototype.push = function(x) {
this.stack.push(x);
if (this.minStack.length === 0 || x <= this.minStack[this.minStack.length - 1]) {
this.minStack.push(x);
}
};
/**
* @return {void}
*/
MinStack.prototype.pop = function() {
const popped = this.stack.pop();
if (popped === this.minStack[this.minStack.length - 1]) {
this.minStack.pop();
}
};
/**
* @return {number}
*/
MinStack.prototype.top = function() {
return this.stack[this.stack.length - 1];
};
/**
* @return {number}
*/
MinStack.prototype.getMin = function() {
return this.minStack[this.minStack.length - 1];
};
// 类的写法
// class MinStack {
// constructor() {
// this.stack = [];
// this.minStack = [];
// }
//
// push(x) {
// this.stack.push(x);
// if (this.minStack.length === 0 || x <= this.minStack[this.minStack.length - 1]) {
// this.minStack.push(x);
// }
// }
//
// pop() {
// const popped = this.stack.pop();
// if (popped === this.minStack[this.minStack.length - 1]) {
// this.minStack.pop();
// }
// }
//
// top() {
// return this.stack[this.stack.length - 1];
// }
//
// getMin() {
// return this.minStack[this.minStack.length - 1];
// }
// }
63. 前 K 个高频元素
var topKFrequent = function(nums, k) {
let map = new Map();
let res = [];
for(let i = 0; i < nums.length; i++) {
if(map.has(nums[i])) {
map.set(nums[i], map.get(nums[i]) + 1);
} else {
map.set(nums[i], 1);
}
}
//返回一个按出现次数降序的二维数组
let sortArray = Array.from(map).sort((a, b) => b[1] - a[1]);
for(let i = 0; i < k; i++) {
res.push(sortArray[i][0]);
}
return res;
};
64. 克隆图
var cloneGraph = function(node) {
// 记录复制的节点
let map = new Map()
function dfs(node){
if(!node)return node;
if(map.has(node)) return map.get(node);
// clone当前节点
let cloneNode = new Node(node.val);
map.set(node, cloneNode);
// 当前节点的相邻节点
for(let neighbor of node.neighbors){
cloneNode.neighbors.push(dfs(neighbor))
}
return cloneNode
}
return dfs(node)
};
65.斐波那契数列
// 1.最简单递归
var fib = function(n) {
if(n<2){
return n
}
return fib(n-1) + fib(n-2)
}
// 2.递归加记忆化
var fib = function(n,stateMap=[]) {
if(n<2){
return n
}
if(!stateMap[n]){
stateMap[n] = fib(n-1,stateMap) + fib(n-2,stateMap)
}
return stateMap[n]% 1000000007
};
// 动态规划
var fib = function(n) {
if(n<2){
return n
}
let p = 0
let c = 1
let rt = 0
let i = 1
while (i++ < n) {
rt = (p + c) % 1000000007
p = c
c = rt
}
return rt
};
66.搜索二维矩阵 II
var searchMatrix = function(matrix, target) {
for(let i=0,len=matrix.length;i<len;i++){
if(matrix[i][0]>target){
continue
}
for(let j=0,lens=matrix[i].length;j<lens;j++){
if(matrix[i][j]>target){
break
}
if(matrix[i][j]==target){
return true
}
}
}
return false
};
// 根据数的规律在右上角开始 左边的比他小 下边的比他大
var searchMatrix = function(matrix, target) {
let len = matrix.length
let r = matrix[0].length - 1
let t = 0
while (r>=0 && t<len) {
if(matrix[t][r] > target){
r--
} else if(matrix[t][r] < target){
t++
} else {
return true
}
}
return false
}
68.二进制中1的个数
flg一直左移动。32位后就没了
var hammingWeight = function(n) {
let flg = 1;
let cont = 0
while (flg) {
if(flg & n){
cont ++
}
flg = flg << 1
}
return cont
};
73.扑克牌中的顺子 没重复 最大值-最小值<=4
// 思路:
- 顺子的最小最大值差 <=4
- 顺子没对子
var isStraight = function(nums) {
let rt = {}
// 这俩默认值很关键 用边界 不能直接用nums[0] 第一个可能是 0
// 如果有对子肯定不是顺子。在没有对子的情况下 只要最大-最小 <=4 就是顺子
let min = 14
let max = 1
for(let i=0;i<nums.length;i++){
if(nums[i]!= 0){
if(rt[nums[i]]){
return false
}
rt[nums[i]] = true
min = Math.min(min,nums[i])
max = Math.max(max,nums[i])
}
}
if(max-min <=4){
return true
}else{
return false
}
};
74.数组中出现次数超过一半的数字
var majorityElement = function(nums) {
let len = nums.length
let rt = {}
for(let i=0;i<len;i++){
let temp = nums[i]
if(rt[temp]){
rt[temp]++
} else{
rt[temp] = 1
}
if(rt[temp]>(len/2)){
return temp
}
}
};
// [摩尔投票】数组中出现次数超过一半的数字; 一个与其他比
var majorityElement1 = function(nums) {
let ans = 0, count = 0;
for(let i = 0; i < nums.length; i++){
if(!count) {
ans = nums[i];
count++;
}
if(nums[i] === ans) {
count += 1
} else {
count += -1;
}
}
return ans;
};
let nums = [ 1,2,1,3,1,4,1,5,6]
let rt = majorityElement1(nums)
console.log(rt)
83.高度检查器
学校在拍年度纪念照时,一般要求学生按照 非递减 的高度顺序排列。
请你返回能让所有学生以 非递减 高度排列的最小必要移动人数。
注意,当一组学生被选中时,他们之间可以以任何可能的方式重新排序,而未被选中的学生应该保持不动。
高度不在顺序中的拿出来
// 排序后比较不同
var heightChecker = function(heights) {
let temp = [...heights]
heights.sort((a,b)=>{return a-b})
let rt = temp.filter((item, index)=>{
return item != heights[i]
})
return rt.length
};
let heights = [1,1,4,2,1,3]
let rt = heightChecker(heights)
84.同构字符串
给定两个字符串 s 和 t,判断它们是否是同构的。
如果 s 中的字符可以按某种映射关系替换得到 t ,那么这两个字符串是同构的。
每个出现的字符都应当映射到另一个字符,同时不改变字符的顺序。不同字符不能映射到同一个字符上,相同字符只能映射到同一个字符上,字符可以映射到自己本身。
//直接记录每个映射关系;如果映射关系重复了直接判断不是同构字符串
var isIsomorphic = function(s, t) {
let mapS = {}
let mapT={}
for(let i=0,len=s.length;i<len;i++){
let valueS = s[i]
let valueT = t[i]
if(!mapS[valueS]){
// 如果不存在映射就直接赋值
mapS[valueS] = valueT
} else if(mapS[valueS] !== valueT){
// 如果存在映射但是不相等 就返回false
return false
}
if(!mapT[valueT]){
mapT[valueT] = valueS
} else if(mapT[valueT] != valueS){
return false
}
}
return true;
};
87.猜数字游戏
你在和朋友一起玩 猜数字(Bulls and Cows)游戏,该游戏规则如下:
你写出一个秘密数字,并请朋友猜这个数字是多少。 朋友每猜测一次,你就会给他一个提示,告诉他的猜测数字中有多少位属于数字和确切位置都猜对了(称为“Bulls”, 公牛),有多少位属于数字猜对了但是位置不对(称为“Cows”, 奶牛)。 朋友根据提示继续猜,直到猜出秘密数字。 请写出一个根据秘密数字和朋友的猜测数返回提示的函数,返回字符串的格式为 xAyB ,x 和 y 都是数字,A 表示公牛,用 B 表示奶牛。
xA 表示有 x 位数字出现在秘密数字中,且位置都与秘密数字一致。 yB 表示有 y 位数字出现在秘密数字中,但位置与秘密数字不一致。 请注意秘密数字和朋友的猜测数都可能含有重复数字,每位数字只能统计一次。
示例 1:
输入: secret = "1807", guess = "7810" 输出: "1A3B" 解释: 1 公牛和 3 奶牛。公牛是 8,奶牛是 0, 1 和 7。
var getHint = function(secret, guess) {
var bull = 0;
var cow = 0;
// 存储secret[n] guess[n]数字不同的元素
var skeep = []
var gkeep = []
// 先判断位置数字都一样,剩下的就用sk gk来存储存
for(var i in guess){
// 位置数字都一样 bull++
if(secret[i] == guess[i]){
bull++
} else {
skeep.push(secret[i])
gkeep.push(guess[i])
}
}
// 因为bull已经处理过;这边只要gkeep内的元素出现在skeep内 代表一个cow
for(var j in gkeep){
var findIndex = skeep.indexOf(gkeep[j]);
if(findIndex !=-1){
cow++;
skeep[findIndex] = null
}
}
return bull + "A" + cow + "B"
};
89.Nim 游戏](leetcode-cn.com/problems/ni…)
你和你的朋友,两个人一起玩 Nim 游戏:
桌子上有一堆石头。 你们轮流进行自己的回合,你作为先手。 每一回合,轮到的人拿掉 1 - 3 块石头。 拿掉最后一块石头的人就是获胜者。 假设你们每一步都是最优解。请编写一个函数,来判断你是否可以在给定石头数量为 n 的情况下赢得游戏。如果可以赢,返回 true;否则,返回 false 。
示例 1:
输入:n = 4 输出:false 解释:如果堆中有 4 块石头,那么你永远不会赢得比赛; 因为无论你拿走 1 块、2 块 还是 3 块石头,最后一块石头总是会被你的朋友拿走。
// 找规律
var canWinNim = function(n) {
if(n<4){
return true
}
if(n%4 == 0){
return false
}
return true
};
90.计数质数
统计所有小于非负整数 n
的质数的数量。
var countPrimes = function(n) {
let cont = 0;
let state = {2:false}// 2是质数
for(let i=2;i<n;i++){
if(!state[i]){
cont++
质数的平方肯定不是质数。合数的 几倍肯定还是合数。 从小到达累计标记
for(let j=i*i;j<n;j+=i){
state[j] = true
}
}
}
return cont;
};
91.旋转数组
给定一个数组,将数组中的元素向右移动 k
个位置,其中 k
是非负数。
var rotate = function(nums, k) {
let step = nums.length % k// 旋转整圈算是不动 看余数 直接把前面的移动到后边即可
let temp = nums.splice(0,nums.length - step)
nums.push(...temp)
};
var rotate = function(nums, k) {
nums.push(...(nums.splice(0,nums.length-k%nums.length)))
};
95.圆圈中最后剩下的数字 ans = (ans + m) % i;
0,1,···,n-1这n个数字排成一个圆圈,从数字0开始,每次从这个圆圈里删除第m个数字(删除后从下一个数字开始计数)。求出这个圆圈里剩下的最后一个数字。
例如,0、1、2、3、4这5个数字组成一个圆圈,从数字0开始每次删除第3个数字,则删除的前4个数字依次是2、0、4、1,因此最后剩下的数字是3。
var lastRemaining = function(n, m) {
let ans = 0;
for (let i = 2; i <= n; i++) {
ans = (ans + m) % i;
}
return ans;
};
102.机器人的运动范围
地上有一个m行n列的方格,从坐标 [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。请问该机器人能够到达多少个格子?
// 广度优先遍历
var movingCount = function(m, n, k) {
// 位数和
function getSum(num) {
let sum = 0;
while(num) {
sum += num % 10;
num = Math.floor(num / 10);
}
return sum;
}
// 方向数组
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++) {
每个方向x y有一对值
let offsetX = x + directionAry[i][0];
let offsetY = y + directionAry[i][1];
// 临界值判断 注意 m n 都是长度 offsetX offsetY 是坐标
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;
};
103.把数组排成最小的数 并不是数字从小到大排序即可 字符串排序
输入一个非负整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。
var minNumber = function(nums) {
nums.sort((a, b)=>{
return (a+''+b) - (b+''+a)
})
return nums.join('')
}
104.加一
给定一个由 整数 组成的 非空 数组所表示的非负整数,在该数的基础上加一。 最高位数字存放在数组的首位, 数组中每个元素只存储单个数字。 你可以假设除了整数 0 之外,这个整数不会以零开头。 注意点: 满10 进位
function plusOne(digits) {
let len = digits.length-1
digits[len] = digits[len] + 1
// 从后往前检查 看可不可以进位置
for(let i=len;i>=0;i--){
let r = Math.floor(digits[i]/10)
if(r){
进位 看前面是否有值 有值直接进位 没有值 直接添加一位 unshift
digits[i] = digits[i]%10
// 如果前一位有值
if(digits[i-1]){
digits[i-1] = digits[i-1] + r
} else {
// 如果前一位没有值了 就把进位 加在数组的第一位
digits.unshift(r)
}
} else {
break
}
}
return digits
}
var digits= [9]
var rt = plusOne(digits)
console.log(rt)
105.二进制求和
二进制求和 给你两个二进制字符串,返回它们的和(用二进制表示)。 输入为 非空 字符串且只包含数字 1 和 0。
示例 1: 输入: a = "11", b = "1" 输出: "100"
function addBinary(a, b){
let i = a.length-1
let j = b.length-1
let rt = ''
let c = 0
a = a.split('')
b = b.split('')
while (i>=0||j>=0||c) { // 边界处理 总位数可能比任意两个长
let s = (a[i]||0)*1 + (b[j]||0)*1 + c*1 // 加完后的值
// 主要就是判断加完后的值 然后给当前位置赋值 保留进位的值 留给前一位 一直累积到最后
switch (s) {
case 0:
rt = 0 + rt
c = 0
break;
case 1:
rt = 1 + rt
c = 0
break;
case 2:
rt = 0 + rt
c=1
break;
case 3:
rt = 1 + rt
c=1
break;
default:
if(c == 1){
rt = 1 + rt
c = 0
}
break;
}
i--
j--
}
return rt
}
let a = "11", b = "1"
let rt = addBinary(a,b)
console.log(rt)
106.平方根 二分
实现 int sqrt(int x) 函数。 计算并返回 x 的平方根,其中 x 是非负整数。 由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去。
var mySqrt = function(x) {
if(x < 0) return NaN;
if(x == 0) return 0;
let min = 1;
let max = x;
// 1 的情况 避免死循环
while(max - min > 1){
let mid = Math.ceil((max + min)/2);
// 二分 mid*mid 如果等于x 就是他的平方根; 和二分查找一样
if (mid*mid < x ) {
min = mid;
} else if (mid*mid > x) {
max = mid;
} else {
return mid
}
};
// 如果一直没找到就用最小的
return min;
};
let rt = mySqrt(2)
107.快速排序
let arr = [1,2,3,4,9,8,7,6,5]
let quickSort = function(arr){
let len = arr.length;
if(len<2)return arr; // 限制
let pivot = Math.floor(len/2)
let mid = arr[pivot];
let minArr = []
let maxArr = []
for(let i=0;i<len;i++){
// 临界值
if(arr[i] <= mid && i !=pivot){
minArr.push(arr[i])
} else if(arr[i] > mid){
maxArr.push(arr[i])
}
}
return [...quickSort(minArr),mid,...quickSort(maxArr)]//*****
}
let rt = quickSort(arr)
console.log(111, rt)
108.字符串大小写转换 charCodeAt fromCharCode a:97 A:32。A~a = 32
写个方法 将字符串 ‘abCDEg’ 转换成 ‘ABcdeG’;大小写转换
// charCodeAt fromCharCode 97 32
function resetChart(str) {
let rtStr = ''
for(let i=0;i<str.length;i++){
let rcode = str[i].charCodeAt()>=97?str[i].charCodeAt()-32:str[i].charCodeAt()+32
rtStr += String.fromCharCode(rcode)
}
return rtStr;
}
// toUpperCase toLowerCase
function resetChart2(str) {
let rtStr = ''
for(let i=0;i<str.length;i++){
rtStr += str[i].charCodeAt()>=97?str[i].toUpperCase():str[i].toLowerCase()
}
return rtStr;
}
109.数组拍平并去重复
如 将数组[[1,2,3], [4,5,6], [7,8], [6,7,9],[3,2,1],[1,1,1]] 转换为 [1,2,3,4,5,6,7,8,9]
let resetArr = function (arr){
let tempArr = [] // 存储不重复值
let rtArr = arr.reduce((rt,item)=>{
return rt.concat(item)
}).filter((item)=>{
if(!tempArr.includes(item)){
tempArr.push(item)
return true
}
})
return rtArr
}
// 直接循环等方法 用key不唯一的方式。
let resetArr1 = function (arr){
let rtArr = []
let arrMap = {}
// 用set存 更好些
arr.forEach(item => {
item.forEach((subItem)=>{
arrMap[subItem] = ''
})
});
rtArr = Object.keys(arrMap)
return rtArr
}
let arr = [[1,2,3], [4,5,6], [7,8], [6,7,9],[3,2,1],[1,1,1]]
resetArr(arr)
110.求两个数组的交集
如 arr1 = [1, 2, 2, 1],arr2 = [2, 2],返回 [2, 2]
// 一个数组上拿出值 看另一个数组是否存在。重点是删除操作 保证不重复
let intersect2 = function(arr1, arr2) {
let rtArr =[]
while (arr1.length) {
let item = arr1.pop()
let index = arr2.indexOf (item)
if (index != -1) {
// 确保重复的不被漏掉
arr2.splice(index, 1)
rtArr.push(item)
}
}
return rtArr
}
// 哈希表存储一个数组的值 另一个循环直接比较
let intersect3 = function(arr1, arr2) {
let rtArr =[]
const map = {}
for (let n of arr1) {
// 哈希表 存储每个值。 有相同的++
if (map[n]) {
map[n]++
} else {
map[n] = 1
}
}
for (let n of arr2) {
if (map[n] > 0) {
rtArr.push(n)
// 获取到一个相同的 --
map[n]--
}
}
return rtArr
}
let arr1 = [1, 2, 2, 1],arr2 = [2, 2]
intersect(arr1, arr2)
111.逆序number
将number类型的数转换为逆序后的字符串。如:输入整型 1234,返回字符串“4321”
// 转换成字符串直接循环
function numberReverse2(num){
// 直接转字符串循环
let strNum = String(num)
let strLen = strNum.length
let rtStr = ''
for(let i=strLen-1;i>=0;i--){
rtStr +=strNum[i]
}
return rtStr
}
// 用取余和相除做处理
function numberReverse3 (num){
let rtStr = ''
while (num!=0) {
// 利用 取余 和取整
rtStr += num%10
num = Math.floor(num/10)
}
return rtStr
}
let num = 1234
numberReverse(num)
112.实现findStrIndex算法
从长度为 n 的字符串 s 中,查找是否存在字符串 t,t 的长度是 m,存在返回位置,不存在返回-1。
let findStrIndex2 = function (s, n, t, m) {
let strMap = {},i=0;
// 循环直接把t的长度的值存起来当key
while (i<=n-m) {
// 把所有可能性都存起来 m个长度一个key
strMap[String(s.slice(i,i+m))] = i
i++
}
return strMap[t]||-1;
}
let s = 'abcdqwertyui'
let n = 12
let t = 'bc'
let m = 2
findStrIndex(s, n, t, m)
113.文档的相似度(交集并集长度)
两个(具有不同单词的)文档的交集(intersection)中元素的个数除以并集(union)中元素的个数,就是这两个文档的相似度。例如,{1, 5, 3} 和 {1, 7, 2, 3} 的相似度是 0.4,其中,交集的元素有 2 个,并集的元素有 5 个。给定一系列的长篇文档,每个文档元素各不相同,并与一个 ID 相关联。它们的相似度非常“稀疏”,也就是说任选 2 个文档,相似度都很接近 0。请设计一个算法返回每对文档的 ID 及其相似度。只需输出相似度大于 0 的组合,保留4位小数。请忽略空文档。为简单起见,可以假定每个文档由一个含有不同整数的数组表示。
输入:
[
[14, 15, 100, 9, 3],
[32, 1, 9, 3, 5],
[15, 29, 2, 6, 8, 7],
[7, 10]
]
输出:
[
"0,1: 0.2500",
"0,2: 0.1000",
"2,3: 0.1429"
]
let computeSimilarities1 = function(arr){
let rt = []
let len = arr.length
for(let i=0;i<len-1;i++){
for(let j=i+1;j<len;j++){
// 获取交集
let union= Array.from(new Set(arr[i].concat(arr[j])))
let iLenth = arr[i].length
let jLenth = arr[j].length
// 知道并集 交集的长度 可直接确定;
// 交集的长度 = 数组1长度 + 数组2长度 - 数组1数组2并集长度 ****
let similarities = (iLenth + jLenth - union.length)/union.length
if(similarities > 0.0001){
rt.push(i + ',' + j + ': ' + (similarities.toFixed(4)))
}
}
}
return rt
}
114.搜索二维矩阵
编写一个高效的算法来判断 m x n
矩阵中,是否存在一个目标值。该矩阵具有如下特性:
- 每行中的整数从左到右按升序排列。
- 每行的第一个整数大于前一行的最后一个整数。
var searchMatrix = function(matrix, target) {
// 如果每行第一个都比target大 就继续下一行;如果不比target大 就循环这一行的每一个比较
for(let i=0,len=matrix.length;i<len;i++){
if(matrix[i][0]>target){
continue
}
for(let j=0,lens=matrix[i].length;j<lens;j++){
if(matrix[i][j]>target){
break
}
if(matrix[i][j]==target){
return true
}
}
}
return false
};
// 转换成字符串 直接找
var searchMatrix1 = function(matrix, target) {
return matrix.toString().split(',').indexOf(target + '')==-1?false:true
};
115.月份对象转数组
某公司 1 到 12 月份的销售额存在一个对象里面
如下:{1:222, 2:123, 5:888},
请把数据处理为如下结构:[222, 123, null, null, 888, null, null, null, null, null, null, null]。
let setMounthData1 = function(data){
let rtData = new Array(12).fill(null).map((item, index)=>{
return data[index+1] || null
})
return rtData
}
116.求两个数的最大公约数 辗转相除法 更相减损术(出自九章算术)
求两个数的最大公约数(GCD,Greatest Common Divisor)的一个经典算法是欧几里得算法(Euclidean algorithm)。这个算法基于一个重要的原理:两个整数的最大公约数与它们的差的最大公约数相同。更具体地说,对于任意两个整数 a 和 b(假设 a>b),它们的最大公约数等于 a 和 a−b 的最大公约数。欧几里得算法通过重复应用这个原理,逐步减小整数的大小,直到找到最大公约数。
欧几里得算法的一个更实用的形式是使用余数替代差值,这样可以更快地减小数的大小。具体算法如下:
- 若 a=0b=0,则最大公约数为 a。
- 否则,计算 a 除以 b 的余数r。
- 将 a 设置为 b,将b 设置为 r。
- 重复步骤 2 和 3,直到 a=0b=0,此时的 a 即为两数的最大公约数。
function gcd(a,b){
while(b!=0){
let temp = b
b = a%b
a = temp
}
return a
}
117. 的幂
给你一个整数 n,请你判断该整数是否是 2 的幂次方。如果是,返回 true ;否则,返回 false 。如果存在一个整数 x 使得 n == 2x ,则认为 n 是 2 的幂次方。
二进制 的2 10, 2-1 01; 4 , 100 4-1 11。
对于任何大于 0 的整数 *n*,如果 *n* 是22 的幂次方,那么它的二进制表示中有且仅有一个 1,并且在这个 1 之后全是 00
下面是按位与运算的基本逻辑:如果有0就是0
0 & 0 = 0
0 & 1 = 0
1 & 0 = 0
1 & 1 = 1
按位或的基本逻辑: 如果有1就是1
0 | 0 = 0
0 | 1 = 1
1 | 0 = 1
1 | 1 = 1
var isPowerOfTwo = function(n) {
if(n<=0){return false}
return (n&n-1) == 0
};
手写
1.手写实现new
function myNew(constructor, ...args) {
// 步骤1: 创建一个空对象,并且这个对象继承自构造函数的prototype属性。
const obj = Object.create(constructor.prototype);
// 步骤2: 将构造函数的作用域赋给新对象(因此this就指向了这个新对象)。
const result = constructor.apply(obj, args);
// 步骤3: 如果构造函数返回了一个对象,则返回这个对象;否则,返回刚创建的新对象。
return result instanceof Object ? result : obj;
}
2.手写 instanceof 方法
function myInstanceof(left, right) {
// 获取对象的原型
let proto = Object.getPrototypeOf(left);
// 获取构造函数的 prototype 对象
const prototype = right.prototype;
// 遍历原型链
while (proto) {
// 检查构造函数的 prototype 是否出现在实例对象的原型链上
if (proto === prototype) {
return true;
}
// 沿原型链向上移动
proto = Object.getPrototypeOf(proto);
}
// 如果没有找到,返回 false
return false;
}
3.手写promise
class MyPromise {
constructor(executor) {
this.state = 'pending'; // Promise的初始状态
this.value = undefined; // 成功时的值
this.reason = undefined; // 失败时的原因
// 成功
const resolve = (value) => {
if (this.state === 'pending') {
this.state = 'fulfilled';
this.value = value;
this.onFulfilledCallbacks.forEach(fn => fn());
}
};
// 失败
const reject = (reason) => {
if (this.state === 'pending') {
this.state = 'rejected';
this.reason = reason;
this.onRejectedCallbacks.forEach(fn => fn());
}
};
try {
executor(resolve, reject);
} catch (error) {
reject(error);
}
}
// 存储成功和失败的回调
onFulfilledCallbacks = [];
onRejectedCallbacks = [];
then(onFulfilled, onRejected) {
// 在then中根据状态执行相应操作
if (this.state === 'fulfilled') {
onFulfilled(this.value);
}
if (this.state === 'rejected') {
onRejected(this.reason);
}
// 处理异步
if (this.state === 'pending') {
this.onFulfilledCallbacks.push(() => {
onFulfilled(this.value);
});
this.onRejectedCallbacks.push(() => {
onRejected(this.reason);
});
}
}
}
4. 手写 Promise.all
function promiseAll(promises) {
return new Promise((resolve, reject) => {
if (!Array.isArray(promises)) {
return reject(new TypeError('arguments must be an array'));
}
let resolvedCounter = 0;
let promiseNum = promises.length;
let resolvedValues = new Array(promiseNum);
for (let i = 0; i < promiseNum; i++) {
// 直接调用 Promise.resolve 确保每个元素都被当作 Promise 对待
Promise.resolve(promises[i]).then(value => {
resolvedCounter++;
resolvedValues[i] = value;
// 当所有元素都被解决后,Promise.all 返回的 promise 状态变为 resolved
if (resolvedCounter === promiseNum) {
return resolve(resolvedValues);
}
}, reason => {
// 任何一个元素被 reject 时,Promise.all 返回的 promise 状态变为 rejected
return reject(reason);
});
}
// 如果输入的是空数组,直接解决
if (promiseNum === 0) {
resolve(resolvedValues);
}
});
}
5.手写 Promise.race
function promiseRace(promises) {
return new Promise((resolve, reject) => {
// 检查输入是否为数组
if (!Array.isArray(promises)) {
return reject(new TypeError('arguments must be an array'));
}
// 遍历所有的 Promise 对象
for (let i = 0; i < promises.length; i++) {
// 对每个 Promise 使用 Promise.resolve() 包装以确保它们是 Promise 对象
// 然后使用 .then 方法订阅其解决或拒绝状态
Promise.resolve(promises[i]).then(resolve, reject);
}
});
}
6.手写防抖函数
function debounce(fn, delay) {
let timeoutID = null;
return function(...args) {
// 如果此前已经设置了延时调用,则取消之前的调用
if (timeoutID) {
clearTimeout(timeoutID);
}
// 设置一个新的延时调用
timeoutID = setTimeout(() => {
fn.apply(this, args);
}, delay);
};
}
7.手写节流函数
function throttle(fn, interval) {
let lastTime = 0;
return function(...args) {
const now = new Date().getTime();
if (now - lastTime > interval) {
lastTime = now;
fn.apply(this, args);
}
};
}
8.手写类型判断函数
function getType(value) {
// 首先排除 null 的情况
if (value === null) {
return "Null";
}
// 排除非对象类型的情况
else if (typeof value !== "object") {
return typeof value;
}
// 对于 Array, Function, RegExp 等,使用 Object.prototype.toString
else {
return Object.prototype.toString.call(value).slice(8, -1);
}
}
9.手写 call 函数
Function.prototype.myCall = function(context = window, ...args) {
if (typeof this !== 'function') {
throw new TypeError('Type error');
}
// 避免与现有属性冲突,使用 Symbol
const fnSymbol = Symbol();
// this 指向调用 call 的函数
context[fnSymbol] = this;
// 执行函数
const result = context[fnSymbol](...args);
// 删除属性
delete context[fnSymbol];
// 返回执行结果
return result;
}
10.手写 apply 函数
Function.prototype.myApply = function(context = window, args = []) {
if (this === Function.prototype) {
return undefined; // 防止 Function.prototype.myApply() 直接调用
}
context = context || window;
const fnSymbol = Symbol(); // 使用 Symbol 确保唯一性
context[fnSymbol] = this; // 将调用函数设为对象的方法
let result;
// 判断 args 是否为有效数组
if (Array.isArray(args)) {
result = context[fnSymbol](...args); // 执行函数
} else {
throw new TypeError('CreateListFromArrayLike called on non-object');
}
delete context[fnSymbol]; // 删除刚才赋值的属性
return result; // 返回执行结果
};
11.手写 bind 函数
Function.prototype.myBind = function(context, ...args) {
if (this === Function.prototype) {
throw new TypeError('Error');
}
const _this = this; // 保存当前函数的引用
return function F(...bindArgs) {
// 处理函数使用 new 操作符调用的情况
if (this instanceof F) {
return new _this(...args, ...bindArgs);
}
// ***** context
return _this.apply(context, [...args, ...bindArgs]);
};
};
12.函数柯里化的实现
重点返回两层函数
一层记录
一层判断执行
function curry(fn) {
// 检查需要的参数数量 .length
const arity = fn.length;
// `nextCurried` 函数负责收集参数,直到参数数量足够,然后执行 `fn`
function nextCurried(prevArgs) {
return function(arg) {
// 合并之前收集的参数和当前参数
const args = [...prevArgs, arg];
// 如果参数数量足够,则执行 `fn`
if (args.length >= arity) {
return fn(...args);
}
// 否则,返回一个新的柯里化函数,继续收集参数
else {
return nextCurried(args);
}
};
}
// 开始柯里化过程,初始参数为空
return nextCurried([]);
}
13.实现深拷贝
function deepCopy(obj, hash = new WeakMap()) {
// 处理 null、undefined 和基本数据类型
if (obj === null || typeof obj !== 'object') return obj;
// 处理日期类型
if (obj instanceof Date) return new Date(obj);
// 处理 Map 类型
if (obj instanceof Map) return new Map([...obj]);
// 避免循环引用
if (hash.has(obj)) return hash.get(obj);
// 处理数组和对象,使用 Array.isArray() 检查是否为数组
const result = Array.isArray(obj) ? [] : {};
// 保存拷贝的对象,用于处理循环引用
hash.set(obj, result);
// 递归拷贝所有属性
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
result[key] = deepCopy(obj[key], hash);
}
}
// 返回拷贝后的对象
return result;
}
15.实现数组的扁平化
let arr = [1, [2, [3, 4, 5]]];
function flatten(arr) {
let result = [];
for(let i = 0; i < arr.length; i++) {
if(Array.isArray(arr[i])) {
result = result.concat(flatten(arr[i]));
} else {
result.push(arr[i]);
}
}
return result;
}
flatten(arr); // [1, 2, 3, 4,5]
16.发布订阅模式
class EventEmitter {
constructor() {
this.events = {};
}
// 实现订阅
on(type, callBack) {
if (!this.events[type]) {
this.events[type] = [callBack];
} else {
this.events[type].push(callBack);
}
}
// 删除订阅
off(type, callBack) {
if (!this.events[type]) return;
this.events[type] = this.events[type].filter((item) => {
return item !== callBack;
});
}
// 只执行一次订阅事件
once(type, callBack) {
function fn() {
callBack();
this.off(type, fn);
}
this.on(type, fn);
}
// 触发事件
emit(type, ...rest) {
this.events[type] &&
this.events[type].forEach((fn) => fn.apply(this, rest));
}
}
// 使用如下
// const event = new EventEmitter();
// const handle = (...rest) => {
// console.log(rest);
// };
// event.on("click", handle);
// event.emit("click", 1, 2, 3, 4);
// event.off("click", handle);
// event.emit("click", 1, 2);
// event.once("dbClick", () => {
// console.log(123456);
// });
// event.emit("dbClick");
// event.emit("dbClick");
17.数组去重
function uniqueArr(arr) {
return [...new Set(arr)];
}
18.数组扁平化
function flatter(arr) {
if (!arr.length) return;
return arr.reduce(
(pre, cur) =>
Array.isArray(cur) ? [...pre, ...flatter(cur)] : [...pre, cur],
[]
);
}
function flatter(arr) {
if (!arr.length) return;
while (arr.some((item) => Array.isArray(item))) {
arr = [].concat(...arr);
}
return arr;
}
19.寄生组合继承
function Parent(name) {
this.name = name;
this.say = () => {
console.log(111);
};
}
Parent.prototype.play = () => {
console.log(222);
};
function Children(name) {
Parent.call(this);
this.name = name;
}
Children.prototype = Object.create(Parent.prototype);
Children.prototype.constructor = Children;
// let child = new Children("111");
// // console.log(child.name);
// // child.say();
// // child.play();
20.实现有并行限制的 Promise 调度器
class Scheduler {
constructor(limit) {
this.queue = [];
this.maxCount = limit;
this.runCounts = 0;
}
add(time, order) {
const promiseCreator = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(order);
resolve();
}, time);
});
};
this.queue.push(promiseCreator);
}
taskStart() {
for (let i = 0; i < this.maxCount; i++) {
this.request();
}
}
request() {
if (!this.queue || !this.queue.length || this.runCounts >= this.maxCount) {
return;
}
this.runCounts++;
this.queue
.shift()()
.then(() => {
this.runCounts--;
this.request();
});
}
}
const scheduler = new Scheduler(2);
const addTask = (time, order) => {
scheduler.add(time, order);
};
addTask(1000, "1");
addTask(500, "2");
addTask(300, "3");
addTask(400, "4");
scheduler.taskStart();