LintCode(2):单链表相关操作

单链表的相关操作是面试中经常被提问的内容,也是反映应聘者数据结构基本能力很好的途径,因此系统归纳总结单链表的一些常用操作和实现,是十分必要的。

而且在实现单链表各种操作的过程中,看到了变量和指针的重要性,引入适当的变量和指针或者引用,来记录编程中的中间状态,会大大简化编程逻辑

package DataStructure;

import java.util.Stack;

import javax.swing.text.AbstractDocument.LeafElement;

//Java语言实现单链表以及各种操作
public class LinkList {
	// 实际上链表类中维护一个头节点类即可
	private Node head;
	// 这个节点表示当前节点的索引,可以理解为链表中最后一个节点,在添加节点的时候使用
	private Node current;

	// 1.向链表中添加节点,肯定是添加到链表结尾
	public void add(int data) {
		// 如果头节点为空,表示链表还没开始创建。在链表的操作中,记住永远要判断头结点这个特殊节点
		// 因为Java中没有指针,名称实际上也是引用,所以为了好理解,可以将对象的名称当成指针来理解
		if (head == null) {
			head = new Node(data);
			current = head;
		} else {
			current.next = new Node(data);
			// 更新当前节点的指向
			current = current.next;
		}
	}

	// 2.删除链表的最后一个节点
	public void delete() {
		if (head == null)
			return;
		if (head == current) {
			head = null;
			current = null;
		} else {
			// 引入前一个节点,然后遍历递进
			Node prev = head;
			while (prev.next != current) {
				prev = prev.next;
			}
			prev.next = null;
			// 千万不要忘记将current更新为最新
			current = prev;
		}
	}

	// 3.遍历链表,不一定从head开始遍历,所以传入的node表示遍历开始节点
	public void print(Node node) {
		// 对传进来的参数首先做非空非null判断,是良好的编程习惯,防止抛出空指针异常
		if (node == null)
			return;
		// 用来做遍历操作的引用,局部变量
		Node temp = node;
		while (temp != null) {
			System.out.print(temp.data + "->");
			temp = temp.next;
		}
		System.out.println("");
	}

	// 4.求单链表的长度
	public int getLength() {
		if (head == null)
			return 0;
		int length = 0;
		Node temp = head;
		while (temp != null) {
			length++;
			temp = temp.next;
		}
		return length;

	}

	// 5.查找倒数第K个节点
	public Node findLastNode(int k) {
		// 判断传入参数K的值,如果为0,则返回null
		if (k == 0)
			return null;
		// 定义两个节点,用两个节点的差值来定位倒数第K个
		Node first = head;
		Node second = head;
		for (int i = 0; i < k - 1; i++) {
			second = second.next;
		}
		if (second == null)
			return null;
		while (second.next != null) {
			second = second.next;
			first = first.next;
		}
		return first;
	}

	// 6.查找链表的中间节点
	public Node findMidNode() {
		Node first = head;
		Node second = head;
		while (second != null && second.next != null) {
			// 这样对于偶数个节点,是n/2+1
			first = first.next;
			second = second.next.next;
		}
		return null;
	}

	// 7.两个有序单链表合成一条有序单链表
	public Node mergeLinkList(Node head1, Node head2) {
		if (head1 == null && head2 == null) {
			return null;
		}
		if (head1 == null) {
			return head2;
		}
		if (head2 == null) {
			return head1;
		}
		Node head = null; // 新链表的头结点
		Node current = null;// current指向新链表的节点
		// head1和head2不断的迭代递进
		if (head1.data <= head2.data) {
			head = head1;
			current = head;
			head1 = head1.next;
		} else {
			head = head2;
			current = head;
			head2 = head2.next;
		}
		// 只要由一个走到最后的节点就停止循环
		while (head1 != null && head2 != null) {
			if (head1.data <= head2.data) {
				current.next = head1;
				current = current.next;
				head1 = head1.next;
			} else {
				current.next = head2;
				current = current.next;
				head2 = head2.next;
			}
		}
		if (head1 != null) {
			current.next = head1;
		}
		if (head2 != null) {
			current.next = head2;
		}
		return head;

	}

	// 8.翻转单链表 出现频率极高
	public Node reverseList(Node head) {
		if (head == null || head.next == null)
			return head;
		Node current = head;
		// 保持前一个节点prev
		Node prev = null;
		// 在前面的正序遍历中,需要定义一个prev指针,这里需要后面的节点,则需要一个next指针
		Node next = head.next;
		while (current != null) {
			// 保存下一个节点
			next = current.next;
			// 将当前节点指向前一个节点
			current.next = prev;
			// 做完一次操作,两个指针都往前迭代
			prev = current;
			current = next;
		}
		return prev;

	}

	// 9.从尾到头打印链表,对于这种颠倒顺序的,使用栈来完成,要么自己实现栈,要是使用系统的栈,也就是递归
	// 自己使用栈,基于循环实现,代码更好
	// 如果使用递归,栈深度太大容易溢出
	public void reversePrint(Node head) {
		if (head == null)
			return;
		Stack<Node> stack = new Stack<Node>(); // 新建一个栈,java中已经实现了stack类,实际编程中可以使用
		Node current = head;
		while (current != null) {
			stack.push(current);// 压栈
			current = current.next;
		}
		while (stack.size() > 0) {
			System.out.println(stack.pop().data);// 出栈
		}
	}

	// 使用递归,代码简洁
	public void reversePrintByRecusion(Node head) {
		// 既是判断参数条件,也是递归终止条件 递归的最终结束就是遇到return,虽然这个return没有返回任何数据,这时压入栈的数据开始返回
		if (head == null)
			return;
		reversePrintByRecusion(head.next);
		System.out.println(head.data);
	}

	// 10.判断单链表是否有环
	public boolean hasCycle(Node head) {
		if (head == null)
			return false;
		Node first = head;
		Node second = head;
		while (second != null) {
			first = first.next;
			second = second.next.next;
			if (first == second) {
				return true; // 一旦两个指针相遇,表示有环
			}
		}
		return false;
	}

	// 11.得到有环链表中两个指针的相遇节点
	public Node hasMeetPoint(Node head) {
		if (head == null) {
			return null;
		}

		Node first = head;
		Node second = head;
		while (second != null) {
			first = first.next;
			second = second.next.next;
			if (first == second) { // 一旦两个指针相遇,说明链表是有环的
				return first; // 将相遇的那个结点进行返回
			}
		}
		return null;
	}

	// 12.得到环的长度
	public int getCycleLength(Node node) {
		if (node == null)
			return 0;
		Node current = node;
		int length = 0;
		while (current != null) {
			// 必须先迭代一步,再判断是否相等,否则会一直循环
			current = current.next;
			length++;
			if (current == node) {
				return length;
			}
		}
		return length;
	}

	// 13.获取环的起始点 需要用到判断是否有环、得到相遇点已经得到环的长度这几个方法的配合,再用追击迫近的思想求出起点
	// 本质上还是迫近追击,找到倒数第K个值的问题
	public Node getCycleStart(Node head, int cycleLength) {
		if (head == null)
			return null;
		Node first = head;
		Node second = head;
		for (int i = 0; i < cycleLength; i++) {
			second = second.next;
		}
		while (first != null && second != null) {
			first = first.next;
			second = second.next;
			if (first == second)
				return first;// 两个指针相遇表示在起始点回合
		}
		return null;
	}

	// 方法:获取单链表的长度
	public int getLength(Node head) {
		if (head == null) {
			return 0;
		}
		int length = 0;
		Node current = head;
		while (current != null) {
			length++;
			current = current.next;
		}
		return length;
	}

	// 14.得到两个单链表的相交节点 快慢指针 追击迫近
	public Node getFirstCommonNode(Node head1, Node head2) {
		if (head1 == null || head2 == null) {
			return null;
		}
		int length1 = getLength(head1);
		int length2 = getLength(head1);
		int lengthDif = 0;
		Node longHead = null;
		Node shortHead = null;
		//找到长短链表的头部和长度差值,分别保存在自定义的变量中
		if (length1 > length2) {
			longHead = head1;
			shortHead = head2;
			lengthDif = length1 - length2;
		} else {
			longHead = head2;
			shortHead = head1;
			lengthDif = length2 - length1;
		}
		//将较长的那个链表的指针向前走length个距离
		 for (int i = 0; i < lengthDif; i++) {
		  longHead = longHead.next;
		 }
		 
		 //将两个链表的指针同时向前移动
		 while (longHead != null && shortHead != null) {
		  if (longHead == shortHead) { //第一个相同的结点就是相交的第一个结点
		   return longHead;
		  }
		  longHead = longHead.next;
		  shortHead = shortHead.next;
		 }
		 
		 return null;
	}

	public static void main(String[] args) {
		LinkList list1 = new LinkList();
		LinkList list2 = new LinkList();
		LinkList list3 = new LinkList();
		// 向LinkList中添加数据
		for (int i = 0; i < 5; i++) {
			list1.add(i);
		}
		for (int i = 10; i < 5; i++) {
			list2.add(i);
		}
		list3.print(list3.mergeLinkList(list1.head, list2.head));

	}

	// 链表节点类,内部类实现,包含数据域和指针域
	class Node {
		int data; // 数据域
		Node next;// 指针域

		public Node(int data) {
			this.data = data;
		}
	}

}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值