DAY1 第一章
1、链表理论基础
- 链表是通过指针串联在一起的线性结构,每一个节点由两部分组成,一个是数据域,一个是执指针域。链表的入口节点称为链表的头节点,也就是head。
- 链表又分为单链表,双链表和循环链表
- 链表在内存中不是连续分布的。各个节点分布在内存的不同地址空间上,通过指针串联在一起
链表的定义
class ListNode {
val;
next = null;
constructor(value) {
this.val = value;
this.next = null;
}
}
2、移除链表元素
题意:删除链表中等于给定值 val 的所有节点。
示例 1: 输入:head = [1,2,6,3,4,5,6], val = 6 输出:[1,2,3,4,5]
示例 2: 输入:head = [], val = 1 输出:[]
思路:设置一个虚拟头节点进行删除操作
/**
* 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} val
* @return {ListNode}
*/
var removeElements = function(head, val) {
const ret= new ListNode(0,head);
let cur=ret;
while(cur.next){
if(cur.next.val===val){
cur.next=cur.next.next;
}
else{
cur=cur.next;
}
}
return ret.next
}
3、设计链表
题意:
在链表类中实现这些功能:
- get(index):获取链表中第 index 个节点的值。如果索引无效,则返回-1。
- addAtHead(val):在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点。
- addAtTail(val):将值为 val 的节点追加到链表的最后一个元素。
- addAtIndex(index,val):在链表中的第 index 个节点之前添加值为 val 的节点。如果 index 等于链表的长度,则该节点将附加到链表的末尾。如果 index 大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。
- deleteAtIndex(index):如果索引 index 有效,则删除链表中的第 index 个节点。
-
class ListNode { constructor(val, next) { this.val = val; this.next = next; } } var MyLinkedList = function () { this.head = null; this.val = null; this.size = 0; }; /** * @param {number} index * @return {number} */ MyLinkedList.prototype.get = function (index) { console.log('get index: ',index) //判断index有效性 if (index < 0 || index >= this.size) return -1; //定义一个虚拟头节点,他的下一个是原链表的头节点 const dummyHead = new ListNode(-1, this.head); let current = dummyHead.next; while (index) { current = current.next; index--;//很细腻 } console.log('get: ',current.val) return current.val; }; /** * @param {number} val * @return {void} */ MyLinkedList.prototype.addAtHead = function (val) { //定义一个虚拟头节点,他的下一个是原链表的头节点 const dummyHead = new ListNode(-1, this.head); //定义新节点 const newNode = new ListNode(val, null); newNode.next = dummyHead.next; dummyHead.next = newNode; this.head = newNode; this.size++; console.log('addAtHead: ',this.head) }; /** * @param {number} val * @return {void} */ MyLinkedList.prototype.addAtTail = function (val) { //定义一个虚拟头节点,他的下一个是原链表的头节点 const dummyHead = new ListNode(-1, this.head); //定义新节点 const newNode = new ListNode(val, null); if (this.size == 0) { this.head = newNode; this.size++; console.log('addAtTail: ', this.head) return; } let current = dummyHead;//很细腻,要插入尾节点,必须找到当前的尾节点,但可能存在当前链表为空的情况 while (current.next != null) { current = current.next; } current.next = newNode; this.size++; console.log('addAtTail: ', this.head) }; /** * @param {number} index * @param {number} val * @return {void} */ MyLinkedList.prototype.addAtIndex = function (index, val) { if (index <= 0) { this.addAtHead(val); return; } if (index == this.size) { this.addAtTail(val); return; } if (index > this.size) return; //定义一个虚拟头节点,他的下一个是原链表的头节点 const dummyHead = new ListNode(-1, this.head); //定义新节点 const newNode = new ListNode(val, null); let current = dummyHead;//很细腻 while (index) { current = current.next; index--; } newNode.next = current.next; current.next = newNode; this.size++; console.log('addAtIndex: ', this.head) }; /** * @param {number} index * @return {void} */ MyLinkedList.prototype.deleteAtIndex = function (index) { // console.log('deleteAtIndex: ', this.head) if (index < 0 || index >= this.size) return; if (index == 0) { this.head = this.head.next; this.size--; return; } //定义一个虚拟头节点,他的下一个是原链表的头节点 const dummyHead = new ListNode(-1, this.head); let current = dummyHead;//很细腻 while (index) { current = current.next; index--; } current.next = current.next.next; this.size--; console.log('deleteAtIndex: ', this.head) }; /** * Your MyLinkedList object will be instantiated and called as such: * var obj = new MyLinkedList() * var param_1 = obj.get(index) * obj.addAtHead(val) * obj.addAtTail(val) * obj.addAtIndex(index,val) * obj.deleteAtIndex(index) */
这个题目综合考察链表的用法,包括获取第Index个节点数值,在前面或后面插入节点,或者在Index位置插入,以及删除节点。我们要对Index进行判断,每次变化对size进行改变。对边界不确定的时候,可以使用极端值。
4、翻转链表
给你单链表的头节点 head
,请你反转链表,并返回反转后的链表。206. 反转链表 - 力扣(LeetCode)
思路:双指针法,定义指针cur和pre(反过来的指针指向),两个指针依次往后移动。
注意我们循环遍历终止条件,以及考虑临时指针存放下一节点的。考虑pre和cur移动的先后顺序
先移动pre,后移动cur
var reverseList = function(head) {
let cur=head;
let pre =null;
while(cur){
temp=cur.next;
cur.next=pre;
pre=cur;
cur=temp;
}
return pre;
};
DAY2 第二章
1、24. 两两交换链表中的节点
题目:给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。24. 两两交换链表中的节点 - 力扣(LeetCode)
思路:如图所示,若实现两两交换,需要执行以下三个步骤。
首先创建虚拟头节点,使cur节点指向dummyHead,每次移动两个节点,判断循环结束条件为cur.next(链表长度为偶数)和cur.next.next(链表长度为奇数)是否为空。
然后注意临时指针储存,这里我们存储1和3的指针
/**
* 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}
*/
var swapPairs = function(head) {
const dummpyHead= new ListNode(0,head);
let cur=dummpyHead;
while(cur.next!=null&&cur.next.next!=null){
let temp=cur.next;
let temp1=cur.next.next.next;
cur.next=cur.next.next;
cur.next.next=temp;
temp.next=temp1;
cur=cur.next.next;
}
return dummpyHead.next;
};
2、19.删除链表的倒数第N个节点
19. 删除链表的倒数第 N 个结点 - 力扣(LeetCode)
题目:
给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
进阶:你能尝试使用一趟扫描实现吗?
输入:head = [1,2,3,4,5], n = 2 输出:[1,2,3,5] 示例 2:
输入:head = [1], n = 1 输出:[] 示例 3:
思路:利用快慢指针的思想,快指针第一次走n步,第二次快指针和慢指针一同出发,直到快执政指向链表末尾,这时候删掉慢指针所指向节点。这里需要用到虚拟头节点
/**
* 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}
*/
var removeNthFromEnd = function(head, n) {
const dummyHead=new ListNode(0,head);
let fast=dummyHead;
let slow=dummyHead;
for(let i=1; i<=n+1; i++) {
fast = fast.next;
}
while(fast != null) {
fast = fast.next;
slow = slow.next;
}
// Skip the desired node
slow.next = slow.next.next;
return dummyHead.next;
};
3、面试题 02.07. 链表相交
面试题 02.07. 链表相交 - 力扣(LeetCode)
题目描述:给你两个单链表的头节点 headA
和 headB
,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null
。
思路:首先让他们尾部对齐排列,然后比较curA和curB是否相同,若不相同,则同时向后移动,如果遇到curA===curB,则找到焦点。
那么如何使得他们尾部对其,首先先算出两个单链表的长度,然后求出两个单链表长度的差值,固定A链表比较长,让curA移动到和curB末尾对其的位置。
/**
* Definition for singly-linked list.
* function ListNode(val) {
* this.val = val;
* this.next = null;
* }
*/
/**
* @param {ListNode} headA
* @param {ListNode} headB
* @return {ListNode}
*/
var getLens=function(head){
let lens=0;
cur=head;
while(cur!=null){
cur=cur.next;
lens=lens+1;
}
return lens;
}
var getIntersectionNode = function(headA, headB) {
let curA=headA,
curB=headB,
lensA=getLens(headA),
lensB=getLens(headB);
if(lensA<lensB){
[curA,curB]=[curB,curA];
[lensA,lensB]=[lensB,lensA];
}
let i=lensA-lensB;
while(i-->0){
curA=curA.next
}
while(curA && curA!=curB){
curA=curA.next;
curB=curB.next;
}
return curA;
};
4、 142.环形链表II
给定一个链表的头节点 head
,返回链表开始入环的第一个节点。 如果链表无环,则返回 null
。
如果链表中有某个节点,可以通过连续跟踪 next
指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos
来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos
是 -1
,则在该链表中没有环。注意:pos
不作为参数进行传递,仅仅是为了标识链表的实际情况。
不允许修改 链表。
思路
这道题目,不仅考察对链表的操作,而且还需要一些数学运算。
主要考察两知识点:
- 判断链表是否环
- 如果有环,如何找到这个环的入口
首先判断链表是否环,使用快慢指针法,从头节点出发,fast指针每次移动两个,slow指针每次移动1个。相对于slow来说,fast是一个一个节点靠近slow,所以fast一定可以和slow重合。
如果有环,如何找到环的入口,这个时候需要用到数学方程推导。
/**
* Definition for singly-linked list.
* function ListNode(val) {
* this.val = val;
* this.next = null;
* }
*/
/**
* @param {ListNode} head
* @return {ListNode}
*/
var detectCycle = function(head) {
let fast=head;
let slow=head;
while(fast!=null&&fast.next!=null){
fast=fast.next.next;
slow=slow.next;
if(slow==fast){
index1=fast;
index2=head;
while(index1!=index2){
index1=index1.next;
index2=index2.next;
}
return index2;
}
}
return null;
};