链表定义
// 单链表
struct ListNode {
int val; // 节点上存储的元素
ListNode *next; // 指向下一个节点的指针
ListNode(int x) : val(x), next(NULL) {} // 节点的构造函数
};
如果定义了节点的构造函数,那么在初始化时就可以直接传值,例如:
ListNode * head = new ListNode(5);
如果没有定义构造函数就在初始化时不能传值,即:
ListNode* head = new ListNode();
head->val = 5;
这里补充一个点,就是 n e w new new对象时加不加括号的区别,即:
ListNode* head = new ListNode;
head->val = 5;
上面两种方法最后都能够让val的值为5,主要区别在于:
- 加括号(里面没有参数)则会调用没有参数的构造函数(可能有重构),不加括号调用默认的构造函数或者唯一的构造函数。
- 对于有构造函数的结构体,不论在生成对象时加不加括号,都会调用构造函数来初始化;对于没有构造函数的类,不加括号的new只是分配了内存空间,加括号的new会在分配内存空间后初始化为0
203、移除链表元素
该题虽然看起来很简单,但实际上有很多需要注意的细节之处!
首先对于中间节点的处理是很简单的,但是对于头结点的处理就不太一样,而且还要考虑有没有一开头连续好几个目标元素的可能,因此要加入while循环,其次是各种判断条件的顺序,极为重要!这里先贴出我的代码:
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
while(head != NULL && head->val == val){
ListNode *temF = new ListNode();
temF = head;
head = temF->next;
delete temF;
}
ListNode*temP = new ListNode();
temP = head;
while( temP != NULL && temP->next != NULL){
if (temP->next->val == val){
ListNode*temF = new ListNode();
temF = temP->next;
temP->next = temF->next;
delete temF;
}else{
temP = temP->next;
}
}
return head;
}
};
首先是在第一个while处,判断条件为 h e a d ! = N U L L a n d h e a d − > v a l = = v a l head != NULL\quad and\quad head->val == val head!=NULLandhead−>val==val,这里顺序是一定的,如果不注意写成: h e a d − > v a l = = v a l a n d h e a d ! = N U L L head->val == val\quad and\quad head != NULL head−>val==valandhead!=NULL,那么在遇到空集的这种特殊情况时,当前head为空,没有val这个属性,因此进入判断就调用了它的val值,就是对空指针执行操作会报错;如果是正确的顺序就会在第一个判断中判断到它空指针就不进行第二个判断,因此能正确通过。
其次是第二个while处,如果也写反了,先判断 t e m P − > n e x t ! = N U L L temP->next != NULL temP−>next!=NULL,那么特殊情况时处理到最后 t e m P temP temP指向了一个空指针,那么如果还调用 t e m P − > n e x t temP->next temP−>next就同样会报错。
另一种是加入一个头指针的形式,这样就可以把头结点的处理方式和其他节点统一了,具体如下:
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
ListNode*newHead = new ListNode(0);
newHead->next = head;
ListNode*temP = newHead;
while(temP != NULL && temP->next != NULL){
if(temP->next->val == val){
ListNode*temF = temP->next;
temP->next = temF->next;
delete temF;
}else{
temP = temP->next;
}
}
return newHead->next;
}
};
707、设计链表
这一题比较简单,就是比较繁琐,要弄清楚各个要求的思路。
首先创建一个结构体!然后重要的思路就是用一个私有成员变量来记录当前元素的个数,这在后续各种判断的条件中是很有用的。另外就是创建虚拟头结点,让第一个结点的操作和其他节点统一更加方便。
class MyLinkedList {
public:
struct LinkedNode{
int val;
LinkedNode* next;
LinkedNode(int val):val(val),next(nullptr){}
};
MyLinkedList() {
this->newHead = new LinkedNode(0);
this->size = 0;
}
int get(int index) {
if(index > (this->size - 1) || index < 0){
return -1;
}
int curIndex = 0;
LinkedNode*curP = this->newHead->next;
while(curIndex < index){
curP = curP->next;
curIndex++;
}
return curP->val;
}
void addAtHead(int val) {
LinkedNode* temP = new LinkedNode(val);
temP->next = newHead->next;
newHead->next = temP;
this->size++;
}
void addAtTail(int val) {
LinkedNode* temP = new LinkedNode(val);
LinkedNode* curP = newHead;
while(curP->next != NULL){
curP = curP->next;
}
curP->next = temP;
this->size++;
}
void addAtIndex(int index, int val) {
if(index < 0){
addAtHead(val);
}
else if(index > this->size){
return;
}
else if(index == this->size){
addAtTail(val);
}
else{
int curIndex = 0;
LinkedNode* curP = newHead;
LinkedNode* temP = new LinkedNode(val);
while(curIndex < index){
curIndex++;
curP = curP->next;
}
temP->next = curP->next;
curP->next = temP;
this->size++;
}
}
void deleteAtIndex(int index) {
if(index >(this->size-1) || index < 0){
return;
}
int curIndex = 0;
LinkedNode* curP = newHead;
while(curIndex < index){
curP = curP->next;
curIndex ++;
}
LinkedNode*temP = curP->next;
curP->next = temP->next;
delete temP;
this->size--;
}
private:
LinkedNode* newHead;
int size;
};
206、反转链表
这一题一开始我用的是生成另外一个链表,然后不断将原链表的元素插入到新链表的头部,具体如下:
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode*newList = new ListNode;//虚拟头结点
ListNode*temP = head;
while(temP != NULL){
ListNode*temF = new ListNode(temP->val);
temF->next = newList->next;
newList->next = temF;
temP = temP->next;
}
return newList->next;
}
};
但经过代码随想录的提醒,还可以通过改变指针指向的想法来节约空间,因此思路为:
- 有一个指针 c u r P curP curP不断遍历链表的每一个结点作为当前结点,然后改变它们的指向
- 有一个指针 p r e P preP preP不断跟随 c u r P curP curP移动,一直在 c u r P curP curP的前面一位,方便 c u r P curP curP改变指向时能够找到它前面一位
- 有一个指针 t e m P temP temP,用来存储 c u r P curP curP后面一位,否则当改变 c u r P − > n e x t = p r e P curP->next = preP curP−>next=preP时,后面一位将丢失
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode* curP = head; // 用来遍历每一个结点
ListNode* preP = NULL; // 用来指向当前结点的前一个元素
while(curP != NULL){
ListNode* temP = curP->next; // 用来保存当前结点的后一个元素防止丢失
curP->next = preP; // 改变指向
preP = curP; // 更新前一个元素
curP = temP; // 更新当前元素
}
return preP; // 因此最后会使得curP指向最后一个元素的下一个也就是NULL所以返回preP
}
};
24. 两两交换链表中的节点
这道题主要是需要在转换各个节点的指向时思路要清晰,并且最好是加入虚拟头结点,这能够将头结点和其他结点统一,同时也更容易进行两两翻转后与之后的节点的正确连接。
关键点是始终让cur作为待转换的两个结点的前一个结点,因此虚拟头结点可以将头结点与其他统一,并且也能够将两两之间进行连接。其次是这个步骤的问题,只要从始至终保持这个步骤的统一便不会丢失节点。
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
//如果是单数就最后一个不管
ListNode*newHead = new ListNode(); //头结点
newHead->next = head;
if(head == NULL || head->next == NULL){
return head; // 如果是空或者只有一个元素不用翻转
}
ListNode*curP = newHead;
while(curP->next != NULL && curP->next->next != NULL){
ListNode* temP = curP->next;
ListNode* temQ = curP->next->next->next;
curP->next = curP->next->next; // 让头结点指向第二个元素
curP->next->next = temP; // 让第二个元素指回第一个元素
curP->next->next->next = temQ; // 让第一个元素指向第三个元素,否则会断开
curP = curP->next->next;
}
return newHead->next;
}
};
19、删除链表的倒数第N个结点
这一条可以用双指针法来解决,即让快指针先走n步,然后再让慢指针和快指针一起移动,直到快指针走到了最后,那么慢指针就是具体的倒数位置了。
但由于我们是要删除这个结点,删除时需要知道目标结点的前一个结点才能够连接起来链表,因此我们要让慢指针指向要删除结点的前一个结点,才能够正确删除,因此快指针要走n+1步。
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* newHead = new ListNode(0);
newHead->next = head;
ListNode* fastP = newHead;
ListNode* slowP = newHead;
int numLen = n + 1;
while(numLen > 0 && fastP != NULL){
fastP = fastP->next;
numLen--;
}
while(fastP != NULL){
slowP = slowP->next;
fastP = fastP->next;
}
ListNode*temP = slowP->next;
slowP->next = temP->next;
delete temP;
return newHead->next;
}
};
面试题02.07、链表相交
这一题需要注意的点是:是结点相同,而不是结点值相同!例如看示例1,其中有结点值1相同但并不是相交的。
而关键的思路在于:如果两个链表有交点,就算长度不同,它们的末端也一定是对齐的。因此我们可以将链表末端对齐,即:
然后再来逐个比较结点是否相等。
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
ListNode* curA = headA;
ListNode* curB = headB;
int lenA = 0;
int lenB = 0;
while(curA != NULL){
curA = curA->next;
lenA++;
}// 统计A的长度
while(curB != NULL){
curB = curB->next;
lenB++;
}// 统计B的长度
curA = headA;
curB = headB;
int disLen;
if(lenA > lenB){
disLen = lenA-lenB;
while(disLen > 0){
curA = curA->next;
disLen--;
}// 将curA移动到与B对齐的位置
}
else{
disLen = lenB-lenA;
while(disLen > 0){
curB = curB->next;
disLen--;
}
}
while(curA != NULL){
if(curA == curB){
return curA;
}
curA = curA->next;
curB = curB->next;
}
return NULL;
}
};
142、环形链表二
该题感觉难度较大,也是跟紧代码随想录的思路才能够写出来。
最关键的两个问题是如何判断链表有环以及如果有环如何找到其入口。
我最开始的想法是让一个慢指针固定,然后另外一个快指针不断是向前next,然后与慢指针比较,但如果慢指针不是入口,这样则会陷入死循环,因此不能固定慢指针。
因此判断是否有环的方法就是:让快指针和慢指针同时移动,但是快指针一次移动两个位置,慢指针每次移动一个位置,这样如果存在环,那么快指针将会在环内与慢指针相遇。
为什么一定会使得快指针在环内与慢指针相遇呢?因为如果快慢指针都进入环,那么如果移动速度相同,它们之间的距离将不变;如果快指针每次比慢指针多移动一个位置,那么快指针将会不断缩短它们的距离,而且每次一个位置的缩短是一定可以碰到的。
如果找到了环,那么如何找入口呢?引用代码随想录的图片:
- 假设头结点到入环结点的节点数目为 X X X,入环结点到快慢指针相遇的结点的结点数为 Y Y Y,相遇节点到入口结点的结点数为 Z Z Z。
- 那么在相遇时,慢指针走过的路程为 X + Y X+Y X+Y,快指针走过的路程为 X + Y + n × ( Y + Z ) X+Y+n\times (Y+Z) X+Y+n×(Y+Z),其中 n n n为快指针在环中走了 n n n圈之后才遇到慢指针
- 由于速度相差两倍而时间相同,因此快指针的路程为慢指针的两边,那么有: 2 × ( X + Y ) = X + Y + n × ( Y + Z ) 2\times (X+Y)=X+Y+n\times (Y+Z) 2×(X+Y)=X+Y+n×(Y+Z) 那么化简可得到 X = ( n − 1 ) × ( Y + Z ) + Z X=(n-1)\times (Y+Z)+Z X=(n−1)×(Y+Z)+Z
- 由这个公式可以得知:第一个指针 i n d e x 1 index1 index1从相遇节点出发,第二个指针从 i n d e x 2 index2 index2从头结点出发,当 i n d e x 1 index1 index1走过 ( n − 1 ) × ( Y + Z ) + Z (n-1)\times (Y+Z)+Z (n−1)×(Y+Z)+Z的路途,而 i n d e x 2 index2 index2走过 X X X,那么它们将会在入口结点处相遇。也就是说它们一定会在入环结点处相遇。
那我此时有一个疑问:有没有可能慢指针走过的路程不止 X + Y X+Y X+Y呢,而是在里面也走了几圈呢?
不可能。因为在慢指针到达入环结点时,快指针必然在环内,那么它们之间的距离一定小于环的长度,那么在慢指针第一圈走完之前,快指针一定会追上慢指针,因为慢指针如果真的走超过了一圈,那么快指针就超过了两圈,它们之间的差值是一圈,肯定可以将慢指针刚进来时的差距给填平的。
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
ListNode*fastP = head;
ListNode*slowP = head;
while(fastP!=NULL && fastP->next != NULL){
slowP = slowP->next;
fastP = fastP->next->next;
if(slowP == fastP){//如果此时相遇
ListNode* index1 = fastP;
ListNode* index2 = head;
while(index1 != index2){//不断寻找直到相遇
index1 = index1->next;
index2 = index2->next;
}
return index2;
}
}
return NULL; // 如果前面没有返回说明找不到环
}
};