1.设计链表
class MyLinkedList {
public:
struct LinkedNode{
int val;
struct LinkedNode* next;
LinkedNode(int val):val(val),next(nullptr){} //这里待会搞清楚
};
MyLinkedList() {
_dummyHead=new LinkedNode(0);
_size=0;
}
int get(int index) {
if(index<0||index>(_size-1)) return -1;
LinkedNode *cur=_dummyHead->next; //让cur最终指向第index个结点
while(index--){
cur=cur->next;
}
return cur->val;
}
void addAtHead(int val) {
LinkedNode* newNode=new LinkedNode(val);
newNode->next=_dummyHead->next;
_dummyHead->next=newNode;
_size++;
}
void addAtTail(int val) {
LinkedNode* cur=_dummyHead; //前面两种cur都是从第一个结点开始,这里不行,因为假如是空链表,cur指向第一个结点就是空,根本无法进行插入操作
LinkedNode *newNode=new LinkedNode(val);
while(cur->next) cur=cur->next;//保证了cur最终指向的是最后一个结点
cur->next=newNode;
_size++;
}
void addAtIndex(int index, int val) {
if(index>_size) return;
if(index==_size) {
addAtTail(val);
return;
}
LinkedNode* cur=_dummyHead;
LinkedNode* newNode=new LinkedNode(val);
while(index--) cur=cur->next;
newNode->next=cur->next;
cur->next=newNode;
_size++;
}
void deleteAtIndex(int index) {
if(index<0||index>=_size) return;
LinkedNode* cur=_dummyHead; //要删除第index个结点,就要找第index-1个结点
while(index--) cur=cur->next;
LinkedNode* delNode=cur->next;
cur->next=cur->next->next;
delete delNode;
_size--;
}
private:
int _size;
LinkedNode* _dummyHead;
};
/**
* Your MyLinkedList object will be instantiated and called as such:
* MyLinkedList* obj = new MyLinkedList();
* int param_1 = obj->get(index);
* obj->addAtHead(val);
* obj->addAtTail(val);
* obj->addAtIndex(index,val);
* obj->deleteAtIndex(index);
*/
思路:感觉这道题唯一要注意的就是怎么寻找合适的插入点:
(1)对于get函数,初始化cur指针指向第一个结点,而不是头结点,再利用while(index--) cur=cur->next;
就能够让cur最终指向第index个结点。
LinkedNode *cur=_dummyHead->next; //让cur最终指向第index个结点
while(index--) cur=cur->next;
(2)对于addAtTail函数,最开始,我是直接想参照get函数的方法,初始化cur指针指向第一个结点,然后利用上面的方法,即count=(_size-1); while(count--) cur=cur->next;
,但是仔细考虑就会发现当链表是空链表时这样是不行的,因为这种方法只有在链表不为空时适用,然后才可以通过这个方法找到链表的最后一个元素,但当链表是空链表时,cur指针就被初始化为空指针,而合理的方法是当链表为空时,cur应该指向头结点,这样才能进行插入操作,因此这里应该将cur初始化为指向头结点(_dummyHead),然后利用count=_size;while(count--) cur=cur->next;或者 while(cur->next) cur=cur->next;
循环(但是第二个方法好像更容易想到一点),这样循环结束时,就能保证了cur最终指向的是最后一个结点,当链表为空链表时也同样适用。
(3)对于addAtIndex函数,前面addAtTail函数找插入点的方法是这里找插入点方法的一种特殊情况,这里要在第index前插入节点,也就是要cur指针最终停在第index-1个结点,即初始化cur指针指向_dummyHead,然后进行循环,即LinkedNode* cur=_dummyHead; while(index--) cur=cur->next;
,而前面addAtTail函数,就是这里的Index=_size.
(4)deleteAtIndex函数与addAtIndex函数找插入点的方法 完全一致。
最后总结一下:
1.addAtTail、addAtIndex、deleteAtIndex三个函数中寻找插入点的方法是一致的,都是让cur指针最终停在第index-1个结点,即初始化cur指针指向_dummyHead,然后进行循环,即LinkedNode* cur=_dummyHead; while(index--) cur=cur->next;
,对于addAtTail函数,也就是把index换成了_size,但是更容易想到的方法是while(cur->next) cur=cur->next;
。
2.对于get函数,因为不涉及增加或删除元素,因此不像上面一样初始化cur指针指向头结点,而是指向第一个结点,再利用while(index--) cur=cur->next;
就能够让cur最终指向第index个结点,其实这两种方法的循环条件都是while(index--) cur=cur->next;
,唯一不同的是get函数cur指针初始化指向第一个结点,所以最终cur指向第index个结点,而addAtTail、addAtIndex、deleteAtIndex三个函数cur指针初始化指向头结点,即相比get函数的cur指针前移了一个结点,因此最终cur指向的结点也前移一个,指向了第index-1个结点。
2.反转链表
思路:迭代法(双指针法)其实只要注意下面几点:
(1)开始先初始化cur指针指向头结点,pre指针为nullptr
(2)注意循环条件是while(cur),即cur指向空时循环停止
(3)循环内部即指针的移动就是让cur->next指向pre,即指针反转,但是需要先保存cur->next的值,否则无法cur就无法后移了。
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode* cur=head;
ListNode* pre=nullptr;
while(cur){
ListNode* tmp=cur->next;
cur->next=pre;
pre=cur;
cur=tmp;
}
return pre;
}
};
3.两两交换链表中的结点
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
ListNode* dummyHead=new ListNode();
dummyHead->next=head;
ListNode* cur=dummyHead;
while(cur->next!=nullptr&&cur->next->next!=nullptr) {
ListNode* tmp1=cur->next;
ListNode* tmp2=cur->next->next->next;
cur->next=cur->next->next;
cur->next->next=tmp1;
tmp1->next=tmp2;
cur=cur->next->next;
}
return dummyHead->next;
}
};
思路:
首先要弄清cur指针指向的元素,cur指针被初始化为虚拟头结点,始终在被交换位置的两个结点的前一个结点,循环的终止条件就是cur指针后没有结点了,或者只有一个结点了,对应的判断语句就是cur->next!=nullptr&&cur->next->next!=nullptr
其次指针的指向变换可以看下面这张图,让dummyHead指向节点2,节点2指向节点1,节点1指向节点3,让节点间的指针捋顺之后就可以看到节点1,2被反转过来了,但是要注意的是当dummyHead指向节点2后,节点1变成了一个孤节点了,没有指针可以指向它的位置,因此需要提前创建tmp1指针指向节点1,节点3同理。最后要记得cur指针后移两个结点,即cur=cur->next->next
。
4.删除链表的倒数第N个结点
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* dummyHead=new ListNode(0,head);
ListNode* fast=dummyHead;
ListNode* low=dummyHead;
n++;
while(n--) fast=fast->next;
while(fast!=nullptr){
fast=fast->next;
low=low->next;
}
ListNode* tmp=low->next;
low->next=low->next->next;
delete tmp;
return dummyHead->next;
}
};
思路:其实就是采用双指针算法,fast指针先移动n+1位,为什么移动n+1位是因为要删除倒数第n位,low指针就要指向倒数第n位的前一位,即倒数第n+1位,而low,fast指针同时移动的终止条件是while(fast!=nullptr),即最终fast指向最后结点的下一个结点,如果fast是先移动n位,那么最终low就会停在倒数第n位,而ast先移动n+1位的话,low就会停在倒数第n位的前一位。
5.链表相交
思路:这道题并不难,只要记住几个点:
1.首先要将两个链表尾部对齐,则计算两个链表长度之差,更长的链表指针先后移长度之差个结点。
2.两链表尾部对齐后再同时移动curA、curB指针,直到两指针相等或为空。
3.记得使用swap函数可以简化代码,swap用于交换两个对象的值.让headA始终指向长的那一条链表,可以减少后面的判断语句。
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
int length_A=0,length_B=0;
if(headA==nullptr || headB==nullptr) return NULL;
length_A=count_length(headA);
length_B=count_length(headB);
if(length_B>length_A){
swap(length_A,length_B);
swap(headA,headB);
}
ListNode* curA=headA;
ListNode* curB=headB;
int move=length_A-length_B;
while(move--) curA=curA->next;
while(curA!=nullptr){
if(curA==curB) return curA;
curA=curA->next;
curB=curB->next;
}
return NULL;
// while((curA!=curB)&&(curA!=nullptr)&&(curB!=nullptr)) {
// curA=curA->next;
// curB=curB->next;
// }
// if(curA==nullptr) return NULL;
// return curA;
}
int count_length(ListNode *head){
int length=0;
while(head!=nullptr){
length++;
head=head->next;
}
return length;
}
};