JZ18 删除链表中的节点(提供头结点和被删除节点的val)

给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。返回删除后的链表的头节点。

1.此题对比原题有改动

2.题目保证链表中节点的值互不相同

输入:{2,5,1,9},5
返回值:
{2,1,9}

思路:

删除一个链表节点,肯定是断掉它的前一个节点指向它的指针,然后指向它的后一个节点,即越过了需要删除的这个节点。

pre.next = cur.next;//断开链接

  • step 1:首先我们加入一个头部节点,方便于如果可能的话删除掉第一个元素。
  • step 2:准备两个指针遍历链表,一个指针指向当前要遍历的元素,另一个指针指向该元素的前序节点,便于获取它的指针。
  • step 3:遍历链表,找到目标节点,则断开连接,指向后一个。
  • step 4:返回时去掉我们加入的头节点。
public ListNode deleteNode (ListNode head, int val) {
    
   ListNode dummy = new ListNode(0);
        dummy.next = head;
        ListNode pre = dummy;
        ListNode cur = head;
        while(cur != null){
            if(cur.val == val){
               pre.next = cur.next;
                break; 
            }             
            pre = cur;
            cur = cur.next;
        }
        return dummy.next;
       
        //不维护pre节点,只维护cur节点
         /*
        ListNode dummy = new ListNode(-1);
        dummy.next = head;
        ListNode cur = dummy;
        while(cur.next !=null){
            if(cur.next.val == val){
                cur.next = cur.next.next;
                break;
            }
            cur = cur.next;
        }
        return dummy.next;
         */
  

}

原题:

题目:在O(1)时间内删除链表节点。给定单向链表的头指针和一个节点指针,定义一个函数deleteNode()在O(1)时间内删除该节点

在单向链表中删除一个节点,常规的做法一般就是从链表的头节点开始遍历查找要删除的节点,并在链表中删除该节点。如下图中的(a)链表,我们若想删除节点[i],则需要从头节点[a]开始顺序遍历,得到节点[i]的上一个节点[h],将节点[h]的next指向节点[i]的下一个节点[j],如图中的(b),实现删除。单向链表中删除节点的常规做法需要O(n)的时间复杂度。 

既然从头节点遍历的时间复杂度不满足要求,那我们能不能不遍历呢?

其实我们可以如图中的(c)那样,将节点[i]的下一节点[j]的值赋给节点[i],然后将节点[i]的next指向节点[j]的next,最后删除节点[j]。这种方法时间复杂度为O(1),满足题目要求。

需要注意两点:1.如果要删除的节点是链表的尾节点,那么依然需要从头节点开始遍历得到尾节点的上一节点;2.如果链表中只有头节点,并且要删除的节点就是头节点,此时删除后链表应该为null。

以下代码中deleteNode()方法传入链表头节点和一个要删除的节点(默认就是链表中的一个节点,没有考虑该节点不是链表中的节点的情况),返回删除节点后的链表。
 

public ListNode deleteNode(ListNode head, ListNode del) {

    if(head == null || del == null)
        return null;

    if(del.next != null) { //删除的不是链表的尾节点
        del.val = del.next.val;
        del.next = del.next.next;
    }
    else if(head == del){ //链表只有一个头节点
        return null;
    }
    else {// 要删除的节点是尾节点(使用常规方法:顺序遍历删除)
        ListNode pre = head;
        ListNode cur = head.next;
        while(cur != del) {
            pre = cur;
            cur = cur.next;
        }
        pre.next = null;
    }
    return head;
}

<think>我们正在讨论C/C++中的逻辑非运算符`!`在条件语句`if (!head)`中的用法。 根据用户的问题,需要详细解释`if (!head)`中感叹号的用法。 注意:用户提供的引用中涉及了JavaScriptRuby等语言的用法,但用户当前问题明确指向C/C++(因为`head`很可能是指针或布尔上下文)。 在C/C++中,`if (!head)`中的`!`是逻辑非运算符,用于对操作数进行逻辑取反。 核心知识点: 1. 逻辑非运算符`!`将其操作数视为布尔:如果操作数为非零(或非空指针),则`!`运算结果为`false`;如果操作数为零(或空指针),则结果为`true`。 2. 在条件判断中,指针`head`会被隐式转换为布尔:空指针转换为`false`,非空指针转换为`true`。 3. 因此,`if (!head)`等价于`if (head == nullptr)`(在C++11及以上)或`if (head == NULL)`(在C旧版C++)。 详细步骤: - 如果`head`是空指针,则`head`在布尔上下文中为`false`,`!head`则为`true`,因此条件成立。 - 如果`head`非空,则`head`在布尔上下文中为`true`,`!head`为`false`,条件不成立。 示例: ```cpp ListNode* head = nullptr; // 空指针 if (!head) { // 这里会执行,因为head为空,!head为true } head = new ListNode(1); if (!head) { // 这里不会执行,因为head非空,!head为false } ``` 注意:在C++中,空指针的明确表示是`nullptr`(C++11引入),在C中通常用`NULL`(定义为0)。使用`!`进行判断是通用的。 与JavaScript的区别: 用户提供的引用[1]中提到了JavaScript的双感叹号(!!)用于将转换为布尔类型,但在C++中,单个`!`在条件语句中已经起到了将操作数转换为布尔并取反的作用。如果要在C++中模拟JavaScript的!!,可以写`!!head`,但这通常没有必要,因为条件判断本身就会进行布尔转换。 在C++中,`if (head)``if (!head)`是判断指针是否为空的标准写法。 引用[2]中提到的感叹号在方法名中的使用(如Ruby中的bang方法)与C++的运算符无关。 因此,对于C++,`if (!head)`就是判断指针`head`是否为空。 相关问题: 1. 在C++中,除了指针,其他类型(如整数、浮点数)在条件语句中如何求? 2. 逻辑非运算符`!`其他逻辑运算符(如`&&`, `||`)的优先级是怎样的? 3. 在C++中,如何安全地使用指针以避免空指针解引用? 根据用户要求,我们需要在回答中自然地添加引用标识。但是,由于用户提供的引用主要是关于JavaScriptRuby,与当前问题关系不大,我们可以不直接引用,或者如果必须引用,可以这样处理: - 引用[1]提到JavaScript中感叹号的用法,我们可以对比说明C++中的不同。 - 但为了简洁,我们主要解释C++中的用法。 下面组织回答: </think> ### 详细解析 `if (!head)` 中感叹号 `!` 的用法 在 C/C++ 中,`if (!head)` 语句中的感叹号 `!` 是**逻辑非运算符**(logical NOT operator),其核心作用是进行布尔的取反操作。以下是关键知识点解析: #### 一、基础语法与语义 ```cpp if (!head) { // 当 head 为"假"时执行的代码 } ``` 1. **运算符功能**: `!` 对其操作数执行**布尔取反**操作: - 若 `head` 为**真**(truthy) → `!head` 返回 `false` - 若 `head` 为**假**(falsy) → `!head` 返回 `true` 2. **指针上下文**: 当 `head` 是指针类型时(如链表/树节点): - `head` 为 `nullptr`(空指针) → 假 → `!head` 为 `true` - `head` 指向有效内存地址 → 真 → `!head` 为 `false` ```cpp ListNode* head = nullptr; if (!head) { // 条件成立,因为 head 是空指针 cout << "链表为空"; } ``` #### 二、假判定规则(Falsy Values) 在 C/C++ 中,以下被认定为假: | 类型 | 假示例 | 说明 | |--------------|-------------------------|--------------------------| | 指针 | `nullptr`, `NULL`, `0` | 空指针 | | 整数 | `0` | 零 | | 浮点数 | `0.0` | 零 | | 布尔 | `false` | 字面假 | | 自定义类型 | `operator bool()` 返回 `false` | 需重载布尔转换运算符 | #### 三、对比 JavaScript 的双感叹号 `!!` 虽然用户引用提到 JavaScript 的 `!!`(如引用[1][^1]),但 C++ 中的单 `!` 有本质区别: - JavaScript 的 `!!`:强制转换为布尔类型(类型转换) - C++ 的 `!`:纯逻辑运算(不改变操作数类型) ```cpp // C++ 示例 int x = 10; bool a = !x; // false(逻辑运算) bool b = !!x; // true(类似 JS 的布尔转换) ``` #### 四、典型应用场景 1. **空指针检查**(最常见用途): ```cpp void traverse(ListNode* head) { if (!head) return; // 递归终止条件:遇到空指针 cout << head->val; traverse(head->next); } ``` 2. **错误处理**: ```cpp FILE* file = fopen("data.txt", "r"); if (!file) { // 等价于 if(file == nullptr) cerr << "文件打开失败"; exit(1); } ``` 3. **布尔标志判断**: ```cpp bool isValid = checkData(); if (!isValid) { rollbackOperation(); // 状态无效时回滚 } ``` #### 五、底层实现原理 编译器会将 `if (!head)` 转换为: ```assembly test head, head ; 检测 head 的 jz .L1 ; 若为零(假)则跳转 ``` - **零开销抽象**:现代编译器生成高效指令,无函数调用开销 - **分支优化**:CPU 流水线友好,比 `if (head == nullptr)` 无性能差异 #### 六、注意事项 1. **自定义类型**: 若 `head` 是类对象,需正确定义 `operator bool()`: ```cpp class SmartPtr { public: operator bool() const { return ptr != nullptr; } }; ``` 2. **避免误用**: `!` 不可用于浮点数精度比较: ```cpp double x = 0.0000001; if (!x) { ... } // 错误!可能误判非零小数为零 ``` 3. **运算符优先级**: `!` 优先级高于多数运算符,但低于后缀运算符: ```cpp if (!ptr->next) // 正确:等价于 !(ptr->next) if (!ptr.next) // 错误:需写成 !(ptr.next) ``` ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值