剑指Offer面试题(第三十五天)面试题59(1)、60、61、62

* 面试题59:队列的最大值


     * 题目一:滑动窗口的最大值


     * 给定一个数组和滑动窗口的大小,请找出所有滑动窗口里的最大值。
     * 例如:如果输入数组{2,3,4,2,6,2,5}及滑动窗口的大小为3,那么一共存在6个滑动窗口,他们的最大值分别是{4,4,6,6,6,5}
     * 
     *  * 例如:

 * 如果输入数组{2,3,4,2,6,2,5,1}及滑动窗口的大小3,那么一共存在6个滑动窗口,

 * 他们的最大值分别为{4,4,6,6,6,5}; 

 * 针对数组{2,3,4,2,6,2,5,1}的滑动窗口有以下6个: 

 * {[2, 3, 4], 2, 6, 2, 5, 1}, 

 * {2, [3, 4, 2], 6, 2, 5, 1}, 

 * {2, 3, [4, 2, 6], 2, 5, 1}, 

 * {2, 3, 4, [2, 6, 2], 5, 1}, 

 * {2, 3, 4, 2, [6, 2, 5], 1},

 * {2, 3, 4, 2, 6, [2, 5, 1]}。
 * 
 * 
 * 
     * 思路:运用双端队列,存储最大的下标+次大的下标,队头就是所要求的最大下标
     * 时间复杂度O(n)
     * 若当前值大于双端队列中存储的,则将其弹出  存储当前下标i
     * 然后再判断是否已经滑动窗口内的数量已经到达size,
     * 若表示队首元素是最大元素,但是要将队首元素出滑动窗口了,
     * 需要将其从队列中的队头出弹出,因为此最大值下标已经过期了呢
     * 然后再判断是否应该滑动窗口了,若i>size-1则表示需要滑动窗口,
     * 也就是自从第三个位置(也就是索引值为2的地方)开始就需要每一次都滑动窗口
 

package Test;

import java.util.ArrayList;
import java.util.LinkedList;

public class No59First_MaxInWindows {

	/*
	 * 面试题59:队列的最大值
	 * 题目一:滑动窗口的最大值
	 * 给定一个数组和滑动窗口的大小,请找出所有滑动窗口里的最大值。
	 * 例如:如果输入数组{2,3,4,2,6,2,5}及滑动窗口的大小为3,那么一共存在6个滑动窗口,他们的最大值分别是{4,4,6,6,6,5}
	 * 
	 *  * 例如:

 * 如果输入数组{2,3,4,2,6,2,5,1}及滑动窗口的大小3,那么一共存在6个滑动窗口,

 * 他们的最大值分别为{4,4,6,6,6,5}; 

 * 针对数组{2,3,4,2,6,2,5,1}的滑动窗口有以下6个: 

 * {[2, 3, 4], 2, 6, 2, 5, 1}, 

 * {2, [3, 4, 2], 6, 2, 5, 1}, 

 * {2, 3, [4, 2, 6], 2, 5, 1}, 

 * {2, 3, 4, [2, 6, 2], 5, 1}, 

 * {2, 3, 4, 2, [6, 2, 5], 1},

 * {2, 3, 4, 2, 6, [2, 5, 1]}。
 * 
 * 
 * 
	 * 思路:运用双端队列,存储最大的下标+次大的下标,队头就是所要求的最大下标
	 * 时间复杂度O(n)
	 * 若当前值大于双端队列中存储的,则将其弹出  存储当前下标i
	 * 然后再判断是否已经滑动窗口内的数量已经到达size,
	 * 若表示队首元素是最大元素,但是要将队首元素出滑动窗口了,
	 * 需要将其从队列中的队头出弹出,因为此最大值下标已经过期了呢
	 * 然后再判断是否应该滑动窗口了,若i>size-1则表示需要滑动窗口,
	 * 也就是自从第三个位置(也就是索引值为2的地方)开始就需要每一次都滑动窗口
	 * 
	 * */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		No59First_MaxInWindows m = new No59First_MaxInWindows();
		
		int[] array = {2,3,4,2,6,2,5};
		int size = 3;
		ArrayList<Integer> res = new ArrayList<Integer>();
		res = m.MaxInWindows(array,size);
		System.out.println("滑动窗口的最大值序列是:"+res);
	}

	private ArrayList<Integer> MaxInWindows(int[] array, int size) {
		// TODO Auto-generated method stub
		ArrayList<Integer> res = new ArrayList<Integer>();
		if(array == null || array.length == 0|| array.length < size || size == 0)
			return res;
		
		LinkedList<Integer> qMax = new LinkedList<Integer>();
		//双端队列  存储最大值+次大值
		
		
		for(int i = 0; i < array.length; i++) {
			int cur = array[i];
			//当队列不为空  且  队列中队尾存储的最大值 小于等于  当前值时 则将队尾值弹出 
			while(!qMax.isEmpty() && array[qMax.peekLast()] <= cur)
				qMax.pollLast();
			qMax.add(i);   //将队尾下标存储到队列中
			//当获取队首元素时,队首元素下标 = i - size--->  表示i - 队首元素下标 = size
			//也就是该最大值已经过期 是即将划过去的下标 需要将其弹出队列 则将队首元素弹出 
			if(qMax.peekFirst() == i - size)
				qMax.pollFirst();
			//只要大于等于2 则将窗口右移  滑动到下一个窗口
			if(i >= size - 1) 
				//并且获取到当前窗口的最大值  
				res.add(array[qMax.peekFirst()]);
		}
		
		return res;
	}

}

 * 面试题60:n个骰子的点数


     * 题目:把n个骰子扔到地上,所有骰子朝上一面的点数之和为s。
     * 输入n,打印出s的所有可能的值出现的概率
     * 
     * 需要先统计出每个点数出现的次数,然后再每个点出现的次数除以6的n次方
     * (n个骰子所有点数的排列数),就是每个点数出现的概率
     * 思路:运用循环的思想,时间性能好O(n)
     * 首先构建两个数组存储骰子点数的每个总数s出现的次数
     * 第一次循环:第一个数组中的第n个数字表示:骰子和为n出现的次数
     * 第二次~第n次循环:因为分析发现f(n)和为n的骰子出现的次数
     * =上一轮循环中骰子和为n-1,n-2,n-3,n-4,n-5,n-6的次数的总和
     * 所以从第二次开始每一次都要加上前一个数组的n-1到n-6的值   
     * 其中使用flag对两个数组进行切换  且要保证每次进行切换时数据都经过了清零操作
     * 
     * 程序中使用maxValue这一变量来存储骰子面数6,是为了当其他厂家使用其他点数的骰子时,
     * 只需要更改maxValue的值即可,对程序的扩展性极好

package Test;

public class No60PrintProbability {

	/*
	 * 面试题60:n个骰子的点数
	 * 题目:把n个骰子扔到地上,所有骰子朝上一面的点数之和为s。
	 * 输入n,打印出s的所有可能的值出现的概率
	 * 
	 * 需要先统计出每个点数出现的次数,然后再每个点出现的次数除以6的n次方
	 * (n个骰子所有点数的排列数),就是每个点数出现的概率
	 * 思路:运用循环的思想,时间性能好O(n)
	 * 首先构建两个数组存储骰子点数的每个总数s出现的次数
	 * 第一次循环:第一个数组中的第n个数字表示:骰子和为n出现的次数
	 * 第二次~第n次循环:因为分析发现f(n)和为n的骰子出现的次数
	 * =上一轮循环中骰子和为n-1,n-2,n-3,n-4,n-5,n-6的次数的总和
	 * 所以从第二次开始每一次都要加上前一个数组的n-1到n-6的值   
	 * 其中使用flag对两个数组进行切换  且要保证每次进行切换时数据都经过了清零操作
	 * 
	 * 程序中使用maxValue这一变量来存储骰子面数6,是为了当其他厂家使用其他点数的骰子时,
	 * 只需要更改maxValue的值即可,对程序的扩展性极好
	 * */
	public static void main(String[] args) {
		// TODO Auto-generated method stub

		No60PrintProbability p = new No60PrintProbability();
		int n = 6;//骰子个数
		
		System.out.println("打印出所有的骰子点数的概率(骰子点数及其出现的概率)");
		p.PrintProbability(n);
	}

	int maxValue = 6;
	private void PrintProbability(int n) {
		// TODO Auto-generated method stub
		if(n < 1)
			return;
		
		int[][] pProbability = new int[2][maxValue*n+1];
		for(int i = 0; i < maxValue*n+1;i++) {
			pProbability[0][i] = 0;
			pProbability[1][i] = 0;
			
		}
		
		int flag = 0;
		//第一次循环
		for(int i = 1;i < maxValue;i++)
			pProbability[0][i] = 1;
		
		//第2-n次循环
		for(int k = 2;k < n;k++) {
			for(int i = 0;i < k;i++)
				pProbability[1-flag][i] = 0;
			for(int i = k; i <= maxValue*k;i++) {
				pProbability[1-flag][i] = 0;
				for(int j = 1;j <= i && j <= maxValue;++j)
					//也就是【i】 = 【j】 + 【i-j】
					pProbability[1-flag][i] += pProbability[flag][i-j];
			}
			flag = 1 - flag;
		}
		
		//算出概率并打印
		//Java中使用Math类的pow方法: 
		//public static double pow(double a,double b),
		//返回第一个参数的第二个参数次幂的值
		double total = Math.pow((double)maxValue,n);
		for(int i = n;i <= maxValue*n;i++) {
			System.out.println(total);
			double ratio = (double)pProbability[flag][i]/total;
			System.out.println(i+":"+ratio);
		}
		
	}

}

 * 面试题61:扑克牌中的顺子


     * 题目:从扑克牌中随机抽抽出5张牌,判断是不是一个顺子,
     * 即这5张牌是不是连续的。
     * 2~10为数字本身,A为1,J为11,Q为12,K为13,而大小王可以看成任意数字
     * 
     * 思路:
     * 1>首先对数组进行排序
     * 2>计算出0的个数
     * 3>计算出数与数之间的间隔数  其中若两个值相等 ,即含有对子,则一定是不连续的
     * 判断间隔数是否大于o的个数 若大于 则不连续 否则连续
     * 
     * 这里排序算法使用的是java自带的Arrays.sort函数,不必考虑时间复杂度的问题
     * 这是因为:通常认为不同级别的时间复杂度只有当n足够大的时候才是有意义的
     * 本题中数组的长度固定,只有五张牌吧,所以时间复杂度上不会有太大的区别呢,
     * 所以使用简洁易懂的方法来实现算法就可以

package Test;

import java.util.Arrays;

public class No61IsContinuous {

	/*
	 * 面试题61:扑克牌中的顺子
	 * 题目:从扑克牌中随机抽抽出5张牌,判断是不是一个顺子,
	 * 即这5张牌是不是连续的。
	 * 2~10为数字本身,A为1,J为11,Q为12,K为13,而大小王可以看成任意数字
	 * 
	 * 思路:
	 * 1>首先对数组进行排序
	 * 2>计算出0的个数
	 * 3>计算出数与数之间的间隔数  其中若两个值相等 ,即含有对子,则一定是不连续的
	 * 判断间隔数是否大于o的个数 若大于 则不连续 否则连续
	 * 
	 * 这里排序算法使用的是java自带的Arrays.sort函数,不必考虑时间复杂度的问题
	 * 这是因为:通常认为不同级别的时间复杂度只有当n足够大的时候才是有意义的
	 * 本题中数组的长度固定,只有五张牌吧,所以时间复杂度上不会有太大的区别呢,
	 * 所以使用简洁易懂的方法来实现算法就可以
	 * 
	 * 
	 * */
	public static void main(String[] args) {
		// TODO Auto-generated method stub

		No61IsContinuous c = new No61IsContinuous();
		int[] numbers = {1,3,4,0,5};
		if(c.IsContinuous(numbers))
			System.out.println("随机抽取的这五张扑克牌是顺子!");
		else
			System.out.println("随机抽取的这五张扑克牌不是顺子!!");
	}

	private boolean IsContinuous(int[] numbers) {
		// TODO Auto-generated method stub
		
		if(numbers == null || numbers.length== 0)
			return false;
		
		//对数组进行排序
		Arrays.sort(numbers);
		
		int numberOf0 = 0;
		int numberOfGap = 0;
		
		//计算出0的个数   对已经排序的数组进行统计  0全部在前面呢
		for(int i = 0;i < numbers.length&&numbers[i] == 0;i++)
				numberOf0++;
		
		//计算出间隔数  
		int small = numberOf0;//第一个非0的数
		int big =  small+1;
		
		while(big < numbers.length) {
			if(numbers[small] == numbers[big])
				//相等表示有对子  则不连续
				return false;
			numberOfGap += numbers[big]-numbers[small]-1;
			small ++;
			big ++;
		}
		return (numberOfGap > numberOf0)?false:true;
	}

}

 * 面试题62:圆圈中最后剩下的数字


     * 题目:0,1,2,......,n-1这n个数字排成一个圆圈,从数字0开始,
     * 每次从这个圆圈里删除第m个数字。     * 求出这个圆圈里最后剩下的最后一个数字。
     * 例如:0,1,2,3,4这5个数字组成一个源泉,从数字0开始每次删除3个数字,
     * 则删除的前4个数字依次是2,0,4,1,因此最后剩下的数字是3
     * 
     * 
     * 思路:每次被删除的数字都有一定的规律  就可以直接计算出最后剩下的数字
     * 关系是:f(n) = 0                          当n = 0时 
     *         f(n) = [f(n-1,m)+m]%n    当n > 0时
     * 
     * 也就是要想得到n个数字的序列中最后剩下的数字,
     * 就要得到n-1个数字的序列中最后剩下的数字,以此类推
     * 可使用循环或递归  循环更加简单易懂

package Test;

public class No62LastRemaining {

	/*
	 * 面试题62:圆圈中最后剩下的数字
	 * 题目:0,1,2,......,n-1这n个数字排成一个圆圈,从数字0开始,
	 * 每次从这个圆圈里删除第m个数字。	 * 求出这个圆圈里最后剩下的最后一个数字。
	 * 例如:0,1,2,3,4这5个数字组成一个源泉,从数字0开始每次删除3个数字,
	 * 则删除的前4个数字依次是2,0,4,1,因此最后剩下的数字是3
	 * 
	 * 
	 * 思路:每次被删除的数字都有一定的规律  就可以直接计算出最后剩下的数字
	 * 关系是:f(n) = 0                          当n = 0时 
	 * 		f(n) = [f(n-1,m)+m]%n	当n > 0时
	 * 
	 * 也就是要想得到n个数字的序列中最后剩下的数字,
	 * 就要得到n-1个数字的序列中最后剩下的数字,以此类推
	 * 可使用循环或递归  循环更加简单易懂
	 * */

    public static void main(String[] args) {

       No62LastRemaining n = new No62LastRemaining();

       System.out.println(n.LastRemaining(5, 3));

    }
    
    public int LastRemaining(int n, int m) {

        if (n < 1 || m < 1) {

            return -1;

        }
        //i个数字的序列中最后剩下的数字
        int last = 0;  //当n = 0时

        //当n!=0时    则对其进行+m  再对i(当前在含有i个数字的序列)取余
        //循环执行到最后一个即可  last就是所求
        for (int i = 2; i <= n; i++) 
            last = (last + m) % i;

        return last;

     }

}



猜你喜欢

转载自blog.csdn.net/weixin_43137176/article/details/89847715