设计链表的五个常用技巧

一、获取第N个节点的数值

在链表中,尤其是单链表,头节点的处理有时会带来额外的复杂性。比如,在链表为空的情况下,想要访问头节点或者在其前面插入新的节点时,就需要特别小心地处理边界情况。为了避免这类复杂性,常常会在链表的头部添加一个虚拟的头节点(也叫作哑节点或哨兵节点),这个节点并不存储实际的数据,仅用于简化算法逻辑。

通过引入虚拟头节点 `dummyHead`,可以统一处理链表中的节点操作,而不需要针对头节点进行特判。例如,当我们要获取链表的头节点的值时,可以使用 `dummyHead.next.val` 来代替直接操作 `head.val`。

这样做有几个好处:

1. **简化插入操作**:在链表的开头插入新节点时,只需要修改 `dummyHead.next` 的指向即可,而不需要单独处理头节点的情况。
   
2. **简化删除操作**:删除头节点时,同样只需改变 `dummyHead.next` 的指向,而不需要单独编写处理头节点的代码。

3. **统一迭代逻辑**:在遍历链表时,可以统一迭代逻辑,不需要特判是否是头节点。

以下是一个简化的示例,展示如何通过虚拟头节点来访问头节点的值:

在这个例子中,`dummyHead` 是一个虚拟的头节点,它指向真正的头节点。当链表为空时,`dummyHead.next` 为 `null`,此时尝试获取头节点的值将抛出异常。

public int get(int index) {
       if(index<0 || index >= size){
        return -1;
       }
       ListNode dummy=new ListNode();//设置虚拟节点
       dummy.next=head;
       for(int i=0;i<=index+1;i++){
        dummy=dummy.next;//通过循环遍历找到需要节点的数值
       }
       return dummy.val;
    }
    

二、在头部插入节点

 public void addAtHead(int val) {
        ListNode newnode = new ListNode(val);//创建新的节点
        newnode.next=head.next;
        head.next=newnode;//在头部插入
        size++;

    }

三、在尾部插入节点

 public void addAtTail(int val) {
        ListNode newNode = new ListNode(val);
        ListNode cur = head;
        while(cur.next != null){
            cur=cur.next;
        }
        cur.next=newNode;
        size++;

    }

四、在第N个节点前插入节点

 

1. 参数检查

  • 检查索引是否超出链表长度:如果提供的索引 index 大于链表的当前大小 size,那么直接返回,不做任何插入操作。这是因为索引超出了链表的范围。
  • 处理负索引:如果提供的索引 index 小于0,那么将其设置为0。这相当于把新节点插入到链表的头部。

2. 修改链表大小

  • 更新链表大小:在实际插入节点之前,链表的大小 size 增加1。这是因为无论插入操作成功与否,链表的实际元素数量都会增加一个。

3. 定位前驱节点

  • 定位前驱节点:创建一个名为 pred 的临时变量来追踪当前索引的前驱节点。通过循环遍历链表,每次迭代都将 pred 向前移动一步,直到达到目标索引 index 的前一个节点。

4. 插入新节点

  • 创建新节点:创建一个新的 ListNode 实例 adds,并将其值设置为 val
  • 调整指针:新节点 adds 的 next 指针指向当前 pred 节点的下一个节点。
  • 插入节点:更新 pred 节点的 next 指针,使其指向新创建的节点 adds。这样就完成了在索引 index 处插入新节点的操作。

代码流程实例

假设链表当前的状态如下:

head -> 1 -> 2 -> 3 -> null

链表的大小 size 为 3。

如果调用 addAtIndex(1, 5),即在索引 1 处插入值为 5 的节点:

  1. 检查 index 是否大于 size(3),不是;
  2. 检查 index 是否小于 0,不是;
  3. 增加 size 到 4;
  4. 从 head 开始,迭代 i 次(这里是 1 次),找到前驱节点 pred,即 head 的下一个节点 1
  5. 创建新节点 adds,其值为 5
  6. adds.next 指向 pred.next,也就是 2
  7. pred.next 指向 adds

最终链表变为:

head -> 1 -> 5 -> 2 -> 3 -> null

通过这种方式,可以在单链表的任意位置插入新节点,并且通过虚拟头节点 head 的使用,可以简化插入操作的代码逻辑。

代码:

public void addAtIndex(int index, int val) {
        if (index > size) {
            return;
        }
        if (index < 0) {
            index = 0;
        }
        size++;
        ListNode pred = head;
        for(int i=0;i<index;i++){
            pred=pred.next;
        }
        ListNode adds=new ListNode(val);
        adds.next=pred.next;
        pred.next=adds;

    }

五、删除某个节点的代码

public void deleteAtIndex(int index) {
          if (index < 0 || index >= size) {
            return;
        }
        size--;
        //因为有虚拟头节点,所以不用对Index=0的情况进行特殊处理
        ListNode pred = head;
        for (int i = 0; i < index; i++) {
            pred = pred.next;
        }
        pred.next = pred.next.next;
    }

这种方法不仅简化了代码,而且提高了代码的健壮性和可读性。使用虚拟头节点的方式非常适合那些需要频繁处理头节点场景的应用程序。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值