【剑指offer】面试题18:删除链表的节点

目录

题目一:O(1)时间内删除一个节点 

题目二:删除链表中重复的节点 


题目一:O(1)时间内删除一个节点 

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

 在单向链表中删除一个节点,最常规的方法无疑是从链表的头结点开始,顺序遍历查找要删除的节点,并在链表中删除该节点。

比如下图 a 所表示的链表中,我们要删除节点 i,可以从链表的头节点 a 开始顺序遍历,发现节点 h 的 next 指向要删除的节点i,于是我们可疑把节点h的 next 指向i的下一个节点即为 j。指针调整之后,我们就可以安全地删除节点 i 并保证链表没有断开。这种思路由于需要顺序查找,时间复杂度自然就是O(n)了。

之所以要从头开始查找,是因为我们需要得到被删除的节点的前面一个节点。在单向链表中,节点中没有前一个节点的指针,所以只好从链表的头结点开始顺序查找。

那是不是一定要得到被删除的节点的前一个节点呢?答案是否定的。我们可以很方面地得到要删除的节点的下一个节点。如果我们把下一个节点的内容复制到要删除的节点上覆盖原有的内容,再把下一个节点删除,那是不是就相当于把当前要删除的节点删除了?

我们还是以前面的例子来分析这种思路,我们要删除的节点 i,先把i的下一个节点 j 的内容复制到 i,然后把 i 的指针指向节点 j 的下一个节点。此时再删除节点 j,其效果刚好是把节点 i 给删除了(图c)

上述问题还有一个问题:如果要删除的节点位于链表的尾部,那么它就没有下一个节点,怎么办?我们仍然从链表的头节点开始,顺序遍历得到该节点的前序节点,并完成删除操作。

最后需要注意的是,如果链表中只有一个节点,而我们又要 链表的头节点(也是尾节点),此时我们在删除节点之后,还需要把链表的头节点设置为null。

有了这种思路,现在我们是实现代码:

public class DeleteNodeInO1 {

	class Node{
		private int data;
		private Node next;
		
		public Node(int data){
			this.data = data;
		}
	}
	
	public Node deleteNode(Node head, Node delNode){
		if(head == null || delNode == null){
			return null;
		}
		
		// 1.要删除的节点不是尾节点
		if(delNode.next != null){
			Node node = delNode.next;
			delNode.data = node.data;  // 内容覆盖
			delNode.next = node.next;  // delNode指针指向它下下个节点
		}
		// 2.链表只有一个节点,删除头节点,也是尾节点
		else if(head == delNode && head.next == null){
			delNode = null;
			head = null;
			return null;
		}
		// 3.链表中有多个节点,要删除的节点是尾节点
		else{
			// 从头节点开始遍历,找到待删除节点的前一个节点
			Node node = head;
			while(node.next != delNode){
				node = node.next;
			}
			node.next = null;
			delNode = null;
		}
		return head;
	}
	
	// 打印链表
	public void print(Node head){
		if(head == null){
			System.out.println("链表为空!");
		}
		while(head != null){
			System.out.print(head.data + " ");
			head = head.next;
		}
		System.out.println();
	}
	
	// 测试
	public static void main(String[] args) {
		DeleteNodeInO1 dn = new DeleteNodeInO1();
		
		Node head = null;
		dn.print(dn.deleteNode(head, head));  // 链表为空!
		
		// 只有一个节点,删除尾结点也是头节点
		head = dn.new Node(0);
		Node p = head;
		dn.print(dn.deleteNode(head, head));  // 链表为空!
		
		for (int i = 0; i < 5; i++) {
			Node node = dn.new Node(i);
			p.next = node;
			p = p.next;
		}
		
	        Node tail = dn.new Node(5);
                p.next = tail;

                dn.print(head);   // 0 0 1 2 3 4 5  

                dn.print(dn.deleteNode(head, head));  // 0 1 2 3 4 5  
        
                // 链表中有多个节点,删除尾节点
                dn.print(dn.deleteNode(head, tail));  // 0 1 2 3 4
        
                // 删除的不是尾节点
                dn.print(dn.deleteNode(head, head.next));  // 0 2 3 4
	}
}


题目二:删除链表中重复的节点 

题目2:删除链表中重复的节点。例如在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5。

public class DeleteRepeatNode {

	class Node{
		private int data;
		private Node next;
		
		public Node(int data){
			this.data = data;
		}
	}
	
	public Node deleteDuplication(Node head){
		// 只有0或1个节点时,则返回头节点
		if(head == null || head.next == null){
			return head;
		}
		// 当前节点是重复节点
		if(head.data == head.next.data){
			Node node = head.next;  // 当前节点的下一个节点
			while(node != null && node.data == head.data){
				// 跳过值与当前结点相同的全部结点,找到第1个与当前结点不同的结点
				node = node.next;  // 这个过程其实将中间值相同的都删除了 
			}
			// 从第一个与当前结点不同的结点开始递归
			return deleteDuplication(node);
		}else{
			// 当前节点不是重复节点,则保留当前结点,从下一个结点开始递归
			head.next = deleteDuplication(head.next);
			return head;
		}
	}
}

代码中的巧妙之处在于这个while循环中以及后面的递归过程:

while(node != null && node.data == head.data){
				
    node = node.next;  
}

while循环中找到值相同的节点后,继续循环,直到找到值与当前节点不同的节点,则直接将其设置为node,这就意味着中间值相等的哪些节点直接就被删除了。这种递归的解法思路很清晰。

猜你喜欢

转载自blog.csdn.net/pcwl1206/article/details/85841704