# 这样玩算法才够酷

## 算法题

### 1~n整数中1出现的次数

#### 分析

``````public static int numberOf1Between1AndN(int num) {
if (num < 1) {
return 0;
}
int count = 0;
for (int i = 1; i <= num; i++) {
count += numberOf1(i);
}
return count;
}
private static int numberOf1(int num) {
int count = 0;
while (num != 0) {
if (num % 10 == 1) {
count++;
}
num /= 10;
}
return count;
}

``````public static int numberOf1Between1AndN(int num) {
if (num < 1) {
return 0;
}
StringBuilder sb = new StringBuilder();
for (int i = 1; i <= num; i++) {
sb.append(i);
}
int count = 0;
for (int i = 0, n = sb.length(); i < n; i++) {
if (sb.charAt(i) == '1') {
count++;
}
}
return count;
}

``````public static int numberOf1Between1AndN(int num) {
if (num < 1) {
return 0;
}
int len = numberOfLen(num);//得到位数
if (len == 1) {
return 1;//如果只有1位，那必然是1，也就说只有一个
}
int pow = (int) Math.pow(10, len - 1);//存储起来，避免重复计算
int maxDigit = num / pow;//最高位数字
int maxDigitCount = maxDigit == 1 ? num % pow + 1 : pow;//统计最高位为1的情况
int otherDigitCount = maxDigit * (len - 1) * (pow / 10);//统计剩余位为1的情况
return maxDigitCount + otherDigitCount + numberOf1Between1AndN(num % pow);
}
private static int numberOfLen(int num) {
int len = 0;
while (num != 0) {
len++;
num /= 10;
}
return len;
}

### n个骰子的点数

#### 分析

``````private static final int MAX_VALUE = 6;
public static void printProbabilityOf2() {
int number = 2;
int maxSum = number * MAX_VALUE;
int[] pProbabilities = new int[maxSum - number + 1];
//初始化，开始统计之前都为0次
for (int i = number; i <= maxSum; i++) {
pProbabilities[i - number] = 0;
}
int total = (int) Math.pow(MAX_VALUE, number);
for (int i = 1; i <= MAX_VALUE; i++) {
for (int j = 1; j <= MAX_VALUE; j++) {
pProbabilities[i + j - number]++;
}
}
for (int i = number; i <= maxSum; i++) {
System.out.println(String.format(Locale.getDefault(), "s的值为%d，概率为%d/%d=%.4f", i, pProbabilities[i - number], total, pProbabilities[i - number] * 1.0f / total));
}
}

``````private static final int MAX_VALUE = 6;
public static void printProbability(int number) {
if (number < 1) {
return;
}
int maxSum = number * MAX_VALUE;//点数和最大值6n
int[] pProbabilities = new int[maxSum - number + 1];//存储每一种可能的数组
//初始化，开始统计之前都为0次
for (int i = number; i <= maxSum; i++) {
pProbabilities[i - number] = 0;
}
int total = (int) Math.pow(MAX_VALUE, number);//情况种数6^n
probability(number, pProbabilities);//计算n~6n每种情况出现的次数并存储在pProbabilities中
for (int i = number; i <= maxSum; i++) {
System.out.println(String.format(Locale.getDefault(), "s的值为%d，概率为%d/%d=%.4f", i, pProbabilities[i - number], total, pProbabilities[i - number] * 1.0f / total));
}
}
public static void probability(int number, int[] pProbabilities) {
for (int i = 1; i <= MAX_VALUE; i++) {//从第一个骰子开始
probability(number, 1, i, pProbabilities);
}
}
/**
* 不停递归每条路线直到扔完所有骰子
*
* @param original       总共骰子数
* @param current        当前扔的骰子
* @param sum            每条路线计算和
* @param pProbabilities 存储数组
*/
public static void probability(int original, int current, int sum, int[] pProbabilities) {
if (current == original) {
pProbabilities[sum - original]++;
} else {
for (int i = 1; i <= MAX_VALUE; i++) {
probability(original, current + 1, sum + i, pProbabilities);
}
}
}

``````pProbabilities[i + j - number]++;

i+j其实就是每种情况的和，而我们这里用sum来计算每条路线的和，理解这一点，那么就很简单了。但递归有着显著的缺点，递归由于是方法调用自身，而方法调用时有时间和空间的消耗的：每一次方法调用，都需要在内存栈中分配空间以保存参数、返回地址及临时变量，而且往栈里压入数据和弹出数据都需要时间，另外递归中有可能很多计算都是重复的，从而对性能带来很大的负面影响。其实我内心是崩溃的，你倒是写出来啊（个人认为递归本身是一种很牛B的思路，但计算机不给力）。反正就这道题，我第一次做的时候没想到递归，直接想到的是循环，废话不多说，直接分析下循环是怎么做到的。

``````private static final int MAX_VALUE = 6;
public static void printProbability(int number) {
if (number < 1) {
return;
}
int[][] pProbabilities = new int[2][MAX_VALUE * number + 1];
for (int i = 0; i < MAX_VALUE * number + 1; i++) {//初始化数组
pProbabilities[0][i] = 0;
pProbabilities[1][i] = 0;
}
int flag = 0;
for (int i = 1; i <= MAX_VALUE; i++) {//当第一次抛掷骰子时，有6种可能，每种可能出现一次
pProbabilities[flag][i] = 1;
}
//从第二次开始掷骰子
for (int k = 2; k <= number; k++) {
for (int i = 0; i < k; i++) {//不可能发生的为0
pProbabilities[1 - flag][i] = 0;
}
for (int i = k; i <= MAX_VALUE * k; i++) {//第k次掷骰子，和最小为k，最大为MAX_VALUE*k
pProbabilities[1 - flag][i] = 0;//重置
for (int j = 1; j <= i && j <= MAX_VALUE; j++) {//执行f(k,n)=f(k-1,n-1)+f(k-1,n-2)+f(k-1,n-3)+f(k-1,n-4)+f(k-1,n-5)+f(k-1,n-6)
pProbabilities[1 - flag][i] += pProbabilities[flag][i - j];
}
}
flag = 1 - flag;//切换数组，保证打印的为最新数组，计算的为上一次计算所得数组
}
int total = (int) Math.pow(MAX_VALUE, number);
for (int i = number; i <= MAX_VALUE * number; i++) {
System.out.println(String.format(Locale.getDefault(), "s的值为%d，概率为%d/%d=%.4f", i, pProbabilities[flag][i], total, pProbabilities[flag][i] * 1.0f / total));
}
}

### 圆圈中最后剩下的数字

#### 题目

0，1，···，n-1这n个数字排成一个圆圈，从数字0开始，每次从这圆圈里删除第m个数字。求出这圆圈里剩下的最后一个数字。

#### 分析

``````/**
* @param totalNum 总人数
* @param m        报号数字
*/
public static void yueSeFu(int totalNum, int m) {
// 初始化人数
List<Integer> start = new ArrayList<Integer>();
for (int i = 0; i < totalNum; i++) {
}
//从第k个开始计数，也就是说从1开始报数
int k = 0;
int size;
while ((size = start.size()) > 0) {
k = k + m;
//第m人的索引位置
k = k % size - 1;
if (k < 0) {// 判断是否是最后位置
System.out.println(start.get(size - 1));
start.remove(size - 1);
k = 0;//删除最后一个后重新开始
} else {
System.out.println(start.get(k));
start.remove(k);
}
}
}

``````k+1 -> 0
k+2 -> 1
...
n-1 -> n-k-2
0   -> n-k-1
1   -> n-k
...
k-1 -> n-2

``````/**
* @param totalNum 总人数
* @param m        报号数字
*/
public static void yueSeFu(int totalNum, int m) {
if (totalNum < 1 || m < 1) {
throw new RuntimeException("这游戏至少一个人玩！");
}
int last = 0;
for (int i = 2; i <= totalNum; i++) {
last = (last + m) % i;
}
System.out.println("最后出局的是：" + last);
}

## 题后骚话

• 求1+2+···+n，要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。
• 写一个方法，求两个整数之和，要求在方法体内不得使用+、-、*、/四则运算符号。

## 传送门

Github：github.com/crazysunj/

Android