Jacky's blog Jacky's blog
首页
  • 编码专题
  • 深入浅出 Vite
  • 深入浅出 babel
  • 快速上手API
  • 深入浅出 react
  • Node

    • code-notebook
  • 状态管理

    • redux
  • 前端工程化

    • Wepack
  • React源码

    • React源码
  • 组件库封装

    • 组件库
  • 开发工具

    • Vscode 插件
  • 项目展示
  • 案例中心 (opens new window)
  • First Project
  • 基础算法题
  • 链表题
  • 动态规划
  • 双指针
  • 递归
  • 数据结构
  • 前端学习计划 (opens new window)
  • 技术随笔
  • 转载文章
  • 包管理工具
  • 前端学习周报
  • VSCode插件
  • Promise 专题
  • 函数技巧
  • React 专题
  • 配置文件

    • TSCONFIG-配置 (opens new window)
    • NGINX-配置 (opens new window)
    • 正则规则查询手册 (opens new window)
    • Lint 配置 (opens new window)
  • 教程

    • GIT-教程
    • NPM SCRIPTS-工作流 (opens new window)
    • DOCKER-教程 (opens new window)
    • LERNA-教程 (opens new window)
    • GIT-常用操作整理 (opens new window)
  • VSCode

    • LAUNCH.JSON (opens new window)
  • 指令

    • NPM 指令 (opens new window)
    • NVM 指令 (opens new window)
    • Nginx 指令 (opens new window)
    • YARN 指令 (opens new window)
    • PNPM 指令 (opens new window)
  • 库

    • FS-EXTRA 库 (opens new window)
    • NODE 库-PATH (opens new window)
  • 永远的神

    • 魔法师卡颂-自顶向下学 React 源码 (opens new window)
    • 全栈潇晨 (opens new window)
    • 博客-程序员山月-Daily (opens new window)
    • 淘系前端:冴羽 (opens new window)
  • 系列文章

    • 《图解HTTP》 (opens new window)
    • 《ES6标准入门》 (opens new window)
    • 《现代JavaScript教程》 (opens new window)
    • 《深入浅出Webpack》 (opens new window)
    • VSCode 插件系列:小茗同学 (opens new window)
    • JEST 教程 (opens new window)
    • 前端精读周刊:各种精读系列 (opens new window)
    • 一文吃透系列 (opens new window)
    • 图解 REACT 原理 (opens new window)
  • 实用网站

    • MDN (opens new window)
    • CAN I USE (opens new window)
    • TYPESCRIPT-ESLint-RULES (opens new window)
    • ESLint-RULES (opens new window)
    • FRONT-END TREND (opens new window)
    • NPM TREND (opens new window)
    • 在线分析 Node 依赖 (opens new window)
    • FIND NPM (opens new window)
    • CODE PEN (opens new window)
    • 印记中文 (opens new window)
    • TOOL.LU (opens new window)
    • 阮一峰-网道 (opens new window)
    • DIGITAL OCEAN (opens new window)
    • DEVDOCS.IO (opens new window)
    • JOI (opens new window)
  • 算法

    • 小浩算法 (opens new window)
    • LABULADONG 的算法小抄 (opens new window)
    • 力扣 SOLUTION (opens new window)
    • HACKER RANK (opens new window)
    • 代码随想录 (opens new window)
  • 博客系列

    • 美团大佬 (opens new window)
    • 蜡笔小伟 (opens new window)
    • 优秀博客1 (opens new window)
    • 优秀博客2-umi (opens new window)
    • 优质博客 (opens new window)
  • CSS

    • CSS-EASING 库 (opens new window)
    • ROUGH.JS (opens new window)
    • CSS 网站收集
    • UNOCSS (opens new window)
  • 前端

    • PROMISE (opens new window)
    • UNDERSCORE.JS (opens new window)
    • study with BGM (opens new window)
    • nginx【B站视频】 (opens new window)
    • 机器学习
    • Js基础
  • 掘金已购课程

    • 前端自动化测试精讲 (opens new window)
    • 深入浅出 Vite (opens new window)
    • 现代 Web 布局 (opens new window)
    • 前端算法与数据结构 (opens new window)
    • 基于 Vite 的 SSG 框架开发实战 (opens new window)
    • SSR 实战:官网开发指南 (opens new window)
    • WebGL 入门与实践 (opens new window)
    • 玩转 CSS 的艺术之美 (opens new window)
    • 前端调试通关秘籍 (opens new window)
    • React 进阶实践指南 (opens new window)
    • TypeScript 全面进阶指南 (opens new window)
    • 前端缓存技术与方案解析 (opens new window)
    • npm scripts 前端工作流 (opens new window)
    • Webpack5 核心原理与应用实践 (opens new window)
  • 购物车

    • 张鑫旭-技术写作指南 (opens new window)
    • 深入剖析 Node.js 底层原理 (opens new window)
    • 前端开发者的现代 C++ 课 (opens new window)
    • 从前端到全栈 (opens new window)
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Jacky Wang

行到水穷处,坐看云起时
首页
  • 编码专题
  • 深入浅出 Vite
  • 深入浅出 babel
  • 快速上手API
  • 深入浅出 react
  • Node

    • code-notebook
  • 状态管理

    • redux
  • 前端工程化

    • Wepack
  • React源码

    • React源码
  • 组件库封装

    • 组件库
  • 开发工具

    • Vscode 插件
  • 项目展示
  • 案例中心 (opens new window)
  • First Project
  • 基础算法题
  • 链表题
  • 动态规划
  • 双指针
  • 递归
  • 数据结构
  • 前端学习计划 (opens new window)
  • 技术随笔
  • 转载文章
  • 包管理工具
  • 前端学习周报
  • VSCode插件
  • Promise 专题
  • 函数技巧
  • React 专题
  • 配置文件

    • TSCONFIG-配置 (opens new window)
    • NGINX-配置 (opens new window)
    • 正则规则查询手册 (opens new window)
    • Lint 配置 (opens new window)
  • 教程

    • GIT-教程
    • NPM SCRIPTS-工作流 (opens new window)
    • DOCKER-教程 (opens new window)
    • LERNA-教程 (opens new window)
    • GIT-常用操作整理 (opens new window)
  • VSCode

    • LAUNCH.JSON (opens new window)
  • 指令

    • NPM 指令 (opens new window)
    • NVM 指令 (opens new window)
    • Nginx 指令 (opens new window)
    • YARN 指令 (opens new window)
    • PNPM 指令 (opens new window)
  • 库

    • FS-EXTRA 库 (opens new window)
    • NODE 库-PATH (opens new window)
  • 永远的神

    • 魔法师卡颂-自顶向下学 React 源码 (opens new window)
    • 全栈潇晨 (opens new window)
    • 博客-程序员山月-Daily (opens new window)
    • 淘系前端:冴羽 (opens new window)
  • 系列文章

    • 《图解HTTP》 (opens new window)
    • 《ES6标准入门》 (opens new window)
    • 《现代JavaScript教程》 (opens new window)
    • 《深入浅出Webpack》 (opens new window)
    • VSCode 插件系列:小茗同学 (opens new window)
    • JEST 教程 (opens new window)
    • 前端精读周刊:各种精读系列 (opens new window)
    • 一文吃透系列 (opens new window)
    • 图解 REACT 原理 (opens new window)
  • 实用网站

    • MDN (opens new window)
    • CAN I USE (opens new window)
    • TYPESCRIPT-ESLint-RULES (opens new window)
    • ESLint-RULES (opens new window)
    • FRONT-END TREND (opens new window)
    • NPM TREND (opens new window)
    • 在线分析 Node 依赖 (opens new window)
    • FIND NPM (opens new window)
    • CODE PEN (opens new window)
    • 印记中文 (opens new window)
    • TOOL.LU (opens new window)
    • 阮一峰-网道 (opens new window)
    • DIGITAL OCEAN (opens new window)
    • DEVDOCS.IO (opens new window)
    • JOI (opens new window)
  • 算法

    • 小浩算法 (opens new window)
    • LABULADONG 的算法小抄 (opens new window)
    • 力扣 SOLUTION (opens new window)
    • HACKER RANK (opens new window)
    • 代码随想录 (opens new window)
  • 博客系列

    • 美团大佬 (opens new window)
    • 蜡笔小伟 (opens new window)
    • 优秀博客1 (opens new window)
    • 优秀博客2-umi (opens new window)
    • 优质博客 (opens new window)
  • CSS

    • CSS-EASING 库 (opens new window)
    • ROUGH.JS (opens new window)
    • CSS 网站收集
    • UNOCSS (opens new window)
  • 前端

    • PROMISE (opens new window)
    • UNDERSCORE.JS (opens new window)
    • study with BGM (opens new window)
    • nginx【B站视频】 (opens new window)
    • 机器学习
    • Js基础
  • 掘金已购课程

    • 前端自动化测试精讲 (opens new window)
    • 深入浅出 Vite (opens new window)
    • 现代 Web 布局 (opens new window)
    • 前端算法与数据结构 (opens new window)
    • 基于 Vite 的 SSG 框架开发实战 (opens new window)
    • SSR 实战:官网开发指南 (opens new window)
    • WebGL 入门与实践 (opens new window)
    • 玩转 CSS 的艺术之美 (opens new window)
    • 前端调试通关秘籍 (opens new window)
    • React 进阶实践指南 (opens new window)
    • TypeScript 全面进阶指南 (opens new window)
    • 前端缓存技术与方案解析 (opens new window)
    • npm scripts 前端工作流 (opens new window)
    • Webpack5 核心原理与应用实践 (opens new window)
  • 购物车

    • 张鑫旭-技术写作指南 (opens new window)
    • 深入剖析 Node.js 底层原理 (opens new window)
    • 前端开发者的现代 C++ 课 (opens new window)
    • 从前端到全栈 (opens new window)
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 基础算法篇
  • 链表题
    • 83.删除排序链表中的重复元素
    • 92.反转链表 II
    • 19.删除链表的倒数第N个节点
    • 21.合并两个有序链表
    • 141.环形链表
    • 142.环形链表 II
    • 876.链表的中间结点
    • 206.反转链表
    • 24.两两交换链表中的节点
    • 2. 两数相加
    • 445.两数相加 II
    • 160.相交链表
  • 动态规划
  • 双指针
  • 递归专题
  • JS数据结构基础实现
  • LeetCode刷题
wangjiasheng
2021-01-01
目录

01.链表题

# 01.链表题

  1. 反转链表的写法
  2. 移动节点位置为m的写法:1 ≤ m ≤ n ≤ 链表长度
// head有可能被删掉,所以需要创建dummyNode节点
let dummyNode = new ListNode();
dummyNode.next = head;

let pre = dummyNode;
let cur = head

return dummyNode.next
1
2
3
4
5
6
7
8

新链表的固定写法

var dummyNode = new ListNode();
var cur = dummyNode;
cur.next = ...; // 保存操作
cur = cur.next; // 移动指针
1
2
3
4

赋值规律记忆:

// 对于链表有两个操作:更新指针 以及 断链/重联链
// 1. 更新指针:写法(等号前一定不带.next)
// cur = cur.next; pre = pre.next ; cur = cur.next.next

// 2.改变链表结构
// cur.next 含义是 cur的下一个节点是......
// (起始链头).next = 终止链尾
// cur.next = .... 此时只是链的重新连接,后续还需要指针更新
1
2
3
4
5
6
7
8

# 83.删除排序链表中的重复元素 (opens new window)

题解:

  • 链表基础题
  • 注意if条件中是否用到cur.next,需要放到while

伪代码:

// 先将head保存到cur中
let cur = head; 
while(cur !==null && cur.next !==null){
    if(条件){
        跳转节点
    }else{
        更新cur
    }
}
return head;
1
2
3
4
5
6
7
8
9
10
点击查看

参考代码:

var deleteDuplicates = function(head) {
    let cur = head;
    while(cur !== null && cur.next !== null){
        if(cur.val === cur.next.val){
            cur.next = cur.next.next
        }else{
            cur = cur.next;
        }
    }
    return head
};
1
2
3
4
5
6
7
8
9
10
11

# 92.反转链表 II (opens new window)

题解:

  • 反转链表,需要先将cur.next提前保存下来,最后需要赋值给cur

  • 需要额外两个变量保存住链表的位置:before、after

伪代码:

//1.判断head是否为空节点
//2.设置虚拟节点,pre -> dummyNode,cur -> head
const dummyNode = new ListNode();
dummyNode.next = head;
let pre,cur = ...;

//3.找before、after的位置
for(移动m-1步){更新cur、pre}
结束后,保存当前的cur和pre的指针所在的位置 --> before、after

//4.反转
for(移动n-m+1步){
    反转固定写法[核心:临时保存cur.next]
}
//5.处理before和after
//【这需要比较深入的分析,这里可以直接背下来就好了】
before.next = pre;
after.next = cur;

return dummyNode.next;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
点击查看

参考代码:

var reverseBetween = function(head, m, n) {
    if(!head){
        return head;
    }
    const dummyNode = new ListNode();
    dummyNode.next = head;
    let pre = dummyNode,cur = head;
    console.log(cur.val)
    for(let i=0;i<m-1;i++){
        pre = pre.next;
        cur = cur.next;
    }
    console.log(cur.val);
    let before = pre, after = cur;
    for(let i=0;i<n - m + 1;i++){
        bak = cur.next;
        cur.next = pre;
        pre = cur;
        cur = bak;
    }
    before.next = pre;
    after.next = cur;
    return dummyNode.next;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 19.删除链表的倒数第N个节点 (opens new window)

题解:

  • 使用双指针,让两者相隔n步。

    分析:若需要删除倒数第2个节点,则需要让指针移动到倒数第3个节点。

  • n1 和 n2 必须指向dummyNode【若只有1个节点,需要删除头结点,返回Null很困难】

伪代码:

dummyNode = new ListNode();
dummyNode.next = head;
let n1,n2 = dummyNode;
//1. 让两者相隔n+1步
for(循环n+1步){n1 = n1.next}
//2. 让n2移动到null
while(n2 !== null){
    更新n1和n2
}
//3. 删除节点
n1.next = n1.next.next;
//4. return dummyNode.next
1
2
3
4
5
6
7
8
9
10
11
12
点击查看

参考代码:

var removeNthFromEnd = function(head, n) {
    var dummy = new ListNode();
    dummy.next = head;
    var p1 = dummy;
    var p2 = dummy;
    // 让p2指针先走n+1步,让p2走到null,此时p1走到3
    for(let i=0; i<=n;i++){
        p2 = p2.next;
    };

    while(p2 !== null){
        p1 = p1.next;
        p2 = p2.next
    }
    p1.next = p1.next.next;
    return dummy.next
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 21.合并两个有序链表 (opens new window)

题解:

  • 首先该两链表是升序链表
  • 指定两个指针:l1和l2分别读取两个链表
  • 知识点:新链表的创建

伪代码:

let p = new ListNode();
// 将头结点保存一下
let dummyNode = p;

while(l1!==null && l2!==null){
    if(l1.val<l2.val){
       	将l1的节点压入新的链表中
        l1 = l1.next
    }else{
        否则,操作同上
    }
    新链表指针+1;
}

// 将剩余部分添加到新链表
if(l1 !==null){
    l1压入新链表
}else{
    l2压入新链表
}
// return dummyNode.next
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
点击查看

参考代码:

var mergeTwoLists = function(l1, l2) {
    // 使用一个新的指针:即工作区,类似于之前合并数组区间
    // dummy 作用:保存一下头结点
    let cur = new ListNode();
    let dummy = cur;

    while (l1 !== null && l2 !== null) {
        // && 并集,必须考虑并集外的情况
        if(l1.val<=l2.val){
            cur.next = l1;
            l1 = l1.next;
        }else{
            cur.next = l2;
            l2 = l2.next;
        }
        cur = cur.next;
    }
    // 并集外的情况
    if(l1 !==null){
        cur.next = l1;
    }else{
        cur.next =l2;
    }
    return dummy.next;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# 141.环形链表 (opens new window)

题解:

  • 知识点:快慢指针
  • while循环条件:fast !== null && fast.next !== null
    • slow = slow.next'
    • fast = fast.next.next

经典代码:

var hasCycle = function(head) {
    if(head === null){
        return false;
    }
    // 快慢指针
    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;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 142.环形链表 II (opens new window)

题解:

  • 核心知识点:快慢指针
  • 快慢指针的入口节点:在fast和slow相遇的时候,将fast指针再次移到头部,直至两者再此相遇。

经典代码:

var detectCycle = function(head) {
    var fast = head;
    var slow = head;
    while(fast !==null && fast.next !==null){
        fast = fast.next.next;
        slow = slow.next;
        if(slow == fast){
            break;
        }
    }
    // 循环外部存在两种情况:
    //1. slow或者fast相遇 
    //2. 此时fast指针是 null 或者 fast.next是 null
    if(fast == null || fast.next ==null){
        return null;
    }
    fast = head;
    while(fast!==slow){
        fast = fast.next;
        slow = slow.next;
    }
    return fast;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 876.链表的中间结点 (opens new window)

题解:

  • 知识点:利用快慢指针去寻找链表的中间结点。

经典代码:

var middleNode = function(head) {
    let slow = head;
    let fast = head;
    while(fast !==null && fast.next !== null){
        slow = slow.next;
        fast = fast.next.next;
    }
    return slow;
};
1
2
3
4
5
6
7
8
9

# 206.反转链表 (opens new window)

经典代码:

var reverseList = function(head) {
    let pre = null;
    let cur = head;
    while(cur !== null){
        bak = cur.next;
        cur.next = pre;
        pre = cur;
        cur = bak;
    }
    return pre;
};
1
2
3
4
5
6
7
8
9
10
11

# 24.两两交换链表中的节点 (opens new window)

题解:

  • 需要准备一个指针p指向dummyNode,p.next与p.next.next分别指向第1个节点和第2个节点;结束时,将p指针移动到现原先p.next的位置【但在操作过程中对原链表的结构发生了变化,故需要先将p.next的值先保存下来】

  • 因为步骤太多,所以简化记忆

    1. 第一步将p.next和p.next.next保存下来【1,2的位置】
    2. 先链接l2节点
    3. l2指向l1
    4. l1指向l2的后一节点
    5. 经过上述步骤已经排序结束,故将p指针移动至l1的位置。

    链表的调整顺序原则:[下上中]【如图说明】

    1. 下:p指向4后【即,无法再通过p获取3和4,所以必须提前保存两者的位置】
    2. 中:4指向3的前提是上:3必须先指向5。【即,中间的指向必须是最后确定的】

经典代码:

var swapPairs = function(head) {
    //1. 创建dummyNode节点
    let dummyNode = new ListNode();
    dummyNode.next = head;
    //2. 将工作指针移动到dummyNode
    let p = dummyNode;
    while( p.next !==null && p.next.next !==null){
        let l1 = p.next;
        let l2 = p.next.next;
        p.next = l2;
        l1.next = l2.next;
        l2.next = l1;
        p = l1;
    }
    return dummyNode.next;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 2. 两数相加 (opens new window)

题解:

  • 使用链表来存储数字,必须是个数在尾结点。
  • 通过l1和l2去取出同位数的数字
  • 使用sum去缓存相同位数的结果
    • 将sum中的个位数保存到新链表中
    • 将sum中的十位数保存到carry变量中

伪代码:

// 创建新链表
let dummyNode = new ListNode();
let cur = dummyNode;
let carry = 0;
while(l1和l2都不为空,两者是或的关系){
    let sum = 0;
    if(l1 !== null){1.更新sum 2.移动l1指针}
    if(l2 !== null){1.更新sum 2.移动l2指针}
    sum+=carry 
   	// 提取sum的两个部分,一部分更新为carry,一部分保存为cur中
}
if(假如最后carry中还有指,则放置开头){
    cur.next = new ListNode(carry)
}
return dummyNode.next
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 445.两数相加 II (opens new window)

题解:

  • 在第2题的基础上,可以很好的理解链表为什么要倒存数。
  • 使用stack倒着保存链表值。

伪代码:

// 输入:(7 -> 2 -> 4 -> 3) + (5 -> 6 -> 4)
// 输出:7 -> 8 -> 0 -> 7

// 第1步:构造stack 
// stack1 = [7,2,4,3] ; stack2 = [5,6,4] 

// 第2步:同第2题的算法
while(stack1.length !== 0 || stack2.length !== 0){}
1
2
3
4
5
6
7
8
点击查看

参考代码:

var addTwoNumbers = function(l1, l2) {
    var stack1 = [];
    var stack2 = [];
    while (l1 !== null) {
        stack1.push(l1.val);
        l1 = l1.next;
    }
    while (l2 !== null) {
        stack2.push(l2.val);
        l2 = l2.next;
    }

    var carry = 0;
    var curr = null;
    while (stack1.length !== 0 || stack2.length !== 0) {
        let sum = 0;
        if (stack1.length !== 0) {
            sum += stack1.pop();
        }
        if (stack2.length !== 0) {
            sum += stack2.pop();
        }
        sum += carry;

        // 取sum, 个位和十位
        carry = Math.floor(sum / 10);
        const node = new ListNode(sum % 10);

        //  需要将刚得到的node,添加到链表的头部
        node.next = curr;
        curr = node;       
    }

    if(carry !== 0){
        var node = new ListNode(carry)
        node.next = curr;   
        curr = node;
    }
    return curr;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

# 160.相交链表 (opens new window)

题解:

  • 因为A和B链表的长度不同,说明:当A的链表指针走到头,就将A链表的指针移到B链表的头部。

伪代码:

// p1 和 p2 指向头部
while(p1和p2不停地在比较){
    if(p1有无到头部){
        到了,就将其移动到对面的B链表头部
    }else{
        若没有到,就正常跌倒更新
    }
}
直接return l1 or l2
1
2
3
4
5
6
7
8
9

此时,考虑到了 l1 和 l2 皆为null的情况,

点击查看

参考代码:

var getIntersectionNode = function(headA, headB) {
    let l1 = headA;
    let l2 = headB;
    while(l1 !== l2){
        if(l1 !== null){
            l1 = l1.next;
        }else{
            l1 = headB;
        }
        if(l2 !== null){
            l2 = l2.next;
        }else{
            l2 = headA;
        }
    }   
    return l1;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
编辑 (opens new window)
#刷题
上次更新: 2022/04/06, 15:04:00
基础算法篇
动态规划

← 基础算法篇 动态规划→

最近更新
01
如何理解浏览器的 user agent 用户代理的含义?
11-05
02
浏览器事件循环机制
10-31
03
浏览器页面渲染机制【2023】
10-15
更多文章>
Theme by Vdoing | Copyright © 2020-2023
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式