哈希
两数之和
暴力解法
/**
* @param {number[]} nums
* @param {number} target
* @return {number[]}
*/
var twoSum = function(nums, target) {
for(let i =0;i<nums.length;i++){
let x1 = nums[i]
for(let j = 0 ; j<nums.length;j++){
if(i!=j){
let x2 = nums[j]
if(x1+x2==target){
return [i,j]
}
}
}
}
};
Map法
var twoSum = function(nums, target) {
const map = new Map()
for(let i=0;i<nums.length;i++){
let key = target-nums[i]
if(map.has(key)){
return [map.get(key),i]
}else{
map.set(nums[i],i)
}
}
return []
};
字母异味词分组
/**
* @param {string[]} strs
* @return {string[][]}
*/
var groupAnagrams = function(strs) {
let map = new Map();
for (let i = 0; i < strs.length; i++) {
let word = strs[i];
// 使用数组的 sort 方法对字符串的字符进行排序,并将排序后的字符数组重新连接成字符串
let sorted_word = word.split('').sort().join('');
if (map.has(sorted_word)) {
map.get(sorted_word).push(word);
} else {
map.set(sorted_word, [word]);
}
}
// 将Map中的值(数组)转换为数组返回
return [...map.values()];
};
最长连续序列
var longestConsecutive = function(nums) {
if (nums.length === 0) return 0;
let set = new Set(nums);
let maxSize = 0;
for (let num of set) {
// 跳过非连续序列的起始数字(即那些前面有数字的数字)
if (!set.has(num - 1)) {
let currentNum = num;
let currentLength = 1;
// 遍历连续序列
while (set.has(currentNum + 1)) {
currentNum++;
currentLength++;
}
// 更新最大长度
maxSize = Math.max(maxSize, currentLength);
}
}
return maxSize;
};
双指针
移动零
解法1
先遍历数组,计算非零元素的数量。然后,在第二次遍历中,只将非零元素复制回数组的前部,并跳过0。
function moveZeroes(nums) {
let count = 0;
for (let num of nums) {
if (num !== 0) {
count++;
}
}
let writeIndex = 0;
for (let num of nums) {
if (num !== 0) {
nums[writeIndex++] = num;
}
}
// 填充剩余的0
for (let i = writeIndex; i < nums.length; i++) {
nums[i] = 0;
}
return nums
}
解法2
- 初始化两个指针,
slow
和fast
,都指向数组的第一个元素。 - 遍历数组(
fast
从头到尾),对于每个nums[fast]
:- 如果
nums[fast]
不为 0,则交换nums[slow]
和nums[fast]
,并将slow
向后移动一位(slow++
)。 - 如果
nums[fast]
为 0,则只移动fast
指针。
- 如果
- 遍历结束后,所有非零元素都已经被移动到了数组的前面,而后面的元素则自动成为了 0
var moveZeroes = function(nums) {
let slow=0
let fast = 0
for(fast;fast<nums.length;fast++){
if(nums[fast]!=0){
let swap = nums[slow]
nums[slow] = nums[fast]
nums[fast] = swap
slow++
}
}
return nums
};
盛最多水的容器
- 初始化左指针
left
为 0,右指针right
为height.length - 1
。 - 进入循环,当
left < right
时执行以下步骤:- 计算当前容器的面积
area = Math.min(height[left], height[right]) * (right - left)
。 - 更新最大面积
maxArea
(如果area
大于maxArea
)。 - 如果
height[left] < height[right]
,则left++
(移动左指针),否则right--
(移动右指针)。
- 计算当前容器的面积
- 退出循环后,
maxArea
就是所求的最大面积。
var maxArea = function(height) {
let left = 0
let right = height.length-1
let max = 0
while(left<right){
let h1 = height[left]
let h2 = height[right]
let area = Math.min(h1, h2) * (right - left)
max = Math.max(max, area);
if (h1 > h2) {
right--;
} else {
left++;
}
}
return max
};
三数之和
-
排序:首先对输入的数组
nums
进行排序。这是为了之后能够使用双指针技术有效地查找匹配项。 -
初始化:定义结果数组
res
来存储找到的三元组,以及定义指针i
、left
和right
分别用于遍历数组和作为双指针。 -
遍历数组:使用外层循环遍历数组
nums
,其中i
是第一个数的索引。为了避免找到重复的三元组,如果当前元素与前一个元素相同,则跳过。 -
双指针查找:在内层循环中,使用
left
和right
两个指针,在i
之后的元素中查找满足nums[i] + nums[left] + nums[right] == 0
的另外两个数。left
从i+1
开始,right
从数组末尾开始。 -
计算和并调整指针:计算当前三个数的和
sum
。如果sum
等于0,说明找到了一个满足条件的三元组,将其添加到结果数组中,并移动left
和right
指针,同时跳过所有相同的元素,以避免重复的三元组。如果sum
小于0,说明和太小,需要增加,因此将left
指针右移。如果sum
大于0,说明和太大,需要减小,因此将right
指针左移。 -
返回结果:当遍历完整个数组后,所有满足条件的三元组都已被添加到结果数组中,返回结果数组。
var threeSum = function(nums) {
// 首先对数组进行排序
nums.sort((a, b) => a - b);
const result = [];
let n = nums.length;
// 遍历数组(除了最后两个元素,因为需要至少两个元素与当前元素相加)
for (let i = 0; i < n - 2; i++) {
// 跳过所有与当前nums[i]相同的元素,以避免重复的三元组
if (i > 0 && nums[i] === nums[i - 1]) continue;
let left = i + 1;
let right = n - 1;
// 使用双指针在nums[i]之后的元素中查找
while (left < right) {
const sum = nums[i] + nums[left] + nums[right];
if (sum === 0) {
// 找到一个和为0的三元组,添加到结果中
result.push([nums[i], nums[left], nums[right]]);
// 移动左右指针,并跳过所有相同的元素
while (left < right && nums[left] === nums[left + 1]) left++;
while (left < right && nums[right] === nums[right - 1]) right--;
left++;
right--;
} else if (sum < 0) {
// 和小于0,说明需要增加和的值,因此左指针右移
left++;
} else {
// 和大于0,说明需要减少和的值,因此右指针左移
right--;
}
}
}
return result;
};
滑动窗口
无重复字符的最长字串
var lengthOfLongestSubstring = function(s) {
let window = {}
let left = 0
let right = 0
let res = 0
while(right<s.length){
var c = s[right]
right++
if(window[c]!=undefined){
window[c]++
}else{
window[c]= 1
}
while(window[c]>1){
let d = s[left]
left++
window[d]--
}
res = Math.max(res,right-left)
}
return res
};
找到字符串中所有字母异位词
var findAnagrams = function(s, p) {
// 记录窗口中的字符和需要凑齐的字符:
let window = new Map()
let need = new Map()
// 统计 t 中出现的元素以及它们的个数
for (var i = 0; i < p.length; i++) {
var c = p[i];
if (need.has(c)) {
need.set(c, need.get(c) + 1);
} else {
need.set(c, 1);
}
}
var left = 0, right = 0;
var valid = 0;
var res = []; // 记录结果
while(right<s.length){
var c = s[right];
right++;
// 进行窗口内数据的一系列更新
if (need.has(c)) {
if (window.has(c)) {
window.set(c, window.get(c) + 1);
} else {
window.set(c, 1);
}
if (need.get(c) === window.get(c))
valid++;
}
// 判断左侧窗口是否要收缩
while (right - left >= p.length) {
// 当窗口符合条件时,把起始索引加入 res
if (valid === need.size) {
res.push(left);
}
var d = s[left];
left++;
// 进行窗口内数据的一系列更新
if (need.has(d)) {
if (window.get(d) === need.get(d))
valid--;
window.set(d, window.get(d) - 1);
}
}
}
return res;
};
字串
和为K的字数组
var subarraySum = function(nums, k) {
let count = 0;
let sum = 0;
let prefixSum = new Map(); // 用于存储前缀和及其出现的次数
prefixSum.set(0, 1); // 初始化前缀和为0的次数为1,用于处理数组开头就存在和为k的情况
for (let num of nums) {
sum += num; // 计算当前前缀和
// 检查是否存在一个之前的前缀和,使得sum - 该前缀和 = k
if (prefixSum.has(sum - k)) {
count += prefixSum.get(sum - k); // 累加符合条件的子数组个数
}
// 更新当前前缀和出现的次数
if (!prefixSum.has(sum)) {
prefixSum.set(sum, 0);
}
prefixSum.set(sum, prefixSum.get(sum) + 1);
}
return count;
};
最小覆盖子串
var minWindow = function(s, t) {
// 哈希表 need 记录需要匹配的字符及对应的出现次数
// 哈希表 window 记录窗口中满足 need 条件的字符及其出现次数
let need = new Map();
let window = new Map();
for (let i = 0; i < t.length; i++) {
if (need.has(t[i])) {
need.set(t[i], need.get(t[i]) + 1);
} else {
need.set(t[i], 1);
}
}
let left = 0, right = 0;
let valid = 0;
// 记录最小覆盖子串的起始索引及长度
let start = 0, len = Infinity;
while (right < s.length) {
// c 是将移入窗口的字符
let c = s[right];
// 扩大窗口
right++;
// 进行窗口内数据的一系列更新
if (need.has(c)) {
if (window.has(c)) {
window.set(c, window.get(c) + 1);
} else {
window.set(c, 1);
}
if (window.get(c) === need.get(c)) {
valid++;
}
}
// 判断左侧窗口是否要收缩
while (valid === need.size) {
// 在这里更新最小覆盖子串
if (right - left < len) {
start = left;
len = right - left;
}
// d 是将移出窗口的字符
let d = s[left];
// 缩小窗口
left++;
// 进行窗口内数据的一系列更新
if (need.has(d)) {
if (window.get(d) === need.get(d)) {
valid--;
}
window.set(d, window.get(d) - 1);
}
}
}
// 返回最小覆盖子串
return len === Infinity ? '' : s.substr(start, len);
}
普通数组
最大字数组和
var maxSubArray = function(nums) {
let currentSum = nums[0]; // 当前子数组的和,初始化为数组的第一个元素
let maxSum = nums[0]; // 迄今为止的最大子数组和,也初始化为数组的第一个元素
// 从数组的第二个元素开始遍历
for (let i = 1; i < nums.length; i++) {
// 如果加入当前元素后子数组的和更大,则更新currentSum
// 否则,重新开始一个新的子数组(即currentSum只包含当前元素)
currentSum = Math.max(nums[i], currentSum + nums[i]);
// 更新迄今为止的最大子数组和
maxSum = Math.max(maxSum, currentSum);
}
// 返回最大子数组的和
return maxSum;
};
合并区间
-
排序:首先根据区间的起始位置对所有区间进行排序,这样可以确保相邻的区间在逻辑上是相邻的,便于后续合并。
-
遍历与合并:遍历排序后的区间数组,使用一个
window
变量来维护当前需要合并的区间。对于每个遍历到的区间item
,检查它是否与window
重叠。如果重叠,则更新window
的起始和结束位置以包含所有重叠的区间;如果不重叠,则将window
添加到结果数组res
中,并将item
设置为新的window
。 -
添加最后一个区间:遍历结束后,将最后一个
window
添加到结果数组res
中,因为在最后一次迭代中,如果最后一个window
没有与任何后续的区间重叠,它可能不会被添加到res
中。 -
返回结果:返回合并后的区间数组
res
。
var merge = function(intervals) {
let res = []
intervals.sort((a,b)=>{
return a[0]-b[0]
})
let window = intervals[0]
for(let i=0;i<intervals.length;i++){
let item = intervals[i]
// 判断是否与window重叠
if(item[0]>window[1]){
res.push(window)
window = item
}else{
window = [Math.min(window[0],item[0]),Math.max(window[1],item[1])]
}
}
res.push(window)
return res
};
轮转数组
方法一:使用额外数组
var rotate = function(nums, k) {
k = k % nums.length; // 确保 k 在数组长度的范围内
let temp = nums.slice(nums.length - k)
let temp2 = nums.slice(0,nums.length - k)
for(let i=0;i<k;i++){
nums[i]=temp[i]
}
for(let i=k;i<nums.length;i++){
nums[i]=temp2[i-k]
}
};
方法二:原地轮转
var rotate = function(nums, k) {
const n = nums.length;
k = k % n; // 处理k大于数组长度的情况
// 定义一个反转函数
function reverse(arr, start, end) {
while (start < end) {
[arr[start], arr[end]] = [arr[end], arr[start]]; // 使用解构赋值来交换元素
start++;
end--;
}
}
reverse(nums, 0, n - 1);
reverse(nums, 0, k - 1);
reverse(nums, k, n - 1);
};
除自身以外数组的乘积
var productExceptSelf = function(nums) {
let left = 1
let right = 1
let res = new Array(nums.length).fill(1)
for(let i=0;i<nums.length;i++){
res[i] = left;
left *= nums[i];
}
for(let i=nums.length-1;i>=0;i--){
res[i]*=right
right *= nums[i]
}
return res
};
矩阵
矩阵置零
找到第一个为0的元素的行和列,用那一行和那一列来记录其余行列是否应该清零,最后再处理记录元素的行和列
var setZeroes = function(matrix) {
let m = matrix.length
let n = matrix[0].length
let row = false
let col = false
for(let i=0;i<m;i++){
for(let j=0;j<n;j++){
if(matrix[i][j]==0){
if (i == 0) row = true;
if (j == 0) col = true;
matrix[0][j] = matrix[i][0] = 0;
}
}
}
for(let i=1;i<m;i++){
for(let j=1;j<n;j++){
if(matrix[0][j]==0 || matrix[i][0]==0 ){
matrix[i][j]=0
}
}
}
if (col) {
for (let i = 0; i < m; i++) matrix[i][0] = 0;
}
if (row) {
for (let j = 0; j < n; j++) matrix[0][j] = 0;
}
};
螺旋矩阵
var spiralOrder = function(matrix) {
if(matrix.length==0){
return []
}
let res = [] //记录结果
let arrow = [[0,1],[1,0],[0,-1],[-1,0]] //记录方向数组 左到右 上到下 右到左 下到上
let arrowIndex = 0 //记录当前方向
let m = matrix[0].length
let n = matrix.length
let i=0,j=0
for(let _=0;_<m*n;_++){
res.push(matrix[i][j])
matrix[i][j] = null
const [di,dj] = arrow[arrowIndex]
let ni = di+i
let nj = dj+j
if(ni<0||ni>=n||nj<0||nj>=m||matrix[ni][nj]==null){
arrowIndex = (arrowIndex+1)%4
}
i+=arrow[arrowIndex][0]
j+=arrow[arrowIndex][1]
}
return res
};
旋转图像
var rotate = function(matrix) {
const n = matrix.length;
for (let i = 0; i < n; i++) {
for (let j = i; j < n; j++) {
[matrix[i][j], matrix[j][i]] = [matrix[j][i], matrix[i][j]];
}
}
for (let i = 0; i < n; i++) {
matrix[i].reverse();
}
};
搜索二维矩阵Ⅱ
var searchMatrix = function(matrix, target) {
if(matrix.lenght== 0) {
return false;
}
let i = 0;
let j = matrix[0].lenght-1;
while(i < matrix.length && j >= 0) {
if(matrix[i][j] == target) {
return true;
}
if(matrix[i][j] < target) {
i++;
}
else {
j--;
}
}
return false;
};
链表
相交链表
var getIntersectionNode = function (headA, headB) {
let a = headA
let b = headB
while(a!=b){
if(a){
a = a.next
}else{
a = headB
}
if(b){
b = b.next
}else{
b = headA
}
}
return a
};
反转链表
var reverseList = function(head) {
let prev=null;
let curr=head;
let temp=null;
while(curr!==null){
temp=curr.next;
curr.next=prev;
prev=curr;
curr=temp;
}
return prev;
};
回文链表
var isPalindrome = function(head) {
let half=halflist(head); //找到找到前半部分的尾结点
let halfhead=reverlist(half.next); //反转后半部分链表 应该是half.next 不能是half
//判断是否回文
//定义双指针 一个从头开始 一个从中间开始
let p1=head;
let p2=halfhead;
let flag=true;
while(flag && p2!=null){
if(p1.val!=p2.val){
flag=false;
}
p1=p1.next;
p2=p2.next;
}
return flag;
};
//查找中间结点 定义快慢指针 查找中间结点
//如果是奇数结点 则为最中间 如果是偶数结点 则为前半部分最后一个结点
const halflist=(head)=>{
let fast=head;
let slow=head;
while(fast.next!=null&&fast.next.next!=null){
slow=slow.next;
fast=fast.next.next;
}
return slow;
}
//反转链表
const reverlist = (head)=>{
let prev=null;
let curr=head;
let temp=null;
while(curr!==null){
temp=curr.next;
curr.next=prev;
prev=curr;
curr=temp;
}
return prev;
}
环形链表
var hasCycle = function(head) {
let set = new Set()
let a = head
while(a!=null){
if(set.has(a)){
return true
}
set.add(a)
a = a.next
}
return false
};
双指针
var hasCycle = function(head) {
let low = head
let fast = head
while(fast!=null&&fast.next!=null){
low = low.next
fast = fast.next.next
if(low == fast){
return true
}
}
return false
};
环形链表Ⅱ
var detectCycle = function(head) {
let set = new Set()
let a = head
while(a!=null){
if(set.has(a)){
return a
}
set.add(a)
a = a.next
}
return a
};
双指针
var detectCycle = function(head) {
if (!head || !head.next) {
return null; // 空链表或只有一个节点的链表没有环
}
let slow = head;
let fast = head;
// 先判断是否存在环
while (fast && fast.next) {
slow = slow.next;
fast = fast.next.next;
if (slow === fast) {
break; // 快慢指针相遇,说明有环
}
}
// 如果没有环,fast会先到达链表末尾
if (!fast || !fast.next) {
return null;
}
// 将慢指针移回链表头部,快慢指针每次各移动一步
slow = head;
while (slow !== fast) {
slow = slow.next;
fast = fast.next;
}
// 当快慢指针再次相遇时,它们相遇的点就是环的起始点
return slow;
};
合并两个升序链表
var mergeTwoLists = function (l1, l2) {
const prehead = new ListNode();
let res=prehead
while (l1 != null && l2 != null) {
if (l1.val < l2.val) {
res.next = l1
l1 = l1.next
} else {
res.next = l2
l2 = l2.next
}
res = res.next
}
res.next = l1 == null ? l2 : l1
return prehead.next
};
两数相加
var addTwoNumbers = function(l1, l2) {
let ret = 0; // 保存进位
let header = new ListNode(0); // 创建一个哑节点作为结果链表的头部
let res = header; // 指针,用于构建结果链表
while (l1 != null || l2 != null || ret > 0) {
let num1 = l1 ? l1.val : 0;
let num2 = l2 ? l2.val : 0;
let sum = num1 + num2 + ret;
// 创建新节点并添加到结果链表
let temp = new ListNode(sum % 10);
res.next = temp;
res = res.next;
// 更新进位
ret = Math.floor(sum / 10);
// 移动链表指针
if (l1 !== null) l1 = l1.next;
if (l2 !== null) l2 = l2.next;
if(ret==0&&(l1 == null || l2 == null)){
res.next = l1==null?l2:l1
return header.next
}
}
return header.next; // 返回哑节点的下一个节点
};
删除链表的倒数第N个结点
var removeNthFromEnd = function(head, n) {
//p q均指向head
var p = head,q = head;
//如果链表为空或者只有一个结点,删完之后只能为空
if(head == null || head.next == null) return null;
//让p先右移n次
for(let i = n;i > 0;i--){
p = p.next;
}
//p q同时右移,直到p只想最后一个结点
while(p != null && p.next != null){
q = q.next;
p = p.next;
}
//如果P为空,即要删除的是链表的第一个结点。
if(p == null){
head = q.next;
}else{ //不是删除第一个结点就直接用q删除next。。
q.next = q.next.next;
}
return head;
};
两两交换链表中的结点
var swapPairs = function(head) {
let dummy = new ListNode(0);
dummy.next = head;
if(dummy.next==null||dummy.next.next==null){
return head
}
let slow = dummy
let curl = dummy.next
let fast = dummy.next.next
while(curl!=null&&fast!=null){
slow.next = fast
curl.next = fast.next
fast.next = curl
slow = curl
if(!slow.next||!slow.next.next){
break
}
curl = slow.next
fast = slow.next.next
}
return dummy.next
};