剑指Offer面试题(第十五天)面试题22、23

* 面试题22:链表中倒数第k个节点


     * 题目:输入一个单向链表,输出该链表中倒数第k个节点。
     * 为了符合大多数人的习惯,本体从1开始计数,即链表的尾节点是倒数第一个节点
     * 例如:一个链表有6个节点,从头节点开始,他们的值依次是1、2、3、4、5、6.
     * 这个链表的倒数第3个节点是值为4的节点。
     * 链表定义:
     * struct ListNode{
     *     int m_nValue;
     *  LinkNode m_pnext;
     * }
     * 
     * 思路:
     * 方法一:遍历两边链表  :第一遍统计链表的个数n,第二遍根据n值和k值,得到要从头节点向后走到第n-k+个节点。但是此方法要遍历两边链表。
     * 方法二:遍历一遍链表:设置两个指针,都设置在头节点,第一个指针先走k-1步,第二个指针不动;
     * 从第k步开始,第二个指针向后移动,第一个指针也向后移动,直到第一个指针走到链表的尾部。
     * 因为 开始第一个指针走了k-1步 到达 第k个节点  然后还剩余n-k个节点,然后两个指针一起走,直到第一个指针到达尾部,那就走n-k+1步,
     * 完成这长度为n【k-1步+n-k+1步 == n步】的链表的依次遍历

package Test;

import java.util.Scanner;

public class No22findKthToTail {

	/*
	 * 面试题22:链表中倒数第k个节点
	 * 题目:输入一个单向链表,输出该链表中倒数第k个节点。
	 * 为了符合大多数人的习惯,本体从1开始计数,即链表的尾节点是倒数第一个节点
	 * 例如:一个链表有6个节点,从头节点开始,他们的值依次是1、2、3、4、5、6.
	 * 这个链表的倒数第3个节点是值为4的节点。
	 * 链表定义:
	 * struct ListNode{
	 * 	int m_nValue;
	 *  LinkNode m_pnext;
	 * }
	 * 
	 * 思路:
	 * 方法一:遍历两边链表  :第一遍统计链表的个数n,第二遍根据n值和k值,得到要从头节点向后走到第n-k+个节点。但是此方法要遍历两边链表。
	 * 方法二:遍历一遍链表:设置两个指针,都设置在头节点,第一个指针先走k-1步,第二个指针不动;
	 * 从第k步开始,第二个指针向后移动,第一个指针也向后移动,直到第一个指针走到链表的尾部。
	 * 因为 开始第一个指针走了k-1步 到达 第k个节点  然后还剩余n-k个节点,然后两个指针一起走,直到第一个指针到达尾部,那就走n-k+1步,
	 * 完成这长度为n【k-1步+n-k+1步 == n步】的链表的依次遍历
	 * 
	 * */
	
	static class ListNode{   //单向链表
		int data;
		ListNode next;
		
		ListNode(int data) { //构造函数
			this.data = data;			
		}
	}
	
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		No22findKthToTail f = new No22findKthToTail();
		
		ListNode head = new ListNode(1);
		ListNode second = new ListNode(2);
		ListNode three = new ListNode(3);
		ListNode four = new ListNode(4);
		ListNode five = new ListNode(5);
		ListNode six = new ListNode(6);
		ListNode seven = new ListNode(7);
		head.next = second;
		second.next = three;
		three.next = four;
		four.next = five;
		five.next = six;
		six.next = null;
		seven.next = null;
		
		ListNode node = head;
		System.out.println("输出单向链表数据:");
		for(int i=0;i<6;i++) {
			System.out.println(node.data);
			node = node.next;
		}
		System.out.println("输入要查找的倒数第k个节点的k值:");
		Scanner in = new Scanner(System.in);
		int k = in.nextInt();
		ListNode find = f.findKthToTail(head,k);
		System.out.println("输出单向链表倒数第"+k+"个字符:"+find.data);
		
		
	}

	//方法二:两个指针,遍历一遍链表,查找倒数第k个节点的值
	public ListNode findKthToTail(ListNode head, int k) {
		// TODO Auto-generated method stub
		ListNode first = head;
		ListNode second = head;
		//******************************************
		//增加鲁棒性   1>输入的head为空指针,k为0,都会导致程序崩溃,
		//所以要预防性编程,考虑输入是否符合规范,不符合的处理
		if(head == null || k == 0) {
			return null;
		}
		
		for(int i = 0;i < k-1;i ++) {
			//********************************************************
			//增加鲁棒性  2>输入为head的头节点的连边界点总数少于k ,会导致空指针,造成程序崩溃
			if(first.next != null) {
				first = first.next;	
			}
			else
				return null;
					
		}
		//刚好first走到了链表尾部 second走到了倒数第k个字符
		while(first.next != null) {
			first = first.next;
			second = second.next;
		}
		
		return second;
		
	}

}

* 面试题23:链表中环的入口节点


     * 题目:如果一个链表中包括环,如何找到环的入口节点?
     * 例如:在一个1->2->3->4->5->6 链表中,环的入口节点是3
     *             ^_____________|
     * 
     * 思路:1>确定链表中是否存在环。使用两个指针,一个快指针,一个慢指针。
     * 若是快指针追上了满指针证明有环的存在;若是没有追到快指针走到了末尾,则表示没有环。
     *        2>确定环的入口。定义两个指针,假设环有n个节点构成,则先将一个节点向前移动n个节点;
     *       然后再将两个一起移动,当第二个指针只想入口节点时,刚好第一个指针已经绕着环走了一圈了,又回到了入口节点。
     *     3>确定环中包含几个节点。1>中提到的快慢节点相遇是在环的内部,所以可以从相遇的这个节点出发向前,
     *       并开始计数,当再次回到这个节点时,就能够得到环中节点数了。

package Test;

import java.util.Scanner;

import Test.No22findKthToTail.ListNode;

public class No23entryNodeOfLoop {

	/*
	 * 面试题23:链表中环的入口节点
	 * 题目:如果一个链表中包括环,如何找到环的入口节点?
	 * 例如:在一个1->2->3->4->5->6 链表中,环的入口节点是3
	 * 			^_____________|
	 * 
	 * 思路:1>确定链表中是否存在环。使用两个指针,一个快指针,一个慢指针。
	 * 若是快指针追上了满指针证明有环的存在;若是没有追到快指针走到了末尾,则表示没有环。
	 * 	   2>确定环的入口。定义两个指针,假设环有n个节点构成,则先将一个节点向前移动n个节点;
	 *       然后再将两个一起移动,当第二个指针只想入口节点时,刚好第一个指针已经绕着环走了一圈了,又回到了入口节点。
	 *     3>确定环中包含几个节点。1>中提到的快慢节点相遇是在环的内部,所以可以从相遇的这个节点出发向前,
	 *       并开始计数,当再次回到这个节点时,就能够得到环中节点数了。
	 * */
	
	static class ListNode{   //单向链表
		int data;
		ListNode next;
		
		ListNode(int data) { //构造函数
			this.data = data;			
		}
	}
	public static void main(String[] args) {
		// TODO Auto-generated method stub

		No23entryNodeOfLoop e = new No23entryNodeOfLoop();
		
		ListNode head = new ListNode(1);
		ListNode second = new ListNode(2);
		ListNode three = new ListNode(3);
		ListNode four = new ListNode(4);
		ListNode five = new ListNode(5);
		ListNode six = new ListNode(6);
		ListNode seven = new ListNode(7);
		head.next = second;
		second.next = three;
		three.next = four;
		four.next = five;
		five.next = six;
		six.next = four;
		seven.next = null;
		
		ListNode node = head;
		System.out.println("输出单向链表数据:");
		for(int i=0;i<6;i++) {
			System.out.println(node.data);
			node = node.next;
		}
		
		ListNode entryNode = e.entryNodeOfLoop(head);
		System.out.println("单向链表中环的入口节点为:"+entryNode.data);
		
	}
	
	//环的入口节点   两个节点从头走,其一先走n步
	public ListNode entryNodeOfLoop(ListNode head) {
		// TODO Auto-generated method stub
		//找到相遇的节点
		ListNode meetingNode = meetingNode(head); 
		if(meetingNode == null) {
			return null;
		}
		
		//环中的节点数,使用快慢指针   就使用上一步求出来的相遇的点(若有环,相遇的话,两个指针一定在环内)即可
		int loopCount = 1;
		ListNode node1 = meetingNode;
		while(node1.next != meetingNode) {  //再次回到相遇的节点,就计数完毕
			loopCount++;
			node1 = node1.next;
		}		
		
		//根据环的节点数loopCount计算出环的入口点
		node1 = head;
		for(int i = 0;i < loopCount;i++) {
			node1 = node1.next;
		}
		
		ListNode node2 = head;
		while(node1 != node2) {
			node1 = node1.next;
			node2 = node2.next;
		}
		
		
		return node1;
	}
	
	//两个指针相遇的节点
	public ListNode meetingNode(ListNode head) {
		if(head == null) {
			return null;
		}
		ListNode slow = head.next;
		if(slow == null) {
			return null;
		}
		
		ListNode fast = slow.next;
		while(slow != null || fast != null) {
			if(slow == fast) {
				return slow;
			}
			
			slow = slow.next;
			fast = fast.next;
			
			if(fast != null) {
				fast = fast.next;
			}

		}

		return null;
		
	}

}

猜你喜欢

转载自blog.csdn.net/weixin_43137176/article/details/89089947
今日推荐