删除链表中的节点(递归/迭代)

1.删除排序链表的重复元素(82-中)

题目描述:存在一个按升序排列的链表,给你这个链表的头节点 head ,请你删除链表中所有存在数字重复情况的节点,只保留原始链表中 没有重复出现 的数字。返回同样按升序排列的结果链表。

示例

输入:head = [1,2,3,3,4,4,5]
输出:[1,2,5]

思路:本题通过递归和迭代实现,高频面试题。

法1:递归(自上而下):

  • 递归函数:删除全部重复节点
  • 终止条件:head == null || head.next == null(没有元素或只有一个元素,直接返回)
  • 递归逻辑:比较当前值和下一个节点的值,值相同,记录第一个重复的元素,找到下一个值不同的元素继续递归;值不同,直接连接,继续下一个节点。

法2:迭代实现,由于链表元素是升序的,所以我们进行一次遍历,就能删除链表中的重复元素。具体如下:

  • 如果cur.next与cur.next.next对应的元素不同,那么直接移动cur指针
  • 否则,记录cur.next这个值为x,从cur.next开始,将值等于x节点全部删除。注意:递归中我们是直接舍弃重复元素(从不重复的元素开始),这里重复元素应该从头结点开始遍历(虚拟节点下一个节点)!

注意:由于链表的头节点也可能被删除,所以我们可以使用一个虚拟节点指向链表的头结点(这与83题不同,节点元素唯一)

// 代码1:递归
public ListNode deleteDuplicates(ListNode head) {
    if (head == null || head.next == null) {
        return head;
    }
    if (head.val == head.next.val) {
        ListNode temp = head.next;
        while (temp != null && head.val == temp.val) {
            // 删除重复节点
            temp = temp.next;
        }
        return deleteDuplicates(temp);
    } else {
        head.next = deleteDuplicates(head.next);
    }
    return head;
}

// 代码2:迭代
public ListNode deleteDuplicates(ListNode head) {
    if (head == null) {
        return head;
    }
    ListNode dummyHead = new ListNode();
    dummyHead.next = head;
    ListNode cur = dummyHead;

    while (cur.next != null && cur.next.next != null) {
        if (cur.next.val == cur.next.next.val) {
            int x = cur.next.val;
            while (cur.next != null && cur.next.val == x) {
                // 删除重复节点
                cur.next = cur.next.next;
            }
        } else {
            cur = cur.next;
        }
    }
    return dummyHead.next;
}

2.删除排序链表的重复元素(83-易)

题目描述:删除一个链表中所有重复的元素,保证出现元素的唯一性。与上一题不同的是本题相同元素保留1个。注意链表已经排序。

示例

输入: 1->1->2
输出: 1->2

思路:本题通过递归和迭代实现。

法1:递归(自下而上):

  • 递归函数:找到不重复(元素唯一)链表头结点
  • 终止条件:head == null || head.next == null(没有元素或只有一个元素)
  • 递归逻辑:比较当前值和下一个节点的值,若相同则返回当前节点的下一个节点,否则返回当前节点。

法2:迭代实现:定义两个指针,找到重复元素的区间:

  • pre指针记录重复节点区间的开始;
  • cur指针记录重复节点区间的结尾,cur.next是下一个不重复的元素;
  • 更新pre和cur(保证相邻节点值不同)。
// 代码1:递归
public ListNode deleteDuplicates(ListNode head) {
    if (head == null || head.next == null) return head;  
    head.next = deleteDuplicates(head.next);   //节点连接
    return head.val == head.next.val ? head.next : head;  
}

// 代码2:迭代
public ListNode deleteDuplicates(ListNode head) {
    ListNode cur = head, pre = head;
    while (cur != null) {
        while (cur.next != null && cur.val == cur.next.val) {
            cur = cur.next;
        }
        pre.next = cur.next;  
        cur = cur.next;
        pre = cur; 
    }
    return head;
}

3.删除链表元素(203-易/剑指18)

题目描述:给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点

示例

输入:head = [1,2,6,3,4,5,6], val = 6
输出:[1,2,3,4,5]

思路:本题通过递归和迭代实现。

法1:递归(自下向上,类似T83):

  • 递归函数:获取不含指定元素的链表
  • 终止条件:head == null(没有元素)!
  • 递归逻辑:比较当前节点是不是要删除的节点。如果是继续递归下一个节点,否则进行连接,并返回。

法2:迭代实现:

  • 遍历链表,如果等于给定值,删除(重新连接);否则,更新cur指针。
  • 注意迭代退出的条件,因为本题不是和当前节点的下一个节点进行比较,而是给定了目标值,所以只需要保证cur.next != null即可。

ps:因为头结点可能被删除,所以我们创建虚拟节点,保证每个节点都有前驱节点,这样不用单独考虑删除头结点的情况。(这一点与T82相同)

// 代码1:递归
public ListNode removeElements(ListNode head, int val) {
    if (head == null) {
        return null;
    }
    head.next = removeElements(head.next, val);
    return head.val == val ? head.next : head;
}

// 代码2:迭代
public ListNode removeElements(ListNode head, int val) {
    if (head == null) return head;
    ListNode dummyHead = new ListNode(val - 1);
    dummyHead.next = head;
    ListNode cur = dummyHead;

    while (cur.next != null) {
        if (cur.next.val == val) {
            // 删除节点并连接
            cur.next = cur.next.next;
        } else {
            cur= cur.next;
        }
    }
    return dummyHead.next;
}

4.删除链表元素(237-易)

题目描述函数参数传入要删除的节点,即在O(1)时间复杂度删除该节点。,所以不能遍历链表查找元素。

示例

输入:head = [4,5,1,9], node = 5
输出:[4,1,9]
解释:给定你链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9.

思路:由于题目没有给出头结点,我们无法遍历链表(遍历也不满足时间复杂度要求),更不知道要删除节点的前一个节点,这样我们就不能使用传统的方式删除。

虽然不知道前驱节点,但我们知道要删除节点的下一个节点,解决方案:将要删除的节点赋值为下一个节点的值,然后转化为删除下一个节点。例如:

[4,5,1,9], node = 5  -> [4,1,1,9], 这时就可以删除当前节点的下一个节点。

代码实现:

public void deleteNode(ListNode node) {
    node.val = node.next.val;
    node.next = node.next.next;
}

5. 删除链表的倒数第 n 个节点(19-中/剑指22)

题目描述:删除链表的倒数第 n 个节点,返回链表的头结点。进阶:使用一次遍历!

剑指22要求输出链表中的倒数第n个节点,而不是删除,其实想一下,我们想一下我们在19中删除是先找到这个待删除的点,但是现在我们找的不是倒数第n个节点,而是他的前继节点,然后做返回(类似删除),代码大同小异,自行实现。

示例:

1.删除链表倒数第n个节点

给定一个链表: 1->2->3->4->5, 和 n = 2.
当删除了倒数第二个节点后,链表变为 1->2->3->5.

2.输出链表中的倒数第n个节点
给定一个链表: 1->2->3->4->5, 和 k = 2.
返回链表 4->5.

思路:

法1:两次遍历比较简单,第一次遍历计算链表长度,第二次遍历找到该点删除(找到删除节点的上一个节点)。注意:需要单独判断删除头节点的问题!

法2:在递归求解链表长度时删除,递归的组织方式是从后向前,即当递归遍历到第n个节点就表示链表倒数第n个节点,我们需要在递归时找到倒数第n+1个节点,改变指针就可以

法3:进阶问题:可以使用快慢指针,将倒数第n个元素对称位置作为快指针起点!!当快指针到达终点,慢指针的坐标即为待删除节点!原理:快慢指针的行程相同。

ps:假设链表长度len,那么快指针位于第n个节点位置,再走len - n到达终点,这时慢指针也走了len - n,这样我们就省去了求链表长度的过程,一次遍历即可删除。

代码:

// 代码1:两次遍历
public ListNode removeNthFromEnd(ListNode head, int n) {
    ListNode cur = head;
    int len = 0;
    while (cur != null) {
        len++;
        cur = cur.next;
    }
    cur = head;
    
    int k = len - n;
    if (k == 0) return head.next;  // 删除头结点
    while (k-- > 1) {
        cur = cur.next;    //获取待删除的前一个节点
    }
    cur.next = cur.next.next;
    return head;
}

//代码2:递归求解
public ListNode removeNthFromEnd_1(ListNode head, int n) {
    int pos = length(head, n);
    if (pos == n) return head.next;   //单节点时,即删除头结点
    return head;
} 

private int length(ListNode node, int n) {
    if (node == null) return 0;
    int pos = length(node.next, n) + 1; 
    // 遍历到倒数第n+1个节点,即待删除节点的前一个节点
    if (pos == n + 1) {   
        node.next = node.next.next;  
    }
    return pos;
}

// 代码3:快慢指针(推荐,很巧妙!!)
public ListNode removeNthFromEnd_3(ListNode head, int n) {
    ListNode fast = head, slow = head;
    // 快指针起点(对称位置)
    while (n-- > 0) {
        fast = fast.next;
    }
    
    if (fast == null) return head.next;  //删除倒数第n节点!(链表长度n)
    while (fast.next != null) {
        slow = slow.next;
        fast = fast.next;
    }
    slow.next = slow.next.next;  
    return head;
}

6.移除重复节点(面试题02.01)

题目描述:编写代码,移除未排序链表中的重复节点。保留最开始出现的节点(说明相对位置不能变),保证最后返回的结果中元素唯一。

进阶:如果不得使用临时缓冲区,该怎么解决?

示例

 输入:[1, 2, 3, 3, 2, 1]
 输出:[1, 2, 3]

思路:注意不能改变节点的相对顺序,所以先排序后删除的方案不可取!

  • 法1:可以使用set进行去重,遍历链表,遇到相同的元素过滤(类似删除链表中的指定节点,但这里不是全部删除,不需要虚拟节点),但使用了额外空间 。

  • 法2:双重循环:双指针,一个指向一个固定的值比如m,另一个从m的下一个节点开始扫描,如果遇到和m相同的节点,直接删除,时间复杂度O(n^2),效率比较低。

// 代码1:哈希表
public ListNode removeDuplicateNodes(ListNode head) {
    if (head == null) {
        return head;
    } 
    Set set = new HashSet<>();
    ListNode cur = head;
    while (cur != null && cur.next != null) {
        set.add(cur.val);
        if (set.contains(cur.next.val)) {
            // 已经存在该元素,直接删除
            cur.next = cur.next.next;
        } else {
            cur = cur.next;
        }
    }
    return head;
}
// 代码2:双重循环
public ListNode removeDuplicateNodes(ListNode head) {
    if (head == null) {
        return head;
    } 
    ListNode p = head;
    while (p != null) {
        ListNode q = p;
        while (q.next != null) {
            if (p.val == q.next.val) {
                // 删除重复元素
                q.next = q.next.next;
            } else {
                q = q.next;
            }
        }
        p = p.next;
    }
    return head;
}

你可能感兴趣的:(删除链表中的节点(递归/迭代))