数据结构---抽象建模
2015-09-05 21:50
435 查看
1、n个骰子的点数(剑指offer--43)
题目:把n个骰子扔在地上,所有骰子朝上一面的点数之和为S。输入n,打印出S的所有可能的值出现的概率。
首先解决前提性的问题:一个骰子的点数只可能是[1,6],所以S的值的取值范围是[n,6n],这里当然只考虑整数。
思路一:统计各个S值出现的次数,然后各个S值出现的概率 = 各个S值出现的次数 / n个骰子所有点数的排列数。其中,n个骰子所有点数的排列数等于6n,而各个S值出现的次数就需要建立一个数组来进行统计。这时,问题就变成怎样来求各个S出现的次数了。
要求出n个骰子的点数和,我们可以先把n个骰子分为两堆:第一堆只有一个,另一个有n-1个。单独的那一个有可能出现从1到6的点数。我们需要计算从1到6的每一种点数和剩下的n-1个骰子来计算点数和。接下来把剩下的n-1个骰子还是分成两堆,第一堆只有一个,第二堆有n-2个。我们把上一轮那个单独骰子的点数和这一轮单独骰子的点数相加,再和剩下的n-2个骰子来计算点数和。分析到这里,我们不难发现,这是一种递归的思路。递归结束的条件就是最后只剩下一个骰子了。
2、扑克牌的顺子(剑指offer--44)
题目:从扑克牌中随机抽5张牌,判断是不是一个顺子,即这5张牌是不是连续的。2-10为数字本身,A为1,J为11,Q为12,K为13,而大小王可以看成任意数字。
思路:可以将这5张牌排个序,然后统计出0的个数以及非0数字之间的间隔数,如果出现重复的非0数字,那么不是顺子。如果间隔数小于等于0的个数,那么是顺子。
3、圆圈中最后剩下的数字(剑指offer--45)
题目:n个数字(0,1,…,n-1)形成一个圆圈,从数字0开始,每次从这个圆圈中删除第m个数字(第一个为当前数字本身,第二个为当前数字的下一个数字)。当一个数字删除后,从被删除数字的下一个继续删除第m个数字。求出在这个圆圈中剩下的最后一个数字。
分析1:很容易想到用循环链表。我们可以创建一个总共有n个数字的循环链表(不带头节点),然后每次从这个列表中删除第m个元素,注意删除节点是头结点的特殊情况,直到链表中只剩下最后一个数。
分析2:找规律。(分析详见剑指offer)
定义最初的n个数字(0,1,…,n-1)中最后剩下的数字是关于n和m的方程为f(n,m)
4、不用加减乘除做加法(剑指offer--47)
做加法不用四则运算?那么用什么呢?只剩下位运算了。
基本思路是这样的:考虑二进制加法的过程,
步骤一、A^B,能够得到没有进位的加法。
步骤二、A&B,能够得到相加之后,能够进位的位置的信息。向左移动一位,就是两个二进制数相加之后的进位信息。所以,(A&B)<<1就是两个二进制数相加得到的“进位结果”。
步骤三、将前两步的结果相加。相加的过程就是步骤一和步骤二,直到不再产生进位为止。
题目:把n个骰子扔在地上,所有骰子朝上一面的点数之和为S。输入n,打印出S的所有可能的值出现的概率。
首先解决前提性的问题:一个骰子的点数只可能是[1,6],所以S的值的取值范围是[n,6n],这里当然只考虑整数。
思路一:统计各个S值出现的次数,然后各个S值出现的概率 = 各个S值出现的次数 / n个骰子所有点数的排列数。其中,n个骰子所有点数的排列数等于6n,而各个S值出现的次数就需要建立一个数组来进行统计。这时,问题就变成怎样来求各个S出现的次数了。
要求出n个骰子的点数和,我们可以先把n个骰子分为两堆:第一堆只有一个,另一个有n-1个。单独的那一个有可能出现从1到6的点数。我们需要计算从1到6的每一种点数和剩下的n-1个骰子来计算点数和。接下来把剩下的n-1个骰子还是分成两堆,第一堆只有一个,第二堆有n-2个。我们把上一轮那个单独骰子的点数和这一轮单独骰子的点数相加,再和剩下的n-2个骰子来计算点数和。分析到这里,我们不难发现,这是一种递归的思路。递归结束的条件就是最后只剩下一个骰子了。
// 骰子最大点数 public static int g_maxValue = 6; // n个骰子 public static void PrintSumProbabilityOfDices(int n) { if (n < 1) return; // 初始化存放骰子的数组 int maxSum = n * g_maxValue; int[] p = new int[maxSum - n + 1]; for (int i = 0; i <= p.length - 1; ++i) p[i] = 0; SumProbabilityOfDices(n, p); // 打印概率结果 int total = (int) Math.pow(g_maxValue, n); for (int i = 0; i <= p.length - 1; ++i) { float ratio = (float) p[i] / total; System.out.println((n + i) + ":" + p[i] + "/" + total); } } // 计算每个和S出现的次数 public static void SumProbabilityOfDices(int n, int[] p) { for (int i = 1; i <= g_maxValue; ++i) SumProbabilityOfDices(n, n, i, 0, p); } //original---骰子个数n //current---当前骰子 //value---当前筛子点数 //tempSum---当前骰子前面骰子点数总和 //pProbabilities---存放骰子和的数组 public static void SumProbabilityOfDices(int original, int current, int value, int tempSum, int[] pProbabilities) { if (current == 1) { int sum = value + tempSum; pProbabilities[sum - original]++; } else { for (int i = 1; i <= g_maxValue; ++i) { int sum = value + tempSum; SumProbabilityOfDices(original, current - 1, i, sum, pProbabilities); } } }
2、扑克牌的顺子(剑指offer--44)
题目:从扑克牌中随机抽5张牌,判断是不是一个顺子,即这5张牌是不是连续的。2-10为数字本身,A为1,J为11,Q为12,K为13,而大小王可以看成任意数字。
思路:可以将这5张牌排个序,然后统计出0的个数以及非0数字之间的间隔数,如果出现重复的非0数字,那么不是顺子。如果间隔数小于等于0的个数,那么是顺子。
//函数功能 : 从扑克牌中随机抽5张牌,判断是不是一个顺子 //函数参数 : pCards为牌,nLen为牌的张数 //返回值 : 是否顺子 public static boolean IsContinuous(int []pCards, int nLen) { if(pCards == null || nLen <= 0) return false; sort(pCards); //排序算法 int i; int zeroCount = 0; //大小王用0表示 int capCount = 0; //间隔数 //统计0的个数 for(i = 0; i < nLen; i++) { if(pCards[i] == 0) zeroCount++; else break; } //统计间隔数 int preCard = pCards[i]; for(i = i + 1; i < nLen; i++) { int curCard = pCards[i]; if(preCard == curCard) //与前一张牌比较 return false; else capCount += curCard - preCard - 1; //累加间隔数 preCard = curCard; } return (zeroCount >= capCount)? true: false; //只要王的个数大于间隔数 }
3、圆圈中最后剩下的数字(剑指offer--45)
题目:n个数字(0,1,…,n-1)形成一个圆圈,从数字0开始,每次从这个圆圈中删除第m个数字(第一个为当前数字本身,第二个为当前数字的下一个数字)。当一个数字删除后,从被删除数字的下一个继续删除第m个数字。求出在这个圆圈中剩下的最后一个数字。
分析1:很容易想到用循环链表。我们可以创建一个总共有n个数字的循环链表(不带头节点),然后每次从这个列表中删除第m个元素,注意删除节点是头结点的特殊情况,直到链表中只剩下最后一个数。
static class LNode { protected int data; protected LNode next; public LNode(int data) { this.data = data; } } // 构建约瑟夫环 public static LNode createLink(int[] data) { if (data == null) return null; LNode first = new LNode(data[0]); LNode p = first; for (int i = 1; i < data.length; i++) { LNode node = new LNode(data[i]); p.next = node; p = node; } p.next = first; return first; } //删除当前结点 public static LNode delete(LNode p) { LNode q = p.next; p.next = q.next; p.data=q.data; q.next=null; return p; } // n个数 // 每次删除第M个数 public static int lastRemaining(int n, int m) { if (n < 0 || m < 0) return -1; if (m == 1) return n - 1; // 构造约瑟夫环 int arr[] = new int ; for (int i = 0; i < n; i++) { arr[i] = i; } LNode first = createLink(arr); LNode p = first; int tempM ; for (int i = 0; i < n-1 ; i++) { tempM = m; while (tempM > 1) { p = p.next; tempM--; } p = delete(p); } return p.data; }
分析2:找规律。(分析详见剑指offer)
定义最初的n个数字(0,1,…,n-1)中最后剩下的数字是关于n和m的方程为f(n,m)
public static int Joseph(int n, int m) { if (n < 0 || m < 0) return -1; int fn = 0; for (int i = 2; i <= n; i++) { fn = (fn + m) % i; } return fn; }
4、不用加减乘除做加法(剑指offer--47)
做加法不用四则运算?那么用什么呢?只剩下位运算了。
基本思路是这样的:考虑二进制加法的过程,
步骤一、A^B,能够得到没有进位的加法。
步骤二、A&B,能够得到相加之后,能够进位的位置的信息。向左移动一位,就是两个二进制数相加之后的进位信息。所以,(A&B)<<1就是两个二进制数相加得到的“进位结果”。
步骤三、将前两步的结果相加。相加的过程就是步骤一和步骤二,直到不再产生进位为止。
public static int func(int a, int b) { int sum; int carry; int num1 = a; int num2 = b; do { sum = num1 ^ num2; carry = (num1 & num2) << 1; num1 = sum; num2 = carry; } while (carry != 0); return sum; }