Compare commits

..

No commits in common. "c2c64470b09c7b8d886d86ee1cf07cb981aa8d38" and "72ac2258de9b78a75d42eb3742972711db78e9a9" have entirely different histories.

20 changed files with 0 additions and 1348 deletions

View File

@ -1,148 +0,0 @@
/**
* https://leetcode.cn/problems/copy-list-with-random-pointer/?envType=study-plan-v2&envId=top-interview-150
* // Definition for a _Node.
* function _Node(val, next, random) {
* this.val = val;
* this.next = next;
* this.random = random;
* };
*/
/**
* @param {_Node} head
* @return {_Node}
*/
const copyRandomList = function (head) {
};
// Definition for a _Node.
// eslint-disable-next-line no-underscore-dangle
function _Node(val, next, random) {
this.val = val;
this.next = next;
this.random = random;
}
/*
遍历整个链表创建每一个节点的克隆节点并且使用map将它们直接联系起来原节点作为key新节点作为value,
遍历完毕之后再遍历一一遍原链表根据random_index和map来为可能列表添加random_index
*/
function f1(head) {
const map = new Map(); // 储存原链表和克隆链表直接的关系
const dummyHead = new _Node(); // 哑节点,用于构建克隆链表
let curClone = dummyHead;
let cur = head;
while (cur) {
// 创建克隆节点
curClone.next = new _Node(cur.val);
curClone = curClone.next;
// 将原节点和克隆节点映射
map.set(cur, curClone);
cur = cur.next;
}
// 遍历原节点根据原节点的信息为克隆节点添加random指针
cur = head;
curClone = dummyHead.next;
while (cur) {
if (cur.random) {
curClone.random = map.get(cur.random);
}
cur = cur.next;
curClone = curClone.next;
}
return dummyHead.next;
}
/*
f1 的优化版本首先创建克隆节点和映射之后再拼接克隆链表
*/
function f2(head) {
if (!head) return null;
const map = new Map(); // 储存原节点 => 克隆节点的映射
let cur = head;
// 第一次遍历:创建所有新节点,并放入 map 中
while (cur) {
map.set(cur, new _Node(cur.val));
cur = cur.next;
}
cur = head;
// 第二次遍历:设置新节点的 next 和 random 指针
while (cur) {
const cloneNode = map.get(cur);
cloneNode.next = map.get(cur.next) || null;
cloneNode.random = map.get(cur.random) || null;
cur = cur.next;
}
return map.get(head);
}
/*
直接再原节点中插入克隆节点那么原节点和克隆节点就存在了引用关系之后根据引用关系来设置random最后
分离原链表和克隆链表即可
*/
function f3(head) {
if (!head) return null;
// 创建克隆节点,并且把克隆节点放在原节点后面
let cur = head;
while (cur) {
// 将原节点之后的所有节点移动到克隆节点的后面再将克隆节点作为原节点的next
const clone = new _Node(cur.val);
clone.next = cur.next;
cur.next = clone;
cur = clone.next; // 遍历下一个原节点
}
// 更具原节点的random来设置克隆节点的random
cur = head;
while (cur) {
if (cur.random) {
cur.next.random = cur.random.next;
}
cur = cur.next.next; // 下一个原节点
}
// 分离节点
cur = head;
const cloneHead = head.next;
while (cur) {
const clone = cur.next;
cur.next = clone.next; // 原节点指向原本下一个节点
if (clone.next) {
clone.next = clone.next.next; // 克隆节点指向克隆节点的下一个节点
}
cur = cur.next;
}
return cloneHead;
}
/*
回溯加哈希表
*/
function f4(head, cacheMap = new Map()) {
if (!head) return null;
// 如果没有这个节点的克隆映射
if (!cacheMap.has(head)) {
const clone = new _Node(head.val);
cacheMap.set(head, clone);
// 克隆节点的下一个节点为当前节点的下一个节点的克隆节点,递归调用即可
clone.next = f4(head.next, cacheMap);
// random同理
clone.random = f4(head.random, cacheMap);
}
// 如果有克隆节点,直接返回
return cacheMap.get(head);
}

View File

@ -1,49 +0,0 @@
/**
* https://leetcode.cn/problems/linked-list-cycle/?envType=study-plan-v2&envId=top-interview-150
* Definition for singly-linked list.
* function ListNode(val) {
* this.val = val;
* this.next = null;
* }
*/
/**
* @param {ListNode} head
* @return {boolean}
*/
const hasCycle = function (head) {
};
/*
利用set来检测在遍历的过程中是否发生了重复如果发生了重复说明有环如果没有表示无环
*/
function f1(head) {
const seenNodes = new Set();
let curNote = head;
while (curNote !== null) {
if (seenNodes.has(curNote)) return true; // 之前遍历出现过,直接判定有环
seenNodes.add(curNote);
curNote = curNote.next;
}
return false; // 遍历了整个链表也没有发生重复,表示无环
}
/*
通过快慢指针来判断是否有环
*/
function f2(head) {
let slow = head;
let fast = head;
while (fast !== null && fast.next !== null) {
slow = slow.next;
fast = fast.next.next;
if (slow === fast) return true; // 如果快慢指针相遇,表示有环
}
return false; // 如果快指针走到了链表末尾,表示没有环
}

View File

@ -1,118 +0,0 @@
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} head
* @param {number} n
* @return {ListNode}
*/
const removeNthFromEnd = function (head, n) {
};
function ListNode(val, next) {
this.val = (val === undefined ? 0 : val);
this.next = (next === undefined ? null : next);
}
/*
思路首先统计列表的长度根据长度信息找到倒数第n个节点的前一个节点,把它删掉即可
*/
function f1(head, n) {
// 1.获取列表的长度
const len = getListLength(head);
// 2.创建哑节点,减少处理逻辑
const dummyHead = new ListNode(-1);
dummyHead.next = head;
// 3.找到要删除节点的前一个节点
let pre = dummyHead;
for (let i = 0; i < len - n; i++) {
pre = pre.next;
}
// 4. 删除这个节点
pre.next = pre.next.next;
return dummyHead.next;
}
/**
* 获取链表的节点个数
* @param {ListNode} head - 链表的头节点
* @return {number} - 链表的节点个数
*/
const getListLength = function (head) {
let count = 0; // 初始化计数器
let current = head; // 初始化当前节点为链表头
// 遍历整个链表,直到链表末尾
while (current !== null) {
count++; // 每遍历一个节点,计数器加一
current = current.next; // 移动到下一个节点
}
return count; // 返回链表的长度
};
/*
思路只需要找到要去除节点的前一个节点就能去掉这个节点所以核心问题就变成了
如何寻找这个节点直接利用快慢指针首先初始化两个指针在同一位置然后让快指针
往后走n步之后一同走直到快指针到达最后一个节点此时的慢指针指向的就是要删除
节点的前一个节点
*/
function f2(head, n) {
const dummyHead = new ListNode(-1); // 建立哑节点便于处理head
dummyHead.next = head;
let slow = dummyHead;
let fast = dummyHead;
// 1.快指针先走n+1步(夺走一部是因为从哑节点开始走)
for (let i = 0; i <= n; i++) {
fast = fast.next;
}
// 2.两个指针同时往后走如果fast已经是最后一个节点
while (fast) {
slow = slow.next;
fast = fast.next;
}
// 3.删除slow后面的那个节点
slow.next = slow.next.next;
// 4.返回处理后的链表
return dummyHead.next;
}
/*
利用栈f1中我们统计了链表的长度之后根据长度和n来找到要删除节点的前一个节点这样实际上遍历
了两遍可以直接将所有节点按照顺序压入栈中之后弹出栈顶的n个元素剩下的栈顶元素就是我们要操作
的pre节点
*/
function f3(head, n) {
const dummyHead = new ListNode(-1); // 建立哑节点便于处理head
dummyHead.next = head;
// 1.将所有节点压入栈中
const stack = [];
let cur = dummyHead;
while (cur) {
stack.push(cur);
cur = cur.next;
}
// 2.弹出栈顶的n个元素
for (let i = 0; i < n; i++) {
stack.pop();
}
// 3.操作栈顶元素,这个元素就是要删除元素的前一个节点
const prev = stack[stack.length - 1];
prev.next = prev.next.next;
// 4.返回修改后的链表
return dummyHead.next;
}

View File

@ -1,97 +0,0 @@
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} list1
* @param {ListNode} list2
* @return {ListNode}
*/
const mergeTwoLists = function (list1, list2) {
};
function ListNode(val, next) {
this.val = (val === undefined ? 0 : val);
this.next = (next === undefined ? null : next);
}
/*
遍历两个链表如果l1的值小于l2的值就创建一个新的节点把l1的值放入这个新节点中之后把这个新节点
拼接到l3中之后返回代表l3的头节点即可
*/
function f1(list1, list2) {
const dummyHead = new ListNode(0); // 哑节点,用于拼接链表
let current = dummyHead;
while (list1 !== null || list2 !== null) {
if (list1 && list2) {
if (list1.val <= list2.val) {
current.next = new ListNode(list1.val);
list1 = list1.next;
} else {
current.next = new ListNode(list2.val);
list2 = list2.next;
}
current = current.next;
} else if (list1 === null) { // 当list1为空时直接将list2剩余部分拼接即可
current.next = list2;
break;
} else if (list2 === null) { // 当list2为空时直接将list1剩余部分拼接即可
current.next = list1;
break;
}
}
return dummyHead.next;
}
/*
思路和上面的一致上面的效率之所以慢是因为花费了大量的时间在创建新节点上这个操作虽然直观但是
显得多余
*/
function f2(list1, list2) {
const dummyHead = new ListNode(0); // 哑节点,用于拼接链表
let current = dummyHead;
while (list1 !== null && list2 !== null) {
if (list1.val <= list2.val) {
current.next = list1;
list1 = list1.next;
} else {
current.next = list2;
list2 = list2.next;
}
current = current.next;
}
// 如果 list1 或者 list2 还有剩余节点,直接拼接到结果链表
if (list1 !== null) {
current.next = list1;
} else if (list2 !== null) {
current.next = list2;
}
return dummyHead.next;
}
/*
利用递归思考递归过程首先思考f3的作用f3的作用非常单一就是合并两个有序链表合并完成之后使其
仍然有序假设list1[0]比list2[0]那么list1[0]一定是合并后链表的头节点我们只需把list1[1:]和list2
继续做交给f3把合并后的头节点作为list1[0]的next,不就完成整个递归过程了嘛
*/
function f3(l1, l2) {
if (l1 === null) {
return l2; // 如果 l1 空了,返回 l2
} if (l2 === null) {
return l1; // 如果 l2 空了,返回 l1
} if (l1.val < l2.val) {
// 如果 l1 的值小于 l2则 l1 当前节点连接到后面的结果
l1.next = mergeTwoLists(l1.next, l2);
return l1; // 返回 l1表示 l1 的节点被连接到了合并后的链表中
}
// 否则l2 当前节点连接到后面的结果
l2.next = mergeTwoLists(l1, l2.next);
return l2; // 返回 l2表示 l2 的节点被连接到了合并后的链表中
}

View File

@ -1,85 +0,0 @@
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} head
* @param {number} k
* @return {ListNode}
*/
const reverseKGroup = function (head, k) {
};
function ListNode(val, next) {
this.val = (val === undefined ? 0 : val);
this.next = (next === undefined ? null : next);
}
/*
这个问题可以拆解成n个leetcode的子问题直接利用k把区间划分出来调用reverseBetween即可
*/
function f1(head, k) {
// 利用哑节点来处理头节点问题
const dummyHead = new ListNode(-1);
dummyHead.next = head;
let left = 2;
let right = left + k - 1;
const n = getListLength(dummyHead);
while (right <= n) {
reverseBetween(, left, right);
left = right + 1;
right = left + k - 1;
}
return dummyHead.next;
}
/**
*
* @param {*} head 头节点
* @param {*} left 要反转的区域的第一个节点
* @param {*} right 要反转的区域的最后一个节点
* @returns 返回区域反转后的节点
*/
const reverseBetween = function (head, left, right) {
const dummyHead = new ListNode(-1); // 创建一个哑节点,简化边界情况
dummyHead.next = head; // 哑节点的next指向链表头
let pre = dummyHead; // `pre` 用于指向 `left` 前一个节点
// 寻找 `left` 的前一个节点
for (let i = 0; i < left - 1; i++) {
pre = pre.next;
}
const cur = pre.next; // `cur` 指向要反转部分的第一个节点
// 从 `left` 到 `right` 反转链表部分
for (let i = 0; i < right - left; i++) {
const next = cur.next; // `next` 指向当前节点的下一个节点
cur.next = next.next; // 跳过 `next` 节点
next.next = pre.next; // 将 `next` 插入到 `cur` 前面
pre.next = next; // 更新 `pre.next` 为 `next`
}
return dummyHead.next; // 返回反转后的链表
};
/**
* 获取链表的节点个数
* @param {ListNode} head - 链表的头节点
* @return {number} - 链表的节点个数
*/
let getListLength = function (head) {
let count = 0; // 初始化计数器
let current = head; // 初始化当前节点为链表头
// 遍历整个链表,直到链表末尾
while (current !== null) {
count++; // 每遍历一个节点,计数器加一
current = current.next; // 移动到下一个节点
}
return count; // 返回链表的长度
};

View File

@ -1,92 +0,0 @@
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} l1
* @param {ListNode} l2
* @return {ListNode}
*/
const addTwoNumbers = function (l1, l2) {
};
/*
直接遍历两个链表列表的头节点代表的是各位后一个节点代表的是十位以此类推所以只需定义一个
变量表示进位即可把计算好的结果拼接成l3即可
*/
function ListNode(val, next) {
this.val = (val === undefined ? 0 : val);
this.next = (next === undefined ? null : next);
}
function f1(l1, l2) {
let carry = 0; // 进位
const dummyHead = new ListNode(0); // 哑节点,用于返回结果
let current = dummyHead; // 当前节点,用于构建求和之和的链表
// 遍历两个链表,直到,链表都为空,并且没有进位(只要有节点或者进位不等于0就需要创建新的求和节点)
while (l1 !== null || l2 !== null || carry !== 0) {
let sum = carry;
if (l1) {
sum += l1.val;
l1 = l1.next;
}
if (l2) {
sum += l2.val;
l2 = l2.next;
}
// 根据sum,计算出current节点的值和进位值
current.next = new ListNode(sum % 10);
current = current.next;
carry = Math.floor(sum / 10);
}
return dummyHead.next;
}
/*
将链表转换成字符串数字之后把字符串数字相加最后处理成一个新的列表
*/
function f2(l1, l2) {
let numStr1 = '';
let numStr2 = '';
// 将第一个链表反转并拼接为字符串
let cur1 = l1;
while (cur1 !== null) {
numStr1 = cur1.val + numStr1;
cur1 = cur1.next;
}
// 将第二个链表反转并拼接为字符串
let cur2 = l2;
while (cur2 !== null) {
numStr2 = cur2.val + numStr2;
cur2 = cur2.next;
}
// 将拼接的字符串转为数字并进行加法运算
const sum = BigInt(numStr1) + BigInt(numStr2); // 使用 BigInt 处理大数字
// 将求和结果转换为字符串
const sumStr = sum.toString();
// 将求和结果转换为链表
const dummyHead = new ListNode(0);
let current = dummyHead;
for (let i = sumStr.length - 1; i >= 0; i--) {
current.next = new ListNode(Number(sumStr[i]));
current = current.next;
}
return dummyHead.next; // 返回结果链表的头节点
}

View File

@ -1,48 +0,0 @@
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* https://leetcode.cn/problems/rotate-list/?envType=study-plan-v2&envId=top-interview-150
* @param {ListNode} head
* @param {number} k
* @return {ListNode}
*/
/*
思路观察发现旋转1就等于将链表首位相连从原头节点出发 n-2 ,之后断开这个节点后后面
的节点连接即可例如1->2->3,roate1就是3->1->2来一个长一点的例子1->2->3->4->5->6,我们假设
要roate4想象6->1是首位相连的,从原头节点1开始往后走n-4-1也就是1步到达2的位置这个时候记录
2.next作为返回的头节点并且把它断开就变成了3->4->5->5->1->2,这就是我们要的结果
*/
const rotateRight = function (head, k) {
};
function f1(head, k) {
if (head === null || head.next === null) return head; // 没有节点,或者只有一个节点,直接返回
// 1.遍历链表获取尾节点,和节点个数
let tail = head;
let len = 1;
while (tail.next) {
len++;
tail = tail.next;
}
// 2.把链表首尾相连
tail.next = head;
// 3.从head往后走n-k-1步找到要断开的位置
tail = head;
k %= len; // 当k比链表长度还长时需取模操作比如len=3,要旋转4,那么效果和旋转1是一样的
for (let i = 0, setp = len - k - 1; i < setp; i++) {
tail = tail.next;
}
// 4.保留tail的next作为新节点的头节点然后断开
const rHead = tail.next;
tail.next = null;
return rHead;
}

View File

@ -1,48 +0,0 @@
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} head
* @return {ListNode}
*/
const deleteDuplicates = function (head) {
};
function ListNode(val, next) {
this.val = (val === undefined ? 0 : val);
this.next = (next === undefined ? null : next);
}
/*
思路先建立哑节点用于处理头部重复的情况用cur来遍历整个链表如果cur.next和cur.next.next 的值
相等那么表示从cur.next开始发生了重复我们记录这个val从cur.next开始删除所有值等于val的节点如果
cur.next和cur.next.next不相等那么就没有重复将cur移动到下一个节点cur = cur.next
*/
function f1(head) {
if (!head) {
return head;
}
const dummyHead = new ListNode(-1);
dummyHead.next = head;
let cur = dummyHead;
let curVal; // 保存重复节点的val
// 从哑节点开始查看next和next.next是否相等
while (cur.next && cur.next.next) {
if (cur.next.val === cur.next.next.val) {
curVal = cur.next.val;
// 检测cur.next的值是否等于curVal,如果等于就删除这个节点
while (cur.next && cur.next.val === curVal) {
cur.next = cur.next.next;
}
} else {
cur = cur.next;
}
}
return dummyHead.next;
}

View File

@ -1,46 +0,0 @@
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} head
* @param {number} x
* @return {ListNode}
*/
import { ListNode } from './linked-list-tools';
const partition = function (head, x) {
};
/*
思路遍历整个链表把小于x的值连接到一个叫smallHead的哑节点上把大于等于x的值连接到一个叫largeHead的哑节点上
之后把smallHead整个链表和largeHead整个链表连接起来就行了
*/
function f1(head, x) {
let small = new ListNode(0);
let large = new ListNode(0);
const smallHead = small;
const largeHead = large;
// 遍历整个head表示的列表
while (head) {
if (head.val < x) {
small.next = head;
small = small.next;
} else {
large.next = head;
large = large.next;
}
head = head.next;
}
// 连接两个链表
small.next = largeHead.next;
large.next = null; // 思考1->2->3->1, x=2 这种情况 samllHead:1->1, largeHead:2->3->1,这个时候3->1是不应该存在的
return smallHead.next;
}

View File

@ -1,157 +0,0 @@
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* https://leetcode.cn/problems/reverse-linked-list-ii/?envType=study-plan-v2&envId=top-interview-150
* @param {ListNode} head
* @param {number} left
* @param {number} right
* @return {ListNode}
*/
const reverseBetween = function (head, left, right) {
};
/*
思路: 首先找到left指向的节点和right指向的节点之后把left的前一个节点和right的后一个节点保存起来
之后反转left到right的所有节点再把原先得前后两部分拼接上即可
*/
function f1(head, left, right) {
if (left === right) return head; // 无需反转
let cur = head;
let i = 1;
let pre = null;
let leftNode = null;
let rightNode = null;
let next = null;
while (cur) {
if (i === left - 1) { // 寻找left的前一个节点
pre = cur;
} else if (i === left) { // 寻找left节点
leftNode = cur;
} else if (i === right) { // 寻找right 节点
rightNode = cur;
next = cur.next;
rightNode.next = null; // 将链表截断
break; // 退出循环
}
cur = cur.next;
i++;
}
// 从left到right开始反转
const rHead = reverseLink(leftNode); // rHead和RightNode指向了同一个节点
if (pre) {
pre.next = rHead;
} else {
head = rightNode;
}
leftNode.next = next;
return head;
}
/*
反转一个链表使用递归
*/
function reverseLink(head) {
// 基本情况:如果链表为空或只有一个节点,直接返回
if (!head || !head.next) return head;
// 递归反转后续链表
const rHead = reverseLink(head.next);
// 当前节点的下一个节点的 next 指向当前节点,反转链表
head.next.next = head;
// 断开当前节点与下一个节点的连接,防止形成环
head.next = null;
return rHead;
}
/*
反转一个链表使用迭代
*/
const reverseLinkedList = (head) => {
let pre = null;
let cur = head;
while (cur) {
const next = cur.next;
cur.next = pre;
pre = cur;
cur = next;
}
};
function ListNode(val, next) {
this.val = (val === undefined ? 0 : val);
this.next = (next === undefined ? null : next);
}
/*
优化思路和上面是一样的利用哑节点来优化减少边界判断情况
*/
function f2(head, left, right) {
// left在等于1时和大于1时处理方式不同利用哑节点来减少判断情况
const dummyHead = new ListNode(-1);
dummyHead.next = head;
// 1.寻找left节点的前一个节点pre
// 这里直接使用for循环从dummyHead的位置走left-1步即可
let pre = dummyHead;
for (let i = 0; i < left - 1; i++) {
pre = pre.next;
}
// 2.从pre的位置往后走 right - Left + 1步即可找到right指向的节点
let rightNode = pre;
for (let i = 0; i < right - left + 1; i++) {
rightNode = rightNode.next;
}
// 3.将链表从left到right的位置切断
const leftNode = pre.next;
const surr = rightNode.next;
pre.next = null;
rightNode.next = null;
// 4.反转切断的这一部分
reverseLinkedList(leftNode);
// 5.接回原来的链表中
pre.next = rightNode;
leftNode.next = surr;
return dummyHead.next;
}
/*
直接在链表上面操作把要反转的区域
*/
function f3(head, left, right) {
const dummyHead = new ListNode(-1); // 创建一个哑节点,简化边界情况
dummyHead.next = head; // 哑节点的next指向链表头
let pre = dummyHead; // `pre` 用于指向 `left` 前一个节点
// 寻找 `left` 的前一个节点
for (let i = 0; i < left - 1; i++) {
pre = pre.next;
}
const cur = pre.next; // `cur` 指向要反转部分的第一个节点
// 从 `left` 到 `right` 反转链表部分
for (let i = 0; i < right - left; i++) {
const next = cur.next; // `next` 指向当前节点的下一个节点
cur.next = next.next; // 跳过 `next` 节点
next.next = pre.next; // 将 `next` 插入到 `cur` 前面
pre.next = next; // 更新 `pre.next` 为 `next`
}
return dummyHead.next; // 返回反转后的链表
}

View File

@ -1,27 +0,0 @@
/**
* 获取链表的节点个数
* @param {ListNode} head - 链表的头节点
* @return {number} - 链表的节点个数
*/
export const getListLength = function (head) {
let count = 0; // 初始化计数器
let current = head; // 初始化当前节点为链表头
// 遍历整个链表,直到链表末尾
while (current !== null) {
count++; // 每遍历一个节点,计数器加一
current = current.next; // 移动到下一个节点
}
return count; // 返回链表的长度
};
/**
* 链表节点
* @param {*} val
* @param {*} next
*/
export function ListNode(val, next) {
this.val = (val === undefined ? 0 : val);
this.next = (next === undefined ? null : next);
}

View File

@ -1,38 +0,0 @@
/**
* https://leetcode.cn/problems/summary-ranges/?envType=study-plan-v2&envId=top-interview-150
* @param {number[]} nums
* @return {string[]}
*/
const summaryRanges = function (nums) {
};
/*
思路定义两个指针一个指针表示区间的开头一个表示区间的结尾用left和right表示right扫描连续区间如果发现不
再连续就把left和right表示的连续区间加入到结果集
*/
function f1(nums) {
const result = [];
let left = 0;
while (left < nums.length) {
let right = left;
// 扫描连续区间
while (right + 1 < nums.length && nums[right + 1] === nums[right] + 1) {
right++;
}
// 查看left和right是否相等如果相等则这个区间只有一个数否则是一个完整的区间需要用left->right表示
if (left === right) {
result.push(`${nums[left]}`);
} else {
result.push(`${nums[left]}->${nums[right]}`);
}
// 区间统计完毕,统计下一个区间
left = right + 1;
}
return result;
}

View File

@ -1,38 +0,0 @@
/**
* https://leetcode.cn/problems/minimum-number-of-arrows-to-burst-balloons/?envType=study-plan-v2&envId=top-interview-150
* @param {number[][]} points
* @return {number}
*/
const findMinArrowShots = function (points) {
};
/*
将points按照start排序从第一个point开始遍历所射箭的位置取第一个point的end之后查看第二个point
如果第二个point的end比第一个point的end小就把箭的x坐标移动到第二个point的end这样就能保证之前气球
引爆的情况下也能引爆当前气球可以做到箭的作用的最大化如果之前最小的end比当前point还小则表明需要
新增一直箭
*/
function f1(points) {
if (points.length === 0) return 0;
// 按照气球的左边界进行排序
points.sort((a, b) => a[0] - b[0]);
let arrows = 1; // 初始化箭的数量
let arrowX = points[0][1]; // 第一个箭的位置是第一个气球的右边界
for (let i = 1; i < points.length; i++) {
// 如果当前气球的左边界大于箭的位置,说明需要新的箭
if (arrowX < points[i][0]) {
arrows++; // 增加箭
arrowX = points[i][1]; // 更新箭的位置为当前气球的右边界
} else {
// 否则,箭可以继续覆盖当前气球,更新箭的位置为当前气球的右边界(确保尽量覆盖更多气球)
arrowX = Math.min(arrowX, points[i][1]);
}
}
return arrows;
}

View File

@ -1,26 +0,0 @@
/**
* https://leetcode.cn/problems/merge-intervals/?envType=study-plan-v2&envId=top-interview-150
* @param {number[][]} intervals
* @return {number[][]}
*/
const merge = function (intervals) {
};
/*
思路先对intervals按照左区间进行排序之后遍历所有区间如果当前区间在result中不存在就把它加入到
reslut中如果result中存在区间并且result的最后一个区间的右区间小于当前区间的左区间直接把当前区间加入
到reslut否则合并区间右边界取两个区间中较大的值
*/
function f1(intervals) {
intervals.sort((a, b) => a[0] - b[0]); // 按照左区间从小到大排序
const result = [];
for (let i = 0; i < intervals.length; i++) {
if (result.length === 0 || result[result.length - 1][1] < intervals[i][0]) {
result.push(intervals[i]);
} else {
result[result.length - 1][1] = Math.max(result[result.length - 1][1], intervals[i][1]);
}
}
return result;
}

View File

@ -1,45 +0,0 @@
/**
* http://leetcode.cn/problems/insert-interval/?envType=study-plan-v2&envId=top-interview-150
* @param {number[][]} intervals
* @param {number[]} newInterval
* @return {number[][]}
*/
const insert = function (intervals, newInterval) {
};
/*
假设我们插入的区间是[left, right][start, end]表示当前元素的左右区间由于intervals的所有区间都是
按照左区间排序的当end小于left时表明新插入的区间和当前区间没有重合没必要合并就把当前区间存入result
如果当start大于right时表明这两个区间没有重合也不需要合并其他情况需要合并区间合并的区间为[min(start,left), max(end, right)]
*/
function f1(intervals, newInterval) {
const result = [];
let i = 0;
const n = intervals.length;
// 1. 插入所有与 newInterval 不重叠的区间
while (i < n && intervals[i][1] < newInterval[0]) {
result.push(intervals[i]);
i++;
}
// 2. 合并重叠的区间
while (i < n && intervals[i][0] <= newInterval[1]) {
newInterval[0] = Math.min(newInterval[0], intervals[i][0]);
newInterval[1] = Math.max(newInterval[1], intervals[i][1]);
i++;
}
// 3. 将合并后的 newInterval 插入到结果中
result.push(newInterval);
// 4. 插入剩余的所有区间
while (i < n) {
result.push(intervals[i]);
i++;
}
return result;
}

View File

@ -1,114 +0,0 @@
/**
* https://leetcode.cn/problems/evaluate-reverse-polish-notation/?envType=study-plan-v2&envId=top-interview-150
* @param {string[]} tokens
* @return {number}
*/
const evalRPN = function (tokens) {
};
/*
根据逆波兰表达式的规则我们只需要使用一个栈来存操作数即可遍历tokens如果遇到的是 '+' '-' '*' '/'
这些操作符直接弹出两个元素right和left之和拿left opt right之后把计算的结果放入栈中继续此操作
在最后栈中只会右一个元素这个元素就是整个波兰表达式的结果
*/
function f1(tokens) {
const stack = [];
let left; let
right;
for (let i = 0; i < tokens.length; i++) {
if (tokens[i] === '+') {
right = stack.pop();
left = stack.pop();
stack.push(left + right);
} else if (tokens[i] === '-') {
right = stack.pop();
left = stack.pop();
stack.push(left - right);
} else if (tokens[i] === '*') {
right = stack.pop();
left = stack.pop();
stack.push(left * right);
} else if (tokens[i] === '/') {
right = stack.pop();
left = stack.pop();
let result = left / right;
if (result >= 0) {
result = Math.floor(result);
} else {
result = Math.ceil(result);
}
stack.push(result);
} else {
stack.push(+tokens[i]); // 转换成number
}
}
return stack.pop();
}
/*
优化上面的分支right = stack.pop();left = stack.pop(); 明显冗余所以先判断这个token是否是操作符
如果不是操作符那么它一定是数字字符就把它转换成数组压入栈中如果是操作符就先弹出两个操作数计数之后把它
压入栈中在js中触发要特殊处理因为js不像其他语言会把小数部分去掉
*/
function f2(tokens) {
const stack = [];
const n = tokens.length;
for (let i = 0; i < n; i++) {
const token = tokens[i];
if (token === '+' || token === '-' || token === '*' || token === '/') {
// 先弹出两个操作数
const right = stack.pop();
const left = stack.pop();
// 根据具体操作符进行具体操作
if (token === '+') {
stack.push(left + right);
} else if (token === '-') {
stack.push(left - right);
} else if (token === '*') {
stack.push(left * right);
} else {
// 除法操作要特殊处理
stack.push(left / right > 0 ? Math.floor(left / right) : Math.ceil(left / right));
}
} else {
stack.push(parseInt(token, 10));
}
}
return stack.pop();
}
/*
思路和上面一致但是不使用栈而是使用一个数组来储存要操作的数用一个下标指向数组的最后一个元素
可以理解成栈顶的元素波兰表达式的长度n一定为奇数所以除去操作数应该有n+1/2个数所以数组的长度
设置为n+1/2
*/
function f3(tokens) {
const n = tokens.length;
const optNums = new Array(Math.floor((n + 1) / 2)).fill(0);
let index = -1; // 指向optNums中最后一个元素
for (let i = 0; i < n; i++) {
const token = tokens[i];
if (token === '+') {
index--;
optNums[index] += optNums[index + 1];
} else if (token === '-') {
index--;
optNums[index] -= optNums[index + 1];
} else if (token === '*') {
index--;
optNums[index] *= optNums[index + 1];
} else if (token === '/') {
index--;
const result = optNums[index] / optNums[index + 1];
optNums[index] = result > 0 ? Math.floor(result) : Math.ceil(result);
} else {
// token 为数字将它存入optNums中
index++;
optNums[index] = parseInt(token, 10);
}
}
return optNums[index];
}

View File

@ -1,48 +0,0 @@
const MinStack = function () {
this.data = []; // 内部使用数组维护栈元素
this.minstack = [Infinity]; // 利用栈结构维护最小元素
};
/**
* @param {number} val
* @return {void}
*/
MinStack.prototype.push = function (val) {
// 判断当前插入的元素是否比栈的最小元素还小
if (val < this.minstack[this.minstack.length - 1]) this.minstack.push(val);
this.data.push(val);
};
/**
* @return {void}
*/
MinStack.prototype.pop = function () {
// 判断栈顶的元素是否是最小元素,如果是,最小元素栈顶的元素也要删除
if (this.data[this.data.length - 1] === this.minstack[this.minstack.length - 1]) {
this.minstack.pop();
}
if (this.data.length) this.data.pop();
};
/**
* @return {number}
*/
MinStack.prototype.top = function () {
if (this.data.length) return this.data[this.data.length - 1];
};
/**
* @return {number}
*/
MinStack.prototype.getMin = function () {
return this.minstack[this.minstack.length - 1];
};
/**
* Your MinStack object will be instantiated and called as such:
* var obj = new MinStack()
* obj.push(val)
* obj.pop()
* var param_3 = obj.top()
* var param_4 = obj.getMin()
*/

View File

@ -1,34 +0,0 @@
/**
* @param {string} s
* @return {boolean}
*/
const isValid = function (s) {
};
/*
题目所给的测试用例只包含'('')''{''}''['']'这些字符如果是一个有效字符则说明它一定是一对一对的出现
也就是说s的长度是偶数如果是奇数就一定存在一个括号没有闭合倘若是偶数个我们需要在遇到右括号时检查它的前一个
括号是否是对应的左括号如果是的话则表明这是一对合理的括号之后检查后一个括号以次类推
*/
function f1(s) {
// 如果长度不是偶数,一定不能正常闭合
if (s.length % 2 !== 0) return false;
// 括号的映射表
const map = new Map([
[')', '('],
[']', '['],
['}', '{'],
]);
const stack = []; // 利用栈结构来检查括号是否整除闭合
for (const brack of s) {
if (map.has(brack)) {
// 如果栈为空或者栈顶括号不是对应的括号表明不能正常闭合返回false
if (!stack.length || stack[stack.length - 1] !== map.get(brack)) return false;
stack.pop();// 如果正常闭合,从栈中去掉这个左括号
} else {
stack.push(brack);
}
}
return !stack.length;
}

View File

@ -1,55 +0,0 @@
/**
* https://leetcode.cn/problems/basic-calculator/?envType=study-plan-v2&envId=top-interview-150
* @param {string} s
* @return {number}
*/
const calculate = function (s) {
};
/*
定义一个sign变量表示数前面的符号当数字收集完毕就使用这个符号操作这个变量的默认值为+1原因很简单
表达式为一个整体可以理解为一个括号这个括号之前隐式的包含了一个+比如表达式 -1 + 2 -(1-2),可以
把它看成+(-1 + 2 -(1-2)),我们还需要使用一个栈结构来保存每一层括号表示的符号以上面的表达式为例第一个
括号显然为+,所以栈结构为[+1],之后这个括号下一级的数都会受它影响比如括号里面的-1会变为1*-11*+2
保持不变当遇到"("时需要把这一层的符号压入栈中栈结构为[+1,-1]第二级的括号里面的数都会受他影响当遇到
)表示这一层级的数都操作完必了栈中无需保存这一层级的符号直接弹出即可这样一次遍历即可得到结果
*/
function f1(s) {
const opt = [1];// 保存每一层括号的符号,初始表达式可以看成一个整体,所以默认值为[1]
let sign = 1; // 收集数字之后,数字前面的符号
let ret = 0; // 计数求和的结果
const n = s.length; // 字符串的长度,用于控制遍历
let i = 0; // 遍历下标
while (i < n) {
if (s[i] === ' ') { // 遇到空格直接跳过
i++;
} else if (s[i] === '+') { // 如果遇到加号根据当前层级的符号更新sign
sign = opt[opt.length - 1];
i++;
} else if (s[i] === '-') { // 如果遇到减号,和上面同理
sign = -opt[opt.length - 1];
i++;
} else if (s[i] === '(') { // 遇到左括号表明层级加深把sign压入栈中即可
opt.push(sign);
i++;
} else if (s[i] === ')') { // 遇到右括号,表明当前层级处理完毕,把当前层级的符号弹出即可
opt.pop();
i++;
} else {
// 上面的情况处理完毕,在这里只有一种情况,那么就是数字本身,数字可能是多位数,所以
// 需要一次遍历来收集
let num = 0; // 用于收集数字
while (i < n && s[i] >= '0' && s[i] <= '9') {
num = num * 10 + (s[i] - '0');
i++;
}
// 数字收集完毕通过它前面的符号sign和结果求和
ret += sign * num;
}
}
return ret;
}

View File

@ -1,35 +0,0 @@
/**
* https://leetcode.cn/problems/simplify-path/?envType=study-plan-v2&envId=top-interview-150
* @param {string} path
* @return {string}
*/
const simplifyPath = function (path) {
};
/*
首先通过'/'来分割path,我们会得到如下的字符串目录名"."当前目录".."上级目录如果遇到的是文件名
就把文件名压入栈stack中如果遇到的是.或者空字符无需做任何操作如果遇到的是".."则需要把当前stack栈顶
的目录弹出表示跳转到上级目录
*/
function f1(path) {
const paths = path.split('/'); // 使用/来分割路径
const stack = []; // 利用栈来处理路径
for (const p of paths) {
// if (p === '.' || p === '') continue;
// if (p === '..') {
// // 返回到上级目录
// if (stack.length) stack.pop();
// continue;
// }
// stack.push(p);
// 优化上面分支,实际上我们要做的就两件事情,第一:如果是".."表明要跳转到上级目录,如果一个合规
// 的目录名,就应该压入栈中
if (p === '..') {
if (stack.length) stack.pop();
} else if (p !== '' && p !== '.') {
stack.push(p);
}
}
return `/${stack.join('/')}`;
}