代码随想录算法训练营第三天| LeetCode203 移除链表元素、LeetCode707 设计链表、LeetCode206 反转链表

链表

首元节点: 首元结点是链表中存储第一个数据元素的结点。它是链表数据部分的开始,包含了链表中的第一个元素的值。在没有头结点的链表中,首元结点就是链表的第一个结点。

头节点: 头结点是一个附加在首元结点之前的结点,它的指针域指向首元结点。头结点的数据域可以不存储任何信息,或者存储一些如链表长度等附加信息。头结点的存在使得对链表的第一个数据元素的操作与对其他数据元素的操作一致,无需特殊处理。此外,头结点的引入也便于空表和非空表的统一处理。

头指针: 是指向链表中第一个结点的指针。如果链表有头结点,头指针指向头结点;如果链表没有头结点,头指针指向首元结点。

虚拟头节点(dummy head): 引入虚拟头节点使得虚拟头节点指向头节点,head指向虚拟头节点,此时删除头节点与删除非头节点的代码一致。

LeetCode203 移除链表元素

题目链接: https://leetcode.cn/problems/remove-linked-list-elements/description/

不含虚拟头节点:

head头指针指向链表的首元节点,进行删除操作时,删除首元节点和非首元节点时所进行的操作不一样。

①删除第一个节点的代码为head = head->next

②删除非第一个节点的代码为cur->next = cur->next->next;

class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
        while(head != NULL && head->val == val)
			head = head->next;
		ListNode* cur = head;
		while(cur != NULL && cur->next != NULL){
			if(cur->next->val == val)
				cur->next = cur->next->next;
			else
				cur = cur->next;
		}
		return head;
    }
};

注意点:

①删除首元节点时,要用while而不是if因为可能出现以下情况

target = 1

ele[i]1111

②删除首元节点时,要判断是否为空,因为要判断head->val是否等于target, 若不判断,head为空时,取其值会报错

③删除非首元节点时,为何是cur = head而不是cur = head->next,因为经过删除首元节点的过程,已经把head->val==val的首元节点都已经删除了,则当前head->val一定不为val,进而只能判断head->next,则若删除head->next,那需要找到其前驱节点,即head节点

④删除非首元节点时,不仅要判断cur是否为空,还要判断cur->next是否为空,因为要删除的是cur->next,若为空,取值时会报错

含虚拟节点:

class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
		ListNode* dummyhead = new ListNode();
		dummyhead->next = head;
		ListNode* cur = dummyhead;
		while(cur != NULL && cur->next != NULL){
			if(cur->next->val == val)
				cur->next = cur->next->next;
			else
				cur = cur->next;
		}
		return dummy->next;
    }
};

注意:

①最终return的是dummyhead->next而不是head,因为head节点可能已经被删除了

LeetCode707 设计链表

**题目链接:**​https://leetcode.cn/problems/design-linked-list/description/

思路:

用极端例子快速判断要增加/删除元素位置是否正确

1、获取下标为index节点的值

用index控制while循环次数,从dummyHead->next开始循环index次即可找到下标为index的节点

LinkNode* cur = dummyHead->next;
while(index){
	cur = cur->next;
	index--;
}

2、头部插入节点

LinkNode* node = new LinkNode();
node->val = val;
node->next = dummyHead->next;
dummyHead->next = node;
len++;

3、尾部插入节点

LinkNode* cur = dummyHead;
while(cur->next != NULL)
	cur = cur->next;
LinkNode* node = new LinkNode();
node->val = val;
node->next = NULL;
cur->next = node;
len++;

4、下标为index节点前插入节点

若从dummyHead节点开始遍历,则到达下标为index节点的前一个节点,cur=cur->next需循环index次

举例验证: 当index=0时,while(0)不执行,因此直接头插节点,符合题意故没问题

当index=len时,while(index)执行len次,到达尾节点,直接尾插法,满足题意若index等于len则追加到末尾

LinkNode* cur = dummyHead;
while(index){
	cur = cur->next;
	index--;
}
LinkNode* node = new LinkNode();
node->val = val;
node->next = cur->next;
cur->next = node; 
len++;

5、删下标为index的节点

与4类同,若从dummyHead节点开始遍历,则到达下标为index节点的前一个节点,

cur=cur->next需循环index次

举例验证: 当index=0时,while(0)不执行,因此直接删除头节点,符合题意故没问题

LinkNode* cur = dummyHead;
while(index){
	cur = cur->next;
	index--;
}
cur->next = cur->next->next; 
len--;

题解:

class MyLinkedList {
public:
    struct LinkNode {
        int val;
        LinkNode* next;
    };
    LinkNode* dummyHead; // 虚拟头节点
    int len; // 链表长度

public:
    MyLinkedList() {
        dummyHead = new LinkNode();
        dummyHead->next = NULL;
        len = 0;
    }

    int get(int index) {
        if (index < 0 || index >= len)
            return -1;
        LinkNode* cur = dummyHead->next;
        while (index) {
            cur = cur->next;
            index--;
        }
        return cur->val;
    }

    void addAtHead(int val) {
        LinkNode* node = new LinkNode();
        node->val = val;
        node->next = dummyHead->next;
        dummyHead->next = node;
        len++;
    }

    void addAtTail(int val) {
        LinkNode* cur = dummyHead;
        while (cur->next != NULL)
            cur = cur->next;
        LinkNode* node = new LinkNode();
        node->val = val;
        node->next = NULL;
        cur->next = node;
        len++;
    }

    void addAtIndex(int index, int val) {
        // 插入位置合法性判断
        if (index > len || index < 0)
            return;
        LinkNode* cur = dummyHead;
        while (index) {
            cur = cur->next;
            index--;
        }
        LinkNode* node = new LinkNode();
        node->val = val;
        node->next = cur->next;
        cur->next = node;
        len++;

    }

    void deleteAtIndex(int index) {
        if (index < 0 || index >= len)
            return;
        LinkNode* cur = dummyHead;
        while (index) {
            cur = cur->next;
            index--;
        }
        cur->next = cur->next->next;
        len--;
    }
};

/**
 * 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、C++创建类的对象(类的初始化)的方法区别new和不用

使用new会创建在堆中,MyLinkedList* obj = new MyLinkedList();

不使用new会创建在栈中,MyLinkedList obj;

引用链接: newhttps://blog.youkuaiyun.com/ytusdc/article/details/88621223

LeetCode206 反转链表

题目链接:https://leetcode.cn/problems/reverse-linked-list/description/

双指针解法:

思路:

head→口→口→口→口→null

变为

null ←口←口←口←口←head

定义一个指针pre=NULL,一个指针cur=head,cur指针向后遍历,分别把途中的节点的next指向前一个节点,直到最后一个节点

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode* pre = NULL;
        ListNode* cur = head;
        while (cur != NULL) {
            ListNode* temp = cur->next;
            cur->next = pre;
            pre = cur;
            cur = temp;
        }
        return pre;
    }
};

双指针改递归解法:

写递归的思路:

1、确定递归函数参数个数,根据双指针法可知需要两个变量实现反转,一个是cur,一个是pre,故需要两个参数

ListNode* reverse(ListNode* cur, ListNode* pre){

}

2、确定递归函数的跳出条件,根据双指针法可知,当cur指向最后一个元素,由此确定跳出递归的条件如下

class Solution {
	public:
	ListNode* reverse(ListNode* cur, ListNode* pre){
		if(cur == NULL) 
			return pre;
	}
}

3、书写处理逻辑

3.1 根据双指针法可知,最开始cur=head pre=NULL,因此在第一次调用时,需要给cur与pre赋head与NULL,如下

class Solution {
	public:
	ListNode* reverse(ListNode* cur, ListNode* pre){
		if(cur == NULL) 
			return pre;
	}
	ListNode* ListNode* reverseList(ListNode* head) {
		return reverse(head, NULL);
	}
}

3.2 当cur不是最后一个节点时,需要改变每个节点的指向,临时变量temp记录当前节点cur,cur指向下一个节点,更改temp节点next指向,如下

class Solution {
public:
	ListNode* reverse(ListNode* cur, ListNode* pre){
		if(cur == NULL)
			return pre;
		ListNode* temp = cur->next;
		cur->next = pre;
		pre = cur;
		cur = temp;
		return reverse(cur, pre);
	}
	ListNode* reverseList(ListNode* head) {
		return reverse(head, NULL);
	}
}

反思:

最开始我个人思路是临时指针temp保存当前节点,然后cur指向下一个节点,等改变了temp节点的方向后,pre指针指向temp节点,这样导致无法改写成递归

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        if (head == NULL)
            return head;
        ListNode* pre = NULL;
        ListNode* cur = head;
        while (cur->next != NULL) {
            ListNode* temp = cur;
            cur = cur->next;
            temp->next = pre;
            pre = temp;
        }
        cur->next = pre;
        head = cur;
        return head;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值