剑指Offer面试题(第三十一天)面试题51、52

 * 面试题51:数组中的逆序对


     * 题目:在数组中的两个数字,如果前面的数字大于后面的数字,则这两个数字组成一个逆序对。
     * 输入一个数组,求出数组中的逆序对总数
     * 例如:在数组{7,5,6,4}中,一共存在5个逆序对,分别是{7,5}{7,6}{7,4}{5,4}{6,4}
     * 
     * 思路:统计逆序对
     * 1> 将数组分割成子数组,再将子数组进行分割 ,直到分割为长度为1的数组
     * 2> 统计子数组中最短的数组(两两进行合并)之间的逆序对的数目,然后再统计相邻的子数组中的逆序对的数目,直到合并为一个整体之后
     * 其中,在2>中进行统计逆序对的数目时,需要构建一个辅助数组,将两个数组合并后的新数组存入,构建两个索引,都从最后一个开始
     * 比较两个指针指向的值,
     * (1)若第一个数组中的值大于第二个数组中的值,则逆序对的数目等于第二个数组中前面还剩余元素个数,
     *         并将较大的数存入辅助数组,然后将较大数所在的数组的指针和辅助数组的指针向前移动
     * (2)若第一个数组的值小于等于第二个数组中的值,则无逆序对
     *         并将较大的数存入辅助数组,然后将较大数所在的数组的指针和辅助数组的指针向前移动
     * 
     * 时间复杂度O(nlogn)   空间复杂度O(n)    以空间换时间的算法
      * 
     * 其实现过程实际上就是2-路归并排序的过程:
     * 归并:将两个或两个以上的有序表组合成一个新的有序表
     * 
     * 初始序列含有n个记录,则可以看成时有n个子序列,每个子序列的长度为1,然后进行两两合并,得到n/2(取整)个长度为2或者1的有序子序列。
     * 然后再进行两两合并,。。。,直到得到一个长度为n的有序序列为止
 

package Test;

public class No51InversePairs {

	/*
	 * 面试题51:数组中的逆序对
	 * 题目:在数组中的两个数字,如果前面的数字大于后面的数字,则这两个数字组成一个逆序对。
	 * 输入一个数组,求出数组中的逆序对总数
	 * 例如:在数组{7,5,6,4}中,一共存在5个逆序对,分别是{7,5}{7,6}{7,4}{5,4}{6,4}
	 * 
	 * 思路:统计逆序对
	 * 1> 将数组分割成子数组,再将子数组进行分割 ,直到分割为长度为1的数组
	 * 2> 统计子数组中最短的数组(两两进行合并)之间的逆序对的数目,然后再统计相邻的子数组中的逆序对的数目,直到合并为一个整体之后
	 * 其中,在2>中进行统计逆序对的数目时,需要构建一个辅助数组,将两个数组合并后的新数组存入,构建两个索引,都从最后一个开始
	 * 比较两个指针指向的值,
	 * (1)若第一个数组中的值大于第二个数组中的值,则逆序对的数目等于第二个数组中前面还剩余元素个数,
	 * 		并将较大的数存入辅助数组,然后将较大数所在的数组的指针和辅助数组的指针向前移动
	 * (2)若第一个数组的值小于等于第二个数组中的值,则无逆序对
	 * 		并将较大的数存入辅助数组,然后将较大数所在的数组的指针和辅助数组的指针向前移动
	 * 
	 * 时间复杂度O(nlogn)   空间复杂度O(n)    以空间换时间的算法
 	 * 
	 * 其实现过程实际上就是2-路归并排序的过程:
	 * 归并:将两个或两个以上的有序表组合成一个新的有序表
	 * 
	 * 初始序列含有n个记录,则可以看成时有n个子序列,每个子序列的长度为1,然后进行两两合并,得到n/2(取整)个长度为2或者1的有序子序列。
	 * 然后再进行两两合并,。。。,直到得到一个长度为n的有序序列为止
	 * 
	 * */

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		No51InversePairs p = new No51InversePairs();
		int[] array = {7,5,6,4};//其中array{0] = 7
		System.out.println("数组中逆序对一共有:"+p.InversePairs(array));
	}

	int count = 0;
	//统计逆序对的数目
	private int InversePairs(int[] array) {
		// TODO Auto-generated method stub
		if(array == null || array.length == 0)
			return 0;
		
		mergeSort(array,0,array.length-1);
		return count;
	}
	
	//将长度为n的数组分割为多个子数组 
	private void mergeSort(int[] array, int start, int end) {
		// TODO Auto-generated method stub
		int mid = (start + end)/2;
		if(start < end) {
			//递归到 分割的小小的单位
			mergeSort(array,start,mid);
			mergeSort(array,mid+1,end);
			//然后将两个子数组进行合并
			merge(array,start,mid,end);
			
		}
	}

	//将两个子数组合并
	private void merge(int[] array, int start, int mid, int end) {
		// TODO Auto-generated method stub
		//辅助数组  存储合并后的数组  长度为end - start + 1
		int[] copy = new int[end - start +1];
		
		//然后声明三个指针分别指向第一个子数组的末位,第二个子数组的末位,辅助数组的末位
		int p1 = mid ;
		int p2 = end;
		int p3 = end - start;
		
		//p3<end表示辅助数组还没有满  也就是两个数组还没有合并完成
		//start < mid表示数组中元素超过两个;若数组中元素为1个,则直接比较存入即可 不必再进行数组中的比较
		while(p1 >= start && p2 >= mid+1) {
			//若第一个数组中元素大于第二个数组
			//则逆序对的数量为  第二个数组中还剩余的元素个数
			if(array[p1] > array[p2]) {  
				count += (p2 - mid); 
				copy[p3--] = array[p1--];		
			}
			//若小于,则无逆序对
			else {
				copy[p3--] = array[p2--];
			}
				
		}
				
		while(p1 >= start)
			copy[p3--] = array[p1--];
		
		while(p2 >= mid+1)
			copy[p3--] = array[p2--];
		
		
		for(int i = 0; i < copy.length;i++ )
			array[start++] = copy[i];
		
	}

}

     * 面试题52:两个链表中的第一个公共节点


     * 题目:输入两个链表,找出他们的第一个公共系欸但
     * 链表节点定义: 
     *  struct ListNode{
     *         int m_nKey;
     *         ListNode* m_pNext;
     * }
     * 
     * 思路:链表1 长度为m   链表2长度为n
     * 第一种方法:蛮力法  时间复杂度O(mn)
     * 从头遍历第一个链表,和第二个链表进行对比  一样的话就是公共节点
     * 
     *  第二种方法:使用辅助栈    时间复杂度O(m+n)  空间复杂度O(m+n)     空间换时间的算法
     *  对于有公共节点的单向链表来说,会成为一个Y字型的结构,从尾部进行遍历的话,
     *  直接找到第一个公共子节点就可以了,而且长度会大大缩小
     * 因为单向链表是从头进行遍历的,而从尾部进行比较会减少比较次数,这种情况很像是后进先出的情形,也就是栈的存储结构
     * 所以可以使用两个栈,对两个单向链表进行存储,然后存储后,弹出元素进行对比即可,找到最后一个相等的子节点
     * 
     * 第三种方法:无辅助空间   时间复杂度O(m+n)  空间复杂度O(1)     提高了空间效率
     * 当两个链表的长度不相同时,从头节点遍历,会造成到达尾节点的时间不同,为解决这个问题,可以:
     * 1>第一次遍历两个链表得到它们的长度,算出相差多少LengthDiffer 
     * 2>第二次遍历时,先将长的链表先走LengthDiffer步,然后两个链表再同步遍历,
     * 直到找到第一个相同的节点就是他们的第一个公共节点
     * 
     * 本例使用第三个方法实现

package Test;

public class No52FindFirstCommonNode {

	/*
	 * 面试题52:两个链表中的第一个公共节点
	 * 题目:输入两个链表,找出他们的第一个公共系欸但
	 * 链表节点定义: 
	 *  struct ListNode{
	 * 		int m_nKey;
	 * 		ListNode* m_pNext;
	 * }
	 * 
	 * 思路:链表1 长度为m   链表2长度为n
	 * 第一种方法:蛮力法  时间复杂度O(mn)
	 * 从头遍历第一个链表,和第二个链表进行对比  一样的话就是公共节点
	 * 
	 *  第二种方法:使用辅助栈    时间复杂度O(m+n)  空间复杂度O(m+n)     空间换时间的算法
	 *  对于有公共节点的单向链表来说,会成为一个Y字型的结构,从尾部进行遍历的话,
	 *  直接找到第一个公共子节点就可以了,而且长度会大大缩小
	 * 因为单向链表是从头进行遍历的,而从尾部进行比较会减少比较次数,这种情况很像是后进先出的情形,也就是栈的存储结构
	 * 所以可以使用两个栈,对两个单向链表进行存储,然后存储后,弹出元素进行对比即可,找到最后一个相等的子节点
	 * 
	 * 第三种方法:无辅助空间   时间复杂度O(m+n)  空间复杂度O(1)     提高了空间效率
	 * 当两个链表的长度不相同时,从头节点遍历,会造成到达尾节点的时间不同,为解决这个问题,可以:
	 * 1>第一次遍历两个链表得到它们的长度,算出相差多少LengthDiffer 
	 * 2>第二次遍历时,先将长的链表先走LengthDiffer步,然后两个链表再同步遍历,
	 * 直到找到第一个相同的节点就是他们的第一个公共节点
	 * 
	 * 
	 * */
	
	static class ListNode{
		int val;
		ListNode next;
		
		ListNode(int val){
			this.val = val;
		}
		
	}
	public static void main(String[] args) {
		// TODO Auto-generated method stub

		No52FindFirstCommonNode f = new No52FindFirstCommonNode();
		ListNode head1 = new ListNode(1);
		ListNode head2 = 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);
		ListNode eight = new ListNode(8);
		//链表1
		head1.next = three;
		three.next = five;
		//链表2
		head2.next = four;
		four.next = five;
		//公共
		five.next = seven;
		seven.next = null;
		
		ListNode node1 = head1;
		System.out.println("输出链表1数据:");
		for(int i=0;i<4;i++) {
			System.out.println(node1.val);
			node1 = node1.next;
		}
		ListNode node2 = head2;
		System.out.println("输出链表2数据:");
		for(int i=0;i<4;i++) {
			System.out.println(node2.val);
			node2 = node2.next;
		}
		
		System.out.println("两个链表的第一个公共节点:");
		ListNode firstCommonNode = f.FindFirstCommonNode(head1,head2);
	
		System.out.println(firstCommonNode.val);
		System.out.println("****************************"+"两个链表的第一个公共节点"+"****************************");
	}
	
	
	//两个链表的第一个公共节点
	private ListNode FindFirstCommonNode(ListNode head1, ListNode head2) {
		// TODO Auto-generated method stub
		ListNode p1 = head1;
		ListNode p2 = head2;
		
		int size1 = 0;
		int size2 = 0;
		
		//遍历得到两个链表的长度
		while(p1 != null) {
			size1++;
			p1 = p1.next;
		}
		while(p2 != null) {
			size2++;
			p2 = p2.next;
		}
		//从头再进行遍历
		//但是要将长链表先走他们的长度差
		p1 = head1;
		p2 = head2;
		//若第一个链表长  第一个链表先走一定长度
		if(size1 > size2) {
			for(int i = 0;i < size1-size2;i++) {
				p1 = p1.next;
			}
		}
		//若第二个链表长
		else if(size1 < size2){
			for(int i = 0;i < size2-size1;i++) {
				p2 = p2.next;
			}
		}
		
		//两个链表同时进行比较  直到找到第一个公共节点
		while(p1 != p2 && p1 != null && p2 != null) {
			p1 = p1.next;
			p2 = p2.next;
		}

		return p1;
	}

}

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值