面试题60:n个骰子的点数
题目:把n个骰子扔在地上,所有骰子朝上一面的点数之和为s。输入n,打印出s的所有可能的值出现的概率。
- 算法:循环
- 用两个数组保存骰子总数出现的次数
- 第一个数组保存n个骰子时总和频率
- 第二个数组保存n+1个骰子时总和频率
- 用flag变量交换数组使用上一个骰子迭代计算下一个骰子
- fn(n) = fn-1(n-1) + fn-1(n-2) + fn-1(n-3) + fn-1(n-4) + fn-1(n-5) + fn-1(n-6)
public double[] twoSum(int n) {
if (n <= 0) {
throw new RuntimeException("Invalid arguments");
}
int maxValue = 6;
int[][] count = new int[2][n*maxValue+1];
int flag = 0;
for (int i = 1; i <= maxValue; i++) {
count[flag][i] = 1;
}
for (int k = 2; k <= n; k++) { // k个骰子
for (int i = 1; i < k; i++) { // k个骰子和至少为k,所以小于k的和频率是0
count[1-flag][i] = 0;
}
for (int i = k; i <= k * maxValue; i++) { // k个骰子的和
count[1-flag][i] = 0;
for (int j = 1; j <= maxValue && j < i; j++) { // 使用k-1个骰子计算k个骰子
count[1-flag][i] += count[flag][i-j];
}
}
flag ^= 1;
}
double[] result = new double[n*maxValue-n+1];
double total = Math.pow(maxValue, n);
for (int i = 0; i < n*maxValue-n+1; i++) {
result[i] = count[flag][i+n] / total;
}
return result;
} - 算法:递归
- 类似全排列递归,递归到最后一个骰子终止
public double[] twoSum(int n) {
if (n <= 0) {
throw new RuntimeException("Invalid arguments");
}
int maxValue = 6;
int[] count = new int[n*maxValue-n+1];
for (int i = 1; i <= maxValue; i++) {
countFrequency(n, n-1, maxValue, i, count);
}
double[] result = new double[n*maxValue-n+1];
double total = Math.pow(maxValue, n);
for (int i = 0; i < n*maxValue-n+1; i++) {
result[i] = count[i] / total;
}
return result;
}
// 递归函数的含义:总共original个骰子,现还剩current个骰子未计入和,当前达到的和是sum
public void countFrequency(int original, int current, int maxValue, int sum, int[] count) {
if (current == 0) {
count[sum-original]++;
} else {
for (int i = 1; i <= maxValue; i++) {
countFrequency(original, current-1, maxValue, sum+i, count);
}
}
} 面试题61:扑克牌的顺子
题目:从扑克牌中随机抽5张牌,判断是不是一个顺子,即这5张牌是不是连续的。2~10为数字本身,A为1,J为11,Q为12,K为13,而大、小王可以看成任意数字。
- 算法:
- 1.排序
- 2.统计0也就是大小王的个数
- 3.统计间隔的个数
- 4.判断
public boolean isStraight(int[] nums) {
checkArguments(nums);
countSort(nums);
int numberOfZero = 0;
int numberOfGap = 0;
for (int x : nums) {
numberOfZero += ((x == 0) ? 1 : 0);
}
for (int i = numberOfZero+1; i < nums.length; i++) {
if (nums[i] == nums[i-1]) { // 对子
return false;
}
numberOfGap += (nums[i] - nums[i-1] - 1);
}
return numberOfZero >= numberOfGap;
}
public void countSort(int[] nums) {
int[] count = new int[14];
for (int x : nums) {
count[x]++;
}
for (int i = 0, k = 0; i < 14; i++) {
while (count[i] != 0) {
nums[k++] = i;
count[i]--;
}
}
} 面试题62:圆圈中最后剩下的数字
题目:0, 1, …, n-1这n个数字排成一个圆圈,从数字0开始每次从这个圆圈里删除第m个数字。求出这个圆圈里剩下的最后一个数字。
- 算法:闭公式
1. 第一个被删除的数字: k = (m-1)%n
2. 删除之后: 0 1 2 ... n -> 0 1 2 ... k-1 k+1 ... n
3. 整理序列下次删除从k+1开始计数: k+1 ... n-1 0 1 ... k-1
4. 有如下映射和逆映射 p(x) = (x-k-1) % n
k+1 ... n-1 0 1 ... k-1 <====================> 0 ... n-k-2 n-k-1 n-k ... n-2
p-1(x) = (x+k+1) % n
5. 推导:
f(n, m) = f'(n-1, m) = p-1[f(n-1, m)] = [f(n-1, m)+k+1] % n = [f(n-1, m) + m] % n
f'(n-1, m)表示从删除数字计数下一个要删除的数字的序号
f(n-1, m)表示从零计数下一个要删除的数字的序号,经过逆映射就是f'(n-1, m)原序号
6. 递推公式
f(n, m) = 0, n = 1
= [f(n-1, m)+m] % n, n > 1public int lastRemaining(int n, int m) {
if (n <= 0 || m <= 0) {
return -1;
}
int last = 0;
for (int i = 2; i <= n; i++) {
last = (last + m) % i;
}
return last;
} - 算法:
- Java迭代器
public int lastRemaining(int n, int m) {
if (n <= 0 || m <= 0) {
return -1;
}
LinkedList<Integer> list = new LinkedList<Integer>();
for (int i = 0; i < n; i++) {
list.add(i);
}
Iterator<Integer> it = list.iterator();
while (list.size() > 1) {
int i = 0;
for (; i < m; i++) {
it.next();
if (!it.hasNext()) {
if (i == m - 1) { // 当迭代器需要删除的刚好是最后一个结点时,就删除然后跳出循环从头开始
it.remove();
break;
}
it = list.iterator();
}
}
if (i == m) {
it.remove();
} else {
it = list.iterator();
}
}
return list.peekFirst();
} - 算法:
- 自定义环形链表
public int lastRemaining(int n, int m) {
if (n <= 0 || m <= 0) {
return -1;
}
class ListNode {
int val;
ListNode next;
ListNode(int x) { val = x; }
}
ListNode head = new ListNode(0);
ListNode prev = head;
for (int i = 1; i < n; i++) {
prev.next = new ListNode(i);
prev = prev.next;
}
prev.next = head;
while (head.next != head) {
for (int i = 1; i < m; i++) {
head = head.next;
prev = prev.next;
}
prev.next = head.next;
head = head.next;
}
return head.val;
} 面试题63:股票的最大利润
题目:假设把某股票的价格按照时间先后顺序存储在数组中,请问买卖交易该股票可能获得的利润是多少?例如一只股票在某些时间节点的价格为{9, 11, 8, 5, 7, 12, 16, 14}。如果我们能在价格为5的时候买入并在价格为16时卖出,则能收获最大的利润11。
- 算法:
- 维护变量min保存前i-1个数中最小值
- 当前值减去min即是当前值卖出的最大利润
public int maxProfit(int[] prices) {
if (prices == null) {
return -1;
}
if (prices.length <= 1) {
return 0;
}
int min = prices[0];
int maxProfit = prices[1] - min;
for (int i = 2; i < prices.length; i++) {
if (prices[i-1] < min) {
min = prices[i-1];
}
maxProfit = (maxProfit > prices[i]-min) ? maxProfit : (prices[i]-min);
}
return maxProfit > 0 ? maxProfit : 0;
} - 算法:Kadane算法
- 最大连续子数组问题的变种
- 原数组[a1, a2, a3, a4, a5]
- 新数组[0, a2-a1, a3-a2, a4-a3, a5-a4]
- 那么找原数组中最大差就变成了找新数组中最大连续子数组和
- 最大连续子数组问题
public int maxProfit(int[] prices) {
// maxCur表示以当前值结尾的最大值
// maxSoFar表示已经发现的最大值
int maxCur = 0, maxSoFar = 0;
for(int i = 1; i < prices.length; i++) {
maxCur = Math.max(0, maxCur + (prices[i] - prices[i-1]));
maxSoFar = Math.max(maxCur, maxSoFar);
}
return maxSoFar;
} 

京公网安备 11010502036488号