您的位置:首页 > 理论基础 > 数据结构算法

数据结构---抽象建模

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个骰子来计算点数和。分析到这里,我们不难发现,这是一种递归的思路。递归结束的条件就是最后只剩下一个骰子了。

// 骰子最大点数
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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: