一、203.移除链表元素
自己写的
因为ac不了,所以一直加if语句把特例排除,显得整个非常复杂,没有虚拟头结点会比较复杂,当初数据结构的时候一半也会加一个头结点。
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
if(head!=nullptr&&head->val==val){
head=head->next;
}
if(head==nullptr){
return head;
}
//我上面的几个if和这里其实是一样的,就是复杂了
//while (head != NULL && head->val == val) { // 注意这里不是if
// ListNode* tmp = head;
// head = head->next;
// delete tmp;
//}
ListNode *p=head;
while(p->next!=nullptr){
ListNode* q=p->next;
if(q->val==val){
ListNode *a=q;
p->next=q->next;
delete(a);
}else{
p=q;
}
}
if(head->val==val){
head=head->next;
}
return head;
}
};
加上虚拟头结点后的写法
之所以加虚拟头结点,是因为使得所有删除操作一致,代码会减少,思路也更加清晰。不过C++使用free会报错,用delete就不会,不管了,就用delete了。
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
ListNode *preHead=(ListNode*)malloc(sizeof(ListNode));
preHead->next=head;
ListNode *p=preHead;
while(p->next!=nullptr){
if(p->next->val==val){
ListNode *tmp=p->next;
p->next=p->next->next;
delete(tmp);
}else{
p=p->next;
}
}
return preHead->next;
}
};
二、707.设计链表
自己千辛万苦写出来的
写这个的时候,因为觉得自己当初准备考研初试的时候学数据结构经常手写链表的算法,觉得应该不难,于是在LeetCode上直接写,最后总有测试案例过不了,思来想去都没找到关键点,LeetCode的调试的监事变量有非常鸡肋,很难用,只能说还是不够熟悉。最后把main函数写上去去的vs2022上调,才找到错误,是addAtIndex里面的if中没加return,导致size++了两次。具体在下面注释中也写了。
class MyLinkedList {
public:
typedef struct LinkedNode{
int val;
LinkedNode *next;
LinkedNode(int val):val(val),next(NULL){}
}LinkedNode;
MyLinkedList() {
head=new LinkedNode(0);
size=0;
}
int get(int index) {
if(index<0 || index >=size){
return -1;
}else{
LinkedNode*p=head;
while(0 != index--){
p=p->next;
}
return p->next->val;
}
}
void addAtHead(int val) {
LinkedNode* p = new LinkedNode(val);
p->next = head->next;
head->next = p;
size++;
}
void addAtTail(int val) {
LinkedNode* p = head;
LinkedNode* q = new LinkedNode(val);
while (p->next != nullptr) {
p = p->next;
}
p->next = q;
size++;
}
void addAtIndex(int index, int val) {
if(index > size){
return;
}
if(index < 0 ){
addAtHead(val);
return;//变了size 则return
}
if(index == size){
addAtTail(val);
return;//为什么这里没return就一定不行呢?
//因为size在addAtTail中已经+1了,所以下面再判断index时还是进入if了,因此return一定要写清楚
}
if(index >=0 && index < size){
LinkedNode*p = new LinkedNode(val);
LinkedNode*q = head;
while(0 != index --){
q=q->next;
}
p->next = q->next;
q->next = p;
size++;
return;
}
}
void deleteAtIndex(int index) {
if(index<0 || index >=size){
return;
}else{
LinkedNode*p=head;
while(0 != index--){
p=p->next;
}
LinkedNode*q=p->next;
p->next = q->next;
delete q;
size--;
}
}
private:
int size;
LinkedNode *head;
};
大佬写的
这里只贴addAtIndex,我是跟着题目的思维,用了四个if,然后还用到了上面写的插入首位置和尾位置的函数,导致没有return就会重复操作两次。所以在函数内部,完成步骤了一定要多个return,哪怕不需要,也是得加的,不然这种错误很难找到了。
void addAtIndex(int index, int val) {
if(index > _size) return;
if(index < 0) index = 0;
LinkedNode* newNode = new LinkedNode(val);
LinkedNode* cur = _dummyHead;
while(index--) {
cur = cur->next;
}
newNode->next = cur->next;
cur->next = newNode;
_size++;
}
三、206.反转链表
暴力解
对于链表的序列题,一般都能直接转数组暴力解,涉及到排序的用数组快排会快的多,也不需要过多思考。
class Solution {
public:
ListNode* reverseList(ListNode* head) {
//试一下转数组然后暴力解
if(nullptr == head){
return head;
}
vector<int> array(5000);
ListNode *p=head;
int i=0;
while(nullptr != p){
array[i++] = p->val;
p = p->next;
}
int size = i;
vector<int> reverse(size);
for(int j=0,k=size-1;j<size;j++,k--){
reverse[j] = array[k];
}
i=0;
p = head;
while(i<size && nullptr != p){
p->val = reverse[i++];
p = p->next;
}
return head;
}
};
头插法逆置
头插法不需要额外的数组空间,能节省空间
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode *dummyhead = new ListNode(0);//设置一个虚拟头结点 便于操作
// ListNode *p=dummyhead;
while(nullptr != head){
ListNode *q = new ListNode(head->val);
head = head ->next;
q->next = dummyhead->next;
dummyhead ->next = q;
}
return dummyhead->next;
}
};
双指针法
其实很好理解,pre指针作为新链表被指向的,p指针遍历原链表将要逆置的
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode *p = head;
ListNode *pre = nullptr;
ListNode *tmp;
while(nullptr != p ){
tmp = p;
p = p->next;
tmp->next = pre;
pre = tmp;
}
return pre;
}
};
递归法
递归还是抽象啊,贴上大佬代码
class Solution {
public:
ListNode* reverse(ListNode* head){
// 1.递归结束条件
if (head == nullptr || head->next == nullptr) {
return head;
}
// 递归反转 子链表
ListNode* newList = reverse(head->next);
// 改变 1,2节点的指向。
// 通过 head.next获取节点2
ListNode* t1 = head->next;
// 让 2 的 next 指向 2
t1->next = head;
// 1 的 next 指向 null.
head->next = nullptr;
// 把调整之后的链表返回。
return newList;
}
ListNode* reverseList(ListNode* head) {
return reverse(head);
}
};
用栈
其实和数组的暴力解类似,只不过用到了栈的特性。
class Solution {
public:
ListNode* reverseList(ListNode* head) {
if(nullptr == head){
return head;
}
ListNode *p = head;
stack<int>a;
while(nullptr != p){
a.push(p->val);
p = p->next;
}
ListNode *dummyhead = new ListNode(0);
p = dummyhead;
while(0 != a.size()){
ListNode *q = new ListNode(a.top());
a.pop();
p->next = q;
p = q;
}
p->next = nullptr;
return dummyhead->next;
}
};
四、总结
昨天卡在设计链表的debug中,没看carl哥的建议在本地IDE上重写main函数属实是太蠢了,debug过程就是找到错误所在,根据错误进行优化,找不到这只bug谈何debug?还是代码能力的不足,要多刷题!不要做无用功啦。
(7小时)