(三)剑指offer 链表篇

#看前须知:

链表相对数组而言,他的访问没有那么灵活,只能通过指针一个一个的访问,说所以他的算法题都不是很抽象。

以下是后面链表的结点类型:

	public class ListNode {
		int val;
		ListNode next = null;
		ListNode(int val) {
			this.val = val;
		}
	}

铁子,如果你还不会用Java实现链表,先看看这个吧,有基础的话,十分钟搞定!

*单链表的实现

如果你没有用C实现过链表,或者你对链表的基本概念和理论不清楚,那就从这里看起吧!

*数据结构

1.从尾到头打印链表

题目

输入一个链表,按链表值从尾到头的顺序返回一个ArrayList。

答案

(1)采用额外空间栈:
栈的功能就是先进后出,很适合这道题,但是要使用额外空间,一般情况下,使用额外空间的方法。

	/**
	 * 用额外空间栈
	 * @param listNode
	 * @return
	 */
	public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
		ArrayList<Integer> res = new ArrayList<Integer>();
		Stack<Integer> stack = new Stack<Integer>();
		ListNode next = listNode;
		while(next != null) {
			stack.add(next.val);
			next = next.next;
		}
		while(!stack.isEmpty()) {
			/*
			 * pop()方法:移除栈顶的元素,并将该元素作为方法的返回值
			 * peek()方法:查看栈顶的元素,但是不会将其删除
			 */
			res.add(stack.pop());
		}
		return res;
	}

(2)将链表反转,然后再打印:
画个链表,跟着算法走一遍,就很容易理解,很简单的。

	/**
	 * 反转一个链表
	 * @param listNode
	 * @return
	 */
	public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
		ArrayList<Integer> res = new ArrayList<Integer>();
		ListNode head = listNode;
		ListNode pre = null;
		ListNode next = null;
		while(head != null) {
			next = head.next;
			head.next = pre;
			pre = head;
			head = next;
		}
		while(pre != null) {
			res.add(pre.val);
			pre = pre.next;
		}
		return res;
	}

(3)采用递归的方式:
递归的工作原理就是,先从最外面一层开始,一层一层的扒拉进去,找到最里层递归结束的判断条件,再从最里层一层一层的退出来,所以用在这里再合适不过了。

	/**
	 * 采用递归的方式
	 */
	ArrayList<Integer> result = new ArrayList<Integer>();
	public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
		if(listNode != null) {
			if(listNode.next != null)
				printListFromTailToHead(listNode.next);
			result.add(listNode.val);
		}
		return result;
	}

2.链表中倒数第k个结点

题目:

输入一个链表,输出该链表中倒数第k个结点。

答案:

思路:设置两个指针,让一个指针比另一个指针快k个结点,当那个快的指针为空时,慢的那个指针所指的位置,就是倒数第k个结点。
但是这个题要注意一个点,链表的长度是事先无法知道的,要设置一个标志位,对传入的值k作分析,如果k的值大于链表本身的长度,要返回null

	/**
	 * 输出链表的倒数第k个结点
	 * @param head
	 * @param k
	 * @return
	 */
	public ListNode FindKthToTail(ListNode head,int k) {
		ListNode next = head;
		ListNode pre =head;
		int sign = 0;//这个变量就是为了判断 k值是否大于链表长度
		while(next != null) {
			next = next.next;
			if(sign < k)
				sign++;
			else
				pre = pre.next;
		}
		if(sign == k)
			return pre;
		return null;
	}

3.反转链表

题目:

输入一个链表,反转链表后,输出新链表的表头

答案:

很简单的,跟着下面这个图捋一遍,想想为什么要用temp != null作为循环条件
在这里插入图片描述

	/**
	 * 反转链表
	 * @param head
	 * @return
	 */
  	 public ListNode ReverseList(ListNode head) {
		 ListNode temp = head;
		 ListNode pre = null;
		 ListNode next = null;
		 while(temp != null) {
			 next = temp.next;
			 temp.next = pre;
			 pre = temp;
			 temp = next;
		 }
		 return pre;
	    }

4.合并两个排序链表

题目

输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。

答案

1)非递归实现:
如果这里有值得一说的地方,那就是:可能返回的头结点不好处理,可以新建一个节点,用它的next指向合并好的链表的头结点,就很奈斯,跟着代码走一边,很好理解,很简单。

	/**
	 * 合并两个有序链表
	 * @param list1
	 * @param list2
	 * @return
	 */
	public ListNode Merge(ListNode list1, ListNode list2) {
		if (list1 == null)
			return list2;
		if (list2 == null)
			return list1;
		ListNode head = new ListNode(0);
		ListNode pre = head;
		while (list1 != null && list2 != null) {
			if (list1.val < list2.val) {
				pre.next = list1;
				list1 = list1.next;
				pre = pre.next;
			} else {
				pre.next = list2;
				list2 = list2.next;
				pre = pre.next;
			}
		}
		if (list1 != null)
			pre.next = list1;
		if (list2 != null)
			pre.next = list2;
		return head.next;
	}

2)递归实现
在这里插入图片描述
图片来源于网络,侵删!!!

按照图图上面理解,思路很清晰了,我根本没有想到还能这么写,简单明了。

		public ListNode Merge(ListNode list1,ListNode list2) {
		       if(list1 == null){
		           return list2;
		       }
		       if(list2 == null){
		           return list1;
		       }
		       
		       if(list1.val <= list2.val){
		           list1.next = Merge(list1.next, list2);
		           return list1;
		       }else{
		           list2.next = Merge(list1, list2.next);
		           return list2;
		       }       
		   }

5.删除链表中重复的结点

题目

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

答案

思路:在注释里!!!看图跟着注释和代码走一遍,很简单的。
在这里插入图片描述

	public ListNode deleteDuplication(ListNode pHead) {
		if(pHead == null)
			return null;
		//新建一个结点,让他作为原链表的头结点
		ListNode newHead = new ListNode(0);
		newHead.next = pHead;		//让新链表和原链表相连
		
		//新建一个变量pre,让他记录不被删除的结点,最开始让他和newHead指向同一空间
		ListNode pre = newHead;		
		
		//suf的作用,就是一一访问链表中的每个结点
		ListNode suf = pHead;
		
		while(suf != null && suf.next != null) { //因为suf每次都要和他的下一个结点比较,
//			所以这里的条件设置两个,而且要注意两者的顺序,不能颠倒
			
			if(suf.val == suf.next.val) {	//当suf和他的下一个结点相等的时候
				int val = suf.val;	//记录下val的值
				while(suf != null && suf.val == val) {
					suf = suf.next;	//suf向后滑,知道suf指向值不等于val的结点
				}
				/*如果链表的第一个结点和后面的结点值相等
				 * 这里pre.next  就相当于  newhead.next 
				 */
				pre.next = suf;
			}else {//当suf和他的下一个结点不相等的时候
				//suf向后滑,pre紧跟其后
				pre = suf;
				suf = suf.next;
			}
		}
		//这里返回newHead.next也就相当于返回新链表的头结点
		return newHead.next;
	}

6.复杂链表的复制

题目

输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的head。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)

结点类型:

	public class RandomListNode {
		int label;
		RandomListNode next = null;
		RandomListNode random = null;

		RandomListNode(int label) {
			this.label = label;
		}
	}

答案

思路:看图说话。
最初我没有想到这种方法,看到大佬的这张图就恍然大悟,然后看着图片自己写代码,感觉一切都OK,但是ac的时候,报错空指针,然后看了大佬的代码,发现人家有两处判断空指针,我没有。
在这里插入图片描述
图片来源于网络,侵必删!!!

	public RandomListNode Clone(RandomListNode pHead) {
		if(pHead == null)
			return null;
		RandomListNode p = pHead;
		
		//1.复制每个结点,让新结点跟在被复制结点的后面
		while(p != null) {
			RandomListNode newNode = new RandomListNode(p.label);
			newNode.next = p.next;
			p.next = newNode;
			p = p.next.next;
		}
		
		//2.重新遍历链表,复制老结点的随机指针给新结点,如A1.random = A.random.next;
		p = pHead;
		while(p != null) {
			//老表,关键点在这一步的空指针的判断
			p.next.random = p.random == null ? null : p.random.next;
			p = p.next.next;
		}
		
		//3.拆分链表,将链表拆分为原链表和复制后的链表
		p = pHead;
		RandomListNode copyHead = p.next;
		RandomListNode np = p.next;
		while(p != null) {
			p.next = np.next;
			p = p.next;
			//这一步和上一个空指针的判断一样重要,少一个都不行
			np.next = p == null ? null : p.next;
			np = np.next;
		}
		return copyHead;
	}

7.两个链表的第一个公共结点

题目

输入两个链表,找出它们的第一个公共结点。

答案

加粗样式
思路:
两个链表公共结点之后,就是共用一个链表,所以长度是相等的。只要找出两个链表的长度差,让长的链表先向右滑,等滑到后面的长度和短链表的长度相等时,再将两个链表的同时向右滑,并且一一比较每个结点是否相同,如果第一个相同的结点出现,那这个结点就是,连个链表的公共结点。

	/**
	 * 找到两个链表的公共结点
	 * @param pHead1
	 * @param pHead2
	 * @return
	 */
	public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
		if (pHead1 == null || pHead2 == null)
			return null;
		// 想求出第一个链表的长度
		int len1 = 0;
		ListNode p1 = pHead1;
		while (p1 != null) {
			len1++;
			p1 = p1.next;
		}
		// 在求出第二个链表的长度
		int len2 = 0;
		ListNode p2 = pHead2;
		while (p2 != null) {
			len2++;
			p2 = p2.next;
		}
		int dif = len1 - len2;
		p1 = pHead1;
		p2 = pHead2;
		if (dif > 0) {
			while (dif > 0) {
				p1 = p1.next;
				dif--;
			}
			while (p1 != p2) {
				p1 = p1.next;
				p2 = p2.next;
			}
			return p1;
		} else {
			while (dif < 0) {
				p2 = p2.next;
				dif++;
			}
			while (p1 != p2) {
				p1 = p1.next;
				p2 = p2.next;
			}
			return p1;
		}
	}

8.链表中环的入口结点

题目

给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。

答案

在这里插入图片描述
图片来源于网络,侵必删!!!

思路:主要还是看人家大神的解法。
设置两个指针slow、fast,slow一次移动一步,fast一次移动两步,如果有环,这两个指针就一定会相遇。
在上图中,假设:slow和fast相遇点是Z,
则:
slow走过的路程为:a+b
fast走过的路程为:a+b+c+b
又因为路程在走的路程上:
2 slow = fast
所以:2*(a+b) = a+b+c+b => a = c
那么此时让slow回到起点,fast依然停在z,两个同时开始走,一次走一步
那么它们最终会相遇在y点,正是环的起始点。

	/**
	 * 环的入口结点
	 * 
	 * @param pHead
	 * @return
	 */
	public ListNode EntryNodeOfLoop(ListNode pHead) {
		ListNode slow = pHead;
		ListNode fast = pHead;
		while (fast != null && fast.next != null) {
			slow = slow.next;
			fast = fast.next.next;
			if (slow == fast) {
				fast = pHead;
				while (fast != slow) {
					fast = fast.next;
					slow = slow.next;
				}
				return fast;
			}
		}
		return null;
	}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

yelvens

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值