链表
题意:给你一个链表的头节点head和一个整数val,请你删除链表中所有满足Node.val == val的节点,并返回新的头节点 。
示例:
思路:由于链表删除会涉及到头节点删除和中间节点删除,两种删除方式存在差异,因此,本题有两种解题思路:1.暴力删除法,我们可以将头节点删除和中间节点删除分开讨论,若该节点是头节点,head指针向后移动即可,若是不是,则正常删除。2.头节点法:添加一个头节点,这样所有节点都是中间节点,然后正常删除,返回头节点的下一个节点即可。
方法1代码:
ListNode* removeElements(ListNode* head, int val) {
while(head!=nullptr&&head->val==val)//头节点删除
{
head=head->next;
}
ListNode* cur=head;
while(cur!=nullptr&&cur->next!=nullptr)//中间节点删除
{
if(cur->next->val==val)
{
cur->next=cur->next->next;
continue;
}
cur=cur->next;
}
return head;
}
方法2代码:
ListNode* removeElements(ListNode* head, int val) {
ListNode* cur=new ListNode(0);//创建头节点
cur->next=head;
ListNode* tmp=cur;
while(tmp->next!=nullptr)
{
if(tmp->next->val==val)//如果相等,可能出现相连的重复元素,删除需要用到continue,防止漏删
{
tmp->next=tmp->next->next;//连接之后节点
continue;
}
tmp=tmp->next;//向后移动
}
return cur->next;
}
我们不难发现,当链表有头节点时,思路就比较简单,代码量也比较简单。因此,在我们写有关链表操作的题时,最好还是将头节点带入,这样可以省去很多分类讨论的思路,代码量也比较小。
题意:
示例:
思路:本题就是实现链表的一些基本操作,准备工作:这里我们需要给出节点的结构体,并且为了方便后面插入和删除操作,我们将该链表设置为具有头节点的链表;其次为了判断插入删除操作是否合法,需要给出节点的实际个数。
对于初始化MyLinkedList(),其实就是创建头节点,初始化链表的节点数为0;对于获取指定位置的节点get(),我们需要注意,index相当于数组的下标,因此我们要把握好下标的边界;对于头插和尾插就比较简单了addAtHead()/addAtTail(),由于我们构建的链表具有头节点,找到正确位置插入即可;对于插入任意位置的前置位addAtIndex(),为了体现代码的复用性我分为了四类:不可插入,头插,尾插,中间插入,我们只需要给出中间插入的代码即可;对于删除某一位置的节点deleteAtIndex(),我们需要主义的也是index的范围问题,必须合法。因此,我们得到以下代码
class MyLinkedList {
public:
struct LinkNode {//我们需要创建节点的结构体
int _val;
LinkNode* _next;
LinkNode(int val) :_val(val), _next(nullptr)//默认的结构体
{}
};
MyLinkedList() {
sum = 0;
head = new LinkNode(0);//需要创建一个头节点,便于之后插入和删除
}
int get(int index) {
if (index+1>sum || index<0)//这里需要注意:index相当于数组的下标,因此,index+1必须小于等于sum才是合法的下标
{
return -1;
}
LinkNode* cur = head->_next;
while (index--)
{
cur = cur->_next;
}
return cur->_val;
}
void addAtHead(int val) {//头插,有头节点,插入比较简单
LinkNode* newNode = new LinkNode(val);
newNode->_next = head->_next;
head->_next = newNode;
sum++;
}
void addAtTail(int val) {//尾插
LinkNode* newNode = new LinkNode(val);
LinkNode* cur = head;
while (cur->_next)
{
cur = cur->_next;
}
cur->_next = newNode;
sum++;
}
void addAtIndex(int index, int val) {//加入任意位置
if (index>sum)//不符合条件,直接退出
{
return;
}
else if (index <= 0)//相当于头插
{
addAtHead(val);
}
else if (index == sum)//相当于尾插
{
addAtTail(val);
}
else//中间节点插入
{
LinkNode* cur = head;
LinkNode* newNode = new LinkNode(val);
while (index--)
{
cur = cur->_next;
}
newNode->_next = cur->_next;
cur->_next = newNode;
sum++;
}
}
void deleteAtIndex(int index) {
if (index < sum&&index>=0)//必须是合法的下标,才能进行删除操作
{
LinkNode* cur = head;
while (index--)
{
cur = cur->_next;
}
cur->_next = cur->_next->_next;
sum--;
}
}
private:
int sum;
LinkNode* head;
};
题意:给你单链表的头节点head,请你反转链表,并返回反转后的链表。
示例:
思路:1.三指针法,不难理解,我们先定义三个指针(tmp,cur,_head),tmp初始化为nullptr,cur初始化为head,_head初始化为head->next,然后让cur指向tmp,然后不断向后移动,_head的目的就是为了记录原cur的下一个节点,这里需要注意:当_head第一次为再次为空时,就说明反转结束,可以跳出循环了;2.双指针法:其实和三指针类似,给出的_head指针只需要一直复制原cur的下一个指针即可,就不需要担心空链表和_head越界的问题了;3.递归:其实就是双指针的另一种写法,将指针移动的过程转换为了参数的传递。
方法1代码:
ListNode* reverseList(ListNode* head) {
if(head==nullptr)//排除链表为空的情况
{
return head;
}
//利用三指针的方法,主要是中间指针指向末尾指针的操作,最前头的指针用于保存节点
ListNode* tmp = nullptr;//后
ListNode* cur = head;//中
ListNode* _head = cur->next;//前
while (cur != nullptr)//终止条件
{
cur->next = tmp;
tmp = cur;
cur = _head;
if (_head == nullptr)//最前头的指针首次为空,就说明到了末尾,此时就说明反转结束,可以跳出
break;
_head = _head->next;
}
return tmp;
}
方法2代码:
ListNode* reverseList(ListNode* head) {
ListNode* tmp=nullptr;
ListNode* cur=head;
ListNode* _head=nullptr;
while(cur)
{
_head=cur->next;
cur->next=tmp;
tmp=cur;
cur=_head;
}
return tmp;
}
方法3代码:
ListNode* reList(ListNode* cur,ListNode* tmp)
{
if(cur==nullptr)
{
return tmp;
}
ListNode* _head=cur->next;
cur->next=tmp;
return reList(_head,cur);
}
ListNode* reverseList(ListNode* head) {
return reList(head,nullptr);
}
反转链表我还是建议大家使用双指针的方式,代码量其实相较于递归并没有多很多,反而思路更加清晰了。