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

day_03 - 链表部分

传送门

1. 链表基础知识

C语言的链表定义

 struct ListNode {
     int val;
     struct ListNode *next;
 };

java的链表定义

//  没放在同一个包里面,所以用了 public
public class ListNode {
  public int val;
  public ListNode next;
  ListNode() {}
  ListNode(int val) { this.val = val; }
  ListNode(int val, ListNode next) { this.val = val; this.next = next; }
}

2. 题解部分

203 - 移除链表元素

  • 思路及代码
    • 添加虚拟头,让处理的时候排除头节点的特判情况
    • 遍历整个链表,在这里选择了一前一后指针判断,分情况进行处理
struct ListNode* removeElements(struct ListNode* head, int val){
    typedef struct ListNode ListNode;   //  里面也可以给结构体改名,作用域为当前文件

    ListNode* add_head = malloc(sizeof(ListNode));
    add_head -> next = head;               //  新添加的头节点指向原来的链表

    //  定义 p1, p2 指针一前一后对链表进行处理
    ListNode* p1 = add_head;            // p1 在前
    ListNode* p2 = head;                // p2 在后
    ListNode* p_free = NULL;

    while(p2 != NULL){                  // p2 遍历链表
        if(p2->val == val) {            //  遇到要删除的
            p_free = p2;
            p2 = p2 -> next;
            // free(tmp);
            p1 -> next = p2;
        }
        else{
            p1 -> next = p2;
            p2 = p2 -> next;
            p1 = p1 -> next;
        }
    }

    return add_head -> next;
}
  • 优化,遍历处理的时候,我们需要知晓相邻的两个节点,链表天生就有这样的优势
    • 当前的节点可以很轻松的获取到 next 节点
    • 上面的双指针移动变为单指针移动!可以减少一半的指针移动操作!
//  直接抄袭了卡哥文章里的代码
struct ListNode* removeElements(struct ListNode* head, int val){
    typedef struct ListNode ListNode;
    
    ListNode *shead;
    shead = (ListNode *)malloc(sizeof(ListNode));
    shead->next = head;
    ListNode *cur = shead;
    
    while(cur->next != NULL){
        if (cur->next->val == val){
            ListNode *tmp = cur->next;
            cur->next = cur->next->next;
            free(tmp);
        }
        else{
            cur = cur->next;
        }
    }
    
    head = shead->next;
    free(shead);
    return head;
}
  • 之前写过的java题解,现在看来,太烂了,自己看着都费劲
public class RemoveLinkedListElement {
    public ListNode removeElements(ListNode head, int val) {

        while (head!=null && head.val == val)       //  处理原先的表头,直至表头不为 val
            head = head.next;

        ListNode helper = head;                     //  用于遍历的指针

        while (helper != null && helper.next != null){          //  经过上面的处理后,链表的可能情况: [] ,[ e1 ], [e1,e2,e3...]
            if (helper.next.val == val && helper.next != null)  //  [e1,e2,e3...] 情况
                helper.next = helper.next.next;
            else if(helper.next.val == val && helper.next == null)  //  [ e1 ] 情况
                helper.next = null;
            else
                helper = helper.next;
        }

        return head;
    }
}

707 - 设计链表

有一种梦回数据结构的感觉,呜呜呜
这个实现暂时先放一边,后续过来补上

206 - 反转链表

  • 思路一 : 新建链表,单指针操作

    • 构建一个虚拟头节点
    • 单指针遍历原来的链表,对每个节点使用头插法插入虚拟头后面
    • 结束后返回虚拟头的next
  • 代码如下 (思路一)

    • 循环式
    //	java 实现
    class Solution {
          public ListNode reverseList(ListNode head) {
          if(head == null) return null;
    
          ListNode newHead = new ListNode(-999);
    
          while (head != null){
              ListNode add = new ListNode(head.val);
              add.next = newHead.next;
              newHead.next = add;
              head = head.next;
          }
          
          return newHead.next;
      }
    } 
    
    • 递归式
    //	Java实现
    class Solution {
          public ListNode reverseList(ListNode head) {
          if(head == null) return null;
    
          ListNode newHead = new ListNode(-999);
          add(head,newHead);
          
          return newHead.next;
      }
      
    //    头插法处理
      public void add(ListNode add,ListNode head){
          if (add == null) return;
          
          ListNode temp = new ListNode(add.val);
          temp.next = head.next;
          head.next = temp;
          
          add(add.next,head);
      }
    }
    

休息休息

  • 思路二 : 修改原表,双指针操作

    • 新建一个 null 的头节点
    • 新建两个指针,p1在前,指向新的头节点,p2在后,指向原来的头节点
    • p2向前遍历链表,每次遍历的同时修改, 令p2.next = p1,p2向后移动(需要一个额外指针帮助正确移动)
  • 代码如下 (思路二)

    • 循环代码

      • C语言
      struct ListNode* reverseList(struct ListNode* head){
        typedef struct ListNode ListNode;
      
        ListNode* pre = NULL;               //  就是 p1
        ListNode* cur = head;               //  就是 p2
        ListNode* ptr = head;          //  帮助找到下一节点的指针
      
        while(cur != NULL){
            ptr = ptr->next;
            cur -> next = pre;
            pre = cur;
            cur = ptr;
        }
      
        return pre;
      }
      
      • java (直接抄了卡哥的)
      class Solution {
          public ListNode reverseList(ListNode head) {
              ListNode prev = null;
              ListNode cur = head;
              ListNode temp = null;
              while (cur != null) {
                  temp = cur.next;// 保存下一个节点
                  cur.next = prev;
                  prev = cur;
                  cur = temp;
              }
              return prev;
          }
      }
      
    • 递归代码

      • C语言
      //  递归时,每次只处理相邻两个节点的关系
      //  所以将待处理的两个节点传入递归函数
      //  退出递归的条件是: 传入的两个一前一后节点, 后面的节点为 NULL
      //  递归退出时,返回一个目标值
      typedef struct ListNode ListNode;
      
      ListNode* reverse(ListNode* pre, ListNode* cur){
      if(cur != NULL) {
        ListNode* nextCur = cur -> next;    //  下一个要传入的cur
        cur -> next = pre;
        return reverse(cur, nextCur);
        }
        return pre;
      }
      
      struct ListNode* reverseList(struct ListNode* head){
        return reverse(NULL, head);
      }
      
      • java (抄了卡哥的)
      class Solution {
        public ListNode reverseList(ListNode head) {
            return reverse(null, head);
        }
      
        private ListNode reverse(ListNode prev, ListNode cur) {
            if (cur == null) {
                return prev;
            }
            ListNode temp = null;
            temp = cur.next;// 先保存下一个节点
            cur.next = prev;// 反转
            // 更新prev、cur位置
            // prev = cur;
            // cur = temp;
            return reverse(cur, temp);
            }
        }
      

3. 使用C语言定义链表时的一些疑问

  1. 定义时,指针的内存大小指针的大小

  2. C语言语句的编译顺序,以及 * 如何被解释的指针修饰规则

  3. 语法上如何判断指针修饰的是什么修饰的关键字是什么

  4. 如何正确的声明一个链表的结构体出错了两处地方,listNode指的是单个声明好的struct

  5. malloc申请内存 + 赋值过程申请内存

  6. 如何释放内存 - freefree

  7. 指针释放细节释放细节

  8. 感谢爪哇!不用与直面指针了

  9. C很需要再补一补基础

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值