代码随想录算法训练营第三天|203.移除链表元素 707.设计链表 206.反转链表

今日前置知识

链表理论知识

今日主要题目

203.移除链表元素

707.设计链表

206.反转链表

主要学习网址

代码随想录

做题思路与具体代码

题目一:203.移除链表元素

做题思路一

思路一:不设置虚拟头结点,分两种情况讨论:

1.头结点就是要移除的元素

2.头结点不是要移除的元素

第一种情况:如果头结点是我们要移除的元素,就移除,并继续往下遍历,知道将头结点放在一个不是需要被移除的元素上

第二种情况:此时头结点已经不是我们要移除的元素,初始化移动指针cur向后走,如果cur的下一个节点不为空且下一个节点值是我们要移除的元素,则进行删除,并遍历到能够将cur放在一个不是需要被移除的元素上,记得每次循环将cur向后移动

3.结果返回头结点也就是整个链表

(记住这里两种情况每次循环结束时都需要将head以及cur放在一个不是需要被移除的元素上)

具体代码
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    // 第一种方法:不设置虚拟头结点,分两种情况
    //  1.头结点就是要移除的元素
    //  2.移除的元素不是头结点
    public ListNode removeElements(ListNode head, int val) {
    //  1.移除掉满足条件的头结点
       while(head!=null&&head.val==val){
           head=head.next;
       }    
    //  2.移除掉满足条件的但不是头结点的节点
    //   定义移动指针cur指向head,因为是为了删除元素
    //   我们要删的永远是cur.next,用的是cur.next=cur.next.next
        ListNode cur=head;
    //    遍历整个链表
        while(cur!=null){
    //   这里找到一个要继续往后找,一次性全部删除
    //   这里用while的原因是我们要讲cur放在一个确定不需要被删除的地方,因为cur所在位置的元素不能被删除,所以继  续往前删除可能正确的元素
            while(cur.next!=null&&cur.next.val==val){
    //    找到目标点就替换
                cur.next=cur.next.next;
            }
    //    没找到,cur就向前进一步
           cur=cur.next;
        }
        return head;
    }
    
}

做题思路二

思路二:采用虚拟头结点,新设立一个虚拟头结点,这样的话就不用分成两种情况,即头结点和头结点后面的都可以用同样的规则去删除元素

1.初始化虚拟头结点放在头结点前面

2.初始化移动指针指向虚拟头结点,因为移动指针需要放在一个不是需要被移除的元素上

(侧面说明前面需要前面需要先让head放在一个不是需要被移除的元素上再把移动指针放在上面的原因)

3.剩下的就和思路一一样了,即:

移动指针cur向后走,如果不是仍有下一个节点且下一个节点值是我们要移除的元素,则进行删除,并遍历到能够将cur放在一个不是需要被移除的元素上,记得每次循环将cur向后移动

4.返回头结点也就是虚拟头结点的下一节点也就是整个链表

具体代码
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    // 第二种方法:不设置虚拟头结点
    // 让删除结点全部按照统一规则进行删除
    public ListNode removeElements(ListNode head, int val) {
        //初始化虚拟头结点
        ListNode dumhead=new ListNode(-1,head);
        //初始化移动指针,指向虚拟头结点
        ListNode cur=dumhead;
        //循环条件是当前cur没走到终点
        while(cur!=null){
            // cur接下来还有值且cur接下来的值是我们想要清除的
            while(cur.next!=null&&cur.next.val==val){
                cur.next=cur.next.next;
            }
            // cur往前走一步
            cur=cur.next;
        }
        return dumhead.next; 
    }
    
}

题目二:707.设计链表

做题思路+具体代码

本题分为多个部分:

1.初始化链表  

2.得到某个位置的元素

3.在头部添加

4.在尾部添加

5.在任意位置添加

6.删除某个位置元素

分别讲解一下思路

1.首先初始化链表,需要初始化链表的长度int size=0

同时初始化头结点

具体代码

//   直接使用虚拟头结点的方法来解答
//   没有头结点,直接就是dummyhead
//   初始化链表的大小
    int size;
//   初始化虚拟头结点
    ListNode head=new ListNode(0);

//   初始化链表    
    public MyLinkedList() {
       size=0;
       head=new ListNode(0);
    }

2.得到某个位置的元素

首先判断一下下标合不合法,不合法返回-1,用一个移动指针cur用来遍历,记得采用for循环,因为是得到某个地方元素,所以这个循环的条件需要cur移动到这个位置元素所在的位置,也就是循环运行到i<=index

最后再返回cur所在位置的值

//得到某个位置的元素
    public int get(int index) {
    //    判断此时的index是否合法,不合法返回-1
       if(index<0||index>=size){
          return -1;  
       }
    //  初始化移动指针用来遍历
    //  这个cur一开始所在的位置固定在head位置,移动到哪里取决于这个方法是什么
    //  现在是要得要某个位置的元素,要移动到该元素上
    //  所以循环条件是i<=index
    ListNode cur=head;
    for (int i = 0; i <= index; i++) {
            cur = cur.next;
    }
    return cur.val;
    }

3.在头部添加

4.在尾部添加

统一代码实现具体在在任意位置添加代码中,注意传入参数的不同就行

   //在头部添加
    public void addAtHead(int val) {
         addAtIndex(0,val);
    }
    
    //在尾部添加
    public void addAtTail(int val) {
         addAtIndex(size,val);
    }

5.在任意位置添加

注意三点:

1.在第 index 个节点之前插入一个新节点,例如index为0,那么新插入的节点为链表的新头节点。

2. 如果 index 等于链表的长度,则说明是新插入的节点为链表的尾结点

 3. 如果 index 大于链表的长度,则返回空

首先要注意以上的事项,然后在一开始做判断

然后初始化要插入的节点,移动到要插入的位置的签名,也就是循环运行到i<index,然后采用这个顺序进行添加,注意这个顺序,因为newNode.next=cur.next  cur.next=newNode 因为要是顺序反了,链表就无法成功连接

(在写这些代码时最好画图来展示一下哪条线先连,哪条后连,因为每连一条线就意味着有的线要先断开)

记得添加完元素要size++

具体代码

//在任意位置添加
    public void addAtIndex(int index, int val) {
    // 在第 index 个节点之前插入一个新节点,例如index为0,那么新插入的节点为链表的新头节点。
    // 如果 index 等于链表的长度,则说明是新插入的节点为链表的尾结点
    // 如果 index 大于链表的长度,则返回空
       if(index>size){
          return;  
       }
       if (index < 0) {
          index = 0;
        } 
        
        //初始化新插入的节点
    ListNode newNode=new ListNode(val);

    //  初始化移动指针用来遍历
    //  这个cur一开始所在的位置固定在head位置,移动到哪里取决于这个方法是什么
    //  现在是要添加某个元素在一定位置上,要移动到位置的前面
    //  所以循环条件是i<index
    ListNode cur=head;
    for(int i=0;i<index;i++){
    //   移动到要插入的位置
        cur=cur.next;
    }
    //记得要按这个顺序来进行添加!!!不可更改
    newNode.next=cur.next;
    cur.next=newNode;
    //每添加一次,长度+1
    size++;
    }

6.删除某个位置元素

这部分一开始也要先判断index的合法性

然后移动到要删除元素的前面,类似于添加,比添加难

记得删除完元素要size--

(个人小总结:查询,移动指针需要到达要求位置上,增加删除需要移动指针到达要去位置前,但是这三者移动指针的起步都是一样的,都是已经被排除是目标元素的位置,本题是虚拟头结点所在位置,可以结合前面题目再进行总结)

具体代码

//删除某个位置元素
    public void deleteAtIndex(int index) {
    //    判断此时的index是否合法
       if(index<0||index>size-1){
          return;  
       }    
    //  初始化移动指针用来遍历
    //  这个cur一开始所在的位置固定在head位置,移动到哪里取决于这个方法是什么
    //  现在是要删除某个位置上的某个元素,要移动到该位置的前面
    //  所以循环条件是i<index
    ListNode cur=head;
    for(int i=0;i<index;i++){
    //移动到要删除元素的前面
       cur=cur.next;
    }
    cur.next=cur.next.next;
    //每删除一次长度减一
    size--;
    }

题目三  206.反转链表

做题思路一

使用双指针解法

1.初始化前后两个指针,前指针pre指向null,后指针cur指向head

2.两个指针分别向后走知道走到后指针指向null

3.每走一步,就进行翻转,但是要设定要临时指针temp,先存储cur.next,再进行翻转cur.next=pre

就是把链表给反向了

4.然后前后指针分别向后走,因为反转了线也断了没法找到cur.next了,所以前面锁存储的临时指针就可以派上用场了,所以前指针移动pre=cur,后指针移动cur=temp

5.最后返回pre,也就是反转后链表的头结点也就是反转后的整个链表

具体代码
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    //双指针思路
    public ListNode reverseList(ListNode head) {
        //初始化前指针和后指针
           ListNode pre=null;
           ListNode cur=head;
        // 两个指针同时向后移动,直到后指针运行到null地方退出循环
           while(cur!=null){
        //    这里需要设定个暂时值,因为后面cur.next=pre一翻转原先的连接就变了
        //    这里记得要提前存好,顺序要确定
            ListNode temp=cur.next;
        //    翻转       
            cur.next=pre;
        //  前指针向后移动
            pre=cur;
        //  后指针向后移动
            cur=temp;       
           }
        //返回翻转后链表的头结点
        return pre;
    }
}

做题思路二

本题采用递归简化思路一的双指针写法,也是双指针,只是在用递归简化了前后指针的移动

直接贴代码,这里注意每次递归的参数传入pre传入cur,cur传入temp,实现了下一次递归的初始值已经进行了移动

具体代码
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    //递归思路
    //就是本质还是双指针思路
    public ListNode reverseList(ListNode head) {
    //传入左右指针初始值
       return reverse(null,head);
    }
    private ListNode reverse(ListNode pre, ListNode cur) {
        if (cur == null) {
            return pre;
        }
       //    这里需要设定个暂时值,因为后面cur.next=pre一翻转原先的连接就变了
        //    这里记得要提前存好,顺序要确定
            ListNode temp=cur.next;
        //    翻转       
            cur.next=pre;
        //    实行递归
        return reverse(cur, temp);
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
好的,我知道了。以下是对您的问题的回答: a. 动态分配内存的方法有很多,可以使用C语言的malloc函数,也可以使用C++中的new操作符,具体方法如下: C语言的malloc函数: ```c struct ListNode* node = (struct ListNode*)malloc(sizeof(struct ListNode)); ``` C++中的new操作符: ```c++ ListNode* node = new ListNode(); ``` b. 初始化单链表需要对链表节点的成员变量进行赋值,以下是一个示例: ```c++ struct ListNode { int val; ListNode* next; ListNode(int x) : val(x), next(NULL) {} }; ListNode* head = NULL; ``` c. 头插法创建单链表的示例代码如下: ```c++ ListNode* head = new ListNode(); head->next = NULL; for (int i = 0; i < n; i++) { int x; cin >> x; ListNode* node = new ListNode(x); node->next = head->next; head->next = node; } ``` 尾插法创建单链表的示例代码如下: ```c++ ListNode* head = new ListNode(); head->next = NULL; ListNode* tail = head; for (int i = 0; i < n; i++) { int x; cin >> x; ListNode* node = new ListNode(x); node->next = NULL; tail->next = node; tail = node; } ``` d. 取链表某一位的值可以通过遍历链表来实现,以下是一个示例: ```c++ ListNode* p = head->next; int pos = 3; for (int i = 1; i < pos && p != NULL; i++) { p = p->next; } if (p == NULL) { cout << "链表长度不足" << endl; } else { cout << p->val << endl; } ``` e. 在链表的某一位进行插入操作可以通过遍历链表找到插入位置,然后插入一个新节点来实现,以下是一个示例: ```c++ ListNode* p = head; int pos = 3; for (int i = 1; i < pos && p != NULL; i++) { p = p->next; } if (p == NULL) { cout << "链表长度不足" << endl; } else { int x = 100; ListNode* node = new ListNode(x); node->next = p->next; p->next = node; } ``` f. 遍历链表可以通过循环遍历链表节点来实现,以下是一个示例: ```c++ ListNode* p = head->next; while (p != NULL) { cout << p->val << " "; p = p->next; } cout << endl; ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值